Skip to main content

dig_options/
hydrate.rs

1//! Reconstruct an option from an already-fetched coin spend.
2//!
3//! dig-options is network-free: the CALLER fetches a coin's puzzle reveal + solution (from a
4//! node/indexer) and passes the serialized programs here. [`parse`] decodes an option directly
5//! from its own coin spend; [`parse_child`] walks a parent option spend to the option child it
6//! created. Both reconstruct into the caller-provided [`SpendContext`].
7//!
8//! ## Recoverable fields (an honest SDK limitation)
9//!
10//! An option singleton's on-chain puzzle commits only to its identity fields — the launcher
11//! id, the underlying coin id, the underlying delegated-puzzle hash, and the current p2 puzzle
12//! hash (via the SDK's `OptionInfo`). The option's *terms* (creator puzzle hash, expiry,
13//! underlying amount, strike type) live in the launcher metadata and the underlying coin and
14//! are NOT invertible from an option singleton coin spend. [`ParsedOption`] therefore carries
15//! the recoverable identity fields; a caller that needs the terms retains the
16//! [`crate::CreatedOption`]/[`crate::OptionTerms`] from [`crate::create`].
17
18use chia_protocol::{Bytes32, Coin, Program};
19use chia_wallet_sdk::driver::{OptionContract, Puzzle, SpendContext};
20
21use crate::error::Result;
22
23/// A reconstructed option plus the identity fields recoverable from its coin spend.
24///
25/// The [`ParsedOption::option`] is a spendable [`OptionContract`] in the [`SpendContext`] it
26/// was parsed into. See the module docs for why the option's terms are not included.
27#[derive(Clone, Debug)]
28pub struct ParsedOption {
29    /// The reconstructed option singleton.
30    pub option: OptionContract,
31    /// The launcher coin id — the option's stable identity.
32    pub launcher_id: Bytes32,
33    /// The current coin id of the unspent option singleton.
34    pub coin_id: Bytes32,
35    /// The coin id of the locked underlying this option unlocks on exercise.
36    pub underlying_coin_id: Bytes32,
37    /// The tree hash of the underlying's delegated (settlement) puzzle.
38    pub underlying_delegated_puzzle_hash: Bytes32,
39    /// The current p2 (owner) puzzle hash — where the option singleton lives.
40    pub p2_puzzle_hash: Bytes32,
41}
42
43impl ParsedOption {
44    fn from_option(option: OptionContract) -> Self {
45        Self {
46            launcher_id: option.info.launcher_id,
47            coin_id: option.coin.coin_id(),
48            underlying_coin_id: option.info.underlying_coin_id,
49            underlying_delegated_puzzle_hash: option.info.underlying_delegated_puzzle_hash,
50            p2_puzzle_hash: option.info.p2_puzzle_hash,
51            option,
52        }
53    }
54}
55
56/// Decode an option directly from its own coin spend (its coin, serialized puzzle reveal, and
57/// solution). Returns `Ok(None)` when the puzzle is not an option contract.
58pub fn parse(
59    ctx: &mut SpendContext,
60    coin: Coin,
61    puzzle_reveal: &Program,
62    solution: &Program,
63) -> Result<Option<ParsedOption>> {
64    let puzzle_ptr = ctx.alloc(puzzle_reveal)?;
65    let puzzle = Puzzle::parse(ctx, puzzle_ptr);
66    let solution = ctx.alloc(solution)?;
67    let parsed = OptionContract::parse(ctx, coin, puzzle, solution)?;
68    Ok(parsed.map(|(option, _p2_puzzle, _p2_solution)| ParsedOption::from_option(option)))
69}
70
71/// Reconstruct the option child created by spending `parent_coin`, given that parent's
72/// serialized puzzle reveal and solution (both fetched by the caller).
73///
74/// Returns `Ok(None)` when the parent spend did not produce an option child (its puzzle is not
75/// an option contract).
76pub fn parse_child(
77    ctx: &mut SpendContext,
78    parent_coin: Coin,
79    parent_puzzle_reveal: &Program,
80    parent_solution: &Program,
81) -> Result<Option<ParsedOption>> {
82    let puzzle_ptr = ctx.alloc(parent_puzzle_reveal)?;
83    let puzzle = Puzzle::parse(ctx, puzzle_ptr);
84    let solution = ctx.alloc(parent_solution)?;
85    let child = OptionContract::parse_child(ctx, parent_coin, puzzle, solution)?;
86    Ok(child.map(ParsedOption::from_option))
87}