1use std::convert::Infallible;
23
24use bpwallet::psbt::{
25 Beneficiary as BpBeneficiary, Output as BpOutput, PropKey, PsbtConstructor,
26 PsbtMeta as BpPsbtMeta, TxParams as BpTxParams,
27};
28use bpwallet::{Address, IdxBase, LockTime, NormalIndex, Psbt as BpPsbt, Sats, SeqNo, Wallet};
29use psrgbt::bp_conversion_utils::{
30 address_network_bitcoin_to_bp, address_payload_bp_from_script_pubkey, outpoint_bitcoin_to_bp,
31 script_buf_to_script_pubkey, txid_bitcoin_to_bp, untweakedpublickey_to_internal_pk,
32};
33use psrgbt::{RgbOutExt, RgbPsbtExt, Terminal};
34use rgbstd::containers::Transfer;
35use rgbstd::invoice::{Beneficiary, RgbInvoice};
36use rgbstd::rgbcore::commit_verify::mpc::{Message, ProtocolId};
37use rgbstd::rgbcore::dbc::tapret::TapretCommitment;
38use rgbstd::rgbcore::seals::txout::CloseMethod;
39use rgbstd::validation::DbcProof;
40use rgbstd::{Operation, Outpoint, Txid};
41
42use super::*;
43use crate::pay::{PsbtMeta, TxParams};
44use crate::{CompositionError, DescriptorRgb, WalletError};
45
46impl From<BpTxParams> for TxParams {
47 fn from(value: BpTxParams) -> Self {
48 Self {
49 fee_sats: value.fee.sats(),
50 lock_time: value.lock_time.map(|l| l.into_consensus_u32()),
51 seq_no: value.seq_no.to_consensus_u32(),
52 change_shift: value.change_shift,
53 change_keychain: value.change_keychain.into(),
54 }
55 }
56}
57
58impl From<TxParams> for BpTxParams {
59 fn from(value: TxParams) -> Self {
60 Self {
61 fee: Sats::from_sats(value.fee_sats),
62 lock_time: value.lock_time.map(LockTime::from_consensus_u32),
63 seq_no: SeqNo::from_consensus_u32(value.seq_no),
64 change_shift: value.change_shift,
65 change_keychain: value.change_keychain.into(),
66 }
67 }
68}
69
70impl From<BpPsbtMeta> for PsbtMeta {
71 fn from(value: BpPsbtMeta) -> Self {
72 Self {
73 beneficiary_vout: None,
74 change_vout: value.change_vout.map(|v| v.into_u32()),
75 }
76 }
77}
78
79impl<K, D: DescriptorRgb + bpwallet::Descriptor<K>> WalletProvider for Wallet<K, D> {
80 type P = PropKey;
81 type O = BpOutput;
82 type Psbt = BpPsbt;
83
84 fn close_method(&self) -> CloseMethod { self.descriptor().close_method() }
85
86 fn is_unspent(&self, outpoint: Outpoint) -> bool {
87 self.is_unspent(outpoint_bitcoin_to_bp(outpoint))
88 }
89
90 fn has_outpoint(&self, outpoint: Outpoint) -> bool {
91 self.has_outpoint(outpoint_bitcoin_to_bp(outpoint))
92 }
93
94 fn should_include_witness(&self, witness_id: Option<Txid>) -> bool {
95 let witness_id = witness_id.map(txid_bitcoin_to_bp);
96 self.history()
97 .any(|row| !row.our_inputs.is_empty() && witness_id == Some(row.txid))
98 }
99
100 fn add_tapret_tweak(
101 &mut self,
102 terminal: Terminal,
103 tweak: TapretCommitment,
104 ) -> Result<(), Infallible> {
105 self.descriptor_mut(|descr| {
106 descr.with_descriptor_mut(|d| {
107 d.add_tapret_tweak(terminal, tweak);
108 Ok::<_, Infallible>(())
109 })
110 })
111 }
112
113 fn try_add_tapret_tweak(
114 &mut self,
115 transfer: Transfer,
116 txid: &Txid,
117 ) -> Result<(), Box<WalletError>> {
118 let contract_id = transfer.genesis.contract_id();
119 for keychain in self.keychains() {
120 let last_index = self.next_derivation_index(keychain, false).index() as u16;
121 let descr = self.descriptor();
122 if let Some((idx, tweak)) = transfer
123 .bundles
124 .iter()
125 .find(|bw| bw.witness_id() == *txid)
126 .and_then(|bw| {
127 let bundle_id = bw.bundle().bundle_id();
128 if let DbcProof::Tapret(tapret) = bw.anchor.dbc_proof.clone() {
129 let internal_pk = untweakedpublickey_to_internal_pk(tapret.internal_pk);
130 let commitment = bw
131 .anchor
132 .mpc_proof
133 .clone()
134 .convolve(ProtocolId::from(contract_id), Message::from(bundle_id))
135 .unwrap();
136 let tweak = TapretCommitment::with(commitment, tapret.path_proof.nonce());
137 (0..last_index)
138 .rev()
139 .map(NormalIndex::normal)
140 .find(|i| {
141 descr
142 .derive(keychain, i)
143 .any(|ds| ds.to_internal_pk() == Some(internal_pk))
144 })
145 .map(|idx| (idx, tweak))
146 } else {
147 None
148 }
149 })
150 {
151 let terminal = bpwallet::Terminal::new(keychain, idx);
152 self.add_tapret_tweak(terminal.into(), tweak).unwrap();
153 return Ok(());
154 }
155 }
156 Err(Box::new(WalletError::NoTweakTerminal))
157 }
158
159 fn create_psbt(
160 &mut self,
161 invoice: &RgbInvoice,
162 close_method: CloseMethod,
163 prev_outpoints: impl IntoIterator<Item = Outpoint>,
164 params: TransferParams,
165 ) -> Result<(Self::Psbt, PsbtMeta), CompositionError> {
166 let (beneficiaries, beneficiary_script) = match invoice.beneficiary.into_inner() {
167 Beneficiary::BlindedSeal(_) => (vec![], None),
168 Beneficiary::WitnessVout(pay2vout, _) => {
169 let script_pubkey = script_buf_to_script_pubkey(pay2vout.to_script());
170 let address = Address::new(
171 address_payload_bp_from_script_pubkey(&script_pubkey),
172 address_network_bitcoin_to_bp(invoice.address_network()),
173 );
174 let bp_beneficiary =
175 BpBeneficiary::new(address, Sats::from_sats(params.min_amount));
176 (vec![bp_beneficiary], Some(script_pubkey))
177 }
178 };
179
180 let prev_outpoints = prev_outpoints
181 .into_iter()
182 .map(outpoint_bitcoin_to_bp)
183 .collect::<Vec<_>>();
184
185 let bp_tx_params: BpTxParams = params.tx.into();
186 let (mut psbt, mut meta) =
187 self.construct_psbt(prev_outpoints, &beneficiaries, bp_tx_params)?;
188
189 let change_script = meta
190 .change_vout
191 .and_then(|vout| psbt.output(vout.to_usize()))
192 .map(|output| output.script.clone());
193
194 match close_method {
195 CloseMethod::TapretFirst => {
196 let tap_out_script = if let Some(change_script) = change_script.clone() {
197 psbt.set_rgb_tapret_host_on_change();
198 change_script
199 } else {
200 match invoice.beneficiary.into_inner() {
201 Beneficiary::WitnessVout(_, Some(ikey)) => {
202 let beneficiary_script = beneficiary_script.unwrap();
203 let ikey = untweakedpublickey_to_internal_pk(ikey);
204 psbt.outputs_mut()
205 .find(|o| o.script == beneficiary_script)
206 .unwrap()
207 .tap_internal_key = Some(ikey);
208 beneficiary_script
209 }
210 _ => return Err(CompositionError::NoOutputForTapretCommitment),
211 }
212 };
213 psbt.outputs_mut()
214 .find(|o| o.script.is_p2tr() && o.script == tap_out_script)
215 .map(|o| o.set_tapret_host());
216 psbt.sort_outputs_by(|output| !output.is_tapret_host())
218 .expect("PSBT must be modifiable at this stage");
219 }
220 CloseMethod::OpretFirst => {
221 psbt.set_opret_host();
222 psbt.sort_outputs_by(|output| !output.is_opret_host())
223 .expect("PSBT must be modifiable at this stage");
224 }
225 }
226
227 if let Some(ref change_script) = change_script {
228 for output in psbt.outputs() {
229 if output.script == *change_script {
230 meta.change_vout = Some(output.vout());
231 break;
232 }
233 }
234 }
235
236 let beneficiary_vout = match invoice.beneficiary.into_inner() {
237 Beneficiary::WitnessVout(pay2vout, _) => {
238 let s = script_buf_to_script_pubkey((*pay2vout).to_script());
239 let vout = psbt
240 .outputs()
241 .find(|output| output.script == s)
242 .map(BpOutput::vout)
243 .expect("PSBT without beneficiary address");
244 debug_assert_ne!(Some(vout), meta.change_vout);
245 Some(vout)
246 }
247 Beneficiary::BlindedSeal(_) => None,
248 };
249
250 let mut meta: PsbtMeta = meta.into();
251 meta.beneficiary_vout = beneficiary_vout.map(|v| v.into_u32());
252
253 Ok((psbt, meta))
254 }
255}