Skip to content

feat(platform): send memos with shielded transfers#3836

Merged
QuantumExplorer merged 1 commit into
v3.1-devfrom
claude/shielded-transfer-memo
Jun 10, 2026
Merged

feat(platform): send memos with shielded transfers#3836
QuantumExplorer merged 1 commit into
v3.1-devfrom
claude/shielded-transfer-memo

Conversation

@QuantumExplorer

@QuantumExplorer QuantumExplorer commented Jun 10, 2026

Copy link
Copy Markdown
Member

Issue being fixed or feature implemented

Dash shielded notes reserve a 36-byte DashMemo (4-byte kind tag + 32-byte payload) in every note ciphertext, and all the dpp builders already accept it — but the wallet hardcoded [0u8; 36] on every send, so there was no way to attach a note to a shielded payment. There was also no memo-kind convention and no incoming-side primitive that could recover a memo (the scanner's compact IVK decryption stops before the memo region).

What was done?

  • dpp::shielded::ShieldedMemo (new packages/rs-dpp/src/shielded/memo.rs): structured memo with kind 0 = Empty (the all-zero memo every existing sender writes), kind 1 = UTF-8 text (≤ 32 bytes, zero-padded), and Other { kind, payload } so unknown kinds round-trip verbatim. to_bytes/from_bytes are total and lossless.
  • Memo threading (Type 16 ShieldedTransfer): operations::transfer and PlatformWallet::shielded_transfer_to take memo: [u8; 36]; platform_wallet_manager_shielded_transfer FFI gains a nullable memo_text: *const c_char — null/empty → empty memo, over-32-byte text rejected with ErrorInvalidParameter; encoding lives in Rust (encode_memo_text), Swift only bridges.
  • Swift SDK: shieldedTransfer(walletId:account:recipientRaw43:amount:memo:) with memo: String? = nil.
  • SwiftExampleApp: Send screen shows a "Memo (optional)" field when the flow is shielded → shielded, with a live N/32 bytes UTF-8 byte counter that turns red and disables Send when over the limit. Gated on the flow (not just the recipient type) so the self-shield path — which has no memo parameter — never shows a field that would silently drop text.
  • dash_sdk::platform::shielded::try_decrypt_note_with_memo: full (non-compact) IVK trial decryption over the 104-byte enc_ciphertext, returning (note, address, memo) — the receive-side primitive the compact scanner can't provide.

Other send paths (shield, unshield, withdraw, identity-create) intentionally keep the empty memo.

How Has This Been Tested?

  • cargo test -p dpp shielded::memo — 11 unit tests (round-trips incl. multibyte UTF-8 at exactly 32 bytes, over-limit rejection, unknown-kind preservation, invalid-UTF-8 fallback).
  • cargo test -p platform-wallet --features shielded --release memo_roundtrip — 2 round-trip tests: a real built transition's memo is recovered by the recipient IVK via try_decrypt_note_with_memo, and an OVK-encrypted note's memo is recovered via try_recover_outgoing_note.
  • cargo test -p platform-wallet-ffi --all-features encode_memo_text — 5 tests for the FFI text→bytes rules.
  • cargo check with --all-features --tests on dpp / platform-wallet / platform-wallet-ffi and --features shielded --tests on dash-sdk; cargo fmt --all clean.
  • iOS: build_ios.sh --target sim (regenerated cbindgen header) + SwiftExampleApp xcodebuild clean.
  • Manual UAT on the paloma devnet simulator: sent 0.05 DASH shielded→shielded with memo "Thanks for lunch" — over-limit memo correctly blocks Send with a red 40/32 counter; transfer succeeded on-chain (input note spent, recipient + exact-change notes scanned back in, balance down by exactly the 0.00162851 DASH fee).

Breaking Changes

None consensus-wise. platform_wallet_manager_shielded_transfer (FFI) and operations::transfer/shielded_transfer_to (Rust API) gained a memo parameter; all in-tree callers updated.

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

Summary by CodeRabbit

Release Notes

  • New Features

    • Shielded transfers now support optional recipient memos (UTF-8 text with 32-byte payload limit).
    • Added memo input field in the send UI for shielded-to-shielded transfers with live byte-count validation.
  • Tests

    • Added comprehensive memo encoding/decoding and round-trip recovery validation tests.

Dash shielded notes carry a 36-byte DashMemo (4-byte kind tag +
32-byte payload), but the wallet hardcoded an all-zero memo on every
send. This adds a structured ShieldedMemo type to dpp (kind 0 = empty,
kind 1 = UTF-8 text, unknown kinds preserved verbatim), threads an
optional memo through the shielded -> shielded transfer path
(operations::transfer -> shielded_transfer_to -> FFI -> Swift SDK),
and surfaces a memo field with a live UTF-8 byte counter on the
SwiftExampleApp Send screen for shielded recipients.

Also adds try_decrypt_note_with_memo to rs-sdk: full (non-compact)
IVK trial decryption that recovers the memo alongside the note, with
round-trip tests proving a built transition's memo is recoverable by
the recipient IVK and via OVK recovery.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@QuantumExplorer
QuantumExplorer requested a review from shumkov as a code owner June 10, 2026 08:22
@coderabbitai

coderabbitai Bot commented Jun 10, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: c9ae04dd-3828-4ccb-9f70-ed45f602a5a0

📥 Commits

Reviewing files that changed from the base of the PR and between bfa8f68 and 2c30ac9.

📒 Files selected for processing (12)
  • packages/rs-dpp/src/shielded/memo.rs
  • packages/rs-dpp/src/shielded/mod.rs
  • packages/rs-platform-wallet-ffi/src/shielded_send.rs
  • packages/rs-platform-wallet/src/wallet/platform_wallet.rs
  • packages/rs-platform-wallet/src/wallet/shielded/operations.rs
  • packages/rs-platform-wallet/src/wallet/shielded/sync.rs
  • packages/rs-platform-wallet/src/wallet/shielded/sync/memo_roundtrip_tests.rs
  • packages/rs-sdk/src/platform/shielded/mod.rs
  • packages/rs-sdk/src/platform/shielded/notes_sync/decrypt.rs
  • packages/swift-sdk/Sources/SwiftDashSDK/PlatformWallet/PlatformWalletManagerShieldedSync.swift
  • packages/swift-sdk/SwiftExampleApp/SwiftExampleApp/Core/ViewModels/SendViewModel.swift
  • packages/swift-sdk/SwiftExampleApp/SwiftExampleApp/Core/Views/SendTransactionView.swift

📝 Walkthrough

Walkthrough

This PR adds shielded transfer memo support across Rust and Swift layers: a new 36-byte memo format, memo-aware note decryption, memo propagation through wallet and FFI transfer paths, IVK/OVK round-trip tests, and Swift example app memo input with byte-limit validation.

Changes

Shielded memo support

Layer / File(s) Summary
Memo contract and codec
packages/rs-dpp/src/shielded/memo.rs, packages/rs-dpp/src/shielded/mod.rs
Adds ShieldedMemo, memo size constants, 36-byte encode/decode logic, and public re-exports. Includes tests for round-trip behavior, UTF-8 trimming, overflow rejection, and unknown/invalid fallback handling.
Memo-aware note decryption API
packages/rs-sdk/src/platform/shielded/notes_sync/decrypt.rs, packages/rs-sdk/src/platform/shielded/mod.rs
Adds try_decrypt_note_with_memo that parses full encrypted note fields and returns note, recipient address, and recovered memo bytes. Re-exports and module docs are updated accordingly.
Wallet transfer memo plumbing
packages/rs-platform-wallet/src/wallet/platform_wallet.rs, packages/rs-platform-wallet/src/wallet/shielded/operations.rs
Adds [u8; 36] memo parameter to wallet and transfer operations and forwards it into shielded transition construction instead of using a fixed zero memo.
FFI memo input and validation
packages/rs-platform-wallet-ffi/src/shielded_send.rs
Extends shielded transfer FFI signature with optional memo_text, decodes and validates UTF-8 memo input, encodes memo bytes, maps invalid inputs to existing error variants, and forwards memo to wallet transfer. Includes memo encoding unit tests.
IVK and OVK memo round-trip tests
packages/rs-platform-wallet/src/wallet/shielded/sync.rs, packages/rs-platform-wallet/src/wallet/shielded/sync/memo_roundtrip_tests.rs
Adds a new shielded sync test module and tests proving byte-for-byte memo round-trip through incoming IVK decryption and outgoing OVK recovery paths, including helper construction of encrypted wire note data.
Swift SDK memo parameter wiring
packages/swift-sdk/Sources/SwiftDashSDK/PlatformWallet/PlatformWalletManagerShieldedSync.swift
Extends shieldedTransfer with optional memo: String? = nil, updates docs, and conditionally passes memo as a C string pointer or nil to FFI.
Example app memo UI and send gating
packages/swift-sdk/SwiftExampleApp/SwiftExampleApp/Core/ViewModels/SendViewModel.swift, packages/swift-sdk/SwiftExampleApp/SwiftExampleApp/Core/Views/SendTransactionView.swift
Adds memo text state and UTF-8 byte counting, enforces a 32-byte limit for shielded-to-shielded sends in canSend, forwards trimmed memo when sending, and renders a conditional memo input with live over-limit highlighting.

Sequence Diagram(s)

sequenceDiagram
  participant App
  participant FFI
  participant Wallet
  participant Ops
  participant Builder
  App->>FFI: call shielded transfer with optional memo
  FFI->>FFI: validate memo utf8 and length
  FFI->>Wallet: invoke shielded transfer with memo bytes
  Wallet->>Ops: call transfer with memo bytes
  Ops->>Builder: build shielded transfer transition
  Builder-->>Ops: return transition with memo payload
  Ops-->>FFI: transfer result
  FFI-->>App: ffi result
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly related PRs

  • dashpay/platform#3802: Both changes touch shielded transfer operation wiring in operations.rs, where this PR adds memo propagation and the related PR adjusts transfer-related fee checks.

Suggested reviewers

  • shumkov
  • thepastaclaw

Poem

🐇 I tucked a note in moonlit bytes,
Through shielded paths and silent lights.
From Rusty burrow to Swifty glade,
The memo hopped, correctly made.
Carrot cheers for tests well played! 🥕

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 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 captures the primary change: adding memo support to shielded transfers across the platform.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.
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.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch claude/shielded-transfer-memo

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 and usage tips.

@codecov

codecov Bot commented Jun 10, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 95.79832% with 5 lines in your changes missing coverage. Please review.
✅ Project coverage is 87.05%. Comparing base (af5611e) to head (2c30ac9).
⚠️ Report is 1 commits behind head on v3.1-dev.

Files with missing lines Patch % Lines
packages/rs-dpp/src/shielded/memo.rs 95.79% 5 Missing ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##           v3.1-dev    #3836    +/-   ##
==========================================
  Coverage     87.04%   87.05%            
==========================================
  Files          2677     2679     +2     
  Lines        329918   330113   +195     
==========================================
+ Hits         287182   287372   +190     
- Misses        42736    42741     +5     
Components Coverage Δ
dpp 87.43% <95.79%> (+0.01%) ⬆️
drive 85.99% <ø> (ø)
drive-abci 89.29% <ø> (ø)
sdk ∅ <ø> (∅)
dapi-client ∅ <ø> (∅)
platform-version ∅ <ø> (∅)
platform-value 92.20% <ø> (ø)
platform-wallet ∅ <ø> (∅)
drive-proof-verifier 49.55% <ø> (ø)
🚀 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.

@QuantumExplorer QuantumExplorer left a comment

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Reviewed

@QuantumExplorer
QuantumExplorer merged commit a8678c3 into v3.1-dev Jun 10, 2026
39 checks passed
@QuantumExplorer
QuantumExplorer deleted the claude/shielded-transfer-memo branch June 10, 2026 09:32
@github-actions

Copy link
Copy Markdown
Contributor

✅ DashSDKFFI.xcframework built for this PR.

SwiftPM (host the zip at a stable URL, then use):

.binaryTarget(
  name: "DashSDKFFI",
  url: "https://your.cdn.example/DashSDKFFI.xcframework.zip",
  checksum: "e0d9db421070a304509e11e694af1cda6cd205b17dd7efb0bc3b9c32db29802a"
)

Xcode manual integration:

  • Download 'DashSDKFFI.xcframework' artifact from the run link above.
  • Drag it into your app target (Frameworks, Libraries & Embedded Content) and set Embed & Sign.
  • If using the Swift wrapper package, point its binaryTarget to the xcframework location or add the package and place the xcframework at the expected path.

@QuantumExplorer QuantumExplorer added this to the v4.0.0 milestone Jun 10, 2026
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