<!-- Quickstart: create a Canton payment request
     Source of https://payvol.xyz/docs/quickstart. Generated at build time from what shipped. -->

Get started

- [Your first request](https://payvol.xyz/docs/quickstart)
- Install soon
- Read it back soon
- Watch a settlement soon

Guides

- Send a request with an invoice soon
- Support requests in a wallet soon
- Observe settlements on your node soon
- Reconcile against your invoices soon
- Handle the exceptions soon
- Move from 0.1 to 0.2 soon

Reference

- Request payload soon
- Security envelope soon
- Canonical encoding soon
- Identifier derivation soon
- Settlement metadata soon
- Reconciliation record soon
- Reason codes soon
- Conformance vectors soon

Concepts

- Why the request is off ledger soon
- Why the identifier is derived soon
- Who can read settlement metadata soon
- Why seven verdicts, not one soon
- Why Payvol runs no engine soon
- [What has been established, and what has not](https://payvol.xyz/docs/concepts/what-is-established)

Operate

- Versioning and deprecation soon
- Node access and permissions soon
- Data retention soon
- Reporting a security issue soon

Get started

# Your first request

Ten minutes, no account, and nothing of ours running anywhere. By the end you will have a request, its identifier, and three ways to hand it to somebody.

## Install

Two packages. `@payvol/core` encodes, decodes and validates; `@payvol/qr` turns a request into the forms a payer can actually receive.

```
npm i @payvol/core @payvol/qr
```

> Both are on npm and install from the registry. Tested on Node 20. A party id and an instrument id are explained under [Create a request](#create), because a request that validates is not automatically one a wallet can pay.

## Create a request

A request names who is being paid, in what, how much, and an optional reference that stays yours. Encoding it produces a `canton:` URI, and that URI is the whole artifact: there is no session, and nothing was stored anywhere.

```
import { encode } from '@payvol/core'

const uri = encode({
  version: '0.1',
  type: 'transfer',
  recipient: 'payvol-demo-recipient',
  instrument: 'DSO::1220be58c29e65de40bf273be1dc2b266d43a9a002ea5b18955aeef7aac881bb471a/Amulet',
  amount: '1.5000000000',
  reference: 'PAYVOL-DEMO-2026-08-01',
})
```

**What those two identifiers are.** A Canton party id is `name::fingerprint`, where the fingerprint is 68 hex characters beginning `1220`. 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. An instrument id is `admin/id`, where `admin` is the party id of the registry that issues the asset and `id` is its symbol. The DevNet Canton Coin admin above comes from the network's own registry metadata endpoint, 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. You can try that at [/app/pay](https://payvol.xyz/app/pay).

That returns:

```
canton:payvol-demo-recipient?instrument=DSO%3A%3A1220be58c29e65de40bf273be1dc2b266d43a9a002ea5b18955aeef7aac881bb471a%2FAmulet&amount=1.5000000000&reference=PAYVOL-DEMO-2026-08-01
```

## Seal it

The identifier is derived from the canonical bytes rather than assigned, so two independent implementations that agree on the profile produce the same identifier for the same request. Change one character and it changes completely.

```
import { decode, sha256, toCanonical } from '@payvol/core'

// the canonical bytes are what an identifier is derived FROM, never assigned
const canonical = toCanonical(decode(uri))
const identifier = sha256(canonical)

// change one character of the request and this changes completely
```

> The digest is domain separated, so it cannot collide with a digest computed under another profile over the same bytes. `SPEC.md` in the repository says exactly how.

## Deliver it

The same bytes become a QR code, a link, or an NFC tap. Nothing is installed on the payer side and no page has to stay up for the code to keep working.

```
import { qrPayload, linkPayload, ndefUriRecord } from '@payvol/qr'

// the same bytes, three ways to hand them over
const forQr = qrPayload(uri)
const forLink = linkPayload(uri, { scheme: 'web+canton' })
const forTap = ndefUriRecord(uri)
```

## Read it back

A reader decodes the artifact and checks it against the profile. A request it cannot read is refused with a named reason rather than half understood.

```
import { decode, validate } from '@payvol/core'

const request = decode(scanned)
const result = validate(request)

// a refusal names its rule; it is never a bare false
result.valid   // false
result.code    // 'EXPIRED'
```

Profile 0.1 is pinned by 42 conformance vectors. Run them against your own implementation with `npx @payvol/cli conformance`, which needs nothing installed and no repository checkout.

## What to do next

- [What a wallet must check](https://payvol.xyz/products/verify), and the one answer it cannot reach.
- [Reading settlements](https://payvol.xyz/products/observe) off a participant you already run.
- [What has been established](https://payvol.xyz/docs/concepts/what-is-established), and what has not, before you plan around any of it.
