ERC-1155 balanceOf calldata encoding/decoding + eth_call glue for autotrade on-chain CTF reconciliation. Lives in crates/polymarket-fetch/src/pmv2/autotrade/onchain.rs.

What Was Built

ctf_balance_of(proxy_wallet, token_id, rpc_url) — an async function that encodes an ERC-1155 balanceOf(address,uint256) calldata, fires an eth_call RPC request, and decodes the returned uint256 balance. Pure build-verification only (not tested via network); three unit tests cover encode, decode, and bad-address rejection.

Files Changed

FileChange
crates/polymarket-fetch/Cargo.tomlAdded alloy-sol-types and alloy-primitives as workspace deps
crates/polymarket-fetch/src/config.rsAdded polygon_rpc_url: String field + POLYGON_RPC_URL env load
crates/polymarket-fetch/src/pmv2/autotrade/mod.rsAdded pub mod onchain;
crates/polymarket-fetch/src/pmv2/autotrade/onchain.rsCREATED — ERC-1155 calldata encode/decode + eth_call glue

Alloy sol! Struct Name Generation

sol! macro generates a struct named balanceOfCall — camelCase function name + Call suffix — for balanceOf(address,uint256). The right encoding method is SolCall::abi_encode(), not any manual packing.

use alloy_sol_types::SolCall;
let call = balanceOfCall { account: proxy, id: token_id };
let calldata = call.abi_encode();

For Agents

The Call suffix is consistent across all sol!-generated function structs. If the function is fooBar(uint256) the type is fooBarCall. Use SolCall::abi_encode() for the calldata bytes.

alloy_primitives::hex Re-Export

alloy-primitives re-exports a hex module directly — no separate hex crate needed.

use alloy_primitives::hex;
let hex_str = hex::encode(&calldata); // lowercase, no 0x prefix

The RPC eth_call data field requires a "0x" prefix — prepend manually: format!("0x{}", hex::encode(...)).

Selector Verification

balanceOf(address,uint256) selector is 0x00fdd58e — the ERC-1155 standard.

Different from ERC-20

ERC-20 balanceOf(address) selector is 0x70a08231. These are completely different functions. The CTF contract is ERC-1155, so 0x00fdd58e is always correct here.

Spike Doc Value Error — pUSD Balance

The spike findings doc (docs/superpowers/specs/2026-06-14-pmv2-autotrade-spike-findings.md) contains an error in the hex-to-decimal conversion table:

FieldSpike Doc SaysCorrect Value
Hex0x2c641ab60x2c641ab6
Decimal744144054744757942
pUSD amount744.144054 pUSD744.757942 pUSD

The plan spec test originally used 744_144_054u128 — this is wrong. The actual unit test uses 744_757_942u128. The narrative “Bandi 744 pUSD” is an approximation; the exact on-chain raw value should be verified against a live RPC call in Task 7 preflight.

Verify in Task 7

744_757_942 is the corrected decode of 0x2c641ab6 but the live balance will have changed since the spike. Task 7 preflight must fire a live eth_call and treat whatever comes back as the ground truth.

Binary-Only Crate: Test Invocation

polymarket-fetch is binary-only — there is no src/lib.rs. Tests cannot be run with --lib.

# WRONG
cargo test --lib -p polymarket-fetch
 
# CORRECT
cargo test --bin polymarket-fetch -p polymarket-fetch
# or simply
cargo test -p polymarket-fetch

Config Pattern

polygon_rpc_url was added to AppConfig with #[allow(dead_code)] — it is loaded from POLYGON_RPC_URL env, defaulting to https://polygon-rpc.com. The #[allow(dead_code)] suppression will be removed when Task 7 preflight consumes it.

Test Coverage

Three pure unit tests, zero network calls:

  1. encodebalanceOf(address,uint256) calldata includes the 0x00fdd58e selector prefix
  2. decode0x000…002c641ab6 decodes to 744_757_942u128
  3. bad-address rejection — a non-checksummed / invalid hex address string returns an Err

ctf_balance_of itself is #[allow(dead_code)] and build-verified only; it will be tested end-to-end in Task 7.

Gate Result

PASS (exit 0). All 256 tests pass including the 3 new onchain unit tests.