npm package: @sahilw/dust-http402
Description: HTTP 402 private payment protocol — buyer/seller SDK for privacy-preserving micropayments
Author: Dust Protocol
Latest version: 0.1.0
Deprecated: Moved to dust-http402 (unscoped). Install: npm i dust-http402

# @dust/http402

[![npm version](https://img.shields.io/npm/v/@dust/http402)](https://www.npmjs.com/package/@dust/http402)

HTTP 402 private payment protocol for the Dust Protocol. Provides the types, header encoding, payment client, server-side payment verifier, and facilitator client for building pay-per-request APIs with three privacy levels.

## Installation

```bash
npm install @dust/http402
```

## Overview

HTTP 402 enables a server to respond "Payment Required" before fulfilling a request. The client pays automatically and retries. This package implements a Dust-flavored variant supporting three privacy levels:

- **`transparent`** — The payer signs an EIP-712 intent. Payer identity is visible.
- **`stealth`** — Payment is sent to a one-time stealth address. Payer and recipient are unlinkable.
- **`private`** — Payment is made via DustPool V2 ZK proof. Amount and participants are hidden.

The `transparent` mode is fully implemented. `stealth` and `private` modes are defined in the type system and headers but require integration with `@dust/stealth` and `@dust/pool` in the application layer.

## Quick Start — Server

```typescript
import { createPaymentVerifier, encodePaymentRequirement, decodePaymentProof, HEADER_PAYMENT_REQUIREMENT, HEADER_PAYMENT_PROOF } from '@dust/http402'

const verifier = createPaymentVerifier({
  facilitatorUrl: 'https://facilitator.example.com',
  chainId: 11155111,
})

// On each request
function handler(req, res) {
  const proofHeader = req.headers[HEADER_PAYMENT_PROOF.toLowerCase()]

  if (!proofHeader) {
    const requirement = verifier.createRequirement({
      amount: '0.001',
      asset: 'ETH',
      chainId: 11155111,
      recipient: '0xYourRecipient',
      privacy: 'transparent',
      facilitator: 'https://facilitator.example.com',
    })
    res.status(402)
      .set(HEADER_PAYMENT_REQUIREMENT, encodePaymentRequirement(requirement))
      .json({ error: 'Payment Required' })
    return
  }

  const proof = decodePaymentProof(proofHeader)
  const requirement = verifier.createRequirement({ /* same config */ })
  requirement.nonce = proof.nonce

  const valid = await verifier.verifyPayment(proof, requirement)
  if (!valid) {
    res.status(402).json({ error: 'Payment verification failed' })
    return
  }

  res.json({ data: 'protected content' })
}
```

## Quick Start — Client

```typescript
import { createPaymentClient } from '@dust/http402'

const client = createPaymentClient({
  chainId: 11155111,
  signer: {
    signTypedData: (domain, types, value) => wallet.signTypedData({ domain, types, message: value }),
    getAddress: () => wallet.getAddress(),
  },
})

// Automatically handles 402 — fetches, detects 402, pays, retries
const response = await client.fetchWithPayment('https://api.example.com/premium')
const data = await response.json()
```

## API Reference

### Types

#### `PrivacyLevel`

```typescript
type PrivacyLevel = 'transparent' | 'stealth' | 'private'
```

#### `PaymentRequirement`

Sent by the server in the `X-Dust-402` header when returning a 402 response.

```typescript
interface PaymentRequirement {
  amount: string      // human-readable, e.g. '0.001'
  asset: string       // token symbol or address, e.g. 'ETH'
  chainId: number
  recipient: string   // address that should receive payment
  privacy: PrivacyLevel
  facilitator: string // facilitator base URL
  nonce: string       // UUID, unique per requirement
  expiresAt?: number  // unix seconds
}
```

#### `PaymentProof`

Sent by the client in the `X-Dust-Payment` header on retry.

```typescript
interface PaymentProof {
  nonce: string           // must match requirement nonce
  privacy: PrivacyLevel
  chainId: number
  amount?: string
  asset?: string
  recipient?: string
  // transparent mode:
  signature?: string
  // stealth mode:
  stealthAddress?: string
  ephemeralPublicKey?: string
  txHash?: string
  // private mode:
  proof?: string
  nullifiers?: string[]
  outputCommitments?: string[]
  publicSignals?: string[]
}
```

#### `PaymentReceipt`

```typescript
interface PaymentReceipt {
  nonce: string
  privacy: PrivacyLevel
  status: 'verified' | 'settled' | 'failed'
  txHash?: string
  timestamp: number
  amount: string
  asset: string
  chainId: number
}
```

#### `FacilitatorResponse`

```typescript
interface FacilitatorResponse {
  valid: boolean
  settled: boolean
  receipt?: PaymentReceipt
  error?: string
}
```

#### `PaymentConfig`

Input to `verifier.createRequirement()`.

```typescript
interface PaymentConfig {
  amount: string
  asset: string
  chainId: number
  recipient: string
  privacy: PrivacyLevel
  facilitator: string
  ttlSeconds?: number   // default 300
}
```

#### `PaymentClientConfig`

Input to `createPaymentClient()`.

```typescript
interface PaymentClientConfig {
  chainId: number
  signer?: {
    signTypedData(domain, types, value): Promise<string>
    getAddress(): Promise<string>
  }
  facilitatorUrl?: string
  maxRetries?: number
}
```

#### `VerifierConfig`

Input to `createPaymentVerifier()`.

```typescript
interface VerifierConfig {
  facilitatorUrl: string
  chainId: number
  toleranceSeconds?: number  // expiry tolerance, default 30
}
```

---

### Headers

Header names and base64 encoding/decoding utilities.

#### `HEADER_PAYMENT_REQUIREMENT`

`'X-Dust-402'` — sent by server in 402 response.

#### `HEADER_PAYMENT_PROOF`

`'X-Dust-Payment'` — sent by client on retry.

#### `encodePaymentRequirement(req: PaymentRequirement): string`

Base64-encode a `PaymentRequirement` for use in the `X-Dust-402` header.

#### `decodePaymentRequirement(header: string): PaymentRequirement`

Decode and validate a `PaymentRequirement` from a header value. Throws if required fields are missing.

#### `encodePaymentProof(proof: PaymentProof): string`

Base64-encode a `PaymentProof` for use in the `X-Dust-Payment` header.

#### `decodePaymentProof(header: string): PaymentProof`

Decode and validate a `PaymentProof` from a header value.

```typescript
import {
  HEADER_PAYMENT_REQUIREMENT,
  HEADER_PAYMENT_PROOF,
  encodePaymentRequirement,
  decodePaymentRequirement,
  encodePaymentProof,
  decodePaymentProof,
} from '@dust/http402'

const encoded = encodePaymentRequirement(requirement)
// 'eyJhbW91bnQi...'

const decoded = decodePaymentRequirement(encoded)
```

---

### Payment Verifier (Server-Side)

#### `createPaymentVerifier(config: VerifierConfig): PaymentVerifier`

Create a server-side verifier that delegates to a facilitator.

```typescript
import { createPaymentVerifier } from '@dust/http402'

const verifier = createPaymentVerifier({
  facilitatorUrl: 'https://facilitator.example.com',
  chainId: 11155111,
  toleranceSeconds: 30,
})
```

#### `PaymentVerifier` interface

```typescript
interface PaymentVerifier {
  createRequirement(config: PaymentConfig): PaymentRequirement
  verifyPayment(proof: PaymentProof, requirement: PaymentRequirement): Promise<boolean>
}
```

**`createRequirement(config)`** — Builds a `PaymentRequirement` with a fresh UUID nonce and an expiry of `config.ttlSeconds` (default 300) seconds from now.

**`verifyPayment(proof, requirement)`** — Returns `false` if:
- `proof.nonce !== requirement.nonce`
- `proof.chainId !== requirement.chainId`
- `proof.privacy !== requirement.privacy`
- The requirement has expired (beyond `toleranceSeconds`)
- The facilitator returns `valid: false`

```typescript
const requirement = verifier.createRequirement({
  amount: '0.001',
  asset: 'ETH',
  chainId: 11155111,
  recipient: '0xYourAddress',
  privacy: 'transparent',
  facilitator: 'https://facilitator.example.com',
})

// After receiving proof from client:
const valid = await verifier.verifyPayment(proof, requirement)
```

---

### Payment Client

#### `createPaymentClient(config: PaymentClientConfig): PaymentClient`

Create a client that automatically handles 402 responses.

```typescript
import { createPaymentClient } from '@dust/http402'

const client = createPaymentClient({
  chainId: 11155111,
  signer: {
    signTypedData: async (domain, types, value) => {
      return walletClient.signTypedData({ domain, types, message: value })
    },
    getAddress: async () => walletClient.account.address,
  },
})
```

#### `PaymentClient` interface

```typescript
interface PaymentClient {
  fetchWithPayment(url: string, options?: RequestInit): Promise<Response>
  processPaymentRequired(requirement: PaymentRequirement): Promise<PaymentProof>
}
```

**`fetchWithPayment(url, options?)`** — Fetch a URL. If the server returns 402:
1. Reads the `X-Dust-402` header
2. Calls `processPaymentRequired` to build a proof
3. Verifies the proof with the facilitator
4. Retries the request with `X-Dust-Payment` header attached

```typescript
const response = await client.fetchWithPayment('https://api.example.com/premium-data')
if (response.ok) {
  const data = await response.json()
}
```

**`processPaymentRequired(requirement)`** — Build a `PaymentProof` for a given requirement.
- `transparent`: Signs an EIP-712 `Payment` struct with the provided signer. Requires `config.signer`.
- `stealth`: Throws `'Stealth payment mode not yet implemented'`.
- `private`: Throws `'Private (ZK) payment mode not yet implemented'`.

---

### Facilitator Client

The facilitator is an off-chain service that verifies and settles payments. It bridges between the client's payment proof and the on-chain state.

#### `createFacilitatorClient(baseUrl: string): FacilitatorClient`

Create a facilitator client with automatic retry (up to 2 retries on 5xx) and 10-second timeout per attempt.

```typescript
import { createFacilitatorClient } from '@dust/http402'

const fac = createFacilitatorClient('https://facilitator.example.com')

const result = await fac.verify(proof)
if (result.valid) {
  console.log('Payment valid')
}

const receipt = await fac.getReceipt(nonce)
```

#### `FacilitatorClient` interface

```typescript
interface FacilitatorClient {
  verify(proof: PaymentProof): Promise<FacilitatorResponse>
  settle(proof: PaymentProof): Promise<FacilitatorResponse>
  getReceipt(nonce: string): Promise<PaymentReceipt | null>
  health(): Promise<{ status: string; version: string }>
}
```

**`verify(proof)`** — POST to `/api/v2/http402/verify`. Returns `{ valid, settled, receipt?, error? }`.

**`settle(proof)`** — POST to `/api/v2/http402/settle`. Used to finalize on-chain settlement.

**`getReceipt(nonce)`** — GET `/api/v2/http402/receipt/:nonce`. Returns the receipt or `null` if not found.

**`health()`** — GET `/api/v2/http402/health`. Returns `{ status, version }`.

---

## EIP-712 Payment Signing (Transparent Mode)

When privacy is `'transparent'`, the client signs the following typed data:

```typescript
domain = {
  name: 'DustPayment',
  version: '1',
  chainId: <requirement.chainId>,
}

types = {
  Payment: [
    { name: 'nonce',     type: 'string'  },
    { name: 'amount',    type: 'string'  },
    { name: 'asset',     type: 'string'  },
    { name: 'recipient', type: 'string'  },
    { name: 'chainId',   type: 'uint256' },
  ],
}
```

The resulting signature is included in `PaymentProof.signature`.

## License

MIT