Dashboard
ERC-4337 Account Abstraction

Light Infra

Gasless smart wallet infrastructure with client-side signing. No private keys on the server—ever.

Light Infra is a production API for gasless smart account transactions using ERC-4337. Send tokens, approve contracts, execute swaps, bridge cross-chain — all without users needing ETH for gas. The V2 API uses a prepare → sign → submit flow so private keys never leave the client.

Client-Side Signing

Private keys never touch the server. Sign with Privy, MetaMask, wagmi, ethers, viem — any wallet.

Any Transaction, Gasless

Transfers, approvals, swaps, mints, contract calls — gas paid in USDC/USDT, not ETH.

Multi-Chain + Bridging

9 chains, same smart account address everywhere. Cross-chain bridging via Across Protocol.

Architecture

Client Light Infra Bundler | | | | POST /api/v2/prepare | | | { ownerAddress, chainId, | | | token, amount, ... } | | |------------------------------>| | | | Build UserOp, estimate gas, | | | get paymaster signature | | { userOperation, hashToSign }| | |<------------------------------| | | | | | Sign hashToSign locally | | | (personal_sign / eth_sign) | | | | | | POST /api/v2/submit | | | { userOperation, signature } | | |------------------------------>| | | | eth_sendUserOperation | | |------------------------------>| | | userOperationHash | | { userOperationHash } |<------------------------------| |<------------------------------| |

Supported Chains

ChainIDBundlerPaymasterGas Tokens
Ethereum1PimlicoPimlico ERC-20USDC
Base8453PimlicoPimlico ERC-20USDC
Polygon137PimlicoPimlico ERC-20USDC, USDT
Celo42220PimlicoPimlico ERC-20USDC, USDT
Arbitrum42161PimlicoPimlico ERC-20USDC
Avalanche43114PimlicoPimlico ERC-20USDC
BNB Chain56PimlicoPimlico ERC-20USDT
Berachain80094PimlicoPimlico ERC-20USDC
Lisk 1135 Gelato Custom LiskPaymaster Bridged USDC, Bridged USDT

Note: Lisk uses bridged versions of USDC and USDT — these are not native Circle/Tether tokens but ERC-20s bridged to Lisk via the canonical bridge. Same 6-decimal format.

Transparent routing: The API interface is identical for all chains. Lisk uses a custom verifying paymaster + Gelato bundler behind the scenes, but callers send the exact same request format.

LiskPaymaster Contract

A self-hosted verifying paymaster for Lisk (chain 1135), deployed because Pimlico does not support Lisk. ERC-4337 v0.7 paymaster spec with EntryPoint 0x0000000071727De22E5E9d8BAf0edAc6f37da032.

Contract0x427236c2FbF46372e115027c367E63Bbb11F932C
ChainLisk Mainnet (1135)
EntryPoint0x0000000071727De22E5E9d8BAf0edAc6f37da032 (v0.7)
StandardERC-4337 IPaymaster via BasePaymaster
SourceView on Blockscout

Supported Tokens

TokenAddressDecimals
Bridged USDC0xF242275d3a6527d877f2c927a82D9b057609cc716
Bridged USDT0x05D032ac25d322df992303dCa074EE7392C117b96

How It Works

1. Backend signs the UserOperation off-chain with LISK_PAYMASTER_SIGNER_KEY 2. validatePaymasterUserOp verifies the backend signature on-chain |-- Checks token is configured |-- Checks validUntil / validAfter timestamps `-- Recovers signer via ECDSA & compares to verifyingSigner 3. User's callData executes (token approve + transfer) 4. postOp pulls tokens from the smart account to cover gas |-- tokenCost = gasUsed * ethPrice * markup / 1e18 `-- Transfers tokens from sender -> paymaster owner 5. Transaction complete, user paid gas in USDC/USDT

Key Contract Functions

FunctionAccessDescription
configureToken(address, uint256, bool)OwnerSet token price per ETH and enable/disable
setVerifyingSigner(address)OwnerUpdate the off-chain signer address
getHash(PackedUserOperation, address, uint48, uint48)ViewCompute the hash that the backend signer signs
getTokenCostForGas(uint256, address)ViewCalculate token amount for a given gas cost in wei
deposit()OwnerFund the paymaster's EntryPoint deposit (pays for gas)
addStake(uint32)OwnerStake ETH with EntryPoint (required for paymasters)

paymasterData Layout

The paymasterData field in the UserOperation is packed as:

OffsetBytesField
020token — ERC-20 address (USDC/USDT)
206validUntil — uint48 expiry timestamp
266validAfter — uint48 earliest timestamp
3265signature — ECDSA sig from verifyingSigner

Bundlers

Pimlico — 8 chains

Industry-standard ERC-4337 bundler. Handles UserOp submission, gas estimation, and ERC-20 paymaster sponsorship for Ethereum, Base, Polygon, Celo, Arbitrum, Avalanche, BNB, and Berachain.

https://api.pimlico.io/v2/{chain}/rpc?apikey=KEY

Gelato — Lisk

Bundler for Lisk (chain 1135). Supports EntryPoint v0.7, eth_sendUserOperation, eth_estimateUserOperationGas, and eth_supportedEntryPoints.

https://api.gelato.cloud/rpc/1135?apiKey=KEY

Paymaster Routing

+------------------+ | isLiskChain? | +--------+---------+ No | Yes +------------+----------------+ v v +------------------+ +-----------------------+ | Pimlico | | Custom LiskPaymaster | | ---------------- | | --------------------- | | Bundler: Pimlico | | Bundler: Gelato | | Paymaster: ERC-20 | | Paymaster: Verifying | | Gas est: Pimlico | | Gas est: Lisk RPC | | Pricing: Pimlico | | Pricing: Enso API | +------------------+ +-----------------------+ | | +----------+--+------------- + v Same response to caller

Gas Pricing (Lisk)

ETH price is fetched from the Enso API using Ethereum mainnet (chain ID 1) because Enso does not support the Lisk chain directly. We query the WETH price on mainnet — ETH price is the same regardless of chain:

GET https://api.enso.build/api/v1/prices/1/0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2
Authorization: Bearer ENSO_API_KEY

Token cost calculation (matches on-chain getTokenCostForGas):

// tokenCost = gasCostWei * ethPriceUsd * 10^decimals * markup / (1e18 * 10000)
const pricePerEth = ethPriceUsd * 10 ** tokenDecimals;  // e.g. 2500 * 1e6
const tokenCost = gasCostWei * pricePerEth * markup / (1e18 * 10000);

Gas fee estimation queries the Gelato bundler's minimum priority fee and the Lisk RPC gas price, takes the highest, then adds a 20% buffer:

// Lisk gas fees (never fails -- aggressive fallbacks)
maxPriorityFeePerGas = max(rpcGasPrice, bundlerMinPriority, 1_000_000) * 1.2
maxFeePerGas = rpcGasPrice * 3 + maxPriorityFeePerGas

V2 API — Client-Side Signing

The V2 endpoints use a prepare → sign → submit pattern. The server builds the UserOperation and returns a hash. The client signs it locally. The server relays it to the bundler.

Zero key exposure: The server never receives, stores, or processes any private key. It only needs the owner's public address to compute the deterministic Safe smart account.

Prepare UserOperation

POST /api/v2/prepare Build unsigned UserOp, return hash to sign

Request Body — Token Transfer

{
  "ownerAddress": "0xYourEOAAddress",
  "chainId": 1135,
  "rpcUrl": "https://rpc.api.lisk.com",
  "tokenAddress": "0xF242275d3a6527d877f2c927a82D9b057609cc71",
  "recipient": "0xRecipientAddress",
  "amount": 5.0
}

Request Body — Universal Transaction

{
  "ownerAddress": "0xYourEOAAddress",
  "chainId": 8453,
  "rpcUrl": "https://mainnet.base.org",
  "calls": [
    {
      "to": "0xRecipientAddress",
      "value": "1000000000000000000"
    }
  ],
  "paymasterToken": "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913"
}

Parameters

FieldTypeRequiredDescription
ownerAddressaddressYesEOA address (NOT private key)
chainIdnumberYesTarget chain ID
rpcUrlstringYesChain RPC endpoint
tokenAddressaddressTransfer modeERC-20 token to transfer
recipientaddressTransfer modeTransfer recipient
amountnumberTransfer modeAmount (human readable, e.g. 5.0)
callsarrayTransaction modeArray of calls (to, value, data, abi, ...)
paymasterTokenaddressNoToken to pay gas with (for transaction mode)

Response

{
  "success": true,
  "data": {
    "smartAccountAddress": "0xYourSafeAddress",
    "userOperation": {
      "sender": "0x...",
      "nonce": "0x...",
      "callData": "0x...",
      "callGasLimit": "0x...",
      "verificationGasLimit": "0x...",
      "preVerificationGas": "0x...",
      "maxFeePerGas": "0x...",
      "maxPriorityFeePerGas": "0x...",
      "paymaster": "0x...",
      "paymasterData": "0x...",
      "signature": "0x..."
    },
    "hashToSign": "0xabcdef...",
    "chainId": 1135
  },
  "message": "UserOperation prepared. Sign hashToSign with personal_sign..."
}

Submit Signed UserOperation

POST /api/v2/submit Relay signed UserOp to bundler

Request Body

{
  "chainId": 1135,
  "userOperation": { /* full userOperation from /api/v2/prepare */ },
  "signature": "0x...your_personal_sign_result..."
}

Parameters

FieldTypeRequiredDescription
chainIdnumberYesMust match the chain from prepare
userOperationobjectYesFull UserOp object from prepare response
signaturehex stringYes65-byte ECDSA signature (0x + 130 hex chars)

Response

{
  "success": true,
  "data": {
    "userOperationHash": "0x1234...",
    "chainId": 1135
  },
  "message": "UserOperation submitted successfully on chain 1135"
}

The server encodes the signature in Safe format (adjusts v by +4 for the eth_sign path, prepends validAfter=0 + validUntil=0) and sends to the bundler via eth_sendUserOperation.

Cross-Chain Bridging (Across Protocol)

Bridge tokens between any supported chains using Across Protocol. The V2 bridge endpoint uses the same prepare → sign → submit flow — no private key needed. The server fetches an Across quote, builds the approve + depositV3 calls, and returns a UserOp for you to sign. Gas is paid in the token being bridged.

Supported routes: Bridging works on all Pimlico-supported chains (Ethereum, Base, Polygon, Celo, Arbitrum, Avalanche, BNB, Berachain). Lisk bridging is not directly supported via Across but you can construct depositV3 calls manually via /api/v2/prepare.
POST /api/v2/bridge Prepare a cross-chain bridge (client-side signing)

Request Body

{
  "ownerAddress": "0xYourEOAAddress",
  "sourceChainId": 137,
  "destChainId": 42161,
  "rpcUrl": "https://polygon-rpc.com",
  "tokenAddress": "0x3c499c542cEF5E3811e1192ce70d8cC03d5c3359",
  "amount": 50.0,
  "recipient": "0xRecipientOnDestChain"
}

Parameters

FieldTypeRequiredDescription
ownerAddressaddressYesEOA address (NOT private key)
sourceChainIdnumberYesSource chain ID
destChainIdnumberYesDestination chain ID
rpcUrlstringYesSource chain RPC
tokenAddressaddressYesToken to bridge (USDC, USDT)
amountnumberYesAmount (human-readable)
recipientaddressNoRecipient on dest chain (defaults to same smart account)

Response

{
  "success": true,
  "data": {
    "smartAccountAddress": "0x...",
    "userOperation": { /* ...full UserOp... */ },
    "hashToSign": "0xabcdef...",
    "chainId": 137,
    "bridge": {
      "sourceChainId": 137,
      "destChainId": 42161,
      "inputAmount": "50.0",
      "outputAmount": "49.85",
      "fee": "0.15",
      "estimatedFillTime": "~2-5 minutes"
    }
  },
  "message": "Sign hashToSign and submit to /api/v2/submit"
}

Sign the hashToSign and submit via POST /api/v2/submit — same as any other v2 operation.

Wallet Integrations & Signing

The hashToSign from /api/v2/prepare is the Safe operation hash (EIP-712). Sign it with personal_sign using any wallet:

Important: Use personal_sign (EIP-191), not eth_sign. The Safe4337Module expects the \x19Ethereum Signed Message:\n32 prefix. The server adjusts v (+4) for Safe's eth_sign verification path.

Privy

Privy embedded wallets work seamlessly. Use the useSignMessage hook:

Token Transfer

import { usePrivy, useSignMessage } from '@privy-io/react-auth';

function GaslessTransfer() {
  const { user } = usePrivy();
  const { signMessage } = useSignMessage();
  const address = user?.wallet?.address;

  async function send() {
    // Step 1: Prepare (send EOA address, NOT private key)
    const res = await fetch('/api/v2/prepare', {
      method: 'POST',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify({
        ownerAddress: address,
        chainId: 137,
        rpcUrl: 'https://polygon-rpc.com',
        tokenAddress: '0x3c499c542cEF5E3811e1192ce70d8cC03d5c3359',
        recipient: '0xRecipient',
        amount: 5.0,
      }),
    });
    const { data } = await res.json();

    // Step 2: Sign with Privy embedded wallet
    const signature = await signMessage(data.hashToSign);

    // Step 3: Submit
    await fetch('/api/v2/submit', {
      method: 'POST',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify({ chainId: 137, userOperation: data.userOperation, signature }),
    });
  }
}

ERC-20 Approve (any contract call)

import { usePrivy, useSignMessage } from '@privy-io/react-auth';

function GaslessApprove() {
  const { user } = usePrivy();
  const { signMessage } = useSignMessage();
  const address = user?.wallet?.address;

  async function approveToken(spender, amount) {
    // Use "calls" instead of tokenAddress/recipient/amount
    const res = await fetch('/api/v2/prepare', {
      method: 'POST',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify({
        ownerAddress: address,
        chainId: 1135,
        rpcUrl: 'https://rpc.api.lisk.com',
        calls: [{
          to: '0xF242275d3a6527d877f2c927a82D9b057609cc71', // Bridged USDC on Lisk
          abi: ['function approve(address spender, uint256 amount)'],
          functionName: 'approve',
          args: [spender, amount],
        }],
        paymasterToken: '0xF242275d3a6527d877f2c927a82D9b057609cc71',
      }),
    });
    const { data } = await res.json();

    // Sign with Privy
    const signature = await signMessage(data.hashToSign);

    // Submit
    await fetch('/api/v2/submit', {
      method: 'POST',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify({ chainId: 1135, userOperation: data.userOperation, signature }),
    });
  }

  return (
    <button onClick={() => approveToken('0xDEXRouter', '1000000')}>
      Approve USDC
    </button>
  );
}

Cross-Chain Bridge

import { usePrivy, useSignMessage } from '@privy-io/react-auth';

function GaslessBridge() {
  const { user } = usePrivy();
  const { signMessage } = useSignMessage();
  const address = user?.wallet?.address;

  async function bridge() {
    // Step 1: Prepare bridge UserOp
    const res = await fetch('https://smart-wallet-519w.onrender.com/api/v2/bridge', {
      method: 'POST',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify({
        ownerAddress: address,
        sourceChainId: 137,
        destChainId: 42161,
        rpcUrl: 'https://polygon-rpc.com',
        tokenAddress: '0x3c499c542cEF5E3811e1192ce70d8cC03d5c3359',
        amount: 25.0,
      }),
    });
    const { data } = await res.json();

    // Step 2: Sign with Privy embedded wallet
    const signature = await signMessage(data.hashToSign);

    // Step 3: Submit
    await fetch('https://smart-wallet-519w.onrender.com/api/v2/submit', {
      method: 'POST',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify({ chainId: 137, userOperation: data.userOperation, signature }),
    });
  }
}

wagmi / RainbowKit / ConnectKit

Works with any wagmi-compatible wallet (MetaMask, WalletConnect, Coinbase Wallet, etc.):

import { useAccount, useSignMessage } from 'wagmi';
import { toBytes } from 'viem';

function GaslessAction() {
  const { address } = useAccount();
  const { signMessageAsync } = useSignMessage();

  async function execute() {
    // Prepare — works for transfers, approvals, any contract call
    const res = await fetch('/api/v2/prepare', {
      method: 'POST',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify({
        ownerAddress: address,
        chainId: 8453,
        rpcUrl: 'https://mainnet.base.org',
        calls: [{ to: '0xContract', data: '0x...' }],
        paymasterToken: '0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913',
      }),
    });
    const { data } = await res.json();

    // Sign with connected wallet (MetaMask, WalletConnect, etc.)
    const signature = await signMessageAsync({
      message: { raw: toBytes(data.hashToSign) },
    });

    // Submit
    await fetch('/api/v2/submit', {
      method: 'POST',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify({ chainId: 8453, userOperation: data.userOperation, signature }),
    });
  }
}

Cross-Chain Bridge

import { useAccount, useSignMessage } from 'wagmi';
import { toBytes } from 'viem';

function GaslessBridge() {
  const { address } = useAccount();
  const { signMessageAsync } = useSignMessage();

  async function bridge() {
    // Step 1: Prepare bridge UserOp
    const res = await fetch('https://smart-wallet-519w.onrender.com/api/v2/bridge', {
      method: 'POST',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify({
        ownerAddress: address,
        sourceChainId: 8453,
        destChainId: 42161,
        rpcUrl: 'https://mainnet.base.org',
        tokenAddress: '0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913',
        amount: 100.0,
      }),
    });
    const { data } = await res.json();

    // Step 2: Sign with connected wallet (MetaMask, WalletConnect, etc.)
    const signature = await signMessageAsync({
      message: { raw: toBytes(data.hashToSign) },
    });

    // Step 3: Submit
    await fetch('https://smart-wallet-519w.onrender.com/api/v2/submit', {
      method: 'POST',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify({ chainId: 8453, userOperation: data.userOperation, signature }),
    });
  }
}

Server-Side Signing (ethers.js)

For backend services, bots, or automation — generate or use a private key server-side:

import { ethers } from 'ethers';

// Generate a new wallet (or use existing key)
const wallet = ethers.Wallet.createRandom();
// Or: const wallet = new ethers.Wallet('0xYourPrivateKey');

console.log('EOA:', wallet.address);  // save this
console.log('Key:', wallet.privateKey); // store securely!

// Prepare any transaction
const prepareRes = await fetch('https://smart-wallet-519w.onrender.com/api/v2/prepare', {
  method: 'POST',
  headers: { 'Content-Type': 'application/json' },
  body: JSON.stringify({
    ownerAddress: wallet.address,
    chainId: 1135,
    rpcUrl: 'https://rpc.api.lisk.com',
    tokenAddress: '0xF242275d3a6527d877f2c927a82D9b057609cc71',
    recipient: '0xRecipient',
    amount: 10.0,
  }),
});
const { data } = await prepareRes.json();

// Sign locally
const signature = await wallet.signMessage(ethers.utils.arrayify(data.hashToSign));

// Submit
const result = await fetch('https://smart-wallet-519w.onrender.com/api/v2/submit', {
  method: 'POST',
  headers: { 'Content-Type': 'application/json' },
  body: JSON.stringify({ chainId: 1135, userOperation: data.userOperation, signature }),
}).then(r => r.json());

console.log('UserOp:', result.data.userOperationHash);

Cross-Chain Bridge

import { ethers } from 'ethers';

const wallet = new ethers.Wallet('0xYourPrivateKey');

// Step 1: Prepare bridge UserOp
const prepareRes = await fetch('https://smart-wallet-519w.onrender.com/api/v2/bridge', {
  method: 'POST',
  headers: { 'Content-Type': 'application/json' },
  body: JSON.stringify({
    ownerAddress: wallet.address,
    sourceChainId: 137,
    destChainId: 42161,
    rpcUrl: 'https://polygon-rpc.com',
    tokenAddress: '0x3c499c542cEF5E3811e1192ce70d8cC03d5c3359',
    amount: 50.0,
  }),
});
const { data: bridgeData } = await prepareRes.json();

// Step 2: Sign locally with ethers.Wallet.signMessage
const bridgeSig = await wallet.signMessage(ethers.utils.arrayify(bridgeData.hashToSign));

// Step 3: Submit
const bridgeResult = await fetch('https://smart-wallet-519w.onrender.com/api/v2/submit', {
  method: 'POST',
  headers: { 'Content-Type': 'application/json' },
  body: JSON.stringify({ chainId: 137, userOperation: bridgeData.userOperation, signature: bridgeSig }),
}).then(r => r.json());

console.log('Bridge UserOp:', bridgeResult.data.userOperationHash);

MetaMask / Browser window.ethereum

const signature = await window.ethereum.request({
  method: 'personal_sign',
  params: [hashToSign, ownerAddress],
});

viem (standalone)

import { privateKeyToAccount } from 'viem/accounts';
const account = privateKeyToAccount(privateKey);
const signature = await account.signMessage({ message: { raw: hashToSign } });

Examples

Token Transfer

curl -X POST https://smart-wallet-519w.onrender.com/api/v2/prepare \
  -H "Content-Type: application/json" \
  -d '{
    "ownerAddress": "0xYourEOA",
    "chainId": 1135,
    "rpcUrl": "https://rpc.api.lisk.com",
    "tokenAddress": "0xF242275d3a6527d877f2c927a82D9b057609cc71",
    "recipient": "0xRecipient",
    "amount": 5.0
  }'

ERC-20 Approve (any chain including Lisk)

Approve a contract to spend tokens on your behalf. Works on all chains:

curl -X POST https://smart-wallet-519w.onrender.com/api/v2/prepare \
  -H "Content-Type: application/json" \
  -d '{
    "ownerAddress": "0xYourEOA",
    "chainId": 1135,
    "rpcUrl": "https://rpc.api.lisk.com",
    "calls": [
      {
        "to": "0xF242275d3a6527d877f2c927a82D9b057609cc71",
        "abi": ["function approve(address spender, uint256 amount)"],
        "functionName": "approve",
        "args": ["0xSpenderContract", "1000000000"]
      }
    ],
    "paymasterToken": "0xF242275d3a6527d877f2c927a82D9b057609cc71"
  }'

Multi-Call: Approve + Swap (single UserOp)

Execute multiple contract calls atomically in one transaction:

curl -X POST https://smart-wallet-519w.onrender.com/api/v2/prepare \
  -H "Content-Type: application/json" \
  -d '{
    "ownerAddress": "0xYourEOA",
    "chainId": 8453,
    "rpcUrl": "https://mainnet.base.org",
    "calls": [
      {
        "to": "0xTokenAddress",
        "abi": ["function approve(address,uint256)"],
        "functionName": "approve",
        "args": ["0xDEXRouter", "1000000"]
      },
      {
        "to": "0xDEXRouter",
        "data": "0x...encoded swap calldata..."
      }
    ],
    "paymasterToken": "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913"
  }'

Raw Calldata (any contract interaction)

curl -X POST https://smart-wallet-519w.onrender.com/api/v2/prepare \
  -H "Content-Type: application/json" \
  -d '{
    "ownerAddress": "0xYourEOA",
    "chainId": 1135,
    "rpcUrl": "https://rpc.api.lisk.com",
    "calls": [
      {
        "to": "0xAnyContract",
        "data": "0xabcdef...raw calldata...",
        "value": "0"
      }
    ],
    "paymasterToken": "0xF242275d3a6527d877f2c927a82D9b057609cc71"
  }'

Cross-Chain Bridge (Across Protocol)

Bridge USDC from Polygon to Arbitrum (client-side signing):

# Step 1: Prepare bridge UserOp
curl -X POST https://smart-wallet-519w.onrender.com/api/v2/bridge \
  -H "Content-Type: application/json" \
  -d '{
    "ownerAddress": "0xYourEOA",
    "sourceChainId": 137,
    "destChainId": 42161,
    "rpcUrl": "https://polygon-rpc.com",
    "tokenAddress": "0x3c499c542cEF5E3811e1192ce70d8cC03d5c3359",
    "amount": 50.0
  }'

# Step 2: Sign hashToSign locally with your wallet

# Step 3: Submit signed UserOp
curl -X POST https://smart-wallet-519w.onrender.com/api/v2/submit \
  -H "Content-Type: application/json" \
  -d '{
    "chainId": 137,
    "userOperation": { "...from prepare response..." },
    "signature": "0x...your signature..."
  }'

Use with Claude / LLMs

You can give Claude (or any LLM) the API reference file so it can build transactions for you. Download the markdown file and include it in your prompt context:

Download api-reference.md

How to use with Claude

# In your Claude prompt / system prompt, include:

You have access to the Light Infra API. Here is the API reference:

<api-reference>
{paste contents of api-reference.md}
</api-reference>

Use the /api/v2/prepare and /api/v2/submit endpoints to build
gasless transactions. The user will sign the hashToSign locally.

Example Claude interaction

User: Send 10 USDC to 0xBob on Polygon, my address is 0xAlice

Claude: I'll prepare a gasless USDC transfer for you.

curl -X POST https://smart-wallet-519w.onrender.com/api/v2/prepare \
  -H "Content-Type: application/json" \
  -d '{
    "ownerAddress": "0xAlice",
    "chainId": 137,
    "rpcUrl": "https://polygon-rpc.com",
    "tokenAddress": "0x3c499c542cEF5E3811e1192ce70d8cC03d5c3359",
    "recipient": "0xBob",
    "amount": 10.0
  }'

Sign the hashToSign from the response, then submit:

curl -X POST https://smart-wallet-519w.onrender.com/api/v2/submit ...
Tip: The .md format works best for LLM context. You can also paste the file contents directly into the system prompt for tools like Claude Code, Cursor, or Windsurf.