Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[r2r] NFT integration poc #1652

Merged
merged 62 commits into from
Mar 7, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
62 commits
Select commit Hold shift + click to select a range
c142d97
wip
laruh Jan 20, 2023
b233d01
Merge remote-tracking branch 'origin/dev' into NFT-integration-poc
laruh Jan 20, 2023
df5212a
wip
laruh Jan 22, 2023
4a50218
Merge remote-tracking branch 'origin/dev' into NFT-integration-poc
laruh Jan 23, 2023
6ed1ee8
wip some structures were added
laruh Jan 24, 2023
853d289
Merge remote-tracking branch 'origin/dev' into NFT-integration-poc
laruh Jan 24, 2023
a7dc383
wip
laruh Jan 26, 2023
55e11b7
wip
laruh Jan 27, 2023
5653da3
wip
laruh Jan 27, 2023
28dac60
fn get_my_address was moved into lp_coins.rs
laruh Jan 27, 2023
bd3ac54
Merge remote-tracking branch 'origin/dev' into NFT-integration-poc
laruh Jan 27, 2023
2af943a
wip get_my_address
laruh Feb 1, 2023
2cbbc23
Merge remote-tracking branch 'origin/dev' into NFT-integration-poc
laruh Feb 1, 2023
551f895
get_my_address works
laruh Feb 1, 2023
cd1e11c
send_moralis_request, errors, get_nft_list wip
laruh Feb 2, 2023
f617e5d
add targets for send_moralis_request
laruh Feb 3, 2023
28f3e63
wip
laruh Feb 3, 2023
db638e9
wip
laruh Feb 3, 2023
5fc4e8e
wip use fn slurp_req_body in fn send_moralis_request
laruh Feb 3, 2023
fd2aaef
Merge remote-tracking branch 'origin/dev' into NFT-integration-poc
laruh Feb 6, 2023
778dd60
wip fix wasm
laruh Feb 7, 2023
f83a9ff
wip cursor in get_nft_list
laruh Feb 7, 2023
8636599
wip impl Deserialize for Wrap
laruh Feb 8, 2023
a33be79
get_nft_list
laruh Feb 8, 2023
f9ad871
remove unnecessary notes
laruh Feb 8, 2023
5bef9bf
Merge remote-tracking branch 'origin/dev' into NFT-integration-poc
laruh Feb 8, 2023
36798e9
wip get_nft_transfers
laruh Feb 9, 2023
91cf067
get_nft_transfers works
laruh Feb 9, 2023
f5f19c7
polish code
laruh Feb 9, 2023
7860331
polish code
laruh Feb 9, 2023
9c45de9
remove Option from some fields in Nft struct
laruh Feb 10, 2023
c932d36
wip get_nft_metadata
laruh Feb 10, 2023
3411af2
use NftWrapper in fn get_nft_metadata, add some doc comments
laruh Feb 12, 2023
8185875
remove allow(dead_code)
laruh Feb 12, 2023
831dd5b
change order in Chain enum
laruh Feb 12, 2023
9f4e6c0
fix doc comment
laruh Feb 13, 2023
4311194
Merge remote-tracking branch 'origin/dev' into NFT-integration-poc
laruh Feb 14, 2023
da2d745
beautify json
laruh Feb 14, 2023
cfb8f37
add from in withdraw requests
laruh Feb 14, 2023
9e51761
String::new(), serde UPPERCASE, pub(crate) SerdeStringWrap,line break
laruh Feb 15, 2023
487d10f
use ok_or_else, remove if cursor is null, remove !nfts_list.is_empty()
laruh Feb 15, 2023
92bed1d
derive order, simplify match protocol
laruh Feb 15, 2023
981ac94
Merge remote-tracking branch 'origin/dev' into NFT-integration-poc
laruh Feb 15, 2023
c649bf8
Merge remote-tracking branch 'origin/dev' into NFT-integration-poc
laruh Feb 15, 2023
fbc8af0
Merge remote-tracking branch 'origin/dev' into NFT-integration-poc
laruh Feb 15, 2023
1b3a6fa
replace Chain::Bnb with Chain::Bsc
laruh Feb 17, 2023
b92ec80
change status code matching, add derive Copy
laruh Feb 17, 2023
a8403e4
fn withdraw_erc721
laruh Feb 20, 2023
5cbf0fd
move nft from eth to coin crate
laruh Feb 20, 2023
bc5d59d
remove memo
laruh Feb 20, 2023
0253dcf
Merge remote-tracking branch 'origin/dev' into NFT-integration-poc
laruh Feb 21, 2023
4edc948
wip
laruh Feb 21, 2023
e5d2109
add fn coins_conf_check
laruh Feb 21, 2023
399b85e
Merge remote-tracking branch 'origin/dev' into NFT-integration-poc
laruh Feb 22, 2023
d576053
add from_stringify
laruh Feb 22, 2023
16cfdcc
add feature enable-nft-integration, fix coins_conf_check
laruh Feb 22, 2023
09223fa
add doc comment for withdraw_nft
laruh Feb 22, 2023
1cedc3b
fix coins_conf_check
laruh Feb 22, 2023
1cd530a
fix deref which would be done by auto-deref
laruh Feb 22, 2023
4b1f93a
Merge remote-tracking branch 'origin/dev' into NFT-integration-poc
laruh Feb 25, 2023
10ce258
fix conflicts, add feature log file
laruh Feb 25, 2023
114ad32
fix wasm
laruh Feb 25, 2023
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions dev-logs/2023-feb/features/nft_integration_poc
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
NFT integration PoC added. Includes ERC721 support for ETH and BSC.


author: @laruh <oblomoff616@gmail.com>
1 change: 1 addition & 0 deletions mm2src/coins/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down
164 changes: 163 additions & 1 deletion mm2src/coins/eth.rs
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,8 @@
// 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};
use common::custom_futures::repeatable::{Ready, Retry};
Expand Down Expand Up @@ -97,7 +99,12 @@ pub use rlp;
mod web3_transport;

#[path = "eth/v2_activation.rs"] pub mod v2_activation;
use v2_activation::build_address_and_priv_key_policy;
#[cfg(feature = "enable-nft-integration")]
use crate::nft::WithdrawNftResult;
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};

mod nonce;
use nonce::ParityNonce;
Expand All @@ -109,6 +116,8 @@ use nonce::ParityNonce;
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;
Expand Down Expand Up @@ -147,6 +156,7 @@ const GUI_AUTH_SIGNED_MESSAGE_LIFETIME_SEC: i64 = 90;
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<T> = Box<dyn Future<Item = T, Error = MmError<Web3RpcError>> + Send>;
Expand Down Expand Up @@ -876,6 +886,124 @@ 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",
Chain::Eth => "ETH",
};
let _coin = lp_coinfind_or_err(&ctx, ticker).await?;
unimplemented!()
}

#[cfg(feature = "enable-nft-integration")]
pub async fn withdraw_erc721(ctx: MmArc, req: WithdrawErc721) -> WithdrawNftResult {
let ticker = match req.chain {
Chain::Bsc => "BNB",
Chain::Eth => "ETH",
};
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),
])?;
(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::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,
})
}

#[derive(Clone)]
pub struct EthCoin(Arc<EthCoinImpl>);
impl Deref for EthCoin {
Expand Down Expand Up @@ -4762,3 +4890,37 @@ fn increase_gas_price_by_stage(gas_price: U256, level: &FeeApproxStage) -> U256
},
}
}

#[derive(Debug, Deserialize, Serialize, Display)]
pub enum GetEthAddressError {
PrivKeyPolicyNotAllowed(PrivKeyPolicyNotAllowed),
EthActivationV2Error(EthActivationV2Error),
Internal(String),
}

impl From<PrivKeyPolicyNotAllowed> for GetEthAddressError {
fn from(e: PrivKeyPolicyNotAllowed) -> Self { GetEthAddressError::PrivKeyPolicyNotAllowed(e) }
}

impl From<EthActivationV2Error> for GetEthAddressError {
fn from(e: EthActivationV2Error) -> Self { GetEthAddressError::EthActivationV2Error(e) }
}

impl From<CryptoCtxError> 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(ctx: &MmArc, ticker: &str) -> MmResult<MyWalletAddress, GetEthAddressError> {
let priv_key_policy = PrivKeyBuildPolicy::detect_priv_key_policy(ctx)?;
// Convert `PrivKeyBuildPolicy` to `EthPrivKeyBuildPolicy` if it's possible.
let priv_key_policy = EthPrivKeyBuildPolicy::try_from(priv_key_policy)?;

let (my_address, ..) = build_address_and_priv_key_policy(&ctx.conf, priv_key_policy).await?;
let wallet_address = checksum_address(&format!("{:#02x}", my_address));

Ok(MyWalletAddress {
coin: ticker.to_owned(),
wallet_address,
})
}
Loading