Skip to main content

Updates

Signet for Solvers

Anthony //4 min read

If you run MEV infrastructure on Ethereum, you already have most of what you need to operate on Signet. Same tooling. Same block timing.

Different economics.

On Ethereum, searchers extract value—front-running, sandwiching, arbitraging information asymmetry. On Signet, fillers provide value—satisfying user orders, supplying liquidity, enabling instant exits.

The infrastructure is the same. The game is inverted.

The Economics

Users post Orders on Signet expressing cross-chain intent:

“I want 1 ETH on Ethereum for 3,800 USD on Signet”

As a filler, you:

  1. Monitor the transaction cache for profitable orders
  2. Provide the L1 side (1 ETH) from your inventory
  3. Receive the L2 side (3,800 USD) atomically
  4. Pocket the spread between market rate and order price

If the current ETH price is $3,750, you make $50 per fill. Competition drives spreads toward equilibrium, but there’s always margin for fast, well-capitalized fillers.

What Transfers, What Doesn’t

If you’re running Ethereum MEV infrastructure, most of it carries over. Builder connections work — Signet uses standard bundle formats. Transaction monitoring is similar — the cache API replaces mempool watching. Inventory management and latency optimization are the same 12-second game.

What changes: orders are broadcast to a cache API instead of a mempool, you need liquidity on both Ethereum and Signet, and you’re filling explicit user requests rather than exploiting information asymmetry.

Finding Orders

Orders are broadcast to the transaction cache. Use the SDK to fetch them:

rust
use signet_tx_cache::TxCache;

// Connect to Parmigiana testnet
let cache = TxCache::parmigiana();

// Fetch pending transactions (includes orders)
let response = cache.get_transactions(None).await?;

// Orders are returned directly from the orders endpoint
let orders = cache.get_orders().await?;

for order in orders {
    // Evaluate profitability
    let spread = calculate_spread(&order);
    if spread > MIN_PROFIT {
        fill_order(order).await?;
    }
}

// Pagination for high volume
let mut cursor = None;
loop {
    let response = cache.get_transactions(cursor).await?;
    process_orders(&response.transactions);

    if response.has_more() {
        cursor = response.next_cursor;
    } else {
        break;
    }
}

Each order contains:

  • inputs: What the user is offering (tokens on Signet)
  • outputs: What the user wants (tokens on Ethereum)
  • deadline: Block timestamp expiry (~10 minute validity)
  • permit: Signed Permit2 authorization

Order Structure

Orders use Permit2 for gasless authorization:

rust
pub struct SignedOrder {
    pub permit: Permit2Batch,
    pub outputs: Vec<Output>,
}

pub struct Output {
    pub token: Address,     // ERC20 on destination chain
    pub amount: U256,       // Amount user wants to receive
    pub recipient: Address, // Where to send on destination
    pub chainId: u32,       // Destination chain ID
}

pub struct Permit2Batch {
    pub permit: PermitBatchTransferFrom,
    pub owner: Address,
    pub signature: Bytes,
}

The permit authorizes you to take the user’s Signet tokens. The outputs specify what you must provide on Ethereum. Both execute atomically or neither does.

Filling an Order

To fill, you construct a bundle with both the L1 fill and L2 claim:

rust
use signet_bundle::SignetEthBundle;
use alloy::rpc::types::mev::EthSendBundle;

// Build the fill bundle
let bundle = SignetEthBundle {
    // L1: Permit2 fills for Ethereum-side token transfers
    host_fills: Some(permit2_fills),
    bundle: EthSendBundle {
        // L2: Signet transactions including the order claim
        txs: signet_transactions,
        block_number: target_block,
        reverting_tx_hashes: vec![],
    },
};

// Simulate first
let sim: SignetCallBundleResponse = provider
    .client()
    .request("signet_callBundle", (bundle.clone(),))
    .await?;

// Verify fills satisfy orders
println!("Orders: {:?}", sim.orders);
println!("Fills: {:?}", sim.fills);

// Submit to builder
tx_cache.forward_bundle(bundle).await?;

The bundle is atomic: if your L1 fill doesn’t execute, the L2 claim reverts. No partial fills. No stuck capital.

Bundle Simulation

Always simulate before submitting:

rust
// signet_callBundle response
pub struct SignetCallBundleResponse {
    pub bundle_hash: B256,
    pub total_gas_used: u64,
    pub orders: AggregateOrders,  // What orders require
    pub fills: AggregateFills,    // What fills provide
    pub results: Vec<BundleResult>,
}

A bundle is valid when fills >= orders for every asset/recipient pair. The simulation tells you exactly what’s needed before you commit capital.

Inventory Strategy

Successful filling requires liquidity on both chains:

Ethereum side:

  • ETH, WETH, USDC for common fill pairs
  • Size based on expected volume

Signet side:

  • USD for receiving user deposits
  • Accumulates as you fill orders

Rebalancing

When your Signet inventory grows, exit via your own orders:

rust
use signet_types::signing::order::UnsignedOrder;
use signet_constants::parmigiana as constants;

// Create exit order: 10,000 USD → ~9,950 USDC (0.5% spread)
let exit_order = UnsignedOrder::default()
    .with_input(constants::WUSD, U256::from(10_000) * U256::from(10).pow(U256::from(18)))
    .with_output(
        constants::HOST_USDC,
        U256::from(9_950) * U256::from(10).pow(U256::from(6)),
        your_address,
        constants::HOST_CHAIN_ID,
    )
    .with_chain(constants)
    .with_nonce(next_nonce)
    .sign(&signer)
    .await?;

// Submit to cache
cache.forward_order(exit_order).await?;

Other fillers (or you, on a different system) fill your exit. The spread you pay is the cost of rebalancing.

Economics

FactorImpact
SpreadYour profit margin per fill
VolumeMore orders = more opportunities
CompetitionMore fillers = tighter spreads
Inventory costCapital opportunity cost
GasL1 execution costs eat into margin

Early fillers capture wider spreads. As the market matures, competition compresses margins but volume increases. The equilibrium rewards operational efficiency over information advantages.

Get Started

Testnet:

  • Parmigiana Faucet — Get test assets
  • RPC: https://rpc.parmigiana.signet.sh
  • TX Cache: https://transactions.parmigiana.signet.sh

SDK:

bash
cargo add signet-types signet-bundle signet-tx-cache signet-constants

Docs:

Questions? Get in touch.

ESC

Start typing to search documentation...

Navigate Select ⌘K Open