Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
26 changes: 17 additions & 9 deletions src/app/wallets/get-transactions-for-wallet.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -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,
Expand All @@ -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)
}

Expand All @@ -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}`
}

Expand Down
102 changes: 93 additions & 9 deletions test/flash/unit/app/wallets/get-transactions-for-wallet.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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([
{
Expand Down Expand Up @@ -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", () => {
Expand Down
Loading