1pub fn u64_be(value: u64) -> [u8; 8] {
14 value.to_be_bytes()
15}
16
17pub fn function_selector(name: &str) -> Vec<u8> {
20 let name_bytes = name.as_bytes();
21 let mut result = Vec::with_capacity(8 + name_bytes.len());
22 result.extend_from_slice(&u64_be(name_bytes.len() as u64));
23 result.extend_from_slice(name_bytes);
24 result
25}
26
27pub fn encode_identity(discriminant: u64, address: &[u8; 32]) -> Vec<u8> {
30 let mut result = Vec::with_capacity(40);
31 result.extend_from_slice(&u64_be(discriminant));
32 result.extend_from_slice(address);
33 result
34}
35
36pub fn encode_option_none() -> Vec<u8> {
38 u64_be(0).to_vec()
39}
40
41pub fn encode_option_some(data: &[u8]) -> Vec<u8> {
43 let mut result = Vec::with_capacity(8 + data.len());
44 result.extend_from_slice(&u64_be(1));
45 result.extend_from_slice(data);
46 result
47}
48
49pub fn encode_option_call_data(data: Option<&[u8]>) -> Vec<u8> {
53 match data {
54 None => u64_be(0).to_vec(),
55 Some(d) => {
56 let mut result = Vec::with_capacity(16 + d.len());
57 result.extend_from_slice(&u64_be(1));
58 result.extend_from_slice(&u64_be(d.len() as u64));
59 result.extend_from_slice(d);
60 result
61 }
62 }
63}
64
65#[derive(Debug, Clone)]
67pub enum OrderTypeEncoding {
68 Limit { price: u64, timestamp: u64 },
69 Spot,
70 FillOrKill,
71 PostOnly,
72 Market,
73 BoundedMarket { max_price: u64, min_price: u64 },
74}
75
76pub fn encode_order_args(price: u64, quantity: u64, order_type: &OrderTypeEncoding) -> Vec<u8> {
79 let mut result = Vec::with_capacity(40);
80 result.extend_from_slice(&u64_be(price));
81 result.extend_from_slice(&u64_be(quantity));
82
83 match order_type {
84 OrderTypeEncoding::Limit {
85 price: limit_price,
86 timestamp,
87 } => {
88 result.extend_from_slice(&u64_be(0));
89 result.extend_from_slice(&u64_be(*limit_price));
90 result.extend_from_slice(&u64_be(*timestamp));
91 }
92 OrderTypeEncoding::Spot => {
93 result.extend_from_slice(&u64_be(1));
94 }
95 OrderTypeEncoding::FillOrKill => {
96 result.extend_from_slice(&u64_be(2));
97 }
98 OrderTypeEncoding::PostOnly => {
99 result.extend_from_slice(&u64_be(3));
100 }
101 OrderTypeEncoding::Market => {
102 result.extend_from_slice(&u64_be(4));
103 }
104 OrderTypeEncoding::BoundedMarket {
105 max_price,
106 min_price,
107 } => {
108 result.extend_from_slice(&u64_be(5));
109 result.extend_from_slice(&u64_be(*max_price));
110 result.extend_from_slice(&u64_be(*min_price));
111 }
112 }
113
114 result
115}
116
117pub fn build_session_signing_bytes(
124 nonce: u64,
125 chain_id: u64,
126 session_address: &[u8; 32],
127 contract_ids: &[[u8; 32]],
128 expiry: u64,
129) -> Vec<u8> {
130 let func_name = b"set_session";
131
132 let mut result = Vec::with_capacity(128 + contract_ids.len() * 32);
133
134 result.extend_from_slice(&u64_be(nonce));
136 result.extend_from_slice(&u64_be(chain_id));
137
138 result.extend_from_slice(&u64_be(func_name.len() as u64));
140 result.extend_from_slice(func_name);
141
142 result.extend_from_slice(&u64_be(1));
144 result.extend_from_slice(&u64_be(0));
146 result.extend_from_slice(session_address);
148 result.extend_from_slice(&u64_be(expiry));
150 result.extend_from_slice(&u64_be(contract_ids.len() as u64));
152 for cid in contract_ids {
153 result.extend_from_slice(cid);
154 }
155
156 result
157}
158
159pub struct CallArg {
161 pub contract_id: [u8; 32],
162 pub function_selector: Vec<u8>,
163 pub amount: u64,
164 pub asset_id: [u8; 32],
165 pub gas: u64,
166 pub call_data: Option<Vec<u8>>,
167}
168
169pub const GAS_MAX: u64 = u64::MAX;
171
172pub fn build_actions_signing_bytes(nonce: u64, calls: &[CallArg]) -> Vec<u8> {
179 let mut result = Vec::with_capacity(256);
180
181 result.extend_from_slice(&u64_be(nonce));
182 result.extend_from_slice(&u64_be(calls.len() as u64));
183
184 for call in calls {
185 result.extend_from_slice(&call.contract_id);
186 result.extend_from_slice(&u64_be(call.function_selector.len() as u64));
187 result.extend_from_slice(&call.function_selector);
188 result.extend_from_slice(&u64_be(call.amount));
189 result.extend_from_slice(&call.asset_id);
190 result.extend_from_slice(&u64_be(call.gas));
191 result.extend_from_slice(&encode_option_call_data(call.call_data.as_deref()));
192 }
193
194 result
195}
196
197#[allow(clippy::too_many_arguments)]
199pub fn create_order_to_call(
200 contract_id: &[u8; 32],
201 side: &str,
202 price: u64,
203 quantity: u64,
204 order_type: &OrderTypeEncoding,
205 base_decimals: u32,
206 base_asset: &[u8; 32],
207 quote_asset: &[u8; 32],
208) -> CallArg {
209 let call_data = encode_order_args(price, quantity, order_type);
210
211 let (amount, asset_id) = if side == "Buy" {
212 let amt = (price as u128 * quantity as u128) / 10u128.pow(base_decimals);
213 (amt as u64, *quote_asset)
214 } else {
215 (quantity, *base_asset)
216 };
217
218 CallArg {
219 contract_id: *contract_id,
220 function_selector: function_selector("create_order"),
221 amount,
222 asset_id,
223 gas: GAS_MAX,
224 call_data: Some(call_data),
225 }
226}
227
228pub fn cancel_order_to_call(contract_id: &[u8; 32], order_id: &[u8; 32]) -> CallArg {
230 CallArg {
231 contract_id: *contract_id,
232 function_selector: function_selector("cancel_order"),
233 amount: 0,
234 asset_id: [0u8; 32],
235 gas: GAS_MAX,
236 call_data: Some(order_id.to_vec()),
237 }
238}
239
240pub fn settle_balance_to_call(
243 contract_id: &[u8; 32],
244 to_discriminant: u64,
245 to_address: &[u8; 32],
246) -> CallArg {
247 CallArg {
248 contract_id: *contract_id,
249 function_selector: function_selector("settle_balance"),
250 amount: 0,
251 asset_id: [0u8; 32],
252 gas: GAS_MAX,
253 call_data: Some(encode_identity(to_discriminant, to_address)),
254 }
255}
256
257pub fn build_withdraw_signing_bytes(
264 nonce: u64,
265 chain_id: u64,
266 to_discriminant: u64,
267 to_address: &[u8; 32],
268 asset_id: &[u8; 32],
269 amount: u64,
270) -> Vec<u8> {
271 let func_name = b"withdraw";
272
273 let mut result = Vec::with_capacity(128);
274 result.extend_from_slice(&u64_be(nonce));
275 result.extend_from_slice(&u64_be(chain_id));
276 result.extend_from_slice(&u64_be(func_name.len() as u64));
277 result.extend_from_slice(func_name);
278 result.extend_from_slice(&u64_be(to_discriminant));
280 result.extend_from_slice(to_address);
281 result.extend_from_slice(asset_id);
283 result.extend_from_slice(&u64_be(amount));
285
286 result
287}
288
289pub fn action_to_call(
294 action: &crate::models::Action,
295 market: &crate::models::Market,
296 trade_account_id: &str,
297 accounts_registry_id: Option<&[u8; 32]>,
298) -> Result<(CallArg, serde_json::Value), crate::errors::O2Error> {
299 use crate::crypto::parse_hex_32;
300 use crate::models::{Action, Identity};
301
302 let contract_id = parse_hex_32(&market.contract_id)?;
303
304 match action {
305 Action::CreateOrder {
306 side,
307 price,
308 quantity,
309 order_type,
310 } => {
311 let base_asset = parse_hex_32(market.base.asset.as_str())?;
312 let quote_asset = parse_hex_32(market.quote.asset.as_str())?;
313 let scaled_price = market.scale_price(price)?;
314 let scaled_quantity = market.scale_quantity(quantity)?;
315 let scaled_quantity = market.adjust_quantity(scaled_price, scaled_quantity)?;
316
317 market.validate_order(scaled_price, scaled_quantity)?;
318
319 let (ot_encoding, ot_json) = order_type.to_encoding(market)?;
320 let side_str = side.as_str();
321
322 let call = create_order_to_call(
323 &contract_id,
324 side_str,
325 scaled_price,
326 scaled_quantity,
327 &ot_encoding,
328 market.base.decimals,
329 &base_asset,
330 "e_asset,
331 );
332
333 let json = serde_json::json!({
334 "CreateOrder": {
335 "side": side_str,
336 "price": scaled_price.to_string(),
337 "quantity": scaled_quantity.to_string(),
338 "order_type": ot_json
339 }
340 });
341
342 Ok((call, json))
343 }
344 Action::CancelOrder { order_id } => {
345 let order_id_bytes = parse_hex_32(order_id.as_str())?;
346 let call = cancel_order_to_call(&contract_id, &order_id_bytes);
347 let json = serde_json::json!({
348 "CancelOrder": { "order_id": order_id }
349 });
350 Ok((call, json))
351 }
352 Action::SettleBalance => {
353 let trade_account_bytes = parse_hex_32(trade_account_id)?;
354 let call = settle_balance_to_call(&contract_id, 1, &trade_account_bytes);
355 let json = serde_json::json!({
356 "SettleBalance": { "to": { "ContractId": trade_account_id } }
357 });
358 Ok((call, json))
359 }
360 Action::RegisterReferer { to } => {
361 let registry_id = accounts_registry_id.ok_or_else(|| {
362 crate::errors::O2Error::Other(
363 "accounts_registry_id required for RegisterReferer".into(),
364 )
365 })?;
366 let (disc, addr_hex) = match to {
367 Identity::Address(a) => (0u64, a.as_str()),
368 Identity::ContractId(c) => (1u64, c.as_str()),
369 };
370 let addr_bytes = parse_hex_32(addr_hex)?;
371 let call = register_referer_to_call(registry_id, disc, &addr_bytes);
372 let json = serde_json::json!({
373 "RegisterReferer": { "to": serde_json::to_value(to).unwrap_or_default() }
374 });
375 Ok((call, json))
376 }
377 }
378}
379
380pub fn register_referer_to_call(
382 accounts_registry_id: &[u8; 32],
383 referer_discriminant: u64,
384 referer_address: &[u8; 32],
385) -> CallArg {
386 CallArg {
387 contract_id: *accounts_registry_id,
388 function_selector: function_selector("register_referer"),
389 amount: 0,
390 asset_id: [0u8; 32],
391 gas: GAS_MAX,
392 call_data: Some(encode_identity(referer_discriminant, referer_address)),
393 }
394}