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}