Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion ccip-api-ref/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@chainlink/ccip-api-ref",
"version": "1.9.0",
"version": "1.9.1",
"private": true,
"description": "API Reference documentation for CCIP SDK and CLI",
"scripts": {
Expand Down
4 changes: 2 additions & 2 deletions ccip-api-ref/tsconfig.json
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
{
"compilerOptions": {
"target": "ES2025",
"target": "ESNext",
"module": "preserve",
"lib": ["ESNext", "DOM"],
"moduleResolution": "bundler",
"lib": ["ES2025", "DOM"],
"allowImportingTsExtensions": true,
"jsx": "react-jsx",
"strict": true,
Expand Down
6 changes: 3 additions & 3 deletions ccip-cli/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@chainlink/ccip-cli",
"version": "1.9.0",
"version": "1.9.1",
"description": "CCIP Command Line Interface, based on @chainlink/ccip-sdk",
"author": "Chainlink devs",
"license": "MIT",
Expand Down Expand Up @@ -42,15 +42,15 @@
"!**/__mocks__"
],
"devDependencies": {
"@types/node": "25.9.1",
"@types/node": "25.9.2",
"@types/update-notifier": "^6.0.8",
"@types/yargs": "17.0.35",
"tsx": "4.22.4",
"typescript": "6.0.3"
},
"dependencies": {
"@aptos-labs/ts-sdk": "^6.3.1",
"@chainlink/ccip-sdk": "^1.9.0",
"@chainlink/ccip-sdk": "^1.9.1",
"@coral-xyz/anchor": "^0.29.0",
"@ethers-ext/signer-ledger": "^6.0.0-beta.1",
"@inquirer/prompts": "8.5.2",
Expand Down
4 changes: 2 additions & 2 deletions ccip-cli/src/commands/e2e-helpers.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,8 @@ const __dirname = path.dirname(fileURLToPath(import.meta.url))
export const CLI_PATH = path.join(__dirname, '..', 'index.ts')

export const RPCS = [
process.env['RPC_SEPOLIA'] || 'https://ethereum-sepolia-rpc.publicnode.com',
process.env['RPC_AVAX'] || 'https://avalanche-fuji-c-chain-rpc.publicnode.com',
process.env['RPC_SEPOLIA'] || 'https://sepolia.gateway.tenderly.co',
process.env['RPC_AVAX'] || 'https://api.avax-test.network/ext/bc/C/rpc',
process.env['RPC_APTOS'] || 'testnet',
process.env['RPC_SOLANA'] || 'https://api.devnet.solana.com',
process.env['RPC_TON'] || 'https://testnet.toncenter.com/api/v2',
Expand Down
4 changes: 2 additions & 2 deletions ccip-cli/tsconfig.json
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
{
"compilerOptions": {
"target": "ES2025",
"target": "ESNext",
"module": "NodeNext",
"lib": ["ES2025"],
"lib": ["ESNext"],
"types": ["node"],
"strict": true,
"skipLibCheck": true,
Expand Down
6 changes: 3 additions & 3 deletions ccip-sdk/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@chainlink/ccip-sdk",
"version": "1.9.0",
"version": "1.9.1",
"description": "SDK/Library to interact with CCIP",
"author": "Chainlink devs",
"license": "MIT",
Expand Down Expand Up @@ -57,11 +57,11 @@
},
"devDependencies": {
"@types/bn.js": "^5.2.0",
"@types/node": "25.9.1",
"@types/node": "25.9.2",
"ethers-abitype": "1.0.3",
"prool": "^0.2.4",
"typescript": "6.0.3",
"viem": "^2.52.0"
"viem": "^2.52.2"
},
"dependencies": {
"@aptos-labs/ts-sdk": "^6.3.1",
Expand Down
6 changes: 6 additions & 0 deletions ccip-sdk/src/chain.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1286,6 +1286,10 @@ export abstract class Chain<F extends ChainFamily = ChainFamily> {
>): Promise<true> {
let registry
for (const { token, amount } of message.tokenAmounts ?? []) {
// Native value transfers aren't pool-managed (no registry entry / rate limiter),
// and `router` may be a sender helper (e.g. EtherSenderReceiver) rather than a
// Router/Ramp — skip the token-pool preflight for them.
if (!token || token.match(/^(0x)?0*$/i)) continue
registry ??= await this.getTokenAdminRegistryFor(router)
const { tokenPool } = await this.getRegistryTokenConfig(registry, token)
const remote = await this.getTokenPoolRemote(tokenPool!, destChainSelector)
Expand Down Expand Up @@ -1324,6 +1328,8 @@ export abstract class Chain<F extends ChainFamily = ChainFamily> {
for (const ta of message.tokenAmounts ?? []) {
const amount = ta.amount
const token = 'destTokenAddress' in ta ? ta.destTokenAddress : ta.token
// Native value transfers aren't pool-managed — skip the token-pool preflight.
if (!token || token.match(/^(0x)?0*$/i)) continue
registry ??= await this.getTokenAdminRegistryFor(offRamp)
const { tokenPool } = await this.getRegistryTokenConfig(registry, token)
const { typeAndVersion, lockBox } = await this.getTokenPoolConfig(tokenPool!)
Expand Down
4 changes: 2 additions & 2 deletions ccip-sdk/src/evm/fork.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,10 +26,10 @@ const SEPOLIA_CHAIN_ID = 11155111
const SEPOLIA_SELECTOR = 16015286601757825753n
const SEPOLIA_ROUTER = '0x0BF3dE8c5D3e8A2B34D2BEeB17ABfCeBaf363A59'

const FUJI_RPC = process.env['RPC_FUJI'] || 'https://avalanche-fuji-c-chain-rpc.publicnode.com'
const FUJI_RPC = process.env['RPC_FUJI'] || 'https://api.avax-test.network/ext/bc/C/rpc'
const FUJI_CHAIN_ID = 43113

const ARB_SEP_RPC = process.env['RPC_ARB_SEPOLIA'] || 'https://arbitrum-sepolia-rpc.publicnode.com'
const ARB_SEP_RPC = process.env['RPC_ARB_SEPOLIA'] || 'https://sepolia-rollup.arbitrum.io/rpc'
const ARB_SEP_CHAIN_ID = 421614

const ANVIL_PRIVATE_KEY = '0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80'
Expand Down
66 changes: 29 additions & 37 deletions ccip-sdk/src/evm/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1022,11 +1022,9 @@ export class EVMChain extends Chain<typeof ChainFamily.EVM> {
interfaces.Token,
this.provider,
) as unknown as TypedContract<typeof Token_ABI>
const [symbol, decimals, name] = await Promise.all([
contract.symbol(),
contract.decimals(),
contract.name(),
])
const symbol = await contract.symbol()
const decimals = await contract.decimals()
const name = await contract.name()
return { symbol, decimals: Number(decimals), name }
}

Expand Down Expand Up @@ -1274,10 +1272,8 @@ export class EVMChain extends Chain<typeof ChainFamily.EVM> {
interfaces.CCTPVerifier_v2_0,
this.provider,
) as unknown as TypedContract<typeof CCTPVerifier_2_0_ABI>
const [staticConfig, domainResult] = await Promise.all([
verifier.getStaticConfig(),
verifier.getDomain(destChainSelector),
])
const staticConfig = await verifier.getStaticConfig()
const domainResult = await verifier.getDomain(destChainSelector)
return {
sourceDomain: Number(staticConfig[3]), // localDomainIdentifier
destDomain: Number(domainResult.domainIdentifier),
Expand Down Expand Up @@ -1348,12 +1344,11 @@ export class EVMChain extends Chain<typeof ChainFamily.EVM> {
)
}

const [result, { decimals }] = await Promise.all([
const result =
blockTag != null
? contract.getTokenPrice.staticCall(token, { blockTag })
: contract.getTokenPrice(token),
this.getTokenInfo(token),
])
? await contract.getTokenPrice.staticCall(token, { blockTag })
: await contract.getTokenPrice(token)
const { decimals } = await this.getTokenInfo(token)

const rawPrice = BigInt(result.value)
return { price: Number(rawPrice) * 10 ** (decimals - 36) }
Expand All @@ -1364,10 +1359,9 @@ export class EVMChain extends Chain<typeof ChainFamily.EVM> {
opts: Parameters<Chain['getTotalFeesEstimate']>[0],
): Promise<TotalFeesEstimate> {
const tokenAmounts = opts.message.tokenAmounts
const ccipFee$ = this.getFee(opts)

if (!tokenAmounts?.length) {
return { ccipFee: await ccipFee$ }
return { ccipFee: await this.getFee(opts) }
}

const { token, amount } = tokenAmounts[0]!
Expand All @@ -1385,7 +1379,7 @@ export class EVMChain extends Chain<typeof ChainFamily.EVM> {
const onRamp = await this.getOnRampForRouter(opts.router, opts.destChainSelector)
const [, version] = await this.typeAndVersion(onRamp)
if (version < CCIPVersion.V2_0) {
return { ccipFee: await ccipFee$ }
return { ccipFee: await this.getFee(opts) }
}

const onRampContract = new Contract(
Expand All @@ -1399,19 +1393,17 @@ export class EVMChain extends Chain<typeof ChainFamily.EVM> {
token,
)) as string

const [ccipFee, { tokenTransferFeeConfig }, usdcDomains] = await Promise.all([
ccipFee$,
this.getTokenPoolConfig(poolAddress, {
destChainSelector: opts.destChainSelector,
finality,
tokenArgs,
}),
this.detectUsdcDomains(
poolAddress,
opts.destChainSelector,
extraArgs && 'ccvs' in extraArgs ? extraArgs.ccvs : [],
),
])
const ccipFee = await this.getFee(opts)
const { tokenTransferFeeConfig } = await this.getTokenPoolConfig(poolAddress, {
destChainSelector: opts.destChainSelector,
finality,
tokenArgs,
})
const usdcDomains = await this.detectUsdcDomains(
poolAddress,
opts.destChainSelector,
extraArgs && 'ccvs' in extraArgs ? extraArgs.ccvs : [],
)

// USDC path: use Circle CCTP burn fees
if (usdcDomains) {
Expand Down Expand Up @@ -2138,13 +2130,13 @@ export class EVMChain extends Chain<typeof ChainFamily.EVM> {
) as unknown as TypedContract<typeof FeeQuoter_1_6_ABI>
const tokens = await contract.getFeeTokens()

return Object.fromEntries(
await Promise.all(
tokens.map(
async (token) => [token as string, await this.getTokenInfo(token as string)] as const,
),
),
)
const entries: Array<readonly [string, Awaited<ReturnType<typeof this.getTokenInfo>>]> = []
for (const token of tokens) {
const address = token as string
entries.push([address, await this.getTokenInfo(address)] as const)
}

return Object.fromEntries(entries)
}

/** {@inheritDoc Chain.getVerifications} */
Expand Down
6 changes: 3 additions & 3 deletions ccip-sdk/src/evm/integration.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,13 +22,13 @@ import { EVMChain } from './index.ts'
// Integration tests issue many live RPC calls (no anvil fork to absorb them), so the
// defaults point at reliable public endpoints. Free gateways (tenderly, avax public)
// rate-limit/stall under this load and time out the suite. Override via RPC_* env vars.
const SEPOLIA_RPC = process.env['RPC_SEPOLIA'] || 'https://ethereum-sepolia-rpc.publicnode.com'
const SEPOLIA_RPC = process.env['RPC_SEPOLIA'] || 'https://sepolia.gateway.tenderly.co'
const SEPOLIA_SELECTOR = 16015286601757825753n
const SEPOLIA_ROUTER = '0x0BF3dE8c5D3e8A2B34D2BEeB17ABfCeBaf363A59'

const FUJI_RPC = process.env['RPC_FUJI'] || 'https://avalanche-fuji-c-chain-rpc.publicnode.com'
const FUJI_RPC = process.env['RPC_FUJI'] || 'https://api.avax-test.network/ext/bc/C/rpc'

const ARB_SEP_RPC = process.env['RPC_ARB_SEPOLIA'] || 'https://arbitrum-sepolia-rpc.publicnode.com'
const ARB_SEP_RPC = process.env['RPC_ARB_SEPOLIA'] || 'https://sepolia-rollup.arbitrum.io/rpc'
const ARB_SEP_SELECTOR = 3478487238524512106n
const ARB_SEP_V2_0_ROUTER = '0x8F95FA37c55eF7beFdf05f6abDeC551773E17Fb4'

Expand Down
2 changes: 2 additions & 0 deletions ccip-sdk/src/selectors.ts
Original file line number Diff line number Diff line change
Expand Up @@ -461,6 +461,7 @@ const SELECTORS: Selectors = {
selector: 4348158687435793198n,
name: 'ethereum-mainnet-polygon-zkevm-1',
network_type: 'MAINNET',
deprecated: true,
family: 'EVM',
},
'1111': {
Expand Down Expand Up @@ -751,6 +752,7 @@ const SELECTORS: Selectors = {
selector: 4560701533377838164n,
name: 'bitcoin-mainnet-botanix',
network_type: 'MAINNET',
deprecated: true,
family: 'EVM',
},
'3776': {
Expand Down
24 changes: 20 additions & 4 deletions ccip-sdk/src/solana/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -157,6 +157,10 @@ interface ParsedTokenInfo {
name?: string
symbol?: string
decimals: number
extensions?: Array<{
extension: string
state: { name?: string; symbol?: string; [key: string]: unknown }
}>
}

// hardcoded symbols for tokens without metadata
Expand Down Expand Up @@ -741,15 +745,27 @@ export class SolanaChain extends Chain<typeof ChainFamily.Solana> {
if (typeof mintInfo.value.data === 'object' && 'parsed' in mintInfo.value.data) {
const parsed = mintInfo.value.data.parsed as { info: ParsedTokenInfo }
const data = parsed.info
let symbol = data.symbol || unknownTokens[token] || 'UNKNOWN'
let name = data.name

// Token-2022 tokens may embed metadata in extensions
const tokenMetadataExt = data.extensions?.find((e) => e.extension === 'tokenMetadata')
const extSymbol = tokenMetadataExt?.state.symbol
const extName = tokenMetadataExt?.state.name
const rawSymbol = data.symbol || extSymbol

// Track whether we have an on-chain authoritative symbol/name (parsed fields or T-2022 extension).
// unknownTokens / 'UNKNOWN' are fallbacks — Metaplex can still override them.
let symbol = rawSymbol || unknownTokens[token] || 'UNKNOWN'
let name = data.name || extName

const hasAuthoritativeSymbol = !!rawSymbol && rawSymbol !== 'UNKNOWN'
const hasAuthoritativeName = !!name

// If symbol or name is missing, try to fetch from Metaplex metadata
if (!data.symbol || symbol === 'UNKNOWN' || !data.name) {
if (!hasAuthoritativeSymbol || !hasAuthoritativeName) {
try {
const metadata = await this._fetchTokenMetadata(mint)
if (metadata) {
if (metadata.symbol && (!data.symbol || symbol === 'UNKNOWN')) {
if (metadata.symbol && !hasAuthoritativeSymbol) {
symbol = metadata.symbol
}
if (metadata.name && !name) {
Expand Down
4 changes: 2 additions & 2 deletions ccip-sdk/tsconfig.json
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
{
"compilerOptions": {
"target": "ES2025",
"target": "ESNext",
"module": "NodeNext",
"lib": ["ES2025"],
"lib": ["ESNext"],
"types": ["node"],
"strict": true,
"skipLibCheck": true,
Expand Down
Loading
Loading