chia-sdk-driver 0.34.0

Driver code for interacting with standard puzzles on the Chia blockchain.
Documentation
use chia_protocol::Coin;
use chia_puzzles::SETTLEMENT_PAYMENT_HASH;
use clvmr::Allocator;

use crate::{
    Bulletin, Cat, ClawbackInfo, ClawbackPath, CustodyInfo, DriverError, Nft, RevealedCoinSpend,
    Reveals, parse_inner_spend,
};

#[derive(Debug, Clone)]
pub struct ParsedSpend {
    pub asset: ParsedAsset,
    pub clawback: Option<ClawbackInfo>,
    pub custody: Option<CustodyInfo>,
    pub required_expiration_time: Option<u64>,
    pub revoked: bool,
}

#[derive(Debug, Clone, PartialEq, Eq)]
pub enum ParsedAsset {
    Cat(Cat),
    Nft(Nft),
    Xch(Coin),
    Bulletin(Bulletin),
}

impl ParsedAsset {
    pub fn coin(&self) -> Coin {
        match self {
            Self::Cat(cat) => cat.coin,
            Self::Nft(nft) => nft.coin,
            Self::Xch(coin) => *coin,
            Self::Bulletin(bulletin) => bulletin.coin,
        }
    }
}

pub fn parse_spend(
    reveals: &mut Reveals,
    allocator: &mut Allocator,
    spend: &RevealedCoinSpend,
) -> Result<ParsedSpend, DriverError> {
    // The default is to treat the spend as XCH if we don't have a more complex asset to try and parse.
    let mut asset = ParsedAsset::Xch(spend.coin);
    let mut inner_puzzle = spend.puzzle;
    let mut inner_solution = spend.solution;
    let mut revoked = false;

    if let Some(parsed) = Cat::parse(allocator, spend.coin, spend.puzzle, spend.solution)? {
        asset = ParsedAsset::Cat(parsed.cat);
        inner_puzzle = parsed.p2_puzzle;
        inner_solution = parsed.p2_solution;
        revoked = parsed.revoked;
    } else if let Some((nft, parsed_inner_puzzle, parsed_inner_solution)) =
        Nft::parse(allocator, spend.coin, spend.puzzle, spend.solution)?
    {
        asset = ParsedAsset::Nft(nft);
        inner_puzzle = parsed_inner_puzzle;
        inner_solution = parsed_inner_solution;
    } else if let Some((bulletin, parsed_inner_puzzle, parsed_inner_solution)) =
        Bulletin::parse(allocator, spend.coin, spend.puzzle, spend.solution)?
    {
        asset = ParsedAsset::Bulletin(bulletin);
        inner_puzzle = parsed_inner_puzzle;
        inner_solution = parsed_inner_solution;
    }

    if inner_puzzle.curried_puzzle_hash() == SETTLEMENT_PAYMENT_HASH.into() {
        reveals.reveal_settlement_payment(allocator, spend.puzzle, inner_solution)?;
    }

    let inner_spend = parse_inner_spend(reveals, allocator, inner_puzzle, inner_solution)?;

    // If we're clawing a coin back, we need to keep track of its expiration time.
    // This will be used to ensure that the clawback won't expire before the rest of
    // the transaction. If it might, the facts of this spend will be disregarded.
    let mut required_expiration_time = None;

    if let Some(clawback_info) = &inner_spend.clawback
        && clawback_info.path == ClawbackPath::Sender
    {
        required_expiration_time = Some(clawback_info.clawback.seconds);
    }

    Ok(ParsedSpend {
        asset,
        clawback: inner_spend.clawback,
        custody: inner_spend.custody,
        required_expiration_time,
        revoked,
    })
}