@avalanche-sdk/interchain
Version:
Interchain package for handling ICM/ICTT messages
268 lines (204 loc) • 9.61 kB
Markdown
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
```bash
npm install @avalanche-sdk/interchain viem
```
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 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);
```
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.
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.
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();
```
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.