v0.1 · MIT · BC-UR receiver

Point a camera at an animated QR. Get the bytes.

A single QR caps out around 3 KB. Anything larger is split into an animated, fountain-coded sequence a camera can rebuild even while missing frames. Senders are everywhere; the browser lacked a good receiving end. This is that end: a pure decode core, pluggable frame sources, and a drop-in <ur-scanner> element.

$ npm i @nkwib/ur-scanner @ngraveio/bc-ur

One QR is ~3 KB

QR is a zero-infrastructure transport: no pairing, no network, works across an air gap and in a power cut. But a single code is tiny, so bigger payloads must animate.

Fountain-coded, loss-tolerant

BC-UR fountain-codes the sequence: the receiver rebuilds the whole payload from any set of frames a bit larger than the original, and can join the stream at any point. Miss frame 7? It doesn't matter.

The receiving end

Every hardware wallet ships a sender; the web had no good, framework-agnostic receiver. This is not a wallet library, it hands you bytes. Wallet payloads are one recipe among many.

Drop-in web component

Import the browser-only subpath, add the tag, listen for ur-complete. Ten lines, any framework, or none.

Prefer to own the loop? The headless URReceiver core takes strings from a camera, a file, or a test fixture and is safe to import in SSR bundles.

index.html
<ur-scanner auto-start expected-type="bytes"></ur-scanner>

<script type="module">
  import '@nkwib/ur-scanner/element';
  const scanner = document.querySelector('ur-scanner');
  scanner.addEventListener('ur-complete', (e) => {
    // e.detail.cbor is a Uint8Array; payloads are just bytes.
    console.log(`received ${e.detail.cbor.length} bytes`);
    scanner.stop();
  });
</script>

All processing is local. No telemetry.

No frame, byte, or pixel ever leaves the page. There is no network call of any kind. The camera stream goes to a canvas, the canvas to a QR detector, the strings to a decoder, all inside your tab. The library opens the camera only when you call start() and releases it on stop(). Even the fonts on this page are self-hosted.

Read the security policy →

Browser support

The core decode logic runs anywhere JavaScript does. Camera and detection depend on the browser. Native BarcodeDetector is fastest; where it's missing, install jsqr and it loads lazily.

BrowserQR detectionVerified
Chrome / Edge (desktop + Android)native BarcodeDetectoryes (Playwright/Chromium)
Safari (macOS 14+, iOS 17+)jsqr fallbackhelp wanted
Firefoxjsqr fallbackhelp wanted

The honest, per-device matrix (a community-PR target) lives in the compatibility docs.

See the bytes transfer

Open the demo on a laptop and your phone, point one at the other, and watch the ring fill. No second device? Fixture mode runs the whole pipeline on one screen.

The benchmark page measures decode attempts per second and time to complete on your own phone, through your own lens. It is the number no headless run can give you.

ur-scanner Browser receiver for animated BC-UR QR codes. Camera to bytes, no wallet required.