Querying Polygon Contracts with evmquery: Chain ID, Proxy Resolution, and a Live Example

evmquery now reads Polygon (evm_polygon, chain ID 137) contracts through REST, MCP, and n8n — same ABI resolution and proxy unwinding as Ethereum, Base, and BNB Chain.

evmquery team··8 min read
Share
Querying Polygon contracts with evmquery — chain ID 137 reference

Polygon is the fourth chain evmquery reads, joining Ethereum, Base, and BNB Smart Chain. The addition doesn’t change how you write queries — it changes one field. Swap chain to evm_polygon and every schema, expression, and CEL helper you already use keeps working, including the proxy-unwinding logic that turns Aave’s minimal proxy contract into a full, callable ABI.

Polygon on evmquery — key facts

  • Chain identifier evm_polygon, chain ID 137 — this is Polygon PoS mainnet, not Polygon zkEVM (chain ID 1101), which evmquery does not support.
  • Same ABI resolution pipeline as every other supported chain: verified source first, then known interfaces, selector databases, and bytecode recovery, following EIP-1967 and other proxy standards automatically.
  • Available on every surface — REST (schema.chain: "evm_polygon"), MCP, and the n8n node — with no separate signup or API key.
  • Verified live: Aave V3’s Pool contract on Polygon (0x794a61358D6845594F94dc1DB02A252b5b4814aD) resolves through an EIP-1967 proxy to its implementation and exposes the full getUserAccountData, getReserveData, and getReservesList surface.
  • Free tier limits (60 units/min, 700 units/hour) apply per chain the same way they do on Ethereum, Base, and BNB Smart Chain.

TL;DR

evmquery reads Polygon PoS (evm_polygon, chain ID 137) through the same REST, MCP, and n8n surfaces used for Ethereum, Base, and BNB Smart Chain, with identical ABI resolution and EIP-1967 proxy unwinding. Every example below is validated live against Aave V3’s Pool contract on Polygon.

Querying Polygon via REST

The only thing that changes from an Ethereum query is the chain field. Here’s a live read of Aave V3’s reserve list on Polygon:

curl -X POST https://api.evmquery.com/api/v1/query \
-H "x-api-key: YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"chain": "evm_polygon",
"schema": {
"contracts": { "aave_pool": { "address": "0x794a61358D6845594F94dc1DB02A252b5b4814aD" } }
},
"expression": "aave_pool.getReservesList()"
}'

This is a real response, captured against the live API:

{
"result": {
"value": [
"0x8f3cf7ad23cd3cadbd9735aff958023239c6a063",
"0x2791bca1f2de4661ed88a30c99a7a9449aa84174",
"0x7ceb23fd6bc0add59e62ac25578270cff1b9f619",
"0xc2132d05d31c914a87c6611c10748aeb04b58e8f",
"0x3c499c542cef5e3811e1192ce70d8cc03d5c3359"
],
"type": "list<sol_address>"
},
"meta": { "blockNumber": 91651660 }
}

(The full list has 21 reserves; the excerpt above is enough to make the next section’s point.) No auth flow, no separate Polygon API key, no different request shape — the chain field is the entire integration surface.

The same holds for MCP. If you already have evmquery wired into Claude, Cursor, or another MCP client, Polygon shows up as another value for the chain parameter on execute_query and describe_schema — no reconnection, no new tool registration, no separate credentials. If your integration is a client that lets an agent pick the chain per request, Polygon support means one more valid answer to “which chain,” not a new code path.

Same ABI resolution, same proxy unwinding

Aave doesn’t deploy its Pool contract as a plain, single-address contract. It sits behind an EIP-1967 transparent proxy, which is the standard pattern for upgradeable DeFi protocols: the address you call (0x794a...4aD) holds no logic of its own, it just delegates every call to an implementation contract that can be swapped out later without changing the address integrators depend on.

Calling getUserAccountData directly against the proxy’s own bytecode would resolve nothing useful — the proxy’s real ABI is just implementation() and a couple of admin functions. evmquery resolves through the proxy automatically: it reads the EIP-1967 implementation slot, fetches the verified source for the implementation contract from Sourcify, and exposes every one of its methods as if they lived at the proxy address. This is the same resolution pipeline evmquery runs on Ethereum, Base, and BNB Smart Chain — nothing Polygon-specific was needed to make it work, which is the point of testing a new chain against a contract this indirected instead of a flat ERC-20.

You can see the resolution yourself with describe_schema, which annotates each method with where it actually reads from:

{
"chain": "evm_polygon",
"schema": { "contracts": { "aave_pool": "0x794a61358D6845594F94dc1DB02A252b5b4814aD" } },
"include": ["contracts", "resolution"]
}

The response shows dispatches via eip1967 to 0x6030dB989D47cD74FC17bB6F4FcD3A8B29FEe57e on the contract, and every method (getUserAccountData, getReserveData, getReservesList, getConfiguration, and 25 others) annotated with sourcify, reads from 0x6030d... — the implementation address, not the proxy.

Worked example: reserve rates on Polygon

The reserve list above includes two addresses that look similar but aren’t: 0x2791bca1...84174 is bridged USDC (USDC.e, wrapped from Ethereum via the Polygon PoS bridge), and 0x3c499c54...5c3359 is native, Circle-issued USDC. They’re different assets with different liquidity — a common gotcha if you’re pattern-matching by symbol instead of address. This query reads the live supply rate for native USDC:

curl -X POST https://api.evmquery.com/api/v1/query \
-H "x-api-key: YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"chain": "evm_polygon",
"schema": {
"contracts": { "aave_pool": { "address": "0x794a61358D6845594F94dc1DB02A252b5b4814aD" } },
"context": { "asset": "sol_address" }
},
"context": { "asset": "0x3c499c542cef5e3811e1192ce70d8cc03d5c3359" },
"expression": "formatUnits(aave_pool.getReserveData(asset).currentLiquidityRate, 27)"
}'
{
"result": { "value": 0.028524493958061267, "type": "double" },
"meta": { "blockNumber": 91651683 }
}

Aave stores interest rates in “ray” units — fixed-point with 27 decimals — so formatUnits(rate, 27) converts the raw currentLiquidityRate into an annualized fraction. 0.0285 is roughly a 2.85% supply APR for native USDC at the block this was captured. That number moves with utilization, so treat it as a snapshot, not a constant to hardcode.

Worked example: a wallet’s account data

getUserAccountData(user) is the method most integrations reach for — it returns a struct with the fields you need to render a position: total collateral, total debt, and health factor, all denominated in Aave’s base currency (USD, 8 decimals). Here’s a live call against Polygon:

curl -X POST https://api.evmquery.com/api/v1/query \
-H "x-api-key: YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"chain": "evm_polygon",
"schema": {
"contracts": { "aave_pool": { "address": "0x794a61358D6845594F94dc1DB02A252b5b4814aD" } },
"context": { "user": "sol_address" }
},
"context": { "user": "0x000000000000000000000000000000000000dEaD" },
"expression": "cel.bind(d, aave_pool.getUserAccountData(user), { \"totalCollateralBase\": formatUnits(d.totalCollateralBase, 8), \"totalDebtBase\": formatUnits(d.totalDebtBase, 8), \"healthFactor\": formatUnits(d.healthFactor, 18) })"
}'
{
"result": {
"value": {
"totalCollateralBase": 6.88907725,
"totalDebtBase": 0,
"healthFactor": 1.157920892373162e+59
},
"type": "map<string, double>"
}
}

This is a real address with real collateral and zero debt — and the healthFactor value is the edge case worth knowing before you build a monitor on top of it. 2^256 / 1e18 ≈ 1.16e59 is what healthFactor returns when there’s no active debt to divide by; it represents infinite health, not a bug or an overflow. The Aave V3 health factor guide covers the full math and liquidation thresholds if you’re building a monitor that needs to handle this case correctly instead of alerting on a number that looks like garbage.

Where Polygon fits in the multi-chain picture

evmquery’s chain parameter is per-request, not global — an expression written for evm_polygon doesn’t automatically also run on evm_ethereum. If you need the same read across chains (say, a Multicall3 balance scan on both Ethereum and Polygon), that’s still one request per chain, run in parallel by your client, with results fanned in on your side. There’s no expression-level chain switch, and that’s deliberate: mixing state from two different chains inside one CEL expression would blur “this number came from block X on chain Y” in a way that’s easy to get wrong silently.

What Polygon support does change is the floor for “can I build this at all.” A wallet dashboard, a liquidation monitor, or an agent tool that only reads Ethereum, Base, and BNB Smart Chain now has a real gap for the Polygon side of a user’s portfolio — Aave, Uniswap, and QuickSwap all have meaningful TVL there, and a lending or DEX position on Polygon is invisible to a tool that stops at three chains. Adding a fourth chain to an existing integration is usually a config change, not a rewrite: the same contract wrapper that builds a schema.contracts object for Ethereum builds the identical shape for Polygon, with a different chain string and different addresses.

If you’re wiring evmquery into an agent or backend service, evmquery for developers covers the REST and MCP integration patterns this post’s examples are built on.

What’s actually different about Polygon in practice

The query language and resolution pipeline don’t change chain to chain, but three things are worth knowing before you ship:

  • Native gas token is POL, the 2024 rebrand of MATIC. It has zero effect on read-only queries — evmquery’s REST and MCP surfaces never touch gas or send transactions — but it matters if the rest of your stack also writes to Polygon.
  • Bridged vs. native assets carry different addresses. The USDC.e / native USDC split above is the most common instance; the same pattern shows up for other bridged tokens. Don’t assume a symbol you recognize maps to the address you expect — resolve by address, always.
  • Block times run faster than Ethereum, so if you’re polling on an interval, a fixed 12-second Ethereum-tuned poll loop will lag behind Polygon’s actual block cadence. Read meta.blockNumber from the response and adjust your polling interval to the chain instead of hardcoding one value for every chain you query.

None of these are evmquery quirks — they’re Polygon-the-network facts that any integration has to account for, and they’re the reason the worked examples above call getReserveData and getUserAccountData directly against real Polygon state instead of assuming Ethereum’s numbers transfer over.

Next steps

  • Multicall3 batching guide: batch these same reads (Polygon’s Multicall3 deployment is at the identical 0xcA11bde... address) into one round trip instead of one call per method.
  • Aave V3 health factor explained: the full math behind getUserAccountData, including liquidation thresholds and the max-uint256 edge case shown above.
  • Chainlink price feed addresses: a reference for the other contract type most dashboards need alongside lending data.
  • evmquery for developers: REST and MCP integration patterns for wiring evmquery into an agent or backend service.
Share

Query Polygon in the next five minutes

No credit card, no monthly cap on the free tier. Swap chain to evm_polygon and existing schemas carry over unchanged.