diff --git a/modules/sdk-core/src/coins/ofcToken.ts b/modules/sdk-core/src/coins/ofcToken.ts index 84ffbb4406..8769635f75 100644 --- a/modules/sdk-core/src/coins/ofcToken.ts +++ b/modules/sdk-core/src/coins/ofcToken.ts @@ -8,7 +8,10 @@ import { CoinConstructor, SignTransactionOptions as BaseSignTransactionOptions, SignedTransaction, + ITransactionRecipient, } from '../'; +import { isBolt11Invoice, LIGHTNING_INVOICE } from '../lightning'; + import { Ofc } from './ofc'; export interface SignTransactionOptions extends BaseSignTransactionOptions { @@ -21,6 +24,7 @@ export interface SignTransactionOptions extends BaseSignTransactionOptions { export { OfcTokenConfig }; const publicIdRegex = /^[a-f\d]{32}$/i; + export class OfcToken extends Ofc { public readonly tokenConfig: OfcTokenConfig; @@ -65,6 +69,37 @@ export class OfcToken extends Ofc { return this.tokenConfig.type; } + checkRecipient(recipient: ITransactionRecipient): void { + if (isBolt11Invoice(recipient.address)) { + // should throw error if this isnt bitcoin (mainnet or testnet) + if (this.backingCoin !== 'btc' && this.backingCoin !== 'tbtc') { + throw new Error(`invalid argument - lightning invoice is only supported for bitcoin`); + } + + // amount for bolt11 invoices is either 'invoice' or a non-zero bigint + if (recipient.amount === LIGHTNING_INVOICE) { + return; + } + // try to parse the amount as a bigint + let amount: bigint; + try { + amount = BigInt(recipient.amount); + } catch (e) { + throw new Error( + `invalid argument ${recipient.amount} for amount - lightning invoice amount must be >= 0 or ${LIGHTNING_INVOICE}` + ); + } + if (amount > 0n) { + return; + } + throw new Error( + `invalid argument for amount - lightning invoice amount must be a non-zero bigint or ${LIGHTNING_INVOICE}` + ); + } + + super.checkRecipient(recipient); + } + /** * Flag for sending value of 0 * @returns {boolean} True if okay to send 0 value, false otherwise diff --git a/modules/sdk-core/src/lightning.ts b/modules/sdk-core/src/lightning.ts new file mode 100644 index 0000000000..c6543476df --- /dev/null +++ b/modules/sdk-core/src/lightning.ts @@ -0,0 +1,11 @@ +export const LIGHTNING_INVOICE = 'invoice'; + +export function isBolt11Invoice(value: unknown): value is string { + if (typeof value !== 'string') { + return false; + } + if (value.startsWith('lnbc') || value.startsWith('lntb')) { + return true; + } + return false; +} diff --git a/modules/sdk-core/test/unit/lightning.ts b/modules/sdk-core/test/unit/lightning.ts new file mode 100644 index 0000000000..7d4402a59d --- /dev/null +++ b/modules/sdk-core/test/unit/lightning.ts @@ -0,0 +1,26 @@ +import 'should'; + +import { isBolt11Invoice } from '../../src/lightning'; + +describe('lightning', () => { + describe('isBolt11Invoice', () => { + it('should return true for a valid bolt11 invoice', () => { + const invoice = + 'lnbc500n1p3zv5vkpp5x0thcaz8wep54clc2xt5895azjdzmthyskzzh9yslggy74qtvl6sdpdg3hkuct5d9hkugrxdaezqjn0dphk2fmnypkk2mtsdahkccqzpgxqyz5vqsp5v80q4vq4pwakq2l0hcqgtelgajsymv4ud4jdcrqtnzhvet55qlus9qyyssquqh2wl2m866qs5n72c5vg6wmqx9vzwhs5ypualq4mcu76h2tdkcq3jtjwtggfff7xwtdqxlnwqk8cxpzryjghrmmq3syraswp9vjr7cqry9l96'; + + isBolt11Invoice(invoice).should.equal(true); + }); + + it('should return false for non-string values', () => { + isBolt11Invoice(undefined).should.equal(false); + isBolt11Invoice(null as any).should.equal(false); + isBolt11Invoice(123 as any).should.equal(false); + isBolt11Invoice({} as any).should.equal(false); + }); + + it('should return false for invalid invoice strings', () => { + isBolt11Invoice('').should.equal(false); + isBolt11Invoice('not-an-invoice').should.equal(false); + }); + }); +});