diff --git a/core/api/dev/apollo-federation/supergraph.graphql b/core/api/dev/apollo-federation/supergraph.graphql index 97d100e4aa..fa9fd4a656 100644 --- a/core/api/dev/apollo-federation/supergraph.graphql +++ b/core/api/dev/apollo-federation/supergraph.graphql @@ -47,6 +47,7 @@ interface Account last: Int walletIds: [WalletId] ): TransactionConnection + walletById(walletId: WalletId!): Wallet! wallets: [Wallet!]! } @@ -178,9 +179,11 @@ type BTCWallet implements Wallet """A balance stored in BTC.""" balance: SignedAmount! id: ID! + invoiceByPaymentHash(paymentHash: PaymentHash!): Invoice! """An unconfirmed incoming onchain balance.""" pendingIncomingBalance: SignedAmount! + transactionById(transactionId: ID!): Transaction! """A list of BTC transactions associated with this wallet.""" transactions( @@ -212,6 +215,7 @@ type BTCWallet implements Wallet """Returns the last n items from the list.""" last: Int ): TransactionConnection + transactionsByPaymentHash(paymentHash: PaymentHash!): [Transaction!]! walletCurrency: WalletCurrency! } @@ -324,6 +328,7 @@ type ConsumerAccount implements Account last: Int walletIds: [WalletId] ): TransactionConnection + walletById(walletId: WalletId!): Wallet! wallets: [Wallet!]! } @@ -545,6 +550,25 @@ input IntraLedgerUsdPaymentSendInput walletId: WalletId! } +"""A lightning invoice.""" +interface Invoice + @join__type(graph: PUBLIC) +{ + """The payment hash of the lightning invoice.""" + paymentHash: PaymentHash! + + """The bolt11 invoice to be paid.""" + paymentRequest: LnPaymentRequest! + + """ + The payment secret of the lightning invoice. This is not the preimage of the payment hash. + """ + paymentSecret: LnPaymentSecret! + + """The payment status of the invoice.""" + paymentStatus: InvoicePaymentStatus! +} + enum InvoicePaymentStatus @join__type(graph: PUBLIC) { @@ -576,12 +600,14 @@ enum link__Purpose { EXECUTION } -type LnInvoice +type LnInvoice implements Invoice + @join__implements(graph: PUBLIC, interface: "Invoice") @join__type(graph: PUBLIC) { paymentHash: PaymentHash! paymentRequest: LnPaymentRequest! paymentSecret: LnPaymentSecret! + paymentStatus: InvoicePaymentStatus! satoshis: SatAmount } @@ -660,12 +686,14 @@ type LnInvoicePaymentStatusPayload status: InvoicePaymentStatus } -type LnNoAmountInvoice +type LnNoAmountInvoice implements Invoice + @join__implements(graph: PUBLIC, interface: "Invoice") @join__type(graph: PUBLIC) { paymentHash: PaymentHash! paymentRequest: LnPaymentRequest! paymentSecret: LnPaymentSecret! + paymentStatus: InvoicePaymentStatus! } input LnNoAmountInvoiceCreateInput @@ -1580,9 +1608,11 @@ type UsdWallet implements Wallet accountId: ID! balance: SignedAmount! id: ID! + invoiceByPaymentHash(paymentHash: PaymentHash!): Invoice! """An unconfirmed incoming onchain balance.""" pendingIncomingBalance: SignedAmount! + transactionById(transactionId: ID!): Transaction! transactions( """Returns the items in the list that come after the specified cursor.""" after: String @@ -1612,6 +1642,7 @@ type UsdWallet implements Wallet """Returns the last n items from the list.""" last: Int ): TransactionConnection + transactionsByPaymentHash(paymentHash: PaymentHash!): [Transaction!]! walletCurrency: WalletCurrency! } @@ -1879,7 +1910,14 @@ interface Wallet accountId: ID! balance: SignedAmount! id: ID! + invoiceByPaymentHash( + """ + The lightning invoice with the matching paymentHash belonging to this wallet. + """ + paymentHash: PaymentHash! + ): Invoice! pendingIncomingBalance: SignedAmount! + transactionById(transactionId: ID!): Transaction! """ Transactions are ordered anti-chronologically, @@ -1919,6 +1957,14 @@ interface Wallet """Returns the last n items from the list.""" last: Int ): TransactionConnection + + """ + Returns the transactions that include this paymentHash. This should be a list of size one for a received lightning payment. This can be more that one transaction for a sent lightning payment. + """ + transactionsByPaymentHash( + """The payment hash of the lightning invoice paid in this transaction.""" + paymentHash: PaymentHash! + ): [Transaction!]! walletCurrency: WalletCurrency! } diff --git a/core/api/src/app/payments/update-pending-payments.ts b/core/api/src/app/payments/update-pending-payments.ts index c54e5656ff..4b63915619 100644 --- a/core/api/src/app/payments/update-pending-payments.ts +++ b/core/api/src/app/payments/update-pending-payments.ts @@ -97,7 +97,7 @@ export const updatePendingPaymentByHash = wrapAsyncToRunInSpan({ paymentHash: PaymentHash logger: Logger }): Promise => { - const walletId = await LedgerService().getWalletIdByTransactionHash(paymentHash) + const walletId = await LedgerService().getWalletIdByPaymentHash(paymentHash) if (walletId instanceof Error) return walletId return updatePendingPaymentsByWalletId({ diff --git a/core/api/src/app/wallets/add-invoice-for-wallet.ts b/core/api/src/app/wallets/add-invoice-for-wallet.ts index 9a9a2dc3c8..d3a1ca7560 100644 --- a/core/api/src/app/wallets/add-invoice-for-wallet.ts +++ b/core/api/src/app/wallets/add-invoice-for-wallet.ts @@ -27,7 +27,7 @@ const addInvoiceForSelf = async ({ walletAmount, memo = "", expiresIn, -}: AddInvoiceForSelfArgs): Promise => +}: AddInvoiceForSelfArgs): Promise => addInvoice({ walletId, limitCheckFn: checkSelfWalletIdRateLimits, @@ -45,7 +45,7 @@ const addInvoiceForSelf = async ({ export const addInvoiceForSelfForBtcWallet = async ( args: AddInvoiceForSelfForBtcWalletArgs, -): Promise => { +): Promise => { const walletId = checkedToWalletId(args.walletId) if (walletId instanceof Error) return walletId @@ -63,7 +63,7 @@ export const addInvoiceForSelfForBtcWallet = async ( export const addInvoiceForSelfForUsdWallet = async ( args: AddInvoiceForSelfForUsdWalletArgs, -): Promise => { +): Promise => { const walletId = checkedToWalletId(args.walletId) if (walletId instanceof Error) return walletId @@ -83,7 +83,7 @@ export const addInvoiceNoAmountForSelf = async ({ walletId, memo = "", expiresIn, -}: AddInvoiceNoAmountForSelfArgs): Promise => { +}: AddInvoiceNoAmountForSelfArgs): Promise => { const walletIdChecked = checkedToWalletId(walletId) if (walletIdChecked instanceof Error) return walletIdChecked @@ -118,7 +118,7 @@ const addInvoiceForRecipient = async ({ memo = "", descriptionHash, expiresIn, -}: AddInvoiceForRecipientArgs): Promise => +}: AddInvoiceForRecipientArgs): Promise => addInvoice({ walletId: recipientWalletId, limitCheckFn: checkRecipientWalletIdRateLimits, @@ -136,7 +136,7 @@ const addInvoiceForRecipient = async ({ export const addInvoiceForRecipientForBtcWallet = async ( args: AddInvoiceForRecipientForBtcWalletArgs, -): Promise => { +): Promise => { const recipientWalletId = checkedToWalletId(args.recipientWalletId) if (recipientWalletId instanceof Error) return recipientWalletId @@ -160,7 +160,7 @@ export const addInvoiceForRecipientForBtcWallet = async ( export const addInvoiceForRecipientForUsdWallet = async ( args: AddInvoiceForRecipientForUsdWalletArgs, -): Promise => { +): Promise => { const recipientWalletId = checkedToWalletId(args.recipientWalletId) if (recipientWalletId instanceof Error) return recipientWalletId @@ -184,7 +184,7 @@ export const addInvoiceForRecipientForUsdWallet = async ( export const addInvoiceForRecipientForUsdWalletAndBtcAmount = async ( args: AddInvoiceForRecipientForUsdWalletArgs, -): Promise => { +): Promise => { const recipientWalletId = checkedToWalletId(args.recipientWalletId) if (recipientWalletId instanceof Error) return recipientWalletId @@ -210,7 +210,7 @@ export const addInvoiceNoAmountForRecipient = async ({ recipientWalletId, memo = "", expiresIn, -}: AddInvoiceNoAmountForRecipientArgs): Promise => { +}: AddInvoiceNoAmountForRecipientArgs): Promise => { const walletId = checkedToWalletId(recipientWalletId) if (walletId instanceof Error) return walletId @@ -243,7 +243,7 @@ const addInvoice = async ({ walletId, limitCheckFn, buildWIBWithAmountFn, -}: AddInvoiceArgs): Promise => { +}: AddInvoiceArgs): Promise => { const wallet = await WalletsRepository().findById(walletId) if (wallet instanceof Error) return wallet const account = await AccountsRepository().findById(wallet.accountId) @@ -275,12 +275,11 @@ const addInvoice = async ({ const invoice = await walletIBWithAmount.registerInvoice() if (invoice instanceof Error) return invoice - const { walletInvoice, lnInvoice } = invoice - const persistedInvoice = await WalletInvoicesRepository().persistNew(walletInvoice) + const persistedInvoice = await WalletInvoicesRepository().persistNew(invoice) if (persistedInvoice instanceof Error) return persistedInvoice - return lnInvoice + return invoice } const checkSelfWalletIdRateLimits = async ( diff --git a/core/api/src/app/wallets/get-invoice-for-wallet-by-hash.ts b/core/api/src/app/wallets/get-invoice-for-wallet-by-hash.ts new file mode 100644 index 0000000000..3f1ad39c8b --- /dev/null +++ b/core/api/src/app/wallets/get-invoice-for-wallet-by-hash.ts @@ -0,0 +1,16 @@ +import { WalletInvoicesRepository } from "@/services/mongoose" + +export const getInvoiceForWalletByPaymentHash = ({ + walletId, + paymentHash, +}: { + walletId: WalletId + paymentHash: PaymentHash +}): Promise => { + const walletInvoicesRepository = WalletInvoicesRepository() + + return walletInvoicesRepository.findForWalletByPaymentHash({ + walletId, + paymentHash, + }) +} diff --git a/core/api/src/app/wallets/get-transaction-by-id.ts b/core/api/src/app/wallets/get-transaction-by-id.ts index b6a2143183..897ec33a55 100644 --- a/core/api/src/app/wallets/get-transaction-by-id.ts +++ b/core/api/src/app/wallets/get-transaction-by-id.ts @@ -4,6 +4,31 @@ import { checkedToLedgerTransactionId } from "@/domain/ledger" import { getNonEndUserWalletIds, LedgerService } from "@/services/ledger" +export const getTransactionForWalletById = async ({ + walletId, + transactionId: uncheckedTransactionId, +}: { + walletId: WalletId + transactionId: string +}): Promise => { + const ledger = LedgerService() + + const ledgerTransactionId = checkedToLedgerTransactionId(uncheckedTransactionId) + if (ledgerTransactionId instanceof Error) return ledgerTransactionId + + const ledgerTransaction = await ledger.getTransactionForWalletById({ + walletId, + transactionId: ledgerTransactionId, + }) + if (ledgerTransaction instanceof Error) return ledgerTransaction + + return WalletTransactionHistory.fromLedger({ + ledgerTransactions: [ledgerTransaction], + nonEndUserWalletIds: Object.values(await getNonEndUserWalletIds()), + memoSharingConfig, + }).transactions[0] +} + export const getTransactionById = async ( id: string, ): Promise => { diff --git a/core/api/src/app/wallets/get-transactions-by-hash.ts b/core/api/src/app/wallets/get-transactions-by-hash.ts index 38ebd41b03..957ff2851e 100644 --- a/core/api/src/app/wallets/get-transactions-by-hash.ts +++ b/core/api/src/app/wallets/get-transactions-by-hash.ts @@ -3,6 +3,28 @@ import { WalletTransactionHistory } from "@/domain/wallets" import { getNonEndUserWalletIds, LedgerService } from "@/services/ledger" +export const getTransactionsForWalletByPaymentHash = async ({ + walletId, + paymentHash, +}: { + walletId: WalletId + paymentHash: PaymentHash +}): Promise => { + const ledger = LedgerService() + const ledgerTransactions = await ledger.getTransactionsForWalletByPaymentHash({ + walletId, + paymentHash, + }) + + if (ledgerTransactions instanceof Error) return ledgerTransactions + + return WalletTransactionHistory.fromLedger({ + ledgerTransactions, + nonEndUserWalletIds: Object.values(await getNonEndUserWalletIds()), + memoSharingConfig, + }).transactions +} + export const getTransactionsByHash = async ( hash: PaymentHash | OnChainTxHash, ): Promise => { diff --git a/core/api/src/app/wallets/index.ts b/core/api/src/app/wallets/index.ts index 85bfd63832..f39e97348f 100644 --- a/core/api/src/app/wallets/index.ts +++ b/core/api/src/app/wallets/index.ts @@ -16,6 +16,7 @@ export * from "./settle-payout-txn" export * from "./update-legacy-on-chain-receipt" export * from "./update-pending-invoices" export * from "./validate" +export * from "./get-invoice-for-wallet-by-hash" import { WalletsRepository } from "@/services/mongoose" @@ -24,6 +25,21 @@ export const getWallet = async (walletId: WalletId) => { return wallets.findById(walletId) } +export const getWalletForAccountById = ({ + accountId, + walletId, +}: { + accountId: AccountId + walletId: WalletId +}): Promise => { + const wallets = WalletsRepository() + + return wallets.findForAccountById({ + accountId, + walletId, + }) +} + export const listWalletsByAccountId = async ( accountId: AccountId, ): Promise => { diff --git a/core/api/src/app/wallets/update-pending-invoices.ts b/core/api/src/app/wallets/update-pending-invoices.ts index 1c27f74156..6c543ddf54 100644 --- a/core/api/src/app/wallets/update-pending-invoices.ts +++ b/core/api/src/app/wallets/update-pending-invoices.ts @@ -51,7 +51,10 @@ export const handleHeldInvoices = async (logger: Logger): Promise => { await runInParallel({ iterator: pendingInvoices, logger, - processor: async (walletInvoice: WalletInvoice, index: number) => { + processor: async ( + walletInvoice: WalletInvoiceWithOptionalLnInvoice, + index: number, + ) => { logger.trace("updating pending invoices %s in worker %d", index) return updateOrDeclinePendingInvoice({ @@ -158,7 +161,7 @@ const updateOrDeclinePendingInvoice = async ({ walletInvoice, logger, }: { - walletInvoice: WalletInvoice + walletInvoice: WalletInvoiceWithOptionalLnInvoice logger: Logger }): Promise => WalletInvoiceChecker(walletInvoice).shouldDecline() @@ -173,7 +176,7 @@ const updatePendingInvoiceBeforeFinally = async ({ walletInvoice, logger, }: { - walletInvoice: WalletInvoice + walletInvoice: WalletInvoiceWithOptionalLnInvoice logger: Logger }): Promise => { addAttributesToCurrentSpan({ @@ -419,7 +422,7 @@ export const updatePendingInvoice = wrapAsyncToRunInSpan({ walletInvoice, logger, }: { - walletInvoice: WalletInvoice + walletInvoice: WalletInvoiceWithOptionalLnInvoice logger: Logger }): Promise => { const result = await updatePendingInvoiceBeforeFinally({ diff --git a/core/api/src/domain/errors.ts b/core/api/src/domain/errors.ts index 1a79e3e16b..1fd4d8006f 100644 --- a/core/api/src/domain/errors.ts +++ b/core/api/src/domain/errors.ts @@ -27,6 +27,7 @@ export class DbConnectionClosedError extends RepositoryError { level = ErrorLevel.Critical } export class MultipleWalletsFoundForAccountIdAndCurrency extends RepositoryError {} +export class WalletInvoiceMissingLnInvoiceError extends RepositoryError {} export class CouldNotUnsetPhoneFromUserError extends CouldNotUpdateError {} diff --git a/core/api/src/domain/ledger/index.types.d.ts b/core/api/src/domain/ledger/index.types.d.ts index 428f93b6d7..f837301d52 100644 --- a/core/api/src/domain/ledger/index.types.d.ts +++ b/core/api/src/domain/ledger/index.types.d.ts @@ -236,10 +236,20 @@ interface ILedgerService { id: LedgerTransactionId, ): Promise | LedgerServiceError> + getTransactionForWalletById(args: { + walletId: WalletId + transactionId: LedgerTransactionId + }): Promise | LedgerServiceError> + getTransactionsByHash( paymentHash: PaymentHash | OnChainTxHash, ): Promise[] | LedgerServiceError> + getTransactionsForWalletByPaymentHash(args: { + walletId: WalletId + paymentHash: PaymentHash + }): Promise[] | LedgerServiceError> + getTransactionsByWalletId( walletId: WalletId, ): Promise[] | LedgerServiceError> @@ -291,9 +301,7 @@ interface ILedgerService { revertOnChainPayment(args: RevertOnChainPaymentArgs): Promise - getWalletIdByTransactionHash( - hash: PaymentHash | OnChainTxHash, - ): Promise + getWalletIdByPaymentHash(hash: PaymentHash): Promise listWalletIdsWithPendingPayments: () => AsyncGenerator | LedgerServiceError diff --git a/core/api/src/domain/wallet-invoices/index.ts b/core/api/src/domain/wallet-invoices/index.ts index 5f108baa07..2d0410bcd5 100644 --- a/core/api/src/domain/wallet-invoices/index.ts +++ b/core/api/src/domain/wallet-invoices/index.ts @@ -1 +1,7 @@ export * from "./wallet-invoice-checker" + +export const WalletInvoiceStatus = { + Pending: "Pending", + Paid: "Paid", + Expired: "Expired", +} as const diff --git a/core/api/src/domain/wallet-invoices/index.types.d.ts b/core/api/src/domain/wallet-invoices/index.types.d.ts index 0ad848fb55..460db0bd22 100644 --- a/core/api/src/domain/wallet-invoices/index.types.d.ts +++ b/core/api/src/domain/wallet-invoices/index.types.d.ts @@ -10,6 +10,13 @@ type WalletInvoiceChecker = { shouldDecline: () => boolean } +type WalletInvoiceStatus = + (typeof import("./index").WalletInvoiceStatus)[keyof typeof import("./index").WalletInvoiceStatus] + +type WalletInvoiceStatusChecker = { + status: (currentTime: Date) => WalletInvoiceStatus +} + type WalletInvoiceBuilderConfig = { dealerBtcFromUsd: BtcFromUsdFn dealerUsdFromBtc: UsdFromBtcFn @@ -73,16 +80,11 @@ type WIBWithAmountState = WIBWithExpirationState & { usdAmount?: UsdPaymentAmount } -type LnAndWalletInvoice = { - walletInvoice: WalletInvoice & { paymentRequest: EncodedPaymentRequest } - lnInvoice: LnInvoice -} - type WIBWithAmount = { - registerInvoice: () => Promise + registerInvoice: () => Promise } -type WalletInvoice = { +type WalletInvoiceWithOptionalLnInvoice = { paymentHash: PaymentHash secret: SecretPreImage selfGenerated: boolean @@ -91,7 +93,11 @@ type WalletInvoice = { recipientWalletDescriptor: PartialWalletDescriptor paid: boolean createdAt: Date - paymentRequest?: EncodedPaymentRequest + lnInvoice?: LnInvoice // LnInvoice is optional because some older invoices don't have it +} + +type WalletInvoice = WalletInvoiceWithOptionalLnInvoice & { + lnInvoice: LnInvoice } type WalletAddress = { @@ -140,7 +146,7 @@ type WalletInvoiceReceiverArgs = { receivedBtc: BtcPaymentAmount satsFee?: BtcPaymentAmount - walletInvoice: WalletInvoice + walletInvoice: WalletInvoiceWithOptionalLnInvoice recipientWalletDescriptors: AccountWalletDescriptors } @@ -157,8 +163,11 @@ type WalletAddressReceiverArgs = { walletAddress: WalletAddress } -type WalletInvoicesPersistNewArgs = Omit & { - paymentRequest: EncodedPaymentRequest +type WalletInvoicesPersistNewArgs = Omit + +type WalletInvoiceFindForWalletByPaymentHashArgs = { + walletId: WalletId + paymentHash: PaymentHash } interface IWalletInvoicesRepository { @@ -166,13 +175,19 @@ interface IWalletInvoicesRepository { invoice: WalletInvoicesPersistNewArgs, ) => Promise - markAsPaid: (paymentHash: PaymentHash) => Promise + markAsPaid: ( + paymentHash: PaymentHash, + ) => Promise findByPaymentHash: ( paymentHash: PaymentHash, ) => Promise - yieldPending: () => AsyncGenerator | RepositoryError + findForWalletByPaymentHash: ( + args: WalletInvoiceFindForWalletByPaymentHashArgs, + ) => Promise + + yieldPending: () => AsyncGenerator | RepositoryError deleteByPaymentHash: (paymentHash: PaymentHash) => Promise diff --git a/core/api/src/domain/wallet-invoices/wallet-invoice-builder.ts b/core/api/src/domain/wallet-invoices/wallet-invoice-builder.ts index be719fa4f2..21bb416da2 100644 --- a/core/api/src/domain/wallet-invoices/wallet-invoice-builder.ts +++ b/core/api/src/domain/wallet-invoices/wallet-invoice-builder.ts @@ -143,7 +143,7 @@ export const WIBWithAmount = (state: WIBWithAmountState): WIBWithAmount => { return new InvalidWalletInvoiceBuilderStateError() } - const walletInvoice = { + const walletInvoice: WalletInvoice = { paymentHash, secret, selfGenerated: state.selfGenerated, @@ -152,12 +152,9 @@ export const WIBWithAmount = (state: WIBWithAmountState): WIBWithAmount => { recipientWalletDescriptor: state.recipientWalletDescriptor, paid: false, createdAt: new Date(), - paymentRequest: registeredInvoice.invoice.paymentRequest, - } - return { - walletInvoice, lnInvoice: registeredInvoice.invoice, } + return walletInvoice } return { diff --git a/core/api/src/domain/wallet-invoices/wallet-invoice-checker.ts b/core/api/src/domain/wallet-invoices/wallet-invoice-checker.ts index f1f5c34026..72ba8bdf35 100644 --- a/core/api/src/domain/wallet-invoices/wallet-invoice-checker.ts +++ b/core/api/src/domain/wallet-invoices/wallet-invoice-checker.ts @@ -3,7 +3,7 @@ import { CouldNotFindWalletInvoiceError } from "@/domain/errors" import { WalletCurrency } from "@/domain/shared" export const WalletInvoiceChecker = ( - walletInvoice: WalletInvoice | RepositoryError, + walletInvoice: WalletInvoiceWithOptionalLnInvoice | RepositoryError, ): WalletInvoiceChecker => { const shouldDecline = (): boolean => { if (walletInvoice instanceof CouldNotFindWalletInvoiceError) { diff --git a/core/api/src/domain/wallet-invoices/wallet-invoice-status-checker.ts b/core/api/src/domain/wallet-invoices/wallet-invoice-status-checker.ts new file mode 100644 index 0000000000..3f36856ec1 --- /dev/null +++ b/core/api/src/domain/wallet-invoices/wallet-invoice-status-checker.ts @@ -0,0 +1,21 @@ +import { WalletInvoiceStatus } from "./index" + +export const WalletInvoiceStatusChecker = ( + walletInvoice: WalletInvoice, +): WalletInvoiceStatusChecker => { + const status = (currentTime: Date): WalletInvoiceStatus => { + if (walletInvoice.paid) { + return WalletInvoiceStatus.Paid + } + + if (walletInvoice.lnInvoice.expiresAt < currentTime) { + return WalletInvoiceStatus.Expired + } + + return WalletInvoiceStatus.Pending + } + + return { + status, + } +} diff --git a/core/api/src/domain/wallets/index.types.d.ts b/core/api/src/domain/wallets/index.types.d.ts index 3e754bfd91..398d9265d3 100644 --- a/core/api/src/domain/wallets/index.types.d.ts +++ b/core/api/src/domain/wallets/index.types.d.ts @@ -178,6 +178,10 @@ interface IWalletsRepository { currency, }: NewWalletInfo): Promise findById(walletId: WalletId): Promise + findForAccountById(args: { + accountId: AccountId + walletId: WalletId + }): Promise listByAccountId(accountId: AccountId): Promise findAccountWalletsByAccountId( accountId: AccountId, diff --git a/core/api/src/graphql/admin/schema.graphql b/core/api/src/graphql/admin/schema.graphql index 89c261c695..99fb5fd4a3 100644 --- a/core/api/src/graphql/admin/schema.graphql +++ b/core/api/src/graphql/admin/schema.graphql @@ -81,9 +81,11 @@ type BTCWallet implements Wallet { """A balance stored in BTC.""" balance: SignedAmount! id: ID! + invoiceByPaymentHash(paymentHash: PaymentHash!): Invoice! """An unconfirmed incoming onchain balance.""" pendingIncomingBalance: SignedAmount! + transactionById(transactionId: ID!): Transaction! """A list of BTC transactions associated with this wallet.""" transactions( @@ -115,6 +117,7 @@ type BTCWallet implements Wallet { """Returns the last n items from the list.""" last: Int ): TransactionConnection + transactionsByPaymentHash(paymentHash: PaymentHash!): [Transaction!]! walletCurrency: WalletCurrency! } @@ -172,6 +175,29 @@ type InitiationViaOnChain { address: OnChainAddress! } +"""A lightning invoice.""" +interface Invoice { + """The payment hash of the lightning invoice.""" + paymentHash: PaymentHash! + + """The bolt11 invoice to be paid.""" + paymentRequest: LnPaymentRequest! + + """ + The payment secret of the lightning invoice. This is not the preimage of the payment hash. + """ + paymentSecret: LnPaymentSecret! + + """The payment status of the invoice.""" + paymentStatus: InvoicePaymentStatus! +} + +enum InvoicePaymentStatus { + EXPIRED + PAID + PENDING +} + scalar Language type LightningInvoice { @@ -394,9 +420,11 @@ type UsdWallet implements Wallet { accountId: ID! balance: SignedAmount! id: ID! + invoiceByPaymentHash(paymentHash: PaymentHash!): Invoice! """An unconfirmed incoming onchain balance.""" pendingIncomingBalance: SignedAmount! + transactionById(transactionId: ID!): Transaction! transactions( """Returns the items in the list that come after the specified cursor.""" after: String @@ -426,6 +454,7 @@ type UsdWallet implements Wallet { """Returns the last n items from the list.""" last: Int ): TransactionConnection + transactionsByPaymentHash(paymentHash: PaymentHash!): [Transaction!]! walletCurrency: WalletCurrency! } @@ -444,7 +473,14 @@ interface Wallet { accountId: ID! balance: SignedAmount! id: ID! + invoiceByPaymentHash( + """ + The lightning invoice with the matching paymentHash belonging to this wallet. + """ + paymentHash: PaymentHash! + ): Invoice! pendingIncomingBalance: SignedAmount! + transactionById(transactionId: ID!): Transaction! """ Transactions are ordered anti-chronologically, @@ -484,6 +520,14 @@ interface Wallet { """Returns the last n items from the list.""" last: Int ): TransactionConnection + + """ + Returns the transactions that include this paymentHash. This should be a list of size one for a received lightning payment. This can be more that one transaction for a sent lightning payment. + """ + transactionsByPaymentHash( + """The payment hash of the lightning invoice paid in this transaction.""" + paymentHash: PaymentHash! + ): [Transaction!]! walletCurrency: WalletCurrency! } diff --git a/core/api/src/graphql/error-map.ts b/core/api/src/graphql/error-map.ts index db2e4851e2..7b9e77bb4d 100644 --- a/core/api/src/graphql/error-map.ts +++ b/core/api/src/graphql/error-map.ts @@ -92,6 +92,10 @@ export const mapError = (error: ApplicationError): CustomGraphQLError => { message = `Account does not exist for username ${error.message}` return new NotFoundError({ message, logger: baseLogger }) + case "WalletInvoiceMissingLnInvoiceError": + message = `The associated lightning invoice could not be found.` + return new NotFoundError({ message, logger: baseLogger }) + case "CouldNotFindTransactionsForAccountError": message = "No transactions found for your account." return new NotFoundError({ message, logger: baseLogger }) diff --git a/core/api/src/graphql/public/root/mutation/ln-invoice-create.ts b/core/api/src/graphql/public/root/mutation/ln-invoice-create.ts index 703b812fc4..eb93143887 100644 --- a/core/api/src/graphql/public/root/mutation/ln-invoice-create.ts +++ b/core/api/src/graphql/public/root/mutation/ln-invoice-create.ts @@ -46,20 +46,20 @@ const LnInvoiceCreateMutation = GT.Field({ } } - const lnInvoice = await Wallets.addInvoiceForSelfForBtcWallet({ + const invoice = await Wallets.addInvoiceForSelfForBtcWallet({ walletId, amount, memo, expiresIn, }) - if (lnInvoice instanceof Error) { - return { errors: [mapAndParseErrorForGqlResponse(lnInvoice)] } + if (invoice instanceof Error) { + return { errors: [mapAndParseErrorForGqlResponse(invoice)] } } return { errors: [], - invoice: lnInvoice, + invoice, } }, }) diff --git a/core/api/src/graphql/public/root/mutation/ln-noamount-invoice-create-on-behalf-of-recipient.ts b/core/api/src/graphql/public/root/mutation/ln-noamount-invoice-create-on-behalf-of-recipient.ts index 7ca5d8a177..0da78153ce 100644 --- a/core/api/src/graphql/public/root/mutation/ln-noamount-invoice-create-on-behalf-of-recipient.ts +++ b/core/api/src/graphql/public/root/mutation/ln-noamount-invoice-create-on-behalf-of-recipient.ts @@ -45,25 +45,19 @@ const LnNoAmountInvoiceCreateOnBehalfOfRecipientMutation = GT.Field({ } } - const result = await Wallets.addInvoiceNoAmountForRecipient({ + const invoice = await Wallets.addInvoiceNoAmountForRecipient({ recipientWalletId, memo, expiresIn, }) - if (result instanceof Error) { - return { errors: [mapAndParseErrorForGqlResponse(result)] } + if (invoice instanceof Error) { + return { errors: [mapAndParseErrorForGqlResponse(invoice)] } } - const { paymentRequest, paymentHash, paymentSecret } = result - return { errors: [], - invoice: { - paymentRequest, - paymentHash, - paymentSecret, - }, + invoice, } }, }) diff --git a/core/api/src/graphql/public/root/mutation/ln-noamount-invoice-create.ts b/core/api/src/graphql/public/root/mutation/ln-noamount-invoice-create.ts index 2218c2becf..091bcce4ec 100644 --- a/core/api/src/graphql/public/root/mutation/ln-noamount-invoice-create.ts +++ b/core/api/src/graphql/public/root/mutation/ln-noamount-invoice-create.ts @@ -45,19 +45,19 @@ const LnNoAmountInvoiceCreateMutation = GT.Field({ } } - const lnInvoice = await Wallets.addInvoiceNoAmountForSelf({ + const invoice = await Wallets.addInvoiceNoAmountForSelf({ walletId, memo, expiresIn, }) - if (lnInvoice instanceof Error) { - return { errors: [mapAndParseErrorForGqlResponse(lnInvoice)] } + if (invoice instanceof Error) { + return { errors: [mapAndParseErrorForGqlResponse(invoice)] } } return { errors: [], - invoice: lnInvoice, + invoice, } }, }) diff --git a/core/api/src/graphql/public/root/mutation/ln-usd-invoice-create.ts b/core/api/src/graphql/public/root/mutation/ln-usd-invoice-create.ts index 8518335457..4138777b2d 100644 --- a/core/api/src/graphql/public/root/mutation/ln-usd-invoice-create.ts +++ b/core/api/src/graphql/public/root/mutation/ln-usd-invoice-create.ts @@ -47,20 +47,20 @@ const LnUsdInvoiceCreateMutation = GT.Field({ } } - const lnInvoice = await Wallets.addInvoiceForSelfForUsdWallet({ + const invoice = await Wallets.addInvoiceForSelfForUsdWallet({ walletId, amount, memo, expiresIn, }) - if (lnInvoice instanceof Error) { - return { errors: [mapAndParseErrorForGqlResponse(lnInvoice)] } + if (invoice instanceof Error) { + return { errors: [mapAndParseErrorForGqlResponse(invoice)] } } return { errors: [], - invoice: lnInvoice, + invoice, } }, }) diff --git a/core/api/src/graphql/public/root/query/ln-invoice-payment-status.ts b/core/api/src/graphql/public/root/query/ln-invoice-payment-status.ts index 1a75826056..404f0aa07d 100644 --- a/core/api/src/graphql/public/root/query/ln-invoice-payment-status.ts +++ b/core/api/src/graphql/public/root/query/ln-invoice-payment-status.ts @@ -4,6 +4,7 @@ import { GT } from "@/graphql/index" import { mapError } from "@/graphql/error-map" import LnInvoicePaymentStatusPayload from "@/graphql/public/types/payload/ln-invoice-payment-status" import LnInvoicePaymentStatusInput from "@/graphql/public/types/object/ln-invoice-payment-status-input" +import { WalletInvoiceStatus } from "@/domain/wallet-invoices" const LnInvoicePaymentStatusQuery = GT.Field({ type: GT.NonNull(LnInvoicePaymentStatusPayload), @@ -20,9 +21,11 @@ const LnInvoicePaymentStatusQuery = GT.Field({ const paid = await paymentStatusChecker.invoiceIsPaid() if (paid instanceof Error) throw mapError(paid) - if (paid) return { errors: [], status: "PAID" } + if (paid) return { errors: [], status: WalletInvoiceStatus.Paid } - const status = paymentStatusChecker.isExpired ? "EXPIRED" : "PENDING" + const status = paymentStatusChecker.isExpired + ? WalletInvoiceStatus.Expired + : WalletInvoiceStatus.Pending return { errors: [], status } }, }) diff --git a/core/api/src/graphql/public/root/subscription/ln-invoice-payment-status.ts b/core/api/src/graphql/public/root/subscription/ln-invoice-payment-status.ts index f05dba2f76..124268ccae 100644 --- a/core/api/src/graphql/public/root/subscription/ln-invoice-payment-status.ts +++ b/core/api/src/graphql/public/root/subscription/ln-invoice-payment-status.ts @@ -10,6 +10,7 @@ import LnInvoicePaymentStatusPayload from "@/graphql/public/types/payload/ln-inv import LnInvoicePaymentStatusInput from "@/graphql/public/types/object/ln-invoice-payment-status-input" import { UnknownClientError } from "@/graphql/error" import { mapAndParseErrorForGqlResponse } from "@/graphql/error-map" +import { WalletInvoiceStatus } from "@/domain/wallet-invoices" const pubsub = PubSubService() @@ -82,17 +83,19 @@ const LnInvoicePaymentStatusSubscription = { } if (paid) { - pubsub.publishDelayed({ trigger, payload: { status: "PAID" } }) + pubsub.publishDelayed({ trigger, payload: { status: WalletInvoiceStatus.Paid } }) return pubsub.createAsyncIterator({ trigger }) } - const status = paymentStatusChecker.isExpired ? "EXPIRED" : "PENDING" + const status = paymentStatusChecker.isExpired + ? WalletInvoiceStatus.Expired + : WalletInvoiceStatus.Pending pubsub.publishDelayed({ trigger, payload: { status } }) if (!paymentStatusChecker.isExpired) { const timeout = Math.max(paymentStatusChecker.expiresAt.getTime() - Date.now(), 0) setTimeout(() => { - pubsub.publish({ trigger, payload: { status: "EXPIRED" } }) + pubsub.publish({ trigger, payload: { status: WalletInvoiceStatus.Expired } }) }, timeout + 1000) } diff --git a/core/api/src/graphql/public/root/subscription/my-updates.ts b/core/api/src/graphql/public/root/subscription/my-updates.ts index 24ce0a6282..b51cca929e 100644 --- a/core/api/src/graphql/public/root/subscription/my-updates.ts +++ b/core/api/src/graphql/public/root/subscription/my-updates.ts @@ -19,7 +19,7 @@ import RealtimePrice from "@/graphql/public/types/object/realtime-price" import OnChainTxHash from "@/graphql/shared/types/scalar/onchain-tx-hash" import { AuthenticationError, UnknownClientError } from "@/graphql/error" import TxNotificationType from "@/graphql/public/types/scalar/tx-notification-type" -import InvoicePaymentStatus from "@/graphql/public/types/scalar/invoice-payment-status" +import InvoicePaymentStatus from "@/graphql/shared/types/scalar/invoice-payment-status" import { baseLogger } from "@/services/logger" import { PubSubService } from "@/services/pubsub" diff --git a/core/api/src/graphql/public/schema.graphql b/core/api/src/graphql/public/schema.graphql index eb94a1e5fd..0a133b24ee 100644 --- a/core/api/src/graphql/public/schema.graphql +++ b/core/api/src/graphql/public/schema.graphql @@ -22,6 +22,7 @@ interface Account { last: Int walletIds: [WalletId] ): TransactionConnection + walletById(walletId: WalletId!): Wallet! wallets: [Wallet!]! } @@ -121,9 +122,11 @@ type BTCWallet implements Wallet { """A balance stored in BTC.""" balance: SignedAmount! id: ID! + invoiceByPaymentHash(paymentHash: PaymentHash!): Invoice! """An unconfirmed incoming onchain balance.""" pendingIncomingBalance: SignedAmount! + transactionById(transactionId: ID!): Transaction! """A list of BTC transactions associated with this wallet.""" transactions( @@ -155,6 +158,7 @@ type BTCWallet implements Wallet { """Returns the last n items from the list.""" last: Int ): TransactionConnection + transactionsByPaymentHash(paymentHash: PaymentHash!): [Transaction!]! walletCurrency: WalletCurrency! } @@ -245,6 +249,7 @@ type ConsumerAccount implements Account { last: Int walletIds: [WalletId] ): TransactionConnection + walletById(walletId: WalletId!): Wallet! wallets: [Wallet!]! } @@ -415,6 +420,23 @@ input IntraLedgerUsdPaymentSendInput { walletId: WalletId! } +"""A lightning invoice.""" +interface Invoice { + """The payment hash of the lightning invoice.""" + paymentHash: PaymentHash! + + """The bolt11 invoice to be paid.""" + paymentRequest: LnPaymentRequest! + + """ + The payment secret of the lightning invoice. This is not the preimage of the payment hash. + """ + paymentSecret: LnPaymentSecret! + + """The payment status of the invoice.""" + paymentStatus: InvoicePaymentStatus! +} + enum InvoicePaymentStatus { EXPIRED PAID @@ -423,10 +445,11 @@ enum InvoicePaymentStatus { scalar Language -type LnInvoice { +type LnInvoice implements Invoice { paymentHash: PaymentHash! paymentRequest: LnPaymentRequest! paymentSecret: LnPaymentSecret! + paymentStatus: InvoicePaymentStatus! satoshis: SatAmount } @@ -491,10 +514,11 @@ type LnInvoicePaymentStatusPayload { status: InvoicePaymentStatus } -type LnNoAmountInvoice { +type LnNoAmountInvoice implements Invoice { paymentHash: PaymentHash! paymentRequest: LnPaymentRequest! paymentSecret: LnPaymentSecret! + paymentStatus: InvoicePaymentStatus! } input LnNoAmountInvoiceCreateInput { @@ -1241,9 +1265,11 @@ type UsdWallet implements Wallet { accountId: ID! balance: SignedAmount! id: ID! + invoiceByPaymentHash(paymentHash: PaymentHash!): Invoice! """An unconfirmed incoming onchain balance.""" pendingIncomingBalance: SignedAmount! + transactionById(transactionId: ID!): Transaction! transactions( """Returns the items in the list that come after the specified cursor.""" after: String @@ -1273,6 +1299,7 @@ type UsdWallet implements Wallet { """Returns the last n items from the list.""" last: Int ): TransactionConnection + transactionsByPaymentHash(paymentHash: PaymentHash!): [Transaction!]! walletCurrency: WalletCurrency! } @@ -1476,7 +1503,14 @@ interface Wallet { accountId: ID! balance: SignedAmount! id: ID! + invoiceByPaymentHash( + """ + The lightning invoice with the matching paymentHash belonging to this wallet. + """ + paymentHash: PaymentHash! + ): Invoice! pendingIncomingBalance: SignedAmount! + transactionById(transactionId: ID!): Transaction! """ Transactions are ordered anti-chronologically, @@ -1516,6 +1550,14 @@ interface Wallet { """Returns the last n items from the list.""" last: Int ): TransactionConnection + + """ + Returns the transactions that include this paymentHash. This should be a list of size one for a received lightning payment. This can be more that one transaction for a sent lightning payment. + """ + transactionsByPaymentHash( + """The payment hash of the lightning invoice paid in this transaction.""" + paymentHash: PaymentHash! + ): [Transaction!]! walletCurrency: WalletCurrency! } diff --git a/core/api/src/graphql/public/types/abstract/account.ts b/core/api/src/graphql/public/types/abstract/account.ts index 4245c833c5..0466996f32 100644 --- a/core/api/src/graphql/public/types/abstract/account.ts +++ b/core/api/src/graphql/public/types/abstract/account.ts @@ -62,7 +62,14 @@ const IAccount = GT.Interface({ notificationSettings: { type: GT.NonNull(NotificationSettings), }, - + walletById: { + type: GT.NonNull(Wallet), + args: { + walletId: { + type: GT.NonNull(WalletId), + }, + }, + }, // FUTURE-PLAN: Support a `users: [User!]!` field here }), }) diff --git a/core/api/src/graphql/public/types/object/consumer-account.ts b/core/api/src/graphql/public/types/object/consumer-account.ts index d0245e5aa9..6d7d740bc7 100644 --- a/core/api/src/graphql/public/types/object/consumer-account.ts +++ b/core/api/src/graphql/public/types/object/consumer-account.ts @@ -62,6 +62,26 @@ const ConsumerAccount = GT.Object({ }, }, + walletById: { + type: GT.NonNull(Wallet), + args: { + walletId: { + type: GT.NonNull(WalletId), + }, + }, + resolve: async (source, args) => { + const { walletId } = args + const wallet = await Wallets.getWalletForAccountById({ + walletId, + accountId: source.id, + }) + if (wallet instanceof Error) { + throw mapError(wallet) + } + return wallet + }, + }, + defaultWalletId: { type: GT.NonNull(WalletId), resolve: (source) => source.defaultWalletId, diff --git a/core/api/src/graphql/public/types/object/ln-invoice.ts b/core/api/src/graphql/public/types/object/ln-invoice.ts deleted file mode 100644 index f414ebf895..0000000000 --- a/core/api/src/graphql/public/types/object/ln-invoice.ts +++ /dev/null @@ -1,27 +0,0 @@ -import LnPaymentRequest from "../../../shared/types/scalar/ln-payment-request" -import PaymentHash from "../../../shared/types/scalar/payment-hash" -import LnPaymentSecret from "../../../shared/types/scalar/ln-payment-secret" -import SatAmount from "../../../shared/types/scalar/sat-amount" - -import { GT } from "@/graphql/index" - -const LnInvoice = GT.Object({ - name: "LnInvoice", - fields: () => ({ - paymentRequest: { - type: GT.NonNull(LnPaymentRequest), - }, - paymentHash: { - type: GT.NonNull(PaymentHash), - }, - paymentSecret: { - type: GT.NonNull(LnPaymentSecret), - }, - satoshis: { - type: SatAmount, - resolve: (source) => source.amount, - }, - }), -}) - -export default LnInvoice diff --git a/core/api/src/graphql/public/types/object/ln-noamount-invoice.ts b/core/api/src/graphql/public/types/object/ln-noamount-invoice.ts deleted file mode 100644 index efa9883c38..0000000000 --- a/core/api/src/graphql/public/types/object/ln-noamount-invoice.ts +++ /dev/null @@ -1,22 +0,0 @@ -import LnPaymentRequest from "../../../shared/types/scalar/ln-payment-request" -import PaymentHash from "../../../shared/types/scalar/payment-hash" -import LnPaymentSecret from "../../../shared/types/scalar/ln-payment-secret" - -import { GT } from "@/graphql/index" - -const LnNoAmountInvoice = GT.Object({ - name: "LnNoAmountInvoice", - fields: () => ({ - paymentRequest: { - type: GT.NonNull(LnPaymentRequest), - }, - paymentHash: { - type: GT.NonNull(PaymentHash), - }, - paymentSecret: { - type: GT.NonNull(LnPaymentSecret), - }, - }), -}) - -export default LnNoAmountInvoice diff --git a/core/api/src/graphql/public/types/payload/ln-invoice-payment-status.ts b/core/api/src/graphql/public/types/payload/ln-invoice-payment-status.ts index ac638c5b2f..2ff383f4e0 100644 --- a/core/api/src/graphql/public/types/payload/ln-invoice-payment-status.ts +++ b/core/api/src/graphql/public/types/payload/ln-invoice-payment-status.ts @@ -1,6 +1,6 @@ import IError from "../../../shared/types/abstract/error" -import InvoicePaymentStatus from "../scalar/invoice-payment-status" +import InvoicePaymentStatus from "../../../shared/types/scalar/invoice-payment-status" import { GT } from "@/graphql/index" diff --git a/core/api/src/graphql/public/types/payload/ln-invoice.ts b/core/api/src/graphql/public/types/payload/ln-invoice.ts index 0f93d6dd88..1cce9af689 100644 --- a/core/api/src/graphql/public/types/payload/ln-invoice.ts +++ b/core/api/src/graphql/public/types/payload/ln-invoice.ts @@ -1,6 +1,6 @@ import IError from "../../../shared/types/abstract/error" -import LnInvoice from "../object/ln-invoice" +import LnInvoice from "../../../shared/types/object/ln-invoice" import { GT } from "@/graphql/index" diff --git a/core/api/src/graphql/public/types/payload/ln-noamount-invoice.ts b/core/api/src/graphql/public/types/payload/ln-noamount-invoice.ts index 35a3ccde2b..ee8d3671bc 100644 --- a/core/api/src/graphql/public/types/payload/ln-noamount-invoice.ts +++ b/core/api/src/graphql/public/types/payload/ln-noamount-invoice.ts @@ -1,6 +1,6 @@ import IError from "../../../shared/types/abstract/error" -import LnNoAmountInvoice from "../object/ln-noamount-invoice" +import LnNoAmountInvoice from "../../../shared/types/object/ln-noamount-invoice" import { GT } from "@/graphql/index" diff --git a/core/api/src/graphql/public/types/scalar/invoice-payment-status.ts b/core/api/src/graphql/public/types/scalar/invoice-payment-status.ts deleted file mode 100644 index 564eadfd8b..0000000000 --- a/core/api/src/graphql/public/types/scalar/invoice-payment-status.ts +++ /dev/null @@ -1,12 +0,0 @@ -import { GT } from "@/graphql/index" - -const InvoicePaymentStatus = GT.Enum({ - name: "InvoicePaymentStatus", - values: { - PENDING: {}, - PAID: {}, - EXPIRED: {}, - }, -}) - -export default InvoicePaymentStatus diff --git a/core/api/src/graphql/shared/types/abstract/invoice.ts b/core/api/src/graphql/shared/types/abstract/invoice.ts new file mode 100644 index 0000000000..08c636a196 --- /dev/null +++ b/core/api/src/graphql/shared/types/abstract/invoice.ts @@ -0,0 +1,34 @@ +import PaymentHash from "../scalar/payment-hash" +import LnPaymentRequest from "../scalar/ln-payment-request" + +import LnPaymentSecret from "../scalar/ln-payment-secret" + +import InvoicePaymentStatus from "../scalar/invoice-payment-status" + +import { GT } from "@/graphql/index" + +const IInvoice = GT.Interface({ + name: "Invoice", + description: "A lightning invoice.", + fields: () => ({ + paymentRequest: { + type: GT.NonNull(LnPaymentRequest), + description: "The bolt11 invoice to be paid.", + }, + paymentHash: { + type: GT.NonNull(PaymentHash), + description: "The payment hash of the lightning invoice.", + }, + paymentSecret: { + type: GT.NonNull(LnPaymentSecret), + description: + "The payment secret of the lightning invoice. This is not the preimage of the payment hash.", + }, + paymentStatus: { + type: GT.NonNull(InvoicePaymentStatus), + description: "The payment status of the invoice.", + }, + }), +}) + +export default IInvoice diff --git a/core/api/src/graphql/shared/types/abstract/wallet.ts b/core/api/src/graphql/shared/types/abstract/wallet.ts index 49456e43f4..f4a3910071 100644 --- a/core/api/src/graphql/shared/types/abstract/wallet.ts +++ b/core/api/src/graphql/shared/types/abstract/wallet.ts @@ -1,9 +1,12 @@ import dedent from "dedent" -import { TransactionConnection } from "../object/transaction" +import Transaction, { TransactionConnection } from "../object/transaction" import WalletCurrency from "../scalar/wallet-currency" import SignedAmount from "../scalar/signed-amount" import OnChainAddress from "../scalar/on-chain-address" +import PaymentHash from "../scalar/payment-hash" + +import IInvoice from "./invoice" import { connectionArgs } from "@/graphql/connections" import { GT } from "@/graphql/index" @@ -45,6 +48,36 @@ const IWallet = GT.Interface({ }, }, }, + invoiceByPaymentHash: { + type: GT.NonNull(IInvoice), + args: { + paymentHash: { + type: GT.NonNull(PaymentHash), + description: + "The lightning invoice with the matching paymentHash belonging to this wallet.", + }, + }, + }, + transactionsByPaymentHash: { + type: GT.NonNullList(Transaction), + description: + "Returns the transactions that include this paymentHash. This should be a list of size one for a received lightning payment. This can be more that one transaction for a sent lightning payment.", + args: { + paymentHash: { + type: GT.NonNull(PaymentHash), + description: + "The payment hash of the lightning invoice paid in this transaction.", + }, + }, + }, + transactionById: { + type: GT.NonNull(Transaction), + args: { + transactionId: { + type: GT.NonNullID, + }, + }, + }, }), }) diff --git a/core/api/src/graphql/shared/types/object/btc-wallet.ts b/core/api/src/graphql/shared/types/object/btc-wallet.ts index f32d1dd9f1..15c8d61d85 100644 --- a/core/api/src/graphql/shared/types/object/btc-wallet.ts +++ b/core/api/src/graphql/shared/types/object/btc-wallet.ts @@ -6,7 +6,11 @@ import WalletCurrency from "../scalar/wallet-currency" import OnChainAddress from "../scalar/on-chain-address" -import { TransactionConnection } from "./transaction" +import PaymentHash from "../scalar/payment-hash" + +import IInvoice from "../abstract/invoice" + +import Transaction, { TransactionConnection } from "./transaction" import { GT } from "@/graphql/index" import { normalizePaymentAmount } from "@/graphql/shared/root/mutation" @@ -125,6 +129,75 @@ const BtcWallet = GT.Object({ ) }, }, + invoiceByPaymentHash: { + type: GT.NonNull(IInvoice), + args: { + paymentHash: { + type: GT.NonNull(PaymentHash), + }, + }, + resolve: async (source, args) => { + const { paymentHash } = args + if (paymentHash instanceof Error) throw paymentHash + + const invoice = await Wallets.getInvoiceForWalletByPaymentHash({ + walletId: source.id, + paymentHash, + }) + + if (invoice instanceof Error) { + throw mapError(invoice) + } + + return invoice + }, + }, + transactionsByPaymentHash: { + type: GT.NonNullList(Transaction), + args: { + paymentHash: { + type: GT.NonNull(PaymentHash), + }, + }, + resolve: async (source, args) => { + const { paymentHash } = args + if (paymentHash instanceof Error) throw paymentHash + + const transactions = await Wallets.getTransactionsForWalletByPaymentHash({ + walletId: source.id, + paymentHash, + }) + + if (transactions instanceof Error) { + throw mapError(transactions) + } + + return transactions + }, + }, + transactionById: { + type: GT.NonNull(Transaction), + args: { + transactionId: { + type: GT.NonNullID, + }, + }, + resolve: async (source, args) => { + const { transactionId } = args + if (transactionId instanceof Error) throw transactionId + + const transaction = await Wallets.getTransactionForWalletById({ + walletId: source.id, + transactionId, + }) + + if (transaction instanceof Error) { + throw mapError(transaction) + } + + return transaction + }, + }, }), }) diff --git a/core/api/src/graphql/shared/types/object/ln-invoice.ts b/core/api/src/graphql/shared/types/object/ln-invoice.ts new file mode 100644 index 0000000000..0e4defab7c --- /dev/null +++ b/core/api/src/graphql/shared/types/object/ln-invoice.ts @@ -0,0 +1,45 @@ +import LnPaymentRequest from "../scalar/ln-payment-request" +import PaymentHash from "../scalar/payment-hash" +import LnPaymentSecret from "../scalar/ln-payment-secret" +import SatAmount from "../scalar/sat-amount" + +import IInvoice from "../abstract/invoice" + +import InvoicePaymentStatus from "../scalar/invoice-payment-status" + +import { GT } from "@/graphql/index" +import { WalletInvoiceStatusChecker } from "@/domain/wallet-invoices/wallet-invoice-status-checker" + +const LnInvoice = GT.Object({ + name: "LnInvoice", + interfaces: () => [IInvoice], + isTypeOf: (source) => !!source.lnInvoice.amount, + fields: () => ({ + paymentRequest: { + type: GT.NonNull(LnPaymentRequest), + resolve: (source) => source.lnInvoice.paymentRequest, + }, + paymentHash: { + type: GT.NonNull(PaymentHash), + resolve: (source) => source.lnInvoice.paymentHash, + }, + paymentSecret: { + type: GT.NonNull(LnPaymentSecret), + resolve: (source) => source.lnInvoice.paymentSecret, + }, + satoshis: { + type: SatAmount, + resolve: (source) => source.lnInvoice.amount, + }, + paymentStatus: { + type: GT.NonNull(InvoicePaymentStatus), + resolve: (source) => { + const statusChecker = WalletInvoiceStatusChecker(source) + const status = statusChecker.status(new Date()) + return status + }, + }, + }), +}) + +export default LnInvoice diff --git a/core/api/src/graphql/shared/types/object/ln-noamount-invoice.ts b/core/api/src/graphql/shared/types/object/ln-noamount-invoice.ts new file mode 100644 index 0000000000..9c3772efac --- /dev/null +++ b/core/api/src/graphql/shared/types/object/ln-noamount-invoice.ts @@ -0,0 +1,39 @@ +import LnPaymentRequest from "../scalar/ln-payment-request" +import PaymentHash from "../scalar/payment-hash" +import LnPaymentSecret from "../scalar/ln-payment-secret" + +import IInvoice from "../abstract/invoice" + +import { GT } from "@/graphql/index" +import InvoicePaymentStatus from "@/graphql/shared/types/scalar/invoice-payment-status" +import { WalletInvoiceStatusChecker } from "@/domain/wallet-invoices/wallet-invoice-status-checker" + +const LnNoAmountInvoice = GT.Object({ + name: "LnNoAmountInvoice", + interfaces: () => [IInvoice], + isTypeOf: (source) => !source.lnInvoice.amount, + fields: () => ({ + paymentRequest: { + type: GT.NonNull(LnPaymentRequest), + resolve: (source) => source.lnInvoice.paymentRequest, + }, + paymentHash: { + type: GT.NonNull(PaymentHash), + resolve: (source) => source.lnInvoice.paymentHash, + }, + paymentSecret: { + type: GT.NonNull(LnPaymentSecret), + resolve: (source) => source.lnInvoice.paymentSecret, + }, + paymentStatus: { + type: GT.NonNull(InvoicePaymentStatus), + resolve: (source) => { + const statusChecker = WalletInvoiceStatusChecker(source) + const status = statusChecker.status(new Date()) + return status + }, + }, + }), +}) + +export default LnNoAmountInvoice diff --git a/core/api/src/graphql/shared/types/object/usd-wallet.ts b/core/api/src/graphql/shared/types/object/usd-wallet.ts index 2a7c71afe9..93291623f8 100644 --- a/core/api/src/graphql/shared/types/object/usd-wallet.ts +++ b/core/api/src/graphql/shared/types/object/usd-wallet.ts @@ -6,7 +6,11 @@ import SignedAmount from "../scalar/signed-amount" import OnChainAddress from "../scalar/on-chain-address" -import { TransactionConnection } from "./transaction" +import PaymentHash from "../scalar/payment-hash" + +import IInvoice from "../abstract/invoice" + +import Transaction, { TransactionConnection } from "./transaction" import { GT } from "@/graphql/index" import { @@ -123,6 +127,75 @@ const UsdWallet = GT.Object({ ) }, }, + invoiceByPaymentHash: { + type: GT.NonNull(IInvoice), + args: { + paymentHash: { + type: GT.NonNull(PaymentHash), + }, + }, + resolve: async (source, args) => { + const { paymentHash } = args + if (paymentHash instanceof Error) throw paymentHash + + const invoice = await Wallets.getInvoiceForWalletByPaymentHash({ + walletId: source.id, + paymentHash, + }) + + if (invoice instanceof Error) { + throw mapError(invoice) + } + + return invoice + }, + }, + transactionsByPaymentHash: { + type: GT.NonNullList(Transaction), + args: { + paymentHash: { + type: GT.NonNull(PaymentHash), + }, + }, + resolve: async (source, args) => { + const { paymentHash } = args + if (paymentHash instanceof Error) throw paymentHash + + const transactions = await Wallets.getTransactionsForWalletByPaymentHash({ + walletId: source.id, + paymentHash, + }) + + if (transactions instanceof Error) { + throw mapError(transactions) + } + + return transactions + }, + }, + transactionById: { + type: GT.NonNull(Transaction), + args: { + transactionId: { + type: GT.NonNullID, + }, + }, + resolve: async (source, args) => { + const { transactionId } = args + if (transactionId instanceof Error) throw transactionId + + const transaction = await Wallets.getTransactionForWalletById({ + walletId: source.id, + transactionId, + }) + + if (transaction instanceof Error) { + throw mapError(transaction) + } + + return transaction + }, + }, }), }) diff --git a/core/api/src/graphql/shared/types/scalar/invoice-payment-status.ts b/core/api/src/graphql/shared/types/scalar/invoice-payment-status.ts new file mode 100644 index 0000000000..a691d04754 --- /dev/null +++ b/core/api/src/graphql/shared/types/scalar/invoice-payment-status.ts @@ -0,0 +1,19 @@ +import { WalletInvoiceStatus } from "@/domain/wallet-invoices" +import { GT } from "@/graphql/index" + +const InvoicePaymentStatus = GT.Enum({ + name: "InvoicePaymentStatus", + values: { + PENDING: { + value: WalletInvoiceStatus.Pending, + }, + PAID: { + value: WalletInvoiceStatus.Paid, + }, + EXPIRED: { + value: WalletInvoiceStatus.Expired, + }, + }, +}) + +export default InvoicePaymentStatus diff --git a/core/api/src/services/ledger/index.ts b/core/api/src/services/ledger/index.ts index 0c6db4be75..c194934e9f 100644 --- a/core/api/src/services/ledger/index.ts +++ b/core/api/src/services/ledger/index.ts @@ -88,6 +88,29 @@ export const LedgerService = (): ILedgerService => { } } + const getTransactionForWalletById = async ({ + walletId, + transactionId, + }: { + walletId: WalletId + transactionId: LedgerTransactionId + }): Promise | LedgerServiceError> => { + const liabilitiesWalletId = toLiabilitiesWalletId(walletId) + try { + const _id = toObjectId(transactionId) + const { results } = await MainBook.ledger({ + account: liabilitiesWalletId, + _id, + }) + if (results.length === 1) { + return translateToLedgerTx(results[0]) + } + return new CouldNotFindTransactionError() + } catch (err) { + return new UnknownLedgerError(err) + } + } + const getTransactionsByHash = async ( hash: PaymentHash | OnChainTxHash, ): Promise[] | LedgerServiceError> => { @@ -104,6 +127,28 @@ export const LedgerService = (): ILedgerService => { } } + const getTransactionsForWalletByPaymentHash = async ({ + walletId, + paymentHash, + }: { + walletId: WalletId + paymentHash: PaymentHash + }): Promise[] | LedgerError> => { + const liabilitiesWalletId = toLiabilitiesWalletId(walletId) + try { + const { results } = await MainBook.ledger({ + account: liabilitiesWalletId, + hash: paymentHash, + }) + + /* eslint @typescript-eslint/ban-ts-comment: "off" */ + // @ts-ignore-next-line no-implicit-any error + return results.map((tx) => translateToLedgerTx(tx)) + } catch (err) { + return new UnknownLedgerError(err) + } + } + const getTransactionsByWalletId = async ( walletId: WalletId, ): Promise[] | LedgerError> => { @@ -364,8 +409,8 @@ export const LedgerService = (): ILedgerService => { } } - const getWalletIdByTransactionHash = async ( - hash: PaymentHash | OnChainTxHash, + const getWalletIdByPaymentHash = async ( + hash: PaymentHash, ): Promise => { const bankOwnerWalletId = await caching.getBankOwnerWalletId() const bankOwnerPath = toLiabilitiesWalletId(bankOwnerWalletId) @@ -414,7 +459,9 @@ export const LedgerService = (): ILedgerService => { fns: { updateMetadataByHash, getTransactionById, + getTransactionForWalletById, getTransactionsByHash, + getTransactionsForWalletByPaymentHash, getTransactionsByWalletId, getTransactionsByWalletIds, getTransactionsByWalletIdAndContactUsername, @@ -426,7 +473,7 @@ export const LedgerService = (): ILedgerService => { isOnChainReceiptTxRecordedForWallet, isOnChainTxHashRecorded, isLnTxRecorded, - getWalletIdByTransactionHash, + getWalletIdByPaymentHash, listWalletIdsWithPendingPayments, ...admin, ...send, diff --git a/core/api/src/services/mongoose/wallet-invoices.ts b/core/api/src/services/mongoose/wallet-invoices.ts index 7db3f23c43..e31ee037f4 100644 --- a/core/api/src/services/mongoose/wallet-invoices.ts +++ b/core/api/src/services/mongoose/wallet-invoices.ts @@ -2,10 +2,13 @@ import { WalletInvoice } from "./schema" import { parseRepositoryError } from "./utils" +import { decodeInvoice } from "@/domain/bitcoin/lightning" + import { CouldNotFindWalletInvoiceError, RepositoryError, UnknownRepositoryError, + WalletInvoiceMissingLnInvoiceError, } from "@/domain/errors" import { UsdPaymentAmount } from "@/domain/shared" @@ -18,7 +21,7 @@ export const WalletInvoicesRepository = (): IWalletInvoicesRepository => { pubkey, paid, usdAmount, - paymentRequest, + lnInvoice, }: WalletInvoicesPersistNewArgs): Promise => { try { const walletInvoice = await new WalletInvoice({ @@ -30,9 +33,9 @@ export const WalletInvoicesRepository = (): IWalletInvoicesRepository => { paid, cents: usdAmount ? Number(usdAmount.amount) : undefined, currency: recipientWalletDescriptor.currency, - paymentRequest, + paymentRequest: lnInvoice.paymentRequest, }).save() - return walletInvoiceFromRaw(walletInvoice) + return ensureWalletInvoiceHasLnInvoice(walletInvoiceFromRaw(walletInvoice)) } catch (err) { return parseRepositoryError(err) } @@ -40,7 +43,7 @@ export const WalletInvoicesRepository = (): IWalletInvoicesRepository => { const markAsPaid = async ( paymentHash: PaymentHash, - ): Promise => { + ): Promise => { try { const walletInvoice = await WalletInvoice.findOneAndUpdate( { _id: paymentHash }, @@ -66,13 +69,32 @@ export const WalletInvoicesRepository = (): IWalletInvoicesRepository => { if (!walletInvoice) { return new CouldNotFindWalletInvoiceError(paymentHash) } - return walletInvoiceFromRaw(walletInvoice) + return ensureWalletInvoiceHasLnInvoice(walletInvoiceFromRaw(walletInvoice)) } catch (err) { return parseRepositoryError(err) } } - async function* yieldPending(): AsyncGenerator | RepositoryError { + const findForWalletByPaymentHash = async ({ + walletId, + paymentHash, + }: WalletInvoiceFindForWalletByPaymentHashArgs): Promise< + WalletInvoice | RepositoryError + > => { + try { + const walletInvoice = await WalletInvoice.findOne({ _id: paymentHash, walletId }) + if (!walletInvoice) { + return new CouldNotFindWalletInvoiceError(paymentHash) + } + return ensureWalletInvoiceHasLnInvoice(walletInvoiceFromRaw(walletInvoice)) + } catch (err) { + return parseRepositoryError(err) + } + } + + async function* yieldPending(): + | AsyncGenerator + | RepositoryError { let pending try { pending = WalletInvoice.find({ paid: false }).cursor({ @@ -119,23 +141,44 @@ export const WalletInvoicesRepository = (): IWalletInvoicesRepository => { persistNew, markAsPaid, findByPaymentHash, + findForWalletByPaymentHash, yieldPending, deleteByPaymentHash, deleteUnpaidOlderThan, } } -const walletInvoiceFromRaw = (result: WalletInvoiceRecord): WalletInvoice => ({ - paymentHash: result._id as PaymentHash, - secret: result.secret as SecretPreImage, - recipientWalletDescriptor: { - id: result.walletId as WalletId, - currency: result.currency as WalletCurrency, - }, - selfGenerated: result.selfGenerated, - pubkey: result.pubkey as Pubkey, - paid: result.paid as boolean, - usdAmount: result.cents ? UsdPaymentAmount(BigInt(result.cents)) : undefined, - createdAt: new Date(result.timestamp.getTime()), - paymentRequest: result.paymentRequest as EncodedPaymentRequest, -}) +const walletInvoiceFromRaw = ( + result: WalletInvoiceRecord, +): WalletInvoiceWithOptionalLnInvoice => { + const lnInvoice = result.paymentRequest + ? decodeInvoice(result.paymentRequest) + : undefined + + if (lnInvoice instanceof Error) throw new Error("Corrupt payment request in db") + + return { + paymentHash: result._id as PaymentHash, + secret: result.secret as SecretPreImage, + recipientWalletDescriptor: { + id: result.walletId as WalletId, + currency: result.currency as WalletCurrency, + }, + selfGenerated: result.selfGenerated, + pubkey: result.pubkey as Pubkey, + paid: result.paid as boolean, + usdAmount: result.cents ? UsdPaymentAmount(BigInt(result.cents)) : undefined, + createdAt: new Date(result.timestamp.getTime()), + lnInvoice, + } +} + +const ensureWalletInvoiceHasLnInvoice = ( + walletInvoiceWithOptionalLnInvoice: WalletInvoiceWithOptionalLnInvoice, +) => { + if (!walletInvoiceWithOptionalLnInvoice.lnInvoice) { + return new WalletInvoiceMissingLnInvoiceError() + } + + return walletInvoiceWithOptionalLnInvoice as WalletInvoice +} diff --git a/core/api/src/services/mongoose/wallets.ts b/core/api/src/services/mongoose/wallets.ts index 29fdce80a6..1cd9a05900 100644 --- a/core/api/src/services/mongoose/wallets.ts +++ b/core/api/src/services/mongoose/wallets.ts @@ -51,6 +51,27 @@ export const WalletsRepository = (): IWalletsRepository => { } } + const findForAccountById = async ({ + accountId, + walletId, + }: { + accountId: AccountId + walletId: WalletId + }): Promise => { + try { + const result: WalletRecord | null = await Wallet.findOne({ + id: walletId, + accountId, + }) + if (!result) { + return new CouldNotFindWalletFromIdError() + } + return resultToWallet(result) + } catch (err) { + return parseRepositoryError(err) + } + } + const listByAccountId = async ( accountId: AccountId, ): Promise => { @@ -146,6 +167,7 @@ export const WalletsRepository = (): IWalletsRepository => { return { findById, + findForAccountById, listByAccountId, findAccountWalletsByAccountId, findByAddress, diff --git a/core/api/src/services/notifications/index.ts b/core/api/src/services/notifications/index.ts index 122ad9f508..51b9c2613d 100644 --- a/core/api/src/services/notifications/index.ts +++ b/core/api/src/services/notifications/index.ts @@ -17,6 +17,7 @@ import { import { PubSubService } from "@/services/pubsub" import { wrapAsyncFunctionsToRunInSpan } from "@/services/tracing" +import { WalletInvoiceStatus } from "@/domain/wallet-invoices" export const NotificationsService = (): INotificationsService => { const pubsub = PubSubService() @@ -40,7 +41,7 @@ export const NotificationsService = (): INotificationsService => { }) pubsub.publish({ trigger: lnPaymentStatusTrigger, - payload: { status: "PAID" }, + payload: { status: WalletInvoiceStatus.Paid }, }) // Notify the recipient (via GraphQL subscription if any) @@ -54,7 +55,7 @@ export const NotificationsService = (): INotificationsService => { invoice: { walletId: recipientWalletId, paymentHash, - status: "PAID", + status: WalletInvoiceStatus.Paid, }, }, }) diff --git a/core/api/test/bats/gql/invoice-for-wallet-by-payment-hash.gql b/core/api/test/bats/gql/invoice-for-wallet-by-payment-hash.gql new file mode 100644 index 0000000000..147bb65714 --- /dev/null +++ b/core/api/test/bats/gql/invoice-for-wallet-by-payment-hash.gql @@ -0,0 +1,17 @@ +query me($walletId: WalletId!, $paymentHash: PaymentHash!) { + me { + defaultAccount { + id + walletById(walletId: $walletId) { + id + invoiceByPaymentHash(paymentHash: $paymentHash) { + paymentHash + paymentStatus + ... on LnInvoice { + satoshis + } + } + } + } + } +} diff --git a/core/api/test/bats/gql/transaction-for-wallet-by-id.gql b/core/api/test/bats/gql/transaction-for-wallet-by-id.gql new file mode 100644 index 0000000000..cc5c946f05 --- /dev/null +++ b/core/api/test/bats/gql/transaction-for-wallet-by-id.gql @@ -0,0 +1,54 @@ +query transactionForWalletById($walletId: WalletId!, $transactionId: ID!) { + me { + defaultAccount { + id + displayCurrency + walletById(walletId: $walletId) { + transactionById(transactionId: $transactionId) { + __typename + id + status + direction + memo + createdAt + settlementAmount + settlementFee + settlementDisplayAmount + settlementDisplayFee + settlementDisplayCurrency + settlementCurrency + settlementPrice { + base + offset + } + initiationVia { + __typename + ... on InitiationViaIntraLedger { + counterPartyWalletId + counterPartyUsername + } + ... on InitiationViaLn { + paymentHash + } + ... on InitiationViaOnChain { + address + } + } + settlementVia { + __typename + ... on SettlementViaIntraLedger { + counterPartyWalletId + counterPartyUsername + } + ... on SettlementViaLn { + preImage + } + ... on SettlementViaOnChain { + transactionHash + } + } + } + } + } + } +} diff --git a/core/api/test/bats/gql/transactions-for-wallet-by-payment-hash.gql b/core/api/test/bats/gql/transactions-for-wallet-by-payment-hash.gql new file mode 100644 index 0000000000..515d08232f --- /dev/null +++ b/core/api/test/bats/gql/transactions-for-wallet-by-payment-hash.gql @@ -0,0 +1,57 @@ +query transactionsForWalletByPaymentHash( + $walletId: WalletId! + $paymentHash: PaymentHash! +) { + me { + defaultAccount { + displayCurrency + walletById(walletId: $walletId) { + id + transactionsByPaymentHash(paymentHash: $paymentHash) { + __typename + id + status + direction + memo + createdAt + settlementAmount + settlementFee + settlementDisplayAmount + settlementDisplayFee + settlementDisplayCurrency + settlementCurrency + settlementPrice { + base + offset + } + initiationVia { + __typename + ... on InitiationViaIntraLedger { + counterPartyWalletId + counterPartyUsername + } + ... on InitiationViaLn { + paymentHash + } + ... on InitiationViaOnChain { + address + } + } + settlementVia { + __typename + ... on SettlementViaIntraLedger { + counterPartyWalletId + counterPartyUsername + } + ... on SettlementViaLn { + preImage + } + ... on SettlementViaOnChain { + transactionHash + } + } + } + } + } + } +} diff --git a/core/api/test/bats/ln-receive.bats b/core/api/test/bats/ln-receive.bats index d5f49c4486..8e35714758 100644 --- a/core/api/test/bats/ln-receive.bats +++ b/core/api/test/bats/ln-receive.bats @@ -72,6 +72,19 @@ usd_amount=50 payment_hash="$(echo $invoice | jq -r '.paymentHash')" [[ "${payment_hash}" != "null" ]] || exit 1 + # Get invoice by hash + variables=$( + jq -n \ + --arg wallet_id "$(read_value $btc_wallet_name)" \ + --arg payment_hash "$payment_hash" \ + '{walletId: $wallet_id, paymentHash: $payment_hash}' + ) + exec_graphql "$token_name" 'invoice-for-wallet-by-payment-hash' "$variables" + query_payment_hash="$(graphql_output '.data.me.defaultAccount.walletById.invoiceByPaymentHash.paymentHash')" + invoice_status="$(graphql_output '.data.me.defaultAccount.walletById.invoiceByPaymentHash.paymentStatus')" + [[ "${query_payment_hash}" == "${payment_hash}" ]] || exit 1 + [[ "${invoice_status}" == "PENDING" ]] || exit 1 + # Receive payment lnd_outside_cli payinvoice -f \ --pay_req "$payment_request" @@ -82,6 +95,42 @@ usd_amount=50 # Check for subscriber event check_for_ln_update "$payment_hash" || exit 1 + # Get transaction by hash + variables=$( + jq -n \ + --arg wallet_id "$(read_value $btc_wallet_name)" \ + --arg payment_hash "$payment_hash" \ + '{walletId: $wallet_id, paymentHash: $payment_hash}' + ) + + exec_graphql "$token_name" 'transactions-for-wallet-by-payment-hash' "$variables" + query_payment_hash="$(graphql_output '.data.me.defaultAccount.walletById.transactionsByPaymentHash[0].initiationVia.paymentHash')" + [[ "${query_payment_hash}" == "${payment_hash}" ]] || exit 1 + transaction_id="$(graphql_output '.data.me.defaultAccount.walletById.transactionsByPaymentHash[0].id')" + + # Get transaction by tx id + variables=$( + jq -n \ + --arg wallet_id "$(read_value $btc_wallet_name)" \ + --arg transaction_id "$transaction_id" \ + '{walletId: $wallet_id, transactionId: $transaction_id}' + ) + exec_graphql "$token_name" 'transaction-for-wallet-by-id' "$variables" + query_transaction_id="$(graphql_output '.data.me.defaultAccount.walletById.transactionById.id')" + [[ "${query_transaction_id}" == "${transaction_id}" ]] || exit 1 + + # Ensure invoice status is paid + variables=$( + jq -n \ + --arg wallet_id "$(read_value $btc_wallet_name)" \ + --arg payment_hash "$payment_hash" \ + '{walletId: $wallet_id, paymentHash: $payment_hash}' + ) + exec_graphql "$token_name" 'invoice-for-wallet-by-payment-hash' "$variables" + invoice_status="$(graphql_output '.data.me.defaultAccount.walletById.invoiceByPaymentHash.paymentStatus')" + [[ "${invoice_status}" == "PAID" ]] || exit 1 + + # Check for callback num_callback_events_after=$(cat .e2e-callback.log | grep "$account_id" | wc -l) [[ "$num_callback_events_after" -gt "$num_callback_events_before" ]] || exit 1 diff --git a/core/api/test/helpers/lightning.ts b/core/api/test/helpers/lightning.ts index 2b2f5a6783..e1b35ad593 100644 --- a/core/api/test/helpers/lightning.ts +++ b/core/api/test/helpers/lightning.ts @@ -470,12 +470,12 @@ export const fundWalletIdFromLightning = async ({ : await Wallets.addInvoiceForSelfForUsdWallet({ walletId, amount }) if (invoice instanceof Error) return invoice - safePay({ lnd: lndOutside1, request: invoice.paymentRequest }) + safePay({ lnd: lndOutside1, request: invoice.lnInvoice.paymentRequest }) // TODO: we could use an event instead of a sleep await sleep(500) - const hash = getHash(invoice.paymentRequest) + const hash = getHash(invoice.lnInvoice.paymentRequest) expect( await Wallets.updatePendingInvoiceByPaymentHash({ diff --git a/core/api/test/helpers/user.ts b/core/api/test/helpers/user.ts index 4788df1b09..2d0ac2eec8 100644 --- a/core/api/test/helpers/user.ts +++ b/core/api/test/helpers/user.ts @@ -340,20 +340,20 @@ export const fundWallet = async ({ wallet.currency === WalletCurrency.Btc ? Wallets.addInvoiceForSelfForBtcWallet : Wallets.addInvoiceForSelfForUsdWallet - const lnInvoice = await addInvoiceFn({ + const invoice = await addInvoiceFn({ walletId: wallet.id, amount: Number(balanceAmount.amount), memo: `Fund new wallet ${wallet.id}`, }) - if (lnInvoice instanceof Error) throw lnInvoice - const { paymentRequest: invoice, paymentHash } = lnInvoice + if (invoice instanceof Error) throw invoice + const { paymentRequest, paymentHash } = invoice.lnInvoice const updateInvoice = () => Wallets.updatePendingInvoiceByPaymentHash({ paymentHash, logger: baseLogger, }) const promises = Promise.all([ - safePay({ lnd: lndOutside1, request: invoice }), + safePay({ lnd: lndOutside1, request: paymentRequest }), (async () => { // TODO: we could use event instead of a sleep to lower test latency await sleep(500) diff --git a/core/api/test/integration/app/wallets/send-lightning.spec.ts b/core/api/test/integration/app/wallets/send-lightning.spec.ts index 8410d95c7c..f8af335c64 100644 --- a/core/api/test/integration/app/wallets/send-lightning.spec.ts +++ b/core/api/test/integration/app/wallets/send-lightning.spec.ts @@ -512,7 +512,7 @@ describe("initiated via lightning", () => { pubkey: destination, recipientWalletDescriptor, paid: false, - paymentRequest: lnInvoice.paymentRequest, + lnInvoice, }) if (persisted instanceof Error) throw persisted @@ -562,7 +562,7 @@ describe("initiated via lightning", () => { pubkey: lnInvoice.destination, recipientWalletDescriptor: newWalletDescriptor, paid: false, - paymentRequest: lnInvoice.paymentRequest, + lnInvoice, }) if (persisted instanceof Error) throw persisted @@ -634,7 +634,7 @@ describe("initiated via lightning", () => { recipientWalletDescriptor: usdWalletDescriptor, paid: false, usdAmount, - paymentRequest: lnInvoice.paymentRequest, + lnInvoice, }) if (persisted instanceof Error) throw persisted @@ -655,7 +655,7 @@ describe("initiated via lightning", () => { pubkey: noAmountLnInvoice.destination, recipientWalletDescriptor: usdWalletDescriptor, paid: false, - paymentRequest: lnInvoice.paymentRequest, + lnInvoice, }) if (noAmountPersisted instanceof Error) throw noAmountPersisted @@ -714,7 +714,7 @@ describe("initiated via lightning", () => { pubkey: largeWithAmountLnInvoice.destination, recipientWalletDescriptor: otherWalletDescriptor, paid: false, - paymentRequest: lnInvoice.paymentRequest, + lnInvoice, }) if (persisted instanceof Error) throw persisted @@ -735,7 +735,7 @@ describe("initiated via lightning", () => { pubkey: noAmountLnInvoice.destination, recipientWalletDescriptor: otherWalletDescriptor, paid: false, - paymentRequest: lnInvoice.paymentRequest, + lnInvoice, }) if (noAmountPersisted instanceof Error) throw noAmountPersisted @@ -788,7 +788,7 @@ describe("initiated via lightning", () => { pubkey: noAmountLnInvoice.destination, recipientWalletDescriptor: usdWalletDescriptor, paid: false, - paymentRequest: lnInvoice.paymentRequest, + lnInvoice, }) if (persisted instanceof Error) throw persisted diff --git a/core/api/test/integration/app/wallets/update-pending-invoices.spec.ts b/core/api/test/integration/app/wallets/update-pending-invoices.spec.ts index bdaa4c14f3..74ae7ecc57 100644 --- a/core/api/test/integration/app/wallets/update-pending-invoices.spec.ts +++ b/core/api/test/integration/app/wallets/update-pending-invoices.spec.ts @@ -8,7 +8,11 @@ import { WalletCurrency } from "@/domain/shared" import { baseLogger } from "@/services/logger" import { WalletInvoicesRepository } from "@/services/mongoose" import { WalletInvoice } from "@/services/mongoose/schema" -import { getSecretAndPaymentHash } from "@/domain/bitcoin/lightning" +import { decodeInvoice, getSecretAndPaymentHash } from "@/domain/bitcoin/lightning" + +const mockLnInvoice = decodeInvoice( + "lnbc1pjjahwgpp5zzh9s6tkhpk7heu8jt4l7keuzg7v046p0lzx2hvy3jf6a56w50nqdp82pshjgr5dusyymrfde4jq4mpd3kx2apq24ek2uscqzpuxqyz5vqsp5vl4zmuvhl8rzy4rmq0g3j28060pv3gqp22rh8l7u45xwyu27928q9qyyssqn9drylhlth9ee320e4ahz52y9rklujqgw0kj9ce2gcmltqk6uuay5yv8vgks0y5tggndv0kek2m2n02lf43znx50237mglxsfw4au2cqqr6qax", +) as LnInvoice afterEach(async () => { await WalletInvoice.deleteMany({}) @@ -35,7 +39,7 @@ describe("update pending invoices", () => { currency: WalletCurrency.Usd, }, paid: false, - paymentRequest: "paymentRequest" as EncodedPaymentRequest, + lnInvoice: mockLnInvoice, } const persisted = await WalletInvoicesRepository().persistNew( expiredUsdWalletInvoice, @@ -85,7 +89,7 @@ describe("update pending invoices", () => { currency: WalletCurrency.Btc, }, paid: false, - paymentRequest: "paymentRequest" as EncodedPaymentRequest, + lnInvoice: mockLnInvoice, } const persisted = await WalletInvoicesRepository().persistNew( expiredBtcWalletInvoice, diff --git a/core/api/test/legacy-integration/02-user-wallet/02-receive-lightning.spec.ts b/core/api/test/legacy-integration/02-user-wallet/02-receive-lightning.spec.ts index 606b42af54..7aa2c33a80 100644 --- a/core/api/test/legacy-integration/02-user-wallet/02-receive-lightning.spec.ts +++ b/core/api/test/legacy-integration/02-user-wallet/02-receive-lightning.spec.ts @@ -37,13 +37,13 @@ describe("UserWallet - Lightning", () => { const sats = 500_000 const memo = "myMemo" - const lnInvoice = await Wallets.addInvoiceForSelfForBtcWallet({ + const invoice = await Wallets.addInvoiceForSelfForBtcWallet({ walletId: walletIdB, amount: toSats(sats), memo, }) - if (lnInvoice instanceof Error) throw lnInvoice - const { paymentRequest, paymentHash } = lnInvoice + if (invoice instanceof Error) throw invoice + const { paymentRequest, paymentHash } = invoice.lnInvoice const balanceBefore = await getBalanceHelper(walletIdB) const updateInvoice = async () => { diff --git a/core/api/test/legacy-integration/02-user-wallet/02-send-lightning-limits.spec.ts b/core/api/test/legacy-integration/02-user-wallet/02-send-lightning-limits.spec.ts index 484144db12..f1e421780b 100644 --- a/core/api/test/legacy-integration/02-user-wallet/02-send-lightning-limits.spec.ts +++ b/core/api/test/legacy-integration/02-user-wallet/02-send-lightning-limits.spec.ts @@ -153,12 +153,12 @@ describe("UserWallet Limits - Lightning Pay", () => { ) if (senderAccount instanceof Error) throw senderAccount - const lnInvoice = await Wallets.addInvoiceForSelfForBtcWallet({ + const invoice = await Wallets.addInvoiceForSelfForBtcWallet({ walletId: otherBtcWallet.id, amount: Number(btcThresholdAmount.amount), }) - if (lnInvoice instanceof Error) throw lnInvoice - const { paymentRequest: request } = lnInvoice + if (invoice instanceof Error) throw invoice + const { paymentRequest: request } = invoice.lnInvoice const paymentResult = await Payments.payInvoiceByWalletId({ uncheckedPaymentRequest: request, @@ -209,12 +209,12 @@ describe("UserWallet Limits - Lightning Pay", () => { // Test BTC -> USD limits { - const lnInvoice = await Wallets.addInvoiceForSelfForUsdWallet({ + const invoice = await Wallets.addInvoiceForSelfForUsdWallet({ walletId: usdWalletDescriptor.id, amount: Number(usdPaymentAmount.amount), }) - if (lnInvoice instanceof Error) throw lnInvoice - const { paymentRequest: uncheckedPaymentRequest } = lnInvoice + if (invoice instanceof Error) throw invoice + const { paymentRequest: uncheckedPaymentRequest } = invoice.lnInvoice const paymentResult = await Payments.payInvoiceByWalletId({ uncheckedPaymentRequest, @@ -231,12 +231,12 @@ describe("UserWallet Limits - Lightning Pay", () => { // Test USD -> BTC limits { - const lnInvoice = await Wallets.addInvoiceForSelfForBtcWallet({ + const invoice = await Wallets.addInvoiceForSelfForBtcWallet({ walletId: btcWalletDescriptor.id, amount: Number(btcThresholdAmount.amount), }) - if (lnInvoice instanceof Error) throw lnInvoice - const { paymentRequest: uncheckedPaymentRequestBtc } = lnInvoice + if (invoice instanceof Error) throw invoice + const { paymentRequest: uncheckedPaymentRequestBtc } = invoice.lnInvoice const paymentResultUsd = await Payments.payInvoiceByWalletId({ uncheckedPaymentRequest: uncheckedPaymentRequestBtc, @@ -314,12 +314,12 @@ describe("UserWallet Limits - Lightning Pay", () => { numPayments, }) for (let i = 0; i < numPayments; i++) { - const lnInvoice = await Wallets.addInvoiceForSelfForBtcWallet({ + const invoice = await Wallets.addInvoiceForSelfForBtcWallet({ walletId: otherBtcWallet.id, amount: Number(partialBtcSendAmount.amount), }) - if (lnInvoice instanceof Error) throw lnInvoice - const { paymentRequest: request } = lnInvoice + if (invoice instanceof Error) throw invoice + const { paymentRequest: request } = invoice.lnInvoice const paymentResult = await Payments.payInvoiceByWalletId({ uncheckedPaymentRequest: request, @@ -340,12 +340,12 @@ describe("UserWallet Limits - Lightning Pay", () => { { // Fails for payment just above limit - const lnInvoice = await Wallets.addInvoiceForSelfForBtcWallet({ + const invoice = await Wallets.addInvoiceForSelfForBtcWallet({ walletId: otherBtcWallet.id, amount: Number(btcAmountAboveThreshold.amount), }) - if (lnInvoice instanceof Error) throw lnInvoice - const { paymentRequest: request } = lnInvoice + if (invoice instanceof Error) throw invoice + const { paymentRequest: request } = invoice.lnInvoice const paymentResult = await Payments.payInvoiceByWalletId({ uncheckedPaymentRequest: request, @@ -391,12 +391,12 @@ describe("UserWallet Limits - Lightning Pay", () => { { // Succeeds for same payment just above tradeIntraAccount limit - const lnInvoice = await Wallets.addInvoiceForSelfForUsdWallet({ + const invoice = await Wallets.addInvoiceForSelfForUsdWallet({ walletId: usdWalletDescriptor.id, amount: Number(usdAmountAboveThreshold.amount), }) - if (lnInvoice instanceof Error) throw lnInvoice - const { paymentRequest: uncheckedPaymentRequest } = lnInvoice + if (invoice instanceof Error) throw invoice + const { paymentRequest: uncheckedPaymentRequest } = invoice.lnInvoice const paymentResult = await Payments.payInvoiceByWalletId({ uncheckedPaymentRequest, @@ -444,12 +444,12 @@ describe("UserWallet Limits - Lightning Pay", () => { numPayments, }) { - const lnInvoice = await Wallets.addInvoiceForSelfForUsdWallet({ + const invoice = await Wallets.addInvoiceForSelfForUsdWallet({ walletId: usdWalletDescriptor.id, amount: Number(partialUsdSendAmount.amount), }) - if (lnInvoice instanceof Error) throw lnInvoice - const { paymentRequest: uncheckedPaymentRequest } = lnInvoice + if (invoice instanceof Error) throw invoice + const { paymentRequest: uncheckedPaymentRequest } = invoice.lnInvoice const paymentResult = await Payments.payInvoiceByWalletId({ uncheckedPaymentRequest, @@ -461,12 +461,12 @@ describe("UserWallet Limits - Lightning Pay", () => { expect(paymentResult).toBe(PaymentSendStatus.Success) } { - const lnInvoice = await Wallets.addInvoiceForSelfForBtcWallet({ + const invoice = await Wallets.addInvoiceForSelfForBtcWallet({ walletId: btcWalletDescriptor.id, amount: Number(partialBtcSendAmount.amount), }) - if (lnInvoice instanceof Error) throw lnInvoice - const { paymentRequest: uncheckedPaymentRequest } = lnInvoice + if (invoice instanceof Error) throw invoice + const { paymentRequest: uncheckedPaymentRequest } = invoice.lnInvoice const paymentResult = await Payments.payInvoiceByWalletId({ uncheckedPaymentRequest, @@ -487,12 +487,12 @@ describe("UserWallet Limits - Lightning Pay", () => { { // Fails for payment just above limit, from btc - const lnInvoice = await Wallets.addInvoiceForSelfForUsdWallet({ + const invoice = await Wallets.addInvoiceForSelfForUsdWallet({ walletId: usdWalletDescriptor.id, amount: Number(usdAmountAboveThreshold.amount), }) - if (lnInvoice instanceof Error) throw lnInvoice - const { paymentRequest: uncheckedPaymentRequest } = lnInvoice + if (invoice instanceof Error) throw invoice + const { paymentRequest: uncheckedPaymentRequest } = invoice.lnInvoice const paymentResult = await Payments.payInvoiceByWalletId({ uncheckedPaymentRequest, @@ -509,12 +509,12 @@ describe("UserWallet Limits - Lightning Pay", () => { { // Fails for payment just above limit, from usd - const lnInvoice = await Wallets.addInvoiceForSelfForBtcWallet({ + const invoice = await Wallets.addInvoiceForSelfForBtcWallet({ walletId: btcWalletDescriptor.id, amount: Number(btcAmountAboveThreshold.amount), }) - if (lnInvoice instanceof Error) throw lnInvoice - const { paymentRequest: uncheckedPaymentRequest } = lnInvoice + if (invoice instanceof Error) throw invoice + const { paymentRequest: uncheckedPaymentRequest } = invoice.lnInvoice const paymentResult = await Payments.payInvoiceByWalletId({ uncheckedPaymentRequest, @@ -548,12 +548,12 @@ describe("UserWallet Limits - Lightning Pay", () => { { // Succeeds for same payment just above intraledger limit - const lnInvoice = await Wallets.addInvoiceForSelfForBtcWallet({ + const invoice = await Wallets.addInvoiceForSelfForBtcWallet({ walletId: otherBtcWallet.id, amount: Number(btcAmountAboveThreshold.amount), }) - if (lnInvoice instanceof Error) throw lnInvoice - const { paymentRequest: uncheckedPaymentRequest } = lnInvoice + if (invoice instanceof Error) throw invoice + const { paymentRequest: uncheckedPaymentRequest } = invoice.lnInvoice const paymentResult = await Payments.payInvoiceByWalletId({ uncheckedPaymentRequest, @@ -645,12 +645,12 @@ describe("UserWallet Limits - Lightning Pay", () => { { // Succeeds for same payment just above intraledger limit - const lnInvoice = await Wallets.addInvoiceForSelfForBtcWallet({ + const invoice = await Wallets.addInvoiceForSelfForBtcWallet({ walletId: otherBtcWallet.id, amount: Number(btcAmountAboveThreshold.amount), }) - if (lnInvoice instanceof Error) throw lnInvoice - const { paymentRequest: uncheckedPaymentRequest } = lnInvoice + if (invoice instanceof Error) throw invoice + const { paymentRequest: uncheckedPaymentRequest } = invoice.lnInvoice const paymentResult = await Payments.payInvoiceByWalletId({ uncheckedPaymentRequest, @@ -664,12 +664,12 @@ describe("UserWallet Limits - Lightning Pay", () => { { // Succeeds for same payment just above tradeIntraAccount limit - const lnInvoice = await Wallets.addInvoiceForSelfForUsdWallet({ + const invoice = await Wallets.addInvoiceForSelfForUsdWallet({ walletId: usdWalletDescriptor.id, amount: Number(usdAmountAboveThreshold.amount), }) - if (lnInvoice instanceof Error) throw lnInvoice - const { paymentRequest: uncheckedPaymentRequest } = lnInvoice + if (invoice instanceof Error) throw invoice + const { paymentRequest: uncheckedPaymentRequest } = invoice.lnInvoice const paymentResult = await Payments.payInvoiceByWalletId({ uncheckedPaymentRequest, diff --git a/core/api/test/legacy-integration/02-user-wallet/02-tx-display.spec.ts b/core/api/test/legacy-integration/02-user-wallet/02-tx-display.spec.ts index 80d033cb77..0d55fa96a8 100644 --- a/core/api/test/legacy-integration/02-user-wallet/02-tx-display.spec.ts +++ b/core/api/test/legacy-integration/02-user-wallet/02-tx-display.spec.ts @@ -212,16 +212,16 @@ describe("Display properties on transactions", () => { // Receive payment const memo = "invoiceMemo #" + (Math.random() * 1_000_000).toFixed() - const lnInvoice = await Wallets.addInvoiceForSelfForBtcWallet({ + const invoice = await Wallets.addInvoiceForSelfForBtcWallet({ walletId: recipientWalletId, amount: amountInvoice, memo, }) - if (lnInvoice instanceof Error) throw lnInvoice - const { paymentRequest: invoice, paymentHash } = lnInvoice + if (invoice instanceof Error) throw invoice + const { paymentRequest, paymentHash } = invoice.lnInvoice const [outsideLndpayResult, updateResult] = await Promise.all([ - safePay({ lnd: lndOutside1, request: invoice }), + safePay({ lnd: lndOutside1, request: paymentRequest }), (async () => { // TODO: we could use event instead of a sleep to lower test latency await sleep(500) @@ -285,16 +285,16 @@ describe("Display properties on transactions", () => { // Send payment const memo = "invoiceMemo #" + (Math.random() * 1_000_000).toFixed() - const lnInvoice = await Wallets.addInvoiceForSelfForBtcWallet({ + const invoice = await Wallets.addInvoiceForSelfForBtcWallet({ walletId: recipientWalletId, amount: amountInvoice, memo, }) - if (lnInvoice instanceof Error) throw lnInvoice - const { paymentRequest: invoice } = lnInvoice + if (invoice instanceof Error) throw invoice + const { paymentRequest } = invoice.lnInvoice const paymentResult = await Payments.payInvoiceByWalletId({ - uncheckedPaymentRequest: invoice, + uncheckedPaymentRequest: paymentRequest, memo: null, senderWalletId: senderWalletId, senderAccount, @@ -302,7 +302,7 @@ describe("Display properties on transactions", () => { if (paymentResult instanceof Error) throw paymentResult // Check entries - const txns = await getAllTransactionsByHash(lnInvoice.paymentHash) + const txns = await getAllTransactionsByHash(invoice.paymentHash) if (txns instanceof Error) throw txns const senderTxn = txns.find(({ walletId }) => walletId === senderWalletId) @@ -347,12 +347,12 @@ describe("Display properties on transactions", () => { // Send payment const memo = "invoiceMemo #" + (Math.random() * 1_000_000).toFixed() - const request = await Wallets.addInvoiceNoAmountForSelf({ + const invoice = await Wallets.addInvoiceNoAmountForSelf({ walletId: recipientWalletId, memo, }) - if (request instanceof Error) throw request - const { paymentRequest: uncheckedPaymentRequest, paymentHash } = request + if (invoice instanceof Error) throw invoice + const { paymentRequest: uncheckedPaymentRequest, paymentHash } = invoice.lnInvoice const paymentResult = await Payments.payNoAmountInvoiceByWalletIdForBtcWallet({ uncheckedPaymentRequest, diff --git a/core/api/test/legacy-integration/app/wallets/invoice.spec.ts b/core/api/test/legacy-integration/app/wallets/invoice.spec.ts index 80f2f20d06..9d527ff6d3 100644 --- a/core/api/test/legacy-integration/app/wallets/invoice.spec.ts +++ b/core/api/test/legacy-integration/app/wallets/invoice.spec.ts @@ -59,12 +59,12 @@ describe("Wallet - addInvoice BTC", () => { it("add a self generated invoice", async () => { const amountInput = 1000 - const lnInvoice = await Wallets.addInvoiceForSelfForBtcWallet({ + const invoice = await Wallets.addInvoiceForSelfForBtcWallet({ walletId: walletIdBtc, amount: amountInput, }) - if (lnInvoice instanceof Error) throw lnInvoice - const { paymentRequest: request } = lnInvoice + if (invoice instanceof Error) throw invoice + const { paymentRequest: request } = invoice.lnInvoice expect(request.startsWith("lnbcrt10")).toBeTruthy() const result = await walletInvoices.findByPaymentHash(getHash(request)) @@ -85,11 +85,11 @@ describe("Wallet - addInvoice BTC", () => { }) it("add a self generated invoice without amount", async () => { - const lnInvoice = await Wallets.addInvoiceNoAmountForSelf({ + const invoice = await Wallets.addInvoiceNoAmountForSelf({ walletId: walletIdBtc, }) - if (lnInvoice instanceof Error) throw lnInvoice - const { paymentRequest: request } = lnInvoice + if (invoice instanceof Error) throw invoice + const { paymentRequest: request } = invoice.lnInvoice const result = await walletInvoices.findByPaymentHash(getHash(request)) if (result instanceof Error) throw result @@ -105,12 +105,12 @@ describe("Wallet - addInvoice BTC", () => { it("adds a public with amount invoice", async () => { const amountInput = 10 - const lnInvoice = await Wallets.addInvoiceForRecipientForBtcWallet({ + const invoice = await Wallets.addInvoiceForRecipientForBtcWallet({ recipientWalletId: walletIdBtc, amount: amountInput, }) - if (lnInvoice instanceof Error) throw lnInvoice - const { paymentRequest: request } = lnInvoice + if (invoice instanceof Error) throw invoice + const { paymentRequest: request } = invoice.lnInvoice expect(request.startsWith("lnbcrt1")).toBeTruthy() const result = await walletInvoices.findByPaymentHash(getHash(request)) @@ -132,11 +132,11 @@ describe("Wallet - addInvoice BTC", () => { }) it("adds a public no amount invoice", async () => { - const lnInvoice = await Wallets.addInvoiceNoAmountForRecipient({ + const invoice = await Wallets.addInvoiceNoAmountForRecipient({ recipientWalletId: walletIdBtc, }) - if (lnInvoice instanceof Error) throw lnInvoice - const { paymentRequest: request } = lnInvoice + if (invoice instanceof Error) throw invoice + const { paymentRequest: request } = invoice.lnInvoice expect(request.startsWith("lnbcrt1")).toBeTruthy() const result = await walletInvoices.findByPaymentHash(getHash(request)) @@ -161,12 +161,12 @@ describe("Wallet - addInvoice USD", () => { if (btcAmount instanceof Error) throw btcAmount const sats = Number(btcAmount.amount) - const lnInvoice = await Wallets.addInvoiceForSelfForUsdWallet({ + const invoice = await Wallets.addInvoiceForSelfForUsdWallet({ walletId: walletIdUsd, amount: toCents(centsInput), }) - if (lnInvoice instanceof Error) throw lnInvoice - const { paymentRequest: request } = lnInvoice + if (invoice instanceof Error) throw invoice + const { paymentRequest: request } = invoice.lnInvoice expect(request.startsWith("lnbcrt")).toBeTruthy() const result = await walletInvoices.findByPaymentHash(getHash(request)) @@ -188,11 +188,11 @@ describe("Wallet - addInvoice USD", () => { }) it("add a self generated invoice without amount", async () => { - const lnInvoice = await Wallets.addInvoiceNoAmountForSelf({ + const invoice = await Wallets.addInvoiceNoAmountForSelf({ walletId: walletIdUsd, }) - if (lnInvoice instanceof Error) throw lnInvoice - const { paymentRequest: request } = lnInvoice + if (invoice instanceof Error) throw invoice + const { paymentRequest: request } = invoice.lnInvoice const result = await walletInvoices.findByPaymentHash(getHash(request)) if (result instanceof Error) throw result @@ -214,12 +214,12 @@ describe("Wallet - addInvoice USD", () => { if (btcAmount instanceof Error) throw btcAmount const sats = Number(btcAmount.amount) - const lnInvoice = await Wallets.addInvoiceForRecipientForUsdWallet({ + const invoice = await Wallets.addInvoiceForRecipientForUsdWallet({ recipientWalletId: walletIdUsd, amount: toCents(centsInput), }) - if (lnInvoice instanceof Error) throw lnInvoice - const { paymentRequest: request } = lnInvoice + if (invoice instanceof Error) throw invoice + const { paymentRequest: request } = invoice.lnInvoice expect(request.startsWith("lnbcrt")).toBeTruthy() const result = await walletInvoices.findByPaymentHash(getHash(request)) @@ -242,11 +242,11 @@ describe("Wallet - addInvoice USD", () => { }) it("adds a public invoice", async () => { - const lnInvoice = await Wallets.addInvoiceNoAmountForRecipient({ + const invoice = await Wallets.addInvoiceNoAmountForRecipient({ recipientWalletId: walletIdUsd, }) - if (lnInvoice instanceof Error) throw lnInvoice - const { paymentRequest: request } = lnInvoice + if (invoice instanceof Error) throw invoice + const { paymentRequest: request } = invoice.lnInvoice expect(request.startsWith("lnbcrt1")).toBeTruthy() const result = await walletInvoices.findByPaymentHash(getHash(request)) @@ -272,17 +272,17 @@ describe("Wallet - rate limiting test", () => { // Create max number of invoices const limitsNum = getInvoiceCreateAttemptLimits().points - const promises: Promise[] = [] + const promises: Promise[] = [] for (let i = 0; i < limitsNum; i++) { - const lnInvoicePromise = Wallets.addInvoiceForSelfForBtcWallet({ + const invoicePromise = Wallets.addInvoiceForSelfForBtcWallet({ walletId: walletIdBtc, amount: 1000, }) - promises.push(lnInvoicePromise) + promises.push(invoicePromise) } - const lnInvoices = await Promise.all(promises) + const invoices = await Promise.all(promises) const isNotError = (item: unknown) => !(item instanceof Error) - expect(lnInvoices.every(isNotError)).toBe(true) + expect(invoices.every(isNotError)).toBe(true) return testPastSelfInvoiceLimits({ walletId: walletIdBtc, accountId: accountIdB }) }) @@ -295,16 +295,16 @@ describe("Wallet - rate limiting test", () => { // Create max number of invoices const limitsNum = getInvoiceCreateAttemptLimits().points - const promises: Promise[] = [] + const promises: Promise[] = [] for (let i = 0; i < limitsNum; i++) { - const lnInvoicePromise = Wallets.addInvoiceNoAmountForSelf({ + const invoicePromise = Wallets.addInvoiceNoAmountForSelf({ walletId: walletIdBtc, }) - promises.push(lnInvoicePromise) + promises.push(invoicePromise) } - const lnInvoices = await Promise.all(promises) + const invoices = await Promise.all(promises) const isNotError = (item: unknown) => !(item instanceof Error) - expect(lnInvoices.every(isNotError)).toBe(true) + expect(invoices.every(isNotError)).toBe(true) return testPastSelfInvoiceLimits({ walletId: walletIdBtc, accountId: accountIdB }) }) @@ -317,17 +317,17 @@ describe("Wallet - rate limiting test", () => { // Create max number of invoices const limitsNum = getInvoiceCreateForRecipientAttemptLimits().points - const promises: Promise[] = [] + const promises: Promise[] = [] for (let i = 0; i < limitsNum; i++) { - const lnInvoicePromise = Wallets.addInvoiceForRecipientForBtcWallet({ + const invoicePromise = Wallets.addInvoiceForRecipientForBtcWallet({ recipientWalletId: walletIdBtc, amount: 1000, }) - promises.push(lnInvoicePromise) + promises.push(invoicePromise) } - const lnInvoices = await Promise.all(promises) + const invoices = await Promise.all(promises) const isNotError = (item: unknown) => !(item instanceof Error) - expect(lnInvoices.every(isNotError)).toBe(true) + expect(invoices.every(isNotError)).toBe(true) return testPastRecipientInvoiceLimits({ walletId: walletIdBtc, @@ -343,16 +343,16 @@ describe("Wallet - rate limiting test", () => { // Create max number of invoices const limitsNum = getInvoiceCreateForRecipientAttemptLimits().points - const promises: Promise[] = [] + const promises: Promise[] = [] for (let i = 0; i < limitsNum; i++) { - const lnInvoicePromise = Wallets.addInvoiceNoAmountForRecipient({ + const invoicePromise = Wallets.addInvoiceNoAmountForRecipient({ recipientWalletId: walletIdBtc, }) - promises.push(lnInvoicePromise) + promises.push(invoicePromise) } - const lnInvoices = await Promise.all(promises) + const invoices = await Promise.all(promises) const isNotError = (item: unknown) => !(item instanceof Error) - expect(lnInvoices.every(isNotError)).toBe(true) + expect(invoices.every(isNotError)).toBe(true) return testPastRecipientInvoiceLimits({ walletId: walletIdBtc, @@ -369,11 +369,11 @@ const testPastSelfInvoiceLimits = async ({ accountId: AccountId }) => { // Test that first invoice past the limit fails - const lnInvoice = await Wallets.addInvoiceForSelfForBtcWallet({ + const invoice = await Wallets.addInvoiceForSelfForBtcWallet({ walletId, amount: 1000, }) - expect(lnInvoice).toBeInstanceOf(InvoiceCreateRateLimiterExceededError) + expect(invoice).toBeInstanceOf(InvoiceCreateRateLimiterExceededError) const lnNoAmountInvoice = await Wallets.addInvoiceNoAmountForSelf({ walletId, @@ -385,14 +385,14 @@ const testPastSelfInvoiceLimits = async ({ recipientWalletId: walletId, amount: 1000, }) - expect(lnRecipientInvoice).not.toBeInstanceOf(Error) - expect(lnRecipientInvoice).toHaveProperty("paymentRequest") + if (lnRecipientInvoice instanceof Error) throw lnRecipientInvoice + expect(lnRecipientInvoice.lnInvoice).toHaveProperty("paymentRequest") const lnNoAmountRecipientInvoice = await Wallets.addInvoiceNoAmountForRecipient({ recipientWalletId: walletId, }) - expect(lnNoAmountRecipientInvoice).not.toBeInstanceOf(Error) - expect(lnNoAmountRecipientInvoice).toHaveProperty("paymentRequest") + if (lnNoAmountRecipientInvoice instanceof Error) throw lnNoAmountRecipientInvoice + expect(lnNoAmountRecipientInvoice.lnInvoice).toHaveProperty("paymentRequest") // Reset limits when done for other tests let resetOk = await resetSelfAccountIdLimits(accountId) @@ -425,18 +425,18 @@ const testPastRecipientInvoiceLimits = async ({ ) // Test that recipient invoices still work - const lnInvoice = await Wallets.addInvoiceForSelfForBtcWallet({ + const invoice = await Wallets.addInvoiceForSelfForBtcWallet({ walletId, amount: 1000, }) - expect(lnInvoice).not.toBeInstanceOf(Error) - expect(lnInvoice).toHaveProperty("paymentRequest") + if (invoice instanceof Error) throw invoice + expect(invoice.lnInvoice).toHaveProperty("paymentRequest") const lnNoAmountInvoice = await Wallets.addInvoiceNoAmountForSelf({ walletId, }) - expect(lnNoAmountInvoice).not.toBeInstanceOf(Error) - expect(lnNoAmountInvoice).toHaveProperty("paymentRequest") + if (lnNoAmountInvoice instanceof Error) throw lnNoAmountInvoice + expect(lnNoAmountInvoice.lnInvoice).toHaveProperty("paymentRequest") // Reset limits when done for other tests let resetOk = await resetSelfAccountIdLimits(accountId) diff --git a/core/api/test/legacy-integration/services/dealer/hedge-price.spec.ts b/core/api/test/legacy-integration/services/dealer/hedge-price.spec.ts index 6c9be07793..3fdacfb27f 100644 --- a/core/api/test/legacy-integration/services/dealer/hedge-price.spec.ts +++ b/core/api/test/legacy-integration/services/dealer/hedge-price.spec.ts @@ -133,15 +133,15 @@ const getUsdEquivalentForWithAmountInvoiceSendToBtc = async ({ accountAndWallets: AccountAndWallets }): Promise => { const { newBtcWallet, newUsdWallet, newAccount } = accountAndWallets - const lnInvoice = await Wallets.addInvoiceForSelfForBtcWallet({ + const invoice = await Wallets.addInvoiceForSelfForBtcWallet({ walletId: newBtcWallet.id, amount: toSats(btcPaymentAmount.amount), }) - if (lnInvoice instanceof Error) throw lnInvoice + if (invoice instanceof Error) throw invoice const beforeUsd = await getBalanceHelper(newUsdWallet.id) const result = await Payments.payInvoiceByWalletId({ - uncheckedPaymentRequest: lnInvoice.paymentRequest, + uncheckedPaymentRequest: invoice.lnInvoice.paymentRequest, memo: null, senderWalletId: newUsdWallet.id, senderAccount: newAccount, @@ -160,20 +160,20 @@ const getUsdEquivalentForWithAmountInvoiceProbeAndSendToBtc = async ({ }): Promise => { const { newBtcWallet, newUsdWallet, newAccount } = accountAndWallets - const lnInvoice = await Wallets.addInvoiceForSelfForBtcWallet({ + const invoice = await Wallets.addInvoiceForSelfForBtcWallet({ walletId: newBtcWallet.id, amount: toSats(btcPaymentAmount.amount), }) - if (lnInvoice instanceof Error) throw lnInvoice + if (invoice instanceof Error) throw invoice const beforeUsd = await getBalanceHelper(newUsdWallet.id) const probeResult = await Payments.getLightningFeeEstimationForUsdWallet({ - uncheckedPaymentRequest: lnInvoice.paymentRequest, + uncheckedPaymentRequest: invoice.lnInvoice.paymentRequest, walletId: newUsdWallet.id, }) if (probeResult instanceof Error) throw probeResult const payResult = await Payments.payInvoiceByWalletId({ - uncheckedPaymentRequest: lnInvoice.paymentRequest, + uncheckedPaymentRequest: invoice.lnInvoice.paymentRequest, memo: null, senderWalletId: newUsdWallet.id, senderAccount: newAccount, @@ -193,15 +193,15 @@ const getBtcEquivalentForNoAmountInvoiceSendToUsd = async ({ const { newBtcWallet, newUsdWallet, newAccount } = accountAndWallets const beforeRecipientUsd = await getBalanceHelper(newUsdWallet.id) - const lnInvoice = await Wallets.addInvoiceNoAmountForSelf({ + const invoice = await Wallets.addInvoiceNoAmountForSelf({ walletId: newUsdWallet.id, }) - if (lnInvoice instanceof Error) throw lnInvoice + if (invoice instanceof Error) throw invoice const beforeBtc = await getBalanceHelper(newBtcWallet.id) const result = await Payments.payNoAmountInvoiceByWalletIdForBtcWallet({ amount: Number(btcPaymentAmount.amount), - uncheckedPaymentRequest: lnInvoice.paymentRequest, + uncheckedPaymentRequest: invoice.lnInvoice.paymentRequest, memo: null, senderWalletId: newBtcWallet.id, senderAccount: newAccount, @@ -230,16 +230,16 @@ const getBtcEquivalentForNoAmountInvoiceProbeAndSendToUsd = async ({ const { newBtcWallet, newUsdWallet, newAccount } = accountAndWallets const beforeRecipientUsd = await getBalanceHelper(newUsdWallet.id) - const lnInvoice = await Wallets.addInvoiceNoAmountForSelf({ + const invoice = await Wallets.addInvoiceNoAmountForSelf({ walletId: newUsdWallet.id, }) - if (lnInvoice instanceof Error) throw lnInvoice + if (invoice instanceof Error) throw invoice const beforeBtc = await getBalanceHelper(newBtcWallet.id) const probe = await Payments.getNoAmountLightningFeeEstimationForBtcWallet({ amount: Number(btcPaymentAmount.amount), - uncheckedPaymentRequest: lnInvoice.paymentRequest, + uncheckedPaymentRequest: invoice.lnInvoice.paymentRequest, walletId: newBtcWallet.id, }) if (probe instanceof SubOneCentSatAmountForUsdSelfSendError) { @@ -249,7 +249,7 @@ const getBtcEquivalentForNoAmountInvoiceProbeAndSendToUsd = async ({ const result = await Payments.payNoAmountInvoiceByWalletIdForBtcWallet({ amount: Number(btcPaymentAmount.amount), - uncheckedPaymentRequest: lnInvoice.paymentRequest, + uncheckedPaymentRequest: invoice.lnInvoice.paymentRequest, memo: null, senderWalletId: newBtcWallet.id, senderAccount: newAccount, @@ -421,15 +421,15 @@ describe("arbitrage strategies", () => { const usdBalanceBefore = await getBalanceHelper(newUsdWallet.id) // Step 1: Create invoice from BTC Wallet using discovered 'maxBtcAmountToEarn' from $0.01 - const lnInvoice = await Wallets.addInvoiceForSelfForBtcWallet({ + const invoice = await Wallets.addInvoiceForSelfForBtcWallet({ walletId: newBtcWallet.id, amount: toSats(maxBtcAmountToEarn.amount), }) - if (lnInvoice instanceof Error) throw lnInvoice + if (invoice instanceof Error) throw invoice // Step 2: Pay invoice from USD wallet at favourable rate const result = await Payments.payInvoiceByWalletId({ - uncheckedPaymentRequest: lnInvoice.paymentRequest, + uncheckedPaymentRequest: invoice.lnInvoice.paymentRequest, memo: null, senderWalletId: newUsdWallet.id, senderAccount: newAccount, @@ -437,14 +437,14 @@ describe("arbitrage strategies", () => { if (result instanceof Error) throw result // Step 3: Replenish USD from BTC wallet with $0.01 invoice - const lnInvoiceUsd = await Wallets.addInvoiceForSelfForUsdWallet({ + const invoiceUsd = await Wallets.addInvoiceForSelfForUsdWallet({ walletId: newUsdWallet.id, amount: toCents(1), }) - if (lnInvoiceUsd instanceof Error) throw lnInvoiceUsd + if (invoiceUsd instanceof Error) throw invoiceUsd const resultUsd = await Payments.payInvoiceByWalletId({ - uncheckedPaymentRequest: lnInvoiceUsd.paymentRequest, + uncheckedPaymentRequest: invoiceUsd.lnInvoice.paymentRequest, memo: null, senderWalletId: newBtcWallet.id, senderAccount: newAccount, @@ -485,15 +485,15 @@ describe("arbitrage strategies", () => { const usdBalanceBefore = await getBalanceHelper(newUsdWallet.id) // Step 1: Create invoice from BTC Wallet using discovered 'maxBtcAmountToEarn' from $0.01 - const lnInvoice = await Wallets.addInvoiceForSelfForBtcWallet({ + const invoice = await Wallets.addInvoiceForSelfForBtcWallet({ walletId: newBtcWallet.id, amount: toSats(maxBtcAmountToEarn.amount), }) - if (lnInvoice instanceof Error) throw lnInvoice + if (invoice instanceof Error) throw invoice // Step 2: Pay invoice from USD wallet at favourable rate const result = await Payments.payInvoiceByWalletId({ - uncheckedPaymentRequest: lnInvoice.paymentRequest, + uncheckedPaymentRequest: invoice.lnInvoice.paymentRequest, memo: null, senderWalletId: newUsdWallet.id, senderAccount: newAccount, @@ -501,20 +501,20 @@ describe("arbitrage strategies", () => { if (result instanceof Error) throw result // Step 3: Replenish USD from BTC wallet with $0.01 invoice - const lnInvoiceUsd = await Wallets.addInvoiceForSelfForUsdWallet({ + const invoiceUsd = await Wallets.addInvoiceForSelfForUsdWallet({ walletId: newUsdWallet.id, amount: toCents(1), }) - if (lnInvoiceUsd instanceof Error) throw lnInvoiceUsd + if (invoiceUsd instanceof Error) throw invoiceUsd const probe = await Payments.getLightningFeeEstimationForBtcWallet({ - uncheckedPaymentRequest: lnInvoiceUsd.paymentRequest, + uncheckedPaymentRequest: invoiceUsd.lnInvoice.paymentRequest, walletId: newBtcWallet.id, }) if (probe instanceof Error) throw probe const resultUsd = await Payments.payInvoiceByWalletId({ - uncheckedPaymentRequest: lnInvoiceUsd.paymentRequest, + uncheckedPaymentRequest: invoiceUsd.lnInvoice.paymentRequest, memo: null, senderWalletId: newBtcWallet.id, senderAccount: newAccount, @@ -566,15 +566,15 @@ describe("arbitrage strategies", () => { const usdBalanceBefore = await getBalanceHelper(newUsdWallet.id) // Step 1: Create invoice from BTC Wallet using discovered 'maxBtcAmountToEarn' from $0.01 - const lnInvoice = await Wallets.addInvoiceForSelfForBtcWallet({ + const invoice = await Wallets.addInvoiceForSelfForBtcWallet({ walletId: newBtcWallet.id, amount: toSats(maxBtcAmountToEarn.amount), }) - if (lnInvoice instanceof Error) throw lnInvoice + if (invoice instanceof Error) throw invoice // Step 2: Pay invoice from USD wallet at favourable rate const result = await Payments.payInvoiceByWalletId({ - uncheckedPaymentRequest: lnInvoice.paymentRequest, + uncheckedPaymentRequest: invoice.lnInvoice.paymentRequest, memo: null, senderWalletId: newUsdWallet.id, senderAccount: newAccount, @@ -638,15 +638,15 @@ describe("arbitrage strategies", () => { const usdBalanceBefore = await getBalanceHelper(newUsdWallet.id) // Step 1: Create invoice from BTC Wallet using discovered 'maxBtcAmountToEarn' from $0.01 - const lnInvoice = await Wallets.addInvoiceForSelfForBtcWallet({ + const invoice = await Wallets.addInvoiceForSelfForBtcWallet({ walletId: newBtcWallet.id, amount: toSats(maxBtcAmountToEarn.amount), }) - if (lnInvoice instanceof Error) throw lnInvoice + if (invoice instanceof Error) throw invoice // Step 2: Pay invoice from USD wallet at favourable rate const result = await Payments.payInvoiceByWalletId({ - uncheckedPaymentRequest: lnInvoice.paymentRequest, + uncheckedPaymentRequest: invoice.lnInvoice.paymentRequest, memo: null, senderWalletId: newUsdWallet.id, senderAccount: newAccount, @@ -654,14 +654,14 @@ describe("arbitrage strategies", () => { if (result instanceof Error) throw result // Step 3: Replenish USD from BTC wallet with discovered 'minBtcAmountToSpend' for $0.01 - const lnInvoiceNoAmountUsd = await Wallets.addInvoiceNoAmountForSelf({ + const invoiceNoAmountUsd = await Wallets.addInvoiceNoAmountForSelf({ walletId: newUsdWallet.id, }) - if (lnInvoiceNoAmountUsd instanceof Error) throw lnInvoiceNoAmountUsd + if (invoiceNoAmountUsd instanceof Error) throw invoiceNoAmountUsd const repaid = await Payments.payNoAmountInvoiceByWalletIdForBtcWallet({ amount: toSats(minBtcAmountToSpend.amount), - uncheckedPaymentRequest: lnInvoiceNoAmountUsd.paymentRequest, + uncheckedPaymentRequest: invoiceNoAmountUsd.lnInvoice.paymentRequest, memo: null, senderWalletId: newBtcWallet.id, senderAccount: newAccount, @@ -710,15 +710,15 @@ describe("arbitrage strategies", () => { const usdBalanceBefore = await getBalanceHelper(newUsdWallet.id) // Step 1: Create invoice from BTC Wallet using discovered 'maxBtcAmountToEarn' from $0.01 - const lnInvoice = await Wallets.addInvoiceForSelfForBtcWallet({ + const invoice = await Wallets.addInvoiceForSelfForBtcWallet({ walletId: newBtcWallet.id, amount: toSats(maxBtcAmountToEarn.amount), }) - if (lnInvoice instanceof Error) throw lnInvoice + if (invoice instanceof Error) throw invoice // Step 2: Pay invoice from USD wallet at favourable rate const result = await Payments.payInvoiceByWalletId({ - uncheckedPaymentRequest: lnInvoice.paymentRequest, + uncheckedPaymentRequest: invoice.lnInvoice.paymentRequest, memo: null, senderWalletId: newUsdWallet.id, senderAccount: newAccount, @@ -726,21 +726,21 @@ describe("arbitrage strategies", () => { if (result instanceof Error) throw result // Step 3: Replenish USD from BTC wallet with discovered 'minBtcAmountToSpend' for $0.01 - const lnInvoiceNoAmountUsd = await Wallets.addInvoiceNoAmountForSelf({ + const invoiceNoAmountUsd = await Wallets.addInvoiceNoAmountForSelf({ walletId: newUsdWallet.id, }) - if (lnInvoiceNoAmountUsd instanceof Error) throw lnInvoiceNoAmountUsd + if (invoiceNoAmountUsd instanceof Error) throw invoiceNoAmountUsd const probe = await Payments.getNoAmountLightningFeeEstimationForBtcWallet({ amount: toSats(minBtcAmountToSpend.amount), - uncheckedPaymentRequest: lnInvoiceNoAmountUsd.paymentRequest, + uncheckedPaymentRequest: invoiceNoAmountUsd.lnInvoice.paymentRequest, walletId: newBtcWallet.id, }) if (probe instanceof Error) throw probe const repaid = await Payments.payNoAmountInvoiceByWalletIdForBtcWallet({ amount: toSats(minBtcAmountToSpend.amount), - uncheckedPaymentRequest: lnInvoiceNoAmountUsd.paymentRequest, + uncheckedPaymentRequest: invoiceNoAmountUsd.lnInvoice.paymentRequest, memo: null, senderWalletId: newBtcWallet.id, senderAccount: newAccount, @@ -785,21 +785,21 @@ describe("arbitrage strategies", () => { const usdBalanceBefore = await getBalanceHelper(newUsdWallet.id) // Step 1: Create invoice from BTC Wallet using discovered 'maxBtcAmountToEarn' from $0.01 - const lnInvoice = await Wallets.addInvoiceForSelfForBtcWallet({ + const invoice = await Wallets.addInvoiceForSelfForBtcWallet({ walletId: newBtcWallet.id, amount: toSats(maxBtcAmountToEarn.amount), }) - if (lnInvoice instanceof Error) throw lnInvoice + if (invoice instanceof Error) throw invoice // Step 2: Pay invoice from USD wallet at favourable rate const probe = await Payments.getLightningFeeEstimationForUsdWallet({ - uncheckedPaymentRequest: lnInvoice.paymentRequest, + uncheckedPaymentRequest: invoice.lnInvoice.paymentRequest, walletId: newUsdWallet.id, }) if (probe instanceof Error) throw probe const result = await Payments.payInvoiceByWalletId({ - uncheckedPaymentRequest: lnInvoice.paymentRequest, + uncheckedPaymentRequest: invoice.lnInvoice.paymentRequest, memo: null, senderWalletId: newUsdWallet.id, senderAccount: newAccount, @@ -807,14 +807,14 @@ describe("arbitrage strategies", () => { if (result instanceof Error) throw result // Step 3: Replenish USD from BTC wallet with $0.01 invoice - const lnInvoiceUsd = await Wallets.addInvoiceForSelfForUsdWallet({ + const invoiceUsd = await Wallets.addInvoiceForSelfForUsdWallet({ walletId: newUsdWallet.id, amount: toCents(1), }) - if (lnInvoiceUsd instanceof Error) throw lnInvoiceUsd + if (invoiceUsd instanceof Error) throw invoiceUsd const resultUsd = await Payments.payInvoiceByWalletId({ - uncheckedPaymentRequest: lnInvoiceUsd.paymentRequest, + uncheckedPaymentRequest: invoiceUsd.lnInvoice.paymentRequest, memo: null, senderWalletId: newBtcWallet.id, senderAccount: newAccount, @@ -855,21 +855,21 @@ describe("arbitrage strategies", () => { const usdBalanceBefore = await getBalanceHelper(newUsdWallet.id) // Step 1: Create invoice from BTC Wallet using discovered 'maxBtcAmountToEarn' from $0.01 - const lnInvoice = await Wallets.addInvoiceForSelfForBtcWallet({ + const invoice = await Wallets.addInvoiceForSelfForBtcWallet({ walletId: newBtcWallet.id, amount: toSats(maxBtcAmountToEarn.amount), }) - if (lnInvoice instanceof Error) throw lnInvoice + if (invoice instanceof Error) throw invoice // Step 2: Pay invoice from USD wallet at favourable rate const probe = await Payments.getLightningFeeEstimationForUsdWallet({ - uncheckedPaymentRequest: lnInvoice.paymentRequest, + uncheckedPaymentRequest: invoice.lnInvoice.paymentRequest, walletId: newUsdWallet.id, }) if (probe instanceof Error) throw probe const result = await Payments.payInvoiceByWalletId({ - uncheckedPaymentRequest: lnInvoice.paymentRequest, + uncheckedPaymentRequest: invoice.lnInvoice.paymentRequest, memo: null, senderWalletId: newUsdWallet.id, senderAccount: newAccount, @@ -877,20 +877,20 @@ describe("arbitrage strategies", () => { if (result instanceof Error) throw result // Step 3: Replenish USD from BTC wallet with $0.01 invoice - const lnInvoiceUsd = await Wallets.addInvoiceForSelfForUsdWallet({ + const invoiceUsd = await Wallets.addInvoiceForSelfForUsdWallet({ walletId: newUsdWallet.id, amount: toCents(1), }) - if (lnInvoiceUsd instanceof Error) throw lnInvoiceUsd + if (invoiceUsd instanceof Error) throw invoiceUsd const probeUsd = await Payments.getLightningFeeEstimationForBtcWallet({ - uncheckedPaymentRequest: lnInvoiceUsd.paymentRequest, + uncheckedPaymentRequest: invoiceUsd.lnInvoice.paymentRequest, walletId: newBtcWallet.id, }) if (probeUsd instanceof Error) throw probeUsd const resultUsd = await Payments.payInvoiceByWalletId({ - uncheckedPaymentRequest: lnInvoiceUsd.paymentRequest, + uncheckedPaymentRequest: invoiceUsd.lnInvoice.paymentRequest, memo: null, senderWalletId: newBtcWallet.id, senderAccount: newAccount, @@ -942,21 +942,21 @@ describe("arbitrage strategies", () => { const usdBalanceBefore = await getBalanceHelper(newUsdWallet.id) // Step 1: Create invoice from BTC Wallet using discovered 'maxBtcAmountToEarn' from $0.01 - const lnInvoice = await Wallets.addInvoiceForSelfForBtcWallet({ + const invoice = await Wallets.addInvoiceForSelfForBtcWallet({ walletId: newBtcWallet.id, amount: toSats(maxBtcAmountToEarn.amount), }) - if (lnInvoice instanceof Error) throw lnInvoice + if (invoice instanceof Error) throw invoice // Step 2: Pay invoice from USD wallet at favourable rate const probeResult = await Payments.getLightningFeeEstimationForUsdWallet({ - uncheckedPaymentRequest: lnInvoice.paymentRequest, + uncheckedPaymentRequest: invoice.lnInvoice.paymentRequest, walletId: newUsdWallet.id, }) if (probeResult instanceof Error) throw probeResult const result = await Payments.payInvoiceByWalletId({ - uncheckedPaymentRequest: lnInvoice.paymentRequest, + uncheckedPaymentRequest: invoice.lnInvoice.paymentRequest, memo: null, senderWalletId: newUsdWallet.id, senderAccount: newAccount, @@ -1020,21 +1020,21 @@ describe("arbitrage strategies", () => { const usdBalanceBefore = await getBalanceHelper(newUsdWallet.id) // Step 1: Create invoice from BTC Wallet using discovered 'maxBtcAmountToEarn' from $0.01 - const lnInvoice = await Wallets.addInvoiceForSelfForBtcWallet({ + const invoice = await Wallets.addInvoiceForSelfForBtcWallet({ walletId: newBtcWallet.id, amount: toSats(maxBtcAmountToEarn.amount), }) - if (lnInvoice instanceof Error) throw lnInvoice + if (invoice instanceof Error) throw invoice // Step 2: Pay invoice from USD wallet at favourable rate const probe = await Payments.getLightningFeeEstimationForUsdWallet({ - uncheckedPaymentRequest: lnInvoice.paymentRequest, + uncheckedPaymentRequest: invoice.lnInvoice.paymentRequest, walletId: newUsdWallet.id, }) if (probe instanceof Error) throw probe const result = await Payments.payInvoiceByWalletId({ - uncheckedPaymentRequest: lnInvoice.paymentRequest, + uncheckedPaymentRequest: invoice.lnInvoice.paymentRequest, memo: null, senderWalletId: newUsdWallet.id, senderAccount: newAccount, @@ -1042,14 +1042,14 @@ describe("arbitrage strategies", () => { if (result instanceof Error) throw result // Step 3: Replenish USD from BTC wallet with discovered 'minBtcAmountToSpend' for $0.01 - const lnInvoiceNoAmountUsd = await Wallets.addInvoiceNoAmountForSelf({ + const invoiceNoAmountUsd = await Wallets.addInvoiceNoAmountForSelf({ walletId: newUsdWallet.id, }) - if (lnInvoiceNoAmountUsd instanceof Error) throw lnInvoiceNoAmountUsd + if (invoiceNoAmountUsd instanceof Error) throw invoiceNoAmountUsd const repaid = await Payments.payNoAmountInvoiceByWalletIdForBtcWallet({ amount: toSats(minBtcAmountToSpend.amount), - uncheckedPaymentRequest: lnInvoiceNoAmountUsd.paymentRequest, + uncheckedPaymentRequest: invoiceNoAmountUsd.lnInvoice.paymentRequest, memo: null, senderWalletId: newBtcWallet.id, senderAccount: newAccount, @@ -1098,21 +1098,21 @@ describe("arbitrage strategies", () => { const usdBalanceBefore = await getBalanceHelper(newUsdWallet.id) // Step 1: Create invoice from BTC Wallet using discovered 'maxBtcAmountToEarn' from $0.01 - const lnInvoice = await Wallets.addInvoiceForSelfForBtcWallet({ + const invoice = await Wallets.addInvoiceForSelfForBtcWallet({ walletId: newBtcWallet.id, amount: toSats(maxBtcAmountToEarn.amount), }) - if (lnInvoice instanceof Error) throw lnInvoice + if (invoice instanceof Error) throw invoice // Step 2: Pay invoice from USD wallet at favourable rate const probe = await Payments.getLightningFeeEstimationForUsdWallet({ - uncheckedPaymentRequest: lnInvoice.paymentRequest, + uncheckedPaymentRequest: invoice.lnInvoice.paymentRequest, walletId: newUsdWallet.id, }) if (probe instanceof Error) throw probe const result = await Payments.payInvoiceByWalletId({ - uncheckedPaymentRequest: lnInvoice.paymentRequest, + uncheckedPaymentRequest: invoice.lnInvoice.paymentRequest, memo: null, senderWalletId: newUsdWallet.id, senderAccount: newAccount, @@ -1120,21 +1120,21 @@ describe("arbitrage strategies", () => { if (result instanceof Error) throw result // Step 3: Replenish USD from BTC wallet with discovered 'minBtcAmountToSpend' for $0.01 - const lnInvoiceNoAmountUsd = await Wallets.addInvoiceNoAmountForSelf({ + const invoiceNoAmountUsd = await Wallets.addInvoiceNoAmountForSelf({ walletId: newUsdWallet.id, }) - if (lnInvoiceNoAmountUsd instanceof Error) throw lnInvoiceNoAmountUsd + if (invoiceNoAmountUsd instanceof Error) throw invoiceNoAmountUsd const probeUsd = await Payments.getNoAmountLightningFeeEstimationForBtcWallet({ amount: toSats(minBtcAmountToSpend.amount), - uncheckedPaymentRequest: lnInvoiceNoAmountUsd.paymentRequest, + uncheckedPaymentRequest: invoiceNoAmountUsd.lnInvoice.paymentRequest, walletId: newBtcWallet.id, }) if (probeUsd instanceof Error) throw probeUsd const repaid = await Payments.payNoAmountInvoiceByWalletIdForBtcWallet({ amount: toSats(minBtcAmountToSpend.amount), - uncheckedPaymentRequest: lnInvoiceNoAmountUsd.paymentRequest, + uncheckedPaymentRequest: invoiceNoAmountUsd.lnInvoice.paymentRequest, memo: null, senderWalletId: newBtcWallet.id, senderAccount: newAccount, @@ -1239,14 +1239,14 @@ describe("arbitrage strategies", () => { const usdBalanceBefore = await getBalanceHelper(newUsdWallet.id) // Step 1: Pay min sats via no-amount invoice to USD wallet - const lnInvoice = await Wallets.addInvoiceNoAmountForSelf({ + const invoice = await Wallets.addInvoiceNoAmountForSelf({ walletId: newUsdWallet.id, }) - if (lnInvoice instanceof Error) throw lnInvoice + if (invoice instanceof Error) throw invoice const paid = await Payments.payNoAmountInvoiceByWalletIdForBtcWallet({ amount: Number(minBtcAmountToSpend.amount), - uncheckedPaymentRequest: lnInvoice.paymentRequest, + uncheckedPaymentRequest: invoice.lnInvoice.paymentRequest, memo: null, senderWalletId: newBtcWallet.id, senderAccount: newAccount, @@ -1293,14 +1293,14 @@ describe("arbitrage strategies", () => { const usdBalanceBefore = await getBalanceHelper(newUsdWallet.id) // Step 1: Pay min sats via no-amount invoice to USD wallet - const lnInvoice = await Wallets.addInvoiceNoAmountForSelf({ + const invoice = await Wallets.addInvoiceNoAmountForSelf({ walletId: newUsdWallet.id, }) - if (lnInvoice instanceof Error) throw lnInvoice + if (invoice instanceof Error) throw invoice const paid = await Payments.payNoAmountInvoiceByWalletIdForBtcWallet({ amount: Number(minBtcAmountToSpend.amount), - uncheckedPaymentRequest: lnInvoice.paymentRequest, + uncheckedPaymentRequest: invoice.lnInvoice.paymentRequest, memo: null, senderWalletId: newBtcWallet.id, senderAccount: newAccount, @@ -1308,14 +1308,14 @@ describe("arbitrage strategies", () => { if (paid instanceof Error) throw paid // Step 2: Replenish BTC from USD wallet with $0.01 - const lnInvoiceNoAmountBtc = await Wallets.addInvoiceNoAmountForSelf({ + const invoiceNoAmountBtc = await Wallets.addInvoiceNoAmountForSelf({ walletId: newBtcWallet.id, }) - if (lnInvoiceNoAmountBtc instanceof Error) throw lnInvoiceNoAmountBtc + if (invoiceNoAmountBtc instanceof Error) throw invoiceNoAmountBtc const repaid = await Payments.payNoAmountInvoiceByWalletIdForUsdWallet({ amount: toCents(1), - uncheckedPaymentRequest: lnInvoiceNoAmountBtc.paymentRequest, + uncheckedPaymentRequest: invoiceNoAmountBtc.lnInvoice.paymentRequest, memo: null, senderWalletId: newUsdWallet.id, senderAccount: newAccount, @@ -1352,14 +1352,14 @@ describe("arbitrage strategies", () => { const usdBalanceBefore = await getBalanceHelper(newUsdWallet.id) // Step 1: Pay min sats via no-amount invoice to USD wallet - const lnInvoice = await Wallets.addInvoiceNoAmountForSelf({ + const invoice = await Wallets.addInvoiceNoAmountForSelf({ walletId: newUsdWallet.id, }) - if (lnInvoice instanceof Error) throw lnInvoice + if (invoice instanceof Error) throw invoice const paid = await Payments.payNoAmountInvoiceByWalletIdForBtcWallet({ amount: Number(minBtcAmountToSpend.amount), - uncheckedPaymentRequest: lnInvoice.paymentRequest, + uncheckedPaymentRequest: invoice.lnInvoice.paymentRequest, memo: null, senderWalletId: newBtcWallet.id, senderAccount: newAccount, @@ -1367,21 +1367,21 @@ describe("arbitrage strategies", () => { if (paid instanceof Error) throw paid // Step 2: Replenish BTC from USD wallet with $0.01 - const lnInvoiceNoAmountBtc = await Wallets.addInvoiceNoAmountForSelf({ + const invoiceNoAmountBtc = await Wallets.addInvoiceNoAmountForSelf({ walletId: newBtcWallet.id, }) - if (lnInvoiceNoAmountBtc instanceof Error) throw lnInvoiceNoAmountBtc + if (invoiceNoAmountBtc instanceof Error) throw invoiceNoAmountBtc const probeBtc = await Payments.getNoAmountLightningFeeEstimationForUsdWallet({ amount: toCents(1), - uncheckedPaymentRequest: lnInvoiceNoAmountBtc.paymentRequest, + uncheckedPaymentRequest: invoiceNoAmountBtc.lnInvoice.paymentRequest, walletId: newUsdWallet.id, }) if (probeBtc instanceof Error) throw probeBtc const repaid = await Payments.payNoAmountInvoiceByWalletIdForUsdWallet({ amount: toCents(1), - uncheckedPaymentRequest: lnInvoiceNoAmountBtc.paymentRequest, + uncheckedPaymentRequest: invoiceNoAmountBtc.lnInvoice.paymentRequest, memo: null, senderWalletId: newUsdWallet.id, senderAccount: newAccount, @@ -1432,14 +1432,14 @@ describe("arbitrage strategies", () => { const usdBalanceBefore = await getBalanceHelper(newUsdWallet.id) // Step 1: Pay min sats via no-amount invoice to USD wallet - const lnInvoice = await Wallets.addInvoiceNoAmountForSelf({ + const invoice = await Wallets.addInvoiceNoAmountForSelf({ walletId: newUsdWallet.id, }) - if (lnInvoice instanceof Error) throw lnInvoice + if (invoice instanceof Error) throw invoice const paid = await Payments.payNoAmountInvoiceByWalletIdForBtcWallet({ amount: Number(minBtcAmountToSpend.amount), - uncheckedPaymentRequest: lnInvoice.paymentRequest, + uncheckedPaymentRequest: invoice.lnInvoice.paymentRequest, memo: null, senderWalletId: newBtcWallet.id, senderAccount: newAccount, @@ -1447,14 +1447,14 @@ describe("arbitrage strategies", () => { if (paid instanceof Error) throw paid // Step 2: Pay back $0.01 from USD to BTC wallet - const lnInvoiceBtc = await Wallets.addInvoiceForSelfForBtcWallet({ + const invoiceBtc = await Wallets.addInvoiceForSelfForBtcWallet({ walletId: newBtcWallet.id, amount: toSats(maxBtcAmountToEarn.amount), }) - if (lnInvoiceBtc instanceof Error) throw lnInvoiceBtc + if (invoiceBtc instanceof Error) throw invoiceBtc const repaid = await Payments.payInvoiceByWalletId({ - uncheckedPaymentRequest: lnInvoiceBtc.paymentRequest, + uncheckedPaymentRequest: invoiceBtc.lnInvoice.paymentRequest, memo: null, senderWalletId: newUsdWallet.id, senderAccount: newAccount, @@ -1503,14 +1503,14 @@ describe("arbitrage strategies", () => { const usdBalanceBefore = await getBalanceHelper(newUsdWallet.id) // Step 1: Pay min sats via no-amount invoice to USD wallet - const lnInvoice = await Wallets.addInvoiceNoAmountForSelf({ + const invoice = await Wallets.addInvoiceNoAmountForSelf({ walletId: newUsdWallet.id, }) - if (lnInvoice instanceof Error) throw lnInvoice + if (invoice instanceof Error) throw invoice const paid = await Payments.payNoAmountInvoiceByWalletIdForBtcWallet({ amount: Number(minBtcAmountToSpend.amount), - uncheckedPaymentRequest: lnInvoice.paymentRequest, + uncheckedPaymentRequest: invoice.lnInvoice.paymentRequest, memo: null, senderWalletId: newBtcWallet.id, senderAccount: newAccount, @@ -1518,20 +1518,20 @@ describe("arbitrage strategies", () => { if (paid instanceof Error) throw paid // Step 2: Pay back $0.01 from USD to BTC wallet - const lnInvoiceBtc = await Wallets.addInvoiceForSelfForBtcWallet({ + const invoiceBtc = await Wallets.addInvoiceForSelfForBtcWallet({ walletId: newBtcWallet.id, amount: toSats(maxBtcAmountToEarn.amount), }) - if (lnInvoiceBtc instanceof Error) throw lnInvoiceBtc + if (invoiceBtc instanceof Error) throw invoiceBtc const probeUsd = await Payments.getLightningFeeEstimationForUsdWallet({ - uncheckedPaymentRequest: lnInvoiceBtc.paymentRequest, + uncheckedPaymentRequest: invoiceBtc.lnInvoice.paymentRequest, walletId: newUsdWallet.id, }) if (probeUsd instanceof Error) throw probeUsd const repaid = await Payments.payInvoiceByWalletId({ - uncheckedPaymentRequest: lnInvoiceBtc.paymentRequest, + uncheckedPaymentRequest: invoiceBtc.lnInvoice.paymentRequest, memo: null, senderWalletId: newUsdWallet.id, senderAccount: newAccount, @@ -1572,21 +1572,21 @@ describe("arbitrage strategies", () => { const usdBalanceBefore = await getBalanceHelper(newUsdWallet.id) // Step 1: Pay min sats via no-amount invoice to USD wallet - const lnInvoice = await Wallets.addInvoiceNoAmountForSelf({ + const invoice = await Wallets.addInvoiceNoAmountForSelf({ walletId: newUsdWallet.id, }) - if (lnInvoice instanceof Error) throw lnInvoice + if (invoice instanceof Error) throw invoice const probe = await Payments.getNoAmountLightningFeeEstimationForBtcWallet({ amount: Number(minBtcAmountToSpend.amount), - uncheckedPaymentRequest: lnInvoice.paymentRequest, + uncheckedPaymentRequest: invoice.lnInvoice.paymentRequest, walletId: newBtcWallet.id, }) if (probe instanceof Error) throw probe const paid = await Payments.payNoAmountInvoiceByWalletIdForBtcWallet({ amount: Number(minBtcAmountToSpend.amount), - uncheckedPaymentRequest: lnInvoice.paymentRequest, + uncheckedPaymentRequest: invoice.lnInvoice.paymentRequest, memo: null, senderWalletId: newBtcWallet.id, senderAccount: newAccount, @@ -1633,21 +1633,21 @@ describe("arbitrage strategies", () => { const usdBalanceBefore = await getBalanceHelper(newUsdWallet.id) // Step 1: Pay min sats via no-amount invoice to USD wallet - const lnInvoice = await Wallets.addInvoiceNoAmountForSelf({ + const invoice = await Wallets.addInvoiceNoAmountForSelf({ walletId: newUsdWallet.id, }) - if (lnInvoice instanceof Error) throw lnInvoice + if (invoice instanceof Error) throw invoice const probe = await Payments.getNoAmountLightningFeeEstimationForBtcWallet({ amount: Number(minBtcAmountToSpend.amount), - uncheckedPaymentRequest: lnInvoice.paymentRequest, + uncheckedPaymentRequest: invoice.lnInvoice.paymentRequest, walletId: newBtcWallet.id, }) if (probe instanceof Error) throw probe const paid = await Payments.payNoAmountInvoiceByWalletIdForBtcWallet({ amount: Number(minBtcAmountToSpend.amount), - uncheckedPaymentRequest: lnInvoice.paymentRequest, + uncheckedPaymentRequest: invoice.lnInvoice.paymentRequest, memo: null, senderWalletId: newBtcWallet.id, senderAccount: newAccount, @@ -1655,14 +1655,14 @@ describe("arbitrage strategies", () => { if (paid instanceof Error) throw paid // Step 2: Replenish BTC from USD wallet with $0.01 - const lnInvoiceNoAmountBtc = await Wallets.addInvoiceNoAmountForSelf({ + const invoiceNoAmountBtc = await Wallets.addInvoiceNoAmountForSelf({ walletId: newBtcWallet.id, }) - if (lnInvoiceNoAmountBtc instanceof Error) throw lnInvoiceNoAmountBtc + if (invoiceNoAmountBtc instanceof Error) throw invoiceNoAmountBtc const repaid = await Payments.payNoAmountInvoiceByWalletIdForUsdWallet({ amount: toCents(1), - uncheckedPaymentRequest: lnInvoiceNoAmountBtc.paymentRequest, + uncheckedPaymentRequest: invoiceNoAmountBtc.lnInvoice.paymentRequest, memo: null, senderWalletId: newUsdWallet.id, senderAccount: newAccount, @@ -1699,21 +1699,21 @@ describe("arbitrage strategies", () => { const usdBalanceBefore = await getBalanceHelper(newUsdWallet.id) // Step 1: Pay min sats via no-amount invoice to USD wallet - const lnInvoice = await Wallets.addInvoiceNoAmountForSelf({ + const invoice = await Wallets.addInvoiceNoAmountForSelf({ walletId: newUsdWallet.id, }) - if (lnInvoice instanceof Error) throw lnInvoice + if (invoice instanceof Error) throw invoice const probe = await Payments.getNoAmountLightningFeeEstimationForBtcWallet({ amount: Number(minBtcAmountToSpend.amount), - uncheckedPaymentRequest: lnInvoice.paymentRequest, + uncheckedPaymentRequest: invoice.lnInvoice.paymentRequest, walletId: newBtcWallet.id, }) if (probe instanceof Error) throw probe const paid = await Payments.payNoAmountInvoiceByWalletIdForBtcWallet({ amount: Number(minBtcAmountToSpend.amount), - uncheckedPaymentRequest: lnInvoice.paymentRequest, + uncheckedPaymentRequest: invoice.lnInvoice.paymentRequest, memo: null, senderWalletId: newBtcWallet.id, senderAccount: newAccount, @@ -1721,21 +1721,21 @@ describe("arbitrage strategies", () => { if (paid instanceof Error) throw paid // Step 2: Replenish BTC from USD wallet with $0.01 - const lnInvoiceNoAmountBtc = await Wallets.addInvoiceNoAmountForSelf({ + const invoiceNoAmountBtc = await Wallets.addInvoiceNoAmountForSelf({ walletId: newBtcWallet.id, }) - if (lnInvoiceNoAmountBtc instanceof Error) throw lnInvoiceNoAmountBtc + if (invoiceNoAmountBtc instanceof Error) throw invoiceNoAmountBtc const probeBtc = await Payments.getNoAmountLightningFeeEstimationForUsdWallet({ amount: toCents(1), - uncheckedPaymentRequest: lnInvoiceNoAmountBtc.paymentRequest, + uncheckedPaymentRequest: invoiceNoAmountBtc.lnInvoice.paymentRequest, walletId: newUsdWallet.id, }) if (probeBtc instanceof Error) throw probeBtc const repaid = await Payments.payNoAmountInvoiceByWalletIdForUsdWallet({ amount: toCents(1), - uncheckedPaymentRequest: lnInvoiceNoAmountBtc.paymentRequest, + uncheckedPaymentRequest: invoiceNoAmountBtc.lnInvoice.paymentRequest, memo: null, senderWalletId: newUsdWallet.id, senderAccount: newAccount, @@ -1786,21 +1786,21 @@ describe("arbitrage strategies", () => { const usdBalanceBefore = await getBalanceHelper(newUsdWallet.id) // Step 1: Pay min sats via no-amount invoice to USD wallet - const lnInvoice = await Wallets.addInvoiceNoAmountForSelf({ + const invoice = await Wallets.addInvoiceNoAmountForSelf({ walletId: newUsdWallet.id, }) - if (lnInvoice instanceof Error) throw lnInvoice + if (invoice instanceof Error) throw invoice const probe = await Payments.getNoAmountLightningFeeEstimationForBtcWallet({ amount: Number(minBtcAmountToSpend.amount), - uncheckedPaymentRequest: lnInvoice.paymentRequest, + uncheckedPaymentRequest: invoice.lnInvoice.paymentRequest, walletId: newBtcWallet.id, }) if (probe instanceof Error) throw probe const paid = await Payments.payNoAmountInvoiceByWalletIdForBtcWallet({ amount: Number(minBtcAmountToSpend.amount), - uncheckedPaymentRequest: lnInvoice.paymentRequest, + uncheckedPaymentRequest: invoice.lnInvoice.paymentRequest, memo: null, senderWalletId: newBtcWallet.id, senderAccount: newAccount, @@ -1808,14 +1808,14 @@ describe("arbitrage strategies", () => { if (paid instanceof Error) throw paid // Step 2: Pay back $0.01 from USD to BTC wallet - const lnInvoiceBtc = await Wallets.addInvoiceForSelfForBtcWallet({ + const invoiceBtc = await Wallets.addInvoiceForSelfForBtcWallet({ walletId: newBtcWallet.id, amount: toSats(maxBtcAmountToEarn.amount), }) - if (lnInvoiceBtc instanceof Error) throw lnInvoiceBtc + if (invoiceBtc instanceof Error) throw invoiceBtc const repaid = await Payments.payInvoiceByWalletId({ - uncheckedPaymentRequest: lnInvoiceBtc.paymentRequest, + uncheckedPaymentRequest: invoiceBtc.lnInvoice.paymentRequest, memo: null, senderWalletId: newUsdWallet.id, senderAccount: newAccount, @@ -1864,21 +1864,21 @@ describe("arbitrage strategies", () => { const usdBalanceBefore = await getBalanceHelper(newUsdWallet.id) // Step 1: Pay min sats via no-amount invoice to USD wallet - const lnInvoice = await Wallets.addInvoiceNoAmountForSelf({ + const invoice = await Wallets.addInvoiceNoAmountForSelf({ walletId: newUsdWallet.id, }) - if (lnInvoice instanceof Error) throw lnInvoice + if (invoice instanceof Error) throw invoice const probe = await Payments.getNoAmountLightningFeeEstimationForBtcWallet({ amount: Number(minBtcAmountToSpend.amount), - uncheckedPaymentRequest: lnInvoice.paymentRequest, + uncheckedPaymentRequest: invoice.lnInvoice.paymentRequest, walletId: newBtcWallet.id, }) if (probe instanceof Error) throw probe const paid = await Payments.payNoAmountInvoiceByWalletIdForBtcWallet({ amount: Number(minBtcAmountToSpend.amount), - uncheckedPaymentRequest: lnInvoice.paymentRequest, + uncheckedPaymentRequest: invoice.lnInvoice.paymentRequest, memo: null, senderWalletId: newBtcWallet.id, senderAccount: newAccount, @@ -1886,20 +1886,20 @@ describe("arbitrage strategies", () => { if (paid instanceof Error) throw paid // Step 2: Pay back $0.01 from USD to BTC wallet - const lnInvoiceBtc = await Wallets.addInvoiceForSelfForBtcWallet({ + const invoiceBtc = await Wallets.addInvoiceForSelfForBtcWallet({ walletId: newBtcWallet.id, amount: toSats(maxBtcAmountToEarn.amount), }) - if (lnInvoiceBtc instanceof Error) throw lnInvoiceBtc + if (invoiceBtc instanceof Error) throw invoiceBtc const probeUsd = await Payments.getLightningFeeEstimationForUsdWallet({ - uncheckedPaymentRequest: lnInvoiceBtc.paymentRequest, + uncheckedPaymentRequest: invoiceBtc.lnInvoice.paymentRequest, walletId: newUsdWallet.id, }) if (probeUsd instanceof Error) throw probeUsd const repaid = await Payments.payInvoiceByWalletId({ - uncheckedPaymentRequest: lnInvoiceBtc.paymentRequest, + uncheckedPaymentRequest: invoiceBtc.lnInvoice.paymentRequest, memo: null, senderWalletId: newUsdWallet.id, senderAccount: newAccount, diff --git a/core/api/test/legacy-integration/services/mongoose/wallet-invoices.spec.ts b/core/api/test/legacy-integration/services/mongoose/wallet-invoices.spec.ts index 82e9e38fcf..d4709fa143 100644 --- a/core/api/test/legacy-integration/services/mongoose/wallet-invoices.spec.ts +++ b/core/api/test/legacy-integration/services/mongoose/wallet-invoices.spec.ts @@ -1,7 +1,7 @@ import crypto from "crypto" import { WalletCurrency } from "@/domain/shared" -import { getSecretAndPaymentHash } from "@/domain/bitcoin/lightning" +import { decodeInvoice, getSecretAndPaymentHash } from "@/domain/bitcoin/lightning" import { WalletInvoicesRepository } from "@/services/mongoose" import { createUserAndWalletFromPhone, randomPhone } from "test/helpers" @@ -26,8 +26,10 @@ const createTestWalletInvoice = () => { currency: WalletCurrency.Usd, amount: 10n, }, + lnInvoice: decodeInvoice( + "lnbc1pjjahwgpp5zzh9s6tkhpk7heu8jt4l7keuzg7v046p0lzx2hvy3jf6a56w50nqdp82pshjgr5dusyymrfde4jq4mpd3kx2apq24ek2uscqzpuxqyz5vqsp5vl4zmuvhl8rzy4rmq0g3j28060pv3gqp22rh8l7u45xwyu27928q9qyyssqn9drylhlth9ee320e4ahz52y9rklujqgw0kj9ce2gcmltqk6uuay5yv8vgks0y5tggndv0kek2m2n02lf43znx50237mglxsfw4au2cqqr6qax", + ) as LnInvoice, // Use a real invoice to test decoding createdAt: new Date(), - paymentRequest: "paymentRequest" as EncodedPaymentRequest, } } diff --git a/core/api/test/unit/domain/wallet-invoices/wallet-invoice-builder.spec.ts b/core/api/test/unit/domain/wallet-invoices/wallet-invoice-builder.spec.ts index 210467d40f..84023245da 100644 --- a/core/api/test/unit/domain/wallet-invoices/wallet-invoice-builder.spec.ts +++ b/core/api/test/unit/domain/wallet-invoices/wallet-invoice-builder.spec.ts @@ -76,8 +76,8 @@ describe("WalletInvoiceBuilder", () => { lnRegisterInvoice: registerInvoice, }) - const checkSecretAndHash = ({ lnInvoice, walletInvoice }: LnAndWalletInvoice) => { - const { secret } = walletInvoice + const checkSecretAndHash = (walletInvoice: WalletInvoice) => { + const { secret, lnInvoice } = walletInvoice const hashFromSecret = sha256(Buffer.from(secret, "hex")) expect(hashFromSecret).toEqual(walletInvoice.paymentHash) expect(walletInvoice.paymentHash).toEqual(lnInvoice.paymentHash) @@ -88,14 +88,14 @@ describe("WalletInvoiceBuilder", () => { const WIBWithDescription = WIB.withDescription({ description: testDescription, }) - const checkDescription = ({ lnInvoice }: LnAndWalletInvoice) => { + const checkDescription = ({ lnInvoice }: WalletInvoice) => { expect(lnInvoice.description).toEqual(testDescription) } const expirationInMinutes = checkedToMinutes(3) if (expirationInMinutes instanceof Error) throw expirationInMinutes const testExpiration = new Date("2000-01-01T00:03:00.000Z") - const checkExpiration = ({ lnInvoice }: LnAndWalletInvoice) => { + const checkExpiration = ({ lnInvoice }: WalletInvoice) => { expect(lnInvoice.expiresAt).toEqual(testExpiration) } @@ -110,13 +110,13 @@ describe("WalletInvoiceBuilder", () => { describe("generated for self", () => { const WIBWithCreator = WIBWithDescription.generatedForSelf() - const checkCreator = ({ walletInvoice }: LnAndWalletInvoice) => { + const checkCreator = (walletInvoice: WalletInvoice) => { expect(walletInvoice.selfGenerated).toEqual(true) } describe("with btc recipient wallet", () => { const WIBWithRecipient = WIBWithCreator.withRecipientWallet(recipientBtcWallet) - const checkRecipientWallet = ({ walletInvoice }: LnAndWalletInvoice) => { + const checkRecipientWallet = (walletInvoice: WalletInvoice) => { expect(walletInvoice.recipientWalletDescriptor).toEqual(recipientBtcWallet) } @@ -130,8 +130,8 @@ describe("WalletInvoiceBuilder", () => { ) if (WIBWithAmount instanceof Error) throw WIBWithAmount - const checkAmount = ({ lnInvoice, walletInvoice }: LnAndWalletInvoice) => { - expect(lnInvoice).toEqual( + const checkAmount = (walletInvoice: WalletInvoice) => { + expect(walletInvoice.lnInvoice).toEqual( expect.objectContaining({ amount: uncheckedAmount as Satoshis, paymentAmount: btcCheckedAmount, @@ -174,8 +174,8 @@ describe("WalletInvoiceBuilder", () => { await WIBWithRecipient.withExpiration(expirationInMinutes).withoutAmount() if (WIBWithAmount instanceof Error) throw WIBWithAmount - const checkAmount = ({ lnInvoice, walletInvoice }: LnAndWalletInvoice) => { - expect(lnInvoice).toEqual( + const checkAmount = (walletInvoice: WalletInvoice) => { + expect(walletInvoice.lnInvoice).toEqual( expect.objectContaining({ amount: 0 as Satoshis, paymentAmount: BtcPaymentAmount(BigInt(0)), @@ -205,7 +205,7 @@ describe("WalletInvoiceBuilder", () => { describe("with usd recipient wallet", () => { const WIBWithRecipient = WIBWithCreator.withRecipientWallet(recipientUsdWallet) - const checkRecipientWallet = ({ walletInvoice }: LnAndWalletInvoice) => { + const checkRecipientWallet = (walletInvoice: WalletInvoice) => { expect(walletInvoice.recipientWalletDescriptor).toEqual(recipientUsdWallet) } @@ -219,9 +219,9 @@ describe("WalletInvoiceBuilder", () => { ) if (WIBWithAmount instanceof Error) throw WIBWithAmount - const checkAmount = ({ lnInvoice, walletInvoice }: LnAndWalletInvoice) => { + const checkAmount = (walletInvoice: WalletInvoice) => { const convertedAmount = BigInt(uncheckedAmount) * dealerPriceRatio - expect(lnInvoice).toEqual( + expect(walletInvoice.lnInvoice).toEqual( expect.objectContaining({ amount: Number(convertedAmount) as Satoshis, paymentAmount: BtcPaymentAmount(convertedAmount), @@ -256,8 +256,8 @@ describe("WalletInvoiceBuilder", () => { ) if (WIBWithAmount instanceof Error) throw WIBWithAmount - const checkAmount = ({ lnInvoice, walletInvoice }: LnAndWalletInvoice) => { - expect(lnInvoice).toEqual( + const checkAmount = (walletInvoice: WalletInvoice) => { + expect(walletInvoice.lnInvoice).toEqual( expect.objectContaining({ amount: Number(uncheckedAmount) as Satoshis, paymentAmount: btcCheckedAmount, @@ -303,8 +303,8 @@ describe("WalletInvoiceBuilder", () => { await WIBWithRecipient.withExpiration(expirationInMinutes).withoutAmount() if (WIBWithAmount instanceof Error) throw WIBWithAmount - const checkAmount = ({ lnInvoice, walletInvoice }: LnAndWalletInvoice) => { - expect(lnInvoice).toEqual( + const checkAmount = (walletInvoice: WalletInvoice) => { + expect(walletInvoice.lnInvoice).toEqual( expect.objectContaining({ amount: 0 as Satoshis, paymentAmount: BtcPaymentAmount(BigInt(0)), diff --git a/core/api/test/unit/domain/wallet-invoices/wallet-invoice-receiver.spec.ts b/core/api/test/unit/domain/wallet-invoices/wallet-invoice-receiver.spec.ts index a30568dd6f..51edc1a2f5 100644 --- a/core/api/test/unit/domain/wallet-invoices/wallet-invoice-receiver.spec.ts +++ b/core/api/test/unit/domain/wallet-invoices/wallet-invoice-receiver.spec.ts @@ -53,6 +53,22 @@ describe("WalletInvoiceReceiver", () => { [WalletCurrency.Usd]: recipientUsdWalletDescriptor, } + const mockLnInvoice: LnInvoice = { + destination: "destination" as Pubkey, + paymentHash: "paymentHash" as PaymentHash, + paymentRequest: "paymentRequest" as EncodedPaymentRequest, + paymentSecret: "paymentSecret" as PaymentIdentifyingSecret, + milliSatsAmount: 0 as MilliSatoshis, + description: "description", + routeHints: [] as Hop[][], + features: [] as LnInvoiceFeature[], + expiresAt: new Date(), + isExpired: false, + cltvDelta: null, + amount: null, + paymentAmount: null, + } + describe("for btc invoice", () => { const btcInvoice: WalletInvoice = { paymentHash: "paymentHash" as PaymentHash, @@ -63,6 +79,7 @@ describe("WalletInvoiceReceiver", () => { paid: false, recipientWalletDescriptor: partialRecipientBtcWalletDescriptor, createdAt: new Date(), + lnInvoice: mockLnInvoice, } it("returns correct amounts", async () => { @@ -103,6 +120,7 @@ describe("WalletInvoiceReceiver", () => { usdAmount: UsdPaymentAmount(BigInt(100)), paid: false, createdAt: new Date(), + lnInvoice: mockLnInvoice, } it("returns correct amounts", async () => { @@ -136,6 +154,7 @@ describe("WalletInvoiceReceiver", () => { pubkey: "pubkey" as Pubkey, paid: false, createdAt: new Date(), + lnInvoice: mockLnInvoice, } it("returns correct amounts", async () => { diff --git a/core/api/test/unit/domain/wallet-invoices/wallet-invoice-status-checker.spec.ts b/core/api/test/unit/domain/wallet-invoices/wallet-invoice-status-checker.spec.ts new file mode 100644 index 0000000000..8867979344 --- /dev/null +++ b/core/api/test/unit/domain/wallet-invoices/wallet-invoice-status-checker.spec.ts @@ -0,0 +1,78 @@ +import { WalletCurrency } from "@/domain/shared" +import { WalletInvoiceStatus } from "@/domain/wallet-invoices" +import { WalletInvoiceStatusChecker } from "@/domain/wallet-invoices/wallet-invoice-status-checker" + +const earlierDate = new Date("2023-10-18T20:07:44.949Z") +const laterDate = new Date("2023-10-18T20:08:44.950Z") +const baseInvoice: WalletInvoice = { + paymentHash: "paymentHash" as PaymentHash, + secret: "secretPreImage" as SecretPreImage, + selfGenerated: true, + pubkey: "pubkey" as Pubkey, + recipientWalletDescriptor: { id: "walletId" as WalletId, currency: WalletCurrency.Usd }, + paid: false, + createdAt: new Date(Date.now()), + lnInvoice: { + destination: "destination" as Pubkey, + paymentHash: "paymentHash" as PaymentHash, + paymentRequest: "paymentRequest" as EncodedPaymentRequest, + paymentSecret: "paymentSecret" as PaymentIdentifyingSecret, + milliSatsAmount: 0 as MilliSatoshis, + description: "description", + routeHints: [] as Hop[][], + features: [] as LnInvoiceFeature[], + expiresAt: new Date(), + isExpired: false, + cltvDelta: null, + amount: null, + paymentAmount: null, + }, +} + +describe("WalletInvoiceStatusChecker", () => { + describe("status", () => { + it("returns paid for paid unexpired invoice", () => { + const paidInvoice = { + ...baseInvoice, + paid: true, + lnInvoice: { ...baseInvoice.lnInvoice, expiresAt: laterDate }, + } + expect(WalletInvoiceStatusChecker(paidInvoice).status(earlierDate)).toBe( + WalletInvoiceStatus.Paid, + ) + }) + + it("returns paid for paid expired invoice", () => { + const paidInvoice = { + ...baseInvoice, + paid: true, + lnInvoice: { ...baseInvoice.lnInvoice, expiresAt: earlierDate }, + } + expect(WalletInvoiceStatusChecker(paidInvoice).status(laterDate)).toBe( + WalletInvoiceStatus.Paid, + ) + }) + + it("returns pending for unpaid unexpired invoice", () => { + const unpaidInvoice = { + ...baseInvoice, + paid: false, + lnInvoice: { ...baseInvoice.lnInvoice, expiresAt: laterDate }, + } + expect(WalletInvoiceStatusChecker(unpaidInvoice).status(earlierDate)).toBe( + WalletInvoiceStatus.Pending, + ) + }) + + it("returns expired for unpaid expired invoice", () => { + const unpaidInvoice = { + ...baseInvoice, + paid: false, + lnInvoice: { ...baseInvoice.lnInvoice, expiresAt: earlierDate }, + } + expect(WalletInvoiceStatusChecker(unpaidInvoice).status(laterDate)).toBe( + WalletInvoiceStatus.Expired, + ) + }) + }) +})