transfer100.pizza#71,183,691brc-20minting-claim#67,449,869orc-20#59,574,083gltf#59,574,010gltf#59,574,004gltf#59,573,995gltf#59,573,990gltf#59,573,976gltf#59,573,918gltf#59,573,885gltf#59,573,880gltf#59,573,858gltf#53,610,589veda#53,608,252veda#50,194,517veda#50,141,474vedaInscription #49540326#49,540,326pngInscription #49540322#49,540,322pngInscription #49540320#49,540,320pngInscription #49540319#49,540,319pngInscription #49540318#49,540,318pngInscription #49536173#49,536,173pngInscription #49536156#49,536,156pngInscription #49536147#49,536,147pngmint1,000.dada#46,059,923brc-20mint1,000.dada#46,059,922brc-20mint1,000.dada#46,059,921brc-20mint1,000.dada#46,059,920brc-20mint1,000.dada#46,059,919brc-20 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