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.
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)
Create atsconfig.json file:
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.
The npm run start command loads variables from .env using Node.js native
env-file support.
Step 2: Configure clients and define token metadata
Createindex.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.
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
CalldeployCrossChainToken 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.
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.
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 callingdeployRemoteCrossChainToken, 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
CalldeployRemoteCrossChainToken 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
PollresolveTokenAddress 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
EveryTokenManager 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
CallresolveTokenAddress and resolveTokenManager on both blockchains to
confirm the tokenId resolves correctly.
tokenId.