Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
31 changes: 23 additions & 8 deletions standards/src/wallet/notes/note.rs
Original file line number Diff line number Diff line change
Expand Up @@ -266,16 +266,23 @@ mod tests {
assert_eq!(*PublicNote::decode(&encoded).unwrap(), note);
assert_eq!(PublicNote::static_length(), None);

assert!(matches!(PublicNote::decode(&[]).unwrap_err(), PublicNoteError::TooShort));
assert!(matches!(
PublicNote::decode(&[]).unwrap_err(),
PublicNoteError::TooShort
));
assert!(matches!(
PublicNote::decode(&[BFieldElement::new(TAG_PRIVATE), BFieldElement::new(1)]).unwrap_err(),
PublicNoteError::WrongTag(t) if t == TAG_PRIVATE
));

// 99 isn't a valid NoteContent discriminant, so this exercises the
// #[from] NoteContentError -> PublicNoteError::Content conversion.
let err = PublicNote::decode(&[BFieldElement::new(TAG_PUBLIC), BFieldElement::new(1), BFieldElement::new(99)])
.unwrap_err();
let err = PublicNote::decode(&[
BFieldElement::new(TAG_PUBLIC),
BFieldElement::new(1),
BFieldElement::new(99),
])
.unwrap_err();
assert!(matches!(err, PublicNoteError::Content(_)));
}

Expand All @@ -287,7 +294,10 @@ mod tests {
assert_eq!(*PrivateNote::decode(&encoded).unwrap(), note);
assert_eq!(PrivateNote::static_length(), None);

assert!(matches!(PrivateNote::decode(&[BFieldElement::new(TAG_PRIVATE)]).unwrap_err(), PrivateNoteError::TooShort));
assert!(matches!(
PrivateNote::decode(&[BFieldElement::new(TAG_PRIVATE)]).unwrap_err(),
PrivateNoteError::TooShort
));
assert!(matches!(
PrivateNote::decode(&[BFieldElement::new(TAG_PUBLIC), BFieldElement::new(1)]).unwrap_err(),
PrivateNoteError::WrongTag(t) if t == TAG_PUBLIC
Expand Down Expand Up @@ -315,7 +325,10 @@ mod tests {
#[test]
fn note_decode_errors() {
assert!(matches!(Note::decode(&[]).unwrap_err(), NoteError::Empty));
assert!(matches!(Note::decode(&[BFieldElement::new(2)]).unwrap_err(), NoteError::UnknownTag(2)));
assert!(matches!(
Note::decode(&[BFieldElement::new(2)]).unwrap_err(),
NoteError::UnknownTag(2)
));
// Tag alone, no receiver id: inner decode fails and propagates via #[from].
assert!(matches!(
Note::decode(&[BFieldElement::new(TAG_PUBLIC)]).unwrap_err(),
Expand All @@ -339,11 +352,13 @@ mod tests {
let hrp = format!("note{}", network_hrp_char(Network::Main));

// Right HRP, wrong bech32 variant -> fails the variant check.
let wrong_variant = bech32::encode(&hrp, bytes.to_base32(), bech32::Variant::Bech32).unwrap();
let wrong_variant =
bech32::encode(&hrp, bytes.to_base32(), bech32::Variant::Bech32).unwrap();
assert!(Note::from_bech32m(&wrong_variant, Network::Main).is_err());

// Right variant, wrong HRP -> fails the network check.
let wrong_hrp = bech32::encode("wrong", bytes.to_base32(), bech32::Variant::Bech32m).unwrap();
let wrong_hrp =
bech32::encode("wrong", bytes.to_base32(), bech32::Variant::Bech32m).unwrap();
assert!(Note::from_bech32m(&wrong_hrp, Network::Main).is_err());
}
}
}
3 changes: 3 additions & 0 deletions wallet/cli/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,9 @@ serde_json = "1.0.149"
serde = "1.0.228"
clap = { version = "4.5.54", features = ["derive"] }
itertools = "0.14.0"
ratatui = "0.30"
crossterm = "0.29"
arboard = "3.6.1"

[dev-dependencies]

Expand Down
182 changes: 0 additions & 182 deletions wallet/cli/src/core/console.rs

This file was deleted.

2 changes: 1 addition & 1 deletion wallet/cli/src/core/mod.rs
Original file line number Diff line number Diff line change
@@ -1,2 +1,2 @@
pub mod console;
pub mod storage;
pub mod sync;
2 changes: 1 addition & 1 deletion wallet/cli/src/core/storage.rs
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ use serde::Serialize;
/// Write `contents` to `path` atomically via a `.tmp` sibling + `rename`.
///
/// Readers always see either the previous complete file or the new complete
/// file — never a partial write. `sync_all` ensures bytes reach the device
/// file, never a partial write. `sync_all` ensures bytes reach the device
/// before the rename commits, guarding against crash-after-rename data loss.
fn atomic_write(path: &Path, contents: &[u8]) {
let tmp = path.with_extension("tmp");
Expand Down
62 changes: 62 additions & 0 deletions wallet/cli/src/core/sync.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
use std::time::Duration;

use nyks_wallet_sdk::wallet::Wallet;
use nyks_wallet_sdk::wallet::WalletEvent;
use tracing::info;

use crate::core::storage::Storage;

/// Runs the periodic sync loop: polls the wallet for new chain state,
/// persists discovered/invalidated UTXOs to storage, and updates the stored
/// chain height.
pub async fn run(wallet: Wallet, storage: Storage) {
let mut interval = tokio::time::interval(Duration::from_secs(10));

loop {
interval.tick().await;

let events = match wallet.sync().await {
Ok(events) => events,
Err(err) => {
tracing::error!("sync failed: {err}");
continue;
}
};

for event in events {
match event {
WalletEvent::UtxoReceived { key, utxo } => {
match utxo.inclusion_block {
Some((_, block_height)) => info!(
"Discovered new UTXO containing {} NYKS on block {} (AOCL leaf {}).",
utxo.get_native_currency_amount(),
block_height,
utxo.membership_proof.aocl_leaf_index,
),
None => info!(
"Discovered new UTXO containing {} NYKS (AOCL leaf {}).",
utxo.get_native_currency_amount(),
utxo.membership_proof.aocl_leaf_index,
),
}

storage.utxos.put(&key, &utxo);
}
WalletEvent::UtxoInvalidated { key, utxo } => {
info!(
"UTXO containing {} NYKS was invalidated and marked as spent in an unknown block (AOCL leaf {}).",
utxo.get_native_currency_amount(),
utxo.membership_proof.aocl_leaf_index,
);

storage.utxos.put(&key, &utxo);
}
WalletEvent::UtxosOutgoing { id, utxos } => {
info!("{} UTXOs being spent on transaction {}.", utxos.len(), id);
}
}
}

storage.chain.set_height(wallet.height().await);
}
}
Loading
Loading