OrderFilled events, filters them to a configurable set of wallets, and webhooks each fill into a Compose app that signs and submits the same trade to the Polymarket CLOB. Winning shares auto-redeem on a 5-minute cron.
It demonstrates the Turbo → Compose webhook pattern, cron triggers, sponsored-gas wallets, ctx.fetch for external APIs, and collection-based state.
How it works
- Polygon emits
OrderFilledfrom the CTF Exchange and NegRisk Exchange contracts - Turbo pipeline decodes fills, keeps only trades where maker or taker is in your watched list, and posts each to a Compose HTTP task
copy_tradeparses the fill, checks the wallet’s USDC balance, looks up market metadata, signs a FAK (Fill-and-Kill) order, and submits it via the Fly.io proxy (Polymarket’s CLOB is geo-blocked from US hosts)redeemruns every 5 minutes, polls Polymarket’s data API for redeemable positions, and callsredeemPositionson-chain
Prerequisites
- Goldsky CLI installed
- A Compose API token from your Goldsky project (for the webhook auth secret)
- An EOA private key for the bot’s wallet — no Polymarket UI onboarding or proxy wallet required
- USDC.e (
0x2791Bca1f2de4661ED88A30C99A7a9449Aa84174) on Polygon to fund the bot - A list of wallets you want to mirror (selecting them is out of scope for this guide)
Project structure
Step 1: Set up the project
Clone the example repository and install dependencies:node_modules/, so npm install is required before goldsky compose start or deploy.
Step 2: Pick wallets to copy and update both configs
The pipeline pre-filters on-chain events to the watched list, so the same addresses must appear in two places. Incompose.yaml:
pipeline/polymarket-ctf-events.yaml, update the watched_fills transform:
Step 3: Set the wallet private key
The Compose app signs CLOB orders as an EOA. No Polymarket proxy wallet is involved:Step 4: Create the webhook auth secret
The Turbo webhook authenticates to your Compose app with a Goldsky-level secret. This is a one-time setup per project:Step 5: Deploy the app and pipeline
Step 6: Fund the wallet
Send USDC.e to the EOA that corresponds to the private key from step 3. Compose sponsors gas on all on-chain calls, so you do not need MATIC in the wallet.Step 7: Grant approvals
One-time ERC-20 and ERC-1155 approvals to the CTF Exchange and NegRisk Exchange. Compose sponsors the four on-chain transactions:Understanding the code
Parsing a fill
copy_trade receives the decoded OrderFilled row and determines the direction the watched wallet took. The USDC side of the swap is tagged with asset_id = "0"; whichever side gave USDC is the buyer:
On-chain balance as source of truth
Before every BUY, the task reads USDC.e balance directly from Polygon rather than keeping a local counter:Signing and submitting via ctx.fetch
Polymarket’s @polymarket/clob-client SDK uses axios for HTTP, which fails under Compose’s task runtime because task binaries run without --allow-net. The template reuses the SDK’s pure signing utilities (local crypto only) and routes every HTTP call through ctx.fetch:
ctx.fetch is host-mediated, which means it has network access even though the task itself does not. This is the general pattern for calling external APIs from a Compose task — see calling external APIs for more.
Why the Fly.io proxy
Polymarket’s CLOB API geo-blocks the US. Compose tasks run fromus-west. The template points CLOB_HOST at a shared Goldsky-hosted Fly.io proxy in Amsterdam that forwards every request from an EU IP. If you want to isolate yourself from the shared proxy, deploy your own copy of fly-polymarket-proxy and update CLOB_HOST in compose.yaml.
Key Compose features used
- Turbo → Compose webhook pattern — a pipeline sinks decoded on-chain events directly to an HTTP task via a
COMPOSE_WEBHOOK_AUTHsecret ctx.fetch— all outbound HTTP (CLOB, Gamma, Polymarket data API, Polygon RPC) goes through Compose’s host-mediated fetchctx.evm.walletwithsponsorGas: true— approvals and redemptions use a Compose-sponsored wallet so the EOA does not need MATIC- Cron triggers — the
redeemtask runs every 5 minutes via0 */5 * * * * ctx.collection—positionsandtradescollections persist bot state across invocations- Compose secrets — the wallet private key is stored via
goldsky compose secret set, not baked into the manifest
Customization
Trade size
The default is Polymarket’s $1 minimum notional. Raise it incompose.yaml:
Different markets
The pipeline filters by address only, so it picks up every fill on the CTF Exchange and NegRisk Exchange. To scope to a specific market type (e.g. a particular event’s outcomes), add atoken_id filter to the watched_fills transform.
Your own proxy
CLOB_HOST defaults to the Goldsky-hosted Fly proxy. To isolate from it, deploy fly-polymarket-proxy yourself and point CLOB_HOST at your deployment.