feat(platform): send memos with shielded transfers#3836
Conversation
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>
|
No actionable comments were generated in the recent review. 🎉 ℹ️ Recent review info⚙️ Run configurationConfiguration used: defaults Review profile: CHILL Plan: Pro Run ID: 📒 Files selected for processing (12)
📝 WalkthroughWalkthroughThis 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. ChangesShielded memo support
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
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~25 minutes Possibly related PRs
Suggested reviewers
Poem
🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
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. Comment |
Codecov Report❌ Patch coverage is
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
🚀 New features to boost your workflow:
|
|
✅ 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:
|
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(newpackages/rs-dpp/src/shielded/memo.rs): structured memo with kind0= Empty (the all-zero memo every existing sender writes), kind1= UTF-8 text (≤ 32 bytes, zero-padded), andOther { kind, payload }so unknown kinds round-trip verbatim.to_bytes/from_bytesare total and lossless.operations::transferandPlatformWallet::shielded_transfer_totakememo: [u8; 36];platform_wallet_manager_shielded_transferFFI gains a nullablememo_text: *const c_char— null/empty → empty memo, over-32-byte text rejected withErrorInvalidParameter; encoding lives in Rust (encode_memo_text), Swift only bridges.shieldedTransfer(walletId:account:recipientRaw43:amount:memo:)withmemo: String? = nil.N/32 bytesUTF-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-byteenc_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 viatry_decrypt_note_with_memo, and an OVK-encrypted note's memo is recovered viatry_recover_outgoing_note.cargo test -p platform-wallet-ffi --all-features encode_memo_text— 5 tests for the FFI text→bytes rules.cargo checkwith--all-features --testson dpp / platform-wallet / platform-wallet-ffi and--features shielded --testson dash-sdk;cargo fmt --allclean.build_ios.sh --target sim(regenerated cbindgen header) + SwiftExampleAppxcodebuildclean.Breaking Changes
None consensus-wise.
platform_wallet_manager_shielded_transfer(FFI) andoperations::transfer/shielded_transfer_to(Rust API) gained a memo parameter; all in-tree callers updated.Checklist:
For repository code-owners and collaborators only
🤖 Generated with Claude Code
Summary by CodeRabbit
Release Notes
New Features
Tests