Skip to main content

Updates

Building with Orders

Anthony //5 min read
technical

This guide walks through implementing cross-chain orders on Signet. By the end, you’ll have working code for both sides of the order flow: creating orders as an application, and filling orders as a market participant.

What you’ll build:

  • Order creation: Express user intent through signed Permit2 structures
  • Order filling: Compete to fulfill orders profitably
  • Bundle coordination: Atomic execution across chains

Prerequisites:

All code is available in signet-orders.


Part 1: Creating Orders

Orders express cross-chain intent. A user says: “I’ll give you X on Signet if you give me Y on Ethereum.” Applications construct and sign these orders on behalf of users.

The SendOrder Pattern

Code: src/order.rs

rust
impl SendOrder {
    /// Sign an order and submit it to the transaction cache
    pub async fn sign_and_send_order(&self, order: Order) -> Result<()> {
        let signed = self.sign_order(order).await?;
        self.send_order(signed).await
    }

    /// Sign an order without submitting
    pub async fn sign_order(&self, order: Order) -> Result<SignedOrder> {
        // Build Permit2 authorization and sign
        // ...
    }

    /// Submit a pre-signed order to the transaction cache
    pub async fn send_order(&self, signed: SignedOrder) -> Result<()> {
        self.tx_cache.forward_order(signed).await
    }
}

What’s happening:

  1. Order construction: Build the order specifying inputs (what user gives) and outputs (what user wants)
  2. Permit2 signing: User approves token spend with a single signature (no separate approve tx)
  3. Submission: Order goes to the transaction cache where Fillers discover it

Order Structure

rust
pub struct SignedOrder {
    pub permit: Permit2Batch,      // Single signature authorization
    pub outputs: Vec<Output>,      // What user wants to receive
}

pub struct Output {
    pub token: Address,            // Asset (e.g., WETH)
    pub amount: Uint<256, 4>,      // Amount
    pub recipient: Address,        // Where to send it
    pub chainId: u32,              // Which chain (1 = Ethereum)
}

Orders have a ~10 minute validity window. If no Filler accepts within that window, the order expires and nothing happens.


Part 2: Filling Orders

Fillers are market participants who fulfill orders for profit. They provide the outputs users want and receive the inputs users offer.

The Filler Pattern

Code: src/filler.rs

rust
impl Filler {
    pub async fn fill(&self, orders: Vec<SignedOrder>) -> Result<()> {
        // 1. Construct and sign Permit2 fills for destination chains
        let fills = self.construct_fills(&orders).await?;

        // 2. Build atomic bundle combining initiates and fills
        let bundle = self.build_signet_bundle(&orders, &fills).await?;

        // 3. Submit to Transaction Cache for builder inclusion
        self.tx_cache.submit_bundle(bundle).await
    }
}

What’s happening:

  1. Construct fills: Filler signs their side of the trade (providing outputs to user)
  2. Build bundle: Combine order execution with fill execution
  3. Submit: Bundle goes to builders for atomic inclusion

Two Filling Strategies

Aggregate execution — All orders in one bundle:

rust
pub async fn fill(&self, orders: Vec<SignedOrder>) -> Result<()> {
    // Submit all Orders/Fills in single Bundle
    // Atomic: all succeed or none do
}

Pros: Gas efficient, can reuse capital across orders Cons: Single failure breaks entire bundle

Individual execution — Each order in its own bundle:

rust
pub async fn fill_individually(&self, orders: Vec<SignedOrder>) -> Result<()> {
    // Submit each Order/Fill in separate Bundle
    // Independent: orders succeed or fail separately
}

Pros: Resilient, simple validation Cons: Higher gas, can’t optimize across orders

Choose based on your risk tolerance and order flow.


Part 3: Bundle Construction

Orders and fills execute atomically through Signet’s bundle system.

rust
let bundle = SignetEthBundle {
    host_fills: Some(permit2_fills),    // Ethereum-side actions
    bundle: EthSendBundle {
        txs: signet_transactions,       // Signet-side transactions
        block_number: target_block,
        reverting_tx_hashes: vec![],    // No partial execution
    }
};

Key properties:

  • Block-specific: Bundles target a specific block number
  • Atomic: All transactions succeed or none do
  • Retry-able: Failed bundles can target the next block

The OrderDetector in Signet’s EVM enforces atomicity: order transactions only execute if corresponding fills exist.


Part 4: Running the Example

The roundtrip example demonstrates the complete flow.

Setup

1. Configure environment:

bash
export CHAIN_NAME=parmigiana
export RU_RPC_URL=https://rpc.parmigiana.signet.sh/
export SIGNER_KEY=[your private key or AWS KMS key ID]
export SIGNER_CHAIN_ID=88888

2. Fund your wallet:

Your signing key needs:

  • Input tokens (what you’re swapping from)
  • Output tokens (what you’re providing to users)
  • Gas: ETH on Ethereum, USD on Signet

3. Approve Permit2:

bash
cast send [TOKEN_ADDRESS] "approve(address,uint256)" \
  0x000000000022D473030F116dDEE9F6B43aC78BA3 \
  0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff \
  --rpc-url $RPC_URL

Permit2 uses the same address on Parmigiana and Ethereum mainnet.

Run

Rollup → Host swap:

bash
cargo run --bin order-roundtrip-example

Rollup → Rollup swap:

bash
cargo run --bin order-roundtrip-example -- --rollup

The example supports both AWS KMS and local private keys.


Production Considerations

When building production systems:

Profitability analysis: Calculate spreads accounting for gas costs, inventory costs, and competition.

Key management: Use AWS KMS, HashiCorp Vault, or hardware security modules. Never use raw private keys in production.

Monitoring: Track fill rates, win rates against competitors, and inventory levels.

Error handling: Implement retry logic for network failures. Bundles can target subsequent blocks if the first attempt fails.

Gas optimization: Choose aggregate vs individual filling based on your order patterns. High-volume, correlated orders benefit from aggregation.


Integration Patterns

Cross-chain DeFi: Let users open positions on Ethereum using Signet collateral—instantly.

Payment routing: Route payments to the cheapest chain based on current liquidity.

Arbitrage: Capture price differences while providing useful execution to users.

Wallet interfaces: Offer “swap to any chain” without users understanding the mechanics.


Resources

Code:

Infrastructure:

Concepts:

Questions? Get in touch.

ESC

Start typing to search documentation...

Navigate Select ⌘K Open