> ## Documentation Index
> Fetch the complete documentation index at: https://docs.payai.network/llms.txt
> Use this file to discover all available pages before exploring further.

# Manual Flow

> Step-by-step guide to implementing the x402 payment protocol manually in TypeScript without SDK wrappers.

## Manual x402 client flow (TypeScript)

This page shows how to perform the **entire x402 flow by hand** in TypeScript: no starter kit, no `@x402/fetch` or `@x402/axios` wrappers. You send raw HTTP requests, decode the 402 response, build the payment payload, and resend with the payment signature.

For production you'll usually use the [Fetch](/x402/clients/typescript/fetch) or [Axios](/x402/clients/typescript/axios) quickstarts; this tutorial is for agents or environments that need a minimal, dependency-light implementation or want to understand the protocol step by step.

***

## 1. Request the resource

Send a normal GET (or other method) to the protected URL. No special headers yet.

```ts theme={null}
const url = "http://localhost:4021/weather";
const response = await fetch(url, { method: "GET" });
```

***

## 2. Handle 402 and decode PAYMENT-REQUIRED

If the server requires payment, it returns **402 Payment Required** and puts the payment options in the **PAYMENT-REQUIRED** header (base64-encoded JSON).

```ts theme={null}
if (response.status !== 402) {
  // 200: already paid or free; 4xx/5xx: handle as needed
  console.log(await response.json());
}

const paymentRequiredB64 = response.headers.get("PAYMENT-REQUIRED");
if (!paymentRequiredB64) throw new Error("402 without PAYMENT-REQUIRED header");

const paymentRequiredJson = Buffer.from(paymentRequiredB64, "base64").toString("utf-8");
const paymentRequired = JSON.parse(paymentRequiredJson) as {
  x402Version: number;
  error: string;
  resource: { url: string; description: string; mimeType?: string };
  accepts: Array<{
    scheme: string;
    network: string;
    amount: string;
    asset: string;
    payTo: string;
    maxTimeoutSeconds: number;
    extra?: Record<string, unknown>;
  }>;
  extensions?: Record<string, unknown>;
};
```

Exact field names and shapes are in the [x402 Reference](/x402/reference). You must use **x402Version: 2** and the **accepts** array.

***

## 3. Choose an accepted option

Pick one entry from `paymentRequired.accepts` by network or scheme. For **EVM** (steps below), take an option whose `network` starts with `eip155:`. For **Solana**, take one whose `network` starts with `solana:` (see [Solana (exact scheme)](#solana-exact-scheme) in step 4).

```ts theme={null}
// EVM path:
const accepted = paymentRequired.accepts.find((a) => a.network.startsWith("eip155:"));
if (!accepted) throw new Error("No EVM accept option");
```

***

## 4. Build the payment payload

For the **exact** scheme on **EVM**, the client must produce an EIP-3009-style authorization and sign it with EIP-712 (see [x402 Reference](/x402/reference)). The payload is sent in the **PAYMENT-SIGNATURE** header as base64-encoded JSON.

Example shape (simplified; real code must use correct domain, types, and signing):

```ts theme={null}
import { privateKeyToAccount } from "viem/accounts";
import { signTypedData } from "viem/accounts";

// You need: payer private key, accepted requirement, token contract (asset), validAfter/validBefore, nonce
const payload = {
  x402Version: 2,
  scheme: "exact",
  network: accepted.network,
  accepted: {
    scheme: accepted.scheme,
    network: accepted.network,
    amount: accepted.amount,
    asset: accepted.asset,
    payTo: accepted.payTo,
    maxTimeoutSeconds: accepted.maxTimeoutSeconds,
    ...(accepted.extra && { extra: accepted.extra }),
  },
  payload: {
    signature: "0x...", // EIP-712 signature from signTypedData
    authorization: {
      from: payerAddress, // Replace with your wallet address
      to: accepted.payTo,
      value: accepted.amount,
      validAfter: "0",
      validBefore: String(Math.floor(Date.now() / 1000) + 300),
      nonce: "0x..." + Buffer.from(crypto.getRandomValues(new Uint8Array(32))).toString("hex"),
    },
  },
  extensions: {},
};
const paymentSignatureB64 = Buffer.from(JSON.stringify(payload)).toString("base64");
```

#### Solana (exact scheme)

On **Solana**, you choose a Solana accept option (e.g. `network` starting with `solana:`), then build a **partially-signed transaction** and send it in the payload as base64. The transaction must contain these instructions **in this order**:

1. **SetComputeUnitLimit** — max **40,000** compute units
2. **SetComputeUnitPrice** — max **5** microlamports per compute unit
3. **TransferChecked** — token transfer (amount, mint, decimals, source, destination)
4. *Optional:* Lighthouse (wallets like Phantom/Solflare may add up to two; merchants do not add these)

You need a Solana SDK (e.g. `@solana/web3.js` or `@solana/spl-token`) to build and sign the transaction. Source and destination are **token accounts** (e.g. associated token accounts for the mint); `accepted.payTo` is the recipient **wallet**. Decimals may be in `accepted.extra`. The payment payload is JSON with the transaction in `payload.transaction` as base64:

```ts theme={null}
// 3. Choose Solana option instead of EVM
const accepted = paymentRequired.accepts.find((a) => a.network.startsWith("solana:"));
if (!accepted) throw new Error("No Solana accept option");

// 4. Build Solana transaction (pseudocode — use @solana/web3.js / @solana/spl-token in practice)
// - Add setComputeUnitLimit(40_000)
// - Add setComputeUnitPrice(5)   // microlamports
// - Add createTransferCheckedInstruction: amount=accepted.amount, mint=accepted.asset,
//   decimals from token metadata or accepted.extra, source=payerTokenAccount, destination=payeeTokenAccount
// - Sign with payer keypair (partial sign; facilitator may add Lighthouse instructions)
// - Serialize transaction to buffer, then base64

// const transactionBuffer = serializedSignedTx;  // from your Solana library
const transactionB64 = Buffer.from(transactionBuffer).toString("base64");

const payload = {
  x402Version: 2,
  scheme: "exact",
  network: accepted.network,
  accepted: {
    scheme: accepted.scheme,
    network: accepted.network,
    amount: accepted.amount,
    asset: accepted.asset,
    payTo: accepted.payTo,
    maxTimeoutSeconds: accepted.maxTimeoutSeconds,
    ...(accepted.extra && { extra: accepted.extra }),
  },
  payload: {
    transaction: transactionB64,
  },
  extensions: {},
};
const paymentSignatureB64 = Buffer.from(JSON.stringify(payload)).toString("base64");
```

Exact instruction formats, token-account derivation, and limits are in the [x402 Reference (§6.2 Solana)](/x402/reference).

***

## 5. Resend the request with PAYMENT-SIGNATURE

Send the **same** request again (same URL and method), this time adding the **PAYMENT-SIGNATURE** header.

```ts theme={null}
const retryResponse = await fetch(url, {
  method: "GET",
  headers: {
    "PAYMENT-SIGNATURE": paymentSignatureB64,
  },
});
```

***

## 6. Parse the response and PAYMENT-RESPONSE

On success the server returns **200** and may include **PAYMENT-RESPONSE** (base64-encoded settlement details). Decode the header and parse the JSON to get settlement info.

```ts theme={null}
if (!retryResponse.ok) {
  throw new Error(`Request failed: ${retryResponse.status}`);
}

const body = await retryResponse.json();
const paymentResponseB64 = retryResponse.headers.get("PAYMENT-RESPONSE");
if (paymentResponseB64) {
  const paymentResponse = JSON.parse(
    Buffer.from(paymentResponseB64, "base64").toString("utf-8")
  ) as { success: boolean; transaction?: string; network?: string; payer?: string };
  console.log("Settlement:", paymentResponse);
}
console.log(body);
```

**Decoded PAYMENT-RESPONSE examples**

On success (EVM):

```json theme={null}
{
  "success": true,
  "transaction": "0x1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef",
  "network": "eip155:84532",
  "payer": "0x857b06519E91e3A54538791bDbb0E22373e36b66"
}
```

On success (Solana / SVM):

```json theme={null}
{
  "success": true,
  "transaction": "AXGcLa7sqSjt7pXV4mpVRP5a77tVjokZbgx8gkQ16X8Wgg3vDkWKMh9BrPTr1f2KrDuf9nSX7FrZEAQTkJ3y5UN",
  "network": "solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdpKuc147dw2N9d",
  "payer": "6KPYDyuRnpuKcm1TerUmwLd2BcaihvhF4Ccrr8beruu2"
}
```

On failure (e.g. insufficient funds), the server may return a non-2xx status and/or a PAYMENT-RESPONSE header with an error. Example decoded payload:

```json theme={null}
{
  "x402Version": 2,
  "error": "Payment failed: insufficient funds",
  "accepts": [...]
}
```

***

## Summary

| Step | Action                                                                                       |
| ---- | -------------------------------------------------------------------------------------------- |
| 1    | GET (or other method) the resource URL                                                       |
| 2    | If status is 402, decode **PAYMENT-REQUIRED** (base64 JSON)                                  |
| 3    | Choose one entry from **accepts**                                                            |
| 4    | Build payment payload (EIP-712 sign for EVM exact scheme; or Solana tx) and base64-encode it |
| 5    | Resend the same request with **PAYMENT-SIGNATURE** header                                    |
| 6    | On 200, use response body and optionally decode **PAYMENT-RESPONSE**                         |

For exact field names, types, and facilitator usage, see the [x402 Reference](/x402/reference). For a ready-made client, use the [Fetch](/x402/clients/typescript/fetch) or [Axios](/x402/clients/typescript/axios) quickstarts.

## Need help?

<Card title="Join our Community" icon="discord" href="https://discord.gg/eWJRwMpebQ">
  Have questions or want to connect with other developers? Join our Discord server.
</Card>
