diff --git a/lib/ain-ocean/src/api/address.rs b/lib/ain-ocean/src/api/address.rs index 8d2dbd6b40..98c67e8177 100644 --- a/lib/ain-ocean/src/api/address.rs +++ b/lib/ain-ocean/src/api/address.rs @@ -1,7 +1,8 @@ -use std::{str::FromStr, sync::Arc}; +use std::{collections::BTreeMap, str::FromStr, sync::Arc}; use super::{ - common::address_to_hid, + cache::get_token_cached, + common::{address_to_hid, parse_display_symbol}, path::Path, query::{PaginationQuery, Query}, response::{ApiPagedResponse, Response}, @@ -20,7 +21,9 @@ use ain_macros::ocean_endpoint; use axum::{routing::get, Extension, Router}; use bitcoin::{hashes::Hash, hex::DisplayHex, Txid}; use serde::{Deserialize, Serialize}; +use serde_json::json; use serde_with::skip_serializing_none; +use defichain_rpc::RpcApi; #[derive(Deserialize)] struct Address { @@ -381,13 +384,75 @@ async fn list_transaction_unspent( })) } +// Tokens owned by an address +#[derive(Serialize, Debug)] +#[serde(rename_all = "camelCase")] +struct AddressToken { + id: String, + amount: String, + symbol: String, + display_symbol: String, + symbol_key: String, + name: String, + #[serde(rename = "isDAT")] + is_dat: bool, + #[serde(rename = "isLPS")] + is_lps: bool, + is_loan_token: bool, +} + +#[ocean_endpoint] +async fn list_tokens( + Path(Address { address }): Path
, + Query(query): Query, + Extension(ctx): Extension>, +) -> Result> { + let account: BTreeMap = ctx.client.call( + "getaccount", + &[ + address.into(), + json!({ + "limit": query.size, + "start": query.next.as_ref().and_then(|n| n.parse::().ok()).unwrap_or_default(), + "including_start": query.next.is_none() + }), + true.into(), + ], + ).await?; + + let mut vec = Vec::new(); + for (k, v) in account { + let token = get_token_cached(&ctx, &k).await?; + if token.is_none() { + continue + } + let (id, info) = token.unwrap(); + let address_token = AddressToken { + id, + amount: format!("{:.8}", v), + display_symbol: parse_display_symbol(&info), + symbol: info.symbol, + symbol_key: info.symbol_key, + name: info.name, + is_dat: info.is_dat, + is_lps: info.is_lps, + is_loan_token: info.is_loan_token, + }; + vec.push(address_token) + } + + Ok(ApiPagedResponse::of(vec, query.size, |item| { + item.id.clone() + })) +} + pub fn router(ctx: Arc) -> Router { Router::new() // .route("/history/:height/:txno", get(get_account_history)) // .route("/history", get(list_account_history)) .route("/:address/balance", get(get_balance)) .route("/:address/aggregation", get(get_aggregation)) - // .route("/tokens", get(list_token)) + .route("/:address/tokens", get(list_tokens)) // .route("/vaults", get(list_vault)) .route("/:address/transactions", get(list_transactions)) .route(