diff --git a/src/app/wallets/get-balance-for-wallet.ts b/src/app/wallets/get-balance-for-wallet.ts index 46ddf6cad..7a2a0b945 100644 --- a/src/app/wallets/get-balance-for-wallet.ts +++ b/src/app/wallets/get-balance-for-wallet.ts @@ -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, @@ -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) diff --git a/test/flash/unit/app/wallets/get-balance-for-wallet.spec.ts b/test/flash/unit/app/wallets/get-balance-for-wallet.spec.ts index 4c7fe2680..92738907d 100644 --- a/test/flash/unit/app/wallets/get-balance-for-wallet.spec.ts +++ b/test/flash/unit/app/wallets/get-balance-for-wallet.spec.ts @@ -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", () => ({ @@ -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) + }) })