Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Xythum-Labs/frost-evm-base-contracts

Repository files navigation

Xythum: A Trust-Minimized, FROST-Powered Cross-Chain Asset Bridge

A production-grade, multi-chain architecture for secure asset transfer between EVM-compatible networks, secured by a decentralized network of off-chain nodes running the FROST threshold signature scheme.


1. Vision & Core Problem

The proliferation of blockchain ecosystems has created a fragmented landscape, necessitating bridges to transfer value between them. However, the majority of existing bridges rely on centralized or quasi-centralized security models that introduce significant single points of failure and trust. These models typically include:

  • Centralized Multisig: Control of assets is vested in a small, known set of keyholders. The compromise of a majority of these keys leads to a total loss of funds. The identities of the signers are often public, making them high-value targets.
  • MPC/TSS with Centralized Operator: While Multi-Party Computation (MPC) or Threshold Signature Schemes (TSS) are an improvement, many implementations are operated by a single, centralized entity. If this entity's infrastructure is compromised, it can censor transactions or collude to steal funds.

Xythum is engineered to solve this problem. Our vision is to create a bridging protocol that is as secure and decentralized as the blockchains it connects. We achieve this by replacing centralized operators and small multisig committees with a large, dynamic, and anonymous set of off-chain nodes that collectively authorize transactions using the Flexible Round-Optimized Schnorr Threshold (FROST) signature scheme.

2. Our Solution: FROST & True Decentralization

FROST is a state-of-the-art threshold signature scheme that allows a group of n participants to generate a valid Schnorr signature, provided at least t (the threshold) of them cooperate.

Why FROST is Superior for a Bridge:

  • Trust Minimization: No single participant, nor any group of fewer than t participants, can forge a signature or steal funds. The master private key never exists in its entirety, not even during the key generation phase.
  • Anonymity & Scalability: A FROST signature is indistinguishable from a standard single-party Schnorr signature. The on-chain verifier has no knowledge of which t participants signed, only that a valid signature was produced. This allows for a large, anonymous set of signers, making collusion and targeted attacks incredibly difficult.
  • Efficiency: Schnorr signature verification is highly efficient on-chain, leading to lower gas costs compared to complex multisig validation logic.

High-Level System Architecture

The Xythum protocol consists of three primary actors interacting in a simple, robust flow.

|             | -------------------------------------> |                      |
|    User     |                                        |   On-Chain Contracts |
| (on Chain A)|      5. Receives minted assets         |      (Bridge.sol)      |
|             | <------------------------------------- |                      |
+-------------+      on Chain B                        +----------------------+
                                                                 |  2. Emits "Burned" Event
                                                                 |
                                                                 v
+-----------------------------------------------------------------------------------------------+
|                                      Off-Chain Network                                        |
|                                                                                               |
|  +---------------------+   3. Detects Event,   +------------------------------------------+   |
|  |                     |  Constructs Payload,  |                                          |   |
|  |  Coordinator Node   | --------------------->|          Participant Nodes (n)           |   |
|  | (The Orchestrator)  |  Initiates Signing    |           (The Notaries)                 |   |
|  |                     |                       |                                          |   |
|  +---------------------+   4. Aggregates       +------------------------------------------+   |
|           ^              Signatures, Broadcasts|                    ^                         |
|           |              `execute` tx to       |                    | 4. Signing Ceremony     |
|           |              Chain B               |                    | (Commit & Sign)         |
|           +------------------------------------+--------------------+                         |
|                                                                                               |
+-----------------------------------------------------------------------------------------------+

3. Key Features

  • Multi-EVM & Multi-Asset: Architected from the ground up to support any EVM-compatible chain and any ERC-20 or native asset, configured via the on-chain AssetManager.
  • Trust-Minimized Security: Secured by FROST, eliminating single points of failure and centralized control over user funds.
  • Persistent & Recoverable: The Coordinator uses a persistent database to track all bridging operations, ensuring that no transaction is ever lost, even if the service restarts.
  • Real-Time & Responsive: A WebSocket-powered frontend provides users with live, step-by-step progress updates for their in-flight transactions.
  • Extensible by Design: The on-chain execute function and modular off-chain components allow for the easy addition of new assets, chains, and even bridging mechanisms in the future.

4. Tech Stack

The project is a carefully curated monorepo combining best-in-class technologies for security, performance, and developer experience.

Category Technology / Library Rationale
On-Chain Solidity, Foundry (Anvil, Forge), OpenZeppelin Contracts v5+ Industry-standard for smart contracts; modern, fast, and robust testing framework.
Off-Chain Rust, Tokio, Axum, frost-secp256k1, ethers-rs, sqlx, tokio-tungstenite Performance, memory safety, and concurrency for the core backend logic.
Frontend Next.js, React, TypeScript, Tailwind CSS, wagmi, viem, swr A modern, type-safe, and highly performant stack for building reactive dApps.
DevOps & Tools sqlx-cli, jq, Shell Scripts For automated database migrations, testing workflows, and deployment.

5. Deep Dive: How the Bridge Works

The Xythum protocol is an intricate dance between the user, the on-chain contracts, and the off-chain network. Understanding the specific roles and responsibilities of each component is key to grasping the security and efficiency of the system.

End-to-End Flow Walkthroughs

We support two primary bridging models, distinguished by the type of asset on the source chain.

Flow A: Burn-and-Mint (For Protocol-Native Synthetics)

This is the standard flow for assets created and managed by the bridge protocol itself, such as our xBTC token.

  1. User Action (Chain A): A user with xBTC on Chain A decides to bridge 50 xBTC to Chain B. They connect their wallet to the frontend and submit a transaction that calls the burn() function on the Bridge.sol contract on Chain A.
    • Parameters: assetId (the global ID for xBTC), destinationChainId (Chain B's ID), and amount (50 tokens).
  2. On-Chain Event (Chain A): The Bridge.sol contract verifies that xBTC is a registered SYNTHETIC_MINT asset on Chain A. It then calls the burnFrom() function on the xBTC token contract, destroying the user's 50 tokens. Finally, it emits a Burned event containing a unique operationId, the user's address, the asset ID, the destination chain, and the amount.
  3. Coordinator Detection: The off-chain Coordinator service, which is subscribed to events from all supported chains, detects the Burned event on Chain A. It immediately logs this event to its persistent database, creating a new record with the status DETECTED.
  4. Coordinator Processing: In its main processing loop, the Coordinator picks up the DETECTED operation. It constructs a new, ABI-encoded payload for the destination chain.
    • Payload Content: abi.encode(bytes4(keccak256("MINT")), operationId, userAddress, assetId, amount)
  5. FROST Signing Ceremony: The Coordinator initiates the two-round FROST signing ceremony with the network of Participant nodes to get a signature for the keccak256(payload).
    • Round 1 (Commit): It sends a commit request to all Participants. A threshold (t) of them respond with their nonce commitments.
    • Round 2 (Sign): The Coordinator assembles the SigningPackage and sends it to the successful participants, who respond with their individual signature shares.
  6. Coordinator Action (Chain B): The Coordinator aggregates the shares into a single, valid Schnorr signature. It then broadcasts a transaction to the Bridge.sol contract on Chain B, calling the execute() function with the payload and the aggregated signature.
  7. On-Chain Execution (Chain B): The Bridge.sol contract on Chain B:
    • Verifies that the FROST signature is valid and has not been used before.
    • Decodes the payload and sees the MINT action.
    • Verifies that xBTC is a SYNTHETIC_MINT asset on Chain B.
    • Calls the mint() function on the xBTC token contract, creating 50 new tokens in the user's wallet on Chain B.

Flow B: Lock-and-Mint (For External or Native Assets)

This flow is used for assets that exist outside our protocol, such as native ETH or a canonical USDC token on a specific chain.

  1. User Action (Chain A): A user wants to bridge 1 ETH to Chain B. They call the lock() function on Bridge.sol on Chain A.
    • Parameters: assetId (the global ID for ETH), destinationChainId, and amount. The user also sends 1 ETH with the transaction (msg.value).
  2. On-Chain Event (Chain A): The Bridge.sol contract verifies that ETH is a NATIVE_LOCK asset. It transfers the 1 ETH from the user into its dedicated, secure VaultETH contract. It then emits a Locked event with a unique operationId and the transaction details.
  3. Coordinator & Signing: The Coordinator detects the Locked event and performs the exact same processing and signing ceremony steps (3, 4, and 5) as in the Burn-and-Mint flow. The payload is identical in structure: abi.encode(bytes4(keccak256("MINT")), ...).
  4. On-Chain Execution (Chain B): The execute() function is called on Chain B. The contract decodes the MINT payload, looks up the ETH asset ID in its AssetManager, and sees that on Chain B, this asset corresponds to a synthetic token (xETH). It then calls mint() on the xETH token contract, creating 1 xETH for the user.

The reverse flow, burn-and-release, follows the same pattern, where a Burned event for xETH on Chain B results in the Coordinator authorizing a RELEASE payload that calls the withdraw() function on the VaultETH on Chain A.

The Role of the On-Chain Contracts (The "Constitution")

The smart contracts are the immutable law of the protocol. They define the rules that even the powerful off-chain network cannot break.

  • Bridge.sol: The central authority. Its primary responsibilities are to validate user actions (lock, burn), securely hold ownership of the vaults, and act as the sole gateway for authorized actions via the execute function. Its most important job is to rigorously verify the FROST signature against the known group public key.
  • AssetManager.sol: The source of truth for all assets. It provides the crucial context that allows the system to be multi-asset. By storing the AssetType, it enables the Bridge.sol contract to enforce correct behavior (e.g., preventing a lock on a synthetic asset).
  • VaultETH.sol & VaultERC20.sol: These are simple, "dumb" treasure chests. Their only purpose is to hold assets securely. They have no logic other than to accept deposits from and release funds to their single owner: the Bridge.sol contract. This minimalist design drastically reduces their attack surface.

The Role of the Off-Chain Network (The "Operators")

The off-chain network is the dynamic, active component of the protocol.

  • The Coordinator (The Orchestrator): This is a complex, stateful, and trusted service. Its roles are:

    • Observer: Listens to all configured chains for events.
    • Clerk: Persists the state of all bridge operations in its database.
    • Diplomat: Orchestrates the multi-round signing ceremony with the Participants.
    • Treasurer: Holds funds (in its own EOA) to pay for gas when broadcasting transactions.
    • Bootstrapper: Facilitates the trusted dealer setup for key generation.
    • API Provider: Serves historical data and real-time updates to the frontend.
  • The Participants (The Notaries): These are simple, secure, and largely stateless signing oracles. Their only responsibility is to hold a single FROST key share and use it to sign valid requests from the Coordinator. Their minimalist design is a core security principle: a smaller attack surface means a more secure network.

Security Model & Assumptions

The security of the Xythum bridge rests on the following assumptions:

  1. Cryptographic Security: The underlying secp256k1 elliptic curve and SHA-256 hash function are secure.
  2. Honest Majority of Participants: At least t out of n participants are honest and will not collude to sign malicious payloads. The security of the bridge is defined by this t-of-n threshold.
  3. Coordinator Liveness & Integrity: The Coordinator is assumed to be online and to construct payloads correctly based on on-chain events. While it cannot forge signatures or steal funds on its own, a compromised or malicious Coordinator could censor transactions or attempt to replay old ones (which the contract would reject).
  4. Smart Contract Immutability: The deployed on-chain contracts are assumed to be non-upgradable and their logic is the final arbiter of truth.

6. On-Chain Architecture: A File-by-File Audit

The on-chain portion of the Xythum protocol is designed with three core principles in mind: modularity, security minimalism, and extensibility. The contracts are the rigid "constitution" of the bridge, enforcing rules that the off-chain operators must follow.

Directory Structure (src/)

src
├── FROST.sol
├── Bridge.sol
├── WETH.sol
├── managers
│   ├── AssetManager.sol
|   ├── IManagers.sol
│   └── ChainManager.sol
├── mocks
│   ├── BridgableToken.sol
│   └── MockERC20.sol
└── vaults
    ├── VaultERC20.sol
    └── VaultETH.sol

Bridge.sol

  • Role: The central nervous system and authority of the on-chain protocol.

  • Design Pattern: The Central Authority. This contract is the sole entry point for all state-changing operations authorized by the off-chain network. It owns the vaults and is the only entity permitted to initiate withdrawals. User-facing functions (lock, burn) are entry points to an asynchronous process, while the execute function is the exit point, controlled exclusively by the FROST group.

  • Key Feature: The Unified execute Function. solidity function execute(bytes calldata payload, uint256 rx, uint256 ry, uint256 z)

    This is the most important design choice for extensibility. Instead of having separate, rigid functions like mint and release, we have a single, generic entry point. The payload is an ABI-encoded bytestring that contains an action selector (e.g., bytes4(keccak256("MINT"))) and its corresponding data. This allows us to introduce new actions (e.g., REDEEM_LIQUIDITY, VOTE_ON_UPGRADE) in the future by simply teaching the Coordinator to build new payload formats, without ever needing to redeploy or alter this core contract.

  • Assumptions & Tradeoffs:

    • The keccak256 calculations for signature and payload hashing have been optimized with inline assembly to reduce gas costs, a critical consideration for a contract with frequent, complex interactions.
    • The contract relies on a userNonces mapping to generate a unique operationId. This is a robust method for preventing replay attacks where a user might try to lock the same amount twice in the same block.
  • Pending Improvements for Production:

    • On-Chain Confirmation Logic: The current BROADCASTED status is optimistic. A truly robust system would require a two-step confirmation. The Coordinator would broadcast the transaction and then, after several blocks, submit a second transaction (or have a separate set of "confirmer" nodes do so) proving the first transaction was successfully included and not reverted. This would add a CONFIRMED status to the on-chain event flow.
    • Emergency Pause: For a real-money system, an emergencyPause function, callable by the owner (or a separate security council), is non-negotiable. This would halt all lock, burn, and execute functions in the event of a suspected vulnerability or off-chain network compromise.

AssetManager.sol

  • Role: The on-chain source of truth for all assets.
  • Design Pattern: The On-Chain Registry. This contract's primary purpose is to provide context. The off-chain Coordinator and the on-chain Bridge both consult it to understand the "what" and "how" of an asset on any given chain.
  • Key Feature: The AssetType Enum. enum AssetType { NATIVE_LOCK, ERC20_LOCK, SYNTHETIC_MINT } This is the core of our multi-asset logic. By storing an asset's type on-chain, we empower the Bridge.sol contract to enforce rules. For example, it can programmatically revert if a user tries to lock an asset of type SYNTHETIC_MINT, as this action is logically invalid. This prevents a whole class of potential errors and exploits.
  • Pending Improvements for Production:
    • Richer Asset Metadata: The AssetInfo struct could be expanded to include the token's decimals. This would allow the on-chain contracts and off-chain services to handle amounts for different tokens (e.g., USDC with 6 decimals vs. WBTC with 8) without hardcoding values.
    • Ownership & Governance: In a fully decentralized future, ownership of this contract could be transferred to a DAO, which would vote on proposals to register new assets.

Vaults: VaultETH.sol & VaultERC20.sol

  • Role: Secure, isolated treasuries for locked assets.
  • Design Pattern: The Minimalist Treasury. These contracts are intentionally "dumb." Their logic is as minimal as possible to reduce their attack surface.
    • They are owned only by the Bridge.sol contract.
    • They have no functions callable by external users.
    • Their only functionality is to hold one specific asset and release it upon command from the Bridge.
  • Security Insight: This pattern is a direct lesson learned from numerous DeFi exploits where complex treasury or vault contracts with multiple functions were compromised. By isolating each asset in its own simple, single-purpose vault, we contain the potential blast radius of any unforeseen bug. If a bug were found related to one specific ERC20 token's interaction, only the funds in that token's vault would be at risk, not the entire protocol's treasury.
  • Pending Improvements for Production:
    • These contracts are already near-production quality due to their simplicity. No significant improvements are needed.

7. Off-Chain Architecture: A File-by-File Audit

The off-chain system is a Rust workspace composed of communicating services that collectively act as the bridge's operators. It is designed for performance, concurrency, and—above all—correctness, especially in its cryptographic operations.

Directory Structure (offchain/)

offchain
├── Cargo.toml
├── .env
├── config
│   └── Development.toml
├── migrations
│   ├── <timestamp>_migrate_init.sql
│   └── <timestamp>_add_tx_hashes_to_operations.sql
├── bridge-common
│   └── src
│       ├── api.rs
│       └── lib.rs
├── coordinator
│   ├── sqlx-data.json
│   └── src
│       ├── broadcaster.rs
│       ├── chain_monitor.rs
│       ├── db.rs
│       ├── orchestrator.rs
│       ├── participant_client.rs
│       ├── service.rs
│       ├── websocket.rs
│       └── main.rs
└── participant
    └── src
        └── main.rs

The coordinator Crate

The Coordinator is the brain of the off-chain network. It is a stateful, long-running service responsible for observing, processing, and executing all bridging operations.

  • main.rs:

    • Role: The main entry point and CLI command parser.
    • Design Pattern: A multi-command CLI using argh. It cleanly separates one-off administrative tasks (keygen, sign) from the main service loop (start). The start command is responsible for instantiating all modules and launching the primary API/WebSocket server and the core service logic as concurrent tokio tasks.
  • service.rs:

    • Role: The "Conductor" or main application loop.
    • Design Pattern: A state-driven, polling loop. Instead of reacting directly to events, its main loop periodically queries the database for operations in the DETECTED state. For each pending operation, it spawns a new, independent tokio task (handle_operation). This is a critical design choice for concurrency and resilience. A failure in processing one operation will not halt the processing of others. It also contains the graceful shutdown logic, ensuring all tasks are properly terminated.
  • db.rs & migrations/:

    • Role: The persistent memory of the Coordinator.
    • Design Pattern: A durable state machine store. We use sqlx in "offline" mode with sqlx-data.json, which provides compile-time verification of all SQL queries against our schema. This is a powerful correctness feature that prevents an entire class of runtime bugs. The operations table is the single source of truth for the state of any given bridge transfer.
    • Pending Improvements:
      • Database Indexing: For production scale, the operations table will need indexes on user_address, status, and operation_id to ensure fast queries.
      • Connection Pooling: For high throughput, the SqlitePoolOptions should be configured with a larger connection pool.
  • chain_monitor.rs:

    • Role: The "Eyes and Ears" of the protocol.
    • Design Pattern: A set of independent, long-running WebSocket clients. A separate monitor_chain task is spawned for each configured blockchain. Upon detecting a Burned or Locked event, its sole responsibility is to write a new record to the database with the DETECTED status. This decouples event detection from event processing.
  • orchestrator.rs:

    • Role: The cryptographic heart of the Coordinator.
    • Design Pattern: A client orchestrator for a distributed ceremony. The conduct_signing_ceremony function is designed to be resilient, broadcasting requests to all known Participants and only proceeding if a threshold number of them respond successfully. This makes the system robust against individual node failures.
  • broadcaster.rs:

    • Role: The "Hands" of the protocol.
    • Design Pattern: A resilient, retrying transaction broadcaster. It uses an exponential backoff strategy (tokio-retry) to handle temporary RPC node failures, ensuring that a valid, signed transaction will eventually be included on-chain.
  • websocket.rs:

    • Role: The real-time communication channel to the frontend.
    • Design Pattern: A secure, subscription-based broadcast model. It runs as part of a unified axum server. The logic is carefully designed to hold Mutex locks for the absolute minimum duration, preventing the deadlocks that plagued earlier development iterations. A client must first send a valid Subscribe message for a specific operation_id, and will only receive updates for that operation, ensuring privacy and efficiency.

The participant Crate

The Participant is the simple, secure "notary" of the network. Its design is intentionally minimal.

  • main.rs:
    • Role: A secure signing oracle.
    • Design Pattern: A minimal, stateless API server. It exposes only three endpoints: /install-key to be provisioned by the Coordinator, and the two round-based endpoints /commit and /sign. It has no knowledge of blockchains, assets, or the overall state of the bridge. Its state management is limited to the ephemeral NonceStore for the duration of a single signing ceremony, a critical security feature to prevent nonce reuse.
    • Security Principle: The extreme simplicity of the Participant is its greatest strength. With a minimal attack surface and only one core responsibility (signing), it is significantly easier to secure and audit than a monolithic application.

Lessons Learned: Critical Bugs & Fixes

Our development journey was not without its challenges. The following are the most critical bugs we encountered and the lessons they imparted, which are now enshrined in our architecture:

  1. The sqlx Compile-Time Bug:

    • Symptom: Persistent, confusing E0277 trait bound errors from sqlx::query_as! at compile time.
    • Root Cause: sqlx's compile-time verification requires either a live database connection or an offline metadata file. Furthermore, its type inference from SELECT * is conservative, leading to mismatches between i64 vs Option<i64> and NaiveDateTime vs DateTime<Utc>.
    • The Fix: We implemented the definitive "offline" workflow: 1. cargo sqlx database create, 2. cargo sqlx migrate run, 3. cargo sqlx prepare --workspace. We also made our Rust structs and SELECT queries perfectly and explicitly match, leaving no room for ambiguity.
  2. The WebSocket Deadlock:

    • Symptom: The entire Coordinator service would hang indefinitely as soon as it tried to process the first event.
    • Root Cause: A classic deadlock. The handle_connection task acquired a Mutex lock on the subscriptions map and never released it before entering its main message-forwarding loop. The handle_operation task then tried to acquire the same lock to broadcast a message, and waited forever.
    • The Fix: A complete re-architecture of websocket.rs. The logic was changed to acquire the lock only for the brief moment needed to get a clone of the broadcast sender or subscribe a new receiver. All long-running loops now operate entirely outside the lock.
  3. The Frontend State & Test Race Conditions:

    • Symptom: Intermittent InvalidSignature and "File not found" errors during forge test runs.
    • Root Cause: forge test runs tests in parallel by default. Multiple test instances were calling the Coordinator CLI, which was reading/writing to the same hardcoded file paths (.frost/keys/key.pub, signature.bin), causing a race condition where one test would overwrite another's data.
    • The Fix: A multi-layered solution. The test suite was refactored so that each test function generates its own unique, isolated configuration file in a temporary directory and passes that config path to the CLI. This guarantees perfect state isolation between parallel test runs.

8. Frontend Architecture: A Component-by-Component Audit

The frontend is a modern, reactive, and type-safe Next.js application designed to provide a seamless and intuitive user experience. It handles the complexities of wallet interaction, on-chain data fetching, and real-time updates, presenting the user with a clean and simple interface.

Directory Structure (frontend/)

frontend
├── app
│   ├── history
│   │   └── page.tsx
│   ├── mint
│   │   └── page.tsx
│   ├── layout.tsx
│   └── page.tsx
├── components
│   ├── bridge
│   │   ├── amount-input.tsx
│   │   ├── asset-select.tsx
│   │   ├── bridge-card.tsx
│   │   ├── chain-select.tsx
│   │   └── progress-tracker.tsx
│   ├── history
│   │   └── history-list.tsx
│   ├── wallet
│   │   ├── network-switcher.tsx
│   │   └── wallet-button.tsx
│   ├── ui
│   │   └── ... (shadcn/ui components)
│   └── ... (header, footer, etc.)
├── hooks
│   ├── useBridgeProgress.ts
│   ├── useGasEstimate.ts
│   └── useMint.ts
├── lib
│   ├── abi
│   │   ├── Bridge.json
│   │   └── BridgableToken.json
│   ├── constants.ts
│   ├── contracts.ts
│   └── metadata.ts
├── providers
│   └── Web3Provider.tsx
└── public
    └── icons
        └── ...

Design Philosophy: Custom Hooks for Logic, Components for Display

Our frontend architecture follows a strict separation of concerns, which is critical for maintainability and scalability:

  • Components (components/): These are primarily responsible for rendering JSX and handling user input events. They contain minimal business logic and are kept as "dumb" as possible. Their job is to display data and call functions provided by hooks.
  • Hooks (hooks/): This is where all the complex logic resides. Blockchain interactions (reading data, writing transactions), state management for asynchronous flows (like our WebSocket connection), and complex calculations (like gas estimation) are all encapsulated within custom, reusable hooks.
  • Providers (providers/): These components provide application-wide context, such as the wagmi configuration for wallet connectivity.

This pattern makes the codebase incredibly clean. If you want to understand how we fetch balances, you look at the useReadContract calls in BridgeCard.tsx. If you want to understand how we track progress, you look at useBridgeProgress.ts. If you want to change how a button looks, you look in the component's JSX.

Key Hooks

  • useBridgeProgress.ts:

    • Role: The real-time engine for tracking an in-flight bridge operation.
    • Design Pattern: A stateful WebSocket client manager. It exposes a subscribe(operationId) function. When called, it establishes a new, clean WebSocket connection to the Coordinator and sends a subscription message for that specific operationId. It listens for StatusUpdate messages from the backend that match its subscribed ID and exposes the latestUpdate to the consuming component. It is designed to be reset and reused for multiple bridge operations.
  • useGasEstimate.ts:

    • Role: Provides real-time transaction cost estimates.
    • Design Pattern: A reactive calculation engine. It takes a transaction configuration object (ABI, address, function name, args) as input. It then uses wagmi's useEstimateGas and useGasPrice hooks to fetch the necessary on-chain data and calculates the final fee in both native currency and a mocked USD value. It automatically re-evaluates whenever the input transaction parameters change (e.g., switching from approve to burn).
  • useMint.ts:

    • Role: Encapsulates the entire "Mint Test Tokens" flow.
    • Design Pattern: A complete transaction lifecycle manager. It exposes a single mint(amount) function. Internally, it handles preparing the transaction, calling writeContractAsync, and then uses useWaitForTransactionReceipt to provide reactive state variables (isPending, isConfirming, isSuccess) that the UI can use to display loading states and feedback toasts.

Key Components

  • BridgeCard.tsx:

    • Role: The central controller for the entire bridging experience.
    • Design Pattern: A complex, stateful "container" component. It is the primary consumer of almost all our custom hooks and wagmi hooks. It fetches all on-chain data, derives the application's state (e.g., needsApproval, canBridge), and passes data down to the simpler "display" components. It is responsible for orchestrating the entire multi-step transaction flow (approve then burn).
  • HistoryList.tsx:

    • Role: Displays a user's past and pending bridge operations.
    • Design Pattern: An auto-refreshing data-fetcher. It uses the swr library to poll the Coordinator's /history/:userAddress API endpoint every 5 seconds. This provides a simple but effective "live" view of the user's history without the complexity of a WebSocket connection for this less time-critical feature.

Pending UI/UX Improvements

The Coordinator needs to be updated to mark transactions as FAILED in the database. The frontend HistoryRow component then needs to be updated to display this failed state correctly, including the reason for failure.

9. Local Development & Testing

This project is a complex, multi-component system that requires a specific startup sequence. This guide provides the definitive instructions for setting up and running the entire bridge ecosystem on a local machine for development and testing.

Prerequisites

Before you begin, ensure you have the following software installed and available in your system's PATH:

  • Rust & Cargo: Required for the off-chain Coordinator and Participant nodes. (https://www.rust-lang.org/tools/install)
  • Foundry (Forge & Anvil): Required for compiling, testing, and deploying the on-chain smart contracts, and for running local EVM nodes. (https://book.getfoundry.sh/getting-started/installation)
  • Node.js & npm (or yarn/pnpm): Required for the frontend web application. (https://nodejs.org/)
  • sqlx-cli: A command-line tool for managing the Coordinator's database migrations and compile-time query verification. Install it by running:
    cargo install sqlx-cli
  • jq (Recommended): A command-line JSON processor that the test-flow.sh script uses for parsing deployment data.

Local Development Setup: The Definitive Guide

To run the full end-to-end system, you will need approximately 8 terminal windows.

Step 1: Initial Backend Setup (One-Time Only)

Before the first run, you need to prepare the off-chain database.

  1. Navigate to the offchain directory:
    cd offchain
  2. Create the .env file: This file is used by sqlx-cli to locate the database.
    echo "DATABASE_URL=sqlite:./bridge.db" > .env
  3. Create and Migrate the Database:
    cargo sqlx database create
    cargo sqlx migrate run
  4. Prepare the Compile-Time Query Data: This generates the sqlx-data.json file.
    cargo sqlx prepare --workspace
  5. Build the Rust Binaries:
    cargo build
  6. Return to the project root:
    cd ..

Step 2: Start All Services (Session-Based)

This sequence must be followed every time you start a new development session.

  1. Terminal 1: Start Anvil (Chain A):

    anvil --chain-id 31337
  2. Terminal 2: Start Anvil (Chain B):

    anvil --port 8546 --chain-id 31338
  3. Terminals 3, 4, 5: Start Participant Nodes: Each participant runs as a separate process. They will start and wait for the Coordinator to distribute keys.

    # In Terminal 3:
    RUST_LOG=info cargo run --package participant -- --name "participant-1" --config offchain/config/Development.toml
    
    # In Terminal 4:
    RUST_LOG=info cargo run --package participant -- --name "participant-2" --config offchain/config/Development.toml
    
    # In Terminal 5:
    RUST_LOG=info cargo run --package participant -- --name "participant-3" --config offchain/config/Development.toml
  4. Terminal 6: Deploy Contracts & Generate Keys: This script is the master bootstrapper. It deploys all smart contracts and runs the Coordinator's keygen command to provision the running Participants.

    # Set the default Anvil private key for the deployer
    export PRIVATE_KEY=0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80
    
    forge script script/Deploy.s.sol --rpc-url http://localhost:8545 --broadcast --ffi --via-ir

    This will create a deployments.json file in the project root.

  5. Terminal 7: Start the Coordinator Service: Before starting, you must update the configuration with the fresh contract addresses.

    • Manual Step: Open deployments.json, copy the bridge addresses for chainA and chainB.
    • Manual Step: Open offchain/config/Development.toml and paste these addresses into the bridge_address fields for chains.chain-a and chains.chain-b.

    Now, start the service.

    RUST_LOG=info cargo run --package coordinator -- --config offchain/config/Development.toml start

    You will see logs confirming it has connected to the database, API server, WebSocket server, and chain monitors.

  6. Terminal 8: Start the Frontend:

    cd frontend
    npm install
    npm run dev

    You can now access the web UI at http://localhost:3000.

Running the End-to-End Test Flow

With all services running, you can simulate a user's bridge operation with a single command from the project root.

  1. Ensure PRIVATE_KEY is set:
    export PRIVATE_KEY=0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80
  2. Run the script:
    ./test-flow.sh
    This script will automatically mint xBTC, approve the bridge, and call the burn function. You can observe the entire lifecycle in real-time by watching the logs in the Coordinator terminal (Terminal 7) and the progress tracker in the web UI.

On-Chain Contract Testing

The project contains a comprehensive suite of unit and integration tests for the smart contracts, built with Foundry. These tests verify the on-chain logic in isolation and are critical for ensuring contract correctness.

  • Running all contract tests:
    # This requires the off-chain binaries to be built first
    cargo build
    
    # Run tests with the FFI flag, sequentially to prevent race conditions
    forge test --ffi -j 1
  • Lack of End-to-End Automated Testing: A current limitation of the project is the lack of a fully automated end-to-end test that programmatically starts all services and verifies a full bridge cycle. The current process relies on the manual startup sequence and the test-flow.sh script. A future improvement would be to create a master test runner (e.g., using docker-compose and a test script) that automates this entire sequence.

10. The Future: Roadmap & Extensibility

The current state of the Xythum bridge represents a robust, feature-complete prototype for burn-and-mint style asset transfers across EVM chains. The underlying architecture, however, was intentionally designed to be a foundation for a much broader and more ambitious vision.

Long-Term Vision: Evolving into a Universal Protocol

The modular design of the on-chain contracts and off-chain services allows for significant future expansion beyond the current implementation.

Non-EVM Chain Support (e.g., Bitcoin, Zcash)

The current architecture is primed for UTXO-based chain support. Here is the conceptual path:

  1. On-Chain: No changes would be needed to the EVM contracts. The FROST group's public key corresponds to a valid secp256k1 key, which can control a Bitcoin P2TR (Taproot) address.
  2. Off-Chain (Coordinator):
    • A new UTXO-Monitor service would be created within the Coordinator. It would connect to a Bitcoin node (or an indexer like an Electrum server) to watch for incoming transactions to the bridge's Taproot address.
    • Upon detecting a UTXO lock, it would write a DETECTED_UTXO operation to the database.
  3. Protocol Flow:
    • The handle_operation logic would be expanded. Upon seeing a DETECTED_UTXO event, it would construct a MINT payload for the destination EVM chain, just as it does now.
    • The execute function on the EVM chain would mint a wrapped xBTC token.
    • The reverse flow (burn xBTC on an EVM chain) would trigger the Coordinator to construct and sign a Bitcoin transaction to send the native BTC from the bridge's Taproot address to the user's specified Bitcoin address.

Advanced Liquidity & Bridging Models

The unified execute function is the key to unlocking new bridging paradigms without requiring a protocol overhaul.

  • Hybrid Liquidity Hubs: A future version could deploy liquidity pools (like Uniswap V2 pairs) on popular chains. When a user initiates a bridge, the Coordinator could be programmed with more advanced logic:
    1. Check the destination Vault balance.
    2. If the vault is empty, check the on-chain liquidity pool.
    3. If the pool has sufficient liquidity, the Coordinator could authorize an EXECUTE_SWAP_AND_RELEASE payload, which calls a function on the bridge to pull funds from the LP and send them to the user, providing a much faster settlement.
    4. Only if both the vault and the LP are depleted would it fall back to the canonical mint flow.

11. Architectural Decisions & Critical Lessons

The current architecture is the result of a rigorous, iterative development process. Several critical bugs and design challenges were encountered along the way. Documenting these moments is essential for understanding why the system is built the way it is and for preventing similar issues in the future.

Lesson 1: The Necessity of Absolute State Isolation in Testing

  • Problem: Early versions of our on-chain test suite (forge test) were plagued by intermittent and seemingly random InvalidSignature and "File not found" errors. One test run would pass, and the next would fail with a different error.
  • Root Cause Analysis: We discovered a severe race condition. forge test runs tests in parallel by default. Our tests, which used FFI to call our off-chain Coordinator CLI, were all causing the Coordinator to read from and write to a single, shared set of files (e.g., offchain/.frost/keys/key.pub, signature.bin). One test was overwriting the cryptographic materials that another test was actively using, leading to a complete breakdown of state.
  • The Definitive Solution: We re-architected the entire testing framework to enforce absolute state isolation.
    1. The setUp function in Bridge.t.sol was removed.
    2. Each test function now begins by calling a helper (_setupIsolatedTest) that creates a unique, temporary directory for that specific test run (e.g., offchain/.frost/erc20_trip/).
    3. The test dynamically generates a unique TestConfig.toml file within that directory.
    4. The FFI call was modified to pass the path to this unique config file (--config ...).
    5. Finally, we enforce sequential test execution with forge test --ffi -j 1 as a foolproof guardrail.
  • Core Principle: Any test that interacts with an external process or the file system must operate within a unique, isolated namespace to prevent state pollution.

Lesson 2: The sqlx Compile-Time Verification Workflow

  • Problem: The initial integration of sqlx resulted in persistent compile-time errors, specifically E0277 (trait bound not satisfied) and "unable to open database file," even when the Rust code appeared logically correct.
  • Root Cause Analysis: We learned that sqlx's powerful compile-time query verification requires a live database connection during compilation to validate SQL syntax and type mappings. Furthermore, its type inference is extremely strict; it requires a perfect match between the database schema and the Rust structs, including nullability (Option<T>) and specific datetime types (NaiveDateTime for SQLite).
  • The Definitive Solution: We adopted the official "offline" sqlx workflow, which is now a mandatory part of the development setup.
    1. cargo sqlx database create & migrate run: A clean database with the correct schema is created first.
    2. cargo sqlx prepare --workspace: This command is run once. It connects to the live database, verifies all query! macros in the codebase, and generates a sqlx-data.json file.
    3. Compile Time: Subsequent cargo build or cargo check commands use the sqlx-data.json file for verification, removing the need for a live database connection during compilation.
  • Core Principle: sqlx demands a rigorous setup process. The schema must be explicitly migrated and prepared before the code can be compiled, ensuring a higher degree of type safety at runtime.

Lesson 3: The Dangers of Mutex Deadlocks in Concurrent Services

  • Problem: The first implementation of the WebSocket server caused the entire Coordinator service to hang indefinitely as soon as it tried to process its first event.
  • Root Cause Analysis: A classic deadlock. The task handling a client's WebSocket connection (handle_connection) would acquire a Mutex lock on the map of subscriptions and then enter an infinite loop to listen for messages, never releasing the lock. Meanwhile, the task processing the bridge operation (handle_operation) would try to acquire the same lock to broadcast a status update, and would wait forever.
  • The Definitive Solution: The websocket.rs module was completely rewritten to adhere to the principle of minimal lock contention.
    1. Locks are now acquired only for the brief, synchronous moment needed to get a clone of a broadcast sender or to subscribe a new receiver.
    2. All long-running, await-ing loops now operate entirely outside of the Mutex guard's scope.
  • Core Principle: Never hold a Mutex lock across an .await point if it can be avoided. Locks should be held for the shortest duration possible to perform synchronous, atomic operations.

12. Production Readiness & Security Considerations

The current implementation is a functionally complete, end-to-end prototype. However, deploying this system with real user funds requires an additional, rigorous phase of security hardening, monitoring, and infrastructure refinement. The following is a critical checklist of what is still pending to achieve a production-grade level of trust and reliability.

On-Chain Security

  • Formal Audit: The smart contracts have been built following best practices, but they have not undergone a formal security audit by a reputable third-party firm. This is the highest priority before any mainnet deployment.
  • Emergency Controls: The Bridge.sol contract currently lacks a critical emergencyPause function. In a production environment, a designated owner or security council must have the ability to pause all state-changing functions (lock, burn, execute) in the event of a discovered vulnerability or an off-chain network compromise.
  • Gas Optimizations: While some optimizations (like inline assembly for keccak256) have been made, a thorough gas analysis should be performed on all functions to ensure they are as efficient as possible, especially for L2 deployments where execution costs matter.

Off-Chain Service (Coordinator) Hardening

  • Configuration & Key Management:
    • HSM Integration: The Coordinator's private key, used for broadcasting transactions, is currently loaded from a configuration file. In production, this key must be stored in a Hardware Security Module (HSM) or a secure cloud equivalent (e.g., Google/AWS KMS) to prevent extraction from the server.
    • Secret Management: All sensitive configuration values (RPC URLs with API keys, the private key) should be loaded from a secure secret management service (like HashiCorp Vault or cloud-native secret managers) rather than a plaintext .toml file.
  • Database & State:
    • Production Database: SQLite is excellent for its simplicity but may not be suitable for high-throughput production workloads. Migrating to a production-grade database like PostgreSQL is recommended for better concurrency, scalability, and backup/restore capabilities.
    • Transaction Confirmation Logic: The current BROADCASTED state is optimistic. A production system must implement a "confirmation listener" that waits for a configurable number of blocks (N confirmations) on the destination chain before marking an operation as truly CONFIRMED. This protects against chain reorgs.
  • Error & Failure States:
    • Irrecoverable Errors: The backend state machine must be enhanced with a final FAILED status. If an operation fails for a non-transient reason (e.g., the on-chain execute call reverts due to a contract logic error), the operation should be marked as FAILED in the database with a clear failure_reason.
    • Alerting: The Coordinator must be integrated with an alerting system (like PagerDuty or Alertmanager). It should fire critical alerts for events like: repeated RPC failures, a drop in the number of responsive Participants, or a transaction that fails to get confirmed after a long period.

Frontend & User Security

  • RPC Endpoints: The frontend currently relies on public RPC endpoints configured in wagmi. In production, these should be high-availability, private RPCs (e.g., via Infura, Alchemy) to prevent rate-limiting and provide a stable user experience.
  • Input Validation: While the smart contracts have require statements, the frontend should perform more robust input validation to prevent users from accidentally sending transactions that are known to fail (e.g., bridging to a chain where the asset is not yet registered).
  • Phishing Protection: The useGasEstimate hook is a good first step, but a production UI should also include warnings about potential fee spikes or unusual transaction parameters to help protect users.

About

No description, website, or topics provided.

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published