diff --git a/.gitignore b/.gitignore index 352412d..75dedef 100644 --- a/.gitignore +++ b/.gitignore @@ -5,3 +5,4 @@ dist/ .envrc .env coverage +coverage-summary.txt diff --git a/README.md b/README.md index 53e1565..97e3a59 100644 --- a/README.md +++ b/README.md @@ -9,6 +9,7 @@ in supported blockchains, through its publicly accessible data and methods. > This tool is provided under an MIT license and is for convenience and illustration purposes only. ## Development + In order to run commands/test from this repo, do the following: ```sh @@ -26,6 +27,7 @@ npx path/to/repo/ccip-tools-ts --help # or pointing to folder directly > In dev context below, we'll call `$cli="./src/index.ts"` ## RPCs + All commands require a list of RPCs endpoints for the networks of interest (source and destination). Both `http[s]` and `ws[s]` (websocket) URLs are supported. @@ -38,6 +40,7 @@ arrays should work out of the box. Once the list is gathered, we connect to all RPCs and use the fastest to reply for each network. ## Wallet + Commands which need to send transactions try to get its private key from a `USER_KEY` environment variable. @@ -98,6 +101,7 @@ $cli send 11155111 0x0BF3dE8c5D3e8A2B34D2BEeB17ABfCeBaf363A59 ethereum-testnet-s ``` Sends a message from router on source network, to dest; positional parameters are: + 1. `source`: chainId or name 2. `router`: address on source 3. `dest`: chainId or name @@ -118,7 +122,7 @@ If `--fee-token` is omitted, CCIP fee will be paid in native token. separated by spaces, terminated with `--` if needed). `amount` will be converted using token decimals (e.g. 0.1 = 10^5 of the smallest unit for USDC, which is 6 decimals). -`--allow-out-of-order-exec` is only available on v1.5+ lanes, and opt-out of *sender* `nonce` order +`--allow-out-of-order-exec` is only available on v1.5+ lanes, and opt-out of _sender_ `nonce` order enforcement. It's useful for destinations where execution can't be guaranteed (e.g. zkOverflow). ### `estimateGas` @@ -166,3 +170,78 @@ If 3rd argument is omitted, 2nd argument (address) should be an OnRamp address. Also, performs some validations and warns in case of some mistmatches, e.g. OnRamp is not registered in Router. + +### `getSupportedTokens` + +```sh +$cli getSupportedTokens +``` + +Discovers and validates tokens that can be transferred between chains using CCIP. The command performs a comprehensive analysis of cross-chain token support. + +#### Parameters + +- `source`: Source chain ID or name (e.g., `ethereum-mainnet`, `1`) +- `router`: CCIP Router contract address on source chain +- `dest`: Destination chain ID or name (e.g., `polygon-mainnet`, `137`) + +#### Features + +- Automatic pool version detection +- Custom pool support with fallback to latest ABI +- Rate limiter configuration validation +- Parallel processing with configurable batch sizes +- Comprehensive error collection + +#### Example + +```sh +# Check tokens supported for transfer from Ethereum to Polygon +$cli getSupportedTokens ethereum-mainnet 0x80226fc0Ee2b096224EeAc085Bb9a8cba1146f7D polygon-mainnet --format pretty +``` + +#### Output Format Options + +- `--format pretty` (default): Human-readable output +- `--format json`: Machine-readable JSON +- `--format log`: Basic console logging + +#### Sample Output + +``` +=== Summary === +Timestamp: 2025-01-13T16:30:51.983Z + +Source: + Chain: ethereum-mainnet (1) + Router: 0x80226fc0Ee2b096224EeAc085Bb9a8cba1146f7D + +Destination: + Chain: polygon-mainnet (137) + +Stats: + Total Scanned: 83 + Supported: 20 + Failed: 0 + +=== Supported Tokens === +[INFO] Token: TEST (TEST) at 0xB006...33CC, decimals=18 + Pool: 0x5504...d451 (BurnMintTokenPool v1.5.0) + Remote Token: 0x18a4...044a + Remote Pools: 0xa0c3...1fc6 + Rate Limiters: + Outbound: + Enabled: true + Tokens: 100000000000000000000000 + Capacity: 100000000000000000000000 + Rate: 167000000000000000000 + Inbound: + Enabled: true + Tokens: 100000000000000000000000 + Capacity: 100000000000000000000000 + Rate: 167000000000000000000 + ... +``` + +> [!NOTE] +> The command requires access to both source and destination chain RPCs. Configure RPCs using the global options described in the [RPCs section](#rpcs). diff --git a/src/abi/BurnMintERC677Token.ts b/src/abi/BurnMintERC677Token.ts index 372576e..aeca299 100644 --- a/src/abi/BurnMintERC677Token.ts +++ b/src/abi/BurnMintERC677Token.ts @@ -1,6 +1,6 @@ export default [ // generate: - // fetch('https://github.com/smartcontractkit/ccip/raw/release/2.14.0-ccip1.5/core/gethwrappers/generated/burn_mint_erc677/burn_mint_erc677.go') + // fetch('https://github.com/smartcontractkit/ccip/raw/release/contracts-ccip-1.5.1/core/gethwrappers/generated/burn_mint_erc677/burn_mint_erc677.go') // .then((res) => res.text()) // .then((body) => body.match(/^\s*ABI: "(.*?)",$/m)?.[1]) // .then((abi) => JSON.parse(abi.replace(/\\"/g, '"'))) diff --git a/src/abi/BurnMintTokenPool_1_5_1.ts b/src/abi/BurnMintTokenPool_1_5_1.ts new file mode 100644 index 0000000..5270af8 --- /dev/null +++ b/src/abi/BurnMintTokenPool_1_5_1.ts @@ -0,0 +1,1172 @@ +export default [ + // generate: + // fetch('https://github.com/smartcontractkit/ccip/raw/release/contracts-ccip-1.5.1/core/gethwrappers/ccip/generated/burn_mint_token_pool/burn_mint_token_pool.go') + // .then((res) => res.text()) + // .then((body) => body.match(/^\s*ABI: "(.*?)",$/m)?.[1]) + // .then((abi) => JSON.parse(abi.replace(/\\"/g, '"'))) + // .then((obj) => require('util').inspect(obj, {depth:99}).split('\n').slice(1, -1)) + { + inputs: [ + { + internalType: 'contractIBurnMintERC20', + name: 'token', + type: 'address', + }, + { + internalType: 'uint8', + name: 'localTokenDecimals', + type: 'uint8', + }, + { + internalType: 'address[]', + name: 'allowlist', + type: 'address[]', + }, + { internalType: 'address', name: 'rmnProxy', type: 'address' }, + { internalType: 'address', name: 'router', type: 'address' }, + ], + stateMutability: 'nonpayable', + type: 'constructor', + }, + { + inputs: [ + { internalType: 'uint256', name: 'capacity', type: 'uint256' }, + { internalType: 'uint256', name: 'requested', type: 'uint256' }, + ], + name: 'AggregateValueMaxCapacityExceeded', + type: 'error', + }, + { + inputs: [ + { + internalType: 'uint256', + name: 'minWaitInSeconds', + type: 'uint256', + }, + { internalType: 'uint256', name: 'available', type: 'uint256' }, + ], + name: 'AggregateValueRateLimitReached', + type: 'error', + }, + { inputs: [], name: 'AllowListNotEnabled', type: 'error' }, + { inputs: [], name: 'BucketOverfilled', type: 'error' }, + { + inputs: [{ internalType: 'address', name: 'caller', type: 'address' }], + name: 'CallerIsNotARampOnRouter', + type: 'error', + }, + { inputs: [], name: 'CannotTransferToSelf', type: 'error' }, + { + inputs: [{ internalType: 'uint64', name: 'chainSelector', type: 'uint64' }], + name: 'ChainAlreadyExists', + type: 'error', + }, + { + inputs: [ + { + internalType: 'uint64', + name: 'remoteChainSelector', + type: 'uint64', + }, + ], + name: 'ChainNotAllowed', + type: 'error', + }, + { inputs: [], name: 'CursedByRMN', type: 'error' }, + { + inputs: [ + { + components: [ + { internalType: 'bool', name: 'isEnabled', type: 'bool' }, + { + internalType: 'uint128', + name: 'capacity', + type: 'uint128', + }, + { internalType: 'uint128', name: 'rate', type: 'uint128' }, + ], + internalType: 'structRateLimiter.Config', + name: 'config', + type: 'tuple', + }, + ], + name: 'DisabledNonZeroRateLimit', + type: 'error', + }, + { + inputs: [ + { internalType: 'uint8', name: 'expected', type: 'uint8' }, + { internalType: 'uint8', name: 'actual', type: 'uint8' }, + ], + name: 'InvalidDecimalArgs', + type: 'error', + }, + { + inputs: [ + { + components: [ + { internalType: 'bool', name: 'isEnabled', type: 'bool' }, + { + internalType: 'uint128', + name: 'capacity', + type: 'uint128', + }, + { internalType: 'uint128', name: 'rate', type: 'uint128' }, + ], + internalType: 'structRateLimiter.Config', + name: 'rateLimiterConfig', + type: 'tuple', + }, + ], + name: 'InvalidRateLimitRate', + type: 'error', + }, + { + inputs: [{ internalType: 'bytes', name: 'sourcePoolData', type: 'bytes' }], + name: 'InvalidRemoteChainDecimals', + type: 'error', + }, + { + inputs: [ + { + internalType: 'uint64', + name: 'remoteChainSelector', + type: 'uint64', + }, + { + internalType: 'bytes', + name: 'remotePoolAddress', + type: 'bytes', + }, + ], + name: 'InvalidRemotePoolForChain', + type: 'error', + }, + { + inputs: [ + { + internalType: 'bytes', + name: 'sourcePoolAddress', + type: 'bytes', + }, + ], + name: 'InvalidSourcePoolAddress', + type: 'error', + }, + { + inputs: [{ internalType: 'address', name: 'token', type: 'address' }], + name: 'InvalidToken', + type: 'error', + }, + { inputs: [], name: 'MismatchedArrayLengths', type: 'error' }, + { inputs: [], name: 'MustBeProposedOwner', type: 'error' }, + { + inputs: [ + { + internalType: 'uint64', + name: 'remoteChainSelector', + type: 'uint64', + }, + ], + name: 'NonExistentChain', + type: 'error', + }, + { inputs: [], name: 'OnlyCallableByOwner', type: 'error' }, + { + inputs: [ + { internalType: 'uint8', name: 'remoteDecimals', type: 'uint8' }, + { internalType: 'uint8', name: 'localDecimals', type: 'uint8' }, + { + internalType: 'uint256', + name: 'remoteAmount', + type: 'uint256', + }, + ], + name: 'OverflowDetected', + type: 'error', + }, + { inputs: [], name: 'OwnerCannotBeZero', type: 'error' }, + { + inputs: [ + { + internalType: 'uint64', + name: 'remoteChainSelector', + type: 'uint64', + }, + { + internalType: 'bytes', + name: 'remotePoolAddress', + type: 'bytes', + }, + ], + name: 'PoolAlreadyAdded', + type: 'error', + }, + { inputs: [], name: 'RateLimitMustBeDisabled', type: 'error' }, + { + inputs: [{ internalType: 'address', name: 'sender', type: 'address' }], + name: 'SenderNotAllowed', + type: 'error', + }, + { + inputs: [ + { internalType: 'uint256', name: 'capacity', type: 'uint256' }, + { internalType: 'uint256', name: 'requested', type: 'uint256' }, + { + internalType: 'address', + name: 'tokenAddress', + type: 'address', + }, + ], + name: 'TokenMaxCapacityExceeded', + type: 'error', + }, + { + inputs: [ + { + internalType: 'uint256', + name: 'minWaitInSeconds', + type: 'uint256', + }, + { internalType: 'uint256', name: 'available', type: 'uint256' }, + { + internalType: 'address', + name: 'tokenAddress', + type: 'address', + }, + ], + name: 'TokenRateLimitReached', + type: 'error', + }, + { + inputs: [{ internalType: 'address', name: 'caller', type: 'address' }], + name: 'Unauthorized', + type: 'error', + }, + { inputs: [], name: 'ZeroAddressNotAllowed', type: 'error' }, + { + anonymous: false, + inputs: [ + { + indexed: false, + internalType: 'address', + name: 'sender', + type: 'address', + }, + ], + name: 'AllowListAdd', + type: 'event', + }, + { + anonymous: false, + inputs: [ + { + indexed: false, + internalType: 'address', + name: 'sender', + type: 'address', + }, + ], + name: 'AllowListRemove', + type: 'event', + }, + { + anonymous: false, + inputs: [ + { + indexed: true, + internalType: 'address', + name: 'sender', + type: 'address', + }, + { + indexed: false, + internalType: 'uint256', + name: 'amount', + type: 'uint256', + }, + ], + name: 'Burned', + type: 'event', + }, + { + anonymous: false, + inputs: [ + { + indexed: false, + internalType: 'uint64', + name: 'remoteChainSelector', + type: 'uint64', + }, + { + indexed: false, + internalType: 'bytes', + name: 'remoteToken', + type: 'bytes', + }, + { + components: [ + { internalType: 'bool', name: 'isEnabled', type: 'bool' }, + { + internalType: 'uint128', + name: 'capacity', + type: 'uint128', + }, + { internalType: 'uint128', name: 'rate', type: 'uint128' }, + ], + indexed: false, + internalType: 'structRateLimiter.Config', + name: 'outboundRateLimiterConfig', + type: 'tuple', + }, + { + components: [ + { internalType: 'bool', name: 'isEnabled', type: 'bool' }, + { + internalType: 'uint128', + name: 'capacity', + type: 'uint128', + }, + { internalType: 'uint128', name: 'rate', type: 'uint128' }, + ], + indexed: false, + internalType: 'structRateLimiter.Config', + name: 'inboundRateLimiterConfig', + type: 'tuple', + }, + ], + name: 'ChainAdded', + type: 'event', + }, + { + anonymous: false, + inputs: [ + { + indexed: false, + internalType: 'uint64', + name: 'remoteChainSelector', + type: 'uint64', + }, + { + components: [ + { internalType: 'bool', name: 'isEnabled', type: 'bool' }, + { + internalType: 'uint128', + name: 'capacity', + type: 'uint128', + }, + { internalType: 'uint128', name: 'rate', type: 'uint128' }, + ], + indexed: false, + internalType: 'structRateLimiter.Config', + name: 'outboundRateLimiterConfig', + type: 'tuple', + }, + { + components: [ + { internalType: 'bool', name: 'isEnabled', type: 'bool' }, + { + internalType: 'uint128', + name: 'capacity', + type: 'uint128', + }, + { internalType: 'uint128', name: 'rate', type: 'uint128' }, + ], + indexed: false, + internalType: 'structRateLimiter.Config', + name: 'inboundRateLimiterConfig', + type: 'tuple', + }, + ], + name: 'ChainConfigured', + type: 'event', + }, + { + anonymous: false, + inputs: [ + { + indexed: false, + internalType: 'uint64', + name: 'remoteChainSelector', + type: 'uint64', + }, + ], + name: 'ChainRemoved', + type: 'event', + }, + { + anonymous: false, + inputs: [ + { + components: [ + { internalType: 'bool', name: 'isEnabled', type: 'bool' }, + { + internalType: 'uint128', + name: 'capacity', + type: 'uint128', + }, + { internalType: 'uint128', name: 'rate', type: 'uint128' }, + ], + indexed: false, + internalType: 'structRateLimiter.Config', + name: 'config', + type: 'tuple', + }, + ], + name: 'ConfigChanged', + type: 'event', + }, + { + anonymous: false, + inputs: [ + { + indexed: true, + internalType: 'address', + name: 'sender', + type: 'address', + }, + { + indexed: false, + internalType: 'uint256', + name: 'amount', + type: 'uint256', + }, + ], + name: 'Locked', + type: 'event', + }, + { + anonymous: false, + inputs: [ + { + indexed: true, + internalType: 'address', + name: 'sender', + type: 'address', + }, + { + indexed: true, + internalType: 'address', + name: 'recipient', + type: 'address', + }, + { + indexed: false, + internalType: 'uint256', + name: 'amount', + type: 'uint256', + }, + ], + name: 'Minted', + type: 'event', + }, + { + anonymous: false, + inputs: [ + { + indexed: true, + internalType: 'address', + name: 'from', + type: 'address', + }, + { + indexed: true, + internalType: 'address', + name: 'to', + type: 'address', + }, + ], + name: 'OwnershipTransferRequested', + type: 'event', + }, + { + anonymous: false, + inputs: [ + { + indexed: true, + internalType: 'address', + name: 'from', + type: 'address', + }, + { + indexed: true, + internalType: 'address', + name: 'to', + type: 'address', + }, + ], + name: 'OwnershipTransferred', + type: 'event', + }, + { + anonymous: false, + inputs: [ + { + indexed: false, + internalType: 'address', + name: 'rateLimitAdmin', + type: 'address', + }, + ], + name: 'RateLimitAdminSet', + type: 'event', + }, + { + anonymous: false, + inputs: [ + { + indexed: true, + internalType: 'address', + name: 'sender', + type: 'address', + }, + { + indexed: true, + internalType: 'address', + name: 'recipient', + type: 'address', + }, + { + indexed: false, + internalType: 'uint256', + name: 'amount', + type: 'uint256', + }, + ], + name: 'Released', + type: 'event', + }, + { + anonymous: false, + inputs: [ + { + indexed: true, + internalType: 'uint64', + name: 'remoteChainSelector', + type: 'uint64', + }, + { + indexed: false, + internalType: 'bytes', + name: 'remotePoolAddress', + type: 'bytes', + }, + ], + name: 'RemotePoolAdded', + type: 'event', + }, + { + anonymous: false, + inputs: [ + { + indexed: true, + internalType: 'uint64', + name: 'remoteChainSelector', + type: 'uint64', + }, + { + indexed: false, + internalType: 'bytes', + name: 'remotePoolAddress', + type: 'bytes', + }, + ], + name: 'RemotePoolRemoved', + type: 'event', + }, + { + anonymous: false, + inputs: [ + { + indexed: false, + internalType: 'address', + name: 'oldRouter', + type: 'address', + }, + { + indexed: false, + internalType: 'address', + name: 'newRouter', + type: 'address', + }, + ], + name: 'RouterUpdated', + type: 'event', + }, + { + anonymous: false, + inputs: [ + { + indexed: false, + internalType: 'uint256', + name: 'tokens', + type: 'uint256', + }, + ], + name: 'TokensConsumed', + type: 'event', + }, + { + inputs: [], + name: 'acceptOwnership', + outputs: [], + stateMutability: 'nonpayable', + type: 'function', + }, + { + inputs: [ + { + internalType: 'uint64', + name: 'remoteChainSelector', + type: 'uint64', + }, + { + internalType: 'bytes', + name: 'remotePoolAddress', + type: 'bytes', + }, + ], + name: 'addRemotePool', + outputs: [], + stateMutability: 'nonpayable', + type: 'function', + }, + { + inputs: [ + { internalType: 'address[]', name: 'removes', type: 'address[]' }, + { internalType: 'address[]', name: 'adds', type: 'address[]' }, + ], + name: 'applyAllowListUpdates', + outputs: [], + stateMutability: 'nonpayable', + type: 'function', + }, + { + inputs: [ + { + internalType: 'uint64[]', + name: 'remoteChainSelectorsToRemove', + type: 'uint64[]', + }, + { + components: [ + { + internalType: 'uint64', + name: 'remoteChainSelector', + type: 'uint64', + }, + { + internalType: 'bytes[]', + name: 'remotePoolAddresses', + type: 'bytes[]', + }, + { + internalType: 'bytes', + name: 'remoteTokenAddress', + type: 'bytes', + }, + { + components: [ + { internalType: 'bool', name: 'isEnabled', type: 'bool' }, + { + internalType: 'uint128', + name: 'capacity', + type: 'uint128', + }, + { + internalType: 'uint128', + name: 'rate', + type: 'uint128', + }, + ], + internalType: 'structRateLimiter.Config', + name: 'outboundRateLimiterConfig', + type: 'tuple', + }, + { + components: [ + { internalType: 'bool', name: 'isEnabled', type: 'bool' }, + { + internalType: 'uint128', + name: 'capacity', + type: 'uint128', + }, + { + internalType: 'uint128', + name: 'rate', + type: 'uint128', + }, + ], + internalType: 'structRateLimiter.Config', + name: 'inboundRateLimiterConfig', + type: 'tuple', + }, + ], + internalType: 'structTokenPool.ChainUpdate[]', + name: 'chainsToAdd', + type: 'tuple[]', + }, + ], + name: 'applyChainUpdates', + outputs: [], + stateMutability: 'nonpayable', + type: 'function', + }, + { + inputs: [], + name: 'getAllowList', + outputs: [{ internalType: 'address[]', name: '', type: 'address[]' }], + stateMutability: 'view', + type: 'function', + }, + { + inputs: [], + name: 'getAllowListEnabled', + outputs: [{ internalType: 'bool', name: '', type: 'bool' }], + stateMutability: 'view', + type: 'function', + }, + { + inputs: [ + { + internalType: 'uint64', + name: 'remoteChainSelector', + type: 'uint64', + }, + ], + name: 'getCurrentInboundRateLimiterState', + outputs: [ + { + components: [ + { internalType: 'uint128', name: 'tokens', type: 'uint128' }, + { + internalType: 'uint32', + name: 'lastUpdated', + type: 'uint32', + }, + { internalType: 'bool', name: 'isEnabled', type: 'bool' }, + { + internalType: 'uint128', + name: 'capacity', + type: 'uint128', + }, + { internalType: 'uint128', name: 'rate', type: 'uint128' }, + ], + internalType: 'structRateLimiter.TokenBucket', + name: '', + type: 'tuple', + }, + ], + stateMutability: 'view', + type: 'function', + }, + { + inputs: [ + { + internalType: 'uint64', + name: 'remoteChainSelector', + type: 'uint64', + }, + ], + name: 'getCurrentOutboundRateLimiterState', + outputs: [ + { + components: [ + { internalType: 'uint128', name: 'tokens', type: 'uint128' }, + { + internalType: 'uint32', + name: 'lastUpdated', + type: 'uint32', + }, + { internalType: 'bool', name: 'isEnabled', type: 'bool' }, + { + internalType: 'uint128', + name: 'capacity', + type: 'uint128', + }, + { internalType: 'uint128', name: 'rate', type: 'uint128' }, + ], + internalType: 'structRateLimiter.TokenBucket', + name: '', + type: 'tuple', + }, + ], + stateMutability: 'view', + type: 'function', + }, + { + inputs: [], + name: 'getRateLimitAdmin', + outputs: [{ internalType: 'address', name: '', type: 'address' }], + stateMutability: 'view', + type: 'function', + }, + { + inputs: [ + { + internalType: 'uint64', + name: 'remoteChainSelector', + type: 'uint64', + }, + ], + name: 'getRemotePools', + outputs: [{ internalType: 'bytes[]', name: '', type: 'bytes[]' }], + stateMutability: 'view', + type: 'function', + }, + { + inputs: [ + { + internalType: 'uint64', + name: 'remoteChainSelector', + type: 'uint64', + }, + ], + name: 'getRemoteToken', + outputs: [{ internalType: 'bytes', name: '', type: 'bytes' }], + stateMutability: 'view', + type: 'function', + }, + { + inputs: [], + name: 'getRmnProxy', + outputs: [{ internalType: 'address', name: 'rmnProxy', type: 'address' }], + stateMutability: 'view', + type: 'function', + }, + { + inputs: [], + name: 'getRouter', + outputs: [{ internalType: 'address', name: 'router', type: 'address' }], + stateMutability: 'view', + type: 'function', + }, + { + inputs: [], + name: 'getSupportedChains', + outputs: [{ internalType: 'uint64[]', name: '', type: 'uint64[]' }], + stateMutability: 'view', + type: 'function', + }, + { + inputs: [], + name: 'getToken', + outputs: [ + { + internalType: 'contractIERC20', + name: 'token', + type: 'address', + }, + ], + stateMutability: 'view', + type: 'function', + }, + { + inputs: [], + name: 'getTokenDecimals', + outputs: [{ internalType: 'uint8', name: 'decimals', type: 'uint8' }], + stateMutability: 'view', + type: 'function', + }, + { + inputs: [ + { + internalType: 'uint64', + name: 'remoteChainSelector', + type: 'uint64', + }, + { + internalType: 'bytes', + name: 'remotePoolAddress', + type: 'bytes', + }, + ], + name: 'isRemotePool', + outputs: [{ internalType: 'bool', name: '', type: 'bool' }], + stateMutability: 'view', + type: 'function', + }, + { + inputs: [ + { + internalType: 'uint64', + name: 'remoteChainSelector', + type: 'uint64', + }, + ], + name: 'isSupportedChain', + outputs: [{ internalType: 'bool', name: '', type: 'bool' }], + stateMutability: 'view', + type: 'function', + }, + { + inputs: [{ internalType: 'address', name: 'token', type: 'address' }], + name: 'isSupportedToken', + outputs: [{ internalType: 'bool', name: '', type: 'bool' }], + stateMutability: 'view', + type: 'function', + }, + { + inputs: [ + { + components: [ + { internalType: 'bytes', name: 'receiver', type: 'bytes' }, + { + internalType: 'uint64', + name: 'remoteChainSelector', + type: 'uint64', + }, + { + internalType: 'address', + name: 'originalSender', + type: 'address', + }, + { internalType: 'uint256', name: 'amount', type: 'uint256' }, + { + internalType: 'address', + name: 'localToken', + type: 'address', + }, + ], + internalType: 'structPool.LockOrBurnInV1', + name: 'lockOrBurnIn', + type: 'tuple', + }, + ], + name: 'lockOrBurn', + outputs: [ + { + components: [ + { + internalType: 'bytes', + name: 'destTokenAddress', + type: 'bytes', + }, + { + internalType: 'bytes', + name: 'destPoolData', + type: 'bytes', + }, + ], + internalType: 'structPool.LockOrBurnOutV1', + name: '', + type: 'tuple', + }, + ], + stateMutability: 'nonpayable', + type: 'function', + }, + { + inputs: [], + name: 'owner', + outputs: [{ internalType: 'address', name: '', type: 'address' }], + stateMutability: 'view', + type: 'function', + }, + { + inputs: [ + { + components: [ + { + internalType: 'bytes', + name: 'originalSender', + type: 'bytes', + }, + { + internalType: 'uint64', + name: 'remoteChainSelector', + type: 'uint64', + }, + { + internalType: 'address', + name: 'receiver', + type: 'address', + }, + { internalType: 'uint256', name: 'amount', type: 'uint256' }, + { + internalType: 'address', + name: 'localToken', + type: 'address', + }, + { + internalType: 'bytes', + name: 'sourcePoolAddress', + type: 'bytes', + }, + { + internalType: 'bytes', + name: 'sourcePoolData', + type: 'bytes', + }, + { + internalType: 'bytes', + name: 'offchainTokenData', + type: 'bytes', + }, + ], + internalType: 'structPool.ReleaseOrMintInV1', + name: 'releaseOrMintIn', + type: 'tuple', + }, + ], + name: 'releaseOrMint', + outputs: [ + { + components: [ + { + internalType: 'uint256', + name: 'destinationAmount', + type: 'uint256', + }, + ], + internalType: 'structPool.ReleaseOrMintOutV1', + name: '', + type: 'tuple', + }, + ], + stateMutability: 'nonpayable', + type: 'function', + }, + { + inputs: [ + { + internalType: 'uint64', + name: 'remoteChainSelector', + type: 'uint64', + }, + { + internalType: 'bytes', + name: 'remotePoolAddress', + type: 'bytes', + }, + ], + name: 'removeRemotePool', + outputs: [], + stateMutability: 'nonpayable', + type: 'function', + }, + { + inputs: [ + { + internalType: 'uint64', + name: 'remoteChainSelector', + type: 'uint64', + }, + { + components: [ + { internalType: 'bool', name: 'isEnabled', type: 'bool' }, + { + internalType: 'uint128', + name: 'capacity', + type: 'uint128', + }, + { internalType: 'uint128', name: 'rate', type: 'uint128' }, + ], + internalType: 'structRateLimiter.Config', + name: 'outboundConfig', + type: 'tuple', + }, + { + components: [ + { internalType: 'bool', name: 'isEnabled', type: 'bool' }, + { + internalType: 'uint128', + name: 'capacity', + type: 'uint128', + }, + { internalType: 'uint128', name: 'rate', type: 'uint128' }, + ], + internalType: 'structRateLimiter.Config', + name: 'inboundConfig', + type: 'tuple', + }, + ], + name: 'setChainRateLimiterConfig', + outputs: [], + stateMutability: 'nonpayable', + type: 'function', + }, + { + inputs: [ + { + internalType: 'uint64[]', + name: 'remoteChainSelectors', + type: 'uint64[]', + }, + { + components: [ + { internalType: 'bool', name: 'isEnabled', type: 'bool' }, + { + internalType: 'uint128', + name: 'capacity', + type: 'uint128', + }, + { internalType: 'uint128', name: 'rate', type: 'uint128' }, + ], + internalType: 'structRateLimiter.Config[]', + name: 'outboundConfigs', + type: 'tuple[]', + }, + { + components: [ + { internalType: 'bool', name: 'isEnabled', type: 'bool' }, + { + internalType: 'uint128', + name: 'capacity', + type: 'uint128', + }, + { internalType: 'uint128', name: 'rate', type: 'uint128' }, + ], + internalType: 'structRateLimiter.Config[]', + name: 'inboundConfigs', + type: 'tuple[]', + }, + ], + name: 'setChainRateLimiterConfigs', + outputs: [], + stateMutability: 'nonpayable', + type: 'function', + }, + { + inputs: [ + { + internalType: 'address', + name: 'rateLimitAdmin', + type: 'address', + }, + ], + name: 'setRateLimitAdmin', + outputs: [], + stateMutability: 'nonpayable', + type: 'function', + }, + { + inputs: [{ internalType: 'address', name: 'newRouter', type: 'address' }], + name: 'setRouter', + outputs: [], + stateMutability: 'nonpayable', + type: 'function', + }, + { + inputs: [{ internalType: 'bytes4', name: 'interfaceId', type: 'bytes4' }], + name: 'supportsInterface', + outputs: [{ internalType: 'bool', name: '', type: 'bool' }], + stateMutability: 'pure', + type: 'function', + }, + { + inputs: [{ internalType: 'address', name: 'to', type: 'address' }], + name: 'transferOwnership', + outputs: [], + stateMutability: 'nonpayable', + type: 'function', + }, + { + inputs: [], + name: 'typeAndVersion', + outputs: [{ internalType: 'string', name: '', type: 'string' }], + stateMutability: 'view', + type: 'function', + }, + // generate:end +] as const diff --git a/src/abi/LockReleaseTokenPool_1_5_1.ts b/src/abi/LockReleaseTokenPool_1_5_1.ts new file mode 100644 index 0000000..f333100 --- /dev/null +++ b/src/abi/LockReleaseTokenPool_1_5_1.ts @@ -0,0 +1,1277 @@ +export default [ + // generate: + // fetch('https://github.com/smartcontractkit/ccip/raw/release/contracts-ccip-1.5.1/core/gethwrappers/ccip/generated/lock_release_token_pool/lock_release_token_pool.go') + // .then((res) => res.text()) + // .then((body) => body.match(/^\s*ABI: "(.*?)",$/m)?.[1]) + // .then((abi) => JSON.parse(abi.replace(/\\"/g, '"'))) + // .then((obj) => require('util').inspect(obj, {depth:99}).split('\n').slice(1, -1)) + { + inputs: [ + { + internalType: 'contractIERC20', + name: 'token', + type: 'address', + }, + { + internalType: 'uint8', + name: 'localTokenDecimals', + type: 'uint8', + }, + { + internalType: 'address[]', + name: 'allowlist', + type: 'address[]', + }, + { internalType: 'address', name: 'rmnProxy', type: 'address' }, + { internalType: 'bool', name: 'acceptLiquidity', type: 'bool' }, + { internalType: 'address', name: 'router', type: 'address' }, + ], + stateMutability: 'nonpayable', + type: 'constructor', + }, + { + inputs: [ + { internalType: 'uint256', name: 'capacity', type: 'uint256' }, + { internalType: 'uint256', name: 'requested', type: 'uint256' }, + ], + name: 'AggregateValueMaxCapacityExceeded', + type: 'error', + }, + { + inputs: [ + { + internalType: 'uint256', + name: 'minWaitInSeconds', + type: 'uint256', + }, + { internalType: 'uint256', name: 'available', type: 'uint256' }, + ], + name: 'AggregateValueRateLimitReached', + type: 'error', + }, + { inputs: [], name: 'AllowListNotEnabled', type: 'error' }, + { inputs: [], name: 'BucketOverfilled', type: 'error' }, + { + inputs: [{ internalType: 'address', name: 'caller', type: 'address' }], + name: 'CallerIsNotARampOnRouter', + type: 'error', + }, + { inputs: [], name: 'CannotTransferToSelf', type: 'error' }, + { + inputs: [{ internalType: 'uint64', name: 'chainSelector', type: 'uint64' }], + name: 'ChainAlreadyExists', + type: 'error', + }, + { + inputs: [ + { + internalType: 'uint64', + name: 'remoteChainSelector', + type: 'uint64', + }, + ], + name: 'ChainNotAllowed', + type: 'error', + }, + { inputs: [], name: 'CursedByRMN', type: 'error' }, + { + inputs: [ + { + components: [ + { internalType: 'bool', name: 'isEnabled', type: 'bool' }, + { + internalType: 'uint128', + name: 'capacity', + type: 'uint128', + }, + { internalType: 'uint128', name: 'rate', type: 'uint128' }, + ], + internalType: 'structRateLimiter.Config', + name: 'config', + type: 'tuple', + }, + ], + name: 'DisabledNonZeroRateLimit', + type: 'error', + }, + { inputs: [], name: 'InsufficientLiquidity', type: 'error' }, + { + inputs: [ + { internalType: 'uint8', name: 'expected', type: 'uint8' }, + { internalType: 'uint8', name: 'actual', type: 'uint8' }, + ], + name: 'InvalidDecimalArgs', + type: 'error', + }, + { + inputs: [ + { + components: [ + { internalType: 'bool', name: 'isEnabled', type: 'bool' }, + { + internalType: 'uint128', + name: 'capacity', + type: 'uint128', + }, + { internalType: 'uint128', name: 'rate', type: 'uint128' }, + ], + internalType: 'structRateLimiter.Config', + name: 'rateLimiterConfig', + type: 'tuple', + }, + ], + name: 'InvalidRateLimitRate', + type: 'error', + }, + { + inputs: [{ internalType: 'bytes', name: 'sourcePoolData', type: 'bytes' }], + name: 'InvalidRemoteChainDecimals', + type: 'error', + }, + { + inputs: [ + { + internalType: 'uint64', + name: 'remoteChainSelector', + type: 'uint64', + }, + { + internalType: 'bytes', + name: 'remotePoolAddress', + type: 'bytes', + }, + ], + name: 'InvalidRemotePoolForChain', + type: 'error', + }, + { + inputs: [ + { + internalType: 'bytes', + name: 'sourcePoolAddress', + type: 'bytes', + }, + ], + name: 'InvalidSourcePoolAddress', + type: 'error', + }, + { + inputs: [{ internalType: 'address', name: 'token', type: 'address' }], + name: 'InvalidToken', + type: 'error', + }, + { inputs: [], name: 'LiquidityNotAccepted', type: 'error' }, + { inputs: [], name: 'MismatchedArrayLengths', type: 'error' }, + { inputs: [], name: 'MustBeProposedOwner', type: 'error' }, + { + inputs: [ + { + internalType: 'uint64', + name: 'remoteChainSelector', + type: 'uint64', + }, + ], + name: 'NonExistentChain', + type: 'error', + }, + { inputs: [], name: 'OnlyCallableByOwner', type: 'error' }, + { + inputs: [ + { internalType: 'uint8', name: 'remoteDecimals', type: 'uint8' }, + { internalType: 'uint8', name: 'localDecimals', type: 'uint8' }, + { + internalType: 'uint256', + name: 'remoteAmount', + type: 'uint256', + }, + ], + name: 'OverflowDetected', + type: 'error', + }, + { inputs: [], name: 'OwnerCannotBeZero', type: 'error' }, + { + inputs: [ + { + internalType: 'uint64', + name: 'remoteChainSelector', + type: 'uint64', + }, + { + internalType: 'bytes', + name: 'remotePoolAddress', + type: 'bytes', + }, + ], + name: 'PoolAlreadyAdded', + type: 'error', + }, + { inputs: [], name: 'RateLimitMustBeDisabled', type: 'error' }, + { + inputs: [{ internalType: 'address', name: 'sender', type: 'address' }], + name: 'SenderNotAllowed', + type: 'error', + }, + { + inputs: [ + { internalType: 'uint256', name: 'capacity', type: 'uint256' }, + { internalType: 'uint256', name: 'requested', type: 'uint256' }, + { + internalType: 'address', + name: 'tokenAddress', + type: 'address', + }, + ], + name: 'TokenMaxCapacityExceeded', + type: 'error', + }, + { + inputs: [ + { + internalType: 'uint256', + name: 'minWaitInSeconds', + type: 'uint256', + }, + { internalType: 'uint256', name: 'available', type: 'uint256' }, + { + internalType: 'address', + name: 'tokenAddress', + type: 'address', + }, + ], + name: 'TokenRateLimitReached', + type: 'error', + }, + { + inputs: [{ internalType: 'address', name: 'caller', type: 'address' }], + name: 'Unauthorized', + type: 'error', + }, + { inputs: [], name: 'ZeroAddressNotAllowed', type: 'error' }, + { + anonymous: false, + inputs: [ + { + indexed: false, + internalType: 'address', + name: 'sender', + type: 'address', + }, + ], + name: 'AllowListAdd', + type: 'event', + }, + { + anonymous: false, + inputs: [ + { + indexed: false, + internalType: 'address', + name: 'sender', + type: 'address', + }, + ], + name: 'AllowListRemove', + type: 'event', + }, + { + anonymous: false, + inputs: [ + { + indexed: true, + internalType: 'address', + name: 'sender', + type: 'address', + }, + { + indexed: false, + internalType: 'uint256', + name: 'amount', + type: 'uint256', + }, + ], + name: 'Burned', + type: 'event', + }, + { + anonymous: false, + inputs: [ + { + indexed: false, + internalType: 'uint64', + name: 'remoteChainSelector', + type: 'uint64', + }, + { + indexed: false, + internalType: 'bytes', + name: 'remoteToken', + type: 'bytes', + }, + { + components: [ + { internalType: 'bool', name: 'isEnabled', type: 'bool' }, + { + internalType: 'uint128', + name: 'capacity', + type: 'uint128', + }, + { internalType: 'uint128', name: 'rate', type: 'uint128' }, + ], + indexed: false, + internalType: 'structRateLimiter.Config', + name: 'outboundRateLimiterConfig', + type: 'tuple', + }, + { + components: [ + { internalType: 'bool', name: 'isEnabled', type: 'bool' }, + { + internalType: 'uint128', + name: 'capacity', + type: 'uint128', + }, + { internalType: 'uint128', name: 'rate', type: 'uint128' }, + ], + indexed: false, + internalType: 'structRateLimiter.Config', + name: 'inboundRateLimiterConfig', + type: 'tuple', + }, + ], + name: 'ChainAdded', + type: 'event', + }, + { + anonymous: false, + inputs: [ + { + indexed: false, + internalType: 'uint64', + name: 'remoteChainSelector', + type: 'uint64', + }, + { + components: [ + { internalType: 'bool', name: 'isEnabled', type: 'bool' }, + { + internalType: 'uint128', + name: 'capacity', + type: 'uint128', + }, + { internalType: 'uint128', name: 'rate', type: 'uint128' }, + ], + indexed: false, + internalType: 'structRateLimiter.Config', + name: 'outboundRateLimiterConfig', + type: 'tuple', + }, + { + components: [ + { internalType: 'bool', name: 'isEnabled', type: 'bool' }, + { + internalType: 'uint128', + name: 'capacity', + type: 'uint128', + }, + { internalType: 'uint128', name: 'rate', type: 'uint128' }, + ], + indexed: false, + internalType: 'structRateLimiter.Config', + name: 'inboundRateLimiterConfig', + type: 'tuple', + }, + ], + name: 'ChainConfigured', + type: 'event', + }, + { + anonymous: false, + inputs: [ + { + indexed: false, + internalType: 'uint64', + name: 'remoteChainSelector', + type: 'uint64', + }, + ], + name: 'ChainRemoved', + type: 'event', + }, + { + anonymous: false, + inputs: [ + { + components: [ + { internalType: 'bool', name: 'isEnabled', type: 'bool' }, + { + internalType: 'uint128', + name: 'capacity', + type: 'uint128', + }, + { internalType: 'uint128', name: 'rate', type: 'uint128' }, + ], + indexed: false, + internalType: 'structRateLimiter.Config', + name: 'config', + type: 'tuple', + }, + ], + name: 'ConfigChanged', + type: 'event', + }, + { + anonymous: false, + inputs: [ + { + indexed: true, + internalType: 'address', + name: 'provider', + type: 'address', + }, + { + indexed: true, + internalType: 'uint256', + name: 'amount', + type: 'uint256', + }, + ], + name: 'LiquidityAdded', + type: 'event', + }, + { + anonymous: false, + inputs: [ + { + indexed: true, + internalType: 'address', + name: 'provider', + type: 'address', + }, + { + indexed: true, + internalType: 'uint256', + name: 'amount', + type: 'uint256', + }, + ], + name: 'LiquidityRemoved', + type: 'event', + }, + { + anonymous: false, + inputs: [ + { + indexed: true, + internalType: 'address', + name: 'from', + type: 'address', + }, + { + indexed: false, + internalType: 'uint256', + name: 'amount', + type: 'uint256', + }, + ], + name: 'LiquidityTransferred', + type: 'event', + }, + { + anonymous: false, + inputs: [ + { + indexed: true, + internalType: 'address', + name: 'sender', + type: 'address', + }, + { + indexed: false, + internalType: 'uint256', + name: 'amount', + type: 'uint256', + }, + ], + name: 'Locked', + type: 'event', + }, + { + anonymous: false, + inputs: [ + { + indexed: true, + internalType: 'address', + name: 'sender', + type: 'address', + }, + { + indexed: true, + internalType: 'address', + name: 'recipient', + type: 'address', + }, + { + indexed: false, + internalType: 'uint256', + name: 'amount', + type: 'uint256', + }, + ], + name: 'Minted', + type: 'event', + }, + { + anonymous: false, + inputs: [ + { + indexed: true, + internalType: 'address', + name: 'from', + type: 'address', + }, + { + indexed: true, + internalType: 'address', + name: 'to', + type: 'address', + }, + ], + name: 'OwnershipTransferRequested', + type: 'event', + }, + { + anonymous: false, + inputs: [ + { + indexed: true, + internalType: 'address', + name: 'from', + type: 'address', + }, + { + indexed: true, + internalType: 'address', + name: 'to', + type: 'address', + }, + ], + name: 'OwnershipTransferred', + type: 'event', + }, + { + anonymous: false, + inputs: [ + { + indexed: false, + internalType: 'address', + name: 'rateLimitAdmin', + type: 'address', + }, + ], + name: 'RateLimitAdminSet', + type: 'event', + }, + { + anonymous: false, + inputs: [ + { + indexed: true, + internalType: 'address', + name: 'sender', + type: 'address', + }, + { + indexed: true, + internalType: 'address', + name: 'recipient', + type: 'address', + }, + { + indexed: false, + internalType: 'uint256', + name: 'amount', + type: 'uint256', + }, + ], + name: 'Released', + type: 'event', + }, + { + anonymous: false, + inputs: [ + { + indexed: true, + internalType: 'uint64', + name: 'remoteChainSelector', + type: 'uint64', + }, + { + indexed: false, + internalType: 'bytes', + name: 'remotePoolAddress', + type: 'bytes', + }, + ], + name: 'RemotePoolAdded', + type: 'event', + }, + { + anonymous: false, + inputs: [ + { + indexed: true, + internalType: 'uint64', + name: 'remoteChainSelector', + type: 'uint64', + }, + { + indexed: false, + internalType: 'bytes', + name: 'remotePoolAddress', + type: 'bytes', + }, + ], + name: 'RemotePoolRemoved', + type: 'event', + }, + { + anonymous: false, + inputs: [ + { + indexed: false, + internalType: 'address', + name: 'oldRouter', + type: 'address', + }, + { + indexed: false, + internalType: 'address', + name: 'newRouter', + type: 'address', + }, + ], + name: 'RouterUpdated', + type: 'event', + }, + { + anonymous: false, + inputs: [ + { + indexed: false, + internalType: 'uint256', + name: 'tokens', + type: 'uint256', + }, + ], + name: 'TokensConsumed', + type: 'event', + }, + { + inputs: [], + name: 'acceptOwnership', + outputs: [], + stateMutability: 'nonpayable', + type: 'function', + }, + { + inputs: [ + { + internalType: 'uint64', + name: 'remoteChainSelector', + type: 'uint64', + }, + { + internalType: 'bytes', + name: 'remotePoolAddress', + type: 'bytes', + }, + ], + name: 'addRemotePool', + outputs: [], + stateMutability: 'nonpayable', + type: 'function', + }, + { + inputs: [ + { internalType: 'address[]', name: 'removes', type: 'address[]' }, + { internalType: 'address[]', name: 'adds', type: 'address[]' }, + ], + name: 'applyAllowListUpdates', + outputs: [], + stateMutability: 'nonpayable', + type: 'function', + }, + { + inputs: [ + { + internalType: 'uint64[]', + name: 'remoteChainSelectorsToRemove', + type: 'uint64[]', + }, + { + components: [ + { + internalType: 'uint64', + name: 'remoteChainSelector', + type: 'uint64', + }, + { + internalType: 'bytes[]', + name: 'remotePoolAddresses', + type: 'bytes[]', + }, + { + internalType: 'bytes', + name: 'remoteTokenAddress', + type: 'bytes', + }, + { + components: [ + { internalType: 'bool', name: 'isEnabled', type: 'bool' }, + { + internalType: 'uint128', + name: 'capacity', + type: 'uint128', + }, + { + internalType: 'uint128', + name: 'rate', + type: 'uint128', + }, + ], + internalType: 'structRateLimiter.Config', + name: 'outboundRateLimiterConfig', + type: 'tuple', + }, + { + components: [ + { internalType: 'bool', name: 'isEnabled', type: 'bool' }, + { + internalType: 'uint128', + name: 'capacity', + type: 'uint128', + }, + { + internalType: 'uint128', + name: 'rate', + type: 'uint128', + }, + ], + internalType: 'structRateLimiter.Config', + name: 'inboundRateLimiterConfig', + type: 'tuple', + }, + ], + internalType: 'structTokenPool.ChainUpdate[]', + name: 'chainsToAdd', + type: 'tuple[]', + }, + ], + name: 'applyChainUpdates', + outputs: [], + stateMutability: 'nonpayable', + type: 'function', + }, + { + inputs: [], + name: 'canAcceptLiquidity', + outputs: [{ internalType: 'bool', name: '', type: 'bool' }], + stateMutability: 'view', + type: 'function', + }, + { + inputs: [], + name: 'getAllowList', + outputs: [{ internalType: 'address[]', name: '', type: 'address[]' }], + stateMutability: 'view', + type: 'function', + }, + { + inputs: [], + name: 'getAllowListEnabled', + outputs: [{ internalType: 'bool', name: '', type: 'bool' }], + stateMutability: 'view', + type: 'function', + }, + { + inputs: [ + { + internalType: 'uint64', + name: 'remoteChainSelector', + type: 'uint64', + }, + ], + name: 'getCurrentInboundRateLimiterState', + outputs: [ + { + components: [ + { internalType: 'uint128', name: 'tokens', type: 'uint128' }, + { + internalType: 'uint32', + name: 'lastUpdated', + type: 'uint32', + }, + { internalType: 'bool', name: 'isEnabled', type: 'bool' }, + { + internalType: 'uint128', + name: 'capacity', + type: 'uint128', + }, + { internalType: 'uint128', name: 'rate', type: 'uint128' }, + ], + internalType: 'structRateLimiter.TokenBucket', + name: '', + type: 'tuple', + }, + ], + stateMutability: 'view', + type: 'function', + }, + { + inputs: [ + { + internalType: 'uint64', + name: 'remoteChainSelector', + type: 'uint64', + }, + ], + name: 'getCurrentOutboundRateLimiterState', + outputs: [ + { + components: [ + { internalType: 'uint128', name: 'tokens', type: 'uint128' }, + { + internalType: 'uint32', + name: 'lastUpdated', + type: 'uint32', + }, + { internalType: 'bool', name: 'isEnabled', type: 'bool' }, + { + internalType: 'uint128', + name: 'capacity', + type: 'uint128', + }, + { internalType: 'uint128', name: 'rate', type: 'uint128' }, + ], + internalType: 'structRateLimiter.TokenBucket', + name: '', + type: 'tuple', + }, + ], + stateMutability: 'view', + type: 'function', + }, + { + inputs: [], + name: 'getRateLimitAdmin', + outputs: [{ internalType: 'address', name: '', type: 'address' }], + stateMutability: 'view', + type: 'function', + }, + { + inputs: [], + name: 'getRebalancer', + outputs: [{ internalType: 'address', name: '', type: 'address' }], + stateMutability: 'view', + type: 'function', + }, + { + inputs: [ + { + internalType: 'uint64', + name: 'remoteChainSelector', + type: 'uint64', + }, + ], + name: 'getRemotePools', + outputs: [{ internalType: 'bytes[]', name: '', type: 'bytes[]' }], + stateMutability: 'view', + type: 'function', + }, + { + inputs: [ + { + internalType: 'uint64', + name: 'remoteChainSelector', + type: 'uint64', + }, + ], + name: 'getRemoteToken', + outputs: [{ internalType: 'bytes', name: '', type: 'bytes' }], + stateMutability: 'view', + type: 'function', + }, + { + inputs: [], + name: 'getRmnProxy', + outputs: [{ internalType: 'address', name: 'rmnProxy', type: 'address' }], + stateMutability: 'view', + type: 'function', + }, + { + inputs: [], + name: 'getRouter', + outputs: [{ internalType: 'address', name: 'router', type: 'address' }], + stateMutability: 'view', + type: 'function', + }, + { + inputs: [], + name: 'getSupportedChains', + outputs: [{ internalType: 'uint64[]', name: '', type: 'uint64[]' }], + stateMutability: 'view', + type: 'function', + }, + { + inputs: [], + name: 'getToken', + outputs: [ + { + internalType: 'contractIERC20', + name: 'token', + type: 'address', + }, + ], + stateMutability: 'view', + type: 'function', + }, + { + inputs: [], + name: 'getTokenDecimals', + outputs: [{ internalType: 'uint8', name: 'decimals', type: 'uint8' }], + stateMutability: 'view', + type: 'function', + }, + { + inputs: [ + { + internalType: 'uint64', + name: 'remoteChainSelector', + type: 'uint64', + }, + { + internalType: 'bytes', + name: 'remotePoolAddress', + type: 'bytes', + }, + ], + name: 'isRemotePool', + outputs: [{ internalType: 'bool', name: '', type: 'bool' }], + stateMutability: 'view', + type: 'function', + }, + { + inputs: [ + { + internalType: 'uint64', + name: 'remoteChainSelector', + type: 'uint64', + }, + ], + name: 'isSupportedChain', + outputs: [{ internalType: 'bool', name: '', type: 'bool' }], + stateMutability: 'view', + type: 'function', + }, + { + inputs: [{ internalType: 'address', name: 'token', type: 'address' }], + name: 'isSupportedToken', + outputs: [{ internalType: 'bool', name: '', type: 'bool' }], + stateMutability: 'view', + type: 'function', + }, + { + inputs: [ + { + components: [ + { internalType: 'bytes', name: 'receiver', type: 'bytes' }, + { + internalType: 'uint64', + name: 'remoteChainSelector', + type: 'uint64', + }, + { + internalType: 'address', + name: 'originalSender', + type: 'address', + }, + { internalType: 'uint256', name: 'amount', type: 'uint256' }, + { + internalType: 'address', + name: 'localToken', + type: 'address', + }, + ], + internalType: 'structPool.LockOrBurnInV1', + name: 'lockOrBurnIn', + type: 'tuple', + }, + ], + name: 'lockOrBurn', + outputs: [ + { + components: [ + { + internalType: 'bytes', + name: 'destTokenAddress', + type: 'bytes', + }, + { + internalType: 'bytes', + name: 'destPoolData', + type: 'bytes', + }, + ], + internalType: 'structPool.LockOrBurnOutV1', + name: '', + type: 'tuple', + }, + ], + stateMutability: 'nonpayable', + type: 'function', + }, + { + inputs: [], + name: 'owner', + outputs: [{ internalType: 'address', name: '', type: 'address' }], + stateMutability: 'view', + type: 'function', + }, + { + inputs: [{ internalType: 'uint256', name: 'amount', type: 'uint256' }], + name: 'provideLiquidity', + outputs: [], + stateMutability: 'nonpayable', + type: 'function', + }, + { + inputs: [ + { + components: [ + { + internalType: 'bytes', + name: 'originalSender', + type: 'bytes', + }, + { + internalType: 'uint64', + name: 'remoteChainSelector', + type: 'uint64', + }, + { + internalType: 'address', + name: 'receiver', + type: 'address', + }, + { internalType: 'uint256', name: 'amount', type: 'uint256' }, + { + internalType: 'address', + name: 'localToken', + type: 'address', + }, + { + internalType: 'bytes', + name: 'sourcePoolAddress', + type: 'bytes', + }, + { + internalType: 'bytes', + name: 'sourcePoolData', + type: 'bytes', + }, + { + internalType: 'bytes', + name: 'offchainTokenData', + type: 'bytes', + }, + ], + internalType: 'structPool.ReleaseOrMintInV1', + name: 'releaseOrMintIn', + type: 'tuple', + }, + ], + name: 'releaseOrMint', + outputs: [ + { + components: [ + { + internalType: 'uint256', + name: 'destinationAmount', + type: 'uint256', + }, + ], + internalType: 'structPool.ReleaseOrMintOutV1', + name: '', + type: 'tuple', + }, + ], + stateMutability: 'nonpayable', + type: 'function', + }, + { + inputs: [ + { + internalType: 'uint64', + name: 'remoteChainSelector', + type: 'uint64', + }, + { + internalType: 'bytes', + name: 'remotePoolAddress', + type: 'bytes', + }, + ], + name: 'removeRemotePool', + outputs: [], + stateMutability: 'nonpayable', + type: 'function', + }, + { + inputs: [ + { + internalType: 'uint64', + name: 'remoteChainSelector', + type: 'uint64', + }, + { + components: [ + { internalType: 'bool', name: 'isEnabled', type: 'bool' }, + { + internalType: 'uint128', + name: 'capacity', + type: 'uint128', + }, + { internalType: 'uint128', name: 'rate', type: 'uint128' }, + ], + internalType: 'structRateLimiter.Config', + name: 'outboundConfig', + type: 'tuple', + }, + { + components: [ + { internalType: 'bool', name: 'isEnabled', type: 'bool' }, + { + internalType: 'uint128', + name: 'capacity', + type: 'uint128', + }, + { internalType: 'uint128', name: 'rate', type: 'uint128' }, + ], + internalType: 'structRateLimiter.Config', + name: 'inboundConfig', + type: 'tuple', + }, + ], + name: 'setChainRateLimiterConfig', + outputs: [], + stateMutability: 'nonpayable', + type: 'function', + }, + { + inputs: [ + { + internalType: 'uint64[]', + name: 'remoteChainSelectors', + type: 'uint64[]', + }, + { + components: [ + { internalType: 'bool', name: 'isEnabled', type: 'bool' }, + { + internalType: 'uint128', + name: 'capacity', + type: 'uint128', + }, + { internalType: 'uint128', name: 'rate', type: 'uint128' }, + ], + internalType: 'structRateLimiter.Config[]', + name: 'outboundConfigs', + type: 'tuple[]', + }, + { + components: [ + { internalType: 'bool', name: 'isEnabled', type: 'bool' }, + { + internalType: 'uint128', + name: 'capacity', + type: 'uint128', + }, + { internalType: 'uint128', name: 'rate', type: 'uint128' }, + ], + internalType: 'structRateLimiter.Config[]', + name: 'inboundConfigs', + type: 'tuple[]', + }, + ], + name: 'setChainRateLimiterConfigs', + outputs: [], + stateMutability: 'nonpayable', + type: 'function', + }, + { + inputs: [ + { + internalType: 'address', + name: 'rateLimitAdmin', + type: 'address', + }, + ], + name: 'setRateLimitAdmin', + outputs: [], + stateMutability: 'nonpayable', + type: 'function', + }, + { + inputs: [{ internalType: 'address', name: 'rebalancer', type: 'address' }], + name: 'setRebalancer', + outputs: [], + stateMutability: 'nonpayable', + type: 'function', + }, + { + inputs: [{ internalType: 'address', name: 'newRouter', type: 'address' }], + name: 'setRouter', + outputs: [], + stateMutability: 'nonpayable', + type: 'function', + }, + { + inputs: [{ internalType: 'bytes4', name: 'interfaceId', type: 'bytes4' }], + name: 'supportsInterface', + outputs: [{ internalType: 'bool', name: '', type: 'bool' }], + stateMutability: 'pure', + type: 'function', + }, + { + inputs: [ + { internalType: 'address', name: 'from', type: 'address' }, + { internalType: 'uint256', name: 'amount', type: 'uint256' }, + ], + name: 'transferLiquidity', + outputs: [], + stateMutability: 'nonpayable', + type: 'function', + }, + { + inputs: [{ internalType: 'address', name: 'to', type: 'address' }], + name: 'transferOwnership', + outputs: [], + stateMutability: 'nonpayable', + type: 'function', + }, + { + inputs: [], + name: 'typeAndVersion', + outputs: [{ internalType: 'string', name: '', type: 'string' }], + stateMutability: 'view', + type: 'function', + }, + { + inputs: [{ internalType: 'uint256', name: 'amount', type: 'uint256' }], + name: 'withdrawLiquidity', + outputs: [], + stateMutability: 'nonpayable', + type: 'function', + }, + // generate:end +] as const diff --git a/src/abi/TokenAdminRegistry_1_5.ts b/src/abi/TokenAdminRegistry_1_5.ts new file mode 100644 index 0000000..9e4ef1e --- /dev/null +++ b/src/abi/TokenAdminRegistry_1_5.ts @@ -0,0 +1,332 @@ +export default [ + // generate: + // fetch('https://raw.githubusercontent.com/smartcontractkit/ccip/release/contracts-ccip-1.5.1/core/gethwrappers/ccip/generated/token_admin_registry/token_admin_registry.go') + // .then((res) => res.text()) + // .then((body) => body.match(/^\s*ABI: "(.*?)",$/m)?.[1]) + // .then((abi) => JSON.parse(abi.replace(/\\"/g, '"'))) + // .then((obj) => require('util').inspect(obj, {depth:99}).split('\n').slice(1, -1)) + { + inputs: [{ internalType: 'address', name: 'token', type: 'address' }], + name: 'AlreadyRegistered', + type: 'error', + }, + { + inputs: [{ internalType: 'address', name: 'token', type: 'address' }], + name: 'InvalidTokenPoolToken', + type: 'error', + }, + { + inputs: [ + { internalType: 'address', name: 'sender', type: 'address' }, + { internalType: 'address', name: 'token', type: 'address' }, + ], + name: 'OnlyAdministrator', + type: 'error', + }, + { + inputs: [ + { internalType: 'address', name: 'sender', type: 'address' }, + { internalType: 'address', name: 'token', type: 'address' }, + ], + name: 'OnlyPendingAdministrator', + type: 'error', + }, + { + inputs: [{ internalType: 'address', name: 'sender', type: 'address' }], + name: 'OnlyRegistryModuleOrOwner', + type: 'error', + }, + { inputs: [], name: 'ZeroAddress', type: 'error' }, + { + anonymous: false, + inputs: [ + { + indexed: true, + internalType: 'address', + name: 'token', + type: 'address', + }, + { + indexed: true, + internalType: 'address', + name: 'currentAdmin', + type: 'address', + }, + { + indexed: true, + internalType: 'address', + name: 'newAdmin', + type: 'address', + }, + ], + name: 'AdministratorTransferRequested', + type: 'event', + }, + { + anonymous: false, + inputs: [ + { + indexed: true, + internalType: 'address', + name: 'token', + type: 'address', + }, + { + indexed: true, + internalType: 'address', + name: 'newAdmin', + type: 'address', + }, + ], + name: 'AdministratorTransferred', + type: 'event', + }, + { + anonymous: false, + inputs: [ + { + indexed: true, + internalType: 'address', + name: 'from', + type: 'address', + }, + { + indexed: true, + internalType: 'address', + name: 'to', + type: 'address', + }, + ], + name: 'OwnershipTransferRequested', + type: 'event', + }, + { + anonymous: false, + inputs: [ + { + indexed: true, + internalType: 'address', + name: 'from', + type: 'address', + }, + { + indexed: true, + internalType: 'address', + name: 'to', + type: 'address', + }, + ], + name: 'OwnershipTransferred', + type: 'event', + }, + { + anonymous: false, + inputs: [ + { + indexed: true, + internalType: 'address', + name: 'token', + type: 'address', + }, + { + indexed: true, + internalType: 'address', + name: 'previousPool', + type: 'address', + }, + { + indexed: true, + internalType: 'address', + name: 'newPool', + type: 'address', + }, + ], + name: 'PoolSet', + type: 'event', + }, + { + anonymous: false, + inputs: [ + { + indexed: false, + internalType: 'address', + name: 'module', + type: 'address', + }, + ], + name: 'RegistryModuleAdded', + type: 'event', + }, + { + anonymous: false, + inputs: [ + { + indexed: true, + internalType: 'address', + name: 'module', + type: 'address', + }, + ], + name: 'RegistryModuleRemoved', + type: 'event', + }, + { + inputs: [{ internalType: 'address', name: 'localToken', type: 'address' }], + name: 'acceptAdminRole', + outputs: [], + stateMutability: 'nonpayable', + type: 'function', + }, + { + inputs: [], + name: 'acceptOwnership', + outputs: [], + stateMutability: 'nonpayable', + type: 'function', + }, + { + inputs: [{ internalType: 'address', name: 'module', type: 'address' }], + name: 'addRegistryModule', + outputs: [], + stateMutability: 'nonpayable', + type: 'function', + }, + { + inputs: [ + { internalType: 'uint64', name: 'startIndex', type: 'uint64' }, + { internalType: 'uint64', name: 'maxCount', type: 'uint64' }, + ], + name: 'getAllConfiguredTokens', + outputs: [{ internalType: 'address[]', name: 'tokens', type: 'address[]' }], + stateMutability: 'view', + type: 'function', + }, + { + inputs: [{ internalType: 'address', name: 'token', type: 'address' }], + name: 'getPool', + outputs: [{ internalType: 'address', name: '', type: 'address' }], + stateMutability: 'view', + type: 'function', + }, + { + inputs: [{ internalType: 'address[]', name: 'tokens', type: 'address[]' }], + name: 'getPools', + outputs: [{ internalType: 'address[]', name: '', type: 'address[]' }], + stateMutability: 'view', + type: 'function', + }, + { + inputs: [{ internalType: 'address', name: 'token', type: 'address' }], + name: 'getTokenConfig', + outputs: [ + { + components: [ + { + internalType: 'address', + name: 'administrator', + type: 'address', + }, + { + internalType: 'address', + name: 'pendingAdministrator', + type: 'address', + }, + { + internalType: 'address', + name: 'tokenPool', + type: 'address', + }, + ], + internalType: 'structTokenAdminRegistry.TokenConfig', + name: '', + type: 'tuple', + }, + ], + stateMutability: 'view', + type: 'function', + }, + { + inputs: [ + { internalType: 'address', name: 'localToken', type: 'address' }, + { + internalType: 'address', + name: 'administrator', + type: 'address', + }, + ], + name: 'isAdministrator', + outputs: [{ internalType: 'bool', name: '', type: 'bool' }], + stateMutability: 'view', + type: 'function', + }, + { + inputs: [{ internalType: 'address', name: 'module', type: 'address' }], + name: 'isRegistryModule', + outputs: [{ internalType: 'bool', name: '', type: 'bool' }], + stateMutability: 'view', + type: 'function', + }, + { + inputs: [], + name: 'owner', + outputs: [{ internalType: 'address', name: '', type: 'address' }], + stateMutability: 'view', + type: 'function', + }, + { + inputs: [ + { internalType: 'address', name: 'localToken', type: 'address' }, + { + internalType: 'address', + name: 'administrator', + type: 'address', + }, + ], + name: 'proposeAdministrator', + outputs: [], + stateMutability: 'nonpayable', + type: 'function', + }, + { + inputs: [{ internalType: 'address', name: 'module', type: 'address' }], + name: 'removeRegistryModule', + outputs: [], + stateMutability: 'nonpayable', + type: 'function', + }, + { + inputs: [ + { internalType: 'address', name: 'localToken', type: 'address' }, + { internalType: 'address', name: 'pool', type: 'address' }, + ], + name: 'setPool', + outputs: [], + stateMutability: 'nonpayable', + type: 'function', + }, + { + inputs: [ + { internalType: 'address', name: 'localToken', type: 'address' }, + { internalType: 'address', name: 'newAdmin', type: 'address' }, + ], + name: 'transferAdminRole', + outputs: [], + stateMutability: 'nonpayable', + type: 'function', + }, + { + inputs: [{ internalType: 'address', name: 'to', type: 'address' }], + name: 'transferOwnership', + outputs: [], + stateMutability: 'nonpayable', + type: 'function', + }, + { + inputs: [], + name: 'typeAndVersion', + outputs: [{ internalType: 'string', name: '', type: 'string' }], + stateMutability: 'view', + type: 'function', + }, + // generate:end +] as const diff --git a/src/commands/index.ts b/src/commands/index.ts new file mode 100644 index 0000000..16b7d74 --- /dev/null +++ b/src/commands/index.ts @@ -0,0 +1,2 @@ +export { Format } from './types.js' +export { showSupportedTokens } from './supported-tokens.js' diff --git a/src/commands/supported-tokens.ts b/src/commands/supported-tokens.ts new file mode 100644 index 0000000..277a706 --- /dev/null +++ b/src/commands/supported-tokens.ts @@ -0,0 +1,838 @@ +/** + * CCIP Token Discovery Service + * + * Discovers and validates tokens that can be transferred between chains using Chainlink's CCIP. + * The service handles pagination, parallel processing, and comprehensive error collection. + * + * Architecture: + * 1. Chain & Contract Setup: Validates cross-chain paths and initializes core contracts + * 2. Token Discovery: Fetches all registered tokens with pagination + * 3. Support Validation: Checks token support for destination chain + * 4. Detail Collection: Gathers token and pool information in parallel + * + * Performance Considerations: + * - Uses batching to prevent RPC timeouts (configurable batch sizes) + * - Implements parallel processing with rate limiting + * - Memory-efficient token processing through pagination + * + * Error Handling: + * - Individual token failures don't halt the process + * - Errors are collected and reported comprehensively + * - Detailed error reporting for debugging + * + * @module supported-tokens + */ + +/* eslint-disable @typescript-eslint/no-base-to-string */ +import { type Addressable, type JsonRpcApiProvider, Contract, ZeroAddress } from 'ethers' +import type { TypedContract } from 'ethers-abitype' + +import TokenABI from '../abi/BurnMintERC677Token.js' +import RouterABI from '../abi/Router.js' +import TokenAdminRegistryABI from '../abi/TokenAdminRegistry_1_5.js' + +import { + type CCIPContractType, + type CCIPTokenPoolsVersion, + type CCIPVersion, + CCIPContractTypeBurnMintTokenPool, + CCIPContractTypeTokenPool, + CCIPVersion_1_2, + CCIPVersion_1_5_1, + CCIP_ABIs, + bigIntReplacer, + chainIdFromName, + chainNameFromId, + chainSelectorFromId, + getOnRampLane, +} from '../lib/index.js' +import { chunk, getTypeAndVersion } from '../lib/utils.js' +import type { Providers } from '../providers.js' +import { + type CCIPSupportedToken, + type TokenChunk, + type TokenDetailsError, + type TokenDetailsResult, + type TokenPoolDetails, + type VersionedTokenPool, + Format, +} from './types.js' + +/** + * Performance and reliability configuration. + * Adjust these values based on network conditions and RPC provider capabilities. + */ +const CONFIG = { + /** + * Maximum tokens per registry request. + */ + BATCH_SIZE: 100, + + /** + * Parallel pool support checks. + * - Increase: If RPC can handle more concurrent requests + * - Decrease: If hitting rate limits or timeouts + */ + PARALLEL_POOL_CHECKS: 5, + + /** + * Parallel pool detail fetching. + * Separate from POOL_CHECKS as these calls are heavier. + */ + PARALLEL_POOL_DETAILS: 3, +} as const + +/** + * Type guard for successful token discovery results + * + * @param result - Token discovery result to check + * @returns True if result contains successful token discovery + */ +function isSuccessResult( + result: TokenDetailsResult, +): result is { success: CCIPSupportedToken; error: null } { + return result.success !== null +} + +/** + * Type guard for failed token discovery results + * + * @param result - Token discovery result to check + * @returns True if result contains token discovery error + */ +function isErrorResult( + result: TokenDetailsResult, +): result is { success: null; error: TokenDetailsError } { + return result.error !== null +} + +/** + * Pool information interface + * Contains version, type, and contract details for a token pool + */ +interface PoolInfo { + type: CCIPContractTypeTokenPool + version: CCIPTokenPoolsVersion + contract: TypedContract<(typeof CCIP_ABIs)[CCIPContractTypeTokenPool][CCIPTokenPoolsVersion]> + address: string + isCustomPool?: boolean +} + +/** + * Fetches detailed information about a token pool + * + * Retrieves: + * - Remote token address + * - Associated remote pools + * - Rate limiter configurations (inbound/outbound) + * - Pool type and version information + * + * @param poolInfo - Pool contract and metadata + * @param destSelector - Destination chain selector + * @returns Pool details or null if fetching fails + */ +async function getPoolDetails( + poolInfo: PoolInfo, + destSelector: bigint, +): Promise { + try { + const [remoteToken, remotePools, outboundState, inboundState] = await Promise.all([ + poolInfo.contract.getRemoteToken(destSelector), + getRemotePoolsForVersion(poolInfo, destSelector), + poolInfo.contract.getCurrentOutboundRateLimiterState(destSelector), + poolInfo.contract.getCurrentInboundRateLimiterState(destSelector), + ]) + + return { + remoteToken: remoteToken.toString(), + remotePools, + outboundRateLimiter: { + tokens: outboundState.tokens, + lastUpdated: Number(outboundState.lastUpdated), + isEnabled: outboundState.isEnabled, + capacity: outboundState.capacity, + rate: outboundState.rate, + }, + inboundRateLimiter: { + tokens: inboundState.tokens, + lastUpdated: Number(inboundState.lastUpdated), + isEnabled: inboundState.isEnabled, + capacity: inboundState.capacity, + rate: inboundState.rate, + }, + isCustomPool: poolInfo.isCustomPool, + type: poolInfo.type, + version: poolInfo.version, + } + } catch (error) { + console.error( + `[ERROR] Failed to fetch pool details for pool ${poolInfo.address} | type ${poolInfo.type} v${poolInfo.version}:`, + error instanceof Error ? error.message : String(error), + ) + return null + } +} + +/** + * Resolves chain identifiers and initializes required providers. + * + * @throws {Error} If chain identifiers are invalid or providers unavailable + */ +async function parseChainIds( + providers: Providers, + argv: { source: string; dest: string; router: string; format: Format }, +) { + const sourceChainId = isNaN(+argv.source) ? chainIdFromName(argv.source) : +argv.source + const sourceProvider = await providers.forChainId(sourceChainId) + + const destChainId = isNaN(+argv.dest) ? chainIdFromName(argv.dest) : +argv.dest + const destSelector = chainSelectorFromId(destChainId) + + return { sourceChainId, destChainId, sourceProvider, destSelector } +} + +/** + * Validates that the specified cross-chain lane is supported. + * + * @throws {Error} If the lane is not supported by CCIP + */ +async function checkChainSupport( + routerAddress: string, + sourceProvider: JsonRpcApiProvider, + destSelector: bigint, + sourceChainId: number, + destChainId: number, +) { + const router = new Contract(routerAddress, RouterABI, sourceProvider) as unknown as TypedContract< + typeof RouterABI + > + + const isChainSupported = await router.isChainSupported(destSelector) + if (!isChainSupported) { + throw new Error( + `Lane "${chainNameFromId(sourceChainId)}" -> "${chainNameFromId(destChainId)}" is not supported`, + ) + } + + console.log( + `[INFO] Lane "${chainNameFromId(sourceChainId)}" -> "${chainNameFromId(destChainId)}" is supported`, + ) + + return router +} + +/** + * Retrieves the TokenAdminRegistry contract from the onRamp's configuration. + * + * @throws {Error} If using deprecated CCIP version + */ +async function getRegistryContract( + router: TypedContract, + sourceProvider: JsonRpcApiProvider, + destSelector: bigint, +) { + // Get onRamp address from router + const onRampAddress = await router.getOnRamp(destSelector) + const [lane, onrampContract] = await getOnRampLane(sourceProvider, onRampAddress.toString()) + + // Get registry address from onRamp's static config + const staticConfig = await onrampContract.getStaticConfig() + if (lane.version === CCIPVersion_1_2) { + throw new Error('Deprecated CCIP onRamp version') + } + + const registryAddress = (staticConfig as { tokenAdminRegistry: string | Addressable }) + .tokenAdminRegistry + + console.log(`[INFO] Using TokenAdminRegistry at ${registryAddress.toString()}`) + + const registry = new Contract( + registryAddress, + TokenAdminRegistryABI, + sourceProvider, + ) as unknown as TypedContract + + return registry +} + +/** + * Fetches all registered tokens using pagination to handle large sets. + * + * Performance Notes: + * - Uses BATCH_SIZE to limit request size + * - Implements pagination to handle any number of tokens + * - Memory-efficient through incremental processing + */ +async function fetchAllRegisteredTokens( + registry: TypedContract, + sourceChainId: number, +) { + let startIndex = 0n + const maxCount = CONFIG.BATCH_SIZE + let tokensBatch: Array = [] + let totalScanned = 0 + const allTokens: Array = [] + + console.log( + `[INFO] Fetching all registered tokens from "${chainNameFromId(sourceChainId)}" using TokenAdminRegistry`, + ) + + do { + console.log(`[INFO] Fetching batch: offset=${startIndex}, limit=${maxCount}`) + tokensBatch = [...(await registry.getAllConfiguredTokens(startIndex, BigInt(maxCount)))] + totalScanned += tokensBatch.length + console.log(`[INFO] Found ${tokensBatch.length} tokens (total scanned: ${totalScanned})`) + + allTokens.push(...tokensBatch) + startIndex += BigInt(tokensBatch.length) + } while (tokensBatch.length === maxCount) + + return { allTokens, totalScanned } +} + +/** + * Identifies supported tokens for cross-chain transfer + * + * Process: + * 1. Fetches pool addresses for tokens + * 2. Validates pool contracts and versions + * 3. Checks destination chain support + * 4. Collects pool information for supported tokens + * + * @param registry - Token registry contract + * @param allTokens - List of token addresses to check + * @param sourceProvider - Source chain provider + * @param destSelector - Destination chain selector + * @returns Mapping of token addresses to their pool information + */ +async function findSupportedTokens( + registry: TypedContract, + allTokens: Array, + sourceProvider: JsonRpcApiProvider, + destSelector: bigint, +): Promise> { + const tokenToPoolInfo: Record = {} + const tokenChunks = chunk(allTokens, CONFIG.PARALLEL_POOL_CHECKS) + + for (const chunkTokens of tokenChunks) { + const rawPoolsChunk = await registry.getPools(chunkTokens) + + // Filter out zero addresses and map to corresponding tokens + const validPools = rawPoolsChunk + .map((pool, idx) => ({ + pool: pool.toString(), + token: chunkTokens[idx].toString(), + })) + .filter(({ pool }) => pool !== ZeroAddress) + + const supportChecks = await Promise.all( + validPools.map(async ({ pool, token }) => { + const result = await getVersionedPoolContract(pool, sourceProvider) + + if ('error' in result) { + console.error(`[ERROR] Failed to initialize pool ${pool} | token ${token}`, result.error) + return { + token, + isSupported: false, + error: result.error, + } + } + + try { + const isSupported = await result.contract.isSupportedChain(destSelector) + return { + token, + pool: { + ...result, + address: pool, + }, + isSupported, + error: null, + } + } catch (error) { + console.error( + `[ERROR] Failed to check support for pool ${pool} | type ${result.type} | version ${result.version} | token ${token}`, + error, + ) + return { + token, + isSupported: false, + error: error instanceof Error ? error : new Error(String(error)), + } + } + }), + ) + + // Collect results + for (const check of supportChecks) { + if (check.isSupported && 'pool' in check) { + tokenToPoolInfo[check.token] = check.pool as PoolInfo + } + } + } + + return tokenToPoolInfo +} + +/** + * Gathers detailed information about supported tokens and their pools + * + * Collects: + * - Token metadata (name, symbol, decimals) + * - Pool configuration and status + * - Rate limiter settings + * - Remote token and pool information + * + * @param tokenToPoolInfo - Mapping of tokens to their pool information + * @param sourceProvider - Source chain provider + * @param destSelector - Destination chain selector + * @returns Array of token details or error information + */ +async function fetchTokenDetailsForSupportedTokens( + tokenToPoolInfo: Record, + sourceProvider: JsonRpcApiProvider, + destSelector: bigint, +): Promise { + const tokens = Object.keys(tokenToPoolInfo) + return ( + await Promise.all( + chunk(tokens, CONFIG.PARALLEL_POOL_DETAILS).map(async (tokenChunk: TokenChunk) => { + return Promise.all( + tokenChunk.map(async (token: string): Promise => { + try { + const erc20 = new Contract( + token, + TokenABI, + sourceProvider, + ) as unknown as TypedContract + const poolInfo = tokenToPoolInfo[token] + + const [name, symbol, decimalsBI, poolDetails] = await Promise.all([ + erc20.name(), + erc20.symbol(), + erc20.decimals(), + getPoolDetails(poolInfo, destSelector), + ]) + + const decimals = Number(decimalsBI) + + console.log( + `[INFO] Successfully fetched details for token ${name} (${symbol}) at ${token} | pool ${poolInfo.address} | type ${poolInfo.type} v${poolInfo.version}`, + ) + + return { + success: { + name, + symbol, + decimals, + address: token, + pool: poolInfo.address, + poolDetails: poolDetails ?? undefined, + }, + error: null, + } satisfies TokenDetailsResult + } catch (error: unknown) { + const actualError = error instanceof Error ? error : new Error(String(error)) + return { + success: null, + error: { + token, + error: actualError, + }, + } satisfies TokenDetailsResult + } + }), + ) + }), + ) + ).flat() +} + +/** + * Prepares the final discovery report + * + * Generates: + * - Process metadata (timestamp, chains, router) + * - Statistical summary + * - Supported token details + * - Failed token information + * + * @param tokenDetails - Array of token discovery results + * @param totalScanned - Total number of tokens checked + * @param sourceChainId - Source chain identifier + * @param destChainId - Destination chain identifier + * @param routerAddress - CCIP router address + * @returns Formatted summary and categorized results + */ +function prepareSummary( + tokenDetails: TokenDetailsResult[], + totalScanned: number, + sourceChainId: number, + destChainId: number, + routerAddress: string, +): { + summary: { + metadata: { + timestamp: string + source: { + chain: string + chainId: number + router: string + } + destination: { + chain: string + chainId: number + } + stats: { + totalScanned: number + supported: number + failed: number + } + } + tokens: CCIPSupportedToken[] + failedTokens: { address: string; error: string }[] + } + successfulTokens: CCIPSupportedToken[] + failedTokens: { token: string; error: Error }[] +} { + const successfulTokens: CCIPSupportedToken[] = [] + const failedTokens: { token: string; error: Error }[] = [] + + tokenDetails.forEach((detail) => { + if (isSuccessResult(detail)) { + successfulTokens.push(detail.success) + } else if (isErrorResult(detail)) { + failedTokens.push(detail.error) + } + }) + + const summary = { + metadata: { + timestamp: new Date().toISOString(), + source: { + chain: chainNameFromId(sourceChainId), + chainId: sourceChainId, + router: routerAddress, + }, + destination: { + chain: chainNameFromId(destChainId), + chainId: destChainId, + }, + stats: { + totalScanned, + supported: successfulTokens.length, + failed: failedTokens.length, + }, + }, + tokens: successfulTokens, + failedTokens: failedTokens.map(({ token, error }) => ({ + address: token, + error: error.message, + })), + } + + return { summary, successfulTokens, failedTokens } +} + +/** + * Pool version detection utility + * + * Attempts to detect the pool type and version. Falls back to BurnMintTokenPool + * with latest version if detection fails. + * + * @param address - The pool contract address + * @param provider - JSON RPC provider instance + * @returns Pool type, version and custom pool flag + */ +async function detectPoolVersion( + address: string, + provider: JsonRpcApiProvider, +): Promise<{ type: CCIPContractType; version: CCIPVersion; isCustomPool: boolean }> { + try { + const [type_, version] = await getTypeAndVersion(provider, address) + return { type: type_, version, isCustomPool: false } + } catch (versionError) { + console.warn( + `[WARN] Could not determine pool type and version for pool ${address}. Error: ${ + versionError instanceof Error ? versionError.message : String(versionError) + }`, + ) + console.warn('[WARN] Assuming this is a custom pool, will try with latest version') + + return { + type: CCIPContractTypeBurnMintTokenPool, + version: CCIPVersion_1_5_1, + isCustomPool: true, + } + } +} + +/** + * Pool contract instantiation utility + * + * Creates a typed contract instance for a token pool with the correct ABI. + * + * @param address - The pool contract address + * @param provider - JSON RPC provider instance + * @param type_ - The pool contract type + * @param version - The pool contract version + * @param abi - The contract ABI matching the type and version + * @returns Typed contract instance + */ +function createPoolContract( + address: string, + provider: JsonRpcApiProvider, + type_: CCIPContractTypeTokenPool, + version: CCIPTokenPoolsVersion, + abi: (typeof CCIP_ABIs)[CCIPContractTypeTokenPool][CCIPTokenPoolsVersion], +): TypedContract { + return new Contract(address, abi, provider) as unknown as TypedContract +} + +/** + * Version-aware pool contract factory + * + * Creates a version-aware pool contract instance with proper typing and error handling. + * Handles custom pools by falling back to latest known compatible ABI. + * + * @param address - The pool contract address + * @param provider - JSON RPC provider instance + * @returns Version-aware pool contract or error details + * + * @throws Will not throw, returns error object instead + */ +async function getVersionedPoolContract( + address: string, + provider: JsonRpcApiProvider, +): Promise { + try { + const { type: type_, version, isCustomPool } = await detectPoolVersion(address, provider) + + // Validate pool type + if (!CCIPContractTypeTokenPool.includes(type_)) { + throw new Error( + `Not a token pool: ${address} is "${type_} ${version}" - Supported types: ${CCIPContractTypeTokenPool.join( + ', ', + )}}`, + ) + } + + // Get correct ABI based on type and version + const abi = CCIP_ABIs[type_ as CCIPContractTypeTokenPool][version as CCIPTokenPoolsVersion] + if (!abi) { + throw new Error(`Unsupported pool version: ${version} for type ${type_}`) + } + + const contract = createPoolContract( + address, + provider, + type_ as CCIPContractTypeTokenPool, + version as CCIPTokenPoolsVersion, + abi, + ) + + return { + version: version as CCIPTokenPoolsVersion, + type: type_ as CCIPContractTypeTokenPool, + contract, + isCustomPool, + } + } catch (error) { + return { + error: + error instanceof Error + ? error + : new Error(error instanceof Object ? JSON.stringify(error) : String(error)), + } + } +} + +/** + * Gets remote pools based on contract version + * + * Handles different remote pool retrieval methods based on contract version: + * - v1.5.0: Single remote pool via getRemotePool + * - v1.5.1: Multiple remote pools via getRemotePools + * + * @param versionedPool - The version-aware pool contract + * @param destSelector - Destination chain selector + * @returns Array of remote pool addresses + */ +async function getRemotePoolsForVersion( + versionedPool: VersionedTokenPool, + destSelector: bigint, +): Promise { + try { + switch (versionedPool.version) { + case '1.5.0': { + const remotePool = await versionedPool.contract.getRemotePool(destSelector) + return [remotePool.toString()] + } + case '1.5.1': { + const remotePools = await versionedPool.contract.getRemotePools(destSelector) + return remotePools.map((pool) => pool.toString()) + } + } + } catch (error) { + console.error( + `[ERROR] Failed to get remote pools for ${await versionedPool.contract.getAddress()} | type ${versionedPool.type} v${versionedPool.version}:`, + error instanceof Error ? error.message : String(error), + ) + return [] + } +} + +/** + * Main entry point for token discovery process. + * + * Process Flow: + * 1. Chain setup and validation + * 2. Registry contract initialization + * 3. Token discovery and filtering + * 4. Detailed information gathering + * 5. Result compilation and reporting + * + * Error Handling: + * - Critical errors (chain/contract setup) halt the process + * - Non-critical errors (individual tokens) are collected and reported + * - Comprehensive error reporting for debugging + * + * Output Formats: + * - json: Machine-readable complete output + * - log: Basic console logging + * - pretty: Formatted human-readable output + */ +export async function showSupportedTokens( + providers: Providers, + argv: { source: string; router: string; dest: string; format: Format }, +) { + console.log('[INFO] Starting token discovery for cross-chain transfers') + + // Step 1) Parse chain IDs & providers + const { sourceChainId, destChainId, sourceProvider, destSelector } = await parseChainIds( + providers, + argv, + ) + + // Step 2) Check chain support + const router = await checkChainSupport( + argv.router, + sourceProvider, + destSelector, + sourceChainId, + destChainId, + ) + + // Step 3) Get registry contract + const registry = await getRegistryContract(router, sourceProvider, destSelector) + + // Step 4) Fetch all tokens (paginated) from registry + const { allTokens, totalScanned } = await fetchAllRegisteredTokens(registry, sourceChainId) + + // Step 5) Check which tokens are supported on the destination chain + const tokenToPoolInfo = await findSupportedTokens( + registry, + allTokens, + sourceProvider, + destSelector, + ) + + const supportedTokenCount = Object.keys(tokenToPoolInfo).length + console.log( + `[SUMMARY] Scanned ${totalScanned} tokens, found ${supportedTokenCount} supported for "${chainNameFromId( + sourceChainId, + )}" -> "${chainNameFromId(destChainId)}"`, + ) + + // Step 6) Fetch detailed token + pool info for the supported tokens + console.log('[INFO] Fetching detailed token and pool information') + const tokenDetails = await fetchTokenDetailsForSupportedTokens( + tokenToPoolInfo, + sourceProvider, + destSelector, + ) + + // Step 7) Prepare summary structure + const { summary, successfulTokens, failedTokens } = prepareSummary( + tokenDetails, + totalScanned, + sourceChainId, + destChainId, + argv.router, + ) + + // Step 8) Output results + switch (argv.format) { + case Format.json: + console.log(JSON.stringify(summary, bigIntReplacer, 2)) + break + + case Format.log: + console.log('Supported tokens:', successfulTokens) + break + + case Format.pretty: + default: + // Log metadata first + console.log('\n=== Summary ===') + console.log(`Timestamp: ${summary.metadata.timestamp}`) + console.log('\nSource:') + console.log(` Chain: ${summary.metadata.source.chain} (${summary.metadata.source.chainId})`) + console.log(` Router: ${summary.metadata.source.router}`) + console.log('\nDestination:') + console.log( + ` Chain: ${summary.metadata.destination.chain} (${summary.metadata.destination.chainId})`, + ) + console.log('\nStats:') + console.log(` Total Scanned: ${summary.metadata.stats.totalScanned}`) + console.log(` Supported: ${summary.metadata.stats.supported}`) + console.log(` Failed: ${summary.metadata.stats.failed}`) + + // Log tokens + console.log('\n=== Supported Tokens ===') + for (const token of successfulTokens) { + console.log( + `[INFO] Token: ${token.name} (${token.symbol}) at ${token.address}, decimals=${token.decimals}`, + ) + console.log( + ` Pool: ${token.pool}${ + token.poolDetails?.isCustomPool + ? ' (Custom Pool)' + : ` (${token.poolDetails?.type} v${token.poolDetails?.version})` + }`, + ) + + if (token.poolDetails) { + console.log(` Remote Token: ${token.poolDetails.remoteToken}`) + console.log(` Remote Pools: ${token.poolDetails.remotePools.join(', ')}`) + console.log(' Rate Limiters:') + console.log(' Outbound:') + console.log(` Enabled: ${token.poolDetails.outboundRateLimiter.isEnabled}`) + console.log(` Tokens: ${token.poolDetails.outboundRateLimiter.tokens}`) + console.log(` Capacity: ${token.poolDetails.outboundRateLimiter.capacity}`) + console.log(` Rate: ${token.poolDetails.outboundRateLimiter.rate}`) + console.log(' Inbound:') + console.log(` Enabled: ${token.poolDetails.inboundRateLimiter.isEnabled}`) + console.log(` Tokens: ${token.poolDetails.inboundRateLimiter.tokens}`) + console.log(` Capacity: ${token.poolDetails.inboundRateLimiter.capacity}`) + console.log(` Rate: ${token.poolDetails.inboundRateLimiter.rate}`) + } + console.log('---') + } + + // Log failed tokens + if (failedTokens.length > 0) { + console.log('\n=== Failed Tokens ===') + for (const { token, error } of failedTokens) { + console.error(`[ERROR] Token: ${token}, Error:`, error) + } + } + } + + if (failedTokens.length > 0) { + console.error('[ERROR] Failed to fetch metadata for some tokens:') + for (const { token, error } of failedTokens) { + console.error(`[ERROR] Token: ${token}, Error:`, error) + } + } +} diff --git a/src/commands/types.ts b/src/commands/types.ts new file mode 100644 index 0000000..abc2a8f --- /dev/null +++ b/src/commands/types.ts @@ -0,0 +1,59 @@ +import type { TypedContract } from 'ethers-abitype' +import type { CCIPContractTypeTokenPool, CCIPTokenPoolsVersion, CCIP_ABIs } from '../lib/types.js' + +export enum Format { + log = 'log', + pretty = 'pretty', + json = 'json', +} + +// Extended token info with pool details for CCIP +export interface CCIPSupportedToken { + name: string + symbol: string + decimals: number + address: string + pool: string + poolDetails?: TokenPoolDetails +} + +export interface TokenDetailsError { + token: string + error: Error +} + +// First, let's add the necessary types +export interface TokenBucket { + tokens: bigint + lastUpdated: number + isEnabled: boolean + capacity: bigint + rate: bigint +} + +export interface TokenPoolDetails { + remoteToken: string + remotePools: string[] + outboundRateLimiter: TokenBucket + inboundRateLimiter: TokenBucket + isCustomPool?: boolean + type: CCIPContractTypeTokenPool + version: CCIPTokenPoolsVersion +} + +// Token processing types +export type TokenChunk = readonly string[] + +export type TokenDetailsResult = + | { success: CCIPSupportedToken; error: null } + | { success: null; error: TokenDetailsError } + +/** + * Version-aware token pool contract wrapper + */ +export interface VersionedTokenPool { + type: CCIPContractTypeTokenPool + version: CCIPTokenPoolsVersion + contract: TypedContract<(typeof CCIP_ABIs)[CCIPContractTypeTokenPool][CCIPTokenPoolsVersion]> + isCustomPool?: boolean +} diff --git a/src/index.ts b/src/index.ts index a804df2..88c3598 100755 --- a/src/index.ts +++ b/src/index.ts @@ -5,6 +5,7 @@ import { ZeroAddress, getAddress, isHexString } from 'ethers' import yargs from 'yargs' import { hideBin } from 'yargs/helpers' +import { showSupportedTokens } from './commands/index.js' import { Format, estimateGas, @@ -21,7 +22,7 @@ import { logParsedError } from './utils.js' util.inspect.defaultOptions.depth = 6 // print down to tokenAmounts in requests // generate:nofail // `const VERSION = '${require('./package.json').version}-${require('child_process').execSync('git rev-parse --short HEAD').toString().trim()}'` -const VERSION = '0.1.3-c92e135' +const VERSION = '0.1.3-4790fa5' // generate:end async function main() { @@ -357,6 +358,39 @@ async function main() { .finally(() => providers.destroy()) }, ) + .command( + 'getSupportedTokens ', + 'show supported tokens for cross-chain transfers', + (yargs) => + yargs + .positional('source', { + type: 'string', + demandOption: true, + describe: 'Source chain name or id', + example: 'ethereum-testnet-sepolia', + }) + .positional('router', { + type: 'string', + demandOption: true, + describe: 'router contract address on source', + coerce: getAddress, + }) + .positional('dest', { + type: 'string', + demandOption: true, + describe: 'Destination chain name or id', + example: 'ethereum-testnet-sepolia-optimism-1', + }), + async (argv) => { + const providers = new Providers(argv) + return showSupportedTokens(providers, argv) + .catch((err) => { + process.exitCode = 1 + if (!logParsedError(err)) console.error(err) + }) + .finally(() => providers.destroy()) + }, + ) .demandCommand() .strict() .help() diff --git a/src/lib/index.ts b/src/lib/index.ts index 108b092..6398586 100644 --- a/src/lib/index.ts +++ b/src/lib/index.ts @@ -17,6 +17,7 @@ export { type CCIPExecution, type CCIPMessage, type CCIPRequest, + type CCIPTokenPoolsVersion, type CCIPVersion, type CommitReport, type EVMExtraArgsV1, @@ -24,11 +25,15 @@ export { type ExecutionReceipt, type Lane, type NetworkInfo, + CCIPContractTypeBurnMintTokenPool, CCIPContractTypeCommitStore, CCIPContractTypeOffRamp, CCIPContractTypeOnRamp, + CCIPContractTypeTokenPool, CCIPVersion_1_2, CCIPVersion_1_5, + CCIPVersion_1_5_1, + CCIP_ABIs, ExecutionState, defaultAbiCoder, encodeExtraArgs, diff --git a/src/lib/types.ts b/src/lib/types.ts index 615fe96..4786226 100644 --- a/src/lib/types.ts +++ b/src/lib/types.ts @@ -7,8 +7,12 @@ import { } from 'abitype' import { type Log, type Result, AbiCoder, concat, dataSlice, id } from 'ethers' +import BurnMintTokenPool_1_5_ABI from '../abi/BurnMintTokenPool_1_5.js' +import BurnMintTokenPool_1_5_1_ABI from '../abi/BurnMintTokenPool_1_5_1.js' import CommitStore_1_2_ABI from '../abi/CommitStore_1_2.js' import CommitStore_1_5_ABI from '../abi/CommitStore_1_5.js' +import LockReleaseTokenPool_1_5_ABI from '../abi/LockReleaseTokenPool_1_5.js' +import LockReleaseTokenPool_1_5_1_ABI from '../abi/LockReleaseTokenPool_1_5_1.js' import EVM2EVMOffRamp_1_2_ABI from '../abi/OffRamp_1_2.js' import EVM2EVMOffRamp_1_5_ABI from '../abi/OffRamp_1_5.js' import EVM2EVMOnRamp_1_2_ABI from '../abi/OnRamp_1_2.js' @@ -21,11 +25,14 @@ export type CCIPMessage = AbiParametersToPrimitiveTypes< ExtractAbiEvent['inputs'] >[0] +export const CCIPVersion_1_5_1 = '1.5.1' +export type CCIPVersion_1_5_1 = typeof CCIPVersion_1_5_1 export const CCIPVersion_1_5 = '1.5.0' export type CCIPVersion_1_5 = typeof CCIPVersion_1_5 export const CCIPVersion_1_2 = '1.2.0' export type CCIPVersion_1_2 = typeof CCIPVersion_1_2 -export type CCIPVersion = CCIPVersion_1_5 | CCIPVersion_1_2 +export type CCIPVersion = CCIPVersion_1_5 | CCIPVersion_1_2 | CCIPVersion_1_5_1 +export type CCIPTokenPoolsVersion = CCIPVersion_1_5 | CCIPVersion_1_5_1 export const CCIPContractTypeOnRamp = 'EVM2EVMOnRamp' export type CCIPContractTypeOnRamp = typeof CCIPContractTypeOnRamp @@ -33,24 +40,114 @@ export const CCIPContractTypeOffRamp = 'EVM2EVMOffRamp' export type CCIPContractTypeOffRamp = typeof CCIPContractTypeOffRamp export const CCIPContractTypeCommitStore = 'EVM2EVMCommitStore' export type CCIPContractTypeCommitStore = typeof CCIPContractTypeCommitStore +export const CCIPContractTypeBurnMintTokenPool = 'BurnMintTokenPool' +export type CCIPContractTypeBurnMintTokenPool = typeof CCIPContractTypeBurnMintTokenPool +export const CCIPContractTypeBurnMintTokenPoolAndProxy = 'BurnMintTokenPoolAndProxy' +export type CCIPContractTypeBurnMintTokenPoolAndProxy = + typeof CCIPContractTypeBurnMintTokenPoolAndProxy +export const CCIPContractTypeBurnMintTokenPoolWithTax = 'BurnMintTokenPoolWithTax' + +export const CCIPContractTypeBurnWithFromMintTokenPool = 'BurnWithFromMintTokenPool' +export type CCIPContractTypeBurnWithFromMintTokenPool = + typeof CCIPContractTypeBurnWithFromMintTokenPool + +export const CCIPContractTypeBurnWithFromMintTokenPoolAndProxy = 'BurnWithFromMintTokenPoolAndProxy' +export type CCIPContractTypeBurnWithFromMintTokenPoolAndProxy = + typeof CCIPContractTypeBurnWithFromMintTokenPoolAndProxy + +export type CCIPContractTypeBurnMintTokenPoolWithTax = + typeof CCIPContractTypeBurnMintTokenPoolWithTax +export const CCIPContractTypeBurnWithFromMintRebasingTokenPool = 'BurnWithFromMintRebasingTokenPool' +export type CCIPContractTypeBurnWithFromMintRebasingTokenPool = + typeof CCIPContractTypeBurnWithFromMintRebasingTokenPool +export const CCIPContractTypeLockReleaseTokenPool = 'LockReleaseTokenPool' +export type CCIPContractTypeLockReleaseTokenPool = typeof CCIPContractTypeLockReleaseTokenPool +export const CCIPContractTypeLockReleaseTokenPoolAndProxy = 'LockReleaseTokenPoolAndProxy' +export type CCIPContractTypeLockReleaseTokenPoolAndProxy = + typeof CCIPContractTypeLockReleaseTokenPoolAndProxy +export const CCIPContractTypeUSDCTokenPool = 'USDCTokenPool' +export type CCIPContractTypeUSDCTokenPool = typeof CCIPContractTypeUSDCTokenPool + +export const CCIPContractTypeTokenPool = [ + CCIPContractTypeBurnMintTokenPool, + CCIPContractTypeBurnMintTokenPoolAndProxy, + CCIPContractTypeBurnMintTokenPoolWithTax, + CCIPContractTypeBurnWithFromMintRebasingTokenPool, + CCIPContractTypeLockReleaseTokenPool, + CCIPContractTypeLockReleaseTokenPoolAndProxy, + CCIPContractTypeUSDCTokenPool, + CCIPContractTypeBurnWithFromMintTokenPoolAndProxy, + CCIPContractTypeBurnWithFromMintTokenPool, +] +export type CCIPContractTypeTokenPool = + | CCIPContractTypeBurnMintTokenPool + | CCIPContractTypeLockReleaseTokenPool + | CCIPContractTypeLockReleaseTokenPoolAndProxy + | CCIPContractTypeUSDCTokenPool + | CCIPContractTypeBurnWithFromMintRebasingTokenPool + | CCIPContractTypeBurnMintTokenPoolAndProxy + | CCIPContractTypeBurnMintTokenPoolWithTax + | CCIPContractTypeBurnWithFromMintTokenPoolAndProxy + | CCIPContractTypeBurnWithFromMintTokenPool + export type CCIPContractType = | CCIPContractTypeOnRamp | CCIPContractTypeOffRamp | CCIPContractTypeCommitStore + | CCIPContractTypeTokenPool export const CCIP_ABIs = { [CCIPContractTypeOnRamp]: { + [CCIPVersion_1_5_1]: EVM2EVMOnRamp_1_5_ABI, [CCIPVersion_1_5]: EVM2EVMOnRamp_1_5_ABI, [CCIPVersion_1_2]: EVM2EVMOnRamp_1_2_ABI, }, [CCIPContractTypeOffRamp]: { + [CCIPVersion_1_5_1]: EVM2EVMOffRamp_1_5_ABI, [CCIPVersion_1_5]: EVM2EVMOffRamp_1_5_ABI, [CCIPVersion_1_2]: EVM2EVMOffRamp_1_2_ABI, }, [CCIPContractTypeCommitStore]: { + [CCIPVersion_1_5_1]: CommitStore_1_5_ABI, [CCIPVersion_1_5]: CommitStore_1_5_ABI, [CCIPVersion_1_2]: CommitStore_1_2_ABI, }, + [CCIPContractTypeBurnMintTokenPool]: { + [CCIPVersion_1_5]: BurnMintTokenPool_1_5_ABI, + [CCIPVersion_1_5_1]: BurnMintTokenPool_1_5_1_ABI, + }, + [CCIPContractTypeLockReleaseTokenPool]: { + [CCIPVersion_1_5]: LockReleaseTokenPool_1_5_ABI, + [CCIPVersion_1_5_1]: LockReleaseTokenPool_1_5_1_ABI, + }, + [CCIPContractTypeLockReleaseTokenPoolAndProxy]: { + [CCIPVersion_1_5]: LockReleaseTokenPool_1_5_ABI, + [CCIPVersion_1_5_1]: LockReleaseTokenPool_1_5_1_ABI, + }, + [CCIPContractTypeUSDCTokenPool]: { + [CCIPVersion_1_5]: BurnMintTokenPool_1_5_ABI, + [CCIPVersion_1_5_1]: BurnMintTokenPool_1_5_1_ABI, + }, + [CCIPContractTypeBurnWithFromMintRebasingTokenPool]: { + [CCIPVersion_1_5]: BurnMintTokenPool_1_5_ABI, + [CCIPVersion_1_5_1]: BurnMintTokenPool_1_5_1_ABI, + }, + [CCIPContractTypeBurnMintTokenPoolAndProxy]: { + [CCIPVersion_1_5]: BurnMintTokenPool_1_5_ABI, + [CCIPVersion_1_5_1]: BurnMintTokenPool_1_5_1_ABI, + }, + [CCIPContractTypeBurnMintTokenPoolWithTax]: { + [CCIPVersion_1_5]: BurnMintTokenPool_1_5_ABI, + [CCIPVersion_1_5_1]: BurnMintTokenPool_1_5_1_ABI, + }, + [CCIPContractTypeBurnWithFromMintTokenPoolAndProxy]: { + [CCIPVersion_1_5]: BurnMintTokenPool_1_5_ABI, + [CCIPVersion_1_5_1]: BurnMintTokenPool_1_5_1_ABI, + }, + [CCIPContractTypeBurnWithFromMintTokenPool]: { + [CCIPVersion_1_5]: BurnMintTokenPool_1_5_ABI, + [CCIPVersion_1_5_1]: BurnMintTokenPool_1_5_1_ABI, + }, } as const const _: Record> = CCIP_ABIs diff --git a/src/lib/utils.ts b/src/lib/utils.ts index 2fa9b17..4332a0c 100644 --- a/src/lib/utils.ts +++ b/src/lib/utils.ts @@ -9,8 +9,10 @@ import { CCIPContractTypeCommitStore, CCIPContractTypeOffRamp, CCIPContractTypeOnRamp, + CCIPContractTypeTokenPool, CCIPVersion_1_2, CCIPVersion_1_5, + CCIPVersion_1_5_1, VersionedContractABI, } from './types.js' @@ -128,11 +130,14 @@ export async function getTypeAndVersion( const [version] = version_.split('-', 2) // remove `-dev` suffixes const isCcipContractType = (t: string): t is CCIPContractType => - [CCIPContractTypeOnRamp, CCIPContractTypeOffRamp, CCIPContractTypeCommitStore].some( - (t) => type_ === t, - ) + [ + CCIPContractTypeOnRamp, + CCIPContractTypeOffRamp, + CCIPContractTypeCommitStore, + ...CCIPContractTypeTokenPool, + ].some((t) => type_ === t) const isCcipContractVersion = (v: string): v is CCIPVersion => - [CCIPVersion_1_2, CCIPVersion_1_5].some((v) => version === v) + [CCIPVersion_1_2, CCIPVersion_1_5, CCIPVersion_1_5_1].some((v) => version === v) if (!isCcipContractType(type_)) { throw new Error(`Unknown contract type: ${typeAndVersion}`) } @@ -245,3 +250,30 @@ export function bigIntReviver(_key: string, value: unknown): unknown { } return value } + +/** + * Splits an array into multiple arrays of specified size. + * + * @template T - Type of array elements + * @param array - The array to split into chunks + * @param size - The size of each chunk. Must be greater than 0 + * @returns Array of chunks, each of size `size` (except possibly the last one) + * @throws {Error} If size is less than or equal to 0 + * + * @example + * ```ts + * const numbers = [1, 2, 3, 4, 5]; + * const chunks = chunk(numbers, 2); + * // Result: [[1, 2], [3, 4], [5]] + * ``` + */ +export function chunk(array: readonly T[], size: number): T[][] { + if (size <= 0) { + throw new Error('Chunk size must be greater than 0') + } + const chunks: T[][] = [] + for (let i = 0; i < array.length; i += size) { + chunks.push(array.slice(i, i + size)) + } + return chunks +}