diff --git a/src/configs/time.ts b/src/configs/time.ts index 1524357..41f97bf 100644 --- a/src/configs/time.ts +++ b/src/configs/time.ts @@ -19,7 +19,7 @@ import { Caveat, expirationSatisfier } from 'lsat-js' * amount paid */ const getTimeCaveat: CaveatGetter = ( - _req: Request, + req: Request, invoice: InvoiceResponse ): string => { const amount = @@ -28,10 +28,21 @@ const getTimeCaveat: CaveatGetter = ( : invoice.amount // amount is in satoshis which is equal to the amount of seconds paid for - const milli: number = amount * 1000 + let time + + if (req.boltwallConfig && req.boltwallConfig.rate) { + // rate is expected to be in satoshis per second + const rate = req.boltwallConfig.rate + const seconds = amount / rate + time = Date.now() + seconds * 1000 + } else { + const milli: number = amount * 1000 + time = Date.now() + milli + } // add 200 milliseconds of "free time" as a buffer - const time = Date.now() + milli + 200 + time += 200 + const caveat = new Caveat({ condition: 'expiration', value: time.toString() }) return caveat.encode() } diff --git a/src/routes/paywall.ts b/src/routes/paywall.ts index f2e094e..1accf08 100644 --- a/src/routes/paywall.ts +++ b/src/routes/paywall.ts @@ -75,7 +75,7 @@ export default async function paywall( // challenge caveats should already have been verified at this point for oauth // so we can just continue to the paywall as all remaining checks are against our node if (req?.boltwallConfig?.oauth) { - if (!lsat.isSatisfied()) req.logger.warning(`LSAT submitted to oauth server from ${req.ip} that is not satisfied with preimage`) + if (!lsat.isSatisfied()) req.logger.warning(`LSAT submitted to oauth server from ${req.ip} that is not satisfied with preimage but has valid signature.`) return next() } diff --git a/src/server.ts b/src/server.ts index b5f8d6d..fe799ad 100644 --- a/src/server.ts +++ b/src/server.ts @@ -48,12 +48,14 @@ const { BOLTWALL_OAUTH, BOLTWALL_HODL, BOLTWALL_MIN_AMOUNT, + BOLTWALL_RATE, } = process.env let options: BoltwallConfig = {} if (TIME_CAVEAT) options = TIME_CAVEAT_CONFIGS if (ORIGIN_CAVEAT) options = ORIGIN_CAVEAT_CONFIGS if (BOLTWALL_OAUTH) options.oauth = true if (BOLTWALL_HODL) options.hodl = true +if (BOLTWALL_RATE) options.rate = +BOLTWALL_RATE if (BOLTWALL_MIN_AMOUNT) options.minAmount = BOLTWALL_MIN_AMOUNT app.use(boltwall(options)) diff --git a/src/typings/configs.d.ts b/src/typings/configs.d.ts index d5a0042..13b2765 100644 --- a/src/typings/configs.d.ts +++ b/src/typings/configs.d.ts @@ -35,4 +35,5 @@ export interface BoltwallConfig { minAmount?: string | number hodl?: boolean oauth?: boolean + rate?: number } diff --git a/tests/configs.spec.ts b/tests/configs.spec.ts index c183de9..98832b7 100644 --- a/tests/configs.spec.ts +++ b/tests/configs.spec.ts @@ -16,6 +16,7 @@ describe('configs', () => { ? config.caveatSatisfiers[0] : config.caveatSatisfiers }) + it('should create valid caveat that expires after x seconds, where "x" is number satoshis paid', () => { if (!config.getCaveats) throw new Error('Expected to have a getCaveats property') @@ -47,6 +48,48 @@ describe('configs', () => { expect(value).to.be.lessThan(now + time + amount) }) + it('should support custom rates for adding expiration caveat', () => { + if (!config.getCaveats) + throw new Error('Expected to have a getCaveats property') + // rate is calculated as number of seconds per satoshi + // testing a value that would give us 1 month for 20k sats + const seconds = 60 * 60 * 24 * 30 // 1 month + const sats = 20000 // 20k sats + const rate = (sats / seconds).toFixed(5) + const req = { + boltwallConfig: { + rate, + }, + } + const now = Date.now() + + // make typescript happy since this could be an array + const getCaveats = Array.isArray(config.getCaveats) + ? config.getCaveats[0] + : config.getCaveats + + const result: string = getCaveats( + (req as unknown) as Request, + { + amount: sats, + } as InvoiceResponse + ) + const convertCaveat = (): Caveat => Caveat.decode(result) + const caveat = Caveat.decode(result) + const value: number = +caveat.value + + // convert difference from ms + const actualSeconds = (value - now) / 1000 + + expect(convertCaveat).to.not.throw() + expect(value).to.be.greaterThan(now) + expect(actualSeconds).to.be.approximately( + seconds, + 2500, + `Expected expiration to be approximately ${seconds}ms from now` + ) + }) + it('should return the expected invoice description', () => { const { getInvoiceDescription } = config if (!getInvoiceDescription)