Skip to main content

df_hyperliquid_core/transfer/send/
check.rs

1//! What the venue says about a recipient before a send (`preTransferCheck`): whether it is
2//! sanctioned, whether it exists, and the fee a first transfer to it pays.
3
4use crate::client::{ApiError, Transport, info, info_request};
5use crate::utils::{Address, Dec, Json};
6use serde::Deserialize;
7
8/// `{"type":"preTransferCheck","user":<recipient>,"source":<sender>}`.
9#[must_use]
10pub fn pre_transfer_check_request(recipient: Address, sender: Address) -> String {
11    info_request(
12        "preTransferCheck",
13        &[("user", Json::String(recipient.to_hex())), ("source", Json::String(sender.to_hex()))],
14    )
15}
16
17/// The venue's answer about a recipient. A fresh one costs an activation fee (live: `"1.0"`),
18/// charged to the sender on top of the amount as its own `accountActivationGas` ledger row.
19#[derive(Debug, Clone, PartialEq, Eq, Deserialize)]
20#[serde(rename_all = "camelCase")]
21pub struct PreTransferCheck {
22    /// A send to it is refused.
23    pub is_sanctioned: bool,
24    pub user_exists: bool,
25    pub fee: Dec,
26    pub user_has_sent_tx: bool,
27}
28
29impl PreTransferCheck {
30    /// The fee a first transfer to this address pays, when there is one.
31    #[must_use]
32    pub fn activation_fee(&self) -> Option<Dec> {
33        self.fee.is_positive().then_some(self.fee)
34    }
35}
36
37pub async fn pre_transfer_check(
38    transport: &impl Transport,
39    recipient: Address,
40    sender: Address,
41) -> Result<PreTransferCheck, ApiError> {
42    info(transport, pre_transfer_check_request(recipient, sender)).await
43}
44
45#[cfg(test)]
46mod tests {
47    use std::collections::BTreeMap;
48
49    use super::*;
50
51    /// Both answers in `pre_transfer_check_mainnet.json`, live on mainnet, 2026-10-18.
52    #[test]
53    fn a_fresh_recipient_costs_a_fee_and_a_known_one_does_not() {
54        let recorded: BTreeMap<String, PreTransferCheck> = serde_json::from_str(include_str!(concat!(
55            env!("CARGO_MANIFEST_DIR"),
56            "/fixtures/transfer/pre_transfer_check_mainnet.json"
57        )))
58        .unwrap();
59        let fresh = &recorded["fresh"];
60        assert_eq!(
61            *fresh,
62            PreTransferCheck { is_sanctioned: false, user_exists: false, fee: Dec::ONE, user_has_sent_tx: false }
63        );
64        assert_eq!(fresh.activation_fee(), Some(Dec::ONE));
65        let known = &recorded["known"];
66        assert!(known.user_exists && known.user_has_sent_tx && !known.is_sanctioned);
67        assert_eq!(known.activation_fee(), None);
68    }
69}