@payvol/qr
v0.4.0
Published
Canton payment requests as a QR code, a deep link or an NFC NDEF record.
Maintainers
Readme
@payvol/qr
Carry a Canton payment request as a QR code, a deep link or an NFC tag. Layered
over @payvol/core: one request, whichever channel reaches the payer, with the
same identifier at both ends.
Install
npm i @payvol/qr @payvol/coreTested on Node 20; the engines field records the supported range. Pure and
browser safe, so it bundles.
What a party id and an instrument id are
A Canton party id is name::fingerprint, where the fingerprint is 68 hex
characters beginning 1220 (a multihash prefix: sha2-256 over 32 bytes). You get
yours from the validator or wallet that hosts the party: it is shown when the
party is allocated, and any wallet connected to it will display it. A shorter
string is a placeholder, not a party.
An instrument id is admin/id. admin is the party id of the registry that
issues the asset and id is its symbol within that registry. On Canton DevNet,
Canton Coin is
DSO::1220be58c29e65de40bf273be1dc2b266d43a9a002ea5b18955aeef7aac881bb471a/Amulet,
where the DSO admin comes from the network's own registry metadata endpoint and
is captured in this repository's fixtures rather than typed by hand.
A valid request is not a settleable one. validate() answers whether the
artifact is the right shape. It returns warnings when the recipient is not a
full party id, or the instrument names no registry admin, because those are well
formed and unpayable. What settles a request is a wallet: the payer opens it,
the wallet builds a Canton Token Standard transfer, and the payer signs it.
Example
import { decode } from '@payvol/core';
import { qrPayload, linkPayload, ndefUriRecord, parseNdefUriRecord } from '@payvol/qr';
const request = decode(
'canton:payvol-demo-recipient%3A%3A122013babe3c9a7ed315a3e8f8181bbcf85aaa0bfd0e3efbaf2e166f31ab80e4f9f5' +
'?instrument=DSO%3A%3A1220be58c29e65de40bf273be1dc2b266d43a9a002ea5b18955aeef7aac881bb471a%2FAmulet' +
'&amount=1.50',
);
qrPayload(request); // the string to draw into a QR code
linkPayload(request); // the same request as a link
const record = ndefUriRecord(request); // Uint8Array, one NDEF URI record
parseNdefUriRecord(record) === qrPayload(request); // trueDrawing the QR
This package produces the string; it draws nothing, so you bring a renderer.
Tested against the qrcode package:
import QRCode from 'qrcode';
import { qrPayload } from '@payvol/qr';
await QRCode.toCanvas(canvasElement, qrPayload(request));
await QRCode.toFile('request.png', qrPayload(request), { width: 400 });Encode the string in QR byte mode: percent-encoding is case sensitive in what the octets mean, and the URI has to scan back byte identical.
linkPayload and its second argument
qrPayload and linkPayload return the same string by default. They differ only
when you ask for the browser-registerable scheme:
linkPayload(request); // canton:...
linkPayload(request, { scheme: 'web+canton' }); // web+canton:...LinkOptions.scheme is 'canton' | 'web+canton', defaulting to 'canton'. Use
web+canton for pages that target a browser-registered protocol handler, because
a browser will not let a page register a bare canton: handler. decode()
accepts both forms, so either link round-trips.
normalizeToUri() accepts a request object or a string and returns the
canonical URI, so a caller that already holds one does not have to decode it
first.
The specification
Section 4 of SPEC.md, shipped in @payvol/core,
defines the URI grammar and the NFC mapping. The repository at
github.com/cayvox/Payvol-CPR carries the
same document; those links resolve once the repository is public.
Apache-2.0.
