1use bitcoin::{
2 address::NetworkChecked,
3 blockdata::{opcodes, script::PushBytesBuf},
4 ecdsa,
5 hashes::Hash,
6 key::{
7 constants::{SCHNORR_PUBLIC_KEY_SIZE, SCHNORR_SIGNATURE_SIZE},
8 TapTweak, TweakedKeypair,
9 },
10 locktime::absolute::LockTime,
11 secp256k1::{rand, All, Keypair, Message, Secp256k1, SecretKey},
12 sighash::{EcdsaSighashType, Prevouts, SighashCache, TapSighashType},
13 taproot::{
14 self, LeafVersion, TapLeafHash, TaprootBuilder, TAPROOT_CONTROL_BASE_SIZE,
15 TAPROOT_CONTROL_NODE_SIZE,
16 },
17 transaction::Version,
18 Address, Amount, Network, OutPoint, ScriptBuf, Sequence, Transaction, TxIn, TxOut, Txid,
19 Witness,
20};
21use serde::{Deserialize, Serialize};
22use std::{collections::HashSet, str::FromStr};
23
24use ns_protocol::ns::{Name, MAX_NAME_BYTES};
25
26use crate::bitcoin::{BitCoinRPCOptions, BitcoinRPC};
27
28pub struct InscriberOptions {
29 pub bitcoin: BitCoinRPCOptions,
30}
31
32pub struct Inscriber {
33 secp: Secp256k1<All>,
34 pub bitcoin: BitcoinRPC,
35 pub network: Network,
36}
37
38#[derive(Clone, PartialEq, Debug)]
39pub struct UnspentTxOut {
40 pub txid: Txid,
41 pub vout: u32,
42 pub amount: Amount,
43 pub script_pubkey: ScriptBuf,
44}
45
46impl Inscriber {
47 pub fn new(opts: &InscriberOptions) -> anyhow::Result<Self> {
48 let secp = Secp256k1::new();
49
50 Ok(Self {
51 secp,
52 bitcoin: BitcoinRPC::new(&opts.bitcoin)?,
53 network: opts.bitcoin.network,
54 })
55 }
56
57 pub async fn inscribe(
58 &self,
59 names: &Vec<Name>,
60 fee_rate: Amount,
61 secret: &SecretKey,
62 unspent_txout: &UnspentTxOut,
63 ) -> anyhow::Result<Txid> {
64 let (signed_commit_tx, signed_reveal_tx) = self
65 .build_signed_inscription(names, fee_rate, secret, unspent_txout)
66 .await?;
67
68 let commit = self.bitcoin.send_transaction(&signed_commit_tx).await?;
69 let reveal = self
70 .bitcoin
71 .send_transaction(&signed_reveal_tx)
72 .await
73 .map_err(|err| {
74 anyhow::anyhow!("failed to send reveal transaction: {err}\ncommit tx: {commit}")
75 })?;
76
77 Ok(reveal)
78 }
79
80 pub async fn build_signed_inscription(
81 &self,
82 names: &Vec<Name>,
83 fee_rate: Amount,
84 secret: &SecretKey,
85 unspent_txout: &UnspentTxOut,
86 ) -> anyhow::Result<(Transaction, Transaction)> {
87 let keypair = Keypair::from_secret_key(&self.secp, secret);
88
89 let (unsigned_commit_tx, signed_reveal_tx) = self
90 .build_inscription_transactions(names, fee_rate, unspent_txout, Some(keypair))
91 .await?;
92
93 let mut signed_commit_tx = unsigned_commit_tx;
94 if unspent_txout.script_pubkey.is_p2tr() {
96 let mut sighasher = SighashCache::new(&mut signed_commit_tx);
97 let prevouts = vec![TxOut {
98 value: unspent_txout.amount,
99 script_pubkey: unspent_txout.script_pubkey.clone(),
100 }];
101 let sighash = sighasher
102 .taproot_key_spend_signature_hash(
103 0,
104 &Prevouts::All(&prevouts),
105 TapSighashType::Default,
106 )
107 .expect("failed to construct sighash");
108
109 let tweaked: TweakedKeypair = keypair.tap_tweak(&self.secp, None);
110 let sig = self.secp.sign_schnorr(
111 &Message::from_digest(sighash.to_byte_array()),
112 &tweaked.to_inner(),
113 );
114
115 let signature = taproot::Signature {
116 sig,
117 hash_ty: TapSighashType::Default,
118 };
119
120 sighasher
121 .witness_mut(0)
122 .expect("getting mutable witness reference should work")
123 .push(&signature.to_vec());
124 } else if unspent_txout.script_pubkey.is_p2wpkh() {
125 let mut sighasher = SighashCache::new(&mut signed_commit_tx);
126 let sighash = sighasher
127 .p2wpkh_signature_hash(
128 0,
129 &unspent_txout.script_pubkey,
130 unspent_txout.amount,
131 EcdsaSighashType::All,
132 )
133 .expect("failed to create sighash");
134
135 let sig = self
136 .secp
137 .sign_ecdsa(&Message::from(sighash), &keypair.secret_key());
138 let signature = ecdsa::Signature {
139 sig,
140 hash_ty: EcdsaSighashType::All,
141 };
142 signed_commit_tx.input[0].witness = Witness::p2wpkh(&signature, &keypair.public_key());
143 } else {
144 anyhow::bail!("unsupported script_pubkey");
145 }
146
147 let test_txs = self
148 .bitcoin
149 .test_mempool_accept(&[&signed_commit_tx, &signed_reveal_tx])
150 .await?;
151 for r in &test_txs {
152 if !r.allowed {
153 anyhow::bail!("failed to accept transaction: {:?}", &test_txs);
154 }
155 }
156
157 Ok((signed_commit_tx, signed_reveal_tx))
158 }
159
160 pub async fn send_sats(
161 &self,
162 fee_rate: Amount,
163 secret: &SecretKey,
164 unspent_txout: &UnspentTxOut,
165 to: &Address<NetworkChecked>,
166 amount: Amount,
167 ) -> anyhow::Result<Txid> {
168 let keypair = Keypair::from_secret_key(&self.secp, secret);
169
170 let mut tx = Transaction {
171 version: Version::TWO,
172 lock_time: LockTime::ZERO,
173 input: vec![TxIn {
174 previous_output: OutPoint {
175 txid: unspent_txout.txid,
176 vout: unspent_txout.vout,
177 },
178 script_sig: ScriptBuf::new(),
179 sequence: Sequence::ENABLE_RBF_NO_LOCKTIME,
180 witness: Witness::new(),
181 }],
182 output: vec![
183 TxOut {
184 value: amount,
185 script_pubkey: to.script_pubkey(),
186 },
187 TxOut {
188 value: unspent_txout.amount, script_pubkey: unspent_txout.script_pubkey.clone(),
191 },
192 ],
193 };
194
195 let fee = {
196 let mut v_tx = tx.clone();
197 v_tx.input[0].witness = Witness::from_slice(&[&[0; SCHNORR_SIGNATURE_SIZE]]);
198 fee_rate
199 .checked_mul(v_tx.vsize() as u64)
200 .expect("should compute commit_tx fee")
201 };
202
203 let change_value = unspent_txout
204 .amount
205 .checked_sub(amount)
206 .ok_or_else(|| anyhow::anyhow!("should compute amount"))?;
207 if change_value > fee {
208 tx.output[1].value = change_value - fee;
209 } else {
210 tx.output.pop(); }
212
213 if unspent_txout.script_pubkey.is_p2tr() {
214 let mut sighasher = SighashCache::new(&mut tx);
215 let prevouts = vec![TxOut {
216 value: unspent_txout.amount,
217 script_pubkey: unspent_txout.script_pubkey.clone(),
218 }];
219 let sighash = sighasher
220 .taproot_key_spend_signature_hash(
221 0,
222 &Prevouts::All(&prevouts),
223 TapSighashType::Default,
224 )
225 .expect("failed to construct sighash");
226
227 let tweaked: TweakedKeypair = keypair.tap_tweak(&self.secp, None);
228 let sig = self.secp.sign_schnorr(
229 &Message::from_digest(sighash.to_byte_array()),
230 &tweaked.to_inner(),
231 );
232
233 let signature = taproot::Signature {
234 sig,
235 hash_ty: TapSighashType::Default,
236 };
237
238 sighasher
239 .witness_mut(0)
240 .expect("getting mutable witness reference should work")
241 .push(&signature.to_vec());
242 } else if unspent_txout.script_pubkey.is_p2wpkh() {
243 let mut sighasher = SighashCache::new(&mut tx);
244 let sighash = sighasher
245 .p2wpkh_signature_hash(
246 0,
247 &unspent_txout.script_pubkey,
248 unspent_txout.amount,
249 EcdsaSighashType::All,
250 )
251 .expect("failed to create sighash");
252
253 let sig = self
254 .secp
255 .sign_ecdsa(&Message::from(sighash), &keypair.secret_key());
256 let signature = ecdsa::Signature {
257 sig,
258 hash_ty: EcdsaSighashType::All,
259 };
260 tx.input[0].witness = Witness::p2wpkh(&signature, &keypair.public_key());
261 } else {
262 anyhow::bail!("unsupported script_pubkey");
263 }
264
265 let test_txs = self.bitcoin.test_mempool_accept(&[&tx]).await?;
266 for r in &test_txs {
267 if !r.allowed {
268 anyhow::bail!("failed to accept transaction: {:?}", &test_txs);
269 }
270 }
271
272 let txid = self.bitcoin.send_transaction(&tx).await?;
273 Ok(txid)
274 }
275
276 pub async fn collect_sats(
277 &self,
278 fee_rate: Amount,
279 unspent_txouts: &[(SecretKey, UnspentTxOut)],
280 to: &Address<NetworkChecked>,
281 ) -> anyhow::Result<Txid> {
282 let amount = unspent_txouts.iter().map(|(_, v)| v.amount).sum::<Amount>();
283
284 let mut tx = Transaction {
285 version: Version::TWO,
286 lock_time: LockTime::ZERO,
287 input: unspent_txouts
288 .iter()
289 .map(|(_, v)| TxIn {
290 previous_output: OutPoint {
291 txid: v.txid,
292 vout: v.vout,
293 },
294 script_sig: ScriptBuf::new(),
295 sequence: Sequence::ENABLE_RBF_NO_LOCKTIME,
296 witness: Witness::new(),
297 })
298 .collect(),
299 output: vec![TxOut {
300 value: amount,
301 script_pubkey: to.script_pubkey(),
302 }],
303 };
304
305 let fee = {
306 let mut v_tx = tx.clone();
307 for input in v_tx.input.iter_mut() {
308 input.witness = Witness::from_slice(&[&[0; SCHNORR_SIGNATURE_SIZE]]);
309 }
310 fee_rate
311 .checked_mul(v_tx.vsize() as u64)
312 .expect("should compute commit_tx fee")
313 };
314
315 let change_value = amount
316 .checked_sub(fee)
317 .ok_or_else(|| anyhow::anyhow!("should compute change value"))?;
318 tx.output[0].value = change_value;
319
320 let mut sighasher = SighashCache::new(&mut tx);
321
322 for (i, (secret, unspent_txout)) in unspent_txouts.iter().enumerate() {
323 let keypair = Keypair::from_secret_key(&self.secp, secret);
324
325 if unspent_txout.script_pubkey.is_p2tr() {
326 let tweaked: TweakedKeypair = keypair.tap_tweak(&self.secp, None);
327 let sighash = sighasher
328 .taproot_key_spend_signature_hash(
329 0,
330 &Prevouts::All(&[TxOut {
331 value: unspent_txout.amount,
332 script_pubkey: unspent_txout.script_pubkey.clone(),
333 }]),
334 TapSighashType::Default,
335 )
336 .expect("failed to construct sighash");
337
338 let sig = self.secp.sign_schnorr(
339 &Message::from_digest(sighash.to_byte_array()),
340 &tweaked.to_inner(),
341 );
342
343 let signature = taproot::Signature {
344 sig,
345 hash_ty: TapSighashType::Default,
346 };
347
348 sighasher
349 .witness_mut(i)
350 .expect("getting mutable witness reference should work")
351 .push(&signature.to_vec());
352 } else if unspent_txout.script_pubkey.is_p2wpkh() {
353 let sighash = sighasher
354 .p2wpkh_signature_hash(
355 0,
356 &unspent_txout.script_pubkey,
357 unspent_txout.amount,
358 EcdsaSighashType::All,
359 )
360 .expect("failed to create sighash");
361
362 let sig = self
363 .secp
364 .sign_ecdsa(&Message::from(sighash), &keypair.secret_key());
365 let signature = ecdsa::Signature {
366 sig,
367 hash_ty: EcdsaSighashType::All,
368 };
369 *sighasher
370 .witness_mut(i)
371 .expect("getting mutable witness reference should work") =
372 Witness::p2wpkh(&signature, &keypair.public_key());
373 } else {
374 anyhow::bail!("unsupported script_pubkey");
375 }
376 }
377
378 let test_txs = self.bitcoin.test_mempool_accept(&[&tx]).await?;
379 for r in &test_txs {
380 if !r.allowed {
381 anyhow::bail!("failed to accept transaction: {:?}", &test_txs);
382 }
383 }
384
385 let txid = self.bitcoin.send_transaction(&tx).await?;
386 Ok(txid)
387 }
388
389 pub async fn build_inscription_transactions(
391 &self,
392 names: &Vec<Name>,
393 fee_rate: Amount,
394 unspent_txout: &UnspentTxOut,
395 inscription_keypair: Option<Keypair>,
396 ) -> anyhow::Result<(Transaction, Transaction)> {
397 if names.is_empty() {
398 anyhow::bail!("no names to inscribe");
399 }
400 if fee_rate.to_sat() == 0 {
401 anyhow::bail!("fee rate cannot be zero");
402 }
403
404 if let Some(name) = check_duplicate(names) {
405 anyhow::bail!("duplicate name {}", name);
406 }
407
408 for name in names {
409 if let Err(err) = name.validate() {
410 anyhow::bail!("invalid name {}: {}", name.name, err);
411 }
412 }
413
414 let keypair = inscription_keypair
415 .unwrap_or_else(|| Keypair::new(&self.secp, &mut rand::thread_rng()));
416
417 let (unsigned_commit_tx, signed_reveal_tx) =
418 self.create_inscription_transactions(names, fee_rate, unspent_txout, &keypair)?;
419 Ok((unsigned_commit_tx, signed_reveal_tx))
420 }
421
422 pub fn preview_inscription_transactions(
423 names: &Vec<Name>,
424 fee_rate: Amount,
425 ) -> anyhow::Result<(Transaction, Transaction, Amount)> {
426 if let Some(name) = check_duplicate(names) {
427 anyhow::bail!("duplicate name {}", name);
428 }
429
430 let mut reveal_script = ScriptBuf::builder()
431 .push_slice([0; SCHNORR_PUBLIC_KEY_SIZE])
432 .push_opcode(opcodes::all::OP_CHECKSIG)
433 .push_opcode(opcodes::OP_FALSE)
434 .push_opcode(opcodes::all::OP_IF);
435 for name in names {
436 let data = name.to_bytes()?;
437 if data.len() > MAX_NAME_BYTES {
438 anyhow::bail!("name {} is too large", name.name);
439 }
440 reveal_script = reveal_script.push_slice(PushBytesBuf::try_from(data)?);
441 }
442 let reveal_script = reveal_script
443 .push_opcode(opcodes::all::OP_ENDIF)
444 .into_script();
445
446 let dust_value = reveal_script.dust_value();
447 let dust_value = fee_rate
448 .checked_mul(42)
449 .expect("should compute amount")
450 .checked_add(dust_value)
451 .expect("should compute amount");
452
453 let mut witness = Witness::default();
454 witness.push(
455 taproot::Signature::from_slice(&[0; SCHNORR_SIGNATURE_SIZE])
456 .unwrap()
457 .to_vec(),
458 );
459 witness.push(reveal_script);
460 witness.push([0; TAPROOT_CONTROL_BASE_SIZE + TAPROOT_CONTROL_NODE_SIZE]);
461 let script_pubkey = ScriptBuf::from_bytes([0; SCHNORR_PUBLIC_KEY_SIZE].to_vec());
462 let p_reveal_tx = Transaction {
463 version: Version::TWO,
464 lock_time: LockTime::ZERO,
465 input: vec![TxIn {
466 previous_output: OutPoint::null(),
467 script_sig: ScriptBuf::default(),
468 sequence: Sequence::ENABLE_RBF_NO_LOCKTIME,
469 witness,
470 }],
471 output: vec![TxOut {
472 value: dust_value,
473 script_pubkey: script_pubkey.clone(),
474 }],
475 };
476
477 let reveal_tx_fee = fee_rate
478 .checked_mul(p_reveal_tx.vsize() as u64)
479 .expect("should compute reveal_tx fee");
480 let total_value = reveal_tx_fee
481 .checked_add(dust_value)
482 .expect("should compute amount");
483
484 let p_commit_tx = Transaction {
485 version: Version::TWO,
486 lock_time: LockTime::ZERO,
487 input: vec![TxIn {
488 previous_output: OutPoint::null(),
489 script_sig: ScriptBuf::new(),
490 sequence: Sequence::ENABLE_RBF_NO_LOCKTIME,
491 witness: Witness::from_slice(&[&[0; SCHNORR_SIGNATURE_SIZE]]),
492 }],
493 output: vec![TxOut {
494 value: total_value,
495 script_pubkey: script_pubkey.clone(),
496 }],
497 };
498 let total_value = fee_rate
499 .checked_mul(p_commit_tx.vsize() as u64)
500 .expect("should compute amount")
501 .checked_add(total_value)
502 .expect("should compute amount");
503
504 Ok((p_commit_tx, p_reveal_tx, total_value))
505 }
506
507 fn create_inscription_transactions(
509 &self,
510 names: &Vec<Name>,
511 fee_rate: Amount,
512 unspent_txout: &UnspentTxOut,
513 keypair: &Keypair,
514 ) -> anyhow::Result<(Transaction, Transaction)> {
515 let (public_key, _parity) = keypair.x_only_public_key();
518
519 let mut reveal_script = ScriptBuf::builder()
520 .push_slice(public_key.serialize())
521 .push_opcode(opcodes::all::OP_CHECKSIG)
522 .push_opcode(opcodes::OP_FALSE)
523 .push_opcode(opcodes::all::OP_IF);
524 for name in names {
525 let data = name.to_bytes()?;
526 if data.len() > MAX_NAME_BYTES {
527 anyhow::bail!("name {} is too large", name.name);
528 }
529 reveal_script = reveal_script.push_slice(PushBytesBuf::try_from(data)?);
530 }
531 let reveal_script = reveal_script
532 .push_opcode(opcodes::all::OP_ENDIF)
533 .into_script();
534
535 let dust_value = reveal_script.dust_value();
536
537 let taproot_spend_info = TaprootBuilder::new()
538 .add_leaf(0, reveal_script.clone())
539 .expect("adding leaf should work")
540 .finalize(&self.secp, public_key)
541 .expect("finalizing taproot builder should work");
542
543 let control_block = taproot_spend_info
544 .control_block(&(reveal_script.clone(), LeafVersion::TapScript))
545 .expect("should compute control block");
546 let control_block_bytes = &control_block.serialize();
547
548 let commit_tx_address =
549 Address::p2tr_tweaked(taproot_spend_info.output_key(), self.network);
550
551 let mut reveal_tx = Transaction {
552 version: Version::TWO, lock_time: LockTime::ZERO, input: vec![TxIn {
555 previous_output: OutPoint::null(), script_sig: ScriptBuf::default(), sequence: Sequence::ENABLE_RBF_NO_LOCKTIME,
558 witness: Witness::default(), }],
560 output: vec![TxOut {
561 value: unspent_txout.amount,
562 script_pubkey: unspent_txout.script_pubkey.clone(),
563 }],
564 };
565
566 let reveal_tx_fee = {
567 let mut v_reveal_tx = reveal_tx.clone();
568 let mut witness = Witness::default();
569 witness.push(
570 taproot::Signature::from_slice(&[0; SCHNORR_SIGNATURE_SIZE])
571 .unwrap()
572 .to_vec(),
573 );
574 witness.push(reveal_script.clone());
575 witness.push(control_block_bytes);
576 v_reveal_tx.input[0].witness = witness;
577 fee_rate
578 .checked_mul(v_reveal_tx.vsize() as u64)
579 .expect("should compute reveal_tx fee")
580 };
581
582 let mut commit_tx = Transaction {
583 version: Version::TWO,
584 lock_time: LockTime::ZERO,
585 input: vec![TxIn {
586 previous_output: OutPoint {
587 txid: unspent_txout.txid,
588 vout: unspent_txout.vout,
589 },
590 script_sig: ScriptBuf::new(),
591 sequence: Sequence::ENABLE_RBF_NO_LOCKTIME,
592 witness: Witness::new(),
593 }],
594 output: vec![TxOut {
595 value: unspent_txout.amount,
596 script_pubkey: commit_tx_address.script_pubkey(),
597 }],
598 };
599
600 let commit_tx_fee = {
601 let mut v_commit_tx = commit_tx.clone();
602 v_commit_tx.input[0].witness = Witness::from_slice(&[&[0; SCHNORR_SIGNATURE_SIZE]]);
603 fee_rate
604 .checked_mul(v_commit_tx.vsize() as u64)
605 .expect("should compute commit_tx fee")
606 };
607 let change_value = unspent_txout
608 .amount
609 .checked_sub(commit_tx_fee)
610 .ok_or_else(|| anyhow::anyhow!("should compute commit_tx fee"))?;
611
612 commit_tx.output[0].value = change_value;
613
614 let change_value = change_value
615 .checked_sub(reveal_tx_fee)
616 .ok_or_else(|| anyhow::anyhow!("should compute reveal_tx fee"))?;
617 if change_value < dust_value {
618 anyhow::bail!(
619 "input value is too small, need another {} sats",
620 dust_value - change_value
621 );
622 }
623
624 reveal_tx.output[0].value = change_value;
625 reveal_tx.input[0].previous_output = OutPoint {
626 txid: commit_tx.txid(),
627 vout: 0,
628 };
629
630 {
632 let mut sighasher = SighashCache::new(&mut reveal_tx);
633 let prevouts = vec![commit_tx.output[0].clone()];
634 let sighash = sighasher
635 .taproot_script_spend_signature_hash(
636 0,
637 &Prevouts::All(&prevouts),
638 TapLeafHash::from_script(&reveal_script, LeafVersion::TapScript),
639 TapSighashType::Default,
640 )
641 .expect("failed to construct sighash");
642
643 let sig = self
644 .secp
645 .sign_schnorr(&Message::from_digest(sighash.to_byte_array()), keypair);
646
647 let witness = sighasher
648 .witness_mut(0)
649 .expect("getting mutable witness reference should work");
650 witness.push(
651 taproot::Signature {
652 sig,
653 hash_ty: TapSighashType::Default,
654 }
655 .to_vec(),
656 );
657 witness.push(reveal_script);
658 witness.push(control_block_bytes);
659 }
660
661 Ok((commit_tx, reveal_tx))
662 }
663}
664
665pub fn check_duplicate(names: &Vec<Name>) -> Option<String> {
666 let mut set: HashSet<String> = HashSet::new();
667 for name in names {
668 if set.contains(&name.name) {
669 return Some(name.name.clone());
670 }
671 set.insert(name.name.clone());
672 }
673 None
674}
675
676#[derive(Clone, PartialEq, Debug, Serialize, Deserialize)]
677pub struct UnspentTxOutJSON {
678 pub txid: String,
679 pub vout: u32,
680 pub amount: u64,
681 pub script_pubkey: String,
682}
683
684impl UnspentTxOutJSON {
685 pub fn to(&self) -> anyhow::Result<UnspentTxOut> {
686 Ok(UnspentTxOut {
687 txid: Txid::from_str(&self.txid)?,
688 vout: self.vout,
689 amount: Amount::from_sat(self.amount),
690 script_pubkey: ScriptBuf::from_hex(&self.script_pubkey)?,
691 })
692 }
693
694 pub fn from(tx: UnspentTxOut) -> Self {
695 Self {
696 txid: tx.txid.to_string(),
697 vout: tx.vout,
698 amount: tx.amount.to_sat(),
699 script_pubkey: tx.script_pubkey.to_hex_string(),
700 }
701 }
702}
703
704#[cfg(test)]
705mod tests {
706 use super::*;
707 use hex_literal::hex;
708 use serde_json::to_value;
709
710 use ns_indexer::envelope::Envelope;
711 use ns_protocol::{
712 ed25519,
713 ns::{Bytes32, Operation, PublicKeyParams, Service, ThresholdLevel, Value},
714 };
715
716 fn get_name(name: &str) -> Name {
717 let secret_key = Bytes32(hex!(
718 "7ef3811aabb916dc2f646ef1a371b90adec91bc07992cd4d44c156c42fc1b300"
719 ));
720 let public_key = Bytes32(hex!(
721 "ee90735ac719e85dc2f3e5974036387fdf478af7d9d1f8480e97eee601890266"
722 ));
723 let params = PublicKeyParams {
724 public_keys: vec![public_key],
725 threshold: Some(1),
726 kind: None,
727 };
728
729 let signer = ed25519::SigningKey::from_bytes(&secret_key.0);
730 let signers = vec![signer];
731
732 let mut name = Name {
733 name: name.to_string(),
734 sequence: 0,
735 service: Service {
736 code: 0,
737 operations: vec![Operation {
738 subcode: 1,
739 params: Value::from(¶ms),
740 }],
741 attesters: None,
742 },
743 signatures: vec![],
744 };
745 name.sign(¶ms, ThresholdLevel::Default, &signers)
746 .unwrap();
747 assert!(name.validate().is_ok());
748 name
749 }
750
751 #[test]
752 fn unspent_txout_json_works() {
753 let json_str = serde_json::json!({
754 "txid": "8e9d3e0d762c1d2348a2ca046b36f8de001f740c976b09c046ee1f09a8680131",
755 "vout": 0,
756 "amount": 4929400,
757 "script_pubkey": "0014d37960b3783772f0b6e5a0917f163fa642b3a7fc"
758 });
759 let json: UnspentTxOutJSON = serde_json::from_value(json_str).unwrap();
760 let tx = json.to().unwrap();
761 let json2 = UnspentTxOutJSON::from(tx);
762 assert_eq!(json, json2)
763 }
764
765 #[test]
766 fn preview_inscription_transactions_works() {
767 let names = vec![get_name("0"), get_name("a")];
768 let fee_rate = Amount::from_sat(10);
769 let (commit_tx, reveal_tx, total_value) =
770 Inscriber::preview_inscription_transactions(&names, fee_rate).unwrap();
771
772 assert!(fee_rate.checked_mul(commit_tx.vsize() as u64).unwrap() > Amount::from_sat(0));
773 assert!(fee_rate.checked_mul(reveal_tx.vsize() as u64).unwrap() > Amount::from_sat(0));
774 assert!(total_value > Amount::from_sat(0));
775
776 let envelopes = Envelope::from_transaction(&commit_tx);
777 assert!(envelopes.is_empty());
778
779 let envelopes = Envelope::from_transaction(&reveal_tx);
780
781 assert_eq!(1, envelopes.len());
782 assert_eq!(reveal_tx.txid(), envelopes[0].txid);
783 assert_eq!(0, envelopes[0].vin);
784 assert_eq!(names, envelopes[0].payload);
785 }
786
787 #[tokio::test(flavor = "current_thread")]
788 #[ignore]
789 async fn inscriber_works() {
790 dotenvy::from_filename("sample.env").expect(".env file not found");
791
792 let rpcurl = std::env::var("BITCOIN_RPC_URL").unwrap();
793 let rpcuser = std::env::var("BITCOIN_RPC_USER").unwrap_or_default();
794 let rpcpassword = std::env::var("BITCOIN_RPC_PASSWORD").unwrap_or_default();
795 let rpctoken = std::env::var("BITCOIN_RPC_TOKEN").unwrap_or_default();
796 let network = Network::from_core_arg(&std::env::var("BITCOIN_NETWORK").unwrap_or_default())
797 .unwrap_or(Network::Regtest);
798
799 let secp = Secp256k1::new();
800 let keypair = Keypair::new(&secp, &mut rand::thread_rng());
801 let (public_key, _parity) = keypair.x_only_public_key();
802 let script_pubkey = ScriptBuf::new_p2tr(&secp, public_key, None);
803 let address: Address<NetworkChecked> =
804 Address::from_script(&script_pubkey, network).unwrap();
805
806 println!("rpcurl: {}, network: {}", rpcurl, network);
807 println!("address: {}", address);
808
809 let inscriber = Inscriber::new(&InscriberOptions {
810 bitcoin: BitCoinRPCOptions {
811 rpcurl,
812 rpcuser,
813 rpcpassword,
814 rpctoken,
815 network,
816 },
817 })
818 .unwrap();
819
820 inscriber.bitcoin.ping().await.unwrap();
821
822 let _ = inscriber
825 .bitcoin
826 .call::<serde_json::Value>("createwallet", &["testwallet".into()])
827 .await;
828 let _ = inscriber
829 .bitcoin
830 .call::<serde_json::Value>("loadwallet", &["testwallet".into()])
831 .await;
832 let _ = inscriber
833 .bitcoin
834 .call::<serde_json::Value>(
835 "generatetoaddress",
836 &[1.into(), to_value(&address).unwrap()],
837 )
838 .await
839 .unwrap();
840 let txid: Txid = inscriber
841 .bitcoin
842 .call("sendtoaddress", &[to_value(&address).unwrap(), 1.into()])
843 .await
844 .unwrap();
845
846 let tx = inscriber.bitcoin.get_transaction(&txid).await.unwrap();
848 println!("tx: {:?}", tx);
849
850 let unspent_txs = tx
851 .output
852 .iter()
853 .enumerate()
854 .filter_map(|(i, v)| {
855 if v.script_pubkey == address.script_pubkey() {
856 Some(UnspentTxOut {
857 txid: tx.txid(),
858 vout: i as u32,
859 amount: v.value,
860 script_pubkey: v.script_pubkey.clone(),
861 })
862 } else {
863 None
864 }
865 })
866 .collect::<Vec<_>>();
867 println!("unspent_txs: {:#?}", unspent_txs);
868
869 let names = vec![get_name("0")];
870 let fee_rate = Amount::from_sat(20);
871 let txid = inscriber
872 .inscribe(
873 &names,
874 fee_rate,
875 &keypair.secret_key(),
876 unspent_txs.first().unwrap(),
877 )
878 .await
879 .unwrap();
880 println!("txid: {}", txid);
881
882 let tx = inscriber.bitcoin.get_transaction(&txid).await.unwrap();
884 println!("tx: {:?}", tx);
885
886 let envelopes = Envelope::from_transaction(&tx);
887 assert_eq!(1, envelopes.len());
888 assert_eq!(txid, envelopes[0].txid);
889 assert_eq!(0, envelopes[0].vin);
890 assert_eq!(names, envelopes[0].payload);
891
892 let _ = inscriber
894 .bitcoin
895 .call::<serde_json::Value>(
896 "generatetoaddress",
897 &[1.into(), to_value(&address).unwrap()],
898 )
899 .await
900 .unwrap();
901
902 let tx_info = inscriber.bitcoin.get_transaction_info(&txid).await.unwrap();
903 println!("tx_info: {:?}", tx_info);
904 assert!(tx_info.blockhash.is_some());
905
906 let unspent_txs = tx
908 .output
909 .iter()
910 .enumerate()
911 .filter_map(|(i, v)| {
912 if v.script_pubkey == address.script_pubkey() {
913 Some(UnspentTxOut {
914 txid: tx.txid(),
915 vout: i as u32,
916 amount: v.value,
917 script_pubkey: v.script_pubkey.clone(),
918 })
919 } else {
920 None
921 }
922 })
923 .collect::<Vec<_>>();
924 println!("unspent_txs: {:#?}", unspent_txs);
925
926 let names = "0123456789abcdefghijklmnopqrstuvwxyz"
927 .split("")
928 .skip(1)
929 .take(36)
930 .map(get_name)
931 .collect::<Vec<_>>();
932 assert_eq!(36, names.len());
933 assert_eq!("0", names[0].name);
934 assert_eq!("z", names[35].name);
935
936 let txid = inscriber
937 .inscribe(
938 &names,
939 fee_rate,
940 &keypair.secret_key(),
941 unspent_txs.first().unwrap(),
942 )
943 .await
944 .unwrap();
945 println!("txid: {}", txid);
946
947 let _ = inscriber
949 .bitcoin
950 .call::<serde_json::Value>(
951 "generatetoaddress",
952 &[1.into(), to_value(&address).unwrap()],
953 )
954 .await
955 .unwrap();
956
957 let tx_info = inscriber.bitcoin.get_transaction_info(&txid).await.unwrap();
958 assert!(tx_info.blockhash.is_some());
959 }
960}