ord/subcommand/wallet/
mint.rs

1use 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}