Skip to content

Conversation

@aelmanaa
Copy link
Collaborator

@aelmanaa aelmanaa commented Jan 6, 2026

This pull request introduces a unified token balance query feature across the SDK and CLI for EVM, Solana, and Aptos chains. It adds a new Chain.getBalance() method to the SDK, a CLI command for querying balances, and supporting utilities for Solana token account resolution. The documentation is updated with usage examples for developers.

SDK: Token Balance Query API

  • Added the abstract getBalance() method to the Chain class, with implementations for EVM, Solana, and Aptos chains, enabling native and token balance queries using a unified API. [1] [2] [3] [4]
  • Introduced the GetBalanceOpts type for balance query options, exported from the SDK entrypoint. [1] [2]

CLI: Token Balance Command

  • Added the token <network> <address> [token] command to the CLI for querying balances, supporting native and token assets on EVM, Solana, and Aptos.
  • Updated CLI version string for release.

Solana: ATA Resolution Utility

  • Added the resolveATA() utility to automatically derive the correct Associated Token Account (ATA) and detect SPL Token vs Token-2022 programs, used in both SDK and CLI. [1] [2] [3] [4]

SDK: Chain Implementations and Error Handling

  • Provided mock/test implementations for getBalance() in test chains, and added stub methods for chains where balance queries are not supported (Sui, TON). [1] [2] [3] [4]

Documentation

  • Updated SDK documentation with examples for querying token balances across EVM, Solana, and Aptos chains using the new API.

  - Add getBalance() method to Chain class for native and token balances
  - Implement for EVM (native + ERC-20), Solana (SOL + SPL/Token-2022), Aptos (APT + Fungible Assets)
  - Add resolveATA() utility for Solana ATA derivation with automatic token program detection
  - Add CLI 'token <network> <address> [token]' command for balance queries
  - Sui and TON throw CCIPNotImplementedError (partial support)
@aelmanaa aelmanaa requested a review from a team as a code owner January 6, 2026 17:14
@vercel
Copy link

vercel bot commented Jan 6, 2026

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

Project Deployment Review Updated (UTC)
ccip-tools-ts Error Error Jan 6, 2026 5:15pm

@github-actions
Copy link

github-actions bot commented Jan 6, 2026

👋 aelmanaa, thanks for creating this pull request!

To help reviewers, please consider creating future PRs as drafts first. This allows you to self-review and make any final changes before notifying the team.

Once you're ready, you can mark it as "Ready for review" to request feedback. Thanks!

@github-actions
Copy link

github-actions bot commented Jan 6, 2026

Coverage Report

ℹ tests 642
ℹ suites 202
ℹ pass 642
ℹ fail 0
ℹ cancelled 0
ℹ skipped 0
ℹ todo 0
ℹ duration_ms 172734.489508
-------------------------------|---------|----------|---------|---------|---------------------------
File                           | % Stmts | % Branch | % Funcs | % Lines | Uncovered Line #s         
-------------------------------|---------|----------|---------|---------|---------------------------
All files                      |   66.46 |    79.73 |   55.48 |   66.46 |                           
 ccip-cli/src                  |   86.45 |    83.33 |      50 |   86.45 |                           
  index.ts                     |   86.45 |    83.33 |      50 |   86.45 | 72-76,82-83,88-93         
 ccip-cli/src/commands         |   51.94 |    79.55 |   52.08 |   51.94 |                           
  index.ts                     |     100 |      100 |     100 |     100 |                           
  lane-latency.ts              |   67.74 |     87.5 |   33.33 |   67.74 | 23-37,44-51,85-91         
  manual-exec.ts               |      21 |      100 |       0 |      21 | 42-106,113-123,125-236    
  parse.ts                     |   41.79 |      100 |       0 |   41.79 | 22-26,33-40,42-67         
  send.ts                      |   11.62 |      100 |       0 |   11.62 | 34-136,143-150,152-344    
  show.ts                      |   89.61 |    63.82 |      60 |   89.61 | ...23-129,135-137,146-147 
  supported-tokens.ts          |   23.48 |      100 |       0 |   23.48 | ...83-175,177-232,234-247 
  token.ts                     |   30.52 |      100 |       0 |   30.52 | 23-38,45-52,54-95         
  types.ts                     |     100 |      100 |     100 |     100 |                           
  utils.ts                     |   86.59 |    84.03 |    91.3 |   86.59 | ...99-517,524-532,542-548 
 ccip-cli/src/providers        |   53.94 |       75 |   17.39 |   53.94 |                           
  aptos.ts                     |   53.65 |      100 |       0 |   53.65 | ...70,78-85,91-92,101-123 
  evm.ts                       |   46.03 |      100 |       0 |   46.03 | 20,31-63                  
  index.ts                     |   82.95 |    72.09 |      80 |   82.95 | 49-50,188-223             
  solana.ts                    |   50.42 |      100 |       0 |   50.42 | ...-71,79-85,89-90,99-117 
  sui.ts                       |   64.28 |      100 |       0 |   64.28 | 10-14                     
  ton.ts                       |   14.58 |      100 |       0 |   14.58 | 22-144                    
 ccip-sdk/src                  |   93.34 |    88.68 |   92.98 |   93.34 |                           
  chain.ts                     |   92.99 |       84 |   71.42 |   92.99 | ...40,259-297,608,666-675 
  commits.ts                   |     100 |       90 |     100 |     100 | 35,52                     
  execution.ts                 |   84.61 |      100 |      75 |   84.61 | 122-143                   
  explorer.ts                  |     100 |      100 |     100 |     100 |                           
  extra-args.ts                |     100 |    78.57 |     100 |     100 | 71,90,103                 
  gas.ts                       |   96.42 |    92.85 |     100 |   96.42 | 81-85                     
  http-status.ts               |     100 |      100 |     100 |     100 |                           
  index.ts                     |     100 |      100 |     100 |     100 |                           
  offchain.ts                  |     100 |      100 |     100 |     100 |                           
  requests.ts                  |   78.97 |    76.92 |   85.71 |   78.97 | ...02-303,307-308,331-352 
  supported-chains.ts          |     100 |      100 |     100 |     100 |                           
  types.ts                     |     100 |      100 |     100 |     100 |                           
  utils.ts                     |   95.11 |     90.9 |     100 |   95.11 | ...51,515-516,532,597-605 
 ccip-sdk/src/api              |     100 |    94.73 |     100 |     100 |                           
  index.ts                     |     100 |    94.73 |     100 |     100 | 145                       
 ccip-sdk/src/aptos            |    57.5 |    68.53 |   59.09 |    57.5 |                           
  exec.ts                      |   29.31 |      100 |       0 |   29.31 | 18-58                     
  hasher.ts                    |   76.31 |       80 |   66.66 |   76.31 | 19-38,52-58               
  index.ts                     |   58.13 |    71.95 |   60.86 |   58.13 | ...19-754,758-851,855-866 
  logs.ts                      |   78.52 |    56.52 |      75 |   78.52 | ...90-196,200-233,264-268 
  send.ts                      |    25.2 |      100 |       0 |    25.2 | 10-51,62-79,92-123        
  token.ts                     |   23.75 |       75 |     100 |   23.75 | 35-156                    
  types.ts                     |   69.72 |      100 |       0 |   69.72 | 25-32,77-101              
  utils.ts                     |     100 |      100 |     100 |     100 |                           
 ccip-sdk/src/errors           |   80.94 |    76.61 |   49.55 |   80.94 |                           
  CCIPError.ts                 |     100 |      100 |     100 |     100 |                           
  codes.ts                     |     100 |      100 |     100 |     100 |                           
  index.ts                     |     100 |      100 |     100 |     100 |                           
  recovery.ts                  |     100 |      100 |     100 |     100 |                           
  specialized.ts               |   73.21 |    71.91 |   46.72 |   73.21 | ...39,1647-1656,1664-1673 
  utils.ts                     |   94.52 |    77.77 |     100 |   94.52 | 16,18,23,25               
 ccip-sdk/src/evm              |   64.83 |    78.82 |   61.97 |   64.83 |                           
  const.ts                     |     100 |      100 |     100 |     100 |                           
  errors.ts                    |   82.48 |    78.46 |     100 |   82.48 | ...55-157,188-195,198-216 
  hasher.ts                    |     100 |     87.5 |     100 |     100 | 122                       
  index.ts                     |   58.48 |    84.61 |    57.4 |   58.48 | ...27,1331-1404,1408-1457 
  logs.ts                      |   33.99 |    46.66 |      25 |   33.99 | ...13-214,229-258,274-302 
  messages.ts                  |     100 |      100 |     100 |     100 |                           
  offchain.ts                  |    80.4 |    53.33 |      75 |    80.4 | ...45-147,151-165,191-196 
 ccip-sdk/src/evm/viem         |   77.72 |       92 |   69.23 |   77.72 |                           
  client-adapter.ts            |     100 |     92.3 |     100 |     100 | 48                        
  index.ts                     |     100 |      100 |     100 |     100 |                           
  wallet-adapter.ts            |   63.09 |     90.9 |   55.55 |   63.09 | ...6,53-73,91-124,131-157 
 ccip-sdk/src/hasher           |   94.29 |    78.94 |     100 |   94.29 |                           
  common.ts                    |     100 |      100 |     100 |     100 |                           
  hasher.ts                    |     100 |    66.66 |     100 |     100 | 19                        
  index.ts                     |     100 |      100 |     100 |     100 |                           
  merklemulti.ts               |   93.43 |       78 |     100 |   93.43 | ...59-260,306-307,315-316 
 ccip-sdk/src/solana           |   51.53 |    64.25 |   54.54 |   51.53 |                           
  cleanup.ts                   |   12.22 |      100 |       0 |   12.22 | 29-229                    
  exec.ts                      |    9.27 |      100 |       0 |    9.27 | ...66-341,343-382,384-496 
  hasher.ts                    |   96.58 |    81.81 |     100 |   96.58 | 67-70                     
  index.ts                     |   69.36 |    68.55 |   62.96 |   69.36 | ...37,1441-1465,1469-1490 
  logs.ts                      |   74.19 |    27.27 |     100 |   74.19 | ...,50-51,53-54,56-88,119 
  offchain.ts                  |   78.87 |     64.7 |      50 |   78.87 | ...,96-97,110-111,126-142 
  patchBorsh.ts                |   65.06 |    57.14 |      75 |   65.06 | 30-49,65-66,72-78         
  send.ts                      |   14.13 |      100 |       0 |   14.13 | ...58-247,260-326,328-368 
  types.ts                     |   74.46 |      100 |       0 |   74.46 | 36-47                     
  utils.ts                     |   55.86 |    77.77 |   54.54 |   55.86 | ...21-334,376-439,445-460 
 ccip-sdk/src/sui              |   31.19 |    76.47 |    10.9 |   31.19 |                           
  discovery.ts                 |   16.56 |      100 |       0 |   16.56 | 18-34,44-157              
  events.ts                    |   30.21 |      100 |       0 |   30.21 | ...83-154,199-288,296-321 
  hasher.ts                    |   98.16 |    66.66 |     100 |   98.16 | 33,49                     
  index.ts                     |   33.77 |       80 |    7.31 |   33.77 | ...40-741,745-746,750-751 
  objects.ts                   |   12.77 |      100 |       0 |   12.77 | ...05-146,157-207,218-360 
 ccip-sdk/src/sui/manuallyExec |   39.46 |      100 |       0 |   39.46 |                           
  encoder.ts                   |   47.67 |      100 |       0 |   47.67 | 42-86                     
  index.ts                     |    34.3 |      100 |       0 |    34.3 | 48-137                    
 ccip-sdk/src/ton              |   88.04 |    87.28 |   72.46 |   88.04 |                           
  exec.ts                      |     100 |      100 |     100 |     100 |                           
  hasher.ts                    |   77.95 |    77.77 |      75 |   77.95 | 99-107,155-186            
  index.ts                     |   93.65 |    88.23 |   65.95 |   93.65 | ...61,1065-1066,1070-1071 
  logs.ts                      |     100 |    98.52 |     100 |     100 | 56                        
  types.ts                     |   78.41 |    69.23 |   66.66 |   78.41 | ...-76,94,121-134,136-139 
  utils.ts                     |   75.98 |    74.54 |     100 |   75.98 | ...55-261,273-327,329-332 
-------------------------------|---------|----------|---------|---------|---------------------------

Copy link
Contributor

@PabloMansanet PabloMansanet left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks good and heading in the right direction. There are some issues that need to be addressed though, mostly around error propagation and validation. Main issues blocking merge IMO:

  • Error propagation needs work as RPC errors are swallowed and translated to "balance 0" in ways that could be very opaque to the library user.
  • Solana in particular has some validation problems around ATA derivation, accepting invalid mint accounts.

)
return
case Format.log:
logger.log(`Balance: ${balance}`)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe this one could specify the token too?

Comment on lines +60 to +75
export async function resolveATA(
connection: Connection,
mint: PublicKey,
owner: PublicKey,
): Promise<ResolvedATA | null> {
const mintInfo = await connection.getAccountInfo(mint)
if (!mintInfo) {
return null
}
const ata = getAssociatedTokenAddressSync(mint, owner, undefined, mintInfo.owner)
return {
ata,
tokenProgram: mintInfo.owner,
mintInfo,
}
}
Copy link
Contributor

@PabloMansanet PabloMansanet Jan 9, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this could do with more validation: the promises here are pretty brittle.

For example, this will accept any account as mint as long as it has an owner. There's no validation done here that the owner is any of the two token programs, and it's not done in getAssociatedTokenAddressSync either (it will just perform address derivation with no checks).

This needs to check that mintInfo.owner matches one of the two token programs (SPL token or SPL token 22) to avoid false positives or mixups when switching the order of arguments.

if (!mintInfo) {
return null
}
const ata = getAssociatedTokenAddressSync(mint, owner, undefined, mintInfo.owner)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We're calling with allowOwnerOffCurve undefined, which defaults to false, and that means we can't resolve ATAs for PDAs, only for EOAs. There's significant value in retrieving the balance of PDAs in CCIP in particular (CCIPReceiver contracts often use PDAs as vaults). Unless there's a reason to avoid it, I think we should be passing true (or at least exposing it as an option).

Comment on lines +120 to +125
export type GetBalanceOpts = {
/** Token address. Use null/undefined for native token balance. */
token?: string | null
/** Address to query balance for. */
address: string
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

NIT: Do we need an Opts struct here? With only two parameters that seem globally valid, maybe passing them directly to getBalance is more readable.

Feel free to ignore me here though, might be unfamiliarity with TS practices.

})
return BigInt(balance)
} catch {
return 0n
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hmm, returning 0 balance with a blanket catch-all seems wrong to me. I think we need to bubble up an error instead.

const accountInfo = await this.connection.getTokenAccountBalance(resolved.ata)
return BigInt(accountInfo.value.amount)
} catch {
return 0n // Account doesn't exist = 0 balance
Copy link
Contributor

@PabloMansanet PabloMansanet Jan 9, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm really not sure about using 0 balance as the universal return when there's any error. This catch could've been hit by many reasons, such as connectivity problems.

Also, ironically, on Solana you can have a non-existent account with a positive balance 😆 a pattern that sometimes happens is that people will fund an address they know is derived from their programID, but not initialize it so it remains owned by the SystemProgram and can be used to pay for other things, so this comment wouldn't be accurate either.

Logging doesn't really help since this is a library, so I think we should have error propagation for the user to decide what to do when retrieving balance fails.

mintInfo.owner,
)
const resolved = await resolveATA(connection, token, owner)
if (!resolved) throw new CCIPTokenMintNotFoundError(token.toBase58())
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This error conversion seems to assume too much; ATA resolution could fail for other reasons (currently, for example, if owner is a PDA)

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.

3 participants