use async_trait::async_trait;
use num_bigint::BigUint;
use num_traits::Zero;
use strum::IntoStaticStr;
use tonlib_core::cell::{ArcCell, BagOfCells};
use tonlib_core::TonAddress;
use crate::contract::factory::TonContractFactory;
use crate::contract::{
MapCellError, MapStackError, NftItemContract, TonContractError, TonContractInterface,
};
use crate::meta::MetaDataContent;
use crate::types::TvmStackEntry;
#[derive(Debug, Clone, PartialEq)]
pub struct NftCollectionData {
pub next_item_index: i64,
pub collection_content: MetaDataContent,
pub owner_address: TonAddress,
}
#[derive(IntoStaticStr)]
#[strum(serialize_all = "snake_case")]
enum NftCollectionMethods {
GetCollectionData,
GetNftAddressByIndex,
}
#[async_trait]
pub trait NftCollectionContract: TonContractInterface {
async fn get_collection_data(&self) -> Result<NftCollectionData, TonContractError> {
const NFT_COLLECTION_STACK_ELEMENTS: usize = 3;
let method: &str = NftCollectionMethods::GetCollectionData.into();
let address = self.address().clone();
let stack = self.run_get_method(method, Vec::new()).await?.stack;
if stack.len() == NFT_COLLECTION_STACK_ELEMENTS {
let next_item_index = stack[0].get_i64().map_stack_error(method, &address)?;
let cell = stack[1].get_cell().map_stack_error(method, &address)?;
let collection_content =
read_collection_metadata_content(self.factory(), &address, cell).await?;
let owner_address = stack[2].get_address().map_stack_error(method, &address)?;
Ok(NftCollectionData {
next_item_index,
collection_content,
owner_address,
})
} else {
Err(TonContractError::InvalidMethodResultStackSize {
method: method.to_string(),
address: self.address().clone(),
actual: stack.len(),
expected: NFT_COLLECTION_STACK_ELEMENTS,
})
}
}
async fn get_nft_address_by_index(&self, index: i64) -> Result<TonAddress, TonContractError> {
let method: &str = NftCollectionMethods::GetNftAddressByIndex.into();
let input_stack = vec![TvmStackEntry::Int64(index)];
let stack = self.run_get_method(method, &input_stack).await?.stack;
if stack.len() == 1 {
stack[0]
.get_address()
.map_stack_error(method, self.address())
} else {
Err(TonContractError::InvalidMethodResultStackSize {
method: method.to_string(),
address: self.address().clone(),
actual: stack.len(),
expected: 1,
})
}
}
}
impl<T> NftCollectionContract for T where T: TonContractInterface {}
async fn read_collection_metadata_content(
factory: &TonContractFactory,
collection_address: &TonAddress,
cell: ArcCell,
) -> Result<MetaDataContent, TonContractError> {
let mut parser = cell.parser();
let content_representation = parser
.load_byte()
.map_cell_error("get_collection_data", collection_address)?;
match content_representation {
0 => {
let dict = parser
.load_dict_snake_format()
.map_cell_error("get_collection_data", collection_address)?;
Ok(MetaDataContent::Internal { dict })
}
1 => {
let remaining_bytes = parser.remaining_bytes();
let uri = parser
.load_utf8(remaining_bytes)
.map_cell_error("get_collection_data", collection_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(&BigUint::zero(), boc.clone())
.await?;
let cell = nft_content
.single_root()
.map_cell_error("get_nft_content", collection_address)?
.clone();
let uri = cell
.load_snake_formatted_string()
.map_cell_error("get_nft_content", collection_address)?;
Ok(MetaDataContent::External {
uri: uri.to_string(),
})
}
}
}