Skip to content

fix(solana): confirm native SOL after a swap, and stop the circular USD check - #418

Merged
BitHighlander merged 1 commit into
developfrom
fix/solana-swap-balance-and-circular-usd
Aug 15, 2026
Merged

fix(solana): confirm native SOL after a swap, and stop the circular USD check#418
BitHighlander merged 1 commit into
developfrom
fix/solana-swap-balance-and-circular-usd

Conversation

@BitHighlander

Copy link
Copy Markdown
Collaborator

Two defects from one swap into SOL: the balance never updated, and a wildly
wrong received amount displayed a perfectly plausible dollar figure.

1. Swapping INTO native SOL had no direct confirmation

index.ts post-swap reconcile matched destinations with:

/^(solana:[^/]+)\/(?:token|spl):([1-9A-HJ-NP-Za-km-z]{32,44})$/

That is SPL-only. Native SOL is solana:<net>/slip44:501 — no mint — so it hit
continue and got no direct read at all, falling back entirely to Pioneer's
portfolio indexer, which lags a completed swap. The dashboard kept showing the
pre-swap balance (0.00999 SOL after receiving ~0.23) across refreshes with
nothing to explain why. The comment right above that loop says a completed SPL
swap "can beat Pioneer's portfolio indexer by several seconds" — native SOL has
the same problem and was simply never covered.

Adds getSolanaNativeBalance() (getBalance RPC, 9 decimals — SOL is not a mint
and has no on-chain decimals field) and a native branch in the reconcile loop.

The entry match folds case deliberately: Pioneer returns Solana network ids
LOWERCASED (solana:5eykt4usfv8...) while the vault derives them mixed-case
(solana:5eykt4UsFv8...). A byte-exact compare finds nothing and silently drops
the fresh balance. Both spellings are in one getBalances log.

Following the file's own convention, an RPC that does not answer throws rather
than reporting 0 — a failed lookup is not an empty account.

2. The USD under "You received" could not disagree with the amount

toPriceUsd falls back to deriving the destination price from the quote ratio:

derived = (inAmt / outAmt) * fromPriceUsd

and the panel renders outAmt * toPriceUsd. Substituting:

outAmt × (inAmt / outAmt) × fromPriceUsd  ≡  inAmt × fromPriceUsd

The figure restates what was SENT and is independent of the output entirely. A
quote returning 232,196,097,603.76 SOL still printed "≈ $49.06" — correct to the
cent, and corroborating nothing. That is what made a ~1e12-scaled amount look
credible instead of obviously broken.

Suppress the valuation when the price is quote-derived. The wrong amount then
appears with no dollar figure, which reads as broken — which it is. (The "net vs
send" line already self-cancelled to 0 in this case and rendered nothing.)

Not fixed here: the bad expectedOutput itself. swap-parsing takes Pioneer's
value verbatim ("Trust Pioneer's value directly — no per-asset rescale") and the
destination symbol came back as "SOLANA" rather than "SOL", so that quote's
asset metadata is wrong upstream. Needs a Pioneer-side look with the raw quote
body; this change stops Vault from dressing it up as verified.

make test-unit 480 pass, 0 fail across 32 files, +35 btc-backend, +10 cosmos
tsc --noEmit 627, unchanged, 2 below baseline 629

…SD check

Two defects from one swap into SOL: the balance never updated, and a wildly
wrong received amount displayed a perfectly plausible dollar figure.

## 1. Swapping INTO native SOL had no direct confirmation

index.ts post-swap reconcile matched destinations with:

    /^(solana:[^/]+)\/(?:token|spl):([1-9A-HJ-NP-Za-km-z]{32,44})$/

That is SPL-only. Native SOL is `solana:<net>/slip44:501` — no mint — so it hit
`continue` and got no direct read at all, falling back entirely to Pioneer's
portfolio indexer, which lags a completed swap. The dashboard kept showing the
pre-swap balance (0.00999 SOL after receiving ~0.23) across refreshes with
nothing to explain why. The comment right above that loop says a completed SPL
swap "can beat Pioneer's portfolio indexer by several seconds" — native SOL has
the same problem and was simply never covered.

Adds getSolanaNativeBalance() (getBalance RPC, 9 decimals — SOL is not a mint
and has no on-chain decimals field) and a native branch in the reconcile loop.

The entry match folds case deliberately: Pioneer returns Solana network ids
LOWERCASED (`solana:5eykt4usfv8...`) while the vault derives them mixed-case
(`solana:5eykt4UsFv8...`). A byte-exact compare finds nothing and silently drops
the fresh balance. Both spellings are in one getBalances log.

Following the file's own convention, an RPC that does not answer throws rather
than reporting 0 — a failed lookup is not an empty account.

## 2. The USD under "You received" could not disagree with the amount

toPriceUsd falls back to deriving the destination price from the quote ratio:

    derived = (inAmt / outAmt) * fromPriceUsd

and the panel renders `outAmt * toPriceUsd`. Substituting:

    outAmt × (inAmt / outAmt) × fromPriceUsd  ≡  inAmt × fromPriceUsd

The figure restates what was SENT and is independent of the output entirely. A
quote returning 232,196,097,603.76 SOL still printed "≈ $49.06" — correct to the
cent, and corroborating nothing. That is what made a ~1e12-scaled amount look
credible instead of obviously broken.

Suppress the valuation when the price is quote-derived. The wrong amount then
appears with no dollar figure, which reads as broken — which it is. (The "net vs
send" line already self-cancelled to 0 in this case and rendered nothing.)

Not fixed here: the bad `expectedOutput` itself. swap-parsing takes Pioneer's
value verbatim ("Trust Pioneer's value directly — no per-asset rescale") and the
destination symbol came back as "SOLANA" rather than "SOL", so that quote's
asset metadata is wrong upstream. Needs a Pioneer-side look with the raw quote
body; this change stops Vault from dressing it up as verified.

make test-unit   480 pass, 0 fail across 32 files, +35 btc-backend, +10 cosmos
tsc --noEmit     627, unchanged, 2 below baseline 629
@BitHighlander
BitHighlander merged commit 48daf56 into develop Aug 15, 2026
4 checks passed
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