Claude Code Skill

Portals Foresight API

Simulate EVM transactions and get back structured token changes, gas costs, and metadata. For supported flows, no wallet connection, pre-funded balance, or manual approval transaction is required.

Test without a funded wallet
Set inputToken and inputAmount to test spending supported tokens from any address. No real funds are moved.
Automatic token approval
Test swaps and deposits without sending an approval first. Foresight prepares the allowance for supported input tokens inside the simulation only.
Token metadata included
Responses include name, symbol, and decimals when token transfers are detected and metadata can be resolved.
Human-readable reverts
See why a call reverted, with decoded error names and arguments when available.
Preview before signing
Test supported multisig and smart-account calls before collecting signatures. Check bypassedChecks to see which signature checks were skipped.
Historical re-simulation
Set blockNumber to test against a past block, or replay a confirmed transaction by hash. Historical data availability varies by network.
Base URL

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

Quick Start

Preview wrapping 0.01 ETH into WETH on Ethereum. This complete example needs no funded wallet and sends nothing on-chain.

1. Get an API key

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

2. Make your first request

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

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

response = requests.post(
    "https://foresight.portals.fi/v1/simulate",
    params={"network": "ethereum"},
    headers={
        "Content-Type": "application/json",
        "X-Api-Key": "YOUR_API_KEY",
    },
    json={
        "inputToken": "0x0000000000000000000000000000000000000000",
        "inputAmount": "10000000000000000",
        "tx": {
            "from": "0x1111111111111111111111111111111111111111",
            "to": "0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2",
            "data": "0xd0e30db0",
            "value": "10000000000000000",
        },
    },
)

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

3. Read the response

Check success, then confirm 0.01 ETH was sent and 0.01 WETH received. Selected response fields are shown below; gas usage and cost vary with chain state.

{
  "success": true,
  "sender": "0x1111111111111111111111111111111111111111",
  "assetChanges": [
    {
      "token": "0x0000000000000000000000000000000000000000",
      "amount": "10000000000000000",
      "direction": "sent",
      "standard": "native"
    },
    {
      "token": "0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2",
      "amount": "10000000000000000",
      "direction": "received",
      "standard": "erc20"
    }
  ],
  "gasUsed": "27938",
  "gasCost": "8233129346184",
  "tokenMetadata": {
    "0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2": {
      "name": "Wrapped Ether",
      "symbol": "WETH",
      "decimals": 18
    }
  },
  "errors": []
}

Authentication

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

X-Api-Key: your_api_key_here

Health checks, network discovery, and cached trace retrieval do not require authentication.

Keep your key secret

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

An invalid API key returns 401. Missing keys on x402-enabled endpoints (/v1/simulate, /v1/simulate/batch) return 402 with a payment-required header instead — see x402 Payments.

{
  "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 when available.

Query Parameters

network string Required
The target chain. Case-insensitive. See Supported Chains or GET /v1/networks for the current network registry.

Request Body

inputToken string Required
The ERC20 token address being spent. For the chain's native gas token, use either 0x0000000000000000000000000000000000000000 or 0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE.
inputAmount string Required
Amount in the token's smallest unit as a decimal string of at most 78 digits that fits uint256. For example, "10000000000" for 10,000 USDC (6 decimals) or "1000000000000000000" for 1 ETH (18 decimals). No decimals, no hex. On Arc, native USDC uses 18 decimals, while its ERC-20 interface at 0x3600000000000000000000000000000000000000 uses 6. Both share one balance. Native value and economic output use 18 decimals; ERC-20 inputs, approvals, and calldata keep 6 decimals. Duplicate facade Transfer logs are not counted again. Arc has no wrapped-native deposit contract.
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 supported input tokens — balance is injected automatically when possible.
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 native-token transfer.
tx.value string Required
Native value in wei as a decimal string of at most 78 digits that fits uint256. 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.
blockNumber string | integer Optional
Pin simulation to a positive block number no greater than 9007199254740991, as a decimal string or integer. Omit to use the latest block.
customEvents string[] Optional Pro
Solidity event ABI strings to decode from simulation logs. API-key requests require Pro, Business, or Enterprise; x402 requests are allowed. Up to the first 10 entries are attempted and invalid ABI strings are ignored. Matching logs are returned in the customEvents response field.

Response Format

A completed simulation returns HTTP 200 even when the transaction reverts. Check success for the execution outcome. Invalid requests and service failures use HTTP error codes.

Response Fields

FieldTypeDescription
successbooleanWhether the simulated transaction executed without reverting in the simulated state.
senderstringThe tracked user address used to classify sent and received asset changes. Defaults to tx.from, or to recipient when provided.
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 reported for the simulated execution (decimal string). Not a recommended transaction gas limit.
gasCoststringEstimated gas cost in wei based on current network conditions.
tokenMetadataRecord?Name, symbol, and decimals for detected non-native tokens when metadata is available, keyed by lowercase address.
customEventsDecodedCustomEvent[]?Decoded custom events from simulation logs. Only present when customEvents was provided in the request. See Custom Event Decoding.
errorstring?Human-readable revert reason. Only present when success is false.
errorsstring[]Non-fatal warnings (usually empty).
bypassedChecksBypassInfo[]Signature-gate bypasses automatically applied to make the transaction simulable. Only present when at least one bypass fired. See Bypassed checks.

AssetChange

FieldTypeDescription
tokenstringToken contract address. 0x000...000 for the chain native token.
amountstringAbsolute net amount in base units (always positive). For ERC721 NFTs, always "1".
direction"sent" | "received" | "transfer"Direction relative to the tracked account. "sent"/"received" are used for the simulation sender and recipient. "transfer" appears only in trace responses for pass-through accounts.
standardstringToken standard: "erc20", "native", "erc721", or "erc1155".
tokenIdstring?Token ID for NFTs (ERC721/ERC1155). Not present for fungible tokens.
fromstring?Originating address. Present when the asset left a specific address (set for direction = "sent" and "transfer").
tostring?Destination address. Present when the asset arrived at a specific address (set for direction = "received" and "transfer").

TokenEvent

FieldTypeDescription
typestringEvent kind: transfer, approval, mint, burn, wrap, unwrap, nft_transfer, nft_mint, nft_burn.
standardstringToken standard: "erc20", "native", "erc721", or "erc1155".
tokenstringToken contract address.
fromstring?Source address. Zero address for mints. Not present on approval events.
tostring?Destination address. Zero address for burns. Not present on approval events.
ownerstring?Token owner. Only present on approval events.
spenderstring?Approved spender. Only present on approval events.
amountstringTransfer or approval amount in base units. "1" for ERC721.
tokenIdstring?Token ID for ERC721/ERC1155 events.
logIndexnumberPosition in the transaction's event log.

TokenMetadata

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

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.

BypassInfo

Describes signature checks skipped to preview a supported call before signing. A successful simulation with these entries is not proof that the signatures are valid. Review each entry before treating the transaction as ready to submit.

FieldTypeDescription
kindstringBypass identifier: "safe-exec", "erc20-permit", or "erc4337-handleOps".
targetstringContract address the bypass was applied to.
stepIndexnumber?Optional step index when the bypass is associated with a specific step.
safeobjectPresent when kind = "safe-exec". Contains digest, signers (selected owner addresses used to satisfy the threshold), threshold, ownerCount, version, and optional guardWarning if a Safe Guard is installed (guards are not bypassed and may still revert real transactions).
permitobjectPresent when kind = "erc20-permit". Contains owner, spender, value, slot (storage slot overridden), rewrittenTo (the value written).
erc4337objectPresent when kind = "erc4337-handleOps". Contains entryPointVersion ("v0.6" or "v0.7"), senders (address[]), userOpCount (number), skippedOps (number of UserOps whose signature validation was skipped).

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).

Batch access

API-key batch requests require Pro, Business, or Enterprise. x402 batch requests are accepted without a plan.

Query Parameters

network string Required
The target chain. Case-insensitive. See Supported Chains or GET /v1/networks for the current network registry.

Batch Request Body

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

sender string Required
Execution caller for all steps. Does not need to hold supported input tokens — balance, approvals, and native gas funds are injected for this address when possible.
recipient string Optional
The address receiving output tokens. Defaults to sender if omitted.
trackedAccount string Optional
Account whose net asset movements should be reported in aggregate. Use this when a bundler calls on behalf of a smart account. It changes reporting only: sender still executes the steps, and selecting an account does not fund it, approve spending, or verify its signatures. Omit it to use sender/recipient reporting. See Batch Response for how movements are measured.
customEvents string[] Optional Pro
Solidity event ABI strings to decode from simulation logs. API-key requests require Pro, Business, or Enterprise; x402 requests are allowed. Up to the first 10 entries are attempted and invalid ABI strings are ignored. See Custom Event Decoding for details.
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). Batch execution always uses top-level sender; steps[].tx.from is validated but ignored.
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). For the chain native token, use 0x000...0 or 0xEeee...eeeE.
steps[].inputAmount string Optional
Amount in smallest unit, as a decimal string of at most 78 digits that fits uint256. Required when inputToken is provided.

Batch Response

The response includes a result for each step and a combined aggregate. Its blockNumber (decimal string) and blockHash identify the chain state used before simulated funding, approvals, and execution. Both fields are returned with or without trackedAccount. Foresight checks the block hash again after execution and rejects results if that block changed or cannot be verified. This does not guarantee finality.

Aggregate account scope

With trackedAccount, both aggregate.sender and aggregate.trackedAccount identify the selected account. Its asset changes are net movements across the whole batch: ERC-20 and NFT amounts come from recognized transfer logs; native amounts come from balance checks. This is not a full wallet inventory, a list of gross transfers, or a separate report for each step.

For ERC-4337, a successful handleOps step does not prove that every UserOperation succeeded. Check the matching UserOperationEvent from the expected EntryPoint, including when a step contains multiple accounts. Selecting trackedAccount does not verify signatures or change execution validation.

FieldTypeDescription
successbooleanTrue if all steps succeeded.
failedAtStepnumber?Index of the first failed step. Absent when all steps succeed.
stepsStepResult[]Per-step results (see below).
aggregateSimulationResult & { trackedAccount?: string; blockNumber: string; blockHash: string }Combined net asset changes from the selected perspective, plus verified input-state block provenance.

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.

Custom Event Decoding Pro

Decode protocol-specific events (Uniswap Swap, Curve AddLiquidity, Aave LiquidationCall, etc.) from simulation logs by passing Solidity event ABI strings in the customEvents request field. API-key requests require Pro, Business, or Enterprise and return HTTP 403 on lower plans; x402 requests are allowed.

Request

Add a customEvents array to your request body. Up to the first 10 entries are attempted; invalid ABI strings are skipped. Each entry is a Solidity event signature string:

{
  "inputToken": "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48",
  "inputAmount": "10000000000",
  "customEvents": [
    "event Swap(address indexed sender, uint256 amount0In, uint256 amount1In, uint256 amount0Out, uint256 amount1Out, address indexed to)"
  ],
  "tx": { "from": "0x...", "to": "0x...", "data": "0x...", "value": "0" }
}

Response

Matching logs are returned in the customEvents response field:

{
  "customEvents": [
    {
      "name": "Swap",
      "signature": "event Swap(address indexed sender, uint256 amount0In, uint256 amount1In, uint256 amount0Out, uint256 amount1Out, address indexed to)",
      "address": "0xB4e16d0168e52d35CaCD2c6185b44281Ec28C9Dc",
      "logIndex": 4,
      "args": {
        "sender": "0x...",
        "amount0In": "0",
        "amount1In": "10000000000",
        "amount0Out": "2918274610283746",
        "amount1Out": "0",
        "to": "0x..."
      }
    }
  ]
}

DecodedCustomEvent

FieldTypeDescription
namestringEvent name (e.g., "Swap", "Sync").
signaturestringMinimal Solidity event ABI string.
addressstringContract address that emitted the event.
logIndexnumberPosition in the event log.
argsRecord<string, string>Decoded event parameters as key-value string pairs.

Trace Transaction

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

Simulate a transaction with a full execution trace — decoded call tree, best-effort storage state changes, token transfers, decoded events, gas profiling, and ABI-decoded function inputs/outputs. Returns a shareable traceId for later retrieval. Results are cached for 7 days.

Trace quotas

All plans include traces: Free (50/month), Starter (500), Pro (2,000), Business (10,000), Enterprise (unlimited). Requests beyond quota return HTTP 429 unless paid-plan overage billing is enabled and under its cap.

ERC-20 inputs

For hypothetical traces where the sender does not already hold or approve a supported ERC-20 input token, provide both inputToken and inputAmount. Foresight injects the sender balance and approval for the traced call. Omit both fields for native-token calls or when replaying existing on-chain state.

Query Parameters

network string Required
The target chain. Case-insensitive. See Supported Chains or GET /v1/networks for the current network registry.

Request Body

tx object Required
The transaction to trace. Contains from, to, data, and value.
tx.from string Required
The sender address.
tx.to string Required
The contract address to call.
tx.data string Required
ABI-encoded calldata, 0x-prefixed. Use "0x" for a plain native-token transfer.
tx.value string Required
Native value in wei as a decimal string of at most 78 digits that fits uint256. Use "0" for calls with no native value.
inputToken string Optional
ERC-20 token address to inject for the sender. Must be provided together with inputAmount.
inputAmount string Optional
Amount of inputToken to inject, in base units as a decimal string of at most 78 digits that fits uint256. Must be provided together with inputToken.
includeStateDiffs boolean Optional
Include decoded storage state diffs in the response. Defaults to true.
blockNumber string | integer Optional
Pin the trace to a positive block number no greater than 9007199254740991, as a decimal string or integer. Omit to use the latest block.
abi (string | object)[] Optional
Custom ABI fragment strings or JSON ABI objects for best-effort decoding (max 50). Invalid fragments are ignored. Useful for unverified contracts. Example: ["function swap(address,uint256)"].

Request Example

curl -X POST "https://foresight.portals.fi/v1/simulate/trace?network=ethereum" \
  -H "Content-Type: application/json" \
  -H "X-Api-Key: YOUR_API_KEY" \
  -d '{
  "tx": {
    "from": "0x1111111111111111111111111111111111111111",
    "to": "0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2",
    "data": "0xd0e30db0",
    "value": "10000000000000000"
  },
  "includeStateDiffs": true
}'
const response = await fetch("https://foresight.portals.fi/v1/simulate/trace?network=ethereum", {
  method: "POST",
  headers: {
    "Content-Type": "application/json",
    "X-Api-Key": "YOUR_API_KEY",
  },
  body: JSON.stringify({
    tx: {
      from: "0x1111111111111111111111111111111111111111",
      to: "0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2",
      data: "0xd0e30db0",
      value: "10000000000000000",
    },
    includeStateDiffs: true,
  }),
});

const trace = await response.json();
console.log(trace.traceId, trace.callTrace);
import requests

response = requests.post(
    "https://foresight.portals.fi/v1/simulate/trace",
    params={"network": "ethereum"},
    headers={
        "Content-Type": "application/json",
        "X-Api-Key": "YOUR_API_KEY",
    },
    json={
        "tx": {
            "from": "0x1111111111111111111111111111111111111111",
            "to": "0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2",
            "data": "0xd0e30db0",
            "value": "10000000000000000",
        },
        "includeStateDiffs": True,
    },
)

trace = response.json()
print(trace["traceId"], trace["callTrace"])

Response Format

Returns the call tree, state changes, token movements, and decoded events when available. Selected fields for the wrapping example are shown below; trace IDs, blocks, and gas costs vary between requests.

FieldTypeDescription
successbooleanWhether the transaction succeeded or reverted.
traceIdstring?UUID for sharing and retrieving the trace later. Returned on newly generated traces from POST /v1/simulate/trace and GET /v1/simulate/trace/tx/{hash}. Cached GET /v1/simulate/trace/{id} responses may omit this field; use the requested id as the trace ID.
gasUsedstringGas consumed by the transaction.
gasCoststringGas cost in wei at current base fee.
blockNumbernumberBlock number used for simulation.
networkstringThe chain the trace was executed on.
fromstringTransaction sender address.
tostringTransaction target address.
txHashstring?Original on-chain transaction hash. Only present on replayed traces (GET /v1/simulate/trace/tx/{hash}).
inputTokenstring?Echoed ERC-20 input token when balance and approval injection was requested.
inputAmountstring?Echoed input amount when balance and approval injection was requested.
abi(string | object)[]?Echoed custom ABI fragments when provided for decoding.
callTraceCallFrameNested call tree with decoded function names, inputs, outputs, and sub-calls. See CallFrame.
stateDiffsStateDiff[]Storage slot changes with best-effort decoded variable names, types, and before/after values. See StateDiff.
assetChangesAssetChange[]Net token balance changes per address (ERC20, ERC721, native).
balanceChangesBalanceChange[]Per-address native-token and ERC20 balance deltas with USD valuation when price data is available.
decodedLogsDecodedLog[]Event logs decoded against resolved ABIs when available.
tokenMetadataobjectToken name, symbol, and decimals keyed by address when metadata can be resolved.
tokenPricesRecord<address, number>USD prices pinned to the simulation block, keyed by lowercase token address.
tokenPricesLiveRecord<address, number>Current USD prices (best-effort, non-blocking). Only included on GET /v1/simulate/trace/:id retrievals — the initial POST returns tokenPrices only. Used for "price now vs. price at simulation" toggles.
bypassedChecksBypassInfo[]Signature-gate bypasses automatically applied to make the transaction simulable. Only present when at least one bypass fired. See Bypassed checks.

CallFrame

FieldTypeDescription
typestringCall type: "CALL", "DELEGATECALL", "STATICCALL", "CREATE", "CREATE2".
fromstringCaller.
tostringCallee.
valuestringNative value forwarded (wei).
gasstringGas supplied to the frame.
gasUsedstringGas consumed by the frame (including sub-calls).
inputstringRaw calldata.
outputstringRaw return data.
errorstring?Low-level error (e.g. "execution reverted").
revertReasonstring?Decoded revert string when available.
contractNamestring?Resolved contract name from verified ABI.
functionNamestring?Decoded function name.
decodedInputobject?Named arg→value map.
decodedOutputobject?Named return→value map.
logsCallFrameLog[]?Decoded events emitted inside this frame.
callsCallFrame[]?Nested sub-calls.

StateDiff

FieldTypeDescription
addressstringAffected account.
labelstring?Resolved contract name.
balance{ before, after }?Native balance delta.
nonce{ before, after }?Nonce delta.
storageStorageDiff[]Storage slot changes. See StorageDiff.

StorageDiff

FieldTypeDescription
slotstring32-byte storage key (hex).
beforestringValue before.
afterstringValue after.
decodedLabelstring?Variable name from storage layout.
decodedTypestring?Solidity type.
decodedValue{ before, after }?Decoded value (e.g. "1000000000000000000" for a uint256).
packedMembersPackedMember[]?Present when the slot holds multiple packed variables.
{
  "success": true,
  "traceId": "e8fb04cb-e222-42f9-876a-13408c67ac8f",
  "gasUsed": "27938",
  "gasCost": "6088502720854",
  "blockNumber": 26199345,
  "network": "ethereum",
  "callTrace": {
    "type": "CALL",
    "from": "0x1111111111111111111111111111111111111111",
    "to": "0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2",
    "functionName": "function deposit() payable",
    "input": "0xd0e30db0"
  },
  "assetChanges": [
    {
      "token": "0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2",
      "amount": "10000000000000000",
      "direction": "received",
      "standard": "erc20"
    },
    {
      "token": "0x0000000000000000000000000000000000000000",
      "amount": "10000000000000000",
      "direction": "sent",
      "standard": "native"
    }
  ]
}

Trace Limits

Each plan includes a monthly trace quota. Starter, Pro, and Business accounts can enable per-trace overage billing up to their configured cap.

PlanTraces / monthOverage
Free50—
Starter500$0.01/trace
Pro2,000$0.005/trace
Business10,000$0.003/trace
EnterpriseUnlimited—

Replay Transaction

GET /v1/simulate/trace/tx/:txHash?network={network}

Replay a historical on-chain transaction at its original block with full execution trace. Returns the same response format as POST /v1/simulate/trace. Counts against your monthly trace quota.

Path Parameters

txHash string Required
The transaction hash to replay (e.g., 0x1cddbcf4...ac8f).

Query Parameters

network string Required
The chain to replay on. Case-insensitive.
curl "https://foresight.portals.fi/v1/simulate/trace/tx/0x1cddbcf4e1adf110e500b0df2b33853a60c722b3a173af25913aa9785f394bc8?network=ethereum" \
  -H "X-Api-Key: YOUR_API_KEY"

Get Trace by ID

GET /v1/simulate/trace/:id

Retrieve a previously generated trace by its UUID. No authentication required — traces are shareable via URL. Does not count against quota (read-only cache lookup).

Traces are cached for 7 days. After expiration, returns 404. The cached response body may not include a traceId field, so clients should retain the requested UUID. View traces in the browser at https://foresight.portals.fi/trace?id=YOUR_TRACE_ID.

Path Parameters

id string Required
The trace UUID returned from a POST /v1/simulate/trace or GET /v1/simulate/trace/tx/:txHash request.
curl "https://foresight.portals.fi/v1/simulate/trace/e8fb04cb-e222-42f9-876a-13408c67ac8f"

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

StatusDescriptionResponse Shape
200Simulation completed (check success field).SimulationResult
400Validation error (invalid params or network).{ success: false, errors: [...] }
401Invalid API key.{ success: false, error: "..." }
402Payment Required — no API key present on an x402-enabled endpoint. The response includes a payment-required header describing how to retry with a USDC-on-Base signature. See x402 Payments.{} (details in header)
403Plan upgrade required (e.g., custom events or batch on Free/Starter).{ success: false, error: "..." }
405Method Not Allowed — wrong HTTP verb for the endpoint.{ success: false, error }
413Payload Too Large — request body exceeds 256 kB.{ success: false, error }
429RPS limit, monthly quota, or overage spending cap exceeded.{ success: false, error: "..." }
500Internal server error.{ success: false, error: "..." }
503Service temporarily unavailable, including shutdown or auth dependency failure.{ success: false, error: "..." }

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"
  ]
}

Examples that contain ... inside calldata are shortened for readability. Real requests must send complete 0x-prefixed hex calldata.

Health Check

GET /v1/health

Returns the server's health status and the networks currently available in this deployment. No authentication required.

Returns 200 when healthy or partially available. Returns 503 when a required service fails or no networks are reachable.

Status Values

ValueHTTP CodeDescription
ok200All services healthy.
degraded200Some networks or services are temporarily unavailable. Check the affected networks before retrying.
error503Critical services are down.
{
  "status": "ok",
  "timestamp": "2026-03-16T08:28:36.181Z",
  "totalNetworks": <configured count>,
  "healthyNetworks": <healthy count>,
  "networks": {
    "healthy": ["ethereum", "base", "arbitrum", "..."]
  }
}

Examples

Examples with ... or placeholder addresses are request templates, not executable quotes. Replace them with complete calldata and addresses for your chosen network. The Quick Start is a complete runnable example.

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://foresight.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://foresight.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 token transfer

Simulate a plain native-token transfer. Use the zero address (0x000...0) or the native sentinel (0xEeee...eeeE) 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}${change.amount} ${name} #${change.tokenId}`;
  }

  // Fungible: show decimal amount
  if (!meta) return `${change.amount} ${change.token}`;
  const digits = change.amount.padStart(meta.decimals + 1, "0");
  const whole = meta.decimals ? digits.slice(0, -meta.decimals) : digits;
  const fraction = meta.decimals ? digits.slice(-meta.decimals).replace(/0+$/, "") : "";
  const amount = whole + (fraction ? "." + fraction : "");
  const sign = change.direction === "sent" ? "-" : "+";
  return `${sign}${amount} ${meta.symbol}`;
}

// Output: "-4217.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://foresight.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://foresight.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://foresight.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": {
    "blockNumber": "25932600",
    "blockHash": "0x21760a99d0e609a55db78ddeeb8b8c487eed54e19f39a550fbae40083390100c",
    "success": true,
    "sender": "0x1111111111111111111111111111111111111111",
    "assetChanges": [
      {
        "token": "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48",
        "amount": "10000000000",
        "direction": "sent",
        "standard": "erc20"
      }
    ],
    "inputTokens": [
      { "token": "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48", "amount": "10000000000" }
    ],
    "outputTokens": [...],
    "events": [...],
    "gasUsed": "745479",
    "gasCost": "2386840000000000",
    "tokenMetadata": {
      "0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48": {
        "name": "USD Coin",
        "symbol": "USDC",
        "decimals": 6
      }
    },
    "errors": []
  }
}

Supported Chains

Simulate transactions on supported EVM networks with the same API and response format. Call GET /v1/networks for the current registry, capabilities, and deployment availability.

Loading networks...

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
Prepare token spending
For supported ERC-20 inputs, Foresight gives the called contract permission to spend the simulated balance. You do not need to send a real approval transaction.
3
Preview execution
The transaction runs against the latest chain state, or the block you selected. Nothing is broadcast and no on-chain balances or permissions change.
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 detected tokens when metadata is available. The response includes what 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 supported input tokens — we provision the simulated balance so you can test flows without pre-funding wallets.

You only need to supply the token address and amount. Some tokens take longer to prepare on the first request. Preparation and execution time vary by token and network.

Broad token coverage

Standard ERC-20 tokens, vault shares, LP tokens, and many non-standard tokens can be prepared automatically. If preparation fails, the response reports the problem. Where batch simulation is available, you can also include an explicit approval step before the call. Simulated approvals do not grant permission to spend real funds.

What you can simulate

Any EVM transaction that interacts with tokens or NFTs:

Rate Limits

Simulation and trace requests made with an API key use your plan's requests-per-second limit. Requests without an API key, including x402 requests, share a per-IP limit.

PlanSimulations / MonthTraces / MonthRequests / Second
Free1,000501
Starter25,0005005
Pro100,0002,00020
Business500,00010,00050
EnterpriseUnlimitedUnlimited100
Batch simulations (/simulate/batch) require Pro plans and above for API-key requests; x402 batch requests are also supported. Traces count separately from simulations.

Rate limit headers

RPS-limited responses include these standard 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 on supported simulation endpoints using the x402 protocol instead of an API key. Payments use USDC on Base. Trace endpoints still require API-key auth, except cached trace retrievals that are public.

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 the endpoint, and settles USDC on-chain for 2xx/3xx responses.
No charge on failure

If the endpoint returns a 4xx or 5xx error, payment is not settled. Reverted simulations returned as HTTP 200 are successful API responses and are billable.

Client libraries

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

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

Try the payment challenge

This request previews the same ETH-to-WETH call as the Quick Start, but without an API key. It returns 402 with payment instructions; this first request does not charge you.

curl -i -X POST "https://foresight.portals.fi/v1/simulate?network=ethereum" \
  -H "Content-Type: application/json" \
  -d '{
    "inputToken": "0x0000000000000000000000000000000000000000",
    "inputAmount": "10000000000000000",
    "tx": {
      "from": "0x1111111111111111111111111111111111111111",
      "to": "0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2",
      "data": "0xd0e30db0",
      "value": "10000000000000000"
    }
  }'

To complete a paid request, configure a client using the official JavaScript or Python buyer guide, then submit this same URL, method, and JSON body. The client handles the payment challenge and retry.

Foresight payments use real USDC on Base mainnet, even when the transaction being simulated is on another network. Configure your payment wallet for Base, not Base Sepolia. Simulation does not spend the example's ETH, but completing the payment retry does spend the advertised USDC fee.

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