Skip to content

Commit

Permalink
Merge remote-tracking branch 'origin/das-39-add-gettokenaccounts-supp…
Browse files Browse the repository at this point in the history
…ort-to-das-api-main' into das-54-show-zero-balance-filter-rebase
  • Loading branch information
AhzamAkhtar committed Nov 24, 2024
2 parents 4762848 + 2b3a482 commit e72dcdc
Show file tree
Hide file tree
Showing 13 changed files with 326 additions and 12 deletions.
35 changes: 32 additions & 3 deletions das_api/src/api/api_impl.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,13 +9,13 @@ use digital_asset_types::{
dapi::{
get_asset, get_asset_proofs, get_asset_signatures, get_assets, get_assets_by_authority,
get_assets_by_creator, get_assets_by_group, get_assets_by_owner, get_proof_for_asset,
search_assets,
get_token_accounts, search_assets,
},
rpc::{
filter::{AssetSortBy, SearchConditionType},
response::GetGroupingResponse,
response::{GetGroupingResponse, TokenAccountList},
OwnershipModel, RoyaltyModel,
},
rpc::{OwnershipModel, RoyaltyModel},
};
use open_rpc_derive::document_rpc;
use sea_orm::{sea_query::ConditionType, ConnectionTrait, DbBackend, Statement};
Expand Down Expand Up @@ -516,4 +516,33 @@ impl ApiContract for DasApi {
group_size: gs.size,
})
}

async fn get_token_accounts(
self: &DasApi,
payload: GetTokenAccounts,
) -> Result<TokenAccountList, DasApiError> {
let GetTokenAccounts {
owner_address,
mint_address,
limit,
page,
before,
after,
options,
cursor,
} = payload;
let owner_address = validate_opt_pubkey(&owner_address)?;
let mint_address = validate_opt_pubkey(&mint_address)?;
let options = options.unwrap_or_default();
let page_options = self.validate_pagination(limit, page, &before, &after, &cursor, None)?;
get_token_accounts(
&self.db_connection,
owner_address,
mint_address,
&page_options,
&options,
)
.await
.map_err(Into::into)
}
}
27 changes: 26 additions & 1 deletion das_api/src/api/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ use crate::error::DasApiError;
use async_trait::async_trait;
use digital_asset_types::rpc::filter::{AssetSortDirection, SearchConditionType};
use digital_asset_types::rpc::options::Options;
use digital_asset_types::rpc::response::{AssetList, TransactionSignatureList};
use digital_asset_types::rpc::response::{AssetList, TokenAccountList, TransactionSignatureList};
use digital_asset_types::rpc::{filter::AssetSorting, response::GetGroupingResponse};
use digital_asset_types::rpc::{Asset, AssetProof, Interface, OwnershipModel, RoyaltyModel};
use open_rpc_derive::{document_rpc, rpc};
Expand Down Expand Up @@ -163,6 +163,21 @@ pub struct GetAssetSignatures {
pub sort_direction: Option<AssetSortDirection>,
}

#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
#[serde(deny_unknown_fields, rename_all = "camelCase")]
pub struct GetTokenAccounts {
pub owner_address: Option<String>,
pub mint_address: Option<String>,
pub limit: Option<u32>,
pub page: Option<u32>,
pub before: Option<String>,
pub after: Option<String>,
#[serde(default, alias = "displayOptions")]
pub options: Option<Options>,
#[serde(default)]
pub cursor: Option<String>,
}

#[document_rpc]
#[async_trait]
pub trait ApiContract: Send + Sync + 'static {
Expand Down Expand Up @@ -251,4 +266,14 @@ pub trait ApiContract: Send + Sync + 'static {
summary = "Get a list of assets grouped by a specific authority"
)]
async fn get_grouping(&self, payload: GetGrouping) -> Result<GetGroupingResponse, DasApiError>;

#[rpc(
name = "getTokenAccounts",
params = "named",
summary = "Get a list of token accounts by owner or mint"
)]
async fn get_token_accounts(
&self,
payload: GetTokenAccounts,
) -> Result<TokenAccountList, DasApiError>;
}
13 changes: 13 additions & 0 deletions das_api/src/builder.rs
Original file line number Diff line number Diff line change
Expand Up @@ -118,6 +118,19 @@ impl RpcApiBuilder {
Ok(rpc_context.schema())
})?;

module.register_async_method(
"get_token_accounts",
|rpc_params, rpc_context| async move {
let payload = rpc_params.parse::<GetTokenAccounts>()?;
rpc_context
.get_token_accounts(payload)
.await
.map_err(Into::into)
},
)?;

module.register_alias("getTokenAccounts", "get_token_accounts")?;

Ok(module)
}
}
30 changes: 29 additions & 1 deletion digital_asset_types/src/dao/scopes/asset.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ use crate::{
asset_authority, asset_creators, asset_data, asset_grouping, cl_audits_v2,
extensions::{self, instruction::PascalCase},
sea_orm_active_enums::Instruction,
tokens, Cursor, FullAsset, GroupingSize, Pagination,
token_accounts, tokens, Cursor, FullAsset, GroupingSize, Pagination,
},
rpc::{filter::AssetSortDirection, options::Options},
};
Expand Down Expand Up @@ -590,3 +590,31 @@ pub async fn get_token_by_id(
_ => Err(DbErr::RecordNotFound("Token Not Found".to_string())),
})
}

pub async fn get_token_accounts(
conn: &impl ConnectionTrait,
owner_address: Option<Vec<u8>>,
mint_address: Option<Vec<u8>>,
pagination: &Pagination,
limit: u64,
) -> Result<Vec<token_accounts::Model>, DbErr> {
let mut condition = Condition::all();
if let Some(owner) = owner_address {
condition = condition.add(token_accounts::Column::Owner.eq(owner));
}
if let Some(mint) = mint_address {
condition = condition.add(token_accounts::Column::Mint.eq(mint));
}

let token_accounts = paginate(
pagination,
limit,
token_accounts::Entity::find().filter(condition),
Order::Asc,
token_accounts::Column::Pubkey,
)
.all(conn)
.await?;

Ok(token_accounts)
}
95 changes: 91 additions & 4 deletions digital_asset_types/src/dapi/common/asset.rs
Original file line number Diff line number Diff line change
@@ -1,16 +1,19 @@
use crate::dao::sea_orm_active_enums::SpecificationVersions;
use crate::dao::token_accounts;
use crate::dao::FullAsset;
use crate::dao::PageOptions;
use crate::dao::Pagination;
use crate::dao::{asset, asset_authority, asset_creators, asset_data, asset_grouping};
use crate::rpc::filter::{AssetSortBy, AssetSortDirection, AssetSorting};
use crate::rpc::options::Options;
use crate::rpc::response::TokenAccountList;
use crate::rpc::response::TransactionSignatureList;
use crate::rpc::response::{AssetError, AssetList};
use crate::rpc::response::{AssetList, DasError};
use crate::rpc::TokenInfo;
use crate::rpc::{
Asset as RpcAsset, Authority, Compression, Content, Creator, File, Group, Interface,
MetadataMap, MplCoreInfo, Ownership, Royalty, Scope, Supply, Uses,
MetadataMap, MplCoreInfo, Ownership, Royalty, Scope, Supply, TokenAccount as RpcTokenAccount,
Uses,
};
use jsonpath_lib::JsonPathError;
use log::warn;
Expand Down Expand Up @@ -475,18 +478,102 @@ pub fn asset_to_rpc(asset: FullAsset, options: &Options) -> Result<RpcAsset, DbE
pub fn asset_list_to_rpc(
asset_list: Vec<FullAsset>,
options: &Options,
) -> (Vec<RpcAsset>, Vec<AssetError>) {
) -> (Vec<RpcAsset>, Vec<DasError>) {
asset_list
.into_iter()
.fold((vec![], vec![]), |(mut assets, mut errors), asset| {
let id = bs58::encode(asset.asset.id.clone()).into_string();
match asset_to_rpc(asset, options) {
Ok(rpc_asset) => assets.push(rpc_asset),
Err(e) => errors.push(AssetError {
Err(e) => errors.push(DasError {
id,
error: e.to_string(),
}),
}
(assets, errors)
})
}

pub fn token_account_to_rpc(
token_account: token_accounts::Model,
_options: &Options,
) -> Result<RpcTokenAccount, DbErr> {
let address = bs58::encode(token_account.pubkey.clone()).into_string();
let mint = bs58::encode(token_account.mint.clone()).into_string();
let owner = bs58::encode(token_account.owner.clone()).into_string();
let delegate = token_account
.delegate
.map(|d| bs58::encode(d).into_string());
let close_authority = token_account
.close_authority
.map(|d| bs58::encode(d).into_string());

Ok(RpcTokenAccount {
address,
mint,
amount: token_account.amount as u64,
owner,
frozen: token_account.frozen,
delegate,
delegated_amount: token_account.delegated_amount as u64,
close_authority,
extensions: None,
})
}

pub fn token_account_list_to_rpc(
token_accounts: Vec<token_accounts::Model>,
options: &Options,
) -> (Vec<RpcTokenAccount>, Vec<DasError>) {
token_accounts.into_iter().fold(
(vec![], vec![]),
|(mut accounts, mut errors), token_account| {
let id = bs58::encode(token_account.pubkey.clone()).into_string();
match token_account_to_rpc(token_account, options) {
Ok(rpc_token_account) => accounts.push(rpc_token_account),
Err(e) => errors.push(DasError {
id,
error: e.to_string(),
}),
}
(accounts, errors)
},
)
}

pub fn build_token_list_response(
token_accounts: Vec<token_accounts::Model>,
limit: u64,
pagination: &Pagination,
options: &Options,
) -> TokenAccountList {
let total = token_accounts.len() as u32;
let (page, before, after, cursor) = match pagination {
Pagination::Keyset { before, after } => {
let bef = before.clone().and_then(|x| String::from_utf8(x).ok());
let aft = after.clone().and_then(|x| String::from_utf8(x).ok());
(None, bef, aft, None)
}
Pagination::Page { page } => (Some(*page as u32), None, None, None),
Pagination::Cursor(_) => {
if let Some(last_token_account) = token_accounts.last() {
let cursor_str = bs58::encode(&last_token_account.pubkey.clone()).into_string();
(None, None, None, Some(cursor_str))
} else {
(None, None, None, None)
}
}
};

let (items, errors) = token_account_list_to_rpc(token_accounts, options);
TokenAccountList {
total,
limit: limit as u32,
page,
before,
after,
token_accounts: items,
cursor,
errors,
}
}
34 changes: 34 additions & 0 deletions digital_asset_types/src/dapi/get_token_accounts.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
use sea_orm::{DatabaseConnection, DbErr};

use crate::{
dao::PageOptions,
rpc::{options::Options, response::TokenAccountList},
};

use super::common::{build_token_list_response, create_pagination};

pub async fn get_token_accounts(
db: &DatabaseConnection,
owner_address: Option<Vec<u8>>,
mint_address: Option<Vec<u8>>,
page_options: &PageOptions,
options: &Options,
) -> Result<TokenAccountList, DbErr> {
let pagination = create_pagination(page_options)?;

let token_accounts = crate::dao::scopes::asset::get_token_accounts(
db,
owner_address,
mint_address,
&pagination,
page_options.limit,
)
.await?;

Ok(build_token_list_response(
token_accounts,
page_options.limit,
&pagination,
options,
))
}
2 changes: 2 additions & 0 deletions digital_asset_types/src/dapi/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ mod assets_by_owner;
mod change_logs;
mod get_asset;
mod get_asset_signatures;
mod get_token_accounts;
mod search_assets;

pub mod common;
Expand All @@ -16,4 +17,5 @@ pub use assets_by_owner::*;
pub use change_logs::*;
pub use get_asset::*;
pub use get_asset_signatures::*;
pub use get_token_accounts::*;
pub use search_assets::*;
14 changes: 14 additions & 0 deletions digital_asset_types/src/rpc/asset.rs
Original file line number Diff line number Diff line change
Expand Up @@ -412,3 +412,17 @@ pub struct Asset {
#[serde(skip_serializing_if = "Option::is_none")]
pub unknown_external_plugins: Option<Value>,
}

#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Default, JsonSchema)]
#[serde(default)]
pub struct TokenAccount {
pub address: String,
pub mint: String,
pub amount: u64,
pub owner: String,
pub frozen: bool,
pub delegate: Option<String>,
pub delegated_amount: u64,
pub close_authority: Option<String>,
pub extensions: Option<Value>,
}
23 changes: 20 additions & 3 deletions digital_asset_types/src/rpc/response.rs
Original file line number Diff line number Diff line change
@@ -1,12 +1,13 @@
use schemars::JsonSchema;

use {
crate::rpc::Asset,
crate::rpc::{Asset, TokenAccount},
serde::{Deserialize, Serialize},
};

#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Default, JsonSchema)]
#[serde(default)]
pub struct AssetError {
pub struct DasError {
pub id: String,
pub error: String,
}
Expand Down Expand Up @@ -34,7 +35,7 @@ pub struct AssetList {
pub cursor: Option<String>,
pub items: Vec<Asset>,
#[serde(skip_serializing_if = "Vec::is_empty")]
pub errors: Vec<AssetError>,
pub errors: Vec<DasError>,
}

#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Default, JsonSchema)]
Expand All @@ -50,3 +51,19 @@ pub struct TransactionSignatureList {
pub after: Option<String>,
pub items: Vec<(String, String)>,
}

#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Default, JsonSchema)]
#[serde(default)]
pub struct TokenAccountList {
pub total: u32,
pub limit: u32,
#[serde(skip_serializing_if = "Option::is_none")]
pub page: Option<u32>,
#[serde(skip_serializing_if = "Option::is_none")]
pub before: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub after: Option<String>,
pub token_accounts: Vec<TokenAccount>,
pub cursor: Option<String>,
pub errors: Vec<DasError>,
}
Binary file not shown.
Loading

0 comments on commit e72dcdc

Please sign in to comment.