diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index f6192fd22..e9b317d54 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -169,6 +169,10 @@ jobs: working-directory: libs/sdk-common run: cargo test + - name: Run sdk-common tests with 'liquid' feature + working-directory: libs/sdk-common + run: cargo test --features=liquid + clippy: name: Clippy runs-on: ubuntu-latest diff --git a/libs/Cargo.lock b/libs/Cargo.lock index b5a2969e2..96ab8a428 100644 --- a/libs/Cargo.lock +++ b/libs/Cargo.lock @@ -383,6 +383,12 @@ version = "0.9.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d86b93f97252c47b41663388e6d155714a9d0c398b99f1005cbc5f978b29f445" +[[package]] +name = "bech32" +version = "0.10.0-beta" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "98f7eed2b2781a6f0b5c903471d48e15f56fb4e1165df8a9a2337fd1a59d45ea" + [[package]] name = "bincode" version = "1.3.3" @@ -414,7 +420,7 @@ version = "0.29.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0694ea59225b0c5f3cb405ff3f670e4828358ed26aec49dc352f730f0cb1a8a3" dependencies = [ - "bech32", + "bech32 0.9.1", "bitcoin_hashes 0.11.0", "bitcoinconsensus", "secp256k1 0.24.3", @@ -427,7 +433,7 @@ version = "0.30.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1945a5048598e4189e239d3f809b19bdad4845c4b2ba400d304d2dcf26d2c462" dependencies = [ - "bech32", + "bech32 0.9.1", "bitcoin-private", "bitcoin_hashes 0.12.0", "hex_lit", @@ -435,6 +441,20 @@ dependencies = [ "serde", ] +[[package]] +name = "bitcoin" +version = "0.31.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c85783c2fe40083ea54a33aa2f0ba58831d90fcd190f5bdc47e74e84d2a96ae" +dependencies = [ + "bech32 0.10.0-beta", + "bitcoin-internals", + "bitcoin_hashes 0.13.0", + "hex-conservative", + "hex_lit", + "secp256k1 0.28.2", +] + [[package]] name = "bitcoin-consensus-derive" version = "0.1.0" @@ -446,6 +466,12 @@ dependencies = [ "syn 1.0.109", ] +[[package]] +name = "bitcoin-internals" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9425c3bf7089c983facbae04de54513cce73b41c7f9ff8c845b54e7bc64ebbfb" + [[package]] name = "bitcoin-private" version = "0.1.0" @@ -481,6 +507,16 @@ dependencies = [ "serde", ] +[[package]] +name = "bitcoin_hashes" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1930a4dabfebb8d7d9992db18ebe3ae2876f0a305fab206fd168df931ede293b" +dependencies = [ + "bitcoin-internals", + "hex-conservative", +] + [[package]] name = "bitcoinconsensus" version = "0.20.2-0.5.0" @@ -1047,6 +1083,17 @@ version = "1.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a26ae43d7bcc3b814de94796a5e736d4029efb0ee900c12e2d54c993ad1a1e07" +[[package]] +name = "elements" +version = "0.24.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dd6b8388053196e6b2702a45418078a654680ce9e1fd91799f51f67a40118ff5" +dependencies = [ + "bitcoin 0.31.2", + "secp256k1-zkp", + "serde_json", +] + [[package]] name = "encoding_rs" version = "0.8.33" @@ -1344,7 +1391,7 @@ dependencies = [ "anyhow", "async-trait", "base64 0.21.5", - "bech32", + "bech32 0.9.1", "bytes", "chacha20poly1305", "cln-grpc", @@ -1482,6 +1529,12 @@ version = "0.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" +[[package]] +name = "hex-conservative" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "212ab92002354b4819390025006c897e8140934349e8635c9b077f47b4dcbd20" + [[package]] name = "hex_lit" version = "0.1.1" @@ -1834,7 +1887,7 @@ version = "0.24.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1788c0158526ec27a502043c2911ea6ea58fdc656bdf8749484942c07b790d23" dependencies = [ - "bech32", + "bech32 0.9.1", "bitcoin 0.29.2", "bitcoin_hashes 0.11.0", "lightning", @@ -2797,6 +2850,7 @@ dependencies = [ "bip21", "bitcoin 0.29.2", "cbc", + "elements", "hex", "lightning", "lightning-invoice", @@ -2815,6 +2869,7 @@ dependencies = [ "tonic", "tonic-build", "url", + "urlencoding", ] [[package]] @@ -2849,6 +2904,17 @@ dependencies = [ "serde", ] +[[package]] +name = "secp256k1" +version = "0.28.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d24b59d129cdadea20aea4fb2352fa053712e5d713eee47d700cd4b2bc002f10" +dependencies = [ + "bitcoin_hashes 0.13.0", + "rand", + "secp256k1-sys 0.9.2", +] + [[package]] name = "secp256k1-sys" version = "0.6.1" @@ -2867,6 +2933,37 @@ dependencies = [ "cc", ] +[[package]] +name = "secp256k1-sys" +version = "0.9.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e5d1746aae42c19d583c3c1a8c646bfad910498e2051c551a7f2e3c0c9fbb7eb" +dependencies = [ + "cc", +] + +[[package]] +name = "secp256k1-zkp" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c4e48ef9c98bfbcb98bd15693ffa19676cb3e29426b75eda8b73c05cdd7959f8" +dependencies = [ + "bitcoin-private", + "rand", + "secp256k1 0.28.2", + "secp256k1-zkp-sys", +] + +[[package]] +name = "secp256k1-zkp-sys" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b4ead52f43074bae2ddbd1e0e66e6b170135e76117f5ea9916f33d7bd0b36e29" +dependencies = [ + "cc", + "secp256k1-sys 0.9.2", +] + [[package]] name = "security-framework" version = "2.9.2" @@ -3756,6 +3853,12 @@ dependencies = [ "percent-encoding", ] +[[package]] +name = "urlencoding" +version = "2.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "daf8dba3b7eb870caf1ddeed7bc9d2a049f3cfdfae7cb521b087cc33ae4c49da" + [[package]] name = "uuid" version = "1.8.0" diff --git a/libs/sdk-bindings/Cargo.toml b/libs/sdk-bindings/Cargo.toml index 2402c3b24..a83d22391 100644 --- a/libs/sdk-bindings/Cargo.toml +++ b/libs/sdk-bindings/Cargo.toml @@ -38,4 +38,4 @@ tonic = { workspace = true, features = [ uniffi_build = { version = "0.23.0" } uniffi_bindgen = "0.23.0" anyhow = { workspace = true } -glob = "0.3.1" +glob = "0.3.1" \ No newline at end of file diff --git a/libs/sdk-common/Cargo.toml b/libs/sdk-common/Cargo.toml index d8764454b..585918449 100644 --- a/libs/sdk-common/Cargo.toml +++ b/libs/sdk-common/Cargo.toml @@ -29,6 +29,8 @@ tonic = { workspace = true, features = [ "tls-webpki-roots", ] } url = "2.5.0" +elements = { version = "0.24.1", optional = true } +urlencoding = { version = "2.1.3" } [dev-dependencies] bitcoin = { workspace = true, features = ["rand"] } @@ -37,4 +39,7 @@ tokio = { workspace = true } once_cell = { workspace = true } [build-dependencies] -tonic-build = { workspace = true } \ No newline at end of file +tonic-build = { workspace = true } + +[features] +liquid = ["dep:elements"] diff --git a/libs/sdk-common/src/input_parser.rs b/libs/sdk-common/src/input_parser.rs index 002109715..84061fc89 100644 --- a/libs/sdk-common/src/input_parser.rs +++ b/libs/sdk-common/src/input_parser.rs @@ -5,10 +5,14 @@ use bip21::Uri; use bitcoin::bech32; use bitcoin::bech32::FromBase32; use serde::{Deserialize, Serialize}; +use std::collections::HashMap; use LnUrlRequestData::*; use crate::prelude::*; +#[cfg(feature = "liquid")] +use self::liquid::bip21::LiquidAddressData; + /// Parses generic user input, typically pasted from clipboard or scanned from a QR. /// /// # Examples @@ -175,6 +179,11 @@ pub async fn parse(input: &str) -> Result { }; } + #[cfg(feature = "liquid")] + if let Ok(address) = parse_liquid_address(input) { + return Ok(InputType::LiquidAddress { address }); + } + if let Ok(invoice) = parse_invoice(input) { return Ok(InputType::Bolt11 { invoice }); } @@ -391,6 +400,15 @@ pub enum InputType { address: BitcoinAddressData, }, + /// # Supported standards + /// + /// - plain on-chain liquid address + /// - BIP21 on liquid/liquidtestnet + #[cfg(feature = "liquid")] + LiquidAddress { + address: LiquidAddressData, + }, + /// Also covers URIs like `bitcoin:...&lightning=bolt11`. In this case, it returns the BOLT11 /// and discards all other data. Bolt11 { @@ -577,6 +595,51 @@ pub struct BitcoinAddressData { pub message: Option, } +#[derive(Debug)] +pub enum URISerializationError { + UnsupportedNetwork, + AssetIdMissing, + InvalidAddress, +} + +impl BitcoinAddressData { + /// Converts the structure to a BIP21 URI while also + /// ensuring that all the fields are valid + pub fn to_uri(&self) -> Result { + self.address + .parse::() + .map_err(|_| URISerializationError::InvalidAddress)?; + + let mut optional_keys = HashMap::new(); + + if let Some(amount_sat) = self.amount_sat { + let amount_btc = amount_sat as f64 / 100_000_000.0; + optional_keys.insert("amount", format!("{amount_btc:.8}")); + } + + if let Some(message) = &self.message { + optional_keys.insert("message", urlencoding::encode(message).to_string()); + } + + if let Some(label) = &self.label { + optional_keys.insert("label", urlencoding::encode(label).to_string()); + } + + match optional_keys.is_empty() { + true => Ok(self.address.clone()), + false => { + let scheme = "bitcoin"; + let suffix_str = optional_keys + .iter() + .map(|(key, value)| format!("{key}={value}")) + .collect::>() + .join("&"); + Ok(format!("{scheme}:{}{suffix_str}", self.address)) + } + } + } +} + impl From> for BitcoinAddressData { fn from(uri: Uri) -> Self { BitcoinAddressData { @@ -722,6 +785,51 @@ pub(crate) mod tests { Ok(()) } + #[tokio::test] + #[cfg(feature = "liquid")] + async fn test_liquid_address() -> Result<()> { + assert!(parse("tlq1qqw5ur50rnvcx33vmljjtnez3hrtl6n7vs44tdj2c9fmnxrrgzgwnhw6jtpn8cljkmlr8tgfw9hemrr5y8u2nu024hhak3tpdk") + .await + .is_ok()); + assert!(parse("liquidnetwork:tlq1qqw5ur50rnvcx33vmljjtnez3hrtl6n7vs44tdj2c9fmnxrrgzgwnhw6jtpn8cljkmlr8tgfw9hemrr5y8u2nu024hhak3tpdk") + .await + .is_ok()); + assert!(parse("wrong-net:tlq1qqw5ur50rnvcx33vmljjtnez3hrtl6n7vs44tdj2c9fmnxrrgzgwnhw6jtpn8cljkmlr8tgfw9hemrr5y8u2nu024hhak3tpdk").await.is_err()); + assert!(parse("liquidnetwork:testinvalidaddress").await.is_err()); + + let address: elements::Address = "tlq1qqw5ur50rnvcx33vmljjtnez3hrtl6n7vs44tdj2c9fmnxrrgzgwnhw6jtpn8cljkmlr8tgfw9hemrr5y8u2nu024hhak3tpdk".parse()?; + let amount_btc = 0.00001; // 1000 sats + let label = "label"; + let message = "this%20is%20a%20message"; + let asset_id = elements::issuance::AssetId::LIQUID_BTC.to_string(); + let output = parse(&format!( + "liquidnetwork:{}?amount={amount_btc}&assetid={asset_id}&label={label}&message={message}", + address + )) + .await?; + + if let InputType::LiquidAddress { + address: liquid_address_data, + } = output + { + assert_eq!(Network::Bitcoin, liquid_address_data.network); + assert_eq!(address.to_string(), liquid_address_data.address.to_string()); + assert_eq!( + Some((amount_btc * 100_000_000.0) as u64), + liquid_address_data.amount_sat + ); + assert_eq!(Some(label.to_string()), liquid_address_data.label); + assert_eq!( + Some(urlencoding::decode(message).unwrap().into_owned()), + liquid_address_data.message + ); + } else { + panic!("Invalid input type received"); + } + + Ok(()) + } + #[tokio::test] async fn test_bolt11() -> Result<()> { let bolt11 = "lnbc110n1p38q3gtpp5ypz09jrd8p993snjwnm68cph4ftwp22le34xd4r8ftspwshxhmnsdqqxqyjw5qcqpxsp5htlg8ydpywvsa7h3u4hdn77ehs4z4e844em0apjyvmqfkzqhhd2q9qgsqqqyssqszpxzxt9uuqzymr7zxcdccj5g69s8q7zzjs7sgxn9ejhnvdh6gqjcy22mss2yexunagm5r2gqczh8k24cwrqml3njskm548aruhpwssq9nvrvz"; diff --git a/libs/sdk-common/src/invoice.rs b/libs/sdk-common/src/invoice.rs index 04dac480f..2a61231f1 100644 --- a/libs/sdk-common/src/invoice.rs +++ b/libs/sdk-common/src/invoice.rs @@ -11,6 +11,9 @@ use serde::{Deserialize, Serialize}; use crate::prelude::*; +#[cfg(feature = "liquid")] +use crate::liquid::{bip21::DeserializeError, LiquidAddressData}; + pub type InvoiceResult = Result; #[derive(Debug, thiserror::Error)] @@ -293,6 +296,12 @@ pub fn parse_invoice(bolt11: &str) -> InvoiceResult { Ok(ln_invoice) } +#[cfg(feature = "liquid")] +// Covers BIP 21 URIs and simple onchain Liquid addresses (which are valid BIP 21 with the 'liquidnetwork:' prefix) +pub fn parse_liquid_address(input: &str) -> Result { + LiquidAddressData::from_addr(input).or_else(|_| input.parse::()) +} + #[cfg(test)] mod tests { use crate::invoice::*; diff --git a/libs/sdk-common/src/lib.rs b/libs/sdk-common/src/lib.rs index 60051c41a..2f26c6331 100644 --- a/libs/sdk-common/src/lib.rs +++ b/libs/sdk-common/src/lib.rs @@ -5,6 +5,8 @@ mod fiat; pub mod grpc; pub mod input_parser; pub mod invoice; +#[cfg(feature = "liquid")] +pub mod liquid; mod lnurl; mod model; mod utils; diff --git a/libs/sdk-common/src/liquid/bip21.rs b/libs/sdk-common/src/liquid/bip21.rs new file mode 100644 index 000000000..2b5e1a603 --- /dev/null +++ b/libs/sdk-common/src/liquid/bip21.rs @@ -0,0 +1,214 @@ +use elements::{ + address::{Address, AddressError, AddressParams}, + hashes::hex::HexToArrayError, + issuance::AssetId, +}; +use serde::Serialize; +use std::collections::HashMap; +use std::{num::ParseFloatError, str::FromStr, string::FromUtf8Error}; +use urlencoding::decode; + +use crate::prelude::{Network, URISerializationError}; + +#[derive(Debug, Clone, Serialize)] +pub struct LiquidAddressData { + pub address: String, + pub network: Network, + pub asset_id: Option, + pub amount_sat: Option, + pub label: Option, + pub message: Option, +} + +impl LiquidAddressData { + /// Converts the structure to a BIP21 URI while also + /// ensuring that all the fields are valid + pub fn to_uri(&self) -> Result { + self.address + .parse::
() + .map_err(|_| URISerializationError::InvalidAddress)?; + + let mut optional_keys = HashMap::new(); + + if let Some(amount_sat) = self.amount_sat { + let Some(asset_id) = self.asset_id.clone() else { + return Err(URISerializationError::AssetIdMissing); + }; + + let amount_btc = amount_sat as f64 / 100_000_000.0; + optional_keys.insert("amount", format!("{amount_btc:.8}")); + optional_keys.insert("assetid", asset_id); + } + + if let Some(message) = &self.message { + optional_keys.insert("message", urlencoding::encode(message).to_string()); + } + + if let Some(label) = &self.label { + optional_keys.insert("label", urlencoding::encode(label).to_string()); + } + + match optional_keys.is_empty() { + true => Ok(self.address.clone()), + false => { + let scheme = match self.network { + Network::Bitcoin => "liquidnetwork", + Network::Testnet => "liquidtestnet", + _ => { + return Err(URISerializationError::UnsupportedNetwork); + } + }; + + let suffix_str = optional_keys + .iter() + .map(|(key, value)| format!("{key}={value}")) + .collect::>() + .join("&"); + + Ok(format!("{scheme}:{}{suffix_str}", self.address)) + } + } + } +} + +#[derive(Debug)] +pub enum DeserializeError { + InvalidScheme, + MissingEquals, + UnknownParameter, + AssetNotProvided, + InvalidString(FromUtf8Error), + InvalidAmount(ParseFloatError), + InvalidAsset(HexToArrayError), + InvalidAddress(AddressError), +} + +impl LiquidAddressData { + fn deserialize_raw(string: &str) -> Result { + let (network, address_params) = string + .split_once(':') + .ok_or(DeserializeError::InvalidScheme)?; + + let network = match network { + "liquidnetwork" => Network::Bitcoin, + "liquidtestnet" => Network::Testnet, + _ => return Err(DeserializeError::InvalidScheme), + }; + + let mut address_params = address_params.split('?'); + + let address = address_params + .next() + .ok_or_else(|| { + DeserializeError::InvalidAddress(AddressError::InvalidAddress( + "No address provided".to_string(), + )) + })? + .parse::
() + .map_err(DeserializeError::InvalidAddress)? + .to_string(); + + let mut amount_sat = None; + let mut asset_id = None; + let mut label = None; + let mut message = None; + if let Some(params) = address_params.next() { + for pair in params.split('&') { + if let Some((key, val)) = pair.split_once('=') { + match key { + "amount" => { + let parsed_amount = val + .parse::() + .map_err(DeserializeError::InvalidAmount)?; + amount_sat = Some((parsed_amount * 100_000_000.0) as u64); + } + "assetid" => { + val.parse::() + .map_err(DeserializeError::InvalidAsset)?; + asset_id = Some(val.to_string()); + } + "label" => { + let decoded = decode(val) + .map_err(DeserializeError::InvalidString)? + .into_owned(); + label = Some(decoded) + } + "message" => { + let decoded = decode(val) + .map_err(DeserializeError::InvalidString)? + .into_owned(); + message = Some(decoded) + } + _ => {} + } + } else { + return Err(DeserializeError::MissingEquals); + } + } + } + + // "assetid" MUST be provided if "amount" is present + // See https://github.com/ElementsProject/elements/issues/805#issuecomment-576743532 + if amount_sat.is_some() && asset_id.is_none() { + return Err(DeserializeError::AssetNotProvided); + } + + Ok(Self { + address, + network, + asset_id, + amount_sat, + label, + message, + }) + } + + pub fn from_addr(address: &str) -> Result { + let elements_address = address + .parse::
() + .map_err(DeserializeError::InvalidAddress)?; + + let network = if elements_address.params.eq(&AddressParams::LIQUID) { + Network::Bitcoin + } else if elements_address.params.eq(&AddressParams::LIQUID_TESTNET) { + Network::Testnet + } else { + return Err(DeserializeError::InvalidAddress( + AddressError::InvalidAddress("The specified asset is not supported".to_string()), + )); + }; + + Ok(Self { + address: address.to_string(), + network, + asset_id: None, + amount_sat: None, + label: None, + message: None, + }) + } +} + +impl FromStr for LiquidAddressData { + type Err = DeserializeError; + + fn from_str(s: &str) -> Result { + Self::deserialize_raw(s) + } +} + +impl TryFrom<&str> for LiquidAddressData { + type Error = DeserializeError; + + fn try_from(s: &str) -> Result { + Self::deserialize_raw(s) + } +} + +impl TryFrom for LiquidAddressData { + type Error = DeserializeError; + + fn try_from(s: String) -> Result { + Self::deserialize_raw(&s) + } +} diff --git a/libs/sdk-common/src/liquid/mod.rs b/libs/sdk-common/src/liquid/mod.rs new file mode 100644 index 000000000..1db40b4b6 --- /dev/null +++ b/libs/sdk-common/src/liquid/mod.rs @@ -0,0 +1,2 @@ +pub mod bip21; +pub use bip21::*; diff --git a/libs/sdk-core/src/binding.rs b/libs/sdk-core/src/binding.rs index f22cd88df..45b77825a 100644 --- a/libs/sdk-core/src/binding.rs +++ b/libs/sdk-core/src/binding.rs @@ -172,7 +172,7 @@ pub enum _InputType { #[frb(mirror(BitcoinAddressData))] pub struct _BitcoinAddressData { pub address: String, - pub network: crate::prelude::Network, + pub network: Network, pub amount_sat: Option, pub label: Option, pub message: Option, diff --git a/libs/sdk-flutter/pubspec.lock b/libs/sdk-flutter/pubspec.lock index da1339e81..89162153e 100644 --- a/libs/sdk-flutter/pubspec.lock +++ b/libs/sdk-flutter/pubspec.lock @@ -271,10 +271,10 @@ packages: dependency: "direct main" description: name: freezed_annotation - sha256: c3fd9336eb55a38cc1bbd79ab17573113a8deccd0ecbbf926cca3c62803b5c2d + sha256: c2e2d632dd9b8a2b7751117abcfc2b4888ecfe181bd9fca7170d9ef02e595fe2 url: "https://pub.dev" source: hosted - version: "2.4.1" + version: "2.4.4" frontend_server_client: dependency: transitive description: @@ -295,18 +295,18 @@ packages: dependency: transitive description: name: graphs - sha256: aedc5a15e78fc65a6e23bcd927f24c64dd995062bcd1ca6eda65a3cff92a4d19 + sha256: "741bbf84165310a68ff28fe9e727332eef1407342fca52759cb21ad8177bb8d0" url: "https://pub.dev" source: hosted - version: "2.3.1" + version: "2.3.2" http: dependency: transitive description: name: http - sha256: "761a297c042deedc1ffbb156d6e2af13886bb305c2a343a4d972504cd67dd938" + sha256: b9c29a161230ee03d3ccf545097fccd9b87a5264228c5d348202e0f0c28f9010 url: "https://pub.dev" source: hosted - version: "1.2.1" + version: "1.2.2" http_multi_server: dependency: transitive description: @@ -351,18 +351,18 @@ packages: dependency: transitive description: name: leak_tracker - sha256: "7f0df31977cb2c0b88585095d168e689669a2cc9b97c309665e3386f3e9d341a" + sha256: "3f87a60e8c63aecc975dda1ceedbc8f24de75f09e4856ea27daf8958f2f0ce05" url: "https://pub.dev" source: hosted - version: "10.0.4" + version: "10.0.5" leak_tracker_flutter_testing: dependency: transitive description: name: leak_tracker_flutter_testing - sha256: "06e98f569d004c1315b991ded39924b21af84cf14cc94791b8aea337d25b57f8" + sha256: "932549fb305594d82d7183ecd9fa93463e9914e1b67cacc34bc40906594a1806" url: "https://pub.dev" source: hosted - version: "3.0.3" + version: "3.0.5" leak_tracker_testing: dependency: transitive description: @@ -399,18 +399,18 @@ packages: dependency: transitive description: name: material_color_utilities - sha256: "0e0a020085b65b6083975e499759762399b4475f766c21668c4ecca34ea74e5a" + sha256: f7142bb1154231d7ea5f96bc7bde4bda2a0945d2806bb11670e30b850d56bdec url: "https://pub.dev" source: hosted - version: "0.8.0" + version: "0.11.1" meta: dependency: transitive description: name: meta - sha256: "7687075e408b093f36e6bbf6c91878cc0d4cd10f409506f7bc996f68220b9136" + sha256: bdb68674043280c3428e9ec998512fb681678676b3c54e773629ffe74419f8c7 url: "https://pub.dev" source: hosted - version: "1.12.0" + version: "1.15.0" mime: dependency: transitive description: @@ -471,10 +471,10 @@ packages: dependency: transitive description: name: puppeteer - sha256: c45c51b4ad8d70acdffeb1cfb9d16b60a7eaab7bfef314dd5b02c3607269b556 + sha256: de3f921154e5d336b14cdc05b674ac3db5701a5338f3cb0042868a5146f16e67 url: "https://pub.dev" source: hosted - version: "3.11.0" + version: "3.12.0" quiver: dependency: transitive description: @@ -588,10 +588,10 @@ packages: dependency: transitive description: name: test_api - sha256: "9955ae474176f7ac8ee4e989dadfb411a58c30415bcfb648fa04b2b8a03afa7f" + sha256: "5b8a98dafc4d5c4c9c72d8b31ab2b23fc13422348d2997120294d3bac86b4ddb" url: "https://pub.dev" source: hosted - version: "0.7.0" + version: "0.7.2" timing: dependency: transitive description: @@ -620,10 +620,10 @@ packages: dependency: transitive description: name: uuid - sha256: "814e9e88f21a176ae1359149021870e87f7cddaf633ab678a5d2b0bff7fd1ba8" + sha256: "83d37c7ad7aaf9aa8e275490669535c8080377cfa7a7004c24dfac53afffaa90" url: "https://pub.dev" source: hosted - version: "4.4.0" + version: "4.4.2" vector_math: dependency: transitive description: @@ -636,10 +636,10 @@ packages: dependency: transitive description: name: vm_service - sha256: "3923c89304b715fb1eb6423f017651664a03bf5f4b29983627c4da791f74a4ec" + sha256: f652077d0bdf60abe4c1f6377448e8655008eef28f128bc023f7b5e8dfeb48fc url: "https://pub.dev" source: hosted - version: "14.2.1" + version: "14.2.4" watcher: dependency: transitive description: