Skip to content

fix(solana): GetBalance reads the right chain + wallet management parity - #26

Merged
VickyXAI merged 2 commits into
mainfrom
fix/solana-balance
Aug 26, 2026
Merged

fix(solana): GetBalance reads the right chain + wallet management parity#26
VickyXAI merged 2 commits into
mainfrom
fix/solana-balance

Conversation

@VickyXAI

Copy link
Copy Markdown
Contributor

Two commits, both under "make Solana a first-class citizen". The first is a real bug; the second closes an API-surface gap.

GetBalance read the wrong chain

GetBalance called getUSDCBalance against the Base USDC contract unconditionally, so a Solana client fed its bs58 pubkey into an eth_call on the public Base RPCs. It never touched solanaRPCURL and never reported the caller's balance. Writing the test surfaced both failure modes against real endpoints:

the method eth_call is not supported
RPC returned status 429: Too Many Requests

So depending on the endpoint it either errors or answers about an address that is not the caller's, on a chain the client does not pay from.

It now branches on chain and reads the USDC SPL mint over Solana RPC. Every token account the owner holds for that mint is summed — one owner can hold more than the associated account, so reading only the first under-reports. Raw base units are summed and scaled once rather than converted per account, keeping rounding to a single step, and decimals come from the RPC rather than being assumed. A wallet that has never held USDC has no token account at all, which reads 0 rather than erroring.

GetBalanceTestnet stays Base Sepolia and now returns an explicit error on a Solana client — there is no configured devnet USDC mint, so a refusal is the honest answer.

Adds solanaRPCCallContext so the balance read honours the caller's context; the signing paths have no context to pass and keep using solanaRPCCall, which delegates with context.Background().

Wallet management parity

wallet.go exported thirteen helpers. solana_wallet.go had two, and both only loaded an existing key — its header says it "only LOADS", assuming a Solana user arrives with a funded wallet. That assumption is what made Solana second-class: you could pay with a key you already held, but could not mint a wallet, persist one, discover one another provider wrote, or get a funding link, without leaving the SDK.

Adds CreateSolanaWallet, SaveSolanaWallet, GetOrCreateSolanaWallet, GetSolanaWalletAddressFromEnvOrFile, ScanSolanaWallets, GetSolanaPaymentLinks, GetSolanaPayURI, and the three FormatSolana*Message funding helpers. Keys are written to ~/.blockrun/.solana-session with mode 0600, the same handling the EVM side gives them.

Three places where a naive mirror of the EVM helper would have been wrong:

  • GetSolanaPayURI amounts. EIP-681 encodes a uint256 in base units; Solana Pay's amount is a decimal quantity of the token. Copying the EVM convention would have asked every user for a million times the intended amount. A test pins it.
  • SolanaPaymentLinksInfo is its own type. PaymentLinksInfo's fields are Basescan and Ethereum, which mean nothing on Solana.
  • ScanSolanaWallets trusts the key over the file's address field. A wallet file that disagrees with its own key would otherwise report an address whose funds that key cannot spend.

Left alone deliberately

Onramp stays Base-only. It hardcodes "network": "base" and validates an EVM address, and whether the gateway's /v1/onramp/token accepts a Solana network is a server-side fact this repo cannot answer. Guessing would ship an API that looks usable and 400s on first call — and one that is hard to withdraw once it is in the public surface. Worth a follow-up once someone confirms the gateway side.

Testing

17 new cases across two files. Tests were written first; each fix was then mutated and the matching test confirmed failing before being restored — including the base-units mutation on GetSolanaPayURI and a mint-but-do-not-persist mutation on GetOrCreateSolanaWallet.

go vet, go test -race, and sync-brand-numbers.mjs --check all pass.

README's Solana section documents the new helpers and the Solana Pay amount distinction. VERSION is untouched, per the release gate's convention that PRs leave it alone.

1bcMax added 2 commits August 25, 2026 22:30
GetBalance called getUSDCBalance against the Base USDC contract
unconditionally, so a Solana client fed its bs58 pubkey into an eth_call
on the public Base RPCs. It never touched solanaRPCURL and never reported
the caller's balance — against a real RPC it either errors outright or
answers about an address that is not theirs, on a chain the client does
not pay from. The test suite surfaced both failure modes: "the method
eth_call is not supported" and a 429 from a rate-limited public endpoint.

It now branches on chain and reads the USDC SPL mint over Solana RPC.
Every token account the owner holds for that mint is summed: one owner can
hold more than the associated account, so reading only the first
under-reports the balance. Raw base units are summed and scaled once
rather than converted per account, keeping the rounding to a single step,
and decimals come from the RPC rather than being assumed. A wallet that
has never held USDC has no token account at all, which reads 0 rather than
erroring.

GetBalanceTestnet stays Base Sepolia and now returns an explicit error on
a Solana client. There is no configured devnet USDC mint, so the honest
answer is a refusal rather than a reading from the wrong chain.

Adds solanaRPCCallContext so the balance read honours the caller's
context; the signing paths have no context to pass and keep using
solanaRPCCall, which now delegates with context.Background().

README's payment and security sections are updated to match: GetBalance
works on both chains, and the genuinely Base-only helpers are named.
wallet.go exported thirteen helpers. solana_wallet.go had two, and both
only loaded an existing key — the file's own header says it "only LOADS",
on the assumption that a Solana user arrives with a funded wallet in hand.
That assumption is what made Solana second-class: a user could pay with a
key they already held, but could not mint a wallet, persist one, discover
one another provider wrote, or get a funding link, without leaving the SDK.

Adds CreateSolanaWallet, SaveSolanaWallet, GetOrCreateSolanaWallet,
GetSolanaWalletAddressFromEnvOrFile, ScanSolanaWallets,
GetSolanaPaymentLinks, GetSolanaPayURI, and the three FormatSolana*Message
funding helpers. Keys are written to ~/.blockrun/.solana-session with mode
0600, the same handling the EVM side gives them, and
GetOrCreateSolanaWallet resolves through LoadSolanaWallet so it honours
SOLANA_WALLET_KEY and the provider scan before minting anything.

GetSolanaPayURI is deliberately NOT a mirror of GetEIP681URI. EIP-681
encodes a uint256 in base units; Solana Pay's amount is a decimal quantity
of the token. Copying the EVM convention would have asked every user for a
million times the intended amount, so a test pins the distinction.

Two further departures from a naive mirror. SolanaPaymentLinksInfo is its
own type rather than PaymentLinksInfo, whose fields are Basescan and
Ethereum and mean nothing here. And ScanSolanaWallets trusts the key over
the wallet file's own address field: a file that disagrees with its key
would otherwise report an address whose funds that key cannot spend.

Onramp is left alone. It hardcodes network "base" and validates an EVM
address, and whether the gateway's /v1/onramp/token accepts a Solana
network is a server-side fact not answerable from this repo. Guessing
would ship an API that looks usable and 400s on first call.
@VickyXAI
VickyXAI merged commit 1c365cd into main Aug 26, 2026
1 check passed
VickyXAI pushed a commit that referenced this pull request Aug 26, 2026
…right chain

Ships #26. Minor rather than patch, departing from 0.19.6: that release was
bug fixes whose three new exported symbols were a consequence of the
fixes, so it stayed on the patch position. Here the new surface is the
point — nine exported functions and a new type — and the GetBalance fix is
the smaller half.

Solana wallet management reaches parity with the EVM side. wallet.go
exported thirteen helpers; solana_wallet.go had two, and both only loaded
an existing key. A Solana user could pay with a key they already held but
could not mint a wallet, persist one, discover one another provider wrote,
or get a funding link without leaving the SDK.

GetBalance read the Base USDC contract unconditionally, so a Solana client
fed its bs58 pubkey into an eth_call on public Base RPCs — it either
errored or answered about an address that was not the caller's, on a chain
the client does not pay from. It now branches on chain and sums every USDC
token account the owner holds.

Onramp stays Base-only. Whether the gateway accepts a Solana network is a
server-side fact this repo cannot answer, so it is left for a follow-up
rather than guessed at.
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