feat(kotlin-sdk): bind OP_RETURN, output-order and VIN0-change builder controls - #4288
Conversation
…r controls Kotlin/Android parity for the Swift-only surface dashpay#4286 added: four thin JNI trampolines over core_wallet_tx_builder_add_op_return / _preserve_output_order / _change_to_first_input and core_wallet_signed_transaction_v2_bytes, the matching WalletManagerNative declarations, CoreTransactionBuilder.addOpReturn/preserveOutputOrder/ changeToFirstInput, and FinalizedCoreTransaction.serializedData() — a non-consuming read so callers can assert the MAYACHAIN deposit shape (vault VOUT0, memo VOUT1, change VOUT2) before broadcasting. Instrumented binding test needs no funded wallet: symbols resolve, the Maya option sequence succeeds, an 81-byte memo throws while the builder survives, and the bytes reader rejects a null handle. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
|
Important Review skippedAuto reviews are disabled on base/target branches other than the default branch. Please check the settings in the CodeRabbit UI or the ⚙️ Run configurationConfiguration used: Path: .coderabbit.yaml Review profile: CHILL Plan: Pro Plus Run ID: You can disable this status message by setting the Use the checkbox below for a quick retry:
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 |
|
⛔ Blockers found — Sonnet deferred (commit 27b8502) |
thepastaclaw
left a comment
There was a problem hiding this comment.
Preliminary review — Codex only
The JNI bindings and finalized-transaction byte ownership are implemented consistently with the existing FFI patterns. However, the new MAYACHAIN controls are inaccessible to consumers of the published Kotlin SDK, so the feature cannot be used through the supported public API.
Source: reviewers codex/general=gpt-5.6-sol(completed); codex/ffi-engineer=gpt-5.6-sol(completed); verifier=codex/verifier=gpt-5.6-sol(completed); coordinator=openclaw-agent/cliproxy/gpt-5.6-sol(orchestration-only).
Validated blockers were found in the Codex precheck. Sonnet is deferred until a fresh Codex revalidation clears the blocker gate.
Review provenance
- Codex reviewers:
gpt-5.6-sol— general (completed),gpt-5.6-sol— ffi-engineer (completed) - Verifier:
gpt-5.6-sol— verifier - Sonnet: not run (deferred by blocker gate)
🔴 1 blocking
🤖 Prompt for all review comments with AI agents
These findings are from an automated code review. Verify each finding against the current code and only fix it if needed.
In `packages/kotlin-sdk/sdk/src/main/kotlin/org/dashfoundation/dashsdk/wallet/CoreTransactionBuilder.kt`:
- [BLOCKING] packages/kotlin-sdk/sdk/src/main/kotlin/org/dashfoundation/dashsdk/wallet/CoreTransactionBuilder.kt:71-95: Expose the MAYACHAIN controls through a public atomic API
All three new builder controls are `internal`, as are the builder constructor, `addOutput`, and `finalizeAtomic`; the underlying `WalletManagerNative` object is internal as well. The only public driver, `ManagedPlatformWallet.sendToAddresses`, accepts ordinary positive-value address outputs, applies none of these controls, immediately broadcasts, and never returns a `FinalizedCoreTransaction`. Consequently, an application consuming the published `dash-sdk-android` artifact cannot add the OP_RETURN memo, preserve the required output order, route change to VIN0, or obtain a finalized transaction to inspect with `serializedData()`. The instrumented test only exercises the internal native surface from within the SDK module, so it does not verify consumer accessibility. Add a public atomic prepare/send API that applies these options and returns a `FinalizedCoreTransaction` for inspection while keeping the deprecated split `setFunding`/`buildSigned` path inaccessible.
| internal fun addOpReturn(data: ByteArray): CoreTransactionBuilder = apply { | ||
| WalletManagerNative.coreTxBuilderAddOpReturn(handle, data) | ||
| } | ||
|
|
||
| /** Override the change address (network-checked Rust-side). */ | ||
| internal fun setChangeAddress(address: String): CoreTransactionBuilder = apply { | ||
| WalletManagerNative.coreTxBuilderSetChangeAddress(handle, address) | ||
| } | ||
|
|
||
| /** | ||
| * Preserve outputs in insertion order (skip BIP-69 sorting) for a | ||
| * MAYACHAIN-style deposit — vault must stay VOUT0, memo VOUT1 (mirror of | ||
| * Swift's `preserveOutputOrder`). | ||
| */ | ||
| internal fun preserveOutputOrder(): CoreTransactionBuilder = apply { | ||
| WalletManagerNative.coreTxBuilderPreserveOutputOrder(handle) | ||
| } | ||
|
|
||
| /** | ||
| * Route change to the first selected input's address (VIN0) for a | ||
| * MAYACHAIN-style deposit — MAYAChain identifies the depositor by VIN0 | ||
| * and pays refunds there (mirror of Swift's `changeToFirstInput`). | ||
| */ | ||
| internal fun changeToFirstInput(): CoreTransactionBuilder = apply { | ||
| WalletManagerNative.coreTxBuilderChangeToFirstInput(handle) |
There was a problem hiding this comment.
🔴 Blocking: Expose the MAYACHAIN controls through a public atomic API
All three new builder controls are internal, as are the builder constructor, addOutput, and finalizeAtomic; the underlying WalletManagerNative object is internal as well. The only public driver, ManagedPlatformWallet.sendToAddresses, accepts ordinary positive-value address outputs, applies none of these controls, immediately broadcasts, and never returns a FinalizedCoreTransaction. Consequently, an application consuming the published dash-sdk-android artifact cannot add the OP_RETURN memo, preserve the required output order, route change to VIN0, or obtain a finalized transaction to inspect with serializedData(). The instrumented test only exercises the internal native surface from within the SDK module, so it does not verify consumer accessibility. Add a public atomic prepare/send API that applies these options and returns a FinalizedCoreTransaction for inspection while keeping the deprecated split setFunding/buildSigned path inaccessible.
source: ['codex']
Issue being fixed or feature implemented
Kotlin/Android parity for #4286. That PR exposes the MAYACHAIN-deposit
builder controls (
add_op_return,preserve_output_order,change_to_first_input, and the pre-broadcastcore_wallet_signed_transaction_v2_bytesreader) through the FFI and bindsthem for Swift only. The Android wallet is restoring the same MAYACHAIN swap
routes (dashpay/dash-wallet#1520, the Maya/SwapKit items), and its send path
runs on
dash-sdk-android— without JNI/Kotlin bindings the FFI surface isunreachable there.
What was done?
Stacked on #4286 (base =
feat/maya-op-return); the diff on top is only theKotlin-side plumbing:
packages/rs-unified-sdk-jni/src/wallet_manager.rs: four thin JNItrampolines following the module's one-export-one-FFI-call convention —
coreTxBuilderAddOpReturn,coreTxBuilderPreserveOutputOrder,coreTxBuilderChangeToFirstInput, andcoreSignedTransactionV2Bytes(copies the FFI-owned buffer into a
byte[]and frees it withplatform_wallet_bytes_freeon every path).packages/kotlin-sdk/.../ffi/WalletManagerNative.kt: the matchingexternal fundeclarations.packages/kotlin-sdk/.../wallet/CoreTransactionBuilder.kt:addOpReturn(ByteArray),preserveOutputOrder(),changeToFirstInput()mirroring the Swift
CoreTransactionBuildermethods, andFinalizedCoreTransaction.serializedData()mirroring Swift'sserializedData()— a non-consuming read so the caller can assert thedeposit shape (vault VOUT0 / memo VOUT1 / change VOUT2) before deciding to
broadcast or abandon.
packages/kotlin-sdk/.../androidTest/.../CoreTxBuilderOpReturnBindingTest.kt:instrumented binding coverage that needs no funded wallet — the four
symbols resolve, the canonical Maya option sequence succeeds on a live
builder, an 81-byte memo throws
DashSDKExceptionwhile the buildersurvives (the FFI rejects before consuming builder state), and the bytes
reader rejects a null handle instead of crashing. The full funded
deposit-shape assertion stays with feat(sdk): expose OP_RETURN, output-order and change-to-VIN0 controls #4286's gated Swift integration suite
and the wallet-side testnet verification.
Like #4286, building this locally needs the uncommitted
[patch."https://github.com/dashpay/rust-dashcore"]override untildashpay/rust-dashcore#922 merges and the workspace
revis bumped — the CIguard added there enforces that the override never lands.
How Has This Been Tested?
cargo check -p rs-unified-sdk-jniandcargo fmt --check/cargo clippy -p rs-unified-sdk-jniagainst chore(rs-dpp): fix build issues in rs-dpp #922's key-wallet commits(cherry-picked onto the pinned rev locally).
:sdk:compileDebugKotlin,:sdk:testDebugUnitTest,:sdk:compileDebugAndroidTestKotlin— green.:sdk:connectedDebugAndroidTeston a Pixel 8 API-34 arm64 emulator with alocally built
libdash_sdk_jni.so(dev profile, patched engine):4/4 tests pass — symbols resolve, options apply, the oversize-memo
rejection and null-handle rejection both surface as
DashSDKException.Breaking Changes
None — additive bindings only.
Checklist:
For repository code-owners and collaborators only