pub fn u64_be(value: u64) -> [u8; 8] {
value.to_be_bytes()
}
pub fn function_selector(name: &str) -> Vec<u8> {
let name_bytes = name.as_bytes();
let mut result = Vec::with_capacity(8 + name_bytes.len());
result.extend_from_slice(&u64_be(name_bytes.len() as u64));
result.extend_from_slice(name_bytes);
result
}
pub fn encode_identity(discriminant: u64, address: &[u8; 32]) -> Vec<u8> {
let mut result = Vec::with_capacity(40);
result.extend_from_slice(&u64_be(discriminant));
result.extend_from_slice(address);
result
}
pub fn encode_option_none() -> Vec<u8> {
u64_be(0).to_vec()
}
pub fn encode_option_some(data: &[u8]) -> Vec<u8> {
let mut result = Vec::with_capacity(8 + data.len());
result.extend_from_slice(&u64_be(1));
result.extend_from_slice(data);
result
}
pub fn encode_option_call_data(data: Option<&[u8]>) -> Vec<u8> {
match data {
None => u64_be(0).to_vec(),
Some(d) => {
let mut result = Vec::with_capacity(16 + d.len());
result.extend_from_slice(&u64_be(1));
result.extend_from_slice(&u64_be(d.len() as u64));
result.extend_from_slice(d);
result
}
}
}
#[derive(Debug, Clone)]
pub enum OrderTypeEncoding {
Limit { price: u64, timestamp: u64 },
Spot,
FillOrKill,
PostOnly,
Market,
BoundedMarket { max_price: u64, min_price: u64 },
}
pub fn encode_order_args(price: u64, quantity: u64, order_type: &OrderTypeEncoding) -> Vec<u8> {
let mut result = Vec::with_capacity(40);
result.extend_from_slice(&u64_be(price));
result.extend_from_slice(&u64_be(quantity));
match order_type {
OrderTypeEncoding::Limit {
price: limit_price,
timestamp,
} => {
result.extend_from_slice(&u64_be(0));
result.extend_from_slice(&u64_be(*limit_price));
result.extend_from_slice(&u64_be(*timestamp));
}
OrderTypeEncoding::Spot => {
result.extend_from_slice(&u64_be(1));
}
OrderTypeEncoding::FillOrKill => {
result.extend_from_slice(&u64_be(2));
}
OrderTypeEncoding::PostOnly => {
result.extend_from_slice(&u64_be(3));
}
OrderTypeEncoding::Market => {
result.extend_from_slice(&u64_be(4));
}
OrderTypeEncoding::BoundedMarket {
max_price,
min_price,
} => {
result.extend_from_slice(&u64_be(5));
result.extend_from_slice(&u64_be(*max_price));
result.extend_from_slice(&u64_be(*min_price));
}
}
result
}
pub fn build_session_signing_bytes(
nonce: u64,
chain_id: u64,
session_address: &[u8; 32],
contract_ids: &[[u8; 32]],
expiry: u64,
) -> Vec<u8> {
let func_name = b"set_session";
let mut result = Vec::with_capacity(128 + contract_ids.len() * 32);
result.extend_from_slice(&u64_be(nonce));
result.extend_from_slice(&u64_be(chain_id));
result.extend_from_slice(&u64_be(func_name.len() as u64));
result.extend_from_slice(func_name);
result.extend_from_slice(&u64_be(1));
result.extend_from_slice(&u64_be(0));
result.extend_from_slice(session_address);
result.extend_from_slice(&u64_be(expiry));
result.extend_from_slice(&u64_be(contract_ids.len() as u64));
for cid in contract_ids {
result.extend_from_slice(cid);
}
result
}
pub struct CallArg {
pub contract_id: [u8; 32],
pub function_selector: Vec<u8>,
pub amount: u64,
pub asset_id: [u8; 32],
pub gas: u64,
pub call_data: Option<Vec<u8>>,
}
pub const GAS_MAX: u64 = u64::MAX;
pub fn build_actions_signing_bytes(nonce: u64, calls: &[CallArg]) -> Vec<u8> {
let mut result = Vec::with_capacity(256);
result.extend_from_slice(&u64_be(nonce));
result.extend_from_slice(&u64_be(calls.len() as u64));
for call in calls {
result.extend_from_slice(&call.contract_id);
result.extend_from_slice(&u64_be(call.function_selector.len() as u64));
result.extend_from_slice(&call.function_selector);
result.extend_from_slice(&u64_be(call.amount));
result.extend_from_slice(&call.asset_id);
result.extend_from_slice(&u64_be(call.gas));
result.extend_from_slice(&encode_option_call_data(call.call_data.as_deref()));
}
result
}
#[allow(clippy::too_many_arguments)]
pub fn create_order_to_call(
contract_id: &[u8; 32],
side: &str,
price: u64,
quantity: u64,
order_type: &OrderTypeEncoding,
base_decimals: u32,
base_asset: &[u8; 32],
quote_asset: &[u8; 32],
) -> CallArg {
let call_data = encode_order_args(price, quantity, order_type);
let (amount, asset_id) = if side == "Buy" {
let amt = (price as u128 * quantity as u128) / 10u128.pow(base_decimals);
(amt as u64, *quote_asset)
} else {
(quantity, *base_asset)
};
CallArg {
contract_id: *contract_id,
function_selector: function_selector("create_order"),
amount,
asset_id,
gas: GAS_MAX,
call_data: Some(call_data),
}
}
pub fn cancel_order_to_call(contract_id: &[u8; 32], order_id: &[u8; 32]) -> CallArg {
CallArg {
contract_id: *contract_id,
function_selector: function_selector("cancel_order"),
amount: 0,
asset_id: [0u8; 32],
gas: GAS_MAX,
call_data: Some(order_id.to_vec()),
}
}
pub fn settle_balance_to_call(
contract_id: &[u8; 32],
to_discriminant: u64,
to_address: &[u8; 32],
) -> CallArg {
CallArg {
contract_id: *contract_id,
function_selector: function_selector("settle_balance"),
amount: 0,
asset_id: [0u8; 32],
gas: GAS_MAX,
call_data: Some(encode_identity(to_discriminant, to_address)),
}
}
pub fn build_withdraw_signing_bytes(
nonce: u64,
chain_id: u64,
to_discriminant: u64,
to_address: &[u8; 32],
asset_id: &[u8; 32],
amount: u64,
) -> Vec<u8> {
let func_name = b"withdraw";
let mut result = Vec::with_capacity(128);
result.extend_from_slice(&u64_be(nonce));
result.extend_from_slice(&u64_be(chain_id));
result.extend_from_slice(&u64_be(func_name.len() as u64));
result.extend_from_slice(func_name);
result.extend_from_slice(&u64_be(to_discriminant));
result.extend_from_slice(to_address);
result.extend_from_slice(asset_id);
result.extend_from_slice(&u64_be(amount));
result
}
pub fn action_to_call(
action: &crate::models::Action,
market: &crate::models::Market,
trade_account_id: &str,
accounts_registry_id: Option<&[u8; 32]>,
) -> Result<(CallArg, serde_json::Value), crate::errors::O2Error> {
use crate::crypto::parse_hex_32;
use crate::models::{Action, Identity};
let contract_id = parse_hex_32(&market.contract_id)?;
match action {
Action::CreateOrder {
side,
price,
quantity,
order_type,
} => {
let base_asset = parse_hex_32(market.base.asset.as_str())?;
let quote_asset = parse_hex_32(market.quote.asset.as_str())?;
let scaled_price = market.scale_price(price);
let scaled_quantity = market.scale_quantity(quantity);
let scaled_quantity = market.adjust_quantity(scaled_price, scaled_quantity);
market
.validate_order(scaled_price, scaled_quantity)
.map_err(crate::errors::O2Error::InvalidOrderParams)?;
let (ot_encoding, ot_json) = order_type.to_encoding(market);
let side_str = side.as_str();
let call = create_order_to_call(
&contract_id,
side_str,
scaled_price,
scaled_quantity,
&ot_encoding,
market.base.decimals,
&base_asset,
"e_asset,
);
let json = serde_json::json!({
"CreateOrder": {
"side": side_str,
"price": scaled_price.to_string(),
"quantity": scaled_quantity.to_string(),
"order_type": ot_json
}
});
Ok((call, json))
}
Action::CancelOrder { order_id } => {
let order_id_bytes = parse_hex_32(order_id.as_str())?;
let call = cancel_order_to_call(&contract_id, &order_id_bytes);
let json = serde_json::json!({
"CancelOrder": { "order_id": order_id }
});
Ok((call, json))
}
Action::SettleBalance => {
let trade_account_bytes = parse_hex_32(trade_account_id)?;
let call = settle_balance_to_call(&contract_id, 1, &trade_account_bytes);
let json = serde_json::json!({
"SettleBalance": { "to": { "ContractId": trade_account_id } }
});
Ok((call, json))
}
Action::RegisterReferer { to } => {
let registry_id = accounts_registry_id.ok_or_else(|| {
crate::errors::O2Error::Other(
"accounts_registry_id required for RegisterReferer".into(),
)
})?;
let (disc, addr_hex) = match to {
Identity::Address(a) => (0u64, a.as_str()),
Identity::ContractId(c) => (1u64, c.as_str()),
};
let addr_bytes = parse_hex_32(addr_hex)?;
let call = register_referer_to_call(registry_id, disc, &addr_bytes);
let json = serde_json::json!({
"RegisterReferer": { "to": serde_json::to_value(to).unwrap_or_default() }
});
Ok((call, json))
}
}
}
pub fn register_referer_to_call(
accounts_registry_id: &[u8; 32],
referer_discriminant: u64,
referer_address: &[u8; 32],
) -> CallArg {
CallArg {
contract_id: *accounts_registry_id,
function_selector: function_selector("register_referer"),
amount: 0,
asset_id: [0u8; 32],
gas: GAS_MAX,
call_data: Some(encode_identity(referer_discriminant, referer_address)),
}
}