use async_trait::async_trait;
use factory::TonContractFactory;
use num_bigint::{BigInt, BigUint};
use strum::IntoStaticStr;
use tonlib_core::cell::{ArcCell, BagOfCells};
use tonlib_core::TonAddress;
use crate::contract::{
factory, MapCellError, MapStackError, TonContractError, TonContractInterface,
};
use crate::meta::MetaDataContent;
use crate::types::TvmStackEntry;
#[derive(Debug, Clone, PartialEq)]
pub struct NftItemData {
pub init: bool,
pub index: BigUint,
pub collection_address: TonAddress,
pub owner_address: TonAddress,
pub individual_content: MetaDataContent,
}
#[derive(IntoStaticStr)]
#[strum(serialize_all = "snake_case")]
enum NftItemContractMethods {
GetNftData,
GetNftContent,
}
#[async_trait]
pub trait NftItemContract: TonContractInterface {
async fn get_nft_data(&self) -> Result<NftItemData, TonContractError> {
let method: &str = NftItemContractMethods::GetNftData.into();
const NFT_DATA_STACK_ELEMENTS: usize = 5;
let address = self.address().clone();
let stack = self.run_get_method(method, Vec::new()).await?.stack;
if stack.len() == NFT_DATA_STACK_ELEMENTS {
let init = stack[0].get_bool().map_stack_error(method, &address)?;
let index = stack[1].get_biguint().map_stack_error(method, &address)?;
let collection_address = stack[2].get_address().map_stack_error(method, &address)?;
let owner_address = stack[3].get_address().map_stack_error(method, &address)?;
let cell = stack[4].get_cell().map_stack_error(method, &address)?;
let individual_content = read_item_metadata_content(
self.factory(),
&index.clone(),
&collection_address.clone(),
&address,
cell,
)
.await?;
Ok(NftItemData {
init,
index,
collection_address,
owner_address,
individual_content,
})
} else {
Err(TonContractError::InvalidMethodResultStackSize {
method: method.to_string(),
address: self.address().clone(),
actual: stack.len(),
expected: NFT_DATA_STACK_ELEMENTS,
})
}
}
async fn get_nft_content(
&self,
index: &BigUint,
individual_content: BagOfCells,
) -> Result<BagOfCells, TonContractError> {
let method: &'static str = NftItemContractMethods::GetNftContent.into();
let index = BigInt::from(index.clone());
let cell = individual_content
.single_root()
.map_cell_error(method, self.address())?;
let input_stack = vec![
TvmStackEntry::Int257(index),
TvmStackEntry::Cell(cell.clone()),
];
let stack = self.run_get_method(method, &input_stack).await?.stack;
if stack.len() == 1 {
let cell = stack[0]
.get_cell()
.map_stack_error(method, self.address())?;
let boc = BagOfCells::from_root(cell.as_ref().clone());
log::trace!("Got Boc: {:?}", boc);
Ok(boc)
} else {
Err(TonContractError::InvalidMethodResultStackSize {
method: method.to_string(),
address: self.address().clone(),
actual: stack.len(),
expected: 1,
})?
}
}
}
impl<T> NftItemContract for T where T: TonContractInterface {}
async fn read_item_metadata_content(
factory: &TonContractFactory,
index: &BigUint,
collection_address: &TonAddress,
item_address: &TonAddress,
cell: ArcCell,
) -> Result<MetaDataContent, TonContractError> {
let mut parser = cell.parser();
let content_representation = parser
.load_byte()
.map_cell_error("get_nft_data", item_address)?;
match content_representation {
0 => {
let dict = parser
.load_dict_snake_format()
.map_cell_error("get_nft_data", item_address)?;
Ok(MetaDataContent::Internal { dict })
}
1 => {
let remaining_bytes = parser.remaining_bytes();
let uri = parser
.load_utf8(remaining_bytes)
.map_cell_error("get_nft_data", item_address)?;
Ok(MetaDataContent::External { uri })
}
_ => {
let contract = factory.get_contract(collection_address);
let boc = BagOfCells::from_root(cell.as_ref().clone());
let nft_content = contract.get_nft_content(index, boc.clone()).await?;
let cell = nft_content
.single_root()
.map_cell_error("get_nft_content", item_address)?
.clone();
let uri = cell
.load_snake_formatted_string()
.map_cell_error("get_nft_content", item_address)?;
Ok(MetaDataContent::External {
uri: uri.to_string(),
})
}
}
}