use super::goldilocks::GFp;
use super::gfp5::GFp5;
use super::poseidon2::{hash_to_quintic_extension, bytes_to_field_elements};
pub const TX_TYPE_CREATE_ORDER: u32 = 14;
pub const TX_TYPE_CANCEL_ORDER: u32 = 15;
pub const TX_TYPE_CANCEL_ALL_ORDERS: u32 = 16;
pub const TX_TYPE_MODIFY_ORDER: u32 = 17;
pub const TX_TYPE_TRANSFER: u32 = 12;
pub const TX_TYPE_WITHDRAW: u32 = 13;
pub const TX_TYPE_CHANGE_PUB_KEY: u32 = 8;
pub const TX_TYPE_CREATE_SUB_ACCOUNT: u32 = 9;
pub const TX_TYPE_MINT_SHARES: u32 = 18;
pub const TX_TYPE_BURN_SHARES: u32 = 19;
pub const TX_TYPE_UPDATE_LEVERAGE: u32 = 20;
pub const TX_TYPE_UPDATE_MARGIN: u32 = 29;
pub const TX_TYPE_STAKE_ASSETS: u32 = 35;
pub const TX_TYPE_UNSTAKE_ASSETS: u32 = 36;
pub const TX_TYPE_APPROVE_INTEGRATOR: u32 = 45;
pub const TX_TYPE_CREATE_GROUPED_ORDERS: u32 = 28;
pub const CHAIN_ID_MAINNET: u32 = 304;
pub const CHAIN_ID_TESTNET: u32 = 300;
pub const NIL_CLIENT_ORDER_INDEX: i64 = (1i64 << 48) - 1;
pub const ORDER_TYPE_LIMIT: u8 = 0;
pub const ORDER_TYPE_MARKET: u8 = 1;
pub const ORDER_TYPE_STOP_LOSS: u8 = 2;
pub const ORDER_TYPE_STOP_LOSS_LIMIT: u8 = 3;
pub const ORDER_TYPE_TAKE_PROFIT: u8 = 4;
pub const ORDER_TYPE_TAKE_PROFIT_LIMIT: u8 = 5;
pub const ORDER_TYPE_TWAP: u8 = 6;
pub const TIF_IMMEDIATE_OR_CANCEL: u8 = 0;
pub const TIF_GOOD_TILL_TIME: u8 = 1;
pub const TIF_POST_ONLY: u8 = 2;
#[inline]
pub fn from_u32(v: u32) -> GFp {
GFp::from_canonical_u64(v as u64)
}
#[inline]
pub fn from_i64(v: i64) -> GFp {
GFp::from_canonical_u64(v as u64)
}
#[inline]
pub fn from_u64(v: u64) -> GFp {
GFp::from_canonical_u64(v)
}
#[derive(Debug, Clone)]
pub struct CreateOrderFields {
pub chain_id: u32,
pub nonce: i64,
pub expired_at: i64, pub account_index: i64,
pub api_key_index: u8,
pub market_index: i16,
pub client_order_index: i64, pub base_amount: i64,
pub price: u32,
pub is_ask: bool,
pub order_type: u8,
pub time_in_force: u8,
pub reduce_only: bool,
pub trigger_price: u32,
pub order_expiry: i64, }
pub fn hash_create_order(fields: &CreateOrderFields) -> GFp5 {
let elems: [GFp; 16] = [
from_u32(fields.chain_id), from_u32(TX_TYPE_CREATE_ORDER), from_i64(fields.nonce), from_i64(fields.expired_at), from_i64(fields.account_index), from_u32(fields.api_key_index as u32), from_u32(fields.market_index as u32), from_i64(fields.client_order_index), from_i64(fields.base_amount), from_u32(fields.price), from_u32(fields.is_ask as u32), from_u32(fields.order_type as u32), from_u32(fields.time_in_force as u32), from_u32(fields.reduce_only as u32), from_u32(fields.trigger_price), from_i64(fields.order_expiry), ];
hash_to_quintic_extension(&elems)
}
pub fn hash_create_order_bytes(fields: &CreateOrderFields) -> [u8; 40] {
hash_create_order(fields).encode()
}
#[derive(Debug, Clone)]
pub struct CancelOrderFields {
pub chain_id: u32,
pub nonce: i64,
pub expired_at: i64, pub account_index: i64,
pub api_key_index: u8,
pub market_index: i16,
pub index: i64, }
pub fn hash_cancel_order(fields: &CancelOrderFields) -> GFp5 {
let elems: [GFp; 8] = [
from_u32(fields.chain_id), from_u32(TX_TYPE_CANCEL_ORDER), from_i64(fields.nonce), from_i64(fields.expired_at), from_i64(fields.account_index), from_u32(fields.api_key_index as u32), from_u32(fields.market_index as u32), from_i64(fields.index), ];
hash_to_quintic_extension(&elems)
}
pub fn hash_cancel_order_bytes(fields: &CancelOrderFields) -> [u8; 40] {
hash_cancel_order(fields).encode()
}
pub fn hash_auth_token(deadline: u64, account_index: i64, api_key_index: u8) -> GFp5 {
let message = format!("{}:{}:{}", deadline, account_index, api_key_index);
let elems = bytes_to_field_elements(message.as_bytes());
hash_to_quintic_extension(&elems)
}
pub fn hash_auth_token_bytes(deadline: u64, account_index: i64, api_key_index: u8) -> [u8; 40] {
hash_auth_token(deadline, account_index, api_key_index).encode()
}
#[derive(Debug, Clone)]
pub struct WithdrawFields {
pub chain_id: u32,
pub nonce: i64,
pub expired_at: i64,
pub from_account_index: i64,
pub api_key_index: u8,
pub asset_index: i16,
pub route_type: u8,
pub amount: u64, }
pub fn hash_withdraw(fields: &WithdrawFields) -> GFp5 {
let elems: [GFp; 10] = [
from_u32(fields.chain_id),
from_u32(TX_TYPE_WITHDRAW),
from_i64(fields.nonce),
from_i64(fields.expired_at),
from_i64(fields.from_account_index),
from_u32(fields.api_key_index as u32),
from_u32(fields.asset_index as u32),
from_u32(fields.route_type as u32),
from_u64(fields.amount & 0xFFFFFFFF), from_u64(fields.amount >> 32), ];
hash_to_quintic_extension(&elems)
}
pub fn hash_change_pub_key(
chain_id: u32,
nonce: i64,
expired_at: i64,
account_index: i64,
api_key_index: u8,
new_pub_key: &[u8; 40],
) -> GFp5 {
let mut elems = Vec::with_capacity(11);
elems.push(from_u32(chain_id));
elems.push(from_u32(TX_TYPE_CHANGE_PUB_KEY));
elems.push(from_i64(nonce));
elems.push(from_i64(expired_at));
elems.push(from_i64(account_index));
elems.push(from_u32(api_key_index as u32));
for i in 0..5 {
let chunk: [u8; 8] = new_pub_key[i * 8..(i + 1) * 8].try_into().unwrap();
let v = u64::from_le_bytes(chunk);
elems.push(GFp::from_canonical_u64(v));
}
hash_to_quintic_extension(&elems)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_from_i64_negative() {
let v = from_i64(-1i64);
let canonical = v.to_u64();
let expected = (-1i64 as u64) % GFp::MOD;
assert_eq!(
canonical, expected,
"from_i64(-1) must reinterpret bit pattern, not mathematical -1 mod p"
);
assert_eq!(canonical, 0xFFFF_FFFE, "matches Lighter Go SDK output");
}
#[test]
fn test_hash_create_order_deterministic() {
let fields = CreateOrderFields {
chain_id: CHAIN_ID_MAINNET,
nonce: 1,
expired_at: 1741000000000,
account_index: 12345,
api_key_index: 3,
market_index: 0,
client_order_index: NIL_CLIENT_ORDER_INDEX,
base_amount: 10000,
price: 400000,
is_ask: false,
order_type: ORDER_TYPE_LIMIT,
time_in_force: TIF_POST_ONLY,
reduce_only: false,
trigger_price: 0,
order_expiry: 1741000000000 + 28 * 24 * 60 * 60 * 1000,
};
let hash1 = hash_create_order_bytes(&fields);
let hash2 = hash_create_order_bytes(&fields);
assert_eq!(hash1, hash2, "hash is deterministic");
}
#[test]
fn test_hash_cancel_order_deterministic() {
let fields = CancelOrderFields {
chain_id: CHAIN_ID_MAINNET,
nonce: 2,
expired_at: 1741000000000,
account_index: 12345,
api_key_index: 3,
market_index: 0,
index: 9876,
};
let hash1 = hash_cancel_order_bytes(&fields);
let hash2 = hash_cancel_order_bytes(&fields);
assert_eq!(hash1, hash2, "cancel hash is deterministic");
}
#[test]
fn test_hash_auth_token_format() {
let deadline: u64 = 1741000000;
let account: i64 = 12345;
let api_key: u8 = 1;
let hash = hash_auth_token_bytes(deadline, account, api_key);
assert_ne!(hash, [0u8; 40], "auth token hash is non-zero");
}
#[test]
fn test_nil_client_order_index() {
assert_eq!(NIL_CLIENT_ORDER_INDEX, 281474976710655, "NIL is (1<<48)-1");
}
}