registerCustomToken on each blockchain where your token is deployed, using the
same deployer wallet and the same bytes32 salt on every call. That pair
deterministically computes your token’s crosschain identifier (tokenId), which
links your token deployments across blockchains. The resulting connection is
deployer-owned: you set rate limits, max transfer amount, and hold the pauser
role. After configuration, two additional steps are required before any transfer
succeeds: granting the TokenManager the minter role on your ERC-20 on each
blockchain, and setting a non-zero rate limit. For denylist behavior on existing
ERC-20 custom configurations, see
Denylist layers.
Prerequisites
Before you begin, ensure that you’ve:- Installed Node.js v22+
- Deployed your ERC-20 token on both Ethereum Sepolia and Base Sepolia, and noted both token contract addresses
- Verified that your token uses the same number of decimals on every blockchain
- Prepared an Ethereum-compatible testnet wallet whose private key you have
available—this is the
deployerwallet, and it must be the same wallet used on every blockchain - Funded the wallet with Sepolia ETH and Base Sepolia ETH for gas
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 TokenManager
contracts. You must use this same wallet on every blockchain where you configure
the token.
The npm run start command loads variables from .env using Node.js native
env-file support.
Step 2: Configure clients and choose a salt
Createindex.ts. Set up public and wallet clients for both blockchains, then
define a bytes32 salt. The salt, together with your deployer address,
determines your token’s tokenId—it must be identical on every blockchain where
you configure the token.
Step 3: Configure on Ethereum Sepolia
3.1. Compute your tokenId
The tokenId is purely deterministic: it derives from your deployer address
and salt using two chained view calls. Computing it before submitting any
transactions lets you note the value and confirm it is identical after Base
Sepolia configuration.
3.2. Call registerCustomToken
registerCustomToken is payable. No configuration fee is charged on testnet,
so you can omit value from both calls in this quickstart. If you are
configuring on mainnet, verify whether a fee applies before submitting.
Step 4: Configure on Base Sepolia
CallregisterCustomToken on Base Sepolia using the same deployer wallet and
the same salt. Pass the Base Sepolia token address. The tokenId is derived
from (deployer address, salt)—not from blockchain state—so it is identical to
the one computed in step 3.
Step 5: Retrieve the TokenManager addresses
CallresolveTokenManager on each blockchain to get the deployed TokenManager
address. You need these addresses to grant the minter role in the next step and
to call setRateLimit in step 7.
Step 6: Grant the minter role
TheTokenManager burns your token on the source blockchain and mints it on the
destination blockchain. You must grant it the minter role on your ERC-20 on each
blockchain before transfers can succeed.
The exact call depends on your token’s access-control interface. For
FiatToken-compatible contracts (such as USDC-style tokens), call
configureMinter from the masterMinter address. Replace the function name and
ABI with whatever your contract exposes.
AccessControl or a custom owner-managed role), call the equivalent grant
function on each deployment.
Step 7: Set a non-zero rate limit
EveryTokenManager deploys with rateLimit = 0. A zero rate limit blocks all
outbound and inbound transfers for that token on that blockchain. 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. If the operator field
was left empty ("0x"), only the service operator can set the rate
limit—contact Circle to adjust it.
Step 8: Verify the configuration
CallresolveTokenAddress on each blockchain to confirm that the tokenId
resolves to your token addresses.
tokenId and adjust
the source and destination domains as needed.