SHOWING: First 60
assert_eq!(Sat(0).height(), 0);
assert_eq!(Sat(1).height(), 0);
assert_eq!(Sat(Epoch(0).subsidy()).height(), 1);
assert_eq!(Sat(Epoch(0).subsidy() * 2).height(), 2);
assert_eq!(
Epoch(2).starting_sat().height(),
SUBSIDY_HALVING_INTERVAL * 2
);
assert_eq!(Sat(50 * COIN_VALUE).height(), 1);
assert_eq!(Sat(2099999997689999).height(), 6929999);
assert_eq!(Sat(2099999997689998).height(), 6929998);
}
#[test]
fn name() {
assert_eq!(Sat(0).name(), "nvtdijuwxlp");
assert_eq!(Sat(1).name(), "nvtdijuwxlo");
assert_eq!(Sat(26).name(), "nvtdijuwxkp");
assert_eq!(Sat(27).name(), "nvtdijuwxko");
assert_eq!(Sat(2099999997689999).name(), "a");
assert_eq!(Sat(2099999997689999 - 1).name(), "b");
assert_eq!(Sat(2099999997689999 - 25).name(), "z");
assert_eq!(Sat(2099999997689999 - 26).name(), "aa");
}
#[test]
fn number() {
assert_eq!(Sat(2099999997689999).n(), 2099999997689999);
}
#[test]
fn degree() {
assert_eq!(Sat(0).degree().to_string(), "0°0′0″0‴");
#44,990,188textuse super::*;
#[derive(Debug, Parser)]
pub(crate) struct Find {
#[arg(help = "Find output and offset of <SAT>.")]
sat: Sat,
#[clap(help = "Find output and offset of all sats in the range [<SAT>, <END>).")]
end: Option<Sat>,
}
#[derive(Debug, PartialEq, Serialize, Deserialize)]
pub struct Output {
pub satpoint: SatPoint,
}
#[derive(Debug, PartialEq, Serialize, Deserialize)]
pub struct FindRangeOutput {
pub start: u64,
pub size: u64,
pub satpoint: SatPoint,
}
impl Find {
pub(crate) fn run(self, options: Options) -> SubcommandResult {
let index = Index::open(&options)?;
if !index.has_sat_index() {
bail!("find requires index created with `--index-sats` flag");
}
index.update()?;
match self.end {
Some(end) => match index.find_range(self.sat.0, end.0)? {
Some(result) => Ok(Box::new(result)),
None => Err(anyhow!("range has not been mined as of index height")),
},
None => match index.find(self.sat.0)? {
Some(satpoint) => Ok(Box::new(Output { satpoint })),
#44,990,187text rarity,
name,
});
}
Ok(Box::new(outputs))
}
Some(crate::index::List::Spent) => Err(anyhow!("output spent.")),
None => Err(anyhow!("output not found")),
}
}
}
fn list(outpoint: OutPoint, ranges: Vec<(u64, u64)>) -> Vec<Output> {
let mut offset = 0;
ranges
.into_iter()
.map(|(start, end)| {
let size = end - start;
let output = Output {
output: outpoint,
start,
end,
size,
offset,
name: Sat(start).name(),
rarity: Sat(start).rarity(),
};
offset += size;
output
})
.collect()
}
#[cfg(test)]
mod tests {
use super::*;
fn output(
output: OutPoint,
start: u64,
end: u64,
size: u64,
offset: u64,
rarity: Rarity,
name: String,
) -> super::Output {
super::Output {
output,
start,
end,
size,
offset,
name,
rarity,
}
}
#[test]
fn list_ranges() {
let outpoint =
#44,990,184textuse super::*;
#[derive(Debug, Parser)]
pub(crate) struct List {
#[arg(help = "List sats in <OUTPOINT>.")]
outpoint: OutPoint,
}
#[derive(Debug, PartialEq, Serialize, Deserialize)]
pub struct Output {
pub output: OutPoint,
pub start: u64,
pub end: u64,
pub size: u64,
pub offset: u64,
pub rarity: Rarity,
pub name: String,
}
impl List {
pub(crate) fn run(self, options: Options) -> SubcommandResult {
let index = Index::open(&options)?;
if !index.has_sat_index() {
bail!("list requires index created with `--index-sats` flag");
}
index.update()?;
match index.list(self.outpoint)? {
Some(crate::index::List::Unspent(ranges)) => {
let mut outputs = Vec::new();
for Output {
output,
start,
end,
size,
offset,
rarity,
name,
} in list(self.outpoint, ranges)
{
outputs.push(Output {
output,
start,
end,
size,
offset,
#44,990,183text assert_eq!(
Runestone::decipher(&Transaction {
input: Vec::new(),
output: vec![TxOut {
script_pubkey: script::Builder::new()
.push_opcode(opcodes::all::OP_RETURN)
.push_slice(b"RUNE_TEST")
.push_slice::<&PushBytes>(varint::encode(2).as_slice().try_into().unwrap())
.push_slice::<&PushBytes>(varint::encode(4).as_slice().try_into().unwrap())
.push_slice::<&PushBytes>(varint::encode(1).as_slice().try_into().unwrap())
.push_slice::<&PushBytes>(varint::encode(5).as_slice().try_into().unwrap())
.push_slice::<&PushBytes>(varint::encode(0).as_slice().try_into().unwrap())
.push_slice::<&PushBytes>(varint::encode(1).as_slice().try_into().unwrap())
.push_slice::<&PushBytes>(varint::encode(2).as_slice().try_into().unwrap())
.push_slice::<&PushBytes>(varint::encode(3).as_slice().try_into().unwrap())
.into_script(),
value: 0
}],
lock_time: locktime::absolute::LockTime::ZERO,
#44,988,823text ..Default::default()
});
context.mine_blocks(1);
let id = RuneId {
height: 2,
index: 1,
};
context.assert_runes(
[(
id,
RuneEntry {
etching: txid0,
rune: Rune(RUNE),
supply: u128::max_value(),
timestamp: 2,
..Default::default()
},
)],
[(
OutPoint {
txid: txid0,
vout: 0,
},
vec![(id, u128::max_value())],
)],
);
let txid1 = context.rpc_server.broadcast_tx(TransactionTemplate {
inputs: &[(2, 1, 0, Witness::new())],
outputs: 2,
op_return: Some(
Runestone {
edicts: vec![
Edict {
id: id.into(),
amount: 1000,
output: 0,
},
Edict {
id: id.into(),
amount: 0,
output: 3,
},
],
..Default::default()
}
.encipher(),
),
..Default::default()#44,988,420text
context.assert_runes(
[(
id,
RuneEntry {
etching: txid0,
rune: Rune(RUNE),
supply: u128::max_value(),
timestamp: 2,
..Default::default()
},
)],
[(OutPoint { txid, vout: 1 }, vec![(id, u128::max_value())])],
);
}
#[test]
fn rune_rarity_is_assigned_correctly() {
let context = Context::builder()
.arg("--index-runes-pre-alpha-i-agree-to-get-rekt")
.build();
context.mine_blocks(2);
let txid0 = context.rpc_server.broadcast_tx(TransactionTemplate {
inputs: &[(1, 0, 0, Witness::new())],
op_return: Some(
Runestone {
edicts: vec![Edict {
id: 0,
amount: u128::max_value(),
output: 0,
}],
etching: Some(Etching {
rune: Rune(RUNE),
..Default::default()
}),
..Default::default()
}
.encipher(),
),
..Default::default()
});
let id0 = RuneId {
#44,987,636text </dl>
<p>Output has been spent.</p>
"
.unindent()
);
}
#[test]
fn no_list() {
assert_regex_match!(
OutputHtml {
inscriptions: Vec::new(),
outpoint: outpoint(1),
list: None,
chain: Chain::Mainnet,
output: TxOut {
value: 3,
script_pubkey: ScriptBuf::new_p2pkh(&PubkeyHash::all_zeros()),
},
runes: Vec::new(),
}
.to_string(),
"
<h1>Output <span class=monospace>1{64}:1</span></h1>
<dl>
<dt>value</dt><dd>3</dd>
<dt>script pubkey</dt><dd class=monospace>OP_DUP OP_HASH160 OP_PUSHBYTES_20 0{40} OP_EQUALVERIFY OP_CHECKSIG</dd>
<dt>address</dt><dd class=monospace>1111111111111111111114oLvT2</dd>
<dt>transaction</dt><dd><a class=monospace href=/tx/1{64}>1{64}</a></dd>
</dl>
"
.unindent()
);
}
#[test]
fn with_inscriptions() {
assert_regex_match!(
OutputHtml {
inscriptions: vec![inscription_id(1)],
#44,987,182text }
.encipher(),
),
..Default::default()
});
context.mine_blocks(1);
let id = RuneId {
height: 2,
index: 1,
};
context.assert_runes(
[(
id,
RuneEntry {
etching: txid0,
rune: Rune(RUNE),
supply: u128::max_value(),
timestamp: 2,
..Default::default()
},
)],
[(
OutPoint {
txid: txid0,
vout: 0,
},
vec![(id, u128::max_value())],
)],
);
let txid1 = context.rpc_server.broadcast_tx(TransactionTemplate {
inputs: &[(2, 1, 0, Witness::new())],
op_return: Some(
Runestone {
edicts: vec![
Edict {
id: 0,
amount: 100,
output: 0,
},
Edict {
id: id.into(),
amount: u128::max_value(),
output: 0,
},
],
..Default::default()
}
.encipher(),
#44,987,050textuse super::*;
#[derive(Boilerplate)]
pub(crate) struct InscriptionsBlockHtml {
pub(crate) block: u32,
pub(crate) inscriptions: Vec<InscriptionId>,
pub(crate) prev_block: Option<u32>,
pub(crate) next_block: Option<u32>,
pub(crate) prev_page: Option<usize>,
pub(crate) next_page: Option<usize>,
}
impl InscriptionsBlockHtml {
pub(crate) fn new(
block: u32,
current_blockheight: u32,
inscriptions: Vec<InscriptionId>,
page_index: usize,
) -> Result<Self> {
let num_inscriptions = inscriptions.len();
let start = page_index * 100;
let end = usize::min(start + 100, num_inscriptions);
if start > num_inscriptions || start > end {
return Err(anyhow!("page index {page_index} exceeds inscription count"));
}
let inscriptions = inscriptions[start..end].to_vec();
Ok(Self {
block,
inscriptions,
prev_block: block.checked_sub(1),
next_block: if current_blockheight > block {
Some(block + 1)
} else {
None
},
prev_page: if page_index > 0 {
#44,986,581texttransfer23,000.pi20#42,331,337brc-20mint1,000.pi20#42,323,980brc-20mint1,000.pi20#42,323,979brc-20mint1,000.pi20#42,323,978brc-20mint1,000.pi20#42,323,977brc-20mint1,000.pi20#42,323,976brc-20mint1,000.pi20#42,323,975brc-20mint1,000.pi20#42,323,974brc-20mint1,000.pi20#42,323,973brc-20mint1,000.pi20#42,323,972brc-20mint1,000.pi20#42,323,971brc-20mint1,000.pi20#42,323,970brc-20mint1,000.pi20#42,323,969brc-20mint1,000.pi20#42,323,968brc-20mint1,000.pi20#42,323,967brc-20mint1,000.pi20#42,323,966brc-20mint1,000.pi20#42,323,965brc-20mint1,000.pi20#42,323,964brc-20mint1,000.pi20#42,323,963brc-20mint1,000.pi20#42,323,962brc-20mint1,000.pi20#42,323,961brc-20