/ Documentation
Claude Code Skill

Portals Foresight API

Simulate any EVM transaction and get back structured token changes, gas costs, and metadata. One API call. No wallet connection, no balance, no approval required.

Automatic balance injection
Sender doesn't hold the tokens? We handle it automatically. Simulate with any address, any token, any amount.
Automatic token approval
Token approvals are handled for you. Send the swap calldata and nothing else.
Token metadata included
Every response includes name, symbol, and decimals for all tokens. Display human-readable amounts instantly.
Human-readable reverts
When transactions fail, you get "Insufficient liquidity" instead of a hex blob.
Base URL

All API requests are made to https://simulate.portals.fi/v1

Quick Start

Simulate a USDC to DAI swap on Ethereum in under a minute.

1. Get an API key

Sign up at portals.fi/simulate to get your API key. Free tier includes 1,000 simulations per month.

2. Make your first request

curl -X POST "https://simulate.portals.fi/v1/simulate?network=ethereum" \
  -H "Content-Type: application/json" \
  -H "X-Api-Key: YOUR_API_KEY" \
  -d '{
    "inputToken": "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48",
    "inputAmount": "10000000000",
    "tx": {
      "from": "0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045",
      "to": "0x1111111254EEB25477B68fb85Ed929f73A960582",
      "data": "0x12aa3caf...",
      "value": "0"
    }
  }'
const response = await fetch("https://simulate.portals.fi/v1/simulate?network=ethereum", {
  method: "POST",
  headers: {
    "Content-Type": "application/json",
    "X-Api-Key": "YOUR_API_KEY",
  },
  body: JSON.stringify({
    inputToken: "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48",
    inputAmount: "10000000000",
    tx: {
      from: "0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045",
      to: "0x1111111254EEB25477B68fb85Ed929f73A960582",
      data: "0x12aa3caf...",
      value: "0",
    },
  }),
});

const result = await response.json();
console.log(result.assetChanges);
import requests

response = requests.post(
    "https://simulate.portals.fi/v1/simulate",
    params={"network": "ethereum"},
    headers={
        "Content-Type": "application/json",
        "X-Api-Key": "YOUR_API_KEY",
    },
    json={
        "inputToken": "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48",
        "inputAmount": "10000000000",
        "tx": {
            "from": "0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045",
            "to": "0x1111111254EEB25477B68fb85Ed929f73A960582",
            "data": "0x12aa3caf...",
            "value": "0",
        },
    },
)

result = response.json()
print(result["assetChanges"])

3. Read the response

A successful simulation returns structured asset changes — including NFT positions — gas costs, and token metadata:

{
  "success": true,
  "sender": "0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045",
  "assetChanges": [
    {
      "token": "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48",
      "amount": "4217390000",
      "direction": "sent",
      "standard": "erc20"
    },
    {
      "token": "0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2",
      "amount": "1500000000000000000",
      "direction": "sent",
      "standard": "erc20"
    },
    {
      "token": "0xC36442b4a4522E871399CD717aBDD847Ab11FE88",
      "amount": "1",
      "direction": "received",
      "standard": "erc721",
      "tokenId": "1207888"
    }
  ],
  "gasUsed": "461218",
  "gasCost": "...",
  "tokenMetadata": {
    "0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48": {
      "name": "USD Coin",
      "symbol": "USDC",
      "decimals": 6
    },
    "0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2": {
      "name": "Wrapped Ether",
      "symbol": "WETH",
      "decimals": 18
    },
    "0xc36442b4a4522e871399cd717abdd847ab11fe88": {
      "name": "Uniswap V3 Positions NFT-V1",
      "symbol": "UNI-V3-POS",
      "decimals": 0,
      "standard": "erc721"
    }
  },
  "inputTokens": [...],
  "outputTokens": [...],
  "events": [...],
  "errors": []
}

Authentication

Authenticate requests by passing your API key in the X-Api-Key header.

X-Api-Key: your_api_key_here

The health endpoint is exempt from authentication.

Keep your key secret

Never expose your API key in client-side code. Make simulation requests from your backend server.

Invalid or missing API keys return:

{
  "success": false,
  "error": "Invalid or missing API key"
}

Simulate Transaction

POST /v1/simulate?network={network}

Simulates an EVM transaction against live chain state and returns structured results including net asset changes, gas costs, decoded events, and token metadata.

Query Parameters

network string Required
The target chain. Case-insensitive. See Supported Chains for the full list of 31 networks.

Request Body

inputToken string Required
The ERC20 token address being spent. Use 0x0000000000000000000000000000000000000000 for native ETH (or the chain's native gas token).
inputAmount string Required
Amount in the token's smallest unit as a decimal string. For example, "10000000000" for 10,000 USDC (6 decimals) or "1000000000000000000" for 1 ETH (18 decimals). No decimals, no hex.
tx object Required
The transaction to simulate. Contains from, to, data, and value.
tx.from string Required
The sender address. Does not need to hold the input token — balance is injected automatically.
tx.to string Required
The contract address to call (e.g., a DEX router, vault, or lending pool).
tx.data string Required
ABI-encoded calldata, 0x-prefixed. Use "0x" for a plain ETH transfer.
tx.value string Required
Native value in wei as a decimal string. Use "0" for pure ERC20 swaps.
recipient string Optional
The address receiving output tokens. Defaults to tx.from if omitted. Useful when the protocol routes outputs to a different address.

Response Format

All simulation responses return HTTP 200, including reverted transactions. Check the success field to determine the outcome.

Response Fields

FieldTypeDescription
successbooleanWhether the simulated transaction succeeded on-chain
senderstringThe address used as the sender in the simulation
assetChangesAssetChange[]Net token balance changes relative to the sender/recipient
inputTokensTokenAmount[]Tokens sent by the user (subset of assetChanges where direction is "sent")
outputTokensTokenAmount[]Tokens received by the user (subset of assetChanges where direction is "received")
eventsTokenEvent[]Individual token transfer events from the simulated transaction
gasUsedstringGas units consumed (decimal string)
gasCoststringEstimated gas cost in wei based on current network conditions
tokenMetadataRecordName, symbol, and decimals for each token, keyed by lowercase address
errorstring?Human-readable revert reason (only present when success is false)
errorsstring[]Non-fatal warnings (usually empty)

AssetChange Object

FieldTypeDescription
tokenstringToken contract address. 0x000...000 for native ETH.
amountstringAbsolute net amount in base units (always positive). For ERC721 NFTs, always "1".
direction"sent" | "received"Whether the user spent or gained this asset
standardstringToken standard: "erc20", "native", "erc721", or "erc1155"
tokenIdstring?Token ID for NFTs (ERC721/ERC1155). Not present for fungible tokens.

TokenEvent Object

FieldTypeDescription
typestringOne of: transfer, mint, burn, wrap, unwrap, nft_transfer, nft_mint, nft_burn
standardstringToken standard: "erc20", "native", "erc721", or "erc1155"
tokenstringToken contract address
fromstringSource address
tostringDestination address
amountstringTransfer amount in base units. "1" for ERC721.
tokenIdstring?Token ID for ERC721/ERC1155 events
logIndexnumberPosition in the transaction's event log

TokenMetadata Object

FieldTypeDescription
namestringToken name (e.g., "USD Coin"). Defaults to "Unknown".
symbolstringToken symbol (e.g., "USDC"). Defaults to "????".
decimalsnumberToken decimals (e.g., 6 for USDC, 18 for DAI). Defaults to 18.
standardstring?Token standard when detected via ERC165: "erc721" or "erc1155". Not present for fungible tokens.

TokenAmount Object

Used by inputTokens and outputTokens to represent the tokens sent or received by the user.

FieldTypeDescription
tokenstringToken contract address
amountstringAmount in base units (always positive)
standardstring?Token standard ("erc721" or "erc1155"). Not present for ERC20 or native tokens.
tokenIdstring?Token ID for NFTs (ERC721/ERC1155). Not present for fungible tokens.

Error Handling

Reverted transactions

A reverted simulation still returns HTTP 200 with "success": false. The error field contains the decoded revert reason.

{
  "success": false,
  "error": "Insufficient output amount",
  "sender": "0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045",
  "gasUsed": "50000",
  "gasCost": "500000000000000",
  "assetChanges": [],
  "events": [],
  "errors": []
}

HTTP Error Codes

StatusMeaningResponse Shape
200Simulation completed (check success field)SimulationResult
400Validation error (invalid params or network){ success: false, errors: [...] }
401Invalid or missing API key{ success: false, error: "..." }
429Rate limit exceeded{ success: false, error: "Rate limit exceeded" }
500Internal server error{ success: false, error: "..." }
503Server shutting down{ success: false, error: "Server is shutting down" }

Validation errors

Invalid request fields return HTTP 400 with an errors array listing every issue:

{
  "success": false,
  "errors": [
    "inputToken must be a valid Ethereum address",
    "tx.value must be a valid number string"
  ]
}

Health Check

GET /v1/health

Returns the server's health status, dependency checks, and list of available networks. No authentication required.

Returns 200 when healthy or degraded, 503 when all services are down.

Status Values

ValueHTTP CodeMeaning
ok200All services healthy
degraded200Some RPC providers are unreachable but service is functional
error503Critical services are down
{
  "status": "ok",
  "checks": {
    "redis": {
      "status": "ok",
      "latencyMs": 1
    },
    "rpc": {
      "ethereum": { "status": "ok", "latencyMs": 42 },
      "base": { "status": "ok", "latencyMs": 38 },
      "polygon": { "status": "ok", "latencyMs": 55 }
    }
  },
  "networks": ["ethereum", "polygon", "base", "optimism", ...]
}

Batch Simulation

POST /v1/simulate/batch?network={network}

Simulate multiple transactions in a single call with shared state. All steps execute sequentially in one simulated block — the state changes from each step carry forward to the next. Ideal for multi-step DeFi flows like leverage loops (supply → borrow → swap → re-supply).

Pro plan and above

Batch simulations are available on Pro, Business, and Enterprise plans.

Query Parameters

network string Required
The target chain. Case-insensitive. See Supported Chains for the full list of 31 networks.

Batch Request Body

The request body contains a sender, optional recipient, and an array of steps (1–50).

sender string Required
Sender address for all steps. Does not need to hold the input tokens — balance is injected automatically.
recipient string Optional
The address receiving output tokens. Defaults to sender if omitted.
steps BatchStep[] Required
Array of 1–50 transaction steps to simulate sequentially.
steps[].tx object Required
Transaction object — same shape as the single endpoint tx field (from, to, data, value).
steps[].inputToken string Optional
ERC20 address of the token being spent in this step. Only needed for steps that introduce new tokens (not consumed from a prior step).
steps[].inputAmount string Optional
Amount in smallest unit. Required when inputToken is provided.

Batch Response

The batch response contains per-step results and an aggregate that mirrors the single-endpoint response shape.

FieldTypeDescription
successbooleanTrue if all steps succeeded
failedAtStepnumber?Index of the first failed step. Absent when all steps succeed.
stepsStepResult[]Per-step results (see below)
aggregateSimulationResultCombined net asset changes — same shape as the single endpoint response

StepResult

FieldTypeDescription
stepIndexnumberZero-based index of this step
successbooleanWhether this individual step succeeded
gasUsedstringGas consumed by this step
errorstring?Revert reason if step failed, otherwise absent

Examples

ERC20 token swap

Simulate a WETH to DAI swap through a DEX router. The sender doesn't need to hold WETH — balance is injected automatically.

curl -X POST "https://simulate.portals.fi/v1/simulate?network=ethereum" \
  -H "Content-Type: application/json" \
  -H "X-Api-Key: YOUR_API_KEY" \
  -d '{
    "inputToken": "0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2",
    "inputAmount": "1000000000000000000",
    "tx": {
      "from": "0x1111111111111111111111111111111111111111",
      "to": "0x7a250d5630B4cF539739dF2C5dAcb4c659F2488D",
      "data": "0x38ed1739...",
      "value": "0"
    }
  }'
const result = await fetch("https://simulate.portals.fi/v1/simulate?network=ethereum", {
  method: "POST",
  headers: {
    "Content-Type": "application/json",
    "X-Api-Key": process.env.PORTALS_API_KEY,
  },
  body: JSON.stringify({
    inputToken: "0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2", // WETH
    inputAmount: "1000000000000000000",              // 1 WETH
    tx: {
      from: "0x1111111111111111111111111111111111111111",
      to: "0x7a250d5630B4cF539739dF2C5dAcb4c659F2488D",  // Uniswap V2
      data: "0x38ed1739...",
      value: "0",
    },
  }),
}).then(r => r.json());

Native ETH transfer

Simulate a plain ETH transfer. Use the zero address as inputToken and set tx.value to the amount in wei.

{
  "inputToken": "0x0000000000000000000000000000000000000000",
  "inputAmount": "1000000000000000000",
  "tx": {
    "from": "0x1111111111111111111111111111111111111111",
    "to": "0x2222222222222222222222222222222222222222",
    "data": "0x",
    "value": "1000000000000000000"
  }
}

Vault deposit with separate recipient

Use the recipient field when the output tokens go to a different address than the sender.

{
  "inputToken": "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48",
  "inputAmount": "10000000000",
  "recipient": "0x2222222222222222222222222222222222222222",
  "tx": {
    "from": "0x1111111111111111111111111111111111111111",
    "to": "0x8eB67A509616cd6A7c1B3c8C21D48FF57df3d458",
    "data": "0x6e553f65...",  // ERC4626 deposit(amount, receiver)
    "value": "0"
  }
}

NFT / CLP position mint

Simulate a Uniswap V3 concentrated liquidity position mint. The response includes the ERC721 position NFT with its tokenId.

{
  "inputToken": "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48",
  "inputAmount": "10000000000",
  "tx": {
    "from": "0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045",
    "to": "0xC36442b4a4522E871399CD717aBDD847Ab11FE88",
    "data": "0x88316456...",  // NonfungiblePositionManager.mint()
    "value": "0"
  }
}
{
  "success": true,
  "assetChanges": [
    {
      "token": "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48",
      "amount": "4217390000",
      "direction": "sent",
      "standard": "erc20"
    },
    {
      "token": "0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2",
      "amount": "1500000000000000000",
      "direction": "sent",
      "standard": "erc20"
    },
    {
      "token": "0xC36442b4a4522E871399CD717aBDD847Ab11FE88",
      "amount": "1",
      "direction": "received",
      "standard": "erc721",
      "tokenId": "1207888"
    }
  ],
  "tokenMetadata": {
    "0xc36442b4a4522e871399cd717abdd847ab11fe88": {
      "name": "Uniswap V3 Positions NFT-V1",
      "symbol": "UNI-V3-POS",
      "decimals": 0,
      "standard": "erc721"
    },
    ...
  },
  ...
}

Multi-chain simulation

Simulate on any supported chain by changing the network query parameter. Token addresses differ per chain.

POST /v1/simulate?network=base

{
  "inputToken": "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913",  // USDC on Base
  "inputAmount": "1000000000",
  "tx": { ... }
}
POST /v1/simulate?network=polygon

{
  "inputToken": "0x3c499c542cEF5E3811e1192ce70d8cC03d5c3359",  // USDC on Polygon
  "inputAmount": "1000000000",
  "tx": { ... }
}
POST /v1/simulate?network=arbitrum

{
  "inputToken": "0xaf88d065e77c8cC2239327C5EDb3A432268e5831",  // USDC on Arbitrum
  "inputAmount": "1000000000",
  "tx": { ... }
}

Rendering results in your UI

Use tokenMetadata to display human-readable amounts:

function formatAssetChange(change, metadata) {
  const meta = metadata[change.token.toLowerCase()];

  // NFT: show token name + ID
  if (change.standard === "erc721" || change.standard === "erc1155") {
    const name = meta?.name || change.token;
    const sign = change.direction === "sent" ? "-" : "+";
    return `${sign}1 ${name} #${change.tokenId}`;
  }

  // Fungible: show decimal amount
  if (!meta) return `${change.amount} ${change.token}`;
  const amount = Number(change.amount) / 10 ** meta.decimals;
  const sign = change.direction === "sent" ? "-" : "+";
  return `${sign}${amount.toLocaleString()} ${meta.symbol}`;
}

// Output: "-4,217.39 USDC" or "+1 Uniswap V3 Positions NFT-V1 #1207888"

Batch simulation — leverage loop

Chain Aave V3 supply → borrow → swap in a single call. Only the first step needs inputToken/inputAmount since subsequent steps consume tokens from prior state.

curl -X POST "https://simulate.portals.fi/v1/simulate/batch?network=ethereum" \
  -H "Content-Type: application/json" \
  -H "X-Api-Key: YOUR_API_KEY" \
  -d '{
    "sender": "0x1111111111111111111111111111111111111111",
    "steps": [
      {
        "inputToken": "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48",
        "inputAmount": "10000000000",
        "tx": {
          "from": "0x1111111111111111111111111111111111111111",
          "to": "0x87870Bca3F3fD6335C3F4ce8392D69350B4fA4E2",
          "data": "0x617ba037...",
          "value": "0"
        }
      },
      {
        "tx": {
          "from": "0x1111111111111111111111111111111111111111",
          "to": "0x87870Bca3F3fD6335C3F4ce8392D69350B4fA4E2",
          "data": "0xa415bcad...",
          "value": "0"
        }
      },
      {
        "tx": {
          "from": "0x1111111111111111111111111111111111111111",
          "to": "0xE592427A0AEce92De3Edee1F18E0157C05861564",
          "data": "0xc04b8d59...",
          "value": "0"
        }
      }
    ]
  }'
const response = await fetch(
  "https://simulate.portals.fi/v1/simulate/batch?network=ethereum",
  {
    method: "POST",
    headers: {
      "Content-Type": "application/json",
      "X-Api-Key": process.env.PORTALS_API_KEY,
    },
    body: JSON.stringify({
      sender: "0x1111111111111111111111111111111111111111",
      steps: [
        {
          inputToken: "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48", // USDC
          inputAmount: "10000000000",                      // 10,000 USDC
          tx: {
            from: "0x1111111111111111111111111111111111111111",
            to: "0x87870Bca3F3fD6335C3F4ce8392D69350B4fA4E2",   // Aave V3
            data: "0x617ba037...",                             // supply()
            value: "0",
          },
        },
        {
          tx: {
            from: "0x1111111111111111111111111111111111111111",
            to: "0x87870Bca3F3fD6335C3F4ce8392D69350B4fA4E2",   // Aave V3
            data: "0xa415bcad...",                             // borrow()
            value: "0",
          },
        },
        {
          tx: {
            from: "0x1111111111111111111111111111111111111111",
            to: "0xE592427A0AEce92De3Edee1F18E0157C05861564",   // Uniswap V3
            data: "0xc04b8d59...",                             // exactInput()
            value: "0",
          },
        },
      ],
    }),
  }
);

const data = await response.json();
console.log(data.success);         // true
console.log(data.steps.length);    // 3
console.log(data.aggregate);       // combined SimulationResult
import requests

response = requests.post(
    "https://simulate.portals.fi/v1/simulate/batch",
    params={"network": "ethereum"},
    headers={
        "Content-Type": "application/json",
        "X-Api-Key": "YOUR_API_KEY",
    },
    json={
        "sender": "0x1111111111111111111111111111111111111111",
        "steps": [
            {
                "inputToken": "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48",
                "inputAmount": "10000000000",
                "tx": {
                    "from": "0x1111111111111111111111111111111111111111",
                    "to": "0x87870Bca3F3fD6335C3F4ce8392D69350B4fA4E2",
                    "data": "0x617ba037...",
                    "value": "0",
                },
            },
            {
                "tx": {
                    "from": "0x1111111111111111111111111111111111111111",
                    "to": "0x87870Bca3F3fD6335C3F4ce8392D69350B4fA4E2",
                    "data": "0xa415bcad...",
                    "value": "0",
                },
            },
            {
                "tx": {
                    "from": "0x1111111111111111111111111111111111111111",
                    "to": "0xE592427A0AEce92De3Edee1F18E0157C05861564",
                    "data": "0xc04b8d59...",
                    "value": "0",
                },
            },
        ],
    },
)

data = response.json()
print(data["success"])         # True
print(len(data["steps"]))     # 3
print(data["aggregate"])      # combined SimulationResult
{
  "success": true,
  "steps": [
    { "stepIndex": 0, "success": true, "gasUsed": "248103" },
    { "stepIndex": 1, "success": true, "gasUsed": "312847" },
    { "stepIndex": 2, "success": true, "gasUsed": "184529" }
  ],
  "aggregate": {
    "success": true,
    "sender": "0x1111111111111111111111111111111111111111",
    "assetChanges": [
      {
        "token": "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48",
        "amount": "10000000000",
        "direction": "sent",
        "standard": "erc20"
      }
    ],
    "inputTokens": [
      { "token": "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48", "amount": "10000000000" }
    ],
    "outputTokens": [...],
    "events": [...],
    "gasUsed": "745479",
    "gasCost": "...",
    "tokenMetadata": {
      "0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48": {
        "name": "USD Coin",
        "symbol": "USDC",
        "decimals": 6
      }
    },
    "errors": []
  }
}

Supported Chains

Simulate transactions on 31 EVM chains with the same API and response format.

Ethereum1
Base8453
Arbitrum42161
Polygon137
Optimism10
BNB Chain56
Avalanche43114
Sonic146
HyperEVM999
Fraxtal252
Mode34443
Gnosis100
Unichain130
zkSync324
Worldchain480
Metis1088
Lisk1135
Moonbeam1284
Sei1329
Soneium1868
Ronin2020
Mantle5000
Arb Nova42170
Celo42220
Ink57073
Linea59144
BOB60808
Berachain80094
Blast81457
Taiko167000
Scroll534352

How It Works

Understanding the simulation pipeline helps you get the most out of the API.

The simulation pipeline

When you send a request, here's what happens:

1
Balance preparation
We ensure the sender has enough input tokens for the simulation. If they don't, we handle it automatically behind the scenes — no tokens are actually moved or spent.
2
Approval handled
Token approvals are taken care of automatically. You submit just the swap or deposit calldata — nothing else.
3
Isolated execution
Your transaction runs against the latest on-chain state in a fully isolated environment. No state changes persist. No other users are affected.
4
Result processing
The raw simulation output is processed into structured net asset changes for the sender and recipient. Gas cost is estimated from live network conditions.
5
Metadata enrichment
Token name, symbol, and decimals are resolved and cached for every token in the result. The response includes everything your UI needs to render human-readable amounts.

Balance injection

The key feature behind Portals Foresight is automatic balance injection. The sender address doesn't need to hold the input tokens — we handle it so you can simulate with any address, for any token, at any amount.

Our proprietary detection system resolves token compatibility automatically and caches the result. First simulation for a new token takes slightly longer; every subsequent simulation is near-instant.

Broad token coverage

Balance injection works with 99%+ of ERC20 tokens in the wild — standard tokens, proxies, rebasing tokens, vault shares, LP tokens, wrapped tokens, and more. In the rare case a token isn't directly supported, we use a fallback strategy. Always check the sender field in the response to confirm the address used.

What you can simulate

Any EVM transaction that interacts with tokens or NFTs:

Rate Limits

Rate limits are applied globally per IP address. The current limit is returned in response headers.

PlanSimulations / MonthRequests / Second
Free1,0001
Starter25,0005
Pro100,00020
Business500,00050
EnterpriseUnlimitedCustom
Batch simulations (/simulate/batch) are available on Pro plans and above.

Rate limit headers

Every response includes these headers:

HeaderDescription
RateLimit-LimitMaximum requests per second
RateLimit-RemainingRemaining requests in the current window
RateLimit-ResetSeconds until the rate limit window resets

Request tracing

Send an X-Request-Id header to trace requests through your system. If omitted, a UUID is generated automatically. The value is echoed back in the response header.

# Your request
X-Request-Id: my-trace-id-abc123

# Response header
X-Request-Id: my-trace-id-abc123

x402 Payments

AI agents can pay per-request using the x402 protocol instead of an API key. Payments use USDC on Base.

Pricing

EndpointPrice
POST /v1/simulate$0.001 per request
POST /v1/simulate/batch$0.005 per request

How it works

The x402 protocol uses HTTP 402 Payment Required to enable pay-per-request. The flow is fully automatic with compatible client libraries.

StepDescription
1Send request without API key or payment header
2Server returns 402 with PAYMENT-REQUIRED header (Base64 JSON: price, network, wallet)
3Sign the payment payload with your crypto wallet
4Retry with PAYMENT-SIGNATURE header
5Server verifies payment, executes simulation, settles USDC on-chain
No charge on failure

If the simulation returns a 4xx or 5xx error, payment is not settled. You only pay for successful responses.

Client libraries

Use any x402-compatible client to handle the payment flow automatically:

LanguagePackage
JavaScript@x402/fetch or @x402/axios
Pythonx402
Gox402

Example

import { wrapFetch } from "@x402/fetch";

const x402Fetch = wrapFetch(fetch, walletClient);

const response = await x402Fetch(
  "https://simulate.portals.fi/v1/simulate?network=ethereum",
  {
    method: "POST",
    headers: { "Content-Type": "application/json" },
    body: JSON.stringify({
      inputToken: "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48",
      inputAmount: "10000000000",
      tx: {
        from: "0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045",
        to: "0x1111111254EEB25477B68fb85Ed929f73A960582",
        data: "0x12aa3caf...",
        value: "0",
      },
    }),
  }
);

const result = await response.json();

LLM Integration

Give your AI coding assistant full context on the Portals Foresight API. Copy the docs as markdown or install the Claude Code skill.

Copy as Markdown

Click the Copy as Markdown button at the top of this page to copy the entire documentation as clean markdown. Paste it into ChatGPT, Claude, Gemini, Cursor, or any LLM-powered tool as context.

Claude Code Skill

If you use Claude Code, install the Portals Foresight skill so Claude automatically knows the API when you're writing integration code.

View and copy the skill file, then save it into your project:

# Create the skill directory and save SKILL.md into it
mkdir -p .claude/skills/portals-foresight

# Then paste the copied content into:
# .claude/skills/portals-foresight/SKILL.md

Once installed, Claude Code will automatically reference the Portals Foresight API when you ask it to write simulation code, build integrations, or debug API responses. You can also invoke it directly:

# Claude Code will auto-detect when relevant, or invoke directly:
/portals-foresight

# Example prompts that trigger the skill:
"Write a function that simulates a USDC to DAI swap on Ethereum"
"Add transaction preview to our swap UI using Portals Foresight"
"Debug why my simulation is returning success: false"
Works with any LLM

The Copy as Markdown button works with any AI tool. The skill file is specific to Claude Code but the markdown content works everywhere.

API Explorer

Test every endpoint directly in your browser. The explorer comes with pre-filled examples for simulate, batch simulate, and health.

API key required

Click the Authorize button in the explorer and enter your x-api-key before making requests. Don't have one yet? Sign up — the free tier includes 1,000 simulations/month.

Open API Explorer