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

Impl UriMeta, add meta info and status in tx history #1823

Merged
merged 20 commits into from
May 25, 2023
Merged
Show file tree
Hide file tree
Changes from 19 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,10 @@
## v1.0.5-beta - 2023-06-06

**Features:**

**Enhancements/Fixes:**
- 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

**Features:**
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