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

# Non-EIP-3009 payments

> Step-by-step Meridian integration guide for Permit2-based payment chains

Use this guide to integrate Meridian x402 payments on EVM chains where the
payment token does not expose native EIP-3009 `transferWithAuthorization`.
It is written for sellers, buyers, and AI coding agents implementing either
side of the payment flow.

This current path uses Permit2 and applies to MegaETH, BSC, BOT Chain, BOT
Chain testnet, and Tempo.

Permit2 settlement is same-chain in Meridian's current flow. Do not add
`extra.destinationChainId` to create an Across route for Permit2 payments. Use
the EIP-3009 guide for cross-chain USDC routes where the source token supports
`transferWithAuthorization`.

## How the flow works

```text theme={"system"}
seller creates payment requirements
buyer approves Permit2
buyer signs a Permit2 witness authorization
buyer sends paymentPayload to seller
seller settles through Meridian POST /v1/settle
Meridian facilitator pulls funds and distributes proceeds
```

Meridian-specific rules:

* `paymentRequirements.payTo` is the Meridian facilitator, not the seller wallet
* `paymentRequirements.asset` is the ERC-20 token the buyer pays with
* `paymentRequirements.network` is the source and settlement chain
* The buyer approves Permit2, not the facilitator and not the exact proxy
* The buyer signs with `x402ExactPermit2Proxy` as the Permit2 spender
* `witness.to` is the Meridian facilitator
* The seller keeps the Meridian API key on the backend

## Prerequisites

Seller:

* A Meridian organization
* An organization API key from `https://mrdn.finance/dev/api-keys`
* A backend endpoint that can receive `paymentPayload` from the buyer

Buyer:

* A wallet on the selected chain
* Balance of the selected ERC-20 token
* Permit2 allowance for that token

Install the common client dependencies:

```bash theme={"system"}
npm install viem @uniswap/permit2-sdk
```

## Network constants

The token addresses below are Meridian's default payment tokens for each
network. If your application accepts another transferable ERC-20 on the same
chain, use that token address in `paymentRequirements.asset` and use that
token's real name, version, and decimals.

```ts theme={"system"}
export const MERIDIAN_API_BASE = "https://api.mrdn.finance/v1";
export const PERMIT2 = "0x000000000022D473030F116dDEE9F6B43aC78BA3";
export const X402_EXACT_PERMIT2_PROXY =
  "0x402085c248EeA27D92E8b30b2C58ed07f9E20001";
// Spender for the "upto" scheme (usage-based pricing); same address on every chain.
export const X402_UPTO_PERMIT2_PROXY =
  "0x402015c795ecb48A360bDC6e35a2EaEb313a0002";

export const NON_EIP_3009_NETWORKS = {
  megaeth: {
    chainId: 4326,
    token: "0xFAfDdbb3FC7688494971a79cc65DCa3EF82079E7",
    tokenName: "USDm",
    tokenVersion: "1",
    facilitator: "0x8E7769D440b3460b92159Dd9C6D17302b036e2d6",
  },
  bsc: {
    chainId: 56,
    token: "0x8AC76a51cc950d9822D68b83fE1Ad97B32Cd580d",
    tokenName: "USDC",
    tokenVersion: "1",
    facilitator: "0x8E7769D440b3460b92159Dd9C6D17302b036e2d6",
  },
  "bot-chain": {
    chainId: 677,
    token: "0xaBabc7Ddc03e501d190C676BF3d92ef0e6e87a3C",
    tokenName: "USDT",
    tokenVersion: "1",
    facilitator: "0x8E7769D440b3460b92159Dd9C6D17302b036e2d6",
  },
  "bot-chain-testnet": {
    chainId: 968,
    token: "0x75edC9335175Fc0552D51D48439F229c10420fe3",
    tokenName: "USDT",
    tokenVersion: "1",
    facilitator: "0x8e633dBf31adCc7D41BE3e95B7c8DD3526B5235A",
  },
  tempo: {
    chainId: 4217,
    token: "0x20C000000000000000000000b9537d11c60E8b50",
    tokenName: "USDC.e",
    tokenVersion: "1",
    facilitator: "0x8E7769D440b3460b92159Dd9C6D17302b036e2d6",
  },
} as const;
```

## Seller setup

### 1. Create a Meridian API key

Seller-side setup starts in Meridian:

1. Connect the seller wallet at `https://mrdn.finance`
2. Open `https://mrdn.finance/dev/api-keys`
3. Create an organization-scoped API key
4. Store the public `pk_...` key on the server

Use the key in the `Authorization` header when calling Meridian:

```ts theme={"system"}
const meridianHeaders = {
  Authorization: `Bearer ${process.env.MERIDIAN_API_KEY}`,
  "Content-Type": "application/json",
};
```

The API key can also be created programmatically with a valid SIWE session cookie:

```bash theme={"system"}
curl -X POST https://api.mrdn.finance/v1/api_keys \
  -H "Content-Type: application/json" \
  -H "Cookie: siwe-session=your_session" \
  -d '{
    "name": "My Application Key",
    "test_net": true
  }'
```

### 2. Choose the network and amount

Amounts must use the selected token's base units. Do not assume every token has
6 decimals.

```ts theme={"system"}
const network = "exampleNetwork";
const config = NON_EIP_3009_NETWORKS[network];
const amountInTokenBaseUnits = 1_000_000_000_000_000_000n;
```

### 3. Build `paymentRequirements`

Use the ERC-20 token address as `asset`. Keep `payTo` pointed at the Meridian
facilitator.

```ts theme={"system"}
const paymentRequirements = {
  scheme: "exact",
  network,
  asset: config.token,
  payTo: config.facilitator,
  maxAmountRequired: amountInTokenBaseUnits.toString(),
  resource: "https://seller.example/api/tool",
  description: "Paid access to agent action",
  mimeType: "application/json",
  maxTimeoutSeconds: 300,
  extra: {
    name: config.tokenName,
    version: config.tokenVersion,
  },
};
```

If you expose a standard HTTP 402 challenge, return:

```ts theme={"system"}
return Response.json(
  {
    x402Version: 1,
    accepts: [paymentRequirements],
  },
  { status: 402 },
);
```

### 4. Receive `paymentPayload`

The buyer sends raw JSON, not a transaction they submit on-chain.
Build or retrieve the matching `paymentRequirements` server-side; do not trust
buyer-supplied requirements for pricing, recipient, network, or token selection.

```json theme={"system"}
{
  "x402Version": 1,
  "scheme": "exact",
  "network": "exampleNetwork",
  "payload": {
    "signature": "0x...",
    "owner": "0x...",
    "permit": {
      "permitted": {
        "token": "0x3333333333333333333333333333333333333333",
        "amount": "123456789"
      },
      "nonce": "123",
      "deadline": "1741885200"
    },
    "witness": {
      "to": "0x8E7769D440b3460b92159Dd9C6D17302b036e2d6",
      "validAfter": "0"
    }
  }
}
```

### 5. Settle with Meridian

Call `POST /v1/settle` from the seller backend. Do not expose the Meridian API
key to an untrusted client.

```ts theme={"system"}
const response = await fetch(`${MERIDIAN_API_BASE}/settle`, {
  method: "POST",
  headers: meridianHeaders,
  body: JSON.stringify({
    paymentPayload,
    paymentRequirements,
  }),
});

const result = await response.json();

if (!response.ok || !result.success) {
  throw new Error(result.errorReason ?? "Meridian settlement failed");
}
```

## Buyer setup

### 1. Select a payment requirement

Use the seller-provided requirement for the network and token the buyer will
pay with.

```ts theme={"system"}
const paymentRequirements = accepts[0];
const network =
  paymentRequirements.network as keyof typeof NON_EIP_3009_NETWORKS;
const config = NON_EIP_3009_NETWORKS[network];
```

### 2. Approve Permit2

The buyer approves the canonical Permit2 contract as spender for the token in
`paymentRequirements.asset`. The buyer does not approve the x402ExactPermit2Proxy or the facilitator
to spend their tokens!

```ts theme={"system"}
import { erc20Abi, maxUint256 } from "viem";

const token = paymentRequirements.asset as `0x${string}`;

await walletClient.writeContract({
  address: token,
  abi: erc20Abi,
  functionName: "approve",
  args: [PERMIT2, maxUint256],
});
```

In production, check allowance first and only prompt for approval when needed.

### 3. Sign the Permit2 authorization

Sign a Permit2 witness transfer. The EIP-712 verifying contract is Permit2, the
Permit2 spender is `x402ExactPermit2Proxy`, and the witness destination is the
Meridian facilitator.

```ts theme={"system"}
import { randomBytes } from "node:crypto";
import { SignatureTransfer } from "@uniswap/permit2-sdk";

const nonce = BigInt(`0x${randomBytes(32).toString("hex")}`);

const permit = {
  permitted: {
    token,
    amount: BigInt(paymentRequirements.maxAmountRequired),
  },
  spender: X402_EXACT_PERMIT2_PROXY,
  nonce,
  deadline: BigInt(
    Math.floor(Date.now() / 1000) + paymentRequirements.maxTimeoutSeconds,
  ),
};

const witness = {
  to: paymentRequirements.payTo as `0x${string}`,
  validAfter: 0n,
};

const witnessType = {
  Witness: [
    { name: "to", type: "address" },
    { name: "validAfter", type: "uint256" },
  ],
};

const { domain, types, values } = SignatureTransfer.getPermitData(
  permit,
  PERMIT2,
  config.chainId,
  witness,
  witnessType,
);

const signature = await walletClient.signTypedData({
  account,
  domain,
  types,
  primaryType: "PermitWitnessTransferFrom",
  message: values,
});
```

### 4. Send `paymentPayload` to the seller

Serialize bigint values as decimal strings.

```ts theme={"system"}
const paymentPayload = {
  x402Version: 1,
  scheme: "exact",
  network: paymentRequirements.network,
  payload: {
    signature,
    owner: account.address,
    permit: {
      permitted: {
        token: permit.permitted.token,
        amount: permit.permitted.amount.toString(),
      },
      nonce: permit.nonce.toString(),
      deadline: permit.deadline.toString(),
    },
    witness: {
      to: witness.to,
      validAfter: witness.validAfter.toString(),
    },
  },
};

await fetch("https://seller.example/api/tool", {
  method: "POST",
  headers: { "Content-Type": "application/json" },
  body: JSON.stringify({ paymentPayload }),
});
```

If the token supports ERC-2612 and you want a first-payment flow without a
standing Permit2 approval, attach a verified `permit2612` object to
`paymentPayload.payload`. Only use this shortcut for tokens you have confirmed
implement `permit()`.

## Upto scheme (usage-based pricing)

This playbook covers the `exact` scheme. For usage-based pricing, use
`scheme: "upto"`: the buyer signs the Permit2 witness for the **maximum**
amount with `X402_UPTO_PERMIT2_PROXY` as the spender, the witness becomes
`{ to, facilitator, validAfter }` (both `to` and `facilitator` set to the
Meridian facilitator, with witness type
`[{ name: "to", type: "address" }, { name: "facilitator", type: "address" }, { name: "validAfter", type: "uint256" }]`),
and the seller passes the actual amount at settle time via
`paymentRequirements.extra.settleAmount`. Unlike exact Permit2, `upto` works on
**every** supported network. See
[Payment Types](/api-reference/payment-types/overview) and
[Settle x402 Payment](/api-reference/endpoint/settle-payment).

## Checklist for AI agents

* Read the seller's existing route structure before adding payment handling
* Keep settlement on the backend
* Use `asset = token address`
* Use `payTo = Meridian facilitator`
* Use `permit.spender = x402ExactPermit2Proxy`
* Use `domain.verifyingContract = Permit2`
* Use `witness.to = Meridian facilitator`
* Keep Permit2 payments same-chain
* Convert every bigint in `paymentPayload` to a string before JSON encoding

## Common mistakes

* Setting `payTo` to the seller wallet
* Approving the exact proxy or facilitator instead of Permit2
* Signing with Permit2 as spender instead of `x402ExactPermit2Proxy`
* Setting `witness.to` to anything other than the Meridian facilitator
* Adding `extra.destinationChainId` and expecting Permit2 to bridge through
  Across
* Reusing hardcoded decimals from another chain
* Sending raw `bigint` values through JSON
* Putting the Meridian API key in browser code

## References

* [Supported Networks](/api-reference/supported-networks)
* [Payment Types](/api-reference/payment-types/overview)
* [Settle x402 Payment](/api-reference/endpoint/settle-payment)
* [Smart Contracts](/payments/smart-contracts)
* [Permit2](https://github.com/Uniswap/permit2)
* [x402ExactPermit2Proxy](https://github.com/x402-foundation/x402/tree/main/contracts/evm)
