Token Bridge
Bidirectional fungible token bridge between WAX and Avalanche C-Chain.
Purpose
The token bridge allows existing THC tokens on WAX to be moved to Avalanche, and vice versa. This is required because:
- The game's economy originated on WAX
- Avalanche is the target chain for all new economic activity
- Players must not lose existing balances during migration
- Bidirectional flow prevents users from being trapped on either chain
Existing Implementation
This module documents the already deployed bridge. The system consists of three components:
| Component | Chain | Technology | Repo |
|---|---|---|---|
teleporteos | WAX | EOSIO C++ | wax/faded/bridge/ |
FadedMonsutaTHC | Avalanche | Solidity 0.8 | wax-avax-bridge/contracts/ |
| Oracle Network | Off-chain | Node.js | bridge-oracle/ |
Architecture
WAX → AVALANCHE
═══════════════
Player WAX Chain Oracles Avalanche
────── ───────── ─────── ─────────
│ │ │ │
│── transfer THC ────────►│ │ │
│ (to bridge contract) │ │ │
│ │ │ │
│── teleport() ──────────►│ │ │
│ (specify AVAX addr) │ │ │
│ │── logteleport ────────►│ │
│ │ (event emitted) │ │
│ │ │ │
│ │◄── sign() ────────────│ │
│ │ (oracle signs data, │ │
│ │ sig stored on WAX) │ │
│ │ │ │
│────────────────────────────── claim() ────────────────────────────────►│
│ (submit sigData + │ │ │
│ oracle signatures) │ │ │
│ │ │ │
│ │ │◄── Claimed event ───│
│ │◄── claimed() ─────────│ │
│ │ (mark as claimed) │ │
│ │ │ │
│◄──────────────────────────── THC minted ─────────────────────────────│
AVALANCHE → WAX
═══════════════
Player Avalanche Oracles WAX
────── ───────── ─────── ───
│ │ │ │
│── teleport() ──────────►│ │ │
│ (burns THC, │ │ │
│ specify WAX account) │ │ │
│ │── Teleport event ──►│ │
│ │ │ │
│ │ │── received() ──────►│
│ │ │ (oracle confirms, │
│ │ │ WAX transfers │
│ │ │ THC to player) │
│ │ │ │
│◄──────────────────────────────── THC received ────────────────────│
Contract Details
WAX Side: teleporteos
Contract: teleporteos (EOSIO C++)
Token contract: fadedtesttkn (configurable via TOKEN_CONTRACT)
Tables
| Table | Scope | Fields | Purpose |
|---|---|---|---|
deposits | self | account, quantity | Tokens deposited before teleport |
teleports | self | id, time, account, quantity, chain_id, eth_address, oracles, signatures, claimed | Active teleport records |
receipts | self | id, date, ref, to, chain_id, confirmations, quantity, approvers, completed | Incoming bridge receipts |
oracles | self | account | Registered oracle accounts |
cancels | self | teleport_id | Cancelled teleport IDs |
Key Actions
| Action | Auth Required | Description |
|---|---|---|
transfer | Token contract | Notification handler — records deposit when THC sent to bridge |
teleport | Sender | Initiates bridge transfer from WAX to target chain |
sign | Oracle | Oracle signs a teleport with its attestation |
received | Oracle | Oracle confirms a reverse-direction (AVAX→WAX) transfer |
claimed | Oracle | Oracle marks a WAX→AVAX teleport as claimed on AVAX |
withdraw | Depositor | Withdraw deposited tokens before teleporting |
cancel | Teleport originator | Cancel an unclaimed teleport after 30-day expiry |
regoracle | Contract owner | Register a new oracle |
unregoracle | Contract owner | Remove an oracle |
Flow: WAX → AVAX
- Player transfers THC to the bridge contract (
transfernotification) - Player calls
teleport(from, quantity, chain_id, eth_address) - Contract deducts from deposit, creates teleport record, emits
logteleport - Oracle(s) detect the event, call
sign(oracle_name, id, signature) - Signatures stored in the teleport record's
signaturesvector - Player submits
claim()on the Avalanche contract withsigData+signatures
Minimum Transfer
check(quantity.amount >= 100'0000, "Transfer is below minimum of 100 THC");
100 THC minimum (4 decimal places, so 100'0000 = 100.0000 THC).
Avalanche Side: FadedMonsutaTHC
Contract: FadedMonsutaTHC (Solidity ^0.8.33)
Token: ERC-20 with bridge functionality built-in
Key State
| Variable | Type | Description |
|---|---|---|
threshold | uint8 | Minimum oracle signatures required |
thisChainId | uint8 | Chain ID for this deployment (2 = AVAX) |
DOMAIN_SEPARATOR | bytes32 | Domain separation to prevent cross-contract replay |
signed | mapping(uint64 => mapping(address => bool)) | Tracks which oracles signed which teleport |
claimed | mapping(bytes32 => bool) | Tracks which claims have been processed |
oracles | mapping(address => bool) | Registered oracle addresses |
Key Functions
| Function | Access | Description |
|---|---|---|
teleport(to, tokens, chainId) | Any user | Burns THC, emits Teleport event for oracles |
claim(sigData, signatures) | Any user | Verifies oracle sigs, mints THC to recipient |
regOracle(address) | Owner only | Register an oracle address |
unregOracle(address) | Owner only | Remove an oracle address |
updateThreshold(n) | Owner only | Change required signature count |
updateChainId(id) | Owner only | Update the chain ID |
Signature Data Packet (69 bytes)
The sigData passed to claim() is a tightly packed 69-byte structure:
Offset Size Field Encoding
────── ──── ───── ────────
0 8 id uint64, little-endian
8 4 timestamp uint32, little-endian
12 8 fromAddr uint64, little-endian (WAX account name)
20 8 quantity uint64, little-endian
28 8 symbolRaw uint64, little-endian
36 1 chainId uint8
37 32 toAddress checksum256 (last 20 bytes = EVM address)
Claim Verification
1. Parse sigData → TeleportData struct
2. Verify chainId matches thisChainId
3. Verify timestamp is within 30 days
4. For each signature:
a. Recover signer from keccak256(DOMAIN_SEPARATOR + sigData)
b. Verify signer is registered oracle
c. Verify signer hasn't already signed this teleport ID
d. Increment valid signature count
5. Require valid count >= threshold
6. Verify claim not already processed: claimed[keccak256(DOMAIN_SEPARATOR, id)]
7. Mint tokens to toAddress
8. Emit Claimed event
Oracle Network
Runtime: Node.js Processes: Two oracle scripts — one per direction
| Script | Direction | Listens To | Acts On |
|---|---|---|---|
oracle-eos.js | WAX → AVAX | WAX logteleport via Hyperion/SHIP | Calls sign() on WAX |
oracle-eth.js | AVAX → WAX | Avalanche Teleport events via WS | Calls received() on WAX |
oracle-eth.js | Claim tracking | Avalanche Claimed events | Calls claimed() on WAX |
Configuration
// config.js structure
{
precision: 4, // Token decimal places
symbol: 'THC', // Token symbol
network: 'AVAX', // Target EVM network
eos: {
chainId: '...', // WAX chain ID
wsEndpoint: 'ws://...', // State History (SHIP) node
endpoint: 'https://...', // EOSIO API endpoint
teleportContract: '...', // WAX bridge contract account
oracleAccount: '...', // Oracle's WAX account
privateKey: '...' // Oracle's WAX private key
},
eth: {
teleportContract: '0x...', // Avalanche bridge contract address
wsEndpoint: 'wss://...', // Avalanche WebSocket RPC
endpoint: 'https://...', // Avalanche HTTP RPC
oracleAccount: '0x...', // Oracle's EVM address
privateKey: '...', // Oracle's EVM private key
chainId: 2 // Bridge chain ID (not EVM chain ID)
}
}
Oracle Modes
| Mode | Description |
|---|---|
realtime | Processes new blocks as they arrive |
backfill | Scans historical blocks to catch up on missed events |
Integration Guide for Other Games
To use the token bridge for your own token:
1. Fork the Solidity Contract
Replace in FadedMonsutaTHC.sol:
name→ your token namesymbol→ your token symboldecimals→ your token precisionDOMAIN_SEPARATORhash string → unique to your project
2. Fork the WAX Contract
Replace in teleporteos.hpp:
TOKEN_CONTRACT→ your WAX token contract accountORACLE_CONFIRMATIONS→ your desired threshold- Minimum transfer amount in
transfer()check
3. Deploy Oracles
- Update
config.jswith your contract addresses and keys - Run at least 3 oracle instances on separate infrastructure
- Set
thresholdon the Avalanche contract to match
4. Test
- Deploy to testnets first (Avalanche Fuji, WAX Testnet)
- Verify WAX → AVAX flow end-to-end
- Verify AVAX → WAX flow end-to-end
- Test edge cases: expiry, duplicate claims, insufficient signatures