Skip to content

feat(sdk): carry consensus error codes through the wallet FFI - #4838

Merged
QuantumExplorer merged 1 commit into
v4.2-devfrom
feat/wallet-ffi-consensus-error-code
Sep 19, 2026
Merged

QuantumExplorer merged 1 commit into
v4.2-devfrom
feat/wallet-ffi-consensus-error-code

Conversation

@QuantumExplorer

Copy link
Copy Markdown
Member

Issue being fixed or feature implemented

Follow-up to #4829, where a reviewer asked for a structured result instead of text matching.

A wallet token operation that Platform rejects reaches the mobile apps as text only. platform-wallet rendered the SDK error into PlatformWalletError::TokenError(format!("Token claim failed: {e}")), so PlatformWalletFFIResult, the JNI DashSDKException and the Swift PlatformWalletError had nothing but a message left. Both example apps therefore recognised the once-per-identity already-claimed rejection (TokenOncePerIdentityDistributionAlreadyClaimedError, consensus code 40722) by matching that message: a digit-boundary scan for 40722 plus the sentence rs-dpp renders. A false positive there is permanent, because the local "already claimed" record is never cleared.

What was done?

rs-platform-wallet

  • New PlatformWalletError::TokenOperationFailed { operation, source: dash_sdk::Error }. It keeps the SDK error instead of formatting it away, and its Display is byte-identical to what TokenError rendered for the same failure.
  • PlatformWalletError::token_operation_failed(op, e) builds it for all 12 token state transitions (claim, mint, burn, transfer, freeze, unfreeze, destroy frozen funds, pause, resume, set price, purchase, config update). The structured signer key-unavailable failure still goes to Sdk, so FFI code 31 is unaffected.
  • PlatformWalletError::consensus_error() returns the ConsensusError for Sdk and TokenOperationFailed, covering the wait-stream shape (StateTransitionBroadcastError.cause), the CheckTx shape (Protocol(ConsensusError)) and the NoAvailableAddressesToRetry envelope.

rs-platform-wallet-ffi

  • PlatformWalletFFIResult gains consensus_code: u32 (0 when the failure was not a consensus rejection) and consensus_kind: PlatformWalletFFIConsensusErrorKind (None, Basic, Signature, Fee, State). Rust hands the kind over so neither host derives it from the digits of the code.
  • From<PlatformWalletError> stamps both from consensus_error(). The result code and message do not move: a rejected token operation is still ErrorUnknown with the same text, so no number is claimed in ERROR_CODE_REGISTRY.md and no dedicated code is overridden.
  • The comments calling the struct "ABI-frozen (code + message only)" are corrected to what still holds: it has no fields for a specific error's values, which keep riding the message.

rs-unified-sdk-jni and the Kotlin SDK

  • throw_pwffi_result throws DashSDKException(code, message, consensusCode, consensusKind) when the result carries a rejection, and the existing (Int, String) constructor otherwise. take_pwffi_error and wallet_manager::throw_pwffi share it.
  • New PlatformConsensusError(code, kind) and ConsensusErrorKind; DashSDKException.consensusError; DashSdkError.consensusError reads it off the native cause, so every subtype has it and no existing type or mapping changes.

Swift SDK

  • New PlatformConsensusError (code, kind), PlatformWalletError.consensusRejection(PlatformConsensusError, String) and PlatformWalletError.consensusError. The new case is produced only for the catch-all result code, so every dedicated case keeps arriving as it did.

Example apps

  • OncePerIdentityClaimStore.isAlreadyClaimed (Kotlin) and OncePerIdentityClaimRejection.isAlreadyClaimed (Swift) compare the consensus code with 40722. The regex, the digit-boundary scanner and the message signals are deleted. No text fallback is kept: every path that can produce 40722 goes through token_claim_with_external_signer, which now carries the code.

How Has This Been Tested?

Run locally (macOS, arm64):

  • cargo test -p platform-wallet --lib error::: 22 passed (5 new).
  • cargo test -p platform-wallet-ffi --lib error::: 50 passed (5 new).
  • cargo clippy -p platform-wallet -p platform-wallet-ffi -p rs-unified-sdk-jni --all-targets: clean. cargo check -p platform-wallet-storage -p rs-unified-sdk-ffi: passes. cargo fmt --check on the three edited crates: clean.
  • ./gradlew :sdk:testDebugUnitTest --tests '*DashSdkErrorTest': 21 tests, 0 failures (3 new).
  • ./gradlew :app:testDebugUnitTest --tests '*TokenMaterializerOncePerIdentityTest': 11 tests, 0 failures.

Not run at the time of opening:

  • No Swift file in this PR has been compiled yet. The Swift changes read the new struct fields, so they only build against an xcframework regenerated from this branch, and that build was still running. swift test and the SwiftExampleAppTests/TokenClaimResolverTests xcodebuild run are pending on it; I will update this PR with the results or with fixes.
  • The JNI throw path itself (constructor lookup for the four-argument DashSDKException) is type-checked only; it needs a rebuilt .so and an emulator to exercise. The Android .so was not rebuilt.
  • No end-to-end claim against a network on either platform.

Breaking Changes

None for hosts built from this tree.

Two things change shape and are worth a reviewer's eye:

  • PlatformWalletFFIResult grows by two fields. Both consumers are generated from this source (Swift through the cbindgen header in the xcframework, Kotlin through an rlib dependency of the JNI crate), so they move together; a host linking a prebuilt library against an older header would not.
  • Swift's public PlatformWalletError gains a case, and a rejected token operation now arrives as .consensusRejection where it used to arrive as .unknown. Its errorDescription is the same message.

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 added "!" to the title and described breaking changes in the corresponding section if my code contains any
  • I have made corresponding changes to the documentation if needed

For repository code-owners and collaborators only

  • I have assigned this pull request to a milestone

🤖 Generated with Claude Code

A token operation Platform rejected reached the mobile apps as text only:
platform-wallet rendered the SDK error into `TokenError(String)`, so the
FFI result, the JNI exception and the Swift error had nothing but a message
left, and the example apps recognised the once-per-identity already-claimed
rejection (consensus code 40722) by matching that message.

platform-wallet keeps the `dash_sdk::Error` of a failed token state
transition in a new `TokenOperationFailed` variant, with the same Display
text, and exposes the consensus error it holds through
`PlatformWalletError::consensus_error`. `PlatformWalletFFIResult` gains
`consensus_code` and `consensus_kind` (basic, signature, fee, state), which
the conversion from `PlatformWalletError` fills in. The result code and the
message are unchanged, so existing hosts see what they saw before.

Kotlin surfaces them as `DashSDKException.consensusError` and
`DashSdkError.consensusError`; Swift as a `consensusRejection` case and
`PlatformWalletError.consensusError`. Both example apps now compare the
consensus code and no longer read the message.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
@coderabbitai

coderabbitai Bot commented Sep 19, 2026

Copy link
Copy Markdown
Contributor

Warning

Review limit reached

Next included review available in 9 minutes.

Check out review usage here.

View limit details

Limit details: You’ve used the included review currently available.

You've used all free OSS reviews for now. Wait for the free limit to reset to keep reviewing this public repository.

Learn how review limits work.

Review configuration:

⚙️ Run configuration

Configuration used: Repository: dashpay/platform/.coderabbit.yaml

Review profile: CHILL

Plan: Advanced

Run ID: 72bc9390-3212-4e00-a29c-df19114d0914

📥 Commits

Reviewing files that changed from the base of the PR and between cb9a797 and a844c61.

📒 Files selected for processing (29)
  • packages/kotlin-sdk/CLAUDE.md
  • packages/kotlin-sdk/KotlinExampleApp/app/src/main/java/org/dashfoundation/example/services/tokens/OncePerIdentityClaimStore.kt
  • packages/kotlin-sdk/KotlinExampleApp/app/src/test/java/org/dashfoundation/example/services/tokens/TokenMaterializerOncePerIdentityTest.kt
  • packages/kotlin-sdk/sdk/src/main/kotlin/org/dashfoundation/dashsdk/errors/DashSdkError.kt
  • packages/kotlin-sdk/sdk/src/main/kotlin/org/dashfoundation/dashsdk/errors/PlatformConsensusError.kt
  • packages/kotlin-sdk/sdk/src/main/kotlin/org/dashfoundation/dashsdk/ffi/DashSDKException.kt
  • packages/kotlin-sdk/sdk/src/test/kotlin/org/dashfoundation/dashsdk/errors/DashSdkErrorTest.kt
  • packages/rs-platform-wallet-ffi/src/error.rs
  • packages/rs-platform-wallet-ffi/src/shielded_send.rs
  • packages/rs-platform-wallet/src/error.rs
  • packages/rs-platform-wallet/src/wallet/identity/network/tokens/burn.rs
  • packages/rs-platform-wallet/src/wallet/identity/network/tokens/claim.rs
  • packages/rs-platform-wallet/src/wallet/identity/network/tokens/destroy_frozen_funds.rs
  • packages/rs-platform-wallet/src/wallet/identity/network/tokens/freeze.rs
  • packages/rs-platform-wallet/src/wallet/identity/network/tokens/mint.rs
  • packages/rs-platform-wallet/src/wallet/identity/network/tokens/pause.rs
  • packages/rs-platform-wallet/src/wallet/identity/network/tokens/purchase.rs
  • packages/rs-platform-wallet/src/wallet/identity/network/tokens/resume.rs
  • packages/rs-platform-wallet/src/wallet/identity/network/tokens/set_price.rs
  • packages/rs-platform-wallet/src/wallet/identity/network/tokens/transfer.rs
  • packages/rs-platform-wallet/src/wallet/identity/network/tokens/unfreeze.rs
  • packages/rs-platform-wallet/src/wallet/identity/network/tokens/update_config.rs
  • packages/rs-unified-sdk-jni/src/support.rs
  • packages/rs-unified-sdk-jni/src/wallet_manager.rs
  • packages/swift-sdk/Sources/SwiftDashSDK/PlatformWallet/PlatformWalletManager.swift
  • packages/swift-sdk/Sources/SwiftDashSDK/PlatformWallet/PlatformWalletResult.swift
  • packages/swift-sdk/SwiftExampleApp/SwiftExampleApp/Services/OncePerIdentityClaimStore.swift
  • packages/swift-sdk/SwiftExampleApp/SwiftExampleAppTests/TokenClaimResolverTests.swift
  • packages/swift-sdk/SwiftTests/SwiftDashSDKTests/ErrorHandlingTests.swift

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.

@github-actions github-actions Bot added this to the v4.2.0 milestone Sep 19, 2026
@github-actions

github-actions Bot commented Sep 19, 2026

Copy link
Copy Markdown
Contributor

PR Hygiene

State: waiting-bots · commit a844c61f0ff105eee65c8f2ee5cc24eb04df0054

  • coderabbitai has not reported for the current head
  • thepastaclaw has not reported for the current head

Self-review is an author attestation that you have read the diff:
/self-reviewed — covers everything pushed so far; post it again after a new push.

This report does not bypass CI or repository protection rules.

@QuantumExplorer
QuantumExplorer merged commit c92a176 into v4.2-dev Sep 19, 2026
19 checks passed
@QuantumExplorer
QuantumExplorer deleted the feat/wallet-ffi-consensus-error-code branch September 19, 2026 13:03
QuantumExplorer added a commit that referenced this pull request Sep 19, 2026
Brings in #4838. v14.rs: #4826 and #4828 each added an item to the protocol
version 14 list after this PR numbered its own 11 and 12, leaving two 11s and
two 12s. The shipped items keep 11 and 12; this PR's follow as 13 and 14.
Comments only.

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

codecov Bot commented Sep 19, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 76.56%. Comparing base (a1db364) to head (a844c61).
⚠️ Report is 5 commits behind head on v4.2-dev.

Additional details and impacted files
@@             Coverage Diff              @@
##           v4.2-dev    #4838      +/-   ##
============================================
+ Coverage     73.86%   76.56%   +2.70%     
============================================
  Files          2977     2978       +1     
  Lines        434179   433987     -192     
============================================
+ Hits         320696   332287   +11591     
+ Misses       113483   101700   -11783     
Components Coverage Δ
dpp 73.58% <ø> (+2.29%) ⬆️
drive 78.15% <ø> (+2.62%) ⬆️
drive-abci 77.76% <ø> (+3.75%) ⬆️
sdk ∅ <ø> (∅)
dapi-client ∅ <ø> (∅)
platform-version ∅ <ø> (∅)
platform-value 86.10% <ø> (+0.67%) ⬆️
platform-wallet ∅ <ø> (∅)
drive-proof-verifier 28.03% <ø> (-0.91%) ⬇️
🚀 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.

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.

1 participant