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}