dig_options/types.rs
1//! The public value types every dig-options builder speaks in.
2//!
3//! These types are deliberately **key-free**: an [`Owner`] carries a public key (or an
4//! arbitrary caller-supplied inner spender), never a secret; the parties an option is created
5//! for are named by plain [`Bytes32`] puzzle hashes carried in [`OptionTerms`]. A builder
6//! consumes these, appends `CoinSpend`s to a caller-owned [`SpendContext`], and returns an
7//! [`OptionSpend`] — the built spends plus, for `create`, the [`CreatedOption`] the caller
8//! keeps to later exercise or claw back the option.
9
10use chia_protocol::{Bytes32, Coin, CoinSpend};
11use chia_puzzle_types::standard::StandardArgs;
12use chia_wallet_sdk::driver::{
13 DriverError, OptionContract, OptionType, OptionUnderlying, Spend, SpendContext,
14 SpendWithConditions, StandardLayer,
15};
16use chia_wallet_sdk::prelude::PublicKey;
17use chia_wallet_sdk::types::Conditions;
18
19/// The p2 (owner) layer that authorizes an option's inner spend, expressed WITHOUT any secret.
20///
21/// dig-options never signs; it only builds the spend and reports the signature the caller must
22/// produce. [`Owner::Standard`] is the common case — the standard
23/// `p2_delegated_puzzle_or_hidden_puzzle` layer identified by its public key.
24/// [`Owner::Custom`] is the escape hatch: any layer implementing the SDK's
25/// [`SpendWithConditions`] (a multisig, a custom p2, a settlement layer) borrowed for the
26/// build, so a non-standard owner is fully supported through every builder.
27pub enum Owner<'a> {
28 /// A standard-layer owner identified by its BLS public key.
29 Standard(PublicKey),
30 /// An arbitrary owner layer that knows how to emit a set of conditions.
31 Custom(&'a dyn SpendWithConditions),
32}
33
34impl Owner<'_> {
35 /// The standard p2 puzzle hash of a [`Owner::Standard`] owner.
36 ///
37 /// Returns `None` for [`Owner::Custom`], whose puzzle hash dig-options cannot derive
38 /// without knowing the concrete layer. Used to reject a wrong-party clawback up front
39 /// (a Custom owner skips that check — the consensus still enforces the real path).
40 pub fn standard_puzzle_hash(&self) -> Option<Bytes32> {
41 match self {
42 Owner::Standard(public_key) => Some(StandardArgs::curry_tree_hash(*public_key).into()),
43 Owner::Custom(_) => None,
44 }
45 }
46}
47
48impl SpendWithConditions for Owner<'_> {
49 /// Route an option's output conditions through the concrete owner layer, producing the
50 /// inner [`Spend`]. Neither variant holds or uses a secret key.
51 fn spend_with_conditions(
52 &self,
53 ctx: &mut SpendContext,
54 conditions: Conditions,
55 ) -> std::result::Result<Spend, DriverError> {
56 match self {
57 Owner::Standard(public_key) => {
58 StandardLayer::new(*public_key).spend_with_conditions(ctx, conditions)
59 }
60 Owner::Custom(inner) => inner.spend_with_conditions(ctx, conditions),
61 }
62 }
63}
64
65/// The terms of a covered option: who created it, who owns (may exercise) it, how much XCH it
66/// locks as the underlying, what strike the holder must pay, and when it expires.
67///
68/// Key-free: the parties are named by their [`Bytes32`] puzzle hashes, never by keys. The
69/// underlying is XCH (v0.1.0 scope); `strike_type` may be any [`OptionType`] (it is curried
70/// into the option puzzle), though only an XCH strike can be *exercised* in v0.1.0 (see
71/// [`crate::exercise`]).
72#[derive(Clone, Copy, Debug, PartialEq, Eq)]
73pub struct OptionTerms {
74 /// The puzzle hash the creator reclaims the underlying to on clawback (after expiry).
75 pub creator_puzzle_hash: Bytes32,
76 /// The puzzle hash the option singleton is minted to — its initial holder/owner.
77 pub owner_puzzle_hash: Bytes32,
78 /// The amount of XCH (in mojos) locked as the underlying.
79 pub underlying_amount: u64,
80 /// The asset + amount the holder must pay to exercise the option.
81 pub strike_type: OptionType,
82 /// The absolute unix timestamp (seconds) at which the option expires: exercise is valid
83 /// strictly before it, clawback strictly after it.
84 pub expiry_seconds: u64,
85}
86
87impl OptionTerms {
88 /// Terms with the creator as the initial owner (the common self-minted case).
89 ///
90 /// The option is minted to `creator_puzzle_hash` and, on clawback, reclaimed to it. Use
91 /// the struct literal directly to mint an option owned by a different party.
92 #[must_use]
93 pub fn new(
94 creator_puzzle_hash: Bytes32,
95 underlying_amount: u64,
96 strike_type: OptionType,
97 expiry_seconds: u64,
98 ) -> Self {
99 Self {
100 creator_puzzle_hash,
101 owner_puzzle_hash: creator_puzzle_hash,
102 underlying_amount,
103 strike_type,
104 expiry_seconds,
105 }
106 }
107}
108
109/// The operable handle for a live option: the option singleton, the underlying terms, and the
110/// locked-underlying coin needed to exercise, transfer, or claw it back.
111///
112/// Returned by [`crate::create`] (the freshly minted option), by [`crate::transfer`] (the option
113/// re-homed to its new owner), and by [`crate::rehydrate`] (reconstructed from on-chain state).
114/// The three fields only exist once the create spend is confirmed.
115///
116/// The terms are not recoverable from the option singleton coin alone (see [`crate::parse`]), so a
117/// caller either retains this handle from `create`/`transfer` or rebuilds it with
118/// [`crate::rehydrate`], which reconstructs and verifies it against the option's on-chain
119/// commitments.
120#[derive(Clone, Debug)]
121pub struct CreatedOption {
122 /// The option singleton (the transferable "ticket"); its holder may exercise it.
123 pub option: OptionContract,
124 /// The underlying terms (launcher id, creator ph, expiry seconds, locked amount, strike
125 /// type) — needed to build the exercise / clawback spend.
126 pub underlying: OptionUnderlying,
127 /// The locked-underlying XCH coin (parent = the funding coin, puzzle hash = the
128 /// underlying's 1-of-2 path, amount = the locked amount).
129 pub underlying_coin: Coin,
130}
131
132/// The result of a dig-options builder: the unsigned coin spends it produced, and — for the
133/// builders that yield an operable option — the [`CreatedOption`] handle the caller keeps to
134/// operate it later.
135///
136/// `created` is `Some` for [`crate::create`] (the minted option) and [`crate::transfer`] (the
137/// option re-homed to its new owner), and `None` for [`crate::exercise`]/[`crate::clawback`],
138/// which consume an existing option rather than producing an ongoing one.
139#[derive(Clone, Debug)]
140pub struct OptionSpend {
141 /// The unsigned coin spends this operation produced.
142 pub coin_spends: Vec<CoinSpend>,
143 /// The operable option handle — `Some` for `create` and `transfer`, `None` otherwise.
144 pub created: Option<CreatedOption>,
145}