diff --git a/CHANGELOG.md b/CHANGELOG.md index 8d11b74fc3..1c0da2d553 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -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 diff --git a/mm2src/coins/nft.rs b/mm2src/coins/nft.rs index e6482bd1c8..eb4ea72b33 100644 --- a/mm2src/coins/nft.rs +++ b/mm2src/coins/nft.rs @@ -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; @@ -52,10 +54,11 @@ pub async fn get_nft_list(ctx: MmArc, req: NftListReq) -> MmResult MmResult MmResult MmResult MmResult MmResult MmResult MmResult WithdrawNftResult } #[cfg(not(target_arch = "wasm32"))] -async fn send_moralis_request(uri: &str) -> MmResult { +async fn send_request_to_uri(uri: &str) -> MmResult { use http::header::HeaderValue; use mm2_net::transport::slurp_req_body; @@ -234,7 +254,7 @@ async fn send_moralis_request(uri: &str) -> MmResult { 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 )))); @@ -243,14 +263,14 @@ async fn send_moralis_request(uri: &str) -> MmResult { } #[cfg(target_arch = "wasm32")] -async fn send_moralis_request(uri: &str) -> MmResult { +async fn send_request_to_uri(uri: &str) -> MmResult { 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)))), } }; } @@ -261,7 +281,7 @@ async fn send_moralis_request(uri: &str) -> MmResult { .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 @@ -289,3 +309,26 @@ pub(crate) async fn find_wallet_amount( })?; Ok(nft.amount) } + +async fn try_get_uri_meta(token_uri: &Option) -> MmResult { + 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 + } +} diff --git a/mm2src/coins/nft/nft_errors.rs b/mm2src/coins/nft/nft_errors.rs index 44cad297e0..1ca9d054c8 100644 --- a/mm2src/coins/nft/nft_errors.rs +++ b/mm2src/coins/nft/nft_errors.rs @@ -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)] @@ -31,17 +29,7 @@ pub enum GetNftInfoError { token_address: String, token_id: String, }, -} - -impl From 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 for GetNftInfoError { @@ -61,6 +49,17 @@ impl From for GetNftInfoError { fn from(e: GetEthAddressError) -> Self { GetNftInfoError::GetEthAddressError(e) } } +impl From 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 { @@ -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 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), } } } diff --git a/mm2src/coins/nft/nft_structs.rs b/mm2src/coins/nft/nft_structs.rs index f50ac7f379..11b01b844c 100644 --- a/mm2src/coins/nft/nft_structs.rs +++ b/mm2src/coins/nft/nft_structs.rs @@ -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; @@ -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"), } } } @@ -84,6 +85,16 @@ impl FromStr for ContractType { } } +#[derive(Debug, Default, Deserialize, Serialize)] +pub(crate) struct UriMeta { + pub(crate) image: Option, + #[serde(rename(deserialize = "name"))] + pub(crate) token_name: Option, + description: Option, + attributes: Option, + animation_url: Option, +} + #[derive(Debug, Serialize)] pub struct Nft { pub(crate) chain: Chain, @@ -95,7 +106,7 @@ pub struct Nft { pub(crate) block_number_minted: u64, pub(crate) block_number: u64, pub(crate) contract_type: Option, - pub(crate) name: Option, + pub(crate) collection_name: Option, pub(crate) symbol: Option, pub(crate) token_uri: Option, pub(crate) metadata: Option, @@ -103,6 +114,7 @@ pub struct Nft { pub(crate) last_metadata_sync: Option, pub(crate) minter_address: Option, pub(crate) possible_spam: Option, + pub(crate) uri_meta: UriMeta, } /// This structure is for deserializing NFT json to struct. @@ -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, @@ -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, + pub(crate) image: Option, + pub(crate) token_name: Option, 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, diff --git a/mm2src/coins/nft/nft_tests.rs b/mm2src/coins/nft/nft_tests.rs index 6c6640cb99..4ab868bad0 100644 --- a/mm2src/coins/nft/nft_tests.rs +++ b/mm2src/coins/nft/nft_tests.rs @@ -5,14 +5,14 @@ const TEST_WALLET_ADDR_EVM: &str = "0x394d86994f954ed931b86791b62fe64f4c5dac37"; #[cfg(all(test, not(target_arch = "wasm32")))] mod native_tests { - use crate::nft::nft_structs::{NftTransferHistoryWrapper, NftWrapper}; + use crate::nft::nft_structs::{NftTransferHistoryWrapper, NftWrapper, UriMeta}; use crate::nft::nft_tests::{NFT_HISTORY_URL_TEST, NFT_LIST_URL_TEST, NFT_METADATA_URL_TEST, TEST_WALLET_ADDR_EVM}; - use crate::nft::send_moralis_request; + use crate::nft::send_request_to_uri; use common::block_on; #[test] fn test_moralis_nft_list() { - let response = block_on(send_moralis_request(NFT_LIST_URL_TEST)).unwrap(); + let response = block_on(send_request_to_uri(NFT_LIST_URL_TEST)).unwrap(); let nfts_list = response["result"].as_array().unwrap(); assert_eq!(2, nfts_list.len()); for nft_json in nfts_list { @@ -23,7 +23,7 @@ mod native_tests { #[test] fn test_moralis_nft_transfer_history() { - let response = block_on(send_moralis_request(NFT_HISTORY_URL_TEST)).unwrap(); + let response = block_on(send_request_to_uri(NFT_HISTORY_URL_TEST)).unwrap(); let mut transfer_list = response["result"].as_array().unwrap().clone(); assert_eq!(4, transfer_list.len()); let last_tx = transfer_list.remove(0); @@ -33,9 +33,12 @@ mod native_tests { #[test] fn test_moralis_nft_metadata() { - let response = block_on(send_moralis_request(NFT_METADATA_URL_TEST)).unwrap(); + let response = block_on(send_request_to_uri(NFT_METADATA_URL_TEST)).unwrap(); let nft_wrapper: NftWrapper = serde_json::from_str(&response.to_string()).unwrap(); - assert_eq!(41237364, *nft_wrapper.block_number_minted) + assert_eq!(41237364, *nft_wrapper.block_number_minted); + let token_uri = nft_wrapper.token_uri.unwrap(); + let uri_response = block_on(send_request_to_uri(token_uri.as_str())).unwrap(); + serde_json::from_str::(&uri_response.to_string()).unwrap(); } } @@ -43,14 +46,14 @@ mod native_tests { mod wasm_tests { use crate::nft::nft_structs::{NftTransferHistoryWrapper, NftWrapper}; use crate::nft::nft_tests::{NFT_HISTORY_URL_TEST, NFT_LIST_URL_TEST, NFT_METADATA_URL_TEST, TEST_WALLET_ADDR_EVM}; - use crate::nft::send_moralis_request; + use crate::nft::send_request_to_uri; use wasm_bindgen_test::*; wasm_bindgen_test_configure!(run_in_browser); #[wasm_bindgen_test] async fn test_moralis_nft_list() { - let response = send_moralis_request(NFT_LIST_URL_TEST).await.unwrap(); + let response = send_request_to_uri(NFT_LIST_URL_TEST).await.unwrap(); let nfts_list = response["result"].as_array().unwrap(); assert_eq!(2, nfts_list.len()); for nft_json in nfts_list { @@ -61,7 +64,7 @@ mod wasm_tests { #[wasm_bindgen_test] async fn test_moralis_nft_transfer_history() { - let response = send_moralis_request(NFT_HISTORY_URL_TEST).await.unwrap(); + let response = send_request_to_uri(NFT_HISTORY_URL_TEST).await.unwrap(); let mut transfer_list = response["result"].as_array().unwrap().clone(); assert_eq!(4, transfer_list.len()); let last_tx = transfer_list.remove(0); @@ -71,8 +74,8 @@ mod wasm_tests { #[wasm_bindgen_test] async fn test_moralis_nft_metadata() { - let response = send_moralis_request(NFT_METADATA_URL_TEST).await.unwrap(); + let response = send_request_to_uri(NFT_METADATA_URL_TEST).await.unwrap(); let nft_wrapper: NftWrapper = serde_json::from_str(&response.to_string()).unwrap(); - assert_eq!(41237364, *nft_wrapper.block_number_minted) + assert_eq!(41237364, *nft_wrapper.block_number_minted); } }