Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
31 commits
Select commit Hold shift + click to select a range
78a4b0b
feat(kotlin-sdk): split build/broadcast with reservation release for …
bfoss765 Jul 10, 2026
558455c
fix(kotlin-sdk): bound deferred-payment token lifetime; harden regist…
bfoss765 Jul 11, 2026
308479d
fix(kotlin-sdk): resolve rebase semantic conflicts onto feat/kotlin-s…
bfoss765 Jul 16, 2026
ddc8872
fix(kotlin-sdk): assert native code 26 for the stale-reservation-toke…
bfoss765 Jul 17, 2026
7f7b020
fix(kotlin-sdk): bound the deferred-payment token on the reservation'…
bfoss765 Jul 17, 2026
ade3999
fix(kotlin-sdk): sweep deferred-payment tokens only when the final wa…
bfoss765 Jul 17, 2026
36a6afe
fix(kotlin-sdk): route deferred builds through the atomic finalize-an…
bfoss765 Jul 17, 2026
0f5ac19
fix(kotlin-sdk): delete the dead split register→broadcast chain
bfoss765 Jul 21, 2026
9eb53b3
fix(kotlin-sdk): baseline the deferred token age on the pre-signing r…
bfoss765 Jul 21, 2026
1678804
fix(kotlin-sdk): give the V2 handle and token paths one wallet-genera…
bfoss765 Jul 21, 2026
1458d5b
fix(kotlin-sdk): validate the deferred token under the lock, consume …
bfoss765 Jul 21, 2026
fba8c48
fix(kotlin-sdk): release deferred reservations at final-alias destroy…
bfoss765 Jul 21, 2026
6f5edea
fix(kotlin-sdk): split the conflated deferred-token error code into t…
bfoss765 Jul 21, 2026
1d4b3fa
docs(kotlin-sdk): correct buildSignedPayment KDoc to the finalize-and…
bfoss765 Jul 21, 2026
3ef6e89
fix(kotlin-sdk): make the deferred payment token an owning AutoClosea…
bfoss765 Jul 21, 2026
c5677bf
style(kotlin-sdk): rustfmt wallet_manager.rs after the dead-chain del…
bfoss765 Jul 21, 2026
e219883
fix(kotlin-sdk): object-owning broadcast/release overloads for Signed…
bfoss765 Jul 21, 2026
47b4d53
fix(kotlin-sdk): retain a releasable account handle for CoinJoin-fund…
bfoss765 Jul 22, 2026
6d6bf61
fix(kotlin-sdk): bind deferred-payment reservation cleanup to its own…
bfoss765 Jul 22, 2026
74f9ae4
fix(swift-sdk): surface deferred-token codes 26/27/28 as typed errors
bfoss765 Jul 22, 2026
90f144d
docs(kotlin-sdk): correct broadcast KDoc to the split deferred-token …
bfoss765 Jul 22, 2026
a020d7d
chore(rust-dashcore): re-pin to owner-tagged reservation API rev
bfoss765 Jul 23, 2026
d5f16a6
fix: renumber deferred-reservation error codes to 27/28/29
bfoss765 Jul 23, 2026
3f719d4
fix(platform-wallet): owner-guard the broadcast-reject reservation re…
bfoss765 Jul 23, 2026
440897c
fix(platform-wallet): enforce unique reservation ownership, stop wrap…
bfoss765 Jul 24, 2026
7d85953
fix(platform-wallet): bind registration to correct wallet generation …
bfoss765 Jul 25, 2026
0b0d5c7
fix(platform-wallet): linearize wallet removal with deferred broadcas…
bfoss765 Aug 1, 2026
d854deb
fix(platform-wallet-ffi)!: move ErrorReservationWalletMismatch 29 -> …
bfoss765 Aug 1, 2026
6c37e86
refactor(platform-wallet-ffi): drop stale HandleStorage::any left by …
bfoss765 Aug 1, 2026
d1bed4d
fix(platform-wallet): scope the lifecycle gate to the wallet generati…
bfoss765 Aug 2, 2026
6be6748
fix(platform-wallet): remove the wallet generation by identity, not b…
bfoss765 Aug 2, 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
46 changes: 23 additions & 23 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 @@ -52,14 +52,14 @@ members = [
]

[workspace.dependencies]
dashcore = { git = "https://github.com/dashpay/rust-dashcore", rev = "70d4bf8e36057c58e02d56769a6e9760f701dd06" }
dash-network-seeds = { git = "https://github.com/dashpay/rust-dashcore", rev = "70d4bf8e36057c58e02d56769a6e9760f701dd06" }
dash-spv = { git = "https://github.com/dashpay/rust-dashcore", rev = "70d4bf8e36057c58e02d56769a6e9760f701dd06" }
key-wallet = { git = "https://github.com/dashpay/rust-dashcore", rev = "70d4bf8e36057c58e02d56769a6e9760f701dd06" }
key-wallet-ffi = { git = "https://github.com/dashpay/rust-dashcore", rev = "70d4bf8e36057c58e02d56769a6e9760f701dd06" }
key-wallet-manager = { git = "https://github.com/dashpay/rust-dashcore", rev = "70d4bf8e36057c58e02d56769a6e9760f701dd06" }
dash-network = { git = "https://github.com/dashpay/rust-dashcore", rev = "70d4bf8e36057c58e02d56769a6e9760f701dd06" }
dashcore-rpc = { git = "https://github.com/dashpay/rust-dashcore", rev = "70d4bf8e36057c58e02d56769a6e9760f701dd06" }
dashcore = { git = "https://github.com/bfoss765/rust-dashcore", rev = "e99959ced0062159d629930f488374e29f63c42b" }
dash-network-seeds = { git = "https://github.com/bfoss765/rust-dashcore", rev = "e99959ced0062159d629930f488374e29f63c42b" }
dash-spv = { git = "https://github.com/bfoss765/rust-dashcore", rev = "e99959ced0062159d629930f488374e29f63c42b" }
key-wallet = { git = "https://github.com/bfoss765/rust-dashcore", rev = "e99959ced0062159d629930f488374e29f63c42b" }
key-wallet-ffi = { git = "https://github.com/bfoss765/rust-dashcore", rev = "e99959ced0062159d629930f488374e29f63c42b" }
key-wallet-manager = { git = "https://github.com/bfoss765/rust-dashcore", rev = "e99959ced0062159d629930f488374e29f63c42b" }
dash-network = { git = "https://github.com/bfoss765/rust-dashcore", rev = "e99959ced0062159d629930f488374e29f63c42b" }
dashcore-rpc = { git = "https://github.com/bfoss765/rust-dashcore", rev = "e99959ced0062159d629930f488374e29f63c42b" }
Comment on lines +55 to +62

@coderabbitai coderabbitai Bot Aug 1, 2026

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🔒 Security & Privacy | 🟠 Major | 🏗️ Heavy lift

Do not merge workspace dependencies pinned to a personal fork.

All eight rust-dashcore crates now resolve from https://github.com/bfoss765/rust-dashcore at rev e99959ced0062159d629930f488374e29f63c42b. A personal fork is outside the organization's control: the owner can force-push, rename, or delete the repository, and the build then fails or silently resolves unaudited code. This also makes the whole wallet, SPV, and key-derivation stack unreviewed relative to dashpay/rust-dashcore.

This change is also unrelated to the deferred signed-payment work in this PR. If the fork carries a required upstream change, land that change in dashpay/rust-dashcore first, then pin this workspace to the resulting upstream revision before merge.

🔒 Proposed change once the upstream revision exists
-dashcore = { git = "https://github.com/bfoss765/rust-dashcore", rev = "e99959ced0062159d629930f488374e29f63c42b" }
-dash-network-seeds = { git = "https://github.com/bfoss765/rust-dashcore", rev = "e99959ced0062159d629930f488374e29f63c42b" }
-dash-spv = { git = "https://github.com/bfoss765/rust-dashcore", rev = "e99959ced0062159d629930f488374e29f63c42b" }
-key-wallet = { git = "https://github.com/bfoss765/rust-dashcore", rev = "e99959ced0062159d629930f488374e29f63c42b" }
-key-wallet-ffi = { git = "https://github.com/bfoss765/rust-dashcore", rev = "e99959ced0062159d629930f488374e29f63c42b" }
-key-wallet-manager = { git = "https://github.com/bfoss765/rust-dashcore", rev = "e99959ced0062159d629930f488374e29f63c42b" }
-dash-network = { git = "https://github.com/bfoss765/rust-dashcore", rev = "e99959ced0062159d629930f488374e29f63c42b" }
-dashcore-rpc = { git = "https://github.com/bfoss765/rust-dashcore", rev = "e99959ced0062159d629930f488374e29f63c42b" }
+dashcore = { git = "https://github.com/dashpay/rust-dashcore", rev = "<upstream-rev>" }
+dash-network-seeds = { git = "https://github.com/dashpay/rust-dashcore", rev = "<upstream-rev>" }
+dash-spv = { git = "https://github.com/dashpay/rust-dashcore", rev = "<upstream-rev>" }
+key-wallet = { git = "https://github.com/dashpay/rust-dashcore", rev = "<upstream-rev>" }
+key-wallet-ffi = { git = "https://github.com/dashpay/rust-dashcore", rev = "<upstream-rev>" }
+key-wallet-manager = { git = "https://github.com/dashpay/rust-dashcore", rev = "<upstream-rev>" }
+dash-network = { git = "https://github.com/dashpay/rust-dashcore", rev = "<upstream-rev>" }
+dashcore-rpc = { git = "https://github.com/dashpay/rust-dashcore", rev = "<upstream-rev>" }

Run the following script to check whether the pinned revision exists upstream:

#!/bin/bash
# Check whether the pinned rev is reachable in the upstream org repository.
REV=e99959ced0062159d629930f488374e29f63c42b
echo "--- upstream dashpay/rust-dashcore ---"
gh api "repos/dashpay/rust-dashcore/commits/$REV" --jq '.sha, .commit.message' 2>&1 | head -5
echo "--- fork bfoss765/rust-dashcore ---"
gh api "repos/bfoss765/rust-dashcore" --jq '.full_name, .fork, .private, .parent.full_name' 2>&1 | head -5
echo "--- other fork/personal git deps in the workspace ---"
fd -t f 'Cargo.toml' --exec rg -n 'git = "https://github.com/(?!dashpay/)' -P {} \; 2>/dev/null | head -40
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@Cargo.toml` around lines 55 - 62, Replace the personal-fork git sources for
all eight rust-dashcore dependencies—dashcore, dash-network-seeds, dash-spv,
key-wallet, key-wallet-ffi, key-wallet-manager, dash-network, and
dashcore-rpc—with the corresponding dashpay/rust-dashcore upstream source,
pinned to the required upstream revision after confirming it exists there. Do
not merge the bfoss765 fork revision; if its changes are required, use an
upstream revision containing them.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Agreed — this is a genuine merge blocker, and it can't be fixed inside this PR.

The eight crates are pinned to bfoss765/rust-dashcore @ e99959ce because this
PR depends on an unreleased key-wallet change: owner-tagged reservations, which
close the broadcast/release TOCTOU that the deferred build → broadcast lifecycle
in this PR is built on. Without it the reservation set can't tell which owner a
release belongs to, and the split-build feature has no safe primitive to sit on.

That change is upstream as dashpay/rust-dashcore#916
("feat(key-wallet): owner-tagged reservations to close the broadcast-release
TOCTOU (platform#4185)"). It is open and currently conflicting; we're working it now. This PR is blocked on #916 merging and should not go in before it.

On the "does the rev exist upstream" check in your script — it needs one
correction, because the naive form gives a false pass. gh api repos/dashpay/rust-dashcore/commits/e99959ce does return the commit, but only
because forks share object storage with the parent; resolvability is not
reachability. The real check is against a branch:

$ gh api repos/dashpay/rust-dashcore/compare/dev...e99959ce --jq .status
diverged        # ahead_by=1, behind_by=6
$ gh api repos/dashpay/rust-dashcore/commits/e99959ce/branches-where-head
                # empty

So the pin is fork-only, exactly as you say: one commit ahead of a point on
dev and six behind, on no upstream branch.

Plan of record: once #916 lands, this workspace repins all eight crates to
https://github.com/dashpay/rust-dashcore at the resulting upstream revision —
no bfoss765 remotes in the merged tree. I'll leave this thread open as the
merge gate.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

@bfoss765, acknowledged. The explanation confirms that the personal-fork pin is a required temporary dependency for owner-tagged reservations in #916.

You are correct about the validation detail. A successful commits/<sha> response only proves object availability. It does not prove that an upstream branch reaches the revision. The branch reachability checks are the correct validation.

I will keep this thread open. Before this PR merges, it must repin all eight workspace dependencies to https://github.com/dashpay/rust-dashcore at an upstream revision that contains #916.

You are interacting with an AI system.

Comment on lines +55 to +62

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.

🔴 Blocking: Do not merge workspace-wide dependencies from a personal fork

All eight rust-dashcore workspace dependencies are redirected from dashpay/rust-dashcore to bfoss765/rust-dashcore at e99959ce. That revision is only the first commit of dashpay/rust-dashcore#916, which is currently OPEN with mergeStateStatus=BLOCKED and reviewDecision=REVIEW_REQUIRED; the upstream PR has since added further fixes, including owner-guarded cleanup for asset-lock downstream failures. Merging this head would therefore make the entire workspace depend on a contributor-controlled repository and a stale, unmerged implementation. Land the reservation API and its follow-up fixes in the Dash-owned repository, then repin these entries and Cargo.lock to the corresponding Dash-owned revision.

source: ['codex']


tokio-metrics = "0.5"

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -176,6 +176,49 @@ sealed class DashSdkError(
cause,
)

/**
* `ErrorStaleReservationToken` (native code 27). A deferred
* (BIP70/BIP270) [broadcastSigned][org.dashfoundation.dashsdk.wallet.ManagedPlatformWallet.broadcastSigned]
* token has outlived its funding reservation's lifetime: key-wallet's
* TTL may already have swept and re-selected the inputs, so acting on it
* could touch a newer, unrelated reservation. The call did NOT touch the
* network. NOT retryable in place — rebuild the payment with
* [buildSignedPayment][org.dashfoundation.dashsdk.wallet.ManagedPlatformWallet.buildSignedPayment].
*
* Sibling of the other two deferred-token failures this code used to
* conflate: [ReservationTokenConsumed] (unknown / already broadcast /
* already released) and [ReservationWalletMismatch] (minted against a
* different wallet generation).
*/
class StaleReservationToken(message: String, cause: Throwable? = null) :
PlatformWallet(message, cause)

/**
* `ErrorReservationTokenConsumed` (native code 28). A deferred
* (BIP70/BIP270) [broadcastSigned][org.dashfoundation.dashsdk.wallet.ManagedPlatformWallet.broadcastSigned]
* token is unknown, already broadcast, or already released — the guard
* that turns a double-broadcast (or a broadcast after release) into a
* typed error instead of a second send. The call did NOT touch the
* network. NOT retryable: rebuild the payment with
* [buildSignedPayment][org.dashfoundation.dashsdk.wallet.ManagedPlatformWallet.buildSignedPayment].
* (Release is idempotent and never raises this.)
*/
class ReservationTokenConsumed(message: String, cause: Throwable? = null) :
PlatformWallet(message, cause)

/**
* `ErrorReservationWalletMismatch` (native code 30). A deferred
* (BIP70/BIP270) [broadcastSigned][org.dashfoundation.dashsdk.wallet.ManagedPlatformWallet.broadcastSigned]
* token was minted against a different wallet *generation* than the one
* broadcasting it (e.g. a wallet re-created under the same id); its
* reservation lives in that other generation's reservation set. The call
* did NOT touch the network and did NOT consume the rightful owner's
* token. NOT retryable through this handle: rebuild the payment with
* [buildSignedPayment][org.dashfoundation.dashsdk.wallet.ManagedPlatformWallet.buildSignedPayment].
*/
class ReservationWalletMismatch(message: String, cause: Throwable? = null) :
PlatformWallet(message, cause)

/**
* Any other `PlatformWalletFFIResultCode` without a dedicated type.
* Carries the platform-wallet [nativeCode] (already de-offset) and
Expand Down Expand Up @@ -235,7 +278,20 @@ sealed class DashSdkError(
6 -> PlatformWallet.WalletOperation(message, cause) // ErrorWalletOperation
7, // ErrorIdentityNotFound
8, // ErrorContactNotFound
98, // NotFound (Option returned as an error)
// NotFound. Handle/Option lookup failures, plus the
// wallet-was-REMOVED case on BOTH deferred-send paths:
// * deferred (BIP70/BIP270) TOKEN path — a signed-payment broadcast
// whose wallet is no longer registered in the manager, or a
// signed-payment finalize whose wallet was removed while it was
// being signed;
// * finalized-transaction HANDLE (V2) path — a tx-builder finalize
// whose wallet was removed or re-created during signing (no handle
// is published), or a V2 broadcast whose generation is gone.
// Every one reconciles the build's UTXO reservation before returning.
// Nothing was broadcast, and unlike ReservationWalletMismatch (30)
// no other live generation holds the payment either — so it is not
// retryable. See dashpay/platform#4185.
98,
-> NotFound(message, cause)
16 -> PlatformWallet.ShieldedBroadcastFailed(message, cause) // ErrorShieldedBroadcastFailed
18 -> PlatformWallet.ShieldedSpendUnconfirmed(message, cause) // ErrorShieldedSpendUnconfirmed
Expand All @@ -245,6 +301,11 @@ sealed class DashSdkError(
23 -> PlatformWallet.AssetLockNotTracked(message, cause) // ErrorAssetLockNotTracked
24 -> PlatformWallet.AssetLockAlreadyConsumed(message, cause) // ErrorAssetLockAlreadyConsumed
25 -> PlatformWallet.AssetLockFundingMismatch(message, cause) // ErrorAssetLockFundingMismatch
27 -> PlatformWallet.StaleReservationToken(message, cause) // ErrorStaleReservationToken
28 -> PlatformWallet.ReservationTokenConsumed(message, cause) // ErrorReservationTokenConsumed
// 29 is ErrorAssetLockInsufficientFunds (dashpay/platform#4184); this
// code is 30. See packages/rs-platform-wallet-ffi/ERROR_CODE_REGISTRY.md.
30 -> PlatformWallet.ReservationWalletMismatch(message, cause) // ErrorReservationWalletMismatch
else -> PlatformWallet.Generic(code, message, cause)
Comment on lines 296 to 309

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.

🟡 Suggestion: Preserve definitive broadcast rejection as a typed Kotlin error

A definitive deferred-payment broadcast rejection is preserved as native code 26 through the C ABI and JNI, but this switch falls through to PlatformWallet.Generic. Callers can inspect Generic.nativeCode manually, but the public typed hierarchy otherwise distinguishes ambiguous broadcast outcomes and all deferred-token failures. The new broadcastSigned API should likewise expose definitive rejection as a dedicated subtype so callers can distinguish a consumed-and-released rejected payment, which must be rebuilt or corrected, from unrelated generic wallet failures. Add the subtype, map code 26, and pin its non-retry-in-place/rebuild semantics in DashSdkErrorTest.

source: ['codex']

}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -246,6 +246,45 @@ internal object WalletManagerNative {
*/
external fun coreTransactionFree(tx: Long)

/**
* `core_wallet_signed_payment_finalize` — atomically fund, reserve, sign,
* AND register a builder for deferred (BIP70/BIP270) submission in one
* native call. Selection and reservation commit as a single unit under the
* wallet-manager lock, closing the double-selection window. CONSUMES
* [builder]. [accountType]/[accountIndex] identify the funding account
* (0 BIP44, 1 BIP32, 2 CoinJoin); [coreSignerHandle] is a
* `MnemonicResolverHandle`.
*
* Returns a big-endian BLOB decoded into a `SignedCoreTransaction`:
* `u64 token, u64 feeDuffs, u32 txidLen, txid utf8, u32 txBytesLen, txBytes`.
*/
external fun coreWalletFinalizeSignedPayment(
builder: Long,
walletHandle: Long,
accountType: Int,
accountIndex: Int,
coreSignerHandle: Long,
): ByteArray

/**
* `core_wallet_signed_payment_broadcast` — broadcast the payment behind
* [token], reconciling its reservation on failure and consuming the token.
* Rather than double-broadcasting, an unusable token throws one of three
* sibling codes — `ErrorStaleReservationToken` (27, aged out),
* `ErrorReservationTokenConsumed` (28, already consumed/unknown), or
* `ErrorReservationWalletMismatch` (30, different wallet generation).
* [coreHandle] must resolve to the wallet the token was minted against.
* Returns the txid as a lowercase hex string.
*/
external fun coreWalletBroadcastSignedPayment(coreHandle: Long, token: Long): String

/**
* `core_wallet_signed_payment_release` — release the funding reservation
* behind [token] and drop it. Idempotent: releasing an unknown /
* already-consumed token is a silent no-op.
*/
external fun coreWalletReleaseSignedPayment(token: Long)

/**
* Enumerate the wallet's Platform-payment addresses with cached credit
* balances, as a big-endian blob: `u32 rowCount` then per row
Expand Down
Loading
Loading