Skip to content
Merged
9 changes: 7 additions & 2 deletions packages/rs-platform-wallet-ffi/src/manager.rs
Original file line number Diff line number Diff line change
Expand Up @@ -101,7 +101,7 @@ pub unsafe extern "C" fn platform_wallet_manager_create_wallet_from_seed(
};

let option = PLATFORM_WALLET_MANAGER_STORAGE.with_item(manager_handle, |manager| {
runtime().block_on(manager.create_wallet_from_seed_bytes(network, seed, accounts))
runtime().block_on(manager.create_wallet_from_seed_bytes(network, seed, accounts, None))
});
let result = unwrap_option_or_return!(option);
let wallet = unwrap_result_or_return!(result);
Expand Down Expand Up @@ -139,7 +139,12 @@ pub unsafe extern "C" fn platform_wallet_manager_create_wallet_from_mnemonic(
};

let option = PLATFORM_WALLET_MANAGER_STORAGE.with_item(manager_handle, |manager| {
runtime().block_on(manager.create_wallet_from_mnemonic(mnemonic_str, network, accounts))
runtime().block_on(manager.create_wallet_from_mnemonic(
mnemonic_str,
network,
accounts,
None,
))
Comment thread
lklimek marked this conversation as resolved.
Comment thread
lklimek marked this conversation as resolved.
Comment thread
lklimek marked this conversation as resolved.
});
let result = unwrap_option_or_return!(option);
let wallet = unwrap_result_or_return!(result);
Expand Down
1 change: 1 addition & 0 deletions packages/rs-platform-wallet/examples/basic_usage.rs
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,7 @@ async fn main() -> Result<(), Box<dyn std::error::Error>> {
Network::Testnet,
seed_bytes,
WalletAccountCreationOptions::Default,
None,
)
.await?;

Expand Down
62 changes: 48 additions & 14 deletions packages/rs-platform-wallet/src/manager/wallet_lifecycle.rs
Original file line number Diff line number Diff line change
Expand Up @@ -56,11 +56,23 @@ impl<P: PlatformWalletPersistence + 'static> PlatformWalletManager<P> {
/// [`parse_mnemonic_any_language`]). For passphrase-only flows or
/// out-of-band seed material, derive the seed externally and use
/// [`Self::create_wallet_from_seed_bytes`].
///
/// `birth_height_override` controls SPV's compact-filter scan
/// window for the new wallet. `None` (the default for fresh
/// wallets) seeds the birth height to SPV's current confirmed
/// header tip, so the scan window is `[H_now, ∞)` — anything
/// funded before init is invisible. `Some(0)` requests a full
/// historical scan from genesis (use sparingly — expensive on
/// long-lived chains, but required when an address may have
/// received funds before the wallet was first registered).
/// `Some(h)` pins the scan start to a specific block height,
/// useful when a known funding block is on record.
Comment thread
coderabbitai[bot] marked this conversation as resolved.
Outdated
Comment thread
lklimek marked this conversation as resolved.
Outdated
pub async fn create_wallet_from_mnemonic(
&self,
mnemonic_phrase: &str,
network: Network,
accounts: WalletAccountCreationOptions,
birth_height_override: Option<u32>,
) -> Result<Arc<PlatformWallet>, PlatformWalletError> {
let mnemonic = parse_mnemonic_any_language(mnemonic_phrase)
.map_err(|e| PlatformWalletError::WalletCreation(format!("Invalid mnemonic: {}", e)))?;
Expand All @@ -70,35 +82,64 @@ impl<P: PlatformWalletPersistence + 'static> PlatformWalletManager<P> {
e
))
})?;
self.register_wallet(wallet).await
self.register_wallet(wallet, birth_height_override).await
}

/// Create a PlatformWallet from raw seed bytes, initialize persisted
/// state, register it with the manager and return an `Arc` handle.
///
/// See [`Self::create_wallet_from_mnemonic`] for the
/// `birth_height_override` semantics. `None` keeps the
/// pre-existing behaviour (scan from current SPV tip forward);
/// `Some(h)` is for callers that need to see funding deposited
/// before the wallet was registered (e.g. a long-lived bank
/// address pre-funded with testnet duffs).
pub async fn create_wallet_from_seed_bytes(
&self,
network: Network,
seed_bytes: [u8; 64],
accounts: WalletAccountCreationOptions,
birth_height_override: Option<u32>,
) -> Result<Arc<PlatformWallet>, PlatformWalletError> {
let wallet = Wallet::from_seed_bytes(seed_bytes, network, accounts).map_err(|e| {
PlatformWalletError::WalletCreation(format!(
"Failed to create wallet from seed bytes: {}",
e
))
})?;
self.register_wallet(wallet).await
self.register_wallet(wallet, birth_height_override).await
}
Comment thread
lklimek marked this conversation as resolved.

/// Register a pre-built `Wallet` with the manager: insert into the
/// `WalletManager`, build a `PlatformWallet` handle, load persisted
/// state, and return an `Arc` to the managed wallet.
///
/// `birth_height_override` flows through to both the in-memory
/// `ManagedWalletInfo` sync checkpoint and the persisted
/// `WalletMetadataEntry` so the SPV scan window is consistent
/// across restarts. See [`Self::create_wallet_from_mnemonic`] for
/// the contract.
#[allow(clippy::type_complexity)]
async fn register_wallet(
&self,
wallet: Wallet,
birth_height_override: Option<u32>,
) -> Result<Arc<PlatformWallet>, PlatformWalletError> {
let wallet_info = ManagedWalletInfo::from_wallet(&wallet, 0);
// Birth height resolution: explicit override wins; otherwise
// fall back to SPV's confirmed header tip (default for fresh
// wallets — they only need to see funding from now on); 0 if
// SPV isn't running yet.
let birth_height: u32 = match birth_height_override {
Some(h) => h,
None => self
.spv_manager
.sync_progress()
.await
.and_then(|p| p.headers().ok().map(|h| h.tip_height()))
.unwrap_or(0),
};
Comment on lines 126 to +143

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💬 Nitpick: Option<u32> overloads three distinct birth-height policies

birth_height_override: Option<u32> encodes three semantically different policies — 'pick a default from SPV state (else fall back to 0)', 'force a full genesis rescan' (Some(0)), and 'pin to height h' (Some(h>0)). The rustdoc at lines 60-72 spells this out because the type itself doesn't, and Some(0) colliding with the None-fallback value is a foot-gun. A dedicated enum (e.g. BirthHeightPolicy::{Auto, FromGenesis, At(u32)}) would make the three intents explicit at every call site and let the resolver match exhaustively. Not blocking; easiest to reshape now while the API has no external Rust consumers, harder once iOS picks it up.

source: ['claude']


let wallet_info = ManagedWalletInfo::from_wallet(&wallet, birth_height);

let balance = Arc::new(WalletBalance::new());

Expand Down Expand Up @@ -192,17 +233,10 @@ impl<P: PlatformWalletPersistence + 'static> PlatformWalletManager<P> {
// the persister is a best-effort channel, not a source of
// truth in steady state.

// Birth height = SPV's confirmed header tip if SPV is running,
// otherwise 0 (caller can bump it later when SPV catches up).
// 0 means "scan from genesis", which is safe-correct for
// fresh wallets.
let birth_height: u32 = self
.spv_manager
.sync_progress()
.await
.and_then(|p| p.headers().ok().map(|h| h.tip_height()))
.unwrap_or(0);

// `birth_height` was resolved at the top of `register_wallet`
// and seeded into `ManagedWalletInfo`; reuse it here so the
// persisted `WalletMetadataEntry` agrees with the in-memory
// sync checkpoint.
let mut registration_changeset = PlatformWalletChangeSet {
wallet_metadata: Some(WalletMetadataEntry {
network: self.sdk.network,
Expand Down
7 changes: 6 additions & 1 deletion packages/rs-platform-wallet/tests/spv_sync.rs
Original file line number Diff line number Diff line change
Expand Up @@ -182,7 +182,12 @@ async fn test_spv_sync_and_balance() {
let seed_bytes = mnemonic.to_seed("");

let platform_wallet = manager
.create_wallet_from_seed_bytes(network, seed_bytes, WalletAccountCreationOptions::Default)
.create_wallet_from_seed_bytes(
network,
seed_bytes,
WalletAccountCreationOptions::Default,
None,
)
.await
.expect("Failed to create platform wallet");

Expand Down
Loading