fix(solana): GetBalance reads the right chain + wallet management parity - #26
Merged
Conversation
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
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.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Two commits, both under "make Solana a first-class citizen". The first is a real bug; the second closes an API-surface gap.
GetBalanceread the wrong chainGetBalancecalledgetUSDCBalanceagainst the Base USDC contract unconditionally, so a Solana client fed its bs58 pubkey into aneth_callon the public Base RPCs. It never touchedsolanaRPCURLand never reported the caller's balance. Writing the test surfaced both failure modes against real endpoints: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.
GetBalanceTestnetstays 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
solanaRPCCallContextso the balance read honours the caller's context; the signing paths have no context to pass and keep usingsolanaRPCCall, which delegates withcontext.Background().Wallet management parity
wallet.goexported thirteen helpers.solana_wallet.gohad 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 threeFormatSolana*Messagefunding helpers. Keys are written to~/.blockrun/.solana-sessionwith mode 0600, the same handling the EVM side gives them.Three places where a naive mirror of the EVM helper would have been wrong:
GetSolanaPayURIamounts. EIP-681 encodes a uint256 in base units; Solana Pay'samountis 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.SolanaPaymentLinksInfois its own type.PaymentLinksInfo's fields areBasescanandEthereum, which mean nothing on Solana.ScanSolanaWalletstrusts the key over the file'saddressfield. A wallet file that disagrees with its own key would otherwise report an address whose funds that key cannot spend.Left alone deliberately
Onrampstays Base-only. It hardcodes"network": "base"and validates an EVM address, and whether the gateway's/v1/onramp/tokenaccepts 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
GetSolanaPayURIand a mint-but-do-not-persist mutation onGetOrCreateSolanaWallet.go vet,go test -race, andsync-brand-numbers.mjs --checkall pass.README's Solana section documents the new helpers and the Solana Pay amount distinction.
VERSIONis untouched, per the release gate's convention that PRs leave it alone.