Skip to main content
Deploying a native crosschain token creates a brand-new ERC-20 that the protocol manages across multiple blockchains, with no pre-existing contract required. The service uses CREATE3 to deploy a CrossChainToken ERC-20 and a NATIVE_CROSSCHAIN_TOKEN TokenManager at deterministic addresses derived from your deployment wallet and a salt you choose. This quickstart walks you through deploying on Ethereum Sepolia and extending the token to Base Sepolia.
Three conditions silently block or break deployment if not met:
  • Same wallet and salt on every blockchain: The tokenId is derived from (deployer address, salt). Using a different wallet or a different salt on any blockchain produces a different tokenId—no error is thrown, and the bridge connection is silently broken.
  • Rate limit starts at zero: Every TokenManager deploys with rateLimit = 0, which blocks all transfers. You must call setRateLimit with a non-zero value on each blockchain before any transfer can proceed.
  • name, symbol, and decimals must match on every blockchain: The protocol does not enforce consistency. A mismatch breaks user experience and indexers silently—there is no onchain error.

Prerequisites

Before you begin, ensure that you’ve:
  • Installed Node.js v22+
  • Prepared an Ethereum-compatible testnet wallet whose private key you have available—this is the deployment wallet, and it must be the same wallet used on every blockchain
  • Funded the wallet with Sepolia ETH and Base Sepolia ETH for gas
  • Obtained access to the Iris fee-quote endpoint (POST /v2/quote/cctpx/{tokenId}/{sourceDomain}/{destinationDomain})

Step 1: Set up the project

1.1. Create the project and install dependencies

1.2. Configure TypeScript (optional)

This step is optional. It helps prevent missing types in your IDE or editor.
Create a tsconfig.json file:
Then, update the tsconfig.json file:

1.3. Set environment variables

Open .env in your editor and add:
PRIVATE_KEY is the private key for the wallet that deploys the token. You must use this same wallet on every blockchain where you deploy.
Open .env in your editor rather than writing values with shell commands, and add .env to your .gitignore. This prevents credentials from leaking into your shell history or version control.
The npm run start command loads variables from .env using Node.js native env-file support.
This example uses one or more private keys for local testing. In production, use a secure key management solution and never expose or share private keys.

Step 2: Configure clients and define token metadata

Create index.ts. Set up public and wallet clients for both blockchains, then define your token metadata and a bytes32 salt. The salt, together with your deployer address, determines your token’s tokenId—it must be identical on every blockchain.
Compute your tokenId using the crossChainTokenId view call. Pass the raw salt directly—not customTokenDeploySalt, which is only used by registerCustomToken.

Step 3: Deploy on Ethereum Sepolia

Call deployCrossChainToken on Ethereum Sepolia. No fee quote is needed—this is a local CREATE3 deploy. The protocol creates the CrossChainToken ERC-20 and a NATIVE_CROSSCHAIN_TOKEN TokenManager in the same transaction.
Verify the live ABI before copying this struct. The sandbox contract’s DeploymentParams may omit initialSupply while the production contract includes it, or vice versa. A struct with the wrong number of fields causes the transaction to revert. Fetch the ABI from the contract source for the blockchain you are targeting before submitting, and add or remove initialSupply accordingly. The following code includes it based on the documented production struct.
deployCrossChainToken is payable. No fee is charged on testnet, so you can omit value. If you are deploying on mainnet, verify whether a fee applies before submitting.
The CrossChainTokenIdClaimed event emits tokenAddress as address(0) for native deploys—this is expected behavior, not an error. Use resolveTokenAddress to read the real token address after the receipt confirms.

Step 4: Fetch a fee quote for the remote deploy

Before calling deployRemoteCrossChainToken, fetch a fee quote from Iris. The quote covers destination gas and Circle’s message delivery. Pass DeployTokenMessage as the message type so Iris prices a deploy operation rather than a transfer. If your deployment wallet has funds on Base Sepolia, you can instead call deployCrossChainToken directly on Base Sepolia with the same wallet, salt, and token metadata, then skip to step 7. This avoids the CCTP message entirely and is useful when you have funds available on each blockchain.

Step 5: Deploy to Base Sepolia

Call deployRemoteCrossChainToken on Ethereum Sepolia, targeting Base Sepolia (domain 6). Pass the same salt, token metadata, and DeploymentParams as in step 3—any mismatch produces a different tokenId on the destination.

Step 6: Confirm delivery on Base Sepolia

Poll resolveTokenAddress on the Base Sepolia service until it returns a non-zero address. Circle typically delivers the message in a few minutes after the Ethereum Sepolia transaction confirms.

Step 7: Set rate limits

Every TokenManager deploys with rateLimit = 0, which blocks all transfers. Call setRateLimit on each TokenManager with a non-zero value. Only the operator can call setRateLimit—if you encoded your address as the operator in DeploymentParams, you can call it directly.

Step 8: Verify

Call resolveTokenAddress and resolveTokenManager on both blockchains to confirm the tokenId resolves correctly.
Your token is now deployed on both blockchains and ready for crosschain transfers. Before enabling fast transfers, note that fast transfers require additional Circle coordination—FX pricing and backend indexing must be configured by Circle before fast transfers work for newly deployed tokens. Once the token is deployed and rate limits are set, transferring it follows the same pattern as Transfer EURC from Ethereum to Arc—swap in your new tokenId.