Cashu: large tokens should export and scan as animated QR NUT-16, via cdk 0.18
Maintainers usually reply within 1 day
Nobody has claimed this yet.
Assessment
- Difficulty
- 4/5
- Estimated time
- 3-5 days
- Newbie friendliness
- 35/100
Research direction
Start with docs/cashu/cdk-spike.md and review the cdk 0.18 API changes, then inspect rust/src/api/ and rust/src/cashu/mod.rs for the bridge and web stub. Follow _TokenDialog in cashu_wallet_screen.dart and the PlatformAwareQrScanner call sites to understand the existing QR flows. Done means large tokens can be exported as animated UR frames and scanned to completion with progress, while retaining the text-copy path.
Written by the indexing model from the issue text.
Description
Context
C3 (#237) handles a token too large for a QR by degrading to selectable text plus "too large for a QR — copy it instead". That is the correct floor — the red error box is gone and no funds are at risk — but it is not the ecosystem's answer to the problem.
NUT-16 specifies it: a serialized token with ≤ 2 proofs usually fits a static QR; beyond that the sender displays an animated QR — UR-encoded fragments (Blockchain Commons' Uniform Resources), one frame per fragment — and the receiver scans frames until the decoder completes. The reference wallet implements exactly this: cashu.me ships @gandlaf21/bc-ur (the gandlaf fork of ngraveio's bc-ur, the library the NUT links) and switches on the spec's proofs.length <= 2 heuristic. Note the threshold: even tokens that technically fit a version-35+ QR are miserable to scan with a real camera, so wallets animate well before the ~2.9 KB hard limit (version 40-L, byte mode: 2,953 bytes). A token funded from many small proofs easily reaches tens of KB (61 KB observed from a faucet in the #237 review). Whether we switch on the proof-count heuristic or on serialized length can be decided in the PR — TokenUrEncoder::is_single_fragment() / fragment_count() make either trivial.
The protocol half is already written: cdk implements NUT-16
Since cdk 0.18.0 (changelog: "cashu: NUT-16 animated QR token encoding and fountain-fragment decoding, with matching FFI types and fuzz coverage"), cashu::nuts::nut16 — re-exported through cdk, no feature flag — ships both sides:
- Sender:
Token::ur_encoder(max_fragment_length) -> Result<TokenUrEncoder>→next_part()yields one UR frame per call (fountain-coded: the receiver completes from any sufficient subset, order-independent).DEFAULT_MAX_FRAGMENT_LENGTHis 200 bytes;current_index()/fragment_count()support a progress UI. - Receiver:
TokenUrDecoder—receive(frame)per scanned QR,complete(),token() -> Result<Option<Token>>, withresolved_fragment_count()/fragment_count()for progress. Also accepts the single-partur:bytes/...form and normalizes V3 tokens to V4.
So no Dart bc-ur dependency and no hand-rolled fragmentation: the logic lands in Rust, per the repo's golden rule, and Dart does presentation only.
Prerequisite
This issue starts with a 0.17.3 → 0.18.0 bump of both cdk and cdk-sqlite (exact-pinned in lockstep on purpose — pre-1.0, wallet API moves between minors): review the wallet-API churn per docs/cashu/cdk-spike.md. The new ur + ciborium deps don't affect the web build — cdk is native-only here (web gets the typed stub in rust/src/cashu/mod.rs) — and ur's fountain seeding is deterministic (rand_xoshiro seeded from sequence number + CRC32, no getrandom), so it won't block Cashu-on-web later either.
App-side scope
- Bridge: expose the encoder/decoder in
rust/src/api/(+ FRB regen). Frames are plain strings; the decoder is a small stateful session. (cdk's owncdk-ffiUR types are a different binding layer — UniFFI — and are not reusable here.) - Sender UI —
_TokenDialogincashu_wallet_screen.dart: above the static threshold, cyclenext_part()frames through the QR on a timer (cashu.me repaints every 150 ms; a QR widget at that rate is trivial for Flutter). Keep the copy-text path regardless: NUT-16 is an optional NUT — a receiving wallet may not implement it, so a non-UR wallet still needs the text. - Receiver UI —
PlatformAwareQrScanneris currently one-shot by contract ("called exactly once",_detectedlatch) and both call sites pop it on the first result, so this needs a multi-frame mode: keep the scanner alive, feedur:frames to the decoder untilcomplete(), show progress viaresolved_fragment_count()/fragment_count(), and useDetectionSpeed.noDuplicates— the defaultnormalmode time-throttles content-blind at 250 ms, which would drop distinct fountain frames. On web the fallback is a paste field (and Cashu is stubbed on web anyway). Same shared widget as #458 ; related but separable.
Sibling of #390/#391/#392 — GA-quality gaps found during C3 manual verification, out of scope for the C3 PR itself.
- Dominant language
- Dart
- Stars
- 11
- Forks
- 9
- Avg merge
- 13h 4m
- Merged PRs (30d)
- 259
Getting set up
- No Dockerfile or Docker Compose file
- Has a pull request template
- Read the contributing guide
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
More from MostroP2P/app
-
Difficulty 2/5 1-3 hours Newbie friendliness 85/100
Maintainers usually reply within 1 day
-
Add-invoice screen stays on "Sent, waiting for the node" after a late acceptance on a sell orderOpenbug priority: medium
Difficulty 2/5 1-3 hours Newbie friendliness 85/100
Maintainers usually reply within 1 day
-
Difficulty 1/5 Under an hour Newbie friendliness 85/100
Maintainers usually reply within 1 day
-
area: ui
Difficulty 2/5 1-3 hours Newbie friendliness 72/100
Maintainers usually reply within 1 day
-
Difficulty 2/5 1-3 hours Newbie friendliness 72/100
MostroP2P/app#341 · 1 comment ·
Maintainers usually reply within 1 day
Similar issues
-
[BUG][All] VLESS URIs with flow=xtls-rprx-vision-udp443 are silently dropped on subscription importOpen
Difficulty 2/5 1-3 hours Newbie friendliness 68/100
-
area::timeline needs::triage regression
Difficulty 2/5 1-3 hours Newbie friendliness 80/100
linagora/twake-on-matrix#3450 ·
Maintainers usually reply within 3 days
-
Difficulty 2/5 1-3 hours Newbie friendliness 82/100
genkit-ai/genkit-dart#637 ·
Maintainers usually reply within 1 day
-
enhancement
Difficulty 2/5 1-3 hours Newbie friendliness 70/100
bggRGjQaUbCoE/PiliPlus#3235 ·
Maintainers usually reply within 1 day
-
Difficulty 2/5 1-3 hours Newbie friendliness 74/100
lichess-org/mobile#3830 ·
Maintainers usually reply within 2 days