Hacktoberfest 2026: the issues maintainers tagged for October, open and beginner-friendly. Browse Hacktoberfest issues

Cashu: large tokens should export and scan as animated QR NUT-16, via cdk 0.18

Open
#457 0 comments 0 reactions 0 assignees View on GitHub

Maintainers usually reply within 1 day

Nobody has claimed this yet.

Assessment

Difficulty
4/5
Estimated time
3-5 days
Newbie friendliness
35/100
Issue type
Feature
Clarity
Clearly specified
Activity status
Active
Tech stack
dart, flutter, rust
Domain
mobile

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_LENGTH is 200 bytes; current_index() / fragment_count() support a progress UI.
  • Receiver: TokenUrDecoder — receive(frame) per scanned QR, complete(), token() -> Result<Option<Token>>, with resolved_fragment_count() / fragment_count() for progress. Also accepts the single-part ur: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

  1. Bridge: expose the encoder/decoder in rust/src/api/ (+ FRB regen). Frames are plain strings; the decoder is a small stateful session. (cdk's own cdk-ffi UR types are a different binding layer — UniFFI — and are not reusable here.)
  2. Sender UI — _TokenDialog in cashu_wallet_screen.dart: above the static threshold, cycle next_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.
  3. Receiver UI — PlatformAwareQrScanner is currently one-shot by contract ("called exactly once", _detected latch) and both call sites pop it on the first result, so this needs a multi-frame mode: keep the scanner alive, feed ur: frames to the decoder until complete(), show progress via resolved_fragment_count() / fragment_count(), and use DetectionSpeed.noDuplicates — the default normal mode 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

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

More from MostroP2P/app

All issues in MostroP2P/app

Similar issues

More Dart issues

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.