Sequencing

How Adrift leverages Syndicate's modular sequencer architecture for onchain games

Adrift is a live, production example of how Syndicate’s modular sequencer architecture enables new kinds of onchain applications. Here’s how it works at the contract level:

Modular Sequencer Architecture

Syndicate’s sequencer is not a monolithic service—it’s a set of smart contracts (modules) that can be composed and extended. The main module types are:

  • Permission Modules: Who can sequence transactions (e.g., allowlist, token holders, auction winners)
  • Ordering Modules: How transactions are ordered and batched (e.g., FIFO, custom logic)
  • Custom Modules: Any additional logic, such as randomness injection, MEV protection, or game-specific rules

Adrift uses a custom module called AdriftBundler to implement game-specific sequencing and randomness.

The AdriftBundler Contract

The AdriftBundler contract is a concrete example of a custom sequencing module. Here’s what it does:

  • Mempool Management: When a player submits a check-in, the bundler decodes the transaction (using RLPTxBreakdown) and, if it’s a checkIn() call, adds it to an internal mempool.
  • Role-based Access: Only addresses with the SEQUENCER_ROLE can process transactions, and only those with RANDOMNESS_ROLE can inject new randomness.
  • Randomness Coordination: When new randomness is injected (via addRandomness), the bundler:
    1. Submits the randomness transaction to the underlying sequencing chain.
    2. Processes all pending check-ins in the mempool in a single batch, ensuring all outcomes are determined using the latest randomness.
  • Bulk Processing: The bundler can process transactions in bulk, enabling efficient game rounds or batch actions.

Key contract logic:

function addRandomness(bytes calldata randomnessTx) external onlyRole(RANDOMNESS_ROLE) {
    sequencingAddress.processTransactionUncompressed(randomnessTx);
 
    if (mempool.length != 0) {
        sequencingAddress.processTransactionsBulk(mempool);
        delete mempool;
        emit MempoolCleared();
    }
}

This ensures that all check-ins are resolved after new randomness is set, making outcomes tamper-proof and fair.

Transaction Decoding and Filtering

The bundler uses RLPTxBreakdown and RLPReader to decode raw EIP-1559 transactions. It checks the function selector to identify checkIn() calls, and only these are added to the mempool for batch processing.

Example:

function _processTransactionUncompressed(bytes memory txn) internal {
    RLPTxBreakdown.DecodedTransaction memory decodedTx = RLPTxBreakdown.decodeTx(txn);
    if (decodedTx.data.length > 0 && !decodedTx.isContractDeployment) {
        // 0x183ff085 = checkIn()
        if (getFunctionSelector(decodedTx.data) == hex"183ff085") {
            playerContractCheckinNonces[decodedTx.to][decodedTx.from]++;
            mempool.push(txn);
            emit MempoolUpdated(mempool.length, txn);
            return;
        }
    }
    sequencingAddress.processTransactionUncompressed(txn);
}

Benefits of Syndicate’s Sequencer Modules for Adrift

  • Programmability: Adrift can define exactly how and when randomness is injected, and how transactions are batched and processed.
  • Fairness: All check-ins in a round are resolved using the same randomness, and outcomes are locked in as soon as the batch is processed—no reorgs, no manipulation.
  • Extensibility: The bundler is just one module. Developers can add permissioning, auctions, or other custom logic by composing additional modules (see core sequencing modules).
  • Transparency: All sequencing logic is onchain and auditable, not hidden in offchain infrastructure.
  • Abuse resistance: Check-in flows can use per-player nonces to make manipulation detectable. The outcomes contract increments a nonce on every check-in and mixes it into the randomness, and a bundler can maintain its own per-player check-in counters to cross-check for intentional reverts (protecting against “Meebits”-style exploits).

How Developers Can Leverage This Pattern

  • Write your own modules: Start from the AdriftBundler or Syndicate’s base modules and add your own batching, permissioning, or randomness logic.
  • Compose modules: Use permission, ordering, and custom modules together to create unique sequencing flows for your appchain.
  • Audit and extend: All logic is open source and onchain, so you can verify, fork, or extend as needed.

See also:

By leveraging Syndicate’s modular sequencer architecture, Adrift demonstrates how onchain games (and any appchain) can achieve fairness, extensibility, and transparency that’s impossible with traditional, offchain sequencers.

On this page