Skip to main content
In this quickstart, you deploy a new ERC-20 token on Arc Testnet, extend it to Base Sepolia so it exists on both blockchains, then mint tokens and transfer them crosschain. The deployment uses CREATE3 for deterministic addresses and Circle’s forwarding service to cover remote execution, so you only need gas on Arc Testnet.
Use the same wallet and salt on every blockchain. The tokenId is derived from (deployer address, salt). A different wallet or salt produces a different tokenId with no onchain error, and the bridge connection is silently broken.

Prerequisites

Before you begin, ensure that you’ve:
  • Installed Node.js v22+
  • Prepared an EVM testnet wallet whose private key you have available. This is the deployment wallet, and it must be the same wallet on every blockchain
  • Funded the wallet with Arc Testnet USDC for gas from the Circle Faucet

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 the script

Create index.ts and build it in three parts: clients, token params, then the service ABI used by later steps.

2.1. Set up clients and constants

You only need a Base public client for this path. Iris FORWARD submits the remote deploy and the destination mint, so no Base wallet is required.

2.2. Define token metadata and DeploymentParams

The salt and deployer address determine your tokenId and they must be identical on every blockchain. This example sets rateLimit and maxTransferAmount to type(uint256).max so transfers work once the token exists. For production caps, see Configure rate limits and transfer caps.
  • Sandbox omits initialSupply: Encoding a field the live ABI does not expect produces an empty revert. Some production layouts include it—verify the contract ABI for your target. See Contract reference.
  • rateLimit: 0 or maxTransferAmount: 0 blocks transfers: Defaults are zero if you omit non-zero values.
  • Pack addresses with encodePacked: encodeAbiParameters pads to 32 bytes and breaks the contract’s address decoding.

2.3. Add the CrossChainTokenService ABI

Add the service fragments this script calls:
  • deployCrossChainToken / deployRemoteCrossChainToken: home CREATE3 deploy and CCTP remote deploy
  • CrossChainTokenIdClaimed: source of tokenId after home deploy
  • resolveTokenAddress / resolveTokenManager: look up deployed addresses by tokenId
  • crossChainTransfer: initiate the Arc → Base transfer in step 7

Step 3: Deploy on Arc Testnet

Call deployCrossChainToken on Arc Testnet. 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. Read tokenId from the CrossChainTokenIdClaimed event on the receipt. Append steps 3–7 to index.ts in order. They form one try/catch block.
  • For native deploys, CrossChainTokenIdClaimed emits tokenAddress as address(0). That is expected. Use tokenId from the event, then resolveTokenAddress for the real token address.
  • If home deploy already succeeded and you re-run this step with the same salt, the call reverts. Change the salt for a new token, or continue from the remote deploy using the existing tokenId.

Step 4: Quote and deploy remotely to Base Sepolia

Fetch an Iris fee quote with msgType: "DeployTokenMessage", then call deployRemoteCrossChainToken on Arc targeting Base Sepolia (domain 6). Pass the same salt, metadata, and DeploymentParams as the home deploy.
name, symbol, and decimals must match the home deploy. The protocol does not enforce consistency. A mismatch breaks user experience and indexers with no onchain error.
If the wallet already has gas on Base Sepolia, you can call deployCrossChainToken there with the same wallet, salt, and metadata instead of remote deploy, then continue from mint. That path skips the Iris deploy quote.

Step 5: Wait for the token on Base Sepolia

Poll resolveTokenAddress on Base until it returns a non-zero address. Iris typically delivers in a few minutes after the Arc transaction confirms.

Step 6: Mint on Arc Testnet

Mint supply to the deployer. tokenMinter in DeploymentParams must be your address (as in this example).

Step 7: Transfer Arc → Base with Iris FORWARD

Approve the Arc TokenManager, quote a TransferMessage with FORWARD, then call crossChainTransfer. Iris submits the destination mint so you do not need to call receiveMessage on Base, and you do not need Base ETH for this step.
Run the script:
When Base balance prints, the token is deployed on both chains and a transfer has completed. Fast transfers require additional Circle coordination (FX pricing and backend indexing) before they work for newly deployed tokens—use FINALITY = 2000 (standard) until that is configured. To transfer other configured tokens with this same pattern, see Transfer EURC between Base and Arc and substitute your tokenId.