Order Processing
Orders in Signet enable cross-chain transfers and atomic swaps. They require precise sequencing and coordination between different transactions to ensure secure execution.
Order Components
Orders consist of two essential parts that must execute within the same block:
- Initiate - Transfers inputs from the User to the Filler
- Fill - Transfers outputs from the Filler to the User
Important Ordering Constraints
Initiate transactions can only mine if the Fill transactions have already mined. This critical sequencing requirement ensures atomic execution.
Component | Signing Order | Mining Order | Chain Location |
---|---|---|---|
Initiate | Signed First | Mines Second | Always on Rollup |
Fill | Signed Second | Mines First | Host chain, Rollup, or both |
Order Execution Flow
- User signs the Initiate permit (creating a SignedOrder)
- User sends the SignedOrder to a Filler
- Filler signs the Fill permit (creating a SignedFill)
- Filler creates a transaction Bundle with Fill transaction(s) first, followed by Initiate transaction(s)
- Filler sends the Bundle to a Block Builder
- Builder mines the Bundle atomically on both Host & Rollup
Note: Despite its name, “Initiate” transactions are always mined after the “Fill” transaction.
Best Practices for Fillers
Fillers should take special precautions when handling SignedFills:
- Keep SignedFills private until mined on-chain
- Anyone with a SignedFill can withdraw tokens from the Filler’s account
- Only share SignedFills with trusted Builders who are contractually bound to maintain Bundle integrity
- Leaked SignedFills risk financial loss
- Ensure proper transaction ordering
- Fill transactions MUST mine before Initiate transactions
- Host chain transactions automatically mine before Rollup transactions
- For Rollup-based Fills, explicit ordering is required to ensure they mine before Initiate transactions
Chain Execution Requirements
- Conditional Transactions: Only the Rollup can enforce conditional transactions, which is why Initiate transactions always happen on the Rollup
- Atomic Execution: Both parts of an Order must execute in the same block or neither will execute
- Cross-Chain Coordination: Orders can involve transactions on both the Host chain and the Rollup, requiring careful synchronization
Order and Fill Validation
Proper validation is critical for both users and fillers to ensure orders and fills will execute successfully.
Order Validation
Before submitting orders, users should validate them to catch potential issues:
1use signet_types::signing::order::SignedOrder;
2use signet_types::signing::{SignedPermitError};
3
4fn validate_order(signed_order: &SignedOrder, current_timestamp: u64) -> Result<(), String> {
5 // 1. Basic validation
6 match signed_order.validate(current_timestamp) {
7 Ok(()) => {
8 println!("✓ Order passes basic validation");
9 }
10 Err(SignedPermitError::DeadlinePassed { current, deadline }) => {
11 return Err(format!("Order deadline has passed: {} > {}", current, deadline));
12 }
13 Err(e) => {
14 return Err(format!("Order validation failed: {:?}", e));
15 }
16 }
17
18 // 2. Additional business logic validation
19 validate_order_economics(signed_order)?;
20
21 Ok(())
22}
23
24fn validate_order_economics(signed_order: &SignedOrder) -> Result<(), String> {
25 // Check minimum amounts
26 for output in &signed_order.outputs {
27 if output.amount < MIN_OUTPUT_AMOUNT {
28 return Err(format!("Output amount too small: {}", output.amount));
29 }
30 }
31
32 // Validate recipient addresses
33 for output in &signed_order.outputs {
34 if output.recipient == Address::ZERO {
35 return Err("Invalid recipient address".to_string());
36 }
37 }
38
39 Ok(())
40}
Source: Core validation API from signet-sdk/crates/types/src/signing/, extended with custom business logic examples
Fill Validation
Fillers must validate fills before signing to ensure they’re economically viable:
1use signet_types::signing::fill::SignedFill;
2use signet_types::agg::AggregateOrders;
3
4fn validate_fill(signed_fill: &SignedFill, current_timestamp: u64) -> Result<(), String> {
5 // 1. Basic validation
6 match signed_fill.validate(current_timestamp) {
7 Ok(()) => {
8 println!("✓ Fill passes basic validation");
9 }
10 Err(SignedPermitError::DeadlinePassed { current, deadline }) => {
11 return Err(format!("Fill deadline has passed: {} > {}", current, deadline));
12 }
13 Err(SignedPermitError::PermitMismatch) => {
14 return Err("Permit tokens/amounts don't match outputs".to_string());
15 }
16 Err(e) => {
17 return Err(format!("Fill validation failed: {:?}", e));
18 }
19 }
20
21 // 2. Profitability validation
22 validate_fill_profitability(signed_fill)?;
23
24 Ok(())
25}
26
27fn validate_fill_profitability(signed_fill: &SignedFill) -> Result<(), String> {
28 // Calculate total output value
29 let mut total_output_value = 0u64;
30 for output in &signed_fill.outputs {
31 let token_price = get_token_price(&output.token)?;
32 total_output_value += (output.amount * token_price) as u64;
33 }
34
35 // Ensure minimum profit margin
36 let estimated_gas_cost = estimate_transaction_cost();
37 let minimum_profit = total_output_value / 100; // 1% minimum
38
39 if estimated_gas_cost + minimum_profit > total_output_value {
40 return Err("Fill not profitable after gas costs".to_string());
41 }
42
43 Ok(())
44}
Source: Core validation API from signet-sdk/crates/types/src/signing/fill.rs, extended with custom profitability logic examples
Pre-Bundle Validation
Before submitting bundles, validate the complete order/fill pairing:
1fn validate_bundle_atomicity(
2 signed_orders: &[SignedOrder],
3 signed_fills: &[SignedFill],
4 current_timestamp: u64
5) -> Result<(), String> {
6 // 1. Validate all orders and fills individually
7 for order in signed_orders {
8 validate_order(order, current_timestamp)?;
9 }
10
11 for fill in signed_fills {
12 validate_fill(fill, current_timestamp)?;
13 }
14
15 // 2. Validate order/fill compatibility
16 validate_order_fill_matching(signed_orders, signed_fills)?;
17
18 // 3. Check deadline alignment
19 validate_deadline_consistency(signed_orders, signed_fills)?;
20
21 Ok(())
22}
23
24fn validate_order_fill_matching(
25 orders: &[SignedOrder],
26 fills: &[SignedFill]
27) -> Result<(), String> {
28 // Ensure fills provide exactly what orders request
29 let mut required_outputs = HashMap::new();
30 let mut provided_outputs = HashMap::new();
31
32 // Aggregate required outputs from orders
33 for order in orders {
34 for output in &order.outputs {
35 let key = (output.chainId, output.token);
36 *required_outputs.entry(key).or_insert(0u128) += output.amount.as_u128();
37 }
38 }
39
40 // Aggregate provided outputs from fills
41 for fill in fills {
42 for output in &fill.outputs {
43 let key = (output.chainId, output.token);
44 *provided_outputs.entry(key).or_insert(0u128) += output.amount.as_u128();
45 }
46 }
47
48 // Verify exact matching
49 if required_outputs != provided_outputs {
50 return Err("Order outputs don't match fill outputs".to_string());
51 }
52
53 Ok(())
54}
Source: Custom implementation examples using SDK types from signet-sdk/crates/types
Common Validation Errors
Error Type | Cause | Solution |
---|---|---|
DeadlinePassed | Order/fill deadline has expired | Create new order/fill with future deadline |
PermitMismatch | Permit2 structure doesn’t match outputs | Regenerate permit with correct tokens/amounts |
InsufficientBalance | Filler lacks required tokens | Top up token balance before signing fill |
InvalidRecipient | Zero or invalid recipient address | Use valid addresses for all recipients |
AmountTooSmall | Order amount below minimum threshold | Increase order size or combine multiple orders |
Validation Best Practices
- Always validate before signing - Catch issues early to avoid gas costs
- Check current balances - Ensure sufficient funds for fills
- Verify deadline alignment - Orders and fills should have compatible deadlines
- Test on simulation endpoints - Use
signet_simBundle
before production submission - Monitor for deadline drift - Account for block time variations when setting deadlines