DEV Community

NOX Ventures
NOX Ventures

Posted on

Building a Cross-Chain Bridge API for RustChain: RIP-305 Track C

Building a Cross-Chain Bridge API for RustChain: RIP-305 Track C

RustChain is a Proof-of-Antiquity blockchain where vintage hardware mines real crypto. But until now, there was no way to move RTC (RustChain's native token) onto other chains. Today I built the bridge API that makes it possible — connecting RTC to Solana and Base L2.

What Is RIP-305?

RIP-305 is RustChain's cross-chain airdrop protocol. It distributes 50,000 wrapped RTC (wRTC) across two target chains:

  • Track A: wRTC as a Solana SPL token (30,000 wRTC)
  • Track B: wRTC as a Base ERC-20 token (20,000 wRTC)
  • Track C: The bridge API connecting them ← this post
  • Track D: An airdrop claim page

Track C is where the magic happens. Without a bridge API, the tokens on Solana and Base would just be isolated assets with no connection to the actual RTC ecosystem.

Architecture: Phase 1 (Admin-Controlled)

Phase 1 is intentionally simple and trustworthy. Rather than building a complex trustless bridge with cross-chain message passing (which adds enormous attack surface), Phase 1 uses an admin-controlled mint pattern:

User → POST /bridge/lock → lock_id
         ↓
    Lock recorded in SQLite ledger
    Admin monitors pending locks
    Admin mints wRTC on Solana (spl-token) or Base (ERC-20.mint())
         ↓
    POST /bridge/release (release_tx)
         ↓
    state: "complete" — fully transparent on ledger
Enter fullscreen mode Exit fullscreen mode

The tradeoff is obvious: you're trusting the admin. But Phase 1 bridges are common in DeFi (Arbitrum launched this way), and Phase 2 can upgrade to trustless with on-chain verification.

The Lock Ledger

Every bridge action is recorded in a SQLite database with full event history. The ledger is publicly queryable, so anyone can verify:

  1. How much RTC has been locked
  2. Whether their lock was released
  3. The corresponding target chain transaction hash

Here are the key endpoints:

@bridge_bp.route("/ledger", methods=["GET"])
def get_ledger():
    """Filterable by state, chain, sender — transparent to all."""
    ...

@bridge_bp.route("/status/<lock_id>", methods=["GET"])
def lock_status(lock_id: str):
    """Full status + complete event log for a specific lock."""
    ...
Enter fullscreen mode Exit fullscreen mode

Sample lock status response:

{
  "lock_id": "lock_6752ac1dc0140e90a2852eab",
  "state": "complete",
  "amount_rtc": 100.0,
  "target_chain": "solana",
  "target_wallet": "7xKXtg2CW87...",
  "release_tx": "4vqsH2kLpUx...",
  "events": [
    {"type": "lock_created", "actor": "my-wallet", "ts": 1741593600},
    {"type": "released", "actor": "admin", "ts": 1741594200}
  ]
}
Enter fullscreen mode Exit fullscreen mode

Building the Lock Endpoint

The /bridge/lock endpoint has careful input validation:

@bridge_bp.route("/lock", methods=["POST"])
def lock_rtc():
    # Validate target chain
    if target_chain not in {"solana", "base"}:
        return jsonify({"error": f"target_chain must be 'solana' or 'base'"}), 400

    # Validate amount
    if amount_float < 1.0:
        return jsonify({"error": "minimum lock amount is 1 RTC"}), 400
    if amount_float > 10_000:
        return jsonify({"error": "maximum lock amount is 10,000 RTC"}), 400

    # Validate wallet format
    if target_chain == "base" and not target_wallet.startswith("0x"):
        return jsonify({"error": "Base wallet must be a 0x EVM address"}), 400

    # Store with 6 decimal precision
    amount_base = int(round(amount_float * 10**6))
    lock_id = _generate_lock_id(sender, amount_base, target_chain, now)
    ...
Enter fullscreen mode Exit fullscreen mode

A key design decision: storing amounts as integers (millionths of RTC, like satoshis in Bitcoin). This avoids floating-point precision issues when doing accounting across the ledger.

Lock States

The state machine tracks every lock through its lifecycle:

pending → confirmed → releasing → complete
             ↓                        ↑
           failed                 refunded
Enter fullscreen mode Exit fullscreen mode
  • pending: Lock received, awaiting confirmation
  • confirmed: Admin verified the RTC was actually sent
  • releasing: wRTC mint transaction in flight on target chain
  • complete: wRTC successfully minted, lock closed
  • failed/refunded: Something went wrong, RTC returned

Integration with Track A (Solana) and Track B (Base)

The bridge API plugs directly into the SPL token (Track A) and ERC-20 (Track B):

# After admin confirms lock, mint on Solana:
# spl-token mint <WRTC_MINT_ADDRESS> <AMOUNT> <TARGET_WALLET>

# Or on Base:
# cast send <WRTC_CONTRACT> "mint(address,uint256)" <TARGET_WALLET> <AMOUNT>

# Then call /bridge/release with the tx hash:
POST /bridge/release
{
  "lock_id": "lock_abc123",
  "release_tx": "0xabcdef...",
  "notes": "Minted 100 wRTC on Base"
}
Enter fullscreen mode Exit fullscreen mode

Testing: 14 Tests, All Passing

I wrote a full test suite covering all endpoints:

TestLockEndpoint      (7 tests) — validation, min/max amounts, bad wallets
TestReleaseEndpoint   (3 tests) — admin auth, full cycle, not found
TestLedgerEndpoint    (3 tests) — filtering by chain/state/sender  
TestStatsEndpoint     (1 test)  — response structure
Enter fullscreen mode Exit fullscreen mode

Running the tests:

BRIDGE_DB_PATH=/tmp/bridge_test.db \
BRIDGE_ADMIN_KEY=test-key \
python3 -m pytest test_bridge_api.py -v

# === 14 passed in 1.03s ===
Enter fullscreen mode Exit fullscreen mode

Bridge Statistics

The /bridge/stats endpoint gives a birds-eye view:

{
  "by_state": {
    "pending":  {"count": 3,  "total_rtc": 150.0},
    "complete": {"count": 47, "total_rtc": 3200.0}
  },
  "by_chain": {
    "solana": {"bridged_count": 25, "total_wrtc_minted": 1800.0},
    "base":   {"bridged_count": 22, "total_wrtc_minted": 1400.0}
  },
  "all_time": {
    "total_locks": 50,
    "total_rtc_locked": 3350.0
  }
}
Enter fullscreen mode Exit fullscreen mode

Dropping Into the Existing Flask App

The bridge module is a Flask Blueprint, so it drops cleanly into any existing Flask app:

# In your existing node file:
from bridge.bridge_api import register_bridge_routes

app = Flask(__name__)
register_bridge_routes(app)  # adds /bridge/* routes
Enter fullscreen mode Exit fullscreen mode

This was important because RustChain's node already runs a complex Flask app with attestation, miner tracking, governance, and more endpoints. I didn't want to touch any of that.

What's Next (Phase 2)

Phase 2 will make the bridge trustless by:

  1. On-chain lock proofs: RTC locked via a signed transaction that's verifiable without an admin
  2. Light client verification: SPL/ERC-20 minting triggered by verifiable proofs from RustChain
  3. Decentralized relayers: Multiple independent nodes process releases

But for Phase 1? The transparent ledger + admin key model is honest about its trust assumptions and gets real wRTC into the hands of RustChain supporters quickly.

Resources

Top comments (0)