chik-sdk-driver 0.25.0

Driver code for interacting with standard puzzles on the Chik blockchain.
Documentation
use chik_protocol::{Bytes32, Coin};
use chik_puzzle_types::{
    cat::CatArgs,
    offer::{NotarizedPayment, Payment},
    Memos,
};
use chik_puzzles::SETTLEMENT_PAYMENT_HASH;
use chik_sdk_types::{
    conditions::{
        AssertBeforeSecondsAbsolute, AssertPuzzleAnnouncement, AssertSecondsAbsolute, CreateCoin,
    },
    puzzles::{
        AugmentedConditionArgs, AugmentedConditionSolution, P2OneOfManySolution, RevocationArgs,
        SingletonMember, SingletonMemberSolution,
    },
    MerkleTree, Mod,
};
use klvm_traits::{klvm_list, klvm_quote, match_list, ToKlvm};
use klvm_utils::{ToTreeHash, TreeHash};
use klvmr::{Allocator, NodePtr};

use crate::{
    member_puzzle_hash, payment_assertion, DriverError, InnerPuzzleSpend, Layer, MipsSpend,
    P2OneOfManyLayer, Spend, SpendContext,
};

use super::OptionType;

pub type OptionDelegatedPuzzle = (
    u8,
    match_list!(
        AssertBeforeSecondsAbsolute,
        AssertPuzzleAnnouncement,
        CreateCoin<Bytes32>
    ),
);

#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub struct OptionUnderlying {
    pub launcher_id: Bytes32,
    pub creator_puzzle_hash: Bytes32,
    pub seconds: u64,
    pub amount: u64,
    pub strike_type: OptionType,
}

impl OptionUnderlying {
    pub fn new(
        launcher_id: Bytes32,
        creator_puzzle_hash: Bytes32,
        seconds: u64,
        amount: u64,
        strike_type: OptionType,
    ) -> Self {
        Self {
            launcher_id,
            creator_puzzle_hash,
            seconds,
            amount,
            strike_type,
        }
    }

    pub fn merkle_tree(&self) -> MerkleTree {
        MerkleTree::new(&[self.exercise_path_hash(), self.clawback_path_hash()])
    }

    pub fn exercise_path_hash(&self) -> Bytes32 {
        let singleton_member_hash = SingletonMember::new(self.launcher_id).curry_tree_hash();
        member_puzzle_hash(0, Vec::new(), singleton_member_hash, true).into()
    }

    pub fn clawback_path_hash(&self) -> Bytes32 {
        AugmentedConditionArgs::<TreeHash, TreeHash>::new(
            AssertSecondsAbsolute::new(self.seconds).into(),
            self.creator_puzzle_hash.into(),
        )
        .curry_tree_hash()
        .into()
    }

    pub fn into_1_of_n(&self) -> P2OneOfManyLayer {
        P2OneOfManyLayer::new(self.merkle_tree().root())
    }

    pub fn requested_payment_ptr(
        &self,
        allocator: &mut Allocator,
    ) -> Result<NotarizedPayment<NodePtr>, DriverError> {
        Ok(NotarizedPayment {
            nonce: self.launcher_id,
            payments: vec![Payment {
                puzzle_hash: self.creator_puzzle_hash,
                amount: self.strike_type.amount(),
                memos: if self.strike_type.is_hinted() {
                    Memos::Some(vec![self.creator_puzzle_hash].to_klvm(allocator)?)
                } else {
                    Memos::None
                },
            }],
        })
    }

    pub fn requested_payment_hash(&self) -> NotarizedPayment<TreeHash> {
        NotarizedPayment {
            nonce: self.launcher_id,
            payments: vec![Payment {
                puzzle_hash: self.creator_puzzle_hash,
                amount: self.strike_type.amount(),
                memos: if self.strike_type.is_hinted() {
                    Memos::Some(vec![self.creator_puzzle_hash].tree_hash())
                } else {
                    Memos::None
                },
            }],
        }
    }

    pub fn delegated_puzzle(&self) -> OptionDelegatedPuzzle {
        let puzzle_hash = match self.strike_type {
            OptionType::Xck { .. } => SETTLEMENT_PAYMENT_HASH.into(),
            OptionType::Cat { asset_id, .. } => {
                CatArgs::curry_tree_hash(asset_id, SETTLEMENT_PAYMENT_HASH.into()).into()
            }
            OptionType::RevocableCat {
                asset_id,
                hidden_puzzle_hash,
                ..
            } => CatArgs::curry_tree_hash(
                asset_id,
                RevocationArgs::new(hidden_puzzle_hash, SETTLEMENT_PAYMENT_HASH.into())
                    .curry_tree_hash(),
            )
            .into(),
            OptionType::Nft {
                settlement_puzzle_hash,
                ..
            } => settlement_puzzle_hash,
        };

        klvm_quote!(klvm_list!(
            AssertBeforeSecondsAbsolute::new(self.seconds),
            payment_assertion(puzzle_hash, &self.requested_payment_hash()),
            CreateCoin::new(SETTLEMENT_PAYMENT_HASH.into(), self.amount, Memos::None)
        ))
    }

    pub fn exercise_spend(
        &self,
        ctx: &mut SpendContext,
        singleton_inner_puzzle_hash: Bytes32,
        singleton_amount: u64,
    ) -> Result<Spend, DriverError> {
        let merkle_tree = self.merkle_tree();

        let custody_hash: TreeHash = self.exercise_path_hash().into();
        let merkle_proof = merkle_tree
            .proof(custody_hash.into())
            .ok_or(DriverError::InvalidMerkleProof)?;

        let delegated_puzzle = ctx.alloc(&self.delegated_puzzle())?;
        let delegated_spend = Spend::new(delegated_puzzle, NodePtr::NIL);

        let mut mips = MipsSpend::new(delegated_spend);

        let singleton_member_puzzle = ctx.curry(SingletonMember::new(self.launcher_id))?;
        let singleton_member_solution = ctx.alloc(&SingletonMemberSolution::new(
            singleton_inner_puzzle_hash,
            singleton_amount,
        ))?;
        mips.members.insert(
            custody_hash,
            InnerPuzzleSpend::new(
                0,
                Vec::new(),
                Spend::new(singleton_member_puzzle, singleton_member_solution),
            ),
        );

        let spend = mips.spend(ctx, custody_hash)?;

        P2OneOfManyLayer::new(merkle_tree.root()).construct_spend(
            ctx,
            P2OneOfManySolution::new(merkle_proof, spend.puzzle, spend.solution),
        )
    }

    pub fn clawback_spend(
        &self,
        ctx: &mut SpendContext,
        spend: Spend,
    ) -> Result<Spend, DriverError> {
        let merkle_tree = self.merkle_tree();

        let puzzle_hash = self.clawback_path_hash();
        let merkle_proof = merkle_tree
            .proof(puzzle_hash)
            .ok_or(DriverError::InvalidMerkleProof)?;

        let puzzle = ctx.curry(AugmentedConditionArgs::<NodePtr, NodePtr>::new(
            AssertSecondsAbsolute::new(self.seconds).into(),
            spend.puzzle,
        ))?;

        let solution = ctx.alloc(&AugmentedConditionSolution::new(spend.solution))?;

        P2OneOfManyLayer::new(merkle_tree.root()).construct_spend(
            ctx,
            P2OneOfManySolution::new(merkle_proof, puzzle, solution),
        )
    }

    pub fn exercise_coin_spend(
        &self,
        ctx: &mut SpendContext,
        coin: Coin,
        singleton_inner_puzzle_hash: Bytes32,
        singleton_amount: u64,
    ) -> Result<(), DriverError> {
        let spend = self.exercise_spend(ctx, singleton_inner_puzzle_hash, singleton_amount)?;
        ctx.spend(coin, spend)
    }

    pub fn clawback_coin_spend(
        &self,
        ctx: &mut SpendContext,
        coin: Coin,
        spend: Spend,
    ) -> Result<(), DriverError> {
        let spend = self.clawback_spend(ctx, spend)?;
        ctx.spend(coin, spend)
    }
}

impl ToTreeHash for OptionUnderlying {
    fn tree_hash(&self) -> TreeHash {
        self.into_1_of_n().tree_hash()
    }
}