Skip to content

Commit

Permalink
Show fungible filter
Browse files Browse the repository at this point in the history
Add filter options for show_fungibles which will include token_info on
assets where applicable. Integration tests cover variations of in query
and valid responses. Set up entity orm relations between asset and token
models.

Co-authored-by: Kyle Espinola <[email protected]>
Co-authored-by: Ahzam Akhtar <[email protected]>
  • Loading branch information
3 people committed Jan 6, 2025
1 parent d914e4b commit e436a14
Show file tree
Hide file tree
Showing 44 changed files with 1,098 additions and 45 deletions.
12 changes: 12 additions & 0 deletions digital_asset_types/src/dao/extensions/asset.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ use crate::dao::{
asset, asset_authority, asset_creators, asset_data, asset_grouping,
asset_v1_account_attachments,
sea_orm_active_enums::{OwnerType, RoyaltyTargetType},
token_accounts,
};

#[derive(Copy, Clone, Debug, EnumIter)]
Expand All @@ -13,6 +14,7 @@ pub enum Relation {
AssetAuthority,
AssetCreators,
AssetGrouping,
TokenAccounts,
}

impl RelationTrait for Relation {
Expand All @@ -22,6 +24,10 @@ impl RelationTrait for Relation {
.from(asset::Column::AssetData)
.to(asset_data::Column::Id)
.into(),
Self::TokenAccounts => asset::Entity::belongs_to(token_accounts::Entity)
.from(asset::Column::Id)
.to(token_accounts::Column::Mint)
.into(),
Self::AssetV1AccountAttachments => {
asset::Entity::has_many(asset_v1_account_attachments::Entity).into()
}
Expand Down Expand Up @@ -62,6 +68,12 @@ impl Related<asset_grouping::Entity> for asset::Entity {
}
}

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

impl Default for RoyaltyTargetType {
fn default() -> Self {
Self::Creators
Expand Down
1 change: 1 addition & 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,4 @@ pub mod asset_data;
pub mod asset_grouping;
pub mod asset_v1_account_attachment;
pub mod instruction;
pub mod token_accounts;
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::{asset, token_accounts};

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

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

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

use super::tokens;

#[derive(Clone, Debug, PartialEq)]
pub struct FullAsset {
pub asset: asset::Model,
pub data: Option<asset_data::Model>,
pub data: asset_data::Model,
pub token_info: Option<tokens::Model>,
pub authorities: Vec<asset_authority::Model>,
pub creators: Vec<asset_creators::Model>,
pub groups: Vec<asset_grouping::Model>,
Expand Down
50 changes: 42 additions & 8 deletions 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,
token_accounts, Cursor, FullAsset, GroupingSize, Pagination,
token_accounts, tokens, Cursor, FullAsset, GroupingSize, Pagination,
},
rpc::{filter::AssetSortDirection, options::Options},
};
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::Owner.eq(owner.clone()))
.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 @@ -274,10 +276,11 @@ pub async fn get_related_for_assets(
let id = asset.id.clone();
let fa = FullAsset {
asset,
data: Some(ad.clone()),
data: ad.clone(),
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(
}
}

find_tokens(conn, ids.clone())
.await?
.into_iter()
.for_each(|t| {
if let Some(asset) = assets_map.get_mut(&t.mint.clone()) {
asset.token_info = Some(t);
}
});

let cond = if show_unverified_collections {
Condition::all()
} else {
Expand Down Expand Up @@ -385,19 +397,29 @@ pub async fn get_by_id(
conn: &impl ConnectionTrait,
asset_id: Vec<u8>,
include_no_supply: bool,
options: &Options,
) -> Result<FullAsset, DbErr> {
let mut asset_data =
asset::Entity::find_by_id(asset_id.clone()).find_also_related(asset_data::Entity);
if !include_no_supply {
asset_data = asset_data.filter(Condition::all().add(asset::Column::Supply.gt(0)));
}
let asset_data: (asset::Model, Option<asset_data::Model>) =

let token_info = if options.show_fungible {
find_tokens(conn, vec![asset_id.clone()])
.await?
.into_iter()
.next()
} else {
None
};

let (asset, data): (asset::Model, asset_data::Model) =
asset_data.one(conn).await.and_then(|o| match o {
Some((a, d)) => Ok((a, d)),
Some((a, Some(d))) => Ok((a, d)),
_ => Err(DbErr::RecordNotFound("Asset Not Found".to_string())),
})?;

let (asset, data) = asset_data;
let authorities: Vec<asset_authority::Model> = asset_authority::Entity::find()
.filter(asset_authority::Column::AssetId.eq(asset.id.clone()))
.order_by_asc(asset_authority::Column::AssetId)
Expand Down Expand Up @@ -430,6 +452,7 @@ pub async fn get_by_id(
authorities,
creators,
groups: grouping,
token_info,
})
}

Expand Down Expand Up @@ -595,3 +618,14 @@ pub async fn get_token_accounts(

Ok(token_accounts)
}

async fn find_tokens(
conn: &impl ConnectionTrait,
ids: Vec<Vec<u8>>,
) -> Result<Vec<tokens::Model>, DbErr> {
tokens::Entity::find()
.filter(tokens::Column::Mint.is_in(ids))
.all(conn)
.await
.map_err(|_| DbErr::RecordNotFound("Token (s) 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
62 changes: 38 additions & 24 deletions digital_asset_types/src/dapi/common/asset.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ use crate::rpc::options::Options;
use crate::rpc::response::TokenAccountList;
use crate::rpc::response::TransactionSignatureList;
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, TokenAccount as RpcTokenAccount,
Expand Down Expand Up @@ -348,36 +349,32 @@ 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);
let rpc_groups = to_grouping(groups, options)?;
let interface = get_interface(&asset)?;
let content = get_content(&data);
let mut chain_data_selector_fn = jsonpath_lib::selector(&data.chain_data);
let chain_data_selector = &mut chain_data_selector_fn;

let (content, edition_nonce, basis_points, mutable, uses) = if let Some(data) = &data {
let mut chain_data_selector_fn = jsonpath_lib::selector(&data.chain_data);
let chain_data_selector = &mut chain_data_selector_fn;
(
get_content(data),
safe_select(chain_data_selector, "$.edition_nonce").and_then(|v| v.as_u64()),
safe_select(chain_data_selector, "$.primary_sale_happened")
.and_then(|v| v.as_bool())
.unwrap_or(false),
data.chain_data_mutability.clone().into(),
data.chain_data.get("uses").map(|u| Uses {
use_method: u
.get("use_method")
.and_then(|s| s.as_str())
.unwrap_or("Single")
.to_string()
.into(),
total: u.get("total").and_then(|t| t.as_u64()).unwrap_or(0),
remaining: u.get("remaining").and_then(|t| t.as_u64()).unwrap_or(0),
}),
)
} else {
(None, None, false, false, None)
};
let edition_nonce =
safe_select(chain_data_selector, "$.edition_nonce").and_then(|v| v.as_u64());
let basis_points = safe_select(chain_data_selector, "$.primary_sale_happened")
.and_then(|v| v.as_bool())
.unwrap_or(false);
let mutable = data.chain_data_mutability.clone().into();
let uses = data.chain_data.get("uses").map(|u| Uses {
use_method: u
.get("use_method")
.and_then(|s| s.as_str())
.unwrap_or("Single")
.to_string()
.into(),
total: u.get("total").and_then(|t| t.as_u64()).unwrap_or(0),
remaining: u.get("remaining").and_then(|t| t.as_u64()).unwrap_or(0),
});

let mpl_core_info = match interface {
Interface::MplCoreAsset | Interface::MplCoreCollection => Some(MplCoreInfo {
Expand All @@ -388,6 +385,22 @@ pub fn asset_to_rpc(asset: FullAsset, options: &Options) -> Result<RpcAsset, DbE
_ => None,
};

let token_info = if options.show_fungible {
token_info.map(|token_info| TokenInfo {
supply: token_info.supply.try_into().unwrap_or(0),
decimals: token_info.decimals as u8,
mint_authority: token_info
.mint_authority
.map(|s| bs58::encode(s).into_string()),
freeze_authority: token_info
.freeze_authority
.map(|s| bs58::encode(s).into_string()),
token_program: bs58::encode(token_info.token_program).into_string(),
})
} else {
None
};

Ok(RpcAsset {
interface: interface.clone(),
id: bs58::encode(asset.id).into_string(),
Expand Down Expand Up @@ -446,6 +459,7 @@ pub fn asset_to_rpc(asset: FullAsset, options: &Options) -> Result<RpcAsset, DbE
},
uses,
burnt: asset.burnt,
token_info,
mint_extensions: asset.mint_extensions,
plugins: asset.mpl_core_plugins,
unknown_plugins: asset.mpl_core_unknown_plugins,
Expand Down
2 changes: 1 addition & 1 deletion digital_asset_types/src/dapi/get_asset.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ pub async fn get_asset(
id: Vec<u8>,
options: &Options,
) -> Result<Asset, DbErr> {
let asset = scopes::asset::get_by_id(db, id, false).await?;
let asset = scopes::asset::get_by_id(db, id, false, options).await?;
asset_to_rpc(asset, options)
}

Expand Down
13 changes: 13 additions & 0 deletions digital_asset_types/src/rpc/asset.rs
Original file line number Diff line number Diff line change
Expand Up @@ -379,6 +379,17 @@ pub struct MplCoreInfo {
pub plugins_json_version: Option<i32>,
}

#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)]
pub struct TokenInfo {
pub supply: u64,
pub decimals: u8,
pub token_program: 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, Default)]
pub struct Asset {
pub interface: Interface,
Expand Down Expand Up @@ -406,6 +417,8 @@ pub struct Asset {
#[serde(skip_serializing_if = "Option::is_none")]
pub mint_extensions: Option<Value>,
#[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
2 changes: 2 additions & 0 deletions digital_asset_types/src/rpc/options.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,4 +8,6 @@ pub struct Options {
pub show_unverified_collections: bool,
#[serde(default)]
pub show_zero_balance: bool,
#[serde(default)]
pub show_fungible: bool,
}
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Original file line number Diff line number Diff line change
@@ -1,13 +1,12 @@
use super::common::*;
use das_api::{
api::{self, ApiContract},
error::DasApiError,
};
use function_name::named;

use das_api::api::{self, ApiContract};

use itertools::Itertools;

use serial_test::serial;

use super::common::*;

#[tokio::test]
#[serial]
#[named]
Expand Down Expand Up @@ -134,7 +133,7 @@ async fn test_fungible_token_get_asset_scenario_1() {
#[tokio::test]
#[serial]
#[named]
async fn test_fungible_token_get_asset_scenario_2() {
async fn test_token_keg_fungible_with_no_metadata() {
let name = trim_test_name(function_name!());
let setup = TestSetup::new_with_options(
name.clone(),
Expand All @@ -156,7 +155,7 @@ async fn test_fungible_token_get_asset_scenario_2() {
"#;

let request: api::GetAsset = serde_json::from_str(request).unwrap();
let response = setup.das_api.get_asset(request).await.unwrap();
let response = setup.das_api.get_asset(request).await;

insta::assert_json_snapshot!(name, response);
assert!(matches!(response, Err(DasApiError::DatabaseError(_))));
}
Loading

0 comments on commit e436a14

Please sign in to comment.