From 884d1e206781e82ea9261dded399cb0c633165de Mon Sep 17 00:00:00 2001 From: shamardy Date: Tue, 11 Mar 2025 16:44:30 +0200 Subject: [PATCH 1/3] make update_nft work with hd wallets using the enabled address --- mm2src/coins/eth.rs | 9 +-------- mm2src/coins/lp_coins.rs | 4 ++-- 2 files changed, 3 insertions(+), 10 deletions(-) diff --git a/mm2src/coins/eth.rs b/mm2src/coins/eth.rs index 55fabfd7b5..5aeb248871 100644 --- a/mm2src/coins/eth.rs +++ b/mm2src/coins/eth.rs @@ -6634,14 +6634,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(), diff --git a/mm2src/coins/lp_coins.rs b/mm2src/coins/lp_coins.rs index 1888e81ad4..8d0bc1cc9c 100644 --- a/mm2src/coins/lp_coins.rs +++ b/mm2src/coins/lp_coins.rs @@ -4997,7 +4997,7 @@ pub async fn delegations_info(ctx: MmArc, req: DelegationsInfo) -> Result { let MmCoinEnum::QtumCoin(qtum) = coin else { return MmError::err(StakingInfoError::InvalidPayload { - reason: format!("{} is not a Qtum coin", req.coin) + reason: format!("{} is not a Qtum coin", req.coin), }); }; @@ -5048,7 +5048,7 @@ pub async fn claim_staking_rewards(ctx: MmArc, req: ClaimStakingRewardsRequest) let MmCoinEnum::Tendermint(tendermint) = coin else { return MmError::err(DelegationError::InvalidPayload { - reason: format!("{} is not a Cosmos coin", req.coin) + reason: format!("{} is not a Cosmos coin", req.coin), }); }; From cce4a615cad1dc9270e3b1deece003292d198345 Mon Sep 17 00:00:00 2001 From: laruh Date: Thu, 20 Mar 2025 22:24:27 +0700 Subject: [PATCH 2/3] use global nft from CoinsContext in update_nft RPC --- mm2src/coins/nft.rs | 63 +++++++++++++++------------------- mm2src/coins/nft/nft_errors.rs | 8 ++++- 2 files changed, 34 insertions(+), 37 deletions(-) diff --git a/mm2src/coins/nft.rs b/mm2src/coins/nft.rs index afc5f260a9..e62b7ce53a 100644 --- a/mm2src/coins/nft.rs +++ b/mm2src/coins/nft.rs @@ -11,15 +11,15 @@ pub(crate) mod storage; #[cfg(any(test, target_arch = "wasm32"))] mod nft_tests; -use crate::{coin_conf, get_my_address, lp_coinfind_or_err, CoinsContext, HDPathAccountToAddressId, MarketCoinOps, - MmCoinEnum, MmCoinStruct, MyAddressReq, WithdrawError}; +use crate::{lp_coinfind_or_err, AddrToString, 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::{eth_addr_to_hex, get_eth_address, withdraw_erc1155, withdraw_erc721, EthCoin, EthCoinType, - EthTxFeeDetails, LegacyGasPrice, PayForGasOption}; +use crate::eth::{eth_addr_to_hex, 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, @@ -237,9 +237,8 @@ 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 { @@ -247,6 +246,8 @@ pub async fn update_nft(ctx: MmArc, req: UpdateNftReq) -> MmResult<(), UpdateNft }) }, }; + 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) @@ -263,14 +264,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?; @@ -280,7 +281,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?; @@ -303,7 +304,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?; @@ -635,13 +636,13 @@ where Ok(()) } -async fn get_moralis_nft_list(ctx: &MmArc, wrapper: &UrlSignWrapper<'_>) -> MmResult, GetNftInfoError> { +async fn get_moralis_nft_list( + wallet_address: &str, + wrapper: &UrlSignWrapper<'_>, +) -> MmResult, 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(); @@ -734,16 +735,13 @@ fn process_nft_list_for_activation( } async fn get_moralis_nft_transfers( - ctx: &MmArc, from_block: Option, - eth_coin: EthCoin, + global_nft: EthCoin, + wallet_address: &str, wrapper: &UrlSignWrapper<'_>, ) -> MmResult, 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 @@ -751,7 +749,7 @@ async fn get_moralis_nft_transfers( .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 { @@ -767,13 +765,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(), ð_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() { @@ -793,7 +790,7 @@ async fn process_transfer_list( transfer_list: &[Json], chain: &Chain, wallet_address: &str, - eth_coin: &EthCoin, + global_nft: &EthCoin, res_list: &mut Vec, ) -> MmResult<(), GetNftInfoError> { for transfer in transfer_list { @@ -804,7 +801,7 @@ async fn process_transfer_list( }; 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(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, @@ -843,7 +840,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 { let hash = H256::from_str(transaction_hash).ok()?; let receipt = eth_coin.web3().await.ok()?.eth().transaction_receipt(hash).await.ok()?; @@ -1016,20 +1012,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( - 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(()) } @@ -1294,11 +1285,11 @@ async fn mark_as_spam_and_build_empty_meta( - ctx: &MmArc, + wallet_address: &str, storage: &T, wrapper: &UrlSignWrapper<'_>, ) -> MmResult, 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); diff --git a/mm2src/coins/nft/nft_errors.rs b/mm2src/coins/nft/nft_errors.rs index 12e8d326a0..74d5032cbe 100644 --- a/mm2src/coins/nft/nft_errors.rs +++ b/mm2src/coins/nft/nft_errors.rs @@ -217,6 +217,7 @@ pub enum UpdateNftError { }, #[display(fmt = "Private key policy is not allowed: {}", _0)] PrivKeyPolicyNotAllowed(PrivKeyPolicyNotAllowed), + UnexpectedDerivationMethod(UnexpectedDerivationMethod), } impl From for UpdateNftError { @@ -264,6 +265,10 @@ impl From for UpdateNftError { } } +impl From for UpdateNftError { + fn from(e: UnexpectedDerivationMethod) -> Self { Self::UnexpectedDerivationMethod(e) } +} + impl HttpStatusCode for UpdateNftError { fn status_code(&self) -> StatusCode { match self { @@ -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, } } } From 74674638ca352a348b69720851309e2b63e97e57 Mon Sep 17 00:00:00 2001 From: laruh Date: Thu, 20 Mar 2025 23:24:52 +0700 Subject: [PATCH 3/3] imports fixed --- mm2src/coins/nft.rs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/mm2src/coins/nft.rs b/mm2src/coins/nft.rs index 9166ed0d0f..ebd4c83146 100644 --- a/mm2src/coins/nft.rs +++ b/mm2src/coins/nft.rs @@ -12,15 +12,15 @@ pub(crate) mod storage; #[cfg(any(test, target_arch = "wasm32"))] mod nft_tests; use crate::hd_wallet::AddrToString; -use crate::{lp_coinfind_or_err, AddrToString, CoinWithDerivationMethod, CoinsContext, MarketCoinOps, MmCoinEnum, - MmCoinStruct, 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::{eth_addr_to_hex, 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,