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

feat(nft): enable eth with non fungible tokens #2049

Merged
merged 48 commits into from
Mar 1, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
48 commits
Select commit Hold shift + click to select a range
a2b3075
WIP
laruh Dec 31, 2023
750ab10
WIP enable_global_non_fungible_token func in Ops
laruh Jan 3, 2024
5e949c1
impl `global_nft_from_platform_coin`, use Json ref in `enable_platfor…
laruh Jan 7, 2024
9a90130
fix tests
laruh Jan 7, 2024
d865452
impl get_nft_activation_result and use url in global_nft_from_platfor…
laruh Jan 8, 2024
ca89aa6
impl re_enable_passive_platform_coin_with_nfts
laruh Jan 9, 2024
f8138d6
clone some fields from platform to global nft
laruh Jan 9, 2024
498c015
add `EthCoinType::Nft`
laruh Jan 9, 2024
c8a0246
fix lint
laruh Jan 9, 2024
87ab779
add NFT in CoinsContext, impl fn update_nft_global_in_coins_ctx
laruh Jan 11, 2024
87d5944
add construct_moralis_uri_for_nft
laruh Jan 17, 2024
be71278
more doc comms, move `ParseContractTypeError` to nft_errors.rs
laruh Jan 17, 2024
711651d
remove unnecessary drop_mutability
laruh Jan 19, 2024
52b353a
added `process_transfer_list`, `process_nft_list_for_activation` func…
laruh Jan 19, 2024
7df590f
doc coms for Chain methods, simplify matching in Chain
laruh Jan 19, 2024
a9b50de
revert **self
laruh Jan 19, 2024
ec2dfb3
doc comm for `non_fungible_tokens_infos` field
laruh Jan 20, 2024
7023761
use `nfts_infos` name
laruh Feb 6, 2024
9acf8cb
rename function to `enable_global_nft`
laruh Feb 6, 2024
f6ddd62
delete `enable_eth_with_non_fungible_tokens` RPC, move nft activation…
laruh Feb 6, 2024
39673cb
use enum types in `enable_token` for `EthCoin`
laruh Feb 7, 2024
d631564
use `Nft { platform }` for ticker
laruh Feb 14, 2024
24f954d
Merge remote-tracking branch 'origin/dev' into enable-eth-non-fungibl…
laruh Feb 14, 2024
2fb318f
fix merge conflicts
laruh Feb 14, 2024
84f944f
remove doc-comments for the functions arguments
laruh Feb 14, 2024
90d02c4
use optional `global_nft` param in `add_platform_with_tokens`
laruh Feb 15, 2024
0ac2dfd
use `load` to create the same `AtomicU64` for required_confirmations
laruh Feb 15, 2024
59170c6
return `EthTokenActivationError` in `global_nft_from_platform_coin`
laruh Feb 15, 2024
9517082
link Global NFT `abortable_system` to platform coin
laruh Feb 15, 2024
5177505
add NFT support in `coin_conf_with_protocol` function
laruh Feb 15, 2024
0334772
Dont register Nft in platform coin
laruh Feb 15, 2024
9009802
support NFT match type for the enable_token function
laruh Feb 16, 2024
b1a6b38
rename mod `erc20_token_activation` to `eth_tokens_activation`
laruh Feb 16, 2024
407a79f
remove unused `get_nft_activation_result` fn and `get_nfts_infos` fn
laruh Feb 16, 2024
9b96e5f
change match errors
laruh Feb 16, 2024
81af437
provide more info about global nft in doc com, match Transport err wi…
laruh Feb 18, 2024
8e6537a
use new idb cursor methods, add more err variants in nft wasm storage
laruh Feb 19, 2024
5379350
provide doc com and note for `update_nft_infos` fnc
laruh Feb 20, 2024
af5c356
Merge remote-tracking branch 'origin/dev' into enable-eth-non-fungibl…
laruh Feb 21, 2024
11284b9
more doc comms, simplify `ParseChainTypeError`
laruh Feb 22, 2024
c8dff90
move `nfts_map` creation from the beginning of `get_activation_result…
laruh Feb 22, 2024
429cbe2
use `untagged` for `EthTokenInitResult`, add match protocol_conf
laruh Feb 25, 2024
1fee1cf
use `untagged` for `EthTokenActivationParams` and change types order …
laruh Feb 25, 2024
6dd8f31
return Ok(None) in enable_global_nft, remove unused error, use "not s…
laruh Feb 27, 2024
7e98ae1
rollback name changing of erc20_token_activation module
laruh Feb 27, 2024
4d8fc17
Merge remote-tracking branch 'origin/dev' into enable-eth-non-fungibl…
laruh Feb 27, 2024
0769f03
fix merge conflicts
laruh Feb 27, 2024
8fc8d08
refactor NftActivationRequest struct, add NftProviderEnum, match Coin…
laruh Feb 29, 2024
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
1 change: 1 addition & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 3 additions & 0 deletions mm2src/coins/coin_errors.rs
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,8 @@ pub enum ValidatePaymentError {
WatcherRewardError(String),
/// Input payment timelock overflows the type used by specific coin.
TimelockOverflow(TryFromIntError),
#[display(fmt = "Nft Protocol is not supported yet!")]
NftProtocolNotSupported,
}

impl From<rlp::DecoderError> for ValidatePaymentError {
Expand Down Expand Up @@ -79,6 +81,7 @@ impl From<Web3RpcError> for ValidatePaymentError {
Web3RpcError::Internal(internal) | Web3RpcError::Timeout(internal) => {
ValidatePaymentError::InternalError(internal)
},
Web3RpcError::NftProtocolNotSupported => ValidatePaymentError::NftProtocolNotSupported,
}
}
}
Expand Down
118 changes: 112 additions & 6 deletions mm2src/coins/eth.rs

Large diffs are not rendered by default.

7 changes: 7 additions & 0 deletions mm2src/coins/eth/eth_tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -131,6 +131,7 @@ fn eth_coin_from_keypair(
let ticker = match coin_type {
EthCoinType::Eth => "ETH".to_string(),
EthCoinType::Erc20 { .. } => "JST".to_string(),
EthCoinType::Nft { ref platform } => platform.to_string(),
};

let eth_coin = EthCoin(Arc::new(EthCoinImpl {
Expand All @@ -154,6 +155,7 @@ fn eth_coin_from_keypair(
logs_block_range: DEFAULT_LOGS_BLOCK_RANGE,
nonce_lock: new_nonce_lock(),
erc20_tokens_infos: Default::default(),
nfts_infos: Default::default(),
abortable_system: AbortableQueue::default(),
}));
(ctx, eth_coin)
Expand Down Expand Up @@ -364,6 +366,7 @@ fn test_nonce_several_urls() {
logs_block_range: DEFAULT_LOGS_BLOCK_RANGE,
nonce_lock: new_nonce_lock(),
erc20_tokens_infos: Default::default(),
nfts_infos: Default::default(),
abortable_system: AbortableQueue::default(),
}));

Expand Down Expand Up @@ -415,6 +418,7 @@ fn test_wait_for_payment_spend_timeout() {
logs_block_range: DEFAULT_LOGS_BLOCK_RANGE,
nonce_lock: new_nonce_lock(),
erc20_tokens_infos: Default::default(),
nfts_infos: Default::default(),
abortable_system: AbortableQueue::default(),
};

Expand Down Expand Up @@ -1126,6 +1130,7 @@ fn test_message_hash() {
logs_block_range: DEFAULT_LOGS_BLOCK_RANGE,
nonce_lock: new_nonce_lock(),
erc20_tokens_infos: Default::default(),
nfts_infos: Default::default(),
abortable_system: AbortableQueue::default(),
}));

Expand Down Expand Up @@ -1173,6 +1178,7 @@ fn test_sign_verify_message() {
logs_block_range: DEFAULT_LOGS_BLOCK_RANGE,
nonce_lock: new_nonce_lock(),
erc20_tokens_infos: Default::default(),
nfts_infos: Default::default(),
abortable_system: AbortableQueue::default(),
}));

Expand Down Expand Up @@ -1229,6 +1235,7 @@ fn test_eth_extract_secret() {
logs_block_range: DEFAULT_LOGS_BLOCK_RANGE,
nonce_lock: new_nonce_lock(),
erc20_tokens_infos: Default::default(),
nfts_infos: Default::default(),
abortable_system: AbortableQueue::default(),
}));

Expand Down
1 change: 1 addition & 0 deletions mm2src/coins/eth/eth_wasm_tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@ async fn test_send() {
logs_block_range: DEFAULT_LOGS_BLOCK_RANGE,
nonce_lock: new_nonce_lock(),
erc20_tokens_infos: Default::default(),
nfts_infos: Default::default(),
abortable_system: AbortableQueue::default(),
}));
let maker_payment_args = SendPaymentArgs {
Expand Down
157 changes: 149 additions & 8 deletions mm2src/coins/eth/v2_activation.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,7 @@
use super::*;
use crate::nft::get_nfts_for_activation;
use crate::nft::nft_errors::{GetNftInfoError, ParseChainTypeError};
use crate::nft::nft_structs::Chain;
#[cfg(target_arch = "wasm32")] use crate::EthMetamaskPolicy;
use common::executor::AbortedError;
use crypto::{CryptoCtxError, StandardHDCoinAddress};
Expand All @@ -7,6 +10,8 @@ use instant::Instant;
use mm2_err_handle::common_errors::WithInternal;
#[cfg(target_arch = "wasm32")]
use mm2_metamask::{from_metamask_error, MetamaskError, MetamaskRpcError, WithMetamaskRpcError};
use std::sync::atomic::Ordering;
use url::Url;
use web3_transport::websocket_transport::WebsocketTransport;

#[derive(Clone, Debug, Deserialize, Display, EnumFromTrait, PartialEq, Serialize, SerializeErrorType)]
Expand Down Expand Up @@ -39,6 +44,7 @@ pub enum EthActivationV2Error {
#[from_trait(WithInternal::internal)]
#[display(fmt = "Internal: {}", _0)]
InternalError(String),
Transport(String),
}

impl From<MyAddressError> for EthActivationV2Error {
Expand All @@ -57,11 +63,28 @@ impl From<UnexpectedDerivationMethod> for EthActivationV2Error {
fn from(e: UnexpectedDerivationMethod) -> Self { EthActivationV2Error::InternalError(e.to_string()) }
}

impl From<EthTokenActivationError> for EthActivationV2Error {
fn from(e: EthTokenActivationError) -> Self {
match e {
EthTokenActivationError::InternalError(err) => EthActivationV2Error::InternalError(err),
EthTokenActivationError::CouldNotFetchBalance(err) => EthActivationV2Error::CouldNotFetchBalance(err),
EthTokenActivationError::InvalidPayload(err) => EthActivationV2Error::InvalidPayload(err),
EthTokenActivationError::Transport(err) | EthTokenActivationError::ClientConnectionFailed(err) => {
EthActivationV2Error::Transport(err)
},
}
}
}

#[cfg(target_arch = "wasm32")]
impl From<MetamaskError> for EthActivationV2Error {
fn from(e: MetamaskError) -> Self { from_metamask_error(e) }
}

impl From<ParseChainTypeError> for EthActivationV2Error {
fn from(e: ParseChainTypeError) -> Self { EthActivationV2Error::InternalError(e.to_string()) }
}

/// An alternative to `crate::PrivKeyActivationPolicy`, typical only for ETH coin.
#[derive(Clone, Deserialize)]
pub enum EthPrivKeyActivationPolicy {
Expand Down Expand Up @@ -116,44 +139,113 @@ pub struct EthNode {

#[derive(Serialize, SerializeErrorType)]
#[serde(tag = "error_type", content = "error_data")]
pub enum Erc20TokenActivationError {
pub enum EthTokenActivationError {
InternalError(String),
ClientConnectionFailed(String),
CouldNotFetchBalance(String),
InvalidPayload(String),
Transport(String),
}

impl From<AbortedError> for Erc20TokenActivationError {
fn from(e: AbortedError) -> Self { Erc20TokenActivationError::InternalError(e.to_string()) }
impl From<AbortedError> for EthTokenActivationError {
fn from(e: AbortedError) -> Self { EthTokenActivationError::InternalError(e.to_string()) }
}

impl From<MyAddressError> for Erc20TokenActivationError {
impl From<MyAddressError> for EthTokenActivationError {
fn from(err: MyAddressError) -> Self { Self::InternalError(err.to_string()) }
}

impl From<GetNftInfoError> for EthTokenActivationError {
fn from(e: GetNftInfoError) -> Self {
match e {
GetNftInfoError::InvalidRequest(err) => EthTokenActivationError::InvalidPayload(err),
GetNftInfoError::ContractTypeIsNull => EthTokenActivationError::InvalidPayload(
"The contract type is required and should not be null.".to_string(),
),
GetNftInfoError::Transport(err) | GetNftInfoError::InvalidResponse(err) => {
EthTokenActivationError::Transport(err)
},
GetNftInfoError::Internal(err) | GetNftInfoError::DbError(err) | GetNftInfoError::NumConversError(err) => {
EthTokenActivationError::InternalError(err)
},
GetNftInfoError::GetEthAddressError(err) => EthTokenActivationError::InternalError(err.to_string()),
GetNftInfoError::ParseRfc3339Err(err) => EthTokenActivationError::InternalError(err.to_string()),
GetNftInfoError::ProtectFromSpamError(err) => EthTokenActivationError::InternalError(err.to_string()),
GetNftInfoError::TransferConfirmationsError(err) => EthTokenActivationError::InternalError(err.to_string()),
GetNftInfoError::TokenNotFoundInWallet {
token_address,
token_id,
} => EthTokenActivationError::InternalError(format!(
"Token not found in wallet: {}, {}",
token_address, token_id
)),
Comment on lines +175 to +181
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Q: How exactly we raise GetNftInfoError::TokenNotFoundInWallet errors ?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Currently we propagate GetNftInfoError::TokenNotFoundInWallet error when we try to get nft object from storage.

Right now we need to impl FromGetNftInfoError for EthTokenActivationError, bcz in global_nft_from_platform_coin function we use get_nfts_for_activation function, which return GetNftInfoError.
GetNftInfoError is an error when we try to get nft from storage/from moralis sources.

}
}
}

impl From<ParseChainTypeError> for EthTokenActivationError {
fn from(e: ParseChainTypeError) -> Self { EthTokenActivationError::InternalError(e.to_string()) }
}

/// Represents the parameters required for activating either an ERC-20 token or an NFT on the Ethereum platform.
#[derive(Clone, Deserialize)]
#[serde(untagged)]
pub enum EthTokenActivationParams {
Nft(NftActivationRequest),
Erc20(Erc20TokenActivationRequest),
}

/// Holds ERC-20 token-specific activation parameters, including optional confirmation requirements.
#[derive(Clone, Deserialize)]
pub struct Erc20TokenActivationRequest {
pub required_confirmations: Option<u64>,
}

/// Encapsulates the request parameters for NFT activation, specifying the provider to be used.
#[derive(Clone, Deserialize)]
pub struct NftActivationRequest {
pub provider: NftProviderEnum,
}

/// Defines available NFT providers and their configuration.
#[derive(Clone, Deserialize)]
#[serde(tag = "type", content = "info")]
pub enum NftProviderEnum {
Moralis { url: Url },
}

/// Represents the protocol type for an Ethereum-based token, distinguishing between ERC-20 tokens and NFTs.
pub enum EthTokenProtocol {
Erc20(Erc20Protocol),
Nft(NftProtocol),
}

/// Details for an ERC-20 token protocol.
pub struct Erc20Protocol {
pub platform: String,
pub token_addr: Address,
}

/// Details for an NFT protocol.
#[derive(Debug)]
pub struct NftProtocol {
pub platform: String,
}

#[cfg_attr(test, mockable)]
impl EthCoin {
pub async fn initialize_erc20_token(
&self,
activation_params: Erc20TokenActivationRequest,
protocol: Erc20Protocol,
ticker: String,
) -> MmResult<EthCoin, Erc20TokenActivationError> {
) -> MmResult<EthCoin, EthTokenActivationError> {
// TODO
// Check if ctx is required.
// Remove it to avoid circular references if possible
let ctx = MmArc::from_weak(&self.ctx)
.ok_or_else(|| String::from("No context"))
.map_err(Erc20TokenActivationError::InternalError)?;
.map_err(EthTokenActivationError::InternalError)?;

let conf = coin_conf(&ctx, &ticker);

Expand All @@ -162,11 +254,11 @@ impl EthCoin {
&self
.web3()
.await
.map_err(|e| Erc20TokenActivationError::ClientConnectionFailed(e.to_string()))?,
.map_err(|e| EthTokenActivationError::ClientConnectionFailed(e.to_string()))?,
protocol.token_addr,
)
.await
.map_err(Erc20TokenActivationError::InternalError)?,
.map_err(EthTokenActivationError::InternalError)?,
Some(d) => d as u8,
};

Expand Down Expand Up @@ -221,11 +313,59 @@ impl EthCoin {
logs_block_range: self.logs_block_range,
nonce_lock: self.nonce_lock.clone(),
erc20_tokens_infos: Default::default(),
nfts_infos: Default::default(),
abortable_system,
};

Ok(EthCoin(Arc::new(token)))
}

/// Initializes a Global NFT instance for a specific blockchain platform (e.g., Ethereum, Polygon).
///
/// A "Global NFT" consolidates information about all NFTs owned by a user into a single `EthCoin` instance,
/// avoiding the need for separate instances for each NFT.
/// The function configures the necessary settings for the Global NFT, including web3 connections and confirmation requirements.
/// It fetches NFT details from a given URL to populate the `nfts_infos` field, which stores information about the user's NFTs.
///
/// This setup allows the Global NFT to function like a coin, supporting swap operations and providing easy access to NFT details via `nfts_infos`.
pub async fn global_nft_from_platform_coin(&self, url: &Url) -> MmResult<EthCoin, EthTokenActivationError> {
let chain = Chain::from_ticker(self.ticker())?;
let ticker = chain.to_nft_ticker().to_string();

// Create an abortable system linked to the `platform_coin` (which is self) so if the platform coin is disabled,
// all spawned futures related to global Non-Fungible Token will be aborted as well.
let abortable_system = self.abortable_system.create_subsystem()?;

let nft_infos = get_nfts_for_activation(&chain, &self.my_address, url).await?;

let global_nft = EthCoinImpl {
ticker,
coin_type: EthCoinType::Nft {
platform: self.ticker.clone(),
},
priv_key_policy: self.priv_key_policy.clone(),
my_address: self.my_address,
sign_message_prefix: self.sign_message_prefix.clone(),
swap_contract_address: self.swap_contract_address,
fallback_swap_contract: self.fallback_swap_contract,
contract_supports_watchers: self.contract_supports_watchers,
web3_instances: self.web3_instances.lock().await.clone().into(),
decimals: self.decimals,
gas_station_url: self.gas_station_url.clone(),
gas_station_decimals: self.gas_station_decimals,
gas_station_policy: self.gas_station_policy.clone(),
history_sync_state: Mutex::new(self.history_sync_state.lock().unwrap().clone()),
required_confirmations: AtomicU64::new(self.required_confirmations.load(Ordering::Relaxed)),
ctx: self.ctx.clone(),
chain_id: self.chain_id,
logs_block_range: self.logs_block_range,
nonce_lock: self.nonce_lock.clone(),
erc20_tokens_infos: Default::default(),
nfts_infos: Arc::new(AsyncMutex::new(nft_infos)),
abortable_system,
};
Ok(EthCoin(Arc::new(global_nft)))
}
}

pub async fn eth_coin_from_conf_and_request_v2(
Expand Down Expand Up @@ -329,6 +469,7 @@ pub async fn eth_coin_from_conf_and_request_v2(
logs_block_range: conf["logs_block_range"].as_u64().unwrap_or(DEFAULT_LOGS_BLOCK_RANGE),
nonce_lock,
erc20_tokens_infos: Default::default(),
nfts_infos: Default::default(),
abortable_system,
};

Expand Down
Loading
Loading