Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
106 commits
Select commit Hold shift + click to select a range
9a9c2f1
chore: update to new platform and dashcore
shumkov Mar 17, 2026
e6c9c99
feat: add PlatformWalletManager bridge to AppContext
shumkov Mar 17, 2026
33ed537
feat: migrate backend tasks to validate via PlatformWallet bridge
shumkov Mar 18, 2026
1d9e242
feat: migrate signing callsites to PlatformAddressWallet
shumkov Mar 18, 2026
8d5f530
feat: PR-2 evo-tool integration — signing, asset locks, address table
shumkov Mar 18, 2026
2b12cf5
Merge remote-tracking branch 'origin/v1.0-dev' into feat/platform-wallet
shumkov Mar 31, 2026
76f71d5
fix: use Arc clone instead of &self in shielded task dispatch
shumkov Mar 31, 2026
c787b66
feat: PR-9 Phase 1 — migrate token tasks to platform-wallet
shumkov Mar 31, 2026
d19250c
feat: PR-9 Phase 2 — migrate identity tasks to platform-wallet
shumkov Mar 31, 2026
1b1d602
feat: PR-13 — migrate registration, top-up, discovery to platform-wallet
shumkov Apr 1, 2026
8e3b0f4
feat: migrate remaining token tasks — destroy, pause, resume, update_…
shumkov Apr 1, 2026
808ea34
feat: migrate refresh_identity to platform-wallet
shumkov Apr 1, 2026
479f03a
feat: migrate load_identity_from_wallet + load_by_dpns_name to platfo…
shumkov Apr 1, 2026
86860f9
feat: route all identities through platform-wallet IdentityManager
shumkov Apr 1, 2026
0f970f8
feat: migrate DashPay send/accept contact requests to platform-wallet
shumkov Apr 1, 2026
958fe69
feat: migrate refresh_dpns_names to SDK query
shumkov Apr 1, 2026
2728e1c
refactor: replace DPNS DocumentQuery boilerplate with SDK method
shumkov Apr 1, 2026
ee18f7e
fix: address review findings in evo-tool migration
shumkov Apr 1, 2026
64c2264
fix(spv): restore SpvEventHandler lost during v1.0-dev merge
shumkov Apr 1, 2026
f1089ee
revert: restore original 60s SPV peer timeout
shumkov Apr 1, 2026
73f2c6d
chore: update testnet DAPI addresses in .env.example
shumkov Apr 1, 2026
5987991
fix: adapt to TransactionRecord API changes in dashcore update
shumkov Apr 2, 2026
2f3d7da
refactor: use unified build_asset_lock_transaction API
shumkov Apr 2, 2026
e854311
refactor: adapt to PlatformWalletManager API changes
shumkov Apr 2, 2026
d9d682b
refactor: use CoreWallet.wallet_info() lock accessor
shumkov Apr 2, 2026
8d4311f
Merge remote-tracking branch 'origin/v1.0-dev' into feat/platform-wallet
shumkov Apr 2, 2026
a07084a
fix: use Arc clone in DiscoverDapiNodes dispatch (merge fix)
shumkov Apr 2, 2026
dfc5000
feat: sync balance to ManagedWalletInfo from SPV and RPC
shumkov Apr 3, 2026
4793e5e
feat: migrate MCP wallet balance to WalletBalance (lock-free)
shumkov Apr 3, 2026
d42e559
feat: migrate UI balance reads to lock-free WalletBalance
shumkov Apr 3, 2026
ca0e66e
feat: migrate UI UTXO/address reads to PlatformWallet
shumkov Apr 3, 2026
8678c6e
feat: embed PlatformWallet inside Wallet struct
shumkov Apr 3, 2026
52580a0
refactor: remove platform_wallets bridge map from AppContext
shumkov Apr 3, 2026
8a5bce2
refactor: remove duplicate balance fields from Wallet struct
shumkov Apr 3, 2026
98bc3dd
refactor: remove address_balances and address_total_received from Wallet
shumkov Apr 3, 2026
f1bc41b
refactor: migrate RPC send payment to PlatformWallet, remove dead tx …
shumkov Apr 3, 2026
df48447
refactor: migrate all asset lock building to PlatformWallet
shumkov Apr 3, 2026
d9a957c
refactor: remove dead UTXO code, make used_utxos optional
shumkov Apr 3, 2026
3b37f02
refactor: migrate known_addresses reads to PlatformWallet helpers
shumkov Apr 3, 2026
c7fee99
refactor: migrate address_input core addresses to CoreAddressInfo
shumkov Apr 3, 2026
a665551
fix: derivation_path_for_address falls back to known_addresses
shumkov Apr 3, 2026
710f7b4
feat: bootstrap DashPay contact accounts on wallet load
shumkov Apr 3, 2026
38f46fc
refactor: remove known_addresses/watched_addresses writes from DashPay
shumkov Apr 3, 2026
c82dae2
fix: bootstrap contact accounts into both Wallet and ManagedWalletInfo
shumkov Apr 3, 2026
038793b
feat: delegate receive_address/change_address to PlatformWallet
shumkov Apr 3, 2026
6ebf47e
refactor: remove utxos field from Wallet struct
shumkov Apr 3, 2026
b068daf
refactor: migrate watched_addresses reads to CoreAddressInfo
shumkov Apr 3, 2026
f8561d5
refactor: migrate remaining known_addresses reads to CoreAddressInfo
shumkov Apr 3, 2026
e3f1d5d
refactor: skip address bootstrap when PlatformWallet available
shumkov Apr 3, 2026
636be80
refactor: restrict known/watched_addresses visibility, remove network…
shumkov Apr 3, 2026
37bc648
fix: remove known_addresses fallback from derivation_path_for_address
shumkov Apr 3, 2026
24ac401
refactor: make transactions field pub(crate)
shumkov Apr 3, 2026
224a217
refactor: remove all known/watched_addresses fallbacks for locked wal…
shumkov Apr 3, 2026
8f7070e
refactor: remove bootstrap_known_addresses and all sub-methods (dead …
shumkov Apr 3, 2026
3a65a56
refactor: create PlatformWallet in register_wallet, remove more known…
shumkov Apr 3, 2026
5224222
refactor: remove derive_first_address, initialize known/watched empty
shumkov Apr 3, 2026
5696fe2
refactor: remove known_addresses and watched_addresses fields from Wa…
shumkov Apr 3, 2026
1d299b4
refactor: use Arc<PlatformWallet> in Wallet struct
shumkov Apr 4, 2026
6d5dbae
refactor: remove transactions field from Wallet, read from ManagedWal…
shumkov Apr 4, 2026
fc4cef0
refactor: remove dead wallet methods
shumkov Apr 4, 2026
162b028
feat: add SqliteWalletPersister implementing WalletPersistence
shumkov Apr 4, 2026
3ab7158
feat: wire persist() calls after SPV reconciliation and user actions
shumkov Apr 4, 2026
ce2a596
refactor: update to PlatformWalletChangeSet
shumkov Apr 5, 2026
ea89a3a
feat: stage PlatformWalletChangeSet after contact/identity operations
shumkov Apr 5, 2026
88bdd2b
feat: complete changeset persistence — update SqlitePersister, remove…
shumkov Apr 5, 2026
369840d
fix: stage changesets for contact account creation and identity balan…
shumkov Apr 6, 2026
1017a01
refactor: wrap SingleKeyWallet DB writes in atomic transactions
shumkov Apr 6, 2026
ad145f9
feat: complete SqlitePersister — persist all changeset types, load in…
shumkov Apr 6, 2026
b888127
refactor: update to new PlatformWalletPersistence trait
shumkov Apr 6, 2026
3a5505d
feat: wire initialize through apply, add FlushStrategy
shumkov Apr 6, 2026
82564c6
refactor: rename persistence/ to changeset/ in evo-tool
shumkov Apr 6, 2026
f84c768
refactor: remove platform_address_info and duplicate platform address…
shumkov Apr 6, 2026
f8b8c94
refactor: delete duplicate DIP-14 derivation code from evo-tool
shumkov Apr 6, 2026
0c3a697
refactor: use create_funded_asset_lock_proof for FundWithWallet path
shumkov Apr 6, 2026
f967c93
refactor: use AssetLockManager instead of CoreWallet for asset locks
shumkov Apr 6, 2026
8dc8127
refactor: remove spv_runtime parameter from create_funded_asset_lock_…
shumkov Apr 6, 2026
0b97446
refactor: sync asset locks with AssetLockManager on recovery and dete…
shumkov Apr 6, 2026
591449d
refactor: update to new platform-wallet AssetLockManager API
shumkov Apr 6, 2026
50c5c36
refactor: update to OutPoint-keyed AssetLockManager API
shumkov Apr 6, 2026
4a39c13
refactor: simplify UseAssetLock to pass OutPoint instead of raw data
shumkov Apr 6, 2026
71c93e9
refactor: pass None for new PutSettings parameter in identity operations
shumkov Apr 6, 2026
b4b409f
feat: persist full TrackedAssetLock state in SQLite
shumkov Apr 6, 2026
0b4ca9b
refactor: pass OutPoint to recover_asset_lock_blocking instead of out…
shumkov Apr 6, 2026
dc172ad
refactor: remove unused_asset_locks Vec, read from AssetLockManager
shumkov Apr 6, 2026
5965919
refactor: remove dead orchestration code, migrate to AssetLockManager
shumkov Apr 6, 2026
57af8b8
refactor: remove FundWithUtxo, add TODO for DB-based asset lock reads
shumkov Apr 6, 2026
1ea6fbc
refactor: use PlatformWalletManager to create wallets, UI reads from DB
shumkov Apr 6, 2026
f5781cb
chore: remove unused Hash import from asset_lock_detail_screen
shumkov Apr 6, 2026
bec0ce3
refactor: remove dead code — asset_lock_transaction.rs, identities field
shumkov Apr 6, 2026
aeb0fce
feat: add SpvEventBridge and extract SpvStatus types
shumkov Apr 6, 2026
79321a8
feat: replace SpvManager with PlatformWalletManager (PR-28)
shumkov Apr 6, 2026
329decf
refactor: adapt to shared persister on PlatformWalletManager
shumkov Apr 6, 2026
b24ed1c
refactor: use block_in_place for async create_wallet_from_seed_bytes
shumkov Apr 6, 2026
61ffdc8
refactor: rename persister methods — store/flush/load
shumkov Apr 6, 2026
edcce8d
refactor: use SpvRuntime for asset lock broadcast in create_asset_lock
shumkov Apr 6, 2026
dff566b
refactor: use wallet_manager.broadcast_transaction() instead of .spv()
shumkov Apr 6, 2026
6c66e64
fix: fix test compilation — rename initialize→load, fix Arc return type
shumkov Apr 6, 2026
b8281cd
fix: update backend-e2e tests for SPV migration
shumkov Apr 7, 2026
8d28cf5
fix: enable testing feature for backend-e2e test target
shumkov Apr 7, 2026
40833c8
fix: fix blocking_read panic in async context, reorder test harness
shumkov Apr 7, 2026
97d9ffe
fix: stable e2e test workdir, async wallet info in reconcile
shumkov Apr 7, 2026
e1fe340
refactor: migrate to single-lock PlatformWalletInfo API
shumkov Apr 7, 2026
34d44dc
refactor: use wallet.state() instead of wallet.core().state()
shumkov Apr 8, 2026
0262cfc
fix(test): resolve deadlock in E2E test cleanup — don't hold std::syn…
shumkov Apr 8, 2026
4de564b
refactor(identity): switch to platform-wallet one-call identity APIs
shumkov Apr 8, 2026
5412d9e
feat: integrate WalletManager API and wire BalanceUpdated events
shumkov Apr 8, 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
579 changes: 384 additions & 195 deletions Cargo.lock

Large diffs are not rendered by default.

29 changes: 28 additions & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ qrcode = "0.14.1"
nix = { version = "0.31.1", features = ["signal"] }
eframe = { version = "0.33.3", features = ["persistence", "wgpu"] }
base64 = "0.22.1"
dash-sdk = { git = "https://github.com/dashpay/platform", rev = "a10190399d7033e7e56e3f756411e9a5dab87829", features = [
dash-sdk = { path = "../platform/packages/rs-sdk", features = [
"core_key_wallet",
"core_key_wallet_manager",
"core_bincode",
Expand All @@ -28,6 +28,7 @@ dash-sdk = { git = "https://github.com/dashpay/platform", rev = "a10190399d7033e
"core_spv",
"shielded",
] }
platform-wallet = { path = "../platform/packages/rs-platform-wallet" }
rs-sdk-trusted-context-provider = { git = "https://github.com/dashpay/platform", rev = "a10190399d7033e7e56e3f756411e9a5dab87829" }
zip32 = "0.2.0"
grovestark = { git = "https://www.github.com/dashpay/grovestark", rev = "5b9e289cca54c79b1305d5f4f40bf1148f1eb0e3" }
Expand Down Expand Up @@ -113,6 +114,16 @@ name = "det-cli"
path = "src/bin/det_cli/main.rs"
required-features = ["cli"]

[[test]]
name = "e2e"
path = "tests/e2e/main.rs"
required-features = ["testing"]

[[test]]
name = "backend-e2e"
path = "tests/backend-e2e/main.rs"
required-features = ["testing"]

[build-dependencies]
winres = "0.1"

Expand All @@ -130,3 +141,19 @@ check-cfg = ["cfg(tokio_unstable)", "cfg(feature, values(\"testing\", \"mcp\", \
[lints.clippy]
uninlined_format_args = "allow"

# Override the git dashcore crates with local versions (since the platform
# workspace uses `git = "https://github.com/dashpay/rust-dashcore"` for its
# dashcore deps and we have local changes in rust-dashcore).
[patch."https://github.com/dashpay/rust-dashcore"]
dashcore = { path = "../rust-dashcore/dash" }
dash-spv = { path = "../rust-dashcore/dash-spv" }
key-wallet = { path = "../rust-dashcore/key-wallet" }
key-wallet-manager = { path = "../rust-dashcore/key-wallet-manager" }
dashcore-rpc = { path = "../rust-dashcore/rpc-client" }
dashcore-rpc-json = { path = "../rust-dashcore/rpc-json" }

# dashcore_hashes is published on crates.io but we need the local version
# to match our local dashcore changes.
[patch.crates-io]
dashcore_hashes = { path = "../rust-dashcore/hashes" }

59 changes: 55 additions & 4 deletions src/app.rs
Original file line number Diff line number Diff line change
Expand Up @@ -117,6 +117,10 @@ pub struct AppState {
/// Shared MCP context -- follows network switches via `ArcSwap`.
#[cfg(feature = "mcp")]
pub mcp_app_context: Option<Arc<arc_swap::ArcSwap<AppContext>>>,
/// Keeps the temporary data directory alive for the lifetime of `AppState`
/// when running under the `testing` feature.
#[cfg(feature = "testing")]
_temp_dir: Option<tempfile::TempDir>,
}

#[derive(Debug, Clone, PartialEq)]
Expand Down Expand Up @@ -210,21 +214,31 @@ impl AppState {
Self::new_inner(ctx, db, data_dir)
}

/// Creates a new `AppState` using an in-memory database for testing.
/// Creates a new `AppState` using an in-memory database and an isolated
/// temporary data directory for testing.
///
/// Available only when the `testing` feature is active. This prevents tests
/// from reading or writing the production database.
/// from reading or writing the production database and ensures parallel
/// tests never share the same data directory.
#[cfg(feature = "testing")]
pub fn new(ctx: egui::Context) -> Result<Self, Box<dyn std::error::Error + Send + Sync>> {
let data_dir = app_user_data_dir_path()?;
// Create an isolated temp directory so parallel tests never collide.
let temp_dir = tempfile::tempdir()
.map_err(|e| format!("Failed to create temp data dir: {}", e))?;
let data_dir = temp_dir.path().to_path_buf();
ensure_data_dir_exists(&data_dir)?;

// Copy the bundled .env.example so Config::load_from succeeds.
ensure_env_file(&data_dir);

let db = Arc::new(
crate::database::test_helpers::create_test_database()
.map_err(|e| format!("Failed to create test database: {}", e))?,
);
Self::new_inner(ctx, db, data_dir)
let mut state = Self::new_inner(ctx, db, data_dir)?;
// Keep the temp dir alive for the lifetime of AppState.
state._temp_dir = Some(temp_dir);
Ok(state)
}

fn new_inner(
Expand Down Expand Up @@ -791,6 +805,8 @@ impl AppState {
accessibility_retries: 0,
#[cfg(feature = "mcp")]
mcp_app_context,
#[cfg(feature = "testing")]
_temp_dir: None,
};

// Initialize welcome screen if needed (after mainnet_app_context is owned by the struct)
Expand Down Expand Up @@ -879,6 +895,39 @@ impl AppState {
}
}

/// Flush all platform wallet persisters across every network context.
///
/// Called during shutdown to ensure any staged-but-unflushed changesets
/// (e.g. from `FlushStrategy::Manual`) are written before the process exits.
fn flush_all_wallet_persistence(&self) {
let contexts: Vec<&Arc<AppContext>> = [
Some(&self.mainnet_app_context),
self.testnet_app_context.as_ref(),
self.devnet_app_context.as_ref(),
self.local_app_context.as_ref(),
]
.into_iter()
.flatten()
.collect();

for ctx in contexts {
if let Ok(wallets) = ctx.wallets.read() {
for wallet_arc in wallets.values() {
if let Ok(wallet) = wallet_arc.read() {
if let Some(pw) = &wallet.platform_wallet {
if let Err(e) = pw.flush_persist() {
tracing::warn!(
error = %e,
"Failed to flush wallet persistence on shutdown"
);
}
}
}
}
}
}
}

fn context_available_for_network(&self, network: Network) -> bool {
match network {
Network::Mainnet => true, // Mainnet is always available
Expand Down Expand Up @@ -1200,6 +1249,7 @@ impl App for AppState {
MessageType::Warning,
);
tracing::debug!("Close requested, starting async shutdown");
self.flush_all_wallet_persistence();
self.shutdown_receiver = Some(self.subtasks.shutdown_async());
self.shutdown_started = Some(std::time::Instant::now());
ctx.request_repaint();
Expand Down Expand Up @@ -1655,6 +1705,7 @@ impl App for AppState {
return;
}
tracing::debug!("on_exit: fallback blocking shutdown");
self.flush_all_wallet_persistence();
if let Err(e) = self.subtasks.shutdown() {
tracing::error!("Error during task shutdown: {}", e);
}
Expand Down
160 changes: 94 additions & 66 deletions src/backend_task/core/create_asset_lock.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ use crate::backend_task::error::TaskError;
use crate::context::AppContext;
use crate::model::wallet::Wallet;
use dash_sdk::dpp::balances::credits::CREDITS_PER_DUFF;
use dash_sdk::dpp::dashcore::OutPoint;
use dash_sdk::dpp::fee::Credits;
use std::sync::{Arc, RwLock};

Expand All @@ -11,98 +12,125 @@ impl AppContext {
&self,
wallet: Arc<RwLock<Wallet>>,
amount: Credits,
allow_take_fee_from_amount: bool,
_allow_take_fee_from_amount: bool,
identity_index: u32,
) -> Result<BackendTaskSuccessResult, TaskError> {
let amount_duffs = amount / CREDITS_PER_DUFF;

let (asset_lock_transaction, _private_key, _change_address, _used_utxos) = {
let mut wallet_guard = wallet.write()?;

wallet_guard
.registration_asset_lock_transaction(
self,
self.network,
amount_duffs,
allow_take_fee_from_amount,
identity_index,
None,
)
.map_err(|e| TaskError::AssetLockTransactionBuildFailed { detail: e })?
let (platform_wallet, _seed_hash) = {
let guard = wallet.read()?;
let pw = guard
.platform_wallet
.clone()
.ok_or(TaskError::WalletNotFound)?;
(pw, guard.seed_hash())
};

let tx_id = asset_lock_transaction.txid();

{
let mut proofs = self.transactions_waiting_for_finality.lock()?;
proofs.insert(tx_id, None);
}

if let Err(e) = self
.broadcast_raw_transaction(&asset_lock_transaction)
let (tx, _private_key) = platform_wallet
.asset_locks()
.build_asset_lock_transaction(
amount_duffs,
0,
platform_wallet::AssetLockFundingType::IdentityRegistration,
identity_index,
)
.await
{
if let Ok(mut proofs) = self.transactions_waiting_for_finality.lock() {
proofs.remove(&tx_id);
} else {
tracing::warn!(
"Failed to clean up finality tracking for tx {tx_id}: Mutex poisoned"
);
}
return Err(e);
}
.map_err(|e| TaskError::AssetLockTransactionBuildFailed {
detail: e.to_string(),
})?;

Ok(BackendTaskSuccessResult::Message(format!(
"Asset lock transaction broadcast successfully. TX ID: {}",
tx_id
)))
let result = self
.broadcast_and_track_asset_lock_via_manager(
tx,
amount_duffs,
platform_wallet::AssetLockFundingType::IdentityRegistration,
identity_index,
&platform_wallet,
)
.await?;

Ok(result)
}

pub async fn create_top_up_asset_lock(
&self,
wallet: Arc<RwLock<Wallet>>,
amount: Credits,
allow_take_fee_from_amount: bool,
_allow_take_fee_from_amount: bool,
identity_index: u32,
top_up_index: u32,
_topup_index: u32,
) -> Result<BackendTaskSuccessResult, TaskError> {
let amount_duffs = amount / CREDITS_PER_DUFF;

let (asset_lock_transaction, _private_key, _change_address, _used_utxos) = {
let mut wallet_guard = wallet.write()?;

wallet_guard
.top_up_asset_lock_transaction(
self,
self.network,
amount_duffs,
allow_take_fee_from_amount,
identity_index,
top_up_index,
None,
)
.map_err(|e| TaskError::AssetLockTransactionBuildFailed { detail: e })?
let (platform_wallet, _seed_hash) = {
let guard = wallet.read()?;
let pw = guard
.platform_wallet
.clone()
.ok_or(TaskError::WalletNotFound)?;
(pw, guard.seed_hash())
};

let (tx, _private_key) = platform_wallet
.asset_locks()
.build_asset_lock_transaction(
amount_duffs,
0,
platform_wallet::AssetLockFundingType::IdentityTopUp,
identity_index,
)
.await
.map_err(|e| TaskError::AssetLockTransactionBuildFailed {
detail: e.to_string(),
})?;

let result = self
.broadcast_and_track_asset_lock_via_manager(
tx,
amount_duffs,
platform_wallet::AssetLockFundingType::IdentityTopUp,
identity_index,
&platform_wallet,
)
.await?;

Ok(result)
}

/// Build, register, and broadcast an asset lock transaction via
/// AssetLockManager. The TX is tracked internally by the manager;
/// the proof will arrive later via SPV events.
async fn broadcast_and_track_asset_lock_via_manager(
&self,
asset_lock_transaction: dash_sdk::dpp::dashcore::Transaction,
amount_duffs: u64,
funding_type: platform_wallet::AssetLockFundingType,
identity_index: u32,
platform_wallet: &platform_wallet::PlatformWallet,
) -> Result<BackendTaskSuccessResult, TaskError> {
let tx_id = asset_lock_transaction.txid();
let out_point = OutPoint::new(tx_id, 0);

{
let mut proofs = self.transactions_waiting_for_finality.lock()?;
proofs.insert(tx_id, None);
}
// Register with AssetLockManager so SPV events can resolve it.
platform_wallet.asset_locks().recover_asset_lock_blocking(
asset_lock_transaction.clone(),
amount_duffs,
0,
funding_type,
identity_index,
out_point,
None,
);

// Broadcast via SPV P2P peers.
if let Err(e) = self
.broadcast_raw_transaction(&asset_lock_transaction)
.wallet_manager
.broadcast_transaction(&asset_lock_transaction)
.await
{
if let Ok(mut proofs) = self.transactions_waiting_for_finality.lock() {
proofs.remove(&tx_id);
} else {
tracing::warn!(
"Failed to clean up finality tracking for tx {tx_id}: Mutex poisoned"
);
}
return Err(e);
return Err(TaskError::AssetLockTransactionBuildFailed {
detail: e.to_string(),
});
}

Ok(BackendTaskSuccessResult::Message(format!(
Expand Down
Loading