From 2f439293defc8b8652cf4a8ae93a2220ef31d8ed Mon Sep 17 00:00:00 2001 From: Raj Ranjan Date: Fri, 1 Aug 2025 18:31:29 +0530 Subject: [PATCH 1/2] ictt docs and enhancements --- interchain/README.md | 110 +++- interchain/examples/sendICM.ts | 4 +- interchain/examples/sendTokens.ts | 81 +++ interchain/package-lock.json | 524 ++++++++++++++++++ interchain/package.json | 33 +- .../{examples => src}/chains/avalancheFuji.ts | 7 +- .../{examples => src}/chains/dispatch.ts | 7 +- interchain/src/chains/index.ts | 2 + interchain/src/chains/types/chainConfig.ts | 10 + interchain/src/icm/icm.ts | 65 ++- interchain/src/icm/index.ts | 3 + interchain/src/icm/types/chainConfig.ts | 5 - interchain/src/icm/types/defaultICMParams.ts | 18 +- interchain/src/icm/types/sendMsgArgs.ts | 6 +- interchain/src/ictt/ictt.ts | 360 +++++++++--- interchain/src/ictt/index.ts | 2 + .../src/ictt/types/approveTokenParams.ts | 10 + interchain/src/ictt/types/deployERC20Token.ts | 11 + .../types/deployTokenHomeContractParams.ts | 11 + .../types/deployTokenRemoteContractParams.ts | 11 + .../types/registerRemoteWithHomeParams.ts | 11 + interchain/src/ictt/types/sendTokenParams.ts | 14 + interchain/src/ictt/utils/approveToken.ts | 35 ++ interchain/src/ictt/utils/deployERC20Token.ts | 35 ++ .../src/ictt/utils/getERC20TokenInfo.ts | 37 ++ .../src/ictt/utils/getInfoFromTokenHome.ts | 45 ++ interchain/src/ictt/utils/index.ts | 4 + interchain/src/index.ts | 4 +- interchain/tsconfig.json | 4 +- 29 files changed, 1361 insertions(+), 108 deletions(-) create mode 100644 interchain/examples/sendTokens.ts create mode 100644 interchain/package-lock.json rename interchain/{examples => src}/chains/avalancheFuji.ts (50%) rename interchain/{examples => src}/chains/dispatch.ts (72%) create mode 100644 interchain/src/chains/index.ts create mode 100644 interchain/src/chains/types/chainConfig.ts create mode 100644 interchain/src/icm/index.ts delete mode 100644 interchain/src/icm/types/chainConfig.ts create mode 100644 interchain/src/ictt/index.ts create mode 100644 interchain/src/ictt/types/approveTokenParams.ts create mode 100644 interchain/src/ictt/types/deployERC20Token.ts create mode 100644 interchain/src/ictt/types/deployTokenHomeContractParams.ts create mode 100644 interchain/src/ictt/types/deployTokenRemoteContractParams.ts create mode 100644 interchain/src/ictt/types/registerRemoteWithHomeParams.ts create mode 100644 interchain/src/ictt/types/sendTokenParams.ts create mode 100644 interchain/src/ictt/utils/approveToken.ts create mode 100644 interchain/src/ictt/utils/deployERC20Token.ts create mode 100644 interchain/src/ictt/utils/getERC20TokenInfo.ts create mode 100644 interchain/src/ictt/utils/getInfoFromTokenHome.ts create mode 100644 interchain/src/ictt/utils/index.ts diff --git a/interchain/README.md b/interchain/README.md index 5d2bbbb2..5b5655b4 100644 --- a/interchain/README.md +++ b/interchain/README.md @@ -21,7 +21,7 @@ The `@avalanche-sdk/interchain` package provides a developer-friendly TypeScript npm install @avalanche-sdk/interchain viem ``` -## Quickstart +## Sending Messages Cross-Chain Here’s a minimal example demonstrating how to send a cross-chain message using `createICMClient`: @@ -31,8 +31,7 @@ import { createICMClient } from "@avalanche-sdk/interchain"; import { privateKeyToAccount } from "viem/accounts"; // these will be made available in a separate SDK soon -import { avalancheFuji } from "./chains/avalancheFuji"; -import { dispatch } from "./chains/dispatch"; +import { avalancheFuji, dispatch } from "@avalanche-sdk/interchain/chains"; // Load your signer/account const account = privateKeyToAccount('0x63e0730edea86f6e9e95db48dbcab18406e60bebae45ad33e099f09d21450ebf'); @@ -58,3 +57,108 @@ async function main() { main(); ``` + +## Sending Tokens Cross-Chain + +Sending ERC20 tokens from one Avalanche L1 to another is possible using Teleporter. +Before sending tokens, we need to setup some contracts on both source and destination chains. Here's a quick summary of steps we'd need to follow: + +- Deploy ERC20 Token. +- Deploy `TokenHome` contract on the source chain. +- Deploy `TokenRemote` contract on the destination chain. +- Register `TokenRemote` on `TokenHome` by issuing a transaction on `TokenRemote` contract, which in turn will emit an event on the source chain. +- Approve `TokenHome` contract to spend (and hence lock) ERC20 tokens. +- Send ERC20 Token from source to destination chain. + +Here's a demo to do that with the Interchain SDK. + +```typescript +import { http, createWalletClient } from "viem"; +import { privateKeyToAccount } from "viem/accounts"; +import { avalancheFuji, dispatch } from "@avalanche-sdk/interchain/chains"; +import { createICTTClient } from "@avalanche-sdk/interchain"; + +const account = privateKeyToAccount('0x63e0730edea86f6e9e95db48dbcab18406e60bebae45ad33e099f09d21450ebf'); + +// We need separate wallet clients for each to do certain operations +const fujiWallet = createWalletClient({ + chain: avalancheFuji, + transport: http(), + account, +}) +const dispatchWallet = createWalletClient({ + chain: dispatch, + transport: http(), + account, +}) + +// We can create ICTT client with or without default source/destination chains +const ictt = createICTTClient(); + +async function main() { + // Deploy ERC20 token on Avalanche Fuji + const { contractAddress: tokenAddress } = await ictt.deployERC20Token({ + walletClient: fujiWallet, + sourceChain: avalancheFuji, + name: 'Test Token', + symbol: 'TEST', + initialSupply: 1000000, + }); + console.log(`Token deployed on Avalanche Fuji: ${tokenAddress}`); + + // Deploy Token Home Contract on Avalanche Fuji. This is one-time process for each token we + // want to send to another chain. + const { contractAddress: tokenHomeContract } = await ictt.deployTokenHomeContract({ + walletClient: fujiWallet, + sourceChain: avalancheFuji, + erc20TokenAddress: tokenAddress, + minimumTeleporterVersion: 1, + }); + console.log(`Token home contract deployed on Avalanche Fuji: ${tokenHomeContract}`); + + // Deploy Token Remote Contract on Dispatch. This is one-time process for each token we + // want to send to another chain. + const { contractAddress: tokenRemoteContract } = await ictt.deployTokenRemoteContract({ + walletClient: dispatchWallet, + sourceChain: avalancheFuji, + destinationChain: dispatch, + tokenHomeContract, + }); + console.log(`Token remote contract deployed on Dispatch: ${tokenRemoteContract}`); + + // Register Token Remote Contract with Token Home Contract on Dispatch. This is one-time process for each token we + // want to send to another chain. + const { txHash: registerRemoteWithHomeTxHash } = await ictt.registerRemoteWithHome({ + walletClient: dispatchWallet, + sourceChain: avalancheFuji, + destinationChain: dispatch, + tokenRemoteContract, + }) + console.log(`Token remote contract registered with home on Dispatch: ${registerRemoteWithHomeTxHash}`); + + // Approve token on Avalanche Fuji. This operation approves the HomeContract to + // lock token on Fuji as collateral for the interchain transfer. + const { txHash: approveTokenTxHash } = await ictt.approveToken({ + walletClient: fujiWallet, + sourceChain: avalancheFuji, + tokenHomeContract, + tokenAddress, + amountInBaseUnit: 2, + }) + console.log(`Token approved on Avalanche Fuji: ${approveTokenTxHash}`); + + // Send token from Avalanche Fuji to Dispatch. + const { txHash: sendTokenTxHash } = await ictt.sendToken({ + walletClient: fujiWallet, + sourceChain: avalancheFuji, + destinationChain: dispatch, + tokenHomeContract, + tokenRemoteContract, + amountInBaseUnit: 1, + recipient: '0x909d71Ed4090ac6e57E3645dcF2042f8c6548664', + }) + console.log(`Token sent from Avalanche Fuji to Dispatch: ${sendTokenTxHash}`); +} + +main().catch(console.error); +``` \ No newline at end of file diff --git a/interchain/examples/sendICM.ts b/interchain/examples/sendICM.ts index 80bb9c98..2d9f38f2 100644 --- a/interchain/examples/sendICM.ts +++ b/interchain/examples/sendICM.ts @@ -1,8 +1,8 @@ import { createWalletClient, http } from "viem"; import { createICMClient } from "../src/icm/icm"; import { privateKeyToAccount } from "viem/accounts"; -import { avalancheFuji } from "./chains/avalancheFuji"; -import { dispatch } from "./chains/dispatch"; +import { avalancheFuji } from "../src/chains/avalancheFuji"; +import { dispatch } from "../src/chains/dispatch"; const account = privateKeyToAccount('0x63e0730edea86f6e9e95db48dbcab18406e60bebae45ad33e099f09d21450ebf'); diff --git a/interchain/examples/sendTokens.ts b/interchain/examples/sendTokens.ts new file mode 100644 index 00000000..53e7c3a0 --- /dev/null +++ b/interchain/examples/sendTokens.ts @@ -0,0 +1,81 @@ +import { createWalletClient } from "viem"; +import { http } from "viem"; +import { createICTTClient } from "../src/ictt/ictt"; +import { privateKeyToAccount } from "viem/accounts"; +import { avalancheFuji, dispatch } from "../src/chains"; + +const account = privateKeyToAccount('0x63e0730edea86f6e9e95db48dbcab18406e60bebae45ad33e099f09d21450ebf'); + +const fujiWallet = createWalletClient({ + chain: avalancheFuji, + transport: http(), + account, +}) + +const dispatchWallet = createWalletClient({ + chain: dispatch, + transport: http(), + account, +}) + +const ictt = createICTTClient( + avalancheFuji, + dispatch, +); + +async function main() { + const { contractAddress: tokenAddress } = await ictt.deployERC20Token({ + walletClient: fujiWallet, + sourceChain: avalancheFuji, + name: 'Test Token', + symbol: 'TEST', + initialSupply: 1000000, + }); + console.log(`Token deployed on Avalanche Fuji: ${tokenAddress}`); + + const { contractAddress: tokenHomeContract } = await ictt.deployTokenHomeContract({ + walletClient: fujiWallet, + sourceChain: avalancheFuji, + erc20TokenAddress: tokenAddress, + minimumTeleporterVersion: 1, + }); + console.log(`Token home contract deployed on Avalanche Fuji: ${tokenHomeContract}`); + + const { contractAddress: tokenRemoteContract } = await ictt.deployTokenRemoteContract({ + walletClient: dispatchWallet, + sourceChain: avalancheFuji, + destinationChain: dispatch, + tokenHomeContract, + }); + console.log(`Token remote contract deployed on Dispatch: ${tokenRemoteContract}`); + + const { txHash: registerRemoteWithHomeTxHash } = await ictt.registerRemoteWithHome({ + walletClient: dispatchWallet, + sourceChain: avalancheFuji, + destinationChain: dispatch, + tokenRemoteContract, + }) + console.log(`Token remote contract registered with home on Dispatch: ${registerRemoteWithHomeTxHash}`); + + const { txHash: approveTokenTxHash } = await ictt.approveToken({ + walletClient: fujiWallet, + sourceChain: avalancheFuji, + tokenHomeContract, + tokenAddress, + amountInBaseUnit: 2, + }) + console.log(`Token approved on Avalanche Fuji: ${approveTokenTxHash}`); + + const { txHash: sendTokenTxHash } = await ictt.sendToken({ + walletClient: fujiWallet, + sourceChain: avalancheFuji, + destinationChain: dispatch, + tokenHomeContract, + tokenRemoteContract, + amountInBaseUnit: 1, + recipient: '0x909d71Ed4090ac6e57E3645dcF2042f8c6548664', + }) + console.log(`Token sent from Avalanche Fuji to Dispatch: ${sendTokenTxHash}`); +} + +main().catch(console.error); \ No newline at end of file diff --git a/interchain/package-lock.json b/interchain/package-lock.json new file mode 100644 index 00000000..cc2b21e4 --- /dev/null +++ b/interchain/package-lock.json @@ -0,0 +1,524 @@ +{ + "name": "@avalanche-sdk/interchain", + "version": "0.0.1-alpha.1", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "@avalanche-sdk/interchain", + "version": "0.0.1-alpha.1", + "license": "ISC", + "dependencies": { + "@avalanche-sdk/rpc": "^0.0.4-alpha.1", + "viem": "^2.33.1" + }, + "devDependencies": { + "@types/node": "^20.0.0", + "typescript": "^5.0.0" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@adraffy/ens-normalize": { + "version": "1.11.0", + "resolved": "https://registry.npmjs.org/@adraffy/ens-normalize/-/ens-normalize-1.11.0.tgz", + "integrity": "sha512-/3DDPKHqqIqxUULp8yP4zODUY1i+2xvVWsv8A79xGWdCAG+8sb0hRh0Rk2QyOJUnnbyPUAZYcpBuRe3nS2OIUg==" + }, + "node_modules/@avalabs/avalanchejs": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/@avalabs/avalanchejs/-/avalanchejs-5.0.0.tgz", + "integrity": "sha512-0hJK/Hdf8v+q05c8+5K6arFmzq7o1W4I05/Dmr+Es1XRi8canvTu1Y0RruYd6ea2rrvX3UhKrPs3BzLhCTHDrw==", + "dependencies": { + "@noble/curves": "1.3.0", + "@noble/hashes": "1.3.3", + "@noble/secp256k1": "2.0.0", + "@scure/base": "1.1.5", + "micro-eth-signer": "0.7.2" + }, + "engines": { + "node": ">=20" + } + }, + "node_modules/@avalabs/avalanchejs/node_modules/@noble/curves": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/@noble/curves/-/curves-1.3.0.tgz", + "integrity": "sha512-t01iSXPuN+Eqzb4eBX0S5oubSqXbK/xXa1Ne18Hj8f9pStxztHCE2gfboSp/dZRLSqfuLpRK2nDXDK+W9puocA==", + "license": "MIT", + "dependencies": { + "@noble/hashes": "1.3.3" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/@avalabs/avalanchejs/node_modules/@noble/hashes": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-1.3.3.tgz", + "integrity": "sha512-V7/fPHgl+jsVPXqqeOzT8egNj2iBIVt+ECeMMG8TdcnTikP3oaBtUVqpT/gYCR68aEBJSF+XbYUxStjbFMqIIA==", + "license": "MIT", + "engines": { + "node": ">= 16" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/@avalabs/avalanchejs/node_modules/@scure/base": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/@scure/base/-/base-1.1.5.tgz", + "integrity": "sha512-Brj9FiG2W1MRQSTB212YVPRrcbjkv48FoZi/u4l/zds/ieRrqsh7aUf6CLwkAq61oKXr/ZlTzlY66gLIj3TFTQ==", + "license": "MIT", + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/@avalanche-sdk/rpc": { + "version": "0.0.4-alpha.1", + "resolved": "https://registry.npmjs.org/@avalanche-sdk/rpc/-/rpc-0.0.4-alpha.1.tgz", + "integrity": "sha512-pg4cZu+rzcOjqwG61wIsAfKPp4KhXNNFV4chdd9ptOWJrTEqFhV31CrcNi8TzabFzqUmcxi5/kCLeX36srLgPQ==", + "license": "MIT", + "dependencies": { + "@avalabs/avalanchejs": "^5.0.0", + "@noble/hashes": "1.3.3", + "viem": "2.30.5" + }, + "engines": { + "node": ">=20", + "npm": ">=10" + } + }, + "node_modules/@avalanche-sdk/rpc/node_modules/@noble/curves": { + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/@noble/curves/-/curves-1.9.1.tgz", + "integrity": "sha512-k11yZxZg+t+gWvBbIswW0yoJlu8cHOC7dhunwOzoWH/mXGBiYyR4YY6hAEK/3EUs4UpB8la1RfdRpeGsFHkWsA==", + "license": "MIT", + "dependencies": { + "@noble/hashes": "1.8.0" + }, + "engines": { + "node": "^14.21.3 || >=16" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/@avalanche-sdk/rpc/node_modules/@noble/curves/node_modules/@noble/hashes": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-1.8.0.tgz", + "integrity": "sha512-jCs9ldd7NwzpgXDIf6P3+NrHh9/sD6CQdxHyjQI+h/6rDNo88ypBxxz45UDuZHz9r3tNz7N/VInSVoVdtXEI4A==", + "license": "MIT", + "engines": { + "node": "^14.21.3 || >=16" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/@avalanche-sdk/rpc/node_modules/@noble/hashes": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-1.3.3.tgz", + "integrity": "sha512-V7/fPHgl+jsVPXqqeOzT8egNj2iBIVt+ECeMMG8TdcnTikP3oaBtUVqpT/gYCR68aEBJSF+XbYUxStjbFMqIIA==", + "license": "MIT", + "engines": { + "node": ">= 16" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/@avalanche-sdk/rpc/node_modules/ox": { + "version": "0.7.1", + "resolved": "https://registry.npmjs.org/ox/-/ox-0.7.1.tgz", + "integrity": "sha512-+k9fY9PRNuAMHRFIUbiK9Nt5seYHHzSQs9Bj+iMETcGtlpS7SmBzcGSVUQO3+nqGLEiNK4598pHNFlVRaZbRsg==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/wevm" + } + ], + "license": "MIT", + "dependencies": { + "@adraffy/ens-normalize": "^1.10.1", + "@noble/ciphers": "^1.3.0", + "@noble/curves": "^1.6.0", + "@noble/hashes": "^1.5.0", + "@scure/bip32": "^1.5.0", + "@scure/bip39": "^1.4.0", + "abitype": "^1.0.6", + "eventemitter3": "5.0.1" + }, + "peerDependencies": { + "typescript": ">=5.4.0" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@avalanche-sdk/rpc/node_modules/ox/node_modules/@noble/hashes": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-1.8.0.tgz", + "integrity": "sha512-jCs9ldd7NwzpgXDIf6P3+NrHh9/sD6CQdxHyjQI+h/6rDNo88ypBxxz45UDuZHz9r3tNz7N/VInSVoVdtXEI4A==", + "license": "MIT", + "engines": { + "node": "^14.21.3 || >=16" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/@avalanche-sdk/rpc/node_modules/viem": { + "version": "2.30.5", + "resolved": "https://registry.npmjs.org/viem/-/viem-2.30.5.tgz", + "integrity": "sha512-YymUl7AKsIw3BhQLZxr3j+g8OwqsxmV3xu7zDMmmuFACtvQ3YZaFsKrH7N8eTXpPHYgMlClvKIjgXS8Twt+sQQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/wevm" + } + ], + "license": "MIT", + "dependencies": { + "@noble/curves": "1.9.1", + "@noble/hashes": "1.8.0", + "@scure/bip32": "1.7.0", + "@scure/bip39": "1.6.0", + "abitype": "1.0.8", + "isows": "1.0.7", + "ox": "0.7.1", + "ws": "8.18.2" + }, + "peerDependencies": { + "typescript": ">=5.0.4" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@avalanche-sdk/rpc/node_modules/viem/node_modules/@noble/hashes": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-1.8.0.tgz", + "integrity": "sha512-jCs9ldd7NwzpgXDIf6P3+NrHh9/sD6CQdxHyjQI+h/6rDNo88ypBxxz45UDuZHz9r3tNz7N/VInSVoVdtXEI4A==", + "license": "MIT", + "engines": { + "node": "^14.21.3 || >=16" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/@ethereumjs/rlp": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/@ethereumjs/rlp/-/rlp-5.0.0.tgz", + "integrity": "sha512-WuS1l7GJmB0n0HsXLozCoEFc9IwYgf3l0gCkKVYgR67puVF1O4OpEaN0hWmm1c+iHUHFCKt1hJrvy5toLg+6ag==", + "license": "MPL-2.0", + "bin": { + "rlp": "bin/rlp" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@noble/ciphers": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/@noble/ciphers/-/ciphers-1.3.0.tgz", + "integrity": "sha512-2I0gnIVPtfnMw9ee9h1dJG7tp81+8Ob3OJb3Mv37rx5L40/b0i7djjCVvGOVqc9AEIQyvyu1i6ypKdFw8R8gQw==", + "engines": { + "node": "^14.21.3 || >=16" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/@noble/curves": { + "version": "1.9.2", + "resolved": "https://registry.npmjs.org/@noble/curves/-/curves-1.9.2.tgz", + "integrity": "sha512-HxngEd2XUcg9xi20JkwlLCtYwfoFw4JGkuZpT+WlsPD4gB/cxkvTD8fSsoAnphGZhFdZYKeQIPCuFlWPm1uE0g==", + "dependencies": { + "@noble/hashes": "1.8.0" + }, + "engines": { + "node": "^14.21.3 || >=16" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/@noble/hashes": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-1.8.0.tgz", + "integrity": "sha512-jCs9ldd7NwzpgXDIf6P3+NrHh9/sD6CQdxHyjQI+h/6rDNo88ypBxxz45UDuZHz9r3tNz7N/VInSVoVdtXEI4A==", + "engines": { + "node": "^14.21.3 || >=16" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/@noble/secp256k1": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@noble/secp256k1/-/secp256k1-2.0.0.tgz", + "integrity": "sha512-rUGBd95e2a45rlmFTqQJYEFA4/gdIARFfuTuTqLglz0PZ6AKyzyXsEZZq7UZn8hZsvaBgpCzKKBJizT2cJERXw==", + "funding": [ + { + "type": "individual", + "url": "https://paulmillr.com/funding/" + } + ], + "license": "MIT" + }, + "node_modules/@scure/base": { + "version": "1.2.6", + "resolved": "https://registry.npmjs.org/@scure/base/-/base-1.2.6.tgz", + "integrity": "sha512-g/nm5FgUa//MCj1gV09zTJTaM6KBAHqLN907YVQqf7zC49+DcO4B1so4ZX07Ef10Twr6nuqYEH9GEggFXA4Fmg==", + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/@scure/bip32": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/@scure/bip32/-/bip32-1.7.0.tgz", + "integrity": "sha512-E4FFX/N3f4B80AKWp5dP6ow+flD1LQZo/w8UnLGYZO674jS6YnYeepycOOksv+vLPSpgN35wgKgy+ybfTb2SMw==", + "dependencies": { + "@noble/curves": "~1.9.0", + "@noble/hashes": "~1.8.0", + "@scure/base": "~1.2.5" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/@scure/bip39": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/@scure/bip39/-/bip39-1.6.0.tgz", + "integrity": "sha512-+lF0BbLiJNwVlev4eKelw1WWLaiKXw7sSl8T6FvBlWkdX+94aGJ4o8XjUdlyhTCjd8c+B3KT3JfS8P0bLRNU6A==", + "dependencies": { + "@noble/hashes": "~1.8.0", + "@scure/base": "~1.2.5" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/@types/node": { + "version": "20.19.9", + "resolved": "https://registry.npmjs.org/@types/node/-/node-20.19.9.tgz", + "integrity": "sha512-cuVNgarYWZqxRJDQHEB58GEONhOK79QVR/qYx4S7kcUObQvUwvFnYxJuuHUKm2aieN9X3yZB4LZsuYNU1Qphsw==", + "dev": true, + "license": "MIT", + "dependencies": { + "undici-types": "~6.21.0" + } + }, + "node_modules/abitype": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/abitype/-/abitype-1.0.8.tgz", + "integrity": "sha512-ZeiI6h3GnW06uYDLx0etQtX/p8E24UaHHBj57RSjK7YBFe7iuVn07EDpOeP451D06sF27VOz9JJPlIKJmXgkEg==", + "funding": { + "url": "https://github.com/sponsors/wevm" + }, + "peerDependencies": { + "typescript": ">=5.0.4", + "zod": "^3 >=3.22.0" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + }, + "zod": { + "optional": true + } + } + }, + "node_modules/eventemitter3": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-5.0.1.tgz", + "integrity": "sha512-GWkBvjiSZK87ELrYOSESUYeVIc9mvLLf/nXalMOS5dYrgZq9o5OVkbZAVM06CVxYsCwH9BDZFPlQTlPA1j4ahA==" + }, + "node_modules/isows": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/isows/-/isows-1.0.7.tgz", + "integrity": "sha512-I1fSfDCZL5P0v33sVqeTDSpcstAg/N+wF5HS033mogOVIp4B+oHC7oOCsA3axAbBSGTJ8QubbNmnIRN/h8U7hg==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/wevm" + } + ], + "peerDependencies": { + "ws": "*" + } + }, + "node_modules/micro-eth-signer": { + "version": "0.7.2", + "resolved": "https://registry.npmjs.org/micro-eth-signer/-/micro-eth-signer-0.7.2.tgz", + "integrity": "sha512-uFH23nqPNdg2KZ9ZdvLG4GO3bTAOWRhwGTsecY4Et2IdQOJ26x6inu8lJ9oyslnYL/0o1vnETCGhMimMvO0SqQ==", + "license": "MIT", + "dependencies": { + "@ethereumjs/rlp": "5.0.0", + "@noble/curves": "~1.3.0", + "@noble/hashes": "~1.3.3", + "@scure/base": "~1.1.5", + "micro-packed": "~0.5.1" + } + }, + "node_modules/micro-eth-signer/node_modules/@noble/curves": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/@noble/curves/-/curves-1.3.0.tgz", + "integrity": "sha512-t01iSXPuN+Eqzb4eBX0S5oubSqXbK/xXa1Ne18Hj8f9pStxztHCE2gfboSp/dZRLSqfuLpRK2nDXDK+W9puocA==", + "license": "MIT", + "dependencies": { + "@noble/hashes": "1.3.3" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/micro-eth-signer/node_modules/@noble/hashes": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-1.3.3.tgz", + "integrity": "sha512-V7/fPHgl+jsVPXqqeOzT8egNj2iBIVt+ECeMMG8TdcnTikP3oaBtUVqpT/gYCR68aEBJSF+XbYUxStjbFMqIIA==", + "license": "MIT", + "engines": { + "node": ">= 16" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/micro-eth-signer/node_modules/@scure/base": { + "version": "1.1.9", + "resolved": "https://registry.npmjs.org/@scure/base/-/base-1.1.9.tgz", + "integrity": "sha512-8YKhl8GHiNI/pU2VMaofa2Tor7PJRAjwQLBBuilkJ9L5+13yVbC7JO/wS7piioAvPSwR3JKM1IJ/u4xQzbcXKg==", + "license": "MIT", + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/micro-packed": { + "version": "0.5.3", + "resolved": "https://registry.npmjs.org/micro-packed/-/micro-packed-0.5.3.tgz", + "integrity": "sha512-zWRoH+qUb/ZMp9gVZhexvRGCENDM5HEQF4sflqpdilUHWK2/zKR7/MT8GBctnTwbhNJwy1iuk5q6+TYP7/twYA==", + "license": "MIT", + "dependencies": { + "@scure/base": "~1.1.5" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/micro-packed/node_modules/@scure/base": { + "version": "1.1.9", + "resolved": "https://registry.npmjs.org/@scure/base/-/base-1.1.9.tgz", + "integrity": "sha512-8YKhl8GHiNI/pU2VMaofa2Tor7PJRAjwQLBBuilkJ9L5+13yVbC7JO/wS7piioAvPSwR3JKM1IJ/u4xQzbcXKg==", + "license": "MIT", + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/ox": { + "version": "0.8.1", + "resolved": "https://registry.npmjs.org/ox/-/ox-0.8.1.tgz", + "integrity": "sha512-e+z5epnzV+Zuz91YYujecW8cF01mzmrUtWotJ0oEPym/G82uccs7q0WDHTYL3eiONbTUEvcZrptAKLgTBD3u2A==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/wevm" + } + ], + "dependencies": { + "@adraffy/ens-normalize": "^1.11.0", + "@noble/ciphers": "^1.3.0", + "@noble/curves": "^1.9.1", + "@noble/hashes": "^1.8.0", + "@scure/bip32": "^1.7.0", + "@scure/bip39": "^1.6.0", + "abitype": "^1.0.8", + "eventemitter3": "5.0.1" + }, + "peerDependencies": { + "typescript": ">=5.4.0" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/typescript": { + "version": "5.8.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.8.3.tgz", + "integrity": "sha512-p1diW6TqL9L07nNxvRMM7hMMw4c5XOo/1ibL4aAIGmSAt9slTE1Xgw5KWuof2uTOvCg9BY7ZRi+GaF+7sfgPeQ==", + "devOptional": true, + "license": "Apache-2.0", + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, + "node_modules/undici-types": { + "version": "6.21.0", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.21.0.tgz", + "integrity": "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/viem": { + "version": "2.33.1", + "resolved": "https://registry.npmjs.org/viem/-/viem-2.33.1.tgz", + "integrity": "sha512-++Dkj8HvSOLPMKEs+ZBNNcWbBRlUHcXNWktjIU22hgr6YmbUldV1sPTGLZa6BYRm06WViMjXj6HIsHt8rD+ZKQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/wevm" + } + ], + "license": "MIT", + "dependencies": { + "@noble/curves": "1.9.2", + "@noble/hashes": "1.8.0", + "@scure/bip32": "1.7.0", + "@scure/bip39": "1.6.0", + "abitype": "1.0.8", + "isows": "1.0.7", + "ox": "0.8.1", + "ws": "8.18.2" + }, + "peerDependencies": { + "typescript": ">=5.0.4" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/ws": { + "version": "8.18.2", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.18.2.tgz", + "integrity": "sha512-DMricUmwGZUVr++AEAe2uiVM7UoO9MAVZMDu05UQOaUII0lp+zOzLLU4Xqh/JvTqklB1T4uELaaPBKyjE1r4fQ==", + "engines": { + "node": ">=10.0.0" + }, + "peerDependencies": { + "bufferutil": "^4.0.1", + "utf-8-validate": ">=5.0.2" + }, + "peerDependenciesMeta": { + "bufferutil": { + "optional": true + }, + "utf-8-validate": { + "optional": true + } + } + } + } +} diff --git a/interchain/package.json b/interchain/package.json index e8fa38a0..12e8996e 100644 --- a/interchain/package.json +++ b/interchain/package.json @@ -2,7 +2,12 @@ "name": "@avalanche-sdk/interchain", "version": "0.0.1-alpha.1", "description": "Interchain package for handling ICM/ICTT messages", - "main": "index.js", + "type": "module", + "main": "./dist/index.js", + "types": "./dist/index.d.ts", + "engines": { + "node": ">=20.0.0" + }, "scripts": { "build": "npx tsc" }, @@ -24,11 +29,33 @@ }, "homepage": "https://github.com/ava-labs/avalanche-sdk-typescript#readme", "dependencies": { - "@avalanche-sdk/rpc": "^0.0.4-alpha.1", - "viem": "^2.31.7" + "@avalanche-sdk/rpc": "^0.0.4-alpha.1" + }, + "peerDependencies": { + "viem": "^2.33.1" }, "devDependencies": { "@types/node": "^20.0.0", "typescript": "^5.0.0" + }, + "sideEffects": false, + "exports": { + ".": "./dist/index.js", + "./chains": "./dist/chains/index.js", + "./icm": "./dist/icm/index.js", + "./ictt": "./dist/ictt/index.js" + }, + "typesVersions": { + "*": { + "chains": [ + "dist/chains/index.d.ts" + ], + "icm": [ + "dist/icm/index.d.ts" + ], + "ictt": [ + "dist/ictt/index.d.ts" + ] + } } } diff --git a/interchain/examples/chains/avalancheFuji.ts b/interchain/src/chains/avalancheFuji.ts similarity index 50% rename from interchain/examples/chains/avalancheFuji.ts rename to interchain/src/chains/avalancheFuji.ts index 6a54e602..442eac3a 100644 --- a/interchain/examples/chains/avalancheFuji.ts +++ b/interchain/src/chains/avalancheFuji.ts @@ -1,7 +1,12 @@ import { defineChain } from 'viem'; import { avalancheFuji as avalancheFujiBase } from 'viem/chains'; +import { ChainConfig } from './types/chainConfig'; export const avalancheFuji = defineChain({ ...avalancheFujiBase, blockchainId: '0x7fc93d85c6d62c5b2ac0b519c87010ea5294012d1e407030d6acd0021cac10d5', -}); + interchainContracts: { + teleporterRegistry: '0xF86Cb19Ad8405AEFa7d09C778215D2Cb6eBfB228', + teleporterManager: '0x253b2784c75e510dD0fF1da844684a1aC0aa5fcf', + } +}) as ChainConfig; diff --git a/interchain/examples/chains/dispatch.ts b/interchain/src/chains/dispatch.ts similarity index 72% rename from interchain/examples/chains/dispatch.ts rename to interchain/src/chains/dispatch.ts index cb95e7a7..978e3eb4 100644 --- a/interchain/examples/chains/dispatch.ts +++ b/interchain/src/chains/dispatch.ts @@ -1,4 +1,5 @@ import { defineChain } from "viem"; +import { ChainConfig } from "./types/chainConfig"; export const dispatch = defineChain({ id: 779672, @@ -22,5 +23,9 @@ export const dispatch = defineChain({ address: '0x083e276d96ce818f2225d901b44e358dcfc5d606' } }, + interchainContracts: { + teleporterRegistry: '0xF86Cb19Ad8405AEFa7d09C778215D2Cb6eBfB228', + teleporterManager: '0x253b2784c75e510dD0fF1da844684a1aC0aa5fcf', + }, testnet: true -}); \ No newline at end of file +}) as ChainConfig; \ No newline at end of file diff --git a/interchain/src/chains/index.ts b/interchain/src/chains/index.ts new file mode 100644 index 00000000..f3e86e27 --- /dev/null +++ b/interchain/src/chains/index.ts @@ -0,0 +1,2 @@ +export { avalancheFuji } from "./avalancheFuji"; +export { dispatch } from "./dispatch"; diff --git a/interchain/src/chains/types/chainConfig.ts b/interchain/src/chains/types/chainConfig.ts new file mode 100644 index 00000000..d2335d17 --- /dev/null +++ b/interchain/src/chains/types/chainConfig.ts @@ -0,0 +1,10 @@ +import { Address } from "viem"; +import { Chain } from "viem/chains"; + +export interface ChainConfig extends Chain { + blockchainId: string; + interchainContracts: { + teleporterRegistry: Address; + teleporterManager: Address; + } +} \ No newline at end of file diff --git a/interchain/src/icm/icm.ts b/interchain/src/icm/icm.ts index e34a25cb..6d358fa7 100644 --- a/interchain/src/icm/icm.ts +++ b/interchain/src/icm/icm.ts @@ -1,24 +1,44 @@ -import { type WalletClient, encodeAbiParameters, parseAbiParameters } from 'viem'; +import { WalletClient, encodeAbiParameters, parseAbiParameters } from 'viem'; import { teleporterABI } from '../abis/teleporterABI'; -import { ChainConfig } from './types/chainConfig'; +import { ChainConfig } from '../chains/types/chainConfig'; import { SendMessageArgs } from './types/sendMsgArgs'; import { DefaultICMParams } from './types/defaultICMParams'; import { DEFAULT_TELEPORTER_ADDRESS } from './consts'; import { TeleporterMessageInput } from './types/contractMessageInput'; +/** + * Inter-Chain Messaging (ICM) client for sending cross-chain messages via Teleporter protocol. + * + * This class provides functionality to send messages between different blockchains + * using the Teleporter bridge protocol. It handles message encoding, fee management, + * and contract interactions. +*/ export class ICM { + /** The Teleporter contract address for cross-chain messaging */ teleporterAddress: `0x${string}`; + + /** The viem's wallet client for signing and sending transactions */ wallet: WalletClient; - // Default hoisted values + /** Default recipient address when none is specified (zero address) */ defaultRecipient: '0x0000000000000000000000000000000000000000'; + + /** Source blockchain configuration */ sourceChain: ChainConfig | undefined; + + /** Destination blockchain configuration */ destinationChain: ChainConfig | undefined; + + /** Default fee information for cross-chain messages */ feeInfo = { feeTokenAddress: '0x0000000000000000000000000000000000000000', amount: 0n } + + /** Default gas limit required for message execution on destination chain */ requiredGasLimit = 100000n; + + /** List of allowed relayer addresses for message delivery */ allowedRelayerAddresses: string[] = []; constructor( @@ -37,14 +57,38 @@ export class ICM { this.teleporterAddress = DEFAULT_TELEPORTER_ADDRESS; } + /** + * Sends a cross-chain message to the specified destination blockchain. + * + * This method encodes the message, constructs the Teleporter message input, + * and submits the transaction to the Teleporter contract on the source chain. + * + * @param params - Parameters for sending the cross-chain message + * @param params.message - The message content to send (will be encoded as string) + * @param params.sourceChain - Source blockchain configuration (required if not set in constructor) + * @param params.destinationChain - Destination blockchain configuration (required if not set in constructor) + * @param params.recipientAddress - Optional recipient address on destination chain (defaults to zero address) + * @param params.feeInfo - Optional fee information (overrides default if provided) + * @param params.requiredGasLimit - Optional gas limit (overrides default if provided) + * @param params.allowedRelayerAddresses - Optional relayer addresses (overrides default if provided) + * + * @returns Promise - The transaction hash of the submitted cross-chain message + */ async sendMsg(params: SendMessageArgs) { const encodedMessage: string = encodeAbiParameters( parseAbiParameters('string'), [params.message] ); + const sourceChain = params.sourceChain ?? this.sourceChain; + const destinationChain = params.destinationChain ?? this.destinationChain; + + if (!sourceChain || !destinationChain) { + throw new Error('Source or destination chain not set or not provided in the params'); + } + const messageInput: TeleporterMessageInput = { - destinationBlockchainID: params.destinationChain.blockchainId, + destinationBlockchainID: sourceChain.blockchainId, destinationAddress: params.recipientAddress || this.defaultRecipient, feeInfo: params.feeInfo || this.feeInfo, requiredGasLimit: params.requiredGasLimit || this.requiredGasLimit, @@ -57,7 +101,7 @@ export class ICM { abi: teleporterABI, functionName: 'sendCrossChainMessage', args: [messageInput], - chain: params.sourceChain, + chain: sourceChain, account: this.wallet.account ?? null, }); @@ -65,6 +109,17 @@ export class ICM { } } +/** + * Creates a new ICM client instance. + * + * @param wallet - The Viem's wallet client for signing and sending transactions + * @param sourceChain - Optional source blockchain configuration. If not provided, must be specified in sendMsg calls + * @param destinationChain - Optional destination blockchain configuration. If not provided, must be specified in sendMsg calls + * @param defaultParams - Optional default parameters for cross-chain messaging + * @param defaultParams.feeInfo - Default fee information (token address and amount) + * @param defaultParams.requiredGasLimit - Default gas limit for message execution + * @param defaultParams.allowedRelayerAddresses - Default list of allowed relayer addresses + */ export function createICMClient( wallet: WalletClient, sourceChain?: ChainConfig, diff --git a/interchain/src/icm/index.ts b/interchain/src/icm/index.ts new file mode 100644 index 00000000..624d9dde --- /dev/null +++ b/interchain/src/icm/index.ts @@ -0,0 +1,3 @@ +export { ICM, createICMClient } from './icm' +export type { DefaultICMParams } from './types/defaultICMParams' +export type { SendMessageArgs } from './types/sendMsgArgs' diff --git a/interchain/src/icm/types/chainConfig.ts b/interchain/src/icm/types/chainConfig.ts deleted file mode 100644 index 75b8dd6b..00000000 --- a/interchain/src/icm/types/chainConfig.ts +++ /dev/null @@ -1,5 +0,0 @@ -import { Chain } from "viem/chains"; - -export interface ChainConfig extends Chain { - blockchainId: string; -} \ No newline at end of file diff --git a/interchain/src/icm/types/defaultICMParams.ts b/interchain/src/icm/types/defaultICMParams.ts index 63996750..7be51e04 100644 --- a/interchain/src/icm/types/defaultICMParams.ts +++ b/interchain/src/icm/types/defaultICMParams.ts @@ -1,9 +1,25 @@ - +/** + * Default parameters for ICM client + * + * These parameters define the default configuration for cross-chain messaging + * using the Teleporter protocol. These params can be overridden in the client's + * sendMsg() call. +*/ export type DefaultICMParams = { + /** + * Default fee information rewarded to the relayer + * of this message + */ feeInfo?: { feeTokenAddress: string; amount: bigint; }; + /** + * Default gas limit for message execution + */ requiredGasLimit?: bigint; + /** + * Default list of allowed relayer addresses + */ allowedRelayerAddresses?: string[]; } \ No newline at end of file diff --git a/interchain/src/icm/types/sendMsgArgs.ts b/interchain/src/icm/types/sendMsgArgs.ts index 25096c1d..35c601f7 100644 --- a/interchain/src/icm/types/sendMsgArgs.ts +++ b/interchain/src/icm/types/sendMsgArgs.ts @@ -1,10 +1,10 @@ import { Prettify } from "viem"; -import { ChainConfig } from "./chainConfig"; +import { ChainConfig } from "../../chains/types/chainConfig"; import { DefaultICMParams } from "./defaultICMParams"; export type SendMessageArgs = Prettify<{ - sourceChain: ChainConfig; - destinationChain: ChainConfig; + sourceChain?: ChainConfig; + destinationChain?: ChainConfig; message: string; recipientAddress?: `0x${string}`; } & DefaultICMParams>; diff --git a/interchain/src/ictt/ictt.ts b/interchain/src/ictt/ictt.ts index ac820afd..dde5b267 100644 --- a/interchain/src/ictt/ictt.ts +++ b/interchain/src/ictt/ictt.ts @@ -1,140 +1,338 @@ -import { Address, WalletClient } from "viem"; -import { ChainConfig } from "../icm/types/chainConfig"; +import { createPublicClient, http } from "viem"; +import { ChainConfig } from "../chains/types/chainConfig"; import { tokenHomeABI } from "../abis/tokenHomeABI"; -import { erc20ABI } from "../abis/erc20ABI"; import { DEFAULT_TELEPORTER_ADDRESS } from "../icm/consts"; import { tokenRemoteABI } from "../abis/tokenRemoteABI"; +import { getERC20TokenInfo } from "./utils/getERC20TokenInfo"; +import { getInfoFromTokenHomeContract } from "./utils/getInfoFromTokenHome"; +import { DeployTokenHomeContractParams } from "./types/deployTokenHomeContractParams"; +import { DeployTokenRemoteContractParams } from "./types/deployTokenRemoteContractParams"; +import { RegisterRemoteWithHomeParams } from "./types/registerRemoteWithHomeParams"; +import { SendTokenParams } from "./types/sendTokenParams"; +import { deployERC20Token } from "./utils/deployERC20Token"; +import { DeployERC20TokenParams } from "./types/deployERC20Token"; +import { ApproveTokenParams } from "./types/approveTokenParams"; +import { approveToken } from "./utils/approveToken"; -// TODO: Add dev docs export class ICTT { - private walletClient: WalletClient; private sourceChain: ChainConfig | undefined; private destinationChain: ChainConfig | undefined; private gasLimit: bigint; constructor( - walletClient: WalletClient, sourceChain?: ChainConfig, destinationChain?: ChainConfig, gasLimit?: bigint, ) { - this.walletClient = walletClient; this.sourceChain = sourceChain; this.destinationChain = destinationChain; this.gasLimit = gasLimit ?? BigInt(250000); } - async approveToken( - tokenHomeContract: `0x${string}`, - tokenAddress: `0x${string}`, - amountInBaseUnit: number, - sourceChain: ChainConfig | undefined, - ) { - const amountInWei = BigInt(amountInBaseUnit) * BigInt(10 ** 18); - const approveTxHash = await this.walletClient.writeContract({ - address: tokenAddress, - abi: erc20ABI.abi, - functionName: "approve", - args: [tokenHomeContract, amountInWei], - chain: sourceChain ?? this.sourceChain, - account: this.walletClient.account ?? null, + /** + * Deploys a new ERC20 token on the source chain. + * @param params - The parameters for deploying the ERC20 token. + * @param params.walletClient - The wallet client to use for deploying the ERC20 token. + * @param params.sourceChain - The source chain to deploy the ERC20 token on. + * @param params.name - The name of the token. + * @param params.symbol - The symbol of the token. + * @param params.initialSupply - The initial supply of the token. + * @param params.recipient - The recipient of the newly minted tokens. + * @returns The transaction hash and the contract address of the deployed ERC20 token. + */ + async deployERC20Token(params: DeployERC20TokenParams): Promise<{ + txHash: `0x${string}`, + contractAddress: `0x${string}`, + }> { + const sourceChain = params.sourceChain ?? this.sourceChain; + if (!sourceChain) { + throw new Error('Source chain not set.'); + } + const recipient = params.recipient ?? params.walletClient.account?.address; + if (!recipient) { + throw new Error('Recipient not set or not present in wallet client.'); + } + return deployERC20Token( + params.walletClient, + sourceChain, + params.name, + params.symbol, + params.initialSupply, + recipient, + ); + } + + /** + * Approves the token for the token home contract on the source chain. + * @param params - The parameters for approving the token. + * @param params.walletClient - The wallet client to use for approving the token. + * @param params.sourceChain - The source chain to approve the token on. + * @param params.tokenHomeContract - The address of the token home contract to approve the token for. + * @param params.tokenAddress - The address of the token to approve. + * @param params.amountInBaseUnit - The amount of the token to approve. + * @returns The transaction hash of the approval. + */ + async approveToken(params: ApproveTokenParams): Promise<{ + txHash: `0x${string}`, + }> { + const sourceChain = params.sourceChain ?? this.sourceChain; + if (!sourceChain) { + throw new Error('Source chain not set.'); + } + return approveToken({ + walletClient: params.walletClient, + chain: sourceChain, + spenderAddress: params.tokenHomeContract, + tokenAddress: params.tokenAddress, + amountInBaseUnit: params.amountInBaseUnit, }); - return approveTxHash; } - async deployTokenHomeContract( - sourceChain: ChainConfig | undefined, - teleporterRegistry: Address, - erc20TokenAddress: Address, - erc20TokenDecimals: number, - minimumTeleporterVersion: number, - ) { - return await this.walletClient.deployContract({ - abi: tokenHomeABI.abi, - bytecode: tokenHomeABI.bytecode as `0x${string}`, + /** + * Deploy a token home contract on the source chain. + * @param params - The parameters for deploying the token home contract. + * @param params.walletClient - The wallet client to use for deploying the token home contract. + * @param params.sourceChain - The source chain to deploy the token home contract on. + * @param params.erc20TokenAddress - The address of the ERC20 token to be used as the token home contract. + * @param params.minimumTeleporterVersion - The minimum teleporter version required for the token home contract. + * @param params.tokenHomeCustomByteCode - Optional. The customized bytecode for the token home contract. + * @param params.tokenHomeCustomABI - Optional. The customized ABI for the token home contract. + * @returns The transaction hash and the contract address of the deployed token home contract. + */ + async deployTokenHomeContract(params: DeployTokenHomeContractParams): Promise<{ + txHash: `0x${string}`, + contractAddress: `0x${string}`, + }> { + const sourceChain = params.sourceChain ?? this.sourceChain; + if (!sourceChain) { + throw new Error('Source chain not set.'); + } + const publicClient = createPublicClient({ + chain: sourceChain, + transport: http(), + }); + + const { tokenDecimals } = await getERC20TokenInfo(sourceChain, params.erc20TokenAddress); + + const txHash = await params.walletClient.deployContract({ + abi: params.tokenHomeCustomABI ?? tokenHomeABI.abi, + bytecode: params.tokenHomeCustomByteCode ?? tokenHomeABI.bytecode as `0x${string}`, args: [ - teleporterRegistry, + sourceChain.interchainContracts.teleporterRegistry, DEFAULT_TELEPORTER_ADDRESS, - minimumTeleporterVersion, - erc20TokenAddress, - erc20TokenDecimals, + BigInt(params.minimumTeleporterVersion), + params.erc20TokenAddress, + tokenDecimals, ], - chain: sourceChain ?? this.sourceChain ?? null, - account: this.walletClient.account ?? null, + chain: sourceChain, + account: params.walletClient.account ?? null, }); + + const receipt = await publicClient.waitForTransactionReceipt({ hash: txHash }); + if (receipt.status === 'success' && receipt.contractAddress) { + return { + txHash, + contractAddress: receipt.contractAddress, + } + } else { + throw new Error('Failed to deploy token home contract.', { cause: receipt }); + } } - async deployTokenRemoteContract( - destinationChain: ChainConfig | undefined, - teleporterRegistry: Address, - minimumTeleporterVersion: number, - tokenHomeBlockchainID: string, - tokenHomeAddress: Address, - tokenHomeDecimals: number, - tokenName: string, - tokenSymbol: string, - destinationTokenDecimals: number, - ) { - return await this.walletClient.deployContract({ - abi: tokenRemoteABI.abi, - bytecode: tokenRemoteABI.bytecode as `0x${string}`, + /** + * Deploy a token remote contract on the destination chain. + * @param params - The parameters for deploying the token remote contract. + * @param params.walletClient - The wallet client to use for deploying the token remote contract. + * @param params.sourceChain - The source chain to deploy the token remote contract on. + * @param params.destinationChain - The destination chain to deploy the token remote contract on. + * @param params.tokenHomeContract - The address of the token home contract to be used as the token remote contract. + * @param params.tokenRemoteCustomByteCode - Optional. The customized bytecode for the token remote contract. + * @param params.tokenRemoteCustomABI - Optional. The customized ABI for the token remote contract. + * @returns The transaction hash and the contract address of the deployed token remote contract. + */ + async deployTokenRemoteContract(params: DeployTokenRemoteContractParams): Promise<{ + txHash: `0x${string}`, + contractAddress: `0x${string}`, + }> { + const sourceChain = params.sourceChain ?? this.sourceChain; + if (!sourceChain) { + throw new Error('Source chain not set.'); + } + const destinationChain = params.destinationChain ?? this.destinationChain; + if (!destinationChain) { + throw new Error('Destination chain not set.'); + } + + const publicClient = createPublicClient({ + chain: destinationChain, + transport: http(), + }); + + const { + tokenName, + tokenSymbol, + tokenDecimals, + minTeleporterVersion, + } = await getInfoFromTokenHomeContract( + sourceChain, + params.tokenHomeContract, + ); + + const txHash = await params.walletClient.deployContract({ + abi: params.tokenRemoteCustomABI ?? tokenRemoteABI.abi, + bytecode: params.tokenRemoteCustomByteCode ?? tokenRemoteABI.bytecode as `0x${string}`, args: [ { - teleporterRegistryAddress: teleporterRegistry, + teleporterRegistryAddress: destinationChain.interchainContracts.teleporterRegistry, teleporterManager: DEFAULT_TELEPORTER_ADDRESS, - minTeleporterVersion: BigInt(minimumTeleporterVersion), - tokenHomeBlockchainID: tokenHomeBlockchainID, - tokenHomeAddress: tokenHomeAddress, - tokenHomeDecimals: Number(tokenHomeDecimals), // uint8 + minTeleporterVersion: BigInt(minTeleporterVersion), + tokenHomeBlockchainID: sourceChain.blockchainId, + tokenHomeAddress: params.tokenHomeContract, + tokenHomeDecimals: Number(tokenDecimals), }, tokenName, tokenSymbol, - Number(destinationTokenDecimals) + Number(tokenDecimals) ], - chain: destinationChain ?? this.destinationChain ?? null, - account: this.walletClient.account ?? null, + chain: destinationChain, + account: params.walletClient.account ?? null, }); + + const receipt = await publicClient.waitForTransactionReceipt({ hash: txHash }); + if (receipt.status === 'success' && receipt.contractAddress) { + return { + txHash, + contractAddress: receipt.contractAddress, + } + } else { + throw new Error('Failed to deploy token remote contract.', { cause: receipt }); + } } - async sendToken( - tokenHomeContract: Address, - tokenRemoteContract: Address, - recipient: Address, - tokenAddress: Address, - amountInBaseUnit: number, - sourceChain: ChainConfig | undefined, - destinationChain: ChainConfig | undefined, - ) { - const amountInWei = BigInt(amountInBaseUnit) * BigInt(10 ** 18); + /** + * Emits event from the TokenRemote contract to get it registered with the TokenHome contract on the source chain. + * @param params - The parameters for registering the token remote contract with the token home contract. + * @param params.walletClient - The wallet client to use for registering the token remote contract with the token home contract. + * @param params.sourceChain - The source chain to register the token remote contract with the token home contract on. + * @param params.destinationChain - The destination chain to register the token remote contract with the token home contract on. + * @param params.tokenRemoteContract - The address of the token remote contract to be registered with the token home contract. + * @param params.feeTokenAddress - Optional. The address of the fee token to be used for the registration. + * @param params.feeAmount - Optional. The amount of the fee to be used for the registration. + * @returns The transaction hash of the registration. + */ + async registerRemoteWithHome(params: RegisterRemoteWithHomeParams): Promise<{ + txHash: `0x${string}`, + }> { + const sourceChain = params.sourceChain ?? this.sourceChain; + if (!sourceChain) { + throw new Error('Source chain not set.'); + } + const destinationChain = params.destinationChain ?? this.destinationChain; + if (!destinationChain) { + throw new Error('Destination chain not set.'); + } + const publicClient = createPublicClient({ + chain: destinationChain, + transport: http(), + }); + + const feeInfo = [ + params.feeTokenAddress ?? '0x0000000000000000000000000000000000000000', + params.feeAmount ?? 0, + ]; + + const txHash = await params.walletClient.writeContract({ + address: params.tokenRemoteContract, + abi: tokenRemoteABI.abi, + functionName: 'registerWithHome', + args: [feeInfo], + chain: destinationChain, + account: params.walletClient.account ?? null, + }); + + const receipt = await publicClient.waitForTransactionReceipt({ hash: txHash }); + if (receipt.status === 'success') { + return { txHash }; + } else { + throw new Error('Failed to register token remote contract with token home contract.', { cause: receipt }); + } + } + + /** + * Sends tokens from the TokenHome contract on the source chain to the TokenRemote contract on the destination chain. + * @param params - The parameters for sending the tokens. + * @param params.walletClient - The wallet client to use for sending the tokens. + * @param params.sourceChain - The source chain to send the tokens from. + * @param params.destinationChain - The destination chain to send the tokens to. + * @param params.tokenHomeContract - The address of the token home contract to send the tokens from. + * @param params.tokenRemoteContract - The address of the token remote contract to send the tokens to. + * @param params.recipient - The address of the recipient of the tokens. + * @param params.amountInBaseUnit - The amount of the tokens to be sent. + * @param params.feeTokenAddress - Optional. The address of the fee token to be used for the transfer. + * @param params.feeAmount - Optional. The amount of the fee to be used for the transfer. + * @returns The transaction hash of the transfer. + */ + async sendToken(params: SendTokenParams): Promise<{ + txHash: `0x${string}`, + }> { + const sourceChain = params.sourceChain ?? this.sourceChain; + if (!sourceChain) { + throw new Error('Source chain not set.'); + } + const destinationChain = params.destinationChain ?? this.destinationChain; + if (!destinationChain) { + throw new Error('Destination chain not set.'); + } + const publicClient = createPublicClient({ + chain: sourceChain, + transport: http(), + }); + + const { tokenContractAddress, tokenDecimals } = await getInfoFromTokenHomeContract( + sourceChain, + params.tokenHomeContract, + ); + const amountInWei = BigInt(params.amountInBaseUnit) * BigInt(10 ** tokenDecimals); const sendTokensInput = { - destinationBlockchainID: destinationChain?.blockchainId ?? this.destinationChain?.blockchainId, - destinationTokenTransferrerAddress: tokenRemoteContract, - recipient, - primaryFeeTokenAddress: tokenAddress, - primaryFee: 0, + destinationBlockchainID: destinationChain.blockchainId, + destinationTokenTransferrerAddress: params.tokenRemoteContract, + recipient: params.recipient, + primaryFeeTokenAddress: tokenContractAddress ?? '0x0000000000000000000000000000000000000000', + primaryFee: params.feeAmount ?? 0, secondaryFee: 0, requiredGasLimit: this.gasLimit, multiHopFallback: '0x0000000000000000000000000000000000000000', }; - const sendTxHash = await this.walletClient.writeContract({ - address: tokenHomeContract, + const txHash = await params.walletClient.writeContract({ + address: params.tokenHomeContract, abi: tokenHomeABI.abi, functionName: 'send', args: [sendTokensInput, amountInWei], chain: sourceChain, - account: this.walletClient.account ?? null, + account: params.walletClient.account ?? null, }); - return sendTxHash; + const receipt = await publicClient.waitForTransactionReceipt({ hash: txHash }); + if (receipt.status === 'success') { + return { txHash }; + } else { + throw new Error('Failed to send tokens.', { cause: receipt }); + } } } +/** + * Creates a new ICTT client. + * @param sourceChain - The default source chain to use for the client. + * @param destinationChain - The default destination chain to use for the client. + * @param gasLimit - The default gas limit to use for the client. + * @returns A new ICTT client. + */ export function createICTTClient( - walletClient: WalletClient, sourceChain?: ChainConfig, destinationChain?: ChainConfig, gasLimit?: bigint, ) { - return new ICTT(walletClient, sourceChain, destinationChain, gasLimit); + return new ICTT(sourceChain, destinationChain, gasLimit); } diff --git a/interchain/src/ictt/index.ts b/interchain/src/ictt/index.ts new file mode 100644 index 00000000..8988b359 --- /dev/null +++ b/interchain/src/ictt/index.ts @@ -0,0 +1,2 @@ +export { ICTT, createICTTClient } from './ictt'; +export * from './utils'; \ No newline at end of file diff --git a/interchain/src/ictt/types/approveTokenParams.ts b/interchain/src/ictt/types/approveTokenParams.ts new file mode 100644 index 00000000..7ca7229d --- /dev/null +++ b/interchain/src/ictt/types/approveTokenParams.ts @@ -0,0 +1,10 @@ +import { Address, WalletClient } from "viem"; +import { ChainConfig } from "../../chains/types/chainConfig"; + +export type ApproveTokenParams = { + walletClient: WalletClient, + sourceChain: ChainConfig | undefined, + tokenHomeContract: Address, + tokenAddress: Address, + amountInBaseUnit: number, +} diff --git a/interchain/src/ictt/types/deployERC20Token.ts b/interchain/src/ictt/types/deployERC20Token.ts new file mode 100644 index 00000000..c49e1c86 --- /dev/null +++ b/interchain/src/ictt/types/deployERC20Token.ts @@ -0,0 +1,11 @@ +import { WalletClient } from "viem" +import { ChainConfig } from "../../chains/types/chainConfig" +import { Address } from "viem" +export type DeployERC20TokenParams = { + walletClient: WalletClient, + sourceChain: ChainConfig | undefined, + name: string, + symbol: string, + initialSupply: number, + recipient?: Address, +} \ No newline at end of file diff --git a/interchain/src/ictt/types/deployTokenHomeContractParams.ts b/interchain/src/ictt/types/deployTokenHomeContractParams.ts new file mode 100644 index 00000000..a9f6f77b --- /dev/null +++ b/interchain/src/ictt/types/deployTokenHomeContractParams.ts @@ -0,0 +1,11 @@ +import { Abi, Address, WalletClient } from "viem"; +import { ChainConfig } from "../../chains/types/chainConfig"; + +export type DeployTokenHomeContractParams = { + walletClient: WalletClient, + sourceChain: ChainConfig | undefined, + erc20TokenAddress: Address, + minimumTeleporterVersion: number, + tokenHomeCustomByteCode?: `0x${string}`, + tokenHomeCustomABI?: Abi, +} diff --git a/interchain/src/ictt/types/deployTokenRemoteContractParams.ts b/interchain/src/ictt/types/deployTokenRemoteContractParams.ts new file mode 100644 index 00000000..a9c0da45 --- /dev/null +++ b/interchain/src/ictt/types/deployTokenRemoteContractParams.ts @@ -0,0 +1,11 @@ +import { Abi, Address, WalletClient } from "viem"; +import { ChainConfig } from "../../chains/types/chainConfig"; + +export type DeployTokenRemoteContractParams = { + walletClient: WalletClient, + sourceChain: ChainConfig | undefined, + destinationChain: ChainConfig | undefined, + tokenHomeContract: Address, + tokenRemoteCustomByteCode?: `0x${string}`, + tokenRemoteCustomABI?: Abi, +} diff --git a/interchain/src/ictt/types/registerRemoteWithHomeParams.ts b/interchain/src/ictt/types/registerRemoteWithHomeParams.ts new file mode 100644 index 00000000..557c264a --- /dev/null +++ b/interchain/src/ictt/types/registerRemoteWithHomeParams.ts @@ -0,0 +1,11 @@ +import { Address, WalletClient } from "viem"; +import { ChainConfig } from "../../chains/types/chainConfig"; + +export type RegisterRemoteWithHomeParams = { + walletClient: WalletClient, + sourceChain: ChainConfig | undefined, + destinationChain: ChainConfig | undefined, + tokenRemoteContract: Address, + feeTokenAddress?: Address, + feeAmount?: number, +} diff --git a/interchain/src/ictt/types/sendTokenParams.ts b/interchain/src/ictt/types/sendTokenParams.ts new file mode 100644 index 00000000..b7caec03 --- /dev/null +++ b/interchain/src/ictt/types/sendTokenParams.ts @@ -0,0 +1,14 @@ +import { Address, WalletClient } from "viem"; +import { ChainConfig } from "../../chains/types/chainConfig"; + +export type SendTokenParams = { + walletClient: WalletClient, + sourceChain: ChainConfig | undefined, + destinationChain: ChainConfig | undefined, + tokenHomeContract: Address, + tokenRemoteContract: Address, + recipient: Address, + amountInBaseUnit: number, + feeTokenAddress?: Address, + feeAmount?: number, +} diff --git a/interchain/src/ictt/utils/approveToken.ts b/interchain/src/ictt/utils/approveToken.ts new file mode 100644 index 00000000..fbdbfaf7 --- /dev/null +++ b/interchain/src/ictt/utils/approveToken.ts @@ -0,0 +1,35 @@ +import { createPublicClient, http, WalletClient } from "viem"; +import { erc20ABI } from "../../abis/erc20ABI"; +import { ChainConfig } from "../../chains/types/chainConfig"; +import { getERC20TokenInfo } from "./getERC20TokenInfo"; + +export async function approveToken(params: { + walletClient: WalletClient, + chain: ChainConfig, + spenderAddress: `0x${string}`, + tokenAddress: `0x${string}`, + amountInBaseUnit: number, +}) { + const publicClient = createPublicClient({ + chain: params.chain, + transport: http(), + }); + + const { tokenDecimals } = await getERC20TokenInfo(params.chain, params.tokenAddress); + const amountInWei = BigInt(params.amountInBaseUnit) * BigInt(10 ** tokenDecimals); + const approveTxHash = await params.walletClient.writeContract({ + address: params.tokenAddress, + abi: erc20ABI.abi, + functionName: "approve", + args: [params.spenderAddress, amountInWei], + chain: params.chain, + account: params.walletClient.account ?? null, + }); + + const receipt = await publicClient.waitForTransactionReceipt({ hash: approveTxHash }); + if (receipt.status === 'success') { + return { txHash: approveTxHash }; + } else { + throw new Error('Failed to approve token.', { cause: receipt }); + } +} \ No newline at end of file diff --git a/interchain/src/ictt/utils/deployERC20Token.ts b/interchain/src/ictt/utils/deployERC20Token.ts new file mode 100644 index 00000000..9608ec21 --- /dev/null +++ b/interchain/src/ictt/utils/deployERC20Token.ts @@ -0,0 +1,35 @@ +import { Address, createPublicClient, http, WalletClient } from "viem"; +import { erc20ABI } from "../../abis/erc20ABI"; +import { ChainConfig } from "../../chains/types/chainConfig"; + +export async function deployERC20Token( + walletClient: WalletClient, + chain: ChainConfig, + name: string, + symbol: string, + initialSupply: number, + recipient: Address, +) { + const publicClient = createPublicClient({ + chain, + transport: http(), + }); + + const txHash = await walletClient.deployContract({ + abi: erc20ABI.abi, + bytecode: erc20ABI.bytecode as `0x${string}`, + args: [name, symbol, initialSupply * 10 ** 18, recipient], + chain, + account: walletClient.account ?? null, + }); + + const receipt = await publicClient.waitForTransactionReceipt({ hash: txHash }) + if (receipt.status === 'success' && receipt.contractAddress) { + return { + txHash, + contractAddress: receipt.contractAddress, + }; + } else { + throw new Error('Failed to deploy ERC20 token.', { cause: receipt }); + } +} \ No newline at end of file diff --git a/interchain/src/ictt/utils/getERC20TokenInfo.ts b/interchain/src/ictt/utils/getERC20TokenInfo.ts new file mode 100644 index 00000000..cb05fa71 --- /dev/null +++ b/interchain/src/ictt/utils/getERC20TokenInfo.ts @@ -0,0 +1,37 @@ +import { Address } from "viem"; +import { createPublicClient, http } from "viem"; +import { erc20ABI } from "../../abis/erc20ABI"; +import { ChainConfig } from "../../chains/types/chainConfig"; + +export async function getERC20TokenInfo( + chain: ChainConfig, + erc20TokenAddress: Address, +) { + const sourcePublicClient = createPublicClient({ + chain, + transport: http(), + }); + + const [tokenName, tokenSymbol, tokenDecimals] = await Promise.all([ + sourcePublicClient.readContract({ + address: erc20TokenAddress, + abi: erc20ABI.abi, + functionName: "name", + }) as Promise, + sourcePublicClient.readContract({ + address: erc20TokenAddress, + abi: erc20ABI.abi, + functionName: "symbol", + }) as Promise, + sourcePublicClient.readContract({ + address: erc20TokenAddress, + abi: erc20ABI.abi, + functionName: "decimals", + }) as Promise, + ]); + return { + tokenName, + tokenSymbol, + tokenDecimals, + }; +} \ No newline at end of file diff --git a/interchain/src/ictt/utils/getInfoFromTokenHome.ts b/interchain/src/ictt/utils/getInfoFromTokenHome.ts new file mode 100644 index 00000000..e44b7828 --- /dev/null +++ b/interchain/src/ictt/utils/getInfoFromTokenHome.ts @@ -0,0 +1,45 @@ +import { Address } from "viem"; +import { createPublicClient, http } from "viem"; +import { tokenHomeABI } from "../../abis/tokenHomeABI"; +import { ChainConfig } from "../../chains/types/chainConfig"; +import { getERC20TokenInfo } from "./getERC20TokenInfo"; + +export async function getInfoFromTokenHomeContract( + chain: ChainConfig, + tokenHomeAddress: Address, +) { + const sourcePublicClient = createPublicClient({ + chain, + transport: http(), + }); + + const [tokenContractAddress, minTeleporterVersion] = await Promise.all([ + sourcePublicClient.readContract({ + address: tokenHomeAddress, + abi: tokenHomeABI.abi, + functionName: "getTokenAddress", + }) as Promise
, + sourcePublicClient.readContract({ + address: tokenHomeAddress, + abi: tokenHomeABI.abi, + functionName: "getMinTeleporterVersion", + }) as Promise, + ]); + + const { + tokenName, + tokenSymbol, + tokenDecimals, + } = await getERC20TokenInfo( + chain, + tokenContractAddress, + ); + + return { + tokenContractAddress, + tokenName, + tokenSymbol, + tokenDecimals, + minTeleporterVersion, + }; +} diff --git a/interchain/src/ictt/utils/index.ts b/interchain/src/ictt/utils/index.ts new file mode 100644 index 00000000..2e5ac2a5 --- /dev/null +++ b/interchain/src/ictt/utils/index.ts @@ -0,0 +1,4 @@ +export { approveToken } from './approveToken'; +export { deployERC20Token } from './deployERC20Token'; +export { getERC20TokenInfo } from './getERC20TokenInfo'; +export { getInfoFromTokenHomeContract } from './getInfoFromTokenHome'; \ No newline at end of file diff --git a/interchain/src/index.ts b/interchain/src/index.ts index 7b58602e..7e54bead 100644 --- a/interchain/src/index.ts +++ b/interchain/src/index.ts @@ -1 +1,3 @@ -export { createICMClient } from './icm/icm'; +export * from './chains' +export * from './icm' +export * from './ictt' diff --git a/interchain/tsconfig.json b/interchain/tsconfig.json index 8281ca42..5e20adb0 100644 --- a/interchain/tsconfig.json +++ b/interchain/tsconfig.json @@ -2,11 +2,11 @@ "compilerOptions": { "incremental": true, "tsBuildInfoFile": ".tsbuildinfo", - "target": "ES2020", + "target": "ES2022", "lib": ["ES2022", "DOM", "DOM.Iterable"], "jsx": "react-jsx", - "module": "ES2020", + "module": "ES2022", "moduleResolution": "bundler", "allowJs": true, From 8b9776f026e679dc7addb13391a4feb4f3c71b8a Mon Sep 17 00:00:00 2001 From: Raj Ranjan Date: Fri, 1 Aug 2025 18:32:17 +0530 Subject: [PATCH 2/2] nit --- interchain/README.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/interchain/README.md b/interchain/README.md index 5b5655b4..c6f31828 100644 --- a/interchain/README.md +++ b/interchain/README.md @@ -75,9 +75,11 @@ Here's a demo to do that with the Interchain SDK. ```typescript import { http, createWalletClient } from "viem"; import { privateKeyToAccount } from "viem/accounts"; -import { avalancheFuji, dispatch } from "@avalanche-sdk/interchain/chains"; import { createICTTClient } from "@avalanche-sdk/interchain"; +// These will be made available in separate SDK +import { avalancheFuji, dispatch } from "@avalanche-sdk/interchain/chains"; + const account = privateKeyToAccount('0x63e0730edea86f6e9e95db48dbcab18406e60bebae45ad33e099f09d21450ebf'); // We need separate wallet clients for each to do certain operations