1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
//! Reconstruct a full, operable [`CreatedOption`] from on-chain state.
//!
//! [`crate::parse`]/[`crate::parse_child`] recover only an option's *identity* fields — the
//! option singleton's puzzle does not commit to its terms (creator puzzle hash, expiry, underlying
//! amount, strike type), so those cannot be inverted from the singleton coin spend alone. But
//! [`crate::exercise`], [`crate::clawback`], and [`crate::transfer`] all need the full
//! [`OptionUnderlying`] terms. Without a way to recover them, a caller could only ever operate an
//! option it minted itself in the same session.
//!
//! [`rehydrate`] closes that gap. The caller supplies the terms it can observe off-chain — the
//! creator puzzle hash it recorded, plus the expiry + strike recovered from the launcher metadata
//! via [`parse_metadata`] — together with the parsed option and its fetched underlying coin.
//! `rehydrate` reconstructs the [`OptionUnderlying`] and **verifies it against the option's
//! on-chain commitments**: the 1-of-2 underlying path, the underlying delegated-puzzle hash, and
//! the underlying coin id all must match. A single wrong term changes one of those hashes and is
//! rejected — so a successfully rehydrated [`CreatedOption`] is guaranteed to bind to the real
//! on-chain option and produce spends the consensus will accept.
use ;
use ;
use ToTreeHash;
use crate;
use crateCreatedOption;
// Re-exported so a caller need not depend on the SDK directly to name the recovered metadata.
pub use OptionMetadata;
/// The terms a caller supplies to [`rehydrate`] a previously-minted option.
///
/// Every field is verified against the option's on-chain commitments, so these are asserted, not
/// trusted: a wrong value is rejected rather than producing an option handle that builds an
/// unspendable bundle. `expiry_seconds` and `strike_type` are recoverable from the launcher
/// metadata ([`parse_metadata`]); `creator_puzzle_hash` is the party the caller recorded at mint
/// (it is committed only inside the underlying's clawback path, so it is supplied and verified
/// rather than inverted).
/// Recover an option's [`OptionMetadata`] (expiry seconds + strike type) from the launcher coin's
/// solution, which the caller fetched from a node/indexer.
///
/// The launcher solution carries the option's key-value metadata; this decodes it into the
/// caller-owned [`SpendContext`]. Network-free: the caller supplies the serialized solution.
/// Reconstruct a full [`CreatedOption`] from a parsed `option`, caller-supplied `terms`, and the
/// fetched `underlying_coin`, verifying every reconstructed field against the option's on-chain
/// commitments.
///
/// Rebuilds the [`OptionUnderlying`] from `option.info.launcher_id`, `terms`, and
/// `underlying_coin.amount`, then rejects the reconstruction unless ALL THREE of the following
/// match the on-chain option. The three are **jointly** load-bearing — no single check covers
/// every field, so none is mere defense-in-depth (verified against the chia-wallet-sdk 0.30
/// `OptionUnderlying` derivation):
/// - **1-of-2 path hash** equals `underlying_coin.puzzle_hash`. Per the SDK, the path is
/// `merkle([exercise_path(launcher_id), clawback_path(expiry, creator_ph)])`, so it binds ONLY
/// the launcher id, expiry, and creator puzzle hash — NOT the amount or strike type. A wrong
/// creator hash or expiry is caught here.
/// - **delegated-puzzle hash** equals `option.info.underlying_delegated_puzzle_hash`. The
/// delegated puzzle commits to the expiry, the underlying amount, and the strike type (settlement
/// target + requested-payment amount). A wrong **strike type** is caught ONLY here; a wrong
/// amount is caught both here and by the coin-id check below.
/// - **underlying coin id** equals `option.info.underlying_coin_id`. This binds the coin's full
/// identity (parent + puzzle hash + amount), uniquely rejecting a substituted coin of the right
/// shape but wrong parent that would slip past the two hash checks.
///
/// On success the returned [`CreatedOption`] is operable by [`crate::exercise`],
/// [`crate::clawback`], and [`crate::transfer`] exactly as one returned by [`crate::create`].
///
/// **Pure: performs no I/O and holds no key.** The caller fetches the option spend + underlying
/// coin and recovers the metadata; `rehydrate` only reconstructs + verifies.