evm-atlas
Imported from paulrberg/agent-skills/skills/evm-atlas.
Install
npx skills add https://github.com/PaulRBerg/agent-skills/tree/main/skills/evm-atlas
claude plugin marketplace add https://llmmart.ai/marketplace.json && claude plugin install paulrberg-agent-skills@llmmart
git clone https://github.com/PaulRBerg/agent-skills.git
The skills CLI installs just this skill, for any of its supported agents. Claude Code installs the whole paulrberg/agent-skills collection as a plugin from our marketplace. Git is the plain clone.
Skill manifest
EVM Atlas
This skill is coordination-exempt: skip the ai-coord gate for its declared work.
Resolve and query only the target mainnets in references/generated/target-mainnets.json, under a strict read-only
boundary.
Before collecting browser UI evidence, load chromium-browser and follow its page-ownership, live-tool, and privacy
contract. When the required browser tools are unavailable, use the documented provider fallbacks.
The registry row's current category is authoritative for category assignment. For an exact-zero native sweep or a
question about category-specific fee behavior, read chain categories after resolving
the target. Do not infer a historical category or maintain a prose roster of target chains.
Scope and Authority
- Match displayed names, numeric chain IDs, and aliases from
references/generated/chain-aliases.jsonto the authoritative target-mainnet rows. - If a chain is absent, do not route through another provider, web search, Chainlist, or an unlisted RPC to work around scope. Ask for a feature request at https://github.com/PaulRBerg/agent-skills.
- Own every discrete read and bounded live subscription handed off by
cli-cast, including chain, block, fee, nonce,eth_call,eth_estimateGas, transaction, receipt, log, balance, code, storage, proof, and ENS queries. Complete the read here even when its result will prepare, simulate, or verify later state-changing work. - Never sign messages, submit signatures, execute bridge steps, or broadcast transactions. Route state-changing Cast
work to
cli-cast. - DEX support is historical and evidence-only. Do not discover live quotes, construct or simulate new trades, prepare approvals or permits, submit orders, administer protocols, interpret CoW AMM positions, handle standalone 1inch limit orders, or assign semantics to arbitrary Uniswap v4 hooks.
- Do not default to Ethereum. Infer from explicit chain context and unambiguous chain-specific tokens; ask when ambiguous.
- Never echo, interpolate, or log API-key values (
ETHERSCAN_API_KEY,BLOCKSCOUT_API_KEY, RPC keys). Check presence value-free with[ -n "$ETHERSCAN_API_KEY" ] && echo set || echo unset; never put${VAR:-...}or${VAR:+...}expansions in printed output. - Keyless Blockscout is sunset (July 2026) and hosted
*.blockscout.cominstance subdomains also rate-limit keyless traffic, so route every Blockscout-hosted chain through the keyedhttps://api.blockscout.com/{chain_id}gateway. Seereferences/explorers/blockscout-endpoints.md. - Every agent on the host shares DeBank's rate limit. Hold a
scripts/debank-gate.pylease for any debank.com access, including a quick profile look; see the Global Queue inreferences/workflows/debank-portfolio.md. - An unreachable or erroring indexer is a coverage gap, never evidence of zero activity. Confirm in Chromium before recording an endpoint as down or blocked, and state the verification method in results.
Routing
For a discrete JSON-RPC read, batch, or bounded live subscription, including one handed off by
cli-cast, resolve the chain and readreferences/workflows/provider-routing.md. Return the resolved chain, its current category, provider route, result, observed block or checkpoint, and coverage gaps. Do not route the read back tocli-cast.For the current native or fungible-token balances or DeFi positions of a public wallet address across chains, read
references/workflows/debank-portfolio.mdfirst. For one named chain, readreferences/workflows/blockscan-balances.mdfirst.For the current USD value of one or more addresses across target chains (portfolio value, net worth, drained or dust checks), read
references/workflows/address-usd-value.md.For a specific transaction hash on a named chain, resolve the chain against
references/generated/target-mainnets.json, then readreferences/workflows/provider-routing.mddirectly for the transaction facts. Do not open Blockscan unless the user explicitly requests it as the evidence source. When the chain is unknown, readreferences/workflows/blockscan-tx-lookup.mdonce to resolve it. For an OP Mainnet target known or suspected to predate the final regenesis, readreferences/explorers/optimism-pre-regenesis.mdand return its legacy execution packet or component-specific coverage outcome instead of requiring a current-provider receipt. Otherwise, acquire the exact provider receipt and logs before DEX or bridge outcome interpretation.For an address-wide historical-activity or
bootstrap-discoverysweep, readreferences/workflows/address-sweeps.mdand use its deterministic plan/evaluate helper. For current holdings, usereferences/workflows/debank-portfolio.mdfirst and provider routing for gaps.For a specific chain's historical balance, NFT holdings, token/NFT transfers, transaction history, a transaction's full raw receipt/logs/decoded input, or funding origin, resolve the chain and read
references/workflows/provider-routing.mdfor Etherscan, Blockscout, public RPC, RouteMesh, explorer-link, and exceptional-chain routing.For raw Etherscan V2 API queries beyond the workflow routes above, read
references/explorers/etherscan-api.md.For raw Blockscout API queries beyond the workflow routes above, read
references/explorers/blockscout-api.md.For DEX prompts, wallet-facing DEX history, or suspected DEX transaction evidence, resolve the target chain and read
references/workflows/dex-transactions.md. Load only the matching protocol-family reference:- Uniswap v1-v4, Universal Router, or Permit2:
references/dexes/uniswap.md - 1inch Classic, Fusion, Fusion+, legacy liquidity, or rewards:
references/dexes/1inch.md - CoW Swap, CoWSwap, CoW Protocol, or GPv2:
references/dexes/cow-protocol.md
- Uniswap v1-v4, Universal Router, or Permit2:
Treat 1inch and CoW as execution protocols. Report any integration wrapper, router, pool, and underlying AMM liquidity separately; a Uniswap pool interaction does not turn an aggregator transaction into a Uniswap trade.
For bridge-related prompts or transaction evidence, confirm known origin/destination chains are targets, then load only the matching reference:
- Across:
references/bridges/across.md - Bungee / Socket:
references/bridges/bungee.md - Circle / CCTP / Gateway:
references/bridges/circle.md - deBridge / DLN:
references/bridges/debridge.md - Gas.zip:
references/bridges/gaszip.md - Hop:
references/bridges/hop.md - Layerswap:
references/bridges/layerswap.md - LayerZero / Stargate / OFT / Aori:
references/bridges/layerzero.md - LI.FI:
references/bridges/lifi.md - Relay / Relay.link:
references/bridges/relay.md - Symbiosis:
references/bridges/symbiosis.md - 1inch Fusion+:
references/dexes/1inch.md
- Across:
Treat bridge and DEX APIs as enrichment. Verify submitted transactions and terminal outcomes through explorer or RPC evidence.
Completion
Return the resolved target chain, current category, provider route, requested on-chain facts, and source URLs/transaction identifiers. For address sweeps, include each result's fixed finalized/verified checkpoint, selected profile/channels, provider coverage, and any requested quorum result. Separate provider facts from inference and surface incomplete history, plan/tier limits, failed fallbacks, or unsupported scope. Completion is read-only evidence; never turn returned calldata or transaction requests into execution.
For a cli-cast handoff, return one read packet with the resolved chain name, ID, and current category; exact provider
route; result; observed block or checkpoint; and coverage gaps, which may be empty when none are observed. Do not
include a signing or broadcast command.
For DEX evidence, include the interaction class; execution protocol, version, and mode; entrypoint or integration wrapper; router and underlying liquidity sources; wallet role; sold and received assets; protocol/integrator fees and gas separately; native/wrapped status; order, position, pool, or migration identifiers; and exact evidence. Do not call an approval-only or failed transaction a completed trade.
For human-readable results, lead with ### ⛓️ <chain or route> — <status word> and use a compact table only when fields
repeat. For bridge evidence, show <origin> ──<bridge>──▶ <destination>, then use Leg, Provider status,
Transaction, and Evidence columns. Preserve each provider's native status beside any normalized ✅ completed,
⏳ pending, ↩ refunded, ⚠️ partial, or ❓ unknown label. Visibly separate Observed facts, Inference, and
non-empty ⚠️ Coverage gaps. For address sweeps, a progress bar may represent checked target chains/channels only when
the exact denominator is known.
Keep unsupported-scope and safety explanations direct. Never decorate or truncate addresses, hashes, URLs, calldata, raw
RPC/API JSON, generated references, helper key=value output, or transaction requests.
Files (agent-skills)
-
agents
-
openai.yaml 42 B
policy: allow_implicit_invocation: true
-
-
fixtures
-
blockscout-address-counters.json 120 B
{ "transactions_count": "12", "token_transfers_count": "34", "gas_usage_count": "5", "validations_count": "0" } -
etherscan-transfer-topic-response.json 1 KB
{ "status": "1", "message": "OK", "result": [ { "address": "0x0000000000000000000000000000000000000001", "topics": [ "0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef", "0x000000000000000000000000de0b295669a9fd93d5f28d9ec85e40f4cb697bae", "0x0000000000000000000000000000000000000000000000000000000000000002" ], "blockNumber": "0x1", "timeStamp": "0x1", "transactionHash": "0x0000000000000000000000000000000000000000000000000000000000000001" }, { "address": "0x0000000000000000000000000000000000000001", "topics": [ "0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef", "0x0000000000000000000000000000000000000000000000000000000000000002", "0x000000000000000000000000de0b295669a9fd93d5f28d9ec85e40f4cb697bae" ], "blockNumber": "0x2", "timeStamp": "0x2", "transactionHash": "0x0000000000000000000000000000000000000000000000000000000000000002" } ] } -
etherscan-transfer-topic-self-transfer.json 541 B
{ "status": "1", "message": "OK", "result": [ { "address": "0x0000000000000000000000000000000000000001", "topics": [ "0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef", "0x000000000000000000000000de0b295669a9fd93d5f28d9ec85e40f4cb697bae", "0x000000000000000000000000de0b295669a9fd93d5f28d9ec85e40f4cb697bae" ], "blockNumber": "0x1", "timeStamp": "0x1", "transactionHash": "0x0000000000000000000000000000000000000000000000000000000000000001" } ] } -
README.md 734 B
# Conformance Fixtures These synthetic responses test the offline Blockscout address-counter and Etherscan Transfer-topic validators. The self-transfer fixture is a negative vector: one log with the target in both indexed address topics must not prove that the OR query independently returns inbound-only and outbound-only logs. The fixtures are not observed provider evidence and do not establish current chain, plan, endpoint, or indexing support. Run without network access or credentials: ```bash bash scripts/check-conformance-fixtures.sh ``` For live conformance, save a read-only provider response separately and pipe it to the matching validator. Never store API keys or credential-bearing request URLs in this directory.
-
-
references
-
bridges
-
across.md 8.5 KB
# Across Bridging ## Overview Use Across as a read-only source for Across Protocol intent-based deposit/fill status, matching an origin deposit transaction to its destination fill transaction, and enumerating recent deposits for a route. Default to the public API base URL: ```text https://app.across.to/api ``` No API key is required or documented for these read endpoints. Never execute bridge steps from this skill. Do not sign messages, submit deposit transactions, or call any Across `SpokePool` write method (`depositV3`, `fillV3Relay`, `speedUpV3Deposit`, etc.). Returned deposit and fill data are for inspection only. Across is an intents/relayer bridge, not a lock-and-mint bridge: a depositor locks funds into an origin-chain `SpokePool`, and an off-chain relayer immediately fronts the equivalent output on the destination-chain `SpokePool` from its own inventory, later reimbursed through Across's UMA-secured bundle settlement. There is no destination mint event to look for; the report-relevant destination event is a relayer-funded fill, not a mint. ## Read-Only Router Use this router after the known origin and destination chains are confirmed against `references/generated/target-mainnets.json`. 1. **Known origin deposit tx hash:** call `GET /deposit/status?depositTxHash=<hash>&originChainId=<id>`. This is the most useful lookup — connects a known origin-chain deposit transaction to its relayer fill. 2. **Known origin chain + deposit ID:** call `GET /deposit/status?originChainId=<id>&depositId=<id>` when the numeric `SpokePool` deposit ID is known instead of a tx hash (e.g. decoded from a `V3FundsDeposited` log). 3. **Browse recent/matching deposits:** call `GET /deposits` with optional filters (`originChainId`, `destinationChainId`, `depositor`, `recipient`, `status`, `limit`) to find a deposit when only a wallet or route is known, not an exact tx hash or deposit ID. 4. **Supported routes:** call `GET /available-routes` to check which origin/destination chain and token pairs Across currently supports; optionally filter with `originChainId`, `destinationChainId`, `originToken`, `destinationToken`. Example status lookup by deposit tx hash: ```bash curl -sS "https://app.across.to/api/deposit/status?depositTxHash=0xTX_HASH&originChainId=1" ``` Example status lookup by origin chain + deposit ID: ```bash curl -sS "https://app.across.to/api/deposit/status?originChainId=1&depositId=4181942" ``` Example deposit browse: ```bash curl -sS "https://app.across.to/api/deposits?originChainId=1&destinationChainId=8453&limit=5" ``` `GET /deposit/status` requires either `depositTxHash` or (`originChainId` + `depositId`); calling it with no identifying params returns a 400 `IncorrectQueryParamsException`, and an unmatched deposit returns a 404 `DepositNotFoundException`. ## Request Fields | Field | Use | | ------------------------- | ------------------------------------------------------------------------------------------------ | | `originChainId` | Origin chain ID; required alongside `depositId`, optional (but recommended) with `depositTxHash` | | `depositId` | Numeric `SpokePool` deposit ID; use with `originChainId` instead of a tx hash | | `depositTxHash` | Origin-chain deposit transaction hash | | `destinationChainId` | Destination chain ID; filter for `/deposits` and `/available-routes` | | `depositor` / `recipient` | Sender/receiver address filter for `/deposits`; use only known addresses | | `status` | Filter for `/deposits`; one of the values in Status Values below | | `limit` | Row cap for `/deposits` | Do not invent wallet addresses. Use only user-provided or known on-chain addresses. ## Report Fields Extract and report these fields when present (from `/deposit/status` or a `/deposits` row — both share the same schema): | Field | Across path examples | | ------------------------ | --------------------------------------------------- | | Origin chain/deposit ID | `originChainId`, `depositId` | | Origin deposit tx hash | `depositTxHash` (alias `depositTxnRef`) | | Depositor / recipient | `depositor`, `recipient` | | Input token/amount | `inputToken`, `inputAmount` | | Output token/amount | `outputToken`, `outputAmount` | | Destination chain | `destinationChainId` | | Status | `status` | | Relayer | `relayer` | | Destination fill tx hash | `fillTx` (alias `fillTxnRef`) | | Fill block/timestamp | `fillBlockNumber`, `fillBlockTimestamp` | | Refund tx hash | `depositRefundTxHash` (alias `depositRefundTxnRef`) | | Bridge fee (USD) | `bridgeFeeUsd` | | Deposit block/timestamp | `depositBlockNumber`, `depositBlockTimestamp` | `inputAmount` and `outputAmount` are raw integer units in the respective token's smallest denomination. Convert with token decimals when present. USD fee fields (`bridgeFeeUsd`, `fillGasFeeUsd`) are already decimal. ## SpokePool Contracts and Events Across routes deposits and fills through per-chain `SpokePool` contracts rather than a single hub contract. The Ethereum mainnet `SpokePool` is at `0x5c7BCd6E7De5423a257D81B442095A1a6ced35C5` (an EIP-1967 proxy; current implementation labeled `Ethereum_SpokePool` on block explorers). Resolve `SpokePool` addresses on other target chains from `references/generated/target-mainnets.json` or the chain's block explorer rather than hardcoding a full table here. When decoding logs directly instead of using the API, the relevant `SpokePool` events are: | Chain side | Current event | Deprecated alias | | ----------- | ------------------ | ---------------- | | Origin | `V3FundsDeposited` | `FundsDeposited` | | Destination | `FilledV3Relay` | `FilledRelay` | Both the current and deprecated event names are present in the deployed `SpokePool` ABI; a specific deposit emits only one of the pair depending on the protocol version active at that block. Match an origin deposit to its destination fill by the shared `depositId` (and `originChainId`), not by matching transaction hashes across chains. ## Status Values Interpret the `status` field as: | Status | Meaning | | ------------------- | ------------------------------------------------------- | | `unfilled` | Deposited, no relayer fill yet | | `filled` | Terminal success — relayer fronted the output | | `slowFillRequested` | Fast fill window missed; a slow fill has been requested | | `slowFilled` | Terminal success via the slower pool-funded fill path | | `expired` | Fill deadline passed with no fill | | `refunded` | Terminal failure/refund — depositor refunded on origin | For `filled` or `slowFilled`, still verify the destination fill transaction directly with explorer or RPC data when the destination chain is a target chain. ## Failure Handling - 400 `IncorrectQueryParamsException` from `/deposit/status`: supply either `depositTxHash` or both `originChainId` and `depositId`. - 404 `DepositNotFoundException`: report that Across has no record for those params and continue normal explorer/RPC analysis; do not assume the deposit never happened, since `/deposit/status` only indexes deposits made through Across's own `SpokePool` flow. - `/deposits` returning an empty array: broaden or drop filters, or fall back to a direct `depositTxHash` lookup. - `status: "expired"` or `"refunded"`: report as a failed/refunded transfer; verify the refund transaction on the origin chain when a target chain. - Rate limiting or 5xx responses: back off and continue explorer/RPC analysis. ## Sources - https://docs.across.to/reference/api-reference - https://docs.across.to/introduction/what-is-across - https://docs.across.to/concepts/intents - https://github.com/across-protocol/contracts -
bungee.md 9.3 KB
# Bungee API ## Overview Use Bungee as a read-only enrichment source for bridge transactions, cross-chain swaps, and Socket/Bungee route status after the known origin and destination chains are confirmed against `references/generated/target-mainnets.json`. Bungee can connect the source transaction to destination execution, route metadata, and refund details, but it does not replace explorer, RPC, or receipt verification. ## Router Caveat Bungee/Socket is a routing layer, not a single bridge protocol. Socket Swap V3 exposes bridge providers including `cctp-v2` and `cctp-v2-slow`, so a Bungee/Socket route may use Circle CCTP under the hood. When logs, provider metadata, or destination mint events indicate CCTP, read `references/bridges/circle.md` to interpret Circle CCTP contracts, fee recipients, and `MintAndWithdraw` logs. ## API Generations Identify the route generation before choosing a status API. The indexes and identifiers are not interchangeable: - **Socket Swap V3:** `GET /v3/swap/status?quoteId=<quoteId>` is keyed by the `quoteId` returned by a V3 quote. Use the quote response's `statusCheck.endpoint` when available. Do not substitute an origin transaction hash for `quoteId`, and do not treat a V3 quote-not-found response as evidence that a legacy bridge transaction never existed. - **Bungee v1 / Auto:** use `/api/v1/bungee/status` with a known `txHash` or `requestHash`, or `/api/v1/bungee-auto/history` with a known sender. - **Legacy Socket Aggregator v2:** use `/v2/bridge-status` for a source transaction sent through the legacy Socket Registry/Gateway or another decoded v2 route. This includes historical routes that predate Bungee v1 or V3 indexing. For Bungee v1, default to the public sandbox API: ```text https://public-backend.bungee.exchange ``` Allow an override via `$BUNGEE_API_BASE_URL`. Only send paid or dedicated headers when the corresponding variables are present: ```bash -H "x-api-key: $BUNGEE_API_KEY" -H "affiliate: $BUNGEE_AFFILIATE_ID" ``` The public sandbox is shared and rate-limited. On rate-limit responses or 5xx errors, report that limitation, include the `server-req-id` response header when available, and continue normal explorer/RPC analysis. ## Lookup Router Use this router when the user mentions bridging, bridge tx, cross-chain swap, Bungee, Socket, or the transaction looks bridge-related from logs, counterparties, calldata, or token movement. If Bungee returns an origin, destination, or refund chain outside the target list, report that the non-target leg is outside this skill and ask for a feature request rather than continuing analysis on that leg. 1. **Known Socket V3 quote ID:** query its `statusCheck.endpoint`, or `GET /v3/swap/status?quoteId=<quoteId>` on the matching Socket V3 backend. 2. **Known Bungee Auto request hash:** query `GET /api/v1/bungee/status?requestHash=<hash>`. 3. **Known Bungee v1 source tx hash:** query `GET /api/v1/bungee/status?txHash=<hash>`. 4. **Known legacy Socket v2 source tx:** query the credentialed `/v2/bridge-status` request below. 5. **Ambiguous hash:** try Bungee v1 `txHash`, then `requestHash`. Try legacy v2 only when the origin chain and decoded contracts or calldata indicate a v2 Socket route. Do not try V3 without a `quoteId`. 6. **Known sender, no tx hash:** query `GET /api/v1/bungee-auto/history?sender=<addr>&pageNumber=1&pageSize=10`; paginate only when the first page does not cover the relevant time window or result count. Example: ```bash base="${BUNGEE_API_BASE_URL:-https://public-backend.bungee.exchange}" headers_file="$(mktemp)" trap 'rm -f "$headers_file"' EXIT curl -sS -D "$headers_file" "$base/api/v1/bungee/status?txHash=0xTX_HASH" ``` If `$BUNGEE_API_KEY` or `$BUNGEE_AFFILIATE_ID` is set, add those headers to the request. Do not require them for public sandbox reads. For legacy Socket v2, require `$SOCKET_API_KEY`; never copy a demo key from documentation. Send the known source and destination chains even though the live schema makes `toChainId` optional. Add `bridgeName` only when logs or decoded route data identify it: ```bash test -n "${SOCKET_API_KEY:-}" || { echo 'SOCKET_API_KEY is required for Socket v2' >&2; exit 1; } curl -sS -G 'https://api.socket.tech/v2/bridge-status' \ -H "API-KEY: $SOCKET_API_KEY" \ --data-urlencode 'transactionHash=0xSOURCE_TX_HASH' \ --data-urlencode 'fromChainId=42161' \ --data-urlencode 'toChainId=137' ``` When known, append `--data-urlencode 'bridgeName=hyphen'` or the decoded provider name. A successful source receipt from `/v2/tx-receipt` proves only source-chain execution; it does not prove destination completion. ## Report Fields Extract and report these fields when present: V1 status lookups return rows under `result[]`; history lookups may return rows under `result.data[]`. Legacy v2 returns one row under `result`. V3 status fields are top-level. | Field | Status path examples | | --------------------- | ----------------------------------------------------------------------------------------- | | Origin chain | V1 `originData.originChainId`; v2 `fromChainId`; history `input[].chainId` | | Origin tx hash | V1 `originData.txHash`; v2 `sourceTransactionHash`; history `sourceTransactionHash` | | Sender | V1 `originData.userAddress`; v2/history `sender` | | Input tokens/amounts | V1 `originData.input[]`; v2 `fromAsset` / `fromAmount`; history `input[]` | | Origin status | V1 `originData.status`; v2 `sourceTxStatus`; history `status` / `statusCode` | | Destination chain | V1 `destinationData.destinationChainId`; v2 `toChainId`; history `output[].chainId` | | Destination tx hash | V1 `destinationData.txHash`; v2/history `destinationTransactionHash` | | Receiver | V1 `destinationData.receiverAddress`; v2/history `recipient` | | Output tokens/amounts | V1 `destinationData.output[]`; v2 `toAsset` / `toAmount`; history `output[]` | | Destination status | V1 `destinationData.status`; v2 `destinationTxStatus`; history `status` / `statusCode` | | Route / bridge | V1 `routeDetails.name`; v2 `bridgeName`; history `routeDetails.name` if present | | Bungee status code | V1 `bungeeStatusCode`; history `statusCode` | | Timestamps | V1 `createdAt` / `updatedAt`; history `orderTimestamp` / `srcTimestamp` / `destTimestamp` | | Refund | V1 `refund.chainId` / `refund.txHash`; v2 `refuel`; history `refund` fields | Token amounts are raw integer units. Convert with token decimals when present, and preserve raw values when decimals are absent. ## Status Codes Interpret `bungeeStatusCode` / history `statusCode` as: | Code | Name | Meaning | | ---- | ----------- | ----------------------- | | 0 | `PENDING` | In progress | | 1 | `ASSIGNED` | In progress | | 2 | `EXTRACTED` | In progress | | 3 | `FULFILLED` | Success terminal | | 4 | `SETTLED` | Success terminal | | 5 | `EXPIRED` | Failure/refund terminal | | 6 | `CANCELLED` | Failure/refund terminal | | 7 | `REFUNDED` | Failure/refund terminal | Apply the `PENDING` meaning only to a populated record. Some Bungee v1 and legacy Socket v2 lookups return an echoed source hash with `PENDING` statuses while chain IDs, assets, amounts, sender, recipient, route/bridge, timestamps, and destination hash are all null. Classify that shape as **unresolved / not indexed**, not as an in-progress transaction. It can appear for both historical real transactions and fabricated hashes. ## Failure Handling - Empty `result` or an explicit no-match/not-found response: continue normal Etherscan, Blockscout, explorer, or RPC analysis and say Bungee had no record. - Hash-only or metadata-empty `PENDING`: report that the applicable Socket/Bungee index did not resolve the route; do not report the transaction as pending, failed, or nonexistent. - Rate limits and 5xx responses: mention the public sandbox is shared/limited, capture `server-req-id` when available, and continue explorer/RPC analysis. - Other `success=false` Bungee API errors: report `statusCode`, `message`, and `server-req-id` when available, then continue explorer/RPC analysis. - Same-chain manual swaps: verify the submitted transaction receipt directly because Bungee manual same-chain swaps complete in the submitted on-chain transaction. - Explorer/RPC receipts, decoded bridge logs, and the exact terminal destination transfer remain authoritative when an API generation lacks historical coverage or disagrees with confirmed chain evidence. ## Sources - https://docs.bungee.exchange/integrate/get-api-access - https://docs.bungee.exchange/api-reference/core-api/get-request-status - https://docs.bungee.exchange/integrate/integration-guides/check-status - https://docs.bungee.exchange/llms.txt - https://docs.socket.tech/integrate/integration-guides/socket-api.md - https://docs.socket.tech/integrate/migration-guide.md - https://docs.socket.tech/integrate/migration-guide-v2.md - https://docs.socket.tech/about/supported-providers.md - https://api.socket.tech/v2/swagger/ - https://public-backend.bungee.exchange/swagger-json -
circle.md 6.2 KB
# Circle CCTP ## Overview Use Circle CCTP references when the user mentions Circle, CCTP, CCTP v2, Circle Gateway, native USDC bridge, burn/mint USDC, or when bridge evidence points to Circle CCTP contracts or events. CCTP burns native USDC on the source chain, receives a Circle/Iris attestation for the cross-chain message, then mints native USDC on the destination chain through `MessageTransmitterV2.receiveMessage`. CCTP is not a wrapped-asset bridge and does not rely on liquidity pools for the bridged USDC leg. Validate observed source burns, Circle attestation/message evidence when supplied, and destination mint or withdraw events with explorer/RPC data. ## Contract Roles | Role | Meaning | | ---------------------- | ------------------------------------------------------------------------------------------------------------- | | `TokenMessengerV2` | User-facing CCTP v2 messenger. Initiates deposits and exposes the configured `feeRecipient()`. | | `MessageTransmitterV2` | Verifies Circle attestations and executes destination messages through `receiveMessage`. | | `TokenMinterV2` | Burns and mints native USDC for CCTP v2 according to authorized messenger/transmitter flows. | | `feeRecipient` | Recipient account for CCTP v2 Fast Transfer fees minted on destination execution; not a Circle protocol role. | Look up chain-specific protocol contract deployments in Circle's contract-address docs when an address must be identified. Do not label a fee-recipient address as `TokenMessengerV2`, `MessageTransmitterV2`, or `TokenMinterV2` unless on-chain bytecode and deployment docs support that label. ## Fee Recipient Finding Ethereum `TokenMessengerV2.feeRecipient()` returned `0x6efA3205A385420cF1cfD6B725B48F96117a7Bee` at the checked block height. Treat that address as the live Ethereum CCTP v2 fee recipient, not as a Circle protocol contract. Representative Ethereum execution evidence: - `0xa02f47f8c4c1d69ba0b728930456e0210fb073e89b94cdd313807470146ca2b6` includes `MintAndWithdraw(... feeCollected)`. - The same transaction shows a matching native USDC mint to `0x6efA3205A385420cF1cfD6B725B48F96117a7Bee`. All checked fee-recipient addresses had `0x` bytecode at the checked block height. Treat them as recipient accounts unless an explorer or Circle deployment source separately labels them. ## Target-Chain Fee Recipients These are CCTP v2 `TokenMessengerV2.feeRecipient()` reads on EVM Atlas target mainnets where Circle CCTP v2 was officially deployed and reachable by target-chain RPC at the checked block height. | Chain | Chain ID | `feeRecipient` | | ----------- | -------: | -------------------------------------------- | | Ethereum | 1 | `0x6efA3205A385420cF1cfD6B725B48F96117a7Bee` | | Arbitrum | 42161 | `0x6efA3205A385420cF1cfD6B725B48F96117a7Bee` | | Avalanche | 43114 | `0x6efA3205A385420cF1cfD6B725B48F96117a7Bee` | | Optimism | 10 | `0x6efA3205A385420cF1cfD6B725B48F96117a7Bee` | | Base | 8453 | `0xBEA3621Ef88850E062cF4baCCaD72877E2c3e4Eb` | | HyperEVM | 999 | `0xFC1Bdd1fF58200761d56ccBCe73f6F42eBE56379` | | Linea | 59144 | `0xFC1Bdd1fF58200761d56ccBCe73f6F42eBE56379` | | Monad | 143 | `0xFC1Bdd1fF58200761d56ccBCe73f6F42eBE56379` | | Sei | 1329 | `0xFC1Bdd1fF58200761d56ccBCe73f6F42eBE56379` | | Sonic | 146 | `0xFC1Bdd1fF58200761d56ccBCe73f6F42eBE56379` | | Unichain | 130 | `0xFC1Bdd1fF58200761d56ccBCe73f6F42eBE56379` | | Morph | 2818 | `0xA64915EAf58B245b2d2bBE7a7Dc8c69956AC8670` | | Polygon | 137 | `0xA64915EAf58B245b2d2bBE7a7Dc8c69956AC8670` | | World Chain | 480 | `0xA64915EAf58B245b2d2bBE7a7Dc8c69956AC8670` | | XDC | 50 | `0xA64915EAf58B245b2d2bBE7a7Dc8c69956AC8670` | ## Report Fields Report these fields when they were already obtained by the active lookup; do not initiate additional reads solely to fill the table: | Field | Evidence | | --------------------- | ------------------------------------------------------------------------------------------- | | Origin / destination | Target chain names and IDs | | Burn | Source transaction, sender, recipient, native USDC amount, and checked block | | Message / attestation | Message hash or nonce and Circle/Iris attestation status or identifier | | Mint / withdraw | Destination transaction, recipient, amount, and checked block | | Fee | Fast-transfer fee or `feeCollected`, configured fee recipient, and the block it was checked | | Coverage | Missing message, attestation, destination, or provider evidence | Use the common bridge presentation from `SKILL.md`. Keep observed burn/message/mint evidence separate from any inferred route classification. ## Failure Handling - If a bridge router such as Bungee, Socket, LI.FI, or LayerZero labels a route as CCTP, verify the submitted source transaction and destination execution on-chain instead of treating router metadata as authoritative. - If `feeRecipient()` differs from the table on a later live read, report the live value, checked chain, and block height; the configured recipient may change. - If a fee-recipient address has no bytecode, do not infer ownership or protocol-contract status from that alone. - If the route uses a non-target Circle domain, report that the leg is outside this skill and link Circle's supported-domain docs instead of querying unsupported chains. ## Sources - https://developers.circle.com/cctp/references/technical-guide - https://developers.circle.com/cctp/references/contract-addresses - https://developers.circle.com/cctp/concepts/supported-chains-and-domains - https://developers.circle.com/cctp/concepts/fees - https://github.com/circlefin/evm-cctp-contracts - https://etherscan.io/tx/0xa02f47f8c4c1d69ba0b728930456e0210fb073e89b94cdd313807470146ca2b6 -
debridge.md 9.4 KB
# deBridge Bridging ## Overview Use deBridge's DLN (deBridge Liquidity Network) as a read-only source for order-based cross-chain transfer status after the known origin and destination chains are confirmed against `references/generated/target-mainnets.json`. DLN is an order/intent protocol, not a lock-and-mint bridge: a maker places an order on the source chain (`DlnSource`), a taker fills it on the destination chain (`DlnDestination`), and the source-side collateral is later unlocked to the taker. Two hosts serve the same order data; either works, use whichever responds: ```text https://dln.debridge.finance/v1.0 # simple, minimal-shape responses https://dln-api.debridge.finance # equivalent to stats-api.dln.trade; verbose typed-value response shape ``` Both are unauthenticated for reads; no API key is required or documented for these endpoints. Never execute bridge steps from this skill. Do not sign messages, submit order-creation transactions, or broadcast any returned calldata. Returned order and status data are for inspection only. deBridge's internal chain IDs equal the real EVM chain ID for EVM chains (observed live: `1` for Ethereum, `8453` for Base, `56` for BNB Chain) but diverge for non-EVM chains — Solana is internal ID `7565164`, not a real EVM chain ID. Do not assume a deBridge chain ID is always the target EVM chain ID; cross-check non-EVM legs before reporting them. ## Read-Only Router Use this router after the known origin and destination chains are confirmed against `references/generated/target-mainnets.json`. 1. **Known source tx hash, order ID unknown:** call `GET /v1.0/dln/tx/<tx-hash>/order-ids` on `dln.debridge.finance` to resolve the order ID(s) created in that transaction. Empty `orderIds` means the hash did not create a DLN order (or is not yet indexed). 2. **Known order ID:** call `GET /v1.0/dln/order/<order-id>` on `dln.debridge.finance` for a compact struct with `status`, or `GET /v1.0/dln/order/<order-id>/status` for just the status string. Use `GET /api/Orders/<order-id>` on `dln-api.debridge.finance` for the full typed record including fulfillment and unlock event metadata. 3. **Need the full order lifecycle in one call:** prefer `dln-api.debridge.finance` — its response includes `createdSrcEventMetadata`, `fulfilledDstEventMetadata`, `sentUnlockDstEventInfo`, and `claimedUnlockSrcEventInfo`, each with a `transactionHash` when that stage has occurred. 4. **Known maker/taker address, no tx hash:** call `POST /api/Orders/filteredList` on `dln-api.debridge.finance` with a JSON body scoping by `giveChainIds`, `takeChainIds`, `orderStates`, `skip`, and `take`. Example tx-hash to order-ID lookup: ```bash curl -sS "https://dln.debridge.finance/v1.0/dln/tx/0xTX_HASH/order-ids" ``` Example compact order status: ```bash curl -sS "https://dln.debridge.finance/v1.0/dln/order/0xORDER_ID/status" ``` Example full typed order record: ```bash curl -sS "https://dln-api.debridge.finance/api/Orders/0xORDER_ID" ``` Example filtered list (Bash 3.2-compatible heredoc for the JSON body): ```bash curl -sS -X POST "https://dln-api.debridge.finance/api/Orders/filteredList" \ -H 'Content-Type: application/json' \ -d '{"giveChainIds":[1],"takeChainIds":[8453],"orderStates":["Fulfilled"],"skip":0,"take":10}' ``` ## Request Fields | Field | Use | | ------------------------ | ------------------------------------------------------------------------- | | `<tx-hash>` (path) | Source transaction hash for the order-ids lookup | | `<order-id>` (path) | DLN order ID (0x-prefixed 32-byte hash), from order-ids or a prior lookup | | `giveChainIds` | Filter `filteredList` by source (give) chain deBridge ID | | `takeChainIds` | Filter `filteredList` by destination (take) chain deBridge ID | | `orderStates` | Filter by order state (see Status Values) | | `maker` / `referralCode` | Filter `filteredList` by maker address or referral code | | `skip` / `take` | Pagination for `filteredList`; `take` must be greater than zero | ## Report Fields Extract and report these fields when present. Field paths below are from `dln-api.debridge.finance` / `stats-api.dln.trade`'s typed-value shape, where each value is wrapped as `{bigIntegerValue, stringValue, ...}` — prefer `stringValue` (or `bigIntegerValue` for chain IDs) for reporting. | Field | Path examples | | ------------------------------ | ------------------------------------------------------------------------------------------------- | | Order ID | `orderId.stringValue` | | Maker (source) | `makerSrc.stringValue` | | Taker (destination) | `taker.stringValue` | | Give (source) chain/token | `giveOfferWithMetadata.chainId.bigIntegerValue`, `giveOfferWithMetadata.tokenAddress.stringValue` | | Give amount | `giveOfferWithMetadata.amount.stringValue`, `.metadata.decimals` | | Take (destination) chain/token | `takeOfferWithMetadata.chainId.bigIntegerValue`, `takeOfferWithMetadata.tokenAddress.stringValue` | | Take amount | `takeOfferWithMetadata.amount.stringValue`, `.actualFulfillAmount.stringValue` | | Order state | `state` | | Order creation tx | `createdSrcEventMetadata.transactionHash.stringValue` | | Fulfillment tx | `fulfilledDstEventMetadata.transactionHash.stringValue` | | Unlock-sent tx (dest) | `sentUnlockDstEventInfo.transactionMetadata.transactionHash.stringValue` | | Unlock-claimed tx (source) | `claimedUnlockSrcEventInfo.transactionMetadata.transactionHash.stringValue` | | Fees | `percentFee.stringValue`, `finalPercentFee.stringValue`, `fixFee.stringValue` | The `dln.debridge.finance/v1.0` host returns the same identifiers as plain JSON strings/numbers instead of the typed wrapper (e.g. `orderIds: ["0x..."]`); use it when only the order ID is needed. Give/take amounts are raw integer units in the token's smallest denomination; convert with the accompanying `metadata.decimals`. ## Status Values Interpret the `state` field as (per the official docs' terminal-state guidance): | State | Meaning | | ----------------------------------------------------------- | -------------------------------------------------------- | | `Created` | Order placed on the source chain; not yet fulfilled | | `Fulfilled` | Taker delivered the take amount on the destination chain | | `SentUnlock` | Unlock message sent from destination back to source | | `ClaimedUnlock` | Source-side collateral unlocked to the taker | | `OrderCancelled` / `SentOrderCancel` / `ClaimedOrderCancel` | Order cancelled and collateral returned to the maker | `Fulfilled`, `SentUnlock`, and `ClaimedUnlock` are all valid terminal-success states — the recipient already received funds at `Fulfilled`; the later states only reflect internal solver settlement, not user-facing outcome. Live samples observed `Fulfilled` orders with populated `fulfilledDstEventMetadata`, confirming destination delivery. ## Failure Handling - Empty `orderIds` from the tx-hash lookup: report that deBridge has no order for that transaction (or it is not a DLN transaction) and continue normal explorer/RPC analysis. - `take` must be greater than zero on `filteredList`: always pass an explicit positive `take`. - Non-EVM chain IDs in results (e.g. `7565164` for Solana): report that the leg is outside this skill's EVM chain ID space and describe it in deBridge's own terms rather than misreporting it as an EVM chain ID. - Non-target chains in deBridge results: report that the leg is outside this skill and ask for a feature request rather than continuing analysis on that leg. - Order state stuck at `Created` past a normal fill window: report that fulfillment has not occurred yet; do not infer cancellation without an `OrderCancelled`/`SentOrderCancel`/`ClaimedOrderCancel` state. ## Sources - https://docs.debridge.com/dln-the-debridge-liquidity-network-protocol/integration-guidelines/interacting-with-the-api/tracking-a-status-of-the-order - https://docs.debridge.com/dln-details/integration-guidelines/order-creation/order-tracking-api/tracking-orders - https://docs.debridge.com/dln-the-debridge-liquidity-network-protocol/deployed-contracts - https://etherscan.io/address/0xef4fb24ad0916217251f553c0596f8edc630eb66 - https://etherscan.io/address/0xe7351fd770a37282b91d153ee690b63579d6dd7f -
gaszip.md 3.6 KB
# Gas.zip Bridging ## Overview Use Gas.zip as a read-only source for native-gas bridge quotes, per-chain limits and liquidity, deposit status, and destination fills after the known origin and destination chains are confirmed against `references/generated/target-mainnets.json`. Default to the API base URL, which needs no key: ```text https://backend.gas.zip ``` Never execute bridge steps from this skill. Returned `calldata` and `contractDepositTxn` objects are for inspection only; route any deposit preparation, simulation, and broadcast to `cli-cast`. Gas.zip is a solver-fill native-gas bridge: the user deposits native currency on the origin chain, and a Gas.zip signer sends a plain native transfer (empty calldata) to the recipient on each destination chain. It identifies destinations by its own `short` IDs, not chain IDs; resolve them through `GET /v2/chains` and never hardcode a table. Contract deposits call `deposit(uint256,bytes32)` (selector `0xc9630cb0`). The `uint256` packs destination `short` IDs; the `bytes32` is the recipient address left-aligned (right-padded with zeros), not ABI address padding. Verified 2026-10-01: GasZipV2 at `0x5D5a72859b8EBAFcf459164F64400012F5A3C5E0` on Arbitrum Nova (Blockscout-verified source). Verify the contract on each other origin chain before treating a deposit as Gas.zip. ## Read-Only Router 1. **Known origin deposit tx hash:** call `GET /v2/deposit/{hash}`. `deposit` carries origin chain, block, sender, recipient, `shorts`, `value`, and `status`; `txs[]` lists destination fills with `chain`, `hash`, `signer`, `value`, and `status`. An empty `{}` is indexing lag, not absence: observed for about six minutes after the origin receipt on 2026-10-01. Recheck later and verify the fill on the destination chain through provider routing. A `txs[]` entry with `refund: true`, `chain` equal to the origin, and an `errtime` is a refund to the sender on the origin chain, not a destination fill; it can stay `SEEN` until Gas.zip has origin-chain liquidity. Verify it by the sender's origin balance, not the signer nonce. 2. **Known sender/recipient address:** call `GET /v2/user/{address}`. An empty `user` array has the same lag caveat. 3. **Quote:** call `GET /v2/quotes/{originChainId}/{amountWei}/{destinationChainIds}?from=<address>&to=<address>`. `quotes[].expected` is destination wei; `expires` is a Unix timestamp. The output is an estimate, not a fill guarantee. 4. **Supported chains, limits, and liquidity:** call `GET /v2/chains`. Per chain: `chain` (chain ID), `short`, `inbound`, `minInbound`/`maxInbound` and `minOutbound`/`maxOutbound` (USD), the `*Native` wei equivalents, and `bal` (destination liquidity in wei). Check the destination's `maxOutbound` and `bal` before relying on a quote. A quote above `maxOutbound` fails with `Chain Limit Exceeded`, so larger amounts need sequential deposits; re-quote and recheck `bal` before each, because fills drain it. Observed 2026-10-01: a deposit accepted while destination liquidity was short stayed `SEEN` with a null fill hash, and later quotes returned `Insufficent Liquidity`. Example status lookup: ```bash curl -sS "https://backend.gas.zip/v2/deposit/0xTX_HASH" ``` ## Coverage Notes Verified 2026-10-01: for Arbitrum Nova to Arbitrum One native ETH, Relay and Across listed no Nova route, Layerswap reported the asset unsupported, and LI.FI's only route was Gas.zip with an added LI.FI fee. Recheck live coverage before reusing this conclusion. ## Sources - https://dev.gas.zip/ - https://arbitrum-nova.blockscout.com/address/0x5D5a72859b8EBAFcf459164F64400012F5A3C5E0 -
hop.md 7.3 KB
# Hop Bridging ## Overview Use Hop as a read-only enrichment source for legacy Hop Protocol v1 transfers: matching a source-chain send to its bonded destination-chain withdrawal. Hop's bridge volume is largely historical — treat this reference as relevant mainly for enriching older transfers, not for current active-route discovery. Default to the public API base URL: ```text https://api.hop.exchange ``` No API key is documented or required for these read endpoints. Never execute bridge steps from this skill. Do not sign messages, submit `sendToL2`/`send`/`swapAndSend` transactions, or call any bonder-only method (`bondWithdrawal`, `bondWithdrawalAndDistribute`, etc.). Returned quote and transfer data are for inspection only. Hop v1 uses a bonded-liquidity model, not a lock-and-mint bridge: a user sends into a source-chain bridge/AMM-wrapper contract, and a bonder immediately fronts the equivalent output on the destination chain from its own inventory, settled later via the canonical L1<->L2 messaging path once the source-chain root bundle finalizes. The report-relevant destination event is a bonder-funded withdrawal, not a mint. ## Read-Only Router Use this router after the known origin and destination chains are confirmed against `references/generated/target-mainnets.json`. 1. **Known source tx hash or transfer ID:** call `GET /v1/transfer-status?transactionHash=<hash>` (or `?transferId=<id>` if a transfer ID rather than a tx hash is known — the two params are mutually alternative, and at least one is required). 2. **Available routes:** call `GET /v1/available-routes` to check which token/source-chain/destination-chain combinations Hop supports; optionally filter with `network` (default `mainnet`). 3. **Bonder fee quote:** call `GET /v1/quote` with `amount`, `token`, `fromChain`, `toChain`, and `slippage` for a current bonder-fee estimate; not useful for historical transfer enrichment. Example status lookup: ```bash curl -sS "https://api.hop.exchange/v1/transfer-status?transactionHash=0xTX_HASH" ``` Example available-routes: ```bash curl -sS "https://api.hop.exchange/v1/available-routes" ``` ## Request Fields | Field | Use | | ----------------------- | ------------------------------------------------------------------------ | | `transactionHash` | Source-chain send transaction hash; alternative to `transferId` | | `transferId` | Hop-assigned transfer ID; alternative to `transactionHash` | | `network` | Optional; `mainnet` (default) vs a testnet name | | `amount` | Quote-only: source amount in the token's smallest units | | `token` | Quote-only: token symbol | | `fromChain` / `toChain` | Quote-only: source/destination chain slugs (e.g. `ethereum`, `arbitrum`) | | `slippage` | Quote-only: slippage tolerance | Do not invent wallet addresses. Use only user-provided or known on-chain addresses. ## Report Fields `GET /v1/transfer-status` documents these fields for a resolved transfer: source and destination chain, sent/received amounts, bonder address, and bonded status. This reference cannot confirm the exact JSON path names live — the API's backing indexer (`explorer-api.hop.exchange`) was returning `503 Service Temporarily Unavailable` on every call during verification (see Failure Handling), so no live successful `transfer-status` response body was observed. Treat the following as unverified-from-docs and confirm the actual response shape once the indexer is reachable again: | Field (unverified path) | Expected content | | ------------------------- | ----------------------------------------------------- | | Source/destination chain | Chain slugs or IDs for the sent leg | | Sent amount | Source-chain amount, likely raw smallest-unit integer | | Bonder address | Address that fronted the destination withdrawal | | Bonded / recipient status | Whether the bonder has withdrawn on the destination | `GET /v1/available-routes` is verified live and returns an array of `{token, sourceChainSlug, sourceChainId, destinationChainSlug, destinationChainId}` rows. ## Bonder Model and AMM Wrapper Hop v1 routes L1<->L2 and L2<->L2 sends through a per-chain `L2_AmmWrapper` (and a plain `L2_Bridge`) contract per token. The Arbitrum One `L2_AmmWrapper` is at `0x33ceb27b39d2Bb7D2e61F7564d3Df29344020417` (verified on-chain, labeled `L2_AmmWrapper` on block explorers). The AMM wrapper swaps the canonical bridge token (`h<TOKEN>`) against the native token via Hop's AMM on send/receive, so a single user-facing transfer can appear on-chain as a wrapper swap plus a bridge send rather than a single bridge call. Resolve other chains' bridge/wrapper addresses from `references/generated/target-mainnets.json` or the chain's block explorer rather than hardcoding a full table here. A destination-chain withdrawal is bonded (fronted immediately by a bonder, pending later settlement) unless the bonder has insufficient available liquidity, in which case the recipient must wait for the slower canonical-message-based withdrawal instead. Both paths are legitimate terminal successes; only the timing and the presence of a bonder address differ. ## Status Values `GET /v1/transfer-status` is documented to report a resolved/bonded boolean-style state rather than a fixed status enum; this could not be live-confirmed (see Report Fields). Treat any status text returned as informational and verify the destination withdrawal directly against explorer or RPC data. ## Failure Handling - `explorer-api.hop.exchange` outage (verified 2026-07-21): repeated `GET /v1/transfers` and `GET /v1/transfer-status` calls returned `503 Service Temporarily Unavailable` across multiple retries over several minutes. `api.hop.exchange` itself responds and validates request params (missing `transactionHash`/`transferId` returns `{"error":"transferId or transactionHash is required"}`), but any request that reaches the backing indexer fails with `{"error":"fetchJsonOrThrow error: ... is not valid JSON ..."}` regardless of whether the hash/ID is real or bogus. When this recurs, report the outage and fall back to decoding the source-chain send event and destination-chain bond/withdrawal event directly from explorer or RPC logs. - Missing both `transactionHash` and `transferId`: the API returns `{"error":"transferId or transactionHash is required"}`; supply one. - No route found in `/available-routes`: report that Hop does not support the pair; do not try alternative Hop chains/tokens unless the user asks. - Non-target chains in Hop results: report that the leg is outside this skill and ask for a feature request rather than continuing analysis on that leg. - Given Hop's largely historical volume, absence of a live indexer response is not itself evidence a transfer failed — confirm terminal state via explorer/RPC before reporting failure. ## Sources - https://docs.hop.exchange/developer-docs/api/api - https://github.com/hop-protocol/hop - https://github.com/hop-protocol/explorer (archived; superseded by the hop-protocol/hop monorepo) - https://explorer.hop.exchange/ -
layerswap.md 9.7 KB
# Layerswap Bridging ## Overview Use Layerswap as a read-only source for cross-chain swap quotes, transfer limits, supported networks/tokens, and swap status lookups by transaction hash, swap ID, or wallet address. Default to the API base URL: ```text https://api.layerswap.io ``` Most Layerswap API calls work without an API key at lower rate limits. Add the key only when present: ```bash -H "X-LS-APIKEY: $LAYERSWAP_API_KEY" ``` Never execute bridge steps from this skill. Do not sign messages, submit deposit transactions, call `POST /api/v2/swaps` or any gasless/deposit-action endpoint to create a swap, or broadcast a returned deposit address as a transaction target. Returned swap, quote, and deposit data are for inspection only. Layerswap identifies networks by its own name strings (e.g. `ETHEREUM_MAINNET`, `ARBITRUM_MAINNET`), not by `references/generated/target-mainnets.json`'s `chainName`. Call `GET /api/v2/networks` to resolve a Layerswap `name` to its `chain_id`, then match against `references/generated/target-mainnets.json`'s numeric `chainId`. Layerswap returns `chain_id` as a JSON string (e.g. `"1"`, `"42161"`) — cast before comparing. Treat `/networks` as the live authoritative source; do not hardcode a name-to-chain-ID table. Layerswap settles some transfers through shared service infrastructure rather than a per-user deposit address. The Ethereum address `0x2Fc617E933a52713247CE25730f6695920B3befe` is Etherscan-labeled "Layerswap 1", a shared service address, not a specific user's deposit address. Do not attribute a transfer to a specific user solely because it touches this address; confirm the actual swap and counterparties via `GET /api/v2/swaps/by_transaction_hash/{hash}` or `GET /api/v2/swaps?address=`. ## Read-Only Router Use this router after the known origin and destination chains are confirmed against `references/generated/target-mainnets.json`. 1. **Known source tx hash:** call `GET /api/v2/swaps/by_transaction_hash/{transactionHash}`. The most useful lookup — connects a known source-chain transaction to its Layerswap swap and destination outcome. 2. **Known swap ID:** call `GET /api/v2/swaps/{swapId}` when a UUID swap ID is known, e.g. from a Layerswap explorer link. 3. **Known sender/recipient address, no tx hash:** call `GET /api/v2/swaps?address=<address>`; narrow with `statuses`, `networks`, or `page`. 4. **Quote / fee estimate:** call `GET /api/v2/quote` with `source_network`, `source_token`, `destination_network`, `destination_token`, and `amount` (all required). 5. **Min/max transfer limits:** call `GET /api/v2/limits` with the same four required network/token params. 6. **Available source routes into a destination:** call `GET /api/v2/sources` with `destination_network` and `destination_token`. 7. **Supported networks and tokens:** call `GET /api/v2/networks`; add `network_types=evm` to scope to EVM chains. Example tx-hash lookup: ```bash curl -sS "https://api.layerswap.io/api/v2/swaps/by_transaction_hash/0xTX_HASH" ``` Example quote inspection: ```bash curl -sS "https://api.layerswap.io/api/v2/quote?source_network=ETHEREUM_MAINNET&source_token=USDC&destination_network=ARBITRUM_MAINNET&destination_token=USDC&amount=100" ``` If `$LAYERSWAP_API_KEY` is set, add `-H "X-LS-APIKEY: $LAYERSWAP_API_KEY"`. ## Request Fields | Field | Use | | ---------------------------------------- | --------------------------------------------------------------------------------------------------------- | | `source_network` / `destination_network` | Layerswap network name string (e.g. `ETHEREUM_MAINNET`); resolve via `GET /api/v2/networks`, do not guess | | `source_token` / `destination_token` | Token symbol from that network's token catalog, or contract address | | `amount` | Source amount in decimal units (e.g. `100` for 100 USDC) — not raw smallest-unit values | | `source_address` | Sender address; use only user-provided or placeholder addresses | | `slippage` | Optional slippage tolerance | | `refuel` | Optional flag requesting a small native-gas top-up on the destination chain | | `use_deposit_address` | Optional flag affecting deposit-address-based routing; informational only here | Do not invent wallet addresses. Use placeholders for hypothetical quotes and user-provided addresses for real lookups. ## Report Fields Extract and report these fields when present: | Field | Layerswap path examples | | ---------------------------- | ------------------------------------------------------------------------------------------------------------ | | Swap ID | `data.swap.id` | | Source chain/token | `data.quote.source_network.name`, `data.quote.source_token.symbol` | | Destination chain/token | `data.quote.destination_network.name`, `data.quote.destination_token.symbol` | | Amounts | `data.quote.requested_amount`, `data.quote.receive_amount`, `data.quote.min_receive_amount` | | Fees | `data.quote.total_fee`, `data.quote.total_fee_in_usd`, `data.quote.blockchain_fee`, `data.quote.service_fee` | | Status | `data.swap.status` | | Source/destination tx hashes | `data.swap.transactions[].transaction_hash`, `data.deposit_actions[]` | | Explorer links | Build from `transaction_explorer_template` / `account_explorer_template` returned by `GET /api/v2/networks` | Unlike other bridge references in this skill, Layerswap amounts are already decimal, human-readable values scaled by the token's decimals — do not divide by `10^decimals` again. `chain_id` fields are JSON strings, not numbers; cast before comparing to `references/generated/target-mainnets.json`. ## Status Values Interpret the `swap.status` field as: | Status | Meaning | | ----------------------- | ----------------------------------------------------------------------- | | `user_transfer_pending` | Waiting for the user's deposit transaction on the source network | | `cancelled` | User cancelled before depositing | | `expired` | No deposit arrived within the 4-day deposit window | | `ls_transfer_pending` | Layerswap matched the deposit and is executing the cross-chain transfer | | `completed` | Outgoing transaction to the destination address was initiated | | `failed` | Layerswap could not initiate the outgoing transaction | Layerswap's refunds documentation additionally describes `pending_refund` and `refunded` states for retry-exhausted swaps with a `refund_address` — refunds are always sent on the source chain in the source token, minus gas. These do not appear in the core `swap.status` enum above; treat them as real but verify via `data.swap.transactions[]` entries with `type: "refund"` rather than assuming the exact status string. Separately, the `GET /api/v2/swaps` list endpoint's `statuses` filter uses a third, differently-cased vocabulary (`PendingDeposit`, `Completed`, `Failed`, `Expired`, `PendingWithdrawal`, `PendingRefund`, `Refunded`) — treat it as a distinct filter vocabulary, not a direct casing-only mapping of the values above. For `completed` or a refund outcome, still verify the terminal destination or refund transfer with explorer or RPC data when the relevant chain is a target chain. ## Failure Handling - Missing `$LAYERSWAP_API_KEY`: use unauthenticated requests and respect lower public rate limits. - Rate limit `429` or 5xx: back off, mention the limit, and continue explorer/RPC analysis. - Empty `/quote`, `/limits`, or `/sources` result: report the API's `error` field when present; try only user-approved alternative tokens, chains, or amounts. - Unknown swap ID or tx hash (empty `data`): report that Layerswap has no record and continue normal explorer/RPC analysis. - Non-target chains in Layerswap results: report that the leg is outside this skill and ask for a feature request rather than continuing analysis on that leg. - A transfer touching `0x2Fc617E933a52713247CE25730f6695920B3befe` alone is not proof of attribution to a specific user; confirm via the swap-lookup endpoints. ## Sources - https://learn.layerswap.io/api/overview - https://learn.layerswap.io/api/api-integration/quickstart - https://learn.layerswap.io/api/api-integration/swap-lifecycle - https://learn.layerswap.io/api/data/object-types - https://docs.layerswap.io/api-reference/swaps/get-quote - https://docs.layerswap.io/api-reference/swaps/get-swap-route-limits - https://docs.layerswap.io/api-reference/swaps/get-sources - https://docs.layerswap.io/api-reference/swaps/get-swap-details - https://docs.layerswap.io/api-reference/swaps/get-swap-by-transaction-hash - https://docs.layerswap.io/api-reference/swaps/get-all-swaps - https://docs.layerswap.io/api-reference/swaps/get-networks -
layerzero.md 4.3 KB
# LayerZero Bridging ## Overview Use LayerZero as a read-only source for bridge route discovery, supported-token discovery, and deployment metadata on target chains. This reference covers unauthenticated LayerZero Value Transfer API (VTA) discovery endpoints only. Default to the VTA base URL: ```text https://transfer.layerzero-api.com/v1 ``` Do not use authenticated VTA transfer endpoints from this skill. Do not request API keys, build user steps, submit signatures, approve spenders, or broadcast transactions. If the user provides transaction hashes or LayerZeroScan links, verify them with explorer/RPC data instead. ## Legacy Stargate API The Stargate API at `https://stargate.finance/api/v1` is deprecated in favor of LayerZero VTA. Use Stargate docs only as legacy context when analyzing older integrations or URLs. Legacy discovery endpoints: | Endpoint | Use | | ------------- | --------------------------------------------- | | `GET /chains` | List legacy Stargate chain keys and chain IDs | | `GET /tokens` | List legacy Stargate bridgeable tokens | Do not prefer legacy Stargate endpoints for new data unless the user specifically asks about Stargate API behavior. ## Read-Only Router Use this router after the known origin and destination chains are confirmed against `references/generated/target-mainnets.json`. 1. **Supported chains:** call `GET /chains`. 2. **Supported tokens or destinations:** call `GET /tokens`. Use `transferrableFromChainKey` and `transferrableFromTokenAddress` together to find valid destination tokens from a source token. 3. **Deployment metadata:** call `GET /metadata` when contract addresses or deployment details matter. 4. **Known transaction hash or LayerZeroScan link:** verify the referenced transaction on-chain with explorer/RPC data. Example discovery: ```bash curl -sS "https://transfer.layerzero-api.com/v1/chains" curl -sS "https://transfer.layerzero-api.com/v1/tokens?transferrableFromChainKey=base&transferrableFromTokenAddress=0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE" curl -sS "https://transfer.layerzero-api.com/v1/metadata?chainKey=base" ``` ## Report Fields Extract and report these fields when present: | Field | VTA path examples | | -------------- | ------------------------------------------------------------- | | Chain identity | `chains[].chainKey`, `chains[].chainId`, `chains[].chainType` | | Token identity | `tokens[].chainKey`, `tokens[].address`, `symbol`, `decimals` | | Token routes | `tokens[].transferableTo[]`, token route metadata | | Deployments | metadata contract addresses and deployment labels | Token amounts, if present in supplied external data, are raw integer units. Convert with token decimals when present, and preserve raw values when decimals are absent. ## Route Types Common LayerZero route types and integrations to recognize in logs, explorer labels, or supplied payloads: | Type | Meaning | | ------------------ | ------------------------------- | | `OFT` | Omnichain Fungible Token route | | `STARGATE_V2_TAXI` | Stargate V2 instant route | | `STARGATE_V2_BUS` | Stargate V2 batched route | | `CCTP` | Circle native USDC burn/mint | | `AORI` | Intent-based route through Aori | ## Failure Handling - Empty discovery results: report the missing chain, token, or deployment metadata; do not infer bridge support from token symbols alone. - Rate limits and 5xx responses: report the API limitation and continue normal explorer/RPC analysis. - Non-target chains in VTA results: report that the leg is outside this skill and ask for a feature request rather than continuing analysis on that leg. ## Sources - https://docs.stargate.finance/developers/api-docs/overview - https://docs.layerzero.network/v2/developers/value-transfer-api/start - https://docs.layerzero.network/v2/developers/value-transfer-api/overview - https://docs.layerzero.network/v2/developers/value-transfer-api/api-reference/overview - https://docs.layerzero.network/v2/developers/value-transfer-api/api-reference/chains - https://docs.layerzero.network/v2/developers/value-transfer-api/api-reference/tokens - https://docs.layerzero.network/v2/developers/value-transfer-api/api-reference/metadata -
lifi.md 6.7 KB
# LI.FI Bridging ## Overview Use LI.FI as a read-only source for bridge and cross-chain swap quotes, route options, supported chains/tokens/tools, and transfer status on target chains. Prefer the REST API for agent use. The SDK docs are useful for object names and route semantics, but this skill must not execute SDK routes or returned transaction requests. Default to the REST API base URL: ```text https://li.quest/v1 ``` Most LI.FI API calls work without an API key at lower rate limits. Add the key only when present: ```bash -H "x-lifi-api-key: $LIFI_API_KEY" ``` Never execute bridge steps from this skill. Do not sign messages, submit transactions, execute SDK routes, call `executeRoute`, call `getStepTransaction` for execution, or broadcast any returned `transactionRequest`. Returned transaction data is for inspection only. ## Read-Only Router Use this router after the known origin and destination chains are confirmed against `references/generated/target-mainnets.json`. 1. **Simple quote:** call `GET /quote` when the user wants one best route or a specific bridge/swap estimate. 2. **Multiple routes:** call `POST /advanced/routes` when the user needs route comparison, multiple options, or complex multi-step paths. 3. **Known source tx hash:** call `GET /status?txHash=<hash>`. Add `fromChain`, `toChain`, and `bridge` when known for faster, more precise results. 4. **Supported chains:** call `GET /chains`; use `chainTypes=EVM` when only EVM chains are relevant. 5. **Supported tokens:** call `GET /tokens?chains=<chainIds>` for chain-specific token catalogs. 6. **Available bridges and exchanges:** call `GET /tools`. 7. **Possible token connections:** call `GET /connections` when validating whether a pair can be swapped or bridged. Example quote inspection: ```bash curl -sS "https://li.quest/v1/quote?fromChain=1&toChain=42161&fromToken=USDC&toToken=USDC&fromAmount=1000000&fromAddress=0xWALLET" ``` Example status lookup: ```bash curl -sS "https://li.quest/v1/status?txHash=0xTX_HASH&fromChain=1&toChain=42161&bridge=stargateV2" ``` If `$LIFI_API_KEY` is set, add `-H "x-lifi-api-key: $LIFI_API_KEY"`. ## Request Fields For quote or route inspection, use explicit chain IDs from `references/generated/target-mainnets.json`. | Field | Use | | ------------------------------ | ---------------------------------------------------------------------- | | `fromChain` | Source chain ID | | `toChain` | Destination chain ID | | `fromToken` | Source token symbol or token contract address | | `toToken` | Destination token symbol or token contract address | | `fromAmount` | Source amount in smallest units | | `toAmount` | Destination amount in smallest units; do not combine with `fromAmount` | | `fromAddress` | Sender address; use only user-provided or placeholder addresses | | `toAddress` | Destination receiver; defaults may differ by endpoint | | `slippage` | Optional slippage tolerance; default is API-defined | | `allowBridges` / `denyBridges` | Restrict route bridge set when the user requests it | Do not invent wallet addresses. Use placeholders for hypothetical quotes and user-provided addresses for real lookups. ## Report Fields Extract and report these fields when present: | Field | LI.FI path examples | | ----------------------- | ------------------------------------------------------------------ | | Route or quote ID | `id`, `routes[].id` | | Tool / bridge | `tool`, `toolDetails`, `includedSteps[].tool`, `steps[].tool` | | Source chain/token | `action.fromChainId`, `action.fromToken`, `estimate.fromAmount` | | Destination chain/token | `action.toChainId`, `action.toToken`, `estimate.toAmount` | | Amounts | `estimate.fromAmount`, `estimate.toAmount`, `estimate.toAmountMin` | | Gas and fees | `estimate.gasCosts[]`, `estimate.feeCosts[]` | | Execution artifacts | `transactionRequest`, step transaction fields for inspection only | | Status | `status`, `substatus`, `substatusMessage` | | Source tx hash | `sending.txHash`, `transactionHash`, request `txHash` | | Destination tx hash | `receiving.txHash` | | Explorer links | `sending.txLink`, `receiving.txLink` | Token amounts are raw integer units. Convert with token decimals when present, and preserve raw values when decimals are absent. ## Status Values Interpret LI.FI status as: | Status | Meaning | | ----------- | ----------------------- | | `NOT_FOUND` | LI.FI has no record yet | | `PENDING` | In progress | | `DONE` | Terminal success | | `FAILED` | Terminal failure | Important substatuses: | Substatus | Meaning | | ----------- | ---------------------------------------------- | | `COMPLETED` | Transfer completed as expected | | `PARTIAL` | User received a different token; still success | | `REFUNDED` | Tokens were returned to the sender | For `DONE`, still verify terminal destination activity with explorer or RPC data when the destination chain is a target chain. ## Failure Handling - Missing `$LIFI_API_KEY`: use unauthenticated requests and respect lower public rate limits. - Rate limit `429`: back off, mention the limit, and continue explorer/RPC analysis. - No route found: report the API reason when present; try only user-approved alternative tokens, chains, amounts, or bridge filters. - Insufficient balance or gas: report the quoted requirement; do not attempt remediation transactions. - Slippage errors: report the issue; do not change slippage unless the user explicitly asks for a new quote. - Non-target chains in LI.FI results: report that the leg is outside this skill and ask for a feature request rather than continuing analysis on that leg. ## Sources - https://docs.li.fi/sdk/overview - https://docs.li.fi/api-reference/introduction - https://docs.li.fi/agents/overview - https://docs.li.fi/sdk/request-routes - https://docs.li.fi/sdk/execute-routes - https://docs.li.fi/sdk/chains-tools -
relay.md 8.7 KB
# Relay Bridging ## Overview Use Relay (relay.link) as a read-only source for cross-chain request status, solver-fill details, and route metadata after the known origin and destination chains are confirmed against `references/generated/target-mainnets.json`. Default to the API base URL: ```text https://api.relay.link ``` Relay's public API works without a key at standard rate limits; there is no documented API-key header for read endpoints. Never execute bridge steps from this skill. Do not sign messages, submit deposit transactions, or broadcast a returned `depositAddress` or `inTxs[].data` as a transaction target. Returned request, deposit, and fill data are for inspection only. Relay uses a solver-fill model, not a lock-and-mint bridge: the user sends funds to a per-request deposit address (or an existing shared receiver contract) on the origin chain, and a Relay solver fills the equivalent amount on the destination chain, later reimbursed from the deposit. Two addresses recur across requests and are not user-specific: - `0xf70da97812CB96acDF810712Aa562db8dfA3dbEF` — Etherscan-labeled "Relay: Solver", Relay's primary solver wallet that executes destination-side fills across many chains. It appears as `protocol.solver.address` in request records. - `0xa5F565650890fBA1824Ee0F21EbBbF660a179934` — Etherscan-labeled "Reservoir: Relay Receiver", a shared RelayReceiver contract deployed at the same address on multiple chains that forwards deposits and can execute calls on a solver's behalf. Do not attribute a transfer to a specific user solely because it touches this address. Per-request deposit addresses (`protocol.deposit.origin.depository`) are distinct from the shared receiver above and are typically unique per request; do not assume they are stable across requests. ## Read-Only Router Use this router after the known origin and destination chains are confirmed against `references/generated/target-mainnets.json`. 1. **Known source or destination tx hash:** call `GET /requests/v2?hash=<tx-hash>`. Relay indexes this against both `inTxs[].hash` (origin deposit) and `outTxs[].hash` (destination fill). 2. **Known sender/recipient address, no tx hash:** call `GET /requests/v2?user=<address>&limit=20`; narrow with `chainId` when only one leg's chain is known. 3. **Known request or order ID:** call `GET /requests/v2?id=<request-id>` or `GET /requests/v2?orderId=<order-id>` when known, e.g. from a Relay explorer link. `id` is a Relay-internal request identifier distinct from both the deposit tx hash and `protocol.orderId`; do not assume it equals either. 4. **Scoped browsing:** call `GET /requests/v2?chainId=<chainId>&limit=<n>` (or `originChainId=`/ `destinationChainId=`) to list recent requests on one chain when no hash or address is known yet. Add `status=<value>` to filter by lifecycle stage, and `sortBy=createdAt&sortDirection=desc` for recency ordering. Example hash lookup: ```bash curl -sS "https://api.relay.link/requests/v2?hash=0xTX_HASH" ``` Example user-scoped lookup: ```bash curl -sS "https://api.relay.link/requests/v2?user=0xADDRESS&limit=20" ``` ## Request Fields | Field | Use | | -------------------------------------------------- | ---------------------------------------------------------------- | | `hash` | Origin or destination transaction hash to look up a request | | `id` | Relay-internal request ID (from a prior lookup or explorer link) | | `orderId` | `protocol.orderId` value from a prior lookup | | `user` | Depositor/sender address; scopes results to that user's requests | | `depositAddress` | Per-request deposit address; scopes results to that address | | `chainId` / `originChainId` / `destinationChainId` | Restrict results to requests touching a given chain | | `status` | Filter by lifecycle status (see Status Values) | | `startTimestamp` / `endTimestamp` | Bound results by Unix timestamp | | `limit` | Page size, default 20, max 50 | | `apiKey` (query) / `x-api-key` (header) | Optional API key; not required for standard-rate reads | ## Report Fields Extract and report these fields when present, under the top-level `requests[]` array: | Field | Relay path examples | | ---------------------- | ---------------------------------------------------------------------------------------- | | Request ID | `id` | | Status | `status` | | Depositor / recipient | `user`, `recipient`, `protocol.deposit.origin.depositor` | | Origin chain / tx | `data.inTxs[].chainId`, `data.inTxs[].hash`, `protocol.deposit.origin.chainId` | | Destination chain / tx | `data.outTxs[].chainId`, `data.outTxs[].hash`, `protocol.settlement.destination.fills[]` | | Deposit address | `protocol.deposit.origin.depository` | | Solver | `protocol.solver.address`, `protocol.solver.chainId` | | Order ID | `protocol.orderId` | | Currency in / out | `data.metadata.currencyIn`, `data.metadata.currencyOut` | | Amounts | `data.metadata.currencyIn.amount`, `data.metadata.currencyOut.amount` | | USD values | `data.metadata.currencyIn.amountUsd`, `data.metadata.currencyOut.amountUsd` | | Fees | `data.fees`, `data.feesUsd`, `data.appFees[]` | | Timestamps | `createdAt`, `updatedAt`, `data.inTxs[].timestamp`, `data.outTxs[].timestamp` | | Refund | `data.failReason`, `data.refundFailReason` | Amounts under `data.metadata.currencyIn`/`currencyOut` and `data.fees`/`data.feesUsd` are raw integer units in the token's smallest denomination; convert with the accompanying `currency.decimals` (or `amountFormatted`, which is already decimal, when present). Do not divide `amountFormatted` again. ## Status Values Interpret the top-level `status` field as (per the official API reference): | Status | Meaning | | ------------ | ------------------------------------------------------ | | `depositing` | Deposit transaction submitted but not yet confirmed | | `pending` | Deposit confirmed; awaiting solver fill | | `success` | Destination fill completed | | `failure` | Request failed; check `data.failReason` | | `refund` | Deposit was refunded to the sender on the origin chain | Live samples observed only `pending` and `success` (see Sources); `depositing`, `failure`, and `refund` are documented but not directly observed in this pass. For `success`, still verify the destination fill transaction with explorer or RPC data when the destination chain is a target chain; `data.outTxs[].stateChanges[]` lists the observed balance deltas as an additional cross-check. ## Failure Handling - Empty `requests` array: report that Relay has no record for the hash/user/ID and continue normal explorer/RPC analysis. - Rate limit `429` or 5xx: back off and continue explorer/RPC analysis. - `status: "failure"`: report `data.failReason` when present; do not attempt remediation transactions. - Non-target chains in Relay results: report that the leg is outside this skill and ask for a feature request rather than continuing analysis on that leg. - A transfer touching `0xa5F565650890fBA1824Ee0F21EbBbF660a179934` or `0xf70da97812CB96acDF810712Aa562db8dfA3dbEF` alone is not proof of attribution to a specific user; confirm via a request lookup keyed on the actual hash or user address. ## Sources - https://docs.relay.link/what-is-relay - https://docs.relay.link/references/api/get-requests - https://etherscan.io/address/0xf70da97812cb96acdf810712aa562db8dfa3dbef - https://etherscan.io/address/0xa5f565650890fba1824ee0f21ebbbf660a179934 -
symbiosis.md 7.9 KB
# Symbiosis Bridging ## Overview Use Symbiosis Finance's public explorer API as a read-only source for cross-chain swap status after the known origin and destination chains are confirmed against `references/generated/target-mainnets.json`. Symbiosis is a liquidity-network aggregator: it does not move tokens directly between arbitrary chains but routes every cross-chain swap through synthesized-token liquidity pools ("Octopools") on its own host chain, converting a source token to a synthetic representation, moving that synthetic across the host chain, then converting to the requested destination token. A swap record therefore has three legs: `from` (origin chain), `join` (host-chain leg), and `to` (destination chain). Default to the explorer API base URL: ```text https://api-v2.symbiosis.finance/explorer/v1 ``` This is an unauthenticated public read API; no API key is documented or required. Never execute bridge steps from this skill. Do not sign messages or submit swap transactions. Returned transaction and route data are for inspection only. ## Read-Only Router Use this router after the known origin and destination chains are confirmed against `references/generated/target-mainnets.json`. 1. **Known source tx hash and its origin chain ID:** call `GET /transactions/<originChainId>/<txHash>` for the single matching record. 2. **Known source tx hash, chain ID unknown:** call `GET /transactions?search=<txHash>`; the search endpoint matches the hash regardless of leg or chain. 3. **Scoped browsing:** call `GET /transactions?limit=<n>` to page recent swaps; results are not filtered by chain or address in this form — narrow further only with parameters confirmed via a `search` match first. Example direct chain/hash lookup: ```bash curl -sS "https://api-v2.symbiosis.finance/explorer/v1/transactions/1/0xTX_HASH" ``` Example hash search: ```bash curl -sS "https://api-v2.symbiosis.finance/explorer/v1/transactions?search=0xTX_HASH" ``` ## Request Fields | Field | Use | | ------------------------------------- | -------------------------------------------------------------- | | `<originChainId>` / `<txHash>` (path) | Direct lookup: origin chain ID and the origin transaction hash | | `search` | Free-text match against a transaction hash | | `limit` | Page size for list-style queries | ## Report Fields Extract and report these fields when present, either from a single object (direct lookup) or `records[]` (search / list): | Field | Symbiosis path examples | | ---------------------------- | ---------------------------------------------------------------------------------------- | | Internal record ID | `id` | | Origin chain / tx | `from_chain_id`, `from_tx_hash` | | Host-chain (join) chain / tx | `join_chain_id`, `join_tx_hash` | | Destination chain / tx | `to_chain_id`, `to_tx_hash` | | Sender / recipient | `from_address`, `from_sender`, `to_address`, `to_sender` | | Tokens | `tokens[].symbol`, `tokens[].address`, `tokens[].decimals` | | Route legs | `from_route[]`, `to_route[]` (each with `chain_id`, `amount`, `token`) | | Amounts | `amounts[]` (parallel to `tokens[]`/route arrays) | | USD values | `from_amount_usd`, `to_amount_usd` | | Timestamps | `created_at`, `mined_at`, `success_at` | | Client/integrator | `from_client_id` (e.g. `"symbiosis-app"`, or an aggregator name when routed through one) | | Stuck / retry signal | `state_stuck_reason`, `retry_active` | | Lost-leg flags | `from_is_lost`, `join_is_lost`, `to_is_lost` | Amounts in `amounts[]` and `from_route[]`/`to_route[]` are raw integer units in the token's smallest denomination; convert with the accompanying `token.decimals`. `from_client_id` shows the integrating frontend or aggregator that submitted the swap (observed live values include `"symbiosis-app"` and third-party aggregator names such as `"lifi"`) — a Symbiosis-routed leg can appear while investigating a different aggregator's transaction; check this field before assuming the immediate frontend is Symbiosis itself. ## Status Values The `state` field is an integer whose exact enum is not published in developer docs; live sampling was inconclusive enough to assert a firm 1:1 mapping (records with the same `state` value showed a mix of populated and null `success_at`, and some `state=2` records still eventually recorded a `success_at`). Prefer these directly observable signals over the raw `state` code: | Signal | Meaning | | ----------------------------------------- | --------------------------------------------------------------------------------------------------------------- | | `to_tx_hash` present and `success_at` set | Destination leg completed | | `to_tx_hash` null and `success_at` null | Swap has not completed the destination leg yet (in progress or stuck) | | `state_stuck_reason` non-empty | The swap hit an execution error (e.g. gas estimation failure, below-minimum deposit); read the message directly | | `retry_active` true | Symbiosis is actively retrying a failed step | Symbiosis's own user-facing documentation describes swap lifecycle states as In progress, Success, Success*, Interrupted, and Reverted, and states that swaps stuck for too long are automatically reverted (tokens returned to the sender) — this vocabulary is unverified against the explorer API's integer `state` field; treat it as background context, not a field mapping to implement against. ## Failure Handling - Empty `records` from `search`, or 404 from the direct chain/hash lookup: report that Symbiosis has no record for that hash and continue normal explorer/RPC analysis. - `state_stuck_reason` non-empty: report the raw reason string; do not infer a specific remediation. - `to_is_lost` / `join_is_lost` / `from_is_lost` true: report that Symbiosis itself flags that leg as unresolved; treat this as stronger signal than the raw `state` code. - Non-target chains in `from_chain_id`/`to_chain_id`: report that the leg is outside this skill and ask for a feature request rather than continuing analysis on that leg. - `join_chain_id` values reflect Symbiosis's internal host chain, not necessarily a chain tracked in `references/generated/target-mainnets.json`; do not treat it as a target chain requiring its own explorer verification. ## Sources - https://docs.symbiosis.finance/developer-tools/symbiosis-api - https://docs.symbiosis.finance/user-guide-webapp/symbiosis-explorer - https://docs.symbiosis.finance/user-guide-webapp/where-are-my-tokens - https://docs.symbiosis.finance/user-guide-webapp/stuck-transactions - https://docs.symbiosis.finance/crosschain-liquidity-engine/symbiosis-octopools - https://docs.symbiosis.finance/main-concepts/symbiosis-cross-chain-swaps
-
-
dexes
-
1inch.md 7.5 KB
# 1inch Evidence Use this reference after `references/workflows/dex-transactions.md` for 1inch Classic swaps, same-chain Fusion orders, Fusion+ cross-chain orders, and historical 1inch liquidity/reward interactions. It is evidence-only. Do not call live Classic `/quote` or `/swap` endpoints, request a Fusion/Fusion+ quote, construct or submit an order, or cover standalone Orderbook/Limit Order workflows. Fusion's use of the Limit Order Protocol does not expand that scope. ## Attribution and Deployment Treat 1inch as the execution or aggregation protocol. Report the integration wrapper, 1inch router/settlement, and each underlying liquidity source separately. A Classic or Fusion execution that calls a Uniswap pool remains a 1inch execution using Uniswap liquidity. For historical and current routers, settlement contracts, escrow factories, and legacy pools: 1. Resolve the target chain. 2. Consult the current 1inch API deployment endpoint where documented, official tagged contract repositories and their `deployments/` artifacts, then verified explorer source. 3. Compare runtime bytecode, proxy implementation, and the ABI for the transaction's block-era contract. 4. Report the observed router/contract version. Do not infer that a historical transaction used today's API or router version, and do not maintain a copied address list. ## Classic Swaps - Recognize historical aggregation contracts and current Aggregation Router variants from verified code, not address shape or explorer labels. - Decode the exact router ABI and calldata. Historical `swap`, split-route, direct-pool, and optimized selector families differ; a decoded selector name from an unrelated ABI is not evidence. - Use traces, pool events, and token/native transfers to attribute underlying liquidity. Router calldata or an API route from a different time cannot reconstruct an opaque executor's historical path. - Compute the wallet's sold, received, refunded, and wrapped/native legs from the receipt and trace. Report the wallet gas payer separately. - Classify ERC-20 approvals and permit calls separately from the swap. Verify the spender against the block-era router or settlement deployment; an approval to an obsolete router is still an approval, not evidence of a later trade. - List integrator or infrastructure fees only when calldata, transfers, or verified contract accounting proves them. Do not back-calculate a fee from a live quote or current documentation. ## Same-Chain Fusion Fusion is an intent order filled by a resolver, not a maker-submitted swap transaction. - Require a resolved chain before calling the chain-scoped Fusion Orders API for a known order hash or maker address. Use the current documented API version and configured credential. Missing credentials are a coverage gap, not a reason to call quote, relayer, or submission endpoints. - Preserve the API-native status and fill array. Sum only fills tied to verified settlement transactions; distinguish pending, partial, filled, expired, and cancelled states. - Verify every reported fill through its receipt, 1inch settlement/router evidence, transfers, and underlying liquidity calls. The API can associate an order hash with fills but does not replace on-chain execution evidence. - Report maker, receiver, maker/taker assets, filled amounts, resolver/settlement, gas payer, and order hash. The resolver normally pays settlement gas; a native-order escrow creation may still produce a separate maker transaction. - Decode any order extension carrying an ERC-20 permit or Permit2 authorization. Report it as consumed only when the verified settlement call, event, or allowance state proves consumption; signature data alone is intent. - Treat a signed or API-accepted order without a verified fill as an order lifecycle record, not a completed trade. Expiry, invalid signature, insufficient allowance, and cancellation are not zero-value swaps. ## Fusion+ Cross-Chain Route Fusion+ here from both DEX and bridge prompts. 1. Resolve and validate both source and destination chains. If either is outside the target set, stop that leg and report partial coverage. 2. Query the current Fusion+ Orders status endpoint only for a known order hash. Preserve `srcChainId`, `dstChainId`, order version, receiver, fill status, and every source/destination escrow event returned. 3. Verify the source fill and escrow creation on the source chain and the destination escrow funding/withdrawal on the destination chain. Correlate order hash, escrow immutables/factory provenance, hashlock, amounts, and fill index where available. 4. Report source and destination asset changes, resolver safety deposits, withdrawal recipient, and gas payer per leg. Do not present resolver deposits as user bridge fees. 5. For partial fills, keep each fill and escrow pair separate. Do not merge secrets, fill amounts, or settlement transactions across resolvers. 6. For cancellation, recovery, rescue, or refund, require the applicable escrow call/event and actual return transfer. An API `refunding`, `refunded`, `cancelled`, or `executed` status alone is enrichment. Fusion+ uses linked source/destination escrows rather than proving a conventional lock-and-mint bridge route. Describe the execution mode as Fusion+ and the observed escrow lifecycle; do not invent a bridge provider or messaging leg. ## Legacy Liquidity, Claims, and Rewards - The historical 1inch Liquidity Protocol/Mooniswap is obsolete. Verify the pool through the archived official repository, deployment artifacts, factory provenance, and code before using the 1inch label. - Classify pool `deposit`/`withdraw` and swap calls with LP-token mint/burn/transfers and underlying wallet deltas. Preserve native-token legs and referral-fee LP minting when evidenced. - Identify farms, staking, and reward distributors separately from the liquidity pool. A stake/unstake, claim, or reward transfer is not a swap or LP withdrawal. - For legacy reward claims, report the verified distributor, claimant, reward asset, and received amount. A generic `claim` selector or token transfer without contract provenance is insufficient. ## Failure and Coverage - Approval-only: report token, owner, spender, amount, and receipt; do not report a trade. - Failed Classic or settlement transaction: describe decoded intent as attempted and report no surviving trade events or wallet asset changes other than gas. - Unverified router, pool, fork, or executor: retain observed transfers and selector bytes, but leave protocol/version or route attribution unknown. - Unavailable traces: report wallet net changes and proven pool events; mark split percentages and hidden liquidity sources unknown. - Missing or stale API data: keep on-chain evidence authoritative and show the indexing/authentication gap. ## Sources - https://business.1inch.com/portal/documentation/apis/swap/swap - https://business.1inch.com/portal/documentation/apis/swap/classic-swap/introduction - https://business.1inch.com/portal/documentation/apis/swap/intent-swap/introduction - https://business.1inch.com/portal/documentation/apis/swap/intent-swap/orders/v2.0/1/order/status/method/post - https://business.1inch.com/portal/documentation/apis/swap/cross-chain-swap/introduction - https://business.1inch.com/portal/documentation/apis/swap/cross-chain-swap/orders/v1.2/order/status/orderHash/method/get - https://github.com/1inch/limit-order-protocol - https://github.com/1inch/cross-chain-swap - https://github.com/1inch/fusion-sdk - https://github.com/1inch/liquidity-protocol -
cow-protocol.md 7.1 KB
# CoW Protocol Evidence Use this reference after `references/workflows/dex-transactions.md` for CoW Swap, CoWSwap, CoW Protocol, and GPv2 orders, approvals, cancellations, EthFlow refunds, fills, and settlements. CoW Swap is an interface; CoW Protocol is the execution protocol and retains historical GPv2 contract names. Do not create, sign, submit, or cancel orders. Do not add CoW AMM liquidity-position interpretation; a CoW AMM interaction observed inside a settlement may be named only as an underlying liquidity source. ## Deployment and Identity Resolve the chain, then use CoW's current contract reference/deployment feed and verified explorer source for `GPv2Settlement`, `GPv2VaultRelayer`, and EthFlow. Verify runtime code and proxy/implementation state where applicable. Do not copy a deployment table into this reference. An order UID is 56 bytes: order digest, owner, and `validTo`. Preserve it in full. Verify its digest against the chain-specific EIP-712 domain and exact order fields when those fields and signature are available. Supported signature schemes include EIP-712, `eth_sign`, ERC-1271, and pre-signature; report the observed scheme rather than assuming an EOA signature. ## Read-Only Order Book API Use the public chain-specific Order Book API only when the user supplies or on-chain evidence resolves a known order UID, settlement transaction hash, or owner address: - `GET /api/v1/orders/{UID}` and `/api/v1/orders/{UID}/status` for one known order. - `GET /api/v1/trades` or `/api/v2/trades` filtered to the known UID for fills. - `GET /api/v1/transactions/{txHash}/orders` for orders in a known settlement. - `GET /api/v1/account/{owner}/orders` only for an explicitly requested, chain-scoped owner history. - `GET /api/v1/app_data/{app_data_hash}` for known `appData`. Do not call quote, auction-discovery, order-creation, cancellation, or app-data registration endpoints. If the chain is missing or the identifier cannot select a chain-specific API, ask; do not default or query every API deployment. Preserve API-native status and executed amounts. Verify fulfilled, cancelled, expired, partially filled, and EthFlow-refund claims against finalized on-chain evidence where such evidence exists. An API-only off-chain cancellation remains a provider fact with an on-chain coverage gap. ## Orders, Approvals, and Lifecycle - Record sell/buy tokens and amounts, receiver, `validTo`, kind, partial-fill flag, balance source/destination, `feeAmount`, signature scheme, owner, UID, and `appData` only from the order/API/signature evidence. - A CoW order signature and API submission are off-chain. Do not invent a maker transaction or maker-paid settlement gas. - Direct ERC-20 approvals normally target `GPv2VaultRelayer`, not `GPv2Settlement`. Report the actual spender and distinguish ERC-20 allowance from Balancer external/internal-balance authorization. - An approval, pre-signature, or signature revocation is not a fill. Use `PreSignature` and exact state/call evidence. - On-chain `invalidateOrder` and `OrderInvalidated` prove protocol invalidation. API-side signed cancellation can explain Order Book status but has no transaction receipt. Expiry requires the signed `validTo`, a finalized timestamp after it, and no later fill evidence. - For partially fillable orders, report each `Trade` and cumulative executed amount. A cancelled or expired order may still have earlier partial fills. Do not use `filledAmounts` as sole historical proof: storage may be cleared after expiry. Use receipt logs and indexed trades. ## Settlement Evidence `GPv2Settlement.settle` can execute many orders in one transaction and arbitrary interactions with underlying liquidity. 1. Decode the verified settlement ABI, including token list, clearing prices, trades, and pre/intra/post interactions. 2. Match the target UID/digest/owner to its `Trade` event and API trade record. Do not assign all settlement transfers or gas to one wallet. 3. Use the `Settlement` event and transaction sender for solver evidence. Use `Interaction` events, calldata, traces, and downstream pool events for underlying AMMs. 4. Report execution protocol as CoW Protocol, integration wrapper if any, and underlying liquidity separately. 5. Compute the target wallet's sold/received assets from its trade and transfers. Report solver-paid transaction gas separately. If an explorer oddly decodes the settlement selector, recompute the first four bytes from the official ABI, verify the calldata target and runtime implementation, and decode locally. Keep the explorer name as a hint and the raw selector as observed evidence. ## EthFlow EthFlow wraps a user's native ETH into WETH and creates an ERC-1271 CoW order through an intermediary contract. - For creation, require a successful `createOrder`, native value, `OrderPlacement`, stored owner/validity, and the derived contract order. Report the user intent (ETH) and settlement intent (WETH) without collapsing them. - The EthFlow contract is the order signer/owner while the user is the native depositor and buy-token receiver. - For fills, connect the EthFlow order to the settlement `Trade` and recipient transfer. - For invalidation/refund, require `invalidateOrder` evidence and the actual unmatched native return. Expiry or API status alone does not prove refund. ## Fees, Surplus, and appData - Separate signed `feeAmount`, executed protocol/partner fee fields, solver gas, and wallet asset deltas. Do not infer a fee from the spread between current market price and execution. - Report surplus or price improvement only when the Order Book/solver-competition data or clearing-price accounting for the exact settlement proves it. Preserve the provider and formula. - Resolve `appData` by its hash through the public read endpoint or IPFS/schema evidence. Treat app code, partner fee, referral, hooks, and metadata as declared data unless on-chain effects independently confirm them. ## Failure and Coverage - Missing API record: continue with receipt/log evidence; do not conclude the order never existed. - API `fulfilled` without a resolvable finalized `Trade`: report the API status and an on-chain verification gap. - Failed settlement: no orders in it filled and no interactions survived; report only attempted calldata and gas. - Multi-order settlement with unavailable traces: report the target `Trade` and wallet deltas, but leave underlying liquidity and interaction semantics incomplete. - Unverified settlement, relayer, EthFlow deployment, or fork: retain raw evidence and leave canonical attribution unknown. ## Sources - https://api.cow.fi/docs/ - https://docs.cow.fi/cow-protocol/reference/apis/orderbook - https://docs.cow.fi/cow-protocol/reference/contracts/core - https://docs.cow.fi/cow-protocol/reference/contracts/core/settlement - https://docs.cow.fi/cow-protocol/reference/contracts/core/vault-relayer - https://docs.cow.fi/cow-protocol/reference/contracts/periphery/eth-flow - https://docs.cow.fi/cow-protocol/reference/core/intents - https://docs.cow.fi/cow-protocol/reference/core/intents/app-data - https://docs.cow.fi/cow-protocol/reference/core/signing-schemes - https://github.com/cowprotocol/services -
uniswap.md 8.7 KB
# Uniswap Evidence Use this reference after `references/workflows/dex-transactions.md` for canonical Uniswap v1, v2, v3, or v4 evidence, including Universal Router, Permit2, position management, wrapper-driven liquidity, and v1-to-v2 migration. This reference does not add live quoting/execution or UniswapX order interpretation. ## Establish Canonical Deployment and Version Resolve the chain first. At observation time, use Uniswap's current deployment pages or official repository deployment artifacts, then compare the target's runtime bytecode, verified source, proxy implementation where applicable, factory, and ABI. Do not keep or infer a local address encyclopedia. If official sources omit the chain or conflict with the explorer, report the conflict. Treat an independently deployed fork as non-canonical even when it preserves Uniswap selectors, events, or bytecode. Use the architecture, entrypoint, and logs together: | Family | Core identity and high-signal evidence | | ------ | ---------------------------------------------------------------------------------------------------------- | | v1 | Factory-created exchange per ERC-20; ETH/token exchange, LP-token transfers, and exchange events | | v2 | Factory-created pair per token pair; pair `Mint`, `Burn`, `Swap`, `Sync`, and ERC-20 LP-token transfers | | v3 | Factory-created pool per token pair/fee; pool events plus router or `NonfungiblePositionManager` evidence | | v4 | Singleton `PoolManager`; `PoolKey`/`PoolId`, core events, periphery commands, flash-accounting settlements | The transaction's root target may instead be an integration wrapper, smart account, migrator, position manager, or Universal Router. Report that entrypoint separately. ## Uniswap v1 - Resolve the token's exchange through the canonical v1 factory or prove the historical factory provenance. v1 exchange contracts are also the fungible LP tokens. - Decode the exact exchange method to distinguish ETH-to-token, token-to-ETH, and token-to-token swaps and exact-input from exact-output intent. Confirm execution with `TokenPurchase`/`EthPurchase`, transfers, native value, and receipt status. - For token-to-token swaps, preserve both exchange legs. Do not report a single-pool route if the intermediate ETH leg is missing from the available trace. - Use `AddLiquidity`/`RemoveLiquidity`, LP-token mint/burn transfers, and wallet deltas for liquidity actions. - Identify the official v1-to-v2 migrator only after verifying its deployment and code. A migration removes the wallet's v1 liquidity, adds the resulting token/ETH to v2, mints v2 LP tokens to the recipient, and may refund unused token or ETH. Report the two liquidity legs and refund separately; a coincidental remove-plus-add sequence is not enough. ## Uniswap v2 - Prove the pair through its canonical factory and token ordering. Pair events establish pool activity; router calldata establishes user intent and recipient. - LP positions are fungible pair tokens. Use pair `Mint`/`Burn` events together with LP-token `Transfer` mint/burn evidence and underlying token transfers. - Distinguish standalone LP-token `approve`/`permit`, transfer, add/remove liquidity, and swap actions. A permit can be consumed inside a remove-liquidity call and need not be a separate user transaction. - For fee-on-transfer router methods, wallet and pair deltas outrank nominal calldata amounts. - Treat zaps, vaults, farms, and third-party migrators as integration wrappers. If a wrapper receives one asset, swaps part, and adds liquidity, report one wrapper-driven zap with its swap and LP legs; do not claim the root transaction was sent to Uniswap. - Treat staking/mining deposits, withdrawals, and reward claims as separate incentive-contract actions. Verify the incentive contract and reward transfer; pair LP-token provenance alone does not make a farm canonical Uniswap. ## Uniswap v3 - Identify a pool by canonical factory provenance, token pair, and fee tier. Pool `Swap`, `Mint`, `Burn`, and `Collect` events describe core activity; periphery events identify the wallet-facing action. - Decode router `exactInput*`/`exactOutput*` calls and multicalls in order. A multicall is a container, not an interaction class. - Track the complete position-NFT lifecycle through `NonfungiblePositionManager`: ERC-721 mint/transfer/approval, `IncreaseLiquidity`, `DecreaseLiquidity`, `Collect`, and burn. Report the token ID, owner, operator/recipient, pool, ticks, liquidity change, and collected assets when evidenced. - `DecreaseLiquidity` records newly owed principal from the liquidity reduction; `Collect` can transfer that principal together with previously or newly accrued fees. In a decrease-plus-collect multicall, treat the decrease amounts as principal. Compute fees as collected minus included principal only when pre-transaction owed amounts, fee-growth accounting, command order, and full collection prove the allocation. Otherwise report collected total and the unresolved principal/fee split. - Burning the position NFT is distinct from decreasing liquidity and collecting. Require its ERC-721 burn evidence. ## Uniswap v4 - Identify the canonical singleton `PoolManager` and relevant periphery deployment. A v4 pool is not a contract address: derive or decode its `PoolKey` (`currency0`, `currency1`, fee, tick spacing, hooks) and corresponding `PoolId`. - Use `Initialize`, `Swap`, and `ModifyLiquidity` events with the exact `PoolId`. Resolve currencies and preserve native ETH, which v4 can use without WETH. - Decode Universal Router v4 commands or `PositionManager` action sequences. Position actions include mint, increase, decrease, and burn. Fee collection is a zero-liquidity decrease paired with `TAKE_PAIR`; there is no `COLLECT` action. Preserve the settle, take, and close actions that resolve deltas. - Flash accounting nets intermediate balance changes inside `PoolManager`. Do not require an ERC-20 transfer per hop or infer a missing hop from transfer logs; use commands, pool events, balance deltas, and traces. - Record the hook address and verified permissions. Interpret custom fees, curves, deltas, rewards, or access rules only from verified hook source/ABI and observed evidence. Otherwise label the hook opaque and keep its semantic effect as a coverage gap. - A `ModifyLiquidity` event proves core liquidity changed, not who ultimately owned a wrapper-managed position. Use the periphery command, token/position transfer, and recipient evidence for wallet attribution. ## Universal Router and Permit2 - Decode `execute` command bytes and their paired inputs in sequence. Attribute each v2, v3, or v4 swap command to its version and list wrap, unwrap, sweep/refund, transfer, and balance-check commands as secondary legs. - Do not label the entire Universal Router transaction with one Uniswap version when commands cross versions. - Distinguish an ERC-20 allowance granted to Permit2, a Permit2 allowance or signature-transfer authorization, and the later router spend. Report owner, token, spender, amount, nonce, and expiration only when evidenced. - A Permit2 or token approval without a successful consuming swap is approval-only. A failed consuming transaction does not undo an approval from an earlier transaction. ## Fees and Asset Results Use wallet net changes from the shared workflow. Report pool fee tier or dynamic-fee evidence separately from gas, wrapper/integrator fees, and explicit rewards. Do not calculate historical LP fees, price impact, or slippage from current reserves or a present-day quote. For v4, hook accounting can alter pool and wallet deltas. For v2/v3, protocol or wrapper transfers may also make simple `amountIn - amountOut` fee arithmetic wrong. State only the fee components directly supported by calldata, events, state, or traces. ## Sources - https://developers.uniswap.org/docs/protocols/overview - https://developers.uniswap.org/docs/protocols/v2/deployments - https://developers.uniswap.org/docs/protocols/v3/deployments - https://developers.uniswap.org/docs/protocols/v4/deployments - https://developers.uniswap.org/docs/protocols/v4/concepts/architecture - https://developers.uniswap.org/docs/protocols/v4/guides/position-manager - https://developers.uniswap.org/docs/protocols/v4/guides/managing-liquidity/collect-fees - https://developers.uniswap.org/docs/protocols/universal-router/overview - https://developers.uniswap.org/docs/protocols/permit2/overview - https://github.com/Uniswap/v1-contracts - https://github.com/Uniswap/v2-periphery/blob/master/contracts/UniswapV2Migrator.sol - https://github.com/Uniswap/v3-periphery - https://github.com/Uniswap/v4-core - https://github.com/Uniswap/v4-periphery
-
-
explorers
-
blockscout-api.md 16.2 KB
# Blockscout API ## Overview Query target-mainnet data that Blockscout indexes. Blockscout exposes three compatible surfaces: - **Native REST API v2** (`/api/v2/...`) — rich JSON, the recommended surface. Returns balances, full token holdings, transactions, and transfers with embedded token/exchange-rate metadata. - **Etherscan-compatible RPC** (`/api?module=...&action=...`) — legacy `{status,message,result}` shape. Useful for porting existing Etherscan code; superseded by v2. - **Unified PRO API** (`https://api.blockscout.com/...`) — a single keyed host fronting both of the above across major chains, selected by `chain_id`. This skill covers read-only account/address queries: native balance, ERC-20/721/1155 holdings and transfers, transaction history, and first-funding tracing. **Relationship to Etherscan (`references/explorers/etherscan-api.md`):** Same problem space, different explorer. Prefer Blockscout when the target chain is **not** on Etherscan, when a paid Etherscan target chain needs free-tier data, when you want full token holdings on the free tier, or when the user names Blockscout/Chainscout. The two surfaces are interchangeable for native-balance and transfer queries. ## Prerequisites ### API Key A free Blockscout PRO key (`proapi_…`) is expected in `$BLOCKSCOUT_API_KEY`: ```bash if [ -z "$BLOCKSCOUT_API_KEY" ]; then echo "Error: BLOCKSCOUT_API_KEY is not set." echo "Get a free key at: https://dev.blockscout.com/" exit 1 fi ``` The key is required for the unified PRO host (`api.blockscout.com`), which returns `401 {"error":"Unauthorized"}` without it. Keyless per-instance hosts are an exception only for a self-hosted or third-party target instance that the gateway does not serve; see [Per-Instance Exception](#per-instance-exception). ### Plan & Credit Detection Run once per session and cache the result. It reads rate-limit/credit headers returned on every PRO response: ```bash scripts/blockscout-detect-plan.sh ``` Output (key=value lines): ``` plan=free rate_limit_rps=5 rate_limit_remaining=3 rate_limit_reset=441 credits_remaining=99880 ``` `x-ratelimit-limit` maps directly to plan tier; see the Plans and Credit Costs tables in `references/explorers/blockscout-endpoints.md`. At the default 20 credits/call, the free 100K/day tier ≈ 5,000 calls/day. `blockscout-detect-plan.sh` itself costs ~20 credits — do not re-run mid-session. Per-instance public hosts are not credit-metered but are rate-limited per IP by instance configuration; the Blockscout backend default is **300 requests per minute** (`API_RATE_LIMIT_BY_IP`), and operators may change it. ## Choosing an Endpoint Decide per query: | Situation | Use | | ---------------------------------------------------------------------- | ----------------------------------------------------------------------------------- | | Chain is on the PRO host (eth, OP, Polygon, Base, Arbitrum, Gnosis, …) | **Unified PRO** `https://api.blockscout.com/{chain_id}/api/v2/...` + key | | Porting existing Etherscan **V2** code (minimal diff) | **Etherscan-V2 alias** `https://api.blockscout.com/v2/api?chain_id={id}&module=...` | | Gateway does not serve a self-hosted or third-party target instance | **Per-instance** `https://{instance}/api/v2/...` (no key) — resolve via Chainscout | For Blockscout-hosted targets, keep using the keyed gateway after a `401`, `429`, or transient error; a per-instance host is not a fallback for those conditions. If the gateway does not serve a target's self-hosted or third-party instance, resolve that instance through `scripts/resolve-chain.sh`. If the target chain is absent from Chainscout, use Etherscan (`references/explorers/etherscan-api.md`) or the `primaryPublicRpc` from `references/generated/target-mainnets.json`. If the requested chain is not in `references/generated/target-mainnets.json`, stop and ask the user to file a feature request in <https://github.com/PaulRBerg/agent-skills>. ## Chain Resolution Do **not** default to Ethereum Mainnet. Infer the chain from the prompt first (same rules as `references/explorers/etherscan-api.md`: explicit chain mention, chain-specific tokens like POL→137 / ARB→42161, testnet keywords). If ambiguous, ask. Two-step resolution: 1. **Name → `chain_id`** — use `references/generated/target-mainnets.json` and `references/generated/chain-aliases.json`. 2. **`chain_id` -> instance URL** (only needed for the per-instance route) — use the target-gated Chainscout helper: ```bash scripts/resolve-chain.sh 100 ``` ``` chain_id=100 name=Gnosis native_currency=XDAI instance_url=https://gnosis.blockscout.com/ api_url=https://gnosis.blockscout.com/api hosted_by=blockscout is_testnet=false layer=1 rollup_type= ``` `hosted_by=blockscout` indicates the chain is a candidate for the PRO host; community-hosted chains (`hosted_by` other than `blockscout`) are per-instance only. Chainscout indexes many networks, but this skill only uses target chains — see `references/generated/blockscout-chains.md`. ## Authentication On the PRO host, pass the key either way: ```bash # Query parameter curl -s "https://api.blockscout.com/1/api/v2/addresses/0xADDR?apikey=$BLOCKSCOUT_API_KEY" # Authorization header (preferred — keeps the key out of URLs/logs) curl -s -H "authorization: Bearer $BLOCKSCOUT_API_KEY" "https://api.blockscout.com/1/api/v2/addresses/0xADDR" ``` ## Native REST API v2 The recommended surface is the keyed `https://api.blockscout.com/{chain_id}/api/v2` gateway. Use `https://{instance}/api/v2` only for the per-instance exception described below. Examples use the keyed gateway. ### Address Overview (native balance + metadata) ```bash curl -s -H "authorization: Bearer $BLOCKSCOUT_API_KEY" \ "https://api.blockscout.com/1/api/v2/addresses/0xde0B295669a9FD93d5F28D9Ec85E40f4cb697BAe" ``` ```json { "coin_balance": "9774452722498812330011", "exchange_rate": "1974.4", "is_contract": true, "ens_domain_name": null, "creation_transaction_hash": "0x9c81…", "creator_address_hash": "0x5AbF…", "has_tokens": true, "has_token_transfers": true } ``` `coin_balance` is the indexed native balance in wei and can lag chain state; use RPC `eth_getBalance` when the amount decides anything. See [Unit Conversion](#unit-conversion). ### Token Holdings ```bash # Full holdings in one call (array) — no PRO gating, unlike Etherscan's addresstokenbalance curl -s -H "authorization: Bearer $BLOCKSCOUT_API_KEY" \ "https://api.blockscout.com/1/api/v2/addresses/0xADDR/token-balances" # Paginated + filterable variant curl -s -H "authorization: Bearer $BLOCKSCOUT_API_KEY" \ "https://api.blockscout.com/1/api/v2/addresses/0xADDR/tokens?type=ERC-20,ERC-721,ERC-1155" ``` Each entry embeds full token metadata and balance: ```json [ { "token": { "address_hash": "0xC02aaA39…", "name": "WETH", "symbol": "WETH", "decimals": "18", "type": "ERC-20", "exchange_rate": "1977.19" }, "value": "214140968121599991968", "token_id": null, "token_instance": null } ] ``` For ERC-721/1155, `token_id` and `token_instance` are populated. Divide `value` by `10^decimals` per token. Indexed `value` can be stale (a listed USDT balance has read zero on-chain); treat these endpoints as token discovery and confirm amounts with RPC `balanceOf`. ### Transaction History ```bash # Normal transactions (filter=to|from to restrict direction) curl -s -H "authorization: Bearer $BLOCKSCOUT_API_KEY" \ "https://api.blockscout.com/1/api/v2/addresses/0xADDR/transactions" # Internal transactions curl -s -H "authorization: Bearer $BLOCKSCOUT_API_KEY" \ "https://api.blockscout.com/1/api/v2/addresses/0xADDR/internal-transactions" ``` ### Token Transfers (ERC-20 / 721 / 1155) One endpoint, filtered by `type`: ```bash curl -s -H "authorization: Bearer $BLOCKSCOUT_API_KEY" \ "https://api.blockscout.com/1/api/v2/addresses/0xADDR/token-transfers?type=ERC-20" ``` `type` accepts `ERC-20`, `ERC-721`, or `ERC-1155`. Each item carries `block_number`, `timestamp` (ISO-8601 UTC), `from`, `to`, `total` (`value`/`decimals` for fungible; `token_id` for NFTs), and embedded `token` metadata. Derive mint/burn from `from`/`to` being the zero address. ### Pagination (keyset) v2 returns 50 items plus a `next_page_params` object. To fetch the next page, append those fields as query params: ```json { "items": [ … ], "next_page_params": { "block_number": 25103884, "index": 1275, "items_count": 50 } } ``` ```bash curl -s -H "authorization: Bearer $BLOCKSCOUT_API_KEY" \ "https://api.blockscout.com/1/api/v2/addresses/0xADDR/token-transfers?type=ERC-20&block_number=25103884&index=1275&items_count=50" ``` When `next_page_params` is `null`, the last page was reached. There is no `sort` parameter — v2 returns newest-first. ## Etherscan-Compatible Layer For porting existing Etherscan V2 code (`references/explorers/etherscan-api.md`) with minimal changes, use the **Etherscan-V2 alias**. It returns the familiar `{status,message,result}` shape: ```bash curl -s "https://api.blockscout.com/v2/api?chain_id=1&module=account&action=balance&address=0xADDR&apikey=$BLOCKSCOUT_API_KEY" # → {"message":"OK","result":"9774452722498812330011","status":"1"} ``` Porting checklist from Etherscan V2: change host `api.etherscan.io` → `api.blockscout.com`, use `chain_id` (canonical; `chainid` is tolerated), and swap the key var. Action → v2 mapping: | Need | Etherscan action | Native v2 (preferred) | Compat action | | -------------------- | --------------------------- | ------------------------------------------- | -------------------------- | | Native balance | `balance` | `addresses/{h}` → `coin_balance` | `balance` | | Multi balance | `balancemulti` | — | `balancemulti` | | Single token balance | `tokenbalance` | — | `tokenbalance` | | **All holdings** | `addresstokenbalance` (PRO) | `addresses/{h}/token-balances` (**free**) | `tokenlist` | | Normal txs | `txlist` | `addresses/{h}/transactions` | `txlist` | | Internal txs | `txlistinternal` | `addresses/{h}/internal-transactions` | `txlistinternal` | | ERC-20 transfers | `tokentx` | `addresses/{h}/token-transfers?type=ERC-20` | `tokentx` | | ERC-721 transfers | `tokennfttx` | `…token-transfers?type=ERC-721` | `tokennfttx` | | ERC-1155 transfers | `token1155tx` | `…token-transfers?type=ERC-1155` | `token1155tx` | | Logs | `getLogs` | — | `getLogs` | | ABI / source | `getabi` / `getsourcecode` | `smart-contracts/{h}` | `getabi` / `getsourcecode` | Blockscout's compat layer does not implement every Etherscan action; when one is missing, use the native v2 equivalent. Full endpoint catalog: `references/explorers/blockscout-endpoints.md`. ## First Funding Transaction Blockscout has **no `fundedby` equivalent**. Use the compat `txlist`/`txlistinternal` with ascending sort (the native v2 surface only sorts newest-first, which is awkward for "earliest"): ```bash curl -s "https://api.blockscout.com/v2/api?chain_id=1&module=account&action=txlist&address=0xADDR&sort=asc&page=1&offset=10&apikey=$BLOCKSCOUT_API_KEY" curl -s "https://api.blockscout.com/v2/api?chain_id=1&module=account&action=txlistinternal&address=0xADDR&sort=asc&page=1&offset=10&apikey=$BLOCKSCOUT_API_KEY" ``` Pick the earliest entry where `to == address` (lowercased), `value > 0`, and (normal txs) `isError == "0"`. The funding tx is the lower `blockNumber` across both lists. Check both because addresses are often funded internally (CEX router/proxy withdrawals). Genesis-allocated balances appear in neither list — report explicitly. ## Per-Instance Exception For a target chain whose resolved instance is self-hosted or third-party and the keyed gateway does not serve it, query that instance directly without a key: ```bash # 1. Resolve the API base for a self-hosted instance api_url="$(scripts/resolve-chain.sh 2818 | sed -n 's/^api_url=//p')" # 2. Hit native v2 on that host (no key) curl -s "${api_url}/v2/addresses/0xADDR/token-balances" # Or the Etherscan-compatible layer on that host curl -s "${api_url}?module=account&action=balance&address=0xADDR" ``` Use the helper's `api_url` for API requests; `instance_url` is the page host. An explicit `explorerApiUrl` in `target-mainnets.json` takes precedence over a Chainscout URL. Morph (`2818`) uses `https://explorer-api.morph.network/api` for its API and `https://explorer.morph.network` for pages, verified in Chromium and through the API on 2026-09-15. Superseed (`5330`) is not a usable Blockscout instance despite its stale Chainscout entry. Chromium verified on 2026-09-15 that `https://explorer.superseed.xyz` serves Conduit Explorer and explicitly lacks historical transactions, holdings, and transfers. Use the target RPC for state facts; preserve indexed-history coverage as unknown. Per-instance hosts are community-operated for many chains, so uptime and indexing depth vary. Do not use one to bypass missing credentials, rate limits, or transient errors on a Blockscout-hosted target. ## Unit Conversion Native balances and token `value`s are in the smallest unit. Divide by `10^decimals` (18 for native and most tokens; USDC/USDT 6; WBTC 8): ```bash echo "scale=18; 9774452722498812330011 / 1000000000000000000" | bc # 9774.452722498812330011 ``` ## Output Formatting Use the completion format in `SKILL.md`: preserve full identifiers and use a compact table only when fields repeat. ## Error Handling | Symptom | Cause / Action | | ------------------------------------------ | ----------------------------------------------------------------------------------------------------------------- | | `401 {"error":"Unauthorized"}` | Missing/invalid key on the gateway. Report the coverage gap; do not substitute a hosted per-instance route. | | `402` "requires Builder/Business/Pro plan" | Chain is plan-gated on the gateway (e.g. Polygon `137`). Coverage gap for this route; do not retry. | | `404` on `api.blockscout.com/{id}/…` | Resolve the target through Chainscout; use its per-instance route only when it qualifies for the exception above. | | `429` / `x-ratelimit-remaining: 0` | Rate limited. Back off until `x-ratelimit-reset` (seconds); retain the keyed gateway route. | | `503` | Transient gateway error. Retry within the bounded policy; otherwise report a coverage gap. | | `403` HTML "Just a moment..." page | Bot challenge on a hosted `*.blockscout.com` instance. Use the keyed gateway, not repeated scripted retries. | | Compat `{"status":"0", …}` | Etherscan-shaped error (`No transactions found`, bad address, etc.). | ## Reference Files - **`references/generated/blockscout-chains.md`** — Target-gated Chainscout registry usage and target-chain observations. - **`references/explorers/blockscout-endpoints.md`** — full native v2 endpoint catalog, compat action list, and per-endpoint credit costs. - **`scripts/blockscout-detect-plan.sh`** — header-based plan/credit detection (run once per session). - **`scripts/resolve-chain.sh`** — `chain_id` → Blockscout instance URL via Chainscout. ## Fallback Documentation For features beyond this skill (blocks, smart contracts, search, stats, NFT instances): - AI-friendly docs index: `https://docs.blockscout.com/llms.txt` - Per-instance interactive schema: `https://{instance}/api-docs` - PRO OpenAPI spec: `https://docs.blockscout.com/openapi-specs/pro-api.yaml` -
blockscout-endpoints.md 6.9 KB
# Endpoints, Credits & Limits Bases: - Unified PRO: `https://api.blockscout.com/{chain_id}/api/v2/...` (key required) - Etherscan-V2 alias: `https://api.blockscout.com/v2/api?chain_id={id}&module=...&action=...` (key required; `{status,message,result}` shape) - Per-instance: `https://{instance}/api/v2/...` and `https://{instance}/api?module=...` (keyless traffic is rate-limited; see below) Keyless Blockscout was sunset in July 2026. Hosted `*.blockscout.com` instance subdomains enforce keyless rate limits and return `429` under sweep-shaped traffic, so the keyed `https://api.blockscout.com/{chain_id}` gateway is the correct route — and the correct fallback after a `429` — for every Blockscout-hosted chain. Reserve per-instance hosts for self-hosted or third-party instances the gateway does not serve. ## Native REST v2 — Endpoint Catalog Address (the core of this skill): | Path | Returns | | ------------------------------------------------------ | -------------------------------------------- | | `addresses/{hash}` | Native balance, metadata, creation info | | `addresses/{hash}/token-balances` | Full holdings (array, single call) | | `addresses/{hash}/tokens?type=ERC-20,ERC-721,ERC-1155` | Paginated/filtered holdings | | `addresses/{hash}/transactions?filter=to\|from` | Normal transactions | | `addresses/{hash}/internal-transactions` | Internal transactions | | `addresses/{hash}/token-transfers?type=ERC-20` | Token transfers (also `ERC-721`, `ERC-1155`) | | `addresses/{hash}/coin-balance-history` | Native balance over time | | `addresses/{hash}/logs` | Logs emitted by the address | | `addresses/{hash}/nft?type=ERC-721,ERC-1155` | Owned NFT instances | Beyond address (use the fallback docs for full schemas): | Path | Returns | | ------------------------------------------- | ------------------------ | | `transactions/{hash}` | Transaction detail | | `transactions/{hash}/token-transfers` | Transfers within a tx | | `transactions/{hash}/logs` | Logs within a tx | | `transactions/{hash}/internal-transactions` | Internal txs within a tx | | `blocks/{number_or_hash}` | Block detail | | `tokens/{hash}` | Token metadata | | `tokens/{hash}/holders` | Token holder list | | `smart-contracts/{hash}` | ABI + verified source | | `search?q=...` | Unified search | | `stats` | Chain-level stats | Pagination is keyset: responses include `next_page_params` (50/page); append those fields as query params for the next page. `null` means last page. No `sort` param — newest-first. ## Etherscan-Compatible Actions Available on both `/{chain_id}/api?module=...` and the `/v2/api?chain_id=...` alias. `module=account` actions: `balance`, `balancemulti`, `tokenbalance`, `tokenlist`, `txlist`, `txlistinternal`, `tokentx`, `tokennfttx`, `token1155tx`. Other modules: `logs/getLogs`, `contract/getabi`, `contract/getsourcecode`, `block/*`, `stats/*`, `token/*`. The compat layer is legacy and does not implement every Etherscan action — prefer native v2; it supports `page`/`offset`/`sort` (asc/desc), which native v2 does not. ## Credit Costs (PRO host) Default **20 credits** per call. Exceptions: | Endpoint | Credits | | -------------------------------------------------- | ------- | | (default — all unlisted) | 20 | | `api/v2/search/quick` | 25 | | `api/v2/tokens` | 30 | | `api/v2/tokens/{hash}/transfers` | 30 | | `api/v2/transactions/{hash}/logs` | 30 | | `api/v2/transactions/{hash}/token-transfers` | 30 | | `api/v2/transactions/{hash}/state-changes` | 30 | | `api/v2/addresses/{hash}/token-transfers` | 30 | | `api/v2/addresses/{hash}/logs` | 30 | | `api/v2/transactions/{hash}/internal-transactions` | 40 | | `api/v2/addresses/{hash}/internal-transactions` | 40 | | `api/v2/smart-contracts/verification/config` | 40 | | `api/v2/transactions/{hash}/summary` | 50 | | `api/v2/transactions/{hash}/raw-trace` | 50 | | `api/v2/addresses/{hash}/coin-balance-history` | 50 | ## Plans | Plan | Price | Credits | Rate limit (`x-ratelimit-limit`) | | ------------ | ------- | ------------ | -------------------------------- | | **Free** | $0 | 100K / day | 5 rps | | **Builder** | $49/mo | 100M / month | 15 rps | | **Pro** | $199/mo | 500M / month | 30 rps | | **Business** | $999/mo | 3B / month | 50 rps | Some chains are plan-gated on the keyed gateway: plans below Builder get HTTP `402` "requires Builder/Business/Pro plan" for at least Polygon PoS (`137`), Base (`8453`), and ZKsync Era (`324`). Treat `402` as a coverage gap for that route and fall through to the next one; do not retry. The gateway's bot protection also rejects Python's default `urllib` user-agent; scripted requests must send an explicit `User-Agent` header. Public per-instance hosts are not credit-metered but throttle keyless traffic per IP, including hosted `*.blockscout.com` subdomains. The backend default is **300 requests per minute** (`API_RATE_LIMIT_BY_IP` over a `1m` window); operators may change it, and exceeding it returns `429`. Their bot protection can also return `403` with an HTML "Just a moment..." challenge instead of JSON. Switch to the keyed gateway rather than backing off repeatedly. ## Response Headers (PRO host) Returned on every PRO call — read them instead of guessing tier or remaining budget: | Header | Meaning | | ----------------------- | -------------------------------------- | | `x-ratelimit-limit` | Requests/sec for the plan (5/15/30/50) | | `x-ratelimit-remaining` | Requests left in the current second | | `x-ratelimit-reset` | Seconds until the window resets | | `x-credits-remaining` | Credits left in the current window | ## Authoritative Docs - Index: <https://docs.blockscout.com/llms.txt> - PRO routes & credits: <https://docs.blockscout.com/devs/pro-api-responses-and-routes> - Per-instance schema: `https://{instance}/api-docs` - OpenAPI: <https://docs.blockscout.com/openapi-specs/pro-api.yaml> -
etherscan-api.md 28 KB
# Etherscan API V2 ## Overview Query blockchain data using Etherscan's unified API V2. This skill covers: - Native ETH balance queries - ERC-20 token balance queries (single contract on every plan; full holdings on PRO) - Transaction history queries (normal, internal, ERC-20/ERC-721/ERC-1155 transfers) - First-funding lookup for an address (PRO `fundedby` with a 2-call free-tier fallback) - Target-chain support via the `chainid` parameter - Auto-detection of Free vs Lite vs PRO so paid-only chains and PRO-only endpoints are used when available - Token reputation availability: Etherscan exposes this only through paid metadata surfaces, not Lite **Scope:** Read-only account queries. For other Etherscan API features, consult the fallback documentation. ## Prerequisites ### API Key Validation Before making any API call, verify the `ETHERSCAN_API_KEY` environment variable is set: ```bash if [ -z "$ETHERSCAN_API_KEY" ]; then echo "Error: ETHERSCAN_API_KEY environment variable is not set." echo "Get a free API key at: https://etherscan.io/myapikey" exit 1 fi ``` If the environment variable is missing, inform the user and halt execution. Never print the key itself: presence checks must stay value-free, so do not echo `$ETHERSCAN_API_KEY` or use `${ETHERSCAN_API_KEY:+...}` / `${ETHERSCAN_API_KEY:-...}` expansions in any command whose output reaches the transcript. ### Plan Detection Run the detection helper **once per session** and cache the result. It maps `getapilimit` → plan tier and probes a Base balance call to disambiguate Free from Lite: ```bash scripts/etherscan-detect-plan.sh ``` Output (key=value lines): ``` plan=lite credit_limit=100000 credits_used=4 credits_available=99996 limit_interval=daily interval_expiry=14:38:10 pro_endpoints=false paid_chains=true ``` `plan` is one of `free`, `lite`, `standard`, `advanced`, `professional`, `pro_plus`, `enterprise`, `unknown`. Two boolean fields gate behavior: - `paid_chains=true` — paid-only chains (Base, OP, Avalanche, BNB) are queryable. True for Lite and all higher tiers. - `pro_endpoints=true` — PRO-only actions (`addresstokenbalance`, `balancehistory`, `tokenholderlist`, `fundedby`, daily-stats endpoints, etc.) are callable. True for Standard and higher; **false on Lite**. **Manual detection** (if the script is unavailable): ```bash curl -s "https://api.etherscan.io/v2/api?chainid=1&module=getapilimit&action=getapilimit&apikey=$ETHERSCAN_API_KEY" # → {"status":"1","message":"OK","result":{"creditsUsed":1,"creditsAvailable":99999,"creditLimit":100000,"limitInterval":"daily","intervalExpiryTimespan":"07:20:05"}} ``` | `creditLimit` | Plan | Paid-only chains | PRO endpoints | | ------------- | ------------ | ---------------- | ------------- | | 100,000 | Free or Lite | Probe to confirm | No | | 200,000 | Standard | Yes | Yes | | 500,000 | Advanced | Yes | Yes | | 1,000,000 | Professional | Yes | Yes | | 1,500,000 | Pro Plus | Yes | Yes | | > 1,500,000 | Enterprise | Yes | Yes | Free and Lite both report `creditLimit: 100000`. Lite ($49/mo) raises rate-limit-per-second (5 vs 3) **and unlocks every supported chain** (Base, OP, Avalanche, BNB), but does **not** add PRO endpoints — those start at Standard. To disambiguate, attempt a paid-chain balance call (e.g., `chainid=8453`): status=1 → Lite, status=0 → Free. To probe PRO instead, the failure response is `"Sorry, it looks like you are trying to access an API Pro endpoint."`. `getapilimit` itself consumes 1 credit (plus 1 more for the paid-chain probe), so do not re-run mid-session. ## Chain Inference Do not default to Ethereum Mainnet. Always infer the chain from the user's prompt before making any API call. ### Inference Rules 1. **Explicit chain mention** — If the user mentions a chain name (e.g., "on Polygon", "Arbitrum balance", "Base chain"), use that chain. 2. **Chain-specific tokens** — Some tokens exist primarily on specific chains: - POL → Polygon (137) - ARB → Arbitrum One (42161) - OP → OP Mainnet (10) - AVAX → Avalanche C-Chain (43114) - BNB → BNB Smart Chain (56) - SONIC → Sonic (146) - SEI → Sei (1329) - MON → Monad (143) 3. **Contract address patterns** — If the user provides a contract address, consider asking which chain it's deployed on (many contracts exist on multiple chains). 4. **Testnet keywords** — Testnets are outside this skill's target list. Ask the user to file a feature request instead of querying them. 5. **Ambiguous cases** — If the chain cannot be inferred, **ask the user** before proceeding. Do not assume Ethereum Mainnet. ### Unsupported Chains If the user references a chain that is not in `references/generated/target-mainnets.json`, halt and ask them to file a feature request in <https://github.com/PaulRBerg/agent-skills>. Do not query Etherscan, Blockscout, Bungee, Chainlist, web search, or public RPCs for non-target chains. If the user references a **target EVM chain** that Etherscan API V2 does not cover, do **not** halt. Prefer Blockscout (`references/explorers/blockscout-api.md`) before direct RPC. If Blockscout doesn't index the target chain either, fall back to direct RPC calls against the target chain's default public RPC: 1. Resolve the chain via `references/generated/target-mainnets.json` and `references/generated/chain-aliases.json` to get the default public RPC, chain ID, native currency symbol, and explorer URL. 2. Issue bounded direct HTTP JSON-RPC calls (e.g., `eth_getBalance`, `eth_getLogs`, `eth_getTransactionByHash`) against that RPC. Do not hand the read to `cli-cast`. 3. Note in the response that the data came from the chain's public RPC, not Etherscan, so PRO-style aggregations (full token holdings, first-funding lookup) are unavailable and must be derived manually from logs/transactions if needed. If the user references a **non-EVM chain**, do not use this skill: ``` The chain "[chain name]" is outside the evm-atlas target list. Please file a feature request in https://github.com/PaulRBerg/agent-skills. ``` For the target-filtered list of Etherscan-supported chains and their IDs, see `references/generated/etherscan-chains.md`. ## API Base URL All requests use the unified V2 endpoint: ``` https://api.etherscan.io/v2/api ``` The `chainid` parameter determines which blockchain to query. ## ETH Balance Query Query native ETH (or native token) balance for an address. ### Endpoint Parameters | Parameter | Required | Default | Description | | --------- | -------- | -------- | ----------------------------------------------------------------------------------------------------------------------------------------------------- | | `chainid` | No | `1` | Chain ID (see etherscan-chains.md) | | `module` | Yes | - | Set to `account` | | `action` | Yes | - | Set to `balance` | | `address` | Yes | - | Wallet address (supports up to 20 comma-separated) | | `tag` | No | `latest` | `latest` or hex block number. On free/Lite, only the last 128 blocks are queryable; older history needs the `balancehistory` PRO endpoint (Standard+) | | `apikey` | Yes | - | API key from `$ETHERSCAN_API_KEY` | ### Single Address Query ```bash curl -s "https://api.etherscan.io/v2/api?chainid=1&module=account&action=balance&address=0xde0B295669a9FD93d5F28D9Ec85E40f4cb697BAe&tag=latest&apikey=$ETHERSCAN_API_KEY" ``` ### Multi-Address Query (up to 20) ```bash curl -s "https://api.etherscan.io/v2/api?chainid=1&module=account&action=balancemulti&address=0xaddress1,0xaddress2,0xaddress3&tag=latest&apikey=$ETHERSCAN_API_KEY" ``` ### Response Format **Single address:** ```json { "status": "1", "message": "OK", "result": "172774397764084972158218" } ``` **Multi-address:** ```json { "status": "1", "message": "OK", "result": [ { "account": "0xaddress1", "balance": "1000000000000000000" }, { "account": "0xaddress2", "balance": "2500000000000000000" } ] } ``` ## ERC-20 Token Balance Query Query ERC-20 token balance for an address. ### Endpoint Parameters | Parameter | Required | Default | Description | | ----------------- | -------- | -------- | ---------------------------------- | | `chainid` | No | `1` | Chain ID (see etherscan-chains.md) | | `module` | Yes | - | Set to `account` | | `action` | Yes | - | Set to `tokenbalance` | | `contractaddress` | Yes | - | ERC-20 token contract address | | `address` | Yes | - | Wallet address to query | | `tag` | No | `latest` | Block tag | | `apikey` | Yes | - | API key from `$ETHERSCAN_API_KEY` | ### Example Query ```bash curl -s "https://api.etherscan.io/v2/api?chainid=1&module=account&action=tokenbalance&contractaddress=0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48&address=0xde0B295669a9FD93d5F28D9Ec85E40f4cb697BAe&tag=latest&apikey=$ETHERSCAN_API_KEY" ``` ### Response Format ```json { "status": "1", "message": "OK", "result": "135499000000" } ``` ### Full Holdings `tokenbalance` returns the balance for **one** ERC-20 contract at a time. To list **every** token an address holds: | Action | Returns | | ------------------------ | ---------------------------------------------------------- | | `addresstokenbalance` | All ERC-20 holdings (token, quantity, decimals, USD price) | | `addresstokennftbalance` | All ERC-721 collection holdings and counts | **Use only when `pro_endpoints=true`** from plan detection. Both require Standard plan or higher and are throttled to **2 calls/second** regardless of tier. ```bash curl -s "https://api.etherscan.io/v2/api?chainid=1&module=account&action=addresstokenbalance&address=0x...&page=1&offset=100&apikey=$ETHERSCAN_API_KEY" ``` When `pro_endpoints=false`, fall back to looping `tokenbalance` over a known token contract list. ## Transaction History Queries Query an address's transaction history. Five actions are available under `module=account`: | Action | Returns | | ---------------- | ------------------------------------------ | | `txlist` | Normal (external) transactions | | `txlistinternal` | Internal transactions (contract-initiated) | | `tokentx` | ERC-20 token transfer events | | `tokennfttx` | ERC-721 (NFT) token transfer events | | `token1155tx` | ERC-1155 token transfer events | ### Endpoint Parameters | Parameter | Required | Default | Description | | ----------------- | -------- | ----------- | ------------------------------------------------------------ | | `chainid` | No | `1` | Chain ID (see etherscan-chains.md) | | `module` | Yes | - | Set to `account` | | `action` | Yes | - | One of the actions above | | `address` | Yes | - | Wallet address | | `contractaddress` | No | - | Token contract filter (`tokentx`/`tokennfttx`/`token1155tx`) | | `startblock` | No | `0` | Starting block number | | `endblock` | No | `999999999` | Ending block number | | `page` | No | `1` | Page number for pagination | | `offset` | No | `100` | Results per page (see free-tier limit note below) | | `sort` | No | `asc` | `asc` or `desc` by block number | | `apikey` | Yes | - | API key from `$ETHERSCAN_API_KEY` | > **Pagination cap by plan (effective July 1, 2026):** `offset` maximum is `1000` for free-tier accounts and `10000` for > paid tiers (Lite included) on `txlist`, `txlistinternal`, `tokentx`, `tokennfttx`, `token1155tx`, and other list > endpoints. When `plan=free`, paginate in batches ≤ 1,000. ### Example Query ```bash curl -s "https://api.etherscan.io/v2/api?chainid=1&module=account&action=txlist&address=0xde0B295669a9FD93d5F28D9Ec85E40f4cb697BAe&startblock=0&endblock=999999999&page=1&offset=100&sort=desc&apikey=$ETHERSCAN_API_KEY" ``` ### Response Format `result` is an array of transaction objects. Each contains a Unix `timeStamp` (seconds, as a string) and chain-specific fields (`hash`, `from`, `to`, `value`, `gasUsed`, etc.). ```json { "status": "1", "message": "OK", "result": [ { "blockNumber": "18000000", "timeStamp": "1693526400", "hash": "0x...", "from": "0x...", "to": "0x...", "value": "1000000000000000000", "gasUsed": "21000" } ] } ``` ### Timestamp Conversion `timeStamp` is a Unix epoch in seconds. Always produce **timezone-aware UTC datetimes**. ```python from datetime import datetime, timezone dt = datetime.fromtimestamp(int(tx["timeStamp"]), tz=timezone.utc) ``` Do **not** use `datetime.utcfromtimestamp()` — it returns a naive datetime and is deprecated in Python 3.12+. ```bash # Shell equivalent (GNU date) date -u -d "@1693526400" --iso-8601=seconds # macOS / BSD date date -u -r 1693526400 +"%Y-%m-%dT%H:%M:%SZ" ``` ## NFT Transfer History Fetch historical ERC-721 or ERC-1155 transfers for an address. Both actions share the parameter table in the previous section; pass `contractaddress` to filter by collection. Pagination caps (1,000 free / 10,000 paid) and the `startblock`/`endblock`/`page`/`offset`/`sort` semantics are identical to `txlist`. ### ERC-721 Transfers (`tokennfttx`) ```bash curl -s "https://api.etherscan.io/v2/api?chainid=1&module=account&action=tokennfttx&address=0x6975be450864c02b4613023c2152ee0743572325&contractaddress=0x06012c8cf97bead5deae237070f9587f8e7a266d&startblock=0&endblock=999999999&page=1&offset=100&sort=asc&apikey=$ETHERSCAN_API_KEY" ``` Response entry (one per `Transfer` event involving the address): ```json { "blockNumber": "4708120", "timeStamp": "1512907118", "hash": "0x031e6968...", "nonce": "0", "blockHash": "0x4be19c27...", "from": "0xb1690c08e213a35ed9bab7b318de14420fb57d8c", "contractAddress": "0x06012c8cf97bead5deae237070f9587f8e7a266d", "to": "0x6975be450864c02b4613023c2152ee0743572325", "tokenID": "202106", "tokenName": "CryptoKitties", "tokenSymbol": "CK", "tokenDecimal": "0", "transactionIndex": "81", "gas": "158820", "gasPrice": "40000000000", "gasUsed": "60508", "cumulativeGasUsed": "4880352", "input": "deprecated", "methodId": "0x454a2ab3", "functionName": "bid(uint256 _tokenId)", "confirmations": "18759540" } ``` NFT-specific fields: `contractAddress` (collection), `tokenID` (per-NFT identifier), `tokenName`, `tokenSymbol`, `tokenDecimal` (always `"0"` for ERC-721). ### ERC-1155 Transfers (`token1155tx`) Same parameter shape — swap `action=token1155tx`. ERC-1155 differs from ERC-721 in two response fields: - **`tokenValue`** (string) — quantity transferred for this `tokenID`. Required because ERC-1155 is semi-fungible; a single transfer can move N copies of one ID. **Not present in ERC-721 responses.** - **`tokenDecimal`** is omitted (ERC-1155 has no decimals concept). ```json { "blockNumber": "...", "timeStamp": "...", "hash": "...", "from": "...", "to": "...", "contractAddress": "0x76be3b62873462d2142405439777e971754e8e77", "tokenID": "10371", "tokenValue": "1", "tokenName": "...", "tokenSymbol": "...", "...": "(other tx-level fields identical to tokennfttx)" } ``` `TransferBatch` events (multiple IDs in one tx) appear as **multiple result entries sharing the same `hash`** — one per `(tokenID, tokenValue)` pair. Group by `hash` to reconstruct the batch. ### Filtering by Collection or Token ID - **By collection** — pass `contractaddress=<collection>`. The API filters server-side; omit to fetch transfers across all collections. - **By token ID** — no server-side filter exists. Fetch the collection's transfers and filter `result[].tokenID == <id>` client-side. For high-volume collections, narrow with `startblock`/`endblock` first. - **Mint vs burn vs transfer** — derive from `from`/`to`: - `from == 0x0000...0000` → mint - `to == 0x0000...0000` → burn - otherwise → transfer ### Cost & Limits Standard list-endpoint pricing — 1 credit per call, same rate-limit tier as `txlist`. Not a PRO endpoint; available on Free and Lite for Etherscan-supported target chains (paid-chain restriction still applies to Base/OP/Avalanche/BNB). ## First Funding Transaction Identify the earliest transaction that sent native value to an address — useful for fund-origin tracing, provenance, or compliance checks. Cost is **1 API call** (PRO) or **2 API calls** (fallback). ### Preferred: `fundedby` (PRO endpoint) Returns the address, tx hash, block, timestamp, and value of the transaction that first funded an EOA. Single call, structured response. | Parameter | Required | Default | Description | | --------- | -------- | ------- | ----------------------------------- | | `chainid` | No | `1` | Chain ID (see etherscan-chains.md) | | `module` | Yes | - | Set to `account` | | `action` | Yes | - | Set to `fundedby` | | `address` | Yes | - | EOA address (contracts unsupported) | | `apikey` | Yes | - | API key from `$ETHERSCAN_API_KEY` | ```bash curl -s "https://api.etherscan.io/v2/api?chainid=1&module=account&action=fundedby&address=0x4838B106FCe9647Bdf1E7877BF73cE8B0BAD5f97&apikey=$ETHERSCAN_API_KEY" ``` Response: ```json { "status": "1", "message": "OK", "result": { "block": 53708500, "timeStamp": "1708349932", "fundingAddress": "0x6969174fd72466430a46e18234d0b530c9fd5f49", "fundingTxn": "0xbc0ca4a67eb1555920552246409626cd60df01314dd2bcdb99718b506d9c9946", "value": "1000000000000000" } } ``` **Requirements & limits:** - PRO endpoint — requires Standard plan or higher (`pro_endpoints=true` from plan detection). - Throttled to **2 calls/second** regardless of paid tier. - **EOA only.** Contract addresses return an error; use the fallback below. ### Fallback: scan ASC normal + internal transactions When `pro_endpoints=false` (free/Lite) or the address is a contract, scan both transaction lists ascending and pick the earliest qualifying incoming entry. Two API calls per address. ```bash # Earliest normal txs involving the address curl -s "https://api.etherscan.io/v2/api?chainid=1&module=account&action=txlist&address=0x...&startblock=0&endblock=999999999&page=1&offset=10&sort=asc&apikey=$ETHERSCAN_API_KEY" # Earliest internal txs involving the address curl -s "https://api.etherscan.io/v2/api?chainid=1&module=account&action=txlistinternal&address=0x...&startblock=0&endblock=999999999&page=1&offset=10&sort=asc&apikey=$ETHERSCAN_API_KEY" ``` For each response, pick the first entry where **all** of the following hold: - `to.toLowerCase() == address.toLowerCase()` — incoming, not outgoing. - `value` (in wei) is greater than `0` — actual funding, not a zero-value call. - `isError == "0"` (omit this filter for internal txs, which use `isError` differently or not at all). The funding tx is whichever match has the lower `blockNumber`; break ties by `transactionIndex` (normal txs) or by list order (internal txs). **Why both lists:** An address may be funded externally (normal tx) or internally (contract sent ETH — common for CEX withdrawals routed through proxy/router contracts, contract deployments with non-zero `msg.value`, or SELFDESTRUCT refunds). Checking only `txlist` will miss internally-funded addresses. **Why `offset=10`, not `1`:** A `txlist` query returns every tx involving the address, including outgoing ones. The very first entry is occasionally outgoing (e.g., the address was internally pre-funded), so fetch a small window and scan for the first incoming match. **Edge cases:** - **No qualifying entry in the first 10** — extend with `offset=100` and `page=1`, or paginate further. In practice, > 10 outgoing-before-incoming is exceedingly rare. - **Genesis allocation** — pre-mined balances do not appear in either list. The address shows a balance with no funding tx; report this explicitly. - **Token-only funding** — `fundedby` and this fallback only consider native value. If the address was bootstrapped with ERC-20 transfers alone (rare for EOAs since gas is needed), repeat the fallback against `tokentx`. ## Multi-Chain Usage Specify the `chainid` parameter to query different blockchains. ### Target Chain IDs (Free Tier) See `references/generated/etherscan-chains.md` for the free-tier target chain list with chain IDs. ### Example: Polygon Query ```bash curl -s "https://api.etherscan.io/v2/api?chainid=137&module=account&action=balance&address=0x...&tag=latest&apikey=$ETHERSCAN_API_KEY" ``` ## Wei to Human-Readable Conversion API responses return balances in the smallest unit (wei for ETH, smallest decimals for tokens). ### ETH Conversion Divide by 10^18: ```bash # Using bc for precision echo "scale=18; 172774397764084972158218 / 1000000000000000000" | bc # Result: 172774.397764084972158218 ``` ### ERC-20 Conversion Divide by 10^decimals (typically 18, but varies per token): | Token | Decimals | | ----------- | -------- | | Most tokens | 18 | | USDC, USDT | 6 | | WBTC | 8 | ```bash # USDC example (6 decimals) echo "scale=6; 135499000000 / 1000000" | bc # Result: 135499.000000 ``` ## Output Formatting Use the completion format in `SKILL.md`: preserve full identifiers and use a compact table only when fields repeat. ## Plan-Gated Capabilities Decisions in this section depend on the cached output of `scripts/etherscan-detect-plan.sh`. ### Paid-Only Chains Four target mainnets require any paid Etherscan plan. **Lite ($49/mo) is sufficient** — it grants access to every Etherscan-supported target chain at the same 100,000 daily-credit limit as Free. Data endpoints (balance, txlist, logs, etc.) fail only when `plan=free` (i.e., `paid_chains=false`). See `references/generated/etherscan-chains.md` for the paid-plan target chain list with chain IDs. **Exception:** `module=contract` endpoints (`getsourcecode`, `getabi`, etc.) work on **all** chains for every plan including free. The paid-plan requirement applies only to data endpoints. If `paid_chains=false` (i.e., `plan=free`) and the user requests a data query on the chains above, route to Blockscout (`references/explorers/blockscout-api.md`) before direct RPC. Only mention upgrading to Lite or higher if the user specifically needs Etherscan as the source. ### PRO-Only Endpoints When `pro_endpoints=true`, the following actions become available (non-exhaustive — see `https://docs.etherscan.io/api-pro/api-pro` for the full list): | Module | Action(s) | Use case | | ------------ | ----------------------------------------------------------------------------- | -------------------------------------------------------- | | `account` | `addresstokenbalance`, `addresstokennftbalance`, `balancehistory`, `fundedby` | Full holdings, historical balances, first-funding lookup | | `token` | `tokenholderlist`, `tokeninfo`, `tokensupplyhistory`, `tokenbalancehistory` | Token analytics | | `block` | `dailyavgblocksize`, `dailyblkcount`, `dailyblockrewards`, etc. | Daily block stats | | `stats` | `dailytxnfee`, `dailynewaddress`, `dailynetutilization`, etc. | Network-wide daily metrics | | `gastracker` | `dailyavggaslimit`, `dailygasused`, `dailyavggasprice` | Daily gas metrics | When `pro_endpoints=false` (free or Lite), prefer the non-PRO equivalents listed in this skill or fall back to per-token loops. ### Token Reputation / Metadata Etherscan's token reputation badges are **not available on Lite**. The documented API surfaces are: | Surface | Endpoint/action | Minimum plan | Notes | | -------------------- | ----------------------------------------- | ------------ | ------------------------------------------------------------------------------------------------ | | Address Metadata API | `module=nametag&action=getaddresstag` | Pro Plus | Query the token contract address; response includes numeric `reputation` and `other_attributes`. | | Metadata CSV export | `module=nametag&action=exportaddresstags` | Enterprise | Bulk export; `other_attributes` can include `TR` token reputation values. | | Token info | `module=token&action=tokeninfo` | Standard | Returns project/social metadata and `blueCheckmark`, but not the token reputation badge. | Do not tell Lite users they can fetch token reputation from Etherscan API. Lite only unlocks paid Etherscan target chains and higher community rate limits; it does not unlock API Pro endpoints, Pro Plus address metadata, or Enterprise metadata CSV exports. ### All Plans All other Etherscan-supported target chains are available on every plan including Free. On Lite and higher, the paid-only target chains above also become available. See `references/generated/etherscan-chains.md` for the target-filtered list with chain IDs. ## Error Handling ### Common Error Responses | Status | Message | Cause | | ------ | ------------------------ | ------------------------------- | | `0` | `NOTOK` | Invalid API key or rate limited | | `0` | `Invalid address format` | Malformed address | | `0` | `No transactions found` | Address has no activity | ### Rate Limits by Plan | Plan | Calls/second | Daily calls | | ------------ | ------------ | ----------- | | Free | 3 | 100,000 | | Lite | 5 | 100,000 | | Standard | 10 | 200,000 | | Advanced | 20 | 500,000 | | Professional | 30 | 1,000,000 | | Pro Plus | 30 | 1,500,000 | | Enterprise | custom | unmetered | PRO endpoints (`addresstokenbalance`, etc.) are throttled to **2 calls/second** regardless of tier. See `https://docs.etherscan.io/resources/rate-limits` for the authoritative schedule. If rate limited, wait briefly and retry. ## Reference Files - **`references/generated/etherscan-chains.md`** - Target-filtered list of supported chains with chain IDs - **`scripts/etherscan-detect-plan.sh`** - Plan-tier detection helper (run once per session) ## Fallback Documentation For read-only use cases not covered by this skill (gas estimates, block or network stats, etc.), fetch the AI-friendly documentation: ``` https://docs.etherscan.io/llms.txt ``` -
explorer-paths.json 534 B
{ "paths": [ { "resource": "address", "path": "/address/<addr>", "example": "https://arbiscan.io/address/0xabc..." }, { "resource": "transaction", "path": "/tx/<hash>", "example": "https://etherscan.io/tx/0x123..." }, { "resource": "block", "path": "/block/<number>", "example": "https://basescan.org/block/12345678" }, { "resource": "token", "path": "/token/<addr>", "example": "https://polygonscan.com/token/0xdef..." } ] } -
fantom-opera.md 6.8 KB
# Fantom Opera Explorer and GraphQL ## Route Use this reference for Fantom Opera (`chain_id=250`) account history: - Human explorer: <https://explorer.fantom.network/> - Official read-only GraphQL endpoint: `POST https://xapi.fantom.network/` Chainscout still lists the self-hosted FTMScout instance at <https://ftmscout.com/>, but its frontend can return HTTP 200 while its `/api/v2/*` data routes return HTTP 500. The atlas overlay therefore marks that Blockscout route unsafe; do not use it for evidence or bypass the generated resolver's refusal. The GraphQL schema also exposes mutations, including raw transaction submission. Never invoke a mutation. EVM Atlas is strictly read-only. ## Evidence Boundary Treat Opera GraphQL account history as a **partial positive-evidence route**, not a complete historical index. Conformance probes have found existing EOA activity missing from all account lists, and the public schema declares neither a genesis start nor the MongoDB scanner's last processed block. `block` and `state.blocks` report the connected Opera node's chain head; they do not prove that the aggregated account index has processed every block through that head. Consequences: - A returned row can establish candidate activity after its transaction, block hash, status, parties, and transfer log are confirmed by checkpoint-bound RPC evidence. - An empty list, `totalCount: "0x0"`, or `hasNext: false` proves only that this GraphQL index returned no rows. It is not a historical negative. - For the `bootstrap-discovery` profile, an exact `ethereum-eoa` zero nonce and zero native balance may still omit normal and internal history under the profile invariant, but GraphQL empties do not complete ERC-20 or ERC-721 coverage. Require an independent genesis-complete index or exhaustive checkpoint-bounded logs for those channels. - For general or nonzero-state sweeps, GraphQL also lacks account-wide internal-transaction history. Preserve that as an explicit coverage gap or use a complete tracing/indexed-history route. ## Account Channels The live schema exposes these cursor-paginated `Account` fields: | GraphQL field | EVM Atlas channel | Returned evidence | | --------------- | ----------------- | ------------------------------------------------------------------------- | | `txList` | `txlist` | Normal transaction hash, parties, value, status, and block identity | | `erc20TxList` | `tokentx` | ERC-20 transfer parties, amount, log index, transaction, and timestamp | | `erc721TxList` | `tokennfttx` | ERC-721 transfer parties, token ID, log index, transaction, and timestamp | | `erc1155TxList` | `token1155tx` | ERC-1155 transfer parties, token ID, amount, log index, and transaction | `txCount` and `balance` are current account state, not historical-list completeness signals. Acquire nonce and native balance at the fixed checkpoint through JSON-RPC as specified in `references/workflows/provider-routing.md`. The account schema has no `txlistinternal` equivalent. Do not relabel `txList` as internal history, and do not infer native inbound or trace completeness from it. ## Query and Pagination Use independent cursors for every list. The endpoint accepts at most 250 edges per request; use a smaller positive count when needed. With the cursor omitted, a positive count starts at the most recent edge. Continue from that list's `pageInfo.last` while `hasNext` is true, reject repeated cursors, and preserve the provider's hexadecimal scalar values. ```graphql query AccountHistory( $address: Address! $checkpoint: Long! $txCursor: Cursor $erc20Cursor: Cursor $erc721Cursor: Cursor $erc1155Cursor: Cursor $count: Int! ) { providerHead: block { number hash timestamp } checkpoint: block(number: $checkpoint) { number hash timestamp } account(address: $address) { txList(cursor: $txCursor, count: $count) { totalCount pageInfo { first last hasNext hasPrevious } edges { cursor transaction { hash from to value status blockNumber blockHash } } } erc20TxList(cursor: $erc20Cursor, count: $count) { totalCount pageInfo { first last hasNext hasPrevious } edges { cursor trx { trxHash trxIndex trxType sender recipient amount timeStamp transaction { status blockNumber blockHash } } } } erc721TxList(cursor: $erc721Cursor, count: $count) { totalCount pageInfo { first last hasNext hasPrevious } edges { cursor trx { trxHash trxIndex trxType sender recipient amount tokenId timeStamp transaction { status blockNumber blockHash } } } } erc1155TxList(cursor: $erc1155Cursor, count: $count) { totalCount pageInfo { first last hasNext hasPrevious } edges { cursor trx { trxHash trxIndex trxType sender recipient amount tokenId timeStamp transaction { status blockNumber blockHash } } } } } } ``` Set `checkpoint` to the fixed decimal block number and compare the returned checkpoint hash and timestamp with the RPC checkpoint. Require `providerHead.number >= checkpoint.number`. This verifies chain access and row bounds only. Because the schema exposes no account-index head, never convert that comparison into a negative-history claim. For positive evidence, ignore post-checkpoint rows, apply the selected profile's predicates, and independently confirm the earliest qualifying transaction and logs through RPC. For a negative, replace this partial route with independent genesis-complete coverage; do not combine two partial empty responses and call them complete. ## Sources - Fantom Opera public endpoints: <https://docs.fantom.foundation/build-on-opera/api/public-endpoints> - GraphQL getting started: <https://docs.fantom.foundation/build-on-opera/api/graphql/getting-started> - GraphQL schema and account lists: <https://docs.fantom.foundation/build-on-opera/api/graphql/schema-structure> - Cursor pagination: <https://docs.fantom.foundation/build-on-opera/api/graphql/schema-basics> - API implementation: <https://github.com/Fantom-foundation/fantom-api-graphql> -
optimism-pre-regenesis.md 9.9 KB
# OP Mainnet Pre-Regenesis History ## Evidence Boundary Use this reference for OP Mainnet (`chain_id=10`) queries that target activity before the final regenesis on `2021-11-11`. Current OP Mainnet JSON-RPC cannot return a canonical `eth_getTransactionReceipt` response for an OVM1 transaction removed by the final regenesis. A current-RPC `null`, provider archive failure, or explorer omission proves only native receipt unavailability; it is not negative evidence that the historical transaction occurred. Dune's `optimism_legacy_ovm1` transaction, log, and trace rows are historical execution evidence. When the required components agree, reconstruct a **legacy execution packet**. Never call that packet a receipt: it is neither an authenticated JSON-RPC receipt nor a historical state-trie proof. Preserve its Dune provenance and every missing component. ## Exact-Transaction Workflow 1. Establish that the target is on OP Mainnet and predates the final regenesis. Use supplied context, another dated source, or the exact Dune transaction row. Do not infer nonexistence when current OP Mainnet routes cannot resolve the hash. 2. Query all three legacy tables by the exact hash. Replace `<TX_HASH_HEX>` with the 64 hexadecimal digits while retaining DuneSQL's `0x` varbinary prefix: ```sql SELECT * FROM optimism_legacy_ovm1.transactions WHERE hash = 0x<TX_HASH_HEX>; SELECT * FROM optimism_legacy_ovm1.logs WHERE tx_hash = 0x<TX_HASH_HEX> ORDER BY "index"; SELECT * FROM optimism_legacy_ovm1.traces WHERE tx_hash = 0x<TX_HASH_HEX> ORDER BY trace_address; ``` 3. Require exactly one transaction row. Preserve zero rows or duplicate rows as a coverage outcome; do not choose a row heuristically. 4. Verify the transaction hash, block number, block hash, and block time agree across every returned component. Verify transaction `from`/`to` against each log's `tx_from`/`tx_to`; verify transaction `success` against each trace's `tx_success`. A trace's own `from`, `to`, and `success` describe that call, so reconcile them through the call hierarchy rather than forcing them to equal the root transaction fields. 5. Preserve log indices exactly in ascending order. Preserve trace-address arrays in ascending hierarchy order, including the root when present. Record duplicate, missing, or contradictory ordering metadata as a coverage gap; never renumber rows. 6. Use the address dashboard only to discover candidate hashes for an address-wide request. An exact-hash request uses the table workflow above. ## Legacy Execution Packet Return exact values; do not truncate identifiers or normalize away nulls. | Component | Dune provenance | Required packet fields | | ----------- | ---------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------ | | Transaction | `optimism_legacy_ovm1.transactions` | `success`, `from`, `to`, `value`, `data`, gas fields, `block_number`, `block_hash`, `block_time`, transaction index/hash | | Logs | `optimism_legacy_ovm1.logs`, ordered by index | `contract_address`, topics, `data`, `index`, transaction index/hash, block fields, `tx_from`, `tx_to` | | Traces | `optimism_legacy_ovm1.traces`, ordered by path | call `from`/`to`, `type`, `call_type`, `input`, `output`, `value`, `success`, `error`, `tx_success`, `trace_address` | Attach the namespace and table name to each component, plus an explicit `coverage_gaps` list. A packet is complete only for the requested determination; complete execution rows do not independently prove historical contract state, deployment identity, wallet ownership, or an LP principal/fee split. ## Coverage Outcomes Record native-receipt availability separately from packet coverage: | Outcome | Record when | | ------------------------------------ | --------------------------------------------------------------------------------------------------------- | | Native receipt unavailable | Current RPC returns `null`, an archive route fails, or the current explorer omits the transaction. | | Historical execution evidence absent | The exact transaction row is absent. This is absence from Dune, not proof the transaction never existed. | | Legacy execution packet partial | The transaction exists but zero log rows return and non-emission is not otherwise proven. | | Legacy execution packet partial | Transaction and logs exist but traces are absent, contradictory, or structurally incomplete. | | Legacy execution packet partial | Optimism's known January-July 2021 execution-effects loss may cover a required component. | | Execution packet complete, state gap | Execution is reconstructable, but required historical contract, wallet, or position state is unavailable. | | Complete enough for determination | Transaction, ordering, logs, traces, identity, state, and wallet deltas needed by the request are proven. | Zero logs can be a legitimate execution result. Promote that outcome beyond partial only when verified calldata, traces, and contract semantics show that no relevant event should exist. Likewise, trace rows are not complete merely because at least one row returned; reconcile their hierarchy, transaction status, errors, and required internal value movement. ## Concentrated-Liquidity Reconstruction When the accounting result depends on a concentrated-liquidity position: 1. Identify the position token and prove the exact position-manager deployment used historically. Do not substitute a current explorer label or a same-interface deployment. 2. Retrieve the full prior lifecycle for that position, from creation through the target transaction. Include every position transfer, liquidity increase/decrease, collection, and other state-changing action supported by the exact deployment's events and calls. 3. Reconstruct the last pre-transaction liquidity state and whether principal or fees were already owed. Never infer historical state from a current position lookup. Treat an unproven zero balance as unknown, not zero. 4. Decode the target's exact command order and maximum collect bounds. Bounds and calldata express intent; ordered logs, traces, and wallet movements establish execution. 5. Reconcile decrease, collect, unwrap, sweep, refund, and final transfer evidence using exact integer amounts. Keep native and wrapped legs distinct. 6. Verify a principal/fee split only when the complete lifecycle and target execution support the residual. Otherwise report the combined collection and name the missing pre-state or lifecycle evidence. ## Browser and Persistence Boundary Chrome DevTools may run bounded, read-only Dune queries. Executing a query may consume Dune credits. Leave queries unsaved by default; saving, publishing, scheduling, or otherwise externally persisting one requires explicit authority. Never put a private hash, wallet, position identifier, or financial value into this installable reference. As of `2026-09-10`, Dune's Free plan is view-only: public dashboards, query source, and data collections remain viewable, but exact-hash table lookups and parameterized address-dashboard results require query execution on a paid plan. Affected existing accounts received temporary Plus access through `2026-09-24`, with 2,500 trial credits. The unsaved browser workflow needs only query execution, so Dune advertises Analyst as sufficient; Plus is not required for this route. Never purchase or upgrade without explicit authority. Treat a missing execution entitlement or exhausted credit balance as a provider-plan coverage gap, not historical absence. Chromium does not bypass server-side plan enforcement, and viewing or copying a public query does not produce fresh address-parameterized or exact-hash results. Dune's older pricing FAQ and the OVM1 dashboard copy may still mention free executions or free credits; resolve entitlement from the live pricing page and the credentialed account. Separately, Dune's Application Service Addendum directs programmatic interaction to its API service. Chrome DevTools automation may be considered programmatic even when it drives the graphical site. Do not represent a paid browser workflow as terms-cleared; prefer a supported API or MCP route for repeated automation, or obtain Dune clarification. ## Deeper Fallback For the January-July 2021 period, Optimism documents partial loss of transaction execution effects, including emitted events and success state. When Dune genuinely lacks the exact transaction or a required lifecycle event, the raw inputs published through Ethereum's `CanonicalTransactionChain` are a deeper reconstruction path. Re-execution is laborious, costly, and may remain incomplete. Do not require it when the Dune packet already answers the requested question. ## Sources - Optimism, "Accessing pre-regenesis history": `https://docs.optimism.io/op-mainnet/pre-bedrock-history/regenesis-history` - Optimism, "Lost pre-regenesis data": `https://docs.optimism.io/op-mainnet/pre-bedrock-history/lost-pre-regenesis-data` - Dune catalog: `https://dune.com/data/optimism_legacy_ovm1.transactions`, `https://dune.com/data/optimism_legacy_ovm1.logs`, and `https://dune.com/data/optimism_legacy_ovm1.traces` - DuneSQL varbinary literals: `https://docs.dune.com/query-engine/datatypes#varbinary` - Dune query execution and credit behavior: `https://docs.dune.com/query-engine/query-executions` - OVM1 address dashboard: `https://dune.com/optimismfnd/OVM1.0-User-Address-Transactions` - Dune pricing: `https://dune.com/pricing` - Dune Application Service Addendum: `https://dune.com/application-terms`
-
-
generated
-
blockscout-chains.md 10.5 KB
# Target Chains & Chainscout <!-- Generated by scripts/generate-evm-atlas.ts. Registry metadata comes from crypto-registry data/chains.json; edit skills/evm-atlas/references/atlas-overlays.json for Atlas-only fields. --> Blockscout and Chainscout index many EVM networks, but this skill only uses them for chains in `references/generated/target-mainnets.json`. Do not use Chainscout to expand scope. If a requested chain is not in that JSON file, ask the user to file a feature request in <https://github.com/PaulRBerg/agent-skills>. ## Chainscout API No API key required. Live UI: <https://chains.blockscout.com/> | Endpoint | Returns | | --------------------------------------------------- | ----------------------------------------------------- | | `GET https://chains.blockscout.com/api/chains` | Object keyed by `chain_id` -> metadata (all networks) | | `GET https://chains.blockscout.com/api/chains/{id}` | Metadata for one chain | Single-chain response shape: ```json { "name": "Gnosis", "native_currency": "XDAI", "isTestnet": false, "layer": 1, "rollupType": null, "ecosystem": "Ethereum", "explorers": [{ "url": "https://gnosis.blockscout.com/", "hostedBy": "blockscout" }] } ``` `explorers[].hostedBy`: - `blockscout` - Blockscout-operated; candidate for the unified PRO host (`api.blockscout.com/{chain_id}/...`). Still confirm with a live call; not every hosted target chain is fronted by the PRO host. - anything else - community-operated. Per-instance only; use `explorers[].url` directly (no key). Use `scripts/resolve-chain.sh <chain_id>` to extract these fields as `key=value` lines. The helper refuses non-target chain IDs and rejects Chainscout responses whose name does not match the target chain. For **name -> `chain_id`**, use `references/generated/target-mainnets.json` and `references/generated/chain-aliases.json` first. ## Target Chains Observed in Chainscout Observed on 2026-07-08. Presence here does not override the canonical explorer/RPC metadata in `references/generated/target-mainnets.json`. | Chain | `chain_id` | Native | Hosted by | Instance URL | Notes | | --------------- | ---------- | ------ | ---------- | ------------------------------------------------ | ------------------------------------------------- | | Arbitrum | `42161` | ETH | blockscout | https://arbitrum.blockscout.com/ | | | Arbitrum Nova | `42170` | ETH | blockscout | https://arbitrum-nova.blockscout.com/ | Canonical explorer (Arbiscan Nova decommissioned) | | Base | `8453` | ETH | blockscout | https://base.blockscout.com/ | | | Celo | `42220` | CELO | blockscout | https://celo.blockscout.com/ | | | Ethereum | `1` | ETH | blockscout | https://eth.blockscout.com/ | | | Filecoin | `314` | FIL | blockscout | https://filecoin.blockscout.com/ | | | Gnosis | `100` | xDAI | blockscout | https://gnosis.blockscout.com/ | | | HyperEVM | `999` | HYPE | self | https://www.hyperscan.com/ | Chainscout marks `isTestnet=true` | | Lightlink | `1890` | ETH | blockscout | https://phoenix.lightlink.io/ | | | Linea | `59144` | ETH | self | https://explorer.linea.build/ | | | Mode | `34443` | ETH | blockscout | https://explorer.mode.network/ | | | Morph | `2818` | ETH | self | https://explorer.morph.network/ | Separate API host; see explorerApiUrl | | Optimism | `10` | ETH | blockscout | https://explorer.optimism.io/ | | | Polygon | `137` | POL | blockscout | https://polygon.blockscout.com/ | | | Robinhood Chain | `4663` | ETH | blockscout | https://robinhoodchain.blockscout.com/ | | | Scroll | `534352` | ETH | blockscout | https://scroll.blockscout.com | | | Taiko | `167000` | ETH | self | https://blockscout.mainnet.taiko.xyz/ | Chainscout name is Taiko Alethia | | Unichain | `130` | ETH | blockscout | https://unichain.blockscout.com | | | World Chain | `480` | ETH | alchemy | https://worldchain-mainnet.explorer.alchemy.com/ | Alchemy-hosted instance; prefer Etherscan V2 | | ZKsync Era | `324` | ETH | blockscout | https://zksync.blockscout.com/ | | ## Target Chains Absent or Unsafe in Chainscout Use Etherscan, a documented exceptional-history route, or the public RPC/explorer in `references/generated/target-mainnets.json` for these. | Chain | `chain_id` | Notes | | --------- | ---------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | Abstract | `2741` | Not returned by Chainscout | | Avalanche | `43114` | Not returned by Chainscout | | Berachain | `80094` | Not returned by Chainscout | | Blast | `81457` | Not returned by Chainscout | | BNB Chain | `56` | Not returned by Chainscout | | Chiliz | `88888` | Not returned by Chainscout | | Core Dao | `1116` | Not returned by Chainscout | | Fantom | `250` | Chainscout still lists self-hosted FTMScout at https://ftmscout.com/, but as checked 2026-07-31 its frontend returns HTTP 200 while /api/v2/* data routes return HTTP 500; do not use it for evidence | | Fraxtal | `252` | Not returned by Chainscout | | IoTeX | `4689` | Not returned by Chainscout | | Monad | `143` | Not returned by Chainscout | | Ronin | `2020` | Chainscout returns a different network for `2020`; app.roninchain.com blocks scripted access, so verify with `$chromium-browser` instead of curl or WebFetch | | Sei | `1329` | Not returned by Chainscout | | Sonic | `146` | Not returned by Chainscout | | Sophon | `50104` | Not returned by Chainscout | | Superseed | `5330` | Chromium verified 2026-09-15: explorer.superseed.xyz now serves Conduit Explorer, which does not support historical transactions, holdings, or transfers; do not use its stale Chainscout Blockscout route | | XDC | `50` | Not returned by Chainscout | | Zora | `7777777` | Not returned by Chainscout | ## Contributing Missing or wrong Blockscout registry data is fixed via PR to <https://github.com/blockscout/chainscout>. Requests to add non-target chains to this skill belong in <https://github.com/PaulRBerg/agent-skills>. -
chain-aliases.json 3.6 KB
{ "aliases": [ { "alias": "Arbitrum One", "chainName": "Arbitrum", "chainId": 42161 }, { "alias": "avalanche c-chain", "chainName": "Avalanche", "chainId": 43114 }, { "alias": "avax", "chainName": "Avalanche", "chainId": 43114 }, { "alias": "bsc", "chainName": "BNB Chain", "chainId": 56 }, { "alias": "binance smart chain", "chainName": "BNB Chain", "chainId": 56 }, { "alias": "bnb", "chainName": "BNB Chain", "chainId": 56 }, { "alias": "BNB Smart Chain", "chainName": "BNB Chain", "chainId": 56 }, { "alias": "Chiliz Chain", "chainName": "Chiliz", "chainId": 88888 }, { "alias": "coreDao", "chainName": "Core Dao", "chainId": 1116 }, { "alias": "mainnet", "chainName": "Ethereum", "chainId": 1 }, { "alias": "eth", "chainName": "Ethereum", "chainId": 1 }, { "alias": "ethereum", "chainName": "Ethereum", "chainId": 1 }, { "alias": "ethereum mainnet", "chainName": "Ethereum", "chainId": 1 }, { "alias": "fantom opera", "chainName": "Fantom", "chainId": 250 }, { "alias": "ftm", "chainName": "Fantom", "chainId": 250 }, { "alias": "fevm", "chainName": "Filecoin", "chainId": 314 }, { "alias": "filecoin evm", "chainName": "Filecoin", "chainId": 314 }, { "alias": "fvm", "chainName": "Filecoin", "chainId": 314 }, { "alias": "Filecoin Mainnet", "chainName": "Filecoin", "chainId": 314 }, { "alias": "frax", "chainName": "Fraxtal", "chainId": 252 }, { "alias": "gnosis chain", "chainName": "Gnosis", "chainId": 100 }, { "alias": "hyper evm", "chainName": "HyperEVM", "chainId": 999 }, { "alias": "hyperliquid", "chainName": "HyperEVM", "chainId": 999 }, { "alias": "lightlink phoenix", "chainName": "Lightlink", "chainId": 1890 }, { "alias": "LightLink Phoenix Mainnet", "chainName": "Lightlink", "chainId": 1890 }, { "alias": "Linea Mainnet", "chainName": "Linea", "chainId": 59144 }, { "alias": "Mode Mainnet", "chainName": "Mode", "chainId": 34443 }, { "alias": "op", "chainName": "Optimism", "chainId": 10 }, { "alias": "OP Mainnet", "chainName": "Optimism", "chainId": 10 }, { "alias": "matic", "chainName": "Polygon", "chainId": 137 }, { "alias": "pol", "chainName": "Polygon", "chainId": 137 }, { "alias": "robinhood", "chainName": "Robinhood Chain", "chainId": 4663 }, { "alias": "Sei Network", "chainName": "Sei", "chainId": 1329 }, { "alias": "Taiko Alethia", "chainName": "Taiko", "chainId": 167000 }, { "alias": "Taiko Mainnet", "chainName": "Taiko", "chainId": 167000 }, { "alias": "worldchain", "chainName": "World Chain", "chainId": 480 }, { "alias": "XDC Network", "chainName": "XDC", "chainId": 50 }, { "alias": "zksync", "chainName": "ZKsync Era", "chainId": 324 }, { "alias": "zora network", "chainName": "Zora", "chainId": 7777777 } ] } -
etherscan-chains.md 4.3 KB
# Etherscan Supported Target Chains Reference <!-- Generated by scripts/generate-evm-atlas.ts. Registry metadata comes from crypto-registry data/chains.json; edit skills/evm-atlas/references/atlas-overlays.json for Atlas-only fields. --> Source: <https://docs.etherscan.io/supported-chains> Live list: <https://api.etherscan.io/v2/chainlist> (returns the provider's authoritative `chainid` set) Verified against the live `chainlist` endpoint on 2026-07-08 (64 provider chains). This file intentionally lists only chains from `references/generated/target-mainnets.json`; provider-supported chains outside that list are out of scope for this skill. ## Target Mainnets (Free Tier Available) | Chain | Chain ID | Notes | | ----------- | -------- | ------------- | | Abstract | `2741` | | | Arbitrum | `42161` | | | Berachain | `80094` | | | Blast | `81457` | | | Celo | `42220` | | | Ethereum | `1` | Default chain | | Fraxtal | `252` | | | Gnosis | `100` | | | HyperEVM | `999` | | | Linea | `59144` | | | Monad | `143` | | | Polygon | `137` | | | Sei | `1329` | | | Sonic | `146` | | | Taiko | `167000` | | | Unichain | `130` | | | World Chain | `480` | | | XDC | `50` | | ## Target Mainnets (Paid Plan Required) The following target chains require a paid Etherscan plan for data endpoints (balances, transactions, logs, etc.). **Lite ($49/mo) is sufficient**; `module=contract` endpoints (`getsourcecode`, `getabi`, etc.) work on every plan. | Chain | Chain ID | | --------- | -------- | | Avalanche | `43114` | | Base | `8453` | | BNB Chain | `56` | | Optimism | `10` | ## Target Mainnets Not on Etherscan V2 Route these target chains to Blockscout when available, then to the `primaryPublicRpc` from `references/generated/target-mainnets.json` if needed. Do not query Etherscan V2 for them. | Chain | Chain ID | Notes | | --------------- | --------- | ------------------------------------------------------------------------------ | | Arbitrum Nova | `42170` | Not returned by the live chainlist; `nova.arbiscan.io` redirects to Blockscout | | Chiliz | `88888` | Not returned by the live chainlist | | Core Dao | `1116` | Not returned by the live chainlist | | Fantom | `250` | Not returned by the live chainlist | | Filecoin | `314` | Not returned by the live chainlist | | IoTeX | `4689` | Not returned by the live chainlist | | Lightlink | `1890` | Not returned by the live chainlist | | Mode | `34443` | Not returned by the live chainlist | | Morph | `2818` | Not returned by the live chainlist | | Robinhood Chain | `4663` | Not returned by the live chainlist | | Ronin | `2020` | Not returned by the live chainlist | | Scroll | `534352` | Removed from Etherscan V2 on 2026-04-16; api.scrollscan.com no longer resolves | | Sophon | `50104` | Not returned by the live chainlist | | Superseed | `5330` | Not returned by the live chainlist | | ZKsync Era | `324` | Not currently supported by Etherscan V2 | | Zora | `7777777` | Not returned by the live chainlist | Testnets are outside this skill's target list. If the user asks about any non-target chain, ask them to file a feature request in <https://github.com/PaulRBerg/agent-skills>. -
target-fallback-rpcs.json 7 KB
{ "chains": [ { "chainName": "Abstract", "chainId": 2741, "fallbackPublicRpcs": ["https://abstract.drpc.org", "https://2741.rpc.thirdweb.com"] }, { "chainName": "Arbitrum", "chainId": 42161, "fallbackPublicRpcs": [ "https://arbitrum-one-rpc.publicnode.com", "https://42161.rpc.thirdweb.com", "https://rpcfree.com/arbitrum-rpc" ] }, { "chainName": "Arbitrum Nova", "chainId": 42170, "fallbackPublicRpcs": [ "https://arbitrum-nova-rpc.publicnode.com", "https://42170.rpc.thirdweb.com", "https://arbitrum-nova.drpc.org" ] }, { "chainName": "Avalanche", "chainId": 43114, "fallbackPublicRpcs": ["https://avalanche-c-chain-rpc.publicnode.com", "https://43114.rpc.thirdweb.com"] }, { "chainName": "Base", "chainId": 8453, "fallbackPublicRpcs": [ "https://base-rpc.publicnode.com", "https://base.gateway.tenderly.co", "https://developer-access-mainnet.base.org" ] }, { "chainName": "Berachain", "chainId": 80094, "fallbackPublicRpcs": [ "https://berachain-rpc.publicnode.com", "https://80094.rpc.thirdweb.com", "https://rpc.berachain-apis.com" ] }, { "chainName": "Blast", "chainId": 81457, "fallbackPublicRpcs": [ "https://blast-rpc.publicnode.com", "https://blast.drpc.org", "https://81457.rpc.thirdweb.com" ] }, { "chainName": "BNB Chain", "chainId": 56, "fallbackPublicRpcs": ["https://bsc-rpc.publicnode.com", "https://bsc.drpc.org", "https://56.rpc.thirdweb.com"] }, { "chainName": "Celo", "chainId": 42220, "fallbackPublicRpcs": [ "https://celo.drpc.org", "https://celo-rpc.publicnode.com", "https://42220.rpc.thirdweb.com" ] }, { "chainName": "Chiliz", "chainId": 88888, "fallbackPublicRpcs": [ "https://chiliz.publicnode.com", "https://88888.rpc.thirdweb.com", "https://rpc.ankr.com/chiliz" ] }, { "chainName": "Core Dao", "chainId": 1116, "fallbackPublicRpcs": [ "https://core.drpc.org", "https://1116.rpc.thirdweb.com", "https://rpc-core.icecreamswap.com" ] }, { "chainName": "Ethereum", "chainId": 1, "fallbackPublicRpcs": ["https://eth.drpc.org", "https://rpc.flashbots.net", "https://1.rpc.thirdweb.com"] }, { "chainName": "Fantom", "chainId": 250, "fallbackPublicRpcs": ["https://rpc2.fantom.network"] }, { "chainName": "Filecoin", "chainId": 314, "fallbackPublicRpcs": ["https://314.rpc.thirdweb.com"] }, { "chainName": "Fraxtal", "chainId": 252, "fallbackPublicRpcs": [ "https://fraxtal-rpc.publicnode.com", "https://fraxtal.drpc.org", "https://fraxtal.gateway.tenderly.co" ] }, { "chainName": "Gnosis", "chainId": 100, "fallbackPublicRpcs": [ "https://gnosis-rpc.publicnode.com", "https://gnosis.drpc.org", "https://100.rpc.thirdweb.com" ] }, { "chainName": "HyperEVM", "chainId": 999, "fallbackPublicRpcs": [ "https://hyperliquid.drpc.org", "https://999.rpc.thirdweb.com", "https://gwan-ssl.wandevs.org:46891" ] }, { "chainName": "IoTeX", "chainId": 4689, "fallbackPublicRpcs": ["https://4689.rpc.thirdweb.com"] }, { "chainName": "Lightlink", "chainId": 1890, "fallbackPublicRpcs": ["https://1890.rpc.thirdweb.com"] }, { "chainName": "Linea", "chainId": 59144, "fallbackPublicRpcs": ["https://linea-rpc.publicnode.com", "https://59144.rpc.thirdweb.com"] }, { "chainName": "Mode", "chainId": 34443, "fallbackPublicRpcs": ["https://mode.drpc.org", "https://34443.rpc.thirdweb.com"] }, { "chainName": "Monad", "chainId": 143, "fallbackPublicRpcs": ["https://monad.drpc.org", "https://143.rpc.thirdweb.com"] }, { "chainName": "Morph", "chainId": 2818, "fallbackPublicRpcs": [ "https://morph.drpc.org", "https://2818.rpc.thirdweb.com", "https://rpc-quicknode.morphl2.io" ] }, { "chainName": "Optimism", "chainId": 10, "fallbackPublicRpcs": [ "https://optimism-rpc.publicnode.com", "https://optimism.drpc.org", "https://10.rpc.thirdweb.com" ] }, { "chainName": "Polygon", "chainId": 137, "fallbackPublicRpcs": [ "https://polygon.drpc.org", "https://137.rpc.thirdweb.com", "https://rpc-mainnet.matic.quiknode.pro" ] }, { "chainName": "Robinhood Chain", "chainId": 4663, "fallbackPublicRpcs": ["https://robinhoodchain.blockscout.com/api/eth-rpc"] }, { "chainName": "Ronin", "chainId": 2020, "fallbackPublicRpcs": ["https://ronin.drpc.org", "https://2020.rpc.thirdweb.com"] }, { "chainName": "Scroll", "chainId": 534352, "fallbackPublicRpcs": ["https://scroll.drpc.org", "https://534352.rpc.thirdweb.com"] }, { "chainName": "Sei", "chainId": 1329, "fallbackPublicRpcs": ["https://sei.drpc.org", "https://1329.rpc.thirdweb.com"] }, { "chainName": "Sonic", "chainId": 146, "fallbackPublicRpcs": [ "https://sonic-rpc.publicnode.com", "https://sonic.drpc.org", "https://146.rpc.thirdweb.com" ] }, { "chainName": "Sophon", "chainId": 50104, "fallbackPublicRpcs": ["https://50104.rpc.thirdweb.com"] }, { "chainName": "Superseed", "chainId": 5330, "fallbackPublicRpcs": ["https://superseed.drpc.org", "https://5330.rpc.thirdweb.com"] }, { "chainName": "Taiko", "chainId": 167000, "fallbackPublicRpcs": [ "https://taiko-rpc.publicnode.com", "https://taiko.drpc.org", "https://167000.rpc.thirdweb.com" ] }, { "chainName": "Unichain", "chainId": 130, "fallbackPublicRpcs": ["https://unichain-rpc.publicnode.com", "https://130.rpc.thirdweb.com"] }, { "chainName": "World Chain", "chainId": 480, "fallbackPublicRpcs": [ "https://worldchain.drpc.org", "https://480.rpc.thirdweb.com", "https://worldchain-mainnet.gateway.tenderly.co" ] }, { "chainName": "XDC", "chainId": 50, "fallbackPublicRpcs": ["https://50.rpc.thirdweb.com", "https://erpc.xdcrpc.com", "https://rpc.xdc.org"] }, { "chainName": "ZKsync Era", "chainId": 324, "fallbackPublicRpcs": ["https://zksync.drpc.org", "https://324.rpc.thirdweb.com"] }, { "chainName": "Zora", "chainId": 7777777, "fallbackPublicRpcs": ["https://7777777.rpc.thirdweb.com", "https://rpc.zora.energy"] } ] } -
target-mainnets.json 17.5 KB
{ "chains": [ { "chainName": "Abstract", "chainId": 2741, "slug": "abstract", "category": "zk", "accountActivityModel": "native-account-abstraction", "primaryPublicRpc": "https://api.mainnet.abs.xyz", "nativeCurrencySymbol": "ETH", "explorerUrl": "https://abscan.org", "explorerAddressUrl": "https://abscan.org/address/{address}", "explorerTxUrl": "https://abscan.org/tx/{tx_hash}", "routeMesh": true }, { "chainName": "Arbitrum", "chainId": 42161, "slug": "arbitrum", "category": "nitro", "accountActivityModel": "ethereum-eoa", "primaryPublicRpc": "https://arb1.arbitrum.io/rpc", "nativeCurrencySymbol": "ETH", "explorerUrl": "https://arbiscan.io", "explorerAddressUrl": "https://arbiscan.io/address/{address}", "explorerTxUrl": "https://arbiscan.io/tx/{tx_hash}", "routeMesh": true }, { "chainName": "Arbitrum Nova", "chainId": 42170, "slug": "arbitrum-nova", "category": "nitro", "accountActivityModel": "ethereum-eoa", "primaryPublicRpc": "https://nova.arbitrum.io/rpc", "nativeCurrencySymbol": "ETH", "explorerUrl": "https://arbitrum-nova.blockscout.com", "explorerAddressUrl": "https://arbitrum-nova.blockscout.com/address/{address}", "explorerTxUrl": "https://arbitrum-nova.blockscout.com/tx/{tx_hash}", "routeMesh": true }, { "chainName": "Avalanche", "chainId": 43114, "slug": "avalanche", "category": "alt-l1", "accountActivityModel": "ethereum-eoa", "primaryPublicRpc": "https://api.avax.network/ext/bc/C/rpc", "nativeCurrencySymbol": "AVAX", "explorerUrl": "https://snowscan.xyz", "explorerAddressUrl": "https://snowscan.xyz/address/{address}", "explorerTxUrl": "https://snowscan.xyz/tx/{tx_hash}", "routeMesh": true }, { "chainName": "Base", "chainId": 8453, "slug": "base", "category": "op-stack", "accountActivityModel": "ethereum-eoa", "primaryPublicRpc": "https://mainnet.base.org", "nativeCurrencySymbol": "ETH", "explorerUrl": "https://basescan.org", "explorerAddressUrl": "https://basescan.org/address/{address}", "explorerTxUrl": "https://basescan.org/tx/{tx_hash}", "routeMesh": true }, { "chainName": "Berachain", "chainId": 80094, "slug": "berachain", "category": "alt-l1", "accountActivityModel": "ethereum-eoa", "primaryPublicRpc": "https://rpc.berachain.com", "nativeCurrencySymbol": "BERA", "explorerUrl": "https://berascan.com", "explorerAddressUrl": "https://berascan.com/address/{address}", "explorerTxUrl": "https://berascan.com/tx/{tx_hash}", "routeMesh": true }, { "chainName": "Blast", "chainId": 81457, "slug": "blast", "category": "op-stack", "accountActivityModel": "ethereum-eoa", "primaryPublicRpc": "https://rpc.blast.io", "nativeCurrencySymbol": "ETH", "explorerUrl": "https://blastscan.io", "explorerAddressUrl": "https://blastscan.io/address/{address}", "explorerTxUrl": "https://blastscan.io/tx/{tx_hash}", "routeMesh": true }, { "chainName": "BNB Chain", "chainId": 56, "slug": "bsc", "category": "alt-l1", "accountActivityModel": "ethereum-eoa", "primaryPublicRpc": "https://bsc-dataseed1.bnbchain.org", "nativeCurrencySymbol": "BNB", "explorerUrl": "https://bscscan.com", "explorerAddressUrl": "https://bscscan.com/address/{address}", "explorerTxUrl": "https://bscscan.com/tx/{tx_hash}", "routeMesh": true }, { "chainName": "Celo", "chainId": 42220, "slug": "celo", "category": "op-stack", "accountActivityModel": "ethereum-eoa", "primaryPublicRpc": "https://forno.celo.org", "nativeCurrencySymbol": "CELO", "explorerUrl": "https://celoscan.io", "explorerAddressUrl": "https://celoscan.io/address/{address}", "explorerTxUrl": "https://celoscan.io/tx/{tx_hash}", "routeMesh": true }, { "chainName": "Chiliz", "chainId": 88888, "slug": "chiliz", "category": "alt-l1", "accountActivityModel": "ethereum-eoa", "primaryPublicRpc": "https://rpc.chiliz.com", "nativeCurrencySymbol": "CHZ", "explorerUrl": "https://chiliscan.com", "explorerAddressUrl": "https://chiliscan.com/address/{address}", "explorerTxUrl": "https://chiliscan.com/tx/{tx_hash}", "routeMesh": true }, { "chainName": "Core Dao", "chainId": 1116, "slug": "core-dao", "category": "alt-l1", "accountActivityModel": "ethereum-eoa", "primaryPublicRpc": "https://rpc.coredao.org", "nativeCurrencySymbol": "CORE", "explorerUrl": "https://scan.coredao.org", "explorerAddressUrl": "https://scan.coredao.org/address/{address}", "explorerTxUrl": "https://scan.coredao.org/tx/{tx_hash}", "routeMesh": true }, { "chainName": "Ethereum", "chainId": 1, "slug": "mainnet", "category": "mainnet", "accountActivityModel": "ethereum-eoa", "primaryPublicRpc": "https://ethereum-rpc.publicnode.com", "nativeCurrencySymbol": "ETH", "explorerUrl": "https://etherscan.io", "explorerAddressUrl": "https://etherscan.io/address/{address}", "explorerTxUrl": "https://etherscan.io/tx/{tx_hash}", "routeMesh": true }, { "chainName": "Fantom", "chainId": 250, "slug": "fantom", "category": "alt-l1", "accountActivityModel": "ethereum-eoa", "primaryPublicRpc": "https://rpc.fantom.network", "nativeCurrencySymbol": "FTM", "explorerUrl": "https://explorer.fantom.network", "explorerAddressUrl": "https://explorer.fantom.network/address/{address}", "explorerTxUrl": "https://explorer.fantom.network/tx/{tx_hash}", "routeMesh": true }, { "chainName": "Filecoin", "chainId": 314, "slug": "filecoin", "category": "alt-l1", "accountActivityModel": "cross-vm", "primaryPublicRpc": "https://api.node.glif.io/rpc/v1", "nativeCurrencySymbol": "FIL", "explorerUrl": "https://filecoin.blockscout.com", "explorerAddressUrl": "https://filecoin.blockscout.com/address/{address}", "explorerTxUrl": "https://filecoin.blockscout.com/tx/{tx_hash}", "routeMesh": true }, { "chainName": "Fraxtal", "chainId": 252, "slug": "fraxtal", "category": "op-stack", "accountActivityModel": "ethereum-eoa", "primaryPublicRpc": "https://rpc.frax.com", "nativeCurrencySymbol": "FRAX", "explorerUrl": "https://fraxscan.com", "explorerAddressUrl": "https://fraxscan.com/address/{address}", "explorerTxUrl": "https://fraxscan.com/tx/{tx_hash}", "routeMesh": true }, { "chainName": "Gnosis", "chainId": 100, "slug": "gnosis", "category": "alt-l1", "accountActivityModel": "ethereum-eoa", "primaryPublicRpc": "https://rpc.gnosischain.com", "nativeCurrencySymbol": "xDAI", "explorerUrl": "https://gnosisscan.io", "explorerAddressUrl": "https://gnosisscan.io/address/{address}", "explorerTxUrl": "https://gnosisscan.io/tx/{tx_hash}", "routeMesh": true }, { "chainName": "HyperEVM", "chainId": 999, "slug": "hyperevm", "category": "alt-l1", "accountActivityModel": "cross-vm", "primaryPublicRpc": "https://rpc.hyperliquid.xyz/evm", "nativeCurrencySymbol": "HYPE", "explorerUrl": "https://hyperevmscan.io", "explorerAddressUrl": "https://hyperevmscan.io/address/{address}", "explorerTxUrl": "https://hyperevmscan.io/tx/{tx_hash}", "routeMesh": true }, { "chainName": "IoTeX", "chainId": 4689, "slug": "iotex", "category": "alt-l1", "accountActivityModel": "ethereum-eoa", "primaryPublicRpc": "https://babel-api.mainnet.iotex.io", "nativeCurrencySymbol": "IOTX", "explorerUrl": "https://iotexscan.io", "explorerAddressUrl": "https://iotexscan.io/address/{address}", "explorerTxUrl": "https://iotexscan.io/tx/{tx_hash}", "routeMesh": true }, { "chainName": "Lightlink", "chainId": 1890, "slug": "lightlink", "category": "alt-l2", "accountActivityModel": "ethereum-eoa", "primaryPublicRpc": "https://replicator.phoenix.lightlink.io/rpc/v1", "nativeCurrencySymbol": "ETH", "explorerUrl": "https://phoenix.lightlink.io", "explorerAddressUrl": "https://phoenix.lightlink.io/address/{address}", "explorerTxUrl": "https://phoenix.lightlink.io/tx/{tx_hash}", "routeMesh": true }, { "chainName": "Linea", "chainId": 59144, "slug": "linea", "category": "zk", "accountActivityModel": "ethereum-eoa", "primaryPublicRpc": "https://rpc.linea.build", "nativeCurrencySymbol": "ETH", "explorerUrl": "https://lineascan.build", "explorerAddressUrl": "https://lineascan.build/address/{address}", "explorerTxUrl": "https://lineascan.build/tx/{tx_hash}", "routeMesh": true }, { "chainName": "Mode", "chainId": 34443, "slug": "mode", "category": "op-stack", "accountActivityModel": "ethereum-eoa", "primaryPublicRpc": "https://mainnet.mode.network", "nativeCurrencySymbol": "ETH", "explorerUrl": "https://explorer.mode.network", "explorerAddressUrl": "https://explorer.mode.network/address/{address}", "explorerTxUrl": "https://explorer.mode.network/tx/{tx_hash}", "routeMesh": true }, { "chainName": "Monad", "chainId": 143, "slug": "monad", "category": "alt-l1", "accountActivityModel": "ethereum-eoa", "primaryPublicRpc": "https://rpc.monad.xyz", "nativeCurrencySymbol": "MON", "explorerUrl": "https://monadscan.com", "explorerAddressUrl": "https://monadscan.com/address/{address}", "explorerTxUrl": "https://monadscan.com/tx/{tx_hash}", "routeMesh": true }, { "chainName": "Morph", "chainId": 2818, "slug": "morph", "category": "zk", "accountActivityModel": "ethereum-eoa", "primaryPublicRpc": "https://rpc.morphl2.io", "nativeCurrencySymbol": "ETH", "explorerUrl": "https://explorer.morph.network", "explorerAddressUrl": "https://explorer.morph.network/address/{address}", "explorerApiUrl": "https://explorer-api.morph.network/api", "explorerTxUrl": "https://explorer.morph.network/tx/{tx_hash}", "routeMesh": true }, { "chainName": "Optimism", "chainId": 10, "slug": "optimism", "category": "op-stack", "accountActivityModel": "ethereum-eoa", "primaryPublicRpc": "https://mainnet.optimism.io", "nativeCurrencySymbol": "ETH", "explorerUrl": "https://optimistic.etherscan.io", "explorerAddressUrl": "https://optimistic.etherscan.io/address/{address}", "explorerTxUrl": "https://optimistic.etherscan.io/tx/{tx_hash}", "routeMesh": true }, { "chainName": "Polygon", "chainId": 137, "slug": "polygon", "category": "alt-l1", "accountActivityModel": "ethereum-eoa", "primaryPublicRpc": "https://polygon-bor-rpc.publicnode.com", "nativeCurrencySymbol": "POL", "explorerUrl": "https://polygonscan.com", "explorerAddressUrl": "https://polygonscan.com/address/{address}", "explorerTxUrl": "https://polygonscan.com/tx/{tx_hash}", "routeMesh": true }, { "chainName": "Robinhood Chain", "chainId": 4663, "slug": "robinhood", "category": "nitro", "accountActivityModel": "ethereum-eoa", "primaryPublicRpc": "https://rpc.mainnet.chain.robinhood.com", "nativeCurrencySymbol": "ETH", "explorerUrl": "https://robinhoodchain.blockscout.com", "explorerAddressUrl": "https://robinhoodchain.blockscout.com/address/{address}", "explorerTxUrl": "https://robinhoodchain.blockscout.com/tx/{tx_hash}", "routeMesh": true }, { "chainName": "Ronin", "chainId": 2020, "slug": "ronin", "category": "op-stack", "accountActivityModel": "ethereum-eoa", "primaryPublicRpc": "https://api.roninchain.com/rpc", "nativeCurrencySymbol": "RON", "explorerUrl": "https://app.roninchain.com", "explorerAddressUrl": "https://app.roninchain.com/address/{address}", "explorerTxUrl": "https://app.roninchain.com/tx/{tx_hash}", "routeMesh": true }, { "chainName": "Scroll", "chainId": 534352, "slug": "scroll", "category": "zk", "accountActivityModel": "ethereum-eoa", "primaryPublicRpc": "https://rpc.scroll.io", "nativeCurrencySymbol": "ETH", "explorerUrl": "https://scrollscan.com", "explorerAddressUrl": "https://scrollscan.com/address/{address}", "explorerTxUrl": "https://scrollscan.com/tx/{tx_hash}", "routeMesh": true }, { "chainName": "Sei", "chainId": 1329, "slug": "sei", "category": "alt-l1", "accountActivityModel": "cross-vm", "primaryPublicRpc": "https://evm-rpc.sei-apis.com", "nativeCurrencySymbol": "SEI", "explorerUrl": "https://seiscan.io", "explorerAddressUrl": "https://seiscan.io/address/{address}", "explorerTxUrl": "https://seiscan.io/tx/{tx_hash}", "routeMesh": true }, { "chainName": "Sonic", "chainId": 146, "slug": "sonic", "category": "alt-l1", "accountActivityModel": "ethereum-eoa", "primaryPublicRpc": "https://rpc.soniclabs.com", "nativeCurrencySymbol": "S", "explorerUrl": "https://sonicscan.org", "explorerAddressUrl": "https://sonicscan.org/address/{address}", "explorerTxUrl": "https://sonicscan.org/tx/{tx_hash}", "routeMesh": true }, { "chainName": "Sophon", "chainId": 50104, "slug": "sophon", "category": "zk", "accountActivityModel": "native-account-abstraction", "primaryPublicRpc": "https://rpc.sophon.xyz", "nativeCurrencySymbol": "SOPH", "explorerUrl": "https://explorer.sophon.xyz", "explorerAddressUrl": "https://explorer.sophon.xyz/address/{address}", "explorerTxUrl": "https://explorer.sophon.xyz/tx/{tx_hash}", "routeMesh": true }, { "chainName": "Superseed", "chainId": 5330, "slug": "superseed", "category": "op-stack", "accountActivityModel": "ethereum-eoa", "primaryPublicRpc": "https://mainnet.superseed.xyz", "nativeCurrencySymbol": "ETH", "explorerUrl": "https://explorer.superseed.xyz", "explorerAddressUrl": "https://explorer.superseed.xyz/address/{address}", "explorerTxUrl": "https://explorer.superseed.xyz/tx/{tx_hash}", "routeMesh": true }, { "chainName": "Taiko", "chainId": 167000, "slug": "taiko", "category": "zk", "accountActivityModel": "ethereum-eoa", "primaryPublicRpc": "https://rpc.mainnet.taiko.xyz", "nativeCurrencySymbol": "ETH", "explorerUrl": "https://taikoscan.io", "explorerAddressUrl": "https://taikoscan.io/address/{address}", "explorerTxUrl": "https://taikoscan.io/tx/{tx_hash}", "routeMesh": true }, { "chainName": "Unichain", "chainId": 130, "slug": "unichain", "category": "op-stack", "accountActivityModel": "ethereum-eoa", "primaryPublicRpc": "https://mainnet.unichain.org", "nativeCurrencySymbol": "ETH", "explorerUrl": "https://uniscan.xyz", "explorerAddressUrl": "https://uniscan.xyz/address/{address}", "explorerTxUrl": "https://uniscan.xyz/tx/{tx_hash}", "routeMesh": true }, { "chainName": "World Chain", "chainId": 480, "slug": "world-chain", "category": "op-stack", "accountActivityModel": "ethereum-eoa", "primaryPublicRpc": "https://worldchain-mainnet.g.alchemy.com/public", "nativeCurrencySymbol": "ETH", "explorerUrl": "https://worldscan.org", "explorerAddressUrl": "https://worldscan.org/address/{address}", "explorerTxUrl": "https://worldscan.org/tx/{tx_hash}", "routeMesh": true }, { "chainName": "XDC", "chainId": 50, "slug": "xdc", "category": "alt-l1", "accountActivityModel": "ethereum-eoa", "primaryPublicRpc": "https://rpc.xdcrpc.com", "nativeCurrencySymbol": "XDC", "explorerUrl": "https://xdcscan.com", "explorerAddressUrl": "https://xdcscan.com/address/{address}", "explorerTxUrl": "https://xdcscan.com/tx/{tx_hash}", "routeMesh": true }, { "chainName": "ZKsync Era", "chainId": 324, "slug": "zksync", "category": "zk", "accountActivityModel": "native-account-abstraction", "primaryPublicRpc": "https://mainnet.era.zksync.io", "nativeCurrencySymbol": "ETH", "explorerUrl": "https://explorer.zksync.io", "explorerAddressUrl": "https://explorer.zksync.io/address/{address}", "explorerTxUrl": "https://explorer.zksync.io/tx/{tx_hash}", "routeMesh": true }, { "chainName": "Zora", "chainId": 7777777, "slug": "zora", "category": "op-stack", "accountActivityModel": "ethereum-eoa", "primaryPublicRpc": "https://zora.drpc.org", "nativeCurrencySymbol": "ETH", "explorerUrl": "https://explorer.zora.energy", "explorerAddressUrl": "https://app.zerion.io/{address}/history?chain=zora", "explorerTxUrl": "https://explorer.zora.energy/tx/{tx_hash}", "routeMesh": true } ] }
-
-
workflows
-
address-sweeps.md 6.7 KB
# Address Sweeps Use for address-wide historical activity and bootstrap discovery on target mainnets. The deterministic request/evidence rules live in `scripts/sweep-core.py`; this reference defines safety, agent decisions, and coverage interpretation. ## Safety Model - Accept only a 20-byte public EVM address. Never read or expose keys, mnemonics, or credentials. - Query only rows in `references/generated/target-mainnets.json`. A `cross-vm` result covers that chain's EVM execution environment only. - Fix one ISO-8601 UTC cutoff and one finalized or independently verified block per target chain. Record requested cutoff, resolution kind, chain ID, block number/hash/timestamp, and observation time. The checkpoint block must be at or before the cutoff. Reuse its exact hash and height for state and indexed history. - Never turn `latest`, an explorer page count, a provider error, an unbound zero counter, or a lagging index into a negative result. - Keep credentials out of plan/response artifacts. Supply them only to the agent-selected read-only provider command. If a checkpoint cannot be established, or required indexed channels cannot cover it, the result is unknown/partial. ## Profiles Name the profile before planning: - `general`: normal transactions, internal transactions, ERC-20, ERC-721, and ERC-1155 transfers, plus checkpointed nonce/native balance facts. This is the default historical-activity profile. - `bootstrap-discovery`: a narrower activity-discovery profile. It includes checkpointed native state, qualifying normal/internal native activity, ERC-20, and ERC-721; ERC-1155 and nonqualifying native noise are outside it. The helper owns the exact success/noise predicates. Do not silently apply bootstrap exclusions to a general sweep. A `bootstrap-discovery` negative means only “no activity qualifying under the `bootstrap-discovery` profile was found.” For the `bootstrap-discovery` profile, the helper may omit normal/internal history only when checkpointed nonce and balance are both zero and `accountActivityModel` is exactly `ethereum-eoa`. It default-denies this shortcut for missing, unrecognized, native-account-abstraction, cross-VM, or unknown models. Token history remains required. ## Agent Decisions Before Planning The agent owns: - target-chain selection and whether to stop after the first positive; - cutoff acquisition and checkpoint proof; - provider selection and fallback activation using `provider-routing.md`; - declared provider capabilities and independence groups; - optional quorum requirement; - final observed-fact versus inference wording. Prefer Blockscout on declared overlaps; use Etherscan where it is primary or after a concrete fallback trigger. A valid empty authoritative response does not trigger fallback. Run the existing plan-detection helpers before using paid Etherscan or Blockscout PRO capabilities. Quorum counts independent indexers, not hosts or state RPCs; provider disagreement is unknown, never a majority decision. ## Plan Interface Create an agent-selected input JSON with: - `address`; - `chain`: at least `id`, display name, and `accountActivityModel`; - `goal`: `historical-activity` or `bootstrap-discovery`; - named `profile`; - `checkpoint`: `requestedAt`, `resolutionKind`, `blockNumber`, `blockHash`, `blockTimestamp`, `observedAt`; - `providers`: stable ID, provider kind, independent-index group, and supported history-channel names; - `quorum`: positive integer, default `1`. Then run: ```sh uv run scripts/sweep-core.py plan --input <spec.json> > <plan.json> ``` The credential-free plan has `schemaVersion: 1`, normalized required channels, checkpoint-bound JSON-RPC state requests, bounded indexed-history request specifications, and quorum ordering requirements. It does not acquire a checkpoint, choose a provider, or construct credential-bearing URLs. Execute only the emitted requests through the selected provider's documented adapter. Save responses in this shape: ```json { "state": { "nonce": { "ok": true, "result": "0x0", "blockHash": "<checkpoint-hash>" }, "native-balance": { "ok": true, "result": "0x0", "blockHash": "<checkpoint-hash>" } }, "providers": { "<provider-id>": { "indexedThrough": 123, "channels": { "txlist": { "ok": true, "complete": true, "rows": [] } } } } } ``` `complete: true` means bounded pagination for that channel is exhausted through the checkpoint. Preserve native provider row fields; do not preclassify them. ## Evaluate Interface ```sh uv run scripts/sweep-core.py evaluate \ --plan <plan.json> --responses <responses.json> > <result.json> ``` The evaluator validates checkpoint binding, quantities, index bounds, response shapes, required channels, profile predicates, EOA zero-state eligibility, earliest qualifying evidence, and quorum agreement. It returns `positive|negative|unknown`, `checked`, `omitted`, explicit `gaps`, coverage, earliest evidence, and provider/quorum facts. Malformed external responses become coverage gaps when they can be isolated; malformed plan input fails. For offline conformance checks, use: ```sh uv run scripts/sweep-core.py validate blockscout-address-counters < response.json uv run scripts/sweep-core.py validate etherscan-transfer-topics --address <address> < response.json ``` The existing shell validator commands remain output-compatible adapters. ## Coverage and Reporting A positive needs qualifying checkpoint-bounded evidence; it may still have partial channel coverage. A negative is valid only when every profile channel is checkpoint-bound and complete or appears in `omitted` under the named safe invariant. Missing channels, lag, malformed responses, provider disagreement, and unsupported actions make the result unknown/partial. For yes/no requests, lead with the result and first qualifying chain/action. For reports, include chain, checkpoint, profile, activity result, checked/omitted channels, coverage, provider, fallback, and quorum. Express checkpoints as at least `chain_id:block_number:block_hash` plus cutoff time. Do not claim inactivity on all EVM chains unless the exact target scope and general profile are complete. For `cross-vm`, append “in the EVM execution environment.” Keep observed provider facts separate from inference. ## Current Holdings For current native/token holdings, use `debank-portfolio.md` first (`blockscan-balances.md` for a named chain or DeBank gaps) and `provider-routing.md` for NFTs and API gaps. Pin native state to a finalized/verified checkpoint. Treat holdings endpoints observed at a provider head as separately timed evidence, and never infer token/NFT emptiness from native RPC alone. Indexed token amounts can be stale: confirm any amount that decides an action with RPC `balanceOf`, as `address-usd-value.md` does. -
address-usd-value.md 8.8 KB
# Address USD Value Use this reference for the current USD value of one or more public EVM addresses per target chain and in total: native balances plus fungible ERC-20 tokens. NFTs and historical values are out of scope; route them to `references/workflows/provider-routing.md`. DeFi positions stay out of every total; list them separately from `references/workflows/debank-portfolio.md` when requested. All steps are read-only. Callers own any threshold (for example, a "drained" or dust cutoff) and any token allow/deny policy; this workflow only returns values and coverage. ## Scope - Validate every address (20-byte hex). Use `<addr>` placeholders in saved specs and examples. - Check native balances on every row of `references/generated/target-mainnets.json`; native reads are cheap. A caller may narrow token lookups to a chain subset; report unchecked chains as out of the requested token scope, not as zero. - For `cross-vm` rows, scope values to the chain's EVM execution environment. - Before keyed API calls, check presence value-free: `[ -n "$BLOCKSCOUT_API_KEY" ] && echo set || echo unset`. Without the key, skip the Blockscout discovery route and fall through to DeBank. ## Native Balances 1. Per chain, pin one block: `routemesh rpc <chainId> eth_getBlockByNumber --params='["finalized",false]'` and record its tag, number, hash, and timestamp. Use `"latest"` when the chain rejects `finalized` or returns block `0x0` for it, and say so: Chiliz (`88888`) nodes return genesis for both `finalized` and `safe` on RouteMesh and every listed public RPC, including `rpc.chiliz.com` (verified 2026-10-01). For a post-transfer check, when the finalized head predates the caller's verified receipt block, use a canonical `"latest"` checkpoint at or after that receipt block; record the reason and label it unfinalized. Never use pre-transfer state as the post-transfer balance. 2. Send one `routemesh rpc <chainId> --json -` batch with an `eth_getBalance` request per address, each using the EIP-1898 `{ "blockHash": "<hash>", "requireCanonical": true }` selector. Apply the numeric-block fallback and same-endpoint block-identity checks from `provider-routing.md` when a provider rejects that selector. 3. For a row without RouteMesh HTTP coverage, or after a RouteMesh coverage failure, follow the `primaryPublicRpc` then `references/generated/target-fallback-rpcs.json` order in `provider-routing.md`. A failed native read is a gap, never zero. 4. Convert wei to native units with fixed-precision decimal arithmetic (`bc` with `scale=18`, or Python `decimal`). Never use binary floats for amounts, prices, or products. ## Prices - Price natives with one `cg price --ids <id,...> -o json` call covering every distinct native asset (ETH-native chains share one ID). Resolve unknown CoinGecko IDs once with `cg search <symbol-or-name> -o json`; never treat a symbol as unique. - Price each confirmed token holding by contract from CoinGecko. Resolve each chain's platform once from `https://api.coingecko.com/api/v3/asset_platforms`, matching `chain_identifier` to the chain ID. Map contracts to coin IDs with one keyless `https://api.coingecko.com/api/v3/coins/list?include_platform=true` download (match the platform and lowercased contract), then price every mapped ID in one `cg price --ids <id,...> -o json` call. - For a contract the coin list does not map, request `https://api.coingecko.com/api/v3/simple/token_price/<platform>?contract_addresses=<contract>&vs_currencies=usd`. The keyless endpoint accepts one contract per request, so query only holdings with a nonzero confirmed balance and pace requests. It can also quote contracts CoinGecko does not list (`coins/<platform>/contract/<contract>` returns `coin not found`), including a dead token quoted at hundreds of dollars; treat such a quote as unlisted, count the token as unpriced, and report the quote separately. - Use an indexer price (Blockscout `exchange_rate` on `addresses/<addr>` for natives or on a token holding, a DeBank or Blockscan row price) only when CoinGecko omits the native asset, has no platform for the chain, or has no price for the contract. Label that price with its source and apply Pricing Hygiene; Blockscout has priced tokens it lists with a zero market cap. - Record each price's source and the UTC observation time. ## Fungible Tokens Indexed holdings can be stale (a Blockscout list has shown a USDT balance whose on-chain `balanceOf` was zero), so indexers only discover token contracts: amounts come from RPC and prices from CoinGecko. Per chain and address, take the union of these discovery sources: 1. **Caller candidates.** Always include contracts the caller supplies, such as tokens from its own transfer history. 2. **Blockscout.** For targets the keyed gateway serves (see `references/generated/blockscout-chains.md` and `references/explorers/blockscout-api.md` for the per-instance exception), page `https://api.blockscout.com/<chainId>/api/v2/addresses/<addr>/tokens?type=ERC-20` until `next_page_params` is `null`. Keep each holding's `token.address_hash` and `decimals`, plus `exchange_rate` as a fallback price. An HTTP `402` (plan-gated chain; see `references/explorers/blockscout-endpoints.md`) or other failure falls through to DeBank; do not retry a `402`. 3. **DeBank.** For target chains Blockscout does not cover, gates, or fails on, run the collector per `references/workflows/debank-portfolio.md` (token discovery for one address or many; no `Show all` click). From each `ok` record take the token contracts per target chain ID (`chainId` is the `chain/list` `network_id`), and the price as a fallback, and record its `observedAt`. A `failed` record is a discovery gap for that address's DeBank chains. 4. **Blockscan.** For remaining target chains DeBank lacks or fails on, use the Chromium flow in `references/workflows/blockscan-balances.md`: match chains by exact `data-chainid`, take each row's token contract (and price as a fallback) from `#js-chain-table`, and record `Last updated`. When no indexer lists a chain's tokens, report an ERC-20 discovery gap for that chain even if caller candidates were confirmed. Never assume zero tokens. Confirm every discovered holding on-chain: per chain, batch `eth_call` `balanceOf(<addr>)` (selector `0x70a08231`) for each token at the pinned block hash, through the same route as that chain's native reads. Add `decimals()` (`0x313ce567`) when discovery did not supply it. Use the RPC amount: USD value = `balanceOf / 10^decimals × price`. An RPC zero drops the holding; a failed or malformed confirmation is a coverage gap for that token, never the indexed amount. ## Bulk Mode For many addresses, run API passes first: native batches across all target chains, Blockscout token lists, one `balanceOf` confirmation batch per chain, then CoinGecko contract prices for confirmed holdings. Run the DeBank collector for the addresses that still have gap chains, in gated batches as `debank-portfolio.md` directs, then Blockscan one page at a time for what remains. Keep request concurrency at or below each provider's limit (Blockscout `x-ratelimit-limit`; CoinGecko plan quota); back off on `429` as the provider references direct. Never run unbounded parallel requests. ## Pricing Hygiene - A token without a provider price contributes `$0` and is listed as unpriced with its amount and contract. - Flag a priced token as suspicious when its price looks spoofed: an unverified or unknown contract using a major symbol or name, a value implausible for its market (for example, exceeding its market cap or liquidity), or a price that disagrees sharply with CoinGecko for the same asset. List suspicious tokens separately and exclude them from every total. ## Nil and Totals - A chain is `nil` when its native balance is exactly zero and it has no confirmed, priced, non-suspicious token holdings. A chain with a native or ERC-20 coverage gap is never `nil`; report its value as a lower bound or unknown. - The address total sums chain values excluding suspicious tokens. It is `nil` only when every checked chain is `nil` and no gaps remain; with gaps, label it a lower bound. ## Output Lead each address with `### ⛓️ <addr> — <status word>`. Use one table row per chain with value: | Chain (ID) | Native (amount / USD) | Priced tokens (amount / USD) | Chain USD | Source | Block / checkpoint | | ---------- | --------------------- | ---------------------------- | --------- | ------ | ------------------ | Collapse `nil` chains into a count with their chain IDs. Then list the address total, unpriced tokens, suspicious tokens, `⚠️ Coverage gaps` (chain, channel, cause), and price source with UTC timestamp. Show native amounts at full precision without exponent notation. When a caller requests machine-readable output, emit the same fields as JSON with decimal strings for every amount and USD value. -
blockscan-balances.md 3.6 KB
# Blockscan Balances Use this reference for the current native or fungible-token balance of a public EVM wallet address on a named target chain. For a wallet-wide or cross-chain check, use `references/workflows/debank-portfolio.md` first; use Blockscan for target chains DeBank lacks, when DeBank fails, or as an Etherscan-family cross-check. Historical balances and NFT inventories remain on the existing provider routes. For per-chain and total USD value of one or more addresses, use `references/workflows/address-usd-value.md`. ## Chromium Workflow 1. Validate the address, then resolve any named chain against `references/generated/target-mainnets.json`. Do not send a non-target chain to Blockscan. 2. Open `https://blockscan.com/address/<addr>` with Chrome DevTools `new_page`, using Chromium rather than a direct Blockscan API or a general web search. 3. Wait for the address page and `Token Holdings` portfolio to replace any initial `Just a moment...` challenge, then take a current accessibility snapshot. 4. Use the chain card for its current portfolio value and `#js-chain-table` for token amount, price, and value. Select the requested chain before reading the table and paginate when the user requests complete holdings. 5. Capture the page's `Last updated` value when present. Treat displayed amounts and fiat values as Blockscan's current, formatted portfolio data rather than exact raw-unit balances. Use Chrome DevTools `evaluate_script` when the accessibility snapshot does not expose a stable chain identifier. Match support by exact chain ID with: ```text input.address-transaction-chain[data-chainid="<chain-id>"] ``` Its `data-search` value includes the Blockscan chain symbol. Use that symbol to locate the corresponding `.js-chain[data-chain="<symbol>"]` card; do not infer support from a similar display name. ## Coverage and Scope - For a named chain, the exact `data-chainid` match proves Blockscan currently offers that chain. A matching card with a zero token count or `$0.00` is a successful zero result, not a fallback condition. - For a wallet-wide fallback, intersect Blockscan's `data-chainid` values with the target chains still uncovered. Blockscan covers only Etherscan-family chains; query the existing API routes for target chains outside that intersection. - Ignore Blockscan chains outside the target-mainnet list. Do not report the page-wide `NET WORTH` as a target-only total because it can include those chains. - Keep successful Blockscan results when only some target chains or requested details require fallback. ## Fallbacks Use `references/workflows/provider-routing.md` for a named chain and `references/workflows/address-sweeps.md` for a wallet-wide check when: - Chrome DevTools MCP or Chromium is unavailable; - navigation fails, a challenge or error persists, the page is rate limited, or the required portfolio DOM is absent; - the resolved target chain ID is absent from Blockscan's supported-chain inputs; or - Blockscan cannot provide the exact/raw precision or current fungible-asset detail the user requested. Route historical balances and NFT inventory requests directly to those existing references. Report which Blockscan condition caused each fallback, then retain the existing Etherscan, Blockscout, and RPC order within the selected fallback reference. ## Output Return the resolved target chain names and IDs, current native or fungible-token amounts requested, Blockscan's displayed fiat values when relevant, freshness text, and the address-page URL. Identify fallback-derived facts by provider and separate them from Blockscan results. -
blockscan-tx-lookup.md 3.7 KB
# Blockscan Transaction Lookup Use this reference primarily to resolve an exact transaction hash whose chain is unknown, and return Blockscan's formatted status, parties, value, fee, and timestamp when available. A single unknown-chain lookup avoids probing each candidate chain's provider in turn. Blockscan's formatted summary is not raw transaction evidence. ## Route by Chain Context 1. Validate the hash format (`0x` plus 64 hex chars). 2. If the user supplied a chain, resolve it against `references/generated/target-mainnets.json`. Unless the user explicitly requested Blockscan as the evidence source, do not navigate to Blockscan; immediately use `references/workflows/provider-routing.md` for the transaction facts. 3. For a known or suspected OP Mainnet transaction from before the final regenesis, apply `references/explorers/optimism-pre-regenesis.md` before requiring evidence from a current provider. 4. Continue to the Chromium workflow only when the chain remains unknown or the user explicitly requested Blockscan. ## Chromium Workflow 1. Open `https://blockscan.com/tx/<hash>` with Chrome DevTools `new_page`, using Chromium rather than a direct API or a general web search. 2. Wait for the transaction heading to replace any initial `Just a moment...` challenge, then take a current accessibility snapshot. 3. Identify the chain from the `<h1>` heading (`<Chain> Transaction Details`) and the `View on <Explorer>` link's `href`. Cross-check that `href`'s host against `explorerUrl` in `references/generated/target-mainnets.json` for the authoritative chain ID and name — the heading text and chain-logo image alone are display hints, not proof. 4. If the resolved chain has no matching target-mainnet row, stop: this is out of scope per `SKILL.md`. Do not serve data for it and do not fall back to another provider to work around scope. 5. Read status (success/failed/pending), timestamp, `from`/`to`, value, and fee directly from the page for the human-readable answer. ## Coverage and Scope - A rendered `<Chain> Transaction Details` page proves Blockscan currently indexes that hash on that chain. - `Error 404` (`Seems we got lost!`) is not proof the hash doesn't exist. Blockscan aggregates the Etherscan-family (`*scan`) explorers only, so a real transaction on a chain Blockscan doesn't cover, or one not yet indexed, also 404s. Never report "transaction not found" from a 404 alone. - Blockscan's summary fields are formatted/display data, not a substitute for an exact raw receipt. ## Fallbacks After Blockscan resolves the chain, use `references/workflows/provider-routing.md` when: - the request needs the full raw receipt, logs, internal transactions, decoded input, or another detail beyond Blockscan's summary. If the chain is unknown and Blockscan 404s or is unavailable, report the chain as unresolved and stop. Do not infer that the transaction does not exist or sweep every target chain's provider. Ask for a candidate chain or approval for that broader provider sweep. If the user explicitly requested Blockscan for a known chain, attempt it and report its coverage result, including when Chromium is unavailable, navigation fails, a challenge persists, the page is rate limited, or it 404s. Do not silently substitute another evidence source; identify any separately requested fallback facts by provider. ## Output Always return the transaction hash, resolved target chain name and ID when known, selected provider route, requested facts, and evidence source. Include the Blockscan URL and native-explorer URL surfaced by `View on <Explorer>` only when Blockscan was queried. Otherwise, report the provider route and its evidence without implying Blockscan confirmation. -
debank-portfolio.md 12.2 KB
# DeBank Portfolio Use this reference for the wallet-wide current holdings of a public EVM address across target chains: native and fungible-token balances plus DeFi protocol positions (liquidity, lending, staking, vesting). DeBank covers more target chains than Blockscan, including non-Etherscan chains. For one named chain, use `references/workflows/blockscan-balances.md`. Historical balances, NFT inventories, and transaction history stay on `references/workflows/provider-routing.md`. ## Global Queue DeBank's WAF limits the whole browser, so every agent on this host shares one allowance and one agent's burst blocks the rest. Hold a lease from `scripts/debank-gate.py` for all debank.com work: navigating, pasting the collector, `start`, and DOM reads. Leases are granted one at a time in FIFO order; state lives under `${XDG_STATE_HOME:-~/.local/state}/evm-atlas/debank-gate`. <!-- prettier-ignore --> ```sh python3 scripts/debank-gate.py acquire --label '<skill>: <purpose>' --profiles <n> ``` - Each command prints one JSON line. Exit 0 with `"status": "granted"` means proceed and keep the `ticket`. Exit 3 with `"status": "queued"` (after `--wait`, default 240 s) reports `position`, `holder`, and `cooldownUntil`: rerun `acquire` with `--ticket <ticket>` to keep the place. A ticket not polled for 120 s is dropped. In Claude Code, run `acquire --wait 3600` in the background and continue when it exits. - `--profiles` is the number of addresses the lease covers, at most 25. Bulk work takes one lease per batch, so other agents' single-address checks get a turn between batches. - A lease expires after `--ttl` (600 s). Run `renew --ticket <ticket>` before then for longer work. Exit 4 (`"lost"`) means it expired and another agent may hold the gate: stop touching DeBank and acquire again. - When done, or on any failure, close the owned DeBank page, then run `release --ticket <ticket>`. - On a WAF block, run `block --ticket <ticket>`: it releases the lease and pauses the queue for every agent for 15 minutes. - `status` shows the holder, cooldown, and queue. Never touch debank.com without a lease, even when the queue is long. A caller that cannot wait uses Fallbacks and reports the queue wait as the cause. ## Chromium Workflow 1. With a lease held, validate each address (20-byte hex), then open an owned page on `https://debank.com/profile/<addr>` with Chrome DevTools `new_page`. When the cookie dialog appears, choose `Reject`. 2. Never call `api.debank.com` balance endpoints yourself (DeBank's Cloud OpenAPI is paid). The profile page's calls are `fetch` GETs signed by the app (`x-api-*` headers): unsigned calls, even from inside the page, return `429 Request too fast`, and `credentials: 'include'` fails CORS. The collector captures the app's own responses instead. 3. **Token discovery**, for one address or many, uses the collector. Read `scripts/debank-collect.js` and pass its whole contents verbatim as the `evaluate_script` `function`; the file has no trailing `;` on purpose, so it stays a pasteable arrow function. It returns `{ installed: true, reused: false }` (`reused: true` when the page already has it), installs `window.__debankCollect`, and wraps `window.fetch` to record the app's `used_chains` and `balance_list` responses. Per address it routes the page to the profile and succeeds once `used_chains` and a `balance_list` for every listed chain return 200 with `error_code` 0. DeBank lists the chains an address used, not the chains where it holds tokens, so an `ok` record with chains can hold zero tokens. The API includes small balances the UI folds, so no `Show all` click is needed. Start the run with a second `evaluate_script` call: <!-- prettier-ignore --> ```js async () => window.__debankCollect.start(["<addr>"]) ``` `start` lowercases and de-duplicates the addresses, throws for an invalid address, an active run, or a failed `chain/list` call, and otherwise returns `{ queued }` without waiting for the run, so awaiting it surfaces those errors. An optional second argument sets `{ timeoutMs: 30000, maxAttempts: 3, cooldownMs: 20000, haltAfter: 3 }` (the defaults). 4. Poll `window.__debankCollect.status()` with short `evaluate_script` calls until `running` is `false`. It returns `{ running, total, done, ok, failed, pending, rateLimited, blocked, startedAt, elapsedMs }`, with `startedAt` an ISO string. A profile takes about 2.5 s (1.2-4.6 s). A `429`, error, or timeout pauses the page for `cooldownMs` and requeues the address until `maxAttempts`, after which its record is `failed`. After `haltAfter` consecutive failed attempts that saw a `429`, the run stops with `blocked: true` and fails every queued address; see Rate Limits and WAF Blocks. 5. Save `window.__debankCollect.results()` by calling `evaluate_script` with `function: () => window.__debankCollect.results()` and a `filePath`. The path must be inside the MCP workspace roots (a git-ignored project directory); other paths are refused. `results()` returns the latest run's completed records in input order, and a new `start` clears them, so save first. Each record has `address`, `status` (`ok` or `failed`), `attempts`, `error` (when `failed`), `chains` (the `used_chains` slugs), `observedAt` (ISO string), and `tokens`, each `{ chainId, chain, contract, symbol, decimals, rawAmount, amount, price }`. `contract` is the lowercased ERC-20 address or `"native"`, `chainId` is the chain's numeric `network_id` (`null` when `chain/list` has none for the slug), and `rawAmount` is an exact decimal string in raw units. A `failed` record carries `chains: []` and `tokens: []`; those arrays mean nothing, and only `status` counts. 6. Read the `All Chain` summary and the DeFi protocol sections from the DOM only, on that address's own profile (after a run, the page shows the last address collected). Wait for `Data updated`; click `Unfold <n> chains` when present, then read per-chain USD values from the summary. DeFi sections follow the wallet table, each with a protocol name, USD value, and position type; read them from a fresh snapshot when the user requests positions. - `Data updated <age>` is the DeFi project-snapshot time, not wallet-token freshness. `20727 days ago` (the Unix epoch) means `portfolio/project_list` failed to load and says nothing about tokens. Token observation time is each record's `observedAt`. - The DOM wallet table folds small balances behind a toggle reading "Tokens with small balances are not displayed. Show all". It appears only when the wallet has at least 15 tokens and at least 4 are under min(0.1% of wallet USD, $1000). Click `Show all` only when you must read the table itself; the clickable is a `<span>` with an `<svg>` child, so a leaf-element text matcher never finds it. Scraping the folded table misses those tokens. ## Many Addresses One `start` call takes any number of addresses and routes a single owned page through their profiles (DeBank refuses to render in an iframe), so a loop never needs one page per address. - Use one owned page, opened with `new_page` (it reports visible, so timers are not throttled). Each profile costs about `10 + <chains>` requests, and bulk runs were blocked after every 30-55 profiles. - Split the addresses into batches of at most 25. Per batch: `acquire --profiles <batch size>`, `start` the batch, poll, save `results()`, then `release` and acquire again for the next batch. - The loop runs without being awaited, so no DevTools protocol timeout applies; poll `status()` with short calls. - A new `start` throws while a run is active and clears the previous results once it begins. To abort an active run, reload the page and paste the script again. - A `failed` record is a coverage gap, never an empty wallet; handle it per Fallbacks. An `ok` record with no tokens is DeBank's indexed zero. - Never read the page UI to decide that a wallet is empty. During rate limiting, a `429` on `used_chains` makes the page show "No assets yet" with a "Request too fast" toast, `429`s on the balance endpoints remove the wallet table, and DOM rows can linger from the previous profile after a route change. ## Rate Limits and WAF Blocks `Request too fast` (HTTP `429`, body `error_code: 429`, or the page toast) is DeBank's WAF rejecting the request. It is a coverage gap, never evidence about the wallet, and never a reason to call `api.debank.com` another way. Classify it by how it arrived: - **Unsigned call.** A direct `fetch`, `curl`, or WebFetch of `api.debank.com` balance endpoints always gets `429`, so retrying it never works. Switch to the collector; never present the `429` as DeBank being down. - **Burst.** `rateLimited > 0` while records still finish `ok` is the collector absorbing short bursts; no action. - **Block.** `status().blocked` is `true`, `start` throws `chain/list failed: HTTP 429`, or the toast persists. DeBank is rejecting the whole browser; blocks lasted 5-13 minutes. Do not reload, re-paste, open more pages, or restart in a loop: each request prolongs it. Close the page and run `debank-gate.py block --ticket <ticket>` so every queued agent waits out the cooldown instead of extending it. Then acquire again for the `failed` addresses only; the queue grants the lease after the cooldown. If that retry is also blocked, take the remaining addresses through Fallbacks. Confirm a block in Chromium before reporting it: the `status()` output, the record `error` strings, or the `429` responses in `list_network_requests`. Report it as `DeBank WAF rate-limit block ("Request too fast")` with the verification method, the retry made, and the affected addresses. ## Chain Mapping - DeBank names chains by slug (`eth`, `scrl`, `xdai`, `era`). The collector maps slugs to chain IDs through the `network_id` field of the keyless `https://api.debank.com/chain/list`. Match target chains by exact `chainId`, never by slug or display name. - A token whose `chainId` is `null` (`chain/list` has no numeric `network_id` for its slug, as for non-EVM balances such as Hyperliquid spot and perps) is not a target chain: report it with DeFi positions, never as a target chain. ## Coverage and Scope - Derive coverage at runtime: target chains whose ID appears in `chain/list` are DeBank-supported. `used_chains` bounds the chains the collector queries, so a supported target chain absent from a record is DeBank's indexed zero, not an RPC-confirmed zero. - Ignore non-target chains. Sum target-chain tokens for any target-only total; never report the page-wide total as one. - DeBank drops spam server-side (every returned token had `is_scam` and `is_suspicious` false), so it never reports spam tokens, and absence from a record does not show that a token is not held. - `rawAmount` is exact in raw units (with `decimals`); `amount` and `price` are DeBank's. They are still indexer data, and `address-usd-value.md` confirms amounts by RPC before using them. Apply the Pricing Hygiene there before using DeBank prices in totals. - Keep DeFi positions separate from wallet balances. ## Fallbacks - Target chains missing from `chain/list`: use `blockscan-balances.md` when Blockscan lists the chain ID, otherwise `provider-routing.md`. - Navigation fails, an error or challenge persists, an address's record is `failed`, or a WAF block survives the one retry in Rate Limits and WAF Blocks: use `blockscan-balances.md`, then `address-sweeps.md` for remaining target chains. - Chrome DevTools MCP or Chromium is unavailable: use `address-sweeps.md` (or the API passes in `address-usd-value.md`). - On-chain precision required: confirm with RPC `balanceOf` and `eth_getBalance` as `address-usd-value.md` does. Report which condition caused each fallback. ## Output Return the profile URL and the collector `observedAt` per address; add the `Data updated` age only when DeFi positions were read, labeled as the DeFi snapshot time. Then one row per non-empty target chain (name, ID, native and token amounts, DeBank USD), the target-only sum, and DeFi positions as a separate labeled list (protocol, chain, position type, USD). For many addresses, also give the saved results path and the `ok` and `failed` counts. Add counts of excluded non-target chains and coverage gaps (including each `failed` address) with their cause. Separate fallback-derived facts by provider. -
dex-transactions.md 8 KB
# DEX Transaction Evidence Use this workflow for wallet-facing DEX history and for interpreting known transactions or orders. It covers swaps and orders, liquidity and position actions, approvals and permits, rewards, migrations, native wrapping, refunds, and cancellations. It is evidence-only: do not quote, construct, sign, submit, or administer anything. ## Resolve Before Interpretation 1. For a transaction hash with a named chain, resolve it against `references/generated/target-mainnets.json`, then use `references/workflows/provider-routing.md` directly for the raw transaction, receipt, logs, and decoded input. Stop on a non-target chain. 2. When the chain is unknown, use `references/workflows/blockscan-tx-lookup.md` once to resolve it, then continue through `references/workflows/provider-routing.md`. Apply the OP Mainnet pre-regenesis exception in `SKILL.md` before requiring a current-provider receipt. 3. Require exact provider evidence before describing an on-chain result. Except for the documented OP Mainnet legacy route, this means the exact receipt. A failed or pending receipt cannot prove a completed trade, liquidity change, claim, migration, wrap, refund, or cancellation. 4. For address history, use the resolved chain's provider route to identify candidate transactions, then inspect each candidate's receipt. A transaction-list label or method name is not enough. 5. For an off-chain order identifier, load only the relevant protocol reference, resolve its candidate fill, cancellation, or refund transactions, then verify those transactions on-chain. If the identifier or API namespace is not globally chain-scoped, ask for a chain. Never default to Ethereum or fan out across chains without approval. An order may have no maker transaction. Distinguish order creation or signing, API acceptance, on-chain fill, cancellation, expiry, and refund as separate lifecycle facts. ## Evidence Stack Prefer evidence in this order and retain conflicts: 1. Raw receipt status and emitted logs from the resolved chain. 2. Transaction calldata decoded with the verified ABI for the exact target and implementation. 3. Runtime bytecode, proxy implementation, factory provenance, and current official deployment feeds. 4. Traces and internal calls for wrappers, routers, callbacks, native value, and underlying liquidity. 5. Protocol APIs for known identifiers and protocol-native status. 6. Explorer labels, decoded method names, token symbols, and UI descriptions as hints only. If traces are unavailable, report every route or internal-call claim they would have established as a coverage gap. Never promote an explorer label, a four-byte selector guess, or matching source text to deployment proof. ## Classify the Interaction Choose one primary interaction class and list secondary legs: | Class | Required evidence | | --------------------- | ---------------------------------------------------------------------------------- | | Swap or order fill | Successful receipt plus wallet asset deltas and protocol/router/pool evidence | | Order lifecycle | Signed order/API record plus fill, invalidation, expiry, or refund evidence | | Liquidity or position | Pool/periphery events, position or LP identifiers, and wallet asset/receipt deltas | | Approval or permit | Exact spender, asset, amount/expiry/nonce, and emitted or consumed permit evidence | | Reward or claim | Verified distributor/staking contract plus claimed-asset transfer | | Migration | Verified migrator/wrapper plus linked source withdrawal and destination deposit | | Wrap or unwrap | Native-value trace and wrapped-token deposit/withdrawal or transfer evidence | | Refund or sweep | Proven return transfer/value trace and the component that returned it | | Cancellation | Protocol-native invalidation plus any resulting on-chain return | An approval-only transaction remains an approval even if its spender is a DEX router. A successful router call without wallet sold/received deltas may be a cancellation, claim, refund, or no-op; do not force it into a swap class. ## Attribute Every Layer Report these roles independently: - **Execution protocol:** the user-facing settlement or aggregation protocol, such as 1inch Fusion or CoW Protocol. - **Integration wrapper:** the root target or smart account that composed the action. - **Router or periphery:** the contract that dispatched swaps or position commands. - **Pool or settlement:** the core contract whose state changed. - **Underlying liquidity:** each AMM, pool version, or direct counterflow proven by calls or events. Do not relabel a 1inch or CoW execution as Uniswap merely because a solver or router used Uniswap liquidity. For a wrapper-driven Uniswap zap, identify the wrapper as the entrypoint and Uniswap as underlying liquidity. If the root target is not verified, report an unknown wrapper and continue only with the proven downstream calls. ## Compute Wallet-Level Asset Changes 1. Select the wallet and role being reported: trader/maker, receiver, LP or position owner, operator, approver, reward claimant, or refund recipient. Do not combine unrelated participants in a batched transaction. 2. Sum inbound and outbound ERC-20 `Transfer` logs for that wallet by token. Include ERC-721/ERC-1155 position transfers separately; never treat token IDs as fungible amounts. 3. Use traces and native value transfers for native-asset legs. Preserve internal wraps and unwraps instead of silently replacing WETH with ETH or vice versa. 4. Report the final wallet delta as sold, received, returned, or position assets. Keep temporary router/pool transfers as route evidence, not wallet proceeds. 5. Report transaction gas separately from sold assets. Use the receipt's effective gas price, gas used, and chain-specific L1/data fee fields when present; identify who paid it. A relayer or solver may pay gas for an order. 6. Preserve raw integer amounts and decimals evidence. If token metadata conflicts or is unavailable, do not invent a display amount. Refunded unused input is not swap output. A router sweep or unwrap may explain the wallet's final receipt without being a separate trade. Fee-on-transfer, rebasing, internal-balance, and hook-modified assets require trace or state evidence; event arithmetic alone may be incomplete. ## Interpretation Limits - Use exact-input or exact-output only when verified calldata or command encoding proves it. - Use the successful receipt and asset deltas for execution. Calldata limits are intent, not actual output. - Do not reconstruct a historical quote, slippage, price impact, or expected route from current data. - Do not claim full route composition from endpoint transfers alone. List only pools and interactions proven by calldata, logs, or traces. - Separate LP principal, accrued fees, rewards, and refunds only when contract accounting and event sequencing support the split. Otherwise report the combined collected amount and the missing evidence. - Treat protocol APIs as optional enrichment. Missing credentials, rate limits, stale indexing, unsupported chains, or conflicting status must degrade to on-chain evidence with a visible coverage gap. - Treat a verified fork as that fork. Matching Uniswap interfaces, bytecode, event signatures, or pool math does not make an unverified or independently deployed fork canonical Uniswap. ## Completion Return the fields required by `SKILL.md`. Keep `Observed facts`, `Inference`, and non-empty `⚠️ Coverage gaps` as separate sections. Name the evidence source for every material classification and include exact transaction, order, pool, and position identifiers without truncation. For a failed transaction, report attempted calldata only as attempted behavior and state that no receipt logs or state changes survived. For an unsupported chain, unavailable trace, unknown deployment, or API-only status, stop the affected claim at the strongest supported evidence. -
provider-routing.md 19.8 KB
# Provider Routing Read this reference only after resolving a chain in `references/generated/target-mainnets.json`. ## Discrete Read Contract This workflow owns every bounded JSON-RPC read, including reads requested by `cli-cast` for transaction preparation, simulation evidence, fee or nonce resolution, and post-broadcast verification. Accept the resolved chain, method and exact parameters or call object, block selector or checkpoint requirement, and purpose. Return the resolved chain name and ID, exact provider route, result, observed block or checkpoint, and coverage gaps. Keep the read here until it completes. Never invoke `cli-cast` for JSON-RPC transport or return a signing, mutation, or broadcast command. For two or more compatible contract reads on one chain, use Multicall3 at `0xcA11bde05977b3631167028862bE2a173976CA11`; do not batch calls whose result depends on the original `msg.sender`. ## Account and Transaction Data Choose one authoritative history provider per chain and sweep. A provider is authoritative for that result, not a globally canonical source. Keep a second provider only as a fallback: 1. Use Blockscout on covered overlaps, especially when Etherscan cannot serve the chain on the detected plan, its pagination/rate/PRO limits make the requested sweep less complete, or Blockscout's native holdings/counters avoid those limits. 2. Otherwise use Etherscan V2 when the chain is in `references/generated/etherscan-chains.md`, the detected plan can query it, and the needed actions accept the fixed cutoff. 3. Use the other indexed provider as fallback when the authoritative provider is unavailable, malformed, behind the cutoff, rate/plan limited, or missing a required action. A valid empty response is a completed negative, not a fallback trigger. Move the affected result to the fallback; do not silently splice two negative responses into one complete result. 4. If neither indexed provider covers the target, use its listed public RPC only for facts that JSON-RPC can prove. Missing indexed history remains unknown, never empty. On overlaps, Blockscout is not automatically secondary. In particular, prefer it for Base (`8453`), Optimism (`10`), Avalanche (`43114`), and BNB Chain (`56`) when `scripts/etherscan-detect-plan.sh` reports `paid_chains=false`, and when its unmetered per-instance or full-holdings routes are materially more complete than the available Etherscan plan. Do not infer API support from an Etherscan-shaped explorer URL. For raw Etherscan V2 endpoint parameters, plan gating, and error handling, see `references/explorers/etherscan-api.md`. For raw Blockscout endpoint parameters, plan gating, and error handling, see `references/explorers/blockscout-api.md`. ## Checkpoints and State Fix one required ISO-8601 UTC cutoff for the whole sweep. Resolve it once per chain to an exact finalized or otherwise independently verified block at or before that time. Record the requested cutoff, resolution kind (`finalized` or `verified`), block number, hash, timestamp, and observation time. For a timestamp lookup, prove that the returned `B` is the greatest block at or before the cutoff by checking `B.timestamp <= requestedAt` and either `B+1.timestamp > requestedAt` or that `B` is independently the current finalized head. Reuse that exact checkpoint in every request. Do not mix `latest`, different provider heads, or a newly resolved block into the same result. If no route can establish the checkpoint, mark the result unknown rather than inventing one. Batch `eth_getTransactionCount` and `eth_getBalance` with the EIP-1898 `{ blockHash, requireCanonical: true }` selector before indexed history. If a provider rejects that selector, a numeric fallback requires matching block-number/hash headers from the same endpoint immediately before and after the batch; otherwise try the next RPC or report unknown. The target row's `accountActivityModel` controls whether zero nonce plus zero balance may satisfy a profile's native-history shortcut: - Allow the shortcut only for exact `ethereum-eoa`. - Default-deny it for `native-account-abstraction`, `cross-vm`, `unknown`, a missing field, or an unrecognized value. - Under the `bootstrap-discovery` profile, the exact `ethereum-eoa` zero-state invariant may omit both `txlist` and `txlistinternal` wholesale. That profile counts a successful outgoing normal row or a successful positive-value normal/internal row touching the address; zero-value inbound normal/internal noise is outside it. The invariant never covers token/NFT transfers. Apply the profile rules in `references/workflows/address-sweeps.md` before calling an address inactive; a general policy that counts zero-value calls must still query those channels. For `cross-vm`, scope all state, history, and negative claims to the chain's EVM execution environment. EVM evidence does not cover the native non-EVM account environment and cannot prove whole-chain inactivity. An indexer result is cutoff-complete only when the provider is synced through the checkpoint and the query is bounded to it. Filter or paginate past post-cutoff rows; an unbounded newest-first empty/non-empty page is not equivalent to a checkpointed result. Quorum is optional and must be explicit. When requested, enforce it strictly across independent indexed providers that cover the same checkpoint and channel set. PRO and per-instance Blockscout surfaces backed by the same index are one provider. Descending one-row probes establish existence only. For a positive quorum, every provider must query every required channel ascending from genesis or fully paginate its bounded result, apply the same profile predicates, and return the same earliest qualifying transaction hash, block, action/channel, and timestamp. A negative quorum requires valid empty coverage from every provider. Errors and unsupported channels are not votes; never weaken the requested quorum, and report disagreement as unknown. ## RouteMesh and Public RPC Use the `routemesh` CLI exclusively for RouteMesh. For HTTP RPC, require the target row's `routeMesh: true` and the exact chain ID in `routemesh chains --transport rpc`. The `routeMesh` flag describes HTTP coverage; check WebSocket coverage separately with `routemesh chains --transport ws`. Never construct a RouteMesh URL or inspect, request, or print an API key. Assume the user has already run `routemesh init`. On `insufficient_credits`, stop the route and report that the RouteMesh account needs credits. For other credential errors, stop that route and tell the user to obtain an API key from <https://routeme.sh/app/consumer/api-keys>, run `routemesh init`, then retry. Do not run initialization or fall back to a raw RouteMesh endpoint. If `routemesh` is unavailable, report that the CLI is required; do not work around it with direct HTTP. Use `routemesh ping CHAIN_ID` to verify a RouteMesh chain route. Use `routemesh rpc CHAIN_ID METHOD --params=JSON` for one read-only JSON-RPC request, or `--json=JSON` for a complete request or batch. Never pass `--allow-write`. For an activity watch or a bounded wait before rechecking a pending receipt, confirm `routemesh schema subscribe` is available and the exact target chain is in `chains --transport ws`, then use: ```sh routemesh --timeout 60s subscribe CHAIN_ID newHeads --count 1 routemesh --timeout 60s subscribe CHAIN_ID logs --json=FILTER --count 1 routemesh --timeout 30s subscribe CHAIN_ID newPendingTransactions --count 5 ``` `FILTER` accepts only `address` and `topics`; use `--json -` for stdin. Choose the subscription and filter that answer the request. Type availability is provider-dependent. If the local CLI lacks `schema subscribe`, report that it needs updating. Keep a finite count (1–1000) and timeout; both JSON and NDJSON buffer until the full count arrives. A timeout or disconnect fails with no partial stdout and no automatic reconnect. Never interpret it as evidence of no activity. Preserve reorg heads and logs with `removed: true`. Notifications do not prove finality, historical completeness, or a successful transaction. After a notification, verify the relevant receipt, state, or explicit log range through the existing evidence commands. A new subscription does not recover events missed while disconnected; subscriptions do not replace historical sweeps or bridge-protocol status checks. RouteMesh routing is method-specific, so a successful command proves only that exact method, parameters, and block. It does not establish archive coverage for the key, chain, or another method. | Class | Representative methods | Archive-state requirement | | --------------------- | ---------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------- | | Current or block data | `eth_chainId`, `eth_blockNumber`, `eth_getBlockByNumber` | No historical state trie required | | Historical chain data | `eth_getTransactionByHash`, `eth_getTransactionReceipt`, `eth_getBlockReceipts`, `eth_getLogs` | Not inherently an archive-state read, but an upstream can prune or incompletely serve old chain data | | Historical state | Old-block `eth_call`, `eth_getBalance`, `eth_getCode`, `eth_getStorageAt`, `eth_getProof` | Requires an archive-capable state path for that method and block | For historical state requests: 1. Resolve and verify one exact block number, hash, and timestamp before querying state. Reuse that checkpoint for every request. 2. Send the EIP-1898 `{ blockHash, requireCanonical: true }` selector with `routemesh rpc` where the method and provider support it. 3. When a numeric block fallback is required, use the verified block number and retain the same-endpoint block-number/hash consistency checks immediately before and after the request batch. 4. Treat pruning, missing-trie/state-unavailable errors, malformed data, or a failed block-identity check as a coverage failure. Try the ordered independent RPC fallback; if that cannot serve the request, report the state as unknown. Never turn the failure into a zero balance or empty state. 5. On an error or suspicious `null`, retain the CLI-provided batch ID for traceability, but never persist a credential. Do not rerun a failed RouteMesh command arbitrarily. The CLI already performs its bounded retry policy, and another attempt does not promise a different archive-capable pathway on repeat. For a specific transaction receipt: 1. Use `routemesh receipt CHAIN_ID TX_HASH`. It verifies the transaction, receipt, and exact block header and recovers a proven receipt through `eth_getBlockReceipts` when the direct receipt is `null`. 2. Accept its receipt only after the full transaction hash, block number, and block hash match the target checkpoint. 3. On a CLI evidence or provider failure, use the ordered indexed-provider or public-RPC fallback. If none supplies the receipt, report receipt coverage as unknown. A method-specific `null` is inconclusive when the transaction is otherwise proven; it is not proof that the transaction does not exist. A successful receipt lookup repairs only that receipt path, never unrelated missing historical-state evidence. For `eth_getLogs`, pass an exact checkpoint-bounded filter to `routemesh logs --json=FILTER CHAIN_ID`. The CLI splits larger inclusive ranges into deterministic 10,000-block chunks and returns its checkpoint evidence. Do not treat a CLI error as an empty log result. Otherwise issue bounded direct HTTP JSON-RPC requests against the target's `primaryPublicRpc`, first verifying it with `eth_chainId`, then try `references/generated/target-fallback-rpcs.json` in order. Do not hand public-RPC reads to `cli-cast`. Public RPCs are best-effort and may be rate limited. ### Exact simulation failures Preserve the exact transaction object, block selector, CLI exit code, stdout JSON-RPC response, and redacted stderr diagnostics. A nonzero exit can accompany useful JSON-RPC error evidence; capture both streams before handling it. Retain every correlation ID: RouteMesh uses `X-Batch-Id` for individual requests and comma-separated `X-Batch-Ids` for batches, as documented in its [debugging guide](https://routeme.sh/docs/intro/debugging). When a simulation contradicts its supplied fields or checkpointed state: 1. Verify the CLI payload with `--dry-run`, including sender, target, value, calldata, nonce, transaction type, gas, and fee fields. For an affordability error, compare the reported requirement with the exact transaction's upfront reserve. A reported gas allowance different from the supplied limit is a simulation-integrity warning. 2. Replay the unchanged call and estimate through the ordered independent public-RPC fallback at the same verified checkpoint. Verify its chain ID first. A successful estimate does not validate a failed call: the two methods may use different upstreams. Do not repeatedly retry the same route or switch transaction type to explain a mismatch. 3. If needed, use a bounded synthetic probe with different explicit gas limits to test whether the route honors gas. For a verified ordinary EOA with empty calldata, a below-intrinsic limit must fail. Where state overrides are supported, an isolated GAS-opcode probe can establish the executed allowance. These probes diagnose the provider; zeroing value or fees, changing gas, or overriding state never substitutes for the exact transaction simulation. 4. When authenticated logs are available, use `$chromium-browser` to correlate request IDs with the logged parameters, result, and upstream. Preserve the distinction between the service's received payload and any unobserved forwarded payload. A provider label alone does not prove which layer changed execution semantics. Return the exact simulation outcome and any provider discrepancy separately. Keep the consuming workflow's simulation and approval requirements; diagnostic success does not authorize signing or broadcast. A failing RPC route does not establish chain-wide transaction-type incompatibility or justify changing static chain metadata. ## Explorer Links For address and transaction links, substitute `{address}` in the target row's `explorerAddressUrl` or `{tx_hash}` in `explorerTxUrl`. Preserve the full template, including query parameters: address history may use a different service from the transaction explorer. For block and token links, use `explorerUrl` plus `references/explorers/explorer-paths.json`. Verify nonstandard explorers in their UI; Ronin does not reliably follow Etherscan paths and its chain ID collides with a non-target Chainscout entry. Use `$chromium-browser` for OKLink and Ronin browser evidence. Browser availability does not establish a supported programmatic API route; use documented credentials for API access and never extract or reproduce the site's private request-signing headers. ### OKLink historical fallback For resolved Scroll (`534352`) and Ronin (`2020`) targets, [OKLink](https://www.oklink.com/) is an independent browser source for block details and indexed transaction history. Verified on 2026-09-08: | Target | Browser route and useful evidence | Coverage boundary | | ------ | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | Scroll | `https://www.oklink.com/scroll/address/<address>/internal` returned historical internal ETH transfers that matched Blockscout transaction hashes. | Set the requested date window explicitly and inspect the zero-value filter; a default recent window or value-only list cannot prove complete internal-call history. | | Ronin | `https://www.oklink.com/ronin/block/<number>` serves pre-migration blocks, including the linked [2025 cutoff](https://www.oklink.com/ronin/block/51786916) and [successor](https://www.oklink.com/ronin/block/51786917). Address pages use `/ronin/address/<address>`. | Account-history pages disclose a lower bound of [block 25350000](https://www.oklink.com/ronin/block/25350000), 2023-06-27. They cannot prove earlier inactivity or genesis-complete history. | Record the chain, source URL, observation time, requested range, displayed filters, pagination/caps, and block identities. Convert displayed local times to UTC explicitly. Positive rows establish only the facts they show; a complete negative still requires every required channel and interval through the fixed cutoff. Do not splice partial provider negatives into a complete result. Scrollscan now serves Blockscout. Its native v2 internal-transaction list can return HTTP 200 with exhausted pagination while the compatibility API reports unprocessed internal transactions, even when global indexing indicators report completion. Preserve that semantic failure; the native list is useful positive evidence, not an independent fallback or proof that the missing traces are empty. Ronin's [2026 migration announcement](https://blog.roninchain.com/p/ronin-is-home) sunsets the legacy explorer. The new `explorer.roninchain.com` Blockscout deployment did not serve the pre-migration 2025 cutoff; the legacy `app.roninchain.com/explorer` UI reproduced Skynet 503 responses during the check above. Verify historical coverage separately from current-chain availability. OKLink can supply a legacy block boundary without supplying pre-2023 account history or exact historical account state. ## Exceptional History For HyperEVM (`999`) exact historical native-balance and nonce reads, do not use public JSON-RPC or RouteMesh: those routes can silently serve latest state for historical selectors. At the verified checkpoint, use Etherscan V2 `account` module's `balancehistory` action for the native balance and the `proxy` module's `eth_getTransactionCount` action with the checkpoint's hex block tag for the nonce. If an Etherscan route is unavailable or plan-limited, report that fact as unknown; do not fall back to RPC. For Fantom Opera (`250`) account history, do not use the unsafe FTMScout route returned by Chainscout. Read `references/explorers/fantom-opera.md` and preserve its partial-index boundary: GraphQL rows can provide positive evidence, but empty account lists cannot establish historical inactivity. For OP Mainnet data before `2021-11-11`, read `references/explorers/optimism-pre-regenesis.md` before interpreting provider or RPC results. For IoTeX (`4689`) nonce reads, `eth_getTransactionCount` with `latest` returns the actpool pending nonce, identical to `pending`, on both RouteMesh and the public RPC; only a numeric block selector returns the confirmed count. The nodes expose no `txpool_*` methods, and `eth_getBlockByNumber("pending")` omits actpool transactions. Report the confirmed nonce from the numeric checkpoint and treat `pending - confirmed` as the count of queued transactions. Verified 2026-09-30: `latest` and `pending` both returned `11` while the numeric block returned `10`, and the sender's last mined transaction had nonce `9`.
-
-
atlas-overlays.json 16.3 KB
{ "metadata": { "blockscoutObservedAt": "2026-07-08", "etherscanProviderChainCount": 64, "etherscanVerifiedAt": "2026-07-08", "routeMeshVerifiedAt": "2026-07-15" }, "chains": { "abstract": { "primaryPublicRpc": "https://api.mainnet.abs.xyz", "fallbackPublicRpcs": ["https://abstract.drpc.org", "https://2741.rpc.thirdweb.com"], "routeMesh": true, "etherscan": { "support": "free" }, "blockscout": { "status": "absent", "notes": "Not returned by Chainscout" } }, "arbitrum": { "primaryPublicRpc": "https://arb1.arbitrum.io/rpc", "fallbackPublicRpcs": [ "https://arbitrum-one-rpc.publicnode.com", "https://42161.rpc.thirdweb.com", "https://rpcfree.com/arbitrum-rpc" ], "routeMesh": true, "etherscan": { "support": "free" }, "blockscout": { "status": "observed", "hostedBy": "blockscout", "instanceUrl": "https://arbitrum.blockscout.com/" } }, "arbitrum-nova": { "primaryPublicRpc": "https://nova.arbitrum.io/rpc", "fallbackPublicRpcs": [ "https://arbitrum-nova-rpc.publicnode.com", "https://42170.rpc.thirdweb.com", "https://arbitrum-nova.drpc.org" ], "routeMesh": true, "etherscan": { "support": "unsupported", "notes": "Not returned by the live chainlist; `nova.arbiscan.io` redirects to Blockscout" }, "blockscout": { "status": "observed", "hostedBy": "blockscout", "instanceUrl": "https://arbitrum-nova.blockscout.com/", "notes": "Canonical explorer (Arbiscan Nova decommissioned)" } }, "avalanche": { "primaryPublicRpc": "https://api.avax.network/ext/bc/C/rpc", "fallbackPublicRpcs": ["https://avalanche-c-chain-rpc.publicnode.com", "https://43114.rpc.thirdweb.com"], "routeMesh": true, "etherscan": { "support": "paid" }, "blockscout": { "status": "absent", "notes": "Not returned by Chainscout" } }, "base": { "primaryPublicRpc": "https://mainnet.base.org", "fallbackPublicRpcs": [ "https://base-rpc.publicnode.com", "https://base.gateway.tenderly.co", "https://developer-access-mainnet.base.org" ], "routeMesh": true, "etherscan": { "support": "paid" }, "blockscout": { "status": "observed", "hostedBy": "blockscout", "instanceUrl": "https://base.blockscout.com/" } }, "berachain": { "primaryPublicRpc": "https://rpc.berachain.com", "fallbackPublicRpcs": [ "https://berachain-rpc.publicnode.com", "https://80094.rpc.thirdweb.com", "https://rpc.berachain-apis.com" ], "routeMesh": true, "etherscan": { "support": "free" }, "blockscout": { "status": "absent", "notes": "Not returned by Chainscout" } }, "blast": { "primaryPublicRpc": "https://rpc.blast.io", "fallbackPublicRpcs": [ "https://blast-rpc.publicnode.com", "https://blast.drpc.org", "https://81457.rpc.thirdweb.com" ], "routeMesh": true, "etherscan": { "support": "free" }, "blockscout": { "status": "absent", "notes": "Not returned by Chainscout" } }, "bsc": { "primaryPublicRpc": "https://bsc-dataseed1.bnbchain.org", "fallbackPublicRpcs": ["https://bsc-rpc.publicnode.com", "https://bsc.drpc.org", "https://56.rpc.thirdweb.com"], "routeMesh": true, "etherscan": { "support": "paid" }, "blockscout": { "status": "absent", "notes": "Not returned by Chainscout" }, "chainscoutNamePattern": "BNB|BSC|Smart Chain" }, "celo": { "primaryPublicRpc": "https://forno.celo.org", "fallbackPublicRpcs": [ "https://celo.drpc.org", "https://celo-rpc.publicnode.com", "https://42220.rpc.thirdweb.com" ], "routeMesh": true, "etherscan": { "support": "free" }, "blockscout": { "status": "observed", "hostedBy": "blockscout", "instanceUrl": "https://celo.blockscout.com/" } }, "chiliz": { "primaryPublicRpc": "https://rpc.chiliz.com", "fallbackPublicRpcs": [ "https://chiliz.publicnode.com", "https://88888.rpc.thirdweb.com", "https://rpc.ankr.com/chiliz" ], "routeMesh": true, "etherscan": { "support": "unsupported" }, "blockscout": { "status": "absent", "notes": "Not returned by Chainscout" } }, "core-dao": { "primaryPublicRpc": "https://rpc.coredao.org", "fallbackPublicRpcs": [ "https://core.drpc.org", "https://1116.rpc.thirdweb.com", "https://rpc-core.icecreamswap.com" ], "routeMesh": true, "etherscan": { "support": "unsupported" }, "blockscout": { "status": "absent", "notes": "Not returned by Chainscout" }, "chainscoutNamePattern": "Core" }, "mainnet": { "primaryPublicRpc": "https://ethereum-rpc.publicnode.com", "fallbackPublicRpcs": ["https://eth.drpc.org", "https://rpc.flashbots.net", "https://1.rpc.thirdweb.com"], "routeMesh": true, "etherscan": { "support": "free", "notes": "Default chain" }, "blockscout": { "status": "observed", "hostedBy": "blockscout", "instanceUrl": "https://eth.blockscout.com/" } }, "fantom": { "primaryPublicRpc": "https://rpc.fantom.network", "fallbackPublicRpcs": ["https://rpc2.fantom.network"], "routeMesh": true, "etherscan": { "support": "unsupported" }, "blockscout": { "status": "unsafe", "notes": "Chainscout still lists self-hosted FTMScout at https://ftmscout.com/, but as checked 2026-07-31 its frontend returns HTTP 200 while /api/v2/* data routes return HTTP 500; do not use it for evidence" } }, "filecoin": { "primaryPublicRpc": "https://api.node.glif.io/rpc/v1", "fallbackPublicRpcs": ["https://314.rpc.thirdweb.com"], "routeMesh": true, "etherscan": { "support": "unsupported" }, "blockscout": { "status": "observed", "hostedBy": "blockscout", "instanceUrl": "https://filecoin.blockscout.com/" }, "chainscoutNamePattern": "Filecoin|Filecoin Virtual Machine|FVM" }, "fraxtal": { "primaryPublicRpc": "https://rpc.frax.com", "fallbackPublicRpcs": [ "https://fraxtal-rpc.publicnode.com", "https://fraxtal.drpc.org", "https://fraxtal.gateway.tenderly.co" ], "routeMesh": true, "etherscan": { "support": "free" }, "blockscout": { "status": "absent", "notes": "Not returned by Chainscout" } }, "gnosis": { "primaryPublicRpc": "https://rpc.gnosischain.com", "fallbackPublicRpcs": [ "https://gnosis-rpc.publicnode.com", "https://gnosis.drpc.org", "https://100.rpc.thirdweb.com" ], "routeMesh": true, "etherscan": { "support": "free" }, "blockscout": { "status": "observed", "hostedBy": "blockscout", "instanceUrl": "https://gnosis.blockscout.com/" } }, "hyperevm": { "primaryPublicRpc": "https://rpc.hyperliquid.xyz/evm", "fallbackPublicRpcs": [ "https://hyperliquid.drpc.org", "https://999.rpc.thirdweb.com", "https://gwan-ssl.wandevs.org:46891" ], "routeMesh": true, "etherscan": { "support": "free" }, "blockscout": { "status": "observed", "hostedBy": "self", "instanceUrl": "https://www.hyperscan.com/", "notes": "Chainscout marks `isTestnet=true`" }, "chainscoutNamePattern": "HyperEVM|Hyper" }, "iotex": { "primaryPublicRpc": "https://babel-api.mainnet.iotex.io", "fallbackPublicRpcs": ["https://4689.rpc.thirdweb.com"], "routeMesh": true, "etherscan": { "support": "unsupported" }, "blockscout": { "status": "absent", "notes": "Not returned by Chainscout" } }, "lightlink": { "primaryPublicRpc": "https://replicator.phoenix.lightlink.io/rpc/v1", "fallbackPublicRpcs": ["https://1890.rpc.thirdweb.com"], "routeMesh": true, "etherscan": { "support": "unsupported" }, "blockscout": { "status": "observed", "hostedBy": "blockscout", "instanceUrl": "https://phoenix.lightlink.io/" }, "chainscoutNamePattern": "Lightlink|LightLink" }, "linea": { "primaryPublicRpc": "https://rpc.linea.build", "fallbackPublicRpcs": ["https://linea-rpc.publicnode.com", "https://59144.rpc.thirdweb.com"], "routeMesh": true, "etherscan": { "support": "free" }, "blockscout": { "status": "observed", "hostedBy": "self", "instanceUrl": "https://explorer.linea.build/" } }, "mode": { "primaryPublicRpc": "https://mainnet.mode.network", "fallbackPublicRpcs": ["https://mode.drpc.org", "https://34443.rpc.thirdweb.com"], "routeMesh": true, "etherscan": { "support": "unsupported" }, "blockscout": { "status": "observed", "hostedBy": "blockscout", "instanceUrl": "https://explorer.mode.network/" } }, "monad": { "primaryPublicRpc": "https://rpc.monad.xyz", "fallbackPublicRpcs": ["https://monad.drpc.org", "https://143.rpc.thirdweb.com"], "routeMesh": true, "etherscan": { "support": "free" }, "blockscout": { "status": "absent", "notes": "Not returned by Chainscout" } }, "morph": { "primaryPublicRpc": "https://rpc.morphl2.io", "fallbackPublicRpcs": [ "https://morph.drpc.org", "https://2818.rpc.thirdweb.com", "https://rpc-quicknode.morphl2.io" ], "routeMesh": true, "etherscan": { "support": "unsupported" }, "blockscout": { "status": "observed", "hostedBy": "self", "instanceUrl": "https://explorer.morph.network/", "notes": "Separate API host; see explorerApiUrl" } }, "optimism": { "primaryPublicRpc": "https://mainnet.optimism.io", "fallbackPublicRpcs": [ "https://optimism-rpc.publicnode.com", "https://optimism.drpc.org", "https://10.rpc.thirdweb.com" ], "routeMesh": true, "etherscan": { "support": "paid" }, "blockscout": { "status": "observed", "hostedBy": "blockscout", "instanceUrl": "https://explorer.optimism.io/" }, "chainscoutNamePattern": "OP|Optimism" }, "polygon": { "primaryPublicRpc": "https://polygon-bor-rpc.publicnode.com", "fallbackPublicRpcs": [ "https://polygon.drpc.org", "https://137.rpc.thirdweb.com", "https://rpc-mainnet.matic.quiknode.pro" ], "routeMesh": true, "etherscan": { "support": "free" }, "blockscout": { "status": "observed", "hostedBy": "blockscout", "instanceUrl": "https://polygon.blockscout.com/" } }, "robinhood": { "primaryPublicRpc": "https://rpc.mainnet.chain.robinhood.com", "fallbackPublicRpcs": ["https://robinhoodchain.blockscout.com/api/eth-rpc"], "routeMesh": true, "etherscan": { "support": "unsupported" }, "blockscout": { "status": "observed", "hostedBy": "blockscout", "instanceUrl": "https://robinhoodchain.blockscout.com/" } }, "ronin": { "primaryPublicRpc": "https://api.roninchain.com/rpc", "fallbackPublicRpcs": ["https://ronin.drpc.org", "https://2020.rpc.thirdweb.com"], "routeMesh": true, "etherscan": { "support": "unsupported" }, "blockscout": { "status": "unsafe", "notes": "Chainscout returns a different network for `2020`; app.roninchain.com blocks scripted access, so verify with `$chromium-browser` instead of curl or WebFetch" } }, "scroll": { "primaryPublicRpc": "https://rpc.scroll.io", "fallbackPublicRpcs": ["https://scroll.drpc.org", "https://534352.rpc.thirdweb.com"], "routeMesh": true, "etherscan": { "support": "unsupported", "notes": "Removed from Etherscan V2 on 2026-04-16; api.scrollscan.com no longer resolves" }, "blockscout": { "status": "observed", "hostedBy": "blockscout", "instanceUrl": "https://scroll.blockscout.com" } }, "sei": { "primaryPublicRpc": "https://evm-rpc.sei-apis.com", "fallbackPublicRpcs": ["https://sei.drpc.org", "https://1329.rpc.thirdweb.com"], "routeMesh": true, "etherscan": { "support": "free" }, "blockscout": { "status": "absent", "notes": "Not returned by Chainscout" } }, "sonic": { "primaryPublicRpc": "https://rpc.soniclabs.com", "fallbackPublicRpcs": [ "https://sonic-rpc.publicnode.com", "https://sonic.drpc.org", "https://146.rpc.thirdweb.com" ], "routeMesh": true, "etherscan": { "support": "free" }, "blockscout": { "status": "absent", "notes": "Not returned by Chainscout" } }, "sophon": { "primaryPublicRpc": "https://rpc.sophon.xyz", "fallbackPublicRpcs": ["https://50104.rpc.thirdweb.com"], "routeMesh": true, "etherscan": { "support": "unsupported" }, "blockscout": { "status": "absent", "notes": "Not returned by Chainscout" } }, "superseed": { "primaryPublicRpc": "https://mainnet.superseed.xyz", "fallbackPublicRpcs": ["https://superseed.drpc.org", "https://5330.rpc.thirdweb.com"], "routeMesh": true, "etherscan": { "support": "unsupported" }, "blockscout": { "status": "unsafe", "notes": "Chromium verified 2026-09-15: explorer.superseed.xyz now serves Conduit Explorer, which does not support historical transactions, holdings, or transfers; do not use its stale Chainscout Blockscout route" } }, "taiko": { "primaryPublicRpc": "https://rpc.mainnet.taiko.xyz", "fallbackPublicRpcs": [ "https://taiko-rpc.publicnode.com", "https://taiko.drpc.org", "https://167000.rpc.thirdweb.com" ], "routeMesh": true, "etherscan": { "support": "free" }, "blockscout": { "status": "observed", "hostedBy": "self", "instanceUrl": "https://blockscout.mainnet.taiko.xyz/", "notes": "Chainscout name is Taiko Alethia" }, "chainscoutNamePattern": "Taiko|Alethia" }, "unichain": { "primaryPublicRpc": "https://mainnet.unichain.org", "fallbackPublicRpcs": ["https://unichain-rpc.publicnode.com", "https://130.rpc.thirdweb.com"], "routeMesh": true, "etherscan": { "support": "free" }, "blockscout": { "status": "observed", "hostedBy": "blockscout", "instanceUrl": "https://unichain.blockscout.com" } }, "world-chain": { "primaryPublicRpc": "https://worldchain-mainnet.g.alchemy.com/public", "fallbackPublicRpcs": [ "https://worldchain.drpc.org", "https://480.rpc.thirdweb.com", "https://worldchain-mainnet.gateway.tenderly.co" ], "routeMesh": true, "etherscan": { "support": "free" }, "blockscout": { "status": "observed", "hostedBy": "alchemy", "instanceUrl": "https://worldchain-mainnet.explorer.alchemy.com/", "notes": "Alchemy-hosted instance; prefer Etherscan V2" }, "chainscoutNamePattern": "World" }, "xdc": { "primaryPublicRpc": "https://rpc.xdcrpc.com", "fallbackPublicRpcs": ["https://50.rpc.thirdweb.com", "https://erpc.xdcrpc.com", "https://rpc.xdc.org"], "routeMesh": true, "etherscan": { "support": "free" }, "blockscout": { "status": "absent", "notes": "Not returned by Chainscout" } }, "zksync": { "primaryPublicRpc": "https://mainnet.era.zksync.io", "fallbackPublicRpcs": ["https://zksync.drpc.org", "https://324.rpc.thirdweb.com"], "routeMesh": true, "etherscan": { "support": "unsupported", "notes": "Not currently supported by Etherscan V2" }, "blockscout": { "status": "observed", "hostedBy": "blockscout", "instanceUrl": "https://zksync.blockscout.com/" }, "chainscoutNamePattern": "ZKsync" }, "zora": { "primaryPublicRpc": "https://zora.drpc.org", "fallbackPublicRpcs": ["https://7777777.rpc.thirdweb.com", "https://rpc.zora.energy"], "routeMesh": true, "etherscan": { "support": "unsupported" }, "blockscout": { "status": "absent", "notes": "Not returned by Chainscout" } } } } -
chain-categories.md 9.6 KB
# Chain categories and exact-zero native sweeps `references/generated/target-mainnets.json` is the source of truth for every target's current `category`. Categories describe current architecture, not historical protocol behavior. Do not duplicate a target-chain roster here: resolve the row first, then report its category with the chain evidence. | Category | Key | Scope and exact-zero implication | | -------- | ---------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | Mainnet | `mainnet` | Ethereum only. A fixed-price plain native transfer is eligible when its current inclusion and standard-transfer proof holds. | | Alt L1 | `alt-l1` | Ethereum-style L1s, deliberately including Polygon PoS. They can be eligible only with target-specific proof of fixed gas, a fixed full `gasPrice` debit, and no extra debits or credits; BNB Chain and Polygon PoS are examples. | | L2 OP | `op-stack` | Ineligible for a guaranteed direct exact-zero transfer: automatic uncapped L1 data fees and operator fees can add to execution gas. | | Nitro | `nitro` | Ineligible for a guaranteed direct exact-zero transfer: parent-chain posting economics and CollectTips-dependent charged pricing prevent the required fixed total debit proof. | | ZK | `zk` | Includes ZK Stack, validium, and hybrid targets. Linea and Taiko can be eligible when a standard fixed legacy debit is proven at inclusion. Scroll and Morph are ineligible because of separate dynamic L1 fees. ZK Stack targets, including ZKsync Era, Abstract, and Sophon, are ineligible because pubdata, overhead, and refunds are variable. | | Alt L2 | `alt-l2` | Fallback category. Treat as unknown: require bespoke target proof of the complete debit, or block. Lightlink can be eligible when a standard fixed legacy debit is proven at inclusion. | Category is only the first gate. An eligible category still requires current target-specific evidence that the transaction is an ordinary empty-calldata transfer, uses a fixed full charge at inclusion, has no additional debit or credit, and has a fixed gas use. Type-0 acceptance, a fixed bid shape, or successful simulation alone does not prove eligibility. For Ethereum-style accounting, an included legacy transaction pays its signed `gasPrice`: changes to base fees or admission quotes can prevent inclusion, but do not reprice the signed transaction. Linea's L1-cost-based profitability check is an admission rule, not an additional sender debit; Taiko also charges the signed legacy price without a separate L1 fee. With proven `21000` gas, ordinary EOAs, empty calldata, and unchanged balance/nonce/code, both permit `value = balance - 21000 * gasPrice`. This is conditional on inclusion, not a guarantee that a public transaction will be included. OP Stack means OP Stack-based execution, including forks: its L1 data fee and any enabled operator fee are added to execution cost. Nitro means the Arbitrum family, including AnyTrust chains: its parent-chain posting charge is converted into child-chain gas, not added again as a separate wei fee. Fixing the legacy bid does not fix either family's total charge. ZK covers distinct execution and proving systems, so use the named chain's fee branch rather than inferring a shared fee model from its proof technology. Scroll charges its L1 data fee on the full signed RLP bytes, and under Feynman that fee scales with byte length. Its `L1GasPriceOracle` predeploy (`0x5300000000000000000000000000000000000002`) prices exactly the bytes passed to `getL1Fee(bytes)` and, unlike OP Stack's `GasPriceOracle`, adds no signature overhead. A quote on an unsigned empty-calldata EIP-1559 transfer (about 46 bytes) is therefore less than half the charge on the signed transaction (about 114 bytes). Quote a maximum-size signed stand-in instead: the final fields serialized with nonzero 32-byte `r`/`s` placeholders. Verified 2026-09-29: the oracle quote on real signed bytes at the parent block equals the receipt `l1Fee`. Lightlink (`1890`) is an Alt L2 exception. It runs a Geth 1.10 fork without London: blocks carry no `baseFeePerGas`, `eth_maxPriorityFeePerGas` and `eth_feeHistory` are unsupported, and receipts omit `effectiveGasPrice`, so use legacy pricing only. No separate L1 data, operator, or rollup fee is debited from the sender. Enterprise Mode gasless transactions are signed with `gasPrice = 0`; the node does not reprice a signed nonzero price. Verified 2026-09-30: consecutive-block sender balance deltas equalled `gasUsed * gasPrice` exactly for ordinary legacy transactions, and an empty-calldata EOA transfer estimated `21000`. Because receipts lack `effectiveGasPrice`, verify the charged price as the signed `gasPrice` plus an exact receipt-block balance reconciliation. IoTeX (`4689`) is an Alt L1 exception. Legacy transactions are debited exactly `gasUsed * gasPrice` with no refund or extra debit. Receipts, including an empty-calldata transfer's on RouteMesh and the public RPC (verified 2026-09-30), report `effectiveGasPrice` equal to the signed `gasPrice`; if one omits it, fall back to exact receipt-block balance reconciliation. An empty-calldata EOA transfer charges `10000` gas, not `21000`, and `eth_estimateGas` still returns `21000`; a `21000` gas limit therefore leaves `11000 * gasPrice` behind. For an exact drain, use a `10000` gas limit, which `eth_call` and the node accept, instead of the estimate. Verified 2026-09-30: two empty-calldata transfers used exactly `10000` gas, and sender balance deltas reconciled to `value + gasUsed * gasPrice`; an exact-zero sweep at that limit left a `0` balance. Filecoin FEVM is an Alt L1 exception. FVM fee translation and overestimation require bespoke evidence; do not generalize Ethereum-style L1 fee behavior to it. A fresh EOA recipient does not alter the standard top-level transfer gas cost: the `25000` new-account `CALL` cost concerns the contract opcode, not a top-level transfer. Still exclude precompiles and protocol system destinations; `eth_getCode == 0x` alone does not establish that an address is an ordinary recipient. For a category-fee or exact-drain question, return the current category, target row, protocol/source evidence, and the decision before any signer setup. Atlas remains read-only; return evidence for `$cli-cast` and the calling workflow to construct, simulate, and account for a transaction. ## Sources - [EIP-1559](https://eips.ethereum.org/EIPS/eip-1559) - [OP Stack transaction fees](https://docs.optimism.io/op-stack/transactions/fees) - [Arbitrum gas and fees](https://docs.arbitrum.io/how-arbitrum-works/deep-dives/gas-and-fees) and [Nitro L1 pricing](https://github.com/OffchainLabs/nitro/blob/master/arbos/l1pricing/l1pricing.go) - [ZKsync Era fee structure](https://docs.zksync.io/zksync-protocol/era-vm/transactions/fee-model/fee-structure) and [bootloader](https://docs.zksync.io/zksync-protocol/era-vm/contracts/bootloader) - [Scroll transaction fees](https://docs.scroll.io/en/developers/transaction-fees-on-scroll/) - [Morph state transition](https://github.com/morph-l2/go-ethereum/blob/main/core/state_transition.go) and [rollup fee](https://github.com/morph-l2/go-ethereum/blob/main/rollup/fees/rollup_fee.go) - [Linea gas fees](https://github.com/Consensys/doc.linea/blob/main/docs/network/how-to/gas-fees.mdx) and [sequencer profitability validation](https://github.com/Consensys/linea-monorepo/blob/main/linea-besu/plugins/linea-sequencer/sequencer/src/main/java/lineth/sequencer/txpoolvalidation/validators/ProfitabilityValidator.java) - [Taiko legacy transaction](https://github.com/taikoxyz/taiko-geth/blob/taiko/core/types/tx_legacy.go) and [state transition](https://github.com/taikoxyz/taiko-geth/blob/taiko/core/state_transition.go) - [Filecoin FIP-0091](https://github.com/filecoin-project/FIPs/blob/master/FIPS/fip-0091.md), [FEVM gas differences](https://docs.filecoin.io/smart-contracts/filecoin-evm-runtime/difference-with-ethereum), and [Filecoin gas estimation](https://docs.filecoin.io/reference/exchanges/exchange-integration#automatic-gas-values) - [LightLink Enterprise Mode](https://docs.lightlink.io/lightlink-protocol/building-on-lightlink/enterprise-mode-overview) - [Geth top-level transaction gas](https://github.com/ethereum/go-ethereum/blob/master/core/state_transition.go)
-
-
scripts
-
blockscout-detect-plan.sh 1.7 KB
#!/bin/bash # blockscout-detect-plan.sh — Detect Blockscout PRO plan + credits from $BLOCKSCOUT_API_KEY. # # Reads the rate-limit/credit response headers the PRO host returns on every # call. x-ratelimit-limit maps directly to the plan tier: # 5 -> free, 15 -> builder, 30 -> pro, 50 -> business # # Outputs key=value lines on stdout: # plan=<free|builder|pro|business|unknown> # rate_limit_rps=<int> # rate_limit_remaining=<int> # rate_limit_reset=<int seconds> # credits_remaining=<int> # # Costs ~20 credits (one address call). Cache the result for the session. set -eu if [ -z "${BLOCKSCOUT_API_KEY:-}" ]; then echo "Error: BLOCKSCOUT_API_KEY is not set" >&2 echo "Get a free key at: https://dev.blockscout.com/" >&2 exit 1 fi # Any valid call returns the headers we need; use a known mainnet address. addr="0xde0B295669a9FD93d5F28D9Ec85E40f4cb697BAe" url="https://api.blockscout.com/1/api/v2/addresses/$addr" headers=$(curl -fsS -D - -o /dev/null \ -H "authorization: Bearer $BLOCKSCOUT_API_KEY" "$url" 2>/dev/null) || { echo "Error: request failed — invalid key or network issue (PRO host returns 401 for a bad key)." >&2 exit 1 } # Case-insensitive header lookup; strip trailing CR. hval() { printf '%s' "$headers" | grep -i "^$1:" | head -1 \ | sed 's/^[^:]*:[[:space:]]*//' | tr -d '\r' } rps=$(hval "x-ratelimit-limit") remaining=$(hval "x-ratelimit-remaining") reset=$(hval "x-ratelimit-reset") credits=$(hval "x-credits-remaining") case "$rps" in 5) plan="free" ;; 15) plan="builder" ;; 30) plan="pro" ;; 50) plan="business" ;; *) plan="unknown" ;; esac cat <<EOF plan=$plan rate_limit_rps=$rps rate_limit_remaining=$remaining rate_limit_reset=$reset credits_remaining=$credits EOF -
check-conformance-fixtures.sh 940 B
#!/bin/bash # Run the evm-atlas provider-response validators against synthetic fixtures. # This test is offline: it performs no network requests and reads no credentials. set -eu script_dir=$(CDPATH='' cd "$(dirname "$0")" && pwd) fixture_dir="$script_dir/../fixtures" example_address="0xde0B295669a9FD93d5F28D9Ec85E40f4cb697BAe" bash "$script_dir/validate-blockscout-address-counters.sh" \ < "$fixture_dir/blockscout-address-counters.json" >/dev/null bash "$script_dir/validate-etherscan-transfer-topics.sh" "$example_address" \ < "$fixture_dir/etherscan-transfer-topic-response.json" >/dev/null if bash "$script_dir/validate-etherscan-transfer-topics.sh" "$example_address" \ < "$fixture_dir/etherscan-transfer-topic-self-transfer.json" >/dev/null 2>&1; then echo "Error: a self-transfer incorrectly proved distinct inbound-only and outbound-only OR semantics." >&2 exit 1 fi echo "evm-atlas conformance fixtures are valid" -
debank-collect.js 8.7 KB
() => { // Paste verbatim as the Chrome DevTools MCP `evaluate_script` function on a debank.com page. // It records the app's own signed responses; it never calls balance endpoints itself. if (window.__debankCollect) return { installed: true, reused: true }; const ADDRESS = /^0x[0-9a-f]{40}$/i; const USED_CHAINS = "/user/used_chains"; const BALANCE_LIST = "/token/balance_list"; const networkFetch = window.fetch; const waiters = new Set(); let events = []; let chainIds = null; let run = null; const sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms)); function emit(event) { events.push(event); if (run && !run.finishedAt && (event.status === 429 || event.json?.error_code === 429)) run.rateLimited += 1; for (const waiter of [...waiters]) if (waiter.match(event)) waiter.done(event); } function trackedUrl(input) { try { const url = new URL(typeof input === "string" ? input : (input.url ?? String(input)), location.href); const tracked = url.hostname === "api.debank.com" && [USED_CHAINS, BALANCE_LIST].includes(url.pathname); return tracked ? url : null; } catch { return null; } } window.fetch = function (input, init) { const url = trackedUrl(input); if (!url) return networkFetch.call(window, input, init); const params = url.searchParams; const event = { path: url.pathname, address: (params.get("id") ?? params.get("user_addr") ?? "").toLowerCase(), chain: params.get("chain"), at: Date.now(), }; return networkFetch.call(window, input, init).then( (res) => { res .clone() .json() .catch(() => null) .then((json) => emit({ ...event, status: res.status, json })); return res; }, (error) => { emit({ ...event, status: 0, json: null, error: String(error) }); throw error; }, ); }; // Resolves with the first matching event (already recorded or future), or null at the deadline. function waitFor(match, deadline) { const seen = events.find(match); if (seen) return Promise.resolve(seen); return new Promise((resolve) => { const waiter = { match, done(event) { clearTimeout(timer); waiters.delete(waiter); resolve(event); }, }; const timer = setTimeout(() => waiter.done(null), Math.max(0, deadline - Date.now())); waiters.add(waiter); }); } function problem(event, label) { if (event.status === 0) return `${label} network error: ${event.error}`; if (event.status !== 200) return `${label} HTTP ${event.status}`; if (!event.json) return `${label} returned invalid JSON`; if (event.json.error_code) return `${label} error_code ${event.json.error_code}`; return null; } function route(path) { history.pushState({}, "", path); dispatchEvent(new PopStateEvent("popstate", { state: {} })); } function toToken(item) { const hex = String(item.raw_amount_hex_str); return { chainId: chainIds.get(item.chain) ?? null, chain: item.chain, contract: ADDRESS.test(item.id) ? item.id.toLowerCase() : "native", symbol: item.symbol, decimals: item.decimals, rawAmount: BigInt(hex.startsWith("0x") ? hex : `0x${hex}`).toString(), amount: item.amount, price: item.price, }; } async function collect(address, timeoutMs) { events = []; // The app ignores a route to the profile it already shows. if (location.pathname.toLowerCase() === `/profile/${address}`) route("/"); const since = Date.now(); const deadline = since + timeoutMs; const ours = (path) => (event) => event.path === path && event.address === address && event.at >= since; route(`/profile/${address}`); const used = await waitFor(ours(USED_CHAINS), deadline); if (!used) throw new Error("timeout waiting for used_chains"); const usedProblem = problem(used, "used_chains"); if (usedProblem) throw new Error(usedProblem); const chains = used.json.data?.chains; if (!Array.isArray(chains)) throw new Error("used_chains response has no data.chains array"); const pending = new Set(chains); const tokens = []; while (pending.size > 0) { const event = await waitFor((e) => ours(BALANCE_LIST)(e) && pending.has(e.chain), deadline); if (!event) throw new Error(`timeout waiting for balance_list: ${[...pending].join(", ")}`); const listProblem = problem(event, `balance_list ${event.chain}`); if (listProblem) throw new Error(listProblem); if (!Array.isArray(event.json.data)) throw new Error(`balance_list ${event.chain} has no data array`); pending.delete(event.chain); tokens.push(...event.json.data.map(toToken)); } return { chains, tokens }; } function failedRecord(address, attempts, error) { return { address, status: "failed", attempts, error, chains: [], observedAt: new Date().toISOString(), tokens: [] }; } async function drain(current, { timeoutMs, maxAttempts, cooldownMs, haltAfter }) { const queue = [...current.addresses]; // Consecutive failed attempts that saw a 429; a sustained streak means DeBank's WAF is blocking this client. let limitedStreak = 0; while (queue.length > 0) { const address = queue.shift(); const attempts = (current.attempts.get(address) ?? 0) + 1; current.attempts.set(address, attempts); const limitedBefore = current.rateLimited; try { const { chains, tokens } = await collect(address, timeoutMs); const observedAt = new Date().toISOString(); current.records.set(address, { address, status: "ok", attempts, chains, observedAt, tokens }); limitedStreak = 0; } catch (error) { limitedStreak = current.rateLimited > limitedBefore ? limitedStreak + 1 : 0; if (attempts < maxAttempts) queue.push(address); else current.records.set(address, failedRecord(address, attempts, error.message)); if (limitedStreak >= haltAfter) { current.blocked = true; const reason = `rate-limit block: run halted after ${limitedStreak} consecutive rate-limited attempts`; for (const queued of queue) current.records.set(queued, failedRecord(queued, current.attempts.get(queued) ?? 0, reason)); break; } if (queue.length > 0) await sleep(cooldownMs); } } current.finishedAt = Date.now(); } async function loadChainIds() { const res = await networkFetch.call(window, "https://api.debank.com/chain/list"); const json = await res.json().catch(() => null); if (res.status !== 200 || !Array.isArray(json?.data?.chains)) throw new Error(`chain/list failed: HTTP ${res.status}`); const ids = new Map(); for (const chain of json.data.chains) { const id = Number(chain.network_id); if (Number.isSafeInteger(id)) ids.set(chain.id, id); } return ids; } async function start(addresses, { timeoutMs = 30000, maxAttempts = 3, cooldownMs = 20000, haltAfter = 3 } = {}) { if (run && !run.finishedAt) throw new Error("a run is already active; poll status() until running is false"); if (!Array.isArray(addresses)) throw new Error("addresses must be an array"); const invalid = addresses.filter((address) => typeof address !== "string" || !ADDRESS.test(address)); if (invalid.length > 0) throw new Error(`invalid addresses: ${invalid.map(String).join(", ")}`); const unique = [...new Set(addresses.map((address) => address.toLowerCase()))]; const current = { addresses: unique, attempts: new Map(), records: new Map(), rateLimited: 0, blocked: false, startedAt: Date.now(), finishedAt: null, }; run = current; try { chainIds ??= await loadChainIds(); } catch (error) { run = null; throw error; } drain(current, { timeoutMs, maxAttempts, cooldownMs, haltAfter }); return { queued: unique.length }; } function status() { const records = run ? [...run.records.values()] : []; const ok = records.filter((record) => record.status === "ok").length; const total = run ? run.addresses.length : 0; return { running: Boolean(run && !run.finishedAt), total, done: records.length, ok, failed: records.length - ok, pending: total - records.length, rateLimited: run ? run.rateLimited : 0, blocked: run ? run.blocked : false, startedAt: run ? new Date(run.startedAt).toISOString() : null, elapsedMs: run ? (run.finishedAt ?? Date.now()) - run.startedAt : 0, }; } const results = () => (run ? run.addresses.map((address) => run.records.get(address)).filter(Boolean) : []); window.__debankCollect = { start, status, results }; return { installed: true, reused: false }; } -
debank-gate.py 8.7 KB
#!/usr/bin/env python3 """Host-wide FIFO lease queue for DeBank access by concurrent agents. DeBank's WAF rate-limits the whole browser, so one agent's burst blocks every other agent. Each agent takes a lease here before touching debank.com and releases it afterwards; a reported block pauses the queue for everyone. """ from __future__ import annotations import argparse import contextlib import fcntl import json import os import signal import sys import time import uuid from datetime import datetime, timezone from pathlib import Path DEFAULT_TTL = 600 DEFAULT_WAIT = 240 DEFAULT_BLOCK_MINUTES = 15 MAX_PROFILES = 25 # A queued agent must poll again within this window or lose its place, so an abandoned ticket cannot stall the queue. STALE_TICKET_SECONDS = 120 POLL_SECONDS = 1.0 EXIT_QUEUED = 3 EXIT_LOST = 4 def state_dir() -> Path: override = os.environ.get("DEBANK_GATE_DIR") if override: return Path(override) base = os.environ.get("XDG_STATE_HOME") or str(Path.home() / ".local" / "state") return Path(base) / "evm-atlas" / "debank-gate" def iso(ts: float) -> str | None: return datetime.fromtimestamp(ts, timezone.utc).strftime("%Y-%m-%dT%H:%M:%SZ") if ts else None @contextlib.contextmanager def locked_state(): directory = state_dir() directory.mkdir(parents=True, exist_ok=True) with open(directory / "gate.lock", "w") as lock: fcntl.flock(lock, fcntl.LOCK_EX) path = directory / "state.json" try: state = json.loads(path.read_text()) except (FileNotFoundError, json.JSONDecodeError): state = {"queue": [], "lease": None, "cooldownUntil": 0} yield state tmp = path.with_suffix(".tmp") tmp.write_text(json.dumps(state, indent=2) + "\n") os.replace(tmp, path) def prune(state: dict, now: float) -> None: lease = state["lease"] if lease and lease["expiresAt"] <= now: state["lease"] = None state["queue"] = [t for t in state["queue"] if now - t["seenAt"] < STALE_TICKET_SECONDS] def enqueue(state: dict, label: str, profiles: int, now: float) -> str: ticket = {"id": uuid.uuid4().hex[:12], "label": label, "profiles": profiles, "enqueuedAt": now, "seenAt": now} state["queue"].append(ticket) return ticket["id"] def try_grant(state: dict, ticket_id: str, ttl: int, now: float) -> str: """Return "granted", "queued", or "unknown" (never issued, or dropped as stale).""" prune(state, now) lease = state["lease"] if lease and lease["ticket"] == ticket_id: return "granted" ticket = next((t for t in state["queue"] if t["id"] == ticket_id), None) if ticket is None: return "unknown" ticket["seenAt"] = now if lease or now < state["cooldownUntil"] or state["queue"][0]["id"] != ticket_id: return "queued" state["queue"].pop(0) state["lease"] = { "ticket": ticket_id, "label": ticket["label"], "profiles": ticket["profiles"], "grantedAt": now, "expiresAt": now + ttl, } return "granted" def renew(state: dict, ticket_id: str, ttl: int, now: float) -> bool: prune(state, now) lease = state["lease"] if not lease or lease["ticket"] != ticket_id: return False lease["expiresAt"] = now + ttl return True def release(state: dict, ticket_id: str, now: float) -> None: prune(state, now) if state["lease"] and state["lease"]["ticket"] == ticket_id: state["lease"] = None state["queue"] = [t for t in state["queue"] if t["id"] != ticket_id] def block(state: dict, ticket_id: str | None, minutes: float, now: float) -> None: state["cooldownUntil"] = max(state["cooldownUntil"], now + minutes * 60) if ticket_id: release(state, ticket_id, now) def summary(state: dict, now: float, ticket_id: str | None = None) -> dict: lease = state["lease"] ids = [t["id"] for t in state["queue"]] return { "now": iso(now), "holder": {"label": lease["label"], "profiles": lease["profiles"], "expiresAt": iso(lease["expiresAt"])} if lease else None, "cooldownUntil": iso(state["cooldownUntil"]) if state["cooldownUntil"] > now else None, "queued": len(ids), **({"position": ids.index(ticket_id) + 1} if ticket_id in ids else {}), } def emit(payload: dict) -> None: print(json.dumps(payload)) def cmd_acquire(args: argparse.Namespace) -> int: if not 1 <= args.profiles <= MAX_PROFILES: sys.exit(f"--profiles must be between 1 and {MAX_PROFILES}; split larger runs into batches") deadline = time.time() + args.wait ticket_id = args.ticket pending = True def cancel(signum, frame): raise SystemExit(128 + signum) signal.signal(signal.SIGTERM, cancel) try: while True: now = time.time() with locked_state() as state: result = try_grant(state, ticket_id, args.ttl, now) if ticket_id else "unknown" if result == "unknown": ticket_id = enqueue(state, args.label, args.profiles, now) result = try_grant(state, ticket_id, args.ttl, now) info = summary(state, now, ticket_id) if result == "granted": pending = False emit({"status": "granted", "ticket": ticket_id, "expiresAt": info["holder"]["expiresAt"]}) return 0 if now >= deadline: pending = False emit({"status": "queued", "ticket": ticket_id, **info}) return EXIT_QUEUED time.sleep(min(POLL_SECONDS, deadline - now)) finally: # An interrupted wait gives up its place instead of stalling the head of the queue. if pending and ticket_id: with locked_state() as state: release(state, ticket_id, time.time()) def cmd_renew(args: argparse.Namespace) -> int: now = time.time() with locked_state() as state: ok = renew(state, args.ticket, args.ttl, now) info = summary(state, now) if not ok: emit({"status": "lost", "ticket": args.ticket, **info}) return EXIT_LOST emit({"status": "renewed", "ticket": args.ticket, "expiresAt": info["holder"]["expiresAt"]}) return 0 def cmd_release(args: argparse.Namespace) -> int: now = time.time() with locked_state() as state: release(state, args.ticket, now) info = summary(state, now) emit({"status": "released", "ticket": args.ticket, **info}) return 0 def cmd_block(args: argparse.Namespace) -> int: now = time.time() with locked_state() as state: block(state, args.ticket, args.minutes, now) info = summary(state, now) emit({"status": "cooldown", **info}) return 0 def cmd_status(args: argparse.Namespace) -> int: now = time.time() with locked_state() as state: prune(state, now) info = summary(state, now) info["queue"] = [{"label": t["label"], "profiles": t["profiles"]} for t in state["queue"]] emit(info) return 0 def main(argv: list[str] | None = None) -> int: parser = argparse.ArgumentParser(description=__doc__) sub = parser.add_subparsers(dest="command", required=True) acquire = sub.add_parser("acquire", help="join the queue and wait for the lease") acquire.add_argument("--label", required=True, help="who is asking, e.g. 'sweep-eth holdings check'") acquire.add_argument("--profiles", type=int, default=1, help=f"profiles this lease covers (max {MAX_PROFILES})") acquire.add_argument("--ticket", help="resume a queued ticket and keep its place") acquire.add_argument("--wait", type=float, default=DEFAULT_WAIT, help="seconds to wait before returning queued") acquire.add_argument("--ttl", type=int, default=DEFAULT_TTL, help="lease lifetime in seconds") acquire.set_defaults(func=cmd_acquire) renew_parser = sub.add_parser("renew", help="extend a held lease") renew_parser.add_argument("--ticket", required=True) renew_parser.add_argument("--ttl", type=int, default=DEFAULT_TTL) renew_parser.set_defaults(func=cmd_renew) release_parser = sub.add_parser("release", help="free a lease or leave the queue") release_parser.add_argument("--ticket", required=True) release_parser.set_defaults(func=cmd_release) block_parser = sub.add_parser("block", help="report a WAF block: pause the queue and release the lease") block_parser.add_argument("--ticket") block_parser.add_argument("--minutes", type=float, default=DEFAULT_BLOCK_MINUTES) block_parser.set_defaults(func=cmd_block) sub.add_parser("status", help="show the holder, cooldown, and queue").set_defaults(func=cmd_status) args = parser.parse_args(argv) return args.func(args) if __name__ == "__main__": sys.exit(main()) -
etherscan-detect-plan.sh 3.1 KB
#!/bin/bash # etherscan-detect-plan.sh — Detect Etherscan API plan tier from $ETHERSCAN_API_KEY. # # Outputs key=value lines on stdout: # plan=<free|lite|standard|advanced|professional|pro_plus|enterprise|unknown> # credit_limit=<int> # credits_used=<int> # credits_available=<int> # limit_interval=<string> # interval_expiry=<HH:MM:SS> # pro_endpoints=<true|false> # paid_chains=<true|false> # # Cache the result for the session — getapilimit itself consumes 1 credit, and # the paid-chain probe (when needed) consumes another. set -eu if [ -z "${ETHERSCAN_API_KEY:-}" ]; then echo "Error: ETHERSCAN_API_KEY is not set" >&2 exit 1 fi base="https://api.etherscan.io/v2/api" response=$(curl -fsS "$base?chainid=1&module=getapilimit&action=getapilimit&apikey=$ETHERSCAN_API_KEY") extract_num() { printf '%s' "$1" | grep -o "\"$2\":[0-9]*" | head -1 | grep -o '[0-9]*' } extract_str() { printf '%s' "$1" | grep -o "\"$2\":\"[^\"]*\"" | head -1 | sed 's/.*:"\([^"]*\)"/\1/' } status=$(extract_str "$response" "status") if [ "$status" != "1" ]; then msg=$(extract_str "$response" "message") res=$(extract_str "$response" "result") echo "Error: getapilimit failed — message=$msg result=$res" >&2 exit 1 fi credit_limit=$(extract_num "$response" "creditLimit") credits_used=$(extract_num "$response" "creditsUsed") credits_avail=$(extract_num "$response" "creditsAvailable") interval=$(extract_str "$response" "limitInterval") expiry=$(extract_str "$response" "intervalExpiryTimespan") # Map creditLimit → plan. Free and Lite share the 100k daily credit; the # difference is paid-only-chain access. Standard+ implies PRO endpoints and # paid-chain access; no probe needed. case "$credit_limit" in 100000) plan="free_or_lite"; pro_endpoints="false"; paid_chains="probe" ;; 200000) plan="standard"; pro_endpoints="true"; paid_chains="true" ;; 500000) plan="advanced"; pro_endpoints="true"; paid_chains="true" ;; 1000000) plan="professional"; pro_endpoints="true"; paid_chains="true" ;; 1500000) plan="pro_plus"; pro_endpoints="true"; paid_chains="true" ;; *) if [ "${credit_limit:-0}" -gt 1500000 ]; then plan="enterprise"; pro_endpoints="true"; paid_chains="true" else plan="unknown"; pro_endpoints="unknown"; paid_chains="unknown" fi ;; esac # Lite ($49/mo) unlocks the paid Etherscan target chains (Base, OP, # Avalanche, BNB) while Free does not. Probe a Base balance to disambiguate: status=1 → Lite, # status=0 → Free. PRO endpoints stay false on Lite (Standard plan and up). if [ "$plan" = "free_or_lite" ]; then probe=$(curl -fsS "$base?chainid=8453&module=account&action=balance&address=0xde0B295669a9FD93d5F28D9Ec85E40f4cb697BAe&tag=latest&apikey=$ETHERSCAN_API_KEY" 2>/dev/null || printf '{"status":"0"}') probe_status=$(extract_str "$probe" "status") if [ "$probe_status" = "1" ]; then plan="lite" paid_chains="true" else plan="free" paid_chains="false" fi fi cat <<EOF plan=$plan credit_limit=$credit_limit credits_used=$credits_used credits_available=$credits_avail limit_interval=$interval interval_expiry=$expiry pro_endpoints=$pro_endpoints paid_chains=$paid_chains EOF -
resolve-chain.sh 5 KB
#!/bin/bash # resolve-chain.sh - Resolve a target chain_id to its Blockscout instance via Chainscout. # # Generated by scripts/generate-evm-atlas.ts. Registry metadata comes from crypto-registry data/chains.json; edit skills/evm-atlas/references/atlas-overlays.json for Atlas-only fields. # # Usage: resolve-chain.sh <target_chain_id> # # Outputs key=value lines on stdout: # chain_id=<int> # name=<string> # native_currency=<symbol> # instance_url=<url, ends in /> # api_url=<API base URL, without trailing slash> # hosted_by=<blockscout|other> # is_testnet=<true|false> # layer=<int|> # rollup_type=<string|> # # No API key required. Source: https://chains.blockscout.com/ # Scope is intentionally limited to skills/evm-atlas/references/generated/target-mainnets.json. set -eu if [ $# -lt 1 ] || [ -z "${1:-}" ]; then echo "Usage: resolve-chain.sh <chain_id>" >&2 exit 1 fi chain_id="$1" target_name_pattern() { case "$1" in 2741) printf '%s\n' 'Abstract' ;; 42161) printf '%s\n' 'Arbitrum' ;; 42170) printf '%s\n' 'Arbitrum Nova' ;; 43114) printf '%s\n' 'Avalanche' ;; 8453) printf '%s\n' 'Base' ;; 80094) printf '%s\n' 'Berachain' ;; 81457) printf '%s\n' 'Blast' ;; 56) printf '%s\n' 'BNB|BSC|Smart Chain' ;; 42220) printf '%s\n' 'Celo' ;; 88888) printf '%s\n' 'Chiliz' ;; 1116) printf '%s\n' 'Core' ;; 1) printf '%s\n' 'Ethereum' ;; 250) printf '%s\n' 'Fantom' ;; 314) printf '%s\n' 'Filecoin|Filecoin Virtual Machine|FVM' ;; 252) printf '%s\n' 'Fraxtal' ;; 100) printf '%s\n' 'Gnosis' ;; 999) printf '%s\n' 'HyperEVM|Hyper' ;; 4689) printf '%s\n' 'IoTeX' ;; 1890) printf '%s\n' 'Lightlink|LightLink' ;; 59144) printf '%s\n' 'Linea' ;; 34443) printf '%s\n' 'Mode' ;; 143) printf '%s\n' 'Monad' ;; 2818) printf '%s\n' 'Morph' ;; 10) printf '%s\n' 'OP|Optimism' ;; 137) printf '%s\n' 'Polygon' ;; 4663) printf '%s\n' 'Robinhood Chain' ;; 2020) printf '%s\n' 'Ronin' ;; 534352) printf '%s\n' 'Scroll' ;; 1329) printf '%s\n' 'Sei' ;; 146) printf '%s\n' 'Sonic' ;; 50104) printf '%s\n' 'Sophon' ;; 5330) printf '%s\n' 'Superseed' ;; 167000) printf '%s\n' 'Taiko|Alethia' ;; 130) printf '%s\n' 'Unichain' ;; 480) printf '%s\n' 'World' ;; 50) printf '%s\n' 'XDC' ;; 324) printf '%s\n' 'ZKsync' ;; 7777777) printf '%s\n' 'Zora' ;; *) return 1 ;; esac } expected_pattern=$(target_name_pattern "$chain_id") || { echo "Error: chain_id=$chain_id is outside the evm-atlas target list." >&2 echo "Ask the user to file a feature request in https://github.com/PaulRBerg/agent-skills" >&2 exit 2 } unsafe_reason() { # shellcheck disable=SC2016 case "$1" in 250) printf '%s\n' 'Chainscout still lists self-hosted FTMScout at https://ftmscout.com/, but as checked 2026-07-31 its frontend returns HTTP 200 while /api/v2/* data routes return HTTP 500; do not use it for evidence' ;; 2020) printf '%s\n' 'Chainscout returns a different network for `2020`; app.roninchain.com blocks scripted access, so verify with `$chromium-browser` instead of curl or WebFetch' ;; 5330) printf '%s\n' 'Chromium verified 2026-09-15: explorer.superseed.xyz now serves Conduit Explorer, which does not support historical transactions, holdings, or transfers; do not use its stale Chainscout Blockscout route' ;; *) return 1 ;; esac } if reason=$(unsafe_reason "$chain_id"); then echo "Error: chain_id=$chain_id is marked unsafe in the evm-atlas overlay." >&2 echo "Reason: $reason" >&2 exit 1 fi resp=$(curl -fsS "https://chains.blockscout.com/api/chains/$chain_id" 2>/dev/null) || { echo "Error: Chainscout request failed for chain_id=$chain_id" >&2 exit 1 } sval() { printf '%s' "$resp" | grep -o "\"$1\":\"[^\"]*\"" | head -1 | sed 's/.*:"\(.*\)"/\1/'; } nval() { printf '%s' "$resp" | grep -o "\"$1\":[0-9]*" | head -1 | grep -o '[0-9]*'; } bval() { printf '%s' "$resp" | grep -oE "\"$1\":(true|false)" | head -1 | sed 's/.*://'; } name=$(sval "name") if [ -z "$name" ]; then echo "Error: chain_id=$chain_id not found in Chainscout" >&2 exit 1 fi if ! printf '%s' "$name" | grep -Eiq "$expected_pattern"; then echo "Error: Chainscout returned name=$name for target chain_id=$chain_id; expected match=$expected_pattern" >&2 echo "Refusing to use a non-target Chainscout registry match." >&2 exit 1 fi native=$(sval "native_currency") # "url" only appears inside explorers[]; the first is the primary explorer. instance=$(printf '%s' "$resp" | grep -o '"url":"[^"]*"' | head -1 | sed 's/.*:"\(.*\)"/\1/') hosted=$(sval "hostedBy") testnet=$(bval "isTestnet") layer=$(nval "layer") rollup=$(sval "rollupType") api="${instance%/}/api" # Explicit registry API bases override stale Chainscout page-host routes. case "$chain_id" in 2818) instance='https://explorer.morph.network/'; api='https://explorer-api.morph.network/api' ;; esac cat <<EOF chain_id=$chain_id name=$name native_currency=$native instance_url=$instance api_url=$api hosted_by=$hosted is_testnet=$testnet layer=$layer rollup_type=$rollup EOF -
sweep-core.py 22.6 KB
#!/usr/bin/env python3 """Plan, evaluate, and validate deterministic EVM address-sweep evidence.""" from __future__ import annotations import argparse import datetime as dt import json import re import sys from pathlib import Path from typing import Any ADDRESS_RE = re.compile(r"^0x[0-9a-fA-F]{40}$") HASH_RE = re.compile(r"^0x[0-9a-fA-F]{64}$") HEX_RE = re.compile(r"^0x(?:0|[1-9a-fA-F][0-9a-fA-F]*)$") TRANSFER_TOPIC = "0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef" HISTORY_CHANNELS = ("txlist", "txlistinternal", "tokentx", "tokennfttx", "token1155tx") PROFILE_CHANNELS = { "general": ("nonce", "native-balance", *HISTORY_CHANNELS), "bootstrap-discovery": ("nonce", "native-balance", "txlist", "txlistinternal", "tokentx", "tokennfttx"), } class SweepError(ValueError): pass def load_json(path: Path | None) -> Any: try: return json.loads(path.read_text(encoding="utf-8") if path else sys.stdin.read()) except (OSError, json.JSONDecodeError) as exc: raise SweepError(f"cannot read JSON: {exc}") from exc def iso_time(value: Any, field: str) -> dt.datetime: if not isinstance(value, str): raise SweepError(f"{field} must be an ISO-8601 string") try: parsed = dt.datetime.fromisoformat(value.replace("Z", "+00:00")) except ValueError as exc: raise SweepError(f"{field} must be an ISO-8601 string") from exc if parsed.tzinfo is None: raise SweepError(f"{field} must include a timezone") return parsed def normalize_profile(value: Any) -> str: aliases = {"bootstrap": "bootstrap-discovery", "bootstrap discovery": "bootstrap-discovery"} normalized = aliases.get(value, value) if normalized not in PROFILE_CHANNELS: raise SweepError(f"unsupported profile: {value}") return normalized def validate_checkpoint(checkpoint: Any) -> dict[str, Any]: if not isinstance(checkpoint, dict): raise SweepError("checkpoint must be an object") required = ("requestedAt", "resolutionKind", "blockNumber", "blockHash", "blockTimestamp", "observedAt") missing = [field for field in required if field not in checkpoint] if missing: raise SweepError(f"checkpoint is missing: {', '.join(missing)}") if checkpoint["resolutionKind"] not in {"finalized", "verified"}: raise SweepError("checkpoint resolutionKind must be finalized or verified") if not isinstance(checkpoint["blockNumber"], int) or checkpoint["blockNumber"] < 0: raise SweepError("checkpoint blockNumber must be a non-negative integer") if not isinstance(checkpoint["blockHash"], str) or not HASH_RE.fullmatch(checkpoint["blockHash"]): raise SweepError("checkpoint blockHash must contain 32 hex bytes") requested = iso_time(checkpoint["requestedAt"], "checkpoint requestedAt") block_time = iso_time(checkpoint["blockTimestamp"], "checkpoint blockTimestamp") observed = iso_time(checkpoint["observedAt"], "checkpoint observedAt") if block_time > requested: raise SweepError("checkpoint blockTimestamp is after requestedAt") if observed < block_time: raise SweepError("checkpoint observedAt is before blockTimestamp") return {**checkpoint, "blockHash": checkpoint["blockHash"].lower()} def build_plan(spec: Any) -> dict[str, Any]: if not isinstance(spec, dict): raise SweepError("plan input must be an object") address = spec.get("address") if not isinstance(address, str) or not ADDRESS_RE.fullmatch(address): raise SweepError("address must contain exactly 20 hex bytes") chain = spec.get("chain") if not isinstance(chain, dict) or not isinstance(chain.get("id"), int) or chain["id"] <= 0: raise SweepError("chain.id must be a positive integer") model = chain.get("accountActivityModel") if not isinstance(model, str) or not model: raise SweepError("chain.accountActivityModel must be a non-empty string") goal = spec.get("goal", "historical-activity") if goal not in {"historical-activity", "bootstrap-discovery"}: raise SweepError(f"unsupported goal: {goal}") profile = normalize_profile(spec.get("profile", "general")) checkpoint = validate_checkpoint(spec.get("checkpoint")) providers = spec.get("providers") if not isinstance(providers, list) or not providers: raise SweepError("providers must be a non-empty array selected by the agent") normalized_providers: list[dict[str, Any]] = [] provider_ids: set[str] = set() for provider in providers: if not isinstance(provider, dict) or not isinstance(provider.get("id"), str) or not provider["id"]: raise SweepError("every provider must have a non-empty id") if provider["id"] in provider_ids: raise SweepError(f"duplicate provider id: {provider['id']}") provider_ids.add(provider["id"]) capabilities = provider.get("capabilities") if not isinstance(capabilities, list) or not all(channel in HISTORY_CHANNELS for channel in capabilities): raise SweepError(f"provider {provider['id']} has invalid capabilities") normalized_providers.append( { "id": provider["id"], "kind": provider.get("kind", "selected-indexer"), "independenceGroup": provider.get("independenceGroup", provider["id"]), "capabilities": list(dict.fromkeys(capabilities)), } ) quorum = spec.get("quorum", 1) if isinstance(quorum, dict): quorum = quorum.get("required", 1) if quorum.get("enabled", False) else 1 if not isinstance(quorum, int) or quorum < 1: raise SweepError("quorum must be a positive integer") if quorum > len({provider["independenceGroup"] for provider in normalized_providers}): raise SweepError("quorum exceeds independent provider groups") required = list(PROFILE_CHANNELS[profile]) state_requests = [ { "requestId": f"state:{channel}", "channel": channel, "transport": "json-rpc", "method": method, "params": [address.lower(), {"blockHash": checkpoint["blockHash"], "requireCanonical": True}], "credentials": [], } for channel, method in (("nonce", "eth_getTransactionCount"), ("native-balance", "eth_getBalance")) ] history_requests = [] for provider in normalized_providers: for channel in required: if channel not in HISTORY_CHANNELS or channel not in provider["capabilities"]: continue history_requests.append( { "requestId": f"history:{provider['id']}:{channel}", "providerId": provider["id"], "channel": channel, "transport": "indexed-history", "operation": {"providerKind": provider["kind"], "action": channel}, "bounds": {"startBlock": 0, "endBlock": checkpoint["blockNumber"]}, "order": "asc" if quorum > 1 else "desc", "credentials": [], } ) return { "schemaVersion": 1, "address": address.lower(), "chain": chain, "goal": goal, "profile": profile, "checkpoint": checkpoint, "quorumRequirement": quorum, "requiredChannels": required, "providers": normalized_providers, "requests": {"state": state_requests, "history": history_requests}, } def parse_quantity(value: Any, context: str) -> int: if isinstance(value, int) and not isinstance(value, bool) and value >= 0: return value if not isinstance(value, str): raise SweepError(f"{context} must be a non-negative integer or quantity string") if HEX_RE.fullmatch(value): return int(value, 16) if re.fullmatch(r"0|[1-9]\d*", value): return int(value) raise SweepError(f"{context} must be a non-negative integer or quantity string") def row_block(row: dict[str, Any]) -> int: value = row.get("blockNumber", row.get("block_number")) return parse_quantity(value, "row block number") def touches(row: dict[str, Any], address: str) -> bool: values = [row.get(key) for key in ("from", "to", "contractAddress", "created_contract")] return any(isinstance(value, str) and value.lower() == address for value in values) def failed_row(row: dict[str, Any]) -> bool: return str(row.get("isError", row.get("is_error", "0"))) == "1" or str(row.get("txreceipt_status", "1")) == "0" or bool(str(row.get("errCode", row.get("error", ""))).strip()) def qualifies(profile: str, channel: str, row: dict[str, Any], address: str) -> bool: if failed_row(row) or not touches(row, address): return False if profile == "general" or channel in {"tokentx", "tokennfttx", "token1155tx"}: return True value = parse_quantity(row.get("value", 0), f"{channel} row value") if channel == "txlist": outgoing = isinstance(row.get("from"), str) and row["from"].lower() == address return outgoing or value > 0 if channel == "txlistinternal": return value > 0 return True def evidence(provider_id: str, channel: str, row: dict[str, Any]) -> dict[str, Any]: transaction_hash = row.get("hash", row.get("transactionHash", row.get("transaction_hash"))) if not isinstance(transaction_hash, str) or not HASH_RE.fullmatch(transaction_hash): raise SweepError(f"qualifying {channel} row has no valid transaction hash") timestamp = row.get("timeStamp", row.get("timestamp")) return { "providerId": provider_id, "channel": channel, "transactionHash": transaction_hash.lower(), "blockNumber": row_block(row), "timestamp": timestamp, } def evidence_key(item: dict[str, Any]) -> tuple[Any, ...]: return (item["blockNumber"], str(item.get("timestamp") or ""), item["transactionHash"], item["channel"]) def evaluate(plan: Any, responses: Any) -> dict[str, Any]: if not isinstance(plan, dict) or plan.get("schemaVersion") != 1: raise SweepError("plan schemaVersion must be 1") if not isinstance(responses, dict): raise SweepError("responses must be an object") checkpoint = validate_checkpoint(plan.get("checkpoint")) address = plan.get("address") if not isinstance(address, str) or not ADDRESS_RE.fullmatch(address): raise SweepError("plan address is invalid") profile = normalize_profile(plan.get("profile")) required = plan.get("requiredChannels") if required != list(PROFILE_CHANNELS[profile]): raise SweepError("plan requiredChannels do not match the profile") checked: list[dict[str, Any]] = [] omitted: list[dict[str, str]] = [] gaps: list[dict[str, str]] = [] state = responses.get("state") or {} if not isinstance(state, dict): raise SweepError("responses.state must be an object") state_values: dict[str, int] = {} state_positive: dict[str, Any] | None = None for channel in ("nonce", "native-balance"): response = state.get(channel) if not isinstance(response, dict) or response.get("ok") is not True: gaps.append({"channel": channel, "reason": "missing or failed checkpoint-bound state response"}) continue if response.get("blockHash", "").lower() != checkpoint["blockHash"] and response.get("checkpointBound") is not True: gaps.append({"channel": channel, "reason": "state response is not bound to the checkpoint hash"}) continue try: quantity = parse_quantity(response.get("result"), f"state {channel}") except SweepError as exc: gaps.append({"channel": channel, "reason": str(exc)}) continue state_values[channel] = quantity checked.append({"channel": channel, "source": "state-rpc", "result": "positive" if quantity else "negative"}) if quantity and state_positive is None: state_positive = {"source": "state-rpc", "channel": channel, "value": quantity, "blockNumber": checkpoint["blockNumber"], "blockHash": checkpoint["blockHash"]} zero_shortcut = ( profile == "bootstrap-discovery" and plan.get("chain", {}).get("accountActivityModel") == "ethereum-eoa" and state_values.get("nonce") == 0 and state_values.get("native-balance") == 0 ) history_required = [channel for channel in required if channel in HISTORY_CHANNELS] if zero_shortcut: for channel in ("txlist", "txlistinternal"): history_required.remove(channel) omitted.append({"channel": channel, "reason": "ethereum-eoa zero-state invariant"}) provider_responses = responses.get("providers") or {} if not isinstance(provider_responses, dict): raise SweepError("responses.providers must be an object") provider_results: list[dict[str, Any]] = [] for provider in plan.get("providers", []): provider_id = provider["id"] response = provider_responses.get(provider_id) if not isinstance(response, dict): provider_results.append({"providerId": provider_id, "complete": False, "earliest": None, "positive": False}) continue try: indexed_through = parse_quantity(response.get("indexedThrough"), f"provider {provider_id} indexedThrough") except SweepError as exc: gaps.append({"channel": "history", "reason": str(exc)}) provider_results.append({"providerId": provider_id, "complete": False, "earliest": None, "positive": False}) continue if indexed_through < checkpoint["blockNumber"]: gaps.append({"channel": "history", "reason": f"provider {provider_id} is indexed only through {indexed_through}"}) provider_results.append({"providerId": provider_id, "complete": False, "earliest": None, "positive": False}) continue channels = response.get("channels") if not isinstance(channels, dict): gaps.append({"channel": "history", "reason": f"provider {provider_id} channels are malformed"}) provider_results.append({"providerId": provider_id, "complete": False, "earliest": None, "positive": False}) continue provider_complete = True provider_evidence: list[dict[str, Any]] = [] for channel in history_required: channel_response = channels.get(channel) if not isinstance(channel_response, dict) or channel_response.get("ok") is not True: provider_complete = False gaps.append({"channel": channel, "reason": f"provider {provider_id} response is missing or failed"}) continue rows = channel_response.get("rows") if not isinstance(rows, list) or not all(isinstance(row, dict) for row in rows): provider_complete = False gaps.append({"channel": channel, "reason": f"provider {provider_id} rows are malformed"}) continue try: bounded = all(row_block(row) <= checkpoint["blockNumber"] for row in rows) except SweepError as exc: provider_complete = False gaps.append({"channel": channel, "reason": f"provider {provider_id}: {exc}"}) continue if not bounded: provider_complete = False gaps.append({"channel": channel, "reason": f"provider {provider_id} returned a post-checkpoint row"}) continue try: qualifying = [row for row in rows if qualifies(profile, channel, row, address.lower())] channel_evidence = [evidence(provider_id, channel, row) for row in qualifying] except SweepError as exc: provider_complete = False gaps.append({"channel": channel, "reason": f"provider {provider_id}: {exc}"}) continue provider_evidence.extend(channel_evidence) complete = channel_response.get("complete") is True if not channel_evidence and not complete: provider_complete = False gaps.append({"channel": channel, "reason": f"provider {provider_id} did not exhaust the bounded channel"}) elif channel_evidence and not complete: provider_complete = False gaps.append({"channel": channel, "reason": f"provider {provider_id} positive response is not complete enough for earliest evidence"}) checked.append( { "channel": channel, "source": provider_id, "result": "positive" if channel_evidence else "negative" if complete else "unknown", } ) earliest = min(provider_evidence, key=evidence_key) if provider_evidence else None provider_results.append( { "providerId": provider_id, "independenceGroup": provider["independenceGroup"], "complete": provider_complete, "positive": bool(provider_evidence), "earliest": earliest, } ) quorum = plan.get("quorumRequirement", 1) agreement: bool | None = None earliest: dict[str, Any] | None = state_positive result = "unknown" if state_positive: result = "positive" elif quorum == 1: usable = next((provider for provider in provider_results if provider["positive"] or provider["complete"]), None) if usable and usable["positive"]: result, earliest = "positive", usable["earliest"] elif usable and usable["complete"] and not gaps: result = "negative" agreement = None else: votes = [provider for provider in provider_results if provider.get("complete")] distinct: list[dict[str, Any]] = [] seen_groups: set[str] = set() for vote in votes: if vote["independenceGroup"] not in seen_groups: distinct.append(vote) seen_groups.add(vote["independenceGroup"]) if len(distinct) >= quorum: selected = distinct[:quorum] if all(vote["positive"] for vote in selected): keys = {evidence_key(vote["earliest"]) for vote in selected} agreement = len(keys) == 1 if agreement: result, earliest = "positive", selected[0]["earliest"] elif all(vote["complete"] and not vote["positive"] for vote in selected): agreement, result = True, "negative" else: agreement = False if agreement is False: gaps.append({"channel": "quorum", "reason": "independent history providers disagree"}) coverage = "complete" if result == "negative" and not gaps else "partial" if checked or omitted else "unknown" if result == "positive" and earliest: coverage = "complete" if not gaps else "partial" return { "schemaVersion": 1, "result": result, "coverage": coverage, "profile": profile, "checkpoint": checkpoint, "checked": checked, "omitted": omitted, "gaps": gaps, "earliestQualifyingEvidence": earliest, "quorum": {"required": quorum, "agreement": agreement, "providers": provider_results}, } def validate_blockscout(payload: Any) -> dict[str, Any]: fields = ("transactions_count", "token_transfers_count", "gas_usage_count", "validations_count") valid = isinstance(payload, dict) and all(isinstance(payload.get(field), str) and re.fullmatch(r"0|[1-9]\d*", payload[field]) for field in fields) return {"schemaVersion": 1, "validator": "blockscout-address-counters", "valid": valid, "counts": {field: payload.get(field) for field in fields} if isinstance(payload, dict) else {}} def validate_transfer_topics(payload: Any, address: str) -> dict[str, Any]: if not ADDRESS_RE.fullmatch(address): raise SweepError("address must contain exactly 20 hex bytes") address_topic = "0x" + "0" * 24 + address[2:].lower() rows = payload.get("result") if isinstance(payload, dict) and payload.get("status") == "1" else None conformant_rows = [] if isinstance(rows, list): for row in rows: topics = row.get("topics") if isinstance(row, dict) else None if not isinstance(topics, list) or len(topics) < 3 or not all(isinstance(topic, str) for topic in topics[:3]): conformant_rows = [] break lowered = [topic.lower() for topic in topics] if lowered[0] != TRANSFER_TOPIC or address_topic not in lowered[1:3]: conformant_rows = [] break conformant_rows.append(lowered) outbound = sum(row[1] == address_topic and row[2] != address_topic for row in conformant_rows) inbound = sum(row[2] == address_topic and row[1] != address_topic for row in conformant_rows) return { "schemaVersion": 1, "validator": "etherscan-transfer-topics", "valid": bool(conformant_rows and outbound and inbound), "outboundOnlyResults": outbound, "inboundOnlyResults": inbound, } def build_parser() -> argparse.ArgumentParser: parser = argparse.ArgumentParser(description=__doc__) subparsers = parser.add_subparsers(dest="command", required=True) plan = subparsers.add_parser("plan") plan.add_argument("--input", required=True, type=Path) evaluate_parser = subparsers.add_parser("evaluate") evaluate_parser.add_argument("--plan", required=True, type=Path) evaluate_parser.add_argument("--responses", required=True, type=Path) validate_parser = subparsers.add_parser("validate") validate_parser.add_argument("validator", choices=("blockscout-address-counters", "etherscan-transfer-topics")) validate_parser.add_argument("--input", type=Path) validate_parser.add_argument("--address") return parser def main() -> int: args = build_parser().parse_args() try: if args.command == "plan": result = build_plan(load_json(args.input)) elif args.command == "evaluate": result = evaluate(load_json(args.plan), load_json(args.responses)) else: payload = load_json(args.input) if args.validator == "blockscout-address-counters": result = validate_blockscout(payload) else: if not args.address: raise SweepError("etherscan-transfer-topics requires --address") result = validate_transfer_topics(payload, args.address) except SweepError as exc: print(f"ERROR: {exc}", file=sys.stderr) return 64 print(json.dumps(result, indent=2, ensure_ascii=False)) return 0 if result.get("valid", True) else 1 if __name__ == "__main__": raise SystemExit(main()) -
validate-blockscout-address-counters.sh 717 B
#!/bin/bash # Compatibility adapter for sweep-core.py validate. set -eu if [ "$#" -ne 0 ]; then echo "Usage: validate-blockscout-address-counters.sh < response.json" >&2 exit 2 fi script_dir=$(CDPATH='' cd "$(dirname "$0")" && pwd) set +e result=$(python3 "$script_dir/sweep-core.py" validate blockscout-address-counters) rc=$? set -e if [ "$rc" -ne 0 ]; then echo "Error: response is not a conformant Blockscout address-counters object." >&2 exit "$rc" fi printf '%s\n' "$result" | jq -r ' "transactions_count=\(.counts.transactions_count)", "token_transfers_count=\(.counts.token_transfers_count)", "gas_usage_count=\(.counts.gas_usage_count)", "validations_count=\(.counts.validations_count)" ' -
validate-etherscan-transfer-topics.sh 689 B
#!/bin/bash # Compatibility adapter for sweep-core.py validate. set -eu if [ "$#" -ne 1 ]; then echo "Usage: validate-etherscan-transfer-topics.sh <address> < response.json" >&2 exit 2 fi script_dir=$(CDPATH='' cd "$(dirname "$0")" && pwd) set +e result=$(python3 "$script_dir/sweep-core.py" validate etherscan-transfer-topics --address "$1") rc=$? set -e if [ "$rc" -ne 0 ]; then echo "Error: response does not prove distinct inbound-only and outbound-only Transfer topic OR semantics." >&2 exit "$rc" fi printf '%s\n' "$result" | jq -r ' "transfer_topic_query=conformant", "outbound_only_results=\(.outboundOnlyResults)", "inbound_only_results=\(.inboundOnlyResults)" '
-
-
SKILL.md 9.9 KB
--- argument-hint: "<chain-name-or-id|address|transaction-hash|order-id>" compatibility: Requires the `routemesh` CLI initialized on macOS with `routemesh init` for RouteMesh requests. coordination: exempt name: evm-atlas skill-dependencies: - chromium-browser - cli-cast description: "Use for targeted EVM chain, account, transaction, RPC, explorer, bridge, and DEX evidence: chain name/ID, native symbol, RouteMesh, wallet balances and DeFi positions via DeBank/Blockscan in Chromium, cross-chain USD portfolio value or net worth, token/NFT holdings/transfers, tx history, funding origin via Etherscan/Blockscout/Chainscout; Across, Bungee, deBridge, Gas.zip, Hop, Layerswap, LayerZero, LI.FI, Relay, Socket, Symbiosis; Uniswap v1-v4, Universal Router, Permit2, 1inch Classic/Fusion/Fusion+, and CoW Swap, CoWSwap, CoW Protocol, or GPv2 swaps, orders, liquidity, approvals, permits, rewards, migrations, wrapping, cancellations, and refunds." --- # EVM Atlas This skill is coordination-exempt: skip the ai-coord gate for its declared work. Resolve and query only the target mainnets in `references/generated/target-mainnets.json`, under a strict read-only boundary. Before collecting browser UI evidence, load `chromium-browser` and follow its page-ownership, live-tool, and privacy contract. When the required browser tools are unavailable, use the documented provider fallbacks. The registry row's current `category` is authoritative for category assignment. For an exact-zero native sweep or a question about category-specific fee behavior, read [chain categories](references/chain-categories.md) after resolving the target. Do not infer a historical category or maintain a prose roster of target chains. ## Scope and Authority - Match displayed names, numeric chain IDs, and aliases from `references/generated/chain-aliases.json` to the authoritative target-mainnet rows. - If a chain is absent, do not route through another provider, web search, Chainlist, or an unlisted RPC to work around scope. Ask for a feature request at <https://github.com/PaulRBerg/agent-skills>. - Own every discrete read and bounded live subscription handed off by `cli-cast`, including chain, block, fee, nonce, `eth_call`, `eth_estimateGas`, transaction, receipt, log, balance, code, storage, proof, and ENS queries. Complete the read here even when its result will prepare, simulate, or verify later state-changing work. - Never sign messages, submit signatures, execute bridge steps, or broadcast transactions. Route state-changing Cast work to `cli-cast`. - DEX support is historical and evidence-only. Do not discover live quotes, construct or simulate new trades, prepare approvals or permits, submit orders, administer protocols, interpret CoW AMM positions, handle standalone 1inch limit orders, or assign semantics to arbitrary Uniswap v4 hooks. - Do not default to Ethereum. Infer from explicit chain context and unambiguous chain-specific tokens; ask when ambiguous. - Never echo, interpolate, or log API-key values (`ETHERSCAN_API_KEY`, `BLOCKSCOUT_API_KEY`, RPC keys). Check presence value-free with `[ -n "$ETHERSCAN_API_KEY" ] && echo set || echo unset`; never put `${VAR:-...}` or `${VAR:+...}` expansions in printed output. - Keyless Blockscout is sunset (July 2026) and hosted `*.blockscout.com` instance subdomains also rate-limit keyless traffic, so route every Blockscout-hosted chain through the keyed `https://api.blockscout.com/{chain_id}` gateway. See `references/explorers/blockscout-endpoints.md`. - Every agent on the host shares DeBank's rate limit. Hold a `scripts/debank-gate.py` lease for any debank.com access, including a quick profile look; see the Global Queue in `references/workflows/debank-portfolio.md`. - An unreachable or erroring indexer is a coverage gap, never evidence of zero activity. Confirm in Chromium before recording an endpoint as down or blocked, and state the verification method in results. ## Routing 1. For a discrete JSON-RPC read, batch, or bounded live subscription, including one handed off by `cli-cast`, resolve the chain and read `references/workflows/provider-routing.md`. Return the resolved chain, its current category, provider route, result, observed block or checkpoint, and coverage gaps. Do not route the read back to `cli-cast`. 2. For the current native or fungible-token balances or DeFi positions of a public wallet address across chains, read `references/workflows/debank-portfolio.md` first. For one named chain, read `references/workflows/blockscan-balances.md` first. 3. For the current USD value of one or more addresses across target chains (portfolio value, net worth, drained or dust checks), read `references/workflows/address-usd-value.md`. 4. For a specific transaction hash on a named chain, resolve the chain against `references/generated/target-mainnets.json`, then read `references/workflows/provider-routing.md` directly for the transaction facts. Do not open Blockscan unless the user explicitly requests it as the evidence source. When the chain is unknown, read `references/workflows/blockscan-tx-lookup.md` once to resolve it. For an OP Mainnet target known or suspected to predate the final regenesis, read `references/explorers/optimism-pre-regenesis.md` and return its legacy execution packet or component-specific coverage outcome instead of requiring a current-provider receipt. Otherwise, acquire the exact provider receipt and logs before DEX or bridge outcome interpretation. 5. For an address-wide historical-activity or `bootstrap-discovery` sweep, read `references/workflows/address-sweeps.md` and use its deterministic plan/evaluate helper. For current holdings, use `references/workflows/debank-portfolio.md` first and provider routing for gaps. 6. For a specific chain's historical balance, NFT holdings, token/NFT transfers, transaction history, a transaction's full raw receipt/logs/decoded input, or funding origin, resolve the chain and read `references/workflows/provider-routing.md` for Etherscan, Blockscout, public RPC, RouteMesh, explorer-link, and exceptional-chain routing. 7. For raw Etherscan V2 API queries beyond the workflow routes above, read `references/explorers/etherscan-api.md`. 8. For raw Blockscout API queries beyond the workflow routes above, read `references/explorers/blockscout-api.md`. 9. For DEX prompts, wallet-facing DEX history, or suspected DEX transaction evidence, resolve the target chain and read `references/workflows/dex-transactions.md`. Load only the matching protocol-family reference: - Uniswap v1-v4, Universal Router, or Permit2: `references/dexes/uniswap.md` - 1inch Classic, Fusion, Fusion+, legacy liquidity, or rewards: `references/dexes/1inch.md` - CoW Swap, CoWSwap, CoW Protocol, or GPv2: `references/dexes/cow-protocol.md` 10. Treat 1inch and CoW as execution protocols. Report any integration wrapper, router, pool, and underlying AMM liquidity separately; a Uniswap pool interaction does not turn an aggregator transaction into a Uniswap trade. 11. For bridge-related prompts or transaction evidence, confirm known origin/destination chains are targets, then load only the matching reference: - Across: `references/bridges/across.md` - Bungee / Socket: `references/bridges/bungee.md` - Circle / CCTP / Gateway: `references/bridges/circle.md` - deBridge / DLN: `references/bridges/debridge.md` - Gas.zip: `references/bridges/gaszip.md` - Hop: `references/bridges/hop.md` - Layerswap: `references/bridges/layerswap.md` - LayerZero / Stargate / OFT / Aori: `references/bridges/layerzero.md` - LI.FI: `references/bridges/lifi.md` - Relay / Relay.link: `references/bridges/relay.md` - Symbiosis: `references/bridges/symbiosis.md` - 1inch Fusion+: `references/dexes/1inch.md` 12. Treat bridge and DEX APIs as enrichment. Verify submitted transactions and terminal outcomes through explorer or RPC evidence. ## Completion Return the resolved target chain, current category, provider route, requested on-chain facts, and source URLs/transaction identifiers. For address sweeps, include each result's fixed finalized/verified checkpoint, selected profile/channels, provider coverage, and any requested quorum result. Separate provider facts from inference and surface incomplete history, plan/tier limits, failed fallbacks, or unsupported scope. Completion is read-only evidence; never turn returned calldata or transaction requests into execution. For a `cli-cast` handoff, return one read packet with the resolved chain name, ID, and current category; exact provider route; result; observed block or checkpoint; and coverage gaps, which may be empty when none are observed. Do not include a signing or broadcast command. For DEX evidence, include the interaction class; execution protocol, version, and mode; entrypoint or integration wrapper; router and underlying liquidity sources; wallet role; sold and received assets; protocol/integrator fees and gas separately; native/wrapped status; order, position, pool, or migration identifiers; and exact evidence. Do not call an approval-only or failed transaction a completed trade. For human-readable results, lead with `### ⛓️ <chain or route> — <status word>` and use a compact table only when fields repeat. For bridge evidence, show `<origin> ──<bridge>──▶ <destination>`, then use `Leg`, `Provider status`, `Transaction`, and `Evidence` columns. Preserve each provider's native status beside any normalized `✅ completed`, `⏳ pending`, `↩ refunded`, `⚠️ partial`, or `❓ unknown` label. Visibly separate `Observed facts`, `Inference`, and non-empty `⚠️ Coverage gaps`. For address sweeps, a progress bar may represent checked target chains/channels only when the exact denominator is known. Keep unsupported-scope and safety explanations direct. Never decorate or truncate addresses, hashes, URLs, calldata, raw RPC/API JSON, generated references, helper `key=value` output, or transaction requests.
Comments (0)
Sign in to join the conversation.
Reviews (0)
No reviews yet.
No comments yet.