Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
30 changes: 29 additions & 1 deletion src/commands/tx.rs
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,20 @@ fn run_register_collection(
passphrase: Option<String>,
) -> Result<()> {
let wallet = load_wallet(ctx)?;
let mut lock_outpoints = Vec::new();
let db_path = ctx.data_dir.join("brc721.sqlite");
if db_path.exists() {
let storage = crate::storage::SqliteStorage::new(&db_path);
let wallet_utxos = wallet.list_unspent(0).context("list wallet UTXOs")?;
lock_outpoints = compute_wallet_token_outpoints_to_lock(&storage, &wallet_utxos, &[])
.context("compute lock set")?;
} else {
log::warn!(
"scanner database not found at {} (proceeding without ownership UTXO locks)",
db_path.to_string_lossy()
);
}

let msg = RegisterCollectionData {
evm_collection_address,
rebaseable,
Expand All @@ -91,7 +105,7 @@ fn run_register_collection(

let passphrase = resolve_passphrase(passphrase)?;
let tx = wallet
.build_tx(output, fee_rate, passphrase)
.build_tx(output, fee_rate, &lock_outpoints, passphrase)
.context("build tx")?;
let txid = wallet.broadcast(&tx)?;

Expand All @@ -112,6 +126,19 @@ fn run_register_ownership(
passphrase: Option<String>,
) -> Result<()> {
let mut wallet = load_wallet(ctx)?;
let mut lock_outpoints = Vec::new();
let db_path = ctx.data_dir.join("brc721.sqlite");
if db_path.exists() {
let storage = crate::storage::SqliteStorage::new(&db_path);
let wallet_utxos = wallet.list_unspent(0).context("list wallet UTXOs")?;
lock_outpoints = compute_wallet_token_outpoints_to_lock(&storage, &wallet_utxos, &[])
.context("compute lock set")?;
} else {
log::warn!(
"scanner database not found at {} (proceeding without ownership UTXO locks)",
db_path.to_string_lossy()
);
}

// Output 1 is the ownership UTXO tracked by the indexer for this registration.
// Use a new wallet-derived address so the NFTs are spendable by this wallet.
Expand All @@ -138,6 +165,7 @@ fn run_register_ownership(
output,
vec![(ownership_address.clone(), ownership_amount)],
fee_rate,
&lock_outpoints,
passphrase,
)
.context("build tx")?;
Expand Down
4 changes: 3 additions & 1 deletion src/integration_tests/txs.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ use crate::{
};
use age::secrecy::SecretString;
use bdk_wallet::bip39::{Language, Mnemonic};
use bitcoin::{Amount, Network};
use bitcoin::{Amount, Network, OutPoint};
use bitcoincore_rpc::{Auth, Client, RpcApi};
use corepc_node::Node;
use ethereum_types::H160;
Expand Down Expand Up @@ -48,10 +48,12 @@ fn test_build_tx_creates_signed_tx_with_custom_output() {
output.script_pubkey.to_string(),
"OP_RETURN OP_PUSHNUM_15 OP_PUSHBYTES_22 00000000000000000000000000000000000000000000"
);
let lock_outpoints: Vec<OutPoint> = Vec::new();
let tx = wallet
.build_tx(
output,
Some(1.5),
&lock_outpoints,
SecretString::from("passphrase".to_string()),
)
.expect("build tx");
Expand Down
48 changes: 41 additions & 7 deletions src/wallet/brc721_wallet.rs
Original file line number Diff line number Diff line change
Expand Up @@ -116,12 +116,28 @@ impl Brc721Wallet {
&self,
output: bitcoin::TxOut,
fee_rate: Option<f64>,
lock_outpoints: &[OutPoint],
passphrase: SecretString,
) -> Result<bitcoin::Transaction> {
let psbt = self
.remote
.create_psbt_from_txout(output, fee_rate)
.context("create psbt from outputs")?;
let locked = self.remote.list_locked_unspent()?;
let to_lock = lock_outpoints
.iter()
.filter(|outpoint| !locked.contains(outpoint))
.cloned()
.collect::<Vec<_>>();

self.remote
.lock_unspent_outpoints(&to_lock)
.context("lock token outpoints")?;

let psbt_res = self.remote.create_psbt_from_txout(output, fee_rate);

let unlock_res = self.remote.unlock_unspent_outpoints(&to_lock);
if let Err(unlock_err) = unlock_res {
log::warn!("Failed to unlock outpoints: {unlock_err:#}");
}

let psbt = psbt_res.context("create psbt from outputs")?;

self.sign(psbt, &passphrase)
}
Expand All @@ -131,12 +147,30 @@ impl Brc721Wallet {
op_return: bitcoin::TxOut,
payments: Vec<(Address, Amount)>,
fee_rate: Option<f64>,
lock_outpoints: &[OutPoint],
passphrase: SecretString,
) -> Result<bitcoin::Transaction> {
let psbt = self
let locked = self.remote.list_locked_unspent()?;
let to_lock = lock_outpoints
.iter()
.filter(|outpoint| !locked.contains(outpoint))
.cloned()
.collect::<Vec<_>>();

self.remote
.lock_unspent_outpoints(&to_lock)
.context("lock token outpoints")?;

let psbt_res = self
.remote
.create_psbt_from_opreturn_and_payments(op_return, payments, fee_rate)
.context("create psbt from op_return + payments")?;
.create_psbt_from_opreturn_and_payments(op_return, payments, fee_rate);

let unlock_res = self.remote.unlock_unspent_outpoints(&to_lock);
if let Err(unlock_err) = unlock_res {
log::warn!("Failed to unlock outpoints: {unlock_err:#}");
}

let psbt = psbt_res.context("create psbt from op_return + payments")?;

self.sign(psbt, &passphrase)
}
Expand Down