chik_sdk_driver/offers/
offer.rs1use 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}