Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
47 commits
Select commit Hold shift + click to select a range
3dc3a99
feat(kotlin-sdk): split build/broadcast with reservation release for …
bfoss765 Jul 10, 2026
3004f7e
fix(kotlin-sdk): bound deferred-payment token lifetime; harden regist…
bfoss765 Jul 11, 2026
53854b5
fix(kotlin-sdk): resolve rebase semantic conflicts onto feat/kotlin-s…
bfoss765 Jul 16, 2026
12ae58c
fix(kotlin-sdk): assert native code 26 for the stale-reservation-toke…
bfoss765 Jul 17, 2026
f8e1102
fix(kotlin-sdk): bound the deferred-payment token on the reservation'…
bfoss765 Jul 17, 2026
82516e6
fix(kotlin-sdk): sweep deferred-payment tokens only when the final wa…
bfoss765 Jul 17, 2026
b5a38bc
fix(kotlin-sdk): route deferred builds through the atomic finalize-an…
bfoss765 Jul 17, 2026
221a501
fix(kotlin-sdk): delete the dead split register→broadcast chain
bfoss765 Jul 21, 2026
67ad00a
fix(kotlin-sdk): baseline the deferred token age on the pre-signing r…
bfoss765 Jul 21, 2026
667c26e
fix(kotlin-sdk): give the V2 handle and token paths one wallet-genera…
bfoss765 Jul 21, 2026
f5dff18
fix(kotlin-sdk): validate the deferred token under the lock, consume …
bfoss765 Jul 21, 2026
5e875d0
fix(kotlin-sdk): release deferred reservations at final-alias destroy…
bfoss765 Jul 21, 2026
b5478b1
fix(kotlin-sdk): split the conflated deferred-token error code into t…
bfoss765 Jul 21, 2026
dbaa10b
docs(kotlin-sdk): correct buildSignedPayment KDoc to the finalize-and…
bfoss765 Jul 21, 2026
4815dce
fix(kotlin-sdk): make the deferred payment token an owning AutoClosea…
bfoss765 Jul 21, 2026
c27ad80
style(kotlin-sdk): rustfmt wallet_manager.rs after the dead-chain del…
bfoss765 Jul 21, 2026
50c93f3
fix(kotlin-sdk): object-owning broadcast/release overloads for Signed…
bfoss765 Jul 21, 2026
b9f3b98
fix(kotlin-sdk): retain a releasable account handle for CoinJoin-fund…
bfoss765 Jul 22, 2026
5b425f6
fix(kotlin-sdk): bind deferred-payment reservation cleanup to its own…
bfoss765 Jul 22, 2026
3151f16
fix(swift-sdk): surface deferred-token codes 26/27/28 as typed errors
bfoss765 Jul 22, 2026
c8ccb8b
chore(rust-dashcore): pin to canonical dashpay dev rev with owner-tag…
bfoss765 Jul 22, 2026
0058dee
fix: renumber deferred-reservation error codes to 27/28/29
bfoss765 Jul 23, 2026
8fa0670
fix(platform-wallet): owner-guard the broadcast-reject reservation re…
bfoss765 Jul 23, 2026
2844483
fix(platform-wallet): enforce unique reservation ownership, stop wrap…
bfoss765 Jul 24, 2026
4f5261a
fix(platform-wallet): bind registration to correct wallet generation …
bfoss765 Jul 25, 2026
93646f9
fix(platform-wallet): linearize wallet removal with deferred broadcas…
bfoss765 Aug 1, 2026
f39e6b9
fix(platform-wallet-ffi)!: move ErrorReservationWalletMismatch 29 -> …
bfoss765 Aug 1, 2026
24b614c
refactor(platform-wallet-ffi): drop stale HandleStorage::any left by …
bfoss765 Aug 1, 2026
cfd7ab7
fix(platform-wallet): scope the lifecycle gate to the wallet generati…
bfoss765 Aug 2, 2026
8a343b4
fix(platform-wallet): remove the wallet generation by identity, not b…
bfoss765 Aug 2, 2026
43699c6
fix(platform-wallet): move the deferred-token trio off the codes #426…
bfoss765 Aug 3, 2026
f71995a
fix(kotlin-sdk): stop the stray 98 arm from shadowing PlatformWallet.…
bfoss765 Aug 4, 2026
a6389a1
feat(platform-wallet): union-funding build_signed_payment core primitive
bfoss765 Jul 13, 2026
ede5f29
feat(sdk): plumb build_signed_payment through FFI, JNI, and Kotlin
bfoss765 Jul 13, 2026
96591be
fix(platform-wallet): re-scope build_signed_payment to single-account…
bfoss765 Jul 30, 2026
5c2377c
feat(platform-wallet): expose account-level derivationPath in account…
bfoss765 Jul 31, 2026
54bd89b
fix(platform-wallet): dust + size bounds, typed build errors, and tes…
bfoss765 Jul 31, 2026
d96d54a
fix(platform-wallet): fail closed when no wallet-level account matche…
bfoss765 Jul 31, 2026
ed0bfc9
feat(kotlin-sdk): expose release_payment_reservation for abandoned bu…
bfoss765 Aug 1, 2026
782f128
docs(ffi): drop the redundant "later ... afterwards" in the send modu…
bfoss765 Aug 3, 2026
3da586e
fix(platform-wallet): adopt #4185's WalletGeneration in the send-path…
bfoss765 Aug 3, 2026
50fd380
fix(swift-sdk): mirror ErrorTransactionBuild (32) into the Swift resu…
bfoss765 Aug 3, 2026
e9c2d82
style(platform-wallet): rustfmt the restacked send-path test fixtures
bfoss765 Aug 3, 2026
0131940
fix(platform-wallet): owner-guard the raw-payment reservation release
bfoss765 Aug 4, 2026
c8403de
fix(kotlin-sdk): release a cancelled buildSignedPayment's reservation
bfoss765 Aug 4, 2026
163f20f
fix(kotlin-sdk): release reservation on JNI pack failure; harden defe…
bfoss765 Aug 4, 2026
42529bd
fix(kotlin-sdk): release the minted token on the finalize null-txid path
bfoss765 Aug 4, 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
26 changes: 13 additions & 13 deletions Cargo.lock

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

Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,24 @@ sealed class DashSdkError(
class CoreInsufficientFunds(message: String, cause: Throwable? = null) :
PlatformWallet(message, cause)

/**
* `ErrorTransactionBuild` (native code 32). A Core transaction could
* not be assembled from the request. The REQUEST is at fault, so a
* verbatim retry fails identically — the caller must change it.
*
* It is what every non-shortfall `buildSignedPayment` rejection
* surfaces as: a `fundingPath` matching no spendable funds account or
* naming a watch-only one (the two failure modes the single-account
* send design rests on, dashpay/platform#4184), a request breaching a
* monetary bound (MAX_MONEY total, max fee rate, a below-dust
* recipient, an over-100 kB recipient list), or a malformed recipients
* blob. Before this code existed they all arrived as [Generic] with
* native code 99 and could only be told apart by string-matching the
* message. The specific cause is still in [message].
*/
class TransactionBuild(message: String, cause: Throwable? = null) :
PlatformWallet(message, cause)

class AssetLockNotTracked(message: String, cause: Throwable? = null) :
PlatformWallet(message, cause)

Expand Down Expand Up @@ -239,6 +257,67 @@ sealed class DashSdkError(
class NotFound(message: String, cause: Throwable? = null) :
PlatformWallet(message, cause)

/**
* `ErrorTransactionBroadcastRejected` (native code 26). Core
* DEFINITIVELY rejected the core transaction: it is not on the network
* and will not get there. The build's UTXO reservation was released and,
* on the deferred (BIP70/BIP270) path, the token was consumed at the
* same time — so the inputs are spendable again and the token is gone.
*
* The definitive counterpart to [TransactionBroadcastUnconfirmed] (20),
* whose outcome is AMBIGUOUS and which therefore keeps its inputs
* reserved. Because the reservation and token are already gone, this is
* NOT retryable in place: address the rejection reason carried in the
* message, then rebuild with
* [buildSignedPayment][org.dashfoundation.dashsdk.wallet.ManagedPlatformWallet.buildSignedPayment]
* (deferred) or re-issue the send.
*/
class TransactionBroadcastRejected(message: String, cause: Throwable? = null) :
PlatformWallet(message, cause)

/**
* `ErrorStaleReservationToken` (native code 34). 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 35). 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 36). 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 @@ -335,6 +414,20 @@ sealed class DashSdkError(
// RAW code via translateManagedIdentityNotFoundToZero (#4051)
// before this mapping ever runs. BREAKING for Kotlin hosts that
// caught DashSdkError.NotFound from platform-wallet operations.
//
// 98 is also what the wallet-was-REMOVED case returns 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 (36)
// no other live generation holds the payment either — so it is not
// retryable. See dashpay/platform#4185.
PLATFORM_WALLET_NOT_FOUND_CODE ->
PlatformWallet.NotFound(message, cause)
16 -> PlatformWallet.ShieldedBroadcastFailed(message, cause) // ErrorShieldedBroadcastFailed
Expand All @@ -345,6 +438,17 @@ sealed class DashSdkError(
23 -> PlatformWallet.AssetLockNotTracked(message, cause) // ErrorAssetLockNotTracked
24 -> PlatformWallet.AssetLockAlreadyConsumed(message, cause) // ErrorAssetLockAlreadyConsumed
25 -> PlatformWallet.AssetLockFundingMismatch(message, cause) // ErrorAssetLockFundingMismatch
26 -> PlatformWallet.TransactionBroadcastRejected(message, cause) // ErrorTransactionBroadcastRejected
32 -> PlatformWallet.TransactionBuild(message, cause) // ErrorTransactionBuild
// The deferred-token trio sits at the contiguous block 34-36 because
// 27-33 are claimed elsewhere: 27 ErrorShutdownIncomplete
// (dashpay/platform#4268, merged), 29 ErrorAssetLockInsufficientFunds
// (#4184), 31 ErrorSigningKeyUnavailable (#4183/#4259), 32
// ErrorTransactionBuild (#4247/#4256), 33 ErrorTransactionSigning
// (#4256). See packages/rs-platform-wallet-ffi/ERROR_CODE_REGISTRY.md.
34 -> PlatformWallet.StaleReservationToken(message, cause) // ErrorStaleReservationToken
35 -> PlatformWallet.ReservationTokenConsumed(message, cause) // ErrorReservationTokenConsumed
36 -> PlatformWallet.ReservationWalletMismatch(message, cause) // ErrorReservationWalletMismatch
// ErrorSigningKeyUnavailable — the STRUCTURED signer
// discriminator (dashpay/platform#4060 finding 7): the typed
// completion code rides the whole Rust round-trip, no message
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -227,6 +227,76 @@ internal object WalletManagerNative {
*/
external fun platformWalletGetCore(walletHandle: Long): Long

/**
* `core_wallet_build_signed_payment` — build + sign a standard L1 payment
* funded from ONE of the wallet's signable funds accounts, WITHOUT
* broadcasting.
*
* [coreHandle] is a core-wallet handle from [platformWalletGetCore].
* [outputsBlob] encodes the recipients big-endian as `u32 count` then per
* row `u32 addrLen, addr utf8, u64 amount`. [feePerKb] is duffs/kB (0 =
* default). [coreSignerHandle] is the manager's `MnemonicResolverHandle`.
* [fundingPath] is an optional UTF-8 BIP32 derivation-path string
* (dashpay/platform#4184) naming the single funds account whose UTXOs fund
* the payment: null (the default) funds from the unmixed BIP44 account; an
* explicit account-level path (e.g. the DIP-9 CoinJoin account path) funds
* strictly from that one account, with no union across accounts and no
* consent gate.
*
* Returns a `byte[]` packed big-endian as
* `u64 fee, u64 change, u64 reservationHandle,` then the
* consensus-serialized signed transaction bytes (0-length / null after
* throwing). Does NOT broadcast and does NOT persist a debit.
*
* `reservationHandle` is the opaque stand-in for the key-wallet reservation
* token this build stamped on its inputs (0 = it reserved nothing); it must
* be passed back to [coreWalletReleasePaymentReservation] to abandon the
* build. The token itself cannot cross the ABI — it has no public
* constructor, by design, so that it cannot be forged.
*/
external fun coreWalletBuildSignedPayment(
coreHandle: Long,
outputsBlob: ByteArray,
feePerKb: Long,
coreSignerHandle: Long,
fundingPath: String?,
): ByteArray

/**
* `core_wallet_release_payment_reservation` — release the UTXO reservation
* a [coreWalletBuildSignedPayment] call took, for a build that will NOT be
* broadcast.
*
* `build_signed_payment` leaves its selected inputs reserved on success,
* expecting a broadcast to follow. A caller that abandons the build must
* call this or the coins stay unselectable until key-wallet's 24-block TTL
* backstop reclaims them — and that backstop never fires before the first
* sync completes (`ReservationSet::sweep` early-returns at height 0), so an
* abandoned build on a freshly restored wallet can otherwise strand the
* whole balance for the life of the process (dashpay/platform#4247 review).
* This call consults no height.
*
* [coreHandle] is a core-wallet handle from [platformWalletGetCore].
* [txBytes] is the consensus-serialized signed transaction exactly as
* [coreWalletBuildSignedPayment] returned it. [fundingPath] must be the SAME
* optional path the build was given (null = the unmixed BIP44 account).
* [reservationHandle] must be the SAME handle that build returned: it is the
* ownership proof that stops this release from freeing a concurrent build's
* inputs after key-wallet's TTL swept this build's reservation and that other
* build re-reserved the same outpoint (dashpay/platform#4247 review). An
* unrecognised non-zero handle throws rather than releasing unguarded.
*
* FOR ABANDONED BUILDS ONLY — never after a successful broadcast. Repeated
* releases of the same abandoned build are idempotent; that is the only sense
* in which this is safe to call twice.
*/
external fun coreWalletReleasePaymentReservation(
coreHandle: Long,
txBytes: ByteArray,
fundingPath: String?,
reservationHandle: Long,
)

/**
* `core_wallet_broadcast_transaction` — broadcast a transaction built by
* [coreTxBuilderBuildSigned]. [accountType]/[accountIndex] identify the
Expand Down Expand Up @@ -262,6 +332,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` (34, aged out),
* `ErrorReservationTokenConsumed` (35, already consumed/unknown), or
* `ErrorReservationWalletMismatch` (36, 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