Skip to content

Token Deployment Example

Deploy an ERC20 token across multiple blockchains.

Overview

This example demonstrates how to:

  • Create a simple ERC20 token
  • Deploy to multiple chains simultaneously
  • Verify state synchronization
  • Monitor cross-chain balances

Quick Start

# Create project from template
switchboard init my-token --template token --dev-mode

cd my-token
npm install

# Configure environment
cp .env.example .env
# Edit .env with your keys

# Deploy to testnets
switchboard deploy --dev-mode

The Contract

// contracts/evm/MyToken.sol
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.19;

import "@openzeppelin/contracts/token/ERC20/ERC20.sol";
import "@openzeppelin/contracts/access/Ownable.sol";

contract MyToken is ERC20, Ownable {
    constructor(
        string memory name,
        string memory symbol,
        uint256 initialSupply
    ) ERC20(name, symbol) Ownable(msg.sender) {
        _mint(msg.sender, initialSupply * 10 ** decimals());
    }

    function mint(address to, uint256 amount) public onlyOwner {
        _mint(to, amount);
    }
}

Configuration

switchboard.config.js

module.exports = {
  mode: 'development',

  networks: {
    sepolia: {
      rpcUrl: process.env.ETHEREUM_RPC_URL,
      chainId: 11155111,
      accounts: [process.env.PRIVATE_KEY],
    },
    mumbai: {
      rpcUrl: process.env.POLYGON_RPC_URL,
      chainId: 80001,
      accounts: [process.env.PRIVATE_KEY],
    },
    fuji: {
      rpcUrl: process.env.AVALANCHE_RPC_URL,
      chainId: 43113,
      accounts: [process.env.PRIVATE_KEY],
    },
  },

  solana: {
    rpcUrl: process.env.SOLANA_RPC_URL,
    commitment: 'confirmed',
  },

  deployment: {
    gasOptimization: true,
    verification: true,
    confirmations: 2,
  },
};

Environment Variables

# .env
SOLANA_RPC_URL=https://api.devnet.solana.com
ETHEREUM_RPC_URL=https://eth-sepolia.g.alchemy.com/v2/YOUR_KEY
POLYGON_RPC_URL=https://polygon-mumbai.g.alchemy.com/v2/YOUR_KEY
AVALANCHE_RPC_URL=https://api.avax-test.network/ext/bc/C/rpc
PRIVATE_KEY=0x...

Deployment Script

// scripts/deploy.js
import { Switchboard } from '@switchboard/sdk';
import { readFileSync } from 'fs';

async function main() {
  // Load compiled contract
  const artifact = JSON.parse(
    readFileSync('./artifacts/MyToken.json', 'utf-8')
  );

  // Initialize Switchboard
  const switchboard = new Switchboard({
    solana: { rpcUrl: process.env.SOLANA_RPC_URL },
    networks: {
      sepolia: {
        rpcUrl: process.env.ETHEREUM_RPC_URL,
        privateKey: process.env.PRIVATE_KEY,
      },
      mumbai: {
        rpcUrl: process.env.POLYGON_RPC_URL,
        privateKey: process.env.PRIVATE_KEY,
      },
      fuji: {
        rpcUrl: process.env.AVALANCHE_RPC_URL,
        privateKey: process.env.PRIVATE_KEY,
      },
    },
  });

  console.log('Starting deployment...\n');

  // Deploy across chains
  const deployment = await switchboard.deployContract({
    name: 'MyToken',
    bytecode: artifact.bytecode,
    abi: artifact.abi,
    constructorArgs: ['My Token', 'MTK', 1000000],
    chains: ['sepolia', 'mumbai', 'fuji'],
    options: {
      verify: true,
    },
  });

  console.log(`Deployment ID: ${deployment.id}\n`);

  // Wait and monitor
  let status = await switchboard.trackDeployment(deployment.id);

  while (status.status !== 'completed' && status.status !== 'failed') {
    console.log(`Status: ${status.status}`);
    console.log(`Completed: ${status.completedChains.join(', ') || 'none'}`);
    console.log(`Pending: ${status.pendingChains.join(', ') || 'none'}\n`);

    await new Promise((r) => setTimeout(r, 5000));
    status = await switchboard.trackDeployment(deployment.id);
  }

  if (status.status === 'completed') {
    console.log('Deployment successful!\n');
    console.log('Contract Addresses:');
    for (const [chain, address] of Object.entries(status.addresses)) {
      console.log(`  ${chain}: ${address}`);
    }
  } else {
    console.error('Deployment failed:', status.errors);
  }
}

main().catch(console.error);

Verification

After deployment, verify on block explorers:

# Verify on Etherscan (Sepolia)
switchboard verify --contract MyToken --network sepolia

# Verify on Polygonscan (Mumbai)
switchboard verify --contract MyToken --network mumbai

# Verify on Snowtrace (Fuji)
switchboard verify --contract MyToken --network fuji

Testing

// tests/token.test.js
import { Switchboard } from '@switchboard/sdk';
import { expect } from 'chai';

describe('MyToken', () => {
  let switchboard;
  let addresses;

  before(async () => {
    switchboard = new Switchboard({
      solana: { rpcUrl: process.env.SOLANA_RPC_URL },
    });

    addresses = {
      sepolia: '0x...',
      mumbai: '0x...',
      fuji: '0x...',
    };
  });

  it('should have consistent state across chains', async () => {
    const state = await switchboard.getState({
      contractAddress: addresses.sepolia,
      chains: ['sepolia', 'mumbai', 'fuji'],
    });

    expect(state.isConsistent).to.be.true;
  });

  it('should have the same total supply on all chains', async () => {
    // Query each chain for total supply
    // Compare values
  });
});

Interacting with Deployed Tokens

Read Balance

import { ethers } from 'ethers';

async function getBalance(rpcUrl, contractAddress, walletAddress) {
  const provider = new ethers.JsonRpcProvider(rpcUrl);
  const abi = ['function balanceOf(address) view returns (uint256)'];
  const contract = new ethers.Contract(contractAddress, abi, provider);

  const balance = await contract.balanceOf(walletAddress);
  return ethers.formatEther(balance);
}

// Check balance on all chains
const balances = await Promise.all([
  getBalance(process.env.ETHEREUM_RPC_URL, addresses.sepolia, wallet),
  getBalance(process.env.POLYGON_RPC_URL, addresses.mumbai, wallet),
  getBalance(process.env.AVALANCHE_RPC_URL, addresses.fuji, wallet),
]);

console.log('Balances:', balances);

Transfer Tokens

async function transfer(rpcUrl, contractAddress, privateKey, to, amount) {
  const provider = new ethers.JsonRpcProvider(rpcUrl);
  const wallet = new ethers.Wallet(privateKey, provider);
  const abi = ['function transfer(address, uint256) returns (bool)'];
  const contract = new ethers.Contract(contractAddress, abi, wallet);

  const tx = await contract.transfer(to, ethers.parseEther(amount));
  await tx.wait();

  return tx.hash;
}

Production Deployment

When ready for production:

  1. Audit the contract
  2. Update configuration for mainnets
  3. Secure private keys
  4. Deploy with verification
# Deploy to mainnets
switchboard deploy --prod-mode --networks ethereum,polygon,avalanche

Next Steps