Skip to content

Commit

Permalink
Spend NFTs
Browse files Browse the repository at this point in the history
  • Loading branch information
Rigidity committed Sep 24, 2024
1 parent 4fa42d6 commit 61a68c7
Show file tree
Hide file tree
Showing 7 changed files with 174 additions and 6 deletions.
21 changes: 19 additions & 2 deletions crates/chia-sdk-driver/src/layers/p2_singleton.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ use clvm_utils::{CurriedProgram, ToTreeHash, TreeHash};
use clvmr::{Allocator, NodePtr};
use hex_literal::hex;

use crate::{DriverError, Layer, Puzzle, SpendContext};
use crate::{DriverError, Layer, Puzzle, Spend, SpendContext};

/// The p2 singleton [`Layer`] allows for requiring that a
/// singleton be spent alongside this coin to authorize it.
Expand All @@ -20,6 +20,23 @@ impl P2Singleton {
}

pub fn spend(
&self,
ctx: &mut SpendContext,
coin_id: Bytes32,
singleton_inner_puzzle_hash: Bytes32,
) -> Result<Spend, DriverError> {
let puzzle = self.construct_puzzle(ctx)?;
let solution = self.construct_solution(
ctx,
P2SingletonSolution {
singleton_inner_puzzle_hash,
my_id: coin_id,
},
)?;
Ok(Spend { puzzle, solution })
}

pub fn spend_coin(
&self,
ctx: &mut SpendContext,
coin: Coin,
Expand Down Expand Up @@ -187,7 +204,7 @@ mod tests {
)?;

let p2_coin = Coin::new(coin.coin_id(), p2_singleton_hash, 1);
p2_singleton.spend(ctx, p2_coin, puzzle_hash)?;
p2_singleton.spend_coin(ctx, p2_coin, puzzle_hash)?;

let inner_solution = p2
.spend_with_conditions(
Expand Down
7 changes: 7 additions & 0 deletions napi/index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@ export interface ParsedNft {
}
export declare function parseNftInfo(puzzleReveal: Uint8Array): ParsedNft | null
export declare function parseUnspentNft(parentCoin: Coin, parentPuzzleReveal: Uint8Array, parentSolution: Uint8Array, coin: Coin): Nft | null
export declare function spendNft(nft: Nft, innerSpend: Spend): Array<CoinSpend>
export interface NftMint {
metadata: NftMetadata
p2PuzzleHash: Uint8Array
Expand All @@ -61,3 +62,9 @@ export interface MintedNfts {
parentConditions: Array<Uint8Array>
}
export declare function mintNfts(parentCoinId: Uint8Array, nftMints: Array<NftMint>): MintedNfts
export interface Spend {
puzzle: Uint8Array
solution: Uint8Array
}
export declare function spendP2Standard(syntheticKey: Uint8Array, conditions: Array<Uint8Array>): Spend
export declare function spendP2Singleton(launcherId: Uint8Array, coinId: Uint8Array, singletonInnerPuzzleHash: Uint8Array): Spend
5 changes: 4 additions & 1 deletion napi/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -310,9 +310,12 @@ if (!nativeBinding) {
throw new Error(`Failed to load native binding`)
}

const { toCoinId, parseNftInfo, parseUnspentNft, mintNfts } = nativeBinding
const { toCoinId, parseNftInfo, parseUnspentNft, spendNft, mintNfts, spendP2Standard, spendP2Singleton } = nativeBinding

module.exports.toCoinId = toCoinId
module.exports.parseNftInfo = parseNftInfo
module.exports.parseUnspentNft = parseUnspentNft
module.exports.spendNft = spendNft
module.exports.mintNfts = mintNfts
module.exports.spendP2Standard = spendP2Standard
module.exports.spendP2Singleton = spendP2Singleton
2 changes: 2 additions & 0 deletions napi/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,10 +10,12 @@ mod coin_spend;
mod lineage_proof;
mod nft;
mod nft_mint;
mod spend;
mod traits;

pub use coin::*;
pub use coin_spend::*;
pub use lineage_proof::*;
pub use nft::*;
pub use nft_mint::*;
pub use spend::*;
42 changes: 40 additions & 2 deletions napi/src/nft.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
use chia::puzzles::nft;
use chia_wallet_sdk::{self as sdk, Primitive};
use chia_wallet_sdk::{self as sdk, Primitive, SpendContext};
use clvmr::{
serde::{node_from_bytes, node_to_bytes},
Allocator,
Expand All @@ -8,7 +8,7 @@ use napi::bindgen_prelude::*;

use crate::{
traits::{FromJs, IntoJs, IntoRust},
Coin, LineageProof,
Coin, CoinSpend, LineageProof, Spend,
};

#[napi(object)]
Expand All @@ -28,6 +28,16 @@ impl IntoJs<Nft> for sdk::Nft<nft::NftMetadata> {
}
}

impl FromJs<Nft> for sdk::Nft<nft::NftMetadata> {
fn from_js(nft: Nft) -> Result<Self> {
Ok(sdk::Nft {
coin: nft.coin.into_rust()?,
proof: nft.lineage_proof.into_rust()?,
info: nft.info.into_rust()?,
})
}
}

#[napi(object)]
pub struct NftInfo {
pub launcher_id: Uint8Array,
Expand All @@ -53,6 +63,20 @@ impl IntoJs<NftInfo> for sdk::NftInfo<nft::NftMetadata> {
}
}

impl FromJs<NftInfo> for sdk::NftInfo<nft::NftMetadata> {
fn from_js(info: NftInfo) -> Result<Self> {
Ok(sdk::NftInfo {
launcher_id: info.launcher_id.into_rust()?,
metadata: info.metadata.into_rust()?,
metadata_updater_puzzle_hash: info.metadata_updater_puzzle_hash.into_rust()?,
current_owner: info.current_owner.map(IntoRust::into_rust).transpose()?,
royalty_puzzle_hash: info.royalty_puzzle_hash.into_rust()?,
royalty_ten_thousandths: info.royalty_ten_thousandths,
p2_puzzle_hash: info.p2_puzzle_hash.into_rust()?,
})
}
}

#[napi(object)]
pub struct NftMetadata {
pub edition_number: BigInt,
Expand Down Expand Up @@ -149,3 +173,17 @@ pub fn parse_unspent_nft(

Ok(Some(nft.into_js()?))
}

#[napi]
pub fn spend_nft(nft: Nft, inner_spend: Spend) -> Result<Vec<CoinSpend>> {
let mut ctx = SpendContext::new();

let nft = sdk::Nft::<nft::NftMetadata>::from_js(nft)?;
let puzzle = node_from_bytes(&mut ctx.allocator, &inner_spend.puzzle)?;
let solution = node_from_bytes(&mut ctx.allocator, &inner_spend.solution)?;

nft.spend(&mut ctx, sdk::Spend { puzzle, solution })
.map_err(|error| Error::from_reason(error.to_string()))?;

ctx.take().into_iter().map(IntoJs::into_js).collect()
}
68 changes: 68 additions & 0 deletions napi/src/spend.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
use chia::{bls::PublicKey, clvm_traits::FromClvm};
use chia_wallet_sdk::{
Condition, Conditions, P2Singleton, SpendContext, SpendWithConditions, StandardLayer,
};
use clvmr::{
serde::{node_from_bytes, node_to_bytes},
NodePtr,
};
use napi::bindgen_prelude::*;

use crate::traits::{FromJs, IntoRust};

#[napi(object)]
pub struct Spend {
pub puzzle: Uint8Array,
pub solution: Uint8Array,
}

#[napi]
pub fn spend_p2_standard(synthetic_key: Uint8Array, conditions: Vec<Uint8Array>) -> Result<Spend> {
let mut ctx = SpendContext::new();

let synthetic_key = PublicKey::from_js(synthetic_key)?;
let p2 = StandardLayer::new(synthetic_key);

let mut spend_conditions = Conditions::new();

for condition in conditions {
let condition = node_from_bytes(&mut ctx.allocator, &condition)?;
spend_conditions = spend_conditions.with(
Condition::<NodePtr>::from_clvm(&ctx.allocator, condition)
.map_err(|error| Error::from_reason(error.to_string()))?,
);
}

let spend = p2
.spend_with_conditions(&mut ctx, spend_conditions)
.map_err(|error| Error::from_reason(error.to_string()))?;

Ok(Spend {
puzzle: node_to_bytes(&ctx.allocator, spend.puzzle)?.into(),
solution: node_to_bytes(&ctx.allocator, spend.solution)?.into(),
})
}

#[napi]
pub fn spend_p2_singleton(
launcher_id: Uint8Array,
coin_id: Uint8Array,
singleton_inner_puzzle_hash: Uint8Array,
) -> Result<Spend> {
let mut ctx = SpendContext::new();

let p2 = P2Singleton::new(launcher_id.into_rust()?);

let spend = p2
.spend(
&mut ctx,
coin_id.into_rust()?,
singleton_inner_puzzle_hash.into_rust()?,
)
.map_err(|error| Error::from_reason(error.to_string()))?;

Ok(Spend {
puzzle: node_to_bytes(&ctx.allocator, spend.puzzle)?.into(),
solution: node_to_bytes(&ctx.allocator, spend.solution)?.into(),
})
}
35 changes: 34 additions & 1 deletion napi/src/traits.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,7 @@
use chia::protocol::{BytesImpl, Program};
use chia::{
bls::PublicKey,
protocol::{BytesImpl, Program},
};
use napi::bindgen_prelude::*;

pub(crate) trait IntoJs<T> {
Expand Down Expand Up @@ -40,6 +43,36 @@ impl<const N: usize> FromJs<Uint8Array> for BytesImpl<N> {
}
}

impl<const N: usize> IntoJs<Uint8Array> for [u8; N] {
fn into_js(self) -> Result<Uint8Array> {
Ok(Uint8Array::new(self.to_vec()))
}
}

impl<const N: usize> FromJs<Uint8Array> for [u8; N] {
fn from_js(js_value: Uint8Array) -> Result<Self> {
js_value.to_vec().try_into().map_err(|bytes: Vec<u8>| {
Error::from_reason(format!("Expected length {N}, found {}", bytes.len()))
})
}
}

impl IntoJs<Uint8Array> for PublicKey {
fn into_js(self) -> Result<Uint8Array> {
Ok(Uint8Array::new(self.to_bytes().to_vec()))
}
}

impl FromJs<Uint8Array> for PublicKey {
fn from_js(js_value: Uint8Array) -> Result<Self>
where
Self: Sized,
{
PublicKey::from_bytes(&js_value.into_rust()?)
.map_err(|error| Error::from_reason(error.to_string()))
}
}

impl IntoJs<Uint8Array> for Program {
fn into_js(self) -> Result<Uint8Array> {
Ok(Uint8Array::new(self.to_vec()))
Expand Down

0 comments on commit 61a68c7

Please sign in to comment.