Skip to content
Open
Show file tree
Hide file tree
Changes from 17 commits
Commits
Show all changes
77 commits
Select commit Hold shift + click to select a range
1e83b53
feat(swift-sdk,platform-wallet): wire shielded transfer/unshield/with…
QuantumExplorer May 5, 2026
2180c68
feat(swift-sdk,platform-wallet): wire shield (Platform → Shielded, Ty…
QuantumExplorer May 5, 2026
c15a175
fix(swift-sdk,platform-wallet): hydrate persisted address balances on…
QuantumExplorer May 5, 2026
a8d9b14
fix(platform-wallet-ffi): run shielded proof on worker thread
QuantumExplorer May 5, 2026
dfbb2f8
fix(platform-wallet): surface Platform's per-input view on shield bro…
QuantumExplorer May 6, 2026
6c72239
fix(platform-wallet): reserve fee headroom on shield input 0
QuantumExplorer May 6, 2026
6e4931c
fix(swift-sdk,platform-wallet): address PR review feedback
QuantumExplorer May 6, 2026
3627d0f
fix(platform-wallet): wire shielded spend Merkle witnesses through Sh…
QuantumExplorer May 6, 2026
9211a0d
fix(swift-example-app): per-wallet shielded commitment-tree DB
QuantumExplorer May 6, 2026
3ffce1a
fix(swift-example-app): repoint ShieldedService when opening a wallet…
QuantumExplorer May 6, 2026
4ba2c42
feat(platform-wallet,swift-sdk): multi-account shielded wallet (Desig…
QuantumExplorer May 6, 2026
771b01c
feat(platform-wallet): shielded changeset + per-subwallet restore-on-…
QuantumExplorer May 6, 2026
784ce02
feat(swift-sdk,platform-wallet-ffi): SwiftData persistence for shield…
QuantumExplorer May 6, 2026
589ee68
feat(swift-example-app): storage explorer rows for shielded notes + s…
QuantumExplorer May 6, 2026
9ab48e3
feat(swift-example-app): multi-account shielded UI in WalletDetailView
QuantumExplorer May 6, 2026
2daf333
fix(platform-wallet): derive shielded spend anchor from witness paths
QuantumExplorer May 6, 2026
44f9f57
fix(platform-wallet): use monotonic checkpoint id in shielded sync
QuantumExplorer May 6, 2026
515a694
fix(swift-example-app): route orphan recovery to the wallet's intende…
QuantumExplorer May 6, 2026
a3f4edd
fix(swift-example-app): aggregate orphan-recovery failures with actio…
QuantumExplorer May 6, 2026
00624ad
fix(swift-example-app): log orphan-recovery errors via SDKLogger
QuantumExplorer May 6, 2026
ed30077
fix(swift-sdk,swift-example-app): always log orphan-recovery failures
QuantumExplorer May 6, 2026
83054cb
fix(platform-wallet): validate spend anchor against Platform's record…
QuantumExplorer May 6, 2026
e532eef
fix(platform-wallet,sdk): fall back to most-recent shielded anchor wh…
QuantumExplorer May 6, 2026
9609aee
Merge remote-tracking branch 'origin/v3.1-dev' into platform-wallet/s…
QuantumExplorer May 7, 2026
497b74f
docs(claude): fix iOS framework build path in project CLAUDE.md
QuantumExplorer May 7, 2026
d6a890a
fix(swift-example-app): only overlay shielded-notes empty state when …
QuantumExplorer May 7, 2026
6cc4d9a
fix(swift-example-app): add storage-explorer detail views for shielde…
QuantumExplorer May 7, 2026
1911d73
revert(platform-wallet): drop spend-side anchor pre-flight, trust dep…
QuantumExplorer May 7, 2026
9e4ecdf
feat(swift-example-app): show per-account synced index in shielded sy…
QuantumExplorer May 7, 2026
532b886
fix(swift-example-app): use renamed boundWalletId in shielded sync ev…
QuantumExplorer May 7, 2026
33267c5
fix(swift-example-app): show persisted balance per shielded account, …
QuantumExplorer May 7, 2026
c1bb53a
Merge remote-tracking branch 'origin/v3.1-dev' into platform-wallet/s…
QuantumExplorer May 7, 2026
c1b0eaf
Merge remote-tracking branch 'origin/v3.1-dev' into platform-wallet/s…
QuantumExplorer May 7, 2026
aea6dd1
fix(platform-wallet,swift-sdk): pending-then-confirm shielded spends …
QuantumExplorer May 8, 2026
1e22b90
Merge remote-tracking branch 'origin/v3.1-dev' into platform-wallet/s…
QuantumExplorer May 8, 2026
64f8edd
Merge remote-tracking branch 'origin/v3.1-dev' into platform-wallet/s…
QuantumExplorer May 19, 2026
39b9917
fix(platform-wallet-ffi): include shielded callbacks in PersistenceCa…
QuantumExplorer May 19, 2026
c031340
Merge remote-tracking branch 'origin/v3.1-dev' into platform-wallet/s…
QuantumExplorer May 20, 2026
e233bc5
fix(swift-example-app): drop leftover unused `amount` guard in coreTo…
QuantumExplorer May 20, 2026
51da731
fix(swift-example-app): include shielded balance in Wallets row total
QuantumExplorer May 20, 2026
b0fa943
fix(swift-example-app): make shielded-sync Clear button actually wipe…
QuantumExplorer May 20, 2026
15650d3
fix(swift-example-app): shielded Clear should stop, not auto-rebind
QuantumExplorer May 20, 2026
4d7d7ff
fix(swift-example-app): shielded-only wallets show their balance, not…
QuantumExplorer May 20, 2026
d4323c0
fix(swift-example-app): Clear wipes all shielded state, not just boun…
QuantumExplorer May 20, 2026
9f664de
fix(swift-example-app): stop manager-wide shielded sync before wiping…
QuantumExplorer May 20, 2026
3da3fc0
fix(swift-example-app): make post-Clear shielded state look intention…
QuantumExplorer May 20, 2026
6673cc2
fix(platform-wallet): shielded sync stops re-counting + re-fetching t…
QuantumExplorer May 20, 2026
2e7d392
fix(swift-example-app): gate shielded Clear on isSyncing
QuantumExplorer May 20, 2026
81addc9
fix(platform-wallet): drop MutexGuard before await in shielded sync c…
QuantumExplorer May 20, 2026
c3faf57
style(platform-wallet): apply rustfmt to sync.rs cooldown closure
QuantumExplorer May 20, 2026
d3673f2
fix(swift-example-app): post-Clear Sync Now self-binds + syncs from t…
QuantumExplorer May 20, 2026
524a722
fix(swift-example-app): stop deleting per-network SQLite tree on shie…
QuantumExplorer May 20, 2026
bf16bc8
fix(swift-example-app): restart manager-wide shielded sync after Clea…
QuantumExplorer May 20, 2026
848c905
fix(swift-example-app): run manual shielded sync before restarting ba…
QuantumExplorer May 20, 2026
b398e1f
fix(swift-example-app): don't tick shielded sync counter/timestamp on…
QuantumExplorer May 20, 2026
7897f2d
fix(platform-wallet,swift-sdk): plumb cooldown-skip flag through FFI …
QuantumExplorer May 20, 2026
c2db315
fix(platform-wallet,swift-sdk): make shielded cooldown skip infallibl…
QuantumExplorer May 20, 2026
6cfbdbf
refactor(platform-wallet): Phase 0 — shielded coordinator skeleton + …
QuantumExplorer May 20, 2026
30bf425
refactor(platform-wallet,ffi,swift-sdk): Phase 1 — share network SQLi…
QuantumExplorer May 20, 2026
000882c
refactor(platform-wallet): Phase 2a — coordinator owns network cooldo…
QuantumExplorer May 20, 2026
aeaa861
refactor(platform-wallet): Phase 2b — collapse N→1 SDK calls per shie…
QuantumExplorer May 20, 2026
b908651
feat(platform-wallet,ffi,swift-sdk): Phase 3 — single platform_wallet…
QuantumExplorer May 20, 2026
1c56d31
refactor(platform-wallet): Phase 4a — remove dead per-wallet shielded…
QuantumExplorer May 20, 2026
6043c35
refactor(platform-wallet): Phase 4c — collapse AccountState wrapper i…
QuantumExplorer May 20, 2026
a7dee49
refactor(platform-wallet): Phase 4d.1 — delete dead fallback paths in…
QuantumExplorer May 20, 2026
18448c8
refactor(platform-wallet): Phase 4d.2 — drop now-orphaned per-wallet …
QuantumExplorer May 20, 2026
27ad86d
refactor(platform-wallet): Phase 4b — lift restore_from_snapshot to c…
QuantumExplorer May 20, 2026
2eaffb5
refactor(platform-wallet): Phase 4d.3 — delete ShieldedWallet; lift s…
QuantumExplorer May 20, 2026
6e66e65
fix(platform-wallet): shielded spend witness + lifecycle regressions …
QuantumExplorer May 20, 2026
fd934ba
fix(platform-wallet): rebind is replace-not-merge; reject amount==0 i…
QuantumExplorer May 20, 2026
f9810d4
Merge branch 'v3.1-dev' into platform-wallet/shielded-spend-ffi
QuantumExplorer May 20, 2026
76ad3b5
fix(platform-wallet): gate shielded tree appends by tree size, not wa…
QuantumExplorer May 20, 2026
407e0da
feat(swift-example-app): aggregate shielded balance + notes-synced on…
QuantumExplorer May 20, 2026
e83524e
fix(platform-wallet): address shielded review feedback (Rust)
QuantumExplorer May 21, 2026
7917a9f
fix(swift-sdk): address shielded review feedback (Swift)
QuantumExplorer May 21, 2026
82cb6e5
fix(platform-wallet): harden shield flow — reuse rs-sdk nonce fetch +…
QuantumExplorer May 21, 2026
64096d9
fix(swift-example-app): correct self-shield recipient hint; doc best-…
QuantumExplorer May 21, 2026
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
6 changes: 6 additions & 0 deletions packages/rs-platform-wallet-ffi/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,10 @@ pub mod platform_addresses;
pub mod platform_wallet_info;
mod runtime;
#[cfg(feature = "shielded")]
pub mod shielded_persistence;
#[cfg(feature = "shielded")]
pub mod shielded_send;
#[cfg(feature = "shielded")]
pub mod shielded_sync;
pub mod shielded_types;
pub mod sign_with_mnemonic_resolver;
Expand Down Expand Up @@ -107,6 +111,8 @@ pub use platform_address_types::*;
pub use platform_addresses::*;
pub use platform_wallet_info::*;
#[cfg(feature = "shielded")]
pub use shielded_send::*;
#[cfg(feature = "shielded")]
pub use shielded_sync::*;
pub use shielded_types::*;
pub use sign_with_mnemonic_resolver::*;
Expand Down
378 changes: 378 additions & 0 deletions packages/rs-platform-wallet-ffi/src/persistence.rs

Large diffs are not rendered by default.

125 changes: 125 additions & 0 deletions packages/rs-platform-wallet-ffi/src/shielded_persistence.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,125 @@
//! C ABI types + callback signatures for shielded note persistence.
//!
//! Mirror of [`platform_wallet::changeset::ShieldedChangeSet`] for the
//! FFI boundary: per-subwallet decrypted notes, spent marks, sync
//! watermarks, nullifier checkpoints. Hosts implement the four
//! callbacks below in [`crate::persistence::PersistenceCallbacks`]
//! so changesets emitted by the Rust-side `ShieldedWallet` reach
//! durable storage (typically SwiftData on iOS).
//!
//! All pointers in these structs are valid for the duration of the
//! callback only — the host must copy any bytes it needs to retain
//! before the call returns.

use std::ffi::c_void;

/// One decrypted shielded note for the host to persist.
///
/// The host writes one row keyed by
/// `(wallet_id, account_index, position)`. Re-saves with the same
/// `(wallet_id, account_index, nullifier)` overwrite the existing
/// row in place — Orchard nullifiers are globally unique, so a
/// rescan after a restart shouldn't produce duplicates.
#[repr(C)]
pub struct ShieldedNoteFFI {
/// 32-byte wallet identifier.
pub wallet_id: [u8; 32],
/// ZIP-32 account index.
pub account_index: u32,
/// Global commitment-tree position.
pub position: u64,
/// Note commitment (32 bytes).
pub cmx: [u8; 32],
/// Nullifier (32 bytes).
pub nullifier: [u8; 32],
/// Block height the note was first observed at.
pub block_height: u64,
/// `1` if this note has been observed as spent on-chain, `0`
/// otherwise. (`bool` would still take 1 byte but `u8` is
/// less surprising across the C ABI.)
pub is_spent: u8,
/// Note value in credits.
pub value: u64,
/// Pointer to the serialized `orchard::Note` payload.
/// `recipient(43) || value(8 LE) || rho(32) || rseed(32)` =
/// 115 bytes. Valid only for the callback window — the host
/// must copy.
pub note_data_ptr: *const u8,
/// Length of `note_data_ptr` in bytes (always 115 for valid notes).
pub note_data_len: usize,
}

/// One nullifier observed as spent for `(wallet_id, account_index)`.
/// The host flips the matching `is_spent` flag on the existing
/// `ShieldedNoteFFI` row.
#[repr(C)]
pub struct ShieldedNullifierSpentFFI {
pub wallet_id: [u8; 32],
pub account_index: u32,
pub nullifier: [u8; 32],
}

/// One per-subwallet sync-watermark advance.
#[repr(C)]
pub struct ShieldedSyncedIndexFFI {
pub wallet_id: [u8; 32],
pub account_index: u32,
/// Highest global commitment-tree index the subwallet has scanned.
pub last_synced_index: u64,
}

/// One per-subwallet nullifier-sync checkpoint.
#[repr(C)]
pub struct ShieldedNullifierCheckpointFFI {
pub wallet_id: [u8; 32],
pub account_index: u32,
/// Block height of the most recent nullifier sync pass.
pub height: u64,
/// Block timestamp (Unix seconds) of the most recent pass.
pub timestamp: u64,
}

// ── Restore (load) ──────────────────────────────────────────────────────

/// One persisted note as the host hands it back at boot. Mirrors
/// [`ShieldedNoteFFI`] but lives in a Swift-allocated array, so
/// the buffer ownership / free contract differs (see
/// [`OnLoadShieldedNotesFreeFn`]).
#[repr(C)]
pub struct ShieldedNoteRestoreFFI {
pub wallet_id: [u8; 32],
pub account_index: u32,
pub position: u64,
pub cmx: [u8; 32],
pub nullifier: [u8; 32],
pub block_height: u64,
pub is_spent: u8,
pub value: u64,
pub note_data_ptr: *const u8,
pub note_data_len: usize,
}

/// One per-subwallet sync-watermark + nullifier-checkpoint snapshot.
/// Restored alongside notes so the rehydrated `SubwalletState`
/// resumes incremental sync from the right place.
#[repr(C)]
pub struct ShieldedSubwalletSyncStateFFI {
pub wallet_id: [u8; 32],
pub account_index: u32,
pub last_synced_index: u64,
/// `1` iff the optional `nullifier_checkpoint` is populated.
pub has_nullifier_checkpoint: u8,
pub nullifier_checkpoint_height: u64,
pub nullifier_checkpoint_timestamp: u64,
}

// The `on_load_shielded_*_fn` callback types are inlined inside
// [`PersistenceCallbacks`] (rather than declared as `pub type`
// aliases here) so cbindgen sees the full signature, walks into
// the referenced structs, and emits their full field layout in
// the generated header. Bare `pub type X = unsafe extern "C" fn`
// aliases are mangled into opaque structs by cbindgen and don't
// drag in their function-pointer arguments.

#[allow(dead_code)]
fn _keep_c_void_in_scope(_x: *const c_void) {}
Loading
Loading