# 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",
]);
```

***


---

# Agent Instructions: Querying This Documentation

If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://docs.crosscurve.fi/developer-documentation/integration-guide-old/direct-integration-with-rest-api/erc-4337-smart-account-swap.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
