Skip to content

fix(app): resolve the token program before reading a balance - #21

Merged
OxToF merged 1 commit into
mainfrom
fix/t22-balance-display
Sep 14, 2026
Merged

OxToF merged 1 commit into
mainfrom
fix/t22-balance-display

Conversation

@OxToF

@OxToF OxToF commented Sep 14, 2026

Copy link
Copy Markdown
Owner

The symptom

Swapping USDC for a Token-2022 xStock on devnet succeeded — USDC debited, NVDAx in the wallet — and the swap card then showed a zero balance for the token the wallet was now holding. The percentage shortcuts are gated on that balance (disabled={!balanceIn}), so the token could neither be swapped back nor added as liquidity.

Nothing was ever at risk on-chain. The NVDAx mint Fno1yG9awfotJwo4XJ1wE6c6vzRXPo3TERrmcQkMBGfo is owned by TokenzQdBNbLqP5VEhdkAS6EPFLC1PHnBqCXEpPxuEb (verified against devnet), and the tokens sit in the Token-2022 ATA. That address is simply not the one the UI was looking at.

The cause

An associated-token address is seeded with the token program. Deriving a Token-2022 ATA under Tokenkeg yields an address that does not exist. On a transaction that fails loudly at simulation; on a balance read it throws, the caller catches, and the UI renders 0 — indistinguishable from an empty wallet.

Four sites, one pattern

In every one, the transaction beside it had already been migrated to Token-2022 and resolves both programs correctly, while the balance read kept userAta's classic-SPL default:

File What it reads Effect
AmmSwap.tsx swap input balance the reported symptom
Pools.tsx add-liquidity balances, either side LP form believes the wallet is empty
PartnerStream.tsx partner's bribe-token balance partner shown 0 of a token they hold
Gauge.tsx bribe deposit balance same

The last two matter beyond the xStocks: reward_mint is arbitrary by design — a partner bribing in USDG never touches the AMM — which is the same surface the program-side Token-2022 migration had to be reminded of.

The fix

userAtaAuto pairs the existing memoised getMintProgram with the ATA derivation, so a caller holding a mint from a picker, a pool or a partner record cannot silently get the wrong address. userAta keeps its default for the mints we control (SOLA, oSOLA, USDC and LP mints are deliberately classic SPL) and now carries a ☢️ pointing at the other helper.

Verification

  • yarn build clean — type-check included, which is what caught a bad import on the first pass.
  • Mint ownership confirmed against devnet rather than inferred from the frontend.

🤖 Generated with Claude Code

Swapping USDC for a Token-2022 xStock succeeded and then showed a zero balance
for the token the wallet was now holding, so the percentage shortcuts stayed
disabled and it could neither be swapped back nor added as liquidity. Nothing
was lost on-chain: the mint is owned by TokenzQdBN… and the tokens are in the
Token-2022 ATA, which is simply not the address the UI was looking at.

An associated-token address is seeded with the token program, so deriving a
Token-2022 ATA under Tokenkeg yields an address that does not exist. On a
transaction that fails loudly at simulation. On a balance read it throws, the
caller catches, and the UI renders 0 — indistinguishable from an empty wallet.

Four read sites had it, and the pattern was identical in every one: the
*transaction* next to it had been migrated to Token-2022 and resolves both
programs properly, while the *balance read* kept `userAta`'s classic-SPL
default.

- AmmSwap: the swap card's input balance (the reported symptom).
- Pools: the add-liquidity balances for either pool side.
- PartnerStream and Gauge: the bribe-token balance. `reward_mint` is arbitrary
  by design — a partner bribing in USDG never touches the AMM — which is the
  same surface the program-side migration had to be reminded of.

`userAtaAuto` now pairs the existing memoised `getMintProgram` with the ATA
derivation, so a caller holding a mint from a picker, a pool or a partner
record cannot silently get the wrong address. `userAta` keeps its default for
the mints we control — SOLA, oSOLA, USDC and LP mints are deliberately classic
SPL — and carries a ☢️ pointing at the other helper.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@vercel

vercel Bot commented Sep 14, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated
soladrome-lzn5 Ready Ready Preview Sep 14, 2026 3:19pm UTC

@OxToF
OxToF merged commit 8a9f2e1 into main Sep 14, 2026
6 checks passed
@OxToF
OxToF deleted the fix/t22-balance-display branch September 14, 2026 17:24
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant