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
9 changes: 1 addition & 8 deletions mm2src/coins/eth.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6620,14 +6620,7 @@ pub async fn get_eth_address(

let (_, derivation_method) =
build_address_and_priv_key_policy(ctx, ticker, conf, priv_key_policy, path_to_address, None).await?;
let my_address = match derivation_method {
EthDerivationMethod::SingleAddress(my_address) => my_address,
EthDerivationMethod::HDWallet(_) => {
return Err(MmError::new(GetEthAddressError::UnexpectedDerivationMethod(
UnexpectedDerivationMethod::UnsupportedError("HDWallet is not supported for NFT yet!".to_owned()),
)));
},
};
let my_address = derivation_method.single_addr_or_err().await?;

Ok(MyWalletAddress {
coin: ticker.to_owned(),
Expand Down
63 changes: 27 additions & 36 deletions mm2src/coins/nft.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,15 +12,15 @@ pub(crate) mod storage;
#[cfg(any(test, target_arch = "wasm32"))] mod nft_tests;

use crate::hd_wallet::AddrToString;
use crate::{coin_conf, get_my_address, lp_coinfind_or_err, CoinsContext, HDPathAccountToAddressId, MarketCoinOps,
MmCoinEnum, MmCoinStruct, MyAddressReq, WithdrawError};
use crate::{lp_coinfind_or_err, CoinWithDerivationMethod, CoinsContext, MarketCoinOps, MmCoinEnum, MmCoinStruct,
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::{get_eth_address, withdraw_erc1155, withdraw_erc721, EthCoin, EthCoinType, EthTxFeeDetails,
LegacyGasPrice, PayForGasOption};
use crate::eth::{withdraw_erc1155, withdraw_erc721, EthCoin, EthCoinType, EthTxFeeDetails, LegacyGasPrice,
PayForGasOption};
use crate::nft::nft_errors::{ClearNftDbError, MetaFromUrlError, ProtectFromSpamError, TransferConfirmationsError,
UpdateSpamPhishingError};
use crate::nft::nft_structs::{build_nft_with_empty_meta, BuildNftFields, ClearNftDbReq, NftCommon, NftCtx, NftInfo,
Expand Down Expand Up @@ -238,16 +238,17 @@ pub async fn update_nft(ctx: MmArc, req: UpdateNftReq) -> MmResult<(), UpdateNft
NftTransferHistoryStorageOps::init(&storage, chain).await?;
None
};
// TODO activate and use global NFT instead of ETH coin after adding enable nft using coin conf support
let coin_enum = lp_coinfind_or_err(&ctx, chain.to_ticker()).await?;
let eth_coin = match coin_enum {
let coin_enum = lp_coinfind_or_err(&ctx, chain.to_nft_ticker()).await?;
let global_nft = match coin_enum {
MmCoinEnum::EthCoin(eth_coin) => eth_coin,
_ => {
return MmError::err(UpdateNftError::CoinDoesntSupportNft {
coin: coin_enum.ticker().to_owned(),
})
},
};
let my_address = global_nft.derivation_method().single_addr_or_err().await?;
let my_address_str = my_address.addr_to_string();
let proxy_sign = if req.komodo_proxy {
let uri = Uri::from_str(req.url.as_ref()).map_err(|e| UpdateNftError::Internal(e.to_string()))?;
let proxy_sign = RawMessage::sign(p2p_ctx.keypair(), &uri, 0, common::PROXY_REQUEST_EXPIRATION_SEC)
Expand All @@ -264,14 +265,14 @@ pub async fn update_nft(ctx: MmArc, req: UpdateNftReq) -> MmResult<(), UpdateNft
proxy_sign,
};

let nft_transfers = get_moralis_nft_transfers(&ctx, from_block, eth_coin, &wrapper).await?;
let nft_transfers = get_moralis_nft_transfers(from_block, global_nft, &my_address_str, &wrapper).await?;
storage.add_transfers_to_history(*chain, nft_transfers).await?;

let nft_block = match NftListStorageOps::get_last_block_number(&storage, chain).await {
Ok(Some(block)) => block,
Ok(None) => {
// if there are no rows in NFT LIST table we can try to get nft list from moralis.
let nft_list = cache_nfts_from_moralis(&ctx, &storage, &wrapper).await?;
let nft_list = cache_nfts_from_moralis(&my_address_str, &storage, &wrapper).await?;
update_meta_in_transfers(&storage, chain, nft_list).await?;
update_transfers_with_empty_meta(&storage, &wrapper).await?;
update_spam(&storage, *chain, &req.url_antispam).await?;
Expand All @@ -281,7 +282,7 @@ pub async fn update_nft(ctx: MmArc, req: UpdateNftReq) -> MmResult<(), UpdateNft
Err(_) => {
// if there is an error, then NFT LIST table doesn't exist, so we need to cache nft list from moralis.
NftListStorageOps::init(&storage, chain).await?;
let nft_list = cache_nfts_from_moralis(&ctx, &storage, &wrapper).await?;
let nft_list = cache_nfts_from_moralis(&my_address_str, &storage, &wrapper).await?;
update_meta_in_transfers(&storage, chain, nft_list).await?;
update_transfers_with_empty_meta(&storage, &wrapper).await?;
update_spam(&storage, *chain, &req.url_antispam).await?;
Expand All @@ -304,7 +305,7 @@ pub async fn update_nft(ctx: MmArc, req: UpdateNftReq) -> MmResult<(), UpdateNft
last_nft_block: nft_block.to_string(),
});
}
update_nft_list(ctx.clone(), &storage, scanned_block + 1, &wrapper).await?;
update_nft_list(&storage, scanned_block + 1, &my_address_str, &wrapper).await?;
update_nft_global_in_coins_ctx(&ctx, &storage, *chain).await?;
update_transfers_with_empty_meta(&storage, &wrapper).await?;
update_spam(&storage, *chain, &req.url_antispam).await?;
Expand Down Expand Up @@ -636,13 +637,13 @@ where
Ok(())
}

async fn get_moralis_nft_list(ctx: &MmArc, wrapper: &UrlSignWrapper<'_>) -> MmResult<Vec<Nft>, GetNftInfoError> {
async fn get_moralis_nft_list(
wallet_address: &str,
wrapper: &UrlSignWrapper<'_>,
) -> MmResult<Vec<Nft>, GetNftInfoError> {
let mut res_list = Vec::new();
let chain = wrapper.chain;
let ticker = chain.to_ticker();
let conf = coin_conf(ctx, ticker);
let my_address = get_eth_address(ctx, &conf, ticker, &HDPathAccountToAddressId::default()).await?;
let uri_without_cursor = construct_moralis_uri_for_nft(wrapper.orig_url, &my_address.wallet_address, chain)?;
let uri_without_cursor = construct_moralis_uri_for_nft(wrapper.orig_url, wallet_address, chain)?;

// The cursor returned in the previous response (used for getting the next page).
let mut cursor = String::new();
Expand Down Expand Up @@ -735,24 +736,21 @@ fn process_nft_list_for_activation(
}

async fn get_moralis_nft_transfers(
ctx: &MmArc,
from_block: Option<u64>,
eth_coin: EthCoin,
global_nft: EthCoin,
wallet_address: &str,
wrapper: &UrlSignWrapper<'_>,
) -> MmResult<Vec<NftTransferHistory>, GetNftInfoError> {
let chain = wrapper.chain;
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, &HDPathAccountToAddressId::default()).await?;

let mut uri_without_cursor = wrapper.orig_url.clone();
uri_without_cursor
.path_segments_mut()
.map_to_mm(|_| GetNftInfoError::Internal("Invalid URI".to_string()))?
.push(MORALIS_API)
.push(MORALIS_ENDPOINT_V)
.push(&my_address.wallet_address)
.push(wallet_address)
.push("nft")
.push("transfers");
let from_block = match from_block {
Expand All @@ -768,13 +766,12 @@ async fn get_moralis_nft_transfers(

// 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 {
// Create a new URL instance from uri_without_cursor and modify its query to include the cursor if present
let uri = format!("{}{}", uri_without_cursor, cursor);
let response = build_and_send_request(uri.as_str(), &wrapper.proxy_sign).await?;
if let Some(transfer_list) = response["result"].as_array() {
process_transfer_list(transfer_list, chain, wallet_address.as_str(), &eth_coin, &mut res_list).await?;
process_transfer_list(transfer_list, chain, wallet_address, &global_nft, &mut res_list).await?;
// if the cursor is not null, there are other NFTs transfers on next page,
// and we need to send new request with cursor to get info from the next page.
if let Some(cursor_res) = response["cursor"].as_str() {
Expand All @@ -794,7 +791,7 @@ async fn process_transfer_list(
transfer_list: &[Json],
chain: &Chain,
wallet_address: &str,
eth_coin: &EthCoin,
global_nft: &EthCoin,
res_list: &mut Vec<NftTransferHistory>,
) -> MmResult<(), GetNftInfoError> {
for transfer in transfer_list {
Expand All @@ -805,7 +802,7 @@ async fn process_transfer_list(
};
let status = get_transfer_status(wallet_address, &transfer_moralis.common.to_address.addr_to_string());
let block_timestamp = parse_rfc3339_to_timestamp(&transfer_moralis.block_timestamp)?;
let fee_details = get_fee_details(eth_coin, &transfer_moralis.common.transaction_hash).await;
let fee_details = get_fee_details(global_nft, &transfer_moralis.common.transaction_hash).await;
let transfer_history = NftTransferHistory {
common: NftTransferCommon {
block_hash: transfer_moralis.common.block_hash,
Expand Down Expand Up @@ -844,7 +841,6 @@ async fn process_transfer_list(
Ok(())
}

// TODO: get fee details from non fungible token instead of eth coin?
async fn get_fee_details(eth_coin: &EthCoin, transaction_hash: &str) -> Option<EthTxFeeDetails> {
let hash = H256::from_str(transaction_hash).ok()?;
let receipt = eth_coin.web3().await.ok()?.eth().transaction_receipt(hash).await.ok()?;
Expand Down Expand Up @@ -1017,20 +1013,15 @@ fn get_transfer_status(my_wallet: &str, to_address: &str) -> TransferStatus {
/// `update_nft_list` function gets nft transfers from NFT HISTORY table, iterates through them
/// and updates NFT LIST table info.
async fn update_nft_list<T: NftListStorageOps + NftTransferHistoryStorageOps>(
ctx: MmArc,
storage: &T,
scan_from_block: u64,
wallet_address: &str,
wrapper: &UrlSignWrapper<'_>,
) -> MmResult<(), UpdateNftError> {
let chain = wrapper.chain;
let transfers = storage.get_transfers_from_block(*chain, scan_from_block).await?;
let req = MyAddressReq {
coin: chain.to_ticker().to_string(),
path_to_address: HDPathAccountToAddressId::default(),
};
let my_address = get_my_address(ctx.clone(), req).await?.wallet_address.to_lowercase();
for transfer in transfers.into_iter() {
handle_nft_transfer(storage, wrapper, transfer, &my_address).await?;
handle_nft_transfer(storage, wrapper, transfer, wallet_address).await?;
}
Ok(())
}
Expand Down Expand Up @@ -1295,11 +1286,11 @@ async fn mark_as_spam_and_build_empty_meta<T: NftListStorageOps + NftTransferHis
}

async fn cache_nfts_from_moralis<T: NftListStorageOps + NftTransferHistoryStorageOps>(
ctx: &MmArc,
wallet_address: &str,
storage: &T,
wrapper: &UrlSignWrapper<'_>,
) -> MmResult<Vec<Nft>, UpdateNftError> {
let nft_list = get_moralis_nft_list(ctx, wrapper).await?;
let nft_list = get_moralis_nft_list(wallet_address, wrapper).await?;
let last_scanned_block = NftTransferHistoryStorageOps::get_last_block_number(storage, wrapper.chain)
.await?
.unwrap_or(0);
Expand Down
8 changes: 7 additions & 1 deletion mm2src/coins/nft/nft_errors.rs
Original file line number Diff line number Diff line change
Expand Up @@ -217,6 +217,7 @@ pub enum UpdateNftError {
},
#[display(fmt = "Private key policy is not allowed: {}", _0)]
PrivKeyPolicyNotAllowed(PrivKeyPolicyNotAllowed),
UnexpectedDerivationMethod(UnexpectedDerivationMethod),
}

impl From<GetNftInfoError> for UpdateNftError {
Expand Down Expand Up @@ -264,6 +265,10 @@ impl From<GenerateSignedMessageError> for UpdateNftError {
}
}

impl From<UnexpectedDerivationMethod> for UpdateNftError {
fn from(e: UnexpectedDerivationMethod) -> Self { Self::UnexpectedDerivationMethod(e) }
}

impl HttpStatusCode for UpdateNftError {
fn status_code(&self) -> StatusCode {
match self {
Expand All @@ -283,7 +288,8 @@ impl HttpStatusCode for UpdateNftError {
| UpdateNftError::ProtectFromSpamError(_)
| UpdateNftError::NoSuchCoin { .. }
| UpdateNftError::CoinDoesntSupportNft { .. }
| UpdateNftError::PrivKeyPolicyNotAllowed(_) => StatusCode::INTERNAL_SERVER_ERROR,
| UpdateNftError::PrivKeyPolicyNotAllowed(_)
| UpdateNftError::UnexpectedDerivationMethod(_) => StatusCode::INTERNAL_SERVER_ERROR,
}
}
}
Expand Down
Loading