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.
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.
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
tparticipants, 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
tparticipants 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.
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) |
| +------------------------------------+--------------------+ |
| |
+-----------------------------------------------------------------------------------------------+
- 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
executefunction and modular off-chain components allow for the easy addition of new assets, chains, and even bridging mechanisms in the future.
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. |
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.
We support two primary bridging models, distinguished by the type of asset on the source chain.
This is the standard flow for assets created and managed by the bridge protocol itself, such as our xBTC token.
- User Action (Chain A): A user with
xBTCon Chain A decides to bridge 50xBTCto Chain B. They connect their wallet to the frontend and submit a transaction that calls theburn()function on theBridge.solcontract on Chain A.- Parameters:
assetId(the global ID forxBTC),destinationChainId(Chain B's ID), andamount(50 tokens).
- Parameters:
- On-Chain Event (Chain A): The
Bridge.solcontract verifies thatxBTCis a registeredSYNTHETIC_MINTasset on Chain A. It then calls theburnFrom()function on thexBTCtoken contract, destroying the user's 50 tokens. Finally, it emits aBurnedevent containing a uniqueoperationId, the user's address, the asset ID, the destination chain, and the amount. - Coordinator Detection: The off-chain Coordinator service, which is subscribed to events from all supported chains, detects the
Burnedevent on Chain A. It immediately logs this event to its persistent database, creating a new record with the statusDETECTED. - Coordinator Processing: In its main processing loop, the Coordinator picks up the
DETECTEDoperation. It constructs a new, ABI-encodedpayloadfor the destination chain.- Payload Content:
abi.encode(bytes4(keccak256("MINT")), operationId, userAddress, assetId, amount)
- Payload Content:
- 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
SigningPackageand sends it to the successful participants, who respond with their individual signature shares.
- Round 1 (Commit): It sends a commit request to all Participants. A threshold (
- Coordinator Action (Chain B): The Coordinator aggregates the shares into a single, valid Schnorr signature. It then broadcasts a transaction to the
Bridge.solcontract on Chain B, calling theexecute()function with thepayloadand the aggregated signature. - On-Chain Execution (Chain B): The
Bridge.solcontract on Chain B:- Verifies that the FROST signature is valid and has not been used before.
- Decodes the
payloadand sees theMINTaction. - Verifies that
xBTCis aSYNTHETIC_MINTasset on Chain B. - Calls the
mint()function on thexBTCtoken contract, creating 50 new tokens in the user's wallet on Chain B.
This flow is used for assets that exist outside our protocol, such as native ETH or a canonical USDC token on a specific chain.
- User Action (Chain A): A user wants to bridge 1 ETH to Chain B. They call the
lock()function onBridge.solon Chain A.- Parameters:
assetId(the global ID forETH),destinationChainId, andamount. The user also sends 1 ETH with the transaction (msg.value).
- Parameters:
- On-Chain Event (Chain A): The
Bridge.solcontract verifies thatETHis aNATIVE_LOCKasset. It transfers the 1 ETH from the user into its dedicated, secureVaultETHcontract. It then emits aLockedevent with a uniqueoperationIdand the transaction details. - Coordinator & Signing: The Coordinator detects the
Lockedevent 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")), ...). - On-Chain Execution (Chain B): The
execute()function is called on Chain B. The contract decodes theMINTpayload, looks up theETHasset ID in itsAssetManager, and sees that on Chain B, this asset corresponds to a synthetic token (xETH). It then callsmint()on thexETHtoken contract, creating 1xETHfor 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 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 theexecutefunction. 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 theAssetType, it enables theBridge.solcontract to enforce correct behavior (e.g., preventing alockon 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: theBridge.solcontract. This minimalist design drastically reduces their attack surface.
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.
The security of the Xythum bridge rests on the following assumptions:
- Cryptographic Security: The underlying
secp256k1elliptic curve and SHA-256 hash function are secure. - Honest Majority of Participants: At least
tout ofnparticipants are honest and will not collude to sign malicious payloads. The security of the bridge is defined by thist-of-nthreshold. - 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).
- Smart Contract Immutability: The deployed on-chain contracts are assumed to be non-upgradable and their logic is the final arbiter of truth.
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.
src
├── FROST.sol
├── Bridge.sol
├── WETH.sol
├── managers
│ ├── AssetManager.sol
| ├── IManagers.sol
│ └── ChainManager.sol
├── mocks
│ ├── BridgableToken.sol
│ └── MockERC20.sol
└── vaults
├── VaultERC20.sol
└── VaultETH.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 theexecutefunction is the exit point, controlled exclusively by the FROST group. -
Key Feature: The Unified
executeFunction. 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
mintandrelease, we have a single, generic entry point. Thepayloadis 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
keccak256calculations 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
userNoncesmapping to generate a uniqueoperationId. This is a robust method for preventing replay attacks where a user might try tolockthe same amount twice in the same block.
- The
-
Pending Improvements for Production:
- On-Chain Confirmation Logic: The current
BROADCASTEDstatus 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 aCONFIRMEDstatus to the on-chain event flow. - Emergency Pause: For a real-money system, an
emergencyPausefunction, callable by the owner (or a separate security council), is non-negotiable. This would halt alllock,burn, andexecutefunctions in the event of a suspected vulnerability or off-chain network compromise.
- On-Chain Confirmation Logic: The current
- 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
Bridgeboth consult it to understand the "what" and "how" of an asset on any given chain. - Key Feature: The
AssetTypeEnum. 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 theBridge.solcontract to enforce rules. For example, it can programmatically revert if a user tries tolockan asset of typeSYNTHETIC_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
AssetInfostruct could be expanded to include the token'sdecimals. 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.
- Richer Asset Metadata: The
- 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.solcontract. - 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.
- They are owned only by the
- 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.
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.
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 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). Thestartcommand is responsible for instantiating all modules and launching the primary API/WebSocket server and the core service logic as concurrenttokiotasks.
-
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
DETECTEDstate. For each pending operation, it spawns a new, independenttokiotask (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
sqlxin "offline" mode withsqlx-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. Theoperationstable is the single source of truth for the state of any given bridge transfer. - Pending Improvements:
- Database Indexing: For production scale, the
operationstable will need indexes onuser_address,status, andoperation_idto ensure fast queries. - Connection Pooling: For high throughput, the
SqlitePoolOptionsshould be configured with a larger connection pool.
- Database Indexing: For production scale, the
-
chain_monitor.rs:- Role: The "Eyes and Ears" of the protocol.
- Design Pattern: A set of independent, long-running WebSocket clients. A separate
monitor_chaintask is spawned for each configured blockchain. Upon detecting aBurnedorLockedevent, its sole responsibility is to write a new record to the database with theDETECTEDstatus. 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_ceremonyfunction is designed to be resilient, broadcasting requests to all known Participants and only proceeding if athresholdnumber 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
axumserver. The logic is carefully designed to holdMutexlocks for the absolute minimum duration, preventing the deadlocks that plagued earlier development iterations. A client must first send a validSubscribemessage for a specificoperation_id, and will only receive updates for that operation, ensuring privacy and efficiency.
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-keyto be provisioned by the Coordinator, and the two round-based endpoints/commitand/sign. It has no knowledge of blockchains, assets, or the overall state of the bridge. Its state management is limited to the ephemeralNonceStorefor 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.
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:
-
The
sqlxCompile-Time Bug:- Symptom: Persistent, confusing
E0277trait bound errors fromsqlx::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 fromSELECT *is conservative, leading to mismatches betweeni64vsOption<i64>andNaiveDateTimevsDateTime<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 andSELECTqueries perfectly and explicitly match, leaving no room for ambiguity.
- Symptom: Persistent, confusing
-
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_connectiontask acquired aMutexlock on the subscriptions map and never released it before entering its main message-forwarding loop. Thehandle_operationtask 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.
-
The Frontend State & Test Race Conditions:
- Symptom: Intermittent
InvalidSignatureand "File not found" errors duringforge testruns. - Root Cause:
forge testruns 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.
- Symptom: Intermittent
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.
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
└── ...
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 thewagmiconfiguration 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.
-
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 specificoperationId. It listens forStatusUpdatemessages from the backend that match its subscribed ID and exposes thelatestUpdateto 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'suseEstimateGasanduseGasPricehooks 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 fromapprovetoburn).
-
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, callingwriteContractAsync, and then usesuseWaitForTransactionReceiptto provide reactive state variables (isPending,isConfirming,isSuccess) that the UI can use to display loading states and feedback toasts.
-
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
wagmihooks. 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 (approvethenburn).
-
HistoryList.tsx:- Role: Displays a user's past and pending bridge operations.
- Design Pattern: An auto-refreshing data-fetcher. It uses the
swrlibrary to poll the Coordinator's/history/:userAddressAPI 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.
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.
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.
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 thetest-flow.shscript uses for parsing deployment data.
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.
- Navigate to the
offchaindirectory:cd offchain - Create the
.envfile: This file is used bysqlx-clito locate the database.echo "DATABASE_URL=sqlite:./bridge.db" > .env
- Create and Migrate the Database:
cargo sqlx database create cargo sqlx migrate run
- Prepare the Compile-Time Query Data: This generates the
sqlx-data.jsonfile.cargo sqlx prepare --workspace
- Build the Rust Binaries:
cargo build
- 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.
-
Terminal 1: Start Anvil (Chain A):
anvil --chain-id 31337
-
Terminal 2: Start Anvil (Chain B):
anvil --port 8546 --chain-id 31338
-
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
-
Terminal 6: Deploy Contracts & Generate Keys: This script is the master bootstrapper. It deploys all smart contracts and runs the Coordinator's
keygencommand 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.jsonfile in the project root. -
Terminal 7: Start the Coordinator Service: Before starting, you must update the configuration with the fresh contract addresses.
- Manual Step: Open
deployments.json, copy thebridgeaddresses forchainAandchainB. - Manual Step: Open
offchain/config/Development.tomland paste these addresses into thebridge_addressfields forchains.chain-aandchains.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.
- Manual Step: Open
-
Terminal 8: Start the Frontend:
cd frontend npm install npm run devYou can now access the web UI at
http://localhost:3000.
With all services running, you can simulate a user's bridge operation with a single command from the project root.
- Ensure
PRIVATE_KEYis set:export PRIVATE_KEY=0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80 - Run the script:
This script will automatically mint
./test-flow.sh
xBTC, approve the bridge, and call theburnfunction. 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.
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.shscript. A future improvement would be to create a master test runner (e.g., usingdocker-composeand a test script) that automates this entire sequence.
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.
The modular design of the on-chain contracts and off-chain services allows for significant future expansion beyond the current implementation.
The current architecture is primed for UTXO-based chain support. Here is the conceptual path:
- On-Chain: No changes would be needed to the EVM contracts. The FROST group's public key corresponds to a valid
secp256k1key, which can control a Bitcoin P2TR (Taproot) address. - Off-Chain (Coordinator):
- A new
UTXO-Monitorservice 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_UTXOoperation to the database.
- A new
- Protocol Flow:
- The
handle_operationlogic would be expanded. Upon seeing aDETECTED_UTXOevent, it would construct aMINTpayload for the destination EVM chain, just as it does now. - The
executefunction on the EVM chain would mint a wrappedxBTCtoken. - The reverse flow (
burnxBTCon 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.
- The
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:
- Check the destination
Vaultbalance. - If the vault is empty, check the on-chain liquidity pool.
- If the pool has sufficient liquidity, the Coordinator could authorize an
EXECUTE_SWAP_AND_RELEASEpayload, which calls a function on the bridge to pull funds from the LP and send them to the user, providing a much faster settlement. - Only if both the vault and the LP are depleted would it fall back to the canonical
mintflow.
- Check the destination
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.
- Problem: Early versions of our on-chain test suite (
forge test) were plagued by intermittent and seemingly randomInvalidSignatureand "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 testruns 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.
- The
setUpfunction inBridge.t.solwas removed. - 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/). - The test dynamically generates a unique
TestConfig.tomlfile within that directory. - The FFI call was modified to pass the path to this unique config file (
--config ...). - Finally, we enforce sequential test execution with
forge test --ffi -j 1as a foolproof guardrail.
- The
- 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.
- Problem: The initial integration of
sqlxresulted in persistent compile-time errors, specificallyE0277(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 (NaiveDateTimefor SQLite). - The Definitive Solution: We adopted the official "offline"
sqlxworkflow, which is now a mandatory part of the development setup.cargo sqlx database create&migrate run: A clean database with the correct schema is created first.cargo sqlx prepare --workspace: This command is run once. It connects to the live database, verifies allquery!macros in the codebase, and generates asqlx-data.jsonfile.- Compile Time: Subsequent
cargo buildorcargo checkcommands use thesqlx-data.jsonfile for verification, removing the need for a live database connection during compilation.
- Core Principle:
sqlxdemands 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.
- 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 aMutexlock 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.rsmodule was completely rewritten to adhere to the principle of minimal lock contention.- 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.
- All long-running,
await-ing loops now operate entirely outside of theMutexguard's scope.
- Core Principle: Never hold a
Mutexlock across an.awaitpoint if it can be avoided. Locks should be held for the shortest duration possible to perform synchronous, atomic operations.
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.
- 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.solcontract currently lacks a criticalemergencyPausefunction. 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.
- 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
.tomlfile.
- Database & State:
- Production Database:
SQLiteis excellent for its simplicity but may not be suitable for high-throughput production workloads. Migrating to a production-grade database likePostgreSQLis recommended for better concurrency, scalability, and backup/restore capabilities. - Transaction Confirmation Logic: The current
BROADCASTEDstate is optimistic. A production system must implement a "confirmation listener" that waits for a configurable number of blocks (Nconfirmations) on the destination chain before marking an operation as trulyCONFIRMED. This protects against chain reorgs.
- Production Database:
- Error & Failure States:
- Irrecoverable Errors: The backend state machine must be enhanced with a final
FAILEDstatus. If an operation fails for a non-transient reason (e.g., the on-chainexecutecall reverts due to a contract logic error), the operation should be marked asFAILEDin the database with a clearfailure_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.
- Irrecoverable Errors: The backend state machine must be enhanced with a final
- 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
requirestatements, 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
useGasEstimatehook 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.