Website: https://foresight-predict.vercel.app/
Foresight is a prediction app built on Arc Testnet, using USDC for gas payments. Powered by an autonomous AI agent that makes 5 min, 15 min, 1 hour, and 4 hour BTC and ETH predictions, gets paid, and refunds the user 90% when wrong.
Foresight is an autonomous prediction agent built on Arc that brings AI-powered forecasts to Polymarket's short-term Bitcoin and Ethereum markets, 5 minutes, 15 minutes, 1 hour, or 4 hours out. Every prediction is requested and paid for through Circle's x402 Nanopayments, a gasless, signed agentic payment protocol, and the agent itself holds a verifiable onchain identity and reputation record under the ERC-8004 standard, so its real accuracy can be checked independently, not just claimed.
Built on Arc, where USDC is the native gas token, so there's nothing extra to hold just to pay network fees. A live example of the agentic economy in action.
Foresight uses Binance klines as the primary price source, since it updates every minute with no API key needed, and Polymarket's own CLOB midpoint as a comparison signal, since it shows what the market itself is pricing the outcome at right now — letting the app show when its own prediction agrees or disagrees with the crowd.
A live timer shows how much time remains until the prediction resolves; once it hits zero, a Check Outcome button appears.
Foresight has four wallets, each with one clear job.
- Seller is where the $0.01 payment first lands when someone pays for a prediction.
- Escrow is where that money actually ends up living, it either keeps the $0.01 if the prediction was right, or sends 90% of it back if the prediction was wrong.
- Validator is the wallet that holds the AI agent's onchain identity (ERC-8004, agent ID 851687) — a name tag proving the agent is a real, trackable thing, not invisible code.
- Reporter is a separate wallet that writes down, permanently, whether each prediction the Validator checked was correct or wrong, so anyone can look up its real track record.
| Wallet | Env variable | Type | What it does |
|---|---|---|---|
| Seller | X402_SELLER_ADDRESS, X402_SELLER_PRIVATE_KEY | Self-custodied | Receives each $0.01 payment |
| Escrow | ESCROW_WALLET_ADDRESS | Circle-managed | Holds the funds and sends refunds when a prediction is wrong |
| Validator | VALIDATOR_WALLET_ADDRESS, VALIDATOR_WALLET_ID, VALIDATOR_AGENT_ID | Circle-managed | Holds the AI agent's onchain identity |
| Reporter | REPORTER_WALLET_ADDRESS, REPORTER_WALLET_ID | Circle-managed | Independently records whether each prediction was right or wrong |
Visitor's own wallet
-> deposits USDC into their own Gateway balance
-> pays $0.01 (signed, gasless) -> lands in Seller's Gateway balance
-> sweep fires automatically -> burns from Seller's Gateway balance
-> mints real onchain USDC into Escrow
-> market resolves:
correct prediction -> Escrow keeps the $0.01
wrong prediction -> Escrow sends 0.009 USDC back to the visitor
Validator (has an onchain identity, ERC-8004 agent ID 851687)
-> identity registered via IdentityRegistry.register()
Reporter (independent observer, never the Validator itself,
required by ERC-8004's anti-self-dealing rule)
-> once a prediction's market has resolved, submits a real feedback
transaction to ReputationRegistry.giveFeedback():
correct prediction -> score 100, tag "prediction_correct"
wrong prediction -> score 20, tag "prediction_incorrect"
-> each one is a real, independently verifiable onchain event
Once the market closes, a Check Outcome button appears in the app so you can verify the result yourself. If the prediction was wrong, the Escrow wallet automatically refunds you 90% of the fee, and the result then stays fixed in place, so the same prediction is never checked or refunded twice.
Note on the name: the project was originally called PolyPredict, but the name was already in use by another product, so it was changed to Foresight. The only place "PolyPredict" still appears is the Validator agent's onchain ERC-8004 identity, registered before the rename. Since that registration is permanent, it keeps the original name even though the project itself is now Foresight.
foresight/
├── lib/
│ ├── analyst-agent.ts # Combines Binance price signals + CLOB odds into a prediction
│ ├── arc.ts # Arc network config: chain ID, RPC, USDC address
│ ├── gateway-deposit.ts # Gateway balance deposit helpers (frontend-facing)
│ ├── gateway-signer.ts # EIP-712 signing for gasless x402 payments
│ ├── history.ts # Best-effort local history (not used for /stats on Vercel)
│ ├── polymarket.ts # Fetches market data from Polymarket's Gamma API + CLOB
│ ├── stats-onchain.ts # /stats: reads ERC-8004 reputation via view functions (onchain)
│ └── validator-agent.ts # Checks a resolved market's outcome, decides match/refund
├── server/
│ ├── index.ts # Express app entry point, mounts all routes
│ ├── predict.ts # POST /predict, x402-gated, returns a prediction
│ └── validate.ts # POST /validate (checks outcome, refund + feedback), GET /stats
├── scripts/
│ ├── create-validator-identity.ts # Registers the Validator's ERC-8004 identity
│ ├── generate-seller-wallet.ts # Creates the Seller wallet (viem keypair)
│ ├── get-validator-reputation.ts # Reads back the Validator's onchain track record
│ ├── refund-user.ts # Sends the 90% refund from Escrow
│ └── sweep-to-escrow.ts # Sweeps Seller's Gateway balance into real onchain Escrow USDC
├── frontend/
│ ├── App.tsx # Main UI: wallet connect, Gateway deposit, predictions, Check Outcome
│ ├── main.tsx # React entry point
│ └── styles.css # Design system + component styles
├── tests/
│ └── run.ts # Deterministic unit tests (npm test)
├── .env.example
└── README.md
Visitor's wallet (MetaMask)
|
v
EIP-6963 wallet discovery + viem (wallet connect, chain check, EIP-712 signing)
|
v
frontend/App.tsx
|
+-- reads market data via: lib/polymarket.ts -> Polymarket Gamma API + CLOB
+-- reads price signals via: lib/analyst-agent.ts -> Binance klines
+-- pays via: x402 Nanopayments -> server/predict.ts
+-- checks outcome via: POST /validate -> server/validate.ts
|
v
server/validate.ts
|
+-- resolves market: lib/validator-agent.ts
+-- refunds if wrong: scripts/refund-user.ts -> Escrow wallet
+-- logs reputation: ReputationRegistry.giveFeedback() -> Reporter wallet
Foresight deploys as two separate Vercel projects from one repository: the frontend (foresight-predict.vercel.app, Vite preset) and the backend (foresight-api.vercel.app, Express preset). The frontend's vercel.json rewrites every /api/* request to the backend project.
Why not one Express + Vite project? Vercel snapshots public/** for its static builder before the build command runs. On a clean deploy the freshly-built frontend doesn't exist yet, so the static output is always empty and every request falls through to Express — the classic Cannot GET /. Splitting the projects lets each one use the builder it was made for.
vite.config.tsbuilds intodist/(Vite's default). The earlier setup pointedbuild.outDiratpublic/, which made Vercel's static collection non-deterministic — empty builds on some deploys, a race between Vite's output and the static builder on others.dist/keeps the two apart.typescriptis pinned to6.0.3. TypeScript 7's native compiler doesn't exposets.systhe way@vercel/node's tooling expects, so the backend build breaks on TS7.- The Circle SDK (
@circle-fin/developer-controlled-wallets) is loaded lazily withcreateRequire(import.meta.url)+require(), which resolves to the package's CJS build (dist/*.cjs.js). Vercel's Node runtime cannot load the package's ESM build (dist/*.es.js— the package has no"type": "module"and.es.jsis not a recognized ESM extension there), so a dynamicimport()crashed/validatewith "Cannot use import statement outside a module". Loading lazily also means a Circle-side failure only breaks the function that uses it, never the whole server module. package.jsonoverridesuuidto^11.1.1.@circle-fin/app-kitunconditionally requires@solana/web3.js, which pulls inrpc-websockets, whichrequire()suuid— and uuid 14 dropped CommonJS entirely.- The wallet layer doesn't use wagmi. Wallets are discovered through raw EIP-6963
announceProviderevents, connected with a directeth_requestAccounts, and switched to Arc withwallet_switchEthereumChain(falling back towallet_addEthereumChainon 4902) — all on the chosen wallet's own provider object, with viem clients created directly. wagmi's connector/client wrappers silently blocked the network switch in real wallets; the raw-provider path is how working Arc dApps (AnchorPay, ArcShift) do it. - Arc Testnet parameters: chain ID 5042002, RPC
https://rpc.testnet.arc.io(fallbackhttps://rpc.testnet.arc.network), USDC as the native gas token with 18 decimals, explorertestnet.arcscan.app.
Testing the wallet layer: test locally with npm run dev and a real wallet (MetaMask or Rabby) — not by deploying. Local dev runs on tsx, which is more permissive about ESM/CJS interop than Vercel's runtime, so a bug that only appears after deploy is the expensive way to find it.
| Contract | Address | Purpose |
|---|---|---|
| IdentityRegistry (ERC-8004) | 0x8004A818BFB912233c491871b3d84c89A494BD9e | Holds the Validator's onchain identity, agent ID 851687 |
| ReputationRegistry (ERC-8004) | 0x8004B663056A597Dffe9eCcC1965A193B7388713 | Stores every giveFeedback() outcome the Reporter submits |
| USDC (ERC-20 interface) | 0x3600000000000000000000000000000000000000 | Used for all deposits, payments, and refunds |
View the Validator's identity onchain: https://testnet.arcscan.app/token/0x8004A818BFB912233c491871b3d84c89A494BD9e/instance/851687
View a verified onchain reputation write (a NewFeedback transaction from the Reporter): https://testnet.arcscan.app/tx/0x3b2ca029cbc2898b5b13b2ebec1978734e3c8c94b09dddbced65d91720103c38
Foresight's track record is not stored on any server. The stats panel reads it directly from the agent's ERC-8004 reputation on Arc through ReputationRegistry view functions (getClients + getSummary) — a couple of eth_calls, no log scanning, no files, nothing to edit, reset, or lose when the app redeploys.
A prediction enters the track record only after you press Check outcome and the result is written on chain (a NewFeedback event; value 100 = correct, 20 = incorrect). That is why resolved always equals predictions: only resolved predictions are recorded. Every checked prediction, right or wrong, gets a permanent onchain log entry.
- Chain ID: 5042002
- Native gas token: USDC, two interfaces on one token: native gas balance uses 18 decimals, the ERC-20 interface (0x3600...) uses 6 decimals. All app-level transfers use the 6-decimal ERC-20 interface.
- Explorer: testnet.arcscan.app
- RPC: primary https://rpc.testnet.arc.io, fallback https://rpc.testnet.arc.network, configured in lib/arc.ts (single source of truth for the chain everywhere).
- Nanopayments: built on @circle-fin/x402-batching, not the generic x402-foundation packages, since Arc support is specific to Circle's own Gateway-based implementation.
Node.js and React, Circle's Nanopayments and wallet tools for instant USDC payments, live Binance and Polymarket data for price signals, OpenAI for reasoning, and the ERC-8004 standard on Arc Testnet for the agent's onchain identity and track record.
| Layer | Technology |
|---|---|
| Backend | Node.js / TypeScript, Express |
| Frontend | React, Vite, viem (EIP-6963 wallet discovery) |
| Payments | @circle-fin/x402-batching (gasless Nanopayments) |
| Wallets | @circle-fin/developer-controlled-wallets |
| Price signals | Binance public klines API |
| Market signal | Polymarket Gamma API + CLOB midpoint |
| Reasoning | OpenAI |
| Identity & reputation | ERC-8004 (IdentityRegistry, ReputationRegistry) on Arc Testnet |
npm install
cp .env.example .envFill in .env with:
- A Circle API key and registered Entity Secret (from console.circle.com)
- Fresh Seller, Validator, and Reporter wallet credentials (see
scripts/) - An OpenAI API key
Fund your wallet with testnet USDC at faucet.circle.com, then deposit into your Gateway balance from the app's onboarding flow before making a prediction.
Run the backend and frontend:
npm run server # starts the Express API on :3001
npm run dev # starts the Vite frontend on :5173A judge or reviewer can verify the whole flow themselves, end to end:
- Open the app and click Connect Wallet. Confirm you're prompted to switch to Arc Testnet if you're not already on it.
- Enter an amount in AMOUNT and click Deposit. Confirm GATEWAY BALANCE updates after the transaction settles.
- Type a question like "Bitcoin up or down?", or paste a Polymarket market link.
- Click Predict on the 5 Min column. Approve the signature prompt in your wallet, this is gasless, not a transaction.
- Confirm a prediction renders: outcome (UP/DOWN), a confidence score, and a plain-language explanation referencing real price data.
- Watch the countdown tick down live. Once it reaches zero, confirm a Check Outcome button appears.
- Click it. Confirm the result renders: either a green "correct, fee kept" message, or a red "wrong, refunded" message with a transaction hash linking to Arcscan.
- Click the transaction link and confirm it resolves to a real, confirmed transaction on Arc Testnet's explorer.
.env is never committed. Every credential (API key, entity secret, private keys) must be generated fresh by anyone cloning this repo, see .env.example for the required variable names.
This is a testnet project built for the Arc Agentic Economy Hackathon and is not intended for production use without further security review.
