chik_sdk_driver/offers/
offer.rs

1use chik_protocol::{Bytes32, SpendBundle};
2use chik_puzzle_types::offer::SettlementPaymentsSolution;
3use chik_traits::Streamable;
4use indexmap::IndexMap;
5use klvm_traits::{FromKlvm, ToKlvm};
6use klvm_utils::{tree_hash, ToTreeHash};
7use klvmr::Allocator;
8
9use crate::{
10    compress_offer_bytes, decode_offer_data, decompress_offer_bytes, encode_offer_data, Make,
11    OfferBuilder, OfferError, ParsedOffer, Puzzle, Take,
12};
13
14#[derive(Debug, Clone)]
15pub struct Offer {
16    spend_bundle: SpendBundle,
17}
18
19impl Offer {
20    pub fn new(spend_bundle: SpendBundle) -> Self {
21        Self { spend_bundle }
22    }
23
24    pub fn build(coin_ids: Vec<Bytes32>) -> OfferBuilder<Make> {
25        Self::build_with_nonce(Self::nonce(coin_ids))
26    }
27
28    pub fn build_with_nonce(nonce: Bytes32) -> OfferBuilder<Make> {
29        OfferBuilder::new(nonce)
30    }
31
32    pub fn nonce(mut coin_ids: Vec<Bytes32>) -> Bytes32 {
33        coin_ids.sort();
34        coin_ids.tree_hash().into()
35    }
36
37    pub fn to_bytes(&self) -> Result<Vec<u8>, OfferError> {
38        Ok(self.spend_bundle.to_bytes()?)
39    }
40
41    pub fn from_bytes(bytes: &[u8]) -> Result<Self, OfferError> {
42        Ok(SpendBundle::from_bytes(bytes)?.into())
43    }
44
45    pub fn compress(&self) -> Result<Vec<u8>, OfferError> {
46        compress_offer_bytes(&self.to_bytes()?)
47    }
48
49    pub fn decompress(bytes: &[u8]) -> Result<Self, OfferError> {
50        Self::from_bytes(&decompress_offer_bytes(bytes)?)
51    }
52
53    pub fn encode(&self) -> Result<String, OfferError> {
54        encode_offer_data(&self.compress()?)
55    }
56
57    pub fn decode(text: &str) -> Result<Self, OfferError> {
58        Self::decompress(&decode_offer_data(text)?)
59    }
60
61    pub fn take(self, allocator: &mut Allocator) -> Result<OfferBuilder<Take>, OfferError> {
62        Ok(self.parse(allocator)?.take())
63    }
64
65    pub fn parse(self, allocator: &mut Allocator) -> Result<ParsedOffer, OfferError> {
66        let mut parsed = ParsedOffer {
67            aggregated_signature: self.spend_bundle.aggregated_signature,
68            coin_spends: Vec::new(),
69            requested_payments: IndexMap::new(),
70        };
71
72        for coin_spend in self.spend_bundle.coin_spends {
73            if coin_spend.coin.parent_coin_info != Bytes32::default() {
74                parsed.coin_spends.push(coin_spend);
75                continue;
76            }
77
78            if coin_spend.coin.amount != 0 {
79                parsed.coin_spends.push(coin_spend);
80                continue;
81            }
82
83            let puzzle = coin_spend.puzzle_reveal.to_klvm(allocator)?;
84            let puzzle_hash = tree_hash(allocator, puzzle).into();
85
86            if puzzle_hash != coin_spend.coin.puzzle_hash {
87                return Err(OfferError::PuzzleMismatch);
88            }
89
90            let solution = coin_spend.solution.to_klvm(allocator)?;
91            let settlement_solution = SettlementPaymentsSolution::from_klvm(allocator, solution)?;
92
93            let puzzle = Puzzle::parse(allocator, puzzle);
94
95            parsed
96                .requested_payments
97                .entry(puzzle_hash)
98                .or_insert_with(|| (puzzle, Vec::new()))
99                .1
100                .extend(settlement_solution.notarized_payments);
101        }
102
103        Ok(parsed)
104    }
105}
106
107impl From<SpendBundle> for Offer {
108    fn from(spend_bundle: SpendBundle) -> Self {
109        Self::new(spend_bundle)
110    }
111}
112
113impl From<Offer> for SpendBundle {
114    fn from(offer: Offer) -> Self {
115        offer.spend_bundle
116    }
117}