Skip to content

Commit

Permalink
NFT bindings
Browse files Browse the repository at this point in the history
  • Loading branch information
Rigidity committed Sep 24, 2024
1 parent 219b9d1 commit 4b6a281
Show file tree
Hide file tree
Showing 8 changed files with 224 additions and 3 deletions.
1 change: 1 addition & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions napi/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ napi = { workspace = true, features = ["napi6"] }
napi-derive = { workspace = true }
chia-wallet-sdk = { workspace = true }
chia = { workspace = true }
clvmr = { workspace = true }

[build-dependencies]
napi-build = "2.0.1"
35 changes: 35 additions & 0 deletions napi/index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,3 +14,38 @@ export interface CoinSpend {
puzzleReveal: Uint8Array
solution: Uint8Array
}
export interface LineageProof {
parentParentCoinInfo: Uint8Array
parentInnerPuzzleHash?: Uint8Array
parentAmount: bigint
}
export interface Nft {
coin: Coin
lineageProof: LineageProof
info: NftInfo
}
export interface NftInfo {
launcherId: Uint8Array
metadata: NftMetadata
metadataUpdaterPuzzleHash: Uint8Array
currentOwner?: Uint8Array
royaltyPuzzleHash: Uint8Array
royaltyTenThousandths: number
p2PuzzleHash: Uint8Array
}
export interface NftMetadata {
editionNumber: bigint
editionTotal: bigint
dataUris: Array<string>
dataHash?: Uint8Array
metadataUris: Array<string>
metadataHash?: Uint8Array
licenseUris: Array<string>
licenseHash?: Uint8Array
}
export interface ParsedNft {
nftInfo: NftInfo
innerPuzzle: Uint8Array
}
export declare function parseNftInfo(puzzleReveal: Uint8Array): ParsedNft | null
export declare function parseUnspentNft(parentCoin: Coin, parentPuzzleReveal: Uint8Array, parentSolution: Uint8Array, coin: Coin): Nft | null
4 changes: 3 additions & 1 deletion napi/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -310,6 +310,8 @@ if (!nativeBinding) {
throw new Error(`Failed to load native binding`)
}

const { toCoinId } = nativeBinding
const { toCoinId, parseNftInfo, parseUnspentNft } = nativeBinding

module.exports.toCoinId = toCoinId
module.exports.parseNftInfo = parseNftInfo
module.exports.parseUnspentNft = parseUnspentNft
6 changes: 6 additions & 0 deletions napi/src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,11 +1,17 @@
#![allow(missing_debug_implementations)]
#![allow(missing_copy_implementations)]
#![allow(clippy::needless_pass_by_value)]

#[macro_use]
extern crate napi_derive;

mod coin;
mod coin_spend;
mod lineage_proof;
mod nft;
mod traits;

pub use coin::*;
pub use coin_spend::*;
pub use lineage_proof::*;
pub use nft::*;
45 changes: 45 additions & 0 deletions napi/src/lineage_proof.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
use chia::puzzles;
use napi::bindgen_prelude::*;

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

#[napi(object)]
pub struct LineageProof {
pub parent_parent_coin_info: Uint8Array,
pub parent_inner_puzzle_hash: Option<Uint8Array>,
pub parent_amount: BigInt,
}

impl FromJs<LineageProof> for puzzles::Proof {
fn from_js(value: LineageProof) -> Result<Self> {
if let Some(parent_inner_puzzle_hash) = value.parent_inner_puzzle_hash {
Ok(Self::Lineage(puzzles::LineageProof {
parent_parent_coin_info: value.parent_parent_coin_info.into_rust()?,
parent_inner_puzzle_hash: parent_inner_puzzle_hash.into_rust()?,
parent_amount: value.parent_amount.into_rust()?,
}))
} else {
Ok(Self::Eve(puzzles::EveProof {
parent_parent_coin_info: value.parent_parent_coin_info.into_rust()?,
parent_amount: value.parent_amount.into_rust()?,
}))
}
}
}

impl IntoJs<LineageProof> for puzzles::Proof {
fn into_js(self) -> Result<LineageProof> {
match self {
Self::Lineage(proof) => Ok(LineageProof {
parent_parent_coin_info: proof.parent_parent_coin_info.into_js()?,
parent_inner_puzzle_hash: Some(proof.parent_inner_puzzle_hash.into_js()?),
parent_amount: proof.parent_amount.into_js()?,
}),
Self::Eve(proof) => Ok(LineageProof {
parent_parent_coin_info: proof.parent_parent_coin_info.into_js()?,
parent_inner_puzzle_hash: None,
parent_amount: proof.parent_amount.into_js()?,
}),
}
}
}
133 changes: 133 additions & 0 deletions napi/src/nft.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,133 @@
use chia::puzzles::nft;
use chia_wallet_sdk::{self as sdk, Primitive};
use clvmr::{
serde::{node_from_bytes, node_to_bytes},
Allocator,
};
use napi::bindgen_prelude::*;

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

#[napi(object)]
pub struct Nft {
pub coin: Coin,
pub lineage_proof: LineageProof,
pub info: NftInfo,
}

impl IntoJs<Nft> for sdk::Nft<nft::NftMetadata> {
fn into_js(self) -> Result<Nft> {
Ok(Nft {
coin: self.coin.into_js()?,
lineage_proof: self.proof.into_js()?,
info: self.info.into_js()?,
})
}
}

#[napi(object)]
pub struct NftInfo {
pub launcher_id: Uint8Array,
pub metadata: NftMetadata,
pub metadata_updater_puzzle_hash: Uint8Array,
pub current_owner: Option<Uint8Array>,
pub royalty_puzzle_hash: Uint8Array,
pub royalty_ten_thousandths: u16,
pub p2_puzzle_hash: Uint8Array,
}

impl IntoJs<NftInfo> for sdk::NftInfo<nft::NftMetadata> {
fn into_js(self) -> Result<NftInfo> {
Ok(NftInfo {
launcher_id: self.launcher_id.into_js()?,
metadata: self.metadata.into_js()?,
metadata_updater_puzzle_hash: self.metadata_updater_puzzle_hash.into_js()?,
current_owner: self.current_owner.map(IntoJs::into_js).transpose()?,
royalty_puzzle_hash: self.royalty_puzzle_hash.into_js()?,
royalty_ten_thousandths: self.royalty_ten_thousandths,
p2_puzzle_hash: self.p2_puzzle_hash.into_js()?,
})
}
}

#[napi(object)]
pub struct NftMetadata {
pub edition_number: BigInt,
pub edition_total: BigInt,
pub data_uris: Vec<String>,
pub data_hash: Option<Uint8Array>,
pub metadata_uris: Vec<String>,
pub metadata_hash: Option<Uint8Array>,
pub license_uris: Vec<String>,
pub license_hash: Option<Uint8Array>,
}

impl IntoJs<NftMetadata> for nft::NftMetadata {
fn into_js(self) -> Result<NftMetadata> {
Ok(NftMetadata {
edition_number: self.edition_number.into_js()?,
edition_total: self.edition_total.into_js()?,
data_uris: self.data_uris,
data_hash: self.data_hash.map(IntoJs::into_js).transpose()?,
metadata_uris: self.metadata_uris,
metadata_hash: self.metadata_hash.map(IntoJs::into_js).transpose()?,
license_uris: self.license_uris,
license_hash: self.license_hash.map(IntoJs::into_js).transpose()?,
})
}
}

#[napi(object)]
pub struct ParsedNft {
pub nft_info: NftInfo,
pub inner_puzzle: Uint8Array,
}

#[napi]
pub fn parse_nft_info(puzzle_reveal: Uint8Array) -> Result<Option<ParsedNft>> {
let mut allocator = Allocator::new();
let ptr = node_from_bytes(&mut allocator, puzzle_reveal.as_ref())?;
let puzzle = sdk::Puzzle::parse(&allocator, ptr);

let Some((nft_info, inner_puzzle)) =
sdk::NftInfo::<nft::NftMetadata>::parse(&allocator, puzzle)
.map_err(|error| Error::from_reason(error.to_string()))?
else {
return Ok(None);
};

Ok(Some(ParsedNft {
nft_info: nft_info.into_js()?,
inner_puzzle: node_to_bytes(&allocator, inner_puzzle.ptr())?.into(),
}))
}

#[napi]
pub fn parse_unspent_nft(
parent_coin: Coin,
parent_puzzle_reveal: Uint8Array,
parent_solution: Uint8Array,
coin: Coin,
) -> Result<Option<Nft>> {
let mut allocator = Allocator::new();
let parent_ptr = node_from_bytes(&mut allocator, parent_puzzle_reveal.as_ref())?;
let parent_puzzle = sdk::Puzzle::parse(&allocator, parent_ptr);
let parent_solution = node_from_bytes(&mut allocator, parent_solution.as_ref())?;

let Some(nft) = sdk::Nft::<nft::NftMetadata>::from_parent_spend(
&mut allocator,
parent_coin.into_rust()?,
parent_puzzle,
parent_solution,
coin.into_rust()?,
)
.map_err(|error| Error::from_reason(error.to_string()))?
else {
return Ok(None);
};

Ok(Some(nft.into_js()?))
}
2 changes: 0 additions & 2 deletions napi/src/traits.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,6 @@ pub(crate) trait IntoRust<T> {
fn into_rust(self) -> Result<T>;
}

// Implement ToRust for every type that implements FromJs

impl<T, U> IntoRust<U> for T
where
U: FromJs<T>,
Expand Down

0 comments on commit 4b6a281

Please sign in to comment.