Skip to main content

dig_offers/
types.rs

1//! The public value types every dig-offers builder speaks in.
2//!
3//! These types are deliberately **key-free**: every side of an offer carries its participant's
4//! *public* keys (an `IndexMap<Bytes32, PublicKey>` from a coin's p2 puzzle hash to the public
5//! key that authorizes it), never a secret. A builder consumes these, appends unsigned
6//! `CoinSpend`s to a caller-owned [`SpendContext`](chia_wallet_sdk::driver::SpendContext), and
7//! returns the unsigned artifact for the caller to sign.
8
9use std::marker::PhantomData;
10
11use chia_protocol::{Bytes32, Coin, CoinSpend};
12use chia_wallet_sdk::driver::{AssetInfo, Cat, Nft, NftAssetInfo, Offer, RequestedPayments};
13use chia_wallet_sdk::prelude::PublicKey;
14use indexmap::IndexMap;
15
16/// A single asset leg, used to describe what an offer offers or requests in a read-only
17/// [`OfferSummary`]. Amounts are the asset's base units (mojos for XCH, base units for CATs);
18/// an NFT is always quantity one and identified by its launcher id.
19#[derive(Debug, Clone, Copy, PartialEq, Eq)]
20pub enum OfferAsset {
21    /// XCH, amount in mojos.
22    Xch(u64),
23    /// A CAT identified by its `asset_id` (TAIL hash), amount in base units.
24    Cat {
25        /// The CAT's asset id (TAIL hash).
26        asset_id: Bytes32,
27        /// The amount in the CAT's base units.
28        amount: u64,
29    },
30    /// An NFT identified by its launcher id (quantity is always one).
31    Nft {
32        /// The NFT singleton's launcher id — its stable on-chain identity.
33        launcher_id: Bytes32,
34    },
35}
36
37impl OfferAsset {
38    /// The fungible amount this leg carries (zero for an NFT, which is not fungible).
39    #[must_use]
40    pub fn amount(&self) -> u64 {
41        match self {
42            OfferAsset::Xch(amount) | OfferAsset::Cat { amount, .. } => *amount,
43            OfferAsset::Nft { .. } => 0,
44        }
45    }
46}
47
48/// The maker's side of a make-offer: the coins it spends into the settlement puzzle (its funding
49/// XCH, CAT, and NFT coins), the fungible amounts it offers, and where change returns.
50///
51/// The funding coins fund the offered amounts (plus any network fee); surplus returns to
52/// `change_puzzle_hash`. `owner_keys` maps every funding coin's p2 (inner) puzzle hash to the
53/// **public** key that authorizes it — the builder never sees a secret. `nfts` are the NFTs to
54/// offer (each spent whole into settlement); they must be parsed in the SAME
55/// [`SpendContext`](chia_wallet_sdk::driver::SpendContext) passed to `make_build`, since an NFT
56/// carries an allocator-relative metadata pointer.
57pub struct OfferedSide<'a> {
58    /// Where offered-coin change (and fee surplus) returns.
59    pub change_puzzle_hash: Bytes32,
60    /// Every funding coin's p2 puzzle hash → the public key authorizing it.
61    pub owner_keys: IndexMap<Bytes32, PublicKey>,
62    /// Spendable XCH coins available to fund the offered XCH and the fee.
63    pub xch_coins: Vec<Coin>,
64    /// Spendable CAT coins (with lineage proofs) available to fund the offered CAT legs.
65    pub cat_coins: Vec<Cat>,
66    /// The NFTs to offer, each spent whole into settlement (parsed in the build context).
67    pub nfts: Vec<Nft>,
68    /// The XCH (mojos) to offer.
69    pub offer_xch: u64,
70    /// The CATs to offer, as `(asset_id, amount)` in base units.
71    pub offer_cats: Vec<(Bytes32, u64)>,
72    /// Ties the borrowed NFTs' lifetime to the build context.
73    pub _pd: PhantomData<&'a ()>,
74}
75
76/// The maker's requested side of a make-offer: what the taker must pay, and where it is paid.
77///
78/// Every requested payment is notarized to the offer's nonce and asserted (never self-funded).
79/// A requested NFT needs its [`NftAssetInfo`] (metadata pointer, royalty) so the settlement
80/// puzzle hash can be rebuilt correctly.
81pub struct RequestedSide {
82    /// The address every requested payment is paid to (the maker's receive address).
83    pub payee_puzzle_hash: Bytes32,
84    /// The XCH (mojos) requested.
85    pub xch: u64,
86    /// The CATs requested, as `(asset_id, amount)` in base units.
87    pub cats: Vec<(Bytes32, u64)>,
88    /// The NFTs requested, as `(launcher_id, asset_info)` — the asset info rebuilds settlement.
89    pub nfts: Vec<(Bytes32, NftAssetInfo)>,
90}
91
92/// The taker's funding coins for taking an offer: its spendable XCH, CAT, and NFT coins, and
93/// where change / received assets return.
94///
95/// `owner_keys` maps every funding coin's p2 puzzle hash to the **public** key authorizing it;
96/// `change_puzzle_hash` receives change, surplus, and the assets the offer delivers to the taker.
97/// NFTs the taker gives up (for an NFT-for-NFT take) are supplied in `nfts`, parsed in the SAME
98/// context passed to `take_build`.
99pub struct TakerFunds<'a> {
100    /// Where change, surplus, and received assets return.
101    pub change_puzzle_hash: Bytes32,
102    /// Every funding coin's p2 puzzle hash → the public key authorizing it.
103    pub owner_keys: IndexMap<Bytes32, PublicKey>,
104    /// Spendable XCH coins available to fund the requested XCH and the fee.
105    pub xch_coins: Vec<Coin>,
106    /// Spendable CAT coins (with lineage proofs) available to fund requested CAT payments.
107    pub cat_coins: Vec<Cat>,
108    /// NFTs the taker gives up to fulfil a requested-NFT leg (parsed in the build context).
109    pub nfts: Vec<Nft>,
110    /// Ties the borrowed NFTs' lifetime to the build context.
111    pub _pd: PhantomData<&'a ()>,
112}
113
114/// What a taker must fund to take an offer: the requested-over-offered surplus (the offer's
115/// arbitrage). NFTs the taker receives are not a cost; NFTs the taker gives up are expressed by
116/// the requested-NFT legs, not here.
117#[derive(Debug, Default, Clone, PartialEq, Eq)]
118pub struct OfferCost {
119    /// XCH (mojos) the taker must pay.
120    pub xch: u64,
121    /// Per-asset-id CAT base units the taker must pay.
122    pub cats: Vec<(Bytes32, u64)>,
123}
124
125/// A read-only summary of an `offer1…` string: what it offers, what it requests, the taker's
126/// arbitrage cost, and any NFT royalties it carries. Produced by
127/// [`summarize`](crate::summarize) without committing to the offer.
128#[derive(Debug, Default, Clone, PartialEq, Eq)]
129pub struct OfferSummary {
130    /// Assets the offer gives the taker.
131    pub offered: Vec<OfferAsset>,
132    /// Assets the offer asks the taker to pay.
133    pub requested: Vec<OfferAsset>,
134    /// The net the taker must fund (the requested-over-offered surplus).
135    pub arbitrage: OfferCost,
136    /// Royalty legs the offer carries: `(NFT launcher id, royalty basis points)`.
137    pub royalties: Vec<(Bytes32, u16)>,
138}
139
140/// The unsigned artifact of [`make_build`](crate::make_build): the coin spends the caller must
141/// sign, and the requested-payment context to hand back to [`make_assemble`](crate::make_assemble)
142/// once signed.
143///
144/// **The custody boundary.** `coin_spends` are unsigned; the caller computes their required
145/// signatures with [`required_signatures`](crate::required_signatures), signs, aggregates into a
146/// `SpendBundle`, and passes that plus `requested_payments` + `requested_asset_info` back to
147/// `make_assemble`. dig-offers never produces the signature.
148#[derive(Debug)]
149pub struct UnsignedMake {
150    /// The unsigned coin spends the caller must sign.
151    pub coin_spends: Vec<CoinSpend>,
152    /// The requested payments (what the taker will pay), carried to `make_assemble`.
153    pub requested_payments: RequestedPayments,
154    /// The requested asset metadata (rebuilds settlement for requested CAT/NFT legs).
155    pub requested_asset_info: AssetInfo,
156    /// The offer nonce derived from the offered coins — reported for audit; already baked into
157    /// the requested payments.
158    pub nonce: Bytes32,
159}
160
161/// The unsigned artifact of [`take_build`](crate::take_build): the taker's own coin spends to
162/// sign, the maker's already-signed offer, and the cost the take funds.
163///
164/// **The custody boundary.** `coin_spends` are the TAKER's unsigned spends only; the maker's half
165/// is already signed inside `offer`. The caller signs `coin_spends`, wraps them in a `SpendBundle`,
166/// and calls [`take_combine`](crate::take_combine) with `offer` to produce the atomic settlement.
167#[derive(Debug)]
168pub struct UnsignedTake {
169    /// The taker's unsigned coin spends (funding + received-asset routing).
170    pub coin_spends: Vec<CoinSpend>,
171    /// The maker's already-signed offer, carried to `take_combine`.
172    pub offer: Offer,
173    /// What the taker funds to take the offer (the arbitrage).
174    pub cost: OfferCost,
175}
176
177/// The unsigned artifact of [`cancel_build`](crate::cancel_build): the reclaim coin spends the
178/// maker must sign to invalidate an outstanding offer.
179#[derive(Debug)]
180pub struct UnsignedCancel {
181    /// The unsigned coin spends that reclaim the offered coins to the maker.
182    pub coin_spends: Vec<CoinSpend>,
183}
184
185#[cfg(test)]
186mod tests {
187    use super::*;
188
189    #[test]
190    fn offer_asset_amount_reports_fungible_value() {
191        assert_eq!(OfferAsset::Xch(500).amount(), 500);
192        assert_eq!(
193            OfferAsset::Cat {
194                asset_id: Bytes32::default(),
195                amount: 42
196            }
197            .amount(),
198            42
199        );
200        // An NFT is non-fungible — it carries no fungible amount.
201        assert_eq!(
202            OfferAsset::Nft {
203                launcher_id: Bytes32::default()
204            }
205            .amount(),
206            0
207        );
208    }
209}