Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
62 commits
Select commit Hold shift + click to select a range
72a4eca
feat(platform-wallet-storage): SecretStore foundation — zeroizing wra…
lklimek May 19, 2026
183c9f3
feat(platform-wallet-storage): EncryptedFileStore — Argon2id + XChaCh…
lklimek May 19, 2026
bfbb551
feat(platform-wallet-storage): KeyringStore — OS keyring backend (key…
lklimek May 19, 2026
e3ac1a6
test(platform-wallet-storage): positive secrets guard + API-shape int…
lklimek May 19, 2026
029753f
ci(platform-wallet-storage): cargo-deny advisories gate covering the …
lklimek May 19, 2026
1c55f89
fix(platform-wallet-storage): passphrase-verification token + hardene…
lklimek May 19, 2026
0a7c3f0
fix(platform-wallet-storage): map keyring-core NoStorageAccess to Key…
lklimek May 19, 2026
884d470
fix(platform-wallet-storage): MemoryStore stores SecretBytes so it ze…
lklimek May 19, 2026
1256cb8
docs(platform-wallet-storage): correct keyring-core attribution in Ca…
lklimek May 19, 2026
1c29698
docs(platform-wallet-storage): SECRETS.md reflects the delivered Secr…
lklimek May 19, 2026
2c7927b
chore(platform-wallet-storage,ci): drop cargo-deny, flip secrets defa…
lklimek May 20, 2026
123f908
refactor(platform-wallet-storage): adopt keyring_core SPI for secret …
lklimek May 20, 2026
f733d38
docs(platform-wallet-storage): SECRETS.md + lib root reflect keyring_…
lklimek May 20, 2026
18a0655
test(platform-wallet-storage): default-build proof for the secrets su…
lklimek May 20, 2026
3eefec2
fix(platform-wallet-storage): forbid == on SecretBytes/SecretString (…
lklimek May 20, 2026
c7a4ded
feat(platform-wallet-storage): add keyless account-manifest reader (A1)
lklimek May 19, 2026
b9af993
feat(platform-wallet): SeedProvider port + CredentialStore adapter + …
lklimek May 20, 2026
79f53ed
feat(platform-wallet-storage): bulk core-state reconstruction reader (B)
lklimek May 19, 2026
e49fcc4
feat(platform-wallet-storage): Consumed-filtered asset-lock rehydrati…
lklimek May 19, 2026
afd14cb
feat(platform-wallet-storage): wire keyless load() rehydration payloa…
lklimek May 19, 2026
8232896
test(platform-wallet): end-to-end rehydration RT suite (E)
lklimek May 19, 2026
e2e04b7
test(platform-wallet-storage): flip deferral test to positive rehydra…
lklimek May 19, 2026
b9d46a5
test(platform-wallet-storage): allow-list one-shot rehydration reader…
lklimek May 19, 2026
96a9aa9
fix(platform-wallet): no-BIP44 wallet silent-zero balance + chainlock…
lklimek May 19, 2026
62bd475
refactor(platform-wallet): drop dead post-insert wallet_id re-check (F4)
lklimek May 19, 2026
b7508a0
fix(platform-wallet-ffi): adapt FFI consumer to keyless rehydration A…
lklimek May 19, 2026
e93dfc7
Merge branch 'feat/platform-wallet-sqlite-persistor' into feat/platfo…
lklimek May 22, 2026
34532e5
Merge branch 'feat/platform-wallet-storage-secrets' into feat/platfor…
lklimek May 22, 2026
8a5ef7a
fix(platform-wallet-storage): rekey returns FileStoreError::Busy inst…
lklimek May 22, 2026
b6a84fd
refactor(platform-wallet-storage)!: unify FileStoreError, drop error_…
lklimek May 22, 2026
647567e
fix(platform-wallet-storage): remove redundant SecretString Drop (UB)…
lklimek May 22, 2026
8ab4208
feat(platform-wallet-storage)!: serde_json vault format with versione…
lklimek May 22, 2026
68ed3d1
fix(platform-wallet-storage): cross-platform atomic vault write via N…
lklimek May 22, 2026
0066a5a
feat(platform-wallet-storage)!: public SecretStore API exposing Secre…
lklimek May 22, 2026
c636ac0
refactor(platform-wallet-storage): string-only keyring_core From; typ…
lklimek May 22, 2026
a5c5bf0
fix(platform-wallet-storage): box typed FileStoreError into keyring_c…
lklimek May 22, 2026
e1c7fa9
refactor(platform-wallet-storage): remove MemoryCredentialStore; reti…
lklimek May 22, 2026
671ce69
fix(platform-wallet-storage): enforce lowercase-hex service, widen ex…
lklimek May 22, 2026
dc492cc
docs(platform-wallet-storage): strip historical comments + license he…
lklimek May 22, 2026
c58a2b5
feat(platform-wallet-storage): log swallowed mlock + corruption/write…
lklimek May 22, 2026
6aa2942
docs(platform-wallet-storage): drop deleted MemoryCredentialStore / _…
lklimek May 22, 2026
cfb93a2
refactor(platform-wallet): seedless watch-only load via Wallet::new_w…
lklimek May 25, 2026
21215d3
refactor(platform-wallet-ffi): drop resolver arg from load_from_persi…
lklimek May 25, 2026
92f849b
fix(swift-sdk): align PlatformWalletManager.loadFromPersistor with se…
lklimek May 25, 2026
3cd4264
style: cargo fmt across seedless-load touch points
lklimek May 25, 2026
f57b117
docs(platform-wallet): adjust rehydration_load test header to reflect…
lklimek May 25, 2026
81ed297
Merge branch 'feat/platform-wallet-storage-secrets' into feat/platfor…
lklimek May 25, 2026
34c8ecb
Merge remote-tracking branch 'origin/feat/platform-wallet-sqlite-pers…
lklimek May 25, 2026
db7b6b5
Merge branch 'feat/platform-wallet-sqlite-persistor' into feat/platfo…
lklimek May 26, 2026
543d0da
Merge branch 'feat/platform-wallet-storage-secrets' into feat/platfor…
lklimek May 26, 2026
e7e1de8
Merge remote-tracking branch 'origin/feat/platform-wallet-sqlite-pers…
lklimek May 27, 2026
4d21651
Merge remote-tracking branch 'origin/feat/platform-wallet-storage-sec…
lklimek May 27, 2026
9e2d2b0
feat(platform-wallet): add contacts and identity-key rehydration (ite…
Claudius-Maginificent Jun 2, 2026
7c2b2f9
Merge remote-tracking branch 'origin/feat/platform-wallet-sqlite-pers…
lklimek Jun 2, 2026
d99d7a5
chore(platform-wallet): untrack .review-3625 review scratch + gitignore
lklimek Jun 2, 2026
14d868e
refactor(platform-wallet): remove dead wrong-seed-gate scaffolding (#…
lklimek Jun 2, 2026
0a9c972
Merge remote-tracking branch 'origin/feat/platform-wallet-rehydration…
lklimek Jun 2, 2026
c3cf8b0
fix(platform-wallet-storage): reconcile identity_keys schema with #36…
lklimek Jun 2, 2026
ddfa66e
test(platform-wallet-storage): pin identity_keys dual-FK cascade + no…
lklimek Jun 2, 2026
052db80
Merge commit '932b923b2b277dc80c7c0dd59a332e0ab7dc76b1' into feat/pla…
lklimek Jun 2, 2026
2f35190
Merge #3625 (feat/platform-wallet-sqlite-persistor) into rehydration
lklimek Jun 3, 2026
3f2e7d2
Merge #3625 (feat/platform-wallet-sqlite-persistor @ 04662411cf) into…
lklimek Jun 3, 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
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -102,3 +102,6 @@ __pycache__/

# Security audit reports (local-only, not committed)
audits/

# Review scratch (grumpy-review / triage output, local-only)
.review-*/
136 changes: 130 additions & 6 deletions packages/rs-platform-wallet-ffi/src/manager.rs
Original file line number Diff line number Diff line change
Expand Up @@ -174,6 +174,51 @@ unsafe fn create_wallet_from_mnemonic_impl(
PlatformWalletFFIResult::ok()
}

/// One wallet skipped during `load_from_persistor` because its
/// persisted row was structurally corrupt (per-row decode failure).
/// The load path is seedless and watch-only, so this is the only skip
/// reason. `reason_code` is per-`CorruptKind` family — see its table.
#[repr(C)]
#[derive(Debug, Clone, Copy)]
pub struct SkippedWalletFFI {
/// The (public) 32-byte wallet id that was skipped.
pub wallet_id: [u8; 32],
/// Structural skip reason. `100` = missing account manifest,
/// `101` = malformed account xpub, `102` = any other structural
/// decode error. No secret material is ever carried.
pub reason_code: u32,
}

/// C-visible summary of one `load_from_persistor` pass so the host can
/// see which wallets loaded and which were skipped (and why) instead
/// of the outcome being silently discarded.
///
/// `skipped` is a heap array of length `skipped_count`; pass this
/// struct (by pointer) to
/// [`platform_wallet_load_outcome_free`] exactly once to release it.
#[repr(C)]
#[derive(Debug)]
pub struct LoadOutcomeFFI {
/// Number of wallets fully reconstructed + registered.
pub loaded_count: usize,
/// Length of the `skipped` array.
pub skipped_count: usize,
/// Heap-allocated skipped-wallet array (null iff `skipped_count`
/// is 0). Owned by Rust until `platform_wallet_load_outcome_free`.
pub skipped: *mut SkippedWalletFFI,
}

fn skip_reason_code(reason: &platform_wallet::SkipReason) -> u32 {
use platform_wallet::manager::load_outcome::CorruptKind;
match reason {
platform_wallet::SkipReason::CorruptPersistedRow { kind } => match kind {
CorruptKind::MissingManifest => 100,
CorruptKind::MalformedXpub => 101,
CorruptKind::DecodeError(_) => 102,
},
}
}

/// Create a wallet from raw seed bytes (64 bytes).
///
/// On success, `out_wallet_handle` is set to a `PlatformWallet` handle and
Expand Down Expand Up @@ -296,23 +341,102 @@ pub unsafe extern "C" fn platform_wallet_manager_create_wallet_from_mnemonic_wit
///
/// Triggers `on_load_wallet_list_fn` on the persistence callbacks to
/// fetch the persisted wallet list from the client side (SwiftData),
/// reconstructs each wallet as **watch-only** via its stored root +
/// per-account xpubs, and registers them inside the manager. Does not
/// produce wallet handles — the caller should follow up with
/// [`platform_wallet_manager_get_wallet`] per `wallet_id` it knows
/// about.
/// builds a keyless reconstruction payload per wallet, then registers
/// each one as a **watch-only** wallet. No signing keys are derived
/// here — signing happens later, on demand, via the configured
/// `MnemonicResolverHandle` (`sign_with_mnemonic_resolver` and its
/// siblings), which fail-closed gate the resolver-supplied seed
/// against the loaded `wallet_id`. Does not produce wallet handles —
/// follow up with [`platform_wallet_manager_get_wallet`] per
/// `wallet_id`.
///
/// A wallet whose persisted row is structurally corrupt is
/// **skipped**, not failed: the call still returns `Success`, every
/// skipped `(wallet_id, reason)` is logged, and — when `out_outcome`
/// is non-null — surfaced through it.
///
/// # Safety
/// - `out_outcome` may be null (caller doesn't want the summary);
/// otherwise it must point to writable `LoadOutcomeFFI` storage and
/// the caller must later release it via
/// [`platform_wallet_load_outcome_free`].
#[no_mangle]
pub unsafe extern "C" fn platform_wallet_manager_load_from_persistor(
manager_handle: Handle,
out_outcome: *mut LoadOutcomeFFI,
) -> PlatformWalletFFIResult {
let option = PLATFORM_WALLET_MANAGER_STORAGE.with_item(manager_handle, |manager| {
runtime().block_on(manager.load_from_persistor())
});
let result = unwrap_option_or_return!(option);
unwrap_result_or_return!(result);
let outcome = unwrap_result_or_return!(result);

// Never silently drop the outcome: log a structured summary plus
// one line per skipped wallet (the host can inspect / clear the
// corrupt rows).
tracing::info!(
loaded = outcome.loaded.len(),
skipped = outcome.skipped.len(),
"platform_wallet_manager_load_from_persistor complete"
);
for (wid, reason) in &outcome.skipped {
tracing::warn!(
wallet_id = %hex::encode(wid),
reason = %reason,
"load_from_persistor skipped wallet (corrupt persisted row)"
);
}

if !out_outcome.is_null() {
let skipped_vec: Vec<SkippedWalletFFI> = outcome
.skipped
.iter()
.map(|(wid, reason)| SkippedWalletFFI {
wallet_id: *wid,
reason_code: skip_reason_code(reason),
})
.collect();
let skipped_count = skipped_vec.len();
let skipped_ptr = if skipped_count == 0 {
std::ptr::null_mut()
} else {
let boxed = skipped_vec.into_boxed_slice();
Box::into_raw(boxed) as *mut SkippedWalletFFI
};
std::ptr::write(
out_outcome,
LoadOutcomeFFI {
loaded_count: outcome.loaded.len(),
skipped_count,
skipped: skipped_ptr,
},
);
}
PlatformWalletFFIResult::ok()
}

/// Release the heap `skipped` array a successful
/// [`platform_wallet_manager_load_from_persistor`] wrote into a
/// `LoadOutcomeFFI`. Idempotent: nulls the pointer after freeing, and
/// a null `outcome` (or already-freed array) is a no-op.
///
/// # Safety
/// `outcome` must point to a `LoadOutcomeFFI` previously populated by
/// `platform_wallet_manager_load_from_persistor`, not freed already.
#[no_mangle]
pub unsafe extern "C" fn platform_wallet_load_outcome_free(outcome: *mut LoadOutcomeFFI) {
if outcome.is_null() {
return;
}
let o = &mut *outcome;
if !o.skipped.is_null() && o.skipped_count > 0 {
let slice = std::slice::from_raw_parts_mut(o.skipped, o.skipped_count);
drop(Box::from_raw(slice as *mut [SkippedWalletFFI]));
}
o.skipped = std::ptr::null_mut();
o.skipped_count = 0;
}

/// Get a `PlatformWallet` handle for a wallet registered in the
/// manager. Returns `NotFound` if no wallet with the given
/// id is currently held.
Expand Down
52 changes: 50 additions & 2 deletions packages/rs-platform-wallet-ffi/src/persistence.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2831,11 +2831,59 @@ fn build_wallet_start_state(
// status without rebroadcasting.
let unused_asset_locks = build_unused_asset_locks(entry)?;

// Project the reconstructed `wallet` + `wallet_info` into the
// keyless `ClientWalletStartState` the persister contract requires
// (SECRETS.md: no `Wallet`/seed crosses `load()`). The manager
// rebuilds a watch-only wallet from this manifest via
// `Wallet::new_watch_only` and applies this `core_state` projection.
// Signing happens later via the on-demand
// `sign_with_mnemonic_resolver` path, which fail-closed gates the
// resolver-supplied seed against the loaded `wallet_id`. The
// locally-built `wallet` is dropped — it was only needed to shape
// the account collection / UTXO routing above.
let account_manifest: Vec<AccountRegistrationEntry> = wallet
.accounts
.all_accounts()
.into_iter()
.map(|a| AccountRegistrationEntry {
account_type: a.account_type,
account_xpub: a.account_xpub,
})
.collect();
let new_utxos: Vec<key_wallet::Utxo> = wallet_info
.accounts
.all_funding_accounts()
.into_iter()
.flat_map(|acct| acct.utxos.values().cloned())
.collect();
let core_state = platform_wallet::changeset::CoreChangeSet {
new_utxos,
last_processed_height: (wallet_info.metadata.last_processed_height > 0)
.then_some(wallet_info.metadata.last_processed_height),
synced_height: (wallet_info.metadata.synced_height > 0)
.then_some(wallet_info.metadata.synced_height),
..Default::default()
};

// `contacts` / `identity_keys` are the PR-3 keyless feed the
// manager layers onto the managed identities via
// `apply_contacts_and_keys`. The iOS path does NOT use them:
// identity PUBLIC keys are already reconstructed straight into
// `Identity.public_keys` by `build_wallet_identity_bucket` (feeding
// the slot too would double-apply), and `WalletRestoreEntryFFI`
// carries no contacts back from Swift on load — surfacing them
// would need a new cross-boundary struct field + Swift wiring,
// tracked as a follow-up. Empty slots make `apply_contacts_and_keys`
// a no-op for this path, preserving the established iOS behaviour.
let wallet_state = ClientWalletStartState {
wallet,
wallet_info,
network,
birth_height: entry.birth_height,
account_manifest,
core_state,
identity_manager,
unused_asset_locks,
contacts: Default::default(),
identity_keys: Default::default(),
};

let platform_address_state = if per_account.is_empty()
Expand Down
28 changes: 18 additions & 10 deletions packages/rs-platform-wallet-storage/migrations/V001__initial.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,14 +8,16 @@
//! Per-wallet tables carry `wallet_id BLOB` in (or as all of) their
//! primary key plus a native `FOREIGN KEY (wallet_id) REFERENCES
//! wallet_metadata(wallet_id) ON DELETE CASCADE`. Identity-owned
//! tables (`identity_keys`, `dashpay_profiles`,
//! `dashpay_payments_overlay`, `token_balances`) are keyed by
//! `identity_id` only; their FK targets `identities(identity_id)` so
//! cascade flows `wallet_metadata → identities → child` through the
//! nullable `identities.wallet_id` link. `identities.wallet_id` is
//! NULL-allowed so identity-only flows (no parent wallet, e.g. the
//! identity-sync manager populating rows before any wallet is
//! registered) work without a placeholder.
//! tables (`dashpay_profiles`, `dashpay_payments_overlay`,
//! `token_balances`) are keyed by `identity_id` only; their FK targets
//! `identities(identity_id)` so cascade flows `wallet_metadata →
//! identities → child` through the nullable `identities.wallet_id`
//! link. `identity_keys` additionally carries its own `wallet_id`
//! column (so per-wallet reads stay a direct `WHERE wallet_id = ?`)
//! and keeps the `identity_id` FK for the identity-delete cascade.
//! `identities.wallet_id` is NULL-allowed so identity-only flows (no
//! parent wallet, e.g. the identity-sync manager populating rows
//! before any wallet is registered) work without a placeholder.
//!
//! The one relationship that stays a trigger is
//! `core_utxos.spent_in_txid` clearing to NULL on transaction delete —
Expand Down Expand Up @@ -165,15 +167,21 @@ CREATE TABLE identities (
CREATE INDEX idx_identities_wallet ON identities(wallet_id);

CREATE TABLE identity_keys (
wallet_id BLOB NOT NULL,
identity_id BLOB NOT NULL,
key_id INTEGER NOT NULL,
public_key_blob BLOB NOT NULL,
public_key_hash BLOB NOT NULL,
PRIMARY KEY (identity_id, key_id),
-- Reserved for a future typed projection; always NULL today.
-- derivation_indices lives inside public_key_blob (the
-- IdentityKeyWire blob is the single source of truth).
derivation_blob BLOB,
PRIMARY KEY (wallet_id, identity_id, key_id),
FOREIGN KEY (wallet_id) REFERENCES wallet_metadata(wallet_id) ON DELETE CASCADE,
FOREIGN KEY (identity_id) REFERENCES identities(identity_id) ON DELETE CASCADE
);

CREATE INDEX idx_identity_keys_identity ON identity_keys(identity_id);
CREATE INDEX idx_identity_keys_wallet_identity ON identity_keys(wallet_id, identity_id);

CREATE TABLE contacts (
wallet_id BLOB NOT NULL,
Expand Down
87 changes: 72 additions & 15 deletions packages/rs-platform-wallet-storage/src/sqlite/persister.rs
Original file line number Diff line number Diff line change
Expand Up @@ -20,12 +20,11 @@ use crate::sqlite::schema::{self, count_rows_for_wallet_sql, PER_WALLET_TABLES};
use crate::sqlite::util::permissions::apply_secure_permissions;
use crate::sqlite::util::safe_cast;

/// Sub-areas of `ClientStartState` that `load()` does not yet
/// reconstruct (blocked on upstream `Wallet::from_persisted`).
///
/// Surfaced via the structured `tracing::info!` summary on every
/// `load()` (`unimplemented` + `wallets_pending_rehydration` fields).
pub(crate) const LOAD_UNIMPLEMENTED: &[&str] = &["ClientStartState::wallets"];
/// Sub-areas still deferred after contacts + identity-keys rehydration
/// landed (PR-3). Only `last_applied_chain_lock` remains — it re-warms
/// on the first post-load SPV chainlock (no V001 column). Surfaced via
/// the structured `tracing::info!` summary on every `load()`.
pub(crate) const LOAD_UNIMPLEMENTED: &[&str] = &["core::last_applied_chain_lock"];

/// Outcome of a `prune_backups` call.
///
Expand Down Expand Up @@ -903,11 +902,16 @@ impl PlatformWalletPersistence for SqlitePersister {

/// Load every wallet's start-state from disk.
///
/// Populates `platform_addresses` per wallet. `wallets` stays empty
/// pending an upstream `key_wallet::Wallet::from_persisted`
/// constructor — the count of wallets that *would* be rehydrated is
/// surfaced as the structured field `wallets_pending_rehydration`
/// on the `tracing::info!` summary.
/// Populates both `platform_addresses` and the keyless per-wallet
/// `wallets` rehydration payload (network, birth height, account
/// manifest, reconstructed core state, identities, and the
/// `Consumed`-filtered asset-lock feed). The return type carries
/// **no** `Wallet` and no key material — the manager rebuilds each
/// wallet watch-only via `Wallet::new_watch_only` from the manifest
/// and applies this state. Signing happens later on demand via the
/// `sign_with_mnemonic_resolver` path, which fail-closed gates the
/// resolver-supplied seed against the loaded `wallet_id`. The
/// structured `tracing::info!` summary reports `wallets_rehydrated`.
///
/// Fail-hard: any row that fails to decode (or carries a malformed
/// `wallet_id`) aborts the whole load with a typed
Expand Down Expand Up @@ -970,9 +974,7 @@ impl PlatformWalletPersistence for SqlitePersister {
let mut state = ClientStartState::default();

let addrs_all = schema::platform_addrs::load_all(&conn).map_err(PersistenceError::from)?;
let wallets_seen = addrs_all.len();
let mut addresses_loaded: usize = 0;

for (wallet_id, (addrs, count)) in addrs_all {
if count > 0
|| addrs.sync_height > 0
Expand All @@ -984,11 +986,66 @@ impl PlatformWalletPersistence for SqlitePersister {
}
}

// Per-wallet keyless rehydration payload. The persister never
// mints a `Wallet` or touches key material — it hands back the
// network/birth-height + account manifest + reconstructed core
// state + identities + the Consumed-filtered asset-lock feed.
// The manager rebuilds each wallet watch-only and applies this;
// signing-key derivation happens later on demand via the
// on-demand sign path.
let wallet_ids = schema::wallet_meta::list_ids(&conn).map_err(PersistenceError::from)?;
let wallets_seen = wallet_ids.len();
for wallet_id in wallet_ids {
let (network_str, birth_height) = schema::wallet_meta::fetch(&conn, &wallet_id)
.map_err(PersistenceError::from)?
.ok_or_else(|| {
PersistenceError::backend(format!(
"wallet_metadata row vanished mid-load for {}",
hex::encode(wallet_id)
))
})?;
let network = schema::wallet_meta::parse_network(&network_str).ok_or_else(|| {
PersistenceError::backend(format!(
"unknown persisted network {:?} for wallet {}",
network_str,
hex::encode(wallet_id)
))
})?;

let account_manifest =
schema::accounts::load_state(&conn, &wallet_id).map_err(PersistenceError::from)?;
let core_state = schema::core_state::load_state(&conn, &wallet_id, network)
.map_err(PersistenceError::from)?;
let identity_manager = schema::identities::load_state(&conn, &wallet_id)
.map_err(PersistenceError::from)?;
let unused_asset_locks = schema::asset_locks::load_unconsumed(&conn, &wallet_id)
.map_err(PersistenceError::from)?;
let contacts = schema::contacts::load_changeset(&conn, &wallet_id)
.map_err(PersistenceError::from)?;
let identity_keys = schema::identity_keys::load_state(&conn, &wallet_id)
.map_err(PersistenceError::from)?;

state.wallets.insert(
wallet_id,
platform_wallet::changeset::ClientWalletStartState {
network,
birth_height,
account_manifest,
core_state,
identity_manager,
unused_asset_locks,
contacts,
identity_keys,
},
);
}
let wallets_rehydrated = state.wallets.len();

tracing::info!(
wallets_seen,
addresses_loaded,
wallets_rehydrated = 0usize,
wallets_pending_rehydration = wallets_seen,
wallets_rehydrated,
wallets_pending_rehydration = 0usize,
unimplemented = ?LOAD_UNIMPLEMENTED,
"load() summary"
);
Expand Down
Loading
Loading