chia-sdk-driver 0.33.0

Driver code for interacting with standard puzzles on the Chia blockchain.
Documentation
use chia_protocol::{Bytes32, Coin};
use chia_puzzle_types::{
    Memos,
    cat::{CatArgs, GenesisByCoinIdTailArgs},
    offer::NotarizedPayment,
};
use chia_puzzles::{SETTLEMENT_PAYMENT_HASH, SINGLETON_LAUNCHER_HASH};
use chia_sdk_types::conditions::{AssertPuzzleAnnouncement, CreateCoin};

use crate::{
    Asset, Cat, Delta, DriverError, Launcher, OptionLauncher, OptionLauncherInfo, OptionType,
    Output, OutputSet, SpendContext, SpendKind,
};

#[derive(Debug, Clone)]
pub struct FungibleSpends<A> {
    pub items: Vec<FungibleSpend<A>>,
    pub payment_assertions: Vec<AssertPuzzleAnnouncement>,
}

impl<A> FungibleSpends<A>
where
    A: FungibleAsset,
{
    pub fn new() -> Self {
        Self::default()
    }

    pub fn selected_amount(&self) -> u64 {
        self.items
            .iter()
            .filter(|item| !item.ephemeral)
            .map(|item| item.asset.amount())
            .sum()
    }

    pub fn output_source(
        &mut self,
        ctx: &mut SpendContext,
        output: &Output,
    ) -> Result<usize, DriverError> {
        if let Some(index) = self
            .items
            .iter()
            .position(|item| item.kind.is_allowed(output, &item.asset.constraints()))
        {
            return Ok(index);
        }

        self.intermediate_source(ctx)
    }

    pub fn notarized_payment_source(
        &mut self,
        notarized_payment: &NotarizedPayment,
    ) -> Result<usize, DriverError> {
        if let Some(index) = self.items.iter().position(|item| {
            item.kind.is_settlement()
                && notarized_payment.payments.iter().all(|payment| {
                    item.kind.is_allowed(
                        &Output::new(payment.puzzle_hash, payment.amount),
                        &item.asset.constraints(),
                    )
                })
        }) {
            return Ok(index);
        }

        self.intermediate_settlement_source()?
            .ok_or(DriverError::NoSourceForOutput)
    }

    pub fn run_tail_source(&mut self, ctx: &mut SpendContext) -> Result<usize, DriverError> {
        if let Some(index) = self
            .items
            .iter()
            .position(|item| item.kind.can_run_cat_tail())
        {
            return Ok(index);
        }

        self.intermediate_source(ctx)
    }

    pub fn cat_issuance_source(
        &mut self,
        ctx: &mut SpendContext,
        asset_id: Option<Bytes32>,
        amount: u64,
    ) -> Result<usize, DriverError> {
        if let Some(index) = self.items.iter().position(|item| {
            item.kind.is_allowed(
                &Output::new(
                    CatArgs::curry_tree_hash(
                        asset_id.unwrap_or_else(|| {
                            GenesisByCoinIdTailArgs::curry_tree_hash(item.asset.coin_id()).into()
                        }),
                        item.asset.p2_puzzle_hash().into(),
                    )
                    .into(),
                    amount,
                ),
                &item.asset.constraints(),
            )
        }) {
            return Ok(index);
        }

        self.intermediate_source(ctx)
    }

    pub fn intermediate_source(&mut self, ctx: &mut SpendContext) -> Result<usize, DriverError> {
        let Some((index, amount)) = self.items.iter().enumerate().find_map(|(index, item)| {
            item.kind
                .find_amount(item.asset.p2_puzzle_hash(), &item.asset.constraints())
                .map(|amount| (index, amount))
        }) else {
            return Err(DriverError::NoSourceForOutput);
        };

        let source = &mut self.items[index];

        source.kind.create_intermediate_coin(CreateCoin::new(
            source.asset.p2_puzzle_hash(),
            amount,
            source
                .asset
                .child_memos(ctx, source.asset.p2_puzzle_hash())?,
        ));

        let child = FungibleSpend::new(
            source
                .asset
                .make_child(source.asset.p2_puzzle_hash(), amount),
            true,
        );

        self.items.push(child);

        Ok(self.items.len() - 1)
    }

    pub fn intermediate_settlement_source(&mut self) -> Result<Option<usize>, DriverError> {
        let Some((index, amount)) = self.items.iter().enumerate().find_map(|(index, item)| {
            item.kind
                .find_amount(SETTLEMENT_PAYMENT_HASH.into(), &item.asset.constraints())
                .map(|amount| (index, amount))
        }) else {
            return Ok(None);
        };

        let source = &mut self.items[index];

        source.kind.create_intermediate_coin(CreateCoin::new(
            SETTLEMENT_PAYMENT_HASH.into(),
            amount,
            Memos::None,
        ));

        let child = FungibleSpend::new(
            source
                .asset
                .make_child(SETTLEMENT_PAYMENT_HASH.into(), amount),
            true,
        );

        self.items.push(child);

        Ok(Some(self.items.len() - 1))
    }

    pub fn intermediate_conditions_source(
        &mut self,
        ctx: &mut SpendContext,
        intermediate_puzzle_hash: Bytes32,
    ) -> Result<Option<usize>, DriverError> {
        let Some((index, amount)) = self.items.iter().enumerate().find_map(|(index, item)| {
            item.kind
                .find_amount(intermediate_puzzle_hash, &item.asset.constraints())
                .map(|amount| (index, amount))
        }) else {
            return Ok(None);
        };

        let source = &mut self.items[index];

        let hint = ctx.hint(intermediate_puzzle_hash)?;

        source.kind.create_intermediate_coin(CreateCoin::new(
            intermediate_puzzle_hash,
            amount,
            hint,
        ));

        let child = FungibleSpend::new(
            source.asset.make_child(intermediate_puzzle_hash, amount),
            true,
        );

        self.items.push(child);

        Ok(Some(self.items.len() - 1))
    }

    pub fn launcher_source(&mut self) -> Result<(usize, u64), DriverError> {
        let Some((index, amount)) = self.items.iter().enumerate().find_map(|(index, item)| {
            item.kind
                .find_amount(SINGLETON_LAUNCHER_HASH.into(), &item.asset.constraints())
                .map(|amount| (index, amount))
        }) else {
            return Err(DriverError::NoSourceForOutput);
        };

        Ok((index, amount))
    }

    pub fn create_launcher(
        &mut self,
        singleton_amount: u64,
    ) -> Result<(usize, Launcher), DriverError> {
        let (index, launcher_amount) = self.launcher_source()?;

        let (create_coin, launcher) =
            Launcher::create_early(self.items[index].asset.coin_id(), launcher_amount);

        self.items[index].kind.create_intermediate_coin(create_coin);

        Ok((index, launcher.with_singleton_amount(singleton_amount)))
    }

    pub fn create_option_launcher(
        &mut self,
        ctx: &mut SpendContext,
        singleton_amount: u64,
        creator_puzzle_hash: Bytes32,
        seconds: u64,
        underlying_amount: u64,
        strike_type: OptionType,
    ) -> Result<(usize, OptionLauncher), DriverError> {
        let (index, launcher_amount) = self.launcher_source()?;

        let source = &mut self.items[index];

        let (create_coin, launcher) = OptionLauncher::create_early(
            ctx,
            source.asset.coin_id(),
            launcher_amount,
            OptionLauncherInfo::new(
                creator_puzzle_hash,
                source.asset.p2_puzzle_hash(),
                seconds,
                underlying_amount,
                strike_type,
            ),
            singleton_amount,
        )?;

        source.kind.create_intermediate_coin(create_coin);

        Ok((index, launcher))
    }

    pub fn create_change(
        &mut self,
        ctx: &mut SpendContext,
        delta: &Delta,
        change_puzzle_hash: Bytes32,
    ) -> Result<Option<A>, DriverError> {
        let change = (self.selected_amount() + delta.input).saturating_sub(delta.output);

        if change == 0 {
            return Ok(None);
        }

        let output = Output::new(change_puzzle_hash, change);
        let source = self.output_source(ctx, &output)?;
        let item = &mut self.items[source];

        let parent_puzzle_hash = item.asset.full_puzzle_hash();
        let create_coin = CreateCoin::new(
            change_puzzle_hash,
            change,
            item.asset.child_memos(ctx, change_puzzle_hash)?,
        );
        item.kind.create_coin_with_assertion(
            ctx,
            parent_puzzle_hash,
            &mut self.payment_assertions,
            create_coin,
        );

        Ok(Some(item.asset.make_child(change_puzzle_hash, change)))
    }
}

impl<A> Default for FungibleSpends<A> {
    fn default() -> Self {
        Self {
            items: Vec::new(),
            payment_assertions: Vec::new(),
        }
    }
}

#[derive(Debug, Clone)]
pub struct FungibleSpend<T> {
    pub asset: T,
    pub kind: SpendKind,
    pub ephemeral: bool,
}

impl<T> FungibleSpend<T>
where
    T: FungibleAsset,
{
    pub fn new(asset: T, ephemeral: bool) -> Self {
        let kind = if asset.p2_puzzle_hash() == SETTLEMENT_PAYMENT_HASH.into() {
            SpendKind::settlement()
        } else {
            SpendKind::conditions()
        };

        Self {
            asset,
            kind,
            ephemeral,
        }
    }
}

pub trait FungibleAsset: Clone + Asset {
    #[must_use]
    fn make_child(&self, p2_puzzle_hash: Bytes32, amount: u64) -> Self;
    fn child_memos(
        &self,
        ctx: &mut SpendContext,
        p2_puzzle_hash: Bytes32,
    ) -> Result<Memos, DriverError>;
}

impl FungibleAsset for Coin {
    fn make_child(&self, p2_puzzle_hash: Bytes32, amount: u64) -> Self {
        Coin::new(self.coin_id(), p2_puzzle_hash, amount)
    }

    fn child_memos(
        &self,
        _ctx: &mut SpendContext,
        _p2_puzzle_hash: Bytes32,
    ) -> Result<Memos, DriverError> {
        Ok(Memos::None)
    }
}

impl FungibleAsset for Cat {
    fn make_child(&self, p2_puzzle_hash: Bytes32, amount: u64) -> Self {
        self.child(p2_puzzle_hash, amount)
    }

    fn child_memos(
        &self,
        ctx: &mut SpendContext,
        p2_puzzle_hash: Bytes32,
    ) -> Result<Memos, DriverError> {
        ctx.hint(p2_puzzle_hash)
    }
}