Skip to content

Commit

Permalink
feat(nft): introduce UriMeta struct and add additional fields to NftT…
Browse files Browse the repository at this point in the history
…ransferHistory (#1823)

This commit introduces a new struct called UriMeta that covers the necessary info about a token: image, token_name, description, attributes, and animation_url. It also adds new fields to NftTransferHistory such as collection_name and status. The collection_name field shows the name of the collection that the token belongs to. The status field indicates whether the transfer status is Receive or Send.
  • Loading branch information
laruh authored May 25, 2023
1 parent b2daae8 commit 1e2ec29
Show file tree
Hide file tree
Showing 5 changed files with 138 additions and 43 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

**Enhancements/Fixes:**
- Remove deprecated dependency `wasm-timer` from mm2 tree [#1836](https://github.com/KomodoPlatform/atomicDEX-API/pull/1836)
- UriMeta to get info from token uri, status and metadata in nft tx history were added [#1823](https://github.com/KomodoPlatform/atomicDEX-API/pull/1823)

## v1.0.4-beta - 2023-05-23

Expand Down
67 changes: 55 additions & 12 deletions mm2src/coins/nft.rs
Original file line number Diff line number Diff line change
@@ -1,19 +1,21 @@
use mm2_core::mm_ctx::MmArc;
use mm2_err_handle::prelude::{MmError, MmResult};
use std::str::FromStr;

pub(crate) mod nft_errors;
pub(crate) mod nft_structs;
#[cfg(any(test, target_arch = "wasm32"))] mod nft_tests;

use crate::WithdrawError;
use nft_errors::GetNftInfoError;
use nft_errors::{GetInfoFromUriError, GetNftInfoError};
use nft_structs::{ConvertChain, Nft, NftList, NftListReq, NftMetadataReq, NftTransferHistory,
NftTransferHistoryWrapper, NftTransfersReq, NftWrapper, NftsTransferHistoryList,
TransactionNftDetails, WithdrawNftReq};

use crate::eth::{get_eth_address, withdraw_erc1155, withdraw_erc721};
use crate::nft::nft_structs::WithdrawNftType;
use crate::nft::nft_structs::{TransferStatus, UriMeta, WithdrawNftType};
use common::APPLICATION_JSON;
use ethereum_types::Address;
use http::header::ACCEPT;
use mm2_err_handle::map_to_mm::MapToMmResult;
use mm2_number::BigDecimal;
Expand Down Expand Up @@ -52,10 +54,11 @@ pub async fn get_nft_list(ctx: MmArc, req: NftListReq) -> MmResult<NftList, GetN
let mut cursor = String::new();
loop {
let uri = format!("{}{}", uri_without_cursor, cursor);
let response = send_moralis_request(uri.as_str()).await?;
let response = send_request_to_uri(uri.as_str()).await?;
if let Some(nfts_list) = response["result"].as_array() {
for nft_json in nfts_list {
let nft_wrapper: NftWrapper = serde_json::from_str(&nft_json.to_string())?;
let uri_meta = try_get_uri_meta(&nft_wrapper.token_uri).await?;
let nft = Nft {
chain,
token_address: nft_wrapper.token_address,
Expand All @@ -66,14 +69,15 @@ pub async fn get_nft_list(ctx: MmArc, req: NftListReq) -> MmResult<NftList, GetN
block_number_minted: *nft_wrapper.block_number_minted,
block_number: *nft_wrapper.block_number,
contract_type: nft_wrapper.contract_type.map(|v| v.0),
name: nft_wrapper.name,
collection_name: nft_wrapper.name,
symbol: nft_wrapper.symbol,
token_uri: nft_wrapper.token_uri,
metadata: nft_wrapper.metadata,
last_token_uri_sync: nft_wrapper.last_token_uri_sync,
last_metadata_sync: nft_wrapper.last_metadata_sync,
minter_address: nft_wrapper.minter_address,
possible_spam: nft_wrapper.possible_spam,
uri_meta,
};
// collect NFTs from the page
res_list.push(nft);
Expand Down Expand Up @@ -115,8 +119,9 @@ pub async fn get_nft_metadata(_ctx: MmArc, req: NftMetadataReq) -> MmResult<Nft,
.append_pair(MORALIS_FORMAT_QUERY_NAME, MORALIS_FORMAT_QUERY_VALUE);
drop_mutability!(uri);

let response = send_moralis_request(uri.as_str()).await?;
let response = send_request_to_uri(uri.as_str()).await?;
let nft_wrapper: NftWrapper = serde_json::from_str(&response.to_string())?;
let uri_meta = try_get_uri_meta(&nft_wrapper.token_uri).await?;
let nft_metadata = Nft {
chain: req.chain,
token_address: nft_wrapper.token_address,
Expand All @@ -127,14 +132,15 @@ pub async fn get_nft_metadata(_ctx: MmArc, req: NftMetadataReq) -> MmResult<Nft,
block_number_minted: *nft_wrapper.block_number_minted,
block_number: *nft_wrapper.block_number,
contract_type: nft_wrapper.contract_type.map(|v| v.0),
name: nft_wrapper.name,
collection_name: nft_wrapper.name,
symbol: nft_wrapper.symbol,
token_uri: nft_wrapper.token_uri,
metadata: nft_wrapper.metadata,
last_token_uri_sync: nft_wrapper.last_token_uri_sync,
last_metadata_sync: nft_wrapper.last_metadata_sync,
minter_address: nft_wrapper.minter_address,
possible_spam: nft_wrapper.possible_spam,
uri_meta,
};
Ok(nft_metadata)
}
Expand Down Expand Up @@ -164,12 +170,22 @@ pub async fn get_nft_transfers(ctx: MmArc, req: NftTransfersReq) -> MmResult<Nft

// The cursor returned in the previous response (used for getting the next page).
let mut cursor = String::new();
let wallet_address = my_address.wallet_address;
loop {
let uri = format!("{}{}", uri_without_cursor, cursor);
let response = send_moralis_request(uri.as_str()).await?;
let response = send_request_to_uri(uri.as_str()).await?;
if let Some(transfer_list) = response["result"].as_array() {
for transfer in transfer_list {
let transfer_wrapper: NftTransferHistoryWrapper = serde_json::from_str(&transfer.to_string())?;
let status = get_tx_status(&wallet_address, &transfer_wrapper.to_address);
let req = NftMetadataReq {
token_address: Address::from_str(&transfer_wrapper.token_address)
.map_to_mm(|e| GetNftInfoError::AddressError(e.to_string()))?,
token_id: transfer_wrapper.token_id.clone(),
chain,
url: req.url.clone(),
};
let nft_meta = get_nft_metadata(ctx.clone(), req).await?;
let transfer_history = NftTransferHistory {
chain,
block_number: *transfer_wrapper.block_number,
Expand All @@ -183,8 +199,12 @@ pub async fn get_nft_transfers(ctx: MmArc, req: NftTransfersReq) -> MmResult<Nft
transaction_type: transfer_wrapper.transaction_type,
token_address: transfer_wrapper.token_address,
token_id: transfer_wrapper.token_id.0,
collection_name: nft_meta.collection_name,
image: nft_meta.uri_meta.image,
token_name: nft_meta.uri_meta.token_name,
from_address: transfer_wrapper.from_address,
to_address: transfer_wrapper.to_address,
status,
amount: transfer_wrapper.amount.0,
verified: transfer_wrapper.verified,
operator: transfer_wrapper.operator,
Expand Down Expand Up @@ -222,7 +242,7 @@ pub async fn withdraw_nft(ctx: MmArc, req: WithdrawNftReq) -> WithdrawNftResult
}

#[cfg(not(target_arch = "wasm32"))]
async fn send_moralis_request(uri: &str) -> MmResult<Json, GetNftInfoError> {
async fn send_request_to_uri(uri: &str) -> MmResult<Json, GetInfoFromUriError> {
use http::header::HeaderValue;
use mm2_net::transport::slurp_req_body;

Expand All @@ -234,7 +254,7 @@ async fn send_moralis_request(uri: &str) -> MmResult<Json, GetNftInfoError> {

let (status, _header, body) = slurp_req_body(request).await?;
if !status.is_success() {
return Err(MmError::new(GetNftInfoError::Transport(format!(
return Err(MmError::new(GetInfoFromUriError::Transport(format!(
"Response !200 from {}: {}, {}",
uri, status, body
))));
Expand All @@ -243,14 +263,14 @@ async fn send_moralis_request(uri: &str) -> MmResult<Json, GetNftInfoError> {
}

#[cfg(target_arch = "wasm32")]
async fn send_moralis_request(uri: &str) -> MmResult<Json, GetNftInfoError> {
async fn send_request_to_uri(uri: &str) -> MmResult<Json, GetInfoFromUriError> {
use mm2_net::wasm_http::FetchRequest;

macro_rules! try_or {
($exp:expr, $errtype:ident) => {
match $exp {
Ok(x) => x,
Err(e) => return Err(MmError::new(GetNftInfoError::$errtype(ERRL!("{:?}", e)))),
Err(e) => return Err(MmError::new(GetInfoFromUriError::$errtype(ERRL!("{:?}", e)))),
}
};
}
Expand All @@ -261,7 +281,7 @@ async fn send_moralis_request(uri: &str) -> MmResult<Json, GetNftInfoError> {
.await;
let (status_code, response_str) = try_or!(result, Transport);
if !status_code.is_success() {
return Err(MmError::new(GetNftInfoError::Transport(ERRL!(
return Err(MmError::new(GetInfoFromUriError::Transport(ERRL!(
"!200: {}, {}",
status_code,
response_str
Expand Down Expand Up @@ -289,3 +309,26 @@ pub(crate) async fn find_wallet_amount(
})?;
Ok(nft.amount)
}

async fn try_get_uri_meta(token_uri: &Option<String>) -> MmResult<UriMeta, GetNftInfoError> {
match token_uri {
Some(token_uri) => {
if let Ok(response_meta) = send_request_to_uri(token_uri).await {
let uri_meta_res: UriMeta = serde_json::from_str(&response_meta.to_string())?;
Ok(uri_meta_res)
} else {
Ok(UriMeta::default())
}
},
None => Ok(UriMeta::default()),
}
}

fn get_tx_status(my_wallet: &str, to_address: &str) -> TransferStatus {
// if my_wallet == from_address && my_wallet == to_address it is incoming tx, so we can check just to_address.
if my_wallet.to_lowercase() == to_address.to_lowercase() {
TransferStatus::Receive
} else {
TransferStatus::Send
}
}
54 changes: 40 additions & 14 deletions mm2src/coins/nft/nft_errors.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,6 @@ use web3::Error;
#[derive(Clone, Debug, Deserialize, Display, EnumFromStringify, PartialEq, Serialize, SerializeErrorType)]
#[serde(tag = "error_type", content = "error_data")]
pub enum GetNftInfoError {
/// `http::Error` can appear on an HTTP request [`http::Builder::build`] building.
#[from_stringify("http::Error")]
#[display(fmt = "Invalid request: {}", _0)]
InvalidRequest(String),
#[display(fmt = "Transport: {}", _0)]
Expand All @@ -31,17 +29,7 @@ pub enum GetNftInfoError {
token_address: String,
token_id: String,
},
}

impl From<SlurpError> for GetNftInfoError {
fn from(e: SlurpError) -> Self {
let error_str = e.to_string();
match e {
SlurpError::ErrorDeserializing { .. } => GetNftInfoError::InvalidResponse(error_str),
SlurpError::Transport { .. } | SlurpError::Timeout { .. } => GetNftInfoError::Transport(error_str),
SlurpError::Internal(_) | SlurpError::InvalidRequest(_) => GetNftInfoError::Internal(error_str),
}
}
AddressError(String),
}

impl From<web3::Error> for GetNftInfoError {
Expand All @@ -61,6 +49,17 @@ impl From<GetEthAddressError> for GetNftInfoError {
fn from(e: GetEthAddressError) -> Self { GetNftInfoError::GetEthAddressError(e) }
}

impl From<GetInfoFromUriError> for GetNftInfoError {
fn from(e: GetInfoFromUriError) -> Self {
match e {
GetInfoFromUriError::InvalidRequest(e) => GetNftInfoError::InvalidRequest(e),
GetInfoFromUriError::Transport(e) => GetNftInfoError::Transport(e),
GetInfoFromUriError::InvalidResponse(e) => GetNftInfoError::InvalidResponse(e),
GetInfoFromUriError::Internal(e) => GetNftInfoError::Internal(e),
}
}
}

impl HttpStatusCode for GetNftInfoError {
fn status_code(&self) -> StatusCode {
match self {
Expand All @@ -69,7 +68,34 @@ impl HttpStatusCode for GetNftInfoError {
GetNftInfoError::Transport(_)
| GetNftInfoError::Internal(_)
| GetNftInfoError::GetEthAddressError(_)
| GetNftInfoError::TokenNotFoundInWallet { .. } => StatusCode::INTERNAL_SERVER_ERROR,
| GetNftInfoError::TokenNotFoundInWallet { .. }
| GetNftInfoError::AddressError(_) => StatusCode::INTERNAL_SERVER_ERROR,
}
}
}

#[derive(Clone, Debug, Deserialize, Display, EnumFromStringify, PartialEq, Serialize)]
pub(crate) enum GetInfoFromUriError {
/// `http::Error` can appear on an HTTP request [`http::Builder::build`] building.
#[from_stringify("http::Error")]
#[display(fmt = "Invalid request: {}", _0)]
InvalidRequest(String),
#[display(fmt = "Transport: {}", _0)]
Transport(String),
#[from_stringify("serde_json::Error")]
#[display(fmt = "Invalid response: {}", _0)]
InvalidResponse(String),
#[display(fmt = "Internal: {}", _0)]
Internal(String),
}

impl From<SlurpError> for GetInfoFromUriError {
fn from(e: SlurpError) -> Self {
let error_str = e.to_string();
match e {
SlurpError::ErrorDeserializing { .. } => GetInfoFromUriError::InvalidResponse(error_str),
SlurpError::Transport { .. } | SlurpError::Timeout { .. } => GetInfoFromUriError::Transport(error_str),
SlurpError::Internal(_) | SlurpError::InvalidRequest(_) => GetInfoFromUriError::Internal(error_str),
}
}
}
34 changes: 28 additions & 6 deletions mm2src/coins/nft/nft_structs.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ use ethereum_types::Address;
use mm2_number::BigDecimal;
use rpc::v1::types::Bytes as BytesJson;
use serde::Deserialize;
use serde_json::Value as Json;
use std::fmt;
use std::str::FromStr;
use url::Url;
Expand Down Expand Up @@ -34,11 +35,11 @@ pub(crate) enum Chain {
impl fmt::Display for Chain {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match &self {
Chain::Avalanche => write!(f, "avalanche"),
Chain::Bsc => write!(f, "bsc"),
Chain::Eth => write!(f, "eth"),
Chain::Fantom => write!(f, "fantom"),
Chain::Polygon => write!(f, "polygon"),
Chain::Avalanche => write!(f, "AVALANCHE"),
Chain::Bsc => write!(f, "BSC"),
Chain::Eth => write!(f, "ETH"),
Chain::Fantom => write!(f, "FANTOM"),
Chain::Polygon => write!(f, "POLYGON"),
}
}
}
Expand Down Expand Up @@ -84,6 +85,16 @@ impl FromStr for ContractType {
}
}

#[derive(Debug, Default, Deserialize, Serialize)]
pub(crate) struct UriMeta {
pub(crate) image: Option<String>,
#[serde(rename(deserialize = "name"))]
pub(crate) token_name: Option<String>,
description: Option<String>,
attributes: Option<Json>,
animation_url: Option<String>,
}

#[derive(Debug, Serialize)]
pub struct Nft {
pub(crate) chain: Chain,
Expand All @@ -95,14 +106,15 @@ pub struct Nft {
pub(crate) block_number_minted: u64,
pub(crate) block_number: u64,
pub(crate) contract_type: Option<ContractType>,
pub(crate) name: Option<String>,
pub(crate) collection_name: Option<String>,
pub(crate) symbol: Option<String>,
pub(crate) token_uri: Option<String>,
pub(crate) metadata: Option<String>,
pub(crate) last_token_uri_sync: Option<String>,
pub(crate) last_metadata_sync: Option<String>,
pub(crate) minter_address: Option<String>,
pub(crate) possible_spam: Option<bool>,
pub(crate) uri_meta: UriMeta,
}

/// This structure is for deserializing NFT json to struct.
Expand Down Expand Up @@ -223,6 +235,12 @@ pub struct NftTransfersReq {
pub(crate) url: Url,
}

#[derive(Debug, Deserialize, Serialize)]
pub(crate) enum TransferStatus {
Receive,
Send,
}

#[derive(Debug, Serialize)]
pub(crate) struct NftTransferHistory {
pub(crate) chain: Chain,
Expand All @@ -238,8 +256,12 @@ pub(crate) struct NftTransferHistory {
pub(crate) transaction_type: String,
pub(crate) token_address: String,
pub(crate) token_id: BigDecimal,
pub(crate) collection_name: Option<String>,
pub(crate) image: Option<String>,
pub(crate) token_name: Option<String>,
pub(crate) from_address: String,
pub(crate) to_address: String,
pub(crate) status: TransferStatus,
pub(crate) amount: BigDecimal,
pub(crate) verified: u64,
pub(crate) operator: Option<String>,
Expand Down
Loading

0 comments on commit 1e2ec29

Please sign in to comment.