Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 19 additions & 0 deletions nekoton-contracts/src/access/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
use anyhow::Result;
use nekoton_abi::*;

use super::RunLocalSimple;

pub mod ownable_internal_contract;

#[derive(Copy, Clone)]
pub struct OwnableInternalContract<'a>(pub ExecutionContext<'a>);

impl OwnableInternalContract<'_> {
pub fn owner(&self) -> Result<ton_block::MsgAddressInt> {
let result = self
.0
.run_local_simple(ownable_internal_contract::owner(), &[])?
.unpack_first()?;
Ok(result)
}
}
23 changes: 23 additions & 0 deletions nekoton-contracts/src/access/ownable_internal_contract.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
use ton_abi::{Param, ParamType};

use crate::utils::declare_function;

/// Returns collection owner address
///
/// # Type
/// Simple getter method
///
/// # Inputs
/// No inputs
///
/// # Outputs
/// * `value0: address` - Address of NFT contract
///
pub fn owner() -> &'static ton_abi::Function {
declare_function! {
header: [pubkey, time, expire],
name: "owner",
inputs: vec![],
outputs: vec![Param::new("value0", ParamType::Address)],
}
}
1 change: 1 addition & 0 deletions nekoton-contracts/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,7 @@
use anyhow::Result;
use nekoton_abi::{ExecutionContext, ExecutionOutput};

pub mod access;
pub mod dens;
pub mod old_tip3;
pub mod tip1155;
Expand Down
20 changes: 20 additions & 0 deletions nekoton-contracts/src/tip4_2_2/collection_contract.rs
Original file line number Diff line number Diff line change
Expand Up @@ -24,3 +24,23 @@ pub fn get_nft_url() -> &'static ton_abi::Function {
outputs: vec![Param::new("nftUrl", ParamType::String)]
}
}

/// Return url to metadata for NFT collection.
///
/// # Type
/// Responsible getter method
///
/// # Inputs
/// * `answerId: uint32` - responsible answer id
///
/// # Outputs
/// * `collectionUrl: string` - NFT collection metadata URL
pub fn get_collection_url() -> &'static ton_abi::Function {
declare_function! {
name: "getCollectionUrl",
inputs: vec![
Param::new("answerId", ParamType::Uint(32)),
],
outputs: vec![Param::new("collectionUrl", ParamType::String)]
}
}
11 changes: 10 additions & 1 deletion nekoton-contracts/src/tip4_2_2/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ pub mod metadata_contract;
pub struct MetadataContract<'a>(pub ExecutionContext<'a>);

impl MetadataContract<'_> {
pub fn get_url_parts(&self) -> Result<String> {
pub fn get_url_parts(&self) -> Result<ton_types::Cell> {
let inputs = [0u32.token_value().named("answerId")];
let result = self
.0
Expand All @@ -35,4 +35,13 @@ impl CollectionContract<'_> {
.unpack_first()?;
Ok(result)
}

pub fn get_collection_url(&self) -> Result<String> {
let inputs = [0u32.token_value().named("answerId")];
let result = self
.0
.run_local_responsible_simple(collection_contract::get_collection_url(), &inputs)?
.unpack_first()?;
Ok(result)
}
}
102 changes: 81 additions & 21 deletions src/core/nft_wallet/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ use std::sync::Arc;

use anyhow::Result;
use nekoton_abi::MessageBuilder;
use nekoton_contracts::access::OwnableInternalContract;
use nekoton_contracts::tip4_1::nft_contract::{self, GetInfoOutputs, NftCallbackPayload};
use nekoton_contracts::tip4_3::index_contract::IndexGetInfoOutputs;
use nekoton_contracts::*;
Expand All @@ -25,7 +26,14 @@ pub struct NftCollection {
collection_address: MsgAddressInt,
state: ExistingContract,
index_code: Cell,
json_info: Option<String>,
interfaces: CollectionInterfaces,
json_info: Option<JsonInfo>,
owner: Option<MsgAddressInt>,
}

pub enum JsonInfo {
Json(String),
Url(String),
}

impl NftCollection {
Expand All @@ -48,17 +56,31 @@ impl NftCollection {

let index_code = contract.resolve_collection_index_code(clock)?;

let json_info = interfaces
let ctx = state.as_context(clock);
let json_data = interfaces
.tip4_2
.then(|| tip4_2::MetadataContract(state.as_context(clock)).get_json())
.transpose()?;
.then(|| tip4_2::MetadataContract(ctx).get_json())
.transpose()?
.map(JsonInfo::Json);

let json_url = interfaces
.tip4_2_2
.then(|| tip4_2_2::CollectionContract(ctx).get_collection_url())
.transpose()?
.map(JsonInfo::Url);

let json_info = json_data.or(json_url);

let owner = OwnableInternalContract(ctx).owner().ok();

Ok(NftCollection {
transport,
collection_address,
state,
index_code,
interfaces,
json_info,
owner,
})
}

Expand All @@ -70,10 +92,14 @@ impl NftCollection {
&self.index_code
}

pub fn json_info(&self) -> &Option<String> {
pub fn json_info(&self) -> &Option<JsonInfo> {
&self.json_info
}

pub fn owner(&self) -> &Option<MsgAddressInt> {
&self.owner
}

pub fn compute_collection_code_hash(&self, owner: &MsgAddressInt) -> Result<UInt256> {
CollectionContractState(&self.state)
.get_collection_code_hash(owner, self.index_code.clone())
Expand All @@ -90,6 +116,10 @@ impl NftCollection {
.get_accounts_by_code_hash(&code_hash, limit, &continuation)
.await
}

pub fn interfaces(&self) -> &CollectionInterfaces {
&self.interfaces
}
}

pub struct Nft {
Expand All @@ -99,7 +129,7 @@ pub struct Nft {
owner: MsgAddressInt,
manager: MsgAddressInt,
interfaces: NftInterfaces,
json_info: Option<String>,
json_info: Option<JsonInfo>,
contract_subscription: ContractSubscription,
handler: Arc<dyn NftSubscriptionHandler>,
}
Expand Down Expand Up @@ -149,12 +179,28 @@ impl Nft {

// NOTE: Assume that TIP4.1 support is mandatory
let mut info = nft_state.get_info(clock.as_ref())?;
let json_info = if interfaces.tip4_2 {
Some(nft_state.get_json(clock.as_ref())?)
} else {
None

let collection_contract = match transport.get_contract_state(&info.collection).await? {
RawContractState::Exists(state) => state,
RawContractState::NotExists { .. } => return Err(NftError::ContractNotExist.into()),
};

let collection_state = CollectionContractState(&collection_contract);

let json_data = interfaces
.tip4_2
.then(|| nft_state.get_json(clock.as_ref()))
.transpose()?;

let json_url = interfaces
.tip4_2_2
.then(|| nft_state.get_url_parts(clock.as_ref()))
.transpose()?
.map(|part| collection_state.get_nft_url(clock.as_ref(), part))
.transpose()?;

let json_info = json_data.or(json_url);

let contract_subscription = ContractSubscription::subscribe(
clock.clone(),
transport,
Expand Down Expand Up @@ -209,7 +255,7 @@ impl Nft {
&self.manager
}

pub fn metadata(&self) -> &Option<String> {
pub fn metadata(&self) -> &Option<JsonInfo> {
&self.json_info
}

Expand Down Expand Up @@ -447,6 +493,8 @@ impl<'a> CollectionContractState<'a> {

if tip6_interface.supports_interface(tip4_3::collection_contract::INTERFACE_ID)? {
result.tip4_3 = true;
result.tip4_2_2 =
tip6_interface.supports_interface(tip4_2_2::collection_contract::INTERFACE_ID)?;
result.tip4_2 =
tip6_interface.supports_interface(tip4_2::metadata_contract::INTERFACE_ID)?;
}
Expand Down Expand Up @@ -482,11 +530,18 @@ impl<'a> CollectionContractState<'a> {
let cell = nekoton_abi::set_code_salt(code_index, salt)?;
Ok(cell.hash(0))
}

pub fn get_nft_url(&self, clock: &dyn Clock, part: Cell) -> Result<JsonInfo> {
let ctx = self.0.as_context(clock);
let tip4_2_2_interface = tip4_2_2::CollectionContract(ctx);
tip4_2_2_interface.get_nft_url(part).map(JsonInfo::Url)
}
}

#[derive(Copy, Clone, Default)]
pub struct CollectionInterfaces {
pub tip4_3: bool,
pub tip4_2_2: bool,
pub tip4_2: bool,
}

Expand All @@ -506,21 +561,26 @@ impl<'a> NftContractState<'a> {
let ctx = self.0.as_context(clock);
let tip6_interface = tip6::SidContract(ctx);

let mut result = NftInterfaces::default();
result.tip4_1 = tip6_interface.supports_interface(tip4_1::nft_contract::INTERFACE_ID)?;
result.tip4_2 =
tip6_interface.supports_interface(tip4_2::metadata_contract::INTERFACE_ID)?;
result.tip4_2_2 =
tip6_interface.supports_interface(tip4_2_2::metadata_contract::INTERFACE_ID)?;
result.tip4_3 = tip6_interface.supports_interface(tip4_3::nft_contract::INTERFACE_ID)?;
let result = NftInterfaces {
tip4_1: tip6_interface.supports_interface(tip4_1::nft_contract::INTERFACE_ID)?,
tip4_2: tip6_interface.supports_interface(tip4_2::metadata_contract::INTERFACE_ID)?,
tip4_2_2: tip6_interface
.supports_interface(tip4_2_2::metadata_contract::INTERFACE_ID)?,
tip4_3: tip6_interface.supports_interface(tip4_3::nft_contract::INTERFACE_ID)?,
};

Ok((result.tip4_1 || result.tip4_3).then(|| result))
Ok((result.tip4_1 || result.tip4_3).then_some(result))
}

pub fn get_json(&self, clock: &dyn Clock) -> Result<String> {
pub fn get_json(&self, clock: &dyn Clock) -> Result<JsonInfo> {
let ctx = self.0.as_context(clock);
let tip4_2_interface = tip4_2::MetadataContract(ctx);
tip4_2_interface.get_json()
tip4_2_interface.get_json().map(JsonInfo::Json)
}

pub fn get_url_parts(&self, clock: &dyn Clock) -> Result<Cell> {
let ctx = self.0.as_context(clock);
tip4_2_2::MetadataContract(ctx).get_url_parts()
}

pub fn get_info(&self, clock: &dyn Clock) -> Result<GetInfoOutputs> {
Expand Down