Skip to content

Dash Platform v4.0.0

Latest

Choose a tag to compare

@QuantumExplorer QuantumExplorer released this 01 Jul 13:17
9f9092c

Executive Summary

Dash Platform 4.0 introduces a Shielded Pool for private credit transfers, Document Aggregation queries (count, sum, average) backed by sum trees, and a unified Platform Wallet that consolidates Core chain and Platform operations into a single Rust-native wallet stack. Significant hardening work was done across the release cycle, with the help of newer AI models, to ensure the shielded pool shipped in a stable state.


Key Improvements and Capabilities

1. Shielded Pool

Platform 4.0 adds a privacy layer for Platform credits built on a fork of the Orchard protocol. Credits can be moved into a shielded pool, transferred privately, and moved back out — all within Platform state, enforced by consensus, and verifiable through proofs.

1.1 Cryptographic Primitives

The shielded pool uses the Orchard cryptographic stack via a Dash-parameterized fork of the Zcash orchard crate (hosted in dashpay/grovedb as grovedb-commitment-tree):

  • Pallas / Vesta curves: the elliptic curve pair underlying Orchard. Note commitments and key operations use Pallas; the Halo 2 inner product argument uses Vesta
  • Sinsemilla: collision-resistant hash used for the note commitment Merkle tree (depth-32 incremental tree over cmx values). The tree root is the Orchard anchor
  • RedPallas: Schnorr-like signatures on Pallas used for per-action spend authorization and the bundle binding signature
  • Halo 2: the zero-knowledge proof system. Each shielded bundle carries a Halo 2 circuit proof demonstrating that inputs and outputs balance, nullifiers are correctly derived, and spend authorization is valid ‚Äî without revealing amounts or addresses
  • ChaCha20-Poly1305: AEAD cipher for note encryption (both the recipient ciphertext and the sender's outgoing ciphertext)
  • ZIP-32 key derivation: hierarchical deterministic key derivation at path m / 32' / coin_type' / account' (coin type 5 for mainnet, 1 for testnet)

1.2 Dash-Specific Modifications

While built on Orchard, the implementation diverges from Zcash in several ways:

  • Compact memos: Dash uses a 36-byte DashMemo (4-byte kind tag + 32-byte payload) instead of Zcash's 512-byte memo, reducing encrypted note size from ~692 bytes to 216 bytes per note
  • Platform sighash: SHA-256("DashPlatformSighash" || bundle_commitment || extra_data) instead of Zcash's transaction sighash. The extra data binds Dash-specific transparent fields (output addresses, withdrawal scripts, identity keys) to the Orchard binding signature
  • Address encoding: Bech32m with HRP dash1z (mainnet) / tdash1z (testnet), without F4Jumble or Unified Address wrappers
  • No fee bidding: shielded transitions carry no user_fee_increase ‚Äî the fee is pinned to a flat minimum so every transition of a given size pays identically, preventing fee-based fingerprinting

1.3 Key Hierarchy

Each wallet account derives an Orchard key set from the master seed:

  • Spending Key (32 bytes) ‚ derived once, then dropped
  • Spend Authorizing Key ‚ signs per-action spend authorizations (RedPallas)
  • Full Viewing Key ‚ can view all transactions; derives addresses and viewing keys
  • Incoming Viewing Key (from FVK) ‚ trial-decrypts incoming notes
  • Outgoing Viewing Key (from FVK, External scope) ‚ recovers sent notes for wallet history
  • Payment Address (43 bytes: 11-byte diversifier + 32-byte pk_d) ‚ derived via fvk.address_at(index, Scope::External)

1.4 Note Structure

Each on-chain action stores 344 bytes across two trees:

Field Size Location
cmx (note commitment) 32 bytes Notes tree (Sinsemilla leaf)
rho (nullifier derivation input) 32 bytes Notes tree
cv_net (Pedersen value commitment) 32 bytes Notes tree
Encrypted note (see below) 216 bytes Notes tree
Nullifier 32 bytes Nullifiers tree

The encrypted note (216 bytes) contains:

  • epk (32 bytes): ephemeral public key for Diffie-Hellman key agreement
  • enc_ciphertext (104 bytes): note encrypted to recipient ‚Äî 52-byte compact prefix (version + diversifier + value + rseed) + 36-byte DashMemo + 16-byte AEAD tag
  • out_ciphertext (80 bytes): encrypted to sender for OVK recovery ‚Äî 64-byte plaintext (pk_d + esk) + 16-byte AEAD tag

1.5 State Transitions

Six new state transition types support the shielded pool:

Type ID Description
Shield 15 Move credits from a Platform Address into the shielded pool
ShieldedTransfer 16 Transfer credits privately within the shielded pool (supports memos)
Unshield 17 Move credits from the shielded pool back to a Platform Address
ShieldFromAssetLock 18 Lock Dash on the payment chain and shield credits directly
ShieldedWithdrawal 19 Withdraw credits from the shielded pool to the payment chain
IdentityCreateFromShieldedPool 20 Create a new identity funded entirely from shielded credits

Each transition carries an Orchard bundle containing the Halo 2 circuit proof, value commitments, encrypted notes, nullifiers, per-action RedPallas spend-auth signatures, and a binding signature. Two bundle construction patterns exist:

  • Output-only (Shield, ShieldFromAssetLock): funds entering the pool; spends disabled, uses empty-tree anchor, padded to a 2-action minimum
  • Spend+output (all others): requires real anchor from the commitment tree and SpendableNote inputs with Merkle inclusion paths

IdentityCreateFromShieldedPool restricts exit values to a fixed set of denominations (shielded_identity_create_denominations), maximizing anonymity sets by making all identity-create transactions of a given denomination indistinguishable.

1.6 On-Chain Storage

The shielded pool lives under the ShieldedBalances root tree in GroveDB, which is itself a SumTree providing aggregate balance cross-checks. A single credit pool (keyed "M") contains five subtrees, deliberately positioned across the byte-key space so GroveDB's AVL balancing places the highest-traffic subtree at the root:

                      [128] NOTES            ‚Üê root (touched every sync/spend)
                      /          \
          [64] NULLIFIERS    [192] ANCHORS_IN_POOL
           /        \
  [32] TOTAL    [96] BY_HEIGHT
Key Name GroveDB Type Purpose
128 Notes CommitmentTree (chunk power 11 = 2048 items/chunk) Sinsemilla incremental Merkle tree storing all note commitments and encrypted payloads
64 Nullifiers ProvableCountTree Permanent set of spent nullifiers — insertion uses "known not to exist" for atomic double-spend rejection
192 Anchors-in-pool NormalTree (anchor_bytes ‚ block_height) O(1) membership check at spend time
96 Anchors-by-height NormalTree (block_height ‚ anchor_bytes) Reverse index for pruning and most-recent anchor derivation via limit 1 reverse query
32 Total balance SumItem<i64> Running credit total, cross-checked against the parent SumTree aggregate

Anchors are recorded per-block only when the commitment tree root changes. Pruning removes old anchors by height but always preserves at least one entry to prevent freezing spends.

1.7 Client Synchronization

Clients synchronize shielded state by downloading note commitments and encrypted payloads in chunked ranges via six gRPC endpoints:

  • getShieldedEncryptedNotes: paginated note fetch by (start_index, count), supporting up to 4 MMR chunks per query (8192 notes per request at protocol version 12)
  • getShieldedNotesCount: leaf count in the commitment tree for sync progress tracking
  • getShieldedAnchors / getMostRecentShieldedAnchor: anchor retrieval for spend construction
  • getShieldedPoolState: total shielded pool balance
  • getShieldedNullifiers: batch nullifier spend-status check

The SDK sync pipeline uses a sliding window of up to 4 concurrent in-flight chunk fetches with a reorder buffer that guarantees strictly ascending emission order. Trial decryption runs per-chunk using the wallet's Incoming Viewing Key (try_compact_note_decryption) to identify notes belonging to the wallet. The Outgoing Viewing Key enables senders to decrypt their own outgoing notes for full send-history reconstruction.

1.8 Fee Handling and Conservation

Shielded operations incur fees composed of two parts:

  • Compute fee: proof_verification_fee + num_actions √ó per_action_processing_fee (covers Halo 2 verification and RedPallas signature checks that GroveDB metering cannot see)
  • Storage fee: num_actions √ó 344 bytes √ó per_byte_rate (344 = 312 note bytes in the CommitmentTree + 32 nullifier bytes)

Certain transitions add flat surcharges: Unshield adds 222 bytes for the address balance write; ShieldedWithdrawal adds 4100 bytes for the Core withdrawal document; IdentityCreateFromShieldedPool adds the standard identity creation cost.

Credit conservation is enforced through distinct execution paths per transition type:

  • Pool-paid transitions (ShieldedTransfer, Unshield, ShieldedWithdrawal): fees are embedded in the ZK-proven value_balance. The binding signature proves Œ£ inputs = Œ£ outputs + fees
  • Shield (transparent entry): address inputs fund the transition; GroveDB meters real storage writes; compute fee added as a fixed surcharge
  • ShieldFromAssetLock: asset lock value fully consumed into system credits, then split between shield amount, surplus output, and fees
  • IdentityCreateFromShieldedPool: new identity receives the full denomination debited from the pool, then metered write cost + compute fee is deducted from the new identity's balance

All fee calculations use checked arithmetic throughout. The ShieldedBalances root tree being a GroveDB SumTree provides an additional cross-check on the total shielded balance.


2. Document Aggregation

Platform 4.0 adds native count, sum, and average queries on document collections, backed by GroveDB sum trees. All aggregation results are cryptographically provable — light clients can verify them against the Tenderdash commit without trusting the server.

2.1 Schema Flags

Document types opt into aggregation via four flags on DocumentTypeV2:

Flag Effect
documentsCountable: true Primary-key tree becomes a CountTree with O(1) total count
rangeCountable: true (implies countable) Upgrades to ProvableCountTree with per-node counts for O(log n) range counts
documentsSummable: "<property>" Primary-key tree becomes a SumTree with O(1) total sum of the named integer property
rangeSummable: true (requires summable) Upgrades to ProvableSumTree with per-node sums for O(log n) range sums

Average requires no separate flag — it composes count and sum. When both are set, the tree becomes a CountSumTree carrying both metrics.

These flags compose orthogonally, producing 9 distinct GroveDB tree variants:

                    not summable        summable          range summable
not countable       NormalTree          SumTree           ProvableSumTree
countable           CountTree           CountSumTree      CountProvableSumTree
range countable     ProvableCountTree   ProvableCountProvableSumTree
                                                          (+ ProvableCountSumTree)

The same flags can be applied per-index, enabling targeted aggregation on specific query paths (e.g., "count of documents WHERE class = 'Math'" on an index covering class).

2.2 Query Surface

Two query layers expose aggregation:

  • GetDocumentsCount / GetDocumentsSplitCount: dedicated count endpoints with proof support
  • getDocuments v1: a unified SQL-shaped endpoint supporting:
    • SELECT COUNT(*), SELECT SUM(field), SELECT AVG(field)
    • WHERE clauses with typed operators (in, range comparisons)
    • GROUP BY modes: Aggregate (no grouping, single scalar), GroupByIn (one result per In value), GroupByRange (one per distinct range value), GroupByCompound (one per (In-value, range-value) pair)
    • HAVING clauses for post-aggregation filtering

Averages are returned as (count, sum) pairs — the client divides for full precision rather than accepting a server-rounded result.

2.3 Proof Verification

Every aggregation result is provable through GroveDB's tree structure. The proof verification layer provides dedicated verifiers for each shape:

  • Count proofs: total count (O(1) from tree root), range aggregate, point lookup (Equal/In), distinct-key walk, compound carrier
  • Sum proofs: total sum (O(1) from tree root), range aggregate, point lookup, distinct-key walk, compound carrier
  • Average proofs: combined count+sum from AggregateCountAndSumOnRange, total from CountSumTree root, per-key and compound shapes

The prover and verifier share compiled DriveDocumentCountQuery / DriveDocumentSumQuery objects, ensuring path-query bytes match byte-for-byte across the network.


3. Platform Wallet

Platform 4.0 replaces the previous scattered wallet logic with a unified rs-platform-wallet crate that manages both Core chain operations and Platform state in one stack. The implementation spans three crates: the core library, a SQLite persistence backend, and a C-compatible FFI layer for iOS integration.

3.1 Architecture

The wallet is organized around a single PlatformWallet handle backed by domain-specific sub-wallets:

Sub-wallet Responsibility
CoreWallet SPV chain sync, BIP-44 HD accounts, UTXO tracking, transaction building and broadcasting (via SPV P2P or DAPI gRPC)
IdentityWallet Identity lifecycle (register, top-up, transfer, withdraw, update keys), DashPay (contact requests, profiles, payments, DPNS names), and all token operations (transfer, mint, burn, freeze, unfreeze, claim, purchase, set-price, pause, resume, destroy-frozen, update-config)
PlatformAddressWallet DIP-17 platform address pools, auto/explicit input selection, fund-from-asset-lock, transfer, and withdrawal
ShieldedWallet Orchard ZIP-32 key derivation, shielded sync, note selection, Halo 2 proving, all 6 shielded transitions
AssetLockManager Asset lock lifecycle from transaction build through InstantSend/ChainLock confirmation — shared across all sub-wallets via Arc

A PlatformWalletManager coordinates these sub-wallets and runs background sync loops for SPV, platform address balances (BLAST), identity state, and shielded notes.

3.2 State Model

All mutable state lives in PlatformWalletInfo behind Arc<RwLock<WalletManager>>:

  • Core accounts, UTXOs, address pools
  • Arc<WalletBalance> with lock-free atomics for UI reads (separate from the RwLock contention path)
  • IdentityManager holding an IndexMap<Identifier, ManagedIdentity> with per-identity DashPay state, sync tracking, and primary selection
  • Tracked asset locks with lifecycle status

PlatformWallet is cheaply cloneable (shared handle, not independent copy).

3.3 Changeset Persistence

Operations produce delta-based PlatformWalletChangeSet structs (never full snapshots) containing sub-changesets for each domain:

  • CoreChangeSet: transaction records, UTXO adds/removes, InstantSend locks, height watermarks
  • IdentityChangeSet: identity entries with keys, DPNS names, DashPay profiles, status
  • ContactChangeSet: sent/received requests, established contacts
  • AssetLockChangeSet: tracked asset lock entries with status
  • PlatformAddressChangeSet: address balances, sync state
  • TokenBalanceChangeSet: per-(identity, token) balances
  • ShieldedChangeSet: Orchard note/nullifier state

Changesets use last-write-wins merge semantics and are atomically applied and flushed.

3.4 SQLite Storage

The rs-platform-wallet-storage crate provides a SQLite-backed persister (WAL journal, NORMAL synchronous) with:

  • 23 tables with ON DELETE CASCADE foreign keys, anchored at wallet_metadata. Covers: wallet metadata, account registrations, address pools, core transactions, UTXOs, instant locks, derived addresses, sync state, identities, identity public keys, asset locks, token balances, contacts, contact requests, DashPay profiles, platform addresses, platform address sync snapshots
  • Per-object KV metadata (meta_* tables) for app-managed annotations
  • Hard secrets boundary: no private keys, mnemonics, or seeds ever touch SQLite. The secrets module provides a SecretStore with two backends: Argon2id + XChaCha20-Poly1305 encrypted-file vault and OS keyring
  • Schema migrations via refinery, with automatic backup before destructive operations
  • Cross-process locking (BEGIN EXCLUSIVE) for wallet restore operations
  • Secret scanning to prevent accidental key material in non-secret columns
  • Operational tooling: CLI with migrate/backup/restore/prune subcommands

Transient persistence failures preserve the changeset buffer for retry; fatal failures drop it.

3.5 SPV Integration

The Core chain is synced via an integrated SPV client (DashSpvClient) managed through the wallet's SpvRuntime. SPV events (new blocks, transactions, InstantSend locks, ChainLocks) flow through a PlatformEventManager using ArcSwap for lock-free dynamic handler registration. The SPV client runs in its own async runtime to prevent deadlocks with the Platform wallet's main runtime.

3.6 Key Management

Identities follow DIP-13/14/15 derivation paths. A MnemonicResolverHandle callback vtable lets Swift-side code provide the mnemonic only when needed — it is held in Zeroizing buffers and scrubbed after use. The KeychainSigner delegates signing to the iOS Keychain so private keys are never held in wallet memory. External signable wallets allow signing through caller-provided resolvers for hardware wallet integration.

Private keys are zeroized after use in all FFI crypto operations.

3.7 FFI Layer

60+ C-compatible FFI functions in rs-platform-wallet-ffi expose the full wallet surface to Swift/iOS: wallet creation and restore, SPV lifecycle, identity management, contact system, core transactions, asset locks, platform address sync, token operations, shielded operations, and signing with mnemonic resolver. Handle-based resource management with thread-safe storage and proper destroy functions.


4. Document History

Platform 4.0 adds document history retrieval, allowing clients to query previous revisions of a document. This is available for document types on contracts that have opted into history keeping (keepsHistory: true).


5. Additional Improvements

5.1 Protocol Version 12

Platform 4.0 activates protocol version 12, which gates the shielded pool, document aggregation, and several other features behind a consensus-enforced version boundary.

5.2 SDK

  • JS/WASM SDK (js-evo-sdk): comprehensive refactoring, shielded pool bindings, document aggregation facade (count/sum/average), token-paid document support, first-class devnet support, state transition broadcast results
  • Rust SDK: shielded pool support, auto-detected protocol version from network metadata, proven protocol version refresh, UNIMPLEMENTED response failover, rate-limit-aware node banning with Envoy reset window
  • Swift SDK: full iOS wallet experience with shielded pool UI, QR code scanning, document CRUD, token operations, DashPay, and TestFlight distribution as "Dash Developer Pro"

5.3 Hardening

Extensive hardening across the release cycle:

  • Fee/credit conservation checks on all shielded paths with checked arithmetic throughout
  • Private key zeroization in FFI crypto operations
  • Heap corruption, double-free, and Vec capacity UB fixes across FFI boundaries
  • Panic-to-error conversions in consensus-critical paths (SetPrices, quorum parsing, storage-fee refund, index property parsing)
  • Idempotent shielded snapshot ingest for crash recovery across InitChain retries
  • Proof verification tightened for shielded, address, and identity-create operations
  • Correct OVK encryption so senders can read their own note history
  • Note reservation safety on ambiguous broadcast failures

5.4 Dashmate

  • BIP158 compact-filter index enabled by default across all presets
  • Tenderdash 1.6.0
  • Envoy gateway bumped to 1.35.11 for CVE-2026-47774 (HTTP/2 DoS)
  • Network-specific port differentiation to avoid conflicts

5.5 Test Coverage

Over 150 test PRs were merged during this cycle, substantially increasing coverage across Drive, Drive-ABCI, DPP, the proof verification layer, and platform-value. The test suite was reorganized with sccache-backed sharded execution, self-hosted Mac runners, and nightly-only long-running tests.

What's Changed

Features

Shielded Pool (Orchard / Medusa)

  • feat(dpp): shielded state transitions and Orchard bundle types (Medusa) by @QuantumExplorer in #3177
  • feat(drive): add shielded pool storage, actions, and verification (Medusa part 2) by @QuantumExplorer in #3198
  • feat(drive-abci): add shielded pool drive-abci integration (medusa part 3) by @QuantumExplorer in #3220
  • feat(platform): add shielded pool query layer (medusa part 4) by @QuantumExplorer in #3228
  • feat(rs-sdk): add shielded pool SDK support by @QuantumExplorer in #3230
  • feat(rs-sdk-ffi): add shielded pool FFI bindings with BLAST sync and transitions by @QuantumExplorer in #3239
  • feat(wasm-sdk)!: add shielded pool WASM bindings and query methods by @QuantumExplorer in #3235
  • feat(swift-sdk): add full shielded pool (ZK) support for iOS by @QuantumExplorer in #3348
  • feat: seed Orchard shielded pool at genesis with fast, observable sync by @shumkov in #3732
  • feat(drive): shielded fees for Shield/ShieldFromAssetLock + shield credit conservation by @QuantumExplorer in #3793
  • feat: add IdentityCreateFromShieldedPool state transition (shielded-pool-funded identity creation) by @QuantumExplorer in #3816
  • feat: shielded scan-based spend detection and OVK outgoing-note history by @QuantumExplorer in #3819
  • feat(platform): send memos with shielded transfers by @QuantumExplorer in #3836
  • feat: shielded funding from asset-lock proofs by @shumkov in #3753
  • feat(platform): add GetShieldedNotesCount query for sync progress by @QuantumExplorer in #3769
  • feat(drive-abci): gate shielded-pool seeding behind shielded_test_data feature by @shumkov in #3774
  • feat(drive-abci, sdk)!: allow shielded-notes queries to span 4 MMR chunks by @QuantumExplorer in #3756
  • feat(platform): shielded transaction history by @QuantumExplorer in #3870
  • feat(dpp): add getters and setters for new shielded state transitions by @QuantumExplorer in #3879
  • fix(dpp): enforce sum(inputs) >= amount in shield transition by @QuantumExplorer in #3240
  • fix(dpp): validate encrypted_note length in structure validation by @QuantumExplorer in #3368
  • fix(dpp): add upper fee bound to unshield and withdrawal builders by @QuantumExplorer in #3364
  • fix(dpp): bind unshielding_amount to sighash in client builders by @QuantumExplorer in #3362
  • fix(dpp): reduce max_shielded_transition_actions from 100 to 16 (#3411) by @lklimek in #3498
  • fix(drive): credits-not-balanced from shielded nullifier metadata by @QuantumExplorer in #3624
  • fix(drive,drive-abci): retire SHIELDED_MOST_RECENT_ANCHOR_KEY; derive most-recent from [8] and never empty it by @QuantumExplorer in #3605
  • fix(drive,drive-abci): post-merge follow-ups for shielded anchor refactor by @QuantumExplorer in #3606
  • fix(drive): rebalance shielded credit pool subtree keys by access frequency by @QuantumExplorer in #3607
  • fix(drive): charge fees for unshield and shielded withdrawal by @QuantumExplorer in #3800
  • fix(drive): strict merged-query verification for unshield & shielded withdrawal proofs by @QuantumExplorer in #3814
  • fix(drive): unify shielded pool genesis/upgrade construction to prevent state divergence by @shumkov in #3801
  • fix(drive): verify identity-create-from-shielded-pool results without unbounded terminal-key queries by @QuantumExplorer in #3859
  • fix(drive): unify shielded per-action processing fee across all protocol versions by @QuantumExplorer in #3877
  • fix(drive-abci): use checked arithmetic in shielded fee calculation by @QuantumExplorer in #3365
  • fix(drive-abci): use checked_sub for ShieldFromAssetLock fee computation by @QuantumExplorer in #3366
  • fix(drive-abci): make shielded snapshot ingest idempotent across InitChain retries by @shumkov in #3824
  • fix(platform): encrypt shielded outputs to the sender's outgoing viewing key by @QuantumExplorer in #3839
  • fix(platform): derive shielded identity-create id from the padded bundle's published nullifiers by @QuantumExplorer in #3843
  • fix(platform-wallet): keep note reservations on ambiguous shielded spend confirmation failures by @QuantumExplorer in #3863
  • fix(platform-wallet): align shielded_sync example with filler-only seeded notes by @QuantumExplorer in #3832
  • fix: build shielded FFI load path under --all-features by @QuantumExplorer in #3826

Document Aggregation (Count / Sum / Average)

  • feat(platform): document count index by @shumkov in #2516
  • feat(dpp): add documents_countable to DocumentTypeV2 for O(1) total document counts by @QuantumExplorer in #3457
  • feat(platform): add GetDocumentsCount and GetDocumentsSplitCount queries by @QuantumExplorer in #3435
  • feat(platform)!: verifiable, bounded count queries on a unified endpoint by @QuantumExplorer in #3623
  • feat(platform): getDocuments v1 ‚Äî SQL-shaped select + count surface by @QuantumExplorer in #3633
  • feat(drive): expand count-index group-by carrier shapes (G1a/G1b/G8a-c) by @QuantumExplorer in #3652
  • feat(drive): document sum + average proof primitives, with SDK fan-out scaffolding and reproducible benchmarks by @QuantumExplorer in #3661
  • feat(sdk): expose document count/sum/average aggregates in js-evo-sdk facade by @PastaPastaPasta in #3767
  • feat(sdk): implement document sum/average aggregation FFI (DOC-13/14) by @QuantumExplorer in #3935

Platform Wallet

Document History

Platform Address & Funding

  • feat(rs-sdk): implement incremental address balance synchronization by @QuantumExplorer in #3152
  • feat(sdk): add platform address transition WASM bindings by @shumkov in #3147
  • feat(platform)!: update PlatformAddress encoding and HRP constants by @QuantumExplorer in #3059
  • feat: identity registration with asset-lock proofs by @shumkov in #3634
  • feat: platform-address funding from asset-lock proofs by @shumkov in #3671

SDK (JS / WASM / Rust)

  • feat(sdk)!: state transition broadcast result for Evo SDK by @shumkov in #3077
  • feat(sdk): retry the wait for result request on deadline exeeded by @shumkov in #3035
  • feat(sdk): token config update JS binding by @shumkov in #3038
  • feat(sdk): auto-detect protocol version from network response metadata by @lklimek in #3483
  • feat(sdk): source mainnet/testnet bootstrap from dash-network-seeds (backport #3533) by @Claudius-Maginificent in #3570
  • feat(rs-sdk): implement getTokenPreProgrammedDistributions query by @lklimek in #3246
  • feat(rs-sdk-ffi): expose optional platform_version in DashSDKConfig by @Claudius-Maginificent in #3751
  • feat(rs-sdk-ffi): expose dash_sdk_signer_can_sign for signer-delegated key preflight by @QuantumExplorer in #3924
  • feat(rs-sdk-ffi): add masternode contested-resource vote broadcast (FFI + Swift UI) by @QuantumExplorer in #3883
  • feat(rs-platform-wallet-ffi): expose devnet name and LLMQ_DEVNET override in spv_start by @QuantumExplorer in #3758
  • feat(wasm-sdk): first-class devnet support with trusted-context prefetch by @PastaPastaPasta in #3748
  • feat(wasm-sdk): add token-paid document support by @PastaPastaPasta in #3599
  • feat(wasm): add pre-flight check for wasm-bindgen-cli version by @QuantumExplorer in #3094
  • feat(dpp)!: convert Signer trait to async by @QuantumExplorer in #3492
  • feat(dpp): add max_asset_lock_transaction_inputs limit to prevent stuck funds by @lklimek in #3491
  • feat: record DAPI address ban reason and expose via platform-wallet FFI by @QuantumExplorer in #3890
  • feat(dapi): add method to retrieve all non-banned endpoints by @lklimek in #3072

Swift SDK / iOS Example App

Drive & GroveDB

  • feat(drive): allow deleting non-empty trees in targeted batch operations by @QuantumExplorer in #3210
  • feat(drive): bump grovedb and expose key_exists_as_boundary for pagination by @QuantumExplorer in #3373
  • feat(drive): add paginated fetch_contract_ids and fetch_contracts by @QuantumExplorer in #3480
  • feat(drive-abci): debugging tool to replay abci requests by @lklimek in #2862

Dashmate

  • feat(dashmate): default-on the BIP158 compact-filter index across all presets by @QuantumExplorer in #3587
  • feat(dashmate): add Tenderdash 1.6 allowlistOnly option by @lklimek in #3067
  • feat(dashmate): configure docker build args via config by @shumkov in #3764

Other Features


Bug Fixes

SDK (JS / WASM / Rust)

  • fix(sdk)!: getSignableBytes is not compatible with sign and verify by @shumkov in #3048
  • fix(sdk): inconsistent document query operator by @shumkov in #3039
  • fix(sdk): deserialization error due to outdated contract cache by @shumkov in #3052
  • fix(sdk): outdated platfrom version in JS SDK by @shumkov in #3046
  • fix(sdk): missing getSignedBytes method by @shumkov in #3073
  • fix(sdk): use string keys instead of object keys in JavaScript Maps by @shumkov in #3145
  • fix(sdk): remove unused Sdk::parse_proof and Sdk::parse_proof_with_metadata by @thepastaclaw in #3141
  • fix(sdk): propagate PutSettings in token freeze/mint/unfreeze/set_price transitions by @thepastaclaw in #3132
  • fix(sdk): prevent sized_integer_types config downgrade that breaks document by @shumkov in #3071
  • fix(sdk): remove Document fetch_many override referencing removed parse_proof by @shumkov in #3179
  • fix(sdk): default to nonce 0 for first-time identity/contract interactions by @lklimek in #3170
  • fix(sdk): use deterministic identity ID in address funding proof verification by @thepastaclaw in #3208
  • fix(sdk): add custom deallocator to signer vtable for FFI safety by @QuantumExplorer in #3304
  • fix(sdk): remove unsound catch_unwind on raw pointer dereference in FFI by @QuantumExplorer in #3299
  • fix(sdk): remove unsafe Copy derive and fix ContextProviderWrapper leak by @QuantumExplorer in #3301
  • fix(sdk): add unified dash_sdk_result_free to prevent memory leaks by @QuantumExplorer in #3298
  • fix(sdk): replace env::set_var with direct filter in FFI logging setup by @QuantumExplorer in #3302
  • fix(sdk): add Regtest support to trusted context provider activation height by @QuantumExplorer in #3464
  • fix(sdk): default initial protocol version to 10 when unpinned (upgrade-safe ratchet floor) by @Claudius-Maginificent in #3809
  • fix(sdk): refresh SDK protocol version to the network's on startup and network switch by @QuantumExplorer in #3886
  • fix(sdk): refresh protocol version via a proven query, not unproved getStatus by @QuantumExplorer in #3893
  • fix(sdk): ban node and retry elsewhere on UNIMPLEMENTED responses by @QuantumExplorer in #3875
  • fix(sdk): verify quorum signature on broadcast wait-path before trusting metadata by @Claudius-Maginificent in #3872
  • fix(sdk): default to latest protocol version instead of pinning testnet to v1 by @shumkov in #3937
  • fix(sdk): ban rate-limited node for Envoy-advertised reset window by @Claudius-Maginificent in #3951
  • fix(sdk): forward wasm grpc-web trailers to tonic by @thepastaclaw in #3726
  • fix(sdk): wallet-flow network fixes for SwiftExampleApp by @QuantumExplorer in #3772
  • fix(sdk): emits incompatible getDocuments wire against pre-v3.1 networks (QueryContext approach) by @lklimek in #3711
  • fix(rs-sdk): withdrawals orderBy bug (#2409) by @QuantumExplorer in #3536
  • fix(rs-sdk): case-insensitive .dash suffix in DPNS name resolution by @Claudius-Maginificent in #3914
  • fix(rs-sdk-ffi): prevent heap corruption from Vec capacity mismatch in FFI by @QuantumExplorer in #3289
  • fix(rs-sdk-ffi): shrink signature allocation to len before leaking (capacity UB) by @QuantumExplorer in #3798
  • fix(rs-sdk-ffi): fix double-free in address result free functions by @QuantumExplorer in #3338
  • fix(rs-sdk-ffi): fix Vec capacity mismatch across FFI boundary by @QuantumExplorer in #3339
  • fix(rs-sdk-ffi): zeroize private key arrays after use in crypto/signer FFI by @QuantumExplorer in #3433
  • fix(rs-sdk-ffi): stop labeling unclassified SDK errors as "failed to fetch balances" by @QuantumExplorer in #3878
  • fix(rs-sdk-ffi): classify DAPI transport and timeout errors instead of internalError by @QuantumExplorer in #3916
  • fix(rs-sdk-ffi): run blocking FFI calls on a large stack to prevent proof-verification stack overflow by @QuantumExplorer in #3896
  • fix(rs-sdk-ffi): don't export ContestedResourceVoteChoiceFFI to the C header by @QuantumExplorer in #3892
  • fix(wasm-sdk): increment address nonce in identity_create_from_addresses by @lklimek in #3084
  • fix(wasm-sdk): label getTokenContractInfo parameter as tokenId, not contractId by @thephez in #3851
  • fix(wasm-sdk): support binary grove path elements by @PastaPastaPasta in #3657
  • fix(wasm-sdk): preserve user-supplied addresses in withTrustedContext by @thepastaclaw in #3912

DPP (Dash Platform Protocol)

  • fix(dpp): add missing #[test] attribute to should_set_empty_schema_defs by @thepastaclaw in #3101
  • fix(dpp): add toJSON() serialization to TokenContractInfoWasm by @thepastaclaw in #3089
  • fix(dpp)!: enforce bincode byte-budget limit on enum deserialization by @QuantumExplorer in #3223
  • fix(dpp): add additionalProperties: false to document meta-schema by @QuantumExplorer in #3475
  • fix(dpp): bind token config update action_id to payload value (v1) by @QuantumExplorer in #3346
  • fix(dpp): bind SetPriceForDirectPurchase action_id to full pricing schedule by @QuantumExplorer in #3357
  • fix(dpp): populate transferred_at in random_document_with_params when required by @QuantumExplorer in #3517
  • fix(dpp): correct misleading non-mainnet minimum-interval error message by @lklimek in #3668
  • fix(dpp): block pre-programmed distribution changes on token update by @PastaPastaPasta in #3461
  • fix(dpp): harden nested document-property position parsing by @QuantumExplorer in #3857
  • fix(dpp): reject empty token pricing schedules to prevent a direct-purchase chain halt by @QuantumExplorer in #3865
  • fix(dpp): enforce byte array encoding stability in data contract updates by @QuantumExplorer in #3868
  • fix(dpp): guard index property parsing against empty/oversized maps to prevent check_tx panic by @QuantumExplorer in #3866
  • fix(dpp)!: make platform/orchard address decoders network-agnostic by @Claudius-Maginificent in #3781
  • fix(dpp): return error instead of panicking on storage-fee refund div-by-zero by @QuantumExplorer in #3799
  • fix(dpp): remove erroneous keywords field from document-meta schema and fix contract keywords docs by @thephez in #3471
  • fix(dpp): use DIP-0002 version 3 in asset-lock tx fixtures by @QuantumExplorer in #3621

Drive & Drive-ABCI

  • fix(drive): prevent overflow in SetPrices direct purchase pricing by @QuantumExplorer in #3292
  • fix(drive): return error instead of panicking on empty SetPrices direct purchase by @QuantumExplorer in #3856
  • fix(drive): eliminate panic in grovedb operations logging under concurrent execution by @thepastaclaw in #3142
  • fix(drive): verify root hash consistency in double-proof identity lookup by @QuantumExplorer in #3341
  • fix(drive): error on unexpected element type in anchor retrieval by @QuantumExplorer in #3369
  • fix(drive): handle malicious quorum_type without panicking by @QuantumExplorer in #3288
  • fix(drive): add bounds check for i64 cast in token balance addition by @QuantumExplorer in #3295
  • fix(drive): replace silent epoch u16 truncation with checked conversion by @QuantumExplorer in #3293
  • fix(drive): correct fee/credit accounting on the address-funding asset-lock penalty path by @QuantumExplorer in #3818
  • fix(drive): return empty history for contracts that don't keep history by @QuantumExplorer in #3884
  • fix(drive): consolidate historical contract proof verification retry logic by @thepastaclaw in #3165
  • fix(drive-abci): guard purpose cast overflow in identities_contract_keys query by @QuantumExplorer in #3275
  • fix(drive-abci): add input bounds to batch query endpoints by @QuantumExplorer in #3296
  • fix(drive-abci): swap operands in core-sync chain lock height check by @QuantumExplorer in #3518
  • fix(drive-abci): bill batch transformer drive reads by @shumkov in #3670
  • fix(drive-abci): correct DECRYPTION bounds branch + bill grovedb reads in bounds validation by @QuantumExplorer in #3697
  • fix: paid/unpaid classification for invalid batch transitions by @shumkov in #3616
  • fix: grovedb incompatibilty issues by @shumkov in #3789

Platform Wallet

Swift SDK / iOS Example App

  • fix(swift-sdk): add form validation helpers and stabilize example app tests by @alfie-dash in #3030
  • fix(swift-sdk): fix spv usage by @ZocoLini in #3026
  • fix(swift-sdk): made executeAsync generic implement Sendable by @ZocoLini in #3058
  • fix(swift-sdk): fixed dUplicated symbols issue in BaseViewModel by @ZocoLini in #3074
  • fix(swift-sdk): eliminate Swift compiler warnings that fail CI by @QuantumExplorer in #3171
  • fix(swift-sdk): use platform-wallet-ffi headers instead of hardcoded function signatures by @ZocoLini in #3500
  • fix(swift-sdk): fixed ios app transaction display by @ZocoLini in #3081
  • fix(swift-sdk): fixed wallet balance calculation by @ZocoLini in #3082
  • fix(swift-sdk): update SPVSyncState methods to match dash-spv implementation by @ZocoLini in #3378
  • fix(swift-sdk): pending transaction display by @ZocoLini in #3447
  • fix(swift-sdk): fix transaction list view not showing new transactions by @ZocoLini in #3574
  • fix(swift-sdk): drop transitive keypath from PersistentTxo unspent prefetch by @llbartekll in #3691
  • fix(swift-sdk): reconcile spending tx <-> spent TXO + tx-detail UX + dashcore bump by @QuantumExplorer in #3581
  • fix(swift-sdk): persist addresses derived by gap-limit extension by @QuantumExplorer in #3582
  • fix(swift-sdk): scope UI by active network and add multi-wallet recovery sheet by @QuantumExplorer in #3583
  • fix(swift-sdk): contracts integration polish by @llbartekll in #3604
  • fix(swift-sdk): sort transfer outputs lexicographically before ReduceOutput by @llbartekll in #3752
  • fix(swift-sdk): load address pools into the wallet after restart by @ZocoLini in #3686
  • fix(swift-sdk): attribute shielded registration errors to the right step and keep unconfirmed broadcasts safe by @QuantumExplorer in #3862
  • fix(swift-sdk): fixed mempool tx categorization after restart by @ZocoLini in #3777
  • fix(swift-sdk): freeze failed registration step at the failure instant by @QuantumExplorer in #3854
  • fix(swift-sdk): show shielded funding steps and real fee estimate by @QuantumExplorer in #3845
  • fix(swift-sdk): example app ask if the spv is running directly instead of using the sync state by @ZocoLini in #3821
  • fix(swift-sdk): make Document transition contract/type pickers idb-drivable by @QuantumExplorer in #3921
  • fix(swift-sdk): sign document state transitions with an available AUTHENTICATION key by @QuantumExplorer in #3922
  • fix(swift-sdk): parse getIdentitiesTokenBalances NSNumber result by @QuantumExplorer in #3943
  • fix(swift-sdk): seed testnet at the per-network protocol-version floor instead of pinning PV11 by @QuantumExplorer in #3944
  • fix(swift-sdk): keychain privk storage by @ZocoLini in #3946
  • fix(swift-example-app): hold one PlatformWalletManager per network by @QuantumExplorer in #3591
  • fix(swift-example-app): point regtest+docker SPV at dashmate seed port by @QuantumExplorer in #3589
  • fix(swift-example-app): receive address always picks BIP44 account over BIP32 by @QuantumExplorer in #3600
  • fix(swift-example-app): show real platform balance on Send screen by @QuantumExplorer in #3602
  • fix(swift-example-app): route orphan recovery to per-network managers by @QuantumExplorer in #3612
  • fix(swift-example-app): crash when switching to devnet in settings by @QuantumExplorer in #3394
  • fix(swift-example-app): show success title on DPNS registration alert by @QuantumExplorer in #3873
  • fix(swift-example-app): harden Withdraw Credits amount + address validation by @QuantumExplorer in #3907
  • fix(swift-example-app): prevent UInt64.max overflow crash in TransferCreditsView amount parsing by @QuantumExplorer in #3909
  • fix(swift-example-app): expose Picker options to UI automation by @QuantumExplorer in #3903
  • fix(swift-example-app): fix compile timeout on on newest xcode versions by @ZocoLini in #3899
  • fix(swift-example-app): load document price on Purchase sheet appear by @QuantumExplorer in #3947
  • fix(swift-example-app): stamp firstSeen at insert for unconfirmed transactions by @QuantumExplorer in #3874
  • fix(swift-sdk): show combined wallet balance on Wallets list by @QuantumExplorer in #3537
  • fix: use header file to define platform-wallet-ffi public ABI and force swift-sdk to use it by @ZocoLini in #3553
  • fix(ffi): post-merge compilation fixes for iOS SDK by @QuantumExplorer in #3159
  • fix(swift-sdk): serialize SwiftData ModelContext access from FFI callbacks by @QuantumExplorer in #3558

DAPI & Networking

  • fix(dapi): files generated outside sandbox by @lklimek in #3056
  • fix(dapi): use deterministic keys in subscribeToNewTransactions test to prevent bloom filter false positives by @thepastaclaw in #3160
  • fix(rs-dapi): remove unused functions and unnecessary cast by @QuantumExplorer in #3253
  • fix(rs-dapi): correct RPC error code to DapiError mapping by @QuantumExplorer in #3316
  • fix(rs-dapi,sdk): decode base64 CBOR error messages from Tenderdash by @lklimek in #3350

Dashmate

  • fix(dashmate): bump systeminformation and ajv to fix npm audit failures by @thepastaclaw in #3139
  • fix(dashmate)!: differentiate service ports between networks to avoid conflicts by @lklimek in #3085
  • fix(dashmate): lower HP node RAM requirement to 7.3GB by @ktechmidas in #3153
  • fix(dashmate): prevent orphaned verification container blocking SSL renewal by @ktechmidas in #3162
  • fix(dashmate): bump Envoy gateway to 1.35.11 for HTTP/2 DoS (CVE-2026-47774) by @shumkov in #3794
  • fix(dashmate): use active_dkgs for safe DKG stop by @thepastaclaw in #3941

Other Fixes


Refactoring

  • refactor(sdk)!: comprehensive Evo SDK refactoring by @shumkov in #2999
  • refactor(sdk)!: fix type inconsistencies across wasm-sdk and js-evo-sdk by @shumkov in #3047
  • refactor(sdk): get rid of static trusted contexts by @shumkov in #3043
  • refactor(sdk): rewrite NonceCache with LRU eviction, drift detection, and structured errors by @lklimek in #3111
  • refactor(sdk): per-network protocol-version floor + version_pinned unification by @Claudius-Maginificent in #3900
  • refactor(rs-sdk): async AddressProvider callbacks by @shumkov in #3495
  • refactor(dpp): extract user_fee_increase from StateTransitionLike into its own trait by @QuantumExplorer in #3183
  • refactor(dpp)!: cleanup and unify JSON/Object conversion by @shumkov in #3167
  • refactor(drive): unify AVG no-prove dispatch into a single count+sum walk by @QuantumExplorer in #3690
  • refactor(drive): remove dead deduct_from_prefunded_specialized_balance dispatcher by @QuantumExplorer in #3508
  • refactor: structure v1 getDocuments where/order_by/having as typed proto messages by @QuantumExplorer in #3654
  • refactor: remove orphaned shielded nullifier-changes subsystem by @QuantumExplorer in #3823
  • refactor: consolodate Network structs and enum variants into one by @ZocoLini in #3567
  • refactor: rs-platform-wallet-ffi error framework by @ZocoLini in #3566
  • refactor(platform-wallet): delegate address sync to key-wallet, PlatformAddress in AddressProvider by @QuantumExplorer in #3482
  • refactor(platform-wallet): adopt rust-dashcore wallet event-bus API by @QuantumExplorer in #3556
  • refactor(swift-sdk): extract key management logic into centralized KeyManager by @alfie-dash in #3033
  • refactor(swift-sdk): add ViewModels for address operations by @alfie-dash in #3034
  • refactor(swift-sdk): key management by @alfie-dash in #3050
  • refactor(swift-sdk): state management by @alfie-dash in #3051
  • refactor(swift-sdk): treat warnings as errors by @alfie-dash in #3064
  • refactor(swift-sdk): extract validation logic by @alfie-dash in #3042
  • refactor(swift-sdk): data transformers by @alfie-dash in #3045
  • refactor(swift-sdk): lean down stale app and SDK layers by @llbartekll in #3539
  • refactor(swift-sdk): redesign Persistent* tx schema and fix per-wallet tx push stall by @QuantumExplorer in #3561
  • refactor(swift-sdk,platform-wallet): rebuild DashPay/DPNS persistence + identity sync, drop TokenWallet by @QuantumExplorer in #3564
  • revert(sdk): deserialization error due to outdated contract cache by @shumkov in #3114
  • Revert "feat(drive): allow deleting non-empty trees in targeted batch operations" by @QuantumExplorer in #3215

Tests


Performance


Documentation


Build & Dependencies


CI / CD


Chores & Maintenance

Release Bumps


New Contributors

Full Changelog: v3.0.1...v4.0.0