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
12 changes: 10 additions & 2 deletions src/app/wallets/get-balance-for-wallet.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { UnknownLedgerError } from "@domain/ledger"
import { USDAmount, USDTAmount, WalletCurrency } from "@domain/shared"
import Ibex from "@services/ibex/client"
import { IbexError, UnexpectedIbexResponse } from "@services/ibex/errors"
import { IbexError } from "@services/ibex/errors"

export const getBalanceForWallet = async ({
walletId,
Expand All @@ -18,7 +18,15 @@ export const getBalanceForWallet = async ({
}
return resp
}
if (resp.balance === undefined) return new UnexpectedIbexResponse("Balance not found")
if (resp.balance === undefined) {
// IBEX omits `balance` for drained / never-funded accounts (per-account and
// bulk endpoints alike: absent means zero — verified in prod during the USDT
// cutover). Post-cutover, every migrated account's legacy USD wallet reads
// this way; returning an error here broke the admin API's wallets[].balance
// for all migrated accounts (the cash-wallet compat redirect only runs when
// client capabilities are in ctx, i.e. for app users — never for admin).
return currency === WalletCurrency.Usdt ? USDTAmount.ZERO : USDAmount.ZERO
}
return resp.balance
} catch (err) {
return new UnknownLedgerError(err)
Expand Down
33 changes: 32 additions & 1 deletion test/flash/unit/app/wallets/get-balance-for-wallet.spec.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { getBalanceForWallet } from "@app/wallets/get-balance-for-wallet"
import { USDTAmount, WalletCurrency } from "@domain/shared"
import { USDAmount, USDTAmount, WalletCurrency } from "@domain/shared"
import Ibex from "@services/ibex/client"

jest.mock("@services/ibex/client", () => ({
Expand Down Expand Up @@ -35,4 +35,35 @@ describe("getBalanceForWallet", () => {
expect(Ibex.getCryptoReceiveBalance).not.toHaveBeenCalled()
expect(result).toBe(balance)
})

// IBEX omits `balance` on drained / never-funded accounts (absent means zero —
// verified in prod during the USDT cutover). Post-cutover every migrated
// account's legacy USD wallet reads this way; it must resolve to zero, not an
// error (an error here broke admin-API wallets[].balance for all migrated
// accounts, since the compat redirect never runs in admin ctx).
it("treats a missing balance field as zero for USD (drained legacy wallet)", async () => {
jest.mocked(Ibex.getAccountDetails).mockResolvedValue({
id: "drained-usd-ibex-id",
} as never)

const result = await getBalanceForWallet({
walletId: "drained-usd-ibex-id" as WalletId,
currency: WalletCurrency.Usd,
})

expect(result).toBe(USDAmount.ZERO)
})

it("treats a missing balance field as zero for USDT", async () => {
jest.mocked(Ibex.getAccountDetails).mockResolvedValue({
id: "drained-usdt-ibex-id",
} as never)

const result = await getBalanceForWallet({
walletId: "drained-usdt-ibex-id" as WalletId,
currency: WalletCurrency.Usdt,
})

expect(result).toBe(USDTAmount.ZERO)
})
})
Loading