Skip to main content

aeko_rust_sdk/
client.rs

1use {
2    crate::{
3        builders::{
4            default_token_721_program_id, default_wallet_permissions_program_id, Aeko721Collection,
5            Aeko721Token, WalletPermissionAccount, WalletPermissionAuditLogAccount,
6        },
7        error::{AekoRustSdkError, AekoRustSdkResult},
8    },
9    base64::{engine::general_purpose::STANDARD as BASE64, Engine},
10    borsh::BorshDeserialize,
11    reqwest::Client,
12    serde::de::DeserializeOwned,
13    serde::Deserialize,
14    serde_json::{json, Value},
15};
16
17#[derive(Clone, Debug, Deserialize)]
18pub struct AccountInfoValue {
19    pub data: Value,
20    pub executable: bool,
21    pub lamports: u64,
22    pub owner: String,
23}
24
25#[derive(Clone, Debug, Deserialize)]
26pub struct ProgramAccount {
27    pub pubkey: String,
28    pub account: AccountInfoValue,
29}
30
31#[derive(Clone, Debug, Deserialize)]
32pub struct SignatureStatus {
33    pub slot: u64,
34    pub confirmations: Option<u64>,
35    pub err: Option<Value>,
36    #[serde(rename = "confirmationStatus")]
37    pub confirmation_status: Option<String>,
38}
39
40pub struct AekoDeveloperClient {
41    http: Client,
42    endpoint: String,
43}
44
45impl AekoDeveloperClient {
46    pub fn new(url: String) -> Self {
47        Self {
48            http: Client::new(),
49            endpoint: url,
50        }
51    }
52
53    pub async fn get_latest_blockhash(&self) -> AekoRustSdkResult<String> {
54        let value: RpcValueResponse<LatestBlockhashValue> =
55            self.rpc("getLatestBlockhash", json!([])).await?;
56        Ok(value.value.blockhash)
57    }
58
59    pub async fn get_balance(&self, pubkey: &str) -> AekoRustSdkResult<u64> {
60        let value: RpcValueResponse<u64> = self.rpc("getBalance", json!([pubkey])).await?;
61        Ok(value.value)
62    }
63
64    pub async fn get_account_info(
65        &self,
66        pubkey: &str,
67    ) -> AekoRustSdkResult<Option<AccountInfoValue>> {
68        let value: RpcValueResponse<Option<AccountInfoValue>> = self
69            .rpc("getAccountInfo", json!([pubkey, {"encoding": "base64"}]))
70            .await?;
71        Ok(value.value)
72    }
73
74    pub async fn get_program_accounts(
75        &self,
76        program_id: &str,
77    ) -> AekoRustSdkResult<Vec<ProgramAccount>> {
78        self.rpc(
79            "getProgramAccounts",
80            json!([program_id, {"encoding": "base64"}]),
81        )
82        .await
83    }
84
85    pub async fn get_signature_statuses(
86        &self,
87        signatures: &[String],
88    ) -> AekoRustSdkResult<Vec<Option<SignatureStatus>>> {
89        let value: RpcValueResponse<Vec<Option<SignatureStatus>>> = self
90            .rpc("getSignatureStatuses", json!([signatures]))
91            .await?;
92        Ok(value.value)
93    }
94
95    pub async fn send_transaction_base64(
96        &self,
97        signed_transaction_base64: &str,
98    ) -> AekoRustSdkResult<String> {
99        self.rpc(
100            "sendTransaction",
101            json!([signed_transaction_base64, {"encoding": "base64"}]),
102        )
103        .await
104    }
105
106    pub async fn get_wallet_permission_account(
107        &self,
108        pubkey: &str,
109    ) -> AekoRustSdkResult<WalletPermissionAccount> {
110        let account = self
111            .get_account_info(pubkey)
112            .await?
113            .ok_or(AekoRustSdkError::DecodeAccount {
114                label: "wallet permission state",
115            })?;
116        ensure_owner(
117            &account,
118            &default_wallet_permissions_program_id(),
119            "wallet permission state",
120        )?;
121        decode_account(&account, "wallet permission state")
122    }
123
124    pub async fn get_wallet_permission_audit_log(
125        &self,
126        pubkey: &str,
127    ) -> AekoRustSdkResult<WalletPermissionAuditLogAccount> {
128        let account = self
129            .get_account_info(pubkey)
130            .await?
131            .ok_or(AekoRustSdkError::DecodeAccount {
132                label: "wallet permission audit log",
133            })?;
134        ensure_owner(
135            &account,
136            &default_wallet_permissions_program_id(),
137            "wallet permission audit log",
138        )?;
139        decode_account(&account, "wallet permission audit log")
140    }
141
142    pub async fn get_token_721_collection(
143        &self,
144        pubkey: &str,
145    ) -> AekoRustSdkResult<Aeko721Collection> {
146        let account = self
147            .get_account_info(pubkey)
148            .await?
149            .ok_or(AekoRustSdkError::DecodeAccount {
150                label: "AEKO-721 collection",
151            })?;
152        ensure_owner(&account, &default_token_721_program_id(), "AEKO-721 collection")?;
153        decode_account(&account, "AEKO-721 collection")
154    }
155
156    pub async fn get_token_721_token(
157        &self,
158        pubkey: &str,
159    ) -> AekoRustSdkResult<Aeko721Token> {
160        let account = self
161            .get_account_info(pubkey)
162            .await?
163            .ok_or(AekoRustSdkError::DecodeAccount {
164                label: "AEKO-721 token",
165            })?;
166        ensure_owner(&account, &default_token_721_program_id(), "AEKO-721 token")?;
167        decode_account(&account, "AEKO-721 token")
168    }
169
170    async fn rpc<T: DeserializeOwned>(&self, method: &str, params: Value) -> AekoRustSdkResult<T> {
171        let response: JsonRpcEnvelope<T> = self
172            .http
173            .post(&self.endpoint)
174            .json(&json!({
175                "jsonrpc": "2.0",
176                "id": 1,
177                "method": method,
178                "params": params,
179            }))
180            .send()
181            .await?
182            .json()
183            .await?;
184
185        match (response.result, response.error) {
186            (Some(result), None) => Ok(result),
187            (_, Some(error)) => Err(AekoRustSdkError::Rpc(error.message)),
188            _ => Err(AekoRustSdkError::Rpc("missing JSON-RPC result".to_string())),
189        }
190    }
191}
192
193fn ensure_owner(
194    account: &AccountInfoValue,
195    expected_owner: &str,
196    label: &'static str,
197) -> AekoRustSdkResult<()> {
198    if account.owner != expected_owner {
199        return Err(AekoRustSdkError::InvalidAccountOwner {
200            label,
201            expected: expected_owner.to_string(),
202            found: account.owner.clone(),
203        });
204    }
205    Ok(())
206}
207
208fn decode_account<T: BorshDeserialize>(
209    account: &AccountInfoValue,
210    label: &'static str,
211) -> AekoRustSdkResult<T> {
212    let data = match &account.data {
213        Value::Array(values) if !values.is_empty() => values[0]
214            .as_str()
215            .ok_or(AekoRustSdkError::DecodeAccount { label })?,
216        Value::String(value) => value.as_str(),
217        _ => return Err(AekoRustSdkError::DecodeAccount { label }),
218    };
219    let bytes = BASE64
220        .decode(data)
221        .map_err(|_| AekoRustSdkError::DecodeAccount { label })?;
222    let mut slice = bytes.as_slice();
223    T::deserialize(&mut slice).map_err(|_| AekoRustSdkError::DecodeAccount { label })
224}
225
226#[derive(Debug, Deserialize)]
227struct JsonRpcEnvelope<T> {
228    result: Option<T>,
229    error: Option<JsonRpcError>,
230}
231
232#[derive(Debug, Deserialize)]
233struct JsonRpcError {
234    message: String,
235}
236
237#[derive(Debug, Deserialize)]
238struct RpcValueResponse<T> {
239    value: T,
240}
241
242#[derive(Debug, Deserialize)]
243struct LatestBlockhashValue {
244    blockhash: String,
245}