use {
super::*,
bitcoin::{
blockdata::{opcodes, script},
key::PrivateKey,
key::{TapTweak, TweakedKeypair, TweakedPublicKey, UntweakedKeypair},
secp256k1::{self, Secp256k1, XOnlyPublicKey, constants::SCHNORR_SIGNATURE_SIZE, rand},
sighash::{Prevouts, SighashCache, TapSighashType},
taproot::Signature,
taproot::{ControlBlock, LeafVersion, TapLeafHash, TaprootBuilder},
},
bitcoincore_rpc::bitcoincore_rpc_json::{ImportDescriptors, SignRawTransactionInput, Timestamp},
wallet::transaction_builder::Target,
};
pub(crate) use transactions::Transactions;
pub use {
entry::Entry, etching::Etching, file::File, mode::Mode, plan::Plan, range::Range, terms::Terms,
};
pub mod entry;
mod etching;
pub mod file;
pub mod mode;
pub mod plan;
mod range;
mod terms;
mod transactions;
#[derive(Debug, Serialize, Deserialize, Clone, PartialEq)]
pub struct Output {
pub commit: Txid,
pub commit_psbt: Option<String>,
pub inscriptions: Vec<InscriptionInfo>,
pub parents: Vec<InscriptionId>,
pub reveal: Txid,
pub reveal_broadcast: bool,
pub reveal_psbt: Option<String>,
pub rune: Option<RuneInfo>,
pub total_fees: u64,
}
#[derive(Serialize, Deserialize, Debug, PartialEq, Clone)]
pub struct InscriptionInfo {
pub destination: Address<NetworkUnchecked>,
pub id: InscriptionId,
pub location: SatPoint,
}
#[derive(Serialize, Deserialize, Debug, PartialEq, Clone)]
pub struct RuneInfo {
pub destination: Option<Address<NetworkUnchecked>>,
pub location: Option<OutPoint>,
pub rune: SpacedRune,
}
#[derive(Clone, Debug)]
pub struct ParentInfo {
pub destination: Address,
pub id: InscriptionId,
pub location: SatPoint,
pub tx_out: TxOut,
}
#[cfg(test)]
mod tests {
use {
super::*,
crate::wallet::batch::{self, ParentInfo},
bitcoin::policy::MAX_STANDARD_TX_WEIGHT,
};
#[test]
fn reveal_transaction_pays_fee() {
let utxos = vec![(outpoint(1), tx_out(20000, address(0)))];
let inscription = inscription("text/plain", "ord");
let commit_address = change(0);
let reveal_address = recipient_address();
let reveal_change = [commit_address, change(1)];
let batch::Transactions {
commit_tx,
reveal_tx,
..
} = batch::Plan {
satpoint: Some(satpoint(1, 0)),
parent_info: Vec::new(),
inscriptions: vec![inscription],
destinations: vec![reveal_address],
commit_fee_rate: FeeRate::try_from(1.0).unwrap(),
reveal_fee_rate: FeeRate::try_from(1.0).unwrap(),
no_limit: false,
reinscribe: false,
postages: vec![TARGET_POSTAGE],
mode: batch::Mode::SharedOutput,
..default()
}
.create_batch_transactions(
BTreeMap::new(),
Chain::Mainnet,
BTreeSet::new(),
BTreeSet::new(),
utxos.into_iter().collect(),
reveal_change,
change(2),
)
.unwrap();
#[allow(clippy::cast_possible_truncation)]
#[allow(clippy::cast_sign_loss)]
let fee = Amount::from_sat((1.0 * (reveal_tx.vsize() as f64)).ceil() as u64);
assert_eq!(
reveal_tx.output[0].value.to_sat(),
20000 - fee.to_sat() - (20000 - commit_tx.output[0].value.to_sat()),
);
}
#[test]
fn inscribe_transactions_opt_in_to_rbf() {
let utxos = vec![(outpoint(1), tx_out(20000, address(0)))];
let inscription = inscription("text/plain", "ord");
let commit_address = change(0);
let reveal_address = recipient_address();
let reveal_change = [commit_address, change(1)];
let batch::Transactions {
commit_tx,
reveal_tx,
..
} = batch::Plan {
satpoint: Some(satpoint(1, 0)),
parent_info: Vec::new(),
inscriptions: vec![inscription],
destinations: vec![reveal_address],
commit_fee_rate: FeeRate::try_from(1.0).unwrap(),
reveal_fee_rate: FeeRate::try_from(1.0).unwrap(),
no_limit: false,
reinscribe: false,
postages: vec![TARGET_POSTAGE],
mode: batch::Mode::SharedOutput,
..default()
}
.create_batch_transactions(
BTreeMap::new(),
Chain::Mainnet,
BTreeSet::new(),
BTreeSet::new(),
utxos.into_iter().collect(),
reveal_change,
change(2),
)
.unwrap();
assert!(commit_tx.is_explicitly_rbf());
assert!(reveal_tx.is_explicitly_rbf());
}
#[test]
fn inscribe_with_no_satpoint_and_no_cardinal_utxos() {
let utxos = vec![(outpoint(1), tx_out(1000, address(0)))];
let mut inscriptions = BTreeMap::new();
inscriptions.insert(
SatPoint {
outpoint: outpoint(1),
offset: 0,
},
vec![inscription_id(1)],
);
let inscription = inscription("text/plain", "ord");
let satpoint = None;
let commit_address = change(0);
let reveal_address = recipient_address();
let error = batch::Plan {
satpoint,
parent_info: Vec::new(),
inscriptions: vec![inscription],
destinations: vec![reveal_address],
commit_fee_rate: FeeRate::try_from(1.0).unwrap(),
reveal_fee_rate: FeeRate::try_from(1.0).unwrap(),
no_limit: false,
reinscribe: false,
postages: vec![TARGET_POSTAGE],
mode: batch::Mode::SharedOutput,
..default()
}
.create_batch_transactions(
inscriptions,
Chain::Mainnet,
BTreeSet::new(),
BTreeSet::new(),
utxos.into_iter().collect(),
[commit_address, change(1)],
change(2),
)
.unwrap_err()
.to_string();
assert!(
error.contains("wallet contains no cardinal utxos"),
"{}",
error
);
}
#[test]
fn inscribe_with_no_satpoint_and_enough_cardinal_utxos() {
let utxos = vec![
(outpoint(1), tx_out(20_000, address(0))),
(outpoint(2), tx_out(20_000, address(0))),
];
let mut inscriptions = BTreeMap::new();
inscriptions.insert(
SatPoint {
outpoint: outpoint(1),
offset: 0,
},
vec![inscription_id(1)],
);
let inscription = inscription("text/plain", "ord");
let satpoint = None;
let commit_address = change(0);
let reveal_address = recipient_address();
assert!(
batch::Plan {
satpoint,
parent_info: Vec::new(),
inscriptions: vec![inscription],
destinations: vec![reveal_address],
commit_fee_rate: FeeRate::try_from(1.0).unwrap(),
reveal_fee_rate: FeeRate::try_from(1.0).unwrap(),
no_limit: false,
reinscribe: false,
postages: vec![TARGET_POSTAGE],
mode: batch::Mode::SharedOutput,
..default()
}
.create_batch_transactions(
inscriptions,
Chain::Mainnet,
BTreeSet::new(),
BTreeSet::new(),
utxos.into_iter().collect(),
[commit_address, change(1)],
change(2),
)
.is_ok()
)
}
#[test]
fn inscribe_with_custom_fee_rate() {
let utxos = vec![
(outpoint(1), tx_out(10_000, address(0))),
(outpoint(2), tx_out(20_000, address(0))),
];
let mut inscriptions = BTreeMap::new();
inscriptions.insert(
SatPoint {
outpoint: outpoint(1),
offset: 0,
},
vec![inscription_id(1)],
);
let inscription = inscription("text/plain", "ord");
let satpoint = None;
let commit_address = change(0);
let reveal_address = recipient_address();
let fee_rate = 3.3;
let batch::Transactions {
commit_tx,
reveal_tx,
..
} = batch::Plan {
satpoint,
parent_info: Vec::new(),
inscriptions: vec![inscription],
destinations: vec![reveal_address],
commit_fee_rate: FeeRate::try_from(fee_rate).unwrap(),
reveal_fee_rate: FeeRate::try_from(fee_rate).unwrap(),
no_limit: false,
reinscribe: false,
postages: vec![TARGET_POSTAGE],
mode: batch::Mode::SharedOutput,
..default()
}
.create_batch_transactions(
inscriptions,
Chain::Signet,
BTreeSet::new(),
BTreeSet::new(),
utxos.into_iter().collect(),
[commit_address, change(1)],
change(2),
)
.unwrap();
let sig_vbytes = 17;
let fee = FeeRate::try_from(fee_rate)
.unwrap()
.fee(commit_tx.vsize() + sig_vbytes)
.to_sat();
let reveal_value = commit_tx
.output
.iter()
.map(|o| o.value)
.reduce(|acc, i| acc + i)
.unwrap();
assert_eq!(reveal_value.to_sat(), 20_000 - fee);
let fee = FeeRate::try_from(fee_rate)
.unwrap()
.fee(reveal_tx.vsize())
.to_sat();
assert_eq!(
reveal_tx.output[0].value.to_sat(),
20_000 - fee - (20_000 - commit_tx.output[0].value.to_sat()),
);
}
#[test]
fn inscribe_with_parent() {
let utxos = vec![
(outpoint(1), tx_out(10_000, address(0))),
(outpoint(2), tx_out(20_000, address(0))),
];
let mut inscriptions = BTreeMap::new();
let parent_inscription = inscription_id(1);
let parent_info = ParentInfo {
destination: change(3),
id: parent_inscription,
location: SatPoint {
outpoint: outpoint(1),
offset: 0,
},
tx_out: TxOut {
script_pubkey: change(0).script_pubkey(),
value: Amount::from_sat(10000),
},
};
inscriptions.insert(parent_info.location, vec![parent_inscription]);
let child_inscription = InscriptionTemplate {
parents: vec![parent_inscription],
..default()
}
.into();
let commit_address = change(1);
let reveal_address = recipient_address();
let fee_rate = 4.0;
let batch::Transactions {
commit_tx,
reveal_tx,
..
} = batch::Plan {
satpoint: None,
parent_info: vec![parent_info.clone()],
inscriptions: vec![child_inscription],
destinations: vec![reveal_address],
commit_fee_rate: FeeRate::try_from(fee_rate).unwrap(),
reveal_fee_rate: FeeRate::try_from(fee_rate).unwrap(),
no_limit: false,
reinscribe: false,
postages: vec![TARGET_POSTAGE],
mode: batch::Mode::SharedOutput,
..default()
}
.create_batch_transactions(
inscriptions,
Chain::Signet,
BTreeSet::new(),
BTreeSet::new(),
utxos.into_iter().collect(),
[commit_address, change(2)],
change(1),
)
.unwrap();
let sig_vbytes = 17;
let fee = FeeRate::try_from(fee_rate)
.unwrap()
.fee(commit_tx.vsize() + sig_vbytes)
.to_sat();
let reveal_value = commit_tx
.output
.iter()
.map(|o| o.value)
.reduce(|acc, i| acc + i)
.unwrap();
assert_eq!(reveal_value.to_sat(), 20_000 - fee);
let sig_vbytes = 16;
let fee = FeeRate::try_from(fee_rate)
.unwrap()
.fee(reveal_tx.vsize() + sig_vbytes);
assert_eq!(fee, commit_tx.output[0].value - reveal_tx.output[1].value);
assert_eq!(
reveal_tx.output[0].script_pubkey,
parent_info.destination.script_pubkey()
);
assert_eq!(reveal_tx.output[0].value, parent_info.tx_out.value);
pretty_assert_eq!(
reveal_tx.input[0],
TxIn {
previous_output: parent_info.location.outpoint,
sequence: Sequence::ENABLE_RBF_NO_LOCKTIME,
..default()
}
);
}
#[test]
fn inscribe_with_commit_fee_rate() {
let utxos = vec![
(outpoint(1), tx_out(10_000, address(0))),
(outpoint(2), tx_out(20_000, address(0))),
];
let mut inscriptions = BTreeMap::new();
inscriptions.insert(
SatPoint {
outpoint: outpoint(1),
offset: 0,
},
vec![inscription_id(1)],
);
let inscription = inscription("text/plain", "ord");
let satpoint = None;
let commit_address = change(0);
let reveal_address = recipient_address();
let commit_fee_rate = 3.3;
let fee_rate = 1.0;
let batch::Transactions {
commit_tx,
reveal_tx,
..
} = batch::Plan {
satpoint,
parent_info: Vec::new(),
inscriptions: vec![inscription],
destinations: vec![reveal_address],
commit_fee_rate: FeeRate::try_from(commit_fee_rate).unwrap(),
reveal_fee_rate: FeeRate::try_from(fee_rate).unwrap(),
no_limit: false,
reinscribe: false,
postages: vec![TARGET_POSTAGE],
mode: batch::Mode::SharedOutput,
..default()
}
.create_batch_transactions(
inscriptions,
Chain::Signet,
BTreeSet::new(),
BTreeSet::new(),
utxos.into_iter().collect(),
[commit_address, change(1)],
change(2),
)
.unwrap();
let sig_vbytes = 17;
let fee = FeeRate::try_from(commit_fee_rate)
.unwrap()
.fee(commit_tx.vsize() + sig_vbytes)
.to_sat();
let reveal_value = commit_tx
.output
.iter()
.map(|o| o.value)
.reduce(|acc, i| acc + i)
.unwrap();
assert_eq!(reveal_value.to_sat(), 20_000 - fee);
let fee = FeeRate::try_from(fee_rate)
.unwrap()
.fee(reveal_tx.vsize())
.to_sat();
assert_eq!(
reveal_tx.output[0].value.to_sat(),
20_000 - fee - (20_000 - commit_tx.output[0].value.to_sat()),
);
}
#[test]
fn inscribe_over_max_standard_tx_weight() {
let utxos = vec![(outpoint(1), tx_out(50 * COIN_VALUE, address(0)))];
let inscription = inscription("text/plain", [0; MAX_STANDARD_TX_WEIGHT as usize]);
let satpoint = None;
let commit_address = change(0);
let reveal_address = recipient_address();
let error = batch::Plan {
satpoint,
parent_info: Vec::new(),
inscriptions: vec![inscription],
destinations: vec![reveal_address],
commit_fee_rate: FeeRate::try_from(1.0).unwrap(),
reveal_fee_rate: FeeRate::try_from(1.0).unwrap(),
no_limit: false,
reinscribe: false,
postages: vec![TARGET_POSTAGE],
mode: batch::Mode::SharedOutput,
..default()
}
.create_batch_transactions(
BTreeMap::new(),
Chain::Mainnet,
BTreeSet::new(),
BTreeSet::new(),
utxos.into_iter().collect(),
[commit_address, change(1)],
change(2),
)
.unwrap_err()
.to_string();
assert!(
error.contains(&format!("reveal transaction weight greater than {MAX_STANDARD_TX_WEIGHT} (MAX_STANDARD_TX_WEIGHT): 402799")),
"{}",
error
);
}
#[test]
fn inscribe_with_no_max_standard_tx_weight() {
let utxos = vec![(outpoint(1), tx_out(50 * COIN_VALUE, address(0)))];
let inscription = inscription("text/plain", [0; MAX_STANDARD_TX_WEIGHT as usize]);
let satpoint = None;
let commit_address = change(0);
let reveal_address = recipient_address();
let batch::Transactions { reveal_tx, .. } = batch::Plan {
satpoint,
parent_info: Vec::new(),
inscriptions: vec![inscription],
destinations: vec![reveal_address],
commit_fee_rate: FeeRate::try_from(1.0).unwrap(),
reveal_fee_rate: FeeRate::try_from(1.0).unwrap(),
no_limit: true,
reinscribe: false,
postages: vec![TARGET_POSTAGE],
mode: batch::Mode::SharedOutput,
..default()
}
.create_batch_transactions(
BTreeMap::new(),
Chain::Mainnet,
BTreeSet::new(),
BTreeSet::new(),
utxos.into_iter().collect(),
[commit_address, change(1)],
change(2),
)
.unwrap();
assert!(reveal_tx.total_size() >= MAX_STANDARD_TX_WEIGHT as usize);
}
#[test]
fn batch_inscribe_with_parent() {
let utxos = vec![
(outpoint(1), tx_out(10_000, address(0))),
(outpoint(2), tx_out(50_000, address(0))),
];
let parent = inscription_id(1);
let parent_info = ParentInfo {
destination: change(3),
id: parent,
location: SatPoint {
outpoint: outpoint(1),
offset: 0,
},
tx_out: TxOut {
script_pubkey: change(0).script_pubkey(),
value: Amount::from_sat(10000),
},
};
let mut wallet_inscriptions = BTreeMap::new();
wallet_inscriptions.insert(parent_info.location, vec![parent]);
let commit_address = change(1);
let reveal_addresses = vec![recipient_address()];
let inscriptions = vec![
InscriptionTemplate {
parents: vec![parent],
..default()
}
.into(),
InscriptionTemplate {
parents: vec![parent],
..default()
}
.into(),
InscriptionTemplate {
parents: vec![parent],
..default()
}
.into(),
];
let mode = batch::Mode::SharedOutput;
let fee_rate = 4.0.try_into().unwrap();
let batch::Transactions {
commit_tx,
reveal_tx,
..
} = batch::Plan {
satpoint: None,
parent_info: vec![parent_info.clone()],
inscriptions,
destinations: reveal_addresses,
commit_fee_rate: fee_rate,
reveal_fee_rate: fee_rate,
no_limit: false,
reinscribe: false,
postages: vec![Amount::from_sat(10_000); 3],
mode,
..default()
}
.create_batch_transactions(
wallet_inscriptions,
Chain::Signet,
BTreeSet::new(),
BTreeSet::new(),
utxos.into_iter().collect(),
[commit_address, change(2)],
change(2),
)
.unwrap();
let sig_vbytes = 17;
let fee = fee_rate.fee(commit_tx.vsize() + sig_vbytes).to_sat();
let reveal_value = commit_tx
.output
.iter()
.map(|o| o.value)
.reduce(|acc, i| acc + i)
.unwrap();
assert_eq!(reveal_value.to_sat(), 50_000 - fee);
let sig_vbytes = 16;
let fee = fee_rate.fee(reveal_tx.vsize() + sig_vbytes);
assert_eq!(fee, commit_tx.output[0].value - reveal_tx.output[1].value);
assert_eq!(
reveal_tx.output[0].script_pubkey,
parent_info.destination.script_pubkey()
);
assert_eq!(reveal_tx.output[0].value, parent_info.tx_out.value);
pretty_assert_eq!(
reveal_tx.input[0],
TxIn {
previous_output: parent_info.location.outpoint,
sequence: Sequence::ENABLE_RBF_NO_LOCKTIME,
..default()
}
);
}
#[test]
fn batch_inscribe_satpoints_with_parent() {
let utxos = vec![
(outpoint(1), tx_out(1_111, address(0))),
(outpoint(2), tx_out(2_222, address(0))),
(outpoint(3), tx_out(3_333, address(0))),
(outpoint(4), tx_out(10_000, address(0))),
(outpoint(5), tx_out(50_000, address(0))),
(outpoint(6), tx_out(60_000, address(0))),
];
let parent = inscription_id(1);
let parent_info = ParentInfo {
destination: change(3),
id: parent,
location: SatPoint {
outpoint: outpoint(4),
offset: 0,
},
tx_out: TxOut {
script_pubkey: change(0).script_pubkey(),
value: Amount::from_sat(10_000),
},
};
let mut wallet_inscriptions = BTreeMap::new();
wallet_inscriptions.insert(parent_info.location, vec![parent]);
let commit_address = change(1);
let reveal_addresses = vec![
recipient_address(),
recipient_address(),
recipient_address(),
];
let inscriptions = vec![
InscriptionTemplate {
parents: vec![parent],
pointer: Some(10_000),
}
.into(),
InscriptionTemplate {
parents: vec![parent],
pointer: Some(11_111),
}
.into(),
InscriptionTemplate {
parents: vec![parent],
pointer: Some(13_3333),
}
.into(),
];
let reveal_satpoints = utxos
.iter()
.take(3)
.map(|(outpoint, txout)| {
(
SatPoint {
outpoint: *outpoint,
offset: 0,
},
txout.clone(),
)
})
.collect::<Vec<(SatPoint, TxOut)>>();
let mode = batch::Mode::SatPoints;
let fee_rate = 1.0.try_into().unwrap();
let batch::Transactions {
commit_tx,
reveal_tx,
..
} = batch::Plan {
reveal_satpoints: reveal_satpoints.clone(),
parent_info: vec![parent_info.clone()],
inscriptions,
destinations: reveal_addresses,
commit_fee_rate: fee_rate,
reveal_fee_rate: fee_rate,
postages: vec![
Amount::from_sat(1_111),
Amount::from_sat(2_222),
Amount::from_sat(3_333),
],
mode,
..default()
}
.create_batch_transactions(
wallet_inscriptions,
Chain::Signet,
reveal_satpoints
.iter()
.map(|(satpoint, _)| satpoint.outpoint)
.collect(),
BTreeSet::new(),
utxos.into_iter().collect(),
[commit_address, change(2)],
change(3),
)
.unwrap();
let sig_vbytes = 17;
let fee = fee_rate.fee(commit_tx.vsize() + sig_vbytes).to_sat();
let reveal_value = commit_tx
.output
.iter()
.map(|o| o.value)
.reduce(|acc, i| acc + i)
.unwrap();
assert_eq!(reveal_value.to_sat(), 50_000 - fee);
assert_eq!(
reveal_tx.output[0].script_pubkey,
parent_info.destination.script_pubkey()
);
assert_eq!(reveal_tx.output[0].value, parent_info.tx_out.value);
pretty_assert_eq!(
reveal_tx.input[0],
TxIn {
previous_output: parent_info.location.outpoint,
sequence: Sequence::ENABLE_RBF_NO_LOCKTIME,
..default()
}
);
}
#[test]
fn batch_inscribe_with_parent_not_enough_cardinals_utxos_fails() {
let utxos = vec![
(outpoint(1), tx_out(10_000, address(0))),
(outpoint(2), tx_out(20_000, address(0))),
];
let parent = inscription_id(1);
let parent_info = ParentInfo {
destination: change(3),
id: parent,
location: SatPoint {
outpoint: outpoint(1),
offset: 0,
},
tx_out: TxOut {
script_pubkey: change(0).script_pubkey(),
value: Amount::from_sat(10000),
},
};
let mut wallet_inscriptions = BTreeMap::new();
wallet_inscriptions.insert(parent_info.location, vec![parent]);
let inscriptions = vec![
InscriptionTemplate {
parents: vec![parent],
..default()
}
.into(),
InscriptionTemplate {
parents: vec![parent],
..default()
}
.into(),
InscriptionTemplate {
parents: vec![parent],
..default()
}
.into(),
];
let commit_address = change(1);
let reveal_addresses = vec![recipient_address()];
let error = batch::Plan {
satpoint: None,
parent_info: vec![parent_info.clone()],
inscriptions,
destinations: reveal_addresses,
commit_fee_rate: 4.0.try_into().unwrap(),
reveal_fee_rate: 4.0.try_into().unwrap(),
no_limit: false,
reinscribe: false,
postages: vec![Amount::from_sat(10_000); 3],
mode: batch::Mode::SharedOutput,
..default()
}
.create_batch_transactions(
wallet_inscriptions,
Chain::Signet,
BTreeSet::new(),
BTreeSet::new(),
utxos.into_iter().collect(),
[commit_address, change(2)],
change(3),
)
.unwrap_err()
.to_string();
assert!(error.contains(
"wallet does not contain enough cardinal UTXOs, please add additional funds to wallet."
));
}
#[test]
#[should_panic(expected = "invariant: shared-output has only one destination")]
fn batch_inscribe_with_inconsistent_reveal_addresses_panics() {
let utxos = vec![
(outpoint(1), tx_out(10_000, address(0))),
(outpoint(2), tx_out(80_000, address(0))),
];
let parent = inscription_id(1);
let parent_info = ParentInfo {
destination: change(3),
id: parent,
location: SatPoint {
outpoint: outpoint(1),
offset: 0,
},
tx_out: TxOut {
script_pubkey: change(0).script_pubkey(),
value: Amount::from_sat(10000),
},
};
let mut wallet_inscriptions = BTreeMap::new();
wallet_inscriptions.insert(parent_info.location, vec![parent]);
let inscriptions = vec![
InscriptionTemplate {
parents: vec![parent],
..default()
}
.into(),
InscriptionTemplate {
parents: vec![parent],
..default()
}
.into(),
InscriptionTemplate {
parents: vec![parent],
..default()
}
.into(),
];
let commit_address = change(1);
let reveal_addresses = vec![recipient_address(), recipient_address()];
let _ = batch::Plan {
satpoint: None,
parent_info: vec![parent_info.clone()],
inscriptions,
destinations: reveal_addresses,
commit_fee_rate: 4.0.try_into().unwrap(),
reveal_fee_rate: 4.0.try_into().unwrap(),
no_limit: false,
reinscribe: false,
postages: vec![Amount::from_sat(10_000)],
mode: batch::Mode::SharedOutput,
..default()
}
.create_batch_transactions(
wallet_inscriptions,
Chain::Signet,
BTreeSet::new(),
BTreeSet::new(),
utxos.into_iter().collect(),
[commit_address, change(2)],
change(3),
);
}
#[test]
fn batch_inscribe_over_max_standard_tx_weight() {
let utxos = vec![(outpoint(1), tx_out(50 * COIN_VALUE, address(0)))];
let wallet_inscriptions = BTreeMap::new();
let inscriptions = vec![
inscription("text/plain", [0; MAX_STANDARD_TX_WEIGHT as usize / 3]),
inscription("text/plain", [0; MAX_STANDARD_TX_WEIGHT as usize / 3]),
inscription("text/plain", [0; MAX_STANDARD_TX_WEIGHT as usize / 3]),
];
let commit_address = change(1);
let reveal_addresses = vec![recipient_address()];
let error = batch::Plan {
satpoint: None,
parent_info: Vec::new(),
inscriptions,
destinations: reveal_addresses,
commit_fee_rate: 1.0.try_into().unwrap(),
reveal_fee_rate: 1.0.try_into().unwrap(),
no_limit: false,
reinscribe: false,
postages: vec![Amount::from_sat(30_000); 3],
mode: batch::Mode::SharedOutput,
..default()
}
.create_batch_transactions(
wallet_inscriptions,
Chain::Signet,
BTreeSet::new(),
BTreeSet::new(),
utxos.into_iter().collect(),
[commit_address, change(2)],
change(3),
)
.unwrap_err()
.to_string();
assert!(
error.contains(&format!("reveal transaction weight greater than {MAX_STANDARD_TX_WEIGHT} (MAX_STANDARD_TX_WEIGHT): 402841")),
"{}",
error
);
}
#[test]
fn batch_inscribe_into_separate_outputs() {
let utxos = vec![
(outpoint(1), tx_out(10_000, address(0))),
(outpoint(2), tx_out(80_000, address(0))),
];
let wallet_inscriptions = BTreeMap::new();
let commit_address = change(1);
let reveal_addresses = vec![
recipient_address(),
recipient_address(),
recipient_address(),
];
let inscriptions = vec![
inscription("text/plain", [b'O'; 100]),
inscription("text/plain", [b'O'; 111]),
inscription("text/plain", [b'O'; 222]),
];
let mode = batch::Mode::SeparateOutputs;
let fee_rate = 4.0.try_into().unwrap();
let batch::Transactions { reveal_tx, .. } = batch::Plan {
satpoint: None,
parent_info: Vec::new(),
inscriptions,
destinations: reveal_addresses,
commit_fee_rate: fee_rate,
reveal_fee_rate: fee_rate,
no_limit: false,
reinscribe: false,
postages: vec![Amount::from_sat(10_000); 3],
mode,
..default()
}
.create_batch_transactions(
wallet_inscriptions,
Chain::Signet,
BTreeSet::new(),
BTreeSet::new(),
utxos.into_iter().collect(),
[commit_address, change(2)],
change(3),
)
.unwrap();
assert_eq!(reveal_tx.output.len(), 3);
assert!(
reveal_tx
.output
.iter()
.all(|output| output.value == TARGET_POSTAGE)
);
}
#[test]
fn batch_inscribe_into_separate_outputs_with_parent() {
let utxos = vec![
(outpoint(1), tx_out(10_000, address(0))),
(outpoint(2), tx_out(50_000, address(0))),
];
let parent = inscription_id(1);
let parent_info = ParentInfo {
destination: change(3),
id: parent,
location: SatPoint {
outpoint: outpoint(1),
offset: 0,
},
tx_out: TxOut {
script_pubkey: change(0).script_pubkey(),
value: Amount::from_sat(10000),
},
};
let mut wallet_inscriptions = BTreeMap::new();
wallet_inscriptions.insert(parent_info.location, vec![parent]);
let commit_address = change(1);
let reveal_addresses = vec![
recipient_address(),
recipient_address(),
recipient_address(),
];
let inscriptions = vec![
InscriptionTemplate {
parents: vec![parent],
..default()
}
.into(),
InscriptionTemplate {
parents: vec![parent],
..default()
}
.into(),
InscriptionTemplate {
parents: vec![parent],
..default()
}
.into(),
];
let mode = batch::Mode::SeparateOutputs;
let fee_rate = 4.0.try_into().unwrap();
let batch::Transactions {
commit_tx,
reveal_tx,
..
} = batch::Plan {
satpoint: None,
parent_info: vec![parent_info.clone()],
inscriptions,
destinations: reveal_addresses,
commit_fee_rate: fee_rate,
reveal_fee_rate: fee_rate,
no_limit: false,
reinscribe: false,
postages: vec![Amount::from_sat(10_000); 3],
mode,
..default()
}
.create_batch_transactions(
wallet_inscriptions,
Chain::Signet,
BTreeSet::new(),
BTreeSet::new(),
utxos.into_iter().collect(),
[commit_address, change(2)],
change(3),
)
.unwrap();
assert_eq!(
vec![parent],
ParsedEnvelope::from_transaction(&reveal_tx)[0]
.payload
.parents(),
);
assert_eq!(
vec![parent],
ParsedEnvelope::from_transaction(&reveal_tx)[1]
.payload
.parents(),
);
let sig_vbytes = 17;
let fee = fee_rate.fee(commit_tx.vsize() + sig_vbytes).to_sat();
let reveal_value = commit_tx
.output
.iter()
.map(|o| o.value)
.reduce(|acc, i| acc + i)
.unwrap();
assert_eq!(reveal_value.to_sat(), 50_000 - fee);
assert_eq!(
reveal_tx.output[0].script_pubkey,
parent_info.destination.script_pubkey()
);
assert_eq!(reveal_tx.output[0].value, parent_info.tx_out.value);
pretty_assert_eq!(
reveal_tx.input[0],
TxIn {
previous_output: parent_info.location.outpoint,
sequence: Sequence::ENABLE_RBF_NO_LOCKTIME,
..default()
}
);
}
}