use sha3::{Digest, Keccak256};
use solana_pubkey::Pubkey;
use solana_signature::Signature;
#[cfg(feature = "native-auth")]
use solana_keypair::Keypair;
#[cfg(feature = "native-auth")]
use solana_signer::Signer;
use crate::program::constants::{ORDER_SIZE, SIGNED_ORDER_SIZE};
use crate::program::error::{SdkError, SdkResult};
use crate::program::types::{AskOrderParams, BidOrderParams, OrderSide};
use crate::shared::SubmitOrderRequest;
#[derive(Debug, Clone)]
pub struct OrderPayload {
pub nonce: u64,
pub salt: u64,
pub maker: Pubkey,
pub market: Pubkey,
pub base_mint: Pubkey,
pub quote_mint: Pubkey,
pub side: OrderSide,
pub amount_in: u64,
pub amount_out: u64,
pub expiration: i64,
pub signature: [u8; 64],
}
impl OrderPayload {
pub const LEN: usize = SIGNED_ORDER_SIZE;
pub const HASH_SIZE: usize = 169;
pub fn new_bid(params: BidOrderParams) -> Self {
Self {
nonce: params.nonce,
salt: params.salt,
maker: params.maker,
market: params.market,
base_mint: params.base_mint,
quote_mint: params.quote_mint,
side: OrderSide::Bid,
amount_in: params.amount_in,
amount_out: params.amount_out,
expiration: params.expiration,
signature: [0u8; 64],
}
}
pub fn new_ask(params: AskOrderParams) -> Self {
Self {
nonce: params.nonce,
salt: params.salt,
maker: params.maker,
market: params.market,
base_mint: params.base_mint,
quote_mint: params.quote_mint,
side: OrderSide::Ask,
amount_in: params.amount_in,
amount_out: params.amount_out,
expiration: params.expiration,
signature: [0u8; 64],
}
}
fn signing_message(&self) -> [u8; Self::HASH_SIZE] {
let mut data = [0u8; Self::HASH_SIZE];
data[0..8].copy_from_slice(&self.nonce.to_le_bytes());
data[8..16].copy_from_slice(&self.salt.to_le_bytes());
data[16..48].copy_from_slice(self.maker.as_ref());
data[48..80].copy_from_slice(self.market.as_ref());
data[80..112].copy_from_slice(self.base_mint.as_ref());
data[112..144].copy_from_slice(self.quote_mint.as_ref());
data[144] = self.side as u8;
data[145..153].copy_from_slice(&self.amount_in.to_le_bytes());
data[153..161].copy_from_slice(&self.amount_out.to_le_bytes());
data[161..169].copy_from_slice(&self.expiration.to_le_bytes());
data
}
pub fn hash(&self) -> [u8; 32] {
Keccak256::digest(self.signing_message()).into()
}
pub fn hash_hex(&self) -> String {
hex::encode(self.hash())
}
#[cfg(feature = "native-auth")]
pub fn sign(&mut self, keypair: &Keypair) {
let hash = self.hash_hex();
let sig = keypair.sign_message(hash.as_bytes());
self.signature.copy_from_slice(sig.as_ref());
}
#[cfg(feature = "native-auth")]
pub fn new_bid_signed(params: BidOrderParams, keypair: &Keypair) -> Self {
let mut order = Self::new_bid(params);
order.sign(keypair);
order
}
#[cfg(feature = "native-auth")]
pub fn new_ask_signed(params: AskOrderParams, keypair: &Keypair) -> Self {
let mut order = Self::new_ask(params);
order.sign(keypair);
order
}
pub fn verify_signature(&self) -> SdkResult<()> {
let hash_hex = self.hash_hex();
let sig = Signature::try_from(self.signature.as_slice())
.map_err(|_| SdkError::InvalidSignature)?;
if !sig.verify(self.maker.as_ref(), hash_hex.as_bytes()) {
return Err(SdkError::SignatureVerificationFailed);
}
Ok(())
}
pub fn apply_signature(&mut self, sig_bs58: String) -> SdkResult<()> {
let signature = sig_bs58
.parse::<Signature>()
.map_err(|_| SdkError::InvalidSignature)?;
self.signature = signature.into();
Ok(())
}
pub fn serialize(&self) -> [u8; SIGNED_ORDER_SIZE] {
let mut data = [0u8; SIGNED_ORDER_SIZE];
data[0..8].copy_from_slice(&self.nonce.to_le_bytes());
data[8..16].copy_from_slice(&self.salt.to_le_bytes());
data[16..48].copy_from_slice(self.maker.as_ref());
data[48..80].copy_from_slice(self.market.as_ref());
data[80..112].copy_from_slice(self.base_mint.as_ref());
data[112..144].copy_from_slice(self.quote_mint.as_ref());
data[144] = self.side as u8;
data[145..153].copy_from_slice(&self.amount_in.to_le_bytes());
data[153..161].copy_from_slice(&self.amount_out.to_le_bytes());
data[161..169].copy_from_slice(&self.expiration.to_le_bytes());
data[169..233].copy_from_slice(&self.signature);
data
}
pub fn deserialize(data: &[u8]) -> SdkResult<Self> {
if data.len() < SIGNED_ORDER_SIZE {
return Err(SdkError::InvalidDataLength {
expected: SIGNED_ORDER_SIZE,
actual: data.len(),
});
}
let mut nonce_bytes = [0u8; 8];
nonce_bytes.copy_from_slice(&data[0..8]);
let mut salt_bytes = [0u8; 8];
salt_bytes.copy_from_slice(&data[8..16]);
let mut maker_bytes = [0u8; 32];
maker_bytes.copy_from_slice(&data[16..48]);
let mut market_bytes = [0u8; 32];
market_bytes.copy_from_slice(&data[48..80]);
let mut base_mint_bytes = [0u8; 32];
base_mint_bytes.copy_from_slice(&data[80..112]);
let mut quote_mint_bytes = [0u8; 32];
quote_mint_bytes.copy_from_slice(&data[112..144]);
let mut amount_in_bytes = [0u8; 8];
amount_in_bytes.copy_from_slice(&data[145..153]);
let mut amount_out_bytes = [0u8; 8];
amount_out_bytes.copy_from_slice(&data[153..161]);
let mut expiration_bytes = [0u8; 8];
expiration_bytes.copy_from_slice(&data[161..169]);
let mut signature = [0u8; 64];
signature.copy_from_slice(&data[169..233]);
Ok(Self {
nonce: u64::from_le_bytes(nonce_bytes),
salt: u64::from_le_bytes(salt_bytes),
maker: Pubkey::new_from_array(maker_bytes),
market: Pubkey::new_from_array(market_bytes),
base_mint: Pubkey::new_from_array(base_mint_bytes),
quote_mint: Pubkey::new_from_array(quote_mint_bytes),
side: OrderSide::try_from(data[144])?,
amount_in: u64::from_le_bytes(amount_in_bytes),
amount_out: u64::from_le_bytes(amount_out_bytes),
expiration: i64::from_le_bytes(expiration_bytes),
signature,
})
}
pub fn to_order(&self) -> Order {
Order {
nonce: self.nonce as u32,
salt: self.salt,
side: self.side,
amount_in: self.amount_in,
amount_out: self.amount_out,
expiration: self.expiration,
}
}
pub fn signature_hex(&self) -> String {
hex::encode(self.signature)
}
pub fn is_signed(&self) -> bool {
self.signature != [0u8; 64]
}
pub(crate) fn to_submit_request(
&self,
orderbook_id: impl Into<String>,
time_in_force: Option<crate::shared::TimeInForce>,
trigger_price: Option<f64>,
trigger_type: Option<crate::shared::TriggerType>,
deposit_source: Option<crate::shared::DepositSource>,
) -> Result<SubmitOrderRequest, SdkError> {
if self.signature == [0u8; 64] {
return Err(SdkError::UnsignedOrder);
}
Ok(SubmitOrderRequest {
maker: self.maker.to_string(),
nonce: self.nonce,
salt: self.salt,
market_pubkey: self.market.to_string(),
base_token: self.base_mint.to_string(),
quote_token: self.quote_mint.to_string(),
side: self.side as u32,
amount_in: self.amount_in,
amount_out: self.amount_out,
expiration: self.expiration,
signature: hex::encode(self.signature),
orderbook_id: orderbook_id.into(),
time_in_force,
trigger_price,
trigger_type,
deposit_source,
})
}
pub fn derive_orderbook_id(&self) -> String {
crate::shared::derive_orderbook_id(
&self.base_mint.to_string(),
&self.quote_mint.to_string(),
)
.to_string()
}
}
#[derive(Debug, Clone)]
pub struct Order {
pub nonce: u32,
pub salt: u64,
pub side: OrderSide,
pub amount_in: u64,
pub amount_out: u64,
pub expiration: i64,
}
impl Order {
pub const LEN: usize = ORDER_SIZE;
pub fn serialize(&self) -> [u8; ORDER_SIZE] {
let mut data = [0u8; ORDER_SIZE];
data[0..4].copy_from_slice(&self.nonce.to_le_bytes());
data[4..12].copy_from_slice(&self.salt.to_le_bytes());
data[12] = self.side as u8;
data[13..21].copy_from_slice(&self.amount_in.to_le_bytes());
data[21..29].copy_from_slice(&self.amount_out.to_le_bytes());
data[29..37].copy_from_slice(&self.expiration.to_le_bytes());
data
}
pub fn deserialize(data: &[u8]) -> SdkResult<Self> {
if data.len() < ORDER_SIZE {
return Err(SdkError::InvalidDataLength {
expected: ORDER_SIZE,
actual: data.len(),
});
}
let mut nonce_bytes = [0u8; 4];
nonce_bytes.copy_from_slice(&data[0..4]);
let mut salt_bytes = [0u8; 8];
salt_bytes.copy_from_slice(&data[4..12]);
let mut amount_in_bytes = [0u8; 8];
amount_in_bytes.copy_from_slice(&data[13..21]);
let mut amount_out_bytes = [0u8; 8];
amount_out_bytes.copy_from_slice(&data[21..29]);
let mut expiration_bytes = [0u8; 8];
expiration_bytes.copy_from_slice(&data[29..37]);
Ok(Self {
nonce: u32::from_le_bytes(nonce_bytes),
salt: u64::from_le_bytes(salt_bytes),
side: OrderSide::try_from(data[12])?,
amount_in: u64::from_le_bytes(amount_in_bytes),
amount_out: u64::from_le_bytes(amount_out_bytes),
expiration: i64::from_le_bytes(expiration_bytes),
})
}
pub fn to_signed(
&self,
maker: Pubkey,
market: Pubkey,
base_mint: Pubkey,
quote_mint: Pubkey,
signature: [u8; 64],
) -> OrderPayload {
OrderPayload {
nonce: self.nonce as u64,
salt: self.salt,
maker,
market,
base_mint,
quote_mint,
side: self.side,
amount_in: self.amount_in,
amount_out: self.amount_out,
expiration: self.expiration,
signature,
}
}
}
pub fn is_order_expired(order: &OrderPayload, current_time: i64) -> bool {
order.expiration != 0 && current_time >= order.expiration
}
pub fn orders_can_cross(buy_order: &OrderPayload, sell_order: &OrderPayload) -> bool {
if buy_order.side != OrderSide::Bid || sell_order.side != OrderSide::Ask {
return false;
}
if buy_order.amount_in == 0
|| buy_order.amount_out == 0
|| sell_order.amount_in == 0
|| sell_order.amount_out == 0
{
return false;
}
let buyer_cross = (buy_order.amount_in as u128) * (sell_order.amount_in as u128);
let seller_cross = (buy_order.amount_out as u128) * (sell_order.amount_out as u128);
buyer_cross >= seller_cross
}
pub fn calculate_taker_fill(maker_order: &OrderPayload, maker_fill_amount: u64) -> SdkResult<u64> {
if maker_order.amount_in == 0 {
return Err(SdkError::Overflow);
}
let result = (maker_fill_amount as u128)
.checked_mul(maker_order.amount_out as u128)
.ok_or(SdkError::Overflow)?
.checked_div(maker_order.amount_in as u128)
.ok_or(SdkError::Overflow)?;
if result > u64::MAX as u128 {
return Err(SdkError::Overflow);
}
Ok(result as u64)
}
pub fn derive_condition_id(oracle: &Pubkey, question_id: &[u8; 32], num_outcomes: u8) -> [u8; 32] {
let mut hasher = Keccak256::new();
hasher.update(oracle.as_ref());
hasher.update(question_id);
hasher.update([num_outcomes]);
hasher.finalize().into()
}
pub fn cancel_order_message(order_hash: &str) -> Vec<u8> {
order_hash.as_bytes().to_vec()
}
pub fn cancel_trigger_order_message(trigger_order_id: &str) -> Vec<u8> {
trigger_order_id.as_bytes().to_vec()
}
pub fn cancel_all_message(
user_pubkey: &str,
orderbook_id: &str,
timestamp: i64,
salt: &str,
) -> String {
format!(
"cancel_all:{}:{}:{}:{}",
user_pubkey, orderbook_id, timestamp, salt
)
}
pub fn generate_salt() -> u64 {
rand::random::<u64>()
}
pub fn generate_cancel_all_salt() -> String {
let mut bytes = rand::random::<[u8; 16]>();
bytes[6] = (bytes[6] & 0x0f) | 0x40;
bytes[8] = (bytes[8] & 0x3f) | 0x80;
format!(
"{}-{}-{}-{}-{}",
hex::encode(&bytes[0..4]),
hex::encode(&bytes[4..6]),
hex::encode(&bytes[6..8]),
hex::encode(&bytes[8..10]),
hex::encode(&bytes[10..16]),
)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_order_payload_serialization_roundtrip() {
let order = OrderPayload {
nonce: 12345,
salt: 0,
maker: Pubkey::new_unique(),
market: Pubkey::new_unique(),
base_mint: Pubkey::new_unique(),
quote_mint: Pubkey::new_unique(),
side: OrderSide::Bid,
amount_in: 1000000,
amount_out: 500000,
expiration: 1234567890,
signature: [0u8; 64],
};
let serialized = order.serialize();
let deserialized = OrderPayload::deserialize(&serialized).unwrap();
assert_eq!(order.nonce, deserialized.nonce);
assert_eq!(order.salt, deserialized.salt);
assert_eq!(order.maker, deserialized.maker);
assert_eq!(order.market, deserialized.market);
assert_eq!(order.base_mint, deserialized.base_mint);
assert_eq!(order.quote_mint, deserialized.quote_mint);
assert_eq!(order.side, deserialized.side);
assert_eq!(order.amount_in, deserialized.amount_in);
assert_eq!(order.amount_out, deserialized.amount_out);
assert_eq!(order.expiration, deserialized.expiration);
}
#[test]
fn test_order_serialization_roundtrip() {
let order = Order {
nonce: 12345,
salt: 0,
side: OrderSide::Ask,
amount_in: 1000000,
amount_out: 500000,
expiration: 1234567890,
};
let serialized = order.serialize();
let deserialized = Order::deserialize(&serialized).unwrap();
assert_eq!(order.nonce, deserialized.nonce);
assert_eq!(order.salt, deserialized.salt);
assert_eq!(order.side, deserialized.side);
assert_eq!(order.amount_in, deserialized.amount_in);
assert_eq!(order.amount_out, deserialized.amount_out);
assert_eq!(order.expiration, deserialized.expiration);
}
#[test]
fn test_order_size() {
assert_eq!(ORDER_SIZE, 37);
let order = Order {
nonce: 1,
salt: 0,
side: OrderSide::Bid,
amount_in: 100,
amount_out: 50,
expiration: 0,
};
assert_eq!(order.serialize().len(), 37);
}
#[test]
fn test_order_hash_consistency() {
let order = OrderPayload {
nonce: 1,
salt: 0,
maker: Pubkey::new_from_array([1u8; 32]),
market: Pubkey::new_from_array([2u8; 32]),
base_mint: Pubkey::new_from_array([3u8; 32]),
quote_mint: Pubkey::new_from_array([4u8; 32]),
side: OrderSide::Bid,
amount_in: 100,
amount_out: 50,
expiration: 0,
signature: [0u8; 64],
};
let hash1 = order.hash();
let hash2 = order.hash();
assert_eq!(hash1, hash2);
}
#[test]
fn test_signed_order_to_order_roundtrip() {
let signed = OrderPayload {
nonce: 42,
salt: 0,
maker: Pubkey::new_unique(),
market: Pubkey::new_unique(),
base_mint: Pubkey::new_unique(),
quote_mint: Pubkey::new_unique(),
side: OrderSide::Bid,
amount_in: 1000,
amount_out: 500,
expiration: 12345,
signature: [7u8; 64],
};
let order = signed.to_order();
assert_eq!(order.nonce, 42);
assert_eq!(order.side, OrderSide::Bid);
assert_eq!(order.amount_in, 1000);
assert_eq!(order.amount_out, 500);
assert_eq!(order.expiration, 12345);
let back = order.to_signed(
signed.maker,
signed.market,
signed.base_mint,
signed.quote_mint,
signed.signature,
);
assert_eq!(back.nonce, 42);
assert_eq!(back.maker, signed.maker);
assert_eq!(back.amount_in, 1000);
}
#[test]
fn test_orders_can_cross() {
let buy_order = OrderPayload {
nonce: 1,
salt: 0,
maker: Pubkey::new_unique(),
market: Pubkey::new_unique(),
base_mint: Pubkey::new_unique(),
quote_mint: Pubkey::new_unique(),
side: OrderSide::Bid,
amount_in: 100, amount_out: 50, expiration: 0,
signature: [0u8; 64],
};
let sell_order = OrderPayload {
nonce: 2,
salt: 0,
maker: Pubkey::new_unique(),
market: buy_order.market,
base_mint: buy_order.base_mint,
quote_mint: buy_order.quote_mint,
side: OrderSide::Ask,
amount_in: 50, amount_out: 90, expiration: 0,
signature: [0u8; 64],
};
assert!(orders_can_cross(&buy_order, &sell_order));
}
#[test]
fn test_orders_cannot_cross() {
let buy_order = OrderPayload {
nonce: 1,
salt: 0,
maker: Pubkey::new_unique(),
market: Pubkey::new_unique(),
base_mint: Pubkey::new_unique(),
quote_mint: Pubkey::new_unique(),
side: OrderSide::Bid,
amount_in: 50, amount_out: 50, expiration: 0,
signature: [0u8; 64],
};
let sell_order = OrderPayload {
nonce: 2,
salt: 0,
maker: Pubkey::new_unique(),
market: buy_order.market,
base_mint: buy_order.base_mint,
quote_mint: buy_order.quote_mint,
side: OrderSide::Ask,
amount_in: 50, amount_out: 100, expiration: 0,
signature: [0u8; 64],
};
assert!(!orders_can_cross(&buy_order, &sell_order));
}
#[test]
fn test_calculate_taker_fill() {
let maker_order = OrderPayload {
nonce: 1,
salt: 0,
maker: Pubkey::new_unique(),
market: Pubkey::new_unique(),
base_mint: Pubkey::new_unique(),
quote_mint: Pubkey::new_unique(),
side: OrderSide::Ask,
amount_in: 100, amount_out: 200, expiration: 0,
signature: [0u8; 64],
};
let taker_fill = calculate_taker_fill(&maker_order, 50).unwrap();
assert_eq!(taker_fill, 100);
}
#[test]
#[cfg(feature = "native-auth")]
fn test_to_submit_request() {
use solana_keypair::Keypair;
use solana_signer::Signer;
let keypair = Keypair::new();
let maker = keypair.pubkey();
let market = Pubkey::new_unique();
let base_mint = Pubkey::new_unique();
let quote_mint = Pubkey::new_unique();
let mut order = OrderPayload {
nonce: 42,
salt: 0,
maker,
market,
base_mint,
quote_mint,
side: OrderSide::Bid,
amount_in: 1_000_000,
amount_out: 500_000,
expiration: 1234567890,
signature: [0u8; 64],
};
order.sign(&keypair);
let request = order
.to_submit_request("test_orderbook", None, None, None, None)
.unwrap();
assert_eq!(request.maker, maker.to_string());
assert_eq!(request.nonce, 42);
assert_eq!(request.market_pubkey, market.to_string());
assert_eq!(request.base_token, base_mint.to_string());
assert_eq!(request.quote_token, quote_mint.to_string());
assert_eq!(request.side, 0); assert_eq!(request.amount_in, 1_000_000);
assert_eq!(request.amount_out, 500_000);
assert_eq!(request.expiration, 1234567890);
assert_eq!(request.orderbook_id, "test_orderbook");
assert_eq!(request.signature.len(), 128); }
#[test]
fn test_derive_orderbook_id() {
let order = OrderPayload {
nonce: 1,
salt: 0,
maker: Pubkey::new_from_array([1u8; 32]),
market: Pubkey::new_from_array([2u8; 32]),
base_mint: Pubkey::new_from_array([3u8; 32]),
quote_mint: Pubkey::new_from_array([4u8; 32]),
side: OrderSide::Bid,
amount_in: 100,
amount_out: 50,
expiration: 0,
signature: [0u8; 64],
};
let orderbook_id = order.derive_orderbook_id();
let base_str = order.base_mint.to_string();
let quote_str = order.quote_mint.to_string();
let expected = format!("{}_{}", &base_str[..8], "e_str[..8]);
assert_eq!(orderbook_id, expected);
}
#[test]
#[cfg(feature = "native-auth")]
fn test_is_signed() {
use solana_keypair::Keypair;
use solana_signer::Signer;
let keypair = Keypair::new();
let mut order = OrderPayload {
nonce: 1,
salt: 0,
maker: keypair.pubkey(),
market: Pubkey::new_unique(),
base_mint: Pubkey::new_unique(),
quote_mint: Pubkey::new_unique(),
side: OrderSide::Bid,
amount_in: 100,
amount_out: 50,
expiration: 0,
signature: [0u8; 64],
};
assert!(!order.is_signed());
order.sign(&keypair);
assert!(order.is_signed());
}
#[test]
#[cfg(feature = "native-auth")]
fn test_signature_and_hash_hex() {
use solana_keypair::Keypair;
use solana_signer::Signer;
let keypair = Keypair::new();
let mut order = OrderPayload {
nonce: 1,
salt: 0,
maker: keypair.pubkey(),
market: Pubkey::new_unique(),
base_mint: Pubkey::new_unique(),
quote_mint: Pubkey::new_unique(),
side: OrderSide::Bid,
amount_in: 100,
amount_out: 50,
expiration: 0,
signature: [0u8; 64],
};
order.sign(&keypair);
let sig_hex = order.signature_hex();
let hash_hex = order.hash_hex();
assert_eq!(sig_hex.len(), 128);
assert_eq!(hash_hex.len(), 64);
assert!(hex::decode(&sig_hex).is_ok());
assert!(hex::decode(&hash_hex).is_ok());
}
#[test]
fn test_to_submit_request_errors_unsigned() {
let order = OrderPayload {
nonce: 1,
salt: 0,
maker: Pubkey::new_unique(),
market: Pubkey::new_unique(),
base_mint: Pubkey::new_unique(),
quote_mint: Pubkey::new_unique(),
side: OrderSide::Bid,
amount_in: 100,
amount_out: 50,
expiration: 0,
signature: [0u8; 64],
};
let result = order.to_submit_request("test_orderbook", None, None, None, None);
assert!(result.is_err());
assert!(result.unwrap_err().to_string().contains("must be signed"),);
}
#[test]
fn test_cancel_trigger_order_message() {
let id = "trigger-order-uuid-123";
let message = cancel_trigger_order_message(id);
assert_eq!(message, id.as_bytes());
}
#[test]
fn test_cancel_order_message() {
let hash = "abcdef1234567890abcdef1234567890abcdef1234567890abcdef1234567890";
let message = cancel_order_message(hash);
assert_eq!(message, hash.as_bytes());
}
#[test]
fn test_cancel_all_message() {
let pubkey = "SomePubkey123";
let orderbook_id = "test_orderbook";
let timestamp = 1700000000i64;
let salt = "550e8400-e29b-41d4-a716-446655440000";
let message = cancel_all_message(pubkey, orderbook_id, timestamp, salt);
assert_eq!(
message,
"cancel_all:SomePubkey123:test_orderbook:1700000000:550e8400-e29b-41d4-a716-446655440000"
);
}
#[test]
fn test_generate_cancel_all_salt() {
let salt = generate_cancel_all_salt();
assert_eq!(salt.len(), 36);
assert_eq!(salt.chars().filter(|c| *c == '-').count(), 4);
assert_eq!(salt.chars().nth(14), Some('4'));
}
#[test]
#[cfg(feature = "native-auth")]
fn test_cancel_body_signed() {
use crate::domain::order::CancelBody;
use solana_keypair::Keypair;
use solana_signer::Signer;
let keypair = Keypair::new();
let order_hash = "abcdef1234567890abcdef1234567890abcdef1234567890abcdef1234567890";
let maker = crate::shared::PubkeyStr::from_pubkey(keypair.pubkey());
let body = CancelBody::signed(order_hash.to_string(), maker, &keypair);
assert_eq!(body.signature.len(), 128);
assert_eq!(body.order_hash, order_hash);
let sig_bytes = hex::decode(&body.signature).unwrap();
let sig = Signature::try_from(sig_bytes.as_slice()).unwrap();
assert!(sig.verify(keypair.pubkey().as_ref(), order_hash.as_bytes()));
}
#[test]
#[cfg(feature = "native-auth")]
fn test_cancel_all_body_signed() {
use crate::domain::order::CancelAllBody;
use solana_keypair::Keypair;
use solana_signer::Signer;
let keypair = Keypair::new();
let pubkey_str = crate::shared::PubkeyStr::from_pubkey(keypair.pubkey());
let orderbook_id = crate::shared::OrderBookId::from("");
let timestamp = 1700000000i64;
let salt = "550e8400-e29b-41d4-a716-446655440000".to_string();
let body = CancelAllBody::signed(
pubkey_str.clone(),
orderbook_id.clone(),
timestamp,
salt.clone(),
&keypair,
);
assert_eq!(body.signature.len(), 128);
assert_eq!(body.salt, salt);
let message =
cancel_all_message(pubkey_str.as_str(), orderbook_id.as_str(), timestamp, &salt);
let sig_bytes = hex::decode(&body.signature).unwrap();
let sig = Signature::try_from(sig_bytes.as_slice()).unwrap();
assert!(sig.verify(keypair.pubkey().as_ref(), message.as_bytes()));
}
}