Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

getTokenAccount rpc method, showFungible and showZeroBalance options #224

2 changes: 2 additions & 0 deletions digital_asset_types/src/dao/extensions/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,3 +5,5 @@ pub mod asset_data;
pub mod asset_grouping;
pub mod asset_v1_account_attachment;
pub mod instruction;
pub mod token_accounts;
pub mod tokens;
25 changes: 25 additions & 0 deletions digital_asset_types/src/dao/extensions/token_accounts.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
use sea_orm::{EntityTrait, EnumIter, Related, RelationDef, RelationTrait};

use crate::dao::{token_accounts, tokens};

#[derive(Copy, Clone, Debug, EnumIter)]
pub enum Relation {
Tokens,
}

impl RelationTrait for Relation {
fn def(&self) -> RelationDef {
match self {
Self::Tokens => token_accounts::Entity::belongs_to(tokens::Entity)
.from(token_accounts::Column::Mint)
.to(tokens::Column::Mint)
.into(),
}
}
}

impl Related<tokens::Entity> for token_accounts::Entity {
fn to() -> RelationDef {
Relation::Tokens.def()
}
}
36 changes: 36 additions & 0 deletions digital_asset_types/src/dao/extensions/tokens.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
use sea_orm::{EntityTrait, EnumIter, Related, RelationDef, RelationTrait};

use crate::dao::{asset, token_accounts, tokens};

#[derive(Copy, Clone, Debug, EnumIter)]
pub enum Relation {
TokenAccounts,
Asset,
}

impl RelationTrait for Relation {
fn def(&self) -> RelationDef {
match self {
Self::TokenAccounts => tokens::Entity::belongs_to(token_accounts::Entity)
.from(tokens::Column::Mint)
.to(token_accounts::Column::Mint)
.into(),
Self::Asset => tokens::Entity::belongs_to(asset::Entity)
.from(tokens::Column::Mint)
.to(asset::Column::Id)
.into(),
}
}
}

impl Related<token_accounts::Entity> for tokens::Entity {
fn to() -> RelationDef {
Relation::TokenAccounts.def()
}
}

impl Related<asset::Entity> for tokens::Entity {
fn to() -> RelationDef {
Relation::Asset.def()
}
}
7 changes: 6 additions & 1 deletion digital_asset_types/src/dao/full_asset.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,8 @@
use crate::dao::{asset, asset_authority, asset_creators, asset_data, asset_grouping};
use crate::dao::{
asset, asset_authority, asset_creators, asset_data, asset_grouping, token_accounts,
};

use super::tokens;

#[derive(Clone, Debug, PartialEq)]
pub struct FullAsset {
Expand All @@ -7,6 +11,7 @@ pub struct FullAsset {
pub authorities: Vec<asset_authority::Model>,
pub creators: Vec<asset_creators::Model>,
pub groups: Vec<asset_grouping::Model>,
pub token_info: Option<(tokens::Model, Option<token_accounts::Model>)>,
}
#[derive(Clone, Debug, PartialEq)]
pub struct AssetRelated {
Expand Down
42 changes: 36 additions & 6 deletions digital_asset_types/src/dao/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@
mod full_asset;
mod generated;
pub mod scopes;
use crate::rpc::options::Options;

use self::sea_orm_active_enums::{
OwnerType, RoyaltyTargetType, SpecificationAssetClass, SpecificationVersions,
};
Expand Down Expand Up @@ -75,7 +77,7 @@ pub struct SearchAssetsQuery {
}

impl SearchAssetsQuery {
pub fn conditions(&self) -> Result<(Condition, Vec<RelationDef>), DbErr> {
pub fn conditions(&self, options: &Options) -> Result<(Condition, Vec<RelationDef>), DbErr> {
let mut conditions = match self.condition_type {
// None --> default to all when no option is provided
None | Some(ConditionType::All) => Condition::all(),
Expand Down Expand Up @@ -150,11 +152,19 @@ impl SearchAssetsQuery {
// In theory, the owner_type=single check should be sufficient,
// however there is an old bug that has marked some non-NFTs as "single" with supply > 1.
// The supply check guarentees we do not include those.
conditions = conditions.add_option(Some(
asset::Column::OwnerType
.eq(OwnerType::Single)
.and(asset::Column::Supply.lte(1)),
));
if options.show_zero_balance {
conditions = conditions.add(
token_accounts::Column::Amount
.eq(0)
.or(token_accounts::Column::Amount.ne(0)),
);
} else {
conditions = conditions.add(
tokens::Column::Supply
.ne(0)
.or(asset::Column::Burnt.eq(true)),
);
}
}

if let Some(c) = self.creator_address.to_owned() {
Expand All @@ -181,6 +191,26 @@ impl SearchAssetsQuery {
joins.push(rel);
}

let rel = extensions::tokens::Relation::Asset
.def()
.rev()
.on_condition(|left, right| {
Expr::tbl(right, tokens::Column::Mint)
.eq(Expr::tbl(left, asset::Column::Id))
.into_condition()
});
joins.push(rel);

let rel = extensions::token_accounts::Relation::Tokens
.def()
.rev()
.on_condition(|left, right| {
Expr::tbl(right, token_accounts::Column::Mint)
.eq(Expr::tbl(left, tokens::Column::Mint))
.into_condition()
});
joins.push(rel);

if let Some(a) = self.authority_address.to_owned() {
conditions = conditions.add(asset_authority::Column::Authority.eq(a));
let rel = extensions::asset_authority::Relation::Asset
Expand Down
42 changes: 38 additions & 4 deletions digital_asset_types/src/dao/scopes/asset.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,9 @@ use crate::{
asset_authority, asset_creators, asset_data, asset_grouping, cl_audits_v2,
extensions::{self, instruction::PascalCase},
sea_orm_active_enums::Instruction,
Cursor, FullAsset, GroupingSize, Pagination,
token_accounts, tokens, Cursor, FullAsset, GroupingSize, Pagination,
},
rpc::filter::AssetSortDirection,
rpc::{filter::AssetSortDirection, options::Options},
};
use indexmap::IndexMap;
use sea_orm::{entity::*, query::*, ConnectionTrait, DbErr, Order};
Expand Down Expand Up @@ -149,11 +149,12 @@ pub async fn get_assets_by_owner(
sort_direction: Order,
pagination: &Pagination,
limit: u64,
show_unverified_collections: bool,
options: &Options,
) -> Result<Vec<FullAsset>, DbErr> {
let cond = Condition::all()
.add(asset::Column::Owner.eq(owner))
.add(asset::Column::Supply.gt(0));

get_assets_by_condition(
conn,
cond,
Expand All @@ -162,7 +163,7 @@ pub async fn get_assets_by_owner(
sort_direction,
pagination,
limit,
show_unverified_collections,
options.show_unverified_collections,
)
.await
}
Expand All @@ -176,6 +177,7 @@ pub async fn get_assets(
let cond = Condition::all()
.add(asset::Column::Id.is_in(asset_ids))
.add(asset::Column::Supply.gt(0));

get_assets_by_condition(
conn,
cond,
Expand Down Expand Up @@ -278,6 +280,7 @@ pub async fn get_related_for_assets(
authorities: vec![],
creators: vec![],
groups: vec![],
token_info: None,
};
acc.insert(id, fa);
};
Expand Down Expand Up @@ -325,6 +328,15 @@ pub async fn get_related_for_assets(
}
}

for id in ids.clone() {
let id_clone = id.clone();
if let Ok(token_info) = get_token_by_id(conn, id_clone).await {
if let Some(asset) = assets_map.get_mut(&id) {
asset.token_info = Some(token_info);
}
}
}

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
for id in ids.clone() {
let id_clone = id.clone();
if let Ok(token_info) = get_token_by_id(conn, id_clone).await {
if let Some(asset) = assets_map.get_mut(&id) {
asset.token_info = Some(token_info);
}
}
}
for id in ids.clone() {
get_token_by_id(conn, id.clone())
.await
.ok()
.map(|t| {
assets_map.get_mut(&id).map(|asset| {
asset.token_info = Some(t);
});
});
}

let cond = if show_unverified_collections {
Condition::all()
} else {
Expand Down Expand Up @@ -391,6 +403,12 @@ pub async fn get_by_id(
if !include_no_supply {
asset_data = asset_data.filter(Condition::all().add(asset::Column::Supply.gt(0)));
}

let token_info = match get_token_by_id(conn, asset_id.clone()).await {
Ok(info) => Some(info),
Err(_) => return Err(DbErr::RecordNotFound("Token Not Found".to_string())),
};

let asset_data: (asset::Model, asset_data::Model) =
asset_data.one(conn).await.and_then(|o| match o {
Some((a, Some(d))) => Ok((a, d)),
Expand Down Expand Up @@ -430,6 +448,7 @@ pub async fn get_by_id(
authorities,
creators,
groups: grouping,
token_info,
})
}

Expand Down Expand Up @@ -553,3 +572,18 @@ fn filter_out_stale_creators(creators: &mut Vec<asset_creators::Model>) {
}
}
}

pub async fn get_token_by_id(
conn: &impl ConnectionTrait,
id: Vec<u8>,
) -> Result<(tokens::Model, Option<token_accounts::Model>), DbErr> {
tokens::Entity::find_by_id(id)
.find_also_related(token_accounts::Entity)
.order_by_asc(tokens::Column::Mint)
.one(conn)
.await
.and_then(|o| match o {
Some(t) => Ok(t),
_ => Err(DbErr::RecordNotFound("Token Not Found".to_string())),
})
}
2 changes: 1 addition & 1 deletion digital_asset_types/src/dapi/assets_by_owner.rs
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ pub async fn get_assets_by_owner(
sort_direction,
&pagination,
page_options.limit,
options.show_unverified_collections,
options,
)
.await?;
Ok(build_asset_response(
Expand Down
26 changes: 26 additions & 0 deletions digital_asset_types/src/dapi/common/asset.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ use crate::rpc::filter::{AssetSortBy, AssetSortDirection, AssetSorting};
use crate::rpc::options::Options;
use crate::rpc::response::TransactionSignatureList;
use crate::rpc::response::{AssetError, AssetList};
use crate::rpc::TokenInfo;
use crate::rpc::{
Asset as RpcAsset, Authority, Compression, Content, Creator, File, Group, Interface,
MetadataMap, MplCoreInfo, Ownership, Royalty, Scope, Supply, Uses,
Expand Down Expand Up @@ -355,6 +356,7 @@ pub fn asset_to_rpc(asset: FullAsset, options: &Options) -> Result<RpcAsset, DbE
authorities,
creators,
groups,
token_info,
} = asset;
let rpc_authorities = to_authority(authorities);
let rpc_creators = to_creators(creators);
Expand All @@ -377,6 +379,29 @@ pub fn asset_to_rpc(asset: FullAsset, options: &Options) -> Result<RpcAsset, DbE
_ => None,
};

let token_info = if let Some(token_info) = token_info {
Some(TokenInfo {
balance: token_info
.1
.as_ref()
.map_or(0, |info| info.amount.try_into().unwrap_or(0)),
supply: token_info.0.supply.try_into().unwrap_or(0),
decimals: token_info.0.decimals as u8,
mint_authority: token_info
.0
.mint_authority
.map(|s| bs58::encode(s).into_string()),
freeze_authority: token_info
.0
.freeze_authority
.map(|s| bs58::encode(s).into_string()),
token_program: bs58::encode(token_info.0.token_program).into_string(),
associated_token_address : token_info.1.as_ref().map(|info| bs58::encode(info.pubkey.clone()).into_string()),
})
} else {
None
};

Ok(RpcAsset {
interface: interface.clone(),
id: bs58::encode(asset.id).into_string(),
Expand Down Expand Up @@ -444,6 +469,7 @@ pub fn asset_to_rpc(asset: FullAsset, options: &Options) -> Result<RpcAsset, DbE
remaining: u.get("remaining").and_then(|t| t.as_u64()).unwrap_or(0),
}),
burnt: asset.burnt,
token_info,
plugins: asset.mpl_core_plugins,
unknown_plugins: asset.mpl_core_unknown_plugins,
mpl_core_info,
Expand Down
2 changes: 1 addition & 1 deletion digital_asset_types/src/dapi/search_assets.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ pub async fn search_assets(
) -> Result<AssetList, DbErr> {
let pagination = create_pagination(page_options)?;
let (sort_direction, sort_column) = create_sorting(sorting);
let (condition, joins) = search_assets_query.conditions()?;
let (condition, joins) = search_assets_query.conditions(options)?;
let assets = scopes::asset::get_assets_by_condition(
db,
condition,
Expand Down
16 changes: 16 additions & 0 deletions digital_asset_types/src/rpc/asset.rs
Original file line number Diff line number Diff line change
Expand Up @@ -366,6 +366,20 @@ pub struct MplCoreInfo {
pub plugins_json_version: Option<i32>,
}

#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)]
pub struct TokenInfo {
pub balance: u64,
pub supply: u64,
pub decimals: u8,
pub token_program: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub associated_token_address: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub mint_authority: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub freeze_authority: Option<String>,
}

#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)]
pub struct Asset {
pub interface: Interface,
Expand All @@ -389,6 +403,8 @@ pub struct Asset {
pub mutable: bool,
pub burnt: bool,
#[serde(skip_serializing_if = "Option::is_none")]
pub token_info: Option<TokenInfo>,
#[serde(skip_serializing_if = "Option::is_none")]
pub plugins: Option<Value>,
#[serde(skip_serializing_if = "Option::is_none")]
pub unknown_plugins: Option<Value>,
Expand Down
4 changes: 4 additions & 0 deletions digital_asset_types/src/rpc/options.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,4 +6,8 @@ use serde::{Deserialize, Serialize};
pub struct Options {
#[serde(default)]
pub show_unverified_collections: bool,
#[serde(default)]
pub show_fungible: bool,
#[serde(default)]
pub show_zero_balance: bool,
}
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
1 change: 1 addition & 0 deletions integration_tests/tests/integration_tests/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,3 +4,4 @@ mod common;
mod general_scenario_tests;
mod mpl_core_tests;
mod regular_nft_tests;
mod show_fungible_flag_tests;
Loading