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:
- Audit the contract
- Update configuration for mainnets
- Secure private keys
- Deploy with verification
Next Steps¶
- DeFi Example - Build cross-chain DeFi
- SDK Documentation - Full SDK reference
- API Reference - REST API documentation