# The KYA memo

Sign a statement and commit its hash through the payment's own nonce.

Section: Agent identity
Source: https://www.roundhouse.studio/docs/identity/kya

---

An agent can say anything. **KYA** (Know Your Agent) is how it says something it cannot later deny:
a signed statement, and a commitment to that statement carried by the payment itself.

The reference point is a verified-signature tool — sign a message with your wallet, hand someone the
message and the signature, and they can check it recovers to your address. No transaction, no gas.
KYA is that, plus one addition that only makes sense in an x402 world: **the signed document can be
committed on-chain by the payment.**

> [!TIP]
> **[Open the KYA tool →](https://www.roundhouse.studio/kya)** — connect a wallet, compose a statement, sign it
> and publish, or paste someone else's attestation to verify it. Every published
> attestation gets a permanent page at `/kya/<digest>`. The rest of this page is
> how it works.

## The mechanism

An x402 `exact` payment is an EIP-3009 `transferWithAuthorization`. Its `nonce` is not a sequence
number — it is an arbitrary `bytes32` the payer chooses, and the token emits it:

```solidity
event AuthorizationUsed(address indexed authorizer, bytes32 indexed nonce);
```

So if the payer sets `authorization.nonce` to the sha256 of a signed document, that log **is** the
commitment: permanent, public, timestamped, and binding the document to the settlement.

Nothing else is needed. No memo field, no extra calldata, no change to the x402 spec, no change to
USDC.

Three properties fall out of that, and they are why the design is shaped this way:

- **The digest discloses nothing.** Committing and publishing are separate decisions. Bind a document
  to a payment now and reveal it later, to one counterparty, or never.
- **A commitment is single-use.** EIP-3009 nonces cannot repeat per authorizer, so one attestation
  binds to at most one settlement — replaying it reverts on-chain. That is the point: an attestation
  is about *this* payment.
- **It works without us.** Any x402 payment on any chain can carry a memo. Roundhouse indexes the
  nonce and holds the document; it does not sit in the path.

## The document

Nine fields, fixed order, no whitespace. An absent optional is the **empty string**, not omitted —
EIP-712 hashes a fixed struct, and a disappearing field would change the type hash.

```json
{
  "kya": 1,
  "agent": "0xaaaa…",
  "chainId": 8453,
  "subject": "https://example.com/report.csv",
  "statement": "This report was produced by agent alpha from the settlements index on 2026-08-20.",
  "dataHash": "0xe3b0c442…",
  "issuedAt": "2026-08-20T10:00:00.000Z",
  "expiresAt": "",
  "nonce": "0x11…"
}
```

| Field | Meaning |
| --- | --- |
| `kya` | Document version. `1` |
| `agent` | The signing wallet, lowercased. **Sign with the wallet you pay from** — that is what ties the claim to a payment history rather than to an unrelated key |
| `chainId` | The EIP-712 domain chain. Should match where the payment settles |
| `subject` | What the statement is about: a resource URL, a counterparty wallet, an ERC-8004 id. `""` when unscoped |
| `statement` | The claim itself, in words. This is what the signature asserts |
| `dataHash` | sha256 of the canonical form of an arbitrary JSON payload, so you can commit to data without disclosing it. sha256 of the empty string when there is none |
| `issuedAt` | ISO 8601. Rejected more than 5 minutes in the future or more than a year old |
| `expiresAt` | ISO 8601, or `""` for no expiry. Reported on the public page; never a reason to reject a write |
| `nonce` | 32 random bytes **of the document**, so two otherwise-identical statements are distinct. Not the payment's nonce |

**The memo** is `sha256` of the canonical serialisation of exactly those bytes. It is the
attestation's primary key, its URL, and the `bytes32` you put in `authorization.nonce`.

> [!CAUTION]
> Canonicalisation is load-bearing, exactly as it is for
> [agent cards](https://www.roundhouse.studio/docs/identity/agent-cards#the-canonical-json-rule): object keys
> sorted at every depth, array order kept, `undefined` dropped. Two parties
> holding the same payload must compute the same `dataHash` whatever their JSON
> serialiser does with key order.

## Signing

EIP-712, so a wallet can render what is being attested to:

```text
domain      { name: "Roundhouse KYA", version: "1", chainId }
primaryType Attestation
types       Attestation(uint8 kya, address agent, string subject, string statement,
                        bytes32 dataHash, string issuedAt, string expiresAt, bytes32 nonce)
```

`chainId` is in the **domain** and not in the struct — the domain already binds the chain, so a
signature lifted onto another chain fails, and duplicating it would force every caller to marshal a
`uint256` bigint through JSON for no extra guarantee. There is no `verifyingContract`: KYA is
entirely off-chain.

`personal_sign` over the readable rendering is accepted as a fallback for runtimes that only expose
message signing; verifiers try both and record which one recovered. **ECDSA only** — a smart-account
(ERC-1271) signature needs an RPC round trip, so it is reported as `unrecognised_signature` rather
than silently accepted.

## Attaching one to a payment

The two halves are independent — either works without the other.

**On-chain commitment.** Set `authorization.nonce = memo` when you sign the EIP-3009 authorization.
That is the whole mechanism.

```javascript
// The browser signer does this for you, and rejects a malformed memo rather than
// falling back to a random nonce: a payment made with a random nonce commits to
// nothing, while appearing to have committed the document.
await signX402Payment({ ...requirements, memo });
```

**Off-chain delivery.** `X-PAYMENT-MEMO`, base64 JSON, alongside `X-PAYMENT`:

```json
{ "attestation": { }, "signature": "0x…", "method": "eip712", "data": { }, "publish": false }
```

A resource that records memos advertises it in its `402` body, beside `accepts`:

```json
{
  "x402Version": 1,
  "error": "payment required",
  "accepts": [],
  "kya": {
    "version": 1,
    "header": "X-PAYMENT-MEMO",
    "required": false,
    "commitment": "authorization.nonce"
  }
}
```

Memos are optional. A memo that is present but malformed, expired, or whose signature does not
recover is rejected with `400` **before** verify and settle, so a payer learns the memo was
unusable while the payment can still be retried.

[`GET /v0/test/x402`](https://www.roundhouse.studio/docs/guides/first-x402-payment) advertises KYA and reports the outcome under
`kya` in its response, so you can exercise the whole path for a cent.

## The endpoints

```http
POST /v0/kya                    verify and store an attestation
POST /v0/kya/verify             verify one without storing it
GET  /v0/kya/<digest>           read one attestation
GET  /v0/kya/agents/<wallet>    attestations by one signer
```

Every read **re-derives the digest from the stored document**, so a page is proof rather than a
database lookup. If the bytes were altered, the digest would not match and the record would not
resolve.

## The human tool

**[`/kya`](https://www.roundhouse.studio/kya)** is connect → compose → sign → publish, plus a paste-and-verify tab for checking
someone else's. **`/kya/<digest>`** is the public verified-signature page: it shows the canonical
bytes and the signature so a reader can recompute both without trusting us.

| | |
| --- | --- |
| [Sign an attestation](https://www.roundhouse.studio/kya) | Compose, sign, and choose whether to publish |
| [Verify one](https://www.roundhouse.studio/kya) | Paste a document and signature; nothing is stored |
| [`/v0/kya/verify`](https://www.roundhouse.studio/docs/api/playground) | The same check over HTTP, in the playground |

## What it is not

- **Not an identity claim.** An attestation proves a wallet *said* something. Who owns that wallet is
  what [ERC-8004](https://www.roundhouse.studio/docs/identity/erc-8004) and ENS resolution are for, and the two compose — the
  public page links the signer to its agent profile, where the resolved identity lives.
- **Not a global surface.** Attestations are read one digest at a time, or per signer. There is no
  site-wide attestation feed, count or ranking; [the aggregation rule](https://www.roundhouse.studio/docs/data/data-model#the-aggregation-rule)
  applies here as everywhere.
- **Not custodial or authoritative.** An unverified document never gets a public page.

## Privacy

**A published attestation is public forever.** `publish` defaults to `false`, and there is no public
unpublish path — a document, once disclosed, cannot be un-disclosed, and a button implying otherwise
would be a lie. Commit now, disclose later, is the intended shape.

Attestations that fail verification are kept deliberately: a forged memo attached to a real payment
is evidence.

## Worth attaching a memo to

The mechanism is general, so these are illustrative rather than exhaustive:

- **Provenance.** "This dataset was produced by me, from these inputs, at this time" — with
  `dataHash` committing to the output without publishing it.
- **Terms.** "I am paying for the licence described in this document", bound to the payment that
  bought it.
- **Delegation.** "I am acting for this principal on this task."
- **A disclosure you may need later.** Commit now, publish only if it becomes contested.

```prompt
Attach a KYA memo to your next x402 payment, and show me the proof.

1. Compose the attestation: subject, statement, and a dataHash over the payload
   if there is one. Show me the document before signing it.
2. Canonicalise it (keys sorted at every depth, no whitespace), sha256 it, and
   sign the EIP-712 Attestation over the "Roundhouse KYA" domain. Print the
   digest.
3. Make the payment with authorization.nonce set to that digest — NOT a random
   nonce. If your signer will not accept the memo, stop and tell me; do not fall
   back to a random nonce.
4. Send the document off-chain in the X-PAYMENT-MEMO header, with publish: false.
5. Afterwards, show me GET /api/v0/kya/<digest> and the settlement's tx hash, and
   confirm the AuthorizationUsed nonce in the receipt equals the digest.
```

## Next steps

- [Test your agent identity](https://www.roundhouse.studio/docs/guides/test-your-agent-identity) — verify an attestation resolves
- [ERC-8004](https://www.roundhouse.studio/docs/identity/erc-8004) — who the signing wallet belongs to
- [Anatomy of a payment](https://www.roundhouse.studio/docs/x402/anatomy-of-a-payment) — the nonce this reuses
- [Vet a counterparty](https://www.roundhouse.studio/docs/guides/vet-a-counterparty) — deciding whether to pay at all

---

Every page in these docs is available as markdown at its own URL plus `.md`.
Full index: https://www.roundhouse.studio/docs.md
