1use alloc::collections::btree_map::Entry;
29use alloc::collections::{btree_set, BTreeMap, BTreeSet};
30use alloc::vec;
31use core::mem;
32use std::collections::HashMap;
33
34use amplify::confinement::{
35 Collection, KeyedCollection, NonEmptyVec, SmallOrdMap, SmallOrdSet, U8 as U8MAX,
36};
37use amplify::{confinement, ByteArray, Bytes32, MultiError, Wrapper};
38use bp::dbc::tapret::TapretProof;
39pub use bp::seals;
40use bp::seals::{mmb, Anchor, Noise, TxoSeal, TxoSealExt, WOutpoint, WTxoSeal};
41use bp::{Outpoint, Sats, ScriptPubkey, Tx, Txid, Vout};
42use commit_verify::mpc::ProtocolId;
43use commit_verify::{mpc, Digest, DigestExt, Sha256, StrictHash};
44use hypersonic::{
45 AcceptError, AuthToken, CallParams, CellAddr, ContractId, CoreParams, DataCell, MethodName,
46 NamedState, Operation, Satisfaction, StateAtom, StateCalc, StateCalcError, StateName,
47 StateUnknown, Stock,
48};
49use invoice::bp::{Address, WitnessOut};
50use invoice::{RgbBeneficiary, RgbInvoice};
51use rgb::RgbSealDef;
52use rgbcore::LIB_NAME_RGB;
53use strict_encoding::{ReadRaw, StrictDecode, StrictReader, TypeName};
54use strict_types::StrictVal;
55
56use crate::contracts::SyncError;
57use crate::{
58 Assignment, CodexId, Consensus, ConsumeError, Contract, ContractState, Contracts, CreateParams,
59 EitherSeal, Identity, Issuer, IssuerError, OwnedState, Pile, SigBlob, Stockpile, WalletState,
60 WitnessStatus,
61};
62
63pub trait WalletProvider {
65 type Error: core::error::Error;
66
67 fn has_utxo(&self, outpoint: Outpoint) -> bool;
68 fn utxos(&self) -> impl Iterator<Item = Outpoint>;
69
70 #[cfg(not(feature = "async"))]
71 fn update_utxos(&mut self) -> Result<(), Self::Error>;
72 #[cfg(feature = "async")]
73 async fn update_utxos_async(&mut self) -> Result<(), Self::Error>;
74
75 fn register_seal(&mut self, seal: WTxoSeal);
76 fn resolve_seals(
77 &self,
78 seals: impl Iterator<Item = AuthToken>,
79 ) -> impl Iterator<Item = WTxoSeal>;
80
81 fn noise_seed(&self) -> Bytes32;
82 fn next_address(&mut self) -> Address;
83 fn next_nonce(&mut self) -> u64;
84
85 #[cfg(not(feature = "async"))]
86 fn txid_resolver(&self) -> impl Fn(Txid) -> Result<WitnessStatus, Self::Error>;
89 #[cfg(feature = "async")]
90 fn txid_resolver_async(&self) -> impl AsyncFn(Txid) -> Result<WitnessStatus, Self::Error>;
93
94 #[cfg(not(feature = "async"))]
95 fn last_block_height(&self) -> Result<u64, Self::Error>;
97 #[cfg(feature = "async")]
98 async fn last_block_height_async(&self) -> Result<u64, Self::Error>;
100
101 #[cfg(not(feature = "async"))]
102 fn broadcast(&mut self, tx: &Tx, change: Option<(Vout, u32, u32)>) -> Result<(), Self::Error>;
104 #[cfg(feature = "async")]
105 async fn broadcast_async(
107 &mut self,
108 tx: &Tx,
109 change: Option<(Vout, u32, u32)>,
110 ) -> Result<(), Self::Error>;
111}
112
113pub trait Coinselect {
114 fn coinselect<'a>(
115 &mut self,
116 invoiced_state: &StrictVal,
117 calc: &mut StateCalc,
118 owned_state: impl IntoIterator<
120 Item = &'a OwnedState<Outpoint>,
121 IntoIter: DoubleEndedIterator<Item = &'a OwnedState<Outpoint>>,
122 >,
123 ) -> Option<Vec<(CellAddr, Outpoint)>>;
124}
125
126pub const BP_BLANK_METHOD: &str = "_";
127
128#[derive(Copy, Clone, Ord, PartialOrd, Eq, PartialEq, Hash, Debug)]
129#[cfg_attr(feature = "serde", derive(Serialize, Deserialize), serde(rename_all = "camelCase"))]
130pub struct PrefabSeal {
131 pub vout: Vout,
132 pub noise: Option<Noise>,
133}
134
135#[derive(Clone, Eq, PartialEq, Hash, Debug, Display)]
136#[display("{wout}/{sats}")]
137#[cfg_attr(feature = "serde", derive(Serialize, Deserialize), serde(rename_all = "camelCase"))]
138pub struct WoutAssignment {
139 #[cfg_attr(feature = "serde", serde(with = "serde_with::rust::display_fromstr"))]
140 pub wout: WitnessOut,
141 pub sats: Sats,
142}
143
144impl From<WoutAssignment> for ScriptPubkey {
145 fn from(val: WoutAssignment) -> Self { val.script_pubkey() }
146}
147
148impl WoutAssignment {
149 pub fn script_pubkey(&self) -> ScriptPubkey { self.wout.script_pubkey() }
150}
151
152impl<T> EitherSeal<T> {
153 pub fn map<U>(self, f: impl FnOnce(T) -> U) -> EitherSeal<U> {
154 match self {
155 Self::Alt(seal) => EitherSeal::Alt(f(seal)),
156 Self::Token(auth) => EitherSeal::Token(auth),
157 }
158 }
159}
160
161impl EitherSeal<Outpoint> {
162 pub fn transform(self, noise_engine: Sha256, nonce: u64) -> EitherSeal<WTxoSeal> {
163 match self {
164 EitherSeal::Alt(seal) => {
165 EitherSeal::Alt(WTxoSeal::no_fallback(seal, noise_engine, nonce))
166 }
167 EitherSeal::Token(auth) => EitherSeal::Token(auth),
168 }
169 }
170}
171
172impl CreateParams<Outpoint> {
173 pub fn transform(self, mut noise_engine: Sha256) -> CreateParams<WTxoSeal> {
174 noise_engine.input_raw(self.issuer.codex_id().as_slice());
175 noise_engine.input_raw(&[self.consensus as u8]);
176 noise_engine.input_raw(self.method.as_bytes());
177 noise_engine.input_raw(self.name.as_bytes());
178 noise_engine.input_raw(&self.timestamp.unwrap_or_default().timestamp().to_le_bytes());
179 CreateParams {
180 issuer: self.issuer,
181 consensus: self.consensus,
182 testnet: self.testnet,
183 method: self.method,
184 name: self.name,
185 timestamp: self.timestamp,
186 global: self.global,
187 owned: self
188 .owned
189 .into_iter()
190 .enumerate()
191 .map(|(nonce, assignment)| NamedState {
192 name: assignment.name,
193 state: Assignment {
194 seal: assignment
195 .state
196 .seal
197 .transform(noise_engine.clone(), nonce as u64),
198 data: assignment.state.data,
199 },
200 })
201 .collect(),
202 }
203 }
204}
205
206#[derive(Clone, Debug)]
207#[cfg_attr(feature = "serde", derive(Serialize, Deserialize), serde(rename_all = "camelCase"))]
208pub struct UsedState {
209 pub addr: CellAddr,
210 pub outpoint: Outpoint,
211 pub satisfaction: Option<Satisfaction>,
212}
213
214pub type PaymentScript = OpRequestSet<Option<WoutAssignment>>;
215
216#[derive(Wrapper, WrapperMut, Clone, Debug, From)]
218#[wrapper(Deref)]
219#[wrapper_mut(DerefMut)]
220#[cfg_attr(
221 feature = "serde",
222 derive(Serialize, Deserialize),
223 serde(
224 rename_all = "camelCase",
225 bound = "T: serde::Serialize + for<'d> serde::Deserialize<'d>"
226 )
227)]
228pub struct OpRequestSet<T>(NonEmptyVec<OpRequest<T>, U8MAX>);
229
230impl<T> IntoIterator for OpRequestSet<T> {
231 type Item = OpRequest<T>;
232 type IntoIter = vec::IntoIter<OpRequest<T>>;
233
234 fn into_iter(self) -> Self::IntoIter { self.0.into_iter() }
235}
236
237impl<T> OpRequestSet<T> {
238 pub fn with(request: OpRequest<T>) -> Self { Self(NonEmptyVec::with(request)) }
239}
240
241impl OpRequestSet<Option<WoutAssignment>> {
242 pub fn resolve_seals(
243 self,
244 resolver: impl Fn(&ScriptPubkey) -> Option<Vout>,
245 change: Option<Vout>,
246 ) -> Result<OpRequestSet<PrefabSeal>, UnresolvedSeal> {
247 let mut items = Vec::with_capacity(self.0.len());
248 for request in self.0 {
249 let mut owned = Vec::with_capacity(request.owned.len());
250 for assignment in request.owned {
251 let seal = match assignment.state.seal {
252 EitherSeal::Alt(Some(seal)) => {
253 let spk = seal.script_pubkey();
254 let vout = resolver(&spk).ok_or(UnresolvedSeal::Spk(spk))?;
255 let seal = PrefabSeal { vout, noise: Some(seal.wout.noise()) };
256 EitherSeal::Alt(seal)
257 }
258 EitherSeal::Alt(None) => {
259 let change = change.ok_or(UnresolvedSeal::Change)?;
260 let seal = PrefabSeal { vout: change, noise: None };
261 EitherSeal::Alt(seal)
262 }
263 EitherSeal::Token(auth) => EitherSeal::Token(auth),
264 };
265 owned.push(NamedState {
266 name: assignment.name,
267 state: Assignment { seal, data: assignment.state.data },
268 });
269 }
270 items.push(OpRequest {
271 contract_id: request.contract_id,
272 method: request.method,
273 reading: request.reading,
274 using: request.using,
275 global: request.global,
276 owned,
277 });
278 }
279 Ok(OpRequestSet(NonEmptyVec::from_iter_checked(items)))
280 }
281}
282
283#[derive(Clone, PartialEq, Eq, Debug, Display, Error)]
284#[display(doc_comments)]
285pub enum UnresolvedSeal {
286 Spk(ScriptPubkey),
288
289 Change,
291}
292
293#[derive(Clone, Debug)]
303#[cfg_attr(
304 feature = "serde",
305 derive(Serialize, Deserialize),
306 serde(
307 rename_all = "camelCase",
308 bound = "T: serde::Serialize + for<'d> serde::Deserialize<'d>"
309 )
310)]
311pub struct OpRequest<T> {
312 pub contract_id: ContractId,
313 pub method: MethodName,
314 pub reading: Vec<CellAddr>,
315 pub using: Vec<UsedState>,
316 pub global: Vec<NamedState<StateAtom>>,
317 pub owned: Vec<NamedState<Assignment<EitherSeal<T>>>>,
318}
319
320impl OpRequest<Option<WoutAssignment>> {
321 pub fn resolve_seal(
322 &self,
323 wout: WitnessOut,
324 resolver: impl Fn(&ScriptPubkey) -> Option<Vout>,
325 ) -> Option<WTxoSeal> {
326 for assignment in &self.owned {
327 if let EitherSeal::Alt(Some(assignment)) = &assignment.state.seal {
328 if assignment.wout == wout {
329 let spk = assignment.script_pubkey();
330 let vout = resolver(&spk)?;
331 let primary = WOutpoint::Wout(vout);
332 let seal = WTxoSeal { primary, secondary: TxoSealExt::Noise(wout.noise()) };
333 return Some(seal);
334 }
335 }
336 }
337 None
338 }
339}
340
341#[derive(Clone, PartialEq, Eq, Debug, Display, Error, From)]
342#[display(doc_comments)]
343pub enum UnmatchedState {
344 #[from(StateUnknown)]
346 StateNameUnknown,
347
348 #[from]
349 #[display(inner)]
350 StateCalc(StateCalcError),
351
352 NotEnoughChange(StateName),
354}
355
356#[derive(Clone, Eq, PartialEq, Ord, PartialOrd, Debug)]
359#[derive(StrictType, StrictDumb, StrictEncode, StrictDecode)]
360#[strict_type(lib = LIB_NAME_RGB)]
361#[cfg_attr(feature = "serde", derive(Serialize, Deserialize), serde(rename_all = "camelCase"))]
362pub struct Prefab {
363 pub closes: SmallOrdSet<Outpoint>,
364 pub defines: SmallOrdSet<Vout>,
365 pub operation: Operation,
366}
367
368#[derive(Wrapper, WrapperMut, Clone, Eq, PartialEq, Debug, Default, From)]
373#[wrapper(Deref)]
374#[wrapper_mut(DerefMut)]
375#[derive(StrictType, StrictEncode, StrictDecode)]
376#[strict_type(lib = LIB_NAME_RGB)]
377#[cfg_attr(feature = "serde", derive(Serialize, Deserialize), serde(transparent))]
378pub struct PrefabBundle(SmallOrdSet<Prefab>);
379
380impl IntoIterator for PrefabBundle {
381 type Item = Prefab;
382 type IntoIter = btree_set::IntoIter<Prefab>;
383
384 fn into_iter(self) -> Self::IntoIter { self.0.into_iter() }
385}
386
387impl<'a> IntoIterator for &'a PrefabBundle {
388 type Item = &'a Prefab;
389 type IntoIter = btree_set::Iter<'a, Prefab>;
390
391 fn into_iter(self) -> Self::IntoIter { self.0.iter() }
392}
393
394impl PrefabBundle {
395 pub fn new(items: impl IntoIterator<Item = Prefab>) -> Result<Self, confinement::Error> {
396 let items = SmallOrdSet::try_from_iter(items.into_iter())?;
397 Ok(Self(items))
398 }
399
400 pub fn closes(&self) -> impl Iterator<Item = Outpoint> + use<'_> {
401 self.0.iter().flat_map(|item| item.closes.iter().copied())
402 }
403
404 pub fn defines(&self) -> impl Iterator<Item = Vout> + use<'_> {
405 self.0.iter().flat_map(|item| item.defines.iter().copied())
406 }
407}
408
409pub struct RgbWallet<
413 W,
414 Sp,
415 S = HashMap<CodexId, Issuer>,
417 C = HashMap<ContractId, Contract<<Sp as Stockpile>::Stock, <Sp as Stockpile>::Pile>>,
418> where
419 W: WalletProvider,
420 Sp: Stockpile,
421 Sp::Pile: Pile<Seal = TxoSeal>,
422 S: KeyedCollection<Key = CodexId, Value = Issuer>,
423 C: KeyedCollection<Key = ContractId, Value = Contract<Sp::Stock, Sp::Pile>>,
424{
425 pub wallet: W,
426 pub contracts: Contracts<Sp, S, C>,
427}
428
429impl<W, Sp, S, C> RgbWallet<W, Sp, S, C>
430where
431 W: WalletProvider,
432 Sp: Stockpile,
433 Sp::Pile: Pile<Seal = TxoSeal>,
434 S: KeyedCollection<Key = CodexId, Value = Issuer>,
435 C: KeyedCollection<Key = ContractId, Value = Contract<Sp::Stock, Sp::Pile>>,
436{
437 pub fn with_components(wallet: W, contracts: Contracts<Sp, S, C>) -> Self {
438 Self { wallet, contracts }
439 }
440
441 pub fn into_components(self) -> (W, Contracts<Sp, S, C>) { (self.wallet, self.contracts) }
442
443 pub fn switch_wallet(&mut self, new: W) -> W { mem::replace(&mut self.wallet, new) }
444
445 pub fn issue(
446 &mut self,
447 params: CreateParams<Outpoint>,
448 ) -> Result<
449 ContractId,
450 MultiError<IssuerError, <Sp::Stock as Stock>::Error, <Sp::Pile as Pile>::Error>,
451 > {
452 self.contracts.issue(params.transform(self.noise_engine()))
453 }
454
455 pub fn auth_token(&mut self, nonce: Option<u64>) -> Option<AuthToken> {
456 let outpoint = self.wallet.utxos().next()?;
457 let nonce = nonce.unwrap_or_else(|| self.wallet.next_nonce());
458 let seal = WTxoSeal::no_fallback(outpoint, self.noise_engine(), nonce);
459 let auth = seal.auth_token();
460 self.wallet.register_seal(seal);
461 Some(auth)
462 }
463
464 pub fn wout(&mut self, nonce: Option<u64>) -> WitnessOut {
465 let address = self.wallet.next_address();
466 let nonce = nonce.unwrap_or_else(|| self.wallet.next_nonce());
467 WitnessOut::new(address.payload, nonce)
468 }
469
470 pub fn wallet_state(&self) -> WalletState<TxoSeal> {
471 let iter = self
472 .contracts
473 .contract_ids()
474 .map(|id| (id, self.contracts.contract_state(id)));
475 WalletState::from_contracts_state(iter)
476 }
477
478 pub fn wallet_contract_state(&self, contract_id: ContractId) -> ContractState<Outpoint> {
479 self.contracts
480 .contract_state(contract_id)
481 .clone()
482 .filter_map(
483 |seal| {
484 if self.wallet.has_utxo(seal.primary) {
485 Some(seal.primary)
486 } else {
487 None
488 }
489 },
490 )
491 }
492
493 pub fn contract_state_full(
494 &self,
495 contract_id: ContractId,
496 ) -> ContractState<<Sp::Pile as Pile>::Seal> {
497 self.contracts.contract_state(contract_id)
498 }
499
500 fn noise_engine(&self) -> Sha256 {
501 let noise_seed = self.wallet.noise_seed();
502 let mut noise_engine = Sha256::new();
503 noise_engine.input_raw(noise_seed.as_ref());
504 noise_engine
505 }
506
507 pub fn fulfill(
508 &mut self,
509 invoice: &RgbInvoice<ContractId>,
510 mut coinselect: impl Coinselect,
511 giveaway: Option<Sats>,
512 ) -> Result<OpRequest<Option<WoutAssignment>>, FulfillError> {
513 let contract_id = invoice.scope;
514
515 let articles = self.contracts.contract_articles(contract_id);
517 let api = articles.default_api();
518 let call = invoice
519 .call
520 .as_ref()
521 .or(api.default_call.as_ref())
522 .ok_or(FulfillError::CallStateUnknown)?;
523 let method = call.method.clone();
524 let state_name = call.owned.clone().ok_or(FulfillError::StateNameUnknown)?;
525 let mut calc = api.calculate(state_name.clone())?;
526
527 let value = invoice.data.as_ref().ok_or(FulfillError::ValueMissed)?;
528
529 let state = self.wallet_contract_state(contract_id);
531 let state = state
532 .owned
533 .get(&state_name)
534 .ok_or(FulfillError::StateUnavailable)?;
535 let mut using = coinselect
537 .coinselect(value, &mut calc, state)
538 .ok_or(FulfillError::StateInsufficient)?;
539 let (addrs, outpoints) = using
542 .iter()
543 .copied()
544 .unzip::<_, _, BTreeSet<_>, BTreeSet<_>>();
545 using.extend(
546 state
547 .iter()
548 .filter(|s| outpoints.contains(&s.assignment.seal) && !addrs.contains(&s.addr))
549 .filter_map(|s| {
550 calc.accumulate(&s.assignment.data).ok()?;
551 Some((s.addr, s.assignment.seal))
552 }),
553 );
554 let using = using
555 .into_iter()
556 .map(|(addr, outpoint)| UsedState { addr, outpoint, satisfaction: None })
557 .collect();
558
559 let seal = match invoice.auth {
561 RgbBeneficiary::Token(auth) => EitherSeal::Token(auth),
562 RgbBeneficiary::WitnessOut(wout) => {
563 let wout = WoutAssignment {
564 wout,
565 sats: giveaway.ok_or(FulfillError::WoutRequiresGiveaway)?,
566 };
567 EitherSeal::Alt(Some(wout))
568 }
569 };
570 calc.lessen(value)?;
571 let assignment = Assignment { seal, data: value.clone() };
572 let state = NamedState { name: state_name.clone(), state: assignment };
573 let mut owned = vec![state];
574
575 let diff = calc.diff()?;
577 let seal = EitherSeal::Alt(None);
578 for data in diff {
579 let assignment = Assignment { seal: seal.clone(), data };
580 let state = NamedState { name: state_name.clone(), state: assignment };
581 owned.push(state);
582 }
583
584 Ok(OpRequest {
586 contract_id,
587 method,
588 reading: none!(),
589 global: none!(),
590 using,
591 owned,
592 })
593 }
594
595 pub fn check_request<T>(&self, request: &OpRequest<T>) -> Result<(), UnmatchedState> {
598 let contract_id = request.contract_id;
599 let state = self.contracts.contract_state(contract_id);
600 let articles = self.contracts.contract_articles(contract_id);
601 let api = articles.default_api();
602 let mut calcs = BTreeMap::new();
603
604 for inp in &request.using {
605 let (state_name, val) = state
606 .owned
607 .iter()
608 .find_map(|(state_name, map)| {
609 map.iter()
610 .find(|owned| owned.addr == inp.addr)
611 .map(|owned| (state_name, owned))
612 })
613 .expect("unknown state included in the contract stock");
614 let calc = match calcs.entry(state_name.clone()) {
615 Entry::Vacant(entry) => {
616 let calc = api.calculate(state_name.clone())?;
617 entry.insert(calc)
618 }
619 Entry::Occupied(entry) => entry.into_mut(),
620 };
621 calc.accumulate(&val.assignment.data)?;
622 }
623 for out in &request.owned {
624 let calc = match calcs.entry(out.name.clone()) {
625 Entry::Vacant(entry) => {
626 let calc = api.calculate(out.name.clone())?;
627 entry.insert(calc)
628 }
629 Entry::Occupied(entry) => entry.into_mut(),
630 };
631 calc.lessen(&out.state.data)?;
632 }
633 for (state_name, calc) in calcs {
634 if !calc.diff()?.is_empty() {
635 return Err(UnmatchedState::NotEnoughChange(state_name.clone()));
636 }
637 }
638 Ok(())
639 }
640
641 pub fn prefab(
643 &mut self,
644 request: OpRequest<PrefabSeal>,
645 ) -> Result<Prefab, MultiError<PrefabError, <Sp::Stock as Stock>::Error>> {
646 self.check_request(&request).map_err(MultiError::from_a)?;
647
648 let (closes, using) = request
650 .using
651 .into_iter()
652 .map(|used| (used.outpoint, (used.addr, used.satisfaction)))
653 .unzip();
654 let closes =
655 SmallOrdSet::try_from(closes).map_err(|_| MultiError::A(PrefabError::TooManyInputs))?;
656 let mut defines = SmallOrdSet::new();
657
658 let mut seals = SmallOrdMap::new();
659 let mut noise_engine = self.noise_engine();
660 noise_engine.input_raw(request.contract_id.as_slice());
661
662 let mut owned = Vec::with_capacity(request.owned.len());
663 for (opout_no, assignment) in request.owned.into_iter().enumerate() {
664 let auth = match assignment.state.seal {
665 EitherSeal::Alt(seal) => {
666 defines
667 .push(seal.vout)
668 .map_err(|_| MultiError::A(PrefabError::TooManyOutputs))?;
669 let primary = WOutpoint::Wout(seal.vout);
670 let noise = seal.noise.unwrap_or_else(|| {
671 Noise::with(primary, noise_engine.clone(), opout_no as u64)
672 });
673 let seal = WTxoSeal { primary, secondary: TxoSealExt::Noise(noise) };
674 seals.insert(opout_no as u16, seal).expect("checked above");
675 seal.auth_token()
676 }
677 EitherSeal::Token(auth) => auth,
678 };
679 let state = DataCell { data: assignment.state.data, auth, lock: None };
680 let named_state = NamedState { name: assignment.name, state };
681 owned.push(named_state);
682 }
683
684 let call = CallParams {
685 core: CoreParams { method: request.method, global: request.global, owned },
686 using,
687 reading: request.reading,
688 };
689
690 let operation = self
691 .contracts
692 .contract_call(request.contract_id, call, seals)
693 .map_err(MultiError::from_other_a)?;
694
695 Ok(Prefab { closes, defines, operation })
696 }
697
698 pub fn bundle(
710 &mut self,
711 requests: impl IntoIterator<Item = OpRequest<PrefabSeal>>,
712 change: Option<Vout>,
713 ) -> Result<PrefabBundle, MultiError<BundleError, <Sp::Stock as Stock>::Error>> {
714 let ops = requests.into_iter().map(|params| self.prefab(params));
715
716 let mut outpoints = BTreeSet::<Outpoint>::new();
717 let mut contracts = BTreeSet::new();
718 let mut prefabs = BTreeSet::new();
719 for prefab in ops {
720 let prefab = prefab.map_err(MultiError::from_other_a)?;
721 contracts.insert(prefab.operation.contract_id);
722 outpoints.extend(&prefab.closes);
723 prefabs.insert(prefab);
724 }
725
726 let mut blank_requests = Vec::new();
728 let root_noise_engine = self.noise_engine();
729 for contract_id in self.contracts.contract_ids() {
730 if contracts.contains(&contract_id) {
731 continue;
732 }
733 let owned = self.contracts.contract_state(contract_id).owned.clone();
735 let (using, prev): (Vec<_>, Vec<_>) = owned
736 .iter()
737 .flat_map(|(name, map)| map.iter().map(move |owned| (name, owned)))
738 .filter_map(|(name, owned)| {
739 let outpoint = owned.assignment.seal.primary;
740 if !outpoints.contains(&outpoint) {
741 return None;
742 }
743 let prevout = UsedState { addr: owned.addr, outpoint, satisfaction: None };
744 Some((prevout, (name.clone(), owned)))
745 })
746 .unzip();
747
748 if using.is_empty() {
749 continue;
750 };
751
752 let articles = self.contracts.contract_articles(contract_id);
753 let api = articles.default_api();
754 let mut calcs = BTreeMap::<StateName, StateCalc>::new();
755 for (name, state) in prev {
756 let calc = match calcs.entry(name.clone()) {
757 Entry::Vacant(entry) => {
758 let calc = api.calculate(name).map_err(MultiError::from_a)?;
759 entry.insert(calc)
760 }
761 Entry::Occupied(entry) => entry.into_mut(),
762 };
763 calc.accumulate(&state.assignment.data)
764 .map_err(MultiError::from_a)?;
765 }
766
767 let mut owned = Vec::new();
768 let mut nonce = 0;
769 let mut noise_engine = root_noise_engine.clone();
770 noise_engine.input_raw(contract_id.as_slice());
771 for (name, calc) in calcs {
772 for data in calc.diff().map_err(MultiError::from_a)? {
773 let vout = change.ok_or(MultiError::A(BundleError::ChangeRequired))?;
774 let noise =
775 Some(Noise::with(WOutpoint::Wout(vout), noise_engine.clone(), nonce));
776 let change = PrefabSeal { vout, noise };
777 nonce += 1;
778 let state = NamedState {
779 name: name.clone(),
780 state: Assignment { seal: EitherSeal::Alt(change), data },
781 };
782 owned.push(state);
783 }
784 }
785
786 let params = OpRequest {
787 contract_id,
788 method: MethodName::from(BP_BLANK_METHOD),
789 global: none!(),
790 reading: none!(),
791 using,
792 owned,
793 };
794 blank_requests.push(params);
795 }
796
797 for request in blank_requests {
798 let prefab = self.prefab(request).map_err(|err| match err {
799 MultiError::A(e) => MultiError::A(BundleError::Blank(e)),
800 MultiError::B(e) => MultiError::B(e),
801 MultiError::C(_) => unreachable!(),
802 })?;
803 prefabs.push(prefab);
804 }
805
806 Ok(PrefabBundle(
807 SmallOrdSet::try_from(prefabs)
808 .map_err(|_| MultiError::A(BundleError::TooManyBlanks))?,
809 ))
810 }
811
812 pub fn include(
814 &mut self,
815 bundle: &PrefabBundle,
816 witness: &Tx,
817 mpc: mpc::MerkleBlock,
818 dbc: Option<TapretProof>,
819 prevouts: &[Outpoint],
820 ) -> Result<(), IncludeError> {
821 for prefab in bundle {
822 let protocol_id = ProtocolId::from(prefab.operation.contract_id.to_byte_array());
823 let opid = prefab.operation.opid();
824 let mut map = bmap! {};
825 for prevout in &prefab.closes {
826 let pos = prevouts
827 .iter()
828 .position(|p| p == prevout)
829 .ok_or(IncludeError::MissingPrevout(*prevout))?;
830 map.insert(pos as u32, mmb::Message::from_byte_array(opid.to_byte_array()));
831 }
832 let anchor = Anchor {
833 mmb_proof: mmb::BundleProof { map: SmallOrdMap::from_checked(map) },
834 mpc_protocol: protocol_id,
835 mpc_proof: mpc.to_merkle_proof(protocol_id)?,
836 dbc_proof: dbc.clone(),
837 fallback_proof: default!(),
838 };
839 self.contracts
840 .include(prefab.operation.contract_id, opid, witness, anchor);
841 }
842 Ok(())
843 }
844
845 #[allow(clippy::result_large_err)]
859 pub fn consume<E>(
860 &mut self,
861 allow_unknown: bool,
862 reader: &mut StrictReader<impl ReadRaw>,
863 sig_validator: impl FnOnce(StrictHash, &Identity, &SigBlob) -> Result<(), E>,
864 ) -> Result<
865 (),
866 MultiError<ConsumeError<WTxoSeal>, <Sp::Stock as Stock>::Error, <Sp::Pile as Pile>::Error>,
867 >
868 where
869 <Sp::Pile as Pile>::Conf: From<<Sp::Stock as Stock>::Conf>,
870 {
871 let seal_resolver = |op: &Operation| {
872 self.wallet
873 .resolve_seals(op.destructible_out.iter().map(|cell| cell.auth))
874 .map(|seal| {
875 let auth = seal.auth_token();
876 let op_out =
877 op.destructible_out
878 .iter()
879 .position(|cell| cell.auth == auth)
880 .expect("invalid wallet implementation") as u16;
881 (op_out, seal)
882 })
883 .collect()
884 };
885 self.contracts
886 .consume(allow_unknown, reader, seal_resolver, sig_validator)
887 }
888
889 #[cfg(not(feature = "async"))]
890 pub fn update(
896 &mut self,
897 min_conformations: u32,
898 ) -> Result<(), MultiError<SyncError<W::Error>, <Sp::Stock as Stock>::Error>> {
899 self.wallet
900 .update_utxos()
901 .map_err(SyncError::Wallet)
902 .map_err(MultiError::from_a)?;
903 let last_height = self
904 .wallet
905 .last_block_height()
906 .map_err(SyncError::Wallet)
907 .map_err(MultiError::from_a)?;
908 self.contracts
909 .update_witnesses(self.wallet.txid_resolver(), last_height, min_conformations)
910 .map_err(MultiError::from_other_a)
911 }
912
913 #[cfg(feature = "async")]
914 pub async fn update_async(
920 &mut self,
921 min_conformations: u32,
922 ) -> Result<(), MultiError<SyncError<W::Error>, <Sp::Stock as Stock>::Error>>
923 where
924 Sp::Stock: 'static,
925 Sp::Pile: 'static,
926 {
927 self.wallet
928 .update_utxos_async()
929 .await
930 .map_err(SyncError::Wallet)
931 .map_err(MultiError::from_a)?;
932 let last_height = self
933 .wallet
934 .last_block_height_async()
935 .await
936 .map_err(SyncError::Wallet)
937 .map_err(MultiError::from_a)?;
938 self.contracts
939 .update_witnesses_async(
940 self.wallet.txid_resolver_async(),
941 last_height,
942 min_conformations,
943 )
944 .await
945 .map_err(MultiError::from_other_a)
946 }
947}
948
949impl CreateParams<Outpoint> {
950 pub fn new_bitcoin_testnet(codex_id: CodexId, name: impl Into<TypeName>) -> Self {
951 Self::new_testnet(codex_id, Consensus::Bitcoin, name)
952 }
953}
954
955#[derive(Debug, Display, Error, From)]
956#[display(doc_comments)]
957pub enum PrefabError {
958 TooManyInputs,
960
961 TooManyOutputs,
963
964 #[from]
965 #[display(inner)]
966 UnmatchedState(UnmatchedState),
967
968 #[from]
969 #[display(inner)]
970 Accept(AcceptError),
971}
972
973#[derive(Debug, Display, Error, From)]
974#[display(doc_comments)]
975pub enum BundleError {
976 #[from]
977 #[display(inner)]
978 Prefab(PrefabError),
979
980 Blank(PrefabError),
982
983 ChangeRequired,
986
987 #[from(StateUnknown)]
989 StateNameUnknown,
990
991 #[from]
992 #[display(inner)]
993 StateCalc(StateCalcError),
994
995 TooManyBlanks,
998}
999
1000#[derive(Copy, Clone, Eq, PartialEq, Debug, Display, Error, From)]
1001#[display(doc_comments)]
1002pub enum FulfillError {
1003 CallStateUnknown,
1005
1006 #[from(StateUnknown)]
1008 StateNameUnknown,
1009
1010 StateUnavailable,
1012
1013 StateInsufficient,
1015
1016 #[from]
1017 #[display(inner)]
1018 StateCalc(StateCalcError),
1019
1020 WoutRequiresGiveaway,
1024
1025 ValueMissed,
1027}
1028
1029#[derive(Clone, Eq, PartialEq, Debug, Display, Error, From)]
1030#[display(doc_comments)]
1031pub enum IncludeError {
1032 MissingPrevout(Outpoint),
1034
1035 #[from]
1037 Mpc(mpc::LeafNotKnown),
1038}
1039
1040#[cfg(feature = "binfile")]
1041mod _fs {
1042 use std::io;
1043 use std::path::Path;
1044
1045 use amplify::confinement::U24 as U24MAX;
1046 use binfile::BinFile;
1047 use commit_verify::StrictHash;
1048 use strict_encoding::{DecodeError, StreamReader, StreamWriter, StrictEncode};
1049
1050 use super::*;
1051 use crate::{Identity, SigBlob, CONSIGN_MAGIC_NUMBER, CONSIGN_VERSION};
1052
1053 pub const PREFAB_MAGIC_NUMBER: u64 = u64::from_be_bytes(*b"PREFABND");
1055 pub const PREFAB_VERSION: u16 = 0;
1057
1058 impl<W, Sp, S, C> RgbWallet<W, Sp, S, C>
1059 where
1060 W: WalletProvider,
1061 Sp: Stockpile,
1062 Sp::Pile: Pile<Seal = TxoSeal>,
1063 S: KeyedCollection<Key = CodexId, Value = Issuer>,
1064 C: KeyedCollection<Key = ContractId, Value = Contract<Sp::Stock, Sp::Pile>>,
1065 {
1066 #[allow(clippy::result_large_err)]
1067 pub fn consume_from_file<E>(
1068 &mut self,
1069 allow_unknown: bool,
1070 path: impl AsRef<Path>,
1071 sig_validator: impl FnOnce(StrictHash, &Identity, &SigBlob) -> Result<(), E>,
1072 ) -> Result<
1073 (),
1074 MultiError<
1075 ConsumeError<WTxoSeal>,
1076 <Sp::Stock as Stock>::Error,
1077 <Sp::Pile as Pile>::Error,
1078 >,
1079 >
1080 where
1081 <Sp::Pile as Pile>::Conf: From<<Sp::Stock as Stock>::Conf>,
1082 {
1083 let file = BinFile::<CONSIGN_MAGIC_NUMBER, CONSIGN_VERSION>::open(path)
1084 .map_err(MultiError::from_a)?;
1085 let mut reader = StrictReader::with(StreamReader::new::<{ usize::MAX }>(file));
1086 self.consume(allow_unknown, &mut reader, sig_validator)
1087 }
1088 }
1089
1090 impl PrefabBundle {
1091 pub fn load(path: impl AsRef<Path>) -> Result<Self, DecodeError> {
1092 let file = BinFile::<PREFAB_MAGIC_NUMBER, PREFAB_VERSION>::open(path)?;
1093 let reader = StreamReader::new::<U24MAX>(file);
1094 Self::strict_read(reader)
1095 }
1097
1098 pub fn save(&self, path: impl AsRef<Path>) -> io::Result<()> {
1099 let file = BinFile::<PREFAB_MAGIC_NUMBER, PREFAB_VERSION>::create_new(path)?;
1100 let writer = StreamWriter::new::<U24MAX>(file);
1101 self.strict_write(writer)
1102 }
1103 }
1104}