Skip to content

Commit

Permalink
implement burn
Browse files Browse the repository at this point in the history
  • Loading branch information
onchainguy-btc committed Nov 30, 2023
1 parent 6e07786 commit cc1d397
Show file tree
Hide file tree
Showing 10 changed files with 468 additions and 233 deletions.
28 changes: 28 additions & 0 deletions src/subcommand/server.rs
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,10 @@ use {
Router, TypedHeader,
},
axum_server::Handle,
bitcoin::blockdata::script::Instruction::{
Op, PushBytes
},
bitcoin::blockdata::opcodes::all::OP_RETURN,
rust_embed::RustEmbed,
rustls_acme::{
acme::{LETS_ENCRYPT_PRODUCTION_DIRECTORY, LETS_ENCRYPT_STAGING_DIRECTORY},
Expand Down Expand Up @@ -1237,11 +1241,32 @@ impl Server {
Charm::Lost.set(&mut charms);
}

let mut is_burned = false;
let burn_payload = output.as_ref().and_then(|o| {
let mut instructions = o.script_pubkey.instructions();

// Check if the first instruction is OP_RETURN
if let Some(Ok(Op(OP_RETURN))) = instructions.next() {
is_burned = true;
// Extract the payload if it exists
instructions.filter_map(|instr| {
if let Ok(PushBytes(data)) = instr {
String::from_utf8(data.as_bytes().to_vec()).ok()
} else {
None
}
}).next()
} else {
None
}
});

Ok(if accept_json.0 {
Json(InscriptionJson {
inscription_id,
children,
inscription_number: entry.inscription_number,
is_burned: Some(is_burned),
genesis_height: entry.height,
parent,
genesis_fee: entry.fee,
Expand All @@ -1258,10 +1283,12 @@ impl Server {
previous,
next,
rune,
burn_payload
})
.into_response()
} else {
InscriptionHtml {
burn_payload,
chain: page_config.chain,
charms,
children,
Expand All @@ -1270,6 +1297,7 @@ impl Server {
inscription,
inscription_id,
inscription_number: entry.inscription_number,
is_burned: Some(is_burned),
next,
output,
parent,
Expand Down
2 changes: 1 addition & 1 deletion src/subcommand/wallet.rs
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ pub(crate) enum Wallet {
Restore(restore::Restore),
#[command(about = "List wallet satoshis")]
Sats(sats::Sats),
#[command(about = "Send sat or inscription")]
#[command(about = "Send sat or inscription (option to burn)")]
Send(send::Send),
#[command(about = "See wallet transactions")]
Transactions(transactions::Transactions),
Expand Down
59 changes: 44 additions & 15 deletions src/subcommand/wallet/send.rs
Original file line number Diff line number Diff line change
@@ -1,14 +1,32 @@
use {super::*, crate::subcommand::wallet::transaction_builder::Target, crate::wallet::Wallet};
use {
super::*,
crate::{
subcommand::wallet::transaction_builder::{OutputScript, Target},
wallet::Wallet,
},
};

#[derive(Debug, Parser, Clone)]
#[clap(
group = ArgGroup::new("output")
.required(true)
.args(&["recipient", "burn"]),
)]
pub(crate) struct Send {
address: Address<NetworkUnchecked>,
outgoing: Outgoing,
#[arg(long, conflicts_with = "burn", help = "Recipient address")]
recipient: Option<Address<NetworkUnchecked>>,
#[arg(
long,
conflicts_with = "recipient",
help = "Message to append when burning sats"
)]
burn: Option<String>,
#[arg(long, help = "Use fee rate of <FEE_RATE> sats/vB")]
fee_rate: FeeRate,
#[arg(
long,
help = "Target amount of postage to include with sent inscriptions. Default `10000sat`"
long,
help = "Target amount of postage to include with sent inscriptions. Default `10000sat`"
)]
pub(crate) postage: Option<Amount>,
}
Expand All @@ -20,10 +38,7 @@ pub struct Output {

impl Send {
pub(crate) fn run(self, options: Options) -> SubcommandResult {
let address = self
.address
.clone()
.require_network(options.chain().network())?;
let output = self.get_output(&options)?;

let index = Index::open(&options)?;
index.update()?;
Expand Down Expand Up @@ -61,11 +76,14 @@ impl Send {
Outgoing::InscriptionId(id) => index
.get_inscription_satpoint_by_id(id)?
.ok_or_else(|| anyhow!("Inscription {id} not found"))?,
Outgoing::Amount(amount) => {
Self::lock_inscriptions(&client, inscriptions, runic_outputs, unspent_outputs)?;
let txid = Self::send_amount(&client, amount, address, self.fee_rate.n())?;
return Ok(Box::new(Output { transaction: txid }));
}
Outgoing::Amount(amount) => match output {
OutputScript::OpReturn(_) => bail!("refusing to burn amount"),
OutputScript::PubKey(address) => {
Self::lock_inscriptions(&client, inscriptions, runic_outputs, unspent_outputs)?;
let txid = Self::send_amount(&client, amount, address, self.fee_rate.n())?;
return Ok(Box::new(Output { transaction: txid }));
}
},
};

let change = [
Expand All @@ -85,12 +103,12 @@ impl Send {
unspent_outputs,
locked_outputs,
runic_outputs,
address.clone(),
output,
change,
self.fee_rate,
postage,
)
.build_transaction()?;
.build_transaction()?;

let signed_tx = client
.sign_raw_transaction_with_wallet(&unsigned_transaction, None, None)?
Expand All @@ -101,6 +119,17 @@ impl Send {
Ok(Box::new(Output { transaction: txid }))
}

fn get_output(&self, options: &Options) -> Result<OutputScript, Error> {
if let Some(address) = &self.recipient {
let address = address.clone().require_network(options.chain().network())?;
Ok(OutputScript::PubKey(address))
} else if let Some(msg) = &self.burn {
Ok(OutputScript::OpReturn(Vec::from(msg.clone())))
} else {
bail!("no valid output given")
}
}

fn lock_inscriptions(
client: &Client,
inscriptions: BTreeMap<SatPoint, InscriptionId>,
Expand Down
Loading

0 comments on commit cc1d397

Please sign in to comment.