SHOWING: First 60
["/content/5ac5662894624ebde8ce122739c5f00f7eedd7125617491ce3598e326273d924i0",{"p":"brc-20","op":"mint","tick":"cpos","amt":"40"},{"p":"ocf","op":"call","name":"forest_launch","params":["0"]}]#69,843,114text["/content/5ac5662894624ebde8ce122739c5f00f7eedd7125617491ce3598e326273d924i0",{"p":"brc-20","op":"mint","tick":"cpos","amt":"40"},{"p":"ocf","op":"call","name":"forest_launch","params":["0"]}]#69,843,113text["/content/5ac5662894624ebde8ce122739c5f00f7eedd7125617491ce3598e326273d924i0",{"p":"brc-20","op":"mint","tick":"cpos","amt":"40"},{"p":"ocf","op":"call","name":"forest_launch","params":["0"]}]#69,843,112text["/content/5ac5662894624ebde8ce122739c5f00f7eedd7125617491ce3598e326273d924i0",{"p":"brc-20","op":"mint","tick":"cpos","amt":"40"},{"p":"ocf","op":"call","name":"forest_launch","params":["0"]}]#69,843,111text["/content/5ac5662894624ebde8ce122739c5f00f7eedd7125617491ce3598e326273d924i0",{"p":"brc-20","op":"mint","tick":"cpos","amt":"40"},{"p":"ocf","op":"call","name":"forest_launch","params":["0"]}]#69,843,110text["/content/5ac5662894624ebde8ce122739c5f00f7eedd7125617491ce3598e326273d924i0",{"p":"brc-20","op":"mint","tick":"cpos","amt":"40"},{"p":"ocf","op":"call","name":"forest_launch","params":["0"]}]#69,843,109text["/content/5ac5662894624ebde8ce122739c5f00f7eedd7125617491ce3598e326273d924i0",{"p":"brc-20","op":"mint","tick":"cpos","amt":"40"},{"p":"ocf","op":"call","name":"forest_launch","params":["0"]}]#69,843,108text["/content/5ac5662894624ebde8ce122739c5f00f7eedd7125617491ce3598e326273d924i0",{"p":"brc-20","op":"mint","tick":"cpos","amt":"40"},{"p":"ocf","op":"call","name":"forest_launch","params":["0"]}]#69,843,107text["/content/5ac5662894624ebde8ce122739c5f00f7eedd7125617491ce3598e326273d924i0",{"p":"brc-20","op":"mint","tick":"cpos","amt":"40"},{"p":"ocf","op":"call","name":"forest_launch","params":["0"]}]#69,843,106text#67,757,160mint1,000.rwas#60,398,163brc-339mint1,000.rwas#60,398,162brc-339mint1,000.rwas#60,398,161brc-339mint1,000.rwas#60,398,160brc-339mint1,000.rwas#60,398,159brc-339mint1,000.rwas#60,398,158brc-339mint1,000.rwas#60,398,157brc-339mint1,000.rwas#60,398,156brc-339mint1,000.rwas#60,398,155brc-339mint1,000.rwas#60,398,154brc-339 let context = Context::builder()
.arg("--index-runes-pre-alpha-i-agree-to-get-rekt")
.build();
context.mine_blocks(1);
let etching = context.rpc_server.broadcast_tx(TransactionTemplate {
inputs: &[(1, 0, 0, Witness::new())],
op_return: Some(
Runestone {
etching: Some(Etching {
rune: Rune(RUNE),
limit: Some(1000),
..Default::default()
}),
edicts: vec![Edict {
id: 0,
amount: 2000,
output: 0,
}],
..Default::default()
}
.encipher(),
),
..Default::default()
});
context.mine_blocks(1);
let id = RuneId {
height: 2,
index: 1,
};
context.assert_runes(
[(
id,
RuneEntry {
etching,
rune: Rune(RUNE),
limit: Some(1000),
timestamp: 2,
supply: 1000,
..Default::default()
},
)],
[(
OutPoint {
#45,051,221text let split_coinbase_output = TransactionTemplate {
inputs: &[(1, 0, 0, Default::default())],
outputs: 2,
fee: 0,
..Default::default()
};
let txid = context.rpc_server.broadcast_tx(split_coinbase_output);
context.mine_blocks(1);
assert_eq!(
context.index.list(OutPoint::new(txid, 0)).unwrap().unwrap(),
List::Unspent(vec![(50 * COIN_VALUE, 75 * COIN_VALUE)])
);
assert_eq!(
context.index.list(OutPoint::new(txid, 1)).unwrap().unwrap(),
List::Unspent(vec![(75 * COIN_VALUE, 100 * COIN_VALUE)])
);
}
#[test]
fn list_merge_ranges_are_tracked_correctly() {
let context = Context::builder().arg("--index-sats").build();
context.mine_blocks(2);
let merge_coinbase_outputs = TransactionTemplate {
inputs: &[(1, 0, 0, Default::default()), (2, 0, 0, Default::default())],
fee: 0,
..Default::default()
};
let txid = context.rpc_server.broadcast_tx(merge_coinbase_outputs);
context.mine_blocks(1);
assert_eq!(
#45,017,568textuse {super::*, crate::wallet::Wallet};
#[derive(Debug, Parser)]
pub(crate) struct Sats {
#[arg(
long,
help = "Find satoshis listed in first column of tab-separated value file <TSV>."
)]
tsv: Option<PathBuf>,
}
#[derive(Serialize, Deserialize)]
pub struct OutputTsv {
pub sat: String,
pub output: OutPoint,
}
#[derive(Serialize, Deserialize)]
pub struct OutputRare {
pub sat: Sat,
pub output: OutPoint,
pub offset: u64,
pub rarity: Rarity,
}
impl Sats {
pub(crate) fn run(&self, options: Options) -> SubcommandResult {
let index = Index::open(&options)?;
if !index.has_sat_index() {
bail!("sats requires index created with `--index-sats` flag");
}
index.update()?;
let utxos = index.get_unspent_output_ranges(Wallet::load(&options)?)?;
if let Some(path) = &self.tsv {
let mut output = Vec::new();
for (outpoint, sat) in sats_from_tsv(
utxos,
&fs::read_to_string(path)
.with_context(|| format!("I/O error reading `{}`", path.display()))?,#44,990,050text };
let unsigned_transaction = TransactionBuilder::new(
satpoint,
inscriptions,
unspent_outputs,
locked_outputs,
address.clone(),
change,
self.fee_rate,
postage,
)
.build_transaction()?;
let signed_tx = client
.sign_raw_transaction_with_wallet(&unsigned_transaction, None, None)?
.hex;
let txid = client.send_raw_transaction(&signed_tx)?;
Ok(Box::new(Output { transaction: txid }))
}
fn lock_inscriptions(
client: &Client,
inscriptions: BTreeMap<SatPoint, InscriptionId>,
unspent_outputs: BTreeMap<bitcoin::OutPoint, bitcoin::Amount>,
) -> Result {
let all_inscription_outputs = inscriptions
.keys()
.map(|satpoint| satpoint.outpoint)
.collect::<HashSet<OutPoint>>();
let wallet_inscription_outputs = unspent_outputs
.keys()
.filter(|utxo| all_inscription_outputs.contains(utxo))
.cloned()
.collect::<Vec<OutPoint>>();
if !client.lock_unspent(&wallet_inscription_outputs)? {#44,990,049text match config {
SpawnConfig::Https(_) => "https",
_ => "http",
}
);
}
Ok(tokio::spawn(async move {
match config {
SpawnConfig::Https(acceptor) => {
axum_server::Server::bind(addr)
.handle(handle)
.acceptor(acceptor)
.serve(router.into_make_service())
.await
}
SpawnConfig::Redirect(destination) => {
axum_server::Server::bind(addr)
.handle(handle)
.serve(
Router::new()
.fallback(Self::redirect_http_to_https)
.layer(Extension(destination))
.into_make_service(),
)
.await
}
SpawnConfig::Http => {
axum_server::Server::bind(addr)
.handle(handle)
.serve(router.into_make_service())
.await
}
}
}))
}
fn acme_cache(acme_cache: Option<&PathBuf>, options: &Options) -> Result<PathBuf> {
#44,989,399text x = x * 26 + c as u64 - 'a' as u64 + 1;
if x > Self::SUPPLY {
bail!("sat name out of range");
}
}
_ => bail!("invalid character in sat name: {c}"),
}
}
Ok(Sat(Self::SUPPLY - x))
}
fn from_degree(degree: &str) -> Result<Self> {
let (cycle_number, rest) = degree
.split_once('°')
.ok_or_else(|| anyhow!("missing degree symbol"))?;
let cycle_number = cycle_number.parse::<u32>()?;
let (epoch_offset, rest) = rest
.split_once('′')
.ok_or_else(|| anyhow!("missing minute symbol"))?;
let epoch_offset = epoch_offset.parse::<u32>()?;
if epoch_offset >= SUBSIDY_HALVING_INTERVAL {
bail!("invalid epoch offset");
}
let (period_offset, rest) = rest
.split_once('″')
.ok_or_else(|| anyhow!("missing second symbol"))?;
let period_offset = period_offset.parse::<u32>()?;
if period_offset >= DIFFCHANGE_INTERVAL {
bail!("invalid period offset");
}
let cycle_start_epoch = cycle_number * CYCLE_EPOCHS;
#44,989,398textuse {super::*, fee_rate::FeeRate};
#[derive(Debug, Parser)]
pub(crate) struct Preview {
#[command(flatten)]
server: super::server::Server,
inscriptions: Vec<PathBuf>,
}
struct KillOnDrop(process::Child);
impl Drop for KillOnDrop {
fn drop(&mut self) {
self.0.kill().unwrap()
}
}
impl Preview {
pub(crate) fn run(self) -> SubcommandResult {
let tmpdir = TempDir::new()?;
let rpc_port = TcpListener::bind("127.0.0.1:0")?.local_addr()?.port();
let bitcoin_data_dir = tmpdir.path().join("bitcoin");
fs::create_dir(&bitcoin_data_dir)?;
let _bitcoind = KillOnDrop(
Command::new("bitcoind")
.arg({
let mut arg = OsString::from("-datadir=");
arg.push(&bitcoin_data_dir);
arg
})
.arg("-regtest")
.arg("-txindex")
.arg("-listen=0")
.arg(format!("-rpcport={rpc_port}"))
.spawn()
.context("failed to spawn `bitcoind`")?,
);
let options = Options {
chain_argument: Chain::Regtest,
#44,989,397text </dd>
.*
</dl>
.*
"
),
);
}
#[test]
fn charm_nineball() {
let server = TestServer::new_with_regtest_with_index_sats();
server.mine_blocks(9);
let txid = server.bitcoin_rpc_server.broadcast_tx(TransactionTemplate {
inputs: &[(9, 0, 0, inscription("text/plain", "foo").to_witness())],
..Default::default()
});
let id = InscriptionId { txid, index: 0 };
server.mine_blocks(1);
server.assert_response_regex(
format!("/inscription/{id}"),
StatusCode::OK,
format!(
".*<h1>Inscription 0</h1>.*
<dl>
<dt>id</dt>
<dd class=monospace>{id}</dd>
<dt>charms</dt>
<dd>
<span title=uncommon>🌱</span>
<span title=nineball>9️⃣</span>
</dd>
.*
</dl>
.*
"
),
);
}
#[test]
fn charm_reinscription() {
let server = TestServer::new_with_regtest();
server.mine_blocks(1);
server.bitcoin_rpc_server.broadcast_tx(TransactionTemplate {
inputs: &[(1, 0, 0, inscription("text/plain", "foo").to_witness())],
#44,989,396text value: 0,
}],
lock_time: locktime::absolute::LockTime::ZERO,
version: 0,
});
match result {
Ok(_) => panic!("expected error"),
Err(Error::Script(_)) => {}
Err(err) => panic!("unexpected error: {err}"),
}
}
#[test]
fn deciphering_runestone_with_invalid_varint_returns_varint_error() {
let result = 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([128])
.into_script(),
value: 0,
}],
lock_time: locktime::absolute::LockTime::ZERO,
version: 0,
});
match result {
Ok(_) => panic!("expected error"),
Err(Error::Varint) => {}
Err(err) => panic!("unexpected error: {err}"),
}
}
#[test]
fn non_push_opcodes_in_runestone_are_ignored() {
assert_eq!(
Runestone::decipher(&Transaction {
input: Vec::new(),
#44,986,956text where
D: Deserializer<'de>,
{
Ok(DeserializeFromStr::deserialize(deserializer)?.0)
}
}
impl FromStr for SatPoint {
type Err = Error;
fn from_str(s: &str) -> Result<Self, Self::Err> {
let (outpoint, offset) = s
.rsplit_once(':')
.ok_or_else(|| anyhow!("invalid satpoint: {s}"))?;
Ok(SatPoint {
outpoint: outpoint.parse()?,
offset: offset.parse()?,
})
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn from_str_ok() {
assert_eq!(
"1111111111111111111111111111111111111111111111111111111111111111:1:1"
.parse::<SatPoint>()
.unwrap(),
SatPoint {
outpoint: "1111111111111111111111111111111111111111111111111111111111111111:1"
.parse()
.unwrap(),
offset: 1,
}
);
}
#[test]
fn from_str_err() {
"abc".parse::<SatPoint>().unwrap_err();
"abc:xyz".parse::<SatPoint>().unwrap_err();
"1111111111111111111111111111111111111111111111111111111111111111:1"
.parse::<SatPoint>()
#44,986,955textuse {super::*, std::num::TryFromIntError};
#[derive(Debug, PartialEq, Copy, Clone, Hash, Eq, Ord, PartialOrd)]
pub(crate) struct RuneId {
pub(crate) height: u32,
pub(crate) index: u16,
}
impl TryFrom<u128> for RuneId {
type Error = TryFromIntError;
fn try_from(n: u128) -> Result<Self, Self::Error> {
Ok(Self {
height: u32::try_from(n >> 16)?,
index: u16::try_from(n & 0xFFFF).unwrap(),
})
}
}
impl From<RuneId> for u128 {
fn from(id: RuneId) -> Self {
u128::from(id.height) << 16 | u128::from(id.index)
}
}
impl Display for RuneId {
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
write!(f, "{}/{}", self.height, self.index,)
}
}
impl FromStr for RuneId {
type Err = crate::Error;
fn from_str(s: &str) -> Result<Self, Self::Err> {
let (height, index) = s
.split_once('/')
.ok_or_else(|| anyhow!("invalid rune ID: {s}"))?;
Ok(Self {
height: height.parse()?,
index: index.parse()?,
})
}
}
#[cfg(test)]
mod tests {
use super::*;
#44,986,954text
let curse = if inscription.payload.unrecognized_even_field {
Some(Curse::UnrecognizedEvenField)
} else if inscription.payload.duplicate_field {
Some(Curse::DuplicateField)
} else if inscription.payload.incomplete_field {
Some(Curse::IncompleteField)
} else if inscription.input != 0 {
Some(Curse::NotInFirstInput)
} else if inscription.offset != 0 {
Some(Curse::NotAtOffsetZero)
} else if inscription.payload.pointer.is_some() {
Some(Curse::Pointer)
} else if inscription.pushnum {
Some(Curse::Pushnum)
} else if let Some((id, count)) = inscribed_offset {
if *count > 1 {
Some(Curse::Reinscription)
} else {
let initial_inscription_sequence_number =
self.id_to_sequence_number.get(id.store())?.unwrap().value();
let initial_inscription_is_cursed = InscriptionEntry::load(
self
.sequence_number_to_entry
#44,986,547textdmt-mint.nat#42,522,321tapdmt-mint.nat#42,522,293tapdmt-mint.nat#42,522,276tapdmt-mint.nat#42,522,274tapdmt-mint.nat#42,522,255tap