From c4af8fce0bec26ab54d4e99f7de021c7f4fe4092 Mon Sep 17 00:00:00 2001 From: Vandana Date: Wed, 17 Jun 2026 08:27:33 -0700 Subject: [PATCH 1/2] fix(ibex): map crypto send transactions as USDT sends --- .../wallets/get-transactions-for-wallet.ts | 113 ++++++++++-------- .../get-transactions-for-wallet.spec.ts | 21 ++++ 2 files changed, 84 insertions(+), 50 deletions(-) diff --git a/src/app/wallets/get-transactions-for-wallet.ts b/src/app/wallets/get-transactions-for-wallet.ts index 9b1d6fd47..b6a72d838 100644 --- a/src/app/wallets/get-transactions-for-wallet.ts +++ b/src/app/wallets/get-transactions-for-wallet.ts @@ -4,7 +4,7 @@ import Ibex from "@services/ibex/client" import { IbexError } from "@services/ibex/errors" import { baseLogger } from "@services/logger" import { GResponse200 } from "ibex-client" -import { ConnectionArguments, ConnectionCursor } from "graphql-relay" +import { ConnectionArguments } from "graphql-relay" export const getTransactionsForWallets = async ({ wallets, @@ -14,37 +14,43 @@ export const getTransactionsForWallets = async ({ paginationArgs?: PaginationArgs }): Promise>> => { const walletIds = wallets.map((wallet) => wallet.id) - - const ibexCalls = await Promise.all(walletIds - .map(id => Ibex.getAccountTransactions({ - account_id: id, - ...toIbexPaginationArgs(paginationArgs) - })) + + const ibexCalls = await Promise.all( + walletIds.map((id) => + Ibex.getAccountTransactions({ + account_id: id, + ...toIbexPaginationArgs(paginationArgs), + }), + ), ) - const transactions = ibexCalls.flatMap(resp => { - if (resp instanceof IbexError) return [] + const transactions = ibexCalls.flatMap((resp) => { + if (resp instanceof IbexError) return [] else return toWalletTransactions(resp) }) return PartialResult.ok({ slice: transactions, - total: transactions.length + total: transactions.length, }) } -const currencyFromIbexCurrencyId = (currencyId: number | undefined): WalletCurrency | undefined => { +const currencyFromIbexCurrencyId = ( + currencyId: number | undefined, +): WalletCurrency | undefined => { if (currencyId === USDAmount.currencyId) return WalletCurrency.Usd if (currencyId === USDTAmount.currencyId) return WalletCurrency.Usdt return undefined } export const toWalletTransactions = (ibexResp: GResponse200): IbexTransaction[] => { - return ibexResp.map(trx => { + return ibexResp.map((trx) => { const currency = currencyFromIbexCurrencyId(trx.currencyId) if (!currency) { - baseLogger.error(`Failed to parse Ibex transaction currency. { WalletId: ${trx.accountId}, TransactionId: ${trx.id}, currencyId: ${trx.currencyId} }`) + baseLogger.error( + `Failed to parse Ibex transaction currency. { WalletId: ${trx.accountId}, TransactionId: ${trx.id}, currencyId: ${trx.currencyId} }`, + ) return { walletId: (trx.accountId || "") as WalletId, settlementAmount: 0 as Satoshis, @@ -67,23 +73,28 @@ export const toWalletTransactions = (ibexResp: GResponse200): IbexTransaction[] } as UnknownTypeTransaction } - const settlementDisplayPrice: WalletMinorUnitDisplayPrice = { - base: trx.exchangeRateCurrencySats ? BigInt(Math.floor(trx.exchangeRateCurrencySats)) : 0n, + const settlementDisplayPrice: WalletMinorUnitDisplayPrice< + WalletCurrency, + DisplayCurrency + > = { + base: trx.exchangeRateCurrencySats + ? BigInt(Math.floor(trx.exchangeRateCurrencySats)) + : 0n, offset: 0n, // what is this? displayCurrency: "USD" as DisplayCurrency, - walletCurrency: currency + walletCurrency: currency, } const baseTrx: BaseWalletTransaction = { - walletId: (trx.accountId || "") as WalletId, + walletId: (trx.accountId || "") as WalletId, settlementAmount: toSettlementAmount(trx.amount, trx.transactionTypeId, currency), settlementFee: toSettlementMinorUnit(trx.networkFee, currency), - settlementCurrency: currency, - settlementDisplayAmount: `${trx.amount}`, - settlementDisplayFee: `${trx.networkFee}`, + settlementCurrency: currency, + settlementDisplayAmount: `${trx.amount}`, + settlementDisplayFee: `${trx.networkFee}`, settlementDisplayPrice: settlementDisplayPrice, createdAt: trx.createdAt ? new Date(trx.createdAt) : new Date(), // should always return - id: trx.id || "null", // "LedgerTransactionId" - this is likely unused + id: trx.id || "null", // "LedgerTransactionId" - this is likely unused status: "success" as TxStatus, // assuming Ibex returns on completed memo: null, // query transaction details } @@ -94,25 +105,28 @@ export const toWalletTransactions = (ibexResp: GResponse200): IbexTransaction[] return { ...baseTrx, // Ibex does not provide paymentHash, pubkey and preimage in transactions endpoint. To get these fields, - // we need to query the transaction details for each trx individually. - initiationVia: { type: 'lightning', paymentHash: "", pubkey: "" }, - settlementVia: { type: 'lightning', revealedPreImage: undefined } + // we need to query the transaction details for each trx individually. + initiationVia: { type: "lightning", paymentHash: "", pubkey: "" }, + settlementVia: { type: "lightning", revealedPreImage: undefined }, } as WalletLnSettledTransaction case 3: case 4: + case 10: return { ...baseTrx, // Ibex does not provide paymentHash, pubkey and preimage in transactions endpoint. To get these fields, - // we need to query the transaction details for each trx individually. - initiationVia: { type: 'onchain', address: "" }, - settlementVia: { type: 'onchain', transactionHash: '', vout: undefined } + // we need to query the transaction details for each trx individually. + initiationVia: { type: "onchain", address: "" }, + settlementVia: { type: "onchain", transactionHash: "", vout: undefined }, } as WalletOnChainSettledTransaction // assuming Ibex only gives us settled default: - baseLogger.error(`Failed to parse Ibex transaction type. { WalletId: ${baseTrx.walletId}, TransactionId: ${trx.id}, transactionTypeId: ${trx.transactionTypeId}`) - return { + baseLogger.error( + `Failed to parse Ibex transaction type. { WalletId: ${baseTrx.walletId}, TransactionId: ${trx.id}, transactionTypeId: ${trx.transactionTypeId}`, + ) + return { ...baseTrx, - initiationVia: { type: 'unknown' }, - settlementVia: { type: 'unknown' } + initiationVia: { type: "unknown" }, + settlementVia: { type: "unknown" }, } as UnknownTypeTransaction } }) @@ -129,9 +143,7 @@ const toUsdtMicros = (amount: number): UsdtMicros => { return Number(usdtAmount.asSmallestUnits()) as UsdtMicros } -const zeroSettlementMinorUnit = ( - currency: WalletCurrency, -): SettlementMinorUnitAmount => { +const zeroSettlementMinorUnit = (currency: WalletCurrency): SettlementMinorUnitAmount => { if (currency === WalletCurrency.Usd) return 0 as UsdCents if (currency === WalletCurrency.Usdt) return 0 as UsdtMicros return 0 as Satoshis @@ -148,39 +160,40 @@ const toSettlementMinorUnit = ( } const toSettlementAmount = ( - ibexAmount: number | undefined, - transactionTypeId: number | undefined, - currency: WalletCurrency + ibexAmount: number | undefined, + transactionTypeId: number | undefined, + currency: WalletCurrency, ): SettlementMinorUnitAmount => { if (ibexAmount === undefined) { baseLogger.warn("Ibex did not return transaction amount") return toSettlementMinorUnit(ibexAmount, currency) } // When sending, make negative - const amt = (transactionTypeId === 2 || transactionTypeId === 4) - ? -1 * ibexAmount - : ibexAmount + const amt = + transactionTypeId === 2 || transactionTypeId === 4 || transactionTypeId === 10 + ? -1 * ibexAmount + : ibexAmount return toSettlementMinorUnit(amt, currency) } enum SortOrder { RECENT = "settledAt", - OLDEST = "-settledAt" + OLDEST = "-settledAt", } type IbexPaginationArgs = { - page?: number | undefined; // ibex default (0) start at page 0 - limit?: number | undefined; // ibex default (0) returns all - sort?: SortOrder | undefined; // defaults to SortOrder.RECENT + page?: number | undefined // ibex default (0) start at page 0 + limit?: number | undefined // ibex default (0) returns all + sort?: SortOrder | undefined // defaults to SortOrder.RECENT } export function toIbexPaginationArgs( - args: ConnectionArguments | undefined + args: ConnectionArguments | undefined, ): IbexPaginationArgs { const DEFAULTS = { - page: 0, - limit: 0, - sort: SortOrder.RECENT, + page: 0, + limit: 0, + sort: SortOrder.RECENT, } // Prefer 'first' over 'last') @@ -188,13 +201,13 @@ export function toIbexPaginationArgs( return { ...DEFAULTS, limit: args.first, - sort: SortOrder.RECENT, + sort: SortOrder.RECENT, } } else if (args && args.last != null) { return { ...DEFAULTS, limit: args.last, - sort: SortOrder.OLDEST, + sort: SortOrder.OLDEST, } } else return DEFAULTS } 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 1ac0611fe..69cf4db7d 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 @@ -76,6 +76,27 @@ describe("toWalletTransactions", () => { expect(transaction.settlementFee).toBe(1) }) + it("maps IBEX crypto send transaction type to outgoing on-chain USDT", () => { + const [transaction] = toWalletTransactions([ + { + id: "crypto-send-trx-id", + accountId: "wallet-id", + amount: 2.5, + networkFee: 0.179554, + currencyId: 29, + transactionTypeId: 10, + createdAt: "2026-06-17T05:42:53.512218Z", + }, + ] as GResponse200) + + expect(transaction.settlementCurrency).toBe(WalletCurrency.Usdt) + expect(transaction.settlementAmount).toBe(-2_500_000) + expect(transaction.settlementFee).toBe(179_554) + expect(transaction.settlementDisplayFee).toBe("0.179554") + expect(transaction.initiationVia.type).toBe("onchain") + expect(transaction.settlementVia.type).toBe("onchain") + }) + it("defaults omitted IBEX USDT amount and network fee to zero micros", () => { const [transaction] = toWalletTransactions([ { From 2444ccf66d006f45ec67cb8601d7c07ccf731dc5 Mon Sep 17 00:00:00 2001 From: Vandana Date: Thu, 18 Jun 2026 14:13:15 -0700 Subject: [PATCH 2/2] fix: sign IBEX crypto send display amounts --- src/app/wallets/get-transactions-for-wallet.ts | 17 ++++++++++++++++- .../wallets/get-transactions-for-wallet.spec.ts | 2 ++ 2 files changed, 18 insertions(+), 1 deletion(-) diff --git a/src/app/wallets/get-transactions-for-wallet.ts b/src/app/wallets/get-transactions-for-wallet.ts index b6a72d838..46dce9e7e 100644 --- a/src/app/wallets/get-transactions-for-wallet.ts +++ b/src/app/wallets/get-transactions-for-wallet.ts @@ -90,7 +90,10 @@ export const toWalletTransactions = (ibexResp: GResponse200): IbexTransaction[] settlementAmount: toSettlementAmount(trx.amount, trx.transactionTypeId, currency), settlementFee: toSettlementMinorUnit(trx.networkFee, currency), settlementCurrency: currency, - settlementDisplayAmount: `${trx.amount}`, + settlementDisplayAmount: toSettlementDisplayAmount( + trx.amount, + trx.transactionTypeId, + ), settlementDisplayFee: `${trx.networkFee}`, settlementDisplayPrice: settlementDisplayPrice, createdAt: trx.createdAt ? new Date(trx.createdAt) : new Date(), // should always return @@ -176,6 +179,18 @@ const toSettlementAmount = ( return toSettlementMinorUnit(amt, currency) } +const toSettlementDisplayAmount = ( + ibexAmount: number | undefined, + transactionTypeId: number | undefined, +): string => { + if (ibexAmount === undefined) return `${ibexAmount}` + const amount = + transactionTypeId === 2 || transactionTypeId === 4 || transactionTypeId === 10 + ? -1 * ibexAmount + : ibexAmount + return `${amount}` +} + enum SortOrder { RECENT = "settledAt", OLDEST = "-settledAt", 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 69cf4db7d..d8503e30e 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 @@ -73,6 +73,7 @@ describe("toWalletTransactions", () => { expect(transaction.settlementCurrency).toBe(WalletCurrency.Usdt) expect(transaction.settlementAmount).toBe(-500_000) + expect(transaction.settlementDisplayAmount).toBe("-0.5") expect(transaction.settlementFee).toBe(1) }) @@ -91,6 +92,7 @@ describe("toWalletTransactions", () => { expect(transaction.settlementCurrency).toBe(WalletCurrency.Usdt) expect(transaction.settlementAmount).toBe(-2_500_000) + expect(transaction.settlementDisplayAmount).toBe("-2.5") expect(transaction.settlementFee).toBe(179_554) expect(transaction.settlementDisplayFee).toBe("0.179554") expect(transaction.initiationVia.type).toBe("onchain")