Axelar
Axelar is a cross-chain communication network that connects blockchains through a proof-of-stake validator set. It provides two core primitives: General Message Passing (GMP) for sending arbitrary contract calls across chains, and Interchain Token Service (ITS) for deploying and managing tokens that exist natively on multiple chains. Contracts receive cross-chain messages by inheriting AxelarExecutable and implementing the _execute() callback, which the Axelar Gateway invokes after validator consensus. Gas for destination-chain execution must be prepaid on the source chain via AxelarGasService.
What You Probably Got Wrong
AI agents frequently confuse Axelar's chain naming, gas payment model, and ITS vs. Gateway token transfers. These corrections are critical.
- Chain identifiers are STRINGS, not numeric IDs. Axelar uses human-readable chain names like
"ethereum","arbitrum","base". These are NOT EVM chain IDs (1, 42161, 8453) and NOT LayerZero eids. Passing a numeric chain ID will silently fail or route to a nonexistent chain. Always use the exact string from Axelar's chain registry. - Gas MUST be paid upfront on the source chain. Unlike protocols with automatic relayers, Axelar requires you to call
AxelarGasService.payNativeGasForContractCall()(or theWithTokenvariant) BEFORE callinggateway.callContract(). If you skip gas payment, the message is submitted to the network but never executed on the destination. There is no retry mechanism -- you must send a new transaction. _execute()is the callback, NOTexecute(). Your contract inheritsAxelarExecutableand overrides_execute(string calldata sourceChain, string calldata sourceAddress, bytes calldata payload). The publicexecute()function is called by the Axelar relayer and routes to your_execute()after Gateway validation. Never overrideexecute()directly.- You MUST validate
sourceChainandsourceAddressin_execute(). The Gateway validates that the message came through Axelar, but it does NOT validate the sender's identity. Anyone can send a GMP message. Your_execute()must check thatsourceChainandsourceAddressmatch your trusted remote contract. Without this, any contract on any chain can trigger your_execute(). - ITS is NOT the same as Gateway token transfers.
gateway.callContractWithToken()moves Axelar-wrapped tokens (axlUSDC, axlWETH). ITS (InterchainTokenService) deploys and manages tokens that are canonical on every chain -- burn on source, mint on destination with the same token address derivation. Use ITS for new token deployments; use Gateway for existing Axelar-wrapped assets. sourceAddressis a STRING, not an address type. Cross-chain messages come from non-EVM chains too. ThesourceAddressparameter in_execute()is astring, which is the lowercase hex representation of the sender address. To compare with an EVM address, convert:keccak256(bytes(sourceAddress)) == keccak256(bytes(Strings.toHexString(trustedAddress))).- ITS token IDs are deterministic, derived from the deployer and salt. The
tokenIdfor an ITS-deployed token iskeccak256(abi.encode(address(deployer), salt)). If you deploy from different addresses or with different salts on different chains, the tokens will NOT be linked. Always use the same deployer and salt on every chain. callContractWithTokenrequires the token to be an Axelar-supported asset. You cannot send arbitrary ERC-20s throughcallContractWithToken. Only tokens registered in the Axelar Gateway (like axlUSDC, axlWETH, WBTC, etc.) are supported. For arbitrary tokens, use ITS or bridge first.- Gas refunds go to the
refundAddress, notmsg.sender. When paying gas viaAxelarGasService, excess gas is refunded to therefundAddressparameter. If you passaddress(0)or a contract that cannot receive ETH, the refund is lost.
Quick Start
Installation
npm install @axelar-network/axelar-gmp-sdk-solidity @openzeppelin/contracts
For Foundry projects:
forge install axelarnetwork/axelar-gmp-sdk-solidity
For the Axelar SDK (TypeScript):
npm install @axelar-network/axelarjs-sdk viem
Minimal GMP Contract
// SPDX-License-Identifier: MIT
pragma solidity 0.8.22;
import {AxelarExecutable} from "@axelar-network/axelar-gmp-sdk-solidity/contracts/executable/AxelarExecutable.sol";
import {IAxelarGateway} from "@axelar-network/axelar-gmp-sdk-solidity/contracts/interfaces/IAxelarGateway.sol";
import {IAxelarGasService} from "@axelar-network/axelar-gmp-sdk-solidity/contracts/interfaces/IAxelarGasService.sol";
contract MyGMPReceiver is AxelarExecutable {
IAxelarGasService public immutable GAS_SERVICE;
/// @dev Trusted remote: sourceChain -> sourceAddress (lowercase hex string)
mapping(string => string) public trustedRemotes;
event MessageReceived(string sourceChain, string sourceAddress, bytes payload);
event MessageSent(string destinationChain, string destinationAddress, bytes payload);
error UntrustedRemote(string sourceChain, string sourceAddress);
error InsufficientGasPayment();
constructor(
address gateway_,
address gasService_
) AxelarExecutable(gateway_) {
GAS_SERVICE = IAxelarGasService(gasService_);
}
/// @notice Sends a GMP message to a destination chain
/// @param destinationChain Axelar chain name (e.g., "arbitrum")
/// @param destinationAddress Contract address on destination (string)
/// @param payload Arbitrary encoded data
function sendMessage(
string calldata destinationChain,
string calldata destinationAddress,
bytes calldata payload
) external payable {
if (msg.value == 0) revert InsufficientGasPayment();
GAS_SERVICE.payNativeGasForContractCall{value: msg.value}(
address(this),
destinationChain,
destinationAddress,
payload,
msg.sender
);
gateway().callContract(destinationChain, destinationAddress, payload);
emit MessageSent(destinationChain, destinationAddress, payload);
}
/// @dev Called by the Axelar Gateway after validator consensus
function _execute(
string calldata sourceChain,
string calldata sourceAddress,
bytes calldata payload
) internal override {
// Validate the sender is a trusted remote
string memory trusted = trustedRemotes[sourceChain];
if (keccak256(bytes(trusted)) != keccak256(bytes(sourceAddress))) {
revert UntrustedRemote(sourceChain, sourceAddress);
}
emit MessageReceived(sourceChain, sourceAddress, payload);
}
/// @notice Sets a trusted remote contract for a given chain
/// @param chain Axelar chain name
/// @param remoteAddress Lowercase hex address string of the remote contract
function setTrustedRemote(
string calldata chain,
string calldata remoteAddress
) external {
trustedRemotes[chain] = remoteAddress;
}
}
Client Setup (TypeScript)
import {
createPublicClient,
createWalletClient,
http,
parseAbi,
type Address,
} from "viem";
import { privateKeyToAccount } from "viem/accounts";
import { mainnet, arbitrum } from "viem/chains";
const account = privateKeyToAccount(
process.env.PRIVATE_KEY as `0x${string}`
);
const ethereumClient = createPublicClient({
chain: mainnet,
transport: http(process.env.ETHEREUM_RPC_URL),
});
const walletClient = createWalletClient({
account,
chain: mainnet,
transport: http(process.env.ETHEREUM_RPC_URL),
});
Core Concepts
Architecture Overview
Source Chain Destination Chain
+-------------+ +-------------+
| Your | callContract() | Your |
| Contract | -----> Gateway | Contract |
+-------------+ | +-------------+
^ | ^
| v | _execute()
+-------------+ +----------+ +----------+
| GasService | | Axelar | | Gateway |
| (pay gas) | | Network | | |
+-------------+ +----------+ +----------+
| ^
v |
+-----------+ +-----------+
| Validators| | Relayer |
| (PoS) | --------> | (execute) |
+-----------+ +-----------+
Gateway
The on-chain entry point for Axelar GMP. One per chain. Handles callContract() for sending messages and validates incoming messages before invoking _execute() on receiving contracts.
GasService
Pays for destination-chain execution. You call payNativeGasForContractCall() with native tokens on the source chain. The Axelar relayer uses these funds to pay for gas on the destination chain. Excess is refunded to the refundAddress.
AxelarExecutable
The base contract your GMP receiver inherits. Provides the execute() entry point (called by the relayer) and the _execute() hook (overridden by you). The execute() function validates the message through the Gateway before calling _execute().
Interchain Token Service (ITS)
Deploys tokens that exist natively on multiple chains. Unlike wrapped tokens, ITS tokens are canonical on every chain -- same tokenId, same supply accounting, burn-on-source/mint-on-destination. ITS handles the cross-chain token transfer lifecycle.
Interchain Token Factory
A convenience contract for deploying ITS tokens. Provides deployInterchainToken() for new tokens and deployRemoteInterchainToken() to extend an existing ITS token to a new chain.
General Message Passing (GMP)
Sending a Message
/// @notice GMP message flow: pay gas, then call Gateway
function sendCrossChain(
string calldata destChain,
string calldata destAddress,
bytes calldata payload
) external payable {
if (msg.value == 0) revert InsufficientGasPayment();
// Step 1: Pay gas on source chain
GAS_SERVICE.payNativeGasForContractCall{value: msg.value}(
address(this), // sender
destChain, // "arbitrum", "base", etc.
destAddress, // "0x1234..." as string
payload,
msg.sender // refundAddress for excess gas
);
// Step 2: Submit message to Gateway
gateway().callContract(destChain, destAddress, payload);
}
Sending a Message with Token
import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
/// @notice GMP + token transfer (Axelar-wrapped tokens only)
function sendWithToken(
string calldata destChain,
string calldata destAddress,
bytes calldata payload,
string calldata symbol,
uint256 amount
) external payable {
if (msg.value == 0) revert InsufficientGasPayment();
address tokenAddress = gateway().tokenAddresses(symbol);
IERC20(tokenAddress).transferFrom(msg.sender, address(this), amount);
IERC20(tokenAddress).approve(address(gateway()), amount);
GAS_SERVICE.payNativeGasForContractCallWithToken{value: msg.value}(
address(this),
destChain,
destAddress,
payload,
symbol,
amount,
msg.sender
);
gateway().callContractWithToken(
destChain,
destAddress,
payload,
symbol,
amount
);
}
Receiving a Message
/// @dev Override for GMP messages without tokens
function _execute(
string calldata sourceChain,
string calldata sourceAddress,
bytes calldata payload
) internal override {
// ALWAYS validate the remote sender
_validateRemote(sourceChain, sourceAddress);
(address recipient, uint256 amount) = abi.decode(payload, (address, uint256));
// Process the decoded data
}
/// @dev Override for GMP messages WITH tokens
function _executeWithToken(
string calldata sourceChain,
string calldata sourceAddress,
bytes calldata payload,
string calldata tokenSymbol,
uint256 amount
) internal override {
_validateRemote(sourceChain, sourceAddress);
address tokenAddress = gateway().tokenAddresses(tokenSymbol);
(address recipient) = abi.decode(payload, (address));
// CEI: effects before interactions
// (state updates would go here)
IERC20(tokenAddress).transfer(recipient, amount);
}
Trusted Remote Validation Pattern
mapping(string => string) public trustedRemotes;
error UntrustedRemote(string chain, string sender);
function _validateRemote(
string calldata sourceChain,
string calldata sourceAddress
) internal view {
string memory trusted = trustedRemotes[sourceChain];
if (bytes(trusted).length == 0) {
revert UntrustedRemote(sourceChain, sourceAddress);
}
if (keccak256(bytes(trusted)) != keccak256(bytes(sourceAddress))) {
revert UntrustedRemote(sourceChain, sourceAddress);
}
}
/// @notice Register a trusted remote contract
/// @dev sourceAddress must be the lowercase hex string of the contract address
function setTrustedRemote(
string calldata chain,
string calldata addr
) external onlyOwner {
trustedRemotes[chain] = addr;
}
Interchain Token Service (ITS)
Deploy a New Interchain Token
ITS tokens are deployed through the InterchainTokenFactory. The same salt and deployer address must be used on every chain to produce linked tokens.
// SPDX-License-Identifier: MIT
pragma solidity 0.8.22;
import {IInterchainTokenFactory} from "@axelar-network/interchain-token-service/contracts/interfaces/IInterchainTokenFactory.sol";
import {IInterchainTokenService} from "@axelar-network/interchain-token-service/contracts/interfaces/IInterchainTokenService.sol";
contract TokenDeployer {
IInterchainTokenFactory public immutable FACTORY;
IInterchainTokenService public immutable ITS;
constructor(address factory_, address its_) {
FACTORY = IInterchainTokenFactory(factory_);
ITS = IInterchainTokenService(its_);
}
/// @notice Deploy a new interchain token on this chain
/// @param salt Unique salt (must be the same on all chains)
/// @param name Token name
/// @param symbol Token symbol
/// @param decimals Token decimals
/// @param initialSupply Mint this amount to msg.sender on this chain
function deployToken(
bytes32 salt,
string calldata name,
string calldata symbol,
uint8 decimals,
uint256 initialSupply
) external payable returns (bytes32 tokenId) {
tokenId = FACTORY.deployInterchainToken(
salt,
name,
symbol,
decimals,
initialSupply,
msg.sender
);
}
/// @notice Deploy this token remotely on another chain
/// @param salt Same salt used in the original deployment
/// @param destinationChain Axelar chain name to deploy to
/// @param gasValue Native token to pay for remote deployment gas
function deployRemote(
bytes32 salt,
string calldata destinationChain,
uint256 gasValue
) external payable returns (bytes32 tokenId) {
tokenId = FACTORY.deployRemoteInterchainToken{value: gasValue}(
"",
salt,
msg.sender,
destinationChain,
gasValue
);
}
}
Send ITS Tokens Cross-Chain
const itsAbi = parseAbi([
"function interchainTransfer(bytes32 tokenId, string calldata destinationChain, bytes calldata destinationAddress, uint256 amount, bytes calldata metadata, uint256 gasValue) payable",
"function interchainTransferFrom(address sender, bytes32 tokenId, string calldata destinationChain, bytes calldata destinationAddress, uint256 amount, bytes calldata metadata, uint256 gasValue) payable",
]);
const ITS_ADDRESS = "0xB5FB4BE02232B1bBA4dC8f81dc24C26980dE9e3C" as const;
const TOKEN_ID = "0x..." as `0x${string}`; // from deployInterchainToken
const AMOUNT = 1000_000000000000000000n; // 1000 tokens, 18 decimals
// Estimate gas for the cross-chain transfer
const gasValue = 500000000000000n; // 0.0005 ETH -- use Axelar gas estimator
const { request } = await ethereumClient.simulateContract({
address: ITS_ADDRESS,
abi: itsAbi,
functionName: "interchainTransfer",
args: [
TOKEN_ID,
"arbitrum",
`0x${account.address.slice(2).padStart(64, "0")}` as `0x${string}`,
AMOUNT,
"0x" as `0x${string}`,
gasValue,
],
value: gasValue,
account: account.address,
});
const hash = await walletClient.writeContract(request);
const receipt = await ethereumClient.waitForTransactionReceipt({ hash });
if (receipt.status !== "success") throw new Error("ITS transfer reverted");
Register an Existing ERC-20 with ITS
For existing tokens that cannot be modified, use registerCanonicalInterchainToken:
/// @notice Register an existing ERC-20 as a canonical interchain token
/// @param tokenAddress The existing ERC-20 on this chain
function registerExisting(address tokenAddress) external returns (bytes32 tokenId) {
tokenId = FACTORY.registerCanonicalInterchainToken(tokenAddress);
}
/// @notice Deploy a remote representation of the canonical token
/// @param tokenAddress The original token on this chain
/// @param destinationChain Target chain name
/// @param gasValue Gas payment for remote deployment
function deployCanonicalRemote(
address tokenAddress,
string calldata destinationChain,
uint256 gasValue
) external payable returns (bytes32 tokenId) {
tokenId = FACTORY.deployRemoteCanonicalInterchainToken{value: gasValue}(
"",
tokenAddress,
destinationChain,
gasValue
);
}
Gas Estimation
Using the Axelar SDK
import { AxelarQueryAPI, Environment } from "@axelar-network/axelarjs-sdk";
const axelarQuery = new AxelarQueryAPI({
environment: Environment.MAINNET,
});
async function estimateGasFee(
sourceChain: string,
destinationChain: string,
gasLimit: bigint
): Promise<bigint> {
const gasFee = await axelarQuery.estimateGasFee(
sourceChain,
destinationChain,
Number(gasLimit),
"auto",
undefined,
undefined,
);
// gasFee is returned as a string in wei
return BigInt(gasFee as string);
}
const fee = await estimateGasFee("ethereum", "arbitrum", 250000n);
Using the GasService Contract Directly
const gasServiceAbi = parseAbi([
"function payNativeGasForContractCall(address sender, string calldata destinationChain, string calldata destinationAddress, bytes calldata payload, address refundAddress) payable",
"function payNativeGasForContractCallWithToken(address sender, string calldata destinationChain, string calldata destinationAddress, bytes calldata payload, string calldata symbol, uint256 amount, address refundAddress) payable",
]);
const GAS_SERVICE: Address = "0x2d5d7d31F671F86C782533cc367F14109a082712";
async function payGasAndSend(
destinationChain: string,
destinationAddress: string,
payload: `0x${string}`,
gasValue: bigint
): Promise<`0x${string}`> {
// Pay gas first
const { request: gasRequest } = await ethereumClient.simulateContract({
address: GAS_SERVICE,
abi: gasServiceAbi,
functionName: "payNativeGasForContractCall",
args: [
account.address,
destinationChain,
destinationAddress,
payload,
account.address,
],
value: gasValue,
account: account.address,
});
const gasHash = await walletClient.writeContract(gasRequest);
const gasReceipt = await ethereumClient.waitForTransactionReceipt({ hash: gasHash });
if (gasReceipt.status !== "success") throw new Error("Gas payment reverted");
// Then call Gateway
const gatewayAbi = parseAbi([
"function callContract(string calldata destinationChain, string calldata contractAddress, bytes calldata payload) external",
]);
const GATEWAY: Address = "0x4F4495243837681061C4743b74B3eEdf548D56A5";
const { request: callRequest } = await ethereumClient.simulateContract({
address: GATEWAY,
abi: gatewayAbi,
functionName: "callContract",
args: [destinationChain, destinationAddress, payload],
account: account.address,
});
const callHash = await walletClient.writeContract(callRequest);
const callReceipt = await ethereumClient.waitForTransactionReceipt({ hash: callHash });
if (callReceipt.status !== "success") throw new Error("callContract reverted");
return callHash;
}
Gas Estimation Guidelines
| Route | Typical Gas Limit | Estimated Cost (ETH) |
|---|---|---|
| Ethereum -> Arbitrum | 250,000 | ~0.0003 |
| Ethereum -> Base | 250,000 | ~0.0003 |
| Ethereum -> Polygon | 250,000 | ~0.0005 |
| Arbitrum -> Ethereum | 250,000 | ~0.002 |
| L2 -> L2 | 250,000 | ~0.0001 |
Costs vary with gas prices. Always use the SDK estimator for production.
Deployment Pattern
Multi-Chain Deploy Sequence
- Deploy your
AxelarExecutablecontract on each chain (with that chain's Gateway and GasService) - Call
setTrustedRemote()on every chain for every other chain's contract address - Test with a small GMP message before production use
- For ITS: deploy token on origin chain first, then
deployRemoteInterchainTokento each target
const AXELAR_CONTRACTS: Record<string, { gateway: Address; gasService: Address }> = {
ethereum: {
gateway: "0x4F4495243837681061C4743b74B3eEdf548D56A5",
gasService: "0x2d5d7d31F671F86C782533cc367F14109a082712",
},
arbitrum: {
gateway: "0xe432150cce91c13a887f7D836923d5597adD8E31",
gasService: "0x2d5d7d31F671F86C782533cc367F14109a082712",
},
base: {
gateway: "0xe432150cce91c13a887f7D836923d5597adD8E31",
gasService: "0x2d5d7d31F671F86C782533cc367F14109a082712",
},
optimism: {
gateway: "0xe432150cce91c13a887f7D836923d5597adD8E31",
gasService: "0x2d5d7d31F671F86C782533cc367F14109a082712",
},
polygon: {
gateway: "0xe432150cce91c13a887f7D836923d5597adD8E31",
gasService: "0x2d5d7d31F671F86C782533cc367F14109a082712",
},
};
async function setTrustedRemotes(
deployments: Map<string, Address>,
sendTx: (chain: string, to: Address, data: `0x${string}`) => Promise<void>
) {
const chains = [...deployments.keys()];
const setRemoteAbi = parseAbi([
"function setTrustedRemote(string calldata chain, string calldata addr) external",
]);
for (const srcChain of chains) {
for (const dstChain of chains) {
if (srcChain === dstChain) continue;
const srcContract = deployments.get(srcChain)!;
const dstContract = deployments.get(dstChain)!;
const data = encodeFunctionData({
abi: setRemoteAbi,
functionName: "setTrustedRemote",
args: [dstChain, dstContract.toLowerCase()],
});
await sendTx(srcChain, srcContract, data);
}
}
}
Foundry Deploy Script
// SPDX-License-Identifier: MIT
pragma solidity 0.8.22;
import {Script, console} from "forge-std/Script.sol";
import {MyGMPReceiver} from "../src/MyGMPReceiver.sol";
contract DeployGMP is Script {
function run() external {
uint256 deployerKey = vm.envUint("PRIVATE_KEY");
address gateway = vm.envAddress("AXELAR_GATEWAY");
address gasService = vm.envAddress("AXELAR_GAS_SERVICE");
vm.startBroadcast(deployerKey);
MyGMPReceiver receiver = new MyGMPReceiver(gateway, gasService);
console.log("MyGMPReceiver deployed:", address(receiver));
vm.stopBroadcast();
}
}
Tracking Cross-Chain Messages
Using Axelarscan
After a GMP transaction, track delivery status at axelarscan.io. Enter the source transaction hash to see:
- Message submitted (source chain)
- Gas paid (source chain)
- Confirmed by validators
- Approved on destination Gateway
- Executed on destination chain
Programmatic Status Check
import { AxelarGMPRecoveryAPI, Environment } from "@axelar-network/axelarjs-sdk";
const gmpApi = new AxelarGMPRecoveryAPI({
environment: Environment.MAINNET,
});
async function checkStatus(txHash: string): Promise<string> {
const status = await gmpApi.queryTransactionStatus(txHash);
// Returns: "source_gateway_called" | "confirmed" | "approved" | "executed" | "error"
return status.status;
}
Manually Execute a Stuck Message
If a message is approved but not executed (relayer missed it):
async function manuallyExecute(txHash: string): Promise<void> {
const result = await gmpApi.manualRelayToDestChain(txHash);
if (!result.success) {
throw new Error(`Manual relay failed: ${result.error}`);
}
}
Error Handling
Common Reverts
| Error | Cause | Fix |
|---|---|---|
NotApprovedByGateway |
Message not yet approved by validators | Wait for Axelar consensus, check axelarscan |
InvalidAddress |
Empty or malformed destination address string | Pass valid lowercase hex address string |
TokenDoesNotExist |
Token symbol not registered in Gateway | Use only Axelar-supported token symbols |
InsufficientBalance |
Contract does not hold enough tokens for callContractWithToken |
Transfer and approve tokens to the sender contract first |
AlreadyExecuted |
Message already processed on destination | This is expected for replay protection -- no action needed |
NotSelf |
ITS function called externally that should be internal | Use the ITS SDK or Factory pattern instead of direct calls |
Debugging Cross-Chain Failures
-
Check source transaction on explorer. If it reverted, the message was never submitted. Fix gas payment or Gateway call.
-
Verify gas was paid. Search for
GasPaidForContractCallevent on the source chain in the same transaction. If missing, gas was not paid and the message will not be relayed. -
Check axelarscan.io. Enter the source tx hash. Status flow:
source_gateway_called->confirmed->approved->executed. -
If stuck at "confirmed", validators approved but the destination Gateway has not received the approval. This is rare and usually resolves automatically.
-
If stuck at "approved", the relayer failed to execute. Use
manualRelayToDestChain()from the SDK or submit the execute transaction manually. -
If "executed" but your logic failed, the
_execute()callback reverted. Debug your implementation -- check payload encoding matches decoding, check trusted remote is set.
# Verify Gateway has code
cast code 0x4F4495243837681061C4743b74B3eEdf548D56A5 --rpc-url $ETH_RPC_URL
# Check if a command is approved
cast call 0x4F4495243837681061C4743b74B3eEdf548D56A5 \
"isCommandExecuted(bytes32)(bool)" <commandId> --rpc-url $ETH_RPC_URL
# Check token address for a symbol
cast call 0x4F4495243837681061C4743b74B3eEdf548D56A5 \
"tokenAddresses(string)(address)" "axlUSDC" --rpc-url $ETH_RPC_URL
Contract Addresses
Last verified: 2025-05-01
Gateway
| Chain | Address |
|---|---|
| Ethereum | 0x4F4495243837681061C4743b74B3eEdf548D56A5 |
| Arbitrum | 0xe432150cce91c13a887f7D836923d5597adD8E31 |
| Base | 0xe432150cce91c13a887f7D836923d5597adD8E31 |
| Optimism | 0xe432150cce91c13a887f7D836923d5597adD8E31 |
| Polygon | 0xe432150cce91c13a887f7D836923d5597adD8E31 |
| Avalanche | 0x5029C0EFf6C34351a0CEc334542cDb22c7928f78 |
| BNB Chain | 0x304acf330bbE08d1e512eefaa92F6a57871fD895 |
| Fantom | 0x304acf330bbE08d1e512eefaa92F6a57871fD895 |
GasService
| Chain | Address |
|---|---|
| Ethereum | 0x2d5d7d31F671F86C782533cc367F14109a082712 |
| Arbitrum | 0x2d5d7d31F671F86C782533cc367F14109a082712 |
| Base | 0x2d5d7d31F671F86C782533cc367F14109a082712 |
| Optimism | 0x2d5d7d31F671F86C782533cc367F14109a082712 |
| Polygon | 0x2d5d7d31F671F86C782533cc367F14109a082712 |
| Avalanche | 0x2d5d7d31F671F86C782533cc367F14109a082712 |
| BNB Chain | 0x2d5d7d31F671F86C782533cc367F14109a082712 |
Interchain Token Service (ITS)
| Chain | Address |
|---|---|
| Ethereum | 0xB5FB4BE02232B1bBA4dC8f81dc24C26980dE9e3C |
| Arbitrum | 0xB5FB4BE02232B1bBA4dC8f81dc24C26980dE9e3C |
| Base | 0xB5FB4BE02232B1bBA4dC8f81dc24C26980dE9e3C |
| Optimism | 0xB5FB4BE02232B1bBA4dC8f81dc24C26980dE9e3C |
| Polygon | 0xB5FB4BE02232B1bBA4dC8f81dc24C26980dE9e3C |
Interchain Token Factory
| Chain | Address |
|---|---|
| Ethereum | 0x83a93500d23Fbc3e82B410aD07A6a9F7A0670D12 |
| Arbitrum | 0x83a93500d23Fbc3e82B410aD07A6a9F7A0670D12 |
| Base | 0x83a93500d23Fbc3e82B410aD07A6a9F7A0670D12 |
| Optimism | 0x83a93500d23Fbc3e82B410aD07A6a9F7A0670D12 |
| Polygon | 0x83a93500d23Fbc3e82B410aD07A6a9F7A0670D12 |