"bitflags 1.3.2", ] [[package]] name = "redox_users" version = "0.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b033d837a7cf162d7993aded9304e30a83213c648b6e389db233191f891e5c2b" dependencies = [ "getrandom 0.2.10", "redox_syscall 0.2.16", "thiserror", ] [[package]] name = "regex" version = "1.9.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b2eae68fc220f7cf2532e4494aded17545fce192d59cd996e0fe7887f4ceb575" dependencies = [ "aho-corasick", "memchr", "regex-automata", "regex-syntax", ] [[package]] name = "regex-automata" version = "0.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "39354c10dd07468c2e73926b23bb9c2caca74c5501e38a35da70406f1d923310" dependencies = [ "aho-corasick", "memchr", "regex-syntax", ] [[package]] name = "regex-syntax" version = "0.7.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e5ea92a5b6195c6ef2a0295ea818b312502c6fc94dde986c5553242e18fd4ce2" #44,990,590textchecksum = "e17bb3549cc1321ae1296b9cdc2698e2b6cb1992adfa19a8c72e5b7a738f44cd" dependencies = [ "time 0.3.23", ] #44,990,562textedition = "2018" max_width = 100 newline_style = "Unix" tab_spaces = 2 use_field_init_shorthand = true use_try_shorthand = true #44,986,934text }; fields.entry(tag).or_insert(value); } Self { fields, body } } } impl Runestone { pub fn from_transaction(transaction: &Transaction) -> Option<Self> { Self::decipher(transaction).ok().flatten() } fn decipher(transaction: &Transaction) -> Result<Option<Self>> { let Some(payload) = Runestone::payload(transaction)? else { return Ok(None); }; let mut integers = Vec::new(); let mut i = 0; while i < payload.len() { let (integer, length) = varint::decode(&payload[i..])?; integers.push(integer); i += length; } let Message { mut fields, body } = Message::from_integers(&integers); let etching = fields.remove(&TAG_RUNE).map(|rune| Etching { divisibility: fields .remove(&TAG_DIVISIBILITY) .and_then(|divisibility| u8::try_from(divisibility).ok()) .and_then(|divisibility| (divisibility <= MAX_DIVISIBILITY).then_some(divisibility)) .unwrap_or_default(), limit: fields .remove(&TAG_LIMIT) #44,985,374text version: 0, }), Ok(Some(Runestone { edicts: vec![Edict { id: 1, amount: 2, output: 3, }], ..Default::default() })) ); } #[test] fn runestone_may_be_after_non_matching_op_return() { let payload = payload(&[0, 1, 2, 3]); let payload: &PushBytes = payload.as_slice().try_into().unwrap(); 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"FOO") .into_script(), value: 0, }, TxOut { script_pubkey: script::Builder::new() .push_opcode(opcodes::all::OP_RETURN) .push_slice(b"RUNE_TEST") .push_slice(payload) .into_script(), value: 0 } ], lock_time: locktime::absolute::LockTime::ZERO, #44,985,370text version: 0, }), Ok(Some(Runestone { edicts: vec![Edict { id: 1, amount: 2, output: 3, }], etching: Some(Etching { rune: Rune(4), divisibility: 5, ..Default::default() }), ..Default::default() })) ); } #[test] fn runestone_may_be_in_second_output() { let payload = payload(&[0, 1, 2, 3]); let payload: &PushBytes = payload.as_slice().try_into().unwrap(); assert_eq!( Runestone::decipher(&Transaction { input: Vec::new(), output: vec![ TxOut { script_pubkey: ScriptBuf::new(), value: 0, }, TxOut { script_pubkey: script::Builder::new() .push_opcode(opcodes::all::OP_RETURN) .push_slice(b"RUNE_TEST") .push_slice(payload) .into_script(), value: 0 } ], lock_time: locktime::absolute::LockTime::ZERO, #44,985,359text assert_eq!( "0000000000000000000000000000000000000000000000000000000000000000:0:0" .parse::<Outgoing>() .unwrap(), Outgoing::SatPoint( "0000000000000000000000000000000000000000000000000000000000000000:0:0" .parse() .unwrap() ), ); assert_eq!( "0 sat".parse::<Outgoing>().unwrap(), Outgoing::Amount("0 sat".parse().unwrap()), ); assert_eq!( "0sat".parse::<Outgoing>().unwrap(), Outgoing::Amount("0 sat".parse().unwrap()), ); assert!("0".parse::<Outgoing>().is_err()); } } #44,985,358textuse super::*; #[derive(Copy, Clone)] pub(crate) enum Blocktime { Confirmed(DateTime<Utc>), Expected(DateTime<Utc>), } impl Blocktime { pub(crate) fn confirmed(seconds: u32) -> Self { Self::Confirmed(timestamp(seconds)) } pub(crate) fn timestamp(self) -> DateTime<Utc> { match self { Self::Confirmed(timestamp) | Self::Expected(timestamp) => timestamp, } } pub(crate) fn unix_timestamp(self) -> i64 { match self { Self::Confirmed(timestamp) | Self::Expected(timestamp) => timestamp.timestamp(), } } pub(crate) fn suffix(self) -> &'static str { match self { Self::Confirmed(_) => "", Self::Expected(_) => " (expected)", } } } #44,983,636text .iter() .flat_map(|(_, _, _, extensions)| extensions.first().cloned()) .collect::<Vec<&str>>(); extensions.sort(); Err(anyhow!( "unsupported file extension `.{extension}`, supported extensions: {}", extensions.join(" "), )) } pub(crate) fn check_mp4_codec(path: &Path) -> Result<(), Error> { let f = File::open(path)?; let size = f.metadata()?.len(); let reader = BufReader::new(f); let mp4 = Mp4Reader::read_header(reader, size)?; for track in mp4.tracks().values() { if let TrackType::Video = track.track_type()? { let media_type = track.media_type()?; if media_type != MediaType::H264 { return Err(anyhow!( "Unsupported video codec, only H.264 is supported in MP4: {media_type}" )); } } } Ok(()) } } impl FromStr for Media { type Err = Error; fn from_str(s: &str) -> Result<Self, Self::Err> { for entry in Self::TABLE { if entry.0 == s { return Ok(entry.2); #44,983,510texttoken-mint1.tapart tapx 44226185#44,518,673taptoken-mint1.tapart tapx 44226185#44,518,672taptoken-mint1.tapart tapx 44226185#44,518,671tapInscription #44105452#44,105,452webpInscription #44105375#44,105,375webpmint5.dfsn#41,361,271brc-20mint5.dfsn#41,361,264brc-20mint5.dfsn#41,361,263brc-20mint5.dfsn#41,361,261brc-20mint5.dfsn#41,361,260brc-20mint5.dfsn#41,361,259brc-20mint5.dfsn#41,361,258brc-20mint5.dfsn#41,361,256brc-20mint5.dfsn#41,361,255brc-20mint5.dfsn#41,361,254brc-20