# Payment Protocol V1

Date: 2026-09-05  
Status: This document describes the **implemented** application-layer protocol.

The blockchain is the source of consensus. The payment protocol is not a
central payment company. There is no required central server, database,
sequencer, gateway, or alias resolver.

```
DECENTRALIZED L1
        |
   Node / Node / Node
        |
  PAYMENT PROTOCOL
        |
 Wallet / Merchant / POS
```

Gateway = OPTIONAL convenience. It is not source of truth.

Status codes: IMPLEMENTED | PARTIALLY_IMPLEMENTED | PLANNED

## Consensus data vs payment application data

| Layer | Fields | Authority |
| --- | --- | --- |
| CONSENSUS | `from`, `to`, `amount`, `nonce`, `networkId`, `chainId`, signature, block, QC | Blockchain |
| PAYMENT APPLICATION | PaymentRequest, paymentId, memo, merchantReference, QR/URI, invoice status | Application; derived from the chain |

`Transaction.data` is already a signed V1 field. Payment binding writes
`CRYPTO_PROJECT|PAYMENT_BIND|V1|<paymentId>` into that field. This binding:

- does not change transaction validity
- does not change state transition / balance / finality rules
- is only for matching and verification

Merchant metadata is not a consensus-state field.

## Canonical Payment Request

```
PaymentRequest {
  protocolVersion    // "1"
  chainId
  networkId
  recipient          // Account Model address
  amount             // positive decimal string (V1 native unit)
  asset              // V1: "NATIVE"
  paymentId          // 64 hex SHA-256
  expiration         // unix ms; 0 = none
  memo               // optional; otherwise ""
  merchantReference  // optional invoice/ref; otherwise ""
  alias              // optional; otherwise ""; if set, canonical acct:name
  merchantPublicKey  // optional
  merchantSignature  // optional
}
```

Serialization: `canonicalJson` (same Transaction Protocol serializer).
A request with a changed amount, recipient, chain, network, or paymentId is
invalid (`paymentId` will not match when recomputed).

## Payment identifier

```
CRYPTO_PROJECT|PAYMENT_ID|V1|<networkId>|<chainId>
<canonicalJson({
  amount, asset, chainId, expiration, memo, merchantReference,
  networkId, protocolVersion, recipient
  [, alias]   // only if non-empty; empty alias keeps the M3 paymentId
})>
```

`paymentId = SHA-256(payload)` (64 hex).

Collision assumption: the same identity fields ⇒ the same paymentId (intentional).
For distinct invoices the merchant must use `merchantReference` (e.g. `invoice-2026-00123`).
SHA-256 collision is ignored in practice. There is no central counter or central random ID.

## Optional merchant signature

Default: the request is unsigned; authority is the settlement transaction signature (A).

Optional (B): the merchant signs with Ed25519. Domain:

```
CRYPTO_PROJECT|PAYMENT_REQUEST|V1|<networkId>|<chainId>
<canonicalJson(fields except signature/publicKey)>
```

This signature is **not** blockchain transaction authority.
TRANSACTION_SIGN / CONSENSUS / QC domains are not reused.

## Payment → transaction

```
Payment Request
      |
Customer Wallet (display + confirm)
      |
Signed Blockchain Transaction  (data = PAYMENT_BIND)
      |
Decentralized Network
      |
Finalized Block (QC)
      |
Payment Verification
```

QR / HTTP / UI / mempool acceptance is not settlement.

## Status model

Status is derived from the chain + mempool + (if any) the request expiration field.
A central backend does not impose status.

| Status | Meaning |
| --- | --- |
| REQUESTED | No matching tx; the request has not expired |
| PENDING | Matching tx is in the mempool |
| INCLUDED | Matching tx is in a block; no QC (e.g. demo produce) |
| FINALIZED | Matching tx is in a block that carries a valid consensus finalityProof |
| FAILED | Matching candidate is invalid / rejected |
| EXPIRED | Request expired and there is no matching settlement on chain |
| UNKNOWN | No request and no match on chain |

Transitions:

```
REQUESTED → PENDING → INCLUDED → FINALIZED
REQUESTED → EXPIRED
REQUESTED → INCLUDED          (mempool skipped)
PENDING → FAILED
PENDING → EXPIRED            (tx is ignored; chain tx validity is not broken)
```

Expiration is a Payment Request rule. It does not automatically invalidate a
valid blockchain transaction.

## Finality

A merchant accepts FINALIZED only when all of these hold:

1. The transaction exists and is canonical
2. `to` = request.recipient
3. `amount` is compared to the expected value (match / under / over)
4. `networkId` / `chainId` are correct
5. The nonce/state transition was applied in the block
6. The transaction is in the correct block
7. The block is finalized by consensus (`finalityProof` + `justification`)

Light client (M9): `PaymentProof` + `walletVerifyPaymentProof`. A gateway may
distribute the package; `FINALIZED` comes only from QC+Merkle verification.
Full-node view: `GET /payments/:paymentId`. Proof: `GET /proofs/payment/:id`
(`authoritative: false`).
A gateway response is not cryptographic finality.

Demo `produceBlock` does not produce a QC. On that path status is at most INCLUDED.

## Lookup

Priority: `paymentId` (from the tx.data bind). Also `transactionId`,
`merchantReference` (multiple results). Recipient+amount+nonce are helpers.

INDEX = CACHE / PERFORMANCE  
BLOCKCHAIN = SOURCE OF TRUTH

The index is node-local; it is rebuilt from the chain after a restart.
A poisoned index does not change blockchain state.

## QR and URI

The same canonical PaymentRequest is independent of the carrier.

- QR payload = canonical JSON
- URI = `cryptoproject://pay?<canonical query>`

URI ↔ PaymentRequest is deterministic. Field order does not change parse.
If `alias` is present it stays in the query as canonical `acct:name`; the decoder
does not silently replace recipient with the alias target.

Future: NFC, Bluetooth, copy/paste carry the same object.

## Refund model

There is no automatic central refund. A refund is a new blockchain transfer.
Optional `data` = `CRYPTO_PROJECT|PAYMENT_REFUND|V1|<originalPaymentId>`
is application metadata only; it is not a consensus rule.

## Under / over / duplicate (application policy)

The blockchain records the amount that happened. Merchant software decides.

| Case | Consensus | Merchant (V1 default) |
| --- | --- | --- |
| UNDERPAYMENT | valid tx | invoice not paid; partial record |
| OVERPAYMENT | valid tx | invoice paid; excess tracked separately |
| DUPLICATE | nonce replay rejected or second tx | second settlement for the same paymentId |
| MULTIPLE | more than one valid tx | separated by reference |
| PARTIAL | none | same as underpayment |

## Replay

The blockchain nonce is authority. The application can reduce accidental double
submit with `paymentId + transactionId`; there is no second replay protocol.

## Versioning

`protocolVersion = "1"`. Unknown versions are rejected.
Future (PLANNED, not present): multi-asset, contracts, recurring, escrow,
proven refunds, multi-sig, offline.

## Gateway

OPTIONAL. Not required for send, receive, verify, finality, or consensus.

## Status summary

| Item | Status |
| --- | --- |
| Alias field (optional) | IMPLEMENTED |
| Canonical PaymentRequest | IMPLEMENTED |
| Payment ID | IMPLEMENTED |
| Optional merchant signature | IMPLEMENTED |
| QR / URI | IMPLEMENTED |
| Node verify API | IMPLEMENTED |
| Wallet / merchant | IMPLEMENTED |
| Gateway required | IMPLEMENTED — no |
| Light client | PLANNED |
| Multi-asset / escrow / staking | PLANNED (outside this milestone) |
