Skip to content
Merged
Show file tree
Hide file tree
Changes from all 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
5 changes: 5 additions & 0 deletions mm2src/coins/eth.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5293,6 +5293,10 @@ fn checksum_address(addr: &str) -> String {
result
}

/// `eth_addr_to_hex` converts Address to hex format.
/// Note: the result will be in lowercase.
pub(crate) fn eth_addr_to_hex(address: &Address) -> String { format!("{:#02x}", address) }

/// Checks that input is valid mixed-case checksum form address
/// The input must be 0x prefixed hex string
fn is_valid_checksum_addr(addr: &str) -> bool { addr == checksum_address(addr) }
Expand Down Expand Up @@ -5408,6 +5412,7 @@ impl From<CryptoCtxError> for GetEthAddressError {
}

/// `get_eth_address` returns wallet address for coin with `ETH` protocol type.
/// Note: result address has mixed-case checksum form.
pub async fn get_eth_address(ctx: &MmArc, ticker: &str) -> MmResult<MyWalletAddress, GetEthAddressError> {
let priv_key_policy = PrivKeyBuildPolicy::detect_priv_key_policy(ctx)?;
// Convert `PrivKeyBuildPolicy` to `EthPrivKeyBuildPolicy` if it's possible.
Expand Down
5 changes: 0 additions & 5 deletions mm2src/coins/lp_coins.rs
Original file line number Diff line number Diff line change
Expand Up @@ -100,7 +100,6 @@ cfg_wasm32! {
use mm2_db::indexed_db::{ConstructibleDb, DbLocked, SharedDb};
use tx_history_storage::wasm::{clear_tx_history, load_tx_history, save_tx_history, TxHistoryDb};
pub type TxHistoryDbLocked<'a> = DbLocked<'a, TxHistoryDb>;
use nft::storage::wasm::nft_idb::NftCacheIDB;
}

// using custom copy of try_fus as futures crate was renamed to futures01
Expand Down Expand Up @@ -2547,8 +2546,6 @@ pub struct CoinsContext {
tx_history_db: SharedDb<TxHistoryDb>,
#[cfg(target_arch = "wasm32")]
hd_wallet_db: SharedDb<HDWalletDb>,
#[cfg(target_arch = "wasm32")]
pub nft_cache_db: SharedDb<NftCacheIDB>,
}

#[derive(Debug)]
Expand All @@ -2573,8 +2570,6 @@ impl CoinsContext {
tx_history_db: ConstructibleDb::new(ctx).into_shared(),
#[cfg(target_arch = "wasm32")]
hd_wallet_db: ConstructibleDb::new_shared_db(ctx).into_shared(),
#[cfg(target_arch = "wasm32")]
nft_cache_db: ConstructibleDb::new(ctx).into_shared(),
})
})))
}
Expand Down
102 changes: 79 additions & 23 deletions mm2src/coins/nft.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,32 +14,35 @@ use nft_structs::{Chain, ContractType, ConvertChain, Nft, NftFromMoralis, NftLis
NftTransferHistory, NftTransfersReq, NftTxHistoryFromMoralis, NftsTransferHistoryList,
TransactionNftDetails, UpdateNftReq, WithdrawNftReq};

use crate::eth::{get_eth_address, withdraw_erc1155, withdraw_erc721};
use crate::eth::{eth_addr_to_hex, get_eth_address, withdraw_erc1155, withdraw_erc721};
use crate::nft::nft_errors::ProtectFromSpamError;
use crate::nft::nft_structs::{NftCommon, NftTransferCommon, RefreshMetadataReq, TransferStatus, TxMeta, UriMeta};
use crate::nft::nft_structs::{NftCommon, NftCtx, NftTransferCommon, RefreshMetadataReq, TransferStatus, TxMeta,
UriMeta};
use crate::nft::storage::{NftListStorageOps, NftStorageBuilder, NftTxHistoryStorageOps};
use common::{parse_rfc3339_to_timestamp, APPLICATION_JSON};
use ethereum_types::Address;
use http::header::ACCEPT;
use mm2_err_handle::map_to_mm::MapToMmResult;
use mm2_number::BigDecimal;
use regex::Regex;
use serde_json::Value as Json;
use std::cmp::Ordering;
use std::str::FromStr;

const MORALIS_API_ENDPOINT: &str = "api/v2";
/// query parameters for moralis request: The format of the token ID
const MORALIS_FORMAT_QUERY_NAME: &str = "format";
const MORALIS_FORMAT_QUERY_VALUE: &str = "decimal";
/// query parameters for moralis request: The transfer direction
const MORALIS_DIRECTION_QUERY_NAME: &str = "direction";
const MORALIS_DIRECTION_QUERY_VALUE: &str = "both";
/// The minimum block number from which to get the transfers
const MORALIS_FROM_BLOCK_QUERY_NAME: &str = "from_block";

pub type WithdrawNftResult = Result<TransactionNftDetails, MmError<WithdrawError>>;

/// `get_nft_list` function returns list of NFTs on requested chains owned by user.
pub async fn get_nft_list(ctx: MmArc, req: NftListReq) -> MmResult<NftList, GetNftInfoError> {
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()?;
for chain in req.chains.iter() {
if !NftListStorageOps::is_initialized(&storage, chain).await? {
Expand All @@ -60,6 +63,9 @@ pub async fn get_nft_list(ctx: MmArc, req: NftListReq) -> MmResult<NftList, GetN

/// `get_nft_metadata` function returns info of one specific NFT.
pub async fn get_nft_metadata(ctx: MmArc, req: NftMetadataReq) -> MmResult<Nft, GetNftInfoError> {
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()?;
if !NftListStorageOps::is_initialized(&storage, &req.chain).await? {
NftListStorageOps::init(&storage, &req.chain).await?;
Expand All @@ -80,6 +86,9 @@ pub async fn get_nft_metadata(ctx: MmArc, req: NftMetadataReq) -> MmResult<Nft,

/// `get_nft_transfers` function returns a transfer history of NFTs on requested chains owned by user.
pub async fn get_nft_transfers(ctx: MmArc, req: NftTransfersReq) -> MmResult<NftsTransferHistoryList, GetNftInfoError> {
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()?;
for chain in req.chains.iter() {
if !NftTxHistoryStorageOps::is_initialized(&storage, chain).await? {
Expand All @@ -100,6 +109,9 @@ pub async fn get_nft_transfers(ctx: MmArc, req: NftTransfersReq) -> MmResult<Nft

/// `update_nft` function updates cache of nft transfer history and nft list.
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()?;
for chain in req.chains.iter() {
let tx_history_initialized = NftTxHistoryStorageOps::is_initialized(&storage, chain).await?;
Expand Down Expand Up @@ -160,6 +172,9 @@ pub async fn update_nft(ctx: MmArc, req: UpdateNftReq) -> MmResult<(), UpdateNft
}

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 moralis_meta = get_moralis_metadata(
format!("{:#02x}", req.token_address),
Expand All @@ -174,7 +189,7 @@ pub async fn refresh_nft_metadata(ctx: MmArc, req: RefreshMetadataReq) -> MmResu
chain: req.chain,
protect_from_spam: false,
};
let mut nft_db = get_nft_metadata(ctx, req).await?;
let mut nft_db = get_nft_metadata(ctx.clone(), req).await?;
let token_uri = check_moralis_ipfs_bafy(moralis_meta.common.token_uri.as_deref());
let uri_meta = get_uri_meta(token_uri.as_deref(), moralis_meta.common.metadata.as_deref()).await;
nft_db.common.collection_name = moralis_meta.common.collection_name;
Expand Down Expand Up @@ -266,7 +281,6 @@ async fn get_moralis_nft_transfers(
.query_pairs_mut()
.append_pair("chain", &chain.to_string())
.append_pair(MORALIS_FORMAT_QUERY_NAME, MORALIS_FORMAT_QUERY_VALUE)
.append_pair(MORALIS_DIRECTION_QUERY_NAME, MORALIS_DIRECTION_QUERY_VALUE)
.append_pair(MORALIS_FROM_BLOCK_QUERY_NAME, &from_block);
drop_mutability!(uri_without_cursor);

Expand All @@ -283,7 +297,7 @@ async fn get_moralis_nft_transfers(
Some(contract_type) => contract_type,
None => continue,
};
let status = get_tx_status(&wallet_address, &transfer_moralis.common.to_address);
let status = get_tx_status(&wallet_address, &eth_addr_to_hex(&transfer_moralis.common.to_address));
let block_timestamp = parse_rfc3339_to_timestamp(&transfer_moralis.block_timestamp)?;
let transfer_history = NftTransferHistory {
common: NftTransferCommon {
Expand Down Expand Up @@ -517,16 +531,25 @@ async fn handle_send_erc721<T: NftListStorageOps + NftTxHistoryStorageOps>(
tx: NftTransferHistory,
) -> MmResult<(), UpdateNftError> {
let nft_db = storage
.get_nft(chain, tx.common.token_address.clone(), tx.common.token_id.clone())
.get_nft(
chain,
eth_addr_to_hex(&tx.common.token_address),
tx.common.token_id.clone(),
)
.await?
.ok_or_else(|| UpdateNftError::TokenNotFoundInWallet {
token_address: tx.common.token_address.clone(),
token_address: eth_addr_to_hex(&tx.common.token_address),
token_id: tx.common.token_id.to_string(),
})?;
let tx_meta = TxMeta::from(nft_db);
storage.update_txs_meta_by_token_addr_id(chain, tx_meta).await?;
storage
.remove_nft_from_list(chain, tx.common.token_address, tx.common.token_id, tx.block_number)
.remove_nft_from_list(
chain,
eth_addr_to_hex(&tx.common.token_address),
tx.common.token_id,
tx.block_number,
)
.await?;
Ok(())
}
Expand All @@ -539,13 +562,17 @@ async fn handle_receive_erc721<T: NftListStorageOps + NftTxHistoryStorageOps>(
my_address: &str,
) -> MmResult<(), UpdateNftError> {
let nft = match storage
.get_nft(chain, tx.common.token_address.clone(), tx.common.token_id.clone())
.get_nft(
chain,
eth_addr_to_hex(&tx.common.token_address),
tx.common.token_id.clone(),
)
.await?
{
Some(mut nft_db) => {
// An error is raised if user tries to receive an identical ERC-721 token they already own
// and if owner address != from address
if my_address != tx.common.from_address {
if my_address != eth_addr_to_hex(&tx.common.from_address) {
return MmError::err(UpdateNftError::AttemptToReceiveAlreadyOwnedErc721 {
tx_hash: tx.common.transaction_hash,
});
Expand All @@ -559,10 +586,17 @@ async fn handle_receive_erc721<T: NftListStorageOps + NftTxHistoryStorageOps>(
},
// If token isn't in NFT LIST table then add nft to the table.
None => {
let mut nft = get_moralis_metadata(tx.common.token_address, tx.common.token_id, chain, url).await?;
let mut nft = get_moralis_metadata(
eth_addr_to_hex(&tx.common.token_address),
tx.common.token_id,
chain,
url,
)
.await?;
// sometimes moralis updates Get All NFTs (which also affects Get Metadata) later
// than History by Wallet update
nft.common.owner_of = my_address.to_string();
nft.common.owner_of =
Address::from_str(my_address).map_to_mm(|e| UpdateNftError::InvalidHexString(e.to_string()))?;
nft.block_number = tx.block_number;
drop_mutability!(nft);
storage
Expand All @@ -582,16 +616,25 @@ async fn handle_send_erc1155<T: NftListStorageOps + NftTxHistoryStorageOps>(
tx: NftTransferHistory,
) -> MmResult<(), UpdateNftError> {
let mut nft_db = storage
.get_nft(chain, tx.common.token_address.clone(), tx.common.token_id.clone())
.get_nft(
chain,
eth_addr_to_hex(&tx.common.token_address),
tx.common.token_id.clone(),
)
.await?
.ok_or_else(|| UpdateNftError::TokenNotFoundInWallet {
token_address: tx.common.token_address.clone(),
token_address: eth_addr_to_hex(&tx.common.token_address),
token_id: tx.common.token_id.to_string(),
})?;
match nft_db.common.amount.cmp(&tx.common.amount) {
Ordering::Equal => {
storage
.remove_nft_from_list(chain, tx.common.token_address, tx.common.token_id, tx.block_number)
.remove_nft_from_list(
chain,
eth_addr_to_hex(&tx.common.token_address),
tx.common.token_id,
tx.block_number,
)
.await?;
},
Ordering::Greater => {
Expand Down Expand Up @@ -620,13 +663,17 @@ async fn handle_receive_erc1155<T: NftListStorageOps + NftTxHistoryStorageOps>(
my_address: &str,
) -> MmResult<(), UpdateNftError> {
let nft = match storage
.get_nft(chain, tx.common.token_address.clone(), tx.common.token_id.clone())
.get_nft(
chain,
eth_addr_to_hex(&tx.common.token_address),
tx.common.token_id.clone(),
)
.await?
{
Some(mut nft_db) => {
// if owner address == from address, then owner sent tokens to themself,
// which means that the amount will not change.
if my_address != tx.common.from_address {
if my_address != eth_addr_to_hex(&tx.common.from_address) {
nft_db.common.amount += tx.common.amount;
}
nft_db.block_number = tx.block_number;
Expand All @@ -638,16 +685,22 @@ async fn handle_receive_erc1155<T: NftListStorageOps + NftTxHistoryStorageOps>(
},
// If token isn't in NFT LIST table then add nft to the table.
None => {
let moralis_meta =
get_moralis_metadata(tx.common.token_address, tx.common.token_id.clone(), chain, url).await?;
let moralis_meta = get_moralis_metadata(
eth_addr_to_hex(&tx.common.token_address),
tx.common.token_id.clone(),
chain,
url,
)
.await?;
let token_uri = check_moralis_ipfs_bafy(moralis_meta.common.token_uri.as_deref());
let uri_meta = get_uri_meta(token_uri.as_deref(), moralis_meta.common.metadata.as_deref()).await;
let nft = Nft {
common: NftCommon {
token_address: moralis_meta.common.token_address,
token_id: moralis_meta.common.token_id,
amount: tx.common.amount,
owner_of: my_address.to_string(),
owner_of: Address::from_str(my_address)
.map_to_mm(|e| UpdateNftError::InvalidHexString(e.to_string()))?,
token_hash: moralis_meta.common.token_hash,
collection_name: moralis_meta.common.collection_name,
symbol: moralis_meta.common.symbol,
Expand Down Expand Up @@ -681,6 +734,9 @@ pub(crate) async fn find_wallet_nft_amount(
token_address: String,
token_id: BigDecimal,
) -> MmResult<BigDecimal, GetNftInfoError> {
let nft_ctx = NftCtx::from_ctx(ctx).map_err(GetNftInfoError::Internal)?;
let _lock = nft_ctx.guard.lock().await;

let storage = NftStorageBuilder::new(ctx).build()?;
if !NftListStorageOps::is_initialized(&storage, chain).await? {
NftListStorageOps::init(&storage, chain).await?;
Expand Down
5 changes: 4 additions & 1 deletion mm2src/coins/nft/nft_errors.rs
Original file line number Diff line number Diff line change
Expand Up @@ -166,6 +166,8 @@ pub enum UpdateNftError {
AttemptToReceiveAlreadyOwnedErc721 {
tx_hash: String,
},
#[display(fmt = "Invalid hex string: {}", _0)]
InvalidHexString(String),
}

impl From<CreateNftStorageError> for UpdateNftError {
Expand Down Expand Up @@ -199,7 +201,8 @@ impl HttpStatusCode for UpdateNftError {
| UpdateNftError::InsufficientAmountInCache { .. }
| UpdateNftError::InvalidBlockOrder { .. }
| UpdateNftError::LastScannedBlockNotFound { .. }
| UpdateNftError::AttemptToReceiveAlreadyOwnedErc721 { .. } => StatusCode::INTERNAL_SERVER_ERROR,
| UpdateNftError::AttemptToReceiveAlreadyOwnedErc721 { .. }
| UpdateNftError::InvalidHexString(_) => StatusCode::INTERNAL_SERVER_ERROR,
}
}
}
Expand Down
Loading