Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
18 commits
Select commit Hold shift + click to select a range
cea465e
fix(sdk): wallet-flow network fixes for SwiftExampleApp
QuantumExplorer May 31, 2026
ee59225
fix(sdk): per-network wallet persistence and add-to-network support
QuantumExplorer May 31, 2026
207b4f8
fix(sdk): address review — network-scope deletes, surface errors, rea…
QuantumExplorer May 31, 2026
65807e7
fix(sdk): drop identifier shortcut in hasAnyPrivateKey (review)
QuantumExplorer May 31, 2026
076c0a0
fix(sdk): network-scope deriveAndStoreIdentityKey wallet lookup (review)
QuantumExplorer May 31, 2026
cee4b2c
fix(sdk): surface partial-failure in multi-network wallet create (rev…
QuantumExplorer May 31, 2026
815751a
fix(sdk): correct two review-fix regressions (review)
QuantumExplorer Jun 1, 2026
14d353e
fix(sdk): network-scoped walletId closes cross-network cache leak (#2)
QuantumExplorer Jun 1, 2026
5e76921
test(sdk): cover all four networks in scoped-walletId stability test
QuantumExplorer Jun 1, 2026
7f14210
fix(sdk): add walletGroupId so Wallet Info finds sibling-network wallets
QuantumExplorer Jun 1, 2026
92b6b28
refactor(sdk): tighten PersistentWallet uniqueness to walletId alone
QuantumExplorer Jun 1, 2026
795284a
Merge remote-tracking branch 'origin/v3.1-dev' into fix/swift-wallet-…
QuantumExplorer Jun 6, 2026
4bdc604
refactor(sdk): drop dead predicate(walletId:network:) overload
QuantumExplorer Jun 7, 2026
dc55e6d
fix(sdk): use target-network birthHeight in enableNetwork metadata
QuantumExplorer Jun 7, 2026
4cf30a4
fix(swift): scope resumable asset locks to active network
QuantumExplorer Jun 8, 2026
1c0fb97
fix(sdk): classify duplicate-wallet errors by typed FFI code
QuantumExplorer Jun 8, 2026
21391d5
fix(sdk): emit WalletAlreadyExists on duplicate create; scope restora…
QuantumExplorer Jun 8, 2026
24344b0
fix(swift): group legacy wallets with per-network siblings in Wallet …
llbartekll Jun 9, 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
48 changes: 24 additions & 24 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

16 changes: 8 additions & 8 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -49,14 +49,14 @@ members = [
]

[workspace.dependencies]
dashcore = { git = "https://github.com/dashpay/rust-dashcore", rev = "eb889af13f667ed39c35e8e8a0830eeedf523476" }
dash-network-seeds = { git = "https://github.com/dashpay/rust-dashcore", rev = "eb889af13f667ed39c35e8e8a0830eeedf523476" }
dash-spv = { git = "https://github.com/dashpay/rust-dashcore", rev = "eb889af13f667ed39c35e8e8a0830eeedf523476" }
key-wallet = { git = "https://github.com/dashpay/rust-dashcore", rev = "eb889af13f667ed39c35e8e8a0830eeedf523476" }
key-wallet-ffi = { git = "https://github.com/dashpay/rust-dashcore", rev = "eb889af13f667ed39c35e8e8a0830eeedf523476" }
key-wallet-manager = { git = "https://github.com/dashpay/rust-dashcore", rev = "eb889af13f667ed39c35e8e8a0830eeedf523476" }
dash-network = { git = "https://github.com/dashpay/rust-dashcore", rev = "eb889af13f667ed39c35e8e8a0830eeedf523476" }
dashcore-rpc = { git = "https://github.com/dashpay/rust-dashcore", rev = "eb889af13f667ed39c35e8e8a0830eeedf523476" }
dashcore = { git = "https://github.com/dashpay/rust-dashcore", rev = "3d0d5dcd4ad64e2199a726651bca7f8ffac123e6" }
dash-network-seeds = { git = "https://github.com/dashpay/rust-dashcore", rev = "3d0d5dcd4ad64e2199a726651bca7f8ffac123e6" }
dash-spv = { git = "https://github.com/dashpay/rust-dashcore", rev = "3d0d5dcd4ad64e2199a726651bca7f8ffac123e6" }
key-wallet = { git = "https://github.com/dashpay/rust-dashcore", rev = "3d0d5dcd4ad64e2199a726651bca7f8ffac123e6" }
key-wallet-ffi = { git = "https://github.com/dashpay/rust-dashcore", rev = "3d0d5dcd4ad64e2199a726651bca7f8ffac123e6" }
key-wallet-manager = { git = "https://github.com/dashpay/rust-dashcore", rev = "3d0d5dcd4ad64e2199a726651bca7f8ffac123e6" }
dash-network = { git = "https://github.com/dashpay/rust-dashcore", rev = "3d0d5dcd4ad64e2199a726651bca7f8ffac123e6" }
dashcore-rpc = { git = "https://github.com/dashpay/rust-dashcore", rev = "3d0d5dcd4ad64e2199a726651bca7f8ffac123e6" }

# Optimize heavy crypto crates even in dev/test builds so that
# Halo 2 proof generation and verification run at near-release speed.
Expand Down
36 changes: 36 additions & 0 deletions packages/rs-platform-wallet-ffi/src/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,14 @@ pub enum PlatformWalletFFIResultCode {
/// receive address, consolidate sub-min balances, or fall back to
/// `InputSelection::Explicit`.
ErrorNoSelectableInputs = 14,
/// Maps `PlatformWalletError::WalletAlreadyExists`. Callers that create
/// a wallet across multiple networks (or enable an additional network on
/// an existing wallet) treat this as a benign "already present" no-op
/// rather than a hard failure — the wallet's mnemonic/metadata were
/// stored under its scoped id at original creation, so there is nothing
/// to re-persist. The typed Display rendering still survives as the
/// result message for logging/detail.
ErrorWalletAlreadyExists = 15,

NotFound = 98, // Used exclusively for all the Option that are retuned as errors
ErrorUnknown = 99,
Expand Down Expand Up @@ -180,6 +188,9 @@ impl From<PlatformWalletError> for PlatformWalletFFIResult {
| PlatformWalletError::OnlyDustInputs { .. } => {
PlatformWalletFFIResultCode::ErrorNoSelectableInputs
}
PlatformWalletError::WalletAlreadyExists(..) => {
PlatformWalletFFIResultCode::ErrorWalletAlreadyExists
}
_ => PlatformWalletFFIResultCode::ErrorUnknown,
};
PlatformWalletFFIResult::err(code, error.to_string())
Expand Down Expand Up @@ -458,6 +469,31 @@ mod tests {
}
}

/// `WalletAlreadyExists` maps to the dedicated
/// `ErrorWalletAlreadyExists` FFI code rather than flattening to
/// `ErrorUnknown`, so multi-network wallet create/enable callers can
/// branch on the typed code instead of substring-matching the Display
/// text. The typed Display rendering still survives as the message.
#[test]
fn wallet_already_exists_maps_to_dedicated_code() {
let err = PlatformWalletError::WalletAlreadyExists("wallet 0xdeadbeef".to_string());
let rendered = err.to_string();
let result: PlatformWalletFFIResult = err.into();
assert_eq!(
result.code,
PlatformWalletFFIResultCode::ErrorWalletAlreadyExists,
"WalletAlreadyExists should map to ErrorWalletAlreadyExists (rendered: {rendered})"
);
assert!(!result.message.is_null());
let msg = unsafe { std::ffi::CStr::from_ptr(result.message) }
.to_string_lossy()
.into_owned();
assert_eq!(
msg, rendered,
"Display payload must survive the FFI boundary verbatim"
);
}

/// Other wallet-error variants without a dedicated FFI arm still
/// fall through to `ErrorUnknown` while carrying the typed
/// Display rendering as the message. Pin this so the catch-all
Expand Down
19 changes: 14 additions & 5 deletions packages/rs-platform-wallet-ffi/src/persistence.rs
Original file line number Diff line number Diff line change
Expand Up @@ -177,11 +177,18 @@ pub struct PersistenceCallbacks {
),
>,
/// Called once per registration round with the wallet's
/// network tag + birth height. `network` uses the same
/// discriminant as `WalletRestoreEntryFFI.network` (0 = Mainnet,
/// 1 = Testnet, 2 = Devnet, 3 = Regtest). `birth_height` is the
/// best estimate of the block at which the wallet started; zero
/// means "scan from genesis / unknown".
/// network tag, network-independent group id + birth height.
/// `network` uses the same discriminant as
/// `WalletRestoreEntryFFI.network` (0 = Mainnet, 1 = Testnet,
/// 2 = Devnet, 3 = Regtest). `wallet_group_id` points to 32
/// readable bytes (same shape as `wallet_id`) — the
/// NETWORK-INDEPENDENT id shared by every network's wallet derived
/// from the same seed, so a consumer can group a seed's
/// sibling-network rows by it (the per-network `wallet_id` differs
/// per network for the same seed). For watch-only /
/// external-signable wallets it equals `wallet_id` (a group of
/// one). `birth_height` is the best estimate of the block at which
/// the wallet started; zero means "scan from genesis / unknown".
///
/// Returns 0 on success. A non-zero return flips the round's
/// `success` flag to `false` so [`Self::on_changeset_end_fn`]
Expand All @@ -191,6 +198,7 @@ pub struct PersistenceCallbacks {
context: *mut c_void,
wallet_id: *const u8,
network: FFINetwork,
wallet_group_id: *const u8,
birth_height: u32,
) -> i32,
>,
Expand Down Expand Up @@ -570,6 +578,7 @@ impl PlatformWalletPersistence for FFIPersister {
self.callbacks.context,
wallet_id.as_ptr(),
meta.network.into(),
meta.wallet_group_id.as_ptr(),
meta.birth_height,
)
};
Expand Down
16 changes: 14 additions & 2 deletions packages/rs-platform-wallet/src/changeset/changeset.rs
Original file line number Diff line number Diff line change
Expand Up @@ -794,8 +794,9 @@ impl Merge for TokenBalanceChangeSet {

/// Per-wallet metadata captured at registration. Carries fields not
/// derivable from the xpub alone: which network the wallet is bound
/// to and the birth-height best estimate (the SPV tip at create time;
/// 0 means "scan from genesis / unknown").
/// to, the network-independent group id that ties a seed's per-network
/// wallets together, and the birth-height best estimate (the SPV tip
/// at create time; 0 means "scan from genesis / unknown").
///
/// The shape sits on [`PlatformWalletChangeSet`] as
/// `Option<WalletMetadataEntry>` because the round emits at most one
Expand All @@ -811,6 +812,17 @@ impl Merge for TokenBalanceChangeSet {
pub struct WalletMetadataEntry {
/// Network the wallet is bound to.
pub network: Network,
/// Network-INDEPENDENT 32-byte id shared by every network's wallet
/// derived from the same seed. Computed as
/// `Wallet::compute_wallet_id_from_root_extended_pub_key(root, None)`
/// — `SHA256(root_public_key || root_chain_code)` with no network
/// byte folded in. Distinct from the per-network [`Self::network`]-
/// scoped `wallet_id` the changeset is keyed on: that id differs per
/// network for the same seed, this one is the same across all of
/// them, so consumers can group a seed's sibling-network rows by it.
/// For watch-only / external-signable wallets (which carry no root
/// key) this falls back to the scoped `wallet_id` — a group of one.
pub wallet_group_id: [u8; 32],
/// Best estimate of the chain tip at creation time. `0` means
/// "scan from genesis / unknown".
pub birth_height: u32,
Expand Down
Loading
Loading