use serde::{Deserialize, Deserializer, de};
use serde_json::value::RawValue;
use crate::client::{ApiError, Transport, info, info_request};
use crate::earn::lending::Operation;
use crate::utils::{Address, Dec, Json};
#[must_use]
pub fn ledger_request(user: Address, start_time: u64, end_time: Option<u64>) -> String {
let mut fields = vec![("user", Json::String(user.to_hex())), ("startTime", Json::Number(start_time.to_string()))];
if let Some(end_time) = end_time {
fields.push(("endTime", Json::Number(end_time.to_string())));
}
info_request("userNonFundingLedgerUpdates", &fields)
}
pub async fn ledger(
transport: &impl Transport,
user: Address,
start_time: u64,
end_time: Option<u64>,
) -> Result<Vec<LedgerUpdate>, ApiError> {
info(transport, ledger_request(user, start_time, end_time)).await
}
#[must_use]
pub fn find(rows: &[LedgerUpdate], nonce: u64) -> Option<&LedgerUpdate> {
rows.iter().find(|row| {
matches!(row.delta, Delta::Send { nonce: Some(said), .. } | Delta::SpotTransfer { nonce: Some(said), .. } if said == nonce)
})
}
#[derive(Debug, Clone, PartialEq, Eq, Deserialize)]
pub struct LedgerUpdate {
pub time: u64,
pub hash: String,
#[serde(deserialize_with = "delta")]
pub delta: Delta,
}
impl LedgerUpdate {
#[must_use]
pub fn kind(&self) -> &str {
self.delta.kind()
}
}
#[derive(Debug, Clone, PartialEq, Eq, Deserialize)]
#[serde(tag = "type", rename_all = "camelCase")]
pub enum Delta {
#[serde(rename_all = "camelCase")]
Send {
user: Address,
destination: Address,
source_dex: String,
destination_dex: String,
token: String,
amount: Dec,
usdc_value: Dec,
fee: Dec,
#[serde(default)]
native_token_fee: Option<Dec>,
#[serde(default)]
nonce: Option<u64>,
fee_token: String,
},
#[serde(rename_all = "camelCase")]
InternalTransfer {
usdc: Dec,
user: Address,
destination: Address,
fee: Dec,
},
#[serde(rename_all = "camelCase")]
SpotTransfer {
token: String,
amount: Dec,
usdc_value: Dec,
user: Address,
destination: Address,
fee: Dec,
#[serde(default)]
native_token_fee: Option<Dec>,
#[serde(default)]
nonce: Option<u64>,
fee_token: String,
},
Deposit {
usdc: Dec,
},
#[serde(rename_all = "camelCase")]
Withdraw {
usdc: Dec,
nonce: u64,
fee: Dec,
},
#[serde(rename_all = "camelCase")]
AccountClassTransfer {
usdc: Dec,
to_perp: bool,
},
AccountActivationGas {
amount: Dec,
token: String,
},
VaultDeposit {
vault: Address,
usdc: Dec,
},
#[serde(rename_all = "camelCase")]
VaultWithdraw {
vault: Address,
user: Address,
requested_usd: Dec,
commission: Dec,
closing_cost: Dec,
basis: Dec,
net_withdrawn_usd: Dec,
},
#[serde(rename_all = "camelCase")]
CStakingTransfer {
token: String,
amount: Dec,
is_deposit: bool,
},
SpotGenesis {
token: String,
amount: Dec,
},
DeployGasAuction {
token: String,
amount: Dec,
},
RewardsClaim {
amount: Dec,
token: String,
},
SubAccountTransfer {
usdc: Dec,
user: Address,
destination: Address,
},
VaultCreate {
vault: Address,
usdc: Dec,
fee: Dec,
},
#[serde(rename_all = "camelCase")]
BorrowLend {
token: String,
operation: Operation,
amount: Dec,
interest_amount: Dec,
},
#[serde(rename_all = "camelCase")]
Liquidation {
liquidated_ntl_pos: Dec,
account_value: Dec,
leverage_type: String,
liquidated_positions: Vec<LiquidatedPosition>,
},
#[serde(skip_deserializing)]
Other {
kind: String,
fields: Json,
},
}
#[derive(Debug, Clone, PartialEq, Eq, Deserialize)]
pub struct LiquidatedPosition {
pub coin: String,
pub szi: Dec,
}
impl Delta {
#[must_use]
pub fn kind(&self) -> &str {
match self {
Delta::Send { .. } => "send",
Delta::InternalTransfer { .. } => "internalTransfer",
Delta::SpotTransfer { .. } => "spotTransfer",
Delta::Deposit { .. } => "deposit",
Delta::Withdraw { .. } => "withdraw",
Delta::AccountClassTransfer { .. } => "accountClassTransfer",
Delta::AccountActivationGas { .. } => "accountActivationGas",
Delta::VaultDeposit { .. } => "vaultDeposit",
Delta::VaultWithdraw { .. } => "vaultWithdraw",
Delta::CStakingTransfer { .. } => "cStakingTransfer",
Delta::SpotGenesis { .. } => "spotGenesis",
Delta::DeployGasAuction { .. } => "deployGasAuction",
Delta::RewardsClaim { .. } => "rewardsClaim",
Delta::SubAccountTransfer { .. } => "subAccountTransfer",
Delta::VaultCreate { .. } => "vaultCreate",
Delta::BorrowLend { .. } => "borrowLend",
Delta::Liquidation { .. } => "liquidation",
Delta::Other { kind, .. } => kind,
}
}
#[must_use]
pub const fn nonce(&self) -> Option<u64> {
match self {
Delta::Send { nonce, .. } | Delta::SpotTransfer { nonce, .. } => *nonce,
Delta::Withdraw { nonce, .. } => Some(*nonce),
_ => None,
}
}
}
fn delta<'de, D: Deserializer<'de>>(deserializer: D) -> Result<Delta, D::Error> {
let raw = Box::<RawValue>::deserialize(deserializer)?;
if let Ok(delta) = serde_json::from_str::<Delta>(raw.get()) {
return Ok(delta);
}
let fields = Json::parse(raw.get()).map_err(de::Error::custom)?;
let kind = fields.get("type").and_then(Json::as_str).unwrap_or_default().to_owned();
Ok(Delta::Other { kind, fields })
}
#[cfg(test)]
#[path = "tests/ledger.rs"]
mod tests;