Skip to content

feat(kotlin-sdk): expose core_wallet_set_gap_limit to Kotlin (migrated-wallet address-window heal) - #4377

Open
bfoss765 wants to merge 5 commits into
v4.2-devfrom
feat/kotlin-sdk-gap-limit-jni
Open

feat(kotlin-sdk): expose core_wallet_set_gap_limit to Kotlin (migrated-wallet address-window heal)#4377
bfoss765 wants to merge 5 commits into
v4.2-devfrom
feat/kotlin-sdk-gap-limit-jni

Conversation

@bfoss765

@bfoss765 bfoss765 commented Aug 11, 2026

Copy link
Copy Markdown
Collaborator

Issue being fixed or feature implemented

A wallet migrated from dashj can be silently blind to its own funds: if a same-seed client (the shipped dashj app, still run in parallel by migrated users) spends past the SDK's derived address window, the change output — and every descendant transaction — never enters the SDK's filter-scan query. The wallet reports synced with the wrong balance and missing history.

Field case (mainnet, long-lived ~150-contact wallet): a 70 DASH payment's 37.17 change went to an address one step past the watched window. The SDK saw the 70 leave and never saw the change return — confirmed balance collapsed from 70.6 to 0.6 at the spend height, and everything downstream of that change stayed invisible.

The Rust seam to fix this existed end-to-end (AddressPool::set_gap_limitManagedCoreFundsAccountCoreWallet::set_gap_limit → the core_wallet_set_gap_limit C export, in-tree since #3970) but stopped at the C boundary — no JNI trampoline, so no Kotlin host could reach it.

What was done?

~40 lines of plumbing, no engine changes:

  • rs-unified-sdk-jni: Java_…_WalletManagerNative_coreWalletSetGapLimit (account-type mapping via the existing core_account_type, same guard shape as the sibling exports).
  • kotlin-sdk: the external fun declaration and a ManagedCoreWallet.setGapLimit(accountType, accountIndex, gapLimit) wrapper under mapNativeErrors. Rust clamps to MAX_GAP_LIMIT = 1000; mark_used keeps rolling the window forward during the subsequent re-scan, so frontiers deeper than one window still recover.

The intended host usage is: widen the standard families once (BIP44/BIP32/CoinJoin), rewind the SPV filter watermark (rescanSpvFilters, already exposed), and let the scan re-match history against the widened script set. The Android wallet implements this as a one-shot per-version migration heal.

How Has This Been Tested?

  • cargo check -p rs-unified-sdk-jni clean on this branch.
  • Controlled testnet reproduction (Android host, arm64 device): restored a seed on the SDK wallet, then funded index 45 on BOTH the external and change chains (past the default gap of 30) from a second client. The wallet showed 0.01/"synced" while 0.045 sat confirmed on-chain — the field bug, reproduced. After an in-place upgrade to a build with the heal (widen to 1000 + watermark rewind through this API): full recovery — 0.045, all transactions, one-shot latch verified across restarts, and a no-regression pass on a healthy wallet (history preserved, identity intact).
  • Mainnet field verification: the affected wallet above took the heal build as an in-place upgrade and completed one full re-scan — fully synced, correct balance, the previously-invisible payment and its history now shown. The address-window blindness did not recur across restarts.

🤖 Generated with Claude Code

Summary by CodeRabbit

  • New Features
    • Added support for configuring an account’s address gap limit.
    • Gap limits can be set per account type and account index.
    • Invalid values are rejected, and an enforced maximum helps protect wallet discovery and scanning performance.
    • Clear errors are provided when unsupported account types, invalid indexes, or unusable limits are supplied.

The Rust seam existed end-to-end (AddressPool::set_gap_limit ->
ManagedCoreFundsAccount -> CoreWallet::set_gap_limit -> the
core_wallet_set_gap_limit C export, in-tree since #3970) but stopped at
the C boundary: WalletManagerNative had no trampoline, so no Kotlin host
could widen an address window. Adds the JNI export (account-type mapping
via the existing core_account_type; from-height guard mirrors the
sibling exports), the external fun, and a ManagedCoreWallet.setGapLimit
wrapper under mapNativeErrors.

Motivation: a migrated wallet whose OTHER same-seed client (dashj) kept
deriving past the SDK's watched window goes silently blind to the change
output and every descendant — the wallet reports synced with the wrong
balance. Widening the gap limit (Rust caps at 1000) and re-scanning
recovers the history; the Android app's one-shot migration heal is the
first consumer.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@github-actions github-actions Bot added this to the v4.2.0 milestone Aug 11, 2026
@coderabbitai

coderabbitai Bot commented Aug 11, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

Caution

Review failed

An error occurred during the review process. Please try again later.

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: 9145e2e2-69e7-4143-98ab-eef8c677f348

📥 Commits

Reviewing files that changed from the base of the PR and between 69cacb6 and 6637b68.

📒 Files selected for processing (2)
  • packages/kotlin-sdk/sdk/src/androidTest/kotlin/org/dashfoundation/dashsdk/FfiSmokeTest.kt
  • packages/kotlin-sdk/sdk/src/androidTest/kotlin/org/dashfoundation/dashsdk/wallet/CoreWalletSetGapLimitBindingTest.kt

Included review availability: Your plan includes up to 3 reviews per rolling hour; 2 remain after this review.


📝 Walkthrough

Walkthrough

The change adds an account-specific setGapLimit API to the Kotlin wallet SDK. The JNI layer validates its parameters and forwards valid requests to the native wallet FFI. Android tests cover invalid arguments and native binding behavior.

Changes

Wallet gap-limit API

Layer / File(s) Summary
Kotlin gap-limit contract
packages/kotlin-sdk/sdk/src/main/kotlin/org/dashfoundation/dashsdk/ffi/WalletManagerNative.kt, packages/kotlin-sdk/sdk/src/main/kotlin/org/dashfoundation/dashsdk/wallet/ManagedCoreWallet.kt
Adds the native coreWalletSetGapLimit declaration and the public setGapLimit method for a selected account. Native errors are mapped to Kotlin errors.
JNI validation, forwarding, and Android coverage
packages/rs-unified-sdk-jni/src/wallet_manager.rs, packages/kotlin-sdk/sdk/src/androidTest/kotlin/org/dashfoundation/dashsdk/FfiSmokeTest.kt, packages/kotlin-sdk/sdk/src/androidTest/kotlin/org/dashfoundation/dashsdk/wallet/CoreWalletSetGapLimitBindingTest.kt
Documents and adds the JNI bridge. It rejects unsupported account types, negative account indices, and non-positive gap limits. Tests verify symbol binding, validation errors, and forwarding to the FFI invalid-handle path.

Estimated code review effort: 3 (Moderate) | ~20 minutes

Merge Risk: ⚪ Minimal · up to 6637b

The PR adds a localized Kotlin/JNI bridge for widening migrated-wallet address windows, with the described recovery and regression tests covering the intended behavior. No actionable merge-blocking risk remains beyond normal checks and review.

Sequence Diagram(s)

sequenceDiagram
  participant ManagedCoreWallet
  participant WalletManagerNative
  participant JNIWalletManager
  participant PlatformWalletFFI
  ManagedCoreWallet->>WalletManagerNative: set gap limit for account
  WalletManagerNative->>JNIWalletManager: call coreWalletSetGapLimit
  JNIWalletManager->>PlatformWalletFFI: validate and forward request
  PlatformWalletFFI-->>JNIWalletManager: return result or error
  JNIWalletManager-->>ManagedCoreWallet: map native error
Loading

Suggested reviewers: quantumexplorer, shumkov

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 25.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly and concisely describes exposing core_wallet_set_gap_limit to Kotlin for migrated-wallet address-window recovery.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches 💡 1
📝 Generate docstrings 💡
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/kotlin-sdk-gap-limit-jni

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

@thepastaclaw

thepastaclaw commented Aug 11, 2026

Copy link
Copy Markdown
Collaborator

✅ Final review complete — no blockers (commit ceb42b6)

@coderabbitai coderabbitai Bot left a comment

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.

Actionable comments posted: 1

🤖 Prompt for all review comments with 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.

Inline comments:
In `@packages/rs-unified-sdk-jni/src/wallet_manager.rs`:
- Around line 645-648: Restrict the account type handling around
core_account_type to accept only BIP44, BIP32, and CoinJoin for this per-account
bridge operation. Explicitly reject AllSpendable (account_type 3) before
forwarding the value, while preserving the existing out-of-range exception
behavior.
🪄 Autofix

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: 76b15fb1-440b-4305-a885-ecb512cb0c64

📥 Commits

Reviewing files that changed from the base of the PR and between 480271e and d035604.

📒 Files selected for processing (3)
  • packages/kotlin-sdk/sdk/src/main/kotlin/org/dashfoundation/dashsdk/ffi/WalletManagerNative.kt
  • packages/kotlin-sdk/sdk/src/main/kotlin/org/dashfoundation/dashsdk/wallet/ManagedCoreWallet.kt
  • packages/rs-unified-sdk-jni/src/wallet_manager.rs

Comment thread packages/rs-unified-sdk-jni/src/wallet_manager.rs Outdated
A gap limit belongs to one account's address pools; the aggregate (3)
has none, so refuse it at the boundary with a clear message instead of
forwarding it to a per-account FFI.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@codecov

codecov Bot commented Aug 11, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 85.33%. Comparing base (480271e) to head (ceb42b6).
⚠️ Report is 20 commits behind head on v4.2-dev.

Additional details and impacted files
@@             Coverage Diff              @@
##           v4.2-dev    #4377      +/-   ##
============================================
- Coverage     87.63%   85.33%   -2.30%     
============================================
  Files          2670     2712      +42     
  Lines        339447   355932   +16485     
============================================
+ Hits         297465   303750    +6285     
- Misses        41982    52182   +10200     
Components Coverage Δ
dpp 86.58% <ø> (-2.29%) ⬇️
drive 84.28% <ø> (-1.97%) ⬇️
drive-abci 86.93% <ø> (-2.73%) ⬇️
sdk ∅ <ø> (∅)
dapi-client ∅ <ø> (∅)
platform-version ∅ <ø> (∅)
platform-value 92.92% <ø> (+0.03%) ⬆️
platform-wallet ∅ <ø> (∅)
drive-proof-verifier 39.27% <ø> (-8.76%) ⬇️
🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@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.

Final validation — Codex/Sol only (Phase 2 disabled)

The Kotlin declaration, public wrapper, and JNI trampoline correctly validate signed inputs, reject the AllSpendable aggregate, map concrete account types, and forward native errors. The remaining in-scope issue is the absence of automated Android binding coverage for the new cross-language symbol and validation branches.

Source: codex-general reviewer backend gpt-5.6-sol; codex-ffi-engineer reviewer backend gpt-5.6-sol; final verifier backend gpt-5.6-sol. openclaw-agent/cliproxy/gpt-5.6-sol is orchestration-only and not reviewer evidence.

Validated zero-blocker Codex/Sol precheck evidence was promoted to final because Phase 2 (Sonnet/Opus) is temporarily disabled. This is Codex/Sol-only final validation, not Codex + Sonnet/Opus coverage.

Review provenance

  • Codex reviewers: gpt-5.6-sol — general (completed), gpt-5.6-sol — ffi-engineer (completed)
  • Verifier: gpt-5.6-sol — verifier
  • Sonnet/Opus: not run (Phase 2 disabled — temporary Codex/Sol-only final)
  • Secondary pass: disabled (temporary_phase2_sonnet_disable)

🟡 1 suggestion(s)

🤖 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/rs-unified-sdk-jni/src/wallet_manager.rs`:
- [SUGGESTION] packages/rs-unified-sdk-jni/src/wallet_manager.rs:648-675: Add an instrumented binding test for the new JNI export
  No repository test invokes `coreWalletSetGapLimit`, so the Kotlin external declaration, generated JNI symbol, parameter descriptor, and the new validation branches can regress without detection. `cargo check -p rs-unified-sdk-jni` verifies only the Rust side and cannot detect a Kotlin/JNI naming or signature mismatch that would produce `UnsatisfiedLinkError` on Android. The existing Android instrumented suite already uses invalid handles to pin JNI bindings without requiring a funded wallet; add equivalent coverage that loads the native library, verifies that account type `3`, a negative account index, and a non-positive gap limit are rejected as invalid parameters, then calls a concrete account type with handle `0` and verifies that execution reaches the underlying FFI's invalid-handle path.

Comment on lines +648 to +675
let account_type = match core_account_type(account_type) {
Some(platform_wallet_ffi::CoreAccountTypeFFI::AllSpendable) | None => {
throw_sdk_exception(
env,
1,
"accountType must be a concrete account (0=BIP44, 1=BIP32, 2=CoinJoin)",
);
return;
}
Some(concrete) => concrete,
};
if account_index < 0 {
throw_sdk_exception(env, 1, "accountIndex must be non-negative");
return;
}
if gap_limit <= 0 {
throw_sdk_exception(env, 1, "gapLimit must be positive");
return;
}
let result = unsafe {
platform_wallet_ffi::core_wallet_set_gap_limit(
wallet_handle as Handle,
account_type,
account_index as u32,
gap_limit as u32,
)
};
let _ = take_pwffi_error(env, result);

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: Add an instrumented binding test for the new JNI export

No repository test invokes coreWalletSetGapLimit, so the Kotlin external declaration, generated JNI symbol, parameter descriptor, and the new validation branches can regress without detection. cargo check -p rs-unified-sdk-jni verifies only the Rust side and cannot detect a Kotlin/JNI naming or signature mismatch that would produce UnsatisfiedLinkError on Android. The existing Android instrumented suite already uses invalid handles to pin JNI bindings without requiring a funded wallet; add equivalent coverage that loads the native library, verifies that account type 3, a negative account index, and a non-positive gap limit are rejected as invalid parameters, then calls a concrete account type with handle 0 and verifies that execution reaches the underlying FFI's invalid-handle path.

source: ['codex']

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Added in 25000de — CoreWalletSetGapLimitBindingTest (androidTest, mirrors the existing CoreTxBuilderOpReturnBindingTest no-wallet pattern): five cases pin the validation branches (AllSpendable and unknown account types, negative accountIndex, gapLimit 0/-1) to raw code 1 with branch-naming message assertions that double as int-parameter-order pins, and each concrete account type with a dead handle must surface an FFI-translated code >= PLATFORM_WALLET_CODE_OFFSET — proving execution crossed the JNI symbol into core_wallet_set_gap_limit rather than dying in the trampoline. Compiles green (:sdk:compileDebugAndroidTestKotlin); rides the next instrumented device run since the only prebuilt local .so predates the export.

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.

Resolved in this update — Add an instrumented binding test for the new JNI export no longer present.

Auto-resolved by the review system based on the latest commit diff. If you believe this was closed in error, reopen the thread.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Added in the commit above — setGapLimitSymbolBindsAndRejectsInvalidArguments in FfiSmokeTest.

It follows the existing syncFaultDetectedSymbolBindsAndRejectsInvalidHandle pattern you pointed at, so it needs no funded wallet: every case is rejected before the handle is dereferenced, which makes an invalid handle a legitimate argument rather than a workaround.

Covers all three validation branches — AllSpendable (3), a negative account index, and a zero/negative gap limit — plus otherwise-valid arguments against a null handle. That last case is the control: it proves the earlier rejections came from the validation branches and not merely from the bad handle.

On your point about UnsatisfiedLinkError: that is exactly why this is worth having as an instrumented test. A binding or descriptor mismatch raises UnsatisfiedLinkError, which is not a DashSDKException, so assertThrows(DashSDKException::class.java) fails loudly rather than passing on the wrong exception.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Done — added in 6637b684 as a dedicated instrumented suite, CoreWalletSetGapLimitBindingTest, rather than folding it into the smoke test. It loads the native library and calls through the real external fun (so a name/descriptor mismatch surfaces as UnsatisfiedLinkError, which is not a DashSDKException and fails the assertion loudly rather than passing on the wrong exception), and needs no funded wallet because every case is rejected before the handle is dereferenced:

  • allSpendableAggregateIsRejectedBeforeTheFfi — account type 3
  • unknownAccountTypeIsRejectedBeforeTheFfi
  • negativeAccountIndexIsRejectedBeforeTheFfi
  • nonPositiveGapLimitIsRejectedBeforeTheFfi — zero and negative
  • concreteAccountTypesReachTheFfiInvalidHandlePath — all three concrete mappings with handle 0, proving the earlier rejections came from validation rather than the handle

It also asserts on branch-specific messages, so parameter ordering is pinned too.

…imit

No test invoked the new JNI export, so the Kotlin external declaration,
generated symbol, parameter descriptor, and the trampoline's validation
branches could regress silently (an UnsatisfiedLinkError only in
production). Mirror the CoreTxBuilderOpReturnBindingTest no-wallet
discipline: pin the AllSpendable (3) and unknown account-type
rejections, the negative accountIndex rejection, and the non-positive
gapLimit rejections to the JNI-side DashSDKException (raw code 1, branch-
naming messages — which also pins the int parameter order), then prove
every concrete account type (0/1/2) passes validation and crosses into
core_wallet_set_gap_limit by asserting handle 0 surfaces the FFI's
translated error in the platform-wallet code range instead.

Validated with :sdk:compileDebugAndroidTestKotlin (BUILD SUCCESSFUL);
an on-device run needs a libdash_sdk_jni.so built from this branch —
the machine's only prebuilt binary predates the export.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

@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.

Final validation — Codex/Sol only (Phase 2 disabled)

The current head correctly exposes the per-account gap-limit operation through Kotlin and JNI, rejects invalid signed inputs and the AllSpendable aggregate, and forwards concrete account requests through the existing platform-wallet FFI error path. The newly added Android instrumented test invokes the native export and covers symbol resolution, argument ordering, all JNI validation branches, all three concrete account types, and native error translation; no in-scope issues remain.
Source: codex-general reviewer backend gpt-5.6-sol; codex-ffi-engineer reviewer backend gpt-5.6-sol; final verifier backend gpt-5.6-sol. openclaw-agent/cliproxy/gpt-5.6-sol is orchestration-only and not reviewer evidence.

Validated zero-blocker Codex/Sol precheck evidence was promoted to final because Phase 2 (Sonnet/Opus) is temporarily disabled. This is Codex/Sol-only final validation, not Codex + Sonnet/Opus coverage.

Review provenance

  • Codex reviewers: gpt-5.6-sol — general (completed), gpt-5.6-sol — ffi-engineer (completed)
  • Verifier: gpt-5.6-sol — verifier
  • Sonnet/Opus: not run (Phase 2 disabled — temporary Codex/Sol-only final)
  • Secondary pass: disabled (temporary_phase2_sonnet_disable)

@bfoss765

Copy link
Copy Markdown
Collaborator Author

Status for a resolution pass — head `25000dea5`. The single thread (instrumented binding test for coreWalletSetGapLimit) is addressed in `25000dea58`. MERGEABLE, workspace tests passing. Ready to resolve — thanks.

#4377 review finding 3c43490f1527: no repository test invoked
the new export, so the Kotlin external declaration, the generated JNI symbol,
its parameter descriptor and the validation branches could all regress silently.

`cargo check -p rs-unified-sdk-jni` proves only that the Rust compiles; it cannot
catch a name or descriptor mismatch between the Kotlin `external fun` and the
generated symbol, which surfaces as an UnsatisfiedLinkError on device rather than
at build time. Loading the library and calling through the real declaration is
the only thing that pins that contract, hence instrumented rather than unit.

Follows the existing invalid-handle pattern (syncFaultDetectedSymbolBindsAndRejects
InvalidHandle), so it needs no funded wallet: every case is rejected before the
handle is dereferenced. Covers all three validation branches — AllSpendable (3),
a negative account index, and a zero/negative gap limit — plus otherwise-valid
arguments against a null handle, which proves the earlier rejections came from
validation rather than from the handle.

A binding failure raises UnsatisfiedLinkError, which is NOT a DashSDKException,
so the assertions fail loudly instead of passing on the wrong exception.

@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.

Final validation — Codex/Sol only (Phase 2 disabled)

The Kotlin wrapper and JNI trampoline correctly expose the per-account gap-limit operation, reject invalid inputs and the AllSpendable aggregate, and preserve native error translation. The only remaining in-scope suggestion is to remove the redundant FfiSmokeTest coverage because the dedicated binding suite runs in the same unfiltered Android task and covers the contract more precisely.
Source: codex-general reviewer backend gpt-5.6-sol; codex-ffi-engineer reviewer backend gpt-5.6-sol; final verifier backend gpt-5.6-sol. openclaw-agent/cliproxy/gpt-5.6-sol is orchestration-only and not reviewer evidence.

Validated zero-blocker Codex/Sol precheck evidence was promoted to final because Phase 2 (Sonnet/Opus) is temporarily disabled. This is Codex/Sol-only final validation, not Codex + Sonnet/Opus coverage.

Review provenance

  • Codex reviewers: gpt-5.6-sol — general (completed), gpt-5.6-sol — ffi-engineer (completed)
  • Verifier: gpt-5.6-sol — verifier
  • Sonnet/Opus: not run (Phase 2 disabled — temporary Codex/Sol-only final)
  • Secondary pass: disabled (temporary_phase2_sonnet_disable)

🟡 1 suggestion(s)

🤖 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/androidTest/kotlin/org/dashfoundation/dashsdk/FfiSmokeTest.kt`:
- [SUGGESTION] packages/kotlin-sdk/sdk/src/androidTest/kotlin/org/dashfoundation/dashsdk/FfiSmokeTest.kt:105-145: Remove the duplicate gap-limit instrumented test
  This method duplicates a subset of `CoreWalletSetGapLimitBindingTest`, and both classes run under the same unfiltered `:sdk:connectedDebugAndroidTest` task in CI. The dedicated suite already invokes the JNI symbol and covers AllSpendable, unknown account types, negative indexes, non-positive limits, all three concrete account mappings, parameter ordering through branch-specific messages, raw JNI validation codes, and translated FFI errors. Keeping this second copy adds redundant device execution and another test that must be updated whenever the binding contract changes without providing distinct coverage; retain the dedicated binding test and remove this method and its KDoc.

Comment on lines +105 to +145
/**
* `coreWalletSetGapLimit` binds, and rejects every invalid argument.
*
* `cargo check -p rs-unified-sdk-jni` proves only the Rust side compiles;
* it cannot catch a name or descriptor mismatch between the Kotlin
* `external fun` and the generated JNI symbol, which surfaces as an
* `UnsatisfiedLinkError` on device rather than at build time. Loading the
* library and calling through the real declaration is the only thing that
* pins that contract — hence an instrumented test rather than a unit one.
*
* Needs no funded wallet: every case here is rejected before the handle is
* dereferenced, so an invalid handle is a legitimate argument. A binding
* failure surfaces as `UnsatisfiedLinkError`, which is NOT a
* `DashSDKException`, so the assertion below fails loudly rather than
* passing on the wrong exception.
*/
@Test
fun setGapLimitSymbolBindsAndRejectsInvalidArguments() {
NativeLoader.ensureLoaded()

// AllSpendable (3) is not a single account — a gap limit is per-account.
assertThrows(DashSDKException::class.java) {
WalletManagerNative.coreWalletSetGapLimit(0L, 3, 0, 20)
}
// Negative account index.
assertThrows(DashSDKException::class.java) {
WalletManagerNative.coreWalletSetGapLimit(0L, 0, -1, 20)
}
// Non-positive gap limit: zero and negative.
assertThrows(DashSDKException::class.java) {
WalletManagerNative.coreWalletSetGapLimit(0L, 0, 0, 0)
}
assertThrows(DashSDKException::class.java) {
WalletManagerNative.coreWalletSetGapLimit(0L, 0, 0, -5)
}
// Otherwise-valid arguments against a null handle still fail — proving
// the earlier rejections came from validation and not from the handle.
assertThrows(DashSDKException::class.java) {
WalletManagerNative.coreWalletSetGapLimit(0L, 0, 0, 20)
}
}

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: Remove the duplicate gap-limit instrumented test

This method duplicates a subset of CoreWalletSetGapLimitBindingTest, and both classes run under the same unfiltered :sdk:connectedDebugAndroidTest task in CI. The dedicated suite already invokes the JNI symbol and covers AllSpendable, unknown account types, negative indexes, non-positive limits, all three concrete account mappings, parameter ordering through branch-specific messages, raw JNI validation codes, and translated FFI errors. Keeping this second copy adds redundant device execution and another test that must be updated whenever the binding contract changes without providing distinct coverage; retain the dedicated binding test and remove this method and its KDoc.

source: ['codex']

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Agreed and removed in ceb42b68 — you're right that it was a strict subset of CoreWalletSetGapLimitBindingTest and bought only redundant device time plus a second place to update when the binding contract moves. The dedicated suite is now the only coverage of this symbol.

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.

Resolved in ceb42b6Remove the duplicate gap-limit instrumented test no longer present.

Auto-resolved by the review system based on the latest commit diff. If you believe this was closed in error, reopen the thread.

`CoreWalletSetGapLimitBindingTest` already pins the JNI binding on device
and covers a strict superset of this method: AllSpendable, unknown account
types, negative indexes, non-positive limits, all three concrete account
mappings, parameter ordering via branch-specific messages, the raw JNI
validation codes, and the translated FFI errors. Both classes run under the
same unfiltered `:sdk:connectedDebugAndroidTest`, so the copy only bought
redundant device time and a second place to update whenever the binding
contract moves.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

@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.

Final validation — Codex/Sol only (Phase 2 disabled)

At exact head ceb42b6, the Kotlin declaration, managed wrapper, JNI validation and forwarding, and dedicated Android binding coverage are consistent; no in-scope defects remain. The prior duplicate-test finding is fixed because commit ceb42b6 removed the redundant FfiSmokeTest method and KDoc while retaining the broader CoreWalletSetGapLimitBindingTest suite as the sole coverage for this symbol. Source: Codex general reviewer backend gpt-5.6-sol; Codex FFI-engineer reviewer backend gpt-5.6-sol; final verifier backend gpt-5.6-sol. openclaw-agent/cliproxy/gpt-5.6-sol is orchestration-only and not reviewer evidence.

Validated zero-blocker Codex/Sol precheck evidence was promoted to final because Phase 2 (Sonnet/Opus) is temporarily disabled. This is Codex/Sol-only final validation, not Codex + Sonnet/Opus coverage.

Review provenance

  • Codex reviewers: gpt-5.6-sol — general (completed), gpt-5.6-sol — ffi-engineer (completed)
  • Verifier: gpt-5.6-sol — verifier
  • Sonnet/Opus: not run (Phase 2 disabled — temporary Codex/Sol-only final)
  • Secondary pass: disabled (temporary_phase2_sonnet_disable)

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