use chia_bls::PublicKey;
use chia_protocol::{Bytes32, Coin, CoinSpend};
use chia_puzzle_types::nft::NftMetadata;
use chia_puzzle_types::standard::StandardArgs;
use chia_sdk_driver::{
Did, IntermediateLauncher, Launcher, NftMint, SingletonInfo, SpendContext, StandardLayer,
};
use chia_sdk_types::{conditions::TransferNft, Conditions};
use crate::error::WalletError;
#[derive(Clone, Debug)]
pub struct DidAttribution {
pub launcher_id: Bytes32,
pub inner_puzzle_hash: Bytes32,
}
#[derive(Clone, Debug, Default, serde::Serialize, serde::Deserialize)]
pub struct NftMediaMetadata {
pub data_uris: Vec<String>,
pub data_hash: Option<Bytes32>,
pub metadata_uris: Vec<String>,
pub metadata_hash: Option<Bytes32>,
pub license_uris: Vec<String>,
pub license_hash: Option<Bytes32>,
pub edition_number: u64,
pub edition_total: u64,
}
impl NftMediaMetadata {
fn to_chain(&self) -> NftMetadata {
NftMetadata {
edition_number: if self.edition_number == 0 {
1
} else {
self.edition_number
},
edition_total: if self.edition_total == 0 {
1
} else {
self.edition_total
},
data_uris: self.data_uris.clone(),
data_hash: self.data_hash,
metadata_uris: self.metadata_uris.clone(),
metadata_hash: self.metadata_hash,
license_uris: self.license_uris.clone(),
license_hash: self.license_hash,
}
}
}
#[derive(Clone, Debug)]
pub struct NftMintParams {
pub metadata: NftMediaMetadata,
pub p2_puzzle_hash: Bytes32,
pub royalty_puzzle_hash: Bytes32,
pub royalty_basis_points: u16,
pub did: Option<DidAttribution>,
}
#[derive(Clone, Debug)]
pub struct NftMintResponse {
pub coin_spends: Vec<CoinSpend>,
pub launcher_id: Bytes32,
pub nft_coin: Coin,
}
pub fn mint_nft(
minter_synthetic_key: PublicKey,
selected_coins: Vec<Coin>,
params: NftMintParams,
fee: u64,
) -> Result<NftMintResponse, WalletError> {
if selected_coins.is_empty() {
return Err(WalletError::Parse("selected_coins is empty".to_string()));
}
let minter_puzzle_hash: Bytes32 = StandardArgs::curry_tree_hash(minter_synthetic_key).into();
let total_amount_from_coins: u64 = selected_coins.iter().map(|c| c.amount).sum();
let reserved = fee + 1;
let mut ctx = SpendContext::new();
let p2 = StandardLayer::new(minter_synthetic_key);
let lead_coin = selected_coins[0];
let lead_coin_name = lead_coin.coin_id();
for coin in selected_coins.into_iter().skip(1) {
p2.spend(
&mut ctx,
coin,
Conditions::new().assert_concurrent_spend(lead_coin_name),
)?;
}
let metadata_ptr = ctx.alloc_hashed(¶ms.metadata.to_chain())?;
let transfer_condition = params.did.as_ref().map(|did| {
TransferNft::new(
Some(did.launcher_id),
Vec::new(),
Some(did.inner_puzzle_hash),
)
});
let mut nft_mint = NftMint::new(
metadata_ptr,
params.p2_puzzle_hash,
params.royalty_basis_points,
transfer_condition,
);
nft_mint.royalty_puzzle_hash = params.royalty_puzzle_hash;
let launcher = Launcher::new(lead_coin_name, 1);
let (mint_conditions, nft) = launcher.mint_nft(&mut ctx, &nft_mint)?;
let mut lead_conditions = mint_conditions;
if fee > 0 {
lead_conditions = lead_conditions.reserve_fee(fee);
}
if total_amount_from_coins > reserved {
let hint = ctx.hint(minter_puzzle_hash)?;
lead_conditions = lead_conditions.create_coin(
minter_puzzle_hash,
total_amount_from_coins - reserved,
hint,
);
}
p2.spend(&mut ctx, lead_coin, lead_conditions)?;
Ok(NftMintResponse {
coin_spends: ctx.take(),
launcher_id: nft.info.launcher_id,
nft_coin: nft.coin,
})
}
pub fn mint_nft_with_did(
minter_synthetic_key: PublicKey,
selected_coins: Vec<Coin>,
did: Did,
params: NftMintParams,
fee: u64,
) -> Result<NftMintResponse, WalletError> {
if selected_coins.is_empty() {
return Err(WalletError::Parse("selected_coins is empty".to_string()));
}
let minter_puzzle_hash: Bytes32 = StandardArgs::curry_tree_hash(minter_synthetic_key).into();
let total_amount_from_coins: u64 = selected_coins.iter().map(|c| c.amount).sum();
let mut ctx = SpendContext::new();
let p2 = StandardLayer::new(minter_synthetic_key);
let lead_coin = selected_coins[0];
let lead_coin_name = lead_coin.coin_id();
let did_coin_name = did.coin.coin_id();
for coin in selected_coins.into_iter().skip(1) {
p2.spend(
&mut ctx,
coin,
Conditions::new().assert_concurrent_spend(lead_coin_name),
)?;
}
let did_attr = DidAttribution {
launcher_id: did.info.launcher_id,
inner_puzzle_hash: did.info.inner_puzzle_hash().into(),
};
let metadata_ptr = ctx.alloc_hashed(¶ms.metadata.to_chain())?;
let transfer = TransferNft::new(
Some(did_attr.launcher_id),
Vec::new(),
Some(did_attr.inner_puzzle_hash),
);
let mut nft_mint = NftMint::new(
metadata_ptr,
params.p2_puzzle_hash,
params.royalty_basis_points,
Some(transfer),
);
nft_mint.royalty_puzzle_hash = params.royalty_puzzle_hash;
let (mint_conditions, nft) = IntermediateLauncher::new(did_coin_name, 0, 1)
.create(&mut ctx)?
.mint_nft(&mut ctx, &nft_mint)?;
let mut lead_conditions = Conditions::new().assert_concurrent_spend(did_coin_name);
if fee > 0 {
lead_conditions = lead_conditions.reserve_fee(fee);
}
if total_amount_from_coins > fee {
let hint = ctx.hint(minter_puzzle_hash)?;
lead_conditions =
lead_conditions.create_coin(minter_puzzle_hash, total_amount_from_coins - fee, hint);
}
p2.spend(&mut ctx, lead_coin, lead_conditions)?;
let _recreated_did = did.update(&mut ctx, &p2, mint_conditions)?;
Ok(NftMintResponse {
coin_spends: ctx.take(),
launcher_id: nft.info.launcher_id,
nft_coin: nft.coin,
})
}