Skip to content

feat(sdk): multiple choice decision proposals in the Swift and Kotlin SDKs - #5919

Open
QuantumExplorer wants to merge 2 commits into
v5.1-devfrom
claude/multiple-choice-proposal-mobile
Open

QuantumExplorer wants to merge 2 commits into
v5.1-devfrom
claude/multiple-choice-proposal-mobile

Conversation

@QuantumExplorer

@QuantumExplorer QuantumExplorer commented Nov 13, 2026 •

Copy link
Copy Markdown
Member

Basic explanation

What this does: A multiple choice proposal is a question put to the masternodes whose answers ("choices") anyone, or only its author, can enter; the masternodes vote and the choice with the most votes is kept. The Rust and JavaScript SDKs got helpers for it in #5903. This adds the same to the Swift and Kotlin SDKs: create a proposal, edit it, enter a choice, list proposals, read the vote and the kept choice, and cast a masternode's vote, with screens for all of it in both example apps.

Value: Yes/no proposals already work end to end on mobile; multiple choice proposals had nothing there. A mobile app can now show the cost before signing (0.2 Dash for a proposal, the contest fund for a choice) and vote for a choice by its document id without assembling the contest by hand.

Risks: Low. No consensus code changes: this is client code in the wallet, the two FFI crates, the JNI bridge and the two SDKs. The existing yes/no code is touched only to share helpers (visibility changes, one helper that now takes the document type name). Not exercised against a live network, because no protocol version 15 network exists yet, and the Kotlin instrumented test was not run (no emulator).

Issue being fixed or feature implemented

Part of #5679 (decision proposals). Follows #5903 (Rust and JavaScript helpers) and mirrors #5704 (yes/no proposals on mobile). docs/protocol/decision-proposals.md said "The mobile SDKs have no multiple choice helpers yet."

What was done?

Before and after

Before, the only route to a vote among a proposal's choices from Kotlin was the generic contested vote call, written for DPNS labels: the caller named the contract, the document type, the index and its values, and passed the choice's document id where the call expects a contender's identity id.

walletManager.voteCasting.castVote(
    sdk, dataContractId = "3iNyoNwjCTkb7XS3RHyWUtLx7pgC6AFAEQNNDHzxqngF",
    documentTypeName = "choice", indexName = "byProposal",
    indexValues = listOf(proposalIdAsIndexValue),
    choice = VoteChoice.TowardsIdentity(choiceDocumentId),
    proTxHash, votingPrivateKey, networkOrd,
)

After:

sdk.multipleChoiceProposals.castVote(
    proposalId,
    MultipleChoiceVoteChoice.Choice(choiceId), // or NoneOfTheChoices, or Abstain
    proTxHash, votingPrivateKey,
)
try await sdk.castMultipleChoiceProposalVote(
    proposalId: proposalId,
    choice: .choice(id: choiceId), // or .noneOfTheChoices, or .abstain
    proTxHash: proTxHash, votingPrivateKey: votingPrivateKey
)

rs-platform-wallet and rs-platform-wallet-ffi

  • IdentityWallet::create_multiple_choice_proposal_with_signer, edit_multiple_choice_proposal_with_signer, enter_choice_with_signer. rs-sdk builds the documents; the wallet selects the signing key, puts the document and keeps the owner's proven balance, as the yes/no create does.
  • The edit reads the proposal once and puts it at the revision after the one read, so Platform refuses an edit another one overtook (40106) instead of letting it overwrite that one.
  • Entering a choice needs no path of its own: the ordinary document put already reads the contest fund and states it, as it does for a contested DPNS name. A test shows the fund is read once before the put reaches the broadcast.
  • C functions platform_wallet_multiple_choice_proposal_create, _edit, _enter_choice, with DescriptionDocumentFFI, MultipleChoiceProposalParamsFFI, MultipleChoiceProposalEditFFI (keep, set or remove for both the url and the description document) and ChoiceParamsFFI.

rs-sdk-ffi

dash_sdk_multiple_choice_proposal_fetch, _list_by_owner, _list_by_created_at, _get_vote, _get_kept_choice, _get_masternode_vote, _get_processing_fee, _get_choice_fund_to_join, _cast_vote. Reads return JSON; the two amounts return credits as a decimal string, as the identity balance does. _cast_vote takes a byte (0 for a choice with its id, 1 for none of the choices, 2 for abstain), refuses any other combination before a network call, and votes through the MasternodeVoter the contested and yes/no votes share.

Swift SDK and SwiftExampleApp

  • MultipleChoiceProposal, ProposalChoice, MultipleChoiceProposalVote, MultipleChoiceVoteChoice, and the params and edit types.
  • SDK: multipleChoiceProposal(id:), multipleChoiceProposalsByOwner, multipleChoiceProposalsByCreatedAt, multipleChoiceProposalVote, multipleChoiceProposalKeptChoice, multipleChoiceProposalMasternodeVote, multipleChoiceProposalProcessingFee, multipleChoiceProposalChoiceFundToJoin, castMultipleChoiceProposalVote.
  • ManagedPlatformWallet: createMultipleChoiceProposal, editMultipleChoiceProposal, enterMultipleChoiceProposalChoice.
  • App: a list (Mine, Newest), a detail view (choices with tallies, the result and the kept choice, the masternode vote), and forms to create, edit and enter a choice, reached from a second link on the Voting queries screen.

Kotlin SDK, rs-unified-sdk-jni and KotlinExampleApp

  • JNI: one export per C function in multiple_choice_proposals.rs, sharing the yes/no bridge's helpers.
  • Sdk.multipleChoiceProposals (fetch, listByOwner, listByCreatedAt, vote, keptChoice, masternodeVote, processingFee, choiceFundToJoin, castVote) and PlatformWalletManager.multipleChoiceProposals (create, edit, enterChoice).
  • App: a list, a create screen and a detail screen with vote, enter-a-choice and edit dialogs; routes, the link beside the yes/no one, QueryRegistry rows, PARITY.md and TEST_PLAN.md entries.

What the wrappers leave to Platform

They marshal only. What a proposal or a choice may hold, who may enter a choice and how long a proposal may be edited are checked on chain: both apps show Edit to the proposal's owner at any time and let Platform refuse it after the window, as the yes/no screens do.

Once a vote ends, the vote read no longer carries the choices' text, so the detail screens make a second read for the kept choice.

How Has This Been Tested?

  • cargo test -p platform-wallet --lib -- multiple_choice_proposal decision_proposal: 16 passed (8 new).
  • cargo test -p platform-wallet-ffi --lib -- multiple_choice_proposal decision_proposal: 14 passed (7 new).
  • cargo test -p rs-sdk-ffi --lib -- decision_proposal: 27 passed (12 new).
  • cargo test -p rs-unified-sdk-jni --lib -- multiple_choice decision_proposal: 5 passed (4 new).
  • cargo fmt --all -- --check clean; cargo clippy --all-targets on the four crates shows nothing in the touched files.
  • Kotlin: :sdk:compileDebugKotlin :app:compileDebugKotlin :sdk:compileDebugAndroidTestKotlin build; MultipleChoiceProposalModelsTest 16, MultipleChoiceProposalFormTest 10, MultipleChoiceDocumentCreateViewModelTest 5, with the yes/no DecisionProposalModelsTest 14 and CreateDecisionProposalViewModelTest 3 still passing.
  • Swift: ./build_ios.sh --target sim --profile dev, the SwiftExampleApp simulator build, and swift test on the four decision proposal test classes: 46 passed (26 new).
  • python3 scripts/check_sdk_parity_manifest.py: OK.

Not run: the Kotlin instrumented MultipleChoiceProposalsFfiTest (compiled only), either app on a device or simulator, and anything against a live network.

Breaking Changes

None.

Checklist:

  • I have performed a self-review of my own code
  • My Rust, FFI, WASM, Swift or Kotlin changes follow the coding conventions
  • 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
  • If I added or changed GroveDB structure, I described it in the area's structure.rs, regenerated grovedb-structure.json, and checked the structure viewer link posted on this pull request
  • If I added or changed a system contract's document schemas, I checked the contract viewer link posted on this pull request

For repository code-owners and collaborators only

  • I have assigned this pull request to a milestone

🤖 Generated with Claude Code

PR Hygiene · c07a7e6

  • Bots — thepastaclaw not yet — /skip-bots proceeds without the ones not yet reported
  • Self-review — post /self-reviewed
  • Reviewer requests paused — your 5 review slots are occupied; this PR is excluded from reviewers' queues. Required approvals still count without a slot.
  • Build running
  • Approvals
    • files with no dedicated owner — you own it
    • kotlin-sdk (packages/kotlin-sdk/KotlinExampleApp/TEST_PLAN.md, packages/kotlin-sdk/KotlinExampleApp/app/src/main/java/org/dashfoundation/example/navigation/AppNavHost.kt, packages/kotlin-sdk/KotlinExampleApp/app/src/main/java/org/dashfoundation/example/navigation/Routes.kt and 16 more) — HashEngineering
    • rs-platform-wallet-ffi (packages/rs-platform-wallet-ffi/src/decision_proposal.rs, packages/rs-platform-wallet-ffi/src/lib.rs, packages/rs-platform-wallet-ffi/src/multiple_choice_proposal.rs) — HashEngineering or ZocoLini or llbartekll or romchornyi
    • rs-platform-wallet (packages/rs-platform-wallet/src/wallet/identity/network/decision_proposal.rs, packages/rs-platform-wallet/src/wallet/identity/network/mod.rs, packages/rs-platform-wallet/src/wallet/identity/network/multiple_choice_proposal.rs) — HashEngineering or ZocoLini or llbartekll or romchornyi
    • rust-sdk-ffi (packages/rs-sdk-ffi/src/decision_proposal/json.rs, packages/rs-sdk-ffi/src/decision_proposal/mod.rs, packages/rs-sdk-ffi/src/decision_proposal/multiple_choice/json.rs and 2 more) — lklimek or shumkov
    • swift-sdk (packages/swift-sdk/Sources/SwiftDashSDK/PlatformWallet/ManagedPlatformWalletMultipleChoiceProposals.swift, packages/swift-sdk/Sources/SwiftDashSDK/Voting/MultipleChoiceProposal.swift, packages/swift-sdk/Sources/SwiftDashSDK/Voting/SDK+DecisionProposals.swift and 7 more) — llbartekll or romchornyi

When every merge requirement is met, the PR Hygiene check passes. Reviewer limits do not block merging; other required GitHub checks and protections still apply.

QuantumExplorer and others added 2 commits November 13, 2026 22:24
… SDKs

Multiple choice proposals had helpers in rs-sdk, wasm-sdk and js-evo-sdk but
nothing on mobile, where yes/no proposals have a full stack. This adds the
same stack for multiple choice.

rs-platform-wallet and its FFI: create a multiple choice proposal, edit it
(read once, put at the next revision, so Platform refuses an edit another one
overtook) and enter a choice. A choice goes through the ordinary document
put, which already reads and states the contest fund.

rs-sdk-ffi: the proposal, the listings by owner and by creation time, the
vote among the choices, the kept choice, a masternode's answer, the
processing fee, the fund to enter a choice, and the masternode vote (for a
choice by its document id, for none of the choices, or abstain) through the
shared MasternodeVoter.

Swift and Kotlin (through rs-unified-sdk-jni): models, reads, the vote and
the wallet writes over those functions, with example app screens beside the
yes/no ones: a list, a detail screen with the tallies, the result and the
vote, and forms to create, edit and enter a choice.

The wrappers marshal only: what a proposal or a choice may hold, who may
enter a choice and how long a proposal may be edited stay with Platform.

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

coderabbitai Bot commented Nov 13, 2026

Copy link
Copy Markdown
Contributor

Warning

Review limit reached

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

Next included review available in 1 minute.

Check out review usage here.

View limit details

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

Learn how review limits work.

Review configuration:

Run configuration

Commits

Reviewing files that changed from the base of the PR and between b2e7395 and c07a7e6.

Files selected for processing: 44

  • docs/protocol/decision-proposals.md
  • packages/kotlin-sdk/KotlinExampleApp/TEST_PLAN.md
  • packages/kotlin-sdk/KotlinExampleApp/app/src/main/java/org/dashfoundation/example/navigation/AppNavHost.kt
  • packages/kotlin-sdk/KotlinExampleApp/app/src/main/java/org/dashfoundation/example/navigation/Routes.kt
  • packages/kotlin-sdk/KotlinExampleApp/app/src/main/java/org/dashfoundation/example/ui/contracts/QueryDetailScreen.kt
  • packages/kotlin-sdk/KotlinExampleApp/app/src/main/java/org/dashfoundation/example/ui/contracts/QueryRegistry.kt
  • packages/kotlin-sdk/KotlinExampleApp/app/src/main/java/org/dashfoundation/example/ui/identity/DecisionProposalDetailScreen.kt
  • packages/kotlin-sdk/KotlinExampleApp/app/src/main/java/org/dashfoundation/example/ui/identity/MultipleChoiceProposalDetailScreen.kt
  • packages/kotlin-sdk/KotlinExampleApp/app/src/main/java/org/dashfoundation/example/ui/identity/MultipleChoiceProposalsScreen.kt
  • packages/kotlin-sdk/KotlinExampleApp/app/src/test/java/org/dashfoundation/example/ui/identity/MultipleChoiceDocumentCreateViewModelTest.kt
  • packages/kotlin-sdk/KotlinExampleApp/app/src/test/java/org/dashfoundation/example/ui/identity/MultipleChoiceProposalFormTest.kt
  • packages/kotlin-sdk/PARITY.md
  • packages/kotlin-sdk/sdk/src/androidTest/kotlin/org/dashfoundation/dashsdk/MultipleChoiceProposalsFfiTest.kt
  • packages/kotlin-sdk/sdk/src/main/kotlin/org/dashfoundation/dashsdk/Sdk.kt
  • packages/kotlin-sdk/sdk/src/main/kotlin/org/dashfoundation/dashsdk/ffi/MultipleChoiceProposalsNative.kt
  • packages/kotlin-sdk/sdk/src/main/kotlin/org/dashfoundation/dashsdk/voting/DecisionProposalModels.kt
  • packages/kotlin-sdk/sdk/src/main/kotlin/org/dashfoundation/dashsdk/voting/MultipleChoiceProposalModels.kt
  • packages/kotlin-sdk/sdk/src/main/kotlin/org/dashfoundation/dashsdk/voting/MultipleChoiceProposals.kt
  • packages/kotlin-sdk/sdk/src/main/kotlin/org/dashfoundation/dashsdk/wallet/PlatformWalletManager.kt
  • packages/kotlin-sdk/sdk/src/test/kotlin/org/dashfoundation/dashsdk/voting/MultipleChoiceProposalModelsTest.kt
  • packages/rs-platform-wallet-ffi/src/decision_proposal.rs
  • packages/rs-platform-wallet-ffi/src/lib.rs
  • packages/rs-platform-wallet-ffi/src/multiple_choice_proposal.rs
  • packages/rs-platform-wallet/src/wallet/identity/network/decision_proposal.rs
  • packages/rs-platform-wallet/src/wallet/identity/network/mod.rs
  • packages/rs-platform-wallet/src/wallet/identity/network/multiple_choice_proposal.rs
  • packages/rs-sdk-ffi/src/decision_proposal/json.rs
  • packages/rs-sdk-ffi/src/decision_proposal/mod.rs
  • packages/rs-sdk-ffi/src/decision_proposal/multiple_choice/json.rs
  • packages/rs-sdk-ffi/src/decision_proposal/multiple_choice/mod.rs
  • packages/rs-sdk-ffi/src/decision_proposal/queries.rs
  • packages/rs-unified-sdk-jni/src/decision_proposals.rs
  • packages/rs-unified-sdk-jni/src/lib.rs
  • packages/rs-unified-sdk-jni/src/multiple_choice_proposals.rs
  • packages/swift-sdk/Sources/SwiftDashSDK/PlatformWallet/ManagedPlatformWalletMultipleChoiceProposals.swift
  • packages/swift-sdk/Sources/SwiftDashSDK/Voting/MultipleChoiceProposal.swift
  • packages/swift-sdk/Sources/SwiftDashSDK/Voting/SDK+DecisionProposals.swift
  • packages/swift-sdk/Sources/SwiftDashSDK/Voting/SDK+MultipleChoiceProposals.swift
  • packages/swift-sdk/SwiftExampleApp/SwiftExampleApp/Views/DecisionProposals/MultipleChoiceProposalDetailView.swift
  • packages/swift-sdk/SwiftExampleApp/SwiftExampleApp/Views/DecisionProposals/MultipleChoiceProposalFormViews.swift
  • packages/swift-sdk/SwiftExampleApp/SwiftExampleApp/Views/DecisionProposals/MultipleChoiceProposalsView.swift
  • packages/swift-sdk/SwiftExampleApp/SwiftExampleApp/Views/PlatformQueriesView.swift
  • packages/swift-sdk/SwiftTests/SwiftDashSDKTests/MultipleChoiceProposalDecodingTests.swift
  • packages/swift-sdk/SwiftTests/SwiftDashSDKTests/MultipleChoiceProposalMarshallingTests.swift
  • Autofix · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts

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

@github-actions github-actions Bot added this to the v5.1.0 milestone Nov 13, 2026
@github-actions

github-actions Bot commented Nov 13, 2026 •

Copy link
Copy Markdown
Contributor

Waiting for bot review — thepastaclaw not yet. Wait for the missing reviews, or a writer can post /skip-bots to proceed without them; blocking findings still need addressing.
Full checklist in the description.

@github-actions github-actions Bot added the waiting-bots Waiting for the review bots to report on this head label Nov 13, 2026
@thepastaclaw

thepastaclaw commented Nov 13, 2026 •

Copy link
Copy Markdown
Collaborator

🔍 Review in progress — actively reviewing now (commit c07a7e6) · triage: normal
██████████████████░░ 92% · about 5 min left · running for 1 h 4 min
✅ triage → ✅ Phase 1 → ✅ verify 1 → ✅ gate → ✅ Phase 2 → ⏳ fresh Phase 2 (3/5 lanes) → ▫️ publish
Estimated from recent reviews of this tier · updated 17:02 UTC · live progress

This branch has not been deployed

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

Labels

waiting-bots Waiting for the review bots to report on this head

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants