Skip to content

feat: example code to sign & send Orders #44

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 4 commits into
base: anna/filler-ex
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions crates/rpc/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -34,3 +34,7 @@ tokio = { workspace = true, features = ["macros"] }
tokio-util = "0.7.13"
tower-http = { version = "0.6.2", features = ["cors"] }
tracing.workspace = true

[dev-dependencies]
signet-zenith.workspace = true
chrono.workspace = true
16 changes: 8 additions & 8 deletions crates/rpc/examples/filler.rs
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,7 @@ where

/// Gets a set of fillable Orders from the TransactionCache and fills them in aggregate
/// by submitting them all in a single, atomic Bundle.
///
///
/// Filling Orders in aggregate means that Fills are batched and more gas efficient;
/// however, if a single Order cannot be filled, then the entire Bundle will not mine.
pub async fn run(&self) -> Result<(), Error> {
Expand All @@ -84,7 +84,7 @@ where
}

/// Gets a set of fillable Orders from the TransactionCache and fills them individually.
///
///
/// Filling Orders individually ensures that even if some Orders are not fillable, others may still mine;
/// however, it is less gas efficient.
pub async fn run_individual(&self) -> Result<(), Error> {
Expand Down Expand Up @@ -120,7 +120,7 @@ where
///
/// Filling Orders individually ensures that even if some Orders are not fillable, others may still mine;
/// however, it is less gas efficient.
pub async fn fill_individual(&self, orders:Vec<SignedOrder>) -> Result<(), Error> {
pub async fn fill_individual(&self, orders: Vec<SignedOrder>) -> Result<(), Error> {
// submit one bundle per individual order
for order in orders {
self.fill(vec![order]).await?;
Expand All @@ -130,17 +130,17 @@ where
}

/// Fills one or more Orders in a single, atomic Bundle.
///
/// Signs Fill(s) for the Order(s);
///
/// Signs Fill(s) for the Order(s);
/// constructs a Bundle of transactions to fill the Order(s);
/// sends the Bundle to the transaction cache.
///
/// If more than one Order is passed to this fn,
///
/// If more than one Order is passed to this fn,
/// Filling them in aggregate means that Fills are batched and more gas efficient;
/// however, if a single Order cannot be filled, then the entire Bundle will not mine.
/// For example, using this strategy, if one Order is filled by another Filler first, then all other Orders will also not be filled.
///
/// If a single Order is passed to this fn,
/// If a single Order is passed to this fn,
/// Filling Orders individually ensures that even if some Orders are not fillable, others may still mine;
/// however, it is less gas efficient.
pub async fn fill(&self, orders: Vec<SignedOrder>) -> Result<(), Error> {
Expand Down
109 changes: 109 additions & 0 deletions crates/rpc/examples/order.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
use alloy::{
primitives::{Address, U256},
signers::Signer,
};
use chrono::Utc;
use eyre::Error;
use signet_rpc::TxCache;
use signet_types::UnsignedOrder;
use signet_zenith::RollupOrders::{Input, Order, Output};

/// Helper fn to convert from a human readable amount to a U256 token amount.
fn token_amount(amount: u64, decimals: u32) -> U256 {
U256::from(amount * 10u64.pow(decimals))
}
Comment on lines +12 to +14
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Use ParseUnits from alloy::primitives to perform this conversion—no need to implement yourself


/// Empty main to silence clippy.
fn main() {}
Comment on lines +16 to +17
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

let's put the main fn at the bottom


/// Example code demonstrating API usage and patterns for signing an Order.
#[derive(Debug)]
pub struct SendOrder<S: Signer> {
/// The signer to use for signing the order.
signer: S,
/// The transaction cache endpoint.
tx_cache: TxCache,
/// The address of the Order contract on the rollup.
ru_order_contract: Address,
/// The address of USDC on the rollup.
ru_usdc_address: Address,
/// The address of USDC on the host.
host_usdc_address: Address,
/// The chain id of the rollup.
ru_chain_id: u64,
/// The chain id of the host.
host_chain_id: u64,
}

impl<S> SendOrder<S>
where
S: Signer,
{
/// Create a new SendOrder instance.
pub const fn new(
signer: S,
tx_cache: TxCache,
ru_order_contract: Address,
ru_usdc_address: Address,
host_usdc_address: Address,
ru_chain_id: u64,
host_chain_id: u64,
) -> Self {
Self {
signer,
tx_cache,
ru_order_contract,
ru_usdc_address,
host_usdc_address,
ru_chain_id,
host_chain_id,
}
}

/// Construct a simple example Order, sign it, and send it.
pub async fn run(&self) -> Result<(), Error> {
// get an example order
let order = self.example_order();

// sign and send the order
self.sign_and_send_order(order).await
}

/// Sign an Order and send it to the transaction cache to be Filled.
pub async fn sign_and_send_order(&self, order: Order) -> Result<(), Error> {
// make an UnsignedOrder from the Order
let unsigned = UnsignedOrder::from(&order);

// sign it
let signed = unsigned
.with_chain(self.ru_chain_id, self.ru_order_contract)
.sign(&self.signer)
.await?;

// send the SignedOrder to the transaction cache
self.tx_cache.forward_order(signed).await
}

/// Get an example Order which swaps 1 USDC on the rollup for 1 USDC on the host.
fn example_order(&self) -> Order {
let usdc_decimals: u32 = 6;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

let's make this a const at the top

let one_usdc = token_amount(1, usdc_decimals);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why are we calculating this at all? it could be a file-level const const ONE_USDC = uint!(1_000_000_U256);


// input is 1 USDC on the rollup
let input = Input { token: self.ru_usdc_address, amount: one_usdc };

// output is 1 USDC on the host chain
let output = Output {
token: self.host_usdc_address,
amount: one_usdc,
chainId: self.host_chain_id as u32,
recipient: self.signer.address(),
};
Comment on lines +92 to +101
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

unrelated to this pr, but i still think we should have builder types for these so we don't have to declare them like this

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

agree, I started working on that code already but was prioritizing this first


// deadline 60 seconds (or ~5 blocks) from now
let deadline = Utc::now().timestamp() + 60;

// construct the order
Order { inputs: vec![input], outputs: vec![output], deadline: U256::from(deadline) }
}
}