ord/subcommand/wallet/
mint.rs1use super::*;
2
3#[derive(Debug, Parser)]
4pub(crate) struct Mint {
5 #[clap(long, help = "Use <FEE_RATE> sats/vbyte for mint transaction.")]
6 fee_rate: FeeRate,
7 #[clap(long, help = "Mint <RUNE>. May contain `.` or `•`as spacers.")]
8 rune: SpacedRune,
9 #[clap(
10 long,
11 help = "Include <AMOUNT> postage with mint output. [default: 10000sat]"
12 )]
13 postage: Option<Amount>,
14 #[clap(long, help = "Send minted runes to <DESTINATION>.")]
15 destination: Option<Address<NetworkUnchecked>>,
16}
17
18#[derive(Serialize, Deserialize, Debug)]
19pub struct Output {
20 pub rune: SpacedRune,
21 pub pile: Pile,
22 pub mint: Txid,
23}
24
25impl Mint {
26 pub(crate) fn run(self, wallet: Wallet) -> SubcommandResult {
27 ensure!(
28 wallet.has_rune_index(),
29 "`ord wallet mint` requires index created with `--index-runes` flag",
30 );
31
32 let rune = self.rune.rune;
33
34 let bitcoin_client = wallet.bitcoin_client();
35
36 let block_height = bitcoin_client.get_block_count()?;
37
38 let Some((id, rune_entry, _)) = wallet.get_rune(rune)? else {
39 bail!("rune {rune} has not been etched");
40 };
41
42 let postage = self.postage.unwrap_or(TARGET_POSTAGE);
43
44 let amount = rune_entry
45 .mintable(block_height + 1)
46 .map_err(|err| anyhow!("rune {rune} {err}"))?;
47
48 let chain = wallet.chain();
49
50 let destination = match self.destination {
51 Some(destination) => destination.require_network(chain.network())?,
52 None => wallet.get_change_address()?,
53 };
54
55 ensure!(
56 destination.script_pubkey().dust_value() < postage,
57 "postage below dust limit of {}sat",
58 destination.script_pubkey().dust_value().to_sat()
59 );
60
61 let runestone = Runestone {
62 mint: Some(id),
63 ..default()
64 };
65
66 let script_pubkey = runestone.encipher();
67
68 ensure!(
69 script_pubkey.len() <= 82,
70 "runestone greater than maximum OP_RETURN size: {} > 82",
71 script_pubkey.len()
72 );
73
74 let unfunded_transaction = Transaction {
75 version: 2,
76 lock_time: LockTime::ZERO,
77 input: Vec::new(),
78 output: vec![
79 TxOut {
80 script_pubkey,
81 value: 0,
82 },
83 TxOut {
84 script_pubkey: destination.script_pubkey(),
85 value: postage.to_sat(),
86 },
87 ],
88 };
89
90 wallet.lock_non_cardinal_outputs()?;
91
92 let unsigned_transaction =
93 fund_raw_transaction(bitcoin_client, self.fee_rate, &unfunded_transaction)?;
94
95 let signed_transaction = bitcoin_client
96 .sign_raw_transaction_with_wallet(&unsigned_transaction, None, None)?
97 .hex;
98
99 let signed_transaction = consensus::encode::deserialize(&signed_transaction)?;
100
101 assert_eq!(
102 Runestone::decipher(&signed_transaction),
103 Some(Artifact::Runestone(runestone)),
104 );
105
106 let transaction = bitcoin_client.send_raw_transaction(&signed_transaction)?;
107
108 Ok(Some(Box::new(Output {
109 rune: self.rune,
110 pile: Pile {
111 amount,
112 divisibility: rune_entry.divisibility,
113 symbol: rune_entry.symbol,
114 },
115 mint: transaction,
116 })))
117 }
118}