Market Participants
Our market-based approach eliminates the need for verification periods or complex bridging. Users move assets instantly between chains.
Searchers
Searchers connect orders with liquidity and are essential for creating efficient markets by:
- Scanning orders cache
- Building matching permits into bundles
- Submitting these bundles to block builders
- Driving efficiency by connecting orders with liquidity
Finding Opportunities
To operate as a Searcher:
- Connect to the orders cache to receive updates about new orders
- Implement efficient filtering to identify profitable opportunities
- Use the order aggregation functionality to organize multiple orders
1// Example order aggregation
2pub struct AggregateOrders {
3 // Outputs to be transferred to users
4 pub outputs: HashMap<(u64, Address), HashMap<Address, U256>>,
5 // Inputs to be transferred to the filler
6 pub inputs: HashMap<Address, U256>,
7}
Creating Bundles
When building bundles that include orders:
- Collect compatible orders that can be matched together
- Ensure atomic execution across chains
- Include necessary host fills for cross-chain actions
- Submit the bundle to the bundle cache
Fillers
Fillers provide liquidity to the system by:
- Fulfilling open orders with their own assets
- Enabling cross-chain transfers without traditional bridges
- Making instant withdrawals possible
Becoming a Filler
To operate as a Filler:
- Maintain sufficient liquidity on both Signet and Ethereum
- Monitor the orders cache for opportunities
- Create corresponding transactions to fulfill orders
- Submit fulfillment transactions through bundles
Filling Cross-Chain Orders
When fulfilling a cross-chain order:
- Verify the user’s input assets on Signet
- Prepare corresponding assets on Ethereum
- Submit a bundle with the necessary host fills
- Collect the user’s assets once the order executes
Fill Signing Implementation
Creating and Signing Fills
Fillers must create SignedFill
transactions to provide the outputs requested by orders. The Signet SDK provides utilities to handle the complex Permit2 signing process:
Complete Fill Signing Workflow
1use signet_types::signing::fill::{UnsignedFill, SignedFill};
2use signet_types::agg::AggregateOrders;
3use alloy::signers::Signer;
4use std::collections::HashMap;
5
6// 1. Aggregate compatible orders
7let aggregate_orders = AggregateOrders {
8 outputs: order_outputs, // What users want to receive
9 inputs: order_inputs, // What fillers will receive
10};
11
12// 2. Create UnsignedFill for multiple target chains
13let unsigned_fill = UnsignedFill::new(&aggregate_orders)
14 .with_rv_chain_id(signet_chain_id) // Rollup chain where orders originated
15 .with_chain(ethereum_chain_id, ethereum_order_contract) // Ethereum
16 .with_chain(base_chain_id, base_order_contract) // Base L2
17 .with_deadline(current_timestamp + 600) // 10 minutes
18 .with_nonce(custom_nonce); // Optional
19
20// 3. Sign fills for all target chains
21let signed_fills: HashMap<u64, SignedFill> = unsigned_fill.sign(&signer).await?;
22
23// 4. Generate transactions for each chain
24for (chain_id, signed_fill) in signed_fills {
25 let fill_tx = signed_fill.to_fill_tx(order_contracts[&chain_id]);
26 submit_transaction(chain_id, fill_tx).await?;
27}
Source: Implementation example inspired by signet-sdk/crates/types/src/signing/fill.rs
Single Chain Fill Example
For simpler use cases filling orders on a single chain:
1// Fill orders only on Ethereum
2let unsigned_fill = UnsignedFill::new(&aggregate_orders)
3 .with_rv_chain_id(signet_chain_id)
4 .with_chain(ethereum_chain_id, ethereum_order_contract)
5 .with_deadline(current_timestamp + 300); // 5 minutes
6
7let signed_fill = unsigned_fill.sign_for(ethereum_chain_id, &signer).await?;
8let fill_tx = signed_fill.to_fill_tx(ethereum_order_contract);
Source: Implementation example inspired by signet-sdk/crates/types/src/signing/fill.rs
Fill Validation and Security
1// Validate fills before submission
2match signed_fill.validate(current_timestamp) {
3 Ok(()) => {
4 println!("Fill is valid, proceeding with submission");
5 }
6 Err(SignedPermitError::DeadlinePassed { current, deadline }) => {
7 println!("Fill deadline has passed: {} > {}", current, deadline);
8 return Err("Fill expired".into());
9 }
10 Err(SignedPermitError::PermitMismatch) => {
11 println!("Permit tokens/amounts don't match outputs");
12 return Err("Invalid fill structure".into());
13 }
14 Err(e) => {
15 println!("Fill validation error: {:?}", e);
16 return Err(e.into());
17 }
18}
Source: Exact API usage from signet-sdk/crates/types/src/signing/
Keep SignedFills Private: A
SignedFill
should remain private until it is mined. There is no guarantee that desired Order Inputs will be received in return for the Outputs offered by the signed Permit2Batch.Use Private Transaction Relays: Use private transaction relays to send bundles containing SignedFills to builders. Public mempools expose fills to potential front-running.
Atomic Submission: SignedFills should be submitted atomically with the corresponding SignedOrder(s) to claim the inputs. Avoid submitting a fill without the matching orders.
Trusted Endpoints: Bundles can be sent to a trusted Signet Node’s
signet_sendBundle
endpoint for processing.
Advanced Fill Patterns
Multi-Chain Fill Coordination:
1// Ensure all target chains are filled atomically
2let mut transactions = HashMap::new();
3
4for (chain_id, signed_fill) in signed_fills {
5 let tx = signed_fill.to_fill_tx(order_contracts[&chain_id]);
6 transactions.insert(chain_id, tx);
7}
8
9// Submit all transactions in a coordinated bundle
10submit_cross_chain_bundle(transactions).await?;
Source: Implementation pattern using signet-sdk types
**Fill Profitability Calculation:
1// Calculate expected profit before signing fills
2let input_value = calculate_token_value(&aggregate_orders.inputs);
3let output_value = calculate_token_value(&aggregate_orders.outputs);
4let gas_costs = estimate_gas_costs(&signed_fills);
5
6let expected_profit = input_value - output_value - gas_costs;
7if expected_profit <= minimum_profit_threshold {
8 return Err("Insufficient profit margin".into());
9}
Source: Custom implementation example for filler profitability calculations
Error Recovery
If a fill fails, fillers need to handle various scenarios:
1match fill_result {
2 Ok(receipt) => {
3 println!("Fill successful: {:?}", receipt.transaction_hash);
4 }
5 Err(FillError::InsufficientBalance) => {
6 // Filler doesn't have enough tokens
7 println!("Need to top up token balance");
8 }
9 Err(FillError::OrderAlreadyFilled) => {
10 // Another filler got there first
11 println!("Order was filled by another participant");
12 }
13 Err(FillError::SlippageExceeded) => {
14 // Price moved unfavorably
15 println!("Order no longer profitable");
16 }
17}
Source: Custom implementation example for fill error handling
Orders API Reference
Order Cache
The Order Cache provides endpoints for submitting and retrieving orders:
GET /orders
: Retrieve all active orders in the cacheGET /orders/{id}
: Get a specific order by IDPOST /orders
: Submit a new order to the cacheDELETE /orders/{id}
: Cancel an order (requires signature)
Order Simulation
Before submitting orders or bundles, you can simulate their execution:
1// Example simulation request
2{
3 "jsonrpc": "2.0",
4 "id": 1,
5 "method": "signet_simBundle",
6 "params": [{
7 "txs": ["0x..."], // Array of signed transactions
8 "blockNumber": "0x...", // Target block number
9 "host_fills": {
10 // Host fills specification
11 }
12 }]
13}
Error Handling
Common errors when working with orders:
Error Code | Description | Resolution |
---|---|---|
1001 | Invalid signature | Verify signing process |
1002 | Insufficient balance | Ensure adequate token balance |
1003 | Expired order | Check order deadline |
1004 | Price slippage | Adjust price expectations |