# ERC-4337 Smart Account Swap

For smart wallets using the ERC-4337 standard (Pimlico, ZeroDev, etc.). The routing step is identical to the EOA flow — use `POST /routing/scan/stream` (see Find a Route). The difference is in the `/tx/create` request and response.

Four gas payment modes are available:

* **Mode A** -- ERC-20 paymaster: gas is paid in an ERC-20 token (e.g. USDC) via the CrossCurve Pimlico proxy.
* **Mode B** -- Sponsored / custom paymaster: your own verifying paymaster sponsors gas.
* **Mode C** -- Native gas: no paymaster; the smart wallet pays gas in the native token directly.
* **Mode D** -- Calldata only: raw calldata returned; integrator builds their own UserOperation and manages approvals.

> The smart wallet address (`from`) must already be deployed on the source chain. If the contract is not deployed, the server returns a `400 NOT_SMART_ACCOUNT` error.

#### Request Fields

The request body uses the same base fields as EOA (`from`, `recipient`, `routing`) plus these AA-specific fields:

| Field              | Type    | Required | Description                                        |
| ------------------ | ------- | -------- | -------------------------------------------------- |
| `walletType`       | string  | Yes      | `"4337"`                                           |
| `gasToken`         | string  | No       | ERC-20 token address for gas payment via paymaster |
| `paymasterAddress` | string  | No       | Custom paymaster contract address                  |
| `entryPoint`       | string  | No       | Custom EntryPoint contract address                 |
| `calldataOnly`     | boolean | No       | Return raw calldata without `calls[]` wrapper      |

Do not include `buildCalldata` -- it is ignored for AA wallets.

#### Mode A -- ERC-20 Paymaster

Gas is paid in an ERC-20 token through CrossCurve's Pimlico paymaster. The response includes a `pimlicoChainName` used to route subsequent bundler and paymaster calls through the `/pimlico/{chainName}` proxy endpoint.

**Request:**

```json
{
  "from": "0xSMART_WALLET_ADDRESS",
  "recipient": "0xRECIPIENT_ADDRESS",
  "routing": { },
  "walletType": "4337",
  "gasToken": "0xERC20_TOKEN_FOR_GAS"
}
```

**Response:**

```json
{
  "walletType": "4337",
  "calls": [
    { "to": "0xGAS_TOKEN", "value": "0", "data": "0x..." },
    { "to": "0xTOKEN_IN", "value": "0", "data": "0x..." },
    { "to": "0xROUTER", "value": "0", "data": "0x..." }
  ],
  "chainId": 11155111,
  "pimlicoChainName": "sepolia",
  "paymasterContext": { "token": "0xGAS_TOKEN_ADDRESS" },
  "paymasterAddress": "0x...",
  "entryPoint": "0x..."
}
```

**Response fields:**

| Field              | Type     | Description                                                         |
| ------------------ | -------- | ------------------------------------------------------------------- |
| `walletType`       | `"4337"` | Wallet type echo                                                    |
| `calls`            | array    | Ordered calls for the UserOperation (see below)                     |
| `chainId`          | number   | Source chain ID                                                     |
| `pimlicoChainName` | string   | Chain name for the `/pimlico/{chainName}` proxy                     |
| `paymasterContext` | object   | Contains `token` -- the ERC-20 address used for gas payment         |
| `paymasterAddress` | string   | Paymaster contract address (present in ERC-20 and sponsored modes)  |
| `entryPoint`       | string   | EntryPoint contract address (present in ERC-20 and sponsored modes) |

Each item in `calls` has:

| Field   | Type   | Description              |
| ------- | ------ | ------------------------ |
| `to`    | string | Target contract address  |
| `value` | string | Native token value (wei) |
| `data`  | string | Encoded calldata         |

The three calls in order:

1. **Paymaster token approve** -- approves the paymaster to spend the gas token.
2. **Router token approve** -- approves the router to spend the input token.
3. **Router start** -- calls `router.start()` to initiate the swap.

**Full flow (10 steps):**

1. `POST /routing/scan/stream` -- find a route (see EOA Step 2).
2. `POST /tx/create` with `walletType: "4337"` and `gasToken` -- receive `calls`, `pimlicoChainName`, `paymasterContext`, `paymasterAddress`, `entryPoint`.
3. `POST /pimlico/{pimlicoChainName}` -- call `pimlico_getUserOperationGasPrice` to get gas prices.
4. Build a UserOperation from `calls`. Set `maxFeePerGas` and `maxPriorityFeePerGas` from step 3.
5. `POST /pimlico/{pimlicoChainName}` -- call `pm_getPaymasterStubData` to get stub data for gas estimation.
6. `POST /pimlico/{pimlicoChainName}` -- call `eth_estimateUserOperationGas` to fill gas limits.
7. `POST /pimlico/{pimlicoChainName}` -- call `pm_getPaymasterData` to get final paymaster data.
8. Sign the UserOperation.
9. `POST /pimlico/{pimlicoChainName}` -- call `eth_sendUserOperation` to submit.
10. Poll with `eth_getUserOperationReceipt` via `POST /pimlico/{pimlicoChainName}` until the operation is confirmed.

**Full Example (JavaScript)**

```js
const API = "https://api.crosscurve.fi";
const API_KEY = "YOUR_API_KEY";

const SMART_WALLET = "0xSMART_WALLET_ADDRESS";
const TOKEN_IN = "0xTOKEN_IN";
const TOKEN_OUT = "0xTOKEN_OUT";
const GAS_TOKEN = "0xGAS_TOKEN";
const CHAIN_ID_IN = 11155111;
const CHAIN_ID_OUT = 64165;
const AMOUNT_IN = "1000000";

// Step 1: Find a route (scanRoutes helper defined in EOA example above)
const routes = await scanRoutes({
  from: SMART_WALLET,
  recipient: SMART_WALLET,
  params: {
    tokenIn: TOKEN_IN,
    amountIn: AMOUNT_IN,
    chainIdIn: CHAIN_ID_IN,
    tokenOut: TOKEN_OUT,
    chainIdOut: CHAIN_ID_OUT,
  },
  slippage: 1,
});
routes.sort((a, b) => {
  if (BigInt(a.amountOut) > BigInt(b.amountOut)) return -1;
  if (BigInt(a.amountOut) < BigInt(b.amountOut)) return 1;
  return 0;
});
const route = routes[0];

// Step 2: Build AA transaction
const txRes = await fetch(`${API}/tx/create`, {
  method: "POST",
  headers: { "Content-Type": "application/json" },
  body: JSON.stringify({
    from: SMART_WALLET,
    recipient: SMART_WALLET,
    routing: route,
    walletType: "4337",
    gasToken: GAS_TOKEN,
  }),
});
const txData = await txRes.json();
// txData = { walletType, calls, chainId, pimlicoChainName, paymasterContext, paymasterAddress, entryPoint }

const { calls, pimlicoChainName, paymasterContext, entryPoint } = txData;

// Helper: call Pimlico proxy
async function pimlicoRpc(chainName, method, params) {
  const res = await fetch(`${API}/pimlico/${chainName}`, {
    method: "POST",
    headers: {
      "Content-Type": "application/json",
      "api-key": API_KEY,
    },
    body: JSON.stringify({ jsonrpc: "2.0", method, params, id: 1 }),
  });
  const json = await res.json();
  if (json.error) throw new Error(json.error.message);
  return json.result;
}

// Step 3: Get gas prices
const gasPrice = await pimlicoRpc(pimlicoChainName, "pimlico_getUserOperationGasPrice", []);

// Step 4: Build UserOperation
const userOp = {
  sender: SMART_WALLET,
  nonce: await getNonce(SMART_WALLET, entryPoint), // from your smart account
  callData: encodeMulticall(calls), // encode calls into a single callData
  callGasLimit: "0x0",
  verificationGasLimit: "0x0",
  preVerificationGas: "0x0",
  maxFeePerGas: gasPrice.standard.maxFeePerGas,
  maxPriorityFeePerGas: gasPrice.standard.maxPriorityFeePerGas,
  signature: "0x", // empty signature for estimation
};

// Step 5: Get paymaster stub data
const stubData = await pimlicoRpc(pimlicoChainName, "pm_getPaymasterStubData", [
  userOp,
  entryPoint,
  pimlicoChainName,
  { token: paymasterContext.token },
]);
userOp.paymasterAndData = stubData.paymasterAndData;

// Step 6: Estimate gas
const gasEstimate = await pimlicoRpc(
  pimlicoChainName,
  "eth_estimateUserOperationGas",
  [userOp, entryPoint]
);
userOp.callGasLimit = gasEstimate.callGasLimit;
userOp.verificationGasLimit = gasEstimate.verificationGasLimit;
userOp.preVerificationGas = gasEstimate.preVerificationGas;

// Step 7: Get final paymaster data
const pmData = await pimlicoRpc(pimlicoChainName, "pm_getPaymasterData", [
  userOp,
  entryPoint,
  pimlicoChainName,
  { token: paymasterContext.token },
]);
userOp.paymasterAndData = pmData.paymasterAndData;

// Step 8: Sign
userOp.signature = await signUserOp(userOp, entryPoint, txData.chainId);

// Step 9: Send
const userOpHash = await pimlicoRpc(
  pimlicoChainName,
  "eth_sendUserOperation",
  [userOp, entryPoint]
);
console.log("UserOperation hash:", userOpHash);

// Step 10: Poll for receipt
async function pollUserOp(hash) {
  while (true) {
    const receipt = await pimlicoRpc(
      pimlicoChainName,
      "eth_getUserOperationReceipt",
      [hash]
    );
    if (receipt) return receipt;
    await new Promise((r) => setTimeout(r, 5000));
  }
}

const receipt = await pollUserOp(userOpHash);
console.log("UserOperation confirmed:", receipt.receipt.transactionHash);
```

**Full Example (TypeScript + permissionless)**

```ts
import { createPublicClient, http } from "viem";
import { sepolia } from "viem/chains";
import {
  createSmartAccountClient,
  type SmartAccountClient,
} from "permissionless";
import { createPimlicoClient } from "permissionless/clients/pimlico";

const API = "https://api.crosscurve.fi";
const API_KEY = "YOUR_API_KEY";

const SMART_WALLET = "0xSMART_WALLET_ADDRESS" as `0x${string}`;
const TOKEN_IN = "0xTOKEN_IN" as `0x${string}`;
const TOKEN_OUT = "0xTOKEN_OUT" as `0x${string}`;
const GAS_TOKEN = "0xGAS_TOKEN" as `0x${string}`;
const CHAIN_ID_IN = 11155111;
const CHAIN_ID_OUT = 64165;
const AMOUNT_IN = "1000000";

interface AACall {
  to: string;
  value: string;
  data: string;
}

interface PaymasterContext {
  token: string;
}

interface CreateTx4337Response {
  walletType: "4337";
  calls: AACall[];
  chainId: number;
  pimlicoChainName?: string;
  paymasterContext: PaymasterContext;
  paymasterAddress?: string;
  entryPoint?: string;
}

// Step 1: Find a route (scanRoutes helper defined in EOA example above)
const routes = await scanRoutes({
  from: SMART_WALLET,
  recipient: SMART_WALLET,
  params: {
    tokenIn: TOKEN_IN,
    amountIn: AMOUNT_IN,
    chainIdIn: CHAIN_ID_IN,
    tokenOut: TOKEN_OUT,
    chainIdOut: CHAIN_ID_OUT,
  },
  slippage: 1,
});
routes.sort((a: any, b: any) => {
  if (BigInt(a.amountOut) > BigInt(b.amountOut)) return -1;
  if (BigInt(a.amountOut) < BigInt(b.amountOut)) return 1;
  return 0;
});
const route = routes[0];

// Step 2: Build AA transaction
const txRes = await fetch(`${API}/tx/create`, {
  method: "POST",
  headers: { "Content-Type": "application/json" },
  body: JSON.stringify({
    from: SMART_WALLET,
    recipient: SMART_WALLET,
    routing: route,
    walletType: "4337",
    gasToken: GAS_TOKEN,
  }),
});
const txData: CreateTx4337Response = await txRes.json();

const { calls, pimlicoChainName, paymasterContext, entryPoint } = txData;

// Create a Pimlico client that routes through the CrossCurve proxy
const pimlicoClient = createPimlicoClient({
  chain: sepolia,
  transport: http(`${API}/pimlico/${pimlicoChainName}`, {
    fetchOptions: {
      headers: { "api-key": API_KEY },
    },
  }),
  entryPoint: {
    address: entryPoint as `0x${string}`,
    version: "0.7",
  },
});

// Create your smart account client (implementation depends on your account type)
// For example, with a Safe, Kernel, or other account:
const smartAccountClient: SmartAccountClient = createSmartAccountClient({
  account: yourSmartAccount, // your account implementation
  chain: sepolia,
  bundlerTransport: http(`${API}/pimlico/${pimlicoChainName}`, {
    fetchOptions: {
      headers: { "api-key": API_KEY },
    },
  }),
  paymaster: pimlicoClient,
  paymasterContext: { token: paymasterContext.token },
});

// Step 3: Send the UserOperation
const userOpHash = await smartAccountClient.sendUserOperation({
  calls: calls.map((call) => ({
    to: call.to as `0x${string}`,
    value: BigInt(call.value),
    data: call.data as `0x${string}`,
  })),
});

console.log("UserOperation hash:", userOpHash);

// Step 4: Wait for receipt
const receipt = await smartAccountClient.waitForUserOperationReceipt({
  hash: userOpHash,
});

console.log("Confirmed:", receipt.receipt.transactionHash);
```

#### Mode B -- Sponsored / Custom Paymaster

Your own verifying paymaster sponsors gas. No `gasToken` is needed.

**Request:**

```json
{
  "from": "0xSMART_WALLET_ADDRESS",
  "recipient": "0xRECIPIENT_ADDRESS",
  "routing": { },
  "walletType": "4337",
  "paymasterAddress": "0xYOUR_VERIFYING_PAYMASTER",
  "entryPoint": "0xENTRYPOINT"
}
```

**Response:**

```json
{
  "walletType": "4337",
  "calls": [
    { "to": "0xTOKEN_IN", "value": "0", "data": "0x..." },
    { "to": "0xROUTER", "value": "0", "data": "0x..." }
  ],
  "chainId": 11155111,
  "paymasterContext": { "token": "0x0000000000000000000000000000000000000000" },
  "paymasterAddress": "0xYOUR_VERIFYING_PAYMASTER",
  "entryPoint": "0x..."
}
```

Key differences from Mode A:

* **2 calls** instead of 3 -- no paymaster token approve (your paymaster sponsors gas).
* `pimlicoChainName` is absent -- the Pimlico proxy is not needed.
* `paymasterContext.token` is the zero address.
* The integrator manages their own paymaster and bundler infrastructure.

**Example (JavaScript)**

```js
const API = "https://api.crosscurve.fi";
const SMART_WALLET = "0xSMART_WALLET_ADDRESS";

// After obtaining a route from /routing/scan/stream (see EOA example)...

const txRes = await fetch(`${API}/tx/create`, {
  method: "POST",
  headers: { "Content-Type": "application/json" },
  body: JSON.stringify({
    from: SMART_WALLET,
    recipient: SMART_WALLET,
    routing: route,
    walletType: "4337",
    paymasterAddress: "0xYOUR_VERIFYING_PAYMASTER",
    entryPoint: "0xENTRYPOINT",
  }),
});
const txData = await txRes.json();
// txData = { walletType, calls, chainId, paymasterContext, paymasterAddress, entryPoint }

// Build UserOperation from txData.calls
const userOp = {
  sender: SMART_WALLET,
  nonce: await getNonce(SMART_WALLET, txData.entryPoint),
  callData: encodeMulticall(txData.calls),
  // ... fill gas fields via your bundler
};

// Use your own paymaster to sponsor and sign the UserOperation
// Use your own bundler to estimate gas and submit
const userOpHash = await yourBundler.sendUserOperation(userOp, txData.entryPoint);
```

#### Mode C -- Native Gas

No paymaster is used. The smart wallet pays gas in the native token directly, the same way an EOA would.

**Request:**

```json
{
  "from": "0xSMART_WALLET_ADDRESS",
  "recipient": "0xRECIPIENT_ADDRESS",
  "routing": { },
  "walletType": "4337"
}
```

**Response:**

```json
{
  "walletType": "4337",
  "calls": [
    { "to": "0xTOKEN_IN", "value": "0", "data": "0x..." },
    { "to": "0xROUTER", "value": "1234567", "data": "0x..." }
  ],
  "chainId": 11155111,
  "paymasterContext": { "token": "0x0000000000000000000000000000000000000000" }
}
```

Key differences:

* **2 calls** -- router approve and router start.
* `value` on the router start call may be non-zero.
* `pimlicoChainName`, `paymasterAddress`, and `entryPoint` are absent.
* `paymasterContext.token` is the zero address.
* No paymaster is involved -- the smart wallet pays gas in the native token.

Estimate gas and submit the UserOperation through your own bundler or node.

#### Mode D -- Calldata Only

For integrators with their own AA infrastructure who want raw calldata without the `calls[]` wrapper. The integrator builds their own approve calls, UserOperation, and paymaster interactions.

**Request:**

```json
{
  "from": "0xSMART_WALLET_ADDRESS",
  "recipient": "0xRECIPIENT_ADDRESS",
  "routing": { },
  "walletType": "4337",
  "calldataOnly": true
}
```

**Response:**

```json
{
  "calldataOnly": true,
  "to": "0xROUTER",
  "data": "0x...",
  "value": "0",
  "feeToken": "0x...",
  "executionPrice": "...",
  "chainId": 11155111
}
```

**Response fields:**

| Field            | Type   | Description                                                   |
| ---------------- | ------ | ------------------------------------------------------------- |
| `calldataOnly`   | `true` | Indicates calldata-only mode                                  |
| `to`             | string | Router contract address                                       |
| `data`           | string | Encoded calldata for `router.start()`                         |
| `value`          | string | Native token value (wei)                                      |
| `feeToken`       | string | Token address for fee payment (zero address for native token) |
| `executionPrice` | string | Delivery fee amount in `feeToken` units                       |
| `chainId`        | number | Source chain ID                                               |

The integrator is responsible for:

* Building approve calls (if `feeToken` is an ERC-20, approve the router for `amountIn + executionPrice`).
* Constructing the full UserOperation.
* Managing paymaster interactions (if any).
* Submitting through their own bundler.

***

### EIP-7702 Swap

For EOA wallets with EIP-7702 code delegation. The EOA temporarily gains smart account functionality for the duration of the operation, without needing a separate smart wallet contract.

**Key differences from ERC-4337:**

* `walletType` is `"7702"` instead of `"4337"`.
* The sender address in the UserOperation is the EOA itself, not a separate smart account.
* Requires an EIP-7702 authorization signature for code delegation.
* Uses EntryPoint v0.8 (ERC-4337 uses v0.6).

The same gas payment modes are available as ERC-4337: ERC-20 paymaster, sponsored/custom paymaster, and native gas. The response shape is identical -- see the ERC-4337 response fields for field descriptions.

#### Request

The request body uses the same fields as ERC-4337 with `walletType` set to `"7702"`.

**ERC-20 paymaster mode:**

```json
{
  "from": "0xYOUR_EOA",
  "recipient": "0xRECIPIENT",
  "routing": { },
  "walletType": "7702",
  "gasToken": "0xGAS_TOKEN"
}
```

**Native gas mode (no paymaster):**

```json
{
  "from": "0xYOUR_EOA",
  "recipient": "0xRECIPIENT",
  "routing": { },
  "walletType": "7702"
}
```

#### Response

Same shape as ERC-4337 but with `walletType: "7702"`:

```json
{
  "walletType": "7702",
  "calls": [
    { "to": "0xTOKEN_ADDRESS", "value": "0", "data": "0xAPPROVE_CALLDATA" },
    { "to": "0xROUTER_ADDRESS", "value": "0", "data": "0xSTART_CALLDATA" }
  ],
  "chainId": 11155111,
  "pimlicoChainName": "sepolia",
  "paymasterContext": { "token": "0xERC20_GAS_TOKEN" },
  "paymasterAddress": "0x...",
  "entryPoint": "0x..."
}
```

#### Full Flow (ERC-20 Paymaster)

1. `POST /routing/scan/stream` -- find a route (see EOA Step 2).
2. `POST /tx/create` with `walletType: "7702"` and `gasToken` -- receive `calls`, `pimlicoChainName`, `paymasterContext`, `paymasterAddress`, `entryPoint`.
3. `POST /pimlico/{pimlicoChainName}` -- call `pimlico_getUserOperationGasPrice` to get gas prices.
4. Sign an EIP-7702 authorization for code delegation to the Smart7702 implementation.
5. Build a UserOperation with the authorization. Set `maxFeePerGas` and `maxPriorityFeePerGas` from step 3.
6. `POST /pimlico/{pimlicoChainName}` -- call `pm_getPaymasterStubData` to get stub data for gas estimation.
7. `POST /pimlico/{pimlicoChainName}` -- call `eth_estimateUserOperationGas` to fill gas limits.
8. `POST /pimlico/{pimlicoChainName}` -- call `pm_getPaymasterData` to get final paymaster data.
9. Sign and send the UserOperation via `POST /pimlico/{pimlicoChainName}` using `eth_sendUserOperation`.

#### Full Flow (Native Gas)

1. `POST /routing/scan/stream` -- find a route (see EOA Step 2).
2. `POST /tx/create` with `walletType: "7702"` (no `gasToken`) -- receive `calls`.
3. Sign an EIP-7702 authorization for code delegation.
4. Build a UserOperation from `calls`.
5. Estimate gas, sign, and send. No paymaster is needed -- the EOA pays gas in the native token.

#### Full Example (JavaScript, ERC-20 Paymaster)

This example shows the 7702-specific authorization step. See the ERC-4337 example for the `pimlicoRpc` helper and common patterns.

```js
const API = "https://api.crosscurve.fi";
const API_KEY = "YOUR_API_KEY";

const EOA_ADDRESS = "0xYOUR_EOA";
const TOKEN_IN = "0xTOKEN_IN";
const TOKEN_OUT = "0xTOKEN_OUT";
const GAS_TOKEN = "0xGAS_TOKEN";
const CHAIN_ID_IN = 11155111;
const CHAIN_ID_OUT = 64165;
const AMOUNT_IN = "1000000";

// Helper: call Pimlico proxy (same as ERC-4337 example)
async function pimlicoRpc(chainName, method, params) {
  const res = await fetch(`${API}/pimlico/${chainName}`, {
    method: "POST",
    headers: {
      "Content-Type": "application/json",
      "api-key": API_KEY,
    },
    body: JSON.stringify({ jsonrpc: "2.0", method, params, id: 1 }),
  });
  const json = await res.json();
  if (json.error) throw new Error(json.error.message);
  return json.result;
}

// Step 1: Find a route (scanRoutes helper defined in EOA example above)
const routes = await scanRoutes({
  from: EOA_ADDRESS,
  recipient: EOA_ADDRESS,
  params: {
    tokenIn: TOKEN_IN,
    amountIn: AMOUNT_IN,
    chainIdIn: CHAIN_ID_IN,
    tokenOut: TOKEN_OUT,
    chainIdOut: CHAIN_ID_OUT,
  },
  slippage: 1,
});
routes.sort((a, b) => {
  if (BigInt(a.amountOut) > BigInt(b.amountOut)) return -1;
  if (BigInt(a.amountOut) < BigInt(b.amountOut)) return 1;
  return 0;
});
const route = routes[0];

// Step 2: Build AA transaction with 7702
const txRes = await fetch(`${API}/tx/create`, {
  method: "POST",
  headers: { "Content-Type": "application/json" },
  body: JSON.stringify({
    from: EOA_ADDRESS,
    recipient: EOA_ADDRESS,
    routing: route,
    walletType: "7702",
    gasToken: GAS_TOKEN,
  }),
});
const txData = await txRes.json();

const { calls, pimlicoChainName, paymasterContext, entryPoint } = txData;

// Step 3: Get gas prices
const gasPrice = await pimlicoRpc(pimlicoChainName, "pimlico_getUserOperationGasPrice", []);

// Step 4: Sign EIP-7702 authorization for code delegation
const authorization = await walletClient.signAuthorization({
  contractAddress: SMART_7702_IMPLEMENTATION, // delegation target
  chainId: txData.chainId,
});

// Step 5: Build UserOperation with authorization
const userOp = {
  sender: EOA_ADDRESS,
  nonce: await getNonce(EOA_ADDRESS, entryPoint),
  callData: encodeMulticall(calls),
  callGasLimit: "0x0",
  verificationGasLimit: "0x0",
  preVerificationGas: "0x0",
  maxFeePerGas: gasPrice.standard.maxFeePerGas,
  maxPriorityFeePerGas: gasPrice.standard.maxPriorityFeePerGas,
  authorization, // EIP-7702 authorization
  signature: "0x",
};

// Step 6: Get paymaster stub data
const stubData = await pimlicoRpc(pimlicoChainName, "pm_getPaymasterStubData", [
  userOp,
  entryPoint,
  pimlicoChainName,
  { token: paymasterContext.token },
]);
userOp.paymasterAndData = stubData.paymasterAndData;

// Step 7: Estimate gas
const gasEstimate = await pimlicoRpc(
  pimlicoChainName,
  "eth_estimateUserOperationGas",
  [userOp, entryPoint]
);
userOp.callGasLimit = gasEstimate.callGasLimit;
userOp.verificationGasLimit = gasEstimate.verificationGasLimit;
userOp.preVerificationGas = gasEstimate.preVerificationGas;

// Step 8: Get final paymaster data
const pmData = await pimlicoRpc(pimlicoChainName, "pm_getPaymasterData", [
  userOp,
  entryPoint,
  pimlicoChainName,
  { token: paymasterContext.token },
]);
userOp.paymasterAndData = pmData.paymasterAndData;

// Step 9: Sign and send
userOp.signature = await signUserOp(userOp, entryPoint, txData.chainId);

const userOpHash = await pimlicoRpc(
  pimlicoChainName,
  "eth_sendUserOperation",
  [userOp, entryPoint]
);
console.log("UserOperation hash:", userOpHash);
```

***

### Pimlico Proxy

```
POST /pimlico/{chainName}
```

JSON-RPC 2.0 proxy for account abstraction bundler and paymaster operations. Routes requests to the Pimlico infrastructure through the CrossCurve API.

`{chainName}` is the chain identifier from the `pimlicoChainName` field in the `/tx/create` response (e.g. `sepolia`, `sonic-testnet`).

#### Request Format

```json
{
  "jsonrpc": "2.0",
  "id": 1,
  "method": "METHOD_NAME",
  "params": [...]
}
```

**Headers:**

| Header         | Value              |
| -------------- | ------------------ |
| `Content-Type` | `application/json` |
| `api-key`      | `YOUR_API_KEY`     |

#### Allowed Methods

| Method                             | Description                           |
| ---------------------------------- | ------------------------------------- |
| `eth_sendUserOperation`            | Submit a UserOperation                |
| `eth_estimateUserOperationGas`     | Estimate gas for a UserOperation      |
| `eth_getUserOperationReceipt`      | Get receipt by UserOperation hash     |
| `eth_getUserOperationByHash`       | Get UserOperation by hash             |
| `eth_supportedEntryPoints`         | List supported EntryPoint addresses   |
| `pm_getPaymasterData`              | Get paymaster data for gas sponsoring |
| `pm_getPaymasterStubData`          | Get stub data for gas estimation      |
| `pm_supportedTokens`               | List tokens available for gas payment |
| `pimlico_getUserOperationGasPrice` | Get recommended gas prices            |
| `pimlico_getTokenQuotes`           | Get token quotes for paymaster        |

Any method not in this list returns a `400` error.

#### Limits

| Constraint     | Value                         |
| -------------- | ----------------------------- |
| Rate limit     | 60 requests per minute per IP |
| Max payload    | 50 KB                         |
| Timeout        | 15 seconds                    |
| Batch JSON-RPC | Not supported                 |

For `eth_sendUserOperation` and `eth_estimateUserOperationGas`, the EntryPoint address passed in `params[1]` must match one of the EntryPoint addresses configured for the chain.

#### Example -- Reusable Helper (JavaScript)

```js
const API = "https://api.crosscurve.fi";
const API_KEY = "YOUR_API_KEY";

async function pimlicoRpc(chainName, method, params) {
  const res = await fetch(`${API}/pimlico/${chainName}`, {
    method: "POST",
    headers: {
      "Content-Type": "application/json",
      "api-key": API_KEY,
    },
    body: JSON.stringify({
      jsonrpc: "2.0",
      id: 1,
      method,
      params,
    }),
  });
  const json = await res.json();
  if (json.error) throw new Error(json.error.message);
  return json.result;
}

// Usage: get gas prices
const gasPrice = await pimlicoRpc("sepolia", "pimlico_getUserOperationGasPrice", []);

// Usage: estimate gas for a UserOperation
const gasEstimate = await pimlicoRpc("sepolia", "eth_estimateUserOperationGas", [
  userOp,
  "0xENTRYPOINT_ADDRESS",
]);
```

***
