From dbc4949b59e759868ace7f25c56dd68ab5892782 Mon Sep 17 00:00:00 2001 From: Binarybaron Date: Tue, 4 Nov 2025 19:09:05 +0100 Subject: [PATCH 001/113] fix react bug nullish check --- .../alert/SwapStatusAlert/SwapStatusAlert.tsx | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/src-gui/src/renderer/components/alert/SwapStatusAlert/SwapStatusAlert.tsx b/src-gui/src/renderer/components/alert/SwapStatusAlert/SwapStatusAlert.tsx index 25d4734ee1..cb29ef6ffe 100644 --- a/src-gui/src/renderer/components/alert/SwapStatusAlert/SwapStatusAlert.tsx +++ b/src-gui/src/renderer/components/alert/SwapStatusAlert/SwapStatusAlert.tsx @@ -175,6 +175,10 @@ export function StateAlert({ timelock: ExpiredTimelocks | null; isRunning: boolean; }) { + if (swap == null) { + return null; + } + switch (swap.state_name) { // This is the state where the swap is safe because the other party has redeemed the Bitcoin // It cannot be punished anymore @@ -243,7 +247,11 @@ export default function SwapStatusAlert({ swap: GetSwapInfoResponseExt | null; onlyShowIfUnusualAmountOfTimeHasPassed?: boolean; }) { +<<<<<<< HEAD const swapId = swap?.swap_id ?? null; +======= + const swapId = swap?.swap_id ?? ""; +>>>>>>> b539b31b (fix react bug nullish check) const timelock = useAppSelector(selectSwapTimelock(swapId)); const isRunning = useIsSpecificSwapRunning(swapId); @@ -269,7 +277,7 @@ export default function SwapStatusAlert({ return ( +<<<<<<< HEAD Swap {swap.swap_id} is not running +======= + Swap {swapId} is not running +>>>>>>> b539b31b (fix react bug nullish check) )} From f46f4ee0696a0b30221ab032a391b4c3e6860a4d Mon Sep 17 00:00:00 2001 From: Binarybaron Date: Tue, 4 Nov 2025 19:55:47 +0100 Subject: [PATCH 002/113] add tx_refund_amnesty --- swap-core/src/bitcoin.rs | 2 + swap-core/src/bitcoin/cancel.rs | 36 ++++++++ swap-core/src/bitcoin/refund.rs | 67 ++++++++++++-- swap-core/src/bitcoin/refund_amnesty.rs | 118 ++++++++++++++++++++++++ 4 files changed, 216 insertions(+), 7 deletions(-) create mode 100644 swap-core/src/bitcoin/refund_amnesty.rs diff --git a/swap-core/src/bitcoin.rs b/swap-core/src/bitcoin.rs index 113195cae4..e3bf6011d0 100644 --- a/swap-core/src/bitcoin.rs +++ b/swap-core/src/bitcoin.rs @@ -6,6 +6,7 @@ mod lock; mod punish; mod redeem; mod refund; +mod refund_amnesty; mod timelocks; pub use crate::bitcoin::cancel::TxCancel; @@ -14,6 +15,7 @@ pub use crate::bitcoin::lock::TxLock; pub use crate::bitcoin::punish::TxPunish; pub use crate::bitcoin::redeem::TxRedeem; pub use crate::bitcoin::refund::TxRefund; +pub use crate::bitcoin::refund_amnesty::TxRefundAmnesty; pub use crate::bitcoin::timelocks::{BlockHeight, ExpiredTimelocks}; pub use crate::bitcoin::timelocks::{CancelTimelock, PunishTimelock}; pub use ::bitcoin::amount::Amount; diff --git a/swap-core/src/bitcoin/cancel.rs b/swap-core/src/bitcoin/cancel.rs index a04d3898be..3dbb892c31 100644 --- a/swap-core/src/bitcoin/cancel.rs +++ b/swap-core/src/bitcoin/cancel.rs @@ -193,6 +193,42 @@ impl TxCancel { } } + pub fn build_refund_with_amnesty_transaction( + &self, + refund_address: &Address, + amnesty_descriptor: &Descriptor<::bitcoin::PublicKey>, + amnesty_amount: Amount, + spending_fee: Amount, + ) -> Transaction { + let previous_output = self.as_outpoint(); + + let tx_in = TxIn { + previous_output, + script_sig: Default::default(), + sequence: Sequence(0xFFFF_FFFF), + witness: Default::default(), + }; + + let refund_amount = self.amount() - amnesty_amount - spending_fee; + + let tx_out_refund = TxOut { + value: refund_amount, + script_pubkey: refund_address.script_pubkey(), + }; + + let tx_out_amnesty = TxOut { + value: amnesty_amount, + script_pubkey: amnesty_descriptor.script_pubkey(), + }; + + Transaction { + version: Version(2), + lock_time: PackedLockTime::from_height(0).expect("0 to be below lock time threshold"), + input: vec![tx_in], + output: vec![tx_out_refund, tx_out_amnesty], + } + } + pub fn weight() -> Weight { Weight::from_wu(596) } diff --git a/swap-core/src/bitcoin/refund.rs b/swap-core/src/bitcoin/refund.rs index 77fe2080ab..27bee44892 100644 --- a/swap-core/src/bitcoin/refund.rs +++ b/swap-core/src/bitcoin/refund.rs @@ -2,8 +2,8 @@ use crate::bitcoin; use crate::bitcoin::{ - verify_sig, Address, Amount, EmptyWitnessStack, NoInputs, NotThreeWitnesses, PublicKey, - TooManyInputs, Transaction, TxCancel, + build_shared_output_descriptor, verify_sig, Address, Amount, EmptyWitnessStack, NoInputs, + NotThreeWitnesses, PublicKey, TooManyInputs, Transaction, TxCancel, }; use ::bitcoin::sighash::SighashCache; use ::bitcoin::{secp256k1, ScriptBuf, Weight}; @@ -23,16 +23,32 @@ pub struct TxRefund { inner: Transaction, digest: Sighash, cancel_output_descriptor: Descriptor<::bitcoin::PublicKey>, + pub(in crate::bitcoin) amnesty_output_descriptor: Descriptor<::bitcoin::PublicKey>, watch_script: ScriptBuf, } impl TxRefund { - pub fn new(tx_cancel: &TxCancel, refund_address: &Address, spending_fee: Amount) -> Self { - let tx_refund = tx_cancel.build_spend_transaction(refund_address, None, spending_fee); + pub fn new( + tx_cancel: &TxCancel, + refund_address: &Address, + A: PublicKey, + B: PublicKey, + amnesty_amount: Amount, + spending_fee: Amount, + ) -> Result { + let amnesty_output_descriptor = build_shared_output_descriptor(A.0, B.0)?; + + let tx_refund = tx_cancel.build_refund_with_amnesty_transaction( + refund_address, + &amnesty_output_descriptor, + amnesty_amount, + spending_fee, + ); let digest = SighashCache::new(&tx_refund) .p2wsh_signature_hash( - 0, // Only one input: cancel transaction + // Only one input: cancel transaction + 0, &tx_cancel .output_descriptor .script_code() @@ -42,12 +58,13 @@ impl TxRefund { ) .expect("sighash"); - Self { + Ok(Self { inner: tx_refund, digest, cancel_output_descriptor: tx_cancel.output_descriptor.clone(), + amnesty_output_descriptor, watch_script: refund_address.script_pubkey(), - } + }) } pub fn txid(&self) -> Txid { @@ -58,6 +75,41 @@ impl TxRefund { self.digest } + pub fn amnesty_amount(&self) -> Amount { + self.inner.output[1].value + } + + pub fn amnesty_outpoint(&self) -> ::bitcoin::OutPoint { + ::bitcoin::OutPoint::new(self.txid(), 1) + } + + pub fn build_amnesty_spend_transaction( + &self, + refund_address: &Address, + spending_fee: Amount, + ) -> Transaction { + use ::bitcoin::{transaction::Version, locktime::absolute::LockTime as PackedLockTime, Sequence, TxIn, TxOut}; + + let tx_in = TxIn { + previous_output: self.amnesty_outpoint(), + script_sig: Default::default(), + sequence: Sequence(0xFFFF_FFFF), + witness: Default::default(), + }; + + let tx_out = TxOut { + value: self.amnesty_amount() - spending_fee, + script_pubkey: refund_address.script_pubkey(), + }; + + Transaction { + version: Version(2), + lock_time: PackedLockTime::from_height(0).expect("0 to be below lock time threshold"), + input: vec![tx_in], + output: vec![tx_out], + } + } + pub fn add_signatures( self, (A, sig_a): (PublicKey, Signature), @@ -77,6 +129,7 @@ impl TxRefund { let sig_a = secp256k1::ecdsa::Signature::from_compact(&sig_a.to_bytes())?; let sig_b = secp256k1::ecdsa::Signature::from_compact(&sig_b.to_bytes())?; + // The order in which these are inserted doesn't matter satisfier.insert( A, diff --git a/swap-core/src/bitcoin/refund_amnesty.rs b/swap-core/src/bitcoin/refund_amnesty.rs new file mode 100644 index 0000000000..ef4e2a6e87 --- /dev/null +++ b/swap-core/src/bitcoin/refund_amnesty.rs @@ -0,0 +1,118 @@ +use crate::bitcoin; +use crate::bitcoin::{ + verify_sig, Address, Amount, EmptyWitnessStack, NoInputs, NotThreeWitnesses, PublicKey, + TooManyInputs, Transaction, TxRefund, +}; +use ::bitcoin::sighash::SighashCache; +use ::bitcoin::{secp256k1, ScriptBuf, Weight}; +use ::bitcoin::{sighash::SegwitV0Sighash as Sighash, EcdsaSighashType, Txid}; +use anyhow::{bail, Context, Result}; +use bdk_wallet::miniscript::Descriptor; +use bitcoin_wallet::primitives::Watchable; +use curve25519_dalek::scalar::Scalar; +use ecdsa_fun::Signature; +use std::collections::HashMap; +use std::sync::Arc; + +use super::extract_ecdsa_sig; + +#[derive(Debug, Clone)] +pub struct TxRefundAmnesty { + inner: Transaction, + digest: Sighash, + amensty_output_descriptor: Descriptor<::bitcoin::PublicKey>, + watch_script: ScriptBuf, +} + +impl TxRefundAmnesty { + pub fn new(tx_refund: &TxRefund, refund_address: &Address, spending_fee: Amount) -> Self { + let tx_refund_amnesty = tx_refund.build_amnesty_spend_transaction(refund_address, spending_fee); + + let digest = SighashCache::new(&tx_refund_amnesty) + .p2wsh_signature_hash( + 0, // Only one input: amnesty box from tx_refund + &tx_refund + .amnesty_output_descriptor + .script_code() + .expect("scriptcode"), + tx_refund.amnesty_amount(), + EcdsaSighashType::All, + ) + .expect("sighash"); + + Self { + inner: tx_refund_amnesty, + digest, + amensty_output_descriptor: tx_refund.amnesty_output_descriptor.clone(), + watch_script: refund_address.script_pubkey(), + } + } + + pub fn txid(&self) -> Txid { + self.inner.compute_txid() + } + + pub fn digest(&self) -> Sighash { + self.digest + } + + pub fn add_signatures( + self, + (A, sig_a): (PublicKey, Signature), + (B, sig_b): (PublicKey, Signature), + ) -> Result { + let satisfier = { + let mut satisfier = HashMap::with_capacity(2); + + let A = ::bitcoin::PublicKey { + compressed: true, + inner: secp256k1::PublicKey::from_slice(&A.0.to_bytes())?, + }; + let B = ::bitcoin::PublicKey { + compressed: true, + inner: secp256k1::PublicKey::from_slice(&B.0.to_bytes())?, + }; + + let sig_a = secp256k1::ecdsa::Signature::from_compact(&sig_a.to_bytes())?; + let sig_b = secp256k1::ecdsa::Signature::from_compact(&sig_b.to_bytes())?; + + // The order in which these are inserted doesn't matter + satisfier.insert( + A, + ::bitcoin::ecdsa::Signature { + signature: sig_a, + sighash_type: EcdsaSighashType::All, + }, + ); + satisfier.insert( + B, + ::bitcoin::ecdsa::Signature { + signature: sig_b, + sighash_type: EcdsaSighashType::All, + }, + ); + + satisfier + }; + + let mut tx_refund = self.inner; + self.amensty_output_descriptor + .satisfy(&mut tx_refund.input[0], satisfier)?; + + Ok(tx_refund) + } + + pub fn weight() -> Weight { + Weight::from_wu(548) + } +} + +impl Watchable for TxRefundAmnesty { + fn id(&self) -> Txid { + self.txid() + } + + fn script(&self) -> ScriptBuf { + self.watch_script.clone() + } +} From 95b9e3212b1b7477a4bea58179715d3cb6609384 Mon Sep 17 00:00:00 2001 From: einliterflasche Date: Mon, 1 Dec 2025 18:28:01 +0100 Subject: [PATCH 003/113] move TxPartialRefund to partial_refund.rs and TxFullRefund to full_refund.rs and add backwards compatible BobRefundType for abstracting over refund signatures --- swap-core/src/bitcoin.rs | 5 +- swap-core/src/bitcoin/full_refund.rs | 177 ++++++++++++++++++ .../bitcoin/{refund.rs => partial_refund.rs} | 6 +- swap-core/src/bitcoin/refund_amnesty.rs | 13 +- swap-machine/src/alice/mod.rs | 8 +- swap-machine/src/bob/mod.rs | 104 +++++++++- swap/src/bitcoin/wallet.rs | 4 +- swap/src/protocol/bob/swap.rs | 4 +- 8 files changed, 292 insertions(+), 29 deletions(-) create mode 100644 swap-core/src/bitcoin/full_refund.rs rename swap-core/src/bitcoin/{refund.rs => partial_refund.rs} (98%) diff --git a/swap-core/src/bitcoin.rs b/swap-core/src/bitcoin.rs index e3bf6011d0..c74f437c48 100644 --- a/swap-core/src/bitcoin.rs +++ b/swap-core/src/bitcoin.rs @@ -5,7 +5,8 @@ mod early_refund; mod lock; mod punish; mod redeem; -mod refund; +mod full_refund; +mod partial_refund; mod refund_amnesty; mod timelocks; @@ -14,7 +15,7 @@ pub use crate::bitcoin::early_refund::TxEarlyRefund; pub use crate::bitcoin::lock::TxLock; pub use crate::bitcoin::punish::TxPunish; pub use crate::bitcoin::redeem::TxRedeem; -pub use crate::bitcoin::refund::TxRefund; +pub use crate::bitcoin::full_refund::TxFullRefund; pub use crate::bitcoin::refund_amnesty::TxRefundAmnesty; pub use crate::bitcoin::timelocks::{BlockHeight, ExpiredTimelocks}; pub use crate::bitcoin::timelocks::{CancelTimelock, PunishTimelock}; diff --git a/swap-core/src/bitcoin/full_refund.rs b/swap-core/src/bitcoin/full_refund.rs new file mode 100644 index 0000000000..5e36ce875a --- /dev/null +++ b/swap-core/src/bitcoin/full_refund.rs @@ -0,0 +1,177 @@ +#![allow(non_snake_case)] + +use crate::bitcoin; +use crate::bitcoin::{ + verify_sig, Address, Amount, EmptyWitnessStack, NoInputs, NotThreeWitnesses, PublicKey, + TooManyInputs, Transaction, TxCancel, +}; +use ::bitcoin::sighash::SighashCache; +use ::bitcoin::{secp256k1, ScriptBuf, Weight}; +use ::bitcoin::{sighash::SegwitV0Sighash as Sighash, EcdsaSighashType, Txid}; +use anyhow::{bail, Context, Result}; +use bdk_wallet::miniscript::Descriptor; +use bitcoin_wallet::primitives::Watchable; +use curve25519_dalek::scalar::Scalar; +use ecdsa_fun::Signature; +use std::collections::HashMap; +use std::sync::Arc; + +use super::extract_ecdsa_sig; + +/// A transaction that refunds 100% of the locked Bitcoin. +/// Previously to the partial refund protocol change, this was the only type of refund transaction. +/// +/// Now there also is the partial refund transaction, which refunds only a portion of the locked Bitcoin. +/// For more information, see [#675](https://github.com/eigenwallet/core/pull/675). +/// +/// The main reason this struct is still here is to 1) keep backwards compatibility in the database +/// and 2) avoid having to pay fees for 2 Bitcoin transactions when we want to get a full refund anyway. +#[derive(Debug, Clone)] +pub struct TxFullRefund { + inner: Transaction, + digest: Sighash, + cancel_output_descriptor: Descriptor<::bitcoin::PublicKey>, + watch_script: ScriptBuf, +} + +impl TxFullRefund { + pub fn new(tx_cancel: &TxCancel, refund_address: &Address, spending_fee: Amount) -> Self { + let tx_refund = tx_cancel.build_spend_transaction(refund_address, None, spending_fee); + + let digest = SighashCache::new(&tx_refund) + .p2wsh_signature_hash( + 0, // Only one input: cancel transaction + &tx_cancel + .output_descriptor + .script_code() + .expect("scriptcode"), + tx_cancel.amount(), + EcdsaSighashType::All, + ) + .expect("sighash"); + + Self { + inner: tx_refund, + digest, + cancel_output_descriptor: tx_cancel.output_descriptor.clone(), + watch_script: refund_address.script_pubkey(), + } + } + + pub fn txid(&self) -> Txid { + self.inner.compute_txid() + } + + pub fn digest(&self) -> Sighash { + self.digest + } + + pub fn add_signatures( + self, + (A, sig_a): (PublicKey, Signature), + (B, sig_b): (PublicKey, Signature), + ) -> Result { + let satisfier = { + let mut satisfier = HashMap::with_capacity(2); + + let A = ::bitcoin::PublicKey { + compressed: true, + inner: secp256k1::PublicKey::from_slice(&A.0.to_bytes())?, + }; + let B = ::bitcoin::PublicKey { + compressed: true, + inner: secp256k1::PublicKey::from_slice(&B.0.to_bytes())?, + }; + + let sig_a = secp256k1::ecdsa::Signature::from_compact(&sig_a.to_bytes())?; + let sig_b = secp256k1::ecdsa::Signature::from_compact(&sig_b.to_bytes())?; + // The order in which these are inserted doesn't matter + satisfier.insert( + A, + ::bitcoin::ecdsa::Signature { + signature: sig_a, + sighash_type: EcdsaSighashType::All, + }, + ); + satisfier.insert( + B, + ::bitcoin::ecdsa::Signature { + signature: sig_b, + sighash_type: EcdsaSighashType::All, + }, + ); + + satisfier + }; + + let mut tx_refund = self.inner; + self.cancel_output_descriptor + .satisfy(&mut tx_refund.input[0], satisfier)?; + + Ok(tx_refund) + } + + pub fn extract_monero_private_key( + &self, + signed_refund_tx: Arc, + s_a: Scalar, + a: bitcoin::SecretKey, + S_b_bitcoin: bitcoin::PublicKey, + ) -> Result { + let tx_refund_sig = self + .extract_signature_by_key(signed_refund_tx, a.public()) + .context("Failed to extract signature from Bitcoin refund tx")?; + let tx_refund_encsig = a.encsign(S_b_bitcoin, self.digest()); + + let s_b = bitcoin::recover(S_b_bitcoin, tx_refund_sig, tx_refund_encsig) + .context("Failed to recover Monero secret key from Bitcoin signature")?; + + let s_b = crate::monero::primitives::private_key_from_secp256k1_scalar(s_b.into()); + + let spend_key = s_a + s_b; + + Ok(spend_key) + } + + fn extract_signature_by_key( + &self, + candidate_transaction: Arc, + B: PublicKey, + ) -> Result { + let input = match candidate_transaction.input.as_slice() { + [input] => input, + [] => bail!(NoInputs), + inputs => bail!(TooManyInputs(inputs.len())), + }; + + let sigs = match input.witness.to_vec().as_slice() { + [sig_1, sig_2, _script] => [sig_1, sig_2] + .into_iter() + .map(|sig| extract_ecdsa_sig(sig)) + .collect::, _>>(), + [] => bail!(EmptyWitnessStack), + witnesses => bail!(NotThreeWitnesses(witnesses.len())), + }?; + + let sig = sigs + .into_iter() + .find(|sig| verify_sig(&B, &self.digest(), sig).is_ok()) + .context("Neither signature on witness stack verifies against B")?; + + Ok(sig) + } + + pub fn weight() -> Weight { + Weight::from_wu(548) + } +} + +impl Watchable for TxFullRefund { + fn id(&self) -> Txid { + self.txid() + } + + fn script(&self) -> ScriptBuf { + self.watch_script.clone() + } +} diff --git a/swap-core/src/bitcoin/refund.rs b/swap-core/src/bitcoin/partial_refund.rs similarity index 98% rename from swap-core/src/bitcoin/refund.rs rename to swap-core/src/bitcoin/partial_refund.rs index 27bee44892..50d93e9554 100644 --- a/swap-core/src/bitcoin/refund.rs +++ b/swap-core/src/bitcoin/partial_refund.rs @@ -19,7 +19,7 @@ use std::sync::Arc; use super::extract_ecdsa_sig; #[derive(Debug, Clone)] -pub struct TxRefund { +pub struct TxPartialRefund { inner: Transaction, digest: Sighash, cancel_output_descriptor: Descriptor<::bitcoin::PublicKey>, @@ -27,7 +27,7 @@ pub struct TxRefund { watch_script: ScriptBuf, } -impl TxRefund { +impl TxPartialRefund { pub fn new( tx_cancel: &TxCancel, refund_address: &Address, @@ -211,7 +211,7 @@ impl TxRefund { } } -impl Watchable for TxRefund { +impl Watchable for TxPartialRefund { fn id(&self) -> Txid { self.txid() } diff --git a/swap-core/src/bitcoin/refund_amnesty.rs b/swap-core/src/bitcoin/refund_amnesty.rs index ef4e2a6e87..3ffc3c04ea 100644 --- a/swap-core/src/bitcoin/refund_amnesty.rs +++ b/swap-core/src/bitcoin/refund_amnesty.rs @@ -1,20 +1,15 @@ -use crate::bitcoin; +use crate::bitcoin::partial_refund::TxPartialRefund; use crate::bitcoin::{ - verify_sig, Address, Amount, EmptyWitnessStack, NoInputs, NotThreeWitnesses, PublicKey, - TooManyInputs, Transaction, TxRefund, + Address, Amount, PublicKey, Transaction, }; use ::bitcoin::sighash::SighashCache; use ::bitcoin::{secp256k1, ScriptBuf, Weight}; use ::bitcoin::{sighash::SegwitV0Sighash as Sighash, EcdsaSighashType, Txid}; -use anyhow::{bail, Context, Result}; +use anyhow::Result; use bdk_wallet::miniscript::Descriptor; use bitcoin_wallet::primitives::Watchable; -use curve25519_dalek::scalar::Scalar; use ecdsa_fun::Signature; use std::collections::HashMap; -use std::sync::Arc; - -use super::extract_ecdsa_sig; #[derive(Debug, Clone)] pub struct TxRefundAmnesty { @@ -25,7 +20,7 @@ pub struct TxRefundAmnesty { } impl TxRefundAmnesty { - pub fn new(tx_refund: &TxRefund, refund_address: &Address, spending_fee: Amount) -> Self { + pub fn new(tx_refund: &TxPartialRefund, refund_address: &Address, spending_fee: Amount) -> Self { let tx_refund_amnesty = tx_refund.build_amnesty_spend_transaction(refund_address, spending_fee); let digest = SighashCache::new(&tx_refund_amnesty) diff --git a/swap-machine/src/alice/mod.rs b/swap-machine/src/alice/mod.rs index 72390a6eda..ebabb79c07 100644 --- a/swap-machine/src/alice/mod.rs +++ b/swap-machine/src/alice/mod.rs @@ -9,7 +9,7 @@ use std::fmt; use std::sync::Arc; use swap_core::bitcoin::{ current_epoch, CancelTimelock, ExpiredTimelocks, PunishTimelock, Transaction, TxCancel, - TxEarlyRefund, TxPunish, TxRedeem, TxRefund, Txid, + TxEarlyRefund, TxPunish, TxRedeem, TxFullRefund, Txid, }; use swap_core::monero; use swap_core::monero::primitives::{BlockHeight, TransferProof, TransferRequest, WatchRequest}; @@ -348,7 +348,7 @@ impl State2 { .expect("valid cancel tx"); let tx_refund = - swap_core::bitcoin::TxRefund::new(&tx_cancel, &self.refund_address, self.tx_refund_fee); + swap_core::bitcoin::TxFullRefund::new(&tx_cancel, &self.refund_address, self.tx_refund_fee); // Alice encsigns the refund transaction(bitcoin) digest with Bob's monero // pubkey(S_b). The refund transaction spends the output of // tx_lock_bitcoin to Bob's refund address. @@ -535,8 +535,8 @@ impl State3 { .expect("valid cancel tx") } - pub fn tx_refund(&self) -> TxRefund { - swap_core::bitcoin::TxRefund::new( + pub fn tx_refund(&self) -> TxFullRefund { + swap_core::bitcoin::TxFullRefund::new( &self.tx_cancel(), &self.refund_address, self.tx_refund_fee, diff --git a/swap-machine/src/bob/mod.rs b/swap-machine/src/bob/mod.rs index 28c678801f..dbf725ea86 100644 --- a/swap-machine/src/bob/mod.rs +++ b/swap-machine/src/bob/mod.rs @@ -66,6 +66,30 @@ pub enum BobState { SafelyAborted, } +/// !IMPORTANT: This enum must be #[untagged] and maintain the field names in order to be backwards compatible +/// with the database. +/// Changing any of that is a breaking change +#[derive(Debug, Clone, PartialEq, Deserialize, Serialize)] +#[serde(untagged)] +pub enum BobRefundType { + /// Alice has only signed the partial refund transaction (most cases). + Partial { + tx_partial_refund_encsig: bitcoin::EncryptedSignature, + }, + /// Alice has signed both the partial and full refund transactions. + Full { + tx_partial_refund_encsig: bitcoin::EncryptedSignature, + tx_refund_encsig: bitcoin::EncryptedSignature, + }, + /// Alice has only signed the full refund transaction. + /// This is only used to maintain backwards compatibility for older swaps + /// from before the partial refund protocol change. + /// See [#675](https://github.com/eigenwallet/core/pull/675). + Legacy { + tx_refund_encsig: bitcoin::EncryptedSignature, + } +} + impl fmt::Display for BobState { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match self { @@ -311,7 +335,7 @@ impl State1 { )?; let tx_refund = - bitcoin::TxRefund::new(&tx_cancel, &self.refund_address, self.tx_refund_fee); + bitcoin::TxFullRefund::new(&tx_cancel, &self.refund_address, self.tx_refund_fee); bitcoin::verify_sig(&self.A, &tx_cancel.digest(), &msg.tx_cancel_sig)?; bitcoin::verify_encsig( @@ -366,7 +390,14 @@ pub struct State2 { punish_address: bitcoin::Address, pub tx_lock: bitcoin::TxLock, tx_cancel_sig_a: Signature, - tx_refund_encsig: bitcoin::EncryptedSignature, + /// This field was changed in [#675](https://github.com/eigenwallet/core/pull/675). + /// It boils down to the same json except that it now may also contain a partial refund signature. + #[serde(flatten)] + bob_refund_type: BobRefundType, + /// This field was added in [#675](https://github.com/eigenwallet/core/pull/675). + /// It allows Bob to retrieve the refund fee introduced in the PR. + /// This signature is voluntarily revealed by alice. + tx_refund_amnesty_sig: Option, min_monero_confirmations: u64, #[serde(with = "::bitcoin::amount::serde::as_sat")] tx_redeem_fee: bitcoin::Amount, @@ -456,7 +487,14 @@ pub struct State3 { redeem_address: bitcoin::Address, pub tx_lock: bitcoin::TxLock, tx_cancel_sig_a: Signature, - tx_refund_encsig: bitcoin::EncryptedSignature, + /// This field was changed in [#675](https://github.com/eigenwallet/core/pull/675). + /// It boils down to the same json except that it now may also contain a partial refund signature. + #[serde(flatten)] + bob_refund_type: BobRefundType, + /// This field was added in [#675](https://github.com/eigenwallet/core/pull/675). + /// It allows Bob to retrieve the refund fee introduced in the PR. + /// This signature is voluntarily revealed by alice. + tx_refund_amnesty_sig: Option, min_monero_confirmations: u64, #[serde(with = "::bitcoin::amount::serde::as_sat")] tx_redeem_fee: bitcoin::Amount, @@ -593,7 +631,14 @@ pub struct State4 { redeem_address: bitcoin::Address, pub tx_lock: bitcoin::TxLock, tx_cancel_sig_a: Signature, - tx_refund_encsig: bitcoin::EncryptedSignature, + /// This field was changed in [#675](https://github.com/eigenwallet/core/pull/675). + /// It boils down to the same json except that it now may also contain a partial refund signature. + #[serde(flatten)] + bob_refund_type: BobRefundType, + /// This field was added in [#675](https://github.com/eigenwallet/core/pull/675). + /// It allows Bob to retrieve the refund fee introduced in the PR. + /// This signature is voluntarily revealed by alice. + tx_refund_amnesty_sig: Option, monero_wallet_restore_blockheight: BlockHeight, lock_transfer_proof: TransferProof, #[serde(with = "::bitcoin::amount::serde::as_sat")] @@ -767,7 +812,14 @@ pub struct State6 { refund_address: bitcoin::Address, pub tx_lock: bitcoin::TxLock, tx_cancel_sig_a: Signature, - tx_refund_encsig: bitcoin::EncryptedSignature, + /// This field was changed in [#675](https://github.com/eigenwallet/core/pull/675). + /// It boils down to the same json except that it now may also contain a partial refund signature. + #[serde(flatten)] + bob_refund_type: BobRefundType, + /// This field was added in [#675](https://github.com/eigenwallet/core/pull/675). + /// It allows Bob to retrieve the refund fee introduced in the PR. + /// This signature is voluntarily revealed by alice. + tx_refund_amnesty_sig: Option, #[serde(with = "::bitcoin::amount::serde::as_sat")] pub tx_refund_fee: bitcoin::Amount, #[serde(with = "::bitcoin::amount::serde::as_sat")] @@ -847,11 +899,11 @@ impl State6 { Ok(signed_tx_refund_txid) } - pub fn construct_tx_refund(&self) -> Result { + pub fn construct_tx_refund(&self) -> Result { let tx_cancel = self.construct_tx_cancel()?; let tx_refund = - bitcoin::TxRefund::new(&tx_cancel, &self.refund_address, self.tx_refund_fee); + bitcoin::TxFullRefund::new(&tx_cancel, &self.refund_address, self.tx_refund_fee); Ok(tx_refund) } @@ -911,3 +963,41 @@ impl State6 { Ok(tx) } } + +impl BobRefundType { + pub fn legacy_full_refund(full_refund_encsig: bitcoin::EncryptedSignature) -> Self { + Self::Legacy { tx_refund_encsig: full_refund_encsig } + } + + pub fn partial_refund(partial_refund_encsig: bitcoin::EncryptedSignature) -> Self { + Self::Partial { tx_partial_refund_encsig: partial_refund_encsig } + } + + pub fn full_refund(partial_refund_encsig: bitcoin::EncryptedSignature, full_refund_encsig: bitcoin::EncryptedSignature) -> Self { + Self::Full { tx_partial_refund_encsig: partial_refund_encsig, tx_refund_encsig: full_refund_encsig } + } + + pub fn upgrade_with_full_refund(&self, full_refund_encsig: bitcoin::EncryptedSignature) -> Self { + match self { + BobRefundType::Partial { tx_partial_refund_encsig } => BobRefundType::Full { tx_partial_refund_encsig: tx_partial_refund_encsig.clone(), tx_refund_encsig: full_refund_encsig }, + BobRefundType::Full { tx_partial_refund_encsig, .. } => BobRefundType::Full { tx_partial_refund_encsig: tx_partial_refund_encsig.clone(), tx_refund_encsig: full_refund_encsig }, + BobRefundType::Legacy { tx_refund_encsig } => BobRefundType::Full { tx_partial_refund_encsig: tx_refund_encsig.clone(), tx_refund_encsig: full_refund_encsig }, + } + } + + pub fn tx_full_refund_encsig(&self) -> Option { + match self { + BobRefundType::Partial { .. } => None, + BobRefundType::Full { tx_refund_encsig, .. } => Some(tx_refund_encsig.clone()), + BobRefundType::Legacy { tx_refund_encsig } => Some(tx_refund_encsig.clone()), + } + } + + pub fn tx_partial_refund_encsig(&self) -> Option { + match self { + BobRefundType::Partial { tx_partial_refund_encsig } => Some(tx_partial_refund_encsig.clone()), + BobRefundType::Full { tx_partial_refund_encsig, .. } => Some(tx_partial_refund_encsig.clone()), + BobRefundType::Legacy { .. } => None, + } + } +} diff --git a/swap/src/bitcoin/wallet.rs b/swap/src/bitcoin/wallet.rs index c1148fc578..a4a97d9595 100644 --- a/swap/src/bitcoin/wallet.rs +++ b/swap/src/bitcoin/wallet.rs @@ -3810,7 +3810,7 @@ mod swap_core_bitcoin_tests { assert_weight(redeem_transaction, TxRedeem::weight().to_wu(), "TxRedeem"); assert_weight(cancel_transaction, TxCancel::weight().to_wu(), "TxCancel"); assert_weight(punish_transaction, TxPunish::weight().to_wu(), "TxPunish"); - assert_weight(refund_transaction, TxRefund::weight().to_wu(), "TxRefund"); + assert_weight(refund_transaction, TxFullRefund::weight().to_wu(), "TxRefund"); // Test TxEarlyRefund transaction let early_refund_transaction = alice_state3 @@ -3932,7 +3932,7 @@ mod swap_core_bitcoin_tests { // It should be the same as TxRedeem and TxRefund weights since they have similar structure assert_eq!(TxEarlyRefund::weight() as u64, TxRedeem::weight().to_wu()); - assert_eq!(TxEarlyRefund::weight() as u64, TxRefund::weight().to_wu()); + assert_eq!(TxEarlyRefund::weight() as u64, TxFullRefund::weight().to_wu()); } // Weights fluctuate because of the length of the signatures. Valid ecdsa diff --git a/swap/src/protocol/bob/swap.rs b/swap/src/protocol/bob/swap.rs index 46a86c9d69..ccfbea4933 100644 --- a/swap/src/protocol/bob/swap.rs +++ b/swap/src/protocol/bob/swap.rs @@ -11,7 +11,7 @@ use crate::protocol::{bob, Database}; use anyhow::{Context as AnyContext, Result}; use std::sync::Arc; use std::time::Duration; -use swap_core::bitcoin::{ExpiredTimelocks, TxCancel, TxRefund}; +use swap_core::bitcoin::{ExpiredTimelocks, TxCancel, TxFullRefund}; use swap_core::monero::TxHash; use swap_env::env; use swap_machine::bob::State5; @@ -107,7 +107,7 @@ async fn next_state( tx_lock_fee, } => { let tx_refund_fee = bitcoin_wallet - .estimate_fee(TxRefund::weight(), Some(btc_amount)) + .estimate_fee(TxFullRefund::weight(), Some(btc_amount)) .await?; let tx_cancel_fee = bitcoin_wallet .estimate_fee(TxCancel::weight(), Some(btc_amount)) From 2d8f8438bc00e71ad42d3f7ced2e4b4bcf49104d Mon Sep 17 00:00:00 2001 From: einliterflasche Date: Tue, 2 Dec 2025 11:06:37 +0100 Subject: [PATCH 004/113] remove unnecessary functions on BobRefundType --- swap-core/src/bitcoin.rs | 1 + swap-machine/src/bob/mod.rs | 19 ++----------------- 2 files changed, 3 insertions(+), 17 deletions(-) diff --git a/swap-core/src/bitcoin.rs b/swap-core/src/bitcoin.rs index c74f437c48..ab9d102c89 100644 --- a/swap-core/src/bitcoin.rs +++ b/swap-core/src/bitcoin.rs @@ -12,6 +12,7 @@ mod timelocks; pub use crate::bitcoin::cancel::TxCancel; pub use crate::bitcoin::early_refund::TxEarlyRefund; +pub use crate::bitcoin::partial_refund::TxPartialRefund; pub use crate::bitcoin::lock::TxLock; pub use crate::bitcoin::punish::TxPunish; pub use crate::bitcoin::redeem::TxRedeem; diff --git a/swap-machine/src/bob/mod.rs b/swap-machine/src/bob/mod.rs index dbf725ea86..17d0ea33a4 100644 --- a/swap-machine/src/bob/mod.rs +++ b/swap-machine/src/bob/mod.rs @@ -334,6 +334,7 @@ impl State1 { self.tx_cancel_fee, )?; + // TODO: send and receive and verify _partial_ refund signature instead of full refund signature. let tx_refund = bitcoin::TxFullRefund::new(&tx_cancel, &self.refund_address, self.tx_refund_fee); @@ -965,26 +966,10 @@ impl State6 { } impl BobRefundType { - pub fn legacy_full_refund(full_refund_encsig: bitcoin::EncryptedSignature) -> Self { - Self::Legacy { tx_refund_encsig: full_refund_encsig } - } - - pub fn partial_refund(partial_refund_encsig: bitcoin::EncryptedSignature) -> Self { + pub fn from_partial_refund_sig(partial_refund_encsig: bitcoin::EncryptedSignature) -> Self { Self::Partial { tx_partial_refund_encsig: partial_refund_encsig } } - pub fn full_refund(partial_refund_encsig: bitcoin::EncryptedSignature, full_refund_encsig: bitcoin::EncryptedSignature) -> Self { - Self::Full { tx_partial_refund_encsig: partial_refund_encsig, tx_refund_encsig: full_refund_encsig } - } - - pub fn upgrade_with_full_refund(&self, full_refund_encsig: bitcoin::EncryptedSignature) -> Self { - match self { - BobRefundType::Partial { tx_partial_refund_encsig } => BobRefundType::Full { tx_partial_refund_encsig: tx_partial_refund_encsig.clone(), tx_refund_encsig: full_refund_encsig }, - BobRefundType::Full { tx_partial_refund_encsig, .. } => BobRefundType::Full { tx_partial_refund_encsig: tx_partial_refund_encsig.clone(), tx_refund_encsig: full_refund_encsig }, - BobRefundType::Legacy { tx_refund_encsig } => BobRefundType::Full { tx_partial_refund_encsig: tx_refund_encsig.clone(), tx_refund_encsig: full_refund_encsig }, - } - } - pub fn tx_full_refund_encsig(&self) -> Option { match self { BobRefundType::Partial { .. } => None, From 6cdf50721b6ec711211295d3edd4c9663164ea87 Mon Sep 17 00:00:00 2001 From: einliterflasche Date: Tue, 2 Dec 2025 11:27:47 +0100 Subject: [PATCH 005/113] move run_swap_setup of alice into seperate function --- swap-p2p/src/protocols/swap_setup/alice.rs | 293 +++++++++++---------- 1 file changed, 157 insertions(+), 136 deletions(-) diff --git a/swap-p2p/src/protocols/swap_setup/alice.rs b/swap-p2p/src/protocols/swap_setup/alice.rs index 42a8bf4cff..7123aa084f 100644 --- a/swap-p2p/src/protocols/swap_setup/alice.rs +++ b/swap-p2p/src/protocols/swap_setup/alice.rs @@ -297,7 +297,7 @@ where ConnectionEvent::FullyNegotiatedInbound(substream) => { self.keep_alive_until = None; - let mut substream = substream.protocol; + let substream = substream.protocol; let (sender, receiver) = bmrng::channel_with_timeout::< bitcoin::Amount, @@ -307,145 +307,22 @@ where let resume_only = self.resume_only; let min_buy = self.min_buy; let max_buy = self.max_buy; - let latest_rate = self.latest_rate.latest_rate(); + let latest_rate = self.latest_rate.latest_rate().map_err(anyhow::Error::from); let env_config = self.env_config; // We wrap the entire handshake in a timeout future - let protocol = tokio::time::timeout(self.negotiation_timeout, async move { - let request = swap_setup::read_cbor_message::(&mut substream) - .await - .context("Failed to read spot price request")?; - - let wallet_snapshot = sender - .send_receive(request.btc) - .await - .context("Failed to receive wallet snapshot")?; - - // wrap all of these into another future so we can `return` from all the - // different blocks - let validate = async { - if resume_only { - return Err(Error::ResumeOnlyMode); - }; - - let blockchain_network = BlockchainNetwork { - bitcoin: env_config.bitcoin_network, - monero: env_config.monero_network, - }; - - if request.blockchain_network != blockchain_network { - return Err(Error::BlockchainNetworkMismatch { - cli: request.blockchain_network, - asb: blockchain_network, - }); - } - - let btc = request.btc; - - if btc < min_buy { - return Err(Error::AmountBelowMinimum { - min: min_buy, - buy: btc, - }); - } - - if btc > max_buy { - return Err(Error::AmountAboveMaximum { - max: max_buy, - buy: btc, - }); - } - - let rate = - latest_rate.map_err(|e| Error::LatestRateFetchFailed(Box::new(e)))?; - let xmr = rate - .sell_quote(btc) - .map_err(Error::SellQuoteCalculationFailed)?; - - let unlocked = wallet_snapshot.unlocked_balance; - - let needed_balance = xmr + wallet_snapshot.lock_fee.into(); - if unlocked.as_piconero() < needed_balance.as_pico() { - tracing::warn!( - unlocked_balance = %unlocked, - needed_balance = %needed_balance, - "Rejecting swap, unlocked balance too low" - ); - return Err(Error::BalanceTooLow { - balance: wallet_snapshot.unlocked_balance, - buy: btc, - }); - } - - Ok(xmr) - }; - - let result = validate.await; - - let converted_result = match result { - Ok(xmr) => Ok(xmr.into()), - Err(e) => Err(e), - }; - swap_setup::write_cbor_message( - &mut substream, - SpotPriceResponse::from_result_ref(&converted_result), - ) - .await - .context("Failed to write spot price response")?; - - let xmr = converted_result?; - - let state0 = State0::new( - request.btc, - xmr, + let protocol = tokio::time::timeout( + self.negotiation_timeout, + run_swap_setup( + substream, + sender, + resume_only, env_config, - wallet_snapshot.redeem_address, - wallet_snapshot.punish_address, - wallet_snapshot.redeem_fee, - wallet_snapshot.punish_fee, - &mut rand::thread_rng(), - ); - - let message0 = swap_setup::read_cbor_message::(&mut substream) - .await - .context("Failed to read message0")?; - let (swap_id, state1) = state0 - .receive(message0) - .context("Failed to transition state0 -> state1 using message0")?; - - swap_setup::write_cbor_message(&mut substream, state1.next_message()) - .await - .context("Failed to send message1")?; - - let message2 = swap_setup::read_cbor_message::(&mut substream) - .await - .context("Failed to read message2")?; - let state2 = state1 - .receive(message2) - .context("Failed to transition state1 -> state2 using message2")?; - - swap_setup::write_cbor_message(&mut substream, state2.next_message()) - .await - .context("Failed to send message3")?; - - let message4 = swap_setup::read_cbor_message::(&mut substream) - .await - .context("Failed to read message4")?; - let state3 = state2 - .receive(message4) - .context("Failed to transition state2 -> state3 using message4")?; - - substream - .flush() - .await - .context("Failed to flush substream after all messages were sent")?; - substream - .close() - .await - .context("Failed to close substream after all messages were sent")?; - - Ok((swap_id, state3)) - }); + min_buy, + max_buy, + latest_rate, + ), + ); let max_seconds = self.negotiation_timeout.as_secs(); self.inbound_stream = OptionFuture::from(Some( @@ -572,3 +449,147 @@ impl Error { } } } + +async fn run_swap_setup( + mut substream: libp2p::swarm::Stream, + sender: bmrng::RequestSender, + resume_only: bool, + env_config: env::Config, + min_buy: bitcoin::Amount, + max_buy: bitcoin::Amount, + latest_rate: Result, +) -> Result<(Uuid, State3)> { + let request = swap_setup::read_cbor_message::(&mut substream) + .await + .context("Failed to read spot price request")?; + + let wallet_snapshot = sender + .send_receive(request.btc) + .await + .context("Failed to receive wallet snapshot")?; + + // wrap all of these into another future so we can `return` from all the + // different blocks + let validate = async { + if resume_only { + return Err(Error::ResumeOnlyMode); + }; + + let blockchain_network = BlockchainNetwork { + bitcoin: env_config.bitcoin_network, + monero: env_config.monero_network, + }; + + if request.blockchain_network != blockchain_network { + return Err(Error::BlockchainNetworkMismatch { + cli: request.blockchain_network, + asb: blockchain_network, + }); + } + + let btc = request.btc; + + if btc < min_buy { + return Err(Error::AmountBelowMinimum { + min: min_buy, + buy: btc, + }); + } + + if btc > max_buy { + return Err(Error::AmountAboveMaximum { + max: max_buy, + buy: btc, + }); + } + + let rate = + latest_rate.map_err(|e| Error::LatestRateFetchFailed(Box::new(e)))?; + let xmr = rate + .sell_quote(btc) + .map_err(Error::SellQuoteCalculationFailed)?; + + let unlocked = wallet_snapshot.unlocked_balance; + + let needed_balance = xmr + wallet_snapshot.lock_fee.into(); + if unlocked.as_piconero() < needed_balance.as_pico() { + tracing::warn!( + unlocked_balance = %unlocked, + needed_balance = %needed_balance, + "Rejecting swap, unlocked balance too low" + ); + return Err(Error::BalanceTooLow { + balance: wallet_snapshot.unlocked_balance, + buy: btc, + }); + } + + Ok(xmr) + }; + + let result = validate.await; + + let converted_result = match result { + Ok(xmr) => Ok(xmr.into()), + Err(e) => Err(e), + }; + swap_setup::write_cbor_message( + &mut substream, + SpotPriceResponse::from_result_ref(&converted_result), + ) + .await + .context("Failed to write spot price response")?; + + let xmr = converted_result?; + + let state0 = State0::new( + request.btc, + xmr, + env_config, + wallet_snapshot.redeem_address, + wallet_snapshot.punish_address, + wallet_snapshot.redeem_fee, + wallet_snapshot.punish_fee, + &mut rand::thread_rng(), + ); + + let message0 = swap_setup::read_cbor_message::(&mut substream) + .await + .context("Failed to read message0")?; + let (swap_id, state1) = state0 + .receive(message0) + .context("Failed to transition state0 -> state1 using message0")?; + + swap_setup::write_cbor_message(&mut substream, state1.next_message()) + .await + .context("Failed to send message1")?; + + let message2 = swap_setup::read_cbor_message::(&mut substream) + .await + .context("Failed to read message2")?; + let state2 = state1 + .receive(message2) + .context("Failed to transition state1 -> state2 using message2")?; + + swap_setup::write_cbor_message(&mut substream, state2.next_message()) + .await + .context("Failed to send message3")?; + + let message4 = swap_setup::read_cbor_message::(&mut substream) + .await + .context("Failed to read message4")?; + let state3 = state2 + .receive(message4) + .context("Failed to transition state2 -> state3 using message4")?; + + substream + .flush() + .await + .context("Failed to flush substream after all messages were sent")?; + substream + .close() + .await + .context("Failed to close substream after all messages were sent")?; + + Ok((swap_id, state3)) +} From 208f4851f8f0b09da828f88c3574380736d49333 Mon Sep 17 00:00:00 2001 From: einliterflasche Date: Tue, 2 Dec 2025 11:39:06 +0100 Subject: [PATCH 006/113] change tx_refund_encsig to tx_partial_refund_encsig in Message3 --- swap-machine/src/alice/mod.rs | 2 +- swap-machine/src/bob/mod.rs | 6 +++--- swap-machine/src/common/mod.rs | 2 +- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/swap-machine/src/alice/mod.rs b/swap-machine/src/alice/mod.rs index ebabb79c07..3cd86c985f 100644 --- a/swap-machine/src/alice/mod.rs +++ b/swap-machine/src/alice/mod.rs @@ -359,7 +359,7 @@ impl State2 { let tx_cancel_sig = self.a.sign(tx_cancel.digest()); Message3 { tx_cancel_sig, - tx_refund_encsig, + tx_partial_refund_encsig: tx_refund_encsig, } } diff --git a/swap-machine/src/bob/mod.rs b/swap-machine/src/bob/mod.rs index 17d0ea33a4..fff1006783 100644 --- a/swap-machine/src/bob/mod.rs +++ b/swap-machine/src/bob/mod.rs @@ -336,14 +336,14 @@ impl State1 { // TODO: send and receive and verify _partial_ refund signature instead of full refund signature. let tx_refund = - bitcoin::TxFullRefund::new(&tx_cancel, &self.refund_address, self.tx_refund_fee); + bitcoin::TxPartialRefund::new(&tx_cancel, &self.refund_address, self.A, self.b.public(), spending_fee) bitcoin::verify_sig(&self.A, &tx_cancel.digest(), &msg.tx_cancel_sig)?; bitcoin::verify_encsig( self.A, bitcoin::PublicKey::from(self.s_b.to_secpfun_scalar()), &tx_refund.digest(), - &msg.tx_refund_encsig, + &msg.tx_partial_refund_encsig, )?; Ok(State2 { @@ -361,7 +361,7 @@ impl State1 { punish_address: self.punish_address, tx_lock: self.tx_lock, tx_cancel_sig_a: msg.tx_cancel_sig, - tx_refund_encsig: msg.tx_refund_encsig, + bob_refund_type: BobRefundType::from_partial_refund_sig(msg.tx_partial_refund_encsig), min_monero_confirmations: self.min_monero_confirmations, tx_redeem_fee: self.tx_redeem_fee, tx_refund_fee: self.tx_refund_fee, diff --git a/swap-machine/src/common/mod.rs b/swap-machine/src/common/mod.rs index ff72483064..18d90267fc 100644 --- a/swap-machine/src/common/mod.rs +++ b/swap-machine/src/common/mod.rs @@ -69,7 +69,7 @@ pub struct Message2 { #[derive(Clone, Debug, Serialize, Deserialize)] pub struct Message3 { pub tx_cancel_sig: bitcoin::Signature, - pub tx_refund_encsig: bitcoin::EncryptedSignature, + pub tx_partial_refund_encsig: bitcoin::EncryptedSignature, } #[allow(non_snake_case)] From 9e4cfff8fb8613c0bdf91bb25ebe3a3bf71a9da3 Mon Sep 17 00:00:00 2001 From: einliterflasche Date: Tue, 2 Dec 2025 12:00:33 +0100 Subject: [PATCH 007/113] add amnesty_amount and tx_partial_refund_fee to swap_setup --- swap-machine/src/alice/mod.rs | 8 ++++++++ swap-machine/src/bob/mod.rs | 31 +++++++++++++++++++++++-------- swap-machine/src/common/mod.rs | 6 ++++++ swap/src/bitcoin/wallet.rs | 2 +- swap/src/protocol/bob/swap.rs | 4 ++-- 5 files changed, 40 insertions(+), 11 deletions(-) diff --git a/swap-machine/src/alice/mod.rs b/swap-machine/src/alice/mod.rs index 3cd86c985f..dd51f411db 100644 --- a/swap-machine/src/alice/mod.rs +++ b/swap-machine/src/alice/mod.rs @@ -148,6 +148,7 @@ pub struct State0 { dleq_proof_s_a: CrossCurveDLEQProof, btc: bitcoin::Amount, xmr: monero::Amount, + btc_amnesty_amount: bitcoin::Amount, cancel_timelock: CancelTimelock, punish_timelock: PunishTimelock, redeem_address: bitcoin::Address, @@ -161,6 +162,7 @@ impl State0 { pub fn new( btc: bitcoin::Amount, xmr: monero::Amount, + btc_amnesty_amount: bitcoin::Amount, env_config: Config, redeem_address: bitcoin::Address, punish_address: bitcoin::Address, @@ -186,6 +188,7 @@ impl State0 { point: S_a_monero.compress(), }, dleq_proof_s_a, + btc_amnesty_amount, redeem_address, punish_address, btc, @@ -230,6 +233,7 @@ impl State0 { dleq_proof_s_a: self.dleq_proof_s_a, btc: self.btc, xmr: self.xmr, + btc_amnesty_amount: self.btc_amnesty_amount, cancel_timelock: self.cancel_timelock, punish_timelock: self.punish_timelock, refund_address: msg.refund_address, @@ -238,6 +242,7 @@ impl State0 { tx_redeem_fee: self.tx_redeem_fee, tx_punish_fee: self.tx_punish_fee, tx_refund_fee: msg.tx_refund_fee, + tx_partial_refund_fee: msg.tx_partial_refund_fee, tx_cancel_fee: msg.tx_cancel_fee, }, )) @@ -259,6 +264,7 @@ pub struct State1 { dleq_proof_s_a: CrossCurveDLEQProof, btc: bitcoin::Amount, xmr: monero::Amount, + btc_amnesty_amount: bitcoin::Amount, cancel_timelock: CancelTimelock, punish_timelock: PunishTimelock, refund_address: bitcoin::Address, @@ -267,6 +273,7 @@ pub struct State1 { tx_redeem_fee: bitcoin::Amount, tx_punish_fee: bitcoin::Amount, tx_refund_fee: bitcoin::Amount, + tx_partial_refund_fee: bitcoin::Amount, tx_cancel_fee: bitcoin::Amount, } @@ -282,6 +289,7 @@ impl State1 { punish_address: self.punish_address.clone(), tx_redeem_fee: self.tx_redeem_fee, tx_punish_fee: self.tx_punish_fee, + amnesty_amount: self.btc_amnesty_amount, } } diff --git a/swap-machine/src/bob/mod.rs b/swap-machine/src/bob/mod.rs index fff1006783..19278ac9d5 100644 --- a/swap-machine/src/bob/mod.rs +++ b/swap-machine/src/bob/mod.rs @@ -178,6 +178,7 @@ pub struct State0 { punish_timelock: PunishTimelock, refund_address: bitcoin::Address, min_monero_confirmations: u64, + tx_partial_refund_fee: bitcoin::Amount, tx_refund_fee: bitcoin::Amount, tx_cancel_fee: bitcoin::Amount, tx_lock_fee: bitcoin::Amount, @@ -194,6 +195,7 @@ impl State0 { punish_timelock: PunishTimelock, refund_address: bitcoin::Address, min_monero_confirmations: u64, + tx_partial_refund_fee: bitcoin::Amount, tx_refund_fee: bitcoin::Amount, tx_cancel_fee: bitcoin::Amount, tx_lock_fee: bitcoin::Amount, @@ -221,6 +223,7 @@ impl State0 { punish_timelock, refund_address, min_monero_confirmations, + tx_partial_refund_fee, tx_refund_fee, tx_cancel_fee, tx_lock_fee, @@ -237,6 +240,7 @@ impl State0 { v_b: self.v_b, refund_address: self.refund_address.clone(), tx_refund_fee: self.tx_refund_fee, + tx_partial_refund_fee: self.tx_partial_refund_fee, tx_cancel_fee: self.tx_cancel_fee, } } @@ -280,6 +284,7 @@ impl State0 { S_a_bitcoin: msg.S_a_bitcoin, v, xmr: self.xmr, + btc_amnesty_amount: msg.amnesty_amount, cancel_timelock: self.cancel_timelock, punish_timelock: self.punish_timelock, refund_address: self.refund_address, @@ -289,6 +294,7 @@ impl State0 { min_monero_confirmations: self.min_monero_confirmations, tx_redeem_fee: msg.tx_redeem_fee, tx_refund_fee: self.tx_refund_fee, + tx_partial_refund_fee: self.tx_partial_refund_fee, tx_punish_fee: msg.tx_punish_fee, tx_cancel_fee: self.tx_cancel_fee, }) @@ -305,6 +311,7 @@ pub struct State1 { S_a_bitcoin: bitcoin::PublicKey, v: monero::PrivateViewKey, xmr: monero::Amount, + btc_amnesty_amount: bitcoin::Amount, cancel_timelock: CancelTimelock, punish_timelock: PunishTimelock, refund_address: bitcoin::Address, @@ -312,6 +319,7 @@ pub struct State1 { punish_address: bitcoin::Address, tx_lock: bitcoin::TxLock, min_monero_confirmations: u64, + tx_partial_refund_fee: bitcoin::Amount, tx_redeem_fee: bitcoin::Amount, tx_refund_fee: bitcoin::Amount, tx_punish_fee: bitcoin::Amount, @@ -336,7 +344,7 @@ impl State1 { // TODO: send and receive and verify _partial_ refund signature instead of full refund signature. let tx_refund = - bitcoin::TxPartialRefund::new(&tx_cancel, &self.refund_address, self.A, self.b.public(), spending_fee) + bitcoin::TxPartialRefund::new(&tx_cancel, &self.refund_address, self.A, self.b.public(), self.btc_amnesty_amount, self.tx_partial_refund_fee)?; bitcoin::verify_sig(&self.A, &tx_cancel.digest(), &msg.tx_cancel_sig)?; bitcoin::verify_encsig( @@ -362,6 +370,7 @@ impl State1 { tx_lock: self.tx_lock, tx_cancel_sig_a: msg.tx_cancel_sig, bob_refund_type: BobRefundType::from_partial_refund_sig(msg.tx_partial_refund_encsig), + tx_refund_amnesty_sig: compile_error!("TODO: Mechanism for sometimes sending this during swap setup, sometimes not"), min_monero_confirmations: self.min_monero_confirmations, tx_redeem_fee: self.tx_redeem_fee, tx_refund_fee: self.tx_refund_fee, @@ -459,7 +468,8 @@ impl State2 { redeem_address: self.redeem_address, tx_lock: self.tx_lock.clone(), tx_cancel_sig_a: self.tx_cancel_sig_a, - tx_refund_encsig: self.tx_refund_encsig, + bob_refund_type: self.bob_refund_type, + tx_refund_amnesty_sig: self.tx_refund_amnesty_sig, min_monero_confirmations: self.min_monero_confirmations, tx_redeem_fee: self.tx_redeem_fee, tx_refund_fee: self.tx_refund_fee, @@ -542,7 +552,8 @@ impl State3 { redeem_address: self.redeem_address, tx_lock: self.tx_lock, tx_cancel_sig_a: self.tx_cancel_sig_a, - tx_refund_encsig: self.tx_refund_encsig, + bob_refund_type: self.bob_refund_type, + tx_refund_amnesty_sig: self.tx_refund_amnesty_sig, monero_wallet_restore_blockheight, lock_transfer_proof, tx_redeem_fee: self.tx_redeem_fee, @@ -563,7 +574,8 @@ impl State3 { refund_address: self.refund_address.clone(), tx_lock: self.tx_lock.clone(), tx_cancel_sig_a: self.tx_cancel_sig_a.clone(), - tx_refund_encsig: self.tx_refund_encsig.clone(), + bob_refund_type: self.bob_refund_type.clone(), + tx_refund_amnesty_sig: self.tx_refund_amnesty_sig.clone(), tx_refund_fee: self.tx_refund_fee, tx_cancel_fee: self.tx_cancel_fee, xmr: self.xmr, @@ -744,7 +756,8 @@ impl State4 { refund_address: self.refund_address, tx_lock: self.tx_lock, tx_cancel_sig_a: self.tx_cancel_sig_a, - tx_refund_encsig: self.tx_refund_encsig, + bob_refund_type: self.bob_refund_type, + tx_refund_amnesty_sig: self.tx_refund_amnesty_sig, tx_refund_fee: self.tx_refund_fee, tx_cancel_fee: self.tx_cancel_fee, xmr: self.xmr, @@ -893,7 +906,7 @@ impl State6 { &self, bitcoin_wallet: &dyn bitcoin_wallet::BitcoinWallet, ) -> Result { - let signed_tx_refund = self.signed_refund_transaction()?; + let signed_tx_refund = self.signed_full_refund_transaction()?; let signed_tx_refund_txid = signed_tx_refund.compute_txid(); bitcoin_wallet.broadcast(signed_tx_refund, "refund").await?; @@ -909,14 +922,16 @@ impl State6 { Ok(tx_refund) } - pub fn signed_refund_transaction(&self) -> Result { + pub fn signed_full_refund_transaction(&self) -> Result { + let tx_full_refund_encsig = self.bob_refund_type.tx_full_refund_encsig().context("Can't sign full refund transaction because we don't have the necessary signature")?; + let tx_refund = self.construct_tx_refund()?; let adaptor = Adaptor::, Deterministic>::default(); let sig_b = self.b.sign(tx_refund.digest()); let sig_a = - adaptor.decrypt_signature(&self.s_b.to_secpfun_scalar(), self.tx_refund_encsig.clone()); + adaptor.decrypt_signature(&self.s_b.to_secpfun_scalar(), tx_full_refund_encsig); let signed_tx_refund = tx_refund.add_signatures((self.A, sig_a), (self.b.public(), sig_b))?; diff --git a/swap-machine/src/common/mod.rs b/swap-machine/src/common/mod.rs index 18d90267fc..4b14706919 100644 --- a/swap-machine/src/common/mod.rs +++ b/swap-machine/src/common/mod.rs @@ -38,6 +38,8 @@ pub struct Message0 { #[serde(with = "::bitcoin::amount::serde::as_sat")] pub tx_refund_fee: bitcoin::Amount, #[serde(with = "::bitcoin::amount::serde::as_sat")] + pub tx_partial_refund_fee: bitcoin::Amount, + #[serde(with = "::bitcoin::amount::serde::as_sat")] pub tx_cancel_fee: bitcoin::Amount, } @@ -57,6 +59,10 @@ pub struct Message1 { pub tx_redeem_fee: bitcoin::Amount, #[serde(with = "::bitcoin::amount::serde::as_sat")] pub tx_punish_fee: bitcoin::Amount, + #[serde(with = "::bitcoin::amount::serde::as_sat")] + /// The amount of Bitcoin that Bob will only receive in case of a refund + /// _and_ if Alice decides to. Introduced in [#675](https://github.com/eigenwallet/core/pull/675). + pub amnesty_amount: bitcoin::Amount, } #[allow(non_snake_case)] diff --git a/swap/src/bitcoin/wallet.rs b/swap/src/bitcoin/wallet.rs index a4a97d9595..fe6494742e 100644 --- a/swap/src/bitcoin/wallet.rs +++ b/swap/src/bitcoin/wallet.rs @@ -3805,7 +3805,7 @@ mod swap_core_bitcoin_tests { let redeem_transaction = alice_state3 .signed_redeem_transaction(encrypted_signature) .unwrap(); - let refund_transaction = bob_state6.signed_refund_transaction().unwrap(); + let refund_transaction = bob_state6.signed_full_refund_transaction().unwrap(); assert_weight(redeem_transaction, TxRedeem::weight().to_wu(), "TxRedeem"); assert_weight(cancel_transaction, TxCancel::weight().to_wu(), "TxCancel"); diff --git a/swap/src/protocol/bob/swap.rs b/swap/src/protocol/bob/swap.rs index ccfbea4933..4def0656f0 100644 --- a/swap/src/protocol/bob/swap.rs +++ b/swap/src/protocol/bob/swap.rs @@ -797,7 +797,7 @@ async fn next_state( event_emitter.emit_swap_progress_event( swap_id, TauriSwapProgressEvent::BtcRefundPublished { - btc_refund_txid: state.signed_refund_transaction()?.compute_txid(), + btc_refund_txid: state.signed_full_refund_transaction()?.compute_txid(), }, ); @@ -894,7 +894,7 @@ async fn next_state( event_emitter.emit_swap_progress_event( swap_id, TauriSwapProgressEvent::BtcRefunded { - btc_refund_txid: state.signed_refund_transaction()?.compute_txid(), + btc_refund_txid: state.signed_full_refund_transaction()?.compute_txid(), }, ); From 1842cacd22aa1fc35a480ce2a0af89ea049d9838 Mon Sep 17 00:00:00 2001 From: einliterflasche Date: Tue, 2 Dec 2025 12:07:19 +0100 Subject: [PATCH 008/113] add tx_partial_refund_fee to NewSwap --- swap-p2p/src/protocols/swap_setup/alice.rs | 1 + swap-p2p/src/protocols/swap_setup/bob.rs | 3 +++ 2 files changed, 4 insertions(+) diff --git a/swap-p2p/src/protocols/swap_setup/alice.rs b/swap-p2p/src/protocols/swap_setup/alice.rs index 7123aa084f..be290df8c0 100644 --- a/swap-p2p/src/protocols/swap_setup/alice.rs +++ b/swap-p2p/src/protocols/swap_setup/alice.rs @@ -545,6 +545,7 @@ async fn run_swap_setup( let state0 = State0::new( request.btc, xmr, + compile_error!("TODO: Implement system for alice to decide amnesty amount"), env_config, wallet_snapshot.redeem_address, wallet_snapshot.punish_address, diff --git a/swap-p2p/src/protocols/swap_setup/bob.rs b/swap-p2p/src/protocols/swap_setup/bob.rs index 279eb0e746..651e26d9bc 100644 --- a/swap-p2p/src/protocols/swap_setup/bob.rs +++ b/swap-p2p/src/protocols/swap_setup/bob.rs @@ -346,6 +346,7 @@ pub struct NewSwap { pub btc: bitcoin::Amount, pub tx_lock_fee: bitcoin::Amount, pub tx_refund_fee: bitcoin::Amount, + pub tx_partial_refund_fee: bitcoin::Amount, pub tx_cancel_fee: bitcoin::Amount, pub bitcoin_refund_address: bitcoin::Address, } @@ -530,9 +531,11 @@ async fn run_swap_setup( env_config.bitcoin_punish_timelock.into(), new_swap_request.bitcoin_refund_address.clone(), env_config.monero_finality_confirmations, + new_swap_request.tx_partial_refund_fee, new_swap_request.tx_refund_fee, new_swap_request.tx_cancel_fee, new_swap_request.tx_lock_fee, + ); tracing::trace!( From f5b7984737347859a2152223a15cf48a50894ac4 Mon Sep 17 00:00:00 2001 From: einliterflasche Date: Tue, 2 Dec 2025 12:21:56 +0100 Subject: [PATCH 009/113] fix compilation errors in bitcoin wallet tests --- swap/src/bitcoin/wallet.rs | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/swap/src/bitcoin/wallet.rs b/swap/src/bitcoin/wallet.rs index fe6494742e..32509a5947 100644 --- a/swap/src/bitcoin/wallet.rs +++ b/swap/src/bitcoin/wallet.rs @@ -3725,6 +3725,7 @@ mod swap_core_bitcoin_tests { .await; let spending_fee = Amount::from_sat(1_000); let btc_amount = Amount::from_sat(500_000); + let btc_amnesty_amount = Amount::from_sat(0); let xmr_amount = crate::monero::Amount::from_piconero(10000); let tx_redeem_fee = alice_wallet @@ -3747,6 +3748,7 @@ mod swap_core_bitcoin_tests { let alice_state0 = alice::State0::new( btc_amount, xmr_amount, + btc_amnesty_amount, config, redeem_address, punish_address, @@ -3766,6 +3768,7 @@ mod swap_core_bitcoin_tests { config.monero_finality_confirmations, spending_fee, spending_fee, + spending_fee, tx_lock_fee, ); @@ -3835,6 +3838,7 @@ mod swap_core_bitcoin_tests { let spending_fee = Amount::from_sat(1_000); let btc_amount = Amount::from_sat(500_000); let xmr_amount = crate::monero::Amount::from_piconero(10000); + let btc_amnesty_amount = Amount::from_sat(0); let tx_redeem_fee = alice_wallet .estimate_fee(TxRedeem::weight(), Some(btc_amount)) @@ -3852,6 +3856,7 @@ mod swap_core_bitcoin_tests { let alice_state0 = alice::State0::new( btc_amount, xmr_amount, + btc_amnesty_amount, config, refund_address.clone(), punish_address, @@ -3872,6 +3877,7 @@ mod swap_core_bitcoin_tests { spending_fee, spending_fee, spending_fee, + spending_fee, ); // Complete the state machine up to State3 From ea5bcf3006135d24265fcf647d343ae767fe388c Mon Sep 17 00:00:00 2001 From: einliterflasche Date: Tue, 2 Dec 2025 12:22:34 +0100 Subject: [PATCH 010/113] bob: estimate partial refund fee --- swap/src/protocol/bob/swap.rs | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/swap/src/protocol/bob/swap.rs b/swap/src/protocol/bob/swap.rs index 4def0656f0..3adb456f67 100644 --- a/swap/src/protocol/bob/swap.rs +++ b/swap/src/protocol/bob/swap.rs @@ -109,6 +109,12 @@ async fn next_state( let tx_refund_fee = bitcoin_wallet .estimate_fee(TxFullRefund::weight(), Some(btc_amount)) .await?; + let tx_partial_refund_fee = bitcoin_wallet. + // The actual amount of partial refund may be smaller than btc_amount, + // but at this point we don't know how much smaller. + // We still use btc_amount to set an upper limit on the tx fee - + // Even if this limit is higher than it would be given the actual amount. + estimate_fee(TxPartialRefund::weight(), Some(btc_amount)).await?; let tx_cancel_fee = bitcoin_wallet .estimate_fee(TxCancel::weight(), Some(btc_amount)) .await?; @@ -127,6 +133,7 @@ async fn next_state( btc: btc_amount, tx_lock_fee, tx_refund_fee, + tx_partial_refund_fee, tx_cancel_fee, bitcoin_refund_address: change_address, }) From 46cd40c0d58e592a5dddc4ae93d7958f13b8ad62 Mon Sep 17 00:00:00 2001 From: einliterflasche Date: Tue, 2 Dec 2025 12:44:08 +0100 Subject: [PATCH 011/113] allow Alice to send full refund encisg and amnesty sig during swap setup (optional) --- swap-machine/src/alice/mod.rs | 3 +++ swap-machine/src/bob/mod.rs | 18 +++++++++++++----- swap-machine/src/common/mod.rs | 9 +++++++-- 3 files changed, 23 insertions(+), 7 deletions(-) diff --git a/swap-machine/src/alice/mod.rs b/swap-machine/src/alice/mod.rs index dd51f411db..663cc3d946 100644 --- a/swap-machine/src/alice/mod.rs +++ b/swap-machine/src/alice/mod.rs @@ -365,9 +365,12 @@ impl State2 { let tx_refund_encsig = self.a.encsign(self.S_b_bitcoin, tx_refund.digest()); let tx_cancel_sig = self.a.sign(tx_cancel.digest()); + let (tx_full_refund_encsig, tx_refund_amnesty_sig) = compile_error!("TODO: Implement system for Alice to decide commiting to full refund"); Message3 { tx_cancel_sig, tx_partial_refund_encsig: tx_refund_encsig, + tx_full_refund_encsig, + tx_refund_amnesty_sig, } } diff --git a/swap-machine/src/bob/mod.rs b/swap-machine/src/bob/mod.rs index 19278ac9d5..c011918dbb 100644 --- a/swap-machine/src/bob/mod.rs +++ b/swap-machine/src/bob/mod.rs @@ -342,18 +342,18 @@ impl State1 { self.tx_cancel_fee, )?; - // TODO: send and receive and verify _partial_ refund signature instead of full refund signature. - let tx_refund = + let tx_partial_refund = bitcoin::TxPartialRefund::new(&tx_cancel, &self.refund_address, self.A, self.b.public(), self.btc_amnesty_amount, self.tx_partial_refund_fee)?; bitcoin::verify_sig(&self.A, &tx_cancel.digest(), &msg.tx_cancel_sig)?; bitcoin::verify_encsig( self.A, bitcoin::PublicKey::from(self.s_b.to_secpfun_scalar()), - &tx_refund.digest(), + &tx_partial_refund.digest(), &msg.tx_partial_refund_encsig, )?; + let bob_refund_type = BobRefundType::from_possibly_full_refund_sig(msg.tx_partial_refund_encsig, msg.tx_full_refund_encsig); Ok(State2 { A: self.A, b: self.b, @@ -369,8 +369,8 @@ impl State1 { punish_address: self.punish_address, tx_lock: self.tx_lock, tx_cancel_sig_a: msg.tx_cancel_sig, - bob_refund_type: BobRefundType::from_partial_refund_sig(msg.tx_partial_refund_encsig), - tx_refund_amnesty_sig: compile_error!("TODO: Mechanism for sometimes sending this during swap setup, sometimes not"), + bob_refund_type, + tx_refund_amnesty_sig: msg.tx_refund_amnesty_sig, min_monero_confirmations: self.min_monero_confirmations, tx_redeem_fee: self.tx_redeem_fee, tx_refund_fee: self.tx_refund_fee, @@ -981,6 +981,14 @@ impl State6 { } impl BobRefundType { + pub fn from_possibly_full_refund_sig(partial_refund_encsig: bitcoin::EncryptedSignature, full_refund_encsig: Option) -> Self { + if let Some(full_refund_encsig) = full_refund_encsig { + Self::Full { tx_partial_refund_encsig: partial_refund_encsig, tx_refund_encsig: full_refund_encsig } + } else { + Self::Partial { tx_partial_refund_encsig: partial_refund_encsig } + } + } + pub fn from_partial_refund_sig(partial_refund_encsig: bitcoin::EncryptedSignature) -> Self { Self::Partial { tx_partial_refund_encsig: partial_refund_encsig } } diff --git a/swap-machine/src/common/mod.rs b/swap-machine/src/common/mod.rs index 4b14706919..77b3cf015f 100644 --- a/swap-machine/src/common/mod.rs +++ b/swap-machine/src/common/mod.rs @@ -60,8 +60,8 @@ pub struct Message1 { #[serde(with = "::bitcoin::amount::serde::as_sat")] pub tx_punish_fee: bitcoin::Amount, #[serde(with = "::bitcoin::amount::serde::as_sat")] - /// The amount of Bitcoin that Bob will only receive in case of a refund - /// _and_ if Alice decides to. Introduced in [#675](https://github.com/eigenwallet/core/pull/675). + /// The amount of Bitcoin that Bob not get refunded unless Alice decides so. + /// Introduced in [#675](https://github.com/eigenwallet/core/pull/675) to combat spam. pub amnesty_amount: bitcoin::Amount, } @@ -75,7 +75,12 @@ pub struct Message2 { #[derive(Clone, Debug, Serialize, Deserialize)] pub struct Message3 { pub tx_cancel_sig: bitcoin::Signature, + /// The following fields were reworked in [#675](https://github.com/eigenwallet/core/pull/675). + /// Alice _may_ choose to commit to a full refund during the swap setup already, but doesn't + /// have to. pub tx_partial_refund_encsig: bitcoin::EncryptedSignature, + pub tx_full_refund_encsig: Option, + pub tx_refund_amnesty_sig: Option, } #[allow(non_snake_case)] From 0a42da89f8c86411b28bd077ede7e228651e180c Mon Sep 17 00:00:00 2001 From: einliterflasche Date: Tue, 2 Dec 2025 12:51:28 +0100 Subject: [PATCH 012/113] add tx_partial_refund_fee and btc_amnesty_amoutn to all Alice states --- swap-machine/src/alice/mod.rs | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/swap-machine/src/alice/mod.rs b/swap-machine/src/alice/mod.rs index 663cc3d946..5d57071355 100644 --- a/swap-machine/src/alice/mod.rs +++ b/swap-machine/src/alice/mod.rs @@ -307,6 +307,7 @@ impl State1 { v: self.v, btc: self.btc, xmr: self.xmr, + btc_amnesty_amount: self.btc_amnesty_amount, cancel_timelock: self.cancel_timelock, punish_timelock: self.punish_timelock, refund_address: self.refund_address, @@ -316,6 +317,7 @@ impl State1 { tx_redeem_fee: self.tx_redeem_fee, tx_punish_fee: self.tx_punish_fee, tx_refund_fee: self.tx_refund_fee, + tx_partial_refund_fee: self.tx_partial_refund_fee, tx_cancel_fee: self.tx_cancel_fee, }) } @@ -332,6 +334,7 @@ pub struct State2 { v: monero::PrivateViewKey, btc: bitcoin::Amount, xmr: monero::Amount, + btc_amnesty_amount: bitcoin::Amount, cancel_timelock: CancelTimelock, punish_timelock: PunishTimelock, refund_address: bitcoin::Address, @@ -341,6 +344,7 @@ pub struct State2 { tx_redeem_fee: bitcoin::Amount, tx_punish_fee: bitcoin::Amount, tx_refund_fee: bitcoin::Amount, + tx_partial_refund_fee: bitcoin::Amount, tx_cancel_fee: bitcoin::Amount, } @@ -424,6 +428,7 @@ impl State2 { v: self.v, btc: self.btc, xmr: self.xmr, + btc_amnesty_amount: self.btc_amnesty_amount, cancel_timelock: self.cancel_timelock, punish_timelock: self.punish_timelock, refund_address: self.refund_address, @@ -436,6 +441,7 @@ impl State2 { tx_redeem_fee: self.tx_redeem_fee, tx_punish_fee: self.tx_punish_fee, tx_refund_fee: self.tx_refund_fee, + tx_partial_refund_fee: self.tx_partial_refund_fee, tx_cancel_fee: self.tx_cancel_fee, }) } @@ -453,6 +459,7 @@ pub struct State3 { #[serde(with = "::bitcoin::amount::serde::as_sat")] pub btc: bitcoin::Amount, pub xmr: monero::Amount, + pub btc_amnesty_amount: bitcoin::Amount, pub cancel_timelock: CancelTimelock, pub punish_timelock: PunishTimelock, #[serde(with = "swap_serde::bitcoin::address_serde")] @@ -482,6 +489,8 @@ pub struct State3 { #[serde(with = "::bitcoin::amount::serde::as_sat")] pub tx_refund_fee: bitcoin::Amount, #[serde(with = "::bitcoin::amount::serde::as_sat")] + pub tx_partial_refund_fee: bitcoin::Amount, + #[serde(with = "::bitcoin::amount::serde::as_sat")] pub tx_cancel_fee: bitcoin::Amount, } From a596087ea9e0a8d6ed255b8f7a07a61932a33de3 Mon Sep 17 00:00:00 2001 From: einliterflasche Date: Tue, 2 Dec 2025 13:19:14 +0100 Subject: [PATCH 013/113] rename tx_refund_encsig to tx_full_refund_encsig while keeping db backwards compatible --- swap-machine/src/bob/mod.rs | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/swap-machine/src/bob/mod.rs b/swap-machine/src/bob/mod.rs index c011918dbb..dd999f720d 100644 --- a/swap-machine/src/bob/mod.rs +++ b/swap-machine/src/bob/mod.rs @@ -79,14 +79,18 @@ pub enum BobRefundType { /// Alice has signed both the partial and full refund transactions. Full { tx_partial_refund_encsig: bitcoin::EncryptedSignature, - tx_refund_encsig: bitcoin::EncryptedSignature, + // Serde rename keeps + untagged + flatten keeps this backwards compatible with old swaps in the database. + #[serde(rename = "tx_refund_encsig")] + tx_full_refund_encsig: bitcoin::EncryptedSignature, }, /// Alice has only signed the full refund transaction. /// This is only used to maintain backwards compatibility for older swaps /// from before the partial refund protocol change. /// See [#675](https://github.com/eigenwallet/core/pull/675). Legacy { - tx_refund_encsig: bitcoin::EncryptedSignature, + // Serde raname keeps + untagged + flatten keeps this backwards compatible with old swaps in the database. + #[serde(rename = "tx_refund_encsig")] + tx_full_refund_encsig: bitcoin::EncryptedSignature, } } @@ -983,7 +987,7 @@ impl State6 { impl BobRefundType { pub fn from_possibly_full_refund_sig(partial_refund_encsig: bitcoin::EncryptedSignature, full_refund_encsig: Option) -> Self { if let Some(full_refund_encsig) = full_refund_encsig { - Self::Full { tx_partial_refund_encsig: partial_refund_encsig, tx_refund_encsig: full_refund_encsig } + Self::Full { tx_partial_refund_encsig: partial_refund_encsig, tx_full_refund_encsig: full_refund_encsig } } else { Self::Partial { tx_partial_refund_encsig: partial_refund_encsig } } @@ -996,8 +1000,8 @@ impl BobRefundType { pub fn tx_full_refund_encsig(&self) -> Option { match self { BobRefundType::Partial { .. } => None, - BobRefundType::Full { tx_refund_encsig, .. } => Some(tx_refund_encsig.clone()), - BobRefundType::Legacy { tx_refund_encsig } => Some(tx_refund_encsig.clone()), + BobRefundType::Full { tx_full_refund_encsig, .. } => Some(tx_full_refund_encsig.clone()), + BobRefundType::Legacy { tx_full_refund_encsig } => Some(tx_full_refund_encsig.clone()), } } From 0d5be57ff681a0554bd2255f8d087601dd63906f Mon Sep 17 00:00:00 2001 From: einliterflasche Date: Tue, 2 Dec 2025 13:39:04 +0100 Subject: [PATCH 014/113] add tx_refund_amnesty_fee to swap setup messages and alice/bob states --- swap-machine/src/alice/mod.rs | 4 ++++ swap-machine/src/bob/mod.rs | 4 ++++ swap-machine/src/common/mod.rs | 2 ++ swap-p2p/src/protocols/swap_setup/bob.rs | 2 ++ swap/src/bitcoin/wallet.rs | 2 ++ swap/src/protocol/bob/swap.rs | 19 ++++++++++++------- 6 files changed, 26 insertions(+), 7 deletions(-) diff --git a/swap-machine/src/alice/mod.rs b/swap-machine/src/alice/mod.rs index 5d57071355..3b68ed80c1 100644 --- a/swap-machine/src/alice/mod.rs +++ b/swap-machine/src/alice/mod.rs @@ -243,6 +243,7 @@ impl State0 { tx_punish_fee: self.tx_punish_fee, tx_refund_fee: msg.tx_refund_fee, tx_partial_refund_fee: msg.tx_partial_refund_fee, + tx_refund_amnesty_fee: msg.tx_refund_amnesty_fee, tx_cancel_fee: msg.tx_cancel_fee, }, )) @@ -274,6 +275,7 @@ pub struct State1 { tx_punish_fee: bitcoin::Amount, tx_refund_fee: bitcoin::Amount, tx_partial_refund_fee: bitcoin::Amount, + tx_refund_amnesty_fee: bitcoin::Amount, tx_cancel_fee: bitcoin::Amount, } @@ -318,6 +320,7 @@ impl State1 { tx_punish_fee: self.tx_punish_fee, tx_refund_fee: self.tx_refund_fee, tx_partial_refund_fee: self.tx_partial_refund_fee, + tx_refund_amnesty_fee: self.tx_refund_amnesty_fee, tx_cancel_fee: self.tx_cancel_fee, }) } @@ -345,6 +348,7 @@ pub struct State2 { tx_punish_fee: bitcoin::Amount, tx_refund_fee: bitcoin::Amount, tx_partial_refund_fee: bitcoin::Amount, + tx_refund_amnesty_fee: bitcoin::Amount, tx_cancel_fee: bitcoin::Amount, } diff --git a/swap-machine/src/bob/mod.rs b/swap-machine/src/bob/mod.rs index dd999f720d..bf4f6d1162 100644 --- a/swap-machine/src/bob/mod.rs +++ b/swap-machine/src/bob/mod.rs @@ -183,6 +183,7 @@ pub struct State0 { refund_address: bitcoin::Address, min_monero_confirmations: u64, tx_partial_refund_fee: bitcoin::Amount, + tx_refund_amnesty_fee: bitcoin::Amount, tx_refund_fee: bitcoin::Amount, tx_cancel_fee: bitcoin::Amount, tx_lock_fee: bitcoin::Amount, @@ -200,6 +201,7 @@ impl State0 { refund_address: bitcoin::Address, min_monero_confirmations: u64, tx_partial_refund_fee: bitcoin::Amount, + tx_refund_amnesty_fee: bitcoin::Amount, tx_refund_fee: bitcoin::Amount, tx_cancel_fee: bitcoin::Amount, tx_lock_fee: bitcoin::Amount, @@ -228,6 +230,7 @@ impl State0 { refund_address, min_monero_confirmations, tx_partial_refund_fee, + tx_refund_amnesty_fee, tx_refund_fee, tx_cancel_fee, tx_lock_fee, @@ -245,6 +248,7 @@ impl State0 { refund_address: self.refund_address.clone(), tx_refund_fee: self.tx_refund_fee, tx_partial_refund_fee: self.tx_partial_refund_fee, + tx_refund_amnesty_fee: self.tx_refund_amnesty_fee, tx_cancel_fee: self.tx_cancel_fee, } } diff --git a/swap-machine/src/common/mod.rs b/swap-machine/src/common/mod.rs index 77b3cf015f..05e1a99c4a 100644 --- a/swap-machine/src/common/mod.rs +++ b/swap-machine/src/common/mod.rs @@ -40,6 +40,8 @@ pub struct Message0 { #[serde(with = "::bitcoin::amount::serde::as_sat")] pub tx_partial_refund_fee: bitcoin::Amount, #[serde(with = "::bitcoin::amount::serde::as_sat")] + pub tx_refund_amnesty_fee: bitcoin::Amount, + #[serde(with = "::bitcoin::amount::serde::as_sat")] pub tx_cancel_fee: bitcoin::Amount, } diff --git a/swap-p2p/src/protocols/swap_setup/bob.rs b/swap-p2p/src/protocols/swap_setup/bob.rs index 651e26d9bc..ca1541a90c 100644 --- a/swap-p2p/src/protocols/swap_setup/bob.rs +++ b/swap-p2p/src/protocols/swap_setup/bob.rs @@ -347,6 +347,7 @@ pub struct NewSwap { pub tx_lock_fee: bitcoin::Amount, pub tx_refund_fee: bitcoin::Amount, pub tx_partial_refund_fee: bitcoin::Amount, + pub tx_refund_amnesty_fee: bitcoin::Amount, pub tx_cancel_fee: bitcoin::Amount, pub bitcoin_refund_address: bitcoin::Address, } @@ -532,6 +533,7 @@ async fn run_swap_setup( new_swap_request.bitcoin_refund_address.clone(), env_config.monero_finality_confirmations, new_swap_request.tx_partial_refund_fee, + new_swap_request.tx_refund_amnesty_fee, new_swap_request.tx_refund_fee, new_swap_request.tx_cancel_fee, new_swap_request.tx_lock_fee, diff --git a/swap/src/bitcoin/wallet.rs b/swap/src/bitcoin/wallet.rs index 32509a5947..07bb0fb269 100644 --- a/swap/src/bitcoin/wallet.rs +++ b/swap/src/bitcoin/wallet.rs @@ -3769,6 +3769,7 @@ mod swap_core_bitcoin_tests { spending_fee, spending_fee, spending_fee, + spending_fee, tx_lock_fee, ); @@ -3878,6 +3879,7 @@ mod swap_core_bitcoin_tests { spending_fee, spending_fee, spending_fee, + spending_fee, ); // Complete the state machine up to State3 diff --git a/swap/src/protocol/bob/swap.rs b/swap/src/protocol/bob/swap.rs index 3adb456f67..f968506a35 100644 --- a/swap/src/protocol/bob/swap.rs +++ b/swap/src/protocol/bob/swap.rs @@ -106,19 +106,23 @@ async fn next_state( change_address, tx_lock_fee, } => { + let tx_cancel_fee = bitcoin_wallet + .estimate_fee(TxCancel::weight(), Some(btc_amount)) + .await?; let tx_refund_fee = bitcoin_wallet .estimate_fee(TxFullRefund::weight(), Some(btc_amount)) .await?; + + // At this point we don't know how high btc_amnesty_amount is. + // This means we don't know how large the amount of the partial refund and amnesty transactions will be. + // We therefore specify the same upper limit on tx fees as for the other transactions, even though + // the maximum fee percentage might be higher due to that. let tx_partial_refund_fee = bitcoin_wallet. - // The actual amount of partial refund may be smaller than btc_amount, - // but at this point we don't know how much smaller. - // We still use btc_amount to set an upper limit on the tx fee - - // Even if this limit is higher than it would be given the actual amount. estimate_fee(TxPartialRefund::weight(), Some(btc_amount)).await?; - let tx_cancel_fee = bitcoin_wallet - .estimate_fee(TxCancel::weight(), Some(btc_amount)) + let tx_refund_amnesty_fee = bitcoin_wallet + .estimate_fee(TxRefundAmnesty::weight(), Some(btc_amount)) .await?; - + // Emit an event to tauri that we are negotiating with the maker to lock the Bitcoin event_emitter.emit_swap_progress_event( swap_id, @@ -134,6 +138,7 @@ async fn next_state( tx_lock_fee, tx_refund_fee, tx_partial_refund_fee, + tx_refund_amnesty_fee, tx_cancel_fee, bitcoin_refund_address: change_address, }) From bcdbec7ba56d8cb9b29af2d2e1afe9bc1077cb8c Mon Sep 17 00:00:00 2001 From: einliterflasche Date: Tue, 2 Dec 2025 13:57:24 +0100 Subject: [PATCH 015/113] add tx_refund_amnesty_fee to Bob State1, verify partial_refund and refund_amnesty signatures before accepting --- swap-machine/src/alice/mod.rs | 2 +- swap-machine/src/bob/mod.rs | 19 +++++++++++++++++++ swap-p2p/src/protocols/swap_setup/alice.rs | 10 +++++----- 3 files changed, 25 insertions(+), 6 deletions(-) diff --git a/swap-machine/src/alice/mod.rs b/swap-machine/src/alice/mod.rs index 3b68ed80c1..c8e85841aa 100644 --- a/swap-machine/src/alice/mod.rs +++ b/swap-machine/src/alice/mod.rs @@ -373,7 +373,7 @@ impl State2 { let tx_refund_encsig = self.a.encsign(self.S_b_bitcoin, tx_refund.digest()); let tx_cancel_sig = self.a.sign(tx_cancel.digest()); - let (tx_full_refund_encsig, tx_refund_amnesty_sig) = compile_error!("TODO: Implement system for Alice to decide commiting to full refund"); + let (tx_full_refund_encsig, tx_refund_amnesty_sig) = todo!("TODO: Implement system for Alice to decide commiting to full refund"); Message3 { tx_cancel_sig, tx_partial_refund_encsig: tx_refund_encsig, diff --git a/swap-machine/src/bob/mod.rs b/swap-machine/src/bob/mod.rs index bf4f6d1162..eb3c589548 100644 --- a/swap-machine/src/bob/mod.rs +++ b/swap-machine/src/bob/mod.rs @@ -303,6 +303,7 @@ impl State0 { tx_redeem_fee: msg.tx_redeem_fee, tx_refund_fee: self.tx_refund_fee, tx_partial_refund_fee: self.tx_partial_refund_fee, + tx_refund_amnesty_fee: self.tx_refund_amnesty_fee, tx_punish_fee: msg.tx_punish_fee, tx_cancel_fee: self.tx_cancel_fee, }) @@ -328,6 +329,7 @@ pub struct State1 { tx_lock: bitcoin::TxLock, min_monero_confirmations: u64, tx_partial_refund_fee: bitcoin::Amount, + tx_refund_amnesty_fee: bitcoin::Amount, tx_redeem_fee: bitcoin::Amount, tx_refund_fee: bitcoin::Amount, tx_punish_fee: bitcoin::Amount, @@ -361,6 +363,23 @@ impl State1 { &msg.tx_partial_refund_encsig, )?; + // Verify the full refund signature if it is present + if let Some(tx_full_refund_encsig) = &msg.tx_full_refund_encsig { + let tx_full_refund = bitcoin::TxFullRefund::new(&tx_cancel, &self.refund_address, self.tx_refund_fee); + bitcoin::verify_encsig( + self.A, + bitcoin::PublicKey::from(self.s_b.to_secpfun_scalar()), + &tx_full_refund.digest(), + tx_full_refund_encsig, + )?; + } + + // Verify the refund amnesty signature if it is present + if let Some(tx_refund_amnesty_sig) = &msg.tx_refund_amnesty_sig { + let tx_refund_amnesty = bitcoin::TxRefundAmnesty::new(&tx_partial_refund, &self.refund_address, self.tx_refund_amnesty_fee); + bitcoin::verify_sig(&self.A, &tx_refund_amnesty.digest(), tx_refund_amnesty_sig)?; + } + let bob_refund_type = BobRefundType::from_possibly_full_refund_sig(msg.tx_partial_refund_encsig, msg.tx_full_refund_encsig); Ok(State2 { A: self.A, diff --git a/swap-p2p/src/protocols/swap_setup/alice.rs b/swap-p2p/src/protocols/swap_setup/alice.rs index be290df8c0..7bbb39d583 100644 --- a/swap-p2p/src/protocols/swap_setup/alice.rs +++ b/swap-p2p/src/protocols/swap_setup/alice.rs @@ -307,7 +307,7 @@ where let resume_only = self.resume_only; let min_buy = self.min_buy; let max_buy = self.max_buy; - let latest_rate = self.latest_rate.latest_rate().map_err(anyhow::Error::from); + let latest_rate = self.latest_rate.latest_rate(); let env_config = self.env_config; // We wrap the entire handshake in a timeout future @@ -320,7 +320,7 @@ where env_config, min_buy, max_buy, - latest_rate, + latest_rate.map_err(|error| Box::new(error) as Box), ), ); @@ -457,7 +457,7 @@ async fn run_swap_setup( env_config: env::Config, min_buy: bitcoin::Amount, max_buy: bitcoin::Amount, - latest_rate: Result, + latest_rate: Result>, ) -> Result<(Uuid, State3)> { let request = swap_setup::read_cbor_message::(&mut substream) .await @@ -504,7 +504,7 @@ async fn run_swap_setup( } let rate = - latest_rate.map_err(|e| Error::LatestRateFetchFailed(Box::new(e)))?; + latest_rate.map_err(Error::LatestRateFetchFailed)?; let xmr = rate .sell_quote(btc) .map_err(Error::SellQuoteCalculationFailed)?; @@ -545,7 +545,7 @@ async fn run_swap_setup( let state0 = State0::new( request.btc, xmr, - compile_error!("TODO: Implement system for alice to decide amnesty amount"), + todo!("TODO: Implement system for alice to decide amnesty amount"), env_config, wallet_snapshot.redeem_address, wallet_snapshot.punish_address, From 80bdcd4aa1d22b6491bf9a17325ab79364494a8b Mon Sep 17 00:00:00 2001 From: einliterflasche Date: Wed, 3 Dec 2025 15:47:33 +0100 Subject: [PATCH 016/113] remove unnecessary serde(with = bitcoin::amount::serde::as_sats) as it is the default --- swap-db/src/bob.rs | 1 - swap-machine/src/alice/mod.rs | 13 ++++--------- swap-machine/src/bob/mod.rs | 21 ++++++++------------- swap-machine/src/common/mod.rs | 7 ------- swap-p2p/src/protocols/quote.rs | 3 --- swap-p2p/src/protocols/swap_setup.rs | 6 ------ swap/src/cli/api/request.rs | 8 +------- swap/src/cli/api/tauri_bindings.rs | 6 ------ 8 files changed, 13 insertions(+), 52 deletions(-) diff --git a/swap-db/src/bob.rs b/swap-db/src/bob.rs index 039b325206..cbb79974bb 100644 --- a/swap-db/src/bob.rs +++ b/swap-db/src/bob.rs @@ -7,7 +7,6 @@ use swap_machine::bob::BobState; #[derive(Clone, Debug, Deserialize, Serialize, PartialEq)] pub enum Bob { Started { - #[serde(with = "::bitcoin::amount::serde::as_sat")] btc_amount: bitcoin::Amount, #[serde(with = "swap_serde::bitcoin::address_serde")] change_address: bitcoin::Address, diff --git a/swap-machine/src/alice/mod.rs b/swap-machine/src/alice/mod.rs index c8e85841aa..6cd0a0cc35 100644 --- a/swap-machine/src/alice/mod.rs +++ b/swap-machine/src/alice/mod.rs @@ -274,8 +274,8 @@ pub struct State1 { tx_redeem_fee: bitcoin::Amount, tx_punish_fee: bitcoin::Amount, tx_refund_fee: bitcoin::Amount, - tx_partial_refund_fee: bitcoin::Amount, - tx_refund_amnesty_fee: bitcoin::Amount, + tx_partial_refund_fee: Option, + tx_refund_amnesty_fee: Option, tx_cancel_fee: bitcoin::Amount, } @@ -460,7 +460,6 @@ pub struct State3 { S_b_monero: monero::PublicKey, S_b_bitcoin: swap_core::bitcoin::PublicKey, pub v: monero::PrivateViewKey, - #[serde(with = "::bitcoin::amount::serde::as_sat")] pub btc: bitcoin::Amount, pub xmr: monero::Amount, pub btc_amnesty_amount: bitcoin::Amount, @@ -486,15 +485,11 @@ pub struct State3 { /// to wait for the timelock to expire. #[serde(default)] tx_early_refund_sig_bob: Option, - #[serde(with = "::bitcoin::amount::serde::as_sat")] tx_redeem_fee: bitcoin::Amount, - #[serde(with = "::bitcoin::amount::serde::as_sat")] pub tx_punish_fee: bitcoin::Amount, - #[serde(with = "::bitcoin::amount::serde::as_sat")] pub tx_refund_fee: bitcoin::Amount, - #[serde(with = "::bitcoin::amount::serde::as_sat")] - pub tx_partial_refund_fee: bitcoin::Amount, - #[serde(with = "::bitcoin::amount::serde::as_sat")] + pub tx_partial_refund_fee: Option, + pub tx_refund_amnesty_fee: Option, pub tx_cancel_fee: bitcoin::Amount, } diff --git a/swap-machine/src/bob/mod.rs b/swap-machine/src/bob/mod.rs index eb3c589548..2ecdaa75f6 100644 --- a/swap-machine/src/bob/mod.rs +++ b/swap-machine/src/bob/mod.rs @@ -26,7 +26,6 @@ use uuid::Uuid; #[derive(Debug, Clone, PartialEq, Deserialize, Serialize)] pub enum BobState { Started { - #[serde(with = "::bitcoin::amount::serde::as_sat")] btc_amount: bitcoin::Amount, tx_lock_fee: bitcoin::Amount, #[serde(with = "address_serde")] @@ -436,14 +435,12 @@ pub struct State2 { /// This signature is voluntarily revealed by alice. tx_refund_amnesty_sig: Option, min_monero_confirmations: u64, - #[serde(with = "::bitcoin::amount::serde::as_sat")] tx_redeem_fee: bitcoin::Amount, - #[serde(with = "::bitcoin::amount::serde::as_sat")] tx_punish_fee: bitcoin::Amount, - #[serde(with = "::bitcoin::amount::serde::as_sat")] pub tx_refund_fee: bitcoin::Amount, - #[serde(with = "::bitcoin::amount::serde::as_sat")] pub tx_cancel_fee: bitcoin::Amount, + tx_partial_refund_fee: Option, + tx_refund_amnesty_fee: Option, } impl State2 { @@ -534,11 +531,10 @@ pub struct State3 { /// This signature is voluntarily revealed by alice. tx_refund_amnesty_sig: Option, min_monero_confirmations: u64, - #[serde(with = "::bitcoin::amount::serde::as_sat")] tx_redeem_fee: bitcoin::Amount, - #[serde(with = "::bitcoin::amount::serde::as_sat")] tx_refund_fee: bitcoin::Amount, - #[serde(with = "::bitcoin::amount::serde::as_sat")] + tx_partial_refund_fee: Option, + tx_refund_amnesty_fee: Option, tx_cancel_fee: bitcoin::Amount, } @@ -681,11 +677,10 @@ pub struct State4 { tx_refund_amnesty_sig: Option, monero_wallet_restore_blockheight: BlockHeight, lock_transfer_proof: TransferProof, - #[serde(with = "::bitcoin::amount::serde::as_sat")] tx_redeem_fee: bitcoin::Amount, - #[serde(with = "::bitcoin::amount::serde::as_sat")] tx_refund_fee: bitcoin::Amount, - #[serde(with = "::bitcoin::amount::serde::as_sat")] + tx_partial_refund_fee: Option, + tx_refund_amnesty_fee: Option, tx_cancel_fee: bitcoin::Amount, } @@ -861,10 +856,10 @@ pub struct State6 { /// It allows Bob to retrieve the refund fee introduced in the PR. /// This signature is voluntarily revealed by alice. tx_refund_amnesty_sig: Option, - #[serde(with = "::bitcoin::amount::serde::as_sat")] pub tx_refund_fee: bitcoin::Amount, - #[serde(with = "::bitcoin::amount::serde::as_sat")] pub tx_cancel_fee: bitcoin::Amount, + tx_partial_refund_fee: Option, + tx_refund_amnesty_fee: Option, } impl State6 { diff --git a/swap-machine/src/common/mod.rs b/swap-machine/src/common/mod.rs index 05e1a99c4a..07f61002e3 100644 --- a/swap-machine/src/common/mod.rs +++ b/swap-machine/src/common/mod.rs @@ -35,13 +35,9 @@ pub struct Message0 { pub v_b: monero::PrivateViewKey, #[serde(with = "swap_serde::bitcoin::address_serde")] pub refund_address: bitcoin::Address, - #[serde(with = "::bitcoin::amount::serde::as_sat")] pub tx_refund_fee: bitcoin::Amount, - #[serde(with = "::bitcoin::amount::serde::as_sat")] pub tx_partial_refund_fee: bitcoin::Amount, - #[serde(with = "::bitcoin::amount::serde::as_sat")] pub tx_refund_amnesty_fee: bitcoin::Amount, - #[serde(with = "::bitcoin::amount::serde::as_sat")] pub tx_cancel_fee: bitcoin::Amount, } @@ -57,11 +53,8 @@ pub struct Message1 { pub redeem_address: bitcoin::Address, #[serde(with = "swap_serde::bitcoin::address_serde")] pub punish_address: bitcoin::Address, - #[serde(with = "::bitcoin::amount::serde::as_sat")] pub tx_redeem_fee: bitcoin::Amount, - #[serde(with = "::bitcoin::amount::serde::as_sat")] pub tx_punish_fee: bitcoin::Amount, - #[serde(with = "::bitcoin::amount::serde::as_sat")] /// The amount of Bitcoin that Bob not get refunded unless Alice decides so. /// Introduced in [#675](https://github.com/eigenwallet/core/pull/675) to combat spam. pub amnesty_amount: bitcoin::Amount, diff --git a/swap-p2p/src/protocols/quote.rs b/swap-p2p/src/protocols/quote.rs index a7fe31f84b..7e126a87b9 100644 --- a/swap-p2p/src/protocols/quote.rs +++ b/swap-p2p/src/protocols/quote.rs @@ -27,16 +27,13 @@ impl AsRef for BidQuoteProtocol { #[typeshare] pub struct BidQuote { /// The price at which the maker is willing to buy at. - #[serde(with = "::bitcoin::amount::serde::as_sat")] #[typeshare(serialized_as = "number")] pub price: bitcoin::Amount, /// The minimum quantity the maker is willing to buy. /// #[typeshare(serialized_as = "number")] - #[serde(with = "::bitcoin::amount::serde::as_sat")] #[typeshare(serialized_as = "number")] pub min_quantity: bitcoin::Amount, /// The maximum quantity the maker is willing to buy. - #[serde(with = "::bitcoin::amount::serde::as_sat")] #[typeshare(serialized_as = "number")] pub max_quantity: bitcoin::Amount, } diff --git a/swap-p2p/src/protocols/swap_setup.rs b/swap-p2p/src/protocols/swap_setup.rs index 60f50ceb1b..33b654781e 100644 --- a/swap-p2p/src/protocols/swap_setup.rs +++ b/swap-p2p/src/protocols/swap_setup.rs @@ -43,7 +43,6 @@ pub struct BlockchainNetwork { #[derive(Serialize, Deserialize, Debug, Clone)] pub struct SpotPriceRequest { - #[serde(with = "::bitcoin::amount::serde::as_sat")] pub btc: bitcoin::Amount, pub blockchain_network: BlockchainNetwork, } @@ -58,19 +57,14 @@ pub enum SpotPriceResponse { pub enum SpotPriceError { NoSwapsAccepted, AmountBelowMinimum { - #[serde(with = "::bitcoin::amount::serde::as_sat")] min: bitcoin::Amount, - #[serde(with = "::bitcoin::amount::serde::as_sat")] buy: bitcoin::Amount, }, AmountAboveMaximum { - #[serde(with = "::bitcoin::amount::serde::as_sat")] max: bitcoin::Amount, - #[serde(with = "::bitcoin::amount::serde::as_sat")] buy: bitcoin::Amount, }, BalanceTooLow { - #[serde(with = "::bitcoin::amount::serde::as_sat")] buy: bitcoin::Amount, }, BlockchainNetworkMismatch { diff --git a/swap/src/cli/api/request.rs b/swap/src/cli/api/request.rs index 95f92cbf3f..49b6f877b7 100644 --- a/swap/src/cli/api/request.rs +++ b/swap/src/cli/api/request.rs @@ -160,7 +160,7 @@ impl Request for MoneroRecoveryArgs { #[derive(Debug, Eq, PartialEq, Serialize, Deserialize)] pub struct WithdrawBtcArgs { #[typeshare(serialized_as = "number")] - #[serde(default, with = "::bitcoin::amount::serde::as_sat::opt")] + #[serde(default)] pub amount: Option, #[typeshare(serialized_as = "string")] #[serde(with = "swap_serde::bitcoin::address_serde")] @@ -171,7 +171,6 @@ pub struct WithdrawBtcArgs { #[derive(Serialize, Deserialize, Debug)] pub struct WithdrawBtcResponse { #[typeshare(serialized_as = "number")] - #[serde(with = "::bitcoin::amount::serde::as_sat")] pub amount: bitcoin::Amount, pub txid: String, } @@ -229,18 +228,14 @@ pub struct GetSwapInfoResponse { #[typeshare(serialized_as = "number")] pub xmr_amount: monero::Amount, #[typeshare(serialized_as = "number")] - #[serde(with = "::bitcoin::amount::serde::as_sat")] pub btc_amount: bitcoin::Amount, #[typeshare(serialized_as = "string")] pub tx_lock_id: Txid, #[typeshare(serialized_as = "number")] - #[serde(with = "::bitcoin::amount::serde::as_sat")] pub tx_cancel_fee: bitcoin::Amount, #[typeshare(serialized_as = "number")] - #[serde(with = "::bitcoin::amount::serde::as_sat")] pub tx_refund_fee: bitcoin::Amount, #[typeshare(serialized_as = "number")] - #[serde(with = "::bitcoin::amount::serde::as_sat")] pub tx_lock_fee: bitcoin::Amount, pub btc_refund_address: String, pub cancel_timelock: CancelTimelock, @@ -291,7 +286,6 @@ pub struct BalanceArgs { #[derive(Serialize, Deserialize, Debug, Clone)] pub struct BalanceResponse { #[typeshare(serialized_as = "number")] - #[serde(with = "::bitcoin::amount::serde::as_sat")] pub balance: bitcoin::Amount, } diff --git a/swap/src/cli/api/tauri_bindings.rs b/swap/src/cli/api/tauri_bindings.rs index c9cc2ad69a..22283c3438 100644 --- a/swap/src/cli/api/tauri_bindings.rs +++ b/swap/src/cli/api/tauri_bindings.rs @@ -61,10 +61,8 @@ pub struct ContextStatus { #[derive(Clone, Debug, Serialize, Deserialize)] pub struct LockBitcoinDetails { #[typeshare(serialized_as = "number")] - #[serde(with = "::bitcoin::amount::serde::as_sat")] pub btc_lock_amount: bitcoin::Amount, #[typeshare(serialized_as = "number")] - #[serde(with = "::bitcoin::amount::serde::as_sat")] pub btc_network_fee: bitcoin::Amount, #[typeshare(serialized_as = "number")] pub xmr_receive_amount: monero::Amount, @@ -79,7 +77,6 @@ pub struct SelectMakerDetails { #[typeshare(serialized_as = "string")] pub swap_id: Uuid, #[typeshare(serialized_as = "number")] - #[serde(with = "::bitcoin::amount::serde::as_sat")] pub btc_amount_to_swap: bitcoin::Amount, pub maker: QuoteWithAddress, } @@ -888,16 +885,13 @@ pub enum TauriSwapProgressEvent { #[typeshare(serialized_as = "string")] deposit_address: bitcoin::Address, #[typeshare(serialized_as = "number")] - #[serde(with = "::bitcoin::amount::serde::as_sat")] max_giveable: bitcoin::Amount, #[typeshare(serialized_as = "number")] - #[serde(with = "::bitcoin::amount::serde::as_sat")] min_bitcoin_lock_tx_fee: bitcoin::Amount, known_quotes: Vec, }, SwapSetupInflight { #[typeshare(serialized_as = "number")] - #[serde(with = "::bitcoin::amount::serde::as_sat")] btc_lock_amount: bitcoin::Amount, }, RetrievingMoneroBlockheight, From 2bc6b837f472914683c1e10b423be44fc965fda5 Mon Sep 17 00:00:00 2001 From: einliterflasche Date: Wed, 3 Dec 2025 15:50:07 +0100 Subject: [PATCH 017/113] add tx_partial_refund_fee and tx_refund_amnesty_fee to all bob and alice states + make them optional for db compatibility --- swap-machine/src/alice/mod.rs | 9 +- swap-machine/src/bob/mod.rs | 125 ++++++++++++++++------- swap-machine/src/common/mod.rs | 6 +- swap-p2p/src/protocols/swap_setup.rs | 2 +- swap-p2p/src/protocols/swap_setup/bob.rs | 26 +++-- swap/src/protocol/bob/swap.rs | 15 +-- 6 files changed, 123 insertions(+), 60 deletions(-) diff --git a/swap-machine/src/alice/mod.rs b/swap-machine/src/alice/mod.rs index 6cd0a0cc35..6a35ababba 100644 --- a/swap-machine/src/alice/mod.rs +++ b/swap-machine/src/alice/mod.rs @@ -242,8 +242,8 @@ impl State0 { tx_redeem_fee: self.tx_redeem_fee, tx_punish_fee: self.tx_punish_fee, tx_refund_fee: msg.tx_refund_fee, - tx_partial_refund_fee: msg.tx_partial_refund_fee, - tx_refund_amnesty_fee: msg.tx_refund_amnesty_fee, + tx_partial_refund_fee: Some(msg.tx_partial_refund_fee), + tx_refund_amnesty_fee: Some(msg.tx_refund_amnesty_fee), tx_cancel_fee: msg.tx_cancel_fee, }, )) @@ -347,8 +347,8 @@ pub struct State2 { tx_redeem_fee: bitcoin::Amount, tx_punish_fee: bitcoin::Amount, tx_refund_fee: bitcoin::Amount, - tx_partial_refund_fee: bitcoin::Amount, - tx_refund_amnesty_fee: bitcoin::Amount, + tx_partial_refund_fee: Option, + tx_refund_amnesty_fee: Option, tx_cancel_fee: bitcoin::Amount, } @@ -446,6 +446,7 @@ impl State2 { tx_punish_fee: self.tx_punish_fee, tx_refund_fee: self.tx_refund_fee, tx_partial_refund_fee: self.tx_partial_refund_fee, + tx_refund_amnesty_fee: self.tx_refund_amnesty_fee, tx_cancel_fee: self.tx_cancel_fee, }) } diff --git a/swap-machine/src/bob/mod.rs b/swap-machine/src/bob/mod.rs index 2ecdaa75f6..cee14ffe57 100644 --- a/swap-machine/src/bob/mod.rs +++ b/swap-machine/src/bob/mod.rs @@ -1,11 +1,11 @@ #![allow(non_snake_case)] -use crate::common::{Message0, Message1, Message2, Message3, Message4, CROSS_CURVE_PROOF_SYSTEM}; -use anyhow::{anyhow, bail, Context, Result}; +use crate::common::{CROSS_CURVE_PROOF_SYSTEM, Message0, Message1, Message2, Message3, Message4}; +use anyhow::{Context, Result, anyhow, bail}; use bitcoin_wallet::primitives::Subscription; +use ecdsa_fun::Signature; use ecdsa_fun::adaptor::{Adaptor, HashTranscript}; use ecdsa_fun::nonce::Deterministic; -use ecdsa_fun::Signature; use monero::BlockHeight; use rand::{CryptoRng, RngCore}; use serde::{Deserialize, Serialize}; @@ -14,11 +14,11 @@ use sigma_fun::ext::dl_secp256k1_ed25519_eq::CrossCurveDLEQProof; use std::fmt; use std::sync::Arc; use swap_core::bitcoin::{ - self, current_epoch, CancelTimelock, ExpiredTimelocks, PunishTimelock, Transaction, TxCancel, - TxLock, Txid, + self, CancelTimelock, ExpiredTimelocks, PunishTimelock, Transaction, TxCancel, TxLock, Txid, + current_epoch, }; -use swap_core::monero::primitives::WatchRequest; use swap_core::monero::ScalarExt; +use swap_core::monero::primitives::WatchRequest; use swap_core::monero::{self, TransferProof}; use swap_serde::bitcoin::address_serde; use uuid::Uuid; @@ -83,14 +83,14 @@ pub enum BobRefundType { tx_full_refund_encsig: bitcoin::EncryptedSignature, }, /// Alice has only signed the full refund transaction. - /// This is only used to maintain backwards compatibility for older swaps + /// This is only used to maintain backwards compatibility for older swaps /// from before the partial refund protocol change. /// See [#675](https://github.com/eigenwallet/core/pull/675). Legacy { // Serde raname keeps + untagged + flatten keeps this backwards compatible with old swaps in the database. #[serde(rename = "tx_refund_encsig")] tx_full_refund_encsig: bitcoin::EncryptedSignature, - } + }, } impl fmt::Display for BobState { @@ -181,8 +181,8 @@ pub struct State0 { punish_timelock: PunishTimelock, refund_address: bitcoin::Address, min_monero_confirmations: u64, - tx_partial_refund_fee: bitcoin::Amount, - tx_refund_amnesty_fee: bitcoin::Amount, + tx_partial_refund_fee: Option, + tx_refund_amnesty_fee: Option, tx_refund_fee: bitcoin::Amount, tx_cancel_fee: bitcoin::Amount, tx_lock_fee: bitcoin::Amount, @@ -228,16 +228,16 @@ impl State0 { punish_timelock, refund_address, min_monero_confirmations, - tx_partial_refund_fee, - tx_refund_amnesty_fee, + tx_partial_refund_fee: Some(tx_partial_refund_fee), + tx_refund_amnesty_fee: Some(tx_refund_amnesty_fee), tx_refund_fee, tx_cancel_fee, tx_lock_fee, } } - pub fn next_message(&self) -> Message0 { - Message0 { + pub fn next_message(&self) -> Result { + Ok(Message0 { swap_id: self.swap_id, B: self.b.public(), S_b_monero: self.S_b_monero, @@ -246,10 +246,14 @@ impl State0 { v_b: self.v_b, refund_address: self.refund_address.clone(), tx_refund_fee: self.tx_refund_fee, - tx_partial_refund_fee: self.tx_partial_refund_fee, - tx_refund_amnesty_fee: self.tx_refund_amnesty_fee, + tx_partial_refund_fee: self + .tx_partial_refund_fee + .context("tx_partial_refund_fee missing but required to setup swap")?, + tx_refund_amnesty_fee: self + .tx_refund_amnesty_fee + .context("tx_refund_amnesty_fee missing but required to setup swap")?, tx_cancel_fee: self.tx_cancel_fee, - } + }) } pub async fn receive( @@ -327,8 +331,8 @@ pub struct State1 { punish_address: bitcoin::Address, tx_lock: bitcoin::TxLock, min_monero_confirmations: u64, - tx_partial_refund_fee: bitcoin::Amount, - tx_refund_amnesty_fee: bitcoin::Amount, + tx_partial_refund_fee: Option, + tx_refund_amnesty_fee: Option, tx_redeem_fee: bitcoin::Amount, tx_refund_fee: bitcoin::Amount, tx_punish_fee: bitcoin::Amount, @@ -351,8 +355,15 @@ impl State1 { self.tx_cancel_fee, )?; - let tx_partial_refund = - bitcoin::TxPartialRefund::new(&tx_cancel, &self.refund_address, self.A, self.b.public(), self.btc_amnesty_amount, self.tx_partial_refund_fee)?; + let tx_partial_refund = bitcoin::TxPartialRefund::new( + &tx_cancel, + &self.refund_address, + self.A, + self.b.public(), + self.btc_amnesty_amount, + self.tx_partial_refund_fee + .context("tx_partial_refund_fee missing but required to setup swap")?, + )?; bitcoin::verify_sig(&self.A, &tx_cancel.digest(), &msg.tx_cancel_sig)?; bitcoin::verify_encsig( @@ -364,7 +375,8 @@ impl State1 { // Verify the full refund signature if it is present if let Some(tx_full_refund_encsig) = &msg.tx_full_refund_encsig { - let tx_full_refund = bitcoin::TxFullRefund::new(&tx_cancel, &self.refund_address, self.tx_refund_fee); + let tx_full_refund = + bitcoin::TxFullRefund::new(&tx_cancel, &self.refund_address, self.tx_refund_fee); bitcoin::verify_encsig( self.A, bitcoin::PublicKey::from(self.s_b.to_secpfun_scalar()), @@ -375,11 +387,19 @@ impl State1 { // Verify the refund amnesty signature if it is present if let Some(tx_refund_amnesty_sig) = &msg.tx_refund_amnesty_sig { - let tx_refund_amnesty = bitcoin::TxRefundAmnesty::new(&tx_partial_refund, &self.refund_address, self.tx_refund_amnesty_fee); + let tx_refund_amnesty = bitcoin::TxRefundAmnesty::new( + &tx_partial_refund, + &self.refund_address, + self.tx_refund_amnesty_fee + .context("tx_refund_amnesty_fee missing but required to setup swap")?, + ); bitcoin::verify_sig(&self.A, &tx_refund_amnesty.digest(), tx_refund_amnesty_sig)?; } - let bob_refund_type = BobRefundType::from_possibly_full_refund_sig(msg.tx_partial_refund_encsig, msg.tx_full_refund_encsig); + let bob_refund_type = BobRefundType::from_possibly_full_refund_sig( + msg.tx_partial_refund_encsig, + msg.tx_full_refund_encsig, + ); Ok(State2 { A: self.A, b: self.b, @@ -400,6 +420,8 @@ impl State1 { min_monero_confirmations: self.min_monero_confirmations, tx_redeem_fee: self.tx_redeem_fee, tx_refund_fee: self.tx_refund_fee, + tx_partial_refund_fee: self.tx_partial_refund_fee, + tx_refund_amnesty_fee: self.tx_refund_amnesty_fee, tx_punish_fee: self.tx_punish_fee, tx_cancel_fee: self.tx_cancel_fee, }) @@ -497,6 +519,8 @@ impl State2 { min_monero_confirmations: self.min_monero_confirmations, tx_redeem_fee: self.tx_redeem_fee, tx_refund_fee: self.tx_refund_fee, + tx_partial_refund_fee: self.tx_partial_refund_fee, + tx_refund_amnesty_fee: self.tx_refund_amnesty_fee, tx_cancel_fee: self.tx_cancel_fee, }, self.tx_lock, @@ -582,6 +606,8 @@ impl State3 { tx_redeem_fee: self.tx_redeem_fee, tx_refund_fee: self.tx_refund_fee, tx_cancel_fee: self.tx_cancel_fee, + tx_partial_refund_fee: self.tx_partial_refund_fee, + tx_refund_amnesty_fee: self.tx_refund_amnesty_fee, } } @@ -601,6 +627,8 @@ impl State3 { tx_refund_amnesty_sig: self.tx_refund_amnesty_sig.clone(), tx_refund_fee: self.tx_refund_fee, tx_cancel_fee: self.tx_cancel_fee, + tx_partial_refund_fee: self.tx_partial_refund_fee, + tx_refund_amnesty_fee: self.tx_refund_amnesty_fee, xmr: self.xmr, } } @@ -783,6 +811,8 @@ impl State4 { tx_refund_fee: self.tx_refund_fee, tx_cancel_fee: self.tx_cancel_fee, xmr: self.xmr, + tx_partial_refund_fee: self.tx_partial_refund_fee, + tx_refund_amnesty_fee: self.tx_refund_amnesty_fee, } } @@ -945,15 +975,16 @@ impl State6 { } pub fn signed_full_refund_transaction(&self) -> Result { - let tx_full_refund_encsig = self.bob_refund_type.tx_full_refund_encsig().context("Can't sign full refund transaction because we don't have the necessary signature")?; - + let tx_full_refund_encsig = self.bob_refund_type.tx_full_refund_encsig().context( + "Can't sign full refund transaction because we don't have the necessary signature", + )?; + let tx_refund = self.construct_tx_refund()?; let adaptor = Adaptor::, Deterministic>::default(); let sig_b = self.b.sign(tx_refund.digest()); - let sig_a = - adaptor.decrypt_signature(&self.s_b.to_secpfun_scalar(), tx_full_refund_encsig); + let sig_a = adaptor.decrypt_signature(&self.s_b.to_secpfun_scalar(), tx_full_refund_encsig); let signed_tx_refund = tx_refund.add_signatures((self.A, sig_a), (self.b.public(), sig_b))?; @@ -1003,30 +1034,50 @@ impl State6 { } impl BobRefundType { - pub fn from_possibly_full_refund_sig(partial_refund_encsig: bitcoin::EncryptedSignature, full_refund_encsig: Option) -> Self { + pub fn from_possibly_full_refund_sig( + partial_refund_encsig: bitcoin::EncryptedSignature, + full_refund_encsig: Option, + ) -> Self { if let Some(full_refund_encsig) = full_refund_encsig { - Self::Full { tx_partial_refund_encsig: partial_refund_encsig, tx_full_refund_encsig: full_refund_encsig } + Self::Full { + tx_partial_refund_encsig: partial_refund_encsig, + tx_full_refund_encsig: full_refund_encsig, + } } else { - Self::Partial { tx_partial_refund_encsig: partial_refund_encsig } + Self::Partial { + tx_partial_refund_encsig: partial_refund_encsig, + } } } - + pub fn from_partial_refund_sig(partial_refund_encsig: bitcoin::EncryptedSignature) -> Self { - Self::Partial { tx_partial_refund_encsig: partial_refund_encsig } + Self::Partial { + tx_partial_refund_encsig: partial_refund_encsig, + } } pub fn tx_full_refund_encsig(&self) -> Option { match self { BobRefundType::Partial { .. } => None, - BobRefundType::Full { tx_full_refund_encsig, .. } => Some(tx_full_refund_encsig.clone()), - BobRefundType::Legacy { tx_full_refund_encsig } => Some(tx_full_refund_encsig.clone()), + BobRefundType::Full { + tx_full_refund_encsig, + .. + } => Some(tx_full_refund_encsig.clone()), + BobRefundType::Legacy { + tx_full_refund_encsig, + } => Some(tx_full_refund_encsig.clone()), } } pub fn tx_partial_refund_encsig(&self) -> Option { match self { - BobRefundType::Partial { tx_partial_refund_encsig } => Some(tx_partial_refund_encsig.clone()), - BobRefundType::Full { tx_partial_refund_encsig, .. } => Some(tx_partial_refund_encsig.clone()), + BobRefundType::Partial { + tx_partial_refund_encsig, + } => Some(tx_partial_refund_encsig.clone()), + BobRefundType::Full { + tx_partial_refund_encsig, + .. + } => Some(tx_partial_refund_encsig.clone()), BobRefundType::Legacy { .. } => None, } } diff --git a/swap-machine/src/common/mod.rs b/swap-machine/src/common/mod.rs index 07f61002e3..0c0f316945 100644 --- a/swap-machine/src/common/mod.rs +++ b/swap-machine/src/common/mod.rs @@ -1,15 +1,15 @@ -use crate::alice::is_complete as alice_is_complete; use crate::alice::AliceState; -use crate::bob::is_complete as bob_is_complete; +use crate::alice::is_complete as alice_is_complete; use crate::bob::BobState; +use crate::bob::is_complete as bob_is_complete; use anyhow::Result; use async_trait::async_trait; use conquer_once::Lazy; use libp2p::{Multiaddr, PeerId}; use serde::{Deserialize, Serialize}; use sha2::Sha256; -use sigma_fun::ext::dl_secp256k1_ed25519_eq::{CrossCurveDLEQ, CrossCurveDLEQProof}; use sigma_fun::HashTranscript; +use sigma_fun::ext::dl_secp256k1_ed25519_eq::{CrossCurveDLEQ, CrossCurveDLEQProof}; use std::convert::TryInto; use swap_core::bitcoin; use swap_core::monero::{self, MoneroAddressPool}; diff --git a/swap-p2p/src/protocols/swap_setup.rs b/swap-p2p/src/protocols/swap_setup.rs index 33b654781e..09bb31984b 100644 --- a/swap-p2p/src/protocols/swap_setup.rs +++ b/swap-p2p/src/protocols/swap_setup.rs @@ -18,7 +18,7 @@ pub mod protocol { use libp2p::swarm::Stream; use void::Void; - use super::vendor_from_fn::{from_fn, FromFnUpgrade}; + use super::vendor_from_fn::{FromFnUpgrade, from_fn}; pub fn new() -> SwapSetup { from_fn( diff --git a/swap-p2p/src/protocols/swap_setup/bob.rs b/swap-p2p/src/protocols/swap_setup/bob.rs index ca1541a90c..474c5a97c1 100644 --- a/swap-p2p/src/protocols/swap_setup/bob.rs +++ b/swap-p2p/src/protocols/swap_setup/bob.rs @@ -1,12 +1,12 @@ use crate::out_event; use crate::protocols::swap_setup::{ - protocol, BlockchainNetwork, SpotPriceError, SpotPriceResponse, + BlockchainNetwork, SpotPriceError, SpotPriceResponse, protocol, }; use anyhow::{Context, Result}; use bitcoin_wallet::BitcoinWallet; -use futures::future::{BoxFuture, OptionFuture}; use futures::AsyncWriteExt; use futures::FutureExt; +use futures::future::{BoxFuture, OptionFuture}; use libp2p::core::upgrade; use libp2p::swarm::behaviour::ConnectionEstablished; use libp2p::swarm::dial_opts::{DialOpts, PeerCondition}; @@ -26,7 +26,7 @@ use swap_machine::bob::{State0, State2}; use swap_machine::common::{Message1, Message3}; use uuid::Uuid; -use super::{read_cbor_message, write_cbor_message, SpotPriceRequest}; +use super::{SpotPriceRequest, read_cbor_message, write_cbor_message}; #[allow(missing_debug_implementations)] pub struct Behaviour { @@ -537,7 +537,6 @@ async fn run_swap_setup( new_swap_request.tx_refund_fee, new_swap_request.tx_cancel_fee, new_swap_request.tx_lock_fee, - ); tracing::trace!( @@ -545,9 +544,14 @@ async fn run_swap_setup( "Transitioned into state0 during swap setup", ); - write_cbor_message(&mut substream, state0.next_message()) - .await - .context("Failed to send state0 message to Alice")?; + write_cbor_message( + &mut substream, + state0 + .next_message() + .context("Couldn't generate Message0")?, + ) + .await + .context("Failed to send state0 message to Alice")?; let message1 = read_cbor_message::(&mut substream) .await .context("Failed to read message1 from Alice")?; @@ -620,10 +624,14 @@ pub enum Error { max: bitcoin::Amount, buy: bitcoin::Amount, }, - #[error("Seller's XMR balance is currently too low to fulfill the swap request to buy {buy}, please try again later")] + #[error( + "Seller's XMR balance is currently too low to fulfill the swap request to buy {buy}, please try again later" + )] BalanceTooLow { buy: bitcoin::Amount }, - #[error("Seller blockchain network {asb:?} setup did not match your blockchain network setup {cli:?}")] + #[error( + "Seller blockchain network {asb:?} setup did not match your blockchain network setup {cli:?}" + )] BlockchainNetworkMismatch { cli: BlockchainNetwork, asb: BlockchainNetwork, diff --git a/swap/src/protocol/bob/swap.rs b/swap/src/protocol/bob/swap.rs index f968506a35..86c3cbb88a 100644 --- a/swap/src/protocol/bob/swap.rs +++ b/swap/src/protocol/bob/swap.rs @@ -11,7 +11,9 @@ use crate::protocol::{bob, Database}; use anyhow::{Context as AnyContext, Result}; use std::sync::Arc; use std::time::Duration; -use swap_core::bitcoin::{ExpiredTimelocks, TxCancel, TxFullRefund}; +use swap_core::bitcoin::{ + ExpiredTimelocks, TxCancel, TxFullRefund, TxPartialRefund, TxRefundAmnesty, +}; use swap_core::monero::TxHash; use swap_env::env; use swap_machine::bob::State5; @@ -112,17 +114,18 @@ async fn next_state( let tx_refund_fee = bitcoin_wallet .estimate_fee(TxFullRefund::weight(), Some(btc_amount)) .await?; - - // At this point we don't know how high btc_amnesty_amount is. + + // At this point we don't know how high btc_amnesty_amount is. // This means we don't know how large the amount of the partial refund and amnesty transactions will be. // We therefore specify the same upper limit on tx fees as for the other transactions, even though // the maximum fee percentage might be higher due to that. - let tx_partial_refund_fee = bitcoin_wallet. - estimate_fee(TxPartialRefund::weight(), Some(btc_amount)).await?; + let tx_partial_refund_fee = bitcoin_wallet + .estimate_fee(TxPartialRefund::weight(), Some(btc_amount)) + .await?; let tx_refund_amnesty_fee = bitcoin_wallet .estimate_fee(TxRefundAmnesty::weight(), Some(btc_amount)) .await?; - + // Emit an event to tauri that we are negotiating with the maker to lock the Bitcoin event_emitter.emit_swap_progress_event( swap_id, From 4f73fba0494d072f5c52e51a94505685ed6eedf8 Mon Sep 17 00:00:00 2001 From: einliterflasche Date: Wed, 3 Dec 2025 16:09:45 +0100 Subject: [PATCH 018/113] fix merge conflict --- .../alert/SwapStatusAlert/SwapStatusAlert.tsx | 15 ++++----------- 1 file changed, 4 insertions(+), 11 deletions(-) diff --git a/src-gui/src/renderer/components/alert/SwapStatusAlert/SwapStatusAlert.tsx b/src-gui/src/renderer/components/alert/SwapStatusAlert/SwapStatusAlert.tsx index cb29ef6ffe..7fb8594fdd 100644 --- a/src-gui/src/renderer/components/alert/SwapStatusAlert/SwapStatusAlert.tsx +++ b/src-gui/src/renderer/components/alert/SwapStatusAlert/SwapStatusAlert.tsx @@ -247,11 +247,7 @@ export default function SwapStatusAlert({ swap: GetSwapInfoResponseExt | null; onlyShowIfUnusualAmountOfTimeHasPassed?: boolean; }) { -<<<<<<< HEAD const swapId = swap?.swap_id ?? null; -======= - const swapId = swap?.swap_id ?? ""; ->>>>>>> b539b31b (fix react bug nullish check) const timelock = useAppSelector(selectSwapTimelock(swapId)); const isRunning = useIsSpecificSwapRunning(swapId); @@ -296,14 +292,11 @@ export default function SwapStatusAlert({ ) ) : ( <> -<<<<<<< HEAD Swap {swap.swap_id} is not running -======= - Swap {swapId} is not running ->>>>>>> b539b31b (fix react bug nullish check) - )} - + ) + } + {timelock && } - + ); } From e809b42aba514a6ed145bc6975ccbceb4aa4b290 Mon Sep 17 00:00:00 2001 From: einliterflasche Date: Wed, 3 Dec 2025 16:27:14 +0100 Subject: [PATCH 019/113] add PartiallyRefunded, AmnestyPublished, AmnestyConfirmed states for Bob --- swap-db/src/bob.rs | 13 +++++++++++++ swap-machine/src/bob/mod.rs | 11 ++++++++++- 2 files changed, 23 insertions(+), 1 deletion(-) diff --git a/swap-db/src/bob.rs b/swap-db/src/bob.rs index cbb79974bb..4742b754d5 100644 --- a/swap-db/src/bob.rs +++ b/swap-db/src/bob.rs @@ -44,6 +44,8 @@ pub enum Bob { BtcCancelled(bob::State6), BtcRefundPublished(bob::State6), BtcEarlyRefundPublished(bob::State6), + BtcPartiallyRefunded(bob::State6), + BtcAmnestyPublished(bob::State6), Done(BobEndState), } @@ -53,6 +55,7 @@ pub enum BobEndState { XmrRedeemed { tx_lock_id: bitcoin::Txid }, BtcRefunded(Box), BtcEarlyRefunded(Box), + BtcAmnestyConfirmed(Box), } impl From for Bob { @@ -108,6 +111,11 @@ impl From for Bob { BobState::BtcEarlyRefunded(state6) => { Bob::Done(BobEndState::BtcEarlyRefunded(Box::new(state6))) } + BobState::BtcPartiallyRefunded(state6) => Bob::BtcPartiallyRefunded(state6), + BobState::BtcAmnestyPublished(state6) => Bob::BtcAmnestyPublished(state6), + BobState::BtcAmnestyConfirmed(state6) => { + Bob::Done(BobEndState::BtcAmnestyConfirmed(Box::new(state6))) + } BobState::SafelyAborted => Bob::Done(BobEndState::SafelyAborted), } } @@ -157,6 +165,8 @@ impl From for BobState { Bob::CancelTimelockExpired(state6) => BobState::CancelTimelockExpired(state6), Bob::BtcCancelled(state6) => BobState::BtcCancelled(state6), Bob::BtcRefundPublished(state6) => BobState::BtcRefundPublished(state6), + Bob::BtcPartiallyRefunded(state6) => BobState::BtcPartiallyRefunded(state6), + Bob::BtcAmnestyPublished(state6) => BobState::BtcAmnestyPublished(state6), Bob::BtcEarlyRefundPublished(state6) => BobState::BtcEarlyRefundPublished(state6), Bob::BtcPunished { state, tx_lock_id } => BobState::BtcPunished { state, tx_lock_id }, Bob::Done(end_state) => match end_state { @@ -164,6 +174,7 @@ impl From for BobState { BobEndState::XmrRedeemed { tx_lock_id } => BobState::XmrRedeemed { tx_lock_id }, BobEndState::BtcRefunded(state6) => BobState::BtcRefunded(*state6), BobEndState::BtcEarlyRefunded(state6) => BobState::BtcEarlyRefunded(*state6), + BobEndState::BtcAmnestyConfirmed(state6) => BobState::BtcAmnestyConfirmed(*state6), }, } } @@ -188,6 +199,8 @@ impl fmt::Display for Bob { Bob::Done(end_state) => write!(f, "Done: {}", end_state), Bob::EncSigSent { .. } => f.write_str("Encrypted signature sent"), Bob::BtcPunished { .. } => f.write_str("Bitcoin punished"), + Bob::BtcPartiallyRefunded { .. } => f.write_str("Bitcoin partially refunded"), + Bob::BtcAmnestyPublished { .. } => f.write_str("Bitcoin amnesty published"), } } } diff --git a/swap-machine/src/bob/mod.rs b/swap-machine/src/bob/mod.rs index cee14ffe57..7c7d72af75 100644 --- a/swap-machine/src/bob/mod.rs +++ b/swap-machine/src/bob/mod.rs @@ -55,6 +55,9 @@ pub enum BobState { BtcEarlyRefundPublished(State6), BtcRefunded(State6), BtcEarlyRefunded(State6), + BtcPartiallyRefunded(State6), + BtcAmnestyPublished(State6), + BtcAmnestyConfirmed(State6), XmrRedeemed { tx_lock_id: bitcoin::Txid, }, @@ -116,6 +119,9 @@ impl fmt::Display for BobState { BobState::XmrRedeemed { .. } => write!(f, "xmr is redeemed"), BobState::BtcPunished { .. } => write!(f, "btc is punished"), BobState::BtcEarlyRefunded { .. } => write!(f, "btc is early refunded"), + BobState::BtcPartiallyRefunded { .. } => write!(f, "btc is partially refunded"), + BobState::BtcAmnestyPublished { .. } => write!(f, "btc amnesty is published"), + BobState::BtcAmnestyConfirmed { .. } => write!(f, "btc amnesty is confirmed"), BobState::SafelyAborted => write!(f, "safely aborted"), } } @@ -143,7 +149,10 @@ impl BobState { BobState::CancelTimelockExpired(state) | BobState::BtcCancelled(state) | BobState::BtcRefundPublished(state) - | BobState::BtcEarlyRefundPublished(state) => { + | BobState::BtcEarlyRefundPublished(state) + | BobState::BtcPartiallyRefunded(state) + | BobState::BtcAmnestyPublished(state) + | BobState::BtcAmnestyConfirmed(state) => { Some(state.expired_timelock(bitcoin_wallet.as_ref()).await?) } BobState::BtcPunished { .. } => Some(ExpiredTimelocks::Punish), From 9851f357cce2b446b3505e76ca81cde0a89d3054 Mon Sep 17 00:00:00 2001 From: einliterflasche Date: Wed, 3 Dec 2025 16:32:20 +0100 Subject: [PATCH 020/113] add Bob::BtcPartialRefundPublished state --- swap-db/src/bob.rs | 6 ++++++ swap-machine/src/bob/mod.rs | 5 +++++ 2 files changed, 11 insertions(+) diff --git a/swap-db/src/bob.rs b/swap-db/src/bob.rs index 4742b754d5..71cfac23ca 100644 --- a/swap-db/src/bob.rs +++ b/swap-db/src/bob.rs @@ -44,6 +44,7 @@ pub enum Bob { BtcCancelled(bob::State6), BtcRefundPublished(bob::State6), BtcEarlyRefundPublished(bob::State6), + BtcPartialRefundPublished(bob::State6), BtcPartiallyRefunded(bob::State6), BtcAmnestyPublished(bob::State6), Done(BobEndState), @@ -103,6 +104,7 @@ impl From for Bob { BobState::BtcCancelled(state6) => Bob::BtcCancelled(state6), BobState::BtcRefundPublished(state6) => Bob::BtcRefundPublished(state6), BobState::BtcEarlyRefundPublished(state6) => Bob::BtcEarlyRefundPublished(state6), + BobState::BtcPartialRefundPublished(state6) => Bob::BtcPartialRefundPublished(state6), BobState::BtcPunished { state, tx_lock_id } => Bob::BtcPunished { state, tx_lock_id }, BobState::BtcRefunded(state6) => Bob::Done(BobEndState::BtcRefunded(Box::new(state6))), BobState::XmrRedeemed { tx_lock_id } => { @@ -165,6 +167,7 @@ impl From for BobState { Bob::CancelTimelockExpired(state6) => BobState::CancelTimelockExpired(state6), Bob::BtcCancelled(state6) => BobState::BtcCancelled(state6), Bob::BtcRefundPublished(state6) => BobState::BtcRefundPublished(state6), + Bob::BtcPartialRefundPublished(state6) => BobState::BtcPartialRefundPublished(state6), Bob::BtcPartiallyRefunded(state6) => BobState::BtcPartiallyRefunded(state6), Bob::BtcAmnestyPublished(state6) => BobState::BtcAmnestyPublished(state6), Bob::BtcEarlyRefundPublished(state6) => BobState::BtcEarlyRefundPublished(state6), @@ -195,6 +198,9 @@ impl fmt::Display for Bob { Bob::BtcCancelled(_) => f.write_str("Bitcoin refundable"), Bob::BtcRefundPublished { .. } => f.write_str("Bitcoin refund published"), Bob::BtcEarlyRefundPublished { .. } => f.write_str("Bitcoin early refund published"), + Bob::BtcPartialRefundPublished { .. } => { + f.write_str("Bitcoin partially refund published") + } Bob::BtcRedeemed(_) => f.write_str("Monero redeemable"), Bob::Done(end_state) => write!(f, "Done: {}", end_state), Bob::EncSigSent { .. } => f.write_str("Encrypted signature sent"), diff --git a/swap-machine/src/bob/mod.rs b/swap-machine/src/bob/mod.rs index 7c7d72af75..6d4fb3c383 100644 --- a/swap-machine/src/bob/mod.rs +++ b/swap-machine/src/bob/mod.rs @@ -53,6 +53,7 @@ pub enum BobState { BtcCancelled(State6), BtcRefundPublished(State6), BtcEarlyRefundPublished(State6), + BtcPartialRefundPublished(State6), BtcRefunded(State6), BtcEarlyRefunded(State6), BtcPartiallyRefunded(State6), @@ -115,6 +116,9 @@ impl fmt::Display for BobState { BobState::BtcCancelled(..) => write!(f, "btc is cancelled"), BobState::BtcRefundPublished { .. } => write!(f, "btc refund is published"), BobState::BtcEarlyRefundPublished { .. } => write!(f, "btc early refund is published"), + BobState::BtcPartialRefundPublished { .. } => { + write!(f, "btc partially refund is published") + } BobState::BtcRefunded(..) => write!(f, "btc is refunded"), BobState::XmrRedeemed { .. } => write!(f, "xmr is redeemed"), BobState::BtcPunished { .. } => write!(f, "btc is punished"), @@ -150,6 +154,7 @@ impl BobState { | BobState::BtcCancelled(state) | BobState::BtcRefundPublished(state) | BobState::BtcEarlyRefundPublished(state) + | BobState::BtcPartialRefundPublished(state) | BobState::BtcPartiallyRefunded(state) | BobState::BtcAmnestyPublished(state) | BobState::BtcAmnestyConfirmed(state) => { From 2b6b05b3ed2ba0fd34f4a09ea91d35073876b285 Mon Sep 17 00:00:00 2001 From: einliterflasche Date: Wed, 3 Dec 2025 17:05:18 +0100 Subject: [PATCH 021/113] add new states to match arms --- swap/src/cli/cancel_and_refund.rs | 8 ++++++++ swap/src/protocol/bob/swap.rs | 4 ++++ 2 files changed, 12 insertions(+) diff --git a/swap/src/cli/cancel_and_refund.rs b/swap/src/cli/cancel_and_refund.rs index 3bd008f258..3d41a7c7e3 100644 --- a/swap/src/cli/cancel_and_refund.rs +++ b/swap/src/cli/cancel_and_refund.rs @@ -67,6 +67,10 @@ pub async fn cancel( BobState::BtcCancelled(state6) => state6, BobState::BtcRefundPublished(state6) => state6, BobState::BtcEarlyRefundPublished(state6) => state6, + BobState::BtcPartialRefundPublished(state6) => state6, + BobState::BtcPartiallyRefunded(state6) => state6, + BobState::BtcAmnestyConfirmed(state6) => state6, + BobState::BtcAmnestyPublished(state6) => state6, BobState::Started { .. } | BobState::BtcRedeemed(_) @@ -170,6 +174,10 @@ pub async fn refund( BobState::BtcRefunded(state6) => state6, BobState::BtcRefundPublished(state6) => state6, BobState::BtcEarlyRefundPublished(state6) => state6, + BobState::BtcPartialRefundPublished(state6) => state6, + BobState::BtcPartiallyRefunded(state6) => state6, + BobState::BtcAmnestyPublished(state6) => state6, + BobState::BtcAmnestyConfirmed(state6) => state6, BobState::Started { .. } | BobState::SwapSetupCompleted(_) | BobState::BtcRedeemed(_) diff --git a/swap/src/protocol/bob/swap.rs b/swap/src/protocol/bob/swap.rs index 86c3cbb88a..c65bf71f82 100644 --- a/swap/src/protocol/bob/swap.rs +++ b/swap/src/protocol/bob/swap.rs @@ -1052,6 +1052,10 @@ async fn next_state( } // TODO: Emit a Tauri event here BobState::BtcEarlyRefunded(state) => BobState::BtcEarlyRefunded(state), + BobState::BtcPartialRefundPublished(state) + | BobState::BtcPartiallyRefunded(state) + | BobState::BtcAmnestyPublished(state) + | BobState::BtcAmnestyConfirmed(state) => todo!(), BobState::SafelyAborted => BobState::SafelyAborted, BobState::XmrRedeemed { tx_lock_id } => { event_emitter.emit_swap_progress_event( From b70275d2bc9ed66b304394a70f2b3f89f3566614 Mon Sep 17 00:00:00 2001 From: einliterflasche Date: Wed, 3 Dec 2025 17:09:51 +0100 Subject: [PATCH 022/113] fix compilation errors in tests --- swap/src/bitcoin/wallet.rs | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/swap/src/bitcoin/wallet.rs b/swap/src/bitcoin/wallet.rs index 07bb0fb269..1d8d8abafc 100644 --- a/swap/src/bitcoin/wallet.rs +++ b/swap/src/bitcoin/wallet.rs @@ -3773,7 +3773,7 @@ mod swap_core_bitcoin_tests { tx_lock_fee, ); - let message0 = bob_state0.next_message(); + let message0 = bob_state0.next_message().unwrap(); let (_, alice_state1) = alice_state0.receive(message0).unwrap(); let alice_message1 = alice_state1.next_message(); @@ -3814,7 +3814,11 @@ mod swap_core_bitcoin_tests { assert_weight(redeem_transaction, TxRedeem::weight().to_wu(), "TxRedeem"); assert_weight(cancel_transaction, TxCancel::weight().to_wu(), "TxCancel"); assert_weight(punish_transaction, TxPunish::weight().to_wu(), "TxPunish"); - assert_weight(refund_transaction, TxFullRefund::weight().to_wu(), "TxRefund"); + assert_weight( + refund_transaction, + TxFullRefund::weight().to_wu(), + "TxRefund", + ); // Test TxEarlyRefund transaction let early_refund_transaction = alice_state3 @@ -3883,7 +3887,7 @@ mod swap_core_bitcoin_tests { ); // Complete the state machine up to State3 - let message0 = bob_state0.next_message(); + let message0 = bob_state0.next_message().unwrap(); let (_, alice_state1) = alice_state0.receive(message0).unwrap(); let alice_message1 = alice_state1.next_message(); @@ -3940,7 +3944,10 @@ mod swap_core_bitcoin_tests { // It should be the same as TxRedeem and TxRefund weights since they have similar structure assert_eq!(TxEarlyRefund::weight() as u64, TxRedeem::weight().to_wu()); - assert_eq!(TxEarlyRefund::weight() as u64, TxFullRefund::weight().to_wu()); + assert_eq!( + TxEarlyRefund::weight() as u64, + TxFullRefund::weight().to_wu() + ); } // Weights fluctuate because of the length of the signatures. Valid ecdsa From c6e1385dfce13482f5a4fab97121c04c7d437101 Mon Sep 17 00:00:00 2001 From: einliterflasche Date: Sat, 6 Dec 2025 11:42:42 +0100 Subject: [PATCH 023/113] add btc_amnesty_amount (optional) to all Bob states --- swap-machine/src/bob/mod.rs | 19 ++++++++++++++++--- 1 file changed, 16 insertions(+), 3 deletions(-) diff --git a/swap-machine/src/bob/mod.rs b/swap-machine/src/bob/mod.rs index 6d4fb3c383..39e835f1f5 100644 --- a/swap-machine/src/bob/mod.rs +++ b/swap-machine/src/bob/mod.rs @@ -309,7 +309,7 @@ impl State0 { S_a_bitcoin: msg.S_a_bitcoin, v, xmr: self.xmr, - btc_amnesty_amount: msg.amnesty_amount, + btc_amnesty_amount: Some(msg.amnesty_amount), cancel_timelock: self.cancel_timelock, punish_timelock: self.punish_timelock, refund_address: self.refund_address, @@ -337,7 +337,7 @@ pub struct State1 { S_a_bitcoin: bitcoin::PublicKey, v: monero::PrivateViewKey, xmr: monero::Amount, - btc_amnesty_amount: bitcoin::Amount, + btc_amnesty_amount: Option, cancel_timelock: CancelTimelock, punish_timelock: PunishTimelock, refund_address: bitcoin::Address, @@ -374,7 +374,8 @@ impl State1 { &self.refund_address, self.A, self.b.public(), - self.btc_amnesty_amount, + self.btc_amnesty_amount + .context("btc_amnesty_amount is missing but required to create TxPartialRefund")?, self.tx_partial_refund_fee .context("tx_partial_refund_fee missing but required to setup swap")?, )?; @@ -422,6 +423,7 @@ impl State1 { S_a_bitcoin: self.S_a_bitcoin, v: self.v, xmr: self.xmr, + btc_amnesty_amount: self.btc_amnesty_amount, cancel_timelock: self.cancel_timelock, punish_timelock: self.punish_timelock, refund_address: self.refund_address, @@ -452,6 +454,7 @@ pub struct State2 { S_a_bitcoin: bitcoin::PublicKey, v: monero::PrivateViewKey, pub xmr: monero::Amount, + btc_amnesty_amount: Option, pub cancel_timelock: CancelTimelock, pub punish_timelock: PunishTimelock, #[serde(with = "address_serde")] @@ -522,6 +525,7 @@ impl State2 { S_a_bitcoin: self.S_a_bitcoin, v: self.v, xmr: self.xmr, + btc_amnesty_amount: self.btc_amnesty_amount, cancel_timelock: self.cancel_timelock, punish_timelock: self.punish_timelock, refund_address: self.refund_address, @@ -552,6 +556,7 @@ pub struct State3 { S_a_bitcoin: bitcoin::PublicKey, v: monero::PrivateViewKey, xmr: monero::Amount, + btc_amnesty_amount: Option, pub cancel_timelock: CancelTimelock, punish_timelock: PunishTimelock, #[serde(with = "address_serde")] @@ -607,6 +612,7 @@ impl State3 { S_a_bitcoin: self.S_a_bitcoin, v: self.v, xmr: self.xmr, + btc_amnesty_amount: self.btc_amnesty_amount, cancel_timelock: self.cancel_timelock, punish_timelock: self.punish_timelock, refund_address: self.refund_address, @@ -644,6 +650,7 @@ impl State3 { tx_partial_refund_fee: self.tx_partial_refund_fee, tx_refund_amnesty_fee: self.tx_refund_amnesty_fee, xmr: self.xmr, + btc_ammesty_amount: self.btc_amnesty_amount, } } @@ -701,6 +708,7 @@ pub struct State4 { S_a_bitcoin: bitcoin::PublicKey, v: monero::PrivateViewKey, xmr: monero::Amount, + btc_amnesty_amount: Option, pub cancel_timelock: CancelTimelock, punish_timelock: PunishTimelock, #[serde(with = "address_serde")] @@ -750,6 +758,7 @@ impl State4 { s_b: self.s_b, v: self.v, xmr: self.xmr, + btc_amnesty_amount: self.btc_amnesty_amount, tx_lock: self.tx_lock.clone(), monero_wallet_restore_blockheight: self.monero_wallet_restore_blockheight, lock_transfer_proof: self.lock_transfer_proof.clone(), @@ -825,6 +834,7 @@ impl State4 { tx_refund_fee: self.tx_refund_fee, tx_cancel_fee: self.tx_cancel_fee, xmr: self.xmr, + btc_ammesty_amount: self.btc_amnesty_amount, tx_partial_refund_fee: self.tx_partial_refund_fee, tx_refund_amnesty_fee: self.tx_refund_amnesty_fee, } @@ -842,6 +852,7 @@ pub struct State5 { s_b: monero::Scalar, v: monero::PrivateViewKey, xmr: monero::Amount, + btc_amnesty_amount: Option, tx_lock: bitcoin::TxLock, pub monero_wallet_restore_blockheight: BlockHeight, pub lock_transfer_proof: TransferProof, @@ -885,6 +896,7 @@ pub struct State6 { s_b: monero::Scalar, v: monero::PrivateViewKey, pub xmr: monero::Amount, + btc_ammesty_amount: Option, pub monero_wallet_restore_blockheight: BlockHeight, pub cancel_timelock: CancelTimelock, punish_timelock: PunishTimelock, @@ -1026,6 +1038,7 @@ impl State6 { s_b: self.s_b, v: self.v, xmr: self.xmr, + btc_amnesty_amount: self.btc_ammesty_amount, tx_lock: self.tx_lock.clone(), monero_wallet_restore_blockheight: self.monero_wallet_restore_blockheight, lock_transfer_proof, From 2b6e81acd5d304e1e28cd0def7a28caeef8e88ad Mon Sep 17 00:00:00 2001 From: einliterflasche Date: Sat, 6 Dec 2025 11:43:18 +0100 Subject: [PATCH 024/113] add construct_tx_partial_refund and signed_partial_refund_transaction to Bob's State6 --- swap-machine/src/bob/mod.rs | 35 +++++++++++++++++++++++++++++++++++ 1 file changed, 35 insertions(+) diff --git a/swap-machine/src/bob/mod.rs b/swap-machine/src/bob/mod.rs index 39e835f1f5..81bfecd8ca 100644 --- a/swap-machine/src/bob/mod.rs +++ b/swap-machine/src/bob/mod.rs @@ -1018,6 +1018,41 @@ impl State6 { Ok(signed_tx_refund) } + pub fn construct_tx_partial_refund(&self) -> Result { + let tx_cancel = self.construct_tx_cancel()?; + bitcoin::TxPartialRefund::new( + &tx_cancel, + &self.refund_address, + self.A, + self.b.public(), + self.btc_ammesty_amount + .context("Can't construct TxPartialRefund because btc_amnesty_amount is missing")?, + self.tx_partial_refund_fee.context( + "Can't construct TxPartialRefund because tx_partial_refund_fee is missing", + )?, + ) + } + + pub fn signed_partial_refund_transaction(&self) -> Result { + let tx_partial_refund_encsig = self + .bob_refund_type + .tx_partial_refund_encsig() + .context("Can't finalize TxPartialRefund because Alice's encsig is missing")?; + + let tx_partial_refund = self.construct_tx_partial_refund()?; + + let adaptor = Adaptor::, Deterministic>::default(); + + let sig_b = self.b.sign(tx_partial_refund.digest()); + let sig_a = + adaptor.decrypt_signature(&self.s_b.to_secpfun_scalar(), tx_partial_refund_encsig); + + let signed_tx_partial_refund = + tx_partial_refund.add_signatures((self.A, sig_a), (self.b.public(), sig_b))?; + + Ok(signed_tx_partial_refund) + } + pub fn construct_tx_early_refund(&self) -> bitcoin::TxEarlyRefund { bitcoin::TxEarlyRefund::new(&self.tx_lock, &self.refund_address, self.tx_refund_fee) } From 55f413f2f2883a82fdbeefcb2217f4c823310b63 Mon Sep 17 00:00:00 2001 From: einliterflasche Date: Sat, 6 Dec 2025 14:19:15 +0100 Subject: [PATCH 025/113] alice: actually send TxPartialRefund encsig --- swap-machine/src/alice/mod.rs | 39 +++++++++++++--------- swap-p2p/src/protocols/swap_setup/alice.rs | 22 +++++++----- swap/src/bitcoin/wallet.rs | 4 +-- 3 files changed, 39 insertions(+), 26 deletions(-) diff --git a/swap-machine/src/alice/mod.rs b/swap-machine/src/alice/mod.rs index 6a35ababba..6c94fbec5d 100644 --- a/swap-machine/src/alice/mod.rs +++ b/swap-machine/src/alice/mod.rs @@ -1,19 +1,19 @@ #![allow(non_snake_case)] -use crate::common::{Message0, Message1, Message2, Message3, Message4, CROSS_CURVE_PROOF_SYSTEM}; -use anyhow::{anyhow, bail, Context, Result}; +use crate::common::{CROSS_CURVE_PROOF_SYSTEM, Message0, Message1, Message2, Message3, Message4}; +use anyhow::{Context, Result, anyhow, bail}; use rand::{CryptoRng, RngCore}; use serde::{Deserialize, Serialize}; use sigma_fun::ext::dl_secp256k1_ed25519_eq::CrossCurveDLEQProof; use std::fmt; use std::sync::Arc; use swap_core::bitcoin::{ - current_epoch, CancelTimelock, ExpiredTimelocks, PunishTimelock, Transaction, TxCancel, - TxEarlyRefund, TxPunish, TxRedeem, TxFullRefund, Txid, + CancelTimelock, ExpiredTimelocks, PunishTimelock, Transaction, TxCancel, TxEarlyRefund, + TxFullRefund, TxPunish, TxRedeem, Txid, current_epoch, }; use swap_core::monero; -use swap_core::monero::primitives::{BlockHeight, TransferProof, TransferRequest, WatchRequest}; use swap_core::monero::ScalarExt; +use swap_core::monero::primitives::{BlockHeight, TransferProof, TransferRequest, WatchRequest}; use swap_env::env::Config; use uuid::Uuid; @@ -353,7 +353,7 @@ pub struct State2 { } impl State2 { - pub fn next_message(&self) -> Message3 { + pub fn next_message(&self) -> Result { let tx_cancel = swap_core::bitcoin::TxCancel::new( &self.tx_lock, self.cancel_timelock, @@ -363,23 +363,32 @@ impl State2 { ) .expect("valid cancel tx"); - let tx_refund = - swap_core::bitcoin::TxFullRefund::new(&tx_cancel, &self.refund_address, self.tx_refund_fee); - // Alice encsigns the refund transaction(bitcoin) digest with Bob's monero - // pubkey(S_b). The refund transaction spends the output of - // tx_lock_bitcoin to Bob's refund address. + let tx_partial_refund = swap_core::bitcoin::TxPartialRefund::new( + &tx_cancel, + &self.refund_address, + self.a.public(), + self.B, + self.btc_amnesty_amount, + self.tx_refund_fee, + )?; + // Alice encsigns the partial refund transaction(bitcoin) digest with Bob's monero + // pubkey(S_b). The partial refund transaction spends the output of + // tx_lock_bitcoin to Bob's refund address (except for the amnesty output). // recover(encsign(a, S_b, d), sign(a, d), S_b) = s_b where d is a digest, (a, // A) is alice's keypair and (s_b, S_b) is bob's keypair. - let tx_refund_encsig = self.a.encsign(self.S_b_bitcoin, tx_refund.digest()); + let tx_refund_encsig = self.a.encsign(self.S_b_bitcoin, tx_partial_refund.digest()); let tx_cancel_sig = self.a.sign(tx_cancel.digest()); - let (tx_full_refund_encsig, tx_refund_amnesty_sig) = todo!("TODO: Implement system for Alice to decide commiting to full refund"); - Message3 { + // TODO: When to send these? + let tx_full_refund_encsig = None; + let tx_refund_amnesty_sig = None; + + Ok(Message3 { tx_cancel_sig, tx_partial_refund_encsig: tx_refund_encsig, tx_full_refund_encsig, tx_refund_amnesty_sig, - } + }) } pub fn receive(self, msg: Message4) -> Result { diff --git a/swap-p2p/src/protocols/swap_setup/alice.rs b/swap-p2p/src/protocols/swap_setup/alice.rs index 7bbb39d583..7fd57d14a9 100644 --- a/swap-p2p/src/protocols/swap_setup/alice.rs +++ b/swap-p2p/src/protocols/swap_setup/alice.rs @@ -1,12 +1,12 @@ use crate::out_event; use crate::protocols::swap_setup; use crate::protocols::swap_setup::{ - protocol, BlockchainNetwork, SpotPriceError, SpotPriceRequest, SpotPriceResponse, + BlockchainNetwork, SpotPriceError, SpotPriceRequest, SpotPriceResponse, protocol, }; -use anyhow::{anyhow, Context, Result}; -use futures::future::{BoxFuture, OptionFuture}; +use anyhow::{Context, Result, anyhow}; use futures::AsyncWriteExt; use futures::FutureExt; +use futures::future::{BoxFuture, OptionFuture}; use libp2p::core::upgrade; use libp2p::swarm::handler::ConnectionEvent; use libp2p::swarm::{ConnectionHandler, ConnectionId}; @@ -320,7 +320,9 @@ where env_config, min_buy, max_buy, - latest_rate.map_err(|error| Box::new(error) as Box), + latest_rate.map_err(|error| { + Box::new(error) as Box + }), ), ); @@ -503,8 +505,7 @@ async fn run_swap_setup( }); } - let rate = - latest_rate.map_err(Error::LatestRateFetchFailed)?; + let rate = latest_rate.map_err(Error::LatestRateFetchFailed)?; let xmr = rate .sell_quote(btc) .map_err(Error::SellQuoteCalculationFailed)?; @@ -572,9 +573,12 @@ async fn run_swap_setup( .receive(message2) .context("Failed to transition state1 -> state2 using message2")?; - swap_setup::write_cbor_message(&mut substream, state2.next_message()) - .await - .context("Failed to send message3")?; + swap_setup::write_cbor_message( + &mut substream, + state2.next_message().context("Couldn't produce Message3")?, + ) + .await + .context("Failed to send message3")?; let message4 = swap_setup::read_cbor_message::(&mut substream) .await diff --git a/swap/src/bitcoin/wallet.rs b/swap/src/bitcoin/wallet.rs index 1d8d8abafc..a64c097db4 100644 --- a/swap/src/bitcoin/wallet.rs +++ b/swap/src/bitcoin/wallet.rs @@ -3785,7 +3785,7 @@ mod swap_core_bitcoin_tests { let bob_message2 = bob_state1.next_message(); let alice_state2 = alice_state1.receive(bob_message2).unwrap(); - let alice_message3 = alice_state2.next_message(); + let alice_message3 = alice_state2.next_message().unwrap(); let bob_state2 = bob_state1.receive(alice_message3).unwrap(); let bob_message4 = bob_state2.next_message(); @@ -3898,7 +3898,7 @@ mod swap_core_bitcoin_tests { let bob_message2 = bob_state1.next_message(); let alice_state2 = alice_state1.receive(bob_message2).unwrap(); - let alice_message3 = alice_state2.next_message(); + let alice_message3 = alice_state2.next_message().unwrap(); let bob_state2 = bob_state1.receive(alice_message3).unwrap(); let bob_message4 = bob_state2.next_message(); From c3c000c4d1f0280fd0c285bfaf3ffe553ad18505 Mon Sep 17 00:00:00 2001 From: einliterflasche Date: Mon, 8 Dec 2025 10:57:49 +0100 Subject: [PATCH 026/113] add ensure_broadcasted function to BitcoinWallet trait --- bitcoin-wallet/src/lib.rs | 6 +++++ bitcoin-wallet/src/primitives.rs | 16 +++++++++++- swap/src/bitcoin/wallet.rs | 44 +++++++++++++++++++++++++++----- 3 files changed, 59 insertions(+), 7 deletions(-) diff --git a/bitcoin-wallet/src/lib.rs b/bitcoin-wallet/src/lib.rs index b98be65a19..a5ffbe8ff7 100644 --- a/bitcoin-wallet/src/lib.rs +++ b/bitcoin-wallet/src/lib.rs @@ -41,6 +41,12 @@ pub trait BitcoinWallet: Send + Sync { kind: &str, ) -> Result<(Txid, Subscription)>; + async fn ensure_broadcasted( + &self, + transaction: bitcoin::Transaction, + kind: &str, + ) -> Result<(Txid, Subscription)>; + async fn sync(&self) -> Result<()>; async fn subscribe_to(&self, tx: Box) -> Subscription; diff --git a/bitcoin-wallet/src/primitives.rs b/bitcoin-wallet/src/primitives.rs index 4755c1369c..2a84cd1008 100644 --- a/bitcoin-wallet/src/primitives.rs +++ b/bitcoin-wallet/src/primitives.rs @@ -1,5 +1,5 @@ use anyhow::Result; -use bitcoin::{FeeRate, ScriptBuf, Txid}; +use bitcoin::{FeeRate, ScriptBuf, Transaction, Txid}; /// An object that can estimate fee rates and minimum relay fees. pub trait EstimateFeeRate { @@ -192,6 +192,20 @@ impl Watchable for (Txid, ScriptBuf) { } } +// Watching a transaction by watching it's first output +// (because Electrum expects a script hash). This works +// because all outputs of a transaction have the same status. +impl Watchable for Transaction { + fn id(&self) -> Txid { + self.compute_txid() + } + + fn script(&self) -> ScriptBuf { + debug_assert!(!self.output.is_empty(), "Transaction has no outputs"); + self.output[0].script_pubkey.clone() + } +} + impl Watchable for &dyn Watchable { fn id(&self) -> Txid { (*self).id() diff --git a/swap/src/bitcoin/wallet.rs b/swap/src/bitcoin/wallet.rs index a64c097db4..4da262c4db 100644 --- a/swap/src/bitcoin/wallet.rs +++ b/swap/src/bitcoin/wallet.rs @@ -668,12 +668,7 @@ impl Wallet { let txid = transaction.compute_txid(); // to watch for confirmations, watching a single output is enough - let subscription = self - .subscribe_to(Box::new(( - txid, - transaction.output[0].script_pubkey.clone(), - ))) - .await; + let subscription = self.subscribe_to(Box::new(transaction.clone())).await; let client = self.electrum_client.lock().await; let broadcast_results = client @@ -736,6 +731,27 @@ impl Wallet { Ok((txid, subscription)) } + /// Check if a [`Transaction`] has been broadcasted and if not, broadcast it. + pub async fn ensure_broadcasted( + &self, + transaction: bitcoin::Transaction, + kind: &str, + ) -> Result<(Txid, Subscription)> { + let tx_status = self.status_of_script(&transaction).await?; + + // If it's already been broadcasted, return a subscription to it + if matches!( + tx_status, + ScriptStatus::InMempool | ScriptStatus::Confirmed(_) + ) { + let subscription = self.subscribe_to(Box::new(transaction.clone())).await; + return Ok((transaction.compute_txid(), subscription)); + } + + // Otherwise broadcast it + self.broadcast(transaction, kind).await + } + pub async fn get_raw_transaction(&self, txid: Txid) -> Result>> { self.get_tx(txid) .await @@ -2116,6 +2132,14 @@ impl bitcoin_wallet::BitcoinWallet for Wallet { Wallet::broadcast(self, transaction, kind).await } + async fn ensure_broadcasted( + &self, + transaction: bitcoin::Transaction, + kind: &str, + ) -> Result<(Txid, Subscription)> { + Wallet::ensure_broadcasted(self, transaction, kind).await + } + async fn sync(&self) -> Result<()> { Wallet::sync(self).await } @@ -2947,6 +2971,14 @@ mod tests { unimplemented!("stub method called erroneously") } + async fn ensure_broadcasted( + &self, + transaction: bitcoin::Transaction, + kind: &str, + ) -> Result<(Txid, Subscription)> { + unimplemented!("stub method called erroneously") + } + async fn sync(&self) -> Result<()> { unimplemented!("stub method called erroneously") } From 1f83ff7c8f139f50bddea6d4c639920543c34725 Mon Sep 17 00:00:00 2001 From: einliterflasche Date: Mon, 8 Dec 2025 11:44:10 +0100 Subject: [PATCH 027/113] bob state machine: publish best bitcoin refund transaction depending on which signatures we have --- swap-machine/src/bob/mod.rs | 51 +++++++++++++++++++++++++------ swap/src/cli/cancel_and_refund.rs | 5 ++- swap/src/protocol/bob/swap.rs | 17 ++++++++--- 3 files changed, 59 insertions(+), 14 deletions(-) diff --git a/swap-machine/src/bob/mod.rs b/swap-machine/src/bob/mod.rs index 81bfecd8ca..39ec933efc 100644 --- a/swap-machine/src/bob/mod.rs +++ b/swap-machine/src/bob/mod.rs @@ -69,9 +69,14 @@ pub enum BobState { SafelyAborted, } -/// !IMPORTANT: This enum must be #[untagged] and maintain the field names in order to be backwards compatible +/// An enum abstracting over the different combination of +/// refund signatures Alice could have sent us. +/// Maintains backward compatibility with old swaps (which only had the full refund signature). +/// +/// # IMPORTANT +/// This enum must be `#[untagged]` and maintain the field names in order to be backwards compatible /// with the database. -/// Changing any of that is a breaking change +/// Changing any of that is a breaking change. #[derive(Debug, Clone, PartialEq, Deserialize, Serialize)] #[serde(untagged)] pub enum BobRefundType { @@ -907,7 +912,7 @@ pub struct State6 { /// This field was changed in [#675](https://github.com/eigenwallet/core/pull/675). /// It boils down to the same json except that it now may also contain a partial refund signature. #[serde(flatten)] - bob_refund_type: BobRefundType, + pub bob_refund_type: BobRefundType, /// This field was added in [#675](https://github.com/eigenwallet/core/pull/675). /// It allows Bob to retrieve the refund fee introduced in the PR. /// This signature is voluntarily revealed by alice. @@ -980,15 +985,43 @@ impl State6 { Ok((tx_id, subscription)) } - pub async fn publish_refund_btc( + /// Publish the best refund transaction based on the refund signatures Alice has sent us. + /// This is either `TxFullRefund` or `TxPartialRefund`. + /// Returns the corresponding state (`BtcRefundPublished`/`PartialRefundPublished`) on success. + pub async fn publish_best_btc_refund_tx( &self, bitcoin_wallet: &dyn bitcoin_wallet::BitcoinWallet, - ) -> Result { - let signed_tx_refund = self.signed_full_refund_transaction()?; - let signed_tx_refund_txid = signed_tx_refund.compute_txid(); - bitcoin_wallet.broadcast(signed_tx_refund, "refund").await?; + ) -> Result<(Txid, BobState)> { + if self.bob_refund_type.tx_full_refund_encsig().is_some() { + tracing::info!("Have the full refund signature, attempting full Bitcoin refund"); + let tx_full_refund = self + .signed_full_refund_transaction() + .context("Couldn't construct TxFullRefund")?; + let (txid, _) = bitcoin_wallet + .ensure_broadcasted(tx_full_refund, "full refund") + .await + .context("Couldn't ensure broadcast of TxFullRefund")?; + + return Ok((txid, BobState::BtcRefundPublished(self.clone()))); + } + + if self.bob_refund_type.tx_partial_refund_encsig().is_some() { + tracing::info!( + "Don't have the full refund signature, attempting partial Bitcoin refund" + ); + + let tx_partial_refund = self + .signed_partial_refund_transaction() + .context("Couldn't construct TxPartialRefund")?; + let (txid, _) = bitcoin_wallet + .ensure_broadcasted(tx_partial_refund, "partial refund") + .await + .context("Couldn't ensure broadcast of TxPartialRefund")?; + + return Ok((txid, BobState::BtcPartialRefundPublished(self.clone()))); + } - Ok(signed_tx_refund_txid) + unreachable!("We always have either the partial or full refund encsig"); } pub fn construct_tx_refund(&self) -> Result { diff --git a/swap/src/cli/cancel_and_refund.rs b/swap/src/cli/cancel_and_refund.rs index 3d41a7c7e3..be7c8391ca 100644 --- a/swap/src/cli/cancel_and_refund.rs +++ b/swap/src/cli/cancel_and_refund.rs @@ -194,7 +194,10 @@ pub async fn refund( tracing::info!(%swap_id, "Attempting to manually refund swap"); // Attempt to just publish the refund transaction - match state6.publish_refund_btc(bitcoin_wallet.as_ref()).await { + match state6 + .publish_best_btc_refund_tx(bitcoin_wallet.as_ref()) + .await + { Ok(_) => { let state = BobState::BtcRefunded(state6); db.insert_latest_state(swap_id, state.clone().into()) diff --git a/swap/src/protocol/bob/swap.rs b/swap/src/protocol/bob/swap.rs index c65bf71f82..07f92eb7b7 100644 --- a/swap/src/protocol/bob/swap.rs +++ b/swap/src/protocol/bob/swap.rs @@ -772,6 +772,7 @@ async fn next_state( let bitcoin_wallet_for_retry = bitcoin_wallet.clone(); let state_for_retry = state.clone(); + retry( "Check timelocks and try to refund", || { @@ -785,11 +786,19 @@ async fn next_state( ))) } ExpiredTimelocks::Cancel { .. } => { - let btc_refund_txid = state.publish_refund_btc(&*bitcoin_wallet).await.context("Failed to publish refund transaction after ensuring cancel timelock has expired and refund timelock has not expired").map_err(backoff::Error::transient)?; - - tracing::info!(%btc_refund_txid, "Refunded our Bitcoin"); + // Publish the best Bitcoin refund transaction we can sign: + // - either full refund, if alice sent use that signature (prioritized) + // - or just partial refund. + // `publish_best_btc_refund_tx` returns the appropriate state depending on which + // one was published. It also logs the refund type. + let (txid, next_state) = state.publish_best_btc_refund_tx(&*bitcoin_wallet) + .await + .context("Couldn't publish Bitcoin refund tx") + .map_err(backoff::Error::transient)?; - Ok(BobState::BtcRefundPublished(state.clone())) + tracing::info!(%txid, "Published Bitcoin refund transaction"); + + Ok(next_state) } ExpiredTimelocks::Punish => { let tx_lock_id = state.tx_lock_id(); From e0341e973d7e1cee377799af47195cae13487a16 Mon Sep 17 00:00:00 2001 From: einliterflasche Date: Mon, 8 Dec 2025 11:56:35 +0100 Subject: [PATCH 028/113] inline publish_best_btc_refund_tx --- swap/src/protocol/bob/swap.rs | 38 ++++++++++++++++++++++++++--------- 1 file changed, 29 insertions(+), 9 deletions(-) diff --git a/swap/src/protocol/bob/swap.rs b/swap/src/protocol/bob/swap.rs index 07f92eb7b7..8d8a69be96 100644 --- a/swap/src/protocol/bob/swap.rs +++ b/swap/src/protocol/bob/swap.rs @@ -789,16 +789,36 @@ async fn next_state( // Publish the best Bitcoin refund transaction we can sign: // - either full refund, if alice sent use that signature (prioritized) // - or just partial refund. - // `publish_best_btc_refund_tx` returns the appropriate state depending on which - // one was published. It also logs the refund type. - let (txid, next_state) = state.publish_best_btc_refund_tx(&*bitcoin_wallet) - .await - .context("Couldn't publish Bitcoin refund tx") - .map_err(backoff::Error::transient)?; - tracing::info!(%txid, "Published Bitcoin refund transaction"); - - Ok(next_state) + if state.bob_refund_type.tx_full_refund_encsig().is_some() { + tracing::info!("Have the full refund signature, attempting full Bitcoin refund"); + let tx_full_refund = state + .signed_full_refund_transaction() + .context("Couldn't construct TxFullRefund")?; + let (txid, _) = bitcoin_wallet + .ensure_broadcasted(tx_full_refund, "full refund") + .await + .context("Couldn't ensure broadcast of TxFullRefund")?; + tracing::info!(%txid, "Successfully published full bitcoin refund transaction"); + return Ok(BobState::BtcRefundPublished(state.clone())); + } + + if state.bob_refund_type.tx_partial_refund_encsig().is_some() { + tracing::info!( + "Don't have the full refund signature, attempting partial Bitcoin refund" + ); + let tx_partial_refund = state + .signed_partial_refund_transaction() + .context("Couldn't construct TxPartialRefund")?; + let (txid, _) = bitcoin_wallet + .ensure_broadcasted(tx_partial_refund, "partial refund") + .await + .context("Couldn't ensure broadcast of TxPartialRefund")?; + tracing::info!(%txid, "Successfully published partial bitcoin refund transaction"); + return Ok(BobState::BtcPartialRefundPublished(state.clone())); + } + + unreachable!("We always have either the partial or full refund encsig"); } ExpiredTimelocks::Punish => { let tx_lock_id = state.tx_lock_id(); From 651a5fab2200fff08fce43481f301911d9636151 Mon Sep 17 00:00:00 2001 From: einliterflasche Date: Mon, 8 Dec 2025 12:00:26 +0100 Subject: [PATCH 029/113] rename BobRefundType to RefundSignatures --- swap-machine/src/bob/mod.rs | 46 ++++++++++++++++++----------------- swap/src/protocol/bob/swap.rs | 4 +-- 2 files changed, 26 insertions(+), 24 deletions(-) diff --git a/swap-machine/src/bob/mod.rs b/swap-machine/src/bob/mod.rs index 39ec933efc..5238cbed17 100644 --- a/swap-machine/src/bob/mod.rs +++ b/swap-machine/src/bob/mod.rs @@ -79,7 +79,7 @@ pub enum BobState { /// Changing any of that is a breaking change. #[derive(Debug, Clone, PartialEq, Deserialize, Serialize)] #[serde(untagged)] -pub enum BobRefundType { +pub enum RefundSignatures { /// Alice has only signed the partial refund transaction (most cases). Partial { tx_partial_refund_encsig: bitcoin::EncryptedSignature, @@ -416,7 +416,7 @@ impl State1 { bitcoin::verify_sig(&self.A, &tx_refund_amnesty.digest(), tx_refund_amnesty_sig)?; } - let bob_refund_type = BobRefundType::from_possibly_full_refund_sig( + let refund_signatures = RefundSignatures::from_possibly_full_refund_sig( msg.tx_partial_refund_encsig, msg.tx_full_refund_encsig, ); @@ -436,7 +436,7 @@ impl State1 { punish_address: self.punish_address, tx_lock: self.tx_lock, tx_cancel_sig_a: msg.tx_cancel_sig, - bob_refund_type, + refund_signatures, tx_refund_amnesty_sig: msg.tx_refund_amnesty_sig, min_monero_confirmations: self.min_monero_confirmations, tx_redeem_fee: self.tx_redeem_fee, @@ -473,7 +473,7 @@ pub struct State2 { /// This field was changed in [#675](https://github.com/eigenwallet/core/pull/675). /// It boils down to the same json except that it now may also contain a partial refund signature. #[serde(flatten)] - bob_refund_type: BobRefundType, + refund_signatures: RefundSignatures, /// This field was added in [#675](https://github.com/eigenwallet/core/pull/675). /// It allows Bob to retrieve the refund fee introduced in the PR. /// This signature is voluntarily revealed by alice. @@ -537,7 +537,7 @@ impl State2 { redeem_address: self.redeem_address, tx_lock: self.tx_lock.clone(), tx_cancel_sig_a: self.tx_cancel_sig_a, - bob_refund_type: self.bob_refund_type, + refund_signatures: self.refund_signatures, tx_refund_amnesty_sig: self.tx_refund_amnesty_sig, min_monero_confirmations: self.min_monero_confirmations, tx_redeem_fee: self.tx_redeem_fee, @@ -570,10 +570,12 @@ pub struct State3 { redeem_address: bitcoin::Address, pub tx_lock: bitcoin::TxLock, tx_cancel_sig_a: Signature, + /// The (encrypted) signatures Alice sent us for the Bitcoin refund transaction(s). + /// /// This field was changed in [#675](https://github.com/eigenwallet/core/pull/675). /// It boils down to the same json except that it now may also contain a partial refund signature. #[serde(flatten)] - bob_refund_type: BobRefundType, + refund_signatures: RefundSignatures, /// This field was added in [#675](https://github.com/eigenwallet/core/pull/675). /// It allows Bob to retrieve the refund fee introduced in the PR. /// This signature is voluntarily revealed by alice. @@ -624,7 +626,7 @@ impl State3 { redeem_address: self.redeem_address, tx_lock: self.tx_lock, tx_cancel_sig_a: self.tx_cancel_sig_a, - bob_refund_type: self.bob_refund_type, + refund_signatures: self.refund_signatures, tx_refund_amnesty_sig: self.tx_refund_amnesty_sig, monero_wallet_restore_blockheight, lock_transfer_proof, @@ -648,7 +650,7 @@ impl State3 { refund_address: self.refund_address.clone(), tx_lock: self.tx_lock.clone(), tx_cancel_sig_a: self.tx_cancel_sig_a.clone(), - bob_refund_type: self.bob_refund_type.clone(), + refund_signatures: self.refund_signatures.clone(), tx_refund_amnesty_sig: self.tx_refund_amnesty_sig.clone(), tx_refund_fee: self.tx_refund_fee, tx_cancel_fee: self.tx_cancel_fee, @@ -725,7 +727,7 @@ pub struct State4 { /// This field was changed in [#675](https://github.com/eigenwallet/core/pull/675). /// It boils down to the same json except that it now may also contain a partial refund signature. #[serde(flatten)] - bob_refund_type: BobRefundType, + refund_signatures: RefundSignatures, /// This field was added in [#675](https://github.com/eigenwallet/core/pull/675). /// It allows Bob to retrieve the refund fee introduced in the PR. /// This signature is voluntarily revealed by alice. @@ -834,7 +836,7 @@ impl State4 { refund_address: self.refund_address, tx_lock: self.tx_lock, tx_cancel_sig_a: self.tx_cancel_sig_a, - bob_refund_type: self.bob_refund_type, + refund_signatures: self.refund_signatures, tx_refund_amnesty_sig: self.tx_refund_amnesty_sig, tx_refund_fee: self.tx_refund_fee, tx_cancel_fee: self.tx_cancel_fee, @@ -912,7 +914,7 @@ pub struct State6 { /// This field was changed in [#675](https://github.com/eigenwallet/core/pull/675). /// It boils down to the same json except that it now may also contain a partial refund signature. #[serde(flatten)] - pub bob_refund_type: BobRefundType, + pub refund_signatures: RefundSignatures, /// This field was added in [#675](https://github.com/eigenwallet/core/pull/675). /// It allows Bob to retrieve the refund fee introduced in the PR. /// This signature is voluntarily revealed by alice. @@ -992,7 +994,7 @@ impl State6 { &self, bitcoin_wallet: &dyn bitcoin_wallet::BitcoinWallet, ) -> Result<(Txid, BobState)> { - if self.bob_refund_type.tx_full_refund_encsig().is_some() { + if self.refund_signatures.tx_full_refund_encsig().is_some() { tracing::info!("Have the full refund signature, attempting full Bitcoin refund"); let tx_full_refund = self .signed_full_refund_transaction() @@ -1005,7 +1007,7 @@ impl State6 { return Ok((txid, BobState::BtcRefundPublished(self.clone()))); } - if self.bob_refund_type.tx_partial_refund_encsig().is_some() { + if self.refund_signatures.tx_partial_refund_encsig().is_some() { tracing::info!( "Don't have the full refund signature, attempting partial Bitcoin refund" ); @@ -1034,7 +1036,7 @@ impl State6 { } pub fn signed_full_refund_transaction(&self) -> Result { - let tx_full_refund_encsig = self.bob_refund_type.tx_full_refund_encsig().context( + let tx_full_refund_encsig = self.refund_signatures.tx_full_refund_encsig().context( "Can't sign full refund transaction because we don't have the necessary signature", )?; @@ -1068,7 +1070,7 @@ impl State6 { pub fn signed_partial_refund_transaction(&self) -> Result { let tx_partial_refund_encsig = self - .bob_refund_type + .refund_signatures .tx_partial_refund_encsig() .context("Can't finalize TxPartialRefund because Alice's encsig is missing")?; @@ -1128,7 +1130,7 @@ impl State6 { } } -impl BobRefundType { +impl RefundSignatures { pub fn from_possibly_full_refund_sig( partial_refund_encsig: bitcoin::EncryptedSignature, full_refund_encsig: Option, @@ -1153,12 +1155,12 @@ impl BobRefundType { pub fn tx_full_refund_encsig(&self) -> Option { match self { - BobRefundType::Partial { .. } => None, - BobRefundType::Full { + RefundSignatures::Partial { .. } => None, + RefundSignatures::Full { tx_full_refund_encsig, .. } => Some(tx_full_refund_encsig.clone()), - BobRefundType::Legacy { + RefundSignatures::Legacy { tx_full_refund_encsig, } => Some(tx_full_refund_encsig.clone()), } @@ -1166,14 +1168,14 @@ impl BobRefundType { pub fn tx_partial_refund_encsig(&self) -> Option { match self { - BobRefundType::Partial { + RefundSignatures::Partial { tx_partial_refund_encsig, } => Some(tx_partial_refund_encsig.clone()), - BobRefundType::Full { + RefundSignatures::Full { tx_partial_refund_encsig, .. } => Some(tx_partial_refund_encsig.clone()), - BobRefundType::Legacy { .. } => None, + RefundSignatures::Legacy { .. } => None, } } } diff --git a/swap/src/protocol/bob/swap.rs b/swap/src/protocol/bob/swap.rs index 8d8a69be96..a4f92da284 100644 --- a/swap/src/protocol/bob/swap.rs +++ b/swap/src/protocol/bob/swap.rs @@ -790,7 +790,7 @@ async fn next_state( // - either full refund, if alice sent use that signature (prioritized) // - or just partial refund. - if state.bob_refund_type.tx_full_refund_encsig().is_some() { + if state.refund_signatures.tx_full_refund_encsig().is_some() { tracing::info!("Have the full refund signature, attempting full Bitcoin refund"); let tx_full_refund = state .signed_full_refund_transaction() @@ -803,7 +803,7 @@ async fn next_state( return Ok(BobState::BtcRefundPublished(state.clone())); } - if state.bob_refund_type.tx_partial_refund_encsig().is_some() { + if state.refund_signatures.tx_partial_refund_encsig().is_some() { tracing::info!( "Don't have the full refund signature, attempting partial Bitcoin refund" ); From 4542b6df853d368025e682c1f7eba5b8e99df713 Mon Sep 17 00:00:00 2001 From: einliterflasche Date: Mon, 8 Dec 2025 12:01:41 +0100 Subject: [PATCH 030/113] fix btc_amnesty_amount type --- swap-machine/src/bob/mod.rs | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/swap-machine/src/bob/mod.rs b/swap-machine/src/bob/mod.rs index 5238cbed17..5f66318e26 100644 --- a/swap-machine/src/bob/mod.rs +++ b/swap-machine/src/bob/mod.rs @@ -657,7 +657,7 @@ impl State3 { tx_partial_refund_fee: self.tx_partial_refund_fee, tx_refund_amnesty_fee: self.tx_refund_amnesty_fee, xmr: self.xmr, - btc_ammesty_amount: self.btc_amnesty_amount, + btc_amnesty_amount: self.btc_amnesty_amount, } } @@ -841,7 +841,7 @@ impl State4 { tx_refund_fee: self.tx_refund_fee, tx_cancel_fee: self.tx_cancel_fee, xmr: self.xmr, - btc_ammesty_amount: self.btc_amnesty_amount, + btc_amnesty_amount: self.btc_amnesty_amount, tx_partial_refund_fee: self.tx_partial_refund_fee, tx_refund_amnesty_fee: self.tx_refund_amnesty_fee, } @@ -903,7 +903,7 @@ pub struct State6 { s_b: monero::Scalar, v: monero::PrivateViewKey, pub xmr: monero::Amount, - btc_ammesty_amount: Option, + btc_amnesty_amount: Option, pub monero_wallet_restore_blockheight: BlockHeight, pub cancel_timelock: CancelTimelock, punish_timelock: PunishTimelock, @@ -1060,7 +1060,7 @@ impl State6 { &self.refund_address, self.A, self.b.public(), - self.btc_ammesty_amount + self.btc_amnesty_amount .context("Can't construct TxPartialRefund because btc_amnesty_amount is missing")?, self.tx_partial_refund_fee.context( "Can't construct TxPartialRefund because tx_partial_refund_fee is missing", @@ -1108,7 +1108,7 @@ impl State6 { s_b: self.s_b, v: self.v, xmr: self.xmr, - btc_amnesty_amount: self.btc_ammesty_amount, + btc_amnesty_amount: self.btc_amnesty_amount, tx_lock: self.tx_lock.clone(), monero_wallet_restore_blockheight: self.monero_wallet_restore_blockheight, lock_transfer_proof, From 2fef464517fe956315be743b6fd58cc79851f464 Mon Sep 17 00:00:00 2001 From: einliterflasche Date: Mon, 8 Dec 2025 12:37:50 +0100 Subject: [PATCH 031/113] bob: refactor btc refund publish logic --- swap-machine/src/bob/mod.rs | 63 +++++++++++++++++++++---------- swap/src/bitcoin/wallet.rs | 3 ++ swap/src/cli/cancel_and_refund.rs | 15 ++++++-- swap/src/protocol/bob/swap.rs | 49 +++++++++++------------- 4 files changed, 79 insertions(+), 51 deletions(-) diff --git a/swap-machine/src/bob/mod.rs b/swap-machine/src/bob/mod.rs index 5f66318e26..6426d61673 100644 --- a/swap-machine/src/bob/mod.rs +++ b/swap-machine/src/bob/mod.rs @@ -102,6 +102,15 @@ pub enum RefundSignatures { }, } +/// Either a full refund or a partial refund +pub enum RefundType { + Full, + Partial { + total_swap_amount: bitcoin::Amount, + btc_amnesty_amount: bitcoin::Amount, + }, +} + impl fmt::Display for BobState { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match self { @@ -136,6 +145,15 @@ impl fmt::Display for BobState { } } +impl fmt::Display for RefundType { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + RefundType::Full => write!(f, "full btc refund"), + RefundType::Partial { .. } => write!(f, "partial btc refund"), + } + } +} + impl BobState { /// Fetch the expired timelocks for the swap. /// Depending on the State, there are no locks to expire. @@ -987,40 +1005,37 @@ impl State6 { Ok((tx_id, subscription)) } - /// Publish the best refund transaction based on the refund signatures Alice has sent us. + /// Construct the best refund transaction based on the refund signatures Alice has sent us. /// This is either `TxFullRefund` or `TxPartialRefund`. - /// Returns the corresponding state (`BtcRefundPublished`/`PartialRefundPublished`) on success. - pub async fn publish_best_btc_refund_tx( - &self, - bitcoin_wallet: &dyn bitcoin_wallet::BitcoinWallet, - ) -> Result<(Txid, BobState)> { + /// Returns the fully constructed and signed transaction along with the refund type. + pub async fn construct_best_bitcoin_refund_tx(&self) -> Result<(Transaction, RefundType)> { if self.refund_signatures.tx_full_refund_encsig().is_some() { - tracing::info!("Have the full refund signature, attempting full Bitcoin refund"); + tracing::debug!("Have the full refund signature, constructing full Bitcoin refund"); let tx_full_refund = self .signed_full_refund_transaction() .context("Couldn't construct TxFullRefund")?; - let (txid, _) = bitcoin_wallet - .ensure_broadcasted(tx_full_refund, "full refund") - .await - .context("Couldn't ensure broadcast of TxFullRefund")?; - return Ok((txid, BobState::BtcRefundPublished(self.clone()))); + return Ok((tx_full_refund, RefundType::Full)); } if self.refund_signatures.tx_partial_refund_encsig().is_some() { - tracing::info!( - "Don't have the full refund signature, attempting partial Bitcoin refund" + tracing::debug!( + "Don't have the full refund signature, constructing partial Bitcoin refund" ); let tx_partial_refund = self .signed_partial_refund_transaction() .context("Couldn't construct TxPartialRefund")?; - let (txid, _) = bitcoin_wallet - .ensure_broadcasted(tx_partial_refund, "partial refund") - .await - .context("Couldn't ensure broadcast of TxPartialRefund")?; - - return Ok((txid, BobState::BtcPartialRefundPublished(self.clone()))); + let total_swap_amount = self.tx_lock.lock_amount(); + let btc_amnesty_amount = self.btc_amnesty_amount.context("Missing Bitcoin amnesty amount even though we don't have the full refund signature")?; + + return Ok(( + tx_partial_refund, + RefundType::Partial { + total_swap_amount, + btc_amnesty_amount, + }, + )); } unreachable!("We always have either the partial or full refund encsig"); @@ -1178,4 +1193,12 @@ impl RefundSignatures { RefundSignatures::Legacy { .. } => None, } } + + pub fn has_full_refund_encsig(&self) -> bool { + self.tx_full_refund_encsig().is_some() + } + + pub fn has_partial_refund_encsig(&self) -> bool { + self.tx_partial_refund_encsig().is_some() + } } diff --git a/swap/src/bitcoin/wallet.rs b/swap/src/bitcoin/wallet.rs index 4da262c4db..96e8e125a8 100644 --- a/swap/src/bitcoin/wallet.rs +++ b/swap/src/bitcoin/wallet.rs @@ -737,6 +737,7 @@ impl Wallet { transaction: bitcoin::Transaction, kind: &str, ) -> Result<(Txid, Subscription)> { + let txid = transaction.compute_txid(); let tx_status = self.status_of_script(&transaction).await?; // If it's already been broadcasted, return a subscription to it @@ -744,11 +745,13 @@ impl Wallet { tx_status, ScriptStatus::InMempool | ScriptStatus::Confirmed(_) ) { + tracing::debug!(%txid, %tx_status, "Bitcoin transaction already published, not publishing again"); let subscription = self.subscribe_to(Box::new(transaction.clone())).await; return Ok((transaction.compute_txid(), subscription)); } // Otherwise broadcast it + tracing::debug!(%txid, %tx_status, "Bitcoin transaction not yet published, publishing"); self.broadcast(transaction, kind).await } diff --git a/swap/src/cli/cancel_and_refund.rs b/swap/src/cli/cancel_and_refund.rs index be7c8391ca..15f84eaaee 100644 --- a/swap/src/cli/cancel_and_refund.rs +++ b/swap/src/cli/cancel_and_refund.rs @@ -6,6 +6,7 @@ use bitcoin::Txid; use bitcoin_wallet::BitcoinWallet; use std::sync::Arc; use swap_core::bitcoin::ExpiredTimelocks; +use swap_machine::bob::RefundType; use uuid::Uuid; pub async fn cancel_and_refund( @@ -193,13 +194,21 @@ pub async fn refund( tracing::info!(%swap_id, "Attempting to manually refund swap"); + let (refund_tx, refund_type) = state6.construct_best_bitcoin_refund_tx().await?; + + tracing::info!("Attempting to publish Bitcoin refund transaction. Refund type: {refund_type}"); + // Attempt to just publish the refund transaction - match state6 - .publish_best_btc_refund_tx(bitcoin_wallet.as_ref()) + match bitcoin_wallet + .ensure_broadcasted(refund_tx, &refund_type.to_string()) .await { Ok(_) => { - let state = BobState::BtcRefunded(state6); + let state = match refund_type { + RefundType::Full => BobState::BtcRefundPublished(state6), + RefundType::Partial { .. } => BobState::BtcPartialRefundPublished(state6), + }; + db.insert_latest_state(swap_id, state.clone().into()) .await?; diff --git a/swap/src/protocol/bob/swap.rs b/swap/src/protocol/bob/swap.rs index a4f92da284..9b07c7ecf7 100644 --- a/swap/src/protocol/bob/swap.rs +++ b/swap/src/protocol/bob/swap.rs @@ -8,7 +8,7 @@ use crate::network::cooperative_xmr_redeem_after_punish::Response::{Fullfilled, use crate::network::swap_setup::bob::NewSwap; use crate::protocol::bob::*; use crate::protocol::{bob, Database}; -use anyhow::{Context as AnyContext, Result}; +use anyhow::{Context as AnyContext, Result, anyhow}; use std::sync::Arc; use std::time::Duration; use swap_core::bitcoin::{ @@ -789,36 +789,29 @@ async fn next_state( // Publish the best Bitcoin refund transaction we can sign: // - either full refund, if alice sent use that signature (prioritized) // - or just partial refund. - - if state.refund_signatures.tx_full_refund_encsig().is_some() { - tracing::info!("Have the full refund signature, attempting full Bitcoin refund"); - let tx_full_refund = state - .signed_full_refund_transaction() - .context("Couldn't construct TxFullRefund")?; - let (txid, _) = bitcoin_wallet - .ensure_broadcasted(tx_full_refund, "full refund") + tracing::debug!("Attempting to refund Bitcoin"); + + if state.refund_signatures.has_full_refund_encsig() { + let full_refund_tx = state.signed_full_refund_transaction().context("Couldn't construct full refund Bitcoin transaction")?; + tracing::debug!("Have full refund signature, attempting full refund"); + bitcoin_wallet.ensure_broadcasted(full_refund_tx, "full refund") .await - .context("Couldn't ensure broadcast of TxFullRefund")?; - tracing::info!(%txid, "Successfully published full bitcoin refund transaction"); - return Ok(BobState::BtcRefundPublished(state.clone())); - } - - if state.refund_signatures.tx_partial_refund_encsig().is_some() { - tracing::info!( - "Don't have the full refund signature, attempting partial Bitcoin refund" - ); - let tx_partial_refund = state - .signed_partial_refund_transaction() - .context("Couldn't construct TxPartialRefund")?; - let (txid, _) = bitcoin_wallet - .ensure_broadcasted(tx_partial_refund, "partial refund") + .context("Couldn't ensure broadcast of Bitcoin full refund transaction") + .map_err(backoff::Error::transient)?; + + Ok(BobState::BtcRefundPublished(state.clone())) + } else if state.refund_signatures.has_partial_refund_encsig() { + let partial_refund_tx = state.signed_partial_refund_transaction().context("Couldn't construct partial refund Bitcoin transaction")?; + tracing::debug!("Don't have full refund signature, attempting partial refund"); + bitcoin_wallet.ensure_broadcasted(partial_refund_tx, "partial refund") .await - .context("Couldn't ensure broadcast of TxPartialRefund")?; - tracing::info!(%txid, "Successfully published partial bitcoin refund transaction"); - return Ok(BobState::BtcPartialRefundPublished(state.clone())); + .context("Couldn't ensure broadcast of Bitcoin partial refund transaction") + .map_err(backoff::Error::transient)?; + + Ok(BobState::BtcPartialRefundPublished(state.clone())) + } else { + Err(backoff::Error::permanent(anyhow!("Unreachable - We have neither partial nor full refund signatures"))) } - - unreachable!("We always have either the partial or full refund encsig"); } ExpiredTimelocks::Punish => { let tx_lock_id = state.tx_lock_id(); From 56d4fb31976a6727f56aa99bdcdb1f38de04f177 Mon Sep 17 00:00:00 2001 From: einliterflasche Date: Mon, 8 Dec 2025 12:46:49 +0100 Subject: [PATCH 032/113] add PartialRefund and Amnesty events to SwapProgressEvent --- swap/src/cli/api/tauri_bindings.rs | 28 ++++++++++++++++++++++++++++ 1 file changed, 28 insertions(+) diff --git a/swap/src/cli/api/tauri_bindings.rs b/swap/src/cli/api/tauri_bindings.rs index 22283c3438..c5a5f46cf5 100644 --- a/swap/src/cli/api/tauri_bindings.rs +++ b/swap/src/cli/api/tauri_bindings.rs @@ -943,6 +943,20 @@ pub enum TauriSwapProgressEvent { #[typeshare(serialized_as = "string")] btc_refund_txid: Txid, }, + BtcPartialRefundPublished { + #[typeshare(serialized_as = "string")] + btc_partial_refund_txid: Txid, + /// Whether we have Alice's signature on the amnesty transaction + /// such that we will be able to refund the rest of the locked Bitcoin + /// after the partial refund went through. + has_amnesty_signature: bool, + }, + // BtcAmnesty was published but not yet confirmed. + // Requires BtcPartialRefund to be published first. + BtcAmnestyPublished { + #[typeshare(serialized_as = "string")] + btc_amnesty_txid: Txid, + }, // tx_early_refund has been confirmed BtcEarlyRefunded { #[typeshare(serialized_as = "string")] @@ -953,6 +967,20 @@ pub enum TauriSwapProgressEvent { #[typeshare(serialized_as = "string")] btc_refund_txid: Txid, }, + // We got partially refunded. Might still be able to get amnesty. + BtcPartiallyRefunded { + #[typeshare(serialized_as = "string")] + btc_partial_refund_txid: Txid, + /// Whether we have Alice's signature on the amnesty transaction + /// such that we will be able to refund the rest of the locked Bitcoin + /// after the partial refund went through. + has_amnesty_signature: bool, + }, + // BtcAmnesty was published but not yet confirmed. + BtcAmnestyReceived { + #[typeshare(serialized_as = "string")] + btc_amnesty_txid: Txid, + }, BtcPunished, AttemptingCooperativeRedeem, CooperativeRedeemAccepted, From 2a6c01f2dd3674c19ccc087cf4072a8c1fbaeb66 Mon Sep 17 00:00:00 2001 From: einliterflasche Date: Mon, 8 Dec 2025 13:11:42 +0100 Subject: [PATCH 033/113] bob: handle BtcPartialRefundPublished --- swap-machine/src/bob/mod.rs | 4 +++- swap/src/protocol/bob/swap.rs | 35 +++++++++++++++++++++++++++++++++-- 2 files changed, 36 insertions(+), 3 deletions(-) diff --git a/swap-machine/src/bob/mod.rs b/swap-machine/src/bob/mod.rs index 6426d61673..fe0100df8b 100644 --- a/swap-machine/src/bob/mod.rs +++ b/swap-machine/src/bob/mod.rs @@ -921,6 +921,8 @@ pub struct State6 { s_b: monero::Scalar, v: monero::PrivateViewKey, pub xmr: monero::Amount, + /// How much of the locked Bitcoin will stay locked in case of a partial refund. + /// May still be retrieve by publishing the `TxAmnesty` transaction. btc_amnesty_amount: Option, pub monero_wallet_restore_blockheight: BlockHeight, pub cancel_timelock: CancelTimelock, @@ -936,7 +938,7 @@ pub struct State6 { /// This field was added in [#675](https://github.com/eigenwallet/core/pull/675). /// It allows Bob to retrieve the refund fee introduced in the PR. /// This signature is voluntarily revealed by alice. - tx_refund_amnesty_sig: Option, + pub tx_refund_amnesty_sig: Option, pub tx_refund_fee: bitcoin::Amount, pub tx_cancel_fee: bitcoin::Amount, tx_partial_refund_fee: Option, diff --git a/swap/src/protocol/bob/swap.rs b/swap/src/protocol/bob/swap.rs index 9b07c7ecf7..2eaf5ea7ad 100644 --- a/swap/src/protocol/bob/swap.rs +++ b/swap/src/protocol/bob/swap.rs @@ -927,6 +927,38 @@ async fn next_state( }, } } + BobState::BtcPartialRefundPublished(state)=> { + // 1. Emit a Tauri event + event_emitter.emit_swap_progress_event( + swap_id, + TauriSwapProgressEvent::BtcPartialRefundPublished { + btc_partial_refund_txid: state.construct_tx_partial_refund()?.txid(), + has_amnesty_signature: state.tx_refund_amnesty_sig.is_some(), + }, + ); + + // TxEarlyRefund might still get published+confirmed before the PartialRefund gets confirmed + // 2. Wait for either refund transaction to be confirmed + + let tx_partial_refund = state.construct_tx_partial_refund()?; + let tx_early_refund = state.construct_tx_early_refund(); + + let (tx_partial_refund_status, tx_early_refund_status) = tokio::join!( + bitcoin_wallet.subscribe_to(Box::new(tx_partial_refund.clone())), + bitcoin_wallet.subscribe_to(Box::new(tx_early_refund.clone())), + ); + + select!{ + _ = tx_partial_refund_status.wait_until_final() => { + tracing::info!("TxPartialRefund has been confirmed"); + BobState::BtcPartiallyRefunded(state) + } + _ = tx_early_refund_status.wait_until_final() => { + tracing::info!("TxEarlyRefund has been confirmed"); + BobState::BtcEarlyRefunded(state) + } + } + } BobState::BtcRefunded(state) => { event_emitter.emit_swap_progress_event( swap_id, @@ -1074,8 +1106,7 @@ async fn next_state( } // TODO: Emit a Tauri event here BobState::BtcEarlyRefunded(state) => BobState::BtcEarlyRefunded(state), - BobState::BtcPartialRefundPublished(state) - | BobState::BtcPartiallyRefunded(state) + BobState::BtcPartiallyRefunded(state) | BobState::BtcAmnestyPublished(state) | BobState::BtcAmnestyConfirmed(state) => todo!(), BobState::SafelyAborted => BobState::SafelyAborted, From 3018ddefe6c2519706feb6cbdc8f13fd120be1a5 Mon Sep 17 00:00:00 2001 From: einliterflasche Date: Mon, 8 Dec 2025 15:20:55 +0100 Subject: [PATCH 034/113] bob: publish TxRefundAmnesty if we have the key or wait for Alice to do it --- swap-machine/src/bob/mod.rs | 26 ++++++++++++++++++ swap/src/protocol/bob/swap.rs | 52 ++++++++++++++++++++++++++++++++++- 2 files changed, 77 insertions(+), 1 deletion(-) diff --git a/swap-machine/src/bob/mod.rs b/swap-machine/src/bob/mod.rs index fe0100df8b..259868ea91 100644 --- a/swap-machine/src/bob/mod.rs +++ b/swap-machine/src/bob/mod.rs @@ -1105,6 +1105,32 @@ impl State6 { Ok(signed_tx_partial_refund) } + pub fn signed_amnesty_transaction(&self) -> Result { + let tx_amnesty = self.construct_tx_amnesty()?; + + let sig_a = self.tx_refund_amnesty_sig.clone().context( + "Can't sign amnesty transaction because Alice's amnesty signature is missing", + )?; + let sig_b = self.b.sign(tx_amnesty.digest()); + + let signed_tx_amnesty = + tx_amnesty.add_signatures((self.A, sig_a), (self.b.public(), sig_b))?; + + Ok(signed_tx_amnesty) + } + + pub fn construct_tx_amnesty(&self) -> Result { + let tx_partial_refund = self.construct_tx_partial_refund()?; + + Ok(bitcoin::TxRefundAmnesty::new( + &tx_partial_refund, + &self.refund_address, + self.tx_refund_amnesty_fee.context( + "Can't construct TxRefundAmnesty because tx_refund_amnesty_fee is missing", + )?, + )) + } + pub fn construct_tx_early_refund(&self) -> bitcoin::TxEarlyRefund { bitcoin::TxEarlyRefund::new(&self.tx_lock, &self.refund_address, self.tx_refund_fee) } diff --git a/swap/src/protocol/bob/swap.rs b/swap/src/protocol/bob/swap.rs index 2eaf5ea7ad..77e517493b 100644 --- a/swap/src/protocol/bob/swap.rs +++ b/swap/src/protocol/bob/swap.rs @@ -8,7 +8,7 @@ use crate::network::cooperative_xmr_redeem_after_punish::Response::{Fullfilled, use crate::network::swap_setup::bob::NewSwap; use crate::protocol::bob::*; use crate::protocol::{bob, Database}; -use anyhow::{Context as AnyContext, Result, anyhow}; +use anyhow::{Context as AnyContext, Result, anyhow, bail}; use std::sync::Arc; use std::time::Duration; use swap_core::bitcoin::{ @@ -959,6 +959,56 @@ async fn next_state( } } } + BobState::BtcPartiallyRefunded(state) => { + let has_amnesty_signature = state.tx_refund_amnesty_sig.is_some(); + + event_emitter.emit_swap_progress_event( + swap_id, + TauriSwapProgressEvent::BtcPartiallyRefunded { + btc_partial_refund_txid: state.construct_tx_partial_refund()?.txid(), + has_amnesty_signature, + }, + ); + + // If we have the amnesty signature, we publish the transaction ourselves. + // This also succeeds if the transaction is published by Alice. + if has_amnesty_signature { + retry("Refund amnesty transaction", || async { + let state = state.clone(); + let transaction = state.signed_amnesty_transaction().context("Couldn't construct Bitcoin amnesty transaction").map_err(backoff::Error::permanent)?; + bitcoin_wallet.ensure_broadcasted(transaction, "Bitcoin amnesty transaction") + .await + .context("Couldn't ensure broadcast of Bitcoin amnesty transaction") + .map_err(backoff::Error::transient)?; + Ok(()) + }, + None, + None + ) + .await + .context("Couldn't publish Bitcoin amnesty transaction")?; + + return Ok(BobState::BtcAmnestyPublished(state)) + } + + // If we don't have the amnesty signature, we have to wait for Alice to publish it. + // TODO: Would a timeout make sense here? Maybe once concurrent swap support landed. + + let tx_amnesty = state.construct_tx_amnesty().context("Couldn't construct Bitcoin amnesty transaction")?; + let subscription = bitcoin_wallet.subscribe_to(Box::new(tx_amnesty.clone())).await; + + retry("Waiting for Bitcoin amnesty transaction to be published by Alice", || async { + subscription.clone() + .wait_until_seen() + .await + .context("Failed to wait for Bitcoin amnesty transaction to be published by Alice") + .map_err(backoff::Error::transient)?; + + Ok(BobState::BtcAmnestyPublished(state.clone())) + }, None, None) + .await + .context("Failed to wait for Bitcoin amnesty transaction to be published by Alice")? + } BobState::BtcRefunded(state) => { event_emitter.emit_swap_progress_event( swap_id, From 23391f893cd7eca825cb1f1849e0cbf81346193b Mon Sep 17 00:00:00 2001 From: einliterflasche Date: Mon, 8 Dec 2025 16:05:36 +0100 Subject: [PATCH 035/113] bob: handle BtcAmnestyPublished and BtcAmestyConfirmed --- swap-machine/src/bob/mod.rs | 1 + swap/src/protocol/bob/swap.rs | 51 ++++++++++++++++++++++++++++++----- 2 files changed, 46 insertions(+), 6 deletions(-) diff --git a/swap-machine/src/bob/mod.rs b/swap-machine/src/bob/mod.rs index 259868ea91..89cb5eb9ea 100644 --- a/swap-machine/src/bob/mod.rs +++ b/swap-machine/src/bob/mod.rs @@ -197,6 +197,7 @@ pub fn is_complete(state: &BobState) -> bool { state, BobState::BtcRefunded(..) | BobState::BtcEarlyRefunded { .. } + | BobState::BtcAmnestyConfirmed { .. } | BobState::XmrRedeemed { .. } | BobState::SafelyAborted ) diff --git a/swap/src/protocol/bob/swap.rs b/swap/src/protocol/bob/swap.rs index 77e517493b..8f2a84bbb6 100644 --- a/swap/src/protocol/bob/swap.rs +++ b/swap/src/protocol/bob/swap.rs @@ -1003,7 +1003,7 @@ async fn next_state( .await .context("Failed to wait for Bitcoin amnesty transaction to be published by Alice") .map_err(backoff::Error::transient)?; - + Ok(BobState::BtcAmnestyPublished(state.clone())) }, None, None) .await @@ -1019,6 +1019,38 @@ async fn next_state( BobState::BtcRefunded(state) } + BobState::BtcAmnestyPublished(state) => { + // Here we just wait for the amnesty transaction to be confirmed + let tx_amnesty = state.construct_tx_amnesty().context("Couldn't construct Bitcoin amnesty transaction")?; + + event_emitter.emit_swap_progress_event( + swap_id, + TauriSwapProgressEvent::BtcAmnestyPublished { + btc_amnesty_txid: tx_amnesty.txid(), + }, + ); + + let subscription = bitcoin_wallet.subscribe_to(Box::new(tx_amnesty.clone())).await; + + retry("Waiting for Bitcoin amnesty transaction to be published by Alice", || async { + subscription.clone() + .wait_until_final() + .await + .context("Failed to wait for Bitcoin amnesty transaction to be confirmed") + .map_err(backoff::Error::transient)?; + + event_emitter.emit_swap_progress_event( + swap_id, + TauriSwapProgressEvent::BtcAmnestyReceived { + btc_amnesty_txid: state.construct_tx_amnesty()?.txid(), + }, + ); + + Ok(BobState::BtcAmnestyConfirmed(state.clone())) + }, None, None) + .await + .context("Failed to wait for Bitcoin amnesty transaction to be confirmed")? + } BobState::BtcPunished { state, tx_lock_id } => { tracing::info!("You have been punished for not refunding in time"); event_emitter.emit_swap_progress_event(swap_id, TauriSwapProgressEvent::BtcPunished); @@ -1154,11 +1186,18 @@ async fn next_state( } }; } - // TODO: Emit a Tauri event here - BobState::BtcEarlyRefunded(state) => BobState::BtcEarlyRefunded(state), - BobState::BtcPartiallyRefunded(state) - | BobState::BtcAmnestyPublished(state) - | BobState::BtcAmnestyConfirmed(state) => todo!(), + BobState::BtcEarlyRefunded(state) => { + event_emitter.emit_swap_progress_event(swap_id, TauriSwapProgressEvent::BtcEarlyRefunded { + btc_early_refund_txid: state.construct_tx_early_refund().txid(), + }); + BobState::BtcEarlyRefunded(state) + }, + BobState::BtcAmnestyConfirmed(state) => { + event_emitter.emit_swap_progress_event(swap_id, TauriSwapProgressEvent::BtcAmnestyReceived { + btc_amnesty_txid: state.construct_tx_amnesty()?.txid(), + }); + BobState::BtcAmnestyConfirmed(state) + }, BobState::SafelyAborted => BobState::SafelyAborted, BobState::XmrRedeemed { tx_lock_id } => { event_emitter.emit_swap_progress_event( From cc7b64e3b094e33d2d0ffedcc242ddef08778674 Mon Sep 17 00:00:00 2001 From: einliterflasche Date: Wed, 10 Dec 2025 11:38:44 +0100 Subject: [PATCH 036/113] asb: add refund_policy to Config, apply it in event loop --- swap-asb/src/main.rs | 1 + swap-controller/src/main.rs | 2 +- swap-env/src/config.rs | 39 ++++++++++++++++++---- swap-env/src/defaults.rs | 5 +++ swap-orchestrator/src/main.rs | 5 +-- swap-p2p/src/out_event/alice.rs | 14 +++++--- swap-p2p/src/protocols/swap_setup/alice.rs | 22 ++++++------ swap/src/asb/event_loop.rs | 37 +++++++++++++++++++- 8 files changed, 100 insertions(+), 25 deletions(-) diff --git a/swap-asb/src/main.rs b/swap-asb/src/main.rs index ab3ad20c7d..bb0feba121 100644 --- a/swap-asb/src/main.rs +++ b/swap-asb/src/main.rs @@ -314,6 +314,7 @@ pub async fn main() -> Result<()> { config.maker.max_buy_btc, config.maker.external_bitcoin_redeem_address, tip_config, + config.maker.refund_policy, ) .unwrap(); diff --git a/swap-controller/src/main.rs b/swap-controller/src/main.rs index 6c0361bd8e..cc03eaf3b9 100644 --- a/swap-controller/src/main.rs +++ b/swap-controller/src/main.rs @@ -95,7 +95,7 @@ async fn dispatch(cmd: Cmd, client: impl AsbApiClient) -> anyhow::Result<()> { println!("No rendezvous points configured"); } else { for item in response.registrations { - let address = item.address.as_deref().unwrap_or("?"); + let address = item.address.as_ref().map(String::as_str).unwrap_or("?"); println!( "Connection status to rendezvous point at \"{}\" is \"{:?}\". Registration status is \"{:?}\"", address, item.connection, item.registration diff --git a/swap-env/src/config.rs b/swap-env/src/config.rs index 9544b7e288..b1696fcd8e 100644 --- a/swap-env/src/config.rs +++ b/swap-env/src/config.rs @@ -1,14 +1,14 @@ use crate::defaults::{ - GetDefaults, BITFINEX_PRICE_TICKER_WS_URL, KRAKEN_PRICE_TICKER_WS_URL, + BITFINEX_PRICE_TICKER_WS_URL, GetDefaults, KRAKEN_PRICE_TICKER_WS_URL, KUCOIN_PRICE_TICKER_REST_URL, }; use crate::env::{Mainnet, Testnet}; use crate::prompt; -use anyhow::{bail, Context, Result}; +use anyhow::{Context, Result, bail}; use config::ConfigError; use libp2p::core::Multiaddr; use rust_decimal::Decimal; -use serde::{Deserialize, Serialize}; +use serde::{Deserialize, Serialize, de}; use std::ffi::OsStr; use std::fs; use std::path::{Path, PathBuf}; @@ -101,23 +101,39 @@ pub struct Maker { #[serde(with = "::bitcoin::amount::serde::as_btc")] pub max_buy_btc: bitcoin::Amount, pub ask_spread: Decimal, + /// What refund conditions to give to takers. + #[serde(default)] + pub refund_policy: RefundPolicy, #[serde(default = "default_price_ticker_ws_url_kraken")] pub price_ticker_ws_url_kraken: Url, #[serde(default = "default_price_ticker_ws_url_bitfinex")] pub price_ticker_ws_url_bitfinex: Url, #[serde(default = "default_price_ticker_rest_url_kucoin")] pub price_ticker_rest_url_kucoin: Url, + /// If specified, Bitcoin received from successful swaps will be sent to this address. #[serde(default, with = "swap_serde::bitcoin::address_serde::option")] pub external_bitcoin_redeem_address: Option, /// Percentage (between 0.0 and 1.0) of the swap amount - // that will be donated to the project as part of the Monero lock transaction + /// that will be donated to the devepment fund as part of the Monero lock transaction. #[serde(default = "default_developer_tip")] pub developer_tip: Decimal, } -fn default_developer_tip() -> Decimal { - // By default, we do not tip - Decimal::ZERO +#[derive(Clone, Debug, Deserialize, PartialEq, Eq, Serialize)] +pub struct RefundPolicy { + /// Takers will only receive this percentage of their Bitcoin back by default. + /// Maker can still issue "amnesty" to refund the rest. + /// This protects the maker against griefing attacks. + #[serde(default = "default_taker_refund_ratio")] + pub taker_refund_ratio: Decimal, +} + +impl Default for RefundPolicy { + fn default() -> Self { + Self { + taker_refund_ratio: default_taker_refund_ratio(), + } + } } fn default_price_ticker_ws_url_kraken() -> Url { @@ -132,6 +148,14 @@ fn default_price_ticker_rest_url_kucoin() -> Url { Url::parse(KUCOIN_PRICE_TICKER_REST_URL).expect("default kucoin rest url to be valid") } +fn default_developer_tip() -> Decimal { + Decimal::ZERO +} + +fn default_taker_refund_ratio() -> Decimal { + Decimal::ONE +} + impl Config { pub fn read(config_file: D) -> Result where @@ -249,6 +273,7 @@ pub fn query_user_for_initial_config_with_network( price_ticker_rest_url_kucoin: defaults.price_ticker_rest_url_kucoin, external_bitcoin_redeem_address: None, developer_tip, + refund_policy: defaults.refund_policy, }, }) } diff --git a/swap-env/src/defaults.rs b/swap-env/src/defaults.rs index ce94dc97a4..5effa77619 100644 --- a/swap-env/src/defaults.rs +++ b/swap-env/src/defaults.rs @@ -1,7 +1,9 @@ +use crate::config::RefundPolicy; use crate::env::{Mainnet, Testnet}; use anyhow::{Context, Result}; use libp2p::Multiaddr; use rust_decimal::Decimal; +use std::cell::Ref; use std::path::{Path, PathBuf}; use std::str::FromStr; use swap_fs::{system_config_dir, system_data_dir}; @@ -116,6 +118,7 @@ pub struct Defaults { pub bitcoin_confirmation_target: u16, pub use_mempool_space_fee_estimation: bool, pub developer_tip: Decimal, + pub refund_policy: RefundPolicy, } impl GetDefaults for Mainnet { @@ -133,6 +136,7 @@ impl GetDefaults for Mainnet { bitcoin_confirmation_target: 1, use_mempool_space_fee_estimation: true, developer_tip: Decimal::ZERO, + refund_policy: RefundPolicy::default(), }; Ok(defaults) @@ -154,6 +158,7 @@ impl GetDefaults for Testnet { bitcoin_confirmation_target: 1, use_mempool_space_fee_estimation: true, developer_tip: Decimal::ZERO, + refund_policy: RefundPolicy::default(), }; Ok(defaults) diff --git a/swap-orchestrator/src/main.rs b/swap-orchestrator/src/main.rs index 450dd173cf..8e4f27715c 100644 --- a/swap-orchestrator/src/main.rs +++ b/swap-orchestrator/src/main.rs @@ -4,8 +4,8 @@ mod images; mod prompt; use crate::compose::{ - IntoSpec, OrchestratorDirectories, OrchestratorImage, OrchestratorImages, OrchestratorInput, - OrchestratorNetworks, ASB_DATA_DIR, DOCKER_COMPOSE_FILE, + ASB_DATA_DIR, DOCKER_COMPOSE_FILE, IntoSpec, OrchestratorDirectories, OrchestratorImage, + OrchestratorImages, OrchestratorInput, OrchestratorNetworks, }; use std::path::PathBuf; use swap_env::config::{ @@ -193,6 +193,7 @@ fn main() { price_ticker_ws_url_bitfinex: defaults.price_ticker_ws_url_bitfinex, price_ticker_rest_url_kucoin: defaults.price_ticker_rest_url_kucoin, external_bitcoin_redeem_address: None, + refund_policy: defaults.refund_policy, developer_tip, }, }; diff --git a/swap-p2p/src/out_event/alice.rs b/swap-p2p/src/out_event/alice.rs index ada6c61189..8f769f8775 100644 --- a/swap-p2p/src/out_event/alice.rs +++ b/swap-p2p/src/out_event/alice.rs @@ -1,10 +1,10 @@ -use libp2p::{identify, ping}; use libp2p::{ + PeerId, request_response::{ InboundFailure, InboundRequestId, OutboundFailure, OutboundRequestId, ResponseChannel, }, - PeerId, }; +use libp2p::{identify, ping}; use uuid::Uuid; use crate::protocols::rendezvous; @@ -16,8 +16,14 @@ use crate::protocols::{ #[derive(Debug)] pub enum OutEvent { SwapSetupInitiated { - send_wallet_snapshot: - bmrng::RequestReceiver, + // run_swap_setup in connection handler sends us the amount of + // Bitcoin Bob wants to send. + // We respond with a snapshot of our wallets and how much of that + // should go into the amnesty output + send_wallet_snapshot: bmrng::RequestReceiver< + bitcoin::Amount, + (swap_setup::alice::WalletSnapshot, bitcoin::Amount), + >, }, SwapSetupCompleted { peer_id: PeerId, diff --git a/swap-p2p/src/protocols/swap_setup/alice.rs b/swap-p2p/src/protocols/swap_setup/alice.rs index 594b3c1a65..a96a8b8555 100644 --- a/swap-p2p/src/protocols/swap_setup/alice.rs +++ b/swap-p2p/src/protocols/swap_setup/alice.rs @@ -7,7 +7,7 @@ use anyhow::{Context, Result, anyhow}; use futures::AsyncWriteExt; use futures::FutureExt; use futures::StreamExt; -use futures::future::{BoxFuture, OptionFuture}; +use futures::future::BoxFuture; use futures::stream::FuturesUnordered; use libp2p::core::upgrade; use libp2p::swarm::handler::ConnectionEvent; @@ -29,7 +29,8 @@ use uuid::Uuid; #[allow(clippy::large_enum_variant)] pub enum OutEvent { Initiated { - send_wallet_snapshot: bmrng::RequestReceiver, + send_wallet_snapshot: + bmrng::RequestReceiver, }, Completed { peer_id: PeerId, @@ -259,7 +260,7 @@ impl Handler { #[allow(clippy::large_enum_variant)] #[derive(Debug)] pub enum HandlerOutEvent { - Initiated(bmrng::RequestReceiver), + Initiated(bmrng::RequestReceiver), Completed(Result<(Uuid, State3)>), } @@ -292,11 +293,12 @@ where ConnectionEvent::FullyNegotiatedInbound(substream) => { let substream = substream.protocol; - let (sender, receiver) = - bmrng::channel_with_timeout::( - 1, - crate::defaults::SWAP_SETUP_CHANNEL_TIMEOUT, - ); + let (sender, receiver) = bmrng::channel_with_timeout::< + bitcoin::Amount, + (WalletSnapshot, bitcoin::Amount), + >( + 1, crate::defaults::SWAP_SETUP_CHANNEL_TIMEOUT + ); let resume_only = self.resume_only; let min_buy = self.min_buy; @@ -442,7 +444,7 @@ impl Error { async fn run_swap_setup( mut substream: libp2p::swarm::Stream, - sender: bmrng::RequestSender, + sender: bmrng::RequestSender, resume_only: bool, env_config: env::Config, min_buy: bitcoin::Amount, @@ -453,7 +455,7 @@ async fn run_swap_setup( .await .context("Failed to read spot price request")?; - let wallet_snapshot = sender + let (wallet_snapshot, btc_amnesty_amount) = sender .send_receive(request.btc) .await .context("Failed to receive wallet snapshot")?; diff --git a/swap/src/asb/event_loop.rs b/swap/src/asb/event_loop.rs index 99cfa390c1..e71fdcb226 100644 --- a/swap/src/asb/event_loop.rs +++ b/swap/src/asb/event_loop.rs @@ -21,6 +21,7 @@ use libp2p::request_response::{OutboundFailure, OutboundRequestId, ResponseChann use libp2p::swarm::SwarmEvent; use libp2p::{PeerId, Swarm}; use moka::future::Cache; +use rust_decimal::prelude::FromPrimitive; use rust_decimal::Decimal; use std::collections::HashMap; use std::convert::TryInto; @@ -29,6 +30,7 @@ use std::io::Write; use std::sync::Arc; use std::time::Duration; use swap_core::bitcoin; +use swap_env::config::{Config, RefundPolicy}; use swap_env::env; use swap_feed::LatestRate; use tokio::sync::{mpsc, oneshot}; @@ -51,6 +53,7 @@ where max_buy: bitcoin::Amount, external_redeem_address: Option, developer_tip: TipConfig, + refund_policy: RefundPolicy, /// Cache for quotes quote_cache: Cache, Arc>>, @@ -142,6 +145,7 @@ where max_buy: bitcoin::Amount, external_redeem_address: Option, developer_tip: TipConfig, + refund_policy: RefundPolicy, ) -> Result<(Self, mpsc::Receiver, EventLoopService)> { let swap_channel = MpscChannels::default(); let (outgoing_transfer_proofs_sender, outgoing_transfer_proofs_requests) = @@ -162,6 +166,7 @@ where max_buy, external_redeem_address, developer_tip, + refund_policy, quote_cache, recv_encrypted_signature: Default::default(), inflight_encrypted_signatures: Default::default(), @@ -247,6 +252,14 @@ where } }; + // TODO: propagate error to the swap_setup routine instead of swallowing it + let btc_amnesty_amount = match apply_bitcoin_amnesty_policy(btc, &self.refund_policy) { + Ok(amount) => amount, + Err(error) => { + tracing::error!("Swap request will be ignored because we were unable to create wallet snapshot for swap: {:#}", error); + continue; + } + }; let wallet_snapshot = match capture_wallet_snapshot(self.bitcoin_wallet.clone(), &self.monero_wallet, &self.external_redeem_address, btc).await { Ok(wallet_snapshot) => wallet_snapshot, Err(error) => { @@ -256,7 +269,7 @@ where }; // Ignore result, we should never hit this because the receiver will alive as long as the connection is. - let _ = responder.respond(wallet_snapshot); + let _ = responder.respond((wallet_snapshot, btc_amnesty_amount)); } SwarmEvent::Behaviour(OutEvent::SwapSetupCompleted{peer_id, swap_id, state3}) => { if let Err(error) = self.handle_execution_setup_done(peer_id, swap_id, state3).await { @@ -817,6 +830,28 @@ impl EventLoopHandle { } } +/// For a new swap of `swap_amount`, this function calculates how much +/// Bitcoin should go into the amnesty-lock incase of a refund. +fn apply_bitcoin_amnesty_policy( + swap_amount: bitcoin::Amount, + refund_policy: &RefundPolicy, +) -> Result { + let amount_sats = swap_amount.to_sat(); + let btc_amnesty_ratio = Decimal::ONE + .checked_sub(refund_policy.taker_refund_ratio) + .context("can't have refund ration > 1")?; + + let btc_amnesty_sats = Decimal::from_u64(amount_sats) + .context("Decimal overflowed by Bitcoin sats")? + .checked_mul(btc_amnesty_ratio) + .context("Decimal overflow when computing amnesty amount in sats")? + .floor() + .try_into() + .context("Couldn't convert Decimal to u64")?; + + Ok(bitcoin::Amount::from_sat(btc_amnesty_sats)) +} + async fn capture_wallet_snapshot( bitcoin_wallet: Arc, monero_wallet: &monero::Wallets, From cf5fce9a4d83859f65b9f2a2a3aea2a669150ca0 Mon Sep 17 00:00:00 2001 From: einliterflasche Date: Wed, 10 Dec 2025 11:39:42 +0100 Subject: [PATCH 037/113] update tests to have refund policy --- ..._refund_using_cancel_and_refund_command.rs | 2 +- ...and_refund_command_timelock_not_expired.rs | 2 +- ...refund_using_cancel_then_refund_command.rs | 2 +- ...llet_rpc_after_started_btc_early_refund.rs | 2 +- ..._balance_after_started_btc_early_refund.rs | 2 +- .../alice_manually_punishes_after_bob_dead.rs | 2 +- ...punishes_after_bob_dead_and_bob_cancels.rs | 2 +- ..._manually_redeems_after_enc_sig_learned.rs | 2 +- .../alice_punishes_after_restart_bob_dead.rs | 2 +- ...lice_refunds_after_restart_bob_refunded.rs | 2 +- ...ncurrent_bobs_after_xmr_lock_proof_sent.rs | 2 +- ...current_bobs_before_xmr_lock_proof_sent.rs | 2 +- swap/tests/ensure_same_swap_id.rs | 2 +- swap/tests/happy_path.rs | 2 +- swap/tests/happy_path_alice_developer_tip.rs | 1 + ...ppy_path_alice_developer_tip_subaddress.rs | 1 + ...ath_bob_offline_while_alice_redeems_btc.rs | 69 ++++++++++--------- ...ppy_path_restart_alice_after_xmr_locked.rs | 2 +- ...happy_path_restart_bob_after_xmr_locked.rs | 2 +- ...appy_path_restart_bob_before_xmr_locked.rs | 2 +- swap/tests/harness/mod.rs | 8 +++ swap/tests/punish.rs | 2 +- 22 files changed, 65 insertions(+), 50 deletions(-) diff --git a/swap/tests/alice_and_bob_refund_using_cancel_and_refund_command.rs b/swap/tests/alice_and_bob_refund_using_cancel_and_refund_command.rs index 01991be165..edb0b2880d 100644 --- a/swap/tests/alice_and_bob_refund_using_cancel_and_refund_command.rs +++ b/swap/tests/alice_and_bob_refund_using_cancel_and_refund_command.rs @@ -11,7 +11,7 @@ use swap::{asb, cli}; #[tokio::test] async fn given_alice_and_bob_manually_refund_after_funds_locked_both_refund() { - harness::setup_test(FastCancelConfig, None, |mut ctx| async move { + harness::setup_test(FastCancelConfig, None, None, |mut ctx| async move { let (bob_swap, bob_join_handle) = ctx.bob_swap().await; let bob_swap_id = bob_swap.id; let bob_swap = tokio::spawn(bob::run_until(bob_swap, is_btc_locked)); diff --git a/swap/tests/alice_and_bob_refund_using_cancel_and_refund_command_timelock_not_expired.rs b/swap/tests/alice_and_bob_refund_using_cancel_and_refund_command_timelock_not_expired.rs index ddda32680a..89433e5c0a 100644 --- a/swap/tests/alice_and_bob_refund_using_cancel_and_refund_command_timelock_not_expired.rs +++ b/swap/tests/alice_and_bob_refund_using_cancel_and_refund_command_timelock_not_expired.rs @@ -12,7 +12,7 @@ use swap::{asb, cli}; #[tokio::test] async fn given_alice_and_bob_manually_cancel_when_timelock_not_expired_errors() { - harness::setup_test(SlowCancelConfig, None, |mut ctx| async move { + harness::setup_test(SlowCancelConfig, None, None, |mut ctx| async move { let (bob_swap, bob_join_handle) = ctx.bob_swap().await; let swap_id = bob_swap.id; let bob_swap = tokio::spawn(bob::run_until(bob_swap, is_btc_locked)); diff --git a/swap/tests/alice_and_bob_refund_using_cancel_then_refund_command.rs b/swap/tests/alice_and_bob_refund_using_cancel_then_refund_command.rs index 62c339bc1d..8936568c53 100644 --- a/swap/tests/alice_and_bob_refund_using_cancel_then_refund_command.rs +++ b/swap/tests/alice_and_bob_refund_using_cancel_then_refund_command.rs @@ -11,7 +11,7 @@ use swap::{asb, cli}; #[tokio::test] async fn given_alice_and_bob_manually_cancel_and_refund_after_funds_locked_both_refund() { - harness::setup_test(FastCancelConfig, None, |mut ctx| async move { + harness::setup_test(FastCancelConfig, None, None, |mut ctx| async move { let (bob_swap, bob_join_handle) = ctx.bob_swap().await; let bob_swap_id = bob_swap.id; let bob_swap = tokio::spawn(bob::run_until(bob_swap, is_btc_locked)); diff --git a/swap/tests/alice_broken_wallet_rpc_after_started_btc_early_refund.rs b/swap/tests/alice_broken_wallet_rpc_after_started_btc_early_refund.rs index 1102516589..4a8a4a16ec 100644 --- a/swap/tests/alice_broken_wallet_rpc_after_started_btc_early_refund.rs +++ b/swap/tests/alice_broken_wallet_rpc_after_started_btc_early_refund.rs @@ -10,7 +10,7 @@ use crate::harness::SlowCancelConfig; #[tokio::test] async fn alice_zero_xmr_refunds_bitcoin() { - harness::setup_test(SlowCancelConfig, None, |mut ctx| async move { + harness::setup_test(SlowCancelConfig, None, None, |mut ctx| async move { let (bob_swap, bob_handle) = ctx.bob_swap().await; let bob_swap = tokio::spawn(bob::run_until(bob_swap, is_btc_locked)); diff --git a/swap/tests/alice_empty_balance_after_started_btc_early_refund.rs b/swap/tests/alice_empty_balance_after_started_btc_early_refund.rs index 61d412414f..c63988260f 100644 --- a/swap/tests/alice_empty_balance_after_started_btc_early_refund.rs +++ b/swap/tests/alice_empty_balance_after_started_btc_early_refund.rs @@ -10,7 +10,7 @@ use crate::harness::SlowCancelConfig; #[tokio::test] async fn alice_zero_xmr_refunds_bitcoin() { - harness::setup_test(SlowCancelConfig, None, |mut ctx| async move { + harness::setup_test(SlowCancelConfig, None, None, |mut ctx| async move { let (bob_swap, bob_handle) = ctx.bob_swap().await; let bob_swap = tokio::spawn(bob::run_until(bob_swap, is_btc_locked)); diff --git a/swap/tests/alice_manually_punishes_after_bob_dead.rs b/swap/tests/alice_manually_punishes_after_bob_dead.rs index 9cd9f4474d..f29fc0fdf3 100644 --- a/swap/tests/alice_manually_punishes_after_bob_dead.rs +++ b/swap/tests/alice_manually_punishes_after_bob_dead.rs @@ -14,7 +14,7 @@ use swap::protocol::{alice, bob}; /// punish command. Bob then cooperates with Alice and redeems XMR with her key. #[tokio::test] async fn alice_manually_punishes_after_bob_dead() { - harness::setup_test(FastPunishConfig, None, |mut ctx| async move { + harness::setup_test(FastPunishConfig, None, None, |mut ctx| async move { let (bob_swap, bob_join_handle) = ctx.bob_swap().await; let bob_swap_id = bob_swap.id; let bob_swap = tokio::spawn(bob::run_until(bob_swap, is_btc_locked)); diff --git a/swap/tests/alice_manually_punishes_after_bob_dead_and_bob_cancels.rs b/swap/tests/alice_manually_punishes_after_bob_dead_and_bob_cancels.rs index 21d1cb1025..03bfaa1188 100644 --- a/swap/tests/alice_manually_punishes_after_bob_dead_and_bob_cancels.rs +++ b/swap/tests/alice_manually_punishes_after_bob_dead_and_bob_cancels.rs @@ -14,7 +14,7 @@ use swap::protocol::{alice, bob}; /// punish command. Then Bob tries to refund. #[tokio::test] async fn alice_manually_punishes_after_bob_dead_and_bob_cancels() { - harness::setup_test(FastPunishConfig, None, |mut ctx| async move { + harness::setup_test(FastPunishConfig, None, None, |mut ctx| async move { let (bob_swap, bob_join_handle) = ctx.bob_swap().await; let bob_swap_id = bob_swap.id; let bob_swap = tokio::spawn(bob::run_until(bob_swap, is_btc_locked)); diff --git a/swap/tests/alice_manually_redeems_after_enc_sig_learned.rs b/swap/tests/alice_manually_redeems_after_enc_sig_learned.rs index ad2e97f596..bcb24c7560 100644 --- a/swap/tests/alice_manually_redeems_after_enc_sig_learned.rs +++ b/swap/tests/alice_manually_redeems_after_enc_sig_learned.rs @@ -11,7 +11,7 @@ use swap::protocol::{alice, bob}; /// after learning encsig from Bob #[tokio::test] async fn alice_manually_redeems_after_enc_sig_learned() { - harness::setup_test(SlowCancelConfig, None, |mut ctx| async move { + harness::setup_test(SlowCancelConfig, None, None, |mut ctx| async move { let (bob_swap, _) = ctx.bob_swap().await; let bob_swap = tokio::spawn(bob::run(bob_swap)); diff --git a/swap/tests/alice_punishes_after_restart_bob_dead.rs b/swap/tests/alice_punishes_after_restart_bob_dead.rs index cabf76a0b3..91518e2d7a 100644 --- a/swap/tests/alice_punishes_after_restart_bob_dead.rs +++ b/swap/tests/alice_punishes_after_restart_bob_dead.rs @@ -12,7 +12,7 @@ use swap::protocol::{alice, bob}; /// the encsig and fail to refund or redeem. Alice cancels and punishes. Bob then cooperates with Alice and redeems XMR with her key. #[tokio::test] async fn alice_punishes_after_restart_if_bob_dead() { - harness::setup_test(FastPunishConfig, None, |mut ctx| async move { + harness::setup_test(FastPunishConfig, None, None, |mut ctx| async move { let (bob_swap, bob_join_handle) = ctx.bob_swap().await; let bob_swap_id = bob_swap.id; let bob_swap = tokio::spawn(bob::run_until(bob_swap, is_btc_locked)); diff --git a/swap/tests/alice_refunds_after_restart_bob_refunded.rs b/swap/tests/alice_refunds_after_restart_bob_refunded.rs index b79f52a2ad..7b3dd89832 100644 --- a/swap/tests/alice_refunds_after_restart_bob_refunded.rs +++ b/swap/tests/alice_refunds_after_restart_bob_refunded.rs @@ -10,7 +10,7 @@ use swap::protocol::{alice, bob}; /// Eventually Alice comes back online and refunds as well. #[tokio::test] async fn alice_refunds_after_restart_if_bob_already_refunded() { - harness::setup_test(FastCancelConfig, None, |mut ctx| async move { + harness::setup_test(FastCancelConfig, None, None, |mut ctx| async move { let (bob_swap, _) = ctx.bob_swap().await; let bob_swap = tokio::spawn(bob::run(bob_swap)); diff --git a/swap/tests/concurrent_bobs_after_xmr_lock_proof_sent.rs b/swap/tests/concurrent_bobs_after_xmr_lock_proof_sent.rs index e48683afe6..ec3053b4cc 100644 --- a/swap/tests/concurrent_bobs_after_xmr_lock_proof_sent.rs +++ b/swap/tests/concurrent_bobs_after_xmr_lock_proof_sent.rs @@ -9,7 +9,7 @@ use swap::protocol::{alice, bob}; #[tokio::test] async fn concurrent_bobs_after_xmr_lock_proof_sent() { - harness::setup_test(SlowCancelConfig, None, |mut ctx| async move { + harness::setup_test(SlowCancelConfig, None, None, |mut ctx| async move { let (bob_swap_1, bob_join_handle_1) = ctx.bob_swap().await; let swap_id = bob_swap_1.id; diff --git a/swap/tests/concurrent_bobs_before_xmr_lock_proof_sent.rs b/swap/tests/concurrent_bobs_before_xmr_lock_proof_sent.rs index 775a1e2a35..a255c5ea31 100644 --- a/swap/tests/concurrent_bobs_before_xmr_lock_proof_sent.rs +++ b/swap/tests/concurrent_bobs_before_xmr_lock_proof_sent.rs @@ -9,7 +9,7 @@ use swap::protocol::{alice, bob}; #[tokio::test] async fn concurrent_bobs_before_xmr_lock_proof_sent() { - harness::setup_test(SlowCancelConfig, None, |mut ctx| async move { + harness::setup_test(SlowCancelConfig, None, None, |mut ctx| async move { let (bob_swap_1, bob_join_handle_1) = ctx.bob_swap().await; let swap_id = bob_swap_1.id; diff --git a/swap/tests/ensure_same_swap_id.rs b/swap/tests/ensure_same_swap_id.rs index 8deb1428cc..309f89904a 100644 --- a/swap/tests/ensure_same_swap_id.rs +++ b/swap/tests/ensure_same_swap_id.rs @@ -5,7 +5,7 @@ use swap::protocol::bob; #[tokio::test] async fn ensure_same_swap_id_for_alice_and_bob() { - harness::setup_test(SlowCancelConfig, None, |mut ctx| async move { + harness::setup_test(SlowCancelConfig, None, None, |mut ctx| async move { let (bob_swap, _) = ctx.bob_swap().await; let bob_swap_id = bob_swap.id; tokio::spawn(bob::run(bob_swap)); diff --git a/swap/tests/happy_path.rs b/swap/tests/happy_path.rs index da0ee150bc..bb0b983c98 100644 --- a/swap/tests/happy_path.rs +++ b/swap/tests/happy_path.rs @@ -7,7 +7,7 @@ use tokio::join; #[tokio::test] async fn happy_path() { - harness::setup_test(SlowCancelConfig, None, |mut ctx| async move { + harness::setup_test(SlowCancelConfig, None, None, |mut ctx| async move { let (bob_swap, _) = ctx.bob_swap().await; let bob_swap = tokio::spawn(bob::run(bob_swap)); diff --git a/swap/tests/happy_path_alice_developer_tip.rs b/swap/tests/happy_path_alice_developer_tip.rs index eba9df567e..cc2be6b664 100644 --- a/swap/tests/happy_path_alice_developer_tip.rs +++ b/swap/tests/happy_path_alice_developer_tip.rs @@ -11,6 +11,7 @@ async fn happy_path_alice_developer_tip() { harness::setup_test( SlowCancelConfig, Some((Decimal::from_f32_retain(0.1).unwrap(), false)), + None, |mut ctx| async move { let (bob_swap, _) = ctx.bob_swap().await; let bob_swap = tokio::spawn(bob::run(bob_swap)); diff --git a/swap/tests/happy_path_alice_developer_tip_subaddress.rs b/swap/tests/happy_path_alice_developer_tip_subaddress.rs index df34d3c6d5..2589279b2d 100644 --- a/swap/tests/happy_path_alice_developer_tip_subaddress.rs +++ b/swap/tests/happy_path_alice_developer_tip_subaddress.rs @@ -11,6 +11,7 @@ async fn happy_path_alice_developer_tip_subaddress() { harness::setup_test( SlowCancelConfig, Some((Decimal::from_f32_retain(0.1).unwrap(), true)), + None, |mut ctx| async move { let (bob_swap, _) = ctx.bob_swap().await; let bob_swap = tokio::spawn(bob::run(bob_swap)); diff --git a/swap/tests/happy_path_bob_offline_while_alice_redeems_btc.rs b/swap/tests/happy_path_bob_offline_while_alice_redeems_btc.rs index 1b4774acef..0b97a659bf 100644 --- a/swap/tests/happy_path_bob_offline_while_alice_redeems_btc.rs +++ b/swap/tests/happy_path_bob_offline_while_alice_redeems_btc.rs @@ -8,37 +8,42 @@ use tokio::join; #[tokio::test] async fn given_bob_restarts_while_alice_redeems_btc() { - harness::setup_test(harness::SlowCancelConfig, None, |mut ctx| async move { - let (bob_swap, bob_handle) = ctx.bob_swap().await; - let swap_id = bob_swap.id; - - let bob_swap = tokio::spawn(bob::run_until(bob_swap, is_encsig_sent)); - - let alice_swap = ctx.alice_next_swap().await; - let alice_swap = tokio::spawn(alice::run(alice_swap, FixedRate::default())); - - let (bob_state, alice_state) = join!(bob_swap, alice_swap); - ctx.assert_alice_redeemed(alice_state??).await; - assert!(matches!(bob_state??, BobState::EncSigSent { .. })); - - let (bob_swap, _) = ctx.stop_and_resume_bob_from_db(bob_handle, swap_id).await; - - if let BobState::EncSigSent(state4) = bob_swap.state.clone() { - bob_swap - .bitcoin_wallet - .subscribe_to(Box::new(state4.tx_lock)) - .await - .wait_until_confirmed_with(state4.cancel_timelock) - .await?; - } else { - panic!("Bob in unexpected state {}", bob_swap.state); - } - - // Restart Bob - let bob_state = bob::run(bob_swap).await?; - ctx.assert_bob_redeemed(bob_state).await; - - Ok(()) - }) + harness::setup_test( + harness::SlowCancelConfig, + None, + None, + |mut ctx| async move { + let (bob_swap, bob_handle) = ctx.bob_swap().await; + let swap_id = bob_swap.id; + + let bob_swap = tokio::spawn(bob::run_until(bob_swap, is_encsig_sent)); + + let alice_swap = ctx.alice_next_swap().await; + let alice_swap = tokio::spawn(alice::run(alice_swap, FixedRate::default())); + + let (bob_state, alice_state) = join!(bob_swap, alice_swap); + ctx.assert_alice_redeemed(alice_state??).await; + assert!(matches!(bob_state??, BobState::EncSigSent { .. })); + + let (bob_swap, _) = ctx.stop_and_resume_bob_from_db(bob_handle, swap_id).await; + + if let BobState::EncSigSent(state4) = bob_swap.state.clone() { + bob_swap + .bitcoin_wallet + .subscribe_to(Box::new(state4.tx_lock)) + .await + .wait_until_confirmed_with(state4.cancel_timelock) + .await?; + } else { + panic!("Bob in unexpected state {}", bob_swap.state); + } + + // Restart Bob + let bob_state = bob::run(bob_swap).await?; + ctx.assert_bob_redeemed(bob_state).await; + + Ok(()) + }, + ) .await; } diff --git a/swap/tests/happy_path_restart_alice_after_xmr_locked.rs b/swap/tests/happy_path_restart_alice_after_xmr_locked.rs index 1461947c60..6ab91c02e2 100644 --- a/swap/tests/happy_path_restart_alice_after_xmr_locked.rs +++ b/swap/tests/happy_path_restart_alice_after_xmr_locked.rs @@ -8,7 +8,7 @@ use swap::protocol::{alice, bob}; #[tokio::test] async fn given_alice_restarts_after_xmr_is_locked_resume_swap() { - harness::setup_test(SlowCancelConfig, None, |mut ctx| async move { + harness::setup_test(SlowCancelConfig, None, None, |mut ctx| async move { let (bob_swap, _) = ctx.bob_swap().await; let bob_swap = tokio::spawn(bob::run(bob_swap)); diff --git a/swap/tests/happy_path_restart_bob_after_xmr_locked.rs b/swap/tests/happy_path_restart_bob_after_xmr_locked.rs index 07b44ebc7e..c35b8e065f 100644 --- a/swap/tests/happy_path_restart_bob_after_xmr_locked.rs +++ b/swap/tests/happy_path_restart_bob_after_xmr_locked.rs @@ -8,7 +8,7 @@ use swap::protocol::{alice, bob}; #[tokio::test] async fn given_bob_restarts_after_xmr_is_locked_resume_swap() { - harness::setup_test(SlowCancelConfig, None, |mut ctx| async move { + harness::setup_test(SlowCancelConfig, None, None, |mut ctx| async move { let (bob_swap, bob_join_handle) = ctx.bob_swap().await; let bob_swap_id = bob_swap.id; let bob_swap = tokio::spawn(bob::run_until(bob_swap, is_xmr_locked)); diff --git a/swap/tests/happy_path_restart_bob_before_xmr_locked.rs b/swap/tests/happy_path_restart_bob_before_xmr_locked.rs index 07b44ebc7e..c35b8e065f 100644 --- a/swap/tests/happy_path_restart_bob_before_xmr_locked.rs +++ b/swap/tests/happy_path_restart_bob_before_xmr_locked.rs @@ -8,7 +8,7 @@ use swap::protocol::{alice, bob}; #[tokio::test] async fn given_bob_restarts_after_xmr_is_locked_resume_swap() { - harness::setup_test(SlowCancelConfig, None, |mut ctx| async move { + harness::setup_test(SlowCancelConfig, None, None, |mut ctx| async move { let (bob_swap, bob_join_handle) = ctx.bob_swap().await; let bob_swap_id = bob_swap.id; let bob_swap = tokio::spawn(bob::run_until(bob_swap, is_xmr_locked)); diff --git a/swap/tests/harness/mod.rs b/swap/tests/harness/mod.rs index 5241ee6a5c..5703511d22 100644 --- a/swap/tests/harness/mod.rs +++ b/swap/tests/harness/mod.rs @@ -13,6 +13,7 @@ use rust_decimal::Decimal; use std::cmp::Ordering; use std::fmt; use std::path::PathBuf; +use swap_env::config::RefundPolicy; use std::str::FromStr; use std::sync::Arc; @@ -50,6 +51,7 @@ use uuid::Uuid; pub async fn setup_test( _config: C, developer_tip_ratio: Option<(Decimal, bool)>, + refund_policy: Option, testfn: T, ) where T: Fn(TestContext) -> F, @@ -158,6 +160,7 @@ pub async fn setup_test( alice_bitcoin_wallet.clone(), alice_monero_wallet.clone(), developer_tip.clone(), + refund_policy.clone().unwrap_or_default(), ) .await; @@ -207,6 +210,7 @@ pub async fn setup_test( bob_monero_wallet, developer_tip_monero_wallet, developer_tip, + refund_policy: refund_policy.unwrap_or_default(), monerod_container_id: containers._monerod_container.id().to_string(), }; @@ -298,6 +302,7 @@ async fn start_alice( bitcoin_wallet: Arc, monero_wallet: Arc, developer_tip: TipConfig, + refund_policy: RefundPolicy, ) -> (AliceApplicationHandle, Receiver) { if let Some(parent_dir) = db_path.parent() { ensure_directory_exists(parent_dir).unwrap(); @@ -343,6 +348,7 @@ async fn start_alice( max_buy, None, developer_tip, + refund_policy, ) .unwrap(); @@ -668,6 +674,7 @@ pub struct TestContext { btc_amount: bitcoin::Amount, xmr_amount: monero::Amount, developer_tip: TipConfig, + refund_policy: RefundPolicy, alice_seed: Seed, alice_db_path: PathBuf, @@ -713,6 +720,7 @@ impl TestContext { self.alice_bitcoin_wallet.clone(), self.alice_monero_wallet.clone(), self.developer_tip.clone(), + self.refund_policy.clone(), ) .await; diff --git a/swap/tests/punish.rs b/swap/tests/punish.rs index 4829c7ebec..0d5d2d47db 100644 --- a/swap/tests/punish.rs +++ b/swap/tests/punish.rs @@ -10,7 +10,7 @@ use swap::protocol::{alice, bob}; /// the encsig and fail to refund or redeem. Alice punishes. Bob then cooperates with Alice and redeems XMR with her key. #[tokio::test] async fn alice_punishes_if_bob_never_acts_after_fund() { - harness::setup_test(FastPunishConfig, None, |mut ctx| async move { + harness::setup_test(FastPunishConfig, None, None, |mut ctx| async move { let (bob_swap, bob_join_handle) = ctx.bob_swap().await; let bob_swap_id = bob_swap.id; let bob_swap = tokio::spawn(bob::run_until(bob_swap, is_btc_locked)); From fe944161789011caae47f0ddcc9fb12c329329ed Mon Sep 17 00:00:00 2001 From: einliterflasche Date: Wed, 10 Dec 2025 11:44:15 +0100 Subject: [PATCH 038/113] alice: actually use btc_amnesty_amount in run_swap_setup --- swap-p2p/src/protocols/swap_setup/alice.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/swap-p2p/src/protocols/swap_setup/alice.rs b/swap-p2p/src/protocols/swap_setup/alice.rs index a96a8b8555..57070a9c04 100644 --- a/swap-p2p/src/protocols/swap_setup/alice.rs +++ b/swap-p2p/src/protocols/swap_setup/alice.rs @@ -536,7 +536,7 @@ async fn run_swap_setup( let state0 = State0::new( request.btc, xmr, - todo!("TODO: Implement system for alice to decide amnesty amount"), + btc_amnesty_amount, env_config, wallet_snapshot.redeem_address, wallet_snapshot.punish_address, From 3863fc1804cf9620764caeebf9f262a1a623cb9b Mon Sep 17 00:00:00 2001 From: einliterflasche Date: Wed, 10 Dec 2025 12:55:56 +0100 Subject: [PATCH 039/113] tracing: force ansi output for terminal layer. if anything breaks with docker or something this is the fault --- swap/src/common/tracing_util.rs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/swap/src/common/tracing_util.rs b/swap/src/common/tracing_util.rs index 9a72d89b8a..0f8ab69ac0 100644 --- a/swap/src/common/tracing_util.rs +++ b/swap/src/common/tracing_util.rs @@ -110,10 +110,9 @@ pub fn init( ); // Layer for writing to the terminal - let is_terminal = std::io::stderr().is_terminal(); let terminal_layer = fmt::layer() .with_writer(std::io::stderr) - .with_ansi(is_terminal) + .with_ansi(true) .with_timer(UtcTime::rfc_3339()) .with_target(true) .with_file(true) From ceac03aa4f2c3b316a7b69e0cfb0c58d713fc708 Mon Sep 17 00:00:00 2001 From: einliterflasche Date: Wed, 10 Dec 2025 12:57:09 +0100 Subject: [PATCH 040/113] remove yarn@4 from package.json as package manager --- src-gui/package.json | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src-gui/package.json b/src-gui/package.json index c65eb0d919..1a8f2f617c 100644 --- a/src-gui/package.json +++ b/src-gui/package.json @@ -78,6 +78,5 @@ "vite-plugin-watch": "^0.3.1", "vite-tsconfig-paths": "^4.3.2", "vitest": "^2.1.1" - }, - "packageManager": "yarn@4.12.0" + } } From 53f9aaf8cc43d3aaf97cc38767a5664fd2593ba0 Mon Sep 17 00:00:00 2001 From: einliterflasche Date: Wed, 10 Dec 2025 15:21:02 +0100 Subject: [PATCH 041/113] remove unnecessary env var from just asb-testnet command --- justfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/justfile b/justfile index f77297e6c7..f3ba6bad62 100644 --- a/justfile +++ b/justfile @@ -83,7 +83,7 @@ swap: # Run the asb on testnet asb-testnet: - ASB_DEV_ADDR_OUTPUT_PATH="$PWD/src-gui/.env.development" cargo run -p swap-asb --bin asb -- --testnet --trace start --rpc-bind-port 9944 --rpc-bind-host 0.0.0.0 + cargo run -p swap-asb --bin asb -- --testnet --trace start --rpc-bind-port 9944 --rpc-bind-host 0.0.0.0 # Launch the ASB controller REPL against a local testnet ASB instance asb-testnet-controller: From cb9bec0da0b74ee27fcd6c3571e8b4d3128da4ac Mon Sep 17 00:00:00 2001 From: einliterflasche Date: Wed, 10 Dec 2025 15:22:23 +0100 Subject: [PATCH 042/113] state-machine(alice): make btc_amnesty_amount optional --- swap-machine/src/alice/mod.rs | 23 ++++++++++++---------- swap-machine/src/bob/mod.rs | 2 +- swap-machine/src/lib.rs | 4 ++-- swap-p2p/src/protocols/swap_setup/alice.rs | 11 ++++++++--- 4 files changed, 24 insertions(+), 16 deletions(-) diff --git a/swap-machine/src/alice/mod.rs b/swap-machine/src/alice/mod.rs index 6c94fbec5d..017db7f1d4 100644 --- a/swap-machine/src/alice/mod.rs +++ b/swap-machine/src/alice/mod.rs @@ -148,7 +148,7 @@ pub struct State0 { dleq_proof_s_a: CrossCurveDLEQProof, btc: bitcoin::Amount, xmr: monero::Amount, - btc_amnesty_amount: bitcoin::Amount, + btc_amnesty_amount: Option, cancel_timelock: CancelTimelock, punish_timelock: PunishTimelock, redeem_address: bitcoin::Address, @@ -188,7 +188,7 @@ impl State0 { point: S_a_monero.compress(), }, dleq_proof_s_a, - btc_amnesty_amount, + btc_amnesty_amount: Some(btc_amnesty_amount), redeem_address, punish_address, btc, @@ -265,7 +265,7 @@ pub struct State1 { dleq_proof_s_a: CrossCurveDLEQProof, btc: bitcoin::Amount, xmr: monero::Amount, - btc_amnesty_amount: bitcoin::Amount, + btc_amnesty_amount: Option, cancel_timelock: CancelTimelock, punish_timelock: PunishTimelock, refund_address: bitcoin::Address, @@ -280,8 +280,8 @@ pub struct State1 { } impl State1 { - pub fn next_message(&self) -> Message1 { - Message1 { + pub fn next_message(&self) -> Result { + Ok(Message1 { A: self.a.public(), S_a_monero: self.S_a_monero, S_a_bitcoin: self.S_a_bitcoin, @@ -291,8 +291,10 @@ impl State1 { punish_address: self.punish_address.clone(), tx_redeem_fee: self.tx_redeem_fee, tx_punish_fee: self.tx_punish_fee, - amnesty_amount: self.btc_amnesty_amount, - } + amnesty_amount: self + .btc_amnesty_amount + .context("Missing btc_amesty_amount for new swap that should have it")?, + }) } pub fn receive(self, msg: Message2) -> Result { @@ -337,7 +339,7 @@ pub struct State2 { v: monero::PrivateViewKey, btc: bitcoin::Amount, xmr: monero::Amount, - btc_amnesty_amount: bitcoin::Amount, + btc_amnesty_amount: Option, cancel_timelock: CancelTimelock, punish_timelock: PunishTimelock, refund_address: bitcoin::Address, @@ -368,7 +370,8 @@ impl State2 { &self.refund_address, self.a.public(), self.B, - self.btc_amnesty_amount, + self.btc_amnesty_amount + .context("Missing btc_amnesty_amount for new swap that should have it")?, self.tx_refund_fee, )?; // Alice encsigns the partial refund transaction(bitcoin) digest with Bob's monero @@ -472,7 +475,7 @@ pub struct State3 { pub v: monero::PrivateViewKey, pub btc: bitcoin::Amount, pub xmr: monero::Amount, - pub btc_amnesty_amount: bitcoin::Amount, + pub btc_amnesty_amount: Option, pub cancel_timelock: CancelTimelock, pub punish_timelock: PunishTimelock, #[serde(with = "swap_serde::bitcoin::address_serde")] diff --git a/swap-machine/src/bob/mod.rs b/swap-machine/src/bob/mod.rs index 89cb5eb9ea..fa7ac5d3fe 100644 --- a/swap-machine/src/bob/mod.rs +++ b/swap-machine/src/bob/mod.rs @@ -478,7 +478,7 @@ pub struct State2 { S_a_bitcoin: bitcoin::PublicKey, v: monero::PrivateViewKey, pub xmr: monero::Amount, - btc_amnesty_amount: Option, + pub btc_amnesty_amount: Option, pub cancel_timelock: CancelTimelock, pub punish_timelock: PunishTimelock, #[serde(with = "address_serde")] diff --git a/swap-machine/src/lib.rs b/swap-machine/src/lib.rs index c36dea0945..8ac33b2b10 100644 --- a/swap-machine/src/lib.rs +++ b/swap-machine/src/lib.rs @@ -77,7 +77,7 @@ mod tests { let message0 = bob_state0.next_message().unwrap(); let (_, alice_state1) = alice_state0.receive(message0).unwrap(); - let alice_message1 = alice_state1.next_message(); + let alice_message1 = alice_state1.next_message().unwrap(); let bob_state1 = bob_state0 .receive(&bob_wallet, alice_message1) @@ -190,7 +190,7 @@ mod tests { // Complete the state machine up to State3 let message0 = bob_state0.next_message().unwrap(); let (_, alice_state1) = alice_state0.receive(message0).unwrap(); - let alice_message1 = alice_state1.next_message(); + let alice_message1 = alice_state1.next_message().unwrap(); let bob_state1 = bob_state0 .receive(&bob_wallet, alice_message1) diff --git a/swap-p2p/src/protocols/swap_setup/alice.rs b/swap-p2p/src/protocols/swap_setup/alice.rs index 57070a9c04..9ff5de2d21 100644 --- a/swap-p2p/src/protocols/swap_setup/alice.rs +++ b/swap-p2p/src/protocols/swap_setup/alice.rs @@ -552,9 +552,14 @@ async fn run_swap_setup( .receive(message0) .context("Failed to transition state0 -> state1 using message0")?; - swap_setup::write_cbor_message(&mut substream, state1.next_message()) - .await - .context("Failed to send message1")?; + swap_setup::write_cbor_message( + &mut substream, + state1 + .next_message() + .context("Couldn't construct Mesage1")?, + ) + .await + .context("Failed to send message1")?; let message2 = swap_setup::read_cbor_message::(&mut substream) .await From 3a7035ba07f73bf44dba6f114c1d77a271121af4 Mon Sep 17 00:00:00 2001 From: einliterflasche Date: Wed, 10 Dec 2025 15:23:19 +0100 Subject: [PATCH 043/113] gui: show bitcoin amnesty in SwapSetupInflight page --- .../in_progress/SwapSetupInflightPage.tsx | 133 ++++++++++-------- swap/src/asb/event_loop.rs | 11 +- swap/src/cli/api/tauri_bindings.rs | 3 + swap/src/protocol/bob/swap.rs | 2 + 4 files changed, 84 insertions(+), 65 deletions(-) diff --git a/src-gui/src/renderer/components/pages/swap/swap/in_progress/SwapSetupInflightPage.tsx b/src-gui/src/renderer/components/pages/swap/swap/in_progress/SwapSetupInflightPage.tsx index 01ee48ae0d..02e2c66e3f 100644 --- a/src-gui/src/renderer/components/pages/swap/swap/in_progress/SwapSetupInflightPage.tsx +++ b/src-gui/src/renderer/components/pages/swap/swap/in_progress/SwapSetupInflightPage.tsx @@ -75,7 +75,7 @@ export default function SwapSetupInflightPage({ ); } - const { btc_network_fee, monero_receive_pool, xmr_receive_amount } = + const { btc_network_fee, monero_receive_pool, xmr_receive_amount, btc_amnesty_amount } = request.request.content; return ( @@ -104,6 +104,7 @@ export default function SwapSetupInflightPage({ @@ -187,78 +188,88 @@ export default function SwapSetupInflightPage({ interface BitcoinSendSectionProps { btc_lock_amount: number; btc_network_fee: number; + btc_amnesty_amount: number; } -const BitcoinMainBox = ({ +function BitcoinMainBox ({ btc_lock_amount, btc_network_fee, -}: { - btc_lock_amount: number; - btc_network_fee: number; -}) => ( - + btc_amnesty_amount +}: BitcoinSendSectionProps) { + const guaranteedRefundPercentage: number = (btc_lock_amount - btc_amnesty_amount) / btc_lock_amount * 100; + return ( theme.palette.warning.light + "10", - background: (theme) => - `linear-gradient(135deg, ${theme.palette.warning.light}20, ${theme.palette.warning.light}05)`, + flexDirection: "column", + gap: 1, }} > - ({ - color: theme.palette.text.primary, - })} - > - You send - - ({ - fontWeight: "bold", - color: theme.palette.warning.dark, - textShadow: "0 1px 2px rgba(0,0,0,0.1)", - })} + theme.palette.warning.light + "10", + background: (theme) => + `linear-gradient(135deg, ${theme.palette.warning.light}20, ${theme.palette.warning.light}05)`, + }} > - - - + ({ + color: theme.palette.text.primary, + })} + > + You send + + ({ + fontWeight: "bold", + color: theme.palette.warning.dark, + textShadow: "0 1px 2px rgba(0,0,0,0.1)", + })} + > + + + ({ + color: theme.palette.text.primary, + })} + > + ({guaranteedRefundPercentage}% refund guaranteed) + + - {/* Network fee box attached to the bottom */} - theme.palette.warning.main, - color: (theme) => theme.palette.warning.contrastText, - borderRadius: "4px", - fontSize: "0.75rem", - fontWeight: 600, - boxShadow: "0 2px 4px rgba(0,0,0,0.1)", - whiteSpace: "nowrap", - zIndex: 1, - }} - > - Network fee: + {/* Network fee box attached to the bottom */} + theme.palette.warning.main, + color: (theme) => theme.palette.warning.contrastText, + borderRadius: "4px", + fontSize: "0.75rem", + fontWeight: 600, + boxShadow: "0 2px 4px rgba(0,0,0,0.1)", + whiteSpace: "nowrap", + zIndex: 1, + }} + > + Network fee: + - -); + ) +}; interface PoolBreakdownProps { monero_receive_pool: Array<{ diff --git a/swap/src/asb/event_loop.rs b/swap/src/asb/event_loop.rs index e71fdcb226..6e7261cae5 100644 --- a/swap/src/asb/event_loop.rs +++ b/swap/src/asb/event_loop.rs @@ -836,16 +836,19 @@ fn apply_bitcoin_amnesty_policy( swap_amount: bitcoin::Amount, refund_policy: &RefundPolicy, ) -> Result { - let amount_sats = swap_amount.to_sat(); let btc_amnesty_ratio = Decimal::ONE .checked_sub(refund_policy.taker_refund_ratio) .context("can't have refund ration > 1")?; - let btc_amnesty_sats = Decimal::from_u64(amount_sats) - .context("Decimal overflowed by Bitcoin sats")? + let amount_sats = swap_amount.to_sat(); + let amount_decimal = + Decimal::from_u64(amount_sats).context("Decimal overflowed by Bitcoin sats")?; + + let btc_amnesty_decimal = amount_decimal .checked_mul(btc_amnesty_ratio) .context("Decimal overflow when computing amnesty amount in sats")? - .floor() + .floor(); + let btc_amnesty_sats = btc_amnesty_decimal .try_into() .context("Couldn't convert Decimal to u64")?; diff --git a/swap/src/cli/api/tauri_bindings.rs b/swap/src/cli/api/tauri_bindings.rs index acbed85733..fa9e2451d0 100644 --- a/swap/src/cli/api/tauri_bindings.rs +++ b/swap/src/cli/api/tauri_bindings.rs @@ -95,6 +95,9 @@ pub struct LockBitcoinDetails { pub monero_receive_pool: MoneroAddressPool, #[typeshare(serialized_as = "string")] pub swap_id: Uuid, + /// The amount of Bitcoin the taker will only be able to refund with cooperation from the maker + #[typeshare(serialized_as = "number")] + pub btc_amnesty_amount: bitcoin::Amount, } #[typeshare] diff --git a/swap/src/protocol/bob/swap.rs b/swap/src/protocol/bob/swap.rs index 8f2a84bbb6..9e320291f9 100644 --- a/swap/src/protocol/bob/swap.rs +++ b/swap/src/protocol/bob/swap.rs @@ -154,6 +154,7 @@ async fn next_state( BobState::SwapSetupCompleted(state2) => { // Alice and Bob have exchanged all necessary signatures let xmr_receive_amount = state2.xmr; + let btc_amnesty_amount = state2.btc_amnesty_amount.context("btc_amnesty_amount missing")?; // Sign the Bitcoin lock transaction let (state3, tx_lock) = state2.lock_btc().await?; @@ -172,6 +173,7 @@ async fn next_state( let details = LockBitcoinDetails { btc_lock_amount, btc_network_fee, + btc_amnesty_amount, xmr_receive_amount, monero_receive_pool, swap_id, From da31dcc0756f9c0fd8a0c82c0b52e3c596f3bb46 Mon Sep 17 00:00:00 2001 From: einliterflasche Date: Wed, 10 Dec 2025 16:25:46 +0100 Subject: [PATCH 044/113] state-machine(alice): wait for either TxFullRefund or TxPartialRefund --- swap-asb/src/main.rs | 3 ++- swap-machine/src/alice/mod.rs | 35 +++++++++++++++++++++++++++++++-- swap/src/protocol/alice/swap.rs | 17 +++++++++++++++- 3 files changed, 51 insertions(+), 4 deletions(-) diff --git a/swap-asb/src/main.rs b/swap-asb/src/main.rs index bb0feba121..fb829eaddf 100644 --- a/swap-asb/src/main.rs +++ b/swap-asb/src/main.rs @@ -543,7 +543,8 @@ pub async fn main() -> Result<()> { .next() .context("Couldn't find state Started for this swap")?; - let secret_spend_key = match state3.watch_for_btc_tx_refund(&bitcoin_wallet).await { + let secret_spend_key = match state3.watch_for_btc_tx_full_refund(&bitcoin_wallet).await + { Ok(secret) => secret, Err(error) => { tracing::error!( diff --git a/swap-machine/src/alice/mod.rs b/swap-machine/src/alice/mod.rs index 017db7f1d4..5bc115609d 100644 --- a/swap-machine/src/alice/mod.rs +++ b/swap-machine/src/alice/mod.rs @@ -9,7 +9,7 @@ use std::fmt; use std::sync::Arc; use swap_core::bitcoin::{ CancelTimelock, ExpiredTimelocks, PunishTimelock, Transaction, TxCancel, TxEarlyRefund, - TxFullRefund, TxPunish, TxRedeem, Txid, current_epoch, + TxFullRefund, TxPartialRefund, TxPunish, TxRedeem, Txid, current_epoch, }; use swap_core::monero; use swap_core::monero::ScalarExt; @@ -575,6 +575,19 @@ impl State3 { ) } + pub fn tx_partial_refund(&self) -> Result { + swap_core::bitcoin::TxPartialRefund::new( + &self.tx_cancel(), + &self.refund_address, + self.a.public(), + self.B, + self.btc_amnesty_amount + .context("Missing btc_amnesty_amount")?, + self.tx_partial_refund_fee + .context("Missing tx_partial_refund_fee")?, + ) + } + pub fn tx_redeem(&self) -> TxRedeem { TxRedeem::new(&self.tx_lock, &self.redeem_address, self.tx_redeem_fee) } @@ -711,7 +724,7 @@ impl State3 { } } - pub async fn watch_for_btc_tx_refund( + pub async fn watch_for_btc_tx_full_refund( &self, bitcoin_wallet: &dyn bitcoin_wallet::BitcoinWallet, ) -> Result { @@ -728,6 +741,24 @@ impl State3 { "Bitcoin refund transaction not found even though we saw it in the mempool previously", ) } + + pub async fn watch_for_btc_tx_partial_refund( + &self, + bitcoin_wallet: &dyn bitcoin_wallet::BitcoinWallet, + ) -> Result { + let tx_refund_status = bitcoin_wallet + .subscribe_to(Box::new(self.tx_partial_refund()?)) + .await; + + tx_refund_status + .wait_until_seen() + .await + .context("Failed to monitor refund transaction")?; + + self.refund_btc(bitcoin_wallet).await?.context( + "Bitcoin refund transaction not found even though we saw it in the mempool previously", + ) + } } pub trait ReservesMonero { diff --git a/swap/src/protocol/alice/swap.rs b/swap/src/protocol/alice/swap.rs index dd11741079..73fdb990cb 100644 --- a/swap/src/protocol/alice/swap.rs +++ b/swap/src/protocol/alice/swap.rs @@ -607,8 +607,23 @@ where .subscribe_to(Box::new(state3.tx_cancel())) .await; + // We wait for either TxFullRefund or TxPartialRefund to be published + // - both allow us to extract the Monero refund key. + // Otherwise we punish, once that timelock expired. + + // TODO: should we retry here? select! { - spend_key = state3.watch_for_btc_tx_refund(&*bitcoin_wallet) => { + spend_key = state3.watch_for_btc_tx_full_refund(&*bitcoin_wallet) => { + let spend_key = spend_key?; + + AliceState::BtcRefunded { + monero_wallet_restore_blockheight, + transfer_proof, + spend_key, + state3, + } + } + spend_key = state3.watch_for_btc_tx_partial_refund(&*bitcoin_wallet) => { let spend_key = spend_key?; AliceState::BtcRefunded { From 478b9507fa502c143440bb8c4e9199f3cd8c5cd2 Mon Sep 17 00:00:00 2001 From: einliterflasche Date: Thu, 11 Dec 2025 12:02:13 +0100 Subject: [PATCH 045/113] implement BitcoinWallet::ensure_broadcasted --- bitcoin-wallet/src/wallet.rs | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/bitcoin-wallet/src/wallet.rs b/bitcoin-wallet/src/wallet.rs index 40014d6598..5808ffd266 100644 --- a/bitcoin-wallet/src/wallet.rs +++ b/bitcoin-wallet/src/wallet.rs @@ -760,6 +760,25 @@ impl Wallet { Ok((txid, subscription)) } + /// Broadcast a transaction, but only if it's not already in the mempool/blockchain. + /// Return txid and a subcription to it's status in either case. + pub async fn ensure_broadcasted( + &self, + tx: Transaction, + kind: &str, + ) -> Result<(Txid, Subscription)> { + let txid = tx.compute_txid(); + + let status = self.status_of_script(&tx).await?; + + if matches!(status, ScriptStatus::InMempool | ScriptStatus::Confirmed(_)) { + let subscription = self.subscribe_to(Box::new(tx)).await; + return Ok((txid, subscription)); + } + + self.broadcast(tx, kind).await + } + pub async fn get_raw_transaction(&self, txid: Txid) -> Result>> { self.get_tx(txid) .await From aadffaaa0ebfa7bcac14bd00a4f9709d34849132 Mon Sep 17 00:00:00 2001 From: einliterflasche Date: Thu, 11 Dec 2025 12:24:02 +0100 Subject: [PATCH 046/113] state-machine(alice): add BtcPartiallyRefunded and XmrRefundable states --- swap-db/src/alice.rs | 30 +++++++++++++++++++++++++++ swap-machine/src/alice/mod.rs | 16 +++++++++++++- swap/src/asb/recovery/cancel.rs | 2 ++ swap/src/asb/recovery/punish.rs | 2 ++ swap/src/asb/recovery/refund.rs | 2 ++ swap/src/asb/recovery/safely_abort.rs | 2 ++ swap/src/protocol/alice/swap.rs | 22 ++++++++++++++++++++ 7 files changed, 75 insertions(+), 1 deletion(-) diff --git a/swap-db/src/alice.rs b/swap-db/src/alice.rs index 187a1b7a29..001ae7c553 100644 --- a/swap-db/src/alice.rs +++ b/swap-db/src/alice.rs @@ -75,6 +75,13 @@ pub enum Alice { #[serde(with = "swap_serde::monero::private_key")] spend_key: monero::PrivateKey, }, + BtcPartiallyRefunded { + monero_wallet_restore_blockheight: BlockHeight, + transfer_proof: TransferProof, + state3: alice::State3, + #[serde(with = "swap_serde::monero::private_key")] + spend_key: monero::PrivateKey, + }, Done(AliceEndState), } @@ -170,6 +177,17 @@ impl From for Alice { spend_key, state3: state3.as_ref().clone(), }, + AliceState::BtcPartiallyRefunded { + monero_wallet_restore_blockheight, + transfer_proof, + spend_key, + state3, + } => Alice::BtcPartiallyRefunded { + monero_wallet_restore_blockheight, + transfer_proof, + state3: *state3, + spend_key, + }, AliceState::BtcEarlyRefundable { state3 } => Alice::BtcEarlyRefundable { state3: state3.as_ref().clone(), }, @@ -320,6 +338,17 @@ impl From for AliceState { spend_key, state3: Box::new(state3), }, + Alice::BtcPartiallyRefunded { + monero_wallet_restore_blockheight, + transfer_proof, + state3, + spend_key, + } => AliceState::BtcPartiallyRefunded { + monero_wallet_restore_blockheight, + transfer_proof, + spend_key, + state3: Box::new(state3), + }, Alice::BtcEarlyRefundable { state3 } => AliceState::BtcEarlyRefundable { state3: Box::new(state3), }, @@ -366,6 +395,7 @@ impl fmt::Display for Alice { Alice::BtcCancelled { .. } => f.write_str("Bitcoin cancel transaction published"), Alice::BtcPunishable { .. } => f.write_str("Bitcoin punishable"), Alice::BtcRefunded { .. } => f.write_str("Monero refundable"), + Alice::BtcPartiallyRefunded { .. } => f.write_str("Monero refundable"), Alice::BtcEarlyRefundable { .. } => f.write_str("Bitcoin early refundable"), Alice::Done(end_state) => write!(f, "Done: {}", end_state), } diff --git a/swap-machine/src/alice/mod.rs b/swap-machine/src/alice/mod.rs index 5bc115609d..25e422249e 100644 --- a/swap-machine/src/alice/mod.rs +++ b/swap-machine/src/alice/mod.rs @@ -69,11 +69,19 @@ pub enum AliceState { spend_key: monero::PrivateKey, state3: Box, }, - BtcPunishable { + BtcPartiallyRefunded { + monero_wallet_restore_blockheight: BlockHeight, + transfer_proof: TransferProof, + spend_key: monero::PrivateKey, + state3: Box, + }, + XmrRefundable { monero_wallet_restore_blockheight: BlockHeight, transfer_proof: TransferProof, + spend_key: monero::PrivateKey, state3: Box, }, + // TODO: save redeem transaction id XmrRefunded, WaitingForCancelTimelockExpiration { monero_wallet_restore_blockheight: BlockHeight, @@ -85,6 +93,11 @@ pub enum AliceState { transfer_proof: TransferProof, state3: Box, }, + BtcPunishable { + monero_wallet_restore_blockheight: BlockHeight, + transfer_proof: TransferProof, + state3: Box, + }, BtcPunished { state3: Box, transfer_proof: TransferProof, @@ -133,6 +146,7 @@ impl fmt::Display for AliceState { AliceState::CancelTimelockExpired { .. } => write!(f, "cancel timelock is expired"), AliceState::BtcEarlyRefundable { .. } => write!(f, "btc is early refundable"), AliceState::BtcEarlyRefunded(_) => write!(f, "btc is early refunded"), + AliceState::BtcPartiallyRefunded { .. } => write!(f, "btc is partially refunded"), } } } diff --git a/swap/src/asb/recovery/cancel.rs b/swap/src/asb/recovery/cancel.rs index 6f8dc51000..7e58193c82 100644 --- a/swap/src/asb/recovery/cancel.rs +++ b/swap/src/asb/recovery/cancel.rs @@ -30,6 +30,8 @@ pub async fn cancel( | AliceState::CancelTimelockExpired { monero_wallet_restore_blockheight, transfer_proof, state3} | AliceState::BtcCancelled { monero_wallet_restore_blockheight, transfer_proof, state3 } | AliceState::BtcRefunded { monero_wallet_restore_blockheight, transfer_proof, state3 ,.. } + | AliceState::BtcPartiallyRefunded { monero_wallet_restore_blockheight, transfer_proof, state3 ,.. } + | AliceState::XmrRefundable { monero_wallet_restore_blockheight, transfer_proof, state3 ,.. } | AliceState::BtcPunishable { monero_wallet_restore_blockheight, transfer_proof, state3 } => { (monero_wallet_restore_blockheight, transfer_proof, state3) } diff --git a/swap/src/asb/recovery/punish.rs b/swap/src/asb/recovery/punish.rs index a71a1b58a3..76b09a882d 100644 --- a/swap/src/asb/recovery/punish.rs +++ b/swap/src/asb/recovery/punish.rs @@ -34,6 +34,8 @@ pub async fn punish( // The state machine is in a state where punish is theoretically impossible but we try and punish anyway as this is what the user wants | AliceState::BtcRedeemTransactionPublished { state3, transfer_proof, .. } | AliceState::BtcRefunded { state3, transfer_proof,.. } => { (state3, transfer_proof) } + | AliceState::BtcPartiallyRefunded { state3, transfer_proof,.. } => { (state3, transfer_proof) } + | AliceState::XmrRefundable { state3, transfer_proof,.. } => { (state3, transfer_proof) } // Alice already in final state or at the start of the swap so we can't punish | AliceState::Started { .. } diff --git a/swap/src/asb/recovery/refund.rs b/swap/src/asb/recovery/refund.rs index b747af5bc7..79aab3b3ce 100644 --- a/swap/src/asb/recovery/refund.rs +++ b/swap/src/asb/recovery/refund.rs @@ -51,6 +51,8 @@ pub async fn refund( // Refund possible due to cancel transaction already being published | AliceState::BtcCancelled { transfer_proof, state3, .. } | AliceState::BtcRefunded { transfer_proof, state3, .. } + | AliceState::BtcPartiallyRefunded { transfer_proof, state3, .. } + | AliceState::XmrRefundable { transfer_proof, state3, .. } | AliceState::BtcPunishable { transfer_proof, state3, .. } => { (transfer_proof, state3) } diff --git a/swap/src/asb/recovery/safely_abort.rs b/swap/src/asb/recovery/safely_abort.rs index f9e5812a34..f5382d3cc5 100644 --- a/swap/src/asb/recovery/safely_abort.rs +++ b/swap/src/asb/recovery/safely_abort.rs @@ -29,6 +29,8 @@ pub async fn safely_abort(swap_id: Uuid, db: Arc) -> Result AliceState::XmrRefundable { + monero_wallet_restore_blockheight, + transfer_proof, + spend_key, + state3, + }, + AliceState::BtcPartiallyRefunded { + transfer_proof, + spend_key, + state3, + monero_wallet_restore_blockheight, + } => AliceState::XmrRefundable { + monero_wallet_restore_blockheight, + transfer_proof, + spend_key, + state3, + }, + AliceState::XmrRefundable { + monero_wallet_restore_blockheight, + transfer_proof, + spend_key, + state3, } => { retry( "Refund Monero", From de920474c025db7852cfa9df3b725321e44cf36d Mon Sep 17 00:00:00 2001 From: einliterflasche Date: Thu, 11 Dec 2025 15:21:20 +0100 Subject: [PATCH 047/113] add justfile aliases for gui linting --- justfile | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/justfile b/justfile index f3ba6bad62..198b5e0eb9 100644 --- a/justfile +++ b/justfile @@ -108,10 +108,14 @@ fmt: generate-sqlx-cache: ./dev-scripts/regenerate_sqlx_cache.sh + +alias eslint := check_gui_eslint # Run eslint for the GUI frontend check_gui_eslint: cd src-gui && yarn run eslint +alias tsc := check_gui_tsc + # Run the typescript type checker for the GUI frontend check_gui_tsc: cd src-gui && yarn run tsc --noEmit From dd433d4ba3c6d546c7bca542548bae46a565757e Mon Sep 17 00:00:00 2001 From: einliterflasche Date: Thu, 11 Dec 2025 15:22:14 +0100 Subject: [PATCH 048/113] state-machine(alice): add XmrRefundable state --- swap-db/src/alice.rs | 30 ++++++++++++++++++++++++++++++ swap-machine/src/alice/mod.rs | 1 + swap/src/asb/recovery/redeem.rs | 2 ++ swap/src/protocol/alice/swap.rs | 21 ++++++++++++++------- 4 files changed, 47 insertions(+), 7 deletions(-) diff --git a/swap-db/src/alice.rs b/swap-db/src/alice.rs index 001ae7c553..4d3c6cb57f 100644 --- a/swap-db/src/alice.rs +++ b/swap-db/src/alice.rs @@ -82,6 +82,13 @@ pub enum Alice { #[serde(with = "swap_serde::monero::private_key")] spend_key: monero::PrivateKey, }, + XmrRefundable { + monero_wallet_restore_blockheight: BlockHeight, + transfer_proof: TransferProof, + state3: alice::State3, + #[serde(with = "swap_serde::monero::private_key")] + spend_key: monero::PrivateKey, + }, Done(AliceEndState), } @@ -188,6 +195,17 @@ impl From for Alice { state3: *state3, spend_key, }, + AliceState::XmrRefundable { + monero_wallet_restore_blockheight, + transfer_proof, + state3, + spend_key, + } => Alice::XmrRefundable { + monero_wallet_restore_blockheight, + transfer_proof, + state3: *state3, + spend_key, + }, AliceState::BtcEarlyRefundable { state3 } => Alice::BtcEarlyRefundable { state3: state3.as_ref().clone(), }, @@ -352,6 +370,17 @@ impl From for AliceState { Alice::BtcEarlyRefundable { state3 } => AliceState::BtcEarlyRefundable { state3: Box::new(state3), }, + Alice::XmrRefundable { + monero_wallet_restore_blockheight, + transfer_proof, + state3, + spend_key, + } => AliceState::XmrRefundable { + monero_wallet_restore_blockheight, + transfer_proof, + spend_key, + state3: Box::new(state3), + }, Alice::Done(end_state) => match end_state { AliceEndState::SafelyAborted => AliceState::SafelyAborted, AliceEndState::BtcRedeemed => AliceState::BtcRedeemed, @@ -397,6 +426,7 @@ impl fmt::Display for Alice { Alice::BtcRefunded { .. } => f.write_str("Monero refundable"), Alice::BtcPartiallyRefunded { .. } => f.write_str("Monero refundable"), Alice::BtcEarlyRefundable { .. } => f.write_str("Bitcoin early refundable"), + Alice::XmrRefundable { .. } => f.write_str("Bitcoin early refundable"), Alice::Done(end_state) => write!(f, "Done: {}", end_state), } } diff --git a/swap-machine/src/alice/mod.rs b/swap-machine/src/alice/mod.rs index 25e422249e..4f849017a5 100644 --- a/swap-machine/src/alice/mod.rs +++ b/swap-machine/src/alice/mod.rs @@ -147,6 +147,7 @@ impl fmt::Display for AliceState { AliceState::BtcEarlyRefundable { .. } => write!(f, "btc is early refundable"), AliceState::BtcEarlyRefunded(_) => write!(f, "btc is early refunded"), AliceState::BtcPartiallyRefunded { .. } => write!(f, "btc is partially refunded"), + AliceState::XmrRefundable { .. } => write!(f, "xmr is refundable"), } } } diff --git a/swap/src/asb/recovery/redeem.rs b/swap/src/asb/recovery/redeem.rs index 586463bbee..9024450a2a 100644 --- a/swap/src/asb/recovery/redeem.rs +++ b/swap/src/asb/recovery/redeem.rs @@ -87,9 +87,11 @@ pub async fn redeem( | AliceState::CancelTimelockExpired { .. } | AliceState::BtcCancelled { .. } | AliceState::BtcRefunded { .. } + | AliceState::BtcPartiallyRefunded { .. } | AliceState::BtcPunishable { .. } | AliceState::BtcRedeemed | AliceState::XmrRefunded + | AliceState::XmrRefundable { .. } | AliceState::BtcEarlyRefundable { .. } | AliceState::BtcEarlyRefunded(_) | AliceState::BtcPunished { .. } diff --git a/swap/src/protocol/alice/swap.rs b/swap/src/protocol/alice/swap.rs index 2fe001db30..29a981d9d1 100644 --- a/swap/src/protocol/alice/swap.rs +++ b/swap/src/protocol/alice/swap.rs @@ -1,5 +1,6 @@ //! Run an XMR/BTC swap in the role of Alice. //! Alice holds XMR and wishes receive BTC. +use std::any::Any; use std::sync::Arc; use std::time::Duration; @@ -648,7 +649,7 @@ where transfer_proof, spend_key, state3, - .. + monero_wallet_restore_blockheight, } => AliceState::XmrRefundable { monero_wallet_restore_blockheight, transfer_proof, @@ -660,12 +661,18 @@ where spend_key, state3, monero_wallet_restore_blockheight, - } => AliceState::XmrRefundable { - monero_wallet_restore_blockheight, - transfer_proof, - spend_key, - state3, - }, + } => { + let should_grant_amnesty = true; + + // TODO: Publish amnesty transaction/send amnesty tx sig / decide against it + + AliceState::XmrRefundable { + monero_wallet_restore_blockheight, + transfer_proof, + spend_key, + state3, + } + } AliceState::XmrRefundable { monero_wallet_restore_blockheight, transfer_proof, From fdaa70d954504d0e0b7966277c1dafbfb2476ac5 Mon Sep 17 00:00:00 2001 From: einliterflasche Date: Thu, 11 Dec 2025 15:22:28 +0100 Subject: [PATCH 049/113] tiny gui fixes --- src-gui/src/renderer/components/other/MonospaceTextBox.tsx | 6 +++--- .../components/pages/monero/SendTransactionModal.tsx | 4 ++-- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src-gui/src/renderer/components/other/MonospaceTextBox.tsx b/src-gui/src/renderer/components/other/MonospaceTextBox.tsx index bce732a0e8..739fb67258 100644 --- a/src-gui/src/renderer/components/other/MonospaceTextBox.tsx +++ b/src-gui/src/renderer/components/other/MonospaceTextBox.tsx @@ -17,9 +17,9 @@ export default function MonospaceTextBox({ display: "flex", alignItems: "center", justifyContent: "space-between", - backgroundColor: light ? "transparent" : theme.palette.grey[900], - borderRadius: 2, - border: light ? `1px solid ${theme.palette.grey[800]}` : "none", + backgroundColor: theme.palette.action.hover, + borderRadius: theme.shape.borderRadius, + border: "none", padding: theme.spacing(1), gap: 1, })} diff --git a/src-gui/src/renderer/components/pages/monero/SendTransactionModal.tsx b/src-gui/src/renderer/components/pages/monero/SendTransactionModal.tsx index e25aae0110..f589569b6f 100644 --- a/src-gui/src/renderer/components/pages/monero/SendTransactionModal.tsx +++ b/src-gui/src/renderer/components/pages/monero/SendTransactionModal.tsx @@ -27,7 +27,7 @@ export default function SendTransactionModal({ const showSuccess = successResponse !== null; - const handleClose = (event: unknown, reason: string) => { + const handleClose = (_: any, reason: string) => { // We want the user to explicitly close the dialog. // We do not close the dialog upon a backdrop click. if (reason === "backdropClick") { @@ -60,7 +60,7 @@ export default function SendTransactionModal({ )} {showSuccess && ( )} From 1f913b2b22bd200a9cb17041163ad18a0524111a Mon Sep 17 00:00:00 2001 From: einliterflasche Date: Thu, 11 Dec 2025 16:27:58 +0100 Subject: [PATCH 050/113] gui: update types to reflect new states --- src-gui/src/models/tauriModelExt.ts | 19 ++++++++++++++++ .../pages/swap/swap/SwapStatePage.tsx | 14 +++++++++++- .../swap/swap/done/BitcoinRefundedPage.tsx | 22 +++++++++++++++++++ swap-machine/src/bob/mod.rs | 2 +- 4 files changed, 55 insertions(+), 2 deletions(-) diff --git a/src-gui/src/models/tauriModelExt.ts b/src-gui/src/models/tauriModelExt.ts index 2023d94e05..69d112ef2a 100644 --- a/src-gui/src/models/tauriModelExt.ts +++ b/src-gui/src/models/tauriModelExt.ts @@ -50,9 +50,13 @@ export enum BobStateName { CancelTimelockExpired = "cancel timelock is expired", BtcCancelled = "btc is cancelled", BtcRefundPublished = "btc refund is published", + BtcPartialRefundPublished = "btc partial refund is published", BtcEarlyRefundPublished = "btc early refund is published", BtcRefunded = "btc is refunded", BtcEarlyRefunded = "btc is early refunded", + BtcPartiallyRefunded = "btc is partially refunded", + BtcAmnestyPublished = "btc amnesty is published", + BtcAmnestyReceived = "btc amnesty is confirmed", XmrRedeemed = "xmr is redeemed", BtcPunished = "btc is punished", SafelyAborted = "safely aborted", @@ -84,10 +88,18 @@ export function bobStateNameToHumanReadable(stateName: BobStateName): string { return "Bitcoin refund published"; case BobStateName.BtcEarlyRefundPublished: return "Bitcoin early refund published"; + case BobStateName.BtcPartialRefundPublished: + return "Bitcoin partial refund published"; + case BobStateName.BtcAmnestyPublished: + return "Bitcoin amnesty was granted"; case BobStateName.BtcRefunded: return "Bitcoin refunded"; case BobStateName.BtcEarlyRefunded: return "Bitcoin early refunded"; + case BobStateName.BtcPartiallyRefunded: + return "Bitcoin partially refunded"; + case BobStateName.BtcAmnestyReceived: + return "Bitcoin amnesty was received"; case BobStateName.XmrRedeemed: return "Monero redeemed"; case BobStateName.BtcPunished: @@ -133,6 +145,10 @@ export type BobStateNameRunningSwap = Exclude< | BobStateName.Started | BobStateName.SwapSetupCompleted | BobStateName.BtcRefunded + | BobStateName.BtcPartiallyRefunded + | BobStateName.BtcAmnestyPublished + | BobStateName.BtcAmnestyReceived + | BobStateName.BtcRefunded | BobStateName.BtcEarlyRefunded | BobStateName.BtcPunished | BobStateName.SafelyAborted @@ -151,6 +167,9 @@ export function isBobStateNameRunningSwap( BobStateName.SwapSetupCompleted, BobStateName.BtcRefunded, BobStateName.BtcEarlyRefunded, + BobStateName.BtcPartiallyRefunded, + BobStateName.BtcAmnestyPublished, + BobStateName.BtcAmnestyReceived, BobStateName.BtcPunished, BobStateName.SafelyAborted, BobStateName.XmrRedeemed, diff --git a/src-gui/src/renderer/components/pages/swap/swap/SwapStatePage.tsx b/src-gui/src/renderer/components/pages/swap/swap/SwapStatePage.tsx index 455e4c210c..dc78f3a5a5 100644 --- a/src-gui/src/renderer/components/pages/swap/swap/SwapStatePage.tsx +++ b/src-gui/src/renderer/components/pages/swap/swap/SwapStatePage.tsx @@ -7,6 +7,10 @@ import { BitcoinEarlyRefundedPage, BitcoinEarlyRefundPublishedPage, BitcoinRefundPublishedPage, + BitcoinPartialRefundPublished, + BitcoinPartiallyRefunded, + BitcoinAmnestyPublished, + BitcoinAmnestyReceived } from "./done/BitcoinRefundedPage"; import XmrRedeemInMempoolPage from "./done/XmrRedeemInMempoolPage"; import ProcessExitedPage from "./exited/ProcessExitedPage"; @@ -88,7 +92,7 @@ export default function SwapStatePage({ state }: { state: SwapState | null }) { case "BtcCancelled": return ; - //// 4 different types of Bitcoin refund states we can be in + //// 8 different types of Bitcoin refund states we can be in case "BtcRefundPublished": // tx_refund has been published but has not been confirmed yet if (state.curr.type === "BtcRefundPublished") { return ; @@ -109,6 +113,14 @@ export default function SwapStatePage({ state }: { state: SwapState | null }) { return ; } break; + case "BtcPartialRefundPublished": + return ; + case "BtcPartiallyRefunded": + return ; + case "BtcAmnestyPublished": + return ; + case "BtcAmnestyReceived": + return ; //// 4 different types of Bitcoin punished states we can be in case "BtcPunished": diff --git a/src-gui/src/renderer/components/pages/swap/swap/done/BitcoinRefundedPage.tsx b/src-gui/src/renderer/components/pages/swap/swap/done/BitcoinRefundedPage.tsx index 866a6fcc41..5c56cd939f 100644 --- a/src-gui/src/renderer/components/pages/swap/swap/done/BitcoinRefundedPage.tsx +++ b/src-gui/src/renderer/components/pages/swap/swap/done/BitcoinRefundedPage.tsx @@ -91,3 +91,25 @@ function MultiBitcoinRefundedPage({ ); } + +export function BitcoinPartialRefundPublished() { + return ( + <>TxPartialRefund published + ) +} + +export function BitcoinPartiallyRefunded() { + return ( + <>Bitcoin partially refunded + ) +} +export function BitcoinAmnestyPublished() { + return ( + <>TxAmnesty published + ) +} +export function BitcoinAmnestyReceived() { + return ( + <>Bitcoin amnesty received + ) +} diff --git a/swap-machine/src/bob/mod.rs b/swap-machine/src/bob/mod.rs index fa7ac5d3fe..c4a1a2983c 100644 --- a/swap-machine/src/bob/mod.rs +++ b/swap-machine/src/bob/mod.rs @@ -131,7 +131,7 @@ impl fmt::Display for BobState { BobState::BtcRefundPublished { .. } => write!(f, "btc refund is published"), BobState::BtcEarlyRefundPublished { .. } => write!(f, "btc early refund is published"), BobState::BtcPartialRefundPublished { .. } => { - write!(f, "btc partially refund is published") + write!(f, "btc partial refund is published") } BobState::BtcRefunded(..) => write!(f, "btc is refunded"), BobState::XmrRedeemed { .. } => write!(f, "xmr is redeemed"), From a621ccffdb0a0034e6402cca9b13d7c15cf95b6a Mon Sep 17 00:00:00 2001 From: einliterflasche Date: Fri, 12 Dec 2025 12:59:40 +0100 Subject: [PATCH 051/113] bob: send sig for TxRefundAmnesty, alice: publish it --- .../alert/SwapStatusAlert/SwapStatusAlert.tsx | 1 + swap-core/src/bitcoin/refund_amnesty.rs | 35 +++++++--- swap-machine/src/alice/mod.rs | 69 +++++++++++++++++-- swap-machine/src/bob/mod.rs | 32 +++++++-- swap-machine/src/common/mod.rs | 5 +- swap-machine/src/lib.rs | 4 +- swap-p2p/src/protocols/swap_setup/bob.rs | 11 ++- swap/src/protocol/alice/swap.rs | 10 ++- 8 files changed, 138 insertions(+), 29 deletions(-) diff --git a/src-gui/src/renderer/components/alert/SwapStatusAlert/SwapStatusAlert.tsx b/src-gui/src/renderer/components/alert/SwapStatusAlert/SwapStatusAlert.tsx index 7fb8594fdd..f83da10481 100644 --- a/src-gui/src/renderer/components/alert/SwapStatusAlert/SwapStatusAlert.tsx +++ b/src-gui/src/renderer/components/alert/SwapStatusAlert/SwapStatusAlert.tsx @@ -194,6 +194,7 @@ export function StateAlert({ case BobStateName.CancelTimelockExpired: case BobStateName.BtcCancelled: case BobStateName.BtcRefundPublished: // Even if the transactions have been published, it cannot be + case BobStateName.BtcPartialRefundPublished: // Even if the transactions have been published, it cannot be case BobStateName.BtcEarlyRefundPublished: // guaranteed that they will be confirmed in time if (timelock != null) { switch (timelock.type) { diff --git a/swap-core/src/bitcoin/refund_amnesty.rs b/swap-core/src/bitcoin/refund_amnesty.rs index 3ffc3c04ea..0acb6c290e 100644 --- a/swap-core/src/bitcoin/refund_amnesty.rs +++ b/swap-core/src/bitcoin/refund_amnesty.rs @@ -1,11 +1,9 @@ use crate::bitcoin::partial_refund::TxPartialRefund; -use crate::bitcoin::{ - Address, Amount, PublicKey, Transaction, -}; +use crate::bitcoin::{self, Address, Amount, PublicKey, Transaction}; use ::bitcoin::sighash::SighashCache; -use ::bitcoin::{secp256k1, ScriptBuf, Weight}; -use ::bitcoin::{sighash::SegwitV0Sighash as Sighash, EcdsaSighashType, Txid}; -use anyhow::Result; +use ::bitcoin::{EcdsaSighashType, Txid, sighash::SegwitV0Sighash as Sighash}; +use ::bitcoin::{ScriptBuf, Weight, secp256k1}; +use anyhow::{Context, Result}; use bdk_wallet::miniscript::Descriptor; use bitcoin_wallet::primitives::Watchable; use ecdsa_fun::Signature; @@ -20,8 +18,13 @@ pub struct TxRefundAmnesty { } impl TxRefundAmnesty { - pub fn new(tx_refund: &TxPartialRefund, refund_address: &Address, spending_fee: Amount) -> Self { - let tx_refund_amnesty = tx_refund.build_amnesty_spend_transaction(refund_address, spending_fee); + pub fn new( + tx_refund: &TxPartialRefund, + refund_address: &Address, + spending_fee: Amount, + ) -> Self { + let tx_refund_amnesty = + tx_refund.build_amnesty_spend_transaction(refund_address, spending_fee); let digest = SighashCache::new(&tx_refund_amnesty) .p2wsh_signature_hash( @@ -51,6 +54,20 @@ impl TxRefundAmnesty { self.digest } + pub fn complete_as_alice( + &self, + s_a: bitcoin::SecretKey, + B: bitcoin::PublicKey, + sig_b: Signature, + ) -> Result { + let digest = self.digest(); + let sig_a = s_a.sign(digest); + + self.clone() + .add_signatures((s_a.public(), sig_a), (B, sig_b)) + .context("Couldn't add signatures to transaction") + } + pub fn add_signatures( self, (A, sig_a): (PublicKey, Signature), @@ -70,7 +87,7 @@ impl TxRefundAmnesty { let sig_a = secp256k1::ecdsa::Signature::from_compact(&sig_a.to_bytes())?; let sig_b = secp256k1::ecdsa::Signature::from_compact(&sig_b.to_bytes())?; - + // The order in which these are inserted doesn't matter satisfier.insert( A, diff --git a/swap-machine/src/alice/mod.rs b/swap-machine/src/alice/mod.rs index 4f849017a5..19e2cef4c8 100644 --- a/swap-machine/src/alice/mod.rs +++ b/swap-machine/src/alice/mod.rs @@ -5,11 +5,11 @@ use anyhow::{Context, Result, anyhow, bail}; use rand::{CryptoRng, RngCore}; use serde::{Deserialize, Serialize}; use sigma_fun::ext::dl_secp256k1_ed25519_eq::CrossCurveDLEQProof; -use std::fmt; +use std::fmt::{self, Debug}; use std::sync::Arc; use swap_core::bitcoin::{ CancelTimelock, ExpiredTimelocks, PunishTimelock, Transaction, TxCancel, TxEarlyRefund, - TxFullRefund, TxPartialRefund, TxPunish, TxRedeem, Txid, current_epoch, + TxFullRefund, TxPartialRefund, TxPunish, TxRedeem, TxRefundAmnesty, Txid, current_epoch, }; use swap_core::monero; use swap_core::monero::ScalarExt; @@ -63,6 +63,9 @@ pub enum AliceState { state3: Box, }, BtcEarlyRefunded(Box), + // We enter the refund states regardless of whether or not the refund + // transaction was confirmed because we do not care. We can extract the key + // we need to refund ourself regardless. BtcRefunded { monero_wallet_restore_blockheight: BlockHeight, transfer_proof: TransferProof, @@ -313,9 +316,13 @@ impl State1 { } pub fn receive(self, msg: Message2) -> Result { - let tx_lock = - swap_core::bitcoin::TxLock::from_psbt(msg.psbt, self.a.public(), self.B, self.btc) - .context("Failed to re-construct TxLock from received PSBT")?; + let tx_lock = swap_core::bitcoin::TxLock::from_psbt( + msg.tx_lock_psbt, + self.a.public(), + self.B, + self.btc, + ) + .context("Failed to re-construct TxLock from received PSBT")?; Ok(State2 { a: self.a, @@ -450,6 +457,33 @@ impl State2 { ) .context("Failed to verify early refund transaction")?; + // Create TxRefundAmnesty ourself + let tx_partial_refund = TxPartialRefund::new( + &tx_cancel, + &self.refund_address, + self.a.public(), + self.B, + self.btc_amnesty_amount + .context("missing btc_amnesty_amount")?, + self.tx_partial_refund_fee + .context("missing tx_partial_refund_fee")?, + ) + .context("Couldn't construct TxPartialRefund")?; + let tx_refund_amnesty = TxRefundAmnesty::new( + &tx_partial_refund, + &self.refund_address, + self.tx_refund_amnesty_fee + .context("missing tx_refund_amnesty_fee")?, + ); + + // Check if the provided signature by Bob is valid for the transaction + swap_core::bitcoin::verify_sig( + &self.B, + &tx_refund_amnesty.digest(), + &msg.tx_refund_amnesty_sig, + ) + .context("Failed to verify refund amnesty transaction")?; + Ok(State3 { a: self.a, B: self.B, @@ -469,6 +503,7 @@ impl State2 { tx_punish_sig_bob: msg.tx_punish_sig, tx_cancel_sig_bob: msg.tx_cancel_sig, tx_early_refund_sig_bob: msg.tx_early_refund_sig.into(), + tx_refund_amnesty_sig_bob: msg.tx_refund_amnesty_sig.into(), tx_redeem_fee: self.tx_redeem_fee, tx_punish_fee: self.tx_punish_fee, tx_refund_fee: self.tx_refund_fee, @@ -513,6 +548,12 @@ pub struct State3 { /// to wait for the timelock to expire. #[serde(default)] tx_early_refund_sig_bob: Option, + /// This field was added in PR [#675](https://github.com/eigenwallet/core/pull/344). + /// It is optional to maintain backwards compatibility with old swaps in the database. + /// Bob must send this to us during swap setup, in order for us to publish TxRefundAmnesty + /// in case of a refund. Otherwise Bob will only be partially refunded. + #[serde(default)] + tx_refund_amnesty_sig_bob: Option, tx_redeem_fee: bitcoin::Amount, pub tx_punish_fee: bitcoin::Amount, pub tx_refund_fee: bitcoin::Amount, @@ -664,6 +705,24 @@ impl State3 { Ok(tx_id) } + pub fn signed_bitcoin_amnesty_transaction(&self) -> Result { + let tx_partial_refund = self.tx_partial_refund()?; + let tx_amnesty = TxRefundAmnesty::new( + &tx_partial_refund, + &self.refund_address, + self.tx_refund_amnesty_fee + .context("Missing tx_refund_amnesty_fee")?, + ); + + tx_amnesty.complete_as_alice( + self.a.clone(), + self.B, + self.tx_refund_amnesty_sig_bob + .clone() + .context("missing Bob's signature for TxRefundAmnesty")?, + ) + } + pub async fn punish_btc( &self, bitcoin_wallet: &dyn bitcoin_wallet::BitcoinWallet, diff --git a/swap-machine/src/bob/mod.rs b/swap-machine/src/bob/mod.rs index c4a1a2983c..627509fb66 100644 --- a/swap-machine/src/bob/mod.rs +++ b/swap-machine/src/bob/mod.rs @@ -14,8 +14,8 @@ use sigma_fun::ext::dl_secp256k1_ed25519_eq::CrossCurveDLEQProof; use std::fmt; use std::sync::Arc; use swap_core::bitcoin::{ - self, CancelTimelock, ExpiredTimelocks, PunishTimelock, Transaction, TxCancel, TxLock, Txid, - current_epoch, + self, CancelTimelock, ExpiredTimelocks, PunishTimelock, Transaction, TxCancel, TxLock, + TxPartialRefund, TxRefundAmnesty, Txid, current_epoch, }; use swap_core::monero::ScalarExt; use swap_core::monero::primitives::WatchRequest; @@ -380,7 +380,7 @@ pub struct State1 { impl State1 { pub fn next_message(&self) -> Message2 { Message2 { - psbt: self.tx_lock.clone().into(), + tx_lock_psbt: self.tx_lock.clone().into(), } } @@ -507,7 +507,7 @@ pub struct State2 { } impl State2 { - pub fn next_message(&self) -> Message4 { + pub fn next_message(&self) -> Result { let tx_cancel = TxCancel::new( &self.tx_lock, self.cancel_timelock, @@ -532,11 +532,31 @@ impl State2 { let tx_early_refund_sig = self.b.sign(tx_early_refund.digest()); - Message4 { + let tx_partial_refund = TxPartialRefund::new( + &tx_cancel, + &self.refund_address, + self.A, + self.b.public(), + self.btc_amnesty_amount + .context("missing btc_amnesty_amount")?, + self.tx_partial_refund_fee + .context("missing tx_partial_refund_fee")?, + ) + .context("Couldn't construct TxPartialRefund")?; + let tx_refund_amnesty = TxRefundAmnesty::new( + &tx_partial_refund, + &self.refund_address, + self.tx_refund_amnesty_fee + .context("Missing tx_refund_amnesty_fee")?, + ); + let tx_refund_amnesty_sig = self.b.sign(tx_refund_amnesty.digest()); + + Ok(Message4 { tx_punish_sig, tx_cancel_sig, tx_early_refund_sig, - } + tx_refund_amnesty_sig, + }) } pub async fn lock_btc(self) -> Result<(State3, TxLock)> { diff --git a/swap-machine/src/common/mod.rs b/swap-machine/src/common/mod.rs index 78ffcffbbc..86273769e7 100644 --- a/swap-machine/src/common/mod.rs +++ b/swap-machine/src/common/mod.rs @@ -63,7 +63,7 @@ pub struct Message1 { #[allow(non_snake_case)] #[derive(Clone, Debug, Serialize, Deserialize)] pub struct Message2 { - pub psbt: bitcoin::PartiallySignedTransaction, + pub tx_lock_psbt: bitcoin::PartiallySignedTransaction, } #[allow(non_snake_case)] @@ -71,7 +71,7 @@ pub struct Message2 { pub struct Message3 { pub tx_cancel_sig: bitcoin::Signature, /// The following fields were reworked in [#675](https://github.com/eigenwallet/core/pull/675). - /// Alice _may_ choose to commit to a full refund during the swap setup already, but doesn't + /// Alice _may_ choose to commit to a full refund for bob during the swap setup already, but doesn't /// have to. pub tx_partial_refund_encsig: bitcoin::EncryptedSignature, pub tx_full_refund_encsig: Option, @@ -84,6 +84,7 @@ pub struct Message4 { pub tx_punish_sig: bitcoin::Signature, pub tx_cancel_sig: bitcoin::Signature, pub tx_early_refund_sig: bitcoin::Signature, + pub tx_refund_amnesty_sig: bitcoin::Signature, } #[allow(clippy::large_enum_variant)] diff --git a/swap-machine/src/lib.rs b/swap-machine/src/lib.rs index 8ac33b2b10..f00aad1399 100644 --- a/swap-machine/src/lib.rs +++ b/swap-machine/src/lib.rs @@ -89,7 +89,7 @@ mod tests { let alice_message3 = alice_state2.next_message().unwrap(); let bob_state2 = bob_state1.receive(alice_message3).unwrap(); - let bob_message4 = bob_state2.next_message(); + let bob_message4 = bob_state2.next_message().unwrap(); let alice_state3 = alice_state2.receive(bob_message4).unwrap(); @@ -202,7 +202,7 @@ mod tests { let alice_message3 = alice_state2.next_message().unwrap(); let bob_state2 = bob_state1.receive(alice_message3).unwrap(); - let bob_message4 = bob_state2.next_message(); + let bob_message4 = bob_state2.next_message().unwrap(); let alice_state3 = alice_state2.receive(bob_message4).unwrap(); diff --git a/swap-p2p/src/protocols/swap_setup/bob.rs b/swap-p2p/src/protocols/swap_setup/bob.rs index 66bfbcbd71..d5cc2558d0 100644 --- a/swap-p2p/src/protocols/swap_setup/bob.rs +++ b/swap-p2p/src/protocols/swap_setup/bob.rs @@ -623,9 +623,14 @@ async fn run_swap_setup( "Transitioned into state2 during swap setup", ); - write_cbor_message(&mut substream, state2.next_message()) - .await - .context("Failed to send state2 message")?; + write_cbor_message( + &mut substream, + state2 + .next_message() + .context("Couldn't construct Message4")?, + ) + .await + .context("Failed to send state2 message")?; substream .flush() diff --git a/swap/src/protocol/alice/swap.rs b/swap/src/protocol/alice/swap.rs index 29a981d9d1..9f33771c3f 100644 --- a/swap/src/protocol/alice/swap.rs +++ b/swap/src/protocol/alice/swap.rs @@ -662,9 +662,15 @@ where state3, monero_wallet_restore_blockheight, } => { - let should_grant_amnesty = true; - // TODO: Publish amnesty transaction/send amnesty tx sig / decide against it + // TODO: retry, maybe in background? we don't want this to block us from refunding the Monero though + let tx_refund_amnesty = state3 + .signed_bitcoin_amnesty_transaction() + .context("Couldn't construct Bitcoin refund amnesty transaction")?; + + bitcoin_wallet + .ensure_broadcasted(tx_refund_amnesty, "refund amnesty") + .await?; AliceState::XmrRefundable { monero_wallet_restore_blockheight, From 0780e535705bb4554a2e5de511c6090e268ddafa Mon Sep 17 00:00:00 2001 From: einliterflasche Date: Fri, 12 Dec 2025 13:21:29 +0100 Subject: [PATCH 052/113] alice: extract monero private key from partial_refund also --- swap-core/src/bitcoin/partial_refund.rs | 19 ++++++++------- swap-machine/src/alice/mod.rs | 32 +++++++++++++++++++++---- swap/src/asb/recovery/refund.rs | 2 +- 3 files changed, 39 insertions(+), 14 deletions(-) diff --git a/swap-core/src/bitcoin/partial_refund.rs b/swap-core/src/bitcoin/partial_refund.rs index 50d93e9554..954a4cdf22 100644 --- a/swap-core/src/bitcoin/partial_refund.rs +++ b/swap-core/src/bitcoin/partial_refund.rs @@ -2,13 +2,13 @@ use crate::bitcoin; use crate::bitcoin::{ - build_shared_output_descriptor, verify_sig, Address, Amount, EmptyWitnessStack, NoInputs, - NotThreeWitnesses, PublicKey, TooManyInputs, Transaction, TxCancel, + Address, Amount, EmptyWitnessStack, NoInputs, NotThreeWitnesses, PublicKey, TooManyInputs, + Transaction, TxCancel, build_shared_output_descriptor, verify_sig, }; use ::bitcoin::sighash::SighashCache; -use ::bitcoin::{secp256k1, ScriptBuf, Weight}; -use ::bitcoin::{sighash::SegwitV0Sighash as Sighash, EcdsaSighashType, Txid}; -use anyhow::{bail, Context, Result}; +use ::bitcoin::{EcdsaSighashType, Txid, sighash::SegwitV0Sighash as Sighash}; +use ::bitcoin::{ScriptBuf, Weight, secp256k1}; +use anyhow::{Context, Result, bail}; use bdk_wallet::miniscript::Descriptor; use bitcoin_wallet::primitives::Watchable; use curve25519_dalek::scalar::Scalar; @@ -88,7 +88,10 @@ impl TxPartialRefund { refund_address: &Address, spending_fee: Amount, ) -> Transaction { - use ::bitcoin::{transaction::Version, locktime::absolute::LockTime as PackedLockTime, Sequence, TxIn, TxOut}; + use ::bitcoin::{ + Sequence, TxIn, TxOut, locktime::absolute::LockTime as PackedLockTime, + transaction::Version, + }; let tx_in = TxIn { previous_output: self.amnesty_outpoint(), @@ -129,7 +132,7 @@ impl TxPartialRefund { let sig_a = secp256k1::ecdsa::Signature::from_compact(&sig_a.to_bytes())?; let sig_b = secp256k1::ecdsa::Signature::from_compact(&sig_b.to_bytes())?; - + // The order in which these are inserted doesn't matter satisfier.insert( A, @@ -165,7 +168,7 @@ impl TxPartialRefund { ) -> Result { let tx_refund_sig = self .extract_signature_by_key(signed_refund_tx, a.public()) - .context("Failed to extract signature from Bitcoin refund tx")?; + .context("Failed to extract signature from Bitcoin partial refund tx")?; let tx_refund_encsig = a.encsign(S_b_bitcoin, self.digest()); let s_b = bitcoin::recover(S_b_bitcoin, tx_refund_sig, tx_refund_encsig) diff --git a/swap-machine/src/alice/mod.rs b/swap-machine/src/alice/mod.rs index 19e2cef4c8..71dddde03c 100644 --- a/swap-machine/src/alice/mod.rs +++ b/swap-machine/src/alice/mod.rs @@ -656,7 +656,7 @@ impl State3 { ) } - pub fn extract_monero_private_key( + pub fn extract_monero_private_key_from_refund( &self, signed_refund_tx: Arc, ) -> Result { @@ -670,6 +670,20 @@ impl State3 { )) } + pub fn extract_monero_private_key_from_partial_refund( + &self, + signed_partial_refund_tx: Arc, + ) -> Result { + Ok(monero::PrivateKey::from_scalar( + self.tx_partial_refund()?.extract_monero_private_key( + signed_partial_refund_tx, + self.s_a, + self.a.clone(), + self.S_b_bitcoin, + )?, + )) + } + pub async fn check_for_tx_cancel( &self, bitcoin_wallet: &dyn bitcoin_wallet::BitcoinWallet, @@ -788,13 +802,21 @@ impl State3 { let refund_tx = bitcoin_wallet .get_raw_transaction(self.tx_refund().txid()) .await?; + let partial_refund_tx = bitcoin_wallet + .get_raw_transaction(self.tx_partial_refund()?.txid()) + .await?; - match refund_tx { - Some(refund_tx) => { - let spend_key = self.extract_monero_private_key(refund_tx)?; + match (refund_tx, partial_refund_tx) { + (Some(refund_tx), _) => { + let spend_key = self.extract_monero_private_key_from_refund(refund_tx)?; + Ok(Some(spend_key)) + } + (_, Some(partial_refund_tx)) => { + let spend_key = + self.extract_monero_private_key_from_partial_refund(partial_refund_tx)?; Ok(Some(spend_key)) } - None => Ok(None), + (None, None) => Ok(None), } } diff --git a/swap/src/asb/recovery/refund.rs b/swap/src/asb/recovery/refund.rs index 79aab3b3ce..c79d6a41ab 100644 --- a/swap/src/asb/recovery/refund.rs +++ b/swap/src/asb/recovery/refund.rs @@ -73,7 +73,7 @@ pub async fn refund( state3.fetch_tx_refund(bitcoin_wallet.as_ref()).await? { tracing::debug!(%swap_id, "Bitcoin refund transaction found, extracting key to refund Monero"); - state3.extract_monero_private_key(published_refund_tx)? + state3.extract_monero_private_key_from_refund(published_refund_tx)? } else { let bob_peer_id = db.get_peer_id(swap_id).await?; bail!(Error::RefundTransactionNotPublishedYet(bob_peer_id),); From 87b3e9624b21a8ddf891079d5b0d9ac8bef96f45 Mon Sep 17 00:00:00 2001 From: einliterflasche Date: Fri, 12 Dec 2025 13:57:13 +0100 Subject: [PATCH 053/113] switch all uses of BitcoinWallet::broadcast to BitcoinWallet::ensure_broadcasted --- bitcoin-wallet/src/lib.rs | 6 ------ bitcoin-wallet/src/wallet.rs | 16 ---------------- swap-machine/src/alice/mod.rs | 8 ++++++-- swap-machine/src/bob/mod.rs | 4 +++- swap/src/asb/recovery/redeem.rs | 4 +++- swap/src/protocol/alice/swap.rs | 4 ++-- swap/src/protocol/bob/swap.rs | 2 +- 7 files changed, 15 insertions(+), 29 deletions(-) diff --git a/bitcoin-wallet/src/lib.rs b/bitcoin-wallet/src/lib.rs index 4cbc937a81..69959ae161 100644 --- a/bitcoin-wallet/src/lib.rs +++ b/bitcoin-wallet/src/lib.rs @@ -41,12 +41,6 @@ pub trait BitcoinWallet: Send + Sync { async fn sign_and_finalize(&self, psbt: bitcoin::psbt::Psbt) -> Result; - async fn broadcast( - &self, - transaction: bitcoin::Transaction, - kind: &str, - ) -> Result<(Txid, Subscription)>; - async fn ensure_broadcasted( &self, transaction: bitcoin::Transaction, diff --git a/bitcoin-wallet/src/wallet.rs b/bitcoin-wallet/src/wallet.rs index 5808ffd266..43b675cc82 100644 --- a/bitcoin-wallet/src/wallet.rs +++ b/bitcoin-wallet/src/wallet.rs @@ -2141,14 +2141,6 @@ impl BitcoinWallet for Wallet { Wallet::sign_and_finalize(self, psbt).await } - async fn broadcast( - &self, - transaction: bitcoin::Transaction, - kind: &str, - ) -> Result<(Txid, Subscription)> { - Wallet::broadcast(self, transaction, kind).await - } - async fn ensure_broadcasted( &self, tx: bitcoin::Transaction, @@ -2967,14 +2959,6 @@ impl BitcoinWallet for Wallet { unimplemented!("stub method called erroneously") } - async fn broadcast( - &self, - transaction: bitcoin::Transaction, - kind: &str, - ) -> Result<(Txid, Subscription)> { - unimplemented!("stub method called erroneously") - } - async fn sync(&self) -> Result<()> { unimplemented!("stub method called erroneously") } diff --git a/swap-machine/src/alice/mod.rs b/swap-machine/src/alice/mod.rs index 71dddde03c..f28e27231e 100644 --- a/swap-machine/src/alice/mod.rs +++ b/swap-machine/src/alice/mod.rs @@ -715,7 +715,9 @@ impl State3 { bitcoin_wallet: &dyn bitcoin_wallet::BitcoinWallet, ) -> Result { let transaction = self.signed_cancel_transaction()?; - let (tx_id, _) = bitcoin_wallet.broadcast(transaction, "cancel").await?; + let (tx_id, _) = bitcoin_wallet + .ensure_broadcasted(transaction, "cancel") + .await?; Ok(tx_id) } @@ -743,7 +745,9 @@ impl State3 { ) -> Result { let signed_tx_punish = self.signed_punish_transaction()?; - let (txid, subscription) = bitcoin_wallet.broadcast(signed_tx_punish, "punish").await?; + let (txid, subscription) = bitcoin_wallet + .ensure_broadcasted(signed_tx_punish, "punish") + .await?; subscription.wait_until_final().await?; Ok(txid) diff --git a/swap-machine/src/bob/mod.rs b/swap-machine/src/bob/mod.rs index 627509fb66..f48147afcf 100644 --- a/swap-machine/src/bob/mod.rs +++ b/swap-machine/src/bob/mod.rs @@ -1023,7 +1023,9 @@ impl State6 { .complete_as_bob(self.A, self.b.clone(), self.tx_cancel_sig_a.clone()) .context("Failed to complete Bitcoin cancel transaction")?; - let (tx_id, subscription) = bitcoin_wallet.broadcast(transaction, "cancel").await?; + let (tx_id, subscription) = bitcoin_wallet + .ensure_broadcasted(transaction, "cancel") + .await?; Ok((tx_id, subscription)) } diff --git a/swap/src/asb/recovery/redeem.rs b/swap/src/asb/recovery/redeem.rs index 9024450a2a..c5bed032c9 100644 --- a/swap/src/asb/recovery/redeem.rs +++ b/swap/src/asb/recovery/redeem.rs @@ -40,7 +40,9 @@ pub async fn redeem( tracing::info!(%swap_id, "Trying to redeem swap"); let redeem_tx = state3.signed_redeem_transaction(*encrypted_signature)?; - let (txid, subscription) = bitcoin_wallet.broadcast(redeem_tx, "redeem").await?; + let (txid, subscription) = bitcoin_wallet + .ensure_broadcasted(redeem_tx, "redeem") + .await?; subscription.wait_until_seen().await?; diff --git a/swap/src/protocol/alice/swap.rs b/swap/src/protocol/alice/swap.rs index 9f33771c3f..16483a720e 100644 --- a/swap/src/protocol/alice/swap.rs +++ b/swap/src/protocol/alice/swap.rs @@ -270,7 +270,7 @@ where // Retry repeatedly to broadcast tx_early_refund result = async { backoff::future::retry_notify(backoff, || async { - bitcoin_wallet.broadcast(tx_early_refund.clone(), "early_refund").await.map_err(backoff::Error::transient) + bitcoin_wallet.ensure_broadcasted(tx_early_refund.clone(), "early_refund").await.map_err(backoff::Error::transient) }, |e, wait_time: Duration| { tracing::warn!( %tx_early_refund_txid, @@ -475,7 +475,7 @@ where } bitcoin_wallet - .broadcast(tx_redeem.clone(), "redeem") + .ensure_broadcasted(tx_redeem.clone(), "redeem") .await .map(Some) .map_err(backoff::Error::transient) diff --git a/swap/src/protocol/bob/swap.rs b/swap/src/protocol/bob/swap.rs index 9e320291f9..3414b46af4 100644 --- a/swap/src/protocol/bob/swap.rs +++ b/swap/src/protocol/bob/swap.rs @@ -255,7 +255,7 @@ async fn next_state( tracing::info!(txid = %state3.tx_lock_id(), "Bitcoin lock transaction already published, skipping publish"); } else { // Publish the signed Bitcoin lock transaction - let (..) = bitcoin_wallet.broadcast(btc_lock_tx_signed, "lock").await?; + let (..) = bitcoin_wallet.ensure_broadcasted(btc_lock_tx_signed, "lock").await?; } BobState::BtcLocked { From db83f79f1c59a49c2512022f5c2c72b37e92a174 Mon Sep 17 00:00:00 2001 From: einliterflasche Date: Tue, 16 Dec 2025 14:06:28 +0100 Subject: [PATCH 054/113] add remaining_refund_timelock --- swap-core/src/bitcoin.rs | 24 ++++++------- swap-core/src/bitcoin/partial_refund.rs | 4 ++- swap-core/src/bitcoin/refund_amnesty.rs | 10 ++++-- swap-core/src/bitcoin/timelocks.rs | 46 ++++++++++++++++++++++++ swap-env/src/env.rs | 4 +++ swap-machine/src/alice/mod.rs | 17 +++++++-- swap-machine/src/bob/mod.rs | 25 +++++++++++-- swap-p2p/src/protocols/swap_setup/bob.rs | 1 + 8 files changed, 112 insertions(+), 19 deletions(-) diff --git a/swap-core/src/bitcoin.rs b/swap-core/src/bitcoin.rs index 7606c752c9..08b2d5170b 100644 --- a/swap-core/src/bitcoin.rs +++ b/swap-core/src/bitcoin.rs @@ -2,42 +2,42 @@ mod cancel; mod early_refund; +mod full_refund; mod lock; +mod partial_refund; mod punish; mod redeem; -mod full_refund; -mod partial_refund; mod refund_amnesty; mod timelocks; pub use crate::bitcoin::cancel::TxCancel; pub use crate::bitcoin::early_refund::TxEarlyRefund; -pub use crate::bitcoin::partial_refund::TxPartialRefund; +pub use crate::bitcoin::full_refund::TxFullRefund; pub use crate::bitcoin::lock::TxLock; +pub use crate::bitcoin::partial_refund::TxPartialRefund; pub use crate::bitcoin::punish::TxPunish; pub use crate::bitcoin::redeem::TxRedeem; -pub use crate::bitcoin::full_refund::TxFullRefund; pub use crate::bitcoin::refund_amnesty::TxRefundAmnesty; pub use crate::bitcoin::timelocks::{BlockHeight, ExpiredTimelocks}; -pub use crate::bitcoin::timelocks::{CancelTimelock, PunishTimelock}; +pub use crate::bitcoin::timelocks::{CancelTimelock, PunishTimelock, RemainingRefundTimelock}; pub use ::bitcoin::amount::Amount; pub use ::bitcoin::psbt::Psbt as PartiallySignedTransaction; pub use ::bitcoin::{Address, AddressType, Network, Transaction, Txid}; +pub use ecdsa_fun::Signature; pub use ecdsa_fun::adaptor::EncryptedSignature; pub use ecdsa_fun::fun::Scalar; -pub use ecdsa_fun::Signature; use ::bitcoin::hashes::Hash; use ::bitcoin::secp256k1::ecdsa; use ::bitcoin::sighash::SegwitV0Sighash as Sighash; -use anyhow::{bail, Context, Result}; +use anyhow::{Context, Result, bail}; use bdk_wallet::miniscript::descriptor::Wsh; use bdk_wallet::miniscript::{Descriptor, Segwitv0}; use bitcoin_wallet::primitives::ScriptStatus; +use ecdsa_fun::ECDSA; use ecdsa_fun::adaptor::{Adaptor, HashTranscript}; use ecdsa_fun::fun::Point; use ecdsa_fun::nonce::Deterministic; -use ecdsa_fun::ECDSA; use rand::{CryptoRng, RngCore}; use serde::{Deserialize, Serialize}; use sha2::Sha256; @@ -561,8 +561,8 @@ mod tests { /// subscriptions to the transaction are on index `0` when broadcasting the /// transaction. #[tokio::test] - async fn given_amounts_with_change_outputs_when_signing_tx_then_output_index_0_is_ensured_for_script( - ) { + async fn given_amounts_with_change_outputs_when_signing_tx_then_output_index_0_is_ensured_for_script() + { // This value is somewhat arbitrary but the indexation problem usually occurred // on the first or second value (i.e. 547, 548) We keep the test // iterations relatively low because these tests are expensive. @@ -704,9 +704,9 @@ TRACE swap::bitcoin::wallet: Bitcoin transaction status changed txid=00000000000 mod cached_fee_estimator_tests { use super::*; - use std::sync::atomic::{AtomicU32, Ordering}; use std::sync::Arc; - use tokio::time::{sleep, Duration}; + use std::sync::atomic::{AtomicU32, Ordering}; + use tokio::time::{Duration, sleep}; /// Mock fee estimator that tracks how many times methods are called #[derive(Clone)] diff --git a/swap-core/src/bitcoin/partial_refund.rs b/swap-core/src/bitcoin/partial_refund.rs index 954a4cdf22..67c5d6f3c2 100644 --- a/swap-core/src/bitcoin/partial_refund.rs +++ b/swap-core/src/bitcoin/partial_refund.rs @@ -17,6 +17,7 @@ use std::collections::HashMap; use std::sync::Arc; use super::extract_ecdsa_sig; +use super::timelocks::RemainingRefundTimelock; #[derive(Debug, Clone)] pub struct TxPartialRefund { @@ -87,6 +88,7 @@ impl TxPartialRefund { &self, refund_address: &Address, spending_fee: Amount, + remaining_refund_timelock: RemainingRefundTimelock, ) -> Transaction { use ::bitcoin::{ Sequence, TxIn, TxOut, locktime::absolute::LockTime as PackedLockTime, @@ -96,7 +98,7 @@ impl TxPartialRefund { let tx_in = TxIn { previous_output: self.amnesty_outpoint(), script_sig: Default::default(), - sequence: Sequence(0xFFFF_FFFF), + sequence: Sequence(remaining_refund_timelock.0), witness: Default::default(), }; diff --git a/swap-core/src/bitcoin/refund_amnesty.rs b/swap-core/src/bitcoin/refund_amnesty.rs index 0acb6c290e..6fb130476e 100644 --- a/swap-core/src/bitcoin/refund_amnesty.rs +++ b/swap-core/src/bitcoin/refund_amnesty.rs @@ -9,6 +9,8 @@ use bitcoin_wallet::primitives::Watchable; use ecdsa_fun::Signature; use std::collections::HashMap; +use super::timelocks::RemainingRefundTimelock; + #[derive(Debug, Clone)] pub struct TxRefundAmnesty { inner: Transaction, @@ -22,9 +24,13 @@ impl TxRefundAmnesty { tx_refund: &TxPartialRefund, refund_address: &Address, spending_fee: Amount, + remaining_refund_timelock: RemainingRefundTimelock, ) -> Self { - let tx_refund_amnesty = - tx_refund.build_amnesty_spend_transaction(refund_address, spending_fee); + let tx_refund_amnesty = tx_refund.build_amnesty_spend_transaction( + refund_address, + spending_fee, + remaining_refund_timelock, + ); let digest = SighashCache::new(&tx_refund_amnesty) .p2wsh_signature_hash( diff --git a/swap-core/src/bitcoin/timelocks.rs b/swap-core/src/bitcoin/timelocks.rs index fc7b3d870d..204e565573 100644 --- a/swap-core/src/bitcoin/timelocks.rs +++ b/swap-core/src/bitcoin/timelocks.rs @@ -106,12 +106,58 @@ impl PartialEq for u32 { } } +/// How long a taker has to wait to refund the remaining Bitcoin after publishing +/// TxPartialRefund. +#[derive(Debug, Copy, Clone, Serialize, Deserialize, Eq, PartialEq)] +#[serde(transparent)] +#[typeshare] +pub struct RemainingRefundTimelock(pub u32); + +impl From for u32 { + fn from(remainin_refund_timelock: RemainingRefundTimelock) -> Self { + remainin_refund_timelock.0 + } +} + +impl From for RemainingRefundTimelock { + fn from(number_of_blocks: u32) -> Self { + Self(number_of_blocks) + } +} + +impl RemainingRefundTimelock { + pub const fn new(number_of_blocks: u32) -> Self { + Self(number_of_blocks) + } +} + +impl Add for BlockHeight { + type Output = BlockHeight; + + fn add(self, rhs: RemainingRefundTimelock) -> Self::Output { + self + rhs.0 + } +} + +impl PartialOrd for u32 { + fn partial_cmp(&self, other: &RemainingRefundTimelock) -> Option { + self.partial_cmp(&other.0) + } +} + +impl PartialEq for u32 { + fn eq(&self, other: &RemainingRefundTimelock) -> bool { + self.eq(&other.0) + } +} + #[typeshare] #[derive(Serialize, Deserialize, Debug, Clone, Copy, PartialEq, Eq)] #[serde(tag = "type", content = "content")] pub enum ExpiredTimelocks { None { blocks_left: u32 }, Cancel { blocks_left: u32 }, + RemainingRefund { block_left: u32 }, Punish, } diff --git a/swap-env/src/env.rs b/swap-env/src/env.rs index 61e0bc9d7c..9014d93c1c 100644 --- a/swap-env/src/env.rs +++ b/swap-env/src/env.rs @@ -15,6 +15,7 @@ pub struct Config { pub bitcoin_avg_block_time: Duration, pub bitcoin_cancel_timelock: u32, pub bitcoin_punish_timelock: u32, + pub bitcoin_remaining_refund_timelock: u32, pub bitcoin_network: bitcoin::Network, pub monero_avg_block_time: Duration, pub monero_finality_confirmations: u64, @@ -61,6 +62,7 @@ impl GetConfig for Mainnet { bitcoin_avg_block_time: 10.std_minutes(), bitcoin_cancel_timelock: 72, bitcoin_punish_timelock: 144, + bitcoin_remaining_refund_timelock: 2, bitcoin_network: bitcoin::Network::Bitcoin, monero_avg_block_time: 2.std_minutes(), // If Alice cannot lock her Monero within this timeout, @@ -83,6 +85,7 @@ impl GetConfig for Testnet { bitcoin_avg_block_time: 10.std_minutes(), bitcoin_cancel_timelock: 12 * 3, bitcoin_punish_timelock: 24 * 3, + bitcoin_remaining_refund_timelock: 2, bitcoin_network: bitcoin::Network::Testnet, monero_avg_block_time: 2.std_minutes(), monero_lock_retry_timeout: 10.std_minutes(), @@ -103,6 +106,7 @@ impl GetConfig for Regtest { bitcoin_avg_block_time: 5.std_seconds(), bitcoin_cancel_timelock: 100, bitcoin_punish_timelock: 50, + bitcoin_remaining_refund_timelock: 5, bitcoin_network: bitcoin::Network::Regtest, monero_avg_block_time: 1.std_seconds(), monero_lock_retry_timeout: 1.std_minutes(), diff --git a/swap-machine/src/alice/mod.rs b/swap-machine/src/alice/mod.rs index f28e27231e..8bbd2fc00c 100644 --- a/swap-machine/src/alice/mod.rs +++ b/swap-machine/src/alice/mod.rs @@ -8,8 +8,9 @@ use sigma_fun::ext::dl_secp256k1_ed25519_eq::CrossCurveDLEQProof; use std::fmt::{self, Debug}; use std::sync::Arc; use swap_core::bitcoin::{ - CancelTimelock, ExpiredTimelocks, PunishTimelock, Transaction, TxCancel, TxEarlyRefund, - TxFullRefund, TxPartialRefund, TxPunish, TxRedeem, TxRefundAmnesty, Txid, current_epoch, + CancelTimelock, ExpiredTimelocks, PunishTimelock, RemainingRefundTimelock, Transaction, + TxCancel, TxEarlyRefund, TxFullRefund, TxPartialRefund, TxPunish, TxRedeem, TxRefundAmnesty, + Txid, current_epoch, }; use swap_core::monero; use swap_core::monero::ScalarExt; @@ -169,6 +170,7 @@ pub struct State0 { btc_amnesty_amount: Option, cancel_timelock: CancelTimelock, punish_timelock: PunishTimelock, + remaining_refund_timelock: Option, redeem_address: bitcoin::Address, punish_address: bitcoin::Address, tx_redeem_fee: bitcoin::Amount, @@ -213,6 +215,7 @@ impl State0 { xmr, cancel_timelock: env_config.bitcoin_cancel_timelock.into(), punish_timelock: env_config.bitcoin_punish_timelock.into(), + remaining_refund_timelock: Some(env_config.bitcoin_remaining_refund_timelock.into()), tx_redeem_fee, tx_punish_fee, } @@ -254,6 +257,7 @@ impl State0 { btc_amnesty_amount: self.btc_amnesty_amount, cancel_timelock: self.cancel_timelock, punish_timelock: self.punish_timelock, + remaining_refund_timelock: self.remaining_refund_timelock, refund_address: msg.refund_address, redeem_address: self.redeem_address, punish_address: self.punish_address, @@ -286,6 +290,7 @@ pub struct State1 { btc_amnesty_amount: Option, cancel_timelock: CancelTimelock, punish_timelock: PunishTimelock, + remaining_refund_timelock: Option, refund_address: bitcoin::Address, redeem_address: bitcoin::Address, punish_address: bitcoin::Address, @@ -336,6 +341,7 @@ impl State1 { btc_amnesty_amount: self.btc_amnesty_amount, cancel_timelock: self.cancel_timelock, punish_timelock: self.punish_timelock, + remaining_refund_timelock: self.remaining_refund_timelock, refund_address: self.refund_address, redeem_address: self.redeem_address, punish_address: self.punish_address, @@ -364,6 +370,7 @@ pub struct State2 { btc_amnesty_amount: Option, cancel_timelock: CancelTimelock, punish_timelock: PunishTimelock, + remaining_refund_timelock: Option, refund_address: bitcoin::Address, redeem_address: bitcoin::Address, punish_address: bitcoin::Address, @@ -474,6 +481,8 @@ impl State2 { &self.refund_address, self.tx_refund_amnesty_fee .context("missing tx_refund_amnesty_fee")?, + self.remaining_refund_timelock + .context("missing remaining_refund_timelock")?, ); // Check if the provided signature by Bob is valid for the transaction @@ -496,6 +505,7 @@ impl State2 { btc_amnesty_amount: self.btc_amnesty_amount, cancel_timelock: self.cancel_timelock, punish_timelock: self.punish_timelock, + remaining_refund_timelock: self.remaining_refund_timelock, refund_address: self.refund_address, redeem_address: self.redeem_address, punish_address: self.punish_address, @@ -528,6 +538,7 @@ pub struct State3 { pub btc_amnesty_amount: Option, pub cancel_timelock: CancelTimelock, pub punish_timelock: PunishTimelock, + remaining_refund_timelock: Option, #[serde(with = "swap_serde::bitcoin::address_serde")] refund_address: bitcoin::Address, #[serde(with = "swap_serde::bitcoin::address_serde")] @@ -728,6 +739,8 @@ impl State3 { &self.refund_address, self.tx_refund_amnesty_fee .context("Missing tx_refund_amnesty_fee")?, + self.remaining_refund_timelock + .context("Missing remaining_refund_timelock")?, ); tx_amnesty.complete_as_alice( diff --git a/swap-machine/src/bob/mod.rs b/swap-machine/src/bob/mod.rs index f48147afcf..4ab0a4e6cb 100644 --- a/swap-machine/src/bob/mod.rs +++ b/swap-machine/src/bob/mod.rs @@ -14,8 +14,8 @@ use sigma_fun::ext::dl_secp256k1_ed25519_eq::CrossCurveDLEQProof; use std::fmt; use std::sync::Arc; use swap_core::bitcoin::{ - self, CancelTimelock, ExpiredTimelocks, PunishTimelock, Transaction, TxCancel, TxLock, - TxPartialRefund, TxRefundAmnesty, Txid, current_epoch, + self, CancelTimelock, ExpiredTimelocks, PunishTimelock, RemainingRefundTimelock, Transaction, + TxCancel, TxLock, TxPartialRefund, TxRefundAmnesty, Txid, current_epoch, }; use swap_core::monero::ScalarExt; use swap_core::monero::primitives::WatchRequest; @@ -217,6 +217,7 @@ pub struct State0 { xmr: monero::Amount, cancel_timelock: CancelTimelock, punish_timelock: PunishTimelock, + remaining_refund_timelock: Option, refund_address: bitcoin::Address, min_monero_confirmations: u64, tx_partial_refund_fee: Option, @@ -235,6 +236,7 @@ impl State0 { xmr: monero::Amount, cancel_timelock: CancelTimelock, punish_timelock: PunishTimelock, + remaining_refund_timelock: RemainingRefundTimelock, refund_address: bitcoin::Address, min_monero_confirmations: u64, tx_partial_refund_fee: bitcoin::Amount, @@ -264,6 +266,7 @@ impl State0 { dleq_proof_s_b, cancel_timelock, punish_timelock, + remaining_refund_timelock: Some(remaining_refund_timelock), refund_address, min_monero_confirmations, tx_partial_refund_fee: Some(tx_partial_refund_fee), @@ -336,6 +339,7 @@ impl State0 { btc_amnesty_amount: Some(msg.amnesty_amount), cancel_timelock: self.cancel_timelock, punish_timelock: self.punish_timelock, + remaining_refund_timelock: self.remaining_refund_timelock, refund_address: self.refund_address, redeem_address: msg.redeem_address, punish_address: msg.punish_address, @@ -364,6 +368,7 @@ pub struct State1 { btc_amnesty_amount: Option, cancel_timelock: CancelTimelock, punish_timelock: PunishTimelock, + remaining_refund_timelock: Option, refund_address: bitcoin::Address, redeem_address: bitcoin::Address, punish_address: bitcoin::Address, @@ -431,6 +436,8 @@ impl State1 { &self.refund_address, self.tx_refund_amnesty_fee .context("tx_refund_amnesty_fee missing but required to setup swap")?, + self.remaining_refund_timelock + .context("remaining_refund_timelock missing but required to setup swap")?, ); bitcoin::verify_sig(&self.A, &tx_refund_amnesty.digest(), tx_refund_amnesty_sig)?; } @@ -450,6 +457,7 @@ impl State1 { btc_amnesty_amount: self.btc_amnesty_amount, cancel_timelock: self.cancel_timelock, punish_timelock: self.punish_timelock, + remaining_refund_timelock: self.remaining_refund_timelock, refund_address: self.refund_address, redeem_address: self.redeem_address, punish_address: self.punish_address, @@ -481,6 +489,7 @@ pub struct State2 { pub btc_amnesty_amount: Option, pub cancel_timelock: CancelTimelock, pub punish_timelock: PunishTimelock, + remaining_refund_timelock: Option, #[serde(with = "address_serde")] pub refund_address: bitcoin::Address, #[serde(with = "address_serde")] @@ -548,6 +557,8 @@ impl State2 { &self.refund_address, self.tx_refund_amnesty_fee .context("Missing tx_refund_amnesty_fee")?, + self.remaining_refund_timelock + .context("missing remaining_refund_timelock")?, ); let tx_refund_amnesty_sig = self.b.sign(tx_refund_amnesty.digest()); @@ -572,6 +583,7 @@ impl State2 { btc_amnesty_amount: self.btc_amnesty_amount, cancel_timelock: self.cancel_timelock, punish_timelock: self.punish_timelock, + remaining_refund_timelock: self.remaining_refund_timelock, refund_address: self.refund_address, redeem_address: self.redeem_address, tx_lock: self.tx_lock.clone(), @@ -603,6 +615,7 @@ pub struct State3 { btc_amnesty_amount: Option, pub cancel_timelock: CancelTimelock, punish_timelock: PunishTimelock, + remaining_refund_timelock: Option, #[serde(with = "address_serde")] refund_address: bitcoin::Address, #[serde(with = "address_serde")] @@ -661,6 +674,7 @@ impl State3 { btc_amnesty_amount: self.btc_amnesty_amount, cancel_timelock: self.cancel_timelock, punish_timelock: self.punish_timelock, + remaining_refund_timelock: self.remaining_refund_timelock, refund_address: self.refund_address, redeem_address: self.redeem_address, tx_lock: self.tx_lock, @@ -686,6 +700,7 @@ impl State3 { monero_wallet_restore_blockheight, cancel_timelock: self.cancel_timelock, punish_timelock: self.punish_timelock, + remaining_refund_timelock: self.remaining_refund_timelock, refund_address: self.refund_address.clone(), tx_lock: self.tx_lock.clone(), tx_cancel_sig_a: self.tx_cancel_sig_a.clone(), @@ -757,6 +772,7 @@ pub struct State4 { btc_amnesty_amount: Option, pub cancel_timelock: CancelTimelock, punish_timelock: PunishTimelock, + remaining_refund_timelock: Option, #[serde(with = "address_serde")] refund_address: bitcoin::Address, #[serde(with = "address_serde")] @@ -872,6 +888,7 @@ impl State4 { monero_wallet_restore_blockheight: self.monero_wallet_restore_blockheight, cancel_timelock: self.cancel_timelock, punish_timelock: self.punish_timelock, + remaining_refund_timelock: self.remaining_refund_timelock, refund_address: self.refund_address, tx_lock: self.tx_lock, tx_cancel_sig_a: self.tx_cancel_sig_a, @@ -948,6 +965,7 @@ pub struct State6 { pub monero_wallet_restore_blockheight: BlockHeight, pub cancel_timelock: CancelTimelock, punish_timelock: PunishTimelock, + remaining_refund_timelock: Option, #[serde(with = "address_serde")] refund_address: bitcoin::Address, pub tx_lock: bitcoin::TxLock, @@ -1151,6 +1169,9 @@ impl State6 { self.tx_refund_amnesty_fee.context( "Can't construct TxRefundAmnesty because tx_refund_amnesty_fee is missing", )?, + self.remaining_refund_timelock.context( + "Can't construct TxRefundAmnesty because remaining_refund_timelock is missing", + )?, )) } diff --git a/swap-p2p/src/protocols/swap_setup/bob.rs b/swap-p2p/src/protocols/swap_setup/bob.rs index d5cc2558d0..e35b27fe3a 100644 --- a/swap-p2p/src/protocols/swap_setup/bob.rs +++ b/swap-p2p/src/protocols/swap_setup/bob.rs @@ -573,6 +573,7 @@ async fn run_swap_setup( xmr, env_config.bitcoin_cancel_timelock.into(), env_config.bitcoin_punish_timelock.into(), + env_config.bitcoin_remaining_refund_timelock.into(), new_swap_request.bitcoin_refund_address.clone(), env_config.monero_finality_confirmations, new_swap_request.tx_partial_refund_fee, From 0d2cfe6db238dc5d5626f6937dfd9b191879647e Mon Sep 17 00:00:00 2001 From: einliterflasche Date: Wed, 17 Dec 2025 11:38:53 +0100 Subject: [PATCH 055/113] add RemainingRefundTimelock to ExpiredTimelocks --- swap-core/src/bitcoin.rs | 19 +++++++++++- swap-core/src/bitcoin/timelocks.rs | 48 +++++++++++++++++++++++++++++- swap-machine/src/alice/mod.rs | 11 +++++++ swap-machine/src/bob/mod.rs | 47 +++++++++++++++++++++++++++++ swap/src/cli/cancel_and_refund.rs | 23 ++++++++++++++ swap/src/cli/watcher.rs | 2 +- swap/src/protocol/bob/swap.rs | 16 ++++++++++ 7 files changed, 163 insertions(+), 3 deletions(-) diff --git a/swap-core/src/bitcoin.rs b/swap-core/src/bitcoin.rs index 08b2d5170b..007f04566d 100644 --- a/swap-core/src/bitcoin.rs +++ b/swap-core/src/bitcoin.rs @@ -20,6 +20,7 @@ pub use crate::bitcoin::redeem::TxRedeem; pub use crate::bitcoin::refund_amnesty::TxRefundAmnesty; pub use crate::bitcoin::timelocks::{BlockHeight, ExpiredTimelocks}; pub use crate::bitcoin::timelocks::{CancelTimelock, PunishTimelock, RemainingRefundTimelock}; +pub use bitcoin_wallet::ScriptStatus; pub use ::bitcoin::amount::Amount; pub use ::bitcoin::psbt::Psbt as PartiallySignedTransaction; pub use ::bitcoin::{Address, AddressType, Network, Transaction, Txid}; @@ -33,7 +34,6 @@ use ::bitcoin::sighash::SegwitV0Sighash as Sighash; use anyhow::{Context, Result, bail}; use bdk_wallet::miniscript::descriptor::Wsh; use bdk_wallet::miniscript::{Descriptor, Segwitv0}; -use bitcoin_wallet::primitives::ScriptStatus; use ecdsa_fun::ECDSA; use ecdsa_fun::adaptor::{Adaptor, HashTranscript}; use ecdsa_fun::fun::Point; @@ -246,13 +246,30 @@ pub fn recover(S: PublicKey, sig: Signature, encsig: EncryptedSignature) -> Resu pub fn current_epoch( cancel_timelock: CancelTimelock, punish_timelock: PunishTimelock, + remaining_refund_timelock: Option, tx_lock_status: ScriptStatus, tx_cancel_status: ScriptStatus, + tx_partial_refund_status: Option, ) -> ExpiredTimelocks { if tx_cancel_status.is_confirmed_with(punish_timelock) { return ExpiredTimelocks::Punish; } + // Check if TxPartialRefund is confirmed and handle remaining refund timelock + // For old swaps, these will be None and we skip the partial refund checks + if let (Some(remaining_refund_timelock), Some(tx_partial_refund_status)) = + (remaining_refund_timelock, tx_partial_refund_status) + { + if tx_partial_refund_status.is_confirmed_with(remaining_refund_timelock) { + return ExpiredTimelocks::RemainingRefund; + } + if tx_partial_refund_status.is_confirmed() { + return ExpiredTimelocks::WaitingForRemainingRefund { + blocks_left: tx_partial_refund_status.blocks_left_until(remaining_refund_timelock), + }; + } + } + if tx_lock_status.is_confirmed_with(cancel_timelock) { return ExpiredTimelocks::Cancel { blocks_left: tx_cancel_status.blocks_left_until(punish_timelock), diff --git a/swap-core/src/bitcoin/timelocks.rs b/swap-core/src/bitcoin/timelocks.rs index 204e565573..2b8d777916 100644 --- a/swap-core/src/bitcoin/timelocks.rs +++ b/swap-core/src/bitcoin/timelocks.rs @@ -157,7 +157,8 @@ impl PartialEq for u32 { pub enum ExpiredTimelocks { None { blocks_left: u32 }, Cancel { blocks_left: u32 }, - RemainingRefund { block_left: u32 }, + WaitingForRemainingRefund { blocks_left: u32 }, + RemainingRefund, Punish, } @@ -188,8 +189,10 @@ mod tests { let expired_timelock = current_epoch( CancelTimelock::new(5), PunishTimelock::new(5), + None, tx_lock_status, tx_cancel_status, + None, ); assert!(matches!(expired_timelock, ExpiredTimelocks::None { .. })); @@ -203,8 +206,10 @@ mod tests { let expired_timelock = current_epoch( CancelTimelock::new(5), PunishTimelock::new(5), + None, tx_lock_status, tx_cancel_status, + None, ); assert!(matches!(expired_timelock, ExpiredTimelocks::Cancel { .. })); @@ -218,13 +223,54 @@ mod tests { let expired_timelock = current_epoch( CancelTimelock::new(5), PunishTimelock::new(5), + None, tx_lock_status, tx_cancel_status, + None, ); assert_eq!(expired_timelock, ExpiredTimelocks::Punish) } + #[test] + fn partial_refund_confirmed_waiting_for_remaining_refund_timelock() { + let tx_lock_status = ScriptStatus::from_confirmations(10); + let tx_cancel_status = ScriptStatus::from_confirmations(5); + let tx_partial_refund_status = ScriptStatus::from_confirmations(2); + + let expired_timelock = current_epoch( + CancelTimelock::new(5), + PunishTimelock::new(10), + Some(RemainingRefundTimelock::new(5)), + tx_lock_status, + tx_cancel_status, + Some(tx_partial_refund_status), + ); + + assert!(matches!( + expired_timelock, + ExpiredTimelocks::WaitingForRemainingRefund { .. } + )); + } + + #[test] + fn partial_refund_remaining_timelock_expired() { + let tx_lock_status = ScriptStatus::from_confirmations(10); + let tx_cancel_status = ScriptStatus::from_confirmations(5); + let tx_partial_refund_status = ScriptStatus::from_confirmations(5); + + let expired_timelock = current_epoch( + CancelTimelock::new(5), + PunishTimelock::new(10), + Some(RemainingRefundTimelock::new(5)), + tx_lock_status, + tx_cancel_status, + Some(tx_partial_refund_status), + ); + + assert_eq!(expired_timelock, ExpiredTimelocks::RemainingRefund); + } + #[test] fn tx_early_refund_has_correct_weight() { // TxEarlyRefund should have the same weight as other similar transactions diff --git a/swap-machine/src/alice/mod.rs b/swap-machine/src/alice/mod.rs index 8bbd2fc00c..d9142e8ce8 100644 --- a/swap-machine/src/alice/mod.rs +++ b/swap-machine/src/alice/mod.rs @@ -582,12 +582,23 @@ impl State3 { let tx_lock_status = bitcoin_wallet.status_of_script(&self.tx_lock).await?; let tx_cancel_status = bitcoin_wallet.status_of_script(&tx_cancel).await?; + // Only check partial refund status if we have the data to construct it + // (old swaps won't have these fields) + let tx_partial_refund_status = + if let (Some(_), Some(_)) = (self.btc_amnesty_amount, self.tx_partial_refund_fee) { + let tx = self.tx_partial_refund()?; + Some(bitcoin_wallet.status_of_script(&tx).await?) + } else { + None + }; Ok(current_epoch( self.cancel_timelock, self.punish_timelock, + self.remaining_refund_timelock, tx_lock_status, tx_cancel_status, + tx_partial_refund_status, )) } diff --git a/swap-machine/src/bob/mod.rs b/swap-machine/src/bob/mod.rs index 4ab0a4e6cb..6db55cb0b5 100644 --- a/swap-machine/src/bob/mod.rs +++ b/swap-machine/src/bob/mod.rs @@ -733,12 +733,30 @@ impl State3 { let tx_lock_status = bitcoin_wallet.status_of_script(&self.tx_lock).await?; let tx_cancel_status = bitcoin_wallet.status_of_script(&tx_cancel).await?; + let tx_partial_refund_status = + if let (Some(btc_amnesty_amount), Some(tx_partial_refund_fee)) = + (self.btc_amnesty_amount, self.tx_partial_refund_fee) + { + let tx = TxPartialRefund::new( + &tx_cancel, + &self.refund_address, + self.A, + self.b.public(), + btc_amnesty_amount, + tx_partial_refund_fee, + )?; + Some(bitcoin_wallet.status_of_script(&tx).await?) + } else { + None + }; Ok(current_epoch( self.cancel_timelock, self.punish_timelock, + self.remaining_refund_timelock, tx_lock_status, tx_cancel_status, + tx_partial_refund_status, )) } @@ -870,12 +888,30 @@ impl State4 { let tx_lock_status = bitcoin_wallet.status_of_script(&self.tx_lock).await?; let tx_cancel_status = bitcoin_wallet.status_of_script(&tx_cancel).await?; + let tx_partial_refund_status = + if let (Some(btc_amnesty_amount), Some(tx_partial_refund_fee)) = + (self.btc_amnesty_amount, self.tx_partial_refund_fee) + { + let tx = TxPartialRefund::new( + &tx_cancel, + &self.refund_address, + self.A, + self.b.public(), + btc_amnesty_amount, + tx_partial_refund_fee, + )?; + Some(bitcoin_wallet.status_of_script(&tx).await?) + } else { + None + }; Ok(current_epoch( self.cancel_timelock, self.punish_timelock, + self.remaining_refund_timelock, tx_lock_status, tx_cancel_status, + tx_partial_refund_status, )) } @@ -999,12 +1035,23 @@ impl State6 { let tx_lock_status = bitcoin_wallet.status_of_script(&self.tx_lock).await?; let tx_cancel_status = bitcoin_wallet.status_of_script(&tx_cancel).await?; + // Only check partial refund status if we have the data to construct it + // (old swaps won't have these fields) + let tx_partial_refund_status = + if let (Some(_), Some(_)) = (self.btc_amnesty_amount, self.tx_partial_refund_fee) { + let tx = self.construct_tx_partial_refund()?; + Some(bitcoin_wallet.status_of_script(&tx).await?) + } else { + None + }; Ok(current_epoch( self.cancel_timelock, self.punish_timelock, + self.remaining_refund_timelock, tx_lock_status, tx_cancel_status, + tx_partial_refund_status, )) } diff --git a/swap/src/cli/cancel_and_refund.rs b/swap/src/cli/cancel_and_refund.rs index 15f84eaaee..530ea8d282 100644 --- a/swap/src/cli/cancel_and_refund.rs +++ b/swap/src/cli/cancel_and_refund.rs @@ -136,6 +136,17 @@ pub async fn cancel( Ok(ExpiredTimelocks::Cancel { .. }) => { bail!(err.context("Failed to cancel swap even though cancel timelock has expired. This is unexpected.")); } + Ok(ExpiredTimelocks::WaitingForRemainingRefund { blocks_left }) => { + bail!(err.context( + format!( + "Cannot cancel swap because partial refund is already in progress. Waiting {} blocks for amnesty timelock.", + blocks_left + ) + )); + } + Ok(ExpiredTimelocks::RemainingRefund) => { + bail!(err.context("Cannot cancel swap because we are in the partial refund phase. TxRefundAmnesty can be published.")); + } Err(timelock_err) => { bail!(err .context(timelock_err) @@ -243,6 +254,18 @@ pub async fn refund( Ok(ExpiredTimelocks::Cancel { .. }) => { bail!(bitcoin_publication_err.context("Failed to refund swap even though cancel timelock has expired. This is unexpected.")); } + Ok(ExpiredTimelocks::WaitingForRemainingRefund { blocks_left }) => { + bail!( + bitcoin_publication_err.context(format!( + "Cannot refund swap yet. Partial refund was published but waiting {} blocks for amnesty timelock to expire.", + blocks_left + )) + ); + } + Ok(ExpiredTimelocks::RemainingRefund) => { + // TODO: Try to publish TxRefundAmnesty here instead of just reporting the state + bail!(bitcoin_publication_err.context("Amnesty timelock has expired. TxRefundAmnesty can be published.")); + } Err(e) => { bail!(bitcoin_publication_err .context(e) diff --git a/swap/src/cli/watcher.rs b/swap/src/cli/watcher.rs index 299f30b2f4..7dc542f3b4 100644 --- a/swap/src/cli/watcher.rs +++ b/swap/src/cli/watcher.rs @@ -107,7 +107,7 @@ impl Watcher { self.cached_timelocks.insert(swap_id, new_timelock_status); // If the swap has to be refunded, do it in the background - if let Some(ExpiredTimelocks::Cancel { .. }) = new_timelock_status { + if matches!(new_timelock_status, Some(ExpiredTimelocks::Cancel { .. }) | Some(ExpiredTimelocks::RemainingRefund)) { // If the swap is already refunded, we can skip the refund if matches!(state, BobState::BtcRefunded(_)) { continue; diff --git a/swap/src/protocol/bob/swap.rs b/swap/src/protocol/bob/swap.rs index 3414b46af4..dcf1293755 100644 --- a/swap/src/protocol/bob/swap.rs +++ b/swap/src/protocol/bob/swap.rs @@ -822,6 +822,22 @@ async fn next_state( state, }) } + ExpiredTimelocks::WaitingForRemainingRefund { blocks_left } => { + // TxPartialRefund has been published, waiting for remaining_refund_timelock + // This is unusual from BtcCancelled state - means we published partial refund but crashed + // Retry until timelock expires + tracing::debug!("Partial refund published, waiting {} blocks for amnesty timelock", blocks_left); + Err(backoff::Error::transient(anyhow::anyhow!( + "Waiting for remaining refund timelock to expire. Blocks left: {}", + blocks_left + ))) + } + ExpiredTimelocks::RemainingRefund => { + // TxPartialRefund was published and timelock expired - publish TxRefundAmnesty + // Transition to BtcPartiallyRefunded which handles amnesty publication + tracing::info!("Remaining refund timelock expired, can publish amnesty transaction"); + Ok(BobState::BtcPartiallyRefunded(state)) + } } } }, From 9de25bc236b8686647620fd772f3e655ca141427 Mon Sep 17 00:00:00 2001 From: einliterflasche Date: Wed, 17 Dec 2025 12:39:37 +0100 Subject: [PATCH 056/113] fix warnings --- bitcoin-wallet/src/wallet.rs | 4 +--- monero-tests/src/lib.rs | 8 ++++++++ swap-env/src/config.rs | 2 +- swap-env/src/defaults.rs | 1 - swap-orchestrator/src/lib.rs | 6 ++++++ swap-orchestrator/src/main.rs | 2 ++ swap-p2p/src/observe.rs | 2 +- swap-p2p/src/protocols/swap_setup/bob.rs | 2 -- swap/src/asb/event_loop.rs | 4 ++-- swap/src/cli/api/tauri_bindings.rs | 2 +- swap/src/cli/command.rs | 1 - swap/src/common/tracing_util.rs | 2 +- swap/src/protocol/alice/swap.rs | 3 +-- swap/src/protocol/bob/swap.rs | 2 +- 14 files changed, 25 insertions(+), 16 deletions(-) diff --git a/bitcoin-wallet/src/wallet.rs b/bitcoin-wallet/src/wallet.rs index 43b675cc82..72aa6a37ed 100644 --- a/bitcoin-wallet/src/wallet.rs +++ b/bitcoin-wallet/src/wallet.rs @@ -13,8 +13,6 @@ use bdk_wallet::template::{Bip84, DescriptorTemplate}; use bdk_wallet::KeychainKind; use bdk_wallet::WalletPersister; use bdk_wallet::{Balance, PersistedWallet}; -#[allow(deprecated)] -use bitcoin::bip32::ExtendedPrivKey; use bitcoin::bip32::Xpriv; use bitcoin::{psbt::Psbt as PartiallySignedTransaction, Address, Amount, Transaction, Txid}; use bitcoin::{Psbt, ScriptBuf, Weight}; @@ -69,7 +67,7 @@ pub trait BitcoinTauriBackgroundTask: Send + Sync { } pub trait BitcoinWalletSeed { - fn derive_extended_private_key(&self, network: bitcoin::Network) -> Result; + fn derive_extended_private_key(&self, network: bitcoin::Network) -> Result; /// Same as `derive_extended_private_key`, but using the legacy BDK API. /// diff --git a/monero-tests/src/lib.rs b/monero-tests/src/lib.rs index 2235e70956..2807718fee 100644 --- a/monero-tests/src/lib.rs +++ b/monero-tests/src/lib.rs @@ -1 +1,9 @@ // Empty but necessary for cargo +use anyhow as _; +use monero as _; +use monero_harness as _; +use monero_sys as _; +use testcontainers as _; +use tokio as _; +use tracing as _; +use tracing_subscriber as _; diff --git a/swap-env/src/config.rs b/swap-env/src/config.rs index b1696fcd8e..8a26691342 100644 --- a/swap-env/src/config.rs +++ b/swap-env/src/config.rs @@ -8,7 +8,7 @@ use anyhow::{Context, Result, bail}; use config::ConfigError; use libp2p::core::Multiaddr; use rust_decimal::Decimal; -use serde::{Deserialize, Serialize, de}; +use serde::{Deserialize, Serialize}; use std::ffi::OsStr; use std::fs; use std::path::{Path, PathBuf}; diff --git a/swap-env/src/defaults.rs b/swap-env/src/defaults.rs index 5effa77619..28f412efaa 100644 --- a/swap-env/src/defaults.rs +++ b/swap-env/src/defaults.rs @@ -3,7 +3,6 @@ use crate::env::{Mainnet, Testnet}; use anyhow::{Context, Result}; use libp2p::Multiaddr; use rust_decimal::Decimal; -use std::cell::Ref; use std::path::{Path, PathBuf}; use std::str::FromStr; use swap_fs::{system_config_dir, system_data_dir}; diff --git a/swap-orchestrator/src/lib.rs b/swap-orchestrator/src/lib.rs index 8b20132996..719099c42a 100644 --- a/swap-orchestrator/src/lib.rs +++ b/swap-orchestrator/src/lib.rs @@ -1,3 +1,9 @@ pub mod compose; pub mod containers; pub mod images; + +use anyhow as _; +use dialoguer as _; +use swap_env as _; +use toml as _; +use url as _; diff --git a/swap-orchestrator/src/main.rs b/swap-orchestrator/src/main.rs index 8e4f27715c..230e019998 100644 --- a/swap-orchestrator/src/main.rs +++ b/swap-orchestrator/src/main.rs @@ -3,6 +3,8 @@ mod containers; mod images; mod prompt; +use swap_orchestrator as _; + use crate::compose::{ ASB_DATA_DIR, DOCKER_COMPOSE_FILE, IntoSpec, OrchestratorDirectories, OrchestratorImage, OrchestratorImages, OrchestratorInput, OrchestratorNetworks, diff --git a/swap-p2p/src/observe.rs b/swap-p2p/src/observe.rs index f42f55cac7..6d1c48dda7 100644 --- a/swap-p2p/src/observe.rs +++ b/swap-p2p/src/observe.rs @@ -126,7 +126,7 @@ impl NetworkBehaviour for Behaviour { fn poll( &mut self, - cx: &mut std::task::Context<'_>, + _cx: &mut std::task::Context<'_>, ) -> Poll>> { if let Some(event) = self.to_swarm.pop_front() { return Poll::Ready(ToSwarm::GenerateEvent(event)); diff --git a/swap-p2p/src/protocols/swap_setup/bob.rs b/swap-p2p/src/protocols/swap_setup/bob.rs index e35b27fe3a..dba374fd0c 100644 --- a/swap-p2p/src/protocols/swap_setup/bob.rs +++ b/swap-p2p/src/protocols/swap_setup/bob.rs @@ -6,8 +6,6 @@ use crate::protocols::swap_setup::{ use anyhow::{Context, Result}; use bitcoin_wallet::BitcoinWallet; use futures::AsyncWriteExt; -use futures::FutureExt; -use futures::future::{BoxFuture, OptionFuture}; use libp2p::core::upgrade; use libp2p::swarm::behaviour::ConnectionEstablished; use libp2p::swarm::dial_opts::{DialOpts, PeerCondition}; diff --git a/swap/src/asb/event_loop.rs b/swap/src/asb/event_loop.rs index 6e7261cae5..106d6d367d 100644 --- a/swap/src/asb/event_loop.rs +++ b/swap/src/asb/event_loop.rs @@ -6,7 +6,7 @@ use crate::asb::{Behaviour, OutEvent}; use crate::monero; use crate::network::cooperative_xmr_redeem_after_punish::CooperativeXmrRedeemRejectReason; use crate::network::cooperative_xmr_redeem_after_punish::Response::{Fullfilled, Rejected}; -use crate::network::quote::{BidQuote, ReserveProofWithAddress}; +use crate::network::quote::BidQuote; use crate::network::swap_setup::alice::WalletSnapshot; use crate::network::transfer_proof; use crate::protocol::alice::swap::has_already_processed_enc_sig; @@ -30,7 +30,7 @@ use std::io::Write; use std::sync::Arc; use std::time::Duration; use swap_core::bitcoin; -use swap_env::config::{Config, RefundPolicy}; +use swap_env::config::RefundPolicy; use swap_env::env; use swap_feed::LatestRate; use tokio::sync::{mpsc, oneshot}; diff --git a/swap/src/cli/api/tauri_bindings.rs b/swap/src/cli/api/tauri_bindings.rs index fa9e2451d0..e9513e4519 100644 --- a/swap/src/cli/api/tauri_bindings.rs +++ b/swap/src/cli/api/tauri_bindings.rs @@ -8,7 +8,7 @@ use crate::{monero, network::quote::BidQuote}; use anyhow::{anyhow, bail, Context, Result}; use async_trait::async_trait; use bitcoin::Txid; -use libp2p::{Multiaddr, PeerId}; +use libp2p::PeerId; use monero_rpc_pool::pool::PoolStatus; use serde::{Deserialize, Serialize}; use std::collections::HashMap; diff --git a/swap/src/cli/command.rs b/swap/src/cli/command.rs index 39673c55a8..213ef06e38 100644 --- a/swap/src/cli/command.rs +++ b/swap/src/cli/command.rs @@ -3,7 +3,6 @@ use crate::cli::api::request::{ MoneroRecoveryArgs, Request, ResumeSwapArgs, WithdrawBtcArgs, }; use crate::cli::api::Context; -use crate::monero::{self, MoneroAddressPool}; use anyhow::Result; use bitcoin::address::NetworkUnchecked; use bitcoin_wallet::{bitcoin_address, Amount}; diff --git a/swap/src/common/tracing_util.rs b/swap/src/common/tracing_util.rs index 0f8ab69ac0..6231032f07 100644 --- a/swap/src/common/tracing_util.rs +++ b/swap/src/common/tracing_util.rs @@ -1,4 +1,4 @@ -use std::io::{self, IsTerminal}; +use std::io; use std::path::Path; use std::str::FromStr; diff --git a/swap/src/protocol/alice/swap.rs b/swap/src/protocol/alice/swap.rs index 16483a720e..ca5acc6124 100644 --- a/swap/src/protocol/alice/swap.rs +++ b/swap/src/protocol/alice/swap.rs @@ -1,6 +1,5 @@ //! Run an XMR/BTC swap in the role of Alice. //! Alice holds XMR and wishes receive BTC. -use std::any::Any; use std::sync::Arc; use std::time::Duration; @@ -680,7 +679,7 @@ where } } AliceState::XmrRefundable { - monero_wallet_restore_blockheight, + monero_wallet_restore_blockheight: _, transfer_proof, spend_key, state3, diff --git a/swap/src/protocol/bob/swap.rs b/swap/src/protocol/bob/swap.rs index dcf1293755..1371be45fd 100644 --- a/swap/src/protocol/bob/swap.rs +++ b/swap/src/protocol/bob/swap.rs @@ -8,7 +8,7 @@ use crate::network::cooperative_xmr_redeem_after_punish::Response::{Fullfilled, use crate::network::swap_setup::bob::NewSwap; use crate::protocol::bob::*; use crate::protocol::{bob, Database}; -use anyhow::{Context as AnyContext, Result, anyhow, bail}; +use anyhow::{Context as AnyContext, Result, anyhow}; use std::sync::Arc; use std::time::Duration; use swap_core::bitcoin::{ From 8c972914a0465d3dacd8b26b19f0dd98adf2671a Mon Sep 17 00:00:00 2001 From: einliterflasche Date: Wed, 17 Dec 2025 12:55:23 +0100 Subject: [PATCH 057/113] add TxRefundBurn (see comment on #675 for explanation) --- swap-core/src/bitcoin.rs | 2 + swap-core/src/bitcoin/partial_refund.rs | 41 +++++ swap-core/src/bitcoin/refund_burn.rs | 205 ++++++++++++++++++++++++ 3 files changed, 248 insertions(+) create mode 100644 swap-core/src/bitcoin/refund_burn.rs diff --git a/swap-core/src/bitcoin.rs b/swap-core/src/bitcoin.rs index 007f04566d..8c95e72f37 100644 --- a/swap-core/src/bitcoin.rs +++ b/swap-core/src/bitcoin.rs @@ -8,6 +8,7 @@ mod partial_refund; mod punish; mod redeem; mod refund_amnesty; +mod refund_burn; mod timelocks; pub use crate::bitcoin::cancel::TxCancel; @@ -18,6 +19,7 @@ pub use crate::bitcoin::partial_refund::TxPartialRefund; pub use crate::bitcoin::punish::TxPunish; pub use crate::bitcoin::redeem::TxRedeem; pub use crate::bitcoin::refund_amnesty::TxRefundAmnesty; +pub use crate::bitcoin::refund_burn::TxRefundBurn; pub use crate::bitcoin::timelocks::{BlockHeight, ExpiredTimelocks}; pub use crate::bitcoin::timelocks::{CancelTimelock, PunishTimelock, RemainingRefundTimelock}; pub use bitcoin_wallet::ScriptStatus; diff --git a/swap-core/src/bitcoin/partial_refund.rs b/swap-core/src/bitcoin/partial_refund.rs index 67c5d6f3c2..80ffe2806c 100644 --- a/swap-core/src/bitcoin/partial_refund.rs +++ b/swap-core/src/bitcoin/partial_refund.rs @@ -115,6 +115,47 @@ impl TxPartialRefund { } } + /// Build a transaction that spends the amnesty output to a new 2-of-2 multisig (burn output). + /// This is used by TxRefundBurn to "burn" the amnesty by moving it to another multisig. + /// Unlike `build_amnesty_spend_transaction`, this has no timelock. + pub fn build_burn_spend_transaction( + &self, + burn_output_descriptor: &Descriptor<::bitcoin::PublicKey>, + spending_fee: Amount, + ) -> Transaction { + use ::bitcoin::{ + Sequence, TxIn, TxOut, locktime::absolute::LockTime as PackedLockTime, + transaction::Version, + }; + + // TODO: Handle case where fee >= amnesty_amount more gracefully + assert!( + self.amnesty_amount() > spending_fee, + "Burn spend fee ({}) must be less than amnesty amount ({})", + spending_fee, + self.amnesty_amount() + ); + + let tx_in = TxIn { + previous_output: self.amnesty_outpoint(), + script_sig: Default::default(), + sequence: Sequence(0xFFFF_FFFF), // No timelock + witness: Default::default(), + }; + + let tx_out = TxOut { + value: self.amnesty_amount() - spending_fee, + script_pubkey: burn_output_descriptor.script_pubkey(), + }; + + Transaction { + version: Version(2), + lock_time: PackedLockTime::from_height(0).expect("0 to be below lock time threshold"), + input: vec![tx_in], + output: vec![tx_out], + } + } + pub fn add_signatures( self, (A, sig_a): (PublicKey, Signature), diff --git a/swap-core/src/bitcoin/refund_burn.rs b/swap-core/src/bitcoin/refund_burn.rs new file mode 100644 index 0000000000..844dbb0434 --- /dev/null +++ b/swap-core/src/bitcoin/refund_burn.rs @@ -0,0 +1,205 @@ +#![allow(non_snake_case)] + +use crate::bitcoin::partial_refund::TxPartialRefund; +use crate::bitcoin::{self, build_shared_output_descriptor, Address, Amount, PublicKey, Transaction}; +use ::bitcoin::sighash::SighashCache; +use ::bitcoin::{EcdsaSighashType, Txid, sighash::SegwitV0Sighash as Sighash}; +use ::bitcoin::{OutPoint, ScriptBuf, Weight, secp256k1}; +use anyhow::{Context, Result}; +use bdk_wallet::miniscript::Descriptor; +use bitcoin_wallet::primitives::Watchable; +use ecdsa_fun::Signature; +use std::collections::HashMap; + +/// TxRefundBurn spends the amnesty output of TxPartialRefund and sends it to +/// a new 2-of-2 multisig. This allows Alice to "burn" the amnesty (prevent Bob +/// from claiming it via TxRefundAmnesty) while still allowing a later refund +/// via TxFinalAmnesty if Alice cooperates. +/// +/// Unlike TxRefundAmnesty, this transaction has no timelock - Alice can publish +/// it immediately after TxPartialRefund is confirmed. +#[derive(Debug, Clone)] +pub struct TxRefundBurn { + inner: Transaction, + digest: Sighash, + amnesty_output_descriptor: Descriptor<::bitcoin::PublicKey>, + pub(in crate::bitcoin) burn_output_descriptor: Descriptor<::bitcoin::PublicKey>, + watch_script: ScriptBuf, +} + +impl TxRefundBurn { + pub fn new( + tx_partial_refund: &TxPartialRefund, + A: PublicKey, + B: PublicKey, + spending_fee: Amount, + ) -> Result { + // TODO: Handle case where fee >= amnesty_amount more gracefully + // For now, assert to catch this during development + assert!( + tx_partial_refund.amnesty_amount() > spending_fee, + "TxRefundBurn fee ({}) must be less than amnesty amount ({})", + spending_fee, + tx_partial_refund.amnesty_amount() + ); + + let burn_output_descriptor = build_shared_output_descriptor(A.0, B.0)?; + + let tx_refund_burn = tx_partial_refund.build_burn_spend_transaction( + &burn_output_descriptor, + spending_fee, + ); + + let digest = SighashCache::new(&tx_refund_burn) + .p2wsh_signature_hash( + 0, // Only one input: amnesty output from tx_partial_refund + &tx_partial_refund + .amnesty_output_descriptor + .script_code() + .expect("scriptcode"), + tx_partial_refund.amnesty_amount(), + EcdsaSighashType::All, + ) + .expect("sighash"); + + let watch_script = burn_output_descriptor.script_pubkey(); + + Ok(Self { + inner: tx_refund_burn, + digest, + amnesty_output_descriptor: tx_partial_refund.amnesty_output_descriptor.clone(), + burn_output_descriptor, + watch_script, + }) + } + + pub fn txid(&self) -> Txid { + self.inner.compute_txid() + } + + pub fn digest(&self) -> Sighash { + self.digest + } + + pub fn amount(&self) -> Amount { + self.inner.output[0].value + } + + pub fn as_outpoint(&self) -> OutPoint { + OutPoint::new(self.txid(), 0) + } + + pub fn complete_as_alice( + &self, + a: bitcoin::SecretKey, + B: bitcoin::PublicKey, + sig_b: Signature, + ) -> Result { + let sig_a = a.sign(self.digest()); + + self.clone() + .add_signatures((a.public(), sig_a), (B, sig_b)) + .context("Couldn't add signatures to transaction") + } + + pub fn add_signatures( + self, + (A, sig_a): (PublicKey, Signature), + (B, sig_b): (PublicKey, Signature), + ) -> Result { + let satisfier = { + let mut satisfier = HashMap::with_capacity(2); + + let A = ::bitcoin::PublicKey { + compressed: true, + inner: secp256k1::PublicKey::from_slice(&A.0.to_bytes())?, + }; + let B = ::bitcoin::PublicKey { + compressed: true, + inner: secp256k1::PublicKey::from_slice(&B.0.to_bytes())?, + }; + + let sig_a = secp256k1::ecdsa::Signature::from_compact(&sig_a.to_bytes())?; + let sig_b = secp256k1::ecdsa::Signature::from_compact(&sig_b.to_bytes())?; + + // The order in which these are inserted doesn't matter + satisfier.insert( + A, + ::bitcoin::ecdsa::Signature { + signature: sig_a, + sighash_type: EcdsaSighashType::All, + }, + ); + satisfier.insert( + B, + ::bitcoin::ecdsa::Signature { + signature: sig_b, + sighash_type: EcdsaSighashType::All, + }, + ); + + satisfier + }; + + let mut tx = self.inner; + self.amnesty_output_descriptor + .satisfy(&mut tx.input[0], satisfier)?; + + Ok(tx) + } + + /// Build a transaction that spends the burn output to a destination address. + /// Used by TxFinalAmnesty to send the funds back to Bob's refund address. + pub fn build_spend_transaction( + &self, + destination: &Address, + spending_fee: Amount, + ) -> Transaction { + use ::bitcoin::{ + Sequence, TxIn, TxOut, locktime::absolute::LockTime as PackedLockTime, + transaction::Version, + }; + + // TODO: Handle case where fee >= burn amount more gracefully + // For now, assert to catch this during development + assert!( + self.amount() > spending_fee, + "TxFinalAmnesty fee ({}) must be less than burn amount ({})", + spending_fee, + self.amount() + ); + + let tx_in = TxIn { + previous_output: self.as_outpoint(), + script_sig: Default::default(), + sequence: Sequence(0xFFFF_FFFF), // No timelock + witness: Default::default(), + }; + + let tx_out = TxOut { + value: self.amount() - spending_fee, + script_pubkey: destination.script_pubkey(), + }; + + Transaction { + version: Version(2), + lock_time: PackedLockTime::from_height(0).expect("0 to be below lock time threshold"), + input: vec![tx_in], + output: vec![tx_out], + } + } + + pub fn weight() -> Weight { + Weight::from_wu(548) + } +} + +impl Watchable for TxRefundBurn { + fn id(&self) -> Txid { + self.txid() + } + + fn script(&self) -> ScriptBuf { + self.watch_script.clone() + } +} From 95f86a0513b6d296f106d1145a07dd559b5f3c8f Mon Sep 17 00:00:00 2001 From: einliterflasche Date: Wed, 17 Dec 2025 13:32:53 +0100 Subject: [PATCH 058/113] add TxFinalAmnesty --- swap-core/src/bitcoin.rs | 2 + swap-core/src/bitcoin/final_amnesty.rs | 148 +++++++++++++++++++++++++ 2 files changed, 150 insertions(+) create mode 100644 swap-core/src/bitcoin/final_amnesty.rs diff --git a/swap-core/src/bitcoin.rs b/swap-core/src/bitcoin.rs index 8c95e72f37..2940d11fe7 100644 --- a/swap-core/src/bitcoin.rs +++ b/swap-core/src/bitcoin.rs @@ -2,6 +2,7 @@ mod cancel; mod early_refund; +mod final_amnesty; mod full_refund; mod lock; mod partial_refund; @@ -13,6 +14,7 @@ mod timelocks; pub use crate::bitcoin::cancel::TxCancel; pub use crate::bitcoin::early_refund::TxEarlyRefund; +pub use crate::bitcoin::final_amnesty::TxFinalAmnesty; pub use crate::bitcoin::full_refund::TxFullRefund; pub use crate::bitcoin::lock::TxLock; pub use crate::bitcoin::partial_refund::TxPartialRefund; diff --git a/swap-core/src/bitcoin/final_amnesty.rs b/swap-core/src/bitcoin/final_amnesty.rs new file mode 100644 index 0000000000..dc6f3e1734 --- /dev/null +++ b/swap-core/src/bitcoin/final_amnesty.rs @@ -0,0 +1,148 @@ +#![allow(non_snake_case)] + +use crate::bitcoin::refund_burn::TxRefundBurn; +use crate::bitcoin::{self, Address, Amount, PublicKey, Transaction}; +use ::bitcoin::sighash::SighashCache; +use ::bitcoin::{EcdsaSighashType, Txid, sighash::SegwitV0Sighash as Sighash}; +use ::bitcoin::{ScriptBuf, Weight, secp256k1}; +use anyhow::{Context, Result}; +use bdk_wallet::miniscript::Descriptor; +use bitcoin_wallet::primitives::Watchable; +use ecdsa_fun::Signature; +use std::collections::HashMap; + +/// TxFinalAmnesty spends the burn output of TxRefundBurn and sends it to +/// Bob's refund address. This allows Alice to voluntarily refund Bob even +/// after she has "burnt" the amnesty output. +/// +/// This transaction is presigned by Bob during swap setup, but Alice keeps +/// her signature private until she decides to cooperate (e.g., if Bob contacts +/// her to request the refund). +#[derive(Debug, Clone)] +pub struct TxFinalAmnesty { + inner: Transaction, + digest: Sighash, + burn_output_descriptor: Descriptor<::bitcoin::PublicKey>, + watch_script: ScriptBuf, +} + +impl TxFinalAmnesty { + pub fn new( + tx_refund_burn: &TxRefundBurn, + refund_address: &Address, + spending_fee: Amount, + ) -> Self { + // TODO: Handle case where fee >= burn amount more gracefully + assert!( + tx_refund_burn.amount() > spending_fee, + "TxFinalAmnesty fee ({}) must be less than burn amount ({})", + spending_fee, + tx_refund_burn.amount() + ); + + let tx_final_amnesty = tx_refund_burn.build_spend_transaction( + refund_address, + spending_fee, + ); + + let digest = SighashCache::new(&tx_final_amnesty) + .p2wsh_signature_hash( + 0, // Only one input: burn output from tx_refund_burn + &tx_refund_burn + .burn_output_descriptor + .script_code() + .expect("scriptcode"), + tx_refund_burn.amount(), + EcdsaSighashType::All, + ) + .expect("sighash"); + + Self { + inner: tx_final_amnesty, + digest, + burn_output_descriptor: tx_refund_burn.burn_output_descriptor.clone(), + watch_script: refund_address.script_pubkey(), + } + } + + pub fn txid(&self) -> Txid { + self.inner.compute_txid() + } + + pub fn digest(&self) -> Sighash { + self.digest + } + + pub fn complete_as_alice( + &self, + a: bitcoin::SecretKey, + B: bitcoin::PublicKey, + sig_b: Signature, + ) -> Result { + let sig_a = a.sign(self.digest()); + + self.clone() + .add_signatures((a.public(), sig_a), (B, sig_b)) + .context("Couldn't add signatures to transaction") + } + + pub fn add_signatures( + self, + (A, sig_a): (PublicKey, Signature), + (B, sig_b): (PublicKey, Signature), + ) -> Result { + let satisfier = { + let mut satisfier = HashMap::with_capacity(2); + + let A = ::bitcoin::PublicKey { + compressed: true, + inner: secp256k1::PublicKey::from_slice(&A.0.to_bytes())?, + }; + let B = ::bitcoin::PublicKey { + compressed: true, + inner: secp256k1::PublicKey::from_slice(&B.0.to_bytes())?, + }; + + let sig_a = secp256k1::ecdsa::Signature::from_compact(&sig_a.to_bytes())?; + let sig_b = secp256k1::ecdsa::Signature::from_compact(&sig_b.to_bytes())?; + + // The order in which these are inserted doesn't matter + satisfier.insert( + A, + ::bitcoin::ecdsa::Signature { + signature: sig_a, + sighash_type: EcdsaSighashType::All, + }, + ); + satisfier.insert( + B, + ::bitcoin::ecdsa::Signature { + signature: sig_b, + sighash_type: EcdsaSighashType::All, + }, + ); + + satisfier + }; + + let mut tx = self.inner; + self.burn_output_descriptor + .satisfy(&mut tx.input[0], satisfier)?; + + Ok(tx) + } + + pub fn weight() -> Weight { + Weight::from_wu(548) + } +} + +impl Watchable for TxFinalAmnesty { + fn id(&self) -> Txid { + self.txid() + } + + fn script(&self) -> ScriptBuf { + self.watch_script.clone() + } +} From d39305690b470a855b0b5fb0a0d7a62597f3b715 Mon Sep 17 00:00:00 2001 From: einliterflasche Date: Wed, 17 Dec 2025 13:34:12 +0100 Subject: [PATCH 059/113] state-machine(bob): add new states (WaitingForRemainingRefundTimelockExpiration, RemainingRefundTimelockExpired, BtcRefundBurnPublished, BtcRefundBurnt, BtcFinalAmnestyPublished, BtcFinalAmnstyReceived) --- swap-db/src/bob.rs | 39 +++++++++++++++++++++++++++++++ swap-machine/src/bob/mod.rs | 36 +++++++++++++++++++++++++++- swap/src/cli/cancel_and_refund.rs | 12 ++++++++++ swap/src/protocol/bob/swap.rs | 34 ++++++++++++++++++++++++++- 4 files changed, 119 insertions(+), 2 deletions(-) diff --git a/swap-db/src/bob.rs b/swap-db/src/bob.rs index 71cfac23ca..66ff5f1038 100644 --- a/swap-db/src/bob.rs +++ b/swap-db/src/bob.rs @@ -47,6 +47,11 @@ pub enum Bob { BtcPartialRefundPublished(bob::State6), BtcPartiallyRefunded(bob::State6), BtcAmnestyPublished(bob::State6), + WaitingForRemainingRefundTimelockExpiration(bob::State6), + RemainingRefundTimelockExpired(bob::State6), + BtcRefundBurnPublished(bob::State6), + BtcRefundBurnt(bob::State6), + BtcFinalAmnestyPublished(bob::State6), Done(BobEndState), } @@ -57,6 +62,7 @@ pub enum BobEndState { BtcRefunded(Box), BtcEarlyRefunded(Box), BtcAmnestyConfirmed(Box), + BtcFinalAmnestyConfirmed(Box), } impl From for Bob { @@ -118,6 +124,18 @@ impl From for Bob { BobState::BtcAmnestyConfirmed(state6) => { Bob::Done(BobEndState::BtcAmnestyConfirmed(Box::new(state6))) } + BobState::WaitingForRemainingRefundTimelockExpiration(state6) => { + Bob::WaitingForRemainingRefundTimelockExpiration(state6) + } + BobState::RemainingRefundTimelockExpired(state6) => { + Bob::RemainingRefundTimelockExpired(state6) + } + BobState::BtcRefundBurnPublished(state6) => Bob::BtcRefundBurnPublished(state6), + BobState::BtcRefundBurnt(state6) => Bob::BtcRefundBurnt(state6), + BobState::BtcFinalAmnestyPublished(state6) => Bob::BtcFinalAmnestyPublished(state6), + BobState::BtcFinalAmnestyConfirmed(state6) => { + Bob::Done(BobEndState::BtcFinalAmnestyConfirmed(Box::new(state6))) + } BobState::SafelyAborted => Bob::Done(BobEndState::SafelyAborted), } } @@ -172,12 +190,24 @@ impl From for BobState { Bob::BtcAmnestyPublished(state6) => BobState::BtcAmnestyPublished(state6), Bob::BtcEarlyRefundPublished(state6) => BobState::BtcEarlyRefundPublished(state6), Bob::BtcPunished { state, tx_lock_id } => BobState::BtcPunished { state, tx_lock_id }, + Bob::WaitingForRemainingRefundTimelockExpiration(state6) => { + BobState::WaitingForRemainingRefundTimelockExpiration(state6) + } + Bob::RemainingRefundTimelockExpired(state6) => { + BobState::RemainingRefundTimelockExpired(state6) + } + Bob::BtcRefundBurnPublished(state6) => BobState::BtcRefundBurnPublished(state6), + Bob::BtcRefundBurnt(state6) => BobState::BtcRefundBurnt(state6), + Bob::BtcFinalAmnestyPublished(state6) => BobState::BtcFinalAmnestyPublished(state6), Bob::Done(end_state) => match end_state { BobEndState::SafelyAborted => BobState::SafelyAborted, BobEndState::XmrRedeemed { tx_lock_id } => BobState::XmrRedeemed { tx_lock_id }, BobEndState::BtcRefunded(state6) => BobState::BtcRefunded(*state6), BobEndState::BtcEarlyRefunded(state6) => BobState::BtcEarlyRefunded(*state6), BobEndState::BtcAmnestyConfirmed(state6) => BobState::BtcAmnestyConfirmed(*state6), + BobEndState::BtcFinalAmnestyConfirmed(state6) => { + BobState::BtcFinalAmnestyConfirmed(*state6) + } }, } } @@ -207,6 +237,15 @@ impl fmt::Display for Bob { Bob::BtcPunished { .. } => f.write_str("Bitcoin punished"), Bob::BtcPartiallyRefunded { .. } => f.write_str("Bitcoin partially refunded"), Bob::BtcAmnestyPublished { .. } => f.write_str("Bitcoin amnesty published"), + Bob::WaitingForRemainingRefundTimelockExpiration { .. } => { + f.write_str("Waiting for remaining refund timelock to expire") + } + Bob::RemainingRefundTimelockExpired { .. } => { + f.write_str("Remaining refund timelock expired") + } + Bob::BtcRefundBurnPublished { .. } => f.write_str("Bitcoin refund burn published"), + Bob::BtcRefundBurnt { .. } => f.write_str("Bitcoin refund burnt"), + Bob::BtcFinalAmnestyPublished { .. } => f.write_str("Bitcoin final amnesty published"), } } } diff --git a/swap-machine/src/bob/mod.rs b/swap-machine/src/bob/mod.rs index 6db55cb0b5..c549ea229a 100644 --- a/swap-machine/src/bob/mod.rs +++ b/swap-machine/src/bob/mod.rs @@ -59,6 +59,19 @@ pub enum BobState { BtcPartiallyRefunded(State6), BtcAmnestyPublished(State6), BtcAmnestyConfirmed(State6), + /// Waiting for RemainingRefundTimelock to expire after partial refund confirmed. + /// During this time, Alice may publish TxRefundBurn. + WaitingForRemainingRefundTimelockExpiration(State6), + /// RemainingRefundTimelock has expired, we can now publish TxRefundAmnesty. + RemainingRefundTimelockExpired(State6), + /// Alice published TxRefundBurn before we could publish TxRefundAmnesty. + BtcRefundBurnPublished(State6), + /// TxRefundBurn has been confirmed. The amnesty output is now burnt. + BtcRefundBurnt(State6), + /// Alice published TxFinalAmnesty (using our presigned signature) to refund us. + BtcFinalAmnestyPublished(State6), + /// TxFinalAmnesty has been confirmed. We received the burnt funds back. + BtcFinalAmnestyConfirmed(State6), XmrRedeemed { tx_lock_id: bitcoin::Txid, }, @@ -140,6 +153,20 @@ impl fmt::Display for BobState { BobState::BtcPartiallyRefunded { .. } => write!(f, "btc is partially refunded"), BobState::BtcAmnestyPublished { .. } => write!(f, "btc amnesty is published"), BobState::BtcAmnestyConfirmed { .. } => write!(f, "btc amnesty is confirmed"), + BobState::WaitingForRemainingRefundTimelockExpiration { .. } => { + write!(f, "waiting for remaining refund timelock to expire") + } + BobState::RemainingRefundTimelockExpired { .. } => { + write!(f, "remaining refund timelock expired") + } + BobState::BtcRefundBurnPublished { .. } => write!(f, "btc refund burn is published"), + BobState::BtcRefundBurnt { .. } => write!(f, "btc refund is burnt"), + BobState::BtcFinalAmnestyPublished { .. } => { + write!(f, "btc final amnesty is published") + } + BobState::BtcFinalAmnestyConfirmed { .. } => { + write!(f, "btc final amnesty is confirmed") + } BobState::SafelyAborted => write!(f, "safely aborted"), } } @@ -180,10 +207,16 @@ impl BobState { | BobState::BtcPartialRefundPublished(state) | BobState::BtcPartiallyRefunded(state) | BobState::BtcAmnestyPublished(state) - | BobState::BtcAmnestyConfirmed(state) => { + | BobState::BtcAmnestyConfirmed(state) + | BobState::WaitingForRemainingRefundTimelockExpiration(state) + | BobState::RemainingRefundTimelockExpired(state) + | BobState::BtcRefundBurnPublished(state) + | BobState::BtcRefundBurnt(state) + | BobState::BtcFinalAmnestyPublished(state) => { Some(state.expired_timelock(bitcoin_wallet.as_ref()).await?) } BobState::BtcPunished { .. } => Some(ExpiredTimelocks::Punish), + BobState::BtcFinalAmnestyConfirmed(_) => Some(ExpiredTimelocks::RemainingRefund), BobState::BtcRefunded(_) | BobState::BtcEarlyRefunded { .. } | BobState::BtcRedeemed(_) @@ -198,6 +231,7 @@ pub fn is_complete(state: &BobState) -> bool { BobState::BtcRefunded(..) | BobState::BtcEarlyRefunded { .. } | BobState::BtcAmnestyConfirmed { .. } + | BobState::BtcFinalAmnestyConfirmed { .. } | BobState::XmrRedeemed { .. } | BobState::SafelyAborted ) diff --git a/swap/src/cli/cancel_and_refund.rs b/swap/src/cli/cancel_and_refund.rs index 530ea8d282..5c5f624524 100644 --- a/swap/src/cli/cancel_and_refund.rs +++ b/swap/src/cli/cancel_and_refund.rs @@ -72,12 +72,18 @@ pub async fn cancel( BobState::BtcPartiallyRefunded(state6) => state6, BobState::BtcAmnestyConfirmed(state6) => state6, BobState::BtcAmnestyPublished(state6) => state6, + BobState::WaitingForRemainingRefundTimelockExpiration(state6) => state6, + BobState::RemainingRefundTimelockExpired(state6) => state6, + BobState::BtcRefundBurnPublished(state6) => state6, + BobState::BtcFinalAmnestyPublished(state6) => state6, BobState::Started { .. } | BobState::BtcRedeemed(_) | BobState::XmrRedeemed { .. } | BobState::BtcPunished { .. } | BobState::BtcEarlyRefunded { .. } + | BobState::BtcRefundBurnt { .. } + | BobState::BtcFinalAmnestyConfirmed { .. } | BobState::SafelyAborted => bail!( "Cannot cancel swap {} because it is in state {} which is not cancellable.", swap_id, @@ -190,12 +196,18 @@ pub async fn refund( BobState::BtcPartiallyRefunded(state6) => state6, BobState::BtcAmnestyPublished(state6) => state6, BobState::BtcAmnestyConfirmed(state6) => state6, + BobState::WaitingForRemainingRefundTimelockExpiration(state6) => state6, + BobState::RemainingRefundTimelockExpired(state6) => state6, + BobState::BtcRefundBurnPublished(state6) => state6, + BobState::BtcFinalAmnestyPublished(state6) => state6, BobState::Started { .. } | BobState::SwapSetupCompleted(_) | BobState::BtcRedeemed(_) | BobState::BtcEarlyRefunded { .. } | BobState::XmrRedeemed { .. } | BobState::BtcPunished { .. } + | BobState::BtcRefundBurnt { .. } + | BobState::BtcFinalAmnestyConfirmed { .. } | BobState::SafelyAborted => bail!( "Cannot refund swap {} because it is in state {} which is not refundable.", swap_id, diff --git a/swap/src/protocol/bob/swap.rs b/swap/src/protocol/bob/swap.rs index 1371be45fd..e7fa04596b 100644 --- a/swap/src/protocol/bob/swap.rs +++ b/swap/src/protocol/bob/swap.rs @@ -1214,8 +1214,40 @@ async fn next_state( event_emitter.emit_swap_progress_event(swap_id, TauriSwapProgressEvent::BtcAmnestyReceived { btc_amnesty_txid: state.construct_tx_amnesty()?.txid(), }); - BobState::BtcAmnestyConfirmed(state) + BobState::BtcAmnestyConfirmed(state) }, + BobState::WaitingForRemainingRefundTimelockExpiration(_state) => { + // TODO: Wait for timelock expiry while watching for TxRefundBurn + // If timelock expires -> RemainingRefundTimelockExpired + // If TxRefundBurn seen -> BtcRefundBurnPublished + todo!("WaitingForRemainingRefundTimelockExpiration state transition not yet implemented") + } + BobState::RemainingRefundTimelockExpired(_state) => { + // TODO: Check if TxRefundBurn was published/confirmed first + // If TxRefundBurn confirmed -> BtcRefundBurnt + // If TxRefundBurn published -> BtcRefundBurnPublished + // Otherwise publish TxRefundAmnesty -> BtcAmnestyPublished + todo!("RemainingRefundTimelockExpired state transition not yet implemented") + } + BobState::BtcRefundBurnPublished(_state) => { + // TODO: Wait for TxRefundBurn confirmation + // Then -> BtcRefundBurnt + todo!("BtcRefundBurnPublished state transition not yet implemented") + } + BobState::BtcRefundBurnt(state) => { + // Terminal state - Alice needs to manually publish TxFinalAmnesty + // Similar to BtcPunished, we stop here + BobState::BtcRefundBurnt(state) + } + BobState::BtcFinalAmnestyPublished(_state) => { + // TODO: Wait for TxFinalAmnesty confirmation + // Then -> BtcFinalAmnestyConfirmed + todo!("BtcFinalAmnestyPublished state transition not yet implemented") + } + BobState::BtcFinalAmnestyConfirmed(state) => { + // Terminal state - we received the burnt funds back + BobState::BtcFinalAmnestyConfirmed(state) + } BobState::SafelyAborted => BobState::SafelyAborted, BobState::XmrRedeemed { tx_lock_id } => { event_emitter.emit_swap_progress_event( From 310acc1f60f585c4fc51f1246f6522a027b8bdb9 Mon Sep 17 00:00:00 2001 From: einliterflasche Date: Wed, 17 Dec 2025 15:32:57 +0100 Subject: [PATCH 060/113] swap-setup: exchange signatures and fees for TxRefundBurn and TxFinalAmnesty --- swap-machine/src/alice/mod.rs | 51 +++++++++++++++++++++- swap-machine/src/bob/mod.rs | 36 ++++++++++++++- swap-machine/src/common/mod.rs | 4 ++ swap-p2p/src/protocols/swap_setup/alice.rs | 4 ++ swap-p2p/src/protocols/swap_setup/bob.rs | 2 + swap/src/asb/event_loop.rs | 4 ++ swap/src/protocol/bob/swap.rs | 6 ++- 7 files changed, 103 insertions(+), 4 deletions(-) diff --git a/swap-machine/src/alice/mod.rs b/swap-machine/src/alice/mod.rs index d9142e8ce8..d2d6696e50 100644 --- a/swap-machine/src/alice/mod.rs +++ b/swap-machine/src/alice/mod.rs @@ -9,8 +9,8 @@ use std::fmt::{self, Debug}; use std::sync::Arc; use swap_core::bitcoin::{ CancelTimelock, ExpiredTimelocks, PunishTimelock, RemainingRefundTimelock, Transaction, - TxCancel, TxEarlyRefund, TxFullRefund, TxPartialRefund, TxPunish, TxRedeem, TxRefundAmnesty, - Txid, current_epoch, + TxCancel, TxEarlyRefund, TxFinalAmnesty, TxFullRefund, TxPartialRefund, TxPunish, TxRedeem, + TxRefundAmnesty, TxRefundBurn, Txid, current_epoch, }; use swap_core::monero; use swap_core::monero::ScalarExt; @@ -175,6 +175,7 @@ pub struct State0 { punish_address: bitcoin::Address, tx_redeem_fee: bitcoin::Amount, tx_punish_fee: bitcoin::Amount, + tx_refund_burn_fee: Option, } impl State0 { @@ -188,6 +189,7 @@ impl State0 { punish_address: bitcoin::Address, tx_redeem_fee: bitcoin::Amount, tx_punish_fee: bitcoin::Amount, + tx_refund_burn_fee: bitcoin::Amount, rng: &mut R, ) -> Self where @@ -218,6 +220,7 @@ impl State0 { remaining_refund_timelock: Some(env_config.bitcoin_remaining_refund_timelock.into()), tx_redeem_fee, tx_punish_fee, + tx_refund_burn_fee: Some(tx_refund_burn_fee), } } @@ -266,6 +269,8 @@ impl State0 { tx_refund_fee: msg.tx_refund_fee, tx_partial_refund_fee: Some(msg.tx_partial_refund_fee), tx_refund_amnesty_fee: Some(msg.tx_refund_amnesty_fee), + tx_refund_burn_fee: self.tx_refund_burn_fee, + tx_final_amnesty_fee: Some(msg.tx_final_amnesty_fee), tx_cancel_fee: msg.tx_cancel_fee, }, )) @@ -299,6 +304,8 @@ pub struct State1 { tx_refund_fee: bitcoin::Amount, tx_partial_refund_fee: Option, tx_refund_amnesty_fee: Option, + tx_refund_burn_fee: Option, + tx_final_amnesty_fee: Option, tx_cancel_fee: bitcoin::Amount, } @@ -317,6 +324,9 @@ impl State1 { amnesty_amount: self .btc_amnesty_amount .context("Missing btc_amesty_amount for new swap that should have it")?, + tx_refund_burn_fee: self + .tx_refund_burn_fee + .context("Missing tx_refund_burn_fee for new swap that should have it")?, }) } @@ -351,6 +361,8 @@ impl State1 { tx_refund_fee: self.tx_refund_fee, tx_partial_refund_fee: self.tx_partial_refund_fee, tx_refund_amnesty_fee: self.tx_refund_amnesty_fee, + tx_refund_burn_fee: self.tx_refund_burn_fee, + tx_final_amnesty_fee: self.tx_final_amnesty_fee, tx_cancel_fee: self.tx_cancel_fee, }) } @@ -380,6 +392,8 @@ pub struct State2 { tx_refund_fee: bitcoin::Amount, tx_partial_refund_fee: Option, tx_refund_amnesty_fee: Option, + tx_refund_burn_fee: Option, + tx_final_amnesty_fee: Option, tx_cancel_fee: bitcoin::Amount, } @@ -493,6 +507,39 @@ impl State2 { ) .context("Failed to verify refund amnesty transaction")?; + // Create TxRefundBurn ourself + let tx_refund_burn = TxRefundBurn::new( + &tx_partial_refund, + self.a.public(), + self.B, + self.tx_refund_burn_fee + .context("missing tx_refund_burn_fee")?, + )?; + + // Check if the provided signature by Bob is valid for the transaction + swap_core::bitcoin::verify_sig( + &self.B, + &tx_refund_burn.digest(), + &msg.tx_refund_burn_sig, + ) + .context("Failed to verify refund burn transaction")?; + + // Create TxFinalAmnesty ourself + let tx_final_amnesty = TxFinalAmnesty::new( + &tx_refund_burn, + &self.refund_address, + self.tx_final_amnesty_fee + .context("missing tx_final_amnesty_fee")?, + ); + + // Check if the provided signature by Bob is valid for the transaction + swap_core::bitcoin::verify_sig( + &self.B, + &tx_final_amnesty.digest(), + &msg.tx_final_amnesty_sig, + ) + .context("Failed to verify final amnesty transaction")?; + Ok(State3 { a: self.a, B: self.B, diff --git a/swap-machine/src/bob/mod.rs b/swap-machine/src/bob/mod.rs index c549ea229a..edf1c67e92 100644 --- a/swap-machine/src/bob/mod.rs +++ b/swap-machine/src/bob/mod.rs @@ -15,7 +15,8 @@ use std::fmt; use std::sync::Arc; use swap_core::bitcoin::{ self, CancelTimelock, ExpiredTimelocks, PunishTimelock, RemainingRefundTimelock, Transaction, - TxCancel, TxLock, TxPartialRefund, TxRefundAmnesty, Txid, current_epoch, + TxCancel, TxFinalAmnesty, TxLock, TxPartialRefund, TxRefundAmnesty, TxRefundBurn, Txid, + current_epoch, }; use swap_core::monero::ScalarExt; use swap_core::monero::primitives::WatchRequest; @@ -256,6 +257,7 @@ pub struct State0 { min_monero_confirmations: u64, tx_partial_refund_fee: Option, tx_refund_amnesty_fee: Option, + tx_final_amnesty_fee: Option, tx_refund_fee: bitcoin::Amount, tx_cancel_fee: bitcoin::Amount, tx_lock_fee: bitcoin::Amount, @@ -275,6 +277,7 @@ impl State0 { min_monero_confirmations: u64, tx_partial_refund_fee: bitcoin::Amount, tx_refund_amnesty_fee: bitcoin::Amount, + tx_final_amnesty_fee: bitcoin::Amount, tx_refund_fee: bitcoin::Amount, tx_cancel_fee: bitcoin::Amount, tx_lock_fee: bitcoin::Amount, @@ -305,6 +308,7 @@ impl State0 { min_monero_confirmations, tx_partial_refund_fee: Some(tx_partial_refund_fee), tx_refund_amnesty_fee: Some(tx_refund_amnesty_fee), + tx_final_amnesty_fee: Some(tx_final_amnesty_fee), tx_refund_fee, tx_cancel_fee, tx_lock_fee, @@ -327,6 +331,9 @@ impl State0 { tx_refund_amnesty_fee: self .tx_refund_amnesty_fee .context("tx_refund_amnesty_fee missing but required to setup swap")?, + tx_final_amnesty_fee: self + .tx_final_amnesty_fee + .context("tx_final_amnesty_fee missing but required to setup swap")?, tx_cancel_fee: self.tx_cancel_fee, }) } @@ -383,6 +390,8 @@ impl State0 { tx_refund_fee: self.tx_refund_fee, tx_partial_refund_fee: self.tx_partial_refund_fee, tx_refund_amnesty_fee: self.tx_refund_amnesty_fee, + tx_refund_burn_fee: Some(msg.tx_refund_burn_fee), + tx_final_amnesty_fee: self.tx_final_amnesty_fee, tx_punish_fee: msg.tx_punish_fee, tx_cancel_fee: self.tx_cancel_fee, }) @@ -410,6 +419,8 @@ pub struct State1 { min_monero_confirmations: u64, tx_partial_refund_fee: Option, tx_refund_amnesty_fee: Option, + tx_refund_burn_fee: Option, + tx_final_amnesty_fee: Option, tx_redeem_fee: bitcoin::Amount, tx_refund_fee: bitcoin::Amount, tx_punish_fee: bitcoin::Amount, @@ -504,6 +515,8 @@ impl State1 { tx_refund_fee: self.tx_refund_fee, tx_partial_refund_fee: self.tx_partial_refund_fee, tx_refund_amnesty_fee: self.tx_refund_amnesty_fee, + tx_refund_burn_fee: self.tx_refund_burn_fee, + tx_final_amnesty_fee: self.tx_final_amnesty_fee, tx_punish_fee: self.tx_punish_fee, tx_cancel_fee: self.tx_cancel_fee, }) @@ -547,6 +560,8 @@ pub struct State2 { pub tx_cancel_fee: bitcoin::Amount, tx_partial_refund_fee: Option, tx_refund_amnesty_fee: Option, + tx_refund_burn_fee: Option, + tx_final_amnesty_fee: Option, } impl State2 { @@ -596,11 +611,30 @@ impl State2 { ); let tx_refund_amnesty_sig = self.b.sign(tx_refund_amnesty.digest()); + let tx_refund_burn = TxRefundBurn::new( + &tx_partial_refund, + self.A, + self.b.public(), + self.tx_refund_burn_fee + .context("Missing tx_refund_burn_fee")?, + )?; + let tx_refund_burn_sig = self.b.sign(tx_refund_burn.digest()); + + let tx_final_amnesty = TxFinalAmnesty::new( + &tx_refund_burn, + &self.refund_address, + self.tx_final_amnesty_fee + .context("Missing tx_final_amnesty_fee")?, + ); + let tx_final_amnesty_sig = self.b.sign(tx_final_amnesty.digest()); + Ok(Message4 { tx_punish_sig, tx_cancel_sig, tx_early_refund_sig, tx_refund_amnesty_sig, + tx_refund_burn_sig, + tx_final_amnesty_sig, }) } diff --git a/swap-machine/src/common/mod.rs b/swap-machine/src/common/mod.rs index 86273769e7..75754a3db3 100644 --- a/swap-machine/src/common/mod.rs +++ b/swap-machine/src/common/mod.rs @@ -39,6 +39,7 @@ pub struct Message0 { pub tx_partial_refund_fee: bitcoin::Amount, pub tx_refund_amnesty_fee: bitcoin::Amount, pub tx_cancel_fee: bitcoin::Amount, + pub tx_final_amnesty_fee: bitcoin::Amount, } #[allow(non_snake_case)] @@ -58,6 +59,7 @@ pub struct Message1 { /// The amount of Bitcoin that Bob not get refunded unless Alice decides so. /// Introduced in [#675](https://github.com/eigenwallet/core/pull/675) to combat spam. pub amnesty_amount: bitcoin::Amount, + pub tx_refund_burn_fee: bitcoin::Amount, } #[allow(non_snake_case)] @@ -85,6 +87,8 @@ pub struct Message4 { pub tx_cancel_sig: bitcoin::Signature, pub tx_early_refund_sig: bitcoin::Signature, pub tx_refund_amnesty_sig: bitcoin::Signature, + pub tx_refund_burn_sig: bitcoin::Signature, + pub tx_final_amnesty_sig: bitcoin::Signature, } #[allow(clippy::large_enum_variant)] diff --git a/swap-p2p/src/protocols/swap_setup/alice.rs b/swap-p2p/src/protocols/swap_setup/alice.rs index 9ff5de2d21..159d0e958e 100644 --- a/swap-p2p/src/protocols/swap_setup/alice.rs +++ b/swap-p2p/src/protocols/swap_setup/alice.rs @@ -55,6 +55,7 @@ pub struct WalletSnapshot { redeem_fee: bitcoin::Amount, punish_fee: bitcoin::Amount, + refund_burn_fee: bitcoin::Amount, } impl WalletSnapshot { @@ -64,6 +65,7 @@ impl WalletSnapshot { punish_address: bitcoin::Address, redeem_fee: bitcoin::Amount, punish_fee: bitcoin::Amount, + refund_burn_fee: bitcoin::Amount, ) -> Self { Self { unlocked_balance, @@ -72,6 +74,7 @@ impl WalletSnapshot { punish_address, redeem_fee, punish_fee, + refund_burn_fee, } } } @@ -542,6 +545,7 @@ async fn run_swap_setup( wallet_snapshot.punish_address, wallet_snapshot.redeem_fee, wallet_snapshot.punish_fee, + wallet_snapshot.refund_burn_fee, &mut rand::thread_rng(), ); diff --git a/swap-p2p/src/protocols/swap_setup/bob.rs b/swap-p2p/src/protocols/swap_setup/bob.rs index dba374fd0c..245d11bb22 100644 --- a/swap-p2p/src/protocols/swap_setup/bob.rs +++ b/swap-p2p/src/protocols/swap_setup/bob.rs @@ -331,6 +331,7 @@ pub struct NewSwap { pub tx_refund_fee: bitcoin::Amount, pub tx_partial_refund_fee: bitcoin::Amount, pub tx_refund_amnesty_fee: bitcoin::Amount, + pub tx_final_amnesty_fee: bitcoin::Amount, pub tx_cancel_fee: bitcoin::Amount, pub bitcoin_refund_address: bitcoin::Address, } @@ -576,6 +577,7 @@ async fn run_swap_setup( env_config.monero_finality_confirmations, new_swap_request.tx_partial_refund_fee, new_swap_request.tx_refund_amnesty_fee, + new_swap_request.tx_final_amnesty_fee, new_swap_request.tx_refund_fee, new_swap_request.tx_cancel_fee, new_swap_request.tx_lock_fee, diff --git a/swap/src/asb/event_loop.rs b/swap/src/asb/event_loop.rs index 106d6d367d..c4de258e37 100644 --- a/swap/src/asb/event_loop.rs +++ b/swap/src/asb/event_loop.rs @@ -879,6 +879,9 @@ async fn capture_wallet_snapshot( let punish_fee = bitcoin_wallet .estimate_fee(bitcoin::TxPunish::weight(), Some(transfer_amount)) .await?; + let refund_burn_fee = bitcoin_wallet + .estimate_fee(bitcoin::TxRefundBurn::weight(), Some(transfer_amount)) + .await?; Ok(WalletSnapshot::new( unlocked_balance.into(), @@ -886,6 +889,7 @@ async fn capture_wallet_snapshot( punish_address, redeem_fee, punish_fee, + refund_burn_fee, )) } diff --git a/swap/src/protocol/bob/swap.rs b/swap/src/protocol/bob/swap.rs index e7fa04596b..c888e12817 100644 --- a/swap/src/protocol/bob/swap.rs +++ b/swap/src/protocol/bob/swap.rs @@ -12,7 +12,7 @@ use anyhow::{Context as AnyContext, Result, anyhow}; use std::sync::Arc; use std::time::Duration; use swap_core::bitcoin::{ - ExpiredTimelocks, TxCancel, TxFullRefund, TxPartialRefund, TxRefundAmnesty, + ExpiredTimelocks, TxCancel, TxFinalAmnesty, TxFullRefund, TxPartialRefund, TxRefundAmnesty, }; use swap_core::monero::TxHash; use swap_env::env; @@ -125,6 +125,9 @@ async fn next_state( let tx_refund_amnesty_fee = bitcoin_wallet .estimate_fee(TxRefundAmnesty::weight(), Some(btc_amount)) .await?; + let tx_final_amnesty_fee = bitcoin_wallet + .estimate_fee(TxFinalAmnesty::weight(), Some(btc_amount)) + .await?; // Emit an event to tauri that we are negotiating with the maker to lock the Bitcoin event_emitter.emit_swap_progress_event( @@ -142,6 +145,7 @@ async fn next_state( tx_refund_fee, tx_partial_refund_fee, tx_refund_amnesty_fee, + tx_final_amnesty_fee, tx_cancel_fee, bitcoin_refund_address: change_address, }) From 7afbde5712956785887db0fa2fa4e9697368e531 Mon Sep 17 00:00:00 2001 From: einliterflasche Date: Wed, 17 Dec 2025 16:29:55 +0100 Subject: [PATCH 061/113] state-machine(bob): add tx_final_amnesty_fee and tx_refund_burn_fee to all bob states and implement new state transitions --- swap-machine/src/bob/mod.rs | 39 ++++++++++- swap/src/protocol/bob/swap.rs | 126 ++++++++++++++++++---------------- 2 files changed, 106 insertions(+), 59 deletions(-) diff --git a/swap-machine/src/bob/mod.rs b/swap-machine/src/bob/mod.rs index edf1c67e92..412f959b72 100644 --- a/swap-machine/src/bob/mod.rs +++ b/swap-machine/src/bob/mod.rs @@ -663,6 +663,8 @@ impl State2 { tx_refund_fee: self.tx_refund_fee, tx_partial_refund_fee: self.tx_partial_refund_fee, tx_refund_amnesty_fee: self.tx_refund_amnesty_fee, + tx_refund_burn_fee: self.tx_refund_burn_fee, + tx_final_amnesty_fee: self.tx_final_amnesty_fee, tx_cancel_fee: self.tx_cancel_fee, }, self.tx_lock, @@ -705,6 +707,8 @@ pub struct State3 { tx_refund_fee: bitcoin::Amount, tx_partial_refund_fee: Option, tx_refund_amnesty_fee: Option, + tx_refund_burn_fee: Option, + tx_final_amnesty_fee: Option, tx_cancel_fee: bitcoin::Amount, } @@ -756,6 +760,8 @@ impl State3 { tx_cancel_fee: self.tx_cancel_fee, tx_partial_refund_fee: self.tx_partial_refund_fee, tx_refund_amnesty_fee: self.tx_refund_amnesty_fee, + tx_refund_burn_fee: self.tx_refund_burn_fee, + tx_final_amnesty_fee: self.tx_final_amnesty_fee, } } @@ -778,6 +784,8 @@ impl State3 { tx_cancel_fee: self.tx_cancel_fee, tx_partial_refund_fee: self.tx_partial_refund_fee, tx_refund_amnesty_fee: self.tx_refund_amnesty_fee, + tx_refund_burn_fee: self.tx_refund_burn_fee, + tx_final_amnesty_fee: self.tx_final_amnesty_fee, xmr: self.xmr, btc_amnesty_amount: self.btc_amnesty_amount, } @@ -879,6 +887,8 @@ pub struct State4 { tx_refund_fee: bitcoin::Amount, tx_partial_refund_fee: Option, tx_refund_amnesty_fee: Option, + tx_refund_burn_fee: Option, + tx_final_amnesty_fee: Option, tx_cancel_fee: bitcoin::Amount, } @@ -1004,6 +1014,8 @@ impl State4 { btc_amnesty_amount: self.btc_amnesty_amount, tx_partial_refund_fee: self.tx_partial_refund_fee, tx_refund_amnesty_fee: self.tx_refund_amnesty_fee, + tx_refund_burn_fee: self.tx_refund_burn_fee, + tx_final_amnesty_fee: self.tx_final_amnesty_fee, } } @@ -1069,7 +1081,7 @@ pub struct State6 { pub monero_wallet_restore_blockheight: BlockHeight, pub cancel_timelock: CancelTimelock, punish_timelock: PunishTimelock, - remaining_refund_timelock: Option, + pub remaining_refund_timelock: Option, #[serde(with = "address_serde")] refund_address: bitcoin::Address, pub tx_lock: bitcoin::TxLock, @@ -1086,6 +1098,8 @@ pub struct State6 { pub tx_cancel_fee: bitcoin::Amount, tx_partial_refund_fee: Option, tx_refund_amnesty_fee: Option, + tx_refund_burn_fee: Option, + tx_final_amnesty_fee: Option, } impl State6 { @@ -1290,6 +1304,29 @@ impl State6 { )) } + pub fn construct_tx_refund_burn(&self) -> Result { + let tx_partial_refund = self.construct_tx_partial_refund()?; + bitcoin::TxRefundBurn::new( + &tx_partial_refund, + self.A, + self.b.public(), + self.tx_refund_burn_fee.context( + "Can't construct TxRefundBurn because tx_refund_burn_fee is missing", + )?, + ) + } + + pub fn construct_tx_final_amnesty(&self) -> Result { + let tx_refund_burn = self.construct_tx_refund_burn()?; + Ok(bitcoin::TxFinalAmnesty::new( + &tx_refund_burn, + &self.refund_address, + self.tx_final_amnesty_fee.context( + "Can't construct TxFinalAmnesty because tx_final_amnesty_fee is missing", + )?, + )) + } + pub fn construct_tx_early_refund(&self) -> bitcoin::TxEarlyRefund { bitcoin::TxEarlyRefund::new(&self.tx_lock, &self.refund_address, self.tx_refund_fee) } diff --git a/swap/src/protocol/bob/swap.rs b/swap/src/protocol/bob/swap.rs index c888e12817..22f740c3f3 100644 --- a/swap/src/protocol/bob/swap.rs +++ b/swap/src/protocol/bob/swap.rs @@ -983,7 +983,7 @@ async fn next_state( } BobState::BtcPartiallyRefunded(state) => { let has_amnesty_signature = state.tx_refund_amnesty_sig.is_some(); - + event_emitter.emit_swap_progress_event( swap_id, TauriSwapProgressEvent::BtcPartiallyRefunded { @@ -992,44 +992,9 @@ async fn next_state( }, ); - // If we have the amnesty signature, we publish the transaction ourselves. - // This also succeeds if the transaction is published by Alice. - if has_amnesty_signature { - retry("Refund amnesty transaction", || async { - let state = state.clone(); - let transaction = state.signed_amnesty_transaction().context("Couldn't construct Bitcoin amnesty transaction").map_err(backoff::Error::permanent)?; - bitcoin_wallet.ensure_broadcasted(transaction, "Bitcoin amnesty transaction") - .await - .context("Couldn't ensure broadcast of Bitcoin amnesty transaction") - .map_err(backoff::Error::transient)?; - Ok(()) - }, - None, - None - ) - .await - .context("Couldn't publish Bitcoin amnesty transaction")?; - - return Ok(BobState::BtcAmnestyPublished(state)) - } - - // If we don't have the amnesty signature, we have to wait for Alice to publish it. - // TODO: Would a timeout make sense here? Maybe once concurrent swap support landed. - - let tx_amnesty = state.construct_tx_amnesty().context("Couldn't construct Bitcoin amnesty transaction")?; - let subscription = bitcoin_wallet.subscribe_to(Box::new(tx_amnesty.clone())).await; - - retry("Waiting for Bitcoin amnesty transaction to be published by Alice", || async { - subscription.clone() - .wait_until_seen() - .await - .context("Failed to wait for Bitcoin amnesty transaction to be published by Alice") - .map_err(backoff::Error::transient)?; - - Ok(BobState::BtcAmnestyPublished(state.clone())) - }, None, None) - .await - .context("Failed to wait for Bitcoin amnesty transaction to be published by Alice")? + // Transition to waiting state where we race remaining_refund_timelock + // against Alice potentially publishing TxRefundBurn + BobState::WaitingForRemainingRefundTimelockExpiration(state) } BobState::BtcRefunded(state) => { event_emitter.emit_swap_progress_event( @@ -1220,33 +1185,78 @@ async fn next_state( }); BobState::BtcAmnestyConfirmed(state) }, - BobState::WaitingForRemainingRefundTimelockExpiration(_state) => { - // TODO: Wait for timelock expiry while watching for TxRefundBurn - // If timelock expires -> RemainingRefundTimelockExpired - // If TxRefundBurn seen -> BtcRefundBurnPublished - todo!("WaitingForRemainingRefundTimelockExpiration state transition not yet implemented") + BobState::WaitingForRemainingRefundTimelockExpiration(state) => { + // Race between: + // - Remaining refund timelock expiring (so we can publish TxRefundAmnesty) + // - Alice publishing TxRefundBurn (burns the amnesty output) + let tx_partial_refund = state.construct_tx_partial_refund()?; + let tx_refund_burn = state.construct_tx_refund_burn()?; + + let remaining_refund_timelock = state.remaining_refund_timelock.context( + "Can't wait for remaining refund timelock because remaining_refund_timelock is missing", + )?; + + let (tx_partial_refund_status, tx_refund_burn_status) = tokio::join!( + bitcoin_wallet.subscribe_to(Box::new(tx_partial_refund)), + bitcoin_wallet.subscribe_to(Box::new(tx_refund_burn)), + ); + + select! { + // Wait for remaining_refund_timelock confirmations on tx_partial_refund + result = tx_partial_refund_status.wait_until_confirmed_with(remaining_refund_timelock) => { + result?; + tracing::info!("Remaining refund timelock expired, can now publish TxRefundAmnesty"); + BobState::RemainingRefundTimelockExpired(state) + } + // Watch for Alice publishing TxRefundBurn + _ = tx_refund_burn_status.wait_until_seen() => { + tracing::info!("Alice published TxRefundBurn, amnesty output is being burnt"); + BobState::BtcRefundBurnPublished(state) + } + } } - BobState::RemainingRefundTimelockExpired(_state) => { - // TODO: Check if TxRefundBurn was published/confirmed first - // If TxRefundBurn confirmed -> BtcRefundBurnt - // If TxRefundBurn published -> BtcRefundBurnPublished - // Otherwise publish TxRefundAmnesty -> BtcAmnestyPublished - todo!("RemainingRefundTimelockExpired state transition not yet implemented") + BobState::RemainingRefundTimelockExpired(state) => { + // TODO: We should retry this and the check + // First check if TxRefundBurn was seen (we may have missed it while offline) + let tx_refund_burn = state.construct_tx_refund_burn()?; + let tx_refund_burn_status = bitcoin_wallet.status_of_script(&tx_refund_burn).await?; + + if tx_refund_burn_status.has_been_seen() { + tracing::info!("TxRefundBurn was already published, transitioning to BtcRefundBurnPublished"); + return Ok(BobState::BtcRefundBurnPublished(state)); + } + + // TxRefundBurn not published, we can publish TxRefundAmnesty + // Alice always sends the amnesty signature in swap setup + let transaction = state.signed_amnesty_transaction() + .context("Couldn't construct Bitcoin amnesty transaction")?; + bitcoin_wallet.ensure_broadcasted(transaction, "amnesty") + .await + .context("Couldn't ensure broadcast of Bitcoin amnesty transaction")?; + BobState::BtcAmnestyPublished(state) } - BobState::BtcRefundBurnPublished(_state) => { - // TODO: Wait for TxRefundBurn confirmation - // Then -> BtcRefundBurnt - todo!("BtcRefundBurnPublished state transition not yet implemented") + BobState::BtcRefundBurnPublished(state) => { + // Wait for TxRefundBurn confirmation + let tx_refund_burn = state.construct_tx_refund_burn()?; + let subscription = bitcoin_wallet.subscribe_to(Box::new(tx_refund_burn)).await; + + subscription.wait_until_final().await?; + tracing::info!("TxRefundBurn confirmed, amnesty output is burnt"); + BobState::BtcRefundBurnt(state) } BobState::BtcRefundBurnt(state) => { // Terminal state - Alice needs to manually publish TxFinalAmnesty // Similar to BtcPunished, we stop here BobState::BtcRefundBurnt(state) } - BobState::BtcFinalAmnestyPublished(_state) => { - // TODO: Wait for TxFinalAmnesty confirmation - // Then -> BtcFinalAmnestyConfirmed - todo!("BtcFinalAmnestyPublished state transition not yet implemented") + BobState::BtcFinalAmnestyPublished(state) => { + // Wait for TxFinalAmnesty confirmation + let tx_final_amnesty = state.construct_tx_final_amnesty()?; + let subscription = bitcoin_wallet.subscribe_to(Box::new(tx_final_amnesty)).await; + + subscription.wait_until_final().await?; + tracing::info!("TxFinalAmnesty confirmed, received burnt funds back"); + BobState::BtcFinalAmnestyConfirmed(state) } BobState::BtcFinalAmnestyConfirmed(state) => { // Terminal state - we received the burnt funds back From 37c6635af3778be81886584025eb1e2c15a258a1 Mon Sep 17 00:00:00 2001 From: einliterflasche Date: Wed, 17 Dec 2025 17:08:32 +0100 Subject: [PATCH 062/113] swap-setup/state-machine(bob): make alice's sig on TxRefundAmnesty non-optional --- swap-machine/src/alice/mod.rs | 15 ++++++- swap-machine/src/bob/mod.rs | 78 ++++++++++++++++------------------ swap-machine/src/common/mod.rs | 3 +- swap/src/protocol/bob/swap.rs | 4 +- 4 files changed, 54 insertions(+), 46 deletions(-) diff --git a/swap-machine/src/alice/mod.rs b/swap-machine/src/alice/mod.rs index d2d6696e50..c801b7e910 100644 --- a/swap-machine/src/alice/mod.rs +++ b/swap-machine/src/alice/mod.rs @@ -425,9 +425,20 @@ impl State2 { let tx_refund_encsig = self.a.encsign(self.S_b_bitcoin, tx_partial_refund.digest()); let tx_cancel_sig = self.a.sign(tx_cancel.digest()); - // TODO: When to send these? + + // Construct and sign TxRefundAmnesty + let tx_refund_amnesty = swap_core::bitcoin::TxRefundAmnesty::new( + &tx_partial_refund, + &self.refund_address, + self.tx_refund_amnesty_fee + .context("Missing tx_refund_amnesty_fee for new swap")?, + self.remaining_refund_timelock + .context("Missing remaining_refund_timelock for new swap")?, + ); + let tx_refund_amnesty_sig = self.a.sign(tx_refund_amnesty.digest()); + + // TODO: When to send full refund encsig? let tx_full_refund_encsig = None; - let tx_refund_amnesty_sig = None; Ok(Message3 { tx_cancel_sig, diff --git a/swap-machine/src/bob/mod.rs b/swap-machine/src/bob/mod.rs index 412f959b72..44dfe2f916 100644 --- a/swap-machine/src/bob/mod.rs +++ b/swap-machine/src/bob/mod.rs @@ -95,22 +95,26 @@ pub enum BobState { #[serde(untagged)] pub enum RefundSignatures { /// Alice has only signed the partial refund transaction (most cases). + /// Includes the amnesty signature which is always provided in new swaps. Partial { tx_partial_refund_encsig: bitcoin::EncryptedSignature, + tx_refund_amnesty_sig: bitcoin::Signature, }, /// Alice has signed both the partial and full refund transactions. + /// Includes the amnesty signature which is always provided in new swaps. Full { tx_partial_refund_encsig: bitcoin::EncryptedSignature, // Serde rename keeps + untagged + flatten keeps this backwards compatible with old swaps in the database. #[serde(rename = "tx_refund_encsig")] tx_full_refund_encsig: bitcoin::EncryptedSignature, + tx_refund_amnesty_sig: bitcoin::Signature, }, /// Alice has only signed the full refund transaction. /// This is only used to maintain backwards compatibility for older swaps /// from before the partial refund protocol change. /// See [#675](https://github.com/eigenwallet/core/pull/675). Legacy { - // Serde raname keeps + untagged + flatten keeps this backwards compatible with old swaps in the database. + // Serde rename keeps + untagged + flatten keeps this backwards compatible with old swaps in the database. #[serde(rename = "tx_refund_encsig")] tx_full_refund_encsig: bitcoin::EncryptedSignature, }, @@ -474,22 +478,21 @@ impl State1 { )?; } - // Verify the refund amnesty signature if it is present - if let Some(tx_refund_amnesty_sig) = &msg.tx_refund_amnesty_sig { - let tx_refund_amnesty = bitcoin::TxRefundAmnesty::new( - &tx_partial_refund, - &self.refund_address, - self.tx_refund_amnesty_fee - .context("tx_refund_amnesty_fee missing but required to setup swap")?, - self.remaining_refund_timelock - .context("remaining_refund_timelock missing but required to setup swap")?, - ); - bitcoin::verify_sig(&self.A, &tx_refund_amnesty.digest(), tx_refund_amnesty_sig)?; - } + // Verify the refund amnesty signature (always provided in new swaps) + let tx_refund_amnesty = bitcoin::TxRefundAmnesty::new( + &tx_partial_refund, + &self.refund_address, + self.tx_refund_amnesty_fee + .context("tx_refund_amnesty_fee missing but required to setup swap")?, + self.remaining_refund_timelock + .context("remaining_refund_timelock missing but required to setup swap")?, + ); + bitcoin::verify_sig(&self.A, &tx_refund_amnesty.digest(), &msg.tx_refund_amnesty_sig)?; let refund_signatures = RefundSignatures::from_possibly_full_refund_sig( msg.tx_partial_refund_encsig, msg.tx_full_refund_encsig, + msg.tx_refund_amnesty_sig, ); Ok(State2 { A: self.A, @@ -509,7 +512,6 @@ impl State1 { tx_lock: self.tx_lock, tx_cancel_sig_a: msg.tx_cancel_sig, refund_signatures, - tx_refund_amnesty_sig: msg.tx_refund_amnesty_sig, min_monero_confirmations: self.min_monero_confirmations, tx_redeem_fee: self.tx_redeem_fee, tx_refund_fee: self.tx_refund_fee, @@ -549,10 +551,6 @@ pub struct State2 { /// It boils down to the same json except that it now may also contain a partial refund signature. #[serde(flatten)] refund_signatures: RefundSignatures, - /// This field was added in [#675](https://github.com/eigenwallet/core/pull/675). - /// It allows Bob to retrieve the refund fee introduced in the PR. - /// This signature is voluntarily revealed by alice. - tx_refund_amnesty_sig: Option, min_monero_confirmations: u64, tx_redeem_fee: bitcoin::Amount, tx_punish_fee: bitcoin::Amount, @@ -657,7 +655,6 @@ impl State2 { tx_lock: self.tx_lock.clone(), tx_cancel_sig_a: self.tx_cancel_sig_a, refund_signatures: self.refund_signatures, - tx_refund_amnesty_sig: self.tx_refund_amnesty_sig, min_monero_confirmations: self.min_monero_confirmations, tx_redeem_fee: self.tx_redeem_fee, tx_refund_fee: self.tx_refund_fee, @@ -698,10 +695,6 @@ pub struct State3 { /// It boils down to the same json except that it now may also contain a partial refund signature. #[serde(flatten)] refund_signatures: RefundSignatures, - /// This field was added in [#675](https://github.com/eigenwallet/core/pull/675). - /// It allows Bob to retrieve the refund fee introduced in the PR. - /// This signature is voluntarily revealed by alice. - tx_refund_amnesty_sig: Option, min_monero_confirmations: u64, tx_redeem_fee: bitcoin::Amount, tx_refund_fee: bitcoin::Amount, @@ -752,7 +745,6 @@ impl State3 { tx_lock: self.tx_lock, tx_cancel_sig_a: self.tx_cancel_sig_a, refund_signatures: self.refund_signatures, - tx_refund_amnesty_sig: self.tx_refund_amnesty_sig, monero_wallet_restore_blockheight, lock_transfer_proof, tx_redeem_fee: self.tx_redeem_fee, @@ -779,7 +771,6 @@ impl State3 { tx_lock: self.tx_lock.clone(), tx_cancel_sig_a: self.tx_cancel_sig_a.clone(), refund_signatures: self.refund_signatures.clone(), - tx_refund_amnesty_sig: self.tx_refund_amnesty_sig.clone(), tx_refund_fee: self.tx_refund_fee, tx_cancel_fee: self.tx_cancel_fee, tx_partial_refund_fee: self.tx_partial_refund_fee, @@ -877,10 +868,6 @@ pub struct State4 { /// It boils down to the same json except that it now may also contain a partial refund signature. #[serde(flatten)] refund_signatures: RefundSignatures, - /// This field was added in [#675](https://github.com/eigenwallet/core/pull/675). - /// It allows Bob to retrieve the refund fee introduced in the PR. - /// This signature is voluntarily revealed by alice. - tx_refund_amnesty_sig: Option, monero_wallet_restore_blockheight: BlockHeight, lock_transfer_proof: TransferProof, tx_redeem_fee: bitcoin::Amount, @@ -1007,7 +994,6 @@ impl State4 { tx_lock: self.tx_lock, tx_cancel_sig_a: self.tx_cancel_sig_a, refund_signatures: self.refund_signatures, - tx_refund_amnesty_sig: self.tx_refund_amnesty_sig, tx_refund_fee: self.tx_refund_fee, tx_cancel_fee: self.tx_cancel_fee, xmr: self.xmr, @@ -1090,10 +1076,6 @@ pub struct State6 { /// It boils down to the same json except that it now may also contain a partial refund signature. #[serde(flatten)] pub refund_signatures: RefundSignatures, - /// This field was added in [#675](https://github.com/eigenwallet/core/pull/675). - /// It allows Bob to retrieve the refund fee introduced in the PR. - /// This signature is voluntarily revealed by alice. - pub tx_refund_amnesty_sig: Option, pub tx_refund_fee: bitcoin::Amount, pub tx_cancel_fee: bitcoin::Amount, tx_partial_refund_fee: Option, @@ -1278,7 +1260,7 @@ impl State6 { pub fn signed_amnesty_transaction(&self) -> Result { let tx_amnesty = self.construct_tx_amnesty()?; - let sig_a = self.tx_refund_amnesty_sig.clone().context( + let sig_a = self.refund_signatures.tx_refund_amnesty_sig().context( "Can't sign amnesty transaction because Alice's amnesty signature is missing", )?; let sig_b = self.b.sign(tx_amnesty.digest()); @@ -1373,25 +1355,22 @@ impl RefundSignatures { pub fn from_possibly_full_refund_sig( partial_refund_encsig: bitcoin::EncryptedSignature, full_refund_encsig: Option, + refund_amnesty_sig: bitcoin::Signature, ) -> Self { if let Some(full_refund_encsig) = full_refund_encsig { Self::Full { tx_partial_refund_encsig: partial_refund_encsig, tx_full_refund_encsig: full_refund_encsig, + tx_refund_amnesty_sig: refund_amnesty_sig, } } else { Self::Partial { tx_partial_refund_encsig: partial_refund_encsig, + tx_refund_amnesty_sig: refund_amnesty_sig, } } } - pub fn from_partial_refund_sig(partial_refund_encsig: bitcoin::EncryptedSignature) -> Self { - Self::Partial { - tx_partial_refund_encsig: partial_refund_encsig, - } - } - pub fn tx_full_refund_encsig(&self) -> Option { match self { RefundSignatures::Partial { .. } => None, @@ -1409,6 +1388,7 @@ impl RefundSignatures { match self { RefundSignatures::Partial { tx_partial_refund_encsig, + .. } => Some(tx_partial_refund_encsig.clone()), RefundSignatures::Full { tx_partial_refund_encsig, @@ -1418,6 +1398,22 @@ impl RefundSignatures { } } + /// Returns Alice's signature for the amnesty transaction. + /// Only available for new swaps (Partial/Full variants), not Legacy swaps. + pub fn tx_refund_amnesty_sig(&self) -> Option { + match self { + RefundSignatures::Partial { + tx_refund_amnesty_sig, + .. + } => Some(tx_refund_amnesty_sig.clone()), + RefundSignatures::Full { + tx_refund_amnesty_sig, + .. + } => Some(tx_refund_amnesty_sig.clone()), + RefundSignatures::Legacy { .. } => None, + } + } + pub fn has_full_refund_encsig(&self) -> bool { self.tx_full_refund_encsig().is_some() } diff --git a/swap-machine/src/common/mod.rs b/swap-machine/src/common/mod.rs index 75754a3db3..68c596555f 100644 --- a/swap-machine/src/common/mod.rs +++ b/swap-machine/src/common/mod.rs @@ -77,7 +77,8 @@ pub struct Message3 { /// have to. pub tx_partial_refund_encsig: bitcoin::EncryptedSignature, pub tx_full_refund_encsig: Option, - pub tx_refund_amnesty_sig: Option, + /// Alice's signature for the amnesty transaction - always provided. + pub tx_refund_amnesty_sig: bitcoin::Signature, } #[allow(non_snake_case)] diff --git a/swap/src/protocol/bob/swap.rs b/swap/src/protocol/bob/swap.rs index 22f740c3f3..1f66f22292 100644 --- a/swap/src/protocol/bob/swap.rs +++ b/swap/src/protocol/bob/swap.rs @@ -955,7 +955,7 @@ async fn next_state( swap_id, TauriSwapProgressEvent::BtcPartialRefundPublished { btc_partial_refund_txid: state.construct_tx_partial_refund()?.txid(), - has_amnesty_signature: state.tx_refund_amnesty_sig.is_some(), + has_amnesty_signature: state.refund_signatures.tx_refund_amnesty_sig().is_some(), }, ); @@ -982,7 +982,7 @@ async fn next_state( } } BobState::BtcPartiallyRefunded(state) => { - let has_amnesty_signature = state.tx_refund_amnesty_sig.is_some(); + let has_amnesty_signature = state.refund_signatures.tx_refund_amnesty_sig().is_some(); event_emitter.emit_swap_progress_event( swap_id, From 2e6175f714a4848af836e176b58bfc76bff56add Mon Sep 17 00:00:00 2001 From: einliterflasche Date: Wed, 17 Dec 2025 17:37:24 +0100 Subject: [PATCH 063/113] fix tests: pass remaining_refund_timelock --- swap-machine/src/lib.rs | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/swap-machine/src/lib.rs b/swap-machine/src/lib.rs index f00aad1399..ceea4c2a49 100644 --- a/swap-machine/src/lib.rs +++ b/swap-machine/src/lib.rs @@ -55,6 +55,7 @@ mod tests { punish_address, tx_redeem_fee, tx_punish_fee, + spending_fee, &mut OsRng, ); @@ -65,12 +66,14 @@ mod tests { xmr_amount, CancelTimelock::new(config.bitcoin_cancel_timelock), PunishTimelock::new(config.bitcoin_punish_timelock), + RemainingRefundTimelock::new(config.bitcoin_remaining_refund_timelock), bob_wallet.new_address().await.unwrap(), config.monero_finality_confirmations, spending_fee, spending_fee, spending_fee, spending_fee, + spending_fee, tx_lock_fee, ); @@ -168,6 +171,7 @@ mod tests { punish_address, tx_redeem_fee, tx_punish_fee, + spending_fee, &mut OsRng, ); @@ -178,6 +182,7 @@ mod tests { xmr_amount, CancelTimelock::new(config.bitcoin_cancel_timelock), PunishTimelock::new(config.bitcoin_punish_timelock), + RemainingRefundTimelock::new(config.bitcoin_remaining_refund_timelock), bob_wallet.new_address().await.unwrap(), config.monero_finality_confirmations, spending_fee, @@ -185,6 +190,7 @@ mod tests { spending_fee, spending_fee, spending_fee, + spending_fee, ); // Complete the state machine up to State3 From 06478ebdf9e1c3841c0386b2a52d615515cfa0bd Mon Sep 17 00:00:00 2001 From: einliterflasche Date: Wed, 17 Dec 2025 20:30:20 +0100 Subject: [PATCH 064/113] alice: sent encsig for TxFullRefund during swap setup when btc_amnesty_amount == 0 --- swap-machine/src/alice/mod.rs | 10 ++++++++-- swap/src/asb/event_loop.rs | 6 ++++++ 2 files changed, 14 insertions(+), 2 deletions(-) diff --git a/swap-machine/src/alice/mod.rs b/swap-machine/src/alice/mod.rs index 4c674d316b..a734dc23d8 100644 --- a/swap-machine/src/alice/mod.rs +++ b/swap-machine/src/alice/mod.rs @@ -439,8 +439,14 @@ impl State2 { ); let tx_refund_amnesty_sig = self.a.sign(tx_refund_amnesty.digest()); - // TODO: When to send full refund encsig? - let tx_full_refund_encsig = None; + // Send full refund encsig when btc_amnesty_amount is None or ZERO (ratio = 1.0) + let tx_full_refund = TxFullRefund::new(&tx_cancel, &self.refund_address, self.tx_refund_fee); + let tx_full_refund_encsig = + if self.btc_amnesty_amount.unwrap_or(bitcoin::Amount::ZERO) == bitcoin::Amount::ZERO { + Some(self.a.encsign(self.S_b_bitcoin, tx_full_refund.digest())) + } else { + None + }; Ok(Message3 { tx_cancel_sig, diff --git a/swap/src/asb/event_loop.rs b/swap/src/asb/event_loop.rs index c4de258e37..058099f05f 100644 --- a/swap/src/asb/event_loop.rs +++ b/swap/src/asb/event_loop.rs @@ -832,10 +832,16 @@ impl EventLoopHandle { /// For a new swap of `swap_amount`, this function calculates how much /// Bitcoin should go into the amnesty-lock incase of a refund. +/// Returns ZERO when taker_refund_ratio is 1.0 (100%), indicating full refund. fn apply_bitcoin_amnesty_policy( swap_amount: bitcoin::Amount, refund_policy: &RefundPolicy, ) -> Result { + // When ratio is 1.0, no amnesty - use full refund path + if refund_policy.taker_refund_ratio == Decimal::ONE { + return Ok(bitcoin::Amount::ZERO); + } + let btc_amnesty_ratio = Decimal::ONE .checked_sub(refund_policy.taker_refund_ratio) .context("can't have refund ration > 1")?; From 6cfe0092aab28543d00cbf619c7b2a5690a963e2 Mon Sep 17 00:00:00 2001 From: einliterflasche Date: Wed, 17 Dec 2025 20:30:54 +0100 Subject: [PATCH 065/113] bob: make fee fiels in state puiblic --- swap-machine/src/bob/mod.rs | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/swap-machine/src/bob/mod.rs b/swap-machine/src/bob/mod.rs index bdc3a8aba2..a5a8745a5f 100644 --- a/swap-machine/src/bob/mod.rs +++ b/swap-machine/src/bob/mod.rs @@ -1084,7 +1084,7 @@ pub struct State6 { pub xmr: monero::Amount, /// How much of the locked Bitcoin will stay locked in case of a partial refund. /// May still be retrieve by publishing the `TxAmnesty` transaction. - btc_amnesty_amount: Option, + pub btc_amnesty_amount: Option, pub monero_wallet_restore_blockheight: BlockHeight, pub cancel_timelock: CancelTimelock, punish_timelock: PunishTimelock, @@ -1099,10 +1099,10 @@ pub struct State6 { pub refund_signatures: RefundSignatures, pub tx_refund_fee: bitcoin::Amount, pub tx_cancel_fee: bitcoin::Amount, - tx_partial_refund_fee: Option, - tx_refund_amnesty_fee: Option, - tx_refund_burn_fee: Option, - tx_final_amnesty_fee: Option, + pub tx_partial_refund_fee: Option, + pub tx_refund_amnesty_fee: Option, + pub tx_refund_burn_fee: Option, + pub tx_final_amnesty_fee: Option, } impl State6 { From 0276e8c96319abe8e6213b97d781a848bbda4df7 Mon Sep 17 00:00:00 2001 From: einliterflasche Date: Wed, 17 Dec 2025 20:31:55 +0100 Subject: [PATCH 066/113] tests: add integration test for partial refund + amnesty : --- swap/tests/harness/mod.rs | 155 ++++++++++++++++++ .../partial_refund_bob_claims_amnesty.rs | 53 ++++++ 2 files changed, 208 insertions(+) create mode 100644 swap/tests/partial_refund_bob_claims_amnesty.rs diff --git a/swap/tests/harness/mod.rs b/swap/tests/harness/mod.rs index 288ecd3965..f5171906e0 100644 --- a/swap/tests/harness/mod.rs +++ b/swap/tests/harness/mod.rs @@ -904,6 +904,121 @@ impl TestContext { .unwrap(); } + pub async fn assert_bob_partially_refunded(&self, state: BobState) { + self.bob_bitcoin_wallet.sync().await.unwrap(); + + let (lock_tx_id, cancel_fee, partial_refund_fee, amnesty_amount) = match state { + BobState::BtcPartiallyRefunded(state6) => ( + state6.tx_lock_id(), + state6.tx_cancel_fee, + state6.tx_partial_refund_fee.expect("partial refund fee"), + state6.btc_amnesty_amount.expect("amnesty amount"), + ), + _ => panic!("Bob is not in btc partially refunded state: {:?}", state), + }; + let lock_tx_bitcoin_fee = self + .bob_bitcoin_wallet + .transaction_fee(lock_tx_id) + .await + .unwrap(); + + let btc_balance_after_swap = self.bob_bitcoin_wallet.balance().await.unwrap(); + let expected_balance = self.bob_starting_balances.btc + - lock_tx_bitcoin_fee + - cancel_fee + - partial_refund_fee + - amnesty_amount; + + assert_eq!(btc_balance_after_swap, expected_balance); + } + + pub async fn assert_bob_amnesty_received(&self, state: BobState) { + self.bob_bitcoin_wallet.sync().await.unwrap(); + + let (lock_tx_id, cancel_fee, partial_refund_fee, amnesty_fee) = match state { + BobState::BtcAmnestyConfirmed(state6) => ( + state6.tx_lock_id(), + state6.tx_cancel_fee, + state6.tx_partial_refund_fee.expect("partial refund fee"), + state6.tx_refund_amnesty_fee.expect("amnesty fee"), + ), + _ => panic!("Bob is not in btc amnesty confirmed state: {:?}", state), + }; + let lock_tx_bitcoin_fee = self + .bob_bitcoin_wallet + .transaction_fee(lock_tx_id) + .await + .unwrap(); + + let btc_balance_after_swap = self.bob_bitcoin_wallet.balance().await.unwrap(); + // Bob gets full amount back minus all the fees + let expected_balance = self.bob_starting_balances.btc + - lock_tx_bitcoin_fee + - cancel_fee + - partial_refund_fee + - amnesty_fee; + + assert_eq!(btc_balance_after_swap, expected_balance); + } + + pub async fn assert_bob_refund_burnt(&self, state: BobState) { + self.bob_bitcoin_wallet.sync().await.unwrap(); + + let (lock_tx_id, cancel_fee, partial_refund_fee, amnesty_amount) = match state { + BobState::BtcRefundBurnt(state6) => ( + state6.tx_lock_id(), + state6.tx_cancel_fee, + state6.tx_partial_refund_fee.expect("partial refund fee"), + state6.btc_amnesty_amount.expect("amnesty amount"), + ), + _ => panic!("Bob is not in btc refund burnt state: {:?}", state), + }; + let lock_tx_bitcoin_fee = self + .bob_bitcoin_wallet + .transaction_fee(lock_tx_id) + .await + .unwrap(); + + let btc_balance_after_swap = self.bob_bitcoin_wallet.balance().await.unwrap(); + // Bob lost the amnesty amount (it was burnt) + let expected_balance = self.bob_starting_balances.btc + - lock_tx_bitcoin_fee + - cancel_fee + - partial_refund_fee + - amnesty_amount; + + assert_eq!(btc_balance_after_swap, expected_balance); + } + + pub async fn assert_bob_final_amnesty_received(&self, state: BobState) { + self.bob_bitcoin_wallet.sync().await.unwrap(); + + let (lock_tx_id, cancel_fee, partial_refund_fee, final_amnesty_fee) = match state { + BobState::BtcFinalAmnestyConfirmed(state6) => ( + state6.tx_lock_id(), + state6.tx_cancel_fee, + state6.tx_partial_refund_fee.expect("partial refund fee"), + state6.tx_final_amnesty_fee.expect("final amnesty fee"), + ), + _ => panic!("Bob is not in btc final amnesty confirmed state: {:?}", state), + }; + let lock_tx_bitcoin_fee = self + .bob_bitcoin_wallet + .transaction_fee(lock_tx_id) + .await + .unwrap(); + + let btc_balance_after_swap = self.bob_bitcoin_wallet.balance().await.unwrap(); + // Bob gets full amount back via final amnesty + let expected_balance = self.bob_starting_balances.btc + - lock_tx_bitcoin_fee + - cancel_fee + - partial_refund_fee + - final_amnesty_fee; + + assert_eq!(btc_balance_after_swap, expected_balance); + } + fn alice_redeemed_xmr_balance(&self) -> monero::Amount { self.alice_starting_balances.xmr - self.xmr_amount } @@ -1218,6 +1333,10 @@ pub mod alice_run_until { pub fn is_btc_redeemed(state: &AliceState) -> bool { matches!(state, AliceState::BtcRedeemed { .. }) } + + pub fn is_btc_partially_refunded(state: &AliceState) -> bool { + matches!(state, AliceState::BtcPartiallyRefunded { .. }) + } } pub mod bob_run_until { @@ -1238,6 +1357,30 @@ pub mod bob_run_until { pub fn is_encsig_sent(state: &BobState) -> bool { matches!(state, BobState::EncSigSent(..)) } + + pub fn is_btc_partially_refunded(state: &BobState) -> bool { + matches!(state, BobState::BtcPartiallyRefunded(..)) + } + + pub fn is_waiting_for_remaining_refund_timelock(state: &BobState) -> bool { + matches!(state, BobState::WaitingForRemainingRefundTimelockExpiration(..)) + } + + pub fn is_remaining_refund_timelock_expired(state: &BobState) -> bool { + matches!(state, BobState::RemainingRefundTimelockExpired(..)) + } + + pub fn is_btc_amnesty_confirmed(state: &BobState) -> bool { + matches!(state, BobState::BtcAmnestyConfirmed(..)) + } + + pub fn is_btc_refund_burnt(state: &BobState) -> bool { + matches!(state, BobState::BtcRefundBurnt(..)) + } + + pub fn is_btc_final_amnesty_confirmed(state: &BobState) -> bool { + matches!(state, BobState::BtcFinalAmnestyConfirmed(..)) + } } pub struct SlowCancelConfig; @@ -1273,3 +1416,15 @@ impl GetConfig for FastPunishConfig { } } } + +pub struct FastAmnestyConfig; + +impl GetConfig for FastAmnestyConfig { + fn get_config() -> Config { + Config { + bitcoin_cancel_timelock: CancelTimelock::new(10).into(), + bitcoin_remaining_refund_timelock: 3, + ..env::Regtest::get_config() + } + } +} diff --git a/swap/tests/partial_refund_bob_claims_amnesty.rs b/swap/tests/partial_refund_bob_claims_amnesty.rs new file mode 100644 index 0000000000..4c1c0c7c83 --- /dev/null +++ b/swap/tests/partial_refund_bob_claims_amnesty.rs @@ -0,0 +1,53 @@ +pub mod harness; + +use harness::alice_run_until::is_xmr_lock_transaction_sent; +use harness::FastAmnestyConfig; +use rust_decimal::Decimal; +use swap::asb::FixedRate; +use swap::protocol::alice::AliceState; +use swap::protocol::{alice, bob}; +use swap_env::config::RefundPolicy; + +/// Bob locks Btc and Alice locks Xmr. Alice does not act so Bob does a partial +/// refund, waits for the remaining refund timelock, and then claims the amnesty. +#[tokio::test] +async fn given_partial_refund_bob_claims_amnesty_after_timelock() { + // Use 95% refund ratio - Bob gets 95% immediately, 5% locked in amnesty + let refund_policy = Some(RefundPolicy { + taker_refund_ratio: Decimal::new(95, 2), // 0.95 = 95% + }); + + harness::setup_test(FastAmnestyConfig, None, refund_policy, |mut ctx| async move { + let (bob_swap, _) = ctx.bob_swap().await; + let bob_swap = tokio::spawn(bob::run(bob_swap)); + + let alice_swap = ctx.alice_next_swap().await; + let alice_swap = tokio::spawn(alice::run_until( + alice_swap, + is_xmr_lock_transaction_sent, + FixedRate::default(), + )); + + // Alice finishes first (just sends XMR lock and stops) + let alice_state = alice_swap.await??; + assert!(matches!( + alice_state, + AliceState::XmrLockTransactionSent { .. } + )); + + // Bob takes longer: cancel timelock -> partial refund -> remaining refund timelock -> amnesty + let bob_state = bob_swap.await??; + ctx.assert_bob_amnesty_received(bob_state).await; + + // Restart Alice so she can refund her XMR + ctx.restart_alice().await; + let alice_swap = ctx.alice_next_swap().await; + let alice_swap = tokio::spawn(alice::run(alice_swap, FixedRate::default())); + + let alice_state = alice_swap.await??; + ctx.assert_alice_refunded(alice_state).await; + + Ok(()) + }) + .await; +} From 92c7ee37a7a7a1d4a75bfdc632d2b10a8536f949 Mon Sep 17 00:00:00 2001 From: einliterflasche Date: Wed, 17 Dec 2025 20:38:27 +0100 Subject: [PATCH 067/113] alice: don't try to publish TxRefundAmnesty --- swap/src/protocol/alice/swap.rs | 13 +++---------- 1 file changed, 3 insertions(+), 10 deletions(-) diff --git a/swap/src/protocol/alice/swap.rs b/swap/src/protocol/alice/swap.rs index 2185471ac0..37a26839c2 100644 --- a/swap/src/protocol/alice/swap.rs +++ b/swap/src/protocol/alice/swap.rs @@ -667,16 +667,9 @@ where state3, monero_wallet_restore_blockheight, } => { - // TODO: Publish amnesty transaction/send amnesty tx sig / decide against it - // TODO: retry, maybe in background? we don't want this to block us from refunding the Monero though - let tx_refund_amnesty = state3 - .signed_bitcoin_amnesty_transaction() - .context("Couldn't construct Bitcoin refund amnesty transaction")?; - - bitcoin_wallet - .ensure_broadcasted(tx_refund_amnesty, "refund amnesty") - .await?; - + // Bob has the pre-signed TxRefundAmnesty from swap setup and can + // publish it himself after the remaining refund timelock expires. + // TODO: implement system for publishing TxRefundBurn at this point AliceState::XmrRefundable { monero_wallet_restore_blockheight, transfer_proof, From 840d029879a816a94461c248df2ccbe2a1c9bc52 Mon Sep 17 00:00:00 2001 From: einliterflasche Date: Wed, 17 Dec 2025 20:43:40 +0100 Subject: [PATCH 068/113] add new integration test to ci --- .github/workflows/ci.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 97649afc23..82a97c4e40 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -182,6 +182,8 @@ jobs: test_name: alice_broken_wallet_rpc_after_started_btc_early_refund - package: swap test_name: happy_path_alice_does_not_send_transfer_proof + - package: swap + test_name: partial_refund_bob_claims_amnesty - package: monero-tests test_name: reserve_proof - package: monero-tests From a91ac5f2a5d62012fc0752552634c90aec4525cc Mon Sep 17 00:00:00 2001 From: einliterflasche Date: Thu, 18 Dec 2025 08:09:25 +0100 Subject: [PATCH 069/113] fmt --- swap-machine/src/alice/mod.rs | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) diff --git a/swap-machine/src/alice/mod.rs b/swap-machine/src/alice/mod.rs index a734dc23d8..30291b18de 100644 --- a/swap-machine/src/alice/mod.rs +++ b/swap-machine/src/alice/mod.rs @@ -440,7 +440,8 @@ impl State2 { let tx_refund_amnesty_sig = self.a.sign(tx_refund_amnesty.digest()); // Send full refund encsig when btc_amnesty_amount is None or ZERO (ratio = 1.0) - let tx_full_refund = TxFullRefund::new(&tx_cancel, &self.refund_address, self.tx_refund_fee); + let tx_full_refund = + TxFullRefund::new(&tx_cancel, &self.refund_address, self.tx_refund_fee); let tx_full_refund_encsig = if self.btc_amnesty_amount.unwrap_or(bitcoin::Amount::ZERO) == bitcoin::Amount::ZERO { Some(self.a.encsign(self.S_b_bitcoin, tx_full_refund.digest())) @@ -536,12 +537,8 @@ impl State2 { )?; // Check if the provided signature by Bob is valid for the transaction - swap_core::bitcoin::verify_sig( - &self.B, - &tx_refund_burn.digest(), - &msg.tx_refund_burn_sig, - ) - .context("Failed to verify refund burn transaction")?; + swap_core::bitcoin::verify_sig(&self.B, &tx_refund_burn.digest(), &msg.tx_refund_burn_sig) + .context("Failed to verify refund burn transaction")?; // Create TxFinalAmnesty ourself let tx_final_amnesty = TxFinalAmnesty::new( From 15ed6905e73445870a90696402525d8098a4be4d Mon Sep 17 00:00:00 2001 From: einliterflasche Date: Thu, 18 Dec 2025 08:59:29 +0100 Subject: [PATCH 070/113] controller: output table when running get-swaps --- Cargo.lock | 1 + swap-controller/Cargo.toml | 1 + swap-controller/src/main.rs | 16 +++++++++----- swap-controller/src/util.rs | 43 +++++++++++++++++++++++++++++++++++++ 4 files changed, 56 insertions(+), 5 deletions(-) create mode 100644 swap-controller/src/util.rs diff --git a/Cargo.lock b/Cargo.lock index ac7415cbd1..b7cffb695e 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -10744,6 +10744,7 @@ version = "3.6.1" dependencies = [ "anyhow", "clap 4.5.53", + "comfy-table", "jsonrpsee", "monero", "rustyline", diff --git a/swap-controller/Cargo.toml b/swap-controller/Cargo.toml index ef6397c4f1..c3ad61cdf4 100644 --- a/swap-controller/Cargo.toml +++ b/swap-controller/Cargo.toml @@ -10,6 +10,7 @@ path = "src/main.rs" [dependencies] anyhow = { workspace = true } clap = { version = "4", features = ["derive"] } +comfy-table = "7.2.1" jsonrpsee = { workspace = true, features = ["client-core", "http-client"] } monero = { workspace = true } rustyline = "17.0.0" diff --git a/swap-controller/src/main.rs b/swap-controller/src/main.rs index cc03eaf3b9..9f697acfcb 100644 --- a/swap-controller/src/main.rs +++ b/swap-controller/src/main.rs @@ -1,9 +1,11 @@ mod cli; mod repl; +mod util; use clap::Parser; use cli::{Cli, Cmd}; use swap_controller_api::{AsbApiClient, MoneroSeedResponse}; +use util::ToTable; #[tokio::main] async fn main() -> anyhow::Result<()> { @@ -76,13 +78,17 @@ async fn dispatch(cmd: Cmd, client: impl AsbApiClient) -> anyhow::Result<()> { } Cmd::GetSwaps => { let swaps = client.get_swaps().await?; + + // Create a table containing the swap id and state + let mut table = swaps.iter().map(|swap| (&swap.id, &swap.state)).to_table(); + if swaps.is_empty() { - println!("No swaps found"); - } else { - for swap in swaps { - println!("{}: {}", swap.id, swap.state); - } + table.add_row(["No swaps found"]); } + + table.set_header(["ID", "State"]); + + println!("{table}"); } Cmd::BitcoinSeed => { let response = client.bitcoin_seed().await?; diff --git a/swap-controller/src/util.rs b/swap-controller/src/util.rs new file mode 100644 index 0000000000..c7838ee157 --- /dev/null +++ b/swap-controller/src/util.rs @@ -0,0 +1,43 @@ +//! Utilities for easily generating tables. + +use comfy_table::Table; +use std::fmt::Display; + +pub trait ToTable { + fn to_table(self) -> Table; +} + +trait TupleToRow { + fn to_row(self) -> Vec; +} + +impl TupleToRow for (A,) { + fn to_row(self) -> Vec { + vec![self.0.to_string()] + } +} + +impl TupleToRow for (A, B) { + fn to_row(self) -> Vec { + vec![self.0.to_string(), self.1.to_string()] + } +} + +impl TupleToRow for (A, B, C) { + fn to_row(self) -> Vec { + vec![self.0.to_string(), self.1.to_string(), self.2.to_string()] + } +} + +impl ToTable for I +where + I: IntoIterator, +{ + fn to_table(self) -> Table { + let mut table = Table::new(); + for item in self { + table.add_row(item.to_row()); + } + table + } +} From 8a4ac957f58c5e361ea836d9dedb1312b662439b Mon Sep 17 00:00:00 2001 From: einliterflasche Date: Thu, 18 Dec 2025 16:47:50 +0100 Subject: [PATCH 071/113] controller: display more information about swaps --- justfile | 2 +- swap-controller-api/src/lib.rs | 14 +++++- swap-controller/src/main.rs | 33 ++++++++++--- swap-controller/src/util.rs | 43 ---------------- swap-core/src/bitcoin/cancel.rs | 7 +-- swap-core/src/bitcoin/final_amnesty.rs | 6 +-- swap-core/src/bitcoin/refund_burn.rs | 11 +++-- swap-machine/src/alice/mod.rs | 3 ++ swap-machine/src/common/mod.rs | 20 ++++++++ swap/src/asb/rpc/server.rs | 68 +++++++++++++++++++------- swap/src/database/sqlite.rs | 27 +++++++--- 11 files changed, 146 insertions(+), 88 deletions(-) delete mode 100644 swap-controller/src/util.rs diff --git a/justfile b/justfile index 198b5e0eb9..8621457913 100644 --- a/justfile +++ b/justfile @@ -62,7 +62,7 @@ tests: # Run docker tests (e.g., "just docker_test happy_path_alice_developer_tip") docker_test test_name: - cargo test --package swap --test {{test_name}} -- --nocapture + RUST_BACKTRACE=1 cargo test --package swap --test {{test_name}} -- --nocapture docker_test_happy_path: just docker_test happy_path diff --git a/swap-controller-api/src/lib.rs b/swap-controller-api/src/lib.rs index 6790dec75d..afc1f4de28 100644 --- a/swap-controller-api/src/lib.rs +++ b/swap-controller-api/src/lib.rs @@ -64,10 +64,22 @@ pub struct RegistrationStatusResponse { pub registrations: Vec, } +// TODO: we should not need both this and asb::SwapDetails #[derive(Serialize, Deserialize, Debug, Clone)] pub struct Swap { - pub id: String, + pub swap_id: String, + pub start_date: String, pub state: String, + pub btc_lock_txid: String, + #[serde(with = "bitcoin::amount::serde::as_sat")] + pub btc_amount: bitcoin::Amount, + /// Monero amount in piconero + pub xmr_amount: u64, + /// Exchange rate: BTC per XMR (amount of BTC needed to buy 1 XMR) + #[serde(with = "bitcoin::amount::serde::as_sat")] + pub exchange_rate: bitcoin::Amount, + pub peer_id: String, + pub completed: bool, } #[derive(Serialize, Deserialize, Debug, Clone)] diff --git a/swap-controller/src/main.rs b/swap-controller/src/main.rs index 9f697acfcb..042b0b6b9e 100644 --- a/swap-controller/src/main.rs +++ b/swap-controller/src/main.rs @@ -1,11 +1,9 @@ mod cli; mod repl; -mod util; use clap::Parser; use cli::{Cli, Cmd}; use swap_controller_api::{AsbApiClient, MoneroSeedResponse}; -use util::ToTable; #[tokio::main] async fn main() -> anyhow::Result<()> { @@ -79,15 +77,38 @@ async fn dispatch(cmd: Cmd, client: impl AsbApiClient) -> anyhow::Result<()> { Cmd::GetSwaps => { let swaps = client.get_swaps().await?; - // Create a table containing the swap id and state - let mut table = swaps.iter().map(|swap| (&swap.id, &swap.state)).to_table(); + let mut table = comfy_table::Table::new(); + table.set_header([ + "ID", + "Started", + "State", + "BTC Lock TxID", + "BTC", + "XMR", + "Rate (BTC/XMR)", + "Peer ID", + "Completed", + ]); if swaps.is_empty() { table.add_row(["No swaps found"]); + } else { + for swap in &swaps { + let xmr = monero::Amount::from_pico(swap.xmr_amount); + table.add_row([ + &swap.swap_id, + &swap.start_date, + &swap.state, + &swap.btc_lock_txid, + &swap.btc_amount.to_string(), + &format!("{:.12} XMR", xmr.as_xmr()), + &swap.exchange_rate.to_string(), + &swap.peer_id, + &swap.completed.to_string(), + ]); + } } - table.set_header(["ID", "State"]); - println!("{table}"); } Cmd::BitcoinSeed => { diff --git a/swap-controller/src/util.rs b/swap-controller/src/util.rs deleted file mode 100644 index c7838ee157..0000000000 --- a/swap-controller/src/util.rs +++ /dev/null @@ -1,43 +0,0 @@ -//! Utilities for easily generating tables. - -use comfy_table::Table; -use std::fmt::Display; - -pub trait ToTable { - fn to_table(self) -> Table; -} - -trait TupleToRow { - fn to_row(self) -> Vec; -} - -impl TupleToRow for (A,) { - fn to_row(self) -> Vec { - vec![self.0.to_string()] - } -} - -impl TupleToRow for (A, B) { - fn to_row(self) -> Vec { - vec![self.0.to_string(), self.1.to_string()] - } -} - -impl TupleToRow for (A, B, C) { - fn to_row(self) -> Vec { - vec![self.0.to_string(), self.1.to_string(), self.2.to_string()] - } -} - -impl ToTable for I -where - I: IntoIterator, -{ - fn to_table(self) -> Table { - let mut table = Table::new(); - for item in self { - table.add_row(item.to_row()); - } - table - } -} diff --git a/swap-core/src/bitcoin/cancel.rs b/swap-core/src/bitcoin/cancel.rs index 3dbb892c31..a84d8e65e7 100644 --- a/swap-core/src/bitcoin/cancel.rs +++ b/swap-core/src/bitcoin/cancel.rs @@ -2,14 +2,14 @@ use crate::bitcoin::{self, CancelTimelock, PunishTimelock}; use crate::bitcoin::{ - build_shared_output_descriptor, Address, Amount, PublicKey, Transaction, TxLock, + Address, Amount, PublicKey, Transaction, TxLock, build_shared_output_descriptor, }; +use ::bitcoin::Weight; use ::bitcoin::sighash::SighashCache; use ::bitcoin::transaction::Version; -use ::bitcoin::Weight; use ::bitcoin::{ - locktime::absolute::LockTime as PackedLockTime, secp256k1, sighash::SegwitV0Sighash as Sighash, EcdsaSighashType, OutPoint, ScriptBuf, Sequence, TxIn, TxOut, Txid, + locktime::absolute::LockTime as PackedLockTime, secp256k1, sighash::SegwitV0Sighash as Sighash, }; use anyhow::Result; use bdk_wallet::miniscript::Descriptor; @@ -209,6 +209,7 @@ impl TxCancel { witness: Default::default(), }; + assert!(self.amount() > (amnesty_amount + spending_fee)); let refund_amount = self.amount() - amnesty_amount - spending_fee; let tx_out_refund = TxOut { diff --git a/swap-core/src/bitcoin/final_amnesty.rs b/swap-core/src/bitcoin/final_amnesty.rs index dc6f3e1734..98b99674d7 100644 --- a/swap-core/src/bitcoin/final_amnesty.rs +++ b/swap-core/src/bitcoin/final_amnesty.rs @@ -40,10 +40,7 @@ impl TxFinalAmnesty { tx_refund_burn.amount() ); - let tx_final_amnesty = tx_refund_burn.build_spend_transaction( - refund_address, - spending_fee, - ); + let tx_final_amnesty = tx_refund_burn.build_spend_transaction(refund_address, spending_fee); let digest = SighashCache::new(&tx_final_amnesty) .p2wsh_signature_hash( @@ -132,6 +129,7 @@ impl TxFinalAmnesty { Ok(tx) } + // TODO: calculate actual weight pub fn weight() -> Weight { Weight::from_wu(548) } diff --git a/swap-core/src/bitcoin/refund_burn.rs b/swap-core/src/bitcoin/refund_burn.rs index 844dbb0434..bcc577ad63 100644 --- a/swap-core/src/bitcoin/refund_burn.rs +++ b/swap-core/src/bitcoin/refund_burn.rs @@ -1,7 +1,9 @@ #![allow(non_snake_case)] use crate::bitcoin::partial_refund::TxPartialRefund; -use crate::bitcoin::{self, build_shared_output_descriptor, Address, Amount, PublicKey, Transaction}; +use crate::bitcoin::{ + self, Address, Amount, PublicKey, Transaction, build_shared_output_descriptor, +}; use ::bitcoin::sighash::SighashCache; use ::bitcoin::{EcdsaSighashType, Txid, sighash::SegwitV0Sighash as Sighash}; use ::bitcoin::{OutPoint, ScriptBuf, Weight, secp256k1}; @@ -45,10 +47,8 @@ impl TxRefundBurn { let burn_output_descriptor = build_shared_output_descriptor(A.0, B.0)?; - let tx_refund_burn = tx_partial_refund.build_burn_spend_transaction( - &burn_output_descriptor, - spending_fee, - ); + let tx_refund_burn = + tx_partial_refund.build_burn_spend_transaction(&burn_output_descriptor, spending_fee); let digest = SighashCache::new(&tx_refund_burn) .p2wsh_signature_hash( @@ -189,6 +189,7 @@ impl TxRefundBurn { } } + // TODO: calculate actual weight pub fn weight() -> Weight { Weight::from_wu(548) } diff --git a/swap-machine/src/alice/mod.rs b/swap-machine/src/alice/mod.rs index 30291b18de..8e6894d9c6 100644 --- a/swap-machine/src/alice/mod.rs +++ b/swap-machine/src/alice/mod.rs @@ -601,6 +601,7 @@ pub struct State3 { pub btc_amnesty_amount: Option, pub cancel_timelock: CancelTimelock, pub punish_timelock: PunishTimelock, + #[serde(default)] remaining_refund_timelock: Option, #[serde(with = "swap_serde::bitcoin::address_serde")] refund_address: bitcoin::Address, @@ -631,7 +632,9 @@ pub struct State3 { tx_redeem_fee: bitcoin::Amount, pub tx_punish_fee: bitcoin::Amount, pub tx_refund_fee: bitcoin::Amount, + #[serde(default)] pub tx_partial_refund_fee: Option, + #[serde(default)] pub tx_refund_amnesty_fee: Option, pub tx_cancel_fee: bitcoin::Amount, } diff --git a/swap-machine/src/common/mod.rs b/swap-machine/src/common/mod.rs index 6ced6bd2d0..094112dc6a 100644 --- a/swap-machine/src/common/mod.rs +++ b/swap-machine/src/common/mod.rs @@ -169,6 +169,26 @@ pub trait Database { async fn get_state(&self, swap_id: Uuid) -> Result; async fn get_states(&self, swap_id: Uuid) -> Result>; async fn all(&self) -> Result>; + + /// Returns the current (latest) state and the starting state for a swap. + async fn get_current_and_starting_state(&self, swap_id: Uuid) -> Result<(State, State)> { + use anyhow::Context; + + let states = self + .get_states(swap_id) + .await + .context("Error fetching all states of swap from database")?; + let starting = states.first().cloned().context("No states found")?; + let current = states.last().cloned().context("No states found")?; + + // Sanity check: both states must be from the same role + match (¤t, &starting) { + (State::Alice(_), State::Alice(_)) | (State::Bob(_), State::Bob(_)) => {} + _ => anyhow::bail!("Current and starting states have mismatched roles"), + } + + Ok((current, starting)) + } async fn insert_buffered_transfer_proof( &self, swap_id: Uuid, diff --git a/swap/src/asb/rpc/server.rs b/swap/src/asb/rpc/server.rs index 31cf5b5a45..402ff7f58c 100644 --- a/swap/src/asb/rpc/server.rs +++ b/swap/src/asb/rpc/server.rs @@ -154,24 +154,58 @@ impl AsbApiServer for RpcImpl { } async fn get_swaps(&self) -> Result, ErrorObjectOwned> { - let swaps = self.db.all().await.into_json_rpc_result()?; + use crate::protocol::alice::{is_complete, AliceState}; + use crate::protocol::State; - let swaps = swaps - .into_iter() - .map(|(swap_id, state)| { - let state_str = match state { - crate::protocol::State::Alice(state) => format!("{state}"), - crate::protocol::State::Bob(state) => format!("{state}"), - }; - - Swap { - id: swap_id.to_string(), - state: state_str, - } - }) - .collect(); - - Ok(swaps) + let swaps = self + .db + .all() + .await + .context("Error fetching all swap's from database") + .into_json_rpc_result()?; + let mut results = Vec::with_capacity(swaps.len()); + + for (swap_id, _) in swaps { + let (current, starting) = self + .db + .get_current_and_starting_state(swap_id) + .await + .context("Error fetching current and first state from database") + .into_json_rpc_result()?; + + let (State::Alice(current_alice), State::Alice(AliceState::Started { state3 })) = + (current, starting) + else { + continue; // Skip non-Alice swaps + }; + + let start_date = self + .db + .get_swap_start_date(swap_id) + .await + .into_json_rpc_result()?; + let peer_id = self.db.get_peer_id(swap_id).await.into_json_rpc_result()?; + + // Exchange rate: BTC per XMR (amount of BTC needed to buy 1 XMR) + let rate_btc_per_xmr = state3.btc.to_btc() / state3.xmr.as_xmr(); + let exchange_rate = bitcoin::Amount::from_btc(rate_btc_per_xmr) + .context("exchange rate should be valid") + .into_json_rpc_result()?; + + results.push(Swap { + swap_id: swap_id.to_string(), + start_date, + state: current_alice.to_string(), + btc_lock_txid: state3.tx_lock.txid().to_string(), + btc_amount: state3.btc, + xmr_amount: state3.xmr.as_piconero(), + exchange_rate, + peer_id: peer_id.to_string(), + completed: is_complete(¤t_alice), + }); + } + + Ok(results) } async fn registration_status(&self) -> Result { diff --git a/swap/src/database/sqlite.rs b/swap/src/database/sqlite.rs index 421863349f..28a4f2f26d 100644 --- a/swap/src/database/sqlite.rs +++ b/swap/src/database/sqlite.rs @@ -411,18 +411,29 @@ impl Database for SqliteDatabase { let result = rows .iter() - .map(|row| { + .filter_map(|row| { let state_str: &str = &row.state; - let state = match serde_json::from_str::(state_str) { - Ok(a) => Ok(State::from(a)), - Err(e) => Err(e), - }?; - Ok(state) + match serde_json::from_str::(state_str) { + Ok(a) => { + // debugging + if swap_id.to_string().as_str() == "ef5435d3-bb7d-4b3a-a83b-42f2b9f7ca4b" { + tracing::info!("Managed to deserialize swap: {a}") + } + Some(State::from(a)) + } + Err(e) => { + // debugging + if swap_id.to_string().as_str() == "ef5435d3-bb7d-4b3a-a83b-42f2b9f7ca4b" { + tracing::error!(error=%e, "Failed to deserialize swap") + } + None + } + } }) - .collect::>>(); + .collect::>(); - result + Ok(result) } async fn insert_buffered_transfer_proof( From db2343f9a8e23dd719a6adbb1275156294b2e5e2 Mon Sep 17 00:00:00 2001 From: einliterflasche Date: Fri, 19 Dec 2025 13:08:32 +0100 Subject: [PATCH 072/113] swap-setup(alice+bob): only construct TxRefundAmnesty/TxRefundBurn/TxFinalAmnesty when the amnsty output is greater than zero --- swap-core/src/bitcoin/partial_refund.rs | 12 +- swap-core/src/bitcoin/refund_amnesty.rs | 18 ++- swap-machine/src/alice/mod.rs | 125 +++++++++++---- swap-machine/src/bob/mod.rs | 199 ++++++++++++++---------- swap-machine/src/common/mod.rs | 17 +- 5 files changed, 232 insertions(+), 139 deletions(-) diff --git a/swap-core/src/bitcoin/partial_refund.rs b/swap-core/src/bitcoin/partial_refund.rs index 80ffe2806c..40406ee776 100644 --- a/swap-core/src/bitcoin/partial_refund.rs +++ b/swap-core/src/bitcoin/partial_refund.rs @@ -89,7 +89,7 @@ impl TxPartialRefund { refund_address: &Address, spending_fee: Amount, remaining_refund_timelock: RemainingRefundTimelock, - ) -> Transaction { + ) -> Result { use ::bitcoin::{ Sequence, TxIn, TxOut, locktime::absolute::LockTime as PackedLockTime, transaction::Version, @@ -103,16 +103,19 @@ impl TxPartialRefund { }; let tx_out = TxOut { - value: self.amnesty_amount() - spending_fee, + value: self + .amnesty_amount() + .checked_sub(spending_fee) + .context("btc amnesty amount is less than spending fee")?, script_pubkey: refund_address.script_pubkey(), }; - Transaction { + Ok(Transaction { version: Version(2), lock_time: PackedLockTime::from_height(0).expect("0 to be below lock time threshold"), input: vec![tx_in], output: vec![tx_out], - } + }) } /// Build a transaction that spends the amnesty output to a new 2-of-2 multisig (burn output). @@ -252,6 +255,7 @@ impl TxPartialRefund { Ok(sig) } + // TODO: calculate actual weight pub fn weight() -> Weight { Weight::from_wu(548) } diff --git a/swap-core/src/bitcoin/refund_amnesty.rs b/swap-core/src/bitcoin/refund_amnesty.rs index 6fb130476e..c6e726ecfa 100644 --- a/swap-core/src/bitcoin/refund_amnesty.rs +++ b/swap-core/src/bitcoin/refund_amnesty.rs @@ -25,12 +25,14 @@ impl TxRefundAmnesty { refund_address: &Address, spending_fee: Amount, remaining_refund_timelock: RemainingRefundTimelock, - ) -> Self { - let tx_refund_amnesty = tx_refund.build_amnesty_spend_transaction( - refund_address, - spending_fee, - remaining_refund_timelock, - ); + ) -> Result { + let tx_refund_amnesty = tx_refund + .build_amnesty_spend_transaction( + refund_address, + spending_fee, + remaining_refund_timelock, + ) + .context("Couldn't build tx refund amnesty")?; let digest = SighashCache::new(&tx_refund_amnesty) .p2wsh_signature_hash( @@ -44,12 +46,12 @@ impl TxRefundAmnesty { ) .expect("sighash"); - Self { + Ok(Self { inner: tx_refund_amnesty, digest, amensty_output_descriptor: tx_refund.amnesty_output_descriptor.clone(), watch_script: refund_address.script_pubkey(), - } + }) } pub fn txid(&self) -> Txid { diff --git a/swap-machine/src/alice/mod.rs b/swap-machine/src/alice/mod.rs index 8e6894d9c6..a4c8b6df22 100644 --- a/swap-machine/src/alice/mod.rs +++ b/swap-machine/src/alice/mod.rs @@ -410,6 +410,24 @@ impl State2 { ) .expect("valid cancel tx"); + let tx_cancel_sig = self.a.sign(tx_cancel.digest()); + + // When the amnesty output is zero, we can't construct the tx partial refund transaction + // due to an integer underflow. + // We only send the cancel and full refund signatures. + if self.btc_amnesty_amount.unwrap_or(bitcoin::Amount::ZERO) == bitcoin::Amount::ZERO { + let tx_full_refund = + TxFullRefund::new(&tx_cancel, &self.refund_address, self.tx_refund_fee); + let tx_refund_encsig = self.a.encsign(self.S_b_bitcoin, tx_full_refund.digest()); + + return Ok(Message3 { + tx_cancel_sig, + tx_partial_refund_encsig: None, + tx_full_refund_encsig: Some(tx_refund_encsig), + tx_refund_amnesty_sig: None, + }); + } + let tx_partial_refund = swap_core::bitcoin::TxPartialRefund::new( &tx_cancel, &self.refund_address, @@ -424,9 +442,7 @@ impl State2 { // tx_lock_bitcoin to Bob's refund address (except for the amnesty output). // recover(encsign(a, S_b, d), sign(a, d), S_b) = s_b where d is a digest, (a, // A) is alice's keypair and (s_b, S_b) is bob's keypair. - let tx_refund_encsig = self.a.encsign(self.S_b_bitcoin, tx_partial_refund.digest()); - - let tx_cancel_sig = self.a.sign(tx_cancel.digest()); + let tx_partial_refund_encsig = self.a.encsign(self.S_b_bitcoin, tx_partial_refund.digest()); // Construct and sign TxRefundAmnesty let tx_refund_amnesty = swap_core::bitcoin::TxRefundAmnesty::new( @@ -436,24 +452,14 @@ impl State2 { .context("Missing tx_refund_amnesty_fee for new swap")?, self.remaining_refund_timelock .context("Missing remaining_refund_timelock for new swap")?, - ); + )?; let tx_refund_amnesty_sig = self.a.sign(tx_refund_amnesty.digest()); - // Send full refund encsig when btc_amnesty_amount is None or ZERO (ratio = 1.0) - let tx_full_refund = - TxFullRefund::new(&tx_cancel, &self.refund_address, self.tx_refund_fee); - let tx_full_refund_encsig = - if self.btc_amnesty_amount.unwrap_or(bitcoin::Amount::ZERO) == bitcoin::Amount::ZERO { - Some(self.a.encsign(self.S_b_bitcoin, tx_full_refund.digest())) - } else { - None - }; - Ok(Message3 { tx_cancel_sig, - tx_partial_refund_encsig: tx_refund_encsig, - tx_full_refund_encsig, - tx_refund_amnesty_sig, + tx_partial_refund_encsig: Some(tx_partial_refund_encsig), + tx_refund_amnesty_sig: Some(tx_refund_amnesty_sig), + tx_full_refund_encsig: None, }) } @@ -498,6 +504,45 @@ impl State2 { ) .context("Failed to verify early refund transaction")?; + // When the bitcoin amnesty amount is zero, we can't construct the transactions for the partial refund path. + // We sent Bob the encsig for the full refund path already, so we don't + // care about the partial refund path signatures of Bob anyway. + // We just save `None`. + if self.btc_amnesty_amount.unwrap_or(bitcoin::Amount::ZERO) == bitcoin::Amount::ZERO { + return Ok(State3 { + a: self.a, + B: self.B, + s_a: self.s_a, + S_b_monero: self.S_b_monero, + S_b_bitcoin: self.S_b_bitcoin, + v: self.v, + btc: self.btc, + xmr: self.xmr, + btc_amnesty_amount: self.btc_amnesty_amount, + cancel_timelock: self.cancel_timelock, + punish_timelock: self.punish_timelock, + remaining_refund_timelock: self.remaining_refund_timelock, + refund_address: self.refund_address, + redeem_address: self.redeem_address, + punish_address: self.punish_address, + tx_lock: self.tx_lock, + tx_punish_sig_bob: msg.tx_punish_sig, + tx_cancel_sig_bob: msg.tx_cancel_sig, + tx_early_refund_sig_bob: msg.tx_early_refund_sig.into(), + tx_refund_amnesty_sig_bob: None, + tx_redeem_fee: self.tx_redeem_fee, + tx_punish_fee: self.tx_punish_fee, + tx_refund_fee: self.tx_refund_fee, + tx_partial_refund_fee: self.tx_partial_refund_fee, + tx_refund_amnesty_fee: self.tx_refund_amnesty_fee, + tx_refund_burn_fee: self.tx_refund_burn_fee, + tx_final_amnesty_fee: self.tx_final_amnesty_fee, + tx_cancel_fee: self.tx_cancel_fee, + tx_refund_burn_sig_bob: None, + tx_final_amnesty_sig_bob: None, + }); + } + // Create TxRefundAmnesty ourself let tx_partial_refund = TxPartialRefund::new( &tx_cancel, @@ -517,15 +562,15 @@ impl State2 { .context("missing tx_refund_amnesty_fee")?, self.remaining_refund_timelock .context("missing remaining_refund_timelock")?, - ); + )?; // Check if the provided signature by Bob is valid for the transaction - swap_core::bitcoin::verify_sig( - &self.B, - &tx_refund_amnesty.digest(), - &msg.tx_refund_amnesty_sig, - ) - .context("Failed to verify refund amnesty transaction")?; + let tx_refund_amnesty_sig = msg + .tx_refund_amnesty_sig + .as_ref() + .context("Missing tx_refund_amnesty_sig from Bob")?; + swap_core::bitcoin::verify_sig(&self.B, &tx_refund_amnesty.digest(), tx_refund_amnesty_sig) + .context("Failed to verify refund amnesty transaction")?; // Create TxRefundBurn ourself let tx_refund_burn = TxRefundBurn::new( @@ -537,7 +582,11 @@ impl State2 { )?; // Check if the provided signature by Bob is valid for the transaction - swap_core::bitcoin::verify_sig(&self.B, &tx_refund_burn.digest(), &msg.tx_refund_burn_sig) + let tx_refund_burn_sig = msg + .tx_refund_burn_sig + .as_ref() + .context("Missing tx_refund_burn_sig from Bob")?; + swap_core::bitcoin::verify_sig(&self.B, &tx_refund_burn.digest(), tx_refund_burn_sig) .context("Failed to verify refund burn transaction")?; // Create TxFinalAmnesty ourself @@ -549,12 +598,12 @@ impl State2 { ); // Check if the provided signature by Bob is valid for the transaction - swap_core::bitcoin::verify_sig( - &self.B, - &tx_final_amnesty.digest(), - &msg.tx_final_amnesty_sig, - ) - .context("Failed to verify final amnesty transaction")?; + let tx_final_amnesty_sig = msg + .tx_final_amnesty_sig + .as_ref() + .context("Missing tx_final_amnesty_sig from Bob")?; + swap_core::bitcoin::verify_sig(&self.B, &tx_final_amnesty.digest(), tx_final_amnesty_sig) + .context("Failed to verify final amnesty transaction")?; Ok(State3 { a: self.a, @@ -582,7 +631,11 @@ impl State2 { tx_refund_fee: self.tx_refund_fee, tx_partial_refund_fee: self.tx_partial_refund_fee, tx_refund_amnesty_fee: self.tx_refund_amnesty_fee, + tx_refund_burn_fee: self.tx_refund_burn_fee, + tx_final_amnesty_fee: self.tx_final_amnesty_fee, tx_cancel_fee: self.tx_cancel_fee, + tx_refund_burn_sig_bob: msg.tx_refund_burn_sig, + tx_final_amnesty_sig_bob: msg.tx_final_amnesty_sig, }) } } @@ -636,7 +689,15 @@ pub struct State3 { pub tx_partial_refund_fee: Option, #[serde(default)] pub tx_refund_amnesty_fee: Option, + #[serde(default)] + pub tx_refund_burn_fee: Option, + #[serde(default)] + pub tx_final_amnesty_fee: Option, pub tx_cancel_fee: bitcoin::Amount, + #[serde(default)] + tx_refund_burn_sig_bob: Option, + #[serde(default)] + tx_final_amnesty_sig_bob: Option, } impl State3 { @@ -805,7 +866,7 @@ impl State3 { .context("Missing tx_refund_amnesty_fee")?, self.remaining_refund_timelock .context("Missing remaining_refund_timelock")?, - ); + )?; tx_amnesty.complete_as_alice( self.a.clone(), diff --git a/swap-machine/src/bob/mod.rs b/swap-machine/src/bob/mod.rs index a5a8745a5f..1496a14487 100644 --- a/swap-machine/src/bob/mod.rs +++ b/swap-machine/src/bob/mod.rs @@ -15,8 +15,8 @@ use std::fmt; use std::sync::Arc; use swap_core::bitcoin::{ self, CancelTimelock, ExpiredTimelocks, PunishTimelock, RemainingRefundTimelock, Transaction, - TxCancel, TxFinalAmnesty, TxLock, TxPartialRefund, TxRefundAmnesty, TxRefundBurn, Txid, - current_epoch, + TxCancel, TxFinalAmnesty, TxFullRefund, TxLock, TxPartialRefund, TxRefundAmnesty, TxRefundBurn, + Txid, current_epoch, }; use swap_core::compat::IntoDalekNg; use swap_core::monero; @@ -477,53 +477,67 @@ impl State1 { self.tx_cancel_fee, )?; - let tx_partial_refund = bitcoin::TxPartialRefund::new( - &tx_cancel, - &self.refund_address, - self.A, - self.b.public(), - self.btc_amnesty_amount - .context("btc_amnesty_amount is missing but required to create TxPartialRefund")?, - self.tx_partial_refund_fee - .context("tx_partial_refund_fee missing but required to setup swap")?, - )?; - - bitcoin::verify_sig(&self.A, &tx_cancel.digest(), &msg.tx_cancel_sig)?; - bitcoin::verify_encsig( - self.A, - bitcoin::PublicKey::from(self.s_b.to_secpfun_scalar()), - &tx_partial_refund.digest(), - &msg.tx_partial_refund_encsig, - )?; + // Depending on which signatures we get, verify and store them + let refund_signatures = match ( + msg.tx_full_refund_encsig, + msg.tx_partial_refund_encsig, + msg.tx_refund_amnesty_sig, + ) { + // We got the encrypted signature for the full refund - awesome + (Some(tx_full_refund_encsig), _, _) => { + let tx_full_refund = + TxFullRefund::new(&tx_cancel, &self.refund_address, self.tx_refund_fee); + bitcoin::verify_encsig( + self.A, + bitcoin::PublicKey::from(self.s_b.to_secpfun_scalar()), + &tx_full_refund.digest(), + &tx_full_refund_encsig, + ) + .context("Couldn't verify Alice's signature on TxFullRefund")?; + + RefundSignatures::Legacy { + tx_full_refund_encsig, + } + } + // We got the encrypted signatures for the partial refund path. + (None, Some(tx_partial_refund_encsig), Some(tx_refund_amnesty_sig)) => { + let tx_partial_refund = TxPartialRefund::new( + &tx_cancel, + &self.refund_address, + self.A, + self.b.public(), + self.btc_amnesty_amount + .context("Missing btc_amnesty_amount")?, + self.tx_partial_refund_fee + .context("Missing tx_partial_refund_fee")?, + )?; + bitcoin::verify_encsig( + self.A, + bitcoin::PublicKey::from(self.s_b.to_secpfun_scalar()), + &tx_partial_refund.digest(), + &tx_partial_refund_encsig, + )?; - // Verify the full refund signature if it is present - if let Some(tx_full_refund_encsig) = &msg.tx_full_refund_encsig { - let tx_full_refund = - bitcoin::TxFullRefund::new(&tx_cancel, &self.refund_address, self.tx_refund_fee); - bitcoin::verify_encsig( - self.A, - bitcoin::PublicKey::from(self.s_b.to_secpfun_scalar()), - &tx_full_refund.digest(), - tx_full_refund_encsig, - )?; - } + let tx_refund_amnesty = TxRefundAmnesty::new( + &tx_partial_refund, + &self.refund_address, + self.tx_refund_amnesty_fee + .context("missing tx_refund_amnesty_fee")?, + self.remaining_refund_timelock + .context("missing remaining_refund_timelock")?, + )?; + bitcoin::verify_sig(&self.A, &tx_refund_amnesty.digest(), &tx_refund_amnesty_sig)?; - // Verify the refund amnesty signature (always provided in new swaps) - let tx_refund_amnesty = bitcoin::TxRefundAmnesty::new( - &tx_partial_refund, - &self.refund_address, - self.tx_refund_amnesty_fee - .context("tx_refund_amnesty_fee missing but required to setup swap")?, - self.remaining_refund_timelock - .context("remaining_refund_timelock missing but required to setup swap")?, - ); - bitcoin::verify_sig(&self.A, &tx_refund_amnesty.digest(), &msg.tx_refund_amnesty_sig)?; + RefundSignatures::Partial { + tx_partial_refund_encsig, + tx_refund_amnesty_sig, + } + } + (_, _, _) => anyhow::bail!( + "Alice sent us neither TxFullRefund encsig nor signatures for the partial refund path" + ), + }; - let refund_signatures = RefundSignatures::from_possibly_full_refund_sig( - msg.tx_partial_refund_encsig, - msg.tx_full_refund_encsig, - msg.tx_refund_amnesty_sig, - ); Ok(State2 { A: self.A, b: self.b, @@ -618,43 +632,57 @@ impl State2 { let tx_early_refund_sig = self.b.sign(tx_early_refund.digest()); - let tx_partial_refund = TxPartialRefund::new( - &tx_cancel, - &self.refund_address, - self.A, - self.b.public(), - self.btc_amnesty_amount - .context("missing btc_amnesty_amount")?, - self.tx_partial_refund_fee - .context("missing tx_partial_refund_fee")?, - ) - .context("Couldn't construct TxPartialRefund")?; - let tx_refund_amnesty = TxRefundAmnesty::new( - &tx_partial_refund, - &self.refund_address, - self.tx_refund_amnesty_fee - .context("Missing tx_refund_amnesty_fee")?, - self.remaining_refund_timelock - .context("missing remaining_refund_timelock")?, - ); - let tx_refund_amnesty_sig = self.b.sign(tx_refund_amnesty.digest()); + // We can only construct a valid TxRefundAmnesty/TxRefundBurn/TxFinalAmnesty when the amnesty amount + // is greater than zero. Thus we only send our signatures for them if that is the case. + // Alice accepts this because she sent us her signature for TxFullRefund already anyway. + let (tx_refund_amnesty_sig, tx_refund_burn_sig, tx_final_amnesty_sig) = + if self.btc_amnesty_amount.unwrap_or(bitcoin::Amount::ZERO) == bitcoin::Amount::ZERO { + (None, None, None) + } else { + let tx_partial_refund = TxPartialRefund::new( + &tx_cancel, + &self.refund_address, + self.A, + self.b.public(), + self.btc_amnesty_amount + .context("missing btc_amnesty_amount")?, + self.tx_partial_refund_fee + .context("missing tx_partial_refund_fee")?, + ) + .context("Couldn't construct TxPartialRefund")?; + let tx_refund_amnesty = TxRefundAmnesty::new( + &tx_partial_refund, + &self.refund_address, + self.tx_refund_amnesty_fee + .context("Missing tx_refund_amnesty_fee")?, + self.remaining_refund_timelock + .context("missing remaining_refund_timelock")?, + )?; + let tx_refund_amnesty_sig = self.b.sign(tx_refund_amnesty.digest()); - let tx_refund_burn = TxRefundBurn::new( - &tx_partial_refund, - self.A, - self.b.public(), - self.tx_refund_burn_fee - .context("Missing tx_refund_burn_fee")?, - )?; - let tx_refund_burn_sig = self.b.sign(tx_refund_burn.digest()); + let tx_refund_burn = TxRefundBurn::new( + &tx_partial_refund, + self.A, + self.b.public(), + self.tx_refund_burn_fee + .context("Missing tx_refund_burn_fee")?, + )?; + let tx_refund_burn_sig = self.b.sign(tx_refund_burn.digest()); - let tx_final_amnesty = TxFinalAmnesty::new( - &tx_refund_burn, - &self.refund_address, - self.tx_final_amnesty_fee - .context("Missing tx_final_amnesty_fee")?, - ); - let tx_final_amnesty_sig = self.b.sign(tx_final_amnesty.digest()); + let tx_final_amnesty = TxFinalAmnesty::new( + &tx_refund_burn, + &self.refund_address, + self.tx_final_amnesty_fee + .context("Missing tx_final_amnesty_fee")?, + ); + let tx_final_amnesty_sig = self.b.sign(tx_final_amnesty.digest()); + + ( + Some(tx_refund_amnesty_sig), + Some(tx_refund_burn_sig), + Some(tx_final_amnesty_sig), + ) + }; Ok(Message4 { tx_punish_sig, @@ -1295,7 +1323,7 @@ impl State6 { pub fn construct_tx_amnesty(&self) -> Result { let tx_partial_refund = self.construct_tx_partial_refund()?; - Ok(bitcoin::TxRefundAmnesty::new( + bitcoin::TxRefundAmnesty::new( &tx_partial_refund, &self.refund_address, self.tx_refund_amnesty_fee.context( @@ -1304,7 +1332,7 @@ impl State6 { self.remaining_refund_timelock.context( "Can't construct TxRefundAmnesty because remaining_refund_timelock is missing", )?, - )) + ) } pub fn construct_tx_refund_burn(&self) -> Result { @@ -1313,9 +1341,8 @@ impl State6 { &tx_partial_refund, self.A, self.b.public(), - self.tx_refund_burn_fee.context( - "Can't construct TxRefundBurn because tx_refund_burn_fee is missing", - )?, + self.tx_refund_burn_fee + .context("Can't construct TxRefundBurn because tx_refund_burn_fee is missing")?, ) } diff --git a/swap-machine/src/common/mod.rs b/swap-machine/src/common/mod.rs index 094112dc6a..be359dc835 100644 --- a/swap-machine/src/common/mod.rs +++ b/swap-machine/src/common/mod.rs @@ -72,13 +72,12 @@ pub struct Message2 { #[derive(Clone, Debug, Serialize, Deserialize)] pub struct Message3 { pub tx_cancel_sig: bitcoin::Signature, - /// The following fields were reworked in [#675](https://github.com/eigenwallet/core/pull/675). - /// Alice _may_ choose to commit to a full refund for bob during the swap setup already, but doesn't - /// have to. - pub tx_partial_refund_encsig: bitcoin::EncryptedSignature, + // The following fields were reworked in [#675](https://github.com/eigenwallet/core/pull/675). + // Alice will send either the full refund encsig or signatures for both partial refund + // and tx refund amnesty. pub tx_full_refund_encsig: Option, - /// Alice's signature for the amnesty transaction - always provided. - pub tx_refund_amnesty_sig: bitcoin::Signature, + pub tx_partial_refund_encsig: Option, + pub tx_refund_amnesty_sig: Option, } #[allow(non_snake_case)] @@ -87,9 +86,9 @@ pub struct Message4 { pub tx_punish_sig: bitcoin::Signature, pub tx_cancel_sig: bitcoin::Signature, pub tx_early_refund_sig: bitcoin::Signature, - pub tx_refund_amnesty_sig: bitcoin::Signature, - pub tx_refund_burn_sig: bitcoin::Signature, - pub tx_final_amnesty_sig: bitcoin::Signature, + pub tx_refund_amnesty_sig: Option, + pub tx_refund_burn_sig: Option, + pub tx_final_amnesty_sig: Option, } #[allow(clippy::large_enum_variant)] From 5c2261d8e93c1ef4f43a316ccb68765508d35914 Mon Sep 17 00:00:00 2001 From: einliterflasche Date: Fri, 19 Dec 2025 13:39:18 +0100 Subject: [PATCH 073/113] just command for listing docker tests --- justfile | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/justfile b/justfile index 8621457913..e9f7279a79 100644 --- a/justfile +++ b/justfile @@ -60,6 +60,10 @@ build-gui-windows: tests: cargo nextest run +# List all available docker integration tests +list-docker-tests: + @find swap/tests -maxdepth 1 -type f -name "*.rs" | xargs -n1 basename | sed 's/\.rs$//' | sort + # Run docker tests (e.g., "just docker_test happy_path_alice_developer_tip") docker_test test_name: RUST_BACKTRACE=1 cargo test --package swap --test {{test_name}} -- --nocapture From cdd260ec6768a53aefe7abbe105a7e62ef21e05f Mon Sep 17 00:00:00 2001 From: einliterflasche Date: Fri, 26 Dec 2025 17:44:29 +0100 Subject: [PATCH 074/113] store should_publish_tx_refund_burn in alice states --- swap-machine/src/alice/mod.rs | 15 +++++++++++++++ swap-machine/src/lib.rs | 4 ++++ swap-p2p/src/out_event/alice.rs | 2 +- swap-p2p/src/protocols/swap_setup/alice.rs | 20 ++++++++++---------- swap/src/asb/event_loop.rs | 19 ++++++++++++++----- 5 files changed, 44 insertions(+), 16 deletions(-) diff --git a/swap-machine/src/alice/mod.rs b/swap-machine/src/alice/mod.rs index a4c8b6df22..872f76c2aa 100644 --- a/swap-machine/src/alice/mod.rs +++ b/swap-machine/src/alice/mod.rs @@ -177,6 +177,7 @@ pub struct State0 { tx_redeem_fee: bitcoin::Amount, tx_punish_fee: bitcoin::Amount, tx_refund_burn_fee: Option, + should_publish_tx_refund_burn: Option, } impl State0 { @@ -191,6 +192,7 @@ impl State0 { tx_redeem_fee: bitcoin::Amount, tx_punish_fee: bitcoin::Amount, tx_refund_burn_fee: bitcoin::Amount, + should_publish_tx_refund_burn: bool, rng: &mut R, ) -> Self where @@ -223,6 +225,7 @@ impl State0 { tx_redeem_fee, tx_punish_fee, tx_refund_burn_fee: Some(tx_refund_burn_fee), + should_publish_tx_refund_burn: Some(should_publish_tx_refund_burn), } } @@ -274,6 +277,7 @@ impl State0 { tx_refund_burn_fee: self.tx_refund_burn_fee, tx_final_amnesty_fee: Some(msg.tx_final_amnesty_fee), tx_cancel_fee: msg.tx_cancel_fee, + should_publish_tx_refund_burn: self.should_publish_tx_refund_burn, }, )) } @@ -309,6 +313,7 @@ pub struct State1 { tx_refund_burn_fee: Option, tx_final_amnesty_fee: Option, tx_cancel_fee: bitcoin::Amount, + should_publish_tx_refund_burn: Option, } impl State1 { @@ -366,6 +371,7 @@ impl State1 { tx_refund_burn_fee: self.tx_refund_burn_fee, tx_final_amnesty_fee: self.tx_final_amnesty_fee, tx_cancel_fee: self.tx_cancel_fee, + should_publish_tx_refund_burn: self.should_publish_tx_refund_burn, }) } } @@ -397,6 +403,7 @@ pub struct State2 { tx_refund_burn_fee: Option, tx_final_amnesty_fee: Option, tx_cancel_fee: bitcoin::Amount, + should_publish_tx_refund_burn: Option, } impl State2 { @@ -540,6 +547,7 @@ impl State2 { tx_cancel_fee: self.tx_cancel_fee, tx_refund_burn_sig_bob: None, tx_final_amnesty_sig_bob: None, + should_publish_tx_refund_burn: self.should_publish_tx_refund_burn, }); } @@ -636,6 +644,7 @@ impl State2 { tx_cancel_fee: self.tx_cancel_fee, tx_refund_burn_sig_bob: msg.tx_refund_burn_sig, tx_final_amnesty_sig_bob: msg.tx_final_amnesty_sig, + should_publish_tx_refund_burn: self.should_publish_tx_refund_burn, }) } } @@ -698,6 +707,12 @@ pub struct State3 { tx_refund_burn_sig_bob: Option, #[serde(default)] tx_final_amnesty_sig_bob: Option, + /// Whether Alice should publish TxRefundBurn to deny Bob's amnesty. + /// None = no decision yet (legacy swaps or awaiting controller input) + /// Some(false) = don't burn (default for new swaps) + /// Some(true) = burn the amnesty output + #[serde(default)] + pub should_publish_tx_refund_burn: Option, } impl State3 { diff --git a/swap-machine/src/lib.rs b/swap-machine/src/lib.rs index 64cf053733..484fa2dda1 100644 --- a/swap-machine/src/lib.rs +++ b/swap-machine/src/lib.rs @@ -28,6 +28,7 @@ mod tests { let spending_fee = Amount::from_sat(1_000); let btc_amount = Amount::from_sat(500_000); let btc_amnesty_amount = Amount::from_sat(100_000); + let should_publish_tx_refund = false; let xmr_amount = swap_core::monero::primitives::Amount::from_piconero(10000); let tx_redeem_fee = alice_wallet @@ -57,6 +58,7 @@ mod tests { tx_redeem_fee, tx_punish_fee, spending_fee, + should_publish_tx_refund, &mut OsRng, ); @@ -149,6 +151,7 @@ mod tests { let spending_fee = Amount::from_sat(1_000); let btc_amount = Amount::from_sat(500_000); let btc_amnesty_amount = Amount::from_sat(100_000); + let should_publish_tx_refund = false; let xmr_amount = swap_core::monero::primitives::Amount::from_piconero(10000); let tx_redeem_fee = alice_wallet @@ -174,6 +177,7 @@ mod tests { tx_redeem_fee, tx_punish_fee, spending_fee, + should_publish_tx_refund, &mut OsRng, ); diff --git a/swap-p2p/src/out_event/alice.rs b/swap-p2p/src/out_event/alice.rs index 8f769f8775..51642300a9 100644 --- a/swap-p2p/src/out_event/alice.rs +++ b/swap-p2p/src/out_event/alice.rs @@ -22,7 +22,7 @@ pub enum OutEvent { // should go into the amnesty output send_wallet_snapshot: bmrng::RequestReceiver< bitcoin::Amount, - (swap_setup::alice::WalletSnapshot, bitcoin::Amount), + (swap_setup::alice::WalletSnapshot, bitcoin::Amount, bool), >, }, SwapSetupCompleted { diff --git a/swap-p2p/src/protocols/swap_setup/alice.rs b/swap-p2p/src/protocols/swap_setup/alice.rs index 159d0e958e..e96fd5192a 100644 --- a/swap-p2p/src/protocols/swap_setup/alice.rs +++ b/swap-p2p/src/protocols/swap_setup/alice.rs @@ -30,7 +30,7 @@ use uuid::Uuid; pub enum OutEvent { Initiated { send_wallet_snapshot: - bmrng::RequestReceiver, + bmrng::RequestReceiver, }, Completed { peer_id: PeerId, @@ -263,7 +263,7 @@ impl Handler { #[allow(clippy::large_enum_variant)] #[derive(Debug)] pub enum HandlerOutEvent { - Initiated(bmrng::RequestReceiver), + Initiated(bmrng::RequestReceiver), Completed(Result<(Uuid, State3)>), } @@ -296,12 +296,11 @@ where ConnectionEvent::FullyNegotiatedInbound(substream) => { let substream = substream.protocol; - let (sender, receiver) = bmrng::channel_with_timeout::< - bitcoin::Amount, - (WalletSnapshot, bitcoin::Amount), - >( - 1, crate::defaults::SWAP_SETUP_CHANNEL_TIMEOUT - ); + let (sender, receiver) = + bmrng::channel_with_timeout::< + bitcoin::Amount, + (WalletSnapshot, bitcoin::Amount, bool), + >(1, crate::defaults::SWAP_SETUP_CHANNEL_TIMEOUT); let resume_only = self.resume_only; let min_buy = self.min_buy; @@ -447,7 +446,7 @@ impl Error { async fn run_swap_setup( mut substream: libp2p::swarm::Stream, - sender: bmrng::RequestSender, + sender: bmrng::RequestSender, resume_only: bool, env_config: env::Config, min_buy: bitcoin::Amount, @@ -458,7 +457,7 @@ async fn run_swap_setup( .await .context("Failed to read spot price request")?; - let (wallet_snapshot, btc_amnesty_amount) = sender + let (wallet_snapshot, btc_amnesty_amount, should_burn_on_refund) = sender .send_receive(request.btc) .await .context("Failed to receive wallet snapshot")?; @@ -546,6 +545,7 @@ async fn run_swap_setup( wallet_snapshot.redeem_fee, wallet_snapshot.punish_fee, wallet_snapshot.refund_burn_fee, + should_burn_on_refund, &mut rand::thread_rng(), ); diff --git a/swap/src/asb/event_loop.rs b/swap/src/asb/event_loop.rs index 058099f05f..0f811ac600 100644 --- a/swap/src/asb/event_loop.rs +++ b/swap/src/asb/event_loop.rs @@ -13,6 +13,7 @@ use crate::protocol::alice::swap::has_already_processed_enc_sig; use crate::protocol::alice::{AliceState, State3, Swap, TipConfig}; use crate::protocol::{Database, State}; use anyhow::{anyhow, Context, Result}; +use bdk::bitcoin::hashes::sha1; use bitcoin_wallet::BitcoinWallet; use futures::future; use futures::future::{BoxFuture, FutureExt}; @@ -253,13 +254,14 @@ where }; // TODO: propagate error to the swap_setup routine instead of swallowing it - let btc_amnesty_amount = match apply_bitcoin_amnesty_policy(btc, &self.refund_policy) { + let (btc_amnesty_amount, should_publish_tx_refund_burn )= match apply_bitcoin_amnesty_policy(btc, &self.refund_policy) { Ok(amount) => amount, Err(error) => { tracing::error!("Swap request will be ignored because we were unable to create wallet snapshot for swap: {:#}", error); continue; } }; + let wallet_snapshot = match capture_wallet_snapshot(self.bitcoin_wallet.clone(), &self.monero_wallet, &self.external_redeem_address, btc).await { Ok(wallet_snapshot) => wallet_snapshot, Err(error) => { @@ -269,7 +271,7 @@ where }; // Ignore result, we should never hit this because the receiver will alive as long as the connection is. - let _ = responder.respond((wallet_snapshot, btc_amnesty_amount)); + let _ = responder.respond((wallet_snapshot, btc_amnesty_amount, should_publish_tx_refund_burn)); } SwarmEvent::Behaviour(OutEvent::SwapSetupCompleted{peer_id, swap_id, state3}) => { if let Err(error) = self.handle_execution_setup_done(peer_id, swap_id, state3).await { @@ -833,13 +835,17 @@ impl EventLoopHandle { /// For a new swap of `swap_amount`, this function calculates how much /// Bitcoin should go into the amnesty-lock incase of a refund. /// Returns ZERO when taker_refund_ratio is 1.0 (100%), indicating full refund. +/// Also returns whether or not to burn the the amnesty output if the taker refunds. fn apply_bitcoin_amnesty_policy( swap_amount: bitcoin::Amount, refund_policy: &RefundPolicy, -) -> Result { +) -> Result<(bitcoin::Amount, bool)> { + // TODO: decide this somehow + let should_burn_on_refund = false; + // When ratio is 1.0, no amnesty - use full refund path if refund_policy.taker_refund_ratio == Decimal::ONE { - return Ok(bitcoin::Amount::ZERO); + return Ok((bitcoin::Amount::ZERO, should_burn_on_refund)); } let btc_amnesty_ratio = Decimal::ONE @@ -858,7 +864,10 @@ fn apply_bitcoin_amnesty_policy( .try_into() .context("Couldn't convert Decimal to u64")?; - Ok(bitcoin::Amount::from_sat(btc_amnesty_sats)) + Ok(( + bitcoin::Amount::from_sat(btc_amnesty_sats), + should_burn_on_refund, + )) } async fn capture_wallet_snapshot( From a7e6d97cd18db1ebf08a2ac09f921f542c94ff61 Mon Sep 17 00:00:00 2001 From: einliterflasche Date: Sat, 27 Dec 2025 11:44:36 +0100 Subject: [PATCH 075/113] alice: add states BtcRefundBurn{Published, Confirmed} and BtcFinalAmnesty{Granted, Published, Confirmed} --- monero-harness/src/lib.rs | 3 +- monero-harness/tests/wallet.rs | 6 +- monero-tests/tests/reserve_proof.rs | 2 +- monero-tests/tests/transfers.rs | 2 +- monero-tests/tests/transfers_wrong_key.rs | 2 +- swap-db/src/alice.rs | 110 +++++++++++++++--- swap-env/src/config.rs | 6 + swap-machine/src/alice/mod.rs | 106 +++++++++++++++-- swap-machine/src/bob/mod.rs | 1 + swap/src/asb/event_loop.rs | 3 +- swap/src/asb/recovery/cancel.rs | 7 +- swap/src/asb/recovery/punish.rs | 7 +- swap/src/asb/recovery/redeem.rs | 7 +- swap/src/asb/recovery/refund.rs | 11 +- swap/src/asb/recovery/safely_abort.rs | 7 +- swap/src/protocol/alice/swap.rs | 75 +++++++++++- swap/tests/harness/mod.rs | 94 ++++++++++++++- .../partial_refund_bob_claims_amnesty.rs | 2 + 18 files changed, 402 insertions(+), 49 deletions(-) diff --git a/monero-harness/src/lib.rs b/monero-harness/src/lib.rs index cdc65e4a65..364288e24c 100644 --- a/monero-harness/src/lib.rs +++ b/monero-harness/src/lib.rs @@ -306,7 +306,8 @@ impl<'c> Monero { Ok(()) } - pub async fn generate_block(&self) -> Result<()> { + /// Generates 15 blocks + pub async fn generate_blocks(&self) -> Result<()> { let miner_wallet = self.wallet("miner")?; let miner_address = miner_wallet.address().await?.to_string(); self.monerod() diff --git a/monero-harness/tests/wallet.rs b/monero-harness/tests/wallet.rs index 6277b6af34..4487f33db5 100644 --- a/monero-harness/tests/wallet.rs +++ b/monero-harness/tests/wallet.rs @@ -53,7 +53,7 @@ async fn fund_transfer_and_check_tx_key() { .await .unwrap(); - monero.generate_block().await.unwrap(); + monero.generate_blocks().await.unwrap(); tracing::info!("Waiting for Bob to catch up"); @@ -69,7 +69,7 @@ async fn fund_transfer_and_check_tx_key() { .await .unwrap(); - monero.generate_block().await.unwrap(); + monero.generate_blocks().await.unwrap(); wait_for_wallet_to_catch_up(bob_wallet, 0).await; @@ -86,7 +86,7 @@ async fn fund_transfer_and_check_tx_key() { .await .unwrap(); - monero.generate_block().await.unwrap(); + monero.generate_blocks().await.unwrap(); wait_for_wallet_to_catch_up(alice_wallet, 0).await; diff --git a/monero-tests/tests/reserve_proof.rs b/monero-tests/tests/reserve_proof.rs index 9859bea378..9577f7fb89 100644 --- a/monero-tests/tests/reserve_proof.rs +++ b/monero-tests/tests/reserve_proof.rs @@ -31,7 +31,7 @@ async fn setup(cli: &Cli) -> anyhow::Result> { // Fund alice's wallet miner.sweep(&alice.address().await?).await?; - monero.generate_block().await?; + monero.generate_blocks().await?; alice.refresh().await?; let alice_balance = alice.balance().await?; diff --git a/monero-tests/tests/transfers.rs b/monero-tests/tests/transfers.rs index 27ebd938f2..77777fda3b 100644 --- a/monero-tests/tests/transfers.rs +++ b/monero-tests/tests/transfers.rs @@ -41,7 +41,7 @@ async fn monero_transfers() -> anyhow::Result<()> { "Expect one tx key for each non-change output" ); - monero.generate_block().await?; + monero.generate_blocks().await?; let alice_txkey = tx_receipt .tx_keys diff --git a/monero-tests/tests/transfers_wrong_key.rs b/monero-tests/tests/transfers_wrong_key.rs index 1c90878a4c..8b411fcd7e 100644 --- a/monero-tests/tests/transfers_wrong_key.rs +++ b/monero-tests/tests/transfers_wrong_key.rs @@ -40,7 +40,7 @@ async fn monero_transfers_wrong_key() { "Expect one tx key for the output" ); - monero.generate_block().await.unwrap(); + monero.generate_blocks().await.unwrap(); // Use a wrong private key (just a simple constant key, not the real transfer key) let wrong_key = monero::PrivateKey::from_slice(&[ diff --git a/swap-db/src/alice.rs b/swap-db/src/alice.rs index 4d3c6cb57f..e3cc963c35 100644 --- a/swap-db/src/alice.rs +++ b/swap-db/src/alice.rs @@ -96,7 +96,10 @@ pub enum Alice { pub enum AliceEndState { SafelyAborted, BtcRedeemed, - XmrRefunded, + XmrRefunded { + #[serde(default)] + state3: Option, + }, BtcEarlyRefunded { state3: alice::State3, }, @@ -104,19 +107,34 @@ pub enum AliceEndState { state3: alice::State3, transfer_proof: TransferProof, }, + BtcRefundBurnPublished { + state3: alice::State3, + }, + BtcRefundBurnConfirmed { + state3: alice::State3, + }, + BtcFinalAmnestyGranted { + state3: alice::State3, + }, + BtcRefundFinalAmnestyPublished { + state3: alice::State3, + }, + BtcRefundFinalAmnestyConfirmed { + state3: alice::State3, + }, } impl From for Alice { fn from(alice_state: AliceState) -> Self { match alice_state { AliceState::Started { state3 } => Alice::Started { - state3: state3.as_ref().clone(), + state3: *state3, }, AliceState::BtcLockTransactionSeen { state3 } => Alice::BtcLockTransactionSeen { - state3: state3.as_ref().clone(), + state3: *state3, }, AliceState::BtcLocked { state3 } => Alice::BtcLocked { - state3: state3.as_ref().clone(), + state3: *state3, }, AliceState::XmrLockTransactionSent { monero_wallet_restore_blockheight, @@ -125,7 +143,7 @@ impl From for Alice { } => Alice::XmrLockTransactionSent { monero_wallet_restore_blockheight, transfer_proof, - state3: state3.as_ref().clone(), + state3: *state3, }, AliceState::XmrLocked { monero_wallet_restore_blockheight, @@ -134,7 +152,7 @@ impl From for Alice { } => Alice::XmrLocked { monero_wallet_restore_blockheight, transfer_proof, - state3: state3.as_ref().clone(), + state3: *state3, }, AliceState::XmrLockTransferProofSent { monero_wallet_restore_blockheight, @@ -143,7 +161,7 @@ impl From for Alice { } => Alice::XmrLockTransferProofSent { monero_wallet_restore_blockheight, transfer_proof, - state3: state3.as_ref().clone(), + state3: *state3, }, AliceState::EncSigLearned { monero_wallet_restore_blockheight, @@ -153,14 +171,14 @@ impl From for Alice { } => Alice::EncSigLearned { monero_wallet_restore_blockheight, transfer_proof, - state3: state3.as_ref().clone(), + state3: *state3, encrypted_signature: encrypted_signature.as_ref().clone(), }, AliceState::BtcRedeemTransactionPublished { state3, transfer_proof, } => Alice::BtcRedeemTransactionPublished { - state3: state3.as_ref().clone(), + state3: *state3, transfer_proof, }, AliceState::BtcRedeemed => Alice::Done(AliceEndState::BtcRedeemed), @@ -171,7 +189,7 @@ impl From for Alice { } => Alice::BtcCancelled { monero_wallet_restore_blockheight, transfer_proof, - state3: state3.as_ref().clone(), + state3: *state3, }, AliceState::BtcRefunded { monero_wallet_restore_blockheight, @@ -182,7 +200,7 @@ impl From for Alice { monero_wallet_restore_blockheight, transfer_proof, spend_key, - state3: state3.as_ref().clone(), + state3: *state3, }, AliceState::BtcPartiallyRefunded { monero_wallet_restore_blockheight, @@ -207,10 +225,10 @@ impl From for Alice { spend_key, }, AliceState::BtcEarlyRefundable { state3 } => Alice::BtcEarlyRefundable { - state3: state3.as_ref().clone(), + state3: *state3, }, AliceState::BtcEarlyRefunded(state3) => Alice::Done(AliceEndState::BtcEarlyRefunded { - state3: state3.as_ref().clone(), + state3: *state3, }), AliceState::BtcPunishable { monero_wallet_restore_blockheight, @@ -219,9 +237,36 @@ impl From for Alice { } => Alice::BtcPunishable { monero_wallet_restore_blockheight, transfer_proof, - state3: state3.as_ref().clone(), + state3: *state3, }, - AliceState::XmrRefunded => Alice::Done(AliceEndState::XmrRefunded), + AliceState::XmrRefunded { state3 } => Alice::Done(AliceEndState::XmrRefunded { + state3: state3.map(|s| s.as_ref().clone()), + }), + AliceState::BtcRefundBurnPublished { state3 } => { + Alice::Done(AliceEndState::BtcRefundBurnPublished { + state3: *state3, + }) + } + AliceState::BtcRefundBurnConfirmed { state3 } => { + Alice::Done(AliceEndState::BtcRefundBurnConfirmed { + state3: *state3, + }) + } + AliceState::BtcFinalAmnestyGranted { state3 } => { + Alice::Done(AliceEndState::BtcFinalAmnestyGranted { + state3: *state3, + }) + } + AliceState::BtcRefundFinalAmnestyPublished { state3 } => { + Alice::Done(AliceEndState::BtcRefundFinalAmnestyPublished { + state3: *state3, + }) + } + AliceState::BtcRefundFinalAmnestyConfirmed { state3 } => { + Alice::Done(AliceEndState::BtcRefundFinalAmnestyConfirmed { + state3: *state3, + }) + } AliceState::WaitingForCancelTimelockExpiration { monero_wallet_restore_blockheight, transfer_proof, @@ -229,7 +274,7 @@ impl From for Alice { } => Alice::WaitingForCancelTimelockExpiration { monero_wallet_restore_blockheight, transfer_proof, - state3: state3.as_ref().clone(), + state3: *state3, }, AliceState::CancelTimelockExpired { monero_wallet_restore_blockheight, @@ -238,13 +283,13 @@ impl From for Alice { } => Alice::CancelTimelockExpired { monero_wallet_restore_blockheight, transfer_proof, - state3: state3.as_ref().clone(), + state3: *state3, }, AliceState::BtcPunished { state3, transfer_proof, } => Alice::Done(AliceEndState::BtcPunished { - state3: state3.as_ref().clone(), + state3: *state3, transfer_proof, }), AliceState::SafelyAborted => Alice::Done(AliceEndState::SafelyAborted), @@ -384,7 +429,9 @@ impl From for AliceState { Alice::Done(end_state) => match end_state { AliceEndState::SafelyAborted => AliceState::SafelyAborted, AliceEndState::BtcRedeemed => AliceState::BtcRedeemed, - AliceEndState::XmrRefunded => AliceState::XmrRefunded, + AliceEndState::XmrRefunded { state3 } => AliceState::XmrRefunded { + state3: state3.map(Box::new), + }, AliceEndState::BtcPunished { state3, transfer_proof, @@ -395,6 +442,31 @@ impl From for AliceState { AliceEndState::BtcEarlyRefunded { state3 } => { AliceState::BtcEarlyRefunded(Box::new(state3)) } + AliceEndState::BtcRefundBurnPublished { state3 } => { + AliceState::BtcRefundBurnPublished { + state3: Box::new(state3), + } + } + AliceEndState::BtcRefundBurnConfirmed { state3 } => { + AliceState::BtcRefundBurnConfirmed { + state3: Box::new(state3), + } + } + AliceEndState::BtcFinalAmnestyGranted { state3 } => { + AliceState::BtcFinalAmnestyGranted { + state3: Box::new(state3), + } + } + AliceEndState::BtcRefundFinalAmnestyPublished { state3 } => { + AliceState::BtcRefundFinalAmnestyPublished { + state3: Box::new(state3), + } + } + AliceEndState::BtcRefundFinalAmnestyConfirmed { state3 } => { + AliceState::BtcRefundFinalAmnestyConfirmed { + state3: Box::new(state3), + } + } }, } } diff --git a/swap-env/src/config.rs b/swap-env/src/config.rs index 8a26691342..f1607a98a2 100644 --- a/swap-env/src/config.rs +++ b/swap-env/src/config.rs @@ -126,12 +126,18 @@ pub struct RefundPolicy { /// This protects the maker against griefing attacks. #[serde(default = "default_taker_refund_ratio")] pub taker_refund_ratio: Decimal, + /// If true, Alice will publish TxRefundBurn after refunding her XMR, + /// denying Bob access to the amnesty output. Alice can later grant + /// final amnesty to return the funds to Bob. + #[serde(default)] + pub burn_on_refund: bool, } impl Default for RefundPolicy { fn default() -> Self { Self { taker_refund_ratio: default_taker_refund_ratio(), + burn_on_refund: false, } } } diff --git a/swap-machine/src/alice/mod.rs b/swap-machine/src/alice/mod.rs index 872f76c2aa..fc61eea3e6 100644 --- a/swap-machine/src/alice/mod.rs +++ b/swap-machine/src/alice/mod.rs @@ -87,7 +87,26 @@ pub enum AliceState { state3: Box, }, // TODO: save redeem transaction id - XmrRefunded, + XmrRefunded { + state3: Option>, + }, + BtcRefundBurnPublished { + state3: Box, + }, + BtcRefundBurnConfirmed { + state3: Box, + }, + /// Operator has decided to grant final amnesty to Bob. + /// This state will publish TxFinalAmnesty and transition to BtcRefundFinalAmnestyPublished. + BtcFinalAmnestyGranted { + state3: Box, + }, + BtcRefundFinalAmnestyPublished { + state3: Box, + }, + BtcRefundFinalAmnestyConfirmed { + state3: Box, + }, WaitingForCancelTimelockExpiration { monero_wallet_restore_blockheight: BlockHeight, transfer_proof: TransferProof, @@ -111,14 +130,20 @@ pub enum AliceState { } pub fn is_complete(state: &AliceState) -> bool { - matches!( - state, - AliceState::XmrRefunded - | AliceState::BtcRedeemed - | AliceState::BtcPunished { .. } - | AliceState::SafelyAborted - | AliceState::BtcEarlyRefunded(_) - ) + match state { + // XmrRefunded is only complete if we don't need to publish TxRefundBurn + AliceState::XmrRefunded { state3 } => match state3 { + Some(s3) if s3.should_publish_tx_refund_burn == Some(true) => false, + _ => true, + }, + AliceState::BtcRedeemed + | AliceState::BtcPunished { .. } + | AliceState::SafelyAborted + | AliceState::BtcEarlyRefunded(_) + | AliceState::BtcRefundBurnConfirmed { .. } + | AliceState::BtcRefundFinalAmnestyConfirmed { .. } => true, + _ => false, + } } impl fmt::Display for AliceState { @@ -144,7 +169,16 @@ impl fmt::Display for AliceState { AliceState::BtcPunished { .. } => write!(f, "btc is punished"), AliceState::SafelyAborted => write!(f, "safely aborted"), AliceState::BtcPunishable { .. } => write!(f, "btc is punishable"), - AliceState::XmrRefunded => write!(f, "xmr is refunded"), + AliceState::XmrRefunded { .. } => write!(f, "xmr is refunded"), + AliceState::BtcRefundBurnPublished { .. } => write!(f, "btc refund burn published"), + AliceState::BtcRefundBurnConfirmed { .. } => write!(f, "btc refund burn confirmed"), + AliceState::BtcFinalAmnestyGranted { .. } => write!(f, "btc final amnesty granted"), + AliceState::BtcRefundFinalAmnestyPublished { .. } => { + write!(f, "btc final amnesty published") + } + AliceState::BtcRefundFinalAmnestyConfirmed { .. } => { + write!(f, "btc final amnesty confirmed") + } AliceState::WaitingForCancelTimelockExpiration { .. } => { write!(f, "waiting for cancel timelock expiration") } @@ -892,6 +926,58 @@ impl State3 { ) } + /// Check if we have Bob's signature for TxRefundBurn. + pub fn has_tx_refund_burn_sig(&self) -> bool { + self.tx_refund_burn_sig_bob.is_some() + } + + /// Construct TxRefundBurn from tx_partial_refund output. + pub fn tx_refund_burn(&self) -> Result { + TxRefundBurn::new( + &self.tx_partial_refund()?, + self.a.public(), + self.B, + self.tx_refund_burn_fee + .context("Missing tx_refund_burn_fee")?, + ) + } + + /// Construct signed TxRefundBurn using Alice's key and Bob's presigned signature. + pub fn signed_refund_burn_transaction(&self) -> Result { + let tx_refund_burn = self.tx_refund_burn()?; + + tx_refund_burn.complete_as_alice( + self.a.clone(), + self.B, + self.tx_refund_burn_sig_bob + .clone() + .context("missing Bob's signature for TxRefundBurn")?, + ) + } + + /// Construct TxFinalAmnesty from tx_refund_burn output. + pub fn tx_final_amnesty(&self) -> Result { + Ok(TxFinalAmnesty::new( + &self.tx_refund_burn()?, + &self.refund_address, + self.tx_final_amnesty_fee + .context("Missing tx_final_amnesty_fee")?, + )) + } + + /// Construct signed TxFinalAmnesty using Alice's key and Bob's presigned signature. + pub fn signed_final_amnesty_transaction(&self) -> Result { + let tx_final_amnesty = self.tx_final_amnesty()?; + + tx_final_amnesty.complete_as_alice( + self.a.clone(), + self.B, + self.tx_final_amnesty_sig_bob + .clone() + .context("missing Bob's signature for TxFinalAmnesty")?, + ) + } + pub async fn punish_btc( &self, bitcoin_wallet: &dyn bitcoin_wallet::BitcoinWallet, diff --git a/swap-machine/src/bob/mod.rs b/swap-machine/src/bob/mod.rs index 1496a14487..416480588d 100644 --- a/swap-machine/src/bob/mod.rs +++ b/swap-machine/src/bob/mod.rs @@ -266,6 +266,7 @@ pub fn is_complete(state: &BobState) -> bool { | BobState::BtcEarlyRefunded { .. } | BobState::BtcAmnestyConfirmed { .. } | BobState::BtcFinalAmnestyConfirmed { .. } + | BobState::BtcRefundBurnt { .. } | BobState::XmrRedeemed { .. } | BobState::SafelyAborted ) diff --git a/swap/src/asb/event_loop.rs b/swap/src/asb/event_loop.rs index 0f811ac600..e682daad97 100644 --- a/swap/src/asb/event_loop.rs +++ b/swap/src/asb/event_loop.rs @@ -840,8 +840,7 @@ fn apply_bitcoin_amnesty_policy( swap_amount: bitcoin::Amount, refund_policy: &RefundPolicy, ) -> Result<(bitcoin::Amount, bool)> { - // TODO: decide this somehow - let should_burn_on_refund = false; + let should_burn_on_refund = refund_policy.burn_on_refund; // When ratio is 1.0, no amnesty - use full refund path if refund_policy.taker_refund_ratio == Decimal::ONE { diff --git a/swap/src/asb/recovery/cancel.rs b/swap/src/asb/recovery/cancel.rs index 7e58193c82..9cc8a29cb4 100644 --- a/swap/src/asb/recovery/cancel.rs +++ b/swap/src/asb/recovery/cancel.rs @@ -41,7 +41,12 @@ pub async fn cancel( // Alice already in final state | AliceState::BtcRedeemed - | AliceState::XmrRefunded + | AliceState::XmrRefunded { .. } + | AliceState::BtcRefundBurnPublished { .. } + | AliceState::BtcRefundBurnConfirmed { .. } + | AliceState::BtcFinalAmnestyGranted { .. } + | AliceState::BtcRefundFinalAmnestyPublished { .. } + | AliceState::BtcRefundFinalAmnestyConfirmed { .. } | AliceState::BtcEarlyRefundable { .. } | AliceState::BtcEarlyRefunded(_) | AliceState::BtcPunished { .. } diff --git a/swap/src/asb/recovery/punish.rs b/swap/src/asb/recovery/punish.rs index 76b09a882d..d585564d1d 100644 --- a/swap/src/asb/recovery/punish.rs +++ b/swap/src/asb/recovery/punish.rs @@ -42,7 +42,12 @@ pub async fn punish( | AliceState::BtcLockTransactionSeen { .. } | AliceState::BtcLocked { .. } | AliceState::BtcRedeemed { .. } - | AliceState::XmrRefunded + | AliceState::XmrRefunded { .. } + | AliceState::BtcRefundBurnPublished { .. } + | AliceState::BtcRefundBurnConfirmed { .. } + | AliceState::BtcFinalAmnestyGranted { .. } + | AliceState::BtcRefundFinalAmnestyPublished { .. } + | AliceState::BtcRefundFinalAmnestyConfirmed { .. } | AliceState::BtcPunished { .. } | AliceState::BtcEarlyRefundable { .. } | AliceState::BtcEarlyRefunded(_) diff --git a/swap/src/asb/recovery/redeem.rs b/swap/src/asb/recovery/redeem.rs index c5bed032c9..48dcbf00ef 100644 --- a/swap/src/asb/recovery/redeem.rs +++ b/swap/src/asb/recovery/redeem.rs @@ -92,7 +92,12 @@ pub async fn redeem( | AliceState::BtcPartiallyRefunded { .. } | AliceState::BtcPunishable { .. } | AliceState::BtcRedeemed - | AliceState::XmrRefunded + | AliceState::XmrRefunded { .. } + | AliceState::BtcRefundBurnPublished { .. } + | AliceState::BtcRefundBurnConfirmed { .. } + | AliceState::BtcFinalAmnestyGranted { .. } + | AliceState::BtcRefundFinalAmnestyPublished { .. } + | AliceState::BtcRefundFinalAmnestyConfirmed { .. } | AliceState::XmrRefundable { .. } | AliceState::BtcEarlyRefundable { .. } | AliceState::BtcEarlyRefunded(_) diff --git a/swap/src/asb/recovery/refund.rs b/swap/src/asb/recovery/refund.rs index c79d6a41ab..22ba86f6de 100644 --- a/swap/src/asb/recovery/refund.rs +++ b/swap/src/asb/recovery/refund.rs @@ -60,7 +60,12 @@ pub async fn refund( // Alice already in final state AliceState::BtcRedeemTransactionPublished { .. } | AliceState::BtcRedeemed - | AliceState::XmrRefunded + | AliceState::XmrRefunded { .. } + | AliceState::BtcRefundBurnPublished { .. } + | AliceState::BtcRefundBurnConfirmed { .. } + | AliceState::BtcFinalAmnestyGranted { .. } + | AliceState::BtcRefundFinalAmnestyPublished { .. } + | AliceState::BtcRefundFinalAmnestyConfirmed { .. } | AliceState::BtcEarlyRefundable { .. } | AliceState::BtcEarlyRefunded(_) | AliceState::BtcPunished { .. } @@ -97,7 +102,9 @@ pub async fn refund( ) .await?; - let state = AliceState::XmrRefunded; + let state = AliceState::XmrRefunded { + state3: Some(state3), + }; db.insert_latest_state(swap_id, state.clone().into()) .await?; diff --git a/swap/src/asb/recovery/safely_abort.rs b/swap/src/asb/recovery/safely_abort.rs index f5382d3cc5..aed7c14d4c 100644 --- a/swap/src/asb/recovery/safely_abort.rs +++ b/swap/src/asb/recovery/safely_abort.rs @@ -33,7 +33,12 @@ pub async fn safely_abort(swap_id: Uuid, db: Arc) -> Result AliceState::XmrRefunded, + AliceState::XmrRefunded { state3 } => { + let Some(state3) = state3 else { + tracing::info!( + "Running a pre-partial refund swap, there is no amnesty output to burn" + ); + return Ok(AliceState::XmrRefunded { state3: None }); + }; + + let signed_tx = state3.signed_refund_burn_transaction().context("Can't burn the amnesty output after Bob refunded because we couldn't construct the ")?; + + bitcoin_wallet + .ensure_broadcasted(signed_tx, "refund_burn") + .await + .context("Couldn't publish TxRefundBurn")?; + + AliceState::BtcRefundBurnPublished { state3 } + } + AliceState::BtcRefundBurnPublished { state3 } => { + let tx_refund_burn = state3 + .tx_refund_burn() + .context("Can't construct TxRefundBurn even though we published it")?; + + let subscription = bitcoin_wallet.subscribe_to(Box::new(tx_refund_burn)).await; + + subscription + .wait_until_final() + .await + .context("Failed to wait for TxRefundBurn to be confirmed")?; + + AliceState::BtcRefundBurnConfirmed { state3 } + } + AliceState::BtcRefundBurnConfirmed { state3 } => { + // Nothing to do here. Final amnesty is triggered manually. + AliceState::BtcRefundBurnConfirmed { state3 } + } + AliceState::BtcFinalAmnestyGranted { state3 } => { + // Operator has decided to grant final amnesty to Bob + let signed_tx = state3 + .signed_final_amnesty_transaction() + .context("Failed to construct signed TxFinalAmnesty")?; + + bitcoin_wallet + .ensure_broadcasted(signed_tx, "final_amnesty") + .await + .context("Failed to publish TxFinalAmnesty")?; + + tracing::info!("TxFinalAmnesty published successfully"); + + AliceState::BtcRefundFinalAmnestyPublished { state3 } + } + AliceState::BtcRefundFinalAmnestyPublished { state3 } => { + // Wait for TxFinalAmnesty to be confirmed + let tx_final_amnesty = state3 + .tx_final_amnesty() + .context("Couldn't construct TxFinalAmnesty even though we have published it")?; + + let subscription = bitcoin_wallet + .subscribe_to(Box::new(tx_final_amnesty)) + .await; + + subscription + .wait_until_final() + .await + .context("Failed to wait for TxFinalAmnesty to be confirmed")?; + + AliceState::BtcRefundFinalAmnestyConfirmed { state3 } + } + AliceState::BtcRefundFinalAmnestyConfirmed { state3 } => { + AliceState::BtcRefundFinalAmnestyConfirmed { state3 } + } AliceState::BtcRedeemed => AliceState::BtcRedeemed, AliceState::BtcPunished { state3, diff --git a/swap/tests/harness/mod.rs b/swap/tests/harness/mod.rs index f5171906e0..9ef47fd4b3 100644 --- a/swap/tests/harness/mod.rs +++ b/swap/tests/harness/mod.rs @@ -212,6 +212,7 @@ pub async fn setup_test( developer_tip, refund_policy: refund_policy.unwrap_or_default(), monerod_container_id: containers._monerod_container.id().to_string(), + monero, }; testfn(test).await.unwrap() @@ -695,6 +696,10 @@ pub struct TestContext { // Store the container ID as String instead of reference monerod_container_id: String, + + // Handle for the Monero deamon. This allows us to skip waiting times by generating + // blocks instantly + pub monero: Monero, } impl TestContext { @@ -781,7 +786,7 @@ impl TestContext { } pub async fn assert_alice_refunded(&mut self, state: AliceState) { - assert!(matches!(state, AliceState::XmrRefunded)); + assert!(matches!(state, AliceState::XmrRefunded { .. })); assert_eventual_balance( self.alice_bitcoin_wallet.as_ref(), @@ -801,6 +806,51 @@ impl TestContext { .unwrap(); } + pub async fn assert_alice_refund_burn_confirmed(&mut self, state: AliceState) { + assert!(matches!(state, AliceState::BtcRefundBurnConfirmed { .. })); + + // Same as refunded - Alice still has her XMR back + assert_eventual_balance( + self.alice_bitcoin_wallet.as_ref(), + Ordering::Equal, + self.alice_refunded_btc_balance(), + ) + .await + .unwrap(); + + assert_eventual_balance( + &*self.alice_monero_wallet.main_wallet().await, + Ordering::Greater, + self.alice_refunded_xmr_balance(), + ) + .await + .unwrap(); + } + + pub async fn assert_alice_final_amnesty_confirmed(&mut self, state: AliceState) { + assert!(matches!( + state, + AliceState::BtcRefundFinalAmnestyConfirmed { .. } + )); + + // Same as refunded - Alice still has her XMR back + assert_eventual_balance( + self.alice_bitcoin_wallet.as_ref(), + Ordering::Equal, + self.alice_refunded_btc_balance(), + ) + .await + .unwrap(); + + assert_eventual_balance( + &*self.alice_monero_wallet.main_wallet().await, + Ordering::Greater, + self.alice_refunded_xmr_balance(), + ) + .await + .unwrap(); + } + pub async fn assert_alice_developer_tip_received(&self) { assert_eventual_balance( &*self.developer_tip_monero_wallet.main_wallet().await, @@ -1000,7 +1050,10 @@ impl TestContext { state6.tx_partial_refund_fee.expect("partial refund fee"), state6.tx_final_amnesty_fee.expect("final amnesty fee"), ), - _ => panic!("Bob is not in btc final amnesty confirmed state: {:?}", state), + _ => panic!( + "Bob is not in btc final amnesty confirmed state: {:?}", + state + ), }; let lock_tx_bitcoin_fee = self .bob_bitcoin_wallet @@ -1337,6 +1390,18 @@ pub mod alice_run_until { pub fn is_btc_partially_refunded(state: &AliceState) -> bool { matches!(state, AliceState::BtcPartiallyRefunded { .. }) } + + pub fn is_xmr_refunded(state: &AliceState) -> bool { + matches!(state, AliceState::XmrRefunded { .. }) + } + + pub fn is_btc_refund_burn_confirmed(state: &AliceState) -> bool { + matches!(state, AliceState::BtcRefundBurnConfirmed { .. }) + } + + pub fn is_btc_final_amnesty_confirmed(state: &AliceState) -> bool { + matches!(state, AliceState::BtcRefundFinalAmnestyConfirmed { .. }) + } } pub mod bob_run_until { @@ -1363,7 +1428,10 @@ pub mod bob_run_until { } pub fn is_waiting_for_remaining_refund_timelock(state: &BobState) -> bool { - matches!(state, BobState::WaitingForRemainingRefundTimelockExpiration(..)) + matches!( + state, + BobState::WaitingForRemainingRefundTimelockExpiration(..) + ) } pub fn is_remaining_refund_timelock_expired(state: &BobState) -> bool { @@ -1428,3 +1496,23 @@ impl GetConfig for FastAmnestyConfig { } } } + +/// Config with a longer remaining refund timelock for burn tests. +/// Alice needs time to refund XMR (which waits for 10 Monero confirmations) +/// before publishing the burn transaction. +pub struct SlowAmnestyConfig; + +impl GetConfig for SlowAmnestyConfig { + fn get_config() -> Config { + Config { + bitcoin_cancel_timelock: CancelTimelock::new(10).into(), + // Much longer timelock to give Alice time to: + // 1. Wait for 10 Monero confirmations on the lock tx + // 2. Sweep the XMR to her wallet + // 3. Then publish the burn transaction + // In regtest, each BTC block is ~5s, so 100 blocks is ~8 minutes + bitcoin_remaining_refund_timelock: 100, + ..env::Regtest::get_config() + } + } +} diff --git a/swap/tests/partial_refund_bob_claims_amnesty.rs b/swap/tests/partial_refund_bob_claims_amnesty.rs index 4c1c0c7c83..a37eb4afae 100644 --- a/swap/tests/partial_refund_bob_claims_amnesty.rs +++ b/swap/tests/partial_refund_bob_claims_amnesty.rs @@ -13,8 +13,10 @@ use swap_env::config::RefundPolicy; #[tokio::test] async fn given_partial_refund_bob_claims_amnesty_after_timelock() { // Use 95% refund ratio - Bob gets 95% immediately, 5% locked in amnesty + // Alice does NOT burn - Bob can claim amnesty after timelock let refund_policy = Some(RefundPolicy { taker_refund_ratio: Decimal::new(95, 2), // 0.95 = 95% + burn_on_refund: false, }); harness::setup_test(FastAmnestyConfig, None, refund_policy, |mut ctx| async move { From 88e1bfe6d3d7d58e255e03d3423e06caa926dea3 Mon Sep 17 00:00:00 2001 From: einliterflasche Date: Sat, 27 Dec 2025 11:47:47 +0100 Subject: [PATCH 076/113] add new tests to ci.yml --- .github/workflows/ci.yml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 82a97c4e40..e33b3c1202 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -184,6 +184,10 @@ jobs: test_name: happy_path_alice_does_not_send_transfer_proof - package: swap test_name: partial_refund_bob_claims_amnesty + - package: swap + test_name: partial_refund_alice_burns + - package: swap + test_name: partial_refund_alice_grants_final_amnesty - package: monero-tests test_name: reserve_proof - package: monero-tests From b3696879e908b237a99372654d6028c1c37011ff Mon Sep 17 00:00:00 2001 From: einliterflasche Date: Sat, 27 Dec 2025 22:50:24 +0100 Subject: [PATCH 077/113] add new integration tests for burn scenarios, add BurnRefund command to asb RpcServer --- swap-controller-api/src/lib.rs | 12 +++ swap/src/asb/event_loop.rs | 76 +++++++++++++++ swap/src/asb/rpc/server.rs | 17 ++++ swap/src/protocol/alice/swap.rs | 64 +++++++++++-- swap/tests/harness/mod.rs | 61 ++++++++++-- swap/tests/partial_refund_alice_burns.rs | 86 +++++++++++++++++ ...artial_refund_alice_burns_after_command.rs | 96 +++++++++++++++++++ ...rtial_refund_alice_grants_final_amnesty.rs | 76 +++++++++++++++ 8 files changed, 471 insertions(+), 17 deletions(-) create mode 100644 swap/tests/partial_refund_alice_burns.rs create mode 100644 swap/tests/partial_refund_alice_burns_after_command.rs create mode 100644 swap/tests/partial_refund_alice_grants_final_amnesty.rs diff --git a/swap-controller-api/src/lib.rs b/swap-controller-api/src/lib.rs index afc1f4de28..4d548dd789 100644 --- a/swap-controller-api/src/lib.rs +++ b/swap-controller-api/src/lib.rs @@ -88,6 +88,12 @@ pub struct MoneroSeedResponse { pub restore_height: u64, } +#[derive(Serialize, Deserialize, Debug, Clone)] +pub struct SetBurnOnRefundRequest { + pub swap_id: String, + pub burn: bool, +} + #[rpc(client, server)] pub trait AsbApi { #[method(name = "check_connection")] @@ -112,4 +118,10 @@ pub trait AsbApi { async fn get_swaps(&self) -> Result, ErrorObjectOwned>; #[method(name = "registration_status")] async fn registration_status(&self) -> Result; + #[method(name = "set_burn_on_refund")] + async fn set_burn_on_refund( + &self, + swap_id: String, + burn: bool, + ) -> Result<(), ErrorObjectOwned>; } diff --git a/swap/src/asb/event_loop.rs b/swap/src/asb/event_loop.rs index e682daad97..edd029472b 100644 --- a/swap/src/asb/event_loop.rs +++ b/swap/src/asb/event_loop.rs @@ -69,6 +69,12 @@ where /// the sender is removed from this map. recv_encrypted_signature: HashMap>, + /// Stores where to send burn-on-refund instructions to + /// The corresponding receiver is stored in the EventLoopHandle + /// Uses watch channel to allow multiple updates before consumption + recv_burn_on_refund_instruction: + HashMap>>, + /// Once we receive an [`EncryptedSignature`] from Bob, we forward it to the EventLoopHandle. /// Once the EventLoopHandle acknowledges the receipt of the [`EncryptedSignature`], we need to confirm this to Bob. /// When the EventLoopHandle acknowledges the receipt, a future in this collection resolves and returns the libp2p channel @@ -170,6 +176,7 @@ where refund_policy, quote_cache, recv_encrypted_signature: Default::default(), + recv_burn_on_refund_instruction: Default::default(), inflight_encrypted_signatures: Default::default(), outgoing_transfer_proofs_requests, outgoing_transfer_proofs_sender, @@ -555,6 +562,15 @@ where let _ = respond_to.send(registrations); } + EventLoopRequest::SetBurnOnRefund { swap_id, burn, respond_to } => { + let result = if let Some(sender) = self.recv_burn_on_refund_instruction.get(&swap_id) { + sender.send(Some(burn)) + .map_err(|_| anyhow!("Failed to send burn instruction - receiver dropped")) + } else { + Err(anyhow!("No active swap found with id {}", swap_id)) + }; + let _ = respond_to.send(result); + } } } } @@ -692,12 +708,20 @@ where self.recv_encrypted_signature .insert(swap_id, encrypted_signature_sender); + // Create a watch channel for burn-on-refund instructions + // Uses watch instead of bmrng to allow multiple updates before consumption + let (burn_instruction_sender, burn_instruction_receiver) = + tokio::sync::watch::channel(None); + self.recv_burn_on_refund_instruction + .insert(swap_id, burn_instruction_sender); + let transfer_proof_sender = self.outgoing_transfer_proofs_sender.clone(); EventLoopHandle { swap_id, peer, recv_encrypted_signature: tokio::sync::Mutex::new(Some(encrypted_signature_receiver)), + recv_burn_on_refund_instruction: tokio::sync::Mutex::new(burn_instruction_receiver), transfer_proof_sender: tokio::sync::Mutex::new(Some(transfer_proof_sender)), } } @@ -710,6 +734,8 @@ pub struct EventLoopHandle { peer: PeerId, recv_encrypted_signature: tokio::sync::Mutex>>, + recv_burn_on_refund_instruction: + tokio::sync::Mutex>>, #[allow(clippy::type_complexity)] transfer_proof_sender: tokio::sync::Mutex< Option< @@ -830,6 +856,34 @@ impl EventLoopHandle { Ok(()) } + + /// Wait for a NEW burn-on-refund instruction from the operator + /// + /// This method waits until the operator sends a new decision via the EventLoopService. + /// Use this in select! arms to react to operator commands. + /// + /// Returns the new burn decision when one is received. + pub async fn wait_for_burn_on_refund_instruction(&self) -> Result { + let mut guard = self.recv_burn_on_refund_instruction.lock().await; + + guard + .changed() + .await + .map_err(|_| anyhow!("Burn instruction sender was dropped"))?; + + let value = *guard.borrow(); + Ok(value.expect("changed() returned Ok, so value should be set")) + } + + /// Get the current burn-on-refund instruction value + /// + /// Returns Some(bool) if an instruction has been set, None otherwise. + /// Use this to check the current decision before taking action. + pub async fn get_burn_on_refund_instruction(&self) -> Option { + let guard = self.recv_burn_on_refund_instruction.lock().await; + let value = *guard.borrow(); + value + } } /// For a new swap of `swap_amount`, this function calculates how much @@ -924,6 +978,11 @@ mod service { Vec, >, }, + SetBurnOnRefund { + swap_id: Uuid, + burn: bool, + respond_to: oneshot::Sender>, + }, } /// Tower service for communicating with the EventLoop @@ -969,6 +1028,23 @@ mod service { rx.await .map_err(|_| anyhow::anyhow!("EventLoop service did not respond")) } + + /// Set the burn-on-refund decision for a specific swap + /// + /// This can be called multiple times to update the decision before + /// the swap state machine polls for it. + pub async fn set_burn_on_refund(&self, swap_id: Uuid, burn: bool) -> anyhow::Result<()> { + let (tx, rx) = oneshot::channel(); + self.sender + .send(EventLoopRequest::SetBurnOnRefund { + swap_id, + burn, + respond_to: tx, + }) + .map_err(|_| anyhow::anyhow!("EventLoop service is down"))?; + rx.await + .map_err(|_| anyhow::anyhow!("EventLoop service did not respond"))? + } } } diff --git a/swap/src/asb/rpc/server.rs b/swap/src/asb/rpc/server.rs index 402ff7f58c..2595bbe33b 100644 --- a/swap/src/asb/rpc/server.rs +++ b/swap/src/asb/rpc/server.rs @@ -243,6 +243,23 @@ impl AsbApiServer for RpcImpl { Ok(RegistrationStatusResponse { registrations }) } + + async fn set_burn_on_refund( + &self, + swap_id: String, + burn: bool, + ) -> Result<(), ErrorObjectOwned> { + let swap_id = uuid::Uuid::parse_str(&swap_id) + .context("Invalid swap ID") + .into_json_rpc_result()?; + + self.event_loop_service + .set_burn_on_refund(swap_id, burn) + .await + .into_json_rpc_result()?; + + Ok(()) + } } trait IntoJsonRpcResult { diff --git a/swap/src/protocol/alice/swap.rs b/swap/src/protocol/alice/swap.rs index 995439a143..853a9b3500 100644 --- a/swap/src/protocol/alice/swap.rs +++ b/swap/src/protocol/alice/swap.rs @@ -425,6 +425,17 @@ where state3, } } + burn_instruction = event_loop_handle.wait_for_burn_on_refund_instruction() => { + let burn = burn_instruction.context("Failed to receive burn instruction")?; + let mut updated_state3 = (*state3).clone(); + updated_state3.should_publish_tx_refund_burn = Some(burn); + + AliceState::XmrLockTransferProofSent { + monero_wallet_restore_blockheight, + transfer_proof, + state3: Box::new(updated_state3), + } + } } } AliceState::EncSigLearned { @@ -543,15 +554,26 @@ where .subscribe_to(Box::new(state3.tx_lock.clone())) .await; - // TODO: Retry here - tx_lock_status_subscription - .wait_until_confirmed_with(state3.cancel_timelock) - .await?; + select! { + result = tx_lock_status_subscription.wait_until_confirmed_with(state3.cancel_timelock) => { + result?; + AliceState::CancelTimelockExpired { + monero_wallet_restore_blockheight, + transfer_proof, + state3, + } + } + burn_instruction = event_loop_handle.wait_for_burn_on_refund_instruction() => { + let burn = burn_instruction.context("Failed to receive burn instruction")?; + let mut updated_state3 = (*state3).clone(); + updated_state3.should_publish_tx_refund_burn = Some(burn); - AliceState::CancelTimelockExpired { - monero_wallet_restore_blockheight, - transfer_proof, - state3, + AliceState::WaitingForCancelTimelockExpiration { + monero_wallet_restore_blockheight, + transfer_proof, + state3: Box::new(updated_state3), + } + } } } AliceState::CancelTimelockExpired { @@ -648,6 +670,17 @@ where state3, } } + burn_instruction = event_loop_handle.wait_for_burn_on_refund_instruction() => { + let burn = burn_instruction.context("Failed to receive burn instruction")?; + let mut updated_state3 = (*state3).clone(); + updated_state3.should_publish_tx_refund_burn = Some(burn); + + AliceState::BtcCancelled { + monero_wallet_restore_blockheight, + transfer_proof, + state3: Box::new(updated_state3), + } + } } } AliceState::BtcRefunded { @@ -742,13 +775,26 @@ where .expect("We should never run out of retries while publishing the punish transaction") } AliceState::XmrRefunded { state3 } => { - let Some(state3) = state3 else { + // Only publish TxRefundBurn + let Some(mut state3) = state3 else { tracing::info!( "Running a pre-partial refund swap, there is no amnesty output to burn" ); return Ok(AliceState::XmrRefunded { state3: None }); }; + // Fetch the burn decision, if it was made via the controller + if let Some(burn_decision) = event_loop_handle.get_burn_on_refund_instruction().await { + state3.should_publish_tx_refund_burn = Some(burn_decision); + } + + if !state3.should_publish_tx_refund_burn.unwrap_or(false) { + tracing::info!("Not instructed to partially burn the takers refund. Finishing"); + return Ok(AliceState::XmrRefunded { + state3: Some(state3), + }); + } + let signed_tx = state3.signed_refund_burn_transaction().context("Can't burn the amnesty output after Bob refunded because we couldn't construct the ")?; bitcoin_wallet diff --git a/swap/tests/harness/mod.rs b/swap/tests/harness/mod.rs index 9ef47fd4b3..38d4f4f6d1 100644 --- a/swap/tests/harness/mod.rs +++ b/swap/tests/harness/mod.rs @@ -152,7 +152,13 @@ pub async fn setup_test( .parse() .expect("failed to parse Alice's address"); - let (alice_handle, alice_swap_handle) = start_alice( + let alice_rpc_port = std::net::TcpListener::bind(("127.0.0.1", 0)) + .unwrap() + .local_addr() + .unwrap() + .port(); + + let (alice_handle, alice_swap_handle, alice_rpc_server_handle) = start_alice( &alice_seed, alice_db_path.clone(), alice_listen_address.clone(), @@ -161,9 +167,14 @@ pub async fn setup_test( alice_monero_wallet.clone(), developer_tip.clone(), refund_policy.clone().unwrap_or_default(), + alice_rpc_port, ) .await; + let alice_rpc_client = jsonrpsee::http_client::HttpClientBuilder::default() + .build(format!("http://127.0.0.1:{}", alice_rpc_port)) + .expect("Failed to create RPC client"); + let bob_seed = Seed::random().unwrap(); let bob_starting_balances = StartingBalances::new(btc_amount * 10, monero::Amount::ZERO, None); let bob_monero_dir = TempDir::new().unwrap().path().join("bob-monero-wallets"); @@ -199,6 +210,9 @@ pub async fn setup_test( alice_seed, alice_db_path, alice_listen_address, + alice_rpc_port, + alice_rpc_server_handle, + alice_rpc_client, alice_starting_balances, alice_bitcoin_wallet, alice_monero_wallet, @@ -304,7 +318,12 @@ async fn start_alice( monero_wallet: Arc, developer_tip: TipConfig, refund_policy: RefundPolicy, -) -> (AliceApplicationHandle, Receiver) { + rpc_port: u16, +) -> ( + AliceApplicationHandle, + Receiver, + tokio_util::task::AbortOnDropHandle<()>, +) { if let Some(parent_dir) = db_path.parent() { ensure_directory_exists(parent_dir).unwrap(); } @@ -338,12 +357,12 @@ async fn start_alice( .unwrap(); swarm.listen_on(listen_address).unwrap(); - let (event_loop, swap_handle, _service) = asb::EventLoop::new( + let (event_loop, swap_handle, service) = asb::EventLoop::new( swarm, env_config, - bitcoin_wallet, - monero_wallet, - db, + bitcoin_wallet.clone(), + monero_wallet.clone(), + db.clone(), FixedRate::default(), min_buy, max_buy, @@ -353,10 +372,26 @@ async fn start_alice( ) .unwrap(); + let rpc_server_handle = asb::rpc::RpcServer::start( + "127.0.0.1".to_string(), + rpc_port, + bitcoin_wallet, + monero_wallet, + service, + db, + ) + .await + .expect("Failed to start RPC server") + .spawn(); + let peer_id = event_loop.peer_id(); let handle = tokio::spawn(event_loop.run()); - (AliceApplicationHandle { handle, peer_id }, swap_handle) + ( + AliceApplicationHandle { handle, peer_id }, + swap_handle, + rpc_server_handle, + ) } #[allow(clippy::too_many_arguments)] @@ -680,6 +715,10 @@ pub struct TestContext { alice_seed: Seed, alice_db_path: PathBuf, alice_listen_address: Multiaddr, + alice_rpc_port: u16, + #[allow(dead_code)] + alice_rpc_server_handle: tokio_util::task::AbortOnDropHandle<()>, + pub alice_rpc_client: jsonrpsee::http_client::HttpClient, alice_starting_balances: StartingBalances, alice_bitcoin_wallet: Arc, @@ -716,8 +755,12 @@ impl TestContext { pub async fn restart_alice(&mut self) { self.alice_handle.abort(); + // Abort the old RPC server to release the port before starting a new one + self.alice_rpc_server_handle.abort(); + // Small delay to ensure port is released + tokio::time::sleep(Duration::from_millis(100)).await; - let (alice_handle, alice_swap_handle) = start_alice( + let (alice_handle, alice_swap_handle, alice_rpc_server_handle) = start_alice( &self.alice_seed, self.alice_db_path.clone(), self.alice_listen_address.clone(), @@ -726,11 +769,13 @@ impl TestContext { self.alice_monero_wallet.clone(), self.developer_tip.clone(), self.refund_policy.clone(), + self.alice_rpc_port, ) .await; self.alice_handle = alice_handle; self.alice_swap_handle = alice_swap_handle; + self.alice_rpc_server_handle = alice_rpc_server_handle; } pub async fn alice_next_swap(&mut self) -> alice::Swap { diff --git a/swap/tests/partial_refund_alice_burns.rs b/swap/tests/partial_refund_alice_burns.rs new file mode 100644 index 0000000000..d32651c43d --- /dev/null +++ b/swap/tests/partial_refund_alice_burns.rs @@ -0,0 +1,86 @@ +pub mod harness; + +use std::time::Duration; + +use harness::alice_run_until::is_xmr_lock_transaction_sent; +use harness::bob_run_until::is_btc_partially_refunded; +use harness::SlowAmnestyConfig; +use rust_decimal::Decimal; +use swap::asb::FixedRate; +use swap::protocol::alice::AliceState; +use swap::protocol::bob::BobState; +use swap::protocol::{alice, bob}; +use swap_env::config::RefundPolicy; + +/// Bob locks Btc and Alice locks Xmr. Alice does not act so Bob does a partial +/// refund. Alice then burns the refund, denying Bob access to the amnesty. +#[tokio::test] +async fn given_partial_refund_alice_burns_the_amnesty() { + // Use 95% refund ratio - Bob gets 95% immediately, 5% locked in amnesty + // Alice burns the amnesty + let refund_policy = Some(RefundPolicy { + taker_refund_ratio: Decimal::new(95, 2), // 0.95 = 95% + burn_on_refund: true, + }); + + harness::setup_test( + SlowAmnestyConfig, + None, + refund_policy, + |mut ctx| async move { + // Start Bob's swap + let (bob_swap, bob_join_handle) = ctx.bob_swap().await; + let bob_swap_id = bob_swap.id; + // Bob runs until he has done the partial refund + let bob_swap = tokio::spawn(bob::run_until(bob_swap, is_btc_partially_refunded)); + + // Alice sends XMR lock then stops + let alice_swap = ctx.alice_next_swap().await; + let alice_swap = tokio::spawn(alice::run_until( + alice_swap, + is_xmr_lock_transaction_sent, + FixedRate::default(), + )); + + // Alice finishes first (just sends XMR lock and stops) + let alice_state = alice_swap.await??; + assert!(matches!( + alice_state, + AliceState::XmrLockTransactionSent { .. } + )); + + // Bob runs until partial refund is done + let bob_state = bob_swap.await??; + assert!(matches!(bob_state, BobState::BtcPartiallyRefunded { .. })); + + // Restart Alice so she can refund her XMR and burn Bob's amnesty + // Alice needs to publish burn BEFORE Bob's remaining refund timelock expires + ctx.restart_alice().await; + let alice_swap = ctx.alice_next_swap().await; + let alice_swap = tokio::spawn(alice::run(alice_swap, FixedRate::default())); + + // Bob continues - he's watching for TxRefundBurn while waiting for timelock + // Alice's burn should get published before Bob's timelock expires + let (bob_swap, _) = ctx + .stop_and_resume_bob_from_db(bob_join_handle, bob_swap_id) + .await; + let bob_swap = tokio::spawn(bob::run(bob_swap)); + + // Generate some Monero blocks such that Alice's + // monero refund transaction gets confirmed in time. + tokio::time::sleep(Duration::from_secs(15)).await; + ctx.monero.generate_blocks().await?; + + // Bob should end up in BtcRefundBurnt because Alice's burn beat his amnesty + let bob_state = bob_swap.await??; + ctx.assert_bob_refund_burnt(bob_state).await; + + // Alice should be in refund burn confirmed state + let alice_state = alice_swap.await??; + ctx.assert_alice_refund_burn_confirmed(alice_state).await; + + Ok(()) + }, + ) + .await; +} diff --git a/swap/tests/partial_refund_alice_burns_after_command.rs b/swap/tests/partial_refund_alice_burns_after_command.rs new file mode 100644 index 0000000000..14b620d773 --- /dev/null +++ b/swap/tests/partial_refund_alice_burns_after_command.rs @@ -0,0 +1,96 @@ +pub mod harness; + +use std::time::Duration; + +use harness::alice_run_until::is_xmr_lock_transaction_sent; +use harness::bob_run_until::is_btc_partially_refunded; +use harness::SlowAmnestyConfig; +use rust_decimal::Decimal; +use swap::asb::FixedRate; +use swap::protocol::alice::AliceState; +use swap::protocol::bob::BobState; +use swap::protocol::{alice, bob}; +use swap_controller_api::AsbApiClient; +use swap_env::config::RefundPolicy; + +/// Bob locks Btc and Alice locks Xmr. Alice does not act so Bob does a partial +/// refund. Alice receives an RPC command to burn the amnesty and then burns it. +#[tokio::test] +async fn given_partial_refund_alice_burns_after_command() { + // Use 95% refund ratio - Bob gets 95% immediately, 5% locked in amnesty + // Alice does NOT burn by default - burn_on_refund is false + let refund_policy = Some(RefundPolicy { + taker_refund_ratio: Decimal::new(95, 2), // 0.95 = 95% + burn_on_refund: false, // Do not burn by default + }); + + harness::setup_test( + SlowAmnestyConfig, + None, + refund_policy, + |mut ctx| async move { + // Start Bob's swap + let (bob_swap, bob_join_handle) = ctx.bob_swap().await; + let bob_swap_id = bob_swap.id; + // Bob runs until he has done the partial refund + let bob_swap = tokio::spawn(bob::run_until(bob_swap, is_btc_partially_refunded)); + + // Alice sends XMR lock then stops + let alice_swap = ctx.alice_next_swap().await; + let alice_swap_id = alice_swap.swap_id; + let alice_swap = tokio::spawn(alice::run_until( + alice_swap, + is_xmr_lock_transaction_sent, + FixedRate::default(), + )); + + // Alice finishes first (just sends XMR lock and stops) + let alice_state = alice_swap.await??; + assert!(matches!( + alice_state, + AliceState::XmrLockTransactionSent { .. } + )); + + // Bob runs until partial refund is done + let bob_state = bob_swap.await??; + assert!(matches!(bob_state, BobState::BtcPartiallyRefunded { .. })); + + // Restart Alice so she can refund her XMR and burn Bob's amnesty + // Alice needs to publish burn BEFORE Bob's remaining refund timelock expires + ctx.restart_alice().await; + let alice_swap = ctx.alice_next_swap().await; + + // Send RPC command to Alice to burn this swap's amnesty + // Must be done AFTER restart (so EventLoopHandle exists) but BEFORE running the swap + ctx.alice_rpc_client + .set_burn_on_refund(alice_swap_id.to_string(), true) + .await + .expect("Failed to send burn command to Alice"); + + let alice_swap = tokio::spawn(alice::run(alice_swap, FixedRate::default())); + + // Bob continues - he's watching for TxRefundBurn while waiting for timelock + // Alice's burn should get published before Bob's timelock expires + let (bob_swap, _) = ctx + .stop_and_resume_bob_from_db(bob_join_handle, bob_swap_id) + .await; + let bob_swap = tokio::spawn(bob::run(bob_swap)); + + // Generate some Monero blocks such that Alice's + // monero refund transaction gets confirmed in time. + tokio::time::sleep(Duration::from_secs(15)).await; + ctx.monero.generate_blocks().await?; + + // Bob should end up in BtcRefundBurnt because Alice's burn beat his amnesty + let bob_state = bob_swap.await??; + ctx.assert_bob_refund_burnt(bob_state).await; + + // Alice should be in refund burn confirmed state + let alice_state = alice_swap.await??; + ctx.assert_alice_refund_burn_confirmed(alice_state).await; + + Ok(()) + }, + ) + .await; +} diff --git a/swap/tests/partial_refund_alice_grants_final_amnesty.rs b/swap/tests/partial_refund_alice_grants_final_amnesty.rs new file mode 100644 index 0000000000..d43f1261eb --- /dev/null +++ b/swap/tests/partial_refund_alice_grants_final_amnesty.rs @@ -0,0 +1,76 @@ +pub mod harness; + +use harness::alice_run_until::{is_btc_refund_burn_confirmed, is_xmr_lock_transaction_sent}; +use harness::FastAmnestyConfig; +use rust_decimal::Decimal; +use swap::asb::FixedRate; +use swap::protocol::alice::AliceState; +use swap::protocol::{alice, bob}; +use swap_env::config::RefundPolicy; + +/// Bob locks Btc and Alice locks Xmr. Alice does not act so Bob does a partial +/// refund. Alice burns the refund, then later grants final amnesty to Bob. +/// NOTE: This test cannot pass yet because we haven't implemented the manual +/// trigger for final amnesty. BtcRefundBurnConfirmed is currently terminal. +#[tokio::test] +#[ignore = "final amnesty manual trigger not implemented yet"] +async fn given_partial_refund_alice_grants_final_amnesty() { + // Use 95% refund ratio - Bob gets 95% immediately, 5% locked in amnesty + // Alice burns the amnesty, then grants final amnesty + let refund_policy = Some(RefundPolicy { + taker_refund_ratio: Decimal::new(95, 2), // 0.95 = 95% + burn_on_refund: true, + }); + + harness::setup_test(FastAmnestyConfig, None, refund_policy, |mut ctx| async move { + let (bob_swap, _) = ctx.bob_swap().await; + let bob_swap = tokio::spawn(bob::run(bob_swap)); + + let alice_swap = ctx.alice_next_swap().await; + let alice_swap = tokio::spawn(alice::run_until( + alice_swap, + is_xmr_lock_transaction_sent, + FixedRate::default(), + )); + + // Alice finishes first (just sends XMR lock and stops) + let alice_state = alice_swap.await??; + assert!(matches!( + alice_state, + AliceState::XmrLockTransactionSent { .. } + )); + + // Bob continues: cancel timelock -> partial refund + // Bob will end up in BtcRefundBurnt because Alice burns the amnesty + let bob_state = bob_swap.await??; + ctx.assert_bob_refund_burnt(bob_state.clone()).await; + + // Restart Alice so she can refund her XMR and burn Bob's amnesty + ctx.restart_alice().await; + let alice_swap = ctx.alice_next_swap().await; + let alice_swap = tokio::spawn(alice::run_until( + alice_swap, + is_btc_refund_burn_confirmed, + FixedRate::default(), + )); + + let alice_state = alice_swap.await??; + assert!(matches!( + alice_state, + AliceState::BtcRefundBurnConfirmed { .. } + )); + + // TODO: Trigger final amnesty manually here + // This requires a manual command to Alice to grant final amnesty + // For now, this test is ignored. + + // Bob should receive final amnesty + // ctx.assert_bob_final_amnesty_received(bob_state).await; + + // Alice should be in final amnesty confirmed state + // ctx.assert_alice_final_amnesty_confirmed(alice_state).await; + + Ok(()) + }) + .await; +} From a78f3dfeb0e0d02026b315d58ffdb02ec9b8cd2a Mon Sep 17 00:00:00 2001 From: einliterflasche Date: Sat, 27 Dec 2025 23:14:33 +0100 Subject: [PATCH 078/113] unify ai agent instructions --- AGENT.md | 47 +---------------------------------------------- AGENTS.md | 31 +++++++++++++++++++++++++++++++ CLAUDE.md | 2 +- CONTRIBUTING.md | 13 +++++-------- 4 files changed, 38 insertions(+), 55 deletions(-) mode change 100644 => 120000 AGENT.md create mode 100644 AGENTS.md mode change 100644 => 120000 CLAUDE.md diff --git a/AGENT.md b/AGENT.md deleted file mode 100644 index c805dc7496..0000000000 --- a/AGENT.md +++ /dev/null @@ -1,46 +0,0 @@ -# Repo Overview - -This repository hosts the core of the eigenwallet project. The code base is a Rust workspace with multiple crates and a Tauri based GUI. - -## Important directories - -- **swap/** – contains the main Rust crate with two binaries: - - `swap` – command line interface for performing swaps. - - `asb` – Automated Swap Backend for market makers. - It also hosts library code shared between the binaries and integration tests. -- **src-tauri/** – Rust crate that exposes the `swap` functionality to the Tauri front end and bundles the application. -- **src-gui/** – The front‑end written in TypeScript/React and bundled by Tauri. Communicates with `src-tauri` via Tauri commands. -- **monero-rpc/** and **monero-wallet/** – helper crates for interacting with the Monero ecosystem. -- **docs/** – Next.js documentation site. -- **dev-docs/** – additional markdown documentation for CLI and ASB. - -## Frequently edited files - -Looking at the latest ten pull requests in `git log`, the following files appear most often: - -| File | Times Changed | -| --------------------------- | ------------- | -| `src-tauri/Cargo.toml` | 7 | -| `Cargo.lock` | 7 | -| `CHANGELOG.md` | 7 | -| `swap/Cargo.toml` | 6 | -| `src-tauri/tauri.conf.json` | 5 | -| `.github/workflows/ci.yml` | 3 | - -Other files such as `swap/src/bin/asb.rs`, `swap/src/cli/api.rs`, and `src-gui/package.json` showed up less frequently. - -## Component interaction - -- The **swap** crate implements the atomic swap logic and provides a CLI. The binaries under `swap/src/bin` (`swap.rs` and `asb.rs`) start the client and maker services respectively. -- **src-tauri** wraps the swap crate and exposes its functionality to the GUI via Tauri commands. It also bundles the application with the `src-gui` assets. -- **src-gui** is the TypeScript/React interface. It communicates with the Rust back end through the commands defined in `src-tauri`. -- Helper crates like **monero-rpc** and **monero-wallet** provide abstractions over external services. They are used by the swap crate to interact with Monero. -- Continuous integration and release workflows live in `.github/workflows`. They build binaries, create releases and lint the code base. - -## Pull request titles - -Use descriptive titles following the `(): ` format. Examples include: - -- `feat(gui): New feature` -- `fix(swap): Issue fixed` -- `refactor(ci): Ci changes` diff --git a/AGENT.md b/AGENT.md new file mode 120000 index 0000000000..47dc3e3d86 --- /dev/null +++ b/AGENT.md @@ -0,0 +1 @@ +AGENTS.md \ No newline at end of file diff --git a/AGENTS.md b/AGENTS.md new file mode 100644 index 0000000000..a9efe8781f --- /dev/null +++ b/AGENTS.md @@ -0,0 +1,31 @@ + - When asked about libp2p, check if a rust-libp2p folder exists which contains the cloned rust libp2p codebase. Read through to figure out what the best response it. If its a question about best practice when implementing protocols read @rust-libp2p/protocols/ specificially. + - Never do `cargo clean`. Building `monero-sys` takes ages, and cleaning the build cache will cause a full rebuilt (horrible). + `cargo clean` has never fixed a build problem. + - Before suggesting a change, always give at least a short (1 sentence) summary of which function you are editing and why. + - When being asked to add something, check whether there is a similar thing already implemented, the architecture of which you can follow. + For example, when asked to add a new Tauri command, check out how other tauri commands are implemented and what conventions they follow. + - + + - Think about seperation of concerns. This has many facets. But the most ofen there are questions like: + "Which part of the code should decide how to handle this situation". In the context of an error, the solution is: + - Never use fallback values. They lead to + - swallowed errors + - breaking invariances + - breaking other implicit assumptions + - destroy any meaning the value might have had. + Instead, if an error/invlaid state is encountered, the error should be propagated. + This is most often correctly done by using anyhow's "Context" and the question mark operator`.context("Failed to ")?`. + - Keep error handling simple: it is basically never wrong to just propagate the error using `?` and maybe add some basic context. + + Other facetts of seperation of concern include: + - should this function need to have access to this ? + - should this function decide a parameter itself or just take an argument? + + We follow the principle of LEAST SURPRISE. Take a step back, and come back with a fresh view. Then ask yourself: "would I expect this function to do ?". + If not, then don't do it. + +- coding style tips: + - keep the code succint. Prefer `if let` and `let ... else` to `match` whenever possible. + - avoid nesting if possible. + - prefer early returns to nesting. + diff --git a/CLAUDE.md b/CLAUDE.md deleted file mode 100644 index eb3a82a418..0000000000 --- a/CLAUDE.md +++ /dev/null @@ -1 +0,0 @@ -- When asked about libp2p, check if a rust-libp2p folder exists which contains the cloned rust libp2p codebase. Read through to figure out what the best response it. If its a question about best practice when implementing protocols read @rust-libp2p/protocols/ specificially. diff --git a/CLAUDE.md b/CLAUDE.md new file mode 120000 index 0000000000..47dc3e3d86 --- /dev/null +++ b/CLAUDE.md @@ -0,0 +1 @@ +AGENTS.md \ No newline at end of file diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 65a65cdf5e..779e37b085 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -7,20 +7,17 @@ Thank you for wanting to contribute to this project! There are a couple of things we are going to look out for in PRs and knowing them upfront is going to reduce the number of times we will be going back and forth, making things more efficient. 1. We have CI checks in place that validate formatting and code style. - Make sure `dprint check` and `cargo clippy` both finish without any warnings or errors. - If you don't already have it installed, you can obtain in [various ways](https://dprint.dev/install/). -2. Run the test suite with [cargo-nextest](https://nexte.st/docs/running/). - Install it using `cargo install cargo-nextest` and execute `cargo nextest run`. -3. All text document (`CHANGELOG.md`, `README.md`, etc) should follow the [semantic linebreaks](https://sembr.org/) specification. -4. We strive for atomic commits with good commit messages. + Make sure the branch is building with `--all-features` and `--all-targets` without errors + and all tests are passed. +2. All text document (`CHANGELOG.md`, `README.md`, etc) should follow the [semantic linebreaks](https://sembr.org/) specification. +3. We strive for atomic commits with good commit messages. As an inspiration, read [this](https://chris.beams.io/posts/git-commit/) blogpost. An atomic commit is a cohesive diff with formatting checks, linter and build passing. Ideally, all tests are passing as well but we acknowledge that this is not always possible depending on the change you are making. -5. If you are making any user visible changes, include a changelog entry. +4. If you are making any user visible changes, include a changelog entry. ## Contributing issues When contributing a feature request, please focus on your _problem_ as much as possible. It is okay to include ideas on how the feature should be implemented but they should be 2nd nature of your request. -For more loosely-defined problems and ideas, consider starting a [discussion](https://github.com/comit-network/xmr-btc-swap/discussions/new) instead of opening an issue. From 813d641b913059e56b9e05580270d528eb1e64e4 Mon Sep 17 00:00:00 2001 From: einliterflasche Date: Sun, 28 Dec 2025 00:15:45 +0100 Subject: [PATCH 079/113] swap-controller: add set-burn-on-refund --- swap-controller/src/cli.rs | 8 ++++++++ swap-controller/src/main.rs | 8 ++++++++ 2 files changed, 16 insertions(+) diff --git a/swap-controller/src/cli.rs b/swap-controller/src/cli.rs index f0f89f200f..accc3ddc23 100644 --- a/swap-controller/src/cli.rs +++ b/swap-controller/src/cli.rs @@ -37,4 +37,12 @@ pub enum Cmd { GetSwaps, /// Show rendezvous registration status RegistrationStatus, + /// Set whether to burn Bitcoin on refund for a swap + SetBurnOnRefund { + /// The swap ID + swap_id: String, + /// Whether to burn the Bitcoin (true or false) + #[arg(action = clap::ArgAction::Set)] + burn: bool, + }, } diff --git a/swap-controller/src/main.rs b/swap-controller/src/main.rs index 042b0b6b9e..acaac8864e 100644 --- a/swap-controller/src/main.rs +++ b/swap-controller/src/main.rs @@ -130,6 +130,14 @@ async fn dispatch(cmd: Cmd, client: impl AsbApiClient) -> anyhow::Result<()> { } } } + Cmd::SetBurnOnRefund { swap_id, burn } => { + client.set_burn_on_refund(swap_id.clone(), burn).await?; + if burn { + println!("Burn on refund enabled for swap {swap_id}"); + } else { + println!("Burn on refund disabled for swap {swap_id}"); + } + } } Ok(()) } From 79da674278e0668b112288d8f6f92d797bf3f708 Mon Sep 17 00:00:00 2001 From: einliterflasche Date: Sun, 28 Dec 2025 14:31:14 +0100 Subject: [PATCH 080/113] add docker test instructions to AGENTS.md --- AGENTS.md | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/AGENTS.md b/AGENTS.md index a9efe8781f..4fcf62b2ca 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -28,4 +28,22 @@ - keep the code succint. Prefer `if let` and `let ... else` to `match` whenever possible. - avoid nesting if possible. - prefer early returns to nesting. + +- Docker tests: We have an extended test suite that simulates a whole blockchain environment for the purpose of testing swaps end to end. + The docker tests are located in `swap/tests` and can be executed using `just docker_test `. Get a list of all docker tests by `just list-docker-tests`. +- If you changed something could possibly affect the success of a swap, make sure to run the integration tests that could possibly be affected. + Be very liberal with assuming what might be affected. +- If not explicitly instructed yet, ask the user whether you should add {unit, integration} tests if you just added / changed some behaviour/code +- The docker tests are long (multiple minutes) and produce tens of thousands of log messages. + Don't try to read all of that output, it will fill you context up before finishing + the initialization. + Instead, spawn them as a background-task (each as it's own). + Then you can simply check in on the current status by checking it's output every minute or so. + If you are claude, use claude codes native background task system and read from the `/tmp/claude/tasks/foo/output.tmp` pipe file, or whatever the path is. + If you are not claude, then do the thing that best accomplishises this. + +- Before claiming you finished, make sure everything compiles (`cargo c --all-features`). + Also all tests (`cargo c --tests`) and all targets (`cargo c --all-targets`) must compile. + + From d51518d793789e70e7cb732039e46838302a4f39 Mon Sep 17 00:00:00 2001 From: einliterflasche Date: Sun, 28 Dec 2025 14:38:38 +0100 Subject: [PATCH 081/113] asb: add final amnesty command to asb cli and controller + integration test --- Cargo.lock | 2 + swap-asb/src/command.rs | 21 +++ swap-asb/src/main.rs | 9 +- swap-controller-api/Cargo.toml | 1 + swap-controller-api/src/lib.rs | 9 +- swap-controller/Cargo.toml | 1 + swap-controller/src/cli.rs | 8 +- swap-controller/src/main.rs | 6 +- swap-machine/src/bob/mod.rs | 1 - swap/src/asb.rs | 1 + swap/src/asb/event_loop.rs | 71 +++++++++ swap/src/asb/recovery.rs | 1 + swap/src/asb/recovery/grant_final_amnesty.rs | 29 ++++ swap/src/asb/rpc/server.rs | 18 ++- swap/src/protocol/bob/swap.rs | 21 ++- swap/tests/harness/mod.rs | 3 +- ...rtial_refund_alice_grants_final_amnesty.rs | 144 +++++++++++------- 17 files changed, 273 insertions(+), 73 deletions(-) create mode 100644 swap/src/asb/recovery/grant_final_amnesty.rs diff --git a/Cargo.lock b/Cargo.lock index b7cffb695e..fef092c4df 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -10751,6 +10751,7 @@ dependencies = [ "shell-words", "swap-controller-api", "tokio", + "uuid", ] [[package]] @@ -10760,6 +10761,7 @@ dependencies = [ "bitcoin 0.32.8", "jsonrpsee", "serde", + "uuid", ] [[package]] diff --git a/swap-asb/src/command.rs b/swap-asb/src/command.rs index f0ed7fa488..72a138b446 100644 --- a/swap-asb/src/command.rs +++ b/swap-asb/src/command.rs @@ -178,6 +178,14 @@ where env_config: env_config(testnet), cmd: Command::SafelyAbort { swap_id }, }, + RawCommand::ManualRecovery(ManualRecovery::GrantFinalAmnesty { swap_id }) => Arguments { + testnet, + json, + trace, + config_path: config_path(config, testnet)?, + env_config: env_config(testnet), + cmd: Command::GrantFinalAmnesty { swap_id }, + }, }; Ok(arguments) @@ -250,6 +258,9 @@ pub enum Command { SafelyAbort { swap_id: Uuid, }, + GrantFinalAmnesty { + swap_id: Uuid, + }, ExportBitcoinWallet, ExportMoneroWallet, ExportMoneroLockWallet { @@ -412,6 +423,16 @@ pub enum ManualRecovery { )] swap_id: Uuid, }, + #[structopt( + about = "Grant final amnesty to a swap in BtcRefundBurnConfirmed state, allowing the taker to claim the remaining funds." + )] + GrantFinalAmnesty { + #[structopt( + long = "swap-id", + help = "The swap id can be retrieved using the history subcommand" + )] + swap_id: Uuid, + }, } #[derive(structopt::StructOpt, Debug)] diff --git a/swap-asb/src/main.rs b/swap-asb/src/main.rs index fb829eaddf..e98faf51d9 100644 --- a/swap-asb/src/main.rs +++ b/swap-asb/src/main.rs @@ -27,7 +27,7 @@ use structopt::clap::ErrorKind; mod command; use command::{parse_args, Arguments, Command}; use swap::asb::rpc::RpcServer; -use swap::asb::{cancel, punish, redeem, refund, safely_abort, EventLoop, ExchangeRate, Finality}; +use swap::asb::{cancel, grant_final_amnesty, punish, redeem, refund, safely_abort, EventLoop, ExchangeRate, Finality}; use swap::common::tor::{bootstrap_tor_client, create_tor_client}; use swap::common::tracing_util::Format; use swap::common::{self, get_logs, warn_if_outdated}; @@ -482,6 +482,13 @@ pub async fn main() -> Result<()> { tracing::info!("Swap safely aborted"); } + Command::GrantFinalAmnesty { swap_id } => { + let db = open_db(db_file, AccessMode::ReadWrite, None).await?; + + grant_final_amnesty(swap_id, db).await?; + + tracing::info!("Final amnesty granted for swap {}", swap_id); + } Command::Redeem { swap_id, do_not_await_finality, diff --git a/swap-controller-api/Cargo.toml b/swap-controller-api/Cargo.toml index 6cc73f689b..06bf31f14a 100644 --- a/swap-controller-api/Cargo.toml +++ b/swap-controller-api/Cargo.toml @@ -7,6 +7,7 @@ edition = "2021" bitcoin = { workspace = true } jsonrpsee = { workspace = true, features = ["macros", "server", "client-core", "http-client"] } serde = { workspace = true } +uuid = { workspace = true, features = ["serde"] } [lints] workspace = true diff --git a/swap-controller-api/src/lib.rs b/swap-controller-api/src/lib.rs index 4d548dd789..a22103f773 100644 --- a/swap-controller-api/src/lib.rs +++ b/swap-controller-api/src/lib.rs @@ -1,6 +1,7 @@ use jsonrpsee::proc_macros::rpc; use jsonrpsee::types::ErrorObjectOwned; use serde::{Deserialize, Serialize}; +use uuid::Uuid; #[derive(Serialize, Deserialize, Debug, Clone)] pub struct BitcoinBalanceResponse { @@ -119,9 +120,7 @@ pub trait AsbApi { #[method(name = "registration_status")] async fn registration_status(&self) -> Result; #[method(name = "set_burn_on_refund")] - async fn set_burn_on_refund( - &self, - swap_id: String, - burn: bool, - ) -> Result<(), ErrorObjectOwned>; + async fn set_burn_on_refund(&self, swap_id: Uuid, burn: bool) -> Result<(), ErrorObjectOwned>; + #[method(name = "grant_final_amnesty")] + async fn grant_final_amnesty(&self, swap_id: Uuid) -> Result<(), ErrorObjectOwned>; } diff --git a/swap-controller/Cargo.toml b/swap-controller/Cargo.toml index c3ad61cdf4..ee8c8cf582 100644 --- a/swap-controller/Cargo.toml +++ b/swap-controller/Cargo.toml @@ -17,6 +17,7 @@ rustyline = "17.0.0" shell-words = "1.1" swap-controller-api = { path = "../swap-controller-api" } tokio = { workspace = true } +uuid = { workspace = true, features = ["serde"] } [lints] workspace = true diff --git a/swap-controller/src/cli.rs b/swap-controller/src/cli.rs index accc3ddc23..384502336e 100644 --- a/swap-controller/src/cli.rs +++ b/swap-controller/src/cli.rs @@ -1,4 +1,5 @@ use clap::{Parser, Subcommand}; +use uuid::Uuid; #[derive(Parser)] #[command(name = "asb-controller")] @@ -40,9 +41,14 @@ pub enum Cmd { /// Set whether to burn Bitcoin on refund for a swap SetBurnOnRefund { /// The swap ID - swap_id: String, + swap_id: Uuid, /// Whether to burn the Bitcoin (true or false) #[arg(action = clap::ArgAction::Set)] burn: bool, }, + /// Grant final amnesty for a swap in BtcRefundBurnConfirmed state + GrantFinalAmnesty { + /// The swap ID + swap_id: Uuid, + }, } diff --git a/swap-controller/src/main.rs b/swap-controller/src/main.rs index acaac8864e..d0a5b8fb04 100644 --- a/swap-controller/src/main.rs +++ b/swap-controller/src/main.rs @@ -131,13 +131,17 @@ async fn dispatch(cmd: Cmd, client: impl AsbApiClient) -> anyhow::Result<()> { } } Cmd::SetBurnOnRefund { swap_id, burn } => { - client.set_burn_on_refund(swap_id.clone(), burn).await?; + client.set_burn_on_refund(swap_id, burn).await?; if burn { println!("Burn on refund enabled for swap {swap_id}"); } else { println!("Burn on refund disabled for swap {swap_id}"); } } + Cmd::GrantFinalAmnesty { swap_id } => { + client.grant_final_amnesty(swap_id).await?; + println!("Final amnesty granted for swap {swap_id}"); + } } Ok(()) } diff --git a/swap-machine/src/bob/mod.rs b/swap-machine/src/bob/mod.rs index 416480588d..1496a14487 100644 --- a/swap-machine/src/bob/mod.rs +++ b/swap-machine/src/bob/mod.rs @@ -266,7 +266,6 @@ pub fn is_complete(state: &BobState) -> bool { | BobState::BtcEarlyRefunded { .. } | BobState::BtcAmnestyConfirmed { .. } | BobState::BtcFinalAmnestyConfirmed { .. } - | BobState::BtcRefundBurnt { .. } | BobState::XmrRedeemed { .. } | BobState::SafelyAborted ) diff --git a/swap/src/asb.rs b/swap/src/asb.rs index de055d02a0..2457de7e69 100644 --- a/swap/src/asb.rs +++ b/swap/src/asb.rs @@ -11,6 +11,7 @@ pub use recovery::cancel::cancel; pub use recovery::punish::punish; pub use recovery::redeem::{redeem, Finality}; pub use recovery::refund::refund; +pub use recovery::grant_final_amnesty::grant_final_amnesty; pub use recovery::safely_abort::safely_abort; pub use recovery::{cancel, refund}; pub use swap_feed::{ExchangeRate, FixedRate, LatestRate, Rate}; diff --git a/swap/src/asb/event_loop.rs b/swap/src/asb/event_loop.rs index edd029472b..0b6c3d8dc9 100644 --- a/swap/src/asb/event_loop.rs +++ b/swap/src/asb/event_loop.rs @@ -571,6 +571,10 @@ where }; let _ = respond_to.send(result); } + EventLoopRequest::GrantFinalAmnesty { swap_id, respond_to } => { + let result = self.handle_grant_final_amnesty(swap_id).await; + let _ = respond_to.send(result); + } } } } @@ -725,6 +729,53 @@ where transfer_proof_sender: tokio::sync::Mutex::new(Some(transfer_proof_sender)), } } + + /// Handle a request to grant final amnesty for a swap. + /// + /// This checks that the swap is not currently running, transitions the + /// state to BtcFinalAmnestyGranted, and resumes the swap. + async fn handle_grant_final_amnesty(&mut self, swap_id: Uuid) -> Result<()> { + use crate::asb::grant_final_amnesty; + + // Check if swap is currently running + if self.recv_encrypted_signature.contains_key(&swap_id) + || self.recv_burn_on_refund_instruction.contains_key(&swap_id) + { + return Err(anyhow!( + "Cannot grant final amnesty while swap {} is still running", + swap_id + )); + } + + // Use the grant_final_amnesty function to transition the state + let new_state = grant_final_amnesty(swap_id, self.db.clone()).await?; + + // Get peer ID for this swap + let peer_id = self.db.get_peer_id(swap_id).await?; + + // Create handle and swap to resume + let handle = self.new_handle(peer_id, swap_id); + let swap = Swap { + event_loop_handle: handle, + bitcoin_wallet: self.bitcoin_wallet.clone(), + monero_wallet: self.monero_wallet.clone(), + env_config: self.env_config, + db: self.db.clone(), + state: new_state, + swap_id, + developer_tip: self.developer_tip.clone(), + }; + + // Send swap to be resumed + self.swap_sender + .send(swap) + .await + .context("Failed to send swap to be resumed")?; + + tracing::info!(%swap_id, "Granted final amnesty and resumed swap"); + + Ok(()) + } } // We use a Mutex here to allow recv_encrypted_signature and transfer_proof_sender to be accessed concurrently @@ -983,6 +1034,10 @@ mod service { burn: bool, respond_to: oneshot::Sender>, }, + GrantFinalAmnesty { + swap_id: Uuid, + respond_to: oneshot::Sender>, + }, } /// Tower service for communicating with the EventLoop @@ -1045,6 +1100,22 @@ mod service { rx.await .map_err(|_| anyhow::anyhow!("EventLoop service did not respond"))? } + + /// Grant final amnesty for a swap in BtcRefundBurnConfirmed state + /// + /// This transitions the swap to BtcFinalAmnestyGranted and resumes + /// the swap state machine to publish the final amnesty transaction. + pub async fn grant_final_amnesty(&self, swap_id: Uuid) -> anyhow::Result<()> { + let (tx, rx) = oneshot::channel(); + self.sender + .send(EventLoopRequest::GrantFinalAmnesty { + swap_id, + respond_to: tx, + }) + .map_err(|_| anyhow::anyhow!("EventLoop service is down"))?; + rx.await + .map_err(|_| anyhow::anyhow!("EventLoop service did not respond"))? + } } } diff --git a/swap/src/asb/recovery.rs b/swap/src/asb/recovery.rs index dd4a7b86a7..e730f77110 100644 --- a/swap/src/asb/recovery.rs +++ b/swap/src/asb/recovery.rs @@ -1,4 +1,5 @@ pub mod cancel; +pub mod grant_final_amnesty; pub mod punish; pub mod redeem; pub mod refund; diff --git a/swap/src/asb/recovery/grant_final_amnesty.rs b/swap/src/asb/recovery/grant_final_amnesty.rs new file mode 100644 index 0000000000..5a31cdbace --- /dev/null +++ b/swap/src/asb/recovery/grant_final_amnesty.rs @@ -0,0 +1,29 @@ +use crate::protocol::alice::AliceState; +use crate::protocol::Database; +use anyhow::{bail, Result}; +use std::convert::TryInto; +use std::sync::Arc; +use uuid::Uuid; + +pub async fn grant_final_amnesty( + swap_id: Uuid, + db: Arc, +) -> Result { + let state = db.get_state(swap_id).await?.try_into()?; + + match state { + AliceState::BtcRefundBurnConfirmed { state3 } => { + let new_state = AliceState::BtcFinalAmnestyGranted { state3 }; + + db.insert_latest_state(swap_id, new_state.clone().into()) + .await?; + + Ok(new_state) + } + _ => bail!( + "Cannot grant final amnesty for swap {} because it is in state {} which is not BtcRefundBurnConfirmed", + swap_id, + state + ), + } +} diff --git a/swap/src/asb/rpc/server.rs b/swap/src/asb/rpc/server.rs index 2595bbe33b..cd044ebaaa 100644 --- a/swap/src/asb/rpc/server.rs +++ b/swap/src/asb/rpc/server.rs @@ -14,6 +14,7 @@ use swap_controller_api::{ RendezvousRegistrationStatus, Swap, }; use tokio_util::task::AbortOnDropHandle; +use uuid::Uuid; pub struct RpcServer { handle: ServerHandle, @@ -244,17 +245,18 @@ impl AsbApiServer for RpcImpl { Ok(RegistrationStatusResponse { registrations }) } - async fn set_burn_on_refund( - &self, - swap_id: String, - burn: bool, - ) -> Result<(), ErrorObjectOwned> { - let swap_id = uuid::Uuid::parse_str(&swap_id) - .context("Invalid swap ID") + async fn set_burn_on_refund(&self, swap_id: Uuid, burn: bool) -> Result<(), ErrorObjectOwned> { + self.event_loop_service + .set_burn_on_refund(swap_id, burn) + .await .into_json_rpc_result()?; + Ok(()) + } + + async fn grant_final_amnesty(&self, swap_id: Uuid) -> Result<(), ErrorObjectOwned> { self.event_loop_service - .set_burn_on_refund(swap_id, burn) + .grant_final_amnesty(swap_id) .await .into_json_rpc_result()?; diff --git a/swap/src/protocol/bob/swap.rs b/swap/src/protocol/bob/swap.rs index 2263711117..a3bb7b2bf9 100644 --- a/swap/src/protocol/bob/swap.rs +++ b/swap/src/protocol/bob/swap.rs @@ -48,8 +48,10 @@ pub fn has_already_processed_transfer_proof(state: &BobState) -> bool { // - We want to attempt recovery via cooperative XMR redeem once. // - If unsuccessful, we exit to avoid an infinite retry loop. // - The swap can still be manually resumed later and retried if desired. +// +// The same is true for the BtcRefundBurnt. pub fn is_run_at_most_once(state: &BobState) -> bool { - matches!(state, BobState::BtcPunished { .. }) + matches!(state, BobState::BtcPunished { .. } | BobState::BtcRefundBurnt(..)) } #[allow(clippy::too_many_arguments)] @@ -1311,9 +1313,20 @@ async fn next_state( BobState::BtcRefundBurnt(state) } BobState::BtcRefundBurnt(state) => { - // Terminal state - Alice needs to manually publish TxFinalAmnesty - // Similar to BtcPunished, we stop here - BobState::BtcRefundBurnt(state) + // Watch for Alice publishing TxFinalAmnesty + // Alice may grant final amnesty after burning our refund + // However, we don't expect Alice to publish the tx at once, if at all. + // Thus we only check once, and then stop the swap. + // User's can still manually resume the swap to check again. + let tx_final_amnesty = state.construct_tx_final_amnesty()?; + + let final_amnesty_status = bitcoin_wallet.status_of_script(&tx_final_amnesty).await.context("Failed to check TxFinalAmnesty status")?; + + if final_amnesty_status.has_been_seen() { + BobState::BtcFinalAmnestyPublished(state) + } else { + BobState::BtcRefundBurnt(state) + } } BobState::BtcFinalAmnestyPublished(state) => { // Wait for TxFinalAmnesty confirmation diff --git a/swap/tests/harness/mod.rs b/swap/tests/harness/mod.rs index 38d4f4f6d1..a1b82c1240 100644 --- a/swap/tests/harness/mod.rs +++ b/swap/tests/harness/mod.rs @@ -466,6 +466,7 @@ async fn init_test_wallets( .finality_confirmations(1_u32) .target_block(1_u32) .sync_interval(Duration::from_secs(3)) // high sync interval to speed up tests + .use_mempool_space_fee_estimation(false) .build() .await .expect("could not init btc wallet"); @@ -685,7 +686,7 @@ impl BobParams { } } -pub struct BobApplicationHandle(JoinHandle<()>); +pub struct BobApplicationHandle(pub JoinHandle<()>); impl BobApplicationHandle { pub fn abort(&self) { diff --git a/swap/tests/partial_refund_alice_grants_final_amnesty.rs b/swap/tests/partial_refund_alice_grants_final_amnesty.rs index d43f1261eb..45beeb4aa9 100644 --- a/swap/tests/partial_refund_alice_grants_final_amnesty.rs +++ b/swap/tests/partial_refund_alice_grants_final_amnesty.rs @@ -1,19 +1,25 @@ pub mod harness; +use std::time::Duration; + use harness::alice_run_until::{is_btc_refund_burn_confirmed, is_xmr_lock_transaction_sent}; use harness::FastAmnestyConfig; use rust_decimal::Decimal; use swap::asb::FixedRate; use swap::protocol::alice::AliceState; use swap::protocol::{alice, bob}; +use swap_controller_api::AsbApiClient; use swap_env::config::RefundPolicy; +use swap_machine::bob::BobState; + +use crate::harness::alice_run_until::{is_btc_partially_refunded, is_xmr_refunded}; +use crate::harness::bob_run_until; /// Bob locks Btc and Alice locks Xmr. Alice does not act so Bob does a partial /// refund. Alice burns the refund, then later grants final amnesty to Bob. /// NOTE: This test cannot pass yet because we haven't implemented the manual /// trigger for final amnesty. BtcRefundBurnConfirmed is currently terminal. #[tokio::test] -#[ignore = "final amnesty manual trigger not implemented yet"] async fn given_partial_refund_alice_grants_final_amnesty() { // Use 95% refund ratio - Bob gets 95% immediately, 5% locked in amnesty // Alice burns the amnesty, then grants final amnesty @@ -22,55 +28,91 @@ async fn given_partial_refund_alice_grants_final_amnesty() { burn_on_refund: true, }); - harness::setup_test(FastAmnestyConfig, None, refund_policy, |mut ctx| async move { - let (bob_swap, _) = ctx.bob_swap().await; - let bob_swap = tokio::spawn(bob::run(bob_swap)); - - let alice_swap = ctx.alice_next_swap().await; - let alice_swap = tokio::spawn(alice::run_until( - alice_swap, - is_xmr_lock_transaction_sent, - FixedRate::default(), - )); - - // Alice finishes first (just sends XMR lock and stops) - let alice_state = alice_swap.await??; - assert!(matches!( - alice_state, - AliceState::XmrLockTransactionSent { .. } - )); - - // Bob continues: cancel timelock -> partial refund - // Bob will end up in BtcRefundBurnt because Alice burns the amnesty - let bob_state = bob_swap.await??; - ctx.assert_bob_refund_burnt(bob_state.clone()).await; - - // Restart Alice so she can refund her XMR and burn Bob's amnesty - ctx.restart_alice().await; - let alice_swap = ctx.alice_next_swap().await; - let alice_swap = tokio::spawn(alice::run_until( - alice_swap, - is_btc_refund_burn_confirmed, - FixedRate::default(), - )); - - let alice_state = alice_swap.await??; - assert!(matches!( - alice_state, - AliceState::BtcRefundBurnConfirmed { .. } - )); - - // TODO: Trigger final amnesty manually here - // This requires a manual command to Alice to grant final amnesty - // For now, this test is ignored. - - // Bob should receive final amnesty - // ctx.assert_bob_final_amnesty_received(bob_state).await; - - // Alice should be in final amnesty confirmed state - // ctx.assert_alice_final_amnesty_confirmed(alice_state).await; - - Ok(()) - }) + harness::setup_test( + FastAmnestyConfig, + None, + refund_policy, + |mut ctx| async move { + let (bob_swap, bob_app_handle) = ctx.bob_swap().await; + let bob_state = tokio::spawn(bob::run_until( + bob_swap, + bob_run_until::is_btc_partially_refunded, + )); + + let alice_swap = ctx.alice_next_swap().await; + let alice_swap = tokio::spawn(alice::run_until( + alice_swap, + is_xmr_refunded, + FixedRate::default(), + )); + + // Wait for bob to partially refund - stop here such that he doesn't publish amnesty + // TODO: fix regtest blocktimes instead + let bob_state = bob_state.await??; + + let alice_state = alice_swap.await??; + assert!(matches!(alice_state, AliceState::XmrRefunded { .. })); + + ctx.monero.generate_blocks().await?; + + // Restart alice and wait for bob to be burnt. + ctx.restart_alice().await; + let alice_swap = ctx.alice_next_swap().await; + let swap_id = alice_swap.swap_id; + let alice_swap = tokio::spawn(alice::run(alice_swap, FixedRate::default())); + + // Give alice time to publish TxRefundBurn before restarting bob + tokio::time::sleep(Duration::from_secs(20)).await; + + let (bob_swap, bob_app_handle) = ctx + .stop_and_resume_bob_from_db(bob_app_handle, swap_id) + .await; + let bob_state = tokio::spawn(bob::run(bob_swap)); // Bob should stop automatically after BtcRefundBurnt + + let alice_state = alice_swap.await??; + assert!(matches!( + alice_state, + AliceState::BtcRefundBurnConfirmed { .. } + )); + + let bob_state = bob_state.await??; + assert!(matches!(bob_state, BobState::BtcRefundBurnt(..))); + + // Simulate alice's controller sending the final amnesty command via `controller` cli + ctx.restart_alice().await; + ctx.alice_rpc_client + .grant_final_amnesty(swap_id.to_string()) + .await?; + + let alice_swap = ctx.alice_next_swap().await; + let alice_swap = tokio::spawn(alice::run(alice_swap, FixedRate::default())); + + let (bob_swap, _) = ctx + .stop_and_resume_bob_from_db(bob_app_handle, swap_id) + .await; + assert!(matches!(bob_swap.state, BobState::BtcRefundBurnt(..))); + + let alice_state = alice_swap.await??; + // Only start bob again after alice published the tx. otherwise bob immediately + // terminates when not finding the tx. + // TODO: maybe make bob check for a few minutes before giving up? + let bob_state = tokio::spawn(bob::run(bob_swap)); + let bob_state = bob_state.await??; + + assert!( + matches!( + alice_state, + AliceState::BtcRefundFinalAmnestyConfirmed { .. } + ), + "Actual state: {alice_state}" + ); + assert!( + matches!(bob_state, bob::BobState::BtcFinalAmnestyConfirmed(..)), + "Actual state: {bob_state}" + ); + + Ok(()) + }, + ) .await; } From 5f7276ab7cad6d5083de204450ffe8c5617ceddd Mon Sep 17 00:00:00 2001 From: einliterflasche Date: Mon, 5 Jan 2026 18:19:11 +0100 Subject: [PATCH 082/113] add basic gui components for new states --- src-gui/src/models/tauriModelExt.ts | 35 +++++- .../alert/SwapStatusAlert/SwapStatusAlert.tsx | 113 +++++++++++++++++- .../pages/swap/swap/SwapStatePage.tsx | 20 +++- .../swap/swap/done/BitcoinRefundedPage.tsx | 21 ---- swap-machine/src/bob/mod.rs | 4 +- swap/src/cli/api/tauri_bindings.rs | 22 ++++ swap/src/protocol/bob/swap.rs | 28 +++++ 7 files changed, 211 insertions(+), 32 deletions(-) diff --git a/src-gui/src/models/tauriModelExt.ts b/src-gui/src/models/tauriModelExt.ts index 3fbe3cfeec..f3c9efdaaa 100644 --- a/src-gui/src/models/tauriModelExt.ts +++ b/src-gui/src/models/tauriModelExt.ts @@ -58,6 +58,10 @@ export enum BobStateName { BtcPartiallyRefunded = "btc is partially refunded", BtcAmnestyPublished = "btc amnesty is published", BtcAmnestyReceived = "btc amnesty is confirmed", + BtcRefundBurnPublished = "btc refund burn is published", + BtcRefundBurnt = "btc refund is burnt", + BtcFinalAmnestyPublished = "btc final amnesty is published", + BtcFinalAmnestyConfirmed = "btc final amnesty is confirmed", XmrRedeemed = "xmr is redeemed", BtcPunished = "btc is punished", SafelyAborted = "safely aborted", @@ -103,6 +107,14 @@ export function bobStateNameToHumanReadable(stateName: BobStateName): string { return "Bitcoin partially refunded"; case BobStateName.BtcAmnestyReceived: return "Bitcoin amnesty was received"; + case BobStateName.BtcRefundBurnPublished: + return "Bitcoin refund burn published"; + case BobStateName.BtcRefundBurnt: + return "Bitcoin refund is burnt"; + case BobStateName.BtcFinalAmnestyPublished: + return "Bitcoin final amnesty published"; + case BobStateName.BtcFinalAmnestyConfirmed: + return "Bitcoin final amnesty received"; case BobStateName.XmrRedeemed: return "Monero redeemed"; case BobStateName.BtcPunished: @@ -122,6 +134,14 @@ export type GetSwapInfoResponseExt = GetSwapInfoResponse & { export type TimelockNone = Extract; export type TimelockCancel = Extract; export type TimelockPunish = Extract; +export type TimelockWaitingForRemainingRefund = Extract< + ExpiredTimelocks, + { type: "WaitingForRemainingRefund" } +>; +export type TimelockRemainingRefund = Extract< + ExpiredTimelocks, + { type: "RemainingRefund" } +>; // This function returns the absolute block number of the timelock relative to the block the tx_lock was included in export function getAbsoluteBlock( @@ -138,6 +158,13 @@ export function getAbsoluteBlock( if (timelock.type === "Punish") { return cancelTimelock + punishTimelock; } + // These states are for the partial refund path - we're past cancel/punish timelocks + if (timelock.type === "WaitingForRemainingRefund") { + return cancelTimelock + punishTimelock; + } + if (timelock.type === "RemainingRefund") { + return cancelTimelock + punishTimelock; + } // We match all cases return exhaustiveGuard(timelock); @@ -148,11 +175,11 @@ export type BobStateNameRunningSwap = Exclude< | BobStateName.Started | BobStateName.SwapSetupCompleted | BobStateName.BtcRefunded - | BobStateName.BtcPartiallyRefunded - | BobStateName.BtcAmnestyPublished | BobStateName.BtcAmnestyReceived | BobStateName.BtcRefunded | BobStateName.BtcEarlyRefunded + | BobStateName.BtcRefundBurnt + | BobStateName.BtcFinalAmnestyConfirmed | BobStateName.BtcPunished | BobStateName.SafelyAborted | BobStateName.XmrRedeemed @@ -170,9 +197,9 @@ export function isBobStateNameRunningSwap( BobStateName.SwapSetupCompleted, BobStateName.BtcRefunded, BobStateName.BtcEarlyRefunded, - BobStateName.BtcPartiallyRefunded, - BobStateName.BtcAmnestyPublished, BobStateName.BtcAmnestyReceived, + BobStateName.BtcRefundBurnt, + BobStateName.BtcFinalAmnestyConfirmed, BobStateName.BtcPunished, BobStateName.SafelyAborted, BobStateName.XmrRedeemed, diff --git a/src-gui/src/renderer/components/alert/SwapStatusAlert/SwapStatusAlert.tsx b/src-gui/src/renderer/components/alert/SwapStatusAlert/SwapStatusAlert.tsx index 80b565a594..2b4e003a8b 100644 --- a/src-gui/src/renderer/components/alert/SwapStatusAlert/SwapStatusAlert.tsx +++ b/src-gui/src/renderer/components/alert/SwapStatusAlert/SwapStatusAlert.tsx @@ -161,6 +161,77 @@ function PunishTimelockExpiredAlert() { ); } +/** + * Sub-component for alerts when waiting for remaining refund timelock. + * This occurs after a partial refund was confirmed but we're waiting for the amnesty timelock. + */ +function WaitingForRemainingRefundTimelockAlert({ + blocksLeft, +}: { + blocksLeft: number; +}) { + return ( + + Waiting{" "} + {" "} + for the amnesty timelock to expire + , + "The maker can burn the remaining Bitcoin before the timelock expires", + "If the maker doesn't burn it, you can claim the remaining Bitcoin once the timelock expires", + "Keep the app running or resume the swap once the timelock expires", + ]} + /> + ); +} + +/** + * Sub-component for alerts when remaining refund timelock has expired. + * The amnesty transaction can now be published. + */ +function RemainingRefundTimelockExpiredAlert() { + return ( + + ); +} + +/** + * Sub-component for alerts when the maker has burnt the amnesty output. + */ +function BtcRefundBurnPublishedAlert() { + return ( + + ); +} + +/** + * Sub-component for alerts when the maker has published the final amnesty transaction. + */ +function BtcFinalAmnestyPublishedAlert() { + return ( + + ); +} + /** * Main component for displaying the appropriate swap alert status text. * @param swap - The swap information. @@ -193,10 +264,12 @@ export function StateAlert({ case BobStateName.XmrLocked: case BobStateName.EncSigSent: case BobStateName.CancelTimelockExpired: + // Even if the refund transactions have been published, it cannot be + // guaranteed that they will be confirmed in time case BobStateName.BtcCancelled: - case BobStateName.BtcRefundPublished: // Even if the transactions have been published, it cannot be - case BobStateName.BtcPartialRefundPublished: // Even if the transactions have been published, it cannot be - case BobStateName.BtcEarlyRefundPublished: // guaranteed that they will be confirmed in time + case BobStateName.BtcRefundPublished: + case BobStateName.BtcPartialRefundPublished: + case BobStateName.BtcEarlyRefundPublished: if (timelock != null) { switch (timelock.type) { case "None": @@ -214,16 +287,50 @@ export function StateAlert({ ); case "Punish": return ; + // These two timelock types only exist once the partial refund tx has been confirmed + // They shouldn't occur for these states, so return null + case "WaitingForRemainingRefund": + case "RemainingRefund": + return null; default: exhaustiveGuard(timelock); } } return ; + case BobStateName.BtcPartiallyRefunded: + // Reuse existing timelock alerts for the amnesty waiting period + if (timelock != null) { + switch (timelock.type) { + case "WaitingForRemainingRefund": + return ( + + ); + case "RemainingRefund": + return ; + default: + return null; + } + } + return null; + + case BobStateName.BtcRefundBurnPublished: + return ; + + case BobStateName.BtcFinalAmnestyPublished: + return ; + + case BobStateName.BtcAmnestyPublished: + // Amnesty tx published, waiting for confirmation - no specific alert needed + return null; + // If the Bitcoin lock transaction has not been published yet // there is no need to display an alert case BobStateName.BtcLockReadyToPublish: return null; + default: exhaustiveGuard(swap.state_name); } diff --git a/src-gui/src/renderer/components/pages/swap/swap/SwapStatePage.tsx b/src-gui/src/renderer/components/pages/swap/swap/SwapStatePage.tsx index ed02e8b01a..458d7b89e3 100644 --- a/src-gui/src/renderer/components/pages/swap/swap/SwapStatePage.tsx +++ b/src-gui/src/renderer/components/pages/swap/swap/SwapStatePage.tsx @@ -7,11 +7,17 @@ import { BitcoinEarlyRefundedPage, BitcoinEarlyRefundPublishedPage, BitcoinRefundPublishedPage, +} from "./done/BitcoinRefundedPage"; +import { BitcoinPartialRefundPublished, BitcoinPartiallyRefunded, BitcoinAmnestyPublished, - BitcoinAmnestyReceived -} from "./done/BitcoinRefundedPage"; + BitcoinAmnestyReceived, + BitcoinRefundBurnPublished, + BitcoinRefundBurnt, + BitcoinFinalAmnestyPublished, + BitcoinFinalAmnestyConfirmed, +} from "./done/BitcoinPartialRefundPage"; import XmrRedeemInMempoolPage from "./done/XmrRedeemInMempoolPage"; import ProcessExitedPage from "./exited/ProcessExitedPage"; import BitcoinCancelledPage from "./in_progress/BitcoinCancelledPage"; @@ -140,6 +146,16 @@ export default function SwapStatePage({ state }: { state: SwapState | null }) { case "BtcAmnestyReceived": return ; + //// 4 different types of refund burn / final amnesty states + case "BtcRefundBurnPublished": + return ; + case "BtcRefundBurnt": + return ; + case "BtcFinalAmnestyPublished": + return ; + case "BtcFinalAmnestyConfirmed": + return ; + //// 4 different types of Bitcoin punished states we can be in case "BtcPunished": if (state.curr.type === "BtcPunished") { diff --git a/src-gui/src/renderer/components/pages/swap/swap/done/BitcoinRefundedPage.tsx b/src-gui/src/renderer/components/pages/swap/swap/done/BitcoinRefundedPage.tsx index 5c56cd939f..d1496b85a1 100644 --- a/src-gui/src/renderer/components/pages/swap/swap/done/BitcoinRefundedPage.tsx +++ b/src-gui/src/renderer/components/pages/swap/swap/done/BitcoinRefundedPage.tsx @@ -92,24 +92,3 @@ function MultiBitcoinRefundedPage({ ); } -export function BitcoinPartialRefundPublished() { - return ( - <>TxPartialRefund published - ) -} - -export function BitcoinPartiallyRefunded() { - return ( - <>Bitcoin partially refunded - ) -} -export function BitcoinAmnestyPublished() { - return ( - <>TxAmnesty published - ) -} -export function BitcoinAmnestyReceived() { - return ( - <>Bitcoin amnesty received - ) -} diff --git a/swap-machine/src/bob/mod.rs b/swap-machine/src/bob/mod.rs index 1496a14487..8f2c6614d4 100644 --- a/swap-machine/src/bob/mod.rs +++ b/swap-machine/src/bob/mod.rs @@ -594,7 +594,7 @@ pub struct State2 { /// This field was changed in [#675](https://github.com/eigenwallet/core/pull/675). /// It boils down to the same json except that it now may also contain a partial refund signature. #[serde(flatten)] - refund_signatures: RefundSignatures, + pub refund_signatures: RefundSignatures, min_monero_confirmations: u64, tx_redeem_fee: bitcoin::Amount, tx_punish_fee: bitcoin::Amount, @@ -752,7 +752,7 @@ pub struct State3 { /// This field was changed in [#675](https://github.com/eigenwallet/core/pull/675). /// It boils down to the same json except that it now may also contain a partial refund signature. #[serde(flatten)] - refund_signatures: RefundSignatures, + pub refund_signatures: RefundSignatures, min_monero_confirmations: u64, tx_redeem_fee: bitcoin::Amount, tx_refund_fee: bitcoin::Amount, diff --git a/swap/src/cli/api/tauri_bindings.rs b/swap/src/cli/api/tauri_bindings.rs index 881f5a5bc2..da818a71e1 100644 --- a/swap/src/cli/api/tauri_bindings.rs +++ b/swap/src/cli/api/tauri_bindings.rs @@ -98,6 +98,8 @@ pub struct LockBitcoinDetails { /// The amount of Bitcoin the taker will only be able to refund with cooperation from the maker #[typeshare(serialized_as = "number")] pub btc_amnesty_amount: bitcoin::Amount, + /// Whether we can guarantee we'll get the full refund + pub has_full_refund_signature: bool, } #[typeshare] @@ -1123,6 +1125,26 @@ pub enum TauriSwapProgressEvent { #[typeshare(serialized_as = "string")] btc_amnesty_txid: Txid, }, + // TxRefundBurn has been published (waiting for confirmation) + BtcRefundBurnPublished { + #[typeshare(serialized_as = "string")] + btc_refund_burn_txid: Txid, + }, + // TxRefundBurn has been confirmed - amnesty output is burnt + BtcRefundBurnt { + #[typeshare(serialized_as = "string")] + btc_refund_burn_txid: Txid, + }, + // Alice published TxFinalAmnesty + BtcFinalAmnestyPublished { + #[typeshare(serialized_as = "string")] + btc_final_amnesty_txid: Txid, + }, + // TxFinalAmnesty has been confirmed - user received burnt funds back + BtcFinalAmnestyConfirmed { + #[typeshare(serialized_as = "string")] + btc_final_amnesty_txid: Txid, + }, BtcPunished, AttemptingCooperativeRedeem, CooperativeRedeemAccepted, diff --git a/swap/src/protocol/bob/swap.rs b/swap/src/protocol/bob/swap.rs index a3bb7b2bf9..d81506db77 100644 --- a/swap/src/protocol/bob/swap.rs +++ b/swap/src/protocol/bob/swap.rs @@ -188,6 +188,7 @@ async fn next_state( xmr_receive_amount, monero_receive_pool, swap_id, + has_full_refund_signature: state3.refund_signatures.has_full_refund_encsig() }; // We request approval before publishing the Bitcoin lock transaction, @@ -1306,6 +1307,12 @@ async fn next_state( BobState::BtcRefundBurnPublished(state) => { // Wait for TxRefundBurn confirmation let tx_refund_burn = state.construct_tx_refund_burn()?; + event_emitter.emit_swap_progress_event( + swap_id, + TauriSwapProgressEvent::BtcRefundBurnPublished { + btc_refund_burn_txid: tx_refund_burn.txid(), + }, + ); let subscription = bitcoin_wallet.subscribe_to(Box::new(tx_refund_burn)).await; subscription.wait_until_final().await?; @@ -1318,6 +1325,14 @@ async fn next_state( // However, we don't expect Alice to publish the tx at once, if at all. // Thus we only check once, and then stop the swap. // User's can still manually resume the swap to check again. + let tx_refund_burn = state.construct_tx_refund_burn()?; + event_emitter.emit_swap_progress_event( + swap_id, + TauriSwapProgressEvent::BtcRefundBurnt { + btc_refund_burn_txid: tx_refund_burn.txid(), + }, + ); + let tx_final_amnesty = state.construct_tx_final_amnesty()?; let final_amnesty_status = bitcoin_wallet.status_of_script(&tx_final_amnesty).await.context("Failed to check TxFinalAmnesty status")?; @@ -1331,6 +1346,12 @@ async fn next_state( BobState::BtcFinalAmnestyPublished(state) => { // Wait for TxFinalAmnesty confirmation let tx_final_amnesty = state.construct_tx_final_amnesty()?; + event_emitter.emit_swap_progress_event( + swap_id, + TauriSwapProgressEvent::BtcFinalAmnestyPublished { + btc_final_amnesty_txid: tx_final_amnesty.txid(), + }, + ); let subscription = bitcoin_wallet.subscribe_to(Box::new(tx_final_amnesty)).await; subscription.wait_until_final().await?; @@ -1339,6 +1360,13 @@ async fn next_state( } BobState::BtcFinalAmnestyConfirmed(state) => { // Terminal state - we received the burnt funds back + let tx_final_amnesty = state.construct_tx_final_amnesty()?; + event_emitter.emit_swap_progress_event( + swap_id, + TauriSwapProgressEvent::BtcFinalAmnestyConfirmed { + btc_final_amnesty_txid: tx_final_amnesty.txid(), + }, + ); BobState::BtcFinalAmnestyConfirmed(state) } BobState::SafelyAborted => BobState::SafelyAborted, From fb838c7adeab696fcd09d337d37c96ef78340488 Mon Sep 17 00:00:00 2001 From: einliterflasche Date: Tue, 6 Jan 2026 10:01:50 +0100 Subject: [PATCH 083/113] add placeholder components for partial refund path --- .../swap/done/BitcoinPartialRefundPage.tsx | 45 +++++++++++++++++++ 1 file changed, 45 insertions(+) create mode 100644 src-gui/src/renderer/components/pages/swap/swap/done/BitcoinPartialRefundPage.tsx diff --git a/src-gui/src/renderer/components/pages/swap/swap/done/BitcoinPartialRefundPage.tsx b/src-gui/src/renderer/components/pages/swap/swap/done/BitcoinPartialRefundPage.tsx new file mode 100644 index 0000000000..12f8063e62 --- /dev/null +++ b/src-gui/src/renderer/components/pages/swap/swap/done/BitcoinPartialRefundPage.tsx @@ -0,0 +1,45 @@ +/** + * Pages for the partial refund path of the swap. + * + * This path is taken when Alice only signs the partial refund transaction + * (not the full refund). The flow is: + * + * 1. BtcPartialRefundPublished - TxPartialRefund is published + * 2. BtcPartiallyRefunded - TxPartialRefund is confirmed + * 3. Either: + * a. BtcAmnestyPublished -> BtcAmnestyReceived (Bob claims amnesty via TxRefundAmnesty) + * b. BtcRefundBurnPublished -> BtcRefundBurnt (Alice burns amnesty via TxRefundBurn) + * -> optionally BtcFinalAmnestyPublished -> BtcFinalAmnestyConfirmed (Alice grants final amnesty) + */ + +export function BitcoinPartialRefundPublished() { + return <>TxPartialRefund published; +} + +export function BitcoinPartiallyRefunded() { + return <>Bitcoin partially refunded; +} + +export function BitcoinAmnestyPublished() { + return <>TxAmnesty published; +} + +export function BitcoinAmnestyReceived() { + return <>Bitcoin amnesty received; +} + +export function BitcoinRefundBurnPublished() { + return <>TxRefundBurn published - Alice burned the amnesty output; +} + +export function BitcoinRefundBurnt() { + return <>Bitcoin refund is burnt - waiting for Alice to grant final amnesty; +} + +export function BitcoinFinalAmnestyPublished() { + return <>TxFinalAmnesty published - Alice granted final amnesty; +} + +export function BitcoinFinalAmnestyConfirmed() { + return <>Bitcoin final amnesty received - swap complete; +} From 908db143aab906c0425f13c1212bf7c481ae2e39 Mon Sep 17 00:00:00 2001 From: einliterflasche Date: Tue, 6 Jan 2026 12:24:53 +0100 Subject: [PATCH 084/113] add mock swap states with stepping controls in dev mode --- src-gui/src/dev/mockSwapEvents.ts | 197 ++++++++++++++++++ .../modal/swap/pages/MockSwapControls.tsx | 125 +++++++++++ .../components/pages/swap/swap/SwapWidget.tsx | 17 +- .../swap/swap/exited/ProcessExitedPage.tsx | 5 +- ...artial_refund_alice_burns_after_command.rs | 2 +- ...rtial_refund_alice_grants_final_amnesty.rs | 4 +- 6 files changed, 339 insertions(+), 11 deletions(-) create mode 100644 src-gui/src/dev/mockSwapEvents.ts create mode 100644 src-gui/src/renderer/components/modal/swap/pages/MockSwapControls.tsx diff --git a/src-gui/src/dev/mockSwapEvents.ts b/src-gui/src/dev/mockSwapEvents.ts new file mode 100644 index 0000000000..5d4cc9b31f --- /dev/null +++ b/src-gui/src/dev/mockSwapEvents.ts @@ -0,0 +1,197 @@ +import { + BidQuote, + MoneroAddressPool, + QuoteWithAddress, + TauriSwapProgressEvent, +} from "models/tauriModel"; + +// Mock transaction IDs +const MOCK_BTC_LOCK_TXID = + "f4184fc596403b9d638783cf57adfe4c75c605f6356fbc91338530e9831e9e16"; +const MOCK_XMR_LOCK_TXID = + "a7b8c9d0e1f2a3b4c5d6e7f8a9b0c1d2e3f4a5b6c7d8e9f0a1b2c3d4e5f6a7b8"; +const MOCK_XMR_REDEEM_TXID = + "b8c9d0e1f2a3b4c5d6e7f8a9b0c1d2e3f4a5b6c7d8e9f0a1b2c3d4e5f6a7b8c9"; +const MOCK_BTC_CANCEL_TXID = + "c9d0e1f2a3b4c5d6e7f8a9b0c1d2e3f4a5b6c7d8e9f0a1b2c3d4e5f6a7b8c9d0"; +const MOCK_BTC_REFUND_TXID = + "d0e1f2a3b4c5d6e7f8a9b0c1d2e3f4a5b6c7d8e9f0a1b2c3d4e5f6a7b8c9d0e1"; +const MOCK_BTC_EARLY_REFUND_TXID = + "e1f2a3b4c5d6e7f8a9b0c1d2e3f4a5b6c7d8e9f0a1b2c3d4e5f6a7b8c9d0e1f2"; +const MOCK_BTC_PARTIAL_REFUND_TXID = + "f2a3b4c5d6e7f8a9b0c1d2e3f4a5b6c7d8e9f0a1b2c3d4e5f6a7b8c9d0e1f2a3"; +const MOCK_BTC_AMNESTY_TXID = + "a3b4c5d6e7f8a9b0c1d2e3f4a5b6c7d8e9f0a1b2c3d4e5f6a7b8c9d0e1f2a3b4"; +const MOCK_BTC_REFUND_BURN_TXID = + "b4c5d6e7f8a9b0c1d2e3f4a5b6c7d8e9f0a1b2c3d4e5f6a7b8c9d0e1f2a3b4c5"; +const MOCK_BTC_FINAL_AMNESTY_TXID = + "c5d6e7f8a9b0c1d2e3f4a5b6c7d8e9f0a1b2c3d4e5f6a7b8c9d0e1f2a3b4c5d6"; + +// Mock addresses +const MOCK_BTC_DEPOSIT_ADDRESS = "bc1qar0srrr7xfkvy5l643lydnw9re59gtzzwf5mdq"; +const MOCK_XMR_ADDRESS = + "888tNkZrPN6JsEgekjMnABU4TBzc2Dt29EPAvkRxbANsAnjyPbb3iQ1YBRk1UXcdRsiKc9dhwMVgN5S9cQUiyoogDavup3H"; + +export const MOCK_SWAP_ID = "a1b2c3d4-e5f6-7890-abcd-ef1234567890"; + +const MOCK_QUOTE: BidQuote = { + price: 0.007, + min_quantity: 10_000_000, + max_quantity: 100_000_000, +}; + +const MOCK_QUOTE_WITH_ADDRESS: QuoteWithAddress = { + multiaddr: "/ip4/127.0.0.1/tcp/9939", + peer_id: "12D3KooWCdMKjesXMJz1SiZ7HgotrxuqhQJbP5sgBm2BwP1cqThi", + quote: MOCK_QUOTE, + version: "0.13.0", +}; + +const MOCK_RECEIVE_POOL: MoneroAddressPool = [ + { address: MOCK_XMR_ADDRESS, percentage: 100, label: "Main" }, +]; + +const XMR_TARGET_CONFIRMATIONS = 10; + +// Base scenario: swap start -> XMR locked (10 confirmations) +const baseScenario: TauriSwapProgressEvent[] = [ + { type: "ReceivedQuote", content: MOCK_QUOTE }, + { + type: "WaitingForBtcDeposit", + content: { + deposit_address: MOCK_BTC_DEPOSIT_ADDRESS, + max_giveable: 0, + min_bitcoin_lock_tx_fee: 1000, + known_quotes: [MOCK_QUOTE_WITH_ADDRESS], + }, + }, + { type: "SwapSetupInflight", content: { btc_lock_amount: 50_000_000 } }, + { type: "RetrievingMoneroBlockheight" }, + { type: "BtcLockPublishInflight" }, + // BTC lock confirmations: 0, 1, 2 + { type: "BtcLockTxInMempool", content: { btc_lock_txid: MOCK_BTC_LOCK_TXID, btc_lock_confirmations: 0 } }, + { type: "BtcLockTxInMempool", content: { btc_lock_txid: MOCK_BTC_LOCK_TXID, btc_lock_confirmations: 1 } }, + { type: "BtcLockTxInMempool", content: { btc_lock_txid: MOCK_BTC_LOCK_TXID, btc_lock_confirmations: 2 } }, + { type: "VerifyingXmrLockTx", content: { xmr_lock_txid: MOCK_XMR_LOCK_TXID } }, + // XMR lock confirmations: 0 through 10 + ...Array.from({ length: XMR_TARGET_CONFIRMATIONS + 1 }, (_, i) => ({ + type: "XmrLockTxInMempool" as const, + content: { + xmr_lock_txid: MOCK_XMR_LOCK_TXID, + xmr_lock_tx_confirmations: i, + xmr_lock_tx_target_confirmations: XMR_TARGET_CONFIRMATIONS, + }, + })), +]; + +const happyPath: TauriSwapProgressEvent[] = [ + ...baseScenario, + { type: "PreflightEncSig" }, + { type: "InflightEncSig" }, + { type: "EncryptedSignatureSent" }, + { type: "RedeemingMonero" }, + { + type: "XmrRedeemInMempool", + content: { xmr_redeem_txids: [MOCK_XMR_REDEEM_TXID], xmr_receive_pool: MOCK_RECEIVE_POOL }, + }, + { type: "Released" }, +]; + +const cooperativeRedeem: TauriSwapProgressEvent[] = [ + ...baseScenario, + { type: "AttemptingCooperativeRedeem" }, + { type: "CooperativeRedeemAccepted" }, + { type: "RedeemingMonero" }, + { + type: "XmrRedeemInMempool", + content: { xmr_redeem_txids: [MOCK_XMR_REDEEM_TXID], xmr_receive_pool: MOCK_RECEIVE_POOL }, + }, + { type: "Released" }, +]; + +const cooperativeRedeemRejected: TauriSwapProgressEvent[] = [ + ...baseScenario, + { type: "AttemptingCooperativeRedeem" }, + { type: "CooperativeRedeemRejected", content: { reason: "Peer offline" } }, + { type: "WaitingForCancelTimelockExpiration" }, + { type: "CancelTimelockExpired" }, + { type: "BtcCancelled", content: { btc_cancel_txid: MOCK_BTC_CANCEL_TXID } }, + { type: "BtcRefundPublished", content: { btc_refund_txid: MOCK_BTC_REFUND_TXID } }, + { type: "BtcRefunded", content: { btc_refund_txid: MOCK_BTC_REFUND_TXID } }, + { type: "Released" }, +]; + +const earlyRefund: TauriSwapProgressEvent[] = [ + ...baseScenario, + { type: "BtcEarlyRefundPublished", content: { btc_early_refund_txid: MOCK_BTC_EARLY_REFUND_TXID } }, + { type: "BtcEarlyRefunded", content: { btc_early_refund_txid: MOCK_BTC_EARLY_REFUND_TXID } }, + { type: "Released" }, +]; + +const partialRefundWithAmnesty: TauriSwapProgressEvent[] = [ + ...baseScenario, + { type: "WaitingForCancelTimelockExpiration" }, + { type: "CancelTimelockExpired" }, + { type: "BtcCancelled", content: { btc_cancel_txid: MOCK_BTC_CANCEL_TXID } }, + { + type: "BtcPartialRefundPublished", + content: { btc_partial_refund_txid: MOCK_BTC_PARTIAL_REFUND_TXID, has_amnesty_signature: true }, + }, + { + type: "BtcPartiallyRefunded", + content: { btc_partial_refund_txid: MOCK_BTC_PARTIAL_REFUND_TXID, has_amnesty_signature: true }, + }, + { type: "BtcAmnestyPublished", content: { btc_amnesty_txid: MOCK_BTC_AMNESTY_TXID } }, + { type: "BtcAmnestyReceived", content: { btc_amnesty_txid: MOCK_BTC_AMNESTY_TXID } }, + { type: "Released" }, +]; + +const partialRefundWithBurn: TauriSwapProgressEvent[] = [ + ...baseScenario, + { type: "WaitingForCancelTimelockExpiration" }, + { type: "CancelTimelockExpired" }, + { type: "BtcCancelled", content: { btc_cancel_txid: MOCK_BTC_CANCEL_TXID } }, + { + type: "BtcPartialRefundPublished", + content: { btc_partial_refund_txid: MOCK_BTC_PARTIAL_REFUND_TXID, has_amnesty_signature: false }, + }, + { + type: "BtcPartiallyRefunded", + content: { btc_partial_refund_txid: MOCK_BTC_PARTIAL_REFUND_TXID, has_amnesty_signature: false }, + }, + { type: "BtcRefundBurnPublished", content: { btc_refund_burn_txid: MOCK_BTC_REFUND_BURN_TXID } }, + { type: "BtcRefundBurnt", content: { btc_refund_burn_txid: MOCK_BTC_REFUND_BURN_TXID } }, + { type: "Released" }, +]; + +const partialRefundWithBurnAndFinalAmnesty: TauriSwapProgressEvent[] = [ + ...baseScenario, + { type: "WaitingForCancelTimelockExpiration" }, + { type: "CancelTimelockExpired" }, + { type: "BtcCancelled", content: { btc_cancel_txid: MOCK_BTC_CANCEL_TXID } }, + { + type: "BtcPartialRefundPublished", + content: { btc_partial_refund_txid: MOCK_BTC_PARTIAL_REFUND_TXID, has_amnesty_signature: false }, + }, + { + type: "BtcPartiallyRefunded", + content: { btc_partial_refund_txid: MOCK_BTC_PARTIAL_REFUND_TXID, has_amnesty_signature: false }, + }, + { type: "BtcRefundBurnPublished", content: { btc_refund_burn_txid: MOCK_BTC_REFUND_BURN_TXID } }, + { type: "BtcRefundBurnt", content: { btc_refund_burn_txid: MOCK_BTC_REFUND_BURN_TXID } }, + { type: "BtcFinalAmnestyPublished", content: { btc_final_amnesty_txid: MOCK_BTC_FINAL_AMNESTY_TXID } }, + { type: "BtcFinalAmnestyConfirmed", content: { btc_final_amnesty_txid: MOCK_BTC_FINAL_AMNESTY_TXID } }, + { type: "Released" }, +]; + +export const scenarios: Record = { + happyPath, + cooperativeRedeem, + cooperativeRedeemRejected, + earlyRefund, + partialRefundWithAmnesty, + partialRefundWithBurn, + partialRefundWithBurnAndFinalAmnesty, +}; + +export type MockScenario = keyof typeof scenarios; diff --git a/src-gui/src/renderer/components/modal/swap/pages/MockSwapControls.tsx b/src-gui/src/renderer/components/modal/swap/pages/MockSwapControls.tsx new file mode 100644 index 0000000000..4921a10f60 --- /dev/null +++ b/src-gui/src/renderer/components/modal/swap/pages/MockSwapControls.tsx @@ -0,0 +1,125 @@ +import { useState } from "react"; +import { + Box, + IconButton, + MenuItem, + Paper, + Select, + Switch, + Typography, +} from "@mui/material"; +import ChevronLeftIcon from "@mui/icons-material/ChevronLeft"; +import ChevronRightIcon from "@mui/icons-material/ChevronRight"; +import { scenarios, MockScenario, MOCK_SWAP_ID } from "dev/mockSwapEvents"; +import { SwapState } from "models/storeModel"; + +function buildMockState(scenario: MockScenario, index: number): SwapState { + const events = scenarios[scenario]; + return { + curr: events[index], + prev: index > 0 ? events[index - 1] : null, + swapId: MOCK_SWAP_ID, + }; +} + +interface Props { + onMockStateChange: (state: SwapState | null) => void; +} + +export default function MockSwapControls({ onMockStateChange }: Props) { + const [scenario, setScenario] = useState(null); + const [index, setIndex] = useState(0); + + const enabled = scenario !== null; + const total = scenario ? scenarios[scenario].length : 0; + + const handleToggle = (checked: boolean) => { + if (checked) { + const firstScenario = Object.keys(scenarios)[0] as MockScenario; + setScenario(firstScenario); + setIndex(0); + onMockStateChange(buildMockState(firstScenario, 0)); + } else { + setScenario(null); + setIndex(0); + onMockStateChange(null); + } + }; + + const handleScenarioChange = (newScenario: MockScenario) => { + setScenario(newScenario); + setIndex(0); + onMockStateChange(buildMockState(newScenario, 0)); + }; + + const prev = () => { + if (!scenario || index === 0) return; + const newIndex = index - 1; + setIndex(newIndex); + onMockStateChange(buildMockState(scenario, newIndex)); + }; + + const next = () => { + if (!scenario || index >= total - 1) return; + const newIndex = index + 1; + setIndex(newIndex); + onMockStateChange(buildMockState(scenario, newIndex)); + }; + + const currentStateName = scenario ? scenarios[scenario][index].type : null; + + return ( + + + handleToggle(e.target.checked)} + /> + + Mock + + + {enabled && ( + <> + + + + + {index + 1}/{total} + + + + + + {currentStateName} + + + )} + + + ); +} diff --git a/src-gui/src/renderer/components/pages/swap/swap/SwapWidget.tsx b/src-gui/src/renderer/components/pages/swap/swap/SwapWidget.tsx index 0411806372..06b8ec3378 100644 --- a/src-gui/src/renderer/components/pages/swap/swap/SwapWidget.tsx +++ b/src-gui/src/renderer/components/pages/swap/swap/SwapWidget.tsx @@ -1,4 +1,5 @@ import { Box, Button, Dialog, DialogActions, Paper } from "@mui/material"; +import { useState } from "react"; import { useActiveSwapInfo, useAppSelector } from "store/hooks"; import SwapStatePage from "renderer/components/pages/swap/swap/SwapStatePage"; import CancelButton from "./CancelButton"; @@ -6,13 +7,16 @@ import SwapStateStepper from "renderer/components/modal/swap/SwapStateStepper"; import SwapStatusAlert from "renderer/components/alert/SwapStatusAlert/SwapStatusAlert"; import DebugPageSwitchBadge from "renderer/components/modal/swap/pages/DebugPageSwitchBadge"; import DebugPage from "renderer/components/modal/swap/pages/DebugPage"; -import { useState } from "react"; +import MockSwapControls from "renderer/components/modal/swap/pages/MockSwapControls"; +import { SwapState } from "models/storeModel"; export default function SwapWidget() { - const swap = useAppSelector((state) => state.swap); + const swapState = useAppSelector((state) => state.swap.state); const swapInfo = useActiveSwapInfo(); - const [debug, setDebug] = useState(false); + const [mockState, setMockState] = useState(null); + + const displayState = mockState ?? swapState; return ( - + - {swap.state !== null && ( + {displayState !== null && ( <> - + + {import.meta.env.DEV && } )} diff --git a/src-gui/src/renderer/components/pages/swap/swap/exited/ProcessExitedPage.tsx b/src-gui/src/renderer/components/pages/swap/swap/exited/ProcessExitedPage.tsx index 5859c28019..18c94d1b59 100644 --- a/src-gui/src/renderer/components/pages/swap/swap/exited/ProcessExitedPage.tsx +++ b/src-gui/src/renderer/components/pages/swap/swap/exited/ProcessExitedPage.tsx @@ -21,7 +21,10 @@ export default function ProcessExitedPage({ (prevState.type === "XmrRedeemInMempool" || prevState.type === "BtcRefunded" || prevState.type === "BtcPunished" || - prevState.type === "CooperativeRedeemRejected") + prevState.type === "CooperativeRedeemRejected" || + prevState.type === "BtcRefundBurnt" || + prevState.type === "BtcFinalAmnestyConfirmed" || + prevState.type === "BtcAmnestyReceived") ) { return ( Date: Tue, 6 Jan 2026 16:25:08 +0100 Subject: [PATCH 085/113] improve components for the partial refund path --- src-gui/src/dev/mockSwapEvents.ts | 112 +- .../modal/swap/SwapStateStepper.tsx | 116 +- .../pages/swap/swap/SwapStatePage.tsx | 40 +- .../components/pages/swap/swap/SwapWidget.tsx | 2 +- .../swap/done/BitcoinPartialRefundPage.tsx | 311 +- src-gui/yarn.lock | 11178 ++++++---------- swap/src/cli/api/tauri_bindings.rs | 42 +- swap/src/protocol/bob/swap.rs | 24 +- 8 files changed, 4868 insertions(+), 6957 deletions(-) diff --git a/src-gui/src/dev/mockSwapEvents.ts b/src-gui/src/dev/mockSwapEvents.ts index 5d4cc9b31f..366f131c36 100644 --- a/src-gui/src/dev/mockSwapEvents.ts +++ b/src-gui/src/dev/mockSwapEvents.ts @@ -27,6 +27,10 @@ const MOCK_BTC_REFUND_BURN_TXID = const MOCK_BTC_FINAL_AMNESTY_TXID = "c5d6e7f8a9b0c1d2e3f4a5b6c7d8e9f0a1b2c3d4e5f6a7b8c9d0e1f2a3b4c5d6"; +// Mock amounts for partial refund scenarios +const MOCK_BTC_LOCK_AMOUNT = 50_000_000; // 0.5 BTC +const MOCK_BTC_AMNESTY_AMOUNT = 2_500_000; // 0.025 BTC (5% of lock amount) + // Mock addresses const MOCK_BTC_DEPOSIT_ADDRESS = "bc1qar0srrr7xfkvy5l643lydnw9re59gtzzwf5mdq"; const MOCK_XMR_ADDRESS = @@ -135,14 +139,36 @@ const partialRefundWithAmnesty: TauriSwapProgressEvent[] = [ { type: "BtcCancelled", content: { btc_cancel_txid: MOCK_BTC_CANCEL_TXID } }, { type: "BtcPartialRefundPublished", - content: { btc_partial_refund_txid: MOCK_BTC_PARTIAL_REFUND_TXID, has_amnesty_signature: true }, + content: { + btc_partial_refund_txid: MOCK_BTC_PARTIAL_REFUND_TXID, + btc_lock_amount: MOCK_BTC_LOCK_AMOUNT, + btc_amnesty_amount: MOCK_BTC_AMNESTY_AMOUNT, + }, }, { type: "BtcPartiallyRefunded", - content: { btc_partial_refund_txid: MOCK_BTC_PARTIAL_REFUND_TXID, has_amnesty_signature: true }, + content: { + btc_partial_refund_txid: MOCK_BTC_PARTIAL_REFUND_TXID, + btc_lock_amount: MOCK_BTC_LOCK_AMOUNT, + btc_amnesty_amount: MOCK_BTC_AMNESTY_AMOUNT, + }, + }, + { + type: "BtcAmnestyPublished", + content: { + btc_amnesty_txid: MOCK_BTC_AMNESTY_TXID, + btc_lock_amount: MOCK_BTC_LOCK_AMOUNT, + btc_amnesty_amount: MOCK_BTC_AMNESTY_AMOUNT, + }, + }, + { + type: "BtcAmnestyReceived", + content: { + btc_amnesty_txid: MOCK_BTC_AMNESTY_TXID, + btc_lock_amount: MOCK_BTC_LOCK_AMOUNT, + btc_amnesty_amount: MOCK_BTC_AMNESTY_AMOUNT, + }, }, - { type: "BtcAmnestyPublished", content: { btc_amnesty_txid: MOCK_BTC_AMNESTY_TXID } }, - { type: "BtcAmnestyReceived", content: { btc_amnesty_txid: MOCK_BTC_AMNESTY_TXID } }, { type: "Released" }, ]; @@ -153,14 +179,36 @@ const partialRefundWithBurn: TauriSwapProgressEvent[] = [ { type: "BtcCancelled", content: { btc_cancel_txid: MOCK_BTC_CANCEL_TXID } }, { type: "BtcPartialRefundPublished", - content: { btc_partial_refund_txid: MOCK_BTC_PARTIAL_REFUND_TXID, has_amnesty_signature: false }, + content: { + btc_partial_refund_txid: MOCK_BTC_PARTIAL_REFUND_TXID, + btc_lock_amount: MOCK_BTC_LOCK_AMOUNT, + btc_amnesty_amount: MOCK_BTC_AMNESTY_AMOUNT, + }, }, { type: "BtcPartiallyRefunded", - content: { btc_partial_refund_txid: MOCK_BTC_PARTIAL_REFUND_TXID, has_amnesty_signature: false }, + content: { + btc_partial_refund_txid: MOCK_BTC_PARTIAL_REFUND_TXID, + btc_lock_amount: MOCK_BTC_LOCK_AMOUNT, + btc_amnesty_amount: MOCK_BTC_AMNESTY_AMOUNT, + }, + }, + { + type: "BtcRefundBurnPublished", + content: { + btc_refund_burn_txid: MOCK_BTC_REFUND_BURN_TXID, + btc_lock_amount: MOCK_BTC_LOCK_AMOUNT, + btc_amnesty_amount: MOCK_BTC_AMNESTY_AMOUNT, + }, + }, + { + type: "BtcRefundBurnt", + content: { + btc_refund_burn_txid: MOCK_BTC_REFUND_BURN_TXID, + btc_lock_amount: MOCK_BTC_LOCK_AMOUNT, + btc_amnesty_amount: MOCK_BTC_AMNESTY_AMOUNT, + }, }, - { type: "BtcRefundBurnPublished", content: { btc_refund_burn_txid: MOCK_BTC_REFUND_BURN_TXID } }, - { type: "BtcRefundBurnt", content: { btc_refund_burn_txid: MOCK_BTC_REFUND_BURN_TXID } }, { type: "Released" }, ]; @@ -171,16 +219,52 @@ const partialRefundWithBurnAndFinalAmnesty: TauriSwapProgressEvent[] = [ { type: "BtcCancelled", content: { btc_cancel_txid: MOCK_BTC_CANCEL_TXID } }, { type: "BtcPartialRefundPublished", - content: { btc_partial_refund_txid: MOCK_BTC_PARTIAL_REFUND_TXID, has_amnesty_signature: false }, + content: { + btc_partial_refund_txid: MOCK_BTC_PARTIAL_REFUND_TXID, + btc_lock_amount: MOCK_BTC_LOCK_AMOUNT, + btc_amnesty_amount: MOCK_BTC_AMNESTY_AMOUNT, + }, }, { type: "BtcPartiallyRefunded", - content: { btc_partial_refund_txid: MOCK_BTC_PARTIAL_REFUND_TXID, has_amnesty_signature: false }, + content: { + btc_partial_refund_txid: MOCK_BTC_PARTIAL_REFUND_TXID, + btc_lock_amount: MOCK_BTC_LOCK_AMOUNT, + btc_amnesty_amount: MOCK_BTC_AMNESTY_AMOUNT, + }, + }, + { + type: "BtcRefundBurnPublished", + content: { + btc_refund_burn_txid: MOCK_BTC_REFUND_BURN_TXID, + btc_lock_amount: MOCK_BTC_LOCK_AMOUNT, + btc_amnesty_amount: MOCK_BTC_AMNESTY_AMOUNT, + }, + }, + { + type: "BtcRefundBurnt", + content: { + btc_refund_burn_txid: MOCK_BTC_REFUND_BURN_TXID, + btc_lock_amount: MOCK_BTC_LOCK_AMOUNT, + btc_amnesty_amount: MOCK_BTC_AMNESTY_AMOUNT, + }, + }, + { + type: "BtcFinalAmnestyPublished", + content: { + btc_final_amnesty_txid: MOCK_BTC_FINAL_AMNESTY_TXID, + btc_lock_amount: MOCK_BTC_LOCK_AMOUNT, + btc_amnesty_amount: MOCK_BTC_AMNESTY_AMOUNT, + }, + }, + { + type: "BtcFinalAmnestyConfirmed", + content: { + btc_final_amnesty_txid: MOCK_BTC_FINAL_AMNESTY_TXID, + btc_lock_amount: MOCK_BTC_LOCK_AMOUNT, + btc_amnesty_amount: MOCK_BTC_AMNESTY_AMOUNT, + }, }, - { type: "BtcRefundBurnPublished", content: { btc_refund_burn_txid: MOCK_BTC_REFUND_BURN_TXID } }, - { type: "BtcRefundBurnt", content: { btc_refund_burn_txid: MOCK_BTC_REFUND_BURN_TXID } }, - { type: "BtcFinalAmnestyPublished", content: { btc_final_amnesty_txid: MOCK_BTC_FINAL_AMNESTY_TXID } }, - { type: "BtcFinalAmnestyConfirmed", content: { btc_final_amnesty_txid: MOCK_BTC_FINAL_AMNESTY_TXID } }, { type: "Released" }, ]; diff --git a/src-gui/src/renderer/components/modal/swap/SwapStateStepper.tsx b/src-gui/src/renderer/components/modal/swap/SwapStateStepper.tsx index f270cb0127..609cd0bd5a 100644 --- a/src-gui/src/renderer/components/modal/swap/SwapStateStepper.tsx +++ b/src-gui/src/renderer/components/modal/swap/SwapStateStepper.tsx @@ -1,14 +1,25 @@ import { Step, StepLabel, Stepper, Typography } from "@mui/material"; import { SwapState } from "models/storeModel"; -import { useAppSelector } from "store/hooks"; import logger from "utils/logger"; export enum PathType { HAPPY_PATH = "happy path", - UNHAPPY_PATH = "unhappy path", + RECOVERY_PATH = "recovery path", } -type PathStep = [type: PathType, step: number, isError: boolean]; +export enum RecoveryScenario { + GENERIC = "generic", + FULL_REFUND = "full_refund", + PARTIAL_REFUND = "partial_refund", + COOPERATIVE_REDEEM = "cooperative_redeem", +} + +type PathStep = [ + type: PathType, + step: number, + isError: boolean, + scenario?: RecoveryScenario, +]; /** * Determines the current step in the swap process based on the previous and latest state. @@ -92,38 +103,62 @@ function getActiveStep(state: SwapState | null): PathStep | null { case "XmrRedeemInMempool": return [PathType.HAPPY_PATH, 4, false]; - // Unhappy Path States + // Recovery Path States - Generic (early states before we know outcome) - // Step 1: Cancel timelock has expired. Waiting for cancel transaction to be published + case "WaitingForCancelTimelockExpiration": case "CancelTimelockExpired": - return [PathType.UNHAPPY_PATH, 0, isReleased]; + return [PathType.RECOVERY_PATH, 0, isReleased, RecoveryScenario.GENERIC]; - // Step 2: Swap has been cancelled. Waiting for Bitcoin to be refunded case "BtcCancelled": - return [PathType.UNHAPPY_PATH, 1, isReleased]; + return [PathType.RECOVERY_PATH, 1, isReleased, RecoveryScenario.GENERIC]; + + // Recovery Path States - Full Refund - // Step 2: One of the two Bitcoin refund transactions have been published - // but they haven't been confirmed yet case "BtcRefundPublished": case "BtcEarlyRefundPublished": - return [PathType.UNHAPPY_PATH, 1, isReleased]; + return [PathType.RECOVERY_PATH, 1, isReleased, RecoveryScenario.FULL_REFUND]; - // Step 2: One of the two Bitcoin refund transactions have been confirmed case "BtcRefunded": case "BtcEarlyRefunded": - return [PathType.UNHAPPY_PATH, 2, false]; + return [PathType.RECOVERY_PATH, 2, false, RecoveryScenario.FULL_REFUND]; + + // Recovery Path States - Partial Refund + + case "BtcPartialRefundPublished": + return [PathType.RECOVERY_PATH, 1, isReleased, RecoveryScenario.PARTIAL_REFUND]; + + case "BtcPartiallyRefunded": + return [PathType.RECOVERY_PATH, 2, isReleased, RecoveryScenario.PARTIAL_REFUND]; + + case "BtcAmnestyPublished": + return [PathType.RECOVERY_PATH, 2, isReleased, RecoveryScenario.PARTIAL_REFUND]; + + case "BtcAmnestyReceived": + return [PathType.RECOVERY_PATH, 3, false, RecoveryScenario.PARTIAL_REFUND]; + + case "BtcRefundBurnPublished": + return [PathType.RECOVERY_PATH, 2, true, RecoveryScenario.PARTIAL_REFUND]; + + case "BtcRefundBurnt": + return [PathType.RECOVERY_PATH, 2, true, RecoveryScenario.PARTIAL_REFUND]; + + case "BtcFinalAmnestyPublished": + return [PathType.RECOVERY_PATH, 2, isReleased, RecoveryScenario.PARTIAL_REFUND]; + + case "BtcFinalAmnestyConfirmed": + return [PathType.RECOVERY_PATH, 3, false, RecoveryScenario.PARTIAL_REFUND]; + + // Recovery Path States - Cooperative Redeem (after punishment) - // Step 2 (Failed): Failed to refund Bitcoin - // The timelock expired before we could refund, resulting in punishment case "BtcPunished": - return [PathType.UNHAPPY_PATH, 1, true]; + return [PathType.RECOVERY_PATH, 1, true, RecoveryScenario.COOPERATIVE_REDEEM]; - // Attempting cooperative redemption after punishment case "AttemptingCooperativeRedeem": case "CooperativeRedeemAccepted": - return [PathType.UNHAPPY_PATH, 1, isReleased]; + return [PathType.RECOVERY_PATH, 2, isReleased, RecoveryScenario.COOPERATIVE_REDEEM]; + case "CooperativeRedeemRejected": - return [PathType.UNHAPPY_PATH, 1, true]; + return [PathType.RECOVERY_PATH, 2, true, RecoveryScenario.COOPERATIVE_REDEEM]; case "Resuming": return null; @@ -163,15 +198,34 @@ function SwapStepper({ const HAPPY_PATH_STEP_LABELS = [ { label: "Locking your BTC", duration: "~12min" }, - { label: "They lock their XMR", duration: "~10min" }, + { label: "They lock their XMR", duration: "~20min" }, { label: "They redeem the BTC", duration: "~2min" }, - { label: "Redeeming your XMR", duration: "~10min" }, + { label: "Redeeming your XMR", duration: "~1min" }, ]; -const UNHAPPY_PATH_STEP_LABELS = [ - { label: "Cancelling swap", duration: "~1min" }, - { label: "Attempting recovery", duration: "~5min" }, -]; +const RECOVERY_STEP_LABELS: Record< + RecoveryScenario, + Array<{ label: string; duration: string }> +> = { + [RecoveryScenario.GENERIC]: [ + { label: "Cancelling swap", duration: "~1min" }, + { label: "Attempting recovery", duration: "" }, + ], + [RecoveryScenario.FULL_REFUND]: [ + { label: "Cancelling swap", duration: "~1min" }, + { label: "Bitcoin refunded", duration: "~5min" }, + ], + [RecoveryScenario.PARTIAL_REFUND]: [ + { label: "Cancelling swap", duration: "~1min" }, + { label: "Partial refund", duration: "~30min" }, + { label: "Remaining Bitcoin", duration: "~2min" }, + ], + [RecoveryScenario.COOPERATIVE_REDEEM]: [ + { label: "Cancelling swap", duration: "~1min" }, + { label: "We have been punished", duration: "" }, + { label: "Attempting cooperative recovery", duration: "~2min" }, + ], +}; export default function SwapStateStepper({ state, @@ -184,12 +238,14 @@ export default function SwapStateStepper({ return null; } - const [pathType, activeStep, error] = result; + const [pathType, activeStep, error, scenario] = result; - const steps = - pathType === PathType.HAPPY_PATH - ? HAPPY_PATH_STEP_LABELS - : UNHAPPY_PATH_STEP_LABELS; + let steps: Array<{ label: string; duration: string }>; + if (pathType === PathType.HAPPY_PATH) { + steps = HAPPY_PATH_STEP_LABELS; + } else { + steps = RECOVERY_STEP_LABELS[scenario ?? RecoveryScenario.GENERIC]; + } return ; } diff --git a/src-gui/src/renderer/components/pages/swap/swap/SwapStatePage.tsx b/src-gui/src/renderer/components/pages/swap/swap/SwapStatePage.tsx index 458d7b89e3..a764a473e2 100644 --- a/src-gui/src/renderer/components/pages/swap/swap/SwapStatePage.tsx +++ b/src-gui/src/renderer/components/pages/swap/swap/SwapStatePage.tsx @@ -138,23 +138,47 @@ export default function SwapStatePage({ state }: { state: SwapState | null }) { } break; case "BtcPartialRefundPublished": - return ; + if (state.curr.type === "BtcPartialRefundPublished") { + return ; + } + break; case "BtcPartiallyRefunded": - return ; + if (state.curr.type === "BtcPartiallyRefunded") { + return ; + } + break; case "BtcAmnestyPublished": - return ; + if (state.curr.type === "BtcAmnestyPublished") { + return ; + } + break; case "BtcAmnestyReceived": - return ; + if (state.curr.type === "BtcAmnestyReceived") { + return ; + } + break; //// 4 different types of refund burn / final amnesty states case "BtcRefundBurnPublished": - return ; + if (state.curr.type === "BtcRefundBurnPublished") { + return ; + } + break; case "BtcRefundBurnt": - return ; + if (state.curr.type === "BtcRefundBurnt") { + return ; + } + break; case "BtcFinalAmnestyPublished": - return ; + if (state.curr.type === "BtcFinalAmnestyPublished") { + return ; + } + break; case "BtcFinalAmnestyConfirmed": - return ; + if (state.curr.type === "BtcFinalAmnestyConfirmed") { + return ; + } + break; //// 4 different types of Bitcoin punished states we can be in case "BtcPunished": diff --git a/src-gui/src/renderer/components/pages/swap/swap/SwapWidget.tsx b/src-gui/src/renderer/components/pages/swap/swap/SwapWidget.tsx index 06b8ec3378..2340d8424d 100644 --- a/src-gui/src/renderer/components/pages/swap/swap/SwapWidget.tsx +++ b/src-gui/src/renderer/components/pages/swap/swap/SwapWidget.tsx @@ -28,6 +28,7 @@ export default function SwapWidget() { onlyShowIfUnusualAmountOfTimeHasPassed /> )} + {import.meta.env.DEV && } - {import.meta.env.DEV && } )} diff --git a/src-gui/src/renderer/components/pages/swap/swap/done/BitcoinPartialRefundPage.tsx b/src-gui/src/renderer/components/pages/swap/swap/done/BitcoinPartialRefundPage.tsx index 12f8063e62..628d10c955 100644 --- a/src-gui/src/renderer/components/pages/swap/swap/done/BitcoinPartialRefundPage.tsx +++ b/src-gui/src/renderer/components/pages/swap/swap/done/BitcoinPartialRefundPage.tsx @@ -12,34 +12,313 @@ * -> optionally BtcFinalAmnestyPublished -> BtcFinalAmnestyConfirmed (Alice grants final amnesty) */ -export function BitcoinPartialRefundPublished() { - return <>TxPartialRefund published; +import { Alert, Box, Button, DialogContentText, Typography } from "@mui/material"; +import { TauriSwapProgressEventContent } from "models/tauriModelExt"; +import { useActiveSwapInfo } from "store/hooks"; +import FeedbackInfoBox from "renderer/components/pages/help/FeedbackInfoBox"; +import BitcoinTransactionInfoBox from "renderer/components/pages/swap/swap/components/BitcoinTransactionInfoBox"; +import DiscordIcon from "renderer/components/icons/DiscordIcon"; +import MatrixIcon from "renderer/components/icons/MatrixIcon"; + +export function BitcoinPartialRefundPublished({ + btc_partial_refund_txid, + btc_lock_amount, + btc_amnesty_amount, +}: TauriSwapProgressEventContent<"BtcPartialRefundPublished">) { + return ( + + ); +} + +export function BitcoinPartiallyRefunded({ + btc_partial_refund_txid, + btc_lock_amount, + btc_amnesty_amount, +}: TauriSwapProgressEventContent<"BtcPartiallyRefunded">) { + return ( + + ); +} + +function PartialRefundPage({ + txid, + confirmed, + btcLockAmount, + btcAmnestyAmount, +}: { + txid: string; + confirmed: boolean; + btcLockAmount: number; + btcAmnestyAmount: number; +}) { + const swap = useActiveSwapInfo(); + + const guaranteedPercent = Math.round(((btcLockAmount - btcAmnestyAmount) / btcLockAmount) * 100); + const atRiskPercent = Math.round((btcAmnestyAmount / btcLockAmount) * 100); + + const mainMessage = confirmed + ? `Refunded the first ${guaranteedPercent}% of your Bitcoin. The maker has a short time window to revoke the remaining ${atRiskPercent}%. Unless they do that we will claim it shortly.` + : `Refunding the first ${guaranteedPercent}% of your Bitcoin. The maker has a short time window to revoke the remaining ${atRiskPercent}%. Unless they do that we will claim it shortly.`; + + const additionalContent = swap ? ( + <> + {!confirmed && "Waiting for transaction to be confirmed..."} + {!confirmed &&
} + Refund address: {swap.btc_refund_address} + + ) : null; + + return ( + <> + {mainMessage} + + + Patience: We are first claiming the guaranteed {guaranteedPercent}% of the Bitcoin refund. + It is not guaranteed that we can claim the remaining {atRiskPercent}%. + We will be able to claim the remaining Bitcoin shortly unless the market maker decides to revoke it. + + + + + + + ); +} + +// Amnesty pages - We're claiming the remaining Bitcoin ourselves (good outcome) + +export function BitcoinAmnestyPublished({ + btc_amnesty_txid, +}: TauriSwapProgressEventContent<"BtcAmnestyPublished">) { + return ( + + ); } -export function BitcoinPartiallyRefunded() { - return <>Bitcoin partially refunded; +export function BitcoinAmnestyReceived({ + btc_amnesty_txid, +}: TauriSwapProgressEventContent<"BtcAmnestyReceived">) { + return ( + + ); } -export function BitcoinAmnestyPublished() { - return <>TxAmnesty published; +function AmnestyPage({ + txid, + confirmed, +}: { + txid: string; + confirmed: boolean; +}) { + const swap = useActiveSwapInfo(); + + const mainMessage = confirmed + ? "All your Bitcoin has been refunded. The swap is complete." + : "The remaining Bitcoin is being released to you. Waiting for confirmation."; + + const additionalContent = swap ? ( + <> + {!confirmed && "Waiting for transaction to be confirmed..."} + {!confirmed &&
} + Refund address: {swap.btc_refund_address} + + ) : null; + + return ( + <> + {mainMessage} + + + {confirmed ? "Complete:" : "Almost there:"} The + remaining Bitcoin from your partial refund{" "} + {confirmed ? "has been" : "is being"} released to you. + + + + + + + + ); +} + +// Refund Burn pages - The maker actively burned the remaining Bitcoin (bad outcome) +// Note: By default, the user would have received the remaining Bitcoin after a timelock. +// If we're in this state, it means the maker actively published TxBurn to revoke it. + +export function BitcoinRefundBurnPublished({ + btc_refund_burn_txid, + btc_lock_amount, + btc_amnesty_amount, +}: TauriSwapProgressEventContent<"BtcRefundBurnPublished">) { + return ( + + ); } -export function BitcoinAmnestyReceived() { - return <>Bitcoin amnesty received; +export function BitcoinRefundBurnt({ + btc_refund_burn_txid, + btc_lock_amount, + btc_amnesty_amount, +}: TauriSwapProgressEventContent<"BtcRefundBurnt">) { + return ( + + ); } -export function BitcoinRefundBurnPublished() { - return <>TxRefundBurn published - Alice burned the amnesty output; +function RefundBurnPage({ + txid, + confirmed, + btcLockAmount, + btcAmnestyAmount, +}: { + txid: string; + confirmed: boolean; + btcLockAmount: number; + btcAmnestyAmount: number; +}) { + const atRiskPercent = Math.round((btcAmnestyAmount / btcLockAmount) * 100); + + const mainMessage = confirmed + ? "The market maker has revoked your remaining Bitcoin refund." + : "The market maker is revoking your remaining Bitcoin refund."; + + return ( + + {mainMessage} + + + Refund revoked: The market maker has revoked the remaining {atRiskPercent}% of your Bitcoin refund. + This portion is now lost and we cannot recover it on our own. + + + + + Why did this happen? Aborting a swap incurs significant costs on makers. + To prevent spam attacks, they can revoke a previously agreed upon part of the refund. + The maker has exercised this option because they think you are spamming them. + + + + + You can appeal. If you did not mean to spam the market maker, contact them through our official + community. The maker can still help you recover the remaining Bitcoin. + +
+ + + + +
+ +
+ ); } -export function BitcoinRefundBurnt() { - return <>Bitcoin refund is burnt - waiting for Alice to grant final amnesty; +// Final Amnesty pages - The maker granted final amnesty after the user appealed + +export function BitcoinFinalAmnestyPublished({ + btc_final_amnesty_txid, +}: TauriSwapProgressEventContent<"BtcFinalAmnestyPublished">) { + return ; } -export function BitcoinFinalAmnestyPublished() { - return <>TxFinalAmnesty published - Alice granted final amnesty; +export function BitcoinFinalAmnestyConfirmed({ + btc_final_amnesty_txid, +}: TauriSwapProgressEventContent<"BtcFinalAmnestyConfirmed">) { + return ; } -export function BitcoinFinalAmnestyConfirmed() { - return <>Bitcoin final amnesty received - swap complete; +function FinalAmnestyPage({ + txid, + confirmed, +}: { + txid: string; + confirmed: boolean; +}) { + const swap = useActiveSwapInfo(); + + const mainMessage = confirmed + ? "The market maker has granted you final amnesty. The remaining Bitcoin has been recovered." + : "The market maker is granting you final amnesty. Waiting for confirmation."; + + const additionalContent = swap ? ( + <> + {!confirmed && "Waiting for transaction to be confirmed..."} + {!confirmed &&
} + Refund address: {swap.btc_refund_address} + + ) : null; + + return ( + <> + {mainMessage} + + + Appeal successful: The market maker has decided to + release the remaining Bitcoin to you. All your Bitcoin has now been + fully refunded. + + + + + + + ); } diff --git a/src-gui/yarn.lock b/src-gui/yarn.lock index 49a5789e01..21d2f1cb4b 100644 --- a/src-gui/yarn.lock +++ b/src-gui/yarn.lock @@ -1,6874 +1,4304 @@ -# This file is generated by running "yarn install" inside your project. -# Manual changes might be lost - proceed with caution! - -__metadata: - version: 8 - cacheKey: 10c0 - -"@ampproject/remapping@npm:^2.2.0": - version: 2.3.0 - resolution: "@ampproject/remapping@npm:2.3.0" - dependencies: - "@jridgewell/gen-mapping": "npm:^0.3.5" - "@jridgewell/trace-mapping": "npm:^0.3.24" - checksum: 10c0/81d63cca5443e0f0c72ae18b544cc28c7c0ec2cea46e7cb888bb0e0f411a1191d0d6b7af798d54e30777d8d1488b2ec0732aac2be342d3d7d3ffd271c6f489ed - languageName: node - linkType: hard - -"@babel/code-frame@npm:^7.0.0, @babel/code-frame@npm:^7.27.1": - version: 7.27.1 - resolution: "@babel/code-frame@npm:7.27.1" - dependencies: - "@babel/helper-validator-identifier": "npm:^7.27.1" - js-tokens: "npm:^4.0.0" - picocolors: "npm:^1.1.1" - checksum: 10c0/5dd9a18baa5fce4741ba729acc3a3272c49c25cb8736c4b18e113099520e7ef7b545a4096a26d600e4416157e63e87d66db46aa3fbf0a5f2286da2705c12da00 - languageName: node - linkType: hard - -"@babel/compat-data@npm:^7.27.2": - version: 7.28.0 - resolution: "@babel/compat-data@npm:7.28.0" - checksum: 10c0/c4e527302bcd61052423f757355a71c3bc62362bac13f7f130de16e439716f66091ff5bdecda418e8fa0271d4c725f860f0ee23ab7bf6e769f7a8bb16dfcb531 - languageName: node - linkType: hard - -"@babel/core@npm:^7.24.4": - version: 7.28.5 - resolution: "@babel/core@npm:7.28.5" - dependencies: - "@babel/code-frame": "npm:^7.27.1" - "@babel/generator": "npm:^7.28.5" - "@babel/helper-compilation-targets": "npm:^7.27.2" - "@babel/helper-module-transforms": "npm:^7.28.3" - "@babel/helpers": "npm:^7.28.4" - "@babel/parser": "npm:^7.28.5" - "@babel/template": "npm:^7.27.2" - "@babel/traverse": "npm:^7.28.5" - "@babel/types": "npm:^7.28.5" - "@jridgewell/remapping": "npm:^2.3.5" - convert-source-map: "npm:^2.0.0" - debug: "npm:^4.1.0" - gensync: "npm:^1.0.0-beta.2" - json5: "npm:^2.2.3" - semver: "npm:^6.3.1" - checksum: 10c0/535f82238027621da6bdffbdbe896ebad3558b311d6f8abc680637a9859b96edbf929ab010757055381570b29cf66c4a295b5618318d27a4273c0e2033925e72 - languageName: node - linkType: hard - -"@babel/core@npm:^7.28.0": - version: 7.28.3 - resolution: "@babel/core@npm:7.28.3" - dependencies: - "@ampproject/remapping": "npm:^2.2.0" - "@babel/code-frame": "npm:^7.27.1" - "@babel/generator": "npm:^7.28.3" - "@babel/helper-compilation-targets": "npm:^7.27.2" - "@babel/helper-module-transforms": "npm:^7.28.3" - "@babel/helpers": "npm:^7.28.3" - "@babel/parser": "npm:^7.28.3" - "@babel/template": "npm:^7.27.2" - "@babel/traverse": "npm:^7.28.3" - "@babel/types": "npm:^7.28.2" - convert-source-map: "npm:^2.0.0" - debug: "npm:^4.1.0" - gensync: "npm:^1.0.0-beta.2" - json5: "npm:^2.2.3" - semver: "npm:^6.3.1" - checksum: 10c0/e6b3eb830c4b93f5a442b305776df1cd2bb4fafa4612355366f67c764f3e54a69d45b84def77fb2d4fd83439102667b0a92c3ea2838f678733245b748c602a7b - languageName: node - linkType: hard - -"@babel/generator@npm:^7.28.3": - version: 7.28.3 - resolution: "@babel/generator@npm:7.28.3" - dependencies: - "@babel/parser": "npm:^7.28.3" - "@babel/types": "npm:^7.28.2" - "@jridgewell/gen-mapping": "npm:^0.3.12" - "@jridgewell/trace-mapping": "npm:^0.3.28" - jsesc: "npm:^3.0.2" - checksum: 10c0/0ff58bcf04f8803dcc29479b547b43b9b0b828ec1ee0668e92d79f9e90f388c28589056637c5ff2fd7bcf8d153c990d29c448d449d852bf9d1bc64753ca462bc - languageName: node - linkType: hard - -"@babel/generator@npm:^7.28.5": - version: 7.28.5 - resolution: "@babel/generator@npm:7.28.5" - dependencies: - "@babel/parser": "npm:^7.28.5" - "@babel/types": "npm:^7.28.5" - "@jridgewell/gen-mapping": "npm:^0.3.12" - "@jridgewell/trace-mapping": "npm:^0.3.28" - jsesc: "npm:^3.0.2" - checksum: 10c0/9f219fe1d5431b6919f1a5c60db8d5d34fe546c0d8f5a8511b32f847569234ffc8032beb9e7404649a143f54e15224ecb53a3d11b6bb85c3203e573d91fca752 - languageName: node - linkType: hard - -"@babel/helper-compilation-targets@npm:^7.27.2": - version: 7.27.2 - resolution: "@babel/helper-compilation-targets@npm:7.27.2" - dependencies: - "@babel/compat-data": "npm:^7.27.2" - "@babel/helper-validator-option": "npm:^7.27.1" - browserslist: "npm:^4.24.0" - lru-cache: "npm:^5.1.1" - semver: "npm:^6.3.1" - checksum: 10c0/f338fa00dcfea931804a7c55d1a1c81b6f0a09787e528ec580d5c21b3ecb3913f6cb0f361368973ce953b824d910d3ac3e8a8ee15192710d3563826447193ad1 - languageName: node - linkType: hard - -"@babel/helper-globals@npm:^7.28.0": - version: 7.28.0 - resolution: "@babel/helper-globals@npm:7.28.0" - checksum: 10c0/5a0cd0c0e8c764b5f27f2095e4243e8af6fa145daea2b41b53c0c1414fe6ff139e3640f4e2207ae2b3d2153a1abd346f901c26c290ee7cb3881dd922d4ee9232 - languageName: node - linkType: hard - -"@babel/helper-module-imports@npm:^7.16.7, @babel/helper-module-imports@npm:^7.27.1": - version: 7.27.1 - resolution: "@babel/helper-module-imports@npm:7.27.1" - dependencies: - "@babel/traverse": "npm:^7.27.1" - "@babel/types": "npm:^7.27.1" - checksum: 10c0/e00aace096e4e29290ff8648455c2bc4ed982f0d61dbf2db1b5e750b9b98f318bf5788d75a4f974c151bd318fd549e81dbcab595f46b14b81c12eda3023f51e8 - languageName: node - linkType: hard - -"@babel/helper-module-transforms@npm:^7.28.3": - version: 7.28.3 - resolution: "@babel/helper-module-transforms@npm:7.28.3" - dependencies: - "@babel/helper-module-imports": "npm:^7.27.1" - "@babel/helper-validator-identifier": "npm:^7.27.1" - "@babel/traverse": "npm:^7.28.3" - peerDependencies: - "@babel/core": ^7.0.0 - checksum: 10c0/549be62515a6d50cd4cfefcab1b005c47f89bd9135a22d602ee6a5e3a01f27571868ada10b75b033569f24dc4a2bb8d04bfa05ee75c16da7ade2d0db1437fcdb - languageName: node - linkType: hard - -"@babel/helper-plugin-utils@npm:^7.27.1": - version: 7.27.1 - resolution: "@babel/helper-plugin-utils@npm:7.27.1" - checksum: 10c0/94cf22c81a0c11a09b197b41ab488d416ff62254ce13c57e62912c85700dc2e99e555225787a4099ff6bae7a1812d622c80fbaeda824b79baa10a6c5ac4cf69b - languageName: node - linkType: hard - -"@babel/helper-string-parser@npm:^7.27.1": - version: 7.27.1 - resolution: "@babel/helper-string-parser@npm:7.27.1" - checksum: 10c0/8bda3448e07b5583727c103560bcf9c4c24b3c1051a4c516d4050ef69df37bb9a4734a585fe12725b8c2763de0a265aa1e909b485a4e3270b7cfd3e4dbe4b602 - languageName: node - linkType: hard - -"@babel/helper-validator-identifier@npm:^7.27.1": - version: 7.27.1 - resolution: "@babel/helper-validator-identifier@npm:7.27.1" - checksum: 10c0/c558f11c4871d526498e49d07a84752d1800bf72ac0d3dad100309a2eaba24efbf56ea59af5137ff15e3a00280ebe588560534b0e894a4750f8b1411d8f78b84 - languageName: node - linkType: hard - -"@babel/helper-validator-identifier@npm:^7.28.5": - version: 7.28.5 - resolution: "@babel/helper-validator-identifier@npm:7.28.5" - checksum: 10c0/42aaebed91f739a41f3d80b72752d1f95fd7c72394e8e4bd7cdd88817e0774d80a432451bcba17c2c642c257c483bf1d409dd4548883429ea9493a3bc4ab0847 - languageName: node - linkType: hard - -"@babel/helper-validator-option@npm:^7.27.1": - version: 7.27.1 - resolution: "@babel/helper-validator-option@npm:7.27.1" - checksum: 10c0/6fec5f006eba40001a20f26b1ef5dbbda377b7b68c8ad518c05baa9af3f396e780bdfded24c4eef95d14bb7b8fd56192a6ed38d5d439b97d10efc5f1a191d148 - languageName: node - linkType: hard - -"@babel/helpers@npm:^7.28.3": - version: 7.28.3 - resolution: "@babel/helpers@npm:7.28.3" - dependencies: - "@babel/template": "npm:^7.27.2" - "@babel/types": "npm:^7.28.2" - checksum: 10c0/03a8f94135415eec62d37be9c62c63908f2d5386c7b00e04545de4961996465775330e3eb57717ea7451e19b0e24615777ebfec408c2adb1df3b10b4df6bf1ce - languageName: node - linkType: hard - -"@babel/helpers@npm:^7.28.4": - version: 7.28.4 - resolution: "@babel/helpers@npm:7.28.4" - dependencies: - "@babel/template": "npm:^7.27.2" - "@babel/types": "npm:^7.28.4" - checksum: 10c0/aaa5fb8098926dfed5f223adf2c5e4c7fbba4b911b73dfec2d7d3083f8ba694d201a206db673da2d9b3ae8c01793e795767654558c450c8c14b4c2175b4fcb44 - languageName: node - linkType: hard - -"@babel/parser@npm:^7.1.0, @babel/parser@npm:^7.20.7, @babel/parser@npm:^7.27.2, @babel/parser@npm:^7.28.3": - version: 7.28.3 - resolution: "@babel/parser@npm:7.28.3" - dependencies: - "@babel/types": "npm:^7.28.2" - bin: - parser: ./bin/babel-parser.js - checksum: 10c0/1f41eb82623b0ca0f94521b57f4790c6c457cd922b8e2597985b36bdec24114a9ccf54640286a760ceb60f11fe9102d192bf60477aee77f5d45f1029b9b72729 - languageName: node - linkType: hard - -"@babel/parser@npm:^7.24.4, @babel/parser@npm:^7.28.5": - version: 7.28.5 - resolution: "@babel/parser@npm:7.28.5" - dependencies: - "@babel/types": "npm:^7.28.5" - bin: - parser: ./bin/babel-parser.js - checksum: 10c0/5bbe48bf2c79594ac02b490a41ffde7ef5aa22a9a88ad6bcc78432a6ba8a9d638d531d868bd1f104633f1f6bba9905746e15185b8276a3756c42b765d131b1ef - languageName: node - linkType: hard - -"@babel/plugin-transform-react-jsx-self@npm:^7.27.1": - version: 7.27.1 - resolution: "@babel/plugin-transform-react-jsx-self@npm:7.27.1" - dependencies: - "@babel/helper-plugin-utils": "npm:^7.27.1" - peerDependencies: - "@babel/core": ^7.0.0-0 - checksum: 10c0/00a4f917b70a608f9aca2fb39aabe04a60aa33165a7e0105fd44b3a8531630eb85bf5572e9f242f51e6ad2fa38c2e7e780902176c863556c58b5ba6f6e164031 - languageName: node - linkType: hard - -"@babel/plugin-transform-react-jsx-source@npm:^7.27.1": - version: 7.27.1 - resolution: "@babel/plugin-transform-react-jsx-source@npm:7.27.1" - dependencies: - "@babel/helper-plugin-utils": "npm:^7.27.1" - peerDependencies: - "@babel/core": ^7.0.0-0 - checksum: 10c0/5e67b56c39c4d03e59e03ba80692b24c5a921472079b63af711b1d250fc37c1733a17069b63537f750f3e937ec44a42b1ee6a46cd23b1a0df5163b17f741f7f2 - languageName: node - linkType: hard - -"@babel/runtime@npm:^7.12.5, @babel/runtime@npm:^7.18.3, @babel/runtime@npm:^7.23.2, @babel/runtime@npm:^7.26.9, @babel/runtime@npm:^7.28.2, @babel/runtime@npm:^7.5.5, @babel/runtime@npm:^7.8.7, @babel/runtime@npm:^7.9.2": - version: 7.28.3 - resolution: "@babel/runtime@npm:7.28.3" - checksum: 10c0/b360f82c2c5114f2a062d4d143d7b4ec690094764853937110585a9497977aed66c102166d0e404766c274e02a50ffb8f6d77fef7251ecf3f607f0e03e6397bc - languageName: node - linkType: hard - -"@babel/template@npm:^7.27.2": - version: 7.27.2 - resolution: "@babel/template@npm:7.27.2" - dependencies: - "@babel/code-frame": "npm:^7.27.1" - "@babel/parser": "npm:^7.27.2" - "@babel/types": "npm:^7.27.1" - checksum: 10c0/ed9e9022651e463cc5f2cc21942f0e74544f1754d231add6348ff1b472985a3b3502041c0be62dc99ed2d12cfae0c51394bf827452b98a2f8769c03b87aadc81 - languageName: node - linkType: hard - -"@babel/traverse@npm:^7.27.1, @babel/traverse@npm:^7.28.3": - version: 7.28.3 - resolution: "@babel/traverse@npm:7.28.3" - dependencies: - "@babel/code-frame": "npm:^7.27.1" - "@babel/generator": "npm:^7.28.3" - "@babel/helper-globals": "npm:^7.28.0" - "@babel/parser": "npm:^7.28.3" - "@babel/template": "npm:^7.27.2" - "@babel/types": "npm:^7.28.2" - debug: "npm:^4.3.1" - checksum: 10c0/26e95b29a46925b7b41255e03185b7e65b2c4987e14bbee7bbf95867fb19c69181f301bbe1c7b201d4fe0cce6aa0cbea0282dad74b3a0fef3d9058f6c76fdcb3 - languageName: node - linkType: hard - -"@babel/traverse@npm:^7.28.5": - version: 7.28.5 - resolution: "@babel/traverse@npm:7.28.5" - dependencies: - "@babel/code-frame": "npm:^7.27.1" - "@babel/generator": "npm:^7.28.5" - "@babel/helper-globals": "npm:^7.28.0" - "@babel/parser": "npm:^7.28.5" - "@babel/template": "npm:^7.27.2" - "@babel/types": "npm:^7.28.5" - debug: "npm:^4.3.1" - checksum: 10c0/f6c4a595993ae2b73f2d4cd9c062f2e232174d293edd4abe1d715bd6281da8d99e47c65857e8d0917d9384c65972f4acdebc6749a7c40a8fcc38b3c7fb3e706f - languageName: node - linkType: hard - -"@babel/types@npm:^7.0.0, @babel/types@npm:^7.20.7, @babel/types@npm:^7.27.1, @babel/types@npm:^7.28.2": - version: 7.28.2 - resolution: "@babel/types@npm:7.28.2" - dependencies: - "@babel/helper-string-parser": "npm:^7.27.1" - "@babel/helper-validator-identifier": "npm:^7.27.1" - checksum: 10c0/24b11c9368e7e2c291fe3c1bcd1ed66f6593a3975f479cbb9dd7b8c8d8eab8a962b0d2fca616c043396ce82500ac7d23d594fbbbd013828182c01596370a0b10 - languageName: node - linkType: hard - -"@babel/types@npm:^7.26.0, @babel/types@npm:^7.28.4, @babel/types@npm:^7.28.5": - version: 7.28.5 - resolution: "@babel/types@npm:7.28.5" - dependencies: - "@babel/helper-string-parser": "npm:^7.27.1" - "@babel/helper-validator-identifier": "npm:^7.28.5" - checksum: 10c0/a5a483d2100befbf125793640dec26b90b95fd233a94c19573325898a5ce1e52cdfa96e495c7dcc31b5eca5b66ce3e6d4a0f5a4a62daec271455959f208ab08a - languageName: node - linkType: hard - -"@emotion/babel-plugin@npm:^11.13.5": - version: 11.13.5 - resolution: "@emotion/babel-plugin@npm:11.13.5" - dependencies: - "@babel/helper-module-imports": "npm:^7.16.7" - "@babel/runtime": "npm:^7.18.3" - "@emotion/hash": "npm:^0.9.2" - "@emotion/memoize": "npm:^0.9.0" - "@emotion/serialize": "npm:^1.3.3" - babel-plugin-macros: "npm:^3.1.0" - convert-source-map: "npm:^1.5.0" - escape-string-regexp: "npm:^4.0.0" - find-root: "npm:^1.1.0" - source-map: "npm:^0.5.7" - stylis: "npm:4.2.0" - checksum: 10c0/8ccbfec7defd0e513cb8a1568fa179eac1e20c35fda18aed767f6c59ea7314363ebf2de3e9d2df66c8ad78928dc3dceeded84e6fa8059087cae5c280090aeeeb - languageName: node - linkType: hard - -"@emotion/cache@npm:^11.14.0": - version: 11.14.0 - resolution: "@emotion/cache@npm:11.14.0" - dependencies: - "@emotion/memoize": "npm:^0.9.0" - "@emotion/sheet": "npm:^1.4.0" - "@emotion/utils": "npm:^1.4.2" - "@emotion/weak-memoize": "npm:^0.4.0" - stylis: "npm:4.2.0" - checksum: 10c0/3fa3e7a431ab6f8a47c67132a00ac8358f428c1b6c8421d4b20de9df7c18e95eec04a5a6ff5a68908f98d3280044f247b4965ac63df8302d2c94dba718769724 - languageName: node - linkType: hard - -"@emotion/hash@npm:^0.9.2": - version: 0.9.2 - resolution: "@emotion/hash@npm:0.9.2" - checksum: 10c0/0dc254561a3cc0a06a10bbce7f6a997883fd240c8c1928b93713f803a2e9153a257a488537012efe89dbe1246f2abfe2add62cdb3471a13d67137fcb808e81c2 - languageName: node - linkType: hard - -"@emotion/is-prop-valid@npm:^1.3.0": - version: 1.3.1 - resolution: "@emotion/is-prop-valid@npm:1.3.1" - dependencies: - "@emotion/memoize": "npm:^0.9.0" - checksum: 10c0/123215540c816ff510737ec68dcc499c53ea4deb0bb6c2c27c03ed21046e2e69f6ad07a7a174d271c6cfcbcc9ea44e1763e0cf3875c92192f7689216174803cd - languageName: node - linkType: hard - -"@emotion/memoize@npm:^0.9.0": - version: 0.9.0 - resolution: "@emotion/memoize@npm:0.9.0" - checksum: 10c0/13f474a9201c7f88b543e6ea42f55c04fb2fdc05e6c5a3108aced2f7e7aa7eda7794c56bba02985a46d8aaa914fcdde238727a98341a96e2aec750d372dadd15 - languageName: node - linkType: hard - -"@emotion/react@npm:^11.14.0": - version: 11.14.0 - resolution: "@emotion/react@npm:11.14.0" - dependencies: - "@babel/runtime": "npm:^7.18.3" - "@emotion/babel-plugin": "npm:^11.13.5" - "@emotion/cache": "npm:^11.14.0" - "@emotion/serialize": "npm:^1.3.3" - "@emotion/use-insertion-effect-with-fallbacks": "npm:^1.2.0" - "@emotion/utils": "npm:^1.4.2" - "@emotion/weak-memoize": "npm:^0.4.0" - hoist-non-react-statics: "npm:^3.3.1" - peerDependencies: - react: ">=16.8.0" - peerDependenciesMeta: - "@types/react": - optional: true - checksum: 10c0/d0864f571a9f99ec643420ef31fde09e2006d3943a6aba079980e4d5f6e9f9fecbcc54b8f617fe003c00092ff9d5241179149ffff2810cb05cf72b4620cfc031 - languageName: node - linkType: hard - -"@emotion/serialize@npm:^1.3.3": - version: 1.3.3 - resolution: "@emotion/serialize@npm:1.3.3" - dependencies: - "@emotion/hash": "npm:^0.9.2" - "@emotion/memoize": "npm:^0.9.0" - "@emotion/unitless": "npm:^0.10.0" - "@emotion/utils": "npm:^1.4.2" - csstype: "npm:^3.0.2" - checksum: 10c0/b28cb7de59de382021de2b26c0c94ebbfb16967a1b969a56fdb6408465a8993df243bfbd66430badaa6800e1834724e84895f5a6a9d97d0d224de3d77852acb4 - languageName: node - linkType: hard - -"@emotion/sheet@npm:^1.4.0": - version: 1.4.0 - resolution: "@emotion/sheet@npm:1.4.0" - checksum: 10c0/3ca72d1650a07d2fbb7e382761b130b4a887dcd04e6574b2d51ce578791240150d7072a9bcb4161933abbcd1e38b243a6fb4464a7fe991d700c17aa66bb5acc7 - languageName: node - linkType: hard - -"@emotion/styled@npm:^11.14.0": - version: 11.14.1 - resolution: "@emotion/styled@npm:11.14.1" - dependencies: - "@babel/runtime": "npm:^7.18.3" - "@emotion/babel-plugin": "npm:^11.13.5" - "@emotion/is-prop-valid": "npm:^1.3.0" - "@emotion/serialize": "npm:^1.3.3" - "@emotion/use-insertion-effect-with-fallbacks": "npm:^1.2.0" - "@emotion/utils": "npm:^1.4.2" - peerDependencies: - "@emotion/react": ^11.0.0-rc.0 - react: ">=16.8.0" - peerDependenciesMeta: - "@types/react": - optional: true - checksum: 10c0/2bbf8451df49c967e41fbcf8111a7f6dafe6757f0cc113f2f6e287206c45ac1d54dc8a95a483b7c0cee8614b8a8d08155bded6453d6721de1f8cc8d5b9216963 - languageName: node - linkType: hard - -"@emotion/unitless@npm:^0.10.0": - version: 0.10.0 - resolution: "@emotion/unitless@npm:0.10.0" - checksum: 10c0/150943192727b7650eb9a6851a98034ddb58a8b6958b37546080f794696141c3760966ac695ab9af97efe10178690987aee4791f9f0ad1ff76783cdca83c1d49 - languageName: node - linkType: hard - -"@emotion/use-insertion-effect-with-fallbacks@npm:^1.2.0": - version: 1.2.0 - resolution: "@emotion/use-insertion-effect-with-fallbacks@npm:1.2.0" - peerDependencies: - react: ">=16.8.0" - checksum: 10c0/074dbc92b96bdc09209871070076e3b0351b6b47efefa849a7d9c37ab142130767609ca1831da0055988974e3b895c1de7606e4c421fecaa27c3e56a2afd3b08 - languageName: node - linkType: hard - -"@emotion/utils@npm:^1.4.2": - version: 1.4.2 - resolution: "@emotion/utils@npm:1.4.2" - checksum: 10c0/7d0010bf60a2a8c1a033b6431469de4c80e47aeb8fd856a17c1d1f76bbc3a03161a34aeaa78803566e29681ca551e7bf9994b68e9c5f5c796159923e44f78d9a - languageName: node - linkType: hard - -"@emotion/weak-memoize@npm:^0.4.0": - version: 0.4.0 - resolution: "@emotion/weak-memoize@npm:0.4.0" - checksum: 10c0/64376af11f1266042d03b3305c30b7502e6084868e33327e944b539091a472f089db307af69240f7188f8bc6b319276fd7b141a36613f1160d73d12a60f6ca1a - languageName: node - linkType: hard - -"@esbuild/aix-ppc64@npm:0.21.5": - version: 0.21.5 - resolution: "@esbuild/aix-ppc64@npm:0.21.5" - conditions: os=aix & cpu=ppc64 - languageName: node - linkType: hard - -"@esbuild/android-arm64@npm:0.21.5": - version: 0.21.5 - resolution: "@esbuild/android-arm64@npm:0.21.5" - conditions: os=android & cpu=arm64 - languageName: node - linkType: hard - -"@esbuild/android-arm@npm:0.21.5": - version: 0.21.5 - resolution: "@esbuild/android-arm@npm:0.21.5" - conditions: os=android & cpu=arm - languageName: node - linkType: hard - -"@esbuild/android-x64@npm:0.21.5": - version: 0.21.5 - resolution: "@esbuild/android-x64@npm:0.21.5" - conditions: os=android & cpu=x64 - languageName: node - linkType: hard - -"@esbuild/darwin-arm64@npm:0.21.5": - version: 0.21.5 - resolution: "@esbuild/darwin-arm64@npm:0.21.5" - conditions: os=darwin & cpu=arm64 - languageName: node - linkType: hard - -"@esbuild/darwin-x64@npm:0.21.5": - version: 0.21.5 - resolution: "@esbuild/darwin-x64@npm:0.21.5" - conditions: os=darwin & cpu=x64 - languageName: node - linkType: hard - -"@esbuild/freebsd-arm64@npm:0.21.5": - version: 0.21.5 - resolution: "@esbuild/freebsd-arm64@npm:0.21.5" - conditions: os=freebsd & cpu=arm64 - languageName: node - linkType: hard - -"@esbuild/freebsd-x64@npm:0.21.5": - version: 0.21.5 - resolution: "@esbuild/freebsd-x64@npm:0.21.5" - conditions: os=freebsd & cpu=x64 - languageName: node - linkType: hard - -"@esbuild/linux-arm64@npm:0.21.5": - version: 0.21.5 - resolution: "@esbuild/linux-arm64@npm:0.21.5" - conditions: os=linux & cpu=arm64 - languageName: node - linkType: hard - -"@esbuild/linux-arm@npm:0.21.5": - version: 0.21.5 - resolution: "@esbuild/linux-arm@npm:0.21.5" - conditions: os=linux & cpu=arm - languageName: node - linkType: hard - -"@esbuild/linux-ia32@npm:0.21.5": - version: 0.21.5 - resolution: "@esbuild/linux-ia32@npm:0.21.5" - conditions: os=linux & cpu=ia32 - languageName: node - linkType: hard - -"@esbuild/linux-loong64@npm:0.21.5": - version: 0.21.5 - resolution: "@esbuild/linux-loong64@npm:0.21.5" - conditions: os=linux & cpu=loong64 - languageName: node - linkType: hard - -"@esbuild/linux-mips64el@npm:0.21.5": - version: 0.21.5 - resolution: "@esbuild/linux-mips64el@npm:0.21.5" - conditions: os=linux & cpu=mips64el - languageName: node - linkType: hard - -"@esbuild/linux-ppc64@npm:0.21.5": - version: 0.21.5 - resolution: "@esbuild/linux-ppc64@npm:0.21.5" - conditions: os=linux & cpu=ppc64 - languageName: node - linkType: hard - -"@esbuild/linux-riscv64@npm:0.21.5": - version: 0.21.5 - resolution: "@esbuild/linux-riscv64@npm:0.21.5" - conditions: os=linux & cpu=riscv64 - languageName: node - linkType: hard - -"@esbuild/linux-s390x@npm:0.21.5": - version: 0.21.5 - resolution: "@esbuild/linux-s390x@npm:0.21.5" - conditions: os=linux & cpu=s390x - languageName: node - linkType: hard - -"@esbuild/linux-x64@npm:0.21.5": - version: 0.21.5 - resolution: "@esbuild/linux-x64@npm:0.21.5" - conditions: os=linux & cpu=x64 - languageName: node - linkType: hard - -"@esbuild/netbsd-x64@npm:0.21.5": - version: 0.21.5 - resolution: "@esbuild/netbsd-x64@npm:0.21.5" - conditions: os=netbsd & cpu=x64 - languageName: node - linkType: hard - -"@esbuild/openbsd-x64@npm:0.21.5": - version: 0.21.5 - resolution: "@esbuild/openbsd-x64@npm:0.21.5" - conditions: os=openbsd & cpu=x64 - languageName: node - linkType: hard - -"@esbuild/sunos-x64@npm:0.21.5": - version: 0.21.5 - resolution: "@esbuild/sunos-x64@npm:0.21.5" - conditions: os=sunos & cpu=x64 - languageName: node - linkType: hard - -"@esbuild/win32-arm64@npm:0.21.5": - version: 0.21.5 - resolution: "@esbuild/win32-arm64@npm:0.21.5" - conditions: os=win32 & cpu=arm64 - languageName: node - linkType: hard - -"@esbuild/win32-ia32@npm:0.21.5": - version: 0.21.5 - resolution: "@esbuild/win32-ia32@npm:0.21.5" - conditions: os=win32 & cpu=ia32 - languageName: node - linkType: hard - -"@esbuild/win32-x64@npm:0.21.5": - version: 0.21.5 - resolution: "@esbuild/win32-x64@npm:0.21.5" - conditions: os=win32 & cpu=x64 - languageName: node - linkType: hard - -"@eslint-community/eslint-utils@npm:^4.2.0, @eslint-community/eslint-utils@npm:^4.7.0": - version: 4.7.0 - resolution: "@eslint-community/eslint-utils@npm:4.7.0" - dependencies: - eslint-visitor-keys: "npm:^3.4.3" - peerDependencies: - eslint: ^6.0.0 || ^7.0.0 || >=8.0.0 - checksum: 10c0/c0f4f2bd73b7b7a9de74b716a664873d08ab71ab439e51befe77d61915af41a81ecec93b408778b3a7856185244c34c2c8ee28912072ec14def84ba2dec70adf - languageName: node - linkType: hard - -"@eslint-community/regexpp@npm:^4.10.0, @eslint-community/regexpp@npm:^4.12.1": - version: 4.12.1 - resolution: "@eslint-community/regexpp@npm:4.12.1" - checksum: 10c0/a03d98c246bcb9109aec2c08e4d10c8d010256538dcb3f56610191607214523d4fb1b00aa81df830b6dffb74c5fa0be03642513a289c567949d3e550ca11cdf6 - languageName: node - linkType: hard - -"@eslint/config-array@npm:^0.21.0": - version: 0.21.0 - resolution: "@eslint/config-array@npm:0.21.0" - dependencies: - "@eslint/object-schema": "npm:^2.1.6" - debug: "npm:^4.3.1" - minimatch: "npm:^3.1.2" - checksum: 10c0/0ea801139166c4aa56465b309af512ef9b2d3c68f9198751bbc3e21894fe70f25fbf26e1b0e9fffff41857bc21bfddeee58649ae6d79aadcd747db0c5dca771f - languageName: node - linkType: hard - -"@eslint/config-helpers@npm:^0.3.1": - version: 0.3.1 - resolution: "@eslint/config-helpers@npm:0.3.1" - checksum: 10c0/f6c5b3a0b76a0d7d84cc93e310c259e6c3e0792ddd0a62c5fc0027796ffae44183432cb74b2c2b1162801ee1b1b34a6beb5d90a151632b4df7349f994146a856 - languageName: node - linkType: hard - -"@eslint/core@npm:^0.15.2": - version: 0.15.2 - resolution: "@eslint/core@npm:0.15.2" - dependencies: - "@types/json-schema": "npm:^7.0.15" - checksum: 10c0/c17a6dc4f5a6006ecb60165cc38bcd21fefb4a10c7a2578a0cfe5813bbd442531a87ed741da5adab5eb678e8e693fda2e2b14555b035355537e32bcec367ea17 - languageName: node - linkType: hard - -"@eslint/eslintrc@npm:^3.3.1": - version: 3.3.1 - resolution: "@eslint/eslintrc@npm:3.3.1" - dependencies: - ajv: "npm:^6.12.4" - debug: "npm:^4.3.2" - espree: "npm:^10.0.1" - globals: "npm:^14.0.0" - ignore: "npm:^5.2.0" - import-fresh: "npm:^3.2.1" - js-yaml: "npm:^4.1.0" - minimatch: "npm:^3.1.2" - strip-json-comments: "npm:^3.1.1" - checksum: 10c0/b0e63f3bc5cce4555f791a4e487bf999173fcf27c65e1ab6e7d63634d8a43b33c3693e79f192cbff486d7df1be8ebb2bd2edc6e70ddd486cbfa84a359a3e3b41 - languageName: node - linkType: hard - -"@eslint/js@npm:9.33.0, @eslint/js@npm:^9.9.0": - version: 9.33.0 - resolution: "@eslint/js@npm:9.33.0" - checksum: 10c0/4c42c9abde76a183b8e47205fd6c3116b058f82f07b6ad4de40de56cdb30a36e9ecd40efbea1b63a84d08c206aadbb0aa39a890197e1ad6455a8e542df98f186 - languageName: node - linkType: hard - -"@eslint/object-schema@npm:^2.1.6": - version: 2.1.6 - resolution: "@eslint/object-schema@npm:2.1.6" - checksum: 10c0/b8cdb7edea5bc5f6a96173f8d768d3554a628327af536da2fc6967a93b040f2557114d98dbcdbf389d5a7b290985ad6a9ce5babc547f36fc1fde42e674d11a56 - languageName: node - linkType: hard - -"@eslint/plugin-kit@npm:^0.3.5": - version: 0.3.5 - resolution: "@eslint/plugin-kit@npm:0.3.5" - dependencies: - "@eslint/core": "npm:^0.15.2" - levn: "npm:^0.4.1" - checksum: 10c0/c178c1b58c574200c0fd125af3e4bc775daba7ce434ba6d1eeaf9bcb64b2e9fea75efabffb3ed3ab28858e55a016a5efa95f509994ee4341b341199ca630b89e - languageName: node - linkType: hard - -"@fontsource/roboto@npm:^5.1.0": - version: 5.2.6 - resolution: "@fontsource/roboto@npm:5.2.6" - checksum: 10c0/a2fbadf2f3b8ebb859e7b13ad82d25e8b43901c30ad93b08095c201617fcfded279d99e64a7b110614d2a1b5bca77631df7d009abf1ad6c6a4df301ceb330a51 - languageName: node - linkType: hard - -"@humanfs/core@npm:^0.19.1": - version: 0.19.1 - resolution: "@humanfs/core@npm:0.19.1" - checksum: 10c0/aa4e0152171c07879b458d0e8a704b8c3a89a8c0541726c6b65b81e84fd8b7564b5d6c633feadc6598307d34564bd53294b533491424e8e313d7ab6c7bc5dc67 - languageName: node - linkType: hard - -"@humanfs/node@npm:^0.16.6": - version: 0.16.6 - resolution: "@humanfs/node@npm:0.16.6" - dependencies: - "@humanfs/core": "npm:^0.19.1" - "@humanwhocodes/retry": "npm:^0.3.0" - checksum: 10c0/8356359c9f60108ec204cbd249ecd0356667359b2524886b357617c4a7c3b6aace0fd5a369f63747b926a762a88f8a25bc066fa1778508d110195ce7686243e1 - languageName: node - linkType: hard - -"@humanwhocodes/module-importer@npm:^1.0.1": - version: 1.0.1 - resolution: "@humanwhocodes/module-importer@npm:1.0.1" - checksum: 10c0/909b69c3b86d482c26b3359db16e46a32e0fb30bd306a3c176b8313b9e7313dba0f37f519de6aa8b0a1921349e505f259d19475e123182416a506d7f87e7f529 - languageName: node - linkType: hard - -"@humanwhocodes/retry@npm:^0.3.0": - version: 0.3.1 - resolution: "@humanwhocodes/retry@npm:0.3.1" - checksum: 10c0/f0da1282dfb45e8120480b9e2e275e2ac9bbe1cf016d046fdad8e27cc1285c45bb9e711681237944445157b430093412b4446c1ab3fc4bb037861b5904101d3b - languageName: node - linkType: hard - -"@humanwhocodes/retry@npm:^0.4.2": - version: 0.4.3 - resolution: "@humanwhocodes/retry@npm:0.4.3" - checksum: 10c0/3775bb30087d4440b3f7406d5a057777d90e4b9f435af488a4923ef249e93615fb78565a85f173a186a076c7706a81d0d57d563a2624e4de2c5c9c66c486ce42 - languageName: node - linkType: hard - -"@isaacs/balanced-match@npm:^4.0.1": - version: 4.0.1 - resolution: "@isaacs/balanced-match@npm:4.0.1" - checksum: 10c0/7da011805b259ec5c955f01cee903da72ad97c5e6f01ca96197267d3f33103d5b2f8a1af192140f3aa64526c593c8d098ae366c2b11f7f17645d12387c2fd420 - languageName: node - linkType: hard - -"@isaacs/brace-expansion@npm:^5.0.0": - version: 5.0.0 - resolution: "@isaacs/brace-expansion@npm:5.0.0" - dependencies: - "@isaacs/balanced-match": "npm:^4.0.1" - checksum: 10c0/b4d4812f4be53afc2c5b6c545001ff7a4659af68d4484804e9d514e183d20269bb81def8682c01a22b17c4d6aed14292c8494f7d2ac664e547101c1a905aa977 - languageName: node - linkType: hard - -"@isaacs/fs-minipass@npm:^4.0.0": - version: 4.0.1 - resolution: "@isaacs/fs-minipass@npm:4.0.1" - dependencies: - minipass: "npm:^7.0.4" - checksum: 10c0/c25b6dc1598790d5b55c0947a9b7d111cfa92594db5296c3b907e2f533c033666f692a3939eadac17b1c7c40d362d0b0635dc874cbfe3e70db7c2b07cc97a5d2 - languageName: node - linkType: hard - -"@jridgewell/gen-mapping@npm:^0.3.12, @jridgewell/gen-mapping@npm:^0.3.5": - version: 0.3.13 - resolution: "@jridgewell/gen-mapping@npm:0.3.13" - dependencies: - "@jridgewell/sourcemap-codec": "npm:^1.5.0" - "@jridgewell/trace-mapping": "npm:^0.3.24" - checksum: 10c0/9a7d65fb13bd9aec1fbab74cda08496839b7e2ceb31f5ab922b323e94d7c481ce0fc4fd7e12e2610915ed8af51178bdc61e168e92a8c8b8303b030b03489b13b - languageName: node - linkType: hard - -"@jridgewell/remapping@npm:^2.3.5": - version: 2.3.5 - resolution: "@jridgewell/remapping@npm:2.3.5" - dependencies: - "@jridgewell/gen-mapping": "npm:^0.3.5" - "@jridgewell/trace-mapping": "npm:^0.3.24" - checksum: 10c0/3de494219ffeb2c5c38711d0d7bb128097edf91893090a2dbc8ee0b55d092bb7347b1fd0f478486c5eab010e855c73927b1666f2107516d472d24a73017d1194 - languageName: node - linkType: hard - -"@jridgewell/resolve-uri@npm:^3.1.0": - version: 3.1.2 - resolution: "@jridgewell/resolve-uri@npm:3.1.2" - checksum: 10c0/d502e6fb516b35032331406d4e962c21fe77cdf1cbdb49c6142bcbd9e30507094b18972778a6e27cbad756209cfe34b1a27729e6fa08a2eb92b33943f680cf1e - languageName: node - linkType: hard - -"@jridgewell/sourcemap-codec@npm:^1.4.14, @jridgewell/sourcemap-codec@npm:^1.5.0, @jridgewell/sourcemap-codec@npm:^1.5.5": - version: 1.5.5 - resolution: "@jridgewell/sourcemap-codec@npm:1.5.5" - checksum: 10c0/f9e538f302b63c0ebc06eecb1dd9918dd4289ed36147a0ddce35d6ea4d7ebbda243cda7b2213b6a5e1d8087a298d5cf630fb2bd39329cdecb82017023f6081a0 - languageName: node - linkType: hard - -"@jridgewell/trace-mapping@npm:^0.3.24, @jridgewell/trace-mapping@npm:^0.3.28": - version: 0.3.30 - resolution: "@jridgewell/trace-mapping@npm:0.3.30" - dependencies: - "@jridgewell/resolve-uri": "npm:^3.1.0" - "@jridgewell/sourcemap-codec": "npm:^1.4.14" - checksum: 10c0/3a1516c10f44613b9ba27c37a02ff8f410893776b2b3dad20a391b51b884dd60f97bbb56936d65d2ff8fe978510a0000266654ab8426bdb9ceb5fb4585b19e23 - languageName: node - linkType: hard - -"@mui/core-downloads-tracker@npm:^7.3.1": - version: 7.3.1 - resolution: "@mui/core-downloads-tracker@npm:7.3.1" - checksum: 10c0/4d89ce22ebeba860e76a4fa35aa4946515d921c3c2834551639e99ff622afec2ae6c03fed881c4352283b2cad2f3b731fbde7802740b9b321451bf5e694f733c - languageName: node - linkType: hard - -"@mui/icons-material@npm:^7.1.1": - version: 7.3.1 - resolution: "@mui/icons-material@npm:7.3.1" - dependencies: - "@babel/runtime": "npm:^7.28.2" - peerDependencies: - "@mui/material": ^7.3.1 - "@types/react": ^17.0.0 || ^18.0.0 || ^19.0.0 - react: ^17.0.0 || ^18.0.0 || ^19.0.0 - peerDependenciesMeta: - "@types/react": - optional: true - checksum: 10c0/cd35a2417e5c655b3cda5ef9e90cdb9ff7482d36a0d74b05d22ea5fd43b5dd02be004e3ab314e57defcbabc55342e8bbb910660907ab219551fffd7a3bbc85fa - languageName: node - linkType: hard - -"@mui/material@npm:^7.1.1": - version: 7.3.1 - resolution: "@mui/material@npm:7.3.1" - dependencies: - "@babel/runtime": "npm:^7.28.2" - "@mui/core-downloads-tracker": "npm:^7.3.1" - "@mui/system": "npm:^7.3.1" - "@mui/types": "npm:^7.4.5" - "@mui/utils": "npm:^7.3.1" - "@popperjs/core": "npm:^2.11.8" - "@types/react-transition-group": "npm:^4.4.12" - clsx: "npm:^2.1.1" - csstype: "npm:^3.1.3" - prop-types: "npm:^15.8.1" - react-is: "npm:^19.1.1" - react-transition-group: "npm:^4.4.5" - peerDependencies: - "@emotion/react": ^11.5.0 - "@emotion/styled": ^11.3.0 - "@mui/material-pigment-css": ^7.3.1 - "@types/react": ^17.0.0 || ^18.0.0 || ^19.0.0 - react: ^17.0.0 || ^18.0.0 || ^19.0.0 - react-dom: ^17.0.0 || ^18.0.0 || ^19.0.0 - peerDependenciesMeta: - "@emotion/react": - optional: true - "@emotion/styled": - optional: true - "@mui/material-pigment-css": - optional: true - "@types/react": - optional: true - checksum: 10c0/078fc8ff9aae3ecbc07112a04dc1722afdae2564c51b5201340452f3bbef8deed4565734ca8b3222145431e3171cfeeff093b95920b7afb4f4bea3ea75346368 - languageName: node - linkType: hard - -"@mui/private-theming@npm:^7.3.1": - version: 7.3.1 - resolution: "@mui/private-theming@npm:7.3.1" - dependencies: - "@babel/runtime": "npm:^7.28.2" - "@mui/utils": "npm:^7.3.1" - prop-types: "npm:^15.8.1" - peerDependencies: - "@types/react": ^17.0.0 || ^18.0.0 || ^19.0.0 - react: ^17.0.0 || ^18.0.0 || ^19.0.0 - peerDependenciesMeta: - "@types/react": - optional: true - checksum: 10c0/f92ffdcdbaed42a6dbf4cc721921a6f64ae546e40e59f5da7ca6d7bb432870ae03d9ff84df26aede970e14f5eebb3743f76a212ac2ae46ff2bd06907ddd08005 - languageName: node - linkType: hard - -"@mui/styled-engine@npm:^7.3.1": - version: 7.3.1 - resolution: "@mui/styled-engine@npm:7.3.1" - dependencies: - "@babel/runtime": "npm:^7.28.2" - "@emotion/cache": "npm:^11.14.0" - "@emotion/serialize": "npm:^1.3.3" - "@emotion/sheet": "npm:^1.4.0" - csstype: "npm:^3.1.3" - prop-types: "npm:^15.8.1" - peerDependencies: - "@emotion/react": ^11.4.1 - "@emotion/styled": ^11.3.0 - react: ^17.0.0 || ^18.0.0 || ^19.0.0 - peerDependenciesMeta: - "@emotion/react": - optional: true - "@emotion/styled": - optional: true - checksum: 10c0/1b532153e3b7c357d3988e1a9fe569cb8e37aeece70ac17da8e1d1f3fb7dfc7134f6ec997b8cb3dcb70f8a0fea6574dc05bf198e14d971e0133e64c58d0a7755 - languageName: node - linkType: hard - -"@mui/system@npm:^7.3.1": - version: 7.3.1 - resolution: "@mui/system@npm:7.3.1" - dependencies: - "@babel/runtime": "npm:^7.28.2" - "@mui/private-theming": "npm:^7.3.1" - "@mui/styled-engine": "npm:^7.3.1" - "@mui/types": "npm:^7.4.5" - "@mui/utils": "npm:^7.3.1" - clsx: "npm:^2.1.1" - csstype: "npm:^3.1.3" - prop-types: "npm:^15.8.1" - peerDependencies: - "@emotion/react": ^11.5.0 - "@emotion/styled": ^11.3.0 - "@types/react": ^17.0.0 || ^18.0.0 || ^19.0.0 - react: ^17.0.0 || ^18.0.0 || ^19.0.0 - peerDependenciesMeta: - "@emotion/react": - optional: true - "@emotion/styled": - optional: true - "@types/react": - optional: true - checksum: 10c0/032759c8f3b7c6575771f3865535ce89f74ee0340c7e90769ebe0f44f7e1cd23a14dc1c98b8d7e61bb20f685d7d9b0a283823084f25deb1b73179224cb86357e - languageName: node - linkType: hard - -"@mui/types@npm:^7.4.5": - version: 7.4.5 - resolution: "@mui/types@npm:7.4.5" - dependencies: - "@babel/runtime": "npm:^7.28.2" - peerDependencies: - "@types/react": ^17.0.0 || ^18.0.0 || ^19.0.0 - peerDependenciesMeta: - "@types/react": - optional: true - checksum: 10c0/65015aacdc388a48e62c7bf11d167709f6d7e19e32fa8d4b36d34d391e10edc315bc2d64bf7f542d54fd973e812533800e715c5b0f149c29c3852e2c3dc11587 - languageName: node - linkType: hard - -"@mui/utils@npm:^7.3.1": - version: 7.3.1 - resolution: "@mui/utils@npm:7.3.1" - dependencies: - "@babel/runtime": "npm:^7.28.2" - "@mui/types": "npm:^7.4.5" - "@types/prop-types": "npm:^15.7.15" - clsx: "npm:^2.1.1" - prop-types: "npm:^15.8.1" - react-is: "npm:^19.1.1" - peerDependencies: - "@types/react": ^17.0.0 || ^18.0.0 || ^19.0.0 - react: ^17.0.0 || ^18.0.0 || ^19.0.0 - peerDependenciesMeta: - "@types/react": - optional: true - checksum: 10c0/5b36ba0d21f2394df9f9230d8c059d277f006adb31697b4a82da0cc5f720f8959fb24e281b59e5e1e6de6fc03dbc3e32fbd5ed4390570d465530c48a5ecbb2f8 - languageName: node - linkType: hard - -"@mui/x-date-pickers@npm:^8.8.0": - version: 8.10.2 - resolution: "@mui/x-date-pickers@npm:8.10.2" - dependencies: - "@babel/runtime": "npm:^7.28.2" - "@mui/utils": "npm:^7.3.1" - "@mui/x-internals": "npm:8.10.2" - "@types/react-transition-group": "npm:^4.4.12" - clsx: "npm:^2.1.1" - prop-types: "npm:^15.8.1" - react-transition-group: "npm:^4.4.5" - peerDependencies: - "@emotion/react": ^11.9.0 - "@emotion/styled": ^11.8.1 - "@mui/material": ^5.15.14 || ^6.0.0 || ^7.0.0 - "@mui/system": ^5.15.14 || ^6.0.0 || ^7.0.0 - date-fns: ^2.25.0 || ^3.2.0 || ^4.0.0 - date-fns-jalali: ^2.13.0-0 || ^3.2.0-0 || ^4.0.0-0 - dayjs: ^1.10.7 - luxon: ^3.0.2 - moment: ^2.29.4 - moment-hijri: ^2.1.2 || ^3.0.0 - moment-jalaali: ^0.7.4 || ^0.8.0 || ^0.9.0 || ^0.10.0 - react: ^17.0.0 || ^18.0.0 || ^19.0.0 - react-dom: ^17.0.0 || ^18.0.0 || ^19.0.0 - peerDependenciesMeta: - "@emotion/react": - optional: true - "@emotion/styled": - optional: true - date-fns: - optional: true - date-fns-jalali: - optional: true - dayjs: - optional: true - luxon: - optional: true - moment: - optional: true - moment-hijri: - optional: true - moment-jalaali: - optional: true - checksum: 10c0/5ba6b3ff59b9203f24b91480bfd5c8830d7ef561083ec048fae75af806946635397b605f2cb37f3f0dc835bd8354d57db76861a79984914086da68e16e9e12b6 - languageName: node - linkType: hard - -"@mui/x-internals@npm:8.10.2": - version: 8.10.2 - resolution: "@mui/x-internals@npm:8.10.2" - dependencies: - "@babel/runtime": "npm:^7.28.2" - "@mui/utils": "npm:^7.3.1" - reselect: "npm:^5.1.1" - use-sync-external-store: "npm:^1.5.0" - peerDependencies: - react: ^17.0.0 || ^18.0.0 || ^19.0.0 - checksum: 10c0/644bd1be5fa19b9376c0853eaac0670fef6611f3cac0657acdee1931193a749fb49920deb41ad41ebf9abc2242ca807c0c9e8b6f088e8f7cf9c85a8f802f3022 - languageName: node - linkType: hard - -"@nodelib/fs.scandir@npm:2.1.5": - version: 2.1.5 - resolution: "@nodelib/fs.scandir@npm:2.1.5" - dependencies: - "@nodelib/fs.stat": "npm:2.0.5" - run-parallel: "npm:^1.1.9" - checksum: 10c0/732c3b6d1b1e967440e65f284bd06e5821fedf10a1bea9ed2bb75956ea1f30e08c44d3def9d6a230666574edbaf136f8cfd319c14fd1f87c66e6a44449afb2eb - languageName: node - linkType: hard - -"@nodelib/fs.stat@npm:2.0.5, @nodelib/fs.stat@npm:^2.0.2": - version: 2.0.5 - resolution: "@nodelib/fs.stat@npm:2.0.5" - checksum: 10c0/88dafe5e3e29a388b07264680dc996c17f4bda48d163a9d4f5c1112979f0ce8ec72aa7116122c350b4e7976bc5566dc3ddb579be1ceaacc727872eb4ed93926d - languageName: node - linkType: hard - -"@nodelib/fs.walk@npm:^1.2.3": - version: 1.2.8 - resolution: "@nodelib/fs.walk@npm:1.2.8" - dependencies: - "@nodelib/fs.scandir": "npm:2.1.5" - fastq: "npm:^1.6.0" - checksum: 10c0/db9de047c3bb9b51f9335a7bb46f4fcfb6829fb628318c12115fbaf7d369bfce71c15b103d1fc3b464812d936220ee9bc1c8f762d032c9f6be9acc99249095b1 - languageName: node - linkType: hard - -"@npmcli/agent@npm:^4.0.0": - version: 4.0.0 - resolution: "@npmcli/agent@npm:4.0.0" - dependencies: - agent-base: "npm:^7.1.0" - http-proxy-agent: "npm:^7.0.0" - https-proxy-agent: "npm:^7.0.1" - lru-cache: "npm:^11.2.1" - socks-proxy-agent: "npm:^8.0.3" - checksum: 10c0/f7b5ce0f3dd42c3f8c6546e8433573d8049f67ef11ec22aa4704bc41483122f68bf97752e06302c455ead667af5cb753e6a09bff06632bc465c1cfd4c4b75a53 - languageName: node - linkType: hard - -"@npmcli/fs@npm:^5.0.0": - version: 5.0.0 - resolution: "@npmcli/fs@npm:5.0.0" - dependencies: - semver: "npm:^7.3.5" - checksum: 10c0/26e376d780f60ff16e874a0ac9bc3399186846baae0b6e1352286385ac134d900cc5dafaded77f38d77f86898fc923ae1cee9d7399f0275b1aa24878915d722b - languageName: node - linkType: hard - -"@popperjs/core@npm:^2.11.8": - version: 2.11.8 - resolution: "@popperjs/core@npm:2.11.8" - checksum: 10c0/4681e682abc006d25eb380d0cf3efc7557043f53b6aea7a5057d0d1e7df849a00e281cd8ea79c902a35a414d7919621fc2ba293ecec05f413598e0b23d5a1e63 - languageName: node - linkType: hard - -"@redux-devtools/core@npm:^4.1.1": - version: 4.1.1 - resolution: "@redux-devtools/core@npm:4.1.1" - dependencies: - "@babel/runtime": "npm:^7.26.9" - "@redux-devtools/instrument": "npm:^2.2.0" - peerDependencies: - react: ^16.8.4 || ^17.0.0 || ^18.0.0 || ^19.0.0 - react-redux: ^7.0.0 || ^8.0.0 || ^9.0.0 - redux: ^3.5.2 || ^4.0.0 || ^5.0.0 - checksum: 10c0/02a436c3c3f46a29e03f12b2659ed48c3952b8ee984fb997401b0fdf84e3df12f65b6fe73679a4d45efa12eff8e202460da1d68e981502514507c0b5abae4676 - languageName: node - linkType: hard - -"@redux-devtools/instrument@npm:^2.2.0": - version: 2.2.0 - resolution: "@redux-devtools/instrument@npm:2.2.0" - dependencies: - "@babel/runtime": "npm:^7.23.2" - lodash: "npm:^4.17.21" - peerDependencies: - redux: ^3.4.0 || ^4.0.0 || ^5.0.0 - checksum: 10c0/266cd15f1ef144bbb4a2ea472c096d2a4fa72341c4c892827d633a3c85b62d1240a35aaeab71a29020f1da4da0ab2f778c176fb30c35546f6a5020a26ba1c56a - languageName: node - linkType: hard - -"@redux-devtools/remote@npm:^0.9.5": - version: 0.9.5 - resolution: "@redux-devtools/remote@npm:0.9.5" - dependencies: - "@babel/runtime": "npm:^7.26.9" - "@redux-devtools/instrument": "npm:^2.2.0" - "@redux-devtools/utils": "npm:^3.1.1" - jsan: "npm:^3.1.14" - rn-host-detect: "npm:^1.2.0" - socketcluster-client: "npm:^19.2.3" - peerDependencies: - redux: ^3.5.2 || ^4.0.0 || ^5.0.0 - checksum: 10c0/ecb13fe01008afe25d860221c505a97774e43c81f04c2e381ec9a56e2f64c6ee3eb204ad34142f7d9149db8b64be1607e824d82a79eea3e5d99fe399b9805236 - languageName: node - linkType: hard - -"@redux-devtools/serialize@npm:^0.4.2": - version: 0.4.2 - resolution: "@redux-devtools/serialize@npm:0.4.2" - dependencies: - "@babel/runtime": "npm:^7.23.2" - jsan: "npm:^3.1.14" - peerDependencies: - immutable: ^4.0.0 - checksum: 10c0/9906d5fea1214ed82cb453d9a06b76c31fb9ddeea1f5fab18220af721f82c0efce6764353865cced3c481f0e421bfdd45701f2aea19a7678db07c56a5cdd5ad8 - languageName: node - linkType: hard - -"@redux-devtools/utils@npm:^3.1.1": - version: 3.1.1 - resolution: "@redux-devtools/utils@npm:3.1.1" - dependencies: - "@babel/runtime": "npm:^7.26.9" - "@redux-devtools/core": "npm:^4.1.1" - "@redux-devtools/serialize": "npm:^0.4.2" - "@types/get-params": "npm:^0.1.2" - get-params: "npm:^0.1.2" - immutable: "npm:^4.3.7" - jsan: "npm:^3.1.14" - nanoid: "npm:^5.1.2" - redux: "npm:^5.0.1" - peerDependencies: - "@redux-devtools/core": ^4.1.1 - immutable: ^4.3.7 - redux: ^4.0.0 || ^5.0.0 - checksum: 10c0/ea03fee3e633329d0d8cb84d7fb3aa739302b5e190fed23987e0663de4654e1d26be1ea01a61082b7497b8ac11a029fc4516a9f26d7aa057b5056e53fff170a0 - languageName: node - linkType: hard - -"@reduxjs/toolkit@npm:^2.3.0": - version: 2.8.2 - resolution: "@reduxjs/toolkit@npm:2.8.2" - dependencies: - "@standard-schema/spec": "npm:^1.0.0" - "@standard-schema/utils": "npm:^0.3.0" - immer: "npm:^10.0.3" - redux: "npm:^5.0.1" - redux-thunk: "npm:^3.1.0" - reselect: "npm:^5.1.0" - peerDependencies: - react: ^16.9.0 || ^17.0.0 || ^18 || ^19 - react-redux: ^7.2.1 || ^8.1.3 || ^9.0.0 - peerDependenciesMeta: - react: - optional: true - react-redux: - optional: true - checksum: 10c0/6a7a33bad5f1100340757151ff86ca0c4c248f030ae56ce0e6f1d98b39fa87c8f193e9faa2ebd6d5a4c0416921e9f9f7a2bbdd81156c39f08f6bf5ce70c2b927 - languageName: node - linkType: hard - -"@rolldown/pluginutils@npm:1.0.0-beta.27": - version: 1.0.0-beta.27 - resolution: "@rolldown/pluginutils@npm:1.0.0-beta.27" - checksum: 10c0/9658f235b345201d4f6bfb1f32da9754ca164f892d1cb68154fe5f53c1df42bd675ecd409836dff46884a7847d6c00bdc38af870f7c81e05bba5c2645eb4ab9c - languageName: node - linkType: hard - -"@rollup/plugin-virtual@npm:^3.0.2": - version: 3.0.2 - resolution: "@rollup/plugin-virtual@npm:3.0.2" - peerDependencies: - rollup: ^1.20.0||^2.0.0||^3.0.0||^4.0.0 - peerDependenciesMeta: - rollup: - optional: true - checksum: 10c0/7115edb7989096d1ce334939fcf6e1ba365586b487bf61b2dd4f915386197f350db70904030342c0720fe58f5a52828975c645c4d415c1d432d9b1b6760a22ef - languageName: node - linkType: hard - -"@rollup/rollup-android-arm-eabi@npm:4.47.1": - version: 4.47.1 - resolution: "@rollup/rollup-android-arm-eabi@npm:4.47.1" - conditions: os=android & cpu=arm - languageName: node - linkType: hard - -"@rollup/rollup-android-arm64@npm:4.47.1": - version: 4.47.1 - resolution: "@rollup/rollup-android-arm64@npm:4.47.1" - conditions: os=android & cpu=arm64 - languageName: node - linkType: hard - -"@rollup/rollup-darwin-arm64@npm:4.47.1": - version: 4.47.1 - resolution: "@rollup/rollup-darwin-arm64@npm:4.47.1" - conditions: os=darwin & cpu=arm64 - languageName: node - linkType: hard - -"@rollup/rollup-darwin-x64@npm:4.47.1": - version: 4.47.1 - resolution: "@rollup/rollup-darwin-x64@npm:4.47.1" - conditions: os=darwin & cpu=x64 - languageName: node - linkType: hard - -"@rollup/rollup-freebsd-arm64@npm:4.47.1": - version: 4.47.1 - resolution: "@rollup/rollup-freebsd-arm64@npm:4.47.1" - conditions: os=freebsd & cpu=arm64 - languageName: node - linkType: hard - -"@rollup/rollup-freebsd-x64@npm:4.47.1": - version: 4.47.1 - resolution: "@rollup/rollup-freebsd-x64@npm:4.47.1" - conditions: os=freebsd & cpu=x64 - languageName: node - linkType: hard - -"@rollup/rollup-linux-arm-gnueabihf@npm:4.47.1": - version: 4.47.1 - resolution: "@rollup/rollup-linux-arm-gnueabihf@npm:4.47.1" - conditions: os=linux & cpu=arm & libc=glibc - languageName: node - linkType: hard - -"@rollup/rollup-linux-arm-musleabihf@npm:4.47.1": - version: 4.47.1 - resolution: "@rollup/rollup-linux-arm-musleabihf@npm:4.47.1" - conditions: os=linux & cpu=arm & libc=musl - languageName: node - linkType: hard - -"@rollup/rollup-linux-arm64-gnu@npm:4.47.1": - version: 4.47.1 - resolution: "@rollup/rollup-linux-arm64-gnu@npm:4.47.1" - conditions: os=linux & cpu=arm64 & libc=glibc - languageName: node - linkType: hard - -"@rollup/rollup-linux-arm64-musl@npm:4.47.1": - version: 4.47.1 - resolution: "@rollup/rollup-linux-arm64-musl@npm:4.47.1" - conditions: os=linux & cpu=arm64 & libc=musl - languageName: node - linkType: hard - -"@rollup/rollup-linux-loongarch64-gnu@npm:4.47.1": - version: 4.47.1 - resolution: "@rollup/rollup-linux-loongarch64-gnu@npm:4.47.1" - conditions: os=linux & cpu=loong64 & libc=glibc - languageName: node - linkType: hard - -"@rollup/rollup-linux-ppc64-gnu@npm:4.47.1": - version: 4.47.1 - resolution: "@rollup/rollup-linux-ppc64-gnu@npm:4.47.1" - conditions: os=linux & cpu=ppc64 & libc=glibc - languageName: node - linkType: hard - -"@rollup/rollup-linux-riscv64-gnu@npm:4.47.1": - version: 4.47.1 - resolution: "@rollup/rollup-linux-riscv64-gnu@npm:4.47.1" - conditions: os=linux & cpu=riscv64 & libc=glibc - languageName: node - linkType: hard - -"@rollup/rollup-linux-riscv64-musl@npm:4.47.1": - version: 4.47.1 - resolution: "@rollup/rollup-linux-riscv64-musl@npm:4.47.1" - conditions: os=linux & cpu=riscv64 & libc=musl - languageName: node - linkType: hard - -"@rollup/rollup-linux-s390x-gnu@npm:4.47.1": - version: 4.47.1 - resolution: "@rollup/rollup-linux-s390x-gnu@npm:4.47.1" - conditions: os=linux & cpu=s390x & libc=glibc - languageName: node - linkType: hard - -"@rollup/rollup-linux-x64-gnu@npm:4.47.1": - version: 4.47.1 - resolution: "@rollup/rollup-linux-x64-gnu@npm:4.47.1" - conditions: os=linux & cpu=x64 & libc=glibc - languageName: node - linkType: hard - -"@rollup/rollup-linux-x64-musl@npm:4.47.1": - version: 4.47.1 - resolution: "@rollup/rollup-linux-x64-musl@npm:4.47.1" - conditions: os=linux & cpu=x64 & libc=musl - languageName: node - linkType: hard - -"@rollup/rollup-win32-arm64-msvc@npm:4.47.1": - version: 4.47.1 - resolution: "@rollup/rollup-win32-arm64-msvc@npm:4.47.1" - conditions: os=win32 & cpu=arm64 - languageName: node - linkType: hard - -"@rollup/rollup-win32-ia32-msvc@npm:4.47.1": - version: 4.47.1 - resolution: "@rollup/rollup-win32-ia32-msvc@npm:4.47.1" - conditions: os=win32 & cpu=ia32 - languageName: node - linkType: hard - -"@rollup/rollup-win32-x64-msvc@npm:4.47.1": - version: 4.47.1 - resolution: "@rollup/rollup-win32-x64-msvc@npm:4.47.1" - conditions: os=win32 & cpu=x64 - languageName: node - linkType: hard - -"@rtsao/scc@npm:^1.1.0": - version: 1.1.0 - resolution: "@rtsao/scc@npm:1.1.0" - checksum: 10c0/b5bcfb0d87f7d1c1c7c0f7693f53b07866ed9fec4c34a97a8c948fb9a7c0082e416ce4d3b60beb4f5e167cbe04cdeefbf6771320f3ede059b9ce91188c409a5b - languageName: node - linkType: hard - -"@standard-schema/spec@npm:^1.0.0": - version: 1.0.0 - resolution: "@standard-schema/spec@npm:1.0.0" - checksum: 10c0/a1ab9a8bdc09b5b47aa8365d0e0ec40cc2df6437be02853696a0e377321653b0d3ac6f079a8c67d5ddbe9821025584b1fb71d9cc041a6666a96f1fadf2ece15f - languageName: node - linkType: hard - -"@standard-schema/utils@npm:^0.3.0": - version: 0.3.0 - resolution: "@standard-schema/utils@npm:0.3.0" - checksum: 10c0/6eb74cd13e52d5fc74054df51e37d947ef53f3ab9e02c085665dcca3c38c60ece8d735cebbdf18fbb13c775fbcb9becb3f53109b0e092a63f0f7389ce0993fd0 - languageName: node - linkType: hard - -"@swc/core-darwin-arm64@npm:1.13.4": - version: 1.13.4 - resolution: "@swc/core-darwin-arm64@npm:1.13.4" - conditions: os=darwin & cpu=arm64 - languageName: node - linkType: hard - -"@swc/core-darwin-x64@npm:1.13.4": - version: 1.13.4 - resolution: "@swc/core-darwin-x64@npm:1.13.4" - conditions: os=darwin & cpu=x64 - languageName: node - linkType: hard - -"@swc/core-linux-arm-gnueabihf@npm:1.13.4": - version: 1.13.4 - resolution: "@swc/core-linux-arm-gnueabihf@npm:1.13.4" - conditions: os=linux & cpu=arm - languageName: node - linkType: hard - -"@swc/core-linux-arm64-gnu@npm:1.13.4": - version: 1.13.4 - resolution: "@swc/core-linux-arm64-gnu@npm:1.13.4" - conditions: os=linux & cpu=arm64 & libc=glibc - languageName: node - linkType: hard - -"@swc/core-linux-arm64-musl@npm:1.13.4": - version: 1.13.4 - resolution: "@swc/core-linux-arm64-musl@npm:1.13.4" - conditions: os=linux & cpu=arm64 & libc=musl - languageName: node - linkType: hard - -"@swc/core-linux-x64-gnu@npm:1.13.4": - version: 1.13.4 - resolution: "@swc/core-linux-x64-gnu@npm:1.13.4" - conditions: os=linux & cpu=x64 & libc=glibc - languageName: node - linkType: hard - -"@swc/core-linux-x64-musl@npm:1.13.4": - version: 1.13.4 - resolution: "@swc/core-linux-x64-musl@npm:1.13.4" - conditions: os=linux & cpu=x64 & libc=musl - languageName: node - linkType: hard - -"@swc/core-win32-arm64-msvc@npm:1.13.4": - version: 1.13.4 - resolution: "@swc/core-win32-arm64-msvc@npm:1.13.4" - conditions: os=win32 & cpu=arm64 - languageName: node - linkType: hard - -"@swc/core-win32-ia32-msvc@npm:1.13.4": - version: 1.13.4 - resolution: "@swc/core-win32-ia32-msvc@npm:1.13.4" - conditions: os=win32 & cpu=ia32 - languageName: node - linkType: hard - -"@swc/core-win32-x64-msvc@npm:1.13.4": - version: 1.13.4 - resolution: "@swc/core-win32-x64-msvc@npm:1.13.4" - conditions: os=win32 & cpu=x64 - languageName: node - linkType: hard - -"@swc/core@npm:^1.12.14": - version: 1.13.4 - resolution: "@swc/core@npm:1.13.4" - dependencies: - "@swc/core-darwin-arm64": "npm:1.13.4" - "@swc/core-darwin-x64": "npm:1.13.4" - "@swc/core-linux-arm-gnueabihf": "npm:1.13.4" - "@swc/core-linux-arm64-gnu": "npm:1.13.4" - "@swc/core-linux-arm64-musl": "npm:1.13.4" - "@swc/core-linux-x64-gnu": "npm:1.13.4" - "@swc/core-linux-x64-musl": "npm:1.13.4" - "@swc/core-win32-arm64-msvc": "npm:1.13.4" - "@swc/core-win32-ia32-msvc": "npm:1.13.4" - "@swc/core-win32-x64-msvc": "npm:1.13.4" - "@swc/counter": "npm:^0.1.3" - "@swc/types": "npm:^0.1.24" - peerDependencies: - "@swc/helpers": ">=0.5.17" - dependenciesMeta: - "@swc/core-darwin-arm64": - optional: true - "@swc/core-darwin-x64": - optional: true - "@swc/core-linux-arm-gnueabihf": - optional: true - "@swc/core-linux-arm64-gnu": - optional: true - "@swc/core-linux-arm64-musl": - optional: true - "@swc/core-linux-x64-gnu": - optional: true - "@swc/core-linux-x64-musl": - optional: true - "@swc/core-win32-arm64-msvc": - optional: true - "@swc/core-win32-ia32-msvc": - optional: true - "@swc/core-win32-x64-msvc": - optional: true - peerDependenciesMeta: - "@swc/helpers": - optional: true - checksum: 10c0/4c762961085421dcb88faa187466b36e363984f40003df87a82df0194d42bd930a788a1c8e0523534f007824a2c4c37888e6bceda93c72458e6b1d0caa3dff88 - languageName: node - linkType: hard - -"@swc/counter@npm:^0.1.3": - version: 0.1.3 - resolution: "@swc/counter@npm:0.1.3" - checksum: 10c0/8424f60f6bf8694cfd2a9bca45845bce29f26105cda8cf19cdb9fd3e78dc6338699e4db77a89ae449260bafa1cc6bec307e81e7fb96dbf7dcfce0eea55151356 - languageName: node - linkType: hard - -"@swc/types@npm:^0.1.24": - version: 0.1.24 - resolution: "@swc/types@npm:0.1.24" - dependencies: - "@swc/counter": "npm:^0.1.3" - checksum: 10c0/4ca95a338f070f48303e705996bacfc1219f606c45274bed4f6e3488b86b7b20397bd52792e58fdea0fa924fc939695b5eb5ff7f3ff4737382148fe6097e235a - languageName: node - linkType: hard - -"@swc/wasm@npm:^1.12.14": - version: 1.13.4 - resolution: "@swc/wasm@npm:1.13.4" - checksum: 10c0/cdbd4b1e30d9ad979c878c71b9e6c607c876fcc6a9efa10a4af68291924b47585302bfbba5262510f38c94c478ab3f4384d0a3c96e1ce47e2b3030070b3a3890 - languageName: node - linkType: hard - -"@tauri-apps/api@npm:^2.6.0, @tauri-apps/api@npm:^2.8.0": - version: 2.8.0 - resolution: "@tauri-apps/api@npm:2.8.0" - checksum: 10c0/fb111e4d7572372997b440ebe6879543fa8c4765151878e3fddfbfe809b18da29eed142ce83061d14a9ca6d896b3266dc8a4927c642d71cdc0b4277dc7e3aabf - languageName: node - linkType: hard - -"@tauri-apps/cli-darwin-arm64@npm:2.8.1": - version: 2.8.1 - resolution: "@tauri-apps/cli-darwin-arm64@npm:2.8.1" - conditions: os=darwin & cpu=arm64 - languageName: node - linkType: hard - -"@tauri-apps/cli-darwin-x64@npm:2.8.1": - version: 2.8.1 - resolution: "@tauri-apps/cli-darwin-x64@npm:2.8.1" - conditions: os=darwin & cpu=x64 - languageName: node - linkType: hard - -"@tauri-apps/cli-linux-arm-gnueabihf@npm:2.8.1": - version: 2.8.1 - resolution: "@tauri-apps/cli-linux-arm-gnueabihf@npm:2.8.1" - conditions: os=linux & cpu=arm - languageName: node - linkType: hard - -"@tauri-apps/cli-linux-arm64-gnu@npm:2.8.1": - version: 2.8.1 - resolution: "@tauri-apps/cli-linux-arm64-gnu@npm:2.8.1" - conditions: os=linux & cpu=arm64 & libc=glibc - languageName: node - linkType: hard - -"@tauri-apps/cli-linux-arm64-musl@npm:2.8.1": - version: 2.8.1 - resolution: "@tauri-apps/cli-linux-arm64-musl@npm:2.8.1" - conditions: os=linux & cpu=arm64 & libc=musl - languageName: node - linkType: hard - -"@tauri-apps/cli-linux-riscv64-gnu@npm:2.8.1": - version: 2.8.1 - resolution: "@tauri-apps/cli-linux-riscv64-gnu@npm:2.8.1" - conditions: os=linux & cpu=riscv64 & libc=glibc - languageName: node - linkType: hard - -"@tauri-apps/cli-linux-x64-gnu@npm:2.8.1": - version: 2.8.1 - resolution: "@tauri-apps/cli-linux-x64-gnu@npm:2.8.1" - conditions: os=linux & cpu=x64 & libc=glibc - languageName: node - linkType: hard - -"@tauri-apps/cli-linux-x64-musl@npm:2.8.1": - version: 2.8.1 - resolution: "@tauri-apps/cli-linux-x64-musl@npm:2.8.1" - conditions: os=linux & cpu=x64 & libc=musl - languageName: node - linkType: hard - -"@tauri-apps/cli-win32-arm64-msvc@npm:2.8.1": - version: 2.8.1 - resolution: "@tauri-apps/cli-win32-arm64-msvc@npm:2.8.1" - conditions: os=win32 & cpu=arm64 - languageName: node - linkType: hard - -"@tauri-apps/cli-win32-ia32-msvc@npm:2.8.1": - version: 2.8.1 - resolution: "@tauri-apps/cli-win32-ia32-msvc@npm:2.8.1" - conditions: os=win32 & cpu=ia32 - languageName: node - linkType: hard - -"@tauri-apps/cli-win32-x64-msvc@npm:2.8.1": - version: 2.8.1 - resolution: "@tauri-apps/cli-win32-x64-msvc@npm:2.8.1" - conditions: os=win32 & cpu=x64 - languageName: node - linkType: hard - -"@tauri-apps/cli@npm:^2.0.0": - version: 2.8.1 - resolution: "@tauri-apps/cli@npm:2.8.1" - dependencies: - "@tauri-apps/cli-darwin-arm64": "npm:2.8.1" - "@tauri-apps/cli-darwin-x64": "npm:2.8.1" - "@tauri-apps/cli-linux-arm-gnueabihf": "npm:2.8.1" - "@tauri-apps/cli-linux-arm64-gnu": "npm:2.8.1" - "@tauri-apps/cli-linux-arm64-musl": "npm:2.8.1" - "@tauri-apps/cli-linux-riscv64-gnu": "npm:2.8.1" - "@tauri-apps/cli-linux-x64-gnu": "npm:2.8.1" - "@tauri-apps/cli-linux-x64-musl": "npm:2.8.1" - "@tauri-apps/cli-win32-arm64-msvc": "npm:2.8.1" - "@tauri-apps/cli-win32-ia32-msvc": "npm:2.8.1" - "@tauri-apps/cli-win32-x64-msvc": "npm:2.8.1" - dependenciesMeta: - "@tauri-apps/cli-darwin-arm64": - optional: true - "@tauri-apps/cli-darwin-x64": - optional: true - "@tauri-apps/cli-linux-arm-gnueabihf": - optional: true - "@tauri-apps/cli-linux-arm64-gnu": - optional: true - "@tauri-apps/cli-linux-arm64-musl": - optional: true - "@tauri-apps/cli-linux-riscv64-gnu": - optional: true - "@tauri-apps/cli-linux-x64-gnu": - optional: true - "@tauri-apps/cli-linux-x64-musl": - optional: true - "@tauri-apps/cli-win32-arm64-msvc": - optional: true - "@tauri-apps/cli-win32-ia32-msvc": - optional: true - "@tauri-apps/cli-win32-x64-msvc": - optional: true - bin: - tauri: tauri.js - checksum: 10c0/78ceed9d56d0653ab3d6924a4f54e388f42a3ac92a2004ed43f68fe3e55c5de080f35c00110aef4c622d91cff8aeeb7d4cd87b691973d948043c8d8a959893f2 - languageName: node - linkType: hard - -"@tauri-apps/plugin-cli@npm:^2.4.0": - version: 2.4.0 - resolution: "@tauri-apps/plugin-cli@npm:2.4.0" - dependencies: - "@tauri-apps/api": "npm:^2.6.0" - checksum: 10c0/0ae04cf5a987d48dbd6aa3f89dae284ec8062b5657b7d0edf529754b554841bbe7464fc574f0726b971418bff011414a687b72d32854239f36f65deadf29cfbd - languageName: node - linkType: hard - -"@tauri-apps/plugin-clipboard-manager@npm:^2.3.0": - version: 2.3.0 - resolution: "@tauri-apps/plugin-clipboard-manager@npm:2.3.0" - dependencies: - "@tauri-apps/api": "npm:^2.6.0" - checksum: 10c0/e3bacee3575779d234d4411cfeebf787c548f7f775b481540a4cb81bd3a2cedfa351a7eef799030d449c05b52469af666fa1cf10cdf165de7d788480cd3323fc - languageName: node - linkType: hard - -"@tauri-apps/plugin-dialog@npm:^2.0.0": - version: 2.3.3 - resolution: "@tauri-apps/plugin-dialog@npm:2.3.3" - dependencies: - "@tauri-apps/api": "npm:^2.8.0" - checksum: 10c0/6e8555312dc2788da353a29c9dec9274fc03e5bb233a6f1ae1eee52df4198750f7718ab7dc5036ea2289fa09175d10ecb24737c64e2184ee346f778720fee283 - languageName: node - linkType: hard - -"@tauri-apps/plugin-opener@npm:^2.5.0": - version: 2.5.0 - resolution: "@tauri-apps/plugin-opener@npm:2.5.0" - dependencies: - "@tauri-apps/api": "npm:^2.8.0" - checksum: 10c0/31503644c64678e5f5d1d995c0db145a3dabeb1bef1b9f7cfef84a83e4188c169f630207705fc6484f9e1934be6bd59de50676e4b9f1e2f628d1b54e38f00dbd - languageName: node - linkType: hard - -"@tauri-apps/plugin-process@npm:^2.3.0": - version: 2.3.0 - resolution: "@tauri-apps/plugin-process@npm:2.3.0" - dependencies: - "@tauri-apps/api": "npm:^2.6.0" - checksum: 10c0/ef50344a7436d92278c2ef4526f72daaf3171c4d65743bbc1f7a00fa581644a8583bb8680f637a34af5c7e6a0e8722c22189290e903584fef70ed83b64b6e9c0 - languageName: node - linkType: hard - -"@tauri-apps/plugin-store@npm:^2.4.0": - version: 2.4.0 - resolution: "@tauri-apps/plugin-store@npm:2.4.0" - dependencies: - "@tauri-apps/api": "npm:^2.8.0" - checksum: 10c0/2896fe1e8d9f23015f4a586efd3dd100e8dc96a9d353dd396d7b274ec3fba45bcb4ba7f63915379c18a5a1e109301dbeb3950eee97bb542677f407c6e5229212 - languageName: node - linkType: hard - -"@tauri-apps/plugin-updater@npm:^2.9.0": - version: 2.9.0 - resolution: "@tauri-apps/plugin-updater@npm:2.9.0" - dependencies: - "@tauri-apps/api": "npm:^2.6.0" - checksum: 10c0/72ce83d1c241308a13b9929f0900e4d33453875877009166e3998e3e75a1003ac48c3641086b4d3230f0f18c64f475ad6c3556d1603fc641ca50dc9c18d61866 - languageName: node - linkType: hard - -"@testing-library/react@npm:^16.0.1": - version: 16.3.0 - resolution: "@testing-library/react@npm:16.3.0" - dependencies: - "@babel/runtime": "npm:^7.12.5" - peerDependencies: - "@testing-library/dom": ^10.0.0 - "@types/react": ^18.0.0 || ^19.0.0 - "@types/react-dom": ^18.0.0 || ^19.0.0 - react: ^18.0.0 || ^19.0.0 - react-dom: ^18.0.0 || ^19.0.0 - peerDependenciesMeta: - "@types/react": - optional: true - "@types/react-dom": - optional: true - checksum: 10c0/3a2cb1f87c9a67e1ebbbcfd99b94b01e496fc35147be8bc5d8bf07a699c7d523a09d57ef2f7b1d91afccd1a28e21eda3b00d80187fbb51b1de01e422592d845e - languageName: node - linkType: hard - -"@testing-library/user-event@npm:^14.5.2": - version: 14.6.1 - resolution: "@testing-library/user-event@npm:14.6.1" - peerDependencies: - "@testing-library/dom": ">=7.21.4" - checksum: 10c0/75fea130a52bf320d35d46ed54f3eec77e71a56911b8b69a3fe29497b0b9947b2dc80d30f04054ad4ce7f577856ae3e5397ea7dff0ef14944d3909784c7a93fe - languageName: node - linkType: hard - -"@types/babel__core@npm:^7.20.5": - version: 7.20.5 - resolution: "@types/babel__core@npm:7.20.5" - dependencies: - "@babel/parser": "npm:^7.20.7" - "@babel/types": "npm:^7.20.7" - "@types/babel__generator": "npm:*" - "@types/babel__template": "npm:*" - "@types/babel__traverse": "npm:*" - checksum: 10c0/bdee3bb69951e833a4b811b8ee9356b69a61ed5b7a23e1a081ec9249769117fa83aaaf023bb06562a038eb5845155ff663e2d5c75dd95c1d5ccc91db012868ff - languageName: node - linkType: hard - -"@types/babel__generator@npm:*": - version: 7.27.0 - resolution: "@types/babel__generator@npm:7.27.0" - dependencies: - "@babel/types": "npm:^7.0.0" - checksum: 10c0/9f9e959a8792df208a9d048092fda7e1858bddc95c6314857a8211a99e20e6830bdeb572e3587ae8be5429e37f2a96fcf222a9f53ad232f5537764c9e13a2bbd - languageName: node - linkType: hard - -"@types/babel__template@npm:*": - version: 7.4.4 - resolution: "@types/babel__template@npm:7.4.4" - dependencies: - "@babel/parser": "npm:^7.1.0" - "@babel/types": "npm:^7.0.0" - checksum: 10c0/cc84f6c6ab1eab1427e90dd2b76ccee65ce940b778a9a67be2c8c39e1994e6f5bbc8efa309f6cea8dc6754994524cd4d2896558df76d92e7a1f46ecffee7112b - languageName: node - linkType: hard - -"@types/babel__traverse@npm:*": - version: 7.28.0 - resolution: "@types/babel__traverse@npm:7.28.0" - dependencies: - "@babel/types": "npm:^7.28.2" - checksum: 10c0/b52d7d4e8fc6a9018fe7361c4062c1c190f5778cf2466817cb9ed19d69fbbb54f9a85ffedeb748ed8062d2cf7d4cc088ee739848f47c57740de1c48cbf0d0994 - languageName: node - linkType: hard - -"@types/estree@npm:1.0.8, @types/estree@npm:^1.0.0, @types/estree@npm:^1.0.6": - version: 1.0.8 - resolution: "@types/estree@npm:1.0.8" - checksum: 10c0/39d34d1afaa338ab9763f37ad6066e3f349444f9052b9676a7cc0252ef9485a41c6d81c9c4e0d26e9077993354edf25efc853f3224dd4b447175ef62bdcc86a5 - languageName: node - linkType: hard - -"@types/get-params@npm:^0.1.2": - version: 0.1.2 - resolution: "@types/get-params@npm:0.1.2" - checksum: 10c0/7138af23d6acf0737d462263b8d2e5decfd282f1164aa7e2783c2fc9aeb2ec6aa10f6288605740356a916ecc4d27e01c1d8e6ad4c4fd0cd92790b8f482063cf6 - languageName: node - linkType: hard - -"@types/hoist-non-react-statics@npm:^3.3.0": - version: 3.3.7 - resolution: "@types/hoist-non-react-statics@npm:3.3.7" - dependencies: - hoist-non-react-statics: "npm:^3.3.0" - peerDependencies: - "@types/react": "*" - checksum: 10c0/ed8f4e88338f7d021d0f956adf6089d2a12b2e254a03c05292324f2e986d2376eb9efdb8a4f04596823e8fca88c9d06361d20dab4a2a00dc935fb36ac911de55 - languageName: node - linkType: hard - -"@types/humanize-duration@npm:^3.27.4": - version: 3.27.4 - resolution: "@types/humanize-duration@npm:3.27.4" - checksum: 10c0/0d92b036ac284c4ae248dca6efa826f9d397e7369f2d5e11de1813e05b32fd44e3d696f999acdc5a7eb412efc33668b8a68f6ad671392c0bbcf797a6329d674f - languageName: node - linkType: hard - -"@types/json-schema@npm:^7.0.15": - version: 7.0.15 - resolution: "@types/json-schema@npm:7.0.15" - checksum: 10c0/a996a745e6c5d60292f36731dd41341339d4eeed8180bb09226e5c8d23759067692b1d88e5d91d72ee83dfc00d3aca8e7bd43ea120516c17922cbcb7c3e252db - languageName: node - linkType: hard - -"@types/json5@npm:^0.0.29": - version: 0.0.29 - resolution: "@types/json5@npm:0.0.29" - checksum: 10c0/6bf5337bc447b706bb5b4431d37686aa2ea6d07cfd6f79cc31de80170d6ff9b1c7384a9c0ccbc45b3f512bae9e9f75c2e12109806a15331dc94e8a8db6dbb4ac - languageName: node - linkType: hard - -"@types/lodash@npm:^4.17.6": - version: 4.17.20 - resolution: "@types/lodash@npm:4.17.20" - checksum: 10c0/98cdd0faae22cbb8079a01a3bb65aa8f8c41143367486c1cbf5adc83f16c9272a2a5d2c1f541f61d0d73da543c16ee1d21cf2ef86cb93cd0cc0ac3bced6dd88f - languageName: node - linkType: hard - -"@types/node@npm:*": - version: 24.10.1 - resolution: "@types/node@npm:24.10.1" - dependencies: - undici-types: "npm:~7.16.0" - checksum: 10c0/d6bca7a78f550fbb376f236f92b405d676003a8a09a1b411f55920ef34286ee3ee51f566203920e835478784df52662b5b2af89159d9d319352e9ea21801c002 - languageName: node - linkType: hard - -"@types/node@npm:^22.15.29": - version: 22.17.2 - resolution: "@types/node@npm:22.17.2" - dependencies: - undici-types: "npm:~6.21.0" - checksum: 10c0/23cd13aa35da6322a6d66cf4b3a45dbd40764ba726ab8681960270156c3abba776dd8dc173250c467f708d40612ecd725755d7659b775b513904680d5205eaff - languageName: node - linkType: hard - -"@types/parse-json@npm:^4.0.0": - version: 4.0.2 - resolution: "@types/parse-json@npm:4.0.2" - checksum: 10c0/b1b863ac34a2c2172fbe0807a1ec4d5cb684e48d422d15ec95980b81475fac4fdb3768a8b13eef39130203a7c04340fc167bae057c7ebcafd7dec9fe6c36aeb1 - languageName: node - linkType: hard - -"@types/prop-types@npm:^15.7.15": - version: 15.7.15 - resolution: "@types/prop-types@npm:15.7.15" - checksum: 10c0/b59aad1ad19bf1733cf524fd4e618196c6c7690f48ee70a327eb450a42aab8e8a063fbe59ca0a5701aebe2d92d582292c0fb845ea57474f6a15f6994b0e260b2 - languageName: node - linkType: hard - -"@types/react-dom@npm:^19.1.5": - version: 19.1.7 - resolution: "@types/react-dom@npm:19.1.7" - peerDependencies: - "@types/react": ^19.0.0 - checksum: 10c0/8db5751c1567552fe4e1ece9f5823b682f2994ec8d30ed34ba0ef984e3c8ace1435f8be93d02f55c350147e78ac8c4dbcd8ed2c3b6a60f575bc5374f588c51c9 - languageName: node - linkType: hard - -"@types/react-is@npm:^19.0.0": - version: 19.0.0 - resolution: "@types/react-is@npm:19.0.0" - dependencies: - "@types/react": "npm:*" - checksum: 10c0/d5fa0294f95cf301540f3e28fb44c00524c74a7de3ef9757703d76067206f3ecc002da6486e1581f23b54173b6449db8631acc3088009116e4569ff216df3ecc - languageName: node - linkType: hard - -"@types/react-redux@npm:^7.1.34": - version: 7.1.34 - resolution: "@types/react-redux@npm:7.1.34" - dependencies: - "@types/hoist-non-react-statics": "npm:^3.3.0" - "@types/react": "npm:*" - hoist-non-react-statics: "npm:^3.3.0" - redux: "npm:^4.0.0" - checksum: 10c0/6750964ec656eb6973b0e4fda787549aee5dbc266a0f0e78fc9efb417b4965c0b060d10b99a7b7fa0c8812b8a0a07d97a1ef46d094bf64fee07144e8bbad781a - languageName: node - linkType: hard - -"@types/react-transition-group@npm:^4.4.12": - version: 4.4.12 - resolution: "@types/react-transition-group@npm:4.4.12" - peerDependencies: - "@types/react": "*" - checksum: 10c0/0441b8b47c69312c89ec0760ba477ba1a0808a10ceef8dc1c64b1013ed78517332c30f18681b0ec0b53542731f1ed015169fed1d127cc91222638ed955478ec7 - languageName: node - linkType: hard - -"@types/react@npm:*, @types/react@npm:^19.1.6": - version: 19.1.10 - resolution: "@types/react@npm:19.1.10" - dependencies: - csstype: "npm:^3.0.2" - checksum: 10c0/fb583deacd0a815e2775dc1b9f764532d8cacb748ddd2c2914805a46c257ce6c237b4078f44009692074db212ab61a390301c6470f07f5aa5bfdeb78a2acfda1 - languageName: node - linkType: hard - -"@types/semver@npm:^7.5.8": - version: 7.7.0 - resolution: "@types/semver@npm:7.7.0" - checksum: 10c0/6b5f65f647474338abbd6ee91a6bbab434662ddb8fe39464edcbcfc96484d388baad9eb506dff217b6fc1727a88894930eb1f308617161ac0f376fe06be4e1ee - languageName: node - linkType: hard - -"@types/use-sync-external-store@npm:^0.0.6": - version: 0.0.6 - resolution: "@types/use-sync-external-store@npm:0.0.6" - checksum: 10c0/77c045a98f57488201f678b181cccd042279aff3da34540ad242f893acc52b358bd0a8207a321b8ac09adbcef36e3236944390e2df4fcedb556ce7bb2a88f2a8 - languageName: node - linkType: hard - -"@typescript-eslint/eslint-plugin@npm:8.40.0": - version: 8.40.0 - resolution: "@typescript-eslint/eslint-plugin@npm:8.40.0" - dependencies: - "@eslint-community/regexpp": "npm:^4.10.0" - "@typescript-eslint/scope-manager": "npm:8.40.0" - "@typescript-eslint/type-utils": "npm:8.40.0" - "@typescript-eslint/utils": "npm:8.40.0" - "@typescript-eslint/visitor-keys": "npm:8.40.0" - graphemer: "npm:^1.4.0" - ignore: "npm:^7.0.0" - natural-compare: "npm:^1.4.0" - ts-api-utils: "npm:^2.1.0" - peerDependencies: - "@typescript-eslint/parser": ^8.40.0 - eslint: ^8.57.0 || ^9.0.0 - typescript: ">=4.8.4 <6.0.0" - checksum: 10c0/dc8889c3255bce6956432f099059179dd13826ba29670f81ba9238ecde46764ee63459eb73a7d88f4f30e1144a2f000d79c9e3f256fa759689d9b3b74d423bda - languageName: node - linkType: hard - -"@typescript-eslint/parser@npm:8.40.0": - version: 8.40.0 - resolution: "@typescript-eslint/parser@npm:8.40.0" - dependencies: - "@typescript-eslint/scope-manager": "npm:8.40.0" - "@typescript-eslint/types": "npm:8.40.0" - "@typescript-eslint/typescript-estree": "npm:8.40.0" - "@typescript-eslint/visitor-keys": "npm:8.40.0" - debug: "npm:^4.3.4" - peerDependencies: - eslint: ^8.57.0 || ^9.0.0 - typescript: ">=4.8.4 <6.0.0" - checksum: 10c0/43ca9589b8a1f3f4b30a214c0e2254fa0ad43458ef1258b1d62c5aad52710ad11b9315b124cda79163274147b82201a5d76fab7de413e34bfe8e377142b71e98 - languageName: node - linkType: hard - -"@typescript-eslint/project-service@npm:8.40.0": - version: 8.40.0 - resolution: "@typescript-eslint/project-service@npm:8.40.0" - dependencies: - "@typescript-eslint/tsconfig-utils": "npm:^8.40.0" - "@typescript-eslint/types": "npm:^8.40.0" - debug: "npm:^4.3.4" - peerDependencies: - typescript: ">=4.8.4 <6.0.0" - checksum: 10c0/23d62e9ada9750136d0251f268bbe1f9784442ef258bb340a2e1e866749d8076730a14749d9a320d94d7c76df2d108caf21fe35e5dc100385f04be846dc979cb - languageName: node - linkType: hard - -"@typescript-eslint/scope-manager@npm:8.40.0": - version: 8.40.0 - resolution: "@typescript-eslint/scope-manager@npm:8.40.0" - dependencies: - "@typescript-eslint/types": "npm:8.40.0" - "@typescript-eslint/visitor-keys": "npm:8.40.0" - checksum: 10c0/48af81f9cdcec466994d290561e8d2fa3f6b156a898b71dd0e65633c896543b44729c5353596e84de2ae61bfd20e1398c3309cdfe86714a9663fd5aded4c9cd0 - languageName: node - linkType: hard - -"@typescript-eslint/tsconfig-utils@npm:8.40.0, @typescript-eslint/tsconfig-utils@npm:^8.40.0": - version: 8.40.0 - resolution: "@typescript-eslint/tsconfig-utils@npm:8.40.0" - peerDependencies: - typescript: ">=4.8.4 <6.0.0" - checksum: 10c0/c2366dcd802901d5cd4f59fc4eab7a00ed119aa4591ba59c507fe495d9af4cfca19431a603602ea675e4c861962230d1c2f100896903750cd1fcfc134702a7d0 - languageName: node - linkType: hard - -"@typescript-eslint/type-utils@npm:8.40.0": - version: 8.40.0 - resolution: "@typescript-eslint/type-utils@npm:8.40.0" - dependencies: - "@typescript-eslint/types": "npm:8.40.0" - "@typescript-eslint/typescript-estree": "npm:8.40.0" - "@typescript-eslint/utils": "npm:8.40.0" - debug: "npm:^4.3.4" - ts-api-utils: "npm:^2.1.0" - peerDependencies: - eslint: ^8.57.0 || ^9.0.0 - typescript: ">=4.8.4 <6.0.0" - checksum: 10c0/660b77d801b2538a4ccb65065269ad0e8370d0be985172b5ecb067f3eea22e64aa8af9e981b31bf2a34002339fe3253b09b55d181ce6d8242fc7daa80ac4aaca - languageName: node - linkType: hard - -"@typescript-eslint/types@npm:8.40.0, @typescript-eslint/types@npm:^8.40.0": - version: 8.40.0 - resolution: "@typescript-eslint/types@npm:8.40.0" - checksum: 10c0/225374fff36d59288a5780667a7a1316c75090d5d60b70a8035ac18786120333ccd08dfdf0e05e30d5a82217e44c57b8708b769dd1eed89f12f2ac4d3a769f76 - languageName: node - linkType: hard - -"@typescript-eslint/typescript-estree@npm:8.40.0": - version: 8.40.0 - resolution: "@typescript-eslint/typescript-estree@npm:8.40.0" - dependencies: - "@typescript-eslint/project-service": "npm:8.40.0" - "@typescript-eslint/tsconfig-utils": "npm:8.40.0" - "@typescript-eslint/types": "npm:8.40.0" - "@typescript-eslint/visitor-keys": "npm:8.40.0" - debug: "npm:^4.3.4" - fast-glob: "npm:^3.3.2" - is-glob: "npm:^4.0.3" - minimatch: "npm:^9.0.4" - semver: "npm:^7.6.0" - ts-api-utils: "npm:^2.1.0" - peerDependencies: - typescript: ">=4.8.4 <6.0.0" - checksum: 10c0/6c1ffc17947cb36cbd987cf9705f85223ed1cce584b5244840e36a2b8480861f4dfdb0312f96afbc12e7d1ba586005f0d959042baa0a96a1913ac7ace8e8f6d4 - languageName: node - linkType: hard - -"@typescript-eslint/utils@npm:8.40.0": - version: 8.40.0 - resolution: "@typescript-eslint/utils@npm:8.40.0" - dependencies: - "@eslint-community/eslint-utils": "npm:^4.7.0" - "@typescript-eslint/scope-manager": "npm:8.40.0" - "@typescript-eslint/types": "npm:8.40.0" - "@typescript-eslint/typescript-estree": "npm:8.40.0" - peerDependencies: - eslint: ^8.57.0 || ^9.0.0 - typescript: ">=4.8.4 <6.0.0" - checksum: 10c0/6b3858b8725083fe7db7fb9bcbde930e758a6ba8ddedd1ed27d828fc1cbe04f54b774ef9144602f8eeaafeea9b19b4fd4c46fdad52a10ade99e6b282c7d0df92 - languageName: node - linkType: hard - -"@typescript-eslint/visitor-keys@npm:8.40.0": - version: 8.40.0 - resolution: "@typescript-eslint/visitor-keys@npm:8.40.0" - dependencies: - "@typescript-eslint/types": "npm:8.40.0" - eslint-visitor-keys: "npm:^4.2.1" - checksum: 10c0/592f1c8c2d3da43a7f74f8ead14f05fafc2e4609d5df36811cf92ead5dc94f6f669556a494048e4746cb3774c60bc52a8c83d75369d5e196778d935c70e7d3a1 - languageName: node - linkType: hard - -"@vitejs/plugin-react@npm:^4.2.1": - version: 4.7.0 - resolution: "@vitejs/plugin-react@npm:4.7.0" - dependencies: - "@babel/core": "npm:^7.28.0" - "@babel/plugin-transform-react-jsx-self": "npm:^7.27.1" - "@babel/plugin-transform-react-jsx-source": "npm:^7.27.1" - "@rolldown/pluginutils": "npm:1.0.0-beta.27" - "@types/babel__core": "npm:^7.20.5" - react-refresh: "npm:^0.17.0" - peerDependencies: - vite: ^4.2.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 - checksum: 10c0/692f23960972879485d647713663ec299c478222c96567d60285acf7c7dc5c178e71abfe9d2eefddef1eeb01514dacbc2ed68aad84628debf9c7116134734253 - languageName: node - linkType: hard - -"@vitest/expect@npm:2.1.9": - version: 2.1.9 - resolution: "@vitest/expect@npm:2.1.9" - dependencies: - "@vitest/spy": "npm:2.1.9" - "@vitest/utils": "npm:2.1.9" - chai: "npm:^5.1.2" - tinyrainbow: "npm:^1.2.0" - checksum: 10c0/98d1cf02917316bebef9e4720723e38298a1c12b3c8f3a81f259bb822de4288edf594e69ff64f0b88afbda6d04d7a4f0c2f720f3fec16b4c45f5e2669f09fdbb - languageName: node - linkType: hard - -"@vitest/mocker@npm:2.1.9": - version: 2.1.9 - resolution: "@vitest/mocker@npm:2.1.9" - dependencies: - "@vitest/spy": "npm:2.1.9" - estree-walker: "npm:^3.0.3" - magic-string: "npm:^0.30.12" - peerDependencies: - msw: ^2.4.9 - vite: ^5.0.0 - peerDependenciesMeta: - msw: - optional: true - vite: - optional: true - checksum: 10c0/f734490d8d1206a7f44dfdfca459282f5921d73efa72935bb1dc45307578defd38a4131b14853316373ec364cbe910dbc74594ed4137e0da35aa4d9bb716f190 - languageName: node - linkType: hard - -"@vitest/pretty-format@npm:2.1.9, @vitest/pretty-format@npm:^2.1.9": - version: 2.1.9 - resolution: "@vitest/pretty-format@npm:2.1.9" - dependencies: - tinyrainbow: "npm:^1.2.0" - checksum: 10c0/155f9ede5090eabed2a73361094bb35ed4ec6769ae3546d2a2af139166569aec41bb80e031c25ff2da22b71dd4ed51e5468e66a05e6aeda5f14b32e30bc18f00 - languageName: node - linkType: hard - -"@vitest/runner@npm:2.1.9": - version: 2.1.9 - resolution: "@vitest/runner@npm:2.1.9" - dependencies: - "@vitest/utils": "npm:2.1.9" - pathe: "npm:^1.1.2" - checksum: 10c0/e81f176badb12a815cbbd9bd97e19f7437a0b64e8934d680024b0f768d8670d59cad698ef0e3dada5241b6731d77a7bb3cd2c7cb29f751fd4dd35eb11c42963a - languageName: node - linkType: hard - -"@vitest/snapshot@npm:2.1.9": - version: 2.1.9 - resolution: "@vitest/snapshot@npm:2.1.9" - dependencies: - "@vitest/pretty-format": "npm:2.1.9" - magic-string: "npm:^0.30.12" - pathe: "npm:^1.1.2" - checksum: 10c0/394974b3a1fe96186a3c87f933b2f7f1f7b7cc42f9c781d80271dbb4c987809bf035fecd7398b8a3a2d54169e3ecb49655e38a0131d0e7fea5ce88960613b526 - languageName: node - linkType: hard - -"@vitest/spy@npm:2.1.9": - version: 2.1.9 - resolution: "@vitest/spy@npm:2.1.9" - dependencies: - tinyspy: "npm:^3.0.2" - checksum: 10c0/12a59b5095e20188b819a1d797e0a513d991b4e6a57db679927c43b362a3eff52d823b34e855a6dd9e73c9fa138dcc5ef52210841a93db5cbf047957a60ca83c - languageName: node - linkType: hard - -"@vitest/utils@npm:2.1.9": - version: 2.1.9 - resolution: "@vitest/utils@npm:2.1.9" - dependencies: - "@vitest/pretty-format": "npm:2.1.9" - loupe: "npm:^3.1.2" - tinyrainbow: "npm:^1.2.0" - checksum: 10c0/81a346cd72b47941f55411f5df4cc230e5f740d1e97e0d3f771b27f007266fc1f28d0438582f6409ea571bc0030ed37f684c64c58d1947d6298d770c21026fdf - languageName: node - linkType: hard - -"abbrev@npm:^4.0.0": - version: 4.0.0 - resolution: "abbrev@npm:4.0.0" - checksum: 10c0/b4cc16935235e80702fc90192e349e32f8ef0ed151ef506aa78c81a7c455ec18375c4125414b99f84b2e055199d66383e787675f0bcd87da7a4dbd59f9eac1d5 - languageName: node - linkType: hard - -"acorn-jsx@npm:^5.3.2": - version: 5.3.2 - resolution: "acorn-jsx@npm:5.3.2" - peerDependencies: - acorn: ^6.0.0 || ^7.0.0 || ^8.0.0 - checksum: 10c0/4c54868fbef3b8d58927d5e33f0a4de35f59012fe7b12cf9dfbb345fb8f46607709e1c4431be869a23fb63c151033d84c4198fa9f79385cec34fcb1dd53974c1 - languageName: node - linkType: hard - -"acorn@npm:^8.15.0": - version: 8.15.0 - resolution: "acorn@npm:8.15.0" - bin: - acorn: bin/acorn - checksum: 10c0/dec73ff59b7d6628a01eebaece7f2bdb8bb62b9b5926dcad0f8931f2b8b79c2be21f6c68ac095592adb5adb15831a3635d9343e6a91d028bbe85d564875ec3ec - languageName: node - linkType: hard - -"ag-auth@npm:^2.1.0": - version: 2.1.0 - resolution: "ag-auth@npm:2.1.0" - dependencies: - jsonwebtoken: "npm:^9.0.0" - sc-errors: "npm:^3.0.0" - checksum: 10c0/10f144fb4fbc70379a196d43e2a1d3da8e77d188321ca48795371ac55f1b9ae8a6492dad80bfc596bdd7d534e292d7d6f3dd2557484e37ea384635aabe0aee36 - languageName: node - linkType: hard - -"ag-channel@npm:^5.0.0": - version: 5.0.0 - resolution: "ag-channel@npm:5.0.0" - dependencies: - consumable-stream: "npm:^2.0.0" - checksum: 10c0/54dcac553a21f28687c99beef8287c3c6bdebd23585fa9e1a54205eb46e8bf5bcd204c78ca5753ab0d5b01330576b49462cec6e8fdcfcf0f1478162439536ec4 - languageName: node - linkType: hard - -"ag-request@npm:^1.1.0": - version: 1.1.0 - resolution: "ag-request@npm:1.1.0" - dependencies: - sc-errors: "npm:^3.0.0" - checksum: 10c0/7a7a53252e66cab1a0e4ea422df83214ed9f277e895692c543aa2499184bc8812a65e3c8b4941c78ff949ecdff0fb8e8a12879e14c20e12057187cf801de2471 - languageName: node - linkType: hard - -"agent-base@npm:^7.1.0, agent-base@npm:^7.1.2": - version: 7.1.4 - resolution: "agent-base@npm:7.1.4" - checksum: 10c0/c2c9ab7599692d594b6a161559ada307b7a624fa4c7b03e3afdb5a5e31cd0e53269115b620fcab024c5ac6a6f37fa5eb2e004f076ad30f5f7e6b8b671f7b35fe - languageName: node - linkType: hard - -"ajv@npm:^6.12.4": - version: 6.12.6 - resolution: "ajv@npm:6.12.6" - dependencies: - fast-deep-equal: "npm:^3.1.1" - fast-json-stable-stringify: "npm:^2.0.0" - json-schema-traverse: "npm:^0.4.1" - uri-js: "npm:^4.2.2" - checksum: 10c0/41e23642cbe545889245b9d2a45854ebba51cda6c778ebced9649420d9205f2efb39cb43dbc41e358409223b1ea43303ae4839db682c848b891e4811da1a5a71 - languageName: node - linkType: hard - -"ansi-styles@npm:^4.1.0": - version: 4.3.0 - resolution: "ansi-styles@npm:4.3.0" - dependencies: - color-convert: "npm:^2.0.1" - checksum: 10c0/895a23929da416f2bd3de7e9cb4eabd340949328ab85ddd6e484a637d8f6820d485f53933446f5291c3b760cbc488beb8e88573dd0f9c7daf83dccc8fe81b041 - languageName: node - linkType: hard - -"argparse@npm:^2.0.1": - version: 2.0.1 - resolution: "argparse@npm:2.0.1" - checksum: 10c0/c5640c2d89045371c7cedd6a70212a04e360fd34d6edeae32f6952c63949e3525ea77dbec0289d8213a99bbaeab5abfa860b5c12cf88a2e6cf8106e90dd27a7e - languageName: node - linkType: hard - -"array-buffer-byte-length@npm:^1.0.1, array-buffer-byte-length@npm:^1.0.2": - version: 1.0.2 - resolution: "array-buffer-byte-length@npm:1.0.2" - dependencies: - call-bound: "npm:^1.0.3" - is-array-buffer: "npm:^3.0.5" - checksum: 10c0/74e1d2d996941c7a1badda9cabb7caab8c449db9086407cad8a1b71d2604cc8abf105db8ca4e02c04579ec58b7be40279ddb09aea4784832984485499f48432d - languageName: node - linkType: hard - -"array-includes@npm:^3.1.6, array-includes@npm:^3.1.8, array-includes@npm:^3.1.9": - version: 3.1.9 - resolution: "array-includes@npm:3.1.9" - dependencies: - call-bind: "npm:^1.0.8" - call-bound: "npm:^1.0.4" - define-properties: "npm:^1.2.1" - es-abstract: "npm:^1.24.0" - es-object-atoms: "npm:^1.1.1" - get-intrinsic: "npm:^1.3.0" - is-string: "npm:^1.1.1" - math-intrinsics: "npm:^1.1.0" - checksum: 10c0/0235fa69078abeac05ac4250699c44996bc6f774a9cbe45db48674ce6bd142f09b327d31482ff75cf03344db4ea03eae23edb862d59378b484b47ed842574856 - languageName: node - linkType: hard - -"array.prototype.findlast@npm:^1.2.5": - version: 1.2.5 - resolution: "array.prototype.findlast@npm:1.2.5" - dependencies: - call-bind: "npm:^1.0.7" - define-properties: "npm:^1.2.1" - es-abstract: "npm:^1.23.2" - es-errors: "npm:^1.3.0" - es-object-atoms: "npm:^1.0.0" - es-shim-unscopables: "npm:^1.0.2" - checksum: 10c0/ddc952b829145ab45411b9d6adcb51a8c17c76bf89c9dd64b52d5dffa65d033da8c076ed2e17091779e83bc892b9848188d7b4b33453c5565e65a92863cb2775 - languageName: node - linkType: hard - -"array.prototype.findlastindex@npm:^1.2.6": - version: 1.2.6 - resolution: "array.prototype.findlastindex@npm:1.2.6" - dependencies: - call-bind: "npm:^1.0.8" - call-bound: "npm:^1.0.4" - define-properties: "npm:^1.2.1" - es-abstract: "npm:^1.23.9" - es-errors: "npm:^1.3.0" - es-object-atoms: "npm:^1.1.1" - es-shim-unscopables: "npm:^1.1.0" - checksum: 10c0/82559310d2e57ec5f8fc53d7df420e3abf0ba497935de0a5570586035478ba7d07618cb18e2d4ada2da514c8fb98a034aaf5c06caa0a57e2f7f4c4adedef5956 - languageName: node - linkType: hard - -"array.prototype.flat@npm:^1.3.1, array.prototype.flat@npm:^1.3.3": - version: 1.3.3 - resolution: "array.prototype.flat@npm:1.3.3" - dependencies: - call-bind: "npm:^1.0.8" - define-properties: "npm:^1.2.1" - es-abstract: "npm:^1.23.5" - es-shim-unscopables: "npm:^1.0.2" - checksum: 10c0/d90e04dfbc43bb96b3d2248576753d1fb2298d2d972e29ca7ad5ec621f0d9e16ff8074dae647eac4f31f4fb7d3f561a7ac005fb01a71f51705a13b5af06a7d8a - languageName: node - linkType: hard - -"array.prototype.flatmap@npm:^1.3.3": - version: 1.3.3 - resolution: "array.prototype.flatmap@npm:1.3.3" - dependencies: - call-bind: "npm:^1.0.8" - define-properties: "npm:^1.2.1" - es-abstract: "npm:^1.23.5" - es-shim-unscopables: "npm:^1.0.2" - checksum: 10c0/ba899ea22b9dc9bf276e773e98ac84638ed5e0236de06f13d63a90b18ca9e0ec7c97d622d899796e3773930b946cd2413d098656c0c5d8cc58c6f25c21e6bd54 - languageName: node - linkType: hard - -"array.prototype.tosorted@npm:^1.1.4": - version: 1.1.4 - resolution: "array.prototype.tosorted@npm:1.1.4" - dependencies: - call-bind: "npm:^1.0.7" - define-properties: "npm:^1.2.1" - es-abstract: "npm:^1.23.3" - es-errors: "npm:^1.3.0" - es-shim-unscopables: "npm:^1.0.2" - checksum: 10c0/eb3c4c4fc0381b0bf6dba2ea4d48d367c2827a0d4236a5718d97caaccc6b78f11f4cadf090736e86301d295a6aa4967ed45568f92ced51be8cbbacd9ca410943 - languageName: node - linkType: hard - -"arraybuffer.prototype.slice@npm:^1.0.4": - version: 1.0.4 - resolution: "arraybuffer.prototype.slice@npm:1.0.4" - dependencies: - array-buffer-byte-length: "npm:^1.0.1" - call-bind: "npm:^1.0.8" - define-properties: "npm:^1.2.1" - es-abstract: "npm:^1.23.5" - es-errors: "npm:^1.3.0" - get-intrinsic: "npm:^1.2.6" - is-array-buffer: "npm:^3.0.4" - checksum: 10c0/2f2459caa06ae0f7f615003f9104b01f6435cc803e11bd2a655107d52a1781dc040532dc44d93026b694cc18793993246237423e13a5337e86b43ed604932c06 - languageName: node - linkType: hard - -"assertion-error@npm:^2.0.1": - version: 2.0.1 - resolution: "assertion-error@npm:2.0.1" - checksum: 10c0/bbbcb117ac6480138f8c93cf7f535614282dea9dc828f540cdece85e3c665e8f78958b96afac52f29ff883c72638e6a87d469ecc9fe5bc902df03ed24a55dba8 - languageName: node - linkType: hard - -"async-function@npm:^1.0.0": - version: 1.0.0 - resolution: "async-function@npm:1.0.0" - checksum: 10c0/669a32c2cb7e45091330c680e92eaeb791bc1d4132d827591e499cd1f776ff5a873e77e5f92d0ce795a8d60f10761dec9ddfe7225a5de680f5d357f67b1aac73 - languageName: node - linkType: hard - -"async-stream-emitter@npm:^7.0.1": - version: 7.0.1 - resolution: "async-stream-emitter@npm:7.0.1" - dependencies: - stream-demux: "npm:^10.0.1" - checksum: 10c0/0e8d1daaca584f741dd10375748fa11bb7a52282d88c5b4ddcefeba776edf60dc3d4aa4820cdc0d162519b0838085ec250d427dd3e3b7f0926a91d95da320473 - languageName: node - linkType: hard - -"available-typed-arrays@npm:^1.0.7": - version: 1.0.7 - resolution: "available-typed-arrays@npm:1.0.7" - dependencies: - possible-typed-array-names: "npm:^1.0.0" - checksum: 10c0/d07226ef4f87daa01bd0fe80f8f310982e345f372926da2e5296aecc25c41cab440916bbaa4c5e1034b453af3392f67df5961124e4b586df1e99793a1374bdb2 - languageName: node - linkType: hard - -"babel-plugin-macros@npm:^3.1.0": - version: 3.1.0 - resolution: "babel-plugin-macros@npm:3.1.0" - dependencies: - "@babel/runtime": "npm:^7.12.5" - cosmiconfig: "npm:^7.0.0" - resolve: "npm:^1.19.0" - checksum: 10c0/c6dfb15de96f67871d95bd2e8c58b0c81edc08b9b087dc16755e7157f357dc1090a8dc60ebab955e92587a9101f02eba07e730adc253a1e4cf593ca3ebd3839c - languageName: node - linkType: hard - -"babel-plugin-react-compiler@npm:^1.0.0": - version: 1.0.0 - resolution: "babel-plugin-react-compiler@npm:1.0.0" - dependencies: - "@babel/types": "npm:^7.26.0" - checksum: 10c0/9406267ada8d7dbdfe8906b40ecadb816a5f4cee2922bee23f7729293b369624ee135b5a9b0f263851c263c9787522ac5d97016c9a2b82d1668300e42b18aff8 - languageName: node - linkType: hard - -"balanced-match@npm:^1.0.0": - version: 1.0.2 - resolution: "balanced-match@npm:1.0.2" - checksum: 10c0/9308baf0a7e4838a82bbfd11e01b1cb0f0cf2893bc1676c27c2a8c0e70cbae1c59120c3268517a8ae7fb6376b4639ef81ca22582611dbee4ed28df945134aaee - languageName: node - linkType: hard - -"base64-js@npm:^1.3.1": - version: 1.5.1 - resolution: "base64-js@npm:1.5.1" - checksum: 10c0/f23823513b63173a001030fae4f2dabe283b99a9d324ade3ad3d148e218134676f1ee8568c877cd79ec1c53158dcf2d2ba527a97c606618928ba99dd930102bf - languageName: node - linkType: hard - -"bl@npm:^1.2.1": - version: 1.2.3 - resolution: "bl@npm:1.2.3" - dependencies: - readable-stream: "npm:^2.3.5" - safe-buffer: "npm:^5.1.1" - checksum: 10c0/ee6478864d3b1295614f269f3fbabeb2362a2f2fc7f8dc2f6c1f944a278d84e0572ecefd6d0b0736d7418763f98dc3b2738253191ea9e98e4b08de211cfac0a6 - languageName: node - linkType: hard - -"brace-expansion@npm:^1.1.7": - version: 1.1.12 - resolution: "brace-expansion@npm:1.1.12" - dependencies: - balanced-match: "npm:^1.0.0" - concat-map: "npm:0.0.1" - checksum: 10c0/975fecac2bb7758c062c20d0b3b6288c7cc895219ee25f0a64a9de662dbac981ff0b6e89909c3897c1f84fa353113a721923afdec5f8b2350255b097f12b1f73 - languageName: node - linkType: hard - -"brace-expansion@npm:^2.0.1": - version: 2.0.2 - resolution: "brace-expansion@npm:2.0.2" - dependencies: - balanced-match: "npm:^1.0.0" - checksum: 10c0/6d117a4c793488af86b83172deb6af143e94c17bc53b0b3cec259733923b4ca84679d506ac261f4ba3c7ed37c46018e2ff442f9ce453af8643ecd64f4a54e6cf - languageName: node - linkType: hard - -"braces@npm:^3.0.3": - version: 3.0.3 - resolution: "braces@npm:3.0.3" - dependencies: - fill-range: "npm:^7.1.1" - checksum: 10c0/7c6dfd30c338d2997ba77500539227b9d1f85e388a5f43220865201e407e076783d0881f2d297b9f80951b4c957fcf0b51c1d2d24227631643c3f7c284b0aa04 - languageName: node - linkType: hard - -"browserslist@npm:^4.24.0": - version: 4.25.3 - resolution: "browserslist@npm:4.25.3" - dependencies: - caniuse-lite: "npm:^1.0.30001735" - electron-to-chromium: "npm:^1.5.204" - node-releases: "npm:^2.0.19" - update-browserslist-db: "npm:^1.1.3" - bin: - browserslist: cli.js - checksum: 10c0/cefbbf962b7c0f0d77e952a4b4b37469db7f7f02bc2be7297810ac3cf086660f48daf78d00f7aad8a11b682f88b0ee0022594165ead749e9e4158a0aa49cd161 - languageName: node - linkType: hard - -"buffer-equal-constant-time@npm:^1.0.1": - version: 1.0.1 - resolution: "buffer-equal-constant-time@npm:1.0.1" - checksum: 10c0/fb2294e64d23c573d0dd1f1e7a466c3e978fe94a4e0f8183937912ca374619773bef8e2aceb854129d2efecbbc515bbd0cc78d2734a3e3031edb0888531bbc8e - languageName: node - linkType: hard - -"buffer@npm:^5.2.1": - version: 5.7.1 - resolution: "buffer@npm:5.7.1" - dependencies: - base64-js: "npm:^1.3.1" - ieee754: "npm:^1.1.13" - checksum: 10c0/27cac81cff434ed2876058d72e7c4789d11ff1120ef32c9de48f59eab58179b66710c488987d295ae89a228f835fc66d088652dffeb8e3ba8659f80eb091d55e - languageName: node - linkType: hard - -"cac@npm:^6.7.14": - version: 6.7.14 - resolution: "cac@npm:6.7.14" - checksum: 10c0/4ee06aaa7bab8981f0d54e5f5f9d4adcd64058e9697563ce336d8a3878ed018ee18ebe5359b2430eceae87e0758e62ea2019c3f52ae6e211b1bd2e133856cd10 - languageName: node - linkType: hard - -"cacache@npm:^20.0.1": - version: 20.0.3 - resolution: "cacache@npm:20.0.3" - dependencies: - "@npmcli/fs": "npm:^5.0.0" - fs-minipass: "npm:^3.0.0" - glob: "npm:^13.0.0" - lru-cache: "npm:^11.1.0" - minipass: "npm:^7.0.3" - minipass-collect: "npm:^2.0.1" - minipass-flush: "npm:^1.0.5" - minipass-pipeline: "npm:^1.2.4" - p-map: "npm:^7.0.2" - ssri: "npm:^13.0.0" - unique-filename: "npm:^5.0.0" - checksum: 10c0/c7da1ca694d20e8f8aedabd21dc11518f809a7d2b59aa76a1fc655db5a9e62379e465c157ddd2afe34b19230808882288effa6911b2de26a088a6d5645123462 - languageName: node - linkType: hard - -"call-bind-apply-helpers@npm:^1.0.0, call-bind-apply-helpers@npm:^1.0.1, call-bind-apply-helpers@npm:^1.0.2": - version: 1.0.2 - resolution: "call-bind-apply-helpers@npm:1.0.2" - dependencies: - es-errors: "npm:^1.3.0" - function-bind: "npm:^1.1.2" - checksum: 10c0/47bd9901d57b857590431243fea704ff18078b16890a6b3e021e12d279bbf211d039155e27d7566b374d49ee1f8189344bac9833dec7a20cdec370506361c938 - languageName: node - linkType: hard - -"call-bind@npm:^1.0.7, call-bind@npm:^1.0.8": - version: 1.0.8 - resolution: "call-bind@npm:1.0.8" - dependencies: - call-bind-apply-helpers: "npm:^1.0.0" - es-define-property: "npm:^1.0.0" - get-intrinsic: "npm:^1.2.4" - set-function-length: "npm:^1.2.2" - checksum: 10c0/a13819be0681d915144467741b69875ae5f4eba8961eb0bf322aab63ec87f8250eb6d6b0dcbb2e1349876412a56129ca338592b3829ef4343527f5f18a0752d4 - languageName: node - linkType: hard - -"call-bound@npm:^1.0.2, call-bound@npm:^1.0.3, call-bound@npm:^1.0.4": - version: 1.0.4 - resolution: "call-bound@npm:1.0.4" - dependencies: - call-bind-apply-helpers: "npm:^1.0.2" - get-intrinsic: "npm:^1.3.0" - checksum: 10c0/f4796a6a0941e71c766aea672f63b72bc61234c4f4964dc6d7606e3664c307e7d77845328a8f3359ce39ddb377fed67318f9ee203dea1d47e46165dcf2917644 - languageName: node - linkType: hard - -"callsites@npm:^3.0.0": - version: 3.1.0 - resolution: "callsites@npm:3.1.0" - checksum: 10c0/fff92277400eb06c3079f9e74f3af120db9f8ea03bad0e84d9aede54bbe2d44a56cccb5f6cf12211f93f52306df87077ecec5b712794c5a9b5dac6d615a3f301 - languageName: node - linkType: hard - -"caniuse-lite@npm:^1.0.30001735": - version: 1.0.30001737 - resolution: "caniuse-lite@npm:1.0.30001737" - checksum: 10c0/9d9cfe3b46fe670d171cee10c5c1b0fb641946fd5d6bea26149f804003d53d82ade7ef5a4a640fb3a0eaec47c7839b57e06a6ddae4f0ad2cd58e1187d31997ce - languageName: node - linkType: hard - -"canvas-renderer@npm:~2.2.0": - version: 2.2.1 - resolution: "canvas-renderer@npm:2.2.1" - dependencies: - "@types/node": "npm:*" - checksum: 10c0/436f2f516723cf2cac03dcb189907b282249cc33446c6bce7760fb3ebd22689b0a4a27b3a3ab077a2c8204bd597a5e923da505520735b00cd839a790e6bdda50 - languageName: node - linkType: hard - -"chai@npm:^5.1.2": - version: 5.3.2 - resolution: "chai@npm:5.3.2" - dependencies: - assertion-error: "npm:^2.0.1" - check-error: "npm:^2.1.1" - deep-eql: "npm:^5.0.1" - loupe: "npm:^3.1.0" - pathval: "npm:^2.0.0" - checksum: 10c0/43c2472b243afe6beea6daa9a6b667c1a2ebc80db37b29132412723ba5a2128a21fa245ff94315189318546b0803aa4ae40f45eb4ec4b68492d3dec168a3e9d8 - languageName: node - linkType: hard - -"chalk@npm:^4.0.0": - version: 4.1.2 - resolution: "chalk@npm:4.1.2" - dependencies: - ansi-styles: "npm:^4.1.0" - supports-color: "npm:^7.1.0" - checksum: 10c0/4a3fef5cc34975c898ffe77141450f679721df9dde00f6c304353fa9c8b571929123b26a0e4617bde5018977eb655b31970c297b91b63ee83bb82aeb04666880 - languageName: node - linkType: hard - -"check-error@npm:^2.1.1": - version: 2.1.1 - resolution: "check-error@npm:2.1.1" - checksum: 10c0/979f13eccab306cf1785fa10941a590b4e7ea9916ea2a4f8c87f0316fc3eab07eabefb6e587424ef0f88cbcd3805791f172ea739863ca3d7ce2afc54641c7f0e - languageName: node - linkType: hard - -"chownr@npm:^3.0.0": - version: 3.0.0 - resolution: "chownr@npm:3.0.0" - checksum: 10c0/43925b87700f7e3893296c8e9c56cc58f926411cce3a6e5898136daaf08f08b9a8eb76d37d3267e707d0dcc17aed2e2ebdf5848c0c3ce95cf910a919935c1b10 - languageName: node - linkType: hard - -"clone-deep@npm:^4.0.1": - version: 4.0.1 - resolution: "clone-deep@npm:4.0.1" - dependencies: - is-plain-object: "npm:^2.0.4" - kind-of: "npm:^6.0.2" - shallow-clone: "npm:^3.0.0" - checksum: 10c0/637753615aa24adf0f2d505947a1bb75e63964309034a1cf56ba4b1f30af155201edd38d26ffe26911adaae267a3c138b344a4947d39f5fc1b6d6108125aa758 - languageName: node - linkType: hard - -"clsx@npm:^1.1.0": - version: 1.2.1 - resolution: "clsx@npm:1.2.1" - checksum: 10c0/34dead8bee24f5e96f6e7937d711978380647e936a22e76380290e35486afd8634966ce300fc4b74a32f3762c7d4c0303f442c3e259f4ce02374eb0c82834f27 - languageName: node - linkType: hard - -"clsx@npm:^2.1.1": - version: 2.1.1 - resolution: "clsx@npm:2.1.1" - checksum: 10c0/c4c8eb865f8c82baab07e71bfa8897c73454881c4f99d6bc81585aecd7c441746c1399d08363dc096c550cceaf97bd4ce1e8854e1771e9998d9f94c4fe075839 - languageName: node - linkType: hard - -"color-convert@npm:^2.0.1": - version: 2.0.1 - resolution: "color-convert@npm:2.0.1" - dependencies: - color-name: "npm:~1.1.4" - checksum: 10c0/37e1150172f2e311fe1b2df62c6293a342ee7380da7b9cfdba67ea539909afbd74da27033208d01d6d5cfc65ee7868a22e18d7e7648e004425441c0f8a15a7d7 - languageName: node - linkType: hard - -"color-name@npm:~1.1.4": - version: 1.1.4 - resolution: "color-name@npm:1.1.4" - checksum: 10c0/a1a3f914156960902f46f7f56bc62effc6c94e84b2cae157a526b1c1f74b677a47ec602bf68a61abfa2b42d15b7c5651c6dbe72a43af720bc588dff885b10f95 - languageName: node - linkType: hard - -"concat-map@npm:0.0.1": - version: 0.0.1 - resolution: "concat-map@npm:0.0.1" - checksum: 10c0/c996b1cfdf95b6c90fee4dae37e332c8b6eb7d106430c17d538034c0ad9a1630cb194d2ab37293b1bdd4d779494beee7786d586a50bd9376fd6f7bcc2bd4c98f - languageName: node - linkType: hard - -"consumable-stream@npm:^2.0.0": - version: 2.0.0 - resolution: "consumable-stream@npm:2.0.0" - checksum: 10c0/4276ac662d254ef17e38daeaf280584801906a661ab215bde5cc3a37e6bd3def428c19abd8db601527a1582c065529ad9608466f09619ba3d1dcd89abcfadaea - languageName: node - linkType: hard - -"consumable-stream@npm:^3.0.0": - version: 3.0.0 - resolution: "consumable-stream@npm:3.0.0" - checksum: 10c0/a6e9a7f29666aa135e898628edf3671d92b6fe061dcab1eff6f8eadb35438ea0f6eb2d782b9ca8320bb05ae2e73706986a17392f2c6bcd5e5c6826745d353fcb - languageName: node - linkType: hard - -"convert-source-map@npm:^1.5.0": - version: 1.9.0 - resolution: "convert-source-map@npm:1.9.0" - checksum: 10c0/281da55454bf8126cbc6625385928c43479f2060984180c42f3a86c8b8c12720a24eac260624a7d1e090004028d2dee78602330578ceec1a08e27cb8bb0a8a5b - languageName: node - linkType: hard - -"convert-source-map@npm:^2.0.0": - version: 2.0.0 - resolution: "convert-source-map@npm:2.0.0" - checksum: 10c0/8f2f7a27a1a011cc6cc88cc4da2d7d0cfa5ee0369508baae3d98c260bb3ac520691464e5bbe4ae7cdf09860c1d69ecc6f70c63c6e7c7f7e3f18ec08484dc7d9b - languageName: node - linkType: hard - -"cookie@npm:^1.0.1": - version: 1.0.2 - resolution: "cookie@npm:1.0.2" - checksum: 10c0/fd25fe79e8fbcfcaf6aa61cd081c55d144eeeba755206c058682257cb38c4bd6795c6620de3f064c740695bb65b7949ebb1db7a95e4636efb8357a335ad3f54b - languageName: node - linkType: hard - -"core-util-is@npm:~1.0.0": - version: 1.0.3 - resolution: "core-util-is@npm:1.0.3" - checksum: 10c0/90a0e40abbddfd7618f8ccd63a74d88deea94e77d0e8dbbea059fa7ebebb8fbb4e2909667fe26f3a467073de1a542ebe6ae4c73a73745ac5833786759cd906c9 - languageName: node - linkType: hard - -"cosmiconfig@npm:^7.0.0": - version: 7.1.0 - resolution: "cosmiconfig@npm:7.1.0" - dependencies: - "@types/parse-json": "npm:^4.0.0" - import-fresh: "npm:^3.2.1" - parse-json: "npm:^5.0.0" - path-type: "npm:^4.0.0" - yaml: "npm:^1.10.0" - checksum: 10c0/b923ff6af581638128e5f074a5450ba12c0300b71302398ea38dbeabd33bbcaa0245ca9adbedfcf284a07da50f99ede5658c80bb3e39e2ce770a99d28a21ef03 - languageName: node - linkType: hard - -"cross-spawn@npm:^7.0.3, cross-spawn@npm:^7.0.6": - version: 7.0.6 - resolution: "cross-spawn@npm:7.0.6" - dependencies: - path-key: "npm:^3.1.0" - shebang-command: "npm:^2.0.0" - which: "npm:^2.0.1" - checksum: 10c0/053ea8b2135caff68a9e81470e845613e374e7309a47731e81639de3eaeb90c3d01af0e0b44d2ab9d50b43467223b88567dfeb3262db942dc063b9976718ffc1 - languageName: node - linkType: hard - -"csstype@npm:^3.0.10": - version: 3.2.3 - resolution: "csstype@npm:3.2.3" - checksum: 10c0/cd29c51e70fa822f1cecd8641a1445bed7063697469d35633b516e60fe8c1bde04b08f6c5b6022136bb669b64c63d4173af54864510fbb4ee23281801841a3ce - languageName: node - linkType: hard - -"csstype@npm:^3.0.2, csstype@npm:^3.1.3": - version: 3.1.3 - resolution: "csstype@npm:3.1.3" - checksum: 10c0/80c089d6f7e0c5b2bd83cf0539ab41474198579584fa10d86d0cafe0642202343cbc119e076a0b1aece191989477081415d66c9fefbf3c957fc2fc4b7009f248 - languageName: node - linkType: hard - -"data-view-buffer@npm:^1.0.2": - version: 1.0.2 - resolution: "data-view-buffer@npm:1.0.2" - dependencies: - call-bound: "npm:^1.0.3" - es-errors: "npm:^1.3.0" - is-data-view: "npm:^1.0.2" - checksum: 10c0/7986d40fc7979e9e6241f85db8d17060dd9a71bd53c894fa29d126061715e322a4cd47a00b0b8c710394854183d4120462b980b8554012acc1c0fa49df7ad38c - languageName: node - linkType: hard - -"data-view-byte-length@npm:^1.0.2": - version: 1.0.2 - resolution: "data-view-byte-length@npm:1.0.2" - dependencies: - call-bound: "npm:^1.0.3" - es-errors: "npm:^1.3.0" - is-data-view: "npm:^1.0.2" - checksum: 10c0/f8a4534b5c69384d95ac18137d381f18a5cfae1f0fc1df0ef6feef51ef0d568606d970b69e02ea186c6c0f0eac77fe4e6ad96fec2569cc86c3afcc7475068c55 - languageName: node - linkType: hard - -"data-view-byte-offset@npm:^1.0.1": - version: 1.0.1 - resolution: "data-view-byte-offset@npm:1.0.1" - dependencies: - call-bound: "npm:^1.0.2" - es-errors: "npm:^1.3.0" - is-data-view: "npm:^1.0.1" - checksum: 10c0/fa7aa40078025b7810dcffc16df02c480573b7b53ef1205aa6a61533011005c1890e5ba17018c692ce7c900212b547262d33279fde801ad9843edc0863bf78c4 - languageName: node - linkType: hard - -"dayjs@npm:^1.11.13": - version: 1.11.13 - resolution: "dayjs@npm:1.11.13" - checksum: 10c0/a3caf6ac8363c7dade9d1ee797848ddcf25c1ace68d9fe8678ecf8ba0675825430de5d793672ec87b24a69bf04a1544b176547b2539982275d5542a7955f35b7 - languageName: node - linkType: hard - -"debug@npm:4": - version: 4.4.3 - resolution: "debug@npm:4.4.3" - dependencies: - ms: "npm:^2.1.3" - peerDependenciesMeta: - supports-color: - optional: true - checksum: 10c0/d79136ec6c83ecbefd0f6a5593da6a9c91ec4d7ddc4b54c883d6e71ec9accb5f67a1a5e96d00a328196b5b5c86d365e98d8a3a70856aaf16b4e7b1985e67f5a6 - languageName: node - linkType: hard - -"debug@npm:^3.2.7": - version: 3.2.7 - resolution: "debug@npm:3.2.7" - dependencies: - ms: "npm:^2.1.1" - checksum: 10c0/37d96ae42cbc71c14844d2ae3ba55adf462ec89fd3a999459dec3833944cd999af6007ff29c780f1c61153bcaaf2c842d1e4ce1ec621e4fc4923244942e4a02a - languageName: node - linkType: hard - -"debug@npm:^4.1.0, debug@npm:^4.1.1, debug@npm:^4.3.1, debug@npm:^4.3.2, debug@npm:^4.3.4, debug@npm:^4.3.7": - version: 4.4.1 - resolution: "debug@npm:4.4.1" - dependencies: - ms: "npm:^2.1.3" - peerDependenciesMeta: - supports-color: - optional: true - checksum: 10c0/d2b44bc1afd912b49bb7ebb0d50a860dc93a4dd7d946e8de94abc957bb63726b7dd5aa48c18c2386c379ec024c46692e15ed3ed97d481729f929201e671fcd55 - languageName: node - linkType: hard - -"deep-eql@npm:^5.0.1": - version: 5.0.2 - resolution: "deep-eql@npm:5.0.2" - checksum: 10c0/7102cf3b7bb719c6b9c0db2e19bf0aa9318d141581befe8c7ce8ccd39af9eaa4346e5e05adef7f9bd7015da0f13a3a25dcfe306ef79dc8668aedbecb658dd247 - languageName: node - linkType: hard - -"deep-is@npm:^0.1.3": - version: 0.1.4 - resolution: "deep-is@npm:0.1.4" - checksum: 10c0/7f0ee496e0dff14a573dc6127f14c95061b448b87b995fc96c017ce0a1e66af1675e73f1d6064407975bc4ea6ab679497a29fff7b5b9c4e99cb10797c1ad0b4c - languageName: node - linkType: hard - -"default-gateway@npm:^6.0.3": - version: 6.0.3 - resolution: "default-gateway@npm:6.0.3" - dependencies: - execa: "npm:^5.0.0" - checksum: 10c0/5184f9e6e105d24fb44ade9e8741efa54bb75e84625c1ea78c4ef8b81dff09ca52d6dbdd1185cf0dc655bb6b282a64fffaf7ed2dd561b8d9ad6f322b1f039aba - languageName: node - linkType: hard - -"define-data-property@npm:^1.0.1, define-data-property@npm:^1.1.4": - version: 1.1.4 - resolution: "define-data-property@npm:1.1.4" - dependencies: - es-define-property: "npm:^1.0.0" - es-errors: "npm:^1.3.0" - gopd: "npm:^1.0.1" - checksum: 10c0/dea0606d1483eb9db8d930d4eac62ca0fa16738b0b3e07046cddfacf7d8c868bbe13fa0cb263eb91c7d0d527960dc3f2f2471a69ed7816210307f6744fe62e37 - languageName: node - linkType: hard - -"define-properties@npm:^1.1.3, define-properties@npm:^1.2.1": - version: 1.2.1 - resolution: "define-properties@npm:1.2.1" - dependencies: - define-data-property: "npm:^1.0.1" - has-property-descriptors: "npm:^1.0.0" - object-keys: "npm:^1.1.1" - checksum: 10c0/88a152319ffe1396ccc6ded510a3896e77efac7a1bfbaa174a7b00414a1747377e0bb525d303794a47cf30e805c2ec84e575758512c6e44a993076d29fd4e6c3 - languageName: node - linkType: hard - -"dns-over-http-resolver@npm:^1.2.3": - version: 1.2.3 - resolution: "dns-over-http-resolver@npm:1.2.3" - dependencies: - debug: "npm:^4.3.1" - native-fetch: "npm:^3.0.0" - receptacle: "npm:^1.3.2" - checksum: 10c0/231435742246115aeb4f153721effc4d995ab8f22572240b27d85e1be4123345cbe503e9922bc46b36caaa86307fbcf65ba252302dc7a4794f330aa6d6f920b8 - languageName: node - linkType: hard - -"doctrine@npm:^2.1.0": - version: 2.1.0 - resolution: "doctrine@npm:2.1.0" - dependencies: - esutils: "npm:^2.0.2" - checksum: 10c0/b6416aaff1f380bf56c3b552f31fdf7a69b45689368deca72d28636f41c16bb28ec3ebc40ace97db4c1afc0ceeb8120e8492fe0046841c94c2933b2e30a7d5ac - languageName: node - linkType: hard - -"dom-helpers@npm:^5.0.1": - version: 5.2.1 - resolution: "dom-helpers@npm:5.2.1" - dependencies: - "@babel/runtime": "npm:^7.8.7" - csstype: "npm:^3.0.2" - checksum: 10c0/f735074d66dd759b36b158fa26e9d00c9388ee0e8c9b16af941c38f014a37fc80782de83afefd621681b19ac0501034b4f1c4a3bff5caa1b8667f0212b5e124c - languageName: node - linkType: hard - -"dunder-proto@npm:^1.0.0, dunder-proto@npm:^1.0.1": - version: 1.0.1 - resolution: "dunder-proto@npm:1.0.1" - dependencies: - call-bind-apply-helpers: "npm:^1.0.1" - es-errors: "npm:^1.3.0" - gopd: "npm:^1.2.0" - checksum: 10c0/199f2a0c1c16593ca0a145dbf76a962f8033ce3129f01284d48c45ed4e14fea9bbacd7b3610b6cdc33486cef20385ac054948fefc6272fcce645c09468f93031 - languageName: node - linkType: hard - -"ecdsa-sig-formatter@npm:1.0.11": - version: 1.0.11 - resolution: "ecdsa-sig-formatter@npm:1.0.11" - dependencies: - safe-buffer: "npm:^5.0.1" - checksum: 10c0/ebfbf19d4b8be938f4dd4a83b8788385da353d63307ede301a9252f9f7f88672e76f2191618fd8edfc2f24679236064176fab0b78131b161ee73daa37125408c - languageName: node - linkType: hard - -"electron-to-chromium@npm:^1.5.204": - version: 1.5.208 - resolution: "electron-to-chromium@npm:1.5.208" - checksum: 10c0/75469e473296a24c66b2f77ab8081aaafe62c8ec700c642940b985a71b111cbf75ae3442ac0c25b7bf30bfd21b49f8e74c21366cc7c513e45366233dbb43ce2b - languageName: node - linkType: hard - -"encoding@npm:^0.1.13": - version: 0.1.13 - resolution: "encoding@npm:0.1.13" - dependencies: - iconv-lite: "npm:^0.6.2" - checksum: 10c0/36d938712ff00fe1f4bac88b43bcffb5930c1efa57bbcdca9d67e1d9d6c57cfb1200fb01efe0f3109b2ce99b231f90779532814a81370a1bd3274a0f58585039 - languageName: node - linkType: hard - -"env-paths@npm:^2.2.0": - version: 2.2.1 - resolution: "env-paths@npm:2.2.1" - checksum: 10c0/285325677bf00e30845e330eec32894f5105529db97496ee3f598478e50f008c5352a41a30e5e72ec9de8a542b5a570b85699cd63bd2bc646dbcb9f311d83bc4 - languageName: node - linkType: hard - -"err-code@npm:^2.0.2": - version: 2.0.3 - resolution: "err-code@npm:2.0.3" - checksum: 10c0/b642f7b4dd4a376e954947550a3065a9ece6733ab8e51ad80db727aaae0817c2e99b02a97a3d6cecc648a97848305e728289cf312d09af395403a90c9d4d8a66 - languageName: node - linkType: hard - -"err-code@npm:^3.0.1": - version: 3.0.1 - resolution: "err-code@npm:3.0.1" - checksum: 10c0/78b1c50500adebde6699b8d27b8ce4728c132dcaad75b5d18ba44f6ccb28769d1fff8368ae1164be4559dac8b95d4e26bb15b480ba9999e0cd0f0c64beaf1b24 - languageName: node - linkType: hard - -"error-ex@npm:^1.3.1": - version: 1.3.2 - resolution: "error-ex@npm:1.3.2" - dependencies: - is-arrayish: "npm:^0.2.1" - checksum: 10c0/ba827f89369b4c93382cfca5a264d059dfefdaa56ecc5e338ffa58a6471f5ed93b71a20add1d52290a4873d92381174382658c885ac1a2305f7baca363ce9cce - languageName: node - linkType: hard - -"es-abstract@npm:^1.17.5, es-abstract@npm:^1.23.2, es-abstract@npm:^1.23.3, es-abstract@npm:^1.23.5, es-abstract@npm:^1.23.6, es-abstract@npm:^1.23.9, es-abstract@npm:^1.24.0": - version: 1.24.0 - resolution: "es-abstract@npm:1.24.0" - dependencies: - array-buffer-byte-length: "npm:^1.0.2" - arraybuffer.prototype.slice: "npm:^1.0.4" - available-typed-arrays: "npm:^1.0.7" - call-bind: "npm:^1.0.8" - call-bound: "npm:^1.0.4" - data-view-buffer: "npm:^1.0.2" - data-view-byte-length: "npm:^1.0.2" - data-view-byte-offset: "npm:^1.0.1" - es-define-property: "npm:^1.0.1" - es-errors: "npm:^1.3.0" - es-object-atoms: "npm:^1.1.1" - es-set-tostringtag: "npm:^2.1.0" - es-to-primitive: "npm:^1.3.0" - function.prototype.name: "npm:^1.1.8" - get-intrinsic: "npm:^1.3.0" - get-proto: "npm:^1.0.1" - get-symbol-description: "npm:^1.1.0" - globalthis: "npm:^1.0.4" - gopd: "npm:^1.2.0" - has-property-descriptors: "npm:^1.0.2" - has-proto: "npm:^1.2.0" - has-symbols: "npm:^1.1.0" - hasown: "npm:^2.0.2" - internal-slot: "npm:^1.1.0" - is-array-buffer: "npm:^3.0.5" - is-callable: "npm:^1.2.7" - is-data-view: "npm:^1.0.2" - is-negative-zero: "npm:^2.0.3" - is-regex: "npm:^1.2.1" - is-set: "npm:^2.0.3" - is-shared-array-buffer: "npm:^1.0.4" - is-string: "npm:^1.1.1" - is-typed-array: "npm:^1.1.15" - is-weakref: "npm:^1.1.1" - math-intrinsics: "npm:^1.1.0" - object-inspect: "npm:^1.13.4" - object-keys: "npm:^1.1.1" - object.assign: "npm:^4.1.7" - own-keys: "npm:^1.0.1" - regexp.prototype.flags: "npm:^1.5.4" - safe-array-concat: "npm:^1.1.3" - safe-push-apply: "npm:^1.0.0" - safe-regex-test: "npm:^1.1.0" - set-proto: "npm:^1.0.0" - stop-iteration-iterator: "npm:^1.1.0" - string.prototype.trim: "npm:^1.2.10" - string.prototype.trimend: "npm:^1.0.9" - string.prototype.trimstart: "npm:^1.0.8" - typed-array-buffer: "npm:^1.0.3" - typed-array-byte-length: "npm:^1.0.3" - typed-array-byte-offset: "npm:^1.0.4" - typed-array-length: "npm:^1.0.7" - unbox-primitive: "npm:^1.1.0" - which-typed-array: "npm:^1.1.19" - checksum: 10c0/b256e897be32df5d382786ce8cce29a1dd8c97efbab77a26609bd70f2ed29fbcfc7a31758cb07488d532e7ccccdfca76c1118f2afe5a424cdc05ca007867c318 - languageName: node - linkType: hard - -"es-define-property@npm:^1.0.0, es-define-property@npm:^1.0.1": - version: 1.0.1 - resolution: "es-define-property@npm:1.0.1" - checksum: 10c0/3f54eb49c16c18707949ff25a1456728c883e81259f045003499efba399c08bad00deebf65cccde8c0e07908c1a225c9d472b7107e558f2a48e28d530e34527c - languageName: node - linkType: hard - -"es-errors@npm:^1.3.0": - version: 1.3.0 - resolution: "es-errors@npm:1.3.0" - checksum: 10c0/0a61325670072f98d8ae3b914edab3559b6caa980f08054a3b872052640d91da01d38df55df797fcc916389d77fc92b8d5906cf028f4db46d7e3003abecbca85 - languageName: node - linkType: hard - -"es-iterator-helpers@npm:^1.2.1": - version: 1.2.1 - resolution: "es-iterator-helpers@npm:1.2.1" - dependencies: - call-bind: "npm:^1.0.8" - call-bound: "npm:^1.0.3" - define-properties: "npm:^1.2.1" - es-abstract: "npm:^1.23.6" - es-errors: "npm:^1.3.0" - es-set-tostringtag: "npm:^2.0.3" - function-bind: "npm:^1.1.2" - get-intrinsic: "npm:^1.2.6" - globalthis: "npm:^1.0.4" - gopd: "npm:^1.2.0" - has-property-descriptors: "npm:^1.0.2" - has-proto: "npm:^1.2.0" - has-symbols: "npm:^1.1.0" - internal-slot: "npm:^1.1.0" - iterator.prototype: "npm:^1.1.4" - safe-array-concat: "npm:^1.1.3" - checksum: 10c0/97e3125ca472d82d8aceea11b790397648b52c26d8768ea1c1ee6309ef45a8755bb63225a43f3150c7591cffc17caf5752459f1e70d583b4184370a8f04ebd2f - languageName: node - linkType: hard - -"es-module-lexer@npm:^1.5.4": - version: 1.7.0 - resolution: "es-module-lexer@npm:1.7.0" - checksum: 10c0/4c935affcbfeba7fb4533e1da10fa8568043df1e3574b869385980de9e2d475ddc36769891936dbb07036edb3c3786a8b78ccf44964cd130dedc1f2c984b6c7b - languageName: node - linkType: hard - -"es-object-atoms@npm:^1.0.0, es-object-atoms@npm:^1.1.1": - version: 1.1.1 - resolution: "es-object-atoms@npm:1.1.1" - dependencies: - es-errors: "npm:^1.3.0" - checksum: 10c0/65364812ca4daf48eb76e2a3b7a89b3f6a2e62a1c420766ce9f692665a29d94fe41fe88b65f24106f449859549711e4b40d9fb8002d862dfd7eb1c512d10be0c - languageName: node - linkType: hard - -"es-set-tostringtag@npm:^2.0.3, es-set-tostringtag@npm:^2.1.0": - version: 2.1.0 - resolution: "es-set-tostringtag@npm:2.1.0" - dependencies: - es-errors: "npm:^1.3.0" - get-intrinsic: "npm:^1.2.6" - has-tostringtag: "npm:^1.0.2" - hasown: "npm:^2.0.2" - checksum: 10c0/ef2ca9ce49afe3931cb32e35da4dcb6d86ab02592cfc2ce3e49ced199d9d0bb5085fc7e73e06312213765f5efa47cc1df553a6a5154584b21448e9fb8355b1af - languageName: node - linkType: hard - -"es-shim-unscopables@npm:^1.0.2, es-shim-unscopables@npm:^1.1.0": - version: 1.1.0 - resolution: "es-shim-unscopables@npm:1.1.0" - dependencies: - hasown: "npm:^2.0.2" - checksum: 10c0/1b9702c8a1823fc3ef39035a4e958802cf294dd21e917397c561d0b3e195f383b978359816b1732d02b255ccf63e1e4815da0065b95db8d7c992037be3bbbcdb - languageName: node - linkType: hard - -"es-to-primitive@npm:^1.3.0": - version: 1.3.0 - resolution: "es-to-primitive@npm:1.3.0" - dependencies: - is-callable: "npm:^1.2.7" - is-date-object: "npm:^1.0.5" - is-symbol: "npm:^1.0.4" - checksum: 10c0/c7e87467abb0b438639baa8139f701a06537d2b9bc758f23e8622c3b42fd0fdb5bde0f535686119e446dd9d5e4c0f238af4e14960f4771877cf818d023f6730b - languageName: node - linkType: hard - -"esbuild@npm:^0.21.3": - version: 0.21.5 - resolution: "esbuild@npm:0.21.5" - dependencies: - "@esbuild/aix-ppc64": "npm:0.21.5" - "@esbuild/android-arm": "npm:0.21.5" - "@esbuild/android-arm64": "npm:0.21.5" - "@esbuild/android-x64": "npm:0.21.5" - "@esbuild/darwin-arm64": "npm:0.21.5" - "@esbuild/darwin-x64": "npm:0.21.5" - "@esbuild/freebsd-arm64": "npm:0.21.5" - "@esbuild/freebsd-x64": "npm:0.21.5" - "@esbuild/linux-arm": "npm:0.21.5" - "@esbuild/linux-arm64": "npm:0.21.5" - "@esbuild/linux-ia32": "npm:0.21.5" - "@esbuild/linux-loong64": "npm:0.21.5" - "@esbuild/linux-mips64el": "npm:0.21.5" - "@esbuild/linux-ppc64": "npm:0.21.5" - "@esbuild/linux-riscv64": "npm:0.21.5" - "@esbuild/linux-s390x": "npm:0.21.5" - "@esbuild/linux-x64": "npm:0.21.5" - "@esbuild/netbsd-x64": "npm:0.21.5" - "@esbuild/openbsd-x64": "npm:0.21.5" - "@esbuild/sunos-x64": "npm:0.21.5" - "@esbuild/win32-arm64": "npm:0.21.5" - "@esbuild/win32-ia32": "npm:0.21.5" - "@esbuild/win32-x64": "npm:0.21.5" - dependenciesMeta: - "@esbuild/aix-ppc64": - optional: true - "@esbuild/android-arm": - optional: true - "@esbuild/android-arm64": - optional: true - "@esbuild/android-x64": - optional: true - "@esbuild/darwin-arm64": - optional: true - "@esbuild/darwin-x64": - optional: true - "@esbuild/freebsd-arm64": - optional: true - "@esbuild/freebsd-x64": - optional: true - "@esbuild/linux-arm": - optional: true - "@esbuild/linux-arm64": - optional: true - "@esbuild/linux-ia32": - optional: true - "@esbuild/linux-loong64": - optional: true - "@esbuild/linux-mips64el": - optional: true - "@esbuild/linux-ppc64": - optional: true - "@esbuild/linux-riscv64": - optional: true - "@esbuild/linux-s390x": - optional: true - "@esbuild/linux-x64": - optional: true - "@esbuild/netbsd-x64": - optional: true - "@esbuild/openbsd-x64": - optional: true - "@esbuild/sunos-x64": - optional: true - "@esbuild/win32-arm64": - optional: true - "@esbuild/win32-ia32": - optional: true - "@esbuild/win32-x64": - optional: true - bin: - esbuild: bin/esbuild - checksum: 10c0/fa08508adf683c3f399e8a014a6382a6b65542213431e26206c0720e536b31c09b50798747c2a105a4bbba1d9767b8d3615a74c2f7bf1ddf6d836cd11eb672de - languageName: node - linkType: hard - -"escalade@npm:^3.2.0": - version: 3.2.0 - resolution: "escalade@npm:3.2.0" - checksum: 10c0/ced4dd3a78e15897ed3be74e635110bbf3b08877b0a41be50dcb325ee0e0b5f65fc2d50e9845194d7c4633f327e2e1c6cce00a71b617c5673df0374201d67f65 - languageName: node - linkType: hard - -"escape-string-regexp@npm:^4.0.0": - version: 4.0.0 - resolution: "escape-string-regexp@npm:4.0.0" - checksum: 10c0/9497d4dd307d845bd7f75180d8188bb17ea8c151c1edbf6b6717c100e104d629dc2dfb687686181b0f4b7d732c7dfdc4d5e7a8ff72de1b0ca283a75bbb3a9cd9 - languageName: node - linkType: hard - -"eslint-import-resolver-node@npm:^0.3.9": - version: 0.3.9 - resolution: "eslint-import-resolver-node@npm:0.3.9" - dependencies: - debug: "npm:^3.2.7" - is-core-module: "npm:^2.13.0" - resolve: "npm:^1.22.4" - checksum: 10c0/0ea8a24a72328a51fd95aa8f660dcca74c1429806737cf10261ab90cfcaaf62fd1eff664b76a44270868e0a932711a81b250053942595bcd00a93b1c1575dd61 - languageName: node - linkType: hard - -"eslint-module-utils@npm:^2.12.1": - version: 2.12.1 - resolution: "eslint-module-utils@npm:2.12.1" - dependencies: - debug: "npm:^3.2.7" - peerDependenciesMeta: - eslint: - optional: true - checksum: 10c0/6f4efbe7a91ae49bf67b4ab3644cb60bc5bd7db4cb5521de1b65be0847ffd3fb6bce0dd68f0995e1b312d137f768e2a1f842ee26fe73621afa05f850628fdc40 - languageName: node - linkType: hard - -"eslint-plugin-import@npm:^2.32.0": - version: 2.32.0 - resolution: "eslint-plugin-import@npm:2.32.0" - dependencies: - "@rtsao/scc": "npm:^1.1.0" - array-includes: "npm:^3.1.9" - array.prototype.findlastindex: "npm:^1.2.6" - array.prototype.flat: "npm:^1.3.3" - array.prototype.flatmap: "npm:^1.3.3" - debug: "npm:^3.2.7" - doctrine: "npm:^2.1.0" - eslint-import-resolver-node: "npm:^0.3.9" - eslint-module-utils: "npm:^2.12.1" - hasown: "npm:^2.0.2" - is-core-module: "npm:^2.16.1" - is-glob: "npm:^4.0.3" - minimatch: "npm:^3.1.2" - object.fromentries: "npm:^2.0.8" - object.groupby: "npm:^1.0.3" - object.values: "npm:^1.2.1" - semver: "npm:^6.3.1" - string.prototype.trimend: "npm:^1.0.9" - tsconfig-paths: "npm:^3.15.0" - peerDependencies: - eslint: ^2 || ^3 || ^4 || ^5 || ^6 || ^7.2.0 || ^8 || ^9 - checksum: 10c0/bfb1b8fc8800398e62ddfefbf3638d185286edfed26dfe00875cc2846d954491b4f5112457831588b757fa789384e1ae585f812614c4797f0499fa234fd4a48b - languageName: node - linkType: hard - -"eslint-plugin-react-hooks@npm:^7.0.1": - version: 7.0.1 - resolution: "eslint-plugin-react-hooks@npm:7.0.1" - dependencies: - "@babel/core": "npm:^7.24.4" - "@babel/parser": "npm:^7.24.4" - hermes-parser: "npm:^0.25.1" - zod: "npm:^3.25.0 || ^4.0.0" - zod-validation-error: "npm:^3.5.0 || ^4.0.0" - peerDependencies: - eslint: ^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0-0 || ^9.0.0 - checksum: 10c0/1e711d1a9d1fa9cfc51fa1572500656577201199c70c795c6a27adfc1df39e5c598f69aab6aa91117753d23cc1f11388579a2bed14921cf9a4efe60ae8618496 - languageName: node - linkType: hard - -"eslint-plugin-react@npm:^7.35.0": - version: 7.37.5 - resolution: "eslint-plugin-react@npm:7.37.5" - dependencies: - array-includes: "npm:^3.1.8" - array.prototype.findlast: "npm:^1.2.5" - array.prototype.flatmap: "npm:^1.3.3" - array.prototype.tosorted: "npm:^1.1.4" - doctrine: "npm:^2.1.0" - es-iterator-helpers: "npm:^1.2.1" - estraverse: "npm:^5.3.0" - hasown: "npm:^2.0.2" - jsx-ast-utils: "npm:^2.4.1 || ^3.0.0" - minimatch: "npm:^3.1.2" - object.entries: "npm:^1.1.9" - object.fromentries: "npm:^2.0.8" - object.values: "npm:^1.2.1" - prop-types: "npm:^15.8.1" - resolve: "npm:^2.0.0-next.5" - semver: "npm:^6.3.1" - string.prototype.matchall: "npm:^4.0.12" - string.prototype.repeat: "npm:^1.0.0" - peerDependencies: - eslint: ^3 || ^4 || ^5 || ^6 || ^7 || ^8 || ^9.7 - checksum: 10c0/c850bfd556291d4d9234f5ca38db1436924a1013627c8ab1853f77cac73ec19b020e861e6c7b783436a48b6ffcdfba4547598235a37ad4611b6739f65fd8ad57 - languageName: node - linkType: hard - -"eslint-scope@npm:^8.4.0": - version: 8.4.0 - resolution: "eslint-scope@npm:8.4.0" - dependencies: - esrecurse: "npm:^4.3.0" - estraverse: "npm:^5.2.0" - checksum: 10c0/407f6c600204d0f3705bd557f81bd0189e69cd7996f408f8971ab5779c0af733d1af2f1412066b40ee1588b085874fc37a2333986c6521669cdbdd36ca5058e0 - languageName: node - linkType: hard - -"eslint-visitor-keys@npm:^3.4.3": - version: 3.4.3 - resolution: "eslint-visitor-keys@npm:3.4.3" - checksum: 10c0/92708e882c0a5ffd88c23c0b404ac1628cf20104a108c745f240a13c332a11aac54f49a22d5762efbffc18ecbc9a580d1b7ad034bf5f3cc3307e5cbff2ec9820 - languageName: node - linkType: hard - -"eslint-visitor-keys@npm:^4.2.1": - version: 4.2.1 - resolution: "eslint-visitor-keys@npm:4.2.1" - checksum: 10c0/fcd43999199d6740db26c58dbe0c2594623e31ca307e616ac05153c9272f12f1364f5a0b1917a8e962268fdecc6f3622c1c2908b4fcc2e047a106fe6de69dc43 - languageName: node - linkType: hard - -"eslint@npm:^9.9.0": - version: 9.33.0 - resolution: "eslint@npm:9.33.0" - dependencies: - "@eslint-community/eslint-utils": "npm:^4.2.0" - "@eslint-community/regexpp": "npm:^4.12.1" - "@eslint/config-array": "npm:^0.21.0" - "@eslint/config-helpers": "npm:^0.3.1" - "@eslint/core": "npm:^0.15.2" - "@eslint/eslintrc": "npm:^3.3.1" - "@eslint/js": "npm:9.33.0" - "@eslint/plugin-kit": "npm:^0.3.5" - "@humanfs/node": "npm:^0.16.6" - "@humanwhocodes/module-importer": "npm:^1.0.1" - "@humanwhocodes/retry": "npm:^0.4.2" - "@types/estree": "npm:^1.0.6" - "@types/json-schema": "npm:^7.0.15" - ajv: "npm:^6.12.4" - chalk: "npm:^4.0.0" - cross-spawn: "npm:^7.0.6" - debug: "npm:^4.3.2" - escape-string-regexp: "npm:^4.0.0" - eslint-scope: "npm:^8.4.0" - eslint-visitor-keys: "npm:^4.2.1" - espree: "npm:^10.4.0" - esquery: "npm:^1.5.0" - esutils: "npm:^2.0.2" - fast-deep-equal: "npm:^3.1.3" - file-entry-cache: "npm:^8.0.0" - find-up: "npm:^5.0.0" - glob-parent: "npm:^6.0.2" - ignore: "npm:^5.2.0" - imurmurhash: "npm:^0.1.4" - is-glob: "npm:^4.0.0" - json-stable-stringify-without-jsonify: "npm:^1.0.1" - lodash.merge: "npm:^4.6.2" - minimatch: "npm:^3.1.2" - natural-compare: "npm:^1.4.0" - optionator: "npm:^0.9.3" - peerDependencies: - jiti: "*" - peerDependenciesMeta: - jiti: - optional: true - bin: - eslint: bin/eslint.js - checksum: 10c0/1e1f60d2b62d9d65553e9af916a8dccf00eeedd982103f35bf58c205803907cb1fda73ef595178d47384ea80d8624a182b63682a6b15d8387e9a5d86904a2a2d - languageName: node - linkType: hard - -"espree@npm:^10.0.1, espree@npm:^10.4.0": - version: 10.4.0 - resolution: "espree@npm:10.4.0" - dependencies: - acorn: "npm:^8.15.0" - acorn-jsx: "npm:^5.3.2" - eslint-visitor-keys: "npm:^4.2.1" - checksum: 10c0/c63fe06131c26c8157b4083313cb02a9a54720a08e21543300e55288c40e06c3fc284bdecf108d3a1372c5934a0a88644c98714f38b6ae8ed272b40d9ea08d6b - languageName: node - linkType: hard - -"esquery@npm:^1.5.0": - version: 1.6.0 - resolution: "esquery@npm:1.6.0" - dependencies: - estraverse: "npm:^5.1.0" - checksum: 10c0/cb9065ec605f9da7a76ca6dadb0619dfb611e37a81e318732977d90fab50a256b95fee2d925fba7c2f3f0523aa16f91587246693bc09bc34d5a59575fe6e93d2 - languageName: node - linkType: hard - -"esrecurse@npm:^4.3.0": - version: 4.3.0 - resolution: "esrecurse@npm:4.3.0" - dependencies: - estraverse: "npm:^5.2.0" - checksum: 10c0/81a37116d1408ded88ada45b9fb16dbd26fba3aadc369ce50fcaf82a0bac12772ebd7b24cd7b91fc66786bf2c1ac7b5f196bc990a473efff972f5cb338877cf5 - languageName: node - linkType: hard - -"estraverse@npm:^5.1.0, estraverse@npm:^5.2.0, estraverse@npm:^5.3.0": - version: 5.3.0 - resolution: "estraverse@npm:5.3.0" - checksum: 10c0/1ff9447b96263dec95d6d67431c5e0771eb9776427421260a3e2f0fdd5d6bd4f8e37a7338f5ad2880c9f143450c9b1e4fc2069060724570a49cf9cf0312bd107 - languageName: node - linkType: hard - -"estree-walker@npm:^3.0.3": - version: 3.0.3 - resolution: "estree-walker@npm:3.0.3" - dependencies: - "@types/estree": "npm:^1.0.0" - checksum: 10c0/c12e3c2b2642d2bcae7d5aa495c60fa2f299160946535763969a1c83fc74518ffa9c2cd3a8b69ac56aea547df6a8aac25f729a342992ef0bbac5f1c73e78995d - languageName: node - linkType: hard - -"esutils@npm:^2.0.2": - version: 2.0.3 - resolution: "esutils@npm:2.0.3" - checksum: 10c0/9a2fe69a41bfdade834ba7c42de4723c97ec776e40656919c62cbd13607c45e127a003f05f724a1ea55e5029a4cf2de444b13009f2af71271e42d93a637137c7 - languageName: node - linkType: hard - -"execa@npm:^5.0.0": - version: 5.1.1 - resolution: "execa@npm:5.1.1" - dependencies: - cross-spawn: "npm:^7.0.3" - get-stream: "npm:^6.0.0" - human-signals: "npm:^2.1.0" - is-stream: "npm:^2.0.0" - merge-stream: "npm:^2.0.0" - npm-run-path: "npm:^4.0.1" - onetime: "npm:^5.1.2" - signal-exit: "npm:^3.0.3" - strip-final-newline: "npm:^2.0.0" - checksum: 10c0/c8e615235e8de4c5addf2fa4c3da3e3aa59ce975a3e83533b4f6a71750fb816a2e79610dc5f1799b6e28976c9ae86747a36a606655bf8cb414a74d8d507b304f - languageName: node - linkType: hard - -"expect-type@npm:^1.1.0": - version: 1.2.2 - resolution: "expect-type@npm:1.2.2" - checksum: 10c0/6019019566063bbc7a690d9281d920b1a91284a4a093c2d55d71ffade5ac890cf37a51e1da4602546c4b56569d2ad2fc175a2ccee77d1ae06cb3af91ef84f44b - languageName: node - linkType: hard - -"exponential-backoff@npm:^3.1.1": - version: 3.1.3 - resolution: "exponential-backoff@npm:3.1.3" - checksum: 10c0/77e3ae682b7b1f4972f563c6dbcd2b0d54ac679e62d5d32f3e5085feba20483cf28bd505543f520e287a56d4d55a28d7874299941faf637e779a1aa5994d1267 - languageName: node - linkType: hard - -"fast-deep-equal@npm:^3.1.1, fast-deep-equal@npm:^3.1.3": - version: 3.1.3 - resolution: "fast-deep-equal@npm:3.1.3" - checksum: 10c0/40dedc862eb8992c54579c66d914635afbec43350afbbe991235fdcb4e3a8d5af1b23ae7e79bef7d4882d0ecee06c3197488026998fb19f72dc95acff1d1b1d0 - languageName: node - linkType: hard - -"fast-glob@npm:^3.3.2": - version: 3.3.3 - resolution: "fast-glob@npm:3.3.3" - dependencies: - "@nodelib/fs.stat": "npm:^2.0.2" - "@nodelib/fs.walk": "npm:^1.2.3" - glob-parent: "npm:^5.1.2" - merge2: "npm:^1.3.0" - micromatch: "npm:^4.0.8" - checksum: 10c0/f6aaa141d0d3384cf73cbcdfc52f475ed293f6d5b65bfc5def368b09163a9f7e5ec2b3014d80f733c405f58e470ee0cc451c2937685045cddcdeaa24199c43fe - languageName: node - linkType: hard - -"fast-json-stable-stringify@npm:^2.0.0": - version: 2.1.0 - resolution: "fast-json-stable-stringify@npm:2.1.0" - checksum: 10c0/7f081eb0b8a64e0057b3bb03f974b3ef00135fbf36c1c710895cd9300f13c94ba809bb3a81cf4e1b03f6e5285610a61abbd7602d0652de423144dfee5a389c9b - languageName: node - linkType: hard - -"fast-levenshtein@npm:^2.0.6": - version: 2.0.6 - resolution: "fast-levenshtein@npm:2.0.6" - checksum: 10c0/111972b37338bcb88f7d9e2c5907862c280ebf4234433b95bc611e518d192ccb2d38119c4ac86e26b668d75f7f3894f4ff5c4982899afced7ca78633b08287c4 - languageName: node - linkType: hard - -"fastq@npm:^1.6.0": - version: 1.19.1 - resolution: "fastq@npm:1.19.1" - dependencies: - reusify: "npm:^1.0.4" - checksum: 10c0/ebc6e50ac7048daaeb8e64522a1ea7a26e92b3cee5cd1c7f2316cdca81ba543aa40a136b53891446ea5c3a67ec215fbaca87ad405f102dd97012f62916905630 - languageName: node - linkType: hard - -"fdir@npm:^6.5.0": - version: 6.5.0 - resolution: "fdir@npm:6.5.0" - peerDependencies: - picomatch: ^3 || ^4 - peerDependenciesMeta: - picomatch: - optional: true - checksum: 10c0/e345083c4306b3aed6cb8ec551e26c36bab5c511e99ea4576a16750ddc8d3240e63826cc624f5ae17ad4dc82e68a253213b60d556c11bfad064b7607847ed07f - languageName: node - linkType: hard - -"file-entry-cache@npm:^8.0.0": - version: 8.0.0 - resolution: "file-entry-cache@npm:8.0.0" - dependencies: - flat-cache: "npm:^4.0.0" - checksum: 10c0/9e2b5938b1cd9b6d7e3612bdc533afd4ac17b2fc646569e9a8abbf2eb48e5eb8e316bc38815a3ef6a1b456f4107f0d0f055a614ca613e75db6bf9ff4d72c1638 - languageName: node - linkType: hard - -"fill-range@npm:^7.1.1": - version: 7.1.1 - resolution: "fill-range@npm:7.1.1" - dependencies: - to-regex-range: "npm:^5.0.1" - checksum: 10c0/b75b691bbe065472f38824f694c2f7449d7f5004aa950426a2c28f0306c60db9b880c0b0e4ed819997ffb882d1da02cfcfc819bddc94d71627f5269682edf018 - languageName: node - linkType: hard - -"find-root@npm:^1.1.0": - version: 1.1.0 - resolution: "find-root@npm:1.1.0" - checksum: 10c0/1abc7f3bf2f8d78ff26d9e00ce9d0f7b32e5ff6d1da2857bcdf4746134c422282b091c672cde0572cac3840713487e0a7a636af9aa1b74cb11894b447a521efa - languageName: node - linkType: hard - -"find-up@npm:^5.0.0": - version: 5.0.0 - resolution: "find-up@npm:5.0.0" - dependencies: - locate-path: "npm:^6.0.0" - path-exists: "npm:^4.0.0" - checksum: 10c0/062c5a83a9c02f53cdd6d175a37ecf8f87ea5bbff1fdfb828f04bfa021441bc7583e8ebc0872a4c1baab96221fb8a8a275a19809fb93fbc40bd69ec35634069a - languageName: node - linkType: hard - -"flat-cache@npm:^4.0.0": - version: 4.0.1 - resolution: "flat-cache@npm:4.0.1" - dependencies: - flatted: "npm:^3.2.9" - keyv: "npm:^4.5.4" - checksum: 10c0/2c59d93e9faa2523e4fda6b4ada749bed432cfa28c8e251f33b25795e426a1c6dbada777afb1f74fcfff33934fdbdea921ee738fcc33e71adc9d6eca984a1cfc - languageName: node - linkType: hard - -"flatted@npm:^3.2.9": - version: 3.3.3 - resolution: "flatted@npm:3.3.3" - checksum: 10c0/e957a1c6b0254aa15b8cce8533e24165abd98fadc98575db082b786b5da1b7d72062b81bfdcd1da2f4d46b6ed93bec2434e62333e9b4261d79ef2e75a10dd538 - languageName: node - linkType: hard - -"for-each@npm:^0.3.3, for-each@npm:^0.3.5": - version: 0.3.5 - resolution: "for-each@npm:0.3.5" - dependencies: - is-callable: "npm:^1.2.7" - checksum: 10c0/0e0b50f6a843a282637d43674d1fb278dda1dd85f4f99b640024cfb10b85058aac0cc781bf689d5fe50b4b7f638e91e548560723a4e76e04fe96ae35ef039cee - languageName: node - linkType: hard - -"fs-minipass@npm:^3.0.0": - version: 3.0.3 - resolution: "fs-minipass@npm:3.0.3" - dependencies: - minipass: "npm:^7.0.3" - checksum: 10c0/63e80da2ff9b621e2cb1596abcb9207f1cf82b968b116ccd7b959e3323144cce7fb141462200971c38bbf2ecca51695069db45265705bed09a7cd93ae5b89f94 - languageName: node - linkType: hard - -"fsevents@npm:~2.3.2, fsevents@npm:~2.3.3": - version: 2.3.3 - resolution: "fsevents@npm:2.3.3" - dependencies: - node-gyp: "npm:latest" - checksum: 10c0/a1f0c44595123ed717febbc478aa952e47adfc28e2092be66b8ab1635147254ca6cfe1df792a8997f22716d4cbafc73309899ff7bfac2ac3ad8cf2e4ecc3ec60 - conditions: os=darwin - languageName: node - linkType: hard - -"fsevents@patch:fsevents@npm%3A~2.3.2#optional!builtin, fsevents@patch:fsevents@npm%3A~2.3.3#optional!builtin": - version: 2.3.3 - resolution: "fsevents@patch:fsevents@npm%3A2.3.3#optional!builtin::version=2.3.3&hash=df0bf1" - dependencies: - node-gyp: "npm:latest" - conditions: os=darwin - languageName: node - linkType: hard - -"function-bind@npm:^1.1.2": - version: 1.1.2 - resolution: "function-bind@npm:1.1.2" - checksum: 10c0/d8680ee1e5fcd4c197e4ac33b2b4dce03c71f4d91717292785703db200f5c21f977c568d28061226f9b5900cbcd2c84463646134fd5337e7925e0942bc3f46d5 - languageName: node - linkType: hard - -"function.prototype.name@npm:^1.1.6, function.prototype.name@npm:^1.1.8": - version: 1.1.8 - resolution: "function.prototype.name@npm:1.1.8" - dependencies: - call-bind: "npm:^1.0.8" - call-bound: "npm:^1.0.3" - define-properties: "npm:^1.2.1" - functions-have-names: "npm:^1.2.3" - hasown: "npm:^2.0.2" - is-callable: "npm:^1.2.7" - checksum: 10c0/e920a2ab52663005f3cbe7ee3373e3c71c1fb5558b0b0548648cdf3e51961085032458e26c71ff1a8c8c20e7ee7caeb03d43a5d1fa8610c459333323a2e71253 - languageName: node - linkType: hard - -"functions-have-names@npm:^1.2.3": - version: 1.2.3 - resolution: "functions-have-names@npm:1.2.3" - checksum: 10c0/33e77fd29bddc2d9bb78ab3eb854c165909201f88c75faa8272e35899e2d35a8a642a15e7420ef945e1f64a9670d6aa3ec744106b2aa42be68ca5114025954ca - languageName: node - linkType: hard - -"gensync@npm:^1.0.0-beta.2": - version: 1.0.0-beta.2 - resolution: "gensync@npm:1.0.0-beta.2" - checksum: 10c0/782aba6cba65b1bb5af3b095d96249d20edbe8df32dbf4696fd49be2583faf676173bf4809386588828e4dd76a3354fcbeb577bab1c833ccd9fc4577f26103f8 - languageName: node - linkType: hard - -"get-intrinsic@npm:^1.2.4, get-intrinsic@npm:^1.2.5, get-intrinsic@npm:^1.2.6, get-intrinsic@npm:^1.2.7, get-intrinsic@npm:^1.3.0": - version: 1.3.0 - resolution: "get-intrinsic@npm:1.3.0" - dependencies: - call-bind-apply-helpers: "npm:^1.0.2" - es-define-property: "npm:^1.0.1" - es-errors: "npm:^1.3.0" - es-object-atoms: "npm:^1.1.1" - function-bind: "npm:^1.1.2" - get-proto: "npm:^1.0.1" - gopd: "npm:^1.2.0" - has-symbols: "npm:^1.1.0" - hasown: "npm:^2.0.2" - math-intrinsics: "npm:^1.1.0" - checksum: 10c0/52c81808af9a8130f581e6a6a83e1ba4a9f703359e7a438d1369a5267a25412322f03dcbd7c549edaef0b6214a0630a28511d7df0130c93cfd380f4fa0b5b66a - languageName: node - linkType: hard - -"get-params@npm:^0.1.2": - version: 0.1.2 - resolution: "get-params@npm:0.1.2" - checksum: 10c0/bf83ed37d2c5e9d9cf04b0d9d380fa3f29997e4c4c5cbbcda0112747a43148111ab5a054e1681819aeb24b98726053196b183830ad9a4b246bdc5f79af5792f7 - languageName: node - linkType: hard - -"get-proto@npm:^1.0.0, get-proto@npm:^1.0.1": - version: 1.0.1 - resolution: "get-proto@npm:1.0.1" - dependencies: - dunder-proto: "npm:^1.0.1" - es-object-atoms: "npm:^1.0.0" - checksum: 10c0/9224acb44603c5526955e83510b9da41baf6ae73f7398875fba50edc5e944223a89c4a72b070fcd78beb5f7bdda58ecb6294adc28f7acfc0da05f76a2399643c - languageName: node - linkType: hard - -"get-stream@npm:^6.0.0": - version: 6.0.1 - resolution: "get-stream@npm:6.0.1" - checksum: 10c0/49825d57d3fd6964228e6200a58169464b8e8970489b3acdc24906c782fb7f01f9f56f8e6653c4a50713771d6658f7cfe051e5eb8c12e334138c9c918b296341 - languageName: node - linkType: hard - -"get-symbol-description@npm:^1.1.0": - version: 1.1.0 - resolution: "get-symbol-description@npm:1.1.0" - dependencies: - call-bound: "npm:^1.0.3" - es-errors: "npm:^1.3.0" - get-intrinsic: "npm:^1.2.6" - checksum: 10c0/d6a7d6afca375779a4b307738c9e80dbf7afc0bdbe5948768d54ab9653c865523d8920e670991a925936eb524b7cb6a6361d199a760b21d0ca7620194455aa4b - languageName: node - linkType: hard - -"glob-parent@npm:^5.1.2": - version: 5.1.2 - resolution: "glob-parent@npm:5.1.2" - dependencies: - is-glob: "npm:^4.0.1" - checksum: 10c0/cab87638e2112bee3f839ef5f6e0765057163d39c66be8ec1602f3823da4692297ad4e972de876ea17c44d652978638d2fd583c6713d0eb6591706825020c9ee - languageName: node - linkType: hard - -"glob-parent@npm:^6.0.2": - version: 6.0.2 - resolution: "glob-parent@npm:6.0.2" - dependencies: - is-glob: "npm:^4.0.3" - checksum: 10c0/317034d88654730230b3f43bb7ad4f7c90257a426e872ea0bf157473ac61c99bf5d205fad8f0185f989be8d2fa6d3c7dce1645d99d545b6ea9089c39f838e7f8 - languageName: node - linkType: hard - -"glob@npm:^13.0.0": - version: 13.0.0 - resolution: "glob@npm:13.0.0" - dependencies: - minimatch: "npm:^10.1.1" - minipass: "npm:^7.1.2" - path-scurry: "npm:^2.0.0" - checksum: 10c0/8e2f5821f3f7c312dd102e23a15b80c79e0837a9872784293ba2e15ec73b3f3749a49a42a31bfcb4e52c84820a474e92331c2eebf18819d20308f5c33876630a - languageName: node - linkType: hard - -"globals@npm:^14.0.0": - version: 14.0.0 - resolution: "globals@npm:14.0.0" - checksum: 10c0/b96ff42620c9231ad468d4c58ff42afee7777ee1c963013ff8aabe095a451d0ceeb8dcd8ef4cbd64d2538cef45f787a78ba3a9574f4a634438963e334471302d - languageName: node - linkType: hard - -"globals@npm:^15.9.0": - version: 15.15.0 - resolution: "globals@npm:15.15.0" - checksum: 10c0/f9ae80996392ca71316495a39bec88ac43ae3525a438b5626cd9d5ce9d5500d0a98a266409605f8cd7241c7acf57c354a48111ea02a767ba4f374b806d6861fe - languageName: node - linkType: hard - -"globalthis@npm:^1.0.4": - version: 1.0.4 - resolution: "globalthis@npm:1.0.4" - dependencies: - define-properties: "npm:^1.2.1" - gopd: "npm:^1.0.1" - checksum: 10c0/9d156f313af79d80b1566b93e19285f481c591ad6d0d319b4be5e03750d004dde40a39a0f26f7e635f9007a3600802f53ecd85a759b86f109e80a5f705e01846 - languageName: node - linkType: hard - -"globrex@npm:^0.1.2": - version: 0.1.2 - resolution: "globrex@npm:0.1.2" - checksum: 10c0/a54c029520cf58bda1d8884f72bd49b4cd74e977883268d931fd83bcbd1a9eb96d57c7dbd4ad80148fb9247467ebfb9b215630b2ed7563b2a8de02e1ff7f89d1 - languageName: node - linkType: hard - -"goober@npm:^2.0.33": - version: 2.1.16 - resolution: "goober@npm:2.1.16" - peerDependencies: - csstype: ^3.0.10 - checksum: 10c0/f4c8256bf9c27873d47c1443f348779ac7f322516cb80a5dc647a6ebe790ce6bb9d3f487a0fb8be0b583fb96b9b2f6b7463f7fea3cd680306f95fa6fc9db1f6a - languageName: node - linkType: hard - -"gopd@npm:^1.0.1, gopd@npm:^1.2.0": - version: 1.2.0 - resolution: "gopd@npm:1.2.0" - checksum: 10c0/50fff1e04ba2b7737c097358534eacadad1e68d24cccee3272e04e007bed008e68d2614f3987788428fd192a5ae3889d08fb2331417e4fc4a9ab366b2043cead - languageName: node - linkType: hard - -"graceful-fs@npm:^4.2.6": - version: 4.2.11 - resolution: "graceful-fs@npm:4.2.11" - checksum: 10c0/386d011a553e02bc594ac2ca0bd6d9e4c22d7fa8cfbfc448a6d148c59ea881b092db9dbe3547ae4b88e55f1b01f7c4a2ecc53b310c042793e63aa44cf6c257f2 - languageName: node - linkType: hard - -"graphemer@npm:^1.4.0": - version: 1.4.0 - resolution: "graphemer@npm:1.4.0" - checksum: 10c0/e951259d8cd2e0d196c72ec711add7115d42eb9a8146c8eeda5b8d3ac91e5dd816b9cd68920726d9fd4490368e7ed86e9c423f40db87e2d8dfafa00fa17c3a31 - languageName: node - linkType: hard - -"has-bigints@npm:^1.0.2": - version: 1.1.0 - resolution: "has-bigints@npm:1.1.0" - checksum: 10c0/2de0cdc4a1ccf7a1e75ffede1876994525ac03cc6f5ae7392d3415dd475cd9eee5bceec63669ab61aa997ff6cceebb50ef75561c7002bed8988de2b9d1b40788 - languageName: node - linkType: hard - -"has-flag@npm:^4.0.0": - version: 4.0.0 - resolution: "has-flag@npm:4.0.0" - checksum: 10c0/2e789c61b7888d66993e14e8331449e525ef42aac53c627cc53d1c3334e768bcb6abdc4f5f0de1478a25beec6f0bd62c7549058b7ac53e924040d4f301f02fd1 - languageName: node - linkType: hard - -"has-property-descriptors@npm:^1.0.0, has-property-descriptors@npm:^1.0.2": - version: 1.0.2 - resolution: "has-property-descriptors@npm:1.0.2" - dependencies: - es-define-property: "npm:^1.0.0" - checksum: 10c0/253c1f59e80bb476cf0dde8ff5284505d90c3bdb762983c3514d36414290475fe3fd6f574929d84de2a8eec00d35cf07cb6776205ff32efd7c50719125f00236 - languageName: node - linkType: hard - -"has-proto@npm:^1.2.0": - version: 1.2.0 - resolution: "has-proto@npm:1.2.0" - dependencies: - dunder-proto: "npm:^1.0.0" - checksum: 10c0/46538dddab297ec2f43923c3d35237df45d8c55a6fc1067031e04c13ed8a9a8f94954460632fd4da84c31a1721eefee16d901cbb1ae9602bab93bb6e08f93b95 - languageName: node - linkType: hard - -"has-symbols@npm:^1.0.3, has-symbols@npm:^1.1.0": - version: 1.1.0 - resolution: "has-symbols@npm:1.1.0" - checksum: 10c0/dde0a734b17ae51e84b10986e651c664379018d10b91b6b0e9b293eddb32f0f069688c841fb40f19e9611546130153e0a2a48fd7f512891fb000ddfa36f5a20e - languageName: node - linkType: hard - -"has-tostringtag@npm:^1.0.2": - version: 1.0.2 - resolution: "has-tostringtag@npm:1.0.2" - dependencies: - has-symbols: "npm:^1.0.3" - checksum: 10c0/a8b166462192bafe3d9b6e420a1d581d93dd867adb61be223a17a8d6dad147aa77a8be32c961bb2f27b3ef893cae8d36f564ab651f5e9b7938ae86f74027c48c - languageName: node - linkType: hard - -"hasown@npm:^2.0.2": - version: 2.0.2 - resolution: "hasown@npm:2.0.2" - dependencies: - function-bind: "npm:^1.1.2" - checksum: 10c0/3769d434703b8ac66b209a4cca0737519925bbdb61dd887f93a16372b14694c63ff4e797686d87c90f08168e81082248b9b028bad60d4da9e0d1148766f56eb9 - languageName: node - linkType: hard - -"hermes-estree@npm:0.25.1": - version: 0.25.1 - resolution: "hermes-estree@npm:0.25.1" - checksum: 10c0/48be3b2fa37a0cbc77a112a89096fa212f25d06de92781b163d67853d210a8a5c3784fac23d7d48335058f7ed283115c87b4332c2a2abaaccc76d0ead1a282ac - languageName: node - linkType: hard - -"hermes-parser@npm:^0.25.1": - version: 0.25.1 - resolution: "hermes-parser@npm:0.25.1" - dependencies: - hermes-estree: "npm:0.25.1" - checksum: 10c0/3abaa4c6f1bcc25273f267297a89a4904963ea29af19b8e4f6eabe04f1c2c7e9abd7bfc4730ddb1d58f2ea04b6fee74053d8bddb5656ec6ebf6c79cc8d14202c - languageName: node - linkType: hard - -"hoist-non-react-statics@npm:^3.3.0, hoist-non-react-statics@npm:^3.3.1": - version: 3.3.2 - resolution: "hoist-non-react-statics@npm:3.3.2" - dependencies: - react-is: "npm:^16.7.0" - checksum: 10c0/fe0889169e845d738b59b64badf5e55fa3cf20454f9203d1eb088df322d49d4318df774828e789898dcb280e8a5521bb59b3203385662ca5e9218a6ca5820e74 - languageName: node - linkType: hard - -"http-cache-semantics@npm:^4.1.1": - version: 4.2.0 - resolution: "http-cache-semantics@npm:4.2.0" - checksum: 10c0/45b66a945cf13ec2d1f29432277201313babf4a01d9e52f44b31ca923434083afeca03f18417f599c9ab3d0e7b618ceb21257542338b57c54b710463b4a53e37 - languageName: node - linkType: hard - -"http-proxy-agent@npm:^7.0.0": - version: 7.0.2 - resolution: "http-proxy-agent@npm:7.0.2" - dependencies: - agent-base: "npm:^7.1.0" - debug: "npm:^4.3.4" - checksum: 10c0/4207b06a4580fb85dd6dff521f0abf6db517489e70863dca1a0291daa7f2d3d2d6015a57bd702af068ea5cf9f1f6ff72314f5f5b4228d299c0904135d2aef921 - languageName: node - linkType: hard - -"https-proxy-agent@npm:^7.0.1": - version: 7.0.6 - resolution: "https-proxy-agent@npm:7.0.6" - dependencies: - agent-base: "npm:^7.1.2" - debug: "npm:4" - checksum: 10c0/f729219bc735edb621fa30e6e84e60ee5d00802b8247aac0d7b79b0bd6d4b3294737a337b93b86a0bd9e68099d031858a39260c976dc14cdbba238ba1f8779ac - languageName: node - linkType: hard - -"human-signals@npm:^2.1.0": - version: 2.1.0 - resolution: "human-signals@npm:2.1.0" - checksum: 10c0/695edb3edfcfe9c8b52a76926cd31b36978782062c0ed9b1192b36bebc75c4c87c82e178dfcb0ed0fc27ca59d434198aac0bd0be18f5781ded775604db22304a - languageName: node - linkType: hard - -"humanize-duration@npm:^3.32.1": - version: 3.33.0 - resolution: "humanize-duration@npm:3.33.0" - checksum: 10c0/516c966e848177df526444f83f27d92fdfb7b96f8cd3d42e5b1bdbda950c8b56db0ac8ea25bc130445b577d19c382c35fe3d458643c10f0fe071ab1b8fe8ad80 - languageName: node - linkType: hard - -"iconv-lite@npm:^0.6.2": - version: 0.6.3 - resolution: "iconv-lite@npm:0.6.3" - dependencies: - safer-buffer: "npm:>= 2.1.2 < 3.0.0" - checksum: 10c0/98102bc66b33fcf5ac044099d1257ba0b7ad5e3ccd3221f34dd508ab4070edff183276221684e1e0555b145fce0850c9f7d2b60a9fcac50fbb4ea0d6e845a3b1 - languageName: node - linkType: hard - -"ieee754@npm:^1.1.13": - version: 1.2.1 - resolution: "ieee754@npm:1.2.1" - checksum: 10c0/b0782ef5e0935b9f12883a2e2aa37baa75da6e66ce6515c168697b42160807d9330de9a32ec1ed73149aea02e0d822e572bca6f1e22bdcbd2149e13b050b17bb - languageName: node - linkType: hard - -"ignore@npm:^5.2.0": - version: 5.3.2 - resolution: "ignore@npm:5.3.2" - checksum: 10c0/f9f652c957983634ded1e7f02da3b559a0d4cc210fca3792cb67f1b153623c9c42efdc1c4121af171e295444459fc4a9201101fb041b1104a3c000bccb188337 - languageName: node - linkType: hard - -"ignore@npm:^7.0.0": - version: 7.0.5 - resolution: "ignore@npm:7.0.5" - checksum: 10c0/ae00db89fe873064a093b8999fe4cc284b13ef2a178636211842cceb650b9c3e390d3339191acb145d81ed5379d2074840cf0c33a20bdbd6f32821f79eb4ad5d - languageName: node - linkType: hard - -"immer@npm:^10.0.3": - version: 10.1.1 - resolution: "immer@npm:10.1.1" - checksum: 10c0/b749e10d137ccae91788f41bd57e9387f32ea6d6ea8fd7eb47b23fd7766681575efc7f86ceef7fe24c3bc9d61e38ff5d2f49c2663b2b0c056e280a4510923653 - languageName: node - linkType: hard - -"immutable@npm:^4.3.7": - version: 4.3.7 - resolution: "immutable@npm:4.3.7" - checksum: 10c0/9b099197081b22f6433003e34929da8ecddbbdc1474cdc8aa3b7669dee4adda349c06143de22def36016d1b6de5322b043eccd7a11db1dad2ca85dad4fff5435 - languageName: node - linkType: hard - -"import-fresh@npm:^3.2.1": - version: 3.3.1 - resolution: "import-fresh@npm:3.3.1" - dependencies: - parent-module: "npm:^1.0.0" - resolve-from: "npm:^4.0.0" - checksum: 10c0/bf8cc494872fef783249709385ae883b447e3eb09db0ebd15dcead7d9afe7224dad7bd7591c6b73b0b19b3c0f9640eb8ee884f01cfaf2887ab995b0b36a0cbec - languageName: node - linkType: hard - -"imurmurhash@npm:^0.1.4": - version: 0.1.4 - resolution: "imurmurhash@npm:0.1.4" - checksum: 10c0/8b51313850dd33605c6c9d3fd9638b714f4c4c40250cff658209f30d40da60f78992fb2df5dabee4acf589a6a82bbc79ad5486550754bd9ec4e3fc0d4a57d6a6 - languageName: node - linkType: hard - -"inherits@npm:~2.0.3": - version: 2.0.4 - resolution: "inherits@npm:2.0.4" - checksum: 10c0/4e531f648b29039fb7426fb94075e6545faa1eb9fe83c29f0b6d9e7263aceb4289d2d4557db0d428188eeb449cc7c5e77b0a0b2c4e248ff2a65933a0dee49ef2 - languageName: node - linkType: hard - -"internal-ip@npm:^7.0.0": - version: 7.0.0 - resolution: "internal-ip@npm:7.0.0" - dependencies: - default-gateway: "npm:^6.0.3" - ipaddr.js: "npm:^2.0.1" - is-ip: "npm:^3.1.0" - p-event: "npm:^4.2.0" - checksum: 10c0/22eb24ad1e1c5eb9c3742cfb4ce7c85b3328632aee8f595e5587e699c386b46b44c6655141135187ed09c60a19e7357188f2cf306ee569711f23f8a8cf56f359 - languageName: node - linkType: hard - -"internal-slot@npm:^1.1.0": - version: 1.1.0 - resolution: "internal-slot@npm:1.1.0" - dependencies: - es-errors: "npm:^1.3.0" - hasown: "npm:^2.0.2" - side-channel: "npm:^1.1.0" - checksum: 10c0/03966f5e259b009a9bf1a78d60da920df198af4318ec004f57b8aef1dd3fe377fbc8cce63a96e8c810010302654de89f9e19de1cd8ad0061d15be28a695465c7 - languageName: node - linkType: hard - -"ip-address@npm:^10.0.1": - version: 10.1.0 - resolution: "ip-address@npm:10.1.0" - checksum: 10c0/0103516cfa93f6433b3bd7333fa876eb21263912329bfa47010af5e16934eeeff86f3d2ae700a3744a137839ddfad62b900c7a445607884a49b5d1e32a3d7566 - languageName: node - linkType: hard - -"ip-regex@npm:^4.0.0": - version: 4.3.0 - resolution: "ip-regex@npm:4.3.0" - checksum: 10c0/f9ef1f5d0df05b9133a882974e572ae525ccd205260cb103dae337f1fc7451ed783391acc6ad688e56dd2598f769e8e72ecbb650ec34763396af822a91768562 - languageName: node - linkType: hard - -"ipaddr.js@npm:^2.0.1": - version: 2.2.0 - resolution: "ipaddr.js@npm:2.2.0" - checksum: 10c0/e4ee875dc1bd92ac9d27e06cfd87cdb63ca786ff9fd7718f1d4f7a8ef27db6e5d516128f52d2c560408cbb75796ac2f83ead669e73507c86282d45f84c5abbb6 - languageName: node - linkType: hard - -"is-array-buffer@npm:^3.0.4, is-array-buffer@npm:^3.0.5": - version: 3.0.5 - resolution: "is-array-buffer@npm:3.0.5" - dependencies: - call-bind: "npm:^1.0.8" - call-bound: "npm:^1.0.3" - get-intrinsic: "npm:^1.2.6" - checksum: 10c0/c5c9f25606e86dbb12e756694afbbff64bc8b348d1bc989324c037e1068695131930199d6ad381952715dad3a9569333817f0b1a72ce5af7f883ce802e49c83d - languageName: node - linkType: hard - -"is-arrayish@npm:^0.2.1": - version: 0.2.1 - resolution: "is-arrayish@npm:0.2.1" - checksum: 10c0/e7fb686a739068bb70f860b39b67afc62acc62e36bb61c5f965768abce1873b379c563e61dd2adad96ebb7edf6651111b385e490cf508378959b0ed4cac4e729 - languageName: node - linkType: hard - -"is-async-function@npm:^2.0.0": - version: 2.1.1 - resolution: "is-async-function@npm:2.1.1" - dependencies: - async-function: "npm:^1.0.0" - call-bound: "npm:^1.0.3" - get-proto: "npm:^1.0.1" - has-tostringtag: "npm:^1.0.2" - safe-regex-test: "npm:^1.1.0" - checksum: 10c0/d70c236a5e82de6fc4d44368ffd0c2fee2b088b893511ce21e679da275a5ecc6015ff59a7d7e1bdd7ca39f71a8dbdd253cf8cce5c6b3c91cdd5b42b5ce677298 - languageName: node - linkType: hard - -"is-bigint@npm:^1.1.0": - version: 1.1.0 - resolution: "is-bigint@npm:1.1.0" - dependencies: - has-bigints: "npm:^1.0.2" - checksum: 10c0/f4f4b905ceb195be90a6ea7f34323bf1c18e3793f18922e3e9a73c684c29eeeeff5175605c3a3a74cc38185fe27758f07efba3dbae812e5c5afbc0d2316b40e4 - languageName: node - linkType: hard - -"is-boolean-object@npm:^1.2.1": - version: 1.2.2 - resolution: "is-boolean-object@npm:1.2.2" - dependencies: - call-bound: "npm:^1.0.3" - has-tostringtag: "npm:^1.0.2" - checksum: 10c0/36ff6baf6bd18b3130186990026f5a95c709345c39cd368468e6c1b6ab52201e9fd26d8e1f4c066357b4938b0f0401e1a5000e08257787c1a02f3a719457001e - languageName: node - linkType: hard - -"is-callable@npm:^1.2.7": - version: 1.2.7 - resolution: "is-callable@npm:1.2.7" - checksum: 10c0/ceebaeb9d92e8adee604076971dd6000d38d6afc40bb843ea8e45c5579b57671c3f3b50d7f04869618242c6cee08d1b67806a8cb8edaaaf7c0748b3720d6066f - languageName: node - linkType: hard - -"is-core-module@npm:^2.13.0, is-core-module@npm:^2.16.0, is-core-module@npm:^2.16.1": - version: 2.16.1 - resolution: "is-core-module@npm:2.16.1" - dependencies: - hasown: "npm:^2.0.2" - checksum: 10c0/898443c14780a577e807618aaae2b6f745c8538eca5c7bc11388a3f2dc6de82b9902bcc7eb74f07be672b11bbe82dd6a6edded44a00cb3d8f933d0459905eedd - languageName: node - linkType: hard - -"is-data-view@npm:^1.0.1, is-data-view@npm:^1.0.2": - version: 1.0.2 - resolution: "is-data-view@npm:1.0.2" - dependencies: - call-bound: "npm:^1.0.2" - get-intrinsic: "npm:^1.2.6" - is-typed-array: "npm:^1.1.13" - checksum: 10c0/ef3548a99d7e7f1370ce21006baca6d40c73e9f15c941f89f0049c79714c873d03b02dae1c64b3f861f55163ecc16da06506c5b8a1d4f16650b3d9351c380153 - languageName: node - linkType: hard - -"is-date-object@npm:^1.0.5, is-date-object@npm:^1.1.0": - version: 1.1.0 - resolution: "is-date-object@npm:1.1.0" - dependencies: - call-bound: "npm:^1.0.2" - has-tostringtag: "npm:^1.0.2" - checksum: 10c0/1a4d199c8e9e9cac5128d32e6626fa7805175af9df015620ac0d5d45854ccf348ba494679d872d37301032e35a54fc7978fba1687e8721b2139aea7870cafa2f - languageName: node - linkType: hard - -"is-extglob@npm:^2.1.1": - version: 2.1.1 - resolution: "is-extglob@npm:2.1.1" - checksum: 10c0/5487da35691fbc339700bbb2730430b07777a3c21b9ebaecb3072512dfd7b4ba78ac2381a87e8d78d20ea08affb3f1971b4af629173a6bf435ff8a4c47747912 - languageName: node - linkType: hard - -"is-finalizationregistry@npm:^1.1.0": - version: 1.1.1 - resolution: "is-finalizationregistry@npm:1.1.1" - dependencies: - call-bound: "npm:^1.0.3" - checksum: 10c0/818dff679b64f19e228a8205a1e2d09989a98e98def3a817f889208cfcbf918d321b251aadf2c05918194803ebd2eb01b14fc9d0b2bea53d984f4137bfca5e97 - languageName: node - linkType: hard - -"is-generator-function@npm:^1.0.10": - version: 1.1.0 - resolution: "is-generator-function@npm:1.1.0" - dependencies: - call-bound: "npm:^1.0.3" - get-proto: "npm:^1.0.0" - has-tostringtag: "npm:^1.0.2" - safe-regex-test: "npm:^1.1.0" - checksum: 10c0/fdfa96c8087bf36fc4cd514b474ba2ff404219a4dd4cfa6cf5426404a1eed259bdcdb98f082a71029a48d01f27733e3436ecc6690129a7ec09cb0434bee03a2a - languageName: node - linkType: hard - -"is-glob@npm:^4.0.0, is-glob@npm:^4.0.1, is-glob@npm:^4.0.3": - version: 4.0.3 - resolution: "is-glob@npm:4.0.3" - dependencies: - is-extglob: "npm:^2.1.1" - checksum: 10c0/17fb4014e22be3bbecea9b2e3a76e9e34ff645466be702f1693e8f1ee1adac84710d0be0bd9f967d6354036fd51ab7c2741d954d6e91dae6bb69714de92c197a - languageName: node - linkType: hard - -"is-ip@npm:^3.1.0": - version: 3.1.0 - resolution: "is-ip@npm:3.1.0" - dependencies: - ip-regex: "npm:^4.0.0" - checksum: 10c0/4cb643c831314b8fc72770c93a795c0d3dde339f36c8430544c36727956027e2cb329641ace73c5951085ecf93ac608c898859d3d4f7b117d405e1e13c703c76 - languageName: node - linkType: hard - -"is-map@npm:^2.0.3": - version: 2.0.3 - resolution: "is-map@npm:2.0.3" - checksum: 10c0/2c4d431b74e00fdda7162cd8e4b763d6f6f217edf97d4f8538b94b8702b150610e2c64961340015fe8df5b1fcee33ccd2e9b62619c4a8a3a155f8de6d6d355fc - languageName: node - linkType: hard - -"is-negative-zero@npm:^2.0.3": - version: 2.0.3 - resolution: "is-negative-zero@npm:2.0.3" - checksum: 10c0/bcdcf6b8b9714063ffcfa9929c575ac69bfdabb8f4574ff557dfc086df2836cf07e3906f5bbc4f2a5c12f8f3ba56af640c843cdfc74da8caed86c7c7d66fd08e - languageName: node - linkType: hard - -"is-number-object@npm:^1.1.1": - version: 1.1.1 - resolution: "is-number-object@npm:1.1.1" - dependencies: - call-bound: "npm:^1.0.3" - has-tostringtag: "npm:^1.0.2" - checksum: 10c0/97b451b41f25135ff021d85c436ff0100d84a039bb87ffd799cbcdbea81ef30c464ced38258cdd34f080be08fc3b076ca1f472086286d2aa43521d6ec6a79f53 - languageName: node - linkType: hard - -"is-number@npm:^7.0.0": - version: 7.0.0 - resolution: "is-number@npm:7.0.0" - checksum: 10c0/b4686d0d3053146095ccd45346461bc8e53b80aeb7671cc52a4de02dbbf7dc0d1d2a986e2fe4ae206984b4d34ef37e8b795ebc4f4295c978373e6575e295d811 - languageName: node - linkType: hard - -"is-plain-object@npm:^2.0.4": - version: 2.0.4 - resolution: "is-plain-object@npm:2.0.4" - dependencies: - isobject: "npm:^3.0.1" - checksum: 10c0/f050fdd5203d9c81e8c4df1b3ff461c4bc64e8b5ca383bcdde46131361d0a678e80bcf00b5257646f6c636197629644d53bd8e2375aea633de09a82d57e942f4 - languageName: node - linkType: hard - -"is-regex@npm:^1.2.1": - version: 1.2.1 - resolution: "is-regex@npm:1.2.1" - dependencies: - call-bound: "npm:^1.0.2" - gopd: "npm:^1.2.0" - has-tostringtag: "npm:^1.0.2" - hasown: "npm:^2.0.2" - checksum: 10c0/1d3715d2b7889932349241680032e85d0b492cfcb045acb75ffc2c3085e8d561184f1f7e84b6f8321935b4aea39bc9c6ba74ed595b57ce4881a51dfdbc214e04 - languageName: node - linkType: hard - -"is-set@npm:^2.0.3": - version: 2.0.3 - resolution: "is-set@npm:2.0.3" - checksum: 10c0/f73732e13f099b2dc879c2a12341cfc22ccaca8dd504e6edae26484bd5707a35d503fba5b4daad530a9b088ced1ae6c9d8200fd92e09b428fe14ea79ce8080b7 - languageName: node - linkType: hard - -"is-shared-array-buffer@npm:^1.0.4": - version: 1.0.4 - resolution: "is-shared-array-buffer@npm:1.0.4" - dependencies: - call-bound: "npm:^1.0.3" - checksum: 10c0/65158c2feb41ff1edd6bbd6fd8403a69861cf273ff36077982b5d4d68e1d59278c71691216a4a64632bd76d4792d4d1d2553901b6666d84ade13bba5ea7bc7db - languageName: node - linkType: hard - -"is-stream@npm:^2.0.0": - version: 2.0.1 - resolution: "is-stream@npm:2.0.1" - checksum: 10c0/7c284241313fc6efc329b8d7f08e16c0efeb6baab1b4cd0ba579eb78e5af1aa5da11e68559896a2067cd6c526bd29241dda4eb1225e627d5aa1a89a76d4635a5 - languageName: node - linkType: hard - -"is-string@npm:^1.1.1": - version: 1.1.1 - resolution: "is-string@npm:1.1.1" - dependencies: - call-bound: "npm:^1.0.3" - has-tostringtag: "npm:^1.0.2" - checksum: 10c0/2f518b4e47886bb81567faba6ffd0d8a8333cf84336e2e78bf160693972e32ad00fe84b0926491cc598dee576fdc55642c92e62d0cbe96bf36f643b6f956f94d - languageName: node - linkType: hard - -"is-symbol@npm:^1.0.4, is-symbol@npm:^1.1.1": - version: 1.1.1 - resolution: "is-symbol@npm:1.1.1" - dependencies: - call-bound: "npm:^1.0.2" - has-symbols: "npm:^1.1.0" - safe-regex-test: "npm:^1.1.0" - checksum: 10c0/f08f3e255c12442e833f75a9e2b84b2d4882fdfd920513cf2a4a2324f0a5b076c8fd913778e3ea5d258d5183e9d92c0cd20e04b03ab3df05316b049b2670af1e - languageName: node - linkType: hard - -"is-typed-array@npm:^1.1.13, is-typed-array@npm:^1.1.14, is-typed-array@npm:^1.1.15": - version: 1.1.15 - resolution: "is-typed-array@npm:1.1.15" - dependencies: - which-typed-array: "npm:^1.1.16" - checksum: 10c0/415511da3669e36e002820584e264997ffe277ff136643a3126cc949197e6ca3334d0f12d084e83b1994af2e9c8141275c741cf2b7da5a2ff62dd0cac26f76c4 - languageName: node - linkType: hard - -"is-weakmap@npm:^2.0.2": - version: 2.0.2 - resolution: "is-weakmap@npm:2.0.2" - checksum: 10c0/443c35bb86d5e6cc5929cd9c75a4024bb0fff9586ed50b092f94e700b89c43a33b186b76dbc6d54f3d3d09ece689ab38dcdc1af6a482cbe79c0f2da0a17f1299 - languageName: node - linkType: hard - -"is-weakref@npm:^1.0.2, is-weakref@npm:^1.1.1": - version: 1.1.1 - resolution: "is-weakref@npm:1.1.1" - dependencies: - call-bound: "npm:^1.0.3" - checksum: 10c0/8e0a9c07b0c780949a100e2cab2b5560a48ecd4c61726923c1a9b77b6ab0aa0046c9e7fb2206042296817045376dee2c8ab1dabe08c7c3dfbf195b01275a085b - languageName: node - linkType: hard - -"is-weakset@npm:^2.0.3": - version: 2.0.4 - resolution: "is-weakset@npm:2.0.4" - dependencies: - call-bound: "npm:^1.0.3" - get-intrinsic: "npm:^1.2.6" - checksum: 10c0/6491eba08acb8dc9532da23cb226b7d0192ede0b88f16199e592e4769db0a077119c1f5d2283d1e0d16d739115f70046e887e477eb0e66cd90e1bb29f28ba647 - languageName: node - linkType: hard - -"isarray@npm:^2.0.5": - version: 2.0.5 - resolution: "isarray@npm:2.0.5" - checksum: 10c0/4199f14a7a13da2177c66c31080008b7124331956f47bca57dd0b6ea9f11687aa25e565a2c7a2b519bc86988d10398e3049a1f5df13c9f6b7664154690ae79fd - languageName: node - linkType: hard - -"isarray@npm:~1.0.0": - version: 1.0.0 - resolution: "isarray@npm:1.0.0" - checksum: 10c0/18b5be6669be53425f0b84098732670ed4e727e3af33bc7f948aac01782110eb9a18b3b329c5323bcdd3acdaae547ee077d3951317e7f133bff7105264b3003d - languageName: node - linkType: hard - -"isexe@npm:^2.0.0": - version: 2.0.0 - resolution: "isexe@npm:2.0.0" - checksum: 10c0/228cfa503fadc2c31596ab06ed6aa82c9976eec2bfd83397e7eaf06d0ccf42cd1dfd6743bf9aeb01aebd4156d009994c5f76ea898d2832c1fe342da923ca457d - languageName: node - linkType: hard - -"isexe@npm:^3.1.1": - version: 3.1.1 - resolution: "isexe@npm:3.1.1" - checksum: 10c0/9ec257654093443eb0a528a9c8cbba9c0ca7616ccb40abd6dde7202734d96bb86e4ac0d764f0f8cd965856aacbff2f4ce23e730dc19dfb41e3b0d865ca6fdcc7 - languageName: node - linkType: hard - -"isobject@npm:^3.0.1": - version: 3.0.1 - resolution: "isobject@npm:3.0.1" - checksum: 10c0/03344f5064a82f099a0cd1a8a407f4c0d20b7b8485e8e816c39f249e9416b06c322e8dec5b842b6bb8a06de0af9cb48e7bc1b5352f0fadc2f0abac033db3d4db - languageName: node - linkType: hard - -"iterator.prototype@npm:^1.1.4": - version: 1.1.5 - resolution: "iterator.prototype@npm:1.1.5" - dependencies: - define-data-property: "npm:^1.1.4" - es-object-atoms: "npm:^1.0.0" - get-intrinsic: "npm:^1.2.6" - get-proto: "npm:^1.0.0" - has-symbols: "npm:^1.1.0" - set-function-name: "npm:^2.0.2" - checksum: 10c0/f7a262808e1b41049ab55f1e9c29af7ec1025a000d243b83edf34ce2416eedd56079b117fa59376bb4a724110690f13aa8427f2ee29a09eec63a7e72367626d0 - languageName: node - linkType: hard - -"jdenticon@npm:^3.3.0": - version: 3.3.0 - resolution: "jdenticon@npm:3.3.0" - dependencies: - canvas-renderer: "npm:~2.2.0" - bin: - jdenticon: bin/jdenticon.js - checksum: 10c0/be2642fe3a9a9013d56ced80ec76a2ae6990363d7c2d92470125fc3d646fe711ba8a8695e49cd5bd696b84574fab4e93f3694414fdb95e150c5ce340c385d9e1 - languageName: node - linkType: hard - -"js-tokens@npm:^3.0.0 || ^4.0.0, js-tokens@npm:^4.0.0": - version: 4.0.0 - resolution: "js-tokens@npm:4.0.0" - checksum: 10c0/e248708d377aa058eacf2037b07ded847790e6de892bbad3dac0abba2e759cb9f121b00099a65195616badcb6eca8d14d975cb3e89eb1cfda644756402c8aeed - languageName: node - linkType: hard - -"js-yaml@npm:^4.1.0": - version: 4.1.0 - resolution: "js-yaml@npm:4.1.0" - dependencies: - argparse: "npm:^2.0.1" - bin: - js-yaml: bin/js-yaml.js - checksum: 10c0/184a24b4eaacfce40ad9074c64fd42ac83cf74d8c8cd137718d456ced75051229e5061b8633c3366b8aada17945a7a356b337828c19da92b51ae62126575018f - languageName: node - linkType: hard - -"jsan@npm:^3.1.14": - version: 3.1.14 - resolution: "jsan@npm:3.1.14" - checksum: 10c0/86b6738e90769d8e717849f7bd9ba4742a4ffb1edfa28354521e0c8f106a3addd15eb79d723b47dc9867c70ed06245e4ee14cb2a10c99b91ea612071688137dc - languageName: node - linkType: hard - -"jsesc@npm:^3.0.2": - version: 3.1.0 - resolution: "jsesc@npm:3.1.0" - bin: - jsesc: bin/jsesc - checksum: 10c0/531779df5ec94f47e462da26b4cbf05eb88a83d9f08aac2ba04206508fc598527a153d08bd462bae82fc78b3eaa1a908e1a4a79f886e9238641c4cdefaf118b1 - languageName: node - linkType: hard - -"json-buffer@npm:3.0.1": - version: 3.0.1 - resolution: "json-buffer@npm:3.0.1" - checksum: 10c0/0d1c91569d9588e7eef2b49b59851f297f3ab93c7b35c7c221e288099322be6b562767d11e4821da500f3219542b9afd2e54c5dc573107c1126ed1080f8e96d7 - languageName: node - linkType: hard - -"json-parse-even-better-errors@npm:^2.3.0": - version: 2.3.1 - resolution: "json-parse-even-better-errors@npm:2.3.1" - checksum: 10c0/140932564c8f0b88455432e0f33c4cb4086b8868e37524e07e723f4eaedb9425bdc2bafd71bd1d9765bd15fd1e2d126972bc83990f55c467168c228c24d665f3 - languageName: node - linkType: hard - -"json-schema-traverse@npm:^0.4.1": - version: 0.4.1 - resolution: "json-schema-traverse@npm:0.4.1" - checksum: 10c0/108fa90d4cc6f08243aedc6da16c408daf81793bf903e9fd5ab21983cda433d5d2da49e40711da016289465ec2e62e0324dcdfbc06275a607fe3233fde4942ce - languageName: node - linkType: hard - -"json-stable-stringify-without-jsonify@npm:^1.0.1": - version: 1.0.1 - resolution: "json-stable-stringify-without-jsonify@npm:1.0.1" - checksum: 10c0/cb168b61fd4de83e58d09aaa6425ef71001bae30d260e2c57e7d09a5fd82223e2f22a042dedaab8db23b7d9ae46854b08bb1f91675a8be11c5cffebef5fb66a5 - languageName: node - linkType: hard - -"json5@npm:^1.0.2": - version: 1.0.2 - resolution: "json5@npm:1.0.2" - dependencies: - minimist: "npm:^1.2.0" - bin: - json5: lib/cli.js - checksum: 10c0/9ee316bf21f000b00752e6c2a3b79ecf5324515a5c60ee88983a1910a45426b643a4f3461657586e8aeca87aaf96f0a519b0516d2ae527a6c3e7eed80f68717f - languageName: node - linkType: hard - -"json5@npm:^2.2.3": - version: 2.2.3 - resolution: "json5@npm:2.2.3" - bin: - json5: lib/cli.js - checksum: 10c0/5a04eed94810fa55c5ea138b2f7a5c12b97c3750bc63d11e511dcecbfef758003861522a070c2272764ee0f4e3e323862f386945aeb5b85b87ee43f084ba586c - languageName: node - linkType: hard - -"jsonwebtoken@npm:^9.0.0": - version: 9.0.2 - resolution: "jsonwebtoken@npm:9.0.2" - dependencies: - jws: "npm:^3.2.2" - lodash.includes: "npm:^4.3.0" - lodash.isboolean: "npm:^3.0.3" - lodash.isinteger: "npm:^4.0.4" - lodash.isnumber: "npm:^3.0.3" - lodash.isplainobject: "npm:^4.0.6" - lodash.isstring: "npm:^4.0.1" - lodash.once: "npm:^4.0.0" - ms: "npm:^2.1.1" - semver: "npm:^7.5.4" - checksum: 10c0/d287a29814895e866db2e5a0209ce730cbc158441a0e5a70d5e940eb0d28ab7498c6bf45029cc8b479639bca94056e9a7f254e2cdb92a2f5750c7f358657a131 - languageName: node - linkType: hard - -"jsx-ast-utils@npm:^2.4.1 || ^3.0.0": - version: 3.3.5 - resolution: "jsx-ast-utils@npm:3.3.5" - dependencies: - array-includes: "npm:^3.1.6" - array.prototype.flat: "npm:^1.3.1" - object.assign: "npm:^4.1.4" - object.values: "npm:^1.1.6" - checksum: 10c0/a32679e9cb55469cb6d8bbc863f7d631b2c98b7fc7bf172629261751a6e7bc8da6ae374ddb74d5fbd8b06cf0eb4572287b259813d92b36e384024ed35e4c13e1 - languageName: node - linkType: hard - -"jwa@npm:^1.4.1": - version: 1.4.2 - resolution: "jwa@npm:1.4.2" - dependencies: - buffer-equal-constant-time: "npm:^1.0.1" - ecdsa-sig-formatter: "npm:1.0.11" - safe-buffer: "npm:^5.0.1" - checksum: 10c0/210a544a42ca22203e8fc538835205155ba3af6a027753109f9258bdead33086bac3c25295af48ac1981f87f9c5f941bc8f70303670f54ea7dcaafb53993d92c - languageName: node - linkType: hard - -"jws@npm:^3.2.2": - version: 3.2.2 - resolution: "jws@npm:3.2.2" - dependencies: - jwa: "npm:^1.4.1" - safe-buffer: "npm:^5.0.1" - checksum: 10c0/e770704533d92df358adad7d1261fdecad4d7b66fa153ba80d047e03ca0f1f73007ce5ed3fbc04d2eba09ba6e7e6e645f351e08e5ab51614df1b0aa4f384dfff - languageName: node - linkType: hard - -"keyv@npm:^4.5.4": - version: 4.5.4 - resolution: "keyv@npm:4.5.4" - dependencies: - json-buffer: "npm:3.0.1" - checksum: 10c0/aa52f3c5e18e16bb6324876bb8b59dd02acf782a4b789c7b2ae21107fab95fab3890ed448d4f8dba80ce05391eeac4bfabb4f02a20221342982f806fa2cf271e - languageName: node - linkType: hard - -"kind-of@npm:^6.0.2": - version: 6.0.3 - resolution: "kind-of@npm:6.0.3" - checksum: 10c0/61cdff9623dabf3568b6445e93e31376bee1cdb93f8ba7033d86022c2a9b1791a1d9510e026e6465ebd701a6dd2f7b0808483ad8838341ac52f003f512e0b4c4 - languageName: node - linkType: hard - -"levn@npm:^0.4.1": - version: 0.4.1 - resolution: "levn@npm:0.4.1" - dependencies: - prelude-ls: "npm:^1.2.1" - type-check: "npm:~0.4.0" - checksum: 10c0/effb03cad7c89dfa5bd4f6989364bfc79994c2042ec5966cb9b95990e2edee5cd8969ddf42616a0373ac49fac1403437deaf6e9050fbbaa3546093a59b9ac94e - languageName: node - linkType: hard - -"lines-and-columns@npm:^1.1.6": - version: 1.2.4 - resolution: "lines-and-columns@npm:1.2.4" - checksum: 10c0/3da6ee62d4cd9f03f5dc90b4df2540fb85b352081bee77fe4bbcd12c9000ead7f35e0a38b8d09a9bb99b13223446dd8689ff3c4959807620726d788701a83d2d - languageName: node - linkType: hard - -"linked-list@npm:^2.1.0": - version: 2.1.0 - resolution: "linked-list@npm:2.1.0" - checksum: 10c0/d039f664f847022be908460a06a7f63792aadcdfd3061f999726a92d89bf3a10688e7dfb98236f65d9980e2a175839028a374070e48e0676ab9e66122fca7063 - languageName: node - linkType: hard - -"locate-path@npm:^6.0.0": - version: 6.0.0 - resolution: "locate-path@npm:6.0.0" - dependencies: - p-locate: "npm:^5.0.0" - checksum: 10c0/d3972ab70dfe58ce620e64265f90162d247e87159b6126b01314dd67be43d50e96a50b517bce2d9452a79409c7614054c277b5232377de50416564a77ac7aad3 - languageName: node - linkType: hard - -"lodash.includes@npm:^4.3.0": - version: 4.3.0 - resolution: "lodash.includes@npm:4.3.0" - checksum: 10c0/7ca498b9b75bf602d04e48c0adb842dfc7d90f77bcb2a91a2b2be34a723ad24bc1c8b3683ec6b2552a90f216c723cdea530ddb11a3320e08fa38265703978f4b - languageName: node - linkType: hard - -"lodash.isboolean@npm:^3.0.3": - version: 3.0.3 - resolution: "lodash.isboolean@npm:3.0.3" - checksum: 10c0/0aac604c1ef7e72f9a6b798e5b676606042401dd58e49f051df3cc1e3adb497b3d7695635a5cbec4ae5f66456b951fdabe7d6b387055f13267cde521f10ec7f7 - languageName: node - linkType: hard - -"lodash.isinteger@npm:^4.0.4": - version: 4.0.4 - resolution: "lodash.isinteger@npm:4.0.4" - checksum: 10c0/4c3e023a2373bf65bf366d3b8605b97ec830bca702a926939bcaa53f8e02789b6a176e7f166b082f9365bfec4121bfeb52e86e9040cb8d450e64c858583f61b7 - languageName: node - linkType: hard - -"lodash.isnumber@npm:^3.0.3": - version: 3.0.3 - resolution: "lodash.isnumber@npm:3.0.3" - checksum: 10c0/2d01530513a1ee4f72dd79528444db4e6360588adcb0e2ff663db2b3f642d4bb3d687051ae1115751ca9082db4fdef675160071226ca6bbf5f0c123dbf0aa12d - languageName: node - linkType: hard - -"lodash.isplainobject@npm:^4.0.6": - version: 4.0.6 - resolution: "lodash.isplainobject@npm:4.0.6" - checksum: 10c0/afd70b5c450d1e09f32a737bed06ff85b873ecd3d3d3400458725283e3f2e0bb6bf48e67dbe7a309eb371a822b16a26cca4a63c8c52db3fc7dc9d5f9dd324cbb - languageName: node - linkType: hard - -"lodash.isstring@npm:^4.0.1": - version: 4.0.1 - resolution: "lodash.isstring@npm:4.0.1" - checksum: 10c0/09eaf980a283f9eef58ef95b30ec7fee61df4d6bf4aba3b5f096869cc58f24c9da17900febc8ffd67819b4e29de29793190e88dc96983db92d84c95fa85d1c92 - languageName: node - linkType: hard - -"lodash.merge@npm:^4.6.2": - version: 4.6.2 - resolution: "lodash.merge@npm:4.6.2" - checksum: 10c0/402fa16a1edd7538de5b5903a90228aa48eb5533986ba7fa26606a49db2572bf414ff73a2c9f5d5fd36b31c46a5d5c7e1527749c07cbcf965ccff5fbdf32c506 - languageName: node - linkType: hard - -"lodash.once@npm:^4.0.0": - version: 4.1.1 - resolution: "lodash.once@npm:4.1.1" - checksum: 10c0/46a9a0a66c45dd812fcc016e46605d85ad599fe87d71a02f6736220554b52ffbe82e79a483ad40f52a8a95755b0d1077fba259da8bfb6694a7abbf4a48f1fc04 - languageName: node - linkType: hard - -"lodash@npm:^4.17.21": - version: 4.17.21 - resolution: "lodash@npm:4.17.21" - checksum: 10c0/d8cbea072bb08655bb4c989da418994b073a608dffa608b09ac04b43a791b12aeae7cd7ad919aa4c925f33b48490b5cfe6c1f71d827956071dae2e7bb3a6b74c - languageName: node - linkType: hard - -"loose-envify@npm:^1.4.0": - version: 1.4.0 - resolution: "loose-envify@npm:1.4.0" - dependencies: - js-tokens: "npm:^3.0.0 || ^4.0.0" - bin: - loose-envify: cli.js - checksum: 10c0/655d110220983c1a4b9c0c679a2e8016d4b67f6e9c7b5435ff5979ecdb20d0813f4dec0a08674fcbdd4846a3f07edbb50a36811fd37930b94aaa0d9daceb017e - languageName: node - linkType: hard - -"loupe@npm:^3.1.0, loupe@npm:^3.1.2": - version: 3.2.1 - resolution: "loupe@npm:3.2.1" - checksum: 10c0/910c872cba291309664c2d094368d31a68907b6f5913e989d301b5c25f30e97d76d77f23ab3bf3b46d0f601ff0b6af8810c10c31b91d2c6b2f132809ca2cc705 - languageName: node - linkType: hard - -"lru-cache@npm:^11.0.0, lru-cache@npm:^11.1.0, lru-cache@npm:^11.2.1": - version: 11.2.4 - resolution: "lru-cache@npm:11.2.4" - checksum: 10c0/4a24f9b17537619f9144d7b8e42cd5a225efdfd7076ebe7b5e7dc02b860a818455201e67fbf000765233fe7e339d3c8229fc815e9b58ee6ede511e07608c19b2 - languageName: node - linkType: hard - -"lru-cache@npm:^5.1.1": - version: 5.1.1 - resolution: "lru-cache@npm:5.1.1" - dependencies: - yallist: "npm:^3.0.2" - checksum: 10c0/89b2ef2ef45f543011e38737b8a8622a2f8998cddf0e5437174ef8f1f70a8b9d14a918ab3e232cb3ba343b7abddffa667f0b59075b2b80e6b4d63c3de6127482 - languageName: node - linkType: hard - -"magic-string@npm:^0.30.12": - version: 0.30.18 - resolution: "magic-string@npm:0.30.18" - dependencies: - "@jridgewell/sourcemap-codec": "npm:^1.5.5" - checksum: 10c0/80fba01e13ce1f5c474a0498a5aa462fa158eb56567310747089a0033e432d83a2021ee2c109ac116010cd9dcf90a5231d89fbe3858165f73c00a50a74dbefcd - languageName: node - linkType: hard - -"make-fetch-happen@npm:^15.0.0": - version: 15.0.3 - resolution: "make-fetch-happen@npm:15.0.3" - dependencies: - "@npmcli/agent": "npm:^4.0.0" - cacache: "npm:^20.0.1" - http-cache-semantics: "npm:^4.1.1" - minipass: "npm:^7.0.2" - minipass-fetch: "npm:^5.0.0" - minipass-flush: "npm:^1.0.5" - minipass-pipeline: "npm:^1.2.4" - negotiator: "npm:^1.0.0" - proc-log: "npm:^6.0.0" - promise-retry: "npm:^2.0.1" - ssri: "npm:^13.0.0" - checksum: 10c0/525f74915660be60b616bcbd267c4a5b59481b073ba125e45c9c3a041bb1a47a2bd0ae79d028eb6f5f95bf9851a4158423f5068539c3093621abb64027e8e461 - languageName: node - linkType: hard - -"math-intrinsics@npm:^1.1.0": - version: 1.1.0 - resolution: "math-intrinsics@npm:1.1.0" - checksum: 10c0/7579ff94e899e2f76ab64491d76cf606274c874d8f2af4a442c016bd85688927fcfca157ba6bf74b08e9439dc010b248ce05b96cc7c126a354c3bae7fcb48b7f - languageName: node - linkType: hard - -"merge-stream@npm:^2.0.0": - version: 2.0.0 - resolution: "merge-stream@npm:2.0.0" - checksum: 10c0/867fdbb30a6d58b011449b8885601ec1690c3e41c759ecd5a9d609094f7aed0096c37823ff4a7190ef0b8f22cc86beb7049196ff68c016e3b3c671d0dac91ce5 - languageName: node - linkType: hard - -"merge2@npm:^1.3.0": - version: 1.4.1 - resolution: "merge2@npm:1.4.1" - checksum: 10c0/254a8a4605b58f450308fc474c82ac9a094848081bf4c06778200207820e5193726dc563a0d2c16468810516a5c97d9d3ea0ca6585d23c58ccfff2403e8dbbeb - languageName: node - linkType: hard - -"micromatch@npm:^4.0.8": - version: 4.0.8 - resolution: "micromatch@npm:4.0.8" - dependencies: - braces: "npm:^3.0.3" - picomatch: "npm:^2.3.1" - checksum: 10c0/166fa6eb926b9553f32ef81f5f531d27b4ce7da60e5baf8c021d043b27a388fb95e46a8038d5045877881e673f8134122b59624d5cecbd16eb50a42e7a6b5ca8 - languageName: node - linkType: hard - -"mimic-fn@npm:^2.1.0": - version: 2.1.0 - resolution: "mimic-fn@npm:2.1.0" - checksum: 10c0/b26f5479d7ec6cc2bce275a08f146cf78f5e7b661b18114e2506dd91ec7ec47e7a25bf4360e5438094db0560bcc868079fb3b1fb3892b833c1ecbf63f80c95a4 - languageName: node - linkType: hard - -"minimatch@npm:^10.1.1": - version: 10.1.1 - resolution: "minimatch@npm:10.1.1" - dependencies: - "@isaacs/brace-expansion": "npm:^5.0.0" - checksum: 10c0/c85d44821c71973d636091fddbfbffe62370f5ee3caf0241c5b60c18cd289e916200acb2361b7e987558cd06896d153e25d505db9fc1e43e6b4b6752e2702902 - languageName: node - linkType: hard - -"minimatch@npm:^3.1.2": - version: 3.1.2 - resolution: "minimatch@npm:3.1.2" - dependencies: - brace-expansion: "npm:^1.1.7" - checksum: 10c0/0262810a8fc2e72cca45d6fd86bd349eee435eb95ac6aa45c9ea2180e7ee875ef44c32b55b5973ceabe95ea12682f6e3725cbb63d7a2d1da3ae1163c8b210311 - languageName: node - linkType: hard - -"minimatch@npm:^5.1.1": - version: 5.1.6 - resolution: "minimatch@npm:5.1.6" - dependencies: - brace-expansion: "npm:^2.0.1" - checksum: 10c0/3defdfd230914f22a8da203747c42ee3c405c39d4d37ffda284dac5e45b7e1f6c49aa8be606509002898e73091ff2a3bbfc59c2c6c71d4660609f63aa92f98e3 - languageName: node - linkType: hard - -"minimatch@npm:^9.0.4": - version: 9.0.5 - resolution: "minimatch@npm:9.0.5" - dependencies: - brace-expansion: "npm:^2.0.1" - checksum: 10c0/de96cf5e35bdf0eab3e2c853522f98ffbe9a36c37797778d2665231ec1f20a9447a7e567cb640901f89e4daaa95ae5d70c65a9e8aa2bb0019b6facbc3c0575ed - languageName: node - linkType: hard - -"minimist@npm:^1.2.0, minimist@npm:^1.2.6": - version: 1.2.8 - resolution: "minimist@npm:1.2.8" - checksum: 10c0/19d3fcdca050087b84c2029841a093691a91259a47def2f18222f41e7645a0b7c44ef4b40e88a1e58a40c84d2ef0ee6047c55594d298146d0eb3f6b737c20ce6 - languageName: node - linkType: hard - -"minipass-collect@npm:^2.0.1": - version: 2.0.1 - resolution: "minipass-collect@npm:2.0.1" - dependencies: - minipass: "npm:^7.0.3" - checksum: 10c0/5167e73f62bb74cc5019594709c77e6a742051a647fe9499abf03c71dca75515b7959d67a764bdc4f8b361cf897fbf25e2d9869ee039203ed45240f48b9aa06e - languageName: node - linkType: hard - -"minipass-fetch@npm:^5.0.0": - version: 5.0.0 - resolution: "minipass-fetch@npm:5.0.0" - dependencies: - encoding: "npm:^0.1.13" - minipass: "npm:^7.0.3" - minipass-sized: "npm:^1.0.3" - minizlib: "npm:^3.0.1" - dependenciesMeta: - encoding: - optional: true - checksum: 10c0/9443aab5feab190972f84b64116e54e58dd87a58e62399cae0a4a7461b80568281039b7c3a38ba96453431ebc799d1e26999e548540156216729a4967cd5ef06 - languageName: node - linkType: hard - -"minipass-flush@npm:^1.0.5": - version: 1.0.5 - resolution: "minipass-flush@npm:1.0.5" - dependencies: - minipass: "npm:^3.0.0" - checksum: 10c0/2a51b63feb799d2bb34669205eee7c0eaf9dce01883261a5b77410c9408aa447e478efd191b4de6fc1101e796ff5892f8443ef20d9544385819093dbb32d36bd - languageName: node - linkType: hard - -"minipass-pipeline@npm:^1.2.4": - version: 1.2.4 - resolution: "minipass-pipeline@npm:1.2.4" - dependencies: - minipass: "npm:^3.0.0" - checksum: 10c0/cbda57cea20b140b797505dc2cac71581a70b3247b84480c1fed5ca5ba46c25ecc25f68bfc9e6dcb1a6e9017dab5c7ada5eab73ad4f0a49d84e35093e0c643f2 - languageName: node - linkType: hard - -"minipass-sized@npm:^1.0.3": - version: 1.0.3 - resolution: "minipass-sized@npm:1.0.3" - dependencies: - minipass: "npm:^3.0.0" - checksum: 10c0/298f124753efdc745cfe0f2bdfdd81ba25b9f4e753ca4a2066eb17c821f25d48acea607dfc997633ee5bf7b6dfffb4eee4f2051eb168663f0b99fad2fa4829cb - languageName: node - linkType: hard - -"minipass@npm:^3.0.0": - version: 3.3.6 - resolution: "minipass@npm:3.3.6" - dependencies: - yallist: "npm:^4.0.0" - checksum: 10c0/a114746943afa1dbbca8249e706d1d38b85ed1298b530f5808ce51f8e9e941962e2a5ad2e00eae7dd21d8a4aae6586a66d4216d1a259385e9d0358f0c1eba16c - languageName: node - linkType: hard - -"minipass@npm:^7.0.2, minipass@npm:^7.0.3, minipass@npm:^7.0.4, minipass@npm:^7.1.2": - version: 7.1.2 - resolution: "minipass@npm:7.1.2" - checksum: 10c0/b0fd20bb9fb56e5fa9a8bfac539e8915ae07430a619e4b86ff71f5fc757ef3924b23b2c4230393af1eda647ed3d75739e4e0acb250a6b1eb277cf7f8fe449557 - languageName: node - linkType: hard - -"minizlib@npm:^3.0.1, minizlib@npm:^3.1.0": - version: 3.1.0 - resolution: "minizlib@npm:3.1.0" - dependencies: - minipass: "npm:^7.1.2" - checksum: 10c0/5aad75ab0090b8266069c9aabe582c021ae53eb33c6c691054a13a45db3b4f91a7fb1bd79151e6b4e9e9a86727b522527c0a06ec7d45206b745d54cd3097bcec - languageName: node - linkType: hard - -"ms@npm:^2.1.1, ms@npm:^2.1.3": - version: 2.1.3 - resolution: "ms@npm:2.1.3" - checksum: 10c0/d924b57e7312b3b63ad21fc5b3dc0af5e78d61a1fc7cfb5457edaf26326bf62be5307cc87ffb6862ef1c2b33b0233cdb5d4f01c4c958cc0d660948b65a287a48 - languageName: node - linkType: hard - -"multiaddr@npm:^10.0.1": - version: 10.0.1 - resolution: "multiaddr@npm:10.0.1" - dependencies: - dns-over-http-resolver: "npm:^1.2.3" - err-code: "npm:^3.0.1" - is-ip: "npm:^3.1.0" - multiformats: "npm:^9.4.5" - uint8arrays: "npm:^3.0.0" - varint: "npm:^6.0.0" - checksum: 10c0/948d0c69d75992d754fd36154db4d4a435b977c56a617183a9d8c2075081e2cfcacda3bedb72bbd6759bb329956a45b216e804829e708af8d2f780f8152a4bf6 - languageName: node - linkType: hard - -"multiformats@npm:^9.4.2, multiformats@npm:^9.4.5": - version: 9.9.0 - resolution: "multiformats@npm:9.9.0" - checksum: 10c0/1fdb34fd2fb085142665e8bd402570659b50a5fae5994027e1df3add9e1ce1283ed1e0c2584a5c63ac0a58e871b8ee9665c4a99ca36ce71032617449d48aa975 - languageName: node - linkType: hard - -"nanoid@npm:^3.3.11": - version: 3.3.11 - resolution: "nanoid@npm:3.3.11" - bin: - nanoid: bin/nanoid.cjs - checksum: 10c0/40e7f70b3d15f725ca072dfc4f74e81fcf1fbb02e491cf58ac0c79093adc9b0a73b152bcde57df4b79cd097e13023d7504acb38404a4da7bc1cd8e887b82fe0b - languageName: node - linkType: hard - -"nanoid@npm:^5.1.2": - version: 5.1.5 - resolution: "nanoid@npm:5.1.5" - bin: - nanoid: bin/nanoid.js - checksum: 10c0/e6004f1ad6c7123eeb037062c4441d44982037dc043aabb162457ef6986e99964ba98c63c975f96c547403beb0bf95bc537bd7bf9a09baf381656acdc2975c3c - languageName: node - linkType: hard - -"native-fetch@npm:^3.0.0": - version: 3.0.0 - resolution: "native-fetch@npm:3.0.0" - peerDependencies: - node-fetch: "*" - checksum: 10c0/737cdd209dd366df8b748dabac39340089d57a2bcc460ffc029ec145f30aeffea0c6a6f177013069d6f7f04ffc8c3e39cfb8e3825e7071a373c4f86b187ae1b5 - languageName: node - linkType: hard - -"natural-compare@npm:^1.4.0": - version: 1.4.0 - resolution: "natural-compare@npm:1.4.0" - checksum: 10c0/f5f9a7974bfb28a91afafa254b197f0f22c684d4a1731763dda960d2c8e375b36c7d690e0d9dc8fba774c537af14a7e979129bca23d88d052fbeb9466955e447 - languageName: node - linkType: hard - -"negotiator@npm:^1.0.0": - version: 1.0.0 - resolution: "negotiator@npm:1.0.0" - checksum: 10c0/4c559dd52669ea48e1914f9d634227c561221dd54734070791f999c52ed0ff36e437b2e07d5c1f6e32909fc625fe46491c16e4a8f0572567d4dd15c3a4fda04b - languageName: node - linkType: hard - -"node-gyp@npm:latest": - version: 12.1.0 - resolution: "node-gyp@npm:12.1.0" - dependencies: - env-paths: "npm:^2.2.0" - exponential-backoff: "npm:^3.1.1" - graceful-fs: "npm:^4.2.6" - make-fetch-happen: "npm:^15.0.0" - nopt: "npm:^9.0.0" - proc-log: "npm:^6.0.0" - semver: "npm:^7.3.5" - tar: "npm:^7.5.2" - tinyglobby: "npm:^0.2.12" - which: "npm:^6.0.0" - bin: - node-gyp: bin/node-gyp.js - checksum: 10c0/f43efea8aaf0beb6b2f6184e533edad779b2ae38062953e21951f46221dd104006cc574154f2ad4a135467a5aae92c49e84ef289311a82e08481c5df0e8dc495 - languageName: node - linkType: hard - -"node-releases@npm:^2.0.19": - version: 2.0.19 - resolution: "node-releases@npm:2.0.19" - checksum: 10c0/52a0dbd25ccf545892670d1551690fe0facb6a471e15f2cfa1b20142a5b255b3aa254af5f59d6ecb69c2bec7390bc643c43aa63b13bf5e64b6075952e716b1aa - languageName: node - linkType: hard - -"nopt@npm:^9.0.0": - version: 9.0.0 - resolution: "nopt@npm:9.0.0" - dependencies: - abbrev: "npm:^4.0.0" - bin: - nopt: bin/nopt.js - checksum: 10c0/1822eb6f9b020ef6f7a7516d7b64a8036e09666ea55ac40416c36e4b2b343122c3cff0e2f085675f53de1d2db99a2a89a60ccea1d120bcd6a5347bf6ceb4a7fd - languageName: node - linkType: hard - -"notistack@npm:^3.0.1": - version: 3.0.2 - resolution: "notistack@npm:3.0.2" - dependencies: - clsx: "npm:^1.1.0" - goober: "npm:^2.0.33" - peerDependencies: - react: ^17.0.0 || ^18.0.0 || ^19.0.0 - react-dom: ^17.0.0 || ^18.0.0 || ^19.0.0 - checksum: 10c0/82a0270ee1b9e70bc1dfad4d7e1d2bbb6dc08ac887b920986dcfa58cc2aa975bd48b71f7cf3afc2e11badd142ba9ade5f708c858ee2080b9d4cd950b80c43811 - languageName: node - linkType: hard - -"npm-run-path@npm:^4.0.1": - version: 4.0.1 - resolution: "npm-run-path@npm:4.0.1" - dependencies: - path-key: "npm:^3.0.0" - checksum: 10c0/6f9353a95288f8455cf64cbeb707b28826a7f29690244c1e4bb61ec573256e021b6ad6651b394eb1ccfd00d6ec50147253aba2c5fe58a57ceb111fad62c519ac - languageName: node - linkType: hard - -"object-assign@npm:^4.1.1": - version: 4.1.1 - resolution: "object-assign@npm:4.1.1" - checksum: 10c0/1f4df9945120325d041ccf7b86f31e8bcc14e73d29171e37a7903050e96b81323784ec59f93f102ec635bcf6fa8034ba3ea0a8c7e69fa202b87ae3b6cec5a414 - languageName: node - linkType: hard - -"object-inspect@npm:^1.13.3, object-inspect@npm:^1.13.4": - version: 1.13.4 - resolution: "object-inspect@npm:1.13.4" - checksum: 10c0/d7f8711e803b96ea3191c745d6f8056ce1f2496e530e6a19a0e92d89b0fa3c76d910c31f0aa270432db6bd3b2f85500a376a83aaba849a8d518c8845b3211692 - languageName: node - linkType: hard - -"object-keys@npm:^1.1.1": - version: 1.1.1 - resolution: "object-keys@npm:1.1.1" - checksum: 10c0/b11f7ccdbc6d406d1f186cdadb9d54738e347b2692a14439ca5ac70c225fa6db46db809711b78589866d47b25fc3e8dee0b4c722ac751e11180f9380e3d8601d - languageName: node - linkType: hard - -"object.assign@npm:^4.1.4, object.assign@npm:^4.1.7": - version: 4.1.7 - resolution: "object.assign@npm:4.1.7" - dependencies: - call-bind: "npm:^1.0.8" - call-bound: "npm:^1.0.3" - define-properties: "npm:^1.2.1" - es-object-atoms: "npm:^1.0.0" - has-symbols: "npm:^1.1.0" - object-keys: "npm:^1.1.1" - checksum: 10c0/3b2732bd860567ea2579d1567525168de925a8d852638612846bd8082b3a1602b7b89b67b09913cbb5b9bd6e95923b2ae73580baa9d99cb4e990564e8cbf5ddc - languageName: node - linkType: hard - -"object.entries@npm:^1.1.9": - version: 1.1.9 - resolution: "object.entries@npm:1.1.9" - dependencies: - call-bind: "npm:^1.0.8" - call-bound: "npm:^1.0.4" - define-properties: "npm:^1.2.1" - es-object-atoms: "npm:^1.1.1" - checksum: 10c0/d4b8c1e586650407da03370845f029aa14076caca4e4d4afadbc69cfb5b78035fd3ee7be417141abdb0258fa142e59b11923b4c44d8b1255b28f5ffcc50da7db - languageName: node - linkType: hard - -"object.fromentries@npm:^2.0.8": - version: 2.0.8 - resolution: "object.fromentries@npm:2.0.8" - dependencies: - call-bind: "npm:^1.0.7" - define-properties: "npm:^1.2.1" - es-abstract: "npm:^1.23.2" - es-object-atoms: "npm:^1.0.0" - checksum: 10c0/cd4327e6c3369cfa805deb4cbbe919bfb7d3aeebf0bcaba291bb568ea7169f8f8cdbcabe2f00b40db0c20cd20f08e11b5f3a5a36fb7dd3fe04850c50db3bf83b - languageName: node - linkType: hard - -"object.groupby@npm:^1.0.3": - version: 1.0.3 - resolution: "object.groupby@npm:1.0.3" - dependencies: - call-bind: "npm:^1.0.7" - define-properties: "npm:^1.2.1" - es-abstract: "npm:^1.23.2" - checksum: 10c0/60d0455c85c736fbfeda0217d1a77525956f76f7b2495edeca9e9bbf8168a45783199e77b894d30638837c654d0cc410e0e02cbfcf445bc8de71c3da1ede6a9c - languageName: node - linkType: hard - -"object.values@npm:^1.1.6, object.values@npm:^1.2.1": - version: 1.2.1 - resolution: "object.values@npm:1.2.1" - dependencies: - call-bind: "npm:^1.0.8" - call-bound: "npm:^1.0.3" - define-properties: "npm:^1.2.1" - es-object-atoms: "npm:^1.0.0" - checksum: 10c0/3c47814fdc64842ae3d5a74bc9d06bdd8d21563c04d9939bf6716a9c00596a4ebc342552f8934013d1ec991c74e3671b26710a0c51815f0b603795605ab6b2c9 - languageName: node - linkType: hard - -"onetime@npm:^5.1.2": - version: 5.1.2 - resolution: "onetime@npm:5.1.2" - dependencies: - mimic-fn: "npm:^2.1.0" - checksum: 10c0/ffcef6fbb2692c3c40749f31ea2e22677a876daea92959b8a80b521d95cca7a668c884d8b2045d1d8ee7d56796aa405c405462af112a1477594cc63531baeb8f - languageName: node - linkType: hard - -"optionator@npm:^0.9.3": - version: 0.9.4 - resolution: "optionator@npm:0.9.4" - dependencies: - deep-is: "npm:^0.1.3" - fast-levenshtein: "npm:^2.0.6" - levn: "npm:^0.4.1" - prelude-ls: "npm:^1.2.1" - type-check: "npm:^0.4.0" - word-wrap: "npm:^1.2.5" - checksum: 10c0/4afb687a059ee65b61df74dfe87d8d6815cd6883cb8b3d5883a910df72d0f5d029821f37025e4bccf4048873dbdb09acc6d303d27b8f76b1a80dd5a7d5334675 - languageName: node - linkType: hard - -"own-keys@npm:^1.0.1": - version: 1.0.1 - resolution: "own-keys@npm:1.0.1" - dependencies: - get-intrinsic: "npm:^1.2.6" - object-keys: "npm:^1.1.1" - safe-push-apply: "npm:^1.0.0" - checksum: 10c0/6dfeb3455bff92ec3f16a982d4e3e65676345f6902d9f5ded1d8265a6318d0200ce461956d6d1c70053c7fe9f9fe65e552faac03f8140d37ef0fdd108e67013a - languageName: node - linkType: hard - -"p-event@npm:^4.2.0": - version: 4.2.0 - resolution: "p-event@npm:4.2.0" - dependencies: - p-timeout: "npm:^3.1.0" - checksum: 10c0/f1b6a2fb13d47f2a8afc00150da5ece0d28940ce3d8fa562873e091d3337d298e78fee9cb18b768598ff1d11df608b2ae23868309ff6405b864a2451ccd6d25a - languageName: node - linkType: hard - -"p-finally@npm:^1.0.0": - version: 1.0.0 - resolution: "p-finally@npm:1.0.0" - checksum: 10c0/6b8552339a71fe7bd424d01d8451eea92d379a711fc62f6b2fe64cad8a472c7259a236c9a22b4733abca0b5666ad503cb497792a0478c5af31ded793d00937e7 - languageName: node - linkType: hard - -"p-limit@npm:^3.0.2": - version: 3.1.0 - resolution: "p-limit@npm:3.1.0" - dependencies: - yocto-queue: "npm:^0.1.0" - checksum: 10c0/9db675949dbdc9c3763c89e748d0ef8bdad0afbb24d49ceaf4c46c02c77d30db4e0652ed36d0a0a7a95154335fab810d95c86153105bb73b3a90448e2bb14e1a - languageName: node - linkType: hard - -"p-locate@npm:^5.0.0": - version: 5.0.0 - resolution: "p-locate@npm:5.0.0" - dependencies: - p-limit: "npm:^3.0.2" - checksum: 10c0/2290d627ab7903b8b70d11d384fee714b797f6040d9278932754a6860845c4d3190603a0772a663c8cb5a7b21d1b16acb3a6487ebcafa9773094edc3dfe6009a - languageName: node - linkType: hard - -"p-map@npm:^7.0.2": - version: 7.0.4 - resolution: "p-map@npm:7.0.4" - checksum: 10c0/a5030935d3cb2919d7e89454d1ce82141e6f9955413658b8c9403cfe379283770ed3048146b44cde168aa9e8c716505f196d5689db0ae3ce9a71521a2fef3abd - languageName: node - linkType: hard - -"p-timeout@npm:^3.1.0": - version: 3.2.0 - resolution: "p-timeout@npm:3.2.0" - dependencies: - p-finally: "npm:^1.0.0" - checksum: 10c0/524b393711a6ba8e1d48137c5924749f29c93d70b671e6db761afa784726572ca06149c715632da8f70c090073afb2af1c05730303f915604fd38ee207b70a61 - languageName: node - linkType: hard - -"parent-module@npm:^1.0.0": - version: 1.0.1 - resolution: "parent-module@npm:1.0.1" - dependencies: - callsites: "npm:^3.0.0" - checksum: 10c0/c63d6e80000d4babd11978e0d3fee386ca7752a02b035fd2435960ffaa7219dc42146f07069fb65e6e8bf1caef89daf9af7535a39bddf354d78bf50d8294f556 - languageName: node - linkType: hard - -"parse-json@npm:^5.0.0": - version: 5.2.0 - resolution: "parse-json@npm:5.2.0" - dependencies: - "@babel/code-frame": "npm:^7.0.0" - error-ex: "npm:^1.3.1" - json-parse-even-better-errors: "npm:^2.3.0" - lines-and-columns: "npm:^1.1.6" - checksum: 10c0/77947f2253005be7a12d858aedbafa09c9ae39eb4863adf330f7b416ca4f4a08132e453e08de2db46459256fb66afaac5ee758b44fe6541b7cdaf9d252e59585 - languageName: node - linkType: hard - -"path-exists@npm:^4.0.0": - version: 4.0.0 - resolution: "path-exists@npm:4.0.0" - checksum: 10c0/8c0bd3f5238188197dc78dced15207a4716c51cc4e3624c44fc97acf69558f5ebb9a2afff486fe1b4ee148e0c133e96c5e11a9aa5c48a3006e3467da070e5e1b - languageName: node - linkType: hard - -"path-key@npm:^3.0.0, path-key@npm:^3.1.0": - version: 3.1.1 - resolution: "path-key@npm:3.1.1" - checksum: 10c0/748c43efd5a569c039d7a00a03b58eecd1d75f3999f5a28303d75f521288df4823bc057d8784eb72358b2895a05f29a070bc9f1f17d28226cc4e62494cc58c4c - languageName: node - linkType: hard - -"path-parse@npm:^1.0.7": - version: 1.0.7 - resolution: "path-parse@npm:1.0.7" - checksum: 10c0/11ce261f9d294cc7a58d6a574b7f1b935842355ec66fba3c3fd79e0f036462eaf07d0aa95bb74ff432f9afef97ce1926c720988c6a7451d8a584930ae7de86e1 - languageName: node - linkType: hard - -"path-scurry@npm:^2.0.0": - version: 2.0.1 - resolution: "path-scurry@npm:2.0.1" - dependencies: - lru-cache: "npm:^11.0.0" - minipass: "npm:^7.1.2" - checksum: 10c0/2a16ed0e81fbc43513e245aa5763354e25e787dab0d539581a6c3f0f967461a159ed6236b2559de23aa5b88e7dc32b469b6c47568833dd142a4b24b4f5cd2620 - languageName: node - linkType: hard - -"path-type@npm:^4.0.0": - version: 4.0.0 - resolution: "path-type@npm:4.0.0" - checksum: 10c0/666f6973f332f27581371efaf303fd6c272cc43c2057b37aa99e3643158c7e4b2626549555d88626e99ea9e046f82f32e41bbde5f1508547e9a11b149b52387c - languageName: node - linkType: hard - -"pathe@npm:^1.1.2": - version: 1.1.2 - resolution: "pathe@npm:1.1.2" - checksum: 10c0/64ee0a4e587fb0f208d9777a6c56e4f9050039268faaaaecd50e959ef01bf847b7872785c36483fa5cdcdbdfdb31fef2ff222684d4fc21c330ab60395c681897 - languageName: node - linkType: hard - -"pathval@npm:^2.0.0": - version: 2.0.1 - resolution: "pathval@npm:2.0.1" - checksum: 10c0/460f4709479fbf2c45903a65655fc8f0a5f6d808f989173aeef5fdea4ff4f303dc13f7870303999add60ec49d4c14733895c0a869392e9866f1091fa64fd7581 - languageName: node - linkType: hard - -"picocolors@npm:^1.1.1": - version: 1.1.1 - resolution: "picocolors@npm:1.1.1" - checksum: 10c0/e2e3e8170ab9d7c7421969adaa7e1b31434f789afb9b3f115f6b96d91945041ac3ceb02e9ec6fe6510ff036bcc0bf91e69a1772edc0b707e12b19c0f2d6bcf58 - languageName: node - linkType: hard - -"picomatch@npm:^2.3.1": - version: 2.3.1 - resolution: "picomatch@npm:2.3.1" - checksum: 10c0/26c02b8d06f03206fc2ab8d16f19960f2ff9e81a658f831ecb656d8f17d9edc799e8364b1f4a7873e89d9702dff96204be0fa26fe4181f6843f040f819dac4be - languageName: node - linkType: hard - -"picomatch@npm:^4.0.3": - version: 4.0.3 - resolution: "picomatch@npm:4.0.3" - checksum: 10c0/9582c951e95eebee5434f59e426cddd228a7b97a0161a375aed4be244bd3fe8e3a31b846808ea14ef2c8a2527a6eeab7b3946a67d5979e81694654f939473ae2 - languageName: node - linkType: hard - -"possible-typed-array-names@npm:^1.0.0": - version: 1.1.0 - resolution: "possible-typed-array-names@npm:1.1.0" - checksum: 10c0/c810983414142071da1d644662ce4caebce890203eb2bc7bf119f37f3fe5796226e117e6cca146b521921fa6531072674174a3325066ac66fce089a53e1e5196 - languageName: node - linkType: hard - -"postcss@npm:^8.4.43": - version: 8.5.6 - resolution: "postcss@npm:8.5.6" - dependencies: - nanoid: "npm:^3.3.11" - picocolors: "npm:^1.1.1" - source-map-js: "npm:^1.2.1" - checksum: 10c0/5127cc7c91ed7a133a1b7318012d8bfa112da9ef092dddf369ae699a1f10ebbd89b1b9f25f3228795b84585c72aabd5ced5fc11f2ba467eedf7b081a66fad024 - languageName: node - linkType: hard - -"prelude-ls@npm:^1.2.1": - version: 1.2.1 - resolution: "prelude-ls@npm:1.2.1" - checksum: 10c0/b00d617431e7886c520a6f498a2e14c75ec58f6d93ba48c3b639cf241b54232d90daa05d83a9e9b9fef6baa63cb7e1e4602c2372fea5bc169668401eb127d0cd - languageName: node - linkType: hard - -"proc-log@npm:^6.0.0": - version: 6.1.0 - resolution: "proc-log@npm:6.1.0" - checksum: 10c0/4f178d4062733ead9d71a9b1ab24ebcecdfe2250916a5b1555f04fe2eda972a0ec76fbaa8df1ad9c02707add6749219d118a4fc46dc56bdfe4dde4b47d80bb82 - languageName: node - linkType: hard - -"process-nextick-args@npm:~2.0.0": - version: 2.0.1 - resolution: "process-nextick-args@npm:2.0.1" - checksum: 10c0/bec089239487833d46b59d80327a1605e1c5287eaad770a291add7f45fda1bb5e28b38e0e061add0a1d0ee0984788ce74fa394d345eed1c420cacf392c554367 - languageName: node - linkType: hard - -"promise-retry@npm:^2.0.1": - version: 2.0.1 - resolution: "promise-retry@npm:2.0.1" - dependencies: - err-code: "npm:^2.0.2" - retry: "npm:^0.12.0" - checksum: 10c0/9c7045a1a2928094b5b9b15336dcd2a7b1c052f674550df63cc3f36cd44028e5080448175b6f6ca32b642de81150f5e7b1a98b728f15cb069f2dd60ac2616b96 - languageName: node - linkType: hard - -"prop-types@npm:^15.6.2, prop-types@npm:^15.8.1": - version: 15.8.1 - resolution: "prop-types@npm:15.8.1" - dependencies: - loose-envify: "npm:^1.4.0" - object-assign: "npm:^4.1.1" - react-is: "npm:^16.13.1" - checksum: 10c0/59ece7ca2fb9838031d73a48d4becb9a7cc1ed10e610517c7d8f19a1e02fa47f7c27d557d8a5702bec3cfeccddc853579832b43f449e54635803f277b1c78077 - languageName: node - linkType: hard - -"punycode@npm:^2.1.0": - version: 2.3.1 - resolution: "punycode@npm:2.3.1" - checksum: 10c0/14f76a8206bc3464f794fb2e3d3cc665ae416c01893ad7a02b23766eb07159144ee612ad67af5e84fa4479ccfe67678c4feb126b0485651b302babf66f04f9e9 - languageName: node - linkType: hard - -"qr.js@npm:0.0.0": - version: 0.0.0 - resolution: "qr.js@npm:0.0.0" - checksum: 10c0/1c6a4c7a58d04e52ec2fee99e39b680fdc5b2a510a981df42c36b716a8eac6634d130fc4d65af8f030f2a07dbf5fa046b97cdfa7456c250ebb50a73916efdcb5 - languageName: node - linkType: hard - -"queue-microtask@npm:^1.2.2": - version: 1.2.3 - resolution: "queue-microtask@npm:1.2.3" - checksum: 10c0/900a93d3cdae3acd7d16f642c29a642aea32c2026446151f0778c62ac089d4b8e6c986811076e1ae180a694cedf077d453a11b58ff0a865629a4f82ab558e102 - languageName: node - linkType: hard - -"react-dom@npm:^19.1.0": - version: 19.1.1 - resolution: "react-dom@npm:19.1.1" - dependencies: - scheduler: "npm:^0.26.0" - peerDependencies: - react: ^19.1.1 - checksum: 10c0/8c91198510521299c56e4e8d5e3a4508b2734fb5e52f29eeac33811de64e76fe586ad32c32182e2e84e070d98df67125da346c3360013357228172dbcd20bcdd - languageName: node - linkType: hard - -"react-is@npm:^16.13.1, react-is@npm:^16.7.0": - version: 16.13.1 - resolution: "react-is@npm:16.13.1" - checksum: 10c0/33977da7a5f1a287936a0c85639fec6ca74f4f15ef1e59a6bc20338fc73dc69555381e211f7a3529b8150a1f71e4225525b41b60b52965bda53ce7d47377ada1 - languageName: node - linkType: hard - -"react-is@npm:^19.1.1": - version: 19.1.1 - resolution: "react-is@npm:19.1.1" - checksum: 10c0/3dba763fcd69835ae263dcd6727d7ffcc44c1d616f04b7329e67aefdc66a567af4f8dcecdd29454c7a707c968aa1eb85083a83fb616f01675ef25e71cf082f97 - languageName: node - linkType: hard - -"react-qr-code@npm:^2.0.15": - version: 2.0.18 - resolution: "react-qr-code@npm:2.0.18" - dependencies: - prop-types: "npm:^15.8.1" - qr.js: "npm:0.0.0" - peerDependencies: - react: "*" - checksum: 10c0/4e13b795cbb10f1dcf0e39d682bb59851e4c84010ba2be7225b2ad9d5c1ffea52d2d38f884ee26235b7002b8ca99e83b805f55e877663c39d67496764d975cf1 - languageName: node - linkType: hard - -"react-redux@npm:^9.2.0": - version: 9.2.0 - resolution: "react-redux@npm:9.2.0" - dependencies: - "@types/use-sync-external-store": "npm:^0.0.6" - use-sync-external-store: "npm:^1.4.0" - peerDependencies: - "@types/react": ^18.2.25 || ^19 - react: ^18.0 || ^19 - redux: ^5.0.0 - peerDependenciesMeta: - "@types/react": - optional: true - redux: - optional: true - checksum: 10c0/00d485f9d9219ca1507b4d30dde5f6ff8fb68ba642458f742e0ec83af052f89e65cd668249b99299e1053cc6ad3d2d8ac6cb89e2f70d2ac5585ae0d7fa0ef259 - languageName: node - linkType: hard - -"react-refresh@npm:^0.17.0": - version: 0.17.0 - resolution: "react-refresh@npm:0.17.0" - checksum: 10c0/002cba940384c9930008c0bce26cac97a9d5682bc623112c2268ba0c155127d9c178a9a5cc2212d560088d60dfd503edd808669a25f9b377f316a32361d0b23c - languageName: node - linkType: hard - -"react-router-dom@npm:^7.6.1": - version: 7.8.1 - resolution: "react-router-dom@npm:7.8.1" - dependencies: - react-router: "npm:7.8.1" - peerDependencies: - react: ">=18" - react-dom: ">=18" - checksum: 10c0/7616ae6b6c741446c6168a4ec760c01cbe24e8d14364a605bb002ba3f9bc060c0307ae1fb01cb76552aceb0dd956f023a0fb23b2f40436173e7e587a28301a8f - languageName: node - linkType: hard - -"react-router@npm:7.8.1": - version: 7.8.1 - resolution: "react-router@npm:7.8.1" - dependencies: - cookie: "npm:^1.0.1" - set-cookie-parser: "npm:^2.6.0" - peerDependencies: - react: ">=18" - react-dom: ">=18" - peerDependenciesMeta: - react-dom: - optional: true - checksum: 10c0/6d3229080a0f67f4644e0d8ecdb5e2637f640bf9e3a87c3e1e6ca91cce156cf3606d57dac3899869b25156b8343bc274291345914e45bb30ef38556a14e24eac - languageName: node - linkType: hard - -"react-transition-group@npm:^4.4.5": - version: 4.4.5 - resolution: "react-transition-group@npm:4.4.5" - dependencies: - "@babel/runtime": "npm:^7.5.5" - dom-helpers: "npm:^5.0.1" - loose-envify: "npm:^1.4.0" - prop-types: "npm:^15.6.2" - peerDependencies: - react: ">=16.6.0" - react-dom: ">=16.6.0" - checksum: 10c0/2ba754ba748faefa15f87c96dfa700d5525054a0141de8c75763aae6734af0740e77e11261a1e8f4ffc08fd9ab78510122e05c21c2d79066c38bb6861a886c82 - languageName: node - linkType: hard - -"react@npm:^19.1.0": - version: 19.1.1 - resolution: "react@npm:19.1.1" - checksum: 10c0/8c9769a2dfd02e603af6445058325e6c8a24b47b185d0e461f66a6454765ddcaecb3f0a90184836c68bb509f3c38248359edbc42f0d07c23eb500a5c30c87b4e - languageName: node - linkType: hard - -"readable-stream@npm:^2.3.5, readable-stream@npm:~2.3.6": - version: 2.3.8 - resolution: "readable-stream@npm:2.3.8" - dependencies: - core-util-is: "npm:~1.0.0" - inherits: "npm:~2.0.3" - isarray: "npm:~1.0.0" - process-nextick-args: "npm:~2.0.0" - safe-buffer: "npm:~5.1.1" - string_decoder: "npm:~1.1.1" - util-deprecate: "npm:~1.0.1" - checksum: 10c0/7efdb01f3853bc35ac62ea25493567bf588773213f5f4a79f9c365e1ad13bab845ac0dae7bc946270dc40c3929483228415e92a3fc600cc7e4548992f41ee3fa - languageName: node - linkType: hard - -"receptacle@npm:^1.3.2": - version: 1.3.2 - resolution: "receptacle@npm:1.3.2" - dependencies: - ms: "npm:^2.1.1" - checksum: 10c0/213dc9e4e80969cde60c5877fae08d8438f0bf7dd10bf4ea47916a10c053ca05d6581bda374d8f22ce15e6b50739efe319d847362f5ec9e1a4cbdcbde3ddf355 - languageName: node - linkType: hard - -"redux-persist@npm:^6.0.0": - version: 6.0.0 - resolution: "redux-persist@npm:6.0.0" - peerDependencies: - redux: ">4.0.0" - checksum: 10c0/8242d265ab8d28bbc95cf2dc2a05b869eb67aa309b1ed08163c926f3af56dd8eb1ea62118286083461b8ef2024d3b349fd264e5a62a70eb2e74d068c832d5bf2 - languageName: node - linkType: hard - -"redux-thunk@npm:^3.1.0": - version: 3.1.0 - resolution: "redux-thunk@npm:3.1.0" - peerDependencies: - redux: ^5.0.0 - checksum: 10c0/21557f6a30e1b2e3e470933247e51749be7f1d5a9620069a3125778675ce4d178d84bdee3e2a0903427a5c429e3aeec6d4df57897faf93eb83455bc1ef7b66fd - languageName: node - linkType: hard - -"redux@npm:^4.0.0": - version: 4.2.1 - resolution: "redux@npm:4.2.1" - dependencies: - "@babel/runtime": "npm:^7.9.2" - checksum: 10c0/136d98b3d5dbed1cd6279c8c18a6a74c416db98b8a432a46836bdd668475de6279a2d4fd9d1363f63904e00f0678a8a3e7fa532c897163340baf1e71bb42c742 - languageName: node - linkType: hard - -"redux@npm:^5.0.1": - version: 5.0.1 - resolution: "redux@npm:5.0.1" - checksum: 10c0/b10c28357194f38e7d53b760ed5e64faa317cc63de1fb95bc5d9e127fab956392344368c357b8e7a9bedb0c35b111e7efa522210cfdc3b3c75e5074718e9069c - languageName: node - linkType: hard - -"reflect.getprototypeof@npm:^1.0.6, reflect.getprototypeof@npm:^1.0.9": - version: 1.0.10 - resolution: "reflect.getprototypeof@npm:1.0.10" - dependencies: - call-bind: "npm:^1.0.8" - define-properties: "npm:^1.2.1" - es-abstract: "npm:^1.23.9" - es-errors: "npm:^1.3.0" - es-object-atoms: "npm:^1.0.0" - get-intrinsic: "npm:^1.2.7" - get-proto: "npm:^1.0.1" - which-builtin-type: "npm:^1.2.1" - checksum: 10c0/7facec28c8008876f8ab98e80b7b9cb4b1e9224353fd4756dda5f2a4ab0d30fa0a5074777c6df24e1e0af463a2697513b0a11e548d99cf52f21f7bc6ba48d3ac - languageName: node - linkType: hard - -"regexp.prototype.flags@npm:^1.5.3, regexp.prototype.flags@npm:^1.5.4": - version: 1.5.4 - resolution: "regexp.prototype.flags@npm:1.5.4" - dependencies: - call-bind: "npm:^1.0.8" - define-properties: "npm:^1.2.1" - es-errors: "npm:^1.3.0" - get-proto: "npm:^1.0.1" - gopd: "npm:^1.2.0" - set-function-name: "npm:^2.0.2" - checksum: 10c0/83b88e6115b4af1c537f8dabf5c3744032cb875d63bc05c288b1b8c0ef37cbe55353f95d8ca817e8843806e3e150b118bc624e4279b24b4776b4198232735a77 - languageName: node - linkType: hard - -"reselect@npm:^5.1.0, reselect@npm:^5.1.1": - version: 5.1.1 - resolution: "reselect@npm:5.1.1" - checksum: 10c0/219c30da122980f61853db3aebd173524a2accd4b3baec770e3d51941426c87648a125ca08d8c57daa6b8b086f2fdd2703cb035dd6231db98cdbe1176a71f489 - languageName: node - linkType: hard - -"resolve-from@npm:^4.0.0": - version: 4.0.0 - resolution: "resolve-from@npm:4.0.0" - checksum: 10c0/8408eec31a3112ef96e3746c37be7d64020cda07c03a920f5024e77290a218ea758b26ca9529fd7b1ad283947f34b2291c1c0f6aa0ed34acfdda9c6014c8d190 - languageName: node - linkType: hard - -"resolve@npm:^1.19.0": - version: 1.22.10 - resolution: "resolve@npm:1.22.10" - dependencies: - is-core-module: "npm:^2.16.0" - path-parse: "npm:^1.0.7" - supports-preserve-symlinks-flag: "npm:^1.0.0" - bin: - resolve: bin/resolve - checksum: 10c0/8967e1f4e2cc40f79b7e080b4582b9a8c5ee36ffb46041dccb20e6461161adf69f843b43067b4a375de926a2cd669157e29a29578191def399dd5ef89a1b5203 - languageName: node - linkType: hard - -"resolve@npm:^1.22.4": - version: 1.22.11 - resolution: "resolve@npm:1.22.11" - dependencies: - is-core-module: "npm:^2.16.1" - path-parse: "npm:^1.0.7" - supports-preserve-symlinks-flag: "npm:^1.0.0" - bin: - resolve: bin/resolve - checksum: 10c0/f657191507530f2cbecb5815b1ee99b20741ea6ee02a59c57028e9ec4c2c8d7681afcc35febbd554ac0ded459db6f2d8153382c53a2f266cee2575e512674409 - languageName: node - linkType: hard - -"resolve@npm:^2.0.0-next.5": - version: 2.0.0-next.5 - resolution: "resolve@npm:2.0.0-next.5" - dependencies: - is-core-module: "npm:^2.13.0" - path-parse: "npm:^1.0.7" - supports-preserve-symlinks-flag: "npm:^1.0.0" - bin: - resolve: bin/resolve - checksum: 10c0/a6c33555e3482ea2ec4c6e3d3bf0d78128abf69dca99ae468e64f1e30acaa318fd267fb66c8836b04d558d3e2d6ed875fe388067e7d8e0de647d3c21af21c43a - languageName: node - linkType: hard - -"resolve@patch:resolve@npm%3A^1.19.0#optional!builtin": - version: 1.22.10 - resolution: "resolve@patch:resolve@npm%3A1.22.10#optional!builtin::version=1.22.10&hash=c3c19d" - dependencies: - is-core-module: "npm:^2.16.0" - path-parse: "npm:^1.0.7" - supports-preserve-symlinks-flag: "npm:^1.0.0" - bin: - resolve: bin/resolve - checksum: 10c0/52a4e505bbfc7925ac8f4cd91fd8c4e096b6a89728b9f46861d3b405ac9a1ccf4dcbf8befb4e89a2e11370dacd0160918163885cbc669369590f2f31f4c58939 - languageName: node - linkType: hard - -"resolve@patch:resolve@npm%3A^1.22.4#optional!builtin": - version: 1.22.11 - resolution: "resolve@patch:resolve@npm%3A1.22.11#optional!builtin::version=1.22.11&hash=c3c19d" - dependencies: - is-core-module: "npm:^2.16.1" - path-parse: "npm:^1.0.7" - supports-preserve-symlinks-flag: "npm:^1.0.0" - bin: - resolve: bin/resolve - checksum: 10c0/ee5b182f2e37cb1165465e58c6abc797fec0a80b5ba3231607beb4677db0c9291ac010c47cf092b6daa2b7f518d69a0e21888e7e2b633f68d501a874212a8c63 - languageName: node - linkType: hard - -"resolve@patch:resolve@npm%3A^2.0.0-next.5#optional!builtin": - version: 2.0.0-next.5 - resolution: "resolve@patch:resolve@npm%3A2.0.0-next.5#optional!builtin::version=2.0.0-next.5&hash=c3c19d" - dependencies: - is-core-module: "npm:^2.13.0" - path-parse: "npm:^1.0.7" - supports-preserve-symlinks-flag: "npm:^1.0.0" - bin: - resolve: bin/resolve - checksum: 10c0/78ad6edb8309a2bfb720c2c1898f7907a37f858866ce11a5974643af1203a6a6e05b2fa9c53d8064a673a447b83d42569260c306d43628bff5bb101969708355 - languageName: node - linkType: hard - -"retry@npm:^0.12.0": - version: 0.12.0 - resolution: "retry@npm:0.12.0" - checksum: 10c0/59933e8501727ba13ad73ef4a04d5280b3717fd650408460c987392efe9d7be2040778ed8ebe933c5cbd63da3dcc37919c141ef8af0a54a6e4fca5a2af177bfe - languageName: node - linkType: hard - -"reusify@npm:^1.0.4": - version: 1.1.0 - resolution: "reusify@npm:1.1.0" - checksum: 10c0/4eff0d4a5f9383566c7d7ec437b671cc51b25963bd61bf127c3f3d3f68e44a026d99b8d2f1ad344afff8d278a8fe70a8ea092650a716d22287e8bef7126bb2fa - languageName: node - linkType: hard - -"rn-host-detect@npm:^1.2.0": - version: 1.2.0 - resolution: "rn-host-detect@npm:1.2.0" - checksum: 10c0/94067014566c738b3cb786d507a3cf4f4fab79b51b0d7c38bea0a48d536458c7218e01f259e03fa51cf1cf6f62ad4c01fcd36c6fa3b725a86eace02dd30d929f - languageName: node - linkType: hard - -"rollup@npm:^4.20.0": - version: 4.47.1 - resolution: "rollup@npm:4.47.1" - dependencies: - "@rollup/rollup-android-arm-eabi": "npm:4.47.1" - "@rollup/rollup-android-arm64": "npm:4.47.1" - "@rollup/rollup-darwin-arm64": "npm:4.47.1" - "@rollup/rollup-darwin-x64": "npm:4.47.1" - "@rollup/rollup-freebsd-arm64": "npm:4.47.1" - "@rollup/rollup-freebsd-x64": "npm:4.47.1" - "@rollup/rollup-linux-arm-gnueabihf": "npm:4.47.1" - "@rollup/rollup-linux-arm-musleabihf": "npm:4.47.1" - "@rollup/rollup-linux-arm64-gnu": "npm:4.47.1" - "@rollup/rollup-linux-arm64-musl": "npm:4.47.1" - "@rollup/rollup-linux-loongarch64-gnu": "npm:4.47.1" - "@rollup/rollup-linux-ppc64-gnu": "npm:4.47.1" - "@rollup/rollup-linux-riscv64-gnu": "npm:4.47.1" - "@rollup/rollup-linux-riscv64-musl": "npm:4.47.1" - "@rollup/rollup-linux-s390x-gnu": "npm:4.47.1" - "@rollup/rollup-linux-x64-gnu": "npm:4.47.1" - "@rollup/rollup-linux-x64-musl": "npm:4.47.1" - "@rollup/rollup-win32-arm64-msvc": "npm:4.47.1" - "@rollup/rollup-win32-ia32-msvc": "npm:4.47.1" - "@rollup/rollup-win32-x64-msvc": "npm:4.47.1" - "@types/estree": "npm:1.0.8" - fsevents: "npm:~2.3.2" - dependenciesMeta: - "@rollup/rollup-android-arm-eabi": - optional: true - "@rollup/rollup-android-arm64": - optional: true - "@rollup/rollup-darwin-arm64": - optional: true - "@rollup/rollup-darwin-x64": - optional: true - "@rollup/rollup-freebsd-arm64": - optional: true - "@rollup/rollup-freebsd-x64": - optional: true - "@rollup/rollup-linux-arm-gnueabihf": - optional: true - "@rollup/rollup-linux-arm-musleabihf": - optional: true - "@rollup/rollup-linux-arm64-gnu": - optional: true - "@rollup/rollup-linux-arm64-musl": - optional: true - "@rollup/rollup-linux-loongarch64-gnu": - optional: true - "@rollup/rollup-linux-ppc64-gnu": - optional: true - "@rollup/rollup-linux-riscv64-gnu": - optional: true - "@rollup/rollup-linux-riscv64-musl": - optional: true - "@rollup/rollup-linux-s390x-gnu": - optional: true - "@rollup/rollup-linux-x64-gnu": - optional: true - "@rollup/rollup-linux-x64-musl": - optional: true - "@rollup/rollup-win32-arm64-msvc": - optional: true - "@rollup/rollup-win32-ia32-msvc": - optional: true - "@rollup/rollup-win32-x64-msvc": - optional: true - fsevents: - optional: true - bin: - rollup: dist/bin/rollup - checksum: 10c0/4d792f8a8bfb4408485bbc6c1f393a88422230c14d06b9de4d27bcf443fe3569f9fa4ed6111972bf6b8b515bd0133bfeb16ab66cdf32494ef0fbd2da41dc2855 - languageName: node - linkType: hard - -"run-parallel@npm:^1.1.9": - version: 1.2.0 - resolution: "run-parallel@npm:1.2.0" - dependencies: - queue-microtask: "npm:^1.2.2" - checksum: 10c0/200b5ab25b5b8b7113f9901bfe3afc347e19bb7475b267d55ad0eb86a62a46d77510cb0f232507c9e5d497ebda569a08a9867d0d14f57a82ad5564d991588b39 - languageName: node - linkType: hard - -"safe-array-concat@npm:^1.1.3": - version: 1.1.3 - resolution: "safe-array-concat@npm:1.1.3" - dependencies: - call-bind: "npm:^1.0.8" - call-bound: "npm:^1.0.2" - get-intrinsic: "npm:^1.2.6" - has-symbols: "npm:^1.1.0" - isarray: "npm:^2.0.5" - checksum: 10c0/43c86ffdddc461fb17ff8a17c5324f392f4868f3c7dd2c6a5d9f5971713bc5fd755667212c80eab9567595f9a7509cc2f83e590ddaebd1bd19b780f9c79f9a8d - languageName: node - linkType: hard - -"safe-buffer@npm:^5.0.1, safe-buffer@npm:^5.1.1": - version: 5.2.1 - resolution: "safe-buffer@npm:5.2.1" - checksum: 10c0/6501914237c0a86e9675d4e51d89ca3c21ffd6a31642efeba25ad65720bce6921c9e7e974e5be91a786b25aa058b5303285d3c15dbabf983a919f5f630d349f3 - languageName: node - linkType: hard - -"safe-buffer@npm:~5.1.0, safe-buffer@npm:~5.1.1": - version: 5.1.2 - resolution: "safe-buffer@npm:5.1.2" - checksum: 10c0/780ba6b5d99cc9a40f7b951d47152297d0e260f0df01472a1b99d4889679a4b94a13d644f7dbc4f022572f09ae9005fa2fbb93bbbd83643316f365a3e9a45b21 - languageName: node - linkType: hard - -"safe-push-apply@npm:^1.0.0": - version: 1.0.0 - resolution: "safe-push-apply@npm:1.0.0" - dependencies: - es-errors: "npm:^1.3.0" - isarray: "npm:^2.0.5" - checksum: 10c0/831f1c9aae7436429e7862c7e46f847dfe490afac20d0ee61bae06108dbf5c745a0de3568ada30ccdd3eeb0864ca8331b2eef703abd69bfea0745b21fd320750 - languageName: node - linkType: hard - -"safe-regex-test@npm:^1.1.0": - version: 1.1.0 - resolution: "safe-regex-test@npm:1.1.0" - dependencies: - call-bound: "npm:^1.0.2" - es-errors: "npm:^1.3.0" - is-regex: "npm:^1.2.1" - checksum: 10c0/f2c25281bbe5d39cddbbce7f86fca5ea9b3ce3354ea6cd7c81c31b006a5a9fff4286acc5450a3b9122c56c33eba69c56b9131ad751457b2b4a585825e6a10665 - languageName: node - linkType: hard - -"safer-buffer@npm:>= 2.1.2 < 3.0.0": - version: 2.1.2 - resolution: "safer-buffer@npm:2.1.2" - checksum: 10c0/7e3c8b2e88a1841c9671094bbaeebd94448111dd90a81a1f606f3f67708a6ec57763b3b47f06da09fc6054193e0e6709e77325415dc8422b04497a8070fa02d4 - languageName: node - linkType: hard - -"sc-errors@npm:^3.0.0": - version: 3.0.0 - resolution: "sc-errors@npm:3.0.0" - checksum: 10c0/d015423572cf0b68a6ddb5a9088cb7509da013cdb304691128443f6240e095b3de0e126e0d8e6c5fce14dc0973ad1c3f6ef9c77f4667599f2e99e5377c72a133 - languageName: node - linkType: hard - -"sc-formatter@npm:^4.0.0": - version: 4.0.0 - resolution: "sc-formatter@npm:4.0.0" - checksum: 10c0/1ef2e29bbfcd4ee17c9bbeaaa766169b8e0d210cecac6bcfd71341306438948536b59e060e1edd28ddee1ede1108c3ac1e1789813349d15aab677a5f64ea7cc9 - languageName: node - linkType: hard - -"scheduler@npm:^0.26.0": - version: 0.26.0 - resolution: "scheduler@npm:0.26.0" - checksum: 10c0/5b8d5bfddaae3513410eda54f2268e98a376a429931921a81b5c3a2873aab7ca4d775a8caac5498f8cbc7d0daeab947cf923dbd8e215d61671f9f4e392d34356 - languageName: node - linkType: hard - -"semver@npm:^6.3.1": - version: 6.3.1 - resolution: "semver@npm:6.3.1" - bin: - semver: bin/semver.js - checksum: 10c0/e3d79b609071caa78bcb6ce2ad81c7966a46a7431d9d58b8800cfa9cb6a63699b3899a0e4bcce36167a284578212d9ae6942b6929ba4aa5015c079a67751d42d - languageName: node - linkType: hard - -"semver@npm:^7.3.5": - version: 7.7.3 - resolution: "semver@npm:7.7.3" - bin: - semver: bin/semver.js - checksum: 10c0/4afe5c986567db82f44c8c6faef8fe9df2a9b1d98098fc1721f57c696c4c21cebd572f297fc21002f81889492345b8470473bc6f4aff5fb032a6ea59ea2bc45e - languageName: node - linkType: hard - -"semver@npm:^7.5.4, semver@npm:^7.6.0, semver@npm:^7.6.2": - version: 7.7.2 - resolution: "semver@npm:7.7.2" - bin: - semver: bin/semver.js - checksum: 10c0/aca305edfbf2383c22571cb7714f48cadc7ac95371b4b52362fb8eeffdfbc0de0669368b82b2b15978f8848f01d7114da65697e56cd8c37b0dab8c58e543f9ea - languageName: node - linkType: hard - -"set-cookie-parser@npm:^2.6.0": - version: 2.7.1 - resolution: "set-cookie-parser@npm:2.7.1" - checksum: 10c0/060c198c4c92547ac15988256f445eae523f57f2ceefeccf52d30d75dedf6bff22b9c26f756bd44e8e560d44ff4ab2130b178bd2e52ef5571bf7be3bd7632d9a - languageName: node - linkType: hard - -"set-function-length@npm:^1.2.2": - version: 1.2.2 - resolution: "set-function-length@npm:1.2.2" - dependencies: - define-data-property: "npm:^1.1.4" - es-errors: "npm:^1.3.0" - function-bind: "npm:^1.1.2" - get-intrinsic: "npm:^1.2.4" - gopd: "npm:^1.0.1" - has-property-descriptors: "npm:^1.0.2" - checksum: 10c0/82850e62f412a258b71e123d4ed3873fa9377c216809551192bb6769329340176f109c2eeae8c22a8d386c76739855f78e8716515c818bcaef384b51110f0f3c - languageName: node - linkType: hard - -"set-function-name@npm:^2.0.2": - version: 2.0.2 - resolution: "set-function-name@npm:2.0.2" - dependencies: - define-data-property: "npm:^1.1.4" - es-errors: "npm:^1.3.0" - functions-have-names: "npm:^1.2.3" - has-property-descriptors: "npm:^1.0.2" - checksum: 10c0/fce59f90696c450a8523e754abb305e2b8c73586452619c2bad5f7bf38c7b6b4651895c9db895679c5bef9554339cf3ef1c329b66ece3eda7255785fbe299316 - languageName: node - linkType: hard - -"set-proto@npm:^1.0.0": - version: 1.0.0 - resolution: "set-proto@npm:1.0.0" - dependencies: - dunder-proto: "npm:^1.0.1" - es-errors: "npm:^1.3.0" - es-object-atoms: "npm:^1.0.0" - checksum: 10c0/ca5c3ccbba479d07c30460e367e66337cec825560b11e8ba9c5ebe13a2a0d6021ae34eddf94ff3dfe17a3104dc1f191519cb6c48378b503e5c3f36393938776a - languageName: node - linkType: hard - -"shallow-clone@npm:^3.0.0": - version: 3.0.1 - resolution: "shallow-clone@npm:3.0.1" - dependencies: - kind-of: "npm:^6.0.2" - checksum: 10c0/7bab09613a1b9f480c85a9823aebec533015579fa055ba6634aa56ba1f984380670eaf33b8217502931872aa1401c9fcadaa15f9f604d631536df475b05bcf1e - languageName: node - linkType: hard - -"shebang-command@npm:^2.0.0": - version: 2.0.0 - resolution: "shebang-command@npm:2.0.0" - dependencies: - shebang-regex: "npm:^3.0.0" - checksum: 10c0/a41692e7d89a553ef21d324a5cceb5f686d1f3c040759c50aab69688634688c5c327f26f3ecf7001ebfd78c01f3c7c0a11a7c8bfd0a8bc9f6240d4f40b224e4e - languageName: node - linkType: hard - -"shebang-regex@npm:^3.0.0": - version: 3.0.0 - resolution: "shebang-regex@npm:3.0.0" - checksum: 10c0/1dbed0726dd0e1152a92696c76c7f06084eb32a90f0528d11acd764043aacf76994b2fb30aa1291a21bd019d6699164d048286309a278855ee7bec06cf6fb690 - languageName: node - linkType: hard - -"side-channel-list@npm:^1.0.0": - version: 1.0.0 - resolution: "side-channel-list@npm:1.0.0" - dependencies: - es-errors: "npm:^1.3.0" - object-inspect: "npm:^1.13.3" - checksum: 10c0/644f4ac893456c9490ff388bf78aea9d333d5e5bfc64cfb84be8f04bf31ddc111a8d4b83b85d7e7e8a7b845bc185a9ad02c052d20e086983cf59f0be517d9b3d - languageName: node - linkType: hard - -"side-channel-map@npm:^1.0.1": - version: 1.0.1 - resolution: "side-channel-map@npm:1.0.1" - dependencies: - call-bound: "npm:^1.0.2" - es-errors: "npm:^1.3.0" - get-intrinsic: "npm:^1.2.5" - object-inspect: "npm:^1.13.3" - checksum: 10c0/010584e6444dd8a20b85bc926d934424bd809e1a3af941cace229f7fdcb751aada0fb7164f60c2e22292b7fa3c0ff0bce237081fd4cdbc80de1dc68e95430672 - languageName: node - linkType: hard - -"side-channel-weakmap@npm:^1.0.2": - version: 1.0.2 - resolution: "side-channel-weakmap@npm:1.0.2" - dependencies: - call-bound: "npm:^1.0.2" - es-errors: "npm:^1.3.0" - get-intrinsic: "npm:^1.2.5" - object-inspect: "npm:^1.13.3" - side-channel-map: "npm:^1.0.1" - checksum: 10c0/71362709ac233e08807ccd980101c3e2d7efe849edc51455030327b059f6c4d292c237f94dc0685031dd11c07dd17a68afde235d6cf2102d949567f98ab58185 - languageName: node - linkType: hard - -"side-channel@npm:^1.1.0": - version: 1.1.0 - resolution: "side-channel@npm:1.1.0" - dependencies: - es-errors: "npm:^1.3.0" - object-inspect: "npm:^1.13.3" - side-channel-list: "npm:^1.0.0" - side-channel-map: "npm:^1.0.1" - side-channel-weakmap: "npm:^1.0.2" - checksum: 10c0/cb20dad41eb032e6c24c0982e1e5a24963a28aa6122b4f05b3f3d6bf8ae7fd5474ef382c8f54a6a3ab86e0cac4d41a23bd64ede3970e5bfb50326ba02a7996e6 - languageName: node - linkType: hard - -"siginfo@npm:^2.0.0": - version: 2.0.0 - resolution: "siginfo@npm:2.0.0" - checksum: 10c0/3def8f8e516fbb34cb6ae415b07ccc5d9c018d85b4b8611e3dc6f8be6d1899f693a4382913c9ed51a06babb5201639d76453ab297d1c54a456544acf5c892e34 - languageName: node - linkType: hard - -"signal-exit@npm:^3.0.3": - version: 3.0.7 - resolution: "signal-exit@npm:3.0.7" - checksum: 10c0/25d272fa73e146048565e08f3309d5b942c1979a6f4a58a8c59d5fa299728e9c2fcd1a759ec870863b1fd38653670240cd420dad2ad9330c71f36608a6a1c912 - languageName: node - linkType: hard - -"smart-buffer@npm:^4.2.0": - version: 4.2.0 - resolution: "smart-buffer@npm:4.2.0" - checksum: 10c0/a16775323e1404dd43fabafe7460be13a471e021637bc7889468eb45ce6a6b207261f454e4e530a19500cc962c4cc5348583520843b363f4193cee5c00e1e539 - languageName: node - linkType: hard - -"socketcluster-client@npm:^19.2.3": - version: 19.2.7 - resolution: "socketcluster-client@npm:19.2.7" - dependencies: - ag-auth: "npm:^2.1.0" - ag-channel: "npm:^5.0.0" - ag-request: "npm:^1.1.0" - async-stream-emitter: "npm:^7.0.1" - buffer: "npm:^5.2.1" - clone-deep: "npm:^4.0.1" - linked-list: "npm:^2.1.0" - sc-errors: "npm:^3.0.0" - sc-formatter: "npm:^4.0.0" - stream-demux: "npm:^10.0.1" - uuid: "npm:^8.3.2" - vinyl-buffer: "npm:^1.0.1" - ws: "npm:^8.18.0" - checksum: 10c0/58a333f2ac376043ea7bf38c42a473042258000e9ea8e56eec3d2ff3b6e85c04d8f710b2bd1f79436878bc802358259e3aa1dcc22241a1d7ae2600288fc75412 - languageName: node - linkType: hard - -"socks-proxy-agent@npm:^8.0.3": - version: 8.0.5 - resolution: "socks-proxy-agent@npm:8.0.5" - dependencies: - agent-base: "npm:^7.1.2" - debug: "npm:^4.3.4" - socks: "npm:^2.8.3" - checksum: 10c0/5d2c6cecba6821389aabf18728325730504bf9bb1d9e342e7987a5d13badd7a98838cc9a55b8ed3cb866ad37cc23e1086f09c4d72d93105ce9dfe76330e9d2a6 - languageName: node - linkType: hard - -"socks@npm:^2.8.3": - version: 2.8.7 - resolution: "socks@npm:2.8.7" - dependencies: - ip-address: "npm:^10.0.1" - smart-buffer: "npm:^4.2.0" - checksum: 10c0/2805a43a1c4bcf9ebf6e018268d87b32b32b06fbbc1f9282573583acc155860dc361500f89c73bfbb157caa1b4ac78059eac0ef15d1811eb0ca75e0bdadbc9d2 - languageName: node - linkType: hard - -"source-map-js@npm:^1.2.1": - version: 1.2.1 - resolution: "source-map-js@npm:1.2.1" - checksum: 10c0/7bda1fc4c197e3c6ff17de1b8b2c20e60af81b63a52cb32ec5a5d67a20a7d42651e2cb34ebe93833c5a2a084377e17455854fee3e21e7925c64a51b6a52b0faf - languageName: node - linkType: hard - -"source-map@npm:^0.5.7": - version: 0.5.7 - resolution: "source-map@npm:0.5.7" - checksum: 10c0/904e767bb9c494929be013017380cbba013637da1b28e5943b566031e29df04fba57edf3f093e0914be094648b577372bd8ad247fa98cfba9c600794cd16b599 - languageName: node - linkType: hard - -"ssri@npm:^13.0.0": - version: 13.0.0 - resolution: "ssri@npm:13.0.0" - dependencies: - minipass: "npm:^7.0.3" - checksum: 10c0/405f3a531cd98b013cecb355d63555dca42fd12c7bc6671738aaa9a82882ff41cdf0ef9a2b734ca4f9a760338f114c29d01d9238a65db3ccac27929bd6e6d4b2 - languageName: node - linkType: hard - -"stackback@npm:0.0.2": - version: 0.0.2 - resolution: "stackback@npm:0.0.2" - checksum: 10c0/89a1416668f950236dd5ac9f9a6b2588e1b9b62b1b6ad8dff1bfc5d1a15dbf0aafc9b52d2226d00c28dffff212da464eaeebfc6b7578b9d180cef3e3782c5983 - languageName: node - linkType: hard - -"std-env@npm:^3.8.0": - version: 3.9.0 - resolution: "std-env@npm:3.9.0" - checksum: 10c0/4a6f9218aef3f41046c3c7ecf1f98df00b30a07f4f35c6d47b28329bc2531eef820828951c7d7b39a1c5eb19ad8a46e3ddfc7deb28f0a2f3ceebee11bab7ba50 - languageName: node - linkType: hard - -"stop-iteration-iterator@npm:^1.1.0": - version: 1.1.0 - resolution: "stop-iteration-iterator@npm:1.1.0" - dependencies: - es-errors: "npm:^1.3.0" - internal-slot: "npm:^1.1.0" - checksum: 10c0/de4e45706bb4c0354a4b1122a2b8cc45a639e86206807ce0baf390ee9218d3ef181923fa4d2b67443367c491aa255c5fbaa64bb74648e3c5b48299928af86c09 - languageName: node - linkType: hard - -"stream-demux@npm:^10.0.1": - version: 10.0.1 - resolution: "stream-demux@npm:10.0.1" - dependencies: - consumable-stream: "npm:^3.0.0" - writable-consumable-stream: "npm:^4.1.0" - checksum: 10c0/9c6b42af758560ceab49b0fdae169599cfca95fcd6f234b45fa3e1465751c2576ad65fd86e54bf5365fda6152955d06d6e6957c506b51460343c4801260402cf - languageName: node - linkType: hard - -"string.prototype.matchall@npm:^4.0.12": - version: 4.0.12 - resolution: "string.prototype.matchall@npm:4.0.12" - dependencies: - call-bind: "npm:^1.0.8" - call-bound: "npm:^1.0.3" - define-properties: "npm:^1.2.1" - es-abstract: "npm:^1.23.6" - es-errors: "npm:^1.3.0" - es-object-atoms: "npm:^1.0.0" - get-intrinsic: "npm:^1.2.6" - gopd: "npm:^1.2.0" - has-symbols: "npm:^1.1.0" - internal-slot: "npm:^1.1.0" - regexp.prototype.flags: "npm:^1.5.3" - set-function-name: "npm:^2.0.2" - side-channel: "npm:^1.1.0" - checksum: 10c0/1a53328ada73f4a77f1fdf1c79414700cf718d0a8ef6672af5603e709d26a24f2181208144aed7e858b1bcc1a0d08567a570abfb45567db4ae47637ed2c2f85c - languageName: node - linkType: hard - -"string.prototype.repeat@npm:^1.0.0": - version: 1.0.0 - resolution: "string.prototype.repeat@npm:1.0.0" - dependencies: - define-properties: "npm:^1.1.3" - es-abstract: "npm:^1.17.5" - checksum: 10c0/94c7978566cffa1327d470fd924366438af9b04b497c43a9805e476e2e908aa37a1fd34cc0911156c17556dab62159d12c7b92b3cc304c3e1281fe4c8e668f40 - languageName: node - linkType: hard - -"string.prototype.trim@npm:^1.2.10": - version: 1.2.10 - resolution: "string.prototype.trim@npm:1.2.10" - dependencies: - call-bind: "npm:^1.0.8" - call-bound: "npm:^1.0.2" - define-data-property: "npm:^1.1.4" - define-properties: "npm:^1.2.1" - es-abstract: "npm:^1.23.5" - es-object-atoms: "npm:^1.0.0" - has-property-descriptors: "npm:^1.0.2" - checksum: 10c0/8a8854241c4b54a948e992eb7dd6b8b3a97185112deb0037a134f5ba57541d8248dd610c966311887b6c2fd1181a3877bffb14d873ce937a344535dabcc648f8 - languageName: node - linkType: hard - -"string.prototype.trimend@npm:^1.0.9": - version: 1.0.9 - resolution: "string.prototype.trimend@npm:1.0.9" - dependencies: - call-bind: "npm:^1.0.8" - call-bound: "npm:^1.0.2" - define-properties: "npm:^1.2.1" - es-object-atoms: "npm:^1.0.0" - checksum: 10c0/59e1a70bf9414cb4c536a6e31bef5553c8ceb0cf44d8b4d0ed65c9653358d1c64dd0ec203b100df83d0413bbcde38b8c5d49e14bc4b86737d74adc593a0d35b6 - languageName: node - linkType: hard - -"string.prototype.trimstart@npm:^1.0.8": - version: 1.0.8 - resolution: "string.prototype.trimstart@npm:1.0.8" - dependencies: - call-bind: "npm:^1.0.7" - define-properties: "npm:^1.2.1" - es-object-atoms: "npm:^1.0.0" - checksum: 10c0/d53af1899959e53c83b64a5fd120be93e067da740e7e75acb433849aa640782fb6c7d4cd5b84c954c84413745a3764df135a8afeb22908b86a835290788d8366 - languageName: node - linkType: hard - -"string_decoder@npm:~1.1.1": - version: 1.1.1 - resolution: "string_decoder@npm:1.1.1" - dependencies: - safe-buffer: "npm:~5.1.0" - checksum: 10c0/b4f89f3a92fd101b5653ca3c99550e07bdf9e13b35037e9e2a1c7b47cec4e55e06ff3fc468e314a0b5e80bfbaf65c1ca5a84978764884ae9413bec1fc6ca924e - languageName: node - linkType: hard - -"strip-bom@npm:^3.0.0": - version: 3.0.0 - resolution: "strip-bom@npm:3.0.0" - checksum: 10c0/51201f50e021ef16672593d7434ca239441b7b760e905d9f33df6e4f3954ff54ec0e0a06f100d028af0982d6f25c35cd5cda2ce34eaebccd0250b8befb90d8f1 - languageName: node - linkType: hard - -"strip-final-newline@npm:^2.0.0": - version: 2.0.0 - resolution: "strip-final-newline@npm:2.0.0" - checksum: 10c0/bddf8ccd47acd85c0e09ad7375409d81653f645fda13227a9d459642277c253d877b68f2e5e4d819fe75733b0e626bac7e954c04f3236f6d196f79c94fa4a96f - languageName: node - linkType: hard - -"strip-json-comments@npm:^3.1.1": - version: 3.1.1 - resolution: "strip-json-comments@npm:3.1.1" - checksum: 10c0/9681a6257b925a7fa0f285851c0e613cc934a50661fa7bb41ca9cbbff89686bb4a0ee366e6ecedc4daafd01e83eee0720111ab294366fe7c185e935475ebcecd - languageName: node - linkType: hard - -"stylis@npm:4.2.0": - version: 4.2.0 - resolution: "stylis@npm:4.2.0" - checksum: 10c0/a7128ad5a8ed72652c6eba46bed4f416521bc9745a460ef5741edc725252cebf36ee45e33a8615a7057403c93df0866ab9ee955960792db210bb80abd5ac6543 - languageName: node - linkType: hard - -"supports-color@npm:^7.1.0": - version: 7.2.0 - resolution: "supports-color@npm:7.2.0" - dependencies: - has-flag: "npm:^4.0.0" - checksum: 10c0/afb4c88521b8b136b5f5f95160c98dee7243dc79d5432db7efc27efb219385bbc7d9427398e43dd6cc730a0f87d5085ce1652af7efbe391327bc0a7d0f7fc124 - languageName: node - linkType: hard - -"supports-preserve-symlinks-flag@npm:^1.0.0": - version: 1.0.0 - resolution: "supports-preserve-symlinks-flag@npm:1.0.0" - checksum: 10c0/6c4032340701a9950865f7ae8ef38578d8d7053f5e10518076e6554a9381fa91bd9c6850193695c141f32b21f979c985db07265a758867bac95de05f7d8aeb39 - languageName: node - linkType: hard - -"tar@npm:^7.5.2": - version: 7.5.2 - resolution: "tar@npm:7.5.2" - dependencies: - "@isaacs/fs-minipass": "npm:^4.0.0" - chownr: "npm:^3.0.0" - minipass: "npm:^7.1.2" - minizlib: "npm:^3.1.0" - yallist: "npm:^5.0.0" - checksum: 10c0/a7d8b801139b52f93a7e34830db0de54c5aa45487c7cb551f6f3d44a112c67f1cb8ffdae856b05fd4f17b1749911f1c26f1e3a23bbe0279e17fd96077f13f467 - languageName: node - linkType: hard - -"through2@npm:^2.0.3": - version: 2.0.5 - resolution: "through2@npm:2.0.5" - dependencies: - readable-stream: "npm:~2.3.6" - xtend: "npm:~4.0.1" - checksum: 10c0/cbfe5b57943fa12b4f8c043658c2a00476216d79c014895cef1ac7a1d9a8b31f6b438d0e53eecbb81054b93128324a82ecd59ec1a4f91f01f7ac113dcb14eade - languageName: node - linkType: hard - -"tinybench@npm:^2.9.0": - version: 2.9.0 - resolution: "tinybench@npm:2.9.0" - checksum: 10c0/c3500b0f60d2eb8db65250afe750b66d51623057ee88720b7f064894a6cb7eb93360ca824a60a31ab16dab30c7b1f06efe0795b352e37914a9d4bad86386a20c - languageName: node - linkType: hard - -"tinyexec@npm:^0.3.1": - version: 0.3.2 - resolution: "tinyexec@npm:0.3.2" - checksum: 10c0/3efbf791a911be0bf0821eab37a3445c2ba07acc1522b1fa84ae1e55f10425076f1290f680286345ed919549ad67527d07281f1c19d584df3b74326909eb1f90 - languageName: node - linkType: hard - -"tinyglobby@npm:^0.2.12": - version: 0.2.15 - resolution: "tinyglobby@npm:0.2.15" - dependencies: - fdir: "npm:^6.5.0" - picomatch: "npm:^4.0.3" - checksum: 10c0/869c31490d0d88eedb8305d178d4c75e7463e820df5a9b9d388291daf93e8b1eb5de1dad1c1e139767e4269fe75f3b10d5009b2cc14db96ff98986920a186844 - languageName: node - linkType: hard - -"tinypool@npm:^1.0.1": - version: 1.1.1 - resolution: "tinypool@npm:1.1.1" - checksum: 10c0/bf26727d01443061b04fa863f571016950888ea994ba0cd8cba3a1c51e2458d84574341ab8dbc3664f1c3ab20885c8cf9ff1cc4b18201f04c2cde7d317fff69b - languageName: node - linkType: hard - -"tinyrainbow@npm:^1.2.0": - version: 1.2.0 - resolution: "tinyrainbow@npm:1.2.0" - checksum: 10c0/7f78a4b997e5ba0f5ecb75e7ed786f30bab9063716e7dff24dd84013fb338802e43d176cb21ed12480561f5649a82184cf31efb296601a29d38145b1cdb4c192 - languageName: node - linkType: hard - -"tinyspy@npm:^3.0.2": - version: 3.0.2 - resolution: "tinyspy@npm:3.0.2" - checksum: 10c0/55ffad24e346622b59292e097c2ee30a63919d5acb7ceca87fc0d1c223090089890587b426e20054733f97a58f20af2c349fb7cc193697203868ab7ba00bcea0 - languageName: node - linkType: hard - -"to-regex-range@npm:^5.0.1": - version: 5.0.1 - resolution: "to-regex-range@npm:5.0.1" - dependencies: - is-number: "npm:^7.0.0" - checksum: 10c0/487988b0a19c654ff3e1961b87f471702e708fa8a8dd02a298ef16da7206692e8552a0250e8b3e8759270f62e9d8314616f6da274734d3b558b1fc7b7724e892 - languageName: node - linkType: hard - -"ts-api-utils@npm:^2.1.0": - version: 2.1.0 - resolution: "ts-api-utils@npm:2.1.0" - peerDependencies: - typescript: ">=4.8.4" - checksum: 10c0/9806a38adea2db0f6aa217ccc6bc9c391ddba338a9fe3080676d0d50ed806d305bb90e8cef0276e793d28c8a929f400abb184ddd7ff83a416959c0f4d2ce754f - languageName: node - linkType: hard - -"tsconfck@npm:^3.0.3": - version: 3.1.6 - resolution: "tsconfck@npm:3.1.6" - peerDependencies: - typescript: ^5.0.0 - peerDependenciesMeta: - typescript: - optional: true - bin: - tsconfck: bin/tsconfck.js - checksum: 10c0/269c3c513540be44844117bb9b9258fe6f8aeab026d32aeebf458d5299125f330711429dbb556dbf125a0bc25f4a81e6c24ac96de2740badd295c3fb400f66c4 - languageName: node - linkType: hard - -"tsconfig-paths@npm:^3.15.0": - version: 3.15.0 - resolution: "tsconfig-paths@npm:3.15.0" - dependencies: - "@types/json5": "npm:^0.0.29" - json5: "npm:^1.0.2" - minimist: "npm:^1.2.6" - strip-bom: "npm:^3.0.0" - checksum: 10c0/5b4f301a2b7a3766a986baf8fc0e177eb80bdba6e396792ff92dc23b5bca8bb279fc96517dcaaef63a3b49bebc6c4c833653ec58155780bc906bdbcf7dda0ef5 - languageName: node - linkType: hard - -"type-check@npm:^0.4.0, type-check@npm:~0.4.0": - version: 0.4.0 - resolution: "type-check@npm:0.4.0" - dependencies: - prelude-ls: "npm:^1.2.1" - checksum: 10c0/7b3fd0ed43891e2080bf0c5c504b418fbb3e5c7b9708d3d015037ba2e6323a28152ec163bcb65212741fa5d2022e3075ac3c76440dbd344c9035f818e8ecee58 - languageName: node - linkType: hard - -"typed-array-buffer@npm:^1.0.3": - version: 1.0.3 - resolution: "typed-array-buffer@npm:1.0.3" - dependencies: - call-bound: "npm:^1.0.3" - es-errors: "npm:^1.3.0" - is-typed-array: "npm:^1.1.14" - checksum: 10c0/1105071756eb248774bc71646bfe45b682efcad93b55532c6ffa4518969fb6241354e4aa62af679ae83899ec296d69ef88f1f3763657cdb3a4d29321f7b83079 - languageName: node - linkType: hard - -"typed-array-byte-length@npm:^1.0.3": - version: 1.0.3 - resolution: "typed-array-byte-length@npm:1.0.3" - dependencies: - call-bind: "npm:^1.0.8" - for-each: "npm:^0.3.3" - gopd: "npm:^1.2.0" - has-proto: "npm:^1.2.0" - is-typed-array: "npm:^1.1.14" - checksum: 10c0/6ae083c6f0354f1fce18b90b243343b9982affd8d839c57bbd2c174a5d5dc71be9eb7019ffd12628a96a4815e7afa85d718d6f1e758615151d5f35df841ffb3e - languageName: node - linkType: hard - -"typed-array-byte-offset@npm:^1.0.4": - version: 1.0.4 - resolution: "typed-array-byte-offset@npm:1.0.4" - dependencies: - available-typed-arrays: "npm:^1.0.7" - call-bind: "npm:^1.0.8" - for-each: "npm:^0.3.3" - gopd: "npm:^1.2.0" - has-proto: "npm:^1.2.0" - is-typed-array: "npm:^1.1.15" - reflect.getprototypeof: "npm:^1.0.9" - checksum: 10c0/3d805b050c0c33b51719ee52de17c1cd8e6a571abdf0fffb110e45e8dd87a657e8b56eee94b776b13006d3d347a0c18a730b903cf05293ab6d92e99ff8f77e53 - languageName: node - linkType: hard - -"typed-array-length@npm:^1.0.7": - version: 1.0.7 - resolution: "typed-array-length@npm:1.0.7" - dependencies: - call-bind: "npm:^1.0.7" - for-each: "npm:^0.3.3" - gopd: "npm:^1.0.1" - is-typed-array: "npm:^1.1.13" - possible-typed-array-names: "npm:^1.0.0" - reflect.getprototypeof: "npm:^1.0.6" - checksum: 10c0/e38f2ae3779584c138a2d8adfa8ecf749f494af3cd3cdafe4e688ce51418c7d2c5c88df1bd6be2bbea099c3f7cea58c02ca02ed438119e91f162a9de23f61295 - languageName: node - linkType: hard - -"typescript-eslint@npm:^8.1.0": - version: 8.40.0 - resolution: "typescript-eslint@npm:8.40.0" - dependencies: - "@typescript-eslint/eslint-plugin": "npm:8.40.0" - "@typescript-eslint/parser": "npm:8.40.0" - "@typescript-eslint/typescript-estree": "npm:8.40.0" - "@typescript-eslint/utils": "npm:8.40.0" - peerDependencies: - eslint: ^8.57.0 || ^9.0.0 - typescript: ">=4.8.4 <6.0.0" - checksum: 10c0/b9bf9cbe13a89348ae2a13a7839238b1b058c1e188d9cc1028810c43f1b48cf256f5255ca94c38acf3cd5a405c918ad96d5b7f7a6ad3f82fa7429122a7883a83 - languageName: node - linkType: hard - -"typescript@npm:^5.2.2": - version: 5.9.2 - resolution: "typescript@npm:5.9.2" - bin: - tsc: bin/tsc - tsserver: bin/tsserver - checksum: 10c0/cd635d50f02d6cf98ed42de2f76289701c1ec587a363369255f01ed15aaf22be0813226bff3c53e99d971f9b540e0b3cc7583dbe05faded49b1b0bed2f638a18 - languageName: node - linkType: hard - -"typescript@patch:typescript@npm%3A^5.2.2#optional!builtin": - version: 5.9.2 - resolution: "typescript@patch:typescript@npm%3A5.9.2#optional!builtin::version=5.9.2&hash=5786d5" - bin: - tsc: bin/tsc - tsserver: bin/tsserver - checksum: 10c0/34d2a8e23eb8e0d1875072064d5e1d9c102e0bdce56a10a25c0b917b8aa9001a9cf5c225df12497e99da107dc379360bc138163c66b55b95f5b105b50578067e - languageName: node - linkType: hard - -"uint8arrays@npm:^3.0.0": - version: 3.1.1 - resolution: "uint8arrays@npm:3.1.1" - dependencies: - multiformats: "npm:^9.4.2" - checksum: 10c0/9946668e04f29b46bbb73cca3d190f63a2fbfe5452f8e6551ef4257d9d597b72da48fa895c15ef2ef772808a5335b3305f69da5f13a09f8c2924896b409565ff - languageName: node - linkType: hard - -"unbox-primitive@npm:^1.1.0": - version: 1.1.0 - resolution: "unbox-primitive@npm:1.1.0" - dependencies: - call-bound: "npm:^1.0.3" - has-bigints: "npm:^1.0.2" - has-symbols: "npm:^1.1.0" - which-boxed-primitive: "npm:^1.1.1" - checksum: 10c0/7dbd35ab02b0e05fe07136c72cb9355091242455473ec15057c11430129bab38b7b3624019b8778d02a881c13de44d63cd02d122ee782fb519e1de7775b5b982 - languageName: node - linkType: hard - -"undici-types@npm:~6.21.0": - version: 6.21.0 - resolution: "undici-types@npm:6.21.0" - checksum: 10c0/c01ed51829b10aa72fc3ce64b747f8e74ae9b60eafa19a7b46ef624403508a54c526ffab06a14a26b3120d055e1104d7abe7c9017e83ced038ea5cf52f8d5e04 - languageName: node - linkType: hard - -"undici-types@npm:~7.16.0": - version: 7.16.0 - resolution: "undici-types@npm:7.16.0" - checksum: 10c0/3033e2f2b5c9f1504bdc5934646cb54e37ecaca0f9249c983f7b1fc2e87c6d18399ebb05dc7fd5419e02b2e915f734d872a65da2e3eeed1813951c427d33cc9a - languageName: node - linkType: hard - -"unique-filename@npm:^5.0.0": - version: 5.0.0 - resolution: "unique-filename@npm:5.0.0" - dependencies: - unique-slug: "npm:^6.0.0" - checksum: 10c0/afb897e9cf4c2fb622ea716f7c2bb462001928fc5f437972213afdf1cc32101a230c0f1e9d96fc91ee5185eca0f2feb34127145874975f347be52eb91d6ccc2c - languageName: node - linkType: hard - -"unique-slug@npm:^6.0.0": - version: 6.0.0 - resolution: "unique-slug@npm:6.0.0" - dependencies: - imurmurhash: "npm:^0.1.4" - checksum: 10c0/da7ade4cb04eb33ad0499861f82fe95ce9c7c878b7139dc54d140ecfb6a6541c18a5c8dac16188b8b379fe62c0c1f1b710814baac910cde5f4fec06212126c6a - languageName: node - linkType: hard - -"unstoppableswap-gui-rs@workspace:.": - version: 0.0.0-use.local - resolution: "unstoppableswap-gui-rs@workspace:." - dependencies: - "@emotion/react": "npm:^11.14.0" - "@emotion/styled": "npm:^11.14.0" - "@eslint/js": "npm:^9.9.0" - "@fontsource/roboto": "npm:^5.1.0" - "@mui/icons-material": "npm:^7.1.1" - "@mui/material": "npm:^7.1.1" - "@mui/x-date-pickers": "npm:^8.8.0" - "@redux-devtools/remote": "npm:^0.9.5" - "@reduxjs/toolkit": "npm:^2.3.0" - "@tauri-apps/api": "npm:^2.8.0" - "@tauri-apps/cli": "npm:^2.0.0" - "@tauri-apps/plugin-cli": "npm:^2.4.0" - "@tauri-apps/plugin-clipboard-manager": "npm:^2.3.0" - "@tauri-apps/plugin-dialog": "npm:^2.0.0" - "@tauri-apps/plugin-opener": "npm:^2.5.0" - "@tauri-apps/plugin-process": "npm:^2.3.0" - "@tauri-apps/plugin-store": "npm:^2.4.0" - "@tauri-apps/plugin-updater": "npm:^2.9.0" - "@testing-library/react": "npm:^16.0.1" - "@testing-library/user-event": "npm:^14.5.2" - "@types/humanize-duration": "npm:^3.27.4" - "@types/lodash": "npm:^4.17.6" - "@types/node": "npm:^22.15.29" - "@types/react": "npm:^19.1.6" - "@types/react-dom": "npm:^19.1.5" - "@types/react-is": "npm:^19.0.0" - "@types/react-redux": "npm:^7.1.34" - "@types/semver": "npm:^7.5.8" - "@vitejs/plugin-react": "npm:^4.2.1" - babel-plugin-react-compiler: "npm:^1.0.0" - dayjs: "npm:^1.11.13" - eslint: "npm:^9.9.0" - eslint-plugin-import: "npm:^2.32.0" - eslint-plugin-react: "npm:^7.35.0" - eslint-plugin-react-hooks: "npm:^7.0.1" - globals: "npm:^15.9.0" - humanize-duration: "npm:^3.32.1" - internal-ip: "npm:^7.0.0" - jdenticon: "npm:^3.3.0" - lodash: "npm:^4.17.21" - multiaddr: "npm:^10.0.1" - notistack: "npm:^3.0.1" - react: "npm:^19.1.0" - react-dom: "npm:^19.1.0" - react-qr-code: "npm:^2.0.15" - react-redux: "npm:^9.2.0" - react-router-dom: "npm:^7.6.1" - redux-persist: "npm:^6.0.0" - semver: "npm:^7.6.2" - typescript: "npm:^5.2.2" - typescript-eslint: "npm:^8.1.0" - virtua: "npm:^0.33.2" - vite: "npm:^5.3.1" - vite-plugin-top-level-await: "npm:^1.4.4" - vite-plugin-watch: "npm:^0.3.1" - vite-tsconfig-paths: "npm:^4.3.2" - vitest: "npm:^2.1.1" - languageName: unknown - linkType: soft - -"update-browserslist-db@npm:^1.1.3": - version: 1.1.3 - resolution: "update-browserslist-db@npm:1.1.3" - dependencies: - escalade: "npm:^3.2.0" - picocolors: "npm:^1.1.1" - peerDependencies: - browserslist: ">= 4.21.0" - bin: - update-browserslist-db: cli.js - checksum: 10c0/682e8ecbf9de474a626f6462aa85927936cdd256fe584c6df2508b0df9f7362c44c957e9970df55dfe44d3623807d26316ea2c7d26b80bb76a16c56c37233c32 - languageName: node - linkType: hard - -"uri-js@npm:^4.2.2": - version: 4.4.1 - resolution: "uri-js@npm:4.4.1" - dependencies: - punycode: "npm:^2.1.0" - checksum: 10c0/4ef57b45aa820d7ac6496e9208559986c665e49447cb072744c13b66925a362d96dd5a46c4530a6b8e203e5db5fe849369444440cb22ecfc26c679359e5dfa3c - languageName: node - linkType: hard - -"use-sync-external-store@npm:^1.4.0, use-sync-external-store@npm:^1.5.0": - version: 1.5.0 - resolution: "use-sync-external-store@npm:1.5.0" - peerDependencies: - react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 - checksum: 10c0/1b8663515c0be34fa653feb724fdcce3984037c78dd4a18f68b2c8be55cc1a1084c578d5b75f158d41b5ddffc2bf5600766d1af3c19c8e329bb20af2ec6f52f4 - languageName: node - linkType: hard - -"util-deprecate@npm:~1.0.1": - version: 1.0.2 - resolution: "util-deprecate@npm:1.0.2" - checksum: 10c0/41a5bdd214df2f6c3ecf8622745e4a366c4adced864bc3c833739791aeeeb1838119af7daed4ba36428114b5c67dcda034a79c882e97e43c03e66a4dd7389942 - languageName: node - linkType: hard - -"uuid@npm:10.0.0": - version: 10.0.0 - resolution: "uuid@npm:10.0.0" - bin: - uuid: dist/bin/uuid - checksum: 10c0/eab18c27fe4ab9fb9709a5d5f40119b45f2ec8314f8d4cf12ce27e4c6f4ffa4a6321dc7db6c515068fa373c075b49691ba969f0010bf37f44c37ca40cd6bf7fe - languageName: node - linkType: hard - -"uuid@npm:^8.3.2": - version: 8.3.2 - resolution: "uuid@npm:8.3.2" - bin: - uuid: dist/bin/uuid - checksum: 10c0/bcbb807a917d374a49f475fae2e87fdca7da5e5530820ef53f65ba1d12131bd81a92ecf259cc7ce317cbe0f289e7d79fdfebcef9bfa3087c8c8a2fa304c9be54 - languageName: node - linkType: hard - -"varint@npm:^6.0.0": - version: 6.0.0 - resolution: "varint@npm:6.0.0" - checksum: 10c0/737fc37088a62ed3bd21466e318d21ca7ac4991d0f25546f518f017703be4ed0f9df1c5559f1dd533dddba4435a1b758fd9230e4772c1a930ef72b42f5c750fd - languageName: node - linkType: hard - -"vinyl-buffer@npm:^1.0.1": - version: 1.0.1 - resolution: "vinyl-buffer@npm:1.0.1" - dependencies: - bl: "npm:^1.2.1" - through2: "npm:^2.0.3" - checksum: 10c0/0dedb6bd3dbdd33ef77feae6535284d9fcd65be4826cb15c3afa91e77a0d384ff5c91a02768a96e6914671db5b65133c69ff44dd25b399494d2628dc71de259b - languageName: node - linkType: hard - -"virtua@npm:^0.33.2": - version: 0.33.7 - resolution: "virtua@npm:0.33.7" - peerDependencies: - react: ">=16.14.0" - react-dom: ">=16.14.0" - solid-js: ">=1.0" - svelte: ">=4.0" - vue: ">=3.2" - peerDependenciesMeta: - react: - optional: true - react-dom: - optional: true - solid-js: - optional: true - svelte: - optional: true - vue: - optional: true - checksum: 10c0/bc42cd6fc5119c18f5bc83c9c887e40120e4cb4bbf4abf499cc2543ce790b2d23868815975dd472643b07da5804cc5e11919ac0026fc26f540608793aa3eefba - languageName: node - linkType: hard - -"vite-node@npm:2.1.9": - version: 2.1.9 - resolution: "vite-node@npm:2.1.9" - dependencies: - cac: "npm:^6.7.14" - debug: "npm:^4.3.7" - es-module-lexer: "npm:^1.5.4" - pathe: "npm:^1.1.2" - vite: "npm:^5.0.0" - bin: - vite-node: vite-node.mjs - checksum: 10c0/0d3589f9f4e9cff696b5b49681fdb75d1638c75053728be52b4013f70792f38cb0120a9c15e3a4b22bdd6b795ad7c2da13bcaf47242d439f0906049e73bdd756 - languageName: node - linkType: hard - -"vite-plugin-top-level-await@npm:^1.4.4": - version: 1.6.0 - resolution: "vite-plugin-top-level-await@npm:1.6.0" - dependencies: - "@rollup/plugin-virtual": "npm:^3.0.2" - "@swc/core": "npm:^1.12.14" - "@swc/wasm": "npm:^1.12.14" - uuid: "npm:10.0.0" - peerDependencies: - vite: ">=2.8" - checksum: 10c0/499054e67f38ce925b1bae270cc334e1ee42194d00cae084792a5a00c7f246f27f3fd7798e0d4cfe07510f1c7620288bcbcf463570e7650f57977ac0591bf8da - languageName: node - linkType: hard - -"vite-plugin-watch@npm:^0.3.1": - version: 0.3.1 - resolution: "vite-plugin-watch@npm:0.3.1" - dependencies: - minimatch: "npm:^5.1.1" - checksum: 10c0/a14998cb9d678b5e1b92d68af0e40164d7fb413a4b112edfe44f831bda701a46f44e4253d612bd8101933a55364e6448ee3c7899616c57b0b0477812ebbffa4e - languageName: node - linkType: hard - -"vite-tsconfig-paths@npm:^4.3.2": - version: 4.3.2 - resolution: "vite-tsconfig-paths@npm:4.3.2" - dependencies: - debug: "npm:^4.1.1" - globrex: "npm:^0.1.2" - tsconfck: "npm:^3.0.3" - peerDependencies: - vite: "*" - peerDependenciesMeta: - vite: - optional: true - checksum: 10c0/f390ac1d1c3992fc5ac50f9274c1090f8b55ab34a89ea88893db9a6924a3b26c9f64bc1163615150ad100749db73b6b2cf1d57f6cd60df6e762ceb5b8ad30024 - languageName: node - linkType: hard - -"vite@npm:^5.0.0, vite@npm:^5.3.1": - version: 5.4.19 - resolution: "vite@npm:5.4.19" - dependencies: - esbuild: "npm:^0.21.3" - fsevents: "npm:~2.3.3" - postcss: "npm:^8.4.43" - rollup: "npm:^4.20.0" - peerDependencies: - "@types/node": ^18.0.0 || >=20.0.0 - less: "*" - lightningcss: ^1.21.0 - sass: "*" - sass-embedded: "*" - stylus: "*" - sugarss: "*" - terser: ^5.4.0 - dependenciesMeta: - fsevents: - optional: true - peerDependenciesMeta: - "@types/node": - optional: true - less: - optional: true - lightningcss: - optional: true - sass: - optional: true - sass-embedded: - optional: true - stylus: - optional: true - sugarss: - optional: true - terser: - optional: true - bin: - vite: bin/vite.js - checksum: 10c0/c97601234dba482cea5290f2a2ea0fcd65e1fab3df06718ea48adc8ceb14bc3129508216c4989329c618f6a0470b42f439677a207aef62b0c76f445091c2d89e - languageName: node - linkType: hard - -"vitest@npm:^2.1.1": - version: 2.1.9 - resolution: "vitest@npm:2.1.9" - dependencies: - "@vitest/expect": "npm:2.1.9" - "@vitest/mocker": "npm:2.1.9" - "@vitest/pretty-format": "npm:^2.1.9" - "@vitest/runner": "npm:2.1.9" - "@vitest/snapshot": "npm:2.1.9" - "@vitest/spy": "npm:2.1.9" - "@vitest/utils": "npm:2.1.9" - chai: "npm:^5.1.2" - debug: "npm:^4.3.7" - expect-type: "npm:^1.1.0" - magic-string: "npm:^0.30.12" - pathe: "npm:^1.1.2" - std-env: "npm:^3.8.0" - tinybench: "npm:^2.9.0" - tinyexec: "npm:^0.3.1" - tinypool: "npm:^1.0.1" - tinyrainbow: "npm:^1.2.0" - vite: "npm:^5.0.0" - vite-node: "npm:2.1.9" - why-is-node-running: "npm:^2.3.0" - peerDependencies: - "@edge-runtime/vm": "*" - "@types/node": ^18.0.0 || >=20.0.0 - "@vitest/browser": 2.1.9 - "@vitest/ui": 2.1.9 - happy-dom: "*" - jsdom: "*" - peerDependenciesMeta: - "@edge-runtime/vm": - optional: true - "@types/node": - optional: true - "@vitest/browser": - optional: true - "@vitest/ui": - optional: true - happy-dom: - optional: true - jsdom: - optional: true - bin: - vitest: vitest.mjs - checksum: 10c0/e339e16dccacf4589ff43cb1f38c7b4d14427956ae8ef48702af6820a9842347c2b6c77356aeddb040329759ca508a3cb2b104ddf78103ea5bc98ab8f2c3a54e - languageName: node - linkType: hard - -"which-boxed-primitive@npm:^1.1.0, which-boxed-primitive@npm:^1.1.1": - version: 1.1.1 - resolution: "which-boxed-primitive@npm:1.1.1" - dependencies: - is-bigint: "npm:^1.1.0" - is-boolean-object: "npm:^1.2.1" - is-number-object: "npm:^1.1.1" - is-string: "npm:^1.1.1" - is-symbol: "npm:^1.1.1" - checksum: 10c0/aceea8ede3b08dede7dce168f3883323f7c62272b49801716e8332ff750e7ae59a511ae088840bc6874f16c1b7fd296c05c949b0e5b357bfe3c431b98c417abe - languageName: node - linkType: hard - -"which-builtin-type@npm:^1.2.1": - version: 1.2.1 - resolution: "which-builtin-type@npm:1.2.1" - dependencies: - call-bound: "npm:^1.0.2" - function.prototype.name: "npm:^1.1.6" - has-tostringtag: "npm:^1.0.2" - is-async-function: "npm:^2.0.0" - is-date-object: "npm:^1.1.0" - is-finalizationregistry: "npm:^1.1.0" - is-generator-function: "npm:^1.0.10" - is-regex: "npm:^1.2.1" - is-weakref: "npm:^1.0.2" - isarray: "npm:^2.0.5" - which-boxed-primitive: "npm:^1.1.0" - which-collection: "npm:^1.0.2" - which-typed-array: "npm:^1.1.16" - checksum: 10c0/8dcf323c45e5c27887800df42fbe0431d0b66b1163849bb7d46b5a730ad6a96ee8bfe827d078303f825537844ebf20c02459de41239a0a9805e2fcb3cae0d471 - languageName: node - linkType: hard - -"which-collection@npm:^1.0.2": - version: 1.0.2 - resolution: "which-collection@npm:1.0.2" - dependencies: - is-map: "npm:^2.0.3" - is-set: "npm:^2.0.3" - is-weakmap: "npm:^2.0.2" - is-weakset: "npm:^2.0.3" - checksum: 10c0/3345fde20964525a04cdf7c4a96821f85f0cc198f1b2ecb4576e08096746d129eb133571998fe121c77782ac8f21cbd67745a3d35ce100d26d4e684c142ea1f2 - languageName: node - linkType: hard - -"which-typed-array@npm:^1.1.16, which-typed-array@npm:^1.1.19": - version: 1.1.19 - resolution: "which-typed-array@npm:1.1.19" - dependencies: - available-typed-arrays: "npm:^1.0.7" - call-bind: "npm:^1.0.8" - call-bound: "npm:^1.0.4" - for-each: "npm:^0.3.5" - get-proto: "npm:^1.0.1" - gopd: "npm:^1.2.0" - has-tostringtag: "npm:^1.0.2" - checksum: 10c0/702b5dc878addafe6c6300c3d0af5983b175c75fcb4f2a72dfc3dd38d93cf9e89581e4b29c854b16ea37e50a7d7fca5ae42ece5c273d8060dcd603b2404bbb3f - languageName: node - linkType: hard - -"which@npm:^2.0.1": - version: 2.0.2 - resolution: "which@npm:2.0.2" - dependencies: - isexe: "npm:^2.0.0" - bin: - node-which: ./bin/node-which - checksum: 10c0/66522872a768b60c2a65a57e8ad184e5372f5b6a9ca6d5f033d4b0dc98aff63995655a7503b9c0a2598936f532120e81dd8cc155e2e92ed662a2b9377cc4374f - languageName: node - linkType: hard - -"which@npm:^6.0.0": - version: 6.0.0 - resolution: "which@npm:6.0.0" - dependencies: - isexe: "npm:^3.1.1" - bin: - node-which: bin/which.js - checksum: 10c0/fe9d6463fe44a76232bb6e3b3181922c87510a5b250a98f1e43a69c99c079b3f42ddeca7e03d3e5f2241bf2d334f5a7657cfa868b97c109f3870625842f4cc15 - languageName: node - linkType: hard - -"why-is-node-running@npm:^2.3.0": - version: 2.3.0 - resolution: "why-is-node-running@npm:2.3.0" - dependencies: - siginfo: "npm:^2.0.0" - stackback: "npm:0.0.2" - bin: - why-is-node-running: cli.js - checksum: 10c0/1cde0b01b827d2cf4cb11db962f3958b9175d5d9e7ac7361d1a7b0e2dc6069a263e69118bd974c4f6d0a890ef4eedfe34cf3d5167ec14203dbc9a18620537054 - languageName: node - linkType: hard - -"word-wrap@npm:^1.2.5": - version: 1.2.5 - resolution: "word-wrap@npm:1.2.5" - checksum: 10c0/e0e4a1ca27599c92a6ca4c32260e8a92e8a44f4ef6ef93f803f8ed823f486e0889fc0b93be4db59c8d51b3064951d25e43d434e95dc8c960cc3a63d65d00ba20 - languageName: node - linkType: hard - -"writable-consumable-stream@npm:^4.1.0": - version: 4.2.0 - resolution: "writable-consumable-stream@npm:4.2.0" - dependencies: - consumable-stream: "npm:^3.0.0" - checksum: 10c0/d674d68c52025f988fa18c717e8ea56b3fd10c54fcb04c9716ad1ca45dbaaaf0d027b3b1e6e0e67d495d267ffc6b95ccf37cb62d15daefeec0c942bdae2f616b - languageName: node - linkType: hard - -"ws@npm:^8.18.0": - version: 8.18.3 - resolution: "ws@npm:8.18.3" - peerDependencies: - bufferutil: ^4.0.1 - utf-8-validate: ">=5.0.2" - peerDependenciesMeta: - bufferutil: - optional: true - utf-8-validate: - optional: true - checksum: 10c0/eac918213de265ef7cb3d4ca348b891a51a520d839aa51cdb8ca93d4fa7ff9f6ccb339ccee89e4075324097f0a55157c89fa3f7147bde9d8d7e90335dc087b53 - languageName: node - linkType: hard - -"xtend@npm:~4.0.1": - version: 4.0.2 - resolution: "xtend@npm:4.0.2" - checksum: 10c0/366ae4783eec6100f8a02dff02ac907bf29f9a00b82ac0264b4d8b832ead18306797e283cf19de776538babfdcb2101375ec5646b59f08c52128ac4ab812ed0e - languageName: node - linkType: hard - -"yallist@npm:^3.0.2": - version: 3.1.1 - resolution: "yallist@npm:3.1.1" - checksum: 10c0/c66a5c46bc89af1625476f7f0f2ec3653c1a1791d2f9407cfb4c2ba812a1e1c9941416d71ba9719876530e3340a99925f697142989371b72d93b9ee628afd8c1 - languageName: node - linkType: hard - -"yallist@npm:^4.0.0": - version: 4.0.0 - resolution: "yallist@npm:4.0.0" - checksum: 10c0/2286b5e8dbfe22204ab66e2ef5cc9bbb1e55dfc873bbe0d568aa943eb255d131890dfd5bf243637273d31119b870f49c18fcde2c6ffbb7a7a092b870dc90625a - languageName: node - linkType: hard - -"yallist@npm:^5.0.0": - version: 5.0.0 - resolution: "yallist@npm:5.0.0" - checksum: 10c0/a499c81ce6d4a1d260d4ea0f6d49ab4da09681e32c3f0472dee16667ed69d01dae63a3b81745a24bd78476ec4fcf856114cb4896ace738e01da34b2c42235416 - languageName: node - linkType: hard - -"yaml@npm:^1.10.0": - version: 1.10.2 - resolution: "yaml@npm:1.10.2" - checksum: 10c0/5c28b9eb7adc46544f28d9a8d20c5b3cb1215a886609a2fd41f51628d8aaa5878ccd628b755dbcd29f6bb4921bd04ffbc6dcc370689bb96e594e2f9813d2605f - languageName: node - linkType: hard - -"yocto-queue@npm:^0.1.0": - version: 0.1.0 - resolution: "yocto-queue@npm:0.1.0" - checksum: 10c0/dceb44c28578b31641e13695d200d34ec4ab3966a5729814d5445b194933c096b7ced71494ce53a0e8820685d1d010df8b2422e5bf2cdea7e469d97ffbea306f - languageName: node - linkType: hard - -"zod-validation-error@npm:^3.5.0 || ^4.0.0": - version: 4.0.2 - resolution: "zod-validation-error@npm:4.0.2" - peerDependencies: - zod: ^3.25.0 || ^4.0.0 - checksum: 10c0/0ccfec48c46de1be440b719cd02044d4abb89ed0e14c13e637cd55bf29102f67ccdba373f25def0fc7130e5f15025be4d557a7edcc95d5a3811599aade689e1b - languageName: node - linkType: hard - -"zod@npm:^3.25.0 || ^4.0.0": - version: 4.1.13 - resolution: "zod@npm:4.1.13" - checksum: 10c0/d7e74e82dba81a91ffc3239cd85bc034abe193a28f7087a94ab258a3e48e9a7ca4141920cac979a0d781495b48fc547777394149f26be04c3dc642f58bbc3941 - languageName: node - linkType: hard +# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. +# yarn lockfile v1 + + +"@babel/code-frame@^7.0.0", "@babel/code-frame@^7.27.1": + version "7.27.1" + resolved "https://registry.yarnpkg.com/@babel/code-frame/-/code-frame-7.27.1.tgz#200f715e66d52a23b221a9435534a91cc13ad5be" + integrity sha512-cjQ7ZlQ0Mv3b47hABuTevyTuYN4i+loJKGeV9flcCgIK37cCXRh+L1bd3iBHlynerhQ7BhCkn2BPbQUL+rGqFg== + dependencies: + "@babel/helper-validator-identifier" "^7.27.1" + js-tokens "^4.0.0" + picocolors "^1.1.1" + +"@babel/compat-data@^7.27.2": + version "7.28.5" + resolved "https://registry.yarnpkg.com/@babel/compat-data/-/compat-data-7.28.5.tgz#a8a4962e1567121ac0b3b487f52107443b455c7f" + integrity sha512-6uFXyCayocRbqhZOB+6XcuZbkMNimwfVGFji8CTZnCzOHVGvDqzvitu1re2AU5LROliz7eQPhB8CpAMvnx9EjA== + +"@babel/core@^7.24.4", "@babel/core@^7.28.0": + version "7.28.5" + resolved "https://registry.yarnpkg.com/@babel/core/-/core-7.28.5.tgz#4c81b35e51e1b734f510c99b07dfbc7bbbb48f7e" + integrity sha512-e7jT4DxYvIDLk1ZHmU/m/mB19rex9sv0c2ftBtjSBv+kVM/902eh0fINUzD7UwLLNR+jU585GxUJ8/EBfAM5fw== + dependencies: + "@babel/code-frame" "^7.27.1" + "@babel/generator" "^7.28.5" + "@babel/helper-compilation-targets" "^7.27.2" + "@babel/helper-module-transforms" "^7.28.3" + "@babel/helpers" "^7.28.4" + "@babel/parser" "^7.28.5" + "@babel/template" "^7.27.2" + "@babel/traverse" "^7.28.5" + "@babel/types" "^7.28.5" + "@jridgewell/remapping" "^2.3.5" + convert-source-map "^2.0.0" + debug "^4.1.0" + gensync "^1.0.0-beta.2" + json5 "^2.2.3" + semver "^6.3.1" + +"@babel/generator@^7.28.5": + version "7.28.5" + resolved "https://registry.yarnpkg.com/@babel/generator/-/generator-7.28.5.tgz#712722d5e50f44d07bc7ac9fe84438742dd61298" + integrity sha512-3EwLFhZ38J4VyIP6WNtt2kUdW9dokXA9Cr4IVIFHuCpZ3H8/YFOl5JjZHisrn1fATPBmKKqXzDFvh9fUwHz6CQ== + dependencies: + "@babel/parser" "^7.28.5" + "@babel/types" "^7.28.5" + "@jridgewell/gen-mapping" "^0.3.12" + "@jridgewell/trace-mapping" "^0.3.28" + jsesc "^3.0.2" + +"@babel/helper-compilation-targets@^7.27.2": + version "7.27.2" + resolved "https://registry.yarnpkg.com/@babel/helper-compilation-targets/-/helper-compilation-targets-7.27.2.tgz#46a0f6efab808d51d29ce96858dd10ce8732733d" + integrity sha512-2+1thGUUWWjLTYTHZWK1n8Yga0ijBz1XAhUXcKy81rd5g6yh7hGqMp45v7cadSbEHc9G3OTv45SyneRN3ps4DQ== + dependencies: + "@babel/compat-data" "^7.27.2" + "@babel/helper-validator-option" "^7.27.1" + browserslist "^4.24.0" + lru-cache "^5.1.1" + semver "^6.3.1" + +"@babel/helper-globals@^7.28.0": + version "7.28.0" + resolved "https://registry.yarnpkg.com/@babel/helper-globals/-/helper-globals-7.28.0.tgz#b9430df2aa4e17bc28665eadeae8aa1d985e6674" + integrity sha512-+W6cISkXFa1jXsDEdYA8HeevQT/FULhxzR99pxphltZcVaugps53THCeiWA8SguxxpSp3gKPiuYfSWopkLQ4hw== + +"@babel/helper-module-imports@^7.16.7", "@babel/helper-module-imports@^7.27.1": + version "7.27.1" + resolved "https://registry.yarnpkg.com/@babel/helper-module-imports/-/helper-module-imports-7.27.1.tgz#7ef769a323e2655e126673bb6d2d6913bbead204" + integrity sha512-0gSFWUPNXNopqtIPQvlD5WgXYI5GY2kP2cCvoT8kczjbfcfuIljTbcWrulD1CIPIX2gt1wghbDy08yE1p+/r3w== + dependencies: + "@babel/traverse" "^7.27.1" + "@babel/types" "^7.27.1" + +"@babel/helper-module-transforms@^7.28.3": + version "7.28.3" + resolved "https://registry.yarnpkg.com/@babel/helper-module-transforms/-/helper-module-transforms-7.28.3.tgz#a2b37d3da3b2344fe085dab234426f2b9a2fa5f6" + integrity sha512-gytXUbs8k2sXS9PnQptz5o0QnpLL51SwASIORY6XaBKF88nsOT0Zw9szLqlSGQDP/4TljBAD5y98p2U1fqkdsw== + dependencies: + "@babel/helper-module-imports" "^7.27.1" + "@babel/helper-validator-identifier" "^7.27.1" + "@babel/traverse" "^7.28.3" + +"@babel/helper-plugin-utils@^7.27.1": + version "7.27.1" + resolved "https://registry.yarnpkg.com/@babel/helper-plugin-utils/-/helper-plugin-utils-7.27.1.tgz#ddb2f876534ff8013e6c2b299bf4d39b3c51d44c" + integrity sha512-1gn1Up5YXka3YYAHGKpbideQ5Yjf1tDa9qYcgysz+cNCXukyLl6DjPXhD3VRwSb8c0J9tA4b2+rHEZtc6R0tlw== + +"@babel/helper-string-parser@^7.27.1": + version "7.27.1" + resolved "https://registry.yarnpkg.com/@babel/helper-string-parser/-/helper-string-parser-7.27.1.tgz#54da796097ab19ce67ed9f88b47bb2ec49367687" + integrity sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA== + +"@babel/helper-validator-identifier@^7.27.1", "@babel/helper-validator-identifier@^7.28.5": + version "7.28.5" + resolved "https://registry.yarnpkg.com/@babel/helper-validator-identifier/-/helper-validator-identifier-7.28.5.tgz#010b6938fab7cb7df74aa2bbc06aa503b8fe5fb4" + integrity sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q== + +"@babel/helper-validator-option@^7.27.1": + version "7.27.1" + resolved "https://registry.yarnpkg.com/@babel/helper-validator-option/-/helper-validator-option-7.27.1.tgz#fa52f5b1e7db1ab049445b421c4471303897702f" + integrity sha512-YvjJow9FxbhFFKDSuFnVCe2WxXk1zWc22fFePVNEaWJEu8IrZVlda6N0uHwzZrUM1il7NC9Mlp4MaJYbYd9JSg== + +"@babel/helpers@^7.28.4": + version "7.28.4" + resolved "https://registry.yarnpkg.com/@babel/helpers/-/helpers-7.28.4.tgz#fe07274742e95bdf7cf1443593eeb8926ab63827" + integrity sha512-HFN59MmQXGHVyYadKLVumYsA9dBFun/ldYxipEjzA4196jpLZd8UjEEBLkbEkvfYreDqJhZxYAWFPtrfhNpj4w== + dependencies: + "@babel/template" "^7.27.2" + "@babel/types" "^7.28.4" + +"@babel/parser@^7.1.0", "@babel/parser@^7.20.7", "@babel/parser@^7.24.4", "@babel/parser@^7.27.2", "@babel/parser@^7.28.5": + version "7.28.5" + resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.28.5.tgz#0b0225ee90362f030efd644e8034c99468893b08" + integrity sha512-KKBU1VGYR7ORr3At5HAtUQ+TV3SzRCXmA/8OdDZiLDBIZxVyzXuztPjfLd3BV1PRAQGCMWWSHYhL0F8d5uHBDQ== + dependencies: + "@babel/types" "^7.28.5" + +"@babel/plugin-transform-react-jsx-self@^7.27.1": + version "7.27.1" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-react-jsx-self/-/plugin-transform-react-jsx-self-7.27.1.tgz#af678d8506acf52c577cac73ff7fe6615c85fc92" + integrity sha512-6UzkCs+ejGdZ5mFFC/OCUrv028ab2fp1znZmCZjAOBKiBK2jXD1O+BPSfX8X2qjJ75fZBMSnQn3Rq2mrBJK2mw== + dependencies: + "@babel/helper-plugin-utils" "^7.27.1" + +"@babel/plugin-transform-react-jsx-source@^7.27.1": + version "7.27.1" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-react-jsx-source/-/plugin-transform-react-jsx-source-7.27.1.tgz#dcfe2c24094bb757bf73960374e7c55e434f19f0" + integrity sha512-zbwoTsBruTeKB9hSq73ha66iFeJHuaFkUbwvqElnygoNbj/jHRsSeokowZFN3CZ64IvEqcmmkVe89OPXc7ldAw== + dependencies: + "@babel/helper-plugin-utils" "^7.27.1" + +"@babel/runtime@^7.12.5", "@babel/runtime@^7.18.3", "@babel/runtime@^7.23.2", "@babel/runtime@^7.26.9", "@babel/runtime@^7.28.4", "@babel/runtime@^7.5.5", "@babel/runtime@^7.8.7", "@babel/runtime@^7.9.2": + version "7.28.4" + resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.28.4.tgz#a70226016fabe25c5783b2f22d3e1c9bc5ca3326" + integrity sha512-Q/N6JNWvIvPnLDvjlE1OUBLPQHH6l3CltCEsHIujp45zQUSSh8K+gHnaEX45yAT1nyngnINhvWtzN+Nb9D8RAQ== + +"@babel/template@^7.27.2": + version "7.27.2" + resolved "https://registry.yarnpkg.com/@babel/template/-/template-7.27.2.tgz#fa78ceed3c4e7b63ebf6cb39e5852fca45f6809d" + integrity sha512-LPDZ85aEJyYSd18/DkjNh4/y1ntkE5KwUHWTiqgRxruuZL2F1yuHligVHLvcHY2vMHXttKFpJn6LwfI7cw7ODw== + dependencies: + "@babel/code-frame" "^7.27.1" + "@babel/parser" "^7.27.2" + "@babel/types" "^7.27.1" + +"@babel/traverse@^7.27.1", "@babel/traverse@^7.28.3", "@babel/traverse@^7.28.5": + version "7.28.5" + resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.28.5.tgz#450cab9135d21a7a2ca9d2d35aa05c20e68c360b" + integrity sha512-TCCj4t55U90khlYkVV/0TfkJkAkUg3jZFA3Neb7unZT8CPok7iiRfaX0F+WnqWqt7OxhOn0uBKXCw4lbL8W0aQ== + dependencies: + "@babel/code-frame" "^7.27.1" + "@babel/generator" "^7.28.5" + "@babel/helper-globals" "^7.28.0" + "@babel/parser" "^7.28.5" + "@babel/template" "^7.27.2" + "@babel/types" "^7.28.5" + debug "^4.3.1" + +"@babel/types@^7.0.0", "@babel/types@^7.20.7", "@babel/types@^7.26.0", "@babel/types@^7.27.1", "@babel/types@^7.28.2", "@babel/types@^7.28.4", "@babel/types@^7.28.5": + version "7.28.5" + resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.28.5.tgz#10fc405f60897c35f07e85493c932c7b5ca0592b" + integrity sha512-qQ5m48eI/MFLQ5PxQj4PFaprjyCTLI37ElWMmNs0K8Lk3dVeOdNpB3ks8jc7yM5CDmVC73eMVk/trk3fgmrUpA== + dependencies: + "@babel/helper-string-parser" "^7.27.1" + "@babel/helper-validator-identifier" "^7.28.5" + +"@emotion/babel-plugin@^11.13.5": + version "11.13.5" + resolved "https://registry.yarnpkg.com/@emotion/babel-plugin/-/babel-plugin-11.13.5.tgz#eab8d65dbded74e0ecfd28dc218e75607c4e7bc0" + integrity sha512-pxHCpT2ex+0q+HH91/zsdHkw/lXd468DIN2zvfvLtPKLLMo6gQj7oLObq8PhkrxOZb/gGCq03S3Z7PDhS8pduQ== + dependencies: + "@babel/helper-module-imports" "^7.16.7" + "@babel/runtime" "^7.18.3" + "@emotion/hash" "^0.9.2" + "@emotion/memoize" "^0.9.0" + "@emotion/serialize" "^1.3.3" + babel-plugin-macros "^3.1.0" + convert-source-map "^1.5.0" + escape-string-regexp "^4.0.0" + find-root "^1.1.0" + source-map "^0.5.7" + stylis "4.2.0" + +"@emotion/cache@^11.14.0": + version "11.14.0" + resolved "https://registry.yarnpkg.com/@emotion/cache/-/cache-11.14.0.tgz#ee44b26986eeb93c8be82bb92f1f7a9b21b2ed76" + integrity sha512-L/B1lc/TViYk4DcpGxtAVbx0ZyiKM5ktoIyafGkH6zg/tj+mA+NE//aPYKG0k8kCHSHVJrpLpcAlOBEXQ3SavA== + dependencies: + "@emotion/memoize" "^0.9.0" + "@emotion/sheet" "^1.4.0" + "@emotion/utils" "^1.4.2" + "@emotion/weak-memoize" "^0.4.0" + stylis "4.2.0" + +"@emotion/hash@^0.9.2": + version "0.9.2" + resolved "https://registry.yarnpkg.com/@emotion/hash/-/hash-0.9.2.tgz#ff9221b9f58b4dfe61e619a7788734bd63f6898b" + integrity sha512-MyqliTZGuOm3+5ZRSaaBGP3USLw6+EGykkwZns2EPC5g8jJ4z9OrdZY9apkl3+UP9+sdz76YYkwCKP5gh8iY3g== + +"@emotion/is-prop-valid@^1.3.0": + version "1.4.0" + resolved "https://registry.yarnpkg.com/@emotion/is-prop-valid/-/is-prop-valid-1.4.0.tgz#e9ad47adff0b5c94c72db3669ce46de33edf28c0" + integrity sha512-QgD4fyscGcbbKwJmqNvUMSE02OsHUa+lAWKdEUIJKgqe5IwRSKd7+KhibEWdaKwgjLj0DRSHA9biAIqGBk05lw== + dependencies: + "@emotion/memoize" "^0.9.0" + +"@emotion/memoize@^0.9.0": + version "0.9.0" + resolved "https://registry.yarnpkg.com/@emotion/memoize/-/memoize-0.9.0.tgz#745969d649977776b43fc7648c556aaa462b4102" + integrity sha512-30FAj7/EoJ5mwVPOWhAyCX+FPfMDrVecJAM+Iw9NRoSl4BBAQeqj4cApHHUXOVvIPgLVDsCFoz/hGD+5QQD1GQ== + +"@emotion/react@^11.14.0": + version "11.14.0" + resolved "https://registry.yarnpkg.com/@emotion/react/-/react-11.14.0.tgz#cfaae35ebc67dd9ef4ea2e9acc6cd29e157dd05d" + integrity sha512-O000MLDBDdk/EohJPFUqvnp4qnHeYkVP5B0xEG0D/L7cOKP9kefu2DXn8dj74cQfsEzUqh+sr1RzFqiL1o+PpA== + dependencies: + "@babel/runtime" "^7.18.3" + "@emotion/babel-plugin" "^11.13.5" + "@emotion/cache" "^11.14.0" + "@emotion/serialize" "^1.3.3" + "@emotion/use-insertion-effect-with-fallbacks" "^1.2.0" + "@emotion/utils" "^1.4.2" + "@emotion/weak-memoize" "^0.4.0" + hoist-non-react-statics "^3.3.1" + +"@emotion/serialize@^1.3.3": + version "1.3.3" + resolved "https://registry.yarnpkg.com/@emotion/serialize/-/serialize-1.3.3.tgz#d291531005f17d704d0463a032fe679f376509e8" + integrity sha512-EISGqt7sSNWHGI76hC7x1CksiXPahbxEOrC5RjmFRJTqLyEK9/9hZvBbiYn70dw4wuwMKiEMCUlR6ZXTSWQqxA== + dependencies: + "@emotion/hash" "^0.9.2" + "@emotion/memoize" "^0.9.0" + "@emotion/unitless" "^0.10.0" + "@emotion/utils" "^1.4.2" + csstype "^3.0.2" + +"@emotion/sheet@^1.4.0": + version "1.4.0" + resolved "https://registry.yarnpkg.com/@emotion/sheet/-/sheet-1.4.0.tgz#c9299c34d248bc26e82563735f78953d2efca83c" + integrity sha512-fTBW9/8r2w3dXWYM4HCB1Rdp8NLibOw2+XELH5m5+AkWiL/KqYX6dc0kKYlaYyKjrQ6ds33MCdMPEwgs2z1rqg== + +"@emotion/styled@^11.14.0": + version "11.14.1" + resolved "https://registry.yarnpkg.com/@emotion/styled/-/styled-11.14.1.tgz#8c34bed2948e83e1980370305614c20955aacd1c" + integrity sha512-qEEJt42DuToa3gurlH4Qqc1kVpNq8wO8cJtDzU46TjlzWjDlsVyevtYCRijVq3SrHsROS+gVQ8Fnea108GnKzw== + dependencies: + "@babel/runtime" "^7.18.3" + "@emotion/babel-plugin" "^11.13.5" + "@emotion/is-prop-valid" "^1.3.0" + "@emotion/serialize" "^1.3.3" + "@emotion/use-insertion-effect-with-fallbacks" "^1.2.0" + "@emotion/utils" "^1.4.2" + +"@emotion/unitless@^0.10.0": + version "0.10.0" + resolved "https://registry.yarnpkg.com/@emotion/unitless/-/unitless-0.10.0.tgz#2af2f7c7e5150f497bdabd848ce7b218a27cf745" + integrity sha512-dFoMUuQA20zvtVTuxZww6OHoJYgrzfKM1t52mVySDJnMSEa08ruEvdYQbhvyu6soU+NeLVd3yKfTfT0NeV6qGg== + +"@emotion/use-insertion-effect-with-fallbacks@^1.2.0": + version "1.2.0" + resolved "https://registry.yarnpkg.com/@emotion/use-insertion-effect-with-fallbacks/-/use-insertion-effect-with-fallbacks-1.2.0.tgz#8a8cb77b590e09affb960f4ff1e9a89e532738bf" + integrity sha512-yJMtVdH59sxi/aVJBpk9FQq+OR8ll5GT8oWd57UpeaKEVGab41JWaCFA7FRLoMLloOZF/c/wsPoe+bfGmRKgDg== + +"@emotion/utils@^1.4.2": + version "1.4.2" + resolved "https://registry.yarnpkg.com/@emotion/utils/-/utils-1.4.2.tgz#6df6c45881fcb1c412d6688a311a98b7f59c1b52" + integrity sha512-3vLclRofFziIa3J2wDh9jjbkUz9qk5Vi3IZ/FSTKViB0k+ef0fPV7dYrUIugbgupYDx7v9ud/SjrtEP8Y4xLoA== + +"@emotion/weak-memoize@^0.4.0": + version "0.4.0" + resolved "https://registry.yarnpkg.com/@emotion/weak-memoize/-/weak-memoize-0.4.0.tgz#5e13fac887f08c44f76b0ccaf3370eb00fec9bb6" + integrity sha512-snKqtPW01tN0ui7yu9rGv69aJXr/a/Ywvl11sUjNtEcRc+ng/mQriFL0wLXMef74iHa/EkftbDzU9F8iFbH+zg== + +"@esbuild/aix-ppc64@0.21.5": + version "0.21.5" + resolved "https://registry.yarnpkg.com/@esbuild/aix-ppc64/-/aix-ppc64-0.21.5.tgz#c7184a326533fcdf1b8ee0733e21c713b975575f" + integrity sha512-1SDgH6ZSPTlggy1yI6+Dbkiz8xzpHJEVAlF/AM1tHPLsf5STom9rwtjE4hKAF20FfXXNTFqEYXyJNWh1GiZedQ== + +"@esbuild/android-arm64@0.21.5": + version "0.21.5" + resolved "https://registry.yarnpkg.com/@esbuild/android-arm64/-/android-arm64-0.21.5.tgz#09d9b4357780da9ea3a7dfb833a1f1ff439b4052" + integrity sha512-c0uX9VAUBQ7dTDCjq+wdyGLowMdtR/GoC2U5IYk/7D1H1JYC0qseD7+11iMP2mRLN9RcCMRcjC4YMclCzGwS/A== + +"@esbuild/android-arm@0.21.5": + version "0.21.5" + resolved "https://registry.yarnpkg.com/@esbuild/android-arm/-/android-arm-0.21.5.tgz#9b04384fb771926dfa6d7ad04324ecb2ab9b2e28" + integrity sha512-vCPvzSjpPHEi1siZdlvAlsPxXl7WbOVUBBAowWug4rJHb68Ox8KualB+1ocNvT5fjv6wpkX6o/iEpbDrf68zcg== + +"@esbuild/android-x64@0.21.5": + version "0.21.5" + resolved "https://registry.yarnpkg.com/@esbuild/android-x64/-/android-x64-0.21.5.tgz#29918ec2db754cedcb6c1b04de8cd6547af6461e" + integrity sha512-D7aPRUUNHRBwHxzxRvp856rjUHRFW1SdQATKXH2hqA0kAZb1hKmi02OpYRacl0TxIGz/ZmXWlbZgjwWYaCakTA== + +"@esbuild/darwin-arm64@0.21.5": + version "0.21.5" + resolved "https://registry.yarnpkg.com/@esbuild/darwin-arm64/-/darwin-arm64-0.21.5.tgz#e495b539660e51690f3928af50a76fb0a6ccff2a" + integrity sha512-DwqXqZyuk5AiWWf3UfLiRDJ5EDd49zg6O9wclZ7kUMv2WRFr4HKjXp/5t8JZ11QbQfUS6/cRCKGwYhtNAY88kQ== + +"@esbuild/darwin-x64@0.21.5": + version "0.21.5" + resolved "https://registry.yarnpkg.com/@esbuild/darwin-x64/-/darwin-x64-0.21.5.tgz#c13838fa57372839abdddc91d71542ceea2e1e22" + integrity sha512-se/JjF8NlmKVG4kNIuyWMV/22ZaerB+qaSi5MdrXtd6R08kvs2qCN4C09miupktDitvh8jRFflwGFBQcxZRjbw== + +"@esbuild/freebsd-arm64@0.21.5": + version "0.21.5" + resolved "https://registry.yarnpkg.com/@esbuild/freebsd-arm64/-/freebsd-arm64-0.21.5.tgz#646b989aa20bf89fd071dd5dbfad69a3542e550e" + integrity sha512-5JcRxxRDUJLX8JXp/wcBCy3pENnCgBR9bN6JsY4OmhfUtIHe3ZW0mawA7+RDAcMLrMIZaf03NlQiX9DGyB8h4g== + +"@esbuild/freebsd-x64@0.21.5": + version "0.21.5" + resolved "https://registry.yarnpkg.com/@esbuild/freebsd-x64/-/freebsd-x64-0.21.5.tgz#aa615cfc80af954d3458906e38ca22c18cf5c261" + integrity sha512-J95kNBj1zkbMXtHVH29bBriQygMXqoVQOQYA+ISs0/2l3T9/kj42ow2mpqerRBxDJnmkUDCaQT/dfNXWX/ZZCQ== + +"@esbuild/linux-arm64@0.21.5": + version "0.21.5" + resolved "https://registry.yarnpkg.com/@esbuild/linux-arm64/-/linux-arm64-0.21.5.tgz#70ac6fa14f5cb7e1f7f887bcffb680ad09922b5b" + integrity sha512-ibKvmyYzKsBeX8d8I7MH/TMfWDXBF3db4qM6sy+7re0YXya+K1cem3on9XgdT2EQGMu4hQyZhan7TeQ8XkGp4Q== + +"@esbuild/linux-arm@0.21.5": + version "0.21.5" + resolved "https://registry.yarnpkg.com/@esbuild/linux-arm/-/linux-arm-0.21.5.tgz#fc6fd11a8aca56c1f6f3894f2bea0479f8f626b9" + integrity sha512-bPb5AHZtbeNGjCKVZ9UGqGwo8EUu4cLq68E95A53KlxAPRmUyYv2D6F0uUI65XisGOL1hBP5mTronbgo+0bFcA== + +"@esbuild/linux-ia32@0.21.5": + version "0.21.5" + resolved "https://registry.yarnpkg.com/@esbuild/linux-ia32/-/linux-ia32-0.21.5.tgz#3271f53b3f93e3d093d518d1649d6d68d346ede2" + integrity sha512-YvjXDqLRqPDl2dvRODYmmhz4rPeVKYvppfGYKSNGdyZkA01046pLWyRKKI3ax8fbJoK5QbxblURkwK/MWY18Tg== + +"@esbuild/linux-loong64@0.21.5": + version "0.21.5" + resolved "https://registry.yarnpkg.com/@esbuild/linux-loong64/-/linux-loong64-0.21.5.tgz#ed62e04238c57026aea831c5a130b73c0f9f26df" + integrity sha512-uHf1BmMG8qEvzdrzAqg2SIG/02+4/DHB6a9Kbya0XDvwDEKCoC8ZRWI5JJvNdUjtciBGFQ5PuBlpEOXQj+JQSg== + +"@esbuild/linux-mips64el@0.21.5": + version "0.21.5" + resolved "https://registry.yarnpkg.com/@esbuild/linux-mips64el/-/linux-mips64el-0.21.5.tgz#e79b8eb48bf3b106fadec1ac8240fb97b4e64cbe" + integrity sha512-IajOmO+KJK23bj52dFSNCMsz1QP1DqM6cwLUv3W1QwyxkyIWecfafnI555fvSGqEKwjMXVLokcV5ygHW5b3Jbg== + +"@esbuild/linux-ppc64@0.21.5": + version "0.21.5" + resolved "https://registry.yarnpkg.com/@esbuild/linux-ppc64/-/linux-ppc64-0.21.5.tgz#5f2203860a143b9919d383ef7573521fb154c3e4" + integrity sha512-1hHV/Z4OEfMwpLO8rp7CvlhBDnjsC3CttJXIhBi+5Aj5r+MBvy4egg7wCbe//hSsT+RvDAG7s81tAvpL2XAE4w== + +"@esbuild/linux-riscv64@0.21.5": + version "0.21.5" + resolved "https://registry.yarnpkg.com/@esbuild/linux-riscv64/-/linux-riscv64-0.21.5.tgz#07bcafd99322d5af62f618cb9e6a9b7f4bb825dc" + integrity sha512-2HdXDMd9GMgTGrPWnJzP2ALSokE/0O5HhTUvWIbD3YdjME8JwvSCnNGBnTThKGEB91OZhzrJ4qIIxk/SBmyDDA== + +"@esbuild/linux-s390x@0.21.5": + version "0.21.5" + resolved "https://registry.yarnpkg.com/@esbuild/linux-s390x/-/linux-s390x-0.21.5.tgz#b7ccf686751d6a3e44b8627ababc8be3ef62d8de" + integrity sha512-zus5sxzqBJD3eXxwvjN1yQkRepANgxE9lgOW2qLnmr8ikMTphkjgXu1HR01K4FJg8h1kEEDAqDcZQtbrRnB41A== + +"@esbuild/linux-x64@0.21.5": + version "0.21.5" + resolved "https://registry.yarnpkg.com/@esbuild/linux-x64/-/linux-x64-0.21.5.tgz#6d8f0c768e070e64309af8004bb94e68ab2bb3b0" + integrity sha512-1rYdTpyv03iycF1+BhzrzQJCdOuAOtaqHTWJZCWvijKD2N5Xu0TtVC8/+1faWqcP9iBCWOmjmhoH94dH82BxPQ== + +"@esbuild/netbsd-x64@0.21.5": + version "0.21.5" + resolved "https://registry.yarnpkg.com/@esbuild/netbsd-x64/-/netbsd-x64-0.21.5.tgz#bbe430f60d378ecb88decb219c602667387a6047" + integrity sha512-Woi2MXzXjMULccIwMnLciyZH4nCIMpWQAs049KEeMvOcNADVxo0UBIQPfSmxB3CWKedngg7sWZdLvLczpe0tLg== + +"@esbuild/openbsd-x64@0.21.5": + version "0.21.5" + resolved "https://registry.yarnpkg.com/@esbuild/openbsd-x64/-/openbsd-x64-0.21.5.tgz#99d1cf2937279560d2104821f5ccce220cb2af70" + integrity sha512-HLNNw99xsvx12lFBUwoT8EVCsSvRNDVxNpjZ7bPn947b8gJPzeHWyNVhFsaerc0n3TsbOINvRP2byTZ5LKezow== + +"@esbuild/sunos-x64@0.21.5": + version "0.21.5" + resolved "https://registry.yarnpkg.com/@esbuild/sunos-x64/-/sunos-x64-0.21.5.tgz#08741512c10d529566baba837b4fe052c8f3487b" + integrity sha512-6+gjmFpfy0BHU5Tpptkuh8+uw3mnrvgs+dSPQXQOv3ekbordwnzTVEb4qnIvQcYXq6gzkyTnoZ9dZG+D4garKg== + +"@esbuild/win32-arm64@0.21.5": + version "0.21.5" + resolved "https://registry.yarnpkg.com/@esbuild/win32-arm64/-/win32-arm64-0.21.5.tgz#675b7385398411240735016144ab2e99a60fc75d" + integrity sha512-Z0gOTd75VvXqyq7nsl93zwahcTROgqvuAcYDUr+vOv8uHhNSKROyU961kgtCD1e95IqPKSQKH7tBTslnS3tA8A== + +"@esbuild/win32-ia32@0.21.5": + version "0.21.5" + resolved "https://registry.yarnpkg.com/@esbuild/win32-ia32/-/win32-ia32-0.21.5.tgz#1bfc3ce98aa6ca9a0969e4d2af72144c59c1193b" + integrity sha512-SWXFF1CL2RVNMaVs+BBClwtfZSvDgtL//G/smwAc5oVK/UPu2Gu9tIaRgFmYFFKrmg3SyAjSrElf0TiJ1v8fYA== + +"@esbuild/win32-x64@0.21.5": + version "0.21.5" + resolved "https://registry.yarnpkg.com/@esbuild/win32-x64/-/win32-x64-0.21.5.tgz#acad351d582d157bb145535db2a6ff53dd514b5c" + integrity sha512-tQd/1efJuzPC6rCFwEvLtci/xNFcTZknmXs98FYDfGE4wP9ClFV98nyKrzJKVPMhdDnjzLhdUyMX4PsQAPjwIw== + +"@eslint-community/eslint-utils@^4.8.0", "@eslint-community/eslint-utils@^4.9.1": + version "4.9.1" + resolved "https://registry.yarnpkg.com/@eslint-community/eslint-utils/-/eslint-utils-4.9.1.tgz#4e90af67bc51ddee6cdef5284edf572ec376b595" + integrity sha512-phrYmNiYppR7znFEdqgfWHXR6NCkZEK7hwWDHZUjit/2/U0r6XvkDl0SYnoM51Hq7FhCGdLDT6zxCCOY1hexsQ== + dependencies: + eslint-visitor-keys "^3.4.3" + +"@eslint-community/regexpp@^4.12.1", "@eslint-community/regexpp@^4.12.2": + version "4.12.2" + resolved "https://registry.yarnpkg.com/@eslint-community/regexpp/-/regexpp-4.12.2.tgz#bccdf615bcf7b6e8db830ec0b8d21c9a25de597b" + integrity sha512-EriSTlt5OC9/7SXkRSCAhfSxxoSUgBm33OH+IkwbdpgoqsSsUg7y3uh+IICI/Qg4BBWr3U2i39RpmycbxMq4ew== + +"@eslint/config-array@^0.21.1": + version "0.21.1" + resolved "https://registry.yarnpkg.com/@eslint/config-array/-/config-array-0.21.1.tgz#7d1b0060fea407f8301e932492ba8c18aff29713" + integrity sha512-aw1gNayWpdI/jSYVgzN5pL0cfzU02GT3NBpeT/DXbx1/1x7ZKxFPd9bwrzygx/qiwIQiJ1sw/zD8qY/kRvlGHA== + dependencies: + "@eslint/object-schema" "^2.1.7" + debug "^4.3.1" + minimatch "^3.1.2" + +"@eslint/config-helpers@^0.4.2": + version "0.4.2" + resolved "https://registry.yarnpkg.com/@eslint/config-helpers/-/config-helpers-0.4.2.tgz#1bd006ceeb7e2e55b2b773ab318d300e1a66aeda" + integrity sha512-gBrxN88gOIf3R7ja5K9slwNayVcZgK6SOUORm2uBzTeIEfeVaIhOpCtTox3P6R7o2jLFwLFTLnC7kU/RGcYEgw== + dependencies: + "@eslint/core" "^0.17.0" + +"@eslint/core@^0.17.0": + version "0.17.0" + resolved "https://registry.yarnpkg.com/@eslint/core/-/core-0.17.0.tgz#77225820413d9617509da9342190a2019e78761c" + integrity sha512-yL/sLrpmtDaFEiUj1osRP4TI2MDz1AddJL+jZ7KSqvBuliN4xqYY54IfdN8qD8Toa6g1iloph1fxQNkjOxrrpQ== + dependencies: + "@types/json-schema" "^7.0.15" + +"@eslint/eslintrc@^3.3.1": + version "3.3.3" + resolved "https://registry.yarnpkg.com/@eslint/eslintrc/-/eslintrc-3.3.3.tgz#26393a0806501b5e2b6a43aa588a4d8df67880ac" + integrity sha512-Kr+LPIUVKz2qkx1HAMH8q1q6azbqBAsXJUxBl/ODDuVPX45Z9DfwB8tPjTi6nNZ8BuM3nbJxC5zCAg5elnBUTQ== + dependencies: + ajv "^6.12.4" + debug "^4.3.2" + espree "^10.0.1" + globals "^14.0.0" + ignore "^5.2.0" + import-fresh "^3.2.1" + js-yaml "^4.1.1" + minimatch "^3.1.2" + strip-json-comments "^3.1.1" + +"@eslint/js@9.39.2", "@eslint/js@^9.9.0": + version "9.39.2" + resolved "https://registry.yarnpkg.com/@eslint/js/-/js-9.39.2.tgz#2d4b8ec4c3ea13c1b3748e0c97ecd766bdd80599" + integrity sha512-q1mjIoW1VX4IvSocvM/vbTiveKC4k9eLrajNEuSsmjymSDEbpGddtpfOoN7YGAqBK3NG+uqo8ia4PDTt8buCYA== + +"@eslint/object-schema@^2.1.7": + version "2.1.7" + resolved "https://registry.yarnpkg.com/@eslint/object-schema/-/object-schema-2.1.7.tgz#6e2126a1347e86a4dedf8706ec67ff8e107ebbad" + integrity sha512-VtAOaymWVfZcmZbp6E2mympDIHvyjXs/12LqWYjVw6qjrfF+VK+fyG33kChz3nnK+SU5/NeHOqrTEHS8sXO3OA== + +"@eslint/plugin-kit@^0.4.1": + version "0.4.1" + resolved "https://registry.yarnpkg.com/@eslint/plugin-kit/-/plugin-kit-0.4.1.tgz#9779e3fd9b7ee33571a57435cf4335a1794a6cb2" + integrity sha512-43/qtrDUokr7LJqoF2c3+RInu/t4zfrpYdoSDfYyhg52rwLV6TnOvdG4fXm7IkSB3wErkcmJS9iEhjVtOSEjjA== + dependencies: + "@eslint/core" "^0.17.0" + levn "^0.4.1" + +"@fontsource/roboto@^5.1.0": + version "5.2.9" + resolved "https://registry.yarnpkg.com/@fontsource/roboto/-/roboto-5.2.9.tgz#55092cf992ab8d5082be73b79b42f383a9c0903c" + integrity sha512-ZTkyHiPk74B/aj8BZWbsxD5Yu+Lq+nR64eV4wirlrac2qXR7jYk2h6JlLYuOuoruTkGQWNw2fMuKNavw7/rg0w== + +"@humanfs/core@^0.19.1": + version "0.19.1" + resolved "https://registry.yarnpkg.com/@humanfs/core/-/core-0.19.1.tgz#17c55ca7d426733fe3c561906b8173c336b40a77" + integrity sha512-5DyQ4+1JEUzejeK1JGICcideyfUbGixgS9jNgex5nqkW+cY7WZhxBigmieN5Qnw9ZosSNVC9KQKyb+GUaGyKUA== + +"@humanfs/node@^0.16.6": + version "0.16.7" + resolved "https://registry.yarnpkg.com/@humanfs/node/-/node-0.16.7.tgz#822cb7b3a12c5a240a24f621b5a2413e27a45f26" + integrity sha512-/zUx+yOsIrG4Y43Eh2peDeKCxlRt/gET6aHfaKpuq267qXdYDFViVHfMaLyygZOnl0kGWxFIgsBy8QFuTLUXEQ== + dependencies: + "@humanfs/core" "^0.19.1" + "@humanwhocodes/retry" "^0.4.0" + +"@humanwhocodes/module-importer@^1.0.1": + version "1.0.1" + resolved "https://registry.yarnpkg.com/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz#af5b2691a22b44be847b0ca81641c5fb6ad0172c" + integrity sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA== + +"@humanwhocodes/retry@^0.4.0", "@humanwhocodes/retry@^0.4.2": + version "0.4.3" + resolved "https://registry.yarnpkg.com/@humanwhocodes/retry/-/retry-0.4.3.tgz#c2b9d2e374ee62c586d3adbea87199b1d7a7a6ba" + integrity sha512-bV0Tgo9K4hfPCek+aMAn81RppFKv2ySDQeMoSZuvTASywNTnVJCArCZE2FWqpvIatKu7VMRLWlR1EazvVhDyhQ== + +"@jridgewell/gen-mapping@^0.3.12", "@jridgewell/gen-mapping@^0.3.5": + version "0.3.13" + resolved "https://registry.yarnpkg.com/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz#6342a19f44347518c93e43b1ac69deb3c4656a1f" + integrity sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA== + dependencies: + "@jridgewell/sourcemap-codec" "^1.5.0" + "@jridgewell/trace-mapping" "^0.3.24" + +"@jridgewell/remapping@^2.3.5": + version "2.3.5" + resolved "https://registry.yarnpkg.com/@jridgewell/remapping/-/remapping-2.3.5.tgz#375c476d1972947851ba1e15ae8f123047445aa1" + integrity sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ== + dependencies: + "@jridgewell/gen-mapping" "^0.3.5" + "@jridgewell/trace-mapping" "^0.3.24" + +"@jridgewell/resolve-uri@^3.1.0": + version "3.1.2" + resolved "https://registry.yarnpkg.com/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz#7a0ee601f60f99a20c7c7c5ff0c80388c1189bd6" + integrity sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw== + +"@jridgewell/sourcemap-codec@^1.4.14", "@jridgewell/sourcemap-codec@^1.5.0", "@jridgewell/sourcemap-codec@^1.5.5": + version "1.5.5" + resolved "https://registry.yarnpkg.com/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz#6912b00d2c631c0d15ce1a7ab57cd657f2a8f8ba" + integrity sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og== + +"@jridgewell/trace-mapping@^0.3.24", "@jridgewell/trace-mapping@^0.3.28": + version "0.3.31" + resolved "https://registry.yarnpkg.com/@jridgewell/trace-mapping/-/trace-mapping-0.3.31.tgz#db15d6781c931f3a251a3dac39501c98a6082fd0" + integrity sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw== + dependencies: + "@jridgewell/resolve-uri" "^3.1.0" + "@jridgewell/sourcemap-codec" "^1.4.14" + +"@mui/core-downloads-tracker@^7.3.6": + version "7.3.6" + resolved "https://registry.yarnpkg.com/@mui/core-downloads-tracker/-/core-downloads-tracker-7.3.6.tgz#e7e3a4dc161a377be8224aa988410e89571ab40a" + integrity sha512-QaYtTHlr8kDFN5mE1wbvVARRKH7Fdw1ZuOjBJcFdVpfNfRYKF3QLT4rt+WaB6CKJvpqxRsmEo0kpYinhH5GeHg== + +"@mui/icons-material@^7.1.1": + version "7.3.6" + resolved "https://registry.yarnpkg.com/@mui/icons-material/-/icons-material-7.3.6.tgz#c0092afd04a661603d9751c851e0099a27c1d556" + integrity sha512-0FfkXEj22ysIq5pa41A2NbcAhJSvmcZQ/vcTIbjDsd6hlslG82k5BEBqqS0ZJprxwIL3B45qpJ+bPHwJPlF7uQ== + dependencies: + "@babel/runtime" "^7.28.4" + +"@mui/material@^7.1.1": + version "7.3.6" + resolved "https://registry.yarnpkg.com/@mui/material/-/material-7.3.6.tgz#6bd4705ca97d80fd5ae1b6b2b7c56ba0cfab0d6a" + integrity sha512-R4DaYF3dgCQCUAkr4wW1w26GHXcf5rCmBRHVBuuvJvaGLmZdD8EjatP80Nz5JCw0KxORAzwftnHzXVnjR8HnFw== + dependencies: + "@babel/runtime" "^7.28.4" + "@mui/core-downloads-tracker" "^7.3.6" + "@mui/system" "^7.3.6" + "@mui/types" "^7.4.9" + "@mui/utils" "^7.3.6" + "@popperjs/core" "^2.11.8" + "@types/react-transition-group" "^4.4.12" + clsx "^2.1.1" + csstype "^3.1.3" + prop-types "^15.8.1" + react-is "^19.2.0" + react-transition-group "^4.4.5" + +"@mui/private-theming@^7.3.6": + version "7.3.6" + resolved "https://registry.yarnpkg.com/@mui/private-theming/-/private-theming-7.3.6.tgz#1ca65a08e8f7f538d9a10ba974f1f4db5231a969" + integrity sha512-Ws9wZpqM+FlnbZXaY/7yvyvWQo1+02Tbx50mVdNmzWEi51C51y56KAbaDCYyulOOBL6BJxuaqG8rNNuj7ivVyw== + dependencies: + "@babel/runtime" "^7.28.4" + "@mui/utils" "^7.3.6" + prop-types "^15.8.1" + +"@mui/styled-engine@^7.3.6": + version "7.3.6" + resolved "https://registry.yarnpkg.com/@mui/styled-engine/-/styled-engine-7.3.6.tgz#dde8e6ae32c9b5b400dcd37afd9514a5344f7d91" + integrity sha512-+wiYbtvj+zyUkmDB+ysH6zRjuQIJ+CM56w0fEXV+VDNdvOuSywG+/8kpjddvvlfMLsaWdQe5oTuYGBcodmqGzQ== + dependencies: + "@babel/runtime" "^7.28.4" + "@emotion/cache" "^11.14.0" + "@emotion/serialize" "^1.3.3" + "@emotion/sheet" "^1.4.0" + csstype "^3.1.3" + prop-types "^15.8.1" + +"@mui/system@^7.3.6": + version "7.3.6" + resolved "https://registry.yarnpkg.com/@mui/system/-/system-7.3.6.tgz#460f82fc6fe1b79b8c04dc97694f6b162ffc3d25" + integrity sha512-8fehAazkHNP1imMrdD2m2hbA9sl7Ur6jfuNweh5o4l9YPty4iaZzRXqYvBCWQNwFaSHmMEj2KPbyXGp7Bt73Rg== + dependencies: + "@babel/runtime" "^7.28.4" + "@mui/private-theming" "^7.3.6" + "@mui/styled-engine" "^7.3.6" + "@mui/types" "^7.4.9" + "@mui/utils" "^7.3.6" + clsx "^2.1.1" + csstype "^3.1.3" + prop-types "^15.8.1" + +"@mui/types@^7.4.9": + version "7.4.9" + resolved "https://registry.yarnpkg.com/@mui/types/-/types-7.4.9.tgz#99accc87920b4c8c4ce33c5076a58f7f81b528fa" + integrity sha512-dNO8Z9T2cujkSIaCnWwprfeKmTWh97cnjkgmpFJ2sbfXLx8SMZijCYHOtP/y5nnUb/Rm2omxbDMmtUoSaUtKaw== + dependencies: + "@babel/runtime" "^7.28.4" + +"@mui/utils@^7.3.5", "@mui/utils@^7.3.6": + version "7.3.6" + resolved "https://registry.yarnpkg.com/@mui/utils/-/utils-7.3.6.tgz#508fbe864832f99b215d134eb89e1198cdc66b34" + integrity sha512-jn+Ba02O6PiFs7nKva8R2aJJ9kJC+3kQ2R0BbKNY3KQQ36Qng98GnPRFTlbwYTdMD6hLEBKaMLUktyg/rTfd2w== + dependencies: + "@babel/runtime" "^7.28.4" + "@mui/types" "^7.4.9" + "@types/prop-types" "^15.7.15" + clsx "^2.1.1" + prop-types "^15.8.1" + react-is "^19.2.0" + +"@mui/x-date-pickers@^8.8.0": + version "8.23.0" + resolved "https://registry.yarnpkg.com/@mui/x-date-pickers/-/x-date-pickers-8.23.0.tgz#fe2940550bde75d7faafea6ab4bdfd3127ba61ae" + integrity sha512-uKtam5wqMEuErmRxZLPEX/7CZZFTMfrl05V9cWNjBkpGTcdDBIs1Kba8z2pfQU93e9lSLrRlxbCMJzCu6iF0Rg== + dependencies: + "@babel/runtime" "^7.28.4" + "@mui/utils" "^7.3.5" + "@mui/x-internals" "8.23.0" + "@types/react-transition-group" "^4.4.12" + clsx "^2.1.1" + prop-types "^15.8.1" + react-transition-group "^4.4.5" + +"@mui/x-internals@8.23.0": + version "8.23.0" + resolved "https://registry.yarnpkg.com/@mui/x-internals/-/x-internals-8.23.0.tgz#f02c126e68896b2fc23b877a0112d2a7f2d110c6" + integrity sha512-FN7wdqwTxqq1tJBYVz8TA/HMcViuaHS0Jphr4pEjT/8Iuf94Yt3P82WbsTbXyYrgOQDQl07UqE7qWcJetRcHcg== + dependencies: + "@babel/runtime" "^7.28.4" + "@mui/utils" "^7.3.5" + reselect "^5.1.1" + use-sync-external-store "^1.6.0" + +"@popperjs/core@^2.11.8": + version "2.11.8" + resolved "https://registry.yarnpkg.com/@popperjs/core/-/core-2.11.8.tgz#6b79032e760a0899cd4204710beede972a3a185f" + integrity sha512-P1st0aksCrn9sGZhp8GMYwBnQsbvAWsZAX44oXNNvLHGqAOcoVxmjZiohstwQ7SqKnbR47akdNi+uleWD8+g6A== + +"@redux-devtools/core@^4.1.1": + version "4.1.1" + resolved "https://registry.yarnpkg.com/@redux-devtools/core/-/core-4.1.1.tgz#4e0d6fe7d250f10d927872448f0085b6c48cd933" + integrity sha512-ZyyJwiHX4DFDU0llk45tYSFPoIMekdoKLz0Q7soowpNOtchvTxruQx4Xy//Cohkwsw+DH8W1amdo4C/NYT6ARA== + dependencies: + "@babel/runtime" "^7.26.9" + "@redux-devtools/instrument" "^2.2.0" + +"@redux-devtools/instrument@^2.2.0": + version "2.2.0" + resolved "https://registry.yarnpkg.com/@redux-devtools/instrument/-/instrument-2.2.0.tgz#bc9d015da693aa9fabdb32f4fd07ee4c1328eb95" + integrity sha512-HKaL+ghBQ4ZQkM/kEQIKx8dNwz4E1oeiCDfdQlpPXxEi/BrisyrFFncAXb1y2HIJsLV9zSvQUR2jRtMDWgfi8w== + dependencies: + "@babel/runtime" "^7.23.2" + lodash "^4.17.21" + +"@redux-devtools/remote@^0.9.5": + version "0.9.5" + resolved "https://registry.yarnpkg.com/@redux-devtools/remote/-/remote-0.9.5.tgz#e0553026ea2d2f132246991c68dad57ac4d034e1" + integrity sha512-ETOUWgB5n6yopU4xH6wSwwmcVQT6liGBJbrWHkJkXCbCq9j/VqXHQ7spNN398p59vDseFZWOPo8KXNI0Mvo1RQ== + dependencies: + "@babel/runtime" "^7.26.9" + "@redux-devtools/instrument" "^2.2.0" + "@redux-devtools/utils" "^3.1.1" + jsan "^3.1.14" + rn-host-detect "^1.2.0" + socketcluster-client "^19.2.3" + +"@redux-devtools/serialize@^0.4.2": + version "0.4.2" + resolved "https://registry.yarnpkg.com/@redux-devtools/serialize/-/serialize-0.4.2.tgz#564c0cf2e5cb119a1884b1994a51f6d2e138b9a5" + integrity sha512-YVqZCChJld5l3Ni2psEZ5loe9x5xpf9J4ckz+7OJdzCNsplC7vzjnkQbFxE6+ULZbywRVp+nSBslTXmaXqAw4A== + dependencies: + "@babel/runtime" "^7.23.2" + jsan "^3.1.14" + +"@redux-devtools/utils@^3.1.1": + version "3.1.1" + resolved "https://registry.yarnpkg.com/@redux-devtools/utils/-/utils-3.1.1.tgz#a0c0aecf2c2e0f02518d48450dda90b9fe6eeb11" + integrity sha512-l+m3/8a7lcxULInBADIqE/3Tt2DkTJm5MAGVA/4czMCXW0VE+gdjkoRFqgZhTBoDJW1fi1z8pdL+4G/+R1rDJw== + dependencies: + "@babel/runtime" "^7.26.9" + "@redux-devtools/core" "^4.1.1" + "@redux-devtools/serialize" "^0.4.2" + "@types/get-params" "^0.1.2" + get-params "^0.1.2" + immutable "^4.3.7" + jsan "^3.1.14" + nanoid "^5.1.2" + redux "^5.0.1" + +"@reduxjs/toolkit@^2.3.0": + version "2.11.2" + resolved "https://registry.yarnpkg.com/@reduxjs/toolkit/-/toolkit-2.11.2.tgz#582225acea567329ca6848583e7dd72580d38e82" + integrity sha512-Kd6kAHTA6/nUpp8mySPqj3en3dm0tdMIgbttnQ1xFMVpufoj+ADi8pXLBsd4xzTRHQa7t/Jv8W5UnCuW4kuWMQ== + dependencies: + "@standard-schema/spec" "^1.0.0" + "@standard-schema/utils" "^0.3.0" + immer "^11.0.0" + redux "^5.0.1" + redux-thunk "^3.1.0" + reselect "^5.1.0" + +"@rolldown/pluginutils@1.0.0-beta.27": + version "1.0.0-beta.27" + resolved "https://registry.yarnpkg.com/@rolldown/pluginutils/-/pluginutils-1.0.0-beta.27.tgz#47d2bf4cef6d470b22f5831b420f8964e0bf755f" + integrity sha512-+d0F4MKMCbeVUJwG96uQ4SgAznZNSq93I3V+9NHA4OpvqG8mRCpGdKmK8l/dl02h2CCDHwW2FqilnTyDcAnqjA== + +"@rollup/plugin-virtual@^3.0.2": + version "3.0.2" + resolved "https://registry.yarnpkg.com/@rollup/plugin-virtual/-/plugin-virtual-3.0.2.tgz#17e17eeecb4c9fa1c0a6e72c9e5f66382fddbb82" + integrity sha512-10monEYsBp3scM4/ND4LNH5Rxvh3e/cVeL3jWTgZ2SrQ+BmUoQcopVQvnaMcOnykb1VkxUFuDAN+0FnpTFRy2A== + +"@rollup/rollup-android-arm-eabi@4.55.1": + version "4.55.1" + resolved "https://registry.yarnpkg.com/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.55.1.tgz#76e0fef6533b3ce313f969879e61e8f21f0eeb28" + integrity sha512-9R0DM/ykwfGIlNu6+2U09ga0WXeZ9MRC2Ter8jnz8415VbuIykVuc6bhdrbORFZANDmTDvq26mJrEVTl8TdnDg== + +"@rollup/rollup-android-arm64@4.55.1": + version "4.55.1" + resolved "https://registry.yarnpkg.com/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.55.1.tgz#d3cfc675a40bbdec97bda6d7fe3b3b05f0e1cd93" + integrity sha512-eFZCb1YUqhTysgW3sj/55du5cG57S7UTNtdMjCW7LwVcj3dTTcowCsC8p7uBdzKsZYa8J7IDE8lhMI+HX1vQvg== + +"@rollup/rollup-darwin-arm64@4.55.1": + version "4.55.1" + resolved "https://registry.yarnpkg.com/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.55.1.tgz#eb912b8f59dd47c77b3c50a78489013b1d6772b4" + integrity sha512-p3grE2PHcQm2e8PSGZdzIhCKbMCw/xi9XvMPErPhwO17vxtvCN5FEA2mSLgmKlCjHGMQTP6phuQTYWUnKewwGg== + +"@rollup/rollup-darwin-x64@4.55.1": + version "4.55.1" + resolved "https://registry.yarnpkg.com/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.55.1.tgz#e7d0839fdfd1276a1d34bc5ebbbd0dfd7d0b81a0" + integrity sha512-rDUjG25C9qoTm+e02Esi+aqTKSBYwVTaoS1wxcN47/Luqef57Vgp96xNANwt5npq9GDxsH7kXxNkJVEsWEOEaQ== + +"@rollup/rollup-freebsd-arm64@4.55.1": + version "4.55.1" + resolved "https://registry.yarnpkg.com/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.55.1.tgz#7ff8118760f7351e48fd0cd3717ff80543d6aac8" + integrity sha512-+JiU7Jbp5cdxekIgdte0jfcu5oqw4GCKr6i3PJTlXTCU5H5Fvtkpbs4XJHRmWNXF+hKmn4v7ogI5OQPaupJgOg== + +"@rollup/rollup-freebsd-x64@4.55.1": + version "4.55.1" + resolved "https://registry.yarnpkg.com/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.55.1.tgz#49d330dadbda1d4e9b86b4a3951b59928a9489a9" + integrity sha512-V5xC1tOVWtLLmr3YUk2f6EJK4qksksOYiz/TCsFHu/R+woubcLWdC9nZQmwjOAbmExBIVKsm1/wKmEy4z4u4Bw== + +"@rollup/rollup-linux-arm-gnueabihf@4.55.1": + version "4.55.1" + resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.55.1.tgz#98c5f1f8b9776b4a36e466e2a1c9ed1ba52ef1b6" + integrity sha512-Rn3n+FUk2J5VWx+ywrG/HGPTD9jXNbicRtTM11e/uorplArnXZYsVifnPPqNNP5BsO3roI4n8332ukpY/zN7rQ== + +"@rollup/rollup-linux-arm-musleabihf@4.55.1": + version "4.55.1" + resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.55.1.tgz#b9acecd3672e742f70b0c8a94075c816a91ff040" + integrity sha512-grPNWydeKtc1aEdrJDWk4opD7nFtQbMmV7769hiAaYyUKCT1faPRm2av8CX1YJsZ4TLAZcg9gTR1KvEzoLjXkg== + +"@rollup/rollup-linux-arm64-gnu@4.55.1": + version "4.55.1" + resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.55.1.tgz#7a6ab06651bc29e18b09a50ed1a02bc972977c9b" + integrity sha512-a59mwd1k6x8tXKcUxSyISiquLwB5pX+fJW9TkWU46lCqD/GRDe9uDN31jrMmVP3feI3mhAdvcCClhV8V5MhJFQ== + +"@rollup/rollup-linux-arm64-musl@4.55.1": + version "4.55.1" + resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.55.1.tgz#3c8c9072ba4a4d4ef1156b85ab9a2cbb57c1fad0" + integrity sha512-puS1MEgWX5GsHSoiAsF0TYrpomdvkaXm0CofIMG5uVkP6IBV+ZO9xhC5YEN49nsgYo1DuuMquF9+7EDBVYu4uA== + +"@rollup/rollup-linux-loong64-gnu@4.55.1": + version "4.55.1" + resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-loong64-gnu/-/rollup-linux-loong64-gnu-4.55.1.tgz#17a7af13530f4e4a7b12cd26276c54307a84a8b0" + integrity sha512-r3Wv40in+lTsULSb6nnoudVbARdOwb2u5fpeoOAZjFLznp6tDU8kd+GTHmJoqZ9lt6/Sys33KdIHUaQihFcu7g== + +"@rollup/rollup-linux-loong64-musl@4.55.1": + version "4.55.1" + resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-loong64-musl/-/rollup-linux-loong64-musl-4.55.1.tgz#5cd7a900fd7b077ecd753e34a9b7ff1157fe70c1" + integrity sha512-MR8c0+UxAlB22Fq4R+aQSPBayvYa3+9DrwG/i1TKQXFYEaoW3B5b/rkSRIypcZDdWjWnpcvxbNaAJDcSbJU3Lw== + +"@rollup/rollup-linux-ppc64-gnu@4.55.1": + version "4.55.1" + resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.55.1.tgz#03a097e70243ddf1c07b59d3c20f38e6f6800539" + integrity sha512-3KhoECe1BRlSYpMTeVrD4sh2Pw2xgt4jzNSZIIPLFEsnQn9gAnZagW9+VqDqAHgm1Xc77LzJOo2LdigS5qZ+gw== + +"@rollup/rollup-linux-ppc64-musl@4.55.1": + version "4.55.1" + resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-ppc64-musl/-/rollup-linux-ppc64-musl-4.55.1.tgz#a5389873039d4650f35b4fa060d286392eb21a94" + integrity sha512-ziR1OuZx0vdYZZ30vueNZTg73alF59DicYrPViG0NEgDVN8/Jl87zkAPu4u6VjZST2llgEUjaiNl9JM6HH1Vdw== + +"@rollup/rollup-linux-riscv64-gnu@4.55.1": + version "4.55.1" + resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.55.1.tgz#789e60e7d6e2b76132d001ffb24ba80007fb17d0" + integrity sha512-uW0Y12ih2XJRERZ4jAfKamTyIHVMPQnTZcQjme2HMVDAHY4amf5u414OqNYC+x+LzRdRcnIG1YodLrrtA8xsxw== + +"@rollup/rollup-linux-riscv64-musl@4.55.1": + version "4.55.1" + resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.55.1.tgz#3556fa88d139282e9a73c337c9a170f3c5fe7aa4" + integrity sha512-u9yZ0jUkOED1BFrqu3BwMQoixvGHGZ+JhJNkNKY/hyoEgOwlqKb62qu+7UjbPSHYjiVy8kKJHvXKv5coH4wDeg== + +"@rollup/rollup-linux-s390x-gnu@4.55.1": + version "4.55.1" + resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.55.1.tgz#c085995b10143c16747a67f1a5487512b2ff04b2" + integrity sha512-/0PenBCmqM4ZUd0190j7J0UsQ/1nsi735iPRakO8iPciE7BQ495Y6msPzaOmvx0/pn+eJVVlZrNrSh4WSYLxNg== + +"@rollup/rollup-linux-x64-gnu@4.55.1": + version "4.55.1" + resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.55.1.tgz#9563a5419dd2604841bad31a39ccfdd2891690fb" + integrity sha512-a8G4wiQxQG2BAvo+gU6XrReRRqj+pLS2NGXKm8io19goR+K8lw269eTrPkSdDTALwMmJp4th2Uh0D8J9bEV1vg== + +"@rollup/rollup-linux-x64-musl@4.55.1": + version "4.55.1" + resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.55.1.tgz#691bb06e6269a8959c13476b0cd2aa7458facb31" + integrity sha512-bD+zjpFrMpP/hqkfEcnjXWHMw5BIghGisOKPj+2NaNDuVT+8Ds4mPf3XcPHuat1tz89WRL+1wbcxKY3WSbiT7w== + +"@rollup/rollup-openbsd-x64@4.55.1": + version "4.55.1" + resolved "https://registry.yarnpkg.com/@rollup/rollup-openbsd-x64/-/rollup-openbsd-x64-4.55.1.tgz#223e71224746a59ce6d955bbc403577bb5a8be9d" + integrity sha512-eLXw0dOiqE4QmvikfQ6yjgkg/xDM+MdU9YJuP4ySTibXU0oAvnEWXt7UDJmD4UkYialMfOGFPJnIHSe/kdzPxg== + +"@rollup/rollup-openharmony-arm64@4.55.1": + version "4.55.1" + resolved "https://registry.yarnpkg.com/@rollup/rollup-openharmony-arm64/-/rollup-openharmony-arm64-4.55.1.tgz#0817e5d8ecbfeb8b7939bf58f8ce3c9dd67fce77" + integrity sha512-xzm44KgEP11te3S2HCSyYf5zIzWmx3n8HDCc7EE59+lTcswEWNpvMLfd9uJvVX8LCg9QWG67Xt75AuHn4vgsXw== + +"@rollup/rollup-win32-arm64-msvc@4.55.1": + version "4.55.1" + resolved "https://registry.yarnpkg.com/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.55.1.tgz#de56d8f2013c84570ef5fb917aae034abda93e4a" + integrity sha512-yR6Bl3tMC/gBok5cz/Qi0xYnVbIxGx5Fcf/ca0eB6/6JwOY+SRUcJfI0OpeTpPls7f194as62thCt/2BjxYN8g== + +"@rollup/rollup-win32-ia32-msvc@4.55.1": + version "4.55.1" + resolved "https://registry.yarnpkg.com/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.55.1.tgz#659aff5244312475aeea2c9479a6c7d397b517bf" + integrity sha512-3fZBidchE0eY0oFZBnekYCfg+5wAB0mbpCBuofh5mZuzIU/4jIVkbESmd2dOsFNS78b53CYv3OAtwqkZZmU5nA== + +"@rollup/rollup-win32-x64-gnu@4.55.1": + version "4.55.1" + resolved "https://registry.yarnpkg.com/@rollup/rollup-win32-x64-gnu/-/rollup-win32-x64-gnu-4.55.1.tgz#2cb09549cbb66c1b979f9238db6dd454cac14a88" + integrity sha512-xGGY5pXj69IxKb4yv/POoocPy/qmEGhimy/FoTpTSVju3FYXUQQMFCaZZXJVidsmGxRioZAwpThl/4zX41gRKg== + +"@rollup/rollup-win32-x64-msvc@4.55.1": + version "4.55.1" + resolved "https://registry.yarnpkg.com/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.55.1.tgz#f79437939020b83057faf07e98365b1fa51c458b" + integrity sha512-SPEpaL6DX4rmcXtnhdrQYgzQ5W2uW3SCJch88lB2zImhJRhIIK44fkUrgIV/Q8yUNfw5oyZ5vkeQsZLhCb06lw== + +"@rtsao/scc@^1.1.0": + version "1.1.0" + resolved "https://registry.yarnpkg.com/@rtsao/scc/-/scc-1.1.0.tgz#927dd2fae9bc3361403ac2c7a00c32ddce9ad7e8" + integrity sha512-zt6OdqaDoOnJ1ZYsCYGt9YmWzDXl4vQdKTyJev62gFhRGKdx7mcT54V9KIjg+d2wi9EXsPvAPKe7i7WjfVWB8g== + +"@standard-schema/spec@^1.0.0": + version "1.1.0" + resolved "https://registry.yarnpkg.com/@standard-schema/spec/-/spec-1.1.0.tgz#a79b55dbaf8604812f52d140b2c9ab41bc150bb8" + integrity sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w== + +"@standard-schema/utils@^0.3.0": + version "0.3.0" + resolved "https://registry.yarnpkg.com/@standard-schema/utils/-/utils-0.3.0.tgz#3d5e608f16c2390c10528e98e59aef6bf73cae7b" + integrity sha512-e7Mew686owMaPJVNNLs55PUvgz371nKgwsc4vxE49zsODpJEnxgxRo2y/OKrqueavXgZNMDVj3DdHFlaSAeU8g== + +"@swc/core-darwin-arm64@1.15.8": + version "1.15.8" + resolved "https://registry.yarnpkg.com/@swc/core-darwin-arm64/-/core-darwin-arm64-1.15.8.tgz#f565a2744ee840389eba800f1bd454a5ab5c8235" + integrity sha512-M9cK5GwyWWRkRGwwCbREuj6r8jKdES/haCZ3Xckgkl8MUQJZA3XB7IXXK1IXRNeLjg6m7cnoMICpXv1v1hlJOg== + +"@swc/core-darwin-x64@1.15.8": + version "1.15.8" + resolved "https://registry.yarnpkg.com/@swc/core-darwin-x64/-/core-darwin-x64-1.15.8.tgz#52e1bb71fddca37d8c18dcfc33d4117e9de11789" + integrity sha512-j47DasuOvXl80sKJHSi2X25l44CMc3VDhlJwA7oewC1nV1VsSzwX+KOwE5tLnfORvVJJyeiXgJORNYg4jeIjYQ== + +"@swc/core-linux-arm-gnueabihf@1.15.8": + version "1.15.8" + resolved "https://registry.yarnpkg.com/@swc/core-linux-arm-gnueabihf/-/core-linux-arm-gnueabihf-1.15.8.tgz#19586971697767c465bbecaed96940e03a12ac5c" + integrity sha512-siAzDENu2rUbwr9+fayWa26r5A9fol1iORG53HWxQL1J8ym4k7xt9eME0dMPXlYZDytK5r9sW8zEA10F2U3Xwg== + +"@swc/core-linux-arm64-gnu@1.15.8": + version "1.15.8" + resolved "https://registry.yarnpkg.com/@swc/core-linux-arm64-gnu/-/core-linux-arm64-gnu-1.15.8.tgz#22a55b71cfff34cddd772619aa4ca2bf913032cb" + integrity sha512-o+1y5u6k2FfPYbTRUPvurwzNt5qd0NTumCTFscCNuBksycloXY16J8L+SMW5QRX59n4Hp9EmFa3vpvNHRVv1+Q== + +"@swc/core-linux-arm64-musl@1.15.8": + version "1.15.8" + resolved "https://registry.yarnpkg.com/@swc/core-linux-arm64-musl/-/core-linux-arm64-musl-1.15.8.tgz#ce5a26ff25ab76fb699e7171b90884cfe63c1841" + integrity sha512-koiCqL09EwOP1S2RShCI7NbsQuG6r2brTqUYE7pV7kZm9O17wZ0LSz22m6gVibpwEnw8jI3IE1yYsQTVpluALw== + +"@swc/core-linux-x64-gnu@1.15.8": + version "1.15.8" + resolved "https://registry.yarnpkg.com/@swc/core-linux-x64-gnu/-/core-linux-x64-gnu-1.15.8.tgz#cbacd43d4971fe6e4d41b8d8051ea1a8aabf40e5" + integrity sha512-4p6lOMU3bC+Vd5ARtKJ/FxpIC5G8v3XLoPEZ5s7mLR8h7411HWC/LmTXDHcrSXRC55zvAVia1eldy6zDLz8iFQ== + +"@swc/core-linux-x64-musl@1.15.8": + version "1.15.8" + resolved "https://registry.yarnpkg.com/@swc/core-linux-x64-musl/-/core-linux-x64-musl-1.15.8.tgz#6317cf3b75fde62c88faec3750e3aeb9bec83b3d" + integrity sha512-z3XBnbrZAL+6xDGAhJoN4lOueIxC/8rGrJ9tg+fEaeqLEuAtHSW2QHDHxDwkxZMjuF/pZ6MUTjHjbp8wLbuRLA== + +"@swc/core-win32-arm64-msvc@1.15.8": + version "1.15.8" + resolved "https://registry.yarnpkg.com/@swc/core-win32-arm64-msvc/-/core-win32-arm64-msvc-1.15.8.tgz#3a25a4d530e62be5b6b938b7eca70af117b34832" + integrity sha512-djQPJ9Rh9vP8GTS/Df3hcc6XP6xnG5c8qsngWId/BLA9oX6C7UzCPAn74BG/wGb9a6j4w3RINuoaieJB3t+7iQ== + +"@swc/core-win32-ia32-msvc@1.15.8": + version "1.15.8" + resolved "https://registry.yarnpkg.com/@swc/core-win32-ia32-msvc/-/core-win32-ia32-msvc-1.15.8.tgz#1c7a3ba04216ce3b0a00b4c742e8e3bff9ff8b14" + integrity sha512-/wfAgxORg2VBaUoFdytcVBVCgf1isWZIEXB9MZEUty4wwK93M/PxAkjifOho9RN3WrM3inPLabICRCEgdHpKKQ== + +"@swc/core-win32-x64-msvc@1.15.8": + version "1.15.8" + resolved "https://registry.yarnpkg.com/@swc/core-win32-x64-msvc/-/core-win32-x64-msvc-1.15.8.tgz#5bdcbe3fc0e0ccbae8abc9285a661a1bc3bdd65a" + integrity sha512-GpMePrh9Sl4d61o4KAHOOv5is5+zt6BEXCOCgs/H0FLGeii7j9bWDE8ExvKFy2GRRZVNR1ugsnzaGWHKM6kuzA== + +"@swc/core@^1.12.14": + version "1.15.8" + resolved "https://registry.yarnpkg.com/@swc/core/-/core-1.15.8.tgz#818abeab1cc57546a773b11dec4edd8ab26ae687" + integrity sha512-T8keoJjXaSUoVBCIjgL6wAnhADIb09GOELzKg10CjNg+vLX48P93SME6jTfte9MZIm5m+Il57H3rTSk/0kzDUw== + dependencies: + "@swc/counter" "^0.1.3" + "@swc/types" "^0.1.25" + optionalDependencies: + "@swc/core-darwin-arm64" "1.15.8" + "@swc/core-darwin-x64" "1.15.8" + "@swc/core-linux-arm-gnueabihf" "1.15.8" + "@swc/core-linux-arm64-gnu" "1.15.8" + "@swc/core-linux-arm64-musl" "1.15.8" + "@swc/core-linux-x64-gnu" "1.15.8" + "@swc/core-linux-x64-musl" "1.15.8" + "@swc/core-win32-arm64-msvc" "1.15.8" + "@swc/core-win32-ia32-msvc" "1.15.8" + "@swc/core-win32-x64-msvc" "1.15.8" + +"@swc/counter@^0.1.3": + version "0.1.3" + resolved "https://registry.yarnpkg.com/@swc/counter/-/counter-0.1.3.tgz#cc7463bd02949611c6329596fccd2b0ec782b0e9" + integrity sha512-e2BR4lsJkkRlKZ/qCHPw9ZaSxc0MVUd7gtbtaB7aMvHeJVYe8sOB8DBZkP2DtISHGSku9sCK6T6cnY0CtXrOCQ== + +"@swc/types@^0.1.25": + version "0.1.25" + resolved "https://registry.yarnpkg.com/@swc/types/-/types-0.1.25.tgz#b517b2a60feb37dd933e542d93093719e4cf1078" + integrity sha512-iAoY/qRhNH8a/hBvm3zKj9qQ4oc2+3w1unPJa2XvTK3XjeLXtzcCingVPw/9e5mn1+0yPqxcBGp9Jf0pkfMb1g== + dependencies: + "@swc/counter" "^0.1.3" + +"@swc/wasm@^1.12.14": + version "1.15.8" + resolved "https://registry.yarnpkg.com/@swc/wasm/-/wasm-1.15.8.tgz#8b8adc4a132db05cecc040901a9e0cb1c5bd661b" + integrity sha512-RG2BxGbbsjtddFCo1ghKH6A/BMXbY1eMBfpysV0lJMCpI4DZOjW1BNBnxvBt7YsYmlJtmy5UXIg9/4ekBTFFaQ== + +"@tauri-apps/api@^2.6.0", "@tauri-apps/api@^2.8.0": + version "2.9.1" + resolved "https://registry.yarnpkg.com/@tauri-apps/api/-/api-2.9.1.tgz#e539bfa437e53864fd5e7ed0f28e8217fbc74de1" + integrity sha512-IGlhP6EivjXHepbBic618GOmiWe4URJiIeZFlB7x3czM0yDHHYviH1Xvoiv4FefdkQtn6v7TuwWCRfOGdnVUGw== + +"@tauri-apps/cli-darwin-arm64@2.9.6": + version "2.9.6" + resolved "https://registry.yarnpkg.com/@tauri-apps/cli-darwin-arm64/-/cli-darwin-arm64-2.9.6.tgz#bb1576a6567db0d331e34d2322dc6aebde6681e8" + integrity sha512-gf5no6N9FCk1qMrti4lfwP77JHP5haASZgVbBgpZG7BUepB3fhiLCXGUK8LvuOjP36HivXewjg72LTnPDScnQQ== + +"@tauri-apps/cli-darwin-x64@2.9.6": + version "2.9.6" + resolved "https://registry.yarnpkg.com/@tauri-apps/cli-darwin-x64/-/cli-darwin-x64-2.9.6.tgz#7beb6ba8218002d7e160764326ce03407e76305d" + integrity sha512-oWh74WmqbERwwrwcueJyY6HYhgCksUc6NT7WKeXyrlY/FPmNgdyQAgcLuTSkhRFuQ6zh4Np1HZpOqCTpeZBDcw== + +"@tauri-apps/cli-linux-arm-gnueabihf@2.9.6": + version "2.9.6" + resolved "https://registry.yarnpkg.com/@tauri-apps/cli-linux-arm-gnueabihf/-/cli-linux-arm-gnueabihf-2.9.6.tgz#523ddcd86a99bdda5156bd9de96dfd3e4fa75b7f" + integrity sha512-/zde3bFroFsNXOHN204DC2qUxAcAanUjVXXSdEGmhwMUZeAQalNj5cz2Qli2elsRjKN/hVbZOJj0gQ5zaYUjSg== + +"@tauri-apps/cli-linux-arm64-gnu@2.9.6": + version "2.9.6" + resolved "https://registry.yarnpkg.com/@tauri-apps/cli-linux-arm64-gnu/-/cli-linux-arm64-gnu-2.9.6.tgz#60b4cbd4b8b97c5f5be6cc70fa75455b5a6d6292" + integrity sha512-pvbljdhp9VOo4RnID5ywSxgBs7qiylTPlK56cTk7InR3kYSTJKYMqv/4Q/4rGo/mG8cVppesKIeBMH42fw6wjg== + +"@tauri-apps/cli-linux-arm64-musl@2.9.6": + version "2.9.6" + resolved "https://registry.yarnpkg.com/@tauri-apps/cli-linux-arm64-musl/-/cli-linux-arm64-musl-2.9.6.tgz#ce5e5396db6c7f22b80154757479b1486163364a" + integrity sha512-02TKUndpodXBCR0oP//6dZWGYcc22Upf2eP27NvC6z0DIqvkBBFziQUcvi2n6SrwTRL0yGgQjkm9K5NIn8s6jw== + +"@tauri-apps/cli-linux-riscv64-gnu@2.9.6": + version "2.9.6" + resolved "https://registry.yarnpkg.com/@tauri-apps/cli-linux-riscv64-gnu/-/cli-linux-riscv64-gnu-2.9.6.tgz#aa8ec23d62cbb85a75c3e172637e78f485dbfcf8" + integrity sha512-fmp1hnulbqzl1GkXl4aTX9fV+ubHw2LqlLH1PE3BxZ11EQk+l/TmiEongjnxF0ie4kV8DQfDNJ1KGiIdWe1GvQ== + +"@tauri-apps/cli-linux-x64-gnu@2.9.6": + version "2.9.6" + resolved "https://registry.yarnpkg.com/@tauri-apps/cli-linux-x64-gnu/-/cli-linux-x64-gnu-2.9.6.tgz#036c0463c5eee2298ed6ca8cb2838738816c7290" + integrity sha512-vY0le8ad2KaV1PJr+jCd8fUF9VOjwwQP/uBuTJvhvKTloEwxYA/kAjKK9OpIslGA9m/zcnSo74czI6bBrm2sYA== + +"@tauri-apps/cli-linux-x64-musl@2.9.6": + version "2.9.6" + resolved "https://registry.yarnpkg.com/@tauri-apps/cli-linux-x64-musl/-/cli-linux-x64-musl-2.9.6.tgz#62b05db3d28b0f12c150836a387bd572de44f5be" + integrity sha512-TOEuB8YCFZTWVDzsO2yW0+zGcoMiPPwcUgdnW1ODnmgfwccpnihDRoks+ABT1e3fHb1ol8QQWsHSCovb3o2ENQ== + +"@tauri-apps/cli-win32-arm64-msvc@2.9.6": + version "2.9.6" + resolved "https://registry.yarnpkg.com/@tauri-apps/cli-win32-arm64-msvc/-/cli-win32-arm64-msvc-2.9.6.tgz#01f69ba09a6581e70bdfa206c5801b64b329d28d" + integrity sha512-ujmDGMRc4qRLAnj8nNG26Rlz9klJ0I0jmZs2BPpmNNf0gM/rcVHhqbEkAaHPTBVIrtUdf7bGvQAD2pyIiUrBHQ== + +"@tauri-apps/cli-win32-ia32-msvc@2.9.6": + version "2.9.6" + resolved "https://registry.yarnpkg.com/@tauri-apps/cli-win32-ia32-msvc/-/cli-win32-ia32-msvc-2.9.6.tgz#b36c60db5119d74f126d4c4d8288d1c6ae4b45f0" + integrity sha512-S4pT0yAJgFX8QRCyKA1iKjZ9Q/oPjCZf66A/VlG5Yw54Nnr88J1uBpmenINbXxzyhduWrIXBaUbEY1K80ZbpMg== + +"@tauri-apps/cli-win32-x64-msvc@2.9.6": + version "2.9.6" + resolved "https://registry.yarnpkg.com/@tauri-apps/cli-win32-x64-msvc/-/cli-win32-x64-msvc-2.9.6.tgz#d58c9f8af835b7e4fc30e201e979342c70bea426" + integrity sha512-ldWuWSSkWbKOPjQMJoYVj9wLHcOniv7diyI5UAJ4XsBdtaFB0pKHQsqw/ItUma0VXGC7vB4E9fZjivmxur60aw== + +"@tauri-apps/cli@^2.0.0": + version "2.9.6" + resolved "https://registry.yarnpkg.com/@tauri-apps/cli/-/cli-2.9.6.tgz#f15ae8e03bf48308055c15ab25b439bed9906bc9" + integrity sha512-3xDdXL5omQ3sPfBfdC8fCtDKcnyV7OqyzQgfyT5P3+zY6lcPqIYKQBvUasNvppi21RSdfhy44ttvJmftb0PCDw== + optionalDependencies: + "@tauri-apps/cli-darwin-arm64" "2.9.6" + "@tauri-apps/cli-darwin-x64" "2.9.6" + "@tauri-apps/cli-linux-arm-gnueabihf" "2.9.6" + "@tauri-apps/cli-linux-arm64-gnu" "2.9.6" + "@tauri-apps/cli-linux-arm64-musl" "2.9.6" + "@tauri-apps/cli-linux-riscv64-gnu" "2.9.6" + "@tauri-apps/cli-linux-x64-gnu" "2.9.6" + "@tauri-apps/cli-linux-x64-musl" "2.9.6" + "@tauri-apps/cli-win32-arm64-msvc" "2.9.6" + "@tauri-apps/cli-win32-ia32-msvc" "2.9.6" + "@tauri-apps/cli-win32-x64-msvc" "2.9.6" + +"@tauri-apps/plugin-cli@^2.4.0": + version "2.4.1" + resolved "https://registry.yarnpkg.com/@tauri-apps/plugin-cli/-/plugin-cli-2.4.1.tgz#5cd8353c6cb7f079ac86c21a0e11305fdd32b041" + integrity sha512-8JXofQFI5cmiGolh1PlU4hzE2YJgrgB1lyaztyBYiiMCy13luVxBXaXChYPeqMkUo46J1UadxvYdjRjj0E8zaw== + dependencies: + "@tauri-apps/api" "^2.8.0" + +"@tauri-apps/plugin-clipboard-manager@^2.3.0": + version "2.3.2" + resolved "https://registry.yarnpkg.com/@tauri-apps/plugin-clipboard-manager/-/plugin-clipboard-manager-2.3.2.tgz#41def8a008eca94fd5abe2915d26a9c4b6ce54ca" + integrity sha512-CUlb5Hqi2oZbcZf4VUyUH53XWPPdtpw43EUpCza5HWZJwxEoDowFzNUDt1tRUXA8Uq+XPn17Ysfptip33sG4eQ== + dependencies: + "@tauri-apps/api" "^2.8.0" + +"@tauri-apps/plugin-dialog@^2.0.0": + version "2.4.2" + resolved "https://registry.yarnpkg.com/@tauri-apps/plugin-dialog/-/plugin-dialog-2.4.2.tgz#59937d28757ce834838ad69530ea36cd439acc4f" + integrity sha512-lNIn5CZuw8WZOn8zHzmFmDSzg5zfohWoa3mdULP0YFh/VogVdMVWZPcWSHlydsiJhRQYaTNSYKN7RmZKE2lCYQ== + dependencies: + "@tauri-apps/api" "^2.8.0" + +"@tauri-apps/plugin-opener@^2.5.0": + version "2.5.2" + resolved "https://registry.yarnpkg.com/@tauri-apps/plugin-opener/-/plugin-opener-2.5.2.tgz#6e2127d0ad7627a16103215ed596e4fa42bda199" + integrity sha512-ei/yRRoCklWHImwpCcDK3VhNXx+QXM9793aQ64YxpqVF0BDuuIlXhZgiAkc15wnPVav+IbkYhmDJIv5R326Mew== + dependencies: + "@tauri-apps/api" "^2.8.0" + +"@tauri-apps/plugin-process@^2.3.0": + version "2.3.1" + resolved "https://registry.yarnpkg.com/@tauri-apps/plugin-process/-/plugin-process-2.3.1.tgz#fff77aa7550c9c5347689c859d581f88287bf7ae" + integrity sha512-nCa4fGVaDL/B9ai03VyPOjfAHRHSBz5v6F/ObsB73r/dA3MHHhZtldaDMIc0V/pnUw9ehzr2iEG+XkSEyC0JJA== + dependencies: + "@tauri-apps/api" "^2.8.0" + +"@tauri-apps/plugin-store@^2.4.0": + version "2.4.1" + resolved "https://registry.yarnpkg.com/@tauri-apps/plugin-store/-/plugin-store-2.4.1.tgz#5e2d3362e41861d2fa79a3f1a78c091e12963236" + integrity sha512-ckGSEzZ5Ii4Hf2D5x25Oqnm2Zf9MfDWAzR+volY0z/OOBz6aucPKEY0F649JvQ0Vupku6UJo7ugpGRDOFOunkA== + dependencies: + "@tauri-apps/api" "^2.8.0" + +"@tauri-apps/plugin-updater@^2.9.0": + version "2.9.0" + resolved "https://registry.yarnpkg.com/@tauri-apps/plugin-updater/-/plugin-updater-2.9.0.tgz#ba50b4e644fe19fa6f8465bd86d48119b0d3f41c" + integrity sha512-j++sgY8XpeDvzImTrzWA08OqqGqgkNyxczLD7FjNJJx/uXxMZFz5nDcfkyoI/rCjYuj2101Tci/r/HFmOmoxCg== + dependencies: + "@tauri-apps/api" "^2.6.0" + +"@testing-library/react@^16.0.1": + version "16.3.1" + resolved "https://registry.yarnpkg.com/@testing-library/react/-/react-16.3.1.tgz#60a9f1f6a930399d9e41b506a8bf68dbf4831fe8" + integrity sha512-gr4KtAWqIOQoucWYD/f6ki+j5chXfcPc74Col/6poTyqTmn7zRmodWahWRCp8tYd+GMqBonw6hstNzqjbs6gjw== + dependencies: + "@babel/runtime" "^7.12.5" + +"@testing-library/user-event@^14.5.2": + version "14.6.1" + resolved "https://registry.yarnpkg.com/@testing-library/user-event/-/user-event-14.6.1.tgz#13e09a32d7a8b7060fe38304788ebf4197cd2149" + integrity sha512-vq7fv0rnt+QTXgPxr5Hjc210p6YKq2kmdziLgnsZGgLJ9e6VAShx1pACLuRjd/AS/sr7phAR58OIIpf0LlmQNw== + +"@types/babel__core@^7.20.5": + version "7.20.5" + resolved "https://registry.yarnpkg.com/@types/babel__core/-/babel__core-7.20.5.tgz#3df15f27ba85319caa07ba08d0721889bb39c017" + integrity sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA== + dependencies: + "@babel/parser" "^7.20.7" + "@babel/types" "^7.20.7" + "@types/babel__generator" "*" + "@types/babel__template" "*" + "@types/babel__traverse" "*" + +"@types/babel__generator@*": + version "7.27.0" + resolved "https://registry.yarnpkg.com/@types/babel__generator/-/babel__generator-7.27.0.tgz#b5819294c51179957afaec341442f9341e4108a9" + integrity sha512-ufFd2Xi92OAVPYsy+P4n7/U7e68fex0+Ee8gSG9KX7eo084CWiQ4sdxktvdl0bOPupXtVJPY19zk6EwWqUQ8lg== + dependencies: + "@babel/types" "^7.0.0" + +"@types/babel__template@*": + version "7.4.4" + resolved "https://registry.yarnpkg.com/@types/babel__template/-/babel__template-7.4.4.tgz#5672513701c1b2199bc6dad636a9d7491586766f" + integrity sha512-h/NUaSyG5EyxBIp8YRxo4RMe2/qQgvyowRwVMzhYhBCONbW8PUsg4lkFMrhgZhUe5z3L3MiLDuvyJ/CaPa2A8A== + dependencies: + "@babel/parser" "^7.1.0" + "@babel/types" "^7.0.0" + +"@types/babel__traverse@*": + version "7.28.0" + resolved "https://registry.yarnpkg.com/@types/babel__traverse/-/babel__traverse-7.28.0.tgz#07d713d6cce0d265c9849db0cbe62d3f61f36f74" + integrity sha512-8PvcXf70gTDZBgt9ptxJ8elBeBjcLOAcOtoO/mPJjtji1+CdGbHgm77om1GrsPxsiE+uXIpNSK64UYaIwQXd4Q== + dependencies: + "@babel/types" "^7.28.2" + +"@types/estree@1.0.8", "@types/estree@^1.0.0", "@types/estree@^1.0.6": + version "1.0.8" + resolved "https://registry.yarnpkg.com/@types/estree/-/estree-1.0.8.tgz#958b91c991b1867ced318bedea0e215ee050726e" + integrity sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w== + +"@types/get-params@^0.1.2": + version "0.1.2" + resolved "https://registry.yarnpkg.com/@types/get-params/-/get-params-0.1.2.tgz#815f80eceb0f0e2f0bb00a2527c9d2e6e57e2a52" + integrity sha512-ujqPyr1UDsOTDngJPV+WFbR0iHT5AfZKlNPMX6XOCnQcMhEqR+r64dVC/nwYCitqjR3DcpWofnOEAInUQmI/eA== + +"@types/hoist-non-react-statics@^3.3.0": + version "3.3.7" + resolved "https://registry.yarnpkg.com/@types/hoist-non-react-statics/-/hoist-non-react-statics-3.3.7.tgz#306e3a3a73828522efa1341159da4846e7573a6c" + integrity sha512-PQTyIulDkIDro8P+IHbKCsw7U2xxBYflVzW/FgWdCAePD9xGSidgA76/GeJ6lBKoblyhf9pBY763gbrN+1dI8g== + dependencies: + hoist-non-react-statics "^3.3.0" + +"@types/humanize-duration@^3.27.4": + version "3.27.4" + resolved "https://registry.yarnpkg.com/@types/humanize-duration/-/humanize-duration-3.27.4.tgz#51d6d278213374735440bc3749de920935e9127e" + integrity sha512-yaf7kan2Sq0goxpbcwTQ+8E9RP6HutFBPv74T/IA/ojcHKhuKVlk2YFYyHhWZeLvZPzzLE3aatuQB4h0iqyyUA== + +"@types/json-schema@^7.0.15": + version "7.0.15" + resolved "https://registry.yarnpkg.com/@types/json-schema/-/json-schema-7.0.15.tgz#596a1747233694d50f6ad8a7869fcb6f56cf5841" + integrity sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA== + +"@types/json5@^0.0.29": + version "0.0.29" + resolved "https://registry.yarnpkg.com/@types/json5/-/json5-0.0.29.tgz#ee28707ae94e11d2b827bcbe5270bcea7f3e71ee" + integrity sha512-dRLjCWHYg4oaA77cxO64oO+7JwCwnIzkZPdrrC71jQmQtlhM556pwKo5bUzqvZndkVbeFLIIi+9TC40JNF5hNQ== + +"@types/lodash@^4.17.6": + version "4.17.21" + resolved "https://registry.yarnpkg.com/@types/lodash/-/lodash-4.17.21.tgz#b806831543d696b14f8112db600ea9d3a1df6ea4" + integrity sha512-FOvQ0YPD5NOfPgMzJihoT+Za5pdkDJWcbpuj1DjaKZIr/gxodQjY/uWEFlTNqW2ugXHUiL8lRQgw63dzKHZdeQ== + +"@types/node@*": + version "25.0.3" + resolved "https://registry.yarnpkg.com/@types/node/-/node-25.0.3.tgz#79b9ac8318f373fbfaaf6e2784893efa9701f269" + integrity sha512-W609buLVRVmeW693xKfzHeIV6nJGGz98uCPfeXI1ELMLXVeKYZ9m15fAMSaUPBHYLGFsVRcMmSCksQOrZV9BYA== + dependencies: + undici-types "~7.16.0" + +"@types/node@^22.15.29": + version "22.19.3" + resolved "https://registry.yarnpkg.com/@types/node/-/node-22.19.3.tgz#8dfde7630d7a8528dc9b34db23d34f764467c02c" + integrity sha512-1N9SBnWYOJTrNZCdh/yJE+t910Y128BoyY+zBLWhL3r0TYzlTmFdXrPwHL9DyFZmlEXNQQolTZh3KHV31QDhyA== + dependencies: + undici-types "~6.21.0" + +"@types/parse-json@^4.0.0": + version "4.0.2" + resolved "https://registry.yarnpkg.com/@types/parse-json/-/parse-json-4.0.2.tgz#5950e50960793055845e956c427fc2b0d70c5239" + integrity sha512-dISoDXWWQwUquiKsyZ4Ng+HX2KsPL7LyHKHQwgGFEA3IaKac4Obd+h2a/a6waisAoepJlBcx9paWqjA8/HVjCw== + +"@types/prop-types@^15.7.15": + version "15.7.15" + resolved "https://registry.yarnpkg.com/@types/prop-types/-/prop-types-15.7.15.tgz#e6e5a86d602beaca71ce5163fadf5f95d70931c7" + integrity sha512-F6bEyamV9jKGAFBEmlQnesRPGOQqS2+Uwi0Em15xenOxHaf2hv6L8YCVn3rPdPJOiJfPiCnLIRyvwVaqMY3MIw== + +"@types/react-dom@^19.1.5": + version "19.2.3" + resolved "https://registry.yarnpkg.com/@types/react-dom/-/react-dom-19.2.3.tgz#c1e305d15a52a3e508d54dca770d202cb63abf2c" + integrity sha512-jp2L/eY6fn+KgVVQAOqYItbF0VY/YApe5Mz2F0aykSO8gx31bYCZyvSeYxCHKvzHG5eZjc+zyaS5BrBWya2+kQ== + +"@types/react-is@^19.0.0": + version "19.2.0" + resolved "https://registry.yarnpkg.com/@types/react-is/-/react-is-19.2.0.tgz#b72a01627e4820f2333abdc9945c3daac48245e7" + integrity sha512-NP2xtcjZfORsOa4g2JwdseyEnF+wUCx25fTdG/J/HIY6yKga6+NozRBg2xR2gyh7kKYyd6DXndbq0YbQuTJ7Ew== + dependencies: + "@types/react" "*" + +"@types/react-redux@^7.1.34": + version "7.1.34" + resolved "https://registry.yarnpkg.com/@types/react-redux/-/react-redux-7.1.34.tgz#83613e1957c481521e6776beeac4fd506d11bd0e" + integrity sha512-GdFaVjEbYv4Fthm2ZLvj1VSCedV7TqE5y1kNwnjSdBOTXuRSgowux6J8TAct15T3CKBr63UMk+2CO7ilRhyrAQ== + dependencies: + "@types/hoist-non-react-statics" "^3.3.0" + "@types/react" "*" + hoist-non-react-statics "^3.3.0" + redux "^4.0.0" + +"@types/react-transition-group@^4.4.12": + version "4.4.12" + resolved "https://registry.yarnpkg.com/@types/react-transition-group/-/react-transition-group-4.4.12.tgz#b5d76568485b02a307238270bfe96cb51ee2a044" + integrity sha512-8TV6R3h2j7a91c+1DXdJi3Syo69zzIZbz7Lg5tORM5LEJG7X/E6a1V3drRyBRZq7/utz7A+c4OgYLiLcYGHG6w== + +"@types/react@*", "@types/react@^19.1.6": + version "19.2.7" + resolved "https://registry.yarnpkg.com/@types/react/-/react-19.2.7.tgz#84e62c0f23e8e4e5ac2cadcea1ffeacccae7f62f" + integrity sha512-MWtvHrGZLFttgeEj28VXHxpmwYbor/ATPYbBfSFZEIRK0ecCFLl2Qo55z52Hss+UV9CRN7trSeq1zbgx7YDWWg== + dependencies: + csstype "^3.2.2" + +"@types/semver@^7.5.8": + version "7.7.1" + resolved "https://registry.yarnpkg.com/@types/semver/-/semver-7.7.1.tgz#3ce3af1a5524ef327d2da9e4fd8b6d95c8d70528" + integrity sha512-FmgJfu+MOcQ370SD0ev7EI8TlCAfKYU+B4m5T3yXc1CiRN94g/SZPtsCkk506aUDtlMnFZvasDwHHUcZUEaYuA== + +"@types/use-sync-external-store@^0.0.6": + version "0.0.6" + resolved "https://registry.yarnpkg.com/@types/use-sync-external-store/-/use-sync-external-store-0.0.6.tgz#60be8d21baab8c305132eb9cb912ed497852aadc" + integrity sha512-zFDAD+tlpf2r4asuHEj0XH6pY6i0g5NeAHPn+15wk3BV6JA69eERFXC1gyGThDkVa1zCyKr5jox1+2LbV/AMLg== + +"@typescript-eslint/eslint-plugin@8.52.0": + version "8.52.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.52.0.tgz#9a9f1d2ee974ed77a8b1bda94e77123f697ee8b4" + integrity sha512-okqtOgqu2qmZJ5iN4TWlgfF171dZmx2FzdOv2K/ixL2LZWDStL8+JgQerI2sa8eAEfoydG9+0V96m7V+P8yE1Q== + dependencies: + "@eslint-community/regexpp" "^4.12.2" + "@typescript-eslint/scope-manager" "8.52.0" + "@typescript-eslint/type-utils" "8.52.0" + "@typescript-eslint/utils" "8.52.0" + "@typescript-eslint/visitor-keys" "8.52.0" + ignore "^7.0.5" + natural-compare "^1.4.0" + ts-api-utils "^2.4.0" + +"@typescript-eslint/parser@8.52.0": + version "8.52.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/parser/-/parser-8.52.0.tgz#9fae9f5f13ebb1c8f31a50c34381bfd6bf96a05f" + integrity sha512-iIACsx8pxRnguSYhHiMn2PvhvfpopO9FXHyn1mG5txZIsAaB6F0KwbFnUQN3KCiG3Jcuad/Cao2FAs1Wp7vAyg== + dependencies: + "@typescript-eslint/scope-manager" "8.52.0" + "@typescript-eslint/types" "8.52.0" + "@typescript-eslint/typescript-estree" "8.52.0" + "@typescript-eslint/visitor-keys" "8.52.0" + debug "^4.4.3" + +"@typescript-eslint/project-service@8.52.0": + version "8.52.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/project-service/-/project-service-8.52.0.tgz#5fb4c16af4eda6d74c70cbc62f5d3f77b96e4cbe" + integrity sha512-xD0MfdSdEmeFa3OmVqonHi+Cciab96ls1UhIF/qX/O/gPu5KXD0bY9lu33jj04fjzrXHcuvjBcBC+D3SNSadaw== + dependencies: + "@typescript-eslint/tsconfig-utils" "^8.52.0" + "@typescript-eslint/types" "^8.52.0" + debug "^4.4.3" + +"@typescript-eslint/scope-manager@8.52.0": + version "8.52.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/scope-manager/-/scope-manager-8.52.0.tgz#9884ff690fad30380ccabfb08af1ac200af6b4e5" + integrity sha512-ixxqmmCcc1Nf8S0mS0TkJ/3LKcC8mruYJPOU6Ia2F/zUUR4pApW7LzrpU3JmtePbRUTes9bEqRc1Gg4iyRnDzA== + dependencies: + "@typescript-eslint/types" "8.52.0" + "@typescript-eslint/visitor-keys" "8.52.0" + +"@typescript-eslint/tsconfig-utils@8.52.0", "@typescript-eslint/tsconfig-utils@^8.52.0": + version "8.52.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.52.0.tgz#0296751c22ed05c83787a6eaec65ae221bd8b8ed" + integrity sha512-jl+8fzr/SdzdxWJznq5nvoI7qn2tNYV/ZBAEcaFMVXf+K6jmXvAFrgo/+5rxgnL152f//pDEAYAhhBAZGrVfwg== + +"@typescript-eslint/type-utils@8.52.0": + version "8.52.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/type-utils/-/type-utils-8.52.0.tgz#6e554113f8a074cf9b2faa818d2ebfccb867d6c5" + integrity sha512-JD3wKBRWglYRQkAtsyGz1AewDu3mTc7NtRjR/ceTyGoPqmdS5oCdx/oZMWD5Zuqmo6/MpsYs0wp6axNt88/2EQ== + dependencies: + "@typescript-eslint/types" "8.52.0" + "@typescript-eslint/typescript-estree" "8.52.0" + "@typescript-eslint/utils" "8.52.0" + debug "^4.4.3" + ts-api-utils "^2.4.0" + +"@typescript-eslint/types@8.52.0", "@typescript-eslint/types@^8.52.0": + version "8.52.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/types/-/types-8.52.0.tgz#1eb0a16b324824bc23b89d109a267c38c9213c4a" + integrity sha512-LWQV1V4q9V4cT4H5JCIx3481iIFxH1UkVk+ZkGGAV1ZGcjGI9IoFOfg3O6ywz8QqCDEp7Inlg6kovMofsNRaGg== + +"@typescript-eslint/typescript-estree@8.52.0": + version "8.52.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/typescript-estree/-/typescript-estree-8.52.0.tgz#2ad7721c671be2127951286cb7f44c4ce55b0591" + integrity sha512-XP3LClsCc0FsTK5/frGjolyADTh3QmsLp6nKd476xNI9CsSsLnmn4f0jrzNoAulmxlmNIpeXuHYeEQv61Q6qeQ== + dependencies: + "@typescript-eslint/project-service" "8.52.0" + "@typescript-eslint/tsconfig-utils" "8.52.0" + "@typescript-eslint/types" "8.52.0" + "@typescript-eslint/visitor-keys" "8.52.0" + debug "^4.4.3" + minimatch "^9.0.5" + semver "^7.7.3" + tinyglobby "^0.2.15" + ts-api-utils "^2.4.0" + +"@typescript-eslint/utils@8.52.0": + version "8.52.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/utils/-/utils-8.52.0.tgz#b249be8264899b80d996fa353b4b84da4662f962" + integrity sha512-wYndVMWkweqHpEpwPhwqE2lnD2DxC6WVLupU/DOt/0/v+/+iQbbzO3jOHjmBMnhu0DgLULvOaU4h4pwHYi2oRQ== + dependencies: + "@eslint-community/eslint-utils" "^4.9.1" + "@typescript-eslint/scope-manager" "8.52.0" + "@typescript-eslint/types" "8.52.0" + "@typescript-eslint/typescript-estree" "8.52.0" + +"@typescript-eslint/visitor-keys@8.52.0": + version "8.52.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/visitor-keys/-/visitor-keys-8.52.0.tgz#50361c48a6302676230fe498f80f6decce4bf673" + integrity sha512-ink3/Zofus34nmBsPjow63FP5M7IGff0RKAgqR6+CFpdk22M7aLwC9gOcLGYqr7MczLPzZVERW9hRog3O4n1sQ== + dependencies: + "@typescript-eslint/types" "8.52.0" + eslint-visitor-keys "^4.2.1" + +"@vitejs/plugin-react@^4.2.1": + version "4.7.0" + resolved "https://registry.yarnpkg.com/@vitejs/plugin-react/-/plugin-react-4.7.0.tgz#647af4e7bb75ad3add578e762ad984b90f4a24b9" + integrity sha512-gUu9hwfWvvEDBBmgtAowQCojwZmJ5mcLn3aufeCsitijs3+f2NsrPtlAWIR6OPiqljl96GVCUbLe0HyqIpVaoA== + dependencies: + "@babel/core" "^7.28.0" + "@babel/plugin-transform-react-jsx-self" "^7.27.1" + "@babel/plugin-transform-react-jsx-source" "^7.27.1" + "@rolldown/pluginutils" "1.0.0-beta.27" + "@types/babel__core" "^7.20.5" + react-refresh "^0.17.0" + +"@vitest/expect@2.1.9": + version "2.1.9" + resolved "https://registry.yarnpkg.com/@vitest/expect/-/expect-2.1.9.tgz#b566ea20d58ea6578d8dc37040d6c1a47ebe5ff8" + integrity sha512-UJCIkTBenHeKT1TTlKMJWy1laZewsRIzYighyYiJKZreqtdxSos/S1t+ktRMQWu2CKqaarrkeszJx1cgC5tGZw== + dependencies: + "@vitest/spy" "2.1.9" + "@vitest/utils" "2.1.9" + chai "^5.1.2" + tinyrainbow "^1.2.0" + +"@vitest/mocker@2.1.9": + version "2.1.9" + resolved "https://registry.yarnpkg.com/@vitest/mocker/-/mocker-2.1.9.tgz#36243b27351ca8f4d0bbc4ef91594ffd2dc25ef5" + integrity sha512-tVL6uJgoUdi6icpxmdrn5YNo3g3Dxv+IHJBr0GXHaEdTcw3F+cPKnsXFhli6nO+f/6SDKPHEK1UN+k+TQv0Ehg== + dependencies: + "@vitest/spy" "2.1.9" + estree-walker "^3.0.3" + magic-string "^0.30.12" + +"@vitest/pretty-format@2.1.9", "@vitest/pretty-format@^2.1.9": + version "2.1.9" + resolved "https://registry.yarnpkg.com/@vitest/pretty-format/-/pretty-format-2.1.9.tgz#434ff2f7611689f9ce70cd7d567eceb883653fdf" + integrity sha512-KhRIdGV2U9HOUzxfiHmY8IFHTdqtOhIzCpd8WRdJiE7D/HUcZVD0EgQCVjm+Q9gkUXWgBvMmTtZgIG48wq7sOQ== + dependencies: + tinyrainbow "^1.2.0" + +"@vitest/runner@2.1.9": + version "2.1.9" + resolved "https://registry.yarnpkg.com/@vitest/runner/-/runner-2.1.9.tgz#cc18148d2d797fd1fd5908d1f1851d01459be2f6" + integrity sha512-ZXSSqTFIrzduD63btIfEyOmNcBmQvgOVsPNPe0jYtESiXkhd8u2erDLnMxmGrDCwHCCHE7hxwRDCT3pt0esT4g== + dependencies: + "@vitest/utils" "2.1.9" + pathe "^1.1.2" + +"@vitest/snapshot@2.1.9": + version "2.1.9" + resolved "https://registry.yarnpkg.com/@vitest/snapshot/-/snapshot-2.1.9.tgz#24260b93f798afb102e2dcbd7e61c6dfa118df91" + integrity sha512-oBO82rEjsxLNJincVhLhaxxZdEtV0EFHMK5Kmx5sJ6H9L183dHECjiefOAdnqpIgT5eZwT04PoggUnW88vOBNQ== + dependencies: + "@vitest/pretty-format" "2.1.9" + magic-string "^0.30.12" + pathe "^1.1.2" + +"@vitest/spy@2.1.9": + version "2.1.9" + resolved "https://registry.yarnpkg.com/@vitest/spy/-/spy-2.1.9.tgz#cb28538c5039d09818b8bfa8edb4043c94727c60" + integrity sha512-E1B35FwzXXTs9FHNK6bDszs7mtydNi5MIfUWpceJ8Xbfb1gBMscAnwLbEu+B44ed6W3XjL9/ehLPHR1fkf1KLQ== + dependencies: + tinyspy "^3.0.2" + +"@vitest/utils@2.1.9": + version "2.1.9" + resolved "https://registry.yarnpkg.com/@vitest/utils/-/utils-2.1.9.tgz#4f2486de8a54acf7ecbf2c5c24ad7994a680a6c1" + integrity sha512-v0psaMSkNJ3A2NMrUEHFRzJtDPFn+/VWZ5WxImB21T9fjucJRmS7xCS3ppEnARb9y11OAzaD+P2Ps+b+BGX5iQ== + dependencies: + "@vitest/pretty-format" "2.1.9" + loupe "^3.1.2" + tinyrainbow "^1.2.0" + +acorn-jsx@^5.3.2: + version "5.3.2" + resolved "https://registry.yarnpkg.com/acorn-jsx/-/acorn-jsx-5.3.2.tgz#7ed5bb55908b3b2f1bc55c6af1653bada7f07937" + integrity sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ== + +acorn@^8.15.0: + version "8.15.0" + resolved "https://registry.yarnpkg.com/acorn/-/acorn-8.15.0.tgz#a360898bc415edaac46c8241f6383975b930b816" + integrity sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg== + +ag-auth@^2.1.0: + version "2.1.1" + resolved "https://registry.yarnpkg.com/ag-auth/-/ag-auth-2.1.1.tgz#4a48ba6a34cf71f4cb75863da5e8bb367f07e0f7" + integrity sha512-7XkzbCoW/jWpI/1wdmCCr1LXk4ucywXoORG68OzPQl/Y8/sWTMiGIU/stfnUh07kVkihn4dKelz3HJ2T6niPng== + dependencies: + jsonwebtoken "^9.0.2" + sc-errors "^3.0.0" + +ag-channel@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/ag-channel/-/ag-channel-5.0.0.tgz#c2c00dfbe372ae43e0466ec89e29aca1bbb2fb3e" + integrity sha512-bArHkdqQxynim981t8FLZM5TfA0v7p081OlFdOxs6clB79GSGcGlOQMDa31DT9F5VMjzqNiJmhfGwinvfU/3Zg== + dependencies: + consumable-stream "^2.0.0" + +ag-request@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/ag-request/-/ag-request-1.1.0.tgz#62ef63c572510bbce34993a5d47e467d0040a17f" + integrity sha512-d4K7QC1KnIpzcnUNNOeh1ddxmYMLiIdhdc1M8osxiHbZP/uoia4IINhhf2+1CrlnNJEPUoUH0Y58Sx0qeqoIvg== + dependencies: + sc-errors "^3.0.0" + +ajv@^6.12.4: + version "6.12.6" + resolved "https://registry.yarnpkg.com/ajv/-/ajv-6.12.6.tgz#baf5a62e802b07d977034586f8c3baf5adf26df4" + integrity sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g== + dependencies: + fast-deep-equal "^3.1.1" + fast-json-stable-stringify "^2.0.0" + json-schema-traverse "^0.4.1" + uri-js "^4.2.2" + +ansi-styles@^4.1.0: + version "4.3.0" + resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-4.3.0.tgz#edd803628ae71c04c85ae7a0906edad34b648937" + integrity sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg== + dependencies: + color-convert "^2.0.1" + +argparse@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/argparse/-/argparse-2.0.1.tgz#246f50f3ca78a3240f6c997e8a9bd1eac49e4b38" + integrity sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q== + +array-buffer-byte-length@^1.0.1, array-buffer-byte-length@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/array-buffer-byte-length/-/array-buffer-byte-length-1.0.2.tgz#384d12a37295aec3769ab022ad323a18a51ccf8b" + integrity sha512-LHE+8BuR7RYGDKvnrmcuSq3tDcKv9OFEXQt/HpbZhY7V6h0zlUXutnAD82GiFx9rdieCMjkvtcsPqBwgUl1Iiw== + dependencies: + call-bound "^1.0.3" + is-array-buffer "^3.0.5" + +array-includes@^3.1.6, array-includes@^3.1.8, array-includes@^3.1.9: + version "3.1.9" + resolved "https://registry.yarnpkg.com/array-includes/-/array-includes-3.1.9.tgz#1f0ccaa08e90cdbc3eb433210f903ad0f17c3f3a" + integrity sha512-FmeCCAenzH0KH381SPT5FZmiA/TmpndpcaShhfgEN9eCVjnFBqq3l1xrI42y8+PPLI6hypzou4GXw00WHmPBLQ== + dependencies: + call-bind "^1.0.8" + call-bound "^1.0.4" + define-properties "^1.2.1" + es-abstract "^1.24.0" + es-object-atoms "^1.1.1" + get-intrinsic "^1.3.0" + is-string "^1.1.1" + math-intrinsics "^1.1.0" + +array.prototype.findlast@^1.2.5: + version "1.2.5" + resolved "https://registry.yarnpkg.com/array.prototype.findlast/-/array.prototype.findlast-1.2.5.tgz#3e4fbcb30a15a7f5bf64cf2faae22d139c2e4904" + integrity sha512-CVvd6FHg1Z3POpBLxO6E6zr+rSKEQ9L6rZHAaY7lLfhKsWYUBBOuMs0e9o24oopj6H+geRCX0YJ+TJLBK2eHyQ== + dependencies: + call-bind "^1.0.7" + define-properties "^1.2.1" + es-abstract "^1.23.2" + es-errors "^1.3.0" + es-object-atoms "^1.0.0" + es-shim-unscopables "^1.0.2" + +array.prototype.findlastindex@^1.2.6: + version "1.2.6" + resolved "https://registry.yarnpkg.com/array.prototype.findlastindex/-/array.prototype.findlastindex-1.2.6.tgz#cfa1065c81dcb64e34557c9b81d012f6a421c564" + integrity sha512-F/TKATkzseUExPlfvmwQKGITM3DGTK+vkAsCZoDc5daVygbJBnjEUCbgkAvVFsgfXfX4YIqZ/27G3k3tdXrTxQ== + dependencies: + call-bind "^1.0.8" + call-bound "^1.0.4" + define-properties "^1.2.1" + es-abstract "^1.23.9" + es-errors "^1.3.0" + es-object-atoms "^1.1.1" + es-shim-unscopables "^1.1.0" + +array.prototype.flat@^1.3.1, array.prototype.flat@^1.3.3: + version "1.3.3" + resolved "https://registry.yarnpkg.com/array.prototype.flat/-/array.prototype.flat-1.3.3.tgz#534aaf9e6e8dd79fb6b9a9917f839ef1ec63afe5" + integrity sha512-rwG/ja1neyLqCuGZ5YYrznA62D4mZXg0i1cIskIUKSiqF3Cje9/wXAls9B9s1Wa2fomMsIv8czB8jZcPmxCXFg== + dependencies: + call-bind "^1.0.8" + define-properties "^1.2.1" + es-abstract "^1.23.5" + es-shim-unscopables "^1.0.2" + +array.prototype.flatmap@^1.3.3: + version "1.3.3" + resolved "https://registry.yarnpkg.com/array.prototype.flatmap/-/array.prototype.flatmap-1.3.3.tgz#712cc792ae70370ae40586264629e33aab5dd38b" + integrity sha512-Y7Wt51eKJSyi80hFrJCePGGNo5ktJCslFuboqJsbf57CCPcm5zztluPlc4/aD8sWsKvlwatezpV4U1efk8kpjg== + dependencies: + call-bind "^1.0.8" + define-properties "^1.2.1" + es-abstract "^1.23.5" + es-shim-unscopables "^1.0.2" + +array.prototype.tosorted@^1.1.4: + version "1.1.4" + resolved "https://registry.yarnpkg.com/array.prototype.tosorted/-/array.prototype.tosorted-1.1.4.tgz#fe954678ff53034e717ea3352a03f0b0b86f7ffc" + integrity sha512-p6Fx8B7b7ZhL/gmUsAy0D15WhvDccw3mnGNbZpi3pmeJdxtWsj2jEaI4Y6oo3XiHfzuSgPwKc04MYt6KgvC/wA== + dependencies: + call-bind "^1.0.7" + define-properties "^1.2.1" + es-abstract "^1.23.3" + es-errors "^1.3.0" + es-shim-unscopables "^1.0.2" + +arraybuffer.prototype.slice@^1.0.4: + version "1.0.4" + resolved "https://registry.yarnpkg.com/arraybuffer.prototype.slice/-/arraybuffer.prototype.slice-1.0.4.tgz#9d760d84dbdd06d0cbf92c8849615a1a7ab3183c" + integrity sha512-BNoCY6SXXPQ7gF2opIP4GBE+Xw7U+pHMYKuzjgCN3GwiaIR09UUeKfheyIry77QtrCBlC0KK0q5/TER/tYh3PQ== + dependencies: + array-buffer-byte-length "^1.0.1" + call-bind "^1.0.8" + define-properties "^1.2.1" + es-abstract "^1.23.5" + es-errors "^1.3.0" + get-intrinsic "^1.2.6" + is-array-buffer "^3.0.4" + +assertion-error@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/assertion-error/-/assertion-error-2.0.1.tgz#f641a196b335690b1070bf00b6e7593fec190bf7" + integrity sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA== + +async-function@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/async-function/-/async-function-1.0.0.tgz#509c9fca60eaf85034c6829838188e4e4c8ffb2b" + integrity sha512-hsU18Ae8CDTR6Kgu9DYf0EbCr/a5iGL0rytQDobUcdpYOKokk8LEjVphnXkDkgpi0wYVsqrXuP0bZxJaTqdgoA== + +async-stream-emitter@^7.0.1: + version "7.0.1" + resolved "https://registry.yarnpkg.com/async-stream-emitter/-/async-stream-emitter-7.0.1.tgz#c01832cddcc8f07d8ed528347803ec1517f8886d" + integrity sha512-1bgA3iZ80rCBX2LocvsyZPy0QB3/xM+CsXBze2HDHLmshOqx2JlAANGq23djaJ48e9fpcKzTzS1QM0hAKKI0UQ== + dependencies: + stream-demux "^10.0.1" + +available-typed-arrays@^1.0.7: + version "1.0.7" + resolved "https://registry.yarnpkg.com/available-typed-arrays/-/available-typed-arrays-1.0.7.tgz#a5cc375d6a03c2efc87a553f3e0b1522def14846" + integrity sha512-wvUjBtSGN7+7SjNpq/9M2Tg350UZD3q62IFZLbRAR1bSMlCo1ZaeW+BJ+D090e4hIIZLBcTDWe4Mh4jvUDajzQ== + dependencies: + possible-typed-array-names "^1.0.0" + +babel-plugin-macros@^3.1.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/babel-plugin-macros/-/babel-plugin-macros-3.1.0.tgz#9ef6dc74deb934b4db344dc973ee851d148c50c1" + integrity sha512-Cg7TFGpIr01vOQNODXOOaGz2NpCU5gl8x1qJFbb6hbZxR7XrcE2vtbAsTAbJ7/xwJtUuJEw8K8Zr/AE0LHlesg== + dependencies: + "@babel/runtime" "^7.12.5" + cosmiconfig "^7.0.0" + resolve "^1.19.0" + +babel-plugin-react-compiler@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/babel-plugin-react-compiler/-/babel-plugin-react-compiler-1.0.0.tgz#bdf7360a23a4d5ebfca090255da3893efd07425f" + integrity sha512-Ixm8tFfoKKIPYdCCKYTsqv+Fd4IJ0DQqMyEimo+pxUOMUR9cVPlwTrFt9Avu+3cb6Zp3mAzl+t1MrG2fxxKsxw== + dependencies: + "@babel/types" "^7.26.0" + +balanced-match@^1.0.0: + version "1.0.2" + resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-1.0.2.tgz#e83e3a7e3f300b34cb9d87f615fa0cbf357690ee" + integrity sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw== + +base64-js@^1.3.1: + version "1.5.1" + resolved "https://registry.yarnpkg.com/base64-js/-/base64-js-1.5.1.tgz#1b1b440160a5bf7ad40b650f095963481903930a" + integrity sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA== + +baseline-browser-mapping@^2.9.0: + version "2.9.11" + resolved "https://registry.yarnpkg.com/baseline-browser-mapping/-/baseline-browser-mapping-2.9.11.tgz#53724708c8db5f97206517ecfe362dbe5181deea" + integrity sha512-Sg0xJUNDU1sJNGdfGWhVHX0kkZ+HWcvmVymJbj6NSgZZmW/8S9Y2HQ5euytnIgakgxN6papOAWiwDo1ctFDcoQ== + +bl@^1.2.1: + version "1.2.3" + resolved "https://registry.yarnpkg.com/bl/-/bl-1.2.3.tgz#1e8dd80142eac80d7158c9dccc047fb620e035e7" + integrity sha512-pvcNpa0UU69UT341rO6AYy4FVAIkUHuZXRIWbq+zHnsVcRzDDjIAhGuuYoi0d//cwIwtt4pkpKycWEfjdV+vww== + dependencies: + readable-stream "^2.3.5" + safe-buffer "^5.1.1" + +brace-expansion@^1.1.7: + version "1.1.12" + resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-1.1.12.tgz#ab9b454466e5a8cc3a187beaad580412a9c5b843" + integrity sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg== + dependencies: + balanced-match "^1.0.0" + concat-map "0.0.1" + +brace-expansion@^2.0.1: + version "2.0.2" + resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-2.0.2.tgz#54fc53237a613d854c7bd37463aad17df87214e7" + integrity sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ== + dependencies: + balanced-match "^1.0.0" + +browserslist@^4.24.0: + version "4.28.1" + resolved "https://registry.yarnpkg.com/browserslist/-/browserslist-4.28.1.tgz#7f534594628c53c63101079e27e40de490456a95" + integrity sha512-ZC5Bd0LgJXgwGqUknZY/vkUQ04r8NXnJZ3yYi4vDmSiZmC/pdSN0NbNRPxZpbtO4uAfDUAFffO8IZoM3Gj8IkA== + dependencies: + baseline-browser-mapping "^2.9.0" + caniuse-lite "^1.0.30001759" + electron-to-chromium "^1.5.263" + node-releases "^2.0.27" + update-browserslist-db "^1.2.0" + +buffer-equal-constant-time@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/buffer-equal-constant-time/-/buffer-equal-constant-time-1.0.1.tgz#f8e71132f7ffe6e01a5c9697a4c6f3e48d5cc819" + integrity sha512-zRpUiDwd/xk6ADqPMATG8vc9VPrkck7T07OIx0gnjmJAnHnTVXNQG3vfvWNuiZIkwu9KrKdA1iJKfsfTVxE6NA== + +buffer@^5.2.1: + version "5.7.1" + resolved "https://registry.yarnpkg.com/buffer/-/buffer-5.7.1.tgz#ba62e7c13133053582197160851a8f648e99eed0" + integrity sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ== + dependencies: + base64-js "^1.3.1" + ieee754 "^1.1.13" + +cac@^6.7.14: + version "6.7.14" + resolved "https://registry.yarnpkg.com/cac/-/cac-6.7.14.tgz#804e1e6f506ee363cb0e3ccbb09cad5dd9870959" + integrity sha512-b6Ilus+c3RrdDk+JhLKUAQfzzgLEPy6wcXqS7f/xe1EETvsDP6GORG7SFuOs6cID5YkqchW/LXZbX5bc8j7ZcQ== + +call-bind-apply-helpers@^1.0.0, call-bind-apply-helpers@^1.0.1, call-bind-apply-helpers@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz#4b5428c222be985d79c3d82657479dbe0b59b2d6" + integrity sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ== + dependencies: + es-errors "^1.3.0" + function-bind "^1.1.2" + +call-bind@^1.0.7, call-bind@^1.0.8: + version "1.0.8" + resolved "https://registry.yarnpkg.com/call-bind/-/call-bind-1.0.8.tgz#0736a9660f537e3388826f440d5ec45f744eaa4c" + integrity sha512-oKlSFMcMwpUg2ednkhQ454wfWiU/ul3CkJe/PEHcTKuiX6RpbehUiFMXu13HalGZxfUwCQzZG747YXBn1im9ww== + dependencies: + call-bind-apply-helpers "^1.0.0" + es-define-property "^1.0.0" + get-intrinsic "^1.2.4" + set-function-length "^1.2.2" + +call-bound@^1.0.2, call-bound@^1.0.3, call-bound@^1.0.4: + version "1.0.4" + resolved "https://registry.yarnpkg.com/call-bound/-/call-bound-1.0.4.tgz#238de935d2a2a692928c538c7ccfa91067fd062a" + integrity sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg== + dependencies: + call-bind-apply-helpers "^1.0.2" + get-intrinsic "^1.3.0" + +callsites@^3.0.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/callsites/-/callsites-3.1.0.tgz#b3630abd8943432f54b3f0519238e33cd7df2f73" + integrity sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ== + +caniuse-lite@^1.0.30001759: + version "1.0.30001762" + resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001762.tgz#e4dbfeda63d33258cdde93e53af2023a13ba27d4" + integrity sha512-PxZwGNvH7Ak8WX5iXzoK1KPZttBXNPuaOvI2ZYU7NrlM+d9Ov+TUvlLOBNGzVXAntMSMMlJPd+jY6ovrVjSmUw== + +canvas-renderer@~2.2.0: + version "2.2.1" + resolved "https://registry.yarnpkg.com/canvas-renderer/-/canvas-renderer-2.2.1.tgz#c1d131f78a9799aca8af9679ad0a005052b65550" + integrity sha512-RrBgVL5qCEDIXpJ6NrzyRNoTnXxYarqm/cS/W6ERhUJts5UQtt/XPEosGN3rqUkZ4fjBArlnCbsISJ+KCFnIAg== + dependencies: + "@types/node" "*" + +chai@^5.1.2: + version "5.3.3" + resolved "https://registry.yarnpkg.com/chai/-/chai-5.3.3.tgz#dd3da955e270916a4bd3f625f4b919996ada7e06" + integrity sha512-4zNhdJD/iOjSH0A05ea+Ke6MU5mmpQcbQsSOkgdaUMJ9zTlDTD/GYlwohmIE2u0gaxHYiVHEn1Fw9mZ/ktJWgw== + dependencies: + assertion-error "^2.0.1" + check-error "^2.1.1" + deep-eql "^5.0.1" + loupe "^3.1.0" + pathval "^2.0.0" + +chalk@^4.0.0: + version "4.1.2" + resolved "https://registry.yarnpkg.com/chalk/-/chalk-4.1.2.tgz#aac4e2b7734a740867aeb16bf02aad556a1e7a01" + integrity sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA== + dependencies: + ansi-styles "^4.1.0" + supports-color "^7.1.0" + +check-error@^2.1.1: + version "2.1.3" + resolved "https://registry.yarnpkg.com/check-error/-/check-error-2.1.3.tgz#2427361117b70cca8dc89680ead32b157019caf5" + integrity sha512-PAJdDJusoxnwm1VwW07VWwUN1sl7smmC3OKggvndJFadxxDRyFJBX/ggnu/KE4kQAB7a3Dp8f/YXC1FlUprWmA== + +clone-deep@^4.0.1: + version "4.0.1" + resolved "https://registry.yarnpkg.com/clone-deep/-/clone-deep-4.0.1.tgz#c19fd9bdbbf85942b4fd979c84dcf7d5f07c2387" + integrity sha512-neHB9xuzh/wk0dIHweyAXv2aPGZIVk3pLMe+/RNzINf17fe0OG96QroktYAUm7SM1PBnzTabaLboqqxDyMU+SQ== + dependencies: + is-plain-object "^2.0.4" + kind-of "^6.0.2" + shallow-clone "^3.0.0" + +clsx@^1.1.0: + version "1.2.1" + resolved "https://registry.yarnpkg.com/clsx/-/clsx-1.2.1.tgz#0ddc4a20a549b59c93a4116bb26f5294ca17dc12" + integrity sha512-EcR6r5a8bj6pu3ycsa/E/cKVGuTgZJZdsyUYHOksG/UHIiKfjxzRxYJpyVBwYaQeOvghal9fcc4PidlgzugAQg== + +clsx@^2.1.1: + version "2.1.1" + resolved "https://registry.yarnpkg.com/clsx/-/clsx-2.1.1.tgz#eed397c9fd8bd882bfb18deab7102049a2f32999" + integrity sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA== + +color-convert@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/color-convert/-/color-convert-2.0.1.tgz#72d3a68d598c9bdb3af2ad1e84f21d896abd4de3" + integrity sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ== + dependencies: + color-name "~1.1.4" + +color-name@~1.1.4: + version "1.1.4" + resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.4.tgz#c2a09a87acbde69543de6f63fa3995c826c536a2" + integrity sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA== + +concat-map@0.0.1: + version "0.0.1" + resolved "https://registry.yarnpkg.com/concat-map/-/concat-map-0.0.1.tgz#d8a96bd77fd68df7793a73036a3ba0d5405d477b" + integrity sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg== + +consumable-stream@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/consumable-stream/-/consumable-stream-2.0.0.tgz#11d3c7281b747eb9efd31c199b3a8b1711bec654" + integrity sha512-I6WA2JVYXs/68rEvi1ie3rZjP6qusTVFEQkbzR+WC+fY56TpwiGTIDJETsrnlxv5CsnmK69ps6CkYvIbpEEqBA== + +consumable-stream@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/consumable-stream/-/consumable-stream-3.0.0.tgz#2bf140e0c5f9b63d6fa116ac6b05e53713d3cb41" + integrity sha512-CnnsJ9OG9ouxAjt3pc63/DaerezRo/WudqU71pc5epaIUi7NHu2T4v+3f0nKbbCY7icS/TfQ1Satr9rwZ7Jwsg== + +convert-source-map@^1.5.0: + version "1.9.0" + resolved "https://registry.yarnpkg.com/convert-source-map/-/convert-source-map-1.9.0.tgz#7faae62353fb4213366d0ca98358d22e8368b05f" + integrity sha512-ASFBup0Mz1uyiIjANan1jzLQami9z1PoYSZCiiYW2FczPbenXc45FZdBZLzOT+r6+iciuEModtmCti+hjaAk0A== + +convert-source-map@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/convert-source-map/-/convert-source-map-2.0.0.tgz#4b560f649fc4e918dd0ab75cf4961e8bc882d82a" + integrity sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg== + +cookie@^1.0.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/cookie/-/cookie-1.1.1.tgz#3bb9bdfc82369db9c2f69c93c9c3ceb310c88b3c" + integrity sha512-ei8Aos7ja0weRpFzJnEA9UHJ/7XQmqglbRwnf2ATjcB9Wq874VKH9kfjjirM6UhU2/E5fFYadylyhFldcqSidQ== + +core-util-is@~1.0.0: + version "1.0.3" + resolved "https://registry.yarnpkg.com/core-util-is/-/core-util-is-1.0.3.tgz#a6042d3634c2b27e9328f837b965fac83808db85" + integrity sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ== + +cosmiconfig@^7.0.0: + version "7.1.0" + resolved "https://registry.yarnpkg.com/cosmiconfig/-/cosmiconfig-7.1.0.tgz#1443b9afa596b670082ea46cbd8f6a62b84635f6" + integrity sha512-AdmX6xUzdNASswsFtmwSt7Vj8po9IuqXm0UXz7QKPuEUmPB4XyjGfaAr2PSuELMwkRMVH1EpIkX5bTZGRB3eCA== + dependencies: + "@types/parse-json" "^4.0.0" + import-fresh "^3.2.1" + parse-json "^5.0.0" + path-type "^4.0.0" + yaml "^1.10.0" + +cross-spawn@^7.0.3, cross-spawn@^7.0.6: + version "7.0.6" + resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-7.0.6.tgz#8a58fe78f00dcd70c370451759dfbfaf03e8ee9f" + integrity sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA== + dependencies: + path-key "^3.1.0" + shebang-command "^2.0.0" + which "^2.0.1" + +csstype@^3.0.2, csstype@^3.1.3, csstype@^3.2.2: + version "3.2.3" + resolved "https://registry.yarnpkg.com/csstype/-/csstype-3.2.3.tgz#ec48c0f3e993e50648c86da559e2610995cf989a" + integrity sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ== + +data-view-buffer@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/data-view-buffer/-/data-view-buffer-1.0.2.tgz#211a03ba95ecaf7798a8c7198d79536211f88570" + integrity sha512-EmKO5V3OLXh1rtK2wgXRansaK1/mtVdTUEiEI0W8RkvgT05kfxaH29PliLnpLP73yYO6142Q72QNa8Wx/A5CqQ== + dependencies: + call-bound "^1.0.3" + es-errors "^1.3.0" + is-data-view "^1.0.2" + +data-view-byte-length@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/data-view-byte-length/-/data-view-byte-length-1.0.2.tgz#9e80f7ca52453ce3e93d25a35318767ea7704735" + integrity sha512-tuhGbE6CfTM9+5ANGf+oQb72Ky/0+s3xKUpHvShfiz2RxMFgFPjsXuRLBVMtvMs15awe45SRb83D6wH4ew6wlQ== + dependencies: + call-bound "^1.0.3" + es-errors "^1.3.0" + is-data-view "^1.0.2" + +data-view-byte-offset@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/data-view-byte-offset/-/data-view-byte-offset-1.0.1.tgz#068307f9b71ab76dbbe10291389e020856606191" + integrity sha512-BS8PfmtDGnrgYdOonGZQdLZslWIeCGFP9tpan0hi1Co2Zr2NKADsvGYA8XxuG/4UWgJ6Cjtv+YJnB6MM69QGlQ== + dependencies: + call-bound "^1.0.2" + es-errors "^1.3.0" + is-data-view "^1.0.1" + +dayjs@^1.11.13: + version "1.11.19" + resolved "https://registry.yarnpkg.com/dayjs/-/dayjs-1.11.19.tgz#15dc98e854bb43917f12021806af897c58ae2938" + integrity sha512-t5EcLVS6QPBNqM2z8fakk/NKel+Xzshgt8FFKAn+qwlD1pzZWxh0nVCrvFK7ZDb6XucZeF9z8C7CBWTRIVApAw== + +debug@^3.2.7: + version "3.2.7" + resolved "https://registry.yarnpkg.com/debug/-/debug-3.2.7.tgz#72580b7e9145fb39b6676f9c5e5fb100b934179a" + integrity sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ== + dependencies: + ms "^2.1.1" + +debug@^4.1.0, debug@^4.1.1, debug@^4.3.1, debug@^4.3.2, debug@^4.3.7, debug@^4.4.3: + version "4.4.3" + resolved "https://registry.yarnpkg.com/debug/-/debug-4.4.3.tgz#c6ae432d9bd9662582fce08709b038c58e9e3d6a" + integrity sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA== + dependencies: + ms "^2.1.3" + +deep-eql@^5.0.1: + version "5.0.2" + resolved "https://registry.yarnpkg.com/deep-eql/-/deep-eql-5.0.2.tgz#4b756d8d770a9257300825d52a2c2cff99c3a341" + integrity sha512-h5k/5U50IJJFpzfL6nO9jaaumfjO/f2NjK/oYB2Djzm4p9L+3T9qWpZqZ2hAbLPuuYq9wrU08WQyBTL5GbPk5Q== + +deep-is@^0.1.3: + version "0.1.4" + resolved "https://registry.yarnpkg.com/deep-is/-/deep-is-0.1.4.tgz#a6f2dce612fadd2ef1f519b73551f17e85199831" + integrity sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ== + +default-gateway@^6.0.3: + version "6.0.3" + resolved "https://registry.yarnpkg.com/default-gateway/-/default-gateway-6.0.3.tgz#819494c888053bdb743edbf343d6cdf7f2943a71" + integrity sha512-fwSOJsbbNzZ/CUFpqFBqYfYNLj1NbMPm8MMCIzHjC83iSJRBEGmDUxU+WP661BaBQImeC2yHwXtz+P/O9o+XEg== + dependencies: + execa "^5.0.0" + +define-data-property@^1.0.1, define-data-property@^1.1.4: + version "1.1.4" + resolved "https://registry.yarnpkg.com/define-data-property/-/define-data-property-1.1.4.tgz#894dc141bb7d3060ae4366f6a0107e68fbe48c5e" + integrity sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A== + dependencies: + es-define-property "^1.0.0" + es-errors "^1.3.0" + gopd "^1.0.1" + +define-properties@^1.1.3, define-properties@^1.2.1: + version "1.2.1" + resolved "https://registry.yarnpkg.com/define-properties/-/define-properties-1.2.1.tgz#10781cc616eb951a80a034bafcaa7377f6af2b6c" + integrity sha512-8QmQKqEASLd5nx0U1B1okLElbUuuttJ/AnYmRXbbbGDWh6uS208EjD4Xqq/I9wK7u0v6O08XhTWnt5XtEbR6Dg== + dependencies: + define-data-property "^1.0.1" + has-property-descriptors "^1.0.0" + object-keys "^1.1.1" + +dns-over-http-resolver@^1.2.3: + version "1.2.3" + resolved "https://registry.yarnpkg.com/dns-over-http-resolver/-/dns-over-http-resolver-1.2.3.tgz#194d5e140a42153f55bb79ac5a64dd2768c36af9" + integrity sha512-miDiVSI6KSNbi4SVifzO/reD8rMnxgrlnkrlkugOLQpWQTe2qMdHsZp5DmfKjxNE+/T3VAAYLQUZMv9SMr6+AA== + dependencies: + debug "^4.3.1" + native-fetch "^3.0.0" + receptacle "^1.3.2" + +doctrine@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/doctrine/-/doctrine-2.1.0.tgz#5cd01fc101621b42c4cd7f5d1a66243716d3f39d" + integrity sha512-35mSku4ZXK0vfCuHEDAwt55dg2jNajHZ1odvF+8SSr82EsZY4QmXfuWso8oEd8zRhVObSN18aM0CjSdoBX7zIw== + dependencies: + esutils "^2.0.2" + +dom-helpers@^5.0.1: + version "5.2.1" + resolved "https://registry.yarnpkg.com/dom-helpers/-/dom-helpers-5.2.1.tgz#d9400536b2bf8225ad98fe052e029451ac40e902" + integrity sha512-nRCa7CK3VTrM2NmGkIy4cbK7IZlgBE/PYMn55rrXefr5xXDP0LdtfPnblFDoVdcAfslJ7or6iqAUnx0CCGIWQA== + dependencies: + "@babel/runtime" "^7.8.7" + csstype "^3.0.2" + +dunder-proto@^1.0.0, dunder-proto@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/dunder-proto/-/dunder-proto-1.0.1.tgz#d7ae667e1dc83482f8b70fd0f6eefc50da30f58a" + integrity sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A== + dependencies: + call-bind-apply-helpers "^1.0.1" + es-errors "^1.3.0" + gopd "^1.2.0" + +ecdsa-sig-formatter@1.0.11: + version "1.0.11" + resolved "https://registry.yarnpkg.com/ecdsa-sig-formatter/-/ecdsa-sig-formatter-1.0.11.tgz#ae0f0fa2d85045ef14a817daa3ce9acd0489e5bf" + integrity sha512-nagl3RYrbNv6kQkeJIpt6NJZy8twLB/2vtz6yN9Z4vRKHN4/QZJIEbqohALSgwKdnksuY3k5Addp5lg8sVoVcQ== + dependencies: + safe-buffer "^5.0.1" + +electron-to-chromium@^1.5.263: + version "1.5.267" + resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.5.267.tgz#5d84f2df8cdb6bfe7e873706bb21bd4bfb574dc7" + integrity sha512-0Drusm6MVRXSOJpGbaSVgcQsuB4hEkMpHXaVstcPmhu5LIedxs1xNK/nIxmQIU/RPC0+1/o0AVZfBTkTNJOdUw== + +err-code@^3.0.1: + version "3.0.1" + resolved "https://registry.yarnpkg.com/err-code/-/err-code-3.0.1.tgz#a444c7b992705f2b120ee320b09972eef331c920" + integrity sha512-GiaH0KJUewYok+eeY05IIgjtAe4Yltygk9Wqp1V5yVWLdhf0hYZchRjNIT9bb0mSwRcIusT3cx7PJUf3zEIfUA== + +error-ex@^1.3.1: + version "1.3.4" + resolved "https://registry.yarnpkg.com/error-ex/-/error-ex-1.3.4.tgz#b3a8d8bb6f92eecc1629e3e27d3c8607a8a32414" + integrity sha512-sqQamAnR14VgCr1A618A3sGrygcpK+HEbenA/HiEAkkUwcZIIB/tgWqHFxWgOyDh4nB4JCRimh79dR5Ywc9MDQ== + dependencies: + is-arrayish "^0.2.1" + +es-abstract@^1.17.5, es-abstract@^1.23.2, es-abstract@^1.23.3, es-abstract@^1.23.5, es-abstract@^1.23.6, es-abstract@^1.23.9, es-abstract@^1.24.0, es-abstract@^1.24.1: + version "1.24.1" + resolved "https://registry.yarnpkg.com/es-abstract/-/es-abstract-1.24.1.tgz#f0c131ed5ea1bb2411134a8dd94def09c46c7899" + integrity sha512-zHXBLhP+QehSSbsS9Pt23Gg964240DPd6QCf8WpkqEXxQ7fhdZzYsocOr5u7apWonsS5EjZDmTF+/slGMyasvw== + dependencies: + array-buffer-byte-length "^1.0.2" + arraybuffer.prototype.slice "^1.0.4" + available-typed-arrays "^1.0.7" + call-bind "^1.0.8" + call-bound "^1.0.4" + data-view-buffer "^1.0.2" + data-view-byte-length "^1.0.2" + data-view-byte-offset "^1.0.1" + es-define-property "^1.0.1" + es-errors "^1.3.0" + es-object-atoms "^1.1.1" + es-set-tostringtag "^2.1.0" + es-to-primitive "^1.3.0" + function.prototype.name "^1.1.8" + get-intrinsic "^1.3.0" + get-proto "^1.0.1" + get-symbol-description "^1.1.0" + globalthis "^1.0.4" + gopd "^1.2.0" + has-property-descriptors "^1.0.2" + has-proto "^1.2.0" + has-symbols "^1.1.0" + hasown "^2.0.2" + internal-slot "^1.1.0" + is-array-buffer "^3.0.5" + is-callable "^1.2.7" + is-data-view "^1.0.2" + is-negative-zero "^2.0.3" + is-regex "^1.2.1" + is-set "^2.0.3" + is-shared-array-buffer "^1.0.4" + is-string "^1.1.1" + is-typed-array "^1.1.15" + is-weakref "^1.1.1" + math-intrinsics "^1.1.0" + object-inspect "^1.13.4" + object-keys "^1.1.1" + object.assign "^4.1.7" + own-keys "^1.0.1" + regexp.prototype.flags "^1.5.4" + safe-array-concat "^1.1.3" + safe-push-apply "^1.0.0" + safe-regex-test "^1.1.0" + set-proto "^1.0.0" + stop-iteration-iterator "^1.1.0" + string.prototype.trim "^1.2.10" + string.prototype.trimend "^1.0.9" + string.prototype.trimstart "^1.0.8" + typed-array-buffer "^1.0.3" + typed-array-byte-length "^1.0.3" + typed-array-byte-offset "^1.0.4" + typed-array-length "^1.0.7" + unbox-primitive "^1.1.0" + which-typed-array "^1.1.19" + +es-define-property@^1.0.0, es-define-property@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/es-define-property/-/es-define-property-1.0.1.tgz#983eb2f9a6724e9303f61addf011c72e09e0b0fa" + integrity sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g== + +es-errors@^1.3.0: + version "1.3.0" + resolved "https://registry.yarnpkg.com/es-errors/-/es-errors-1.3.0.tgz#05f75a25dab98e4fb1dcd5e1472c0546d5057c8f" + integrity sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw== + +es-iterator-helpers@^1.2.1: + version "1.2.2" + resolved "https://registry.yarnpkg.com/es-iterator-helpers/-/es-iterator-helpers-1.2.2.tgz#d979a9f686e2b0b72f88dbead7229924544720bc" + integrity sha512-BrUQ0cPTB/IwXj23HtwHjS9n7O4h9FX94b4xc5zlTHxeLgTAdzYUDyy6KdExAl9lbN5rtfe44xpjpmj9grxs5w== + dependencies: + call-bind "^1.0.8" + call-bound "^1.0.4" + define-properties "^1.2.1" + es-abstract "^1.24.1" + es-errors "^1.3.0" + es-set-tostringtag "^2.1.0" + function-bind "^1.1.2" + get-intrinsic "^1.3.0" + globalthis "^1.0.4" + gopd "^1.2.0" + has-property-descriptors "^1.0.2" + has-proto "^1.2.0" + has-symbols "^1.1.0" + internal-slot "^1.1.0" + iterator.prototype "^1.1.5" + safe-array-concat "^1.1.3" + +es-module-lexer@^1.5.4: + version "1.7.0" + resolved "https://registry.yarnpkg.com/es-module-lexer/-/es-module-lexer-1.7.0.tgz#9159601561880a85f2734560a9099b2c31e5372a" + integrity sha512-jEQoCwk8hyb2AZziIOLhDqpm5+2ww5uIE6lkO/6jcOCusfk6LhMHpXXfBLXTZ7Ydyt0j4VoUQv6uGNYbdW+kBA== + +es-object-atoms@^1.0.0, es-object-atoms@^1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/es-object-atoms/-/es-object-atoms-1.1.1.tgz#1c4f2c4837327597ce69d2ca190a7fdd172338c1" + integrity sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA== + dependencies: + es-errors "^1.3.0" + +es-set-tostringtag@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/es-set-tostringtag/-/es-set-tostringtag-2.1.0.tgz#f31dbbe0c183b00a6d26eb6325c810c0fd18bd4d" + integrity sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA== + dependencies: + es-errors "^1.3.0" + get-intrinsic "^1.2.6" + has-tostringtag "^1.0.2" + hasown "^2.0.2" + +es-shim-unscopables@^1.0.2, es-shim-unscopables@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/es-shim-unscopables/-/es-shim-unscopables-1.1.0.tgz#438df35520dac5d105f3943d927549ea3b00f4b5" + integrity sha512-d9T8ucsEhh8Bi1woXCf+TIKDIROLG5WCkxg8geBCbvk22kzwC5G2OnXVMO6FUsvQlgUUXQ2itephWDLqDzbeCw== + dependencies: + hasown "^2.0.2" + +es-to-primitive@^1.3.0: + version "1.3.0" + resolved "https://registry.yarnpkg.com/es-to-primitive/-/es-to-primitive-1.3.0.tgz#96c89c82cc49fd8794a24835ba3e1ff87f214e18" + integrity sha512-w+5mJ3GuFL+NjVtJlvydShqE1eN3h3PbI7/5LAsYJP/2qtuMXjfL2LpHSRqo4b4eSF5K/DH1JXKUAHSB2UW50g== + dependencies: + is-callable "^1.2.7" + is-date-object "^1.0.5" + is-symbol "^1.0.4" + +esbuild@^0.21.3: + version "0.21.5" + resolved "https://registry.yarnpkg.com/esbuild/-/esbuild-0.21.5.tgz#9ca301b120922959b766360d8ac830da0d02997d" + integrity sha512-mg3OPMV4hXywwpoDxu3Qda5xCKQi+vCTZq8S9J/EpkhB2HzKXq4SNFZE3+NK93JYxc8VMSep+lOUSC/RVKaBqw== + optionalDependencies: + "@esbuild/aix-ppc64" "0.21.5" + "@esbuild/android-arm" "0.21.5" + "@esbuild/android-arm64" "0.21.5" + "@esbuild/android-x64" "0.21.5" + "@esbuild/darwin-arm64" "0.21.5" + "@esbuild/darwin-x64" "0.21.5" + "@esbuild/freebsd-arm64" "0.21.5" + "@esbuild/freebsd-x64" "0.21.5" + "@esbuild/linux-arm" "0.21.5" + "@esbuild/linux-arm64" "0.21.5" + "@esbuild/linux-ia32" "0.21.5" + "@esbuild/linux-loong64" "0.21.5" + "@esbuild/linux-mips64el" "0.21.5" + "@esbuild/linux-ppc64" "0.21.5" + "@esbuild/linux-riscv64" "0.21.5" + "@esbuild/linux-s390x" "0.21.5" + "@esbuild/linux-x64" "0.21.5" + "@esbuild/netbsd-x64" "0.21.5" + "@esbuild/openbsd-x64" "0.21.5" + "@esbuild/sunos-x64" "0.21.5" + "@esbuild/win32-arm64" "0.21.5" + "@esbuild/win32-ia32" "0.21.5" + "@esbuild/win32-x64" "0.21.5" + +escalade@^3.2.0: + version "3.2.0" + resolved "https://registry.yarnpkg.com/escalade/-/escalade-3.2.0.tgz#011a3f69856ba189dffa7dc8fcce99d2a87903e5" + integrity sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA== + +escape-string-regexp@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz#14ba83a5d373e3d311e5afca29cf5bfad965bf34" + integrity sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA== + +eslint-import-resolver-node@^0.3.9: + version "0.3.9" + resolved "https://registry.yarnpkg.com/eslint-import-resolver-node/-/eslint-import-resolver-node-0.3.9.tgz#d4eaac52b8a2e7c3cd1903eb00f7e053356118ac" + integrity sha512-WFj2isz22JahUv+B788TlO3N6zL3nNJGU8CcZbPZvVEkBPaJdCV4vy5wyghty5ROFbCRnm132v8BScu5/1BQ8g== + dependencies: + debug "^3.2.7" + is-core-module "^2.13.0" + resolve "^1.22.4" + +eslint-module-utils@^2.12.1: + version "2.12.1" + resolved "https://registry.yarnpkg.com/eslint-module-utils/-/eslint-module-utils-2.12.1.tgz#f76d3220bfb83c057651359295ab5854eaad75ff" + integrity sha512-L8jSWTze7K2mTg0vos/RuLRS5soomksDPoJLXIslC7c8Wmut3bx7CPpJijDcBZtxQ5lrbUdM+s0OlNbz0DCDNw== + dependencies: + debug "^3.2.7" + +eslint-plugin-import@^2.32.0: + version "2.32.0" + resolved "https://registry.yarnpkg.com/eslint-plugin-import/-/eslint-plugin-import-2.32.0.tgz#602b55faa6e4caeaa5e970c198b5c00a37708980" + integrity sha512-whOE1HFo/qJDyX4SnXzP4N6zOWn79WhnCUY/iDR0mPfQZO8wcYE4JClzI2oZrhBnnMUCBCHZhO6VQyoBU95mZA== + dependencies: + "@rtsao/scc" "^1.1.0" + array-includes "^3.1.9" + array.prototype.findlastindex "^1.2.6" + array.prototype.flat "^1.3.3" + array.prototype.flatmap "^1.3.3" + debug "^3.2.7" + doctrine "^2.1.0" + eslint-import-resolver-node "^0.3.9" + eslint-module-utils "^2.12.1" + hasown "^2.0.2" + is-core-module "^2.16.1" + is-glob "^4.0.3" + minimatch "^3.1.2" + object.fromentries "^2.0.8" + object.groupby "^1.0.3" + object.values "^1.2.1" + semver "^6.3.1" + string.prototype.trimend "^1.0.9" + tsconfig-paths "^3.15.0" + +eslint-plugin-react-hooks@^7.0.1: + version "7.0.1" + resolved "https://registry.yarnpkg.com/eslint-plugin-react-hooks/-/eslint-plugin-react-hooks-7.0.1.tgz#66e258db58ece50723ef20cc159f8aa908219169" + integrity sha512-O0d0m04evaNzEPoSW+59Mezf8Qt0InfgGIBJnpC0h3NH/WjUAR7BIKUfysC6todmtiZ/A0oUVS8Gce0WhBrHsA== + dependencies: + "@babel/core" "^7.24.4" + "@babel/parser" "^7.24.4" + hermes-parser "^0.25.1" + zod "^3.25.0 || ^4.0.0" + zod-validation-error "^3.5.0 || ^4.0.0" + +eslint-plugin-react@^7.35.0: + version "7.37.5" + resolved "https://registry.yarnpkg.com/eslint-plugin-react/-/eslint-plugin-react-7.37.5.tgz#2975511472bdda1b272b34d779335c9b0e877065" + integrity sha512-Qteup0SqU15kdocexFNAJMvCJEfa2xUKNV4CC1xsVMrIIqEy3SQ/rqyxCWNzfrd3/ldy6HMlD2e0JDVpDg2qIA== + dependencies: + array-includes "^3.1.8" + array.prototype.findlast "^1.2.5" + array.prototype.flatmap "^1.3.3" + array.prototype.tosorted "^1.1.4" + doctrine "^2.1.0" + es-iterator-helpers "^1.2.1" + estraverse "^5.3.0" + hasown "^2.0.2" + jsx-ast-utils "^2.4.1 || ^3.0.0" + minimatch "^3.1.2" + object.entries "^1.1.9" + object.fromentries "^2.0.8" + object.values "^1.2.1" + prop-types "^15.8.1" + resolve "^2.0.0-next.5" + semver "^6.3.1" + string.prototype.matchall "^4.0.12" + string.prototype.repeat "^1.0.0" + +eslint-scope@^8.4.0: + version "8.4.0" + resolved "https://registry.yarnpkg.com/eslint-scope/-/eslint-scope-8.4.0.tgz#88e646a207fad61436ffa39eb505147200655c82" + integrity sha512-sNXOfKCn74rt8RICKMvJS7XKV/Xk9kA7DyJr8mJik3S7Cwgy3qlkkmyS2uQB3jiJg6VNdZd/pDBJu0nvG2NlTg== + dependencies: + esrecurse "^4.3.0" + estraverse "^5.2.0" + +eslint-visitor-keys@^3.4.3: + version "3.4.3" + resolved "https://registry.yarnpkg.com/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz#0cd72fe8550e3c2eae156a96a4dddcd1c8ac5800" + integrity sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag== + +eslint-visitor-keys@^4.2.1: + version "4.2.1" + resolved "https://registry.yarnpkg.com/eslint-visitor-keys/-/eslint-visitor-keys-4.2.1.tgz#4cfea60fe7dd0ad8e816e1ed026c1d5251b512c1" + integrity sha512-Uhdk5sfqcee/9H/rCOJikYz67o0a2Tw2hGRPOG2Y1R2dg7brRe1uG0yaNQDHu+TO/uQPF/5eCapvYSmHUjt7JQ== + +eslint@^9.9.0: + version "9.39.2" + resolved "https://registry.yarnpkg.com/eslint/-/eslint-9.39.2.tgz#cb60e6d16ab234c0f8369a3fe7cc87967faf4b6c" + integrity sha512-LEyamqS7W5HB3ujJyvi0HQK/dtVINZvd5mAAp9eT5S/ujByGjiZLCzPcHVzuXbpJDJF/cxwHlfceVUDZ2lnSTw== + dependencies: + "@eslint-community/eslint-utils" "^4.8.0" + "@eslint-community/regexpp" "^4.12.1" + "@eslint/config-array" "^0.21.1" + "@eslint/config-helpers" "^0.4.2" + "@eslint/core" "^0.17.0" + "@eslint/eslintrc" "^3.3.1" + "@eslint/js" "9.39.2" + "@eslint/plugin-kit" "^0.4.1" + "@humanfs/node" "^0.16.6" + "@humanwhocodes/module-importer" "^1.0.1" + "@humanwhocodes/retry" "^0.4.2" + "@types/estree" "^1.0.6" + ajv "^6.12.4" + chalk "^4.0.0" + cross-spawn "^7.0.6" + debug "^4.3.2" + escape-string-regexp "^4.0.0" + eslint-scope "^8.4.0" + eslint-visitor-keys "^4.2.1" + espree "^10.4.0" + esquery "^1.5.0" + esutils "^2.0.2" + fast-deep-equal "^3.1.3" + file-entry-cache "^8.0.0" + find-up "^5.0.0" + glob-parent "^6.0.2" + ignore "^5.2.0" + imurmurhash "^0.1.4" + is-glob "^4.0.0" + json-stable-stringify-without-jsonify "^1.0.1" + lodash.merge "^4.6.2" + minimatch "^3.1.2" + natural-compare "^1.4.0" + optionator "^0.9.3" + +espree@^10.0.1, espree@^10.4.0: + version "10.4.0" + resolved "https://registry.yarnpkg.com/espree/-/espree-10.4.0.tgz#d54f4949d4629005a1fa168d937c3ff1f7e2a837" + integrity sha512-j6PAQ2uUr79PZhBjP5C5fhl8e39FmRnOjsD5lGnWrFU8i2G776tBK7+nP8KuQUTTyAZUwfQqXAgrVH5MbH9CYQ== + dependencies: + acorn "^8.15.0" + acorn-jsx "^5.3.2" + eslint-visitor-keys "^4.2.1" + +esquery@^1.5.0: + version "1.7.0" + resolved "https://registry.yarnpkg.com/esquery/-/esquery-1.7.0.tgz#08d048f261f0ddedb5bae95f46809463d9c9496d" + integrity sha512-Ap6G0WQwcU/LHsvLwON1fAQX9Zp0A2Y6Y/cJBl9r/JbW90Zyg4/zbG6zzKa2OTALELarYHmKu0GhpM5EO+7T0g== + dependencies: + estraverse "^5.1.0" + +esrecurse@^4.3.0: + version "4.3.0" + resolved "https://registry.yarnpkg.com/esrecurse/-/esrecurse-4.3.0.tgz#7ad7964d679abb28bee72cec63758b1c5d2c9921" + integrity sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag== + dependencies: + estraverse "^5.2.0" + +estraverse@^5.1.0, estraverse@^5.2.0, estraverse@^5.3.0: + version "5.3.0" + resolved "https://registry.yarnpkg.com/estraverse/-/estraverse-5.3.0.tgz#2eea5290702f26ab8fe5370370ff86c965d21123" + integrity sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA== + +estree-walker@^3.0.3: + version "3.0.3" + resolved "https://registry.yarnpkg.com/estree-walker/-/estree-walker-3.0.3.tgz#67c3e549ec402a487b4fc193d1953a524752340d" + integrity sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g== + dependencies: + "@types/estree" "^1.0.0" + +esutils@^2.0.2: + version "2.0.3" + resolved "https://registry.yarnpkg.com/esutils/-/esutils-2.0.3.tgz#74d2eb4de0b8da1293711910d50775b9b710ef64" + integrity sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g== + +execa@^5.0.0: + version "5.1.1" + resolved "https://registry.yarnpkg.com/execa/-/execa-5.1.1.tgz#f80ad9cbf4298f7bd1d4c9555c21e93741c411dd" + integrity sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg== + dependencies: + cross-spawn "^7.0.3" + get-stream "^6.0.0" + human-signals "^2.1.0" + is-stream "^2.0.0" + merge-stream "^2.0.0" + npm-run-path "^4.0.1" + onetime "^5.1.2" + signal-exit "^3.0.3" + strip-final-newline "^2.0.0" + +expect-type@^1.1.0: + version "1.3.0" + resolved "https://registry.yarnpkg.com/expect-type/-/expect-type-1.3.0.tgz#0d58ed361877a31bbc4dd6cf71bbfef7faf6bd68" + integrity sha512-knvyeauYhqjOYvQ66MznSMs83wmHrCycNEN6Ao+2AeYEfxUIkuiVxdEa1qlGEPK+We3n0THiDciYSsCcgW/DoA== + +fast-deep-equal@^3.1.1, fast-deep-equal@^3.1.3: + version "3.1.3" + resolved "https://registry.yarnpkg.com/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz#3a7d56b559d6cbc3eb512325244e619a65c6c525" + integrity sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q== + +fast-json-stable-stringify@^2.0.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz#874bf69c6f404c2b5d99c481341399fd55892633" + integrity sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw== + +fast-levenshtein@^2.0.6: + version "2.0.6" + resolved "https://registry.yarnpkg.com/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz#3d8a5c66883a16a30ca8643e851f19baa7797917" + integrity sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw== + +fdir@^6.5.0: + version "6.5.0" + resolved "https://registry.yarnpkg.com/fdir/-/fdir-6.5.0.tgz#ed2ab967a331ade62f18d077dae192684d50d350" + integrity sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg== + +file-entry-cache@^8.0.0: + version "8.0.0" + resolved "https://registry.yarnpkg.com/file-entry-cache/-/file-entry-cache-8.0.0.tgz#7787bddcf1131bffb92636c69457bbc0edd6d81f" + integrity sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ== + dependencies: + flat-cache "^4.0.0" + +find-root@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/find-root/-/find-root-1.1.0.tgz#abcfc8ba76f708c42a97b3d685b7e9450bfb9ce4" + integrity sha512-NKfW6bec6GfKc0SGx1e07QZY9PE99u0Bft/0rzSD5k3sO/vwkVUpDUKVm5Gpp5Ue3YfShPFTX2070tDs5kB9Ng== + +find-up@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/find-up/-/find-up-5.0.0.tgz#4c92819ecb7083561e4f4a240a86be5198f536fc" + integrity sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng== + dependencies: + locate-path "^6.0.0" + path-exists "^4.0.0" + +flat-cache@^4.0.0: + version "4.0.1" + resolved "https://registry.yarnpkg.com/flat-cache/-/flat-cache-4.0.1.tgz#0ece39fcb14ee012f4b0410bd33dd9c1f011127c" + integrity sha512-f7ccFPK3SXFHpx15UIGyRJ/FJQctuKZ0zVuN3frBo4HnK3cay9VEW0R6yPYFHC0AgqhukPzKjq22t5DmAyqGyw== + dependencies: + flatted "^3.2.9" + keyv "^4.5.4" + +flatted@^3.2.9: + version "3.3.3" + resolved "https://registry.yarnpkg.com/flatted/-/flatted-3.3.3.tgz#67c8fad95454a7c7abebf74bb78ee74a44023358" + integrity sha512-GX+ysw4PBCz0PzosHDepZGANEuFCMLrnRTiEy9McGjmkCQYwRq4A/X786G/fjM/+OjsWSU1ZrY5qyARZmO/uwg== + +for-each@^0.3.3, for-each@^0.3.5: + version "0.3.5" + resolved "https://registry.yarnpkg.com/for-each/-/for-each-0.3.5.tgz#d650688027826920feeb0af747ee7b9421a41d47" + integrity sha512-dKx12eRCVIzqCxFGplyFKJMPvLEWgmNtUrpTiJIR5u97zEhRG8ySrtboPHZXx7daLxQVrl643cTzbab2tkQjxg== + dependencies: + is-callable "^1.2.7" + +fsevents@~2.3.2, fsevents@~2.3.3: + version "2.3.3" + resolved "https://registry.yarnpkg.com/fsevents/-/fsevents-2.3.3.tgz#cac6407785d03675a2a5e1a5305c697b347d90d6" + integrity sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw== + +function-bind@^1.1.2: + version "1.1.2" + resolved "https://registry.yarnpkg.com/function-bind/-/function-bind-1.1.2.tgz#2c02d864d97f3ea6c8830c464cbd11ab6eab7a1c" + integrity sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA== + +function.prototype.name@^1.1.6, function.prototype.name@^1.1.8: + version "1.1.8" + resolved "https://registry.yarnpkg.com/function.prototype.name/-/function.prototype.name-1.1.8.tgz#e68e1df7b259a5c949eeef95cdbde53edffabb78" + integrity sha512-e5iwyodOHhbMr/yNrc7fDYG4qlbIvI5gajyzPnb5TCwyhjApznQh1BMFou9b30SevY43gCJKXycoCBjMbsuW0Q== + dependencies: + call-bind "^1.0.8" + call-bound "^1.0.3" + define-properties "^1.2.1" + functions-have-names "^1.2.3" + hasown "^2.0.2" + is-callable "^1.2.7" + +functions-have-names@^1.2.3: + version "1.2.3" + resolved "https://registry.yarnpkg.com/functions-have-names/-/functions-have-names-1.2.3.tgz#0404fe4ee2ba2f607f0e0ec3c80bae994133b834" + integrity sha512-xckBUXyTIqT97tq2x2AMb+g163b5JFysYk0x4qxNFwbfQkmNZoiRHb6sPzI9/QV33WeuvVYBUIiD4NzNIyqaRQ== + +generator-function@^2.0.0: + version "2.0.1" + resolved "https://registry.yarnpkg.com/generator-function/-/generator-function-2.0.1.tgz#0e75dd410d1243687a0ba2e951b94eedb8f737a2" + integrity sha512-SFdFmIJi+ybC0vjlHN0ZGVGHc3lgE0DxPAT0djjVg+kjOnSqclqmj0KQ7ykTOLP6YxoqOvuAODGdcHJn+43q3g== + +gensync@^1.0.0-beta.2: + version "1.0.0-beta.2" + resolved "https://registry.yarnpkg.com/gensync/-/gensync-1.0.0-beta.2.tgz#32a6ee76c3d7f52d46b2b1ae5d93fea8580a25e0" + integrity sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg== + +get-intrinsic@^1.2.4, get-intrinsic@^1.2.5, get-intrinsic@^1.2.6, get-intrinsic@^1.2.7, get-intrinsic@^1.3.0: + version "1.3.0" + resolved "https://registry.yarnpkg.com/get-intrinsic/-/get-intrinsic-1.3.0.tgz#743f0e3b6964a93a5491ed1bffaae054d7f98d01" + integrity sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ== + dependencies: + call-bind-apply-helpers "^1.0.2" + es-define-property "^1.0.1" + es-errors "^1.3.0" + es-object-atoms "^1.1.1" + function-bind "^1.1.2" + get-proto "^1.0.1" + gopd "^1.2.0" + has-symbols "^1.1.0" + hasown "^2.0.2" + math-intrinsics "^1.1.0" + +get-params@^0.1.2: + version "0.1.2" + resolved "https://registry.yarnpkg.com/get-params/-/get-params-0.1.2.tgz#bae0dfaba588a0c60d7834c0d8dc2ff60eeef2fe" + integrity sha512-41eOxtlGgHQRbFyA8KTH+w+32Em3cRdfBud7j67ulzmIfmaHX9doq47s0fa4P5o9H64BZX9nrYI6sJvk46Op+Q== + +get-proto@^1.0.0, get-proto@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/get-proto/-/get-proto-1.0.1.tgz#150b3f2743869ef3e851ec0c49d15b1d14d00ee1" + integrity sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g== + dependencies: + dunder-proto "^1.0.1" + es-object-atoms "^1.0.0" + +get-stream@^6.0.0: + version "6.0.1" + resolved "https://registry.yarnpkg.com/get-stream/-/get-stream-6.0.1.tgz#a262d8eef67aced57c2852ad6167526a43cbf7b7" + integrity sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg== + +get-symbol-description@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/get-symbol-description/-/get-symbol-description-1.1.0.tgz#7bdd54e0befe8ffc9f3b4e203220d9f1e881b6ee" + integrity sha512-w9UMqWwJxHNOvoNzSJ2oPF5wvYcvP7jUvYzhp67yEhTi17ZDBBC1z9pTdGuzjD+EFIqLSYRweZjqfiPzQ06Ebg== + dependencies: + call-bound "^1.0.3" + es-errors "^1.3.0" + get-intrinsic "^1.2.6" + +glob-parent@^6.0.2: + version "6.0.2" + resolved "https://registry.yarnpkg.com/glob-parent/-/glob-parent-6.0.2.tgz#6d237d99083950c79290f24c7642a3de9a28f9e3" + integrity sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A== + dependencies: + is-glob "^4.0.3" + +globals@^14.0.0: + version "14.0.0" + resolved "https://registry.yarnpkg.com/globals/-/globals-14.0.0.tgz#898d7413c29babcf6bafe56fcadded858ada724e" + integrity sha512-oahGvuMGQlPw/ivIYBjVSrWAfWLBeku5tpPE2fOPLi+WHffIWbuh2tCjhyQhTBPMf5E9jDEH4FOmTYgYwbKwtQ== + +globals@^15.9.0: + version "15.15.0" + resolved "https://registry.yarnpkg.com/globals/-/globals-15.15.0.tgz#7c4761299d41c32b075715a4ce1ede7897ff72a8" + integrity sha512-7ACyT3wmyp3I61S4fG682L0VA2RGD9otkqGJIwNUMF1SWUombIIk+af1unuDYgMm082aHYwD+mzJvv9Iu8dsgg== + +globalthis@^1.0.4: + version "1.0.4" + resolved "https://registry.yarnpkg.com/globalthis/-/globalthis-1.0.4.tgz#7430ed3a975d97bfb59bcce41f5cabbafa651236" + integrity sha512-DpLKbNU4WylpxJykQujfCcwYWiV/Jhm50Goo0wrVILAv5jOr9d+H+UR3PhSCD2rCCEIg0uc+G+muBTwD54JhDQ== + dependencies: + define-properties "^1.2.1" + gopd "^1.0.1" + +globrex@^0.1.2: + version "0.1.2" + resolved "https://registry.yarnpkg.com/globrex/-/globrex-0.1.2.tgz#dd5d9ec826232730cd6793a5e33a9302985e6098" + integrity sha512-uHJgbwAMwNFf5mLst7IWLNg14x1CkeqglJb/K3doi4dw6q2IvAAmM/Y81kevy83wP+Sst+nutFTYOGg3d1lsxg== + +goober@^2.0.33: + version "2.1.18" + resolved "https://registry.yarnpkg.com/goober/-/goober-2.1.18.tgz#b72d669bd24d552d441638eee26dfd5716ea6442" + integrity sha512-2vFqsaDVIT9Gz7N6kAL++pLpp41l3PfDuusHcjnGLfR6+huZkl6ziX+zgVC3ZxpqWhzH6pyDdGrCeDhMIvwaxw== + +gopd@^1.0.1, gopd@^1.2.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/gopd/-/gopd-1.2.0.tgz#89f56b8217bdbc8802bd299df6d7f1081d7e51a1" + integrity sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg== + +has-bigints@^1.0.2: + version "1.1.0" + resolved "https://registry.yarnpkg.com/has-bigints/-/has-bigints-1.1.0.tgz#28607e965ac967e03cd2a2c70a2636a1edad49fe" + integrity sha512-R3pbpkcIqv2Pm3dUwgjclDRVmWpTJW2DcMzcIhEXEx1oh/CEMObMm3KLmRJOdvhM7o4uQBnwr8pzRK2sJWIqfg== + +has-flag@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-4.0.0.tgz#944771fd9c81c81265c4d6941860da06bb59479b" + integrity sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ== + +has-property-descriptors@^1.0.0, has-property-descriptors@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/has-property-descriptors/-/has-property-descriptors-1.0.2.tgz#963ed7d071dc7bf5f084c5bfbe0d1b6222586854" + integrity sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg== + dependencies: + es-define-property "^1.0.0" + +has-proto@^1.2.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/has-proto/-/has-proto-1.2.0.tgz#5de5a6eabd95fdffd9818b43055e8065e39fe9d5" + integrity sha512-KIL7eQPfHQRC8+XluaIw7BHUwwqL19bQn4hzNgdr+1wXoU0KKj6rufu47lhY7KbJR2C6T6+PfyN0Ea7wkSS+qQ== + dependencies: + dunder-proto "^1.0.0" + +has-symbols@^1.0.3, has-symbols@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/has-symbols/-/has-symbols-1.1.0.tgz#fc9c6a783a084951d0b971fe1018de813707a338" + integrity sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ== + +has-tostringtag@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/has-tostringtag/-/has-tostringtag-1.0.2.tgz#2cdc42d40bef2e5b4eeab7c01a73c54ce7ab5abc" + integrity sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw== + dependencies: + has-symbols "^1.0.3" + +hasown@^2.0.2: + version "2.0.2" + resolved "https://registry.yarnpkg.com/hasown/-/hasown-2.0.2.tgz#003eaf91be7adc372e84ec59dc37252cedb80003" + integrity sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ== + dependencies: + function-bind "^1.1.2" + +hermes-estree@0.25.1: + version "0.25.1" + resolved "https://registry.yarnpkg.com/hermes-estree/-/hermes-estree-0.25.1.tgz#6aeec17d1983b4eabf69721f3aa3eb705b17f480" + integrity sha512-0wUoCcLp+5Ev5pDW2OriHC2MJCbwLwuRx+gAqMTOkGKJJiBCLjtrvy4PWUGn6MIVefecRpzoOZ/UV6iGdOr+Cw== + +hermes-parser@^0.25.1: + version "0.25.1" + resolved "https://registry.yarnpkg.com/hermes-parser/-/hermes-parser-0.25.1.tgz#5be0e487b2090886c62bd8a11724cd766d5f54d1" + integrity sha512-6pEjquH3rqaI6cYAXYPcz9MS4rY6R4ngRgrgfDshRptUZIc3lw0MCIJIGDj9++mfySOuPTHB4nrSW99BCvOPIA== + dependencies: + hermes-estree "0.25.1" + +hoist-non-react-statics@^3.3.0, hoist-non-react-statics@^3.3.1: + version "3.3.2" + resolved "https://registry.yarnpkg.com/hoist-non-react-statics/-/hoist-non-react-statics-3.3.2.tgz#ece0acaf71d62c2969c2ec59feff42a4b1a85b45" + integrity sha512-/gGivxi8JPKWNm/W0jSmzcMPpfpPLc3dY/6GxhX2hQ9iGj3aDfklV4ET7NjKpSinLpJ5vafa9iiGIEZg10SfBw== + dependencies: + react-is "^16.7.0" + +human-signals@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/human-signals/-/human-signals-2.1.0.tgz#dc91fcba42e4d06e4abaed33b3e7a3c02f514ea0" + integrity sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw== + +humanize-duration@^3.32.1: + version "3.33.2" + resolved "https://registry.yarnpkg.com/humanize-duration/-/humanize-duration-3.33.2.tgz#2e41986eabb00cb5ad0eef616a78233099dbdac4" + integrity sha512-K7Ny/ULO1hDm2nnhvAY+SJV1skxFb61fd073SG1IWJl+D44ULrruCuTyjHKjBVVcSuTlnY99DKtgEG39CM5QOQ== + +ieee754@^1.1.13: + version "1.2.1" + resolved "https://registry.yarnpkg.com/ieee754/-/ieee754-1.2.1.tgz#8eb7a10a63fff25d15a57b001586d177d1b0d352" + integrity sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA== + +ignore@^5.2.0: + version "5.3.2" + resolved "https://registry.yarnpkg.com/ignore/-/ignore-5.3.2.tgz#3cd40e729f3643fd87cb04e50bf0eb722bc596f5" + integrity sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g== + +ignore@^7.0.5: + version "7.0.5" + resolved "https://registry.yarnpkg.com/ignore/-/ignore-7.0.5.tgz#4cb5f6cd7d4c7ab0365738c7aea888baa6d7efd9" + integrity sha512-Hs59xBNfUIunMFgWAbGX5cq6893IbWg4KnrjbYwX3tx0ztorVgTDA6B2sxf8ejHJ4wz8BqGUMYlnzNBer5NvGg== + +immer@^11.0.0: + version "11.1.3" + resolved "https://registry.yarnpkg.com/immer/-/immer-11.1.3.tgz#78681e1deb6cec39753acf04eb16d7576c04f4d6" + integrity sha512-6jQTc5z0KJFtr1UgFpIL3N9XSC3saRaI9PwWtzM2pSqkNGtiNkYY2OSwkOGDK2XcTRcLb1pi/aNkKZz0nxVH4Q== + +immutable@^4.3.7: + version "4.3.7" + resolved "https://registry.yarnpkg.com/immutable/-/immutable-4.3.7.tgz#c70145fc90d89fb02021e65c84eb0226e4e5a381" + integrity sha512-1hqclzwYwjRDFLjcFxOM5AYkkG0rpFPpr1RLPMEuGczoS7YA8gLhy8SWXYRAA/XwfEHpfo3cw5JGioS32fnMRw== + +import-fresh@^3.2.1: + version "3.3.1" + resolved "https://registry.yarnpkg.com/import-fresh/-/import-fresh-3.3.1.tgz#9cecb56503c0ada1f2741dbbd6546e4b13b57ccf" + integrity sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ== + dependencies: + parent-module "^1.0.0" + resolve-from "^4.0.0" + +imurmurhash@^0.1.4: + version "0.1.4" + resolved "https://registry.yarnpkg.com/imurmurhash/-/imurmurhash-0.1.4.tgz#9218b9b2b928a238b13dc4fb6b6d576f231453ea" + integrity sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA== + +inherits@~2.0.3: + version "2.0.4" + resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.4.tgz#0fa2c64f932917c3433a0ded55363aae37416b7c" + integrity sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ== + +internal-ip@^7.0.0: + version "7.0.0" + resolved "https://registry.yarnpkg.com/internal-ip/-/internal-ip-7.0.0.tgz#5b1c6a9d7e188aa73a1b69717daf50c8d8ed774f" + integrity sha512-qE4TeD4brqC45Vq/+VASeMiS1KRyfBkR6HT2sh9pZVVCzSjPkaCEfKFU+dL0PRv7NHJtvoKN2r82G6wTfzorkw== + dependencies: + default-gateway "^6.0.3" + ipaddr.js "^2.0.1" + is-ip "^3.1.0" + p-event "^4.2.0" + +internal-slot@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/internal-slot/-/internal-slot-1.1.0.tgz#1eac91762947d2f7056bc838d93e13b2e9604961" + integrity sha512-4gd7VpWNQNB4UKKCFFVcp1AVv+FMOgs9NKzjHKusc8jTMhd5eL1NqQqOpE0KzMds804/yHlglp3uxgluOqAPLw== + dependencies: + es-errors "^1.3.0" + hasown "^2.0.2" + side-channel "^1.1.0" + +ip-regex@^4.0.0: + version "4.3.0" + resolved "https://registry.yarnpkg.com/ip-regex/-/ip-regex-4.3.0.tgz#687275ab0f57fa76978ff8f4dddc8a23d5990db5" + integrity sha512-B9ZWJxHHOHUhUjCPrMpLD4xEq35bUTClHM1S6CBU5ixQnkZmwipwgc96vAd7AAGM9TGHvJR+Uss+/Ak6UphK+Q== + +ipaddr.js@^2.0.1: + version "2.3.0" + resolved "https://registry.yarnpkg.com/ipaddr.js/-/ipaddr.js-2.3.0.tgz#71dce70e1398122208996d1c22f2ba46a24b1abc" + integrity sha512-Zv/pA+ciVFbCSBBjGfaKUya/CcGmUHzTydLMaTwrUUEM2DIEO3iZvueGxmacvmN50fGpGVKeTXpb2LcYQxeVdg== + +is-array-buffer@^3.0.4, is-array-buffer@^3.0.5: + version "3.0.5" + resolved "https://registry.yarnpkg.com/is-array-buffer/-/is-array-buffer-3.0.5.tgz#65742e1e687bd2cc666253068fd8707fe4d44280" + integrity sha512-DDfANUiiG2wC1qawP66qlTugJeL5HyzMpfr8lLK+jMQirGzNod0B12cFB/9q838Ru27sBwfw78/rdoU7RERz6A== + dependencies: + call-bind "^1.0.8" + call-bound "^1.0.3" + get-intrinsic "^1.2.6" + +is-arrayish@^0.2.1: + version "0.2.1" + resolved "https://registry.yarnpkg.com/is-arrayish/-/is-arrayish-0.2.1.tgz#77c99840527aa8ecb1a8ba697b80645a7a926a9d" + integrity sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg== + +is-async-function@^2.0.0: + version "2.1.1" + resolved "https://registry.yarnpkg.com/is-async-function/-/is-async-function-2.1.1.tgz#3e69018c8e04e73b738793d020bfe884b9fd3523" + integrity sha512-9dgM/cZBnNvjzaMYHVoxxfPj2QXt22Ev7SuuPrs+xav0ukGB0S6d4ydZdEiM48kLx5kDV+QBPrpVnFyefL8kkQ== + dependencies: + async-function "^1.0.0" + call-bound "^1.0.3" + get-proto "^1.0.1" + has-tostringtag "^1.0.2" + safe-regex-test "^1.1.0" + +is-bigint@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/is-bigint/-/is-bigint-1.1.0.tgz#dda7a3445df57a42583db4228682eba7c4170672" + integrity sha512-n4ZT37wG78iz03xPRKJrHTdZbe3IicyucEtdRsV5yglwc3GyUfbAfpSeD0FJ41NbUNSt5wbhqfp1fS+BgnvDFQ== + dependencies: + has-bigints "^1.0.2" + +is-boolean-object@^1.2.1: + version "1.2.2" + resolved "https://registry.yarnpkg.com/is-boolean-object/-/is-boolean-object-1.2.2.tgz#7067f47709809a393c71ff5bb3e135d8a9215d9e" + integrity sha512-wa56o2/ElJMYqjCjGkXri7it5FbebW5usLw/nPmCMs5DeZ7eziSYZhSmPRn0txqeW4LnAmQQU7FgqLpsEFKM4A== + dependencies: + call-bound "^1.0.3" + has-tostringtag "^1.0.2" + +is-callable@^1.2.7: + version "1.2.7" + resolved "https://registry.yarnpkg.com/is-callable/-/is-callable-1.2.7.tgz#3bc2a85ea742d9e36205dcacdd72ca1fdc51b055" + integrity sha512-1BC0BVFhS/p0qtw6enp8e+8OD0UrK0oFLztSjNzhcKA3WDuJxxAPXzPuPtKkjEY9UUoEWlX/8fgKeu2S8i9JTA== + +is-core-module@^2.13.0, is-core-module@^2.16.1: + version "2.16.1" + resolved "https://registry.yarnpkg.com/is-core-module/-/is-core-module-2.16.1.tgz#2a98801a849f43e2add644fbb6bc6229b19a4ef4" + integrity sha512-UfoeMA6fIJ8wTYFEUjelnaGI67v6+N7qXJEvQuIGa99l4xsCruSYOVSQ0uPANn4dAzm8lkYPaKLrrijLq7x23w== + dependencies: + hasown "^2.0.2" + +is-data-view@^1.0.1, is-data-view@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/is-data-view/-/is-data-view-1.0.2.tgz#bae0a41b9688986c2188dda6657e56b8f9e63b8e" + integrity sha512-RKtWF8pGmS87i2D6gqQu/l7EYRlVdfzemCJN/P3UOs//x1QE7mfhvzHIApBTRf7axvT6DMGwSwBXYCT0nfB9xw== + dependencies: + call-bound "^1.0.2" + get-intrinsic "^1.2.6" + is-typed-array "^1.1.13" + +is-date-object@^1.0.5, is-date-object@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/is-date-object/-/is-date-object-1.1.0.tgz#ad85541996fc7aa8b2729701d27b7319f95d82f7" + integrity sha512-PwwhEakHVKTdRNVOw+/Gyh0+MzlCl4R6qKvkhuvLtPMggI1WAHt9sOwZxQLSGpUaDnrdyDsomoRgNnCfKNSXXg== + dependencies: + call-bound "^1.0.2" + has-tostringtag "^1.0.2" + +is-extglob@^2.1.1: + version "2.1.1" + resolved "https://registry.yarnpkg.com/is-extglob/-/is-extglob-2.1.1.tgz#a88c02535791f02ed37c76a1b9ea9773c833f8c2" + integrity sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ== + +is-finalizationregistry@^1.1.0: + version "1.1.1" + resolved "https://registry.yarnpkg.com/is-finalizationregistry/-/is-finalizationregistry-1.1.1.tgz#eefdcdc6c94ddd0674d9c85887bf93f944a97c90" + integrity sha512-1pC6N8qWJbWoPtEjgcL2xyhQOP491EQjeUo3qTKcmV8YSDDJrOepfG8pcC7h/QgnQHYSv0mJ3Z/ZWxmatVrysg== + dependencies: + call-bound "^1.0.3" + +is-generator-function@^1.0.10: + version "1.1.2" + resolved "https://registry.yarnpkg.com/is-generator-function/-/is-generator-function-1.1.2.tgz#ae3b61e3d5ea4e4839b90bad22b02335051a17d5" + integrity sha512-upqt1SkGkODW9tsGNG5mtXTXtECizwtS2kA161M+gJPc1xdb/Ax629af6YrTwcOeQHbewrPNlE5Dx7kzvXTizA== + dependencies: + call-bound "^1.0.4" + generator-function "^2.0.0" + get-proto "^1.0.1" + has-tostringtag "^1.0.2" + safe-regex-test "^1.1.0" + +is-glob@^4.0.0, is-glob@^4.0.3: + version "4.0.3" + resolved "https://registry.yarnpkg.com/is-glob/-/is-glob-4.0.3.tgz#64f61e42cbbb2eec2071a9dac0b28ba1e65d5084" + integrity sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg== + dependencies: + is-extglob "^2.1.1" + +is-ip@^3.1.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/is-ip/-/is-ip-3.1.0.tgz#2ae5ddfafaf05cb8008a62093cf29734f657c5d8" + integrity sha512-35vd5necO7IitFPjd/YBeqwWnyDWbuLH9ZXQdMfDA8TEo7pv5X8yfrvVO3xbJbLUlERCMvf6X0hTUamQxCYJ9Q== + dependencies: + ip-regex "^4.0.0" + +is-map@^2.0.3: + version "2.0.3" + resolved "https://registry.yarnpkg.com/is-map/-/is-map-2.0.3.tgz#ede96b7fe1e270b3c4465e3a465658764926d62e" + integrity sha512-1Qed0/Hr2m+YqxnM09CjA2d/i6YZNfF6R2oRAOj36eUdS6qIV/huPJNSEpKbupewFs+ZsJlxsjjPbc0/afW6Lw== + +is-negative-zero@^2.0.3: + version "2.0.3" + resolved "https://registry.yarnpkg.com/is-negative-zero/-/is-negative-zero-2.0.3.tgz#ced903a027aca6381b777a5743069d7376a49747" + integrity sha512-5KoIu2Ngpyek75jXodFvnafB6DJgr3u8uuK0LEZJjrU19DrMD3EVERaR8sjz8CCGgpZvxPl9SuE1GMVPFHx1mw== + +is-number-object@^1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/is-number-object/-/is-number-object-1.1.1.tgz#144b21e95a1bc148205dcc2814a9134ec41b2541" + integrity sha512-lZhclumE1G6VYD8VHe35wFaIif+CTy5SJIi5+3y4psDgWu4wPDoBhF8NxUOinEc7pHgiTsT6MaBb92rKhhD+Xw== + dependencies: + call-bound "^1.0.3" + has-tostringtag "^1.0.2" + +is-plain-object@^2.0.4: + version "2.0.4" + resolved "https://registry.yarnpkg.com/is-plain-object/-/is-plain-object-2.0.4.tgz#2c163b3fafb1b606d9d17928f05c2a1c38e07677" + integrity sha512-h5PpgXkWitc38BBMYawTYMWJHFZJVnBquFE57xFpjB8pJFiF6gZ+bU+WyI/yqXiFR5mdLsgYNaPe8uao6Uv9Og== + dependencies: + isobject "^3.0.1" + +is-regex@^1.2.1: + version "1.2.1" + resolved "https://registry.yarnpkg.com/is-regex/-/is-regex-1.2.1.tgz#76d70a3ed10ef9be48eb577887d74205bf0cad22" + integrity sha512-MjYsKHO5O7mCsmRGxWcLWheFqN9DJ/2TmngvjKXihe6efViPqc274+Fx/4fYj/r03+ESvBdTXK0V6tA3rgez1g== + dependencies: + call-bound "^1.0.2" + gopd "^1.2.0" + has-tostringtag "^1.0.2" + hasown "^2.0.2" + +is-set@^2.0.3: + version "2.0.3" + resolved "https://registry.yarnpkg.com/is-set/-/is-set-2.0.3.tgz#8ab209ea424608141372ded6e0cb200ef1d9d01d" + integrity sha512-iPAjerrse27/ygGLxw+EBR9agv9Y6uLeYVJMu+QNCoouJ1/1ri0mGrcWpfCqFZuzzx3WjtwxG098X+n4OuRkPg== + +is-shared-array-buffer@^1.0.4: + version "1.0.4" + resolved "https://registry.yarnpkg.com/is-shared-array-buffer/-/is-shared-array-buffer-1.0.4.tgz#9b67844bd9b7f246ba0708c3a93e34269c774f6f" + integrity sha512-ISWac8drv4ZGfwKl5slpHG9OwPNty4jOWPRIhBpxOoD+hqITiwuipOQ2bNthAzwA3B4fIjO4Nln74N0S9byq8A== + dependencies: + call-bound "^1.0.3" + +is-stream@^2.0.0: + version "2.0.1" + resolved "https://registry.yarnpkg.com/is-stream/-/is-stream-2.0.1.tgz#fac1e3d53b97ad5a9d0ae9cef2389f5810a5c077" + integrity sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg== + +is-string@^1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/is-string/-/is-string-1.1.1.tgz#92ea3f3d5c5b6e039ca8677e5ac8d07ea773cbb9" + integrity sha512-BtEeSsoaQjlSPBemMQIrY1MY0uM6vnS1g5fmufYOtnxLGUZM2178PKbhsk7Ffv58IX+ZtcvoGwccYsh0PglkAA== + dependencies: + call-bound "^1.0.3" + has-tostringtag "^1.0.2" + +is-symbol@^1.0.4, is-symbol@^1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/is-symbol/-/is-symbol-1.1.1.tgz#f47761279f532e2b05a7024a7506dbbedacd0634" + integrity sha512-9gGx6GTtCQM73BgmHQXfDmLtfjjTUDSyoxTCbp5WtoixAhfgsDirWIcVQ/IHpvI5Vgd5i/J5F7B9cN/WlVbC/w== + dependencies: + call-bound "^1.0.2" + has-symbols "^1.1.0" + safe-regex-test "^1.1.0" + +is-typed-array@^1.1.13, is-typed-array@^1.1.14, is-typed-array@^1.1.15: + version "1.1.15" + resolved "https://registry.yarnpkg.com/is-typed-array/-/is-typed-array-1.1.15.tgz#4bfb4a45b61cee83a5a46fba778e4e8d59c0ce0b" + integrity sha512-p3EcsicXjit7SaskXHs1hA91QxgTw46Fv6EFKKGS5DRFLD8yKnohjF3hxoju94b/OcMZoQukzpPpBE9uLVKzgQ== + dependencies: + which-typed-array "^1.1.16" + +is-weakmap@^2.0.2: + version "2.0.2" + resolved "https://registry.yarnpkg.com/is-weakmap/-/is-weakmap-2.0.2.tgz#bf72615d649dfe5f699079c54b83e47d1ae19cfd" + integrity sha512-K5pXYOm9wqY1RgjpL3YTkF39tni1XajUIkawTLUo9EZEVUFga5gSQJF8nNS7ZwJQ02y+1YCNYcMh+HIf1ZqE+w== + +is-weakref@^1.0.2, is-weakref@^1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/is-weakref/-/is-weakref-1.1.1.tgz#eea430182be8d64174bd96bffbc46f21bf3f9293" + integrity sha512-6i9mGWSlqzNMEqpCp93KwRS1uUOodk2OJ6b+sq7ZPDSy2WuI5NFIxp/254TytR8ftefexkWn5xNiHUNpPOfSew== + dependencies: + call-bound "^1.0.3" + +is-weakset@^2.0.3: + version "2.0.4" + resolved "https://registry.yarnpkg.com/is-weakset/-/is-weakset-2.0.4.tgz#c9f5deb0bc1906c6d6f1027f284ddf459249daca" + integrity sha512-mfcwb6IzQyOKTs84CQMrOwW4gQcaTOAWJ0zzJCl2WSPDrWk/OzDaImWFH3djXhb24g4eudZfLRozAvPGw4d9hQ== + dependencies: + call-bound "^1.0.3" + get-intrinsic "^1.2.6" + +isarray@^2.0.5: + version "2.0.5" + resolved "https://registry.yarnpkg.com/isarray/-/isarray-2.0.5.tgz#8af1e4c1221244cc62459faf38940d4e644a5723" + integrity sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw== + +isarray@~1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/isarray/-/isarray-1.0.0.tgz#bb935d48582cba168c06834957a54a3e07124f11" + integrity sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ== + +isexe@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/isexe/-/isexe-2.0.0.tgz#e8fbf374dc556ff8947a10dcb0572d633f2cfa10" + integrity sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw== + +isobject@^3.0.1: + version "3.0.1" + resolved "https://registry.yarnpkg.com/isobject/-/isobject-3.0.1.tgz#4e431e92b11a9731636aa1f9c8d1ccbcfdab78df" + integrity sha512-WhB9zCku7EGTj/HQQRz5aUQEUeoQZH2bWcltRErOpymJ4boYE6wL9Tbr23krRPSZ+C5zqNSrSw+Cc7sZZ4b7vg== + +iterator.prototype@^1.1.5: + version "1.1.5" + resolved "https://registry.yarnpkg.com/iterator.prototype/-/iterator.prototype-1.1.5.tgz#12c959a29de32de0aa3bbbb801f4d777066dae39" + integrity sha512-H0dkQoCa3b2VEeKQBOxFph+JAbcrQdE7KC0UkqwpLmv2EC4P41QXP+rqo9wYodACiG5/WM5s9oDApTU8utwj9g== + dependencies: + define-data-property "^1.1.4" + es-object-atoms "^1.0.0" + get-intrinsic "^1.2.6" + get-proto "^1.0.0" + has-symbols "^1.1.0" + set-function-name "^2.0.2" + +jdenticon@^3.3.0: + version "3.3.0" + resolved "https://registry.yarnpkg.com/jdenticon/-/jdenticon-3.3.0.tgz#64bae9f9b3cf5c2a210e183648117afe3a89b367" + integrity sha512-DhuBRNRIybGPeAjMjdHbkIfiwZCCmf8ggu7C49jhp6aJ7DYsZfudnvnTY5/1vgUhrGA7JaDAx1WevnpjCPvaGg== + dependencies: + canvas-renderer "~2.2.0" + +"js-tokens@^3.0.0 || ^4.0.0", js-tokens@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/js-tokens/-/js-tokens-4.0.0.tgz#19203fb59991df98e3a287050d4647cdeaf32499" + integrity sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ== + +js-yaml@^4.1.1: + version "4.1.1" + resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-4.1.1.tgz#854c292467705b699476e1a2decc0c8a3458806b" + integrity sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA== + dependencies: + argparse "^2.0.1" + +jsan@^3.1.14: + version "3.1.14" + resolved "https://registry.yarnpkg.com/jsan/-/jsan-3.1.14.tgz#197fee2d260b85acacb049c1ffa41bd09fb1f213" + integrity sha512-wStfgOJqMv4QKktuH273f5fyi3D3vy2pHOiSDGPvpcS/q+wb/M7AK3vkCcaHbkZxDOlDU/lDJgccygKSG2OhtA== + +jsesc@^3.0.2: + version "3.1.0" + resolved "https://registry.yarnpkg.com/jsesc/-/jsesc-3.1.0.tgz#74d335a234f67ed19907fdadfac7ccf9d409825d" + integrity sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA== + +json-buffer@3.0.1: + version "3.0.1" + resolved "https://registry.yarnpkg.com/json-buffer/-/json-buffer-3.0.1.tgz#9338802a30d3b6605fbe0613e094008ca8c05a13" + integrity sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ== + +json-parse-even-better-errors@^2.3.0: + version "2.3.1" + resolved "https://registry.yarnpkg.com/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz#7c47805a94319928e05777405dc12e1f7a4ee02d" + integrity sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w== + +json-schema-traverse@^0.4.1: + version "0.4.1" + resolved "https://registry.yarnpkg.com/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz#69f6a87d9513ab8bb8fe63bdb0979c448e684660" + integrity sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg== + +json-stable-stringify-without-jsonify@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz#9db7b59496ad3f3cfef30a75142d2d930ad72651" + integrity sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw== + +json5@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/json5/-/json5-1.0.2.tgz#63d98d60f21b313b77c4d6da18bfa69d80e1d593" + integrity sha512-g1MWMLBiz8FKi1e4w0UyVL3w+iJceWAFBAaBnnGKOpNa5f8TLktkbre1+s6oICydWAm+HRUGTmI+//xv2hvXYA== + dependencies: + minimist "^1.2.0" + +json5@^2.2.3: + version "2.2.3" + resolved "https://registry.yarnpkg.com/json5/-/json5-2.2.3.tgz#78cd6f1a19bdc12b73db5ad0c61efd66c1e29283" + integrity sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg== + +jsonwebtoken@^9.0.2: + version "9.0.3" + resolved "https://registry.yarnpkg.com/jsonwebtoken/-/jsonwebtoken-9.0.3.tgz#6cd57ab01e9b0ac07cb847d53d3c9b6ee31f7ae2" + integrity sha512-MT/xP0CrubFRNLNKvxJ2BYfy53Zkm++5bX9dtuPbqAeQpTVe0MQTFhao8+Cp//EmJp244xt6Drw/GVEGCUj40g== + dependencies: + jws "^4.0.1" + lodash.includes "^4.3.0" + lodash.isboolean "^3.0.3" + lodash.isinteger "^4.0.4" + lodash.isnumber "^3.0.3" + lodash.isplainobject "^4.0.6" + lodash.isstring "^4.0.1" + lodash.once "^4.0.0" + ms "^2.1.1" + semver "^7.5.4" + +"jsx-ast-utils@^2.4.1 || ^3.0.0": + version "3.3.5" + resolved "https://registry.yarnpkg.com/jsx-ast-utils/-/jsx-ast-utils-3.3.5.tgz#4766bd05a8e2a11af222becd19e15575e52a853a" + integrity sha512-ZZow9HBI5O6EPgSJLUb8n2NKgmVWTwCvHGwFuJlMjvLFqlGG6pjirPhtdsseaLZjSibD8eegzmYpUZwoIlj2cQ== + dependencies: + array-includes "^3.1.6" + array.prototype.flat "^1.3.1" + object.assign "^4.1.4" + object.values "^1.1.6" + +jwa@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/jwa/-/jwa-2.0.1.tgz#bf8176d1ad0cd72e0f3f58338595a13e110bc804" + integrity sha512-hRF04fqJIP8Abbkq5NKGN0Bbr3JxlQ+qhZufXVr0DvujKy93ZCbXZMHDL4EOtodSbCWxOqR8MS1tXA5hwqCXDg== + dependencies: + buffer-equal-constant-time "^1.0.1" + ecdsa-sig-formatter "1.0.11" + safe-buffer "^5.0.1" + +jws@^4.0.1: + version "4.0.1" + resolved "https://registry.yarnpkg.com/jws/-/jws-4.0.1.tgz#07edc1be8fac20e677b283ece261498bd38f0690" + integrity sha512-EKI/M/yqPncGUUh44xz0PxSidXFr/+r0pA70+gIYhjv+et7yxM+s29Y+VGDkovRofQem0fs7Uvf4+YmAdyRduA== + dependencies: + jwa "^2.0.1" + safe-buffer "^5.0.1" + +keyv@^4.5.4: + version "4.5.4" + resolved "https://registry.yarnpkg.com/keyv/-/keyv-4.5.4.tgz#a879a99e29452f942439f2a405e3af8b31d4de93" + integrity sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw== + dependencies: + json-buffer "3.0.1" + +kind-of@^6.0.2: + version "6.0.3" + resolved "https://registry.yarnpkg.com/kind-of/-/kind-of-6.0.3.tgz#07c05034a6c349fa06e24fa35aa76db4580ce4dd" + integrity sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw== + +levn@^0.4.1: + version "0.4.1" + resolved "https://registry.yarnpkg.com/levn/-/levn-0.4.1.tgz#ae4562c007473b932a6200d403268dd2fffc6ade" + integrity sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ== + dependencies: + prelude-ls "^1.2.1" + type-check "~0.4.0" + +lines-and-columns@^1.1.6: + version "1.2.4" + resolved "https://registry.yarnpkg.com/lines-and-columns/-/lines-and-columns-1.2.4.tgz#eca284f75d2965079309dc0ad9255abb2ebc1632" + integrity sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg== + +linked-list@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/linked-list/-/linked-list-2.1.0.tgz#fa7b63a6caf4b17862a1eb90d14ead4ee57649f2" + integrity sha512-0GK/ylO6e5cv1PCOIdTRHxOaCgQ+0jKwHt+cHzkiCAZlx0KM5Id1bBAPad6g2mkvBNp1pNdmG0cohFGfqjkv9A== + +locate-path@^6.0.0: + version "6.0.0" + resolved "https://registry.yarnpkg.com/locate-path/-/locate-path-6.0.0.tgz#55321eb309febbc59c4801d931a72452a681d286" + integrity sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw== + dependencies: + p-locate "^5.0.0" + +lodash.includes@^4.3.0: + version "4.3.0" + resolved "https://registry.yarnpkg.com/lodash.includes/-/lodash.includes-4.3.0.tgz#60bb98a87cb923c68ca1e51325483314849f553f" + integrity sha512-W3Bx6mdkRTGtlJISOvVD/lbqjTlPPUDTMnlXZFnVwi9NKJ6tiAk6LVdlhZMm17VZisqhKcgzpO5Wz91PCt5b0w== + +lodash.isboolean@^3.0.3: + version "3.0.3" + resolved "https://registry.yarnpkg.com/lodash.isboolean/-/lodash.isboolean-3.0.3.tgz#6c2e171db2a257cd96802fd43b01b20d5f5870f6" + integrity sha512-Bz5mupy2SVbPHURB98VAcw+aHh4vRV5IPNhILUCsOzRmsTmSQ17jIuqopAentWoehktxGd9e/hbIXq980/1QJg== + +lodash.isinteger@^4.0.4: + version "4.0.4" + resolved "https://registry.yarnpkg.com/lodash.isinteger/-/lodash.isinteger-4.0.4.tgz#619c0af3d03f8b04c31f5882840b77b11cd68343" + integrity sha512-DBwtEWN2caHQ9/imiNeEA5ys1JoRtRfY3d7V9wkqtbycnAmTvRRmbHKDV4a0EYc678/dia0jrte4tjYwVBaZUA== + +lodash.isnumber@^3.0.3: + version "3.0.3" + resolved "https://registry.yarnpkg.com/lodash.isnumber/-/lodash.isnumber-3.0.3.tgz#3ce76810c5928d03352301ac287317f11c0b1ffc" + integrity sha512-QYqzpfwO3/CWf3XP+Z+tkQsfaLL/EnUlXWVkIk5FUPc4sBdTehEqZONuyRt2P67PXAk+NXmTBcc97zw9t1FQrw== + +lodash.isplainobject@^4.0.6: + version "4.0.6" + resolved "https://registry.yarnpkg.com/lodash.isplainobject/-/lodash.isplainobject-4.0.6.tgz#7c526a52d89b45c45cc690b88163be0497f550cb" + integrity sha512-oSXzaWypCMHkPC3NvBEaPHf0KsA5mvPrOPgQWDsbg8n7orZ290M0BmC/jgRZ4vcJ6DTAhjrsSYgdsW/F+MFOBA== + +lodash.isstring@^4.0.1: + version "4.0.1" + resolved "https://registry.yarnpkg.com/lodash.isstring/-/lodash.isstring-4.0.1.tgz#d527dfb5456eca7cc9bb95d5daeaf88ba54a5451" + integrity sha512-0wJxfxH1wgO3GrbuP+dTTk7op+6L41QCXbGINEmD+ny/G/eCqGzxyCsh7159S+mgDDcoarnBw6PC1PS5+wUGgw== + +lodash.merge@^4.6.2: + version "4.6.2" + resolved "https://registry.yarnpkg.com/lodash.merge/-/lodash.merge-4.6.2.tgz#558aa53b43b661e1925a0afdfa36a9a1085fe57a" + integrity sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ== + +lodash.once@^4.0.0: + version "4.1.1" + resolved "https://registry.yarnpkg.com/lodash.once/-/lodash.once-4.1.1.tgz#0dd3971213c7c56df880977d504c88fb471a97ac" + integrity sha512-Sb487aTOCr9drQVL8pIxOzVhafOjZN9UU54hiN8PU3uAiSV7lx1yYNpbNmex2PK6dSJoNTSJUUswT651yww3Mg== + +lodash@^4.17.21: + version "4.17.21" + resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.21.tgz#679591c564c3bffaae8454cf0b3df370c3d6911c" + integrity sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg== + +loose-envify@^1.4.0: + version "1.4.0" + resolved "https://registry.yarnpkg.com/loose-envify/-/loose-envify-1.4.0.tgz#71ee51fa7be4caec1a63839f7e682d8132d30caf" + integrity sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q== + dependencies: + js-tokens "^3.0.0 || ^4.0.0" + +loupe@^3.1.0, loupe@^3.1.2: + version "3.2.1" + resolved "https://registry.yarnpkg.com/loupe/-/loupe-3.2.1.tgz#0095cf56dc5b7a9a7c08ff5b1a8796ec8ad17e76" + integrity sha512-CdzqowRJCeLU72bHvWqwRBBlLcMEtIvGrlvef74kMnV2AolS9Y8xUv1I0U/MNAWMhBlKIoyuEgoJ0t/bbwHbLQ== + +lru-cache@^5.1.1: + version "5.1.1" + resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-5.1.1.tgz#1da27e6710271947695daf6848e847f01d84b920" + integrity sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w== + dependencies: + yallist "^3.0.2" + +magic-string@^0.30.12: + version "0.30.21" + resolved "https://registry.yarnpkg.com/magic-string/-/magic-string-0.30.21.tgz#56763ec09a0fa8091df27879fd94d19078c00d91" + integrity sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ== + dependencies: + "@jridgewell/sourcemap-codec" "^1.5.5" + +math-intrinsics@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/math-intrinsics/-/math-intrinsics-1.1.0.tgz#a0dd74be81e2aa5c2f27e65ce283605ee4e2b7f9" + integrity sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g== + +merge-stream@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/merge-stream/-/merge-stream-2.0.0.tgz#52823629a14dd00c9770fb6ad47dc6310f2c1f60" + integrity sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w== + +mimic-fn@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/mimic-fn/-/mimic-fn-2.1.0.tgz#7ed2c2ccccaf84d3ffcb7a69b57711fc2083401b" + integrity sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg== + +minimatch@^3.1.2: + version "3.1.2" + resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-3.1.2.tgz#19cd194bfd3e428f049a70817c038d89ab4be35b" + integrity sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw== + dependencies: + brace-expansion "^1.1.7" + +minimatch@^5.1.1: + version "5.1.6" + resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-5.1.6.tgz#1cfcb8cf5522ea69952cd2af95ae09477f122a96" + integrity sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g== + dependencies: + brace-expansion "^2.0.1" + +minimatch@^9.0.5: + version "9.0.5" + resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-9.0.5.tgz#d74f9dd6b57d83d8e98cfb82133b03978bc929e5" + integrity sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow== + dependencies: + brace-expansion "^2.0.1" + +minimist@^1.2.0, minimist@^1.2.6: + version "1.2.8" + resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.8.tgz#c1a464e7693302e082a075cee0c057741ac4772c" + integrity sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA== + +ms@^2.1.1, ms@^2.1.3: + version "2.1.3" + resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.3.tgz#574c8138ce1d2b5861f0b44579dbadd60c6615b2" + integrity sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA== + +multiaddr@^10.0.1: + version "10.0.1" + resolved "https://registry.yarnpkg.com/multiaddr/-/multiaddr-10.0.1.tgz#0d15848871370860a4d266bb44d93b3dac5d90ef" + integrity sha512-G5upNcGzEGuTHkzxezPrrD6CaIHR9uo+7MwqhNVcXTs33IInon4y7nMiGxl2CY5hG7chvYQUQhz5V52/Qe3cbg== + dependencies: + dns-over-http-resolver "^1.2.3" + err-code "^3.0.1" + is-ip "^3.1.0" + multiformats "^9.4.5" + uint8arrays "^3.0.0" + varint "^6.0.0" + +multiformats@^9.4.2, multiformats@^9.4.5: + version "9.9.0" + resolved "https://registry.yarnpkg.com/multiformats/-/multiformats-9.9.0.tgz#c68354e7d21037a8f1f8833c8ccd68618e8f1d37" + integrity sha512-HoMUjhH9T8DDBNT+6xzkrd9ga/XiBI4xLr58LJACwK6G3HTOPeMz4nB4KJs33L2BelrIJa7P0VuNaVF3hMYfjg== + +nanoid@^3.3.11: + version "3.3.11" + resolved "https://registry.yarnpkg.com/nanoid/-/nanoid-3.3.11.tgz#4f4f112cefbe303202f2199838128936266d185b" + integrity sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w== + +nanoid@^5.1.2: + version "5.1.6" + resolved "https://registry.yarnpkg.com/nanoid/-/nanoid-5.1.6.tgz#30363f664797e7d40429f6c16946d6bd7a3f26c9" + integrity sha512-c7+7RQ+dMB5dPwwCp4ee1/iV/q2P6aK1mTZcfr1BTuVlyW9hJYiMPybJCcnBlQtuSmTIWNeazm/zqNoZSSElBg== + +native-fetch@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/native-fetch/-/native-fetch-3.0.0.tgz#06ccdd70e79e171c365c75117959cf4fe14a09bb" + integrity sha512-G3Z7vx0IFb/FQ4JxvtqGABsOTIqRWvgQz6e+erkB+JJD6LrszQtMozEHI4EkmgZQvnGHrpLVzUWk7t4sJCIkVw== + +natural-compare@^1.4.0: + version "1.4.0" + resolved "https://registry.yarnpkg.com/natural-compare/-/natural-compare-1.4.0.tgz#4abebfeed7541f2c27acfb29bdbbd15c8d5ba4f7" + integrity sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw== + +node-releases@^2.0.27: + version "2.0.27" + resolved "https://registry.yarnpkg.com/node-releases/-/node-releases-2.0.27.tgz#eedca519205cf20f650f61d56b070db111231e4e" + integrity sha512-nmh3lCkYZ3grZvqcCH+fjmQ7X+H0OeZgP40OierEaAptX4XofMh5kwNbWh7lBduUzCcV/8kZ+NDLCwm2iorIlA== + +notistack@^3.0.1: + version "3.0.2" + resolved "https://registry.yarnpkg.com/notistack/-/notistack-3.0.2.tgz#009799c3fccddeffac58565ba1657d27616dfabd" + integrity sha512-0R+/arLYbK5Hh7mEfR2adt0tyXJcCC9KkA2hc56FeWik2QN6Bm/S4uW+BjzDARsJth5u06nTjelSw/VSnB1YEA== + dependencies: + clsx "^1.1.0" + goober "^2.0.33" + +npm-run-path@^4.0.1: + version "4.0.1" + resolved "https://registry.yarnpkg.com/npm-run-path/-/npm-run-path-4.0.1.tgz#b7ecd1e5ed53da8e37a55e1c2269e0b97ed748ea" + integrity sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw== + dependencies: + path-key "^3.0.0" + +object-assign@^4.1.1: + version "4.1.1" + resolved "https://registry.yarnpkg.com/object-assign/-/object-assign-4.1.1.tgz#2109adc7965887cfc05cbbd442cac8bfbb360863" + integrity sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg== + +object-inspect@^1.13.3, object-inspect@^1.13.4: + version "1.13.4" + resolved "https://registry.yarnpkg.com/object-inspect/-/object-inspect-1.13.4.tgz#8375265e21bc20d0fa582c22e1b13485d6e00213" + integrity sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew== + +object-keys@^1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/object-keys/-/object-keys-1.1.1.tgz#1c47f272df277f3b1daf061677d9c82e2322c60e" + integrity sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA== + +object.assign@^4.1.4, object.assign@^4.1.7: + version "4.1.7" + resolved "https://registry.yarnpkg.com/object.assign/-/object.assign-4.1.7.tgz#8c14ca1a424c6a561b0bb2a22f66f5049a945d3d" + integrity sha512-nK28WOo+QIjBkDduTINE4JkF/UJJKyf2EJxvJKfblDpyg0Q+pkOHNTL0Qwy6NP6FhE/EnzV73BxxqcJaXY9anw== + dependencies: + call-bind "^1.0.8" + call-bound "^1.0.3" + define-properties "^1.2.1" + es-object-atoms "^1.0.0" + has-symbols "^1.1.0" + object-keys "^1.1.1" + +object.entries@^1.1.9: + version "1.1.9" + resolved "https://registry.yarnpkg.com/object.entries/-/object.entries-1.1.9.tgz#e4770a6a1444afb61bd39f984018b5bede25f8b3" + integrity sha512-8u/hfXFRBD1O0hPUjioLhoWFHRmt6tKA4/vZPyckBr18l1KE9uHrFaFaUi8MDRTpi4uak2goyPTSNJLXX2k2Hw== + dependencies: + call-bind "^1.0.8" + call-bound "^1.0.4" + define-properties "^1.2.1" + es-object-atoms "^1.1.1" + +object.fromentries@^2.0.8: + version "2.0.8" + resolved "https://registry.yarnpkg.com/object.fromentries/-/object.fromentries-2.0.8.tgz#f7195d8a9b97bd95cbc1999ea939ecd1a2b00c65" + integrity sha512-k6E21FzySsSK5a21KRADBd/NGneRegFO5pLHfdQLpRDETUNJueLXs3WCzyQ3tFRDYgbq3KHGXfTbi2bs8WQ6rQ== + dependencies: + call-bind "^1.0.7" + define-properties "^1.2.1" + es-abstract "^1.23.2" + es-object-atoms "^1.0.0" + +object.groupby@^1.0.3: + version "1.0.3" + resolved "https://registry.yarnpkg.com/object.groupby/-/object.groupby-1.0.3.tgz#9b125c36238129f6f7b61954a1e7176148d5002e" + integrity sha512-+Lhy3TQTuzXI5hevh8sBGqbmurHbbIjAi0Z4S63nthVLmLxfbj4T54a4CfZrXIrt9iP4mVAPYMo/v99taj3wjQ== + dependencies: + call-bind "^1.0.7" + define-properties "^1.2.1" + es-abstract "^1.23.2" + +object.values@^1.1.6, object.values@^1.2.1: + version "1.2.1" + resolved "https://registry.yarnpkg.com/object.values/-/object.values-1.2.1.tgz#deed520a50809ff7f75a7cfd4bc64c7a038c6216" + integrity sha512-gXah6aZrcUxjWg2zR2MwouP2eHlCBzdV4pygudehaKXSGW4v2AsRQUK+lwwXhii6KFZcunEnmSUoYp5CXibxtA== + dependencies: + call-bind "^1.0.8" + call-bound "^1.0.3" + define-properties "^1.2.1" + es-object-atoms "^1.0.0" + +onetime@^5.1.2: + version "5.1.2" + resolved "https://registry.yarnpkg.com/onetime/-/onetime-5.1.2.tgz#d0e96ebb56b07476df1dd9c4806e5237985ca45e" + integrity sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg== + dependencies: + mimic-fn "^2.1.0" + +optionator@^0.9.3: + version "0.9.4" + resolved "https://registry.yarnpkg.com/optionator/-/optionator-0.9.4.tgz#7ea1c1a5d91d764fb282139c88fe11e182a3a734" + integrity sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g== + dependencies: + deep-is "^0.1.3" + fast-levenshtein "^2.0.6" + levn "^0.4.1" + prelude-ls "^1.2.1" + type-check "^0.4.0" + word-wrap "^1.2.5" + +own-keys@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/own-keys/-/own-keys-1.0.1.tgz#e4006910a2bf913585289676eebd6f390cf51358" + integrity sha512-qFOyK5PjiWZd+QQIh+1jhdb9LpxTF0qs7Pm8o5QHYZ0M3vKqSqzsZaEB6oWlxZ+q2sJBMI/Ktgd2N5ZwQoRHfg== + dependencies: + get-intrinsic "^1.2.6" + object-keys "^1.1.1" + safe-push-apply "^1.0.0" + +p-event@^4.2.0: + version "4.2.0" + resolved "https://registry.yarnpkg.com/p-event/-/p-event-4.2.0.tgz#af4b049c8acd91ae81083ebd1e6f5cae2044c1b5" + integrity sha512-KXatOjCRXXkSePPb1Nbi0p0m+gQAwdlbhi4wQKJPI1HsMQS9g+Sqp2o+QHziPr7eYJyOZet836KoHEVM1mwOrQ== + dependencies: + p-timeout "^3.1.0" + +p-finally@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/p-finally/-/p-finally-1.0.0.tgz#3fbcfb15b899a44123b34b6dcc18b724336a2cae" + integrity sha512-LICb2p9CB7FS+0eR1oqWnHhp0FljGLZCWBE9aix0Uye9W8LTQPwMTYVGWQWIw9RdQiDg4+epXQODwIYJtSJaow== + +p-limit@^3.0.2: + version "3.1.0" + resolved "https://registry.yarnpkg.com/p-limit/-/p-limit-3.1.0.tgz#e1daccbe78d0d1388ca18c64fea38e3e57e3706b" + integrity sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ== + dependencies: + yocto-queue "^0.1.0" + +p-locate@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/p-locate/-/p-locate-5.0.0.tgz#83c8315c6785005e3bd021839411c9e110e6d834" + integrity sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw== + dependencies: + p-limit "^3.0.2" + +p-timeout@^3.1.0: + version "3.2.0" + resolved "https://registry.yarnpkg.com/p-timeout/-/p-timeout-3.2.0.tgz#c7e17abc971d2a7962ef83626b35d635acf23dfe" + integrity sha512-rhIwUycgwwKcP9yTOOFK/AKsAopjjCakVqLHePO3CC6Mir1Z99xT+R63jZxAT5lFZLa2inS5h+ZS2GvR99/FBg== + dependencies: + p-finally "^1.0.0" + +parent-module@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/parent-module/-/parent-module-1.0.1.tgz#691d2709e78c79fae3a156622452d00762caaaa2" + integrity sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g== + dependencies: + callsites "^3.0.0" + +parse-json@^5.0.0: + version "5.2.0" + resolved "https://registry.yarnpkg.com/parse-json/-/parse-json-5.2.0.tgz#c76fc66dee54231c962b22bcc8a72cf2f99753cd" + integrity sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg== + dependencies: + "@babel/code-frame" "^7.0.0" + error-ex "^1.3.1" + json-parse-even-better-errors "^2.3.0" + lines-and-columns "^1.1.6" + +path-exists@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/path-exists/-/path-exists-4.0.0.tgz#513bdbe2d3b95d7762e8c1137efa195c6c61b5b3" + integrity sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w== + +path-key@^3.0.0, path-key@^3.1.0: + version "3.1.1" + resolved "https://registry.yarnpkg.com/path-key/-/path-key-3.1.1.tgz#581f6ade658cbba65a0d3380de7753295054f375" + integrity sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q== + +path-parse@^1.0.7: + version "1.0.7" + resolved "https://registry.yarnpkg.com/path-parse/-/path-parse-1.0.7.tgz#fbc114b60ca42b30d9daf5858e4bd68bbedb6735" + integrity sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw== + +path-type@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/path-type/-/path-type-4.0.0.tgz#84ed01c0a7ba380afe09d90a8c180dcd9d03043b" + integrity sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw== + +pathe@^1.1.2: + version "1.1.2" + resolved "https://registry.yarnpkg.com/pathe/-/pathe-1.1.2.tgz#6c4cb47a945692e48a1ddd6e4094d170516437ec" + integrity sha512-whLdWMYL2TwI08hn8/ZqAbrVemu0LNaNNJZX73O6qaIdCTfXutsLhMkjdENX0qhsQ9uIimo4/aQOmXkoon2nDQ== + +pathval@^2.0.0: + version "2.0.1" + resolved "https://registry.yarnpkg.com/pathval/-/pathval-2.0.1.tgz#8855c5a2899af072d6ac05d11e46045ad0dc605d" + integrity sha512-//nshmD55c46FuFw26xV/xFAaB5HF9Xdap7HJBBnrKdAd6/GxDBaNA1870O79+9ueg61cZLSVc+OaFlfmObYVQ== + +picocolors@^1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/picocolors/-/picocolors-1.1.1.tgz#3d321af3eab939b083c8f929a1d12cda81c26b6b" + integrity sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA== + +picomatch@^4.0.3: + version "4.0.3" + resolved "https://registry.yarnpkg.com/picomatch/-/picomatch-4.0.3.tgz#796c76136d1eead715db1e7bad785dedd695a042" + integrity sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q== + +possible-typed-array-names@^1.0.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/possible-typed-array-names/-/possible-typed-array-names-1.1.0.tgz#93e3582bc0e5426586d9d07b79ee40fc841de4ae" + integrity sha512-/+5VFTchJDoVj3bhoqi6UeymcD00DAwb1nJwamzPvHEszJ4FpF6SNNbUbOS8yI56qHzdV8eK0qEfOSiodkTdxg== + +postcss@^8.4.43: + version "8.5.6" + resolved "https://registry.yarnpkg.com/postcss/-/postcss-8.5.6.tgz#2825006615a619b4f62a9e7426cc120b349a8f3c" + integrity sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg== + dependencies: + nanoid "^3.3.11" + picocolors "^1.1.1" + source-map-js "^1.2.1" + +prelude-ls@^1.2.1: + version "1.2.1" + resolved "https://registry.yarnpkg.com/prelude-ls/-/prelude-ls-1.2.1.tgz#debc6489d7a6e6b0e7611888cec880337d316396" + integrity sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g== + +process-nextick-args@~2.0.0: + version "2.0.1" + resolved "https://registry.yarnpkg.com/process-nextick-args/-/process-nextick-args-2.0.1.tgz#7820d9b16120cc55ca9ae7792680ae7dba6d7fe2" + integrity sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag== + +prop-types@^15.6.2, prop-types@^15.8.1: + version "15.8.1" + resolved "https://registry.yarnpkg.com/prop-types/-/prop-types-15.8.1.tgz#67d87bf1a694f48435cf332c24af10214a3140b5" + integrity sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg== + dependencies: + loose-envify "^1.4.0" + object-assign "^4.1.1" + react-is "^16.13.1" + +punycode@^2.1.0: + version "2.3.1" + resolved "https://registry.yarnpkg.com/punycode/-/punycode-2.3.1.tgz#027422e2faec0b25e1549c3e1bd8309b9133b6e5" + integrity sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg== + +qr.js@0.0.0: + version "0.0.0" + resolved "https://registry.yarnpkg.com/qr.js/-/qr.js-0.0.0.tgz#cace86386f59a0db8050fa90d9b6b0e88a1e364f" + integrity sha512-c4iYnWb+k2E+vYpRimHqSu575b1/wKl4XFeJGpFmrJQz5I88v9aY2czh7s0w36srfCM1sXgC/xpoJz5dJfq+OQ== + +react-dom@^19.1.0: + version "19.2.3" + resolved "https://registry.yarnpkg.com/react-dom/-/react-dom-19.2.3.tgz#f0b61d7e5c4a86773889fcc1853af3ed5f215b17" + integrity sha512-yELu4WmLPw5Mr/lmeEpox5rw3RETacE++JgHqQzd2dg+YbJuat3jH4ingc+WPZhxaoFzdv9y33G+F7Nl5O0GBg== + dependencies: + scheduler "^0.27.0" + +react-is@^16.13.1, react-is@^16.7.0: + version "16.13.1" + resolved "https://registry.yarnpkg.com/react-is/-/react-is-16.13.1.tgz#789729a4dc36de2999dc156dd6c1d9c18cea56a4" + integrity sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ== + +react-is@^19.2.0: + version "19.2.3" + resolved "https://registry.yarnpkg.com/react-is/-/react-is-19.2.3.tgz#eec2feb69c7fb31f77d0b5c08c10ae1c88886b29" + integrity sha512-qJNJfu81ByyabuG7hPFEbXqNcWSU3+eVus+KJs+0ncpGfMyYdvSmxiJxbWR65lYi1I+/0HBcliO029gc4F+PnA== + +react-qr-code@^2.0.15: + version "2.0.18" + resolved "https://registry.yarnpkg.com/react-qr-code/-/react-qr-code-2.0.18.tgz#237de8fbab537885d6b2b10f4fd5318b371e3b17" + integrity sha512-v1Jqz7urLMhkO6jkgJuBYhnqvXagzceg3qJUWayuCK/c6LTIonpWbwxR1f1APGd4xrW/QcQEovNrAojbUz65Tg== + dependencies: + prop-types "^15.8.1" + qr.js "0.0.0" + +react-redux@^9.2.0: + version "9.2.0" + resolved "https://registry.yarnpkg.com/react-redux/-/react-redux-9.2.0.tgz#96c3ab23fb9a3af2cb4654be4b51c989e32366f5" + integrity sha512-ROY9fvHhwOD9ySfrF0wmvu//bKCQ6AeZZq1nJNtbDC+kk5DuSuNX/n6YWYF/SYy7bSba4D4FSz8DJeKY/S/r+g== + dependencies: + "@types/use-sync-external-store" "^0.0.6" + use-sync-external-store "^1.4.0" + +react-refresh@^0.17.0: + version "0.17.0" + resolved "https://registry.yarnpkg.com/react-refresh/-/react-refresh-0.17.0.tgz#b7e579c3657f23d04eccbe4ad2e58a8ed51e7e53" + integrity sha512-z6F7K9bV85EfseRCp2bzrpyQ0Gkw1uLoCel9XBVWPg/TjRj94SkJzUTGfOa4bs7iJvBWtQG0Wq7wnI0syw3EBQ== + +react-router-dom@^7.6.1: + version "7.11.0" + resolved "https://registry.yarnpkg.com/react-router-dom/-/react-router-dom-7.11.0.tgz#2165f63e52798bd0eb138480c098ad058cdf3413" + integrity sha512-e49Ir/kMGRzFOOrYQBdoitq3ULigw4lKbAyKusnvtDu2t4dBX4AGYPrzNvorXmVuOyeakai6FUPW5MmibvVG8g== + dependencies: + react-router "7.11.0" + +react-router@7.11.0: + version "7.11.0" + resolved "https://registry.yarnpkg.com/react-router/-/react-router-7.11.0.tgz#d3b91567fdbe910caf9064ea69b7b4d9264f2945" + integrity sha512-uI4JkMmjbWCZc01WVP2cH7ZfSzH91JAZUDd7/nIprDgWxBV1TkkmLToFh7EbMTcMak8URFRa2YoBL/W8GWnCTQ== + dependencies: + cookie "^1.0.1" + set-cookie-parser "^2.6.0" + +react-transition-group@^4.4.5: + version "4.4.5" + resolved "https://registry.yarnpkg.com/react-transition-group/-/react-transition-group-4.4.5.tgz#e53d4e3f3344da8521489fbef8f2581d42becdd1" + integrity sha512-pZcd1MCJoiKiBR2NRxeCRg13uCXbydPnmB4EOeRrY7480qNWO8IIgQG6zlDkm6uRMsURXPuKq0GWtiM59a5Q6g== + dependencies: + "@babel/runtime" "^7.5.5" + dom-helpers "^5.0.1" + loose-envify "^1.4.0" + prop-types "^15.6.2" + +react@^19.1.0: + version "19.2.3" + resolved "https://registry.yarnpkg.com/react/-/react-19.2.3.tgz#d83e5e8e7a258cf6b4fe28640515f99b87cd19b8" + integrity sha512-Ku/hhYbVjOQnXDZFv2+RibmLFGwFdeeKHFcOTlrt7xplBnya5OGn/hIRDsqDiSUcfORsDC7MPxwork8jBwsIWA== + +readable-stream@^2.3.5, readable-stream@~2.3.6: + version "2.3.8" + resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-2.3.8.tgz#91125e8042bba1b9887f49345f6277027ce8be9b" + integrity sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA== + dependencies: + core-util-is "~1.0.0" + inherits "~2.0.3" + isarray "~1.0.0" + process-nextick-args "~2.0.0" + safe-buffer "~5.1.1" + string_decoder "~1.1.1" + util-deprecate "~1.0.1" + +receptacle@^1.3.2: + version "1.3.2" + resolved "https://registry.yarnpkg.com/receptacle/-/receptacle-1.3.2.tgz#a7994c7efafc7a01d0e2041839dab6c4951360d2" + integrity sha512-HrsFvqZZheusncQRiEE7GatOAETrARKV/lnfYicIm8lbvp/JQOdADOfhjBd2DajvoszEyxSM6RlAAIZgEoeu/A== + dependencies: + ms "^2.1.1" + +redux-persist@^6.0.0: + version "6.0.0" + resolved "https://registry.yarnpkg.com/redux-persist/-/redux-persist-6.0.0.tgz#b4d2972f9859597c130d40d4b146fecdab51b3a8" + integrity sha512-71LLMbUq2r02ng2We9S215LtPu3fY0KgaGE0k8WRgl6RkqxtGfl7HUozz1Dftwsb0D/5mZ8dwAaPbtnzfvbEwQ== + +redux-thunk@^3.1.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/redux-thunk/-/redux-thunk-3.1.0.tgz#94aa6e04977c30e14e892eae84978c1af6058ff3" + integrity sha512-NW2r5T6ksUKXCabzhL9z+h206HQw/NJkcLm1GPImRQ8IzfXwRGqjVhKJGauHirT0DAuyy6hjdnMZaRoAcy0Klw== + +redux@^4.0.0: + version "4.2.1" + resolved "https://registry.yarnpkg.com/redux/-/redux-4.2.1.tgz#c08f4306826c49b5e9dc901dee0452ea8fce6197" + integrity sha512-LAUYz4lc+Do8/g7aeRa8JkyDErK6ekstQaqWQrNRW//MY1TvCEpMtpTWvlQ+FPbWCx+Xixu/6SHt5N0HR+SB4w== + dependencies: + "@babel/runtime" "^7.9.2" + +redux@^5.0.1: + version "5.0.1" + resolved "https://registry.yarnpkg.com/redux/-/redux-5.0.1.tgz#97fa26881ce5746500125585d5642c77b6e9447b" + integrity sha512-M9/ELqF6fy8FwmkpnF0S3YKOqMyoWJ4+CS5Efg2ct3oY9daQvd/Pc71FpGZsVsbl3Cpb+IIcjBDUnnyBdQbq4w== + +reflect.getprototypeof@^1.0.6, reflect.getprototypeof@^1.0.9: + version "1.0.10" + resolved "https://registry.yarnpkg.com/reflect.getprototypeof/-/reflect.getprototypeof-1.0.10.tgz#c629219e78a3316d8b604c765ef68996964e7bf9" + integrity sha512-00o4I+DVrefhv+nX0ulyi3biSHCPDe+yLv5o/p6d/UVlirijB8E16FtfwSAi4g3tcqrQ4lRAqQSoFEZJehYEcw== + dependencies: + call-bind "^1.0.8" + define-properties "^1.2.1" + es-abstract "^1.23.9" + es-errors "^1.3.0" + es-object-atoms "^1.0.0" + get-intrinsic "^1.2.7" + get-proto "^1.0.1" + which-builtin-type "^1.2.1" + +regexp.prototype.flags@^1.5.3, regexp.prototype.flags@^1.5.4: + version "1.5.4" + resolved "https://registry.yarnpkg.com/regexp.prototype.flags/-/regexp.prototype.flags-1.5.4.tgz#1ad6c62d44a259007e55b3970e00f746efbcaa19" + integrity sha512-dYqgNSZbDwkaJ2ceRd9ojCGjBq+mOm9LmtXnAnEGyHhN/5R7iDW2TRw3h+o/jCFxus3P2LfWIIiwowAjANm7IA== + dependencies: + call-bind "^1.0.8" + define-properties "^1.2.1" + es-errors "^1.3.0" + get-proto "^1.0.1" + gopd "^1.2.0" + set-function-name "^2.0.2" + +reselect@^5.1.0, reselect@^5.1.1: + version "5.1.1" + resolved "https://registry.yarnpkg.com/reselect/-/reselect-5.1.1.tgz#c766b1eb5d558291e5e550298adb0becc24bb72e" + integrity sha512-K/BG6eIky/SBpzfHZv/dd+9JBFiS4SWV7FIujVyJRux6e45+73RaUHXLmIR1f7WOMaQ0U1km6qwklRQxpJJY0w== + +resolve-from@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/resolve-from/-/resolve-from-4.0.0.tgz#4abcd852ad32dd7baabfe9b40e00a36db5f392e6" + integrity sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g== + +resolve@^1.19.0, resolve@^1.22.4: + version "1.22.11" + resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.22.11.tgz#aad857ce1ffb8bfa9b0b1ac29f1156383f68c262" + integrity sha512-RfqAvLnMl313r7c9oclB1HhUEAezcpLjz95wFH4LVuhk9JF/r22qmVP9AMmOU4vMX7Q8pN8jwNg/CSpdFnMjTQ== + dependencies: + is-core-module "^2.16.1" + path-parse "^1.0.7" + supports-preserve-symlinks-flag "^1.0.0" + +resolve@^2.0.0-next.5: + version "2.0.0-next.5" + resolved "https://registry.yarnpkg.com/resolve/-/resolve-2.0.0-next.5.tgz#6b0ec3107e671e52b68cd068ef327173b90dc03c" + integrity sha512-U7WjGVG9sH8tvjW5SmGbQuui75FiyjAX72HX15DwBBwF9dNiQZRQAg9nnPhYy+TUnE0+VcrttuvNI8oSxZcocA== + dependencies: + is-core-module "^2.13.0" + path-parse "^1.0.7" + supports-preserve-symlinks-flag "^1.0.0" + +rn-host-detect@^1.2.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/rn-host-detect/-/rn-host-detect-1.2.0.tgz#8b0396fc05631ec60c1cb8789e5070cdb04d0da0" + integrity sha512-btNg5kzHcjZZ7t7mvvV/4wNJ9e3MPgrWivkRgWURzXL0JJ0pwWlU4zrbmdlz3HHzHOxhBhHB4D+/dbMFfu4/4A== + +rollup@^4.20.0: + version "4.55.1" + resolved "https://registry.yarnpkg.com/rollup/-/rollup-4.55.1.tgz#4ec182828be440648e7ee6520dc35e9f20e05144" + integrity sha512-wDv/Ht1BNHB4upNbK74s9usvl7hObDnvVzknxqY/E/O3X6rW1U1rV1aENEfJ54eFZDTNo7zv1f5N4edCluH7+A== + dependencies: + "@types/estree" "1.0.8" + optionalDependencies: + "@rollup/rollup-android-arm-eabi" "4.55.1" + "@rollup/rollup-android-arm64" "4.55.1" + "@rollup/rollup-darwin-arm64" "4.55.1" + "@rollup/rollup-darwin-x64" "4.55.1" + "@rollup/rollup-freebsd-arm64" "4.55.1" + "@rollup/rollup-freebsd-x64" "4.55.1" + "@rollup/rollup-linux-arm-gnueabihf" "4.55.1" + "@rollup/rollup-linux-arm-musleabihf" "4.55.1" + "@rollup/rollup-linux-arm64-gnu" "4.55.1" + "@rollup/rollup-linux-arm64-musl" "4.55.1" + "@rollup/rollup-linux-loong64-gnu" "4.55.1" + "@rollup/rollup-linux-loong64-musl" "4.55.1" + "@rollup/rollup-linux-ppc64-gnu" "4.55.1" + "@rollup/rollup-linux-ppc64-musl" "4.55.1" + "@rollup/rollup-linux-riscv64-gnu" "4.55.1" + "@rollup/rollup-linux-riscv64-musl" "4.55.1" + "@rollup/rollup-linux-s390x-gnu" "4.55.1" + "@rollup/rollup-linux-x64-gnu" "4.55.1" + "@rollup/rollup-linux-x64-musl" "4.55.1" + "@rollup/rollup-openbsd-x64" "4.55.1" + "@rollup/rollup-openharmony-arm64" "4.55.1" + "@rollup/rollup-win32-arm64-msvc" "4.55.1" + "@rollup/rollup-win32-ia32-msvc" "4.55.1" + "@rollup/rollup-win32-x64-gnu" "4.55.1" + "@rollup/rollup-win32-x64-msvc" "4.55.1" + fsevents "~2.3.2" + +safe-array-concat@^1.1.3: + version "1.1.3" + resolved "https://registry.yarnpkg.com/safe-array-concat/-/safe-array-concat-1.1.3.tgz#c9e54ec4f603b0bbb8e7e5007a5ee7aecd1538c3" + integrity sha512-AURm5f0jYEOydBj7VQlVvDrjeFgthDdEF5H1dP+6mNpoXOMo1quQqJ4wvJDyRZ9+pO3kGWoOdmV08cSv2aJV6Q== + dependencies: + call-bind "^1.0.8" + call-bound "^1.0.2" + get-intrinsic "^1.2.6" + has-symbols "^1.1.0" + isarray "^2.0.5" + +safe-buffer@^5.0.1, safe-buffer@^5.1.1: + version "5.2.1" + resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.2.1.tgz#1eaf9fa9bdb1fdd4ec75f58f9cdb4e6b7827eec6" + integrity sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ== + +safe-buffer@~5.1.0, safe-buffer@~5.1.1: + version "5.1.2" + resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.1.2.tgz#991ec69d296e0313747d59bdfd2b745c35f8828d" + integrity sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g== + +safe-push-apply@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/safe-push-apply/-/safe-push-apply-1.0.0.tgz#01850e981c1602d398c85081f360e4e6d03d27f5" + integrity sha512-iKE9w/Z7xCzUMIZqdBsp6pEQvwuEebH4vdpjcDWnyzaI6yl6O9FHvVpmGelvEHNsoY6wGblkxR6Zty/h00WiSA== + dependencies: + es-errors "^1.3.0" + isarray "^2.0.5" + +safe-regex-test@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/safe-regex-test/-/safe-regex-test-1.1.0.tgz#7f87dfb67a3150782eaaf18583ff5d1711ac10c1" + integrity sha512-x/+Cz4YrimQxQccJf5mKEbIa1NzeCRNI5Ecl/ekmlYaampdNLPalVyIcCZNNH3MvmqBugV5TMYZXv0ljslUlaw== + dependencies: + call-bound "^1.0.2" + es-errors "^1.3.0" + is-regex "^1.2.1" + +sc-errors@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/sc-errors/-/sc-errors-3.0.0.tgz#df2e124f011be5fdd633e92d1de5ce6a6b4c1b85" + integrity sha512-rIqv2HTPb9DVreZwK/DV0ytRUqyw2DbDcoB9XTKjEQL7oMEQKsfPA8V8dGGr7p8ZYfmvaRIGZ4Wu5qwvs/hGDA== + +sc-formatter@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/sc-formatter/-/sc-formatter-4.0.0.tgz#2dda494a08e9d4cb069cbc9238a9f670adb3e7a6" + integrity sha512-MgUIvuca+90fBrCWY5LdlU9YUWjlkPFwdpvmomcwQEu3t2id/6YHdG2nhB6o7nhRp4ocfmcXQTh00r/tJtynSg== + +scheduler@^0.27.0: + version "0.27.0" + resolved "https://registry.yarnpkg.com/scheduler/-/scheduler-0.27.0.tgz#0c4ef82d67d1e5c1e359e8fc76d3a87f045fe5bd" + integrity sha512-eNv+WrVbKu1f3vbYJT/xtiF5syA5HPIMtf9IgY/nKg0sWqzAUEvqY/xm7OcZc/qafLx/iO9FgOmeSAp4v5ti/Q== + +semver@^6.3.1: + version "6.3.1" + resolved "https://registry.yarnpkg.com/semver/-/semver-6.3.1.tgz#556d2ef8689146e46dcea4bfdd095f3434dffcb4" + integrity sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA== + +semver@^7.5.4, semver@^7.6.2, semver@^7.7.3: + version "7.7.3" + resolved "https://registry.yarnpkg.com/semver/-/semver-7.7.3.tgz#4b5f4143d007633a8dc671cd0a6ef9147b8bb946" + integrity sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q== + +set-cookie-parser@^2.6.0: + version "2.7.2" + resolved "https://registry.yarnpkg.com/set-cookie-parser/-/set-cookie-parser-2.7.2.tgz#ccd08673a9ae5d2e44ea2a2de25089e67c7edf68" + integrity sha512-oeM1lpU/UvhTxw+g3cIfxXHyJRc/uidd3yK1P242gzHds0udQBYzs3y8j4gCCW+ZJ7ad0yctld8RYO+bdurlvw== + +set-function-length@^1.2.2: + version "1.2.2" + resolved "https://registry.yarnpkg.com/set-function-length/-/set-function-length-1.2.2.tgz#aac72314198eaed975cf77b2c3b6b880695e5449" + integrity sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg== + dependencies: + define-data-property "^1.1.4" + es-errors "^1.3.0" + function-bind "^1.1.2" + get-intrinsic "^1.2.4" + gopd "^1.0.1" + has-property-descriptors "^1.0.2" + +set-function-name@^2.0.2: + version "2.0.2" + resolved "https://registry.yarnpkg.com/set-function-name/-/set-function-name-2.0.2.tgz#16a705c5a0dc2f5e638ca96d8a8cd4e1c2b90985" + integrity sha512-7PGFlmtwsEADb0WYyvCMa1t+yke6daIG4Wirafur5kcf+MhUnPms1UeR0CKQdTZD81yESwMHbtn+TR+dMviakQ== + dependencies: + define-data-property "^1.1.4" + es-errors "^1.3.0" + functions-have-names "^1.2.3" + has-property-descriptors "^1.0.2" + +set-proto@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/set-proto/-/set-proto-1.0.0.tgz#0760dbcff30b2d7e801fd6e19983e56da337565e" + integrity sha512-RJRdvCo6IAnPdsvP/7m6bsQqNnn1FCBX5ZNtFL98MmFF/4xAIJTIg1YbHW5DC2W5SKZanrC6i4HsJqlajw/dZw== + dependencies: + dunder-proto "^1.0.1" + es-errors "^1.3.0" + es-object-atoms "^1.0.0" + +shallow-clone@^3.0.0: + version "3.0.1" + resolved "https://registry.yarnpkg.com/shallow-clone/-/shallow-clone-3.0.1.tgz#8f2981ad92531f55035b01fb230769a40e02efa3" + integrity sha512-/6KqX+GVUdqPuPPd2LxDDxzX6CAbjJehAAOKlNpqqUpAqPM6HeL8f+o3a+JsyGjn2lv0WY8UsTgUJjU9Ok55NA== + dependencies: + kind-of "^6.0.2" + +shebang-command@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/shebang-command/-/shebang-command-2.0.0.tgz#ccd0af4f8835fbdc265b82461aaf0c36663f34ea" + integrity sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA== + dependencies: + shebang-regex "^3.0.0" + +shebang-regex@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/shebang-regex/-/shebang-regex-3.0.0.tgz#ae16f1644d873ecad843b0307b143362d4c42172" + integrity sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A== + +side-channel-list@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/side-channel-list/-/side-channel-list-1.0.0.tgz#10cb5984263115d3b7a0e336591e290a830af8ad" + integrity sha512-FCLHtRD/gnpCiCHEiJLOwdmFP+wzCmDEkc9y7NsYxeF4u7Btsn1ZuwgwJGxImImHicJArLP4R0yX4c2KCrMrTA== + dependencies: + es-errors "^1.3.0" + object-inspect "^1.13.3" + +side-channel-map@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/side-channel-map/-/side-channel-map-1.0.1.tgz#d6bb6b37902c6fef5174e5f533fab4c732a26f42" + integrity sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA== + dependencies: + call-bound "^1.0.2" + es-errors "^1.3.0" + get-intrinsic "^1.2.5" + object-inspect "^1.13.3" + +side-channel-weakmap@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/side-channel-weakmap/-/side-channel-weakmap-1.0.2.tgz#11dda19d5368e40ce9ec2bdc1fb0ecbc0790ecea" + integrity sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A== + dependencies: + call-bound "^1.0.2" + es-errors "^1.3.0" + get-intrinsic "^1.2.5" + object-inspect "^1.13.3" + side-channel-map "^1.0.1" + +side-channel@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/side-channel/-/side-channel-1.1.0.tgz#c3fcff9c4da932784873335ec9765fa94ff66bc9" + integrity sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw== + dependencies: + es-errors "^1.3.0" + object-inspect "^1.13.3" + side-channel-list "^1.0.0" + side-channel-map "^1.0.1" + side-channel-weakmap "^1.0.2" + +siginfo@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/siginfo/-/siginfo-2.0.0.tgz#32e76c70b79724e3bb567cb9d543eb858ccfaf30" + integrity sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g== + +signal-exit@^3.0.3: + version "3.0.7" + resolved "https://registry.yarnpkg.com/signal-exit/-/signal-exit-3.0.7.tgz#a9a1767f8af84155114eaabd73f99273c8f59ad9" + integrity sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ== + +socketcluster-client@^19.2.3: + version "19.2.7" + resolved "https://registry.yarnpkg.com/socketcluster-client/-/socketcluster-client-19.2.7.tgz#70d96a491323cc044d1171efabc3aa05023d959a" + integrity sha512-c6caNOr/49FUjlVnQfXb0TasMnrqY1uN/uevT99xicF+7NkvGSNwjP6rlMP0v1ZZjz+MosT2/qJNDDc2b3v/Jw== + dependencies: + ag-auth "^2.1.0" + ag-channel "^5.0.0" + ag-request "^1.1.0" + async-stream-emitter "^7.0.1" + buffer "^5.2.1" + clone-deep "^4.0.1" + linked-list "^2.1.0" + sc-errors "^3.0.0" + sc-formatter "^4.0.0" + stream-demux "^10.0.1" + uuid "^8.3.2" + vinyl-buffer "^1.0.1" + ws "^8.18.0" + +source-map-js@^1.2.1: + version "1.2.1" + resolved "https://registry.yarnpkg.com/source-map-js/-/source-map-js-1.2.1.tgz#1ce5650fddd87abc099eda37dcff024c2667ae46" + integrity sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA== + +source-map@^0.5.7: + version "0.5.7" + resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.5.7.tgz#8a039d2d1021d22d1ea14c80d8ea468ba2ef3fcc" + integrity sha512-LbrmJOMUSdEVxIKvdcJzQC+nQhe8FUZQTXQy6+I75skNgn3OoQ0DZA8YnFa7gp8tqtL3KPf1kmo0R5DoApeSGQ== + +stackback@0.0.2: + version "0.0.2" + resolved "https://registry.yarnpkg.com/stackback/-/stackback-0.0.2.tgz#1ac8a0d9483848d1695e418b6d031a3c3ce68e3b" + integrity sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw== + +std-env@^3.8.0: + version "3.10.0" + resolved "https://registry.yarnpkg.com/std-env/-/std-env-3.10.0.tgz#d810b27e3a073047b2b5e40034881f5ea6f9c83b" + integrity sha512-5GS12FdOZNliM5mAOxFRg7Ir0pWz8MdpYm6AY6VPkGpbA7ZzmbzNcBJQ0GPvvyWgcY7QAhCgf9Uy89I03faLkg== + +stop-iteration-iterator@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/stop-iteration-iterator/-/stop-iteration-iterator-1.1.0.tgz#f481ff70a548f6124d0312c3aa14cbfa7aa542ad" + integrity sha512-eLoXW/DHyl62zxY4SCaIgnRhuMr6ri4juEYARS8E6sCEqzKpOiE521Ucofdx+KnDZl5xmvGYaaKCk5FEOxJCoQ== + dependencies: + es-errors "^1.3.0" + internal-slot "^1.1.0" + +stream-demux@^10.0.1: + version "10.0.1" + resolved "https://registry.yarnpkg.com/stream-demux/-/stream-demux-10.0.1.tgz#204b65fb8973c87cea65119e99622405b3dbcc10" + integrity sha512-QjTYLJWpZxZ6uL5R1JzgOzjvao8zDx78ec+uOjHNeVc/9TuasYLldoVrYARZeT1xI1hFYuiKf13IM8b4wamhHg== + dependencies: + consumable-stream "^3.0.0" + writable-consumable-stream "^4.1.0" + +string.prototype.matchall@^4.0.12: + version "4.0.12" + resolved "https://registry.yarnpkg.com/string.prototype.matchall/-/string.prototype.matchall-4.0.12.tgz#6c88740e49ad4956b1332a911e949583a275d4c0" + integrity sha512-6CC9uyBL+/48dYizRf7H7VAYCMCNTBeM78x/VTUe9bFEaxBepPJDa1Ow99LqI/1yF7kuy7Q3cQsYMrcjGUcskA== + dependencies: + call-bind "^1.0.8" + call-bound "^1.0.3" + define-properties "^1.2.1" + es-abstract "^1.23.6" + es-errors "^1.3.0" + es-object-atoms "^1.0.0" + get-intrinsic "^1.2.6" + gopd "^1.2.0" + has-symbols "^1.1.0" + internal-slot "^1.1.0" + regexp.prototype.flags "^1.5.3" + set-function-name "^2.0.2" + side-channel "^1.1.0" + +string.prototype.repeat@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/string.prototype.repeat/-/string.prototype.repeat-1.0.0.tgz#e90872ee0308b29435aa26275f6e1b762daee01a" + integrity sha512-0u/TldDbKD8bFCQ/4f5+mNRrXwZ8hg2w7ZR8wa16e8z9XpePWl3eGEcUD0OXpEH/VJH/2G3gjUtR3ZOiBe2S/w== + dependencies: + define-properties "^1.1.3" + es-abstract "^1.17.5" + +string.prototype.trim@^1.2.10: + version "1.2.10" + resolved "https://registry.yarnpkg.com/string.prototype.trim/-/string.prototype.trim-1.2.10.tgz#40b2dd5ee94c959b4dcfb1d65ce72e90da480c81" + integrity sha512-Rs66F0P/1kedk5lyYyH9uBzuiI/kNRmwJAR9quK6VOtIpZ2G+hMZd+HQbbv25MgCA6gEffoMZYxlTod4WcdrKA== + dependencies: + call-bind "^1.0.8" + call-bound "^1.0.2" + define-data-property "^1.1.4" + define-properties "^1.2.1" + es-abstract "^1.23.5" + es-object-atoms "^1.0.0" + has-property-descriptors "^1.0.2" + +string.prototype.trimend@^1.0.9: + version "1.0.9" + resolved "https://registry.yarnpkg.com/string.prototype.trimend/-/string.prototype.trimend-1.0.9.tgz#62e2731272cd285041b36596054e9f66569b6942" + integrity sha512-G7Ok5C6E/j4SGfyLCloXTrngQIQU3PWtXGst3yM7Bea9FRURf1S42ZHlZZtsNque2FN2PoUhfZXYLNWwEr4dLQ== + dependencies: + call-bind "^1.0.8" + call-bound "^1.0.2" + define-properties "^1.2.1" + es-object-atoms "^1.0.0" + +string.prototype.trimstart@^1.0.8: + version "1.0.8" + resolved "https://registry.yarnpkg.com/string.prototype.trimstart/-/string.prototype.trimstart-1.0.8.tgz#7ee834dda8c7c17eff3118472bb35bfedaa34dde" + integrity sha512-UXSH262CSZY1tfu3G3Secr6uGLCFVPMhIqHjlgCUtCCcgihYc/xKs9djMTMUOb2j1mVSeU8EU6NWc/iQKU6Gfg== + dependencies: + call-bind "^1.0.7" + define-properties "^1.2.1" + es-object-atoms "^1.0.0" + +string_decoder@~1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/string_decoder/-/string_decoder-1.1.1.tgz#9cf1611ba62685d7030ae9e4ba34149c3af03fc8" + integrity sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg== + dependencies: + safe-buffer "~5.1.0" + +strip-bom@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/strip-bom/-/strip-bom-3.0.0.tgz#2334c18e9c759f7bdd56fdef7e9ae3d588e68ed3" + integrity sha512-vavAMRXOgBVNF6nyEEmL3DBK19iRpDcoIwW+swQ+CbGiu7lju6t+JklA1MHweoWtadgt4ISVUsXLyDq34ddcwA== + +strip-final-newline@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/strip-final-newline/-/strip-final-newline-2.0.0.tgz#89b852fb2fcbe936f6f4b3187afb0a12c1ab58ad" + integrity sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA== + +strip-json-comments@^3.1.1: + version "3.1.1" + resolved "https://registry.yarnpkg.com/strip-json-comments/-/strip-json-comments-3.1.1.tgz#31f1281b3832630434831c310c01cccda8cbe006" + integrity sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig== + +stylis@4.2.0: + version "4.2.0" + resolved "https://registry.yarnpkg.com/stylis/-/stylis-4.2.0.tgz#79daee0208964c8fe695a42fcffcac633a211a51" + integrity sha512-Orov6g6BB1sDfYgzWfTHDOxamtX1bE/zo104Dh9e6fqJ3PooipYyfJ0pUmrZO2wAvO8YbEyeFrkV91XTsGMSrw== + +supports-color@^7.1.0: + version "7.2.0" + resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-7.2.0.tgz#1b7dcdcb32b8138801b3e478ba6a51caa89648da" + integrity sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw== + dependencies: + has-flag "^4.0.0" + +supports-preserve-symlinks-flag@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz#6eda4bd344a3c94aea376d4cc31bc77311039e09" + integrity sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w== + +through2@^2.0.3: + version "2.0.5" + resolved "https://registry.yarnpkg.com/through2/-/through2-2.0.5.tgz#01c1e39eb31d07cb7d03a96a70823260b23132cd" + integrity sha512-/mrRod8xqpA+IHSLyGCQ2s8SPHiCDEeQJSep1jqLYeEUClOFG2Qsh+4FU6G9VeqpZnGW/Su8LQGc4YKni5rYSQ== + dependencies: + readable-stream "~2.3.6" + xtend "~4.0.1" + +tinybench@^2.9.0: + version "2.9.0" + resolved "https://registry.yarnpkg.com/tinybench/-/tinybench-2.9.0.tgz#103c9f8ba6d7237a47ab6dd1dcff77251863426b" + integrity sha512-0+DUvqWMValLmha6lr4kD8iAMK1HzV0/aKnCtWb9v9641TnP/MFb7Pc2bxoxQjTXAErryXVgUOfv2YqNllqGeg== + +tinyexec@^0.3.1: + version "0.3.2" + resolved "https://registry.yarnpkg.com/tinyexec/-/tinyexec-0.3.2.tgz#941794e657a85e496577995c6eef66f53f42b3d2" + integrity sha512-KQQR9yN7R5+OSwaK0XQoj22pwHoTlgYqmUscPYoknOoWCWfj/5/ABTMRi69FrKU5ffPVh5QcFikpWJI/P1ocHA== + +tinyglobby@^0.2.15: + version "0.2.15" + resolved "https://registry.yarnpkg.com/tinyglobby/-/tinyglobby-0.2.15.tgz#e228dd1e638cea993d2fdb4fcd2d4602a79951c2" + integrity sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ== + dependencies: + fdir "^6.5.0" + picomatch "^4.0.3" + +tinypool@^1.0.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/tinypool/-/tinypool-1.1.1.tgz#059f2d042bd37567fbc017d3d426bdd2a2612591" + integrity sha512-Zba82s87IFq9A9XmjiX5uZA/ARWDrB03OHlq+Vw1fSdt0I+4/Kutwy8BP4Y/y/aORMo61FQ0vIb5j44vSo5Pkg== + +tinyrainbow@^1.2.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/tinyrainbow/-/tinyrainbow-1.2.0.tgz#5c57d2fc0fb3d1afd78465c33ca885d04f02abb5" + integrity sha512-weEDEq7Z5eTHPDh4xjX789+fHfF+P8boiFB+0vbWzpbnbsEr/GRaohi/uMKxg8RZMXnl1ItAi/IUHWMsjDV7kQ== + +tinyspy@^3.0.2: + version "3.0.2" + resolved "https://registry.yarnpkg.com/tinyspy/-/tinyspy-3.0.2.tgz#86dd3cf3d737b15adcf17d7887c84a75201df20a" + integrity sha512-n1cw8k1k0x4pgA2+9XrOkFydTerNcJ1zWCO5Nn9scWHTD+5tp8dghT2x1uduQePZTZgd3Tupf+x9BxJjeJi77Q== + +ts-api-utils@^2.4.0: + version "2.4.0" + resolved "https://registry.yarnpkg.com/ts-api-utils/-/ts-api-utils-2.4.0.tgz#2690579f96d2790253bdcf1ca35d569ad78f9ad8" + integrity sha512-3TaVTaAv2gTiMB35i3FiGJaRfwb3Pyn/j3m/bfAvGe8FB7CF6u+LMYqYlDh7reQf7UNvoTvdfAqHGmPGOSsPmA== + +tsconfck@^3.0.3: + version "3.1.6" + resolved "https://registry.yarnpkg.com/tsconfck/-/tsconfck-3.1.6.tgz#da1f0b10d82237ac23422374b3fce1edb23c3ead" + integrity sha512-ks6Vjr/jEw0P1gmOVwutM3B7fWxoWBL2KRDb1JfqGVawBmO5UsvmWOQFGHBPl5yxYz4eERr19E6L7NMv+Fej4w== + +tsconfig-paths@^3.15.0: + version "3.15.0" + resolved "https://registry.yarnpkg.com/tsconfig-paths/-/tsconfig-paths-3.15.0.tgz#5299ec605e55b1abb23ec939ef15edaf483070d4" + integrity sha512-2Ac2RgzDe/cn48GvOe3M+o82pEFewD3UPbyoUHHdKasHwJKjds4fLXWf/Ux5kATBKN20oaFGu+jbElp1pos0mg== + dependencies: + "@types/json5" "^0.0.29" + json5 "^1.0.2" + minimist "^1.2.6" + strip-bom "^3.0.0" + +type-check@^0.4.0, type-check@~0.4.0: + version "0.4.0" + resolved "https://registry.yarnpkg.com/type-check/-/type-check-0.4.0.tgz#07b8203bfa7056c0657050e3ccd2c37730bab8f1" + integrity sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew== + dependencies: + prelude-ls "^1.2.1" + +typed-array-buffer@^1.0.3: + version "1.0.3" + resolved "https://registry.yarnpkg.com/typed-array-buffer/-/typed-array-buffer-1.0.3.tgz#a72395450a4869ec033fd549371b47af3a2ee536" + integrity sha512-nAYYwfY3qnzX30IkA6AQZjVbtK6duGontcQm1WSG1MD94YLqK0515GNApXkoxKOWMusVssAHWLh9SeaoefYFGw== + dependencies: + call-bound "^1.0.3" + es-errors "^1.3.0" + is-typed-array "^1.1.14" + +typed-array-byte-length@^1.0.3: + version "1.0.3" + resolved "https://registry.yarnpkg.com/typed-array-byte-length/-/typed-array-byte-length-1.0.3.tgz#8407a04f7d78684f3d252aa1a143d2b77b4160ce" + integrity sha512-BaXgOuIxz8n8pIq3e7Atg/7s+DpiYrxn4vdot3w9KbnBhcRQq6o3xemQdIfynqSeXeDrF32x+WvfzmOjPiY9lg== + dependencies: + call-bind "^1.0.8" + for-each "^0.3.3" + gopd "^1.2.0" + has-proto "^1.2.0" + is-typed-array "^1.1.14" + +typed-array-byte-offset@^1.0.4: + version "1.0.4" + resolved "https://registry.yarnpkg.com/typed-array-byte-offset/-/typed-array-byte-offset-1.0.4.tgz#ae3698b8ec91a8ab945016108aef00d5bff12355" + integrity sha512-bTlAFB/FBYMcuX81gbL4OcpH5PmlFHqlCCpAl8AlEzMz5k53oNDvN8p1PNOWLEmI2x4orp3raOFB51tv9X+MFQ== + dependencies: + available-typed-arrays "^1.0.7" + call-bind "^1.0.8" + for-each "^0.3.3" + gopd "^1.2.0" + has-proto "^1.2.0" + is-typed-array "^1.1.15" + reflect.getprototypeof "^1.0.9" + +typed-array-length@^1.0.7: + version "1.0.7" + resolved "https://registry.yarnpkg.com/typed-array-length/-/typed-array-length-1.0.7.tgz#ee4deff984b64be1e118b0de8c9c877d5ce73d3d" + integrity sha512-3KS2b+kL7fsuk/eJZ7EQdnEmQoaho/r6KUef7hxvltNA5DR8NAUM+8wJMbJyZ4G9/7i3v5zPBIMN5aybAh2/Jg== + dependencies: + call-bind "^1.0.7" + for-each "^0.3.3" + gopd "^1.0.1" + is-typed-array "^1.1.13" + possible-typed-array-names "^1.0.0" + reflect.getprototypeof "^1.0.6" + +typescript-eslint@^8.1.0: + version "8.52.0" + resolved "https://registry.yarnpkg.com/typescript-eslint/-/typescript-eslint-8.52.0.tgz#b8c156b6f2b4dee202a85712ff6a37f614476413" + integrity sha512-atlQQJ2YkO4pfTVQmQ+wvYQwexPDOIgo+RaVcD7gHgzy/IQA+XTyuxNM9M9TVXvttkF7koBHmcwisKdOAf2EcA== + dependencies: + "@typescript-eslint/eslint-plugin" "8.52.0" + "@typescript-eslint/parser" "8.52.0" + "@typescript-eslint/typescript-estree" "8.52.0" + "@typescript-eslint/utils" "8.52.0" + +typescript@^5.2.2: + version "5.9.3" + resolved "https://registry.yarnpkg.com/typescript/-/typescript-5.9.3.tgz#5b4f59e15310ab17a216f5d6cf53ee476ede670f" + integrity sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw== + +uint8arrays@^3.0.0: + version "3.1.1" + resolved "https://registry.yarnpkg.com/uint8arrays/-/uint8arrays-3.1.1.tgz#2d8762acce159ccd9936057572dade9459f65ae0" + integrity sha512-+QJa8QRnbdXVpHYjLoTpJIdCTiw9Ir62nocClWuXIq2JIh4Uta0cQsTSpFL678p2CN8B+XSApwcU+pQEqVpKWg== + dependencies: + multiformats "^9.4.2" + +unbox-primitive@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/unbox-primitive/-/unbox-primitive-1.1.0.tgz#8d9d2c9edeea8460c7f35033a88867944934d1e2" + integrity sha512-nWJ91DjeOkej/TA8pXQ3myruKpKEYgqvpw9lz4OPHj/NWFNluYrjbz9j01CJ8yKQd2g4jFoOkINCTW2I5LEEyw== + dependencies: + call-bound "^1.0.3" + has-bigints "^1.0.2" + has-symbols "^1.1.0" + which-boxed-primitive "^1.1.1" + +undici-types@~6.21.0: + version "6.21.0" + resolved "https://registry.yarnpkg.com/undici-types/-/undici-types-6.21.0.tgz#691d00af3909be93a7faa13be61b3a5b50ef12cb" + integrity sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ== + +undici-types@~7.16.0: + version "7.16.0" + resolved "https://registry.yarnpkg.com/undici-types/-/undici-types-7.16.0.tgz#ffccdff36aea4884cbfce9a750a0580224f58a46" + integrity sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw== + +update-browserslist-db@^1.2.0: + version "1.2.3" + resolved "https://registry.yarnpkg.com/update-browserslist-db/-/update-browserslist-db-1.2.3.tgz#64d76db58713136acbeb4c49114366cc6cc2e80d" + integrity sha512-Js0m9cx+qOgDxo0eMiFGEueWztz+d4+M3rGlmKPT+T4IS/jP4ylw3Nwpu6cpTTP8R1MAC1kF4VbdLt3ARf209w== + dependencies: + escalade "^3.2.0" + picocolors "^1.1.1" + +uri-js@^4.2.2: + version "4.4.1" + resolved "https://registry.yarnpkg.com/uri-js/-/uri-js-4.4.1.tgz#9b1a52595225859e55f669d928f88c6c57f2a77e" + integrity sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg== + dependencies: + punycode "^2.1.0" + +use-sync-external-store@^1.4.0, use-sync-external-store@^1.6.0: + version "1.6.0" + resolved "https://registry.yarnpkg.com/use-sync-external-store/-/use-sync-external-store-1.6.0.tgz#b174bfa65cb2b526732d9f2ac0a408027876f32d" + integrity sha512-Pp6GSwGP/NrPIrxVFAIkOQeyw8lFenOHijQWkUTrDvrF4ALqylP2C/KCkeS9dpUM3KvYRQhna5vt7IL95+ZQ9w== + +util-deprecate@~1.0.1: + version "1.0.2" + resolved "https://registry.yarnpkg.com/util-deprecate/-/util-deprecate-1.0.2.tgz#450d4dc9fa70de732762fbd2d4a28981419a0ccf" + integrity sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw== + +uuid@10.0.0: + version "10.0.0" + resolved "https://registry.yarnpkg.com/uuid/-/uuid-10.0.0.tgz#5a95aa454e6e002725c79055fd42aaba30ca6294" + integrity sha512-8XkAphELsDnEGrDxUOHB3RGvXz6TeuYSGEZBOjtTtPm2lwhGBjLgOzLHB63IUWfBpNucQjND6d3AOudO+H3RWQ== + +uuid@^8.3.2: + version "8.3.2" + resolved "https://registry.yarnpkg.com/uuid/-/uuid-8.3.2.tgz#80d5b5ced271bb9af6c445f21a1a04c606cefbe2" + integrity sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg== + +varint@^6.0.0: + version "6.0.0" + resolved "https://registry.yarnpkg.com/varint/-/varint-6.0.0.tgz#9881eb0ce8feaea6512439d19ddf84bf551661d0" + integrity sha512-cXEIW6cfr15lFv563k4GuVuW/fiwjknytD37jIOLSdSWuOI6WnO/oKwmP2FQTU2l01LP8/M5TSAJpzUaGe3uWg== + +vinyl-buffer@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/vinyl-buffer/-/vinyl-buffer-1.0.1.tgz#96c1a3479b8c5392542c612029013b5b27f88bbf" + integrity sha512-LRBE2/g3C1hSHL2k/FynSZcVTRhEw8sb08oKGt/0hukZXwrh2m8nfy+r5yLhGEk7eFFuclhyIuPct/Bxlxk6rg== + dependencies: + bl "^1.2.1" + through2 "^2.0.3" + +virtua@^0.33.2: + version "0.33.7" + resolved "https://registry.yarnpkg.com/virtua/-/virtua-0.33.7.tgz#bd46d7d31f257886e6245347354fb4e80e27441f" + integrity sha512-IepZaMD/oeEh/ymTqokeQGLrMuRV25+lizPegxVIhOwqX+dEeV9ml1P57Eosok4qiZaeBeQIbIkF9QZrT+EeRQ== + +vite-node@2.1.9: + version "2.1.9" + resolved "https://registry.yarnpkg.com/vite-node/-/vite-node-2.1.9.tgz#549710f76a643f1c39ef34bdb5493a944e4f895f" + integrity sha512-AM9aQ/IPrW/6ENLQg3AGY4K1N2TGZdR5e4gu/MmmR2xR3Ll1+dib+nook92g4TV3PXVyeyxdWwtaCAiUL0hMxA== + dependencies: + cac "^6.7.14" + debug "^4.3.7" + es-module-lexer "^1.5.4" + pathe "^1.1.2" + vite "^5.0.0" + +vite-plugin-top-level-await@^1.4.4: + version "1.6.0" + resolved "https://registry.yarnpkg.com/vite-plugin-top-level-await/-/vite-plugin-top-level-await-1.6.0.tgz#c6ed0be438a1c14f48b4f9a56da859c12821a7c2" + integrity sha512-bNhUreLamTIkoulCR9aDXbTbhLk6n1YE8NJUTTxl5RYskNRtzOR0ASzSjBVRtNdjIfngDXo11qOsybGLNsrdww== + dependencies: + "@rollup/plugin-virtual" "^3.0.2" + "@swc/core" "^1.12.14" + "@swc/wasm" "^1.12.14" + uuid "10.0.0" + +vite-plugin-watch@^0.3.1: + version "0.3.1" + resolved "https://registry.yarnpkg.com/vite-plugin-watch/-/vite-plugin-watch-0.3.1.tgz#5000f7ded6eb1c42e9483d6ea3d812061ab8188f" + integrity sha512-tmLJ5tqSqXY7wSXoM8+huOgbictUG6SKLh/tZ6LAY51KPSAnPBr0dYwyxPPPQm+JgIIBbKSyNnPHpW11ad+qlw== + dependencies: + minimatch "^5.1.1" + +vite-tsconfig-paths@^4.3.2: + version "4.3.2" + resolved "https://registry.yarnpkg.com/vite-tsconfig-paths/-/vite-tsconfig-paths-4.3.2.tgz#321f02e4b736a90ff62f9086467faf4e2da857a9" + integrity sha512-0Vd/a6po6Q+86rPlntHye7F31zA2URZMbH8M3saAZ/xR9QoGN/L21bxEGfXdWmFdNkqPpRdxFT7nmNe12e9/uA== + dependencies: + debug "^4.1.1" + globrex "^0.1.2" + tsconfck "^3.0.3" + +vite@^5.0.0, vite@^5.3.1: + version "5.4.21" + resolved "https://registry.yarnpkg.com/vite/-/vite-5.4.21.tgz#84a4f7c5d860b071676d39ba513c0d598fdc7027" + integrity sha512-o5a9xKjbtuhY6Bi5S3+HvbRERmouabWbyUcpXXUA1u+GNUKoROi9byOJ8M0nHbHYHkYICiMlqxkg1KkYmm25Sw== + dependencies: + esbuild "^0.21.3" + postcss "^8.4.43" + rollup "^4.20.0" + optionalDependencies: + fsevents "~2.3.3" + +vitest@^2.1.1: + version "2.1.9" + resolved "https://registry.yarnpkg.com/vitest/-/vitest-2.1.9.tgz#7d01ffd07a553a51c87170b5e80fea3da7fb41e7" + integrity sha512-MSmPM9REYqDGBI8439mA4mWhV5sKmDlBKWIYbA3lRb2PTHACE0mgKwA8yQ2xq9vxDTuk4iPrECBAEW2aoFXY0Q== + dependencies: + "@vitest/expect" "2.1.9" + "@vitest/mocker" "2.1.9" + "@vitest/pretty-format" "^2.1.9" + "@vitest/runner" "2.1.9" + "@vitest/snapshot" "2.1.9" + "@vitest/spy" "2.1.9" + "@vitest/utils" "2.1.9" + chai "^5.1.2" + debug "^4.3.7" + expect-type "^1.1.0" + magic-string "^0.30.12" + pathe "^1.1.2" + std-env "^3.8.0" + tinybench "^2.9.0" + tinyexec "^0.3.1" + tinypool "^1.0.1" + tinyrainbow "^1.2.0" + vite "^5.0.0" + vite-node "2.1.9" + why-is-node-running "^2.3.0" + +which-boxed-primitive@^1.1.0, which-boxed-primitive@^1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/which-boxed-primitive/-/which-boxed-primitive-1.1.1.tgz#d76ec27df7fa165f18d5808374a5fe23c29b176e" + integrity sha512-TbX3mj8n0odCBFVlY8AxkqcHASw3L60jIuF8jFP78az3C2YhmGvqbHBpAjTRH2/xqYunrJ9g1jSyjCjpoWzIAA== + dependencies: + is-bigint "^1.1.0" + is-boolean-object "^1.2.1" + is-number-object "^1.1.1" + is-string "^1.1.1" + is-symbol "^1.1.1" + +which-builtin-type@^1.2.1: + version "1.2.1" + resolved "https://registry.yarnpkg.com/which-builtin-type/-/which-builtin-type-1.2.1.tgz#89183da1b4907ab089a6b02029cc5d8d6574270e" + integrity sha512-6iBczoX+kDQ7a3+YJBnh3T+KZRxM/iYNPXicqk66/Qfm1b93iu+yOImkg0zHbj5LNOcNv1TEADiZ0xa34B4q6Q== + dependencies: + call-bound "^1.0.2" + function.prototype.name "^1.1.6" + has-tostringtag "^1.0.2" + is-async-function "^2.0.0" + is-date-object "^1.1.0" + is-finalizationregistry "^1.1.0" + is-generator-function "^1.0.10" + is-regex "^1.2.1" + is-weakref "^1.0.2" + isarray "^2.0.5" + which-boxed-primitive "^1.1.0" + which-collection "^1.0.2" + which-typed-array "^1.1.16" + +which-collection@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/which-collection/-/which-collection-1.0.2.tgz#627ef76243920a107e7ce8e96191debe4b16c2a0" + integrity sha512-K4jVyjnBdgvc86Y6BkaLZEN933SwYOuBFkdmBu9ZfkcAbdVbpITnDmjvZ/aQjRXQrv5EPkTnD1s39GiiqbngCw== + dependencies: + is-map "^2.0.3" + is-set "^2.0.3" + is-weakmap "^2.0.2" + is-weakset "^2.0.3" + +which-typed-array@^1.1.16, which-typed-array@^1.1.19: + version "1.1.19" + resolved "https://registry.yarnpkg.com/which-typed-array/-/which-typed-array-1.1.19.tgz#df03842e870b6b88e117524a4b364b6fc689f956" + integrity sha512-rEvr90Bck4WZt9HHFC4DJMsjvu7x+r6bImz0/BrbWb7A2djJ8hnZMrWnHo9F8ssv0OMErasDhftrfROTyqSDrw== + dependencies: + available-typed-arrays "^1.0.7" + call-bind "^1.0.8" + call-bound "^1.0.4" + for-each "^0.3.5" + get-proto "^1.0.1" + gopd "^1.2.0" + has-tostringtag "^1.0.2" + +which@^2.0.1: + version "2.0.2" + resolved "https://registry.yarnpkg.com/which/-/which-2.0.2.tgz#7c6a8dd0a636a0327e10b59c9286eee93f3f51b1" + integrity sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA== + dependencies: + isexe "^2.0.0" + +why-is-node-running@^2.3.0: + version "2.3.0" + resolved "https://registry.yarnpkg.com/why-is-node-running/-/why-is-node-running-2.3.0.tgz#a3f69a97107f494b3cdc3bdddd883a7d65cebf04" + integrity sha512-hUrmaWBdVDcxvYqnyh09zunKzROWjbZTiNy8dBEjkS7ehEDQibXJ7XvlmtbwuTclUiIyN+CyXQD4Vmko8fNm8w== + dependencies: + siginfo "^2.0.0" + stackback "0.0.2" + +word-wrap@^1.2.5: + version "1.2.5" + resolved "https://registry.yarnpkg.com/word-wrap/-/word-wrap-1.2.5.tgz#d2c45c6dd4fbce621a66f136cbe328afd0410b34" + integrity sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA== + +writable-consumable-stream@^4.1.0: + version "4.2.0" + resolved "https://registry.yarnpkg.com/writable-consumable-stream/-/writable-consumable-stream-4.2.0.tgz#731cb8bc7c16d5e120adfaddd7d41c52179934d7" + integrity sha512-A2g0/Xaq/I2DQlYofh7nvKaJYZ0v4UOKuNLePG/G1ylx7p8e904jMTKhS+cdHNO1OulZpP2ModXs37EkG6tqSQ== + dependencies: + consumable-stream "^3.0.0" + +ws@^8.18.0: + version "8.19.0" + resolved "https://registry.yarnpkg.com/ws/-/ws-8.19.0.tgz#ddc2bdfa5b9ad860204f5a72a4863a8895fd8c8b" + integrity sha512-blAT2mjOEIi0ZzruJfIhb3nps74PRWTCz1IjglWEEpQl5XS/UNama6u2/rjFkDDouqr4L67ry+1aGIALViWjDg== + +xtend@~4.0.1: + version "4.0.2" + resolved "https://registry.yarnpkg.com/xtend/-/xtend-4.0.2.tgz#bb72779f5fa465186b1f438f674fa347fdb5db54" + integrity sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ== + +yallist@^3.0.2: + version "3.1.1" + resolved "https://registry.yarnpkg.com/yallist/-/yallist-3.1.1.tgz#dbb7daf9bfd8bac9ab45ebf602b8cbad0d5d08fd" + integrity sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g== + +yaml@^1.10.0: + version "1.10.2" + resolved "https://registry.yarnpkg.com/yaml/-/yaml-1.10.2.tgz#2301c5ffbf12b467de8da2333a459e29e7920e4b" + integrity sha512-r3vXyErRCYJ7wg28yvBY5VSoAF8ZvlcW9/BwUzEtUsjvX/DKs24dIkuwjtuprwJJHsbyUbLApepYTR1BN4uHrg== + +yocto-queue@^0.1.0: + version "0.1.0" + resolved "https://registry.yarnpkg.com/yocto-queue/-/yocto-queue-0.1.0.tgz#0294eb3dee05028d31ee1a5fa2c556a6aaf10a1b" + integrity sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q== + +"zod-validation-error@^3.5.0 || ^4.0.0": + version "4.0.2" + resolved "https://registry.yarnpkg.com/zod-validation-error/-/zod-validation-error-4.0.2.tgz#bc605eba49ce0fcd598c127fee1c236be3f22918" + integrity sha512-Q6/nZLe6jxuU80qb/4uJ4t5v2VEZ44lzQjPDhYJNztRQ4wyWc6VF3D3Kb/fAuPetZQnhS3hnajCf9CsWesghLQ== + +"zod@^3.25.0 || ^4.0.0": + version "4.3.5" + resolved "https://registry.yarnpkg.com/zod/-/zod-4.3.5.tgz#aeb269a6f9fc259b1212c348c7c5432aaa474d2a" + integrity sha512-k7Nwx6vuWx1IJ9Bjuf4Zt1PEllcwe7cls3VNzm4CQ1/hgtFUK2bRNG3rvnpPUhFjmqJKAKtjV576KnUkHocg/g== diff --git a/swap/src/cli/api/tauri_bindings.rs b/swap/src/cli/api/tauri_bindings.rs index da818a71e1..9aa3fc2896 100644 --- a/swap/src/cli/api/tauri_bindings.rs +++ b/swap/src/cli/api/tauri_bindings.rs @@ -1090,16 +1090,20 @@ pub enum TauriSwapProgressEvent { BtcPartialRefundPublished { #[typeshare(serialized_as = "string")] btc_partial_refund_txid: Txid, - /// Whether we have Alice's signature on the amnesty transaction - /// such that we will be able to refund the rest of the locked Bitcoin - /// after the partial refund went through. - has_amnesty_signature: bool, + #[typeshare(serialized_as = "number")] + btc_lock_amount: bitcoin::Amount, + #[typeshare(serialized_as = "number")] + btc_amnesty_amount: bitcoin::Amount, }, // BtcAmnesty was published but not yet confirmed. // Requires BtcPartialRefund to be published first. BtcAmnestyPublished { #[typeshare(serialized_as = "string")] btc_amnesty_txid: Txid, + #[typeshare(serialized_as = "number")] + btc_lock_amount: bitcoin::Amount, + #[typeshare(serialized_as = "number")] + btc_amnesty_amount: bitcoin::Amount, }, // tx_early_refund has been confirmed BtcEarlyRefunded { @@ -1115,35 +1119,55 @@ pub enum TauriSwapProgressEvent { BtcPartiallyRefunded { #[typeshare(serialized_as = "string")] btc_partial_refund_txid: Txid, - /// Whether we have Alice's signature on the amnesty transaction - /// such that we will be able to refund the rest of the locked Bitcoin - /// after the partial refund went through. - has_amnesty_signature: bool, + #[typeshare(serialized_as = "number")] + btc_lock_amount: bitcoin::Amount, + #[typeshare(serialized_as = "number")] + btc_amnesty_amount: bitcoin::Amount, }, - // BtcAmnesty was published but not yet confirmed. + // BtcAmnesty was confirmed. BtcAmnestyReceived { #[typeshare(serialized_as = "string")] btc_amnesty_txid: Txid, + #[typeshare(serialized_as = "number")] + btc_lock_amount: bitcoin::Amount, + #[typeshare(serialized_as = "number")] + btc_amnesty_amount: bitcoin::Amount, }, // TxRefundBurn has been published (waiting for confirmation) BtcRefundBurnPublished { #[typeshare(serialized_as = "string")] btc_refund_burn_txid: Txid, + #[typeshare(serialized_as = "number")] + btc_lock_amount: bitcoin::Amount, + #[typeshare(serialized_as = "number")] + btc_amnesty_amount: bitcoin::Amount, }, // TxRefundBurn has been confirmed - amnesty output is burnt BtcRefundBurnt { #[typeshare(serialized_as = "string")] btc_refund_burn_txid: Txid, + #[typeshare(serialized_as = "number")] + btc_lock_amount: bitcoin::Amount, + #[typeshare(serialized_as = "number")] + btc_amnesty_amount: bitcoin::Amount, }, // Alice published TxFinalAmnesty BtcFinalAmnestyPublished { #[typeshare(serialized_as = "string")] btc_final_amnesty_txid: Txid, + #[typeshare(serialized_as = "number")] + btc_lock_amount: bitcoin::Amount, + #[typeshare(serialized_as = "number")] + btc_amnesty_amount: bitcoin::Amount, }, // TxFinalAmnesty has been confirmed - user received burnt funds back BtcFinalAmnestyConfirmed { #[typeshare(serialized_as = "string")] btc_final_amnesty_txid: Txid, + #[typeshare(serialized_as = "number")] + btc_lock_amount: bitcoin::Amount, + #[typeshare(serialized_as = "number")] + btc_amnesty_amount: bitcoin::Amount, }, BtcPunished, AttemptingCooperativeRedeem, diff --git a/swap/src/protocol/bob/swap.rs b/swap/src/protocol/bob/swap.rs index d81506db77..cba59669c3 100644 --- a/swap/src/protocol/bob/swap.rs +++ b/swap/src/protocol/bob/swap.rs @@ -1027,7 +1027,8 @@ async fn next_state( swap_id, TauriSwapProgressEvent::BtcPartialRefundPublished { btc_partial_refund_txid: state.construct_tx_partial_refund()?.txid(), - has_amnesty_signature: state.refund_signatures.tx_refund_amnesty_sig().is_some(), + btc_lock_amount: state.tx_lock.lock_amount(), + btc_amnesty_amount: state.btc_amnesty_amount.unwrap_or(bitcoin::Amount::ZERO), }, ); @@ -1054,13 +1055,12 @@ async fn next_state( } } BobState::BtcPartiallyRefunded(state) => { - let has_amnesty_signature = state.refund_signatures.tx_refund_amnesty_sig().is_some(); - event_emitter.emit_swap_progress_event( swap_id, TauriSwapProgressEvent::BtcPartiallyRefunded { btc_partial_refund_txid: state.construct_tx_partial_refund()?.txid(), - has_amnesty_signature, + btc_lock_amount: state.tx_lock.lock_amount(), + btc_amnesty_amount: state.btc_amnesty_amount.unwrap_or(bitcoin::Amount::ZERO), }, ); @@ -1086,9 +1086,11 @@ async fn next_state( swap_id, TauriSwapProgressEvent::BtcAmnestyPublished { btc_amnesty_txid: tx_amnesty.txid(), + btc_lock_amount: state.tx_lock.lock_amount(), + btc_amnesty_amount: state.btc_amnesty_amount.unwrap_or(bitcoin::Amount::ZERO), }, ); - + let subscription = bitcoin_wallet.subscribe_to(Box::new(tx_amnesty.clone())).await; retry("Waiting for Bitcoin amnesty transaction to be published by Alice", || async { @@ -1102,6 +1104,8 @@ async fn next_state( swap_id, TauriSwapProgressEvent::BtcAmnestyReceived { btc_amnesty_txid: state.construct_tx_amnesty()?.txid(), + btc_lock_amount: state.tx_lock.lock_amount(), + btc_amnesty_amount: state.btc_amnesty_amount.unwrap_or(bitcoin::Amount::ZERO), }, ); @@ -1251,6 +1255,8 @@ async fn next_state( BobState::BtcAmnestyConfirmed(state) => { event_emitter.emit_swap_progress_event(swap_id, TauriSwapProgressEvent::BtcAmnestyReceived { btc_amnesty_txid: state.construct_tx_amnesty()?.txid(), + btc_lock_amount: state.tx_lock.lock_amount(), + btc_amnesty_amount: state.btc_amnesty_amount.unwrap_or(bitcoin::Amount::ZERO), }); BobState::BtcAmnestyConfirmed(state) }, @@ -1311,6 +1317,8 @@ async fn next_state( swap_id, TauriSwapProgressEvent::BtcRefundBurnPublished { btc_refund_burn_txid: tx_refund_burn.txid(), + btc_lock_amount: state.tx_lock.lock_amount(), + btc_amnesty_amount: state.btc_amnesty_amount.unwrap_or(bitcoin::Amount::ZERO), }, ); let subscription = bitcoin_wallet.subscribe_to(Box::new(tx_refund_burn)).await; @@ -1330,6 +1338,8 @@ async fn next_state( swap_id, TauriSwapProgressEvent::BtcRefundBurnt { btc_refund_burn_txid: tx_refund_burn.txid(), + btc_lock_amount: state.tx_lock.lock_amount(), + btc_amnesty_amount: state.btc_amnesty_amount.unwrap_or(bitcoin::Amount::ZERO), }, ); @@ -1350,6 +1360,8 @@ async fn next_state( swap_id, TauriSwapProgressEvent::BtcFinalAmnestyPublished { btc_final_amnesty_txid: tx_final_amnesty.txid(), + btc_lock_amount: state.tx_lock.lock_amount(), + btc_amnesty_amount: state.btc_amnesty_amount.unwrap_or(bitcoin::Amount::ZERO), }, ); let subscription = bitcoin_wallet.subscribe_to(Box::new(tx_final_amnesty)).await; @@ -1365,6 +1377,8 @@ async fn next_state( swap_id, TauriSwapProgressEvent::BtcFinalAmnestyConfirmed { btc_final_amnesty_txid: tx_final_amnesty.txid(), + btc_lock_amount: state.tx_lock.lock_amount(), + btc_amnesty_amount: state.btc_amnesty_amount.unwrap_or(bitcoin::Amount::ZERO), }, ); BobState::BtcFinalAmnestyConfirmed(state) From 2bf3d518177d0713d2f8c149a11f4f4a4d5f13cf Mon Sep 17 00:00:00 2001 From: einliterflasche Date: Mon, 12 Jan 2026 15:32:09 +0100 Subject: [PATCH 086/113] add RefundPolicyWire (same as RefundPolicy for now) to quote, bump protocol version --- swap-p2p/Cargo.toml | 1 + swap-p2p/src/protocols/quote.rs | 36 ++++++++++++++++++++++++++++++++- swap/src/asb/event_loop.rs | 19 ++++++++++++++--- swap/src/bin/swap.rs | 3 ++- 4 files changed, 54 insertions(+), 5 deletions(-) diff --git a/swap-p2p/Cargo.toml b/swap-p2p/Cargo.toml index a4ea31de93..b5514859c1 100644 --- a/swap-p2p/Cargo.toml +++ b/swap-p2p/Cargo.toml @@ -27,6 +27,7 @@ unsigned-varint = { version = "0.8.0", features = ["codec", "asynchronous_codec" bitcoin = { workspace = true } monero = { workspace = true } rand = { workspace = true } +rust_decimal = { workspace = true } # Utils anyhow = { workspace = true } diff --git a/swap-p2p/src/protocols/quote.rs b/swap-p2p/src/protocols/quote.rs index b84705cb63..f77dd24a88 100644 --- a/swap-p2p/src/protocols/quote.rs +++ b/swap-p2p/src/protocols/quote.rs @@ -1,16 +1,47 @@ use crate::out_event; use libp2p::request_response::{self, ProtocolSupport}; use libp2p::{PeerId, StreamProtocol}; +use rust_decimal::Decimal; use serde::{Deserialize, Serialize}; use swap_core::bitcoin; +use swap_env::config::RefundPolicy; use typeshare::typeshare; -pub(crate) const PROTOCOL: &str = "/comit/xmr/btc/bid-quote/1.0.0"; +pub(crate) const PROTOCOL: &str = "/comit/xmr/btc/bid-quote/2.0.0"; pub type OutEvent = request_response::Event<(), BidQuote>; pub type Message = request_response::Message<(), BidQuote>; pub type Behaviour = request_response::json::Behaviour<(), BidQuote>; +/// The refund policy that will apply if the swap is cancelled. +/// Communicated in quotes so takers know the terms upfront. +#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, Hash)] +#[serde(tag = "type", content = "content")] +#[typeshare] +pub enum RefundPolicyWire { + /// Taker receives 100% of their Bitcoin back on refund. + FullRefund, + /// Taker receives a partial refund; the remainder goes to an amnesty output + /// that the maker may or may not release later. + PartialRefund { + /// Ratio (0.0-1.0) of Bitcoin the taker receives immediately. + #[typeshare(serialized_as = "number")] + taker_refund_ratio: Decimal, + }, +} + +impl From for RefundPolicyWire { + fn from(policy: RefundPolicy) -> Self { + if policy.taker_refund_ratio == Decimal::ONE { + RefundPolicyWire::FullRefund + } else { + RefundPolicyWire::PartialRefund { + taker_refund_ratio: policy.taker_refund_ratio, + } + } + } +} + #[derive(Debug, Clone, Copy, Default)] pub struct BidQuoteProtocol; @@ -33,6 +64,8 @@ pub struct BidQuote { /// The maximum quantity the maker is willing to buy. #[typeshare(serialized_as = "number")] pub max_quantity: bitcoin::Amount, + /// The refund policy that will apply if the swap is cancelled. + pub refund_policy: RefundPolicyWire, /// Monero "ReserveProofV2" which proves that Alice has the funds to fulfill the quote. /// See "Zero to Monero" section 8.1.6 for more details. /// @@ -47,6 +80,7 @@ impl BidQuote { price: bitcoin::Amount::ZERO, min_quantity: bitcoin::Amount::ZERO, max_quantity: bitcoin::Amount::ZERO, + refund_policy: RefundPolicyWire::FullRefund, reserve_proof: None, }; } diff --git a/swap/src/asb/event_loop.rs b/swap/src/asb/event_loop.rs index 0b6c3d8dc9..8f865f3eeb 100644 --- a/swap/src/asb/event_loop.rs +++ b/swap/src/asb/event_loop.rs @@ -6,7 +6,7 @@ use crate::asb::{Behaviour, OutEvent}; use crate::monero; use crate::network::cooperative_xmr_redeem_after_punish::CooperativeXmrRedeemRejectReason; use crate::network::cooperative_xmr_redeem_after_punish::Response::{Fullfilled, Rejected}; -use crate::network::quote::BidQuote; +use crate::network::quote::{BidQuote, RefundPolicyWire}; use crate::network::swap_setup::alice::WalletSnapshot; use crate::network::transfer_proof; use crate::protocol::alice::swap::has_already_processed_enc_sig; @@ -289,7 +289,7 @@ where tracing::warn!(%peer, "Ignoring spot price request: {}", error); } SwarmEvent::Behaviour(OutEvent::QuoteRequested { channel, peer }) => { - match self.make_quote_or_use_cached(self.min_buy, self.max_buy, self.developer_tip.ratio).await { + match self.make_quote_or_use_cached(self.min_buy, self.max_buy, self.developer_tip.ratio, self.refund_policy.clone().into()).await { Ok(quote_arc) => { if self.swarm.behaviour_mut().quote.send_response(channel, (*quote_arc).clone()).is_err() { tracing::debug!(%peer, "Failed to respond with quote"); @@ -588,6 +588,7 @@ where min_buy: bitcoin::Amount, max_buy: bitcoin::Amount, developer_tip: Decimal, + refund_policy: RefundPolicyWire, ) -> Result, Arc> { // We use the min and max buy amounts to create a unique key for the cache // Although these values stay constant over the lifetime of an instance of the asb, this might change in the future @@ -638,6 +639,7 @@ where get_reserved_items, get_reserve_proof, developer_tip, + refund_policy, ) .await; @@ -1128,7 +1130,7 @@ mod quote { use tokio::time::timeout; use crate::{ - network::quote::{BidQuote, ReserveProofWithAddress}, + network::quote::{BidQuote, RefundPolicyWire, ReserveProofWithAddress}, protocol::alice::ReservesMonero, }; @@ -1152,6 +1154,7 @@ mod quote { get_reserved_items: I, get_reserve_proof: P, developer_tip: Decimal, + refund_policy: RefundPolicyWire, ) -> Result, Arc> where LR: LatestRate, @@ -1221,6 +1224,7 @@ mod quote { price: ask_price, min_quantity: bitcoin::Amount::ZERO, max_quantity: bitcoin::Amount::ZERO, + refund_policy: refund_policy.clone(), reserve_proof, })); } @@ -1235,6 +1239,7 @@ mod quote { price: ask_price, min_quantity: min_buy, max_quantity: max_bitcoin_for_monero, + refund_policy: refund_policy.clone(), reserve_proof, })); } @@ -1243,6 +1248,7 @@ mod quote { price: ask_price, min_quantity: min_buy, max_quantity: max_buy, + refund_policy, reserve_proof, })) } @@ -1485,6 +1491,7 @@ mod tests { || async { Ok(reserved_items) }, || async { Err(anyhow::anyhow!("no reserve proof")) }, Decimal::ZERO, + RefundPolicyWire::FullRefund, ) .await .unwrap(); @@ -1517,6 +1524,7 @@ mod tests { || async { Ok(reserved_items) }, || async { Err(anyhow::anyhow!("no reserve proof")) }, Decimal::ZERO, + RefundPolicyWire::FullRefund, ) .await .unwrap(); @@ -1544,6 +1552,7 @@ mod tests { || async { Ok(reserved_items) }, || async { Err(anyhow::anyhow!("no reserve proof")) }, Decimal::ZERO, + RefundPolicyWire::FullRefund, ) .await .unwrap(); @@ -1569,6 +1578,7 @@ mod tests { || async { Ok(reserved_items) }, || async { Err(anyhow::anyhow!("no reserve proof")) }, Decimal::ZERO, + RefundPolicyWire::FullRefund, ) .await .unwrap(); @@ -1599,6 +1609,7 @@ mod tests { || async { Ok(reserved_items) }, || async { Err(anyhow::anyhow!("no reserve proof")) }, Decimal::ZERO, + RefundPolicyWire::FullRefund, ) .await .unwrap(); @@ -1623,6 +1634,7 @@ mod tests { || async { Ok(reserved_items) }, || async { Err(anyhow::anyhow!("no reserve proof")) }, Decimal::ZERO, + RefundPolicyWire::FullRefund, ) .await; @@ -1649,6 +1661,7 @@ mod tests { || async { Ok(reserved_items) }, || async { Err(anyhow::anyhow!("no reserve proof")) }, Decimal::ZERO, + RefundPolicyWire::FullRefund, ) .await .unwrap(); diff --git a/swap/src/bin/swap.rs b/swap/src/bin/swap.rs index b0f5b875b6..f32999099a 100644 --- a/swap/src/bin/swap.rs +++ b/swap/src/bin/swap.rs @@ -46,7 +46,7 @@ mod tests { use std::time::Duration; use swap::cli::api::request::determine_btc_to_swap; use swap::cli::QuoteWithAddress; - use swap::network::quote::BidQuote; + use swap::network::quote::{BidQuote, RefundPolicyWire}; use tracing::level_filters::LevelFilter; use tracing_ext::capture_logs; @@ -424,6 +424,7 @@ mod tests { price: Amount::from_btc(0.001).unwrap(), max_quantity, min_quantity, + refund_policy: RefundPolicyWire::FullRefund, reserve_proof: None, }, version: Some("1.0.0".parse().unwrap()), From 656556986bc58f524f70c834cbacf2cf8054941c Mon Sep 17 00:00:00 2001 From: einliterflasche Date: Mon, 12 Jan 2026 15:33:12 +0100 Subject: [PATCH 087/113] gui: extend mocking system, make it possible to view SwapSetupInflight component for mock swap --- Cargo.lock | 1 + src-gui/src/dev/mockSwapEvents.ts | 74 ++++++++++++- src-gui/src/models/storeModel.ts | 2 + src-gui/src/renderer/background.ts | 5 +- .../modal/swap/pages/MockSwapControls.tsx | 56 ++++++---- .../components/pages/swap/swap/SwapWidget.tsx | 12 +- .../in_progress/SwapSetupInflightPage.tsx | 104 +++++++++++++++--- .../MakerOfferItem.tsx | 20 +++- src-gui/src/renderer/rpc.ts | 5 + src-gui/src/store/features/swapSlice.ts | 14 ++- src-gui/src/store/middleware/storeListener.ts | 5 + 11 files changed, 247 insertions(+), 51 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index fef092c4df..4f03708e94 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -10919,6 +10919,7 @@ dependencies = [ "libp2p-tor", "monero", "rand 0.8.5", + "rust_decimal", "semver", "serde", "serde_cbor", diff --git a/src-gui/src/dev/mockSwapEvents.ts b/src-gui/src/dev/mockSwapEvents.ts index 366f131c36..ddc446ebb2 100644 --- a/src-gui/src/dev/mockSwapEvents.ts +++ b/src-gui/src/dev/mockSwapEvents.ts @@ -1,5 +1,7 @@ import { + ApprovalRequest, BidQuote, + LockBitcoinDetails, MoneroAddressPool, QuoteWithAddress, TauriSwapProgressEvent, @@ -29,7 +31,7 @@ const MOCK_BTC_FINAL_AMNESTY_TXID = // Mock amounts for partial refund scenarios const MOCK_BTC_LOCK_AMOUNT = 50_000_000; // 0.5 BTC -const MOCK_BTC_AMNESTY_AMOUNT = 2_500_000; // 0.025 BTC (5% of lock amount) +const MOCK_BTC_AMNESTY_AMOUNT = 1_000_000; // 0.01 BTC (2% of lock amount) // Mock addresses const MOCK_BTC_DEPOSIT_ADDRESS = "bc1qar0srrr7xfkvy5l643lydnw9re59gtzzwf5mdq"; @@ -42,13 +44,28 @@ const MOCK_QUOTE: BidQuote = { price: 0.007, min_quantity: 10_000_000, max_quantity: 100_000_000, + refund_policy: { type: "FullRefund" }, }; const MOCK_QUOTE_WITH_ADDRESS: QuoteWithAddress = { multiaddr: "/ip4/127.0.0.1/tcp/9939", peer_id: "12D3KooWCdMKjesXMJz1SiZ7HgotrxuqhQJbP5sgBm2BwP1cqThi", quote: MOCK_QUOTE, - version: "0.13.0", + version: "3.6.1", +}; + +const MOCK_QUOTE_PARTIAL_REFUND: BidQuote = { + price: 0.0068, + min_quantity: 5_000_000, + max_quantity: 200_000_000, + refund_policy: { type: "PartialRefund", content: { taker_refund_ratio: 0.98 } }, +}; + +const MOCK_QUOTE_WITH_ADDRESS_PARTIAL: QuoteWithAddress = { + multiaddr: "/ip4/192.168.1.50/tcp/9940", + peer_id: "12D3KooWEyoppNCUzN3sX7atGxPHvqgZvUNQmKzz1mQvNfFhuqP9", + quote: MOCK_QUOTE_PARTIAL_REFUND, + version: "3.6.1", }; const MOCK_RECEIVE_POOL: MoneroAddressPool = [ @@ -66,7 +83,7 @@ const baseScenario: TauriSwapProgressEvent[] = [ deposit_address: MOCK_BTC_DEPOSIT_ADDRESS, max_giveable: 0, min_bitcoin_lock_tx_fee: 1000, - known_quotes: [MOCK_QUOTE_WITH_ADDRESS], + known_quotes: [MOCK_QUOTE_WITH_ADDRESS, MOCK_QUOTE_WITH_ADDRESS_PARTIAL], }, }, { type: "SwapSetupInflight", content: { btc_lock_amount: 50_000_000 } }, @@ -279,3 +296,54 @@ export const scenarios: Record = { }; export type MockScenario = keyof typeof scenarios; + +// Mock LockBitcoin approval requests for testing confirmation screen + +// Partial refund version (5% amnesty) +const MOCK_LOCK_BITCOIN_DETAILS_PARTIAL: LockBitcoinDetails = { + btc_lock_amount: MOCK_BTC_LOCK_AMOUNT, + btc_network_fee: 5000, + xmr_receive_amount: 7_000_000_000_000, // 7 XMR in piconeros + monero_receive_pool: MOCK_RECEIVE_POOL, + swap_id: MOCK_SWAP_ID, + btc_amnesty_amount: MOCK_BTC_AMNESTY_AMOUNT, + has_full_refund_signature: false, +}; + +// Full refund version (no amnesty) +const MOCK_LOCK_BITCOIN_DETAILS_FULL: LockBitcoinDetails = { + btc_lock_amount: MOCK_BTC_LOCK_AMOUNT, + btc_network_fee: 5000, + xmr_receive_amount: 7_000_000_000_000, // 7 XMR in piconeros + monero_receive_pool: MOCK_RECEIVE_POOL, + swap_id: MOCK_SWAP_ID, + btc_amnesty_amount: 0, + has_full_refund_signature: true, +}; + +const PARTIAL_REFUND_SCENARIOS: MockScenario[] = [ + "partialRefundWithAmnesty", + "partialRefundWithBurn", + "partialRefundWithBurnAndFinalAmnesty", +]; + +export function isPartialRefundScenario(scenario: MockScenario): boolean { + return PARTIAL_REFUND_SCENARIOS.includes(scenario); +} + +export function getMockLockBitcoinApproval(scenario: MockScenario | null): ApprovalRequest { + const isPartial = scenario !== null && isPartialRefundScenario(scenario); + return { + request_id: "00000000-0000-0000-0000-000000000001", + request: { + type: "LockBitcoin", + content: isPartial ? MOCK_LOCK_BITCOIN_DETAILS_PARTIAL : MOCK_LOCK_BITCOIN_DETAILS_FULL, + }, + request_status: { + state: "Pending", + content: { + expiration_ts: Number.MAX_SAFE_INTEGER, + }, + }, + }; +} diff --git a/src-gui/src/models/storeModel.ts b/src-gui/src/models/storeModel.ts index 56e14588be..9065265e8d 100644 --- a/src-gui/src/models/storeModel.ts +++ b/src-gui/src/models/storeModel.ts @@ -11,4 +11,6 @@ export interface SwapSlice { state: SwapState | null; logs: CliLog[]; spawnType: SwapSpawnType | null; + /** DEV ONLY: When true, prevents Tauri calls in the swap progress listener */ + _mockOnlyDisableTauriCallsOnSwapProgress: boolean; } diff --git a/src-gui/src/renderer/background.ts b/src-gui/src/renderer/background.ts index bd234e18d0..aebfa073a4 100644 --- a/src-gui/src/renderer/background.ts +++ b/src-gui/src/renderer/background.ts @@ -118,7 +118,10 @@ listen(TAURI_UNIFIED_EVENT_CHANNEL_NAME, (event) => { switch (channelName) { case "SwapProgress": - store.dispatch(swapProgressEventReceived(eventData)); + // Skip when mocking is enabled (DEV only) - mock dispatches bypass this listener + if (!store.getState().swap._mockOnlyDisableTauriCallsOnSwapProgress) { + store.dispatch(swapProgressEventReceived(eventData)); + } break; case "CliLog": diff --git a/src-gui/src/renderer/components/modal/swap/pages/MockSwapControls.tsx b/src-gui/src/renderer/components/modal/swap/pages/MockSwapControls.tsx index 4921a10f60..366388ba77 100644 --- a/src-gui/src/renderer/components/modal/swap/pages/MockSwapControls.tsx +++ b/src-gui/src/renderer/components/modal/swap/pages/MockSwapControls.tsx @@ -1,6 +1,7 @@ import { useState } from "react"; import { Box, + Button, IconButton, MenuItem, Paper, @@ -10,60 +11,70 @@ import { } from "@mui/material"; import ChevronLeftIcon from "@mui/icons-material/ChevronLeft"; import ChevronRightIcon from "@mui/icons-material/ChevronRight"; -import { scenarios, MockScenario, MOCK_SWAP_ID } from "dev/mockSwapEvents"; -import { SwapState } from "models/storeModel"; - -function buildMockState(scenario: MockScenario, index: number): SwapState { - const events = scenarios[scenario]; - return { - curr: events[index], - prev: index > 0 ? events[index - 1] : null, - swapId: MOCK_SWAP_ID, - }; -} - -interface Props { - onMockStateChange: (state: SwapState | null) => void; -} +import { scenarios, MockScenario, MOCK_SWAP_ID, getMockLockBitcoinApproval } from "dev/mockSwapEvents"; +import { useAppDispatch } from "store/hooks"; +import { approvalEventReceived } from "store/features/rpcSlice"; +import { + swapProgressEventReceived, + swapReset, + setMockOnlyDisableTauriCallsOnSwapProgress, +} from "store/features/swapSlice"; -export default function MockSwapControls({ onMockStateChange }: Props) { +export default function MockSwapControls() { + const dispatch = useAppDispatch(); const [scenario, setScenario] = useState(null); const [index, setIndex] = useState(0); const enabled = scenario !== null; const total = scenario ? scenarios[scenario].length : 0; + const dispatchMockState = (mockScenario: MockScenario, eventIndex: number) => { + const event = scenarios[mockScenario][eventIndex]; + dispatch( + swapProgressEventReceived({ + swap_id: MOCK_SWAP_ID, + event, + }), + ); + }; + + const handleMockConfirmation = () => { + dispatch(approvalEventReceived(getMockLockBitcoinApproval(scenario))); + }; + const handleToggle = (checked: boolean) => { if (checked) { const firstScenario = Object.keys(scenarios)[0] as MockScenario; setScenario(firstScenario); setIndex(0); - onMockStateChange(buildMockState(firstScenario, 0)); + dispatch(setMockOnlyDisableTauriCallsOnSwapProgress(true)); + dispatchMockState(firstScenario, 0); } else { setScenario(null); setIndex(0); - onMockStateChange(null); + dispatch(setMockOnlyDisableTauriCallsOnSwapProgress(false)); + dispatch(swapReset()); } }; const handleScenarioChange = (newScenario: MockScenario) => { setScenario(newScenario); setIndex(0); - onMockStateChange(buildMockState(newScenario, 0)); + dispatchMockState(newScenario, 0); }; const prev = () => { if (!scenario || index === 0) return; const newIndex = index - 1; setIndex(newIndex); - onMockStateChange(buildMockState(scenario, newIndex)); + dispatchMockState(scenario, newIndex); }; const next = () => { if (!scenario || index >= total - 1) return; const newIndex = index + 1; setIndex(newIndex); - onMockStateChange(buildMockState(scenario, newIndex)); + dispatchMockState(scenario, newIndex); }; const currentStateName = scenario ? scenarios[scenario][index].type : null; @@ -119,6 +130,9 @@ export default function MockSwapControls({ onMockStateChange }: Props) { )} + ); diff --git a/src-gui/src/renderer/components/pages/swap/swap/SwapWidget.tsx b/src-gui/src/renderer/components/pages/swap/swap/SwapWidget.tsx index 2340d8424d..f166d22ded 100644 --- a/src-gui/src/renderer/components/pages/swap/swap/SwapWidget.tsx +++ b/src-gui/src/renderer/components/pages/swap/swap/SwapWidget.tsx @@ -8,15 +8,11 @@ import SwapStatusAlert from "renderer/components/alert/SwapStatusAlert/SwapStatu import DebugPageSwitchBadge from "renderer/components/modal/swap/pages/DebugPageSwitchBadge"; import DebugPage from "renderer/components/modal/swap/pages/DebugPage"; import MockSwapControls from "renderer/components/modal/swap/pages/MockSwapControls"; -import { SwapState } from "models/storeModel"; export default function SwapWidget() { const swapState = useAppSelector((state) => state.swap.state); const swapInfo = useActiveSwapInfo(); const [debug, setDebug] = useState(false); - const [mockState, setMockState] = useState(null); - - const displayState = mockState ?? swapState; return ( )} - {import.meta.env.DEV && } + {import.meta.env.DEV && } - + - {displayState !== null && ( + {swapState !== null && ( <> - + state.rates.xmrBtcRate); + + // Calculate markup compared to market rate + const makerRate = satsToBtc(btc_lock_amount) / piconerosToXmr(Number(xmr_receive_amount)); + const markupPercent = xmrBtcRate != null ? getMarkup(makerRate, xmrBtcRate) : null; + + // Calculate refund percentages + const guaranteedRefundPercent = ((btc_lock_amount - btc_amnesty_amount) / btc_lock_amount) * 100; + const depositPercent = (btc_amnesty_amount / btc_lock_amount) * 100; + const hasDeposit = btc_amnesty_amount > 0; + return ( @@ -128,6 +140,77 @@ export default function SwapSetupInflightPage({ + {/* Info section: Rate and Refund details */} + + {markupPercent != null && ( + <> + + Rate: {Math.abs(markupPercent).toFixed(1)}% {markupPercent >= 0 ? "above" : "below"} market + + + + )} + {hasDeposit ? ( + + theme.palette.success.main + "15", + borderRadius: 1, + borderLeft: 3, + borderColor: "success.main", + }} + > + + {guaranteedRefundPercent.toFixed(0)}% guaranteed refund + + + theme.palette.info.main + "15", + borderRadius: 1, + borderLeft: 3, + borderColor: "info.main", + }} + > + + {depositPercent.toFixed(0)}% anti-spam deposit + + + └ Usually returned; maker may lock for abuse + + + + ) : ( + theme.palette.success.main + "15", + borderRadius: 1, + borderLeft: 3, + borderColor: "success.main", + }} + > + + Full refund if swap fails (guaranteed) + + + )} + + - ({ - color: theme.palette.text.primary, - })} - > - ({guaranteedRefundPercentage}% refund guaranteed) - {/* Network fee box attached to the bottom */} diff --git a/src-gui/src/renderer/components/pages/swap/swap/init/deposit_and_choose_offer/MakerOfferItem.tsx b/src-gui/src/renderer/components/pages/swap/swap/init/deposit_and_choose_offer/MakerOfferItem.tsx index a7a80ec201..1be09b9aa7 100644 --- a/src-gui/src/renderer/components/pages/swap/swap/init/deposit_and_choose_offer/MakerOfferItem.tsx +++ b/src-gui/src/renderer/components/pages/swap/swap/init/deposit_and_choose_offer/MakerOfferItem.tsx @@ -1,6 +1,6 @@ import { Box, Chip, Divider, Paper, Tooltip, Typography } from "@mui/material"; import Jdenticon from "renderer/components/other/Jdenticon"; -import { QuoteWithAddress } from "models/tauriModel"; +import { QuoteWithAddress, RefundPolicyWire } from "models/tauriModel"; import { MoneroSatsExchangeRate, MoneroSatsMarkup, @@ -11,6 +11,13 @@ import { resolveApproval } from "renderer/rpc"; import { isMakerVersionOutdated } from "utils/multiAddrUtils"; import WarningIcon from "@mui/icons-material/Warning"; +function getRefundPercentage(policy: RefundPolicyWire): number { + if (policy.type === "FullRefund") { + return 100; + } + return policy.content.taker_refund_ratio * 100; +} + export default function MakerOfferItem({ quoteWithAddress, requestId, @@ -130,6 +137,17 @@ export default function MakerOfferItem({ size="small" /> + + + {isMakerVersionOutdated(version) ? ( ( } export async function refreshApprovals(): Promise { + // Skip when mocking is enabled (DEV only) + if (store.getState().swap._mockOnlyDisableTauriCallsOnSwapProgress) { + return; + } + const response = await invokeNoArgs( "get_pending_approvals", ); diff --git a/src-gui/src/store/features/swapSlice.ts b/src-gui/src/store/features/swapSlice.ts index d2acec5c7b..6bf863b53b 100644 --- a/src-gui/src/store/features/swapSlice.ts +++ b/src-gui/src/store/features/swapSlice.ts @@ -8,6 +8,8 @@ const initialState: SwapSlice = { // TODO: Remove this and replace logic entirely with Tauri events spawnType: null, + + _mockOnlyDisableTauriCallsOnSwapProgress: false, }; export const swapSlice = createSlice({ @@ -37,9 +39,19 @@ export const swapSlice = createSlice({ swapReset() { return initialState; }, + setMockOnlyDisableTauriCallsOnSwapProgress( + swap, + action: PayloadAction, + ) { + swap._mockOnlyDisableTauriCallsOnSwapProgress = action.payload; + }, }, }); -export const { swapReset, swapProgressEventReceived } = swapSlice.actions; +export const { + swapReset, + swapProgressEventReceived, + setMockOnlyDisableTauriCallsOnSwapProgress, +} = swapSlice.actions; export default swapSlice.reducer; diff --git a/src-gui/src/store/middleware/storeListener.ts b/src-gui/src/store/middleware/storeListener.ts index aff8524507..38fb9dfefe 100644 --- a/src-gui/src/store/middleware/storeListener.ts +++ b/src-gui/src/store/middleware/storeListener.ts @@ -159,6 +159,11 @@ export function createMainListeners() { listener.startListening({ actionCreator: swapProgressEventReceived, effect: async (action) => { + // Skip Tauri calls when mocking is enabled (DEV only) + if (store.getState().swap._mockOnlyDisableTauriCallsOnSwapProgress) { + return; + } + if (action.payload.event.type === "Released") { logger.info("Swap released, updating bitcoin balance..."); await checkBitcoinBalance(); From 3e65462b10f43dcb83c2709c91bebfb7e035b93c Mon Sep 17 00:00:00 2001 From: einliterflasche Date: Mon, 12 Jan 2026 16:48:46 +0100 Subject: [PATCH 088/113] wait for tx refund confirmation in refund command --- swap/src/cli/cancel_and_refund.rs | 26 ++++++++++++++++++++------ 1 file changed, 20 insertions(+), 6 deletions(-) diff --git a/swap/src/cli/cancel_and_refund.rs b/swap/src/cli/cancel_and_refund.rs index 26f2f5e93e..58722febcc 100644 --- a/swap/src/cli/cancel_and_refund.rs +++ b/swap/src/cli/cancel_and_refund.rs @@ -244,16 +244,30 @@ pub async fn refund( .ensure_broadcasted(refund_tx, &refund_type.to_string()) .await { - Ok(_) => { - let state = match refund_type { - RefundType::Full => BobState::BtcRefundPublished(state6), - RefundType::Partial { .. } => BobState::BtcPartialRefundPublished(state6), + Ok((_txid, subscription)) => { + // First save the "published" state + let published_state = match &refund_type { + RefundType::Full => BobState::BtcRefundPublished(state6.clone()), + RefundType::Partial { .. } => BobState::BtcPartialRefundPublished(state6.clone()), }; - db.insert_latest_state(swap_id, state.clone().into()) + db.insert_latest_state(swap_id, published_state.into()) + .await?; + + // Wait for the transaction to be confirmed + tracing::info!("Waiting for refund transaction to be confirmed..."); + subscription.wait_until_final().await?; + + // Now save and return the confirmed state + let confirmed_state = match refund_type { + RefundType::Full => BobState::BtcRefunded(state6), + RefundType::Partial { .. } => BobState::BtcPartiallyRefunded(state6), + }; + + db.insert_latest_state(swap_id, confirmed_state.clone().into()) .await?; - Ok(state) + Ok(confirmed_state) } // If we fail to submit the refund transaction it can have one of two reasons: From 4f9895f5f08b14d40b818c5366aeb6e569d1dcfe Mon Sep 17 00:00:00 2001 From: einliterflasche Date: Mon, 12 Jan 2026 17:28:01 +0100 Subject: [PATCH 089/113] gui: small react refactors --- .../alert/SwapStatusAlert/SwapStatusAlert.tsx | 1 + .../components/inputs/NumberInput.tsx | 19 +++---- .../modal/donation-tip/DonationTipDialog.tsx | 11 ++-- .../components/modal/feedback/useFeedback.ts | 55 ++++++++----------- .../seed-selection/SeedSelectionDialog.tsx | 35 ++++++------ .../components/other/ValidatedTextField.tsx | 10 ++-- .../pages/monero/SendTransactionModal.tsx | 2 +- .../in_progress/SwapSetupInflightPage.tsx | 5 +- .../MakerDiscoveryStatus.tsx | 20 ++++--- 9 files changed, 76 insertions(+), 82 deletions(-) diff --git a/src-gui/src/renderer/components/alert/SwapStatusAlert/SwapStatusAlert.tsx b/src-gui/src/renderer/components/alert/SwapStatusAlert/SwapStatusAlert.tsx index 2b4e003a8b..22a17dbded 100644 --- a/src-gui/src/renderer/components/alert/SwapStatusAlert/SwapStatusAlert.tsx +++ b/src-gui/src/renderer/components/alert/SwapStatusAlert/SwapStatusAlert.tsx @@ -266,6 +266,7 @@ export function StateAlert({ case BobStateName.CancelTimelockExpired: // Even if the refund transactions have been published, it cannot be // guaranteed that they will be confirmed in time + // falls through case BobStateName.BtcCancelled: case BobStateName.BtcRefundPublished: case BobStateName.BtcPartialRefundPublished: diff --git a/src-gui/src/renderer/components/inputs/NumberInput.tsx b/src-gui/src/renderer/components/inputs/NumberInput.tsx index 4fa2f18158..8909417bf4 100644 --- a/src-gui/src/renderer/components/inputs/NumberInput.tsx +++ b/src-gui/src/renderer/components/inputs/NumberInput.tsx @@ -42,10 +42,15 @@ export default function NumberInput({ return 0; }; - const [userPrecision, setUserPrecision] = useState(() => - getDecimalPrecision(step), - ); // Track user's decimal precision - const [minPrecision, setMinPrecision] = useState(3); + // minPrecision is purely derived from step - no state needed + const minPrecision = getDecimalPrecision(step); + + // Track user's decimal precision, reset when step changes + const [userPrecision, setUserPrecision] = useState(() => minPrecision); + + // eslint-disable-next-line react-hooks/set-state-in-effect -- reset state when prop changes + useEffect(() => setUserPrecision(minPrecision), [step, minPrecision]); + const appliedPrecision = userPrecision > minPrecision ? userPrecision : minPrecision; @@ -61,12 +66,6 @@ export default function NumberInput({ } }, [placeholder, isFocused, value, onChange]); - // Update precision when step changes - useEffect(() => { - setUserPrecision(getDecimalPrecision(step)); - setMinPrecision(getDecimalPrecision(step)); - }, [step]); - // Measure text width to size input dynamically useEffect(() => { if (measureRef.current) { diff --git a/src-gui/src/renderer/components/modal/donation-tip/DonationTipDialog.tsx b/src-gui/src/renderer/components/modal/donation-tip/DonationTipDialog.tsx index 7e089a157a..2d8bea415d 100644 --- a/src-gui/src/renderer/components/modal/donation-tip/DonationTipDialog.tsx +++ b/src-gui/src/renderer/components/modal/donation-tip/DonationTipDialog.tsx @@ -23,7 +23,7 @@ import { } from "store/hooks"; import ExternalLink from "renderer/components/other/ExternalLink"; import GitHubIcon from "@mui/icons-material/GitHub"; -import { useState, useEffect } from "react"; +import { useState } from "react"; const GITHUB_BOUNTIES_URL = "https://eigenwallet.org/bounties"; @@ -239,15 +239,14 @@ export function GlobalDonationTipDialog() { // "Latch" pattern: once conditions are met, we remember that the dialog should be shown. // This prevents the dialog from closing when the user moves their mouse (which would // break the idle condition). Once triggered, it stays triggered. + // Uses "adjust state during render" pattern instead of useEffect. const [hasTriggered, setHasTriggered] = useState(false); const shouldOpen = hasntSelectedTipYet && isExperiencedUser && isIdle; - useEffect(() => { - if (shouldOpen && !hasTriggered) { - setHasTriggered(true); - } - }, [shouldOpen, hasTriggered]); + if (shouldOpen && !hasTriggered) { + setHasTriggered(true); + } // Show dialog if we've triggered and user hasn't dismissed const open = hasTriggered && !dismissed; diff --git a/src-gui/src/renderer/components/modal/feedback/useFeedback.ts b/src-gui/src/renderer/components/modal/feedback/useFeedback.ts index deec3b9862..56fec91e0c 100644 --- a/src-gui/src/renderer/components/modal/feedback/useFeedback.ts +++ b/src-gui/src/renderer/components/modal/feedback/useFeedback.ts @@ -49,13 +49,12 @@ export function useFeedback() { }); const [logsState, setLogsState] = useState(initialLogsState); - const [isPending, setIsPending] = useState(false); const [error, setError] = useState(null); - const bodyTooLong = inputState.bodyText.length > MAX_FEEDBACK_LENGTH; - + // Fetch swap logs when selection changes useEffect(() => { if (inputState.selectedSwap === null) { + // eslint-disable-next-line react-hooks/set-state-in-effect -- clear when deselected setLogsState((prev) => ({ ...prev, swapLogs: [] })); return; } @@ -76,41 +75,33 @@ export function useFeedback() { }); }, [inputState.selectedSwap, inputState.isSwapLogsRedacted]); + // Fetch/process daemon logs when settings change useEffect(() => { if (!inputState.attachDaemonLogs) { + // eslint-disable-next-line react-hooks/set-state-in-effect -- clear when detached setLogsState((prev) => ({ ...prev, daemonLogs: [] })); return; } - try { - const hashedLogs = store.getState().logs?.state.logs ?? []; - - if (inputState.isDaemonLogsRedacted) { - const logs = hashedLogs.map((h) => h.log); - redactLogs(logs) - .then((redactedLogs) => { - setLogsState((prev) => ({ - ...prev, - daemonLogs: hashLogs(redactedLogs), - })); - setError(null); - }) - .catch((e) => { - logger.error(`Failed to redact daemon logs: ${e}`); - setLogsState((prev) => ({ ...prev, daemonLogs: [] })); - setError(`Failed to redact daemon logs: ${e}`); - }); - } else { - setLogsState((prev) => ({ - ...prev, - daemonLogs: hashedLogs, - })); - setError(null); - } - } catch (e) { - logger.error(`Failed to fetch daemon logs: ${e}`); - setLogsState((prev) => ({ ...prev, daemonLogs: [] })); - setError(`Failed to fetch daemon logs: ${e}`); + const hashedLogs = store.getState().logs?.state.logs ?? []; + + if (inputState.isDaemonLogsRedacted) { + const logs = hashedLogs.map((h) => h.log); + redactLogs(logs) + .then((redactedLogs) => { + setLogsState((prev) => ({ + ...prev, + daemonLogs: hashLogs(redactedLogs), + })); + setError(null); + }) + .catch((e) => { + logger.error(`Failed to redact daemon logs: ${e}`); + setLogsState((prev) => ({ ...prev, daemonLogs: [] })); + setError(`Failed to redact daemon logs: ${e}`); + }); + } else { + setLogsState((prev) => ({ ...prev, daemonLogs: hashedLogs })); } }, [inputState.attachDaemonLogs, inputState.isDaemonLogsRedacted]); diff --git a/src-gui/src/renderer/components/modal/seed-selection/SeedSelectionDialog.tsx b/src-gui/src/renderer/components/modal/seed-selection/SeedSelectionDialog.tsx index 9852eb5a22..a23154ef1c 100644 --- a/src-gui/src/renderer/components/modal/seed-selection/SeedSelectionDialog.tsx +++ b/src-gui/src/renderer/components/modal/seed-selection/SeedSelectionDialog.tsx @@ -20,7 +20,7 @@ import { CardContent, } from "@mui/material"; import NewPasswordInput from "renderer/components/other/NewPasswordInput"; -import { useState, useEffect } from "react"; +import { useState, useEffect, useRef } from "react"; import { usePendingSeedSelectionApproval } from "store/hooks"; import { resolveApproval, checkSeed } from "renderer/rpc"; import { SeedChoice } from "models/tauriModel"; @@ -74,7 +74,7 @@ export default function SeedSelectionDialog() { >("RandomSeed"); const [customSeed, setCustomSeed] = useState(""); const [blockheightInput, setBlockheightInput] = useState(""); - const [isSeedValid, setIsSeedValid] = useState(false); + const [asyncSeedValidation, setAsyncSeedValidation] = useState(false); const [password, setPassword] = useState(""); const [isPasswordValid, setIsPasswordValid] = useState(true); const [walletPath, setWalletPath] = useState(""); @@ -87,26 +87,29 @@ export default function SeedSelectionDialog() { ? approval.request.content.recent_wallets : []; + // Only run async validation when in "FromSeed" mode with content + const needsSeedValidation = selectedOption === "FromSeed" && customSeed.trim(); + useEffect(() => { - if (selectedOption === "FromSeed" && customSeed.trim()) { - checkSeed(customSeed.trim()) - .then((valid) => { - setIsSeedValid(valid); - }) - .catch(() => { - setIsSeedValid(false); - }); - } else { - setIsSeedValid(false); - } - }, [customSeed, selectedOption]); + if (!needsSeedValidation) return; + + checkSeed(customSeed.trim()) + .then(setAsyncSeedValidation) + .catch(() => setAsyncSeedValidation(false)); + }, [customSeed, needsSeedValidation]); + + // isSeedValid is derived: only true if we need validation AND async check passed + const isSeedValid = needsSeedValidation && asyncSeedValidation; - // Auto-select the first recent wallet if available + // Auto-select the first recent wallet if available (one-time initialization) + const hasInitializedRef = useRef(false); useEffect(() => { - if (recentWallets.length > 0) { + if (recentWallets.length > 0 && !hasInitializedRef.current) { + hasInitializedRef.current = true; setSelectedOption("FromWalletPath"); setWalletPath(recentWallets[0]); } + // eslint-disable-next-line react-hooks/exhaustive-deps -- only init once when wallets available }, [recentWallets.length]); const selectWalletFile = async () => { diff --git a/src-gui/src/renderer/components/other/ValidatedTextField.tsx b/src-gui/src/renderer/components/other/ValidatedTextField.tsx index 37a8c9d6ec..ca3573cadd 100644 --- a/src-gui/src/renderer/components/other/ValidatedTextField.tsx +++ b/src-gui/src/renderer/components/other/ValidatedTextField.tsx @@ -1,5 +1,5 @@ import { TextFieldProps, TextField } from "@mui/material"; -import { useState, useEffect, useCallback } from "react"; +import { useState, useCallback, useEffect } from "react"; interface ValidatedTextFieldProps extends Omit { @@ -24,6 +24,10 @@ export default function ValidatedTextField({ }: ValidatedTextFieldProps) { const [inputValue, setInputValue] = useState(value || ""); + // Sync internal state with prop (controlled component pattern) + // eslint-disable-next-line react-hooks/set-state-in-effect -- sync internal state with external prop + useEffect(() => setInputValue(value || ""), [value]); + const handleChange = useCallback( (newValue: string) => { const trimmedValue = newValue.trim(); @@ -38,10 +42,6 @@ export default function ValidatedTextField({ [allowEmpty, isValid, onValidatedChange], ); - useEffect(() => { - setInputValue(value || ""); - }, [value]); - const isError = (allowEmpty && inputValue === "") || (inputValue === "" && noErrorWhenEmpty) ? false diff --git a/src-gui/src/renderer/components/pages/monero/SendTransactionModal.tsx b/src-gui/src/renderer/components/pages/monero/SendTransactionModal.tsx index f589569b6f..1f9dd5422b 100644 --- a/src-gui/src/renderer/components/pages/monero/SendTransactionModal.tsx +++ b/src-gui/src/renderer/components/pages/monero/SendTransactionModal.tsx @@ -27,7 +27,7 @@ export default function SendTransactionModal({ const showSuccess = successResponse !== null; - const handleClose = (_: any, reason: string) => { + const handleClose = (_: unknown, reason: string) => { // We want the user to explicitly close the dialog. // We do not close the dialog upon a backdrop click. if (reason === "backdropClick") { diff --git a/src-gui/src/renderer/components/pages/swap/swap/in_progress/SwapSetupInflightPage.tsx b/src-gui/src/renderer/components/pages/swap/swap/in_progress/SwapSetupInflightPage.tsx index 96afe11cb6..0a5eb4eb25 100644 --- a/src-gui/src/renderer/components/pages/swap/swap/in_progress/SwapSetupInflightPage.tsx +++ b/src-gui/src/renderer/components/pages/swap/swap/in_progress/SwapSetupInflightPage.tsx @@ -29,6 +29,8 @@ export default function SwapSetupInflightPage({ btc_lock_amount, }: TauriSwapProgressEventContent<"SwapSetupInflight">) { const request = useActiveLockBitcoinApprovalRequest(); + // Get market rate for markup calculation (must be called unconditionally) + const xmrBtcRate = useAppSelector((state) => state.rates.xmrBtcRate); const [timeLeft, setTimeLeft] = useState(0); const expirationTs = @@ -79,9 +81,6 @@ export default function SwapSetupInflightPage({ const { btc_network_fee, monero_receive_pool, xmr_receive_amount, btc_amnesty_amount } = request.request.content; - // Get market rate for markup calculation - const xmrBtcRate = useAppSelector((state) => state.rates.xmrBtcRate); - // Calculate markup compared to market rate const makerRate = satsToBtc(btc_lock_amount) / piconerosToXmr(Number(xmr_receive_amount)); const markupPercent = xmrBtcRate != null ? getMarkup(makerRate, xmrBtcRate) : null; diff --git a/src-gui/src/renderer/components/pages/swap/swap/init/deposit_and_choose_offer/MakerDiscoveryStatus.tsx b/src-gui/src/renderer/components/pages/swap/swap/init/deposit_and_choose_offer/MakerDiscoveryStatus.tsx index 6256930101..828da25015 100644 --- a/src-gui/src/renderer/components/pages/swap/swap/init/deposit_and_choose_offer/MakerDiscoveryStatus.tsx +++ b/src-gui/src/renderer/components/pages/swap/swap/init/deposit_and_choose_offer/MakerDiscoveryStatus.tsx @@ -24,7 +24,7 @@ import { Close as CloseIcon, Refresh as RefreshIcon, } from "@mui/icons-material"; -import { useEffect, useState, useMemo } from "react"; +import { useState, useMemo, useEffect } from "react"; import { useAppSelector } from "store/hooks"; import { QuoteStatus, ConnectionStatus } from "models/tauriModel"; import { selectPeers } from "store/selectors"; @@ -48,15 +48,17 @@ export default function MakerDiscoveryStatus() { .filter((p) => p.connection === ConnectionStatus.Connected) .map((p) => p.peer_id); - // Track peers that have ever been connected + // Track peers that have ever been connected (accumulating historical state) useEffect(() => { - if (connectedPeerIds.length > 0) { - setEverConnectedPeers((prev) => { - const updated = new Set(prev); - connectedPeerIds.forEach((id) => updated.add(id)); - return updated; - }); - } + if (connectedPeerIds.length === 0) return; + setEverConnectedPeers((prev) => { + const newIds = connectedPeerIds.filter((id) => !prev.has(id)); + if (newIds.length === 0) return prev; + const updated = new Set(prev); + newIds.forEach((id) => updated.add(id)); + return updated; + }); + // eslint-disable-next-line react-hooks/exhaustive-deps -- peers is the stable source }, [peers]); const quotesInflight = peers.filter( From 677323710f1a7915d913108bfc4c614c2296a442 Mon Sep 17 00:00:00 2001 From: einliterflasche Date: Mon, 19 Jan 2026 14:33:05 +0100 Subject: [PATCH 090/113] undo debugging code --- swap/src/database/sqlite.rs | 27 ++++++++------------------- 1 file changed, 8 insertions(+), 19 deletions(-) diff --git a/swap/src/database/sqlite.rs b/swap/src/database/sqlite.rs index 28a4f2f26d..421863349f 100644 --- a/swap/src/database/sqlite.rs +++ b/swap/src/database/sqlite.rs @@ -411,29 +411,18 @@ impl Database for SqliteDatabase { let result = rows .iter() - .filter_map(|row| { + .map(|row| { let state_str: &str = &row.state; - match serde_json::from_str::(state_str) { - Ok(a) => { - // debugging - if swap_id.to_string().as_str() == "ef5435d3-bb7d-4b3a-a83b-42f2b9f7ca4b" { - tracing::info!("Managed to deserialize swap: {a}") - } - Some(State::from(a)) - } - Err(e) => { - // debugging - if swap_id.to_string().as_str() == "ef5435d3-bb7d-4b3a-a83b-42f2b9f7ca4b" { - tracing::error!(error=%e, "Failed to deserialize swap") - } - None - } - } + let state = match serde_json::from_str::(state_str) { + Ok(a) => Ok(State::from(a)), + Err(e) => Err(e), + }?; + Ok(state) }) - .collect::>(); + .collect::>>(); - Ok(result) + result } async fn insert_buffered_transfer_proof( From 3d22a041d36b116e0ea704c826ba5ab37fa2ae74 Mon Sep 17 00:00:00 2001 From: einliterflasche Date: Mon, 19 Jan 2026 14:33:57 +0100 Subject: [PATCH 091/113] extract earnest deposit chip into component --- .../MakerOfferItem.tsx | 37 +++++++++++++------ 1 file changed, 25 insertions(+), 12 deletions(-) diff --git a/src-gui/src/renderer/components/pages/swap/swap/init/deposit_and_choose_offer/MakerOfferItem.tsx b/src-gui/src/renderer/components/pages/swap/swap/init/deposit_and_choose_offer/MakerOfferItem.tsx index 1be09b9aa7..1cc3e06b36 100644 --- a/src-gui/src/renderer/components/pages/swap/swap/init/deposit_and_choose_offer/MakerOfferItem.tsx +++ b/src-gui/src/renderer/components/pages/swap/swap/init/deposit_and_choose_offer/MakerOfferItem.tsx @@ -1,6 +1,6 @@ import { Box, Chip, Divider, Paper, Tooltip, Typography } from "@mui/material"; import Jdenticon from "renderer/components/other/Jdenticon"; -import { QuoteWithAddress, RefundPolicyWire } from "models/tauriModel"; +import { BidQuote, QuoteWithAddress, RefundPolicyWire } from "models/tauriModel"; import { MoneroSatsExchangeRate, MoneroSatsMarkup, @@ -10,6 +10,7 @@ import PromiseInvokeButton from "renderer/components/PromiseInvokeButton"; import { resolveApproval } from "renderer/rpc"; import { isMakerVersionOutdated } from "utils/multiAddrUtils"; import WarningIcon from "@mui/icons-material/Warning"; +import { RefundPolicy } from "store/features/settingsSlice"; function getRefundPercentage(policy: RefundPolicyWire): number { if (policy.type === "FullRefund") { @@ -137,17 +138,7 @@ export default function MakerOfferItem({ size="small" /> - - - + {EarnestDepositChip(quote)} {isMakerVersionOutdated(version) ? ( ); } + +function EarnestDepositChip(quote: BidQuote) { + const full_refund: boolean = quote.refund_policy.type === "FullRefund" ? true : quote.refund_policy.content.taker_refund_ratio === 1; + // Rounded to 0.001 precision + const earnest_deposit_ratio = Math.round( + (quote.refund_policy.type === "FullRefund" ? 0 : 1 - quote.refund_policy.content?.taker_refund_ratio) + * 1000 + ) / 1000; + const tooltip_text = full_refund ? "100% refund cryptographically guaranteed." : `If the swap is refunded, the maker may choose to freeze ${earnest_deposit_ratio * 100}% of your refund. This is allows them to protect themselves against griefing.`; + const text = full_refund ? "No earnest deposit" : `${earnest_deposit_ratio * 100}% earnest deposit`; + + return + + ; +} + From b9b8e985021953287e851d43143a8b9f7a313b71 Mon Sep 17 00:00:00 2001 From: einliterflasche Date: Mon, 19 Jan 2026 15:17:48 +0100 Subject: [PATCH 092/113] gui: update lanugage in partial refund components --- .../modal/swap/SwapStateStepper.tsx | 2 +- .../swap/done/BitcoinPartialRefundPage.tsx | 53 ++++++++++--------- 2 files changed, 29 insertions(+), 26 deletions(-) diff --git a/src-gui/src/renderer/components/modal/swap/SwapStateStepper.tsx b/src-gui/src/renderer/components/modal/swap/SwapStateStepper.tsx index 609cd0bd5a..b0d7f47322 100644 --- a/src-gui/src/renderer/components/modal/swap/SwapStateStepper.tsx +++ b/src-gui/src/renderer/components/modal/swap/SwapStateStepper.tsx @@ -218,7 +218,7 @@ const RECOVERY_STEP_LABELS: Record< [RecoveryScenario.PARTIAL_REFUND]: [ { label: "Cancelling swap", duration: "~1min" }, { label: "Partial refund", duration: "~30min" }, - { label: "Remaining Bitcoin", duration: "~2min" }, + { label: "Claiming deposit", duration: "~2min" }, ], [RecoveryScenario.COOPERATIVE_REDEEM]: [ { label: "Cancelling swap", duration: "~1min" }, diff --git a/src-gui/src/renderer/components/pages/swap/swap/done/BitcoinPartialRefundPage.tsx b/src-gui/src/renderer/components/pages/swap/swap/done/BitcoinPartialRefundPage.tsx index 628d10c955..5bcd3947ed 100644 --- a/src-gui/src/renderer/components/pages/swap/swap/done/BitcoinPartialRefundPage.tsx +++ b/src-gui/src/renderer/components/pages/swap/swap/done/BitcoinPartialRefundPage.tsx @@ -67,8 +67,8 @@ function PartialRefundPage({ const atRiskPercent = Math.round((btcAmnestyAmount / btcLockAmount) * 100); const mainMessage = confirmed - ? `Refunded the first ${guaranteedPercent}% of your Bitcoin. The maker has a short time window to revoke the remaining ${atRiskPercent}%. Unless they do that we will claim it shortly.` - : `Refunding the first ${guaranteedPercent}% of your Bitcoin. The maker has a short time window to revoke the remaining ${atRiskPercent}%. Unless they do that we will claim it shortly.`; + ? `Refunded the first ${guaranteedPercent}% of your Bitcoin. The maker has a short time window to withhold the earnest deposit of ${atRiskPercent}%. Unless they do that we will claim it shortly.` + : `Refunding the first ${guaranteedPercent}% of your Bitcoin. The maker has a short time window to withhold the earnest deposit of ${atRiskPercent}%. Unless they do that we will claim it shortly.`; const additionalContent = swap ? ( <> @@ -83,9 +83,9 @@ function PartialRefundPage({ {mainMessage} - Patience: We are first claiming the guaranteed {guaranteedPercent}% of the Bitcoin refund. - It is not guaranteed that we can claim the remaining {atRiskPercent}%. - We will be able to claim the remaining Bitcoin shortly unless the market maker decides to revoke it. + Patience: We are first refunding the guaranteed {guaranteedPercent}% of the Bitcoin refund. + It is not guaranteed that we can claim the earnest deposit, which makes up the remaining {atRiskPercent}%. + The maker has a short timeframe to withhold the deposit, after that we can claim it. @@ -128,8 +128,8 @@ function AmnestyPage({ const swap = useActiveSwapInfo(); const mainMessage = confirmed - ? "All your Bitcoin has been refunded. The swap is complete." - : "The remaining Bitcoin is being released to you. Waiting for confirmation."; + ? "All your Bitcoin have been refunded. The swap is complete." + : "The remaingin Bitcoin (earnest deposit) are being released to you. Waiting for confirmation."; const additionalContent = swap ? ( <> @@ -162,7 +162,7 @@ function AmnestyPage({ ); } -// Refund Burn pages - The maker actively burned the remaining Bitcoin (bad outcome) +// Refund Burn pages - The maker actively withheld the remaining Bitcoin (bad outcome) // Note: By default, the user would have received the remaining Bitcoin after a timelock. // If we're in this state, it means the maker actively published TxBurn to revoke it. @@ -209,30 +209,33 @@ function RefundBurnPage({ }) { const atRiskPercent = Math.round((btcAmnestyAmount / btcLockAmount) * 100); - const mainMessage = confirmed - ? "The market maker has revoked your remaining Bitcoin refund." - : "The market maker is revoking your remaining Bitcoin refund."; + const mainMessage = "The market maker is withholding the earnest deposit." return ( {mainMessage} - Refund revoked: The market maker has revoked the remaining {atRiskPercent}% of your Bitcoin refund. - This portion is now lost and we cannot recover it on our own. + Earnest deposit withheld: The market maker has choosen to withhold the remaining {atRiskPercent}% of your Bitcoin refund. + - + Why did this happen? Aborting a swap incurs significant costs on makers. - To prevent spam attacks, they can revoke a previously agreed upon part of the refund. - The maker has exercised this option because they think you are spamming them. + To prevent spam attacks, makers can choose to require an "earnest deposit", + which they can withhold if the swap is aborted. + + + Makers do not have access to the withheld deposit. + The maker you are swapping with has exercised their option to withhold, because they think you are spamming them. - You can appeal. If you did not mean to spam the market maker, contact them through our official - community. The maker can still help you recover the remaining Bitcoin. + You can contact the maker: If you think this was a mistake, you can contact the maker through our official + community channels. + The maker can still release the deposit.
@@ -255,14 +258,14 @@ function RefundBurnPage({
-
+
); } @@ -290,8 +293,8 @@ function FinalAmnestyPage({ const swap = useActiveSwapInfo(); const mainMessage = confirmed - ? "The market maker has granted you final amnesty. The remaining Bitcoin has been recovered." - : "The market maker is granting you final amnesty. Waiting for confirmation."; + ? "The market maker has release the earnest deposit they withheld. The refund is complete." + : "The market maker is releasing the earnest deposit they withheld. Waiting for transaction confirmation."; const additionalContent = swap ? ( <> @@ -306,14 +309,14 @@ function FinalAmnestyPage({ {mainMessage} - Appeal successful: The market maker has decided to - release the remaining Bitcoin to you. All your Bitcoin has now been + Mercy granted: The market maker has decided to + release the earnest deposit, which they previously withheld. All your Bitcoin has now been fully refunded. Date: Mon, 19 Jan 2026 16:04:22 +0100 Subject: [PATCH 093/113] gui: simplify language in PartialRefund TxClaim page --- .../pages/swap/swap/done/BitcoinPartialRefundPage.tsx | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/src-gui/src/renderer/components/pages/swap/swap/done/BitcoinPartialRefundPage.tsx b/src-gui/src/renderer/components/pages/swap/swap/done/BitcoinPartialRefundPage.tsx index 5bcd3947ed..e7ce310e4f 100644 --- a/src-gui/src/renderer/components/pages/swap/swap/done/BitcoinPartialRefundPage.tsx +++ b/src-gui/src/renderer/components/pages/swap/swap/done/BitcoinPartialRefundPage.tsx @@ -129,7 +129,7 @@ function AmnestyPage({ const mainMessage = confirmed ? "All your Bitcoin have been refunded. The swap is complete." - : "The remaingin Bitcoin (earnest deposit) are being released to you. Waiting for confirmation."; + : "The remaining Bitcoin (earnest deposit) are being released to you. Waiting for confirmation."; const additionalContent = swap ? ( <> @@ -141,12 +141,9 @@ function AmnestyPage({ return ( <> - {mainMessage} - {confirmed ? "Complete:" : "Almost there:"} The - remaining Bitcoin from your partial refund{" "} - {confirmed ? "has been" : "is being"} released to you. + {confirmed ? "Complete:" : "Almost there:"}{" "}{mainMessage} From ccf4ca7b3b2a84e6c2cf875dffdfab4eff23a637 Mon Sep 17 00:00:00 2001 From: einliterflasche Date: Tue, 20 Jan 2026 12:46:21 +0100 Subject: [PATCH 094/113] bob: emit new tauri event when waiting for timelock on the remaining bitcoin refund to expire --- bitcoin-wallet/src/primitives.rs | 9 ++++++++ swap/src/cli/api/tauri_bindings.rs | 15 +++++++++++++ swap/src/protocol/bob/swap.rs | 34 +++++++++++++++++++++++++++--- 3 files changed, 55 insertions(+), 3 deletions(-) diff --git a/bitcoin-wallet/src/primitives.rs b/bitcoin-wallet/src/primitives.rs index 2a84cd1008..2318f0aa60 100644 --- a/bitcoin-wallet/src/primitives.rs +++ b/bitcoin-wallet/src/primitives.rs @@ -266,4 +266,13 @@ impl ScriptStatus { pub fn has_been_seen(&self) -> bool { matches!(self, ScriptStatus::InMempool | ScriptStatus::Confirmed(_)) } + + pub fn confirmations(&self) -> u32 { + match self { + ScriptStatus::Unseen => 0, + ScriptStatus::InMempool => 0, + ScriptStatus::Retrying => 0, + ScriptStatus::Confirmed(depth) => depth, + } + } } diff --git a/swap/src/cli/api/tauri_bindings.rs b/swap/src/cli/api/tauri_bindings.rs index 9aa3fc2896..1936bdd23b 100644 --- a/swap/src/cli/api/tauri_bindings.rs +++ b/swap/src/cli/api/tauri_bindings.rs @@ -1124,6 +1124,21 @@ pub enum TauriSwapProgressEvent { #[typeshare(serialized_as = "number")] btc_amnesty_amount: bitcoin::Amount, }, + /// Waiting for the earnest deposit timelock to expire after partial refund confirmed. + WaitingForEarnestDepositTimelockExpiration { + #[typeshare(serialized_as = "string")] + btc_partial_refund_txid: Txid, + #[typeshare(serialized_as = "number")] + btc_lock_amount: bitcoin::Amount, + #[typeshare(serialized_as = "number")] + btc_amnesty_amount: bitcoin::Amount, + /// Total blocks required for timelock (target) + #[typeshare(serialized_as = "number")] + target_blocks: u32, + /// Blocks remaining until expiry + #[typeshare(serialized_as = "number")] + blocks_until_expiry: u32, + }, // BtcAmnesty was confirmed. BtcAmnestyReceived { #[typeshare(serialized_as = "string")] diff --git a/swap/src/protocol/bob/swap.rs b/swap/src/protocol/bob/swap.rs index cba59669c3..83fcb87217 100644 --- a/swap/src/protocol/bob/swap.rs +++ b/swap/src/protocol/bob/swap.rs @@ -1271,17 +1271,45 @@ async fn next_state( "Can't wait for remaining refund timelock because remaining_refund_timelock is missing", )?; + event_emitter.emit_swap_progress_event( + swap_id, + TauriSwapProgressEvent::WaitingForEarnestDepositTimelockExpiration { + btc_partial_refund_txid: tx_partial_refund.txid(), + btc_lock_amount: state.tx_lock.lock_amount(), + btc_amnesty_amount: state.btc_amnesty_amount.unwrap_or(bitcoin::Amount::ZERO), + target_blocks: remaining_refund_timelock.into(), + blocks_until_expiry: remaining_refund_timelock.into(), + }, + ); + let (tx_partial_refund_status, tx_refund_burn_status) = tokio::join!( bitcoin_wallet.subscribe_to(Box::new(tx_partial_refund)), bitcoin_wallet.subscribe_to(Box::new(tx_refund_burn)), ); + // Emit a tauri event everytime the TxPartialRefund status changes so we can + // show an estimate when we will be able to claim the remaining bitcoin + let timelock_expired_future = tx_partial_refund_status.wait_until(|status| { + event_emitter.emit_swap_progress_event( + swap_id, + TauriSwapProgressEvent::WaitingForEarnestDepositTimelockExpiration { + btc_partial_refund_txid: tx_partial_refund.txid(), + btc_lock_amount: state.tx_lock.lock_amount(), + btc_amnesty_amount: state.btc_amnesty_amount.unwrap_or(bitcoin::Amount::ZERO), + target_blocks: remaining_refund_timelock.into(), + blocks_until_expiry: status.confirmations(), + }, + ); + + status.is_confirmed_with(remaining_refund_timelock.0) + }); + select! { // Wait for remaining_refund_timelock confirmations on tx_partial_refund - result = tx_partial_refund_status.wait_until_confirmed_with(remaining_refund_timelock) => { + result = timelock_expired_future => { result?; - tracing::info!("Remaining refund timelock expired, can now publish TxRefundAmnesty"); - BobState::RemainingRefundTimelockExpired(state) + tracing::info!("RemainingBitcoinTimelock expired"); + BobState::BtcRemainingTimelockExpired(state) } // Watch for Alice publishing TxRefundBurn _ = tx_refund_burn_status.wait_until_seen() => { From b8a743799de83f16b8be3a52e8ff074e913719c3 Mon Sep 17 00:00:00 2001 From: einliterflasche Date: Tue, 20 Jan 2026 13:05:20 +0100 Subject: [PATCH 095/113] fix compilation errors --- bitcoin-wallet/src/primitives.rs | 2 +- swap/src/protocol/bob/swap.rs | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/bitcoin-wallet/src/primitives.rs b/bitcoin-wallet/src/primitives.rs index 2318f0aa60..2104d0fa5b 100644 --- a/bitcoin-wallet/src/primitives.rs +++ b/bitcoin-wallet/src/primitives.rs @@ -272,7 +272,7 @@ impl ScriptStatus { ScriptStatus::Unseen => 0, ScriptStatus::InMempool => 0, ScriptStatus::Retrying => 0, - ScriptStatus::Confirmed(depth) => depth, + ScriptStatus::Confirmed(confirmed) => confirmed.confirmations(), } } } diff --git a/swap/src/protocol/bob/swap.rs b/swap/src/protocol/bob/swap.rs index 83fcb87217..576d698b96 100644 --- a/swap/src/protocol/bob/swap.rs +++ b/swap/src/protocol/bob/swap.rs @@ -1283,7 +1283,7 @@ async fn next_state( ); let (tx_partial_refund_status, tx_refund_burn_status) = tokio::join!( - bitcoin_wallet.subscribe_to(Box::new(tx_partial_refund)), + bitcoin_wallet.subscribe_to(Box::new(tx_partial_refund.clone())), bitcoin_wallet.subscribe_to(Box::new(tx_refund_burn)), ); @@ -1308,8 +1308,8 @@ async fn next_state( // Wait for remaining_refund_timelock confirmations on tx_partial_refund result = timelock_expired_future => { result?; - tracing::info!("RemainingBitcoinTimelock expired"); - BobState::BtcRemainingTimelockExpired(state) + tracing::info!("Remaining refund timelock expired, can now publish TxRefundAmnesty"); + BobState::RemainingRefundTimelockExpired(state) } // Watch for Alice publishing TxRefundBurn _ = tx_refund_burn_status.wait_until_seen() => { From 19b86b785c2eeb4091c9469fb0bea4c7e140b9c6 Mon Sep 17 00:00:00 2001 From: einliterflasche Date: Tue, 20 Jan 2026 13:06:33 +0100 Subject: [PATCH 096/113] gui: add new page when waiting for timelock on remaining refund to expire --- .../modal/swap/SwapStateStepper.tsx | 7 ++- .../pages/swap/swap/SwapStatePage.tsx | 6 +++ .../swap/done/BitcoinPartialRefundPage.tsx | 53 ++++++++++++++++++- 3 files changed, 61 insertions(+), 5 deletions(-) diff --git a/src-gui/src/renderer/components/modal/swap/SwapStateStepper.tsx b/src-gui/src/renderer/components/modal/swap/SwapStateStepper.tsx index b0d7f47322..1bae6a3bcc 100644 --- a/src-gui/src/renderer/components/modal/swap/SwapStateStepper.tsx +++ b/src-gui/src/renderer/components/modal/swap/SwapStateStepper.tsx @@ -128,8 +128,7 @@ function getActiveStep(state: SwapState | null): PathStep | null { return [PathType.RECOVERY_PATH, 1, isReleased, RecoveryScenario.PARTIAL_REFUND]; case "BtcPartiallyRefunded": - return [PathType.RECOVERY_PATH, 2, isReleased, RecoveryScenario.PARTIAL_REFUND]; - + case "WaitingForEarnestDepositTimelockExpiration": case "BtcAmnestyPublished": return [PathType.RECOVERY_PATH, 2, isReleased, RecoveryScenario.PARTIAL_REFUND]; @@ -217,8 +216,8 @@ const RECOVERY_STEP_LABELS: Record< ], [RecoveryScenario.PARTIAL_REFUND]: [ { label: "Cancelling swap", duration: "~1min" }, - { label: "Partial refund", duration: "~30min" }, - { label: "Claiming deposit", duration: "~2min" }, + { label: "Partial refund", duration: "~2min" }, + { label: "Claiming deposit", duration: "~30min" }, ], [RecoveryScenario.COOPERATIVE_REDEEM]: [ { label: "Cancelling swap", duration: "~1min" }, diff --git a/src-gui/src/renderer/components/pages/swap/swap/SwapStatePage.tsx b/src-gui/src/renderer/components/pages/swap/swap/SwapStatePage.tsx index a764a473e2..9243ab5aed 100644 --- a/src-gui/src/renderer/components/pages/swap/swap/SwapStatePage.tsx +++ b/src-gui/src/renderer/components/pages/swap/swap/SwapStatePage.tsx @@ -11,6 +11,7 @@ import { import { BitcoinPartialRefundPublished, BitcoinPartiallyRefunded, + WaitingForEarnestDepositTimelockExpirationPage, BitcoinAmnestyPublished, BitcoinAmnestyReceived, BitcoinRefundBurnPublished, @@ -147,6 +148,11 @@ export default function SwapStatePage({ state }: { state: SwapState | null }) { return ; } break; + case "WaitingForEarnestDepositTimelockExpiration": + if (state.curr.type === "WaitingForEarnestDepositTimelockExpiration") { + return ; + } + break; case "BtcAmnestyPublished": if (state.curr.type === "BtcAmnestyPublished") { return ; diff --git a/src-gui/src/renderer/components/pages/swap/swap/done/BitcoinPartialRefundPage.tsx b/src-gui/src/renderer/components/pages/swap/swap/done/BitcoinPartialRefundPage.tsx index e7ce310e4f..488e3c75d7 100644 --- a/src-gui/src/renderer/components/pages/swap/swap/done/BitcoinPartialRefundPage.tsx +++ b/src-gui/src/renderer/components/pages/swap/swap/done/BitcoinPartialRefundPage.tsx @@ -12,8 +12,9 @@ * -> optionally BtcFinalAmnestyPublished -> BtcFinalAmnestyConfirmed (Alice grants final amnesty) */ -import { Alert, Box, Button, DialogContentText, Typography } from "@mui/material"; +import { Alert, Box, Button, DialogContentText, LinearProgress, Typography } from "@mui/material"; import { TauriSwapProgressEventContent } from "models/tauriModelExt"; +import HumanizedBitcoinBlockDuration from "renderer/components/other/HumanizedBitcoinBlockDuration"; import { useActiveSwapInfo } from "store/hooks"; import FeedbackInfoBox from "renderer/components/pages/help/FeedbackInfoBox"; import BitcoinTransactionInfoBox from "renderer/components/pages/swap/swap/components/BitcoinTransactionInfoBox"; @@ -50,6 +51,56 @@ export function BitcoinPartiallyRefunded({ ); } +export function WaitingForEarnestDepositTimelockExpirationPage({ + btc_partial_refund_txid, + btc_lock_amount, + btc_amnesty_amount, + target_blocks, + blocks_until_expiry, +}: TauriSwapProgressEventContent<"WaitingForEarnestDepositTimelockExpiration">) { + const swap = useActiveSwapInfo(); + + const blocksConfirmed = target_blocks - blocks_until_expiry; + const progressPercent = Math.round((blocksConfirmed / target_blocks) * 100); + const atRiskPercent = Math.round((btc_amnesty_amount / btc_lock_amount) * 100); + + const additionalContent = swap ? ( + <>Refund address: {swap.btc_refund_address} + ) : null; + + return ( + <> + + Waiting to claim the earnest deposit ({atRiskPercent}% of your Bitcoin). + The maker can still withhold it during this time. + + + + Timelock progress: {blocksConfirmed} of {target_blocks} blocks confirmed ({progressPercent}%). + {blocks_until_expiry > 0 && ( + <> + {" "}Approximately remaining. + + )} + + + + + + + + ); +} + function PartialRefundPage({ txid, confirmed, From de69009407ef306957f312a4d383f1b082bf60f0 Mon Sep 17 00:00:00 2001 From: einliterflasche Date: Wed, 21 Jan 2026 14:24:39 +0100 Subject: [PATCH 097/113] gui: add WaitingForEarnestDeposit page to mock paths --- src-gui/src/dev/mockSwapEvents.ts | 36 +++++++++++++++ .../modal/swap/SwapStateStepper.tsx | 5 +-- .../swap/done/BitcoinPartialRefundPage.tsx | 44 +++++-------------- 3 files changed, 48 insertions(+), 37 deletions(-) diff --git a/src-gui/src/dev/mockSwapEvents.ts b/src-gui/src/dev/mockSwapEvents.ts index ddc446ebb2..071c408bba 100644 --- a/src-gui/src/dev/mockSwapEvents.ts +++ b/src-gui/src/dev/mockSwapEvents.ts @@ -29,6 +29,9 @@ const MOCK_BTC_REFUND_BURN_TXID = const MOCK_BTC_FINAL_AMNESTY_TXID = "c5d6e7f8a9b0c1d2e3f4a5b6c7d8e9f0a1b2c3d4e5f6a7b8c9d0e1f2a3b4c5d6"; +// Mock timelock blocks for earnest deposit +const EARNEST_DEPOSIT_TARGET_BLOCKS = 3; + // Mock amounts for partial refund scenarios const MOCK_BTC_LOCK_AMOUNT = 50_000_000; // 0.5 BTC const MOCK_BTC_AMNESTY_AMOUNT = 1_000_000; // 0.01 BTC (2% of lock amount) @@ -170,6 +173,17 @@ const partialRefundWithAmnesty: TauriSwapProgressEvent[] = [ btc_amnesty_amount: MOCK_BTC_AMNESTY_AMOUNT, }, }, + // Waiting for earnest deposit timelock: 3/3, 2/3, 1/3, 0/3 blocks remaining + ...Array.from({ length: EARNEST_DEPOSIT_TARGET_BLOCKS + 1 }, (_, i) => ({ + type: "WaitingForEarnestDepositTimelockExpiration" as const, + content: { + btc_partial_refund_txid: MOCK_BTC_PARTIAL_REFUND_TXID, + btc_lock_amount: MOCK_BTC_LOCK_AMOUNT, + btc_amnesty_amount: MOCK_BTC_AMNESTY_AMOUNT, + target_blocks: EARNEST_DEPOSIT_TARGET_BLOCKS, + blocks_until_expiry: EARNEST_DEPOSIT_TARGET_BLOCKS - i, + }, + })), { type: "BtcAmnestyPublished", content: { @@ -210,6 +224,17 @@ const partialRefundWithBurn: TauriSwapProgressEvent[] = [ btc_amnesty_amount: MOCK_BTC_AMNESTY_AMOUNT, }, }, + // Waiting for earnest deposit timelock: 3/3, 2/3, 1/3, 0/3 blocks remaining + ...Array.from({ length: EARNEST_DEPOSIT_TARGET_BLOCKS + 1 }, (_, i) => ({ + type: "WaitingForEarnestDepositTimelockExpiration" as const, + content: { + btc_partial_refund_txid: MOCK_BTC_PARTIAL_REFUND_TXID, + btc_lock_amount: MOCK_BTC_LOCK_AMOUNT, + btc_amnesty_amount: MOCK_BTC_AMNESTY_AMOUNT, + target_blocks: EARNEST_DEPOSIT_TARGET_BLOCKS, + blocks_until_expiry: EARNEST_DEPOSIT_TARGET_BLOCKS - i, + }, + })), { type: "BtcRefundBurnPublished", content: { @@ -250,6 +275,17 @@ const partialRefundWithBurnAndFinalAmnesty: TauriSwapProgressEvent[] = [ btc_amnesty_amount: MOCK_BTC_AMNESTY_AMOUNT, }, }, + // Waiting for earnest deposit timelock: 3/3, 2/3, 1/3, 0/3 blocks remaining + ...Array.from({ length: EARNEST_DEPOSIT_TARGET_BLOCKS + 1 }, (_, i) => ({ + type: "WaitingForEarnestDepositTimelockExpiration" as const, + content: { + btc_partial_refund_txid: MOCK_BTC_PARTIAL_REFUND_TXID, + btc_lock_amount: MOCK_BTC_LOCK_AMOUNT, + btc_amnesty_amount: MOCK_BTC_AMNESTY_AMOUNT, + target_blocks: EARNEST_DEPOSIT_TARGET_BLOCKS, + blocks_until_expiry: EARNEST_DEPOSIT_TARGET_BLOCKS - i, + }, + })), { type: "BtcRefundBurnPublished", content: { diff --git a/src-gui/src/renderer/components/modal/swap/SwapStateStepper.tsx b/src-gui/src/renderer/components/modal/swap/SwapStateStepper.tsx index 1bae6a3bcc..7a9b27a846 100644 --- a/src-gui/src/renderer/components/modal/swap/SwapStateStepper.tsx +++ b/src-gui/src/renderer/components/modal/swap/SwapStateStepper.tsx @@ -23,8 +23,7 @@ type PathStep = [ /** * Determines the current step in the swap process based on the previous and latest state. - * @param prevState - The previous state of the swap process (null if it's the initial state) - * @param latestState - The latest state of the swap process + * @param state - The state of the swap process (null if it's the initial state) * @returns A tuple containing [PathType, activeStep, errorFlag] */ function getActiveStep(state: SwapState | null): PathStep | null { @@ -217,7 +216,7 @@ const RECOVERY_STEP_LABELS: Record< [RecoveryScenario.PARTIAL_REFUND]: [ { label: "Cancelling swap", duration: "~1min" }, { label: "Partial refund", duration: "~2min" }, - { label: "Claiming deposit", duration: "~30min" }, + { label: "Reclaiming deposit", duration: "~30min" }, ], [RecoveryScenario.COOPERATIVE_REDEEM]: [ { label: "Cancelling swap", duration: "~1min" }, diff --git a/src-gui/src/renderer/components/pages/swap/swap/done/BitcoinPartialRefundPage.tsx b/src-gui/src/renderer/components/pages/swap/swap/done/BitcoinPartialRefundPage.tsx index 488e3c75d7..9b3dcc4aa4 100644 --- a/src-gui/src/renderer/components/pages/swap/swap/done/BitcoinPartialRefundPage.tsx +++ b/src-gui/src/renderer/components/pages/swap/swap/done/BitcoinPartialRefundPage.tsx @@ -12,9 +12,8 @@ * -> optionally BtcFinalAmnestyPublished -> BtcFinalAmnestyConfirmed (Alice grants final amnesty) */ -import { Alert, Box, Button, DialogContentText, LinearProgress, Typography } from "@mui/material"; +import { Alert, Box, Button, DialogContentText, Typography } from "@mui/material"; import { TauriSwapProgressEventContent } from "models/tauriModelExt"; -import HumanizedBitcoinBlockDuration from "renderer/components/other/HumanizedBitcoinBlockDuration"; import { useActiveSwapInfo } from "store/hooks"; import FeedbackInfoBox from "renderer/components/pages/help/FeedbackInfoBox"; import BitcoinTransactionInfoBox from "renderer/components/pages/swap/swap/components/BitcoinTransactionInfoBox"; @@ -58,45 +57,22 @@ export function WaitingForEarnestDepositTimelockExpirationPage({ target_blocks, blocks_until_expiry, }: TauriSwapProgressEventContent<"WaitingForEarnestDepositTimelockExpiration">) { - const swap = useActiveSwapInfo(); - const blocksConfirmed = target_blocks - blocks_until_expiry; - const progressPercent = Math.round((blocksConfirmed / target_blocks) * 100); const atRiskPercent = Math.round((btc_amnesty_amount / btc_lock_amount) * 100); - const additionalContent = swap ? ( - <>Refund address: {swap.btc_refund_address} - ) : null; - return ( <> - + Waiting to claim the earnest deposit ({atRiskPercent}% of your Bitcoin). - The maker can still withhold it during this time. + The timelock of {target_blocks} Bitcoin blocks needs to expire first. + The maker can choose to withhold it during this time. - - - Timelock progress: {blocksConfirmed} of {target_blocks} blocks confirmed ({progressPercent}%). - {blocks_until_expiry > 0 && ( - <> - {" "}Approximately remaining. - - )} - - - - - - + ); } From 459376150de209fc7dc5d47b015c2d73045f56ba Mon Sep 17 00:00:00 2001 From: einliterflasche Date: Sun, 25 Jan 2026 19:58:11 +0100 Subject: [PATCH 098/113] fix compilation warnings --- Cargo.lock | 1355 ++++++++--------- Cargo.toml | 13 +- monero-oxide-ext/src/lib.rs | 8 + monero-sys/build.rs | 18 +- monero-tests/Cargo.toml | 7 +- monero-tests/tests/subaddresses.rs | 8 +- src-tauri/src/commands.rs | 3 +- swap-controller/src/main.rs | 3 +- swap-core/src/monero/primitives.rs | 1 - swap-machine/src/alice/mod.rs | 22 +- swap-machine/src/lib.rs | 1 - swap-orchestrator/tests/spec.rs | 2 + swap-p2p/Cargo.toml | 7 + swap-p2p/examples/fetch_quotes.rs | 18 +- swap-p2p/src/protocols/swap_setup/alice.rs | 2 +- swap/src/asb/event_loop.rs | 1 - swap/src/asb/rpc/server.rs | 2 +- ...rtial_refund_alice_grants_final_amnesty.rs | 5 +- 18 files changed, 717 insertions(+), 759 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index c74a838e89..fff222ecce 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -50,7 +50,7 @@ version = "0.7.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "891477e0c6a8957309ee5c45a6368af3ae14bb510732d2684ffa19af310920f9" dependencies = [ - "getrandom 0.2.16", + "getrandom 0.2.17", "once_cell", "version_check", ] @@ -106,7 +106,7 @@ dependencies = [ "amplify_derive", "amplify_num", "ascii", - "getrandom 0.2.16", + "getrandom 0.2.17", "getrandom 0.3.4", "wasm-bindgen", ] @@ -274,7 +274,7 @@ dependencies = [ "cfg-if", "derive-deftly", "derive_builder_fork_arti", - "derive_more 2.1.0", + "derive_more 2.1.1", "educe", "fs-mistrust", "futures", @@ -287,7 +287,7 @@ dependencies = [ "rand 0.9.2", "safelog", "serde", - "thiserror 2.0.17", + "thiserror 2.0.18", "time", "tor-async-utils", "tor-basic-utils", @@ -322,27 +322,6 @@ version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d92bec98840b8f03a5ff5413de5293bfcd8bf96467cf5452609f939ec6f5de16" -[[package]] -name = "ashpd" -version = "0.11.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6cbdf310d77fd3aaee6ea2093db7011dc2d35d2eb3481e5607f1f8d942ed99df" -dependencies = [ - "enumflags2", - "futures-channel", - "futures-util", - "rand 0.9.2", - "raw-window-handle", - "serde", - "serde_repr", - "tokio", - "url", - "wayland-backend", - "wayland-client", - "wayland-protocols", - "zbus", -] - [[package]] name = "asn1-rs" version = "0.5.2" @@ -387,7 +366,7 @@ dependencies = [ "nom 7.1.3", "num-traits", "rusticata-macros", - "thiserror 2.0.17", + "thiserror 2.0.18", ] [[package]] @@ -410,7 +389,7 @@ checksum = "965c2d33e53cb6b267e148a4cb0760bc01f4904c1cd4bb4002a085bb016d1490" dependencies = [ "proc-macro2", "quote", - "syn 2.0.111", + "syn 2.0.114", "synstructure 0.13.2", ] @@ -422,7 +401,7 @@ checksum = "3109e49b1e4909e9db6515a30c633684d68cdeaa252f215214cb4fa1a5bfee2c" dependencies = [ "proc-macro2", "quote", - "syn 2.0.111", + "syn 2.0.114", "synstructure 0.13.2", ] @@ -445,7 +424,7 @@ checksum = "7b18050c2cd6fe86c3a76584ef5e0baf286d038cda203eb6223df2cc413565f7" dependencies = [ "proc-macro2", "quote", - "syn 2.0.111", + "syn 2.0.114", ] [[package]] @@ -496,13 +475,12 @@ dependencies = [ [[package]] name = "async-compression" -version = "0.4.36" +version = "0.4.37" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "98ec5f6c2f8bc326c994cb9e241cc257ddaba9afa8555a43cffbb5dd86efaa37" +checksum = "d10e4f991a553474232bc0a31799f6d24b034a84c0971d80d2e2f78b2e576e40" dependencies = [ "compression-codecs", "compression-core", - "futures-core", "futures-io", "pin-project-lite", ] @@ -541,9 +519,9 @@ dependencies = [ [[package]] name = "async-lock" -version = "3.4.1" +version = "3.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5fd03604047cee9b6ce9de9f70c6cd540a0520c813cbd49bae61f33ab80ed1dc" +checksum = "290f7f2596bd5b78a9fec8088ccd89180d7f9f55b94b0576823bbbdc72ee8311" dependencies = [ "event-listener", "event-listener-strategy", @@ -576,7 +554,7 @@ checksum = "3b43422f69d8ff38f95f1b2bb76517c91589a924d1559a0e935d7c8ce0274c11" dependencies = [ "proc-macro2", "quote", - "syn 2.0.111", + "syn 2.0.114", ] [[package]] @@ -597,28 +575,6 @@ dependencies = [ "windows-sys 0.61.2", ] -[[package]] -name = "async-stream" -version = "0.3.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0b5a71a6f37880a80d1d7f19efd781e4b5de42c88f0722cc13bcb6cc2cfe8476" -dependencies = [ - "async-stream-impl", - "futures-core", - "pin-project-lite", -] - -[[package]] -name = "async-stream-impl" -version = "0.3.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c7c24de15d275a1ecfd47a380fb4d5ec9bfe0933f309ed5e705b775596a3574d" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.111", -] - [[package]] name = "async-task" version = "4.7.1" @@ -633,7 +589,7 @@ checksum = "9035ad2d096bed7955a320ee7e2230574d28fd3c3a0f186cbea1ff3c7eed5dbb" dependencies = [ "proc-macro2", "quote", - "syn 2.0.111", + "syn 2.0.114", ] [[package]] @@ -760,9 +716,9 @@ checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8" [[package]] name = "aws-lc-rs" -version = "1.15.1" +version = "1.15.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6b5ce75405893cd713f9ab8e297d8e438f624dde7d706108285f7e17a25a180f" +checksum = "7b7b6141e96a8c160799cc2d5adecd5cbbe5054cb8c7c4af53da0f83bb7ad256" dependencies = [ "aws-lc-sys", "zeroize", @@ -770,9 +726,9 @@ dependencies = [ [[package]] name = "aws-lc-sys" -version = "0.34.0" +version = "0.37.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "179c3777a8b5e70e90ea426114ffc565b2c1a9f82f6c4a0c5a34aa6ef5e781b6" +checksum = "5c34dda4df7017c8db52132f0f8a2e0f8161649d15723ed63fc00c82d0f2081a" dependencies = [ "cc", "cmake", @@ -782,9 +738,9 @@ dependencies = [ [[package]] name = "axum" -version = "0.8.7" +version = "0.8.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5b098575ebe77cb6d14fc7f32749631a6e44edbef6b796f89b020e99ba20d425" +checksum = "8b52af3cb4058c895d37317bb27508dccc8e5f2d39454016b297bf4a400597b8" dependencies = [ "axum-core", "axum-macros", @@ -816,9 +772,9 @@ dependencies = [ [[package]] name = "axum-core" -version = "0.5.5" +version = "0.5.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "59446ce19cd142f8833f856eb31f3eb097812d1479ab224f54d72428ca21ea22" +checksum = "08c78f31d7b1291f7ee735c1c6780ccde7785daae9a9206026862dab7d8792d1" dependencies = [ "bytes", "futures-core", @@ -841,7 +797,7 @@ checksum = "604fde5e028fea851ce1d8570bbdc034bec850d157f7569d10f347d06808c05c" dependencies = [ "proc-macro2", "quote", - "syn 2.0.111", + "syn 2.0.114", ] [[package]] @@ -851,7 +807,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b62ddb9cb1ec0a098ad4bbf9344d0713fa193ae1a80af55febcff2627b6a00c1" dependencies = [ "futures-core", - "getrandom 0.2.16", + "getrandom 0.2.17", "instant", "pin-project-lite", "rand 0.8.5", @@ -916,9 +872,9 @@ checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" [[package]] name = "base64ct" -version = "1.8.1" +version = "1.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0e050f626429857a27ddccb31e0aca21356bfa709c04041aefddac081a8f068a" +checksum = "2af50177e190e07a26ab74f8b1efbfe2ef87da2116221318cb1c2e82baf7de06" [[package]] name = "bdk" @@ -930,7 +886,7 @@ dependencies = [ "bdk-macros", "bitcoin 0.29.2", "electrum-client 0.12.1", - "getrandom 0.2.16", + "getrandom 0.2.17", "js-sys", "log", "miniscript 9.2.1", @@ -1109,7 +1065,7 @@ dependencies = [ "hmac", "jsonrpc_client", "rand 0.8.5", - "reqwest 0.12.25", + "reqwest 0.12.28", "serde", "serde_json", "sha2", @@ -1163,7 +1119,7 @@ dependencies = [ "futures", "moka", "proptest", - "reqwest 0.12.25", + "reqwest 0.12.28", "rust_decimal", "serde", "serde_json", @@ -1250,7 +1206,7 @@ checksum = "e0b121a9fe0df916e362fb3271088d071159cdf11db0e4182d02152850756eff" dependencies = [ "proc-macro2", "quote", - "syn 2.0.111", + "syn 2.0.114", ] [[package]] @@ -1333,7 +1289,7 @@ dependencies = [ "proc-macro-crate 3.4.0", "proc-macro2", "quote", - "syn 2.0.111", + "syn 2.0.114", ] [[package]] @@ -1379,9 +1335,9 @@ dependencies = [ [[package]] name = "bumpalo" -version = "3.19.0" +version = "3.19.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "46c5e41b57b8bba42a04676d81cb89e9ee8e859a1a66f80a5a72e1cb76b34d43" +checksum = "5dd9dc738b7a8311c7ade152424974d8115f2cdad61e8dab8dac9f2362298510" [[package]] name = "by_address" @@ -1474,9 +1430,9 @@ dependencies = [ [[package]] name = "camino" -version = "1.2.1" +version = "1.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "276a59bf2b2c967788139340c9f0c5b12d7fd6630315c15c217e559de85d2609" +checksum = "e629a66d692cb9ff1a1c664e41771b3dcaf961985a9774c0eb0bd1b51cf60a48" dependencies = [ "serde_core", ] @@ -1506,7 +1462,7 @@ dependencies = [ "semver", "serde", "serde_json", - "thiserror 2.0.17", + "thiserror 2.0.18", ] [[package]] @@ -1516,7 +1472,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "374b7c592d9c00c1f4972ea58390ac6b18cbb6ab79011f3bdc90a0b82ca06b77" dependencies = [ "serde", - "toml 0.9.8", + "toml 0.9.11+spec-1.1.0", ] [[package]] @@ -1536,9 +1492,9 @@ dependencies = [ [[package]] name = "cc" -version = "1.2.49" +version = "1.2.54" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "90583009037521a116abf44494efecd645ba48b6622457080f080b85544e2215" +checksum = "6354c81bbfd62d9cfa9cb3c773c2b7b2a3a482d569de977fd0e961f6e7c00583" dependencies = [ "find-msvc-tools", "jobserver", @@ -1611,9 +1567,9 @@ dependencies = [ [[package]] name = "chrono" -version = "0.4.42" +version = "0.4.43" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "145052bdd345b87320e369255277e3fb5152762ad123a901ef5c262dd38fe8d2" +checksum = "fac4744fb15ae8337dc853fee7fb3f4e48c0fbaa23d0afe49c447b4fab126118" dependencies = [ "iana-time-zone", "js-sys", @@ -1678,9 +1634,9 @@ dependencies = [ [[package]] name = "clap" -version = "4.5.53" +version = "4.5.54" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c9e340e012a1bf4935f5282ed1436d1489548e8f72308207ea5df0e23d2d03f8" +checksum = "c6e6ff9dcd79cff5cd969a17a545d79e84ab086e444102a591e288a8aa3ce394" dependencies = [ "clap_builder", "clap_derive", @@ -1688,9 +1644,9 @@ dependencies = [ [[package]] name = "clap_builder" -version = "4.5.53" +version = "4.5.54" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d76b5d13eaa18c901fd2f7fca939fefe3a0727a953561fefdf3b2922b8569d00" +checksum = "fa42cf4d2b7a41bc8f663a7cab4031ebafa1bf3875705bfaf8466dc60ab52c00" dependencies = [ "anstream", "anstyle", @@ -1707,14 +1663,14 @@ dependencies = [ "heck 0.5.0", "proc-macro2", "quote", - "syn 2.0.111", + "syn 2.0.114", ] [[package]] name = "clap_lex" -version = "0.7.6" +version = "0.7.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a1d728cc89cf3aee9ff92b05e62b19ee65a02b5702cff7d5a377e32c6ae29d8d" +checksum = "c3e64b0cc0439b12df2fa678eae89a1c56a529fd067a9115f7827f1fffd22b32" [[package]] name = "clipboard-win" @@ -1727,18 +1683,18 @@ dependencies = [ [[package]] name = "cmake" -version = "0.1.56" +version = "0.1.57" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b042e5d8a74ae91bb0961acd039822472ec99f8ab0948cbf6d1369588f8be586" +checksum = "75443c44cd6b379beb8c5b45d85d0773baf31cce901fe7bb252f4eff3008ef7d" dependencies = [ "cc", ] [[package]] name = "coarsetime" -version = "0.1.36" +version = "0.1.37" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "91849686042de1b41cd81490edc83afbcb0abe5a9b6f2c4114f23ce8cca1bcf4" +checksum = "e58eb270476aa4fc7843849f8a35063e8743b4dbcdf6dd0f8ea0886980c204c2" dependencies = [ "libc", "wasix", @@ -1764,11 +1720,11 @@ checksum = "b05b61dc5112cbb17e4b6cd61790d9845d13888356391624cbe7e41efeac1e75" [[package]] name = "colored" -version = "3.0.0" +version = "3.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fde0e0ec90c9dfb3b4b1a0891a7dcd0e2bffde2f7efed5fe7c9bb00e5bfb915e" +checksum = "faf9468729b8cbcea668e36183cb69d317348c2e08e994829fb56ebfdfbaac34" dependencies = [ - "windows-sys 0.59.0", + "windows-sys 0.61.2", ] [[package]] @@ -1783,9 +1739,9 @@ dependencies = [ [[package]] name = "comfy-table" -version = "7.2.1" +version = "7.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b03b7db8e0b4b2fdad6c551e634134e99ec000e5c8c3b6856c65e8bbaded7a3b" +checksum = "958c5d6ecf1f214b4c2bbbbf6ab9523a864bd136dcf71a7e8904799acfe1ad47" dependencies = [ "crossterm", "unicode-segmentation", @@ -1799,7 +1755,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3fd9b9dc67f2b3024582ec6d861950f0af0aeaabb8350ccda1f0e51ff8e5895c" dependencies = [ "compose_spec_macros", - "indexmap 2.12.1", + "indexmap 2.13.0", "ipnet", "itoa", "serde", @@ -1816,14 +1772,14 @@ checksum = "b77735bd89be8da01c8d7e61faec5a9ccb0e313cece3c773c6b3ae251b90c7d4" dependencies = [ "proc-macro2", "quote", - "syn 2.0.111", + "syn 2.0.114", ] [[package]] name = "compression-codecs" -version = "0.4.35" +version = "0.4.36" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b0f7ac3e5b97fdce45e8922fb05cae2c37f7bbd63d30dd94821dacfd8f3f2bf2" +checksum = "00828ba6fd27b45a448e57dbfe84f1029d4c9f26b368157e9a448a5f49a2ec2a" dependencies = [ "compression-core", "flate2", @@ -1871,9 +1827,9 @@ dependencies = [ [[package]] name = "console" -version = "0.16.1" +version = "0.16.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b430743a6eb14e9764d4260d4c0d8123087d504eeb9c48f2b2a5e810dd369df4" +checksum = "03e45a4a8926227e4197636ba97a9fc9b00477e9f4bd711395687c5f0734bec4" dependencies = [ "encode_unicode", "libc", @@ -2035,7 +1991,7 @@ dependencies = [ "anes", "cast", "ciborium", - "clap 4.5.53", + "clap 4.5.54", "criterion-plot", "itertools 0.13.0", "num-traits", @@ -2200,7 +2156,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "13b588ba4ac1a99f7f2964d24b3d896ddc6bf847ee3855dbd4366f058cfcd331" dependencies = [ "quote", - "syn 2.0.111", + "syn 2.0.114", ] [[package]] @@ -2210,7 +2166,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "32a2785755761f3ddc1492979ce1e48d2c00d09311c39e4466429188f3dd6501" dependencies = [ "quote", - "syn 2.0.111", + "syn 2.0.114", ] [[package]] @@ -2232,7 +2188,7 @@ dependencies = [ "cuprate-hex", "paste", "ref-cast", - "thiserror 2.0.17", + "thiserror 2.0.18", ] [[package]] @@ -2241,7 +2197,7 @@ version = "0.1.0" source = "git+https://github.com/Cuprate/cuprate.git#27eec55f5b7851a2b36e158065a6be95c091e904" dependencies = [ "bytes", - "thiserror 2.0.17", + "thiserror 2.0.18", ] [[package]] @@ -2279,7 +2235,7 @@ checksum = "f46882e17999c6cc590af592290432be3bce0428cb0d5f8b6715e4dc7b383eb3" dependencies = [ "proc-macro2", "quote", - "syn 2.0.111", + "syn 2.0.114", ] [[package]] @@ -2298,9 +2254,9 @@ dependencies = [ [[package]] name = "cxx" -version = "1.0.190" +version = "1.0.194" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a7620f6cfc4dcca21f2b085b7a890e16c60fd66f560cd69ee60594908dc72ab1" +checksum = "747d8437319e3a2f43d93b341c137927ca70c0f5dabeea7a005a73665e247c7e" dependencies = [ "cc", "cxx-build", @@ -2313,49 +2269,49 @@ dependencies = [ [[package]] name = "cxx-build" -version = "1.0.190" +version = "1.0.194" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7a9bc1a22964ff6a355fbec24cf68266a0ed28f8b84c0864c386474ea3d0e479" +checksum = "b0f4697d190a142477b16aef7da8a99bfdc41e7e8b1687583c0d23a79c7afc1e" dependencies = [ "cc", "codespan-reporting", - "indexmap 2.12.1", + "indexmap 2.13.0", "proc-macro2", "quote", "scratch", - "syn 2.0.111", + "syn 2.0.114", ] [[package]] name = "cxxbridge-cmd" -version = "1.0.190" +version = "1.0.194" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b1f29a879d35f7906e3c9b77d7a1005a6a0787d330c09dfe4ffb5f617728cb44" +checksum = "d0956799fa8678d4c50eed028f2de1c0552ae183c76e976cf7ca8c4e36a7c328" dependencies = [ - "clap 4.5.53", + "clap 4.5.54", "codespan-reporting", - "indexmap 2.12.1", + "indexmap 2.13.0", "proc-macro2", "quote", - "syn 2.0.111", + "syn 2.0.114", ] [[package]] name = "cxxbridge-flags" -version = "1.0.190" +version = "1.0.194" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d67109015f93f683e364085aa6489a5b2118b4a40058482101d699936a7836d6" +checksum = "23384a836ab4f0ad98ace7e3955ad2de39de42378ab487dc28d3990392cb283a" [[package]] name = "cxxbridge-macro" -version = "1.0.190" +version = "1.0.194" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d187e019e7b05a1f3e69a8396b70800ee867aa9fc2ab972761173ccee03742df" +checksum = "e6acc6b5822b9526adfb4fc377b67128fdd60aac757cc4a741a6278603f763cf" dependencies = [ - "indexmap 2.12.1", + "indexmap 2.13.0", "proc-macro2", "quote", - "syn 2.0.111", + "syn 2.0.114", ] [[package]] @@ -2437,7 +2393,7 @@ dependencies = [ "proc-macro2", "quote", "strsim 0.11.1", - "syn 2.0.111", + "syn 2.0.114", ] [[package]] @@ -2451,7 +2407,7 @@ dependencies = [ "proc-macro2", "quote", "strsim 0.11.1", - "syn 2.0.111", + "syn 2.0.114", ] [[package]] @@ -2484,7 +2440,7 @@ checksum = "fc34b93ccb385b40dc71c6fceac4b2ad23662c7eeb248cf10d529b7e055b6ead" dependencies = [ "darling_core 0.20.11", "quote", - "syn 2.0.111", + "syn 2.0.114", ] [[package]] @@ -2495,20 +2451,20 @@ checksum = "d38308df82d1080de0afee5d069fa14b0326a88c14f15c5ccda35b4a6c414c81" dependencies = [ "darling_core 0.21.3", "quote", - "syn 2.0.111", + "syn 2.0.114", ] [[package]] name = "data-encoding" -version = "2.9.0" +version = "2.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2a2330da5de22e8a3cb63252ce2abb30116bf5265e89c0e01bc17015ce30a476" +checksum = "d7a1e2f27636f116493b8b860f5546edb47c8d8f8ea73e1d2a20be88e28d1fea" [[package]] name = "data-encoding-macro" -version = "0.1.18" +version = "0.1.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "47ce6c96ea0102f01122a185683611bd5ac8d99e62bc59dd12e6bda344ee673d" +checksum = "8142a83c17aa9461d637e649271eae18bf2edd00e91f2e105df36c3c16355bdb" dependencies = [ "data-encoding", "data-encoding-macro-internal", @@ -2516,12 +2472,12 @@ dependencies = [ [[package]] name = "data-encoding-macro-internal" -version = "0.1.16" +version = "0.1.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8d162beedaa69905488a8da94f5ac3edb4dd4788b732fadb7bd120b2625c1976" +checksum = "7ab67060fc6b8ef687992d439ca0fa36e7ed17e9a0b16b25b601e8757df720de" dependencies = [ "data-encoding", - "syn 2.0.111", + "syn 2.0.114", ] [[package]] @@ -2610,14 +2566,14 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "337f65eb93d9996551b9442423480eca4532586b337484446eb5138d0cd8fcf0" dependencies = [ "heck 0.5.0", - "indexmap 2.12.1", + "indexmap 2.13.0", "itertools 0.14.0", "proc-macro-crate 3.4.0", "proc-macro2", "quote", "sha3", "strum 0.27.2", - "syn 2.0.111", + "syn 2.0.114", "void", ] @@ -2629,7 +2585,7 @@ checksum = "1e567bd82dcff979e4b03460c307b3cdc9e96fde3d73bed1496d2bc75d9dd62a" dependencies = [ "proc-macro2", "quote", - "syn 2.0.111", + "syn 2.0.114", ] [[package]] @@ -2650,7 +2606,7 @@ dependencies = [ "darling 0.20.11", "proc-macro2", "quote", - "syn 2.0.111", + "syn 2.0.114", ] [[package]] @@ -2681,7 +2637,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ab63b0e2bf4d5928aff72e83a7dace85d7bba5fe12dcc3c5a572d78caffd3f3c" dependencies = [ "derive_builder_core", - "syn 2.0.111", + "syn 2.0.114", ] [[package]] @@ -2704,29 +2660,29 @@ dependencies = [ "proc-macro2", "quote", "rustc_version", - "syn 2.0.111", + "syn 2.0.114", ] [[package]] name = "derive_more" -version = "2.1.0" +version = "2.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "10b768e943bed7bf2cab53df09f4bc34bfd217cdb57d971e769874c9a6710618" +checksum = "d751e9e49156b02b44f9c1815bcb94b984cdcc4396ecc32521c739452808b134" dependencies = [ "derive_more-impl", ] [[package]] name = "derive_more-impl" -version = "2.1.0" +version = "2.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6d286bfdaf75e988b4a78e013ecd79c581e06399ab53fbacd2d916c2f904f30b" +checksum = "799a97264921d8623a957f6c3b9011f3b5492f557bbb7a5a19b7fa6d06ba8dcb" dependencies = [ "convert_case 0.10.0", "proc-macro2", "quote", "rustc_version", - "syn 2.0.111", + "syn 2.0.114", "unicode-xid", ] @@ -2889,16 +2845,7 @@ checksum = "97369cbbc041bc366949bc74d34658d6cda5621039731c6310521892a3a20ae0" dependencies = [ "proc-macro2", "quote", - "syn 2.0.111", -] - -[[package]] -name = "dlib" -version = "0.5.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "330c60081dcc4c72131f8eb70510f1ac07223e5d4163db481a04a0befcffa412" -dependencies = [ - "libloading 0.8.9", + "syn 2.0.114", ] [[package]] @@ -2921,7 +2868,7 @@ checksum = "0fbbb781877580993a8707ec48672673ec7b81eeba04cfd2310bd28c08e47c8f" dependencies = [ "proc-macro2", "quote", - "syn 2.0.111", + "syn 2.0.114", ] [[package]] @@ -2962,9 +2909,9 @@ dependencies = [ [[package]] name = "dtoa" -version = "1.0.10" +version = "1.0.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d6add3b8cff394282be81f3fc1a0605db594ed69890078ca6e2cab1c408bcf04" +checksum = "4c3cf4824e2d5f025c7b531afcb2325364084a16806f6d47fbc1f5fbd9960590" [[package]] name = "dtoa-short" @@ -3088,7 +3035,7 @@ dependencies = [ "byteorder", "libc", "log", - "rustls 0.23.35", + "rustls 0.23.36", "serde", "serde_json", "webpki-roots 0.25.4", @@ -3136,7 +3083,7 @@ dependencies = [ "cc", "memchr", "rustc_version", - "toml 0.9.8", + "toml 0.9.11+spec-1.1.0", "vswhom", "winreg 0.55.0", ] @@ -3183,7 +3130,7 @@ dependencies = [ "heck 0.5.0", "proc-macro2", "quote", - "syn 2.0.111", + "syn 2.0.114", ] [[package]] @@ -3196,7 +3143,7 @@ dependencies = [ "num-traits", "proc-macro2", "quote", - "syn 2.0.111", + "syn 2.0.114", ] [[package]] @@ -3208,7 +3155,7 @@ dependencies = [ "once_cell", "proc-macro2", "quote", - "syn 2.0.111", + "syn 2.0.114", ] [[package]] @@ -3229,7 +3176,7 @@ checksum = "67c78a4d8fdf9953a5c9d458f9efe940fd97a0cab0941c075a813ac594733827" dependencies = [ "proc-macro2", "quote", - "syn 2.0.111", + "syn 2.0.114", ] [[package]] @@ -3250,7 +3197,7 @@ dependencies = [ "darling 0.21.3", "proc-macro2", "quote", - "syn 2.0.111", + "syn 2.0.114", ] [[package]] @@ -3363,7 +3310,7 @@ checksum = "a0aca10fb742cb43f9e7bb8467c91aa9bcb8e3ffbc6a6f7389bb93ffc920577d" dependencies = [ "proc-macro2", "quote", - "syn 2.0.111", + "syn 2.0.114", ] [[package]] @@ -3427,21 +3374,20 @@ dependencies = [ [[package]] name = "filetime" -version = "0.2.26" +version = "0.2.27" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bc0505cd1b6fa6580283f6bdf70a73fcf4aba1184038c90902b92b3dd0df63ed" +checksum = "f98844151eee8917efc50bd9e8318cb963ae8b297431495d3f758616ea5c57db" dependencies = [ "cfg-if", "libc", "libredox", - "windows-sys 0.60.2", ] [[package]] name = "find-msvc-tools" -version = "0.1.5" +version = "0.1.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3a3076410a55c90011c298b04d0cfa770b00fa04e1e3c97d3f6c9de105a03844" +checksum = "8591b0bcc8a98a64310a2fae1bb3e9b8564dd10e381e6e28010fde8e8e8568db" [[package]] name = "fixedbitset" @@ -3451,13 +3397,13 @@ checksum = "1d674e81391d1e1ab681a28d99df07927c6d4aa5b027d7da16ba32d1d21ecd99" [[package]] name = "flate2" -version = "1.1.5" +version = "1.1.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bfe33edd8e85a12a67454e37f8c75e730830d83e313556ab9ebf9ee7fbeb3bfb" +checksum = "b375d6465b98090a5f25b1c7703f3859783755aa9a80433b36e0379a3ec2f369" dependencies = [ "crc32fast", - "libz-rs-sys", "miniz_oxide", + "zlib-rs", ] [[package]] @@ -3522,7 +3468,7 @@ checksum = "1a5c6c585bc94aaf2c7b51dd4c2ba22680844aba4c687be581871a6f518c5742" dependencies = [ "proc-macro2", "quote", - "syn 2.0.111", + "syn 2.0.114", ] [[package]] @@ -3556,7 +3502,7 @@ dependencies = [ "libc", "pwd-grp", "serde", - "thiserror 2.0.17", + "thiserror 2.0.18", "walkdir", ] @@ -3602,7 +3548,7 @@ version = "0.5.1" source = "git+https://github.com/eigenwallet/arti?branch=downgraded_rusqlite_arti_1_8_0#2a5db5823e2a5eb413d8ad433a4d3aba902bac07" dependencies = [ "fslock-arti-fork", - "thiserror 2.0.17", + "thiserror 2.0.18", "winapi", ] @@ -3713,7 +3659,7 @@ checksum = "162ee34ebcb7c64a8abebc059ce0fee27c2262618d7b60ed8faf72fef13c3650" dependencies = [ "proc-macro2", "quote", - "syn 2.0.111", + "syn 2.0.114", ] [[package]] @@ -3733,7 +3679,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a8f2f12607f92c69b12ed746fabf9ca4f5c482cba46679c1a75b874ed7c26adb" dependencies = [ "futures-io", - "rustls 0.23.35", + "rustls 0.23.36", "rustls-pki-types", ] @@ -3927,9 +3873,9 @@ dependencies = [ [[package]] name = "getrandom" -version = "0.2.16" +version = "0.2.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "335ff9f135e4384c8150d6f27c6daed433577f86b4750418338c01a1a2528592" +checksum = "ff2abc00be7fca6ebc474524697ae276ad847ad0a6b3faa4bcb027e9a4614ad0" dependencies = [ "cfg-if", "js-sys", @@ -3961,7 +3907,7 @@ dependencies = [ "proc-macro-error2", "proc-macro2", "quote", - "syn 2.0.111", + "syn 2.0.114", ] [[package]] @@ -4053,7 +3999,7 @@ dependencies = [ "proc-macro-error", "proc-macro2", "quote", - "syn 2.0.111", + "syn 2.0.114", ] [[package]] @@ -4161,7 +4107,7 @@ dependencies = [ "proc-macro-error", "proc-macro2", "quote", - "syn 2.0.111", + "syn 2.0.114", ] [[package]] @@ -4176,7 +4122,7 @@ dependencies = [ "futures-sink", "futures-util", "http 0.2.12", - "indexmap 2.12.1", + "indexmap 2.13.0", "slab", "tokio", "tokio-util", @@ -4185,9 +4131,9 @@ dependencies = [ [[package]] name = "h2" -version = "0.4.12" +version = "0.4.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f3c0b69cfcb4e1b9f1bf2f53f95f766e4661169728ec61cd3fe5a0166f2d1386" +checksum = "2f44da3a8150a6703ed5d34e164b875fd14c2cdab9af1252a9a1020bde2bdc54" dependencies = [ "atomic-waker", "bytes", @@ -4195,7 +4141,7 @@ dependencies = [ "futures-core", "futures-sink", "http 1.4.0", - "indexmap 2.12.1", + "indexmap 2.13.0", "slab", "tokio", "tokio-util", @@ -4548,7 +4494,7 @@ dependencies = [ "bytes", "futures-channel", "futures-core", - "h2 0.4.12", + "h2 0.4.13", "http 1.4.0", "http-body 1.0.1", "httparse", @@ -4571,13 +4517,13 @@ dependencies = [ "hyper 1.8.1", "hyper-util", "log", - "rustls 0.23.35", - "rustls-native-certs 0.8.2", + "rustls 0.23.36", + "rustls-native-certs 0.8.3", "rustls-pki-types", "tokio", "tokio-rustls 0.26.4", "tower-service", - "webpki-roots 1.0.4", + "webpki-roots 1.0.5", ] [[package]] @@ -4611,7 +4557,7 @@ dependencies = [ "libc", "percent-encoding", "pin-project-lite", - "socket2 0.6.1", + "socket2 0.6.2", "system-configuration 0.6.1", "tokio", "tower-layer", @@ -4841,9 +4787,9 @@ dependencies = [ [[package]] name = "indexmap" -version = "2.12.1" +version = "2.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0ad4bb2b565bca0645f4d68c5c9af97fba094e9791da685bf83cb5f3ce74acf2" +checksum = "7714e70437a7dc3ac8eb7e6f8df75fd8eb422675fc7678aff7364301092b1017" dependencies = [ "equivalent", "hashbrown 0.16.1", @@ -4930,9 +4876,9 @@ dependencies = [ [[package]] name = "iri-string" -version = "0.7.9" +version = "0.7.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4f867b9d1d896b67beb18518eda36fdb77a32ea590de864f1325b294a6d14397" +checksum = "c91338f0783edbd6195decb37bae672fd3b165faffb89bf7b9e6942f8b1a731a" dependencies = [ "memchr", "serde", @@ -4983,9 +4929,9 @@ dependencies = [ [[package]] name = "itoa" -version = "1.0.15" +version = "1.0.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4a5f13b858c8d314ee3e8f639011f7ccefe71f97f96e50151fb991f267928e2c" +checksum = "92ecc6618181def0457392ccd0ee51198e065e016d1d527a7ac1b6dc7c1f09d2" [[package]] name = "javascriptcore-rs" @@ -5044,9 +4990,9 @@ dependencies = [ [[package]] name = "js-sys" -version = "0.3.83" +version = "0.3.85" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "464a3709c7f55f1f721e5389aa6ea4e3bc6aba669353300af094b29ffbdde1d8" +checksum = "8c942ebf8e95485ca0d52d97da7c5a2c387d0e7f0ba4c35e93bfcaee045955b3" dependencies = [ "once_cell", "wasm-bindgen", @@ -5092,7 +5038,7 @@ source = "git+https://github.com/delta1/rust-jsonrpc-client.git?rev=3b6081697cd6 dependencies = [ "async-trait", "jsonrpc_client_macro", - "reqwest 0.12.25", + "reqwest 0.12.28", "serde", "serde_json", "url", @@ -5141,7 +5087,7 @@ dependencies = [ "rustc-hash", "serde", "serde_json", - "thiserror 2.0.17", + "thiserror 2.0.18", "tokio", "tower", "tracing", @@ -5160,11 +5106,11 @@ dependencies = [ "hyper-util", "jsonrpsee-core", "jsonrpsee-types", - "rustls 0.23.35", + "rustls 0.23.36", "rustls-platform-verifier", "serde", "serde_json", - "thiserror 2.0.17", + "thiserror 2.0.18", "tokio", "tower", "url", @@ -5180,7 +5126,7 @@ dependencies = [ "proc-macro-crate 3.4.0", "proc-macro2", "quote", - "syn 2.0.111", + "syn 2.0.114", ] [[package]] @@ -5202,7 +5148,7 @@ dependencies = [ "serde", "serde_json", "soketto", - "thiserror 2.0.17", + "thiserror 2.0.18", "tokio", "tokio-stream", "tokio-util", @@ -5219,7 +5165,7 @@ dependencies = [ "http 1.4.0", "serde", "serde_json", - "thiserror 2.0.17", + "thiserror 2.0.18", ] [[package]] @@ -5280,7 +5226,7 @@ checksum = "02cb977175687f33fa4afa0c95c112b987ea1443e5a51c8f8ff27dc618270cc2" dependencies = [ "cssparser", "html5ever", - "indexmap 2.12.1", + "indexmap 2.13.0", "selectors", ] @@ -5313,7 +5259,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6e9ec52138abedcc58dc17a7c6c0c00a2bdb4f3427c7f63fa97fd0d859155caf" dependencies = [ "gtk-sys", - "libloading 0.7.4", + "libloading", "once_cell", ] @@ -5325,9 +5271,9 @@ checksum = "2c4a545a15244c7d945065b5d392b2d2d7f21526fba56ce51467b06ed445e8f7" [[package]] name = "libc" -version = "0.2.178" +version = "0.2.180" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "37c93d8daa9d8a012fd8ab92f088405fb202ea0b6ab73ee2482ae66af4f42091" +checksum = "bcc35a38544a891a5f7c865aca548a982ccb3b8650a5b06d0fd33a10283c56fc" [[package]] name = "libgit2-sys" @@ -5351,16 +5297,6 @@ dependencies = [ "winapi", ] -[[package]] -name = "libloading" -version = "0.8.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d7c4b02199fee7c5d21a5ae7d8cfa79a6ef5bb2fc834d6e9058e89c825efdc55" -dependencies = [ - "cfg-if", - "windows-link 0.2.1", -] - [[package]] name = "liblzma" version = "0.4.5" @@ -5372,9 +5308,9 @@ dependencies = [ [[package]] name = "liblzma-sys" -version = "0.4.4" +version = "0.4.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "01b9596486f6d60c3bbe644c0e1be1aa6ccc472ad630fe8927b456973d7cb736" +checksum = "9f2db66f3268487b5033077f266da6777d057949b8f93c8ad82e441df25e6186" dependencies = [ "cc", "libc", @@ -5383,9 +5319,9 @@ dependencies = [ [[package]] name = "libm" -version = "0.2.15" +version = "0.2.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f9fbbcab51052fe104eb5e5d351cf728d30a5be1fe14d9be8a3b097481fb97de" +checksum = "b6d2cec3eae94f9f509c767b45932f1ada8350c4bdb85af2fcab4a3c14807981" [[package]] name = "libp2p" @@ -5397,7 +5333,7 @@ dependencies = [ "either", "futures", "futures-timer", - "getrandom 0.2.16", + "getrandom 0.2.17", "instant", "libp2p-allow-block-list", "libp2p-connection-limits", @@ -5509,7 +5445,7 @@ dependencies = [ "fnv", "futures", "futures-ticker", - "getrandom 0.2.16", + "getrandom 0.2.17", "hex_fmt", "instant", "libp2p-core", @@ -5566,7 +5502,7 @@ dependencies = [ "ring 0.17.14", "serde", "sha2", - "thiserror 2.0.17", + "thiserror 2.0.18", "tracing", "zeroize", ] @@ -5700,7 +5636,7 @@ dependencies = [ "quinn", "rand 0.8.5", "ring 0.17.14", - "rustls 0.23.35", + "rustls 0.23.36", "socket2 0.5.10", "thiserror 1.0.69", "tokio", @@ -5787,7 +5723,7 @@ dependencies = [ "heck 0.5.0", "proc-macro2", "quote", - "syn 2.0.111", + "syn 2.0.114", ] [[package]] @@ -5838,7 +5774,7 @@ dependencies = [ "libp2p-identity", "rcgen", "ring 0.17.14", - "rustls 0.23.35", + "rustls 0.23.36", "rustls-webpki 0.101.7", "thiserror 1.0.69", "x509-parser 0.16.0", @@ -5920,13 +5856,13 @@ dependencies = [ [[package]] name = "libredox" -version = "0.1.10" +version = "0.1.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "416f7e718bdb06000964960ffa43b4335ad4012ae8b99060261aa4a8088d5ccb" +checksum = "3d0b95e02c851351f877147b7deea7b1afb1df71b63aa5f8270716e0c5720616" dependencies = [ "bitflags 2.10.0", "libc", - "redox_syscall 0.5.18", + "redox_syscall 0.7.0", ] [[package]] @@ -5940,15 +5876,6 @@ dependencies = [ "vcpkg", ] -[[package]] -name = "libz-rs-sys" -version = "0.5.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "15413ef615ad868d4d65dce091cb233b229419c7c0c4bcaa746c0901c49ff39c" -dependencies = [ - "zlib-rs", -] - [[package]] name = "libz-sys" version = "1.1.23" @@ -6055,13 +5982,13 @@ dependencies = [ [[package]] name = "match-lookup" -version = "0.1.1" +version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1265724d8cb29dbbc2b0f06fffb8bf1a8c0cf73a78eede9ba73a4a66c52a981e" +checksum = "757aee279b8bdbb9f9e676796fd459e4207a1f986e87886700abf589f5abf771" dependencies = [ "proc-macro2", "quote", - "syn 1.0.109", + "syn 2.0.114", ] [[package]] @@ -6072,7 +5999,7 @@ checksum = "88a9689d8d44bf9964484516275f5cd4c9b59457a6940c1d5d0ecbb94510a36b" dependencies = [ "proc-macro2", "quote", - "syn 2.0.111", + "syn 2.0.114", ] [[package]] @@ -6240,9 +6167,9 @@ dependencies = [ [[package]] name = "moka" -version = "0.12.11" +version = "0.12.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8261cd88c312e0004c1d51baad2980c66528dfdb2bee62003e643a4d8f86b077" +checksum = "a3dec6bd31b08944e08b58fd99373893a6c17054d6f3ea5006cc894f4f4eee2a" dependencies = [ "async-lock", "crossbeam-channel", @@ -6253,7 +6180,6 @@ dependencies = [ "futures-util", "parking_lot 0.12.5", "portable-atomic", - "rustc_version", "smallvec", "tagptr", "uuid", @@ -6262,20 +6188,20 @@ dependencies = [ [[package]] name = "monero-address" version = "0.1.0" -source = "git+https://github.com/kayabaNerve/monero-oxide.git?branch=rpc-rewrite#1a8662faf352e0b7a7b11e157286c0fdb4a65db4" +source = "git+https://github.com/kayabaNerve/monero-oxide.git#c8be5d3d1287669946a83fbfcb296ce2a8852e47" dependencies = [ "monero-base58", "monero-ed25519", "monero-io", "monero-primitives", - "thiserror 2.0.17", + "thiserror 2.0.18", "zeroize", ] [[package]] name = "monero-base58" version = "0.1.0" -source = "git+https://github.com/kayabaNerve/monero-oxide.git?branch=rpc-rewrite#1a8662faf352e0b7a7b11e157286c0fdb4a65db4" +source = "git+https://github.com/kayabaNerve/monero-oxide.git#c8be5d3d1287669946a83fbfcb296ce2a8852e47" dependencies = [ "monero-primitives", "std-shims", @@ -6284,7 +6210,7 @@ dependencies = [ [[package]] name = "monero-borromean" version = "0.1.0" -source = "git+https://github.com/kayabaNerve/monero-oxide.git?branch=rpc-rewrite#1a8662faf352e0b7a7b11e157286c0fdb4a65db4" +source = "git+https://github.com/kayabaNerve/monero-oxide.git#c8be5d3d1287669946a83fbfcb296ce2a8852e47" dependencies = [ "curve25519-dalek", "monero-ed25519", @@ -6296,7 +6222,7 @@ dependencies = [ [[package]] name = "monero-bulletproofs" version = "0.1.0" -source = "git+https://github.com/kayabaNerve/monero-oxide.git?branch=rpc-rewrite#1a8662faf352e0b7a7b11e157286c0fdb4a65db4" +source = "git+https://github.com/kayabaNerve/monero-oxide.git#c8be5d3d1287669946a83fbfcb296ce2a8852e47" dependencies = [ "curve25519-dalek", "monero-bulletproofs-generators", @@ -6305,14 +6231,14 @@ dependencies = [ "monero-primitives", "rand_core 0.6.4", "std-shims", - "thiserror 2.0.17", + "thiserror 2.0.18", "zeroize", ] [[package]] name = "monero-bulletproofs-generators" version = "0.1.0" -source = "git+https://github.com/kayabaNerve/monero-oxide.git?branch=rpc-rewrite#1a8662faf352e0b7a7b11e157286c0fdb4a65db4" +source = "git+https://github.com/kayabaNerve/monero-oxide.git#c8be5d3d1287669946a83fbfcb296ce2a8852e47" dependencies = [ "curve25519-dalek", "monero-ed25519", @@ -6324,7 +6250,7 @@ dependencies = [ [[package]] name = "monero-clsag" version = "0.1.0" -source = "git+https://github.com/kayabaNerve/monero-oxide.git?branch=rpc-rewrite#1a8662faf352e0b7a7b11e157286c0fdb4a65db4" +source = "git+https://github.com/kayabaNerve/monero-oxide.git#c8be5d3d1287669946a83fbfcb296ce2a8852e47" dependencies = [ "curve25519-dalek", "group", @@ -6334,14 +6260,14 @@ dependencies = [ "rand_core 0.6.4", "std-shims", "subtle", - "thiserror 2.0.17", + "thiserror 2.0.18", "zeroize", ] [[package]] name = "monero-daemon-rpc" version = "0.1.0" -source = "git+https://github.com/kayabaNerve/monero-oxide.git?branch=rpc-rewrite#1a8662faf352e0b7a7b11e157286c0fdb4a65db4" +source = "git+https://github.com/kayabaNerve/monero-oxide.git#c8be5d3d1287669946a83fbfcb296ce2a8852e47" dependencies = [ "hex", "monero-address", @@ -6356,7 +6282,7 @@ dependencies = [ [[package]] name = "monero-ed25519" version = "0.1.0" -source = "git+https://github.com/kayabaNerve/monero-oxide.git?branch=rpc-rewrite#1a8662faf352e0b7a7b11e157286c0fdb4a65db4" +source = "git+https://github.com/kayabaNerve/monero-oxide.git#c8be5d3d1287669946a83fbfcb296ce2a8852e47" dependencies = [ "crypto-bigint", "curve25519-dalek", @@ -6371,7 +6297,7 @@ dependencies = [ [[package]] name = "monero-epee" version = "0.2.0" -source = "git+https://github.com/kayabaNerve/monero-oxide.git?branch=rpc-rewrite#1a8662faf352e0b7a7b11e157286c0fdb4a65db4" +source = "git+https://github.com/kayabaNerve/monero-oxide.git#c8be5d3d1287669946a83fbfcb296ce2a8852e47" [[package]] name = "monero-harness" @@ -6384,7 +6310,7 @@ dependencies = [ "monero-simple-request-rpc", "monero-sys", "rand 0.8.5", - "reqwest 0.12.25", + "reqwest 0.12.28", "testcontainers", "tokio", "tracing", @@ -6394,19 +6320,19 @@ dependencies = [ [[package]] name = "monero-interface" version = "0.1.0" -source = "git+https://github.com/kayabaNerve/monero-oxide.git?branch=rpc-rewrite#1a8662faf352e0b7a7b11e157286c0fdb4a65db4" +source = "git+https://github.com/kayabaNerve/monero-oxide.git#c8be5d3d1287669946a83fbfcb296ce2a8852e47" dependencies = [ "hex", "monero-oxide", "std-shims", - "thiserror 2.0.17", + "thiserror 2.0.18", "zeroize", ] [[package]] name = "monero-io" version = "0.1.0" -source = "git+https://github.com/kayabaNerve/monero-oxide.git?branch=rpc-rewrite#1a8662faf352e0b7a7b11e157286c0fdb4a65db4" +source = "git+https://github.com/kayabaNerve/monero-oxide.git#c8be5d3d1287669946a83fbfcb296ce2a8852e47" dependencies = [ "std-shims", ] @@ -6414,20 +6340,20 @@ dependencies = [ [[package]] name = "monero-mlsag" version = "0.1.0" -source = "git+https://github.com/kayabaNerve/monero-oxide.git?branch=rpc-rewrite#1a8662faf352e0b7a7b11e157286c0fdb4a65db4" +source = "git+https://github.com/kayabaNerve/monero-oxide.git#c8be5d3d1287669946a83fbfcb296ce2a8852e47" dependencies = [ "curve25519-dalek", "monero-ed25519", "monero-io", "std-shims", - "thiserror 2.0.17", + "thiserror 2.0.18", "zeroize", ] [[package]] name = "monero-oxide" version = "0.1.4-alpha" -source = "git+https://github.com/kayabaNerve/monero-oxide.git?branch=rpc-rewrite#1a8662faf352e0b7a7b11e157286c0fdb4a65db4" +source = "git+https://github.com/kayabaNerve/monero-oxide.git#c8be5d3d1287669946a83fbfcb296ce2a8852e47" dependencies = [ "curve25519-dalek", "hex-literal", @@ -6451,7 +6377,7 @@ dependencies = [ "curve25519-dalek-ng", "hex", "monero-address", - "monero-wallet 0.1.0 (git+https://github.com/kayabaNerve/monero-oxide.git?branch=rpc-rewrite)", + "monero-wallet 0.1.0 (git+https://github.com/kayabaNerve/monero-oxide.git)", "serde", "typeshare", ] @@ -6459,7 +6385,7 @@ dependencies = [ [[package]] name = "monero-primitives" version = "0.1.0" -source = "git+https://github.com/kayabaNerve/monero-oxide.git?branch=rpc-rewrite#1a8662faf352e0b7a7b11e157286c0fdb4a65db4" +source = "git+https://github.com/kayabaNerve/monero-oxide.git#c8be5d3d1287669946a83fbfcb296ce2a8852e47" dependencies = [ "sha3", ] @@ -6472,7 +6398,7 @@ dependencies = [ "arti-client", "axum", "chrono", - "clap 4.5.53", + "clap 4.5.54", "crossbeam", "cuprate-epee-encoding", "futures", @@ -6511,7 +6437,7 @@ dependencies = [ [[package]] name = "monero-simple-request-rpc" version = "0.1.0" -source = "git+https://github.com/kayabaNerve/monero-oxide.git?branch=rpc-rewrite#1a8662faf352e0b7a7b11e157286c0fdb4a65db4" +source = "git+https://github.com/kayabaNerve/monero-oxide.git#c8be5d3d1287669946a83fbfcb296ce2a8852e47" dependencies = [ "digest_auth", "hex", @@ -6543,7 +6469,7 @@ dependencies = [ "sqlx", "swap-serde", "tempfile", - "thiserror 2.0.17", + "thiserror 2.0.18", "throttle", "tokio", "tracing", @@ -6565,7 +6491,7 @@ dependencies = [ "monero-oxide-ext", "monero-simple-request-rpc", "monero-sys", - "monero-wallet 0.1.0 (git+https://github.com/kayabaNerve/monero-oxide.git?branch=rpc-rewrite)", + "monero-wallet 0.1.0 (git+https://github.com/kayabaNerve/monero-oxide.git)", "monero-wallet-ng", "testcontainers", "tokio", @@ -6588,7 +6514,7 @@ dependencies = [ "monero-oxide-ext", "monero-simple-request-rpc", "monero-sys", - "monero-wallet 0.1.0 (git+https://github.com/kayabaNerve/monero-oxide.git?branch=rpc-rewrite)", + "monero-wallet 0.1.0 (git+https://github.com/kayabaNerve/monero-oxide.git)", "monero-wallet-ng", "swap-core", "throttle", @@ -6602,7 +6528,7 @@ dependencies = [ [[package]] name = "monero-wallet" version = "0.1.0" -source = "git+https://github.com/kayabaNerve/monero-oxide.git?branch=rpc-rewrite#1a8662faf352e0b7a7b11e157286c0fdb4a65db4" +source = "git+https://github.com/kayabaNerve/monero-oxide.git#c8be5d3d1287669946a83fbfcb296ce2a8852e47" dependencies = [ "curve25519-dalek", "hex", @@ -6616,7 +6542,7 @@ dependencies = [ "rand_distr", "std-shims", "subtle", - "thiserror 2.0.17", + "thiserror 2.0.18", "zeroize", ] @@ -6632,7 +6558,7 @@ dependencies = [ "monero-interface", "monero-oxide", "monero-simple-request-rpc", - "monero-wallet 0.1.0 (git+https://github.com/kayabaNerve/monero-oxide.git?branch=rpc-rewrite)", + "monero-wallet 0.1.0 (git+https://github.com/kayabaNerve/monero-oxide.git)", "serde", "serde_json", "thiserror 1.0.69", @@ -6643,9 +6569,9 @@ dependencies = [ [[package]] name = "moxcms" -version = "0.7.10" +version = "0.7.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "80986bbbcf925ebd3be54c26613d861255284584501595cf418320c078945608" +checksum = "ac9557c559cd6fc9867e122e20d2cbefc9ca29d80d027a8e39310920ed2f0a97" dependencies = [ "num-traits", "pxfm", @@ -6668,7 +6594,7 @@ dependencies = [ "once_cell", "png 0.17.16", "serde", - "thiserror 2.0.17", + "thiserror 2.0.18", "windows-sys 0.60.2", ] @@ -6737,7 +6663,7 @@ dependencies = [ "libc", "log", "openssl", - "openssl-probe", + "openssl-probe 0.1.6", "openssl-sys", "schannel", "security-framework 2.11.1", @@ -6823,17 +6749,17 @@ dependencies = [ "log", "netlink-packet-core", "netlink-sys", - "thiserror 2.0.17", + "thiserror 2.0.18", ] [[package]] name = "netlink-sys" -version = "0.8.7" +version = "0.8.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "16c903aa70590cb93691bf97a767c8d1d6122d2cc9070433deb3bbf36ce8bd23" +checksum = "cd6c30ed10fa69cc491d491b85cc971f6bdeb8e7367b7cde2ee6cc878d583fae" dependencies = [ "bytes", - "futures", + "futures-util", "libc", "log", "tokio", @@ -6875,7 +6801,6 @@ dependencies = [ "cfg-if", "cfg_aliases", "libc", - "memoffset", ] [[package]] @@ -6934,15 +6859,18 @@ dependencies = [ [[package]] name = "notify-types" -version = "2.0.0" +version = "2.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5e0826a989adedc2a244799e823aece04662b66609d96af8dff7ac6df9a8925d" +checksum = "42b8cfee0e339a0337359f3c88165702ac6e600dc01c0cc9579a92d62b08477a" +dependencies = [ + "bitflags 2.10.0", +] [[package]] name = "ntapi" -version = "0.4.1" +version = "0.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e8a3895c6391c39d7fe7ebc444a87eb2991b2a0bc718fdabd071eec617fc68e4" +checksum = "c70f219e21142367c70c0b30c6a9e3a14d55b4d12a204d897fbec83a0363f081" dependencies = [ "winapi", ] @@ -6984,9 +6912,9 @@ dependencies = [ [[package]] name = "num-conv" -version = "0.1.0" +version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "51d515d32fb182ee37cda2ccdcb92950d6a3c2893aa280e540671c2cd0f3b1d9" +checksum = "cf97ec579c3c42f953ef76dbf8d55ac91fb219dde70e49aa4a6b7d74e9919050" [[package]] name = "num-integer" @@ -7047,7 +6975,7 @@ dependencies = [ "proc-macro-crate 3.4.0", "proc-macro2", "quote", - "syn 2.0.111", + "syn 2.0.114", ] [[package]] @@ -7378,7 +7306,7 @@ checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c" dependencies = [ "proc-macro2", "quote", - "syn 2.0.111", + "syn 2.0.114", ] [[package]] @@ -7387,6 +7315,12 @@ version = "0.1.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d05e27ee213611ffe7d6348b942e8f942b37114c00cc03cec254295a4a17852e" +[[package]] +name = "openssl-probe" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7c87def4c32ab89d880effc9e097653c8da5d6ef28e6b539d313baaacfbafcbe" + [[package]] name = "openssl-sys" version = "0.9.111" @@ -7454,7 +7388,7 @@ dependencies = [ "objc2-osa-kit", "serde", "serde_json", - "thiserror 2.0.17", + "thiserror 2.0.18", ] [[package]] @@ -7623,9 +7557,9 @@ checksum = "9b4f627cb1b25917193a259e49bdad08f671f8d9708acfd5fe0a8c1455d87220" [[package]] name = "pest" -version = "2.8.4" +version = "2.8.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cbcfd20a6d4eeba40179f05735784ad32bdaef05ce8e8af05f180d45bb3e7e22" +checksum = "2c9eb05c21a464ea704b53158d358a31e6425db2f63a1a7312268b05fe2b75f7" dependencies = [ "memchr", "ucd-trie", @@ -7633,9 +7567,9 @@ dependencies = [ [[package]] name = "pest_derive" -version = "2.8.4" +version = "2.8.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "51f72981ade67b1ca6adc26ec221be9f463f2b5839c7508998daa17c23d94d7f" +checksum = "68f9dbced329c441fa79d80472764b1a2c7e57123553b8519b36663a2fb234ed" dependencies = [ "pest", "pest_generator", @@ -7643,22 +7577,22 @@ dependencies = [ [[package]] name = "pest_generator" -version = "2.8.4" +version = "2.8.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dee9efd8cdb50d719a80088b76f81aec7c41ed6d522ee750178f83883d271625" +checksum = "3bb96d5051a78f44f43c8f712d8e810adb0ebf923fc9ed2655a7f66f63ba8ee5" dependencies = [ "pest", "pest_meta", "proc-macro2", "quote", - "syn 2.0.111", + "syn 2.0.114", ] [[package]] name = "pest_meta" -version = "2.8.4" +version = "2.8.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bf1d70880e76bdc13ba52eafa6239ce793d85c8e43896507e43dd8984ff05b82" +checksum = "602113b5b5e8621770cfd490cfd90b9f84ab29bd2b0e49ad83eb6d186cef2365" dependencies = [ "pest", "sha2", @@ -7672,7 +7606,7 @@ checksum = "8701b58ea97060d5e5b155d383a69952a60943f0e6dfe30b04c287beb0b27455" dependencies = [ "fixedbitset", "hashbrown 0.15.5", - "indexmap 2.12.1", + "indexmap 2.13.0", ] [[package]] @@ -7800,7 +7734,7 @@ dependencies = [ "phf_shared 0.11.3", "proc-macro2", "quote", - "syn 2.0.111", + "syn 2.0.114", ] [[package]] @@ -7813,7 +7747,7 @@ dependencies = [ "phf_shared 0.13.1", "proc-macro2", "quote", - "syn 2.0.111", + "syn 2.0.114", ] [[package]] @@ -7869,7 +7803,7 @@ checksum = "6e918e4ff8c4549eb882f14b3a4bc8c8bc93de829416eacf579f1207a8fbf861" dependencies = [ "proc-macro2", "quote", - "syn 2.0.111", + "syn 2.0.114", ] [[package]] @@ -7929,8 +7863,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "740ebea15c5d1428f910cd1a5f52cebf8d25006245ed8ade92702f4943d91e07" dependencies = [ "base64 0.22.1", - "indexmap 2.12.1", - "quick-xml 0.38.4", + "indexmap 2.13.0", + "quick-xml", "serde", "time", ] @@ -8028,9 +7962,9 @@ dependencies = [ [[package]] name = "portable-atomic" -version = "1.11.1" +version = "1.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f84267b20a16ea918e43c6a88433c2d54fa145c92a811b5b047ccbe153674483" +checksum = "f89776e4d69bb58bc6993e99ffa1d11f228b839984854c7daeb5d37f87cbe950" [[package]] name = "postage" @@ -8064,9 +7998,9 @@ checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391" [[package]] name = "ppmd-rust" -version = "1.3.0" +version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d558c559f0450f16f2a27a1f017ef38468c1090c9ce63c8e51366232d53717b4" +checksum = "efca4c95a19a79d1c98f791f10aebd5c1363b473244630bb7dbde1dc98455a24" [[package]] name = "ppv-lite86" @@ -8099,7 +8033,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "93980406f12d9f8140ed5abe7155acb10bb1e69ea55c88960b9c2f117445ef96" dependencies = [ "equivalent", - "indexmap 2.12.1", + "indexmap 2.13.0", "serde", ] @@ -8129,7 +8063,7 @@ version = "3.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "219cb19e96be00ab2e37d6e299658a0cfa83e52429179969b0f0121b4ac46983" dependencies = [ - "toml_edit 0.23.9", + "toml_edit 0.23.10+spec-1.0.0", ] [[package]] @@ -8175,7 +8109,7 @@ dependencies = [ "proc-macro-error-attr2", "proc-macro2", "quote", - "syn 2.0.111", + "syn 2.0.114", ] [[package]] @@ -8186,9 +8120,9 @@ checksum = "dc375e1527247fe1a97d8b7156678dfe7c1af2fc075c9a4db3690ecd2a148068" [[package]] name = "proc-macro2" -version = "1.0.103" +version = "1.0.106" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5ee95bc4ef87b8d5ba32e8b7714ccc834865276eab0aed5c9958d00ec45f49e8" +checksum = "8fd00f0bb2e90d81d1044c2b32617f68fcb9fa3bb7640c23e9c748e53fb30934" dependencies = [ "unicode-ident", ] @@ -8213,7 +8147,7 @@ checksum = "440f724eba9f6996b75d63681b0a92b06947f1457076d503a4d2e2c8f56442b8" dependencies = [ "proc-macro2", "quote", - "syn 2.0.111", + "syn 2.0.114", ] [[package]] @@ -8264,7 +8198,7 @@ dependencies = [ "derive-deftly", "libc", "paste", - "thiserror 2.0.17", + "thiserror 2.0.18", ] [[package]] @@ -8323,15 +8257,6 @@ dependencies = [ "unsigned-varint 0.8.0", ] -[[package]] -name = "quick-xml" -version = "0.37.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "331e97a1af0bf59823e6eadffe373d7b27f485be8748f71471c662c1f269b7fb" -dependencies = [ - "memchr", -] - [[package]] name = "quick-xml" version = "0.38.4" @@ -8360,7 +8285,7 @@ checksum = "f71ee38b42f8459a88d3362be6f9b841ad2d5421844f61eb1c59c11bff3ac14a" dependencies = [ "proc-macro2", "quote", - "syn 2.0.111", + "syn 2.0.114", ] [[package]] @@ -8376,9 +8301,9 @@ dependencies = [ "quinn-proto", "quinn-udp", "rustc-hash", - "rustls 0.23.35", - "socket2 0.6.1", - "thiserror 2.0.17", + "rustls 0.23.36", + "socket2 0.6.2", + "thiserror 2.0.18", "tokio", "tracing", "web-time", @@ -8396,10 +8321,10 @@ dependencies = [ "rand 0.9.2", "ring 0.17.14", "rustc-hash", - "rustls 0.23.35", + "rustls 0.23.36", "rustls-pki-types", "slab", - "thiserror 2.0.17", + "thiserror 2.0.18", "tinyvec", "tracing", "web-time", @@ -8414,16 +8339,16 @@ dependencies = [ "cfg_aliases", "libc", "once_cell", - "socket2 0.6.1", + "socket2 0.6.2", "tracing", "windows-sys 0.60.2", ] [[package]] name = "quote" -version = "1.0.42" +version = "1.0.44" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a338cc41d27e6cc6dce6cefc13a0729dfbb81c262b1f519331575dd80ef3067f" +checksum = "21b2ebcf727b7760c461f091f9f0f539b77b8e87f2fd88131e7f1b433b3cece4" dependencies = [ "proc-macro2", ] @@ -8482,7 +8407,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6db2770f06117d490610c7488547d543617b21bfa07796d7a12f6f1bd53850d1" dependencies = [ "rand_chacha 0.9.0", - "rand_core 0.9.3", + "rand_core 0.9.5", ] [[package]] @@ -8512,7 +8437,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d3022b5f1df60f26e1ffddd6c66e8aa15de382ae63b3a0c1bfc0e4d3e3f325cb" dependencies = [ "ppv-lite86", - "rand_core 0.9.3", + "rand_core 0.9.5", ] [[package]] @@ -8530,14 +8455,14 @@ version = "0.6.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" dependencies = [ - "getrandom 0.2.16", + "getrandom 0.2.17", ] [[package]] name = "rand_core" -version = "0.9.3" +version = "0.9.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "99d9a13982dcf210057a8a78572b2217b667c3beacbf3a0d8b454f6f82837d38" +checksum = "76afc826de14238e6e8c374ddcc1fa19e374fd8dd986b0d2af0d02377261d83c" dependencies = [ "getrandom 0.3.4", ] @@ -8568,7 +8493,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b16df48f071248e67b8fc5e866d9448d45c08ad8b672baaaf796e2f15e606ff0" dependencies = [ "libc", - "rand_core 0.9.3", + "rand_core 0.9.5", "winapi", ] @@ -8587,7 +8512,7 @@ version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "513962919efc330f829edb2535844d1b912b0fbe2ca165d613e4e8788bb05a5a" dependencies = [ - "rand_core 0.9.3", + "rand_core 0.9.5", ] [[package]] @@ -8655,13 +8580,22 @@ dependencies = [ "bitflags 2.10.0", ] +[[package]] +name = "redox_syscall" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "49f3fe0889e69e2ae9e41f4d6c4c0181701d00e4697b356fb1f74173a5e0ee27" +dependencies = [ + "bitflags 2.10.0", +] + [[package]] name = "redox_users" version = "0.4.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ba009ff324d1fc1b900bd1fdb31564febe58a8ccc8a6fdbb93b543d33b13ca43" dependencies = [ - "getrandom 0.2.16", + "getrandom 0.2.17", "libredox", "thiserror 1.0.69", ] @@ -8672,9 +8606,9 @@ version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a4e608c6638b9c18977b00b475ac1f28d14e84b27d8d42f70e0bf1e3dec127ac" dependencies = [ - "getrandom 0.2.16", + "getrandom 0.2.17", "libredox", - "thiserror 2.0.17", + "thiserror 2.0.18", ] [[package]] @@ -8694,7 +8628,7 @@ checksum = "b7186006dcb21920990093f30e3dea63b7d6e977bf1256be20c3563a5db070da" dependencies = [ "proc-macro2", "quote", - "syn 2.0.111", + "syn 2.0.114", ] [[package]] @@ -8797,15 +8731,15 @@ dependencies = [ [[package]] name = "reqwest" -version = "0.12.25" +version = "0.12.28" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b6eff9328d40131d43bd911d42d79eb6a47312002a4daefc9e37f17e74a7701a" +checksum = "eddd3ca559203180a307f12d114c268abf583f59b03cb906fd0b3ff8646c1147" dependencies = [ "base64 0.22.1", "bytes", "futures-core", "futures-util", - "h2 0.4.12", + "h2 0.4.13", "http 1.4.0", "http-body 1.0.1", "http-body-util", @@ -8817,8 +8751,8 @@ dependencies = [ "percent-encoding", "pin-project-lite", "quinn", - "rustls 0.23.35", - "rustls-native-certs 0.8.2", + "rustls 0.23.36", + "rustls-native-certs 0.8.3", "rustls-pki-types", "serde", "serde_json", @@ -8835,7 +8769,7 @@ dependencies = [ "wasm-bindgen-futures", "wasm-streams", "web-sys", - "webpki-roots 1.0.4", + "webpki-roots 1.0.5", ] [[package]] @@ -8861,11 +8795,10 @@ dependencies = [ [[package]] name = "rfd" -version = "0.15.4" +version = "0.16.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ef2bee61e6cffa4635c72d7d81a84294e28f0930db0ddcb0f66d10244674ebed" +checksum = "a15ad77d9e70a92437d8f74c35d99b4e4691128df018833e99f90bcd36152672" dependencies = [ - "ashpd", "block2", "dispatch2", "glib-sys", @@ -8881,7 +8814,7 @@ dependencies = [ "wasm-bindgen", "wasm-bindgen-futures", "web-sys", - "windows-sys 0.59.0", + "windows-sys 0.60.2", ] [[package]] @@ -8907,7 +8840,7 @@ checksum = "a4689e6c2294d81e88dc6261c768b63bc4fcdb852be6d1352498b114f61383b7" dependencies = [ "cc", "cfg-if", - "getrandom 0.2.16", + "getrandom 0.2.17", "libc", "untrusted 0.9.0", "windows-sys 0.52.0", @@ -8915,9 +8848,9 @@ dependencies = [ [[package]] name = "rkyv" -version = "0.7.45" +version = "0.7.46" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9008cd6385b9e161d8229e1f6549dd23c3d022f132a2ea37ac3a10ac4935779b" +checksum = "2297bf9c81a3f0dc96bc9521370b88f054168c29826a75e89c55ff196e7ed6a1" dependencies = [ "bitvec", "bytecheck", @@ -8933,9 +8866,9 @@ dependencies = [ [[package]] name = "rkyv_derive" -version = "0.7.45" +version = "0.7.46" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "503d1d27590a2b0a3a4ca4c94755aa2875657196ecbf401a42eff41d7de532c0" +checksum = "84d7b42d4b8d06048d3ac8db0eb31bcb942cbeb709f0b5f2b2ebde398d3038f5" dependencies = [ "proc-macro2", "quote", @@ -8950,9 +8883,9 @@ checksum = "afab94fb28594581f62d981211a9a4d53cc8130bbcbbb89a0440d9b8e81a7746" [[package]] name = "rsa" -version = "0.9.9" +version = "0.9.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "40a0376c50d0358279d9d643e4bf7b7be212f1f4ff1da9070a7b54d22ef75c88" +checksum = "b8573f03f5883dcaebdfcf4725caa1ecb9c15b2ef50c43a07b816e06799bb12d" dependencies = [ "const-oid", "digest 0.10.7", @@ -9004,9 +8937,9 @@ dependencies = [ [[package]] name = "rust_decimal" -version = "1.39.0" +version = "1.40.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "35affe401787a9bd846712274d97654355d21b2a2c092a3139aabe31e9022282" +checksum = "61f703d19852dbf87cbc513643fa81428361eb6940f1ac14fd58155d295a3eb0" dependencies = [ "arrayvec", "borsh", @@ -9044,9 +8977,9 @@ dependencies = [ [[package]] name = "rustix" -version = "1.1.2" +version = "1.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cd15f8a2c5551a84d56efdc1cd049089e409ac19a3072d5037a17fd70719ff3e" +checksum = "146c9e247ccc180c1f61615433868c99f3de3ae256a30a43b49f67c2d9171f34" dependencies = [ "bitflags 2.10.0", "errno", @@ -9094,16 +9027,16 @@ dependencies = [ [[package]] name = "rustls" -version = "0.23.35" +version = "0.23.36" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "533f54bc6a7d4f647e46ad909549eda97bf5afc1585190ef692b4286b198bd8f" +checksum = "c665f33d38cea657d9614f766881e4d510e0eda4239891eea56b4cadcf01801b" dependencies = [ "aws-lc-rs", "log", "once_cell", "ring 0.17.14", "rustls-pki-types", - "rustls-webpki 0.103.8", + "rustls-webpki 0.103.9", "subtle", "zeroize", ] @@ -9114,7 +9047,7 @@ version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5a07b7c1885bd8ed3831c289b7870b13ef46fe0e856d288c30d9cc17d75a2092" dependencies = [ - "openssl-probe", + "openssl-probe 0.1.6", "rustls 0.19.1", "schannel", "security-framework 2.11.1", @@ -9122,11 +9055,11 @@ dependencies = [ [[package]] name = "rustls-native-certs" -version = "0.8.2" +version = "0.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9980d917ebb0c0536119ba501e90834767bffc3d60641457fd84a1f3fd337923" +checksum = "612460d5f7bea540c490b2b6395d8e34a953e52b491accd6c86c8164c5932a63" dependencies = [ - "openssl-probe", + "openssl-probe 0.2.1", "rustls-pki-types", "schannel", "security-framework 3.5.1", @@ -9143,9 +9076,9 @@ dependencies = [ [[package]] name = "rustls-pki-types" -version = "1.13.1" +version = "1.14.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "708c0f9d5f54ba0272468c1d306a52c495b31fa155e91bc25371e6df7996908c" +checksum = "be040f8b0a225e40375822a563fa9524378b9d63112f53e19ffff34df5d33fdd" dependencies = [ "web-time", "zeroize", @@ -9162,10 +9095,10 @@ dependencies = [ "jni", "log", "once_cell", - "rustls 0.23.35", - "rustls-native-certs 0.8.2", + "rustls 0.23.36", + "rustls-native-certs 0.8.3", "rustls-platform-verifier-android", - "rustls-webpki 0.103.8", + "rustls-webpki 0.103.9", "security-framework 3.5.1", "security-framework-sys", "webpki-root-certs 0.26.11", @@ -9190,9 +9123,9 @@ dependencies = [ [[package]] name = "rustls-webpki" -version = "0.103.8" +version = "0.103.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2ffdfa2f5286e2247234e03f680868ac2815974dc39e00ea15adc445d0aafe52" +checksum = "d7df23109aa6c1567d1c575b9952556388da57401e4ace1d15f79eedad0d8f53" dependencies = [ "aws-lc-rs", "ring 0.17.14", @@ -9253,20 +9186,20 @@ dependencies = [ [[package]] name = "ryu" -version = "1.0.20" +version = "1.0.22" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "28d3b2b1366ec20994f1fd18c3c594f05c5dd4bc44d8bb0c1c632c8d6829481f" +checksum = "a50f4cf475b65d88e057964e0e9bb1f0aa9bbb2036dc65c64596b42932536984" [[package]] name = "safelog" version = "0.7.1" source = "git+https://github.com/eigenwallet/arti?branch=downgraded_rusqlite_arti_1_8_0#2a5db5823e2a5eb413d8ad433a4d3aba902bac07" dependencies = [ - "derive_more 2.1.0", + "derive_more 2.1.1", "educe", "either", "fluid-let", - "thiserror 2.0.17", + "thiserror 2.0.18", ] [[package]] @@ -9325,9 +9258,9 @@ dependencies = [ [[package]] name = "schemars" -version = "1.1.0" +version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9558e172d4e8533736ba97870c4b2cd63f84b382a3d6eb063da41b91cce17289" +checksum = "54e910108742c57a770f492731f99be216a52fadd361b06c8fb59d74ccc267d2" dependencies = [ "dyn-clone", "ref-cast", @@ -9344,15 +9277,9 @@ dependencies = [ "proc-macro2", "quote", "serde_derive_internals", - "syn 2.0.111", + "syn 2.0.114", ] -[[package]] -name = "scoped-tls" -version = "1.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e1cf6437eb19a8f4a6cc0f7dca544973b0b78843adbfeb3683d1a94a0024a294" - [[package]] name = "scopeguard" version = "1.2.0" @@ -9644,7 +9571,7 @@ checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79" dependencies = [ "proc-macro2", "quote", - "syn 2.0.111", + "syn 2.0.114", ] [[package]] @@ -9655,7 +9582,7 @@ checksum = "18d26a20a969b9e3fdf2fc2d9f21eda6c40e2de84c9408bb5d3b05d499aae711" dependencies = [ "proc-macro2", "quote", - "syn 2.0.111", + "syn 2.0.114", ] [[package]] @@ -9670,15 +9597,15 @@ dependencies = [ [[package]] name = "serde_json" -version = "1.0.145" +version = "1.0.149" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "402a6f66d8c709116cf22f558eab210f5a50187f702eb4d7e5ef38d9a7f1c79c" +checksum = "83fc039473c5595ace860d8c4fafa220ff474b3fc6bfdb4293327f1a37e94d86" dependencies = [ "itoa", "memchr", - "ryu", "serde", "serde_core", + "zmij", ] [[package]] @@ -9700,7 +9627,7 @@ checksum = "175ee3e80ae9982737ca543e96133087cbd9a485eecc3bc4de9c1a37b47ea59c" dependencies = [ "proc-macro2", "quote", - "syn 2.0.111", + "syn 2.0.114", ] [[package]] @@ -9714,9 +9641,9 @@ dependencies = [ [[package]] name = "serde_spanned" -version = "1.0.3" +version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e24345aa0fe688594e73770a5f6d1b216508b4f93484c0026d521acd30134392" +checksum = "f8bbf91e5a4d6315eee45e704372590b30e260ee83af6639d64557f51b067776" dependencies = [ "serde_core", ] @@ -9753,9 +9680,9 @@ dependencies = [ "chrono", "hex", "indexmap 1.9.3", - "indexmap 2.12.1", + "indexmap 2.13.0", "schemars 0.9.0", - "schemars 1.1.0", + "schemars 1.2.0", "serde_core", "serde_json", "serde_with_macros 3.16.1", @@ -9783,7 +9710,7 @@ dependencies = [ "darling 0.21.3", "proc-macro2", "quote", - "syn 2.0.111", + "syn 2.0.114", ] [[package]] @@ -9792,7 +9719,7 @@ version = "0.9.34+deprecated" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6a8b1a1a2ebf674015cc02edccce75287f1a0130d394307b36743c2f5d504b47" dependencies = [ - "indexmap 2.12.1", + "indexmap 2.13.0", "itoa", "ryu", "serde", @@ -9818,7 +9745,7 @@ checksum = "772ee033c0916d670af7860b6e1ef7d658a4629a6d0b4c8c3e67f09b3765b75d" dependencies = [ "proc-macro2", "quote", - "syn 2.0.111", + "syn 2.0.114", ] [[package]] @@ -9924,10 +9851,11 @@ dependencies = [ [[package]] name = "signal-hook-registry" -version = "1.4.7" +version = "1.4.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7664a098b8e616bdfcc2dc0e9ac44eb231eedf41db4e9fe95d8d32ec728dedad" +checksum = "c4db69cba1110affc0e9f7bcd48bbf87b3f4fc7c61fc9155afd4c469eb3d6c1b" dependencies = [ + "errno", "libc", ] @@ -10026,7 +9954,7 @@ dependencies = [ "paste", "serde", "slotmap", - "thiserror 2.0.17", + "thiserror 2.0.18", "void", ] @@ -10068,9 +9996,9 @@ dependencies = [ [[package]] name = "socket2" -version = "0.6.1" +version = "0.6.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "17129e116933cf371d018bb80ae557e889637989d8638274fb25622827b03881" +checksum = "86f4aa3ad99f2088c990dfa82d367e19cb29268ed67c574d10d0a4bfe71f07e0" dependencies = [ "libc", "windows-sys 0.60.2", @@ -10216,7 +10144,7 @@ dependencies = [ "hashbrown 0.14.5", "hashlink", "hex", - "indexmap 2.12.1", + "indexmap 2.13.0", "log", "memchr", "once_cell", @@ -10247,7 +10175,7 @@ dependencies = [ "quote", "sqlx-core", "sqlx-macros-core", - "syn 2.0.111", + "syn 2.0.114", ] [[package]] @@ -10270,7 +10198,7 @@ dependencies = [ "sqlx-mysql", "sqlx-postgres", "sqlx-sqlite", - "syn 2.0.111", + "syn 2.0.114", "tempfile", "tokio", "url", @@ -10553,7 +10481,7 @@ dependencies = [ "proc-macro2", "quote", "rustversion", - "syn 2.0.111", + "syn 2.0.114", ] [[package]] @@ -10565,7 +10493,7 @@ dependencies = [ "heck 0.5.0", "proc-macro2", "quote", - "syn 2.0.111", + "syn 2.0.114", ] [[package]] @@ -10616,9 +10544,9 @@ dependencies = [ "proptest", "rand 0.8.5", "regex", - "reqwest 0.12.25", + "reqwest 0.12.28", "rust_decimal", - "rustls 0.23.35", + "rustls 0.23.36", "semver", "serde", "serde_json", @@ -10667,9 +10595,9 @@ dependencies = [ "monero-address", "monero-rpc-pool", "monero-sys", - "reqwest 0.12.25", + "reqwest 0.12.28", "rust_decimal", - "rustls 0.23.35", + "rustls 0.23.36", "serde", "serde_json", "structopt", @@ -10691,7 +10619,7 @@ name = "swap-controller" version = "3.6.7" dependencies = [ "anyhow", - "clap 4.5.53", + "clap 4.5.54", "comfy-table", "jsonrpsee", "monero-oxide-ext", @@ -10726,7 +10654,7 @@ dependencies = [ "futures", "monero-address", "monero-oxide-ext", - "monero-wallet 0.1.0 (git+https://github.com/kayabaNerve/monero-oxide.git?branch=rpc-rewrite)", + "monero-wallet 0.1.0 (git+https://github.com/kayabaNerve/monero-oxide.git)", "proptest", "rand 0.8.5", "rand_chacha 0.3.1", @@ -10763,7 +10691,7 @@ dependencies = [ "anyhow", "bitcoin 0.32.8", "config", - "console 0.16.1", + "console 0.16.2", "dialoguer", "libp2p", "monero-address", @@ -10773,7 +10701,7 @@ dependencies = [ "swap-serde", "thiserror 1.0.69", "time", - "toml 0.9.8", + "toml 0.9.11+spec-1.1.0", "tracing", "url", ] @@ -10787,7 +10715,7 @@ dependencies = [ "bitcoin 0.32.8", "futures", "monero-oxide-ext", - "reqwest 0.12.25", + "reqwest 0.12.28", "rust_decimal", "serde", "serde_json", @@ -10847,7 +10775,7 @@ dependencies = [ "monero-address", "serde_yaml", "swap-env", - "toml 0.9.8", + "toml 0.9.11+spec-1.1.0", "url", "vergen 8.3.2", ] @@ -10857,6 +10785,7 @@ name = "swap-p2p" version = "0.1.0" dependencies = [ "anyhow", + "arti-client", "async-trait", "asynchronous-codec 0.7.0", "backoff", @@ -10865,6 +10794,7 @@ dependencies = [ "bmrng", "futures", "libp2p", + "libp2p-tor", "monero-address", "rand 0.8.5", "rust_decimal", @@ -10878,7 +10808,9 @@ dependencies = [ "swap-serde", "thiserror 1.0.69", "tokio", + "tor-rtcompat", "tracing", + "tracing-subscriber", "typeshare", "unsigned-varint 0.8.0", "uuid", @@ -10904,7 +10836,7 @@ dependencies = [ "libp2p", "monero-address", "monero-oxide-ext", - "monero-wallet 0.1.0 (git+https://github.com/kayabaNerve/monero-oxide.git?branch=rpc-rewrite)", + "monero-wallet 0.1.0 (git+https://github.com/kayabaNerve/monero-oxide.git)", "serde", "serde_json", "thiserror 1.0.69", @@ -10935,9 +10867,9 @@ dependencies = [ [[package]] name = "syn" -version = "2.0.111" +version = "2.0.114" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "390cc9a294ab71bdb1aa2e99d13be9c753cd2d7bd6560c77118597410c4d2e87" +checksum = "d4d107df263a3013ef9b1879b0df87d706ff80f65a86ea879bd9c31f9b307c2a" dependencies = [ "proc-macro2", "quote", @@ -10979,7 +10911,7 @@ checksum = "728a70f3dbaf5bab7f0c4b1ac8d7ae5ea60a4b5549c8a5914361c99147a709d2" dependencies = [ "proc-macro2", "quote", - "syn 2.0.111", + "syn 2.0.114", ] [[package]] @@ -11105,7 +11037,7 @@ checksum = "f4e16beb8b2ac17db28eab8bca40e62dbfbb34c0fcdc6d9826b11b7b5d047dfd" dependencies = [ "proc-macro2", "quote", - "syn 2.0.111", + "syn 2.0.114", ] [[package]] @@ -11161,7 +11093,7 @@ dependencies = [ "percent-encoding", "plist", "raw-window-handle", - "reqwest 0.12.25", + "reqwest 0.12.28", "serde", "serde_json", "serde_repr", @@ -11172,7 +11104,7 @@ dependencies = [ "tauri-runtime", "tauri-runtime-wry", "tauri-utils", - "thiserror 2.0.17", + "thiserror 2.0.18", "tokio", "tray-icon", "url", @@ -11200,7 +11132,7 @@ dependencies = [ "serde_json", "tauri-utils", "tauri-winres", - "toml 0.9.8", + "toml 0.9.11+spec-1.1.0", "walkdir", ] @@ -11222,9 +11154,9 @@ dependencies = [ "serde", "serde_json", "sha2", - "syn 2.0.111", + "syn 2.0.114", "tauri-utils", - "thiserror 2.0.17", + "thiserror 2.0.18", "time", "url", "uuid", @@ -11240,7 +11172,7 @@ dependencies = [ "heck 0.5.0", "proc-macro2", "quote", - "syn 2.0.111", + "syn 2.0.114", "tauri-codegen", "tauri-utils", ] @@ -11258,7 +11190,7 @@ dependencies = [ "serde", "serde_json", "tauri-utils", - "toml 0.9.8", + "toml 0.9.11+spec-1.1.0", "walkdir", ] @@ -11268,13 +11200,13 @@ version = "2.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "28e78fb2c09a81546bcd376d34db4bda5769270d00990daa9f0d6e7ac1107e25" dependencies = [ - "clap 4.5.53", + "clap 4.5.54", "log", "serde", "serde_json", "tauri", "tauri-plugin", - "thiserror 2.0.17", + "thiserror 2.0.18", ] [[package]] @@ -11289,14 +11221,14 @@ dependencies = [ "serde_json", "tauri", "tauri-plugin", - "thiserror 2.0.17", + "thiserror 2.0.18", ] [[package]] name = "tauri-plugin-dialog" -version = "2.4.2" +version = "2.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "313f8138692ddc4a2127c4c9607d616a46f5c042e77b3722450866da0aad2f19" +checksum = "9204b425d9be8d12aa60c2a83a289cf7d1caae40f57f336ed1155b3a5c0e359b" dependencies = [ "log", "raw-window-handle", @@ -11306,15 +11238,15 @@ dependencies = [ "tauri", "tauri-plugin", "tauri-plugin-fs", - "thiserror 2.0.17", + "thiserror 2.0.18", "url", ] [[package]] name = "tauri-plugin-fs" -version = "2.4.4" +version = "2.4.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "47df422695255ecbe7bac7012440eddaeefd026656171eac9559f5243d3230d9" +checksum = "ed390cc669f937afeb8b28032ce837bac8ea023d975a2e207375ec05afaf1804" dependencies = [ "anyhow", "dunce", @@ -11327,16 +11259,16 @@ dependencies = [ "tauri", "tauri-plugin", "tauri-utils", - "thiserror 2.0.17", - "toml 0.9.8", + "thiserror 2.0.18", + "toml 0.9.11+spec-1.1.0", "url", ] [[package]] name = "tauri-plugin-opener" -version = "2.5.2" +version = "2.5.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c26b72571d25dee25667940027114e60f569fc3974f8cefbe50c2cbc5fd65e3b" +checksum = "fc624469b06f59f5a29f874bbc61a2ed737c0f9c23ef09855a292c389c42e83f" dependencies = [ "dunce", "glob", @@ -11348,7 +11280,7 @@ dependencies = [ "serde_json", "tauri", "tauri-plugin", - "thiserror 2.0.17", + "thiserror 2.0.18", "url", "windows 0.61.3", "zbus", @@ -11366,14 +11298,14 @@ dependencies = [ [[package]] name = "tauri-plugin-single-instance" -version = "2.3.6" +version = "2.3.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dd707f8c86b4e3004e2c141fa24351f1909ba40ce1b8437e30d5ed5277dd3710" +checksum = "acba6b5ca527a96cdfcc96ae09b09ccb91ddff5e33978ca6873b96ea16bb404c" dependencies = [ "serde", "serde_json", "tauri", - "thiserror 2.0.17", + "thiserror 2.0.18", "tracing", "windows-sys 0.60.2", "zbus", @@ -11381,16 +11313,16 @@ dependencies = [ [[package]] name = "tauri-plugin-store" -version = "2.4.1" +version = "2.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "59a77036340a97eb5bbe1b3209c31e5f27f75e6f92a52fd9dd4b211ef08bf310" +checksum = "5ca1a8ff83c269b115e98726ffc13f9e548a10161544a92ad121d6d0a96e16ea" dependencies = [ "dunce", "serde", "serde_json", "tauri", "tauri-plugin", - "thiserror 2.0.17", + "thiserror 2.0.18", "tokio", "tracing", ] @@ -11411,7 +11343,7 @@ dependencies = [ "minisign-verify", "osakit", "percent-encoding", - "reqwest 0.12.25", + "reqwest 0.12.28", "semver", "serde", "serde_json", @@ -11419,7 +11351,7 @@ dependencies = [ "tauri", "tauri-plugin", "tempfile", - "thiserror 2.0.17", + "thiserror 2.0.18", "time", "tokio", "url", @@ -11445,7 +11377,7 @@ dependencies = [ "serde", "serde_json", "tauri-utils", - "thiserror 2.0.17", + "thiserror 2.0.18", "url", "webkit2gtk", "webview2-com", @@ -11510,8 +11442,8 @@ dependencies = [ "serde_json", "serde_with 3.16.1", "swift-rs", - "thiserror 2.0.17", - "toml 0.9.8", + "thiserror 2.0.18", + "toml 0.9.11+spec-1.1.0", "url", "urlpattern", "uuid", @@ -11526,14 +11458,14 @@ checksum = "1087b111fe2b005e42dbdc1990fc18593234238d47453b0c99b7de1c9ab2c1e0" dependencies = [ "dunce", "embed-resource", - "toml 0.9.8", + "toml 0.9.11+spec-1.1.0", ] [[package]] name = "tempfile" -version = "3.23.0" +version = "3.24.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2d31c77bdf42a745371d260a26ca7163f1e0924b64afa0b688e61b5a9fa02f16" +checksum = "655da9c7eb6305c55742045d5a8d2037996d61d8de95806335c7c86ce0f82e9c" dependencies = [ "fastrand", "getrandom 0.3.4", @@ -11599,11 +11531,11 @@ dependencies = [ [[package]] name = "thiserror" -version = "2.0.17" +version = "2.0.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f63587ca0f12b72a0600bcba1d40081f830876000bb46dd2337a3051618f4fc8" +checksum = "4288b5bcbc7920c07a1149a35cf9590a2aa808e0bc1eafaade0b80947865fbc4" dependencies = [ - "thiserror-impl 2.0.17", + "thiserror-impl 2.0.18", ] [[package]] @@ -11614,18 +11546,18 @@ checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1" dependencies = [ "proc-macro2", "quote", - "syn 2.0.111", + "syn 2.0.114", ] [[package]] name = "thiserror-impl" -version = "2.0.17" +version = "2.0.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3ff15c8ecd7de3849db632e14d18d2571fa09dfc5ed93479bc4485c7a517c913" +checksum = "ebc4ee7f67670e9b64d05fa4253e753e016c6c95ff35b89b7941d6b856dec1d5" dependencies = [ "proc-macro2", "quote", - "syn 2.0.111", + "syn 2.0.114", ] [[package]] @@ -11660,9 +11592,9 @@ dependencies = [ [[package]] name = "time" -version = "0.3.44" +version = "0.3.46" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "91e7d9e3bb61134e77bde20dd4825b97c010155709965fedf0f49bb138e52a9d" +checksum = "9da98b7d9b7dad93488a84b8248efc35352b0b2657397d4167e7ad67e5d535e5" dependencies = [ "deranged", "itoa", @@ -11670,22 +11602,22 @@ dependencies = [ "num-conv", "num_threads", "powerfmt", - "serde", + "serde_core", "time-core", "time-macros", ] [[package]] name = "time-core" -version = "0.1.6" +version = "0.1.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "40868e7c1d2f0b8d73e4a8c7f0ff63af4f6d19be117e90bd73eb1d62cf831c6b" +checksum = "7694e1cfe791f8d31026952abf09c69ca6f6fa4e1a1229e18988f06a04a12dca" [[package]] name = "time-macros" -version = "0.2.24" +version = "0.2.26" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "30cfb0125f12d9c277f35663a0a33f8c30190f4e4574868a330595412d34ebf3" +checksum = "78cc610bac2dcee56805c99642447d4c5dbde4d01f752ffea0199aee1f601dc4" dependencies = [ "num-conv", "time-core", @@ -11729,9 +11661,9 @@ checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" [[package]] name = "tokio" -version = "1.48.0" +version = "1.49.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ff360e02eab121e0bc37a2d3b4d4dc622e6eda3a8e5253d5435ecf5bd4c68408" +checksum = "72a2903cd7736441aac9df9d7688bd0ce48edccaadf181c3b90be801e81d3d86" dependencies = [ "bytes", "libc", @@ -11739,9 +11671,8 @@ dependencies = [ "parking_lot 0.12.5", "pin-project-lite", "signal-hook-registry", - "socket2 0.6.1", + "socket2 0.6.2", "tokio-macros", - "tracing", "windows-sys 0.61.2", ] @@ -11753,7 +11684,7 @@ checksum = "af407857209536a95c8e56f8231ef2c2e2aff839b22e07a1ffcbc617e9db9fa5" dependencies = [ "proc-macro2", "quote", - "syn 2.0.111", + "syn 2.0.114", ] [[package]] @@ -11783,15 +11714,15 @@ version = "0.26.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1729aa945f29d91ba541258c8df89027d5792d85a8841fb65e8bf0f4ede4ef61" dependencies = [ - "rustls 0.23.35", + "rustls 0.23.36", "tokio", ] [[package]] name = "tokio-stream" -version = "0.1.17" +version = "0.1.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eca58d7bba4a75707817a2c44174253f9236b2d5fbd055602e9d5c07c139a047" +checksum = "32da49809aab5c3bc678af03902d4ccddea2a87d028d86392a4b1560c6906c70" dependencies = [ "futures-core", "pin-project-lite", @@ -11801,12 +11732,10 @@ dependencies = [ [[package]] name = "tokio-test" -version = "0.4.4" +version = "0.4.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2468baabc3311435b55dd935f702f42cd1b8abb7e754fb7dfb16bd36aa88f9f7" +checksum = "3f6d24790a10a7af737693a3e8f1d03faef7e6ca0cc99aae5066f533766de545" dependencies = [ - "async-stream", - "bytes", "futures-core", "tokio", "tokio-stream", @@ -11831,9 +11760,9 @@ dependencies = [ [[package]] name = "tokio-util" -version = "0.7.17" +version = "0.7.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2efa149fe76073d6e8fd97ef4f4eca7b67f599660115591483572e406e165594" +checksum = "9ae9cec805b01e8fc3fd2fe289f89149a9b66dd16786abd8b19cfa7b48cb0098" dependencies = [ "bytes", "futures-core", @@ -11858,14 +11787,14 @@ dependencies = [ [[package]] name = "toml" -version = "0.9.8" +version = "0.9.11+spec-1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f0dc8b1fb61449e27716ec0e1bdf0f6b8f3e8f6b05391e8497b8b6d7804ea6d8" +checksum = "f3afc9a848309fe1aaffaed6e1546a7a14de1f935dc9d89d32afd9a44bab7c46" dependencies = [ - "indexmap 2.12.1", + "indexmap 2.13.0", "serde_core", - "serde_spanned 1.0.3", - "toml_datetime 0.7.3", + "serde_spanned 1.0.4", + "toml_datetime 0.7.5+spec-1.1.0", "toml_parser", "toml_writer", "winnow 0.7.14", @@ -11882,9 +11811,9 @@ dependencies = [ [[package]] name = "toml_datetime" -version = "0.7.3" +version = "0.7.5+spec-1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f2cdb639ebbc97961c51720f858597f7f24c4fc295327923af55b74c3c724533" +checksum = "92e1cfed4a3038bc5a127e35a2d360f145e1f4b971b551a2ba5fd7aedf7e1347" dependencies = [ "serde_core", ] @@ -11895,7 +11824,7 @@ version = "0.19.15" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1b5bb770da30e5cbfde35a2d7b9b8a2c4b8ef89548a7a6aeab5c9a576e3e7421" dependencies = [ - "indexmap 2.12.1", + "indexmap 2.13.0", "toml_datetime 0.6.3", "winnow 0.5.40", ] @@ -11906,7 +11835,7 @@ version = "0.20.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "396e4d48bbb2b7554c944bde63101b5ae446cff6ec4a24227428f15eb72ef338" dependencies = [ - "indexmap 2.12.1", + "indexmap 2.13.0", "serde", "serde_spanned 0.6.9", "toml_datetime 0.6.3", @@ -11915,30 +11844,30 @@ dependencies = [ [[package]] name = "toml_edit" -version = "0.23.9" +version = "0.23.10+spec-1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5d7cbc3b4b49633d57a0509303158ca50de80ae32c265093b24c414705807832" +checksum = "84c8b9f757e028cee9fa244aea147aab2a9ec09d5325a9b01e0a49730c2b5269" dependencies = [ - "indexmap 2.12.1", - "toml_datetime 0.7.3", + "indexmap 2.13.0", + "toml_datetime 0.7.5+spec-1.1.0", "toml_parser", "winnow 0.7.14", ] [[package]] name = "toml_parser" -version = "1.0.4" +version = "1.0.6+spec-1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c0cbe268d35bdb4bb5a56a2de88d0ad0eb70af5384a99d648cd4b3d04039800e" +checksum = "a3198b4b0a8e11f09dd03e133c0280504d0801269e9afa46362ffde1cbeebf44" dependencies = [ "winnow 0.7.14", ] [[package]] name = "toml_writer" -version = "1.0.4" +version = "1.0.6+spec-1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "df8b2b54733674ad286d16267dcfc7a71ed5c776e4ac7aa3c3e2561f7c637bf2" +checksum = "ab16f14aed21ee8bfd8ec22513f7287cd4a91aa92e44edfe2c17ddd004e92607" [[package]] name = "tor-async-utils" @@ -11951,7 +11880,7 @@ dependencies = [ "oneshot-fused-workaround", "pin-project", "postage", - "thiserror 2.0.17", + "thiserror 2.0.18", "void", ] @@ -11960,7 +11889,7 @@ name = "tor-basic-utils" version = "0.37.0" source = "git+https://github.com/eigenwallet/arti?branch=downgraded_rusqlite_arti_1_8_0#2a5db5823e2a5eb413d8ad433a4d3aba902bac07" dependencies = [ - "derive_more 2.1.0", + "derive_more 2.1.1", "hex", "itertools 0.14.0", "libc", @@ -11970,7 +11899,7 @@ dependencies = [ "serde", "slab", "smallvec", - "thiserror 2.0.17", + "thiserror 2.0.18", ] [[package]] @@ -11984,7 +11913,7 @@ dependencies = [ "educe", "getrandom 0.3.4", "safelog", - "thiserror 2.0.17", + "thiserror 2.0.18", "tor-error", "tor-llcrypto", "zeroize", @@ -12000,13 +11929,13 @@ dependencies = [ "bytes", "caret", "derive-deftly", - "derive_more 2.1.0", + "derive_more 2.1.1", "educe", "itertools 0.14.0", "paste", "rand 0.9.2", "smallvec", - "thiserror 2.0.17", + "thiserror 2.0.18", "tor-basic-utils", "tor-bytes", "tor-cert", @@ -12027,9 +11956,9 @@ source = "git+https://github.com/eigenwallet/arti?branch=downgraded_rusqlite_art dependencies = [ "caret", "derive_builder_fork_arti", - "derive_more 2.1.0", + "derive_more 2.1.1", "digest 0.10.7", - "thiserror 2.0.17", + "thiserror 2.0.18", "tor-bytes", "tor-checkable", "tor-error", @@ -12044,7 +11973,7 @@ dependencies = [ "async-trait", "caret", "derive_builder_fork_arti", - "derive_more 2.1.0", + "derive_more 2.1.1", "educe", "futures", "oneshot-fused-workaround", @@ -12052,7 +11981,7 @@ dependencies = [ "rand 0.9.2", "safelog", "serde", - "thiserror 2.0.17", + "thiserror 2.0.18", "tor-async-utils", "tor-basic-utils", "tor-cell", @@ -12078,7 +12007,7 @@ source = "git+https://github.com/eigenwallet/arti?branch=downgraded_rusqlite_art dependencies = [ "humantime", "signature", - "thiserror 2.0.17", + "thiserror 2.0.18", "tor-llcrypto", ] @@ -12092,7 +12021,7 @@ dependencies = [ "cfg-if", "derive-deftly", "derive_builder_fork_arti", - "derive_more 2.1.0", + "derive_more 2.1.1", "downcast-rs 2.0.2", "dyn-clone", "educe", @@ -12106,7 +12035,7 @@ dependencies = [ "retry-error", "safelog", "serde", - "thiserror 2.0.17", + "thiserror 2.0.18", "tor-async-utils", "tor-basic-utils", "tor-cell", @@ -12153,8 +12082,8 @@ dependencies = [ "serde-value", "serde_ignored", "strum 0.27.2", - "thiserror 2.0.17", - "toml 0.9.8", + "thiserror 2.0.18", + "toml 0.9.11+spec-1.1.0", "tor-basic-utils", "tor-error", "tor-rtcompat", @@ -12170,7 +12099,7 @@ dependencies = [ "directories", "serde", "shellexpand", - "thiserror 2.0.17", + "thiserror 2.0.18", "tor-error", "tor-general-addr", ] @@ -12182,7 +12111,7 @@ source = "git+https://github.com/eigenwallet/arti?branch=downgraded_rusqlite_art dependencies = [ "digest 0.10.7", "hex", - "thiserror 2.0.17", + "thiserror 2.0.18", "tor-llcrypto", ] @@ -12193,7 +12122,7 @@ source = "git+https://github.com/eigenwallet/arti?branch=downgraded_rusqlite_art dependencies = [ "async-compression", "base64ct", - "derive_more 2.1.0", + "derive_more 2.1.1", "futures", "hex", "http 1.4.0", @@ -12201,7 +12130,7 @@ dependencies = [ "httpdate", "itertools 0.14.0", "memchr", - "thiserror 2.0.17", + "thiserror 2.0.18", "tor-circmgr", "tor-error", "tor-hscrypto", @@ -12241,7 +12170,7 @@ dependencies = [ "async-trait", "base64ct", "derive_builder_fork_arti", - "derive_more 2.1.0", + "derive_more 2.1.1", "digest 0.10.7", "educe", "event-listener", @@ -12265,7 +12194,7 @@ dependencies = [ "signature", "static_assertions", "strum 0.27.2", - "thiserror 2.0.17", + "thiserror 2.0.18", "time", "tor-async-utils", "tor-basic-utils", @@ -12292,13 +12221,13 @@ name = "tor-error" version = "0.37.0" source = "git+https://github.com/eigenwallet/arti?branch=downgraded_rusqlite_arti_1_8_0#2a5db5823e2a5eb413d8ad433a4d3aba902bac07" dependencies = [ - "derive_more 2.1.0", + "derive_more 2.1.1", "futures", "paste", "retry-error", "static_assertions", "strum 0.27.2", - "thiserror 2.0.17", + "thiserror 2.0.18", "tracing", "void", ] @@ -12308,8 +12237,8 @@ name = "tor-general-addr" version = "0.37.0" source = "git+https://github.com/eigenwallet/arti?branch=downgraded_rusqlite_arti_1_8_0#2a5db5823e2a5eb413d8ad433a4d3aba902bac07" dependencies = [ - "derive_more 2.1.0", - "thiserror 2.0.17", + "derive_more 2.1.1", + "thiserror 2.0.18", "void", ] @@ -12322,7 +12251,7 @@ dependencies = [ "base64ct", "derive-deftly", "derive_builder_fork_arti", - "derive_more 2.1.0", + "derive_more 2.1.1", "dyn-clone", "educe", "futures", @@ -12337,7 +12266,7 @@ dependencies = [ "safelog", "serde", "strum 0.27.2", - "thiserror 2.0.17", + "thiserror 2.0.18", "tor-async-utils", "tor-basic-utils", "tor-config", @@ -12362,7 +12291,7 @@ source = "git+https://github.com/eigenwallet/arti?branch=downgraded_rusqlite_art dependencies = [ "async-trait", "derive-deftly", - "derive_more 2.1.0", + "derive_more 2.1.1", "educe", "either", "futures", @@ -12374,7 +12303,7 @@ dependencies = [ "safelog", "slotmap-careful", "strum 0.27.2", - "thiserror 2.0.17", + "thiserror 2.0.18", "tor-async-utils", "tor-basic-utils", "tor-bytes", @@ -12406,7 +12335,7 @@ dependencies = [ "cipher", "data-encoding", "derive-deftly", - "derive_more 2.1.0", + "derive_more 2.1.1", "digest 0.10.7", "hex", "humantime", @@ -12417,7 +12346,7 @@ dependencies = [ "serde", "signature", "subtle", - "thiserror 2.0.17", + "thiserror 2.0.18", "tor-basic-utils", "tor-bytes", "tor-error", @@ -12440,7 +12369,7 @@ dependencies = [ "cfg-if", "derive-deftly", "derive_builder_fork_arti", - "derive_more 2.1.0", + "derive_more 2.1.1", "digest 0.10.7", "educe", "fs-mistrust", @@ -12454,13 +12383,13 @@ dependencies = [ "oneshot-fused-workaround", "postage", "rand 0.9.2", - "rand_core 0.9.3", + "rand_core 0.9.5", "retry-error", "safelog", "serde", "serde_with 3.16.1", "strum 0.27.2", - "thiserror 2.0.17", + "thiserror 2.0.18", "tor-async-utils", "tor-basic-utils", "tor-bytes", @@ -12492,14 +12421,14 @@ version = "0.37.0" source = "git+https://github.com/eigenwallet/arti?branch=downgraded_rusqlite_arti_1_8_0#2a5db5823e2a5eb413d8ad433a4d3aba902bac07" dependencies = [ "derive-deftly", - "derive_more 2.1.0", + "derive_more 2.1.1", "downcast-rs 2.0.2", "paste", "rand 0.9.2", "rsa", "signature", "ssh-key", - "thiserror 2.0.17", + "thiserror 2.0.18", "tor-bytes", "tor-cert", "tor-checkable", @@ -12517,7 +12446,7 @@ dependencies = [ "cfg-if", "derive-deftly", "derive_builder_fork_arti", - "derive_more 2.1.0", + "derive_more 2.1.1", "downcast-rs 2.0.2", "dyn-clone", "fs-mistrust", @@ -12530,7 +12459,7 @@ dependencies = [ "serde", "signature", "ssh-key", - "thiserror 2.0.17", + "thiserror 2.0.18", "tor-basic-utils", "tor-bytes", "tor-config", @@ -12556,14 +12485,14 @@ dependencies = [ "caret", "derive-deftly", "derive_builder_fork_arti", - "derive_more 2.1.0", + "derive_more 2.1.1", "hex", "itertools 0.14.0", "safelog", "serde", "serde_with 3.16.1", "strum 0.27.2", - "thiserror 2.0.17", + "thiserror 2.0.18", "tor-basic-utils", "tor-bytes", "tor-config", @@ -12583,7 +12512,7 @@ dependencies = [ "curve25519-dalek", "der-parser 10.0.0", "derive-deftly", - "derive_more 2.1.0", + "derive_more 2.1.1", "digest 0.10.7", "ed25519-dalek", "educe", @@ -12592,7 +12521,7 @@ dependencies = [ "rand 0.9.2", "rand_chacha 0.9.0", "rand_core 0.6.4", - "rand_core 0.9.3", + "rand_core 0.9.5", "rand_jitter", "rdrand", "rsa", @@ -12603,7 +12532,7 @@ dependencies = [ "sha3", "signature", "subtle", - "thiserror 2.0.17", + "thiserror 2.0.18", "tor-error", "tor-memquota", "visibility", @@ -12618,7 +12547,7 @@ source = "git+https://github.com/eigenwallet/arti?branch=downgraded_rusqlite_art dependencies = [ "futures", "humantime", - "thiserror 2.0.17", + "thiserror 2.0.18", "tor-error", "tor-rtcompat", "tracing", @@ -12632,7 +12561,7 @@ source = "git+https://github.com/eigenwallet/arti?branch=downgraded_rusqlite_art dependencies = [ "cfg-if", "derive-deftly", - "derive_more 2.1.0", + "derive_more 2.1.1", "dyn-clone", "educe", "futures", @@ -12643,7 +12572,7 @@ dependencies = [ "slotmap-careful", "static_assertions", "sysinfo", - "thiserror 2.0.17", + "thiserror 2.0.18", "tor-async-utils", "tor-basic-utils", "tor-config", @@ -12661,7 +12590,7 @@ source = "git+https://github.com/eigenwallet/arti?branch=downgraded_rusqlite_art dependencies = [ "async-trait", "bitflags 2.10.0", - "derive_more 2.1.0", + "derive_more 2.1.1", "digest 0.10.7", "futures", "hex", @@ -12671,7 +12600,7 @@ dependencies = [ "rand 0.9.2", "serde", "strum 0.27.2", - "thiserror 2.0.17", + "thiserror 2.0.18", "time", "tor-basic-utils", "tor-error", @@ -12695,7 +12624,7 @@ dependencies = [ "cipher", "derive-deftly", "derive_builder_fork_arti", - "derive_more 2.1.0", + "derive_more 2.1.1", "digest 0.10.7", "educe", "enumset", @@ -12712,7 +12641,7 @@ dependencies = [ "smallvec", "strum 0.27.2", "subtle", - "thiserror 2.0.17", + "thiserror 2.0.18", "time", "tinystr", "tor-basic-utils", @@ -12738,7 +12667,7 @@ source = "git+https://github.com/eigenwallet/arti?branch=downgraded_rusqlite_art dependencies = [ "amplify", "derive-deftly", - "derive_more 2.1.0", + "derive_more 2.1.1", "filetime", "fs-mistrust", "fslock", @@ -12750,7 +12679,7 @@ dependencies = [ "sanitize-filename", "serde", "serde_json", - "thiserror 2.0.17", + "thiserror 2.0.18", "time", "tor-async-utils", "tor-basic-utils", @@ -12775,7 +12704,7 @@ dependencies = [ "criterion-cycles-per-byte", "derive-deftly", "derive_builder_fork_arti", - "derive_more 2.1.0", + "derive_more 2.1.1", "digest 0.10.7", "educe", "enum_dispatch", @@ -12789,14 +12718,14 @@ dependencies = [ "pin-project", "postage", "rand 0.9.2", - "rand_core 0.9.3", + "rand_core 0.9.5", "safelog", "slotmap-careful", "smallvec", "static_assertions", "subtle", "sync_wrapper 1.0.2", - "thiserror 2.0.17", + "thiserror 2.0.18", "tokio", "tokio-util", "tor-async-utils", @@ -12832,7 +12761,7 @@ dependencies = [ "caret", "paste", "serde_with 3.16.1", - "thiserror 2.0.17", + "thiserror 2.0.18", "tor-bytes", ] @@ -12842,7 +12771,7 @@ version = "0.37.0" source = "git+https://github.com/eigenwallet/arti?branch=downgraded_rusqlite_arti_1_8_0#2a5db5823e2a5eb413d8ad433a4d3aba902bac07" dependencies = [ "derive-deftly", - "derive_more 2.1.0", + "derive_more 2.1.1", "humantime", "tor-cert", "tor-checkable", @@ -12875,7 +12804,7 @@ dependencies = [ "async_executors", "asynchronous-codec 0.7.0", "coarsetime", - "derive_more 2.1.0", + "derive_more 2.1.1", "dyn-clone", "educe", "futures", @@ -12885,9 +12814,9 @@ dependencies = [ "paste", "pin-project", "rustls-pki-types", - "rustls-webpki 0.103.8", - "socket2 0.6.1", - "thiserror 2.0.17", + "rustls-webpki 0.103.9", + "socket2 0.6.2", + "thiserror 2.0.18", "tokio", "tokio-util", "tor-error", @@ -12905,7 +12834,7 @@ dependencies = [ "assert_matches", "async-trait", "derive-deftly", - "derive_more 2.1.0", + "derive_more 2.1.1", "educe", "futures", "humantime", @@ -12915,7 +12844,7 @@ dependencies = [ "priority-queue", "slotmap-careful", "strum 0.27.2", - "thiserror 2.0.17", + "thiserror 2.0.18", "tor-error", "tor-general-addr", "tor-rtcompat", @@ -12935,7 +12864,7 @@ dependencies = [ "educe", "safelog", "subtle", - "thiserror 2.0.17", + "thiserror 2.0.18", "tor-bytes", "tor-error", ] @@ -12946,17 +12875,17 @@ version = "0.37.0" source = "git+https://github.com/eigenwallet/arti?branch=downgraded_rusqlite_arti_1_8_0#2a5db5823e2a5eb413d8ad433a4d3aba902bac07" dependencies = [ "derive-deftly", - "derive_more 2.1.0", + "derive_more 2.1.1", "serde", - "thiserror 2.0.17", + "thiserror 2.0.18", "tor-memquota", ] [[package]] name = "tower" -version = "0.5.2" +version = "0.5.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d039ad9159c98b70ecfd540b2573b97f7f52c3e8d9f8ad57a24b916a536975f9" +checksum = "ebe5ef63511595f1344e2d5cfa636d973292adc0eec1f0ad45fae9f0851ab1d4" dependencies = [ "futures-core", "futures-util", @@ -13000,9 +12929,9 @@ checksum = "8df9b6e13f2d32c91b9bd719c00d1958837bc7dec474d94952798cc8e69eeec3" [[package]] name = "tracing" -version = "0.1.43" +version = "0.1.44" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2d15d90a0b5c19378952d479dc858407149d7bb45a14de0142f6c534b16fc647" +checksum = "63e71662fa4b2a2c3a26f570f037eb95bb1f85397f3cd8076caed2f026a6d100" dependencies = [ "log", "pin-project-lite", @@ -13017,7 +12946,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "786d480bce6247ab75f005b14ae1624ad978d3029d9113f0a22fa1ac773faeaf" dependencies = [ "crossbeam-channel", - "thiserror 2.0.17", + "thiserror 2.0.18", "time", "tracing-subscriber", ] @@ -13030,14 +12959,14 @@ checksum = "7490cfa5ec963746568740651ac6781f701c9c5ea257c58e057f3ba8cf69e8da" dependencies = [ "proc-macro2", "quote", - "syn 2.0.111", + "syn 2.0.114", ] [[package]] name = "tracing-core" -version = "0.1.35" +version = "0.1.36" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7a04e24fab5c89c6a36eb8558c9656f30d81de51dfa4d3b45f26b21d61fa0a6c" +checksum = "db97caf9d906fbde555dd62fa95ddba9eecfd14cb388e4f491a66d74cd5fb79a" dependencies = [ "once_cell", "valuable", @@ -13113,14 +13042,14 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "04659ddb06c87d233c566112c1c9c5b9e98256d9af50ec3bc9c8327f873a7568" dependencies = [ "quote", - "syn 2.0.111", + "syn 2.0.114", ] [[package]] name = "tray-icon" -version = "0.21.2" +version = "0.21.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e3d5572781bee8e3f994d7467084e1b1fd7a93ce66bd480f8156ba89dee55a2b" +checksum = "a5e85aa143ceb072062fc4d6356c1b520a51d636e7bc8e77ec94be3608e5e80c" dependencies = [ "crossbeam-channel", "dirs", @@ -13134,7 +13063,7 @@ dependencies = [ "once_cell", "png 0.17.16", "serde", - "thiserror 2.0.17", + "thiserror 2.0.18", "windows-sys 0.60.2", ] @@ -13179,9 +13108,9 @@ dependencies = [ [[package]] name = "typed-index-collections" -version = "3.4.0" +version = "3.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5318ee4ce62a4e948a33915574021a7a953d83e84fba6e25c72ffcfd7dad35ff" +checksum = "898160f1dfd383b4e92e17f0512a7d62f3c51c44937b23b6ffc3a1614a8eaccd" dependencies = [ "bincode 2.0.1", "serde", @@ -13201,9 +13130,9 @@ checksum = "562d481066bde0658276a35467c4af00bdc6ee726305698a55b86e61d7ad82bb" [[package]] name = "typeshare" -version = "1.0.4" +version = "1.0.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "19be0f411120091e76e13e5a0186d8e2bcc3e7e244afdb70152197f1a8486ceb" +checksum = "da1bf9fe204f358ffea7f8f779b53923a20278b3ab8e8d97962c5e1b3a54edb7" dependencies = [ "chrono", "serde", @@ -13218,7 +13147,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "621963e302416b389a1ec177397e9e62de849a78bd8205d428608553def75350" dependencies = [ "quote", - "syn 2.0.111", + "syn 2.0.114", ] [[package]] @@ -13308,9 +13237,9 @@ dependencies = [ [[package]] name = "unicase" -version = "2.8.1" +version = "2.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "75b844d17643ee918803943289730bec8aac480150456169e647ed0b576ba539" +checksum = "dbc4bc3a9f746d862c45cb89d705aa10f187bb96c76001afab07a0d35ce60142" [[package]] name = "unicode-bidi" @@ -13411,7 +13340,7 @@ name = "unstoppableswap-gui-rs" version = "3.6.7" dependencies = [ "dfx-swiss-sdk", - "rustls 0.23.35", + "rustls 0.23.36", "serde", "serde_json", "swap", @@ -13452,14 +13381,15 @@ checksum = "6d49784317cd0d1ee7ec5c716dd598ec5b4483ea832a2dced265471cc0f690ae" [[package]] name = "url" -version = "2.5.7" +version = "2.5.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "08bc136a29a3d1758e07a9cca267be308aeebf5cfd5a10f3f67ab2097683ef5b" +checksum = "ff67a8a4397373c3ef660812acab3268222035010ab8680ec4215f38ba3d0eed" dependencies = [ "form_urlencoded", "idna", "percent-encoding", "serde", + "serde_derive", ] [[package]] @@ -13494,9 +13424,9 @@ checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" [[package]] name = "uuid" -version = "1.19.0" +version = "1.20.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e2e054861b4bd027cd373e18e8d8d8e6548085000e41290d95ce0c373a654b4a" +checksum = "ee48d38b119b0cd71fe4141b30f5ba9c7c5d9f4e7a3a8b4a674e4b6ef789976f" dependencies = [ "getrandom 0.3.4", "js-sys", @@ -13537,15 +13467,15 @@ dependencies = [ [[package]] name = "vergen" -version = "9.0.6" +version = "9.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6b2bf58be11fc9414104c6d3a2e464163db5ef74b12296bda593cac37b6e4777" +checksum = "b849a1f6d8639e8de261e81ee0fc881e3e3620db1af9f2e0da015d4382ceaf75" dependencies = [ "anyhow", "derive_builder", "rustversion", "time", - "vergen-lib", + "vergen-lib 9.1.0", ] [[package]] @@ -13559,8 +13489,8 @@ dependencies = [ "git2", "rustversion", "time", - "vergen 9.0.6", - "vergen-lib", + "vergen 9.1.0", + "vergen-lib 0.1.6", ] [[package]] @@ -13574,6 +13504,17 @@ dependencies = [ "rustversion", ] +[[package]] +name = "vergen-lib" +version = "9.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b34a29ba7e9c59e62f229ae1932fb1b8fb8a6fdcc99215a641913f5f5a59a569" +dependencies = [ + "anyhow", + "derive_builder", + "rustversion", +] + [[package]] name = "version-compare" version = "0.2.1" @@ -13600,7 +13541,7 @@ checksum = "d674d135b4a8c1d7e813e2f8d1c9a58308aee4a680323066025e53132218bd91" dependencies = [ "proc-macro2", "quote", - "syn 2.0.111", + "syn 2.0.114", ] [[package]] @@ -13671,9 +13612,9 @@ checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b" [[package]] name = "wasip2" -version = "1.0.1+wasi-0.2.4" +version = "1.0.2+wasi-0.2.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0562428422c63773dad2c345a1882263bbf4d65cf3f42e90921f787ef5ad58e7" +checksum = "9517f9239f02c069db75e65f174b3da828fe5f5b945c4dd26bd25d89c03ebcf5" dependencies = [ "wit-bindgen", ] @@ -13686,18 +13627,18 @@ checksum = "b8dad83b4f25e74f184f64c43b150b91efe7647395b42289f38e50566d82855b" [[package]] name = "wasix" -version = "0.12.21" +version = "0.13.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c1fbb4ef9bbca0c1170e0b00dd28abc9e3b68669821600cad1caaed606583c6d" +checksum = "1757e0d1f8456693c7e5c6c629bdb54884e032aa0bb53c155f6a39f94440d332" dependencies = [ "wasi 0.11.1+wasi-snapshot-preview1", ] [[package]] name = "wasm-bindgen" -version = "0.2.106" +version = "0.2.108" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0d759f433fa64a2d763d1340820e46e111a7a5ab75f993d1852d70b03dbb80fd" +checksum = "64024a30ec1e37399cf85a7ffefebdb72205ca1c972291c51512360d90bd8566" dependencies = [ "cfg-if", "once_cell", @@ -13708,11 +13649,12 @@ dependencies = [ [[package]] name = "wasm-bindgen-futures" -version = "0.4.56" +version = "0.4.58" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "836d9622d604feee9e5de25ac10e3ea5f2d65b41eac0d9ce72eb5deae707ce7c" +checksum = "70a6e77fd0ae8029c9ea0063f87c46fde723e7d887703d74ad2616d792e51e6f" dependencies = [ "cfg-if", + "futures-util", "js-sys", "once_cell", "wasm-bindgen", @@ -13721,9 +13663,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro" -version = "0.2.106" +version = "0.2.108" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "48cb0d2638f8baedbc542ed444afc0644a29166f1595371af4fecf8ce1e7eeb3" +checksum = "008b239d9c740232e71bd39e8ef6429d27097518b6b30bdf9086833bd5b6d608" dependencies = [ "quote", "wasm-bindgen-macro-support", @@ -13731,22 +13673,22 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro-support" -version = "0.2.106" +version = "0.2.108" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cefb59d5cd5f92d9dcf80e4683949f15ca4b511f4ac0a6e14d4e1ac60c6ecd40" +checksum = "5256bae2d58f54820e6490f9839c49780dff84c65aeab9e772f15d5f0e913a55" dependencies = [ "bumpalo", "proc-macro2", "quote", - "syn 2.0.111", + "syn 2.0.114", "wasm-bindgen-shared", ] [[package]] name = "wasm-bindgen-shared" -version = "0.2.106" +version = "0.2.108" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cbc538057e648b67f72a982e708d485b2efa771e1ac05fec311f9f63e5800db4" +checksum = "1f01b580c9ac74c8d8f0c0e4afb04eeef2acf145458e52c03845ee9cd23e3d12" dependencies = [ "unicode-ident", ] @@ -13766,23 +13708,22 @@ dependencies = [ [[package]] name = "wayland-backend" -version = "0.3.11" +version = "0.3.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "673a33c33048a5ade91a6b139580fa174e19fb0d23f396dca9fa15f2e1e49b35" +checksum = "fee64194ccd96bf648f42a65a7e589547096dfa702f7cadef84347b66ad164f9" dependencies = [ "cc", "downcast-rs 1.2.1", "rustix", - "scoped-tls", "smallvec", "wayland-sys", ] [[package]] name = "wayland-client" -version = "0.31.11" +version = "0.31.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c66a47e840dc20793f2264eb4b3e4ecb4b75d91c0dd4af04b456128e0bdd449d" +checksum = "b8e6faa537fbb6c186cb9f1d41f2f811a4120d1b57ec61f50da451a0c5122bec" dependencies = [ "bitflags 2.10.0", "rustix", @@ -13792,9 +13733,9 @@ dependencies = [ [[package]] name = "wayland-protocols" -version = "0.32.9" +version = "0.32.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "efa790ed75fbfd71283bd2521a1cfdc022aabcc28bdcff00851f9e4ae88d9901" +checksum = "baeda9ffbcfc8cd6ddaade385eaf2393bd2115a69523c735f12242353c3df4f3" dependencies = [ "bitflags 2.10.0", "wayland-backend", @@ -13804,9 +13745,9 @@ dependencies = [ [[package]] name = "wayland-protocols-wlr" -version = "0.3.9" +version = "0.3.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "efd94963ed43cf9938a090ca4f7da58eb55325ec8200c3848963e98dc25b78ec" +checksum = "e9597cdf02cf0c34cd5823786dce6b5ae8598f05c2daf5621b6e178d4f7345f3" dependencies = [ "bitflags 2.10.0", "wayland-backend", @@ -13817,23 +13758,21 @@ dependencies = [ [[package]] name = "wayland-scanner" -version = "0.31.7" +version = "0.31.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "54cb1e9dc49da91950bdfd8b848c49330536d9d1fb03d4bfec8cae50caa50ae3" +checksum = "5423e94b6a63e68e439803a3e153a9252d5ead12fd853334e2ad33997e3889e3" dependencies = [ "proc-macro2", - "quick-xml 0.37.5", + "quick-xml", "quote", ] [[package]] name = "wayland-sys" -version = "0.31.7" +version = "0.31.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "34949b42822155826b41db8e5d0c1be3a2bd296c747577a43a3e6daefc296142" +checksum = "1e6dbfc3ac5ef974c92a2235805cc0114033018ae1290a72e474aa8b28cbbdfd" dependencies = [ - "dlib", - "log", "pkg-config", ] @@ -13845,9 +13784,9 @@ checksum = "323f4da9523e9a669e1eaf9c6e763892769b1d38c623913647bfdc1532fe4549" [[package]] name = "web-sys" -version = "0.3.83" +version = "0.3.85" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9b32828d774c412041098d182a8b38b16ea816958e07cf40eec2bc080ae137ac" +checksum = "312e32e551d92129218ea9a2452120f4aabc03529ef03e4d0d82fb2780608598" dependencies = [ "js-sys", "wasm-bindgen", @@ -13933,14 +13872,14 @@ version = "0.26.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "75c7f0ef91146ebfb530314f5f1d24528d7f0767efbfd31dce919275413e393e" dependencies = [ - "webpki-root-certs 1.0.4", + "webpki-root-certs 1.0.5", ] [[package]] name = "webpki-root-certs" -version = "1.0.4" +version = "1.0.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ee3e3b5f5e80bc89f30ce8d0343bf4e5f12341c51f3e26cbeecbc7c85443e85b" +checksum = "36a29fc0408b113f68cf32637857ab740edfafdf460c326cd2afaa2d84cc05dc" dependencies = [ "rustls-pki-types", ] @@ -13971,18 +13910,18 @@ checksum = "5f20c57d8d7db6d3b86154206ae5d8fba62dd39573114de97c2cb0578251f8e1" [[package]] name = "webpki-roots" -version = "1.0.4" +version = "1.0.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b2878ef029c47c6e8cf779119f20fcf52bde7ad42a731b2a304bc221df17571e" +checksum = "12bed680863276c63889429bfd6cab3b99943659923822de1c8a39c49e4d722c" dependencies = [ "rustls-pki-types", ] [[package]] name = "webview2-com" -version = "0.38.0" +version = "0.38.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d4ba622a989277ef3886dd5afb3e280e3dd6d974b766118950a08f8f678ad6a4" +checksum = "7130243a7a5b33c54a444e54842e6a9e133de08b5ad7b5861cd8ed9a6a5bc96a" dependencies = [ "webview2-com-macros", "webview2-com-sys", @@ -13994,22 +13933,22 @@ dependencies = [ [[package]] name = "webview2-com-macros" -version = "0.8.0" +version = "0.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1d228f15bba3b9d56dde8bddbee66fa24545bd17b48d5128ccf4a8742b18e431" +checksum = "67a921c1b6914c367b2b823cd4cde6f96beec77d30a939c8199bb377cf9b9b54" dependencies = [ "proc-macro2", "quote", - "syn 2.0.111", + "syn 2.0.114", ] [[package]] name = "webview2-com-sys" -version = "0.38.0" +version = "0.38.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "36695906a1b53a3bf5c4289621efedac12b73eeb0b89e7e1a89b517302d5d75c" +checksum = "381336cfffd772377d291702245447a5251a2ffa5bad679c99e61bc48bacbf9c" dependencies = [ - "thiserror 2.0.17", + "thiserror 2.0.18", "windows 0.61.3", "windows-core 0.61.2", ] @@ -14169,7 +14108,7 @@ checksum = "053e2e040ab57b9dc951b72c264860db7eb3b0200ba345b4e4c3b14f67855ddf" dependencies = [ "proc-macro2", "quote", - "syn 2.0.111", + "syn 2.0.114", ] [[package]] @@ -14180,7 +14119,7 @@ checksum = "3f316c4a2570ba26bbec722032c4099d8c8bc095efccdc15688708623367e358" dependencies = [ "proc-macro2", "quote", - "syn 2.0.111", + "syn 2.0.114", ] [[package]] @@ -14616,9 +14555,9 @@ dependencies = [ [[package]] name = "wit-bindgen" -version = "0.46.0" +version = "0.51.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f17a85883d4e6d00e8a97c586de764dabcc06133f7f1d55dce5cdc070ad7fe59" +checksum = "d7249219f66ced02969388cf2bb044a09756a083d0fab1e566056b04d9fbcaa5" [[package]] name = "wl-clipboard-rs" @@ -14630,7 +14569,7 @@ dependencies = [ "log", "os_pipe", "rustix", - "thiserror 2.0.17", + "thiserror 2.0.18", "tree_magic_mini", "wayland-backend", "wayland-client", @@ -14678,7 +14617,7 @@ dependencies = [ "sha2", "soup3", "tao-macros", - "thiserror 2.0.17", + "thiserror 2.0.18", "url", "webkit2gtk", "webkit2gtk-sys", @@ -14872,15 +14811,15 @@ checksum = "b659052874eb698efe5b9e8cf382204678a0086ebf46982b79d6ca3182927e5d" dependencies = [ "proc-macro2", "quote", - "syn 2.0.111", + "syn 2.0.114", "synstructure 0.13.2", ] [[package]] name = "zbus" -version = "5.12.0" +version = "5.13.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b622b18155f7a93d1cd2dc8c01d2d6a44e08fb9ebb7b3f9e6ed101488bad6c91" +checksum = "1bfeff997a0aaa3eb20c4652baf788d2dfa6d2839a0ead0b3ff69ce2f9c4bdd1" dependencies = [ "async-broadcast", "async-executor", @@ -14896,11 +14835,11 @@ dependencies = [ "futures-core", "futures-lite", "hex", - "nix 0.30.1", + "libc", "ordered-stream", + "rustix", "serde", "serde_repr", - "tokio", "tracing", "uds_windows", "uuid", @@ -14913,14 +14852,14 @@ dependencies = [ [[package]] name = "zbus_macros" -version = "5.12.0" +version = "5.13.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1cdb94821ca8a87ca9c298b5d1cbd80e2a8b67115d99f6e4551ac49e42b6a314" +checksum = "0bbd5a90dbe8feee5b13def448427ae314ccd26a49cac47905cafefb9ff846f1" dependencies = [ "proc-macro-crate 3.4.0", "proc-macro2", "quote", - "syn 2.0.111", + "syn 2.0.114", "zbus_names", "zvariant", "zvariant_utils", @@ -14928,34 +14867,33 @@ dependencies = [ [[package]] name = "zbus_names" -version = "4.2.0" +version = "4.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7be68e64bf6ce8db94f63e72f0c7eb9a60d733f7e0499e628dfab0f84d6bcb97" +checksum = "ffd8af6d5b78619bab301ff3c560a5bd22426150253db278f164d6cf3b72c50f" dependencies = [ "serde", - "static_assertions", "winnow 0.7.14", "zvariant", ] [[package]] name = "zerocopy" -version = "0.8.31" +version = "0.8.33" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fd74ec98b9250adb3ca554bdde269adf631549f51d8a8f8f0a10b50f1cb298c3" +checksum = "668f5168d10b9ee831de31933dc111a459c97ec93225beb307aed970d1372dfd" dependencies = [ "zerocopy-derive", ] [[package]] name = "zerocopy-derive" -version = "0.8.31" +version = "0.8.33" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d8a8d209fdf45cf5138cbb5a506f6b52522a25afccc534d1475dad8e31105c6a" +checksum = "2c7962b26b0a8685668b671ee4b54d007a67d4eaf05fda79ac0ecf41e32270f1" dependencies = [ "proc-macro2", "quote", - "syn 2.0.111", + "syn 2.0.114", ] [[package]] @@ -14975,7 +14913,7 @@ checksum = "d71e5d6e06ab090c67b5e44993ec16b72dcbaabc526db883a360057678b48502" dependencies = [ "proc-macro2", "quote", - "syn 2.0.111", + "syn 2.0.114", "synstructure 0.13.2", ] @@ -14990,13 +14928,13 @@ dependencies = [ [[package]] name = "zeroize_derive" -version = "1.4.2" +version = "1.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ce36e65b0d2999d2aafac989fb249189a141aee1f53c612c1f37d72631959f69" +checksum = "85a5b4158499876c763cb03bc4e49185d3cccbabb15b33c627f7884f43db852e" dependencies = [ "proc-macro2", "quote", - "syn 2.0.111", + "syn 2.0.114", ] [[package]] @@ -15030,7 +14968,7 @@ checksum = "eadce39539ca5cb3985590102671f2567e659fca9666581ad3411d59207951f3" dependencies = [ "proc-macro2", "quote", - "syn 2.0.111", + "syn 2.0.114", ] [[package]] @@ -15048,7 +14986,7 @@ dependencies = [ "flate2", "getrandom 0.3.4", "hmac", - "indexmap 2.12.1", + "indexmap 2.13.0", "liblzma", "memchr", "pbkdf2", @@ -15062,9 +15000,15 @@ dependencies = [ [[package]] name = "zlib-rs" -version = "0.5.4" +version = "0.5.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "51f936044d677be1a1168fae1d03b583a285a5dd9d8cbf7b24c23aa1fc775235" +checksum = "40990edd51aae2c2b6907af74ffb635029d5788228222c4bb811e9351c0caad3" + +[[package]] +name = "zmij" +version = "1.0.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dfcd145825aace48cff44a8844de64bf75feec3080e0aa5cdbde72961ae51a65" [[package]] name = "zopfli" @@ -15123,14 +15067,13 @@ dependencies = [ [[package]] name = "zvariant" -version = "5.8.0" +version = "5.9.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2be61892e4f2b1772727be11630a62664a1826b62efa43a6fe7449521cb8744c" +checksum = "68b64ef4f40c7951337ddc7023dd03528a57a3ce3408ee9da5e948bd29b232c4" dependencies = [ "endi", "enumflags2", "serde", - "url", "winnow 0.7.14", "zvariant_derive", "zvariant_utils", @@ -15138,26 +15081,26 @@ dependencies = [ [[package]] name = "zvariant_derive" -version = "5.8.0" +version = "5.9.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "da58575a1b2b20766513b1ec59d8e2e68db2745379f961f86650655e862d2006" +checksum = "484d5d975eb7afb52cc6b929c13d3719a20ad650fea4120e6310de3fc55e415c" dependencies = [ "proc-macro-crate 3.4.0", "proc-macro2", "quote", - "syn 2.0.111", + "syn 2.0.114", "zvariant_utils", ] [[package]] name = "zvariant_utils" -version = "3.2.1" +version = "3.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c6949d142f89f6916deca2232cf26a8afacf2b9fdc35ce766105e104478be599" +checksum = "f75c23a64ef8f40f13a6989991e643554d9bef1d682a281160cf0c1bc389c5e9" dependencies = [ "proc-macro2", "quote", "serde", - "syn 2.0.111", + "syn 2.0.114", "winnow 0.7.14", ] diff --git a/Cargo.toml b/Cargo.toml index 27bbbe56cd..e47eacde4a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -40,12 +40,12 @@ bdk_wallet = "2.0.0" bitcoin = { version = "0.32", features = ["rand", "serde"] } # monero-oxide -monero-address = { git = "https://github.com/kayabaNerve/monero-oxide.git", branch = "rpc-rewrite" } -monero-daemon-rpc = { git = "https://github.com/kayabaNerve/monero-oxide.git", branch = "rpc-rewrite" } -monero-interface = { git = "https://github.com/kayabaNerve/monero-oxide.git", branch = "rpc-rewrite" } -monero-oxide = { git = "https://github.com/kayabaNerve/monero-oxide.git", branch = "rpc-rewrite" } -monero-oxide-wallet = { git = "https://github.com/kayabaNerve/monero-oxide.git", package = "monero-wallet", branch = "rpc-rewrite" } -monero-simple-request-rpc = { git = "https://github.com/kayabaNerve/monero-oxide.git", branch = "rpc-rewrite" } +monero-address = { git = "https://github.com/kayabaNerve/monero-oxide.git" } +monero-daemon-rpc = { git = "https://github.com/kayabaNerve/monero-oxide.git" } +monero-interface = { git = "https://github.com/kayabaNerve/monero-oxide.git" } +monero-oxide = { git = "https://github.com/kayabaNerve/monero-oxide.git" } +monero-oxide-wallet = { git = "https://github.com/kayabaNerve/monero-oxide.git", package = "monero-wallet" } +monero-simple-request-rpc = { git = "https://github.com/kayabaNerve/monero-oxide.git" } # Cryptography curve25519-dalek = { version = "4", package = "curve25519-dalek", features = ["rand_core", "serde"] } @@ -101,6 +101,7 @@ tor-cell = { git = "https://github.com/eigenwallet/arti", branch = "downgraded_r tor-hsservice = { git = "https://github.com/eigenwallet/arti", branch = "downgraded_rusqlite_arti_1_8_0" } tor-proto = { git = "https://github.com/eigenwallet/arti", branch = "downgraded_rusqlite_arti_1_8_0" } tor-rtcompat = { git = "https://github.com/eigenwallet/arti", branch = "downgraded_rusqlite_arti_1_8_0" } +libp2p-tor = { path = "./libp2p-tor" } # Terminal Utilities console = "0.16" diff --git a/monero-oxide-ext/src/lib.rs b/monero-oxide-ext/src/lib.rs index 0517cafcd0..67d61344de 100644 --- a/monero-oxide-ext/src/lib.rs +++ b/monero-oxide-ext/src/lib.rs @@ -254,6 +254,14 @@ impl Amount { self.0 } + /// Get the amount in Monero. + pub fn as_xmr(self) -> f64 { + // Inefficient, but most safe way: monero-rs does it this way, too + let mut buf = String::new(); + fmt_piconero_in_xmr(self.as_pico(), &mut buf).expect("string to be writable"); + buf.parse().expect("Monero amount is floating point number") + } + /// Create an [`Amount`] with monero precision and the given number of monero, string in the format `"1.2"` or `"1"`. pub fn parse_monero(xmr: &str) -> Result { if xmr.is_empty() { diff --git a/monero-sys/build.rs b/monero-sys/build.rs index d5f60c851c..13e2a26121 100644 --- a/monero-sys/build.rs +++ b/monero-sys/build.rs @@ -144,7 +144,7 @@ fn main() { .display() .to_string(); config.define("CMAKE_TOOLCHAIN_FILE", toolchain_file.clone()); - println!("cargo:warning=Using toolchain file: {toolchain_file}"); + println!("cargo:debug=Using toolchain file: {toolchain_file}"); let depends_lib_dir = contrib_depends_dir.join(format!("{target}/lib")); @@ -443,7 +443,7 @@ fn compile_dependencies( "aarch64-apple-ios-sim" => "aarch64-apple-iossimulator".to_string(), _ => target, }; - println!("cargo:warning=Building for target: {target}"); + println!("cargo:debug=Building for target: {target}"); match target.as_str() { "x86_64-apple-darwin" @@ -459,7 +459,7 @@ fn compile_dependencies( _ => panic!("target unsupported: {target}"), } - println!("cargo:warning=Running make HOST={target} in contrib/depends",); + println!("cargo:debug=Running make HOST={target} in contrib/depends",); // Copy monero-depends to out_dir/depends in order to build the dependencies there match fs_extra::copy_items( @@ -554,7 +554,7 @@ fn apply_patches() -> Result<(), Box> { for embedded in EMBEDDED_PATCHES { println!( - "cargo:warning=Processing embedded patch: {} ({})", + "cargo:debug=Processing embedded patch: {} ({})", embedded.name, embedded.description ); @@ -567,14 +567,14 @@ fn apply_patches() -> Result<(), Box> { } println!( - "cargo:warning=Found {} file(s) in patch {}", + "cargo:debug=Found {} file(s) in patch {}", file_patches.len(), embedded.name ); // Apply each file patch individually for (file_path, patch_content) in file_patches { - println!("cargo:warning=Applying patch to file: {file_path}"); + println!("cargo:debug=Applying patch to file: {file_path}"); // Parse the individual file patch let patch = diffy::Patch::from_str(&patch_content) @@ -591,7 +591,7 @@ fn apply_patches() -> Result<(), Box> { // Check if patch is already applied by trying to reverse it if diffy::apply(¤t, &patch.reverse()).is_ok() { - println!("cargo:warning=Patch for {file_path} already applied – skipping",); + println!("cargo:debug=Patch for {file_path} already applied – skipping",); continue; } @@ -601,11 +601,11 @@ fn apply_patches() -> Result<(), Box> { fs::write(&target_path, patched) .map_err(|e| format!("Failed to write {file_path}: {e}"))?; - println!("cargo:warning=Successfully applied patch to: {file_path}"); + println!("cargo:debug=Successfully applied patch to: {file_path}"); } println!( - "cargo:warning=Successfully applied all file patches for: {} ({})", + "cargo:debug=Successfully applied all file patches for: {} ({})", embedded.name, embedded.description ); } diff --git a/monero-tests/Cargo.toml b/monero-tests/Cargo.toml index d3e25d28c5..9b83fb1e2a 100644 --- a/monero-tests/Cargo.toml +++ b/monero-tests/Cargo.toml @@ -3,6 +3,7 @@ name = "monero-tests" version = "0.1.0" edition = "2024" + [dev-dependencies] monero-address = { workspace = true } monero-daemon-rpc = { workspace = true } @@ -22,5 +23,7 @@ tracing-subscriber = { workspace = true } uuid = { workspace = true } zeroize = { workspace = true } -[lints] -workspace = true +# Each test counts as a seperate crate, and would individually get +# warnigns for "unused" dependencies. This silences them +[lints.rust] +unused_crate_dependencies = "allow" diff --git a/monero-tests/tests/subaddresses.rs b/monero-tests/tests/subaddresses.rs index 29e5704f10..a8a2ae4ded 100644 --- a/monero-tests/tests/subaddresses.rs +++ b/monero-tests/tests/subaddresses.rs @@ -53,16 +53,16 @@ async fn subaddress_methods_and_balances() -> anyhow::Result<()> { ); // Mine a block to confirm the transaction and make funds visible - monero.generate_block().await?; + monero.generate_blocks().await?; // Import tx keys so Alice scans this transaction explicitly let sa1_txkey = tx_receipt .tx_keys - .get(&alice_sa1) + .get(&alice_sa1.to_string()) .context("tx key not found for alice subaddress 1")?; let sa2_txkey = tx_receipt .tx_keys - .get(&alice_sa2) + .get(&alice_sa2.to_string()) .context("tx key not found for alice subaddress 2")?; tracing::info!("Importing tx keys for Alice subaddresses"); @@ -82,7 +82,7 @@ async fn subaddress_methods_and_balances() -> anyhow::Result<()> { let bal_sa1 = per_sub.get(&1).copied().unwrap_or(0); let bal_sa2 = per_sub.get(&2).copied().unwrap_or(0); - tracing::info!(bal_sa1=%monero::Amount::from_pico(bal_sa1), bal_sa2=%monero::Amount::from_pico(bal_sa2), "Per-subaddress balances"); + tracing::info!(bal_sa1=%monero_oxide_ext::Amount::from_pico(bal_sa1), bal_sa2=%monero_oxide_ext::Amount::from_pico(bal_sa2), "Per-subaddress balances"); assert!(bal_sa1 > 0, "Subaddress 1 expected to have received funds"); assert!(bal_sa2 > 0, "Subaddress 2 expected to have received funds"); diff --git a/src-tauri/src/commands.rs b/src-tauri/src/commands.rs index c22cfdf026..39d5062c11 100644 --- a/src-tauri/src/commands.rs +++ b/src-tauri/src/commands.rs @@ -80,7 +80,8 @@ macro_rules! generate_command_handlers { get_context_status, get_monero_subaddresses, create_monero_subaddress, - set_monero_subaddress_label + set_monero_subaddress_label, + refresh_p2p ] }; } diff --git a/swap-controller/src/main.rs b/swap-controller/src/main.rs index f5439183e2..d68e2f58c5 100644 --- a/swap-controller/src/main.rs +++ b/swap-controller/src/main.rs @@ -94,13 +94,14 @@ async fn dispatch(cmd: Cmd, client: impl AsbApiClient) -> anyhow::Result<()> { table.add_row(["No swaps found"]); } else { for swap in &swaps { - let xmr = monero:Amount::from_pico(swap.xmr_amount); + let xmr = monero_oxide_ext::Amount::from_pico(swap.xmr_amount); table.add_row([ &swap.swap_id, &swap.start_date, &swap.state, &swap.btc_lock_txid, &swap.btc_amount.to_string(), + // Floating point may introduce very small inaccuracies here &format!("{:.12} XMR", xmr.as_xmr()), &swap.exchange_rate.to_string(), &swap.peer_id, diff --git a/swap-core/src/monero/primitives.rs b/swap-core/src/monero/primitives.rs index dde35855b7..beac72791c 100644 --- a/swap-core/src/monero/primitives.rs +++ b/swap-core/src/monero/primitives.rs @@ -443,7 +443,6 @@ pub struct OverflowError(pub String); #[cfg(test)] mod tests { use super::*; - use crate::compat::IntoDalekNg; #[test] fn display_monero_min() { diff --git a/swap-machine/src/alice/mod.rs b/swap-machine/src/alice/mod.rs index 6b63dc2e35..c76fc1bd98 100644 --- a/swap-machine/src/alice/mod.rs +++ b/swap-machine/src/alice/mod.rs @@ -1,7 +1,7 @@ #![allow(non_snake_case)] use crate::common::{CROSS_CURVE_PROOF_SYSTEM, Message0, Message1, Message2, Message3, Message4}; -use anyhow::{Context, Result, anyhow, bail}; +use anyhow::{Context, Result, bail}; use rand::{CryptoRng, RngCore}; use serde::{Deserialize, Serialize}; use sigma_fun::ext::dl_secp256k1_ed25519_eq::CrossCurveDLEQProof; @@ -13,9 +13,9 @@ use swap_core::bitcoin::{ TxRefundAmnesty, TxRefundBurn, Txid, current_epoch, }; use swap_core::compat::{IntoDalek, IntoDalekNg, IntoMoneroOxide}; -use swap_core::monero; use swap_core::monero::ScalarExt; use swap_core::monero::primitives::{AmountExt, BlockHeight, TransferProof, TransferRequest}; +use swap_core::monero::{self, Scalar}; use swap_env::env::Config; use uuid::Uuid; @@ -850,16 +850,14 @@ impl State3 { &self, signed_partial_refund_tx: Arc, ) -> Result { - Ok(monero::PrivateKey::from_scalar( - self.tx_partial_refund()? - .extract_monero_private_key( - signed_partial_refund_tx, - self.s_a, - self.a.clone(), - self.S_b_bitcoin, - )? - .into_dalek_ng(), - )) + Ok(monero::PrivateKey::from_scalar(Scalar::from( + self.tx_partial_refund()?.extract_monero_private_key( + signed_partial_refund_tx, + self.s_a.into(), + self.a.clone(), + self.S_b_bitcoin, + )?, + ))) } pub async fn check_for_tx_cancel( diff --git a/swap-machine/src/lib.rs b/swap-machine/src/lib.rs index b3017577fc..27c9130b1a 100644 --- a/swap-machine/src/lib.rs +++ b/swap-machine/src/lib.rs @@ -11,7 +11,6 @@ mod tests { use monero_oxide_ext::PrivateKey; use rand::rngs::OsRng; use swap_core::bitcoin::*; - use swap_core::compat::IntoDalekNg; use swap_core::monero::{Scalar, TransferProof}; use swap_env::env::{GetConfig, Regtest}; use uuid::Uuid; diff --git a/swap-orchestrator/tests/spec.rs b/swap-orchestrator/tests/spec.rs index 94a874945e..a24a882031 100644 --- a/swap-orchestrator/tests/spec.rs +++ b/swap-orchestrator/tests/spec.rs @@ -1,3 +1,5 @@ +#![allow(unused_crate_dependencies)] + use swap_orchestrator::compose::{ IntoSpec, OrchestratorDirectories, OrchestratorImage, OrchestratorImages, OrchestratorInput, OrchestratorNetworks, OrchestratorPorts, diff --git a/swap-p2p/Cargo.toml b/swap-p2p/Cargo.toml index 913c3452d7..3c61b73963 100644 --- a/swap-p2p/Cargo.toml +++ b/swap-p2p/Cargo.toml @@ -16,6 +16,7 @@ swap-serde = { path = "../swap-serde" } async-trait = { workspace = true, optional = true } libp2p = { workspace = true, features = ["serde", "request-response", "rendezvous", "cbor", "json", "ping", "identify"] } + # Serialization asynchronous-codec = "0.7.0" serde = { workspace = true } @@ -57,5 +58,11 @@ test-support = ["libp2p/noise", "libp2p/tcp", "libp2p/yamux", "libp2p/tokio", "l async-trait = { workspace = true } libp2p = { workspace = true, features = ["serde", "request-response", "rendezvous", "cbor", "json", "ping", "identify", "noise", "tcp", "yamux", "tokio", "dns"] } +# Networking (for the example) +libp2p-tor = { workspace = true } +tor-rtcompat = { workspace = true } +arti-client = {workspace = true } +tracing-subscriber = { workspace = true } + [lints] workspace = true diff --git a/swap-p2p/examples/fetch_quotes.rs b/swap-p2p/examples/fetch_quotes.rs index 23a0a5ba58..5ebdf7d283 100644 --- a/swap-p2p/examples/fetch_quotes.rs +++ b/swap-p2p/examples/fetch_quotes.rs @@ -1,22 +1,20 @@ +#![allow(unused_crate_dependencies)] + use anyhow::Result; -use arti_client::{config::TorClientConfigBuilder, TorClient}; +use arti_client::{TorClient, config::TorClientConfigBuilder}; use futures::StreamExt; use libp2p::core::muxing::StreamMuxerBox; use libp2p::core::transport::Boxed; use libp2p::core::upgrade::Version; -use libp2p::multiaddr::Protocol; -use libp2p::swarm::dial_opts::DialOpts; use libp2p::swarm::NetworkBehaviour; +use libp2p::{PeerId, SwarmBuilder, Transport, identity, yamux}; use libp2p::{dns, tcp}; -use libp2p::{identify, noise, ping, request_response}; -use libp2p::{identity, yamux, Multiaddr, PeerId, SwarmBuilder, Transport}; +use libp2p::{identify, noise, ping}; use libp2p_tor::{AddressConversion, TorTransport}; -use std::collections::{HashMap, VecDeque}; use std::sync::Arc; use std::time::Duration; use swap_p2p::libp2p_ext::MultiAddrExt; -use swap_p2p::protocols::quote::BidQuote; -use swap_p2p::protocols::{quote, quotes_cached, rendezvous}; +use swap_p2p::protocols::{quotes_cached, rendezvous}; use tor_rtcompat::tokio::TokioRustlsRuntime; const USE_TOR: bool = true; @@ -130,7 +128,7 @@ async fn main() -> Result<()> { match event { libp2p::swarm::SwarmEvent::Behaviour(event) => match event { BehaviourEvent::Rendezvous(event) => match event { - rendezvous::discovery::Event::DiscoveredPeer { peer_id } => {} + rendezvous::discovery::Event::DiscoveredPeer { .. } => {} }, BehaviourEvent::Quote(quotes_cached::Event::CachedQuotes { quotes }) => { println!("================"); @@ -150,7 +148,7 @@ async fn main() -> Result<()> { } _ => {} }, - libp2p::swarm::SwarmEvent::ConnectionEstablished { peer_id, .. } => {} + libp2p::swarm::SwarmEvent::ConnectionEstablished { .. } => {} _ => {} } } diff --git a/swap-p2p/src/protocols/swap_setup/alice.rs b/swap-p2p/src/protocols/swap_setup/alice.rs index e96fd5192a..53427b7c7e 100644 --- a/swap-p2p/src/protocols/swap_setup/alice.rs +++ b/swap-p2p/src/protocols/swap_setup/alice.rs @@ -505,7 +505,7 @@ async fn run_swap_setup( let unlocked = wallet_snapshot.unlocked_balance; let needed_balance = xmr + wallet_snapshot.lock_fee.into(); - if unlocked.as_piconero() < needed_balance.as_pico() { + if unlocked.as_pico() < needed_balance.as_pico() { tracing::warn!( unlocked_balance = %unlocked, needed_balance = %needed_balance, diff --git a/swap/src/asb/event_loop.rs b/swap/src/asb/event_loop.rs index 474c838693..a6b0e014f9 100644 --- a/swap/src/asb/event_loop.rs +++ b/swap/src/asb/event_loop.rs @@ -13,7 +13,6 @@ use crate::protocol::alice::swap::has_already_processed_enc_sig; use crate::protocol::alice::{AliceState, State3, Swap, TipConfig}; use crate::protocol::{Database, State}; use anyhow::{anyhow, Context, Result}; -use bdk::bitcoin::hashes::sha1; use bitcoin_wallet::BitcoinWallet; use futures::future; use futures::future::{BoxFuture, FutureExt}; diff --git a/swap/src/asb/rpc/server.rs b/swap/src/asb/rpc/server.rs index cd044ebaaa..e8aeb94c52 100644 --- a/swap/src/asb/rpc/server.rs +++ b/swap/src/asb/rpc/server.rs @@ -199,7 +199,7 @@ impl AsbApiServer for RpcImpl { state: current_alice.to_string(), btc_lock_txid: state3.tx_lock.txid().to_string(), btc_amount: state3.btc, - xmr_amount: state3.xmr.as_piconero(), + xmr_amount: state3.xmr.as_pico(), exchange_rate, peer_id: peer_id.to_string(), completed: is_complete(¤t_alice), diff --git a/swap/tests/partial_refund_alice_grants_final_amnesty.rs b/swap/tests/partial_refund_alice_grants_final_amnesty.rs index b91673e27f..cc368c8d6d 100644 --- a/swap/tests/partial_refund_alice_grants_final_amnesty.rs +++ b/swap/tests/partial_refund_alice_grants_final_amnesty.rs @@ -2,7 +2,6 @@ pub mod harness; use std::time::Duration; -use harness::alice_run_until::{is_btc_refund_burn_confirmed, is_xmr_lock_transaction_sent}; use harness::FastAmnestyConfig; use rust_decimal::Decimal; use swap::asb::FixedRate; @@ -12,7 +11,7 @@ use swap_controller_api::AsbApiClient; use swap_env::config::RefundPolicy; use swap_machine::bob::BobState; -use crate::harness::alice_run_until::{is_btc_partially_refunded, is_xmr_refunded}; +use crate::harness::alice_run_until::is_xmr_refunded; use crate::harness::bob_run_until; /// Bob locks Btc and Alice locks Xmr. Alice does not act so Bob does a partial @@ -48,7 +47,7 @@ async fn given_partial_refund_alice_grants_final_amnesty() { // Wait for bob to partially refund - stop here such that he doesn't publish amnesty // TODO: fix regtest blocktimes instead - let bob_state = bob_state.await??; + let _bob_state = bob_state.await??; let alice_state = alice_swap.await??; assert!(matches!(alice_state, AliceState::XmrRefunded { .. })); From 4fd84d2ac69a26235ea06c54d6412a14cb062993 Mon Sep 17 00:00:00 2001 From: einliterflasche Date: Mon, 26 Jan 2026 10:26:01 +0100 Subject: [PATCH 099/113] move to yarn@4 again --- src-gui/package.json | 3 +- src-gui/yarn.lock | 6605 ++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 6607 insertions(+), 1 deletion(-) create mode 100644 src-gui/yarn.lock diff --git a/src-gui/package.json b/src-gui/package.json index 7b537185ea..6efda81102 100644 --- a/src-gui/package.json +++ b/src-gui/package.json @@ -76,5 +76,6 @@ "vite-plugin-watch": "^0.3.1", "vite-tsconfig-paths": "^4.3.2", "vitest": "^2.1.1" - } + }, + "packageManager": "yarn@4.12.0" } diff --git a/src-gui/yarn.lock b/src-gui/yarn.lock new file mode 100644 index 0000000000..8f4abf9b8e --- /dev/null +++ b/src-gui/yarn.lock @@ -0,0 +1,6605 @@ +# This file is generated by running "yarn install" inside your project. +# Manual changes might be lost - proceed with caution! + +__metadata: + version: 8 + cacheKey: 10c0 + +"@babel/code-frame@npm:^7.0.0, @babel/code-frame@npm:^7.27.1": + version: 7.27.1 + resolution: "@babel/code-frame@npm:7.27.1" + dependencies: + "@babel/helper-validator-identifier": "npm:^7.27.1" + js-tokens: "npm:^4.0.0" + picocolors: "npm:^1.1.1" + checksum: 10c0/5dd9a18baa5fce4741ba729acc3a3272c49c25cb8736c4b18e113099520e7ef7b545a4096a26d600e4416157e63e87d66db46aa3fbf0a5f2286da2705c12da00 + languageName: node + linkType: hard + +"@babel/compat-data@npm:^7.27.2": + version: 7.28.5 + resolution: "@babel/compat-data@npm:7.28.5" + checksum: 10c0/702a25de73087b0eba325c1d10979eed7c9b6662677386ba7b5aa6eace0fc0676f78343bae080a0176ae26f58bd5535d73b9d0fbb547fef377692e8b249353a7 + languageName: node + linkType: hard + +"@babel/core@npm:^7.24.4, @babel/core@npm:^7.28.0": + version: 7.28.5 + resolution: "@babel/core@npm:7.28.5" + dependencies: + "@babel/code-frame": "npm:^7.27.1" + "@babel/generator": "npm:^7.28.5" + "@babel/helper-compilation-targets": "npm:^7.27.2" + "@babel/helper-module-transforms": "npm:^7.28.3" + "@babel/helpers": "npm:^7.28.4" + "@babel/parser": "npm:^7.28.5" + "@babel/template": "npm:^7.27.2" + "@babel/traverse": "npm:^7.28.5" + "@babel/types": "npm:^7.28.5" + "@jridgewell/remapping": "npm:^2.3.5" + convert-source-map: "npm:^2.0.0" + debug: "npm:^4.1.0" + gensync: "npm:^1.0.0-beta.2" + json5: "npm:^2.2.3" + semver: "npm:^6.3.1" + checksum: 10c0/535f82238027621da6bdffbdbe896ebad3558b311d6f8abc680637a9859b96edbf929ab010757055381570b29cf66c4a295b5618318d27a4273c0e2033925e72 + languageName: node + linkType: hard + +"@babel/generator@npm:^7.28.5": + version: 7.28.5 + resolution: "@babel/generator@npm:7.28.5" + dependencies: + "@babel/parser": "npm:^7.28.5" + "@babel/types": "npm:^7.28.5" + "@jridgewell/gen-mapping": "npm:^0.3.12" + "@jridgewell/trace-mapping": "npm:^0.3.28" + jsesc: "npm:^3.0.2" + checksum: 10c0/9f219fe1d5431b6919f1a5c60db8d5d34fe546c0d8f5a8511b32f847569234ffc8032beb9e7404649a143f54e15224ecb53a3d11b6bb85c3203e573d91fca752 + languageName: node + linkType: hard + +"@babel/helper-compilation-targets@npm:^7.27.2": + version: 7.27.2 + resolution: "@babel/helper-compilation-targets@npm:7.27.2" + dependencies: + "@babel/compat-data": "npm:^7.27.2" + "@babel/helper-validator-option": "npm:^7.27.1" + browserslist: "npm:^4.24.0" + lru-cache: "npm:^5.1.1" + semver: "npm:^6.3.1" + checksum: 10c0/f338fa00dcfea931804a7c55d1a1c81b6f0a09787e528ec580d5c21b3ecb3913f6cb0f361368973ce953b824d910d3ac3e8a8ee15192710d3563826447193ad1 + languageName: node + linkType: hard + +"@babel/helper-globals@npm:^7.28.0": + version: 7.28.0 + resolution: "@babel/helper-globals@npm:7.28.0" + checksum: 10c0/5a0cd0c0e8c764b5f27f2095e4243e8af6fa145daea2b41b53c0c1414fe6ff139e3640f4e2207ae2b3d2153a1abd346f901c26c290ee7cb3881dd922d4ee9232 + languageName: node + linkType: hard + +"@babel/helper-module-imports@npm:^7.16.7, @babel/helper-module-imports@npm:^7.27.1": + version: 7.27.1 + resolution: "@babel/helper-module-imports@npm:7.27.1" + dependencies: + "@babel/traverse": "npm:^7.27.1" + "@babel/types": "npm:^7.27.1" + checksum: 10c0/e00aace096e4e29290ff8648455c2bc4ed982f0d61dbf2db1b5e750b9b98f318bf5788d75a4f974c151bd318fd549e81dbcab595f46b14b81c12eda3023f51e8 + languageName: node + linkType: hard + +"@babel/helper-module-transforms@npm:^7.28.3": + version: 7.28.3 + resolution: "@babel/helper-module-transforms@npm:7.28.3" + dependencies: + "@babel/helper-module-imports": "npm:^7.27.1" + "@babel/helper-validator-identifier": "npm:^7.27.1" + "@babel/traverse": "npm:^7.28.3" + peerDependencies: + "@babel/core": ^7.0.0 + checksum: 10c0/549be62515a6d50cd4cfefcab1b005c47f89bd9135a22d602ee6a5e3a01f27571868ada10b75b033569f24dc4a2bb8d04bfa05ee75c16da7ade2d0db1437fcdb + languageName: node + linkType: hard + +"@babel/helper-plugin-utils@npm:^7.27.1": + version: 7.27.1 + resolution: "@babel/helper-plugin-utils@npm:7.27.1" + checksum: 10c0/94cf22c81a0c11a09b197b41ab488d416ff62254ce13c57e62912c85700dc2e99e555225787a4099ff6bae7a1812d622c80fbaeda824b79baa10a6c5ac4cf69b + languageName: node + linkType: hard + +"@babel/helper-string-parser@npm:^7.27.1": + version: 7.27.1 + resolution: "@babel/helper-string-parser@npm:7.27.1" + checksum: 10c0/8bda3448e07b5583727c103560bcf9c4c24b3c1051a4c516d4050ef69df37bb9a4734a585fe12725b8c2763de0a265aa1e909b485a4e3270b7cfd3e4dbe4b602 + languageName: node + linkType: hard + +"@babel/helper-validator-identifier@npm:^7.27.1, @babel/helper-validator-identifier@npm:^7.28.5": + version: 7.28.5 + resolution: "@babel/helper-validator-identifier@npm:7.28.5" + checksum: 10c0/42aaebed91f739a41f3d80b72752d1f95fd7c72394e8e4bd7cdd88817e0774d80a432451bcba17c2c642c257c483bf1d409dd4548883429ea9493a3bc4ab0847 + languageName: node + linkType: hard + +"@babel/helper-validator-option@npm:^7.27.1": + version: 7.27.1 + resolution: "@babel/helper-validator-option@npm:7.27.1" + checksum: 10c0/6fec5f006eba40001a20f26b1ef5dbbda377b7b68c8ad518c05baa9af3f396e780bdfded24c4eef95d14bb7b8fd56192a6ed38d5d439b97d10efc5f1a191d148 + languageName: node + linkType: hard + +"@babel/helpers@npm:^7.28.4": + version: 7.28.4 + resolution: "@babel/helpers@npm:7.28.4" + dependencies: + "@babel/template": "npm:^7.27.2" + "@babel/types": "npm:^7.28.4" + checksum: 10c0/aaa5fb8098926dfed5f223adf2c5e4c7fbba4b911b73dfec2d7d3083f8ba694d201a206db673da2d9b3ae8c01793e795767654558c450c8c14b4c2175b4fcb44 + languageName: node + linkType: hard + +"@babel/parser@npm:^7.1.0, @babel/parser@npm:^7.20.7, @babel/parser@npm:^7.24.4, @babel/parser@npm:^7.27.2, @babel/parser@npm:^7.28.5": + version: 7.28.5 + resolution: "@babel/parser@npm:7.28.5" + dependencies: + "@babel/types": "npm:^7.28.5" + bin: + parser: ./bin/babel-parser.js + checksum: 10c0/5bbe48bf2c79594ac02b490a41ffde7ef5aa22a9a88ad6bcc78432a6ba8a9d638d531d868bd1f104633f1f6bba9905746e15185b8276a3756c42b765d131b1ef + languageName: node + linkType: hard + +"@babel/plugin-transform-react-jsx-self@npm:^7.27.1": + version: 7.27.1 + resolution: "@babel/plugin-transform-react-jsx-self@npm:7.27.1" + dependencies: + "@babel/helper-plugin-utils": "npm:^7.27.1" + peerDependencies: + "@babel/core": ^7.0.0-0 + checksum: 10c0/00a4f917b70a608f9aca2fb39aabe04a60aa33165a7e0105fd44b3a8531630eb85bf5572e9f242f51e6ad2fa38c2e7e780902176c863556c58b5ba6f6e164031 + languageName: node + linkType: hard + +"@babel/plugin-transform-react-jsx-source@npm:^7.27.1": + version: 7.27.1 + resolution: "@babel/plugin-transform-react-jsx-source@npm:7.27.1" + dependencies: + "@babel/helper-plugin-utils": "npm:^7.27.1" + peerDependencies: + "@babel/core": ^7.0.0-0 + checksum: 10c0/5e67b56c39c4d03e59e03ba80692b24c5a921472079b63af711b1d250fc37c1733a17069b63537f750f3e937ec44a42b1ee6a46cd23b1a0df5163b17f741f7f2 + languageName: node + linkType: hard + +"@babel/runtime@npm:^7.12.5, @babel/runtime@npm:^7.18.3, @babel/runtime@npm:^7.23.2, @babel/runtime@npm:^7.26.9, @babel/runtime@npm:^7.28.4, @babel/runtime@npm:^7.5.5, @babel/runtime@npm:^7.8.7, @babel/runtime@npm:^7.9.2": + version: 7.28.4 + resolution: "@babel/runtime@npm:7.28.4" + checksum: 10c0/792ce7af9750fb9b93879cc9d1db175701c4689da890e6ced242ea0207c9da411ccf16dc04e689cc01158b28d7898c40d75598f4559109f761c12ce01e959bf7 + languageName: node + linkType: hard + +"@babel/template@npm:^7.27.2": + version: 7.27.2 + resolution: "@babel/template@npm:7.27.2" + dependencies: + "@babel/code-frame": "npm:^7.27.1" + "@babel/parser": "npm:^7.27.2" + "@babel/types": "npm:^7.27.1" + checksum: 10c0/ed9e9022651e463cc5f2cc21942f0e74544f1754d231add6348ff1b472985a3b3502041c0be62dc99ed2d12cfae0c51394bf827452b98a2f8769c03b87aadc81 + languageName: node + linkType: hard + +"@babel/traverse@npm:^7.27.1, @babel/traverse@npm:^7.28.3, @babel/traverse@npm:^7.28.5": + version: 7.28.5 + resolution: "@babel/traverse@npm:7.28.5" + dependencies: + "@babel/code-frame": "npm:^7.27.1" + "@babel/generator": "npm:^7.28.5" + "@babel/helper-globals": "npm:^7.28.0" + "@babel/parser": "npm:^7.28.5" + "@babel/template": "npm:^7.27.2" + "@babel/types": "npm:^7.28.5" + debug: "npm:^4.3.1" + checksum: 10c0/f6c4a595993ae2b73f2d4cd9c062f2e232174d293edd4abe1d715bd6281da8d99e47c65857e8d0917d9384c65972f4acdebc6749a7c40a8fcc38b3c7fb3e706f + languageName: node + linkType: hard + +"@babel/types@npm:^7.0.0, @babel/types@npm:^7.20.7, @babel/types@npm:^7.26.0, @babel/types@npm:^7.27.1, @babel/types@npm:^7.28.2, @babel/types@npm:^7.28.4, @babel/types@npm:^7.28.5": + version: 7.28.5 + resolution: "@babel/types@npm:7.28.5" + dependencies: + "@babel/helper-string-parser": "npm:^7.27.1" + "@babel/helper-validator-identifier": "npm:^7.28.5" + checksum: 10c0/a5a483d2100befbf125793640dec26b90b95fd233a94c19573325898a5ce1e52cdfa96e495c7dcc31b5eca5b66ce3e6d4a0f5a4a62daec271455959f208ab08a + languageName: node + linkType: hard + +"@emotion/babel-plugin@npm:^11.13.5": + version: 11.13.5 + resolution: "@emotion/babel-plugin@npm:11.13.5" + dependencies: + "@babel/helper-module-imports": "npm:^7.16.7" + "@babel/runtime": "npm:^7.18.3" + "@emotion/hash": "npm:^0.9.2" + "@emotion/memoize": "npm:^0.9.0" + "@emotion/serialize": "npm:^1.3.3" + babel-plugin-macros: "npm:^3.1.0" + convert-source-map: "npm:^1.5.0" + escape-string-regexp: "npm:^4.0.0" + find-root: "npm:^1.1.0" + source-map: "npm:^0.5.7" + stylis: "npm:4.2.0" + checksum: 10c0/8ccbfec7defd0e513cb8a1568fa179eac1e20c35fda18aed767f6c59ea7314363ebf2de3e9d2df66c8ad78928dc3dceeded84e6fa8059087cae5c280090aeeeb + languageName: node + linkType: hard + +"@emotion/cache@npm:^11.14.0": + version: 11.14.0 + resolution: "@emotion/cache@npm:11.14.0" + dependencies: + "@emotion/memoize": "npm:^0.9.0" + "@emotion/sheet": "npm:^1.4.0" + "@emotion/utils": "npm:^1.4.2" + "@emotion/weak-memoize": "npm:^0.4.0" + stylis: "npm:4.2.0" + checksum: 10c0/3fa3e7a431ab6f8a47c67132a00ac8358f428c1b6c8421d4b20de9df7c18e95eec04a5a6ff5a68908f98d3280044f247b4965ac63df8302d2c94dba718769724 + languageName: node + linkType: hard + +"@emotion/hash@npm:^0.9.2": + version: 0.9.2 + resolution: "@emotion/hash@npm:0.9.2" + checksum: 10c0/0dc254561a3cc0a06a10bbce7f6a997883fd240c8c1928b93713f803a2e9153a257a488537012efe89dbe1246f2abfe2add62cdb3471a13d67137fcb808e81c2 + languageName: node + linkType: hard + +"@emotion/is-prop-valid@npm:^1.3.0": + version: 1.4.0 + resolution: "@emotion/is-prop-valid@npm:1.4.0" + dependencies: + "@emotion/memoize": "npm:^0.9.0" + checksum: 10c0/5f857814ec7d8c7e727727346dfb001af6b1fb31d621a3ce9c3edf944a484d8b0d619546c30899ae3ade2f317c76390ba4394449728e9bf628312defc2c41ac3 + languageName: node + linkType: hard + +"@emotion/memoize@npm:^0.9.0": + version: 0.9.0 + resolution: "@emotion/memoize@npm:0.9.0" + checksum: 10c0/13f474a9201c7f88b543e6ea42f55c04fb2fdc05e6c5a3108aced2f7e7aa7eda7794c56bba02985a46d8aaa914fcdde238727a98341a96e2aec750d372dadd15 + languageName: node + linkType: hard + +"@emotion/react@npm:^11.14.0": + version: 11.14.0 + resolution: "@emotion/react@npm:11.14.0" + dependencies: + "@babel/runtime": "npm:^7.18.3" + "@emotion/babel-plugin": "npm:^11.13.5" + "@emotion/cache": "npm:^11.14.0" + "@emotion/serialize": "npm:^1.3.3" + "@emotion/use-insertion-effect-with-fallbacks": "npm:^1.2.0" + "@emotion/utils": "npm:^1.4.2" + "@emotion/weak-memoize": "npm:^0.4.0" + hoist-non-react-statics: "npm:^3.3.1" + peerDependencies: + react: ">=16.8.0" + peerDependenciesMeta: + "@types/react": + optional: true + checksum: 10c0/d0864f571a9f99ec643420ef31fde09e2006d3943a6aba079980e4d5f6e9f9fecbcc54b8f617fe003c00092ff9d5241179149ffff2810cb05cf72b4620cfc031 + languageName: node + linkType: hard + +"@emotion/serialize@npm:^1.3.3": + version: 1.3.3 + resolution: "@emotion/serialize@npm:1.3.3" + dependencies: + "@emotion/hash": "npm:^0.9.2" + "@emotion/memoize": "npm:^0.9.0" + "@emotion/unitless": "npm:^0.10.0" + "@emotion/utils": "npm:^1.4.2" + csstype: "npm:^3.0.2" + checksum: 10c0/b28cb7de59de382021de2b26c0c94ebbfb16967a1b969a56fdb6408465a8993df243bfbd66430badaa6800e1834724e84895f5a6a9d97d0d224de3d77852acb4 + languageName: node + linkType: hard + +"@emotion/sheet@npm:^1.4.0": + version: 1.4.0 + resolution: "@emotion/sheet@npm:1.4.0" + checksum: 10c0/3ca72d1650a07d2fbb7e382761b130b4a887dcd04e6574b2d51ce578791240150d7072a9bcb4161933abbcd1e38b243a6fb4464a7fe991d700c17aa66bb5acc7 + languageName: node + linkType: hard + +"@emotion/styled@npm:^11.14.0": + version: 11.14.1 + resolution: "@emotion/styled@npm:11.14.1" + dependencies: + "@babel/runtime": "npm:^7.18.3" + "@emotion/babel-plugin": "npm:^11.13.5" + "@emotion/is-prop-valid": "npm:^1.3.0" + "@emotion/serialize": "npm:^1.3.3" + "@emotion/use-insertion-effect-with-fallbacks": "npm:^1.2.0" + "@emotion/utils": "npm:^1.4.2" + peerDependencies: + "@emotion/react": ^11.0.0-rc.0 + react: ">=16.8.0" + peerDependenciesMeta: + "@types/react": + optional: true + checksum: 10c0/2bbf8451df49c967e41fbcf8111a7f6dafe6757f0cc113f2f6e287206c45ac1d54dc8a95a483b7c0cee8614b8a8d08155bded6453d6721de1f8cc8d5b9216963 + languageName: node + linkType: hard + +"@emotion/unitless@npm:^0.10.0": + version: 0.10.0 + resolution: "@emotion/unitless@npm:0.10.0" + checksum: 10c0/150943192727b7650eb9a6851a98034ddb58a8b6958b37546080f794696141c3760966ac695ab9af97efe10178690987aee4791f9f0ad1ff76783cdca83c1d49 + languageName: node + linkType: hard + +"@emotion/use-insertion-effect-with-fallbacks@npm:^1.2.0": + version: 1.2.0 + resolution: "@emotion/use-insertion-effect-with-fallbacks@npm:1.2.0" + peerDependencies: + react: ">=16.8.0" + checksum: 10c0/074dbc92b96bdc09209871070076e3b0351b6b47efefa849a7d9c37ab142130767609ca1831da0055988974e3b895c1de7606e4c421fecaa27c3e56a2afd3b08 + languageName: node + linkType: hard + +"@emotion/utils@npm:^1.4.2": + version: 1.4.2 + resolution: "@emotion/utils@npm:1.4.2" + checksum: 10c0/7d0010bf60a2a8c1a033b6431469de4c80e47aeb8fd856a17c1d1f76bbc3a03161a34aeaa78803566e29681ca551e7bf9994b68e9c5f5c796159923e44f78d9a + languageName: node + linkType: hard + +"@emotion/weak-memoize@npm:^0.4.0": + version: 0.4.0 + resolution: "@emotion/weak-memoize@npm:0.4.0" + checksum: 10c0/64376af11f1266042d03b3305c30b7502e6084868e33327e944b539091a472f089db307af69240f7188f8bc6b319276fd7b141a36613f1160d73d12a60f6ca1a + languageName: node + linkType: hard + +"@esbuild/aix-ppc64@npm:0.21.5": + version: 0.21.5 + resolution: "@esbuild/aix-ppc64@npm:0.21.5" + conditions: os=aix & cpu=ppc64 + languageName: node + linkType: hard + +"@esbuild/android-arm64@npm:0.21.5": + version: 0.21.5 + resolution: "@esbuild/android-arm64@npm:0.21.5" + conditions: os=android & cpu=arm64 + languageName: node + linkType: hard + +"@esbuild/android-arm@npm:0.21.5": + version: 0.21.5 + resolution: "@esbuild/android-arm@npm:0.21.5" + conditions: os=android & cpu=arm + languageName: node + linkType: hard + +"@esbuild/android-x64@npm:0.21.5": + version: 0.21.5 + resolution: "@esbuild/android-x64@npm:0.21.5" + conditions: os=android & cpu=x64 + languageName: node + linkType: hard + +"@esbuild/darwin-arm64@npm:0.21.5": + version: 0.21.5 + resolution: "@esbuild/darwin-arm64@npm:0.21.5" + conditions: os=darwin & cpu=arm64 + languageName: node + linkType: hard + +"@esbuild/darwin-x64@npm:0.21.5": + version: 0.21.5 + resolution: "@esbuild/darwin-x64@npm:0.21.5" + conditions: os=darwin & cpu=x64 + languageName: node + linkType: hard + +"@esbuild/freebsd-arm64@npm:0.21.5": + version: 0.21.5 + resolution: "@esbuild/freebsd-arm64@npm:0.21.5" + conditions: os=freebsd & cpu=arm64 + languageName: node + linkType: hard + +"@esbuild/freebsd-x64@npm:0.21.5": + version: 0.21.5 + resolution: "@esbuild/freebsd-x64@npm:0.21.5" + conditions: os=freebsd & cpu=x64 + languageName: node + linkType: hard + +"@esbuild/linux-arm64@npm:0.21.5": + version: 0.21.5 + resolution: "@esbuild/linux-arm64@npm:0.21.5" + conditions: os=linux & cpu=arm64 + languageName: node + linkType: hard + +"@esbuild/linux-arm@npm:0.21.5": + version: 0.21.5 + resolution: "@esbuild/linux-arm@npm:0.21.5" + conditions: os=linux & cpu=arm + languageName: node + linkType: hard + +"@esbuild/linux-ia32@npm:0.21.5": + version: 0.21.5 + resolution: "@esbuild/linux-ia32@npm:0.21.5" + conditions: os=linux & cpu=ia32 + languageName: node + linkType: hard + +"@esbuild/linux-loong64@npm:0.21.5": + version: 0.21.5 + resolution: "@esbuild/linux-loong64@npm:0.21.5" + conditions: os=linux & cpu=loong64 + languageName: node + linkType: hard + +"@esbuild/linux-mips64el@npm:0.21.5": + version: 0.21.5 + resolution: "@esbuild/linux-mips64el@npm:0.21.5" + conditions: os=linux & cpu=mips64el + languageName: node + linkType: hard + +"@esbuild/linux-ppc64@npm:0.21.5": + version: 0.21.5 + resolution: "@esbuild/linux-ppc64@npm:0.21.5" + conditions: os=linux & cpu=ppc64 + languageName: node + linkType: hard + +"@esbuild/linux-riscv64@npm:0.21.5": + version: 0.21.5 + resolution: "@esbuild/linux-riscv64@npm:0.21.5" + conditions: os=linux & cpu=riscv64 + languageName: node + linkType: hard + +"@esbuild/linux-s390x@npm:0.21.5": + version: 0.21.5 + resolution: "@esbuild/linux-s390x@npm:0.21.5" + conditions: os=linux & cpu=s390x + languageName: node + linkType: hard + +"@esbuild/linux-x64@npm:0.21.5": + version: 0.21.5 + resolution: "@esbuild/linux-x64@npm:0.21.5" + conditions: os=linux & cpu=x64 + languageName: node + linkType: hard + +"@esbuild/netbsd-x64@npm:0.21.5": + version: 0.21.5 + resolution: "@esbuild/netbsd-x64@npm:0.21.5" + conditions: os=netbsd & cpu=x64 + languageName: node + linkType: hard + +"@esbuild/openbsd-x64@npm:0.21.5": + version: 0.21.5 + resolution: "@esbuild/openbsd-x64@npm:0.21.5" + conditions: os=openbsd & cpu=x64 + languageName: node + linkType: hard + +"@esbuild/sunos-x64@npm:0.21.5": + version: 0.21.5 + resolution: "@esbuild/sunos-x64@npm:0.21.5" + conditions: os=sunos & cpu=x64 + languageName: node + linkType: hard + +"@esbuild/win32-arm64@npm:0.21.5": + version: 0.21.5 + resolution: "@esbuild/win32-arm64@npm:0.21.5" + conditions: os=win32 & cpu=arm64 + languageName: node + linkType: hard + +"@esbuild/win32-ia32@npm:0.21.5": + version: 0.21.5 + resolution: "@esbuild/win32-ia32@npm:0.21.5" + conditions: os=win32 & cpu=ia32 + languageName: node + linkType: hard + +"@esbuild/win32-x64@npm:0.21.5": + version: 0.21.5 + resolution: "@esbuild/win32-x64@npm:0.21.5" + conditions: os=win32 & cpu=x64 + languageName: node + linkType: hard + +"@eslint-community/eslint-utils@npm:^4.7.0, @eslint-community/eslint-utils@npm:^4.8.0": + version: 4.9.0 + resolution: "@eslint-community/eslint-utils@npm:4.9.0" + dependencies: + eslint-visitor-keys: "npm:^3.4.3" + peerDependencies: + eslint: ^6.0.0 || ^7.0.0 || >=8.0.0 + checksum: 10c0/8881e22d519326e7dba85ea915ac7a143367c805e6ba1374c987aa2fbdd09195cc51183d2da72c0e2ff388f84363e1b220fd0d19bef10c272c63455162176817 + languageName: node + linkType: hard + +"@eslint-community/regexpp@npm:^4.10.0, @eslint-community/regexpp@npm:^4.12.1": + version: 4.12.2 + resolution: "@eslint-community/regexpp@npm:4.12.2" + checksum: 10c0/fddcbc66851b308478d04e302a4d771d6917a0b3740dc351513c0da9ca2eab8a1adf99f5e0aa7ab8b13fa0df005c81adeee7e63a92f3effd7d367a163b721c2d + languageName: node + linkType: hard + +"@eslint/config-array@npm:^0.21.1": + version: 0.21.1 + resolution: "@eslint/config-array@npm:0.21.1" + dependencies: + "@eslint/object-schema": "npm:^2.1.7" + debug: "npm:^4.3.1" + minimatch: "npm:^3.1.2" + checksum: 10c0/2f657d4edd6ddcb920579b72e7a5b127865d4c3fb4dda24f11d5c4f445a93ca481aebdbd6bf3291c536f5d034458dbcbb298ee3b698bc6c9dd02900fe87eec3c + languageName: node + linkType: hard + +"@eslint/config-helpers@npm:^0.4.2": + version: 0.4.2 + resolution: "@eslint/config-helpers@npm:0.4.2" + dependencies: + "@eslint/core": "npm:^0.17.0" + checksum: 10c0/92efd7a527b2d17eb1a148409d71d80f9ac160b565ac73ee092252e8bf08ecd08670699f46b306b94f13d22e88ac88a612120e7847570dd7cdc72f234d50dcb4 + languageName: node + linkType: hard + +"@eslint/core@npm:^0.17.0": + version: 0.17.0 + resolution: "@eslint/core@npm:0.17.0" + dependencies: + "@types/json-schema": "npm:^7.0.15" + checksum: 10c0/9a580f2246633bc752298e7440dd942ec421860d1946d0801f0423830e67887e4aeba10ab9a23d281727a978eb93d053d1922a587d502942a713607f40ed704e + languageName: node + linkType: hard + +"@eslint/eslintrc@npm:^3.3.1": + version: 3.3.3 + resolution: "@eslint/eslintrc@npm:3.3.3" + dependencies: + ajv: "npm:^6.12.4" + debug: "npm:^4.3.2" + espree: "npm:^10.0.1" + globals: "npm:^14.0.0" + ignore: "npm:^5.2.0" + import-fresh: "npm:^3.2.1" + js-yaml: "npm:^4.1.1" + minimatch: "npm:^3.1.2" + strip-json-comments: "npm:^3.1.1" + checksum: 10c0/532c7acc7ddd042724c28b1f020bd7bf148fcd4653bb44c8314168b5f772508c842ce4ee070299cac51c5c5757d2124bdcfcef5551c8c58ff9986e3e17f2260d + languageName: node + linkType: hard + +"@eslint/js@npm:9.39.2, @eslint/js@npm:^9.9.0": + version: 9.39.2 + resolution: "@eslint/js@npm:9.39.2" + checksum: 10c0/00f51c52b04ac79faebfaa65a9652b2093b9c924e945479f1f3945473f78aee83cbc76c8d70bbffbf06f7024626575b16d97b66eab16182e1d0d39daff2f26f5 + languageName: node + linkType: hard + +"@eslint/object-schema@npm:^2.1.7": + version: 2.1.7 + resolution: "@eslint/object-schema@npm:2.1.7" + checksum: 10c0/936b6e499853d1335803f556d526c86f5fe2259ed241bc665000e1d6353828edd913feed43120d150adb75570cae162cf000b5b0dfc9596726761c36b82f4e87 + languageName: node + linkType: hard + +"@eslint/plugin-kit@npm:^0.4.1": + version: 0.4.1 + resolution: "@eslint/plugin-kit@npm:0.4.1" + dependencies: + "@eslint/core": "npm:^0.17.0" + levn: "npm:^0.4.1" + checksum: 10c0/51600f78b798f172a9915dffb295e2ffb44840d583427bc732baf12ecb963eb841b253300e657da91d890f4b323d10a1bd12934bf293e3018d8bb66fdce5217b + languageName: node + linkType: hard + +"@fontsource/roboto@npm:^5.1.0": + version: 5.2.9 + resolution: "@fontsource/roboto@npm:5.2.9" + checksum: 10c0/8280ab6504ab7da105c77afc1231236be86f7cd02a708e25b6cfc2871975699a44be7a42491e59934f2a84c874a8109bb2babfbc6b1986bcad9f3ac1de3980ca + languageName: node + linkType: hard + +"@humanfs/core@npm:^0.19.1": + version: 0.19.1 + resolution: "@humanfs/core@npm:0.19.1" + checksum: 10c0/aa4e0152171c07879b458d0e8a704b8c3a89a8c0541726c6b65b81e84fd8b7564b5d6c633feadc6598307d34564bd53294b533491424e8e313d7ab6c7bc5dc67 + languageName: node + linkType: hard + +"@humanfs/node@npm:^0.16.6": + version: 0.16.7 + resolution: "@humanfs/node@npm:0.16.7" + dependencies: + "@humanfs/core": "npm:^0.19.1" + "@humanwhocodes/retry": "npm:^0.4.0" + checksum: 10c0/9f83d3cf2cfa37383e01e3cdaead11cd426208e04c44adcdd291aa983aaf72d7d3598844d2fe9ce54896bb1bf8bd4b56883376611c8905a19c44684642823f30 + languageName: node + linkType: hard + +"@humanwhocodes/module-importer@npm:^1.0.1": + version: 1.0.1 + resolution: "@humanwhocodes/module-importer@npm:1.0.1" + checksum: 10c0/909b69c3b86d482c26b3359db16e46a32e0fb30bd306a3c176b8313b9e7313dba0f37f519de6aa8b0a1921349e505f259d19475e123182416a506d7f87e7f529 + languageName: node + linkType: hard + +"@humanwhocodes/retry@npm:^0.4.0, @humanwhocodes/retry@npm:^0.4.2": + version: 0.4.3 + resolution: "@humanwhocodes/retry@npm:0.4.3" + checksum: 10c0/3775bb30087d4440b3f7406d5a057777d90e4b9f435af488a4923ef249e93615fb78565a85f173a186a076c7706a81d0d57d563a2624e4de2c5c9c66c486ce42 + languageName: node + linkType: hard + +"@isaacs/balanced-match@npm:^4.0.1": + version: 4.0.1 + resolution: "@isaacs/balanced-match@npm:4.0.1" + checksum: 10c0/7da011805b259ec5c955f01cee903da72ad97c5e6f01ca96197267d3f33103d5b2f8a1af192140f3aa64526c593c8d098ae366c2b11f7f17645d12387c2fd420 + languageName: node + linkType: hard + +"@isaacs/brace-expansion@npm:^5.0.0": + version: 5.0.0 + resolution: "@isaacs/brace-expansion@npm:5.0.0" + dependencies: + "@isaacs/balanced-match": "npm:^4.0.1" + checksum: 10c0/b4d4812f4be53afc2c5b6c545001ff7a4659af68d4484804e9d514e183d20269bb81def8682c01a22b17c4d6aed14292c8494f7d2ac664e547101c1a905aa977 + languageName: node + linkType: hard + +"@isaacs/fs-minipass@npm:^4.0.0": + version: 4.0.1 + resolution: "@isaacs/fs-minipass@npm:4.0.1" + dependencies: + minipass: "npm:^7.0.4" + checksum: 10c0/c25b6dc1598790d5b55c0947a9b7d111cfa92594db5296c3b907e2f533c033666f692a3939eadac17b1c7c40d362d0b0635dc874cbfe3e70db7c2b07cc97a5d2 + languageName: node + linkType: hard + +"@jridgewell/gen-mapping@npm:^0.3.12, @jridgewell/gen-mapping@npm:^0.3.5": + version: 0.3.13 + resolution: "@jridgewell/gen-mapping@npm:0.3.13" + dependencies: + "@jridgewell/sourcemap-codec": "npm:^1.5.0" + "@jridgewell/trace-mapping": "npm:^0.3.24" + checksum: 10c0/9a7d65fb13bd9aec1fbab74cda08496839b7e2ceb31f5ab922b323e94d7c481ce0fc4fd7e12e2610915ed8af51178bdc61e168e92a8c8b8303b030b03489b13b + languageName: node + linkType: hard + +"@jridgewell/remapping@npm:^2.3.5": + version: 2.3.5 + resolution: "@jridgewell/remapping@npm:2.3.5" + dependencies: + "@jridgewell/gen-mapping": "npm:^0.3.5" + "@jridgewell/trace-mapping": "npm:^0.3.24" + checksum: 10c0/3de494219ffeb2c5c38711d0d7bb128097edf91893090a2dbc8ee0b55d092bb7347b1fd0f478486c5eab010e855c73927b1666f2107516d472d24a73017d1194 + languageName: node + linkType: hard + +"@jridgewell/resolve-uri@npm:^3.1.0": + version: 3.1.2 + resolution: "@jridgewell/resolve-uri@npm:3.1.2" + checksum: 10c0/d502e6fb516b35032331406d4e962c21fe77cdf1cbdb49c6142bcbd9e30507094b18972778a6e27cbad756209cfe34b1a27729e6fa08a2eb92b33943f680cf1e + languageName: node + linkType: hard + +"@jridgewell/sourcemap-codec@npm:^1.4.14, @jridgewell/sourcemap-codec@npm:^1.5.0, @jridgewell/sourcemap-codec@npm:^1.5.5": + version: 1.5.5 + resolution: "@jridgewell/sourcemap-codec@npm:1.5.5" + checksum: 10c0/f9e538f302b63c0ebc06eecb1dd9918dd4289ed36147a0ddce35d6ea4d7ebbda243cda7b2213b6a5e1d8087a298d5cf630fb2bd39329cdecb82017023f6081a0 + languageName: node + linkType: hard + +"@jridgewell/trace-mapping@npm:^0.3.24, @jridgewell/trace-mapping@npm:^0.3.28": + version: 0.3.31 + resolution: "@jridgewell/trace-mapping@npm:0.3.31" + dependencies: + "@jridgewell/resolve-uri": "npm:^3.1.0" + "@jridgewell/sourcemap-codec": "npm:^1.4.14" + checksum: 10c0/4b30ec8cd56c5fd9a661f088230af01e0c1a3888d11ffb6b47639700f71225be21d1f7e168048d6d4f9449207b978a235c07c8f15c07705685d16dc06280e9d9 + languageName: node + linkType: hard + +"@mui/core-downloads-tracker@npm:^7.3.6": + version: 7.3.6 + resolution: "@mui/core-downloads-tracker@npm:7.3.6" + checksum: 10c0/32eefa674df2717b18422f0e7468d7c584f2da93c219b98d29d4d873e9418152e3ae242aecd34ea19ea927dff50632b0d85022b99632d55ab02b84ee03d59078 + languageName: node + linkType: hard + +"@mui/icons-material@npm:^7.1.1": + version: 7.3.6 + resolution: "@mui/icons-material@npm:7.3.6" + dependencies: + "@babel/runtime": "npm:^7.28.4" + peerDependencies: + "@mui/material": ^7.3.6 + "@types/react": ^17.0.0 || ^18.0.0 || ^19.0.0 + react: ^17.0.0 || ^18.0.0 || ^19.0.0 + peerDependenciesMeta: + "@types/react": + optional: true + checksum: 10c0/ec0286beb22a7070566d6242410b75f4106e15b2ead7b5d5115f00e1c7fc70d44d47289a1d27059781d56907cf291e53261868dafd7dba2843245abe18799d65 + languageName: node + linkType: hard + +"@mui/material@npm:^7.1.1": + version: 7.3.6 + resolution: "@mui/material@npm:7.3.6" + dependencies: + "@babel/runtime": "npm:^7.28.4" + "@mui/core-downloads-tracker": "npm:^7.3.6" + "@mui/system": "npm:^7.3.6" + "@mui/types": "npm:^7.4.9" + "@mui/utils": "npm:^7.3.6" + "@popperjs/core": "npm:^2.11.8" + "@types/react-transition-group": "npm:^4.4.12" + clsx: "npm:^2.1.1" + csstype: "npm:^3.1.3" + prop-types: "npm:^15.8.1" + react-is: "npm:^19.2.0" + react-transition-group: "npm:^4.4.5" + peerDependencies: + "@emotion/react": ^11.5.0 + "@emotion/styled": ^11.3.0 + "@mui/material-pigment-css": ^7.3.6 + "@types/react": ^17.0.0 || ^18.0.0 || ^19.0.0 + react: ^17.0.0 || ^18.0.0 || ^19.0.0 + react-dom: ^17.0.0 || ^18.0.0 || ^19.0.0 + peerDependenciesMeta: + "@emotion/react": + optional: true + "@emotion/styled": + optional: true + "@mui/material-pigment-css": + optional: true + "@types/react": + optional: true + checksum: 10c0/8bc88277eea82897a0011f5d1efdccf048a1626e9bcfbf97e1c03ae4b6d1413d240a061a3af7ba10329d9d8e1b1cb296b9d3693978b47e9186d96704ff56c333 + languageName: node + linkType: hard + +"@mui/private-theming@npm:^7.3.6": + version: 7.3.6 + resolution: "@mui/private-theming@npm:7.3.6" + dependencies: + "@babel/runtime": "npm:^7.28.4" + "@mui/utils": "npm:^7.3.6" + prop-types: "npm:^15.8.1" + peerDependencies: + "@types/react": ^17.0.0 || ^18.0.0 || ^19.0.0 + react: ^17.0.0 || ^18.0.0 || ^19.0.0 + peerDependenciesMeta: + "@types/react": + optional: true + checksum: 10c0/c342d81e9499015267d4963423b1b2ce7931d1c6eb25090b4d5a32095c3bebd8c85b9ac87bfc5f4d70be7d772704d8d093c68f477b72a130e5c9dbf510165a45 + languageName: node + linkType: hard + +"@mui/styled-engine@npm:^7.3.6": + version: 7.3.6 + resolution: "@mui/styled-engine@npm:7.3.6" + dependencies: + "@babel/runtime": "npm:^7.28.4" + "@emotion/cache": "npm:^11.14.0" + "@emotion/serialize": "npm:^1.3.3" + "@emotion/sheet": "npm:^1.4.0" + csstype: "npm:^3.1.3" + prop-types: "npm:^15.8.1" + peerDependencies: + "@emotion/react": ^11.4.1 + "@emotion/styled": ^11.3.0 + react: ^17.0.0 || ^18.0.0 || ^19.0.0 + peerDependenciesMeta: + "@emotion/react": + optional: true + "@emotion/styled": + optional: true + checksum: 10c0/7c6cb484a21fe238a2f86bcc5ef9b2ee4ea9bbf270dad3ef898b4019211f180740e8b0a37682d2e5baa0d46910231c3b9640231431ba454c0a02ff19f80e046e + languageName: node + linkType: hard + +"@mui/system@npm:^7.3.6": + version: 7.3.6 + resolution: "@mui/system@npm:7.3.6" + dependencies: + "@babel/runtime": "npm:^7.28.4" + "@mui/private-theming": "npm:^7.3.6" + "@mui/styled-engine": "npm:^7.3.6" + "@mui/types": "npm:^7.4.9" + "@mui/utils": "npm:^7.3.6" + clsx: "npm:^2.1.1" + csstype: "npm:^3.1.3" + prop-types: "npm:^15.8.1" + peerDependencies: + "@emotion/react": ^11.5.0 + "@emotion/styled": ^11.3.0 + "@types/react": ^17.0.0 || ^18.0.0 || ^19.0.0 + react: ^17.0.0 || ^18.0.0 || ^19.0.0 + peerDependenciesMeta: + "@emotion/react": + optional: true + "@emotion/styled": + optional: true + "@types/react": + optional: true + checksum: 10c0/03ae9f9b3ede2aaaf89c95d1097aaadc0fb7ae4e5747ffafb528a184bf6bffd9f01fe55b77cb26af3f7101de6dc743d975c06a9777e02ed849f6a83beca88ae5 + languageName: node + linkType: hard + +"@mui/types@npm:^7.4.9": + version: 7.4.9 + resolution: "@mui/types@npm:7.4.9" + dependencies: + "@babel/runtime": "npm:^7.28.4" + peerDependencies: + "@types/react": ^17.0.0 || ^18.0.0 || ^19.0.0 + peerDependenciesMeta: + "@types/react": + optional: true + checksum: 10c0/ed371a08af12a712fb5985547162b99fc58b5d451ea1101927a6d2f2c005087eb25603553d2c4242ee3a34359333b917432052148c93ed7dded5d80042fec504 + languageName: node + linkType: hard + +"@mui/utils@npm:^7.3.5, @mui/utils@npm:^7.3.6": + version: 7.3.6 + resolution: "@mui/utils@npm:7.3.6" + dependencies: + "@babel/runtime": "npm:^7.28.4" + "@mui/types": "npm:^7.4.9" + "@types/prop-types": "npm:^15.7.15" + clsx: "npm:^2.1.1" + prop-types: "npm:^15.8.1" + react-is: "npm:^19.2.0" + peerDependencies: + "@types/react": ^17.0.0 || ^18.0.0 || ^19.0.0 + react: ^17.0.0 || ^18.0.0 || ^19.0.0 + peerDependenciesMeta: + "@types/react": + optional: true + checksum: 10c0/0af5f65a022028fa25b6a331443c44fdfd9060355495fd10849807f7aee9c9a4f776592cfe427b3c478f4ee4bf96349d9e84d1a035d5e31ba84d821748b4f185 + languageName: node + linkType: hard + +"@mui/x-date-pickers@npm:^8.8.0": + version: 8.23.0 + resolution: "@mui/x-date-pickers@npm:8.23.0" + dependencies: + "@babel/runtime": "npm:^7.28.4" + "@mui/utils": "npm:^7.3.5" + "@mui/x-internals": "npm:8.23.0" + "@types/react-transition-group": "npm:^4.4.12" + clsx: "npm:^2.1.1" + prop-types: "npm:^15.8.1" + react-transition-group: "npm:^4.4.5" + peerDependencies: + "@emotion/react": ^11.9.0 + "@emotion/styled": ^11.8.1 + "@mui/material": ^5.15.14 || ^6.0.0 || ^7.0.0 + "@mui/system": ^5.15.14 || ^6.0.0 || ^7.0.0 + date-fns: ^2.25.0 || ^3.2.0 || ^4.0.0 + date-fns-jalali: ^2.13.0-0 || ^3.2.0-0 || ^4.0.0-0 + dayjs: ^1.10.7 + luxon: ^3.0.2 + moment: ^2.29.4 + moment-hijri: ^2.1.2 || ^3.0.0 + moment-jalaali: ^0.7.4 || ^0.8.0 || ^0.9.0 || ^0.10.0 + react: ^17.0.0 || ^18.0.0 || ^19.0.0 + react-dom: ^17.0.0 || ^18.0.0 || ^19.0.0 + peerDependenciesMeta: + "@emotion/react": + optional: true + "@emotion/styled": + optional: true + date-fns: + optional: true + date-fns-jalali: + optional: true + dayjs: + optional: true + luxon: + optional: true + moment: + optional: true + moment-hijri: + optional: true + moment-jalaali: + optional: true + checksum: 10c0/a5e86d30f07b8f852bd4015deee73e8509e7058a28383a5cc0920dfecb9d03b80b6fcfa30a05d13568aa63d9224877396f4566584fecfd6f6872ceabf1eb5c40 + languageName: node + linkType: hard + +"@mui/x-internals@npm:8.23.0": + version: 8.23.0 + resolution: "@mui/x-internals@npm:8.23.0" + dependencies: + "@babel/runtime": "npm:^7.28.4" + "@mui/utils": "npm:^7.3.5" + reselect: "npm:^5.1.1" + use-sync-external-store: "npm:^1.6.0" + peerDependencies: + react: ^17.0.0 || ^18.0.0 || ^19.0.0 + checksum: 10c0/3195746a3ace98ecc67ed8992cd1d1cb8f6794236cbe60ddab55157d9cbb193023e78de7097047ada47c543305082a6a90240600edaaf3db3d1f00bcf0c55bf4 + languageName: node + linkType: hard + +"@npmcli/agent@npm:^4.0.0": + version: 4.0.0 + resolution: "@npmcli/agent@npm:4.0.0" + dependencies: + agent-base: "npm:^7.1.0" + http-proxy-agent: "npm:^7.0.0" + https-proxy-agent: "npm:^7.0.1" + lru-cache: "npm:^11.2.1" + socks-proxy-agent: "npm:^8.0.3" + checksum: 10c0/f7b5ce0f3dd42c3f8c6546e8433573d8049f67ef11ec22aa4704bc41483122f68bf97752e06302c455ead667af5cb753e6a09bff06632bc465c1cfd4c4b75a53 + languageName: node + linkType: hard + +"@npmcli/fs@npm:^5.0.0": + version: 5.0.0 + resolution: "@npmcli/fs@npm:5.0.0" + dependencies: + semver: "npm:^7.3.5" + checksum: 10c0/26e376d780f60ff16e874a0ac9bc3399186846baae0b6e1352286385ac134d900cc5dafaded77f38d77f86898fc923ae1cee9d7399f0275b1aa24878915d722b + languageName: node + linkType: hard + +"@popperjs/core@npm:^2.11.8": + version: 2.11.8 + resolution: "@popperjs/core@npm:2.11.8" + checksum: 10c0/4681e682abc006d25eb380d0cf3efc7557043f53b6aea7a5057d0d1e7df849a00e281cd8ea79c902a35a414d7919621fc2ba293ecec05f413598e0b23d5a1e63 + languageName: node + linkType: hard + +"@redux-devtools/core@npm:^4.1.1": + version: 4.1.1 + resolution: "@redux-devtools/core@npm:4.1.1" + dependencies: + "@babel/runtime": "npm:^7.26.9" + "@redux-devtools/instrument": "npm:^2.2.0" + peerDependencies: + react: ^16.8.4 || ^17.0.0 || ^18.0.0 || ^19.0.0 + react-redux: ^7.0.0 || ^8.0.0 || ^9.0.0 + redux: ^3.5.2 || ^4.0.0 || ^5.0.0 + checksum: 10c0/02a436c3c3f46a29e03f12b2659ed48c3952b8ee984fb997401b0fdf84e3df12f65b6fe73679a4d45efa12eff8e202460da1d68e981502514507c0b5abae4676 + languageName: node + linkType: hard + +"@redux-devtools/instrument@npm:^2.2.0": + version: 2.2.0 + resolution: "@redux-devtools/instrument@npm:2.2.0" + dependencies: + "@babel/runtime": "npm:^7.23.2" + lodash: "npm:^4.17.21" + peerDependencies: + redux: ^3.4.0 || ^4.0.0 || ^5.0.0 + checksum: 10c0/266cd15f1ef144bbb4a2ea472c096d2a4fa72341c4c892827d633a3c85b62d1240a35aaeab71a29020f1da4da0ab2f778c176fb30c35546f6a5020a26ba1c56a + languageName: node + linkType: hard + +"@redux-devtools/remote@npm:^0.9.5": + version: 0.9.5 + resolution: "@redux-devtools/remote@npm:0.9.5" + dependencies: + "@babel/runtime": "npm:^7.26.9" + "@redux-devtools/instrument": "npm:^2.2.0" + "@redux-devtools/utils": "npm:^3.1.1" + jsan: "npm:^3.1.14" + rn-host-detect: "npm:^1.2.0" + socketcluster-client: "npm:^19.2.3" + peerDependencies: + redux: ^3.5.2 || ^4.0.0 || ^5.0.0 + checksum: 10c0/ecb13fe01008afe25d860221c505a97774e43c81f04c2e381ec9a56e2f64c6ee3eb204ad34142f7d9149db8b64be1607e824d82a79eea3e5d99fe399b9805236 + languageName: node + linkType: hard + +"@redux-devtools/serialize@npm:^0.4.2": + version: 0.4.2 + resolution: "@redux-devtools/serialize@npm:0.4.2" + dependencies: + "@babel/runtime": "npm:^7.23.2" + jsan: "npm:^3.1.14" + peerDependencies: + immutable: ^4.0.0 + checksum: 10c0/9906d5fea1214ed82cb453d9a06b76c31fb9ddeea1f5fab18220af721f82c0efce6764353865cced3c481f0e421bfdd45701f2aea19a7678db07c56a5cdd5ad8 + languageName: node + linkType: hard + +"@redux-devtools/utils@npm:^3.1.1": + version: 3.1.1 + resolution: "@redux-devtools/utils@npm:3.1.1" + dependencies: + "@babel/runtime": "npm:^7.26.9" + "@redux-devtools/core": "npm:^4.1.1" + "@redux-devtools/serialize": "npm:^0.4.2" + "@types/get-params": "npm:^0.1.2" + get-params: "npm:^0.1.2" + immutable: "npm:^4.3.7" + jsan: "npm:^3.1.14" + nanoid: "npm:^5.1.2" + redux: "npm:^5.0.1" + peerDependencies: + "@redux-devtools/core": ^4.1.1 + immutable: ^4.3.7 + redux: ^4.0.0 || ^5.0.0 + checksum: 10c0/ea03fee3e633329d0d8cb84d7fb3aa739302b5e190fed23987e0663de4654e1d26be1ea01a61082b7497b8ac11a029fc4516a9f26d7aa057b5056e53fff170a0 + languageName: node + linkType: hard + +"@reduxjs/toolkit@npm:^2.3.0": + version: 2.11.2 + resolution: "@reduxjs/toolkit@npm:2.11.2" + dependencies: + "@standard-schema/spec": "npm:^1.0.0" + "@standard-schema/utils": "npm:^0.3.0" + immer: "npm:^11.0.0" + redux: "npm:^5.0.1" + redux-thunk: "npm:^3.1.0" + reselect: "npm:^5.1.0" + peerDependencies: + react: ^16.9.0 || ^17.0.0 || ^18 || ^19 + react-redux: ^7.2.1 || ^8.1.3 || ^9.0.0 + peerDependenciesMeta: + react: + optional: true + react-redux: + optional: true + checksum: 10c0/4d388b96dc4b12a577af23607c252b3647c1b3b5136dbb0212e1dbbef9bb309e93d3ba6a95795ee165e87e4286453025cd67a98b5b3bb6d244b93ea487dd1ac0 + languageName: node + linkType: hard + +"@rolldown/pluginutils@npm:1.0.0-beta.27": + version: 1.0.0-beta.27 + resolution: "@rolldown/pluginutils@npm:1.0.0-beta.27" + checksum: 10c0/9658f235b345201d4f6bfb1f32da9754ca164f892d1cb68154fe5f53c1df42bd675ecd409836dff46884a7847d6c00bdc38af870f7c81e05bba5c2645eb4ab9c + languageName: node + linkType: hard + +"@rollup/plugin-virtual@npm:^3.0.2": + version: 3.0.2 + resolution: "@rollup/plugin-virtual@npm:3.0.2" + peerDependencies: + rollup: ^1.20.0||^2.0.0||^3.0.0||^4.0.0 + peerDependenciesMeta: + rollup: + optional: true + checksum: 10c0/7115edb7989096d1ce334939fcf6e1ba365586b487bf61b2dd4f915386197f350db70904030342c0720fe58f5a52828975c645c4d415c1d432d9b1b6760a22ef + languageName: node + linkType: hard + +"@rollup/rollup-android-arm-eabi@npm:4.54.0": + version: 4.54.0 + resolution: "@rollup/rollup-android-arm-eabi@npm:4.54.0" + conditions: os=android & cpu=arm + languageName: node + linkType: hard + +"@rollup/rollup-android-arm64@npm:4.54.0": + version: 4.54.0 + resolution: "@rollup/rollup-android-arm64@npm:4.54.0" + conditions: os=android & cpu=arm64 + languageName: node + linkType: hard + +"@rollup/rollup-darwin-arm64@npm:4.54.0": + version: 4.54.0 + resolution: "@rollup/rollup-darwin-arm64@npm:4.54.0" + conditions: os=darwin & cpu=arm64 + languageName: node + linkType: hard + +"@rollup/rollup-darwin-x64@npm:4.54.0": + version: 4.54.0 + resolution: "@rollup/rollup-darwin-x64@npm:4.54.0" + conditions: os=darwin & cpu=x64 + languageName: node + linkType: hard + +"@rollup/rollup-freebsd-arm64@npm:4.54.0": + version: 4.54.0 + resolution: "@rollup/rollup-freebsd-arm64@npm:4.54.0" + conditions: os=freebsd & cpu=arm64 + languageName: node + linkType: hard + +"@rollup/rollup-freebsd-x64@npm:4.54.0": + version: 4.54.0 + resolution: "@rollup/rollup-freebsd-x64@npm:4.54.0" + conditions: os=freebsd & cpu=x64 + languageName: node + linkType: hard + +"@rollup/rollup-linux-arm-gnueabihf@npm:4.54.0": + version: 4.54.0 + resolution: "@rollup/rollup-linux-arm-gnueabihf@npm:4.54.0" + conditions: os=linux & cpu=arm & libc=glibc + languageName: node + linkType: hard + +"@rollup/rollup-linux-arm-musleabihf@npm:4.54.0": + version: 4.54.0 + resolution: "@rollup/rollup-linux-arm-musleabihf@npm:4.54.0" + conditions: os=linux & cpu=arm & libc=musl + languageName: node + linkType: hard + +"@rollup/rollup-linux-arm64-gnu@npm:4.54.0": + version: 4.54.0 + resolution: "@rollup/rollup-linux-arm64-gnu@npm:4.54.0" + conditions: os=linux & cpu=arm64 & libc=glibc + languageName: node + linkType: hard + +"@rollup/rollup-linux-arm64-musl@npm:4.54.0": + version: 4.54.0 + resolution: "@rollup/rollup-linux-arm64-musl@npm:4.54.0" + conditions: os=linux & cpu=arm64 & libc=musl + languageName: node + linkType: hard + +"@rollup/rollup-linux-loong64-gnu@npm:4.54.0": + version: 4.54.0 + resolution: "@rollup/rollup-linux-loong64-gnu@npm:4.54.0" + conditions: os=linux & cpu=loong64 & libc=glibc + languageName: node + linkType: hard + +"@rollup/rollup-linux-ppc64-gnu@npm:4.54.0": + version: 4.54.0 + resolution: "@rollup/rollup-linux-ppc64-gnu@npm:4.54.0" + conditions: os=linux & cpu=ppc64 & libc=glibc + languageName: node + linkType: hard + +"@rollup/rollup-linux-riscv64-gnu@npm:4.54.0": + version: 4.54.0 + resolution: "@rollup/rollup-linux-riscv64-gnu@npm:4.54.0" + conditions: os=linux & cpu=riscv64 & libc=glibc + languageName: node + linkType: hard + +"@rollup/rollup-linux-riscv64-musl@npm:4.54.0": + version: 4.54.0 + resolution: "@rollup/rollup-linux-riscv64-musl@npm:4.54.0" + conditions: os=linux & cpu=riscv64 & libc=musl + languageName: node + linkType: hard + +"@rollup/rollup-linux-s390x-gnu@npm:4.54.0": + version: 4.54.0 + resolution: "@rollup/rollup-linux-s390x-gnu@npm:4.54.0" + conditions: os=linux & cpu=s390x & libc=glibc + languageName: node + linkType: hard + +"@rollup/rollup-linux-x64-gnu@npm:4.54.0": + version: 4.54.0 + resolution: "@rollup/rollup-linux-x64-gnu@npm:4.54.0" + conditions: os=linux & cpu=x64 & libc=glibc + languageName: node + linkType: hard + +"@rollup/rollup-linux-x64-musl@npm:4.54.0": + version: 4.54.0 + resolution: "@rollup/rollup-linux-x64-musl@npm:4.54.0" + conditions: os=linux & cpu=x64 & libc=musl + languageName: node + linkType: hard + +"@rollup/rollup-openharmony-arm64@npm:4.54.0": + version: 4.54.0 + resolution: "@rollup/rollup-openharmony-arm64@npm:4.54.0" + conditions: os=openharmony & cpu=arm64 + languageName: node + linkType: hard + +"@rollup/rollup-win32-arm64-msvc@npm:4.54.0": + version: 4.54.0 + resolution: "@rollup/rollup-win32-arm64-msvc@npm:4.54.0" + conditions: os=win32 & cpu=arm64 + languageName: node + linkType: hard + +"@rollup/rollup-win32-ia32-msvc@npm:4.54.0": + version: 4.54.0 + resolution: "@rollup/rollup-win32-ia32-msvc@npm:4.54.0" + conditions: os=win32 & cpu=ia32 + languageName: node + linkType: hard + +"@rollup/rollup-win32-x64-gnu@npm:4.54.0": + version: 4.54.0 + resolution: "@rollup/rollup-win32-x64-gnu@npm:4.54.0" + conditions: os=win32 & cpu=x64 + languageName: node + linkType: hard + +"@rollup/rollup-win32-x64-msvc@npm:4.54.0": + version: 4.54.0 + resolution: "@rollup/rollup-win32-x64-msvc@npm:4.54.0" + conditions: os=win32 & cpu=x64 + languageName: node + linkType: hard + +"@rtsao/scc@npm:^1.1.0": + version: 1.1.0 + resolution: "@rtsao/scc@npm:1.1.0" + checksum: 10c0/b5bcfb0d87f7d1c1c7c0f7693f53b07866ed9fec4c34a97a8c948fb9a7c0082e416ce4d3b60beb4f5e167cbe04cdeefbf6771320f3ede059b9ce91188c409a5b + languageName: node + linkType: hard + +"@standard-schema/spec@npm:^1.0.0": + version: 1.1.0 + resolution: "@standard-schema/spec@npm:1.1.0" + checksum: 10c0/d90f55acde4b2deb983529c87e8025fa693de1a5e8b49ecc6eb84d1fd96328add0e03d7d551442156c7432fd78165b2c26ff561b970a9a881f046abb78d6a526 + languageName: node + linkType: hard + +"@standard-schema/utils@npm:^0.3.0": + version: 0.3.0 + resolution: "@standard-schema/utils@npm:0.3.0" + checksum: 10c0/6eb74cd13e52d5fc74054df51e37d947ef53f3ab9e02c085665dcca3c38c60ece8d735cebbdf18fbb13c775fbcb9becb3f53109b0e092a63f0f7389ce0993fd0 + languageName: node + linkType: hard + +"@swc/core-darwin-arm64@npm:1.15.7": + version: 1.15.7 + resolution: "@swc/core-darwin-arm64@npm:1.15.7" + conditions: os=darwin & cpu=arm64 + languageName: node + linkType: hard + +"@swc/core-darwin-x64@npm:1.15.7": + version: 1.15.7 + resolution: "@swc/core-darwin-x64@npm:1.15.7" + conditions: os=darwin & cpu=x64 + languageName: node + linkType: hard + +"@swc/core-linux-arm-gnueabihf@npm:1.15.7": + version: 1.15.7 + resolution: "@swc/core-linux-arm-gnueabihf@npm:1.15.7" + conditions: os=linux & cpu=arm + languageName: node + linkType: hard + +"@swc/core-linux-arm64-gnu@npm:1.15.7": + version: 1.15.7 + resolution: "@swc/core-linux-arm64-gnu@npm:1.15.7" + conditions: os=linux & cpu=arm64 & libc=glibc + languageName: node + linkType: hard + +"@swc/core-linux-arm64-musl@npm:1.15.7": + version: 1.15.7 + resolution: "@swc/core-linux-arm64-musl@npm:1.15.7" + conditions: os=linux & cpu=arm64 & libc=musl + languageName: node + linkType: hard + +"@swc/core-linux-x64-gnu@npm:1.15.7": + version: 1.15.7 + resolution: "@swc/core-linux-x64-gnu@npm:1.15.7" + conditions: os=linux & cpu=x64 & libc=glibc + languageName: node + linkType: hard + +"@swc/core-linux-x64-musl@npm:1.15.7": + version: 1.15.7 + resolution: "@swc/core-linux-x64-musl@npm:1.15.7" + conditions: os=linux & cpu=x64 & libc=musl + languageName: node + linkType: hard + +"@swc/core-win32-arm64-msvc@npm:1.15.7": + version: 1.15.7 + resolution: "@swc/core-win32-arm64-msvc@npm:1.15.7" + conditions: os=win32 & cpu=arm64 + languageName: node + linkType: hard + +"@swc/core-win32-ia32-msvc@npm:1.15.7": + version: 1.15.7 + resolution: "@swc/core-win32-ia32-msvc@npm:1.15.7" + conditions: os=win32 & cpu=ia32 + languageName: node + linkType: hard + +"@swc/core-win32-x64-msvc@npm:1.15.7": + version: 1.15.7 + resolution: "@swc/core-win32-x64-msvc@npm:1.15.7" + conditions: os=win32 & cpu=x64 + languageName: node + linkType: hard + +"@swc/core@npm:^1.12.14": + version: 1.15.7 + resolution: "@swc/core@npm:1.15.7" + dependencies: + "@swc/core-darwin-arm64": "npm:1.15.7" + "@swc/core-darwin-x64": "npm:1.15.7" + "@swc/core-linux-arm-gnueabihf": "npm:1.15.7" + "@swc/core-linux-arm64-gnu": "npm:1.15.7" + "@swc/core-linux-arm64-musl": "npm:1.15.7" + "@swc/core-linux-x64-gnu": "npm:1.15.7" + "@swc/core-linux-x64-musl": "npm:1.15.7" + "@swc/core-win32-arm64-msvc": "npm:1.15.7" + "@swc/core-win32-ia32-msvc": "npm:1.15.7" + "@swc/core-win32-x64-msvc": "npm:1.15.7" + "@swc/counter": "npm:^0.1.3" + "@swc/types": "npm:^0.1.25" + peerDependencies: + "@swc/helpers": ">=0.5.17" + dependenciesMeta: + "@swc/core-darwin-arm64": + optional: true + "@swc/core-darwin-x64": + optional: true + "@swc/core-linux-arm-gnueabihf": + optional: true + "@swc/core-linux-arm64-gnu": + optional: true + "@swc/core-linux-arm64-musl": + optional: true + "@swc/core-linux-x64-gnu": + optional: true + "@swc/core-linux-x64-musl": + optional: true + "@swc/core-win32-arm64-msvc": + optional: true + "@swc/core-win32-ia32-msvc": + optional: true + "@swc/core-win32-x64-msvc": + optional: true + peerDependenciesMeta: + "@swc/helpers": + optional: true + checksum: 10c0/ae1d20db777bfe0404f2c840d8fa66da0664bca1f88ac8727cbe4c0cb04ee28505009b55caf345f1e790b96410f9bcf66d0178372e9c6505a9da76e7562a06bf + languageName: node + linkType: hard + +"@swc/counter@npm:^0.1.3": + version: 0.1.3 + resolution: "@swc/counter@npm:0.1.3" + checksum: 10c0/8424f60f6bf8694cfd2a9bca45845bce29f26105cda8cf19cdb9fd3e78dc6338699e4db77a89ae449260bafa1cc6bec307e81e7fb96dbf7dcfce0eea55151356 + languageName: node + linkType: hard + +"@swc/types@npm:^0.1.25": + version: 0.1.25 + resolution: "@swc/types@npm:0.1.25" + dependencies: + "@swc/counter": "npm:^0.1.3" + checksum: 10c0/847a5b20b131281f89d640a7ed4887fb65724807d53d334b230e84b98c21097aa10cd28a074f9ed287a6ce109e443dd4bafbe7dcfb62333d7806c4ea3e7f8aca + languageName: node + linkType: hard + +"@swc/wasm@npm:^1.12.14": + version: 1.15.7 + resolution: "@swc/wasm@npm:1.15.7" + checksum: 10c0/306722f4841cf88da75a5a46f65987655ee704573cb6f0bac52e7b5b7a481423a1f71f2d8c8e3343438f234d7e5a2d8650ec051c0a2b7b0631ea772f99a13110 + languageName: node + linkType: hard + +"@tauri-apps/api@npm:^2.6.0, @tauri-apps/api@npm:^2.8.0": + version: 2.9.1 + resolution: "@tauri-apps/api@npm:2.9.1" + checksum: 10c0/18c76ec58b579860bfde5cd5b5ea6c3b13019d356c17d436bf395cafdf15dd1f277364cacd24cc94e5d4aa3816f39698f231773d2a18625e98702295ab0c2c8f + languageName: node + linkType: hard + +"@tauri-apps/cli-darwin-arm64@npm:2.9.6": + version: 2.9.6 + resolution: "@tauri-apps/cli-darwin-arm64@npm:2.9.6" + conditions: os=darwin & cpu=arm64 + languageName: node + linkType: hard + +"@tauri-apps/cli-darwin-x64@npm:2.9.6": + version: 2.9.6 + resolution: "@tauri-apps/cli-darwin-x64@npm:2.9.6" + conditions: os=darwin & cpu=x64 + languageName: node + linkType: hard + +"@tauri-apps/cli-linux-arm-gnueabihf@npm:2.9.6": + version: 2.9.6 + resolution: "@tauri-apps/cli-linux-arm-gnueabihf@npm:2.9.6" + conditions: os=linux & cpu=arm + languageName: node + linkType: hard + +"@tauri-apps/cli-linux-arm64-gnu@npm:2.9.6": + version: 2.9.6 + resolution: "@tauri-apps/cli-linux-arm64-gnu@npm:2.9.6" + conditions: os=linux & cpu=arm64 & libc=glibc + languageName: node + linkType: hard + +"@tauri-apps/cli-linux-arm64-musl@npm:2.9.6": + version: 2.9.6 + resolution: "@tauri-apps/cli-linux-arm64-musl@npm:2.9.6" + conditions: os=linux & cpu=arm64 & libc=musl + languageName: node + linkType: hard + +"@tauri-apps/cli-linux-riscv64-gnu@npm:2.9.6": + version: 2.9.6 + resolution: "@tauri-apps/cli-linux-riscv64-gnu@npm:2.9.6" + conditions: os=linux & cpu=riscv64 & libc=glibc + languageName: node + linkType: hard + +"@tauri-apps/cli-linux-x64-gnu@npm:2.9.6": + version: 2.9.6 + resolution: "@tauri-apps/cli-linux-x64-gnu@npm:2.9.6" + conditions: os=linux & cpu=x64 & libc=glibc + languageName: node + linkType: hard + +"@tauri-apps/cli-linux-x64-musl@npm:2.9.6": + version: 2.9.6 + resolution: "@tauri-apps/cli-linux-x64-musl@npm:2.9.6" + conditions: os=linux & cpu=x64 & libc=musl + languageName: node + linkType: hard + +"@tauri-apps/cli-win32-arm64-msvc@npm:2.9.6": + version: 2.9.6 + resolution: "@tauri-apps/cli-win32-arm64-msvc@npm:2.9.6" + conditions: os=win32 & cpu=arm64 + languageName: node + linkType: hard + +"@tauri-apps/cli-win32-ia32-msvc@npm:2.9.6": + version: 2.9.6 + resolution: "@tauri-apps/cli-win32-ia32-msvc@npm:2.9.6" + conditions: os=win32 & cpu=ia32 + languageName: node + linkType: hard + +"@tauri-apps/cli-win32-x64-msvc@npm:2.9.6": + version: 2.9.6 + resolution: "@tauri-apps/cli-win32-x64-msvc@npm:2.9.6" + conditions: os=win32 & cpu=x64 + languageName: node + linkType: hard + +"@tauri-apps/cli@npm:^2.0.0": + version: 2.9.6 + resolution: "@tauri-apps/cli@npm:2.9.6" + dependencies: + "@tauri-apps/cli-darwin-arm64": "npm:2.9.6" + "@tauri-apps/cli-darwin-x64": "npm:2.9.6" + "@tauri-apps/cli-linux-arm-gnueabihf": "npm:2.9.6" + "@tauri-apps/cli-linux-arm64-gnu": "npm:2.9.6" + "@tauri-apps/cli-linux-arm64-musl": "npm:2.9.6" + "@tauri-apps/cli-linux-riscv64-gnu": "npm:2.9.6" + "@tauri-apps/cli-linux-x64-gnu": "npm:2.9.6" + "@tauri-apps/cli-linux-x64-musl": "npm:2.9.6" + "@tauri-apps/cli-win32-arm64-msvc": "npm:2.9.6" + "@tauri-apps/cli-win32-ia32-msvc": "npm:2.9.6" + "@tauri-apps/cli-win32-x64-msvc": "npm:2.9.6" + dependenciesMeta: + "@tauri-apps/cli-darwin-arm64": + optional: true + "@tauri-apps/cli-darwin-x64": + optional: true + "@tauri-apps/cli-linux-arm-gnueabihf": + optional: true + "@tauri-apps/cli-linux-arm64-gnu": + optional: true + "@tauri-apps/cli-linux-arm64-musl": + optional: true + "@tauri-apps/cli-linux-riscv64-gnu": + optional: true + "@tauri-apps/cli-linux-x64-gnu": + optional: true + "@tauri-apps/cli-linux-x64-musl": + optional: true + "@tauri-apps/cli-win32-arm64-msvc": + optional: true + "@tauri-apps/cli-win32-ia32-msvc": + optional: true + "@tauri-apps/cli-win32-x64-msvc": + optional: true + bin: + tauri: tauri.js + checksum: 10c0/f8aed97b4dec2ad99720315d8bf392624e5db13eff30e89340849d14b17020a3f65af847f1ebdad88f187ccb0f00512e77a17af8c607350fc9a7c68c05414b8b + languageName: node + linkType: hard + +"@tauri-apps/plugin-cli@npm:^2.4.0": + version: 2.4.1 + resolution: "@tauri-apps/plugin-cli@npm:2.4.1" + dependencies: + "@tauri-apps/api": "npm:^2.8.0" + checksum: 10c0/3da30cce5fb527972243c28bb226349301328f98cf5afaef757a4c931452f71779a3c62530874d09dff9f18dffa9a8f3652ef9ba46c52e14194f2181818590e9 + languageName: node + linkType: hard + +"@tauri-apps/plugin-clipboard-manager@npm:^2.3.0": + version: 2.3.2 + resolution: "@tauri-apps/plugin-clipboard-manager@npm:2.3.2" + dependencies: + "@tauri-apps/api": "npm:^2.8.0" + checksum: 10c0/26ba8814bdd51c35d80fd0911643eee3862eac241352c70203c9f7fcc3d2dcf68e5efed63315f0d80b8446c16b0e9f80aa040da340461614034c00881ec246d6 + languageName: node + linkType: hard + +"@tauri-apps/plugin-dialog@npm:^2.0.0": + version: 2.4.2 + resolution: "@tauri-apps/plugin-dialog@npm:2.4.2" + dependencies: + "@tauri-apps/api": "npm:^2.8.0" + checksum: 10c0/2950a5aa07727710b5395491e9b80886c13cbefd05445a84412819128e90bdaec751795c47ed39dd80fb5181c4e21ee7aaaae6152551dbf1ddc02aa3eac59b37 + languageName: node + linkType: hard + +"@tauri-apps/plugin-opener@npm:^2.5.0": + version: 2.5.2 + resolution: "@tauri-apps/plugin-opener@npm:2.5.2" + dependencies: + "@tauri-apps/api": "npm:^2.8.0" + checksum: 10c0/b10c39063c59ca71e5a5c43bb93da34387a7e707d1baf65a53110141f1a973ce2b73f19be9f63d9312c030760ba2b876ad3069b9a123abb4883e025059a15c3f + languageName: node + linkType: hard + +"@tauri-apps/plugin-process@npm:^2.3.0": + version: 2.3.1 + resolution: "@tauri-apps/plugin-process@npm:2.3.1" + dependencies: + "@tauri-apps/api": "npm:^2.8.0" + checksum: 10c0/2e5086898f1c9f25f6426a752404c788727237142bbb7c8f418b97c76c5360874d06203150d136e51114df9e720022e4fa3681fd1d4cb6f777dc83c3553f8670 + languageName: node + linkType: hard + +"@tauri-apps/plugin-store@npm:^2.4.0": + version: 2.4.1 + resolution: "@tauri-apps/plugin-store@npm:2.4.1" + dependencies: + "@tauri-apps/api": "npm:^2.8.0" + checksum: 10c0/69af5e6f6ee81b86525b5510aff2f4f5a3ec3dff26b028f191bd5ecdf1d838721cac0942fb9838ec4c293687bfc2238e2c71493f500a5781dbe7c14001fb5660 + languageName: node + linkType: hard + +"@tauri-apps/plugin-updater@npm:^2.9.0": + version: 2.9.0 + resolution: "@tauri-apps/plugin-updater@npm:2.9.0" + dependencies: + "@tauri-apps/api": "npm:^2.6.0" + checksum: 10c0/72ce83d1c241308a13b9929f0900e4d33453875877009166e3998e3e75a1003ac48c3641086b4d3230f0f18c64f475ad6c3556d1603fc641ca50dc9c18d61866 + languageName: node + linkType: hard + +"@testing-library/react@npm:^16.0.1": + version: 16.3.1 + resolution: "@testing-library/react@npm:16.3.1" + dependencies: + "@babel/runtime": "npm:^7.12.5" + peerDependencies: + "@testing-library/dom": ^10.0.0 + "@types/react": ^18.0.0 || ^19.0.0 + "@types/react-dom": ^18.0.0 || ^19.0.0 + react: ^18.0.0 || ^19.0.0 + react-dom: ^18.0.0 || ^19.0.0 + peerDependenciesMeta: + "@types/react": + optional: true + "@types/react-dom": + optional: true + checksum: 10c0/5a26ceaa4ab1d065be722d93e3b019883864ae038f9fd1c974f5b8a173f5f35a25768ecb2baa02a783299f009cbcd09fa7ee0b8b3d360d1c0f81535436358b28 + languageName: node + linkType: hard + +"@testing-library/user-event@npm:^14.5.2": + version: 14.6.1 + resolution: "@testing-library/user-event@npm:14.6.1" + peerDependencies: + "@testing-library/dom": ">=7.21.4" + checksum: 10c0/75fea130a52bf320d35d46ed54f3eec77e71a56911b8b69a3fe29497b0b9947b2dc80d30f04054ad4ce7f577856ae3e5397ea7dff0ef14944d3909784c7a93fe + languageName: node + linkType: hard + +"@types/babel__core@npm:^7.20.5": + version: 7.20.5 + resolution: "@types/babel__core@npm:7.20.5" + dependencies: + "@babel/parser": "npm:^7.20.7" + "@babel/types": "npm:^7.20.7" + "@types/babel__generator": "npm:*" + "@types/babel__template": "npm:*" + "@types/babel__traverse": "npm:*" + checksum: 10c0/bdee3bb69951e833a4b811b8ee9356b69a61ed5b7a23e1a081ec9249769117fa83aaaf023bb06562a038eb5845155ff663e2d5c75dd95c1d5ccc91db012868ff + languageName: node + linkType: hard + +"@types/babel__generator@npm:*": + version: 7.27.0 + resolution: "@types/babel__generator@npm:7.27.0" + dependencies: + "@babel/types": "npm:^7.0.0" + checksum: 10c0/9f9e959a8792df208a9d048092fda7e1858bddc95c6314857a8211a99e20e6830bdeb572e3587ae8be5429e37f2a96fcf222a9f53ad232f5537764c9e13a2bbd + languageName: node + linkType: hard + +"@types/babel__template@npm:*": + version: 7.4.4 + resolution: "@types/babel__template@npm:7.4.4" + dependencies: + "@babel/parser": "npm:^7.1.0" + "@babel/types": "npm:^7.0.0" + checksum: 10c0/cc84f6c6ab1eab1427e90dd2b76ccee65ce940b778a9a67be2c8c39e1994e6f5bbc8efa309f6cea8dc6754994524cd4d2896558df76d92e7a1f46ecffee7112b + languageName: node + linkType: hard + +"@types/babel__traverse@npm:*": + version: 7.28.0 + resolution: "@types/babel__traverse@npm:7.28.0" + dependencies: + "@babel/types": "npm:^7.28.2" + checksum: 10c0/b52d7d4e8fc6a9018fe7361c4062c1c190f5778cf2466817cb9ed19d69fbbb54f9a85ffedeb748ed8062d2cf7d4cc088ee739848f47c57740de1c48cbf0d0994 + languageName: node + linkType: hard + +"@types/estree@npm:1.0.8, @types/estree@npm:^1.0.0, @types/estree@npm:^1.0.6": + version: 1.0.8 + resolution: "@types/estree@npm:1.0.8" + checksum: 10c0/39d34d1afaa338ab9763f37ad6066e3f349444f9052b9676a7cc0252ef9485a41c6d81c9c4e0d26e9077993354edf25efc853f3224dd4b447175ef62bdcc86a5 + languageName: node + linkType: hard + +"@types/get-params@npm:^0.1.2": + version: 0.1.2 + resolution: "@types/get-params@npm:0.1.2" + checksum: 10c0/7138af23d6acf0737d462263b8d2e5decfd282f1164aa7e2783c2fc9aeb2ec6aa10f6288605740356a916ecc4d27e01c1d8e6ad4c4fd0cd92790b8f482063cf6 + languageName: node + linkType: hard + +"@types/hoist-non-react-statics@npm:^3.3.0": + version: 3.3.7 + resolution: "@types/hoist-non-react-statics@npm:3.3.7" + dependencies: + hoist-non-react-statics: "npm:^3.3.0" + peerDependencies: + "@types/react": "*" + checksum: 10c0/ed8f4e88338f7d021d0f956adf6089d2a12b2e254a03c05292324f2e986d2376eb9efdb8a4f04596823e8fca88c9d06361d20dab4a2a00dc935fb36ac911de55 + languageName: node + linkType: hard + +"@types/humanize-duration@npm:^3.27.4": + version: 3.27.4 + resolution: "@types/humanize-duration@npm:3.27.4" + checksum: 10c0/0d92b036ac284c4ae248dca6efa826f9d397e7369f2d5e11de1813e05b32fd44e3d696f999acdc5a7eb412efc33668b8a68f6ad671392c0bbcf797a6329d674f + languageName: node + linkType: hard + +"@types/json-schema@npm:^7.0.15": + version: 7.0.15 + resolution: "@types/json-schema@npm:7.0.15" + checksum: 10c0/a996a745e6c5d60292f36731dd41341339d4eeed8180bb09226e5c8d23759067692b1d88e5d91d72ee83dfc00d3aca8e7bd43ea120516c17922cbcb7c3e252db + languageName: node + linkType: hard + +"@types/json5@npm:^0.0.29": + version: 0.0.29 + resolution: "@types/json5@npm:0.0.29" + checksum: 10c0/6bf5337bc447b706bb5b4431d37686aa2ea6d07cfd6f79cc31de80170d6ff9b1c7384a9c0ccbc45b3f512bae9e9f75c2e12109806a15331dc94e8a8db6dbb4ac + languageName: node + linkType: hard + +"@types/lodash@npm:^4.17.6": + version: 4.17.21 + resolution: "@types/lodash@npm:4.17.21" + checksum: 10c0/73cb006e047d8871e9d63f3a165543bf16c44a5b6fe3f9f6299e37cb8d67a7b1d55ac730959a81f9def510fd07232ff7e30e05413e5d5a12793baad84ebe36c3 + languageName: node + linkType: hard + +"@types/node@npm:*": + version: 25.0.3 + resolution: "@types/node@npm:25.0.3" + dependencies: + undici-types: "npm:~7.16.0" + checksum: 10c0/b7568f0d765d9469621615e2bb257c7fd1953d95e9acbdb58dffb6627a2c4150d405a4600aa1ad8a40182a94fe5f903cafd3c0a2f5132814debd0e3bfd61f835 + languageName: node + linkType: hard + +"@types/node@npm:^22.15.29": + version: 22.19.3 + resolution: "@types/node@npm:22.19.3" + dependencies: + undici-types: "npm:~6.21.0" + checksum: 10c0/a30a75d503da795ddbd5f8851014f3e91925c2a63fa3f14128d54c998f25d682dfba96dc9589c73cf478b87a16d88beb790b11697bb8cd5bee913079237a58f2 + languageName: node + linkType: hard + +"@types/parse-json@npm:^4.0.0": + version: 4.0.2 + resolution: "@types/parse-json@npm:4.0.2" + checksum: 10c0/b1b863ac34a2c2172fbe0807a1ec4d5cb684e48d422d15ec95980b81475fac4fdb3768a8b13eef39130203a7c04340fc167bae057c7ebcafd7dec9fe6c36aeb1 + languageName: node + linkType: hard + +"@types/prop-types@npm:^15.7.15": + version: 15.7.15 + resolution: "@types/prop-types@npm:15.7.15" + checksum: 10c0/b59aad1ad19bf1733cf524fd4e618196c6c7690f48ee70a327eb450a42aab8e8a063fbe59ca0a5701aebe2d92d582292c0fb845ea57474f6a15f6994b0e260b2 + languageName: node + linkType: hard + +"@types/react-dom@npm:^19.1.5": + version: 19.2.3 + resolution: "@types/react-dom@npm:19.2.3" + peerDependencies: + "@types/react": ^19.2.0 + checksum: 10c0/b486ebe0f4e2fb35e2e108df1d8fc0927ca5d6002d5771e8a739de11239fe62d0e207c50886185253c99eb9dedfeeb956ea7429e5ba17f6693c7acb4c02f8cd1 + languageName: node + linkType: hard + +"@types/react-redux@npm:^7.1.34": + version: 7.1.34 + resolution: "@types/react-redux@npm:7.1.34" + dependencies: + "@types/hoist-non-react-statics": "npm:^3.3.0" + "@types/react": "npm:*" + hoist-non-react-statics: "npm:^3.3.0" + redux: "npm:^4.0.0" + checksum: 10c0/6750964ec656eb6973b0e4fda787549aee5dbc266a0f0e78fc9efb417b4965c0b060d10b99a7b7fa0c8812b8a0a07d97a1ef46d094bf64fee07144e8bbad781a + languageName: node + linkType: hard + +"@types/react-transition-group@npm:^4.4.12": + version: 4.4.12 + resolution: "@types/react-transition-group@npm:4.4.12" + peerDependencies: + "@types/react": "*" + checksum: 10c0/0441b8b47c69312c89ec0760ba477ba1a0808a10ceef8dc1c64b1013ed78517332c30f18681b0ec0b53542731f1ed015169fed1d127cc91222638ed955478ec7 + languageName: node + linkType: hard + +"@types/react@npm:*, @types/react@npm:^19.1.6": + version: 19.2.7 + resolution: "@types/react@npm:19.2.7" + dependencies: + csstype: "npm:^3.2.2" + checksum: 10c0/a7b75f1f9fcb34badd6f84098be5e35a0aeca614bc91f93d2698664c0b2ba5ad128422bd470ada598238cebe4f9e604a752aead7dc6f5a92261d0c7f9b27cfd1 + languageName: node + linkType: hard + +"@types/semver@npm:^7.5.8": + version: 7.7.1 + resolution: "@types/semver@npm:7.7.1" + checksum: 10c0/c938aef3bf79a73f0f3f6037c16e2e759ff40c54122ddf0b2583703393d8d3127130823facb880e694caa324eb6845628186aac1997ee8b31dc2d18fafe26268 + languageName: node + linkType: hard + +"@types/use-sync-external-store@npm:^0.0.6": + version: 0.0.6 + resolution: "@types/use-sync-external-store@npm:0.0.6" + checksum: 10c0/77c045a98f57488201f678b181cccd042279aff3da34540ad242f893acc52b358bd0a8207a321b8ac09adbcef36e3236944390e2df4fcedb556ce7bb2a88f2a8 + languageName: node + linkType: hard + +"@typescript-eslint/eslint-plugin@npm:8.50.1": + version: 8.50.1 + resolution: "@typescript-eslint/eslint-plugin@npm:8.50.1" + dependencies: + "@eslint-community/regexpp": "npm:^4.10.0" + "@typescript-eslint/scope-manager": "npm:8.50.1" + "@typescript-eslint/type-utils": "npm:8.50.1" + "@typescript-eslint/utils": "npm:8.50.1" + "@typescript-eslint/visitor-keys": "npm:8.50.1" + ignore: "npm:^7.0.0" + natural-compare: "npm:^1.4.0" + ts-api-utils: "npm:^2.1.0" + peerDependencies: + "@typescript-eslint/parser": ^8.50.1 + eslint: ^8.57.0 || ^9.0.0 + typescript: ">=4.8.4 <6.0.0" + checksum: 10c0/cae56cec414dc5d8347f1ff9fc01ec7b82c7988bcca9597569564b69e1715594e044487805a72ce7a9b4e6e81c3632db92c3d4b6b991874dafa402e1fcb508d5 + languageName: node + linkType: hard + +"@typescript-eslint/parser@npm:8.50.1": + version: 8.50.1 + resolution: "@typescript-eslint/parser@npm:8.50.1" + dependencies: + "@typescript-eslint/scope-manager": "npm:8.50.1" + "@typescript-eslint/types": "npm:8.50.1" + "@typescript-eslint/typescript-estree": "npm:8.50.1" + "@typescript-eslint/visitor-keys": "npm:8.50.1" + debug: "npm:^4.3.4" + peerDependencies: + eslint: ^8.57.0 || ^9.0.0 + typescript: ">=4.8.4 <6.0.0" + checksum: 10c0/60a2591745650b35cd8d425bb1959ef40d598245481bdfdc2654ed1f7878364c2c442ba70ca7105b650d0df2b6109727dd43214be76045667de0d32a221f3955 + languageName: node + linkType: hard + +"@typescript-eslint/project-service@npm:8.50.1": + version: 8.50.1 + resolution: "@typescript-eslint/project-service@npm:8.50.1" + dependencies: + "@typescript-eslint/tsconfig-utils": "npm:^8.50.1" + "@typescript-eslint/types": "npm:^8.50.1" + debug: "npm:^4.3.4" + peerDependencies: + typescript: ">=4.8.4 <6.0.0" + checksum: 10c0/50fee0882188c2d704deddfb39f5283618adf7e5f72418143e9f69a8f3771233d55a3e0fc2673fa09c62e230ec53e500f95c0f1ed331ffac5f6a7f8e7b7a2e8c + languageName: node + linkType: hard + +"@typescript-eslint/scope-manager@npm:8.50.1": + version: 8.50.1 + resolution: "@typescript-eslint/scope-manager@npm:8.50.1" + dependencies: + "@typescript-eslint/types": "npm:8.50.1" + "@typescript-eslint/visitor-keys": "npm:8.50.1" + checksum: 10c0/ef0df092745f5d4e3684a3d770dc47735ab3195456de4ac5825931aeed1857a7e8d7cec14cc9c78c5ed049b3d83b0f8ac43b9463c5032ba548558a06bebb5539 + languageName: node + linkType: hard + +"@typescript-eslint/tsconfig-utils@npm:8.50.1, @typescript-eslint/tsconfig-utils@npm:^8.50.1": + version: 8.50.1 + resolution: "@typescript-eslint/tsconfig-utils@npm:8.50.1" + peerDependencies: + typescript: ">=4.8.4 <6.0.0" + checksum: 10c0/6a1ffb0cd2d9e820ed0c7555a43ebb21438ca80f26c9632e0753bd09e764d9b8e9a352215e4ae60f6d570ab1e77751c9460a00515648b9a2f13f56c56a068a94 + languageName: node + linkType: hard + +"@typescript-eslint/type-utils@npm:8.50.1": + version: 8.50.1 + resolution: "@typescript-eslint/type-utils@npm:8.50.1" + dependencies: + "@typescript-eslint/types": "npm:8.50.1" + "@typescript-eslint/typescript-estree": "npm:8.50.1" + "@typescript-eslint/utils": "npm:8.50.1" + debug: "npm:^4.3.4" + ts-api-utils: "npm:^2.1.0" + peerDependencies: + eslint: ^8.57.0 || ^9.0.0 + typescript: ">=4.8.4 <6.0.0" + checksum: 10c0/e4bfd3dd2459e936f7b6d9ee4b60fdedbf4b8f6b3d832e11d3cb1b58c1ce6da098880daafe3b65b2d33e2f79aba0e75c4b6eafdfa2a66c6e00a9ad3132b8e90d + languageName: node + linkType: hard + +"@typescript-eslint/types@npm:8.50.1, @typescript-eslint/types@npm:^8.50.1": + version: 8.50.1 + resolution: "@typescript-eslint/types@npm:8.50.1" + checksum: 10c0/04e3c296d81293e370578762be6736fccd1581476f9d534938d42fe93968571fcaf26d7d8c3de52ed63a5af2c0b2da922b8ee2011fa5fb9fb401fc7f0916367a + languageName: node + linkType: hard + +"@typescript-eslint/typescript-estree@npm:8.50.1": + version: 8.50.1 + resolution: "@typescript-eslint/typescript-estree@npm:8.50.1" + dependencies: + "@typescript-eslint/project-service": "npm:8.50.1" + "@typescript-eslint/tsconfig-utils": "npm:8.50.1" + "@typescript-eslint/types": "npm:8.50.1" + "@typescript-eslint/visitor-keys": "npm:8.50.1" + debug: "npm:^4.3.4" + minimatch: "npm:^9.0.4" + semver: "npm:^7.6.0" + tinyglobby: "npm:^0.2.15" + ts-api-utils: "npm:^2.1.0" + peerDependencies: + typescript: ">=4.8.4 <6.0.0" + checksum: 10c0/697b53fd3355619271a7bf543c5880731670b96567da63f554a3c3cd4d746feb8153628ec912c8a2df95e3123472e9a77df43c32fad72946b69ace89c2cf8b7e + languageName: node + linkType: hard + +"@typescript-eslint/utils@npm:8.50.1": + version: 8.50.1 + resolution: "@typescript-eslint/utils@npm:8.50.1" + dependencies: + "@eslint-community/eslint-utils": "npm:^4.7.0" + "@typescript-eslint/scope-manager": "npm:8.50.1" + "@typescript-eslint/types": "npm:8.50.1" + "@typescript-eslint/typescript-estree": "npm:8.50.1" + peerDependencies: + eslint: ^8.57.0 || ^9.0.0 + typescript: ">=4.8.4 <6.0.0" + checksum: 10c0/66b19a9c8981b0b601af3a477fdcabdd110b0805591f28eefa11b32bbb88518d80b928e49eaa4c40d42ea8d71605bf5cd2ee5e39802022d1daec2800f1b198df + languageName: node + linkType: hard + +"@typescript-eslint/visitor-keys@npm:8.50.1": + version: 8.50.1 + resolution: "@typescript-eslint/visitor-keys@npm:8.50.1" + dependencies: + "@typescript-eslint/types": "npm:8.50.1" + eslint-visitor-keys: "npm:^4.2.1" + checksum: 10c0/b23839d04b2e5e7964a4006317d75cdc3cf76e56f4c5fde1e0bcd23f3bb78dca910e3dcadca80606f76a09ff9e44b3363ee1e1d6394e3f7479da74a641a8870f + languageName: node + linkType: hard + +"@vitejs/plugin-react@npm:^4.2.1": + version: 4.7.0 + resolution: "@vitejs/plugin-react@npm:4.7.0" + dependencies: + "@babel/core": "npm:^7.28.0" + "@babel/plugin-transform-react-jsx-self": "npm:^7.27.1" + "@babel/plugin-transform-react-jsx-source": "npm:^7.27.1" + "@rolldown/pluginutils": "npm:1.0.0-beta.27" + "@types/babel__core": "npm:^7.20.5" + react-refresh: "npm:^0.17.0" + peerDependencies: + vite: ^4.2.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 + checksum: 10c0/692f23960972879485d647713663ec299c478222c96567d60285acf7c7dc5c178e71abfe9d2eefddef1eeb01514dacbc2ed68aad84628debf9c7116134734253 + languageName: node + linkType: hard + +"@vitest/expect@npm:2.1.9": + version: 2.1.9 + resolution: "@vitest/expect@npm:2.1.9" + dependencies: + "@vitest/spy": "npm:2.1.9" + "@vitest/utils": "npm:2.1.9" + chai: "npm:^5.1.2" + tinyrainbow: "npm:^1.2.0" + checksum: 10c0/98d1cf02917316bebef9e4720723e38298a1c12b3c8f3a81f259bb822de4288edf594e69ff64f0b88afbda6d04d7a4f0c2f720f3fec16b4c45f5e2669f09fdbb + languageName: node + linkType: hard + +"@vitest/mocker@npm:2.1.9": + version: 2.1.9 + resolution: "@vitest/mocker@npm:2.1.9" + dependencies: + "@vitest/spy": "npm:2.1.9" + estree-walker: "npm:^3.0.3" + magic-string: "npm:^0.30.12" + peerDependencies: + msw: ^2.4.9 + vite: ^5.0.0 + peerDependenciesMeta: + msw: + optional: true + vite: + optional: true + checksum: 10c0/f734490d8d1206a7f44dfdfca459282f5921d73efa72935bb1dc45307578defd38a4131b14853316373ec364cbe910dbc74594ed4137e0da35aa4d9bb716f190 + languageName: node + linkType: hard + +"@vitest/pretty-format@npm:2.1.9, @vitest/pretty-format@npm:^2.1.9": + version: 2.1.9 + resolution: "@vitest/pretty-format@npm:2.1.9" + dependencies: + tinyrainbow: "npm:^1.2.0" + checksum: 10c0/155f9ede5090eabed2a73361094bb35ed4ec6769ae3546d2a2af139166569aec41bb80e031c25ff2da22b71dd4ed51e5468e66a05e6aeda5f14b32e30bc18f00 + languageName: node + linkType: hard + +"@vitest/runner@npm:2.1.9": + version: 2.1.9 + resolution: "@vitest/runner@npm:2.1.9" + dependencies: + "@vitest/utils": "npm:2.1.9" + pathe: "npm:^1.1.2" + checksum: 10c0/e81f176badb12a815cbbd9bd97e19f7437a0b64e8934d680024b0f768d8670d59cad698ef0e3dada5241b6731d77a7bb3cd2c7cb29f751fd4dd35eb11c42963a + languageName: node + linkType: hard + +"@vitest/snapshot@npm:2.1.9": + version: 2.1.9 + resolution: "@vitest/snapshot@npm:2.1.9" + dependencies: + "@vitest/pretty-format": "npm:2.1.9" + magic-string: "npm:^0.30.12" + pathe: "npm:^1.1.2" + checksum: 10c0/394974b3a1fe96186a3c87f933b2f7f1f7b7cc42f9c781d80271dbb4c987809bf035fecd7398b8a3a2d54169e3ecb49655e38a0131d0e7fea5ce88960613b526 + languageName: node + linkType: hard + +"@vitest/spy@npm:2.1.9": + version: 2.1.9 + resolution: "@vitest/spy@npm:2.1.9" + dependencies: + tinyspy: "npm:^3.0.2" + checksum: 10c0/12a59b5095e20188b819a1d797e0a513d991b4e6a57db679927c43b362a3eff52d823b34e855a6dd9e73c9fa138dcc5ef52210841a93db5cbf047957a60ca83c + languageName: node + linkType: hard + +"@vitest/utils@npm:2.1.9": + version: 2.1.9 + resolution: "@vitest/utils@npm:2.1.9" + dependencies: + "@vitest/pretty-format": "npm:2.1.9" + loupe: "npm:^3.1.2" + tinyrainbow: "npm:^1.2.0" + checksum: 10c0/81a346cd72b47941f55411f5df4cc230e5f740d1e97e0d3f771b27f007266fc1f28d0438582f6409ea571bc0030ed37f684c64c58d1947d6298d770c21026fdf + languageName: node + linkType: hard + +"abbrev@npm:^4.0.0": + version: 4.0.0 + resolution: "abbrev@npm:4.0.0" + checksum: 10c0/b4cc16935235e80702fc90192e349e32f8ef0ed151ef506aa78c81a7c455ec18375c4125414b99f84b2e055199d66383e787675f0bcd87da7a4dbd59f9eac1d5 + languageName: node + linkType: hard + +"acorn-jsx@npm:^5.3.2": + version: 5.3.2 + resolution: "acorn-jsx@npm:5.3.2" + peerDependencies: + acorn: ^6.0.0 || ^7.0.0 || ^8.0.0 + checksum: 10c0/4c54868fbef3b8d58927d5e33f0a4de35f59012fe7b12cf9dfbb345fb8f46607709e1c4431be869a23fb63c151033d84c4198fa9f79385cec34fcb1dd53974c1 + languageName: node + linkType: hard + +"acorn@npm:^8.15.0": + version: 8.15.0 + resolution: "acorn@npm:8.15.0" + bin: + acorn: bin/acorn + checksum: 10c0/dec73ff59b7d6628a01eebaece7f2bdb8bb62b9b5926dcad0f8931f2b8b79c2be21f6c68ac095592adb5adb15831a3635d9343e6a91d028bbe85d564875ec3ec + languageName: node + linkType: hard + +"ag-auth@npm:^2.1.0": + version: 2.1.1 + resolution: "ag-auth@npm:2.1.1" + dependencies: + jsonwebtoken: "npm:^9.0.2" + sc-errors: "npm:^3.0.0" + checksum: 10c0/c8c15b751deae509020e28bc266d3a5d0a04adb7da5677d932afaf73c5193dfbd41f0b6bbf1ba771d0543bea9cf7c49ba0aaa8362e14160c240d7b586113750d + languageName: node + linkType: hard + +"ag-channel@npm:^5.0.0": + version: 5.0.0 + resolution: "ag-channel@npm:5.0.0" + dependencies: + consumable-stream: "npm:^2.0.0" + checksum: 10c0/54dcac553a21f28687c99beef8287c3c6bdebd23585fa9e1a54205eb46e8bf5bcd204c78ca5753ab0d5b01330576b49462cec6e8fdcfcf0f1478162439536ec4 + languageName: node + linkType: hard + +"ag-request@npm:^1.1.0": + version: 1.1.0 + resolution: "ag-request@npm:1.1.0" + dependencies: + sc-errors: "npm:^3.0.0" + checksum: 10c0/7a7a53252e66cab1a0e4ea422df83214ed9f277e895692c543aa2499184bc8812a65e3c8b4941c78ff949ecdff0fb8e8a12879e14c20e12057187cf801de2471 + languageName: node + linkType: hard + +"agent-base@npm:^7.1.0, agent-base@npm:^7.1.2": + version: 7.1.4 + resolution: "agent-base@npm:7.1.4" + checksum: 10c0/c2c9ab7599692d594b6a161559ada307b7a624fa4c7b03e3afdb5a5e31cd0e53269115b620fcab024c5ac6a6f37fa5eb2e004f076ad30f5f7e6b8b671f7b35fe + languageName: node + linkType: hard + +"ajv@npm:^6.12.4": + version: 6.12.6 + resolution: "ajv@npm:6.12.6" + dependencies: + fast-deep-equal: "npm:^3.1.1" + fast-json-stable-stringify: "npm:^2.0.0" + json-schema-traverse: "npm:^0.4.1" + uri-js: "npm:^4.2.2" + checksum: 10c0/41e23642cbe545889245b9d2a45854ebba51cda6c778ebced9649420d9205f2efb39cb43dbc41e358409223b1ea43303ae4839db682c848b891e4811da1a5a71 + languageName: node + linkType: hard + +"ansi-styles@npm:^4.1.0": + version: 4.3.0 + resolution: "ansi-styles@npm:4.3.0" + dependencies: + color-convert: "npm:^2.0.1" + checksum: 10c0/895a23929da416f2bd3de7e9cb4eabd340949328ab85ddd6e484a637d8f6820d485f53933446f5291c3b760cbc488beb8e88573dd0f9c7daf83dccc8fe81b041 + languageName: node + linkType: hard + +"argparse@npm:^2.0.1": + version: 2.0.1 + resolution: "argparse@npm:2.0.1" + checksum: 10c0/c5640c2d89045371c7cedd6a70212a04e360fd34d6edeae32f6952c63949e3525ea77dbec0289d8213a99bbaeab5abfa860b5c12cf88a2e6cf8106e90dd27a7e + languageName: node + linkType: hard + +"array-buffer-byte-length@npm:^1.0.1, array-buffer-byte-length@npm:^1.0.2": + version: 1.0.2 + resolution: "array-buffer-byte-length@npm:1.0.2" + dependencies: + call-bound: "npm:^1.0.3" + is-array-buffer: "npm:^3.0.5" + checksum: 10c0/74e1d2d996941c7a1badda9cabb7caab8c449db9086407cad8a1b71d2604cc8abf105db8ca4e02c04579ec58b7be40279ddb09aea4784832984485499f48432d + languageName: node + linkType: hard + +"array-includes@npm:^3.1.6, array-includes@npm:^3.1.8, array-includes@npm:^3.1.9": + version: 3.1.9 + resolution: "array-includes@npm:3.1.9" + dependencies: + call-bind: "npm:^1.0.8" + call-bound: "npm:^1.0.4" + define-properties: "npm:^1.2.1" + es-abstract: "npm:^1.24.0" + es-object-atoms: "npm:^1.1.1" + get-intrinsic: "npm:^1.3.0" + is-string: "npm:^1.1.1" + math-intrinsics: "npm:^1.1.0" + checksum: 10c0/0235fa69078abeac05ac4250699c44996bc6f774a9cbe45db48674ce6bd142f09b327d31482ff75cf03344db4ea03eae23edb862d59378b484b47ed842574856 + languageName: node + linkType: hard + +"array.prototype.findlast@npm:^1.2.5": + version: 1.2.5 + resolution: "array.prototype.findlast@npm:1.2.5" + dependencies: + call-bind: "npm:^1.0.7" + define-properties: "npm:^1.2.1" + es-abstract: "npm:^1.23.2" + es-errors: "npm:^1.3.0" + es-object-atoms: "npm:^1.0.0" + es-shim-unscopables: "npm:^1.0.2" + checksum: 10c0/ddc952b829145ab45411b9d6adcb51a8c17c76bf89c9dd64b52d5dffa65d033da8c076ed2e17091779e83bc892b9848188d7b4b33453c5565e65a92863cb2775 + languageName: node + linkType: hard + +"array.prototype.findlastindex@npm:^1.2.6": + version: 1.2.6 + resolution: "array.prototype.findlastindex@npm:1.2.6" + dependencies: + call-bind: "npm:^1.0.8" + call-bound: "npm:^1.0.4" + define-properties: "npm:^1.2.1" + es-abstract: "npm:^1.23.9" + es-errors: "npm:^1.3.0" + es-object-atoms: "npm:^1.1.1" + es-shim-unscopables: "npm:^1.1.0" + checksum: 10c0/82559310d2e57ec5f8fc53d7df420e3abf0ba497935de0a5570586035478ba7d07618cb18e2d4ada2da514c8fb98a034aaf5c06caa0a57e2f7f4c4adedef5956 + languageName: node + linkType: hard + +"array.prototype.flat@npm:^1.3.1, array.prototype.flat@npm:^1.3.3": + version: 1.3.3 + resolution: "array.prototype.flat@npm:1.3.3" + dependencies: + call-bind: "npm:^1.0.8" + define-properties: "npm:^1.2.1" + es-abstract: "npm:^1.23.5" + es-shim-unscopables: "npm:^1.0.2" + checksum: 10c0/d90e04dfbc43bb96b3d2248576753d1fb2298d2d972e29ca7ad5ec621f0d9e16ff8074dae647eac4f31f4fb7d3f561a7ac005fb01a71f51705a13b5af06a7d8a + languageName: node + linkType: hard + +"array.prototype.flatmap@npm:^1.3.3": + version: 1.3.3 + resolution: "array.prototype.flatmap@npm:1.3.3" + dependencies: + call-bind: "npm:^1.0.8" + define-properties: "npm:^1.2.1" + es-abstract: "npm:^1.23.5" + es-shim-unscopables: "npm:^1.0.2" + checksum: 10c0/ba899ea22b9dc9bf276e773e98ac84638ed5e0236de06f13d63a90b18ca9e0ec7c97d622d899796e3773930b946cd2413d098656c0c5d8cc58c6f25c21e6bd54 + languageName: node + linkType: hard + +"array.prototype.tosorted@npm:^1.1.4": + version: 1.1.4 + resolution: "array.prototype.tosorted@npm:1.1.4" + dependencies: + call-bind: "npm:^1.0.7" + define-properties: "npm:^1.2.1" + es-abstract: "npm:^1.23.3" + es-errors: "npm:^1.3.0" + es-shim-unscopables: "npm:^1.0.2" + checksum: 10c0/eb3c4c4fc0381b0bf6dba2ea4d48d367c2827a0d4236a5718d97caaccc6b78f11f4cadf090736e86301d295a6aa4967ed45568f92ced51be8cbbacd9ca410943 + languageName: node + linkType: hard + +"arraybuffer.prototype.slice@npm:^1.0.4": + version: 1.0.4 + resolution: "arraybuffer.prototype.slice@npm:1.0.4" + dependencies: + array-buffer-byte-length: "npm:^1.0.1" + call-bind: "npm:^1.0.8" + define-properties: "npm:^1.2.1" + es-abstract: "npm:^1.23.5" + es-errors: "npm:^1.3.0" + get-intrinsic: "npm:^1.2.6" + is-array-buffer: "npm:^3.0.4" + checksum: 10c0/2f2459caa06ae0f7f615003f9104b01f6435cc803e11bd2a655107d52a1781dc040532dc44d93026b694cc18793993246237423e13a5337e86b43ed604932c06 + languageName: node + linkType: hard + +"assertion-error@npm:^2.0.1": + version: 2.0.1 + resolution: "assertion-error@npm:2.0.1" + checksum: 10c0/bbbcb117ac6480138f8c93cf7f535614282dea9dc828f540cdece85e3c665e8f78958b96afac52f29ff883c72638e6a87d469ecc9fe5bc902df03ed24a55dba8 + languageName: node + linkType: hard + +"async-function@npm:^1.0.0": + version: 1.0.0 + resolution: "async-function@npm:1.0.0" + checksum: 10c0/669a32c2cb7e45091330c680e92eaeb791bc1d4132d827591e499cd1f776ff5a873e77e5f92d0ce795a8d60f10761dec9ddfe7225a5de680f5d357f67b1aac73 + languageName: node + linkType: hard + +"async-generator-function@npm:^1.0.0": + version: 1.0.0 + resolution: "async-generator-function@npm:1.0.0" + checksum: 10c0/2c50ef856c543ad500d8d8777d347e3c1ba623b93e99c9263ecc5f965c1b12d2a140e2ab6e43c3d0b85366110696f28114649411cbcd10b452a92a2318394186 + languageName: node + linkType: hard + +"async-stream-emitter@npm:^7.0.1": + version: 7.0.1 + resolution: "async-stream-emitter@npm:7.0.1" + dependencies: + stream-demux: "npm:^10.0.1" + checksum: 10c0/0e8d1daaca584f741dd10375748fa11bb7a52282d88c5b4ddcefeba776edf60dc3d4aa4820cdc0d162519b0838085ec250d427dd3e3b7f0926a91d95da320473 + languageName: node + linkType: hard + +"available-typed-arrays@npm:^1.0.7": + version: 1.0.7 + resolution: "available-typed-arrays@npm:1.0.7" + dependencies: + possible-typed-array-names: "npm:^1.0.0" + checksum: 10c0/d07226ef4f87daa01bd0fe80f8f310982e345f372926da2e5296aecc25c41cab440916bbaa4c5e1034b453af3392f67df5961124e4b586df1e99793a1374bdb2 + languageName: node + linkType: hard + +"babel-plugin-macros@npm:^3.1.0": + version: 3.1.0 + resolution: "babel-plugin-macros@npm:3.1.0" + dependencies: + "@babel/runtime": "npm:^7.12.5" + cosmiconfig: "npm:^7.0.0" + resolve: "npm:^1.19.0" + checksum: 10c0/c6dfb15de96f67871d95bd2e8c58b0c81edc08b9b087dc16755e7157f357dc1090a8dc60ebab955e92587a9101f02eba07e730adc253a1e4cf593ca3ebd3839c + languageName: node + linkType: hard + +"babel-plugin-react-compiler@npm:^1.0.0": + version: 1.0.0 + resolution: "babel-plugin-react-compiler@npm:1.0.0" + dependencies: + "@babel/types": "npm:^7.26.0" + checksum: 10c0/9406267ada8d7dbdfe8906b40ecadb816a5f4cee2922bee23f7729293b369624ee135b5a9b0f263851c263c9787522ac5d97016c9a2b82d1668300e42b18aff8 + languageName: node + linkType: hard + +"balanced-match@npm:^1.0.0": + version: 1.0.2 + resolution: "balanced-match@npm:1.0.2" + checksum: 10c0/9308baf0a7e4838a82bbfd11e01b1cb0f0cf2893bc1676c27c2a8c0e70cbae1c59120c3268517a8ae7fb6376b4639ef81ca22582611dbee4ed28df945134aaee + languageName: node + linkType: hard + +"base64-js@npm:^1.3.1": + version: 1.5.1 + resolution: "base64-js@npm:1.5.1" + checksum: 10c0/f23823513b63173a001030fae4f2dabe283b99a9d324ade3ad3d148e218134676f1ee8568c877cd79ec1c53158dcf2d2ba527a97c606618928ba99dd930102bf + languageName: node + linkType: hard + +"baseline-browser-mapping@npm:^2.9.0": + version: 2.9.11 + resolution: "baseline-browser-mapping@npm:2.9.11" + bin: + baseline-browser-mapping: dist/cli.js + checksum: 10c0/eba49fcc1b33ab994aeeb73a4848f2670e06a0886dd5b903689ae6f60d47e7f1bea9262dbb2548c48179e858f7eda2b82ddf941ae783b862f4dcc51085a246f2 + languageName: node + linkType: hard + +"bl@npm:^1.2.1": + version: 1.2.3 + resolution: "bl@npm:1.2.3" + dependencies: + readable-stream: "npm:^2.3.5" + safe-buffer: "npm:^5.1.1" + checksum: 10c0/ee6478864d3b1295614f269f3fbabeb2362a2f2fc7f8dc2f6c1f944a278d84e0572ecefd6d0b0736d7418763f98dc3b2738253191ea9e98e4b08de211cfac0a6 + languageName: node + linkType: hard + +"brace-expansion@npm:^1.1.7": + version: 1.1.12 + resolution: "brace-expansion@npm:1.1.12" + dependencies: + balanced-match: "npm:^1.0.0" + concat-map: "npm:0.0.1" + checksum: 10c0/975fecac2bb7758c062c20d0b3b6288c7cc895219ee25f0a64a9de662dbac981ff0b6e89909c3897c1f84fa353113a721923afdec5f8b2350255b097f12b1f73 + languageName: node + linkType: hard + +"brace-expansion@npm:^2.0.1": + version: 2.0.2 + resolution: "brace-expansion@npm:2.0.2" + dependencies: + balanced-match: "npm:^1.0.0" + checksum: 10c0/6d117a4c793488af86b83172deb6af143e94c17bc53b0b3cec259733923b4ca84679d506ac261f4ba3c7ed37c46018e2ff442f9ce453af8643ecd64f4a54e6cf + languageName: node + linkType: hard + +"browserslist@npm:^4.24.0": + version: 4.28.1 + resolution: "browserslist@npm:4.28.1" + dependencies: + baseline-browser-mapping: "npm:^2.9.0" + caniuse-lite: "npm:^1.0.30001759" + electron-to-chromium: "npm:^1.5.263" + node-releases: "npm:^2.0.27" + update-browserslist-db: "npm:^1.2.0" + bin: + browserslist: cli.js + checksum: 10c0/545a5fa9d7234e3777a7177ec1e9134bb2ba60a69e6b95683f6982b1473aad347c77c1264ccf2ac5dea609a9731fbfbda6b85782bdca70f80f86e28a402504bd + languageName: node + linkType: hard + +"buffer-equal-constant-time@npm:^1.0.1": + version: 1.0.1 + resolution: "buffer-equal-constant-time@npm:1.0.1" + checksum: 10c0/fb2294e64d23c573d0dd1f1e7a466c3e978fe94a4e0f8183937912ca374619773bef8e2aceb854129d2efecbbc515bbd0cc78d2734a3e3031edb0888531bbc8e + languageName: node + linkType: hard + +"buffer@npm:^5.2.1": + version: 5.7.1 + resolution: "buffer@npm:5.7.1" + dependencies: + base64-js: "npm:^1.3.1" + ieee754: "npm:^1.1.13" + checksum: 10c0/27cac81cff434ed2876058d72e7c4789d11ff1120ef32c9de48f59eab58179b66710c488987d295ae89a228f835fc66d088652dffeb8e3ba8659f80eb091d55e + languageName: node + linkType: hard + +"cac@npm:^6.7.14": + version: 6.7.14 + resolution: "cac@npm:6.7.14" + checksum: 10c0/4ee06aaa7bab8981f0d54e5f5f9d4adcd64058e9697563ce336d8a3878ed018ee18ebe5359b2430eceae87e0758e62ea2019c3f52ae6e211b1bd2e133856cd10 + languageName: node + linkType: hard + +"cacache@npm:^20.0.1": + version: 20.0.3 + resolution: "cacache@npm:20.0.3" + dependencies: + "@npmcli/fs": "npm:^5.0.0" + fs-minipass: "npm:^3.0.0" + glob: "npm:^13.0.0" + lru-cache: "npm:^11.1.0" + minipass: "npm:^7.0.3" + minipass-collect: "npm:^2.0.1" + minipass-flush: "npm:^1.0.5" + minipass-pipeline: "npm:^1.2.4" + p-map: "npm:^7.0.2" + ssri: "npm:^13.0.0" + unique-filename: "npm:^5.0.0" + checksum: 10c0/c7da1ca694d20e8f8aedabd21dc11518f809a7d2b59aa76a1fc655db5a9e62379e465c157ddd2afe34b19230808882288effa6911b2de26a088a6d5645123462 + languageName: node + linkType: hard + +"call-bind-apply-helpers@npm:^1.0.0, call-bind-apply-helpers@npm:^1.0.1, call-bind-apply-helpers@npm:^1.0.2": + version: 1.0.2 + resolution: "call-bind-apply-helpers@npm:1.0.2" + dependencies: + es-errors: "npm:^1.3.0" + function-bind: "npm:^1.1.2" + checksum: 10c0/47bd9901d57b857590431243fea704ff18078b16890a6b3e021e12d279bbf211d039155e27d7566b374d49ee1f8189344bac9833dec7a20cdec370506361c938 + languageName: node + linkType: hard + +"call-bind@npm:^1.0.7, call-bind@npm:^1.0.8": + version: 1.0.8 + resolution: "call-bind@npm:1.0.8" + dependencies: + call-bind-apply-helpers: "npm:^1.0.0" + es-define-property: "npm:^1.0.0" + get-intrinsic: "npm:^1.2.4" + set-function-length: "npm:^1.2.2" + checksum: 10c0/a13819be0681d915144467741b69875ae5f4eba8961eb0bf322aab63ec87f8250eb6d6b0dcbb2e1349876412a56129ca338592b3829ef4343527f5f18a0752d4 + languageName: node + linkType: hard + +"call-bound@npm:^1.0.2, call-bound@npm:^1.0.3, call-bound@npm:^1.0.4": + version: 1.0.4 + resolution: "call-bound@npm:1.0.4" + dependencies: + call-bind-apply-helpers: "npm:^1.0.2" + get-intrinsic: "npm:^1.3.0" + checksum: 10c0/f4796a6a0941e71c766aea672f63b72bc61234c4f4964dc6d7606e3664c307e7d77845328a8f3359ce39ddb377fed67318f9ee203dea1d47e46165dcf2917644 + languageName: node + linkType: hard + +"callsites@npm:^3.0.0": + version: 3.1.0 + resolution: "callsites@npm:3.1.0" + checksum: 10c0/fff92277400eb06c3079f9e74f3af120db9f8ea03bad0e84d9aede54bbe2d44a56cccb5f6cf12211f93f52306df87077ecec5b712794c5a9b5dac6d615a3f301 + languageName: node + linkType: hard + +"caniuse-lite@npm:^1.0.30001759": + version: 1.0.30001761 + resolution: "caniuse-lite@npm:1.0.30001761" + checksum: 10c0/8ea4158ccd507b9c73c03b9b3b1b897e75d095c5753a131d0f36ef9b64c19a049174467842dd9e8bebe886ac27ed7a5b1d5adcaae5fe887716b07fc1103e100b + languageName: node + linkType: hard + +"canvas-renderer@npm:~2.2.0": + version: 2.2.1 + resolution: "canvas-renderer@npm:2.2.1" + dependencies: + "@types/node": "npm:*" + checksum: 10c0/436f2f516723cf2cac03dcb189907b282249cc33446c6bce7760fb3ebd22689b0a4a27b3a3ab077a2c8204bd597a5e923da505520735b00cd839a790e6bdda50 + languageName: node + linkType: hard + +"chai@npm:^5.1.2": + version: 5.3.3 + resolution: "chai@npm:5.3.3" + dependencies: + assertion-error: "npm:^2.0.1" + check-error: "npm:^2.1.1" + deep-eql: "npm:^5.0.1" + loupe: "npm:^3.1.0" + pathval: "npm:^2.0.0" + checksum: 10c0/b360fd4d38861622e5010c2f709736988b05c7f31042305fa3f4e9911f6adb80ccfb4e302068bf8ed10e835c2e2520cba0f5edc13d878b886987e5aa62483f53 + languageName: node + linkType: hard + +"chalk@npm:^4.0.0": + version: 4.1.2 + resolution: "chalk@npm:4.1.2" + dependencies: + ansi-styles: "npm:^4.1.0" + supports-color: "npm:^7.1.0" + checksum: 10c0/4a3fef5cc34975c898ffe77141450f679721df9dde00f6c304353fa9c8b571929123b26a0e4617bde5018977eb655b31970c297b91b63ee83bb82aeb04666880 + languageName: node + linkType: hard + +"check-error@npm:^2.1.1": + version: 2.1.1 + resolution: "check-error@npm:2.1.1" + checksum: 10c0/979f13eccab306cf1785fa10941a590b4e7ea9916ea2a4f8c87f0316fc3eab07eabefb6e587424ef0f88cbcd3805791f172ea739863ca3d7ce2afc54641c7f0e + languageName: node + linkType: hard + +"chownr@npm:^3.0.0": + version: 3.0.0 + resolution: "chownr@npm:3.0.0" + checksum: 10c0/43925b87700f7e3893296c8e9c56cc58f926411cce3a6e5898136daaf08f08b9a8eb76d37d3267e707d0dcc17aed2e2ebdf5848c0c3ce95cf910a919935c1b10 + languageName: node + linkType: hard + +"clone-deep@npm:^4.0.1": + version: 4.0.1 + resolution: "clone-deep@npm:4.0.1" + dependencies: + is-plain-object: "npm:^2.0.4" + kind-of: "npm:^6.0.2" + shallow-clone: "npm:^3.0.0" + checksum: 10c0/637753615aa24adf0f2d505947a1bb75e63964309034a1cf56ba4b1f30af155201edd38d26ffe26911adaae267a3c138b344a4947d39f5fc1b6d6108125aa758 + languageName: node + linkType: hard + +"clsx@npm:^1.1.0": + version: 1.2.1 + resolution: "clsx@npm:1.2.1" + checksum: 10c0/34dead8bee24f5e96f6e7937d711978380647e936a22e76380290e35486afd8634966ce300fc4b74a32f3762c7d4c0303f442c3e259f4ce02374eb0c82834f27 + languageName: node + linkType: hard + +"clsx@npm:^2.1.1": + version: 2.1.1 + resolution: "clsx@npm:2.1.1" + checksum: 10c0/c4c8eb865f8c82baab07e71bfa8897c73454881c4f99d6bc81585aecd7c441746c1399d08363dc096c550cceaf97bd4ce1e8854e1771e9998d9f94c4fe075839 + languageName: node + linkType: hard + +"color-convert@npm:^2.0.1": + version: 2.0.1 + resolution: "color-convert@npm:2.0.1" + dependencies: + color-name: "npm:~1.1.4" + checksum: 10c0/37e1150172f2e311fe1b2df62c6293a342ee7380da7b9cfdba67ea539909afbd74da27033208d01d6d5cfc65ee7868a22e18d7e7648e004425441c0f8a15a7d7 + languageName: node + linkType: hard + +"color-name@npm:~1.1.4": + version: 1.1.4 + resolution: "color-name@npm:1.1.4" + checksum: 10c0/a1a3f914156960902f46f7f56bc62effc6c94e84b2cae157a526b1c1f74b677a47ec602bf68a61abfa2b42d15b7c5651c6dbe72a43af720bc588dff885b10f95 + languageName: node + linkType: hard + +"concat-map@npm:0.0.1": + version: 0.0.1 + resolution: "concat-map@npm:0.0.1" + checksum: 10c0/c996b1cfdf95b6c90fee4dae37e332c8b6eb7d106430c17d538034c0ad9a1630cb194d2ab37293b1bdd4d779494beee7786d586a50bd9376fd6f7bcc2bd4c98f + languageName: node + linkType: hard + +"consumable-stream@npm:^2.0.0": + version: 2.0.0 + resolution: "consumable-stream@npm:2.0.0" + checksum: 10c0/4276ac662d254ef17e38daeaf280584801906a661ab215bde5cc3a37e6bd3def428c19abd8db601527a1582c065529ad9608466f09619ba3d1dcd89abcfadaea + languageName: node + linkType: hard + +"consumable-stream@npm:^3.0.0": + version: 3.0.0 + resolution: "consumable-stream@npm:3.0.0" + checksum: 10c0/a6e9a7f29666aa135e898628edf3671d92b6fe061dcab1eff6f8eadb35438ea0f6eb2d782b9ca8320bb05ae2e73706986a17392f2c6bcd5e5c6826745d353fcb + languageName: node + linkType: hard + +"convert-source-map@npm:^1.5.0": + version: 1.9.0 + resolution: "convert-source-map@npm:1.9.0" + checksum: 10c0/281da55454bf8126cbc6625385928c43479f2060984180c42f3a86c8b8c12720a24eac260624a7d1e090004028d2dee78602330578ceec1a08e27cb8bb0a8a5b + languageName: node + linkType: hard + +"convert-source-map@npm:^2.0.0": + version: 2.0.0 + resolution: "convert-source-map@npm:2.0.0" + checksum: 10c0/8f2f7a27a1a011cc6cc88cc4da2d7d0cfa5ee0369508baae3d98c260bb3ac520691464e5bbe4ae7cdf09860c1d69ecc6f70c63c6e7c7f7e3f18ec08484dc7d9b + languageName: node + linkType: hard + +"cookie@npm:^1.0.1": + version: 1.1.1 + resolution: "cookie@npm:1.1.1" + checksum: 10c0/79c4ddc0fcad9c4f045f826f42edf54bcc921a29586a4558b0898277fa89fb47be95bc384c2253f493af7b29500c830da28341274527328f18eba9f58afa112c + languageName: node + linkType: hard + +"core-util-is@npm:~1.0.0": + version: 1.0.3 + resolution: "core-util-is@npm:1.0.3" + checksum: 10c0/90a0e40abbddfd7618f8ccd63a74d88deea94e77d0e8dbbea059fa7ebebb8fbb4e2909667fe26f3a467073de1a542ebe6ae4c73a73745ac5833786759cd906c9 + languageName: node + linkType: hard + +"cosmiconfig@npm:^7.0.0": + version: 7.1.0 + resolution: "cosmiconfig@npm:7.1.0" + dependencies: + "@types/parse-json": "npm:^4.0.0" + import-fresh: "npm:^3.2.1" + parse-json: "npm:^5.0.0" + path-type: "npm:^4.0.0" + yaml: "npm:^1.10.0" + checksum: 10c0/b923ff6af581638128e5f074a5450ba12c0300b71302398ea38dbeabd33bbcaa0245ca9adbedfcf284a07da50f99ede5658c80bb3e39e2ce770a99d28a21ef03 + languageName: node + linkType: hard + +"cross-spawn@npm:^7.0.3, cross-spawn@npm:^7.0.6": + version: 7.0.6 + resolution: "cross-spawn@npm:7.0.6" + dependencies: + path-key: "npm:^3.1.0" + shebang-command: "npm:^2.0.0" + which: "npm:^2.0.1" + checksum: 10c0/053ea8b2135caff68a9e81470e845613e374e7309a47731e81639de3eaeb90c3d01af0e0b44d2ab9d50b43467223b88567dfeb3262db942dc063b9976718ffc1 + languageName: node + linkType: hard + +"csstype@npm:^3.0.10, csstype@npm:^3.0.2, csstype@npm:^3.1.3, csstype@npm:^3.2.2": + version: 3.2.3 + resolution: "csstype@npm:3.2.3" + checksum: 10c0/cd29c51e70fa822f1cecd8641a1445bed7063697469d35633b516e60fe8c1bde04b08f6c5b6022136bb669b64c63d4173af54864510fbb4ee23281801841a3ce + languageName: node + linkType: hard + +"data-view-buffer@npm:^1.0.2": + version: 1.0.2 + resolution: "data-view-buffer@npm:1.0.2" + dependencies: + call-bound: "npm:^1.0.3" + es-errors: "npm:^1.3.0" + is-data-view: "npm:^1.0.2" + checksum: 10c0/7986d40fc7979e9e6241f85db8d17060dd9a71bd53c894fa29d126061715e322a4cd47a00b0b8c710394854183d4120462b980b8554012acc1c0fa49df7ad38c + languageName: node + linkType: hard + +"data-view-byte-length@npm:^1.0.2": + version: 1.0.2 + resolution: "data-view-byte-length@npm:1.0.2" + dependencies: + call-bound: "npm:^1.0.3" + es-errors: "npm:^1.3.0" + is-data-view: "npm:^1.0.2" + checksum: 10c0/f8a4534b5c69384d95ac18137d381f18a5cfae1f0fc1df0ef6feef51ef0d568606d970b69e02ea186c6c0f0eac77fe4e6ad96fec2569cc86c3afcc7475068c55 + languageName: node + linkType: hard + +"data-view-byte-offset@npm:^1.0.1": + version: 1.0.1 + resolution: "data-view-byte-offset@npm:1.0.1" + dependencies: + call-bound: "npm:^1.0.2" + es-errors: "npm:^1.3.0" + is-data-view: "npm:^1.0.1" + checksum: 10c0/fa7aa40078025b7810dcffc16df02c480573b7b53ef1205aa6a61533011005c1890e5ba17018c692ce7c900212b547262d33279fde801ad9843edc0863bf78c4 + languageName: node + linkType: hard + +"dayjs@npm:^1.11.13": + version: 1.11.19 + resolution: "dayjs@npm:1.11.19" + checksum: 10c0/7d8a6074a343f821f81ea284d700bd34ea6c7abbe8d93bce7aba818948957c1b7f56131702e5e890a5622cdfc05dcebe8aed0b8313bdc6838a594d7846b0b000 + languageName: node + linkType: hard + +"debug@npm:4, debug@npm:^4.1.0, debug@npm:^4.1.1, debug@npm:^4.3.1, debug@npm:^4.3.2, debug@npm:^4.3.4, debug@npm:^4.3.7": + version: 4.4.3 + resolution: "debug@npm:4.4.3" + dependencies: + ms: "npm:^2.1.3" + peerDependenciesMeta: + supports-color: + optional: true + checksum: 10c0/d79136ec6c83ecbefd0f6a5593da6a9c91ec4d7ddc4b54c883d6e71ec9accb5f67a1a5e96d00a328196b5b5c86d365e98d8a3a70856aaf16b4e7b1985e67f5a6 + languageName: node + linkType: hard + +"debug@npm:^3.2.7": + version: 3.2.7 + resolution: "debug@npm:3.2.7" + dependencies: + ms: "npm:^2.1.1" + checksum: 10c0/37d96ae42cbc71c14844d2ae3ba55adf462ec89fd3a999459dec3833944cd999af6007ff29c780f1c61153bcaaf2c842d1e4ce1ec621e4fc4923244942e4a02a + languageName: node + linkType: hard + +"deep-eql@npm:^5.0.1": + version: 5.0.2 + resolution: "deep-eql@npm:5.0.2" + checksum: 10c0/7102cf3b7bb719c6b9c0db2e19bf0aa9318d141581befe8c7ce8ccd39af9eaa4346e5e05adef7f9bd7015da0f13a3a25dcfe306ef79dc8668aedbecb658dd247 + languageName: node + linkType: hard + +"deep-is@npm:^0.1.3": + version: 0.1.4 + resolution: "deep-is@npm:0.1.4" + checksum: 10c0/7f0ee496e0dff14a573dc6127f14c95061b448b87b995fc96c017ce0a1e66af1675e73f1d6064407975bc4ea6ab679497a29fff7b5b9c4e99cb10797c1ad0b4c + languageName: node + linkType: hard + +"default-gateway@npm:^6.0.3": + version: 6.0.3 + resolution: "default-gateway@npm:6.0.3" + dependencies: + execa: "npm:^5.0.0" + checksum: 10c0/5184f9e6e105d24fb44ade9e8741efa54bb75e84625c1ea78c4ef8b81dff09ca52d6dbdd1185cf0dc655bb6b282a64fffaf7ed2dd561b8d9ad6f322b1f039aba + languageName: node + linkType: hard + +"define-data-property@npm:^1.0.1, define-data-property@npm:^1.1.4": + version: 1.1.4 + resolution: "define-data-property@npm:1.1.4" + dependencies: + es-define-property: "npm:^1.0.0" + es-errors: "npm:^1.3.0" + gopd: "npm:^1.0.1" + checksum: 10c0/dea0606d1483eb9db8d930d4eac62ca0fa16738b0b3e07046cddfacf7d8c868bbe13fa0cb263eb91c7d0d527960dc3f2f2471a69ed7816210307f6744fe62e37 + languageName: node + linkType: hard + +"define-properties@npm:^1.1.3, define-properties@npm:^1.2.1": + version: 1.2.1 + resolution: "define-properties@npm:1.2.1" + dependencies: + define-data-property: "npm:^1.0.1" + has-property-descriptors: "npm:^1.0.0" + object-keys: "npm:^1.1.1" + checksum: 10c0/88a152319ffe1396ccc6ded510a3896e77efac7a1bfbaa174a7b00414a1747377e0bb525d303794a47cf30e805c2ec84e575758512c6e44a993076d29fd4e6c3 + languageName: node + linkType: hard + +"dns-over-http-resolver@npm:^1.2.3": + version: 1.2.3 + resolution: "dns-over-http-resolver@npm:1.2.3" + dependencies: + debug: "npm:^4.3.1" + native-fetch: "npm:^3.0.0" + receptacle: "npm:^1.3.2" + checksum: 10c0/231435742246115aeb4f153721effc4d995ab8f22572240b27d85e1be4123345cbe503e9922bc46b36caaa86307fbcf65ba252302dc7a4794f330aa6d6f920b8 + languageName: node + linkType: hard + +"doctrine@npm:^2.1.0": + version: 2.1.0 + resolution: "doctrine@npm:2.1.0" + dependencies: + esutils: "npm:^2.0.2" + checksum: 10c0/b6416aaff1f380bf56c3b552f31fdf7a69b45689368deca72d28636f41c16bb28ec3ebc40ace97db4c1afc0ceeb8120e8492fe0046841c94c2933b2e30a7d5ac + languageName: node + linkType: hard + +"dom-helpers@npm:^5.0.1": + version: 5.2.1 + resolution: "dom-helpers@npm:5.2.1" + dependencies: + "@babel/runtime": "npm:^7.8.7" + csstype: "npm:^3.0.2" + checksum: 10c0/f735074d66dd759b36b158fa26e9d00c9388ee0e8c9b16af941c38f014a37fc80782de83afefd621681b19ac0501034b4f1c4a3bff5caa1b8667f0212b5e124c + languageName: node + linkType: hard + +"dunder-proto@npm:^1.0.0, dunder-proto@npm:^1.0.1": + version: 1.0.1 + resolution: "dunder-proto@npm:1.0.1" + dependencies: + call-bind-apply-helpers: "npm:^1.0.1" + es-errors: "npm:^1.3.0" + gopd: "npm:^1.2.0" + checksum: 10c0/199f2a0c1c16593ca0a145dbf76a962f8033ce3129f01284d48c45ed4e14fea9bbacd7b3610b6cdc33486cef20385ac054948fefc6272fcce645c09468f93031 + languageName: node + linkType: hard + +"ecdsa-sig-formatter@npm:1.0.11": + version: 1.0.11 + resolution: "ecdsa-sig-formatter@npm:1.0.11" + dependencies: + safe-buffer: "npm:^5.0.1" + checksum: 10c0/ebfbf19d4b8be938f4dd4a83b8788385da353d63307ede301a9252f9f7f88672e76f2191618fd8edfc2f24679236064176fab0b78131b161ee73daa37125408c + languageName: node + linkType: hard + +"electron-to-chromium@npm:^1.5.263": + version: 1.5.267 + resolution: "electron-to-chromium@npm:1.5.267" + checksum: 10c0/0732bdb891b657f2e43266a3db8cf86fff6cecdcc8d693a92beff214e136cb5c2ee7dc5945ed75fa1db16e16bad0c38695527a020d15f39e79084e0b2e447621 + languageName: node + linkType: hard + +"encoding@npm:^0.1.13": + version: 0.1.13 + resolution: "encoding@npm:0.1.13" + dependencies: + iconv-lite: "npm:^0.6.2" + checksum: 10c0/36d938712ff00fe1f4bac88b43bcffb5930c1efa57bbcdca9d67e1d9d6c57cfb1200fb01efe0f3109b2ce99b231f90779532814a81370a1bd3274a0f58585039 + languageName: node + linkType: hard + +"env-paths@npm:^2.2.0": + version: 2.2.1 + resolution: "env-paths@npm:2.2.1" + checksum: 10c0/285325677bf00e30845e330eec32894f5105529db97496ee3f598478e50f008c5352a41a30e5e72ec9de8a542b5a570b85699cd63bd2bc646dbcb9f311d83bc4 + languageName: node + linkType: hard + +"err-code@npm:^2.0.2": + version: 2.0.3 + resolution: "err-code@npm:2.0.3" + checksum: 10c0/b642f7b4dd4a376e954947550a3065a9ece6733ab8e51ad80db727aaae0817c2e99b02a97a3d6cecc648a97848305e728289cf312d09af395403a90c9d4d8a66 + languageName: node + linkType: hard + +"err-code@npm:^3.0.1": + version: 3.0.1 + resolution: "err-code@npm:3.0.1" + checksum: 10c0/78b1c50500adebde6699b8d27b8ce4728c132dcaad75b5d18ba44f6ccb28769d1fff8368ae1164be4559dac8b95d4e26bb15b480ba9999e0cd0f0c64beaf1b24 + languageName: node + linkType: hard + +"error-ex@npm:^1.3.1": + version: 1.3.4 + resolution: "error-ex@npm:1.3.4" + dependencies: + is-arrayish: "npm:^0.2.1" + checksum: 10c0/b9e34ff4778b8f3b31a8377e1c654456f4c41aeaa3d10a1138c3b7635d8b7b2e03eb2475d46d8ae055c1f180a1063e100bffabf64ea7e7388b37735df5328664 + languageName: node + linkType: hard + +"es-abstract@npm:^1.17.5, es-abstract@npm:^1.23.2, es-abstract@npm:^1.23.3, es-abstract@npm:^1.23.5, es-abstract@npm:^1.23.6, es-abstract@npm:^1.23.9, es-abstract@npm:^1.24.0, es-abstract@npm:^1.24.1": + version: 1.24.1 + resolution: "es-abstract@npm:1.24.1" + dependencies: + array-buffer-byte-length: "npm:^1.0.2" + arraybuffer.prototype.slice: "npm:^1.0.4" + available-typed-arrays: "npm:^1.0.7" + call-bind: "npm:^1.0.8" + call-bound: "npm:^1.0.4" + data-view-buffer: "npm:^1.0.2" + data-view-byte-length: "npm:^1.0.2" + data-view-byte-offset: "npm:^1.0.1" + es-define-property: "npm:^1.0.1" + es-errors: "npm:^1.3.0" + es-object-atoms: "npm:^1.1.1" + es-set-tostringtag: "npm:^2.1.0" + es-to-primitive: "npm:^1.3.0" + function.prototype.name: "npm:^1.1.8" + get-intrinsic: "npm:^1.3.0" + get-proto: "npm:^1.0.1" + get-symbol-description: "npm:^1.1.0" + globalthis: "npm:^1.0.4" + gopd: "npm:^1.2.0" + has-property-descriptors: "npm:^1.0.2" + has-proto: "npm:^1.2.0" + has-symbols: "npm:^1.1.0" + hasown: "npm:^2.0.2" + internal-slot: "npm:^1.1.0" + is-array-buffer: "npm:^3.0.5" + is-callable: "npm:^1.2.7" + is-data-view: "npm:^1.0.2" + is-negative-zero: "npm:^2.0.3" + is-regex: "npm:^1.2.1" + is-set: "npm:^2.0.3" + is-shared-array-buffer: "npm:^1.0.4" + is-string: "npm:^1.1.1" + is-typed-array: "npm:^1.1.15" + is-weakref: "npm:^1.1.1" + math-intrinsics: "npm:^1.1.0" + object-inspect: "npm:^1.13.4" + object-keys: "npm:^1.1.1" + object.assign: "npm:^4.1.7" + own-keys: "npm:^1.0.1" + regexp.prototype.flags: "npm:^1.5.4" + safe-array-concat: "npm:^1.1.3" + safe-push-apply: "npm:^1.0.0" + safe-regex-test: "npm:^1.1.0" + set-proto: "npm:^1.0.0" + stop-iteration-iterator: "npm:^1.1.0" + string.prototype.trim: "npm:^1.2.10" + string.prototype.trimend: "npm:^1.0.9" + string.prototype.trimstart: "npm:^1.0.8" + typed-array-buffer: "npm:^1.0.3" + typed-array-byte-length: "npm:^1.0.3" + typed-array-byte-offset: "npm:^1.0.4" + typed-array-length: "npm:^1.0.7" + unbox-primitive: "npm:^1.1.0" + which-typed-array: "npm:^1.1.19" + checksum: 10c0/fca062ef8b5daacf743732167d319a212d45cb655b0bb540821d38d715416ae15b04b84fc86da9e2c89135aa7b337337b6c867f84dcde698d75d55688d5d765c + languageName: node + linkType: hard + +"es-define-property@npm:^1.0.0, es-define-property@npm:^1.0.1": + version: 1.0.1 + resolution: "es-define-property@npm:1.0.1" + checksum: 10c0/3f54eb49c16c18707949ff25a1456728c883e81259f045003499efba399c08bad00deebf65cccde8c0e07908c1a225c9d472b7107e558f2a48e28d530e34527c + languageName: node + linkType: hard + +"es-errors@npm:^1.3.0": + version: 1.3.0 + resolution: "es-errors@npm:1.3.0" + checksum: 10c0/0a61325670072f98d8ae3b914edab3559b6caa980f08054a3b872052640d91da01d38df55df797fcc916389d77fc92b8d5906cf028f4db46d7e3003abecbca85 + languageName: node + linkType: hard + +"es-iterator-helpers@npm:^1.2.1": + version: 1.2.2 + resolution: "es-iterator-helpers@npm:1.2.2" + dependencies: + call-bind: "npm:^1.0.8" + call-bound: "npm:^1.0.4" + define-properties: "npm:^1.2.1" + es-abstract: "npm:^1.24.1" + es-errors: "npm:^1.3.0" + es-set-tostringtag: "npm:^2.1.0" + function-bind: "npm:^1.1.2" + get-intrinsic: "npm:^1.3.0" + globalthis: "npm:^1.0.4" + gopd: "npm:^1.2.0" + has-property-descriptors: "npm:^1.0.2" + has-proto: "npm:^1.2.0" + has-symbols: "npm:^1.1.0" + internal-slot: "npm:^1.1.0" + iterator.prototype: "npm:^1.1.5" + safe-array-concat: "npm:^1.1.3" + checksum: 10c0/1ced8abf845a45e660dd77b5f3a64358421df70e4a0bd1897d5ddfefffed8409a6a2ca21241b9367e639df9eca74abc1c678b3020bffe6bee1f1826393658ddb + languageName: node + linkType: hard + +"es-module-lexer@npm:^1.5.4": + version: 1.7.0 + resolution: "es-module-lexer@npm:1.7.0" + checksum: 10c0/4c935affcbfeba7fb4533e1da10fa8568043df1e3574b869385980de9e2d475ddc36769891936dbb07036edb3c3786a8b78ccf44964cd130dedc1f2c984b6c7b + languageName: node + linkType: hard + +"es-object-atoms@npm:^1.0.0, es-object-atoms@npm:^1.1.1": + version: 1.1.1 + resolution: "es-object-atoms@npm:1.1.1" + dependencies: + es-errors: "npm:^1.3.0" + checksum: 10c0/65364812ca4daf48eb76e2a3b7a89b3f6a2e62a1c420766ce9f692665a29d94fe41fe88b65f24106f449859549711e4b40d9fb8002d862dfd7eb1c512d10be0c + languageName: node + linkType: hard + +"es-set-tostringtag@npm:^2.1.0": + version: 2.1.0 + resolution: "es-set-tostringtag@npm:2.1.0" + dependencies: + es-errors: "npm:^1.3.0" + get-intrinsic: "npm:^1.2.6" + has-tostringtag: "npm:^1.0.2" + hasown: "npm:^2.0.2" + checksum: 10c0/ef2ca9ce49afe3931cb32e35da4dcb6d86ab02592cfc2ce3e49ced199d9d0bb5085fc7e73e06312213765f5efa47cc1df553a6a5154584b21448e9fb8355b1af + languageName: node + linkType: hard + +"es-shim-unscopables@npm:^1.0.2, es-shim-unscopables@npm:^1.1.0": + version: 1.1.0 + resolution: "es-shim-unscopables@npm:1.1.0" + dependencies: + hasown: "npm:^2.0.2" + checksum: 10c0/1b9702c8a1823fc3ef39035a4e958802cf294dd21e917397c561d0b3e195f383b978359816b1732d02b255ccf63e1e4815da0065b95db8d7c992037be3bbbcdb + languageName: node + linkType: hard + +"es-to-primitive@npm:^1.3.0": + version: 1.3.0 + resolution: "es-to-primitive@npm:1.3.0" + dependencies: + is-callable: "npm:^1.2.7" + is-date-object: "npm:^1.0.5" + is-symbol: "npm:^1.0.4" + checksum: 10c0/c7e87467abb0b438639baa8139f701a06537d2b9bc758f23e8622c3b42fd0fdb5bde0f535686119e446dd9d5e4c0f238af4e14960f4771877cf818d023f6730b + languageName: node + linkType: hard + +"esbuild@npm:^0.21.3": + version: 0.21.5 + resolution: "esbuild@npm:0.21.5" + dependencies: + "@esbuild/aix-ppc64": "npm:0.21.5" + "@esbuild/android-arm": "npm:0.21.5" + "@esbuild/android-arm64": "npm:0.21.5" + "@esbuild/android-x64": "npm:0.21.5" + "@esbuild/darwin-arm64": "npm:0.21.5" + "@esbuild/darwin-x64": "npm:0.21.5" + "@esbuild/freebsd-arm64": "npm:0.21.5" + "@esbuild/freebsd-x64": "npm:0.21.5" + "@esbuild/linux-arm": "npm:0.21.5" + "@esbuild/linux-arm64": "npm:0.21.5" + "@esbuild/linux-ia32": "npm:0.21.5" + "@esbuild/linux-loong64": "npm:0.21.5" + "@esbuild/linux-mips64el": "npm:0.21.5" + "@esbuild/linux-ppc64": "npm:0.21.5" + "@esbuild/linux-riscv64": "npm:0.21.5" + "@esbuild/linux-s390x": "npm:0.21.5" + "@esbuild/linux-x64": "npm:0.21.5" + "@esbuild/netbsd-x64": "npm:0.21.5" + "@esbuild/openbsd-x64": "npm:0.21.5" + "@esbuild/sunos-x64": "npm:0.21.5" + "@esbuild/win32-arm64": "npm:0.21.5" + "@esbuild/win32-ia32": "npm:0.21.5" + "@esbuild/win32-x64": "npm:0.21.5" + dependenciesMeta: + "@esbuild/aix-ppc64": + optional: true + "@esbuild/android-arm": + optional: true + "@esbuild/android-arm64": + optional: true + "@esbuild/android-x64": + optional: true + "@esbuild/darwin-arm64": + optional: true + "@esbuild/darwin-x64": + optional: true + "@esbuild/freebsd-arm64": + optional: true + "@esbuild/freebsd-x64": + optional: true + "@esbuild/linux-arm": + optional: true + "@esbuild/linux-arm64": + optional: true + "@esbuild/linux-ia32": + optional: true + "@esbuild/linux-loong64": + optional: true + "@esbuild/linux-mips64el": + optional: true + "@esbuild/linux-ppc64": + optional: true + "@esbuild/linux-riscv64": + optional: true + "@esbuild/linux-s390x": + optional: true + "@esbuild/linux-x64": + optional: true + "@esbuild/netbsd-x64": + optional: true + "@esbuild/openbsd-x64": + optional: true + "@esbuild/sunos-x64": + optional: true + "@esbuild/win32-arm64": + optional: true + "@esbuild/win32-ia32": + optional: true + "@esbuild/win32-x64": + optional: true + bin: + esbuild: bin/esbuild + checksum: 10c0/fa08508adf683c3f399e8a014a6382a6b65542213431e26206c0720e536b31c09b50798747c2a105a4bbba1d9767b8d3615a74c2f7bf1ddf6d836cd11eb672de + languageName: node + linkType: hard + +"escalade@npm:^3.2.0": + version: 3.2.0 + resolution: "escalade@npm:3.2.0" + checksum: 10c0/ced4dd3a78e15897ed3be74e635110bbf3b08877b0a41be50dcb325ee0e0b5f65fc2d50e9845194d7c4633f327e2e1c6cce00a71b617c5673df0374201d67f65 + languageName: node + linkType: hard + +"escape-string-regexp@npm:^4.0.0": + version: 4.0.0 + resolution: "escape-string-regexp@npm:4.0.0" + checksum: 10c0/9497d4dd307d845bd7f75180d8188bb17ea8c151c1edbf6b6717c100e104d629dc2dfb687686181b0f4b7d732c7dfdc4d5e7a8ff72de1b0ca283a75bbb3a9cd9 + languageName: node + linkType: hard + +"eslint-import-resolver-node@npm:^0.3.9": + version: 0.3.9 + resolution: "eslint-import-resolver-node@npm:0.3.9" + dependencies: + debug: "npm:^3.2.7" + is-core-module: "npm:^2.13.0" + resolve: "npm:^1.22.4" + checksum: 10c0/0ea8a24a72328a51fd95aa8f660dcca74c1429806737cf10261ab90cfcaaf62fd1eff664b76a44270868e0a932711a81b250053942595bcd00a93b1c1575dd61 + languageName: node + linkType: hard + +"eslint-module-utils@npm:^2.12.1": + version: 2.12.1 + resolution: "eslint-module-utils@npm:2.12.1" + dependencies: + debug: "npm:^3.2.7" + peerDependenciesMeta: + eslint: + optional: true + checksum: 10c0/6f4efbe7a91ae49bf67b4ab3644cb60bc5bd7db4cb5521de1b65be0847ffd3fb6bce0dd68f0995e1b312d137f768e2a1f842ee26fe73621afa05f850628fdc40 + languageName: node + linkType: hard + +"eslint-plugin-import@npm:^2.32.0": + version: 2.32.0 + resolution: "eslint-plugin-import@npm:2.32.0" + dependencies: + "@rtsao/scc": "npm:^1.1.0" + array-includes: "npm:^3.1.9" + array.prototype.findlastindex: "npm:^1.2.6" + array.prototype.flat: "npm:^1.3.3" + array.prototype.flatmap: "npm:^1.3.3" + debug: "npm:^3.2.7" + doctrine: "npm:^2.1.0" + eslint-import-resolver-node: "npm:^0.3.9" + eslint-module-utils: "npm:^2.12.1" + hasown: "npm:^2.0.2" + is-core-module: "npm:^2.16.1" + is-glob: "npm:^4.0.3" + minimatch: "npm:^3.1.2" + object.fromentries: "npm:^2.0.8" + object.groupby: "npm:^1.0.3" + object.values: "npm:^1.2.1" + semver: "npm:^6.3.1" + string.prototype.trimend: "npm:^1.0.9" + tsconfig-paths: "npm:^3.15.0" + peerDependencies: + eslint: ^2 || ^3 || ^4 || ^5 || ^6 || ^7.2.0 || ^8 || ^9 + checksum: 10c0/bfb1b8fc8800398e62ddfefbf3638d185286edfed26dfe00875cc2846d954491b4f5112457831588b757fa789384e1ae585f812614c4797f0499fa234fd4a48b + languageName: node + linkType: hard + +"eslint-plugin-react-hooks@npm:^7.0.1": + version: 7.0.1 + resolution: "eslint-plugin-react-hooks@npm:7.0.1" + dependencies: + "@babel/core": "npm:^7.24.4" + "@babel/parser": "npm:^7.24.4" + hermes-parser: "npm:^0.25.1" + zod: "npm:^3.25.0 || ^4.0.0" + zod-validation-error: "npm:^3.5.0 || ^4.0.0" + peerDependencies: + eslint: ^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0-0 || ^9.0.0 + checksum: 10c0/1e711d1a9d1fa9cfc51fa1572500656577201199c70c795c6a27adfc1df39e5c598f69aab6aa91117753d23cc1f11388579a2bed14921cf9a4efe60ae8618496 + languageName: node + linkType: hard + +"eslint-plugin-react@npm:^7.35.0": + version: 7.37.5 + resolution: "eslint-plugin-react@npm:7.37.5" + dependencies: + array-includes: "npm:^3.1.8" + array.prototype.findlast: "npm:^1.2.5" + array.prototype.flatmap: "npm:^1.3.3" + array.prototype.tosorted: "npm:^1.1.4" + doctrine: "npm:^2.1.0" + es-iterator-helpers: "npm:^1.2.1" + estraverse: "npm:^5.3.0" + hasown: "npm:^2.0.2" + jsx-ast-utils: "npm:^2.4.1 || ^3.0.0" + minimatch: "npm:^3.1.2" + object.entries: "npm:^1.1.9" + object.fromentries: "npm:^2.0.8" + object.values: "npm:^1.2.1" + prop-types: "npm:^15.8.1" + resolve: "npm:^2.0.0-next.5" + semver: "npm:^6.3.1" + string.prototype.matchall: "npm:^4.0.12" + string.prototype.repeat: "npm:^1.0.0" + peerDependencies: + eslint: ^3 || ^4 || ^5 || ^6 || ^7 || ^8 || ^9.7 + checksum: 10c0/c850bfd556291d4d9234f5ca38db1436924a1013627c8ab1853f77cac73ec19b020e861e6c7b783436a48b6ffcdfba4547598235a37ad4611b6739f65fd8ad57 + languageName: node + linkType: hard + +"eslint-scope@npm:^8.4.0": + version: 8.4.0 + resolution: "eslint-scope@npm:8.4.0" + dependencies: + esrecurse: "npm:^4.3.0" + estraverse: "npm:^5.2.0" + checksum: 10c0/407f6c600204d0f3705bd557f81bd0189e69cd7996f408f8971ab5779c0af733d1af2f1412066b40ee1588b085874fc37a2333986c6521669cdbdd36ca5058e0 + languageName: node + linkType: hard + +"eslint-visitor-keys@npm:^3.4.3": + version: 3.4.3 + resolution: "eslint-visitor-keys@npm:3.4.3" + checksum: 10c0/92708e882c0a5ffd88c23c0b404ac1628cf20104a108c745f240a13c332a11aac54f49a22d5762efbffc18ecbc9a580d1b7ad034bf5f3cc3307e5cbff2ec9820 + languageName: node + linkType: hard + +"eslint-visitor-keys@npm:^4.2.1": + version: 4.2.1 + resolution: "eslint-visitor-keys@npm:4.2.1" + checksum: 10c0/fcd43999199d6740db26c58dbe0c2594623e31ca307e616ac05153c9272f12f1364f5a0b1917a8e962268fdecc6f3622c1c2908b4fcc2e047a106fe6de69dc43 + languageName: node + linkType: hard + +"eslint@npm:^9.9.0": + version: 9.39.2 + resolution: "eslint@npm:9.39.2" + dependencies: + "@eslint-community/eslint-utils": "npm:^4.8.0" + "@eslint-community/regexpp": "npm:^4.12.1" + "@eslint/config-array": "npm:^0.21.1" + "@eslint/config-helpers": "npm:^0.4.2" + "@eslint/core": "npm:^0.17.0" + "@eslint/eslintrc": "npm:^3.3.1" + "@eslint/js": "npm:9.39.2" + "@eslint/plugin-kit": "npm:^0.4.1" + "@humanfs/node": "npm:^0.16.6" + "@humanwhocodes/module-importer": "npm:^1.0.1" + "@humanwhocodes/retry": "npm:^0.4.2" + "@types/estree": "npm:^1.0.6" + ajv: "npm:^6.12.4" + chalk: "npm:^4.0.0" + cross-spawn: "npm:^7.0.6" + debug: "npm:^4.3.2" + escape-string-regexp: "npm:^4.0.0" + eslint-scope: "npm:^8.4.0" + eslint-visitor-keys: "npm:^4.2.1" + espree: "npm:^10.4.0" + esquery: "npm:^1.5.0" + esutils: "npm:^2.0.2" + fast-deep-equal: "npm:^3.1.3" + file-entry-cache: "npm:^8.0.0" + find-up: "npm:^5.0.0" + glob-parent: "npm:^6.0.2" + ignore: "npm:^5.2.0" + imurmurhash: "npm:^0.1.4" + is-glob: "npm:^4.0.0" + json-stable-stringify-without-jsonify: "npm:^1.0.1" + lodash.merge: "npm:^4.6.2" + minimatch: "npm:^3.1.2" + natural-compare: "npm:^1.4.0" + optionator: "npm:^0.9.3" + peerDependencies: + jiti: "*" + peerDependenciesMeta: + jiti: + optional: true + bin: + eslint: bin/eslint.js + checksum: 10c0/bb88ca8fd16bb7e1ac3e13804c54d41c583214460c0faa7b3e7c574e69c5600c7122295500fb4b0c06067831111db740931e98da1340329527658e1cf80073d3 + languageName: node + linkType: hard + +"espree@npm:^10.0.1, espree@npm:^10.4.0": + version: 10.4.0 + resolution: "espree@npm:10.4.0" + dependencies: + acorn: "npm:^8.15.0" + acorn-jsx: "npm:^5.3.2" + eslint-visitor-keys: "npm:^4.2.1" + checksum: 10c0/c63fe06131c26c8157b4083313cb02a9a54720a08e21543300e55288c40e06c3fc284bdecf108d3a1372c5934a0a88644c98714f38b6ae8ed272b40d9ea08d6b + languageName: node + linkType: hard + +"esquery@npm:^1.5.0": + version: 1.6.0 + resolution: "esquery@npm:1.6.0" + dependencies: + estraverse: "npm:^5.1.0" + checksum: 10c0/cb9065ec605f9da7a76ca6dadb0619dfb611e37a81e318732977d90fab50a256b95fee2d925fba7c2f3f0523aa16f91587246693bc09bc34d5a59575fe6e93d2 + languageName: node + linkType: hard + +"esrecurse@npm:^4.3.0": + version: 4.3.0 + resolution: "esrecurse@npm:4.3.0" + dependencies: + estraverse: "npm:^5.2.0" + checksum: 10c0/81a37116d1408ded88ada45b9fb16dbd26fba3aadc369ce50fcaf82a0bac12772ebd7b24cd7b91fc66786bf2c1ac7b5f196bc990a473efff972f5cb338877cf5 + languageName: node + linkType: hard + +"estraverse@npm:^5.1.0, estraverse@npm:^5.2.0, estraverse@npm:^5.3.0": + version: 5.3.0 + resolution: "estraverse@npm:5.3.0" + checksum: 10c0/1ff9447b96263dec95d6d67431c5e0771eb9776427421260a3e2f0fdd5d6bd4f8e37a7338f5ad2880c9f143450c9b1e4fc2069060724570a49cf9cf0312bd107 + languageName: node + linkType: hard + +"estree-walker@npm:^3.0.3": + version: 3.0.3 + resolution: "estree-walker@npm:3.0.3" + dependencies: + "@types/estree": "npm:^1.0.0" + checksum: 10c0/c12e3c2b2642d2bcae7d5aa495c60fa2f299160946535763969a1c83fc74518ffa9c2cd3a8b69ac56aea547df6a8aac25f729a342992ef0bbac5f1c73e78995d + languageName: node + linkType: hard + +"esutils@npm:^2.0.2": + version: 2.0.3 + resolution: "esutils@npm:2.0.3" + checksum: 10c0/9a2fe69a41bfdade834ba7c42de4723c97ec776e40656919c62cbd13607c45e127a003f05f724a1ea55e5029a4cf2de444b13009f2af71271e42d93a637137c7 + languageName: node + linkType: hard + +"execa@npm:^5.0.0": + version: 5.1.1 + resolution: "execa@npm:5.1.1" + dependencies: + cross-spawn: "npm:^7.0.3" + get-stream: "npm:^6.0.0" + human-signals: "npm:^2.1.0" + is-stream: "npm:^2.0.0" + merge-stream: "npm:^2.0.0" + npm-run-path: "npm:^4.0.1" + onetime: "npm:^5.1.2" + signal-exit: "npm:^3.0.3" + strip-final-newline: "npm:^2.0.0" + checksum: 10c0/c8e615235e8de4c5addf2fa4c3da3e3aa59ce975a3e83533b4f6a71750fb816a2e79610dc5f1799b6e28976c9ae86747a36a606655bf8cb414a74d8d507b304f + languageName: node + linkType: hard + +"expect-type@npm:^1.1.0": + version: 1.3.0 + resolution: "expect-type@npm:1.3.0" + checksum: 10c0/8412b3fe4f392c420ab41dae220b09700e4e47c639a29ba7ba2e83cc6cffd2b4926f7ac9e47d7e277e8f4f02acda76fd6931cb81fd2b382fa9477ef9ada953fd + languageName: node + linkType: hard + +"exponential-backoff@npm:^3.1.1": + version: 3.1.3 + resolution: "exponential-backoff@npm:3.1.3" + checksum: 10c0/77e3ae682b7b1f4972f563c6dbcd2b0d54ac679e62d5d32f3e5085feba20483cf28bd505543f520e287a56d4d55a28d7874299941faf637e779a1aa5994d1267 + languageName: node + linkType: hard + +"fast-deep-equal@npm:^3.1.1, fast-deep-equal@npm:^3.1.3": + version: 3.1.3 + resolution: "fast-deep-equal@npm:3.1.3" + checksum: 10c0/40dedc862eb8992c54579c66d914635afbec43350afbbe991235fdcb4e3a8d5af1b23ae7e79bef7d4882d0ecee06c3197488026998fb19f72dc95acff1d1b1d0 + languageName: node + linkType: hard + +"fast-json-stable-stringify@npm:^2.0.0": + version: 2.1.0 + resolution: "fast-json-stable-stringify@npm:2.1.0" + checksum: 10c0/7f081eb0b8a64e0057b3bb03f974b3ef00135fbf36c1c710895cd9300f13c94ba809bb3a81cf4e1b03f6e5285610a61abbd7602d0652de423144dfee5a389c9b + languageName: node + linkType: hard + +"fast-levenshtein@npm:^2.0.6": + version: 2.0.6 + resolution: "fast-levenshtein@npm:2.0.6" + checksum: 10c0/111972b37338bcb88f7d9e2c5907862c280ebf4234433b95bc611e518d192ccb2d38119c4ac86e26b668d75f7f3894f4ff5c4982899afced7ca78633b08287c4 + languageName: node + linkType: hard + +"fdir@npm:^6.5.0": + version: 6.5.0 + resolution: "fdir@npm:6.5.0" + peerDependencies: + picomatch: ^3 || ^4 + peerDependenciesMeta: + picomatch: + optional: true + checksum: 10c0/e345083c4306b3aed6cb8ec551e26c36bab5c511e99ea4576a16750ddc8d3240e63826cc624f5ae17ad4dc82e68a253213b60d556c11bfad064b7607847ed07f + languageName: node + linkType: hard + +"file-entry-cache@npm:^8.0.0": + version: 8.0.0 + resolution: "file-entry-cache@npm:8.0.0" + dependencies: + flat-cache: "npm:^4.0.0" + checksum: 10c0/9e2b5938b1cd9b6d7e3612bdc533afd4ac17b2fc646569e9a8abbf2eb48e5eb8e316bc38815a3ef6a1b456f4107f0d0f055a614ca613e75db6bf9ff4d72c1638 + languageName: node + linkType: hard + +"find-root@npm:^1.1.0": + version: 1.1.0 + resolution: "find-root@npm:1.1.0" + checksum: 10c0/1abc7f3bf2f8d78ff26d9e00ce9d0f7b32e5ff6d1da2857bcdf4746134c422282b091c672cde0572cac3840713487e0a7a636af9aa1b74cb11894b447a521efa + languageName: node + linkType: hard + +"find-up@npm:^5.0.0": + version: 5.0.0 + resolution: "find-up@npm:5.0.0" + dependencies: + locate-path: "npm:^6.0.0" + path-exists: "npm:^4.0.0" + checksum: 10c0/062c5a83a9c02f53cdd6d175a37ecf8f87ea5bbff1fdfb828f04bfa021441bc7583e8ebc0872a4c1baab96221fb8a8a275a19809fb93fbc40bd69ec35634069a + languageName: node + linkType: hard + +"flat-cache@npm:^4.0.0": + version: 4.0.1 + resolution: "flat-cache@npm:4.0.1" + dependencies: + flatted: "npm:^3.2.9" + keyv: "npm:^4.5.4" + checksum: 10c0/2c59d93e9faa2523e4fda6b4ada749bed432cfa28c8e251f33b25795e426a1c6dbada777afb1f74fcfff33934fdbdea921ee738fcc33e71adc9d6eca984a1cfc + languageName: node + linkType: hard + +"flatted@npm:^3.2.9": + version: 3.3.3 + resolution: "flatted@npm:3.3.3" + checksum: 10c0/e957a1c6b0254aa15b8cce8533e24165abd98fadc98575db082b786b5da1b7d72062b81bfdcd1da2f4d46b6ed93bec2434e62333e9b4261d79ef2e75a10dd538 + languageName: node + linkType: hard + +"for-each@npm:^0.3.3, for-each@npm:^0.3.5": + version: 0.3.5 + resolution: "for-each@npm:0.3.5" + dependencies: + is-callable: "npm:^1.2.7" + checksum: 10c0/0e0b50f6a843a282637d43674d1fb278dda1dd85f4f99b640024cfb10b85058aac0cc781bf689d5fe50b4b7f638e91e548560723a4e76e04fe96ae35ef039cee + languageName: node + linkType: hard + +"fs-minipass@npm:^3.0.0": + version: 3.0.3 + resolution: "fs-minipass@npm:3.0.3" + dependencies: + minipass: "npm:^7.0.3" + checksum: 10c0/63e80da2ff9b621e2cb1596abcb9207f1cf82b968b116ccd7b959e3323144cce7fb141462200971c38bbf2ecca51695069db45265705bed09a7cd93ae5b89f94 + languageName: node + linkType: hard + +"fsevents@npm:~2.3.2, fsevents@npm:~2.3.3": + version: 2.3.3 + resolution: "fsevents@npm:2.3.3" + dependencies: + node-gyp: "npm:latest" + checksum: 10c0/a1f0c44595123ed717febbc478aa952e47adfc28e2092be66b8ab1635147254ca6cfe1df792a8997f22716d4cbafc73309899ff7bfac2ac3ad8cf2e4ecc3ec60 + conditions: os=darwin + languageName: node + linkType: hard + +"fsevents@patch:fsevents@npm%3A~2.3.2#optional!builtin, fsevents@patch:fsevents@npm%3A~2.3.3#optional!builtin": + version: 2.3.3 + resolution: "fsevents@patch:fsevents@npm%3A2.3.3#optional!builtin::version=2.3.3&hash=df0bf1" + dependencies: + node-gyp: "npm:latest" + conditions: os=darwin + languageName: node + linkType: hard + +"function-bind@npm:^1.1.2": + version: 1.1.2 + resolution: "function-bind@npm:1.1.2" + checksum: 10c0/d8680ee1e5fcd4c197e4ac33b2b4dce03c71f4d91717292785703db200f5c21f977c568d28061226f9b5900cbcd2c84463646134fd5337e7925e0942bc3f46d5 + languageName: node + linkType: hard + +"function.prototype.name@npm:^1.1.6, function.prototype.name@npm:^1.1.8": + version: 1.1.8 + resolution: "function.prototype.name@npm:1.1.8" + dependencies: + call-bind: "npm:^1.0.8" + call-bound: "npm:^1.0.3" + define-properties: "npm:^1.2.1" + functions-have-names: "npm:^1.2.3" + hasown: "npm:^2.0.2" + is-callable: "npm:^1.2.7" + checksum: 10c0/e920a2ab52663005f3cbe7ee3373e3c71c1fb5558b0b0548648cdf3e51961085032458e26c71ff1a8c8c20e7ee7caeb03d43a5d1fa8610c459333323a2e71253 + languageName: node + linkType: hard + +"functions-have-names@npm:^1.2.3": + version: 1.2.3 + resolution: "functions-have-names@npm:1.2.3" + checksum: 10c0/33e77fd29bddc2d9bb78ab3eb854c165909201f88c75faa8272e35899e2d35a8a642a15e7420ef945e1f64a9670d6aa3ec744106b2aa42be68ca5114025954ca + languageName: node + linkType: hard + +"generator-function@npm:^2.0.0": + version: 2.0.1 + resolution: "generator-function@npm:2.0.1" + checksum: 10c0/8a9f59df0f01cfefafdb3b451b80555e5cf6d76487095db91ac461a0e682e4ff7a9dbce15f4ecec191e53586d59eece01949e05a4b4492879600bbbe8e28d6b8 + languageName: node + linkType: hard + +"gensync@npm:^1.0.0-beta.2": + version: 1.0.0-beta.2 + resolution: "gensync@npm:1.0.0-beta.2" + checksum: 10c0/782aba6cba65b1bb5af3b095d96249d20edbe8df32dbf4696fd49be2583faf676173bf4809386588828e4dd76a3354fcbeb577bab1c833ccd9fc4577f26103f8 + languageName: node + linkType: hard + +"get-intrinsic@npm:^1.2.4, get-intrinsic@npm:^1.2.5, get-intrinsic@npm:^1.2.6, get-intrinsic@npm:^1.2.7, get-intrinsic@npm:^1.3.0": + version: 1.3.1 + resolution: "get-intrinsic@npm:1.3.1" + dependencies: + async-function: "npm:^1.0.0" + async-generator-function: "npm:^1.0.0" + call-bind-apply-helpers: "npm:^1.0.2" + es-define-property: "npm:^1.0.1" + es-errors: "npm:^1.3.0" + es-object-atoms: "npm:^1.1.1" + function-bind: "npm:^1.1.2" + generator-function: "npm:^2.0.0" + get-proto: "npm:^1.0.1" + gopd: "npm:^1.2.0" + has-symbols: "npm:^1.1.0" + hasown: "npm:^2.0.2" + math-intrinsics: "npm:^1.1.0" + checksum: 10c0/9f4ab0cf7efe0fd2c8185f52e6f637e708f3a112610c88869f8f041bb9ecc2ce44bf285dfdbdc6f4f7c277a5b88d8e94a432374d97cca22f3de7fc63795deb5d + languageName: node + linkType: hard + +"get-params@npm:^0.1.2": + version: 0.1.2 + resolution: "get-params@npm:0.1.2" + checksum: 10c0/bf83ed37d2c5e9d9cf04b0d9d380fa3f29997e4c4c5cbbcda0112747a43148111ab5a054e1681819aeb24b98726053196b183830ad9a4b246bdc5f79af5792f7 + languageName: node + linkType: hard + +"get-proto@npm:^1.0.0, get-proto@npm:^1.0.1": + version: 1.0.1 + resolution: "get-proto@npm:1.0.1" + dependencies: + dunder-proto: "npm:^1.0.1" + es-object-atoms: "npm:^1.0.0" + checksum: 10c0/9224acb44603c5526955e83510b9da41baf6ae73f7398875fba50edc5e944223a89c4a72b070fcd78beb5f7bdda58ecb6294adc28f7acfc0da05f76a2399643c + languageName: node + linkType: hard + +"get-stream@npm:^6.0.0": + version: 6.0.1 + resolution: "get-stream@npm:6.0.1" + checksum: 10c0/49825d57d3fd6964228e6200a58169464b8e8970489b3acdc24906c782fb7f01f9f56f8e6653c4a50713771d6658f7cfe051e5eb8c12e334138c9c918b296341 + languageName: node + linkType: hard + +"get-symbol-description@npm:^1.1.0": + version: 1.1.0 + resolution: "get-symbol-description@npm:1.1.0" + dependencies: + call-bound: "npm:^1.0.3" + es-errors: "npm:^1.3.0" + get-intrinsic: "npm:^1.2.6" + checksum: 10c0/d6a7d6afca375779a4b307738c9e80dbf7afc0bdbe5948768d54ab9653c865523d8920e670991a925936eb524b7cb6a6361d199a760b21d0ca7620194455aa4b + languageName: node + linkType: hard + +"glob-parent@npm:^6.0.2": + version: 6.0.2 + resolution: "glob-parent@npm:6.0.2" + dependencies: + is-glob: "npm:^4.0.3" + checksum: 10c0/317034d88654730230b3f43bb7ad4f7c90257a426e872ea0bf157473ac61c99bf5d205fad8f0185f989be8d2fa6d3c7dce1645d99d545b6ea9089c39f838e7f8 + languageName: node + linkType: hard + +"glob@npm:^13.0.0": + version: 13.0.0 + resolution: "glob@npm:13.0.0" + dependencies: + minimatch: "npm:^10.1.1" + minipass: "npm:^7.1.2" + path-scurry: "npm:^2.0.0" + checksum: 10c0/8e2f5821f3f7c312dd102e23a15b80c79e0837a9872784293ba2e15ec73b3f3749a49a42a31bfcb4e52c84820a474e92331c2eebf18819d20308f5c33876630a + languageName: node + linkType: hard + +"globals@npm:^14.0.0": + version: 14.0.0 + resolution: "globals@npm:14.0.0" + checksum: 10c0/b96ff42620c9231ad468d4c58ff42afee7777ee1c963013ff8aabe095a451d0ceeb8dcd8ef4cbd64d2538cef45f787a78ba3a9574f4a634438963e334471302d + languageName: node + linkType: hard + +"globals@npm:^15.9.0": + version: 15.15.0 + resolution: "globals@npm:15.15.0" + checksum: 10c0/f9ae80996392ca71316495a39bec88ac43ae3525a438b5626cd9d5ce9d5500d0a98a266409605f8cd7241c7acf57c354a48111ea02a767ba4f374b806d6861fe + languageName: node + linkType: hard + +"globalthis@npm:^1.0.4": + version: 1.0.4 + resolution: "globalthis@npm:1.0.4" + dependencies: + define-properties: "npm:^1.2.1" + gopd: "npm:^1.0.1" + checksum: 10c0/9d156f313af79d80b1566b93e19285f481c591ad6d0d319b4be5e03750d004dde40a39a0f26f7e635f9007a3600802f53ecd85a759b86f109e80a5f705e01846 + languageName: node + linkType: hard + +"globrex@npm:^0.1.2": + version: 0.1.2 + resolution: "globrex@npm:0.1.2" + checksum: 10c0/a54c029520cf58bda1d8884f72bd49b4cd74e977883268d931fd83bcbd1a9eb96d57c7dbd4ad80148fb9247467ebfb9b215630b2ed7563b2a8de02e1ff7f89d1 + languageName: node + linkType: hard + +"goober@npm:^2.0.33": + version: 2.1.18 + resolution: "goober@npm:2.1.18" + peerDependencies: + csstype: ^3.0.10 + checksum: 10c0/de9bf7b6f57417900afac81a479b85d8c0bcb0322ba8b174f9287d10e7891ba7e33db5fe2b0cdd75281c69130e76eb0c694345acf45ea57e4e4a2db8e4c4f021 + languageName: node + linkType: hard + +"gopd@npm:^1.0.1, gopd@npm:^1.2.0": + version: 1.2.0 + resolution: "gopd@npm:1.2.0" + checksum: 10c0/50fff1e04ba2b7737c097358534eacadad1e68d24cccee3272e04e007bed008e68d2614f3987788428fd192a5ae3889d08fb2331417e4fc4a9ab366b2043cead + languageName: node + linkType: hard + +"graceful-fs@npm:^4.2.6": + version: 4.2.11 + resolution: "graceful-fs@npm:4.2.11" + checksum: 10c0/386d011a553e02bc594ac2ca0bd6d9e4c22d7fa8cfbfc448a6d148c59ea881b092db9dbe3547ae4b88e55f1b01f7c4a2ecc53b310c042793e63aa44cf6c257f2 + languageName: node + linkType: hard + +"has-bigints@npm:^1.0.2": + version: 1.1.0 + resolution: "has-bigints@npm:1.1.0" + checksum: 10c0/2de0cdc4a1ccf7a1e75ffede1876994525ac03cc6f5ae7392d3415dd475cd9eee5bceec63669ab61aa997ff6cceebb50ef75561c7002bed8988de2b9d1b40788 + languageName: node + linkType: hard + +"has-flag@npm:^4.0.0": + version: 4.0.0 + resolution: "has-flag@npm:4.0.0" + checksum: 10c0/2e789c61b7888d66993e14e8331449e525ef42aac53c627cc53d1c3334e768bcb6abdc4f5f0de1478a25beec6f0bd62c7549058b7ac53e924040d4f301f02fd1 + languageName: node + linkType: hard + +"has-property-descriptors@npm:^1.0.0, has-property-descriptors@npm:^1.0.2": + version: 1.0.2 + resolution: "has-property-descriptors@npm:1.0.2" + dependencies: + es-define-property: "npm:^1.0.0" + checksum: 10c0/253c1f59e80bb476cf0dde8ff5284505d90c3bdb762983c3514d36414290475fe3fd6f574929d84de2a8eec00d35cf07cb6776205ff32efd7c50719125f00236 + languageName: node + linkType: hard + +"has-proto@npm:^1.2.0": + version: 1.2.0 + resolution: "has-proto@npm:1.2.0" + dependencies: + dunder-proto: "npm:^1.0.0" + checksum: 10c0/46538dddab297ec2f43923c3d35237df45d8c55a6fc1067031e04c13ed8a9a8f94954460632fd4da84c31a1721eefee16d901cbb1ae9602bab93bb6e08f93b95 + languageName: node + linkType: hard + +"has-symbols@npm:^1.0.3, has-symbols@npm:^1.1.0": + version: 1.1.0 + resolution: "has-symbols@npm:1.1.0" + checksum: 10c0/dde0a734b17ae51e84b10986e651c664379018d10b91b6b0e9b293eddb32f0f069688c841fb40f19e9611546130153e0a2a48fd7f512891fb000ddfa36f5a20e + languageName: node + linkType: hard + +"has-tostringtag@npm:^1.0.2": + version: 1.0.2 + resolution: "has-tostringtag@npm:1.0.2" + dependencies: + has-symbols: "npm:^1.0.3" + checksum: 10c0/a8b166462192bafe3d9b6e420a1d581d93dd867adb61be223a17a8d6dad147aa77a8be32c961bb2f27b3ef893cae8d36f564ab651f5e9b7938ae86f74027c48c + languageName: node + linkType: hard + +"hasown@npm:^2.0.2": + version: 2.0.2 + resolution: "hasown@npm:2.0.2" + dependencies: + function-bind: "npm:^1.1.2" + checksum: 10c0/3769d434703b8ac66b209a4cca0737519925bbdb61dd887f93a16372b14694c63ff4e797686d87c90f08168e81082248b9b028bad60d4da9e0d1148766f56eb9 + languageName: node + linkType: hard + +"hermes-estree@npm:0.25.1": + version: 0.25.1 + resolution: "hermes-estree@npm:0.25.1" + checksum: 10c0/48be3b2fa37a0cbc77a112a89096fa212f25d06de92781b163d67853d210a8a5c3784fac23d7d48335058f7ed283115c87b4332c2a2abaaccc76d0ead1a282ac + languageName: node + linkType: hard + +"hermes-parser@npm:^0.25.1": + version: 0.25.1 + resolution: "hermes-parser@npm:0.25.1" + dependencies: + hermes-estree: "npm:0.25.1" + checksum: 10c0/3abaa4c6f1bcc25273f267297a89a4904963ea29af19b8e4f6eabe04f1c2c7e9abd7bfc4730ddb1d58f2ea04b6fee74053d8bddb5656ec6ebf6c79cc8d14202c + languageName: node + linkType: hard + +"hoist-non-react-statics@npm:^3.3.0, hoist-non-react-statics@npm:^3.3.1": + version: 3.3.2 + resolution: "hoist-non-react-statics@npm:3.3.2" + dependencies: + react-is: "npm:^16.7.0" + checksum: 10c0/fe0889169e845d738b59b64badf5e55fa3cf20454f9203d1eb088df322d49d4318df774828e789898dcb280e8a5521bb59b3203385662ca5e9218a6ca5820e74 + languageName: node + linkType: hard + +"http-cache-semantics@npm:^4.1.1": + version: 4.2.0 + resolution: "http-cache-semantics@npm:4.2.0" + checksum: 10c0/45b66a945cf13ec2d1f29432277201313babf4a01d9e52f44b31ca923434083afeca03f18417f599c9ab3d0e7b618ceb21257542338b57c54b710463b4a53e37 + languageName: node + linkType: hard + +"http-proxy-agent@npm:^7.0.0": + version: 7.0.2 + resolution: "http-proxy-agent@npm:7.0.2" + dependencies: + agent-base: "npm:^7.1.0" + debug: "npm:^4.3.4" + checksum: 10c0/4207b06a4580fb85dd6dff521f0abf6db517489e70863dca1a0291daa7f2d3d2d6015a57bd702af068ea5cf9f1f6ff72314f5f5b4228d299c0904135d2aef921 + languageName: node + linkType: hard + +"https-proxy-agent@npm:^7.0.1": + version: 7.0.6 + resolution: "https-proxy-agent@npm:7.0.6" + dependencies: + agent-base: "npm:^7.1.2" + debug: "npm:4" + checksum: 10c0/f729219bc735edb621fa30e6e84e60ee5d00802b8247aac0d7b79b0bd6d4b3294737a337b93b86a0bd9e68099d031858a39260c976dc14cdbba238ba1f8779ac + languageName: node + linkType: hard + +"human-signals@npm:^2.1.0": + version: 2.1.0 + resolution: "human-signals@npm:2.1.0" + checksum: 10c0/695edb3edfcfe9c8b52a76926cd31b36978782062c0ed9b1192b36bebc75c4c87c82e178dfcb0ed0fc27ca59d434198aac0bd0be18f5781ded775604db22304a + languageName: node + linkType: hard + +"humanize-duration@npm:^3.32.1": + version: 3.33.2 + resolution: "humanize-duration@npm:3.33.2" + checksum: 10c0/eae493c113f1c11c96e42195002e8016ab7c08bfcc3414ba5459027da4faaa2df012429bc4eae200873a65d0a86b7cc6a9656783aa45705ffe6106b5256af973 + languageName: node + linkType: hard + +"iconv-lite@npm:^0.6.2": + version: 0.6.3 + resolution: "iconv-lite@npm:0.6.3" + dependencies: + safer-buffer: "npm:>= 2.1.2 < 3.0.0" + checksum: 10c0/98102bc66b33fcf5ac044099d1257ba0b7ad5e3ccd3221f34dd508ab4070edff183276221684e1e0555b145fce0850c9f7d2b60a9fcac50fbb4ea0d6e845a3b1 + languageName: node + linkType: hard + +"ieee754@npm:^1.1.13": + version: 1.2.1 + resolution: "ieee754@npm:1.2.1" + checksum: 10c0/b0782ef5e0935b9f12883a2e2aa37baa75da6e66ce6515c168697b42160807d9330de9a32ec1ed73149aea02e0d822e572bca6f1e22bdcbd2149e13b050b17bb + languageName: node + linkType: hard + +"ignore@npm:^5.2.0": + version: 5.3.2 + resolution: "ignore@npm:5.3.2" + checksum: 10c0/f9f652c957983634ded1e7f02da3b559a0d4cc210fca3792cb67f1b153623c9c42efdc1c4121af171e295444459fc4a9201101fb041b1104a3c000bccb188337 + languageName: node + linkType: hard + +"ignore@npm:^7.0.0": + version: 7.0.5 + resolution: "ignore@npm:7.0.5" + checksum: 10c0/ae00db89fe873064a093b8999fe4cc284b13ef2a178636211842cceb650b9c3e390d3339191acb145d81ed5379d2074840cf0c33a20bdbd6f32821f79eb4ad5d + languageName: node + linkType: hard + +"immer@npm:^11.0.0": + version: 11.1.0 + resolution: "immer@npm:11.1.0" + checksum: 10c0/cc1f14037534802a662f0113d80b4695685d789b84ab4a56a241a384df32f57f12332d1643fa3b3cc7a2504b076efb07aead08732796c8e019569d95b8b7f1cd + languageName: node + linkType: hard + +"immutable@npm:^4.3.7": + version: 4.3.7 + resolution: "immutable@npm:4.3.7" + checksum: 10c0/9b099197081b22f6433003e34929da8ecddbbdc1474cdc8aa3b7669dee4adda349c06143de22def36016d1b6de5322b043eccd7a11db1dad2ca85dad4fff5435 + languageName: node + linkType: hard + +"import-fresh@npm:^3.2.1": + version: 3.3.1 + resolution: "import-fresh@npm:3.3.1" + dependencies: + parent-module: "npm:^1.0.0" + resolve-from: "npm:^4.0.0" + checksum: 10c0/bf8cc494872fef783249709385ae883b447e3eb09db0ebd15dcead7d9afe7224dad7bd7591c6b73b0b19b3c0f9640eb8ee884f01cfaf2887ab995b0b36a0cbec + languageName: node + linkType: hard + +"imurmurhash@npm:^0.1.4": + version: 0.1.4 + resolution: "imurmurhash@npm:0.1.4" + checksum: 10c0/8b51313850dd33605c6c9d3fd9638b714f4c4c40250cff658209f30d40da60f78992fb2df5dabee4acf589a6a82bbc79ad5486550754bd9ec4e3fc0d4a57d6a6 + languageName: node + linkType: hard + +"inherits@npm:~2.0.3": + version: 2.0.4 + resolution: "inherits@npm:2.0.4" + checksum: 10c0/4e531f648b29039fb7426fb94075e6545faa1eb9fe83c29f0b6d9e7263aceb4289d2d4557db0d428188eeb449cc7c5e77b0a0b2c4e248ff2a65933a0dee49ef2 + languageName: node + linkType: hard + +"internal-ip@npm:^7.0.0": + version: 7.0.0 + resolution: "internal-ip@npm:7.0.0" + dependencies: + default-gateway: "npm:^6.0.3" + ipaddr.js: "npm:^2.0.1" + is-ip: "npm:^3.1.0" + p-event: "npm:^4.2.0" + checksum: 10c0/22eb24ad1e1c5eb9c3742cfb4ce7c85b3328632aee8f595e5587e699c386b46b44c6655141135187ed09c60a19e7357188f2cf306ee569711f23f8a8cf56f359 + languageName: node + linkType: hard + +"internal-slot@npm:^1.1.0": + version: 1.1.0 + resolution: "internal-slot@npm:1.1.0" + dependencies: + es-errors: "npm:^1.3.0" + hasown: "npm:^2.0.2" + side-channel: "npm:^1.1.0" + checksum: 10c0/03966f5e259b009a9bf1a78d60da920df198af4318ec004f57b8aef1dd3fe377fbc8cce63a96e8c810010302654de89f9e19de1cd8ad0061d15be28a695465c7 + languageName: node + linkType: hard + +"ip-address@npm:^10.0.1": + version: 10.1.0 + resolution: "ip-address@npm:10.1.0" + checksum: 10c0/0103516cfa93f6433b3bd7333fa876eb21263912329bfa47010af5e16934eeeff86f3d2ae700a3744a137839ddfad62b900c7a445607884a49b5d1e32a3d7566 + languageName: node + linkType: hard + +"ip-regex@npm:^4.0.0": + version: 4.3.0 + resolution: "ip-regex@npm:4.3.0" + checksum: 10c0/f9ef1f5d0df05b9133a882974e572ae525ccd205260cb103dae337f1fc7451ed783391acc6ad688e56dd2598f769e8e72ecbb650ec34763396af822a91768562 + languageName: node + linkType: hard + +"ipaddr.js@npm:^2.0.1": + version: 2.3.0 + resolution: "ipaddr.js@npm:2.3.0" + checksum: 10c0/084bab99e2f6875d7a62adc3325e1c64b038a12c9521e35fb967b5e263a8b3afb1b8884dd77c276092331f5d63298b767491e10997ef147c62da01b143780bbd + languageName: node + linkType: hard + +"is-array-buffer@npm:^3.0.4, is-array-buffer@npm:^3.0.5": + version: 3.0.5 + resolution: "is-array-buffer@npm:3.0.5" + dependencies: + call-bind: "npm:^1.0.8" + call-bound: "npm:^1.0.3" + get-intrinsic: "npm:^1.2.6" + checksum: 10c0/c5c9f25606e86dbb12e756694afbbff64bc8b348d1bc989324c037e1068695131930199d6ad381952715dad3a9569333817f0b1a72ce5af7f883ce802e49c83d + languageName: node + linkType: hard + +"is-arrayish@npm:^0.2.1": + version: 0.2.1 + resolution: "is-arrayish@npm:0.2.1" + checksum: 10c0/e7fb686a739068bb70f860b39b67afc62acc62e36bb61c5f965768abce1873b379c563e61dd2adad96ebb7edf6651111b385e490cf508378959b0ed4cac4e729 + languageName: node + linkType: hard + +"is-async-function@npm:^2.0.0": + version: 2.1.1 + resolution: "is-async-function@npm:2.1.1" + dependencies: + async-function: "npm:^1.0.0" + call-bound: "npm:^1.0.3" + get-proto: "npm:^1.0.1" + has-tostringtag: "npm:^1.0.2" + safe-regex-test: "npm:^1.1.0" + checksum: 10c0/d70c236a5e82de6fc4d44368ffd0c2fee2b088b893511ce21e679da275a5ecc6015ff59a7d7e1bdd7ca39f71a8dbdd253cf8cce5c6b3c91cdd5b42b5ce677298 + languageName: node + linkType: hard + +"is-bigint@npm:^1.1.0": + version: 1.1.0 + resolution: "is-bigint@npm:1.1.0" + dependencies: + has-bigints: "npm:^1.0.2" + checksum: 10c0/f4f4b905ceb195be90a6ea7f34323bf1c18e3793f18922e3e9a73c684c29eeeeff5175605c3a3a74cc38185fe27758f07efba3dbae812e5c5afbc0d2316b40e4 + languageName: node + linkType: hard + +"is-boolean-object@npm:^1.2.1": + version: 1.2.2 + resolution: "is-boolean-object@npm:1.2.2" + dependencies: + call-bound: "npm:^1.0.3" + has-tostringtag: "npm:^1.0.2" + checksum: 10c0/36ff6baf6bd18b3130186990026f5a95c709345c39cd368468e6c1b6ab52201e9fd26d8e1f4c066357b4938b0f0401e1a5000e08257787c1a02f3a719457001e + languageName: node + linkType: hard + +"is-callable@npm:^1.2.7": + version: 1.2.7 + resolution: "is-callable@npm:1.2.7" + checksum: 10c0/ceebaeb9d92e8adee604076971dd6000d38d6afc40bb843ea8e45c5579b57671c3f3b50d7f04869618242c6cee08d1b67806a8cb8edaaaf7c0748b3720d6066f + languageName: node + linkType: hard + +"is-core-module@npm:^2.13.0, is-core-module@npm:^2.16.1": + version: 2.16.1 + resolution: "is-core-module@npm:2.16.1" + dependencies: + hasown: "npm:^2.0.2" + checksum: 10c0/898443c14780a577e807618aaae2b6f745c8538eca5c7bc11388a3f2dc6de82b9902bcc7eb74f07be672b11bbe82dd6a6edded44a00cb3d8f933d0459905eedd + languageName: node + linkType: hard + +"is-data-view@npm:^1.0.1, is-data-view@npm:^1.0.2": + version: 1.0.2 + resolution: "is-data-view@npm:1.0.2" + dependencies: + call-bound: "npm:^1.0.2" + get-intrinsic: "npm:^1.2.6" + is-typed-array: "npm:^1.1.13" + checksum: 10c0/ef3548a99d7e7f1370ce21006baca6d40c73e9f15c941f89f0049c79714c873d03b02dae1c64b3f861f55163ecc16da06506c5b8a1d4f16650b3d9351c380153 + languageName: node + linkType: hard + +"is-date-object@npm:^1.0.5, is-date-object@npm:^1.1.0": + version: 1.1.0 + resolution: "is-date-object@npm:1.1.0" + dependencies: + call-bound: "npm:^1.0.2" + has-tostringtag: "npm:^1.0.2" + checksum: 10c0/1a4d199c8e9e9cac5128d32e6626fa7805175af9df015620ac0d5d45854ccf348ba494679d872d37301032e35a54fc7978fba1687e8721b2139aea7870cafa2f + languageName: node + linkType: hard + +"is-extglob@npm:^2.1.1": + version: 2.1.1 + resolution: "is-extglob@npm:2.1.1" + checksum: 10c0/5487da35691fbc339700bbb2730430b07777a3c21b9ebaecb3072512dfd7b4ba78ac2381a87e8d78d20ea08affb3f1971b4af629173a6bf435ff8a4c47747912 + languageName: node + linkType: hard + +"is-finalizationregistry@npm:^1.1.0": + version: 1.1.1 + resolution: "is-finalizationregistry@npm:1.1.1" + dependencies: + call-bound: "npm:^1.0.3" + checksum: 10c0/818dff679b64f19e228a8205a1e2d09989a98e98def3a817f889208cfcbf918d321b251aadf2c05918194803ebd2eb01b14fc9d0b2bea53d984f4137bfca5e97 + languageName: node + linkType: hard + +"is-generator-function@npm:^1.0.10": + version: 1.1.2 + resolution: "is-generator-function@npm:1.1.2" + dependencies: + call-bound: "npm:^1.0.4" + generator-function: "npm:^2.0.0" + get-proto: "npm:^1.0.1" + has-tostringtag: "npm:^1.0.2" + safe-regex-test: "npm:^1.1.0" + checksum: 10c0/83da102e89c3e3b71d67b51d47c9f9bc862bceb58f87201727e27f7fa19d1d90b0ab223644ecaee6fc6e3d2d622bb25c966fbdaf87c59158b01ce7c0fe2fa372 + languageName: node + linkType: hard + +"is-glob@npm:^4.0.0, is-glob@npm:^4.0.3": + version: 4.0.3 + resolution: "is-glob@npm:4.0.3" + dependencies: + is-extglob: "npm:^2.1.1" + checksum: 10c0/17fb4014e22be3bbecea9b2e3a76e9e34ff645466be702f1693e8f1ee1adac84710d0be0bd9f967d6354036fd51ab7c2741d954d6e91dae6bb69714de92c197a + languageName: node + linkType: hard + +"is-ip@npm:^3.1.0": + version: 3.1.0 + resolution: "is-ip@npm:3.1.0" + dependencies: + ip-regex: "npm:^4.0.0" + checksum: 10c0/4cb643c831314b8fc72770c93a795c0d3dde339f36c8430544c36727956027e2cb329641ace73c5951085ecf93ac608c898859d3d4f7b117d405e1e13c703c76 + languageName: node + linkType: hard + +"is-map@npm:^2.0.3": + version: 2.0.3 + resolution: "is-map@npm:2.0.3" + checksum: 10c0/2c4d431b74e00fdda7162cd8e4b763d6f6f217edf97d4f8538b94b8702b150610e2c64961340015fe8df5b1fcee33ccd2e9b62619c4a8a3a155f8de6d6d355fc + languageName: node + linkType: hard + +"is-negative-zero@npm:^2.0.3": + version: 2.0.3 + resolution: "is-negative-zero@npm:2.0.3" + checksum: 10c0/bcdcf6b8b9714063ffcfa9929c575ac69bfdabb8f4574ff557dfc086df2836cf07e3906f5bbc4f2a5c12f8f3ba56af640c843cdfc74da8caed86c7c7d66fd08e + languageName: node + linkType: hard + +"is-number-object@npm:^1.1.1": + version: 1.1.1 + resolution: "is-number-object@npm:1.1.1" + dependencies: + call-bound: "npm:^1.0.3" + has-tostringtag: "npm:^1.0.2" + checksum: 10c0/97b451b41f25135ff021d85c436ff0100d84a039bb87ffd799cbcdbea81ef30c464ced38258cdd34f080be08fc3b076ca1f472086286d2aa43521d6ec6a79f53 + languageName: node + linkType: hard + +"is-plain-object@npm:^2.0.4": + version: 2.0.4 + resolution: "is-plain-object@npm:2.0.4" + dependencies: + isobject: "npm:^3.0.1" + checksum: 10c0/f050fdd5203d9c81e8c4df1b3ff461c4bc64e8b5ca383bcdde46131361d0a678e80bcf00b5257646f6c636197629644d53bd8e2375aea633de09a82d57e942f4 + languageName: node + linkType: hard + +"is-regex@npm:^1.2.1": + version: 1.2.1 + resolution: "is-regex@npm:1.2.1" + dependencies: + call-bound: "npm:^1.0.2" + gopd: "npm:^1.2.0" + has-tostringtag: "npm:^1.0.2" + hasown: "npm:^2.0.2" + checksum: 10c0/1d3715d2b7889932349241680032e85d0b492cfcb045acb75ffc2c3085e8d561184f1f7e84b6f8321935b4aea39bc9c6ba74ed595b57ce4881a51dfdbc214e04 + languageName: node + linkType: hard + +"is-set@npm:^2.0.3": + version: 2.0.3 + resolution: "is-set@npm:2.0.3" + checksum: 10c0/f73732e13f099b2dc879c2a12341cfc22ccaca8dd504e6edae26484bd5707a35d503fba5b4daad530a9b088ced1ae6c9d8200fd92e09b428fe14ea79ce8080b7 + languageName: node + linkType: hard + +"is-shared-array-buffer@npm:^1.0.4": + version: 1.0.4 + resolution: "is-shared-array-buffer@npm:1.0.4" + dependencies: + call-bound: "npm:^1.0.3" + checksum: 10c0/65158c2feb41ff1edd6bbd6fd8403a69861cf273ff36077982b5d4d68e1d59278c71691216a4a64632bd76d4792d4d1d2553901b6666d84ade13bba5ea7bc7db + languageName: node + linkType: hard + +"is-stream@npm:^2.0.0": + version: 2.0.1 + resolution: "is-stream@npm:2.0.1" + checksum: 10c0/7c284241313fc6efc329b8d7f08e16c0efeb6baab1b4cd0ba579eb78e5af1aa5da11e68559896a2067cd6c526bd29241dda4eb1225e627d5aa1a89a76d4635a5 + languageName: node + linkType: hard + +"is-string@npm:^1.1.1": + version: 1.1.1 + resolution: "is-string@npm:1.1.1" + dependencies: + call-bound: "npm:^1.0.3" + has-tostringtag: "npm:^1.0.2" + checksum: 10c0/2f518b4e47886bb81567faba6ffd0d8a8333cf84336e2e78bf160693972e32ad00fe84b0926491cc598dee576fdc55642c92e62d0cbe96bf36f643b6f956f94d + languageName: node + linkType: hard + +"is-symbol@npm:^1.0.4, is-symbol@npm:^1.1.1": + version: 1.1.1 + resolution: "is-symbol@npm:1.1.1" + dependencies: + call-bound: "npm:^1.0.2" + has-symbols: "npm:^1.1.0" + safe-regex-test: "npm:^1.1.0" + checksum: 10c0/f08f3e255c12442e833f75a9e2b84b2d4882fdfd920513cf2a4a2324f0a5b076c8fd913778e3ea5d258d5183e9d92c0cd20e04b03ab3df05316b049b2670af1e + languageName: node + linkType: hard + +"is-typed-array@npm:^1.1.13, is-typed-array@npm:^1.1.14, is-typed-array@npm:^1.1.15": + version: 1.1.15 + resolution: "is-typed-array@npm:1.1.15" + dependencies: + which-typed-array: "npm:^1.1.16" + checksum: 10c0/415511da3669e36e002820584e264997ffe277ff136643a3126cc949197e6ca3334d0f12d084e83b1994af2e9c8141275c741cf2b7da5a2ff62dd0cac26f76c4 + languageName: node + linkType: hard + +"is-weakmap@npm:^2.0.2": + version: 2.0.2 + resolution: "is-weakmap@npm:2.0.2" + checksum: 10c0/443c35bb86d5e6cc5929cd9c75a4024bb0fff9586ed50b092f94e700b89c43a33b186b76dbc6d54f3d3d09ece689ab38dcdc1af6a482cbe79c0f2da0a17f1299 + languageName: node + linkType: hard + +"is-weakref@npm:^1.0.2, is-weakref@npm:^1.1.1": + version: 1.1.1 + resolution: "is-weakref@npm:1.1.1" + dependencies: + call-bound: "npm:^1.0.3" + checksum: 10c0/8e0a9c07b0c780949a100e2cab2b5560a48ecd4c61726923c1a9b77b6ab0aa0046c9e7fb2206042296817045376dee2c8ab1dabe08c7c3dfbf195b01275a085b + languageName: node + linkType: hard + +"is-weakset@npm:^2.0.3": + version: 2.0.4 + resolution: "is-weakset@npm:2.0.4" + dependencies: + call-bound: "npm:^1.0.3" + get-intrinsic: "npm:^1.2.6" + checksum: 10c0/6491eba08acb8dc9532da23cb226b7d0192ede0b88f16199e592e4769db0a077119c1f5d2283d1e0d16d739115f70046e887e477eb0e66cd90e1bb29f28ba647 + languageName: node + linkType: hard + +"isarray@npm:^2.0.5": + version: 2.0.5 + resolution: "isarray@npm:2.0.5" + checksum: 10c0/4199f14a7a13da2177c66c31080008b7124331956f47bca57dd0b6ea9f11687aa25e565a2c7a2b519bc86988d10398e3049a1f5df13c9f6b7664154690ae79fd + languageName: node + linkType: hard + +"isarray@npm:~1.0.0": + version: 1.0.0 + resolution: "isarray@npm:1.0.0" + checksum: 10c0/18b5be6669be53425f0b84098732670ed4e727e3af33bc7f948aac01782110eb9a18b3b329c5323bcdd3acdaae547ee077d3951317e7f133bff7105264b3003d + languageName: node + linkType: hard + +"isexe@npm:^2.0.0": + version: 2.0.0 + resolution: "isexe@npm:2.0.0" + checksum: 10c0/228cfa503fadc2c31596ab06ed6aa82c9976eec2bfd83397e7eaf06d0ccf42cd1dfd6743bf9aeb01aebd4156d009994c5f76ea898d2832c1fe342da923ca457d + languageName: node + linkType: hard + +"isexe@npm:^3.1.1": + version: 3.1.1 + resolution: "isexe@npm:3.1.1" + checksum: 10c0/9ec257654093443eb0a528a9c8cbba9c0ca7616ccb40abd6dde7202734d96bb86e4ac0d764f0f8cd965856aacbff2f4ce23e730dc19dfb41e3b0d865ca6fdcc7 + languageName: node + linkType: hard + +"isobject@npm:^3.0.1": + version: 3.0.1 + resolution: "isobject@npm:3.0.1" + checksum: 10c0/03344f5064a82f099a0cd1a8a407f4c0d20b7b8485e8e816c39f249e9416b06c322e8dec5b842b6bb8a06de0af9cb48e7bc1b5352f0fadc2f0abac033db3d4db + languageName: node + linkType: hard + +"iterator.prototype@npm:^1.1.5": + version: 1.1.5 + resolution: "iterator.prototype@npm:1.1.5" + dependencies: + define-data-property: "npm:^1.1.4" + es-object-atoms: "npm:^1.0.0" + get-intrinsic: "npm:^1.2.6" + get-proto: "npm:^1.0.0" + has-symbols: "npm:^1.1.0" + set-function-name: "npm:^2.0.2" + checksum: 10c0/f7a262808e1b41049ab55f1e9c29af7ec1025a000d243b83edf34ce2416eedd56079b117fa59376bb4a724110690f13aa8427f2ee29a09eec63a7e72367626d0 + languageName: node + linkType: hard + +"jdenticon@npm:^3.3.0": + version: 3.3.0 + resolution: "jdenticon@npm:3.3.0" + dependencies: + canvas-renderer: "npm:~2.2.0" + bin: + jdenticon: bin/jdenticon.js + checksum: 10c0/be2642fe3a9a9013d56ced80ec76a2ae6990363d7c2d92470125fc3d646fe711ba8a8695e49cd5bd696b84574fab4e93f3694414fdb95e150c5ce340c385d9e1 + languageName: node + linkType: hard + +"js-tokens@npm:^3.0.0 || ^4.0.0, js-tokens@npm:^4.0.0": + version: 4.0.0 + resolution: "js-tokens@npm:4.0.0" + checksum: 10c0/e248708d377aa058eacf2037b07ded847790e6de892bbad3dac0abba2e759cb9f121b00099a65195616badcb6eca8d14d975cb3e89eb1cfda644756402c8aeed + languageName: node + linkType: hard + +"js-yaml@npm:^4.1.1": + version: 4.1.1 + resolution: "js-yaml@npm:4.1.1" + dependencies: + argparse: "npm:^2.0.1" + bin: + js-yaml: bin/js-yaml.js + checksum: 10c0/561c7d7088c40a9bb53cc75becbfb1df6ae49b34b5e6e5a81744b14ae8667ec564ad2527709d1a6e7d5e5fa6d483aa0f373a50ad98d42fde368ec4a190d4fae7 + languageName: node + linkType: hard + +"jsan@npm:^3.1.14": + version: 3.1.14 + resolution: "jsan@npm:3.1.14" + checksum: 10c0/86b6738e90769d8e717849f7bd9ba4742a4ffb1edfa28354521e0c8f106a3addd15eb79d723b47dc9867c70ed06245e4ee14cb2a10c99b91ea612071688137dc + languageName: node + linkType: hard + +"jsesc@npm:^3.0.2": + version: 3.1.0 + resolution: "jsesc@npm:3.1.0" + bin: + jsesc: bin/jsesc + checksum: 10c0/531779df5ec94f47e462da26b4cbf05eb88a83d9f08aac2ba04206508fc598527a153d08bd462bae82fc78b3eaa1a908e1a4a79f886e9238641c4cdefaf118b1 + languageName: node + linkType: hard + +"json-buffer@npm:3.0.1": + version: 3.0.1 + resolution: "json-buffer@npm:3.0.1" + checksum: 10c0/0d1c91569d9588e7eef2b49b59851f297f3ab93c7b35c7c221e288099322be6b562767d11e4821da500f3219542b9afd2e54c5dc573107c1126ed1080f8e96d7 + languageName: node + linkType: hard + +"json-parse-even-better-errors@npm:^2.3.0": + version: 2.3.1 + resolution: "json-parse-even-better-errors@npm:2.3.1" + checksum: 10c0/140932564c8f0b88455432e0f33c4cb4086b8868e37524e07e723f4eaedb9425bdc2bafd71bd1d9765bd15fd1e2d126972bc83990f55c467168c228c24d665f3 + languageName: node + linkType: hard + +"json-schema-traverse@npm:^0.4.1": + version: 0.4.1 + resolution: "json-schema-traverse@npm:0.4.1" + checksum: 10c0/108fa90d4cc6f08243aedc6da16c408daf81793bf903e9fd5ab21983cda433d5d2da49e40711da016289465ec2e62e0324dcdfbc06275a607fe3233fde4942ce + languageName: node + linkType: hard + +"json-stable-stringify-without-jsonify@npm:^1.0.1": + version: 1.0.1 + resolution: "json-stable-stringify-without-jsonify@npm:1.0.1" + checksum: 10c0/cb168b61fd4de83e58d09aaa6425ef71001bae30d260e2c57e7d09a5fd82223e2f22a042dedaab8db23b7d9ae46854b08bb1f91675a8be11c5cffebef5fb66a5 + languageName: node + linkType: hard + +"json5@npm:^1.0.2": + version: 1.0.2 + resolution: "json5@npm:1.0.2" + dependencies: + minimist: "npm:^1.2.0" + bin: + json5: lib/cli.js + checksum: 10c0/9ee316bf21f000b00752e6c2a3b79ecf5324515a5c60ee88983a1910a45426b643a4f3461657586e8aeca87aaf96f0a519b0516d2ae527a6c3e7eed80f68717f + languageName: node + linkType: hard + +"json5@npm:^2.2.3": + version: 2.2.3 + resolution: "json5@npm:2.2.3" + bin: + json5: lib/cli.js + checksum: 10c0/5a04eed94810fa55c5ea138b2f7a5c12b97c3750bc63d11e511dcecbfef758003861522a070c2272764ee0f4e3e323862f386945aeb5b85b87ee43f084ba586c + languageName: node + linkType: hard + +"jsonwebtoken@npm:^9.0.2": + version: 9.0.3 + resolution: "jsonwebtoken@npm:9.0.3" + dependencies: + jws: "npm:^4.0.1" + lodash.includes: "npm:^4.3.0" + lodash.isboolean: "npm:^3.0.3" + lodash.isinteger: "npm:^4.0.4" + lodash.isnumber: "npm:^3.0.3" + lodash.isplainobject: "npm:^4.0.6" + lodash.isstring: "npm:^4.0.1" + lodash.once: "npm:^4.0.0" + ms: "npm:^2.1.1" + semver: "npm:^7.5.4" + checksum: 10c0/6ca7f1e54886ea3bde7146a5a22b53847c46e25453c7f7307a69818b9a6ad48c390b2e59d5690fcfd03c529b01960060cc4bb0c686991d6edae2285dfd30f4ba + languageName: node + linkType: hard + +"jsx-ast-utils@npm:^2.4.1 || ^3.0.0": + version: 3.3.5 + resolution: "jsx-ast-utils@npm:3.3.5" + dependencies: + array-includes: "npm:^3.1.6" + array.prototype.flat: "npm:^1.3.1" + object.assign: "npm:^4.1.4" + object.values: "npm:^1.1.6" + checksum: 10c0/a32679e9cb55469cb6d8bbc863f7d631b2c98b7fc7bf172629261751a6e7bc8da6ae374ddb74d5fbd8b06cf0eb4572287b259813d92b36e384024ed35e4c13e1 + languageName: node + linkType: hard + +"jwa@npm:^2.0.1": + version: 2.0.1 + resolution: "jwa@npm:2.0.1" + dependencies: + buffer-equal-constant-time: "npm:^1.0.1" + ecdsa-sig-formatter: "npm:1.0.11" + safe-buffer: "npm:^5.0.1" + checksum: 10c0/ab3ebc6598e10dc11419d4ed675c9ca714a387481466b10e8a6f3f65d8d9c9237e2826f2505280a739cf4cbcf511cb288eeec22b5c9c63286fc5a2e4f97e78cf + languageName: node + linkType: hard + +"jws@npm:^4.0.1": + version: 4.0.1 + resolution: "jws@npm:4.0.1" + dependencies: + jwa: "npm:^2.0.1" + safe-buffer: "npm:^5.0.1" + checksum: 10c0/6be1ed93023aef570ccc5ea8d162b065840f3ef12f0d1bb3114cade844de7a357d5dc558201d9a65101e70885a6fa56b17462f520e6b0d426195510618a154d0 + languageName: node + linkType: hard + +"keyv@npm:^4.5.4": + version: 4.5.4 + resolution: "keyv@npm:4.5.4" + dependencies: + json-buffer: "npm:3.0.1" + checksum: 10c0/aa52f3c5e18e16bb6324876bb8b59dd02acf782a4b789c7b2ae21107fab95fab3890ed448d4f8dba80ce05391eeac4bfabb4f02a20221342982f806fa2cf271e + languageName: node + linkType: hard + +"kind-of@npm:^6.0.2": + version: 6.0.3 + resolution: "kind-of@npm:6.0.3" + checksum: 10c0/61cdff9623dabf3568b6445e93e31376bee1cdb93f8ba7033d86022c2a9b1791a1d9510e026e6465ebd701a6dd2f7b0808483ad8838341ac52f003f512e0b4c4 + languageName: node + linkType: hard + +"levn@npm:^0.4.1": + version: 0.4.1 + resolution: "levn@npm:0.4.1" + dependencies: + prelude-ls: "npm:^1.2.1" + type-check: "npm:~0.4.0" + checksum: 10c0/effb03cad7c89dfa5bd4f6989364bfc79994c2042ec5966cb9b95990e2edee5cd8969ddf42616a0373ac49fac1403437deaf6e9050fbbaa3546093a59b9ac94e + languageName: node + linkType: hard + +"lines-and-columns@npm:^1.1.6": + version: 1.2.4 + resolution: "lines-and-columns@npm:1.2.4" + checksum: 10c0/3da6ee62d4cd9f03f5dc90b4df2540fb85b352081bee77fe4bbcd12c9000ead7f35e0a38b8d09a9bb99b13223446dd8689ff3c4959807620726d788701a83d2d + languageName: node + linkType: hard + +"linked-list@npm:^2.1.0": + version: 2.1.0 + resolution: "linked-list@npm:2.1.0" + checksum: 10c0/d039f664f847022be908460a06a7f63792aadcdfd3061f999726a92d89bf3a10688e7dfb98236f65d9980e2a175839028a374070e48e0676ab9e66122fca7063 + languageName: node + linkType: hard + +"locate-path@npm:^6.0.0": + version: 6.0.0 + resolution: "locate-path@npm:6.0.0" + dependencies: + p-locate: "npm:^5.0.0" + checksum: 10c0/d3972ab70dfe58ce620e64265f90162d247e87159b6126b01314dd67be43d50e96a50b517bce2d9452a79409c7614054c277b5232377de50416564a77ac7aad3 + languageName: node + linkType: hard + +"lodash.includes@npm:^4.3.0": + version: 4.3.0 + resolution: "lodash.includes@npm:4.3.0" + checksum: 10c0/7ca498b9b75bf602d04e48c0adb842dfc7d90f77bcb2a91a2b2be34a723ad24bc1c8b3683ec6b2552a90f216c723cdea530ddb11a3320e08fa38265703978f4b + languageName: node + linkType: hard + +"lodash.isboolean@npm:^3.0.3": + version: 3.0.3 + resolution: "lodash.isboolean@npm:3.0.3" + checksum: 10c0/0aac604c1ef7e72f9a6b798e5b676606042401dd58e49f051df3cc1e3adb497b3d7695635a5cbec4ae5f66456b951fdabe7d6b387055f13267cde521f10ec7f7 + languageName: node + linkType: hard + +"lodash.isinteger@npm:^4.0.4": + version: 4.0.4 + resolution: "lodash.isinteger@npm:4.0.4" + checksum: 10c0/4c3e023a2373bf65bf366d3b8605b97ec830bca702a926939bcaa53f8e02789b6a176e7f166b082f9365bfec4121bfeb52e86e9040cb8d450e64c858583f61b7 + languageName: node + linkType: hard + +"lodash.isnumber@npm:^3.0.3": + version: 3.0.3 + resolution: "lodash.isnumber@npm:3.0.3" + checksum: 10c0/2d01530513a1ee4f72dd79528444db4e6360588adcb0e2ff663db2b3f642d4bb3d687051ae1115751ca9082db4fdef675160071226ca6bbf5f0c123dbf0aa12d + languageName: node + linkType: hard + +"lodash.isplainobject@npm:^4.0.6": + version: 4.0.6 + resolution: "lodash.isplainobject@npm:4.0.6" + checksum: 10c0/afd70b5c450d1e09f32a737bed06ff85b873ecd3d3d3400458725283e3f2e0bb6bf48e67dbe7a309eb371a822b16a26cca4a63c8c52db3fc7dc9d5f9dd324cbb + languageName: node + linkType: hard + +"lodash.isstring@npm:^4.0.1": + version: 4.0.1 + resolution: "lodash.isstring@npm:4.0.1" + checksum: 10c0/09eaf980a283f9eef58ef95b30ec7fee61df4d6bf4aba3b5f096869cc58f24c9da17900febc8ffd67819b4e29de29793190e88dc96983db92d84c95fa85d1c92 + languageName: node + linkType: hard + +"lodash.merge@npm:^4.6.2": + version: 4.6.2 + resolution: "lodash.merge@npm:4.6.2" + checksum: 10c0/402fa16a1edd7538de5b5903a90228aa48eb5533986ba7fa26606a49db2572bf414ff73a2c9f5d5fd36b31c46a5d5c7e1527749c07cbcf965ccff5fbdf32c506 + languageName: node + linkType: hard + +"lodash.once@npm:^4.0.0": + version: 4.1.1 + resolution: "lodash.once@npm:4.1.1" + checksum: 10c0/46a9a0a66c45dd812fcc016e46605d85ad599fe87d71a02f6736220554b52ffbe82e79a483ad40f52a8a95755b0d1077fba259da8bfb6694a7abbf4a48f1fc04 + languageName: node + linkType: hard + +"lodash@npm:^4.17.21": + version: 4.17.21 + resolution: "lodash@npm:4.17.21" + checksum: 10c0/d8cbea072bb08655bb4c989da418994b073a608dffa608b09ac04b43a791b12aeae7cd7ad919aa4c925f33b48490b5cfe6c1f71d827956071dae2e7bb3a6b74c + languageName: node + linkType: hard + +"loose-envify@npm:^1.4.0": + version: 1.4.0 + resolution: "loose-envify@npm:1.4.0" + dependencies: + js-tokens: "npm:^3.0.0 || ^4.0.0" + bin: + loose-envify: cli.js + checksum: 10c0/655d110220983c1a4b9c0c679a2e8016d4b67f6e9c7b5435ff5979ecdb20d0813f4dec0a08674fcbdd4846a3f07edbb50a36811fd37930b94aaa0d9daceb017e + languageName: node + linkType: hard + +"loupe@npm:^3.1.0, loupe@npm:^3.1.2": + version: 3.2.1 + resolution: "loupe@npm:3.2.1" + checksum: 10c0/910c872cba291309664c2d094368d31a68907b6f5913e989d301b5c25f30e97d76d77f23ab3bf3b46d0f601ff0b6af8810c10c31b91d2c6b2f132809ca2cc705 + languageName: node + linkType: hard + +"lru-cache@npm:^11.0.0, lru-cache@npm:^11.1.0, lru-cache@npm:^11.2.1": + version: 11.2.4 + resolution: "lru-cache@npm:11.2.4" + checksum: 10c0/4a24f9b17537619f9144d7b8e42cd5a225efdfd7076ebe7b5e7dc02b860a818455201e67fbf000765233fe7e339d3c8229fc815e9b58ee6ede511e07608c19b2 + languageName: node + linkType: hard + +"lru-cache@npm:^5.1.1": + version: 5.1.1 + resolution: "lru-cache@npm:5.1.1" + dependencies: + yallist: "npm:^3.0.2" + checksum: 10c0/89b2ef2ef45f543011e38737b8a8622a2f8998cddf0e5437174ef8f1f70a8b9d14a918ab3e232cb3ba343b7abddffa667f0b59075b2b80e6b4d63c3de6127482 + languageName: node + linkType: hard + +"magic-string@npm:^0.30.12": + version: 0.30.21 + resolution: "magic-string@npm:0.30.21" + dependencies: + "@jridgewell/sourcemap-codec": "npm:^1.5.5" + checksum: 10c0/299378e38f9a270069fc62358522ddfb44e94244baa0d6a8980ab2a9b2490a1d03b236b447eee309e17eb3bddfa482c61259d47960eb018a904f0ded52780c4a + languageName: node + linkType: hard + +"make-fetch-happen@npm:^15.0.0": + version: 15.0.3 + resolution: "make-fetch-happen@npm:15.0.3" + dependencies: + "@npmcli/agent": "npm:^4.0.0" + cacache: "npm:^20.0.1" + http-cache-semantics: "npm:^4.1.1" + minipass: "npm:^7.0.2" + minipass-fetch: "npm:^5.0.0" + minipass-flush: "npm:^1.0.5" + minipass-pipeline: "npm:^1.2.4" + negotiator: "npm:^1.0.0" + proc-log: "npm:^6.0.0" + promise-retry: "npm:^2.0.1" + ssri: "npm:^13.0.0" + checksum: 10c0/525f74915660be60b616bcbd267c4a5b59481b073ba125e45c9c3a041bb1a47a2bd0ae79d028eb6f5f95bf9851a4158423f5068539c3093621abb64027e8e461 + languageName: node + linkType: hard + +"math-intrinsics@npm:^1.1.0": + version: 1.1.0 + resolution: "math-intrinsics@npm:1.1.0" + checksum: 10c0/7579ff94e899e2f76ab64491d76cf606274c874d8f2af4a442c016bd85688927fcfca157ba6bf74b08e9439dc010b248ce05b96cc7c126a354c3bae7fcb48b7f + languageName: node + linkType: hard + +"merge-stream@npm:^2.0.0": + version: 2.0.0 + resolution: "merge-stream@npm:2.0.0" + checksum: 10c0/867fdbb30a6d58b011449b8885601ec1690c3e41c759ecd5a9d609094f7aed0096c37823ff4a7190ef0b8f22cc86beb7049196ff68c016e3b3c671d0dac91ce5 + languageName: node + linkType: hard + +"mimic-fn@npm:^2.1.0": + version: 2.1.0 + resolution: "mimic-fn@npm:2.1.0" + checksum: 10c0/b26f5479d7ec6cc2bce275a08f146cf78f5e7b661b18114e2506dd91ec7ec47e7a25bf4360e5438094db0560bcc868079fb3b1fb3892b833c1ecbf63f80c95a4 + languageName: node + linkType: hard + +"minimatch@npm:^10.1.1": + version: 10.1.1 + resolution: "minimatch@npm:10.1.1" + dependencies: + "@isaacs/brace-expansion": "npm:^5.0.0" + checksum: 10c0/c85d44821c71973d636091fddbfbffe62370f5ee3caf0241c5b60c18cd289e916200acb2361b7e987558cd06896d153e25d505db9fc1e43e6b4b6752e2702902 + languageName: node + linkType: hard + +"minimatch@npm:^3.1.2": + version: 3.1.2 + resolution: "minimatch@npm:3.1.2" + dependencies: + brace-expansion: "npm:^1.1.7" + checksum: 10c0/0262810a8fc2e72cca45d6fd86bd349eee435eb95ac6aa45c9ea2180e7ee875ef44c32b55b5973ceabe95ea12682f6e3725cbb63d7a2d1da3ae1163c8b210311 + languageName: node + linkType: hard + +"minimatch@npm:^5.1.1": + version: 5.1.6 + resolution: "minimatch@npm:5.1.6" + dependencies: + brace-expansion: "npm:^2.0.1" + checksum: 10c0/3defdfd230914f22a8da203747c42ee3c405c39d4d37ffda284dac5e45b7e1f6c49aa8be606509002898e73091ff2a3bbfc59c2c6c71d4660609f63aa92f98e3 + languageName: node + linkType: hard + +"minimatch@npm:^9.0.4": + version: 9.0.5 + resolution: "minimatch@npm:9.0.5" + dependencies: + brace-expansion: "npm:^2.0.1" + checksum: 10c0/de96cf5e35bdf0eab3e2c853522f98ffbe9a36c37797778d2665231ec1f20a9447a7e567cb640901f89e4daaa95ae5d70c65a9e8aa2bb0019b6facbc3c0575ed + languageName: node + linkType: hard + +"minimist@npm:^1.2.0, minimist@npm:^1.2.6": + version: 1.2.8 + resolution: "minimist@npm:1.2.8" + checksum: 10c0/19d3fcdca050087b84c2029841a093691a91259a47def2f18222f41e7645a0b7c44ef4b40e88a1e58a40c84d2ef0ee6047c55594d298146d0eb3f6b737c20ce6 + languageName: node + linkType: hard + +"minipass-collect@npm:^2.0.1": + version: 2.0.1 + resolution: "minipass-collect@npm:2.0.1" + dependencies: + minipass: "npm:^7.0.3" + checksum: 10c0/5167e73f62bb74cc5019594709c77e6a742051a647fe9499abf03c71dca75515b7959d67a764bdc4f8b361cf897fbf25e2d9869ee039203ed45240f48b9aa06e + languageName: node + linkType: hard + +"minipass-fetch@npm:^5.0.0": + version: 5.0.0 + resolution: "minipass-fetch@npm:5.0.0" + dependencies: + encoding: "npm:^0.1.13" + minipass: "npm:^7.0.3" + minipass-sized: "npm:^1.0.3" + minizlib: "npm:^3.0.1" + dependenciesMeta: + encoding: + optional: true + checksum: 10c0/9443aab5feab190972f84b64116e54e58dd87a58e62399cae0a4a7461b80568281039b7c3a38ba96453431ebc799d1e26999e548540156216729a4967cd5ef06 + languageName: node + linkType: hard + +"minipass-flush@npm:^1.0.5": + version: 1.0.5 + resolution: "minipass-flush@npm:1.0.5" + dependencies: + minipass: "npm:^3.0.0" + checksum: 10c0/2a51b63feb799d2bb34669205eee7c0eaf9dce01883261a5b77410c9408aa447e478efd191b4de6fc1101e796ff5892f8443ef20d9544385819093dbb32d36bd + languageName: node + linkType: hard + +"minipass-pipeline@npm:^1.2.4": + version: 1.2.4 + resolution: "minipass-pipeline@npm:1.2.4" + dependencies: + minipass: "npm:^3.0.0" + checksum: 10c0/cbda57cea20b140b797505dc2cac71581a70b3247b84480c1fed5ca5ba46c25ecc25f68bfc9e6dcb1a6e9017dab5c7ada5eab73ad4f0a49d84e35093e0c643f2 + languageName: node + linkType: hard + +"minipass-sized@npm:^1.0.3": + version: 1.0.3 + resolution: "minipass-sized@npm:1.0.3" + dependencies: + minipass: "npm:^3.0.0" + checksum: 10c0/298f124753efdc745cfe0f2bdfdd81ba25b9f4e753ca4a2066eb17c821f25d48acea607dfc997633ee5bf7b6dfffb4eee4f2051eb168663f0b99fad2fa4829cb + languageName: node + linkType: hard + +"minipass@npm:^3.0.0": + version: 3.3.6 + resolution: "minipass@npm:3.3.6" + dependencies: + yallist: "npm:^4.0.0" + checksum: 10c0/a114746943afa1dbbca8249e706d1d38b85ed1298b530f5808ce51f8e9e941962e2a5ad2e00eae7dd21d8a4aae6586a66d4216d1a259385e9d0358f0c1eba16c + languageName: node + linkType: hard + +"minipass@npm:^7.0.2, minipass@npm:^7.0.3, minipass@npm:^7.0.4, minipass@npm:^7.1.2": + version: 7.1.2 + resolution: "minipass@npm:7.1.2" + checksum: 10c0/b0fd20bb9fb56e5fa9a8bfac539e8915ae07430a619e4b86ff71f5fc757ef3924b23b2c4230393af1eda647ed3d75739e4e0acb250a6b1eb277cf7f8fe449557 + languageName: node + linkType: hard + +"minizlib@npm:^3.0.1, minizlib@npm:^3.1.0": + version: 3.1.0 + resolution: "minizlib@npm:3.1.0" + dependencies: + minipass: "npm:^7.1.2" + checksum: 10c0/5aad75ab0090b8266069c9aabe582c021ae53eb33c6c691054a13a45db3b4f91a7fb1bd79151e6b4e9e9a86727b522527c0a06ec7d45206b745d54cd3097bcec + languageName: node + linkType: hard + +"ms@npm:^2.1.1, ms@npm:^2.1.3": + version: 2.1.3 + resolution: "ms@npm:2.1.3" + checksum: 10c0/d924b57e7312b3b63ad21fc5b3dc0af5e78d61a1fc7cfb5457edaf26326bf62be5307cc87ffb6862ef1c2b33b0233cdb5d4f01c4c958cc0d660948b65a287a48 + languageName: node + linkType: hard + +"multiaddr@npm:^10.0.1": + version: 10.0.1 + resolution: "multiaddr@npm:10.0.1" + dependencies: + dns-over-http-resolver: "npm:^1.2.3" + err-code: "npm:^3.0.1" + is-ip: "npm:^3.1.0" + multiformats: "npm:^9.4.5" + uint8arrays: "npm:^3.0.0" + varint: "npm:^6.0.0" + checksum: 10c0/948d0c69d75992d754fd36154db4d4a435b977c56a617183a9d8c2075081e2cfcacda3bedb72bbd6759bb329956a45b216e804829e708af8d2f780f8152a4bf6 + languageName: node + linkType: hard + +"multiformats@npm:^9.4.2, multiformats@npm:^9.4.5": + version: 9.9.0 + resolution: "multiformats@npm:9.9.0" + checksum: 10c0/1fdb34fd2fb085142665e8bd402570659b50a5fae5994027e1df3add9e1ce1283ed1e0c2584a5c63ac0a58e871b8ee9665c4a99ca36ce71032617449d48aa975 + languageName: node + linkType: hard + +"nanoid@npm:^3.3.11": + version: 3.3.11 + resolution: "nanoid@npm:3.3.11" + bin: + nanoid: bin/nanoid.cjs + checksum: 10c0/40e7f70b3d15f725ca072dfc4f74e81fcf1fbb02e491cf58ac0c79093adc9b0a73b152bcde57df4b79cd097e13023d7504acb38404a4da7bc1cd8e887b82fe0b + languageName: node + linkType: hard + +"nanoid@npm:^5.1.2": + version: 5.1.6 + resolution: "nanoid@npm:5.1.6" + bin: + nanoid: bin/nanoid.js + checksum: 10c0/27b5b055ad8332cf4f0f9f6e2a494aa7e5ded89df4cab8c8490d4eabefe72c4423971d2745d22002868b1d50576a5e42b7b05214733b19f576382323282dd26e + languageName: node + linkType: hard + +"native-fetch@npm:^3.0.0": + version: 3.0.0 + resolution: "native-fetch@npm:3.0.0" + peerDependencies: + node-fetch: "*" + checksum: 10c0/737cdd209dd366df8b748dabac39340089d57a2bcc460ffc029ec145f30aeffea0c6a6f177013069d6f7f04ffc8c3e39cfb8e3825e7071a373c4f86b187ae1b5 + languageName: node + linkType: hard + +"natural-compare@npm:^1.4.0": + version: 1.4.0 + resolution: "natural-compare@npm:1.4.0" + checksum: 10c0/f5f9a7974bfb28a91afafa254b197f0f22c684d4a1731763dda960d2c8e375b36c7d690e0d9dc8fba774c537af14a7e979129bca23d88d052fbeb9466955e447 + languageName: node + linkType: hard + +"negotiator@npm:^1.0.0": + version: 1.0.0 + resolution: "negotiator@npm:1.0.0" + checksum: 10c0/4c559dd52669ea48e1914f9d634227c561221dd54734070791f999c52ed0ff36e437b2e07d5c1f6e32909fc625fe46491c16e4a8f0572567d4dd15c3a4fda04b + languageName: node + linkType: hard + +"node-gyp@npm:latest": + version: 12.1.0 + resolution: "node-gyp@npm:12.1.0" + dependencies: + env-paths: "npm:^2.2.0" + exponential-backoff: "npm:^3.1.1" + graceful-fs: "npm:^4.2.6" + make-fetch-happen: "npm:^15.0.0" + nopt: "npm:^9.0.0" + proc-log: "npm:^6.0.0" + semver: "npm:^7.3.5" + tar: "npm:^7.5.2" + tinyglobby: "npm:^0.2.12" + which: "npm:^6.0.0" + bin: + node-gyp: bin/node-gyp.js + checksum: 10c0/f43efea8aaf0beb6b2f6184e533edad779b2ae38062953e21951f46221dd104006cc574154f2ad4a135467a5aae92c49e84ef289311a82e08481c5df0e8dc495 + languageName: node + linkType: hard + +"node-releases@npm:^2.0.27": + version: 2.0.27 + resolution: "node-releases@npm:2.0.27" + checksum: 10c0/f1e6583b7833ea81880627748d28a3a7ff5703d5409328c216ae57befbced10ce2c991bea86434e8ec39003bd017f70481e2e5f8c1f7e0a7663241f81d6e00e2 + languageName: node + linkType: hard + +"nopt@npm:^9.0.0": + version: 9.0.0 + resolution: "nopt@npm:9.0.0" + dependencies: + abbrev: "npm:^4.0.0" + bin: + nopt: bin/nopt.js + checksum: 10c0/1822eb6f9b020ef6f7a7516d7b64a8036e09666ea55ac40416c36e4b2b343122c3cff0e2f085675f53de1d2db99a2a89a60ccea1d120bcd6a5347bf6ceb4a7fd + languageName: node + linkType: hard + +"notistack@npm:^3.0.1": + version: 3.0.2 + resolution: "notistack@npm:3.0.2" + dependencies: + clsx: "npm:^1.1.0" + goober: "npm:^2.0.33" + peerDependencies: + react: ^17.0.0 || ^18.0.0 || ^19.0.0 + react-dom: ^17.0.0 || ^18.0.0 || ^19.0.0 + checksum: 10c0/82a0270ee1b9e70bc1dfad4d7e1d2bbb6dc08ac887b920986dcfa58cc2aa975bd48b71f7cf3afc2e11badd142ba9ade5f708c858ee2080b9d4cd950b80c43811 + languageName: node + linkType: hard + +"npm-run-path@npm:^4.0.1": + version: 4.0.1 + resolution: "npm-run-path@npm:4.0.1" + dependencies: + path-key: "npm:^3.0.0" + checksum: 10c0/6f9353a95288f8455cf64cbeb707b28826a7f29690244c1e4bb61ec573256e021b6ad6651b394eb1ccfd00d6ec50147253aba2c5fe58a57ceb111fad62c519ac + languageName: node + linkType: hard + +"object-assign@npm:^4.1.1": + version: 4.1.1 + resolution: "object-assign@npm:4.1.1" + checksum: 10c0/1f4df9945120325d041ccf7b86f31e8bcc14e73d29171e37a7903050e96b81323784ec59f93f102ec635bcf6fa8034ba3ea0a8c7e69fa202b87ae3b6cec5a414 + languageName: node + linkType: hard + +"object-inspect@npm:^1.13.3, object-inspect@npm:^1.13.4": + version: 1.13.4 + resolution: "object-inspect@npm:1.13.4" + checksum: 10c0/d7f8711e803b96ea3191c745d6f8056ce1f2496e530e6a19a0e92d89b0fa3c76d910c31f0aa270432db6bd3b2f85500a376a83aaba849a8d518c8845b3211692 + languageName: node + linkType: hard + +"object-keys@npm:^1.1.1": + version: 1.1.1 + resolution: "object-keys@npm:1.1.1" + checksum: 10c0/b11f7ccdbc6d406d1f186cdadb9d54738e347b2692a14439ca5ac70c225fa6db46db809711b78589866d47b25fc3e8dee0b4c722ac751e11180f9380e3d8601d + languageName: node + linkType: hard + +"object.assign@npm:^4.1.4, object.assign@npm:^4.1.7": + version: 4.1.7 + resolution: "object.assign@npm:4.1.7" + dependencies: + call-bind: "npm:^1.0.8" + call-bound: "npm:^1.0.3" + define-properties: "npm:^1.2.1" + es-object-atoms: "npm:^1.0.0" + has-symbols: "npm:^1.1.0" + object-keys: "npm:^1.1.1" + checksum: 10c0/3b2732bd860567ea2579d1567525168de925a8d852638612846bd8082b3a1602b7b89b67b09913cbb5b9bd6e95923b2ae73580baa9d99cb4e990564e8cbf5ddc + languageName: node + linkType: hard + +"object.entries@npm:^1.1.9": + version: 1.1.9 + resolution: "object.entries@npm:1.1.9" + dependencies: + call-bind: "npm:^1.0.8" + call-bound: "npm:^1.0.4" + define-properties: "npm:^1.2.1" + es-object-atoms: "npm:^1.1.1" + checksum: 10c0/d4b8c1e586650407da03370845f029aa14076caca4e4d4afadbc69cfb5b78035fd3ee7be417141abdb0258fa142e59b11923b4c44d8b1255b28f5ffcc50da7db + languageName: node + linkType: hard + +"object.fromentries@npm:^2.0.8": + version: 2.0.8 + resolution: "object.fromentries@npm:2.0.8" + dependencies: + call-bind: "npm:^1.0.7" + define-properties: "npm:^1.2.1" + es-abstract: "npm:^1.23.2" + es-object-atoms: "npm:^1.0.0" + checksum: 10c0/cd4327e6c3369cfa805deb4cbbe919bfb7d3aeebf0bcaba291bb568ea7169f8f8cdbcabe2f00b40db0c20cd20f08e11b5f3a5a36fb7dd3fe04850c50db3bf83b + languageName: node + linkType: hard + +"object.groupby@npm:^1.0.3": + version: 1.0.3 + resolution: "object.groupby@npm:1.0.3" + dependencies: + call-bind: "npm:^1.0.7" + define-properties: "npm:^1.2.1" + es-abstract: "npm:^1.23.2" + checksum: 10c0/60d0455c85c736fbfeda0217d1a77525956f76f7b2495edeca9e9bbf8168a45783199e77b894d30638837c654d0cc410e0e02cbfcf445bc8de71c3da1ede6a9c + languageName: node + linkType: hard + +"object.values@npm:^1.1.6, object.values@npm:^1.2.1": + version: 1.2.1 + resolution: "object.values@npm:1.2.1" + dependencies: + call-bind: "npm:^1.0.8" + call-bound: "npm:^1.0.3" + define-properties: "npm:^1.2.1" + es-object-atoms: "npm:^1.0.0" + checksum: 10c0/3c47814fdc64842ae3d5a74bc9d06bdd8d21563c04d9939bf6716a9c00596a4ebc342552f8934013d1ec991c74e3671b26710a0c51815f0b603795605ab6b2c9 + languageName: node + linkType: hard + +"onetime@npm:^5.1.2": + version: 5.1.2 + resolution: "onetime@npm:5.1.2" + dependencies: + mimic-fn: "npm:^2.1.0" + checksum: 10c0/ffcef6fbb2692c3c40749f31ea2e22677a876daea92959b8a80b521d95cca7a668c884d8b2045d1d8ee7d56796aa405c405462af112a1477594cc63531baeb8f + languageName: node + linkType: hard + +"optionator@npm:^0.9.3": + version: 0.9.4 + resolution: "optionator@npm:0.9.4" + dependencies: + deep-is: "npm:^0.1.3" + fast-levenshtein: "npm:^2.0.6" + levn: "npm:^0.4.1" + prelude-ls: "npm:^1.2.1" + type-check: "npm:^0.4.0" + word-wrap: "npm:^1.2.5" + checksum: 10c0/4afb687a059ee65b61df74dfe87d8d6815cd6883cb8b3d5883a910df72d0f5d029821f37025e4bccf4048873dbdb09acc6d303d27b8f76b1a80dd5a7d5334675 + languageName: node + linkType: hard + +"own-keys@npm:^1.0.1": + version: 1.0.1 + resolution: "own-keys@npm:1.0.1" + dependencies: + get-intrinsic: "npm:^1.2.6" + object-keys: "npm:^1.1.1" + safe-push-apply: "npm:^1.0.0" + checksum: 10c0/6dfeb3455bff92ec3f16a982d4e3e65676345f6902d9f5ded1d8265a6318d0200ce461956d6d1c70053c7fe9f9fe65e552faac03f8140d37ef0fdd108e67013a + languageName: node + linkType: hard + +"p-event@npm:^4.2.0": + version: 4.2.0 + resolution: "p-event@npm:4.2.0" + dependencies: + p-timeout: "npm:^3.1.0" + checksum: 10c0/f1b6a2fb13d47f2a8afc00150da5ece0d28940ce3d8fa562873e091d3337d298e78fee9cb18b768598ff1d11df608b2ae23868309ff6405b864a2451ccd6d25a + languageName: node + linkType: hard + +"p-finally@npm:^1.0.0": + version: 1.0.0 + resolution: "p-finally@npm:1.0.0" + checksum: 10c0/6b8552339a71fe7bd424d01d8451eea92d379a711fc62f6b2fe64cad8a472c7259a236c9a22b4733abca0b5666ad503cb497792a0478c5af31ded793d00937e7 + languageName: node + linkType: hard + +"p-limit@npm:^3.0.2": + version: 3.1.0 + resolution: "p-limit@npm:3.1.0" + dependencies: + yocto-queue: "npm:^0.1.0" + checksum: 10c0/9db675949dbdc9c3763c89e748d0ef8bdad0afbb24d49ceaf4c46c02c77d30db4e0652ed36d0a0a7a95154335fab810d95c86153105bb73b3a90448e2bb14e1a + languageName: node + linkType: hard + +"p-locate@npm:^5.0.0": + version: 5.0.0 + resolution: "p-locate@npm:5.0.0" + dependencies: + p-limit: "npm:^3.0.2" + checksum: 10c0/2290d627ab7903b8b70d11d384fee714b797f6040d9278932754a6860845c4d3190603a0772a663c8cb5a7b21d1b16acb3a6487ebcafa9773094edc3dfe6009a + languageName: node + linkType: hard + +"p-map@npm:^7.0.2": + version: 7.0.4 + resolution: "p-map@npm:7.0.4" + checksum: 10c0/a5030935d3cb2919d7e89454d1ce82141e6f9955413658b8c9403cfe379283770ed3048146b44cde168aa9e8c716505f196d5689db0ae3ce9a71521a2fef3abd + languageName: node + linkType: hard + +"p-timeout@npm:^3.1.0": + version: 3.2.0 + resolution: "p-timeout@npm:3.2.0" + dependencies: + p-finally: "npm:^1.0.0" + checksum: 10c0/524b393711a6ba8e1d48137c5924749f29c93d70b671e6db761afa784726572ca06149c715632da8f70c090073afb2af1c05730303f915604fd38ee207b70a61 + languageName: node + linkType: hard + +"parent-module@npm:^1.0.0": + version: 1.0.1 + resolution: "parent-module@npm:1.0.1" + dependencies: + callsites: "npm:^3.0.0" + checksum: 10c0/c63d6e80000d4babd11978e0d3fee386ca7752a02b035fd2435960ffaa7219dc42146f07069fb65e6e8bf1caef89daf9af7535a39bddf354d78bf50d8294f556 + languageName: node + linkType: hard + +"parse-json@npm:^5.0.0": + version: 5.2.0 + resolution: "parse-json@npm:5.2.0" + dependencies: + "@babel/code-frame": "npm:^7.0.0" + error-ex: "npm:^1.3.1" + json-parse-even-better-errors: "npm:^2.3.0" + lines-and-columns: "npm:^1.1.6" + checksum: 10c0/77947f2253005be7a12d858aedbafa09c9ae39eb4863adf330f7b416ca4f4a08132e453e08de2db46459256fb66afaac5ee758b44fe6541b7cdaf9d252e59585 + languageName: node + linkType: hard + +"path-exists@npm:^4.0.0": + version: 4.0.0 + resolution: "path-exists@npm:4.0.0" + checksum: 10c0/8c0bd3f5238188197dc78dced15207a4716c51cc4e3624c44fc97acf69558f5ebb9a2afff486fe1b4ee148e0c133e96c5e11a9aa5c48a3006e3467da070e5e1b + languageName: node + linkType: hard + +"path-key@npm:^3.0.0, path-key@npm:^3.1.0": + version: 3.1.1 + resolution: "path-key@npm:3.1.1" + checksum: 10c0/748c43efd5a569c039d7a00a03b58eecd1d75f3999f5a28303d75f521288df4823bc057d8784eb72358b2895a05f29a070bc9f1f17d28226cc4e62494cc58c4c + languageName: node + linkType: hard + +"path-parse@npm:^1.0.7": + version: 1.0.7 + resolution: "path-parse@npm:1.0.7" + checksum: 10c0/11ce261f9d294cc7a58d6a574b7f1b935842355ec66fba3c3fd79e0f036462eaf07d0aa95bb74ff432f9afef97ce1926c720988c6a7451d8a584930ae7de86e1 + languageName: node + linkType: hard + +"path-scurry@npm:^2.0.0": + version: 2.0.1 + resolution: "path-scurry@npm:2.0.1" + dependencies: + lru-cache: "npm:^11.0.0" + minipass: "npm:^7.1.2" + checksum: 10c0/2a16ed0e81fbc43513e245aa5763354e25e787dab0d539581a6c3f0f967461a159ed6236b2559de23aa5b88e7dc32b469b6c47568833dd142a4b24b4f5cd2620 + languageName: node + linkType: hard + +"path-type@npm:^4.0.0": + version: 4.0.0 + resolution: "path-type@npm:4.0.0" + checksum: 10c0/666f6973f332f27581371efaf303fd6c272cc43c2057b37aa99e3643158c7e4b2626549555d88626e99ea9e046f82f32e41bbde5f1508547e9a11b149b52387c + languageName: node + linkType: hard + +"pathe@npm:^1.1.2": + version: 1.1.2 + resolution: "pathe@npm:1.1.2" + checksum: 10c0/64ee0a4e587fb0f208d9777a6c56e4f9050039268faaaaecd50e959ef01bf847b7872785c36483fa5cdcdbdfdb31fef2ff222684d4fc21c330ab60395c681897 + languageName: node + linkType: hard + +"pathval@npm:^2.0.0": + version: 2.0.1 + resolution: "pathval@npm:2.0.1" + checksum: 10c0/460f4709479fbf2c45903a65655fc8f0a5f6d808f989173aeef5fdea4ff4f303dc13f7870303999add60ec49d4c14733895c0a869392e9866f1091fa64fd7581 + languageName: node + linkType: hard + +"picocolors@npm:^1.1.1": + version: 1.1.1 + resolution: "picocolors@npm:1.1.1" + checksum: 10c0/e2e3e8170ab9d7c7421969adaa7e1b31434f789afb9b3f115f6b96d91945041ac3ceb02e9ec6fe6510ff036bcc0bf91e69a1772edc0b707e12b19c0f2d6bcf58 + languageName: node + linkType: hard + +"picomatch@npm:^4.0.3": + version: 4.0.3 + resolution: "picomatch@npm:4.0.3" + checksum: 10c0/9582c951e95eebee5434f59e426cddd228a7b97a0161a375aed4be244bd3fe8e3a31b846808ea14ef2c8a2527a6eeab7b3946a67d5979e81694654f939473ae2 + languageName: node + linkType: hard + +"possible-typed-array-names@npm:^1.0.0": + version: 1.1.0 + resolution: "possible-typed-array-names@npm:1.1.0" + checksum: 10c0/c810983414142071da1d644662ce4caebce890203eb2bc7bf119f37f3fe5796226e117e6cca146b521921fa6531072674174a3325066ac66fce089a53e1e5196 + languageName: node + linkType: hard + +"postcss@npm:^8.4.43": + version: 8.5.6 + resolution: "postcss@npm:8.5.6" + dependencies: + nanoid: "npm:^3.3.11" + picocolors: "npm:^1.1.1" + source-map-js: "npm:^1.2.1" + checksum: 10c0/5127cc7c91ed7a133a1b7318012d8bfa112da9ef092dddf369ae699a1f10ebbd89b1b9f25f3228795b84585c72aabd5ced5fc11f2ba467eedf7b081a66fad024 + languageName: node + linkType: hard + +"prelude-ls@npm:^1.2.1": + version: 1.2.1 + resolution: "prelude-ls@npm:1.2.1" + checksum: 10c0/b00d617431e7886c520a6f498a2e14c75ec58f6d93ba48c3b639cf241b54232d90daa05d83a9e9b9fef6baa63cb7e1e4602c2372fea5bc169668401eb127d0cd + languageName: node + linkType: hard + +"proc-log@npm:^6.0.0": + version: 6.1.0 + resolution: "proc-log@npm:6.1.0" + checksum: 10c0/4f178d4062733ead9d71a9b1ab24ebcecdfe2250916a5b1555f04fe2eda972a0ec76fbaa8df1ad9c02707add6749219d118a4fc46dc56bdfe4dde4b47d80bb82 + languageName: node + linkType: hard + +"process-nextick-args@npm:~2.0.0": + version: 2.0.1 + resolution: "process-nextick-args@npm:2.0.1" + checksum: 10c0/bec089239487833d46b59d80327a1605e1c5287eaad770a291add7f45fda1bb5e28b38e0e061add0a1d0ee0984788ce74fa394d345eed1c420cacf392c554367 + languageName: node + linkType: hard + +"promise-retry@npm:^2.0.1": + version: 2.0.1 + resolution: "promise-retry@npm:2.0.1" + dependencies: + err-code: "npm:^2.0.2" + retry: "npm:^0.12.0" + checksum: 10c0/9c7045a1a2928094b5b9b15336dcd2a7b1c052f674550df63cc3f36cd44028e5080448175b6f6ca32b642de81150f5e7b1a98b728f15cb069f2dd60ac2616b96 + languageName: node + linkType: hard + +"prop-types@npm:^15.6.2, prop-types@npm:^15.8.1": + version: 15.8.1 + resolution: "prop-types@npm:15.8.1" + dependencies: + loose-envify: "npm:^1.4.0" + object-assign: "npm:^4.1.1" + react-is: "npm:^16.13.1" + checksum: 10c0/59ece7ca2fb9838031d73a48d4becb9a7cc1ed10e610517c7d8f19a1e02fa47f7c27d557d8a5702bec3cfeccddc853579832b43f449e54635803f277b1c78077 + languageName: node + linkType: hard + +"punycode@npm:^2.1.0": + version: 2.3.1 + resolution: "punycode@npm:2.3.1" + checksum: 10c0/14f76a8206bc3464f794fb2e3d3cc665ae416c01893ad7a02b23766eb07159144ee612ad67af5e84fa4479ccfe67678c4feb126b0485651b302babf66f04f9e9 + languageName: node + linkType: hard + +"qr.js@npm:0.0.0": + version: 0.0.0 + resolution: "qr.js@npm:0.0.0" + checksum: 10c0/1c6a4c7a58d04e52ec2fee99e39b680fdc5b2a510a981df42c36b716a8eac6634d130fc4d65af8f030f2a07dbf5fa046b97cdfa7456c250ebb50a73916efdcb5 + languageName: node + linkType: hard + +"react-dom@npm:^19.1.0": + version: 19.2.3 + resolution: "react-dom@npm:19.2.3" + dependencies: + scheduler: "npm:^0.27.0" + peerDependencies: + react: ^19.2.3 + checksum: 10c0/dc43f7ede06f46f3acc16ee83107c925530de9b91d1d0b3824583814746ff4c498ea64fd65cd83aba363205268adff52e2827c582634ae7b15069deaeabc4892 + languageName: node + linkType: hard + +"react-is@npm:^16.13.1, react-is@npm:^16.7.0": + version: 16.13.1 + resolution: "react-is@npm:16.13.1" + checksum: 10c0/33977da7a5f1a287936a0c85639fec6ca74f4f15ef1e59a6bc20338fc73dc69555381e211f7a3529b8150a1f71e4225525b41b60b52965bda53ce7d47377ada1 + languageName: node + linkType: hard + +"react-is@npm:^19.2.0": + version: 19.2.3 + resolution: "react-is@npm:19.2.3" + checksum: 10c0/2b54c422c21b8dbd68a435a1cce21ecd5b6f06f48659531f7d53dd7368365da5a67e946f352fb2010d11ca40658aa67bec90995f0f1ec5556c0f71dbffe54994 + languageName: node + linkType: hard + +"react-qr-code@npm:^2.0.15": + version: 2.0.18 + resolution: "react-qr-code@npm:2.0.18" + dependencies: + prop-types: "npm:^15.8.1" + qr.js: "npm:0.0.0" + peerDependencies: + react: "*" + checksum: 10c0/4e13b795cbb10f1dcf0e39d682bb59851e4c84010ba2be7225b2ad9d5c1ffea52d2d38f884ee26235b7002b8ca99e83b805f55e877663c39d67496764d975cf1 + languageName: node + linkType: hard + +"react-redux@npm:^9.2.0": + version: 9.2.0 + resolution: "react-redux@npm:9.2.0" + dependencies: + "@types/use-sync-external-store": "npm:^0.0.6" + use-sync-external-store: "npm:^1.4.0" + peerDependencies: + "@types/react": ^18.2.25 || ^19 + react: ^18.0 || ^19 + redux: ^5.0.0 + peerDependenciesMeta: + "@types/react": + optional: true + redux: + optional: true + checksum: 10c0/00d485f9d9219ca1507b4d30dde5f6ff8fb68ba642458f742e0ec83af052f89e65cd668249b99299e1053cc6ad3d2d8ac6cb89e2f70d2ac5585ae0d7fa0ef259 + languageName: node + linkType: hard + +"react-refresh@npm:^0.17.0": + version: 0.17.0 + resolution: "react-refresh@npm:0.17.0" + checksum: 10c0/002cba940384c9930008c0bce26cac97a9d5682bc623112c2268ba0c155127d9c178a9a5cc2212d560088d60dfd503edd808669a25f9b377f316a32361d0b23c + languageName: node + linkType: hard + +"react-router-dom@npm:^7.6.1": + version: 7.11.0 + resolution: "react-router-dom@npm:7.11.0" + dependencies: + react-router: "npm:7.11.0" + peerDependencies: + react: ">=18" + react-dom: ">=18" + checksum: 10c0/0e8061fe0ef7915cc411dd92f5f41109f6343b6abef36571b08ff231365bf61f52364bea128d1c964e9b8eb19426c9bd21923df0b3e1bb993d21bd2b7440fb49 + languageName: node + linkType: hard + +"react-router@npm:7.11.0": + version: 7.11.0 + resolution: "react-router@npm:7.11.0" + dependencies: + cookie: "npm:^1.0.1" + set-cookie-parser: "npm:^2.6.0" + peerDependencies: + react: ">=18" + react-dom: ">=18" + peerDependenciesMeta: + react-dom: + optional: true + checksum: 10c0/eb3693d63d1c52221a3449de5db170e2fa9e00536b011998b17f8a277f8b5e89b752d104dbbeb4ee3d474f8e4570167db00293b4510f63277e5e6658c5dab22b + languageName: node + linkType: hard + +"react-transition-group@npm:^4.4.5": + version: 4.4.5 + resolution: "react-transition-group@npm:4.4.5" + dependencies: + "@babel/runtime": "npm:^7.5.5" + dom-helpers: "npm:^5.0.1" + loose-envify: "npm:^1.4.0" + prop-types: "npm:^15.6.2" + peerDependencies: + react: ">=16.6.0" + react-dom: ">=16.6.0" + checksum: 10c0/2ba754ba748faefa15f87c96dfa700d5525054a0141de8c75763aae6734af0740e77e11261a1e8f4ffc08fd9ab78510122e05c21c2d79066c38bb6861a886c82 + languageName: node + linkType: hard + +"react@npm:^19.1.0": + version: 19.2.3 + resolution: "react@npm:19.2.3" + checksum: 10c0/094220b3ba3a76c1b668f972ace1dd15509b157aead1b40391d1c8e657e720c201d9719537375eff08f5e0514748c0319063392a6f000e31303aafc4471f1436 + languageName: node + linkType: hard + +"readable-stream@npm:^2.3.5, readable-stream@npm:~2.3.6": + version: 2.3.8 + resolution: "readable-stream@npm:2.3.8" + dependencies: + core-util-is: "npm:~1.0.0" + inherits: "npm:~2.0.3" + isarray: "npm:~1.0.0" + process-nextick-args: "npm:~2.0.0" + safe-buffer: "npm:~5.1.1" + string_decoder: "npm:~1.1.1" + util-deprecate: "npm:~1.0.1" + checksum: 10c0/7efdb01f3853bc35ac62ea25493567bf588773213f5f4a79f9c365e1ad13bab845ac0dae7bc946270dc40c3929483228415e92a3fc600cc7e4548992f41ee3fa + languageName: node + linkType: hard + +"receptacle@npm:^1.3.2": + version: 1.3.2 + resolution: "receptacle@npm:1.3.2" + dependencies: + ms: "npm:^2.1.1" + checksum: 10c0/213dc9e4e80969cde60c5877fae08d8438f0bf7dd10bf4ea47916a10c053ca05d6581bda374d8f22ce15e6b50739efe319d847362f5ec9e1a4cbdcbde3ddf355 + languageName: node + linkType: hard + +"redux-persist@npm:^6.0.0": + version: 6.0.0 + resolution: "redux-persist@npm:6.0.0" + peerDependencies: + redux: ">4.0.0" + checksum: 10c0/8242d265ab8d28bbc95cf2dc2a05b869eb67aa309b1ed08163c926f3af56dd8eb1ea62118286083461b8ef2024d3b349fd264e5a62a70eb2e74d068c832d5bf2 + languageName: node + linkType: hard + +"redux-thunk@npm:^3.1.0": + version: 3.1.0 + resolution: "redux-thunk@npm:3.1.0" + peerDependencies: + redux: ^5.0.0 + checksum: 10c0/21557f6a30e1b2e3e470933247e51749be7f1d5a9620069a3125778675ce4d178d84bdee3e2a0903427a5c429e3aeec6d4df57897faf93eb83455bc1ef7b66fd + languageName: node + linkType: hard + +"redux@npm:^4.0.0": + version: 4.2.1 + resolution: "redux@npm:4.2.1" + dependencies: + "@babel/runtime": "npm:^7.9.2" + checksum: 10c0/136d98b3d5dbed1cd6279c8c18a6a74c416db98b8a432a46836bdd668475de6279a2d4fd9d1363f63904e00f0678a8a3e7fa532c897163340baf1e71bb42c742 + languageName: node + linkType: hard + +"redux@npm:^5.0.1": + version: 5.0.1 + resolution: "redux@npm:5.0.1" + checksum: 10c0/b10c28357194f38e7d53b760ed5e64faa317cc63de1fb95bc5d9e127fab956392344368c357b8e7a9bedb0c35b111e7efa522210cfdc3b3c75e5074718e9069c + languageName: node + linkType: hard + +"reflect.getprototypeof@npm:^1.0.6, reflect.getprototypeof@npm:^1.0.9": + version: 1.0.10 + resolution: "reflect.getprototypeof@npm:1.0.10" + dependencies: + call-bind: "npm:^1.0.8" + define-properties: "npm:^1.2.1" + es-abstract: "npm:^1.23.9" + es-errors: "npm:^1.3.0" + es-object-atoms: "npm:^1.0.0" + get-intrinsic: "npm:^1.2.7" + get-proto: "npm:^1.0.1" + which-builtin-type: "npm:^1.2.1" + checksum: 10c0/7facec28c8008876f8ab98e80b7b9cb4b1e9224353fd4756dda5f2a4ab0d30fa0a5074777c6df24e1e0af463a2697513b0a11e548d99cf52f21f7bc6ba48d3ac + languageName: node + linkType: hard + +"regexp.prototype.flags@npm:^1.5.3, regexp.prototype.flags@npm:^1.5.4": + version: 1.5.4 + resolution: "regexp.prototype.flags@npm:1.5.4" + dependencies: + call-bind: "npm:^1.0.8" + define-properties: "npm:^1.2.1" + es-errors: "npm:^1.3.0" + get-proto: "npm:^1.0.1" + gopd: "npm:^1.2.0" + set-function-name: "npm:^2.0.2" + checksum: 10c0/83b88e6115b4af1c537f8dabf5c3744032cb875d63bc05c288b1b8c0ef37cbe55353f95d8ca817e8843806e3e150b118bc624e4279b24b4776b4198232735a77 + languageName: node + linkType: hard + +"reselect@npm:^5.1.0, reselect@npm:^5.1.1": + version: 5.1.1 + resolution: "reselect@npm:5.1.1" + checksum: 10c0/219c30da122980f61853db3aebd173524a2accd4b3baec770e3d51941426c87648a125ca08d8c57daa6b8b086f2fdd2703cb035dd6231db98cdbe1176a71f489 + languageName: node + linkType: hard + +"resolve-from@npm:^4.0.0": + version: 4.0.0 + resolution: "resolve-from@npm:4.0.0" + checksum: 10c0/8408eec31a3112ef96e3746c37be7d64020cda07c03a920f5024e77290a218ea758b26ca9529fd7b1ad283947f34b2291c1c0f6aa0ed34acfdda9c6014c8d190 + languageName: node + linkType: hard + +"resolve@npm:^1.19.0, resolve@npm:^1.22.4": + version: 1.22.11 + resolution: "resolve@npm:1.22.11" + dependencies: + is-core-module: "npm:^2.16.1" + path-parse: "npm:^1.0.7" + supports-preserve-symlinks-flag: "npm:^1.0.0" + bin: + resolve: bin/resolve + checksum: 10c0/f657191507530f2cbecb5815b1ee99b20741ea6ee02a59c57028e9ec4c2c8d7681afcc35febbd554ac0ded459db6f2d8153382c53a2f266cee2575e512674409 + languageName: node + linkType: hard + +"resolve@npm:^2.0.0-next.5": + version: 2.0.0-next.5 + resolution: "resolve@npm:2.0.0-next.5" + dependencies: + is-core-module: "npm:^2.13.0" + path-parse: "npm:^1.0.7" + supports-preserve-symlinks-flag: "npm:^1.0.0" + bin: + resolve: bin/resolve + checksum: 10c0/a6c33555e3482ea2ec4c6e3d3bf0d78128abf69dca99ae468e64f1e30acaa318fd267fb66c8836b04d558d3e2d6ed875fe388067e7d8e0de647d3c21af21c43a + languageName: node + linkType: hard + +"resolve@patch:resolve@npm%3A^1.19.0#optional!builtin, resolve@patch:resolve@npm%3A^1.22.4#optional!builtin": + version: 1.22.11 + resolution: "resolve@patch:resolve@npm%3A1.22.11#optional!builtin::version=1.22.11&hash=c3c19d" + dependencies: + is-core-module: "npm:^2.16.1" + path-parse: "npm:^1.0.7" + supports-preserve-symlinks-flag: "npm:^1.0.0" + bin: + resolve: bin/resolve + checksum: 10c0/ee5b182f2e37cb1165465e58c6abc797fec0a80b5ba3231607beb4677db0c9291ac010c47cf092b6daa2b7f518d69a0e21888e7e2b633f68d501a874212a8c63 + languageName: node + linkType: hard + +"resolve@patch:resolve@npm%3A^2.0.0-next.5#optional!builtin": + version: 2.0.0-next.5 + resolution: "resolve@patch:resolve@npm%3A2.0.0-next.5#optional!builtin::version=2.0.0-next.5&hash=c3c19d" + dependencies: + is-core-module: "npm:^2.13.0" + path-parse: "npm:^1.0.7" + supports-preserve-symlinks-flag: "npm:^1.0.0" + bin: + resolve: bin/resolve + checksum: 10c0/78ad6edb8309a2bfb720c2c1898f7907a37f858866ce11a5974643af1203a6a6e05b2fa9c53d8064a673a447b83d42569260c306d43628bff5bb101969708355 + languageName: node + linkType: hard + +"retry@npm:^0.12.0": + version: 0.12.0 + resolution: "retry@npm:0.12.0" + checksum: 10c0/59933e8501727ba13ad73ef4a04d5280b3717fd650408460c987392efe9d7be2040778ed8ebe933c5cbd63da3dcc37919c141ef8af0a54a6e4fca5a2af177bfe + languageName: node + linkType: hard + +"rn-host-detect@npm:^1.2.0": + version: 1.2.0 + resolution: "rn-host-detect@npm:1.2.0" + checksum: 10c0/94067014566c738b3cb786d507a3cf4f4fab79b51b0d7c38bea0a48d536458c7218e01f259e03fa51cf1cf6f62ad4c01fcd36c6fa3b725a86eace02dd30d929f + languageName: node + linkType: hard + +"rollup@npm:^4.20.0": + version: 4.54.0 + resolution: "rollup@npm:4.54.0" + dependencies: + "@rollup/rollup-android-arm-eabi": "npm:4.54.0" + "@rollup/rollup-android-arm64": "npm:4.54.0" + "@rollup/rollup-darwin-arm64": "npm:4.54.0" + "@rollup/rollup-darwin-x64": "npm:4.54.0" + "@rollup/rollup-freebsd-arm64": "npm:4.54.0" + "@rollup/rollup-freebsd-x64": "npm:4.54.0" + "@rollup/rollup-linux-arm-gnueabihf": "npm:4.54.0" + "@rollup/rollup-linux-arm-musleabihf": "npm:4.54.0" + "@rollup/rollup-linux-arm64-gnu": "npm:4.54.0" + "@rollup/rollup-linux-arm64-musl": "npm:4.54.0" + "@rollup/rollup-linux-loong64-gnu": "npm:4.54.0" + "@rollup/rollup-linux-ppc64-gnu": "npm:4.54.0" + "@rollup/rollup-linux-riscv64-gnu": "npm:4.54.0" + "@rollup/rollup-linux-riscv64-musl": "npm:4.54.0" + "@rollup/rollup-linux-s390x-gnu": "npm:4.54.0" + "@rollup/rollup-linux-x64-gnu": "npm:4.54.0" + "@rollup/rollup-linux-x64-musl": "npm:4.54.0" + "@rollup/rollup-openharmony-arm64": "npm:4.54.0" + "@rollup/rollup-win32-arm64-msvc": "npm:4.54.0" + "@rollup/rollup-win32-ia32-msvc": "npm:4.54.0" + "@rollup/rollup-win32-x64-gnu": "npm:4.54.0" + "@rollup/rollup-win32-x64-msvc": "npm:4.54.0" + "@types/estree": "npm:1.0.8" + fsevents: "npm:~2.3.2" + dependenciesMeta: + "@rollup/rollup-android-arm-eabi": + optional: true + "@rollup/rollup-android-arm64": + optional: true + "@rollup/rollup-darwin-arm64": + optional: true + "@rollup/rollup-darwin-x64": + optional: true + "@rollup/rollup-freebsd-arm64": + optional: true + "@rollup/rollup-freebsd-x64": + optional: true + "@rollup/rollup-linux-arm-gnueabihf": + optional: true + "@rollup/rollup-linux-arm-musleabihf": + optional: true + "@rollup/rollup-linux-arm64-gnu": + optional: true + "@rollup/rollup-linux-arm64-musl": + optional: true + "@rollup/rollup-linux-loong64-gnu": + optional: true + "@rollup/rollup-linux-ppc64-gnu": + optional: true + "@rollup/rollup-linux-riscv64-gnu": + optional: true + "@rollup/rollup-linux-riscv64-musl": + optional: true + "@rollup/rollup-linux-s390x-gnu": + optional: true + "@rollup/rollup-linux-x64-gnu": + optional: true + "@rollup/rollup-linux-x64-musl": + optional: true + "@rollup/rollup-openharmony-arm64": + optional: true + "@rollup/rollup-win32-arm64-msvc": + optional: true + "@rollup/rollup-win32-ia32-msvc": + optional: true + "@rollup/rollup-win32-x64-gnu": + optional: true + "@rollup/rollup-win32-x64-msvc": + optional: true + fsevents: + optional: true + bin: + rollup: dist/bin/rollup + checksum: 10c0/62e5fd5d43e72751ac631f13fd7e70bec0fc3809231d5e087c3c0811945e7b8f0956620c5bed4e0cd67085325324266989e5ea4d22985c2677119ac7809b6455 + languageName: node + linkType: hard + +"safe-array-concat@npm:^1.1.3": + version: 1.1.3 + resolution: "safe-array-concat@npm:1.1.3" + dependencies: + call-bind: "npm:^1.0.8" + call-bound: "npm:^1.0.2" + get-intrinsic: "npm:^1.2.6" + has-symbols: "npm:^1.1.0" + isarray: "npm:^2.0.5" + checksum: 10c0/43c86ffdddc461fb17ff8a17c5324f392f4868f3c7dd2c6a5d9f5971713bc5fd755667212c80eab9567595f9a7509cc2f83e590ddaebd1bd19b780f9c79f9a8d + languageName: node + linkType: hard + +"safe-buffer@npm:^5.0.1, safe-buffer@npm:^5.1.1": + version: 5.2.1 + resolution: "safe-buffer@npm:5.2.1" + checksum: 10c0/6501914237c0a86e9675d4e51d89ca3c21ffd6a31642efeba25ad65720bce6921c9e7e974e5be91a786b25aa058b5303285d3c15dbabf983a919f5f630d349f3 + languageName: node + linkType: hard + +"safe-buffer@npm:~5.1.0, safe-buffer@npm:~5.1.1": + version: 5.1.2 + resolution: "safe-buffer@npm:5.1.2" + checksum: 10c0/780ba6b5d99cc9a40f7b951d47152297d0e260f0df01472a1b99d4889679a4b94a13d644f7dbc4f022572f09ae9005fa2fbb93bbbd83643316f365a3e9a45b21 + languageName: node + linkType: hard + +"safe-push-apply@npm:^1.0.0": + version: 1.0.0 + resolution: "safe-push-apply@npm:1.0.0" + dependencies: + es-errors: "npm:^1.3.0" + isarray: "npm:^2.0.5" + checksum: 10c0/831f1c9aae7436429e7862c7e46f847dfe490afac20d0ee61bae06108dbf5c745a0de3568ada30ccdd3eeb0864ca8331b2eef703abd69bfea0745b21fd320750 + languageName: node + linkType: hard + +"safe-regex-test@npm:^1.1.0": + version: 1.1.0 + resolution: "safe-regex-test@npm:1.1.0" + dependencies: + call-bound: "npm:^1.0.2" + es-errors: "npm:^1.3.0" + is-regex: "npm:^1.2.1" + checksum: 10c0/f2c25281bbe5d39cddbbce7f86fca5ea9b3ce3354ea6cd7c81c31b006a5a9fff4286acc5450a3b9122c56c33eba69c56b9131ad751457b2b4a585825e6a10665 + languageName: node + linkType: hard + +"safer-buffer@npm:>= 2.1.2 < 3.0.0": + version: 2.1.2 + resolution: "safer-buffer@npm:2.1.2" + checksum: 10c0/7e3c8b2e88a1841c9671094bbaeebd94448111dd90a81a1f606f3f67708a6ec57763b3b47f06da09fc6054193e0e6709e77325415dc8422b04497a8070fa02d4 + languageName: node + linkType: hard + +"sc-errors@npm:^3.0.0": + version: 3.0.0 + resolution: "sc-errors@npm:3.0.0" + checksum: 10c0/d015423572cf0b68a6ddb5a9088cb7509da013cdb304691128443f6240e095b3de0e126e0d8e6c5fce14dc0973ad1c3f6ef9c77f4667599f2e99e5377c72a133 + languageName: node + linkType: hard + +"sc-formatter@npm:^4.0.0": + version: 4.0.0 + resolution: "sc-formatter@npm:4.0.0" + checksum: 10c0/1ef2e29bbfcd4ee17c9bbeaaa766169b8e0d210cecac6bcfd71341306438948536b59e060e1edd28ddee1ede1108c3ac1e1789813349d15aab677a5f64ea7cc9 + languageName: node + linkType: hard + +"scheduler@npm:^0.27.0": + version: 0.27.0 + resolution: "scheduler@npm:0.27.0" + checksum: 10c0/4f03048cb05a3c8fddc45813052251eca00688f413a3cee236d984a161da28db28ba71bd11e7a3dd02f7af84ab28d39fb311431d3b3772fed557945beb00c452 + languageName: node + linkType: hard + +"semver@npm:^6.3.1": + version: 6.3.1 + resolution: "semver@npm:6.3.1" + bin: + semver: bin/semver.js + checksum: 10c0/e3d79b609071caa78bcb6ce2ad81c7966a46a7431d9d58b8800cfa9cb6a63699b3899a0e4bcce36167a284578212d9ae6942b6929ba4aa5015c079a67751d42d + languageName: node + linkType: hard + +"semver@npm:^7.3.5, semver@npm:^7.5.4, semver@npm:^7.6.0, semver@npm:^7.6.2": + version: 7.7.3 + resolution: "semver@npm:7.7.3" + bin: + semver: bin/semver.js + checksum: 10c0/4afe5c986567db82f44c8c6faef8fe9df2a9b1d98098fc1721f57c696c4c21cebd572f297fc21002f81889492345b8470473bc6f4aff5fb032a6ea59ea2bc45e + languageName: node + linkType: hard + +"set-cookie-parser@npm:^2.6.0": + version: 2.7.2 + resolution: "set-cookie-parser@npm:2.7.2" + checksum: 10c0/4381a9eb7ee951dfe393fe7aacf76b9a3b4e93a684d2162ab35594fa4053cc82a4d7d7582bf397718012c9adcf839b8cd8f57c6c42901ea9effe33c752da4a45 + languageName: node + linkType: hard + +"set-function-length@npm:^1.2.2": + version: 1.2.2 + resolution: "set-function-length@npm:1.2.2" + dependencies: + define-data-property: "npm:^1.1.4" + es-errors: "npm:^1.3.0" + function-bind: "npm:^1.1.2" + get-intrinsic: "npm:^1.2.4" + gopd: "npm:^1.0.1" + has-property-descriptors: "npm:^1.0.2" + checksum: 10c0/82850e62f412a258b71e123d4ed3873fa9377c216809551192bb6769329340176f109c2eeae8c22a8d386c76739855f78e8716515c818bcaef384b51110f0f3c + languageName: node + linkType: hard + +"set-function-name@npm:^2.0.2": + version: 2.0.2 + resolution: "set-function-name@npm:2.0.2" + dependencies: + define-data-property: "npm:^1.1.4" + es-errors: "npm:^1.3.0" + functions-have-names: "npm:^1.2.3" + has-property-descriptors: "npm:^1.0.2" + checksum: 10c0/fce59f90696c450a8523e754abb305e2b8c73586452619c2bad5f7bf38c7b6b4651895c9db895679c5bef9554339cf3ef1c329b66ece3eda7255785fbe299316 + languageName: node + linkType: hard + +"set-proto@npm:^1.0.0": + version: 1.0.0 + resolution: "set-proto@npm:1.0.0" + dependencies: + dunder-proto: "npm:^1.0.1" + es-errors: "npm:^1.3.0" + es-object-atoms: "npm:^1.0.0" + checksum: 10c0/ca5c3ccbba479d07c30460e367e66337cec825560b11e8ba9c5ebe13a2a0d6021ae34eddf94ff3dfe17a3104dc1f191519cb6c48378b503e5c3f36393938776a + languageName: node + linkType: hard + +"shallow-clone@npm:^3.0.0": + version: 3.0.1 + resolution: "shallow-clone@npm:3.0.1" + dependencies: + kind-of: "npm:^6.0.2" + checksum: 10c0/7bab09613a1b9f480c85a9823aebec533015579fa055ba6634aa56ba1f984380670eaf33b8217502931872aa1401c9fcadaa15f9f604d631536df475b05bcf1e + languageName: node + linkType: hard + +"shebang-command@npm:^2.0.0": + version: 2.0.0 + resolution: "shebang-command@npm:2.0.0" + dependencies: + shebang-regex: "npm:^3.0.0" + checksum: 10c0/a41692e7d89a553ef21d324a5cceb5f686d1f3c040759c50aab69688634688c5c327f26f3ecf7001ebfd78c01f3c7c0a11a7c8bfd0a8bc9f6240d4f40b224e4e + languageName: node + linkType: hard + +"shebang-regex@npm:^3.0.0": + version: 3.0.0 + resolution: "shebang-regex@npm:3.0.0" + checksum: 10c0/1dbed0726dd0e1152a92696c76c7f06084eb32a90f0528d11acd764043aacf76994b2fb30aa1291a21bd019d6699164d048286309a278855ee7bec06cf6fb690 + languageName: node + linkType: hard + +"side-channel-list@npm:^1.0.0": + version: 1.0.0 + resolution: "side-channel-list@npm:1.0.0" + dependencies: + es-errors: "npm:^1.3.0" + object-inspect: "npm:^1.13.3" + checksum: 10c0/644f4ac893456c9490ff388bf78aea9d333d5e5bfc64cfb84be8f04bf31ddc111a8d4b83b85d7e7e8a7b845bc185a9ad02c052d20e086983cf59f0be517d9b3d + languageName: node + linkType: hard + +"side-channel-map@npm:^1.0.1": + version: 1.0.1 + resolution: "side-channel-map@npm:1.0.1" + dependencies: + call-bound: "npm:^1.0.2" + es-errors: "npm:^1.3.0" + get-intrinsic: "npm:^1.2.5" + object-inspect: "npm:^1.13.3" + checksum: 10c0/010584e6444dd8a20b85bc926d934424bd809e1a3af941cace229f7fdcb751aada0fb7164f60c2e22292b7fa3c0ff0bce237081fd4cdbc80de1dc68e95430672 + languageName: node + linkType: hard + +"side-channel-weakmap@npm:^1.0.2": + version: 1.0.2 + resolution: "side-channel-weakmap@npm:1.0.2" + dependencies: + call-bound: "npm:^1.0.2" + es-errors: "npm:^1.3.0" + get-intrinsic: "npm:^1.2.5" + object-inspect: "npm:^1.13.3" + side-channel-map: "npm:^1.0.1" + checksum: 10c0/71362709ac233e08807ccd980101c3e2d7efe849edc51455030327b059f6c4d292c237f94dc0685031dd11c07dd17a68afde235d6cf2102d949567f98ab58185 + languageName: node + linkType: hard + +"side-channel@npm:^1.1.0": + version: 1.1.0 + resolution: "side-channel@npm:1.1.0" + dependencies: + es-errors: "npm:^1.3.0" + object-inspect: "npm:^1.13.3" + side-channel-list: "npm:^1.0.0" + side-channel-map: "npm:^1.0.1" + side-channel-weakmap: "npm:^1.0.2" + checksum: 10c0/cb20dad41eb032e6c24c0982e1e5a24963a28aa6122b4f05b3f3d6bf8ae7fd5474ef382c8f54a6a3ab86e0cac4d41a23bd64ede3970e5bfb50326ba02a7996e6 + languageName: node + linkType: hard + +"siginfo@npm:^2.0.0": + version: 2.0.0 + resolution: "siginfo@npm:2.0.0" + checksum: 10c0/3def8f8e516fbb34cb6ae415b07ccc5d9c018d85b4b8611e3dc6f8be6d1899f693a4382913c9ed51a06babb5201639d76453ab297d1c54a456544acf5c892e34 + languageName: node + linkType: hard + +"signal-exit@npm:^3.0.3": + version: 3.0.7 + resolution: "signal-exit@npm:3.0.7" + checksum: 10c0/25d272fa73e146048565e08f3309d5b942c1979a6f4a58a8c59d5fa299728e9c2fcd1a759ec870863b1fd38653670240cd420dad2ad9330c71f36608a6a1c912 + languageName: node + linkType: hard + +"smart-buffer@npm:^4.2.0": + version: 4.2.0 + resolution: "smart-buffer@npm:4.2.0" + checksum: 10c0/a16775323e1404dd43fabafe7460be13a471e021637bc7889468eb45ce6a6b207261f454e4e530a19500cc962c4cc5348583520843b363f4193cee5c00e1e539 + languageName: node + linkType: hard + +"socketcluster-client@npm:^19.2.3": + version: 19.2.7 + resolution: "socketcluster-client@npm:19.2.7" + dependencies: + ag-auth: "npm:^2.1.0" + ag-channel: "npm:^5.0.0" + ag-request: "npm:^1.1.0" + async-stream-emitter: "npm:^7.0.1" + buffer: "npm:^5.2.1" + clone-deep: "npm:^4.0.1" + linked-list: "npm:^2.1.0" + sc-errors: "npm:^3.0.0" + sc-formatter: "npm:^4.0.0" + stream-demux: "npm:^10.0.1" + uuid: "npm:^8.3.2" + vinyl-buffer: "npm:^1.0.1" + ws: "npm:^8.18.0" + checksum: 10c0/58a333f2ac376043ea7bf38c42a473042258000e9ea8e56eec3d2ff3b6e85c04d8f710b2bd1f79436878bc802358259e3aa1dcc22241a1d7ae2600288fc75412 + languageName: node + linkType: hard + +"socks-proxy-agent@npm:^8.0.3": + version: 8.0.5 + resolution: "socks-proxy-agent@npm:8.0.5" + dependencies: + agent-base: "npm:^7.1.2" + debug: "npm:^4.3.4" + socks: "npm:^2.8.3" + checksum: 10c0/5d2c6cecba6821389aabf18728325730504bf9bb1d9e342e7987a5d13badd7a98838cc9a55b8ed3cb866ad37cc23e1086f09c4d72d93105ce9dfe76330e9d2a6 + languageName: node + linkType: hard + +"socks@npm:^2.8.3": + version: 2.8.7 + resolution: "socks@npm:2.8.7" + dependencies: + ip-address: "npm:^10.0.1" + smart-buffer: "npm:^4.2.0" + checksum: 10c0/2805a43a1c4bcf9ebf6e018268d87b32b32b06fbbc1f9282573583acc155860dc361500f89c73bfbb157caa1b4ac78059eac0ef15d1811eb0ca75e0bdadbc9d2 + languageName: node + linkType: hard + +"source-map-js@npm:^1.2.1": + version: 1.2.1 + resolution: "source-map-js@npm:1.2.1" + checksum: 10c0/7bda1fc4c197e3c6ff17de1b8b2c20e60af81b63a52cb32ec5a5d67a20a7d42651e2cb34ebe93833c5a2a084377e17455854fee3e21e7925c64a51b6a52b0faf + languageName: node + linkType: hard + +"source-map@npm:^0.5.7": + version: 0.5.7 + resolution: "source-map@npm:0.5.7" + checksum: 10c0/904e767bb9c494929be013017380cbba013637da1b28e5943b566031e29df04fba57edf3f093e0914be094648b577372bd8ad247fa98cfba9c600794cd16b599 + languageName: node + linkType: hard + +"ssri@npm:^13.0.0": + version: 13.0.0 + resolution: "ssri@npm:13.0.0" + dependencies: + minipass: "npm:^7.0.3" + checksum: 10c0/405f3a531cd98b013cecb355d63555dca42fd12c7bc6671738aaa9a82882ff41cdf0ef9a2b734ca4f9a760338f114c29d01d9238a65db3ccac27929bd6e6d4b2 + languageName: node + linkType: hard + +"stackback@npm:0.0.2": + version: 0.0.2 + resolution: "stackback@npm:0.0.2" + checksum: 10c0/89a1416668f950236dd5ac9f9a6b2588e1b9b62b1b6ad8dff1bfc5d1a15dbf0aafc9b52d2226d00c28dffff212da464eaeebfc6b7578b9d180cef3e3782c5983 + languageName: node + linkType: hard + +"std-env@npm:^3.8.0": + version: 3.10.0 + resolution: "std-env@npm:3.10.0" + checksum: 10c0/1814927a45004d36dde6707eaf17552a546769bc79a6421be2c16ce77d238158dfe5de30910b78ec30d95135cc1c59ea73ee22d2ca170f8b9753f84da34c427f + languageName: node + linkType: hard + +"stop-iteration-iterator@npm:^1.1.0": + version: 1.1.0 + resolution: "stop-iteration-iterator@npm:1.1.0" + dependencies: + es-errors: "npm:^1.3.0" + internal-slot: "npm:^1.1.0" + checksum: 10c0/de4e45706bb4c0354a4b1122a2b8cc45a639e86206807ce0baf390ee9218d3ef181923fa4d2b67443367c491aa255c5fbaa64bb74648e3c5b48299928af86c09 + languageName: node + linkType: hard + +"stream-demux@npm:^10.0.1": + version: 10.0.1 + resolution: "stream-demux@npm:10.0.1" + dependencies: + consumable-stream: "npm:^3.0.0" + writable-consumable-stream: "npm:^4.1.0" + checksum: 10c0/9c6b42af758560ceab49b0fdae169599cfca95fcd6f234b45fa3e1465751c2576ad65fd86e54bf5365fda6152955d06d6e6957c506b51460343c4801260402cf + languageName: node + linkType: hard + +"string.prototype.matchall@npm:^4.0.12": + version: 4.0.12 + resolution: "string.prototype.matchall@npm:4.0.12" + dependencies: + call-bind: "npm:^1.0.8" + call-bound: "npm:^1.0.3" + define-properties: "npm:^1.2.1" + es-abstract: "npm:^1.23.6" + es-errors: "npm:^1.3.0" + es-object-atoms: "npm:^1.0.0" + get-intrinsic: "npm:^1.2.6" + gopd: "npm:^1.2.0" + has-symbols: "npm:^1.1.0" + internal-slot: "npm:^1.1.0" + regexp.prototype.flags: "npm:^1.5.3" + set-function-name: "npm:^2.0.2" + side-channel: "npm:^1.1.0" + checksum: 10c0/1a53328ada73f4a77f1fdf1c79414700cf718d0a8ef6672af5603e709d26a24f2181208144aed7e858b1bcc1a0d08567a570abfb45567db4ae47637ed2c2f85c + languageName: node + linkType: hard + +"string.prototype.repeat@npm:^1.0.0": + version: 1.0.0 + resolution: "string.prototype.repeat@npm:1.0.0" + dependencies: + define-properties: "npm:^1.1.3" + es-abstract: "npm:^1.17.5" + checksum: 10c0/94c7978566cffa1327d470fd924366438af9b04b497c43a9805e476e2e908aa37a1fd34cc0911156c17556dab62159d12c7b92b3cc304c3e1281fe4c8e668f40 + languageName: node + linkType: hard + +"string.prototype.trim@npm:^1.2.10": + version: 1.2.10 + resolution: "string.prototype.trim@npm:1.2.10" + dependencies: + call-bind: "npm:^1.0.8" + call-bound: "npm:^1.0.2" + define-data-property: "npm:^1.1.4" + define-properties: "npm:^1.2.1" + es-abstract: "npm:^1.23.5" + es-object-atoms: "npm:^1.0.0" + has-property-descriptors: "npm:^1.0.2" + checksum: 10c0/8a8854241c4b54a948e992eb7dd6b8b3a97185112deb0037a134f5ba57541d8248dd610c966311887b6c2fd1181a3877bffb14d873ce937a344535dabcc648f8 + languageName: node + linkType: hard + +"string.prototype.trimend@npm:^1.0.9": + version: 1.0.9 + resolution: "string.prototype.trimend@npm:1.0.9" + dependencies: + call-bind: "npm:^1.0.8" + call-bound: "npm:^1.0.2" + define-properties: "npm:^1.2.1" + es-object-atoms: "npm:^1.0.0" + checksum: 10c0/59e1a70bf9414cb4c536a6e31bef5553c8ceb0cf44d8b4d0ed65c9653358d1c64dd0ec203b100df83d0413bbcde38b8c5d49e14bc4b86737d74adc593a0d35b6 + languageName: node + linkType: hard + +"string.prototype.trimstart@npm:^1.0.8": + version: 1.0.8 + resolution: "string.prototype.trimstart@npm:1.0.8" + dependencies: + call-bind: "npm:^1.0.7" + define-properties: "npm:^1.2.1" + es-object-atoms: "npm:^1.0.0" + checksum: 10c0/d53af1899959e53c83b64a5fd120be93e067da740e7e75acb433849aa640782fb6c7d4cd5b84c954c84413745a3764df135a8afeb22908b86a835290788d8366 + languageName: node + linkType: hard + +"string_decoder@npm:~1.1.1": + version: 1.1.1 + resolution: "string_decoder@npm:1.1.1" + dependencies: + safe-buffer: "npm:~5.1.0" + checksum: 10c0/b4f89f3a92fd101b5653ca3c99550e07bdf9e13b35037e9e2a1c7b47cec4e55e06ff3fc468e314a0b5e80bfbaf65c1ca5a84978764884ae9413bec1fc6ca924e + languageName: node + linkType: hard + +"strip-bom@npm:^3.0.0": + version: 3.0.0 + resolution: "strip-bom@npm:3.0.0" + checksum: 10c0/51201f50e021ef16672593d7434ca239441b7b760e905d9f33df6e4f3954ff54ec0e0a06f100d028af0982d6f25c35cd5cda2ce34eaebccd0250b8befb90d8f1 + languageName: node + linkType: hard + +"strip-final-newline@npm:^2.0.0": + version: 2.0.0 + resolution: "strip-final-newline@npm:2.0.0" + checksum: 10c0/bddf8ccd47acd85c0e09ad7375409d81653f645fda13227a9d459642277c253d877b68f2e5e4d819fe75733b0e626bac7e954c04f3236f6d196f79c94fa4a96f + languageName: node + linkType: hard + +"strip-json-comments@npm:^3.1.1": + version: 3.1.1 + resolution: "strip-json-comments@npm:3.1.1" + checksum: 10c0/9681a6257b925a7fa0f285851c0e613cc934a50661fa7bb41ca9cbbff89686bb4a0ee366e6ecedc4daafd01e83eee0720111ab294366fe7c185e935475ebcecd + languageName: node + linkType: hard + +"stylis@npm:4.2.0": + version: 4.2.0 + resolution: "stylis@npm:4.2.0" + checksum: 10c0/a7128ad5a8ed72652c6eba46bed4f416521bc9745a460ef5741edc725252cebf36ee45e33a8615a7057403c93df0866ab9ee955960792db210bb80abd5ac6543 + languageName: node + linkType: hard + +"supports-color@npm:^7.1.0": + version: 7.2.0 + resolution: "supports-color@npm:7.2.0" + dependencies: + has-flag: "npm:^4.0.0" + checksum: 10c0/afb4c88521b8b136b5f5f95160c98dee7243dc79d5432db7efc27efb219385bbc7d9427398e43dd6cc730a0f87d5085ce1652af7efbe391327bc0a7d0f7fc124 + languageName: node + linkType: hard + +"supports-preserve-symlinks-flag@npm:^1.0.0": + version: 1.0.0 + resolution: "supports-preserve-symlinks-flag@npm:1.0.0" + checksum: 10c0/6c4032340701a9950865f7ae8ef38578d8d7053f5e10518076e6554a9381fa91bd9c6850193695c141f32b21f979c985db07265a758867bac95de05f7d8aeb39 + languageName: node + linkType: hard + +"tar@npm:^7.5.2": + version: 7.5.2 + resolution: "tar@npm:7.5.2" + dependencies: + "@isaacs/fs-minipass": "npm:^4.0.0" + chownr: "npm:^3.0.0" + minipass: "npm:^7.1.2" + minizlib: "npm:^3.1.0" + yallist: "npm:^5.0.0" + checksum: 10c0/a7d8b801139b52f93a7e34830db0de54c5aa45487c7cb551f6f3d44a112c67f1cb8ffdae856b05fd4f17b1749911f1c26f1e3a23bbe0279e17fd96077f13f467 + languageName: node + linkType: hard + +"through2@npm:^2.0.3": + version: 2.0.5 + resolution: "through2@npm:2.0.5" + dependencies: + readable-stream: "npm:~2.3.6" + xtend: "npm:~4.0.1" + checksum: 10c0/cbfe5b57943fa12b4f8c043658c2a00476216d79c014895cef1ac7a1d9a8b31f6b438d0e53eecbb81054b93128324a82ecd59ec1a4f91f01f7ac113dcb14eade + languageName: node + linkType: hard + +"tinybench@npm:^2.9.0": + version: 2.9.0 + resolution: "tinybench@npm:2.9.0" + checksum: 10c0/c3500b0f60d2eb8db65250afe750b66d51623057ee88720b7f064894a6cb7eb93360ca824a60a31ab16dab30c7b1f06efe0795b352e37914a9d4bad86386a20c + languageName: node + linkType: hard + +"tinyexec@npm:^0.3.1": + version: 0.3.2 + resolution: "tinyexec@npm:0.3.2" + checksum: 10c0/3efbf791a911be0bf0821eab37a3445c2ba07acc1522b1fa84ae1e55f10425076f1290f680286345ed919549ad67527d07281f1c19d584df3b74326909eb1f90 + languageName: node + linkType: hard + +"tinyglobby@npm:^0.2.12, tinyglobby@npm:^0.2.15": + version: 0.2.15 + resolution: "tinyglobby@npm:0.2.15" + dependencies: + fdir: "npm:^6.5.0" + picomatch: "npm:^4.0.3" + checksum: 10c0/869c31490d0d88eedb8305d178d4c75e7463e820df5a9b9d388291daf93e8b1eb5de1dad1c1e139767e4269fe75f3b10d5009b2cc14db96ff98986920a186844 + languageName: node + linkType: hard + +"tinypool@npm:^1.0.1": + version: 1.1.1 + resolution: "tinypool@npm:1.1.1" + checksum: 10c0/bf26727d01443061b04fa863f571016950888ea994ba0cd8cba3a1c51e2458d84574341ab8dbc3664f1c3ab20885c8cf9ff1cc4b18201f04c2cde7d317fff69b + languageName: node + linkType: hard + +"tinyrainbow@npm:^1.2.0": + version: 1.2.0 + resolution: "tinyrainbow@npm:1.2.0" + checksum: 10c0/7f78a4b997e5ba0f5ecb75e7ed786f30bab9063716e7dff24dd84013fb338802e43d176cb21ed12480561f5649a82184cf31efb296601a29d38145b1cdb4c192 + languageName: node + linkType: hard + +"tinyspy@npm:^3.0.2": + version: 3.0.2 + resolution: "tinyspy@npm:3.0.2" + checksum: 10c0/55ffad24e346622b59292e097c2ee30a63919d5acb7ceca87fc0d1c223090089890587b426e20054733f97a58f20af2c349fb7cc193697203868ab7ba00bcea0 + languageName: node + linkType: hard + +"ts-api-utils@npm:^2.1.0": + version: 2.3.0 + resolution: "ts-api-utils@npm:2.3.0" + peerDependencies: + typescript: ">=4.8.4" + checksum: 10c0/9f2aadb8ac55926c79db03e37ee3b014135923d1705f6868b9e787e6b8822d2fd8e19df2f9002563f4e6268c994425ddaad61df24d0dad833a4be9f26f789213 + languageName: node + linkType: hard + +"tsconfck@npm:^3.0.3": + version: 3.1.6 + resolution: "tsconfck@npm:3.1.6" + peerDependencies: + typescript: ^5.0.0 + peerDependenciesMeta: + typescript: + optional: true + bin: + tsconfck: bin/tsconfck.js + checksum: 10c0/269c3c513540be44844117bb9b9258fe6f8aeab026d32aeebf458d5299125f330711429dbb556dbf125a0bc25f4a81e6c24ac96de2740badd295c3fb400f66c4 + languageName: node + linkType: hard + +"tsconfig-paths@npm:^3.15.0": + version: 3.15.0 + resolution: "tsconfig-paths@npm:3.15.0" + dependencies: + "@types/json5": "npm:^0.0.29" + json5: "npm:^1.0.2" + minimist: "npm:^1.2.6" + strip-bom: "npm:^3.0.0" + checksum: 10c0/5b4f301a2b7a3766a986baf8fc0e177eb80bdba6e396792ff92dc23b5bca8bb279fc96517dcaaef63a3b49bebc6c4c833653ec58155780bc906bdbcf7dda0ef5 + languageName: node + linkType: hard + +"type-check@npm:^0.4.0, type-check@npm:~0.4.0": + version: 0.4.0 + resolution: "type-check@npm:0.4.0" + dependencies: + prelude-ls: "npm:^1.2.1" + checksum: 10c0/7b3fd0ed43891e2080bf0c5c504b418fbb3e5c7b9708d3d015037ba2e6323a28152ec163bcb65212741fa5d2022e3075ac3c76440dbd344c9035f818e8ecee58 + languageName: node + linkType: hard + +"typed-array-buffer@npm:^1.0.3": + version: 1.0.3 + resolution: "typed-array-buffer@npm:1.0.3" + dependencies: + call-bound: "npm:^1.0.3" + es-errors: "npm:^1.3.0" + is-typed-array: "npm:^1.1.14" + checksum: 10c0/1105071756eb248774bc71646bfe45b682efcad93b55532c6ffa4518969fb6241354e4aa62af679ae83899ec296d69ef88f1f3763657cdb3a4d29321f7b83079 + languageName: node + linkType: hard + +"typed-array-byte-length@npm:^1.0.3": + version: 1.0.3 + resolution: "typed-array-byte-length@npm:1.0.3" + dependencies: + call-bind: "npm:^1.0.8" + for-each: "npm:^0.3.3" + gopd: "npm:^1.2.0" + has-proto: "npm:^1.2.0" + is-typed-array: "npm:^1.1.14" + checksum: 10c0/6ae083c6f0354f1fce18b90b243343b9982affd8d839c57bbd2c174a5d5dc71be9eb7019ffd12628a96a4815e7afa85d718d6f1e758615151d5f35df841ffb3e + languageName: node + linkType: hard + +"typed-array-byte-offset@npm:^1.0.4": + version: 1.0.4 + resolution: "typed-array-byte-offset@npm:1.0.4" + dependencies: + available-typed-arrays: "npm:^1.0.7" + call-bind: "npm:^1.0.8" + for-each: "npm:^0.3.3" + gopd: "npm:^1.2.0" + has-proto: "npm:^1.2.0" + is-typed-array: "npm:^1.1.15" + reflect.getprototypeof: "npm:^1.0.9" + checksum: 10c0/3d805b050c0c33b51719ee52de17c1cd8e6a571abdf0fffb110e45e8dd87a657e8b56eee94b776b13006d3d347a0c18a730b903cf05293ab6d92e99ff8f77e53 + languageName: node + linkType: hard + +"typed-array-length@npm:^1.0.7": + version: 1.0.7 + resolution: "typed-array-length@npm:1.0.7" + dependencies: + call-bind: "npm:^1.0.7" + for-each: "npm:^0.3.3" + gopd: "npm:^1.0.1" + is-typed-array: "npm:^1.1.13" + possible-typed-array-names: "npm:^1.0.0" + reflect.getprototypeof: "npm:^1.0.6" + checksum: 10c0/e38f2ae3779584c138a2d8adfa8ecf749f494af3cd3cdafe4e688ce51418c7d2c5c88df1bd6be2bbea099c3f7cea58c02ca02ed438119e91f162a9de23f61295 + languageName: node + linkType: hard + +"typescript-eslint@npm:^8.1.0": + version: 8.50.1 + resolution: "typescript-eslint@npm:8.50.1" + dependencies: + "@typescript-eslint/eslint-plugin": "npm:8.50.1" + "@typescript-eslint/parser": "npm:8.50.1" + "@typescript-eslint/typescript-estree": "npm:8.50.1" + "@typescript-eslint/utils": "npm:8.50.1" + peerDependencies: + eslint: ^8.57.0 || ^9.0.0 + typescript: ">=4.8.4 <6.0.0" + checksum: 10c0/481095a249c48fa1d3551c50ceb8dcfba22413d6175f586ee802200342478a24b566b49d59e618c835631e4071ba1902d8549dc6467f47adb3079d00394d614f + languageName: node + linkType: hard + +"typescript@npm:^5.2.2": + version: 5.9.3 + resolution: "typescript@npm:5.9.3" + bin: + tsc: bin/tsc + tsserver: bin/tsserver + checksum: 10c0/6bd7552ce39f97e711db5aa048f6f9995b53f1c52f7d8667c1abdc1700c68a76a308f579cd309ce6b53646deb4e9a1be7c813a93baaf0a28ccd536a30270e1c5 + languageName: node + linkType: hard + +"typescript@patch:typescript@npm%3A^5.2.2#optional!builtin": + version: 5.9.3 + resolution: "typescript@patch:typescript@npm%3A5.9.3#optional!builtin::version=5.9.3&hash=5786d5" + bin: + tsc: bin/tsc + tsserver: bin/tsserver + checksum: 10c0/ad09fdf7a756814dce65bc60c1657b40d44451346858eea230e10f2e95a289d9183b6e32e5c11e95acc0ccc214b4f36289dcad4bf1886b0adb84d711d336a430 + languageName: node + linkType: hard + +"uint8arrays@npm:^3.0.0": + version: 3.1.1 + resolution: "uint8arrays@npm:3.1.1" + dependencies: + multiformats: "npm:^9.4.2" + checksum: 10c0/9946668e04f29b46bbb73cca3d190f63a2fbfe5452f8e6551ef4257d9d597b72da48fa895c15ef2ef772808a5335b3305f69da5f13a09f8c2924896b409565ff + languageName: node + linkType: hard + +"unbox-primitive@npm:^1.1.0": + version: 1.1.0 + resolution: "unbox-primitive@npm:1.1.0" + dependencies: + call-bound: "npm:^1.0.3" + has-bigints: "npm:^1.0.2" + has-symbols: "npm:^1.1.0" + which-boxed-primitive: "npm:^1.1.1" + checksum: 10c0/7dbd35ab02b0e05fe07136c72cb9355091242455473ec15057c11430129bab38b7b3624019b8778d02a881c13de44d63cd02d122ee782fb519e1de7775b5b982 + languageName: node + linkType: hard + +"undici-types@npm:~6.21.0": + version: 6.21.0 + resolution: "undici-types@npm:6.21.0" + checksum: 10c0/c01ed51829b10aa72fc3ce64b747f8e74ae9b60eafa19a7b46ef624403508a54c526ffab06a14a26b3120d055e1104d7abe7c9017e83ced038ea5cf52f8d5e04 + languageName: node + linkType: hard + +"undici-types@npm:~7.16.0": + version: 7.16.0 + resolution: "undici-types@npm:7.16.0" + checksum: 10c0/3033e2f2b5c9f1504bdc5934646cb54e37ecaca0f9249c983f7b1fc2e87c6d18399ebb05dc7fd5419e02b2e915f734d872a65da2e3eeed1813951c427d33cc9a + languageName: node + linkType: hard + +"unique-filename@npm:^5.0.0": + version: 5.0.0 + resolution: "unique-filename@npm:5.0.0" + dependencies: + unique-slug: "npm:^6.0.0" + checksum: 10c0/afb897e9cf4c2fb622ea716f7c2bb462001928fc5f437972213afdf1cc32101a230c0f1e9d96fc91ee5185eca0f2feb34127145874975f347be52eb91d6ccc2c + languageName: node + linkType: hard + +"unique-slug@npm:^6.0.0": + version: 6.0.0 + resolution: "unique-slug@npm:6.0.0" + dependencies: + imurmurhash: "npm:^0.1.4" + checksum: 10c0/da7ade4cb04eb33ad0499861f82fe95ce9c7c878b7139dc54d140ecfb6a6541c18a5c8dac16188b8b379fe62c0c1f1b710814baac910cde5f4fec06212126c6a + languageName: node + linkType: hard + +"unstoppableswap-gui-rs@workspace:.": + version: 0.0.0-use.local + resolution: "unstoppableswap-gui-rs@workspace:." + dependencies: + "@emotion/react": "npm:^11.14.0" + "@emotion/styled": "npm:^11.14.0" + "@eslint/js": "npm:^9.9.0" + "@fontsource/roboto": "npm:^5.1.0" + "@mui/icons-material": "npm:^7.1.1" + "@mui/material": "npm:^7.1.1" + "@mui/x-date-pickers": "npm:^8.8.0" + "@redux-devtools/remote": "npm:^0.9.5" + "@reduxjs/toolkit": "npm:^2.3.0" + "@tauri-apps/api": "npm:^2.8.0" + "@tauri-apps/cli": "npm:^2.0.0" + "@tauri-apps/plugin-cli": "npm:^2.4.0" + "@tauri-apps/plugin-clipboard-manager": "npm:^2.3.0" + "@tauri-apps/plugin-dialog": "npm:^2.0.0" + "@tauri-apps/plugin-opener": "npm:^2.5.0" + "@tauri-apps/plugin-process": "npm:^2.3.0" + "@tauri-apps/plugin-store": "npm:^2.4.0" + "@tauri-apps/plugin-updater": "npm:^2.9.0" + "@testing-library/react": "npm:^16.0.1" + "@testing-library/user-event": "npm:^14.5.2" + "@types/humanize-duration": "npm:^3.27.4" + "@types/lodash": "npm:^4.17.6" + "@types/node": "npm:^22.15.29" + "@types/react": "npm:^19.1.6" + "@types/react-dom": "npm:^19.1.5" + "@types/react-redux": "npm:^7.1.34" + "@types/semver": "npm:^7.5.8" + "@vitejs/plugin-react": "npm:^4.2.1" + babel-plugin-react-compiler: "npm:^1.0.0" + dayjs: "npm:^1.11.13" + eslint: "npm:^9.9.0" + eslint-plugin-import: "npm:^2.32.0" + eslint-plugin-react: "npm:^7.35.0" + eslint-plugin-react-hooks: "npm:^7.0.1" + globals: "npm:^15.9.0" + humanize-duration: "npm:^3.32.1" + internal-ip: "npm:^7.0.0" + jdenticon: "npm:^3.3.0" + lodash: "npm:^4.17.21" + multiaddr: "npm:^10.0.1" + notistack: "npm:^3.0.1" + react: "npm:^19.1.0" + react-dom: "npm:^19.1.0" + react-qr-code: "npm:^2.0.15" + react-redux: "npm:^9.2.0" + react-router-dom: "npm:^7.6.1" + redux-persist: "npm:^6.0.0" + semver: "npm:^7.6.2" + typescript: "npm:^5.2.2" + typescript-eslint: "npm:^8.1.0" + virtua: "npm:^0.33.2" + vite: "npm:^5.3.1" + vite-plugin-top-level-await: "npm:^1.4.4" + vite-plugin-watch: "npm:^0.3.1" + vite-tsconfig-paths: "npm:^4.3.2" + vitest: "npm:^2.1.1" + languageName: unknown + linkType: soft + +"update-browserslist-db@npm:^1.2.0": + version: 1.2.3 + resolution: "update-browserslist-db@npm:1.2.3" + dependencies: + escalade: "npm:^3.2.0" + picocolors: "npm:^1.1.1" + peerDependencies: + browserslist: ">= 4.21.0" + bin: + update-browserslist-db: cli.js + checksum: 10c0/13a00355ea822388f68af57410ce3255941d5fb9b7c49342c4709a07c9f230bbef7f7499ae0ca7e0de532e79a82cc0c4edbd125f1a323a1845bf914efddf8bec + languageName: node + linkType: hard + +"uri-js@npm:^4.2.2": + version: 4.4.1 + resolution: "uri-js@npm:4.4.1" + dependencies: + punycode: "npm:^2.1.0" + checksum: 10c0/4ef57b45aa820d7ac6496e9208559986c665e49447cb072744c13b66925a362d96dd5a46c4530a6b8e203e5db5fe849369444440cb22ecfc26c679359e5dfa3c + languageName: node + linkType: hard + +"use-sync-external-store@npm:^1.4.0, use-sync-external-store@npm:^1.6.0": + version: 1.6.0 + resolution: "use-sync-external-store@npm:1.6.0" + peerDependencies: + react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 + checksum: 10c0/35e1179f872a53227bdf8a827f7911da4c37c0f4091c29b76b1e32473d1670ebe7bcd880b808b7549ba9a5605c233350f800ffab963ee4a4ee346ee983b6019b + languageName: node + linkType: hard + +"util-deprecate@npm:~1.0.1": + version: 1.0.2 + resolution: "util-deprecate@npm:1.0.2" + checksum: 10c0/41a5bdd214df2f6c3ecf8622745e4a366c4adced864bc3c833739791aeeeb1838119af7daed4ba36428114b5c67dcda034a79c882e97e43c03e66a4dd7389942 + languageName: node + linkType: hard + +"uuid@npm:10.0.0": + version: 10.0.0 + resolution: "uuid@npm:10.0.0" + bin: + uuid: dist/bin/uuid + checksum: 10c0/eab18c27fe4ab9fb9709a5d5f40119b45f2ec8314f8d4cf12ce27e4c6f4ffa4a6321dc7db6c515068fa373c075b49691ba969f0010bf37f44c37ca40cd6bf7fe + languageName: node + linkType: hard + +"uuid@npm:^8.3.2": + version: 8.3.2 + resolution: "uuid@npm:8.3.2" + bin: + uuid: dist/bin/uuid + checksum: 10c0/bcbb807a917d374a49f475fae2e87fdca7da5e5530820ef53f65ba1d12131bd81a92ecf259cc7ce317cbe0f289e7d79fdfebcef9bfa3087c8c8a2fa304c9be54 + languageName: node + linkType: hard + +"varint@npm:^6.0.0": + version: 6.0.0 + resolution: "varint@npm:6.0.0" + checksum: 10c0/737fc37088a62ed3bd21466e318d21ca7ac4991d0f25546f518f017703be4ed0f9df1c5559f1dd533dddba4435a1b758fd9230e4772c1a930ef72b42f5c750fd + languageName: node + linkType: hard + +"vinyl-buffer@npm:^1.0.1": + version: 1.0.1 + resolution: "vinyl-buffer@npm:1.0.1" + dependencies: + bl: "npm:^1.2.1" + through2: "npm:^2.0.3" + checksum: 10c0/0dedb6bd3dbdd33ef77feae6535284d9fcd65be4826cb15c3afa91e77a0d384ff5c91a02768a96e6914671db5b65133c69ff44dd25b399494d2628dc71de259b + languageName: node + linkType: hard + +"virtua@npm:^0.33.2": + version: 0.33.7 + resolution: "virtua@npm:0.33.7" + peerDependencies: + react: ">=16.14.0" + react-dom: ">=16.14.0" + solid-js: ">=1.0" + svelte: ">=4.0" + vue: ">=3.2" + peerDependenciesMeta: + react: + optional: true + react-dom: + optional: true + solid-js: + optional: true + svelte: + optional: true + vue: + optional: true + checksum: 10c0/bc42cd6fc5119c18f5bc83c9c887e40120e4cb4bbf4abf499cc2543ce790b2d23868815975dd472643b07da5804cc5e11919ac0026fc26f540608793aa3eefba + languageName: node + linkType: hard + +"vite-node@npm:2.1.9": + version: 2.1.9 + resolution: "vite-node@npm:2.1.9" + dependencies: + cac: "npm:^6.7.14" + debug: "npm:^4.3.7" + es-module-lexer: "npm:^1.5.4" + pathe: "npm:^1.1.2" + vite: "npm:^5.0.0" + bin: + vite-node: vite-node.mjs + checksum: 10c0/0d3589f9f4e9cff696b5b49681fdb75d1638c75053728be52b4013f70792f38cb0120a9c15e3a4b22bdd6b795ad7c2da13bcaf47242d439f0906049e73bdd756 + languageName: node + linkType: hard + +"vite-plugin-top-level-await@npm:^1.4.4": + version: 1.6.0 + resolution: "vite-plugin-top-level-await@npm:1.6.0" + dependencies: + "@rollup/plugin-virtual": "npm:^3.0.2" + "@swc/core": "npm:^1.12.14" + "@swc/wasm": "npm:^1.12.14" + uuid: "npm:10.0.0" + peerDependencies: + vite: ">=2.8" + checksum: 10c0/499054e67f38ce925b1bae270cc334e1ee42194d00cae084792a5a00c7f246f27f3fd7798e0d4cfe07510f1c7620288bcbcf463570e7650f57977ac0591bf8da + languageName: node + linkType: hard + +"vite-plugin-watch@npm:^0.3.1": + version: 0.3.1 + resolution: "vite-plugin-watch@npm:0.3.1" + dependencies: + minimatch: "npm:^5.1.1" + checksum: 10c0/a14998cb9d678b5e1b92d68af0e40164d7fb413a4b112edfe44f831bda701a46f44e4253d612bd8101933a55364e6448ee3c7899616c57b0b0477812ebbffa4e + languageName: node + linkType: hard + +"vite-tsconfig-paths@npm:^4.3.2": + version: 4.3.2 + resolution: "vite-tsconfig-paths@npm:4.3.2" + dependencies: + debug: "npm:^4.1.1" + globrex: "npm:^0.1.2" + tsconfck: "npm:^3.0.3" + peerDependencies: + vite: "*" + peerDependenciesMeta: + vite: + optional: true + checksum: 10c0/f390ac1d1c3992fc5ac50f9274c1090f8b55ab34a89ea88893db9a6924a3b26c9f64bc1163615150ad100749db73b6b2cf1d57f6cd60df6e762ceb5b8ad30024 + languageName: node + linkType: hard + +"vite@npm:^5.0.0, vite@npm:^5.3.1": + version: 5.4.21 + resolution: "vite@npm:5.4.21" + dependencies: + esbuild: "npm:^0.21.3" + fsevents: "npm:~2.3.3" + postcss: "npm:^8.4.43" + rollup: "npm:^4.20.0" + peerDependencies: + "@types/node": ^18.0.0 || >=20.0.0 + less: "*" + lightningcss: ^1.21.0 + sass: "*" + sass-embedded: "*" + stylus: "*" + sugarss: "*" + terser: ^5.4.0 + dependenciesMeta: + fsevents: + optional: true + peerDependenciesMeta: + "@types/node": + optional: true + less: + optional: true + lightningcss: + optional: true + sass: + optional: true + sass-embedded: + optional: true + stylus: + optional: true + sugarss: + optional: true + terser: + optional: true + bin: + vite: bin/vite.js + checksum: 10c0/468336a1409f728b464160cbf02672e72271fb688d0e605e776b74a89d27e1029509eef3a3a6c755928d8011e474dbf234824d054d07960be5f23cd176bc72de + languageName: node + linkType: hard + +"vitest@npm:^2.1.1": + version: 2.1.9 + resolution: "vitest@npm:2.1.9" + dependencies: + "@vitest/expect": "npm:2.1.9" + "@vitest/mocker": "npm:2.1.9" + "@vitest/pretty-format": "npm:^2.1.9" + "@vitest/runner": "npm:2.1.9" + "@vitest/snapshot": "npm:2.1.9" + "@vitest/spy": "npm:2.1.9" + "@vitest/utils": "npm:2.1.9" + chai: "npm:^5.1.2" + debug: "npm:^4.3.7" + expect-type: "npm:^1.1.0" + magic-string: "npm:^0.30.12" + pathe: "npm:^1.1.2" + std-env: "npm:^3.8.0" + tinybench: "npm:^2.9.0" + tinyexec: "npm:^0.3.1" + tinypool: "npm:^1.0.1" + tinyrainbow: "npm:^1.2.0" + vite: "npm:^5.0.0" + vite-node: "npm:2.1.9" + why-is-node-running: "npm:^2.3.0" + peerDependencies: + "@edge-runtime/vm": "*" + "@types/node": ^18.0.0 || >=20.0.0 + "@vitest/browser": 2.1.9 + "@vitest/ui": 2.1.9 + happy-dom: "*" + jsdom: "*" + peerDependenciesMeta: + "@edge-runtime/vm": + optional: true + "@types/node": + optional: true + "@vitest/browser": + optional: true + "@vitest/ui": + optional: true + happy-dom: + optional: true + jsdom: + optional: true + bin: + vitest: vitest.mjs + checksum: 10c0/e339e16dccacf4589ff43cb1f38c7b4d14427956ae8ef48702af6820a9842347c2b6c77356aeddb040329759ca508a3cb2b104ddf78103ea5bc98ab8f2c3a54e + languageName: node + linkType: hard + +"which-boxed-primitive@npm:^1.1.0, which-boxed-primitive@npm:^1.1.1": + version: 1.1.1 + resolution: "which-boxed-primitive@npm:1.1.1" + dependencies: + is-bigint: "npm:^1.1.0" + is-boolean-object: "npm:^1.2.1" + is-number-object: "npm:^1.1.1" + is-string: "npm:^1.1.1" + is-symbol: "npm:^1.1.1" + checksum: 10c0/aceea8ede3b08dede7dce168f3883323f7c62272b49801716e8332ff750e7ae59a511ae088840bc6874f16c1b7fd296c05c949b0e5b357bfe3c431b98c417abe + languageName: node + linkType: hard + +"which-builtin-type@npm:^1.2.1": + version: 1.2.1 + resolution: "which-builtin-type@npm:1.2.1" + dependencies: + call-bound: "npm:^1.0.2" + function.prototype.name: "npm:^1.1.6" + has-tostringtag: "npm:^1.0.2" + is-async-function: "npm:^2.0.0" + is-date-object: "npm:^1.1.0" + is-finalizationregistry: "npm:^1.1.0" + is-generator-function: "npm:^1.0.10" + is-regex: "npm:^1.2.1" + is-weakref: "npm:^1.0.2" + isarray: "npm:^2.0.5" + which-boxed-primitive: "npm:^1.1.0" + which-collection: "npm:^1.0.2" + which-typed-array: "npm:^1.1.16" + checksum: 10c0/8dcf323c45e5c27887800df42fbe0431d0b66b1163849bb7d46b5a730ad6a96ee8bfe827d078303f825537844ebf20c02459de41239a0a9805e2fcb3cae0d471 + languageName: node + linkType: hard + +"which-collection@npm:^1.0.2": + version: 1.0.2 + resolution: "which-collection@npm:1.0.2" + dependencies: + is-map: "npm:^2.0.3" + is-set: "npm:^2.0.3" + is-weakmap: "npm:^2.0.2" + is-weakset: "npm:^2.0.3" + checksum: 10c0/3345fde20964525a04cdf7c4a96821f85f0cc198f1b2ecb4576e08096746d129eb133571998fe121c77782ac8f21cbd67745a3d35ce100d26d4e684c142ea1f2 + languageName: node + linkType: hard + +"which-typed-array@npm:^1.1.16, which-typed-array@npm:^1.1.19": + version: 1.1.19 + resolution: "which-typed-array@npm:1.1.19" + dependencies: + available-typed-arrays: "npm:^1.0.7" + call-bind: "npm:^1.0.8" + call-bound: "npm:^1.0.4" + for-each: "npm:^0.3.5" + get-proto: "npm:^1.0.1" + gopd: "npm:^1.2.0" + has-tostringtag: "npm:^1.0.2" + checksum: 10c0/702b5dc878addafe6c6300c3d0af5983b175c75fcb4f2a72dfc3dd38d93cf9e89581e4b29c854b16ea37e50a7d7fca5ae42ece5c273d8060dcd603b2404bbb3f + languageName: node + linkType: hard + +"which@npm:^2.0.1": + version: 2.0.2 + resolution: "which@npm:2.0.2" + dependencies: + isexe: "npm:^2.0.0" + bin: + node-which: ./bin/node-which + checksum: 10c0/66522872a768b60c2a65a57e8ad184e5372f5b6a9ca6d5f033d4b0dc98aff63995655a7503b9c0a2598936f532120e81dd8cc155e2e92ed662a2b9377cc4374f + languageName: node + linkType: hard + +"which@npm:^6.0.0": + version: 6.0.0 + resolution: "which@npm:6.0.0" + dependencies: + isexe: "npm:^3.1.1" + bin: + node-which: bin/which.js + checksum: 10c0/fe9d6463fe44a76232bb6e3b3181922c87510a5b250a98f1e43a69c99c079b3f42ddeca7e03d3e5f2241bf2d334f5a7657cfa868b97c109f3870625842f4cc15 + languageName: node + linkType: hard + +"why-is-node-running@npm:^2.3.0": + version: 2.3.0 + resolution: "why-is-node-running@npm:2.3.0" + dependencies: + siginfo: "npm:^2.0.0" + stackback: "npm:0.0.2" + bin: + why-is-node-running: cli.js + checksum: 10c0/1cde0b01b827d2cf4cb11db962f3958b9175d5d9e7ac7361d1a7b0e2dc6069a263e69118bd974c4f6d0a890ef4eedfe34cf3d5167ec14203dbc9a18620537054 + languageName: node + linkType: hard + +"word-wrap@npm:^1.2.5": + version: 1.2.5 + resolution: "word-wrap@npm:1.2.5" + checksum: 10c0/e0e4a1ca27599c92a6ca4c32260e8a92e8a44f4ef6ef93f803f8ed823f486e0889fc0b93be4db59c8d51b3064951d25e43d434e95dc8c960cc3a63d65d00ba20 + languageName: node + linkType: hard + +"writable-consumable-stream@npm:^4.1.0": + version: 4.2.0 + resolution: "writable-consumable-stream@npm:4.2.0" + dependencies: + consumable-stream: "npm:^3.0.0" + checksum: 10c0/d674d68c52025f988fa18c717e8ea56b3fd10c54fcb04c9716ad1ca45dbaaaf0d027b3b1e6e0e67d495d267ffc6b95ccf37cb62d15daefeec0c942bdae2f616b + languageName: node + linkType: hard + +"ws@npm:^8.18.0": + version: 8.18.3 + resolution: "ws@npm:8.18.3" + peerDependencies: + bufferutil: ^4.0.1 + utf-8-validate: ">=5.0.2" + peerDependenciesMeta: + bufferutil: + optional: true + utf-8-validate: + optional: true + checksum: 10c0/eac918213de265ef7cb3d4ca348b891a51a520d839aa51cdb8ca93d4fa7ff9f6ccb339ccee89e4075324097f0a55157c89fa3f7147bde9d8d7e90335dc087b53 + languageName: node + linkType: hard + +"xtend@npm:~4.0.1": + version: 4.0.2 + resolution: "xtend@npm:4.0.2" + checksum: 10c0/366ae4783eec6100f8a02dff02ac907bf29f9a00b82ac0264b4d8b832ead18306797e283cf19de776538babfdcb2101375ec5646b59f08c52128ac4ab812ed0e + languageName: node + linkType: hard + +"yallist@npm:^3.0.2": + version: 3.1.1 + resolution: "yallist@npm:3.1.1" + checksum: 10c0/c66a5c46bc89af1625476f7f0f2ec3653c1a1791d2f9407cfb4c2ba812a1e1c9941416d71ba9719876530e3340a99925f697142989371b72d93b9ee628afd8c1 + languageName: node + linkType: hard + +"yallist@npm:^4.0.0": + version: 4.0.0 + resolution: "yallist@npm:4.0.0" + checksum: 10c0/2286b5e8dbfe22204ab66e2ef5cc9bbb1e55dfc873bbe0d568aa943eb255d131890dfd5bf243637273d31119b870f49c18fcde2c6ffbb7a7a092b870dc90625a + languageName: node + linkType: hard + +"yallist@npm:^5.0.0": + version: 5.0.0 + resolution: "yallist@npm:5.0.0" + checksum: 10c0/a499c81ce6d4a1d260d4ea0f6d49ab4da09681e32c3f0472dee16667ed69d01dae63a3b81745a24bd78476ec4fcf856114cb4896ace738e01da34b2c42235416 + languageName: node + linkType: hard + +"yaml@npm:^1.10.0": + version: 1.10.2 + resolution: "yaml@npm:1.10.2" + checksum: 10c0/5c28b9eb7adc46544f28d9a8d20c5b3cb1215a886609a2fd41f51628d8aaa5878ccd628b755dbcd29f6bb4921bd04ffbc6dcc370689bb96e594e2f9813d2605f + languageName: node + linkType: hard + +"yocto-queue@npm:^0.1.0": + version: 0.1.0 + resolution: "yocto-queue@npm:0.1.0" + checksum: 10c0/dceb44c28578b31641e13695d200d34ec4ab3966a5729814d5445b194933c096b7ced71494ce53a0e8820685d1d010df8b2422e5bf2cdea7e469d97ffbea306f + languageName: node + linkType: hard + +"zod-validation-error@npm:^3.5.0 || ^4.0.0": + version: 4.0.2 + resolution: "zod-validation-error@npm:4.0.2" + peerDependencies: + zod: ^3.25.0 || ^4.0.0 + checksum: 10c0/0ccfec48c46de1be440b719cd02044d4abb89ed0e14c13e637cd55bf29102f67ccdba373f25def0fc7130e5f15025be4d557a7edcc95d5a3811599aade689e1b + languageName: node + linkType: hard + +"zod@npm:^3.25.0 || ^4.0.0": + version: 4.2.1 + resolution: "zod@npm:4.2.1" + checksum: 10c0/ecb5219bddf76a42d092a843fb98ad4cb78f1e1077082772b03ef032ee5cbc80790a4051836b962d26fb4af854323bc784d628bd1b8d9898149eba7af21c5560 + languageName: node + linkType: hard From ae09bb1e50856d2510160b79553cd5d65561a96a Mon Sep 17 00:00:00 2001 From: einliterflasche Date: Mon, 26 Jan 2026 12:19:05 +0100 Subject: [PATCH 100/113] state-machine(alice+bob): rename states to match new transaction names Rename the states as follows: - RemainingRefundTimelock -> ReclaimTimelock - RefundBurn -> Withhold - RefundAmnesty -> Reclaim - FinalAmnesty -> Mercy --- swap-db/src/alice.rs | 91 ++++++++----------- swap-db/src/bob.rs | 76 +++++++--------- swap-machine/src/bob/mod.rs | 54 +++++------ swap/src/cli/cancel_and_refund.rs | 36 ++++---- swap/src/protocol/bob/swap.rs | 42 ++++----- swap/tests/harness/mod.rs | 19 ++-- ...rtial_refund_alice_grants_final_amnesty.rs | 6 +- 7 files changed, 150 insertions(+), 174 deletions(-) diff --git a/swap-db/src/alice.rs b/swap-db/src/alice.rs index e3cc963c35..7462068f0a 100644 --- a/swap-db/src/alice.rs +++ b/swap-db/src/alice.rs @@ -89,6 +89,9 @@ pub enum Alice { #[serde(with = "swap_serde::monero::private_key")] spend_key: monero::PrivateKey, }, + BtcWithholdPublished { + state3: alice::State3, + }, Done(AliceEndState), } @@ -107,19 +110,16 @@ pub enum AliceEndState { state3: alice::State3, transfer_proof: TransferProof, }, - BtcRefundBurnPublished { + BtcWithheld { state3: alice::State3, }, - BtcRefundBurnConfirmed { + BtcMercyGranted { state3: alice::State3, }, - BtcFinalAmnestyGranted { + BtcMercyPublished { state3: alice::State3, }, - BtcRefundFinalAmnestyPublished { - state3: alice::State3, - }, - BtcRefundFinalAmnestyConfirmed { + BtcMercyConfirmed { state3: alice::State3, }, } @@ -127,15 +127,11 @@ pub enum AliceEndState { impl From for Alice { fn from(alice_state: AliceState) -> Self { match alice_state { - AliceState::Started { state3 } => Alice::Started { - state3: *state3, - }, - AliceState::BtcLockTransactionSeen { state3 } => Alice::BtcLockTransactionSeen { - state3: *state3, - }, - AliceState::BtcLocked { state3 } => Alice::BtcLocked { - state3: *state3, - }, + AliceState::Started { state3 } => Alice::Started { state3: *state3 }, + AliceState::BtcLockTransactionSeen { state3 } => { + Alice::BtcLockTransactionSeen { state3: *state3 } + } + AliceState::BtcLocked { state3 } => Alice::BtcLocked { state3: *state3 }, AliceState::XmrLockTransactionSent { monero_wallet_restore_blockheight, transfer_proof, @@ -224,12 +220,12 @@ impl From for Alice { state3: *state3, spend_key, }, - AliceState::BtcEarlyRefundable { state3 } => Alice::BtcEarlyRefundable { - state3: *state3, - }, - AliceState::BtcEarlyRefunded(state3) => Alice::Done(AliceEndState::BtcEarlyRefunded { - state3: *state3, - }), + AliceState::BtcEarlyRefundable { state3 } => { + Alice::BtcEarlyRefundable { state3: *state3 } + } + AliceState::BtcEarlyRefunded(state3) => { + Alice::Done(AliceEndState::BtcEarlyRefunded { state3: *state3 }) + } AliceState::BtcPunishable { monero_wallet_restore_blockheight, transfer_proof, @@ -243,29 +239,19 @@ impl From for Alice { state3: state3.map(|s| s.as_ref().clone()), }), AliceState::BtcRefundBurnPublished { state3 } => { - Alice::Done(AliceEndState::BtcRefundBurnPublished { - state3: *state3, - }) + Alice::BtcWithholdPublished { state3: *state3 } } AliceState::BtcRefundBurnConfirmed { state3 } => { - Alice::Done(AliceEndState::BtcRefundBurnConfirmed { - state3: *state3, - }) + Alice::Done(AliceEndState::BtcWithheld { state3: *state3 }) } AliceState::BtcFinalAmnestyGranted { state3 } => { - Alice::Done(AliceEndState::BtcFinalAmnestyGranted { - state3: *state3, - }) + Alice::Done(AliceEndState::BtcMercyGranted { state3: *state3 }) } AliceState::BtcRefundFinalAmnestyPublished { state3 } => { - Alice::Done(AliceEndState::BtcRefundFinalAmnestyPublished { - state3: *state3, - }) + Alice::Done(AliceEndState::BtcMercyPublished { state3: *state3 }) } AliceState::BtcRefundFinalAmnestyConfirmed { state3 } => { - Alice::Done(AliceEndState::BtcRefundFinalAmnestyConfirmed { - state3: *state3, - }) + Alice::Done(AliceEndState::BtcMercyConfirmed { state3: *state3 }) } AliceState::WaitingForCancelTimelockExpiration { monero_wallet_restore_blockheight, @@ -426,6 +412,9 @@ impl From for AliceState { spend_key, state3: Box::new(state3), }, + Alice::BtcWithholdPublished { state3 } => AliceState::BtcRefundBurnPublished { + state3: Box::new(state3), + }, Alice::Done(end_state) => match end_state { AliceEndState::SafelyAborted => AliceState::SafelyAborted, AliceEndState::BtcRedeemed => AliceState::BtcRedeemed, @@ -442,27 +431,18 @@ impl From for AliceState { AliceEndState::BtcEarlyRefunded { state3 } => { AliceState::BtcEarlyRefunded(Box::new(state3)) } - AliceEndState::BtcRefundBurnPublished { state3 } => { - AliceState::BtcRefundBurnPublished { - state3: Box::new(state3), - } - } - AliceEndState::BtcRefundBurnConfirmed { state3 } => { - AliceState::BtcRefundBurnConfirmed { - state3: Box::new(state3), - } - } - AliceEndState::BtcFinalAmnestyGranted { state3 } => { - AliceState::BtcFinalAmnestyGranted { - state3: Box::new(state3), - } - } - AliceEndState::BtcRefundFinalAmnestyPublished { state3 } => { + AliceEndState::BtcWithheld { state3 } => AliceState::BtcRefundBurnConfirmed { + state3: Box::new(state3), + }, + AliceEndState::BtcMercyGranted { state3 } => AliceState::BtcFinalAmnestyGranted { + state3: Box::new(state3), + }, + AliceEndState::BtcMercyPublished { state3 } => { AliceState::BtcRefundFinalAmnestyPublished { state3: Box::new(state3), } } - AliceEndState::BtcRefundFinalAmnestyConfirmed { state3 } => { + AliceEndState::BtcMercyConfirmed { state3 } => { AliceState::BtcRefundFinalAmnestyConfirmed { state3: Box::new(state3), } @@ -499,6 +479,9 @@ impl fmt::Display for Alice { Alice::BtcPartiallyRefunded { .. } => f.write_str("Monero refundable"), Alice::BtcEarlyRefundable { .. } => f.write_str("Bitcoin early refundable"), Alice::XmrRefundable { .. } => f.write_str("Bitcoin early refundable"), + Alice::BtcWithholdPublished { .. } => { + f.write_str("Bitcoin withhold transaction published") + } Alice::Done(end_state) => write!(f, "Done: {}", end_state), } } diff --git a/swap-db/src/bob.rs b/swap-db/src/bob.rs index 362cd011b8..1c382afb87 100644 --- a/swap-db/src/bob.rs +++ b/swap-db/src/bob.rs @@ -55,12 +55,12 @@ pub enum Bob { BtcEarlyRefundPublished(bob::State6), BtcPartialRefundPublished(bob::State6), BtcPartiallyRefunded(bob::State6), - BtcAmnestyPublished(bob::State6), - WaitingForRemainingRefundTimelockExpiration(bob::State6), - RemainingRefundTimelockExpired(bob::State6), - BtcRefundBurnPublished(bob::State6), - BtcRefundBurnt(bob::State6), - BtcFinalAmnestyPublished(bob::State6), + WaitingForReclaimTimelockExpiration(bob::State6), + ReclaimTimelockExpired(bob::State6), + BtcReclaimPublished(bob::State6), + BtcWithholdPublished(bob::State6), + BtcWithheld(bob::State6), + BtcMercyPublished(bob::State6), Done(BobEndState), } @@ -70,8 +70,8 @@ pub enum BobEndState { XmrRedeemed { tx_lock_id: bitcoin::Txid }, BtcRefunded(Box), BtcEarlyRefunded(Box), - BtcAmnestyConfirmed(Box), - BtcFinalAmnestyConfirmed(Box), + BtcReclaimConfirmed(Box), + BtcMercyConfirmed(Box), } impl From for Bob { @@ -145,21 +145,19 @@ impl From for Bob { Bob::Done(BobEndState::BtcEarlyRefunded(Box::new(state6))) } BobState::BtcPartiallyRefunded(state6) => Bob::BtcPartiallyRefunded(state6), - BobState::BtcAmnestyPublished(state6) => Bob::BtcAmnestyPublished(state6), - BobState::BtcAmnestyConfirmed(state6) => { - Bob::Done(BobEndState::BtcAmnestyConfirmed(Box::new(state6))) + BobState::BtcReclaimPublished(state6) => Bob::BtcReclaimPublished(state6), + BobState::BtcReclaimConfirmed(state6) => { + Bob::Done(BobEndState::BtcReclaimConfirmed(Box::new(state6))) } - BobState::WaitingForRemainingRefundTimelockExpiration(state6) => { - Bob::WaitingForRemainingRefundTimelockExpiration(state6) + BobState::WaitingForReclaimTimelockExpiration(state6) => { + Bob::WaitingForReclaimTimelockExpiration(state6) } - BobState::RemainingRefundTimelockExpired(state6) => { - Bob::RemainingRefundTimelockExpired(state6) - } - BobState::BtcRefundBurnPublished(state6) => Bob::BtcRefundBurnPublished(state6), - BobState::BtcRefundBurnt(state6) => Bob::BtcRefundBurnt(state6), - BobState::BtcFinalAmnestyPublished(state6) => Bob::BtcFinalAmnestyPublished(state6), - BobState::BtcFinalAmnestyConfirmed(state6) => { - Bob::Done(BobEndState::BtcFinalAmnestyConfirmed(Box::new(state6))) + BobState::ReclaimTimelockExpired(state6) => Bob::ReclaimTimelockExpired(state6), + BobState::BtcWithholdPublished(state6) => Bob::BtcWithholdPublished(state6), + BobState::BtcWithheld(state6) => Bob::BtcWithheld(state6), + BobState::BtcMercyPublished(state6) => Bob::BtcMercyPublished(state6), + BobState::BtcMercyConfirmed(state6) => { + Bob::Done(BobEndState::BtcMercyConfirmed(Box::new(state6))) } BobState::SafelyAborted => Bob::Done(BobEndState::SafelyAborted), } @@ -228,27 +226,23 @@ impl From for BobState { Bob::BtcRefundPublished(state6) => BobState::BtcRefundPublished(state6), Bob::BtcPartialRefundPublished(state6) => BobState::BtcPartialRefundPublished(state6), Bob::BtcPartiallyRefunded(state6) => BobState::BtcPartiallyRefunded(state6), - Bob::BtcAmnestyPublished(state6) => BobState::BtcAmnestyPublished(state6), + Bob::BtcReclaimPublished(state6) => BobState::BtcReclaimPublished(state6), Bob::BtcEarlyRefundPublished(state6) => BobState::BtcEarlyRefundPublished(state6), Bob::BtcPunished { state, tx_lock_id } => BobState::BtcPunished { state, tx_lock_id }, - Bob::WaitingForRemainingRefundTimelockExpiration(state6) => { - BobState::WaitingForRemainingRefundTimelockExpiration(state6) - } - Bob::RemainingRefundTimelockExpired(state6) => { - BobState::RemainingRefundTimelockExpired(state6) + Bob::WaitingForReclaimTimelockExpiration(state6) => { + BobState::WaitingForReclaimTimelockExpiration(state6) } - Bob::BtcRefundBurnPublished(state6) => BobState::BtcRefundBurnPublished(state6), - Bob::BtcRefundBurnt(state6) => BobState::BtcRefundBurnt(state6), - Bob::BtcFinalAmnestyPublished(state6) => BobState::BtcFinalAmnestyPublished(state6), + Bob::ReclaimTimelockExpired(state6) => BobState::ReclaimTimelockExpired(state6), + Bob::BtcWithholdPublished(state6) => BobState::BtcWithholdPublished(state6), + Bob::BtcWithheld(state6) => BobState::BtcWithheld(state6), + Bob::BtcMercyPublished(state6) => BobState::BtcMercyPublished(state6), Bob::Done(end_state) => match end_state { BobEndState::SafelyAborted => BobState::SafelyAborted, BobEndState::XmrRedeemed { tx_lock_id } => BobState::XmrRedeemed { tx_lock_id }, BobEndState::BtcRefunded(state6) => BobState::BtcRefunded(*state6), BobEndState::BtcEarlyRefunded(state6) => BobState::BtcEarlyRefunded(*state6), - BobEndState::BtcAmnestyConfirmed(state6) => BobState::BtcAmnestyConfirmed(*state6), - BobEndState::BtcFinalAmnestyConfirmed(state6) => { - BobState::BtcFinalAmnestyConfirmed(*state6) - } + BobEndState::BtcReclaimConfirmed(state6) => BobState::BtcReclaimConfirmed(*state6), + BobEndState::BtcMercyConfirmed(state6) => BobState::BtcMercyConfirmed(*state6), }, } } @@ -281,16 +275,14 @@ impl fmt::Display for Bob { Bob::EncSigSent { .. } => f.write_str("Encrypted signature sent"), Bob::BtcPunished { .. } => f.write_str("Bitcoin punished"), Bob::BtcPartiallyRefunded { .. } => f.write_str("Bitcoin partially refunded"), - Bob::BtcAmnestyPublished { .. } => f.write_str("Bitcoin amnesty published"), - Bob::WaitingForRemainingRefundTimelockExpiration { .. } => { + Bob::BtcReclaimPublished { .. } => f.write_str("Bitcoin amnesty published"), + Bob::WaitingForReclaimTimelockExpiration { .. } => { f.write_str("Waiting for remaining refund timelock to expire") } - Bob::RemainingRefundTimelockExpired { .. } => { - f.write_str("Remaining refund timelock expired") - } - Bob::BtcRefundBurnPublished { .. } => f.write_str("Bitcoin refund burn published"), - Bob::BtcRefundBurnt { .. } => f.write_str("Bitcoin refund burnt"), - Bob::BtcFinalAmnestyPublished { .. } => f.write_str("Bitcoin final amnesty published"), + Bob::ReclaimTimelockExpired { .. } => f.write_str("Remaining refund timelock expired"), + Bob::BtcWithholdPublished { .. } => f.write_str("Bitcoin refund burn published"), + Bob::BtcWithheld { .. } => f.write_str("Bitcoin refund burnt"), + Bob::BtcMercyPublished { .. } => f.write_str("Bitcoin final amnesty published"), } } } diff --git a/swap-machine/src/bob/mod.rs b/swap-machine/src/bob/mod.rs index a4b2743867..87439de93d 100644 --- a/swap-machine/src/bob/mod.rs +++ b/swap-machine/src/bob/mod.rs @@ -78,24 +78,26 @@ pub enum BobState { BtcRefunded(State6), BtcEarlyRefunded(State6), BtcPartiallyRefunded(State6), - BtcAmnestyPublished(State6), - BtcAmnestyConfirmed(State6), /// Waiting for RemainingRefundTimelock to expire after partial refund confirmed. /// During this time, Alice may publish TxRefundBurn. - WaitingForRemainingRefundTimelockExpiration(State6), + WaitingForReclaimTimelockExpiration(State6), /// RemainingRefundTimelock has expired, we can now publish TxRefundAmnesty. - RemainingRefundTimelockExpired(State6), + ReclaimTimelockExpired(State6), /// Alice published TxRefundBurn before we could publish TxRefundAmnesty. - BtcRefundBurnPublished(State6), + BtcWithholdPublished(State6), /// TxRefundBurn has been confirmed. The amnesty output is now burnt. - BtcRefundBurnt(State6), + BtcWithheld(State6), + BtcReclaimPublished(State6), + BtcReclaimConfirmed(State6), /// Alice published TxFinalAmnesty (using our presigned signature) to refund us. - BtcFinalAmnestyPublished(State6), + BtcMercyPublished(State6), /// TxFinalAmnesty has been confirmed. We received the burnt funds back. - BtcFinalAmnestyConfirmed(State6), + BtcMercyConfirmed(State6), XmrRedeemed { tx_lock_id: bitcoin::Txid, }, + /// If we do not refund within `PUNISH_TIMELOCK` blocks of either us or alice publishing + /// [TxCancel], then alice may punish us by forcibly redeeming the BTC. BtcPunished { state: State6, // TODO: This attribute is redundant and unused @@ -181,20 +183,20 @@ impl fmt::Display for BobState { BobState::BtcPunished { .. } => write!(f, "btc is punished"), BobState::BtcEarlyRefunded { .. } => write!(f, "btc is early refunded"), BobState::BtcPartiallyRefunded { .. } => write!(f, "btc is partially refunded"), - BobState::BtcAmnestyPublished { .. } => write!(f, "btc amnesty is published"), - BobState::BtcAmnestyConfirmed { .. } => write!(f, "btc amnesty is confirmed"), - BobState::WaitingForRemainingRefundTimelockExpiration { .. } => { + BobState::BtcReclaimPublished { .. } => write!(f, "btc amnesty is published"), + BobState::BtcReclaimConfirmed { .. } => write!(f, "btc amnesty is confirmed"), + BobState::WaitingForReclaimTimelockExpiration { .. } => { write!(f, "waiting for remaining refund timelock to expire") } - BobState::RemainingRefundTimelockExpired { .. } => { + BobState::ReclaimTimelockExpired { .. } => { write!(f, "remaining refund timelock expired") } - BobState::BtcRefundBurnPublished { .. } => write!(f, "btc refund burn is published"), - BobState::BtcRefundBurnt { .. } => write!(f, "btc refund is burnt"), - BobState::BtcFinalAmnestyPublished { .. } => { + BobState::BtcWithholdPublished { .. } => write!(f, "btc refund burn is published"), + BobState::BtcWithheld { .. } => write!(f, "btc refund is burnt"), + BobState::BtcMercyPublished { .. } => { write!(f, "btc final amnesty is published") } - BobState::BtcFinalAmnestyConfirmed { .. } => { + BobState::BtcMercyConfirmed { .. } => { write!(f, "btc final amnesty is confirmed") } BobState::SafelyAborted => write!(f, "safely aborted"), @@ -240,17 +242,17 @@ impl BobState { | BobState::BtcEarlyRefundPublished(state) | BobState::BtcPartialRefundPublished(state) | BobState::BtcPartiallyRefunded(state) - | BobState::BtcAmnestyPublished(state) - | BobState::BtcAmnestyConfirmed(state) - | BobState::WaitingForRemainingRefundTimelockExpiration(state) - | BobState::RemainingRefundTimelockExpired(state) - | BobState::BtcRefundBurnPublished(state) - | BobState::BtcRefundBurnt(state) - | BobState::BtcFinalAmnestyPublished(state) => { + | BobState::BtcReclaimPublished(state) + | BobState::BtcReclaimConfirmed(state) + | BobState::WaitingForReclaimTimelockExpiration(state) + | BobState::ReclaimTimelockExpired(state) + | BobState::BtcWithholdPublished(state) + | BobState::BtcWithheld(state) + | BobState::BtcMercyPublished(state) => { Some(state.expired_timelock(bitcoin_wallet.as_ref()).await?) } BobState::BtcPunished { .. } => Some(ExpiredTimelocks::Punish), - BobState::BtcFinalAmnestyConfirmed(_) => Some(ExpiredTimelocks::RemainingRefund), + BobState::BtcMercyConfirmed(_) => Some(ExpiredTimelocks::RemainingRefund), BobState::BtcRefunded(_) | BobState::BtcEarlyRefunded { .. } | BobState::BtcRedeemed(_) @@ -264,8 +266,8 @@ pub fn is_complete(state: &BobState) -> bool { state, BobState::BtcRefunded(..) | BobState::BtcEarlyRefunded { .. } - | BobState::BtcAmnestyConfirmed { .. } - | BobState::BtcFinalAmnestyConfirmed { .. } + | BobState::BtcReclaimConfirmed { .. } + | BobState::BtcMercyConfirmed { .. } | BobState::XmrRedeemed { .. } | BobState::SafelyAborted ) diff --git a/swap/src/cli/cancel_and_refund.rs b/swap/src/cli/cancel_and_refund.rs index 58722febcc..252671e529 100644 --- a/swap/src/cli/cancel_and_refund.rs +++ b/swap/src/cli/cancel_and_refund.rs @@ -74,20 +74,20 @@ pub async fn cancel( BobState::BtcEarlyRefundPublished(state6) => state6, BobState::BtcPartialRefundPublished(state6) => state6, BobState::BtcPartiallyRefunded(state6) => state6, - BobState::BtcAmnestyConfirmed(state6) => state6, - BobState::BtcAmnestyPublished(state6) => state6, - BobState::WaitingForRemainingRefundTimelockExpiration(state6) => state6, - BobState::RemainingRefundTimelockExpired(state6) => state6, - BobState::BtcRefundBurnPublished(state6) => state6, - BobState::BtcFinalAmnestyPublished(state6) => state6, + BobState::BtcReclaimConfirmed(state6) => state6, + BobState::BtcReclaimPublished(state6) => state6, + BobState::WaitingForReclaimTimelockExpiration(state6) => state6, + BobState::ReclaimTimelockExpired(state6) => state6, + BobState::BtcWithholdPublished(state6) => state6, + BobState::BtcMercyPublished(state6) => state6, BobState::Started { .. } | BobState::BtcRedeemed(_) | BobState::XmrRedeemed { .. } | BobState::BtcPunished { .. } | BobState::BtcEarlyRefunded { .. } - | BobState::BtcRefundBurnt { .. } - | BobState::BtcFinalAmnestyConfirmed { .. } + | BobState::BtcWithheld { .. } + | BobState::BtcMercyConfirmed { .. } | BobState::SafelyAborted => bail!( "Cannot cancel swap {} because it is in state {} which is not cancellable.", swap_id, @@ -212,20 +212,20 @@ pub async fn refund( BobState::BtcEarlyRefundPublished(state6) => state6, BobState::BtcPartialRefundPublished(state6) => state6, BobState::BtcPartiallyRefunded(state6) => state6, - BobState::BtcAmnestyPublished(state6) => state6, - BobState::BtcAmnestyConfirmed(state6) => state6, - BobState::WaitingForRemainingRefundTimelockExpiration(state6) => state6, - BobState::RemainingRefundTimelockExpired(state6) => state6, - BobState::BtcRefundBurnPublished(state6) => state6, - BobState::BtcFinalAmnestyPublished(state6) => state6, + BobState::BtcReclaimPublished(state6) => state6, + BobState::BtcReclaimConfirmed(state6) => state6, + BobState::WaitingForReclaimTimelockExpiration(state6) => state6, + BobState::ReclaimTimelockExpired(state6) => state6, + BobState::BtcWithholdPublished(state6) => state6, + BobState::BtcMercyPublished(state6) => state6, BobState::Started { .. } | BobState::SwapSetupCompleted(_) | BobState::BtcRedeemed(_) | BobState::BtcEarlyRefunded { .. } | BobState::XmrRedeemed { .. } | BobState::BtcPunished { .. } - | BobState::BtcRefundBurnt { .. } - | BobState::BtcFinalAmnestyConfirmed { .. } + | BobState::BtcWithheld { .. } + | BobState::BtcMercyConfirmed { .. } | BobState::SafelyAborted => bail!( "Cannot refund swap {} because it is in state {} which is not refundable.", swap_id, @@ -308,7 +308,9 @@ pub async fn refund( } Ok(ExpiredTimelocks::RemainingRefund) => { // TODO: Try to publish TxRefundAmnesty here instead of just reporting the state - bail!(bitcoin_publication_err.context("Amnesty timelock has expired. TxRefundAmnesty can be published.")); + bail!(bitcoin_publication_err.context( + "Amnesty timelock has expired. TxRefundAmnesty can be published." + )); } Err(e) => { bail!(bitcoin_publication_err diff --git a/swap/src/protocol/bob/swap.rs b/swap/src/protocol/bob/swap.rs index 576d698b96..5ffefa348f 100644 --- a/swap/src/protocol/bob/swap.rs +++ b/swap/src/protocol/bob/swap.rs @@ -51,7 +51,7 @@ pub fn has_already_processed_transfer_proof(state: &BobState) -> bool { // // The same is true for the BtcRefundBurnt. pub fn is_run_at_most_once(state: &BobState) -> bool { - matches!(state, BobState::BtcPunished { .. } | BobState::BtcRefundBurnt(..)) + matches!(state, BobState::BtcPunished { .. } | BobState::BtcWithheld(..)) } #[allow(clippy::too_many_arguments)] @@ -1066,7 +1066,7 @@ async fn next_state( // Transition to waiting state where we race remaining_refund_timelock // against Alice potentially publishing TxRefundBurn - BobState::WaitingForRemainingRefundTimelockExpiration(state) + BobState::WaitingForReclaimTimelockExpiration(state) } BobState::BtcRefunded(state) => { event_emitter.emit_swap_progress_event( @@ -1078,7 +1078,7 @@ async fn next_state( BobState::BtcRefunded(state) } - BobState::BtcAmnestyPublished(state) => { + BobState::BtcReclaimPublished(state) => { // Here we just wait for the amnesty transaction to be confirmed let tx_amnesty = state.construct_tx_amnesty().context("Couldn't construct Bitcoin amnesty transaction")?; @@ -1109,7 +1109,7 @@ async fn next_state( }, ); - Ok(BobState::BtcAmnestyConfirmed(state.clone())) + Ok(BobState::BtcReclaimConfirmed(state.clone())) }, None, None) .await .context("Failed to wait for Bitcoin amnesty transaction to be confirmed")? @@ -1252,15 +1252,15 @@ async fn next_state( }); BobState::BtcEarlyRefunded(state) }, - BobState::BtcAmnestyConfirmed(state) => { + BobState::BtcReclaimConfirmed(state) => { event_emitter.emit_swap_progress_event(swap_id, TauriSwapProgressEvent::BtcAmnestyReceived { btc_amnesty_txid: state.construct_tx_amnesty()?.txid(), btc_lock_amount: state.tx_lock.lock_amount(), btc_amnesty_amount: state.btc_amnesty_amount.unwrap_or(bitcoin::Amount::ZERO), }); - BobState::BtcAmnestyConfirmed(state) + BobState::BtcReclaimConfirmed(state) }, - BobState::WaitingForRemainingRefundTimelockExpiration(state) => { + BobState::WaitingForReclaimTimelockExpiration(state) => { // Race between: // - Remaining refund timelock expiring (so we can publish TxRefundAmnesty) // - Alice publishing TxRefundBurn (burns the amnesty output) @@ -1309,16 +1309,16 @@ async fn next_state( result = timelock_expired_future => { result?; tracing::info!("Remaining refund timelock expired, can now publish TxRefundAmnesty"); - BobState::RemainingRefundTimelockExpired(state) + BobState::ReclaimTimelockExpired(state) } // Watch for Alice publishing TxRefundBurn _ = tx_refund_burn_status.wait_until_seen() => { tracing::info!("Alice published TxRefundBurn, amnesty output is being burnt"); - BobState::BtcRefundBurnPublished(state) + BobState::BtcWithholdPublished(state) } } } - BobState::RemainingRefundTimelockExpired(state) => { + BobState::ReclaimTimelockExpired(state) => { // TODO: We should retry this and the check // First check if TxRefundBurn was seen (we may have missed it while offline) let tx_refund_burn = state.construct_tx_refund_burn()?; @@ -1326,7 +1326,7 @@ async fn next_state( if tx_refund_burn_status.has_been_seen() { tracing::info!("TxRefundBurn was already published, transitioning to BtcRefundBurnPublished"); - return Ok(BobState::BtcRefundBurnPublished(state)); + return Ok(BobState::BtcWithholdPublished(state)); } // TxRefundBurn not published, we can publish TxRefundAmnesty @@ -1336,9 +1336,9 @@ async fn next_state( bitcoin_wallet.ensure_broadcasted(transaction, "amnesty") .await .context("Couldn't ensure broadcast of Bitcoin amnesty transaction")?; - BobState::BtcAmnestyPublished(state) + BobState::BtcReclaimPublished(state) } - BobState::BtcRefundBurnPublished(state) => { + BobState::BtcWithholdPublished(state) => { // Wait for TxRefundBurn confirmation let tx_refund_burn = state.construct_tx_refund_burn()?; event_emitter.emit_swap_progress_event( @@ -1353,9 +1353,9 @@ async fn next_state( subscription.wait_until_final().await?; tracing::info!("TxRefundBurn confirmed, amnesty output is burnt"); - BobState::BtcRefundBurnt(state) + BobState::BtcWithheld(state) } - BobState::BtcRefundBurnt(state) => { + BobState::BtcWithheld(state) => { // Watch for Alice publishing TxFinalAmnesty // Alice may grant final amnesty after burning our refund // However, we don't expect Alice to publish the tx at once, if at all. @@ -1376,12 +1376,12 @@ async fn next_state( let final_amnesty_status = bitcoin_wallet.status_of_script(&tx_final_amnesty).await.context("Failed to check TxFinalAmnesty status")?; if final_amnesty_status.has_been_seen() { - BobState::BtcFinalAmnestyPublished(state) + BobState::BtcMercyPublished(state) } else { - BobState::BtcRefundBurnt(state) + BobState::BtcWithheld(state) } } - BobState::BtcFinalAmnestyPublished(state) => { + BobState::BtcMercyPublished(state) => { // Wait for TxFinalAmnesty confirmation let tx_final_amnesty = state.construct_tx_final_amnesty()?; event_emitter.emit_swap_progress_event( @@ -1396,9 +1396,9 @@ async fn next_state( subscription.wait_until_final().await?; tracing::info!("TxFinalAmnesty confirmed, received burnt funds back"); - BobState::BtcFinalAmnestyConfirmed(state) + BobState::BtcMercyConfirmed(state) } - BobState::BtcFinalAmnestyConfirmed(state) => { + BobState::BtcMercyConfirmed(state) => { // Terminal state - we received the burnt funds back let tx_final_amnesty = state.construct_tx_final_amnesty()?; event_emitter.emit_swap_progress_event( @@ -1409,7 +1409,7 @@ async fn next_state( btc_amnesty_amount: state.btc_amnesty_amount.unwrap_or(bitcoin::Amount::ZERO), }, ); - BobState::BtcFinalAmnestyConfirmed(state) + BobState::BtcMercyConfirmed(state) } BobState::SafelyAborted => BobState::SafelyAborted, BobState::XmrRedeemed { tx_lock_id } => { diff --git a/swap/tests/harness/mod.rs b/swap/tests/harness/mod.rs index 14dbaefebd..c6cac73f5e 100644 --- a/swap/tests/harness/mod.rs +++ b/swap/tests/harness/mod.rs @@ -1034,7 +1034,7 @@ impl TestContext { self.bob_bitcoin_wallet.sync().await.unwrap(); let (lock_tx_id, cancel_fee, partial_refund_fee, amnesty_fee) = match state { - BobState::BtcAmnestyConfirmed(state6) => ( + BobState::BtcReclaimConfirmed(state6) => ( state6.tx_lock_id(), state6.tx_cancel_fee, state6.tx_partial_refund_fee.expect("partial refund fee"), @@ -1063,7 +1063,7 @@ impl TestContext { self.bob_bitcoin_wallet.sync().await.unwrap(); let (lock_tx_id, cancel_fee, partial_refund_fee, amnesty_amount) = match state { - BobState::BtcRefundBurnt(state6) => ( + BobState::BtcWithheld(state6) => ( state6.tx_lock_id(), state6.tx_cancel_fee, state6.tx_partial_refund_fee.expect("partial refund fee"), @@ -1092,7 +1092,7 @@ impl TestContext { self.bob_bitcoin_wallet.sync().await.unwrap(); let (lock_tx_id, cancel_fee, partial_refund_fee, final_amnesty_fee) = match state { - BobState::BtcFinalAmnestyConfirmed(state6) => ( + BobState::BtcMercyConfirmed(state6) => ( state6.tx_lock_id(), state6.tx_cancel_fee, state6.tx_partial_refund_fee.expect("partial refund fee"), @@ -1476,26 +1476,23 @@ pub mod bob_run_until { } pub fn is_waiting_for_remaining_refund_timelock(state: &BobState) -> bool { - matches!( - state, - BobState::WaitingForRemainingRefundTimelockExpiration(..) - ) + matches!(state, BobState::WaitingForReclaimTimelockExpiration(..)) } pub fn is_remaining_refund_timelock_expired(state: &BobState) -> bool { - matches!(state, BobState::RemainingRefundTimelockExpired(..)) + matches!(state, BobState::ReclaimTimelockExpired(..)) } pub fn is_btc_amnesty_confirmed(state: &BobState) -> bool { - matches!(state, BobState::BtcAmnestyConfirmed(..)) + matches!(state, BobState::BtcReclaimConfirmed(..)) } pub fn is_btc_refund_burnt(state: &BobState) -> bool { - matches!(state, BobState::BtcRefundBurnt(..)) + matches!(state, BobState::BtcWithheld(..)) } pub fn is_btc_final_amnesty_confirmed(state: &BobState) -> bool { - matches!(state, BobState::BtcFinalAmnestyConfirmed(..)) + matches!(state, BobState::BtcMercyConfirmed(..)) } } diff --git a/swap/tests/partial_refund_alice_grants_final_amnesty.rs b/swap/tests/partial_refund_alice_grants_final_amnesty.rs index cc368c8d6d..080f37bfca 100644 --- a/swap/tests/partial_refund_alice_grants_final_amnesty.rs +++ b/swap/tests/partial_refund_alice_grants_final_amnesty.rs @@ -75,7 +75,7 @@ async fn given_partial_refund_alice_grants_final_amnesty() { )); let bob_state = bob_state.await??; - assert!(matches!(bob_state, BobState::BtcRefundBurnt(..))); + assert!(matches!(bob_state, BobState::BtcWithheld(..))); // Simulate alice's controller sending the final amnesty command via `controller` cli ctx.restart_alice().await; @@ -87,7 +87,7 @@ async fn given_partial_refund_alice_grants_final_amnesty() { let (bob_swap, _) = ctx .stop_and_resume_bob_from_db(bob_app_handle, swap_id) .await; - assert!(matches!(bob_swap.state, BobState::BtcRefundBurnt(..))); + assert!(matches!(bob_swap.state, BobState::BtcWithheld(..))); let alice_state = alice_swap.await??; // Only start bob again after alice published the tx. otherwise bob immediately @@ -104,7 +104,7 @@ async fn given_partial_refund_alice_grants_final_amnesty() { "Actual state: {alice_state}" ); assert!( - matches!(bob_state, bob::BobState::BtcFinalAmnestyConfirmed(..)), + matches!(bob_state, bob::BobState::BtcMercyConfirmed(..)), "Actual state: {bob_state}" ); From c956efb839cf6e6b12b22e0445d94183f4212457 Mon Sep 17 00:00:00 2001 From: einliterflasche Date: Mon, 26 Jan 2026 12:26:51 +0100 Subject: [PATCH 101/113] bitcoin(transaction): rename partial refund TXs --- swap-core/src/bitcoin.rs | 14 +++++------ .../bitcoin/{final_amnesty.rs => mercy.rs} | 10 ++++---- .../bitcoin/{refund_amnesty.rs => reclaim.rs} | 6 ++--- .../bitcoin/{refund_burn.rs => withhold.rs} | 6 ++--- swap-machine/src/alice/mod.rs | 22 ++++++++--------- swap-machine/src/bob/mod.rs | 24 +++++++++---------- swap/src/asb/event_loop.rs | 8 +++---- swap/src/protocol/bob/swap.rs | 6 ++--- 8 files changed, 47 insertions(+), 49 deletions(-) rename swap-core/src/bitcoin/{final_amnesty.rs => mercy.rs} (96%) rename swap-core/src/bitcoin/{refund_amnesty.rs => reclaim.rs} (97%) rename swap-core/src/bitcoin/{refund_burn.rs => withhold.rs} (98%) diff --git a/swap-core/src/bitcoin.rs b/swap-core/src/bitcoin.rs index 9dd4cc52b2..4ccd6fc34d 100644 --- a/swap-core/src/bitcoin.rs +++ b/swap-core/src/bitcoin.rs @@ -2,32 +2,32 @@ mod cancel; mod early_refund; -mod final_amnesty; mod full_refund; mod lock; +mod mercy; mod partial_refund; mod punish; +mod reclaim; mod redeem; -mod refund_amnesty; -mod refund_burn; mod timelocks; +mod withhold; pub use crate::bitcoin::cancel::TxCancel; pub use crate::bitcoin::early_refund::TxEarlyRefund; -pub use crate::bitcoin::final_amnesty::TxFinalAmnesty; pub use crate::bitcoin::full_refund::TxFullRefund; pub use crate::bitcoin::lock::TxLock; +pub use crate::bitcoin::mercy::TxMercy; pub use crate::bitcoin::partial_refund::TxPartialRefund; pub use crate::bitcoin::punish::TxPunish; +pub use crate::bitcoin::reclaim::TxReclaim; pub use crate::bitcoin::redeem::TxRedeem; -pub use crate::bitcoin::refund_amnesty::TxRefundAmnesty; -pub use crate::bitcoin::refund_burn::TxRefundBurn; pub use crate::bitcoin::timelocks::{BlockHeight, ExpiredTimelocks}; pub use crate::bitcoin::timelocks::{CancelTimelock, PunishTimelock, RemainingRefundTimelock}; -pub use bitcoin_wallet::ScriptStatus; +pub use crate::bitcoin::withhold::TxWithhold; pub use ::bitcoin::amount::Amount; pub use ::bitcoin::psbt::Psbt as PartiallySignedTransaction; pub use ::bitcoin::{Address, AddressType, Network, Transaction, Txid}; +pub use bitcoin_wallet::ScriptStatus; pub use ecdsa_fun::Signature; pub use ecdsa_fun::adaptor::EncryptedSignature; pub use ecdsa_fun::fun::Scalar; diff --git a/swap-core/src/bitcoin/final_amnesty.rs b/swap-core/src/bitcoin/mercy.rs similarity index 96% rename from swap-core/src/bitcoin/final_amnesty.rs rename to swap-core/src/bitcoin/mercy.rs index 98b99674d7..ce54735944 100644 --- a/swap-core/src/bitcoin/final_amnesty.rs +++ b/swap-core/src/bitcoin/mercy.rs @@ -1,6 +1,6 @@ #![allow(non_snake_case)] -use crate::bitcoin::refund_burn::TxRefundBurn; +use crate::bitcoin::withhold::TxWithhold; use crate::bitcoin::{self, Address, Amount, PublicKey, Transaction}; use ::bitcoin::sighash::SighashCache; use ::bitcoin::{EcdsaSighashType, Txid, sighash::SegwitV0Sighash as Sighash}; @@ -19,16 +19,16 @@ use std::collections::HashMap; /// her signature private until she decides to cooperate (e.g., if Bob contacts /// her to request the refund). #[derive(Debug, Clone)] -pub struct TxFinalAmnesty { +pub struct TxMercy { inner: Transaction, digest: Sighash, burn_output_descriptor: Descriptor<::bitcoin::PublicKey>, watch_script: ScriptBuf, } -impl TxFinalAmnesty { +impl TxMercy { pub fn new( - tx_refund_burn: &TxRefundBurn, + tx_refund_burn: &TxWithhold, refund_address: &Address, spending_fee: Amount, ) -> Self { @@ -135,7 +135,7 @@ impl TxFinalAmnesty { } } -impl Watchable for TxFinalAmnesty { +impl Watchable for TxMercy { fn id(&self) -> Txid { self.txid() } diff --git a/swap-core/src/bitcoin/refund_amnesty.rs b/swap-core/src/bitcoin/reclaim.rs similarity index 97% rename from swap-core/src/bitcoin/refund_amnesty.rs rename to swap-core/src/bitcoin/reclaim.rs index c6e726ecfa..79ce2425a3 100644 --- a/swap-core/src/bitcoin/refund_amnesty.rs +++ b/swap-core/src/bitcoin/reclaim.rs @@ -12,14 +12,14 @@ use std::collections::HashMap; use super::timelocks::RemainingRefundTimelock; #[derive(Debug, Clone)] -pub struct TxRefundAmnesty { +pub struct TxReclaim { inner: Transaction, digest: Sighash, amensty_output_descriptor: Descriptor<::bitcoin::PublicKey>, watch_script: ScriptBuf, } -impl TxRefundAmnesty { +impl TxReclaim { pub fn new( tx_refund: &TxPartialRefund, refund_address: &Address, @@ -127,7 +127,7 @@ impl TxRefundAmnesty { } } -impl Watchable for TxRefundAmnesty { +impl Watchable for TxReclaim { fn id(&self) -> Txid { self.txid() } diff --git a/swap-core/src/bitcoin/refund_burn.rs b/swap-core/src/bitcoin/withhold.rs similarity index 98% rename from swap-core/src/bitcoin/refund_burn.rs rename to swap-core/src/bitcoin/withhold.rs index bcc577ad63..9a75fc38c8 100644 --- a/swap-core/src/bitcoin/refund_burn.rs +++ b/swap-core/src/bitcoin/withhold.rs @@ -21,7 +21,7 @@ use std::collections::HashMap; /// Unlike TxRefundAmnesty, this transaction has no timelock - Alice can publish /// it immediately after TxPartialRefund is confirmed. #[derive(Debug, Clone)] -pub struct TxRefundBurn { +pub struct TxWithhold { inner: Transaction, digest: Sighash, amnesty_output_descriptor: Descriptor<::bitcoin::PublicKey>, @@ -29,7 +29,7 @@ pub struct TxRefundBurn { watch_script: ScriptBuf, } -impl TxRefundBurn { +impl TxWithhold { pub fn new( tx_partial_refund: &TxPartialRefund, A: PublicKey, @@ -195,7 +195,7 @@ impl TxRefundBurn { } } -impl Watchable for TxRefundBurn { +impl Watchable for TxWithhold { fn id(&self) -> Txid { self.txid() } diff --git a/swap-machine/src/alice/mod.rs b/swap-machine/src/alice/mod.rs index c76fc1bd98..2ba8e65fd0 100644 --- a/swap-machine/src/alice/mod.rs +++ b/swap-machine/src/alice/mod.rs @@ -9,8 +9,8 @@ use std::fmt::{self, Debug}; use std::sync::Arc; use swap_core::bitcoin::{ CancelTimelock, ExpiredTimelocks, PunishTimelock, RemainingRefundTimelock, Transaction, - TxCancel, TxEarlyRefund, TxFinalAmnesty, TxFullRefund, TxPartialRefund, TxPunish, TxRedeem, - TxRefundAmnesty, TxRefundBurn, Txid, current_epoch, + TxCancel, TxEarlyRefund, TxFullRefund, TxMercy, TxPartialRefund, TxPunish, TxReclaim, TxRedeem, + TxWithhold, Txid, current_epoch, }; use swap_core::compat::{IntoDalek, IntoDalekNg, IntoMoneroOxide}; use swap_core::monero::ScalarExt; @@ -478,7 +478,7 @@ impl State2 { let tx_partial_refund_encsig = self.a.encsign(self.S_b_bitcoin, tx_partial_refund.digest()); // Construct and sign TxRefundAmnesty - let tx_refund_amnesty = swap_core::bitcoin::TxRefundAmnesty::new( + let tx_refund_amnesty = swap_core::bitcoin::TxReclaim::new( &tx_partial_refund, &self.refund_address, self.tx_refund_amnesty_fee @@ -589,7 +589,7 @@ impl State2 { .context("missing tx_partial_refund_fee")?, ) .context("Couldn't construct TxPartialRefund")?; - let tx_refund_amnesty = TxRefundAmnesty::new( + let tx_refund_amnesty = TxReclaim::new( &tx_partial_refund, &self.refund_address, self.tx_refund_amnesty_fee @@ -607,7 +607,7 @@ impl State2 { .context("Failed to verify refund amnesty transaction")?; // Create TxRefundBurn ourself - let tx_refund_burn = TxRefundBurn::new( + let tx_refund_burn = TxWithhold::new( &tx_partial_refund, self.a.public(), self.B, @@ -624,7 +624,7 @@ impl State2 { .context("Failed to verify refund burn transaction")?; // Create TxFinalAmnesty ourself - let tx_final_amnesty = TxFinalAmnesty::new( + let tx_final_amnesty = TxMercy::new( &tx_refund_burn, &self.refund_address, self.tx_final_amnesty_fee @@ -899,7 +899,7 @@ impl State3 { pub fn signed_bitcoin_amnesty_transaction(&self) -> Result { let tx_partial_refund = self.tx_partial_refund()?; - let tx_amnesty = TxRefundAmnesty::new( + let tx_amnesty = TxReclaim::new( &tx_partial_refund, &self.refund_address, self.tx_refund_amnesty_fee @@ -923,8 +923,8 @@ impl State3 { } /// Construct TxRefundBurn from tx_partial_refund output. - pub fn tx_refund_burn(&self) -> Result { - TxRefundBurn::new( + pub fn tx_refund_burn(&self) -> Result { + TxWithhold::new( &self.tx_partial_refund()?, self.a.public(), self.B, @@ -947,8 +947,8 @@ impl State3 { } /// Construct TxFinalAmnesty from tx_refund_burn output. - pub fn tx_final_amnesty(&self) -> Result { - Ok(TxFinalAmnesty::new( + pub fn tx_final_amnesty(&self) -> Result { + Ok(TxMercy::new( &self.tx_refund_burn()?, &self.refund_address, self.tx_final_amnesty_fee diff --git a/swap-machine/src/bob/mod.rs b/swap-machine/src/bob/mod.rs index 87439de93d..8f535115c9 100644 --- a/swap-machine/src/bob/mod.rs +++ b/swap-machine/src/bob/mod.rs @@ -15,8 +15,8 @@ use std::fmt; use std::sync::Arc; use swap_core::bitcoin::{ self, CancelTimelock, ExpiredTimelocks, PunishTimelock, RemainingRefundTimelock, Transaction, - TxCancel, TxFinalAmnesty, TxFullRefund, TxLock, TxPartialRefund, TxRefundAmnesty, TxRefundBurn, - Txid, current_epoch, + TxCancel, TxFullRefund, TxLock, TxMercy, TxPartialRefund, TxReclaim, TxWithhold, Txid, + current_epoch, }; use swap_core::compat::{IntoDalekNg, IntoMoneroOxide}; use swap_core::monero; @@ -512,7 +512,7 @@ impl State1 { &tx_partial_refund_encsig, )?; - let tx_refund_amnesty = TxRefundAmnesty::new( + let tx_refund_amnesty = TxReclaim::new( &tx_partial_refund, &self.refund_address, self.tx_refund_amnesty_fee @@ -645,7 +645,7 @@ impl State2 { .context("missing tx_partial_refund_fee")?, ) .context("Couldn't construct TxPartialRefund")?; - let tx_refund_amnesty = TxRefundAmnesty::new( + let tx_refund_amnesty = TxReclaim::new( &tx_partial_refund, &self.refund_address, self.tx_refund_amnesty_fee @@ -655,7 +655,7 @@ impl State2 { )?; let tx_refund_amnesty_sig = self.b.sign(tx_refund_amnesty.digest()); - let tx_refund_burn = TxRefundBurn::new( + let tx_refund_burn = TxWithhold::new( &tx_partial_refund, self.A, self.b.public(), @@ -664,7 +664,7 @@ impl State2 { )?; let tx_refund_burn_sig = self.b.sign(tx_refund_burn.digest()); - let tx_final_amnesty = TxFinalAmnesty::new( + let tx_final_amnesty = TxMercy::new( &tx_refund_burn, &self.refund_address, self.tx_final_amnesty_fee @@ -1317,10 +1317,10 @@ impl State6 { Ok(signed_tx_amnesty) } - pub fn construct_tx_amnesty(&self) -> Result { + pub fn construct_tx_amnesty(&self) -> Result { let tx_partial_refund = self.construct_tx_partial_refund()?; - bitcoin::TxRefundAmnesty::new( + bitcoin::TxReclaim::new( &tx_partial_refund, &self.refund_address, self.tx_refund_amnesty_fee.context( @@ -1332,9 +1332,9 @@ impl State6 { ) } - pub fn construct_tx_refund_burn(&self) -> Result { + pub fn construct_tx_refund_burn(&self) -> Result { let tx_partial_refund = self.construct_tx_partial_refund()?; - bitcoin::TxRefundBurn::new( + bitcoin::TxWithhold::new( &tx_partial_refund, self.A, self.b.public(), @@ -1343,9 +1343,9 @@ impl State6 { ) } - pub fn construct_tx_final_amnesty(&self) -> Result { + pub fn construct_tx_final_amnesty(&self) -> Result { let tx_refund_burn = self.construct_tx_refund_burn()?; - Ok(bitcoin::TxFinalAmnesty::new( + Ok(bitcoin::TxMercy::new( &tx_refund_burn, &self.refund_address, self.tx_final_amnesty_fee.context( diff --git a/swap/src/asb/event_loop.rs b/swap/src/asb/event_loop.rs index a6b0e014f9..efb70e6d36 100644 --- a/swap/src/asb/event_loop.rs +++ b/swap/src/asb/event_loop.rs @@ -71,8 +71,7 @@ where /// Stores where to send burn-on-refund instructions to /// The corresponding receiver is stored in the EventLoopHandle /// Uses watch channel to allow multiple updates before consumption - recv_burn_on_refund_instruction: - HashMap>>, + recv_burn_on_refund_instruction: HashMap>>, /// Once we receive an [`EncryptedSignature`] from Bob, we forward it to the EventLoopHandle. /// Once the EventLoopHandle acknowledges the receipt of the [`EncryptedSignature`], we need to confirm this to Bob. @@ -786,8 +785,7 @@ pub struct EventLoopHandle { peer: PeerId, recv_encrypted_signature: tokio::sync::Mutex>>, - recv_burn_on_refund_instruction: - tokio::sync::Mutex>>, + recv_burn_on_refund_instruction: tokio::sync::Mutex>>, #[allow(clippy::type_complexity)] transfer_proof_sender: tokio::sync::Mutex< Option< @@ -1000,7 +998,7 @@ async fn capture_wallet_snapshot( .estimate_fee(bitcoin::TxPunish::weight(), Some(transfer_amount)) .await?; let refund_burn_fee = bitcoin_wallet - .estimate_fee(bitcoin::TxRefundBurn::weight(), Some(transfer_amount)) + .estimate_fee(bitcoin::TxWithhold::weight(), Some(transfer_amount)) .await?; Ok(WalletSnapshot::new( diff --git a/swap/src/protocol/bob/swap.rs b/swap/src/protocol/bob/swap.rs index 5ffefa348f..b1173a9df3 100644 --- a/swap/src/protocol/bob/swap.rs +++ b/swap/src/protocol/bob/swap.rs @@ -17,7 +17,7 @@ use anyhow::{Context as AnyContext, Result, anyhow}; use std::sync::Arc; use std::time::Duration; use swap_core::bitcoin::{ - ExpiredTimelocks, TxCancel, TxFinalAmnesty, TxFullRefund, TxPartialRefund, TxRefundAmnesty, + ExpiredTimelocks, TxCancel, TxMercy, TxFullRefund, TxPartialRefund, TxReclaim, }; use swap_core::monero::BlockHeight; use swap_env::env; @@ -130,10 +130,10 @@ async fn next_state( .estimate_fee(TxPartialRefund::weight(), Some(btc_amount)) .await?; let tx_refund_amnesty_fee = bitcoin_wallet - .estimate_fee(TxRefundAmnesty::weight(), Some(btc_amount)) + .estimate_fee(TxReclaim::weight(), Some(btc_amount)) .await?; let tx_final_amnesty_fee = bitcoin_wallet - .estimate_fee(TxFinalAmnesty::weight(), Some(btc_amount)) + .estimate_fee(TxMercy::weight(), Some(btc_amount)) .await?; // Emit an event to tauri that we are negotiating with the maker to lock the Bitcoin From 97dbf4c2ad91af7ecd0642719a0ee0c266bb809f Mon Sep 17 00:00:00 2001 From: einliterflasche Date: Mon, 26 Jan 2026 12:46:32 +0100 Subject: [PATCH 102/113] asb(config)+protocol(quote): anti_spam_deposit_ratio instead of taker_guaranteed_refund --- swap-core/src/bitcoin/partial_refund.rs | 20 +++++++++---------- swap-core/src/bitcoin/reclaim.rs | 8 ++------ swap-core/src/bitcoin/withhold.rs | 8 ++++---- swap-env/src/config.rs | 20 +++++++++++-------- swap-p2p/src/protocols/quote.rs | 9 +++++---- swap/src/asb/event_loop.rs | 14 ++++++------- swap/tests/partial_refund_alice_burns.rs | 4 ++-- ...artial_refund_alice_burns_after_command.rs | 4 ++-- ...rtial_refund_alice_grants_final_amnesty.rs | 4 ++-- .../partial_refund_bob_claims_amnesty.rs | 4 ++-- 10 files changed, 47 insertions(+), 48 deletions(-) diff --git a/swap-core/src/bitcoin/partial_refund.rs b/swap-core/src/bitcoin/partial_refund.rs index 40406ee776..78a184b987 100644 --- a/swap-core/src/bitcoin/partial_refund.rs +++ b/swap-core/src/bitcoin/partial_refund.rs @@ -76,15 +76,15 @@ impl TxPartialRefund { self.digest } - pub fn amnesty_amount(&self) -> Amount { + pub fn anti_spam_deposit(&self) -> Amount { self.inner.output[1].value } - pub fn amnesty_outpoint(&self) -> ::bitcoin::OutPoint { + pub fn ani_spam_deposit_outpoint(&self) -> ::bitcoin::OutPoint { ::bitcoin::OutPoint::new(self.txid(), 1) } - pub fn build_amnesty_spend_transaction( + pub fn build_reclaim_transaction( &self, refund_address: &Address, spending_fee: Amount, @@ -96,7 +96,7 @@ impl TxPartialRefund { }; let tx_in = TxIn { - previous_output: self.amnesty_outpoint(), + previous_output: self.ani_spam_deposit_outpoint(), script_sig: Default::default(), sequence: Sequence(remaining_refund_timelock.0), witness: Default::default(), @@ -104,7 +104,7 @@ impl TxPartialRefund { let tx_out = TxOut { value: self - .amnesty_amount() + .anti_spam_deposit() .checked_sub(spending_fee) .context("btc amnesty amount is less than spending fee")?, script_pubkey: refund_address.script_pubkey(), @@ -121,7 +121,7 @@ impl TxPartialRefund { /// Build a transaction that spends the amnesty output to a new 2-of-2 multisig (burn output). /// This is used by TxRefundBurn to "burn" the amnesty by moving it to another multisig. /// Unlike `build_amnesty_spend_transaction`, this has no timelock. - pub fn build_burn_spend_transaction( + pub fn build_withhold_transaction( &self, burn_output_descriptor: &Descriptor<::bitcoin::PublicKey>, spending_fee: Amount, @@ -133,21 +133,21 @@ impl TxPartialRefund { // TODO: Handle case where fee >= amnesty_amount more gracefully assert!( - self.amnesty_amount() > spending_fee, + self.anti_spam_deposit() > spending_fee, "Burn spend fee ({}) must be less than amnesty amount ({})", spending_fee, - self.amnesty_amount() + self.anti_spam_deposit() ); let tx_in = TxIn { - previous_output: self.amnesty_outpoint(), + previous_output: self.ani_spam_deposit_outpoint(), script_sig: Default::default(), sequence: Sequence(0xFFFF_FFFF), // No timelock witness: Default::default(), }; let tx_out = TxOut { - value: self.amnesty_amount() - spending_fee, + value: self.anti_spam_deposit() - spending_fee, script_pubkey: burn_output_descriptor.script_pubkey(), }; diff --git a/swap-core/src/bitcoin/reclaim.rs b/swap-core/src/bitcoin/reclaim.rs index 79ce2425a3..66228ad8f6 100644 --- a/swap-core/src/bitcoin/reclaim.rs +++ b/swap-core/src/bitcoin/reclaim.rs @@ -27,11 +27,7 @@ impl TxReclaim { remaining_refund_timelock: RemainingRefundTimelock, ) -> Result { let tx_refund_amnesty = tx_refund - .build_amnesty_spend_transaction( - refund_address, - spending_fee, - remaining_refund_timelock, - ) + .build_reclaim_transaction(refund_address, spending_fee, remaining_refund_timelock) .context("Couldn't build tx refund amnesty")?; let digest = SighashCache::new(&tx_refund_amnesty) @@ -41,7 +37,7 @@ impl TxReclaim { .amnesty_output_descriptor .script_code() .expect("scriptcode"), - tx_refund.amnesty_amount(), + tx_refund.anti_spam_deposit(), EcdsaSighashType::All, ) .expect("sighash"); diff --git a/swap-core/src/bitcoin/withhold.rs b/swap-core/src/bitcoin/withhold.rs index 9a75fc38c8..cb26c185d9 100644 --- a/swap-core/src/bitcoin/withhold.rs +++ b/swap-core/src/bitcoin/withhold.rs @@ -39,16 +39,16 @@ impl TxWithhold { // TODO: Handle case where fee >= amnesty_amount more gracefully // For now, assert to catch this during development assert!( - tx_partial_refund.amnesty_amount() > spending_fee, + tx_partial_refund.anti_spam_deposit() > spending_fee, "TxRefundBurn fee ({}) must be less than amnesty amount ({})", spending_fee, - tx_partial_refund.amnesty_amount() + tx_partial_refund.anti_spam_deposit() ); let burn_output_descriptor = build_shared_output_descriptor(A.0, B.0)?; let tx_refund_burn = - tx_partial_refund.build_burn_spend_transaction(&burn_output_descriptor, spending_fee); + tx_partial_refund.build_withhold_transaction(&burn_output_descriptor, spending_fee); let digest = SighashCache::new(&tx_refund_burn) .p2wsh_signature_hash( @@ -57,7 +57,7 @@ impl TxWithhold { .amnesty_output_descriptor .script_code() .expect("scriptcode"), - tx_partial_refund.amnesty_amount(), + tx_partial_refund.anti_spam_deposit(), EcdsaSighashType::All, ) .expect("sighash"); diff --git a/swap-env/src/config.rs b/swap-env/src/config.rs index 59dac31de5..f8f73321df 100644 --- a/swap-env/src/config.rs +++ b/swap-env/src/config.rs @@ -124,20 +124,20 @@ pub struct RefundPolicy { /// Takers will only receive this percentage of their Bitcoin back by default. /// Maker can still issue "amnesty" to refund the rest. /// This protects the maker against griefing attacks. - #[serde(default = "default_taker_refund_ratio")] - pub taker_refund_ratio: Decimal, + #[serde(default = "default_anti_spam_deposit_ratio")] + pub anti_spam_deposit_ratio: Decimal, /// If true, Alice will publish TxRefundBurn after refunding her XMR, /// denying Bob access to the amnesty output. Alice can later grant /// final amnesty to return the funds to Bob. - #[serde(default)] - pub burn_on_refund: bool, + #[serde(default = "default_always_withhold_deposit")] + pub always_withhold_deposit: bool, } impl Default for RefundPolicy { fn default() -> Self { Self { - taker_refund_ratio: default_taker_refund_ratio(), - burn_on_refund: false, + anti_spam_deposit_ratio: default_anti_spam_deposit_ratio(), + always_withhold_deposit: false, } } } @@ -158,8 +158,12 @@ fn default_developer_tip() -> Decimal { Decimal::ZERO } -fn default_taker_refund_ratio() -> Decimal { - Decimal::ONE +fn default_anti_spam_deposit_ratio() -> Decimal { + Decimal::ZERO +} + +fn default_always_withhold_deposit() -> bool { + false } impl Config { diff --git a/swap-p2p/src/protocols/quote.rs b/swap-p2p/src/protocols/quote.rs index bcd9fef6b9..430a6652d7 100644 --- a/swap-p2p/src/protocols/quote.rs +++ b/swap-p2p/src/protocols/quote.rs @@ -24,19 +24,20 @@ pub enum RefundPolicyWire { /// Taker receives a partial refund; the remainder goes to an amnesty output /// that the maker may or may not release later. PartialRefund { - /// Ratio (0.0-1.0) of Bitcoin the taker receives immediately. + /// Ratio (0.0-1.0) of Bitcoin that goes into the anti-spam deposit + /// and may be withheld by the maker. #[typeshare(serialized_as = "number")] - taker_refund_ratio: Decimal, + anti_spam_deposit_ratio: Decimal, }, } impl From for RefundPolicyWire { fn from(policy: RefundPolicy) -> Self { - if policy.taker_refund_ratio == Decimal::ONE { + if policy.anti_spam_deposit_ratio == Decimal::ONE { RefundPolicyWire::FullRefund } else { RefundPolicyWire::PartialRefund { - taker_refund_ratio: policy.taker_refund_ratio, + anti_spam_deposit_ratio: policy.anti_spam_deposit_ratio, } } } diff --git a/swap/src/asb/event_loop.rs b/swap/src/asb/event_loop.rs index efb70e6d36..05e2430b1a 100644 --- a/swap/src/asb/event_loop.rs +++ b/swap/src/asb/event_loop.rs @@ -944,16 +944,14 @@ fn apply_bitcoin_amnesty_policy( swap_amount: bitcoin::Amount, refund_policy: &RefundPolicy, ) -> Result<(bitcoin::Amount, bool)> { - let should_burn_on_refund = refund_policy.burn_on_refund; + let should_always_withhold = refund_policy.always_withhold_deposit; - // When ratio is 1.0, no amnesty - use full refund path - if refund_policy.taker_refund_ratio == Decimal::ONE { - return Ok((bitcoin::Amount::ZERO, should_burn_on_refund)); + // When ratio is 0.0, no amnesty - use full refund path for fewer fees + if refund_policy.anti_spam_deposit_ratio == Decimal::ZERO { + return Ok((bitcoin::Amount::ZERO, should_always_withhold)); } - let btc_amnesty_ratio = Decimal::ONE - .checked_sub(refund_policy.taker_refund_ratio) - .context("can't have refund ration > 1")?; + let btc_amnesty_ratio = refund_policy.anti_spam_deposit_ratio; let amount_sats = swap_amount.to_sat(); let amount_decimal = @@ -969,7 +967,7 @@ fn apply_bitcoin_amnesty_policy( Ok(( bitcoin::Amount::from_sat(btc_amnesty_sats), - should_burn_on_refund, + should_always_withhold, )) } diff --git a/swap/tests/partial_refund_alice_burns.rs b/swap/tests/partial_refund_alice_burns.rs index d32651c43d..344089794b 100644 --- a/swap/tests/partial_refund_alice_burns.rs +++ b/swap/tests/partial_refund_alice_burns.rs @@ -19,8 +19,8 @@ async fn given_partial_refund_alice_burns_the_amnesty() { // Use 95% refund ratio - Bob gets 95% immediately, 5% locked in amnesty // Alice burns the amnesty let refund_policy = Some(RefundPolicy { - taker_refund_ratio: Decimal::new(95, 2), // 0.95 = 95% - burn_on_refund: true, + anti_spam_deposit_ratio: Decimal::new(95, 2), // 0.95 = 95% + always_withhold_deposit: true, }); harness::setup_test( diff --git a/swap/tests/partial_refund_alice_burns_after_command.rs b/swap/tests/partial_refund_alice_burns_after_command.rs index a02629474e..45d37fa7a4 100644 --- a/swap/tests/partial_refund_alice_burns_after_command.rs +++ b/swap/tests/partial_refund_alice_burns_after_command.rs @@ -20,8 +20,8 @@ async fn given_partial_refund_alice_burns_after_command() { // Use 95% refund ratio - Bob gets 95% immediately, 5% locked in amnesty // Alice does NOT burn by default - burn_on_refund is false let refund_policy = Some(RefundPolicy { - taker_refund_ratio: Decimal::new(95, 2), // 0.95 = 95% - burn_on_refund: false, // Do not burn by default + anti_spam_deposit_ratio: Decimal::new(95, 2), // 0.95 = 95% + always_withhold_deposit: false, // Do not burn by default }); harness::setup_test( diff --git a/swap/tests/partial_refund_alice_grants_final_amnesty.rs b/swap/tests/partial_refund_alice_grants_final_amnesty.rs index 080f37bfca..2dede6bbbe 100644 --- a/swap/tests/partial_refund_alice_grants_final_amnesty.rs +++ b/swap/tests/partial_refund_alice_grants_final_amnesty.rs @@ -23,8 +23,8 @@ async fn given_partial_refund_alice_grants_final_amnesty() { // Use 95% refund ratio - Bob gets 95% immediately, 5% locked in amnesty // Alice burns the amnesty, then grants final amnesty let refund_policy = Some(RefundPolicy { - taker_refund_ratio: Decimal::new(95, 2), // 0.95 = 95% - burn_on_refund: true, + anti_spam_deposit_ratio: Decimal::new(95, 2), // 0.95 = 95% + always_withhold_deposit: true, }); harness::setup_test( diff --git a/swap/tests/partial_refund_bob_claims_amnesty.rs b/swap/tests/partial_refund_bob_claims_amnesty.rs index a37eb4afae..9ccd078e7a 100644 --- a/swap/tests/partial_refund_bob_claims_amnesty.rs +++ b/swap/tests/partial_refund_bob_claims_amnesty.rs @@ -15,8 +15,8 @@ async fn given_partial_refund_bob_claims_amnesty_after_timelock() { // Use 95% refund ratio - Bob gets 95% immediately, 5% locked in amnesty // Alice does NOT burn - Bob can claim amnesty after timelock let refund_policy = Some(RefundPolicy { - taker_refund_ratio: Decimal::new(95, 2), // 0.95 = 95% - burn_on_refund: false, + anti_spam_deposit_ratio: Decimal::new(95, 2), // 0.95 = 95% + always_withhold_deposit: false, }); harness::setup_test(FastAmnestyConfig, None, refund_policy, |mut ctx| async move { From f9d67494238809a7489be99fc9e5c8ab619dcf14 Mon Sep 17 00:00:00 2001 From: einliterflasche Date: Mon, 2 Feb 2026 15:55:53 +0100 Subject: [PATCH 103/113] update terminology accross state machines, commands, etc --- swap-controller-api/src/lib.rs | 7 +-- swap-controller/src/cli.rs | 8 +-- swap-controller/src/main.rs | 17 ++++--- swap-db/src/alice.rs | 50 +++++++++---------- swap-db/src/bob.rs | 12 ++--- swap-machine/src/alice/mod.rs | 28 +++++------ swap/src/asb/event_loop.rs | 4 +- swap/src/asb/recovery/cancel.rs | 10 ++-- swap/src/asb/recovery/grant_final_amnesty.rs | 4 +- swap/src/asb/recovery/punish.rs | 10 ++-- swap/src/asb/recovery/redeem.rs | 10 ++-- swap/src/asb/recovery/refund.rs | 10 ++-- swap/src/asb/recovery/safely_abort.rs | 10 ++-- swap/src/asb/rpc/server.rs | 12 +++-- swap/src/protocol/alice/swap.rs | 22 ++++---- swap/tests/harness/mod.rs | 8 +-- ...artial_refund_alice_burns_after_command.rs | 2 +- ...rtial_refund_alice_grants_final_amnesty.rs | 6 +-- 18 files changed, 116 insertions(+), 114 deletions(-) diff --git a/swap-controller-api/src/lib.rs b/swap-controller-api/src/lib.rs index a22103f773..339c70d6c5 100644 --- a/swap-controller-api/src/lib.rs +++ b/swap-controller-api/src/lib.rs @@ -120,7 +120,8 @@ pub trait AsbApi { #[method(name = "registration_status")] async fn registration_status(&self) -> Result; #[method(name = "set_burn_on_refund")] - async fn set_burn_on_refund(&self, swap_id: Uuid, burn: bool) -> Result<(), ErrorObjectOwned>; - #[method(name = "grant_final_amnesty")] - async fn grant_final_amnesty(&self, swap_id: Uuid) -> Result<(), ErrorObjectOwned>; + async fn set_withhold_deposit(&self, swap_id: Uuid, burn: bool) + -> Result<(), ErrorObjectOwned>; + #[method(name = "grant_mercy")] + async fn grant_mercy(&self, swap_id: Uuid) -> Result<(), ErrorObjectOwned>; } diff --git a/swap-controller/src/cli.rs b/swap-controller/src/cli.rs index 384502336e..1e458d7df5 100644 --- a/swap-controller/src/cli.rs +++ b/swap-controller/src/cli.rs @@ -39,15 +39,15 @@ pub enum Cmd { /// Show rendezvous registration status RegistrationStatus, /// Set whether to burn Bitcoin on refund for a swap - SetBurnOnRefund { + SetWithholdDeposit { /// The swap ID swap_id: Uuid, /// Whether to burn the Bitcoin (true or false) #[arg(action = clap::ArgAction::Set)] - burn: bool, + withhold: bool, }, - /// Grant final amnesty for a swap in BtcRefundBurnConfirmed state - GrantFinalAmnesty { + /// Grant mercy (release the anti-spam deposit) for a swap in BtcWithheld state + GrantMercy { /// The swap ID swap_id: Uuid, }, diff --git a/swap-controller/src/main.rs b/swap-controller/src/main.rs index d68e2f58c5..3aefe3e195 100644 --- a/swap-controller/src/main.rs +++ b/swap-controller/src/main.rs @@ -131,17 +131,20 @@ async fn dispatch(cmd: Cmd, client: impl AsbApiClient) -> anyhow::Result<()> { } } } - Cmd::SetBurnOnRefund { swap_id, burn } => { - client.set_burn_on_refund(swap_id, burn).await?; + Cmd::SetWithholdDeposit { + swap_id, + withhold: burn, + } => { + client.set_withhold_deposit(swap_id, burn).await?; if burn { - println!("Burn on refund enabled for swap {swap_id}"); + println!("Withholding deposit should the taker refund for swap {swap_id}"); } else { - println!("Burn on refund disabled for swap {swap_id}"); + println!("Not withholding deposit should the taker refund for swap {swap_id}"); } } - Cmd::GrantFinalAmnesty { swap_id } => { - client.grant_final_amnesty(swap_id).await?; - println!("Final amnesty granted for swap {swap_id}"); + Cmd::GrantMercy { swap_id } => { + client.grant_mercy(swap_id).await?; + println!("Mercy granted for swap {swap_id}"); } } Ok(()) diff --git a/swap-db/src/alice.rs b/swap-db/src/alice.rs index 7462068f0a..8d4183ba71 100644 --- a/swap-db/src/alice.rs +++ b/swap-db/src/alice.rs @@ -92,6 +92,12 @@ pub enum Alice { BtcWithholdPublished { state3: alice::State3, }, + BtcMercyGranted { + state3: alice::State3, + }, + BtcMercyPublished { + state3: alice::State3, + }, Done(AliceEndState), } @@ -113,12 +119,6 @@ pub enum AliceEndState { BtcWithheld { state3: alice::State3, }, - BtcMercyGranted { - state3: alice::State3, - }, - BtcMercyPublished { - state3: alice::State3, - }, BtcMercyConfirmed { state3: alice::State3, }, @@ -238,19 +238,17 @@ impl From for Alice { AliceState::XmrRefunded { state3 } => Alice::Done(AliceEndState::XmrRefunded { state3: state3.map(|s| s.as_ref().clone()), }), - AliceState::BtcRefundBurnPublished { state3 } => { + AliceState::BtcWithholdPublished { state3 } => { Alice::BtcWithholdPublished { state3: *state3 } } - AliceState::BtcRefundBurnConfirmed { state3 } => { + AliceState::BtcWithholdConfirmed { state3 } => { Alice::Done(AliceEndState::BtcWithheld { state3: *state3 }) } - AliceState::BtcFinalAmnestyGranted { state3 } => { - Alice::Done(AliceEndState::BtcMercyGranted { state3: *state3 }) - } - AliceState::BtcRefundFinalAmnestyPublished { state3 } => { - Alice::Done(AliceEndState::BtcMercyPublished { state3: *state3 }) + AliceState::BtcMercyGranted { state3 } => Alice::BtcMercyGranted { state3: *state3 }, + AliceState::BtcMercyPublished { state3 } => { + Alice::BtcMercyPublished { state3: *state3 } } - AliceState::BtcRefundFinalAmnestyConfirmed { state3 } => { + AliceState::BtcMercyConfirmed { state3 } => { Alice::Done(AliceEndState::BtcMercyConfirmed { state3: *state3 }) } AliceState::WaitingForCancelTimelockExpiration { @@ -412,7 +410,13 @@ impl From for AliceState { spend_key, state3: Box::new(state3), }, - Alice::BtcWithholdPublished { state3 } => AliceState::BtcRefundBurnPublished { + Alice::BtcWithholdPublished { state3 } => AliceState::BtcWithholdPublished { + state3: Box::new(state3), + }, + Alice::BtcMercyGranted { state3 } => AliceState::BtcMercyGranted { + state3: Box::new(state3), + }, + Alice::BtcMercyPublished { state3 } => AliceState::BtcMercyPublished { state3: Box::new(state3), }, Alice::Done(end_state) => match end_state { @@ -431,22 +435,12 @@ impl From for AliceState { AliceEndState::BtcEarlyRefunded { state3 } => { AliceState::BtcEarlyRefunded(Box::new(state3)) } - AliceEndState::BtcWithheld { state3 } => AliceState::BtcRefundBurnConfirmed { + AliceEndState::BtcWithheld { state3 } => AliceState::BtcWithholdConfirmed { state3: Box::new(state3), }, - AliceEndState::BtcMercyGranted { state3 } => AliceState::BtcFinalAmnestyGranted { + AliceEndState::BtcMercyConfirmed { state3 } => AliceState::BtcMercyConfirmed { state3: Box::new(state3), }, - AliceEndState::BtcMercyPublished { state3 } => { - AliceState::BtcRefundFinalAmnestyPublished { - state3: Box::new(state3), - } - } - AliceEndState::BtcMercyConfirmed { state3 } => { - AliceState::BtcRefundFinalAmnestyConfirmed { - state3: Box::new(state3), - } - } }, } } @@ -482,6 +476,8 @@ impl fmt::Display for Alice { Alice::BtcWithholdPublished { .. } => { f.write_str("Bitcoin withhold transaction published") } + Alice::BtcMercyGranted { .. } => f.write_str("Bitcoin mercy initiated"), + Alice::BtcMercyPublished { .. } => f.write_str("Bitcoin mercy published"), Alice::Done(end_state) => write!(f, "Done: {}", end_state), } } diff --git a/swap-db/src/bob.rs b/swap-db/src/bob.rs index 1c382afb87..91e77a52a1 100644 --- a/swap-db/src/bob.rs +++ b/swap-db/src/bob.rs @@ -275,14 +275,14 @@ impl fmt::Display for Bob { Bob::EncSigSent { .. } => f.write_str("Encrypted signature sent"), Bob::BtcPunished { .. } => f.write_str("Bitcoin punished"), Bob::BtcPartiallyRefunded { .. } => f.write_str("Bitcoin partially refunded"), - Bob::BtcReclaimPublished { .. } => f.write_str("Bitcoin amnesty published"), + Bob::BtcReclaimPublished { .. } => f.write_str("Bitcoin reclaim transaction published"), Bob::WaitingForReclaimTimelockExpiration { .. } => { - f.write_str("Waiting for remaining refund timelock to expire") + f.write_str("Waiting for reclaim timelock to expire") } - Bob::ReclaimTimelockExpired { .. } => f.write_str("Remaining refund timelock expired"), - Bob::BtcWithholdPublished { .. } => f.write_str("Bitcoin refund burn published"), - Bob::BtcWithheld { .. } => f.write_str("Bitcoin refund burnt"), - Bob::BtcMercyPublished { .. } => f.write_str("Bitcoin final amnesty published"), + Bob::ReclaimTimelockExpired { .. } => f.write_str("Reclaim timelock expired"), + Bob::BtcWithholdPublished { .. } => f.write_str("Bitcoin withhold published"), + Bob::BtcWithheld { .. } => f.write_str("Bitcoin withheld"), + Bob::BtcMercyPublished { .. } => f.write_str("Bitcoin mercy published"), } } } diff --git a/swap-machine/src/alice/mod.rs b/swap-machine/src/alice/mod.rs index 2ba8e65fd0..c83de4eb7a 100644 --- a/swap-machine/src/alice/mod.rs +++ b/swap-machine/src/alice/mod.rs @@ -90,21 +90,21 @@ pub enum AliceState { XmrRefunded { state3: Option>, }, - BtcRefundBurnPublished { + BtcWithholdPublished { state3: Box, }, - BtcRefundBurnConfirmed { + BtcWithholdConfirmed { state3: Box, }, /// Operator has decided to grant final amnesty to Bob. /// This state will publish TxFinalAmnesty and transition to BtcRefundFinalAmnestyPublished. - BtcFinalAmnestyGranted { + BtcMercyGranted { state3: Box, }, - BtcRefundFinalAmnestyPublished { + BtcMercyPublished { state3: Box, }, - BtcRefundFinalAmnestyConfirmed { + BtcMercyConfirmed { state3: Box, }, WaitingForCancelTimelockExpiration { @@ -140,8 +140,8 @@ pub fn is_complete(state: &AliceState) -> bool { | AliceState::BtcPunished { .. } | AliceState::SafelyAborted | AliceState::BtcEarlyRefunded(_) - | AliceState::BtcRefundBurnConfirmed { .. } - | AliceState::BtcRefundFinalAmnestyConfirmed { .. } => true, + | AliceState::BtcWithholdConfirmed { .. } + | AliceState::BtcMercyConfirmed { .. } => true, _ => false, } } @@ -170,14 +170,14 @@ impl fmt::Display for AliceState { AliceState::SafelyAborted => write!(f, "safely aborted"), AliceState::BtcPunishable { .. } => write!(f, "btc is punishable"), AliceState::XmrRefunded { .. } => write!(f, "xmr is refunded"), - AliceState::BtcRefundBurnPublished { .. } => write!(f, "btc refund burn published"), - AliceState::BtcRefundBurnConfirmed { .. } => write!(f, "btc refund burn confirmed"), - AliceState::BtcFinalAmnestyGranted { .. } => write!(f, "btc final amnesty granted"), - AliceState::BtcRefundFinalAmnestyPublished { .. } => { - write!(f, "btc final amnesty published") + AliceState::BtcWithholdPublished { .. } => write!(f, "btc withhold published"), + AliceState::BtcWithholdConfirmed { .. } => write!(f, "btc withheld"), + AliceState::BtcMercyGranted { .. } => write!(f, "btc mercy granted"), + AliceState::BtcMercyPublished { .. } => { + write!(f, "btc mercy published") } - AliceState::BtcRefundFinalAmnestyConfirmed { .. } => { - write!(f, "btc final amnesty confirmed") + AliceState::BtcMercyConfirmed { .. } => { + write!(f, "btc mercy confirmed") } AliceState::WaitingForCancelTimelockExpiration { .. } => { write!(f, "waiting for cancel timelock expiration") diff --git a/swap/src/asb/event_loop.rs b/swap/src/asb/event_loop.rs index 05e2430b1a..162568d2a1 100644 --- a/swap/src/asb/event_loop.rs +++ b/swap/src/asb/event_loop.rs @@ -1085,7 +1085,7 @@ mod service { /// /// This can be called multiple times to update the decision before /// the swap state machine polls for it. - pub async fn set_burn_on_refund(&self, swap_id: Uuid, burn: bool) -> anyhow::Result<()> { + pub async fn set_withhold_deposit(&self, swap_id: Uuid, burn: bool) -> anyhow::Result<()> { let (tx, rx) = oneshot::channel(); self.sender .send(EventLoopRequest::SetBurnOnRefund { @@ -1102,7 +1102,7 @@ mod service { /// /// This transitions the swap to BtcFinalAmnestyGranted and resumes /// the swap state machine to publish the final amnesty transaction. - pub async fn grant_final_amnesty(&self, swap_id: Uuid) -> anyhow::Result<()> { + pub async fn grant_mercy(&self, swap_id: Uuid) -> anyhow::Result<()> { let (tx, rx) = oneshot::channel(); self.sender .send(EventLoopRequest::GrantFinalAmnesty { diff --git a/swap/src/asb/recovery/cancel.rs b/swap/src/asb/recovery/cancel.rs index 9cc8a29cb4..f99118ad5b 100644 --- a/swap/src/asb/recovery/cancel.rs +++ b/swap/src/asb/recovery/cancel.rs @@ -42,11 +42,11 @@ pub async fn cancel( // Alice already in final state | AliceState::BtcRedeemed | AliceState::XmrRefunded { .. } - | AliceState::BtcRefundBurnPublished { .. } - | AliceState::BtcRefundBurnConfirmed { .. } - | AliceState::BtcFinalAmnestyGranted { .. } - | AliceState::BtcRefundFinalAmnestyPublished { .. } - | AliceState::BtcRefundFinalAmnestyConfirmed { .. } + | AliceState::BtcWithholdPublished { .. } + | AliceState::BtcWithholdConfirmed { .. } + | AliceState::BtcMercyGranted { .. } + | AliceState::BtcMercyPublished { .. } + | AliceState::BtcMercyConfirmed { .. } | AliceState::BtcEarlyRefundable { .. } | AliceState::BtcEarlyRefunded(_) | AliceState::BtcPunished { .. } diff --git a/swap/src/asb/recovery/grant_final_amnesty.rs b/swap/src/asb/recovery/grant_final_amnesty.rs index 5a31cdbace..4f5b898607 100644 --- a/swap/src/asb/recovery/grant_final_amnesty.rs +++ b/swap/src/asb/recovery/grant_final_amnesty.rs @@ -12,8 +12,8 @@ pub async fn grant_final_amnesty( let state = db.get_state(swap_id).await?.try_into()?; match state { - AliceState::BtcRefundBurnConfirmed { state3 } => { - let new_state = AliceState::BtcFinalAmnestyGranted { state3 }; + AliceState::BtcWithholdConfirmed { state3 } => { + let new_state = AliceState::BtcMercyGranted { state3 }; db.insert_latest_state(swap_id, new_state.clone().into()) .await?; diff --git a/swap/src/asb/recovery/punish.rs b/swap/src/asb/recovery/punish.rs index d585564d1d..07657ce96a 100644 --- a/swap/src/asb/recovery/punish.rs +++ b/swap/src/asb/recovery/punish.rs @@ -43,11 +43,11 @@ pub async fn punish( | AliceState::BtcLocked { .. } | AliceState::BtcRedeemed { .. } | AliceState::XmrRefunded { .. } - | AliceState::BtcRefundBurnPublished { .. } - | AliceState::BtcRefundBurnConfirmed { .. } - | AliceState::BtcFinalAmnestyGranted { .. } - | AliceState::BtcRefundFinalAmnestyPublished { .. } - | AliceState::BtcRefundFinalAmnestyConfirmed { .. } + | AliceState::BtcWithholdPublished { .. } + | AliceState::BtcWithholdConfirmed { .. } + | AliceState::BtcMercyGranted { .. } + | AliceState::BtcMercyPublished { .. } + | AliceState::BtcMercyConfirmed { .. } | AliceState::BtcPunished { .. } | AliceState::BtcEarlyRefundable { .. } | AliceState::BtcEarlyRefunded(_) diff --git a/swap/src/asb/recovery/redeem.rs b/swap/src/asb/recovery/redeem.rs index 48dcbf00ef..406bb6eb91 100644 --- a/swap/src/asb/recovery/redeem.rs +++ b/swap/src/asb/recovery/redeem.rs @@ -93,11 +93,11 @@ pub async fn redeem( | AliceState::BtcPunishable { .. } | AliceState::BtcRedeemed | AliceState::XmrRefunded { .. } - | AliceState::BtcRefundBurnPublished { .. } - | AliceState::BtcRefundBurnConfirmed { .. } - | AliceState::BtcFinalAmnestyGranted { .. } - | AliceState::BtcRefundFinalAmnestyPublished { .. } - | AliceState::BtcRefundFinalAmnestyConfirmed { .. } + | AliceState::BtcWithholdPublished { .. } + | AliceState::BtcWithholdConfirmed { .. } + | AliceState::BtcMercyGranted { .. } + | AliceState::BtcMercyPublished { .. } + | AliceState::BtcMercyConfirmed { .. } | AliceState::XmrRefundable { .. } | AliceState::BtcEarlyRefundable { .. } | AliceState::BtcEarlyRefunded(_) diff --git a/swap/src/asb/recovery/refund.rs b/swap/src/asb/recovery/refund.rs index 22ba86f6de..1c46c307e2 100644 --- a/swap/src/asb/recovery/refund.rs +++ b/swap/src/asb/recovery/refund.rs @@ -61,11 +61,11 @@ pub async fn refund( AliceState::BtcRedeemTransactionPublished { .. } | AliceState::BtcRedeemed | AliceState::XmrRefunded { .. } - | AliceState::BtcRefundBurnPublished { .. } - | AliceState::BtcRefundBurnConfirmed { .. } - | AliceState::BtcFinalAmnestyGranted { .. } - | AliceState::BtcRefundFinalAmnestyPublished { .. } - | AliceState::BtcRefundFinalAmnestyConfirmed { .. } + | AliceState::BtcWithholdPublished { .. } + | AliceState::BtcWithholdConfirmed { .. } + | AliceState::BtcMercyGranted { .. } + | AliceState::BtcMercyPublished { .. } + | AliceState::BtcMercyConfirmed { .. } | AliceState::BtcEarlyRefundable { .. } | AliceState::BtcEarlyRefunded(_) | AliceState::BtcPunished { .. } diff --git a/swap/src/asb/recovery/safely_abort.rs b/swap/src/asb/recovery/safely_abort.rs index aed7c14d4c..779bf65033 100644 --- a/swap/src/asb/recovery/safely_abort.rs +++ b/swap/src/asb/recovery/safely_abort.rs @@ -34,11 +34,11 @@ pub async fn safely_abort(swap_id: Uuid, db: Arc) -> Result Result<(), ErrorObjectOwned> { + async fn set_withhold_deposit( + &self, + swap_id: Uuid, + burn: bool, + ) -> Result<(), ErrorObjectOwned> { self.event_loop_service - .set_burn_on_refund(swap_id, burn) + .set_withhold_deposit(swap_id, burn) .await .into_json_rpc_result()?; Ok(()) } - async fn grant_final_amnesty(&self, swap_id: Uuid) -> Result<(), ErrorObjectOwned> { + async fn grant_mercy(&self, swap_id: Uuid) -> Result<(), ErrorObjectOwned> { self.event_loop_service - .grant_final_amnesty(swap_id) + .grant_mercy(swap_id) .await .into_json_rpc_result()?; diff --git a/swap/src/protocol/alice/swap.rs b/swap/src/protocol/alice/swap.rs index 5cc834d31c..f38cde10ea 100644 --- a/swap/src/protocol/alice/swap.rs +++ b/swap/src/protocol/alice/swap.rs @@ -802,9 +802,9 @@ where .await .context("Couldn't publish TxRefundBurn")?; - AliceState::BtcRefundBurnPublished { state3 } + AliceState::BtcWithholdPublished { state3 } } - AliceState::BtcRefundBurnPublished { state3 } => { + AliceState::BtcWithholdPublished { state3 } => { let tx_refund_burn = state3 .tx_refund_burn() .context("Can't construct TxRefundBurn even though we published it")?; @@ -816,13 +816,13 @@ where .await .context("Failed to wait for TxRefundBurn to be confirmed")?; - AliceState::BtcRefundBurnConfirmed { state3 } + AliceState::BtcWithholdConfirmed { state3 } } - AliceState::BtcRefundBurnConfirmed { state3 } => { + AliceState::BtcWithholdConfirmed { state3 } => { // Nothing to do here. Final amnesty is triggered manually. - AliceState::BtcRefundBurnConfirmed { state3 } + AliceState::BtcWithholdConfirmed { state3 } } - AliceState::BtcFinalAmnestyGranted { state3 } => { + AliceState::BtcMercyGranted { state3 } => { // Operator has decided to grant final amnesty to Bob let signed_tx = state3 .signed_final_amnesty_transaction() @@ -835,9 +835,9 @@ where tracing::info!("TxFinalAmnesty published successfully"); - AliceState::BtcRefundFinalAmnestyPublished { state3 } + AliceState::BtcMercyPublished { state3 } } - AliceState::BtcRefundFinalAmnestyPublished { state3 } => { + AliceState::BtcMercyPublished { state3 } => { // Wait for TxFinalAmnesty to be confirmed let tx_final_amnesty = state3 .tx_final_amnesty() @@ -852,11 +852,9 @@ where .await .context("Failed to wait for TxFinalAmnesty to be confirmed")?; - AliceState::BtcRefundFinalAmnestyConfirmed { state3 } - } - AliceState::BtcRefundFinalAmnestyConfirmed { state3 } => { - AliceState::BtcRefundFinalAmnestyConfirmed { state3 } + AliceState::BtcMercyConfirmed { state3 } } + AliceState::BtcMercyConfirmed { state3 } => AliceState::BtcMercyConfirmed { state3 }, AliceState::BtcRedeemed => AliceState::BtcRedeemed, AliceState::BtcPunished { state3, diff --git a/swap/tests/harness/mod.rs b/swap/tests/harness/mod.rs index c6cac73f5e..27bba95e37 100644 --- a/swap/tests/harness/mod.rs +++ b/swap/tests/harness/mod.rs @@ -855,7 +855,7 @@ impl TestContext { } pub async fn assert_alice_refund_burn_confirmed(&mut self, state: AliceState) { - assert!(matches!(state, AliceState::BtcRefundBurnConfirmed { .. })); + assert!(matches!(state, AliceState::BtcWithholdConfirmed { .. })); // Same as refunded - Alice still has her XMR back assert_eventual_balance( @@ -878,7 +878,7 @@ impl TestContext { pub async fn assert_alice_final_amnesty_confirmed(&mut self, state: AliceState) { assert!(matches!( state, - AliceState::BtcRefundFinalAmnestyConfirmed { .. } + AliceState::BtcMercyConfirmed { .. } )); // Same as refunded - Alice still has her XMR back @@ -1444,11 +1444,11 @@ pub mod alice_run_until { } pub fn is_btc_refund_burn_confirmed(state: &AliceState) -> bool { - matches!(state, AliceState::BtcRefundBurnConfirmed { .. }) + matches!(state, AliceState::BtcWithholdConfirmed { .. }) } pub fn is_btc_final_amnesty_confirmed(state: &AliceState) -> bool { - matches!(state, AliceState::BtcRefundFinalAmnestyConfirmed { .. }) + matches!(state, AliceState::BtcMercyConfirmed { .. }) } } diff --git a/swap/tests/partial_refund_alice_burns_after_command.rs b/swap/tests/partial_refund_alice_burns_after_command.rs index 45d37fa7a4..68c876a14e 100644 --- a/swap/tests/partial_refund_alice_burns_after_command.rs +++ b/swap/tests/partial_refund_alice_burns_after_command.rs @@ -63,7 +63,7 @@ async fn given_partial_refund_alice_burns_after_command() { // Send RPC command to Alice to burn this swap's amnesty // Must be done AFTER restart (so EventLoopHandle exists) but BEFORE running the swap ctx.alice_rpc_client - .set_burn_on_refund(alice_swap_id, true) + .set_withhold_deposit(alice_swap_id, true) .await .expect("Failed to send burn command to Alice"); diff --git a/swap/tests/partial_refund_alice_grants_final_amnesty.rs b/swap/tests/partial_refund_alice_grants_final_amnesty.rs index 2dede6bbbe..bc95767f8f 100644 --- a/swap/tests/partial_refund_alice_grants_final_amnesty.rs +++ b/swap/tests/partial_refund_alice_grants_final_amnesty.rs @@ -71,7 +71,7 @@ async fn given_partial_refund_alice_grants_final_amnesty() { let alice_state = alice_swap.await??; assert!(matches!( alice_state, - AliceState::BtcRefundBurnConfirmed { .. } + AliceState::BtcWithholdConfirmed { .. } )); let bob_state = bob_state.await??; @@ -79,7 +79,7 @@ async fn given_partial_refund_alice_grants_final_amnesty() { // Simulate alice's controller sending the final amnesty command via `controller` cli ctx.restart_alice().await; - ctx.alice_rpc_client.grant_final_amnesty(swap_id).await?; + ctx.alice_rpc_client.grant_mercy(swap_id).await?; let alice_swap = ctx.alice_next_swap().await; let alice_swap = tokio::spawn(alice::run(alice_swap, FixedRate::default())); @@ -99,7 +99,7 @@ async fn given_partial_refund_alice_grants_final_amnesty() { assert!( matches!( alice_state, - AliceState::BtcRefundFinalAmnestyConfirmed { .. } + AliceState::BtcMercyConfirmed { .. } ), "Actual state: {alice_state}" ); From 97a332dbb518a4f81c73ac20a922bf4351969c9f Mon Sep 17 00:00:00 2001 From: einliterflasche Date: Wed, 4 Feb 2026 14:11:11 +0100 Subject: [PATCH 104/113] try to fix stackoverflow when selecting offer --- .cargo/config.toml | 3 +++ .helix/ignore | 2 ++ Cargo.toml | 2 ++ bitcoin-wallet/src/wallet.rs | 2 +- justfile | 4 ++-- swap/src/cli/api/tauri_bindings.rs | 16 +++++++++++----- 6 files changed, 21 insertions(+), 8 deletions(-) diff --git a/.cargo/config.toml b/.cargo/config.toml index e27705d7a3..9b0737155b 100644 --- a/.cargo/config.toml +++ b/.cargo/config.toml @@ -10,6 +10,9 @@ linker = "arm-linux-gnueabihf-gcc" [target.'cfg(all(windows, target_env = "msvc"))'] rustflags = ["-C", "link-arg=/STACK:8388608"] +[target.'cfg(macos)'] +rustflags = ["-C", "link-arg=-Wl,stack_size,41943040"] + # For the GNU/MinGW tool-chain [target.'cfg(all(windows, target_env = "gnu"))'] linker = "x86_64-w64-mingw32-g++" # Use g++ driver so -lstdc++ gets pulled in automatically diff --git a/.helix/ignore b/.helix/ignore index 47f73afa1a..a53623a692 100644 --- a/.helix/ignore +++ b/.helix/ignore @@ -1,2 +1,4 @@ src-tauri/gen/ +monero-sys/monero/ +monero-sys/monero-depends/ diff --git a/Cargo.toml b/Cargo.toml index e47eacde4a..8cb6fb6f0a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -123,6 +123,8 @@ crunchy = { git = "https://github.com/eira-fransham/crunchy", rev = "ba7b86cea6b [workspace.lints] rust.unused_crate_dependencies = "warn" +# prevents accidental infinite recursion when implementing a trait method by calling a method with the same name +rust.unconditional_recusion = "deny" [profile.release] codegen-units = 1 diff --git a/bitcoin-wallet/src/wallet.rs b/bitcoin-wallet/src/wallet.rs index fb317d935f..fb0cc7eb68 100644 --- a/bitcoin-wallet/src/wallet.rs +++ b/bitcoin-wallet/src/wallet.rs @@ -2237,7 +2237,7 @@ impl EstimateFeeRate for Client { } async fn min_relay_fee(&self) -> Result { - self.min_relay_fee().await + Client::min_relay_fee(self).await } } diff --git a/justfile b/justfile index e9f7279a79..a561eee619 100644 --- a/justfile +++ b/justfile @@ -30,10 +30,10 @@ test-ffi-address: # Start the Tauri app tauri: - cd src-tauri && cargo tauri dev --no-watch -- -vv -- --testnet + cd src-tauri && RUST_MIN_STACK=41943040 RUST_BACKTRACE=1 cargo tauri dev --no-watch -- -vv -- --testnet tauri-mainnet: - cd src-tauri && cargo tauri dev --no-watch -- -vv + cd src-tauri && RUST_BACKTRACE=1 cargo tauri dev --no-watch -- -vv # Install the GUI dependencies gui_install: diff --git a/swap/src/cli/api/tauri_bindings.rs b/swap/src/cli/api/tauri_bindings.rs index d1b227baf8..64f312f7b2 100644 --- a/swap/src/cli/api/tauri_bindings.rs +++ b/swap/src/cli/api/tauri_bindings.rs @@ -481,10 +481,13 @@ impl bitcoin_wallet::BitcoinTauriBackgroundTask for TauriBackgroundProgressHandle { fn update(&self, consumed: u64, total: u64) { - self.update(TauriBitcoinFullScanProgress::Known { - current_index: consumed, - assumed_total: total, - }); + TauriBackgroundProgressHandle::update( + self, + TauriBitcoinFullScanProgress::Known { + current_index: consumed, + assumed_total: total, + }, + ); } fn finish(&self) { @@ -496,7 +499,10 @@ impl bitcoin_wallet::BitcoinTauriBackgroundTask for TauriBackgroundProgressHandle { fn update(&self, consumed: u64, total: u64) { - self.update(TauriBitcoinSyncProgress::Known { consumed, total }); + TauriBackgroundProgressHandle::update( + self, + TauriBitcoinSyncProgress::Known { consumed, total }, + ); } fn finish(&self) { From deeb8ff7871cb37ed489ff396a6e68e93d81b0a6 Mon Sep 17 00:00:00 2001 From: einliterflasche Date: Fri, 6 Feb 2026 15:05:55 +0100 Subject: [PATCH 105/113] fix: electrum pool bug preventing btc tx publishing In get_script_history we treated _any_ error returned by any of the nodes as a total error if the script history returned by the other nodes is empty. This specifically causes the publishing of tx lock to fail/be falsely recognized as failed when at least one of the nodes is unreachable or errors for some other reason. We now accept the result if at least one of the node returns a script history. --- bitcoin-wallet/Cargo.toml | 2 +- bitcoin-wallet/src/wallet.rs | 16 +++++++++------- swap/src/cli/api/request.rs | 19 +++++++++++++------ 3 files changed, 23 insertions(+), 14 deletions(-) diff --git a/bitcoin-wallet/Cargo.toml b/bitcoin-wallet/Cargo.toml index dd4f62af4a..430dc56e6b 100644 --- a/bitcoin-wallet/Cargo.toml +++ b/bitcoin-wallet/Cargo.toml @@ -1,7 +1,7 @@ [package] name = "bitcoin-wallet" version = "0.1.0" -edition = "2021" +edition = "2024" [dependencies] anyhow = { workspace = true } diff --git a/bitcoin-wallet/src/wallet.rs b/bitcoin-wallet/src/wallet.rs index fb0cc7eb68..7776a140ab 100644 --- a/bitcoin-wallet/src/wallet.rs +++ b/bitcoin-wallet/src/wallet.rs @@ -1773,11 +1773,15 @@ impl Client { // Collect all successful history entries from all servers. let mut all_history_items: Vec = Vec::new(); + let mut any_success = false; let mut first_error = None; for result in results { match result { - Ok(history) => all_history_items.extend(history), + Ok(history) => { + any_success = true; + all_history_items.extend(history); + } Err(e) => { if first_error.is_none() { first_error = Some(e); @@ -1786,12 +1790,10 @@ impl Client { } } - // If we got no history items at all, and there was an error, propagate it. - // Otherwise, it's valid for a script to have no history. - if all_history_items.is_empty() { - if let Some(err) = first_error { - return Err(err.into()); - } + // If any of the calls succeeded, that is fine. Only if none + // succeeded we return the error. + if !any_success && let Some(err) = first_error { + return Err(err.into()); } // Use a map to find the best (highest confirmation) entry for each transaction. diff --git a/swap/src/cli/api/request.rs b/swap/src/cli/api/request.rs index 1e86fa26b8..a9fca0aba7 100644 --- a/swap/src/cli/api/request.rs +++ b/swap/src/cli/api/request.rs @@ -1796,13 +1796,20 @@ impl CheckElectrumNodeArgs { return Ok(CheckElectrumNodeResponse { available: false }); }; - // Check if the node is available - let res = - bitcoin_wallet::Client::new(&[url.as_str().to_string()], Duration::from_secs(60)).await; + // Check if the node is available by performing a lightweight RPC call. + // This forces a real connection and TLS handshake (for ssl:// URLs). + let mut client = + match bitcoin_wallet::Client::new(&[url.as_str().to_string()], Duration::from_secs(60)) + .await + { + Ok(client) => client, + Err(_) => return Ok(CheckElectrumNodeResponse { available: false }), + }; - Ok(CheckElectrumNodeResponse { - available: res.is_ok(), - }) + // Force a rpc call for blockchain height. + let available = client.update_state(true).await.is_ok(); + + Ok(CheckElectrumNodeResponse { available }) } } From 7fa10dcabbe4b4a1d24eaa8dff8366cc319b9957 Mon Sep 17 00:00:00 2001 From: einliterflasche Date: Fri, 6 Feb 2026 16:33:54 +0100 Subject: [PATCH 106/113] update transaction weights, field names in mockSwapEvents --- src-gui/src/dev/mockSwapEvents.ts | 2 +- .../deposit_and_choose_offer/MakerOfferItem.tsx | 6 +++--- swap-core/src/bitcoin/mercy.rs | 1 - swap-core/src/bitcoin/partial_refund.rs | 3 +-- swap-core/src/bitcoin/withhold.rs | 3 +-- swap/src/asb/event_loop.rs | 14 +++++++------- 6 files changed, 13 insertions(+), 16 deletions(-) diff --git a/src-gui/src/dev/mockSwapEvents.ts b/src-gui/src/dev/mockSwapEvents.ts index 071c408bba..56e05ce917 100644 --- a/src-gui/src/dev/mockSwapEvents.ts +++ b/src-gui/src/dev/mockSwapEvents.ts @@ -61,7 +61,7 @@ const MOCK_QUOTE_PARTIAL_REFUND: BidQuote = { price: 0.0068, min_quantity: 5_000_000, max_quantity: 200_000_000, - refund_policy: { type: "PartialRefund", content: { taker_refund_ratio: 0.98 } }, + refund_policy: { type: "PartialRefund", content: { anti_spam_deposit_ratio: 0.02 } }, }; const MOCK_QUOTE_WITH_ADDRESS_PARTIAL: QuoteWithAddress = { diff --git a/src-gui/src/renderer/components/pages/swap/swap/init/deposit_and_choose_offer/MakerOfferItem.tsx b/src-gui/src/renderer/components/pages/swap/swap/init/deposit_and_choose_offer/MakerOfferItem.tsx index 1cc3e06b36..0a6eff34a4 100644 --- a/src-gui/src/renderer/components/pages/swap/swap/init/deposit_and_choose_offer/MakerOfferItem.tsx +++ b/src-gui/src/renderer/components/pages/swap/swap/init/deposit_and_choose_offer/MakerOfferItem.tsx @@ -16,7 +16,7 @@ function getRefundPercentage(policy: RefundPolicyWire): number { if (policy.type === "FullRefund") { return 100; } - return policy.content.taker_refund_ratio * 100; + return policy.content.anti_spam_deposit_ratio * 100; } export default function MakerOfferItem({ @@ -192,10 +192,10 @@ export default function MakerOfferItem({ } function EarnestDepositChip(quote: BidQuote) { - const full_refund: boolean = quote.refund_policy.type === "FullRefund" ? true : quote.refund_policy.content.taker_refund_ratio === 1; + const full_refund: boolean = quote.refund_policy.type === "FullRefund" ? true : quote.refund_policy.content.anti_spam_deposit_ratio === 0; // Rounded to 0.001 precision const earnest_deposit_ratio = Math.round( - (quote.refund_policy.type === "FullRefund" ? 0 : 1 - quote.refund_policy.content?.taker_refund_ratio) + (quote.refund_policy.type === "FullRefund" ? 0 : quote.refund_policy.content?.anti_spam_deposit_ratio) * 1000 ) / 1000; const tooltip_text = full_refund ? "100% refund cryptographically guaranteed." : `If the swap is refunded, the maker may choose to freeze ${earnest_deposit_ratio * 100}% of your refund. This is allows them to protect themselves against griefing.`; diff --git a/swap-core/src/bitcoin/mercy.rs b/swap-core/src/bitcoin/mercy.rs index ce54735944..1630c621cf 100644 --- a/swap-core/src/bitcoin/mercy.rs +++ b/swap-core/src/bitcoin/mercy.rs @@ -129,7 +129,6 @@ impl TxMercy { Ok(tx) } - // TODO: calculate actual weight pub fn weight() -> Weight { Weight::from_wu(548) } diff --git a/swap-core/src/bitcoin/partial_refund.rs b/swap-core/src/bitcoin/partial_refund.rs index 78a184b987..b45861cdd6 100644 --- a/swap-core/src/bitcoin/partial_refund.rs +++ b/swap-core/src/bitcoin/partial_refund.rs @@ -255,9 +255,8 @@ impl TxPartialRefund { Ok(sig) } - // TODO: calculate actual weight pub fn weight() -> Weight { - Weight::from_wu(548) + Weight::from_wu(720) } } diff --git a/swap-core/src/bitcoin/withhold.rs b/swap-core/src/bitcoin/withhold.rs index cb26c185d9..ddc39be5e3 100644 --- a/swap-core/src/bitcoin/withhold.rs +++ b/swap-core/src/bitcoin/withhold.rs @@ -189,9 +189,8 @@ impl TxWithhold { } } - // TODO: calculate actual weight pub fn weight() -> Weight { - Weight::from_wu(548) + Weight::from_wu(596) } } diff --git a/swap/src/asb/event_loop.rs b/swap/src/asb/event_loop.rs index 162568d2a1..b72b8c9848 100644 --- a/swap/src/asb/event_loop.rs +++ b/swap/src/asb/event_loop.rs @@ -259,7 +259,7 @@ where }; // TODO: propagate error to the swap_setup routine instead of swallowing it - let (btc_amnesty_amount, should_publish_tx_refund_burn )= match apply_bitcoin_amnesty_policy(btc, &self.refund_policy) { + let (btc_amnesty_amount, should_publish_tx_refund_burn )= match apply_anti_spam_policy(btc, &self.refund_policy) { Ok(amount) => amount, Err(error) => { tracing::error!("Swap request will be ignored because we were unable to create wallet snapshot for swap: {:#}", error); @@ -937,10 +937,10 @@ impl EventLoopHandle { } /// For a new swap of `swap_amount`, this function calculates how much -/// Bitcoin should go into the amnesty-lock incase of a refund. -/// Returns ZERO when taker_refund_ratio is 1.0 (100%), indicating full refund. -/// Also returns whether or not to burn the the amnesty output if the taker refunds. -fn apply_bitcoin_amnesty_policy( +/// Bitcoin should go into the anti spam deposit incase of a refund. +/// Returns ZERO when anti_spam_deposit_ratio is 0, indicating immediate and full refund. +/// Also returns whether or not to always withhold the the anti spam deposit output if the taker refunds. +fn apply_anti_spam_policy( swap_amount: bitcoin::Amount, refund_policy: &RefundPolicy, ) -> Result<(bitcoin::Amount, bool)> { @@ -951,14 +951,14 @@ fn apply_bitcoin_amnesty_policy( return Ok((bitcoin::Amount::ZERO, should_always_withhold)); } - let btc_amnesty_ratio = refund_policy.anti_spam_deposit_ratio; + let btc_anti_spam_deposit_ratio = refund_policy.anti_spam_deposit_ratio; let amount_sats = swap_amount.to_sat(); let amount_decimal = Decimal::from_u64(amount_sats).context("Decimal overflowed by Bitcoin sats")?; let btc_amnesty_decimal = amount_decimal - .checked_mul(btc_amnesty_ratio) + .checked_mul(btc_anti_spam_deposit_ratio) .context("Decimal overflow when computing amnesty amount in sats")? .floor(); let btc_amnesty_sats = btc_amnesty_decimal From 0cc15ad734699348bb5c49bc9c600f127ee07fdb Mon Sep 17 00:00:00 2001 From: einliterflasche Date: Fri, 6 Feb 2026 17:51:24 +0100 Subject: [PATCH 107/113] alice: sanity check for anti spam deposit ratio alice+bob: sanity check anti spam deposit (greater than fees) --- swap-asb/src/main.rs | 17 +++-------------- swap-env/src/config.rs | 34 ++++++++++++++++++++++++++++++++++ swap-machine/src/alice/mod.rs | 25 +++++++++++++++++++++++++ swap-machine/src/bob/mod.rs | 27 +++++++++++++++++++++++++++ 4 files changed, 89 insertions(+), 14 deletions(-) diff --git a/swap-asb/src/main.rs b/swap-asb/src/main.rs index 94644153b0..859214a64a 100644 --- a/swap-asb/src/main.rs +++ b/swap-asb/src/main.rs @@ -38,7 +38,8 @@ use swap::protocol::alice::{run, AliceState, TipConfig}; use swap::protocol::{Database, State}; use swap::seed::Seed; use swap_env::config::{ - initial_setup, query_user_for_initial_config, read_config, Config, ConfigNotInitialized, + initial_setup, query_user_for_initial_config, read_config, validate_config, Config, + ConfigNotInitialized, }; use swap_feed; use swap_machine::alice::is_complete; @@ -140,19 +141,7 @@ pub async fn main() -> Result<()> { // Initialize tracing initialize_tracing(json, &config, trace)?; - // Check for conflicting env / config values - if config.monero.network != env_config.monero_network { - bail!(format!( - "Expected monero network in config file to be {:?} but was {:?}", - env_config.monero_network, config.monero.network - )); - } - if config.bitcoin.network != env_config.bitcoin_network { - bail!(format!( - "Expected bitcoin network in config file to be {:?} but was {:?}", - env_config.bitcoin_network, config.bitcoin.network - )); - } + validate_config(&config, env_config)?; let seed = Seed::from_file_or_generate(&config.data.dir) .await diff --git a/swap-env/src/config.rs b/swap-env/src/config.rs index f8f73321df..a2bbe3c3e8 100644 --- a/swap-env/src/config.rs +++ b/swap-env/src/config.rs @@ -214,6 +214,40 @@ pub fn read_config(config_path: PathBuf) -> Result Result<()> { + if config.monero.network != env_config.monero_network { + bail!( + "Expected monero network in config file to be {:?} but was {:?}", + env_config.monero_network, + config.monero.network + ); + } + if config.bitcoin.network != env_config.bitcoin_network { + bail!( + "Expected bitcoin network in config file to be {:?} but was {:?}", + env_config.bitcoin_network, + config.bitcoin.network + ); + } + + let ratio = config.maker.refund_policy.anti_spam_deposit_ratio; + if ratio < Decimal::ZERO || ratio > Decimal::ONE { + bail!("anti_spam_deposit_ratio must be between 0 and 1, got {ratio}"); + } + if ratio > MAX_ANTI_SPAM_DEPOSIT_RATIO { + bail!( + "anti_spam_deposit_ratio of {ratio} exceeds maximum of {MAX_ANTI_SPAM_DEPOSIT_RATIO}. \ + Such a high deposit ratio is implausible and likely a misconfiguration." + ); + } + + Ok(()) +} + pub fn initial_setup(config_path: PathBuf, config: Config) -> Result<()> { let toml = toml::to_string(&config)?; diff --git a/swap-machine/src/alice/mod.rs b/swap-machine/src/alice/mod.rs index c83de4eb7a..37301e8e07 100644 --- a/swap-machine/src/alice/mod.rs +++ b/swap-machine/src/alice/mod.rs @@ -271,6 +271,31 @@ impl State0 { bail!("Bob's dleq proof doesn't verify") } + let amnesty_amount = self + .btc_amnesty_amount + .context("btc_amnesty_amount missing for new swap")?; + + if amnesty_amount > bitcoin::Amount::ZERO { + let tx_refund_burn_fee = self + .tx_refund_burn_fee + .context("tx_refund_burn_fee missing for new swap")?; + + let min_amnesty = msg.tx_partial_refund_fee + + msg.tx_refund_amnesty_fee + + tx_refund_burn_fee + + msg.tx_final_amnesty_fee; + if amnesty_amount < min_amnesty { + bail!( + "Amnesty amount ({amnesty_amount}) is less than the combined fees \ + for TxPartialRefund ({}), TxReclaim ({}), TxWithhold ({tx_refund_burn_fee}), \ + and TxMercy ({}). The deposit would be consumed by fees.", + msg.tx_partial_refund_fee, + msg.tx_refund_amnesty_fee, + msg.tx_final_amnesty_fee, + ); + } + } + let v = self.v_a + msg.v_b; Ok(( diff --git a/swap-machine/src/bob/mod.rs b/swap-machine/src/bob/mod.rs index 8f535115c9..d8b78701af 100644 --- a/swap-machine/src/bob/mod.rs +++ b/swap-machine/src/bob/mod.rs @@ -386,6 +386,33 @@ impl State0 { bail!("Alice's dleq proof doesn't verify") } + if msg.amnesty_amount > bitcoin::Amount::ZERO { + let tx_partial_refund_fee = self + .tx_partial_refund_fee + .context("tx_partial_refund_fee missing for new swap")?; + let tx_refund_amnesty_fee = self + .tx_refund_amnesty_fee + .context("tx_refund_amnesty_fee missing for new swap")?; + let tx_final_amnesty_fee = self + .tx_final_amnesty_fee + .context("tx_final_amnesty_fee missing for new swap")?; + + let min_amnesty = tx_partial_refund_fee + + tx_refund_amnesty_fee + + msg.tx_refund_burn_fee + + tx_final_amnesty_fee; + if msg.amnesty_amount < min_amnesty { + bail!( + "Amnesty amount ({}) is less than the combined fees \ + for TxPartialRefund ({tx_partial_refund_fee}), TxReclaim ({tx_refund_amnesty_fee}), \ + TxWithhold ({}), and TxMercy ({tx_final_amnesty_fee}). \ + The deposit would be consumed by fees.", + msg.amnesty_amount, + msg.tx_refund_burn_fee, + ); + } + } + let tx_lock = swap_core::bitcoin::TxLock::new( wallet, self.btc, From f7ced4edec1b37399c0aced9481d5bdd81f171dd Mon Sep 17 00:00:00 2001 From: einliterflasche Date: Fri, 6 Feb 2026 18:22:56 +0100 Subject: [PATCH 108/113] alice+bob: add sanity checks for amnesty amount Alice and Bob now check at swap setup that the amnesty amount is either zero or at least greather than the sum of the fees of TxPartialRefund, TxReclaim, TxWithhold and TxMercy. An upper bound (currently 20% of the lock amount) is also introduced. --- Cargo.lock | 1 + bitcoin-wallet/src/wallet.rs | 3 +- swap-env/src/config.rs | 2 +- swap-machine/Cargo.toml | 1 + swap-machine/src/alice/mod.rs | 31 ++++------ swap-machine/src/bob/mod.rs | 24 +++----- swap-machine/src/common/mod.rs | 105 ++++++++++++++++++++++++++++++++- 7 files changed, 129 insertions(+), 38 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index fff222ecce..6838f36ab3 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -10751,6 +10751,7 @@ dependencies = [ "monero-oxide-ext", "rand 0.8.5", "rand_chacha 0.3.1", + "rust_decimal", "serde", "sha2", "sigma_fun", diff --git a/bitcoin-wallet/src/wallet.rs b/bitcoin-wallet/src/wallet.rs index 7776a140ab..fd63abe8b9 100644 --- a/bitcoin-wallet/src/wallet.rs +++ b/bitcoin-wallet/src/wallet.rs @@ -82,7 +82,8 @@ pub trait BitcoinWalletSeed { const TWENTY_PERCENT: Decimal = Decimal::from_parts(20, 0, 0, false, 2); pub const MAX_RELATIVE_TX_FEE: Decimal = TWENTY_PERCENT; pub const MAX_ABSOLUTE_TX_FEE: Amount = Amount::from_sat(100_000); -pub const MIN_ABSOLUTE_TX_FEE: Amount = Amount::from_sat(1000); +pub const MIN_ABSOLUTE_TX_FEE_SATS: u64 = 1000; +pub const MIN_ABSOLUTE_TX_FEE: Amount = Amount::from_sat(MIN_ABSOLUTE_TX_FEE_SATS); pub const DUST_AMOUNT: Amount = Amount::from_sat(546); /// This is our wrapper around a bdk wallet and a corresponding diff --git a/swap-env/src/config.rs b/swap-env/src/config.rs index a2bbe3c3e8..8f73ed73eb 100644 --- a/swap-env/src/config.rs +++ b/swap-env/src/config.rs @@ -216,7 +216,7 @@ pub fn read_config(config_path: PathBuf) -> Result Result<()> { if config.monero.network != env_config.monero_network { diff --git a/swap-machine/Cargo.toml b/swap-machine/Cargo.toml index f9714c621f..9f4f70da2c 100644 --- a/swap-machine/Cargo.toml +++ b/swap-machine/Cargo.toml @@ -14,6 +14,7 @@ libp2p = { workspace = true } monero-address = { workspace = true } monero-oxide-ext = { path = "../monero-oxide-ext" } rand = { workspace = true } +rust_decimal = { workspace = true } rand_chacha = { workspace = true } serde = { workspace = true } sha2 = { workspace = true } diff --git a/swap-machine/src/alice/mod.rs b/swap-machine/src/alice/mod.rs index 37301e8e07..d9bf8c4f61 100644 --- a/swap-machine/src/alice/mod.rs +++ b/swap-machine/src/alice/mod.rs @@ -274,27 +274,18 @@ impl State0 { let amnesty_amount = self .btc_amnesty_amount .context("btc_amnesty_amount missing for new swap")?; + let tx_refund_burn_fee = self + .tx_refund_burn_fee + .context("tx_refund_burn_fee missing for new swap")?; - if amnesty_amount > bitcoin::Amount::ZERO { - let tx_refund_burn_fee = self - .tx_refund_burn_fee - .context("tx_refund_burn_fee missing for new swap")?; - - let min_amnesty = msg.tx_partial_refund_fee - + msg.tx_refund_amnesty_fee - + tx_refund_burn_fee - + msg.tx_final_amnesty_fee; - if amnesty_amount < min_amnesty { - bail!( - "Amnesty amount ({amnesty_amount}) is less than the combined fees \ - for TxPartialRefund ({}), TxReclaim ({}), TxWithhold ({tx_refund_burn_fee}), \ - and TxMercy ({}). The deposit would be consumed by fees.", - msg.tx_partial_refund_fee, - msg.tx_refund_amnesty_fee, - msg.tx_final_amnesty_fee, - ); - } - } + crate::common::sanity_check_amnesty_amount( + self.btc, + amnesty_amount, + msg.tx_partial_refund_fee, + msg.tx_refund_amnesty_fee, + tx_refund_burn_fee, + msg.tx_final_amnesty_fee, + )?; let v = self.v_a + msg.v_b; diff --git a/swap-machine/src/bob/mod.rs b/swap-machine/src/bob/mod.rs index d8b78701af..6f36f1e1c1 100644 --- a/swap-machine/src/bob/mod.rs +++ b/swap-machine/src/bob/mod.rs @@ -386,7 +386,7 @@ impl State0 { bail!("Alice's dleq proof doesn't verify") } - if msg.amnesty_amount > bitcoin::Amount::ZERO { + { let tx_partial_refund_fee = self .tx_partial_refund_fee .context("tx_partial_refund_fee missing for new swap")?; @@ -397,20 +397,14 @@ impl State0 { .tx_final_amnesty_fee .context("tx_final_amnesty_fee missing for new swap")?; - let min_amnesty = tx_partial_refund_fee - + tx_refund_amnesty_fee - + msg.tx_refund_burn_fee - + tx_final_amnesty_fee; - if msg.amnesty_amount < min_amnesty { - bail!( - "Amnesty amount ({}) is less than the combined fees \ - for TxPartialRefund ({tx_partial_refund_fee}), TxReclaim ({tx_refund_amnesty_fee}), \ - TxWithhold ({}), and TxMercy ({tx_final_amnesty_fee}). \ - The deposit would be consumed by fees.", - msg.amnesty_amount, - msg.tx_refund_burn_fee, - ); - } + crate::common::sanity_check_amnesty_amount( + self.btc, + msg.amnesty_amount, + tx_partial_refund_fee, + tx_refund_amnesty_fee, + msg.tx_refund_burn_fee, + tx_final_amnesty_fee, + )?; } let tx_lock = swap_core::bitcoin::TxLock::new( diff --git a/swap-machine/src/common/mod.rs b/swap-machine/src/common/mod.rs index f1c09e3670..d3d2424e4b 100644 --- a/swap-machine/src/common/mod.rs +++ b/swap-machine/src/common/mod.rs @@ -2,7 +2,8 @@ use crate::alice::AliceState; use crate::alice::is_complete as alice_is_complete; use crate::bob::BobState; use crate::bob::is_complete as bob_is_complete; -use anyhow::Result; +use anyhow::{Result, bail}; +use rust_decimal::prelude::FromPrimitive; use async_trait::async_trait; use libp2p::{Multiaddr, PeerId}; use serde::{Deserialize, Serialize}; @@ -91,6 +92,53 @@ pub struct Message4 { pub tx_final_amnesty_sig: Option, } +/// Validates that the amnesty amount is within sane bounds. +/// +/// - If amnesty is zero, this is a full-refund swap and no checks are needed. +/// - Otherwise, the amnesty must cover all transaction fees that could be spent +/// from it (TxPartialRefund, TxReclaim, TxWithhold, TxMercy). +/// - The amnesty ratio (amnesty / lock amount) must not exceed +/// [`swap_env::config::MAX_ANTI_SPAM_DEPOSIT_RATIO`]. +pub fn sanity_check_amnesty_amount( + lock_amount: bitcoin::Amount, + amnesty_amount: bitcoin::Amount, + tx_partial_refund_fee: bitcoin::Amount, + tx_reclaim_fee: bitcoin::Amount, + tx_withhold_fee: bitcoin::Amount, + tx_mercy_fee: bitcoin::Amount, +) -> Result<()> { + if amnesty_amount == bitcoin::Amount::ZERO { + return Ok(()); + } + + let min_amnesty = + tx_partial_refund_fee + tx_reclaim_fee + tx_withhold_fee + tx_mercy_fee; + if amnesty_amount < min_amnesty { + bail!( + "Amnesty amount ({amnesty_amount}) is less than the combined fees \ + for TxPartialRefund ({tx_partial_refund_fee}), TxReclaim ({tx_reclaim_fee}), \ + TxWithhold ({tx_withhold_fee}), and TxMercy ({tx_mercy_fee}). \ + The deposit would be consumed by fees.", + ); + } + + let amnesty_sats = rust_decimal::Decimal::from_u64(amnesty_amount.to_sat()) + .expect("amnesty sats to fit in Decimal"); + let lock_sats = rust_decimal::Decimal::from_u64(lock_amount.to_sat()) + .expect("lock sats to fit in Decimal"); + let ratio = amnesty_sats / lock_sats; + + if ratio > swap_env::config::MAX_ANTI_SPAM_DEPOSIT_RATIO { + bail!( + "Amnesty ratio ({ratio}) exceeds maximum allowed ratio of {}. \ + The requested deposit is unreasonably high.", + swap_env::config::MAX_ANTI_SPAM_DEPOSIT_RATIO, + ); + } + + Ok(()) +} + #[allow(clippy::large_enum_variant)] #[derive(Clone, Debug, PartialEq)] pub enum State { @@ -199,3 +247,58 @@ pub trait Database { ) -> Result>; async fn has_swap(&self, swap_id: Uuid) -> Result; } + +#[cfg(test)] +mod tests { + use super::*; + use bitcoin_wallet::{MIN_ABSOLUTE_TX_FEE, MIN_ABSOLUTE_TX_FEE_SATS}; + + /// 1 BTC lock amount. + const LOCK: bitcoin::Amount = bitcoin::Amount::from_sat(100_000_000); + const FEE: bitcoin::Amount = MIN_ABSOLUTE_TX_FEE; + /// Sum of all 4 fees (the lower bound). + const FEE_FLOOR: u64 = MIN_ABSOLUTE_TX_FEE_SATS * 4; + /// 20% of LOCK (the upper bound). + const RATIO_CEILING: u64 = 20_000_000; + + #[test] + fn zero_amnesty_always_passes() { + sanity_check_amnesty_amount(LOCK, bitcoin::Amount::ZERO, FEE, FEE, FEE, FEE) + .expect("zero amnesty should always pass"); + } + + #[test] + fn reject_amnesty_below_fee_floor() { + let amnesty = bitcoin::Amount::from_sat(FEE_FLOOR - 1); + sanity_check_amnesty_amount(LOCK, amnesty, FEE, FEE, FEE, FEE) + .expect_err("amnesty below fee floor should be rejected"); + } + + #[test] + fn pass_amnesty_at_fee_floor() { + let amnesty = bitcoin::Amount::from_sat(FEE_FLOOR); + sanity_check_amnesty_amount(LOCK, amnesty, FEE, FEE, FEE, FEE) + .expect("amnesty exactly at fee floor should pass"); + } + + #[test] + fn pass_medium_amnesty() { + let amnesty = bitcoin::Amount::from_sat(10_000_000); + sanity_check_amnesty_amount(LOCK, amnesty, FEE, FEE, FEE, FEE) + .expect("10% amnesty should pass"); + } + + #[test] + fn pass_amnesty_at_ratio_ceiling() { + let amnesty = bitcoin::Amount::from_sat(RATIO_CEILING); + sanity_check_amnesty_amount(LOCK, amnesty, FEE, FEE, FEE, FEE) + .expect("amnesty exactly at 20% ratio should pass"); + } + + #[test] + fn reject_amnesty_above_ratio_ceiling() { + let amnesty = bitcoin::Amount::from_sat(RATIO_CEILING + 1); + sanity_check_amnesty_amount(LOCK, amnesty, FEE, FEE, FEE, FEE) + .expect_err("amnesty above 20% ratio should be rejected"); + } +} From 492fb4625028d2b325d26ac376538e12bd9e40fd Mon Sep 17 00:00:00 2001 From: einliterflasche Date: Fri, 6 Feb 2026 19:26:34 +0100 Subject: [PATCH 109/113] swap: delete unit tests regarding buy-xmr (which was removed) --- swap/src/cli/command.rs | 110 ---------------------------------------- 1 file changed, 110 deletions(-) diff --git a/swap/src/cli/command.rs b/swap/src/cli/command.rs index 3185caa949..383afae150 100644 --- a/swap/src/cli/command.rs +++ b/swap/src/cli/command.rs @@ -397,7 +397,6 @@ mod tests { use super::*; use crate::cli::api::api_test::*; - use swap_serde::monero::address::MoneroAddressNetworkMismatch; const BINARY_NAME: &str = "swap"; const ARGS_DATA_DIR: &str = "/tmp/dir/"; @@ -416,53 +415,6 @@ mod tests { .unwrap(); } - #[tokio::test] - async fn given_buy_xmr_on_mainnet_with_testnet_address_then_fails() { - let raw_ars = [ - BINARY_NAME, - "buy-xmr", - "--receive-address", - MONERO_STAGENET_ADDRESS, - "--change-address", - BITCOIN_TESTNET_ADDRESS, - "--seller", - MULTI_ADDRESS, - ]; - - let err = parse_args_and_apply_defaults(raw_ars).await.unwrap_err(); - assert_eq!( - err.downcast_ref::().unwrap(), - &MoneroAddressNetworkMismatch { - expected: monero_address::Network::Mainnet, - actual: monero_address::Network::Stagenet - } - ); - } - - #[tokio::test] - async fn given_buy_xmr_on_testnet_with_mainnet_address_then_fails() { - let raw_ars = [ - BINARY_NAME, - "--testnet", - "buy-xmr", - "--receive-address", - MONERO_MAINNET_ADDRESS, - "--change-address", - BITCOIN_MAINNET_ADDRESS, - "--seller", - MULTI_ADDRESS, - ]; - - let err = parse_args_and_apply_defaults(raw_ars).await.unwrap_err(); - assert_eq!( - err.downcast_ref::().unwrap(), - &MoneroAddressNetworkMismatch { - expected: monero_address::Network::Stagenet, - actual: monero_address::Network::Mainnet - } - ); - } - #[tokio::test] async fn given_resume_on_mainnet_then_defaults_to_mainnet() { let raw_ars = [BINARY_NAME, "resume", "--swap-id", SWAP_ID]; @@ -602,66 +554,4 @@ mod tests { simple_positive(&raw_ars, (true, true, None), cli_cmd).await; } - #[tokio::test] - async fn only_bech32_addresses_mainnet_are_allowed() { - // TODO: not apply defaults - let mut raw_ars = [ - BINARY_NAME, - "buy-xmr", - "--change-address", - "", - "--receive-address", - MONERO_MAINNET_ADDRESS, - "--seller", - MULTI_ADDRESS, - ]; - raw_ars[3] = "1A5btpLKZjgYm8R22rJAhdbTFVXgSRA2Mp"; - parse_args_and_custom(raw_ars, async |_, _, _| unreachable!()) - .await - .unwrap_err(); - - raw_ars[3] = "36vn4mFhmTXn7YcNwELFPxTXhjorw2ppu2"; - parse_args_and_custom(raw_ars, async |_, _, _| unreachable!()) - .await - .unwrap_err(); - - raw_ars[3] = "bc1qh4zjxrqe3trzg7s6m7y67q2jzrw3ru5mx3z7j3"; - let ParseResult::Success(_) = parse_args_and_custom(raw_ars, async |_, _, _| Ok(())) - .await - .unwrap() - else { - panic!() - }; - } - - #[tokio::test] - async fn only_bech32_addresses_testnet_are_allowed() { - let mut raw_ars = [ - BINARY_NAME, - "--testnet", - "buy-xmr", - "--change-address", - "", - "--receive-address", - MONERO_STAGENET_ADDRESS, - "--seller", - MULTI_ADDRESS, - ]; - raw_ars[4] = "n2czxyeFCQp9e8WRyGpy4oL4YfQAeKkkUH"; - parse_args_and_custom(raw_ars, async |_, _, _| unreachable!()) - .await - .unwrap_err(); - raw_ars[4] = "2ND9a4xmQG89qEWG3ETRuytjKpLmGrW7Jvf"; - parse_args_and_custom(raw_ars, async |_, _, _| unreachable!()) - .await - .unwrap_err(); - - raw_ars[4] = "tb1q958vfh3wkdp232pktq8zzvmttyxeqnj80zkz3v"; - let ParseResult::Success(_) = parse_args_and_custom(raw_ars, async |_, _, _| Ok(())) - .await - .unwrap() - else { - panic!() - }; - } } From db2858f14d827e1dea8ebe121df7cf35d09d2ebe Mon Sep 17 00:00:00 2001 From: einliterflasche Date: Fri, 6 Feb 2026 19:50:47 +0100 Subject: [PATCH 110/113] add some code style guidelines to CONTRIBUTING.md --- AGENTS.md | 1 + CONTRIBUTING.md | 20 ++++++++++++++++++++ 2 files changed, 21 insertions(+) diff --git a/AGENTS.md b/AGENTS.md index 4fcf62b2ca..5b5c206e31 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -1,3 +1,4 @@ + - Read CONTRIBUTING.md and the code style guidelines! - When asked about libp2p, check if a rust-libp2p folder exists which contains the cloned rust libp2p codebase. Read through to figure out what the best response it. If its a question about best practice when implementing protocols read @rust-libp2p/protocols/ specificially. - Never do `cargo clean`. Building `monero-sys` takes ages, and cleaning the build cache will cause a full rebuilt (horrible). `cargo clean` has never fixed a build problem. diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 779e37b085..d50ba6b8dc 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -6,6 +6,7 @@ Thank you for wanting to contribute to this project! There are a couple of things we are going to look out for in PRs and knowing them upfront is going to reduce the number of times we will be going back and forth, making things more efficient. +0. **Read and comply with our [AI Policy](AI_POLICY.md)** 1. We have CI checks in place that validate formatting and code style. Make sure the branch is building with `--all-features` and `--all-targets` without errors and all tests are passed. @@ -21,3 +22,22 @@ There are a couple of things we are going to look out for in PRs and knowing the When contributing a feature request, please focus on your _problem_ as much as possible. It is okay to include ideas on how the feature should be implemented but they should be 2nd nature of your request. +## Code style + +### General + + - File structure + - The content of each file should be ordered in terms of importance / level of abstraction + - Public `struct`s, `enum`s and important constants should be at the top + - `impl` blocks should be below the type declarations + - Both the type declaration part and the implementation part of the file should be internally ordered by level of abstraction/ importance + - For example, `fn main` should always be at least at the top of the implementation + - Prefer early returns over nested `if`/`match` statements + - Don't use fallback values or silent failures + +### Rust + + - Use `cargo fmt` for formatting + - Make use of the powerful `if let` and `let ... else` pattern to enable early returns + - Make use of anyhows `.context` method and the `?` operator for concise error reporting + From 2a9fa9a688fe97960cfc5b1ef23ed375d89ba13c Mon Sep 17 00:00:00 2001 From: einliterflasche Date: Fri, 6 Feb 2026 19:51:44 +0100 Subject: [PATCH 111/113] fix typo - actually enable deny(unconditioned_recursion) --- Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index 8cb6fb6f0a..0ebd9f497b 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -124,7 +124,7 @@ crunchy = { git = "https://github.com/eira-fransham/crunchy", rev = "ba7b86cea6b [workspace.lints] rust.unused_crate_dependencies = "warn" # prevents accidental infinite recursion when implementing a trait method by calling a method with the same name -rust.unconditional_recusion = "deny" +rust.unconditional_recursion = "deny" [profile.release] codegen-units = 1 From 53675cb20b6620ae97d574e7f805164dc6a0c389 Mon Sep 17 00:00:00 2001 From: einliterflasche Date: Fri, 6 Feb 2026 19:52:09 +0100 Subject: [PATCH 112/113] update unit tests --- swap-core/proptest-regressions/bitcoin.txt | 7 +++ swap-machine/src/common/mod.rs | 9 ++- swap/src/asb/event_loop.rs | 31 ++++++++- swap/src/protocol/alice/swap.rs | 73 ++++++++++++++++++++-- 4 files changed, 109 insertions(+), 11 deletions(-) create mode 100644 swap-core/proptest-regressions/bitcoin.txt diff --git a/swap-core/proptest-regressions/bitcoin.txt b/swap-core/proptest-regressions/bitcoin.txt new file mode 100644 index 0000000000..29be8f4aca --- /dev/null +++ b/swap-core/proptest-regressions/bitcoin.txt @@ -0,0 +1,7 @@ +# Seeds for failure cases proptest has generated in the past. It is +# automatically read and these particular cases re-run before any +# novel cases are generated. +# +# It is recommended to check this file in to source control so that +# everyone who runs the test benefits from these saved cases. +cc dd09112d3bef8c0fb013a7fc8a1e2bb68e9a5db3041b954c7b7723e8fcace068 # shrinks to funding_amount = 3000, num_utxos = 1, sats_per_vb = 1, key = Xpriv { network: Test, depth: 0, parent_fingerprint: 00000000, child_number: Normal { index: 0 }, private_key: SecretKey(#dc659542f09c329f), chain_code: c94a49bbee951f7f7401c801db73c23cf17b0b3aa2f6246a8616fe2205a6ca51 }, alice = Point(0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798), bob = Point(02c6047f9441ed7d6d3045406e95c07cd85c778e4b8cef3ca7abac09b95c709ee5) diff --git a/swap-machine/src/common/mod.rs b/swap-machine/src/common/mod.rs index d3d2424e4b..61a5f900a6 100644 --- a/swap-machine/src/common/mod.rs +++ b/swap-machine/src/common/mod.rs @@ -3,9 +3,9 @@ use crate::alice::is_complete as alice_is_complete; use crate::bob::BobState; use crate::bob::is_complete as bob_is_complete; use anyhow::{Result, bail}; -use rust_decimal::prelude::FromPrimitive; use async_trait::async_trait; use libp2p::{Multiaddr, PeerId}; +use rust_decimal::prelude::FromPrimitive; use serde::{Deserialize, Serialize}; use sha2::Sha256; use sigma_fun::HashTranscript; @@ -111,8 +111,7 @@ pub fn sanity_check_amnesty_amount( return Ok(()); } - let min_amnesty = - tx_partial_refund_fee + tx_reclaim_fee + tx_withhold_fee + tx_mercy_fee; + let min_amnesty = tx_partial_refund_fee + tx_reclaim_fee + tx_withhold_fee + tx_mercy_fee; if amnesty_amount < min_amnesty { bail!( "Amnesty amount ({amnesty_amount}) is less than the combined fees \ @@ -124,8 +123,8 @@ pub fn sanity_check_amnesty_amount( let amnesty_sats = rust_decimal::Decimal::from_u64(amnesty_amount.to_sat()) .expect("amnesty sats to fit in Decimal"); - let lock_sats = rust_decimal::Decimal::from_u64(lock_amount.to_sat()) - .expect("lock sats to fit in Decimal"); + let lock_sats = + rust_decimal::Decimal::from_u64(lock_amount.to_sat()).expect("lock sats to fit in Decimal"); let ratio = amnesty_sats / lock_sats; if ratio > swap_env::config::MAX_ANTI_SPAM_DEPOSIT_RATIO { diff --git a/swap/src/asb/event_loop.rs b/swap/src/asb/event_loop.rs index b72b8c9848..4e8957a032 100644 --- a/swap/src/asb/event_loop.rs +++ b/swap/src/asb/event_loop.rs @@ -1671,7 +1671,36 @@ mod tests { #[tokio::test] async fn test_make_quote_with_developer_tip() { - todo!("implement once unit tests compile again") + let min_buy = bitcoin::Amount::from_sat(100_000); + let max_buy = bitcoin::Amount::from_sat(5_000_000); // High enough to be balance-limited + let rate = FixedRate::default(); + let balance = monero::Amount::parse_monero("1.0").unwrap(); + let reserved_items: Vec = vec![]; + let developer_tip = Decimal::new(5, 2); // 0.05 = 5% + + let result = make_quote( + min_buy, + max_buy, + rate.clone(), + || async { Ok(balance) }, + || async { Ok(reserved_items) }, + || async { Err(anyhow::anyhow!("no reserve proof")) }, + developer_tip, + RefundPolicyWire::FullRefund, + ) + .await + .unwrap(); + + // Compute expected max: effective balance is reduced by the tip multiplier + let unreserved = unreserved_monero_balance(balance, std::iter::empty(), developer_tip); + let expected_max = unreserved + .max_bitcoin_for_price(rate.value().ask().unwrap()) + .unwrap(); + + assert_eq!(result.min_quantity, min_buy); + assert_eq!(result.max_quantity, expected_max); + // The tip should have reduced max_quantity below max_buy + assert!(result.max_quantity < max_buy); } // Mock struct for testing diff --git a/swap/src/protocol/alice/swap.rs b/swap/src/protocol/alice/swap.rs index f38cde10ea..b2f5a5cbfe 100644 --- a/swap/src/protocol/alice/swap.rs +++ b/swap/src/protocol/alice/swap.rs @@ -997,28 +997,91 @@ pub(crate) fn has_already_processed_enc_sig(state: &AliceState) -> bool { #[cfg(test)] mod tests { + use super::build_transfer_destinations; + use crate::protocol::alice::TipConfig; + use rust_decimal::Decimal; + + const TEST_ADDRESS_STR: &str = "53gEuGZUhP9JMEBZoGaFNzhwEgiG7hwQdMCqFxiyiTeFPmkbt1mAoNybEUvYBKHcnrSgxnVWgZsTvRBaHBNXPa8tHiCU51a"; + + fn test_address() -> monero_address::MoneroAddress { + monero_address::MoneroAddress::from_str_with_unchecked_network(TEST_ADDRESS_STR).unwrap() + } + #[test] fn test_build_transfer_destinations_without_tip() { - todo!("implement once unit tests compile again") + let lock_amount = monero_oxide_ext::Amount::from_pico(1_000_000_000_000); // 1 XMR + let tip = TipConfig { + ratio: Decimal::ZERO, + address: test_address(), + }; + + let result = build_transfer_destinations(test_address(), lock_amount, tip).unwrap(); + + assert_eq!(result.len(), 1); + assert_eq!(result[0].1, lock_amount); } #[test] fn test_build_transfer_destinations_with_tip() { - todo!("implement once unit tests compile again") + let lock_amount = monero_oxide_ext::Amount::from_pico(10_000_000_000_000); // 10 XMR + let tip = TipConfig { + ratio: Decimal::new(1, 2), // 0.01 = 1% + address: test_address(), + }; + + let result = build_transfer_destinations(test_address(), lock_amount, tip).unwrap(); + + // Tip = 10 XMR * 0.01 = 0.1 XMR = 100_000_000_000 pico >> 30_000_000 threshold + assert_eq!(result.len(), 2); + assert_eq!(result[0].1, lock_amount); + assert_eq!(result[1].1, monero_oxide_ext::Amount::from_pico(100_000_000_000)); } #[test] fn test_build_transfer_destinations_with_small_tip() { - todo!("implement once unit tests compile again") + // ratio * amount < 30_000_000 piconero threshold + let lock_amount = monero_oxide_ext::Amount::from_pico(2_000_000_000); // 0.002 XMR + let tip = TipConfig { + ratio: Decimal::new(1, 2), // 0.01 + address: test_address(), + }; + + let result = build_transfer_destinations(test_address(), lock_amount, tip).unwrap(); + + // Tip = 0.002 XMR * 0.01 = 20_000_000 piconero < 30_000_000 threshold + assert_eq!(result.len(), 1); + assert_eq!(result[0].1, lock_amount); } #[test] fn test_build_transfer_destinations_with_zero_tip() { - todo!("implement once unit tests compile again") + // Nonzero ratio but tiny lock amount → effective tip rounds to near-zero + let lock_amount = monero_oxide_ext::Amount::from_pico(100); + let tip = TipConfig { + ratio: Decimal::new(1, 1), // 0.1 = 10% + address: test_address(), + }; + + let result = build_transfer_destinations(test_address(), lock_amount, tip).unwrap(); + + // Tip = 100 * 0.1 = 10 piconero << 30_000_000 threshold + assert_eq!(result.len(), 1); + assert_eq!(result[0].1, lock_amount); } #[test] fn test_build_transfer_destinations_with_fractional_tip() { - todo!("implement once unit tests compile again") + let lock_amount = monero_oxide_ext::Amount::from_pico(1_000_000_000_000); // 1 XMR + let tip = TipConfig { + ratio: Decimal::new(5, 3), // 0.005 = 0.5% + address: test_address(), + }; + + let result = build_transfer_destinations(test_address(), lock_amount, tip).unwrap(); + + // Tip = 1 XMR * 0.005 = 0.005 XMR = 5_000_000_000 pico >> 30_000_000 threshold + assert_eq!(result.len(), 2); + assert_eq!(result[0].1, lock_amount); + assert_eq!(result[1].1, monero_oxide_ext::Amount::from_pico(5_000_000_000)); } } From 8a72769cda9970c9fd00672797473a50b32d8158 Mon Sep 17 00:00:00 2001 From: einliterflasche Date: Fri, 6 Feb 2026 22:22:43 +0100 Subject: [PATCH 113/113] ci: fix typo -> build and unit tests run again --- .github/workflows/ci.yml | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index e33b3c1202..08d072f790 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -87,14 +87,14 @@ jobs: - target: x86_64-pc-windows-gnu os: ubuntu-22.04 - runs-on: ${{ matrix.host }} + runs-on: ${{ matrix.os }} if: github.event_name == 'push' || !github.event.pull_request.draft steps: - uses: actions/checkout@v4 - name: Setup build environment (cli tools, dependencies) uses: ./.github/actions/setup-build-environment with: - host: ${{ matrix.host }} + host: ${{ matrix.os }} target: ${{ matrix.target }} - name: Build binary @@ -117,21 +117,21 @@ jobs: fail-fast: false matrix: os: [ubuntu-22.04, macos-15] - runs-on: ${{ matrix.host }} + runs-on: ${{ matrix.os }} if: github.event_name == 'push' || !github.event.pull_request.draft steps: - uses: actions/checkout@v4 - name: Setup build environment (cli tools, dependencies) uses: ./.github/actions/setup-build-environment with: - host: ${{ matrix.host }} + host: ${{ matrix.os }} - name: Run monero-harness tests - if: matrix.host == 'ubuntu-22.04' + if: matrix.os == 'ubuntu-22.04' run: cargo test --package monero-harness --all-features - - name: Run library tests for swap - run: cargo test --package swap --lib + - name: Run library tests + run: cargo test --lib docker_tests: strategy: