chia-sdk-driver 0.33.0

Driver code for interacting with standard puzzles on the Chia blockchain.
Documentation
use std::fmt::Debug;

use chia_protocol::Bytes32;
use chia_puzzle_types::singleton::SingletonStruct;
use chia_puzzles::{SINGLETON_LAUNCHER_HASH, SINGLETON_TOP_LAYER_V1_1_HASH};
use chia_sdk_types::puzzles::{
    CatNftMetadata, VERIFICATION_LAYER_PUZZLE_HASH, VerificationLayer1stCurryArgs,
    VerificationLayer2ndCurryArgs, VerificationLayerSolution,
};
use clvm_traits::{FromClvm, ToClvm, clvm_list};
use clvm_utils::{CurriedProgram, ToTreeHash};
use clvmr::{Allocator, NodePtr};

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

#[derive(Debug, Clone, PartialEq, Eq)]
pub struct VerificationLayer {
    pub revocation_singleton_launcher_id: Bytes32,
    pub verified_data: VerifiedData,
}

impl VerificationLayer {
    pub fn new(revocation_singleton_launcher_id: Bytes32, verified_data: VerifiedData) -> Self {
        Self {
            revocation_singleton_launcher_id,
            verified_data,
        }
    }
}

impl Layer for VerificationLayer {
    type Solution = VerificationLayerSolution;

    fn parse_puzzle(allocator: &Allocator, puzzle: Puzzle) -> Result<Option<Self>, DriverError> {
        let Some(puzzle_2nd_curry) = puzzle.as_curried() else {
            return Ok(None);
        };

        let puzzle_2nd_curry =
            CurriedProgram::<NodePtr, NodePtr>::from_clvm(allocator, puzzle_2nd_curry.curried_ptr)?;
        let puzzle_1st_curry = Puzzle::parse(allocator, puzzle_2nd_curry.program);
        let Some(puzzle_1st_curry) = puzzle_1st_curry.as_curried() else {
            return Ok(None);
        };

        if puzzle_1st_curry.mod_hash != VERIFICATION_LAYER_PUZZLE_HASH {
            return Ok(None);
        }

        let args_2nd_curry = VerificationLayer2ndCurryArgs::<VerifiedData>::from_clvm(
            allocator,
            puzzle_2nd_curry.args,
        )?;
        let args_1st_curry =
            VerificationLayer1stCurryArgs::from_clvm(allocator, puzzle_1st_curry.args)?;

        if args_1st_curry
            .revocation_singleton_struct
            .launcher_puzzle_hash
            != SINGLETON_LAUNCHER_HASH.into()
            || args_1st_curry.revocation_singleton_struct.mod_hash
                != SINGLETON_TOP_LAYER_V1_1_HASH.into()
        {
            return Err(DriverError::NonStandardLayer);
        }

        if args_2nd_curry.self_hash
            != VerificationLayer1stCurryArgs::curry_tree_hash(
                args_1st_curry.revocation_singleton_struct.launcher_id,
            )
            .into()
        {
            return Err(DriverError::NonStandardLayer);
        }

        Ok(Some(Self {
            revocation_singleton_launcher_id: args_1st_curry
                .revocation_singleton_struct
                .launcher_id,
            verified_data: args_2nd_curry.verified_data,
        }))
    }

    fn parse_solution(
        allocator: &Allocator,
        solution: NodePtr,
    ) -> Result<Self::Solution, DriverError> {
        VerificationLayerSolution::from_clvm(allocator, solution).map_err(DriverError::FromClvm)
    }

    fn construct_puzzle(&self, ctx: &mut SpendContext) -> Result<NodePtr, DriverError> {
        let puzzle_1st_curry = ctx.curry(VerificationLayer1stCurryArgs {
            revocation_singleton_struct: SingletonStruct::new(
                self.revocation_singleton_launcher_id,
            ),
        })?;
        let self_hash =
            VerificationLayer1stCurryArgs::curry_tree_hash(self.revocation_singleton_launcher_id)
                .into();

        CurriedProgram {
            program: puzzle_1st_curry,
            args: VerificationLayer2ndCurryArgs {
                self_hash,
                verified_data: self.verified_data.clone(),
            },
        }
        .to_clvm(ctx)
        .map_err(DriverError::ToClvm)
    }

    fn construct_solution(
        &self,
        ctx: &mut SpendContext,
        solution: Self::Solution,
    ) -> Result<NodePtr, DriverError> {
        ctx.alloc(&solution)
    }
}

#[derive(ToClvm, FromClvm, Debug, Clone, PartialEq, Eq)]
#[clvm(list)]
pub struct VerifiedData {
    pub version: u32,
    pub asset_id: Bytes32,
    pub data_hash: Bytes32,
    #[clvm(rest)]
    pub comment: String,
}

impl VerifiedData {
    pub fn data_hash_from_cat_nft_metadata(metadata: &CatNftMetadata) -> Bytes32 {
        clvm_list!(
            metadata.ticker.clone(),
            metadata.name.clone(),
            metadata.description.clone(),
            metadata.image_hash,
            metadata.metadata_hash,
            metadata.license_hash,
        )
        .tree_hash()
        .into()
    }

    pub fn from_cat_nft_metadata(
        asset_id: Bytes32,
        metadata: &CatNftMetadata,
        comment: String,
    ) -> Self {
        Self {
            version: 1,
            asset_id,
            data_hash: Self::data_hash_from_cat_nft_metadata(metadata),
            comment,
        }
    }

    pub fn get_hint(&self) -> Bytes32 {
        self.data_hash
    }
}