UNPKG

@avalanche-sdk/interchain

Version:
268 lines (204 loc) 9.61 kB
# Interchain Interchain Messaging (ICM) made simple for Avalanche Subnet ecosystems. The `@avalanche-sdk/interchain` package provides a developer-friendly TypeScript SDK to send and manage cross-chain messages between Avalanche and its subnets using the Interchain Messaging (ICM) protocol. ## Features - Type-safe ICM client for sending cross-chain messages - Works seamlessly with [`viem`](https://viem.sh/) wallet clients - Built-in support for Avalanche C-Chain and custom subnets - Cross-Chain ERC20 tokens transfer (ICTT) - Warp Message Building ## Requirements - Node.js >= 20.0.0 - TypeScript >= 5.0.0 ## Installation ```bash npm install @avalanche-sdk/interchain viem ``` ## Sending Messages Cross-Chain Here’s a minimal example demonstrating how to send a cross-chain message using `createICMClient`: ```ts import { createWalletClient, http } from "viem"; import { createICMClient } from "@avalanche-sdk/interchain"; import { privateKeyToAccount } from "viem/accounts"; // these will be made available in a separate SDK soon import { avalancheFuji, dispatch } from "@avalanche-sdk/interchain/chains"; // Load your signer/account const account = privateKeyToAccount('0x63e0730edea86f6e9e95db48dbcab18406e60bebae45ad33e099f09d21450ebf'); // Create a viem wallet client connected to Avalanche Fuji const wallet = createWalletClient({ transport: http('https://api.avax-test.network/ext/bc/C/rpc'), account, }); // Initialize the ICM client const icmClient = createICMClient(wallet); // Send a message across chains async function main() { const hash = await icmClient.sendMsg({ sourceChain: avalancheFuji, destinationChain: dispatch, message: 'Hello from Avalanche Fuji to Dispatch Fuji!', }); console.log('Message sent with hash:', hash); } 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 { 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 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); ``` ## ReactJS Examples We have also included few examples to integrate the `interchain` SDK with frontend libraries like ReactJS. Run the following commands to setup the React example repository. See examples [here](./examples/react-examples/) ```bash cd examples/react-examples npm install npm run dev ``` Now visit the local website to interact with the ICM and ICTT examples. ## Warp Message Parsing The SDK provides utilities for parsing and working with Warp messages, which are used for cross-chain communication in the Avalanche network. Warp messages are signed messages that can be verified across different chains. ### Message Structure A Warp message consists of several components: - Network ID - Source Chain ID - Addressed Call Payload - Source Address - Message Payload (specific to the message type) - BitSet Signatures For more details on the format, refer to original [AvalancheGo docs](https://github.com/ava-labs/avalanchego/blob/ae36212/vms/platformvm/warp/README.md) ### Supported Message Types The SDK currently supports parsing the following types of AddressedCall payload messages: 1. **RegisterL1ValidatorMessage** 2. **L1ValidatorWeightMessage** 3. **L1ValidatorRegistrationMessage** 4. **SubnetToL1ConversionMessage** For more details on the format of message types, refer to original [ACP 77](https://github.com/avalanche-foundation/ACPs/blob/58c78c/ACPs/77-reinventing-subnets/README.md#p-chain-warp-message-payloads) ### Working with Warp Messages You can parse Warp messages from their hex representation and access their components. The SDK provides type-safe methods to work with different message types. For detailed examples of working with Warp messages, see the [warp examples directory](https://github.com/ava-labs/avalanche-sdk-typescript/tree/main/interchain/examples/warp). ### Example Usage Message types and related builder functions can imported from `/warp` sub-path as shown below. ```typescript import { WarpMessage, RegisterL1ValidatorMessage } from "@avalanche-sdk/interchain/warp"; const signedWarpMsgHex = '<SIGNED_MSG_HEX>' // Parse a signed Warp message const signedWarpMsg = WarpMessage.fromHex(signedWarpMsgHex); // Parse message from signed message, or AddressedCall payload, or the actual message const registerL1ValidatorMsg = RegisterL1ValidatorMessage.fromHex(signedWarpMsgHex); // Convert back to hex const hexBytes = registerL1ValidatorMsg.toHex(); ``` ### Building Unsigned Messages You can also build the unsigned messages like `RegisterL1ValidatorMessage` or `L1ValidatorWeightMessage`. ```typescript import { AddressedCall, L1ValidatorWeightMessage, WarpUnsignedMessage } from "@avalanche-sdk/interchain/warp"; // building the L1ValidatorWeight message using values const newL1ValidatorWeightMsg = L1ValidatorWeightMessage.fromValues( '251q44yFiimeVSHaQbBk69TzoeYqKu9VagGtLVqo92LphUxjmR', 4n, 41n, ) // building AddressedCall payload from the above message const addressedCallPayload = AddressedCall.fromValues( '0x35F884853114D298D7aA8607f4e7e0DB52205f07', newL1ValidatorWeightMsg.toHex() ) // building WarpUnsignedMessage from the above addressed call payload const warpUnsignedMessage = WarpUnsignedMessage.fromValues( 1, '251q44yFiimeVSHaQbBk69TzoeYqKu9VagGtLVqo92LphUxjmR', addressedCallPayload.toHex() ) ``` For more detailed examples and use cases, refer to the [warp examples](https://github.com/ava-labs/avalanche-sdk-typescript/tree/main/interchain/examples/warp) in the repository.