From c142d972c3624515b92f650b8fbf77129bf80322 Mon Sep 17 00:00:00 2001 From: laruh Date: Fri, 20 Jan 2023 18:52:32 +0700 Subject: [PATCH 01/66] wip --- mm2src/coins/eth.rs | 8 ++++++++ mm2src/coins/eth/erc1155_abi.json | 0 mm2src/coins/eth/erc721_abi.json | 0 mm2src/coins/eth/nft/mod.rs | 2 ++ mm2src/coins/eth/nft/nft_errors.rs | 3 +++ mm2src/coins/eth/nft/nft_transfer.rs | 20 +++++++++++++++++++ .../mm2_main/src/rpc/dispatcher/dispatcher.rs | 4 +++- 7 files changed, 36 insertions(+), 1 deletion(-) create mode 100644 mm2src/coins/eth/erc1155_abi.json create mode 100644 mm2src/coins/eth/erc721_abi.json create mode 100644 mm2src/coins/eth/nft/mod.rs create mode 100644 mm2src/coins/eth/nft/nft_errors.rs create mode 100644 mm2src/coins/eth/nft/nft_transfer.rs diff --git a/mm2src/coins/eth.rs b/mm2src/coins/eth.rs index d994b08d5c..563414a646 100644 --- a/mm2src/coins/eth.rs +++ b/mm2src/coins/eth.rs @@ -45,6 +45,8 @@ use mm2_err_handle::prelude::*; use mm2_net::transport::{slurp_url, GuiAuthValidation, GuiAuthValidationGenerator, SlurpError}; use mm2_number::{BigDecimal, MmNumber}; #[cfg(test)] use mocktopus::macros::*; +use nft::nft_errors::WithdrawNFTError; +use nft::nft_transfer::{TransactionNFTDetails, WithdrawErc1155Request, WithdrawErc721Request}; use rand::seq::SliceRandom; use rpc::v1::types::Bytes as BytesJson; use secp256k1::PublicKey; @@ -83,6 +85,7 @@ pub use rlp; #[cfg(test)] mod eth_tests; #[cfg(target_arch = "wasm32")] mod eth_wasm_tests; +mod nft; mod web3_transport; #[path = "eth/v2_activation.rs"] pub mod v2_activation; @@ -136,6 +139,7 @@ lazy_static! { pub type Web3RpcFut = Box> + Send>; pub type Web3RpcResult = Result>; pub type GasStationResult = Result>; +type WithdrawNFTResult = Result>; #[derive(Debug, Display)] pub enum GasStationReqErr { @@ -791,6 +795,10 @@ async fn withdraw_impl(coin: EthCoin, req: WithdrawRequest) -> WithdrawResult { }) } +pub async fn withdraw_erc721(ctx: MmArc, req: WithdrawErc721Request) -> WithdrawNFTResult { todo!() } + +pub async fn withdraw_erc1155(ctx: MmArc, req: WithdrawErc1155Request) -> WithdrawNFTResult { todo!() } + #[derive(Clone)] pub struct EthCoin(Arc); impl Deref for EthCoin { diff --git a/mm2src/coins/eth/erc1155_abi.json b/mm2src/coins/eth/erc1155_abi.json new file mode 100644 index 0000000000..e69de29bb2 diff --git a/mm2src/coins/eth/erc721_abi.json b/mm2src/coins/eth/erc721_abi.json new file mode 100644 index 0000000000..e69de29bb2 diff --git a/mm2src/coins/eth/nft/mod.rs b/mm2src/coins/eth/nft/mod.rs new file mode 100644 index 0000000000..d48ddfabea --- /dev/null +++ b/mm2src/coins/eth/nft/mod.rs @@ -0,0 +1,2 @@ +pub(crate) mod nft_errors; +pub(crate) mod nft_transfer; diff --git a/mm2src/coins/eth/nft/nft_errors.rs b/mm2src/coins/eth/nft/nft_errors.rs new file mode 100644 index 0000000000..49ff7a9bc6 --- /dev/null +++ b/mm2src/coins/eth/nft/nft_errors.rs @@ -0,0 +1,3 @@ +pub enum WithdrawNFTError { + // todo +} diff --git a/mm2src/coins/eth/nft/nft_transfer.rs b/mm2src/coins/eth/nft/nft_transfer.rs new file mode 100644 index 0000000000..bb9eca600b --- /dev/null +++ b/mm2src/coins/eth/nft/nft_transfer.rs @@ -0,0 +1,20 @@ +use mm2_number::BigDecimal; + +pub struct WithdrawErc721Request { + coin: String, + to: String, + token_address: String, + token_id: BigDecimal, +} + +pub struct WithdrawErc1155Request { + coin: String, + to: String, + token_address: String, + token_id: BigDecimal, +} + +pub struct TransactionNFTDetails { + contract_type: String, + // todo +} diff --git a/mm2src/mm2_main/src/rpc/dispatcher/dispatcher.rs b/mm2src/mm2_main/src/rpc/dispatcher/dispatcher.rs index abf6587d8b..9b9bb79c09 100644 --- a/mm2src/mm2_main/src/rpc/dispatcher/dispatcher.rs +++ b/mm2src/mm2_main/src/rpc/dispatcher/dispatcher.rs @@ -9,7 +9,7 @@ use crate::{mm2::lp_stats::{add_node_to_version_stat, remove_node_from_version_s stop_version_stat_collection, update_version_stat_collection}, mm2::lp_swap::{recreate_swap_data, trade_preimage_rpc}, mm2::rpc::lp_commands::{get_public_key, get_public_key_hash}}; -use coins::eth::EthCoin; +use coins::eth::{withdraw_erc1155, withdraw_erc721, EthCoin}; use coins::my_tx_history_v2::my_tx_history_v2_rpc; use coins::rpc_command::{account_balance::account_balance, get_current_mtp::get_current_mtp_rpc, @@ -176,6 +176,8 @@ async fn dispatcher_v2(request: MmRpcRequest, ctx: MmArc) -> DispatcherResult handle_mmrpc(ctx, request, update_version_stat_collection).await, "verify_message" => handle_mmrpc(ctx, request, verify_message).await, "withdraw" => handle_mmrpc(ctx, request, withdraw).await, + "withdraw_erc721" => handle_mmrpc(ctx, request, withdraw_erc721).await, + "withdraw_erc1155" => handle_mmrpc(ctx, request, withdraw_erc1155).await, #[cfg(not(target_arch = "wasm32"))] native_only_methods => match native_only_methods { #[cfg(all(not(target_os = "ios"), not(target_os = "android")))] From df5212a2723958a871f1fb6ec4d698e8ca229283 Mon Sep 17 00:00:00 2001 From: laruh Date: Sun, 22 Jan 2023 21:02:43 +0700 Subject: [PATCH 02/66] wip --- mm2src/coins/eth/nft/nft_transfer.rs | 24 +++++++++++++++++++++++- 1 file changed, 23 insertions(+), 1 deletion(-) diff --git a/mm2src/coins/eth/nft/nft_transfer.rs b/mm2src/coins/eth/nft/nft_transfer.rs index bb9eca600b..a455384659 100644 --- a/mm2src/coins/eth/nft/nft_transfer.rs +++ b/mm2src/coins/eth/nft/nft_transfer.rs @@ -1,4 +1,6 @@ +use crate::TxFeeDetails; use mm2_number::BigDecimal; +use rpc::v1::types::{Bytes as BytesJson, H256 as H256Json}; pub struct WithdrawErc721Request { coin: String, @@ -12,9 +14,29 @@ pub struct WithdrawErc1155Request { to: String, token_address: String, token_id: BigDecimal, + amount: BigDecimal, + #[serde(default)] + max: bool, } pub struct TransactionNFTDetails { + /// Raw bytes of signed transaction, this should be sent as is to `send_raw_transaction_bytes` RPC to broadcast the transaction + tx_hex: BytesJson, + /// Transaction hash in hexadecimal format + tx_hash: String, + /// NFTs are sent from these addresses + from: Vec, + /// NFTs are sent to these addresses + to: Vec, contract_type: String, - // todo + token_address: String, + token_id: BigDecimal, + amount: BigDecimal, + fee_details: Option, + /// Block height + block_height: u64, + /// Transaction timestamp + timestamp: i64, + /// Internal MM2 id used for internal transaction identification, for some coins it might be equal to transaction hash + internal_id: i64, } From 6ed1ee8825f965868ebc5ff48cbe3c6f1df7430c Mon Sep 17 00:00:00 2001 From: laruh Date: Tue, 24 Jan 2023 15:47:14 +0700 Subject: [PATCH 03/66] wip some structures were added --- mm2src/coins/eth.rs | 8 +- mm2src/coins/eth/nft/mod.rs | 2 +- mm2src/coins/eth/nft/nft_structs.rs | 126 +++++++++++++++++++++++++++ mm2src/coins/eth/nft/nft_transfer.rs | 42 --------- 4 files changed, 131 insertions(+), 47 deletions(-) create mode 100644 mm2src/coins/eth/nft/nft_structs.rs delete mode 100644 mm2src/coins/eth/nft/nft_transfer.rs diff --git a/mm2src/coins/eth.rs b/mm2src/coins/eth.rs index feef6515fa..8fb784a3f6 100644 --- a/mm2src/coins/eth.rs +++ b/mm2src/coins/eth.rs @@ -47,7 +47,7 @@ use mm2_net::transport::{slurp_url, GuiAuthValidation, GuiAuthValidationGenerato use mm2_number::{BigDecimal, MmNumber}; #[cfg(test)] use mocktopus::macros::*; use nft::nft_errors::WithdrawNFTError; -use nft::nft_transfer::{TransactionNFTDetails, WithdrawErc1155Request, WithdrawErc721Request}; +use nft::nft_structs::{WithdrawErc1155Request, WithdrawErc721Request, TransactionNftDetails}; use rand::seq::SliceRandom; use rpc::v1::types::Bytes as BytesJson; use secp256k1::PublicKey; @@ -144,7 +144,7 @@ lazy_static! { pub type Web3RpcFut = Box> + Send>; pub type Web3RpcResult = Result>; pub type GasStationResult = Result>; -type WithdrawNFTResult = Result>; +type WithdrawNftResult = Result>; #[derive(Debug, Display)] pub enum GasStationReqErr { @@ -800,9 +800,9 @@ async fn withdraw_impl(coin: EthCoin, req: WithdrawRequest) -> WithdrawResult { }) } -pub async fn withdraw_erc721(ctx: MmArc, req: WithdrawErc721Request) -> WithdrawNFTResult { todo!() } +pub async fn withdraw_erc721(ctx: MmArc, req: WithdrawErc721Request) -> WithdrawNftResult { todo!() } -pub async fn withdraw_erc1155(ctx: MmArc, req: WithdrawErc1155Request) -> WithdrawNFTResult { todo!() } +pub async fn withdraw_erc1155(ctx: MmArc, req: WithdrawErc1155Request) -> WithdrawNftResult { todo!() } #[derive(Clone)] pub struct EthCoin(Arc); diff --git a/mm2src/coins/eth/nft/mod.rs b/mm2src/coins/eth/nft/mod.rs index d48ddfabea..88af3e5130 100644 --- a/mm2src/coins/eth/nft/mod.rs +++ b/mm2src/coins/eth/nft/mod.rs @@ -1,2 +1,2 @@ pub(crate) mod nft_errors; -pub(crate) mod nft_transfer; +pub(crate) mod nft_structs; diff --git a/mm2src/coins/eth/nft/nft_structs.rs b/mm2src/coins/eth/nft/nft_structs.rs new file mode 100644 index 0000000000..792f339c43 --- /dev/null +++ b/mm2src/coins/eth/nft/nft_structs.rs @@ -0,0 +1,126 @@ +use crate::TxFeeDetails; +use mm2_number::BigDecimal; +use rpc::v1::types::Bytes as BytesJson; + +#[allow(dead_code)] +#[derive(Debug)] +enum Chain { + Eth, + Bnb, +} + +#[allow(dead_code)] +#[derive(Debug)] +enum ContractType { + Erc721, + Erc1155, +} + +#[allow(dead_code)] +#[derive(Debug)] +struct Nft { + token_address: String, + token_id: BigDecimal, + amount: BigDecimal, + owner_of: String, + token_hash: String, + block_number_minted: u64, + block_number: u64, + contract_type: ContractType, + name: Option, + symbol: Option, + token_uri: Option, + metadata: Option, + last_token_uri_sync: Option, + last_metadata_sync: Option, + minter_address: Option, +} + +#[allow(dead_code)] +#[derive(Debug)] +struct Nfts { + chain: Chain, + count: i64, + nfts: Vec, +} + +#[allow(dead_code)] +#[derive(Debug)] +pub struct WithdrawErc721Request { + coin: String, + to: String, + token_address: String, + token_id: BigDecimal, +} + +#[allow(dead_code)] +#[derive(Debug, Deserialize)] +pub struct WithdrawErc1155Request { + coin: String, + to: String, + token_address: String, + token_id: BigDecimal, + amount: BigDecimal, + #[serde(default)] + max: bool, +} + +#[allow(dead_code)] +#[derive(Clone, Debug, Deserialize, Serialize)] +pub struct TransactionNftDetails { + /// Raw bytes of signed transaction, this should be sent as is to `send_raw_transaction_bytes` RPC to broadcast the transaction + tx_hex: BytesJson, + /// Transaction hash in hexadecimal format + tx_hash: String, + /// NFTs are sent from these addresses + from: Vec, + /// NFTs are sent to these addresses + to: Vec, + contract_type: String, + token_address: String, + token_id: BigDecimal, + amount: BigDecimal, + fee_details: Option, + /// Block height + block_height: u64, + /// Transaction timestamp + timestamp: u64, + /// Internal MM2 id used for internal transaction identification, for some coins it might be equal to transaction hash + internal_id: i64, +} + +#[allow(dead_code)] +#[derive(Debug)] +enum NftTxType { + Single, +} + +#[allow(dead_code)] +#[derive(Debug)] +struct NftTransferHistory { + block_number: u64, + block_timestamp: u64, + block_hash: String, + /// Transaction hash in hexadecimal format + tx_hash: String, + tx_index: u64, + log_index: u64, + value: u64, + contract_type: ContractType, + tx_type: NftTxType, + token_address: String, + token_id: u64, + from: String, + to: String, + amount: BigDecimal, + verified: u64, + operator: Option, +} + +#[allow(dead_code)] +#[derive(Debug)] +struct NftTransferHistoryByChain { + chain: Chain, + count: i64, + transfer_history: Vec, +} diff --git a/mm2src/coins/eth/nft/nft_transfer.rs b/mm2src/coins/eth/nft/nft_transfer.rs deleted file mode 100644 index a455384659..0000000000 --- a/mm2src/coins/eth/nft/nft_transfer.rs +++ /dev/null @@ -1,42 +0,0 @@ -use crate::TxFeeDetails; -use mm2_number::BigDecimal; -use rpc::v1::types::{Bytes as BytesJson, H256 as H256Json}; - -pub struct WithdrawErc721Request { - coin: String, - to: String, - token_address: String, - token_id: BigDecimal, -} - -pub struct WithdrawErc1155Request { - coin: String, - to: String, - token_address: String, - token_id: BigDecimal, - amount: BigDecimal, - #[serde(default)] - max: bool, -} - -pub struct TransactionNFTDetails { - /// Raw bytes of signed transaction, this should be sent as is to `send_raw_transaction_bytes` RPC to broadcast the transaction - tx_hex: BytesJson, - /// Transaction hash in hexadecimal format - tx_hash: String, - /// NFTs are sent from these addresses - from: Vec, - /// NFTs are sent to these addresses - to: Vec, - contract_type: String, - token_address: String, - token_id: BigDecimal, - amount: BigDecimal, - fee_details: Option, - /// Block height - block_height: u64, - /// Transaction timestamp - timestamp: i64, - /// Internal MM2 id used for internal transaction identification, for some coins it might be equal to transaction hash - internal_id: i64, -} From a7dc3830d841828034b147410c0fb06c28810738 Mon Sep 17 00:00:00 2001 From: laruh Date: Thu, 26 Jan 2023 14:35:52 +0700 Subject: [PATCH 04/66] wip --- mm2src/coins/eth.rs | 12 +++++----- mm2src/coins/eth/nft/nft_errors.rs | 13 ++++++++++- mm2src/coins/eth/nft/nft_structs.rs | 22 +++++++++---------- .../mm2_main/src/rpc/dispatcher/dispatcher.rs | 3 ++- 4 files changed, 32 insertions(+), 18 deletions(-) diff --git a/mm2src/coins/eth.rs b/mm2src/coins/eth.rs index 8fb784a3f6..c9b088461f 100644 --- a/mm2src/coins/eth.rs +++ b/mm2src/coins/eth.rs @@ -46,8 +46,8 @@ use mm2_err_handle::prelude::*; use mm2_net::transport::{slurp_url, GuiAuthValidation, GuiAuthValidationGenerator, SlurpError}; use mm2_number::{BigDecimal, MmNumber}; #[cfg(test)] use mocktopus::macros::*; -use nft::nft_errors::WithdrawNFTError; -use nft::nft_structs::{WithdrawErc1155Request, WithdrawErc721Request, TransactionNftDetails}; +use nft::nft_errors::GetNftInfoError; +use nft::nft_structs::{Nfts, TransactionNftDetails, WithdrawErc1155Request, WithdrawErc721Request}; use rand::seq::SliceRandom; use rpc::v1::types::Bytes as BytesJson; use secp256k1::PublicKey; @@ -144,7 +144,7 @@ lazy_static! { pub type Web3RpcFut = Box> + Send>; pub type Web3RpcResult = Result>; pub type GasStationResult = Result>; -type WithdrawNftResult = Result>; +pub type WithdrawNftResult = Result>; #[derive(Debug, Display)] pub enum GasStationReqErr { @@ -800,9 +800,11 @@ async fn withdraw_impl(coin: EthCoin, req: WithdrawRequest) -> WithdrawResult { }) } -pub async fn withdraw_erc721(ctx: MmArc, req: WithdrawErc721Request) -> WithdrawNftResult { todo!() } +pub async fn get_nft_list(_ctx: MmArc, _chains: Vec) -> MmResult, GetNftInfoError> { todo!() } -pub async fn withdraw_erc1155(ctx: MmArc, req: WithdrawErc1155Request) -> WithdrawNftResult { todo!() } +pub async fn withdraw_erc721(_ctx: MmArc, _req: WithdrawErc721Request) -> WithdrawNftResult { todo!() } + +pub async fn withdraw_erc1155(_ctx: MmArc, _req: WithdrawErc1155Request) -> WithdrawNftResult { todo!() } #[derive(Clone)] pub struct EthCoin(Arc); diff --git a/mm2src/coins/eth/nft/nft_errors.rs b/mm2src/coins/eth/nft/nft_errors.rs index 49ff7a9bc6..026226208a 100644 --- a/mm2src/coins/eth/nft/nft_errors.rs +++ b/mm2src/coins/eth/nft/nft_errors.rs @@ -1,3 +1,14 @@ -pub enum WithdrawNFTError { +use common::HttpStatusCode; +use derive_more::Display; +use http::StatusCode; +use serde::{Deserialize, Serialize}; + +#[derive(Clone, Debug, Display, Serialize, SerializeErrorType, Deserialize)] +#[serde(tag = "error_type", content = "error_data")] +pub enum GetNftInfoError { // todo } + +impl HttpStatusCode for GetNftInfoError { + fn status_code(&self) -> StatusCode { todo!() } +} diff --git a/mm2src/coins/eth/nft/nft_structs.rs b/mm2src/coins/eth/nft/nft_structs.rs index 792f339c43..d3bc2eb976 100644 --- a/mm2src/coins/eth/nft/nft_structs.rs +++ b/mm2src/coins/eth/nft/nft_structs.rs @@ -3,21 +3,21 @@ use mm2_number::BigDecimal; use rpc::v1::types::Bytes as BytesJson; #[allow(dead_code)] -#[derive(Debug)] +#[derive(Debug, Deserialize, Serialize)] enum Chain { Eth, Bnb, } #[allow(dead_code)] -#[derive(Debug)] +#[derive(Debug, Deserialize, Serialize)] enum ContractType { Erc721, Erc1155, } #[allow(dead_code)] -#[derive(Debug)] +#[derive(Debug, Deserialize, Serialize)] struct Nft { token_address: String, token_id: BigDecimal, @@ -37,15 +37,15 @@ struct Nft { } #[allow(dead_code)] -#[derive(Debug)] -struct Nfts { +#[derive(Debug, Deserialize, Serialize)] +pub struct Nfts { chain: Chain, count: i64, nfts: Vec, } #[allow(dead_code)] -#[derive(Debug)] +#[derive(Clone, Deserialize)] pub struct WithdrawErc721Request { coin: String, to: String, @@ -54,7 +54,7 @@ pub struct WithdrawErc721Request { } #[allow(dead_code)] -#[derive(Debug, Deserialize)] +#[derive(Clone, Deserialize)] pub struct WithdrawErc1155Request { coin: String, to: String, @@ -90,13 +90,13 @@ pub struct TransactionNftDetails { } #[allow(dead_code)] -#[derive(Debug)] +#[derive(Debug, Deserialize)] enum NftTxType { Single, } #[allow(dead_code)] -#[derive(Debug)] +#[derive(Debug, Deserialize)] struct NftTransferHistory { block_number: u64, block_timestamp: u64, @@ -118,8 +118,8 @@ struct NftTransferHistory { } #[allow(dead_code)] -#[derive(Debug)] -struct NftTransferHistoryByChain { +#[derive(Debug, Deserialize)] +struct NftsTransferHistoryByChain { chain: Chain, count: i64, transfer_history: Vec, diff --git a/mm2src/mm2_main/src/rpc/dispatcher/dispatcher.rs b/mm2src/mm2_main/src/rpc/dispatcher/dispatcher.rs index 2a599f0f5e..0ac70d7125 100644 --- a/mm2src/mm2_main/src/rpc/dispatcher/dispatcher.rs +++ b/mm2src/mm2_main/src/rpc/dispatcher/dispatcher.rs @@ -9,7 +9,7 @@ use crate::{mm2::lp_stats::{add_node_to_version_stat, remove_node_from_version_s stop_version_stat_collection, update_version_stat_collection}, mm2::lp_swap::{get_locked_amount_rpc, max_maker_vol, recreate_swap_data, trade_preimage_rpc}, mm2::rpc::lp_commands::{get_public_key, get_public_key_hash}}; -use coins::eth::{withdraw_erc1155, withdraw_erc721, EthCoin}; +use coins::eth::{get_nft_list, withdraw_erc1155, withdraw_erc721, EthCoin}; use coins::my_tx_history_v2::my_tx_history_v2_rpc; use coins::rpc_command::{account_balance::account_balance, get_current_mtp::get_current_mtp_rpc, @@ -157,6 +157,7 @@ async fn dispatcher_v2(request: MmRpcRequest, ctx: MmArc) -> DispatcherResult handle_mmrpc(ctx, request, enable_token::).await, "get_current_mtp" => handle_mmrpc(ctx, request, get_current_mtp_rpc).await, "get_enabled_coins" => handle_mmrpc(ctx, request, get_enabled_coins).await, + "get_nft_list" => handle_mmrpc(ctx, request, get_nft_list).await, "get_locked_amount" => handle_mmrpc(ctx, request, get_locked_amount_rpc).await, "get_new_address" => handle_mmrpc(ctx, request, get_new_address).await, "get_public_key" => handle_mmrpc(ctx, request, get_public_key).await, From 55e11b7f4662d1c670365e5d344b512b5ed5e09b Mon Sep 17 00:00:00 2001 From: laruh Date: Fri, 27 Jan 2023 17:04:02 +0700 Subject: [PATCH 05/66] wip --- mm2src/coins/eth.rs | 11 +++++++--- mm2src/coins/eth/nft/nft_errors.rs | 10 ++++++++++ mm2src/coins/eth/nft/nft_structs.rs | 20 +++++++++++++++++++ .../mm2_main/src/rpc/dispatcher/dispatcher.rs | 8 +++++--- 4 files changed, 43 insertions(+), 6 deletions(-) diff --git a/mm2src/coins/eth.rs b/mm2src/coins/eth.rs index c9b088461f..9df6f6cb8b 100644 --- a/mm2src/coins/eth.rs +++ b/mm2src/coins/eth.rs @@ -46,8 +46,9 @@ use mm2_err_handle::prelude::*; use mm2_net::transport::{slurp_url, GuiAuthValidation, GuiAuthValidationGenerator, SlurpError}; use mm2_number::{BigDecimal, MmNumber}; #[cfg(test)] use mocktopus::macros::*; -use nft::nft_errors::GetNftInfoError; -use nft::nft_structs::{Nfts, TransactionNftDetails, WithdrawErc1155Request, WithdrawErc721Request}; +use nft::nft_errors::{GetMyAddressError, GetNftInfoError}; +use nft::nft_structs::{MyAddressReq, NftListReq, NftMetadataReq, Nfts, TransactionNftDetails, WithdrawErc1155Request, + WithdrawErc721Request}; use rand::seq::SliceRandom; use rpc::v1::types::Bytes as BytesJson; use secp256k1::PublicKey; @@ -800,7 +801,11 @@ async fn withdraw_impl(coin: EthCoin, req: WithdrawRequest) -> WithdrawResult { }) } -pub async fn get_nft_list(_ctx: MmArc, _chains: Vec) -> MmResult, GetNftInfoError> { todo!() } +pub async fn get_nft_list(_ctx: MmArc, _req: NftListReq) -> MmResult, GetNftInfoError> { todo!() } + +pub async fn get_my_address(_ctx: MmArc, _req: MyAddressReq) -> MmResult { todo!() } + +pub async fn get_nft_metadata(_ctx: MmArc, _req: NftMetadataReq) -> MmResult, GetNftInfoError> { todo!() } pub async fn withdraw_erc721(_ctx: MmArc, _req: WithdrawErc721Request) -> WithdrawNftResult { todo!() } diff --git a/mm2src/coins/eth/nft/nft_errors.rs b/mm2src/coins/eth/nft/nft_errors.rs index 026226208a..2fbb1df38e 100644 --- a/mm2src/coins/eth/nft/nft_errors.rs +++ b/mm2src/coins/eth/nft/nft_errors.rs @@ -12,3 +12,13 @@ pub enum GetNftInfoError { impl HttpStatusCode for GetNftInfoError { fn status_code(&self) -> StatusCode { todo!() } } + +#[derive(Clone, Debug, Display, Serialize, SerializeErrorType, Deserialize)] +#[serde(tag = "error_type", content = "error_data")] +pub enum GetMyAddressError { + // todo +} + +impl HttpStatusCode for GetMyAddressError { + fn status_code(&self) -> StatusCode { todo!() } +} diff --git a/mm2src/coins/eth/nft/nft_structs.rs b/mm2src/coins/eth/nft/nft_structs.rs index d3bc2eb976..ae3a5c7296 100644 --- a/mm2src/coins/eth/nft/nft_structs.rs +++ b/mm2src/coins/eth/nft/nft_structs.rs @@ -2,6 +2,24 @@ use crate::TxFeeDetails; use mm2_number::BigDecimal; use rpc::v1::types::Bytes as BytesJson; +#[derive(Debug, Deserialize, Serialize)] +pub struct NftListReq { + chains: Vec, + api_key: String, +} + +#[derive(Debug, Deserialize, Serialize)] +pub struct MyAddressReq { + chain: String, + api_key: String, +} + +#[derive(Debug, Deserialize, Serialize)] +pub struct NftMetadataReq { + chain: String, + api_key: String, +} + #[allow(dead_code)] #[derive(Debug, Deserialize, Serialize)] enum Chain { @@ -51,6 +69,7 @@ pub struct WithdrawErc721Request { to: String, token_address: String, token_id: BigDecimal, + api_key: String, } #[allow(dead_code)] @@ -63,6 +82,7 @@ pub struct WithdrawErc1155Request { amount: BigDecimal, #[serde(default)] max: bool, + api_key: String, } #[allow(dead_code)] diff --git a/mm2src/mm2_main/src/rpc/dispatcher/dispatcher.rs b/mm2src/mm2_main/src/rpc/dispatcher/dispatcher.rs index 0ac70d7125..5841e24ada 100644 --- a/mm2src/mm2_main/src/rpc/dispatcher/dispatcher.rs +++ b/mm2src/mm2_main/src/rpc/dispatcher/dispatcher.rs @@ -9,7 +9,7 @@ use crate::{mm2::lp_stats::{add_node_to_version_stat, remove_node_from_version_s stop_version_stat_collection, update_version_stat_collection}, mm2::lp_swap::{get_locked_amount_rpc, max_maker_vol, recreate_swap_data, trade_preimage_rpc}, mm2::rpc::lp_commands::{get_public_key, get_public_key_hash}}; -use coins::eth::{get_nft_list, withdraw_erc1155, withdraw_erc721, EthCoin}; +use coins::eth::{get_my_address, get_nft_list, get_nft_metadata, withdraw_erc1155, withdraw_erc721, EthCoin}; use coins::my_tx_history_v2::my_tx_history_v2_rpc; use coins::rpc_command::{account_balance::account_balance, get_current_mtp::get_current_mtp_rpc, @@ -157,9 +157,11 @@ async fn dispatcher_v2(request: MmRpcRequest, ctx: MmArc) -> DispatcherResult handle_mmrpc(ctx, request, enable_token::).await, "get_current_mtp" => handle_mmrpc(ctx, request, get_current_mtp_rpc).await, "get_enabled_coins" => handle_mmrpc(ctx, request, get_enabled_coins).await, - "get_nft_list" => handle_mmrpc(ctx, request, get_nft_list).await, "get_locked_amount" => handle_mmrpc(ctx, request, get_locked_amount_rpc).await, + "get_my_address" => handle_mmrpc(ctx, request, get_my_address).await, "get_new_address" => handle_mmrpc(ctx, request, get_new_address).await, + "get_nft_list" => handle_mmrpc(ctx, request, get_nft_list).await, + "get_nft_metadata" => handle_mmrpc(ctx, request, get_nft_metadata).await, "get_public_key" => handle_mmrpc(ctx, request, get_public_key).await, "get_public_key_hash" => handle_mmrpc(ctx, request, get_public_key_hash).await, "get_raw_transaction" => handle_mmrpc(ctx, request, get_raw_transaction).await, @@ -179,8 +181,8 @@ async fn dispatcher_v2(request: MmRpcRequest, ctx: MmArc) -> DispatcherResult handle_mmrpc(ctx, request, update_version_stat_collection).await, "verify_message" => handle_mmrpc(ctx, request, verify_message).await, "withdraw" => handle_mmrpc(ctx, request, withdraw).await, - "withdraw_erc721" => handle_mmrpc(ctx, request, withdraw_erc721).await, "withdraw_erc1155" => handle_mmrpc(ctx, request, withdraw_erc1155).await, + "withdraw_erc721" => handle_mmrpc(ctx, request, withdraw_erc721).await, #[cfg(not(target_arch = "wasm32"))] native_only_methods => match native_only_methods { #[cfg(all(not(target_os = "ios"), not(target_os = "android")))] From 5653da308700131462e6686507acc4761dcd26e0 Mon Sep 17 00:00:00 2001 From: laruh Date: Fri, 27 Jan 2023 18:02:45 +0700 Subject: [PATCH 06/66] wip --- mm2src/coins/eth.rs | 14 +++++-- mm2src/coins/eth/nft/nft_structs.rs | 39 +++++++++++++------ .../mm2_main/src/rpc/dispatcher/dispatcher.rs | 4 +- 3 files changed, 41 insertions(+), 16 deletions(-) diff --git a/mm2src/coins/eth.rs b/mm2src/coins/eth.rs index 9df6f6cb8b..6ec67409f3 100644 --- a/mm2src/coins/eth.rs +++ b/mm2src/coins/eth.rs @@ -47,7 +47,8 @@ use mm2_net::transport::{slurp_url, GuiAuthValidation, GuiAuthValidationGenerato use mm2_number::{BigDecimal, MmNumber}; #[cfg(test)] use mocktopus::macros::*; use nft::nft_errors::{GetMyAddressError, GetNftInfoError}; -use nft::nft_structs::{MyAddressReq, NftListReq, NftMetadataReq, Nfts, TransactionNftDetails, WithdrawErc1155Request, +use nft::nft_structs::{MyAddressReq, MyAddressRes, Nft, NftListReq, NftMetadataReq, NftTransfersReq, Nfts, + NftsTransferHistoryByChain, TransactionNftDetails, WithdrawErc1155Request, WithdrawErc721Request}; use rand::seq::SliceRandom; use rpc::v1::types::Bytes as BytesJson; @@ -803,9 +804,16 @@ async fn withdraw_impl(coin: EthCoin, req: WithdrawRequest) -> WithdrawResult { pub async fn get_nft_list(_ctx: MmArc, _req: NftListReq) -> MmResult, GetNftInfoError> { todo!() } -pub async fn get_my_address(_ctx: MmArc, _req: MyAddressReq) -> MmResult { todo!() } +pub async fn get_my_address(_ctx: MmArc, _req: MyAddressReq) -> MmResult { todo!() } -pub async fn get_nft_metadata(_ctx: MmArc, _req: NftMetadataReq) -> MmResult, GetNftInfoError> { todo!() } +pub async fn get_nft_metadata(_ctx: MmArc, _req: NftMetadataReq) -> MmResult { todo!() } + +pub async fn get_nft_transfers( + _ctx: MmArc, + _req: NftTransfersReq, +) -> MmResult, GetNftInfoError> { + todo!() +} pub async fn withdraw_erc721(_ctx: MmArc, _req: WithdrawErc721Request) -> WithdrawNftResult { todo!() } diff --git a/mm2src/coins/eth/nft/nft_structs.rs b/mm2src/coins/eth/nft/nft_structs.rs index ae3a5c7296..87731f2fac 100644 --- a/mm2src/coins/eth/nft/nft_structs.rs +++ b/mm2src/coins/eth/nft/nft_structs.rs @@ -2,19 +2,27 @@ use crate::TxFeeDetails; use mm2_number::BigDecimal; use rpc::v1::types::Bytes as BytesJson; -#[derive(Debug, Deserialize, Serialize)] +#[allow(dead_code)] +#[derive(Debug, Deserialize)] pub struct NftListReq { chains: Vec, api_key: String, } -#[derive(Debug, Deserialize, Serialize)] +#[allow(dead_code)] +#[derive(Debug, Deserialize)] pub struct MyAddressReq { chain: String, - api_key: String, } -#[derive(Debug, Deserialize, Serialize)] +#[allow(dead_code)] +#[derive(Debug, Serialize)] +pub struct MyAddressRes { + wallet_address: String, +} + +#[allow(dead_code)] +#[derive(Debug, Deserialize)] pub struct NftMetadataReq { chain: String, api_key: String, @@ -22,21 +30,21 @@ pub struct NftMetadataReq { #[allow(dead_code)] #[derive(Debug, Deserialize, Serialize)] -enum Chain { +pub enum Chain { Eth, Bnb, } #[allow(dead_code)] #[derive(Debug, Deserialize, Serialize)] -enum ContractType { +pub enum ContractType { Erc721, Erc1155, } #[allow(dead_code)] #[derive(Debug, Deserialize, Serialize)] -struct Nft { +pub struct Nft { token_address: String, token_id: BigDecimal, amount: BigDecimal, @@ -110,14 +118,21 @@ pub struct TransactionNftDetails { } #[allow(dead_code)] -#[derive(Debug, Deserialize)] +#[derive(Clone, Deserialize)] +pub struct NftTransfersReq { + chains: Vec, + api_key: String, +} + +#[allow(dead_code)] +#[derive(Debug, Serialize)] enum NftTxType { Single, } #[allow(dead_code)] -#[derive(Debug, Deserialize)] -struct NftTransferHistory { +#[derive(Debug, Serialize)] +pub struct NftTransferHistory { block_number: u64, block_timestamp: u64, block_hash: String, @@ -138,8 +153,8 @@ struct NftTransferHistory { } #[allow(dead_code)] -#[derive(Debug, Deserialize)] -struct NftsTransferHistoryByChain { +#[derive(Debug, Serialize)] +pub struct NftsTransferHistoryByChain { chain: Chain, count: i64, transfer_history: Vec, diff --git a/mm2src/mm2_main/src/rpc/dispatcher/dispatcher.rs b/mm2src/mm2_main/src/rpc/dispatcher/dispatcher.rs index 5841e24ada..b2ddc36d88 100644 --- a/mm2src/mm2_main/src/rpc/dispatcher/dispatcher.rs +++ b/mm2src/mm2_main/src/rpc/dispatcher/dispatcher.rs @@ -9,7 +9,8 @@ use crate::{mm2::lp_stats::{add_node_to_version_stat, remove_node_from_version_s stop_version_stat_collection, update_version_stat_collection}, mm2::lp_swap::{get_locked_amount_rpc, max_maker_vol, recreate_swap_data, trade_preimage_rpc}, mm2::rpc::lp_commands::{get_public_key, get_public_key_hash}}; -use coins::eth::{get_my_address, get_nft_list, get_nft_metadata, withdraw_erc1155, withdraw_erc721, EthCoin}; +use coins::eth::{get_my_address, get_nft_list, get_nft_metadata, get_nft_transfers, withdraw_erc1155, withdraw_erc721, + EthCoin}; use coins::my_tx_history_v2::my_tx_history_v2_rpc; use coins::rpc_command::{account_balance::account_balance, get_current_mtp::get_current_mtp_rpc, @@ -162,6 +163,7 @@ async fn dispatcher_v2(request: MmRpcRequest, ctx: MmArc) -> DispatcherResult handle_mmrpc(ctx, request, get_new_address).await, "get_nft_list" => handle_mmrpc(ctx, request, get_nft_list).await, "get_nft_metadata" => handle_mmrpc(ctx, request, get_nft_metadata).await, + "get_nft_transfers" => handle_mmrpc(ctx, request, get_nft_transfers).await, "get_public_key" => handle_mmrpc(ctx, request, get_public_key).await, "get_public_key_hash" => handle_mmrpc(ctx, request, get_public_key_hash).await, "get_raw_transaction" => handle_mmrpc(ctx, request, get_raw_transaction).await, From 28dac605c6e7871fa38f4b290e5c0eba0df92c5c Mon Sep 17 00:00:00 2001 From: laruh Date: Fri, 27 Jan 2023 18:52:15 +0700 Subject: [PATCH 07/66] fn get_my_address was moved into lp_coins.rs --- mm2src/coins/eth.rs | 9 +++---- mm2src/coins/eth/nft/nft_errors.rs | 10 -------- mm2src/coins/eth/nft/nft_structs.rs | 12 ---------- mm2src/coins/lp_coins.rs | 24 +++++++++++++++++++ .../mm2_main/src/rpc/dispatcher/dispatcher.rs | 7 +++--- 5 files changed, 30 insertions(+), 32 deletions(-) diff --git a/mm2src/coins/eth.rs b/mm2src/coins/eth.rs index 6ec67409f3..9d5ed3af16 100644 --- a/mm2src/coins/eth.rs +++ b/mm2src/coins/eth.rs @@ -46,10 +46,9 @@ use mm2_err_handle::prelude::*; use mm2_net::transport::{slurp_url, GuiAuthValidation, GuiAuthValidationGenerator, SlurpError}; use mm2_number::{BigDecimal, MmNumber}; #[cfg(test)] use mocktopus::macros::*; -use nft::nft_errors::{GetMyAddressError, GetNftInfoError}; -use nft::nft_structs::{MyAddressReq, MyAddressRes, Nft, NftListReq, NftMetadataReq, NftTransfersReq, Nfts, - NftsTransferHistoryByChain, TransactionNftDetails, WithdrawErc1155Request, - WithdrawErc721Request}; +use nft::nft_errors::GetNftInfoError; +use nft::nft_structs::{Nft, NftListReq, NftMetadataReq, NftTransfersReq, Nfts, NftsTransferHistoryByChain, + TransactionNftDetails, WithdrawErc1155Request, WithdrawErc721Request}; use rand::seq::SliceRandom; use rpc::v1::types::Bytes as BytesJson; use secp256k1::PublicKey; @@ -804,8 +803,6 @@ async fn withdraw_impl(coin: EthCoin, req: WithdrawRequest) -> WithdrawResult { pub async fn get_nft_list(_ctx: MmArc, _req: NftListReq) -> MmResult, GetNftInfoError> { todo!() } -pub async fn get_my_address(_ctx: MmArc, _req: MyAddressReq) -> MmResult { todo!() } - pub async fn get_nft_metadata(_ctx: MmArc, _req: NftMetadataReq) -> MmResult { todo!() } pub async fn get_nft_transfers( diff --git a/mm2src/coins/eth/nft/nft_errors.rs b/mm2src/coins/eth/nft/nft_errors.rs index 2fbb1df38e..026226208a 100644 --- a/mm2src/coins/eth/nft/nft_errors.rs +++ b/mm2src/coins/eth/nft/nft_errors.rs @@ -12,13 +12,3 @@ pub enum GetNftInfoError { impl HttpStatusCode for GetNftInfoError { fn status_code(&self) -> StatusCode { todo!() } } - -#[derive(Clone, Debug, Display, Serialize, SerializeErrorType, Deserialize)] -#[serde(tag = "error_type", content = "error_data")] -pub enum GetMyAddressError { - // todo -} - -impl HttpStatusCode for GetMyAddressError { - fn status_code(&self) -> StatusCode { todo!() } -} diff --git a/mm2src/coins/eth/nft/nft_structs.rs b/mm2src/coins/eth/nft/nft_structs.rs index 87731f2fac..a00da518e8 100644 --- a/mm2src/coins/eth/nft/nft_structs.rs +++ b/mm2src/coins/eth/nft/nft_structs.rs @@ -9,18 +9,6 @@ pub struct NftListReq { api_key: String, } -#[allow(dead_code)] -#[derive(Debug, Deserialize)] -pub struct MyAddressReq { - chain: String, -} - -#[allow(dead_code)] -#[derive(Debug, Serialize)] -pub struct MyAddressRes { - wallet_address: String, -} - #[allow(dead_code)] #[derive(Debug, Deserialize)] pub struct NftMetadataReq { diff --git a/mm2src/coins/lp_coins.rs b/mm2src/coins/lp_coins.rs index f70067ca52..246eb27365 100644 --- a/mm2src/coins/lp_coins.rs +++ b/mm2src/coins/lp_coins.rs @@ -328,6 +328,16 @@ impl From for RawTransactionError { } } +#[derive(Clone, Debug, Display, Serialize, SerializeErrorType, Deserialize)] +#[serde(tag = "error_type", content = "error_data")] +pub enum GetMyAddressError { + // todo +} + +impl HttpStatusCode for GetMyAddressError { + fn status_code(&self) -> StatusCode { todo!() } +} + #[derive(Deserialize)] pub struct RawTransactionRequest { pub coin: String, @@ -340,6 +350,18 @@ pub struct RawTransactionRes { pub tx_hex: BytesJson, } +#[allow(dead_code)] +#[derive(Debug, Deserialize)] +pub struct MyAddressReq { + coin: String, +} + +#[allow(dead_code)] +#[derive(Debug, Serialize)] +pub struct MyAddressRes { + wallet_address: String, +} + pub type SignatureResult = Result>; pub type VerificationResult = Result>; @@ -3371,6 +3393,8 @@ pub fn address_by_coin_conf_and_pubkey_str( } } +pub async fn get_my_address(_ctx: MmArc, _req: MyAddressReq) -> MmResult { todo!() } + #[cfg(target_arch = "wasm32")] fn load_history_from_file_impl(coin: &T, ctx: &MmArc) -> TxHistoryFut> where diff --git a/mm2src/mm2_main/src/rpc/dispatcher/dispatcher.rs b/mm2src/mm2_main/src/rpc/dispatcher/dispatcher.rs index b2ddc36d88..d940373f16 100644 --- a/mm2src/mm2_main/src/rpc/dispatcher/dispatcher.rs +++ b/mm2src/mm2_main/src/rpc/dispatcher/dispatcher.rs @@ -9,8 +9,7 @@ use crate::{mm2::lp_stats::{add_node_to_version_stat, remove_node_from_version_s stop_version_stat_collection, update_version_stat_collection}, mm2::lp_swap::{get_locked_amount_rpc, max_maker_vol, recreate_swap_data, trade_preimage_rpc}, mm2::rpc::lp_commands::{get_public_key, get_public_key_hash}}; -use coins::eth::{get_my_address, get_nft_list, get_nft_metadata, get_nft_transfers, withdraw_erc1155, withdraw_erc721, - EthCoin}; +use coins::eth::{get_nft_list, get_nft_metadata, get_nft_transfers, withdraw_erc1155, withdraw_erc721, EthCoin}; use coins::my_tx_history_v2::my_tx_history_v2_rpc; use coins::rpc_command::{account_balance::account_balance, get_current_mtp::get_current_mtp_rpc, @@ -28,8 +27,8 @@ use coins::utxo::bch::BchCoin; use coins::utxo::qtum::QtumCoin; use coins::utxo::slp::SlpToken; use coins::utxo::utxo_standard::UtxoStandardCoin; -use coins::{add_delegation, get_raw_transaction, get_staking_infos, remove_delegation, sign_message, verify_message, - withdraw}; +use coins::{add_delegation, get_my_address, get_raw_transaction, get_staking_infos, remove_delegation, sign_message, + verify_message, withdraw}; #[cfg(all(not(target_os = "ios"), not(target_os = "android"), not(target_arch = "wasm32")))] use coins::{SolanaCoin, SplToken}; use coins_activation::{cancel_init_l2, cancel_init_standalone_coin, enable_platform_coin_with_tokens, enable_token, From 2af943a6cd8bfd3969bf95faf675ce69dcc218ed Mon Sep 17 00:00:00 2001 From: laruh Date: Wed, 1 Feb 2023 14:05:30 +0700 Subject: [PATCH 08/66] wip get_my_address --- mm2src/coins/eth.rs | 16 ++++++ mm2src/coins/lp_coins.rs | 118 +++++++++++++++++++++++++++++++++++++-- 2 files changed, 130 insertions(+), 4 deletions(-) diff --git a/mm2src/coins/eth.rs b/mm2src/coins/eth.rs index 9d5ed3af16..8e9dba037f 100644 --- a/mm2src/coins/eth.rs +++ b/mm2src/coins/eth.rs @@ -93,6 +93,7 @@ mod nft; mod web3_transport; #[path = "eth/v2_activation.rs"] pub mod v2_activation; +use crate::MyWalletAddress; use v2_activation::build_address_and_priv_key_policy; /// https://github.com/artemii235/etomic-swap/blob/master/contracts/EtomicSwap.sol @@ -4546,3 +4547,18 @@ fn increase_gas_price_by_stage(gas_price: U256, level: &FeeApproxStage) -> U256 }, } } + +#[derive(Debug, Deserialize, Serialize, Display)] +pub enum GetEthAddressError { + // todo +} + +pub async fn get_eth_address( + ctx: &MmArc, + ticker: &str, + conf: &Json, + protocol: CoinProtocol, + priv_key_policy: PrivKeyBuildPolicy, +) -> Result { + todo!() +} diff --git a/mm2src/coins/lp_coins.rs b/mm2src/coins/lp_coins.rs index 246eb27365..9e7653dd79 100644 --- a/mm2src/coins/lp_coins.rs +++ b/mm2src/coins/lp_coins.rs @@ -253,6 +253,7 @@ use utxo::UtxoActivationParams; use utxo::{BlockchainNetwork, GenerateTxError, UtxoFeeDetails, UtxoTx}; #[cfg(not(target_arch = "wasm32"))] pub mod z_coin; +use crate::eth::{get_eth_address, GetEthAddressError}; #[cfg(not(target_arch = "wasm32"))] use z_coin::ZCoin; pub type TransactionFut = Box + Send>; @@ -328,10 +329,25 @@ impl From for RawTransactionError { } } -#[derive(Clone, Debug, Display, Serialize, SerializeErrorType, Deserialize)] +#[derive(Debug, Display, Serialize, SerializeErrorType, Deserialize)] #[serde(tag = "error_type", content = "error_data")] pub enum GetMyAddressError { - // todo + CoinIsNotSupported(String), + Internal(String), + InvalidRequest(String), + GetEthAddressError(GetEthAddressError), +} + +impl From for GetMyAddressError { + fn from(e: CryptoCtxError) -> Self { GetMyAddressError::Internal(e.to_string()) } +} + +impl From for GetMyAddressError { + fn from(e: serde_json::Error) -> Self { GetMyAddressError::InvalidRequest(e.to_string()) } +} + +impl From for GetMyAddressError { + fn from(e: GetEthAddressError) -> Self { GetMyAddressError::GetEthAddressError(e) } } impl HttpStatusCode for GetMyAddressError { @@ -358,7 +374,7 @@ pub struct MyAddressReq { #[allow(dead_code)] #[derive(Debug, Serialize)] -pub struct MyAddressRes { +pub struct MyWalletAddress { wallet_address: String, } @@ -3393,7 +3409,101 @@ pub fn address_by_coin_conf_and_pubkey_str( } } -pub async fn get_my_address(_ctx: MmArc, _req: MyAddressReq) -> MmResult { todo!() } +pub async fn get_my_address(ctx: MmArc, req: MyAddressReq) -> MmResult { + let coins_en = coin_conf(&ctx, req.coin.as_str()); + + if coins_en.is_null() { + let warning = format!( + "Warning, coin {} is used without a corresponding configuration.", + req.coin + ); + ctx.log.log( + "😅", + #[allow(clippy::unnecessary_cast)] + &[&("coin" as &str), &req.coin, &("no-conf" as &str)], + &warning, + ); + } + + if coins_en["mm2"].is_null() { + return MmError::err(GetMyAddressError::CoinIsNotSupported( + "mm2 param is not set in coins config, assuming that coin is not supported".to_owned(), + )); + } + + let priv_key_policy = PrivKeyBuildPolicy::detect_priv_key_policy(&ctx)?; + let protocol: CoinProtocol = json::from_value(coins_en["protocol"].clone())?; + + let my_address = match protocol { + CoinProtocol::ETH => get_eth_address(&ctx, &req.coin, &coins_en, protocol, priv_key_policy).await?, + CoinProtocol::UTXO => { + return MmError::err(GetMyAddressError::CoinIsNotSupported( + "UTXO protocol is not supported by get_my_address".to_owned(), + )) + }, + CoinProtocol::QTUM => { + return MmError::err(GetMyAddressError::CoinIsNotSupported( + "QTUM protocol is not supported by get_my_address".to_owned(), + )) + }, + CoinProtocol::QRC20 { .. } => { + return MmError::err(GetMyAddressError::CoinIsNotSupported( + "QRC20 protocol is not supported by get_my_address".to_owned(), + )) + }, + CoinProtocol::ERC20 { .. } => { + return MmError::err(GetMyAddressError::CoinIsNotSupported( + "ERC20 protocol is not supported by get_my_address".to_owned(), + )) + }, + CoinProtocol::SLPTOKEN { .. } => { + return MmError::err(GetMyAddressError::CoinIsNotSupported( + "SlpToken protocol is not supported by get_my_address".to_owned(), + )) + }, + CoinProtocol::BCH { .. } => { + return MmError::err(GetMyAddressError::CoinIsNotSupported( + "BCH protocol is not supported by get_my_address".to_owned(), + )) + }, + CoinProtocol::TENDERMINT(_) => { + return MmError::err(GetMyAddressError::CoinIsNotSupported( + "TENDERMINT protocol is not supported by get_my_address".to_owned(), + )) + }, + CoinProtocol::TENDERMINTTOKEN(_) => { + return MmError::err(GetMyAddressError::CoinIsNotSupported( + "TENDERMINTTOKEN protocol is not supported by get_my_address".to_owned(), + )) + }, + #[cfg(not(target_arch = "wasm32"))] + CoinProtocol::LIGHTNING { .. } => { + return MmError::err(GetMyAddressError::CoinIsNotSupported( + "LIGHTNING protocol is not supported by get_my_address".to_owned(), + )) + }, + #[cfg(not(target_arch = "wasm32"))] + CoinProtocol::SOLANA => { + return MmError::err(GetMyAddressError::CoinIsNotSupported( + "SOLANA protocol is not supported by get_my_address".to_owned(), + )) + }, + #[cfg(not(target_arch = "wasm32"))] + CoinProtocol::SPLTOKEN { .. } => { + return MmError::err(GetMyAddressError::CoinIsNotSupported( + "SplToken protocol is not supported by get_my_address".to_owned(), + )) + }, + #[cfg(not(target_arch = "wasm32"))] + CoinProtocol::ZHTLC(_) => { + return MmError::err(GetMyAddressError::CoinIsNotSupported( + "ZHTLC protocol is not supported by get_my_address".to_owned(), + )) + }, + }; + + Ok(my_address) +} #[cfg(target_arch = "wasm32")] fn load_history_from_file_impl(coin: &T, ctx: &MmArc) -> TxHistoryFut> From 551f89508b40a0d833c32bb4b075af8a8c8f7d93 Mon Sep 17 00:00:00 2001 From: laruh Date: Wed, 1 Feb 2023 16:55:09 +0700 Subject: [PATCH 09/66] get_my_address works --- mm2src/coins/eth.rs | 26 +++++++++++++++++++++----- mm2src/coins/eth/v2_activation.rs | 2 +- mm2src/coins/lp_coins.rs | 17 ++++++++++++++--- 3 files changed, 36 insertions(+), 9 deletions(-) diff --git a/mm2src/coins/eth.rs b/mm2src/coins/eth.rs index 8e9dba037f..6c9fcdde2b 100644 --- a/mm2src/coins/eth.rs +++ b/mm2src/coins/eth.rs @@ -93,6 +93,7 @@ mod nft; mod web3_transport; #[path = "eth/v2_activation.rs"] pub mod v2_activation; +use crate::eth::v2_activation::EthActivationV2Error; use crate::MyWalletAddress; use v2_activation::build_address_and_priv_key_policy; @@ -4550,15 +4551,30 @@ fn increase_gas_price_by_stage(gas_price: U256, level: &FeeApproxStage) -> U256 #[derive(Debug, Deserialize, Serialize, Display)] pub enum GetEthAddressError { - // todo + PrivKeyPolicyNotAllowed(PrivKeyPolicyNotAllowed), + EthActivationV2Error(EthActivationV2Error), +} + +impl From for GetEthAddressError { + fn from(e: PrivKeyPolicyNotAllowed) -> Self { GetEthAddressError::PrivKeyPolicyNotAllowed(e) } +} +impl From for GetEthAddressError { + fn from(e: EthActivationV2Error) -> Self { GetEthAddressError::EthActivationV2Error(e) } } pub async fn get_eth_address( - ctx: &MmArc, ticker: &str, conf: &Json, - protocol: CoinProtocol, priv_key_policy: PrivKeyBuildPolicy, -) -> Result { - todo!() +) -> MmResult { + // Convert `PrivKeyBuildPolicy` to `EthPrivKeyBuildPolicy` if it's possible. + let priv_key_policy = EthPrivKeyBuildPolicy::try_from(priv_key_policy)?; + + let (my_address, ..) = build_address_and_priv_key_policy(conf, priv_key_policy)?; + let wallet_address = checksum_address(&format!("{:#02x}", my_address)); + + Ok(MyWalletAddress { + coin: ticker.to_owned(), + wallet_address, + }) } diff --git a/mm2src/coins/eth/v2_activation.rs b/mm2src/coins/eth/v2_activation.rs index bbad8b304c..2512911ad3 100644 --- a/mm2src/coins/eth/v2_activation.rs +++ b/mm2src/coins/eth/v2_activation.rs @@ -2,7 +2,7 @@ use super::*; use common::executor::AbortedError; use crypto::{CryptoCtxError, StandardHDPathToCoin}; -#[derive(Display, Serialize, SerializeErrorType)] +#[derive(Display, Serialize, SerializeErrorType, Debug, Deserialize)] #[serde(tag = "error_type", content = "error_data")] pub enum EthActivationV2Error { InvalidPayload(String), diff --git a/mm2src/coins/lp_coins.rs b/mm2src/coins/lp_coins.rs index 9e7653dd79..4c08ed5ee6 100644 --- a/mm2src/coins/lp_coins.rs +++ b/mm2src/coins/lp_coins.rs @@ -333,8 +333,11 @@ impl From for RawTransactionError { #[serde(tag = "error_type", content = "error_data")] pub enum GetMyAddressError { CoinIsNotSupported(String), + #[display(fmt = "Internal error: {}", _0)] Internal(String), + #[display(fmt = "Invalid request error error: {}", _0)] InvalidRequest(String), + #[display(fmt = "Get Eth address error: {}", _0)] GetEthAddressError(GetEthAddressError), } @@ -351,7 +354,14 @@ impl From for GetMyAddressError { } impl HttpStatusCode for GetMyAddressError { - fn status_code(&self) -> StatusCode { todo!() } + fn status_code(&self) -> StatusCode { + match self { + GetMyAddressError::CoinIsNotSupported(_) | GetMyAddressError::InvalidRequest(_) => StatusCode::BAD_REQUEST, + GetMyAddressError::Internal(_) | GetMyAddressError::GetEthAddressError(_) => { + StatusCode::INTERNAL_SERVER_ERROR + }, + } + } } #[derive(Deserialize)] @@ -375,6 +385,7 @@ pub struct MyAddressReq { #[allow(dead_code)] #[derive(Debug, Serialize)] pub struct MyWalletAddress { + coin: String, wallet_address: String, } @@ -396,7 +407,7 @@ pub enum TxHistoryError { InternalError(String), } -#[derive(Clone, Debug, Display)] +#[derive(Clone, Debug, Display, Deserialize)] pub enum PrivKeyPolicyNotAllowed { #[display(fmt = "Hardware Wallet is not supported")] HardwareWalletNotSupported, @@ -3435,7 +3446,7 @@ pub async fn get_my_address(ctx: MmArc, req: MyAddressReq) -> MmResult get_eth_address(&ctx, &req.coin, &coins_en, protocol, priv_key_policy).await?, + CoinProtocol::ETH => get_eth_address(&req.coin, &coins_en, priv_key_policy).await?, CoinProtocol::UTXO => { return MmError::err(GetMyAddressError::CoinIsNotSupported( "UTXO protocol is not supported by get_my_address".to_owned(), From cd1e11c66758e2da51d4455618444fb725289734 Mon Sep 17 00:00:00 2001 From: laruh Date: Thu, 2 Feb 2023 16:55:59 +0700 Subject: [PATCH 10/66] send_moralis_request, errors, get_nft_list wip --- mm2src/coins/eth.rs | 47 +++++++++++++++++-- mm2src/coins/eth/nft/nft_errors.rs | 42 +++++++++++++++-- mm2src/coins/eth/nft/nft_structs.rs | 11 ++--- .../eth/web3_transport/http_transport.rs | 2 +- mm2src/coins/lp_coins.rs | 2 + mm2src/common/common.rs | 1 + 6 files changed, 90 insertions(+), 15 deletions(-) diff --git a/mm2src/coins/eth.rs b/mm2src/coins/eth.rs index 6c9fcdde2b..0ba9cdc53e 100644 --- a/mm2src/coins/eth.rs +++ b/mm2src/coins/eth.rs @@ -25,7 +25,7 @@ use async_trait::async_trait; use bitcrypto::{keccak256, ripemd160, sha256}; use common::executor::{abortable_queue::AbortableQueue, AbortableSystem, AbortedError, Timer}; use common::log::{debug, error, info, warn}; -use common::{get_utc_timestamp, now_ms, small_rng, DEX_FEE_ADDR_RAW_PUBKEY}; +use common::{get_utc_timestamp, now_ms, small_rng, APPLICATION_JSON, DEX_FEE_ADDR_RAW_PUBKEY, X_API_KEY}; use crypto::privkey::key_pair_from_secret; use crypto::{CryptoCtx, CryptoCtxError, GlobalHDAccountArc, KeyPairPolicy}; #[cfg(target_arch = "wasm32")] @@ -40,10 +40,11 @@ use ethkey::{sign, verify_address}; use futures::compat::Future01CompatExt; use futures::future::{join_all, select, Either, FutureExt, TryFutureExt}; use futures01::Future; -use http::StatusCode; +use gstuff::binprint; +use http::{Request, StatusCode}; use mm2_core::mm_ctx::{MmArc, MmWeak}; use mm2_err_handle::prelude::*; -use mm2_net::transport::{slurp_url, GuiAuthValidation, GuiAuthValidationGenerator, SlurpError}; +use mm2_net::transport::{slurp_req, slurp_url, GuiAuthValidation, GuiAuthValidationGenerator, SlurpError}; use mm2_number::{BigDecimal, MmNumber}; #[cfg(test)] use mocktopus::macros::*; use nft::nft_errors::GetNftInfoError; @@ -93,7 +94,9 @@ mod nft; mod web3_transport; #[path = "eth/v2_activation.rs"] pub mod v2_activation; +use crate::eth::nft::nft_structs::Chain; use crate::eth::v2_activation::EthActivationV2Error; +use crate::eth::web3_transport::http_transport::single_response; use crate::MyWalletAddress; use v2_activation::build_address_and_priv_key_policy; @@ -139,6 +142,11 @@ const ETH_GAS: u64 = 150_000; /// Lifetime of generated signed message for gui-auth requests const GUI_AUTH_SIGNED_MESSAGE_LIFETIME_SEC: i64 = 90; +#[allow(dead_code)] +const URL_MORALIS: &str = "https://deep-index.moralis.io/api/v2/"; +#[allow(dead_code)] +const FORMAT_DECIMAL: &str = "format=decimal"; + lazy_static! { pub static ref SWAP_CONTRACT: Contract = Contract::load(SWAP_CONTRACT_ABI.as_bytes()).unwrap(); pub static ref ERC20_CONTRACT: Contract = Contract::load(ERC20_ABI.as_bytes()).unwrap(); @@ -803,7 +811,37 @@ async fn withdraw_impl(coin: EthCoin, req: WithdrawRequest) -> WithdrawResult { }) } -pub async fn get_nft_list(_ctx: MmArc, _req: NftListReq) -> MmResult, GetNftInfoError> { todo!() } +#[allow(dead_code)] +async fn send_moralis_request(uri: String, api_key: String) -> MmResult { + let request = Request::builder() + .method("GET") + .uri(uri.clone()) + .header(X_API_KEY, api_key) + .header("accept", APPLICATION_JSON) + .body(Vec::from(""))?; + + let (status, _headers, body) = slurp_req(request).await?; + if !status.is_success() { + return Err(MmError::new(GetNftInfoError::Transport(format!( + "Response !200 from {}: {}, {}", + uri, + status, + binprint(&body, b'.') + )))); + } + let res = single_response(body, &uri)?; + Ok(res) +} + +pub async fn get_nft_list(_ctx: MmArc, req: NftListReq) -> MmResult, GetNftInfoError> { + for chain in req.chains { + match chain { + Chain::Eth => {}, + Chain::Bnb => {}, + } + } + todo!() +} pub async fn get_nft_metadata(_ctx: MmArc, _req: NftMetadataReq) -> MmResult { todo!() } @@ -4562,6 +4600,7 @@ impl From for GetEthAddressError { fn from(e: EthActivationV2Error) -> Self { GetEthAddressError::EthActivationV2Error(e) } } +/// `get_eth_address` returns wallet address for coin with `ETH` protocol type. pub async fn get_eth_address( ticker: &str, conf: &Json, diff --git a/mm2src/coins/eth/nft/nft_errors.rs b/mm2src/coins/eth/nft/nft_errors.rs index 026226208a..129f8a9718 100644 --- a/mm2src/coins/eth/nft/nft_errors.rs +++ b/mm2src/coins/eth/nft/nft_errors.rs @@ -1,14 +1,50 @@ use common::HttpStatusCode; use derive_more::Display; use http::StatusCode; +use mm2_net::transport::SlurpError; use serde::{Deserialize, Serialize}; +use web3::Error; -#[derive(Clone, Debug, Display, Serialize, SerializeErrorType, Deserialize)] +#[derive(Debug, Display, Serialize, SerializeErrorType, Deserialize)] #[serde(tag = "error_type", content = "error_data")] pub enum GetNftInfoError { - // todo + #[display(fmt = "Invalid request: {}", _0)] + InvalidRequest(String), + #[display(fmt = "Transport: {}", _0)] + Transport(String), + #[display(fmt = "Invalid response: {}", _0)] + InvalidResponse(String), + #[display(fmt = "Internal: {}", _0)] + Internal(String), +} + +/// `http::Error` can appear on an HTTP request [`http::Builder::build`] building. +impl From for GetNftInfoError { + fn from(e: http::Error) -> Self { GetNftInfoError::InvalidRequest(e.to_string()) } +} + +impl From for GetNftInfoError { + fn from(e: SlurpError) -> Self { + let error = e.to_string(); + match e { + SlurpError::ErrorDeserializing { .. } => GetNftInfoError::InvalidResponse(error), + SlurpError::Transport { .. } | SlurpError::Timeout { .. } => GetNftInfoError::Transport(error), + SlurpError::Internal(_) | SlurpError::InvalidRequest(_) => GetNftInfoError::Internal(error), + } + } +} + +impl From for GetNftInfoError { + fn from(e: Error) -> Self { GetNftInfoError::Transport(e.to_string()) } } impl HttpStatusCode for GetNftInfoError { - fn status_code(&self) -> StatusCode { todo!() } + fn status_code(&self) -> StatusCode { + match self { + GetNftInfoError::InvalidRequest(_) => StatusCode::BAD_REQUEST, + GetNftInfoError::Transport(_) | GetNftInfoError::InvalidResponse(_) | GetNftInfoError::Internal(_) => { + StatusCode::INTERNAL_SERVER_ERROR + }, + } + } } diff --git a/mm2src/coins/eth/nft/nft_structs.rs b/mm2src/coins/eth/nft/nft_structs.rs index a00da518e8..3593a1ed24 100644 --- a/mm2src/coins/eth/nft/nft_structs.rs +++ b/mm2src/coins/eth/nft/nft_structs.rs @@ -1,19 +1,17 @@ -use crate::TxFeeDetails; +use crate::{TxFeeDetails, WithdrawFee}; use mm2_number::BigDecimal; use rpc::v1::types::Bytes as BytesJson; #[allow(dead_code)] #[derive(Debug, Deserialize)] pub struct NftListReq { - chains: Vec, - api_key: String, + pub(crate) chains: Vec, } #[allow(dead_code)] #[derive(Debug, Deserialize)] pub struct NftMetadataReq { chain: String, - api_key: String, } #[allow(dead_code)] @@ -65,7 +63,7 @@ pub struct WithdrawErc721Request { to: String, token_address: String, token_id: BigDecimal, - api_key: String, + fee: Option, } #[allow(dead_code)] @@ -78,7 +76,7 @@ pub struct WithdrawErc1155Request { amount: BigDecimal, #[serde(default)] max: bool, - api_key: String, + fee: Option, } #[allow(dead_code)] @@ -109,7 +107,6 @@ pub struct TransactionNftDetails { #[derive(Clone, Deserialize)] pub struct NftTransfersReq { chains: Vec, - api_key: String, } #[allow(dead_code)] diff --git a/mm2src/coins/eth/web3_transport/http_transport.rs b/mm2src/coins/eth/web3_transport/http_transport.rs index fab245d1a4..d9973069ae 100644 --- a/mm2src/coins/eth/web3_transport/http_transport.rs +++ b/mm2src/coins/eth/web3_transport/http_transport.rs @@ -24,7 +24,7 @@ pub struct AuthPayload<'a> { /// Parse bytes RPC response into `Result`. /// Implementation copied from Web3 HTTP transport #[cfg(not(target_arch = "wasm32"))] -fn single_response>(response: T, rpc_url: &str) -> Result { +pub(crate) fn single_response>(response: T, rpc_url: &str) -> Result { let response = serde_json::from_slice(&response) .map_err(|e| Error::from(ErrorKind::InvalidResponse(format!("{}: {}", rpc_url, e))))?; diff --git a/mm2src/coins/lp_coins.rs b/mm2src/coins/lp_coins.rs index 4c08ed5ee6..e013bfd969 100644 --- a/mm2src/coins/lp_coins.rs +++ b/mm2src/coins/lp_coins.rs @@ -3420,6 +3420,8 @@ pub fn address_by_coin_conf_and_pubkey_str( } } +/// `get_my_address` function returns wallet address for necessary coin without its activation. +/// Currently supports only coins with `ETH` protocol type. pub async fn get_my_address(ctx: MmArc, req: MyAddressReq) -> MmResult { let coins_en = coin_conf(&ctx, req.coin.as_str()); diff --git a/mm2src/common/common.rs b/mm2src/common/common.rs index 5a8edbd7da..e2f638c02f 100644 --- a/mm2src/common/common.rs +++ b/mm2src/common/common.rs @@ -174,6 +174,7 @@ cfg_wasm32! { } pub const X_GRPC_WEB: &str = "x-grpc-web"; +pub const X_API_KEY: &str = "X-API-Key"; pub const APPLICATION_JSON: &str = "application/json"; pub const APPLICATION_GRPC_WEB: &str = "application/grpc-web"; pub const APPLICATION_GRPC_WEB_PROTO: &str = "application/grpc-web+proto"; From f617e5dc1f597b4e2c1a3cc2207113cbca51b1a8 Mon Sep 17 00:00:00 2001 From: laruh Date: Fri, 3 Feb 2023 14:43:00 +0700 Subject: [PATCH 11/66] add targets for send_moralis_request --- mm2src/coins/eth.rs | 56 +++++++++++++++++++++++++++--- mm2src/coins/eth/nft/nft_errors.rs | 20 ++++++++--- 2 files changed, 66 insertions(+), 10 deletions(-) diff --git a/mm2src/coins/eth.rs b/mm2src/coins/eth.rs index 0ba9cdc53e..ff95035bd0 100644 --- a/mm2src/coins/eth.rs +++ b/mm2src/coins/eth.rs @@ -40,11 +40,11 @@ use ethkey::{sign, verify_address}; use futures::compat::Future01CompatExt; use futures::future::{join_all, select, Either, FutureExt, TryFutureExt}; use futures01::Future; -use gstuff::binprint; -use http::{Request, StatusCode}; +use http::header::ACCEPT; +use http::{HeaderValue, Request, StatusCode}; use mm2_core::mm_ctx::{MmArc, MmWeak}; use mm2_err_handle::prelude::*; -use mm2_net::transport::{slurp_req, slurp_url, GuiAuthValidation, GuiAuthValidationGenerator, SlurpError}; +use mm2_net::transport::{slurp_url, GuiAuthValidation, GuiAuthValidationGenerator, SlurpError}; use mm2_number::{BigDecimal, MmNumber}; #[cfg(test)] use mocktopus::macros::*; use nft::nft_errors::GetNftInfoError; @@ -145,7 +145,7 @@ const GUI_AUTH_SIGNED_MESSAGE_LIFETIME_SEC: i64 = 90; #[allow(dead_code)] const URL_MORALIS: &str = "https://deep-index.moralis.io/api/v2/"; #[allow(dead_code)] -const FORMAT_DECIMAL: &str = "format=decimal"; +const FORMAT_DECIMAL_MORALIS: &str = "format=decimal"; lazy_static! { pub static ref SWAP_CONTRACT: Contract = Contract::load(SWAP_CONTRACT_ABI.as_bytes()).unwrap(); @@ -812,12 +812,16 @@ async fn withdraw_impl(coin: EthCoin, req: WithdrawRequest) -> WithdrawResult { } #[allow(dead_code)] +#[cfg(not(target_arch = "wasm32"))] async fn send_moralis_request(uri: String, api_key: String) -> MmResult { + use gstuff::binprint; + use mm2_net::transport::slurp_req; + let request = Request::builder() .method("GET") .uri(uri.clone()) .header(X_API_KEY, api_key) - .header("accept", APPLICATION_JSON) + .header(ACCEPT, HeaderValue::from_static(APPLICATION_JSON)) .body(Vec::from(""))?; let (status, _headers, body) = slurp_req(request).await?; @@ -833,6 +837,48 @@ async fn send_moralis_request(uri: String, api_key: String) -> MmResult MmResult { + use mm2_net::wasm_http::FetchRequest; + use web3::helpers::to_result_from_output; + + macro_rules! try_or { + ($exp:expr, $errtype:ident) => { + match $exp { + Ok(x) => x, + Err(e) => return Err(MmError::new(GetNftInfoError::$errtype(ERRL!("{:?}", e)))), + } + }; + } + + let result = FetchRequest::get(uri.as_str()) + .cors() + .body_utf8("") + .header(X_API_KEY, api_key) + .header(ACCEPT, APPLICATION_JSON) + .request_str() + .await; + let (status_code, response_str) = try_or!(result, Transport); + if !status_code.is_success() { + return Err(MmError::new(GetNftInfoError::Transport(ERRL!( + "!200: {}, {}", + status_code, + response_str + )))); + } + + let response: Response = try_or!(serde_json::from_str(&response_str), InvalidResponse); + match response { + Response::Single(output) => { + let res = to_result_from_output(output)?; + Ok(res) + }, + Response::Batch(_) => Err(MmError::new(GetNftInfoError::InvalidResponse( + "Expected single, got batch.".to_owned(), + ))), + } +} + pub async fn get_nft_list(_ctx: MmArc, req: NftListReq) -> MmResult, GetNftInfoError> { for chain in req.chains { match chain { diff --git a/mm2src/coins/eth/nft/nft_errors.rs b/mm2src/coins/eth/nft/nft_errors.rs index 129f8a9718..e4c3a52539 100644 --- a/mm2src/coins/eth/nft/nft_errors.rs +++ b/mm2src/coins/eth/nft/nft_errors.rs @@ -25,17 +25,27 @@ impl From for GetNftInfoError { impl From for GetNftInfoError { fn from(e: SlurpError) -> Self { - let error = e.to_string(); + let error_str = e.to_string(); match e { - SlurpError::ErrorDeserializing { .. } => GetNftInfoError::InvalidResponse(error), - SlurpError::Transport { .. } | SlurpError::Timeout { .. } => GetNftInfoError::Transport(error), - SlurpError::Internal(_) | SlurpError::InvalidRequest(_) => GetNftInfoError::Internal(error), + SlurpError::ErrorDeserializing { .. } => GetNftInfoError::InvalidResponse(error_str), + SlurpError::Transport { .. } | SlurpError::Timeout { .. } => GetNftInfoError::Transport(error_str), + SlurpError::Internal(_) | SlurpError::InvalidRequest(_) => GetNftInfoError::Internal(error_str), } } } impl From for GetNftInfoError { - fn from(e: Error) -> Self { GetNftInfoError::Transport(e.to_string()) } + fn from(e: Error) -> Self { + let error_str = e.to_string(); + match e.kind() { + web3::ErrorKind::InvalidResponse(_) + | web3::ErrorKind::Decoder(_) + | web3::ErrorKind::Msg(_) + | web3::ErrorKind::Rpc(_) => GetNftInfoError::InvalidResponse(error_str), + web3::ErrorKind::Transport(_) | web3::ErrorKind::Io(_) => GetNftInfoError::Transport(error_str), + _ => GetNftInfoError::Internal(error_str), + } + } } impl HttpStatusCode for GetNftInfoError { From 28f3e63349b942b0ea3451a72f899d28267a014e Mon Sep 17 00:00:00 2001 From: laruh Date: Fri, 3 Feb 2023 18:10:13 +0700 Subject: [PATCH 12/66] wip --- mm2src/coins/eth.rs | 175 +++++++++++++++------------- mm2src/coins/eth/nft/nft_errors.rs | 13 ++- mm2src/coins/eth/nft/nft_structs.rs | 10 +- mm2src/coins/lp_coins.rs | 4 +- 4 files changed, 109 insertions(+), 93 deletions(-) diff --git a/mm2src/coins/eth.rs b/mm2src/coins/eth.rs index ff95035bd0..e6bb4bb90a 100644 --- a/mm2src/coins/eth.rs +++ b/mm2src/coins/eth.rs @@ -48,7 +48,7 @@ use mm2_net::transport::{slurp_url, GuiAuthValidation, GuiAuthValidationGenerato use mm2_number::{BigDecimal, MmNumber}; #[cfg(test)] use mocktopus::macros::*; use nft::nft_errors::GetNftInfoError; -use nft::nft_structs::{Nft, NftListReq, NftMetadataReq, NftTransfersReq, Nfts, NftsTransferHistoryByChain, +use nft::nft_structs::{Nft, NftList, NftListReq, NftMetadataReq, NftTransfersReq, NftsTransferHistoryByChain, TransactionNftDetails, WithdrawErc1155Request, WithdrawErc721Request}; use rand::seq::SliceRandom; use rpc::v1::types::Bytes as BytesJson; @@ -97,7 +97,7 @@ mod web3_transport; use crate::eth::nft::nft_structs::Chain; use crate::eth::v2_activation::EthActivationV2Error; use crate::eth::web3_transport::http_transport::single_response; -use crate::MyWalletAddress; +use crate::{lp_coinfind_or_err, MyWalletAddress}; use v2_activation::build_address_and_priv_key_policy; /// https://github.com/artemii235/etomic-swap/blob/master/contracts/EtomicSwap.sol @@ -811,80 +811,13 @@ async fn withdraw_impl(coin: EthCoin, req: WithdrawRequest) -> WithdrawResult { }) } -#[allow(dead_code)] -#[cfg(not(target_arch = "wasm32"))] -async fn send_moralis_request(uri: String, api_key: String) -> MmResult { - use gstuff::binprint; - use mm2_net::transport::slurp_req; - - let request = Request::builder() - .method("GET") - .uri(uri.clone()) - .header(X_API_KEY, api_key) - .header(ACCEPT, HeaderValue::from_static(APPLICATION_JSON)) - .body(Vec::from(""))?; - - let (status, _headers, body) = slurp_req(request).await?; - if !status.is_success() { - return Err(MmError::new(GetNftInfoError::Transport(format!( - "Response !200 from {}: {}, {}", - uri, - status, - binprint(&body, b'.') - )))); - } - let res = single_response(body, &uri)?; - Ok(res) -} - -#[cfg(target_arch = "wasm32")] -async fn send_moralis_request(uri: String, api_key: String) -> MmResult { - use mm2_net::wasm_http::FetchRequest; - use web3::helpers::to_result_from_output; - - macro_rules! try_or { - ($exp:expr, $errtype:ident) => { - match $exp { - Ok(x) => x, - Err(e) => return Err(MmError::new(GetNftInfoError::$errtype(ERRL!("{:?}", e)))), - } - }; - } - - let result = FetchRequest::get(uri.as_str()) - .cors() - .body_utf8("") - .header(X_API_KEY, api_key) - .header(ACCEPT, APPLICATION_JSON) - .request_str() - .await; - let (status_code, response_str) = try_or!(result, Transport); - if !status_code.is_success() { - return Err(MmError::new(GetNftInfoError::Transport(ERRL!( - "!200: {}, {}", - status_code, - response_str - )))); - } - - let response: Response = try_or!(serde_json::from_str(&response_str), InvalidResponse); - match response { - Response::Single(output) => { - let res = to_result_from_output(output)?; - Ok(res) - }, - Response::Batch(_) => Err(MmError::new(GetNftInfoError::InvalidResponse( - "Expected single, got batch.".to_owned(), - ))), - } -} - -pub async fn get_nft_list(_ctx: MmArc, req: NftListReq) -> MmResult, GetNftInfoError> { +pub async fn get_nft_list(ctx: MmArc, req: NftListReq) -> MmResult { for chain in req.chains { - match chain { - Chain::Eth => {}, - Chain::Bnb => {}, - } + let (coin, _chain) = match chain { + Chain::Eth => ("ETH", "eth"), + Chain::Bnb => ("BNB", "bsc"), + }; + let _my_address = get_eth_address(coin, &ctx).await?; } todo!() } @@ -898,9 +831,15 @@ pub async fn get_nft_transfers( todo!() } -pub async fn withdraw_erc721(_ctx: MmArc, _req: WithdrawErc721Request) -> WithdrawNftResult { todo!() } +pub async fn withdraw_erc721(ctx: MmArc, req: WithdrawErc721Request) -> WithdrawNftResult { + let _coin = lp_coinfind_or_err(&ctx, &req.coin).await?; + todo!() +} -pub async fn withdraw_erc1155(_ctx: MmArc, _req: WithdrawErc1155Request) -> WithdrawNftResult { todo!() } +pub async fn withdraw_erc1155(ctx: MmArc, req: WithdrawErc1155Request) -> WithdrawNftResult { + let _coin = lp_coinfind_or_err(&ctx, &req.coin).await?; + todo!() +} #[derive(Clone)] pub struct EthCoin(Arc); @@ -4637,6 +4576,7 @@ fn increase_gas_price_by_stage(gas_price: U256, level: &FeeApproxStage) -> U256 pub enum GetEthAddressError { PrivKeyPolicyNotAllowed(PrivKeyPolicyNotAllowed), EthActivationV2Error(EthActivationV2Error), + Internal(String), } impl From for GetEthAddressError { @@ -4646,16 +4586,17 @@ impl From for GetEthAddressError { fn from(e: EthActivationV2Error) -> Self { GetEthAddressError::EthActivationV2Error(e) } } +impl From for GetEthAddressError { + fn from(e: CryptoCtxError) -> Self { GetEthAddressError::Internal(e.to_string()) } +} + /// `get_eth_address` returns wallet address for coin with `ETH` protocol type. -pub async fn get_eth_address( - ticker: &str, - conf: &Json, - priv_key_policy: PrivKeyBuildPolicy, -) -> MmResult { +pub async fn get_eth_address(ticker: &str, ctx: &MmArc) -> MmResult { + let priv_key_policy = PrivKeyBuildPolicy::detect_priv_key_policy(ctx)?; // Convert `PrivKeyBuildPolicy` to `EthPrivKeyBuildPolicy` if it's possible. let priv_key_policy = EthPrivKeyBuildPolicy::try_from(priv_key_policy)?; - let (my_address, ..) = build_address_and_priv_key_policy(conf, priv_key_policy)?; + let (my_address, ..) = build_address_and_priv_key_policy(&ctx.conf, priv_key_policy)?; let wallet_address = checksum_address(&format!("{:#02x}", my_address)); Ok(MyWalletAddress { @@ -4663,3 +4604,71 @@ pub async fn get_eth_address( wallet_address, }) } + +#[allow(dead_code)] +#[cfg(not(target_arch = "wasm32"))] +async fn send_moralis_request(uri: String, api_key: String) -> MmResult { + use gstuff::binprint; + use mm2_net::transport::slurp_req; + + let request = Request::builder() + .method("GET") + .uri(uri.clone()) + .header(X_API_KEY, api_key) + .header(ACCEPT, HeaderValue::from_static(APPLICATION_JSON)) + .body(Vec::from(""))?; + + let (status, _headers, body) = slurp_req(request).await?; + if !status.is_success() { + return Err(MmError::new(GetNftInfoError::Transport(format!( + "Response !200 from {}: {}, {}", + uri, + status, + binprint(&body, b'.') + )))); + } + let res = single_response(body, &uri)?; + Ok(res) +} + +#[cfg(target_arch = "wasm32")] +async fn send_moralis_request(uri: String, api_key: String) -> MmResult { + use mm2_net::wasm_http::FetchRequest; + use web3::helpers::to_result_from_output; + + macro_rules! try_or { + ($exp:expr, $errtype:ident) => { + match $exp { + Ok(x) => x, + Err(e) => return Err(MmError::new(GetNftInfoError::$errtype(ERRL!("{:?}", e)))), + } + }; + } + + let result = FetchRequest::get(uri.as_str()) + .cors() + .body_utf8("") + .header(X_API_KEY, api_key) + .header(ACCEPT, APPLICATION_JSON) + .request_str() + .await; + let (status_code, response_str) = try_or!(result, Transport); + if !status_code.is_success() { + return Err(MmError::new(GetNftInfoError::Transport(ERRL!( + "!200: {}, {}", + status_code, + response_str + )))); + } + + let response: Response = try_or!(serde_json::from_str(&response_str), InvalidResponse); + match response { + Response::Single(output) => { + let res = to_result_from_output(output)?; + Ok(res) + }, + Response::Batch(_) => Err(MmError::new(GetNftInfoError::InvalidResponse( + "Expected single, got batch.".to_owned(), + ))), + } +} diff --git a/mm2src/coins/eth/nft/nft_errors.rs b/mm2src/coins/eth/nft/nft_errors.rs index e4c3a52539..72b5fc5932 100644 --- a/mm2src/coins/eth/nft/nft_errors.rs +++ b/mm2src/coins/eth/nft/nft_errors.rs @@ -1,3 +1,4 @@ +use crate::eth::GetEthAddressError; use common::HttpStatusCode; use derive_more::Display; use http::StatusCode; @@ -16,6 +17,7 @@ pub enum GetNftInfoError { InvalidResponse(String), #[display(fmt = "Internal: {}", _0)] Internal(String), + GetEthAddressError(GetEthAddressError), } /// `http::Error` can appear on an HTTP request [`http::Builder::build`] building. @@ -48,13 +50,18 @@ impl From for GetNftInfoError { } } +impl From for GetNftInfoError { + fn from(e: GetEthAddressError) -> Self { GetNftInfoError::GetEthAddressError(e) } +} + impl HttpStatusCode for GetNftInfoError { fn status_code(&self) -> StatusCode { match self { GetNftInfoError::InvalidRequest(_) => StatusCode::BAD_REQUEST, - GetNftInfoError::Transport(_) | GetNftInfoError::InvalidResponse(_) | GetNftInfoError::Internal(_) => { - StatusCode::INTERNAL_SERVER_ERROR - }, + GetNftInfoError::Transport(_) + | GetNftInfoError::InvalidResponse(_) + | GetNftInfoError::Internal(_) + | GetNftInfoError::GetEthAddressError(_) => StatusCode::INTERNAL_SERVER_ERROR, } } } diff --git a/mm2src/coins/eth/nft/nft_structs.rs b/mm2src/coins/eth/nft/nft_structs.rs index 3593a1ed24..5f5540f1d5 100644 --- a/mm2src/coins/eth/nft/nft_structs.rs +++ b/mm2src/coins/eth/nft/nft_structs.rs @@ -11,6 +11,8 @@ pub struct NftListReq { #[allow(dead_code)] #[derive(Debug, Deserialize)] pub struct NftMetadataReq { + token_address: String, + token_id: BigDecimal, chain: String, } @@ -31,6 +33,7 @@ pub enum ContractType { #[allow(dead_code)] #[derive(Debug, Deserialize, Serialize)] pub struct Nft { + chain: Chain, token_address: String, token_id: BigDecimal, amount: BigDecimal, @@ -50,8 +53,7 @@ pub struct Nft { #[allow(dead_code)] #[derive(Debug, Deserialize, Serialize)] -pub struct Nfts { - chain: Chain, +pub struct NftList { count: i64, nfts: Vec, } @@ -59,7 +61,7 @@ pub struct Nfts { #[allow(dead_code)] #[derive(Clone, Deserialize)] pub struct WithdrawErc721Request { - coin: String, + pub coin: String, to: String, token_address: String, token_id: BigDecimal, @@ -69,7 +71,7 @@ pub struct WithdrawErc721Request { #[allow(dead_code)] #[derive(Clone, Deserialize)] pub struct WithdrawErc1155Request { - coin: String, + pub coin: String, to: String, token_address: String, token_id: BigDecimal, diff --git a/mm2src/coins/lp_coins.rs b/mm2src/coins/lp_coins.rs index e013bfd969..4f32535965 100644 --- a/mm2src/coins/lp_coins.rs +++ b/mm2src/coins/lp_coins.rs @@ -3443,12 +3443,10 @@ pub async fn get_my_address(ctx: MmArc, req: MyAddressReq) -> MmResult get_eth_address(&req.coin, &coins_en, priv_key_policy).await?, + CoinProtocol::ETH => get_eth_address(&req.coin, &ctx).await?, CoinProtocol::UTXO => { return MmError::err(GetMyAddressError::CoinIsNotSupported( "UTXO protocol is not supported by get_my_address".to_owned(), From db638e9dbd661aa38dd0cc94be82c27c143ba578 Mon Sep 17 00:00:00 2001 From: laruh Date: Fri, 3 Feb 2023 19:47:09 +0700 Subject: [PATCH 13/66] wip --- mm2src/coins/eth.rs | 25 +++++++++++++++++++------ mm2src/coins/eth/nft/nft_errors.rs | 5 ++++- 2 files changed, 23 insertions(+), 7 deletions(-) diff --git a/mm2src/coins/eth.rs b/mm2src/coins/eth.rs index e6bb4bb90a..6cd31a3a98 100644 --- a/mm2src/coins/eth.rs +++ b/mm2src/coins/eth.rs @@ -48,7 +48,7 @@ use mm2_net::transport::{slurp_url, GuiAuthValidation, GuiAuthValidationGenerato use mm2_number::{BigDecimal, MmNumber}; #[cfg(test)] use mocktopus::macros::*; use nft::nft_errors::GetNftInfoError; -use nft::nft_structs::{Nft, NftList, NftListReq, NftMetadataReq, NftTransfersReq, NftsTransferHistoryByChain, +use nft::nft_structs::{Nft, NftListReq, NftMetadataReq, NftTransfersReq, NftsTransferHistoryByChain, TransactionNftDetails, WithdrawErc1155Request, WithdrawErc721Request}; use rand::seq::SliceRandom; use rpc::v1::types::Bytes as BytesJson; @@ -811,15 +811,28 @@ async fn withdraw_impl(coin: EthCoin, req: WithdrawRequest) -> WithdrawResult { }) } -pub async fn get_nft_list(ctx: MmArc, req: NftListReq) -> MmResult { +pub async fn get_nft_list(ctx: MmArc, req: NftListReq) -> MmResult, GetNftInfoError> { + let mut res = Vec::new(); for chain in req.chains { - let (coin, _chain) = match chain { + let (coin, chain) = match chain { Chain::Eth => ("ETH", "eth"), Chain::Bnb => ("BNB", "bsc"), }; - let _my_address = get_eth_address(coin, &ctx).await?; + let my_address = get_eth_address(coin, &ctx).await?; + let uri = format!( + "{}{}/nft?&chain={}&{}", + URL_MORALIS, my_address.wallet_address, chain, FORMAT_DECIMAL_MORALIS + ); + + let api_key = match ctx.conf["api_key"].as_str() { + Some(api_key) => api_key, + None => return Err(MmError::new(GetNftInfoError::ApiKeyError)), + }; + + let response = send_moralis_request(uri, api_key).await?; + res.push(response); } - todo!() + Ok(res) } pub async fn get_nft_metadata(_ctx: MmArc, _req: NftMetadataReq) -> MmResult { todo!() } @@ -4607,7 +4620,7 @@ pub async fn get_eth_address(ticker: &str, ctx: &MmArc) -> MmResult MmResult { +async fn send_moralis_request(uri: String, api_key: &str) -> MmResult { use gstuff::binprint; use mm2_net::transport::slurp_req; diff --git a/mm2src/coins/eth/nft/nft_errors.rs b/mm2src/coins/eth/nft/nft_errors.rs index 72b5fc5932..d9e1a5f44c 100644 --- a/mm2src/coins/eth/nft/nft_errors.rs +++ b/mm2src/coins/eth/nft/nft_errors.rs @@ -18,6 +18,8 @@ pub enum GetNftInfoError { #[display(fmt = "Internal: {}", _0)] Internal(String), GetEthAddressError(GetEthAddressError), + #[display(fmt = "X-API-Key is missing")] + ApiKeyError, } /// `http::Error` can appear on an HTTP request [`http::Builder::build`] building. @@ -61,7 +63,8 @@ impl HttpStatusCode for GetNftInfoError { GetNftInfoError::Transport(_) | GetNftInfoError::InvalidResponse(_) | GetNftInfoError::Internal(_) - | GetNftInfoError::GetEthAddressError(_) => StatusCode::INTERNAL_SERVER_ERROR, + | GetNftInfoError::GetEthAddressError(_) + | GetNftInfoError::ApiKeyError => StatusCode::INTERNAL_SERVER_ERROR, } } } From 5fc4e8e4c462e2642b3b633d6917c679c591acb3 Mon Sep 17 00:00:00 2001 From: laruh Date: Fri, 3 Feb 2023 21:57:50 +0700 Subject: [PATCH 14/66] wip use fn slurp_req_body in fn send_moralis_request --- mm2src/coins/eth.rs | 27 +++++++-------------------- mm2src/mm2_net/src/native_http.rs | 22 +++++++++++++++++++++- mm2src/mm2_net/src/transport.rs | 9 ++++++++- 3 files changed, 36 insertions(+), 22 deletions(-) diff --git a/mm2src/coins/eth.rs b/mm2src/coins/eth.rs index 6cd31a3a98..881a55c492 100644 --- a/mm2src/coins/eth.rs +++ b/mm2src/coins/eth.rs @@ -96,7 +96,6 @@ mod web3_transport; #[path = "eth/v2_activation.rs"] pub mod v2_activation; use crate::eth::nft::nft_structs::Chain; use crate::eth::v2_activation::EthActivationV2Error; -use crate::eth::web3_transport::http_transport::single_response; use crate::{lp_coinfind_or_err, MyWalletAddress}; use v2_activation::build_address_and_priv_key_policy; @@ -4621,27 +4620,23 @@ pub async fn get_eth_address(ticker: &str, ctx: &MmArc) -> MmResult MmResult { - use gstuff::binprint; - use mm2_net::transport::slurp_req; + use mm2_net::transport::slurp_req_body; let request = Request::builder() .method("GET") .uri(uri.clone()) .header(X_API_KEY, api_key) .header(ACCEPT, HeaderValue::from_static(APPLICATION_JSON)) - .body(Vec::from(""))?; + .body(hyper::Body::from(""))?; - let (status, _headers, body) = slurp_req(request).await?; + let (status, _header, body) = slurp_req_body(request).await?; if !status.is_success() { return Err(MmError::new(GetNftInfoError::Transport(format!( "Response !200 from {}: {}, {}", - uri, - status, - binprint(&body, b'.') + uri, status, body )))); } - let res = single_response(body, &uri)?; - Ok(res) + Ok(body) } #[cfg(target_arch = "wasm32")] @@ -4674,14 +4669,6 @@ async fn send_moralis_request(uri: String, api_key: String) -> MmResult { - let res = to_result_from_output(output)?; - Ok(res) - }, - Response::Batch(_) => Err(MmError::new(GetNftInfoError::InvalidResponse( - "Expected single, got batch.".to_owned(), - ))), - } + let response: Value = try_or!(serde_json::from_str(&response_str), InvalidResponse); + Ok(response) } diff --git a/mm2src/mm2_net/src/native_http.rs b/mm2src/mm2_net/src/native_http.rs index ea4b0b2912..e2636ef95d 100644 --- a/mm2src/mm2_net/src/native_http.rs +++ b/mm2src/mm2_net/src/native_http.rs @@ -1,10 +1,11 @@ -use crate::transport::{SlurpError, SlurpResult}; +use crate::transport::{SlurpError, SlurpResult, SlurpResultJson}; use common::wio::{drive03, HYPER}; use common::APPLICATION_JSON; use futures::channel::oneshot::Canceled; use http::{header, Request}; use hyper::Body; use mm2_err_handle::prelude::*; +use serde_json::Value as Json; impl From for SlurpError { fn from(_: Canceled) -> Self { SlurpError::Internal("Spawned Slurp future has been canceled".to_owned()) } @@ -49,6 +50,25 @@ pub async fn slurp_req(request: Request>) -> SlurpResult { Ok((status, headers, output.to_vec())) } +/// Executes a Hyper request, requires [`Request`] and return the response status, headers and body as Json. +pub async fn slurp_req_body(request: Request) -> SlurpResultJson { + let uri = request.uri().to_string(); + + let request_f = HYPER.request(request); + let response = drive03(request_f) + .await? + .map_to_mm(|e| SlurpError::from_hyper_error(e, uri.clone()))?; + let status = response.status(); + let headers = response.headers().clone(); + // Get the response body bytes. + let body_bytes = hyper::body::to_bytes(response.into_body()) + .await + .map_to_mm(|e| SlurpError::from_hyper_error(e, uri.clone()))?; + let body_str = String::from_utf8(body_bytes.to_vec()).unwrap(); + let body: Json = serde_json::from_str(&body_str)?; + Ok((status, headers, body)) +} + /// Executes a GET request, returning the response status, headers and body. pub async fn slurp_url(url: &str) -> SlurpResult { let req = Request::builder().uri(url).body(Vec::new())?; diff --git a/mm2src/mm2_net/src/transport.rs b/mm2src/mm2_net/src/transport.rs index ad51122993..18e4f4feb6 100644 --- a/mm2src/mm2_net/src/transport.rs +++ b/mm2src/mm2_net/src/transport.rs @@ -4,15 +4,18 @@ use ethkey::Secret; use http::{HeaderMap, StatusCode}; use mm2_err_handle::prelude::*; use serde::{Deserialize, Serialize}; +use serde_json::{Error, Value as Json}; #[cfg(not(target_arch = "wasm32"))] -pub use crate::native_http::{slurp_post_json, slurp_req, slurp_url}; +pub use crate::native_http::{slurp_post_json, slurp_req, slurp_req_body, slurp_url}; #[cfg(target_arch = "wasm32")] pub use crate::wasm_http::{slurp_post_json, slurp_url}; pub type SlurpResult = Result<(StatusCode, HeaderMap, Vec), MmError>; +pub type SlurpResultJson = Result<(StatusCode, HeaderMap, Json), MmError>; + #[derive(Debug, Deserialize, Display, Serialize)] pub enum SlurpError { #[display(fmt = "Error deserializing '{}' response: {}", uri, error)] @@ -27,6 +30,10 @@ pub enum SlurpError { Internal(String), } +impl From for SlurpError { + fn from(e: Error) -> Self { SlurpError::Internal(e.to_string()) } +} + impl From for JsonRpcErrorType { fn from(err: SlurpError) -> Self { match err { From 778dd60e2f5660b5342c8179a5e94c8d5c2ec17a Mon Sep 17 00:00:00 2001 From: laruh Date: Tue, 7 Feb 2023 11:38:56 +0700 Subject: [PATCH 15/66] wip fix wasm --- mm2src/coins/eth.rs | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/mm2src/coins/eth.rs b/mm2src/coins/eth.rs index 881a55c492..d9ce9fdefe 100644 --- a/mm2src/coins/eth.rs +++ b/mm2src/coins/eth.rs @@ -41,7 +41,7 @@ use futures::compat::Future01CompatExt; use futures::future::{join_all, select, Either, FutureExt, TryFutureExt}; use futures01::Future; use http::header::ACCEPT; -use http::{HeaderValue, Request, StatusCode}; +use http::StatusCode; use mm2_core::mm_ctx::{MmArc, MmWeak}; use mm2_err_handle::prelude::*; use mm2_net::transport::{slurp_url, GuiAuthValidation, GuiAuthValidationGenerator, SlurpError}; @@ -4620,9 +4620,10 @@ pub async fn get_eth_address(ticker: &str, ctx: &MmArc) -> MmResult MmResult { + use http::header::HeaderValue; use mm2_net::transport::slurp_req_body; - let request = Request::builder() + let request = http::Request::builder() .method("GET") .uri(uri.clone()) .header(X_API_KEY, api_key) @@ -4640,9 +4641,8 @@ async fn send_moralis_request(uri: String, api_key: &str) -> MmResult MmResult { +async fn send_moralis_request(uri: String, api_key: &str) -> MmResult { use mm2_net::wasm_http::FetchRequest; - use web3::helpers::to_result_from_output; macro_rules! try_or { ($exp:expr, $errtype:ident) => { @@ -4655,9 +4655,9 @@ async fn send_moralis_request(uri: String, api_key: String) -> MmResult MmResult Date: Tue, 7 Feb 2023 15:41:25 +0700 Subject: [PATCH 16/66] wip cursor in get_nft_list --- mm2src/coins/eth.rs | 64 ++++-- mm2src/coins/eth/erc1155_abi.json | 314 ++++++++++++++++++++++++++++ mm2src/coins/eth/erc721_abi.json | 158 ++++++++++++++ mm2src/coins/eth/nft/nft_structs.rs | 34 +-- mm2src/mm2_net/src/native_http.rs | 2 +- 5 files changed, 539 insertions(+), 33 deletions(-) diff --git a/mm2src/coins/eth.rs b/mm2src/coins/eth.rs index d9ce9fdefe..b7875a3434 100644 --- a/mm2src/coins/eth.rs +++ b/mm2src/coins/eth.rs @@ -48,8 +48,9 @@ use mm2_net::transport::{slurp_url, GuiAuthValidation, GuiAuthValidationGenerato use mm2_number::{BigDecimal, MmNumber}; #[cfg(test)] use mocktopus::macros::*; use nft::nft_errors::GetNftInfoError; -use nft::nft_structs::{Nft, NftListReq, NftMetadataReq, NftTransfersReq, NftsTransferHistoryByChain, - TransactionNftDetails, WithdrawErc1155Request, WithdrawErc721Request}; +use nft::nft_structs::{Chain, ContractType, Nft, NftList, NftListReq, NftMetadataReq, NftTransfersReq, + NftsTransferHistoryByChain, TransactionNftDetails, WithdrawErc1155Request, + WithdrawErc721Request}; use rand::seq::SliceRandom; use rpc::v1::types::Bytes as BytesJson; use secp256k1::PublicKey; @@ -94,7 +95,6 @@ mod nft; mod web3_transport; #[path = "eth/v2_activation.rs"] pub mod v2_activation; -use crate::eth::nft::nft_structs::Chain; use crate::eth::v2_activation::EthActivationV2Error; use crate::{lp_coinfind_or_err, MyWalletAddress}; use v2_activation::build_address_and_priv_key_policy; @@ -810,28 +810,62 @@ async fn withdraw_impl(coin: EthCoin, req: WithdrawRequest) -> WithdrawResult { }) } -pub async fn get_nft_list(ctx: MmArc, req: NftListReq) -> MmResult, GetNftInfoError> { - let mut res = Vec::new(); +pub async fn get_nft_list(ctx: MmArc, req: NftListReq) -> MmResult { + // let mut res = Vec::new(); for chain in req.chains { - let (coin, chain) = match chain { + let (coin_str, chain_str) = match chain { Chain::Eth => ("ETH", "eth"), Chain::Bnb => ("BNB", "bsc"), }; - let my_address = get_eth_address(coin, &ctx).await?; - let uri = format!( + let my_address = get_eth_address(coin_str, &ctx).await?; + let uri_without_cursor = format!( "{}{}/nft?&chain={}&{}", - URL_MORALIS, my_address.wallet_address, chain, FORMAT_DECIMAL_MORALIS + URL_MORALIS, my_address.wallet_address, chain_str, FORMAT_DECIMAL_MORALIS ); let api_key = match ctx.conf["api_key"].as_str() { Some(api_key) => api_key, None => return Err(MmError::new(GetNftInfoError::ApiKeyError)), }; - - let response = send_moralis_request(uri, api_key).await?; - res.push(response); + let mut cursor = "".to_owned(); + loop { + let uri = format!("{}{}", uri_without_cursor, cursor); + let response = send_moralis_request(uri.as_str(), api_key).await?; + if let Some(nfts_list) = response["result"].as_array() { + if !nfts_list.is_empty() { + for nft_json in nfts_list { + let nft = Nft { + chain: chain.clone(), + token_address: "".to_string(), + token_id: Default::default(), + amount: Default::default(), + owner_of: "".to_string(), + token_hash: "".to_string(), + block_number_minted: 0, + block_number: 0, + contract_type: ContractType::Erc721, + name: None, + symbol: None, + token_uri: None, + metadata: None, + last_token_uri_sync: None, + last_metadata_sync: None, + minter_address: None, + }; + } + } + if !response["cursor"].is_null() { + if let Some(cursor_res) = response["cursor"].as_str() { + cursor = format!("{}{}", "&cursor=", cursor_res); + continue; + } + } else { + break; + } + } + } } - Ok(res) + todo!() } pub async fn get_nft_metadata(_ctx: MmArc, _req: NftMetadataReq) -> MmResult { todo!() } @@ -4619,13 +4653,13 @@ pub async fn get_eth_address(ticker: &str, ctx: &MmArc) -> MmResult MmResult { +async fn send_moralis_request(uri: &str, api_key: &str) -> MmResult { use http::header::HeaderValue; use mm2_net::transport::slurp_req_body; let request = http::Request::builder() .method("GET") - .uri(uri.clone()) + .uri(uri) .header(X_API_KEY, api_key) .header(ACCEPT, HeaderValue::from_static(APPLICATION_JSON)) .body(hyper::Body::from(""))?; diff --git a/mm2src/coins/eth/erc1155_abi.json b/mm2src/coins/eth/erc1155_abi.json index e69de29bb2..211a562a85 100644 --- a/mm2src/coins/eth/erc1155_abi.json +++ b/mm2src/coins/eth/erc1155_abi.json @@ -0,0 +1,314 @@ +[ + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "account", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "operator", + "type": "address" + }, + { + "indexed": false, + "internalType": "bool", + "name": "approved", + "type": "bool" + } + ], + "name": "ApprovalForAll", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "operator", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "from", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "to", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256[]", + "name": "ids", + "type": "uint256[]" + }, + { + "indexed": false, + "internalType": "uint256[]", + "name": "values", + "type": "uint256[]" + } + ], + "name": "TransferBatch", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "operator", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "from", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "to", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "id", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "value", + "type": "uint256" + } + ], + "name": "TransferSingle", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "string", + "name": "value", + "type": "string" + }, + { + "indexed": true, + "internalType": "uint256", + "name": "id", + "type": "uint256" + } + ], + "name": "URI", + "type": "event" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "account", + "type": "address" + }, + { + "internalType": "uint256", + "name": "id", + "type": "uint256" + } + ], + "name": "balanceOf", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address[]", + "name": "accounts", + "type": "address[]" + }, + { + "internalType": "uint256[]", + "name": "ids", + "type": "uint256[]" + } + ], + "name": "balanceOfBatch", + "outputs": [ + { + "internalType": "uint256[]", + "name": "", + "type": "uint256[]" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "account", + "type": "address" + }, + { + "internalType": "address", + "name": "operator", + "type": "address" + } + ], + "name": "isApprovedForAll", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "from", + "type": "address" + }, + { + "internalType": "address", + "name": "to", + "type": "address" + }, + { + "internalType": "uint256[]", + "name": "ids", + "type": "uint256[]" + }, + { + "internalType": "uint256[]", + "name": "amounts", + "type": "uint256[]" + }, + { + "internalType": "bytes", + "name": "data", + "type": "bytes" + } + ], + "name": "safeBatchTransferFrom", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "from", + "type": "address" + }, + { + "internalType": "address", + "name": "to", + "type": "address" + }, + { + "internalType": "uint256", + "name": "id", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "amount", + "type": "uint256" + }, + { + "internalType": "bytes", + "name": "data", + "type": "bytes" + } + ], + "name": "safeTransferFrom", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "operator", + "type": "address" + }, + { + "internalType": "bool", + "name": "approved", + "type": "bool" + } + ], + "name": "setApprovalForAll", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes4", + "name": "interfaceId", + "type": "bytes4" + } + ], + "name": "supportsInterface", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "id", + "type": "uint256" + } + ], + "name": "uri", + "outputs": [ + { + "internalType": "string", + "name": "", + "type": "string" + } + ], + "stateMutability": "view", + "type": "function" + } +] \ No newline at end of file diff --git a/mm2src/coins/eth/erc721_abi.json b/mm2src/coins/eth/erc721_abi.json index e69de29bb2..a309442a43 100644 --- a/mm2src/coins/eth/erc721_abi.json +++ b/mm2src/coins/eth/erc721_abi.json @@ -0,0 +1,158 @@ +[ + { + "anonymous": false, + "inputs": [{"indexed": true, "internalType": "address", "name": "owner", "type": "address"}, { + "indexed": true, + "internalType": "address", + "name": "approved", + "type": "address" + }, {"indexed": true, "internalType": "uint256", "name": "tokenId", "type": "uint256"}], + "name": "Approval", + "type": "event" + }, + { + "anonymous": false, + "inputs": [{"indexed": true, "internalType": "address", "name": "owner", "type": "address"}, { + "indexed": true, + "internalType": "address", + "name": "operator", + "type": "address" + }, {"indexed": false, "internalType": "bool", "name": "approved", "type": "bool"}], + "name": "ApprovalForAll", + "type": "event" + }, + { + "anonymous": false, + "inputs": [{"indexed": true, "internalType": "address", "name": "from", "type": "address"}, { + "indexed": true, + "internalType": "address", + "name": "to", + "type": "address" + }, {"indexed": true, "internalType": "uint256", "name": "tokenId", "type": "uint256"}], + "name": "Transfer", + "type": "event" + }, + { + "inputs": [{"internalType": "address", "name": "to", "type": "address"}, { + "internalType": "uint256", + "name": "tokenId", + "type": "uint256" + }], "name": "approve", "outputs": [], "stateMutability": "nonpayable", "type": "function" + }, + { + "constant": true, + "inputs": [], + "name": "totalSupply", + "outputs": [ + { + "name": "", + "type": "uint256" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [{"internalType": "address", "name": "owner", "type": "address"}], + "name": "balanceOf", + "outputs": [{"internalType": "uint256", "name": "balance", "type": "uint256"}], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [{"internalType": "uint256", "name": "tokenId", "type": "uint256"}], + "name": "getApproved", + "outputs": [{"internalType": "address", "name": "operator", "type": "address"}], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [{"internalType": "address", "name": "owner", "type": "address"}, { + "internalType": "address", + "name": "operator", + "type": "address" + }], + "name": "isApprovedForAll", + "outputs": [{"internalType": "bool", "name": "", "type": "bool"}], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "name", + "outputs": [{"internalType": "string", "name": "", "type": "string"}], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [{"internalType": "uint256", "name": "tokenId", "type": "uint256"}], + "name": "ownerOf", + "outputs": [{"internalType": "address", "name": "owner", "type": "address"}], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [{"internalType": "address", "name": "from", "type": "address"}, { + "internalType": "address", + "name": "to", + "type": "address" + }, + {"internalType": "uint256", "name": "tokenId", "type": "uint256"}], + "name": "safeTransferFrom", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [{"internalType": "address", "name": "from", "type": "address"}, { + "internalType": "address", + "name": "to", + "type": "address" + }, + {"internalType": "uint256", "name": "tokenId", "type": "uint256"}, { + "internalType": "bytes", + "name": "data", + "type": "bytes" + }], "name": "safeTransferFrom", "outputs": [], "stateMutability": "nonpayable", "type": "function" + }, + { + "inputs": [{"internalType": "address", "name": "operator", "type": "address"}, { + "internalType": "bool", + "name": "_approved", + "type": "bool" + }], "name": "setApprovalForAll", "outputs": [], "stateMutability": "nonpayable", "type": "function" + }, + { + "inputs": [{"internalType": "bytes4", "name": "interfaceId", "type": "bytes4"}], + "name": "supportsInterface", + "outputs": [{"internalType": "bool", "name": "", "type": "bool"}], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "symbol", + "outputs": [{"internalType": "string", "name": "", "type": "string"}], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [{"internalType": "uint256", "name": "tokenId", "type": "uint256"}], + "name": "tokenURI", + "outputs": [{"internalType": "string", "name": "", "type": "string"}], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [{"internalType": "address", "name": "from", "type": "address"}, { + "internalType": "address", + "name": "to", + "type": "address" + }, {"internalType": "uint256", "name": "tokenId", "type": "uint256"}], + "name": "transferFrom", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + } +] \ No newline at end of file diff --git a/mm2src/coins/eth/nft/nft_structs.rs b/mm2src/coins/eth/nft/nft_structs.rs index 5f5540f1d5..764539b254 100644 --- a/mm2src/coins/eth/nft/nft_structs.rs +++ b/mm2src/coins/eth/nft/nft_structs.rs @@ -17,7 +17,7 @@ pub struct NftMetadataReq { } #[allow(dead_code)] -#[derive(Debug, Deserialize, Serialize)] +#[derive(Debug, Deserialize, Serialize, Clone)] pub enum Chain { Eth, Bnb, @@ -33,22 +33,22 @@ pub enum ContractType { #[allow(dead_code)] #[derive(Debug, Deserialize, Serialize)] pub struct Nft { - chain: Chain, - token_address: String, - token_id: BigDecimal, - amount: BigDecimal, - owner_of: String, - token_hash: String, - block_number_minted: u64, - block_number: u64, - contract_type: ContractType, - name: Option, - symbol: Option, - token_uri: Option, - metadata: Option, - last_token_uri_sync: Option, - last_metadata_sync: Option, - minter_address: Option, + pub(crate) chain: Chain, + pub(crate) token_address: String, + pub(crate) token_id: BigDecimal, + pub(crate) amount: BigDecimal, + pub(crate) owner_of: String, + pub(crate) token_hash: String, + pub(crate) block_number_minted: u64, + pub(crate) block_number: u64, + pub(crate) contract_type: ContractType, + pub(crate) name: Option, + pub(crate) symbol: Option, + pub(crate) token_uri: Option, + pub(crate) metadata: Option, + pub(crate) last_token_uri_sync: Option, + pub(crate) last_metadata_sync: Option, + pub(crate) minter_address: Option, } #[allow(dead_code)] diff --git a/mm2src/mm2_net/src/native_http.rs b/mm2src/mm2_net/src/native_http.rs index e2636ef95d..641a0cae24 100644 --- a/mm2src/mm2_net/src/native_http.rs +++ b/mm2src/mm2_net/src/native_http.rs @@ -64,7 +64,7 @@ pub async fn slurp_req_body(request: Request) -> SlurpResultJson { let body_bytes = hyper::body::to_bytes(response.into_body()) .await .map_to_mm(|e| SlurpError::from_hyper_error(e, uri.clone()))?; - let body_str = String::from_utf8(body_bytes.to_vec()).unwrap(); + let body_str = String::from_utf8(body_bytes.to_vec()).map_to_mm(|e| SlurpError::Internal(e.to_string()))?; let body: Json = serde_json::from_str(&body_str)?; Ok((status, headers, body)) } From 8636599eac211711fe8f81ebfea92e7685883f4b Mon Sep 17 00:00:00 2001 From: laruh Date: Wed, 8 Feb 2023 15:17:35 +0700 Subject: [PATCH 17/66] wip impl Deserialize for Wrap --- mm2src/coins/eth.rs | 17 +++--- mm2src/coins/eth/nft/nft_structs.rs | 83 ++++++++++++++++++++++++++--- 2 files changed, 84 insertions(+), 16 deletions(-) diff --git a/mm2src/coins/eth.rs b/mm2src/coins/eth.rs index b7875a3434..a138355183 100644 --- a/mm2src/coins/eth.rs +++ b/mm2src/coins/eth.rs @@ -836,14 +836,14 @@ pub async fn get_nft_list(ctx: MmArc, req: NftListReq) -> MmResult MmResult Ok(ContractType::Erc721), + "ERC1155" =>Ok(ContractType::Erc1155), + _ => Err(ParseContractTypeError::UnsupportedContractType), + } + } +} + #[allow(dead_code)] #[derive(Debug, Deserialize, Serialize)] pub struct Nft { pub(crate) chain: Chain, - pub(crate) token_address: String, - pub(crate) token_id: BigDecimal, - pub(crate) amount: BigDecimal, - pub(crate) owner_of: String, - pub(crate) token_hash: String, - pub(crate) block_number_minted: u64, - pub(crate) block_number: u64, - pub(crate) contract_type: ContractType, + pub(crate) token_address: Option, + pub(crate) token_id: Option, + pub(crate) amount: Option, + pub(crate) owner_of: Option, + pub(crate) token_hash: Option, + pub(crate) block_number_minted: Option, + pub(crate) block_number: Option, + pub(crate) contract_type: Option, + pub(crate) name: Option, + pub(crate) symbol: Option, + pub(crate) token_uri: Option, + pub(crate) metadata: Option, + pub(crate) last_token_uri_sync: Option, + pub(crate) last_metadata_sync: Option, + pub(crate) minter_address: Option, +} + +/// This structure is for deserializing NFT json from Moralis to struct. +/// Its needed to convert fields properly, bcz Moralis returns json where all fields have string type. +#[derive(Debug, Deserialize)] +pub struct NftWrapper { + pub(crate) token_address: Option, + pub(crate) token_id: Option>, + pub(crate) amount: Option>, + pub(crate) owner_of: Option, + pub(crate) token_hash: Option, + pub(crate) block_number_minted: Option>, + pub(crate) block_number: Option>, + pub(crate) contract_type: Option>, pub(crate) name: Option, pub(crate) symbol: Option, pub(crate) token_uri: Option, @@ -51,6 +93,31 @@ pub struct Nft { pub(crate) minter_address: Option, } +#[derive(Debug, Clone, Copy)] +pub struct Wrap(T); + +impl<'de, T> Deserialize<'de> for Wrap + where + T: std::str::FromStr, + T::Err: std::fmt::Debug + std::fmt::Display, +{ + fn deserialize>(deserializer: D) -> Result { + let value: &str = Deserialize::deserialize(deserializer)?; + let value: T = match value.parse() { + Ok(v) => v, + Err(e) => return Err(::custom(e)), + }; + Ok(Wrap(value)) + } +} + +impl std::ops::Deref for Wrap { + type Target = T; + fn deref(&self) -> &T { + &self.0 + } +} + #[allow(dead_code)] #[derive(Debug, Deserialize, Serialize)] pub struct NftList { From a33be79c254cc172f861489ac7ddf3c715bb7b41 Mon Sep 17 00:00:00 2001 From: laruh Date: Wed, 8 Feb 2023 17:16:22 +0700 Subject: [PATCH 18/66] get_nft_list --- mm2src/coins/eth.rs | 46 ++++++++++++++++------------- mm2src/coins/eth/nft/nft_errors.rs | 4 +++ mm2src/coins/eth/nft/nft_structs.rs | 32 ++++++++------------ 3 files changed, 43 insertions(+), 39 deletions(-) diff --git a/mm2src/coins/eth.rs b/mm2src/coins/eth.rs index a138355183..100cf419b4 100644 --- a/mm2src/coins/eth.rs +++ b/mm2src/coins/eth.rs @@ -48,7 +48,7 @@ use mm2_net::transport::{slurp_url, GuiAuthValidation, GuiAuthValidationGenerato use mm2_number::{BigDecimal, MmNumber}; #[cfg(test)] use mocktopus::macros::*; use nft::nft_errors::GetNftInfoError; -use nft::nft_structs::{Chain, ContractType, Nft, NftList, NftListReq, NftMetadataReq, NftTransfersReq, +use nft::nft_structs::{Chain, Nft, NftList, NftListReq, NftMetadataReq, NftTransfersReq, NftWrapper, NftsTransferHistoryByChain, TransactionNftDetails, WithdrawErc1155Request, WithdrawErc721Request}; use rand::seq::SliceRandom; @@ -811,7 +811,7 @@ async fn withdraw_impl(coin: EthCoin, req: WithdrawRequest) -> WithdrawResult { } pub async fn get_nft_list(ctx: MmArc, req: NftListReq) -> MmResult { - // let mut res = Vec::new(); + let mut res_list = Vec::new(); for chain in req.chains { let (coin_str, chain_str) = match chain { Chain::Eth => ("ETH", "eth"), @@ -834,24 +834,26 @@ pub async fn get_nft_list(ctx: MmArc, req: NftListReq) -> MmResult MmResult MmResult { todo!() } @@ -4676,7 +4682,7 @@ async fn send_moralis_request(uri: &str, api_key: &str) -> MmResult MmResult { +async fn send_moralis_request(uri: &str, api_key: &str) -> MmResult { use mm2_net::wasm_http::FetchRequest; macro_rules! try_or { @@ -4688,7 +4694,7 @@ async fn send_moralis_request(uri: String, api_key: &str) -> MmResult for GetNftInfoError { fn from(e: GetEthAddressError) -> Self { GetNftInfoError::GetEthAddressError(e) } } +impl From for GetNftInfoError { + fn from(e: serde_json::Error) -> Self { GetNftInfoError::InvalidResponse(e.to_string()) } +} + impl HttpStatusCode for GetNftInfoError { fn status_code(&self) -> StatusCode { match self { diff --git a/mm2src/coins/eth/nft/nft_structs.rs b/mm2src/coins/eth/nft/nft_structs.rs index 98cfb4bbd3..e1f928f189 100644 --- a/mm2src/coins/eth/nft/nft_structs.rs +++ b/mm2src/coins/eth/nft/nft_structs.rs @@ -1,8 +1,8 @@ -use std::str::FromStr; -use serde::Deserialize; use crate::{TxFeeDetails, WithdrawFee}; use mm2_number::BigDecimal; use rpc::v1::types::Bytes as BytesJson; +use serde::Deserialize; +use std::str::FromStr; // use serde_json::Value as Json; #[allow(dead_code)] @@ -19,7 +19,6 @@ pub struct NftMetadataReq { chain: String, } -#[allow(dead_code)] #[derive(Debug, Deserialize, Serialize, Clone)] pub enum Chain { Eth, @@ -31,7 +30,6 @@ pub enum ParseContractTypeError { UnsupportedContractType, } -#[allow(dead_code)] #[derive(Debug, Deserialize, Serialize)] pub enum ContractType { Erc721, @@ -45,13 +43,12 @@ impl FromStr for ContractType { fn from_str(s: &str) -> Result { match s { "ERC721" => Ok(ContractType::Erc721), - "ERC1155" =>Ok(ContractType::Erc1155), + "ERC1155" => Ok(ContractType::Erc1155), _ => Err(ParseContractTypeError::UnsupportedContractType), } } } -#[allow(dead_code)] #[derive(Debug, Deserialize, Serialize)] pub struct Nft { pub(crate) chain: Chain, @@ -74,7 +71,7 @@ pub struct Nft { /// This structure is for deserializing NFT json from Moralis to struct. /// Its needed to convert fields properly, bcz Moralis returns json where all fields have string type. -#[derive(Debug, Deserialize)] +#[derive(Debug, Deserialize, Serialize)] pub struct NftWrapper { pub(crate) token_address: Option, pub(crate) token_id: Option>, @@ -93,13 +90,13 @@ pub struct NftWrapper { pub(crate) minter_address: Option, } -#[derive(Debug, Clone, Copy)] -pub struct Wrap(T); +#[derive(Debug, Clone, Copy, Serialize)] +pub struct Wrap(pub(crate) T); impl<'de, T> Deserialize<'de> for Wrap - where - T: std::str::FromStr, - T::Err: std::fmt::Debug + std::fmt::Display, +where + T: std::str::FromStr, + T::Err: std::fmt::Debug + std::fmt::Display, { fn deserialize>(deserializer: D) -> Result { let value: &str = Deserialize::deserialize(deserializer)?; @@ -113,16 +110,13 @@ impl<'de, T> Deserialize<'de> for Wrap impl std::ops::Deref for Wrap { type Target = T; - fn deref(&self) -> &T { - &self.0 - } + fn deref(&self) -> &T { &self.0 } } -#[allow(dead_code)] #[derive(Debug, Deserialize, Serialize)] pub struct NftList { - count: i64, - nfts: Vec, + pub(crate) count: u64, + pub(crate) nfts: Vec, } #[allow(dead_code)] @@ -210,6 +204,6 @@ pub struct NftTransferHistory { #[derive(Debug, Serialize)] pub struct NftsTransferHistoryByChain { chain: Chain, - count: i64, + count: u64, transfer_history: Vec, } From f9ad87180bb0a96e73297e94d079ae65849c5152 Mon Sep 17 00:00:00 2001 From: laruh Date: Wed, 8 Feb 2023 17:32:11 +0700 Subject: [PATCH 19/66] remove unnecessary notes --- mm2src/coins/eth/nft/nft_structs.rs | 2 -- 1 file changed, 2 deletions(-) diff --git a/mm2src/coins/eth/nft/nft_structs.rs b/mm2src/coins/eth/nft/nft_structs.rs index e1f928f189..e04073c990 100644 --- a/mm2src/coins/eth/nft/nft_structs.rs +++ b/mm2src/coins/eth/nft/nft_structs.rs @@ -3,9 +3,7 @@ use mm2_number::BigDecimal; use rpc::v1::types::Bytes as BytesJson; use serde::Deserialize; use std::str::FromStr; -// use serde_json::Value as Json; -#[allow(dead_code)] #[derive(Debug, Deserialize)] pub struct NftListReq { pub(crate) chains: Vec, From 36798e917b0359783130a78ee9450f06d4a69e3b Mon Sep 17 00:00:00 2001 From: laruh Date: Thu, 9 Feb 2023 19:55:14 +0700 Subject: [PATCH 20/66] wip get_nft_transfers --- mm2src/coins/eth.rs | 91 +++++++++++++++++++++++++---- mm2src/coins/eth/nft/nft_structs.rs | 76 ++++++++++++++---------- 2 files changed, 123 insertions(+), 44 deletions(-) diff --git a/mm2src/coins/eth.rs b/mm2src/coins/eth.rs index 100cf419b4..cacfe793b0 100644 --- a/mm2src/coins/eth.rs +++ b/mm2src/coins/eth.rs @@ -48,9 +48,9 @@ use mm2_net::transport::{slurp_url, GuiAuthValidation, GuiAuthValidationGenerato use mm2_number::{BigDecimal, MmNumber}; #[cfg(test)] use mocktopus::macros::*; use nft::nft_errors::GetNftInfoError; -use nft::nft_structs::{Chain, Nft, NftList, NftListReq, NftMetadataReq, NftTransfersReq, NftWrapper, - NftsTransferHistoryByChain, TransactionNftDetails, WithdrawErc1155Request, - WithdrawErc721Request}; +use nft::nft_structs::{Chain, Nft, NftList, NftListReq, NftMetadataReq, NftTransferHistory, NftTransferHistoryWrapper, + NftTransfersReq, NftWrapper, NftsTransferHistoryList, TransactionNftDetails, + WithdrawErc1155Request, WithdrawErc721Request}; use rand::seq::SliceRandom; use rpc::v1::types::Bytes as BytesJson; use secp256k1::PublicKey; @@ -141,10 +141,12 @@ const ETH_GAS: u64 = 150_000; /// Lifetime of generated signed message for gui-auth requests const GUI_AUTH_SIGNED_MESSAGE_LIFETIME_SEC: i64 = 90; -#[allow(dead_code)] +/// url for moralis requests const URL_MORALIS: &str = "https://deep-index.moralis.io/api/v2/"; -#[allow(dead_code)] +/// query parameter for moralis request: The format of the token ID const FORMAT_DECIMAL_MORALIS: &str = "format=decimal"; +/// query parameter for moralis request: The transfer direction +const DIRECTION_BOTH_MORALIS: &str = "direction=both"; lazy_static! { pub static ref SWAP_CONTRACT: Contract = Contract::load(SWAP_CONTRACT_ABI.as_bytes()).unwrap(); @@ -819,7 +821,7 @@ pub async fn get_nft_list(ctx: MmArc, req: NftListReq) -> MmResult MmResult api_key, None => return Err(MmError::new(GetNftInfoError::ApiKeyError)), }; + // The cursor returned in the previous response (used for getting the next page). let mut cursor = "".to_owned(); loop { let uri = format!("{}{}", uri_without_cursor, cursor); @@ -853,10 +856,12 @@ pub async fn get_nft_list(ctx: MmArc, req: NftListReq) -> MmResult ("BNB", "bsc"), + }; + let my_address = get_eth_address(coin_str, &ctx).await?; + let uri_without_cursor = format!( + "{}{}/nft/transfers?chain={}&{}&{}", + URL_MORALIS, my_address.wallet_address, chain_str, FORMAT_DECIMAL_MORALIS, DIRECTION_BOTH_MORALIS + ); + + let api_key = match ctx.conf["api_key"].as_str() { + Some(api_key) => api_key, + None => return Err(MmError::new(GetNftInfoError::ApiKeyError)), + }; + // The cursor returned in the previous response (used for getting the next page). + let mut cursor = "".to_owned(); + loop { + let uri = format!("{}{}", uri_without_cursor, cursor); + let response = send_moralis_request(uri.as_str(), api_key).await?; + if let Some(transfer_list) = response["result"].as_array() { + for transfer in transfer_list { + let transfer_wrapper: NftTransferHistoryWrapper = serde_json::from_str(&transfer.to_string())?; + let transfer_history = NftTransferHistory { + chain: chain.clone(), + block_number: *transfer_wrapper.block_number, + block_timestamp: *transfer_wrapper.block_timestamp, + block_hash: transfer_wrapper.block_hash, + tx_hash: transfer_wrapper.tx_hash, + tx_index: *transfer_wrapper.tx_index, + log_index: *transfer_wrapper.log_index, + value: transfer_wrapper.value.0, + contract_type: transfer_wrapper.contract_type.0, + tx_type: transfer_wrapper.tx_type, + token_address: transfer_wrapper.token_address, + token_id: transfer_wrapper.token_id.0, + from: transfer_wrapper.from, + to: transfer_wrapper.to, + amount: transfer_wrapper.amount.0, + verified: *transfer_wrapper.verified, + operator: transfer_wrapper.operator, + }; + // collect NFTs transfers from the page + res_list.push(transfer_history); + } + // 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 !response["cursor"].is_null() { + if let Some(cursor_res) = response["cursor"].as_str() { + cursor = format!("{}{}", "&cursor=", cursor_res); + continue; + } + } else { + break; + } + } + } + } + drop_mutability!(res_list); + let transfer_history_list = NftsTransferHistoryList { + count: res_list.len() as u64, + transfer_history: res_list, + }; + Ok(transfer_history_list) } pub async fn withdraw_erc721(ctx: MmArc, req: WithdrawErc721Request) -> WithdrawNftResult { diff --git a/mm2src/coins/eth/nft/nft_structs.rs b/mm2src/coins/eth/nft/nft_structs.rs index e04073c990..cca8e65b94 100644 --- a/mm2src/coins/eth/nft/nft_structs.rs +++ b/mm2src/coins/eth/nft/nft_structs.rs @@ -67,8 +67,8 @@ pub struct Nft { pub(crate) minter_address: Option, } -/// This structure is for deserializing NFT json from Moralis to struct. -/// Its needed to convert fields properly, bcz Moralis returns json where all fields have string type. +/// This structure is for deserializing NFT json to struct. +/// Its needed to convert fields properly, all fields in json from response have string type. #[derive(Debug, Deserialize, Serialize)] pub struct NftWrapper { pub(crate) token_address: Option, @@ -164,44 +164,56 @@ pub struct TransactionNftDetails { internal_id: i64, } -#[allow(dead_code)] -#[derive(Clone, Deserialize)] +#[derive(Debug, Deserialize)] pub struct NftTransfersReq { - chains: Vec, + pub(crate) chains: Vec, } -#[allow(dead_code)] #[derive(Debug, Serialize)] -enum NftTxType { - Single, +pub struct NftTransferHistory { + pub(crate) chain: Chain, + pub(crate) block_number: u64, + pub(crate) block_timestamp: u64, + pub(crate) block_hash: String, + /// Transaction hash in hexadecimal format + pub(crate) tx_hash: String, + pub(crate) tx_index: u64, + pub(crate) log_index: u64, + pub(crate) value: BigDecimal, + pub(crate) contract_type: ContractType, + pub(crate) tx_type: String, + pub(crate) token_address: String, + pub(crate) token_id: BigDecimal, + pub(crate) from: String, + pub(crate) to: String, + pub(crate) amount: BigDecimal, + pub(crate) verified: u64, + pub(crate) operator: Option, } -#[allow(dead_code)] -#[derive(Debug, Serialize)] -pub struct NftTransferHistory { - block_number: u64, - block_timestamp: u64, - block_hash: String, +#[derive(Debug, Deserialize)] +pub struct NftTransferHistoryWrapper { + pub(crate) block_number: Wrap, + pub(crate) block_timestamp: Wrap, + pub(crate) block_hash: String, /// Transaction hash in hexadecimal format - tx_hash: String, - tx_index: u64, - log_index: u64, - value: u64, - contract_type: ContractType, - tx_type: NftTxType, - token_address: String, - token_id: u64, - from: String, - to: String, - amount: BigDecimal, - verified: u64, - operator: Option, + pub(crate) tx_hash: String, + pub(crate) tx_index: Wrap, + pub(crate) log_index: Wrap, + pub(crate) value: Wrap, + pub(crate) contract_type: Wrap, + pub(crate) tx_type: String, + pub(crate) token_address: String, + pub(crate) token_id: Wrap, + pub(crate) from: String, + pub(crate) to: String, + pub(crate) amount: Wrap, + pub(crate) verified: Wrap, + pub(crate) operator: Option, } -#[allow(dead_code)] #[derive(Debug, Serialize)] -pub struct NftsTransferHistoryByChain { - chain: Chain, - count: u64, - transfer_history: Vec, +pub struct NftsTransferHistoryList { + pub(crate) count: u64, + pub(crate) transfer_history: Vec, } From 91cf0675cddc6fdafe3b6832eb766bc27b6c342a Mon Sep 17 00:00:00 2001 From: laruh Date: Thu, 9 Feb 2023 21:05:54 +0700 Subject: [PATCH 21/66] get_nft_transfers works --- mm2src/coins/eth.rs | 16 ++++++++-------- mm2src/coins/eth/nft/nft_structs.rs | 28 ++++++++++++++-------------- 2 files changed, 22 insertions(+), 22 deletions(-) diff --git a/mm2src/coins/eth.rs b/mm2src/coins/eth.rs index cacfe793b0..02af001f1b 100644 --- a/mm2src/coins/eth.rs +++ b/mm2src/coins/eth.rs @@ -911,20 +911,20 @@ pub async fn get_nft_transfers(ctx: MmArc, req: NftTransfersReq) -> MmResult, @@ -194,21 +194,21 @@ pub struct NftTransferHistory { #[derive(Debug, Deserialize)] pub struct NftTransferHistoryWrapper { pub(crate) block_number: Wrap, - pub(crate) block_timestamp: Wrap, + pub(crate) block_timestamp: String, pub(crate) block_hash: String, /// Transaction hash in hexadecimal format - pub(crate) tx_hash: String, - pub(crate) tx_index: Wrap, - pub(crate) log_index: Wrap, + pub(crate) transaction_hash: String, + pub(crate) transaction_index: u64, + pub(crate) log_index: u64, pub(crate) value: Wrap, pub(crate) contract_type: Wrap, - pub(crate) tx_type: String, + pub(crate) transaction_type: String, pub(crate) token_address: String, pub(crate) token_id: Wrap, - pub(crate) from: String, - pub(crate) to: String, + pub(crate) from_address: String, + pub(crate) to_address: String, pub(crate) amount: Wrap, - pub(crate) verified: Wrap, + pub(crate) verified: u64, pub(crate) operator: Option, } From f5f19c7126179473d503365628af89e939fb740c Mon Sep 17 00:00:00 2001 From: laruh Date: Thu, 9 Feb 2023 21:57:52 +0700 Subject: [PATCH 22/66] polish code --- mm2src/coins/eth.rs | 3 +-- mm2src/coins/eth/nft/nft_structs.rs | 10 +++++----- mm2src/coins/eth/web3_transport/http_transport.rs | 2 +- 3 files changed, 7 insertions(+), 8 deletions(-) diff --git a/mm2src/coins/eth.rs b/mm2src/coins/eth.rs index 02af001f1b..e9c0f7a2a2 100644 --- a/mm2src/coins/eth.rs +++ b/mm2src/coins/eth.rs @@ -95,9 +95,8 @@ mod nft; mod web3_transport; #[path = "eth/v2_activation.rs"] pub mod v2_activation; -use crate::eth::v2_activation::EthActivationV2Error; use crate::{lp_coinfind_or_err, MyWalletAddress}; -use v2_activation::build_address_and_priv_key_policy; +use v2_activation::{build_address_and_priv_key_policy, EthActivationV2Error}; /// https://github.com/artemii235/etomic-swap/blob/master/contracts/EtomicSwap.sol /// Dev chain (195.201.0.6:8565) contract address: 0xa09ad3cd7e96586ebd05a2607ee56b56fb2db8fd diff --git a/mm2src/coins/eth/nft/nft_structs.rs b/mm2src/coins/eth/nft/nft_structs.rs index be4ef338a6..dccd667959 100644 --- a/mm2src/coins/eth/nft/nft_structs.rs +++ b/mm2src/coins/eth/nft/nft_structs.rs @@ -17,10 +17,10 @@ pub struct NftMetadataReq { chain: String, } -#[derive(Debug, Deserialize, Serialize, Clone)] +#[derive(Clone, Debug, Deserialize, Serialize)] pub enum Chain { - Eth, Bnb, + Eth, } #[derive(Debug, Display, PartialEq)] @@ -30,8 +30,8 @@ pub enum ParseContractTypeError { #[derive(Debug, Deserialize, Serialize)] pub enum ContractType { - Erc721, Erc1155, + Erc721, } impl FromStr for ContractType { @@ -40,8 +40,8 @@ impl FromStr for ContractType { #[inline] fn from_str(s: &str) -> Result { match s { - "ERC721" => Ok(ContractType::Erc721), "ERC1155" => Ok(ContractType::Erc1155), + "ERC721" => Ok(ContractType::Erc721), _ => Err(ParseContractTypeError::UnsupportedContractType), } } @@ -88,7 +88,7 @@ pub struct NftWrapper { pub(crate) minter_address: Option, } -#[derive(Debug, Clone, Copy, Serialize)] +#[derive(Clone, Copy, Debug, Serialize)] pub struct Wrap(pub(crate) T); impl<'de, T> Deserialize<'de> for Wrap diff --git a/mm2src/coins/eth/web3_transport/http_transport.rs b/mm2src/coins/eth/web3_transport/http_transport.rs index d9973069ae..fab245d1a4 100644 --- a/mm2src/coins/eth/web3_transport/http_transport.rs +++ b/mm2src/coins/eth/web3_transport/http_transport.rs @@ -24,7 +24,7 @@ pub struct AuthPayload<'a> { /// Parse bytes RPC response into `Result`. /// Implementation copied from Web3 HTTP transport #[cfg(not(target_arch = "wasm32"))] -pub(crate) fn single_response>(response: T, rpc_url: &str) -> Result { +fn single_response>(response: T, rpc_url: &str) -> Result { let response = serde_json::from_slice(&response) .map_err(|e| Error::from(ErrorKind::InvalidResponse(format!("{}: {}", rpc_url, e))))?; From 78603319f1a066d104bdcb18e85d16580b52edbb Mon Sep 17 00:00:00 2001 From: laruh Date: Thu, 9 Feb 2023 22:21:34 +0700 Subject: [PATCH 23/66] polish code --- mm2src/coins/eth.rs | 4 ++-- mm2src/coins/eth/nft/nft_structs.rs | 26 +++++++++++++------------- 2 files changed, 15 insertions(+), 15 deletions(-) diff --git a/mm2src/coins/eth.rs b/mm2src/coins/eth.rs index e9c0f7a2a2..cda197d404 100644 --- a/mm2src/coins/eth.rs +++ b/mm2src/coins/eth.rs @@ -950,12 +950,12 @@ pub async fn get_nft_transfers(ctx: MmArc, req: NftTransfersReq) -> MmResult WithdrawNftResult { +pub async fn withdraw_erc1155(ctx: MmArc, req: WithdrawErc1155Request) -> WithdrawNftResult { let _coin = lp_coinfind_or_err(&ctx, &req.coin).await?; todo!() } -pub async fn withdraw_erc1155(ctx: MmArc, req: WithdrawErc1155Request) -> WithdrawNftResult { +pub async fn withdraw_erc721(ctx: MmArc, req: WithdrawErc721Request) -> WithdrawNftResult { let _coin = lp_coinfind_or_err(&ctx, &req.coin).await?; todo!() } diff --git a/mm2src/coins/eth/nft/nft_structs.rs b/mm2src/coins/eth/nft/nft_structs.rs index dccd667959..1017e6604e 100644 --- a/mm2src/coins/eth/nft/nft_structs.rs +++ b/mm2src/coins/eth/nft/nft_structs.rs @@ -23,7 +23,7 @@ pub enum Chain { Eth, } -#[derive(Debug, Display, PartialEq)] +#[derive(Debug, Display)] pub enum ParseContractTypeError { UnsupportedContractType, } @@ -47,7 +47,7 @@ impl FromStr for ContractType { } } -#[derive(Debug, Deserialize, Serialize)] +#[derive(Debug, Serialize)] pub struct Nft { pub(crate) chain: Chain, pub(crate) token_address: Option, @@ -69,7 +69,7 @@ pub struct Nft { /// This structure is for deserializing NFT json to struct. /// Its needed to convert fields properly, all fields in json from response have string type. -#[derive(Debug, Deserialize, Serialize)] +#[derive(Debug, Deserialize)] pub struct NftWrapper { pub(crate) token_address: Option, pub(crate) token_id: Option>, @@ -88,7 +88,7 @@ pub struct NftWrapper { pub(crate) minter_address: Option, } -#[derive(Clone, Copy, Debug, Serialize)] +#[derive(Debug)] pub struct Wrap(pub(crate) T); impl<'de, T> Deserialize<'de> for Wrap @@ -111,7 +111,7 @@ impl std::ops::Deref for Wrap { fn deref(&self) -> &T { &self.0 } } -#[derive(Debug, Deserialize, Serialize)] +#[derive(Debug, Serialize)] pub struct NftList { pub(crate) count: u64, pub(crate) nfts: Vec, @@ -119,29 +119,29 @@ pub struct NftList { #[allow(dead_code)] #[derive(Clone, Deserialize)] -pub struct WithdrawErc721Request { - pub coin: String, +pub struct WithdrawErc1155Request { + pub(crate) coin: String, to: String, token_address: String, token_id: BigDecimal, + amount: BigDecimal, + #[serde(default)] + max: bool, fee: Option, } #[allow(dead_code)] #[derive(Clone, Deserialize)] -pub struct WithdrawErc1155Request { - pub coin: String, +pub struct WithdrawErc721Request { + pub(crate) coin: String, to: String, token_address: String, token_id: BigDecimal, - amount: BigDecimal, - #[serde(default)] - max: bool, fee: Option, } #[allow(dead_code)] -#[derive(Clone, Debug, Deserialize, Serialize)] +#[derive(Debug, Deserialize, Serialize)] pub struct TransactionNftDetails { /// Raw bytes of signed transaction, this should be sent as is to `send_raw_transaction_bytes` RPC to broadcast the transaction tx_hex: BytesJson, From 9c45de9f684a9ce7d5acaca561593d880923e0b9 Mon Sep 17 00:00:00 2001 From: laruh Date: Fri, 10 Feb 2023 15:54:21 +0700 Subject: [PATCH 24/66] remove Option from some fields in Nft struct --- mm2src/coins/eth.rs | 10 ++++----- mm2src/coins/eth/nft/nft_structs.rs | 32 ++++++++++++++--------------- 2 files changed, 21 insertions(+), 21 deletions(-) diff --git a/mm2src/coins/eth.rs b/mm2src/coins/eth.rs index cda197d404..159c7f61f4 100644 --- a/mm2src/coins/eth.rs +++ b/mm2src/coins/eth.rs @@ -840,13 +840,13 @@ pub async fn get_nft_list(ctx: MmArc, req: NftListReq) -> MmResult, - pub(crate) token_id: Option, - pub(crate) amount: Option, - pub(crate) owner_of: Option, - pub(crate) token_hash: Option, - pub(crate) block_number_minted: Option, - pub(crate) block_number: Option, - pub(crate) contract_type: Option, + pub(crate) token_address: String, + pub(crate) token_id: BigDecimal, + pub(crate) amount: BigDecimal, + pub(crate) owner_of: String, + pub(crate) token_hash: String, + pub(crate) block_number_minted: u64, + pub(crate) block_number: u64, + pub(crate) contract_type: ContractType, pub(crate) name: Option, pub(crate) symbol: Option, pub(crate) token_uri: Option, @@ -71,14 +71,14 @@ pub struct Nft { /// Its needed to convert fields properly, all fields in json from response have string type. #[derive(Debug, Deserialize)] pub struct NftWrapper { - pub(crate) token_address: Option, - pub(crate) token_id: Option>, - pub(crate) amount: Option>, - pub(crate) owner_of: Option, - pub(crate) token_hash: Option, - pub(crate) block_number_minted: Option>, - pub(crate) block_number: Option>, - pub(crate) contract_type: Option>, + pub(crate) token_address: String, + pub(crate) token_id: Wrap, + pub(crate) amount: Wrap, + pub(crate) owner_of: String, + pub(crate) token_hash: String, + pub(crate) block_number_minted: Wrap, + pub(crate) block_number: Wrap, + pub(crate) contract_type: Wrap, pub(crate) name: Option, pub(crate) symbol: Option, pub(crate) token_uri: Option, From c932d361dab2299a50b6f23405beab495ff53a4a Mon Sep 17 00:00:00 2001 From: laruh Date: Fri, 10 Feb 2023 20:37:00 +0700 Subject: [PATCH 25/66] wip get_nft_metadata --- mm2src/coins/eth.rs | 64 +++++++++++++++++++++++------ mm2src/coins/eth/nft/nft_structs.rs | 33 ++++++++++++--- 2 files changed, 78 insertions(+), 19 deletions(-) diff --git a/mm2src/coins/eth.rs b/mm2src/coins/eth.rs index 159c7f61f4..5c0027c487 100644 --- a/mm2src/coins/eth.rs +++ b/mm2src/coins/eth.rs @@ -48,9 +48,9 @@ use mm2_net::transport::{slurp_url, GuiAuthValidation, GuiAuthValidationGenerato use mm2_number::{BigDecimal, MmNumber}; #[cfg(test)] use mocktopus::macros::*; use nft::nft_errors::GetNftInfoError; -use nft::nft_structs::{Chain, Nft, NftList, NftListReq, NftMetadataReq, NftTransferHistory, NftTransferHistoryWrapper, - NftTransfersReq, NftWrapper, NftsTransferHistoryList, TransactionNftDetails, - WithdrawErc1155Request, WithdrawErc721Request}; +use nft::nft_structs::{Chain, Nft, NftList, NftListReq, NftMetadataReq, NftMetadataWrapper, NftTransferHistory, + NftTransferHistoryWrapper, NftTransfersReq, NftWrapper, NftsTransferHistoryList, + TransactionNftDetails, WithdrawErc1155Request, WithdrawErc721Request}; use rand::seq::SliceRandom; use rpc::v1::types::Bytes as BytesJson; use secp256k1::PublicKey; @@ -812,7 +812,13 @@ async fn withdraw_impl(coin: EthCoin, req: WithdrawRequest) -> WithdrawResult { } pub async fn get_nft_list(ctx: MmArc, req: NftListReq) -> MmResult { + let api_key = match ctx.conf["api_key"].as_str() { + Some(api_key) => api_key, + None => return Err(MmError::new(GetNftInfoError::ApiKeyError)), + }; + let mut res_list = Vec::new(); + for chain in req.chains { let (coin_str, chain_str) = match chain { Chain::Eth => ("ETH", "eth"), @@ -824,10 +830,6 @@ pub async fn get_nft_list(ctx: MmArc, req: NftListReq) -> MmResult api_key, - None => return Err(MmError::new(GetNftInfoError::ApiKeyError)), - }; // The cursor returned in the previous response (used for getting the next page). let mut cursor = "".to_owned(); loop { @@ -846,7 +848,7 @@ pub async fn get_nft_list(ctx: MmArc, req: NftListReq) -> MmResult MmResult MmResult { todo!() } +pub async fn get_nft_metadata(ctx: MmArc, req: NftMetadataReq) -> MmResult { + let api_key = match ctx.conf["api_key"].as_str() { + Some(api_key) => api_key, + None => return Err(MmError::new(GetNftInfoError::ApiKeyError)), + }; + let chain_str = match req.chain { + Chain::Eth => "eth", + Chain::Bnb => "bsc", + }; + let uri = format!( + "{}nft/{}/{}?chain={}&{}", + URL_MORALIS, req.token_address, req.token_id, chain_str, FORMAT_DECIMAL_MORALIS + ); + let response = send_moralis_request(uri.as_str(), api_key).await?; + let metadata_wrapper: NftMetadataWrapper = serde_json::from_str(&response.to_string())?; + let nft_metadata = Nft { + chain: req.chain, + token_address: metadata_wrapper.token_address, + token_id: metadata_wrapper.token_id.0, + amount: metadata_wrapper.amount.0, + owner_of: metadata_wrapper.owner_of, + token_hash: metadata_wrapper.token_hash, + block_number_minted: *metadata_wrapper.block_number_minted, + block_number: *metadata_wrapper.block_number, + contract_type: metadata_wrapper.contract_type.map(|v| v.0), + name: metadata_wrapper.name, + symbol: metadata_wrapper.symbol, + token_uri: metadata_wrapper.token_uri, + metadata: metadata_wrapper.metadata, + last_token_uri_sync: metadata_wrapper.last_token_uri_sync, + last_metadata_sync: metadata_wrapper.last_metadata_sync, + minter_address: metadata_wrapper.minter_address, + }; + Ok(nft_metadata) +} pub async fn get_nft_transfers(ctx: MmArc, req: NftTransfersReq) -> MmResult { + let api_key = match ctx.conf["api_key"].as_str() { + Some(api_key) => api_key, + None => return Err(MmError::new(GetNftInfoError::ApiKeyError)), + }; + let mut res_list = Vec::new(); + for chain in req.chains { let (coin_str, chain_str) = match chain { Chain::Eth => ("ETH", "eth"), @@ -895,10 +937,6 @@ pub async fn get_nft_transfers(ctx: MmArc, req: NftTransfersReq) -> MmResult api_key, - None => return Err(MmError::new(GetNftInfoError::ApiKeyError)), - }; // The cursor returned in the previous response (used for getting the next page). let mut cursor = "".to_owned(); loop { diff --git a/mm2src/coins/eth/nft/nft_structs.rs b/mm2src/coins/eth/nft/nft_structs.rs index 2ebd1274b4..5090a93c81 100644 --- a/mm2src/coins/eth/nft/nft_structs.rs +++ b/mm2src/coins/eth/nft/nft_structs.rs @@ -9,12 +9,11 @@ pub struct NftListReq { pub(crate) chains: Vec, } -#[allow(dead_code)] #[derive(Debug, Deserialize)] pub struct NftMetadataReq { - token_address: String, - token_id: BigDecimal, - chain: String, + pub(crate) token_address: String, + pub(crate) token_id: BigDecimal, + pub(crate) chain: Chain, } #[derive(Clone, Debug, Deserialize, Serialize)] @@ -57,7 +56,7 @@ pub struct Nft { pub(crate) token_hash: String, pub(crate) block_number_minted: u64, pub(crate) block_number: u64, - pub(crate) contract_type: ContractType, + pub(crate) contract_type: Option, pub(crate) name: Option, pub(crate) symbol: Option, pub(crate) token_uri: Option, @@ -78,7 +77,29 @@ pub struct NftWrapper { pub(crate) token_hash: String, pub(crate) block_number_minted: Wrap, pub(crate) block_number: Wrap, - pub(crate) contract_type: Wrap, + pub(crate) contract_type: Option>, + pub(crate) name: Option, + pub(crate) symbol: Option, + pub(crate) token_uri: Option, + pub(crate) metadata: Option, + pub(crate) last_token_uri_sync: Option, + pub(crate) last_metadata_sync: Option, + pub(crate) minter_address: Option, +} + +#[allow(dead_code)] +#[derive(Debug, Deserialize)] +pub struct NftMetadataWrapper { + pub(crate) token_address: String, + pub(crate) token_id: Wrap, + // currently is not used, but is needed to deserialize json + pub(crate) transfer_index: Vec, + pub(crate) amount: Wrap, + pub(crate) owner_of: String, + pub(crate) token_hash: String, + pub(crate) block_number_minted: Wrap, + pub(crate) block_number: Wrap, + pub(crate) contract_type: Option>, pub(crate) name: Option, pub(crate) symbol: Option, pub(crate) token_uri: Option, From 3411af265e3cec25bf8b18bc5653f9345468f321 Mon Sep 17 00:00:00 2001 From: laruh Date: Sun, 12 Feb 2023 22:44:39 +0700 Subject: [PATCH 26/66] use NftWrapper in fn get_nft_metadata, add some doc comments --- mm2src/coins/eth.rs | 45 +++++++++++++++++------------ mm2src/coins/eth/nft/nft_structs.rs | 24 +-------------- 2 files changed, 27 insertions(+), 42 deletions(-) diff --git a/mm2src/coins/eth.rs b/mm2src/coins/eth.rs index 5c0027c487..9d810ae621 100644 --- a/mm2src/coins/eth.rs +++ b/mm2src/coins/eth.rs @@ -48,9 +48,9 @@ use mm2_net::transport::{slurp_url, GuiAuthValidation, GuiAuthValidationGenerato use mm2_number::{BigDecimal, MmNumber}; #[cfg(test)] use mocktopus::macros::*; use nft::nft_errors::GetNftInfoError; -use nft::nft_structs::{Chain, Nft, NftList, NftListReq, NftMetadataReq, NftMetadataWrapper, NftTransferHistory, - NftTransferHistoryWrapper, NftTransfersReq, NftWrapper, NftsTransferHistoryList, - TransactionNftDetails, WithdrawErc1155Request, WithdrawErc721Request}; +use nft::nft_structs::{Chain, Nft, NftList, NftListReq, NftMetadataReq, NftTransferHistory, NftTransferHistoryWrapper, + NftTransfersReq, NftWrapper, NftsTransferHistoryList, TransactionNftDetails, + WithdrawErc1155Request, WithdrawErc721Request}; use rand::seq::SliceRandom; use rpc::v1::types::Bytes as BytesJson; use secp256k1::PublicKey; @@ -811,6 +811,7 @@ async fn withdraw_impl(coin: EthCoin, req: WithdrawRequest) -> WithdrawResult { }) } +/// `get_nft_list` function returns list of NFTs on ETH or/and BNB chains owned by user. pub async fn get_nft_list(ctx: MmArc, req: NftListReq) -> MmResult { let api_key = match ctx.conf["api_key"].as_str() { Some(api_key) => api_key, @@ -882,6 +883,10 @@ pub async fn get_nft_list(ctx: MmArc, req: NftListReq) -> MmResult MmResult { let api_key = match ctx.conf["api_key"].as_str() { Some(api_key) => api_key, @@ -896,28 +901,30 @@ pub async fn get_nft_metadata(ctx: MmArc, req: NftMetadataReq) -> MmResult MmResult { let api_key = match ctx.conf["api_key"].as_str() { Some(api_key) => api_key, diff --git a/mm2src/coins/eth/nft/nft_structs.rs b/mm2src/coins/eth/nft/nft_structs.rs index 5090a93c81..4198490863 100644 --- a/mm2src/coins/eth/nft/nft_structs.rs +++ b/mm2src/coins/eth/nft/nft_structs.rs @@ -67,7 +67,7 @@ pub struct Nft { } /// This structure is for deserializing NFT json to struct. -/// Its needed to convert fields properly, all fields in json from response have string type. +/// Its needed to convert fields properly, because all fields in json have string type. #[derive(Debug, Deserialize)] pub struct NftWrapper { pub(crate) token_address: String, @@ -87,28 +87,6 @@ pub struct NftWrapper { pub(crate) minter_address: Option, } -#[allow(dead_code)] -#[derive(Debug, Deserialize)] -pub struct NftMetadataWrapper { - pub(crate) token_address: String, - pub(crate) token_id: Wrap, - // currently is not used, but is needed to deserialize json - pub(crate) transfer_index: Vec, - pub(crate) amount: Wrap, - pub(crate) owner_of: String, - pub(crate) token_hash: String, - pub(crate) block_number_minted: Wrap, - pub(crate) block_number: Wrap, - pub(crate) contract_type: Option>, - pub(crate) name: Option, - pub(crate) symbol: Option, - pub(crate) token_uri: Option, - pub(crate) metadata: Option, - pub(crate) last_token_uri_sync: Option, - pub(crate) last_metadata_sync: Option, - pub(crate) minter_address: Option, -} - #[derive(Debug)] pub struct Wrap(pub(crate) T); From 8185875db063fe486397574042dbb3f10ba26652 Mon Sep 17 00:00:00 2001 From: laruh Date: Sun, 12 Feb 2023 22:59:37 +0700 Subject: [PATCH 27/66] remove allow(dead_code) --- mm2src/coins/eth.rs | 1 - mm2src/coins/lp_coins.rs | 5 +---- 2 files changed, 1 insertion(+), 5 deletions(-) diff --git a/mm2src/coins/eth.rs b/mm2src/coins/eth.rs index 9d810ae621..810b432cfd 100644 --- a/mm2src/coins/eth.rs +++ b/mm2src/coins/eth.rs @@ -4769,7 +4769,6 @@ pub async fn get_eth_address(ticker: &str, ctx: &MmArc) -> MmResult MmResult { use http::header::HeaderValue; diff --git a/mm2src/coins/lp_coins.rs b/mm2src/coins/lp_coins.rs index 4f32535965..7981045dda 100644 --- a/mm2src/coins/lp_coins.rs +++ b/mm2src/coins/lp_coins.rs @@ -199,7 +199,7 @@ use coin_errors::{MyAddressError, ValidatePaymentError, ValidatePaymentFut}; pub mod coins_tests; pub mod eth; -use eth::{eth_coin_from_conf_and_request, EthCoin, EthTxFeeDetails, SignedEthTx}; +use eth::{eth_coin_from_conf_and_request, get_eth_address, EthCoin, EthTxFeeDetails, GetEthAddressError, SignedEthTx}; pub mod hd_pubkey; @@ -253,7 +253,6 @@ use utxo::UtxoActivationParams; use utxo::{BlockchainNetwork, GenerateTxError, UtxoFeeDetails, UtxoTx}; #[cfg(not(target_arch = "wasm32"))] pub mod z_coin; -use crate::eth::{get_eth_address, GetEthAddressError}; #[cfg(not(target_arch = "wasm32"))] use z_coin::ZCoin; pub type TransactionFut = Box + Send>; @@ -376,13 +375,11 @@ pub struct RawTransactionRes { pub tx_hex: BytesJson, } -#[allow(dead_code)] #[derive(Debug, Deserialize)] pub struct MyAddressReq { coin: String, } -#[allow(dead_code)] #[derive(Debug, Serialize)] pub struct MyWalletAddress { coin: String, From 831dd5b2e29b3efac405ed5239f61452a37142a0 Mon Sep 17 00:00:00 2001 From: laruh Date: Mon, 13 Feb 2023 01:19:28 +0700 Subject: [PATCH 28/66] change order in Chain enum --- mm2src/coins/eth.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/mm2src/coins/eth.rs b/mm2src/coins/eth.rs index 810b432cfd..3596392ff3 100644 --- a/mm2src/coins/eth.rs +++ b/mm2src/coins/eth.rs @@ -822,8 +822,8 @@ pub async fn get_nft_list(ctx: MmArc, req: NftListReq) -> MmResult ("ETH", "eth"), Chain::Bnb => ("BNB", "bsc"), + Chain::Eth => ("ETH", "eth"), }; let my_address = get_eth_address(coin_str, &ctx).await?; let uri_without_cursor = format!( @@ -893,8 +893,8 @@ pub async fn get_nft_metadata(ctx: MmArc, req: NftMetadataReq) -> MmResult return Err(MmError::new(GetNftInfoError::ApiKeyError)), }; let chain_str = match req.chain { - Chain::Eth => "eth", Chain::Bnb => "bsc", + Chain::Eth => "eth", }; let uri = format!( "{}nft/{}/{}?chain={}&{}", @@ -935,8 +935,8 @@ pub async fn get_nft_transfers(ctx: MmArc, req: NftTransfersReq) -> MmResult ("ETH", "eth"), Chain::Bnb => ("BNB", "bsc"), + Chain::Eth => ("ETH", "eth"), }; let my_address = get_eth_address(coin_str, &ctx).await?; let uri_without_cursor = format!( From 9f4e6c003ae2ed1d309314d0becffe8963b80d6f Mon Sep 17 00:00:00 2001 From: laruh Date: Mon, 13 Feb 2023 13:26:14 +0700 Subject: [PATCH 29/66] fix doc comment --- mm2src/coins/eth.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mm2src/coins/eth.rs b/mm2src/coins/eth.rs index 3596392ff3..4da764ff21 100644 --- a/mm2src/coins/eth.rs +++ b/mm2src/coins/eth.rs @@ -883,7 +883,7 @@ pub async fn get_nft_list(ctx: MmArc, req: NftListReq) -> MmResult Date: Tue, 14 Feb 2023 16:37:17 +0700 Subject: [PATCH 30/66] beautify json --- mm2src/coins/eth/erc721_abi.json | 316 ++++++++++++++++++++++++------- 1 file changed, 252 insertions(+), 64 deletions(-) diff --git a/mm2src/coins/eth/erc721_abi.json b/mm2src/coins/eth/erc721_abi.json index a309442a43..20e0fca0b4 100644 --- a/mm2src/coins/eth/erc721_abi.json +++ b/mm2src/coins/eth/erc721_abi.json @@ -1,43 +1,96 @@ [ { "anonymous": false, - "inputs": [{"indexed": true, "internalType": "address", "name": "owner", "type": "address"}, { - "indexed": true, - "internalType": "address", - "name": "approved", - "type": "address" - }, {"indexed": true, "internalType": "uint256", "name": "tokenId", "type": "uint256"}], + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "owner", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "approved", + "type": "address" + }, + { + "indexed": true, + "internalType": "uint256", + "name": "tokenId", + "type": "uint256" + } + ], "name": "Approval", "type": "event" }, { "anonymous": false, - "inputs": [{"indexed": true, "internalType": "address", "name": "owner", "type": "address"}, { - "indexed": true, - "internalType": "address", - "name": "operator", - "type": "address" - }, {"indexed": false, "internalType": "bool", "name": "approved", "type": "bool"}], + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "owner", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "operator", + "type": "address" + }, + { + "indexed": false, + "internalType": "bool", + "name": "approved", + "type": "bool" + } + ], "name": "ApprovalForAll", "type": "event" }, { "anonymous": false, - "inputs": [{"indexed": true, "internalType": "address", "name": "from", "type": "address"}, { - "indexed": true, - "internalType": "address", - "name": "to", - "type": "address" - }, {"indexed": true, "internalType": "uint256", "name": "tokenId", "type": "uint256"}], + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "from", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "to", + "type": "address" + }, + { + "indexed": true, + "internalType": "uint256", + "name": "tokenId", + "type": "uint256" + } + ], "name": "Transfer", "type": "event" }, { - "inputs": [{"internalType": "address", "name": "to", "type": "address"}, { - "internalType": "uint256", - "name": "tokenId", - "type": "uint256" - }], "name": "approve", "outputs": [], "stateMutability": "nonpayable", "type": "function" + "inputs": [ + { + "internalType": "address", + "name": "to", + "type": "address" + }, + { + "internalType": "uint256", + "name": "tokenId", + "type": "uint256" + } + ], + "name": "approve", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" }, { "constant": true, @@ -54,102 +107,237 @@ "type": "function" }, { - "inputs": [{"internalType": "address", "name": "owner", "type": "address"}], + "inputs": [ + { + "internalType": "address", + "name": "owner", + "type": "address" + } + ], "name": "balanceOf", - "outputs": [{"internalType": "uint256", "name": "balance", "type": "uint256"}], + "outputs": [ + { + "internalType": "uint256", + "name": "balance", + "type": "uint256" + } + ], "stateMutability": "view", "type": "function" }, { - "inputs": [{"internalType": "uint256", "name": "tokenId", "type": "uint256"}], + "inputs": [ + { + "internalType": "uint256", + "name": "tokenId", + "type": "uint256" + } + ], "name": "getApproved", - "outputs": [{"internalType": "address", "name": "operator", "type": "address"}], + "outputs": [ + { + "internalType": "address", + "name": "operator", + "type": "address" + } + ], "stateMutability": "view", "type": "function" }, { - "inputs": [{"internalType": "address", "name": "owner", "type": "address"}, { - "internalType": "address", - "name": "operator", - "type": "address" - }], + "inputs": [ + { + "internalType": "address", + "name": "owner", + "type": "address" + }, + { + "internalType": "address", + "name": "operator", + "type": "address" + } + ], "name": "isApprovedForAll", - "outputs": [{"internalType": "bool", "name": "", "type": "bool"}], + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], "stateMutability": "view", "type": "function" }, { "inputs": [], "name": "name", - "outputs": [{"internalType": "string", "name": "", "type": "string"}], + "outputs": [ + { + "internalType": "string", + "name": "", + "type": "string" + } + ], "stateMutability": "view", "type": "function" }, { - "inputs": [{"internalType": "uint256", "name": "tokenId", "type": "uint256"}], + "inputs": [ + { + "internalType": "uint256", + "name": "tokenId", + "type": "uint256" + } + ], "name": "ownerOf", - "outputs": [{"internalType": "address", "name": "owner", "type": "address"}], + "outputs": [ + { + "internalType": "address", + "name": "owner", + "type": "address" + } + ], "stateMutability": "view", "type": "function" }, { - "inputs": [{"internalType": "address", "name": "from", "type": "address"}, { - "internalType": "address", - "name": "to", - "type": "address" - }, - {"internalType": "uint256", "name": "tokenId", "type": "uint256"}], + "inputs": [ + { + "internalType": "address", + "name": "from", + "type": "address" + }, + { + "internalType": "address", + "name": "to", + "type": "address" + }, + { + "internalType": "uint256", + "name": "tokenId", + "type": "uint256" + } + ], "name": "safeTransferFrom", "outputs": [], "stateMutability": "nonpayable", "type": "function" }, { - "inputs": [{"internalType": "address", "name": "from", "type": "address"}, { - "internalType": "address", - "name": "to", - "type": "address" - }, - {"internalType": "uint256", "name": "tokenId", "type": "uint256"}, { + "inputs": [ + { + "internalType": "address", + "name": "from", + "type": "address" + }, + { + "internalType": "address", + "name": "to", + "type": "address" + }, + { + "internalType": "uint256", + "name": "tokenId", + "type": "uint256" + }, + { "internalType": "bytes", "name": "data", "type": "bytes" - }], "name": "safeTransferFrom", "outputs": [], "stateMutability": "nonpayable", "type": "function" + } + ], + "name": "safeTransferFrom", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" }, { - "inputs": [{"internalType": "address", "name": "operator", "type": "address"}, { - "internalType": "bool", - "name": "_approved", - "type": "bool" - }], "name": "setApprovalForAll", "outputs": [], "stateMutability": "nonpayable", "type": "function" + "inputs": [ + { + "internalType": "address", + "name": "operator", + "type": "address" + }, + { + "internalType": "bool", + "name": "_approved", + "type": "bool" + } + ], + "name": "setApprovalForAll", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" }, { - "inputs": [{"internalType": "bytes4", "name": "interfaceId", "type": "bytes4"}], + "inputs": [ + { + "internalType": "bytes4", + "name": "interfaceId", + "type": "bytes4" + } + ], "name": "supportsInterface", - "outputs": [{"internalType": "bool", "name": "", "type": "bool"}], + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], "stateMutability": "view", "type": "function" }, { "inputs": [], "name": "symbol", - "outputs": [{"internalType": "string", "name": "", "type": "string"}], + "outputs": [ + { + "internalType": "string", + "name": "", + "type": "string" + } + ], "stateMutability": "view", "type": "function" }, { - "inputs": [{"internalType": "uint256", "name": "tokenId", "type": "uint256"}], + "inputs": [ + { + "internalType": "uint256", + "name": "tokenId", + "type": "uint256" + } + ], "name": "tokenURI", - "outputs": [{"internalType": "string", "name": "", "type": "string"}], + "outputs": [ + { + "internalType": "string", + "name": "", + "type": "string" + } + ], "stateMutability": "view", "type": "function" }, { - "inputs": [{"internalType": "address", "name": "from", "type": "address"}, { - "internalType": "address", - "name": "to", - "type": "address" - }, {"internalType": "uint256", "name": "tokenId", "type": "uint256"}], + "inputs": [ + { + "internalType": "address", + "name": "from", + "type": "address" + }, + { + "internalType": "address", + "name": "to", + "type": "address" + }, + { + "internalType": "uint256", + "name": "tokenId", + "type": "uint256" + } + ], "name": "transferFrom", "outputs": [], "stateMutability": "nonpayable", From cfb8f3798591387ef833b698cc17814182a65f6e Mon Sep 17 00:00:00 2001 From: laruh Date: Tue, 14 Feb 2023 18:43:51 +0700 Subject: [PATCH 31/66] add from in withdraw requests --- mm2src/coins/eth/nft/nft_structs.rs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/mm2src/coins/eth/nft/nft_structs.rs b/mm2src/coins/eth/nft/nft_structs.rs index 4198490863..7feade5492 100644 --- a/mm2src/coins/eth/nft/nft_structs.rs +++ b/mm2src/coins/eth/nft/nft_structs.rs @@ -120,6 +120,7 @@ pub struct NftList { #[derive(Clone, Deserialize)] pub struct WithdrawErc1155Request { pub(crate) coin: String, + from: String, to: String, token_address: String, token_id: BigDecimal, @@ -133,6 +134,7 @@ pub struct WithdrawErc1155Request { #[derive(Clone, Deserialize)] pub struct WithdrawErc721Request { pub(crate) coin: String, + from: String, to: String, token_address: String, token_id: BigDecimal, From 9e51761d43e60faf3feaebb81b3adb5391a5b18e Mon Sep 17 00:00:00 2001 From: laruh Date: Wed, 15 Feb 2023 12:40:00 +0700 Subject: [PATCH 32/66] String::new(), serde UPPERCASE, pub(crate) SerdeStringWrap,line break --- mm2src/coins/eth.rs | 5 +++-- mm2src/coins/eth/nft/nft_structs.rs | 30 +++++++++++++++-------------- 2 files changed, 19 insertions(+), 16 deletions(-) diff --git a/mm2src/coins/eth.rs b/mm2src/coins/eth.rs index 4da764ff21..b0e99763ca 100644 --- a/mm2src/coins/eth.rs +++ b/mm2src/coins/eth.rs @@ -832,7 +832,7 @@ pub async fn get_nft_list(ctx: MmArc, req: NftListReq) -> MmResult MmResult for GetEthAddressError { fn from(e: PrivKeyPolicyNotAllowed) -> Self { GetEthAddressError::PrivKeyPolicyNotAllowed(e) } } + impl From for GetEthAddressError { fn from(e: EthActivationV2Error) -> Self { GetEthAddressError::EthActivationV2Error(e) } } diff --git a/mm2src/coins/eth/nft/nft_structs.rs b/mm2src/coins/eth/nft/nft_structs.rs index 7feade5492..4547f1ac46 100644 --- a/mm2src/coins/eth/nft/nft_structs.rs +++ b/mm2src/coins/eth/nft/nft_structs.rs @@ -17,6 +17,7 @@ pub struct NftMetadataReq { } #[derive(Clone, Debug, Deserialize, Serialize)] +#[serde(rename_all = "UPPERCASE")] pub enum Chain { Bnb, Eth, @@ -28,6 +29,7 @@ pub enum ParseContractTypeError { } #[derive(Debug, Deserialize, Serialize)] +#[serde(rename_all = "UPPERCASE")] pub enum ContractType { Erc1155, Erc721, @@ -71,13 +73,13 @@ pub struct Nft { #[derive(Debug, Deserialize)] pub struct NftWrapper { pub(crate) token_address: String, - pub(crate) token_id: Wrap, - pub(crate) amount: Wrap, + pub(crate) token_id: SerdeStringWrap, + pub(crate) amount: SerdeStringWrap, pub(crate) owner_of: String, pub(crate) token_hash: String, - pub(crate) block_number_minted: Wrap, - pub(crate) block_number: Wrap, - pub(crate) contract_type: Option>, + pub(crate) block_number_minted: SerdeStringWrap, + pub(crate) block_number: SerdeStringWrap, + pub(crate) contract_type: Option>, pub(crate) name: Option, pub(crate) symbol: Option, pub(crate) token_uri: Option, @@ -88,9 +90,9 @@ pub struct NftWrapper { } #[derive(Debug)] -pub struct Wrap(pub(crate) T); +pub(crate) struct SerdeStringWrap(pub(crate) T); -impl<'de, T> Deserialize<'de> for Wrap +impl<'de, T> Deserialize<'de> for SerdeStringWrap where T: std::str::FromStr, T::Err: std::fmt::Debug + std::fmt::Display, @@ -101,11 +103,11 @@ where Ok(v) => v, Err(e) => return Err(::custom(e)), }; - Ok(Wrap(value)) + Ok(SerdeStringWrap(value)) } } -impl std::ops::Deref for Wrap { +impl std::ops::Deref for SerdeStringWrap { type Target = T; fn deref(&self) -> &T { &self.0 } } @@ -194,21 +196,21 @@ pub struct NftTransferHistory { #[derive(Debug, Deserialize)] pub struct NftTransferHistoryWrapper { - pub(crate) block_number: Wrap, + pub(crate) block_number: SerdeStringWrap, pub(crate) block_timestamp: String, pub(crate) block_hash: String, /// Transaction hash in hexadecimal format pub(crate) transaction_hash: String, pub(crate) transaction_index: u64, pub(crate) log_index: u64, - pub(crate) value: Wrap, - pub(crate) contract_type: Wrap, + pub(crate) value: SerdeStringWrap, + pub(crate) contract_type: SerdeStringWrap, pub(crate) transaction_type: String, pub(crate) token_address: String, - pub(crate) token_id: Wrap, + pub(crate) token_id: SerdeStringWrap, pub(crate) from_address: String, pub(crate) to_address: String, - pub(crate) amount: Wrap, + pub(crate) amount: SerdeStringWrap, pub(crate) verified: u64, pub(crate) operator: Option, } From 487d10f5df65a5dad7fcb28f11ef303c2713fd30 Mon Sep 17 00:00:00 2001 From: laruh Date: Wed, 15 Feb 2023 14:30:34 +0700 Subject: [PATCH 33/66] use ok_or_else, remove if cursor is null, remove !nfts_list.is_empty() --- mm2src/coins/eth.rs | 83 ++++++++++++++++++++------------------------- 1 file changed, 37 insertions(+), 46 deletions(-) diff --git a/mm2src/coins/eth.rs b/mm2src/coins/eth.rs index b0e99763ca..3c73d711fa 100644 --- a/mm2src/coins/eth.rs +++ b/mm2src/coins/eth.rs @@ -813,10 +813,9 @@ async fn withdraw_impl(coin: EthCoin, req: WithdrawRequest) -> WithdrawResult { /// `get_nft_list` function returns list of NFTs on ETH or/and BNB chains owned by user. pub async fn get_nft_list(ctx: MmArc, req: NftListReq) -> MmResult { - let api_key = match ctx.conf["api_key"].as_str() { - Some(api_key) => api_key, - None => return Err(MmError::new(GetNftInfoError::ApiKeyError)), - }; + let api_key = ctx.conf["api_key"] + .as_str() + .ok_or_else(|| MmError::new(GetNftInfoError::ApiKeyError))?; let mut res_list = Vec::new(); @@ -837,38 +836,34 @@ pub async fn get_nft_list(ctx: MmArc, req: NftListReq) -> MmResult api_key, - None => return Err(MmError::new(GetNftInfoError::ApiKeyError)), - }; + let api_key = ctx.conf["api_key"] + .as_str() + .ok_or_else(|| MmError::new(GetNftInfoError::ApiKeyError))?; let chain_str = match req.chain { Chain::Bnb => "bsc", Chain::Eth => "eth", @@ -926,10 +920,9 @@ pub async fn get_nft_metadata(ctx: MmArc, req: NftMetadataReq) -> MmResult MmResult { - let api_key = match ctx.conf["api_key"].as_str() { - Some(api_key) => api_key, - None => return Err(MmError::new(GetNftInfoError::ApiKeyError)), - }; + let api_key = ctx.conf["api_key"] + .as_str() + .ok_or_else(|| MmError::new(GetNftInfoError::ApiKeyError))?; let mut res_list = Vec::new(); @@ -976,11 +969,9 @@ pub async fn get_nft_transfers(ctx: MmArc, req: NftTransfersReq) -> MmResult MmResult get_eth_address(&req.coin, &ctx).await?, - CoinProtocol::UTXO => { - return MmError::err(GetMyAddressError::CoinIsNotSupported( - "UTXO protocol is not supported by get_my_address".to_owned(), - )) - }, - CoinProtocol::QTUM => { - return MmError::err(GetMyAddressError::CoinIsNotSupported( - "QTUM protocol is not supported by get_my_address".to_owned(), - )) - }, - CoinProtocol::QRC20 { .. } => { - return MmError::err(GetMyAddressError::CoinIsNotSupported( - "QRC20 protocol is not supported by get_my_address".to_owned(), - )) - }, - CoinProtocol::ERC20 { .. } => { - return MmError::err(GetMyAddressError::CoinIsNotSupported( - "ERC20 protocol is not supported by get_my_address".to_owned(), - )) - }, - CoinProtocol::SLPTOKEN { .. } => { - return MmError::err(GetMyAddressError::CoinIsNotSupported( - "SlpToken protocol is not supported by get_my_address".to_owned(), - )) - }, - CoinProtocol::BCH { .. } => { - return MmError::err(GetMyAddressError::CoinIsNotSupported( - "BCH protocol is not supported by get_my_address".to_owned(), - )) - }, - CoinProtocol::TENDERMINT(_) => { - return MmError::err(GetMyAddressError::CoinIsNotSupported( - "TENDERMINT protocol is not supported by get_my_address".to_owned(), - )) - }, - CoinProtocol::TENDERMINTTOKEN(_) => { - return MmError::err(GetMyAddressError::CoinIsNotSupported( - "TENDERMINTTOKEN protocol is not supported by get_my_address".to_owned(), - )) - }, - #[cfg(not(target_arch = "wasm32"))] - CoinProtocol::LIGHTNING { .. } => { - return MmError::err(GetMyAddressError::CoinIsNotSupported( - "LIGHTNING protocol is not supported by get_my_address".to_owned(), - )) - }, - #[cfg(not(target_arch = "wasm32"))] - CoinProtocol::SOLANA => { - return MmError::err(GetMyAddressError::CoinIsNotSupported( - "SOLANA protocol is not supported by get_my_address".to_owned(), - )) - }, - #[cfg(not(target_arch = "wasm32"))] - CoinProtocol::SPLTOKEN { .. } => { - return MmError::err(GetMyAddressError::CoinIsNotSupported( - "SplToken protocol is not supported by get_my_address".to_owned(), - )) - }, - #[cfg(not(target_arch = "wasm32"))] - CoinProtocol::ZHTLC(_) => { - return MmError::err(GetMyAddressError::CoinIsNotSupported( - "ZHTLC protocol is not supported by get_my_address".to_owned(), - )) + _ => { + return MmError::err(GetMyAddressError::CoinIsNotSupported(format!( + "{} doesn't support get_my_address", + req.coin + ))); }, }; From 1b3a6fad4cf43ffcf9db59c887f6c2543e535fe4 Mon Sep 17 00:00:00 2001 From: laruh Date: Fri, 17 Feb 2023 13:40:49 +0700 Subject: [PATCH 35/66] replace Chain::Bnb with Chain::Bsc --- mm2src/coins/eth.rs | 18 +++++++++++++----- mm2src/coins/eth/nft/nft_structs.rs | 12 ++++++++---- 2 files changed, 21 insertions(+), 9 deletions(-) diff --git a/mm2src/coins/eth.rs b/mm2src/coins/eth.rs index 3c73d711fa..58b1181b80 100644 --- a/mm2src/coins/eth.rs +++ b/mm2src/coins/eth.rs @@ -821,7 +821,7 @@ pub async fn get_nft_list(ctx: MmArc, req: NftListReq) -> MmResult ("BNB", "bsc"), + Chain::Bsc => ("BNB", "bsc"), Chain::Eth => ("ETH", "eth"), }; let my_address = get_eth_address(coin_str, &ctx).await?; @@ -887,7 +887,7 @@ pub async fn get_nft_metadata(ctx: MmArc, req: NftMetadataReq) -> MmResult "bsc", + Chain::Bsc => "bsc", Chain::Eth => "eth", }; let uri = format!( @@ -928,7 +928,7 @@ pub async fn get_nft_transfers(ctx: MmArc, req: NftTransfersReq) -> MmResult ("BNB", "bsc"), + Chain::Bsc => ("BNB", "bsc"), Chain::Eth => ("ETH", "eth"), }; let my_address = get_eth_address(coin_str, &ctx).await?; @@ -987,12 +987,20 @@ pub async fn get_nft_transfers(ctx: MmArc, req: NftTransfersReq) -> MmResult WithdrawNftResult { - let _coin = lp_coinfind_or_err(&ctx, &req.coin).await?; + let ticker = match req.chain { + Chain::Bsc => "BNB", + Chain::Eth => "ETH", + }; + let _coin = lp_coinfind_or_err(&ctx, ticker).await?; todo!() } pub async fn withdraw_erc721(ctx: MmArc, req: WithdrawErc721Request) -> WithdrawNftResult { - let _coin = lp_coinfind_or_err(&ctx, &req.coin).await?; + let ticker = match req.chain { + Chain::Bsc => "BNB", + Chain::Eth => "ETH", + }; + let _coin = lp_coinfind_or_err(&ctx, ticker).await?; todo!() } diff --git a/mm2src/coins/eth/nft/nft_structs.rs b/mm2src/coins/eth/nft/nft_structs.rs index 4547f1ac46..2d9f8e6272 100644 --- a/mm2src/coins/eth/nft/nft_structs.rs +++ b/mm2src/coins/eth/nft/nft_structs.rs @@ -1,4 +1,4 @@ -use crate::{TxFeeDetails, WithdrawFee}; +use crate::{TransactionType, TxFeeDetails, WithdrawFee}; use mm2_number::BigDecimal; use rpc::v1::types::Bytes as BytesJson; use serde::Deserialize; @@ -19,7 +19,7 @@ pub struct NftMetadataReq { #[derive(Clone, Debug, Deserialize, Serialize)] #[serde(rename_all = "UPPERCASE")] pub enum Chain { - Bnb, + Bsc, Eth, } @@ -121,7 +121,7 @@ pub struct NftList { #[allow(dead_code)] #[derive(Clone, Deserialize)] pub struct WithdrawErc1155Request { - pub(crate) coin: String, + pub(crate) chain: Chain, from: String, to: String, token_address: String, @@ -135,7 +135,7 @@ pub struct WithdrawErc1155Request { #[allow(dead_code)] #[derive(Clone, Deserialize)] pub struct WithdrawErc721Request { - pub(crate) coin: String, + pub(crate) chain: Chain, from: String, to: String, token_address: String, @@ -165,6 +165,10 @@ pub struct TransactionNftDetails { timestamp: u64, /// Internal MM2 id used for internal transaction identification, for some coins it might be equal to transaction hash internal_id: i64, + /// Type of transactions, default is StandardTransfer + #[serde(default)] + pub(crate) transaction_type: TransactionType, + pub(crate) memo: Option, } #[derive(Debug, Deserialize)] From b92ec80837b496f6fa091565602e01826c8f8c6e Mon Sep 17 00:00:00 2001 From: laruh Date: Fri, 17 Feb 2023 14:43:59 +0700 Subject: [PATCH 36/66] change status code matching, add derive Copy --- mm2src/coins/eth.rs | 4 ++-- mm2src/coins/eth/nft/nft_errors.rs | 10 +++++----- mm2src/coins/eth/nft/nft_structs.rs | 2 +- 3 files changed, 8 insertions(+), 8 deletions(-) diff --git a/mm2src/coins/eth.rs b/mm2src/coins/eth.rs index 58b1181b80..5476af76a9 100644 --- a/mm2src/coins/eth.rs +++ b/mm2src/coins/eth.rs @@ -839,7 +839,7 @@ pub async fn get_nft_list(ctx: MmArc, req: NftListReq) -> MmResult MmResult StatusCode { match self { GetNftInfoError::InvalidRequest(_) => StatusCode::BAD_REQUEST, - GetNftInfoError::Transport(_) - | GetNftInfoError::InvalidResponse(_) - | GetNftInfoError::Internal(_) - | GetNftInfoError::GetEthAddressError(_) - | GetNftInfoError::ApiKeyError => StatusCode::INTERNAL_SERVER_ERROR, + GetNftInfoError::InvalidResponse(_) => StatusCode::FAILED_DEPENDENCY, + GetNftInfoError::ApiKeyError => StatusCode::FORBIDDEN, + GetNftInfoError::Transport(_) | GetNftInfoError::Internal(_) | GetNftInfoError::GetEthAddressError(_) => { + StatusCode::INTERNAL_SERVER_ERROR + }, } } } diff --git a/mm2src/coins/eth/nft/nft_structs.rs b/mm2src/coins/eth/nft/nft_structs.rs index 2d9f8e6272..d5b46b83ab 100644 --- a/mm2src/coins/eth/nft/nft_structs.rs +++ b/mm2src/coins/eth/nft/nft_structs.rs @@ -16,7 +16,7 @@ pub struct NftMetadataReq { pub(crate) chain: Chain, } -#[derive(Clone, Debug, Deserialize, Serialize)] +#[derive(Clone, Copy, Debug, Deserialize, Serialize)] #[serde(rename_all = "UPPERCASE")] pub enum Chain { Bsc, From a8403e4bc34d3a298107cc1b706a9f0c2c7f86f6 Mon Sep 17 00:00:00 2001 From: laruh Date: Mon, 20 Feb 2023 12:55:10 +0700 Subject: [PATCH 37/66] fn withdraw_erc721 --- mm2src/coins/eth.rs | 113 ++++++++++++++++++++++++++-- mm2src/coins/eth/nft/nft_structs.rs | 39 +++++----- mm2src/coins/lp_coins.rs | 15 +++- mm2src/coins/my_tx_history_v2.rs | 3 +- 4 files changed, 139 insertions(+), 31 deletions(-) diff --git a/mm2src/coins/eth.rs b/mm2src/coins/eth.rs index 5476af76a9..53db3c88a3 100644 --- a/mm2src/coins/eth.rs +++ b/mm2src/coins/eth.rs @@ -48,9 +48,9 @@ use mm2_net::transport::{slurp_url, GuiAuthValidation, GuiAuthValidationGenerato use mm2_number::{BigDecimal, MmNumber}; #[cfg(test)] use mocktopus::macros::*; use nft::nft_errors::GetNftInfoError; -use nft::nft_structs::{Chain, Nft, NftList, NftListReq, NftMetadataReq, NftTransferHistory, NftTransferHistoryWrapper, - NftTransfersReq, NftWrapper, NftsTransferHistoryList, TransactionNftDetails, - WithdrawErc1155Request, WithdrawErc721Request}; +use nft::nft_structs::{Chain, ContractType, Nft, NftList, NftListReq, NftMetadataReq, NftTransferHistory, + NftTransferHistoryWrapper, NftTransfersReq, NftWrapper, NftsTransferHistoryList, + TransactionNftDetails, WithdrawErc1155Request, WithdrawErc721Request}; use rand::seq::SliceRandom; use rpc::v1::types::Bytes as BytesJson; use secp256k1::PublicKey; @@ -95,7 +95,7 @@ mod nft; mod web3_transport; #[path = "eth/v2_activation.rs"] pub mod v2_activation; -use crate::{lp_coinfind_or_err, MyWalletAddress}; +use crate::{lp_coinfind_or_err, MmCoinEnum, MyWalletAddress, TransactionType}; use v2_activation::{build_address_and_priv_key_policy, EthActivationV2Error}; /// https://github.com/artemii235/etomic-swap/blob/master/contracts/EtomicSwap.sol @@ -105,6 +105,8 @@ use v2_activation::{build_address_and_priv_key_policy, EthActivationV2Error}; const SWAP_CONTRACT_ABI: &str = include_str!("eth/swap_contract_abi.json"); /// https://github.com/ethereum/EIPs/blob/master/EIPS/eip-20.md const ERC20_ABI: &str = include_str!("eth/erc20_abi.json"); +/// https://github.com/ethereum/EIPs/blob/master/EIPS/eip-721.md +const ERC721_ABI: &str = include_str!("eth/erc721_abi.json"); /// Payment states from etomic swap smart contract: https://github.com/artemii235/etomic-swap/blob/master/contracts/EtomicSwap.sol#L5 pub const PAYMENT_STATE_UNINITIALIZED: u8 = 0; pub const PAYMENT_STATE_SENT: u8 = 1; @@ -150,6 +152,7 @@ const DIRECTION_BOTH_MORALIS: &str = "direction=both"; lazy_static! { pub static ref SWAP_CONTRACT: Contract = Contract::load(SWAP_CONTRACT_ABI.as_bytes()).unwrap(); pub static ref ERC20_CONTRACT: Contract = Contract::load(ERC20_ABI.as_bytes()).unwrap(); + pub static ref ERC721_CONTRACT: Contract = Contract::load(ERC721_ABI.as_bytes()).unwrap(); } pub type Web3RpcFut = Box> + Send>; @@ -1000,8 +1003,106 @@ pub async fn withdraw_erc721(ctx: MmArc, req: WithdrawErc721Request) -> Withdraw Chain::Bsc => "BNB", Chain::Eth => "ETH", }; - let _coin = lp_coinfind_or_err(&ctx, ticker).await?; - todo!() + let coin = lp_coinfind_or_err(&ctx, ticker).await?; + let eth_coin = match coin { + MmCoinEnum::EthCoin(eth_coin) => eth_coin, + _ => { + return MmError::err(WithdrawError::CoinDoesntSupportNftWithdraw { + coin: coin.ticker().to_owned(), + }) + }, + }; + let from_addr = valid_addr_from_str(&req.from).map_to_mm(WithdrawError::InvalidAddress)?; + if eth_coin.my_address != from_addr { + return MmError::err(WithdrawError::AddressMismatchError { + my_address: eth_coin.my_address.to_string(), + from: req.from, + }); + } + let to_addr = valid_addr_from_str(&req.to).map_to_mm(WithdrawError::InvalidAddress)?; + let token_addr = addr_from_str(&req.token_address).map_to_mm(WithdrawError::InvalidAddress)?; + let (eth_value, data, call_addr, fee_coin) = match eth_coin.coin_type { + EthCoinType::Eth => { + let function = ERC721_CONTRACT.function("safeTransferFrom")?; + let token_id_u256 = U256::from_dec_str(&req.token_id.to_string()) + .map_err(|e| format!("{:?}", e)) + .map_to_mm(NumConversError::new)?; + let data = function.encode_input(&[ + Token::Address(from_addr), + Token::Address(to_addr), + Token::Uint(token_id_u256), + Token::Bytes("0x".into()), + ])?; + (0.into(), data, token_addr, eth_coin.ticker()) + }, + EthCoinType::Erc20 { .. } => { + return MmError::err(WithdrawError::InternalError( + "Erc20 coin type doesnt support withdraw nft".to_owned(), + )) + }, + }; + let (gas, gas_price) = match req.fee { + Some(WithdrawFee::EthGas { gas_price, gas }) => { + let gas_price = wei_from_big_decimal(&gas_price, 9)?; + (gas.into(), gas_price) + }, + Some(fee_policy) => { + let error = format!("Expected 'EthGas' fee type, found {:?}", fee_policy); + return MmError::err(WithdrawError::InvalidFeePolicy(error)); + }, + None => { + let gas_price = eth_coin.get_gas_price().compat().await?; + let estimate_gas_req = CallRequest { + value: Some(eth_value), + data: Some(data.clone().into()), + from: Some(eth_coin.my_address), + to: call_addr, + gas: None, + // gas price must be supplied because some smart contracts base their + // logic on gas price, e.g. TUSD: https://github.com/KomodoPlatform/atomicDEX-API/issues/643 + gas_price: Some(gas_price), + }; + // Note if the wallet's balance is insufficient to withdraw, then `estimate_gas` may fail with the `Exception` error. + // Ideally we should determine the case when we have the insufficient balance and return `WithdrawError::NotSufficientBalance`. + let gas_limit = eth_coin.estimate_gas(estimate_gas_req).compat().await?; + (gas_limit, gas_price) + }, + }; + let _nonce_lock = eth_coin.nonce_lock.lock().await; + let nonce_fut = get_addr_nonce(eth_coin.my_address, eth_coin.web3_instances.clone()).compat(); + let nonce = match select(nonce_fut, Timer::sleep(30.)).await { + Either::Left((nonce_res, _)) => nonce_res.map_to_mm(WithdrawError::Transport)?, + Either::Right(_) => return MmError::err(WithdrawError::Transport("Get address nonce timed out".to_owned())), + }; + let tx = UnSignedEthTx { + nonce, + value: eth_value, + action: Action::Call(call_addr), + data, + gas, + gas_price, + }; + let secret = eth_coin.priv_key_policy.key_pair_or_err()?.secret(); + let signed = tx.sign(secret, eth_coin.chain_id); + let bytes = rlp::encode(&signed); + let fee_details = EthTxFeeDetails::new(gas, gas_price, fee_coin)?; + Ok(TransactionNftDetails { + tx_hex: bytes.into(), + tx_hash: format!("{:02x}", signed.tx_hash()), + from: vec![req.from], + to: vec![req.to], + contract_type: ContractType::Erc721, + token_address: req.token_address, + token_id: req.token_id, + amount: 1.into(), + fee_details: Some(fee_details.into()), + coin: eth_coin.ticker.clone(), + block_height: 0, + timestamp: now_ms() / 1000, + internal_id: 0, + transaction_type: TransactionType::NftTransfer, + memo: None, + }) } #[derive(Clone)] diff --git a/mm2src/coins/eth/nft/nft_structs.rs b/mm2src/coins/eth/nft/nft_structs.rs index d5b46b83ab..dda6ac9519 100644 --- a/mm2src/coins/eth/nft/nft_structs.rs +++ b/mm2src/coins/eth/nft/nft_structs.rs @@ -132,39 +132,38 @@ pub struct WithdrawErc1155Request { fee: Option, } -#[allow(dead_code)] #[derive(Clone, Deserialize)] pub struct WithdrawErc721Request { pub(crate) chain: Chain, - from: String, - to: String, - token_address: String, - token_id: BigDecimal, - fee: Option, + pub(crate) from: String, + pub(crate) to: String, + pub(crate) token_address: String, + pub(crate) token_id: BigDecimal, + pub(crate) fee: Option, } -#[allow(dead_code)] #[derive(Debug, Deserialize, Serialize)] pub struct TransactionNftDetails { /// Raw bytes of signed transaction, this should be sent as is to `send_raw_transaction_bytes` RPC to broadcast the transaction - tx_hex: BytesJson, - /// Transaction hash in hexadecimal format - tx_hash: String, + pub(crate) tx_hex: BytesJson, + pub(crate) tx_hash: String, /// NFTs are sent from these addresses - from: Vec, + pub(crate) from: Vec, /// NFTs are sent to these addresses - to: Vec, - contract_type: String, - token_address: String, - token_id: BigDecimal, - amount: BigDecimal, - fee_details: Option, + pub(crate) to: Vec, + pub(crate) contract_type: ContractType, + pub(crate) token_address: String, + pub(crate) token_id: BigDecimal, + pub(crate) amount: BigDecimal, + pub(crate) fee_details: Option, + /// The coin transaction belongs to + pub(crate) coin: String, /// Block height - block_height: u64, + pub(crate) block_height: u64, /// Transaction timestamp - timestamp: u64, + pub(crate) timestamp: u64, /// Internal MM2 id used for internal transaction identification, for some coins it might be equal to transaction hash - internal_id: i64, + pub(crate) internal_id: i64, /// Type of transactions, default is StandardTransfer #[serde(default)] pub(crate) transaction_type: TransactionType, diff --git a/mm2src/coins/lp_coins.rs b/mm2src/coins/lp_coins.rs index 2d5ca36330..a7508d23ce 100644 --- a/mm2src/coins/lp_coins.rs +++ b/mm2src/coins/lp_coins.rs @@ -1217,6 +1217,7 @@ pub enum TransactionType { msg_type: CustomTendermintMsgType, token_id: Option, }, + NftTransfer, } /// Transaction details @@ -1781,6 +1782,10 @@ pub enum WithdrawError { #[from_stringify("NumConversError", "UnexpectedDerivationMethod", "PrivKeyPolicyNotAllowed")] #[display(fmt = "Internal error: {}", _0)] InternalError(String), + #[display(fmt = "{} coin doesn't support NFT withdrawing", coin)] + CoinDoesntSupportNftWithdraw { coin: String }, + #[display(fmt = "My address {} and from address {} mismatch", my_address, from)] + AddressMismatchError { my_address: String, from: String }, } impl HttpStatusCode for WithdrawError { @@ -1799,7 +1804,9 @@ impl HttpStatusCode for WithdrawError { | WithdrawError::FromAddressNotFound | WithdrawError::UnexpectedFromAddress(_) | WithdrawError::UnknownAccount { .. } - | WithdrawError::UnexpectedUserAction { .. } => StatusCode::BAD_REQUEST, + | WithdrawError::UnexpectedUserAction { .. } + | WithdrawError::CoinDoesntSupportNftWithdraw { .. } + | WithdrawError::AddressMismatchError { .. } => StatusCode::BAD_REQUEST, WithdrawError::HwError(_) => StatusCode::GONE, WithdrawError::Transport(_) | WithdrawError::InternalError(_) => StatusCode::INTERNAL_SERVER_ERROR, } @@ -1876,7 +1883,7 @@ impl WithdrawError { } } -#[derive(Serialize, Display, Debug, EnumFromStringify, SerializeErrorType)] +#[derive(Debug, Display, EnumFromStringify, Serialize, SerializeErrorType)] #[serde(tag = "error_type", content = "error_data")] pub enum SignatureError { #[display(fmt = "Invalid request: {}", _0)] @@ -1901,7 +1908,7 @@ impl HttpStatusCode for SignatureError { } } -#[derive(Serialize, Display, Debug, SerializeErrorType)] +#[derive(Debug, Display, Serialize, SerializeErrorType)] #[serde(tag = "error_type", content = "error_data")] pub enum VerificationError { #[display(fmt = "Invalid request: {}", _0)] @@ -2519,7 +2526,7 @@ pub trait CoinWithDerivationMethod { } #[allow(clippy::upper_case_acronyms)] -#[derive(Clone, Debug, Serialize, Deserialize)] +#[derive(Clone, Debug, Deserialize, Serialize)] #[serde(tag = "type", content = "protocol_data")] pub enum CoinProtocol { UTXO, diff --git a/mm2src/coins/my_tx_history_v2.rs b/mm2src/coins/my_tx_history_v2.rs index 971ab7b19e..1635be9e80 100644 --- a/mm2src/coins/my_tx_history_v2.rs +++ b/mm2src/coins/my_tx_history_v2.rs @@ -233,7 +233,8 @@ impl<'a, Addr: Clone + DisplayAddress + Eq + std::hash::Hash, Tx: Transaction> T TransactionType::StakingDelegation | TransactionType::RemoveDelegation | TransactionType::FeeForTokenTx - | TransactionType::StandardTransfer => tx_hash.clone(), + | TransactionType::StandardTransfer + | TransactionType::NftTransfer => tx_hash.clone(), }; TransactionDetails { From 5cbf0fd80ad113175a0a169bea1c27d3681c8b0d Mon Sep 17 00:00:00 2001 From: laruh Date: Mon, 20 Feb 2023 14:20:14 +0700 Subject: [PATCH 38/66] move nft from eth to coin crate --- mm2src/coins/eth.rs | 9 ++- mm2src/coins/lp_coins.rs | 78 ++++++++++++----------- mm2src/coins/{eth => }/nft/mod.rs | 0 mm2src/coins/{eth => }/nft/nft_errors.rs | 0 mm2src/coins/{eth => }/nft/nft_structs.rs | 0 5 files changed, 44 insertions(+), 43 deletions(-) rename mm2src/coins/{eth => }/nft/mod.rs (100%) rename mm2src/coins/{eth => }/nft/nft_errors.rs (100%) rename mm2src/coins/{eth => }/nft/nft_structs.rs (100%) diff --git a/mm2src/coins/eth.rs b/mm2src/coins/eth.rs index 53db3c88a3..38d8f67f8c 100644 --- a/mm2src/coins/eth.rs +++ b/mm2src/coins/eth.rs @@ -21,6 +21,10 @@ // Copyright © 2022 AtomicDEX. All rights reserved. // use super::eth::Action::{Call, Create}; +use crate::nft::nft_errors::GetNftInfoError; +use crate::nft::nft_structs::{Chain, ContractType, Nft, NftList, NftListReq, NftMetadataReq, NftTransferHistory, + NftTransferHistoryWrapper, NftTransfersReq, NftWrapper, NftsTransferHistoryList, + TransactionNftDetails, WithdrawErc1155Request, WithdrawErc721Request}; use async_trait::async_trait; use bitcrypto::{keccak256, ripemd160, sha256}; use common::executor::{abortable_queue::AbortableQueue, AbortableSystem, AbortedError, Timer}; @@ -47,10 +51,6 @@ use mm2_err_handle::prelude::*; use mm2_net::transport::{slurp_url, GuiAuthValidation, GuiAuthValidationGenerator, SlurpError}; use mm2_number::{BigDecimal, MmNumber}; #[cfg(test)] use mocktopus::macros::*; -use nft::nft_errors::GetNftInfoError; -use nft::nft_structs::{Chain, ContractType, Nft, NftList, NftListReq, NftMetadataReq, NftTransferHistory, - NftTransferHistoryWrapper, NftTransfersReq, NftWrapper, NftsTransferHistoryList, - TransactionNftDetails, WithdrawErc1155Request, WithdrawErc721Request}; use rand::seq::SliceRandom; use rpc::v1::types::Bytes as BytesJson; use secp256k1::PublicKey; @@ -91,7 +91,6 @@ pub use rlp; #[cfg(test)] mod eth_tests; #[cfg(target_arch = "wasm32")] mod eth_wasm_tests; -mod nft; mod web3_transport; #[path = "eth/v2_activation.rs"] pub mod v2_activation; diff --git a/mm2src/coins/lp_coins.rs b/mm2src/coins/lp_coins.rs index a7508d23ce..76ec4a037e 100644 --- a/mm2src/coins/lp_coins.rs +++ b/mm2src/coins/lp_coins.rs @@ -252,6 +252,8 @@ use utxo::utxo_standard::{utxo_standard_coin_with_policy, UtxoStandardCoin}; use utxo::UtxoActivationParams; use utxo::{BlockchainNetwork, GenerateTxError, UtxoFeeDetails, UtxoTx}; +mod nft; + #[cfg(not(target_arch = "wasm32"))] pub mod z_coin; #[cfg(not(target_arch = "wasm32"))] use z_coin::ZCoin; @@ -3424,44 +3426,6 @@ pub fn address_by_coin_conf_and_pubkey_str( } } -/// `get_my_address` function returns wallet address for necessary coin without its activation. -/// Currently supports only coins with `ETH` protocol type. -pub async fn get_my_address(ctx: MmArc, req: MyAddressReq) -> MmResult { - let coins_en = coin_conf(&ctx, req.coin.as_str()); - - if coins_en.is_null() { - let warning = format!( - "Warning, coin {} is used without a corresponding configuration.", - req.coin - ); - ctx.log.log( - "😅", - #[allow(clippy::unnecessary_cast)] - &[&("coin" as &str), &req.coin, &("no-conf" as &str)], - &warning, - ); - } - - if coins_en["mm2"].is_null() { - return MmError::err(GetMyAddressError::CoinIsNotSupported( - "mm2 param is not set in coins config, assuming that coin is not supported".to_owned(), - )); - } - let protocol: CoinProtocol = json::from_value(coins_en["protocol"].clone())?; - - let my_address = match protocol { - CoinProtocol::ETH => get_eth_address(&req.coin, &ctx).await?, - _ => { - return MmError::err(GetMyAddressError::CoinIsNotSupported(format!( - "{} doesn't support get_my_address", - req.coin - ))); - }, - }; - - Ok(my_address) -} - #[cfg(target_arch = "wasm32")] fn load_history_from_file_impl(coin: &T, ctx: &MmArc) -> TxHistoryFut> where @@ -3682,3 +3646,41 @@ pub trait RpcCommonOps { /// Returns an alive RPC client or returns an error if no RPC endpoint is currently available. async fn get_live_client(&self) -> Result; } + +/// `get_my_address` function returns wallet address for necessary coin without its activation. +/// Currently supports only coins with `ETH` protocol type. +pub async fn get_my_address(ctx: MmArc, req: MyAddressReq) -> MmResult { + let coins_en = coin_conf(&ctx, req.coin.as_str()); + + if coins_en.is_null() { + let warning = format!( + "Warning, coin {} is used without a corresponding configuration.", + req.coin + ); + ctx.log.log( + "😅", + #[allow(clippy::unnecessary_cast)] + &[&("coin" as &str), &req.coin, &("no-conf" as &str)], + &warning, + ); + } + + if coins_en["mm2"].is_null() { + return MmError::err(GetMyAddressError::CoinIsNotSupported( + "mm2 param is not set in coins config, assuming that coin is not supported".to_owned(), + )); + } + let protocol: CoinProtocol = json::from_value(coins_en["protocol"].clone())?; + + let my_address = match protocol { + CoinProtocol::ETH => get_eth_address(&req.coin, &ctx).await?, + _ => { + return MmError::err(GetMyAddressError::CoinIsNotSupported(format!( + "{} doesn't support get_my_address", + req.coin + ))); + }, + }; + + Ok(my_address) +} diff --git a/mm2src/coins/eth/nft/mod.rs b/mm2src/coins/nft/mod.rs similarity index 100% rename from mm2src/coins/eth/nft/mod.rs rename to mm2src/coins/nft/mod.rs diff --git a/mm2src/coins/eth/nft/nft_errors.rs b/mm2src/coins/nft/nft_errors.rs similarity index 100% rename from mm2src/coins/eth/nft/nft_errors.rs rename to mm2src/coins/nft/nft_errors.rs diff --git a/mm2src/coins/eth/nft/nft_structs.rs b/mm2src/coins/nft/nft_structs.rs similarity index 100% rename from mm2src/coins/eth/nft/nft_structs.rs rename to mm2src/coins/nft/nft_structs.rs From bc5d59ddd550146c00bcae8334dcfe397617081f Mon Sep 17 00:00:00 2001 From: laruh Date: Mon, 20 Feb 2023 18:30:53 +0700 Subject: [PATCH 39/66] remove memo --- mm2src/coins/eth.rs | 1 - mm2src/coins/nft/nft_structs.rs | 1 - 2 files changed, 2 deletions(-) diff --git a/mm2src/coins/eth.rs b/mm2src/coins/eth.rs index 38d8f67f8c..b70228e1ab 100644 --- a/mm2src/coins/eth.rs +++ b/mm2src/coins/eth.rs @@ -1100,7 +1100,6 @@ pub async fn withdraw_erc721(ctx: MmArc, req: WithdrawErc721Request) -> Withdraw timestamp: now_ms() / 1000, internal_id: 0, transaction_type: TransactionType::NftTransfer, - memo: None, }) } diff --git a/mm2src/coins/nft/nft_structs.rs b/mm2src/coins/nft/nft_structs.rs index dda6ac9519..f19f0fda79 100644 --- a/mm2src/coins/nft/nft_structs.rs +++ b/mm2src/coins/nft/nft_structs.rs @@ -167,7 +167,6 @@ pub struct TransactionNftDetails { /// Type of transactions, default is StandardTransfer #[serde(default)] pub(crate) transaction_type: TransactionType, - pub(crate) memo: Option, } #[derive(Debug, Deserialize)] From 4edc948b55cd5170a8e44fcc644ce19878779ba1 Mon Sep 17 00:00:00 2001 From: laruh Date: Tue, 21 Feb 2023 17:34:35 +0700 Subject: [PATCH 40/66] wip --- mm2src/coins/eth.rs | 253 +---------------- mm2src/coins/lp_coins.rs | 2 +- mm2src/coins/nft.rs | 256 ++++++++++++++++++ mm2src/coins/nft/mod.rs | 2 - mm2src/coins/nft/nft_structs.rs | 22 +- .../mm2_main/src/rpc/dispatcher/dispatcher.rs | 10 +- 6 files changed, 282 insertions(+), 263 deletions(-) create mode 100644 mm2src/coins/nft.rs delete mode 100644 mm2src/coins/nft/mod.rs diff --git a/mm2src/coins/eth.rs b/mm2src/coins/eth.rs index b70228e1ab..2c77cc02d2 100644 --- a/mm2src/coins/eth.rs +++ b/mm2src/coins/eth.rs @@ -21,15 +21,12 @@ // Copyright © 2022 AtomicDEX. All rights reserved. // use super::eth::Action::{Call, Create}; -use crate::nft::nft_errors::GetNftInfoError; -use crate::nft::nft_structs::{Chain, ContractType, Nft, NftList, NftListReq, NftMetadataReq, NftTransferHistory, - NftTransferHistoryWrapper, NftTransfersReq, NftWrapper, NftsTransferHistoryList, - TransactionNftDetails, WithdrawErc1155Request, WithdrawErc721Request}; +use crate::nft::nft_structs::{Chain, ContractType, TransactionNftDetails, WithdrawErc1155, WithdrawErc721}; use async_trait::async_trait; use bitcrypto::{keccak256, ripemd160, sha256}; use common::executor::{abortable_queue::AbortableQueue, AbortableSystem, AbortedError, Timer}; use common::log::{debug, error, info, warn}; -use common::{get_utc_timestamp, now_ms, small_rng, APPLICATION_JSON, DEX_FEE_ADDR_RAW_PUBKEY, X_API_KEY}; +use common::{get_utc_timestamp, now_ms, small_rng, DEX_FEE_ADDR_RAW_PUBKEY}; use crypto::privkey::key_pair_from_secret; use crypto::{CryptoCtx, CryptoCtxError, GlobalHDAccountArc, KeyPairPolicy}; #[cfg(target_arch = "wasm32")] @@ -44,7 +41,6 @@ use ethkey::{sign, verify_address}; use futures::compat::Future01CompatExt; use futures::future::{join_all, select, Either, FutureExt, TryFutureExt}; use futures01::Future; -use http::header::ACCEPT; use http::StatusCode; use mm2_core::mm_ctx::{MmArc, MmWeak}; use mm2_err_handle::prelude::*; @@ -94,6 +90,7 @@ pub use rlp; mod web3_transport; #[path = "eth/v2_activation.rs"] pub mod v2_activation; +use crate::nft::WithdrawNftResult; use crate::{lp_coinfind_or_err, MmCoinEnum, MyWalletAddress, TransactionType}; use v2_activation::{build_address_and_priv_key_policy, EthActivationV2Error}; @@ -141,13 +138,6 @@ const ETH_GAS: u64 = 150_000; /// Lifetime of generated signed message for gui-auth requests const GUI_AUTH_SIGNED_MESSAGE_LIFETIME_SEC: i64 = 90; -/// url for moralis requests -const URL_MORALIS: &str = "https://deep-index.moralis.io/api/v2/"; -/// query parameter for moralis request: The format of the token ID -const FORMAT_DECIMAL_MORALIS: &str = "format=decimal"; -/// query parameter for moralis request: The transfer direction -const DIRECTION_BOTH_MORALIS: &str = "direction=both"; - lazy_static! { pub static ref SWAP_CONTRACT: Contract = Contract::load(SWAP_CONTRACT_ABI.as_bytes()).unwrap(); pub static ref ERC20_CONTRACT: Contract = Contract::load(ERC20_ABI.as_bytes()).unwrap(); @@ -157,7 +147,6 @@ lazy_static! { pub type Web3RpcFut = Box> + Send>; pub type Web3RpcResult = Result>; pub type GasStationResult = Result>; -pub type WithdrawNftResult = Result>; #[derive(Debug, Display)] pub enum GasStationReqErr { @@ -813,191 +802,16 @@ async fn withdraw_impl(coin: EthCoin, req: WithdrawRequest) -> WithdrawResult { }) } -/// `get_nft_list` function returns list of NFTs on ETH or/and BNB chains owned by user. -pub async fn get_nft_list(ctx: MmArc, req: NftListReq) -> MmResult { - let api_key = ctx.conf["api_key"] - .as_str() - .ok_or_else(|| MmError::new(GetNftInfoError::ApiKeyError))?; - - let mut res_list = Vec::new(); - - for chain in req.chains { - let (coin_str, chain_str) = match chain { - Chain::Bsc => ("BNB", "bsc"), - Chain::Eth => ("ETH", "eth"), - }; - let my_address = get_eth_address(coin_str, &ctx).await?; - let uri_without_cursor = format!( - "{}{}/nft?chain={}&{}", - URL_MORALIS, my_address.wallet_address, chain_str, FORMAT_DECIMAL_MORALIS - ); - - // The cursor returned in the previous response (used for getting the next page). - let mut cursor = String::new(); - loop { - let uri = format!("{}{}", uri_without_cursor, cursor); - let response = send_moralis_request(uri.as_str(), api_key).await?; - if let Some(nfts_list) = response["result"].as_array() { - for nft_json in nfts_list { - let nft_wrapper: NftWrapper = serde_json::from_str(&nft_json.to_string())?; - let nft = Nft { - chain, - token_address: nft_wrapper.token_address, - token_id: nft_wrapper.token_id.0, - amount: nft_wrapper.amount.0, - owner_of: nft_wrapper.owner_of, - token_hash: nft_wrapper.token_hash, - block_number_minted: *nft_wrapper.block_number_minted, - block_number: *nft_wrapper.block_number, - contract_type: nft_wrapper.contract_type.map(|v| v.0), - name: nft_wrapper.name, - symbol: nft_wrapper.symbol, - token_uri: nft_wrapper.token_uri, - metadata: nft_wrapper.metadata, - last_token_uri_sync: nft_wrapper.last_token_uri_sync, - last_metadata_sync: nft_wrapper.last_metadata_sync, - minter_address: nft_wrapper.minter_address, - }; - // collect NFTs from the page - res_list.push(nft); - } - // if cursor is not null, there are other NFTs 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() { - cursor = format!("{}{}", "&cursor=", cursor_res); - continue; - } else { - break; - } - } - } - } - drop_mutability!(res_list); - let nft_list = NftList { - count: res_list.len() as u64, - nfts: res_list, - }; - Ok(nft_list) -} - -/// `get_nft_metadata` function returns info of one specific NFT. -/// Current implementation sends request to Moralis. -/// Later, after adding caching, metadata lookup can be performed using previously obtained NFTs info without -/// sending new moralis request. The moralis request can be sent as a fallback, if the data was not found in the cache. -pub async fn get_nft_metadata(ctx: MmArc, req: NftMetadataReq) -> MmResult { - let api_key = ctx.conf["api_key"] - .as_str() - .ok_or_else(|| MmError::new(GetNftInfoError::ApiKeyError))?; - let chain_str = match req.chain { - Chain::Bsc => "bsc", - Chain::Eth => "eth", - }; - let uri = format!( - "{}nft/{}/{}?chain={}&{}", - URL_MORALIS, req.token_address, req.token_id, chain_str, FORMAT_DECIMAL_MORALIS - ); - let response = send_moralis_request(uri.as_str(), api_key).await?; - let nft_wrapper: NftWrapper = serde_json::from_str(&response.to_string())?; - let nft_metadata = Nft { - chain: req.chain, - token_address: nft_wrapper.token_address, - token_id: nft_wrapper.token_id.0, - amount: nft_wrapper.amount.0, - owner_of: nft_wrapper.owner_of, - token_hash: nft_wrapper.token_hash, - block_number_minted: *nft_wrapper.block_number_minted, - block_number: *nft_wrapper.block_number, - contract_type: nft_wrapper.contract_type.map(|v| v.0), - name: nft_wrapper.name, - symbol: nft_wrapper.symbol, - token_uri: nft_wrapper.token_uri, - metadata: nft_wrapper.metadata, - last_token_uri_sync: nft_wrapper.last_token_uri_sync, - last_metadata_sync: nft_wrapper.last_metadata_sync, - minter_address: nft_wrapper.minter_address, - }; - Ok(nft_metadata) -} - -/// `get_nft_transfers` function returns a transfer history of NFTs on ETH or/and BNb chains owned by user. -/// Currently doesnt support filters. -pub async fn get_nft_transfers(ctx: MmArc, req: NftTransfersReq) -> MmResult { - let api_key = ctx.conf["api_key"] - .as_str() - .ok_or_else(|| MmError::new(GetNftInfoError::ApiKeyError))?; - - let mut res_list = Vec::new(); - - for chain in req.chains { - let (coin_str, chain_str) = match chain { - Chain::Bsc => ("BNB", "bsc"), - Chain::Eth => ("ETH", "eth"), - }; - let my_address = get_eth_address(coin_str, &ctx).await?; - let uri_without_cursor = format!( - "{}{}/nft/transfers?chain={}&{}&{}", - URL_MORALIS, my_address.wallet_address, chain_str, FORMAT_DECIMAL_MORALIS, DIRECTION_BOTH_MORALIS - ); - - // The cursor returned in the previous response (used for getting the next page). - let mut cursor = String::new(); - loop { - let uri = format!("{}{}", uri_without_cursor, cursor); - let response = send_moralis_request(uri.as_str(), api_key).await?; - if let Some(transfer_list) = response["result"].as_array() { - for transfer in transfer_list { - let transfer_wrapper: NftTransferHistoryWrapper = serde_json::from_str(&transfer.to_string())?; - let transfer_history = NftTransferHistory { - chain, - block_number: *transfer_wrapper.block_number, - block_timestamp: transfer_wrapper.block_timestamp, - block_hash: transfer_wrapper.block_hash, - transaction_hash: transfer_wrapper.transaction_hash, - transaction_index: transfer_wrapper.transaction_index, - log_index: transfer_wrapper.log_index, - value: transfer_wrapper.value.0, - contract_type: transfer_wrapper.contract_type.0, - transaction_type: transfer_wrapper.transaction_type, - token_address: transfer_wrapper.token_address, - token_id: transfer_wrapper.token_id.0, - from_address: transfer_wrapper.from_address, - to_address: transfer_wrapper.to_address, - amount: transfer_wrapper.amount.0, - verified: transfer_wrapper.verified, - operator: transfer_wrapper.operator, - }; - // collect NFTs transfers from the page - res_list.push(transfer_history); - } - // 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() { - cursor = format!("{}{}", "&cursor=", cursor_res); - continue; - } else { - break; - } - } - } - } - drop_mutability!(res_list); - let transfer_history_list = NftsTransferHistoryList { - count: res_list.len() as u64, - transfer_history: res_list, - }; - Ok(transfer_history_list) -} - -pub async fn withdraw_erc1155(ctx: MmArc, req: WithdrawErc1155Request) -> WithdrawNftResult { +pub async fn withdraw_erc1155(ctx: MmArc, req: WithdrawErc1155) -> WithdrawNftResult { let ticker = match req.chain { Chain::Bsc => "BNB", Chain::Eth => "ETH", }; let _coin = lp_coinfind_or_err(&ctx, ticker).await?; - todo!() + unimplemented!() } -pub async fn withdraw_erc721(ctx: MmArc, req: WithdrawErc721Request) -> WithdrawNftResult { +pub async fn withdraw_erc721(ctx: MmArc, req: WithdrawErc721) -> WithdrawNftResult { let ticker = match req.chain { Chain::Bsc => "BNB", Chain::Eth => "ETH", @@ -4867,58 +4681,3 @@ pub async fn get_eth_address(ticker: &str, ctx: &MmArc) -> MmResult MmResult { - use http::header::HeaderValue; - use mm2_net::transport::slurp_req_body; - - let request = http::Request::builder() - .method("GET") - .uri(uri) - .header(X_API_KEY, api_key) - .header(ACCEPT, HeaderValue::from_static(APPLICATION_JSON)) - .body(hyper::Body::from(""))?; - - let (status, _header, body) = slurp_req_body(request).await?; - if !status.is_success() { - return Err(MmError::new(GetNftInfoError::Transport(format!( - "Response !200 from {}: {}, {}", - uri, status, body - )))); - } - Ok(body) -} - -#[cfg(target_arch = "wasm32")] -async fn send_moralis_request(uri: &str, api_key: &str) -> MmResult { - use mm2_net::wasm_http::FetchRequest; - - macro_rules! try_or { - ($exp:expr, $errtype:ident) => { - match $exp { - Ok(x) => x, - Err(e) => return Err(MmError::new(GetNftInfoError::$errtype(ERRL!("{:?}", e)))), - } - }; - } - - let result = FetchRequest::get(uri) - .cors() - .body_utf8("".to_owned()) - .header(X_API_KEY, api_key) - .header(ACCEPT.as_str(), APPLICATION_JSON) - .request_str() - .await; - let (status_code, response_str) = try_or!(result, Transport); - if !status_code.is_success() { - return Err(MmError::new(GetNftInfoError::Transport(ERRL!( - "!200: {}, {}", - status_code, - response_str - )))); - } - - let response: Json = try_or!(serde_json::from_str(&response_str), InvalidResponse); - Ok(response) -} diff --git a/mm2src/coins/lp_coins.rs b/mm2src/coins/lp_coins.rs index 76ec4a037e..64cd6db3dd 100644 --- a/mm2src/coins/lp_coins.rs +++ b/mm2src/coins/lp_coins.rs @@ -252,7 +252,7 @@ use utxo::utxo_standard::{utxo_standard_coin_with_policy, UtxoStandardCoin}; use utxo::UtxoActivationParams; use utxo::{BlockchainNetwork, GenerateTxError, UtxoFeeDetails, UtxoTx}; -mod nft; +pub mod nft; #[cfg(not(target_arch = "wasm32"))] pub mod z_coin; #[cfg(not(target_arch = "wasm32"))] use z_coin::ZCoin; diff --git a/mm2src/coins/nft.rs b/mm2src/coins/nft.rs new file mode 100644 index 0000000000..a380f8fc36 --- /dev/null +++ b/mm2src/coins/nft.rs @@ -0,0 +1,256 @@ +use mm2_core::mm_ctx::MmArc; +use mm2_err_handle::prelude::{MmError, MmResult}; + +pub(crate) mod nft_errors; +pub(crate) mod nft_structs; + +use crate::WithdrawError; +use nft_errors::GetNftInfoError; +use nft_structs::{Chain, Nft, NftList, NftListReq, NftMetadataReq, NftTransferHistory, NftTransferHistoryWrapper, + NftTransfersReq, NftWrapper, NftsTransferHistoryList, TransactionNftDetails, WithdrawNftReq}; + +use crate::eth::get_eth_address; +use common::{APPLICATION_JSON, X_API_KEY}; +use http::header::ACCEPT; +use serde_json::Value as Json; + +/// url for moralis requests +const URL_MORALIS: &str = "https://deep-index.moralis.io/api/v2/"; +/// query parameter for moralis request: The format of the token ID +const FORMAT_DECIMAL_MORALIS: &str = "format=decimal"; +/// query parameter for moralis request: The transfer direction +const DIRECTION_BOTH_MORALIS: &str = "direction=both"; + +pub type WithdrawNftResult = Result>; + +/// `get_nft_list` function returns list of NFTs on ETH or/and BNB chains owned by user. +pub async fn get_nft_list(ctx: MmArc, req: NftListReq) -> MmResult { + let api_key = ctx.conf["api_key"] + .as_str() + .ok_or_else(|| MmError::new(GetNftInfoError::ApiKeyError))?; + + let mut res_list = Vec::new(); + + for chain in req.chains { + let (coin_str, chain_str) = match chain { + Chain::Bsc => ("BNB", "bsc"), + Chain::Eth => ("ETH", "eth"), + }; + let my_address = get_eth_address(coin_str, &ctx).await?; + let uri_without_cursor = format!( + "{}{}/nft?chain={}&{}", + URL_MORALIS, my_address.wallet_address, chain_str, FORMAT_DECIMAL_MORALIS + ); + + // The cursor returned in the previous response (used for getting the next page). + let mut cursor = String::new(); + loop { + let uri = format!("{}{}", uri_without_cursor, cursor); + let response = send_moralis_request(uri.as_str(), api_key).await?; + if let Some(nfts_list) = response["result"].as_array() { + for nft_json in nfts_list { + let nft_wrapper: NftWrapper = serde_json::from_str(&nft_json.to_string())?; + let nft = Nft { + chain, + token_address: nft_wrapper.token_address, + token_id: nft_wrapper.token_id.0, + amount: nft_wrapper.amount.0, + owner_of: nft_wrapper.owner_of, + token_hash: nft_wrapper.token_hash, + block_number_minted: *nft_wrapper.block_number_minted, + block_number: *nft_wrapper.block_number, + contract_type: nft_wrapper.contract_type.map(|v| v.0), + name: nft_wrapper.name, + symbol: nft_wrapper.symbol, + token_uri: nft_wrapper.token_uri, + metadata: nft_wrapper.metadata, + last_token_uri_sync: nft_wrapper.last_token_uri_sync, + last_metadata_sync: nft_wrapper.last_metadata_sync, + minter_address: nft_wrapper.minter_address, + }; + // collect NFTs from the page + res_list.push(nft); + } + // if cursor is not null, there are other NFTs 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() { + cursor = format!("{}{}", "&cursor=", cursor_res); + continue; + } else { + break; + } + } + } + } + drop_mutability!(res_list); + let nft_list = NftList { + count: res_list.len() as u64, + nfts: res_list, + }; + Ok(nft_list) +} + +/// `get_nft_metadata` function returns info of one specific NFT. +/// Current implementation sends request to Moralis. +/// Later, after adding caching, metadata lookup can be performed using previously obtained NFTs info without +/// sending new moralis request. The moralis request can be sent as a fallback, if the data was not found in the cache. +pub async fn get_nft_metadata(ctx: MmArc, req: NftMetadataReq) -> MmResult { + let api_key = ctx.conf["api_key"] + .as_str() + .ok_or_else(|| MmError::new(GetNftInfoError::ApiKeyError))?; + let chain_str = match req.chain { + Chain::Bsc => "bsc", + Chain::Eth => "eth", + }; + let uri = format!( + "{}nft/{}/{}?chain={}&{}", + URL_MORALIS, req.token_address, req.token_id, chain_str, FORMAT_DECIMAL_MORALIS + ); + let response = send_moralis_request(uri.as_str(), api_key).await?; + let nft_wrapper: NftWrapper = serde_json::from_str(&response.to_string())?; + let nft_metadata = Nft { + chain: req.chain, + token_address: nft_wrapper.token_address, + token_id: nft_wrapper.token_id.0, + amount: nft_wrapper.amount.0, + owner_of: nft_wrapper.owner_of, + token_hash: nft_wrapper.token_hash, + block_number_minted: *nft_wrapper.block_number_minted, + block_number: *nft_wrapper.block_number, + contract_type: nft_wrapper.contract_type.map(|v| v.0), + name: nft_wrapper.name, + symbol: nft_wrapper.symbol, + token_uri: nft_wrapper.token_uri, + metadata: nft_wrapper.metadata, + last_token_uri_sync: nft_wrapper.last_token_uri_sync, + last_metadata_sync: nft_wrapper.last_metadata_sync, + minter_address: nft_wrapper.minter_address, + }; + Ok(nft_metadata) +} + +/// `get_nft_transfers` function returns a transfer history of NFTs on ETH or/and BNb chains owned by user. +/// Currently doesnt support filters. +pub async fn get_nft_transfers(ctx: MmArc, req: NftTransfersReq) -> MmResult { + let api_key = ctx.conf["api_key"] + .as_str() + .ok_or_else(|| MmError::new(GetNftInfoError::ApiKeyError))?; + + let mut res_list = Vec::new(); + + for chain in req.chains { + let (coin_str, chain_str) = match chain { + Chain::Bsc => ("BNB", "bsc"), + Chain::Eth => ("ETH", "eth"), + }; + let my_address = get_eth_address(coin_str, &ctx).await?; + let uri_without_cursor = format!( + "{}{}/nft/transfers?chain={}&{}&{}", + URL_MORALIS, my_address.wallet_address, chain_str, FORMAT_DECIMAL_MORALIS, DIRECTION_BOTH_MORALIS + ); + + // The cursor returned in the previous response (used for getting the next page). + let mut cursor = String::new(); + loop { + let uri = format!("{}{}", uri_without_cursor, cursor); + let response = send_moralis_request(uri.as_str(), api_key).await?; + if let Some(transfer_list) = response["result"].as_array() { + for transfer in transfer_list { + let transfer_wrapper: NftTransferHistoryWrapper = serde_json::from_str(&transfer.to_string())?; + let transfer_history = NftTransferHistory { + chain, + block_number: *transfer_wrapper.block_number, + block_timestamp: transfer_wrapper.block_timestamp, + block_hash: transfer_wrapper.block_hash, + transaction_hash: transfer_wrapper.transaction_hash, + transaction_index: transfer_wrapper.transaction_index, + log_index: transfer_wrapper.log_index, + value: transfer_wrapper.value.0, + contract_type: transfer_wrapper.contract_type.0, + transaction_type: transfer_wrapper.transaction_type, + token_address: transfer_wrapper.token_address, + token_id: transfer_wrapper.token_id.0, + from_address: transfer_wrapper.from_address, + to_address: transfer_wrapper.to_address, + amount: transfer_wrapper.amount.0, + verified: transfer_wrapper.verified, + operator: transfer_wrapper.operator, + }; + // collect NFTs transfers from the page + res_list.push(transfer_history); + } + // 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() { + cursor = format!("{}{}", "&cursor=", cursor_res); + continue; + } else { + break; + } + } + } + } + drop_mutability!(res_list); + let transfer_history_list = NftsTransferHistoryList { + count: res_list.len() as u64, + transfer_history: res_list, + }; + Ok(transfer_history_list) +} + +pub async fn withdraw_nft(_ctx: MmArc, _req: WithdrawNftReq) -> WithdrawNftResult { todo!() } + +#[cfg(not(target_arch = "wasm32"))] +async fn send_moralis_request(uri: &str, api_key: &str) -> MmResult { + use http::header::HeaderValue; + use mm2_net::transport::slurp_req_body; + + let request = http::Request::builder() + .method("GET") + .uri(uri) + .header(X_API_KEY, api_key) + .header(ACCEPT, HeaderValue::from_static(APPLICATION_JSON)) + .body(hyper::Body::from(""))?; + + let (status, _header, body) = slurp_req_body(request).await?; + if !status.is_success() { + return Err(MmError::new(GetNftInfoError::Transport(format!( + "Response !200 from {}: {}, {}", + uri, status, body + )))); + } + Ok(body) +} + +#[cfg(target_arch = "wasm32")] +async fn send_moralis_request(uri: &str, api_key: &str) -> MmResult { + use mm2_net::wasm_http::FetchRequest; + + macro_rules! try_or { + ($exp:expr, $errtype:ident) => { + match $exp { + Ok(x) => x, + Err(e) => return Err(MmError::new(GetNftInfoError::$errtype(ERRL!("{:?}", e)))), + } + }; + } + + let result = FetchRequest::get(uri) + .cors() + .body_utf8("".to_owned()) + .header(X_API_KEY, api_key) + .header(ACCEPT.as_str(), APPLICATION_JSON) + .request_str() + .await; + let (status_code, response_str) = try_or!(result, Transport); + if !status_code.is_success() { + return Err(MmError::new(GetNftInfoError::Transport(ERRL!( + "!200: {}, {}", + status_code, + response_str + )))); + } + + let response: Json = try_or!(serde_json::from_str(&response_str), InvalidResponse); + Ok(response) +} diff --git a/mm2src/coins/nft/mod.rs b/mm2src/coins/nft/mod.rs deleted file mode 100644 index 88af3e5130..0000000000 --- a/mm2src/coins/nft/mod.rs +++ /dev/null @@ -1,2 +0,0 @@ -pub(crate) mod nft_errors; -pub(crate) mod nft_structs; diff --git a/mm2src/coins/nft/nft_structs.rs b/mm2src/coins/nft/nft_structs.rs index f19f0fda79..15fdd88f2d 100644 --- a/mm2src/coins/nft/nft_structs.rs +++ b/mm2src/coins/nft/nft_structs.rs @@ -18,19 +18,19 @@ pub struct NftMetadataReq { #[derive(Clone, Copy, Debug, Deserialize, Serialize)] #[serde(rename_all = "UPPERCASE")] -pub enum Chain { +pub(crate) enum Chain { Bsc, Eth, } #[derive(Debug, Display)] -pub enum ParseContractTypeError { +pub(crate) enum ParseContractTypeError { UnsupportedContractType, } #[derive(Debug, Deserialize, Serialize)] #[serde(rename_all = "UPPERCASE")] -pub enum ContractType { +pub(crate) enum ContractType { Erc1155, Erc721, } @@ -71,7 +71,7 @@ pub struct Nft { /// This structure is for deserializing NFT json to struct. /// Its needed to convert fields properly, because all fields in json have string type. #[derive(Debug, Deserialize)] -pub struct NftWrapper { +pub(crate) struct NftWrapper { pub(crate) token_address: String, pub(crate) token_id: SerdeStringWrap, pub(crate) amount: SerdeStringWrap, @@ -120,7 +120,7 @@ pub struct NftList { #[allow(dead_code)] #[derive(Clone, Deserialize)] -pub struct WithdrawErc1155Request { +pub struct WithdrawErc1155 { pub(crate) chain: Chain, from: String, to: String, @@ -133,7 +133,7 @@ pub struct WithdrawErc1155Request { } #[derive(Clone, Deserialize)] -pub struct WithdrawErc721Request { +pub struct WithdrawErc721 { pub(crate) chain: Chain, pub(crate) from: String, pub(crate) to: String, @@ -142,6 +142,12 @@ pub struct WithdrawErc721Request { pub(crate) fee: Option, } +#[derive(Clone, Deserialize)] +pub enum WithdrawNftReq { + WithdrawErc1155(WithdrawErc1155), + WithdrawErc721(WithdrawErc721), +} + #[derive(Debug, Deserialize, Serialize)] pub struct TransactionNftDetails { /// Raw bytes of signed transaction, this should be sent as is to `send_raw_transaction_bytes` RPC to broadcast the transaction @@ -175,7 +181,7 @@ pub struct NftTransfersReq { } #[derive(Debug, Serialize)] -pub struct NftTransferHistory { +pub(crate) struct NftTransferHistory { pub(crate) chain: Chain, pub(crate) block_number: u64, pub(crate) block_timestamp: String, @@ -197,7 +203,7 @@ pub struct NftTransferHistory { } #[derive(Debug, Deserialize)] -pub struct NftTransferHistoryWrapper { +pub(crate) struct NftTransferHistoryWrapper { pub(crate) block_number: SerdeStringWrap, pub(crate) block_timestamp: String, pub(crate) block_hash: String, diff --git a/mm2src/mm2_main/src/rpc/dispatcher/dispatcher.rs b/mm2src/mm2_main/src/rpc/dispatcher/dispatcher.rs index d940373f16..ad54b44c6f 100644 --- a/mm2src/mm2_main/src/rpc/dispatcher/dispatcher.rs +++ b/mm2src/mm2_main/src/rpc/dispatcher/dispatcher.rs @@ -9,7 +9,7 @@ use crate::{mm2::lp_stats::{add_node_to_version_stat, remove_node_from_version_s stop_version_stat_collection, update_version_stat_collection}, mm2::lp_swap::{get_locked_amount_rpc, max_maker_vol, recreate_swap_data, trade_preimage_rpc}, mm2::rpc::lp_commands::{get_public_key, get_public_key_hash}}; -use coins::eth::{get_nft_list, get_nft_metadata, get_nft_transfers, withdraw_erc1155, withdraw_erc721, EthCoin}; +use coins::eth::EthCoin; use coins::my_tx_history_v2::my_tx_history_v2_rpc; use coins::rpc_command::{account_balance::account_balance, get_current_mtp::get_current_mtp_rpc, @@ -27,8 +27,8 @@ use coins::utxo::bch::BchCoin; use coins::utxo::qtum::QtumCoin; use coins::utxo::slp::SlpToken; use coins::utxo::utxo_standard::UtxoStandardCoin; -use coins::{add_delegation, get_my_address, get_raw_transaction, get_staking_infos, remove_delegation, sign_message, - verify_message, withdraw}; +use coins::{add_delegation, get_my_address, get_raw_transaction, get_staking_infos, nft, remove_delegation, + sign_message, verify_message, withdraw}; #[cfg(all(not(target_os = "ios"), not(target_os = "android"), not(target_arch = "wasm32")))] use coins::{SolanaCoin, SplToken}; use coins_activation::{cancel_init_l2, cancel_init_standalone_coin, enable_platform_coin_with_tokens, enable_token, @@ -41,6 +41,7 @@ use http::Response; use mm2_core::mm_ctx::MmArc; use mm2_err_handle::prelude::*; use mm2_rpc::mm_protocol::{MmRpcBuilder, MmRpcRequest, MmRpcVersion}; +use nft::{get_nft_list, get_nft_metadata, get_nft_transfers, withdraw_nft}; use serde::de::DeserializeOwned; use serde_json::{self as json, Value as Json}; use std::net::SocketAddr; @@ -182,8 +183,7 @@ async fn dispatcher_v2(request: MmRpcRequest, ctx: MmArc) -> DispatcherResult handle_mmrpc(ctx, request, update_version_stat_collection).await, "verify_message" => handle_mmrpc(ctx, request, verify_message).await, "withdraw" => handle_mmrpc(ctx, request, withdraw).await, - "withdraw_erc1155" => handle_mmrpc(ctx, request, withdraw_erc1155).await, - "withdraw_erc721" => handle_mmrpc(ctx, request, withdraw_erc721).await, + "withdraw_nft" => handle_mmrpc(ctx, request, withdraw_nft).await, #[cfg(not(target_arch = "wasm32"))] native_only_methods => match native_only_methods { #[cfg(all(not(target_os = "ios"), not(target_os = "android")))] From e5d2109e735ee26640da89e37f72aad93806c85e Mon Sep 17 00:00:00 2001 From: laruh Date: Tue, 21 Feb 2023 20:27:08 +0700 Subject: [PATCH 41/66] add fn coins_conf_check --- mm2src/coins/eth.rs | 2 +- mm2src/coins/lp_coins.rs | 89 +++++++++++++++++---------------- mm2src/coins/nft.rs | 15 ++++-- mm2src/coins/nft/nft_structs.rs | 1 + 4 files changed, 58 insertions(+), 49 deletions(-) diff --git a/mm2src/coins/eth.rs b/mm2src/coins/eth.rs index 2c77cc02d2..eb22fc3a77 100644 --- a/mm2src/coins/eth.rs +++ b/mm2src/coins/eth.rs @@ -4668,7 +4668,7 @@ impl From for GetEthAddressError { } /// `get_eth_address` returns wallet address for coin with `ETH` protocol type. -pub async fn get_eth_address(ticker: &str, ctx: &MmArc) -> MmResult { +pub async fn get_eth_address(ctx: &MmArc, ticker: &str) -> MmResult { let priv_key_policy = PrivKeyBuildPolicy::detect_priv_key_policy(ctx)?; // Convert `PrivKeyBuildPolicy` to `EthPrivKeyBuildPolicy` if it's possible. let priv_key_policy = EthPrivKeyBuildPolicy::try_from(priv_key_policy)?; diff --git a/mm2src/coins/lp_coins.rs b/mm2src/coins/lp_coins.rs index 64cd6db3dd..c7a8bebfd5 100644 --- a/mm2src/coins/lp_coins.rs +++ b/mm2src/coins/lp_coins.rs @@ -333,6 +333,7 @@ impl From for RawTransactionError { #[derive(Debug, Display, Serialize, SerializeErrorType, Deserialize)] #[serde(tag = "error_type", content = "error_data")] pub enum GetMyAddressError { + CoinsConfCheckError(String), CoinIsNotSupported(String), #[display(fmt = "Internal error: {}", _0)] Internal(String), @@ -357,7 +358,9 @@ impl From for GetMyAddressError { impl HttpStatusCode for GetMyAddressError { fn status_code(&self) -> StatusCode { match self { - GetMyAddressError::CoinIsNotSupported(_) | GetMyAddressError::InvalidRequest(_) => StatusCode::BAD_REQUEST, + GetMyAddressError::CoinsConfCheckError(_) + | GetMyAddressError::CoinIsNotSupported(_) + | GetMyAddressError::InvalidRequest(_) => StatusCode::BAD_REQUEST, GetMyAddressError::Internal(_) | GetMyAddressError::GetEthAddressError(_) => { StatusCode::INTERNAL_SERVER_ERROR }, @@ -1788,6 +1791,8 @@ pub enum WithdrawError { CoinDoesntSupportNftWithdraw { coin: String }, #[display(fmt = "My address {} and from address {} mismatch", my_address, from)] AddressMismatchError { my_address: String, from: String }, + #[display(fmt = "Contract type {} doesnt support 'withdraw_nft' yet", _0)] + NftWithdrawingNotImplemented(String), } impl HttpStatusCode for WithdrawError { @@ -1808,7 +1813,8 @@ impl HttpStatusCode for WithdrawError { | WithdrawError::UnknownAccount { .. } | WithdrawError::UnexpectedUserAction { .. } | WithdrawError::CoinDoesntSupportNftWithdraw { .. } - | WithdrawError::AddressMismatchError { .. } => StatusCode::BAD_REQUEST, + | WithdrawError::AddressMismatchError { .. } + | WithdrawError::NftWithdrawingNotImplemented(_) => StatusCode::BAD_REQUEST, WithdrawError::HwError(_) => StatusCode::GONE, WithdrawError::Transport(_) | WithdrawError::InternalError(_) => StatusCode::INTERNAL_SERVER_ERROR, } @@ -2748,34 +2754,11 @@ pub async fn lp_coininit(ctx: &MmArc, ticker: &str, req: &Json) -> Result MmResult { - let coins_en = coin_conf(&ctx, req.coin.as_str()); + let ticker = req.coin.as_str(); + let coins_en = coin_conf(&ctx, ticker); + coins_conf_check(&ctx, &coins_en, ticker, None).map_to_mm(GetMyAddressError::CoinsConfCheckError)?; + + let protocol: CoinProtocol = json::from_value(coins_en["protocol"].clone())?; + + let my_address = match protocol { + CoinProtocol::ETH => get_eth_address(&ctx, ticker).await?, + _ => { + return MmError::err(GetMyAddressError::CoinIsNotSupported(format!( + "{} doesn't support get_my_address", + req.coin + ))); + }, + }; + + Ok(my_address) +} +fn coins_conf_check(ctx: &MmArc, coins_en: &Json, ticker: &str, req: Option<&Json>) -> Result<(), String> { if coins_en.is_null() { let warning = format!( "Warning, coin {} is used without a corresponding configuration.", - req.coin + ticker ); ctx.log.log( "😅", #[allow(clippy::unnecessary_cast)] - &[&("coin" as &str), &req.coin, &("no-conf" as &str)], + &[&("coin" as &str), &ticker, &("no-conf" as &str)], &warning, ); } - if coins_en["mm2"].is_null() { - return MmError::err(GetMyAddressError::CoinIsNotSupported( - "mm2 param is not set in coins config, assuming that coin is not supported".to_owned(), + if let Some(req) = req { + if coins_en["mm2"].is_null() && req["mm2"].is_null() { + return ERR!(concat!( + "mm2 param is not set neither in coins config nor enable request, assuming that coin is not supported" + )); + } + } else { + return ERR!(concat!( + "mm2 param is not set in coins config, assuming that coin is not supported" )); } - let protocol: CoinProtocol = json::from_value(coins_en["protocol"].clone())?; - let my_address = match protocol { - CoinProtocol::ETH => get_eth_address(&req.coin, &ctx).await?, - _ => { - return MmError::err(GetMyAddressError::CoinIsNotSupported(format!( - "{} doesn't support get_my_address", - req.coin - ))); - }, - }; - - Ok(my_address) + if coins_en["protocol"].is_null() { + return ERR!( + r#""protocol" field is missing in coins file. The file format is deprecated, please execute ./mm2 update_config command to convert it or download a new one"# + ); + } + Ok(()) } diff --git a/mm2src/coins/nft.rs b/mm2src/coins/nft.rs index a380f8fc36..c0ea1820fc 100644 --- a/mm2src/coins/nft.rs +++ b/mm2src/coins/nft.rs @@ -9,7 +9,7 @@ use nft_errors::GetNftInfoError; use nft_structs::{Chain, Nft, NftList, NftListReq, NftMetadataReq, NftTransferHistory, NftTransferHistoryWrapper, NftTransfersReq, NftWrapper, NftsTransferHistoryList, TransactionNftDetails, WithdrawNftReq}; -use crate::eth::get_eth_address; +use crate::eth::{get_eth_address, withdraw_erc721}; use common::{APPLICATION_JSON, X_API_KEY}; use http::header::ACCEPT; use serde_json::Value as Json; @@ -36,7 +36,7 @@ pub async fn get_nft_list(ctx: MmArc, req: NftListReq) -> MmResult ("BNB", "bsc"), Chain::Eth => ("ETH", "eth"), }; - let my_address = get_eth_address(coin_str, &ctx).await?; + let my_address = get_eth_address(&ctx, coin_str).await?; let uri_without_cursor = format!( "{}{}/nft?chain={}&{}", URL_MORALIS, my_address.wallet_address, chain_str, FORMAT_DECIMAL_MORALIS @@ -143,7 +143,7 @@ pub async fn get_nft_transfers(ctx: MmArc, req: NftTransfersReq) -> MmResult ("BNB", "bsc"), Chain::Eth => ("ETH", "eth"), }; - let my_address = get_eth_address(coin_str, &ctx).await?; + let my_address = get_eth_address(&ctx, coin_str).await?; let uri_without_cursor = format!( "{}{}/nft/transfers?chain={}&{}&{}", URL_MORALIS, my_address.wallet_address, chain_str, FORMAT_DECIMAL_MORALIS, DIRECTION_BOTH_MORALIS @@ -198,7 +198,14 @@ pub async fn get_nft_transfers(ctx: MmArc, req: NftTransfersReq) -> MmResult WithdrawNftResult { todo!() } +pub async fn withdraw_nft(ctx: MmArc, req_type: WithdrawNftReq) -> WithdrawNftResult { + match req_type { + WithdrawNftReq::WithdrawErc1155(_) => { + MmError::err(WithdrawError::NftWithdrawingNotImplemented("ERC1155".to_owned())) + }, + WithdrawNftReq::WithdrawErc721(erc721_req) => withdraw_erc721(ctx, erc721_req).await, + } +} #[cfg(not(target_arch = "wasm32"))] async fn send_moralis_request(uri: &str, api_key: &str) -> MmResult { diff --git a/mm2src/coins/nft/nft_structs.rs b/mm2src/coins/nft/nft_structs.rs index 15fdd88f2d..efe89cfab4 100644 --- a/mm2src/coins/nft/nft_structs.rs +++ b/mm2src/coins/nft/nft_structs.rs @@ -143,6 +143,7 @@ pub struct WithdrawErc721 { } #[derive(Clone, Deserialize)] +#[serde(tag = "type", content = "withdraw_data")] pub enum WithdrawNftReq { WithdrawErc1155(WithdrawErc1155), WithdrawErc721(WithdrawErc721), From d5760532d06e9cbacfdb655030063631a88271b3 Mon Sep 17 00:00:00 2001 From: laruh Date: Wed, 22 Feb 2023 14:21:43 +0700 Subject: [PATCH 42/66] add from_stringify --- mm2src/coins/lp_coins.rs | 16 +++++----------- mm2src/coins/nft.rs | 6 +++--- mm2src/coins/nft/nft_errors.rs | 15 +++++---------- 3 files changed, 13 insertions(+), 24 deletions(-) diff --git a/mm2src/coins/lp_coins.rs b/mm2src/coins/lp_coins.rs index c7a8bebfd5..7b871a9b21 100644 --- a/mm2src/coins/lp_coins.rs +++ b/mm2src/coins/lp_coins.rs @@ -330,27 +330,21 @@ impl From for RawTransactionError { } } -#[derive(Debug, Display, Serialize, SerializeErrorType, Deserialize)] +#[derive(Debug, Deserialize, Display, EnumFromStringify, Serialize, SerializeErrorType)] #[serde(tag = "error_type", content = "error_data")] pub enum GetMyAddressError { CoinsConfCheckError(String), CoinIsNotSupported(String), + #[from_stringify("CryptoCtxError")] #[display(fmt = "Internal error: {}", _0)] Internal(String), + #[from_stringify("serde_json::Error")] #[display(fmt = "Invalid request error error: {}", _0)] InvalidRequest(String), #[display(fmt = "Get Eth address error: {}", _0)] GetEthAddressError(GetEthAddressError), } -impl From for GetMyAddressError { - fn from(e: CryptoCtxError) -> Self { GetMyAddressError::Internal(e.to_string()) } -} - -impl From for GetMyAddressError { - fn from(e: serde_json::Error) -> Self { GetMyAddressError::InvalidRequest(e.to_string()) } -} - impl From for GetMyAddressError { fn from(e: GetEthAddressError) -> Self { GetMyAddressError::GetEthAddressError(e) } } @@ -1792,7 +1786,7 @@ pub enum WithdrawError { #[display(fmt = "My address {} and from address {} mismatch", my_address, from)] AddressMismatchError { my_address: String, from: String }, #[display(fmt = "Contract type {} doesnt support 'withdraw_nft' yet", _0)] - NftWithdrawingNotImplemented(String), + ContractTypeDoesntSupportNftWithdrawing(String), } impl HttpStatusCode for WithdrawError { @@ -1814,7 +1808,7 @@ impl HttpStatusCode for WithdrawError { | WithdrawError::UnexpectedUserAction { .. } | WithdrawError::CoinDoesntSupportNftWithdraw { .. } | WithdrawError::AddressMismatchError { .. } - | WithdrawError::NftWithdrawingNotImplemented(_) => StatusCode::BAD_REQUEST, + | WithdrawError::ContractTypeDoesntSupportNftWithdrawing(_) => StatusCode::BAD_REQUEST, WithdrawError::HwError(_) => StatusCode::GONE, WithdrawError::Transport(_) | WithdrawError::InternalError(_) => StatusCode::INTERNAL_SERVER_ERROR, } diff --git a/mm2src/coins/nft.rs b/mm2src/coins/nft.rs index c0ea1820fc..a9a7da0f19 100644 --- a/mm2src/coins/nft.rs +++ b/mm2src/coins/nft.rs @@ -200,9 +200,9 @@ pub async fn get_nft_transfers(ctx: MmArc, req: NftTransfersReq) -> MmResult WithdrawNftResult { match req_type { - WithdrawNftReq::WithdrawErc1155(_) => { - MmError::err(WithdrawError::NftWithdrawingNotImplemented("ERC1155".to_owned())) - }, + WithdrawNftReq::WithdrawErc1155(_) => MmError::err(WithdrawError::ContractTypeDoesntSupportNftWithdrawing( + "ERC1155".to_owned(), + )), WithdrawNftReq::WithdrawErc721(erc721_req) => withdraw_erc721(ctx, erc721_req).await, } } diff --git a/mm2src/coins/nft/nft_errors.rs b/mm2src/coins/nft/nft_errors.rs index 48dc56cd74..cea9a0d858 100644 --- a/mm2src/coins/nft/nft_errors.rs +++ b/mm2src/coins/nft/nft_errors.rs @@ -1,18 +1,22 @@ use crate::eth::GetEthAddressError; use common::HttpStatusCode; use derive_more::Display; +use enum_from::EnumFromStringify; use http::StatusCode; use mm2_net::transport::SlurpError; use serde::{Deserialize, Serialize}; use web3::Error; -#[derive(Debug, Display, Serialize, SerializeErrorType, Deserialize)] +#[derive(Debug, Deserialize, Display, EnumFromStringify, Serialize, SerializeErrorType)] #[serde(tag = "error_type", content = "error_data")] pub enum GetNftInfoError { + /// `http::Error` can appear on an HTTP request [`http::Builder::build`] building. + #[from_stringify("http::Error")] #[display(fmt = "Invalid request: {}", _0)] InvalidRequest(String), #[display(fmt = "Transport: {}", _0)] Transport(String), + #[from_stringify("serde_json::Error")] #[display(fmt = "Invalid response: {}", _0)] InvalidResponse(String), #[display(fmt = "Internal: {}", _0)] @@ -22,11 +26,6 @@ pub enum GetNftInfoError { ApiKeyError, } -/// `http::Error` can appear on an HTTP request [`http::Builder::build`] building. -impl From for GetNftInfoError { - fn from(e: http::Error) -> Self { GetNftInfoError::InvalidRequest(e.to_string()) } -} - impl From for GetNftInfoError { fn from(e: SlurpError) -> Self { let error_str = e.to_string(); @@ -56,10 +55,6 @@ impl From for GetNftInfoError { fn from(e: GetEthAddressError) -> Self { GetNftInfoError::GetEthAddressError(e) } } -impl From for GetNftInfoError { - fn from(e: serde_json::Error) -> Self { GetNftInfoError::InvalidResponse(e.to_string()) } -} - impl HttpStatusCode for GetNftInfoError { fn status_code(&self) -> StatusCode { match self { From 16cfdccadcf75f9289151c7bc5f4d84b652d7783 Mon Sep 17 00:00:00 2001 From: laruh Date: Wed, 22 Feb 2023 16:27:39 +0700 Subject: [PATCH 43/66] add feature enable-nft-integration, fix coins_conf_check --- mm2src/coins/Cargo.toml | 1 + mm2src/coins/eth.rs | 8 +++++++- mm2src/coins/lp_coins.rs | 5 +++-- mm2src/mm2_main/Cargo.toml | 1 + mm2src/mm2_main/src/rpc/dispatcher/dispatcher.rs | 10 ++++++++-- 5 files changed, 20 insertions(+), 5 deletions(-) diff --git a/mm2src/coins/Cargo.toml b/mm2src/coins/Cargo.toml index 004bec2ae2..f233e2f465 100644 --- a/mm2src/coins/Cargo.toml +++ b/mm2src/coins/Cargo.toml @@ -9,6 +9,7 @@ zhtlc-native-tests = [] # Remove this once the solana integration becomes stable/completed. disable-solana-tests = [] default = ["disable-solana-tests"] +enable-nft-integration = [] [lib] name = "coins" diff --git a/mm2src/coins/eth.rs b/mm2src/coins/eth.rs index eb22fc3a77..a224186ddc 100644 --- a/mm2src/coins/eth.rs +++ b/mm2src/coins/eth.rs @@ -21,6 +21,7 @@ // Copyright © 2022 AtomicDEX. All rights reserved. // use super::eth::Action::{Call, Create}; +#[cfg(feature = "enable-nft-integration")] use crate::nft::nft_structs::{Chain, ContractType, TransactionNftDetails, WithdrawErc1155, WithdrawErc721}; use async_trait::async_trait; use bitcrypto::{keccak256, ripemd160, sha256}; @@ -90,8 +91,11 @@ pub use rlp; mod web3_transport; #[path = "eth/v2_activation.rs"] pub mod v2_activation; +#[cfg(feature = "enable-nft-integration")] use crate::nft::WithdrawNftResult; -use crate::{lp_coinfind_or_err, MmCoinEnum, MyWalletAddress, TransactionType}; +use crate::MyWalletAddress; +#[cfg(feature = "enable-nft-integration")] +use crate::{lp_coinfind_or_err, MmCoinEnum, TransactionType}; use v2_activation::{build_address_and_priv_key_policy, EthActivationV2Error}; /// https://github.com/artemii235/etomic-swap/blob/master/contracts/EtomicSwap.sol @@ -802,6 +806,7 @@ async fn withdraw_impl(coin: EthCoin, req: WithdrawRequest) -> WithdrawResult { }) } +#[cfg(feature = "enable-nft-integration")] pub async fn withdraw_erc1155(ctx: MmArc, req: WithdrawErc1155) -> WithdrawNftResult { let ticker = match req.chain { Chain::Bsc => "BNB", @@ -811,6 +816,7 @@ pub async fn withdraw_erc1155(ctx: MmArc, req: WithdrawErc1155) -> WithdrawNftRe unimplemented!() } +#[cfg(feature = "enable-nft-integration")] pub async fn withdraw_erc721(ctx: MmArc, req: WithdrawErc721) -> WithdrawNftResult { let ticker = match req.chain { Chain::Bsc => "BNB", diff --git a/mm2src/coins/lp_coins.rs b/mm2src/coins/lp_coins.rs index 7b871a9b21..998dbc699c 100644 --- a/mm2src/coins/lp_coins.rs +++ b/mm2src/coins/lp_coins.rs @@ -252,7 +252,7 @@ use utxo::utxo_standard::{utxo_standard_coin_with_policy, UtxoStandardCoin}; use utxo::UtxoActivationParams; use utxo::{BlockchainNetwork, GenerateTxError, UtxoFeeDetails, UtxoTx}; -pub mod nft; +#[cfg(feature = "enable-nft-integration")] pub mod nft; #[cfg(not(target_arch = "wasm32"))] pub mod z_coin; #[cfg(not(target_arch = "wasm32"))] use z_coin::ZCoin; @@ -3666,7 +3666,8 @@ fn coins_conf_check(ctx: &MmArc, coins_en: &Json, ticker: &str, req: Option<&Jso "mm2 param is not set neither in coins config nor enable request, assuming that coin is not supported" )); } - } else { + } + if coins_en["mm2"].is_null() { return ERR!(concat!( "mm2 param is not set in coins config, assuming that coin is not supported" )); diff --git a/mm2src/mm2_main/Cargo.toml b/mm2src/mm2_main/Cargo.toml index 89deabcf9f..9ac86296f3 100644 --- a/mm2src/mm2_main/Cargo.toml +++ b/mm2src/mm2_main/Cargo.toml @@ -20,6 +20,7 @@ zhtlc-native-tests = ["coins/zhtlc-native-tests"] # Remove this once the solana integration becomes stable/completed. disable-solana-tests = [] default = ["disable-solana-tests"] +enable-nft-integration = ["coins/enable-nft-integration"] [dependencies] async-std = { version = "1.5", features = ["unstable"] } diff --git a/mm2src/mm2_main/src/rpc/dispatcher/dispatcher.rs b/mm2src/mm2_main/src/rpc/dispatcher/dispatcher.rs index ad54b44c6f..d4e482d670 100644 --- a/mm2src/mm2_main/src/rpc/dispatcher/dispatcher.rs +++ b/mm2src/mm2_main/src/rpc/dispatcher/dispatcher.rs @@ -11,6 +11,7 @@ use crate::{mm2::lp_stats::{add_node_to_version_stat, remove_node_from_version_s mm2::rpc::lp_commands::{get_public_key, get_public_key_hash}}; use coins::eth::EthCoin; use coins::my_tx_history_v2::my_tx_history_v2_rpc; +#[cfg(feature = "enable-nft-integration")] use coins::nft; use coins::rpc_command::{account_balance::account_balance, get_current_mtp::get_current_mtp_rpc, get_enabled_coins::get_enabled_coins, @@ -27,8 +28,8 @@ use coins::utxo::bch::BchCoin; use coins::utxo::qtum::QtumCoin; use coins::utxo::slp::SlpToken; use coins::utxo::utxo_standard::UtxoStandardCoin; -use coins::{add_delegation, get_my_address, get_raw_transaction, get_staking_infos, nft, remove_delegation, - sign_message, verify_message, withdraw}; +use coins::{add_delegation, get_my_address, get_raw_transaction, get_staking_infos, remove_delegation, sign_message, + verify_message, withdraw}; #[cfg(all(not(target_os = "ios"), not(target_os = "android"), not(target_arch = "wasm32")))] use coins::{SolanaCoin, SplToken}; use coins_activation::{cancel_init_l2, cancel_init_standalone_coin, enable_platform_coin_with_tokens, enable_token, @@ -41,6 +42,7 @@ use http::Response; use mm2_core::mm_ctx::MmArc; use mm2_err_handle::prelude::*; use mm2_rpc::mm_protocol::{MmRpcBuilder, MmRpcRequest, MmRpcVersion}; +#[cfg(feature = "enable-nft-integration")] use nft::{get_nft_list, get_nft_metadata, get_nft_transfers, withdraw_nft}; use serde::de::DeserializeOwned; use serde_json::{self as json, Value as Json}; @@ -161,8 +163,11 @@ async fn dispatcher_v2(request: MmRpcRequest, ctx: MmArc) -> DispatcherResult handle_mmrpc(ctx, request, get_locked_amount_rpc).await, "get_my_address" => handle_mmrpc(ctx, request, get_my_address).await, "get_new_address" => handle_mmrpc(ctx, request, get_new_address).await, + #[cfg(feature = "enable-nft-integration")] "get_nft_list" => handle_mmrpc(ctx, request, get_nft_list).await, + #[cfg(feature = "enable-nft-integration")] "get_nft_metadata" => handle_mmrpc(ctx, request, get_nft_metadata).await, + #[cfg(feature = "enable-nft-integration")] "get_nft_transfers" => handle_mmrpc(ctx, request, get_nft_transfers).await, "get_public_key" => handle_mmrpc(ctx, request, get_public_key).await, "get_public_key_hash" => handle_mmrpc(ctx, request, get_public_key_hash).await, @@ -183,6 +188,7 @@ async fn dispatcher_v2(request: MmRpcRequest, ctx: MmArc) -> DispatcherResult handle_mmrpc(ctx, request, update_version_stat_collection).await, "verify_message" => handle_mmrpc(ctx, request, verify_message).await, "withdraw" => handle_mmrpc(ctx, request, withdraw).await, + #[cfg(feature = "enable-nft-integration")] "withdraw_nft" => handle_mmrpc(ctx, request, withdraw_nft).await, #[cfg(not(target_arch = "wasm32"))] native_only_methods => match native_only_methods { From 09223fa6f0d044fa7322b1a347eb9ce2325c6f82 Mon Sep 17 00:00:00 2001 From: laruh Date: Wed, 22 Feb 2023 16:39:32 +0700 Subject: [PATCH 44/66] add doc comment for withdraw_nft --- mm2src/coins/nft.rs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/mm2src/coins/nft.rs b/mm2src/coins/nft.rs index a9a7da0f19..0dc9a0aef3 100644 --- a/mm2src/coins/nft.rs +++ b/mm2src/coins/nft.rs @@ -198,6 +198,10 @@ pub async fn get_nft_transfers(ctx: MmArc, req: NftTransfersReq) -> MmResult WithdrawNftResult { match req_type { WithdrawNftReq::WithdrawErc1155(_) => MmError::err(WithdrawError::ContractTypeDoesntSupportNftWithdrawing( From 1cedc3be094e57896080362a79b08e295969ffce Mon Sep 17 00:00:00 2001 From: laruh Date: Wed, 22 Feb 2023 19:43:57 +0700 Subject: [PATCH 45/66] fix coins_conf_check --- mm2src/coins/lp_coins.rs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/mm2src/coins/lp_coins.rs b/mm2src/coins/lp_coins.rs index 998dbc699c..e52a140474 100644 --- a/mm2src/coins/lp_coins.rs +++ b/mm2src/coins/lp_coins.rs @@ -3666,8 +3666,7 @@ fn coins_conf_check(ctx: &MmArc, coins_en: &Json, ticker: &str, req: Option<&Jso "mm2 param is not set neither in coins config nor enable request, assuming that coin is not supported" )); } - } - if coins_en["mm2"].is_null() { + } else if coins_en["mm2"].is_null() { return ERR!(concat!( "mm2 param is not set in coins config, assuming that coin is not supported" )); From 1cd530adda395abe10825e644a58204f897bbe21 Mon Sep 17 00:00:00 2001 From: laruh Date: Wed, 22 Feb 2023 21:15:53 +0700 Subject: [PATCH 46/66] fix deref which would be done by auto-deref --- mm2src/common/shared_ref_counter/src/enable.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mm2src/common/shared_ref_counter/src/enable.rs b/mm2src/common/shared_ref_counter/src/enable.rs index bc4f6a0a67..205b36aab7 100644 --- a/mm2src/common/shared_ref_counter/src/enable.rs +++ b/mm2src/common/shared_ref_counter/src/enable.rs @@ -77,7 +77,7 @@ impl SharedRc { let existing_pointers = self.existing_pointers.read().expect(LOCKING_ERROR); log!(level, "{} exists at:", ident); for (_idx, location) in existing_pointers.iter() { - log!(level, "\t{}", stringify_location(*location)); + log!(level, "\t{}", stringify_location(location)); } } From 10ce2587a57eb6a4b41cf5f308530163736c9a63 Mon Sep 17 00:00:00 2001 From: laruh Date: Sat, 25 Feb 2023 23:01:59 +0700 Subject: [PATCH 47/66] fix conflicts, add feature log file --- .../2023-feb/features/nft_integration_poc | 4 ++++ mm2src/coins/eth.rs | 21 ++++++++++--------- mm2src/coins/nft/nft_errors.rs | 11 +++++----- 3 files changed, 20 insertions(+), 16 deletions(-) create mode 100644 dev-logs/2023-feb/features/nft_integration_poc diff --git a/dev-logs/2023-feb/features/nft_integration_poc b/dev-logs/2023-feb/features/nft_integration_poc new file mode 100644 index 0000000000..bfdaff18c6 --- /dev/null +++ b/dev-logs/2023-feb/features/nft_integration_poc @@ -0,0 +1,4 @@ +NFT integration PoC added. Includes ERC721 support for ETH and BSC. + + +author: @laruh \ No newline at end of file diff --git a/mm2src/coins/eth.rs b/mm2src/coins/eth.rs index 720ae7c2fd..11ad6acc27 100644 --- a/mm2src/coins/eth.rs +++ b/mm2src/coins/eth.rs @@ -930,7 +930,6 @@ pub async fn withdraw_erc721(ctx: MmArc, req: WithdrawErc721) -> WithdrawNftResu Token::Address(from_addr), Token::Address(to_addr), Token::Uint(token_id_u256), - Token::Bytes("0x".into()), ])?; (0.into(), data, token_addr, eth_coin.ticker()) }, @@ -955,11 +954,12 @@ pub async fn withdraw_erc721(ctx: MmArc, req: WithdrawErc721) -> WithdrawNftResu value: Some(eth_value), data: Some(data.clone().into()), from: Some(eth_coin.my_address), - to: call_addr, + to: Some(call_addr), gas: None, // gas price must be supplied because some smart contracts base their // logic on gas price, e.g. TUSD: https://github.com/KomodoPlatform/atomicDEX-API/issues/643 gas_price: Some(gas_price), + ..CallRequest::default() }; // Note if the wallet's balance is insufficient to withdraw, then `estimate_gas` may fail with the `Exception` error. // Ideally we should determine the case when we have the insufficient balance and return `WithdrawError::NotSufficientBalance`. @@ -968,11 +968,12 @@ pub async fn withdraw_erc721(ctx: MmArc, req: WithdrawErc721) -> WithdrawNftResu }, }; let _nonce_lock = eth_coin.nonce_lock.lock().await; - let nonce_fut = get_addr_nonce(eth_coin.my_address, eth_coin.web3_instances.clone()).compat(); - let nonce = match select(nonce_fut, Timer::sleep(30.)).await { - Either::Left((nonce_res, _)) => nonce_res.map_to_mm(WithdrawError::Transport)?, - Either::Right(_) => return MmError::err(WithdrawError::Transport("Get address nonce timed out".to_owned())), - }; + let nonce = get_addr_nonce(eth_coin.my_address, eth_coin.web3_instances.clone()) + .compat() + .timeout_secs(30.) + .await? + .map_to_mm(WithdrawError::Transport)?; + let tx = UnSignedEthTx { nonce, value: eth_value, @@ -983,10 +984,10 @@ pub async fn withdraw_erc721(ctx: MmArc, req: WithdrawErc721) -> WithdrawNftResu }; let secret = eth_coin.priv_key_policy.key_pair_or_err()?.secret(); let signed = tx.sign(secret, eth_coin.chain_id); - let bytes = rlp::encode(&signed); + let signed_bytes = rlp::encode(&signed); let fee_details = EthTxFeeDetails::new(gas, gas_price, fee_coin)?; Ok(TransactionNftDetails { - tx_hex: bytes.into(), + tx_hex: BytesJson::from(signed_bytes.to_vec()), tx_hash: format!("{:02x}", signed.tx_hash()), from: vec![req.from], to: vec![req.to], @@ -4915,7 +4916,7 @@ pub async fn get_eth_address(ctx: &MmArc, ticker: &str) -> MmResult for GetNftInfoError { impl From for GetNftInfoError { fn from(e: Error) -> Self { let error_str = e.to_string(); - match e.kind() { - web3::ErrorKind::InvalidResponse(_) - | web3::ErrorKind::Decoder(_) - | web3::ErrorKind::Msg(_) - | web3::ErrorKind::Rpc(_) => GetNftInfoError::InvalidResponse(error_str), - web3::ErrorKind::Transport(_) | web3::ErrorKind::Io(_) => GetNftInfoError::Transport(error_str), + match e { + web3::Error::InvalidResponse(_) | web3::Error::Decoder(_) | web3::Error::Rpc(_) => { + GetNftInfoError::InvalidResponse(error_str) + }, + web3::Error::Transport(_) | web3::Error::Io(_) => GetNftInfoError::Transport(error_str), _ => GetNftInfoError::Internal(error_str), } } From 114ad32af35362dd0247056461680f9a827aa31f Mon Sep 17 00:00:00 2001 From: laruh Date: Sat, 25 Feb 2023 23:41:43 +0700 Subject: [PATCH 48/66] fix wasm --- mm2src/mm2_metamask/src/metamask_error.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/mm2src/mm2_metamask/src/metamask_error.rs b/mm2src/mm2_metamask/src/metamask_error.rs index 29e3201b9a..68db8af025 100644 --- a/mm2src/mm2_metamask/src/metamask_error.rs +++ b/mm2src/mm2_metamask/src/metamask_error.rs @@ -1,7 +1,7 @@ use derive_more::Display; use jsonrpc_core::{Error as RPCError, ErrorCode as RpcErrorCode}; use mm2_err_handle::prelude::*; -use serde_derive::Serialize; +use serde_derive::{Deserialize, Serialize}; use web3::Error as Web3Error; const USER_CANCELLED_ERROR_CODE: RpcErrorCode = RpcErrorCode::ServerError(4001); @@ -55,7 +55,7 @@ impl From for MetamaskError { /// so please extend it if it's required **only**. /// /// Please also note that this enum is fieldless. -#[derive(Clone, Debug, Display, Serialize, PartialEq)] +#[derive(Clone, Debug, Deserialize, Display, Serialize, PartialEq)] pub enum MetamaskRpcError { EthProviderNotFound, #[display(fmt = "User cancelled request")] From a260ff0f7770e292e8a9305456257a7854903d14 Mon Sep 17 00:00:00 2001 From: laruh Date: Fri, 3 Mar 2023 21:39:21 +0700 Subject: [PATCH 49/66] find_wallet_amount, withdraw_erc1155 --- mm2src/coins/eth.rs | 139 ++++++++++++++++++++++++++++-- mm2src/coins/eth/v2_activation.rs | 2 +- mm2src/coins/lp_coins.rs | 35 +++++++- mm2src/coins/nft.rs | 29 +++++-- mm2src/coins/nft/nft_errors.rs | 18 +++- mm2src/coins/nft/nft_structs.rs | 15 ++-- 6 files changed, 211 insertions(+), 27 deletions(-) diff --git a/mm2src/coins/eth.rs b/mm2src/coins/eth.rs index 11ad6acc27..48b2fafdc2 100644 --- a/mm2src/coins/eth.rs +++ b/mm2src/coins/eth.rs @@ -22,7 +22,7 @@ // use super::eth::Action::{Call, Create}; #[cfg(feature = "enable-nft-integration")] -use crate::nft::nft_structs::{Chain, ContractType, TransactionNftDetails, WithdrawErc1155, WithdrawErc721}; +use crate::nft::nft_structs::{Chain, ContractType, NftListReq, TransactionNftDetails, WithdrawErc1155, WithdrawErc721}; use async_trait::async_trait; use bitcrypto::{keccak256, ripemd160, sha256}; use common::custom_futures::repeatable::{Ready, Retry}; @@ -100,7 +100,7 @@ mod web3_transport; #[path = "eth/v2_activation.rs"] pub mod v2_activation; #[cfg(feature = "enable-nft-integration")] -use crate::nft::WithdrawNftResult; +use crate::nft::{find_wallet_amount, WithdrawNftResult}; use crate::MyWalletAddress; #[cfg(feature = "enable-nft-integration")] use crate::{lp_coinfind_or_err, MmCoinEnum, TransactionType}; @@ -118,6 +118,8 @@ const SWAP_CONTRACT_ABI: &str = include_str!("eth/swap_contract_abi.json"); const ERC20_ABI: &str = include_str!("eth/erc20_abi.json"); /// https://github.com/ethereum/EIPs/blob/master/EIPS/eip-721.md const ERC721_ABI: &str = include_str!("eth/erc721_abi.json"); +/// https://github.com/ethereum/EIPs/blob/master/EIPS/eip-1155.md +const ERC1155_ABI: &str = include_str!("eth/erc1155_abi.json"); /// Payment states from etomic swap smart contract: https://github.com/artemii235/etomic-swap/blob/master/contracts/EtomicSwap.sol#L5 pub const PAYMENT_STATE_UNINITIALIZED: u8 = 0; pub const PAYMENT_STATE_SENT: u8 = 1; @@ -157,6 +159,7 @@ lazy_static! { pub static ref SWAP_CONTRACT: Contract = Contract::load(SWAP_CONTRACT_ABI.as_bytes()).unwrap(); pub static ref ERC20_CONTRACT: Contract = Contract::load(ERC20_ABI.as_bytes()).unwrap(); pub static ref ERC721_CONTRACT: Contract = Contract::load(ERC721_ABI.as_bytes()).unwrap(); + pub static ref ERC1155_CONTRACT: Contract = Contract::load(ERC1155_ABI.as_bytes()).unwrap(); } pub type Web3RpcFut = Box> + Send>; @@ -892,8 +895,134 @@ pub async fn withdraw_erc1155(ctx: MmArc, req: WithdrawErc1155) -> WithdrawNftRe Chain::Bsc => "BNB", Chain::Eth => "ETH", }; - let _coin = lp_coinfind_or_err(&ctx, ticker).await?; - unimplemented!() + let coin = lp_coinfind_or_err(&ctx, ticker).await?; + let eth_coin = match coin { + MmCoinEnum::EthCoin(eth_coin) => eth_coin, + _ => { + return MmError::err(WithdrawError::CoinDoesntSupportNftWithdraw { + coin: coin.ticker().to_owned(), + }) + }, + }; + let from_addr = valid_addr_from_str(&req.from).map_to_mm(WithdrawError::InvalidAddress)?; + if eth_coin.my_address != from_addr { + return MmError::err(WithdrawError::AddressMismatchError { + my_address: eth_coin.my_address.to_string(), + from: req.from, + }); + } + let to_addr = valid_addr_from_str(&req.to).map_to_mm(WithdrawError::InvalidAddress)?; + let token_addr = addr_from_str(&req.token_address).map_to_mm(WithdrawError::InvalidAddress)?; + + // todo check amount in nft cache, instead of sending new moralis req + // dont use `get_nft_metadata` for erc1155, it can return info related to other owner. + let nft_req = NftListReq { + chains: vec![req.chain], + }; + let wallet_amount = find_wallet_amount(ctx, nft_req, req.token_address.clone(), req.token_id.clone()).await?; + + let amount_dec = if req.max { + wallet_amount.clone() + } else { + req.amount.unwrap_or_else(|| 1.into()) + }; + + if amount_dec > wallet_amount { + return MmError::err(WithdrawError::NotEnoughNftsAmount { + token_address: req.token_address, + token_id: req.token_id.to_string(), + available: wallet_amount, + required: amount_dec, + }); + } + + let (eth_value, data, call_addr, fee_coin) = match eth_coin.coin_type { + EthCoinType::Eth => { + let function = ERC1155_CONTRACT.function("safeTransferFrom")?; + let token_id_u256 = U256::from_dec_str(&req.token_id.to_string()) + .map_err(|e| format!("{:?}", e)) + .map_to_mm(NumConversError::new)?; + let amount_u256 = U256::from_dec_str(&amount_dec.to_string()) + .map_err(|e| format!("{:?}", e)) + .map_to_mm(NumConversError::new)?; + let data = function.encode_input(&[ + Token::Address(from_addr), + Token::Address(to_addr), + Token::Uint(token_id_u256), + Token::Uint(amount_u256), + Token::Bytes("0x".into()), + ])?; + (0.into(), data, token_addr, eth_coin.ticker()) + }, + EthCoinType::Erc20 { .. } => { + return MmError::err(WithdrawError::InternalError( + "Erc20 coin type doesnt support withdraw nft".to_owned(), + )) + }, + }; + let (gas, gas_price) = match req.fee { + Some(WithdrawFee::EthGas { gas_price, gas }) => { + let gas_price = wei_from_big_decimal(&gas_price, 9)?; + (gas.into(), gas_price) + }, + Some(fee_policy) => { + let error = format!("Expected 'EthGas' fee type, found {:?}", fee_policy); + return MmError::err(WithdrawError::InvalidFeePolicy(error)); + }, + None => { + let gas_price = eth_coin.get_gas_price().compat().await?; + let estimate_gas_req = CallRequest { + value: Some(eth_value), + data: Some(data.clone().into()), + from: Some(eth_coin.my_address), + to: Some(call_addr), + gas: None, + // gas price must be supplied because some smart contracts base their + // logic on gas price, e.g. TUSD: https://github.com/KomodoPlatform/atomicDEX-API/issues/643 + gas_price: Some(gas_price), + ..CallRequest::default() + }; + // Note if the wallet's balance is insufficient to withdraw, then `estimate_gas` may fail with the `Exception` error. + // Ideally we should determine the case when we have the insufficient balance and return `WithdrawError::NotSufficientBalance`. + let gas_limit = eth_coin.estimate_gas(estimate_gas_req).compat().await?; + (gas_limit, gas_price) + }, + }; + let _nonce_lock = eth_coin.nonce_lock.lock().await; + let nonce = get_addr_nonce(eth_coin.my_address, eth_coin.web3_instances.clone()) + .compat() + .timeout_secs(30.) + .await? + .map_to_mm(WithdrawError::Transport)?; + + let tx = UnSignedEthTx { + nonce, + value: eth_value, + action: Action::Call(call_addr), + data, + gas, + gas_price, + }; + let secret = eth_coin.priv_key_policy.key_pair_or_err()?.secret(); + let signed = tx.sign(secret, eth_coin.chain_id); + let signed_bytes = rlp::encode(&signed); + let fee_details = EthTxFeeDetails::new(gas, gas_price, fee_coin)?; + Ok(TransactionNftDetails { + tx_hex: BytesJson::from(signed_bytes.to_vec()), + tx_hash: format!("{:02x}", signed.tx_hash()), + from: vec![req.from], + to: vec![req.to], + contract_type: ContractType::Erc1155, + token_address: req.token_address, + token_id: req.token_id, + amount: 1.into(), + fee_details: Some(fee_details.into()), + coin: eth_coin.ticker.clone(), + block_height: 0, + timestamp: now_ms() / 1000, + internal_id: 0, + transaction_type: TransactionType::NftTransfer, + }) } #[cfg(feature = "enable-nft-integration")] @@ -4891,7 +5020,7 @@ fn increase_gas_price_by_stage(gas_price: U256, level: &FeeApproxStage) -> U256 } } -#[derive(Debug, Deserialize, Serialize, Display)] +#[derive(Clone, Debug, Deserialize, Display, PartialEq, Serialize)] pub enum GetEthAddressError { PrivKeyPolicyNotAllowed(PrivKeyPolicyNotAllowed), EthActivationV2Error(EthActivationV2Error), diff --git a/mm2src/coins/eth/v2_activation.rs b/mm2src/coins/eth/v2_activation.rs index 0fb6a5d02c..7204a2413e 100644 --- a/mm2src/coins/eth/v2_activation.rs +++ b/mm2src/coins/eth/v2_activation.rs @@ -6,7 +6,7 @@ use mm2_err_handle::common_errors::WithInternal; #[cfg(target_arch = "wasm32")] use mm2_metamask::{from_metamask_error, MetamaskError, MetamaskRpcError, WithMetamaskRpcError}; -#[derive(Debug, Deserialize, Display, EnumFromTrait, Serialize, SerializeErrorType)] +#[derive(Clone, Debug, Deserialize, Display, EnumFromTrait, PartialEq, Serialize, SerializeErrorType)] #[serde(tag = "error_type", content = "error_data")] pub enum EthActivationV2Error { InvalidPayload(String), diff --git a/mm2src/coins/lp_coins.rs b/mm2src/coins/lp_coins.rs index f4a883afc7..a2f0f0003f 100644 --- a/mm2src/coins/lp_coins.rs +++ b/mm2src/coins/lp_coins.rs @@ -256,6 +256,8 @@ use utxo::UtxoActivationParams; use utxo::{BlockchainNetwork, GenerateTxError, UtxoFeeDetails, UtxoTx}; #[cfg(feature = "enable-nft-integration")] pub mod nft; +#[cfg(feature = "enable-nft-integration")] +use nft::nft_errors::GetNftInfoError; #[cfg(not(target_arch = "wasm32"))] pub mod z_coin; #[cfg(not(target_arch = "wasm32"))] use z_coin::ZCoin; @@ -406,7 +408,7 @@ pub enum TxHistoryError { InternalError(String), } -#[derive(Clone, Debug, Display, Deserialize)] +#[derive(Clone, Debug, Deserialize, Display, PartialEq)] pub enum PrivKeyPolicyNotAllowed { #[display(fmt = "Hardware Wallet is not supported")] HardwareWalletNotSupported, @@ -1713,7 +1715,7 @@ impl DelegationError { } } -#[derive(Clone, Debug, Display, EnumFromStringify, EnumFromTrait, Serialize, SerializeErrorType, PartialEq)] +#[derive(Clone, Debug, Display, EnumFromStringify, EnumFromTrait, PartialEq, Serialize, SerializeErrorType)] #[serde(tag = "error_type", content = "error_data")] pub enum WithdrawError { #[display( @@ -1780,8 +1782,30 @@ pub enum WithdrawError { CoinDoesntSupportNftWithdraw { coin: String }, #[display(fmt = "My address {} and from address {} mismatch", my_address, from)] AddressMismatchError { my_address: String, from: String }, + #[cfg(feature = "enable-nft-integration")] #[display(fmt = "Contract type {} doesnt support 'withdraw_nft' yet", _0)] ContractTypeDoesntSupportNftWithdrawing(String), + #[cfg(feature = "enable-nft-integration")] + GetNftInfoError(GetNftInfoError), + #[cfg(feature = "enable-nft-integration")] + #[display( + fmt = "Not enough NFTs amount with token_address: {} and token_id {}. Available {}, required {}", + token_address, + token_id, + available, + required + )] + NotEnoughNftsAmount { + token_address: String, + token_id: String, + available: BigDecimal, + required: BigDecimal, + }, +} + +#[cfg(feature = "enable-nft-integration")] +impl From for WithdrawError { + fn from(e: GetNftInfoError) -> Self { WithdrawError::GetNftInfoError(e) } } impl HttpStatusCode for WithdrawError { @@ -1802,12 +1826,15 @@ impl HttpStatusCode for WithdrawError { | WithdrawError::UnknownAccount { .. } | WithdrawError::UnexpectedUserAction { .. } | WithdrawError::CoinDoesntSupportNftWithdraw { .. } - | WithdrawError::AddressMismatchError { .. } - | WithdrawError::ContractTypeDoesntSupportNftWithdrawing(_) => StatusCode::BAD_REQUEST, + | WithdrawError::AddressMismatchError { .. } => StatusCode::BAD_REQUEST, WithdrawError::HwError(_) => StatusCode::GONE, #[cfg(target_arch = "wasm32")] WithdrawError::BroadcastExpected(_) => StatusCode::BAD_REQUEST, WithdrawError::Transport(_) | WithdrawError::InternalError(_) => StatusCode::INTERNAL_SERVER_ERROR, + #[cfg(feature = "enable-nft-integration")] + WithdrawError::GetNftInfoError(_) + | WithdrawError::ContractTypeDoesntSupportNftWithdrawing(_) + | WithdrawError::NotEnoughNftsAmount { .. } => StatusCode::BAD_REQUEST, } } } diff --git a/mm2src/coins/nft.rs b/mm2src/coins/nft.rs index 0dc9a0aef3..2ad22b74aa 100644 --- a/mm2src/coins/nft.rs +++ b/mm2src/coins/nft.rs @@ -9,9 +9,10 @@ use nft_errors::GetNftInfoError; use nft_structs::{Chain, Nft, NftList, NftListReq, NftMetadataReq, NftTransferHistory, NftTransferHistoryWrapper, NftTransfersReq, NftWrapper, NftsTransferHistoryList, TransactionNftDetails, WithdrawNftReq}; -use crate::eth::{get_eth_address, withdraw_erc721}; +use crate::eth::{get_eth_address, withdraw_erc1155, withdraw_erc721}; use common::{APPLICATION_JSON, X_API_KEY}; use http::header::ACCEPT; +use mm2_number::BigDecimal; use serde_json::Value as Json; /// url for moralis requests @@ -94,6 +95,9 @@ pub async fn get_nft_list(ctx: MmArc, req: NftListReq) -> MmResult MmResult { let api_key = ctx.conf["api_key"] .as_str() @@ -201,12 +205,9 @@ pub async fn get_nft_transfers(ctx: MmArc, req: NftTransfersReq) -> MmResult WithdrawNftResult { match req_type { - WithdrawNftReq::WithdrawErc1155(_) => MmError::err(WithdrawError::ContractTypeDoesntSupportNftWithdrawing( - "ERC1155".to_owned(), - )), + WithdrawNftReq::WithdrawErc1155(erc1155_req) => withdraw_erc1155(ctx, erc1155_req).await, WithdrawNftReq::WithdrawErc721(erc721_req) => withdraw_erc721(ctx, erc721_req).await, } } @@ -265,3 +266,21 @@ async fn send_moralis_request(uri: &str, api_key: &str) -> MmResult MmResult { + let nft_list = get_nft_list(ctx, nft_list).await?; + for nft in nft_list.nfts { + if nft.token_address == token_address_req && nft.token_id == token_id_req { + return Ok(nft.amount); + } + } + MmError::err(GetNftInfoError::TokenNotFindInWallet { + token_address: token_address_req, + token_id: token_id_req.to_string(), + }) +} diff --git a/mm2src/coins/nft/nft_errors.rs b/mm2src/coins/nft/nft_errors.rs index d48753266b..beba19c513 100644 --- a/mm2src/coins/nft/nft_errors.rs +++ b/mm2src/coins/nft/nft_errors.rs @@ -7,7 +7,7 @@ use mm2_net::transport::SlurpError; use serde::{Deserialize, Serialize}; use web3::Error; -#[derive(Debug, Deserialize, Display, EnumFromStringify, Serialize, SerializeErrorType)] +#[derive(Clone, Debug, Deserialize, Display, EnumFromStringify, PartialEq, Serialize, SerializeErrorType)] #[serde(tag = "error_type", content = "error_data")] pub enum GetNftInfoError { /// `http::Error` can appear on an HTTP request [`http::Builder::build`] building. @@ -24,6 +24,15 @@ pub enum GetNftInfoError { GetEthAddressError(GetEthAddressError), #[display(fmt = "X-API-Key is missing")] ApiKeyError, + #[display( + fmt = "Token: token_address {}, token_id {} was not find in wallet", + token_address, + token_id + )] + TokenNotFindInWallet { + token_address: String, + token_id: String, + }, } impl From for GetNftInfoError { @@ -60,9 +69,10 @@ impl HttpStatusCode for GetNftInfoError { GetNftInfoError::InvalidRequest(_) => StatusCode::BAD_REQUEST, GetNftInfoError::InvalidResponse(_) => StatusCode::FAILED_DEPENDENCY, GetNftInfoError::ApiKeyError => StatusCode::FORBIDDEN, - GetNftInfoError::Transport(_) | GetNftInfoError::Internal(_) | GetNftInfoError::GetEthAddressError(_) => { - StatusCode::INTERNAL_SERVER_ERROR - }, + GetNftInfoError::Transport(_) + | GetNftInfoError::Internal(_) + | GetNftInfoError::GetEthAddressError(_) + | GetNftInfoError::TokenNotFindInWallet { .. } => StatusCode::INTERNAL_SERVER_ERROR, } } } diff --git a/mm2src/coins/nft/nft_structs.rs b/mm2src/coins/nft/nft_structs.rs index efe89cfab4..60aeb630be 100644 --- a/mm2src/coins/nft/nft_structs.rs +++ b/mm2src/coins/nft/nft_structs.rs @@ -118,18 +118,17 @@ pub struct NftList { pub(crate) nfts: Vec, } -#[allow(dead_code)] #[derive(Clone, Deserialize)] pub struct WithdrawErc1155 { pub(crate) chain: Chain, - from: String, - to: String, - token_address: String, - token_id: BigDecimal, - amount: BigDecimal, + pub(crate) from: String, + pub(crate) to: String, + pub(crate) token_address: String, + pub(crate) token_id: BigDecimal, + pub(crate) amount: Option, #[serde(default)] - max: bool, - fee: Option, + pub(crate) max: bool, + pub(crate) fee: Option, } #[derive(Clone, Deserialize)] From 5d60912228eebcc6b577e825e8bfe8e991736711 Mon Sep 17 00:00:00 2001 From: laruh Date: Sun, 5 Mar 2023 15:18:44 +0700 Subject: [PATCH 50/66] add Avalanche, Fantom, Polygon chains --- mm2src/coins/eth.rs | 6 ++++++ mm2src/coins/nft.rs | 9 +++++++++ mm2src/coins/nft/nft_structs.rs | 3 +++ 3 files changed, 18 insertions(+) diff --git a/mm2src/coins/eth.rs b/mm2src/coins/eth.rs index 48b2fafdc2..192b6af1f0 100644 --- a/mm2src/coins/eth.rs +++ b/mm2src/coins/eth.rs @@ -892,8 +892,11 @@ async fn withdraw_impl(coin: EthCoin, req: WithdrawRequest) -> WithdrawResult { #[cfg(feature = "enable-nft-integration")] pub async fn withdraw_erc1155(ctx: MmArc, req: WithdrawErc1155) -> WithdrawNftResult { let ticker = match req.chain { + Chain::Avalanche => "AVAX", Chain::Bsc => "BNB", Chain::Eth => "ETH", + Chain::Fantom => "FTM", + Chain::Polygon => "MATIC", }; let coin = lp_coinfind_or_err(&ctx, ticker).await?; let eth_coin = match coin { @@ -1028,8 +1031,11 @@ pub async fn withdraw_erc1155(ctx: MmArc, req: WithdrawErc1155) -> WithdrawNftRe #[cfg(feature = "enable-nft-integration")] pub async fn withdraw_erc721(ctx: MmArc, req: WithdrawErc721) -> WithdrawNftResult { let ticker = match req.chain { + Chain::Avalanche => "AVAX", Chain::Bsc => "BNB", Chain::Eth => "ETH", + Chain::Fantom => "FTM", + Chain::Polygon => "MATIC", }; let coin = lp_coinfind_or_err(&ctx, ticker).await?; let eth_coin = match coin { diff --git a/mm2src/coins/nft.rs b/mm2src/coins/nft.rs index 2ad22b74aa..16ee5e4c12 100644 --- a/mm2src/coins/nft.rs +++ b/mm2src/coins/nft.rs @@ -34,8 +34,11 @@ pub async fn get_nft_list(ctx: MmArc, req: NftListReq) -> MmResult ("AVAX", "avalanche"), Chain::Bsc => ("BNB", "bsc"), Chain::Eth => ("ETH", "eth"), + Chain::Fantom => ("FTM", "fantom"), + Chain::Polygon => ("MATIC", "polygon"), }; let my_address = get_eth_address(&ctx, coin_str).await?; let uri_without_cursor = format!( @@ -103,8 +106,11 @@ pub async fn get_nft_metadata(ctx: MmArc, req: NftMetadataReq) -> MmResult "avalanche", Chain::Bsc => "bsc", Chain::Eth => "eth", + Chain::Fantom => "fantom", + Chain::Polygon => "polygon", }; let uri = format!( "{}nft/{}/{}?chain={}&{}", @@ -144,8 +150,11 @@ pub async fn get_nft_transfers(ctx: MmArc, req: NftTransfersReq) -> MmResult ("AVAX", "avalanche"), Chain::Bsc => ("BNB", "bsc"), Chain::Eth => ("ETH", "eth"), + Chain::Fantom => ("FTM", "fantom"), + Chain::Polygon => ("MATIC", "polygon"), }; let my_address = get_eth_address(&ctx, coin_str).await?; let uri_without_cursor = format!( diff --git a/mm2src/coins/nft/nft_structs.rs b/mm2src/coins/nft/nft_structs.rs index 60aeb630be..f095c942ed 100644 --- a/mm2src/coins/nft/nft_structs.rs +++ b/mm2src/coins/nft/nft_structs.rs @@ -19,8 +19,11 @@ pub struct NftMetadataReq { #[derive(Clone, Copy, Debug, Deserialize, Serialize)] #[serde(rename_all = "UPPERCASE")] pub(crate) enum Chain { + Avalanche, Bsc, Eth, + Fantom, + Polygon, } #[derive(Debug, Display)] From 2bc8cf658070231fef3866c5dc125ff3d79d6409 Mon Sep 17 00:00:00 2001 From: laruh Date: Sun, 5 Mar 2023 17:57:56 +0700 Subject: [PATCH 51/66] simplify code --- mm2src/coins/eth.rs | 101 ++++++++++++++++---------------- mm2src/coins/lp_coins.rs | 25 +++++++- mm2src/coins/nft.rs | 15 ++--- mm2src/coins/nft/nft_structs.rs | 28 +++++++++ 4 files changed, 105 insertions(+), 64 deletions(-) diff --git a/mm2src/coins/eth.rs b/mm2src/coins/eth.rs index 192b6af1f0..aeac4c800e 100644 --- a/mm2src/coins/eth.rs +++ b/mm2src/coins/eth.rs @@ -22,7 +22,8 @@ // use super::eth::Action::{Call, Create}; #[cfg(feature = "enable-nft-integration")] -use crate::nft::nft_structs::{Chain, ContractType, NftListReq, TransactionNftDetails, WithdrawErc1155, WithdrawErc721}; +use crate::nft::nft_structs::{ContractType, ConvertChain, NftListReq, TransactionNftDetails, WithdrawErc1155, + WithdrawErc721}; use async_trait::async_trait; use bitcrypto::{keccak256, ripemd160, sha256}; use common::custom_futures::repeatable::{Ready, Retry}; @@ -891,31 +892,9 @@ async fn withdraw_impl(coin: EthCoin, req: WithdrawRequest) -> WithdrawResult { #[cfg(feature = "enable-nft-integration")] pub async fn withdraw_erc1155(ctx: MmArc, req: WithdrawErc1155) -> WithdrawNftResult { - let ticker = match req.chain { - Chain::Avalanche => "AVAX", - Chain::Bsc => "BNB", - Chain::Eth => "ETH", - Chain::Fantom => "FTM", - Chain::Polygon => "MATIC", - }; - let coin = lp_coinfind_or_err(&ctx, ticker).await?; - let eth_coin = match coin { - MmCoinEnum::EthCoin(eth_coin) => eth_coin, - _ => { - return MmError::err(WithdrawError::CoinDoesntSupportNftWithdraw { - coin: coin.ticker().to_owned(), - }) - }, - }; - let from_addr = valid_addr_from_str(&req.from).map_to_mm(WithdrawError::InvalidAddress)?; - if eth_coin.my_address != from_addr { - return MmError::err(WithdrawError::AddressMismatchError { - my_address: eth_coin.my_address.to_string(), - from: req.from, - }); - } - let to_addr = valid_addr_from_str(&req.to).map_to_mm(WithdrawError::InvalidAddress)?; - let token_addr = addr_from_str(&req.token_address).map_to_mm(WithdrawError::InvalidAddress)?; + let coin = lp_coinfind_or_err(&ctx, &req.chain.to_ticker()).await?; + let (from_addr, to_addr, token_addr, eth_coin) = + get_valid_eth_withdraw_addresses(coin, &req.from, &req.to, &req.token_address)?; // todo check amount in nft cache, instead of sending new moralis req // dont use `get_nft_metadata` for erc1155, it can return info related to other owner. @@ -1030,31 +1009,9 @@ pub async fn withdraw_erc1155(ctx: MmArc, req: WithdrawErc1155) -> WithdrawNftRe #[cfg(feature = "enable-nft-integration")] pub async fn withdraw_erc721(ctx: MmArc, req: WithdrawErc721) -> WithdrawNftResult { - let ticker = match req.chain { - Chain::Avalanche => "AVAX", - Chain::Bsc => "BNB", - Chain::Eth => "ETH", - Chain::Fantom => "FTM", - Chain::Polygon => "MATIC", - }; - let coin = lp_coinfind_or_err(&ctx, ticker).await?; - let eth_coin = match coin { - MmCoinEnum::EthCoin(eth_coin) => eth_coin, - _ => { - return MmError::err(WithdrawError::CoinDoesntSupportNftWithdraw { - coin: coin.ticker().to_owned(), - }) - }, - }; - let from_addr = valid_addr_from_str(&req.from).map_to_mm(WithdrawError::InvalidAddress)?; - if eth_coin.my_address != from_addr { - return MmError::err(WithdrawError::AddressMismatchError { - my_address: eth_coin.my_address.to_string(), - from: req.from, - }); - } - let to_addr = valid_addr_from_str(&req.to).map_to_mm(WithdrawError::InvalidAddress)?; - let token_addr = addr_from_str(&req.token_address).map_to_mm(WithdrawError::InvalidAddress)?; + let coin = lp_coinfind_or_err(&ctx, &req.chain.to_ticker()).await?; + let (from_addr, to_addr, token_addr, eth_coin) = + get_valid_eth_withdraw_addresses(coin, &req.from, &req.to, &req.token_address)?; let (eth_value, data, call_addr, fee_coin) = match eth_coin.coin_type { EthCoinType::Eth => { let function = ERC721_CONTRACT.function("safeTransferFrom")?; @@ -5059,3 +5016,45 @@ pub async fn get_eth_address(ctx: &MmArc, ticker: &str) -> MmResult MmResult<(Address, Address, Address, EthCoin), GetValidEthWithdrawAddError> { + let eth_coin = match coin_enum { + MmCoinEnum::EthCoin(eth_coin) => eth_coin, + _ => { + return MmError::err(GetValidEthWithdrawAddError::CoinDoesntSupportNftWithdraw { + coin: coin_enum.ticker().to_owned(), + }) + }, + }; + let from_addr = valid_addr_from_str(from).map_err(GetValidEthWithdrawAddError::InvalidAddress)?; + if eth_coin.my_address != from_addr { + return MmError::err(GetValidEthWithdrawAddError::AddressMismatchError { + my_address: eth_coin.my_address.to_string(), + from: from.to_string(), + }); + } + let to_addr = valid_addr_from_str(to).map_err(GetValidEthWithdrawAddError::InvalidAddress)?; + let token_addr = addr_from_str(token_add).map_err(GetValidEthWithdrawAddError::InvalidAddress)?; + Ok((from_addr, to_addr, token_addr, eth_coin)) +} diff --git a/mm2src/coins/lp_coins.rs b/mm2src/coins/lp_coins.rs index a2f0f0003f..2cace08db0 100644 --- a/mm2src/coins/lp_coins.rs +++ b/mm2src/coins/lp_coins.rs @@ -200,6 +200,8 @@ use coin_errors::{MyAddressError, ValidatePaymentError, ValidatePaymentFut}; pub mod coins_tests; pub mod eth; +#[cfg(feature = "enable-nft-integration")] +use eth::GetValidEthWithdrawAddError; use eth::{eth_coin_from_conf_and_request, get_eth_address, EthCoin, EthTxFeeDetails, GetEthAddressError, SignedEthTx}; pub mod hd_confirm_address; @@ -1778,8 +1780,10 @@ pub enum WithdrawError { #[from_stringify("NumConversError", "UnexpectedDerivationMethod", "PrivKeyPolicyNotAllowed")] #[display(fmt = "Internal error: {}", _0)] InternalError(String), + #[cfg(feature = "enable-nft-integration")] #[display(fmt = "{} coin doesn't support NFT withdrawing", coin)] CoinDoesntSupportNftWithdraw { coin: String }, + #[cfg(feature = "enable-nft-integration")] #[display(fmt = "My address {} and from address {} mismatch", my_address, from)] AddressMismatchError { my_address: String, from: String }, #[cfg(feature = "enable-nft-integration")] @@ -1824,16 +1828,16 @@ impl HttpStatusCode for WithdrawError { | WithdrawError::FromAddressNotFound | WithdrawError::UnexpectedFromAddress(_) | WithdrawError::UnknownAccount { .. } - | WithdrawError::UnexpectedUserAction { .. } - | WithdrawError::CoinDoesntSupportNftWithdraw { .. } - | WithdrawError::AddressMismatchError { .. } => StatusCode::BAD_REQUEST, + | WithdrawError::UnexpectedUserAction { .. } => StatusCode::BAD_REQUEST, WithdrawError::HwError(_) => StatusCode::GONE, #[cfg(target_arch = "wasm32")] WithdrawError::BroadcastExpected(_) => StatusCode::BAD_REQUEST, WithdrawError::Transport(_) | WithdrawError::InternalError(_) => StatusCode::INTERNAL_SERVER_ERROR, #[cfg(feature = "enable-nft-integration")] WithdrawError::GetNftInfoError(_) + | WithdrawError::AddressMismatchError { .. } | WithdrawError::ContractTypeDoesntSupportNftWithdrawing(_) + | WithdrawError::CoinDoesntSupportNftWithdraw { .. } | WithdrawError::NotEnoughNftsAmount { .. } => StatusCode::BAD_REQUEST, } } @@ -1869,6 +1873,21 @@ impl From for WithdrawError { fn from(e: TimeoutError) -> Self { WithdrawError::Timeout(e.duration) } } +#[cfg(feature = "enable-nft-integration")] +impl From for WithdrawError { + fn from(e: GetValidEthWithdrawAddError) -> Self { + match e { + GetValidEthWithdrawAddError::AddressMismatchError { my_address, from } => { + WithdrawError::AddressMismatchError { my_address, from } + }, + GetValidEthWithdrawAddError::CoinDoesntSupportNftWithdraw { coin } => { + WithdrawError::CoinDoesntSupportNftWithdraw { coin } + }, + GetValidEthWithdrawAddError::InvalidAddress(e) => WithdrawError::InvalidAddress(e), + } + } +} + impl WithdrawError { /// Construct [`WithdrawError`] from [`GenerateTxError`] using additional `coin` and `decimals`. pub fn from_generate_tx_error(gen_tx_err: GenerateTxError, coin: String, decimals: u8) -> WithdrawError { diff --git a/mm2src/coins/nft.rs b/mm2src/coins/nft.rs index 16ee5e4c12..b23dad90f0 100644 --- a/mm2src/coins/nft.rs +++ b/mm2src/coins/nft.rs @@ -6,8 +6,9 @@ pub(crate) mod nft_structs; use crate::WithdrawError; use nft_errors::GetNftInfoError; -use nft_structs::{Chain, Nft, NftList, NftListReq, NftMetadataReq, NftTransferHistory, NftTransferHistoryWrapper, - NftTransfersReq, NftWrapper, NftsTransferHistoryList, TransactionNftDetails, WithdrawNftReq}; +use nft_structs::{Chain, ConvertChain, Nft, NftList, NftListReq, NftMetadataReq, NftTransferHistory, + NftTransferHistoryWrapper, NftTransfersReq, NftWrapper, NftsTransferHistoryList, + TransactionNftDetails, WithdrawNftReq}; use crate::eth::{get_eth_address, withdraw_erc1155, withdraw_erc721}; use common::{APPLICATION_JSON, X_API_KEY}; @@ -33,14 +34,8 @@ pub async fn get_nft_list(ctx: MmArc, req: NftListReq) -> MmResult ("AVAX", "avalanche"), - Chain::Bsc => ("BNB", "bsc"), - Chain::Eth => ("ETH", "eth"), - Chain::Fantom => ("FTM", "fantom"), - Chain::Polygon => ("MATIC", "polygon"), - }; - let my_address = get_eth_address(&ctx, coin_str).await?; + let (coin_str, chain_str) = chain.to_ticker_chain(); + let my_address = get_eth_address(&ctx, &coin_str).await?; let uri_without_cursor = format!( "{}{}/nft?chain={}&{}", URL_MORALIS, my_address.wallet_address, chain_str, FORMAT_DECIMAL_MORALIS diff --git a/mm2src/coins/nft/nft_structs.rs b/mm2src/coins/nft/nft_structs.rs index f095c942ed..b3fdc335a4 100644 --- a/mm2src/coins/nft/nft_structs.rs +++ b/mm2src/coins/nft/nft_structs.rs @@ -26,6 +26,34 @@ pub(crate) enum Chain { Polygon, } +pub(crate) trait ConvertChain { + fn to_ticker(&self) -> String; + + fn to_ticker_chain(&self) -> (String, String); +} + +impl ConvertChain for Chain { + fn to_ticker(&self) -> String { + 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(), + } + } + + fn to_ticker_chain(&self) -> (String, String) { + match self { + Chain::Avalanche => ("AVAX".to_owned(), "avalanche".to_owned()), + Chain::Bsc => ("BNB".to_owned(), "bsc".to_owned()), + Chain::Eth => ("ETH".to_owned(), "eth".to_owned()), + Chain::Fantom => ("FTM".to_owned(), "fantom".to_owned()), + Chain::Polygon => ("MATIC".to_owned(), "polygon".to_owned()), + } + } +} + #[derive(Debug, Display)] pub(crate) enum ParseContractTypeError { UnsupportedContractType, From e57eda9e984292f70ef42dedff39e5e182703f50 Mon Sep 17 00:00:00 2001 From: laruh Date: Sun, 5 Mar 2023 18:05:19 +0700 Subject: [PATCH 52/66] amount_dec in tx details --- mm2src/coins/eth.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mm2src/coins/eth.rs b/mm2src/coins/eth.rs index aeac4c800e..e000eb5a2c 100644 --- a/mm2src/coins/eth.rs +++ b/mm2src/coins/eth.rs @@ -997,7 +997,7 @@ pub async fn withdraw_erc1155(ctx: MmArc, req: WithdrawErc1155) -> WithdrawNftRe contract_type: ContractType::Erc1155, token_address: req.token_address, token_id: req.token_id, - amount: 1.into(), + amount: amount_dec, fee_details: Some(fee_details.into()), coin: eth_coin.ticker.clone(), block_height: 0, From eb41afb15354005ee678bd22875489e1970ebe87 Mon Sep 17 00:00:00 2001 From: laruh Date: Tue, 7 Mar 2023 21:27:35 +0700 Subject: [PATCH 53/66] add get_eth_nft_gas_details --- mm2src/coins/eth.rs | 127 ++++++++++++++++++++++----------------- mm2src/coins/lp_coins.rs | 15 ++++- 2 files changed, 84 insertions(+), 58 deletions(-) diff --git a/mm2src/coins/eth.rs b/mm2src/coins/eth.rs index e000eb5a2c..5b3b488368 100644 --- a/mm2src/coins/eth.rs +++ b/mm2src/coins/eth.rs @@ -35,6 +35,8 @@ use common::{get_utc_timestamp, now_ms, small_rng, DEX_FEE_ADDR_RAW_PUBKEY}; use crypto::privkey::key_pair_from_secret; use crypto::{CryptoCtx, CryptoCtxError, GlobalHDAccountArc, KeyPairPolicy}; use derive_more::Display; +#[cfg(feature = "enable-nft-integration")] +use enum_from::EnumFromStringify; use ethabi::{Contract, Function, Token}; pub use ethcore_transaction::SignedTransaction as SignedEthTx; use ethcore_transaction::{Action, Transaction as UnSignedEthTx, UnverifiedTransaction}; @@ -942,34 +944,8 @@ pub async fn withdraw_erc1155(ctx: MmArc, req: WithdrawErc1155) -> WithdrawNftRe )) }, }; - let (gas, gas_price) = match req.fee { - Some(WithdrawFee::EthGas { gas_price, gas }) => { - let gas_price = wei_from_big_decimal(&gas_price, 9)?; - (gas.into(), gas_price) - }, - Some(fee_policy) => { - let error = format!("Expected 'EthGas' fee type, found {:?}", fee_policy); - return MmError::err(WithdrawError::InvalidFeePolicy(error)); - }, - None => { - let gas_price = eth_coin.get_gas_price().compat().await?; - let estimate_gas_req = CallRequest { - value: Some(eth_value), - data: Some(data.clone().into()), - from: Some(eth_coin.my_address), - to: Some(call_addr), - gas: None, - // gas price must be supplied because some smart contracts base their - // logic on gas price, e.g. TUSD: https://github.com/KomodoPlatform/atomicDEX-API/issues/643 - gas_price: Some(gas_price), - ..CallRequest::default() - }; - // Note if the wallet's balance is insufficient to withdraw, then `estimate_gas` may fail with the `Exception` error. - // Ideally we should determine the case when we have the insufficient balance and return `WithdrawError::NotSufficientBalance`. - let gas_limit = eth_coin.estimate_gas(estimate_gas_req).compat().await?; - (gas_limit, gas_price) - }, - }; + let (gas, gas_price) = + get_eth_nft_gas_details(ð_coin, req.fee, eth_value, data.clone().into(), call_addr).await?; let _nonce_lock = eth_coin.nonce_lock.lock().await; let nonce = get_addr_nonce(eth_coin.my_address, eth_coin.web3_instances.clone()) .compat() @@ -1031,34 +1007,8 @@ pub async fn withdraw_erc721(ctx: MmArc, req: WithdrawErc721) -> WithdrawNftResu )) }, }; - let (gas, gas_price) = match req.fee { - Some(WithdrawFee::EthGas { gas_price, gas }) => { - let gas_price = wei_from_big_decimal(&gas_price, 9)?; - (gas.into(), gas_price) - }, - Some(fee_policy) => { - let error = format!("Expected 'EthGas' fee type, found {:?}", fee_policy); - return MmError::err(WithdrawError::InvalidFeePolicy(error)); - }, - None => { - let gas_price = eth_coin.get_gas_price().compat().await?; - let estimate_gas_req = CallRequest { - value: Some(eth_value), - data: Some(data.clone().into()), - from: Some(eth_coin.my_address), - to: Some(call_addr), - gas: None, - // gas price must be supplied because some smart contracts base their - // logic on gas price, e.g. TUSD: https://github.com/KomodoPlatform/atomicDEX-API/issues/643 - gas_price: Some(gas_price), - ..CallRequest::default() - }; - // Note if the wallet's balance is insufficient to withdraw, then `estimate_gas` may fail with the `Exception` error. - // Ideally we should determine the case when we have the insufficient balance and return `WithdrawError::NotSufficientBalance`. - let gas_limit = eth_coin.estimate_gas(estimate_gas_req).compat().await?; - (gas_limit, gas_price) - }, - }; + let (gas, gas_price) = + get_eth_nft_gas_details(ð_coin, req.fee, eth_value, data.clone().into(), call_addr).await?; let _nonce_lock = eth_coin.nonce_lock.lock().await; let nonce = get_addr_nonce(eth_coin.my_address, eth_coin.web3_instances.clone()) .compat() @@ -5058,3 +5008,68 @@ fn get_valid_eth_withdraw_addresses( let token_addr = addr_from_str(token_add).map_err(GetValidEthWithdrawAddError::InvalidAddress)?; Ok((from_addr, to_addr, token_addr, eth_coin)) } + +#[cfg(feature = "enable-nft-integration")] +#[derive(Clone, Debug, Deserialize, Display, EnumFromStringify, PartialEq, Serialize)] +pub enum EthNftGasDetailsErr { + #[display(fmt = "Invalid fee policy: {}", _0)] + InvalidFeePolicy(String), + #[from_stringify("NumConversError")] + #[display(fmt = "Internal error: {}", _0)] + Internal(String), + #[display(fmt = "Transport: {}", _0)] + Transport(String), +} + +#[cfg(feature = "enable-nft-integration")] +impl From for EthNftGasDetailsErr { + fn from(e: web3::Error) -> Self { EthNftGasDetailsErr::from(Web3RpcError::from(e)) } +} + +#[cfg(feature = "enable-nft-integration")] +impl From for EthNftGasDetailsErr { + fn from(e: Web3RpcError) -> Self { + match e { + Web3RpcError::Transport(tr) | Web3RpcError::InvalidResponse(tr) => EthNftGasDetailsErr::Transport(tr), + Web3RpcError::Internal(internal) => EthNftGasDetailsErr::Internal(internal), + } + } +} + +#[cfg(feature = "enable-nft-integration")] +async fn get_eth_nft_gas_details( + eth_coin: &EthCoin, + fee: Option, + eth_value: U256, + data: Bytes, + call_addr: Address, +) -> MmResult<(U256, U256), EthNftGasDetailsErr> { + match fee { + Some(WithdrawFee::EthGas { gas_price, gas }) => { + let gas_price = wei_from_big_decimal(&gas_price, 9)?; + Ok((gas.into(), gas_price)) + }, + Some(fee_policy) => { + let error = format!("Expected 'EthGas' fee type, found {:?}", fee_policy); + MmError::err(EthNftGasDetailsErr::InvalidFeePolicy(error)) + }, + None => { + let gas_price = eth_coin.get_gas_price().compat().await?; + let estimate_gas_req = CallRequest { + value: Some(eth_value), + data: Some(data), + from: Some(eth_coin.my_address), + to: Some(call_addr), + gas: None, + // gas price must be supplied because some smart contracts base their + // logic on gas price, e.g. TUSD: https://github.com/KomodoPlatform/atomicDEX-API/issues/643 + gas_price: Some(gas_price), + ..CallRequest::default() + }; + // Note if the wallet's balance is insufficient to withdraw, then `estimate_gas` may fail with the `Exception` error. + // Ideally we should determine the case when we have the insufficient balance and return `WithdrawError::NotSufficientBalance`. + let gas_limit = eth_coin.estimate_gas(estimate_gas_req).compat().await?; + Ok((gas_limit, gas_price)) + }, + } +} diff --git a/mm2src/coins/lp_coins.rs b/mm2src/coins/lp_coins.rs index 2cace08db0..89e23ddbb1 100644 --- a/mm2src/coins/lp_coins.rs +++ b/mm2src/coins/lp_coins.rs @@ -200,9 +200,9 @@ use coin_errors::{MyAddressError, ValidatePaymentError, ValidatePaymentFut}; pub mod coins_tests; pub mod eth; -#[cfg(feature = "enable-nft-integration")] -use eth::GetValidEthWithdrawAddError; use eth::{eth_coin_from_conf_and_request, get_eth_address, EthCoin, EthTxFeeDetails, GetEthAddressError, SignedEthTx}; +#[cfg(feature = "enable-nft-integration")] +use eth::{EthNftGasDetailsErr, GetValidEthWithdrawAddError}; pub mod hd_confirm_address; pub mod hd_pubkey; @@ -1888,6 +1888,17 @@ impl From for WithdrawError { } } +#[cfg(feature = "enable-nft-integration")] +impl From for WithdrawError { + fn from(e: EthNftGasDetailsErr) -> Self { + match e { + EthNftGasDetailsErr::InvalidFeePolicy(e) => WithdrawError::InvalidFeePolicy(e), + EthNftGasDetailsErr::Internal(e) => WithdrawError::InternalError(e), + EthNftGasDetailsErr::Transport(e) => WithdrawError::Transport(e), + } + } +} + impl WithdrawError { /// Construct [`WithdrawError`] from [`GenerateTxError`] using additional `coin` and `decimals`. pub fn from_generate_tx_error(gen_tx_err: GenerateTxError, coin: String, decimals: u8) -> WithdrawError { From afe09e89d6c46e7c2ebc558116d72069268386ef Mon Sep 17 00:00:00 2001 From: laruh Date: Wed, 8 Mar 2023 17:18:31 +0700 Subject: [PATCH 54/66] add derive Clone, PartialEq --- mm2src/coins/nft/nft_errors.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mm2src/coins/nft/nft_errors.rs b/mm2src/coins/nft/nft_errors.rs index 61f9cb7fb6..beba19c513 100644 --- a/mm2src/coins/nft/nft_errors.rs +++ b/mm2src/coins/nft/nft_errors.rs @@ -7,7 +7,7 @@ use mm2_net::transport::SlurpError; use serde::{Deserialize, Serialize}; use web3::Error; -#[derive(Debug, Deserialize, Display, EnumFromStringify, Serialize, SerializeErrorType)] +#[derive(Clone, Debug, Deserialize, Display, EnumFromStringify, PartialEq, Serialize, SerializeErrorType)] #[serde(tag = "error_type", content = "error_data")] pub enum GetNftInfoError { /// `http::Error` can appear on an HTTP request [`http::Builder::build`] building. From 2344ebb4cf8e040c84a67670a220f14014792163 Mon Sep 17 00:00:00 2001 From: laruh Date: Wed, 8 Mar 2023 20:44:59 +0700 Subject: [PATCH 55/66] doc comments --- mm2src/coins/eth.rs | 4 ++++ mm2src/coins/nft.rs | 10 ++++++---- mm2src/coins/nft/nft_structs.rs | 2 +- 3 files changed, 11 insertions(+), 5 deletions(-) diff --git a/mm2src/coins/eth.rs b/mm2src/coins/eth.rs index e318cfbcd6..57c831de36 100644 --- a/mm2src/coins/eth.rs +++ b/mm2src/coins/eth.rs @@ -893,6 +893,8 @@ async fn withdraw_impl(coin: EthCoin, req: WithdrawRequest) -> WithdrawResult { } #[cfg(feature = "enable-nft-integration")] +/// `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, req: WithdrawErc1155) -> WithdrawNftResult { let coin = lp_coinfind_or_err(&ctx, &req.chain.to_ticker()).await?; let (from_addr, to_addr, token_addr, eth_coin) = @@ -984,6 +986,8 @@ pub async fn withdraw_erc1155(ctx: MmArc, req: WithdrawErc1155) -> WithdrawNftRe } #[cfg(feature = "enable-nft-integration")] +/// `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, req: WithdrawErc721) -> WithdrawNftResult { let coin = lp_coinfind_or_err(&ctx, &req.chain.to_ticker()).await?; let (from_addr, to_addr, token_addr, eth_coin) = diff --git a/mm2src/coins/nft.rs b/mm2src/coins/nft.rs index b23dad90f0..d9e741337c 100644 --- a/mm2src/coins/nft.rs +++ b/mm2src/coins/nft.rs @@ -25,7 +25,7 @@ const DIRECTION_BOTH_MORALIS: &str = "direction=both"; pub type WithdrawNftResult = Result>; -/// `get_nft_list` function returns list of NFTs on ETH or/and BNB chains owned by user. +/// `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 { let api_key = ctx.conf["api_key"] .as_str() @@ -93,9 +93,10 @@ pub async fn get_nft_list(ctx: MmArc, req: NftListReq) -> MmResult MmResult { let api_key = ctx.conf["api_key"] .as_str() @@ -134,7 +135,7 @@ pub async fn get_nft_metadata(ctx: MmArc, req: NftMetadataReq) -> MmResult MmResult { let api_key = ctx.conf["api_key"] @@ -271,6 +272,7 @@ async fn send_moralis_request(uri: &str, api_key: &str) -> MmResult Date: Thu, 9 Mar 2023 22:43:49 +0700 Subject: [PATCH 56/66] fix merge conflict --- mm2src/coins/lp_coins.rs | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/mm2src/coins/lp_coins.rs b/mm2src/coins/lp_coins.rs index cdfb982370..ccab92efb0 100644 --- a/mm2src/coins/lp_coins.rs +++ b/mm2src/coins/lp_coins.rs @@ -1843,10 +1843,7 @@ impl HttpStatusCode for WithdrawError { | WithdrawError::UnexpectedFromAddress(_) | WithdrawError::UnknownAccount { .. } | WithdrawError::UnexpectedUserAction { .. } - | WithdrawError::CoinDoesntSupportNftWithdraw { .. } - | WithdrawError::AddressMismatchError { .. } - | WithdrawError::ActionNotAllowed(_) - | WithdrawError::ContractTypeDoesntSupportNftWithdrawing(_) => StatusCode::BAD_REQUEST, + | WithdrawError::ActionNotAllowed(_) => StatusCode::BAD_REQUEST, WithdrawError::HwError(_) => StatusCode::GONE, #[cfg(target_arch = "wasm32")] WithdrawError::BroadcastExpected(_) => StatusCode::BAD_REQUEST, From b6b26d6c7e9a6c3e208441e63de1dddc0721253a Mon Sep 17 00:00:00 2001 From: laruh Date: Wed, 15 Mar 2023 14:05:23 +0700 Subject: [PATCH 57/66] TokenNotFoundInWallet --- mm2src/coins/nft.rs | 2 +- mm2src/coins/nft/nft_errors.rs | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/mm2src/coins/nft.rs b/mm2src/coins/nft.rs index d9e741337c..f2126ab155 100644 --- a/mm2src/coins/nft.rs +++ b/mm2src/coins/nft.rs @@ -285,7 +285,7 @@ pub(crate) async fn find_wallet_amount( return Ok(nft.amount); } } - MmError::err(GetNftInfoError::TokenNotFindInWallet { + MmError::err(GetNftInfoError::TokenNotFoundInWallet { token_address: token_address_req, token_id: token_id_req.to_string(), }) diff --git a/mm2src/coins/nft/nft_errors.rs b/mm2src/coins/nft/nft_errors.rs index beba19c513..bd510108ab 100644 --- a/mm2src/coins/nft/nft_errors.rs +++ b/mm2src/coins/nft/nft_errors.rs @@ -29,7 +29,7 @@ pub enum GetNftInfoError { token_address, token_id )] - TokenNotFindInWallet { + TokenNotFoundInWallet { token_address: String, token_id: String, }, @@ -72,7 +72,7 @@ impl HttpStatusCode for GetNftInfoError { GetNftInfoError::Transport(_) | GetNftInfoError::Internal(_) | GetNftInfoError::GetEthAddressError(_) - | GetNftInfoError::TokenNotFindInWallet { .. } => StatusCode::INTERNAL_SERVER_ERROR, + | GetNftInfoError::TokenNotFoundInWallet { .. } => StatusCode::INTERNAL_SERVER_ERROR, } } } From e70c8c07f5c34f5b58c3ae69a4bd8c413f1907c8 Mon Sep 17 00:00:00 2001 From: laruh Date: Wed, 15 Mar 2023 17:39:09 +0700 Subject: [PATCH 58/66] use eth_coin.my_address()? --- mm2src/coins/eth.rs | 28 ++++++++++------------------ mm2src/coins/nft/nft_structs.rs | 2 -- 2 files changed, 10 insertions(+), 20 deletions(-) diff --git a/mm2src/coins/eth.rs b/mm2src/coins/eth.rs index 463d3815dc..4113af0c3c 100644 --- a/mm2src/coins/eth.rs +++ b/mm2src/coins/eth.rs @@ -897,8 +897,8 @@ async fn withdraw_impl(coin: EthCoin, req: WithdrawRequest) -> WithdrawResult { /// which should be sent to`send_raw_transaction` RPC to broadcast the transaction. pub async fn withdraw_erc1155(ctx: MmArc, req: WithdrawErc1155) -> WithdrawNftResult { let coin = lp_coinfind_or_err(&ctx, &req.chain.to_ticker()).await?; - let (from_addr, to_addr, token_addr, eth_coin) = - get_valid_eth_withdraw_addresses(coin, &req.from, &req.to, &req.token_address)?; + let (to_addr, token_addr, eth_coin) = get_valid_eth_withdraw_addresses(coin, &req.to, &req.token_address)?; + let my_address = eth_coin.my_address()?; // todo check amount in nft cache, instead of sending new moralis req // dont use `get_nft_metadata` for erc1155, it can return info related to other owner. @@ -932,7 +932,7 @@ pub async fn withdraw_erc1155(ctx: MmArc, req: WithdrawErc1155) -> WithdrawNftRe .map_err(|e| format!("{:?}", e)) .map_to_mm(NumConversError::new)?; let data = function.encode_input(&[ - Token::Address(from_addr), + Token::Address(eth_coin.my_address), Token::Address(to_addr), Token::Uint(token_id_u256), Token::Uint(amount_u256), @@ -970,7 +970,7 @@ pub async fn withdraw_erc1155(ctx: MmArc, req: WithdrawErc1155) -> WithdrawNftRe Ok(TransactionNftDetails { tx_hex: BytesJson::from(signed_bytes.to_vec()), tx_hash: format!("{:02x}", signed.tx_hash()), - from: vec![req.from], + from: vec![my_address], to: vec![req.to], contract_type: ContractType::Erc1155, token_address: req.token_address, @@ -990,8 +990,8 @@ pub async fn withdraw_erc1155(ctx: MmArc, req: WithdrawErc1155) -> WithdrawNftRe /// which should be sent to`send_raw_transaction` RPC to broadcast the transaction. pub async fn withdraw_erc721(ctx: MmArc, req: WithdrawErc721) -> WithdrawNftResult { let coin = lp_coinfind_or_err(&ctx, &req.chain.to_ticker()).await?; - let (from_addr, to_addr, token_addr, eth_coin) = - get_valid_eth_withdraw_addresses(coin, &req.from, &req.to, &req.token_address)?; + let (to_addr, token_addr, eth_coin) = get_valid_eth_withdraw_addresses(coin, &req.to, &req.token_address)?; + let my_address = eth_coin.my_address()?; let (eth_value, data, call_addr, fee_coin) = match eth_coin.coin_type { EthCoinType::Eth => { let function = ERC721_CONTRACT.function("safeTransferFrom")?; @@ -999,7 +999,7 @@ pub async fn withdraw_erc721(ctx: MmArc, req: WithdrawErc721) -> WithdrawNftResu .map_err(|e| format!("{:?}", e)) .map_to_mm(NumConversError::new)?; let data = function.encode_input(&[ - Token::Address(from_addr), + Token::Address(eth_coin.my_address), Token::Address(to_addr), Token::Uint(token_id_u256), ])?; @@ -1035,7 +1035,7 @@ pub async fn withdraw_erc721(ctx: MmArc, req: WithdrawErc721) -> WithdrawNftResu Ok(TransactionNftDetails { tx_hex: BytesJson::from(signed_bytes.to_vec()), tx_hash: format!("{:02x}", signed.tx_hash()), - from: vec![req.from], + from: vec![my_address], to: vec![req.to], contract_type: ContractType::Erc721, token_address: req.token_address, @@ -5077,10 +5077,9 @@ pub enum GetValidEthWithdrawAddError { #[cfg(feature = "enable-nft-integration")] fn get_valid_eth_withdraw_addresses( coin_enum: MmCoinEnum, - from: &str, to: &str, token_add: &str, -) -> MmResult<(Address, Address, Address, EthCoin), GetValidEthWithdrawAddError> { +) -> MmResult<(Address, Address, EthCoin), GetValidEthWithdrawAddError> { let eth_coin = match coin_enum { MmCoinEnum::EthCoin(eth_coin) => eth_coin, _ => { @@ -5089,16 +5088,9 @@ fn get_valid_eth_withdraw_addresses( }) }, }; - let from_addr = valid_addr_from_str(from).map_err(GetValidEthWithdrawAddError::InvalidAddress)?; - if eth_coin.my_address != from_addr { - return MmError::err(GetValidEthWithdrawAddError::AddressMismatchError { - my_address: eth_coin.my_address.to_string(), - from: from.to_string(), - }); - } let to_addr = valid_addr_from_str(to).map_err(GetValidEthWithdrawAddError::InvalidAddress)?; let token_addr = addr_from_str(token_add).map_err(GetValidEthWithdrawAddError::InvalidAddress)?; - Ok((from_addr, to_addr, token_addr, eth_coin)) + Ok((to_addr, token_addr, eth_coin)) } #[cfg(feature = "enable-nft-integration")] diff --git a/mm2src/coins/nft/nft_structs.rs b/mm2src/coins/nft/nft_structs.rs index 37ec214341..ac8a9d53df 100644 --- a/mm2src/coins/nft/nft_structs.rs +++ b/mm2src/coins/nft/nft_structs.rs @@ -152,7 +152,6 @@ pub struct NftList { #[derive(Clone, Deserialize)] pub struct WithdrawErc1155 { pub(crate) chain: Chain, - pub(crate) from: String, pub(crate) to: String, pub(crate) token_address: String, pub(crate) token_id: BigDecimal, @@ -165,7 +164,6 @@ pub struct WithdrawErc1155 { #[derive(Clone, Deserialize)] pub struct WithdrawErc721 { pub(crate) chain: Chain, - pub(crate) from: String, pub(crate) to: String, pub(crate) token_address: String, pub(crate) token_id: BigDecimal, From ef3fc73174fb8a23094ab60de25b26c01c357ba6 Mon Sep 17 00:00:00 2001 From: laruh Date: Wed, 15 Mar 2023 21:40:34 +0700 Subject: [PATCH 59/66] add entry in the changelog file --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index c8d7435b61..b01de17506 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,7 @@ **Features:** - NFT integration `WIP` [#900](https://github.com/KomodoPlatform/atomicDEX-API/issues/900) - NFT integration PoC added. Includes ERC721 support for ETH and BSC [#1652](https://github.com/KomodoPlatform/atomicDEX-API/pull/1652) + - Withdraw ERC1155 and EVM based chains support added for NFT PoC [#1704](https://github.com/KomodoPlatform/atomicDEX-API/pull/1704) - Swap watcher nodes [#1431](https://github.com/KomodoPlatform/atomicDEX-API/issues/1431) - Watcher rewards for ETH swaps were added [#1658](https://github.com/KomodoPlatform/atomicDEX-API/pull/1658) - Cosmos integration `WIP` [#1432](https://github.com/KomodoPlatform/atomicDEX-API/issues/1432) From fa8987e3c115f1d542769a3d0ae57b0770ab2c4e Mon Sep 17 00:00:00 2001 From: laruh Date: Thu, 16 Mar 2023 17:58:38 +0700 Subject: [PATCH 60/66] use get_eth_gas_details for nft and fungible tokens --- mm2src/coins/eth.rs | 106 +++++++++++++++++++-------------------- mm2src/coins/lp_coins.rs | 16 +++--- 2 files changed, 59 insertions(+), 63 deletions(-) diff --git a/mm2src/coins/eth.rs b/mm2src/coins/eth.rs index 1abda3f7fe..b821709804 100644 --- a/mm2src/coins/eth.rs +++ b/mm2src/coins/eth.rs @@ -35,7 +35,6 @@ use common::{get_utc_timestamp, now_ms, small_rng, DEX_FEE_ADDR_RAW_PUBKEY}; use crypto::privkey::key_pair_from_secret; use crypto::{CryptoCtx, CryptoCtxError, GlobalHDAccountArc, KeyPairPolicy}; use derive_more::Display; -#[cfg(feature = "enable-nft-integration")] use enum_from::EnumFromStringify; use ethabi::{Contract, Function, Token}; pub use ethcore_transaction::SignedTransaction as SignedEthTx; @@ -749,40 +748,16 @@ async fn withdraw_impl(coin: EthCoin, req: WithdrawRequest) -> WithdrawResult { }; let eth_value_dec = u256_to_big_decimal(eth_value, coin.decimals)?; - let (gas, gas_price) = match req.fee { - Some(WithdrawFee::EthGas { gas_price, gas }) => { - let gas_price = wei_from_big_decimal(&gas_price, 9)?; - (gas.into(), gas_price) - }, - Some(fee_policy) => { - let error = format!("Expected 'EthGas' fee type, found {:?}", fee_policy); - return MmError::err(WithdrawError::InvalidFeePolicy(error)); - }, - None => { - let gas_price = coin.get_gas_price().compat().await?; - // covering edge case by deducting the standard transfer fee when we want to max withdraw ETH - let eth_value_for_estimate = if req.max && coin.coin_type == EthCoinType::Eth { - eth_value - gas_price * U256::from(21000) - } else { - eth_value - }; - let estimate_gas_req = CallRequest { - value: Some(eth_value_for_estimate), - data: Some(data.clone().into()), - from: Some(coin.my_address), - to: Some(call_addr), - gas: None, - // gas price must be supplied because some smart contracts base their - // logic on gas price, e.g. TUSD: https://github.com/KomodoPlatform/atomicDEX-API/issues/643 - gas_price: Some(gas_price), - ..CallRequest::default() - }; - // TODO Note if the wallet's balance is insufficient to withdraw, then `estimate_gas` may fail with the `Exception` error. - // TODO Ideally we should determine the case when we have the insufficient balance and return `WithdrawError::NotSufficientBalance`. - let gas_limit = coin.estimate_gas(estimate_gas_req).compat().await?; - (gas_limit, gas_price) - }, - }; + let (gas, gas_price) = get_eth_gas_details( + &coin, + req.fee, + eth_value, + data.clone().into(), + call_addr, + false, + Some(req.max), + ) + .await?; let total_fee = gas * gas_price; let total_fee_dec = u256_to_big_decimal(total_fee, coin.decimals)?; @@ -946,8 +921,16 @@ pub async fn withdraw_erc1155(ctx: MmArc, req: WithdrawErc1155) -> WithdrawNftRe )) }, }; - let (gas, gas_price) = - get_eth_nft_gas_details(ð_coin, req.fee, eth_value, data.clone().into(), call_addr).await?; + let (gas, gas_price) = get_eth_gas_details( + ð_coin, + req.fee, + eth_value, + data.clone().into(), + call_addr, + true, + None, + ) + .await?; let _nonce_lock = eth_coin.nonce_lock.lock().await; let nonce = get_addr_nonce(eth_coin.my_address, eth_coin.web3_instances.clone()) .compat() @@ -1011,8 +994,16 @@ pub async fn withdraw_erc721(ctx: MmArc, req: WithdrawErc721) -> WithdrawNftResu )) }, }; - let (gas, gas_price) = - get_eth_nft_gas_details(ð_coin, req.fee, eth_value, data.clone().into(), call_addr).await?; + let (gas, gas_price) = get_eth_gas_details( + ð_coin, + req.fee, + eth_value, + data.clone().into(), + call_addr, + true, + None, + ) + .await?; let _nonce_lock = eth_coin.nonce_lock.lock().await; let nonce = get_addr_nonce(eth_coin.my_address, eth_coin.web3_instances.clone()) .compat() @@ -5125,9 +5116,8 @@ fn get_valid_eth_withdraw_addresses( Ok((to_addr, token_addr, eth_coin)) } -#[cfg(feature = "enable-nft-integration")] #[derive(Clone, Debug, Deserialize, Display, EnumFromStringify, PartialEq, Serialize)] -pub enum EthNftGasDetailsErr { +pub enum EthGasDetailsErr { #[display(fmt = "Invalid fee policy: {}", _0)] InvalidFeePolicy(String), #[from_stringify("NumConversError")] @@ -5137,29 +5127,28 @@ pub enum EthNftGasDetailsErr { Transport(String), } -#[cfg(feature = "enable-nft-integration")] -impl From for EthNftGasDetailsErr { - fn from(e: web3::Error) -> Self { EthNftGasDetailsErr::from(Web3RpcError::from(e)) } +impl From for EthGasDetailsErr { + fn from(e: web3::Error) -> Self { EthGasDetailsErr::from(Web3RpcError::from(e)) } } -#[cfg(feature = "enable-nft-integration")] -impl From for EthNftGasDetailsErr { +impl From for EthGasDetailsErr { fn from(e: Web3RpcError) -> Self { match e { - Web3RpcError::Transport(tr) | Web3RpcError::InvalidResponse(tr) => EthNftGasDetailsErr::Transport(tr), - Web3RpcError::Internal(internal) => EthNftGasDetailsErr::Internal(internal), + Web3RpcError::Transport(tr) | Web3RpcError::InvalidResponse(tr) => EthGasDetailsErr::Transport(tr), + Web3RpcError::Internal(internal) => EthGasDetailsErr::Internal(internal), } } } -#[cfg(feature = "enable-nft-integration")] -async fn get_eth_nft_gas_details( +async fn get_eth_gas_details( eth_coin: &EthCoin, fee: Option, eth_value: U256, data: Bytes, call_addr: Address, -) -> MmResult<(U256, U256), EthNftGasDetailsErr> { + nft: bool, + fungible_max: Option, +) -> MmResult<(U256, U256), EthGasDetailsErr> { match fee { Some(WithdrawFee::EthGas { gas_price, gas }) => { let gas_price = wei_from_big_decimal(&gas_price, 9)?; @@ -5167,12 +5156,19 @@ async fn get_eth_nft_gas_details( }, Some(fee_policy) => { let error = format!("Expected 'EthGas' fee type, found {:?}", fee_policy); - MmError::err(EthNftGasDetailsErr::InvalidFeePolicy(error)) + MmError::err(EthGasDetailsErr::InvalidFeePolicy(error)) }, None => { let gas_price = eth_coin.get_gas_price().compat().await?; + let mut eth_value_for_estimate = eth_value; + if !nft && fungible_max.is_some() { + // covering edge case by deducting the standard transfer fee when we want to max withdraw ETH + if fungible_max.unwrap() && eth_coin.coin_type == EthCoinType::Eth { + eth_value_for_estimate = eth_value - gas_price * U256::from(21000) + } + } let estimate_gas_req = CallRequest { - value: Some(eth_value), + value: Some(eth_value_for_estimate), data: Some(data), from: Some(eth_coin.my_address), to: Some(call_addr), @@ -5182,8 +5178,8 @@ async fn get_eth_nft_gas_details( gas_price: Some(gas_price), ..CallRequest::default() }; - // Note if the wallet's balance is insufficient to withdraw, then `estimate_gas` may fail with the `Exception` error. - // Ideally we should determine the case when we have the insufficient balance and return `WithdrawError::NotSufficientBalance`. + // TODO Note if the wallet's balance is insufficient to withdraw, then `estimate_gas` may fail with the `Exception` error. + // TODO Ideally we should determine the case when we have the insufficient balance and return `WithdrawError::NotSufficientBalance`. let gas_limit = eth_coin.estimate_gas(estimate_gas_req).compat().await?; Ok((gas_limit, gas_price)) }, diff --git a/mm2src/coins/lp_coins.rs b/mm2src/coins/lp_coins.rs index d2be869602..ff4352ed84 100644 --- a/mm2src/coins/lp_coins.rs +++ b/mm2src/coins/lp_coins.rs @@ -201,9 +201,10 @@ use coin_errors::{MyAddressError, ValidatePaymentError, ValidatePaymentFut}; pub mod coins_tests; pub mod eth; -use eth::{eth_coin_from_conf_and_request, get_eth_address, EthCoin, EthTxFeeDetails, GetEthAddressError, SignedEthTx}; #[cfg(feature = "enable-nft-integration")] -use eth::{EthNftGasDetailsErr, GetValidEthWithdrawAddError}; +use eth::GetValidEthWithdrawAddError; +use eth::{eth_coin_from_conf_and_request, get_eth_address, EthCoin, EthGasDetailsErr, EthTxFeeDetails, + GetEthAddressError, SignedEthTx}; pub mod hd_confirm_address; pub mod hd_pubkey; @@ -1904,13 +1905,12 @@ impl From for WithdrawError { } } -#[cfg(feature = "enable-nft-integration")] -impl From for WithdrawError { - fn from(e: EthNftGasDetailsErr) -> Self { +impl From for WithdrawError { + fn from(e: EthGasDetailsErr) -> Self { match e { - EthNftGasDetailsErr::InvalidFeePolicy(e) => WithdrawError::InvalidFeePolicy(e), - EthNftGasDetailsErr::Internal(e) => WithdrawError::InternalError(e), - EthNftGasDetailsErr::Transport(e) => WithdrawError::Transport(e), + EthGasDetailsErr::InvalidFeePolicy(e) => WithdrawError::InvalidFeePolicy(e), + EthGasDetailsErr::Internal(e) => WithdrawError::InternalError(e), + EthGasDetailsErr::Transport(e) => WithdrawError::Transport(e), } } } From e9245ce4c998e157837acbc7a30030b56329dc0f Mon Sep 17 00:00:00 2001 From: laruh Date: Fri, 17 Mar 2023 14:33:57 +0700 Subject: [PATCH 61/66] remove redundant attributes, count field, add type GasDetails, add iterator --- mm2src/coins/eth.rs | 5 +++-- mm2src/coins/nft.rs | 25 ++++++++++--------------- mm2src/coins/nft/nft_structs.rs | 2 -- 3 files changed, 13 insertions(+), 19 deletions(-) diff --git a/mm2src/coins/eth.rs b/mm2src/coins/eth.rs index b821709804..2b79063937 100644 --- a/mm2src/coins/eth.rs +++ b/mm2src/coins/eth.rs @@ -166,6 +166,7 @@ lazy_static! { pub type Web3RpcFut = Box> + Send>; pub type Web3RpcResult = Result>; pub type GasStationResult = Result>; +type GasDetails = (U256, U256); #[derive(Debug, Display)] pub enum GasStationReqErr { @@ -5083,7 +5084,7 @@ pub async fn get_eth_address(ctx: &MmArc, ticker: &str) -> MmResult, -) -> MmResult<(U256, U256), EthGasDetailsErr> { +) -> MmResult { match fee { Some(WithdrawFee::EthGas { gas_price, gas }) => { let gas_price = wei_from_big_decimal(&gas_price, 9)?; diff --git a/mm2src/coins/nft.rs b/mm2src/coins/nft.rs index f2126ab155..abc3aca4a0 100644 --- a/mm2src/coins/nft.rs +++ b/mm2src/coins/nft.rs @@ -82,10 +82,7 @@ pub async fn get_nft_list(ctx: MmArc, req: NftListReq) -> MmResult MmResult MmResult { - let nft_list = get_nft_list(ctx, nft_list).await?; - for nft in nft_list.nfts { - if nft.token_address == token_address_req && nft.token_id == token_id_req { - return Ok(nft.amount); - } - } - MmError::err(GetNftInfoError::TokenNotFoundInWallet { - token_address: token_address_req, - token_id: token_id_req.to_string(), - }) + let nft_list = get_nft_list(ctx, nft_list).await?.nfts; + let nft = nft_list + .into_iter() + .find(|nft| nft.token_address == token_address_req && nft.token_id == token_id_req) + .ok_or_else(|| GetNftInfoError::TokenNotFoundInWallet { + token_address: token_address_req, + token_id: token_id_req.to_string(), + })?; + Ok(nft.amount) } diff --git a/mm2src/coins/nft/nft_structs.rs b/mm2src/coins/nft/nft_structs.rs index ac8a9d53df..09e247967c 100644 --- a/mm2src/coins/nft/nft_structs.rs +++ b/mm2src/coins/nft/nft_structs.rs @@ -145,7 +145,6 @@ impl std::ops::Deref for SerdeStringWrap { #[derive(Debug, Serialize)] pub struct NftList { - pub(crate) count: u64, pub(crate) nfts: Vec, } @@ -254,6 +253,5 @@ pub(crate) struct NftTransferHistoryWrapper { #[derive(Debug, Serialize)] pub struct NftsTransferHistoryList { - pub(crate) count: u64, pub(crate) transfer_history: Vec, } From 9963fe17fff52ceeaa766666a64411ddabe3f841 Mon Sep 17 00:00:00 2001 From: laruh Date: Wed, 22 Mar 2023 12:35:45 +0700 Subject: [PATCH 62/66] fix merge conflicts --- mm2src/coins/eth.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mm2src/coins/eth.rs b/mm2src/coins/eth.rs index 9603884814..13c9dba8de 100644 --- a/mm2src/coins/eth.rs +++ b/mm2src/coins/eth.rs @@ -5201,7 +5201,7 @@ impl From for EthGasDetailsErr { fn from(e: Web3RpcError) -> Self { match e { Web3RpcError::Transport(tr) | Web3RpcError::InvalidResponse(tr) => EthGasDetailsErr::Transport(tr), - Web3RpcError::Internal(internal) => EthGasDetailsErr::Internal(internal), + Web3RpcError::Internal(internal) | Web3RpcError::Timeout(internal) => EthGasDetailsErr::Internal(internal), } } } From 72a784699fbb46ebf05062be9b072f9c93a50195 Mon Sep 17 00:00:00 2001 From: laruh Date: Wed, 22 Mar 2023 14:07:15 +0700 Subject: [PATCH 63/66] get_valid_nft_add_to_withdraw, use just bool for max --- mm2src/coins/eth.rs | 22 ++++++++++------------ 1 file changed, 10 insertions(+), 12 deletions(-) diff --git a/mm2src/coins/eth.rs b/mm2src/coins/eth.rs index 13c9dba8de..5a7a65be19 100644 --- a/mm2src/coins/eth.rs +++ b/mm2src/coins/eth.rs @@ -763,7 +763,7 @@ async fn withdraw_impl(coin: EthCoin, req: WithdrawRequest) -> WithdrawResult { data.clone().into(), call_addr, false, - Some(req.max), + req.max, ) .await?; let total_fee = gas * gas_price; @@ -880,7 +880,7 @@ async fn withdraw_impl(coin: EthCoin, req: WithdrawRequest) -> WithdrawResult { /// which should be sent to`send_raw_transaction` RPC to broadcast the transaction. pub async fn withdraw_erc1155(ctx: MmArc, req: WithdrawErc1155) -> WithdrawNftResult { let coin = lp_coinfind_or_err(&ctx, &req.chain.to_ticker()).await?; - let (to_addr, token_addr, eth_coin) = get_valid_eth_withdraw_addresses(coin, &req.to, &req.token_address)?; + let (to_addr, token_addr, eth_coin) = get_valid_nft_add_to_withdraw(coin, &req.to, &req.token_address)?; let my_address = eth_coin.my_address()?; // todo check amount in nft cache, instead of sending new moralis req @@ -936,7 +936,7 @@ pub async fn withdraw_erc1155(ctx: MmArc, req: WithdrawErc1155) -> WithdrawNftRe data.clone().into(), call_addr, true, - None, + false, ) .await?; let _nonce_lock = eth_coin.nonce_lock.lock().await; @@ -981,7 +981,7 @@ pub async fn withdraw_erc1155(ctx: MmArc, req: WithdrawErc1155) -> WithdrawNftRe /// which should be sent to`send_raw_transaction` RPC to broadcast the transaction. pub async fn withdraw_erc721(ctx: MmArc, req: WithdrawErc721) -> WithdrawNftResult { let coin = lp_coinfind_or_err(&ctx, &req.chain.to_ticker()).await?; - let (to_addr, token_addr, eth_coin) = get_valid_eth_withdraw_addresses(coin, &req.to, &req.token_address)?; + let (to_addr, token_addr, eth_coin) = get_valid_nft_add_to_withdraw(coin, &req.to, &req.token_address)?; let my_address = eth_coin.my_address()?; let (eth_value, data, call_addr, fee_coin) = match eth_coin.coin_type { EthCoinType::Eth => { @@ -1009,7 +1009,7 @@ pub async fn withdraw_erc721(ctx: MmArc, req: WithdrawErc721) -> WithdrawNftResu data.clone().into(), call_addr, true, - None, + false, ) .await?; let _nonce_lock = eth_coin.nonce_lock.lock().await; @@ -5164,7 +5164,7 @@ pub enum GetValidEthWithdrawAddError { } #[cfg(feature = "enable-nft-integration")] -fn get_valid_eth_withdraw_addresses( +fn get_valid_nft_add_to_withdraw( coin_enum: MmCoinEnum, to: &str, token_add: &str, @@ -5213,7 +5213,7 @@ async fn get_eth_gas_details( data: Bytes, call_addr: Address, nft: bool, - fungible_max: Option, + fungible_max: bool, ) -> MmResult { match fee { Some(WithdrawFee::EthGas { gas_price, gas }) => { @@ -5227,11 +5227,9 @@ async fn get_eth_gas_details( None => { let gas_price = eth_coin.get_gas_price().compat().await?; let mut eth_value_for_estimate = eth_value; - if !nft && fungible_max.is_some() { - // covering edge case by deducting the standard transfer fee when we want to max withdraw ETH - if fungible_max.unwrap() && eth_coin.coin_type == EthCoinType::Eth { - eth_value_for_estimate = eth_value - gas_price * U256::from(21000) - } + // covering edge case by deducting the standard transfer fee when we want to max withdraw ETH + if !nft && fungible_max && eth_coin.coin_type == EthCoinType::Eth { + eth_value_for_estimate = eth_value - gas_price * U256::from(21000) } let estimate_gas_req = CallRequest { value: Some(eth_value_for_estimate), From 9a8f33d27530540952b4d3b859bc53df75f6b272 Mon Sep 17 00:00:00 2001 From: laruh Date: Thu, 23 Mar 2023 13:05:55 +0700 Subject: [PATCH 64/66] polish eth_value_for_estimate --- mm2src/coins/eth.rs | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/mm2src/coins/eth.rs b/mm2src/coins/eth.rs index 5a7a65be19..ce5302dc25 100644 --- a/mm2src/coins/eth.rs +++ b/mm2src/coins/eth.rs @@ -5226,11 +5226,12 @@ async fn get_eth_gas_details( }, None => { let gas_price = eth_coin.get_gas_price().compat().await?; - let mut eth_value_for_estimate = eth_value; // covering edge case by deducting the standard transfer fee when we want to max withdraw ETH - if !nft && fungible_max && eth_coin.coin_type == EthCoinType::Eth { - eth_value_for_estimate = eth_value - gas_price * U256::from(21000) - } + let eth_value_for_estimate = if !nft && fungible_max && eth_coin.coin_type == EthCoinType::Eth { + eth_value - gas_price * U256::from(21000) + } else { + eth_value + }; let estimate_gas_req = CallRequest { value: Some(eth_value_for_estimate), data: Some(data), From 06657f01f653d0963b223f03ed1e382213e6d00e Mon Sep 17 00:00:00 2001 From: laruh Date: Thu, 23 Mar 2023 13:20:57 +0700 Subject: [PATCH 65/66] remove nft: bool --- mm2src/coins/eth.rs | 39 +++++++-------------------------------- 1 file changed, 7 insertions(+), 32 deletions(-) diff --git a/mm2src/coins/eth.rs b/mm2src/coins/eth.rs index ce5302dc25..506a091be6 100644 --- a/mm2src/coins/eth.rs +++ b/mm2src/coins/eth.rs @@ -756,16 +756,8 @@ async fn withdraw_impl(coin: EthCoin, req: WithdrawRequest) -> WithdrawResult { }; let eth_value_dec = u256_to_big_decimal(eth_value, coin.decimals)?; - let (gas, gas_price) = get_eth_gas_details( - &coin, - req.fee, - eth_value, - data.clone().into(), - call_addr, - false, - req.max, - ) - .await?; + let (gas, gas_price) = + get_eth_gas_details(&coin, req.fee, eth_value, data.clone().into(), call_addr, req.max).await?; let total_fee = gas * gas_price; let total_fee_dec = u256_to_big_decimal(total_fee, coin.decimals)?; @@ -929,16 +921,8 @@ pub async fn withdraw_erc1155(ctx: MmArc, req: WithdrawErc1155) -> WithdrawNftRe )) }, }; - let (gas, gas_price) = get_eth_gas_details( - ð_coin, - req.fee, - eth_value, - data.clone().into(), - call_addr, - true, - false, - ) - .await?; + let (gas, gas_price) = + get_eth_gas_details(ð_coin, req.fee, eth_value, data.clone().into(), call_addr, false).await?; let _nonce_lock = eth_coin.nonce_lock.lock().await; let nonce = get_addr_nonce(eth_coin.my_address, eth_coin.web3_instances.clone()) .compat() @@ -1002,16 +986,8 @@ pub async fn withdraw_erc721(ctx: MmArc, req: WithdrawErc721) -> WithdrawNftResu )) }, }; - let (gas, gas_price) = get_eth_gas_details( - ð_coin, - req.fee, - eth_value, - data.clone().into(), - call_addr, - true, - false, - ) - .await?; + let (gas, gas_price) = + get_eth_gas_details(ð_coin, req.fee, eth_value, data.clone().into(), call_addr, false).await?; let _nonce_lock = eth_coin.nonce_lock.lock().await; let nonce = get_addr_nonce(eth_coin.my_address, eth_coin.web3_instances.clone()) .compat() @@ -5212,7 +5188,6 @@ async fn get_eth_gas_details( eth_value: U256, data: Bytes, call_addr: Address, - nft: bool, fungible_max: bool, ) -> MmResult { match fee { @@ -5227,7 +5202,7 @@ async fn get_eth_gas_details( None => { let gas_price = eth_coin.get_gas_price().compat().await?; // covering edge case by deducting the standard transfer fee when we want to max withdraw ETH - let eth_value_for_estimate = if !nft && fungible_max && eth_coin.coin_type == EthCoinType::Eth { + let eth_value_for_estimate = if fungible_max && eth_coin.coin_type == EthCoinType::Eth { eth_value - gas_price * U256::from(21000) } else { eth_value From 8245784ec4b3e9c4db3ba1ca68ec2d16d71ad89f Mon Sep 17 00:00:00 2001 From: laruh Date: Thu, 23 Mar 2023 17:51:15 +0700 Subject: [PATCH 66/66] add line spaces, add bold text --- mm2src/coins/eth.rs | 5 +++++ mm2src/coins/nft.rs | 2 +- 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/mm2src/coins/eth.rs b/mm2src/coins/eth.rs index 506a091be6..7c461e1797 100644 --- a/mm2src/coins/eth.rs +++ b/mm2src/coins/eth.rs @@ -938,10 +938,12 @@ pub async fn withdraw_erc1155(ctx: MmArc, req: WithdrawErc1155) -> WithdrawNftRe gas, gas_price, }; + let secret = eth_coin.priv_key_policy.key_pair_or_err()?.secret(); let signed = tx.sign(secret, eth_coin.chain_id); let signed_bytes = rlp::encode(&signed); let fee_details = EthTxFeeDetails::new(gas, gas_price, fee_coin)?; + Ok(TransactionNftDetails { tx_hex: BytesJson::from(signed_bytes.to_vec()), tx_hash: format!("{:02x}", signed.tx_hash()), @@ -967,6 +969,7 @@ pub async fn withdraw_erc721(ctx: MmArc, req: WithdrawErc721) -> WithdrawNftResu let coin = lp_coinfind_or_err(&ctx, &req.chain.to_ticker()).await?; let (to_addr, token_addr, eth_coin) = get_valid_nft_add_to_withdraw(coin, &req.to, &req.token_address)?; let my_address = eth_coin.my_address()?; + let (eth_value, data, call_addr, fee_coin) = match eth_coin.coin_type { EthCoinType::Eth => { let function = ERC721_CONTRACT.function("safeTransferFrom")?; @@ -1003,10 +1006,12 @@ pub async fn withdraw_erc721(ctx: MmArc, req: WithdrawErc721) -> WithdrawNftResu gas, gas_price, }; + let secret = eth_coin.priv_key_policy.key_pair_or_err()?.secret(); let signed = tx.sign(secret, eth_coin.chain_id); let signed_bytes = rlp::encode(&signed); let fee_details = EthTxFeeDetails::new(gas, gas_price, fee_coin)?; + Ok(TransactionNftDetails { tx_hex: BytesJson::from(signed_bytes.to_vec()), tx_hash: format!("{:02x}", signed.tx_hash()), diff --git a/mm2src/coins/nft.rs b/mm2src/coins/nft.rs index abc3aca4a0..2b768738bd 100644 --- a/mm2src/coins/nft.rs +++ b/mm2src/coins/nft.rs @@ -91,7 +91,7 @@ pub async fn get_nft_list(ctx: MmArc, req: NftListReq) -> MmResult MmResult {