Skip to content

Commit

Permalink
Enable rune burning in wallet (#4117)
Browse files Browse the repository at this point in the history
  • Loading branch information
raphjaph authored Dec 12, 2024
1 parent 894c41d commit 909a4c5
Show file tree
Hide file tree
Showing 5 changed files with 703 additions and 291 deletions.
144 changes: 86 additions & 58 deletions src/subcommand/wallet/burn.rs
Original file line number Diff line number Diff line change
Expand Up @@ -28,68 +28,96 @@ pub struct Burn {
you understand the implications."
)]
no_limit: bool,
inscription: InscriptionId,
asset: Outgoing,
}

impl Burn {
pub(crate) fn run(self, wallet: Wallet) -> SubcommandResult {
let inscription_info = wallet
.inscription_info()
.get(&self.inscription)
.ok_or_else(|| anyhow!("inscription {} not found", self.inscription))?
.clone();

let metadata = WalletCommand::parse_metadata(self.cbor_metadata, self.json_metadata)?;

ensure!(
inscription_info.value.is_some(),
"Cannot burn unbound inscription"
);

let mut builder = script::Builder::new().push_opcode(opcodes::all::OP_RETURN);

// add empty metadata if none is supplied so we can add padding
let metadata = metadata.unwrap_or_default();

let push: &script::PushBytes = metadata.as_slice().try_into().with_context(|| {
format!(
"metadata length {} over maximum {}",
metadata.len(),
u32::MAX
)
})?;
builder = builder.push_slice(push);

// pad OP_RETURN script to least five bytes to ensure transaction base size
// is greater than 64 bytes
let padding = 5usize.saturating_sub(builder.as_script().len());
if padding > 0 {
// subtract one byte push opcode from padding length
let padding = vec![0; padding - 1];
let push: &script::PushBytes = padding.as_slice().try_into().unwrap();
builder = builder.push_slice(push);
}

let script_pubkey = builder.into_script();

ensure!(
self.no_limit || script_pubkey.len() <= MAX_STANDARD_OP_RETURN_SIZE,
"OP_RETURN with metadata larger than maximum: {} > {}",
script_pubkey.len(),
MAX_STANDARD_OP_RETURN_SIZE,
);

let burn_amount = Amount::from_sat(1);

let unsigned_transaction = Self::create_unsigned_burn_transaction(
&wallet,
inscription_info.satpoint,
self.fee_rate,
script_pubkey,
burn_amount,
)?;
let (unsigned_transaction, burn_amount) = match self.asset {
Outgoing::InscriptionId(id) => {
let inscription_info = wallet
.inscription_info()
.get(&id)
.ok_or_else(|| anyhow!("inscription {id} not found"))?
.clone();

let metadata = WalletCommand::parse_metadata(self.cbor_metadata, self.json_metadata)?;

ensure!(
inscription_info.value.is_some(),
"Cannot burn unbound inscription"
);

let mut builder = script::Builder::new().push_opcode(opcodes::all::OP_RETURN);

// add empty metadata if none is supplied so we can add padding
let metadata = metadata.unwrap_or_default();

let push: &script::PushBytes = metadata.as_slice().try_into().with_context(|| {
format!(
"metadata length {} over maximum {}",
metadata.len(),
u32::MAX
)
})?;
builder = builder.push_slice(push);

// pad OP_RETURN script to least five bytes to ensure transaction base size
// is greater than 64 bytes
let padding = 5usize.saturating_sub(builder.as_script().len());
if padding > 0 {
// subtract one byte push opcode from padding length
let padding = vec![0; padding - 1];
let push: &script::PushBytes = padding.as_slice().try_into().unwrap();
builder = builder.push_slice(push);
}

let script_pubkey = builder.into_script();

ensure!(
self.no_limit || script_pubkey.len() <= MAX_STANDARD_OP_RETURN_SIZE,
"OP_RETURN with metadata larger than maximum: {} > {}",
script_pubkey.len(),
MAX_STANDARD_OP_RETURN_SIZE,
);

let burn_amount = Amount::from_sat(1);

(
Self::create_unsigned_burn_satpoint_transaction(
&wallet,
inscription_info.satpoint,
self.fee_rate,
script_pubkey,
burn_amount,
)?,
burn_amount,
)
}
Outgoing::Rune { decimal, rune } => {
ensure!(
self.cbor_metadata.is_none() && self.json_metadata.is_none(),
"metadata not supported when burning runes"
);

(
wallet.create_unsigned_send_or_burn_runes_transaction(
None,
rune,
decimal,
None,
self.fee_rate,
)?,
Amount::ZERO,
)
}
Outgoing::Amount(_) => bail!("burning bitcoin not supported"),
Outgoing::Sat(_) => bail!("burning sat not supported"),
Outgoing::SatPoint(_) => bail!("burning satpoint not supported"),
};

let base_size = unsigned_transaction.base_size();

assert!(
base_size >= 65,
"transaction base size less than minimum standard tx nonwitness size: {base_size} < 65",
Expand All @@ -104,12 +132,12 @@ impl Burn {
Ok(Some(Box::new(send::Output {
txid,
psbt,
asset: Outgoing::InscriptionId(self.inscription),
asset: self.asset,
fee,
})))
}

fn create_unsigned_burn_transaction(
fn create_unsigned_burn_satpoint_transaction(
wallet: &Wallet,
satpoint: SatPoint,
fee_rate: FeeRate,
Expand Down
Loading

0 comments on commit 909a4c5

Please sign in to comment.