Skip to content

feat(kotlin-sdk): bind OP_RETURN, output-order and VIN0-change builder controls - #4288

Open
HashEngineering wants to merge 1 commit into
dashpay:feat/maya-op-returnfrom
HashEngineering:feat/maya-op-return-kotlin
Open

feat(kotlin-sdk): bind OP_RETURN, output-order and VIN0-change builder controls#4288
HashEngineering wants to merge 1 commit into
dashpay:feat/maya-op-returnfrom
HashEngineering:feat/maya-op-return-kotlin

Conversation

@HashEngineering

Copy link
Copy Markdown
Contributor

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-broadcast
core_wallet_signed_transaction_v2_bytes reader) through the FFI and binds
them 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 is
unreachable there.

What was done?

Stacked on #4286 (base = feat/maya-op-return); the diff on top is only the
Kotlin-side plumbing:

  • packages/rs-unified-sdk-jni/src/wallet_manager.rs: four thin JNI
    trampolines following the module's one-export-one-FFI-call convention —
    coreTxBuilderAddOpReturn, coreTxBuilderPreserveOutputOrder,
    coreTxBuilderChangeToFirstInput, and coreSignedTransactionV2Bytes
    (copies the FFI-owned buffer into a byte[] and frees it with
    platform_wallet_bytes_free on every path).
  • packages/kotlin-sdk/.../ffi/WalletManagerNative.kt: the matching
    external fun declarations.
  • packages/kotlin-sdk/.../wallet/CoreTransactionBuilder.kt:
    addOpReturn(ByteArray), preserveOutputOrder(), changeToFirstInput()
    mirroring the Swift CoreTransactionBuilder methods, and
    FinalizedCoreTransaction.serializedData() mirroring Swift's
    serializedData() — a non-consuming read so the caller can assert the
    deposit 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 DashSDKException while the builder
    survives (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 until
dashpay/rust-dashcore#922 merges and the workspace rev is bumped — the CI
guard added there enforces that the override never lands.

How Has This Been Tested?

  • cargo check -p rs-unified-sdk-jni and cargo fmt --check /
    cargo clippy -p rs-unified-sdk-jni against 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:connectedDebugAndroidTest on a Pixel 8 API-34 arm64 emulator with a
    locally 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:

  • I have performed a self-review of my own code
  • I have commented my code, particularly in hard-to-understand areas
  • I have added or updated relevant unit/integration/functional/e2e tests
  • I have made corresponding changes to the documentation

For repository code-owners and collaborators only

  • I have assigned this pull request to a milestone

…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>
@coderabbitai

coderabbitai Bot commented Aug 4, 2026

Copy link
Copy Markdown
Contributor

Important

Review skipped

Auto reviews are disabled on base/target branches other than the default branch.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: b5db805b-6f57-4ab1-9850-95117a442574

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review

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.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@thepastaclaw

thepastaclaw commented Aug 4, 2026

Copy link
Copy Markdown
Collaborator

⛔ Blockers found — Sonnet deferred (commit 27b8502)
Canonical validated blockers: 1

@thepastaclaw thepastaclaw left a comment

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.

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.

Comment on lines +71 to +95
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)

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: 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']

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants