use super::*;
#[test]
fn inscriptions_can_be_sent() {
let core = mockcore::spawn();
let ord = TestServer::spawn_with_server_args(&core, &[], &[]);
create_wallet(&core, &ord);
core.mine_blocks(1);
let (inscription, _) = inscribe(&core, &ord);
core.mine_blocks(1);
let output = CommandBuilder::new(format!(
"wallet send --fee-rate 1 bc1qw508d6qejxtdg4y5r3zarvary0c5xw7kv8f3t4 {inscription}",
))
.core(&core)
.ord(&ord)
.stdout_regex(r".*")
.run_and_deserialize_output::<Send>();
let txid = core.mempool()[0].compute_txid();
assert_eq!(txid, output.txid);
core.mine_blocks(1);
let send_txid = output.txid;
ord.assert_response_regex(
format!("/inscription/{inscription}"),
format!(
".*<h1>Inscription 0</h1>.*<dl>.*
<dt>content length</dt>
<dd>3 bytes</dd>
<dt>content type</dt>
<dd>text/plain;charset=utf-8</dd>
.*
<dt>location</dt>
<dd><a class=collapse href=/satpoint/{send_txid}:0:0>{send_txid}:0:0</a></dd>
.*
</dl>
.*",
),
);
}
#[test]
fn send_unknown_inscription() {
let core = mockcore::spawn();
let ord = TestServer::spawn_with_server_args(&core, &[], &[]);
create_wallet(&core, &ord);
let txid = core.mine_blocks(1)[0].txdata[0].compute_txid();
CommandBuilder::new(format!(
"wallet send --fee-rate 1 bc1qcqgs2pps4u4yedfyl5pysdjjncs8et5utseepv {txid}i0"
))
.core(&core)
.ord(&ord)
.expected_stderr(format!("error: inscription {txid}i0 not found\n"))
.expected_exit_code(1)
.run_and_extract_stdout();
}
#[test]
fn send_inscribed_inscription() {
let core = mockcore::spawn();
let ord = TestServer::spawn_with_server_args(&core, &[], &[]);
create_wallet(&core, &ord);
core.mine_blocks(1);
let (inscription, _) = inscribe(&core, &ord);
core.mine_blocks(1);
let output = CommandBuilder::new(format!(
"wallet send --fee-rate 1 bc1qcqgs2pps4u4yedfyl5pysdjjncs8et5utseepv {inscription}",
))
.core(&core)
.ord(&ord)
.run_and_deserialize_output::<Send>();
core.mine_blocks(1);
let send_txid = output.txid;
ord.assert_response_regex(
format!("/inscription/{inscription}"),
format!(".*<h1>Inscription 0</h1>.*<dt>location</dt>.*{send_txid}:0:0</a></dd>.*",),
);
}
#[test]
fn send_uninscribed_sat() {
let core = mockcore::spawn();
let ord = TestServer::spawn_with_server_args(&core, &["--index-sats"], &[]);
create_wallet(&core, &ord);
let sat = Sat(1);
CommandBuilder::new(format!(
"wallet send --fee-rate 1 bc1qcqgs2pps4u4yedfyl5pysdjjncs8et5utseepv {}",
sat.name(),
))
.core(&core)
.ord(&ord)
.expected_stderr(format!(
"error: could not find sat `{sat}` in wallet outputs\n"
))
.expected_exit_code(1)
.run_and_extract_stdout();
}
#[test]
fn send_inscription_by_sat() {
let core = mockcore::spawn();
let ord = TestServer::spawn_with_server_args(&core, &["--index-sats"], &[]);
create_wallet(&core, &ord);
core.mine_blocks(1);
let (inscription, txid) = inscribe(&core, &ord);
core.mine_blocks(1);
let sat_list = sats(&core, &ord);
let sat = sat_list.iter().find(|s| s.output.txid == txid).unwrap().sat;
let address = "bc1qcqgs2pps4u4yedfyl5pysdjjncs8et5utseepv";
let output = CommandBuilder::new(format!("wallet send --fee-rate 1 {address} {}", sat.name()))
.core(&core)
.ord(&ord)
.run_and_deserialize_output::<Send>();
core.mine_blocks(1);
let send_txid = output.txid;
ord.assert_response_regex(
format!("/inscription/{inscription}"),
format!(
".*<h1>Inscription 0</h1>.*<dt>address</dt>.*<dd><a class=collapse href=/address/{address}>{address}</a></dd>.*<dt>location</dt>.*<dd><a class=collapse href=/satpoint/{send_txid}:0:0>{send_txid}:0:0</a></dd>.*",
),
);
}
#[test]
fn send_on_mainnnet_works_with_wallet_named_foo() {
let core = mockcore::spawn();
let ord = TestServer::spawn_with_server_args(&core, &[], &[]);
let txid = core.mine_blocks(1)[0].txdata[0].compute_txid();
CommandBuilder::new("wallet --name foo create")
.core(&core)
.ord(&ord)
.run_and_deserialize_output::<Create>();
CommandBuilder::new(format!(
"wallet --name foo send --fee-rate 1 bc1qw508d6qejxtdg4y5r3zarvary0c5xw7kv8f3t4 {txid}:0:0"
))
.core(&core)
.ord(&ord)
.run_and_deserialize_output::<Send>();
}
#[test]
fn send_addresses_must_be_valid_for_network() {
let core = mockcore::builder().build();
let ord = TestServer::spawn_with_server_args(&core, &[], &[]);
create_wallet(&core, &ord);
let txid = core.mine_blocks_with_subsidy(1, 1_000)[0].txdata[0].compute_txid();
CommandBuilder::new(format!(
"wallet send --fee-rate 1 tb1q6en7qjxgw4ev8xwx94pzdry6a6ky7wlfeqzunz {txid}:0:0"
))
.core(&core)
.ord(&ord)
.expected_stderr(
"error: validation error\n\nbecause:\n- address tb1q6en7qjxgw4ev8xwx94pzdry6a6ky7wlfeqzunz is not valid on bitcoin\n",
)
.expected_exit_code(1)
.run_and_extract_stdout();
}
#[test]
fn send_on_mainnnet_works_with_wallet_named_ord() {
let core = mockcore::builder().build();
let ord = TestServer::spawn_with_server_args(&core, &[], &[]);
create_wallet(&core, &ord);
let txid = core.mine_blocks_with_subsidy(1, 1_000_000)[0].txdata[0].compute_txid();
let output = CommandBuilder::new(format!(
"wallet send --fee-rate 1 bc1qw508d6qejxtdg4y5r3zarvary0c5xw7kv8f3t4 {txid}:0:0"
))
.core(&core)
.ord(&ord)
.run_and_deserialize_output::<Send>();
assert_eq!(core.mempool()[0].compute_txid(), output.txid);
}
#[test]
fn send_does_not_use_inscribed_sats_as_cardinal_utxos() {
let core = mockcore::spawn();
let ord = TestServer::spawn_with_server_args(&core, &[], &[]);
create_wallet(&core, &ord);
let txid = core.mine_blocks_with_subsidy(1, 10_000)[0].txdata[0].compute_txid();
CommandBuilder::new(format!(
"wallet inscribe --satpoint {txid}:0:0 --file degenerate.png --fee-rate 0"
))
.write("degenerate.png", [1; 100])
.core(&core)
.ord(&ord)
.run_and_deserialize_output::<Batch>();
let txid = core.mine_blocks_with_subsidy(1, 100)[0].txdata[0].compute_txid();
CommandBuilder::new(format!(
"wallet send --fee-rate 1 bc1qw508d6qejxtdg4y5r3zarvary0c5xw7kv8f3t4 {txid}:0:0"
))
.core(&core)
.ord(&ord)
.expected_exit_code(1)
.expected_stderr("error: wallet does not contain enough cardinal UTXOs, please add additional funds to wallet.\n")
.run_and_extract_stdout();
}
#[test]
fn do_not_send_within_dust_limit_of_an_inscription() {
let core = mockcore::spawn();
let ord = TestServer::spawn_with_server_args(&core, &[], &[]);
create_wallet(&core, &ord);
let (inscription, reveal) = inscribe(&core, &ord);
core.mine_blocks(1);
let output = OutPoint {
txid: reveal,
vout: 0,
};
CommandBuilder::new(format!(
"wallet send --fee-rate 1 bc1qw508d6qejxtdg4y5r3zarvary0c5xw7kv8f3t4 {output}:329"
))
.core(&core)
.ord(&ord)
.expected_exit_code(1)
.expected_stderr(format!(
"error: cannot send {output}:329 without also sending inscription {inscription} at {output}:0\n"
))
.run_and_extract_stdout();
}
#[test]
fn can_send_after_dust_limit_from_an_inscription() {
let core = mockcore::spawn();
let ord = TestServer::spawn_with_server_args(&core, &[], &[]);
create_wallet(&core, &ord);
let (_, reveal) = inscribe(&core, &ord);
core.mine_blocks(1);
let output = OutPoint {
txid: reveal,
vout: 0,
};
CommandBuilder::new(format!(
"wallet send --fee-rate 1 bc1qw508d6qejxtdg4y5r3zarvary0c5xw7kv8f3t4 {output}:330"
))
.core(&core)
.ord(&ord)
.run_and_deserialize_output::<Send>();
}
#[test]
fn splitting_merged_inscriptions_is_possible() {
let core = mockcore::spawn();
let ord = TestServer::spawn_with_server_args(&core, &["--index-sats"], &[]);
create_wallet(&core, &ord);
core.mine_blocks(1);
let inscribe = CommandBuilder::new("wallet batch --fee-rate 0 --batch batch.yaml")
.write("inscription.txt", "INSCRIPTION")
.write(
"batch.yaml",
"\
mode: shared-output
inscriptions:
- file: inscription.txt
- file: inscription.txt
- file: inscription.txt
",
)
.core(&core)
.ord(&ord)
.run_and_deserialize_output::<Batch>();
let reveal_txid = inscribe.reveal;
let destination = inscribe.inscriptions[0].destination.clone();
core.mine_blocks(1);
let response = ord.json_request(format!("/output/{reveal_txid}:0"));
assert_eq!(response.status(), StatusCode::OK);
let output_json: api::Output = serde_json::from_str(&response.text().unwrap()).unwrap();
pretty_assert_eq!(
output_json,
api::Output {
address: Some(destination.clone()),
confirmations: 1,
outpoint: OutPoint {
txid: reveal_txid,
vout: 0
},
inscriptions: Some(vec![
InscriptionId {
txid: reveal_txid,
index: 0
},
InscriptionId {
txid: reveal_txid,
index: 1
},
InscriptionId {
txid: reveal_txid,
index: 2
},
]),
indexed: true,
runes: None,
sat_ranges: Some(vec![(5_000_000_000, 5_000_030_000)]),
script_pubkey: destination.assume_checked_ref().script_pubkey(),
spent: false,
transaction: reveal_txid,
value: 30_000,
}
);
CommandBuilder::new(format!(
"wallet send --fee-rate 1 bc1qw508d6qejxtdg4y5r3zarvary0c5xw7kv8f3t4 {reveal_txid}i0"
))
.core(&core)
.ord(&ord)
.expected_exit_code(1)
.expected_stderr(format!(
"error: cannot send {reveal_txid}:0:0 without also sending inscription {reveal_txid}i2 at {reveal_txid}:0:20000\n",
))
.run_and_extract_stdout();
CommandBuilder::new(format!(
"wallet send --fee-rate 1 bc1qw508d6qejxtdg4y5r3zarvary0c5xw7kv8f3t4 {reveal_txid}i2"
))
.core(&core)
.ord(&ord)
.run_and_deserialize_output::<Send>();
core.mine_blocks(1);
CommandBuilder::new(format!(
"wallet send --fee-rate 1 bc1qw508d6qejxtdg4y5r3zarvary0c5xw7kv8f3t4 {reveal_txid}i1"
))
.core(&core)
.ord(&ord)
.run_and_deserialize_output::<Send>();
core.mine_blocks(1);
CommandBuilder::new(format!(
"wallet send --fee-rate 1 bc1qw508d6qejxtdg4y5r3zarvary0c5xw7kv8f3t4 {reveal_txid}i0"
))
.core(&core)
.ord(&ord)
.run_and_deserialize_output::<Send>();
}
#[test]
fn inscriptions_cannot_be_sent_by_satpoint() {
let core = mockcore::spawn();
let ord = TestServer::spawn_with_server_args(&core, &[], &[]);
create_wallet(&core, &ord);
let (_, reveal) = inscribe(&core, &ord);
core.mine_blocks(1);
CommandBuilder::new(format!(
"wallet send --fee-rate 1 bc1qw508d6qejxtdg4y5r3zarvary0c5xw7kv8f3t4 {reveal}:0:0"
))
.core(&core)
.ord(&ord)
.expected_stderr("error: inscriptions must be sent by inscription ID\n")
.expected_exit_code(1)
.run_and_extract_stdout();
}
#[test]
fn send_btc_with_fee_rate() {
let core = mockcore::spawn();
let ord = TestServer::spawn_with_server_args(&core, &[], &[]);
create_wallet(&core, &ord);
core.mine_blocks(1);
CommandBuilder::new(
"wallet send --fee-rate 13.3 bc1qw508d6qejxtdg4y5r3zarvary0c5xw7kv8f3t4 2btc",
)
.core(&core)
.ord(&ord)
.run_and_deserialize_output::<Send>();
let tx = &core.mempool()[0];
let mut fee = Amount::ZERO;
for input in &tx.input {
fee += core.get_utxo_amount(&input.previous_output).unwrap();
}
for output in &tx.output {
fee -= output.value;
}
let fee_rate = fee.to_sat() as f64 / tx.vsize() as f64;
assert!(f64::abs(fee_rate - 13.3) < 0.1);
assert_eq!(
Address::from_script(&tx.output[0].script_pubkey, Network::Bitcoin).unwrap(),
"bc1qw508d6qejxtdg4y5r3zarvary0c5xw7kv8f3t4"
.parse::<Address<NetworkUnchecked>>()
.unwrap()
.assume_checked()
);
assert_eq!(tx.output[0].value.to_sat(), 2 * COIN_VALUE);
}
#[test]
fn send_btc_locks_inscriptions() {
let core = mockcore::spawn();
let ord = TestServer::spawn_with_server_args(&core, &[], &[]);
create_wallet(&core, &ord);
core.mine_blocks(1);
let (_, reveal) = inscribe(&core, &ord);
CommandBuilder::new("wallet send --fee-rate 1 bc1qw508d6qejxtdg4y5r3zarvary0c5xw7kv8f3t4 1btc")
.core(&core)
.ord(&ord)
.run_and_deserialize_output::<Send>();
assert!(core.get_locked().contains(&OutPoint {
txid: reveal,
vout: 0,
}))
}
#[test]
fn send_btc_fails_if_lock_unspent_fails() {
let core = mockcore::builder().fail_lock_unspent(true).build();
let ord = TestServer::spawn_with_server_args(&core, &[], &[]);
create_wallet(&core, &ord);
core.mine_blocks(1);
CommandBuilder::new("wallet send --fee-rate 1 bc1qw508d6qejxtdg4y5r3zarvary0c5xw7kv8f3t4 1btc")
.core(&core)
.ord(&ord)
.expected_stderr("error: failed to lock UTXOs\n")
.expected_exit_code(1)
.run_and_extract_stdout();
}
#[test]
fn wallet_send_with_fee_rate() {
let core = mockcore::spawn();
let ord = TestServer::spawn_with_server_args(&core, &[], &[]);
create_wallet(&core, &ord);
core.mine_blocks(1);
let (inscription, _) = inscribe(&core, &ord);
CommandBuilder::new(format!(
"wallet send bc1qw508d6qejxtdg4y5r3zarvary0c5xw7kv8f3t4 {inscription} --fee-rate 2.0"
))
.core(&core)
.ord(&ord)
.run_and_deserialize_output::<Send>();
let tx = &core.mempool()[0];
let mut fee = Amount::ZERO;
for input in &tx.input {
fee += core.get_utxo_amount(&input.previous_output).unwrap();
}
for output in &tx.output {
fee -= output.value;
}
let fee_rate = fee.to_sat() as f64 / tx.vsize() as f64;
pretty_assert_eq!(fee_rate, 2.0);
}
#[test]
fn user_must_provide_fee_rate_to_send() {
let core = mockcore::spawn();
let ord = TestServer::spawn_with_server_args(&core, &[], &[]);
create_wallet(&core, &ord);
core.mine_blocks(1);
let (inscription, _) = inscribe(&core, &ord);
CommandBuilder::new(format!(
"wallet send bc1qw508d6qejxtdg4y5r3zarvary0c5xw7kv8f3t4 {inscription}"
))
.core(&core)
.ord(&ord)
.expected_exit_code(2)
.stderr_regex(
".*error: the following required arguments were not provided:
.*--fee-rate <FEE_RATE>.*",
)
.run_and_extract_stdout();
}
#[test]
fn wallet_send_with_fee_rate_and_target_postage() {
let core = mockcore::spawn();
let ord = TestServer::spawn_with_server_args(&core, &[], &[]);
create_wallet(&core, &ord);
core.mine_blocks(1);
let (inscription, _) = inscribe(&core, &ord);
CommandBuilder::new(format!(
"wallet send bc1qw508d6qejxtdg4y5r3zarvary0c5xw7kv8f3t4 {inscription} --fee-rate 2.0 --postage 77000sat"
))
.core(&core)
.ord(&ord)
.run_and_deserialize_output::<Send>();
let tx = &core.mempool()[0];
let mut fee = Amount::ZERO;
for input in &tx.input {
fee += core.get_utxo_amount(&input.previous_output).unwrap();
}
for output in &tx.output {
fee -= output.value;
}
let fee_rate = fee.to_sat() as f64 / tx.vsize() as f64;
pretty_assert_eq!(fee_rate, 2.0);
pretty_assert_eq!(tx.output[0].value.to_sat(), 77_000);
}
#[test]
fn send_btc_does_not_send_locked_utxos() {
let core = mockcore::spawn();
let ord = TestServer::spawn_with_server_args(&core, &[], &[]);
create_wallet(&core, &ord);
let coinbase_tx = &core.mine_blocks(1)[0].txdata[0];
let outpoint = OutPoint::new(coinbase_tx.compute_txid(), 0);
core.lock(outpoint);
CommandBuilder::new("wallet send --fee-rate 1 bc1qw508d6qejxtdg4y5r3zarvary0c5xw7kv8f3t4 1btc")
.core(&core)
.ord(&ord)
.expected_exit_code(1)
.stderr_regex("error:.*")
.run_and_extract_stdout();
}
#[test]
fn send_dry_run() {
let core = mockcore::spawn();
let ord = TestServer::spawn_with_server_args(&core, &[], &[]);
create_wallet(&core, &ord);
core.mine_blocks(1);
let (inscription, _) = inscribe(&core, &ord);
core.mine_blocks(1);
let output = CommandBuilder::new(format!(
"wallet send --fee-rate 1 bc1qcqgs2pps4u4yedfyl5pysdjjncs8et5utseepv {inscription} --dry-run",
))
.core(&core)
.ord(&ord)
.run_and_deserialize_output::<Send>();
assert!(core.mempool().is_empty());
assert_eq!(
Psbt::deserialize(&base64_decode(&output.psbt).unwrap())
.unwrap()
.fee()
.unwrap()
.to_sat(),
output.fee
);
assert_eq!(output.asset, Outgoing::InscriptionId(inscription));
}
#[test]
fn sending_rune_that_has_not_been_etched_is_an_error() {
let core = mockcore::builder().network(Network::Regtest).build();
let ord = TestServer::spawn_with_server_args(&core, &["--index-runes", "--regtest"], &[]);
create_wallet(&core, &ord);
let coinbase_tx = &core.mine_blocks(1)[0].txdata[0];
let outpoint = OutPoint::new(coinbase_tx.compute_txid(), 0);
core.lock(outpoint);
CommandBuilder::new("--chain regtest --index-runes wallet send --fee-rate 1 bcrt1qs758ursh4q9z627kt3pp5yysm78ddny6txaqgw 1:FOO")
.core(&core)
.ord(&ord)
.expected_exit_code(1)
.expected_stderr("error: rune `FOO` has not been etched\n")
.run_and_extract_stdout();
}
#[test]
fn sending_rune_with_excessive_precision_is_an_error() {
let core = mockcore::builder().network(Network::Regtest).build();
let ord = TestServer::spawn_with_server_args(&core, &["--index-runes", "--regtest"], &[]);
create_wallet(&core, &ord);
etch(&core, &ord, Rune(RUNE));
CommandBuilder::new(format!(
"--chain regtest --index-runes wallet send --fee-rate 1 bcrt1qs758ursh4q9z627kt3pp5yysm78ddny6txaqgw 1.1:{}",
Rune(RUNE)
))
.core(&core)
.ord(&ord)
.expected_exit_code(1)
.expected_stderr("error: excessive precision\n")
.run_and_extract_stdout();
}
#[test]
fn sending_rune_with_insufficient_balance_is_an_error() {
let core = mockcore::builder().network(Network::Regtest).build();
let ord = TestServer::spawn_with_server_args(&core, &["--index-runes", "--regtest"], &[]);
create_wallet(&core, &ord);
etch(&core, &ord, Rune(RUNE));
CommandBuilder::new(format!(
"--chain regtest --index-runes wallet send --fee-rate 1 bcrt1qs758ursh4q9z627kt3pp5yysm78ddny6txaqgw 1001:{}",
Rune(RUNE)
))
.core(&core)
.ord(&ord)
.expected_exit_code(1)
.expected_stderr("error: insufficient `AAAAAAAAAAAAA` balance, only 1000\u{A0}¢ in wallet\n")
.run_and_extract_stdout();
}
#[test]
fn sending_rune_works() {
let core = mockcore::builder().network(Network::Regtest).build();
let ord = TestServer::spawn_with_server_args(&core, &["--index-runes", "--regtest"], &[]);
create_wallet(&core, &ord);
etch(&core, &ord, Rune(RUNE));
let output = CommandBuilder::new(format!(
"--chain regtest --index-runes wallet send --fee-rate 1 bcrt1qs758ursh4q9z627kt3pp5yysm78ddny6txaqgw 1000:{}",
Rune(RUNE)
))
.core(&core)
.ord(&ord)
.run_and_deserialize_output::<Send>();
core.mine_blocks(1);
let balances = CommandBuilder::new("--regtest --index-runes balances")
.core(&core)
.ord(&ord)
.run_and_deserialize_output::<ord::subcommand::balances::Output>();
pretty_assert_eq!(
balances,
ord::subcommand::balances::Output {
runes: vec![(
SpacedRune::new(Rune(RUNE), 0),
vec![(
OutPoint {
txid: output.txid,
vout: 0
},
Pile {
amount: 1000,
divisibility: 0,
symbol: Some('¢')
},
)]
.into_iter()
.collect()
),]
.into_iter()
.collect(),
}
);
}
#[test]
fn sending_rune_with_change_works() {
let core = mockcore::builder().network(Network::Regtest).build();
let ord = TestServer::spawn_with_server_args(&core, &["--index-runes", "--regtest"], &[]);
create_wallet(&core, &ord);
etch(&core, &ord, Rune(RUNE));
let output = CommandBuilder::new(format!(
"--chain regtest --index-runes wallet send --postage 1234sat --fee-rate 1 bcrt1qs758ursh4q9z627kt3pp5yysm78ddny6txaqgw 777:{}",
Rune(RUNE)
))
.core(&core)
.ord(&ord)
.run_and_deserialize_output::<Send>();
core.mine_blocks(1);
let tx = core.tx_by_id(output.txid);
assert_eq!(tx.output[1].value.to_sat(), 1234);
assert_eq!(tx.output[2].value.to_sat(), 1234);
let balances = CommandBuilder::new("--regtest --index-runes balances")
.core(&core)
.ord(&ord)
.run_and_deserialize_output::<ord::subcommand::balances::Output>();
pretty_assert_eq!(
balances,
ord::subcommand::balances::Output {
runes: [(
SpacedRune::new(Rune(RUNE), 0),
[
(
OutPoint {
txid: output.txid,
vout: 1
},
Pile {
amount: 223,
divisibility: 0,
symbol: Some('¢')
},
),
(
OutPoint {
txid: output.txid,
vout: 2
},
Pile {
amount: 777,
divisibility: 0,
symbol: Some('¢')
},
)
]
.into()
)]
.into()
}
);
}
#[test]
fn sending_rune_creates_change_output_for_non_outgoing_runes() {
let core = mockcore::builder().network(Network::Regtest).build();
let ord = TestServer::spawn_with_server_args(&core, &["--index-runes", "--regtest"], &[]);
create_wallet(&core, &ord);
let a = etch(&core, &ord, Rune(RUNE));
let b = etch(&core, &ord, Rune(RUNE + 1));
let (a_block, a_tx) = core.tx_index(a.output.reveal);
let (b_block, b_tx) = core.tx_index(b.output.reveal);
core.mine_blocks(1);
let address = CommandBuilder::new("--regtest wallet receive")
.core(&core)
.ord(&ord)
.run_and_deserialize_output::<ord::subcommand::wallet::receive::Output>()
.addresses
.into_iter()
.next()
.unwrap();
let merge = core.broadcast_tx(TransactionTemplate {
inputs: &[(a_block, a_tx, 1, default()), (b_block, b_tx, 1, default())],
recipient: Some(address.require_network(Network::Regtest).unwrap()),
..default()
});
core.mine_blocks(1);
let balances = CommandBuilder::new("--regtest --index-runes balances")
.core(&core)
.ord(&ord)
.run_and_deserialize_output::<ord::subcommand::balances::Output>();
pretty_assert_eq!(
balances,
ord::subcommand::balances::Output {
runes: [
(
SpacedRune::new(Rune(RUNE), 0),
[(
OutPoint {
txid: merge,
vout: 0
},
Pile {
amount: 1000,
divisibility: 0,
symbol: Some('¢')
},
)]
.into()
),
(
SpacedRune::new(Rune(RUNE + 1), 0),
[(
OutPoint {
txid: merge,
vout: 0
},
Pile {
amount: 1000,
divisibility: 0,
symbol: Some('¢')
},
)]
.into()
),
]
.into()
}
);
let output = CommandBuilder::new(format!(
"--chain regtest --index-runes wallet send --fee-rate 1 bcrt1qs758ursh4q9z627kt3pp5yysm78ddny6txaqgw 1000:{}",
Rune(RUNE)
))
.core(&core)
.ord(&ord)
.run_and_deserialize_output::<Send>();
core.mine_blocks(1);
let balances = CommandBuilder::new("--regtest --index-runes balances")
.core(&core)
.ord(&ord)
.run_and_deserialize_output::<ord::subcommand::balances::Output>();
pretty_assert_eq!(
balances,
ord::subcommand::balances::Output {
runes: [
(
SpacedRune::new(Rune(RUNE), 0),
[(
OutPoint {
txid: output.txid,
vout: 2
},
Pile {
amount: 1000,
divisibility: 0,
symbol: Some('¢')
},
)]
.into()
),
(
SpacedRune::new(Rune(RUNE + 1), 0),
[(
OutPoint {
txid: output.txid,
vout: 1
},
Pile {
amount: 1000,
divisibility: 0,
symbol: Some('¢')
},
)]
.into()
)
]
.into()
}
);
pretty_assert_eq!(
CommandBuilder::new("--regtest --index-runes wallet balance")
.core(&core)
.ord(&ord)
.run_and_deserialize_output::<Balance>(),
Balance {
cardinal: 84999960000,
ordinal: 20000,
runes: Some([(SpacedRune::new(Rune(RUNE + 1), 0), "1000".parse().unwrap())].into()),
runic: Some(10000),
total: 84999990000,
}
);
}
#[test]
fn sending_spaced_rune_works_with_no_change() {
let core = mockcore::builder().network(Network::Regtest).build();
let ord = TestServer::spawn_with_server_args(&core, &["--index-runes", "--regtest"], &[]);
create_wallet(&core, &ord);
etch(&core, &ord, Rune(RUNE));
let output = CommandBuilder::new(
"--chain regtest --index-runes wallet send --fee-rate 1 bcrt1qs758ursh4q9z627kt3pp5yysm78ddny6txaqgw 1000:A•AAAAAAAAAAAA",
)
.core(&core)
.ord(&ord)
.run_and_deserialize_output::<Send>();
core.mine_blocks(1);
let tx = core.tx_by_id(output.txid);
assert_eq!(tx.output.len(), 2);
let balances = CommandBuilder::new("--regtest --index-runes balances")
.core(&core)
.ord(&ord)
.run_and_deserialize_output::<ord::subcommand::balances::Output>();
assert_eq!(
balances,
ord::subcommand::balances::Output {
runes: vec![(
SpacedRune::new(Rune(RUNE), 0),
vec![(
OutPoint {
txid: output.txid,
vout: 0
},
Pile {
amount: 1000,
divisibility: 0,
symbol: Some('¢')
},
)]
.into_iter()
.collect()
),]
.into_iter()
.collect(),
}
);
}
#[test]
fn sending_rune_with_divisibility_works() {
let core = mockcore::builder().network(Network::Regtest).build();
let ord = TestServer::spawn_with_server_args(&core, &["--index-runes", "--regtest"], &[]);
create_wallet(&core, &ord);
core.mine_blocks(1);
let rune = Rune(RUNE);
batch(
&core,
&ord,
batch::File {
etching: Some(batch::Etching {
divisibility: 1,
rune: SpacedRune { rune, spacers: 0 },
premine: "1000".parse().unwrap(),
supply: "1000".parse().unwrap(),
symbol: '¢',
terms: None,
turbo: false,
}),
inscriptions: vec![batch::Entry {
file: Some("inscription.jpeg".into()),
..default()
}],
..default()
},
);
let output = CommandBuilder::new(format!(
"--chain regtest --index-runes wallet send --fee-rate 1 bcrt1qs758ursh4q9z627kt3pp5yysm78ddny6txaqgw 10.1:{rune}"
))
.core(&core)
.ord(&ord)
.run_and_deserialize_output::<Send>();
core.mine_blocks(1);
let balances = CommandBuilder::new("--regtest --index-runes balances")
.core(&core)
.ord(&ord)
.run_and_deserialize_output::<ord::subcommand::balances::Output>();
pretty_assert_eq!(
balances,
ord::subcommand::balances::Output {
runes: vec![(
SpacedRune::new(Rune(RUNE), 0),
vec![
(
OutPoint {
txid: output.txid,
vout: 1
},
Pile {
amount: 9899,
divisibility: 1,
symbol: Some('¢')
},
),
(
OutPoint {
txid: output.txid,
vout: 2
},
Pile {
amount: 101,
divisibility: 1,
symbol: Some('¢')
},
)
]
.into_iter()
.collect()
),]
.into_iter()
.collect(),
}
);
}
#[test]
fn sending_rune_leaves_unspent_runes_in_wallet() {
let core = mockcore::builder().network(Network::Regtest).build();
let ord = TestServer::spawn_with_server_args(&core, &["--index-runes", "--regtest"], &[]);
create_wallet(&core, &ord);
etch(&core, &ord, Rune(RUNE));
let output = CommandBuilder::new(format!(
"--chain regtest --index-runes wallet send --fee-rate 1 bcrt1qs758ursh4q9z627kt3pp5yysm78ddny6txaqgw 750:{}",
Rune(RUNE)
))
.core(&core)
.ord(&ord)
.run_and_deserialize_output::<Send>();
core.mine_blocks(1);
let balances = CommandBuilder::new("--regtest --index-runes balances")
.core(&core)
.ord(&ord)
.run_and_deserialize_output::<ord::subcommand::balances::Output>();
assert_eq!(
balances,
ord::subcommand::balances::Output {
runes: vec![(
SpacedRune::new(Rune(RUNE), 0),
vec![
(
OutPoint {
txid: output.txid,
vout: 1
},
Pile {
amount: 250,
divisibility: 0,
symbol: Some('¢')
},
),
(
OutPoint {
txid: output.txid,
vout: 2
},
Pile {
amount: 750,
divisibility: 0,
symbol: Some('¢')
},
)
]
.into_iter()
.collect()
),]
.into_iter()
.collect(),
}
);
let tx = core.tx_by_id(output.txid);
let address = Address::from_script(&tx.output[1].script_pubkey, Network::Regtest).unwrap();
assert!(core.state().change_addresses.contains(&address));
}
#[test]
fn sending_rune_creates_transaction_with_expected_runestone() {
let core = mockcore::builder().network(Network::Regtest).build();
let ord = TestServer::spawn_with_server_args(&core, &["--index-runes", "--regtest"], &[]);
create_wallet(&core, &ord);
let etch = etch(&core, &ord, Rune(RUNE));
let output = CommandBuilder::new(format!(
"
--chain regtest
--index-runes
wallet
send
--fee-rate 1
bcrt1qs758ursh4q9z627kt3pp5yysm78ddny6txaqgw 750:{}
",
Rune(RUNE),
))
.core(&core)
.ord(&ord)
.run_and_deserialize_output::<Send>();
core.mine_blocks(1);
let balances = CommandBuilder::new("--regtest --index-runes balances")
.core(&core)
.ord(&ord)
.run_and_deserialize_output::<ord::subcommand::balances::Output>();
assert_eq!(
balances,
ord::subcommand::balances::Output {
runes: vec![(
SpacedRune::new(Rune(RUNE), 0),
vec![
(
OutPoint {
txid: output.txid,
vout: 1
},
Pile {
amount: 250,
divisibility: 0,
symbol: Some('¢')
},
),
(
OutPoint {
txid: output.txid,
vout: 2
},
Pile {
amount: 750,
divisibility: 0,
symbol: Some('¢')
},
)
]
.into_iter()
.collect()
),]
.into_iter()
.collect(),
}
);
let tx = core.tx_by_id(output.txid);
pretty_assert_eq!(
Runestone::decipher(&tx).unwrap(),
Artifact::Runestone(Runestone {
pointer: None,
etching: None,
edicts: vec![Edict {
id: etch.id,
amount: 750,
output: 2
}],
mint: None,
}),
);
}
#[test]
fn error_messages_use_spaced_runes() {
let core = mockcore::builder().network(Network::Regtest).build();
let ord = TestServer::spawn_with_server_args(&core, &["--index-runes", "--regtest"], &[]);
create_wallet(&core, &ord);
etch(&core, &ord, Rune(RUNE));
CommandBuilder::new(
"--chain regtest --index-runes wallet send --fee-rate 1 bcrt1qs758ursh4q9z627kt3pp5yysm78ddny6txaqgw 1001:A•AAAAAAAAAAAA",
)
.core(&core)
.ord(&ord)
.expected_exit_code(1)
.expected_stderr("error: insufficient `A•AAAAAAAAAAAA` balance, only 1000\u{A0}¢ in wallet\n")
.run_and_extract_stdout();
CommandBuilder::new("--chain regtest --index-runes wallet send --fee-rate 1 bcrt1qs758ursh4q9z627kt3pp5yysm78ddny6txaqgw 1:F•OO")
.core(&core)
.ord(&ord)
.expected_exit_code(1)
.expected_stderr("error: rune `FOO` has not been etched\n")
.run_and_extract_stdout();
}