use rust_decimal::Decimal;
use solana_pubkey::Pubkey;
#[cfg(feature = "native-auth")]
use solana_keypair::Keypair;
use crate::domain::orderbook::OrderBookPair;
use crate::program::error::SdkError;
use crate::program::orders::{generate_salt, OrderPayload};
use crate::program::types::OrderSide;
use crate::shared::scaling::{align_price_to_tick, scale_price_size, ScalingError};
use crate::shared::{DepositSource, SubmitOrderRequest, TimeInForce, TriggerType};
#[derive(Debug, Clone, Default)]
struct OrderFields {
nonce: Option<u64>,
salt: Option<u64>,
maker: Option<Pubkey>,
market: Option<Pubkey>,
base_mint: Option<Pubkey>,
quote_mint: Option<Pubkey>,
side: Option<OrderSide>,
amount_in: Option<u64>,
amount_out: Option<u64>,
expiration: i64,
price_raw: Option<String>,
size_raw: Option<String>,
deposit_source: Option<DepositSource>,
}
impl OrderFields {
fn to_payload(&self) -> Result<OrderPayload, SdkError> {
let amount_in = self
.amount_in
.ok_or_else(|| SdkError::MissingField("amount_in".into()))?;
let amount_out = self
.amount_out
.ok_or_else(|| SdkError::MissingField("amount_out".into()))?;
if amount_in == 0 {
return Err(SdkError::MissingField(
"amount_in must be greater than 0".into(),
));
}
if amount_out == 0 {
return Err(SdkError::MissingField(
"amount_out must be greater than 0".into(),
));
}
Ok(OrderPayload {
nonce: self.nonce.unwrap_or(0),
salt: self.salt.unwrap_or_else(generate_salt),
maker: self
.maker
.ok_or_else(|| SdkError::MissingField("maker".into()))?,
market: self
.market
.ok_or_else(|| SdkError::MissingField("market".into()))?,
base_mint: self
.base_mint
.ok_or_else(|| SdkError::MissingField("base_mint".into()))?,
quote_mint: self
.quote_mint
.ok_or_else(|| SdkError::MissingField("quote_mint".into()))?,
side: self
.side
.ok_or_else(|| SdkError::MissingField("side (call .bid() or .ask())".into()))?,
amount_in,
amount_out,
expiration: self.expiration,
signature: [0u8; 64],
})
}
fn auto_fill_from_orderbook(&mut self, orderbook: &OrderBookPair) -> Result<(), SdkError> {
use crate::domain::market::tokens::Token;
if self.market.is_none() {
self.market = Some(orderbook.market_pubkey.to_pubkey().map_err(|error| {
SdkError::MissingField(format!("invalid market pubkey: {error}"))
})?);
}
if self.salt.is_none() {
self.salt = Some(generate_salt())
}
if self.base_mint.is_none() {
self.base_mint = Some(orderbook.base.pubkey().to_pubkey().map_err(|error| {
SdkError::MissingField(format!("invalid base mint pubkey: {error}"))
})?);
}
if self.quote_mint.is_none() {
self.quote_mint = Some(orderbook.quote.pubkey().to_pubkey().map_err(|error| {
SdkError::MissingField(format!("invalid quote mint pubkey: {error}"))
})?);
}
Ok(())
}
fn auto_scale(&mut self, orderbook: &OrderBookPair) -> Result<(), SdkError> {
if self.amount_in.is_some() || self.amount_out.is_some() {
return Ok(());
}
let price_str = self.price_raw.as_deref().ok_or_else(|| {
SdkError::MissingField(
"either price()+size() or amount_in()+amount_out() is required".into(),
)
})?;
let size_str = self.size_raw.as_deref().ok_or_else(|| {
SdkError::MissingField(
"either price()+size() or amount_in()+amount_out() is required".into(),
)
})?;
let price: Decimal =
price_str
.parse()
.map_err(|e: rust_decimal::Error| ScalingError::InvalidDecimal {
input: price_str.to_string(),
reason: e.to_string(),
})?;
let size: Decimal =
size_str
.parse()
.map_err(|e: rust_decimal::Error| ScalingError::InvalidDecimal {
input: size_str.to_string(),
reason: e.to_string(),
})?;
let side = self
.side
.ok_or_else(|| SdkError::MissingField("side (call .bid() or .ask())".into()))?;
let decimals = orderbook.decimals();
let aligned_price = align_price_to_tick(price, &decimals);
let scaled = scale_price_size(aligned_price, size, side, &decimals)?;
self.amount_in = Some(scaled.amount_in);
self.amount_out = Some(scaled.amount_out);
Ok(())
}
}
pub trait OrderEnvelope: Sized {
fn new() -> Self;
fn nonce(self, nonce: u64) -> Self;
fn salt(self, salt: u64) -> Self;
fn maker(self, maker: Pubkey) -> Self;
fn market(self, market: Pubkey) -> Self;
fn base_mint(self, base_mint: Pubkey) -> Self;
fn quote_mint(self, quote_mint: Pubkey) -> Self;
fn bid(self) -> Self;
fn ask(self) -> Self;
fn side(self, side: OrderSide) -> Self;
fn amount_in(self, amount: u64) -> Self;
fn amount_out(self, amount: u64) -> Self;
fn expiration(self, expiration: i64) -> Self;
fn price(self, price: &str) -> Self;
fn size(self, size: &str) -> Self;
fn deposit_source(self, ds: DepositSource) -> Self;
fn payload(&self) -> Result<OrderPayload, SdkError>;
#[cfg(feature = "native-auth")]
fn sign(
self,
keypair: &Keypair,
orderbook: &OrderBookPair,
) -> Result<SubmitOrderRequest, SdkError>;
fn finalize(
self,
sig_bs58: &str,
orderbook: &OrderBookPair,
) -> Result<SubmitOrderRequest, SdkError>;
}
macro_rules! impl_base_methods {
($ty:ident) => {
fn new() -> Self {
Self::default()
}
fn nonce(mut self, nonce: u64) -> Self {
self.fields.nonce = Some(nonce);
self
}
fn salt(mut self, salt: u64) -> Self {
self.fields.salt = Some(salt);
self
}
fn maker(mut self, maker: Pubkey) -> Self {
self.fields.maker = Some(maker);
self
}
fn market(mut self, market: Pubkey) -> Self {
self.fields.market = Some(market);
self
}
fn base_mint(mut self, base_mint: Pubkey) -> Self {
self.fields.base_mint = Some(base_mint);
self
}
fn quote_mint(mut self, quote_mint: Pubkey) -> Self {
self.fields.quote_mint = Some(quote_mint);
self
}
fn bid(mut self) -> Self {
self.fields.side = Some(OrderSide::Bid);
self
}
fn ask(mut self) -> Self {
self.fields.side = Some(OrderSide::Ask);
self
}
fn side(mut self, side: OrderSide) -> Self {
self.fields.side = Some(side);
self
}
fn amount_in(mut self, amount: u64) -> Self {
self.fields.amount_in = Some(amount);
self
}
fn amount_out(mut self, amount: u64) -> Self {
self.fields.amount_out = Some(amount);
self
}
fn expiration(mut self, expiration: i64) -> Self {
self.fields.expiration = expiration;
self
}
fn price(mut self, price: &str) -> Self {
self.fields.price_raw = Some(price.to_string());
self
}
fn size(mut self, size: &str) -> Self {
self.fields.size_raw = Some(size.to_string());
self
}
fn deposit_source(mut self, ds: DepositSource) -> Self {
self.fields.deposit_source = Some(ds);
self
}
fn payload(&self) -> Result<OrderPayload, SdkError> {
self.fields.to_payload()
}
};
}
#[derive(Debug, Clone, Default)]
pub struct LimitOrderEnvelope {
fields: OrderFields,
time_in_force: Option<TimeInForce>,
}
impl OrderEnvelope for LimitOrderEnvelope {
impl_base_methods!(LimitOrderEnvelope);
#[cfg(feature = "native-auth")]
fn sign(
mut self,
keypair: &Keypair,
orderbook: &OrderBookPair,
) -> Result<SubmitOrderRequest, SdkError> {
self.fields.auto_fill_from_orderbook(orderbook)?;
self.fields.auto_scale(orderbook)?;
let mut payload = self.fields.to_payload()?;
payload.sign(keypair);
payload.to_submit_request(
orderbook.orderbook_id.as_str(),
self.time_in_force,
None,
None,
self.fields.deposit_source,
)
}
fn finalize(
mut self,
sig_bs58: &str,
orderbook: &OrderBookPair,
) -> Result<SubmitOrderRequest, SdkError> {
self.fields.auto_fill_from_orderbook(orderbook)?;
self.fields.auto_scale(orderbook)?;
let mut payload = self.fields.to_payload()?;
payload.apply_signature(sig_bs58.to_string())?;
payload.to_submit_request(
orderbook.orderbook_id.as_str(),
self.time_in_force,
None,
None,
self.fields.deposit_source,
)
}
}
impl LimitOrderEnvelope {
pub fn time_in_force(mut self, tif: TimeInForce) -> Self {
self.time_in_force = Some(tif);
self
}
}
#[derive(Debug, Clone, Default)]
pub struct TriggerOrderEnvelope {
fields: OrderFields,
time_in_force: Option<TimeInForce>,
trigger_price: Option<f64>,
trigger_type: Option<TriggerType>,
}
impl OrderEnvelope for TriggerOrderEnvelope {
impl_base_methods!(TriggerOrderEnvelope);
#[cfg(feature = "native-auth")]
fn sign(
mut self,
keypair: &Keypair,
orderbook: &OrderBookPair,
) -> Result<SubmitOrderRequest, SdkError> {
let trigger_price = self.trigger_price.ok_or_else(|| {
SdkError::MissingField("trigger_price is required for trigger orders".into())
})?;
let trigger_type = self.trigger_type.ok_or_else(|| {
SdkError::MissingField("trigger_type is required for trigger orders".into())
})?;
self.fields.auto_fill_from_orderbook(orderbook)?;
self.fields.auto_scale(orderbook)?;
let mut payload = self.fields.to_payload()?;
payload.sign(keypair);
payload.to_submit_request(
orderbook.orderbook_id.as_str(),
self.time_in_force,
Some(trigger_price),
Some(trigger_type),
self.fields.deposit_source,
)
}
fn finalize(
mut self,
sig_bs58: &str,
orderbook: &OrderBookPair,
) -> Result<SubmitOrderRequest, SdkError> {
let trigger_price = self.trigger_price.ok_or_else(|| {
SdkError::MissingField("trigger_price is required for trigger orders".into())
})?;
let trigger_type = self.trigger_type.ok_or_else(|| {
SdkError::MissingField("trigger_type is required for trigger orders".into())
})?;
self.fields.auto_fill_from_orderbook(orderbook)?;
self.fields.auto_scale(orderbook)?;
let mut payload = self.fields.to_payload()?;
payload.apply_signature(sig_bs58.to_string())?;
payload.to_submit_request(
orderbook.orderbook_id.as_str(),
self.time_in_force,
Some(trigger_price),
Some(trigger_type),
self.fields.deposit_source,
)
}
}
impl TriggerOrderEnvelope {
pub fn time_in_force(mut self, tif: TimeInForce) -> Self {
self.time_in_force = Some(tif);
self
}
pub fn trigger_price(mut self, price: f64) -> Self {
self.trigger_price = Some(price);
self
}
pub fn trigger_type(mut self, trigger_type: TriggerType) -> Self {
self.trigger_type = Some(trigger_type);
self
}
pub fn gtc(self) -> Self {
self.time_in_force(TimeInForce::Gtc)
}
pub fn ioc(self) -> Self {
self.time_in_force(TimeInForce::Ioc)
}
pub fn fok(self) -> Self {
self.time_in_force(TimeInForce::Fok)
}
pub fn alo(self) -> Self {
self.time_in_force(TimeInForce::Alo)
}
pub fn take_profit(self, price: f64) -> Self {
self.trigger_price(price)
.trigger_type(TriggerType::TakeProfit)
}
pub fn stop_loss(self, price: f64) -> Self {
self.trigger_price(price)
.trigger_type(TriggerType::StopLoss)
}
}
#[cfg(feature = "http")]
impl LimitOrderEnvelope {
pub async fn submit(
mut self,
client: &crate::client::LightconeClient,
orderbook: &OrderBookPair,
) -> Result<crate::domain::order::SubmitOrderResponse, crate::error::SdkError> {
use crate::shared::signing::SigningStrategy;
self.fields.auto_fill_from_orderbook(orderbook)?;
self.fields.auto_scale(orderbook)?;
match self.fields.nonce {
Some(nonce) => {
client.set_order_nonce(nonce).await;
}
None => {
self.fields.nonce = Some(client.order_nonce().await.unwrap_or(0));
}
}
let strategy = client.signing_strategy().await.ok_or_else(|| {
crate::error::SdkError::Validation("signing strategy is not set on the client".into())
})?;
match strategy {
#[cfg(feature = "native-auth")]
SigningStrategy::Native(keypair) => {
let request = self.sign(&keypair, orderbook)?;
client.orders().submit(&request).await
}
SigningStrategy::WalletAdapter(signer) => {
let hash = self.payload()?.hash_hex();
let sig_bytes = signer
.sign_message(hash.as_bytes())
.await
.map_err(crate::shared::signing::classify_signer_error)?;
let sig_bs58 = bs58::encode(&sig_bytes).into_string();
let request = self.finalize(&sig_bs58, orderbook)?;
client.orders().submit(&request).await
}
SigningStrategy::Privy { wallet_id } => {
let envelope = crate::privy::PrivyOrderEnvelope::from_limit(
&self,
orderbook.orderbook_id.as_str(),
)?;
let result = client
.privy()
.sign_and_send_order(&wallet_id, envelope)
.await?;
serde_json::from_value(result).map_err(|error| {
crate::error::SdkError::Other(format!(
"failed to parse submit order response: {error}"
))
})
}
}
}
}
#[cfg(feature = "http")]
impl TriggerOrderEnvelope {
pub async fn submit(
mut self,
client: &crate::client::LightconeClient,
orderbook: &OrderBookPair,
) -> Result<crate::domain::order::TriggerOrderResponse, crate::error::SdkError> {
use crate::shared::signing::SigningStrategy;
self.fields.auto_fill_from_orderbook(orderbook)?;
self.fields.auto_scale(orderbook)?;
match self.fields.nonce {
Some(nonce) => {
client.set_order_nonce(nonce).await;
}
None => {
self.fields.nonce = Some(client.order_nonce().await.unwrap_or(0));
}
}
let strategy = client.signing_strategy().await.ok_or_else(|| {
crate::error::SdkError::Validation("signing strategy is not set on the client".into())
})?;
match strategy {
#[cfg(feature = "native-auth")]
SigningStrategy::Native(keypair) => {
let request = self.sign(&keypair, orderbook)?;
client.orders().submit_trigger(&request).await
}
SigningStrategy::WalletAdapter(signer) => {
let hash = self.payload()?.hash_hex();
let sig_bytes = signer
.sign_message(hash.as_bytes())
.await
.map_err(crate::shared::signing::classify_signer_error)?;
let sig_bs58 = bs58::encode(&sig_bytes).into_string();
let request = self.finalize(&sig_bs58, orderbook)?;
client.orders().submit_trigger(&request).await
}
SigningStrategy::Privy { wallet_id } => {
let envelope = crate::privy::PrivyOrderEnvelope::from_trigger(
&self,
orderbook.orderbook_id.as_str(),
)?;
let result = client
.privy()
.sign_and_send_order(&wallet_id, envelope)
.await?;
serde_json::from_value(result).map_err(|error| {
crate::error::SdkError::Other(format!(
"failed to parse trigger order response: {error}"
))
})
}
}
}
}
impl LimitOrderEnvelope {
pub fn get_salt(&self) -> Option<u64> {
self.fields.salt
}
pub fn get_maker(&self) -> Option<&Pubkey> {
self.fields.maker.as_ref()
}
pub fn get_market(&self) -> Option<&Pubkey> {
self.fields.market.as_ref()
}
pub fn get_base_mint(&self) -> Option<&Pubkey> {
self.fields.base_mint.as_ref()
}
pub fn get_quote_mint(&self) -> Option<&Pubkey> {
self.fields.quote_mint.as_ref()
}
pub fn get_side(&self) -> Option<OrderSide> {
self.fields.side
}
pub fn get_amount_in(&self) -> Option<u64> {
self.fields.amount_in
}
pub fn get_amount_out(&self) -> Option<u64> {
self.fields.amount_out
}
pub fn get_expiration(&self) -> i64 {
self.fields.expiration
}
pub fn get_nonce(&self) -> Option<u64> {
self.fields.nonce
}
pub fn get_deposit_source(&self) -> Option<DepositSource> {
self.fields.deposit_source
}
}
impl TriggerOrderEnvelope {
pub fn get_salt(&self) -> Option<u64> {
self.fields.salt
}
pub fn get_maker(&self) -> Option<&Pubkey> {
self.fields.maker.as_ref()
}
pub fn get_market(&self) -> Option<&Pubkey> {
self.fields.market.as_ref()
}
pub fn get_base_mint(&self) -> Option<&Pubkey> {
self.fields.base_mint.as_ref()
}
pub fn get_quote_mint(&self) -> Option<&Pubkey> {
self.fields.quote_mint.as_ref()
}
pub fn get_side(&self) -> Option<OrderSide> {
self.fields.side
}
pub fn get_amount_in(&self) -> Option<u64> {
self.fields.amount_in
}
pub fn get_amount_out(&self) -> Option<u64> {
self.fields.amount_out
}
pub fn get_expiration(&self) -> i64 {
self.fields.expiration
}
pub fn get_nonce(&self) -> Option<u64> {
self.fields.nonce
}
pub fn get_deposit_source(&self) -> Option<DepositSource> {
self.fields.deposit_source
}
pub fn get_time_in_force(&self) -> Option<TimeInForce> {
self.time_in_force
}
pub fn get_trigger_price(&self) -> Option<f64> {
self.trigger_price
}
pub fn get_trigger_type(&self) -> Option<TriggerType> {
self.trigger_type
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::domain::orderbook::OrderBookPair;
#[cfg(feature = "native-auth")]
use solana_signer::Signer;
fn test_orderbook() -> OrderBookPair {
OrderBookPair::test_new("test_ob", 6, 6, 0)
}
#[test]
fn test_limit_envelope_payload() {
let maker = Pubkey::new_unique();
let market = Pubkey::new_unique();
let base_mint = Pubkey::new_unique();
let quote_mint = Pubkey::new_unique();
let env = LimitOrderEnvelope::new()
.nonce(1)
.salt(0)
.maker(maker)
.market(market)
.base_mint(base_mint)
.quote_mint(quote_mint)
.bid()
.amount_in(1_000_000)
.amount_out(500_000);
let payload = env.payload().unwrap();
assert_eq!(payload.nonce, 1);
assert_eq!(payload.maker, maker);
assert_eq!(payload.side, OrderSide::Bid);
assert!(!payload.is_signed());
}
#[test]
#[cfg(feature = "native-auth")]
fn test_limit_envelope_sign_raw_amounts() {
let keypair = Keypair::new();
let maker = keypair.pubkey();
let ob = test_orderbook();
let request = LimitOrderEnvelope::new()
.nonce(1)
.salt(0)
.maker(maker)
.market(Pubkey::new_unique())
.base_mint(Pubkey::new_unique())
.quote_mint(Pubkey::new_unique())
.bid()
.amount_in(1_000_000)
.amount_out(500_000)
.sign(&keypair, &ob)
.unwrap();
assert_eq!(request.maker, maker.to_string());
assert_eq!(request.nonce, 1);
assert_eq!(request.side, 0); assert_eq!(request.orderbook_id, "test_ob");
assert_eq!(request.signature.len(), 128);
assert_eq!(request.time_in_force, None);
assert_eq!(request.trigger_price, None);
assert_eq!(request.trigger_type, None);
}
#[test]
#[cfg(feature = "native-auth")]
fn test_limit_envelope_sign_with_auto_scaling() {
let keypair = Keypair::new();
let maker = keypair.pubkey();
let ob = test_orderbook();
let request = LimitOrderEnvelope::new()
.nonce(1)
.salt(0)
.maker(maker)
.market(Pubkey::new_unique())
.base_mint(Pubkey::new_unique())
.quote_mint(Pubkey::new_unique())
.bid()
.price("0.65")
.size("100")
.sign(&keypair, &ob)
.unwrap();
assert_eq!(request.amount_in, 65_000_000);
assert_eq!(request.amount_out, 100_000_000);
assert_eq!(request.signature.len(), 128);
}
#[test]
#[cfg(feature = "native-auth")]
fn test_trigger_envelope_sign() {
let keypair = Keypair::new();
let maker = keypair.pubkey();
let ob = test_orderbook();
let request = TriggerOrderEnvelope::new()
.nonce(1)
.salt(0)
.maker(maker)
.market(Pubkey::new_unique())
.base_mint(Pubkey::new_unique())
.quote_mint(Pubkey::new_unique())
.ask()
.amount_in(500_000)
.amount_out(1_000_000)
.take_profit(0.75)
.gtc()
.sign(&keypair, &ob)
.unwrap();
assert_eq!(request.trigger_price, Some(0.75));
assert_eq!(request.trigger_type, Some(TriggerType::TakeProfit));
assert_eq!(request.time_in_force, Some(TimeInForce::Gtc));
assert_eq!(request.side, 1); assert_eq!(request.signature.len(), 128);
}
#[test]
#[cfg(feature = "native-auth")]
fn test_trigger_envelope_missing_trigger_fields() {
let keypair = Keypair::new();
let ob = test_orderbook();
let result = TriggerOrderEnvelope::new()
.nonce(1)
.salt(0)
.maker(keypair.pubkey())
.market(Pubkey::new_unique())
.base_mint(Pubkey::new_unique())
.quote_mint(Pubkey::new_unique())
.bid()
.amount_in(1_000_000)
.amount_out(500_000)
.sign(&keypair, &ob);
assert!(result.is_err());
assert!(result.unwrap_err().to_string().contains("trigger_price"));
}
#[test]
#[cfg(feature = "native-auth")]
fn test_trigger_envelope_stop_loss() {
use crate::shared::{TimeInForce, TriggerType};
let keypair = Keypair::new();
let ob = test_orderbook();
let request = TriggerOrderEnvelope::new()
.nonce(1)
.salt(0)
.maker(keypair.pubkey())
.market(Pubkey::new_unique())
.base_mint(Pubkey::new_unique())
.quote_mint(Pubkey::new_unique())
.ask()
.amount_in(500_000)
.amount_out(1_000_000)
.stop_loss(0.30)
.ioc()
.sign(&keypair, &ob)
.unwrap();
assert_eq!(request.time_in_force, Some(TimeInForce::Ioc));
assert_eq!(request.trigger_price, Some(0.30));
assert_eq!(request.trigger_type, Some(TriggerType::StopLoss));
}
#[test]
fn test_limit_envelope_zero_amount_in() {
let result = LimitOrderEnvelope::new()
.nonce(1)
.salt(0)
.maker(Pubkey::new_unique())
.market(Pubkey::new_unique())
.base_mint(Pubkey::new_unique())
.quote_mint(Pubkey::new_unique())
.bid()
.amount_in(0)
.amount_out(500_000)
.payload();
assert!(result.is_err());
assert!(
result.unwrap_err().to_string().contains("amount_in"),
"expected error about amount_in"
);
}
#[test]
fn test_limit_envelope_zero_amount_out() {
let result = LimitOrderEnvelope::new()
.nonce(1)
.salt(0)
.maker(Pubkey::new_unique())
.market(Pubkey::new_unique())
.base_mint(Pubkey::new_unique())
.quote_mint(Pubkey::new_unique())
.bid()
.amount_in(1_000_000)
.amount_out(0)
.payload();
assert!(result.is_err());
assert!(
result.unwrap_err().to_string().contains("amount_out"),
"expected error about amount_out"
);
}
#[test]
fn test_limit_envelope_nonce_defaults_to_zero() {
let payload = LimitOrderEnvelope::new()
.maker(Pubkey::new_unique())
.market(Pubkey::new_unique())
.base_mint(Pubkey::new_unique())
.quote_mint(Pubkey::new_unique())
.bid()
.amount_in(1_000_000)
.amount_out(500_000)
.payload()
.unwrap();
assert_eq!(payload.nonce, 0);
}
#[test]
fn test_limit_envelope_missing_side() {
let result = LimitOrderEnvelope::new()
.nonce(1)
.salt(0)
.maker(Pubkey::new_unique())
.market(Pubkey::new_unique())
.base_mint(Pubkey::new_unique())
.quote_mint(Pubkey::new_unique())
.amount_in(1_000_000)
.amount_out(500_000)
.payload();
assert!(result.is_err());
assert!(
result.unwrap_err().to_string().contains("side"),
"expected error about side"
);
}
#[test]
#[cfg(feature = "native-auth")]
fn test_limit_envelope_with_deposit_source() {
let keypair = Keypair::new();
let maker = keypair.pubkey();
let ob = test_orderbook();
let request = LimitOrderEnvelope::new()
.nonce(1)
.salt(0)
.maker(maker)
.market(Pubkey::new_unique())
.base_mint(Pubkey::new_unique())
.quote_mint(Pubkey::new_unique())
.bid()
.amount_in(1_000_000)
.amount_out(500_000)
.deposit_source(DepositSource::Global)
.sign(&keypair, &ob)
.unwrap();
assert_eq!(request.deposit_source, Some(DepositSource::Global));
}
#[test]
#[cfg(feature = "native-auth")]
fn test_limit_envelope_deposit_source_none_by_default() {
let keypair = Keypair::new();
let ob = test_orderbook();
let request = LimitOrderEnvelope::new()
.nonce(1)
.salt(0)
.maker(keypair.pubkey())
.market(Pubkey::new_unique())
.base_mint(Pubkey::new_unique())
.quote_mint(Pubkey::new_unique())
.bid()
.amount_in(1_000_000)
.amount_out(500_000)
.sign(&keypair, &ob)
.unwrap();
assert_eq!(request.deposit_source, None);
}
#[test]
fn test_limit_envelope_deposit_source_accessor() {
let env = LimitOrderEnvelope::new().deposit_source(DepositSource::Market);
assert_eq!(env.get_deposit_source(), Some(DepositSource::Market));
}
}