β οΈ Production Status: EVM integration is not deployed on mainnet. Available on evm-poc branch. See EVM Integration for details.
This guide covers everything you need to set up your development environment for building dApps on BitBadges Chain with EVM compatibility.
Chain Configuration
Chain ID
The BitBadges Chain EVM uses a custom Chain ID for EVM compatibility:
Local Development: 90123
Testnet: TBD (contact us for testnet setup)
Mainnet: Not deployed (see production status notice)
The Chain ID is configured in app/params/constants.go:
EVMChainID="90123"// Default for testing
Important: The Chain ID in your genesis file (app_state.evm.params.chain_config.chain_id) must match this value.
RPC Endpoints
BitBadges Chain provides two RPC interfaces:
Tendermint RPC (Port 26657)
Used for Cosmos SDK queries and transactions
Endpoint: http://localhost:26657
Supports standard Cosmos SDK operations
EVM JSON-RPC (Port 8545)
Used for Ethereum-compatible operations
Endpoint: http://localhost:8545
Supports standard Ethereum JSON-RPC methods
Required for MetaMask, ethers.js, web3.js, etc.
Starting the Chain with EVM
To start the chain with EVM support, use the --json-rpc.enable flag:
Or use the provided startup script:
The startup script automatically enables EVM JSON-RPC on port 8545.
Note: The EVM module is always enabled in the chain binary. You only need to enable the JSON-RPC server to interact with it via Ethereum-compatible tools (MetaMask, ethers.js, etc.).
MetaMask Configuration
To connect MetaMask to your local BitBadges chain:
Open MetaMask
Go to Settings > Networks > Add Network
Add the following network configuration:
For Local Development:
Network Name: BitBadges Local
RPC URL: http://localhost:8545 (EVM JSON-RPC)
Chain ID: 90123
Currency Symbol: BADGE
Block Explorer: (Optional, leave blank for local)
Note: Use port 8545 (EVM JSON-RPC), not 26657 (Tendermint RPC).
Getting Test Tokens
After starting your local chain, fund your MetaMask account:
Simple dApp Setup
Project Structure
A typical dApp project structure:
Basic Contract Template
Here's a minimal contract that uses the tokenization precompile:
Deployment Script (TypeScript/ethers.js)
Frontend Integration (React/ethers.js)
Contract Snippets
Helper Library
The repository includes a comprehensive helper library at contracts/libraries/TokenizationHelpers.sol that provides utility functions for:
Creating UintRange structs (single values, sequences, full ranges)
# From the bitbadgeschain repository root
bitbadgeschaind start --json-rpc.enable --json-rpc.address 0.0.0.0:8545
# From the bitbadgeschain repository root
./start-chain.sh start
# Get your MetaMask address (0x... format)
# Then fund it using the chain's genesis or by transferring from a validator
# Option 1: Transfer from a validator account
bitbadgeschaind tx bank send \
$(bitbadgeschaind keys show alice -a --keyring-backend test) \
<your-metamask-address> \
1000000000ubadge \
--chain-id bitbadges \
--keyring-backend test \
--yes
# Option 2: Use the chain's genesis accounts if configured
import { ethers } from "ethers";
import * as fs from "fs";
async function deploy() {
// Connect to EVM JSON-RPC
const provider = new ethers.JsonRpcProvider("http://localhost:8545");
// Get deployer wallet
const privateKey = process.env.PRIVATE_KEY || "";
if (!privateKey) {
throw new Error("PRIVATE_KEY environment variable required");
}
const wallet = new ethers.Wallet(privateKey, provider);
console.log("Deployer address:", wallet.address);
// Check balance
const balance = await provider.getBalance(wallet.address);
console.log("Balance:", ethers.formatEther(balance), "BADGE");
// Deploy contract
const contractFactory = new ethers.ContractFactory(
CONTRACT_ABI,
CONTRACT_BYTECODE,
wallet
);
const contract = await contractFactory.deploy(collectionId);
await contract.waitForDeployment();
const address = await contract.getAddress();
console.log("Contract deployed at:", address);
// Save deployment info
fs.writeFileSync(
"deployed.json",
JSON.stringify({ address, abi: CONTRACT_ABI }, null, 2)
);
}
deploy().catch(console.error);
import { ethers } from "ethers";
import { useState, useEffect } from "react";
export function useContract() {
const [contract, setContract] = useState<ethers.Contract | null>(null);
const [provider, setProvider] = useState<ethers.BrowserProvider | null>(null);
useEffect(() => {
if (typeof window.ethereum !== "undefined") {
const provider = new ethers.BrowserProvider(window.ethereum);
setProvider(provider);
// Load deployed contract
const contractAddress = "0x..."; // Your deployed contract address
const contract = new ethers.Contract(
contractAddress,
CONTRACT_ABI,
await provider.getSigner()
);
setContract(contract);
}
}, []);
const transfer = async (to: string, amount: bigint, tokenId: bigint) => {
if (!contract) throw new Error("Contract not loaded");
const tx = await contract.transfer(to, amount, tokenId);
await tx.wait();
};
return { contract, provider, transfer };
}
import "./libraries/TokenizationHelpers.sol";
contract MyContract {
function example() external {
// Create a single token ID range
TokenizationTypes.UintRange memory tokenId =
TokenizationHelpers.createSingleTokenIdRange(123);
// Create a full ownership time range (1 to max uint256)
TokenizationTypes.UintRange memory fullTime =
TokenizationHelpers.createFullOwnershipTimeRange();
// Create a token ID sequence (range from start to end)
TokenizationTypes.UintRange memory sequence =
TokenizationHelpers.createTokenIdSequence(1, 100);
// Create a UintRange array from arrays
uint256[] memory starts = new uint256[](2);
uint256[] memory ends = new uint256[](2);
starts[0] = 1; ends[0] = 10;
starts[1] = 20; ends[1] = 30;
TokenizationTypes.UintRange[] memory ranges =
TokenizationHelpers.createUintRangeArray(starts, ends);
}
}