Skip to content
Payvol

Concepts

What has been established, and what has not

This site publishes only what it can prove. Every number is read from a committed artifact at build time, and every measurement carries the environment and the date it was taken on.

This is a young project without a customer list, so the case for taking it seriously has to be made a different way. Every number on this site is read from a file in this repository, or held by a test that fails when the behaviour changes. What follows is all of it, including the two entries that did not go our way.

The three defences

A claim on this site is defended in exactly one of three ways, and anything that cannot be defended in one of them is written as an intention instead, in plain words, with no number attached.

  • Derived. Counts and digests are read from the data when the page is built, so they cannot drift, because nobody types them.
  • Asserted. A guarantee is held by a test that fails when the behaviour stops being true.
  • Cited. A measurement points at a committed artifact and carries its scope: which path, which environment, which date.

Scope is part of the claim

"The digest survives into settlement metadata" is true and nearly useless. The same fact stated with its environment, its path and its date becomes something a reviewer can check, argue with, or reproduce. Stated without them it is a mood.

A citation names three things: the file, the path inside it, and the value shown. The build reads the path and refuses a value that disagrees with what the file holds. Two false citations reached production before that check existed, which is why it exists.

The claim ledger

What has actually been established, including the entries that did not go our way, because a page of evidence that only lists successes is a brochure.

  1. A real settlement, on devnet

    A Payvol request was built, signed by an external party, executed on Canton devnet, and the digest of that request travelled with the payment into the settlement metadata.

    Digest on ledger
    96454e12c84bc20a
    Amount
    0.1 Amulet
    Update id
    1220178721b2d916

    Scope Canton DevNet, 2026-08-27, the direct transfer instruction path, one settlement between two dedicated demo parties.

    What this does not establish Nothing about mainnet, about other instruments, or about any other participant configuration. One settlement is one settlement.

    Read from packages/payvol-conformance/fixtures/metadata-digest-devnet.json · docs/spike/02-VERDICT.md

  2. What it costs a payee to read it

    Reading the settlement off the payee own participant took two Ledger API calls. There was no query store deployed on the participant that was measured, so this is the ordinary cost rather than a best case.

    API calls
    2
    Bytes read
    3,595

    Scope Canton DevNet, 2026-08-27, the payee's own participant, no query store deployed.

    What this does not establish Nothing about how this scales with volume, and nothing about a participant that does have a query store, which would be a different and probably cheaper path.

    Read from packages/payvol-conformance/fixtures/metadata-digest-devnet.json

  3. Who can see the metadata

    The same settlement was fetched as four different parties. The payer, the payee and the instrument registry all read it. A party that took no part was told the update does not exist. That is structural to the template the transfer settles through, not a Payvol choice.

    Parties that can read it
    3
    Parties that cannot
    1
    Uninvolved party response
    not found

    Scope Canton DevNet, 2026-08-27, fetched AS each party rather than reasoned about.

    What this does not establish Nothing about what the registry operator retains on their own infrastructure. The reader set was read from the contract, and the measuring participant does not host that party.

    Read from packages/payvol-conformance/fixtures/visibility-devnet.json · docs/spike/04-VISIBILITY.md

  4. Where the evidence lives afterwards

    After the payee accepts, the digest leaves the active contract set and survives only in the update history. An adapter that polled current state would report every settled request as unpaid.

    In active contracts after settlement
    no
    In update history
    yes

    Scope Canton DevNet, 2026-08-27, measured before and after the accept.

    What this does not establish How long the update history is retained. That is a participant pruning policy, and it is why an adapter is required to persist its own record.

    Read from packages/payvol-conformance/fixtures/metadata-matrix-devnet.json

  5. The conformance suite

    Every accept and reject the profile pins, as versioned vectors any implementation can run. They run in your own browser on the spec page, against the built packages.

    Vectors, profile 0.1
    42

    Scope No network. The vectors are pure, and run wherever the packages run.

    What this does not establish That any other implementation passes them. There is one implementation today, and a second one written by someone who has not read this code is the next milestone.

    Read from packages/payvol-conformance/vectors/ · docs/TEST-VECTORS.md

  6. The reconciliation model

    Five orthogonal statuses over committed scenarios, covering exact, partial, overpaid, late, duplicate, unmatched, cross instrument refusal and ambiguous. The same input produces the same record on every run.

    Scenarios
    17
    Status axes
    5

    Scope Authored inputs, not captures. These exercise the model; they are not things that happened.

    What this does not establish Anything about cross instrument conversion in practice. Devnet offers one instrument, so that rule is exercised by fixture only.

    Read from packages/payvol-conformance/fixtures/reconciliation-cases.json

  7. The allocation route, attempted

    An allocation settlement was attempted on devnet and could not be completed. Two blockers were recorded: there was no allocation request for the payer to allocate against, and no executor application to coordinate it.

    Settled
    no
    Decisive blockers
    2

    Scope Canton DevNet, 2026-08-27. This is a record of an attempt, not a settlement capture.

    What this does not establish That the allocation mapping works. It is a design intention and is labelled untested everywhere it appears, including in the specification.

    Read from packages/payvol-conformance/fixtures/allocation-attempt-devnet.json · docs/spike/05-ALLOCATION.md

  8. Accessibility and performance

    Every route measured with Lighthouse, median of three runs, plus one axe pass. Reported here whether or not the numbers flatter us.

    Routes measured
    13
    Lowest performance score
    83
    Lowest accessibility score
    96
    Open axe findings
    15

    Scope Local preview of the production build, 2026-08-27, run 2 of two that day.

    What this does not establish Nothing about real devices or real networks. No threshold is set and none is in CI, deliberately, until the remaining findings are triaged.

    Read from site/docs/measurements/

What a property test caught

The best evidence that this approach is worth anything is not that the tests pass. It is that they once did not, and what they caught.

In August 2026 the round-trip property failed once during a full run and passed on the next thirty-five. Rerunning it over recorded seeds put the rate at roughly one case in a hundred thousand: four failures in four hundred thousand. Shrunk, every one of them was the same input. A request carrying U+FEFF, an invisible character, at the start of a field.

The decoder was deleting it. Not the encoder, which emitted the right bytes, and not the browser, which round-trips the character correctly: our own percent-decoder, because TextDecoder consumes a leading byte order mark unless told not to, and the option that tells it not to is named the opposite of what it does.

The identifier moved. The canonical form is computed over the decoded object, so a request that went out as a URI and was read back did not necessarily produce the identifier it started with. That is the difference between a cosmetic bug and a correctness one, and it is why this is on the evidence page rather than in a changelog.

Both the fix and the rule are in the repository. The decoder is byte faithful now, verified across a million generated cases, and the counterexample is a permanent conformance vector in both directions, because a property test that once found a bug and has no fixture for it has forgotten what it learned.

Investigating it exposed something larger that the profile had never decided: what a field value may contain. Twelve invisible characters were tried against both profiles. All twelve were accepted and all twelve changed the identifier, which means two requests that render identically to a payer could carry different ones. The draft profile now refuses them with INVISIBLE_CHARACTER, naming the code point and the path.

This rule covers characters that are invisible. Characters that merely look alike, such as Cyrillic and Latin letterforms, are not covered by it. Those are visible, a payer can see them, and telling them apart is a wallet display concern rather than a profile rule. Saying otherwise would be claiming a protection that does not exist.

The whole investigation is written up in docs/spike/06-ROUNDTRIP.md, including the seeds, so a stranger can reproduce it rather than take our word for it.

What we have not established

Read this before you decide. If any of these matters to your integration, it should change your timeline, and we would rather you learn it here than three weeks in.

  • No independent implementation Everything that reads a Payvol request today was written by the same team. Until a second one exists, the format is documented rather than proven interoperable.
  • No production deployment Every measurement on this page comes from devnet. Nothing here has processed a payment anyone actually needed.
  • Key discovery is unsolved A signature can be checked, but tying a key to a company relies on infrastructure the network is still building. The wallet is told this instead of being told a merchant is verified.
  • The allocation path is untested Two blockers stopped the attempt and both are recorded. Until it runs, the mapping to that settlement flow is a design, not a result.
  • No external adopter No wallet, application or operations team outside this project has integrated. When one does, it will be named here rather than counted.
  • Nothing is published Both packages are version 0.0.0 and npm returns a 404 for them. That is why no module on this site reads available, and it is checked by a test rather than remembered.

Check it yourself

Nothing here needs our word for it. Clone the repository, run the suite, and compare the numbers to the ones printed on this site. If a number here does not match what your machine produces, that is a bug worth reporting, and it is the kind we most want to hear about.

# the same commands we run
pnpm install && pnpm build
pnpm test
pnpm conformance