diff --git a/src/app/wallets/get-transactions-for-wallet.ts b/src/app/wallets/get-transactions-for-wallet.ts index 2ea096247..f4dfdb186 100644 --- a/src/app/wallets/get-transactions-for-wallet.ts +++ b/src/app/wallets/get-transactions-for-wallet.ts @@ -97,9 +97,17 @@ export const toWalletTransactions = (ibexResp: GResponse200): IbexTransaction[] initiationVia: { type: "lightning", paymentHash: "", pubkey: "" }, settlementVia: { type: "lightning", revealedPreImage: undefined }, } as WalletLnSettledTransaction + // IBEX type ids per GET /v2/transaction-types/all: 1/2 Lightning + // receive/send, 3/4 On-Chain receive/send, 5 Fund, 6 Defund, 7 Bank + // Deposit, 8 Bank Withdrawal, 9/10 Crypto receive/send (USDT), 11/12 + // Taproot receive/send. Bank/funding ops (5-8) are org-level and + // intentionally left to the logged fallback below. case 3: case 4: + case 9: case 10: + case 11: + case 12: return { ...baseTrx, // Ibex does not provide paymentHash, pubkey and preimage in transactions endpoint. To get these fields, @@ -162,6 +170,13 @@ const toSettlementMinorUnit = ( return amount as Satoshis } +// Sends render negative: Lightning send (2), On-Chain send (4), Crypto send +// (10), Taproot send (12). +const IBEX_SEND_TYPE_IDS = [2, 4, 10, 12] + +const isIbexSendType = (transactionTypeId: number | undefined): boolean => + transactionTypeId !== undefined && IBEX_SEND_TYPE_IDS.includes(transactionTypeId) + const toSettlementAmount = ( ibexAmount: number | undefined, transactionTypeId: number | undefined, @@ -171,11 +186,7 @@ const toSettlementAmount = ( baseLogger.warn("Ibex did not return transaction amount") return toSettlementMinorUnit(ibexAmount, currency) } - // When sending, make negative - const amt = - transactionTypeId === 2 || transactionTypeId === 4 || transactionTypeId === 10 - ? -1 * ibexAmount - : ibexAmount + const amt = isIbexSendType(transactionTypeId) ? -1 * ibexAmount : ibexAmount return toSettlementMinorUnit(amt, currency) } @@ -184,10 +195,7 @@ const toSettlementDisplayAmount = ( transactionTypeId: number | undefined, ): string => { if (ibexAmount === undefined) return `${ibexAmount}` - const amount = - transactionTypeId === 2 || transactionTypeId === 4 || transactionTypeId === 10 - ? -1 * ibexAmount - : ibexAmount + const amount = isIbexSendType(transactionTypeId) ? -1 * ibexAmount : ibexAmount return `${amount}` } diff --git a/test/flash/unit/app/wallets/get-transactions-for-wallet.spec.ts b/test/flash/unit/app/wallets/get-transactions-for-wallet.spec.ts index 9b9ebc614..deabaa9c7 100644 --- a/test/flash/unit/app/wallets/get-transactions-for-wallet.spec.ts +++ b/test/flash/unit/app/wallets/get-transactions-for-wallet.spec.ts @@ -99,6 +99,52 @@ describe("toWalletTransactions", () => { expect(transaction.settlementVia.type).toBe("onchain") }) + it("maps IBEX crypto receive transaction type to incoming on-chain USDT", () => { + const [transaction] = toWalletTransactions([ + { + id: "crypto-receive-trx-id", + accountId: "wallet-id", + amount: 2.5, + currencyId: 29, + transactionTypeId: 9, + createdAt: "2026-07-17T16:30:00.000Z", + }, + ] as GResponse200) + + expect(transaction.settlementCurrency).toBe(WalletCurrency.Usdt) + expect(transaction.settlementAmount).toBe(250) + expect(transaction.settlementDisplayAmount).toBe("2.5") + expect(transaction.initiationVia.type).toBe("onchain") + expect(transaction.settlementVia.type).toBe("onchain") + }) + + it("maps IBEX taproot types to on-chain with send rendered negative", () => { + const [received, sent] = toWalletTransactions([ + { + id: "taproot-receive", + accountId: "wallet-id", + amount: 100, + currencyId: 3, + transactionTypeId: 11, + createdAt: "2026-07-17T16:30:00.000Z", + }, + { + id: "taproot-send", + accountId: "wallet-id", + amount: 100, + currencyId: 3, + transactionTypeId: 12, + createdAt: "2026-07-17T16:30:00.000Z", + }, + ] as GResponse200) + + expect(received.settlementAmount).toBe(100) + expect(received.initiationVia.type).toBe("onchain") + expect(sent.settlementAmount).toBe(-100) + expect(sent.settlementDisplayAmount).toBe("-100") + expect(sent.initiationVia.type).toBe("onchain") + }) + it("defaults omitted IBEX USDT amount and network fee to zero cents", () => { const [transaction] = toWalletTransactions([ { @@ -210,23 +256,61 @@ describe("toWalletTransactions", () => { const transactions = toWalletTransactions( [3, 29].flatMap((currencyId) => - [1, 2, 3, 4, 10, 99, undefined].map((transactionTypeId, i) => ({ - id: `trx-${currencyId}-${i}`, - accountId: "wallet-id", - amount: 10, - currencyId, - transactionTypeId, - createdAt: "2026-05-13T00:00:00.000Z", - })), + [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 99, undefined].map( + (transactionTypeId, i) => ({ + id: `trx-${currencyId}-${i}`, + accountId: "wallet-id", + amount: 10, + currencyId, + transactionTypeId, + createdAt: "2026-05-13T00:00:00.000Z", + }), + ), ) as GResponse200, ) - expect(transactions).toHaveLength(14) + expect(transactions).toHaveLength(28) for (const transaction of transactions) { expect(resolvable).toContain(transaction.initiationVia.type) expect(resolvable).toContain(transaction.settlementVia.type) } }) + + it("keeps IBEX bank/funding ops (5-8) on the logged intraledger fallback", () => { + const errorSpy = jest.spyOn(baseLogger, "error").mockImplementation() + + const transactions = toWalletTransactions( + [5, 6, 7, 8].map((transactionTypeId) => ({ + id: `trx-${transactionTypeId}`, + accountId: "wallet-id", + amount: 10, + currencyId: 3, + transactionTypeId, + createdAt: "2026-05-13T00:00:00.000Z", + })) as GResponse200, + ) + + expect(transactions).toHaveLength(4) + for (const transaction of transactions) { + expect(transaction.initiationVia.type).toBe("intraledger") + expect(transaction.settlementVia.type).toBe("intraledger") + } + + const fallbackLogs = errorSpy.mock.calls.filter( + ([msg]) => + typeof msg === "string" && msg.includes("Failed to parse Ibex transaction type"), + ) + expect(fallbackLogs).toHaveLength(4) + for (const typeId of [5, 6, 7, 8]) { + expect( + fallbackLogs.some(([msg]) => + (msg as string).includes(`transactionTypeId: ${typeId}`), + ), + ).toBe(true) + } + + errorSpy.mockRestore() + }) }) describe("getTransactionsForWallets", () => {