Skip to main content

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:

ComponentChainTechnologyRepo
teleporteosWAXEOSIO C++wax/faded/bridge/
FadedMonsutaTHCAvalancheSolidity 0.8wax-avax-bridge/contracts/
Oracle NetworkOff-chainNode.jsbridge-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

TableScopeFieldsPurpose
depositsselfaccount, quantityTokens deposited before teleport
teleportsselfid, time, account, quantity, chain_id, eth_address, oracles, signatures, claimedActive teleport records
receiptsselfid, date, ref, to, chain_id, confirmations, quantity, approvers, completedIncoming bridge receipts
oraclesselfaccountRegistered oracle accounts
cancelsselfteleport_idCancelled teleport IDs

Key Actions

ActionAuth RequiredDescription
transferToken contractNotification handler — records deposit when THC sent to bridge
teleportSenderInitiates bridge transfer from WAX to target chain
signOracleOracle signs a teleport with its attestation
receivedOracleOracle confirms a reverse-direction (AVAX→WAX) transfer
claimedOracleOracle marks a WAX→AVAX teleport as claimed on AVAX
withdrawDepositorWithdraw deposited tokens before teleporting
cancelTeleport originatorCancel an unclaimed teleport after 30-day expiry
regoracleContract ownerRegister a new oracle
unregoracleContract ownerRemove an oracle

Flow: WAX → AVAX

  1. Player transfers THC to the bridge contract (transfer notification)
  2. Player calls teleport(from, quantity, chain_id, eth_address)
  3. Contract deducts from deposit, creates teleport record, emits logteleport
  4. Oracle(s) detect the event, call sign(oracle_name, id, signature)
  5. Signatures stored in the teleport record's signatures vector
  6. Player submits claim() on the Avalanche contract with sigData + 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

VariableTypeDescription
thresholduint8Minimum oracle signatures required
thisChainIduint8Chain ID for this deployment (2 = AVAX)
DOMAIN_SEPARATORbytes32Domain separation to prevent cross-contract replay
signedmapping(uint64 => mapping(address => bool))Tracks which oracles signed which teleport
claimedmapping(bytes32 => bool)Tracks which claims have been processed
oraclesmapping(address => bool)Registered oracle addresses

Key Functions

FunctionAccessDescription
teleport(to, tokens, chainId)Any userBurns THC, emits Teleport event for oracles
claim(sigData, signatures)Any userVerifies oracle sigs, mints THC to recipient
regOracle(address)Owner onlyRegister an oracle address
unregOracle(address)Owner onlyRemove an oracle address
updateThreshold(n)Owner onlyChange required signature count
updateChainId(id)Owner onlyUpdate 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

ScriptDirectionListens ToActs On
oracle-eos.jsWAX → AVAXWAX logteleport via Hyperion/SHIPCalls sign() on WAX
oracle-eth.jsAVAX → WAXAvalanche Teleport events via WSCalls received() on WAX
oracle-eth.jsClaim trackingAvalanche Claimed eventsCalls 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

ModeDescription
realtimeProcesses new blocks as they arrive
backfillScans 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 name
  • symbol → your token symbol
  • decimals → your token precision
  • DOMAIN_SEPARATOR hash string → unique to your project

2. Fork the WAX Contract

Replace in teleporteos.hpp:

  • TOKEN_CONTRACT → your WAX token contract account
  • ORACLE_CONFIRMATIONS → your desired threshold
  • Minimum transfer amount in transfer() check

3. Deploy Oracles

  • Update config.js with your contract addresses and keys
  • Run at least 3 oracle instances on separate infrastructure
  • Set threshold on 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