Files
lez-programs/modules/token/ffi/src/api/decode.rs
T

179 lines
6.8 KiB
Rust

use nssa_core::{
account::{Account, AccountId},
program::ProgramId,
};
use serde_json::{json, Value};
use token_core::{MetadataStandard, TokenDefinition, TokenHolding, TokenMetadata};
use super::{
parse_token_program_id,
request::{
DecodeAccountRequest, DecodeDefinitionRequest, DecodeHoldingRequest, DecodeMetadataRequest,
},
TokenApiError, TokenResult,
};
use crate::account::{account_id_hex, decode_account as decode_wallet_account};
pub fn decode_definition(request: DecodeDefinitionRequest) -> TokenResult {
let token_program_id = parse_token_program_id(&request.token_program_id)?;
let (account_id, account) = parse_and_validate_account(&request.definition, token_program_id)?;
let definition = TokenDefinition::try_from(&account.data)
.map_err(|_| TokenApiError::new("invalid_definition_data"))?;
Ok(definition_json(account_id, &definition))
}
pub fn decode_holding(request: DecodeHoldingRequest) -> TokenResult {
let token_program_id = parse_token_program_id(&request.token_program_id)?;
let (account_id, account) = parse_and_validate_account(&request.holding, token_program_id)?;
let holding = TokenHolding::try_from(&account.data)
.map_err(|_| TokenApiError::new("invalid_holding_data"))?;
Ok(holding_json(account_id, &holding))
}
pub fn decode_metadata(request: DecodeMetadataRequest) -> TokenResult {
let token_program_id = parse_token_program_id(&request.token_program_id)?;
let (account_id, account) = parse_and_validate_account(&request.metadata, token_program_id)?;
let metadata = TokenMetadata::try_from(&account.data)
.map_err(|_| TokenApiError::new("invalid_metadata_data"))?;
Ok(metadata_json(account_id, &metadata))
}
pub fn decode_account(request: DecodeAccountRequest) -> TokenResult {
let token_program_id = parse_token_program_id(&request.token_program_id)?;
let (account_id, account) = parse_and_validate_account(&request.account, token_program_id)?;
let definition = TokenDefinition::try_from(&account.data).ok();
let holding = TokenHolding::try_from(&account.data).ok();
let metadata = TokenMetadata::try_from(&account.data).ok();
let matches = usize::from(definition.is_some())
+ usize::from(holding.is_some())
+ usize::from(metadata.is_some());
match (matches, definition, holding, metadata) {
(1, Some(value), None, None) => Ok(definition_json(account_id, &value)),
(1, None, Some(value), None) => Ok(holding_json(account_id, &value)),
(1, None, None, Some(value)) => Ok(metadata_json(account_id, &value)),
(0, None, None, None) => Err(TokenApiError::new("invalid_account_data")),
_ => Err(TokenApiError::new("ambiguous_account_type")),
}
}
fn parse_and_validate_account(
read: &crate::account::AccountRead,
token_program_id: ProgramId,
) -> Result<(AccountId, Account), TokenApiError> {
let (account_id, account) = decode_wallet_account(read).map_err(map_account_read_error)?;
if account.program_owner != token_program_id {
return Err(TokenApiError::new("token_program_mismatch"));
}
Ok((account_id, account))
}
fn map_account_read_error(error: String) -> TokenApiError {
if error == "account read failed" {
TokenApiError::new("account_read_failed")
} else {
TokenApiError::new("bad_request")
}
}
fn definition_json(account_id: AccountId, definition: &TokenDefinition) -> Value {
let account_hex = account_id_hex(account_id);
match definition {
TokenDefinition::Fungible {
name,
total_supply,
metadata_id,
authority,
} => json!({
"accountType": "definition",
"kind": "fungible",
"accountId": account_id.to_string(),
"accountIdHex": account_hex,
"name": name,
"totalSupplyRaw": total_supply.to_string(),
"metadataId": metadata_id.map(|value| value.to_string()),
"metadataIdHex": metadata_id.map(account_id_hex),
"mintAuthorityId": authority.map(|value| value.to_string()),
"mintAuthorityIdHex": authority.map(account_id_hex),
}),
TokenDefinition::NonFungible {
name,
printable_supply,
metadata_id,
} => json!({
"accountType": "definition",
"kind": "nonFungible",
"accountId": account_id.to_string(),
"accountIdHex": account_hex,
"name": name,
"printableSupplyRaw": printable_supply.to_string(),
"metadataId": metadata_id.to_string(),
"metadataIdHex": account_id_hex(*metadata_id),
}),
}
}
fn holding_json(account_id: AccountId, holding: &TokenHolding) -> Value {
let account_hex = account_id_hex(account_id);
match holding {
TokenHolding::Fungible {
definition_id,
balance,
} => json!({
"accountType": "holding",
"kind": "fungible",
"accountId": account_id.to_string(),
"accountIdHex": account_hex,
"definitionId": definition_id.to_string(),
"definitionIdHex": account_id_hex(*definition_id),
"balanceRaw": balance.to_string(),
}),
TokenHolding::NftMaster {
definition_id,
print_balance,
} => json!({
"accountType": "holding",
"kind": "nftMaster",
"accountId": account_id.to_string(),
"accountIdHex": account_hex,
"definitionId": definition_id.to_string(),
"definitionIdHex": account_id_hex(*definition_id),
"printBalanceRaw": print_balance.to_string(),
}),
TokenHolding::NftPrintedCopy {
definition_id,
owned,
} => json!({
"accountType": "holding",
"kind": "nftPrintedCopy",
"accountId": account_id.to_string(),
"accountIdHex": account_hex,
"definitionId": definition_id.to_string(),
"definitionIdHex": account_id_hex(*definition_id),
"owned": owned,
}),
}
}
fn metadata_json(account_id: AccountId, metadata: &TokenMetadata) -> Value {
json!({
"accountType": "metadata",
"accountId": account_id.to_string(),
"accountIdHex": account_id_hex(account_id),
"definitionId": metadata.definition_id.to_string(),
"definitionIdHex": account_id_hex(metadata.definition_id),
"standard": metadata_standard_name(&metadata.standard),
"uri": metadata.uri,
"creators": metadata.creators,
"primarySaleDateRaw": metadata.primary_sale_date.to_string(),
})
}
fn metadata_standard_name(value: &MetadataStandard) -> &'static str {
match value {
MetadataStandard::Simple => "simple",
MetadataStandard::Expanded => "expanded",
}
}