diff --git a/Cargo.lock b/Cargo.lock
index 1911e05d20..2c282d6064 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -1740,10 +1740,13 @@ name = "db_common"
version = "0.1.0"
dependencies = [
"common",
+ "crossbeam-channel 0.5.1",
+ "futures 0.3.28",
"hex 0.4.3",
"log",
"rusqlite",
"sql-builder",
+ "tokio",
"uuid 1.2.2",
]
diff --git a/mm2src/coins/eth.rs b/mm2src/coins/eth.rs
index c69ce6981f..331e185b65 100644
--- a/mm2src/coins/eth.rs
+++ b/mm2src/coins/eth.rs
@@ -422,7 +422,7 @@ pub struct EthCoinImpl {
swap_contract_address: Address,
fallback_swap_contract: Option
,
contract_supports_watchers: bool,
- web3: Web3,
+ pub(crate) web3: Web3,
/// The separate web3 instances kept to get nonce, will replace the web3 completely soon
web3_instances: Vec,
decimals: u8,
@@ -875,7 +875,7 @@ async fn withdraw_impl(coin: EthCoin, req: WithdrawRequest) -> WithdrawResult {
/// `withdraw_erc1155` function returns details of `ERC-1155` transaction including tx hex,
/// which should be sent to`send_raw_transaction` RPC to broadcast the transaction.
pub async fn withdraw_erc1155(ctx: MmArc, withdraw_type: WithdrawErc1155) -> WithdrawNftResult {
- let coin = lp_coinfind_or_err(&ctx, &withdraw_type.chain.to_ticker()).await?;
+ let coin = lp_coinfind_or_err(&ctx, withdraw_type.chain.to_ticker()).await?;
let (to_addr, token_addr, eth_coin) =
get_valid_nft_add_to_withdraw(coin, &withdraw_type.to, &withdraw_type.token_address)?;
let my_address = eth_coin.my_address()?;
@@ -977,7 +977,7 @@ pub async fn withdraw_erc1155(ctx: MmArc, withdraw_type: WithdrawErc1155) -> Wit
/// `withdraw_erc721` function returns details of `ERC-721` transaction including tx hex,
/// which should be sent to`send_raw_transaction` RPC to broadcast the transaction.
pub async fn withdraw_erc721(ctx: MmArc, withdraw_type: WithdrawErc721) -> WithdrawNftResult {
- let coin = lp_coinfind_or_err(&ctx, &withdraw_type.chain.to_ticker()).await?;
+ let coin = lp_coinfind_or_err(&ctx, withdraw_type.chain.to_ticker()).await?;
let (to_addr, token_addr, eth_coin) =
get_valid_nft_add_to_withdraw(coin, &withdraw_type.to, &withdraw_type.token_address)?;
let my_address = eth_coin.my_address()?;
@@ -4713,7 +4713,7 @@ pub struct EthTxFeeDetails {
}
impl EthTxFeeDetails {
- fn new(gas: U256, gas_price: U256, coin: &str) -> NumConversResult {
+ pub(crate) fn new(gas: U256, gas_price: U256, coin: &str) -> NumConversResult {
let total_fee = gas * gas_price;
// Fees are always paid in ETH, can use 18 decimals by default
let total_fee = u256_to_big_decimal(total_fee, ETH_DECIMALS)?;
diff --git a/mm2src/coins/my_tx_history_v2.rs b/mm2src/coins/my_tx_history_v2.rs
index 1635be9e80..97c5a5ca8f 100644
--- a/mm2src/coins/my_tx_history_v2.rs
+++ b/mm2src/coins/my_tx_history_v2.rs
@@ -491,7 +491,7 @@ where
_ => {},
};
- let confirmations = if details.block_height == 0 || details.block_height > current_block {
+ let confirmations = if details.block_height > current_block {
0
} else {
current_block + 1 - details.block_height
diff --git a/mm2src/coins/nft.rs b/mm2src/coins/nft.rs
index 0f0cb942e0..40736a6414 100644
--- a/mm2src/coins/nft.rs
+++ b/mm2src/coins/nft.rs
@@ -8,29 +8,34 @@ pub(crate) mod storage;
#[cfg(any(test, target_arch = "wasm32"))] mod nft_tests;
-use crate::{coin_conf, get_my_address, MyAddressReq, WithdrawError};
+use crate::{coin_conf, get_my_address, lp_coinfind_or_err, MarketCoinOps, MmCoinEnum, MyAddressReq, WithdrawError};
use nft_errors::{GetNftInfoError, UpdateNftError};
use nft_structs::{Chain, ContractType, ConvertChain, Nft, NftFromMoralis, NftList, NftListReq, NftMetadataReq,
NftTransferHistory, NftTransferHistoryFromMoralis, NftTransfersReq, NftsTransferHistoryList,
TransactionNftDetails, UpdateNftReq, WithdrawNftReq};
-use crate::eth::{eth_addr_to_hex, get_eth_address, withdraw_erc1155, withdraw_erc721};
-use crate::nft::nft_errors::{MetaFromUrlError, ProtectFromSpamError, UpdateSpamPhishingError};
+use crate::eth::{eth_addr_to_hex, get_eth_address, withdraw_erc1155, withdraw_erc721, EthCoin, EthCoinType,
+ EthTxFeeDetails};
+use crate::nft::nft_errors::{MetaFromUrlError, ProtectFromSpamError, TransferConfirmationsError,
+ UpdateSpamPhishingError};
use crate::nft::nft_structs::{build_nft_with_empty_meta, BuildNftFields, NftCommon, NftCtx, NftTransferCommon,
PhishingDomainReq, PhishingDomainRes, RefreshMetadataReq, SpamContractReq,
SpamContractRes, TransferMeta, TransferStatus, UriMeta};
-use crate::nft::storage::{NftListStorageOps, NftStorageBuilder, NftTransferHistoryStorageOps};
+use crate::nft::storage::{NftListStorageOps, NftTransferHistoryStorageOps};
use common::parse_rfc3339_to_timestamp;
use crypto::StandardHDCoinAddress;
-use ethereum_types::Address;
+use ethereum_types::{Address, H256};
+use futures::compat::Future01CompatExt;
+use futures::future::try_join_all;
use mm2_err_handle::map_to_mm::MapToMmResult;
use mm2_net::transport::send_post_request_to_uri;
-use mm2_number::BigDecimal;
+use mm2_number::{BigDecimal, BigUint};
use regex::Regex;
use serde_json::Value as Json;
use std::cmp::Ordering;
-use std::collections::HashSet;
+use std::collections::{HashMap, HashSet};
use std::str::FromStr;
+use web3::types::TransactionId;
#[cfg(not(target_arch = "wasm32"))]
use mm2_net::native_http::send_request_to_uri;
@@ -74,9 +79,8 @@ pub type WithdrawNftResult = Result MmResult {
let nft_ctx = NftCtx::from_ctx(&ctx).map_to_mm(GetNftInfoError::Internal)?;
- let _lock = nft_ctx.guard.lock().await;
- let storage = NftStorageBuilder::new(&ctx).build()?;
+ let storage = nft_ctx.lock_db().await?;
for chain in req.chains.iter() {
if !NftListStorageOps::is_initialized(&storage, chain).await? {
NftListStorageOps::init(&storage, chain).await?;
@@ -114,9 +118,8 @@ pub async fn get_nft_list(ctx: MmArc, req: NftListReq) -> MmResult MmResult {
let nft_ctx = NftCtx::from_ctx(&ctx).map_to_mm(GetNftInfoError::Internal)?;
- let _lock = nft_ctx.guard.lock().await;
- let storage = NftStorageBuilder::new(&ctx).build()?;
+ let storage = nft_ctx.lock_db().await?;
if !NftListStorageOps::is_initialized(&storage, &req.chain).await? {
NftListStorageOps::init(&storage, &req.chain).await?;
}
@@ -156,26 +159,67 @@ pub async fn get_nft_metadata(ctx: MmArc, req: NftMetadataReq) -> MmResult MmResult {
let nft_ctx = NftCtx::from_ctx(&ctx).map_to_mm(GetNftInfoError::Internal)?;
- let _lock = nft_ctx.guard.lock().await;
- let storage = NftStorageBuilder::new(&ctx).build()?;
+ let storage = nft_ctx.lock_db().await?;
for chain in req.chains.iter() {
if !NftTransferHistoryStorageOps::is_initialized(&storage, chain).await? {
NftTransferHistoryStorageOps::init(&storage, chain).await?;
}
}
let mut transfer_history_list = storage
- .get_transfer_history(req.chains, req.max, req.limit, req.page_number, req.filters)
+ .get_transfer_history(req.chains.clone(), req.max, req.limit, req.page_number, req.filters)
.await?;
if req.protect_from_spam {
for transfer in &mut transfer_history_list.transfer_history {
protect_from_history_spam_links(transfer, true)?;
}
}
+ process_transfers_confirmations(&ctx, req.chains, &mut transfer_history_list).await?;
drop_mutability!(transfer_history_list);
Ok(transfer_history_list)
}
+async fn process_transfers_confirmations(
+ ctx: &MmArc,
+ chains: Vec,
+ history_list: &mut NftsTransferHistoryList,
+) -> MmResult<(), TransferConfirmationsError> {
+ async fn current_block_impl(coin: Coin) -> MmResult {
+ coin.current_block()
+ .compat()
+ .await
+ .map_to_mm(TransferConfirmationsError::GetCurrentBlockErr)
+ }
+
+ let futures = chains.into_iter().map(|chain| async move {
+ let ticker = chain.to_ticker();
+ let coin_enum = lp_coinfind_or_err(ctx, ticker).await?;
+ match coin_enum {
+ MmCoinEnum::EthCoin(eth_coin) => {
+ let current_block = current_block_impl(eth_coin).await?;
+ Ok((ticker, current_block))
+ },
+ _ => MmError::err(TransferConfirmationsError::CoinDoesntSupportNft {
+ coin: coin_enum.ticker().to_owned(),
+ }),
+ }
+ });
+ let blocks_map = try_join_all(futures).await?.into_iter().collect::>();
+
+ for transfer in history_list.transfer_history.iter_mut() {
+ let current_block = match blocks_map.get(transfer.chain.to_ticker()) {
+ Some(block) => *block,
+ None => 0,
+ };
+ transfer.confirmations = if transfer.block_number > current_block {
+ 0
+ } else {
+ current_block + 1 - transfer.block_number
+ };
+ }
+ Ok(())
+}
+
/// Updates NFT transfer history and NFT list in the DB.
///
/// This function refreshes the NFT transfer history and NFT list cache based on new
@@ -192,9 +236,8 @@ pub async fn get_nft_transfers(ctx: MmArc, req: NftTransfersReq) -> MmResult`: A result indicating success or an error.
pub async fn update_nft(ctx: MmArc, req: UpdateNftReq) -> MmResult<(), UpdateNftError> {
let nft_ctx = NftCtx::from_ctx(&ctx).map_to_mm(GetNftInfoError::Internal)?;
- let _lock = nft_ctx.guard.lock().await;
- let storage = NftStorageBuilder::new(&ctx).build()?;
+ let storage = nft_ctx.lock_db().await?;
for chain in req.chains.iter() {
let transfer_history_initialized = NftTransferHistoryStorageOps::is_initialized(&storage, chain).await?;
@@ -205,7 +248,16 @@ pub async fn update_nft(ctx: MmArc, req: UpdateNftReq) -> MmResult<(), UpdateNft
NftTransferHistoryStorageOps::init(&storage, chain).await?;
None
};
- let nft_transfers = get_moralis_nft_transfers(&ctx, chain, from_block, &req.url).await?;
+ let coin_enum = lp_coinfind_or_err(&ctx, chain.to_ticker()).await?;
+ let eth_coin = match coin_enum {
+ MmCoinEnum::EthCoin(eth_coin) => eth_coin,
+ _ => {
+ return MmError::err(UpdateNftError::CoinDoesntSupportNft {
+ coin: coin_enum.ticker().to_owned(),
+ })
+ },
+ };
+ let nft_transfers = get_moralis_nft_transfers(&ctx, chain, from_block, &req.url, eth_coin).await?;
storage.add_transfers_to_history(*chain, nft_transfers).await?;
let nft_block = match NftListStorageOps::get_last_block_number(&storage, chain).await {
@@ -377,9 +429,8 @@ fn prepare_uri_for_blocklist_endpoint(
/// * `MmResult<(), UpdateNftError>`: A result indicating success or an error.
pub async fn refresh_nft_metadata(ctx: MmArc, req: RefreshMetadataReq) -> MmResult<(), UpdateNftError> {
let nft_ctx = NftCtx::from_ctx(&ctx).map_to_mm(GetNftInfoError::Internal)?;
- let _lock = nft_ctx.guard.lock().await;
- let storage = NftStorageBuilder::new(&ctx).build()?;
+ let storage = nft_ctx.lock_db().await?;
let token_address_str = eth_addr_to_hex(&req.token_address);
let moralis_meta = match get_moralis_metadata(
token_address_str.clone(),
@@ -532,8 +583,8 @@ async fn get_moralis_nft_list(
) -> MmResult, GetNftInfoError> {
let mut res_list = Vec::new();
let ticker = chain.to_ticker();
- let conf = coin_conf(ctx, &ticker);
- let my_address = get_eth_address(ctx, &conf, &ticker, &StandardHDCoinAddress::default()).await?;
+ let conf = coin_conf(ctx, ticker);
+ let my_address = get_eth_address(ctx, &conf, ticker, &StandardHDCoinAddress::default()).await?;
let mut uri_without_cursor = url.clone();
uri_without_cursor.set_path(MORALIS_API_ENDPOINT);
@@ -584,11 +635,12 @@ async fn get_moralis_nft_transfers(
chain: &Chain,
from_block: Option,
url: &Url,
+ eth_coin: EthCoin,
) -> MmResult, GetNftInfoError> {
let mut res_list = Vec::new();
let ticker = chain.to_ticker();
- let conf = coin_conf(ctx, &ticker);
- let my_address = get_eth_address(ctx, &conf, &ticker, &StandardHDCoinAddress::default()).await?;
+ let conf = coin_conf(ctx, ticker);
+ let my_address = get_eth_address(ctx, &conf, ticker, &StandardHDCoinAddress::default()).await?;
let mut uri_without_cursor = url.clone();
uri_without_cursor.set_path(MORALIS_API_ENDPOINT);
@@ -625,6 +677,7 @@ async fn get_moralis_nft_transfers(
let status =
get_transfer_status(&wallet_address, ð_addr_to_hex(&transfer_moralis.common.to_address));
let block_timestamp = parse_rfc3339_to_timestamp(&transfer_moralis.block_timestamp)?;
+ let fee_details = get_fee_details(ð_coin, &transfer_moralis.common.transaction_hash).await;
let transfer_history = NftTransferHistory {
common: NftTransferCommon {
block_hash: transfer_moralis.common.block_hash,
@@ -634,7 +687,6 @@ async fn get_moralis_nft_transfers(
value: transfer_moralis.common.value,
transaction_type: transfer_moralis.common.transaction_type,
token_address: transfer_moralis.common.token_address,
- token_id: transfer_moralis.common.token_id,
from_address: transfer_moralis.common.from_address,
to_address: transfer_moralis.common.to_address,
amount: transfer_moralis.common.amount,
@@ -643,6 +695,7 @@ async fn get_moralis_nft_transfers(
possible_spam: transfer_moralis.common.possible_spam,
},
chain: *chain,
+ token_id: transfer_moralis.token_id.0,
block_number: *transfer_moralis.block_number,
block_timestamp,
contract_type,
@@ -654,6 +707,8 @@ async fn get_moralis_nft_transfers(
token_name: None,
status,
possible_phishing: false,
+ fee_details,
+ confirmations: 0,
};
// collect NFTs transfers from the page
res_list.push(transfer_history);
@@ -672,6 +727,35 @@ async fn get_moralis_nft_transfers(
Ok(res_list)
}
+async fn get_fee_details(eth_coin: &EthCoin, transaction_hash: &str) -> Option {
+ let hash = H256::from_str(transaction_hash).ok()?;
+ let receipt = eth_coin.web3.eth().transaction_receipt(hash).await.ok()?;
+ let fee_coin = match eth_coin.coin_type {
+ EthCoinType::Eth => eth_coin.ticker(),
+ EthCoinType::Erc20 { .. } => return None,
+ };
+
+ match receipt {
+ Some(r) => {
+ let gas_used = r.gas_used.unwrap_or_default();
+ match r.effective_gas_price {
+ Some(gas_price) => EthTxFeeDetails::new(gas_used, gas_price, fee_coin).ok(),
+ None => {
+ let web3_tx = eth_coin
+ .web3
+ .eth()
+ .transaction(TransactionId::Hash(hash))
+ .await
+ .ok()??;
+ let gas_price = web3_tx.gas_price.unwrap_or_default();
+ EthTxFeeDetails::new(gas_used, gas_price, fee_coin).ok()
+ },
+ }
+ },
+ None => None,
+ }
+}
+
/// Implements request to the Moralis "Get NFT metadata" endpoint.
///
/// [Moralis Documentation Link](https://docs.moralis.io/web3-data-api/evm/reference/get-nft-metadata)
@@ -683,7 +767,7 @@ async fn get_moralis_nft_transfers(
/// **Dont** use this function to get specific info about owner address, amount etc, you will get info not related to my_address.
async fn get_moralis_metadata(
token_address: String,
- token_id: BigDecimal,
+ token_id: BigUint,
chain: &Chain,
url: &Url,
url_antispam: &Url,
@@ -804,7 +888,7 @@ async fn update_nft_list(
) -> MmResult<(), UpdateNftError> {
let transfers = storage.get_transfers_from_block(*chain, scan_from_block).await?;
let req = MyAddressReq {
- coin: chain.to_ticker(),
+ coin: chain.to_ticker().to_string(),
path_to_address: StandardHDCoinAddress::default(),
};
let my_address = get_my_address(ctx.clone(), req).await?.wallet_address.to_lowercase();
@@ -843,18 +927,18 @@ async fn handle_send_erc721
.get_nft(
chain,
eth_addr_to_hex(&transfer.common.token_address),
- transfer.common.token_id.clone(),
+ transfer.token_id.clone(),
)
.await?
.ok_or_else(|| UpdateNftError::TokenNotFoundInWallet {
token_address: eth_addr_to_hex(&transfer.common.token_address),
- token_id: transfer.common.token_id.to_string(),
+ token_id: transfer.token_id.to_string(),
})?;
storage
.remove_nft_from_list(
chain,
eth_addr_to_hex(&transfer.common.token_address),
- transfer.common.token_id,
+ transfer.token_id,
transfer.block_number,
)
.await?;
@@ -871,7 +955,7 @@ async fn handle_receive_erc721 MmResult<(), UpdateNftError> {
let token_address_str = eth_addr_to_hex(&transfer.common.token_address);
match storage
- .get_nft(chain, token_address_str.clone(), transfer.common.token_id.clone())
+ .get_nft(chain, token_address_str.clone(), transfer.token_id.clone())
.await?
{
Some(mut nft_db) => {
@@ -891,7 +975,7 @@ async fn handle_receive_erc721 {
let mut nft = match get_moralis_metadata(
token_address_str.clone(),
- transfer.common.token_id.clone(),
+ transfer.token_id.clone(),
chain,
url,
url_antispam,
@@ -926,21 +1010,16 @@ async fn handle_send_erc1155 MmResult<(), UpdateNftError> {
let token_address_str = eth_addr_to_hex(&transfer.common.token_address);
let mut nft_db = storage
- .get_nft(chain, token_address_str.clone(), transfer.common.token_id.clone())
+ .get_nft(chain, token_address_str.clone(), transfer.token_id.clone())
.await?
.ok_or_else(|| UpdateNftError::TokenNotFoundInWallet {
token_address: token_address_str.clone(),
- token_id: transfer.common.token_id.to_string(),
+ token_id: transfer.token_id.to_string(),
})?;
match nft_db.common.amount.cmp(&transfer.common.amount) {
Ordering::Equal => {
storage
- .remove_nft_from_list(
- chain,
- token_address_str,
- transfer.common.token_id,
- transfer.block_number,
- )
+ .remove_nft_from_list(chain, token_address_str, transfer.token_id, transfer.block_number)
.await?;
},
Ordering::Greater => {
@@ -969,7 +1048,7 @@ async fn handle_receive_erc1155 MmResult<(), UpdateNftError> {
let token_address_str = eth_addr_to_hex(&transfer.common.token_address);
let mut nft = match storage
- .get_nft(chain, token_address_str.clone(), transfer.common.token_id.clone())
+ .get_nft(chain, token_address_str.clone(), transfer.token_id.clone())
.await?
{
Some(mut nft_db) => {
@@ -989,7 +1068,7 @@ async fn handle_receive_erc1155 {
let nft = match get_moralis_metadata(
token_address_str.clone(),
- transfer.common.token_id.clone(),
+ transfer.token_id.clone(),
chain,
url,
url_antispam,
@@ -1031,7 +1110,6 @@ async fn create_nft_from_moralis_metadata(
let nft = Nft {
common: NftCommon {
token_address: moralis_meta.common.token_address,
- token_id: moralis_meta.common.token_id,
amount: transfer.common.amount.clone(),
owner_of: Address::from_str(my_address).map_to_mm(|e| UpdateNftError::InvalidHexString(e.to_string()))?,
token_hash: moralis_meta.common.token_hash,
@@ -1046,6 +1124,7 @@ async fn create_nft_from_moralis_metadata(
possible_spam: moralis_meta.common.possible_spam,
},
chain: *chain,
+ token_id: moralis_meta.token_id,
block_number_minted: moralis_meta.block_number_minted,
block_number: transfer.block_number,
contract_type: moralis_meta.contract_type,
@@ -1071,7 +1150,7 @@ async fn mark_as_spam_and_build_empty_meta MmResult {
- let nft_ctx = NftCtx::from_ctx(ctx).map_err(GetNftInfoError::Internal)?;
- let _lock = nft_ctx.guard.lock().await;
+ let nft_ctx = NftCtx::from_ctx(ctx).map_to_mm(GetNftInfoError::Internal)?;
- let storage = NftStorageBuilder::new(ctx).build()?;
+ let storage = nft_ctx.lock_db().await?;
if !NftListStorageOps::is_initialized(&storage, chain).await? {
NftListStorageOps::init(&storage, chain).await?;
}
@@ -1290,7 +1368,6 @@ async fn build_nft_from_moralis(
Nft {
common: NftCommon {
token_address: nft_moralis.common.token_address,
- token_id: nft_moralis.common.token_id,
amount: nft_moralis.common.amount,
owner_of: nft_moralis.common.owner_of,
token_hash: nft_moralis.common.token_hash,
@@ -1305,6 +1382,7 @@ async fn build_nft_from_moralis(
possible_spam: nft_moralis.common.possible_spam,
},
chain,
+ token_id: nft_moralis.token_id.0,
block_number_minted: nft_moralis.block_number_minted.map(|v| v.0),
block_number: *nft_moralis.block_number,
contract_type,
diff --git a/mm2src/coins/nft/nft_errors.rs b/mm2src/coins/nft/nft_errors.rs
index 941348db67..f5dd5adaba 100644
--- a/mm2src/coins/nft/nft_errors.rs
+++ b/mm2src/coins/nft/nft_errors.rs
@@ -1,7 +1,11 @@
use crate::eth::GetEthAddressError;
-use crate::nft::storage::{CreateNftStorageError, NftStorageError};
-use crate::{GetMyAddressError, WithdrawError};
+#[cfg(target_arch = "wasm32")]
+use crate::nft::storage::wasm::WasmNftCacheError;
+use crate::nft::storage::NftStorageError;
+use crate::{CoinFindError, GetMyAddressError, WithdrawError};
use common::{HttpStatusCode, ParseRfc3339Err};
+#[cfg(not(target_arch = "wasm32"))]
+use db_common::sqlite::rusqlite::Error as SqlError;
use derive_more::Display;
use enum_from::EnumFromStringify;
use http::StatusCode;
@@ -38,6 +42,7 @@ pub enum GetNftInfoError {
#[display(fmt = "The contract type is required and should not be null.")]
ContractTypeIsNull,
ProtectFromSpamError(ProtectFromSpamError),
+ TransferConfirmationsError(TransferConfirmationsError),
}
impl From for WithdrawError {
@@ -73,14 +78,6 @@ impl From for GetNftInfoError {
fn from(e: GetEthAddressError) -> Self { GetNftInfoError::GetEthAddressError(e) }
}
-impl From for GetNftInfoError {
- fn from(e: CreateNftStorageError) -> Self {
- match e {
- CreateNftStorageError::Internal(err) => GetNftInfoError::Internal(err),
- }
- }
-}
-
impl From for GetNftInfoError {
fn from(err: T) -> Self { GetNftInfoError::DbError(format!("{:?}", err)) }
}
@@ -104,6 +101,14 @@ impl From for GetNftInfoError {
fn from(e: ProtectFromSpamError) -> Self { GetNftInfoError::ProtectFromSpamError(e) }
}
+impl From for GetNftInfoError {
+ fn from(e: LockDBError) -> Self { GetNftInfoError::DbError(e.to_string()) }
+}
+
+impl From for GetNftInfoError {
+ fn from(e: TransferConfirmationsError) -> Self { GetNftInfoError::TransferConfirmationsError(e) }
+}
+
impl HttpStatusCode for GetNftInfoError {
fn status_code(&self) -> StatusCode {
match self {
@@ -115,7 +120,8 @@ impl HttpStatusCode for GetNftInfoError {
| GetNftInfoError::GetEthAddressError(_)
| GetNftInfoError::TokenNotFoundInWallet { .. }
| GetNftInfoError::DbError(_)
- | GetNftInfoError::ProtectFromSpamError(_) => StatusCode::INTERNAL_SERVER_ERROR,
+ | GetNftInfoError::ProtectFromSpamError(_)
+ | GetNftInfoError::TransferConfirmationsError(_) => StatusCode::INTERNAL_SERVER_ERROR,
}
}
}
@@ -184,14 +190,14 @@ pub enum UpdateNftError {
#[from_stringify("serde_json::Error")]
SerdeError(String),
ProtectFromSpamError(ProtectFromSpamError),
-}
-
-impl From for UpdateNftError {
- fn from(e: CreateNftStorageError) -> Self {
- match e {
- CreateNftStorageError::Internal(err) => UpdateNftError::Internal(err),
- }
- }
+ #[display(fmt = "No such coin {}", coin)]
+ NoSuchCoin {
+ coin: String,
+ },
+ #[display(fmt = "{} coin doesn't support NFT", coin)]
+ CoinDoesntSupportNft {
+ coin: String,
+ },
}
impl From for UpdateNftError {
@@ -218,6 +224,18 @@ impl From for UpdateNftError {
fn from(e: ProtectFromSpamError) -> Self { UpdateNftError::ProtectFromSpamError(e) }
}
+impl From for UpdateNftError {
+ fn from(e: LockDBError) -> Self { UpdateNftError::DbError(e.to_string()) }
+}
+
+impl From for UpdateNftError {
+ fn from(e: CoinFindError) -> Self {
+ match e {
+ CoinFindError::NoSuchCoin { coin } => UpdateNftError::NoSuchCoin { coin },
+ }
+ }
+}
+
impl HttpStatusCode for UpdateNftError {
fn status_code(&self) -> StatusCode {
match self {
@@ -234,7 +252,9 @@ impl HttpStatusCode for UpdateNftError {
| UpdateNftError::UpdateSpamPhishingError(_)
| UpdateNftError::GetInfoFromUriError(_)
| UpdateNftError::SerdeError(_)
- | UpdateNftError::ProtectFromSpamError(_) => StatusCode::INTERNAL_SERVER_ERROR,
+ | UpdateNftError::ProtectFromSpamError(_)
+ | UpdateNftError::NoSuchCoin { .. }
+ | UpdateNftError::CoinDoesntSupportNft { .. } => StatusCode::INTERNAL_SERVER_ERROR,
}
}
}
@@ -310,3 +330,39 @@ pub(crate) enum MetaFromUrlError {
impl From for MetaFromUrlError {
fn from(e: GetInfoFromUriError) -> Self { MetaFromUrlError::GetInfoFromUriError(e) }
}
+
+#[derive(Debug, Display)]
+pub enum LockDBError {
+ #[cfg(target_arch = "wasm32")]
+ WasmNftCacheError(WasmNftCacheError),
+ #[cfg(not(target_arch = "wasm32"))]
+ SqlError(SqlError),
+}
+
+#[cfg(not(target_arch = "wasm32"))]
+impl From for LockDBError {
+ fn from(e: SqlError) -> Self { LockDBError::SqlError(e) }
+}
+
+#[cfg(target_arch = "wasm32")]
+impl From for LockDBError {
+ fn from(e: WasmNftCacheError) -> Self { LockDBError::WasmNftCacheError(e) }
+}
+
+#[derive(Clone, Debug, Deserialize, Display, PartialEq, Serialize)]
+pub enum TransferConfirmationsError {
+ #[display(fmt = "No such coin {}", coin)]
+ NoSuchCoin { coin: String },
+ #[display(fmt = "{} coin doesn't support NFT", coin)]
+ CoinDoesntSupportNft { coin: String },
+ #[display(fmt = "Get current block error: {}", _0)]
+ GetCurrentBlockErr(String),
+}
+
+impl From for TransferConfirmationsError {
+ fn from(e: CoinFindError) -> Self {
+ match e {
+ CoinFindError::NoSuchCoin { coin } => TransferConfirmationsError::NoSuchCoin { coin },
+ }
+ }
+}
diff --git a/mm2src/coins/nft/nft_structs.rs b/mm2src/coins/nft/nft_structs.rs
index 165e7cbd93..9173f3bd5b 100644
--- a/mm2src/coins/nft/nft_structs.rs
+++ b/mm2src/coins/nft/nft_structs.rs
@@ -1,13 +1,11 @@
-use crate::nft::eth_addr_to_hex;
-use crate::{TransactionType, TxFeeDetails, WithdrawFee};
use common::ten;
use ethereum_types::Address;
-use futures::lock::Mutex as AsyncMutex;
use mm2_core::mm_ctx::{from_ctx, MmArc};
-use mm2_number::BigDecimal;
+use mm2_err_handle::prelude::*;
+use mm2_number::{BigDecimal, BigUint};
use rpc::v1::types::Bytes as BytesJson;
use serde::de::{self, Deserializer};
-use serde::Deserialize;
+use serde::{Deserialize, Serializer};
use serde_json::Value as Json;
use std::collections::HashMap;
use std::fmt;
@@ -16,12 +14,22 @@ use std::str::FromStr;
use std::sync::Arc;
use url::Url;
-use crate::nft::nft_errors::ParseChainTypeError;
-#[cfg(target_arch = "wasm32")]
-use mm2_db::indexed_db::{ConstructibleDb, SharedDb};
+use crate::eth::EthTxFeeDetails;
+use crate::nft::eth_addr_to_hex;
+use crate::nft::nft_errors::{LockDBError, ParseChainTypeError};
+use crate::nft::storage::{NftListStorageOps, NftTransferHistoryStorageOps};
+use crate::{TransactionType, TxFeeDetails, WithdrawFee};
+
+cfg_native! {
+ use db_common::async_sql_conn::AsyncConnection;
+ use futures::lock::Mutex as AsyncMutex;
+}
-#[cfg(target_arch = "wasm32")]
-use crate::nft::storage::wasm::nft_idb::NftCacheIDB;
+cfg_wasm32! {
+ use mm2_db::indexed_db::{ConstructibleDb, SharedDb};
+ use crate::nft::storage::wasm::WasmNftCacheError;
+ use crate::nft::storage::wasm::nft_idb::NftCacheIDB;
+}
/// Represents a request to list NFTs owned by the user across specified chains.
///
@@ -68,7 +76,8 @@ pub struct NftListFilters {
#[derive(Debug, Deserialize)]
pub struct NftMetadataReq {
pub(crate) token_address: Address,
- pub(crate) token_id: BigDecimal,
+ #[serde(deserialize_with = "deserialize_token_id")]
+ pub(crate) token_id: BigUint,
pub(crate) chain: Chain,
#[serde(default)]
pub(crate) protect_from_spam: bool,
@@ -85,7 +94,8 @@ pub struct NftMetadataReq {
#[derive(Debug, Deserialize)]
pub struct RefreshMetadataReq {
pub(crate) token_address: Address,
- pub(crate) token_id: BigDecimal,
+ #[serde(deserialize_with = "deserialize_token_id")]
+ pub(crate) token_id: BigUint,
pub(crate) chain: Chain,
pub(crate) url: Url,
pub(crate) url_antispam: Url,
@@ -104,17 +114,17 @@ pub enum Chain {
}
pub(crate) trait ConvertChain {
- fn to_ticker(&self) -> String;
+ fn to_ticker(&self) -> &'static str;
}
impl ConvertChain for Chain {
- fn to_ticker(&self) -> String {
+ fn to_ticker(&self) -> &'static str {
match self {
- Chain::Avalanche => "AVAX".to_owned(),
- Chain::Bsc => "BNB".to_owned(),
- Chain::Eth => "ETH".to_owned(),
- Chain::Fantom => "FTM".to_owned(),
- Chain::Polygon => "MATIC".to_owned(),
+ Chain::Avalanche => "AVAX",
+ Chain::Bsc => "BNB",
+ Chain::Eth => "ETH",
+ Chain::Fantom => "FTM",
+ Chain::Polygon => "MATIC",
}
}
}
@@ -258,7 +268,6 @@ impl UriMeta {
#[derive(Clone, Debug, Deserialize, Serialize)]
pub struct NftCommon {
pub(crate) token_address: Address,
- pub(crate) token_id: BigDecimal,
pub(crate) amount: BigDecimal,
pub(crate) owner_of: Address,
pub(crate) token_hash: Option,
@@ -283,6 +292,8 @@ pub struct Nft {
#[serde(flatten)]
pub(crate) common: NftCommon,
pub(crate) chain: Chain,
+ #[serde(serialize_with = "serialize_token_id", deserialize_with = "deserialize_token_id")]
+ pub(crate) token_id: BigUint,
pub(crate) block_number_minted: Option,
pub(crate) block_number: u64,
pub(crate) contract_type: ContractType,
@@ -293,7 +304,7 @@ pub struct Nft {
pub(crate) struct BuildNftFields {
pub(crate) token_address: Address,
- pub(crate) token_id: BigDecimal,
+ pub(crate) token_id: BigUint,
pub(crate) amount: BigDecimal,
pub(crate) owner_of: Address,
pub(crate) contract_type: ContractType,
@@ -306,7 +317,6 @@ pub(crate) fn build_nft_with_empty_meta(nft_fields: BuildNftFields) -> Nft {
Nft {
common: NftCommon {
token_address: nft_fields.token_address,
- token_id: nft_fields.token_id,
amount: nft_fields.amount,
owner_of: nft_fields.owner_of,
token_hash: None,
@@ -321,6 +331,7 @@ pub(crate) fn build_nft_with_empty_meta(nft_fields: BuildNftFields) -> Nft {
possible_spam: nft_fields.possible_spam,
},
chain: nft_fields.chain,
+ token_id: nft_fields.token_id,
block_number_minted: None,
block_number: nft_fields.block_number,
contract_type: nft_fields.contract_type,
@@ -339,6 +350,7 @@ pub(crate) struct NftFromMoralis {
pub(crate) block_number_minted: Option>,
pub(crate) block_number: SerdeStringWrap,
pub(crate) contract_type: Option,
+ pub(crate) token_id: SerdeStringWrap,
}
#[derive(Debug)]
@@ -378,7 +390,8 @@ pub struct WithdrawErc1155 {
pub(crate) chain: Chain,
pub(crate) to: String,
pub(crate) token_address: String,
- pub(crate) token_id: BigDecimal,
+ #[serde(deserialize_with = "deserialize_token_id")]
+ pub(crate) token_id: BigUint,
pub(crate) amount: Option,
#[serde(default)]
pub(crate) max: bool,
@@ -390,7 +403,8 @@ pub struct WithdrawErc721 {
pub(crate) chain: Chain,
pub(crate) to: String,
pub(crate) token_address: String,
- pub(crate) token_id: BigDecimal,
+ #[serde(deserialize_with = "deserialize_token_id")]
+ pub(crate) token_id: BigUint,
pub(crate) fee: Option,
}
@@ -413,7 +427,8 @@ pub struct TransactionNftDetails {
pub(crate) to: Vec,
pub(crate) contract_type: ContractType,
pub(crate) token_address: String,
- pub(crate) token_id: BigDecimal,
+ #[serde(serialize_with = "serialize_token_id")]
+ pub(crate) token_id: BigUint,
pub(crate) amount: BigDecimal,
pub(crate) fee_details: Option,
/// The coin transaction belongs to
@@ -498,7 +513,6 @@ pub struct NftTransferCommon {
pub(crate) value: Option,
pub(crate) transaction_type: Option,
pub(crate) token_address: Address,
- pub(crate) token_id: BigDecimal,
pub(crate) from_address: Address,
pub(crate) to_address: Address,
pub(crate) amount: BigDecimal,
@@ -519,6 +533,8 @@ pub struct NftTransferHistory {
#[serde(flatten)]
pub(crate) common: NftTransferCommon,
pub(crate) chain: Chain,
+ #[serde(serialize_with = "serialize_token_id", deserialize_with = "deserialize_token_id")]
+ pub(crate) token_id: BigUint,
pub(crate) block_number: u64,
pub(crate) block_timestamp: u64,
pub(crate) contract_type: ContractType,
@@ -531,6 +547,8 @@ pub struct NftTransferHistory {
pub(crate) status: TransferStatus,
#[serde(default)]
pub(crate) possible_phishing: bool,
+ pub(crate) fee_details: Option,
+ pub(crate) confirmations: u64,
}
/// Represents an NFT transfer structure specifically for deserialization from Moralis's JSON response.
@@ -543,6 +561,7 @@ pub(crate) struct NftTransferHistoryFromMoralis {
pub(crate) block_number: SerdeStringWrap,
pub(crate) block_timestamp: String,
pub(crate) contract_type: Option,
+ pub(crate) token_id: SerdeStringWrap,
}
/// Represents the detailed transfer history of NFTs, including the total number of transfers
@@ -589,13 +608,13 @@ pub struct UpdateNftReq {
#[derive(Debug, Deserialize, Eq, Hash, PartialEq)]
pub struct NftTokenAddrId {
pub(crate) token_address: String,
- pub(crate) token_id: BigDecimal,
+ pub(crate) token_id: BigUint,
}
#[derive(Debug)]
pub struct TransferMeta {
pub(crate) token_address: String,
- pub(crate) token_id: BigDecimal,
+ pub(crate) token_id: BigUint,
pub(crate) token_uri: Option,
pub(crate) token_domain: Option,
pub(crate) collection_name: Option,
@@ -608,7 +627,7 @@ impl From for TransferMeta {
fn from(nft_db: Nft) -> Self {
TransferMeta {
token_address: eth_addr_to_hex(&nft_db.common.token_address),
- token_id: nft_db.common.token_id,
+ token_id: nft_db.token_id,
token_uri: nft_db.common.token_uri,
token_domain: nft_db.common.token_domain,
collection_name: nft_db.common.collection_name,
@@ -625,26 +644,56 @@ impl From for TransferMeta {
/// required for NFT operations, including guarding against concurrent accesses and
/// dealing with platform-specific storage mechanisms.
pub(crate) struct NftCtx {
- /// An asynchronous mutex to guard against concurrent NFT operations, ensuring data consistency.
- pub(crate) guard: Arc>,
- #[cfg(target_arch = "wasm32")]
/// Platform-specific database for caching NFT data.
+ #[cfg(target_arch = "wasm32")]
pub(crate) nft_cache_db: SharedDb,
+ #[cfg(not(target_arch = "wasm32"))]
+ pub(crate) nft_cache_db: Arc>,
}
impl NftCtx {
/// Create a new `NftCtx` from the given MM context.
///
/// If an `NftCtx` instance doesn't already exist in the MM context, it gets created and cached for subsequent use.
+ #[cfg(not(target_arch = "wasm32"))]
+ pub(crate) fn from_ctx(ctx: &MmArc) -> Result, String> {
+ Ok(try_s!(from_ctx(&ctx.nft_ctx, move || {
+ let async_sqlite_connection = ctx
+ .async_sqlite_connection
+ .ok_or("async_sqlite_connection is not initialized".to_owned())?;
+ Ok(NftCtx {
+ nft_cache_db: async_sqlite_connection.clone(),
+ })
+ })))
+ }
+
+ #[cfg(target_arch = "wasm32")]
pub(crate) fn from_ctx(ctx: &MmArc) -> Result, String> {
Ok(try_s!(from_ctx(&ctx.nft_ctx, move || {
Ok(NftCtx {
- guard: Arc::new(AsyncMutex::new(())),
- #[cfg(target_arch = "wasm32")]
nft_cache_db: ConstructibleDb::new(ctx).into_shared(),
})
})))
}
+
+ /// Lock database to guard against concurrent NFT operations, ensuring data consistency.
+ #[cfg(not(target_arch = "wasm32"))]
+ pub(crate) async fn lock_db(
+ &self,
+ ) -> MmResult {
+ Ok(self.nft_cache_db.lock().await)
+ }
+
+ #[cfg(target_arch = "wasm32")]
+ pub(crate) async fn lock_db(
+ &self,
+ ) -> MmResult {
+ self.nft_cache_db
+ .get_or_initialize()
+ .await
+ .mm_err(WasmNftCacheError::from)
+ .mm_err(LockDBError::from)
+ }
}
#[derive(Debug, Serialize)]
@@ -667,3 +716,19 @@ pub(crate) struct SpamContractRes {
pub(crate) struct PhishingDomainRes {
pub(crate) result: HashMap,
}
+
+fn serialize_token_id(token_id: &BigUint, serializer: S) -> Result
+where
+ S: Serializer,
+{
+ let token_id_str = token_id.to_string();
+ serializer.serialize_str(&token_id_str)
+}
+
+fn deserialize_token_id<'de, D>(deserializer: D) -> Result
+where
+ D: Deserializer<'de>,
+{
+ let s = String::deserialize(deserializer)?;
+ BigUint::from_str(&s).map_err(serde::de::Error::custom)
+}
diff --git a/mm2src/coins/nft/nft_tests.rs b/mm2src/coins/nft/nft_tests.rs
index ae10513987..8f5667cd17 100644
--- a/mm2src/coins/nft/nft_tests.rs
+++ b/mm2src/coins/nft/nft_tests.rs
@@ -2,15 +2,14 @@ use crate::eth::eth_addr_to_hex;
use crate::nft::nft_structs::{Chain, NftFromMoralis, NftListFilters, NftTransferHistoryFilters,
NftTransferHistoryFromMoralis, PhishingDomainReq, PhishingDomainRes, SpamContractReq,
SpamContractRes, TransferMeta, UriMeta};
-use crate::nft::storage::db_test_helpers::{init_nft_history_storage, init_nft_list_storage, nft, nft_list,
- nft_transfer_history};
+use crate::nft::storage::db_test_helpers::{get_nft_ctx, nft, nft_list, nft_transfer_history};
use crate::nft::storage::{NftListStorageOps, NftTransferHistoryStorageOps, RemoveNftResult};
use crate::nft::{check_moralis_ipfs_bafy, get_domain_from_url, process_metadata_for_spam_link,
process_text_for_spam_link};
use common::cross_test;
use ethereum_types::Address;
use mm2_net::transport::send_post_request_to_uri;
-use mm2_number::BigDecimal;
+use mm2_number::{BigDecimal, BigUint};
use std::num::NonZeroUsize;
use std::str::FromStr;
@@ -158,11 +157,13 @@ cross_test!(test_camo, {
cross_test!(test_add_get_nfts, {
let chain = Chain::Bsc;
- let storage = init_nft_list_storage(&chain).await;
+ let nft_ctx = get_nft_ctx(&chain).await;
+ let storage = nft_ctx.lock_db().await.unwrap();
+ NftListStorageOps::init(&storage, &chain).await.unwrap();
let nft_list = nft_list();
storage.add_nfts_to_list(chain, nft_list, 28056726).await.unwrap();
- let token_id = BigDecimal::from_str(TOKEN_ID).unwrap();
+ let token_id = BigUint::from_str(TOKEN_ID).unwrap();
let nft = storage
.get_nft(&chain, TOKEN_ADD.to_string(), token_id)
.await
@@ -173,7 +174,9 @@ cross_test!(test_add_get_nfts, {
cross_test!(test_last_nft_block, {
let chain = Chain::Bsc;
- let storage = init_nft_list_storage(&chain).await;
+ let nft_ctx = get_nft_ctx(&chain).await;
+ let storage = nft_ctx.lock_db().await.unwrap();
+ NftListStorageOps::init(&storage, &chain).await.unwrap();
let nft_list = nft_list();
storage.add_nfts_to_list(chain, nft_list, 28056726).await.unwrap();
@@ -186,7 +189,9 @@ cross_test!(test_last_nft_block, {
cross_test!(test_nft_list, {
let chain = Chain::Bsc;
- let storage = init_nft_list_storage(&chain).await;
+ let nft_ctx = get_nft_ctx(&chain).await;
+ let storage = nft_ctx.lock_db().await.unwrap();
+ NftListStorageOps::init(&storage, &chain).await.unwrap();
let nft_list = nft_list();
storage.add_nfts_to_list(chain, nft_list, 28056726).await.unwrap();
@@ -203,11 +208,13 @@ cross_test!(test_nft_list, {
cross_test!(test_remove_nft, {
let chain = Chain::Bsc;
- let storage = init_nft_list_storage(&chain).await;
+ let nft_ctx = get_nft_ctx(&chain).await;
+ let storage = nft_ctx.lock_db().await.unwrap();
+ NftListStorageOps::init(&storage, &chain).await.unwrap();
let nft_list = nft_list();
storage.add_nfts_to_list(chain, nft_list, 28056726).await.unwrap();
- let token_id = BigDecimal::from_str(TOKEN_ID).unwrap();
+ let token_id = BigUint::from_str(TOKEN_ID).unwrap();
let remove_rslt = storage
.remove_nft_from_list(&chain, TOKEN_ADD.to_string(), token_id, 28056800)
.await
@@ -226,7 +233,9 @@ cross_test!(test_remove_nft, {
cross_test!(test_nft_amount, {
let chain = Chain::Bsc;
- let storage = init_nft_list_storage(&chain).await;
+ let nft_ctx = get_nft_ctx(&chain).await;
+ let storage = nft_ctx.lock_db().await.unwrap();
+ NftListStorageOps::init(&storage, &chain).await.unwrap();
let mut nft = nft();
storage
.add_nfts_to_list(chain, vec![nft.clone()], 25919780)
@@ -236,11 +245,7 @@ cross_test!(test_nft_amount, {
nft.common.amount -= BigDecimal::from(1);
storage.update_nft_amount(&chain, nft.clone(), 25919800).await.unwrap();
let amount = storage
- .get_nft_amount(
- &chain,
- eth_addr_to_hex(&nft.common.token_address),
- nft.common.token_id.clone(),
- )
+ .get_nft_amount(&chain, eth_addr_to_hex(&nft.common.token_address), nft.token_id.clone())
.await
.unwrap()
.unwrap();
@@ -255,7 +260,7 @@ cross_test!(test_nft_amount, {
.await
.unwrap();
let amount = storage
- .get_nft_amount(&chain, eth_addr_to_hex(&nft.common.token_address), nft.common.token_id)
+ .get_nft_amount(&chain, eth_addr_to_hex(&nft.common.token_address), nft.token_id)
.await
.unwrap()
.unwrap();
@@ -266,7 +271,9 @@ cross_test!(test_nft_amount, {
cross_test!(test_refresh_metadata, {
let chain = Chain::Bsc;
- let storage = init_nft_list_storage(&chain).await;
+ let nft_ctx = get_nft_ctx(&chain).await;
+ let storage = nft_ctx.lock_db().await.unwrap();
+ NftListStorageOps::init(&storage, &chain).await.unwrap();
let new_symbol = "NEW_SYMBOL";
let mut nft = nft();
storage
@@ -276,7 +283,7 @@ cross_test!(test_refresh_metadata, {
nft.common.symbol = Some(new_symbol.to_string());
drop_mutability!(nft);
let token_add = eth_addr_to_hex(&nft.common.token_address);
- let token_id = nft.common.token_id.clone();
+ let token_id = nft.token_id.clone();
storage.refresh_nft_metadata(&chain, nft).await.unwrap();
let nft_upd = storage.get_nft(&chain, token_add, token_id).await.unwrap().unwrap();
assert_eq!(new_symbol.to_string(), nft_upd.common.symbol.unwrap());
@@ -284,7 +291,9 @@ cross_test!(test_refresh_metadata, {
cross_test!(test_update_nft_spam_by_token_address, {
let chain = Chain::Bsc;
- let storage = init_nft_list_storage(&chain).await;
+ let nft_ctx = get_nft_ctx(&chain).await;
+ let storage = nft_ctx.lock_db().await.unwrap();
+ NftListStorageOps::init(&storage, &chain).await.unwrap();
let nft_list = nft_list();
storage.add_nfts_to_list(chain, nft_list, 28056726).await.unwrap();
@@ -303,7 +312,9 @@ cross_test!(test_update_nft_spam_by_token_address, {
cross_test!(test_exclude_nft_spam, {
let chain = Chain::Bsc;
- let storage = init_nft_list_storage(&chain).await;
+ let nft_ctx = get_nft_ctx(&chain).await;
+ let storage = nft_ctx.lock_db().await.unwrap();
+ NftListStorageOps::init(&storage, &chain).await.unwrap();
let nft_list = nft_list();
storage.add_nfts_to_list(chain, nft_list, 28056726).await.unwrap();
@@ -320,7 +331,9 @@ cross_test!(test_exclude_nft_spam, {
cross_test!(test_get_animation_external_domains, {
let chain = Chain::Bsc;
- let storage = init_nft_list_storage(&chain).await;
+ let nft_ctx = get_nft_ctx(&chain).await;
+ let storage = nft_ctx.lock_db().await.unwrap();
+ NftListStorageOps::init(&storage, &chain).await.unwrap();
let nft_list = nft_list();
storage.add_nfts_to_list(chain, nft_list, 28056726).await.unwrap();
@@ -332,7 +345,9 @@ cross_test!(test_get_animation_external_domains, {
cross_test!(test_update_nft_phishing_by_domain, {
let chain = Chain::Bsc;
- let storage = init_nft_list_storage(&chain).await;
+ let nft_ctx = get_nft_ctx(&chain).await;
+ let storage = nft_ctx.lock_db().await.unwrap();
+ NftListStorageOps::init(&storage, &chain).await.unwrap();
let nft_list = nft_list();
storage.add_nfts_to_list(chain, nft_list, 28056726).await.unwrap();
@@ -358,7 +373,9 @@ cross_test!(test_update_nft_phishing_by_domain, {
cross_test!(test_exclude_nft_phishing_spam, {
let chain = Chain::Bsc;
- let storage = init_nft_list_storage(&chain).await;
+ let nft_ctx = get_nft_ctx(&chain).await;
+ let storage = nft_ctx.lock_db().await.unwrap();
+ NftListStorageOps::init(&storage, &chain).await.unwrap();
let nft_list = nft_list();
storage.add_nfts_to_list(chain, nft_list, 28056726).await.unwrap();
@@ -380,11 +397,13 @@ cross_test!(test_exclude_nft_phishing_spam, {
cross_test!(test_add_get_transfers, {
let chain = Chain::Bsc;
- let storage = init_nft_history_storage(&chain).await;
+ let nft_ctx = get_nft_ctx(&chain).await;
+ let storage = nft_ctx.lock_db().await.unwrap();
+ NftTransferHistoryStorageOps::init(&storage, &chain).await.unwrap();
let transfers = nft_transfer_history();
storage.add_transfers_to_history(chain, transfers).await.unwrap();
- let token_id = BigDecimal::from_str(TOKEN_ID).unwrap();
+ let token_id = BigUint::from_str(TOKEN_ID).unwrap();
let transfer1 = storage
.get_transfers_by_token_addr_id(chain, TOKEN_ADD.to_string(), token_id)
.await
@@ -405,7 +424,9 @@ cross_test!(test_add_get_transfers, {
cross_test!(test_last_transfer_block, {
let chain = Chain::Bsc;
- let storage = init_nft_history_storage(&chain).await;
+ let nft_ctx = get_nft_ctx(&chain).await;
+ let storage = nft_ctx.lock_db().await.unwrap();
+ NftTransferHistoryStorageOps::init(&storage, &chain).await.unwrap();
let transfers = nft_transfer_history();
storage.add_transfers_to_history(chain, transfers).await.unwrap();
@@ -418,7 +439,9 @@ cross_test!(test_last_transfer_block, {
cross_test!(test_transfer_history, {
let chain = Chain::Bsc;
- let storage = init_nft_history_storage(&chain).await;
+ let nft_ctx = get_nft_ctx(&chain).await;
+ let storage = nft_ctx.lock_db().await.unwrap();
+ NftTransferHistoryStorageOps::init(&storage, &chain).await.unwrap();
let transfers = nft_transfer_history();
storage.add_transfers_to_history(chain, transfers).await.unwrap();
@@ -435,7 +458,9 @@ cross_test!(test_transfer_history, {
cross_test!(test_transfer_history_filters, {
let chain = Chain::Bsc;
- let storage = init_nft_history_storage(&chain).await;
+ let nft_ctx = get_nft_ctx(&chain).await;
+ let storage = nft_ctx.lock_db().await.unwrap();
+ NftTransferHistoryStorageOps::init(&storage, &chain).await.unwrap();
let transfers = nft_transfer_history();
storage.add_transfers_to_history(chain, transfers).await.unwrap();
@@ -495,7 +520,9 @@ cross_test!(test_transfer_history_filters, {
cross_test!(test_get_update_transfer_meta, {
let chain = Chain::Bsc;
- let storage = init_nft_history_storage(&chain).await;
+ let nft_ctx = get_nft_ctx(&chain).await;
+ let storage = nft_ctx.lock_db().await.unwrap();
+ NftTransferHistoryStorageOps::init(&storage, &chain).await.unwrap();
let transfers = nft_transfer_history();
storage.add_transfers_to_history(chain, transfers).await.unwrap();
@@ -528,7 +555,9 @@ cross_test!(test_get_update_transfer_meta, {
cross_test!(test_update_transfer_spam_by_token_address, {
let chain = Chain::Bsc;
- let storage = init_nft_history_storage(&chain).await;
+ let nft_ctx = get_nft_ctx(&chain).await;
+ let storage = nft_ctx.lock_db().await.unwrap();
+ NftTransferHistoryStorageOps::init(&storage, &chain).await.unwrap();
let transfers = nft_transfer_history();
storage.add_transfers_to_history(chain, transfers).await.unwrap();
@@ -547,7 +576,9 @@ cross_test!(test_update_transfer_spam_by_token_address, {
cross_test!(test_get_token_addresses, {
let chain = Chain::Bsc;
- let storage = init_nft_history_storage(&chain).await;
+ let nft_ctx = get_nft_ctx(&chain).await;
+ let storage = nft_ctx.lock_db().await.unwrap();
+ NftTransferHistoryStorageOps::init(&storage, &chain).await.unwrap();
let transfers = nft_transfer_history();
storage.add_transfers_to_history(chain, transfers).await.unwrap();
@@ -557,7 +588,9 @@ cross_test!(test_get_token_addresses, {
cross_test!(test_exclude_transfer_spam, {
let chain = Chain::Bsc;
- let storage = init_nft_history_storage(&chain).await;
+ let nft_ctx = get_nft_ctx(&chain).await;
+ let storage = nft_ctx.lock_db().await.unwrap();
+ NftTransferHistoryStorageOps::init(&storage, &chain).await.unwrap();
let transfers = nft_transfer_history();
storage.add_transfers_to_history(chain, transfers).await.unwrap();
@@ -578,7 +611,9 @@ cross_test!(test_exclude_transfer_spam, {
cross_test!(test_get_domains, {
let chain = Chain::Bsc;
- let storage = init_nft_history_storage(&chain).await;
+ let nft_ctx = get_nft_ctx(&chain).await;
+ let storage = nft_ctx.lock_db().await.unwrap();
+ NftTransferHistoryStorageOps::init(&storage, &chain).await.unwrap();
let transfers = nft_transfer_history();
storage.add_transfers_to_history(chain, transfers).await.unwrap();
@@ -590,7 +625,9 @@ cross_test!(test_get_domains, {
cross_test!(test_update_transfer_phishing_by_domain, {
let chain = Chain::Bsc;
- let storage = init_nft_history_storage(&chain).await;
+ let nft_ctx = get_nft_ctx(&chain).await;
+ let storage = nft_ctx.lock_db().await.unwrap();
+ NftTransferHistoryStorageOps::init(&storage, &chain).await.unwrap();
let transfers = nft_transfer_history();
storage.add_transfers_to_history(chain, transfers).await.unwrap();
@@ -616,7 +653,9 @@ cross_test!(test_update_transfer_phishing_by_domain, {
cross_test!(test_exclude_transfer_phishing_spam, {
let chain = Chain::Bsc;
- let storage = init_nft_history_storage(&chain).await;
+ let nft_ctx = get_nft_ctx(&chain).await;
+ let storage = nft_ctx.lock_db().await.unwrap();
+ NftTransferHistoryStorageOps::init(&storage, &chain).await.unwrap();
let transfers = nft_transfer_history();
storage.add_transfers_to_history(chain, transfers).await.unwrap();
diff --git a/mm2src/coins/nft/storage/db_test_helpers.rs b/mm2src/coins/nft/storage/db_test_helpers.rs
index 9d4e8bfe14..d59b845661 100644
--- a/mm2src/coins/nft/storage/db_test_helpers.rs
+++ b/mm2src/coins/nft/storage/db_test_helpers.rs
@@ -1,16 +1,18 @@
-use crate::nft::nft_structs::{Chain, ContractType, Nft, NftCommon, NftTransferCommon, NftTransferHistory,
+use crate::nft::nft_structs::{Chain, ContractType, Nft, NftCommon, NftCtx, NftTransferCommon, NftTransferHistory,
TransferStatus, UriMeta};
-use crate::nft::storage::{NftListStorageOps, NftStorageBuilder, NftTransferHistoryStorageOps};
use ethereum_types::Address;
-use mm2_number::BigDecimal;
+use mm2_number::{BigDecimal, BigUint};
+#[cfg(not(target_arch = "wasm32"))]
+use mm2_test_helpers::for_tests::mm_ctx_with_custom_async_db;
+#[cfg(target_arch = "wasm32")]
use mm2_test_helpers::for_tests::mm_ctx_with_custom_db;
use std::str::FromStr;
+use std::sync::Arc;
pub(crate) fn nft() -> Nft {
Nft {
common: NftCommon {
token_address: Address::from_str("0x5c7d6712dfaf0cb079d48981781c8705e8417ca0").unwrap(),
- token_id: Default::default(),
amount: BigDecimal::from_str("2").unwrap(),
owner_of: Address::from_str("0xf622a6c52c94b500542e2ae6bcad24c53bc5b6a2").unwrap(),
token_hash: Some("b34ddf294013d20a6d70691027625839".to_string()),
@@ -28,6 +30,7 @@ pub(crate) fn nft() -> Nft {
possible_spam: true,
},
chain: Chain::Bsc,
+ token_id: Default::default(),
block_number_minted: Some(25465916),
block_number: 25919780,
contract_type: ContractType::Erc1155,
@@ -52,7 +55,6 @@ pub(crate) fn nft_list() -> Vec {
let nft = Nft {
common: NftCommon {
token_address: Address::from_str("0x5c7d6712dfaf0cb079d48981781c8705e8417ca0").unwrap(),
- token_id: Default::default(),
amount: BigDecimal::from_str("2").unwrap(),
owner_of: Address::from_str("0xf622a6c52c94b500542e2ae6bcad24c53bc5b6a2").unwrap(),
token_hash: Some("b34ddf294013d20a6d70691027625839".to_string()),
@@ -67,6 +69,7 @@ pub(crate) fn nft_list() -> Vec {
possible_spam: false,
},
chain: Chain::Bsc,
+ token_id: Default::default(),
block_number_minted: Some(25465916),
block_number: 25919780,
contract_type: ContractType::Erc1155,
@@ -89,7 +92,6 @@ pub(crate) fn nft_list() -> Vec {
let nft1 = Nft {
common: NftCommon {
token_address: Address::from_str("0xfd913a305d70a60aac4faac70c739563738e1f81").unwrap(),
- token_id: BigDecimal::from_str("214300047252").unwrap(),
amount: BigDecimal::from_str("1").unwrap(),
owner_of: Address::from_str("0xf622a6c52c94b500542e2ae6bcad24c53bc5b6a2").unwrap(),
token_hash: Some("c5d1cfd75a0535b0ec750c0156e6ddfe".to_string()),
@@ -107,6 +109,7 @@ pub(crate) fn nft_list() -> Vec {
possible_spam: true,
},
chain: Chain::Bsc,
+ token_id: BigUint::from_str("214300047252").unwrap(),
block_number_minted: Some(25721963),
block_number: 28056726,
contract_type: ContractType::Erc721,
@@ -131,7 +134,6 @@ pub(crate) fn nft_list() -> Vec {
let nft2 = Nft {
common: NftCommon {
token_address: Address::from_str("0xfd913a305d70a60aac4faac70c739563738e1f81").unwrap(),
- token_id: BigDecimal::from_str("214300047253").unwrap(),
amount: BigDecimal::from_str("1").unwrap(),
owner_of: Address::from_str("0xf622a6c52c94b500542e2ae6bcad24c53bc5b6a2").unwrap(),
token_hash: Some("c5d1cfd75a0535b0ec750c0156e6ddfe".to_string()),
@@ -149,6 +151,7 @@ pub(crate) fn nft_list() -> Vec {
possible_spam: false,
},
chain: Chain::Bsc,
+ token_id: BigUint::from_str("214300047253").unwrap(),
block_number_minted: Some(25721963),
block_number: 28056726,
contract_type: ContractType::Erc721,
@@ -173,7 +176,6 @@ pub(crate) fn nft_list() -> Vec {
let nft3 = Nft {
common: NftCommon {
token_address: Address::from_str("0xfd913a305d70a60aac4faac70c739563738e1f81").unwrap(),
- token_id: BigDecimal::from_str("214300044414").unwrap(),
amount: BigDecimal::from_str("1").unwrap(),
owner_of: Address::from_str("0xf622a6c52c94b500542e2ae6bcad24c53bc5b6a2").unwrap(),
token_hash: Some("125f8f4e952e107c257960000b4b250e".to_string()),
@@ -191,6 +193,7 @@ pub(crate) fn nft_list() -> Vec {
possible_spam: false,
},
chain: Chain::Bsc,
+ token_id: BigUint::from_str("214300044414").unwrap(),
block_number_minted: Some(25810308),
block_number: 28056721,
contract_type: ContractType::Erc721,
@@ -224,7 +227,6 @@ pub(crate) fn nft_transfer_history() -> Vec {
value: Default::default(),
transaction_type: Some("Single".to_string()),
token_address: Address::from_str("0x5c7d6712dfaf0cb079d48981781c8705e8417ca0").unwrap(),
- token_id: Default::default(),
from_address: Address::from_str("0x4ff0bbc9b64d635a4696d1a38554fb2529c103ff").unwrap(),
to_address: Address::from_str("0xf622a6c52c94b500542e2ae6bcad24c53bc5b6a2").unwrap(),
amount: BigDecimal::from_str("1").unwrap(),
@@ -233,6 +235,7 @@ pub(crate) fn nft_transfer_history() -> Vec {
possible_spam: false,
},
chain: Chain::Bsc,
+ token_id: Default::default(),
block_number: 25919780,
block_timestamp: 1677166110,
contract_type: ContractType::Erc1155,
@@ -244,6 +247,8 @@ pub(crate) fn nft_transfer_history() -> Vec {
token_name: None,
status: TransferStatus::Receive,
possible_phishing: false,
+ fee_details: None,
+ confirmations: 0,
};
let transfer1 = NftTransferHistory {
@@ -255,7 +260,6 @@ pub(crate) fn nft_transfer_history() -> Vec {
value: Default::default(),
transaction_type: Some("Single".to_string()),
token_address: Address::from_str("0xfd913a305d70a60aac4faac70c739563738e1f81").unwrap(),
- token_id: BigDecimal::from_str("214300047252").unwrap(),
from_address: Address::from_str("0x6fad0ec6bb76914b2a2a800686acc22970645820").unwrap(),
to_address: Address::from_str("0xf622a6c52c94b500542e2ae6bcad24c53bc5b6a2").unwrap(),
amount: BigDecimal::from_str("1").unwrap(),
@@ -264,6 +268,7 @@ pub(crate) fn nft_transfer_history() -> Vec {
possible_spam: true,
},
chain: Chain::Bsc,
+ token_id: BigUint::from_str("214300047252").unwrap(),
block_number: 28056726,
block_timestamp: 1683627432,
contract_type: ContractType::Erc721,
@@ -275,6 +280,8 @@ pub(crate) fn nft_transfer_history() -> Vec {
token_name: None,
status: TransferStatus::Receive,
possible_phishing: false,
+ fee_details: None,
+ confirmations: 0,
};
// Same as transfer1 but with different log_index, meaning that transfer1 and transfer2 are part of one batch/multi token transaction
@@ -287,7 +294,6 @@ pub(crate) fn nft_transfer_history() -> Vec {
value: Default::default(),
transaction_type: Some("Single".to_string()),
token_address: Address::from_str("0xfd913a305d70a60aac4faac70c739563738e1f81").unwrap(),
- token_id: BigDecimal::from_str("214300047253").unwrap(),
from_address: Address::from_str("0x6fad0ec6bb76914b2a2a800686acc22970645820").unwrap(),
to_address: Address::from_str("0xf622a6c52c94b500542e2ae6bcad24c53bc5b6a2").unwrap(),
amount: BigDecimal::from_str("1").unwrap(),
@@ -296,6 +302,7 @@ pub(crate) fn nft_transfer_history() -> Vec {
possible_spam: false,
},
chain: Chain::Bsc,
+ token_id: BigUint::from_str("214300047253").unwrap(),
block_number: 28056726,
block_timestamp: 1683627432,
contract_type: ContractType::Erc721,
@@ -307,6 +314,8 @@ pub(crate) fn nft_transfer_history() -> Vec {
token_name: None,
status: TransferStatus::Receive,
possible_phishing: false,
+ fee_details: None,
+ confirmations: 0,
};
let transfer3 = NftTransferHistory {
@@ -318,7 +327,6 @@ pub(crate) fn nft_transfer_history() -> Vec {
value: Default::default(),
transaction_type: Some("Single".to_string()),
token_address: Address::from_str("0xfd913a305d70a60aac4faac70c739563738e1f81").unwrap(),
- token_id: BigDecimal::from_str("214300044414").unwrap(),
from_address: Address::from_str("0x6fad0ec6bb76914b2a2a800686acc22970645820").unwrap(),
to_address: Address::from_str("0xf622a6c52c94b500542e2ae6bcad24c53bc5b6a2").unwrap(),
amount: BigDecimal::from_str("1").unwrap(),
@@ -327,6 +335,7 @@ pub(crate) fn nft_transfer_history() -> Vec {
possible_spam: false,
},
chain: Chain::Bsc,
+ token_id: BigUint::from_str("214300044414").unwrap(),
block_number: 28056721,
block_timestamp: 1683627417,
contract_type: ContractType::Erc721,
@@ -338,26 +347,16 @@ pub(crate) fn nft_transfer_history() -> Vec {
token_name: Some("Nebula Nodes".to_string()),
status: TransferStatus::Receive,
possible_phishing: false,
+ fee_details: None,
+ confirmations: 0,
};
vec![transfer, transfer1, transfer2, transfer3]
}
-pub(crate) async fn init_nft_list_storage(chain: &Chain) -> impl NftListStorageOps + NftTransferHistoryStorageOps {
+pub(crate) async fn get_nft_ctx(_chain: &Chain) -> Arc {
+ #[cfg(not(target_arch = "wasm32"))]
+ let ctx = mm_ctx_with_custom_async_db().await;
+ #[cfg(target_arch = "wasm32")]
let ctx = mm_ctx_with_custom_db();
- let storage = NftStorageBuilder::new(&ctx).build().unwrap();
- NftListStorageOps::init(&storage, chain).await.unwrap();
- let is_initialized = NftListStorageOps::is_initialized(&storage, chain).await.unwrap();
- assert!(is_initialized);
- storage
-}
-
-pub(crate) async fn init_nft_history_storage(chain: &Chain) -> impl NftListStorageOps + NftTransferHistoryStorageOps {
- let ctx = mm_ctx_with_custom_db();
- let storage = NftStorageBuilder::new(&ctx).build().unwrap();
- NftTransferHistoryStorageOps::init(&storage, chain).await.unwrap();
- let is_initialized = NftTransferHistoryStorageOps::is_initialized(&storage, chain)
- .await
- .unwrap();
- assert!(is_initialized);
- storage
+ NftCtx::from_ctx(&ctx).unwrap()
}
diff --git a/mm2src/coins/nft/storage/mod.rs b/mm2src/coins/nft/storage/mod.rs
index 14cc9243f0..c28c33ea54 100644
--- a/mm2src/coins/nft/storage/mod.rs
+++ b/mm2src/coins/nft/storage/mod.rs
@@ -1,13 +1,12 @@
+use crate::eth::EthTxFeeDetails;
use crate::nft::nft_structs::{Chain, Nft, NftList, NftListFilters, NftTokenAddrId, NftTransferHistory,
NftTransferHistoryFilters, NftsTransferHistoryList, TransferMeta};
use crate::WithdrawError;
use async_trait::async_trait;
-use derive_more::Display;
use ethereum_types::Address;
-use mm2_core::mm_ctx::MmArc;
use mm2_err_handle::mm_error::MmResult;
use mm2_err_handle::mm_error::{NotEqual, NotMmError};
-use mm2_number::BigDecimal;
+use mm2_number::{BigDecimal, BigUint};
use serde::{Deserialize, Serialize};
use std::collections::HashSet;
use std::num::NonZeroUsize;
@@ -62,14 +61,14 @@ pub trait NftListStorageOps {
&self,
chain: &Chain,
token_address: String,
- token_id: BigDecimal,
+ token_id: BigUint,
) -> MmResult