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/// A created option, returned by [`crate::create`] so the caller can later exercise or claw it
110/// back — both need the option singleton, the underlying terms, and the locked-underlying
111/// coin, which only exist once the create spend is confirmed.
112///
113/// The caller must retain this (or the equivalent [`OptionTerms`] plus the confirmed coins) to
114/// operate the option: the terms are not recoverable from the option singleton coin alone
115/// (see [`crate::parse`]).
116#[derive(Clone, Debug)]
117pub struct CreatedOption {
118 /// The option singleton (the transferable "ticket"); its holder may exercise it.
119 pub option: OptionContract,
120 /// The underlying terms (launcher id, creator ph, expiry seconds, locked amount, strike
121 /// type) — needed to build the exercise / clawback spend.
122 pub underlying: OptionUnderlying,
123 /// The locked-underlying XCH coin (parent = the funding coin, puzzle hash = the
124 /// underlying's 1-of-2 path, amount = the locked amount).
125 pub underlying_coin: Coin,
126}
127
128/// The result of a dig-options builder: the unsigned coin spends it produced, and — for
129/// [`crate::create`] — the [`CreatedOption`] the caller keeps to operate the option later.
130///
131/// `created` is `Some` for `create` and `None` for `exercise`/`clawback` (which consume an
132/// existing option rather than producing one).
133#[derive(Clone, Debug)]
134pub struct OptionSpend {
135 /// The unsigned coin spends this operation produced.
136 pub coin_spends: Vec<CoinSpend>,
137 /// The created option — `Some` only for `create`.
138 pub created: Option<CreatedOption>,
139}