# @nerochain/x402-extensions

> Optional protocol extensions — payment-identifier (idempotency) and access-token (signed proof-of-payment for cached access).

`@nerochain/x402-extensions` ships optional capabilities that ride on the V2 envelope's `extensions` field or on orthogonal HTTP headers. They are composable with the canonical `@nerochain/x402-server` middleware and `@nerochain/x402-client` signers; nothing in the core wire format changes.

Two extensions are included today.

## Install

```bash
pnpm add @nerochain/x402-extensions
```

## `payment-identifier` — idempotency on retries

A client-supplied identifier carried inside `extensions.paymentIdentifier`. Two requests with the same identifier from the same payer SHOULD result in at most one on-chain settlement; the second response either matches the first (cached) or surfaces a definitive error if the first has not yet completed.

The identifier is opaque to the protocol — any string works. UUIDv4 (or any ≥ 16-byte random value) makes collision negligible.

### Client side

```ts
import {
  generatePaymentIdentifier,
  withPaymentIdentifier,
} from "@nerochain/x402-extensions";

const id = generatePaymentIdentifier();   // crypto.randomUUID() under the hood

// Wrap your signer's payload before sending.
const payload = withPaymentIdentifier(originalPayload, id);
```

Reuse the **same** `id` across retries of the same logical request. A new `id` per logical request, a stable `id` per retry. Persist it in your retry buffer; do not regenerate.

### Server / facilitator side

```ts
import { extractPaymentIdentifier } from "@nerochain/x402-extensions";

const id = extractPaymentIdentifier(payload);
if (id) {
  const cached = await idempotencyStore.get(id);
  if (cached) return cached;             // dedup hit
  // ... process normally, then store the result keyed on `id` ...
}
```

The merchant or facilitator decides where the idempotency store lives. Postgres, Redis, in-memory — any persistence with a `Map<string, Result>` interface works. The identifier travels through the V2 wire format unchanged.

### Relationship to `requestHash`

`requestHash` (defined in the [aa-native scheme](/docs/core-concepts/aa-native-scheme)) is the on-chain replay key, derived deterministically from `(merchant, chainId, method, endpoint, timestampBucket, clientNonce)`. It is the **authoritative** dedup mechanism — the settlement contract rejects duplicate `requestHash` values regardless of any off-chain extension.

`paymentIdentifier` is complementary. It lives at the **HTTP layer**, not the chain layer. It lets a merchant's HTTP layer dedup retries before any chain interaction even happens. Use both: `paymentIdentifier` saves bundler round-trips on duplicates; `requestHash` is the safety net.

## `access-token` — signed proof-of-payment

After a successful settlement, the merchant issues a short-lived signed token from the receipt. The client presents the token on follow-up requests within a TTL to bypass payment for the same resource.

The token rides in a custom header (`X-X402-Access-Token`) so it is orthogonal to the core wire format. The token is HMAC-signed by the merchant: only the merchant can issue or verify; clients treat it as opaque.

### Issuing

```ts
import { issueAccessToken } from "@nerochain/x402-extensions";

// Inside the merchant's settlement-success branch, after readSettlementReceipt(...):
const token = issueAccessToken({
  receipt: {
    payer:           settlement.payer,
    payTo:           settlement.payTo,
    transactionHash: settlement.transactionHash,
  },
  secret:      process.env.MERCHANT_ACCESS_TOKEN_SECRET!,    // ≥ 32 bytes of entropy
  ttlSeconds:  3600,                                          // 1 hour
  scope:       ["GET /api/article/123"],                      // optional
});

res.setHeader("X-X402-Access-Token", token);
res.json({ content: "..." });
```

### Verifying

```ts
import {
  ACCESS_TOKEN_HEADER,
  verifyAccessToken,
} from "@nerochain/x402-extensions";

const tokenHeader = req.headers[ACCESS_TOKEN_HEADER.toLowerCase()];
if (typeof tokenHeader === "string") {
  const result = verifyAccessToken(
    tokenHeader,
    process.env.MERCHANT_ACCESS_TOKEN_SECRET!,
  );
  if (result.valid) {
    // result.claims = { iss, sub, iat, exp, txh, scope? }
    // Skip the payment gate; serve the resource directly.
    return next();
  }
  // result.reason ∈ { "malformed", "signature_mismatch", "expired" }
}
// fall through to normal x402 gate
```

### Security model

- **Secret stays merchant-side.** Only the merchant issues and verifies. A leaked secret means an attacker can mint tokens for any payer; rotate the secret if compromised.
- **Tokens are bound to the original settlement.** `claims.txh` records the on-chain hash that authorized the token; a compliant verifier can additionally check that the transaction is real and matches `claims.sub` (payer) and `claims.iss` (merchant) on chain.
- **TTL bounds blast radius.** Short TTLs (hours, not days) limit the impact of a leaked token.
- **Scope is advisory.** The token's `scope` array is application-level metadata; the merchant's verifier decides how to enforce it.

This is a deliberately minimal mechanism. CAIP-122 / SIWX with full session management is a richer extension worth doing later; for typical "pay once, read for an hour" patterns, signed access tokens are enough.

## Combining the two

A common flow on a paid endpoint with caching:

1. Client sends a request with `X-X402-Access-Token` if it has one.
2. Server verifies the token. On success, serve the response — no payment.
3. On failure (missing/expired/invalid), gate the request as usual: emit 402, accept payment, settle.
4. After settlement, issue a fresh access token. Optionally include a `paymentIdentifier` in the payload to deduplicate retries of the same logical paid request before reaching the bundler.
5. Return the response with both `PAYMENT-RESPONSE` (settlement receipt) and `X-X402-Access-Token` (cached-access grant) headers.

The client's next call lands in step 2 and skips the payment gate until the token expires.
