signet_types/signing/
order.rs

1use crate::signing::{permit_signing_info, SignedPermitError, SigningError};
2use alloy::{
3    network::TransactionBuilder, primitives::Address, rpc::types::TransactionRequest,
4    signers::Signer, sol_types::SolCall,
5};
6use chrono::Utc;
7use serde::{Deserialize, Serialize};
8use signet_zenith::RollupOrders::{
9    initiatePermit2Call, Order, Output, Permit2Batch, TokenPermissions,
10};
11use std::borrow::Cow;
12
13/// A SignedOrder represents a single Order after it has been permit2-encoded and signed.
14/// It is the final format signed by Users and shared with Fillers to request that an Order be filled.
15///
16/// It corresponds to the parameters for `initiatePermit2` on the OrderOrigin contract,
17/// and thus contains all necessary information to initiate the Order.
18///
19/// It can be shared with all Fillers via the Signet Node `signet_sendOrder` RPC call,
20/// or shared directly with specific Filler(s) via private channels.
21/// The type can be signed and published safely, because although the Permit2Batch allows
22/// the Order Inputs to be transferred from the user, the Signet Node ensures that
23/// Inputs cannot be transferred until the Order Outputs have already been filled.
24///
25/// TODO: Link to docs.
26#[derive(Clone, Debug, Deserialize, Serialize, PartialEq, Eq)]
27pub struct SignedOrder {
28    /// The permit batch.
29    #[serde(flatten)]
30    pub permit: Permit2Batch,
31    /// The desired outputs.
32    pub outputs: Vec<Output>,
33}
34
35impl SignedOrder {
36    /// Creates a new signed order.
37    pub const fn new(permit: Permit2Batch, outputs: Vec<Output>) -> Self {
38        Self { permit, outputs }
39    }
40
41    /// Check that this can be syntactically used to initiate an order.
42    ///
43    /// For it to be valid:
44    /// - Deadline must be in the future.
45    pub fn validate(&self, timestamp: u64) -> Result<(), SignedPermitError> {
46        let deadline = self.permit.permit.deadline.saturating_to::<u64>();
47        if timestamp > deadline {
48            return Err(SignedPermitError::DeadlinePassed { current: timestamp, deadline });
49        }
50
51        Ok(())
52    }
53
54    /// Generate a TransactionRequest to `initiate` the SignedOrder.
55    pub fn to_initiate_tx(
56        &self,
57        filler_token_recipient: Address,
58        order_contract: Address,
59    ) -> TransactionRequest {
60        // encode initiate data
61        let initiate_data = initiatePermit2Call {
62            tokenRecipient: filler_token_recipient,
63            outputs: self.outputs.clone(),
64            permit2: self.permit.clone(),
65        }
66        .abi_encode();
67
68        // construct an initiate tx request
69        TransactionRequest::default().with_input(initiate_data).with_to(order_contract)
70    }
71}
72
73/// An UnsignedOrder is a helper type used to easily transform an Order into a SignedOrder with correct permit2 semantics.
74/// Users can do:
75/// let signed_order = UnsignedOrder::from(order).with_chain(rollup_chain_id, rollup_order_address).sign(signer)?;
76/// TxCache::new(tx_cache_endpoint).forward_order(signed_order);
77#[derive(Clone, Debug, Deserialize, Serialize, PartialEq, Eq)]
78pub struct UnsignedOrder<'a> {
79    order: Cow<'a, Order>,
80    nonce: Option<u64>,
81    rollup_chain_id: Option<u64>,
82    rollup_order_address: Option<Address>,
83}
84
85impl<'a> From<&'a Order> for UnsignedOrder<'a> {
86    fn from(order: &'a Order) -> Self {
87        UnsignedOrder::new(order)
88    }
89}
90
91impl<'a> UnsignedOrder<'a> {
92    /// Get a new UnsignedOrder from an Order.
93    pub fn new(order: &'a Order) -> Self {
94        Self { order: order.into(), nonce: None, rollup_chain_id: None, rollup_order_address: None }
95    }
96
97    /// Add a Permit2 nonce to the UnsignedOrder.
98    pub fn with_nonce(self, nonce: u64) -> Self {
99        Self { nonce: Some(nonce), ..self }
100    }
101
102    /// Add the chain id  and Order contract address to the UnsignedOrder.
103    pub fn with_chain(self, chain_id: u64, order_contract_address: Address) -> Self {
104        Self {
105            rollup_chain_id: Some(chain_id),
106            rollup_order_address: Some(order_contract_address),
107            ..self
108        }
109    }
110
111    /// Sign the UnsignedOrder, generating a SignedOrder.
112    pub async fn sign<S: Signer>(&self, signer: &S) -> Result<SignedOrder, SigningError> {
113        // if nonce is None, populate it with the current time
114        let nonce = self.nonce.unwrap_or(Utc::now().timestamp_micros() as u64);
115
116        // get chain id and order contract address
117        let rollup_chain_id = self.rollup_chain_id.ok_or(SigningError::MissingChainId)?;
118        let rollup_order_contract =
119            self.rollup_order_address.ok_or(SigningError::MissingOrderContract(rollup_chain_id))?;
120
121        // get the outputs for the Order
122        let outputs = self.order.outputs().to_vec();
123        // generate the permitted tokens from the Inputs on the Order
124        let permitted: Vec<TokenPermissions> = self.order.inputs().iter().map(Into::into).collect();
125
126        // generate the permit2 signing info
127        let permit = permit_signing_info(
128            outputs,
129            permitted,
130            self.order.deadline(),
131            nonce,
132            rollup_chain_id,
133            rollup_order_contract,
134        );
135
136        // sign it
137        let signature = signer.sign_hash(&permit.signing_hash).await?;
138
139        // return as a SignedOrder
140        Ok(SignedOrder {
141            permit: Permit2Batch {
142                permit: permit.permit,
143                owner: signer.address(),
144                signature: signature.as_bytes().into(),
145            },
146            outputs: permit.outputs,
147        })
148    }
149}