Remove CoinJoin mixing; begin Kotlin SDK migration (Phases 1, 2, 0 prep + Phase 3 start) - #1507
Remove CoinJoin mixing; begin Kotlin SDK migration (Phases 1, 2, 0 prep + Phase 3 start)#1507bfoss765 wants to merge 135 commits into
Conversation
…placement for CoinJoin) Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
- gift-card metadata cache wrote the partially-populated incoming card instead of the merged one, erasing stored fields; also merge redeemUrlChallenge like other fields - CSV export crashed with uninitialized metadataMap unless callers remembered initMetadataMap(); exportString() now self-initializes - GetUsernameVotingResultOperationTest assumed a 2-minute buffer; production DELAY_AFTER_VOTING_PERIOD is 1 minute - gift-card test updated for the insertGiftCardData signature added by the redeem-url change (order/giftCardChallenge/index) Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
CoinJoin removal (Phase 2 of the Kotlin SDK migration plan): - delete the mixing engine wiring (CoinJoinService, CoinJoinConfig, DI binding), all mixing UI (CoinJoinActivity/info/level screens, MixingStatusCard, MixDashFirst dialog, settings row), mixing notification/foreground-service promotion, coinjoin coin selectors, analytics events, and all related strings/drawables/layouts/nav across every locale - previously mixed funds STAY SPENDABLE: the wallet still loads as WalletEx and the CoinJoin keychain is still provisioned on load/create/restore/upgrade, so legacy mixed UTXOs remain visible and are selected by the standard ZeroConfCoinSelector; historical mixing transactions keep grouped display - send flow always uses the standard selectors; useCoinJoin threading removed from identity/invite/top-up funding (dashj still receives useCoinJoin=false) - fix latent time-skew dialog bug exposed by removing the coinjoin branch (seconds were divided by 1000 again and shown as minutes) Start of Phase 1 (dashj seam neutralization in common): - new neutral SyncStage enum; BlockchainStateProvider no longer exposes PeerGroup.SyncStage or AbstractBlockChain - remove dead SendPaymentService.isFeeTooHigh (no callers, always false) - consolidate observeSpendableBalance into observeTotalBalance (identical streams after mixing removal) Verified: assemble_testNet3Debug and the full wallet unit-test suite pass. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
New org.dash.wallet.common.money kit (Dash, FiatValue, MoneyFormat, DashAddressValidator, ExchangeRate conversions): mirrors the Coin/Fiat/ MonetaryFormat APIs and delegates to dashj internally, so behavior is identical while feature/integration modules no longer see dashj types. WalletDataProvider gains neutral networkId/receive-address accessors. integrations/uphold no longer depends on dashj-core. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Gift-card/tx ids flow as hex strings (TxIds helpers), money via the neutral kit, payment URIs via new DashUri parser, FakeDashSpendService moved into the wallet module. dashj-core dependency removed from features/exploredash. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Money/URI/address handling via the neutral kit; the dashj swap-transaction builder (MayaBlockchainApiImpl) moves into the wallet module behind maya's neutral interface. dashj-core dependency removed from integrations/maya. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Neutral sendCoins/estimateNetworkFee overloads on SendPaymentService (txId-hex/TransactionEstimate) implemented in the wallet module; money types via the neutral kit. dashj-core dependency removed from integrations/coinbase. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
CrowdNodeApi and all UI/ViewModel/worker code now use the neutral money kit (Dash amounts, base58 address strings, MoneyFormat). The transaction-protocol layer (tx filters, blockchain api, confirmation handshake) deliberately stays dashj-typed — it moves real funds and is the exact surface that gets rewritten against the Kotlin SDK in the L1-cutover phase — so integrations/crowdnode keeps its dashj-core dependency for that layer only. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Approved prerequisites for the Kotlin SDK (native Halo2/Orchard core is 64-bit-only, minSdk 29). Platform features were already disabled on 32-bit. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Copies PaymentProtocol/PaymentSession/Protos (+X509 helpers) from dashj-core 22.0.3 sources into org.dash.wallet.common.payments.bip70 and repoints all usages. Product decision: BIP70 stays; owning the code lets it be re-based onto the Kotlin SDK when dashj is removed in Phase 5. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…maven-publish added Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
org.dashfoundation:dash-sdk-android:0.1.0-SNAPSHOT (from mavenLocal, built and published from the platform repo's kotlin-sdk package). APK packages the native JNI library for arm64-v8a/x86_64; duplicate-class check passes alongside dashj. Porting PlatformService/DashPay to the SDK comes next. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
|
Important Review skippedToo many files! This PR contains 428 files, which is 378 over the limit of 50. To get a review, narrow the scope: Upgrade to a paid plan to raise the limit. Usage-priced reviews support at most 300 files. ⚙️ Run configurationConfiguration used: defaults Review profile: CHILL Plan: Pro Run ID: ⛔ Files ignored due to path filters (2)
📒 Files selected for processing (447)
You can disable this status message by setting the ✨ Finishing Touches🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
DashSdkService: lazy, explicitly-started SDK runtime (Sdk init, Room DB, WalletStorage, WalletManagerStore activation) mirroring the example app's AppContainer bootstrap order. Zero default behavior change — nothing starts unless ensureStarted() is called; mnemonic bridging is a Phase 3b seam. resolveUsername() is the verified read-side entry point. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
SecurityGuardMnemonicProvider reuses the canonical SecurityFunctions decrypt path (caller owns authentication; provider never prompts). DashSdkService.bindAppWallet(seedWords) idempotently creates/rehydrates the SDK-side wallet (birthHeight=0 until the Phase 5 migration flow maps wallet-creation time to a height). 20 host-JVM tests; no production call sites yet. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
PlatformRepo.getUsername and IdentityRepository.searchUsernames can run on sdk.dpns.resolve/search behind DashPayConfig.USE_KOTLIN_SDK_DPNS_READS (default off, re-read per lookup, automatic fallback to the dashj path on any SDK failure). SdkDpnsMapping synthesizes the dashj document types the existing callers consume; 16 host-JVM mapping/fallback tests. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…Phase 3d) PlatformRepo.getVoteContenders maps sdk.voting.contestedResourceVoteState to dashj Contenders (serialized-document bytes are rs-sdk-identical, so existing deserialization keeps working). Profile reads (updateDashPayProfile, searchUsernames enrichment, contact-profile sync) run on sdk.documents search over dashpay/profile with dashj-parity queries. Same default-off flag and automatic dashj fallback as Phase 3c; 31 new host-JVM mapping tests. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Identity-key derivation parity VERIFIED: dashj's DIP-13 auth tree (m/9'/coin'/5'/0'/0'/0'/i') is byte-identical to the SDK's for identity index 0, so the SDK can sign for dashj-registered identities. Contact-request send and profile create/update route through wallet-bound SDK ops behind USE_KOTLIN_SDK_DASHPAY_WRITES (default off) with a strict three-valued no-double-broadcast contract (NotBroadcast→fallback, Broadcast→skip dashj, Ambiguous→surface, never dashj-retry). Owner-id DPNS reads join the read flag. 40 new host-JVM tests (152 total green). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
SdkWalletBinder binds the app wallet to the SDK and attaches the existing on-chain identity via identityRegistration.discoverIdentities, triggered fire-and-forget where an unlocked key is already in scope (platform sync init, dashpay broadcast paths). Provably inert with flags off; single-flight; never prompts. DIP-15 parity verified funds-safe (xpub/addresses identical; accountReference slice mismatch documented as an SDK hygiene issue). 171 host-JVM tests green. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
One-time seeding (only if unset) in DashPayConfig; release builds keep the flags OFF. QA can still toggle afterwards. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
FirebaseAnalyticsServiceImpl resolved Firebase.analytics at construction, crashing app startup for any build missing the (intentionally optional) Firebase config. Resolve lazily and no-op when FirebaseApp is absent. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Three startup crashes in Firebase-less local builds: placeholder FirebaseApp init (valid-format dummy key) so DI-provided FirebaseAuth constructs; com.crashlytics.RequireBuildId=false so Crashlytics init tolerates the absent gradle-plugin build ID. No effect on builds with the real config. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
… probe Live on-device finding: dashpay.syncState throws a Generic FFI error for identities the wallet doesn't manage instead of returning null, which aborted the binder before identity discovery could run. SDK issue to file: syncState should return null or a typed NotFound. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
… engages 'no private key stored' surfaces pre-submission; observed live when a discovered identity's private keys weren't derived/stored. Contact requests now fall back to dashj instead of erroring. SDK issues to file: typed signing error; discovery should derive/store identity keys (or expose a repair op). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Root cause of live 'no private key stored': the SDK persistence bridge derives identity keys into an auth-gated Keystore alias (30s post-unlock window) and silently skips outside it, so background discovery attached the identity with no signable keys. ensureIdentityKeysSignable derives each key, verifies the public half byte-equals the on-chain key (never poisons the signer store), stores it, and retries on the next binder trigger when the auth window was closed. 11 new tests. SDK issues: expose a repair op that verifies; allow non-gated alias policy. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…e L1 fee The asset-lock coin-selection shortfall is raised while building the transaction, strictly pre-broadcast, so it is NotBroadcast, not Ambiguous (a live Max shield hit the 'may have gone through' dialog on it). Its message reports required WITHOUT the L1 fee (available == required), so unlike the shielded note-selection fix there is no exact deficit to parse: a Max shield instead retries once withholding an estimated fee reserve sized from the wallet's spendable UTXO count (1 duff/byte Rust-side rate, 2x margin — lossless, the builder returns excess as change; dashj's count proxies the SDK's via UTXO parity). Audited every other spend path for fee handling: non-max transfers in both directions classify cleanly post-fix; username-funding gates match Rust's exact-equality fee model (fee metered FROM the denomination, none added at selection); the shield-pool minimum check derives from the prover estimate plus the platform base cost. shieldFromCredits/unshieldToCredits have no UI callers today — their address-funds shortfall messages would need rules before ever being wired up. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…not instant The shield leg builds an L1 asset lock and runs a ~30 s Halo 2 proof before the note lands, so 'instant' misstated it. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…debug seeding The L1 shadow scan requires a bound wallet, but the binder's flag gate and identity gate didn't count USE_KOTLIN_SDK_L1_SHADOW — a shadow-only configuration could never bind (masked on testnet where the shielded flag is seeded too). The shadow flag now opens both gates, binding-only, matching its read-only contract. Debug flag seeding is now network-aware: mainnet debug builds (prodDebug, the external large-wallet validation vehicle) seed ONLY the read-only L1 shadow — never shielded UI or SDK write paths against real funds. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Testers had no user-visible signal for when the SDK shadow scan finishes
(the home header only tracks dashj). Debug builds now fold a 'Kotlin
sync' label into the syncing header — during dashj sync as a suffix
('Syncing 63% · Kotlin sync: scan 42%'), and after dashj finishes the
header stays visible with the Kotlin state alone until 'Kotlin sync
100%' (✓ once a parity probe matches). Percent derives from the
header/filter counts, not the SDK's overallPercent, which under-reports
mid-scan. Hidden entirely on release builds and while the shadow is
idle.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…in percent The first cut's long labels truncated in the header (ellipsize=middle mangled both statuses). Now both engines get a compact status: the Kotlin percent is one monotonic number combining header+filter counts, and the DashJ side is relabeled only while the debug Kotlin label is present — the production 'Syncing N%' rendering is untouched otherwise. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…be, dialog finish-gating 1) checkUsername treated any failed platform lookup as 'name available' (observed live: a registered name showed available). Lookups now fail CLOSED: a non-SUCCESS name query or a thrown contenders read surfaces 'couldn't verify availability' in the entry screen's availability slot, keeps the request button disabled, and logs the failure; retyping retries. getVoteContendersOrThrow propagates errors for this caller while the other call sites keep the old swallow. 2) The L1 funding gate needs a fresh parity report but the parity loop ticks every 60s; after each dashj idle-restart the shadow re-syncs in seconds and the gate then sat closed up to a minute (measured 53s, recurring every idle cycle — blocked live shielded transfers). The loop now also wakes on a conflated SYNCED-edge ping: one immediate probe per edge, schedule and decider logic unchanged. 3) The L1 username submit's processing dialog auto-dismissed: the identity state machine's first creationState flip made the fragments finish the activity under the dialog. Finishing is now gated while a submit is in flight; the dialog's explicit user dismissal finishes to home (programmatic replacement and lifecycle teardown excluded). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Light tertiary text centered below the picture circle, populated from the DashPay profile. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…ation Pre-submit: the username screen probes one DAPI node's core height (Core gRPC getBestBlockHeight — the legacy client has no getStatus) and compares it against dashj's tip; >= 2 blocks behind renders an advisory 'network is running slower than usual' row. Purely informational — the submit gate is untouched, and a failed probe says nothing. During registration: dashj retries the chain-lock asset-lock proof in an internal loop that swallows every per-attempt error (live incident: ~10 minutes of invisible retries on 'core chain height N is higher than the current consensus core height M'), so duration is the only app-side observable — a 30s watchdog around the blocking call flips the home-screen tile hint to 'waiting for the network to catch up', and the per-attempt missing-instant-lock failure (visible app-side) shows 'waiting for network confirmation'. Hints live in an in-memory holder (no schema change) and clear on success. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…l banner at 90s Every username-request submit path (L1 asset lock, shielded, dual) handed the spend to CreateIdentityService / the shielded pipeline, which sign with no user interaction — a non-private username registered on device with no PIN/biometric at all. All five submit sites now route through a shared authenticateThenSubmit gate; a cancelled prompt submits nothing. The error-dialog retry re-authenticates too (fresh spend attempt). Shielded transfer stall banner: 180s → 90s. The live slow case was the SDK waiting one block (~2.5 min on testnet) to confirm its asset lock after its freshly restarted SPV couldn't verify the InstantSend lock, so the banner routinely arrived AFTER the wait was nearly over. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…er 35s dashj's BlockchainService idle-stops tore down the SDK's SPV every few minutes; each restart left it with stale quorum state, and a live shielded transfer right after a restart could not verify its asset lock's InstantSend lock — it waited a full block (~3 min). Debug builds now keep both SDK engines running across routine service teardowns (deliberate battery trade-off for testing). Destructive paths are NOT weakened — the wipe previously relied on shutdown() for the engine stops, so it now calls the new unconditional stopSdkEngines() explicitly before finalizeWipe() on every build type, and the shadow recovery paths already stop the SPV directly. Also: the transfer stall banner fires at 35s (was 90s) per live QA. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…elded flow + auth order Username tile (More screen): show the user's display-form name (was the DPNS-normalized homoglyph label, e.g. 'c0ntested1' for 'contested1') and guard the voting-end date against unset/epoch timestamps (was 'Dec 31, 1969'); root cause was the restore worker persisting the normalized label and a zero votingStartedAt. Tapping the requested-username tile now opens the voting-request details screen instead of dismissing the tile. Username confirm sheet: show the real cost from whichever pool pays — the shielded denomination (0.3 contested / 0.1 non-contested) with a 'from shielded balance' label — instead of a misleading 0.00 on the dual flow's final confirm. Identity creation feedback: the 'network catching up' status hint now also renders as a live secondary line inside the processing dialog the user watches (previously only on the home tile), so a long registration is explained before the auto-lock can fire. Invite flow: new shielded-funding decision sheet (mirrors the username shielded flow — privacy messaging + contested/non-contested cost from the shared denomination source + shield-first vs continue-without-privacy), shown only when shielded features are active. Invite auth moved to the standard order: the fee screen no longer prompts for PIN before showing the amount; authentication happens on the confirm screen right before the spend. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…egression) The prior change showed the shielded denomination on the instant/secondary username confirm in a fresh dual creation. That was wrong: the instant name adds no incremental cost — the identity funding (0.3 shielded / L1 asset lock) is the same with or without it and is disclosed on the PRIMARY contested confirm. Showing a price on the instant confirm contradicted 'instant username at no cost' (and the store behavior). Secondary now always resolves to 0.00; the shielded/L1 cost display stays on the primary confirm. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…t the exit denomination The sheet asks how much to shield, which is the fund-minimum (SHIELDED_USERNAME_FUND_MIN 0.15 / _CONTESTED 0.35 — the 0.1/0.3 Type-20 exit denomination padded for the shielded-spend fee), not the bare denomination (0.1/0.3) that finally leaves the pool. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…icker, entry auth 1) Dismissing a terminal shielded-transfer outcome entered via 'Shield your funds first' unwound the create-username/invite back stack one screen at a time; shield-first entries now clear straight to home (card entries keep the plain finish). 2) The More screen showed the CONTESTED name under the avatar right after a dual creation (profile seeded with the primary; refresh persists the owned instant name) — the under-avatar name now derives from the identity's active username when the dual-voting rule holds. 3) Tapping the requested-username tile flashed and bounced: the XML navGraph eagerly instantiated the request fragment (whose identity observer finishes the activity for a name in voting) before the code set the resolved start destination — graph now set only in code. 4) Opening the shielded transfer screen prompted for auth: createIntent now carries KEEP_UNLOCKED (SendCoinsActivity precedent); auth remains solely on transfer submit. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…d variant
profileDisplayUsername preferred the identity's activeUsername, which is
the DPNS-normalized secondary label (homoglyphs folded: 'c0ntested11-2').
Once the profile refreshes, profileUsername holds that name's display
label ('contested11-2'); prefer it when its normalized form matches the
active label. The normalized value now shows only in the brief first
render before the profile refresh, instead of persistently.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…ool gate The 'shielded pool still syncing' pre-broadcast refusal (nothing spent) surfaced as a red 'network error' dialog. Two fixes: - Fix A: the pool-not-ready reasons are now classified (typed constants + isPoolNotReadyReason) and shown as a calm 'Your shielded balance is still syncing, please wait and try again' dialog, not the error dialog. - Fix B: the Request button follows the LIVE shielded sync status — while the pool is not READY on the shielded path it shows a disabled 'Preparing shielded balance…' and re-enables when ready, instead of accepting the tap from a stale cache and failing at the SDK. The L1 path is never gated on shielded sync status. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The L1/non-shielded submit set usernameRequestSubmitting=true and handed off to CreateIdentityService, which runs out-of-band and never reported back — so the processing dialog stayed up until the user dismissed it manually, even though the identity was already created behind it (the earlier finish-gating fix turned the old screen-finish dismissal into a deadlock for this path). The ViewModel now observes the persisted identity creation state and clears submitting (dismissing the dialog and completing the flow) once it reaches a terminal state (DONE / DONE_AND_DISMISS / VOTING) while a submit is in flight. The shielded path already clears submitting via its executor states — no-op there; a pre-existing DONE with no submit in flight is ignored. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
… on base's upstreamed router fix (dashpay#4074) Rebase of PR dashpay#4074 (fix/kotlin-sdk-assetlock-multi-account) onto feat/kotlin-sdk-and-example-app. The platform-wallet asset-lock changes are preserved; the PR's OWN rust-dashcore customization is dropped because the base branch now carries the same fix upstream. What this brings to the asset-lock code path (packages/rs-platform-wallet): - Multi-account funding builder `build_asset_lock_tx_from_all_funding_accounts` that unions spendable UTXOs across BIP44 + CoinJoin + DashPay funds accounts (dashpay#4073), with LargestFirst coin selection pinned for the many-small-denomination CoinJoin shape. - Exclusion of watch-only `DashpayExternalAccount` UTXOs from the union — those are a contact's coins the local mnemonic can't sign; selecting one yields an invalid input signature. The receiving (ours) DashPay account stays included. - `NoUtxosAvailable` mapped to the typed asset-lock insufficient-funds error. - Regression tests covering the router-fix persistence path (CoinJoin + DashpayReceivingFunds legs) and the watch-only exclusion, plus split-funded test fixtures (`split_funded_wallet_manager`, `..._dashpay`, `..._many_coinjoin`). Why the PR's rust-dashcore vendoring/[patch] is DROPPED: The PR originally shipped the asset-lock transaction-router fix by pinning rust-dashcore at 1860089e and redirecting it via a `[patch]` to a bfoss765 fork (rev e8c7335 = 1860089e + the router fix + a CoinJoin gap-limit 30->100 bump). The base branch's rust-dashcore rev 19690d31 now contains BOTH fixes upstream: * `TransactionRouter::get_relevant_account_types(AssetLock)` includes CoinJoin, DashpayReceivingFunds, and DashpayExternalAccount (via `fund_bearing_account_types()`); * `DEFAULT_COINJOIN_GAP_LIMIT = 100`. So the fork [patch], the pinned 1860089e rev, and the leftover `third_party/rust-dashcore` are all obsolete and removed. Cargo.toml/Cargo.lock are taken as-is from base (rust-dashcore resolves from dashpay @ 19690d31, no patch table). Because base's fix debits CoinJoin/DashPay asset-lock spends via the normal `check_core_transaction` scan, the PR's earlier broadcast-time `debit_router_omitted_asset_lock_spends` mitigation is gone — as it already was in the PR's final state (dashpay/dash-wallet#1507). History note: the PR's 10 original commits touched the same four files the base branch had independently rewritten (+928 lines), and included add-then-remove churn (the interim mitigation) plus vendor-then-git-patch churn that base's upstreamed fix makes moot. They are collapsed into this single commit to keep the rebased history coherent. Verified: `cargo check -p platform-wallet --all-targets`, `-p rs-unified-sdk-jni`, `-p platform-wallet-ffi` all green; `cargo test -p platform-wallet --lib` = 502 passed / 0 failed, including the router-fix and watch-only-exclusion regression tests, against base's 19690d31. Original commits folded in: 9be2e14, 5376235, e1593f4, da97eec (app-code only; vendoring dropped), ce482fd (app-code only; vendored gap-limit dropped), 380645a, b43caed (dropped: pure [patch] plumbing), a5ea9e5, 77561d2, 189e068. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…BIP70 deferred submission BIP70/BIP270 (CTX/DashSpend) sends must sign, POST the raw bytes to a merchant server, and broadcast only on ack — structurally impossible on the one-shot `sendToAddresses`. Expose the existing internal build/broadcast split with an explicit reservation lifecycle, keeping `CoreTransactionBuilder` internal so the manager stays the sole driver of the setFunding/buildSigned race. Rust core (rs-platform-wallet): - New `SignedPaymentRegistry`: a generic, in-memory registry that owns a built+signed tx and its held UTXO reservation between build and submission, keyed by an opaque `ReservationToken`. `broadcast` removes the entry before sending (no double-broadcast — a repeat/concurrent call gets `StaleToken`), binds each token to its originating wallet instance (`Arc::ptr_eq` on the shared `WalletManager`, so a re-created wallet is rejected), and reconciles the reservation on failure via the existing release-on-rejection path. `release` is idempotent. Reservations are memory-only, so a crash between build and broadcast drops both the entry and the reservation on restart — the same property dashj has. - `CoreWallet::release_transaction_reservation` — the explicit "abandoned / nacked" release arm. FFI (platform-wallet-ffi) — additive C ABI: - `core_wallet_transaction_get_bytes`, `core_wallet_signed_payment_register` (token + fee + txid), `core_wallet_signed_payment_broadcast`, `core_wallet_signed_payment_release`, backed by one process-global registry pinned to `SpvBroadcaster`. - New `ErrorStaleReservationToken` (22) result code. JNI (rs-unified-sdk-jni) — additive: `coreTransactionGetBytes`, `coreWalletRegisterSignedPayment` (BLOB), `coreWalletBroadcastSignedPayment`, `coreWalletReleaseSignedPayment`. Kotlin — additive: `ManagedPlatformWallet.SignedCoreTransaction`, `buildSignedPayment` (build under coreSendMutex), `broadcastSigned(token)`, `releaseReservation(token)`; `DashSdkError.PlatformWallet.StaleReservationToken`. No existing signatures change. Refs dashpay#4089, dashpay/dash-wallet#1507 Phase 5c GAP-4. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
… on base's upstreamed router fix (dashpay#4074) Rebase of PR dashpay#4074 (fix/kotlin-sdk-assetlock-multi-account) onto feat/kotlin-sdk-and-example-app. The platform-wallet asset-lock changes are preserved; the PR's OWN rust-dashcore customization is dropped because the base branch now carries the same fix upstream. What this brings to the asset-lock code path (packages/rs-platform-wallet): - Multi-account funding builder `build_asset_lock_tx_from_all_funding_accounts` that unions spendable UTXOs across BIP44 + CoinJoin + DashPay funds accounts (dashpay#4073), with LargestFirst coin selection pinned for the many-small-denomination CoinJoin shape. - Exclusion of watch-only `DashpayExternalAccount` UTXOs from the union — those are a contact's coins the local mnemonic can't sign; selecting one yields an invalid input signature. The receiving (ours) DashPay account stays included. - `NoUtxosAvailable` mapped to the typed asset-lock insufficient-funds error. - Regression tests covering the router-fix persistence path (CoinJoin + DashpayReceivingFunds legs) and the watch-only exclusion, plus split-funded test fixtures (`split_funded_wallet_manager`, `..._dashpay`, `..._many_coinjoin`). Why the PR's rust-dashcore vendoring/[patch] is DROPPED: The PR originally shipped the asset-lock transaction-router fix by pinning rust-dashcore at 1860089e and redirecting it via a `[patch]` to a bfoss765 fork (rev e8c7335 = 1860089e + the router fix + a CoinJoin gap-limit 30->100 bump). The base branch's rust-dashcore rev 19690d31 now contains BOTH fixes upstream: * `TransactionRouter::get_relevant_account_types(AssetLock)` includes CoinJoin, DashpayReceivingFunds, and DashpayExternalAccount (via `fund_bearing_account_types()`); * `DEFAULT_COINJOIN_GAP_LIMIT = 100`. So the fork [patch], the pinned 1860089e rev, and the leftover `third_party/rust-dashcore` are all obsolete and removed. Cargo.toml/Cargo.lock are taken as-is from base (rust-dashcore resolves from dashpay @ 19690d31, no patch table). Because base's fix debits CoinJoin/DashPay asset-lock spends via the normal `check_core_transaction` scan, the PR's earlier broadcast-time `debit_router_omitted_asset_lock_spends` mitigation is gone — as it already was in the PR's final state (dashpay/dash-wallet#1507). History note: the PR's 10 original commits touched the same four files the base branch had independently rewritten (+928 lines), and included add-then-remove churn (the interim mitigation) plus vendor-then-git-patch churn that base's upstreamed fix makes moot. They are collapsed into this single commit to keep the rebased history coherent. Verified: `cargo check -p platform-wallet --all-targets`, `-p rs-unified-sdk-jni`, `-p platform-wallet-ffi` all green; `cargo test -p platform-wallet --lib` = 502 passed / 0 failed, including the router-fix and watch-only-exclusion regression tests, against base's 19690d31. Original commits folded in: 9be2e14, 5376235, e1593f4, da97eec (app-code only; vendoring dropped), ce482fd (app-code only; vendored gap-limit dropped), 380645a, b43caed (dropped: pure [patch] plumbing), a5ea9e5, 77561d2, 189e068. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…BIP70 deferred submission BIP70/BIP270 (CTX/DashSpend) sends must sign, POST the raw bytes to a merchant server, and broadcast only on ack — structurally impossible on the one-shot `sendToAddresses`. Expose the existing internal build/broadcast split with an explicit reservation lifecycle, keeping `CoreTransactionBuilder` internal so the manager stays the sole driver of the setFunding/buildSigned race. Rust core (rs-platform-wallet): - New `SignedPaymentRegistry`: a generic, in-memory registry that owns a built+signed tx and its held UTXO reservation between build and submission, keyed by an opaque `ReservationToken`. `broadcast` removes the entry before sending (no double-broadcast — a repeat/concurrent call gets `StaleToken`), binds each token to its originating wallet instance (`Arc::ptr_eq` on the shared `WalletManager`, so a re-created wallet is rejected), and reconciles the reservation on failure via the existing release-on-rejection path. `release` is idempotent. Reservations are memory-only, so a crash between build and broadcast drops both the entry and the reservation on restart — the same property dashj has. - `CoreWallet::release_transaction_reservation` — the explicit "abandoned / nacked" release arm. FFI (platform-wallet-ffi) — additive C ABI: - `core_wallet_transaction_get_bytes`, `core_wallet_signed_payment_register` (token + fee + txid), `core_wallet_signed_payment_broadcast`, `core_wallet_signed_payment_release`, backed by one process-global registry pinned to `SpvBroadcaster`. - New `ErrorStaleReservationToken` (22) result code. JNI (rs-unified-sdk-jni) — additive: `coreTransactionGetBytes`, `coreWalletRegisterSignedPayment` (BLOB), `coreWalletBroadcastSignedPayment`, `coreWalletReleaseSignedPayment`. Kotlin — additive: `ManagedPlatformWallet.SignedCoreTransaction`, `buildSignedPayment` (build under coreSendMutex), `broadcastSigned(token)`, `releaseReservation(token)`; `DashSdkError.PlatformWallet.StaleReservationToken`. No existing signatures change. Refs dashpay#4089, dashpay/dash-wallet#1507 Phase 5c GAP-4. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
… on base's upstreamed router fix (dashpay#4074) Rebase of PR dashpay#4074 (fix/kotlin-sdk-assetlock-multi-account) onto feat/kotlin-sdk-and-example-app. The platform-wallet asset-lock changes are preserved; the PR's OWN rust-dashcore customization is dropped because the base branch now carries the same fix upstream. What this brings to the asset-lock code path (packages/rs-platform-wallet): - Multi-account funding builder `build_asset_lock_tx_from_all_funding_accounts` that unions spendable UTXOs across BIP44 + CoinJoin + DashPay funds accounts (dashpay#4073), with LargestFirst coin selection pinned for the many-small-denomination CoinJoin shape. - Exclusion of watch-only `DashpayExternalAccount` UTXOs from the union — those are a contact's coins the local mnemonic can't sign; selecting one yields an invalid input signature. The receiving (ours) DashPay account stays included. - `NoUtxosAvailable` mapped to the typed asset-lock insufficient-funds error. - Regression tests covering the router-fix persistence path (CoinJoin + DashpayReceivingFunds legs) and the watch-only exclusion, plus split-funded test fixtures (`split_funded_wallet_manager`, `..._dashpay`, `..._many_coinjoin`). Why the PR's rust-dashcore vendoring/[patch] is DROPPED: The PR originally shipped the asset-lock transaction-router fix by pinning rust-dashcore at 1860089e and redirecting it via a `[patch]` to a bfoss765 fork (rev e8c7335 = 1860089e + the router fix + a CoinJoin gap-limit 30->100 bump). The base branch's rust-dashcore rev 19690d31 now contains BOTH fixes upstream: * `TransactionRouter::get_relevant_account_types(AssetLock)` includes CoinJoin, DashpayReceivingFunds, and DashpayExternalAccount (via `fund_bearing_account_types()`); * `DEFAULT_COINJOIN_GAP_LIMIT = 100`. So the fork [patch], the pinned 1860089e rev, and the leftover `third_party/rust-dashcore` are all obsolete and removed. Cargo.toml/Cargo.lock are taken as-is from base (rust-dashcore resolves from dashpay @ 19690d31, no patch table). Because base's fix debits CoinJoin/DashPay asset-lock spends via the normal `check_core_transaction` scan, the PR's earlier broadcast-time `debit_router_omitted_asset_lock_spends` mitigation is gone — as it already was in the PR's final state (dashpay/dash-wallet#1507). History note: the PR's 10 original commits touched the same four files the base branch had independently rewritten (+928 lines), and included add-then-remove churn (the interim mitigation) plus vendor-then-git-patch churn that base's upstreamed fix makes moot. They are collapsed into this single commit to keep the rebased history coherent. Verified: `cargo check -p platform-wallet --all-targets`, `-p rs-unified-sdk-jni`, `-p platform-wallet-ffi` all green; `cargo test -p platform-wallet --lib` = 502 passed / 0 failed, including the router-fix and watch-only-exclusion regression tests, against base's 19690d31. Original commits folded in: 9be2e14, 5376235, e1593f4, da97eec (app-code only; vendoring dropped), ce482fd (app-code only; vendored gap-limit dropped), 380645a, b43caed (dropped: pure [patch] plumbing), a5ea9e5, 77561d2, 189e068. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
… on base's upstreamed router fix (dashpay#4074) Rebase of PR dashpay#4074 (fix/kotlin-sdk-assetlock-multi-account) onto feat/kotlin-sdk-and-example-app. The platform-wallet asset-lock changes are preserved; the PR's OWN rust-dashcore customization is dropped because the base branch now carries the same fix upstream. What this brings to the asset-lock code path (packages/rs-platform-wallet): - Multi-account funding builder `build_asset_lock_tx_from_all_funding_accounts` that unions spendable UTXOs across BIP44 + CoinJoin + DashPay funds accounts (dashpay#4073), with LargestFirst coin selection pinned for the many-small-denomination CoinJoin shape. - Exclusion of watch-only `DashpayExternalAccount` UTXOs from the union — those are a contact's coins the local mnemonic can't sign; selecting one yields an invalid input signature. The receiving (ours) DashPay account stays included. - `NoUtxosAvailable` mapped to the typed asset-lock insufficient-funds error. - Regression tests covering the router-fix persistence path (CoinJoin + DashpayReceivingFunds legs) and the watch-only exclusion, plus split-funded test fixtures (`split_funded_wallet_manager`, `..._dashpay`, `..._many_coinjoin`). Why the PR's rust-dashcore vendoring/[patch] is DROPPED: The PR originally shipped the asset-lock transaction-router fix by pinning rust-dashcore at 1860089e and redirecting it via a `[patch]` to a bfoss765 fork (rev e8c7335 = 1860089e + the router fix + a CoinJoin gap-limit 30->100 bump). The base branch's rust-dashcore rev 19690d31 now contains BOTH fixes upstream: * `TransactionRouter::get_relevant_account_types(AssetLock)` includes CoinJoin, DashpayReceivingFunds, and DashpayExternalAccount (via `fund_bearing_account_types()`); * `DEFAULT_COINJOIN_GAP_LIMIT = 100`. So the fork [patch], the pinned 1860089e rev, and the leftover `third_party/rust-dashcore` are all obsolete and removed. Cargo.toml/Cargo.lock are taken as-is from base (rust-dashcore resolves from dashpay @ 19690d31, no patch table). Because base's fix debits CoinJoin/DashPay asset-lock spends via the normal `check_core_transaction` scan, the PR's earlier broadcast-time `debit_router_omitted_asset_lock_spends` mitigation is gone — as it already was in the PR's final state (dashpay/dash-wallet#1507). History note: the PR's 10 original commits touched the same four files the base branch had independently rewritten (+928 lines), and included add-then-remove churn (the interim mitigation) plus vendor-then-git-patch churn that base's upstreamed fix makes moot. They are collapsed into this single commit to keep the rebased history coherent. Verified: `cargo check -p platform-wallet --all-targets`, `-p rs-unified-sdk-jni`, `-p platform-wallet-ffi` all green; `cargo test -p platform-wallet --lib` = 502 passed / 0 failed, including the router-fix and watch-only-exclusion regression tests, against base's 19690d31. Original commits folded in: 9be2e14, 5376235, e1593f4, da97eec (app-code only; vendoring dropped), ce482fd (app-code only; vendored gap-limit dropped), 380645a, b43caed (dropped: pure [patch] plumbing), a5ea9e5, 77561d2, 189e068. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…BIP70 deferred submission BIP70/BIP270 (CTX/DashSpend) sends must sign, POST the raw bytes to a merchant server, and broadcast only on ack — structurally impossible on the one-shot `sendToAddresses`. Expose the existing internal build/broadcast split with an explicit reservation lifecycle, keeping `CoreTransactionBuilder` internal so the manager stays the sole driver of the setFunding/buildSigned race. Rust core (rs-platform-wallet): - New `SignedPaymentRegistry`: a generic, in-memory registry that owns a built+signed tx and its held UTXO reservation between build and submission, keyed by an opaque `ReservationToken`. `broadcast` removes the entry before sending (no double-broadcast — a repeat/concurrent call gets `StaleToken`), binds each token to its originating wallet instance (`Arc::ptr_eq` on the shared `WalletManager`, so a re-created wallet is rejected), and reconciles the reservation on failure via the existing release-on-rejection path. `release` is idempotent. Reservations are memory-only, so a crash between build and broadcast drops both the entry and the reservation on restart — the same property dashj has. - `CoreWallet::release_transaction_reservation` — the explicit "abandoned / nacked" release arm. FFI (platform-wallet-ffi) — additive C ABI: - `core_wallet_transaction_get_bytes`, `core_wallet_signed_payment_register` (token + fee + txid), `core_wallet_signed_payment_broadcast`, `core_wallet_signed_payment_release`, backed by one process-global registry pinned to `SpvBroadcaster`. - New `ErrorStaleReservationToken` (22) result code. JNI (rs-unified-sdk-jni) — additive: `coreTransactionGetBytes`, `coreWalletRegisterSignedPayment` (BLOB), `coreWalletBroadcastSignedPayment`, `coreWalletReleaseSignedPayment`. Kotlin — additive: `ManagedPlatformWallet.SignedCoreTransaction`, `buildSignedPayment` (build under coreSendMutex), `broadcastSigned(token)`, `releaseReservation(token)`; `DashSdkError.PlatformWallet.StaleReservationToken`. No existing signatures change. Refs dashpay#4089, dashpay/dash-wallet#1507 Phase 5c GAP-4. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…BIP70 deferred submission BIP70/BIP270 (CTX/DashSpend) sends must sign, POST the raw bytes to a merchant server, and broadcast only on ack — structurally impossible on the one-shot `sendToAddresses`. Expose the existing internal build/broadcast split with an explicit reservation lifecycle, keeping `CoreTransactionBuilder` internal so the manager stays the sole driver of the setFunding/buildSigned race. Rust core (rs-platform-wallet): - New `SignedPaymentRegistry`: a generic, in-memory registry that owns a built+signed tx and its held UTXO reservation between build and submission, keyed by an opaque `ReservationToken`. `broadcast` removes the entry before sending (no double-broadcast — a repeat/concurrent call gets `StaleToken`), binds each token to its originating wallet instance (`Arc::ptr_eq` on the shared `WalletManager`, so a re-created wallet is rejected), and reconciles the reservation on failure via the existing release-on-rejection path. `release` is idempotent. Reservations are memory-only, so a crash between build and broadcast drops both the entry and the reservation on restart — the same property dashj has. - `CoreWallet::release_transaction_reservation` — the explicit "abandoned / nacked" release arm. FFI (platform-wallet-ffi) — additive C ABI: - `core_wallet_transaction_get_bytes`, `core_wallet_signed_payment_register` (token + fee + txid), `core_wallet_signed_payment_broadcast`, `core_wallet_signed_payment_release`, backed by one process-global registry pinned to `SpvBroadcaster`. - New `ErrorStaleReservationToken` (22) result code. JNI (rs-unified-sdk-jni) — additive: `coreTransactionGetBytes`, `coreWalletRegisterSignedPayment` (BLOB), `coreWalletBroadcastSignedPayment`, `coreWalletReleaseSignedPayment`. Kotlin — additive: `ManagedPlatformWallet.SignedCoreTransaction`, `buildSignedPayment` (build under coreSendMutex), `broadcastSigned(token)`, `releaseReservation(token)`; `DashSdkError.PlatformWallet.StaleReservationToken`. No existing signatures change. Refs dashpay#4089, dashpay/dash-wallet#1507 Phase 5c GAP-4. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Summary
First tranche of the dashj → Kotlin SDK migration (full plan:
docs/kotlin-sdk-migration-plan.md).CoinJoin removal (Phase 2)
coinjoin.walletfixturedashj seam neutralization (Phase 1)
org.dash.wallet.common.money(Dash,FiatValue,MoneyFormat, address/URI/txid helpers) — mirrors the dashj APIs and delegates to dashj internally, so behavior is identical by constructionSendPaymentServicegains neutral send/estimate overloadsSDK prerequisites (Phase 0, approved decisions)
org.dash.wallet.common.payments.bip70(kept per product decision; survives dashj removal)647fa982+ (details/conditions in the plan doc)Phase 3 start
org.dashfoundation:dash-sdk-android:0.1.0-SNAPSHOT(published from the platform repo'skotlin-sdkpackage; local build for now — companion platform branchfeat/kotlin-sdk-maven-publishadds the publishing). APK packages the native JNI lib for both ABIs; duplicate-class check passes alongside dashj.Also fixed in passing
Test plan
assemble_testNet3Debug+ full unit-test suites of wallet, common, and all integration modules🤖 Generated with Claude Code