Setup: Compose copy-trader
This skill makes the cloned copy-trader example runnable under the user's own Goldsky account. Two moving parts: a Turbo pipeline (pipeline/polymarket-ctf-events.yaml) that indexes Polymarket OrderFilled events on Polygon and webhooks them into Compose, and a Compose app (compose.yaml) with three tasks — copy_trade (http, mirrors each fill), setup_approvals (http, one-time), and redeem (cron every 5 min).
This is the most complex example. Do not skip any preflight or ordering step.
Non-negotiables
- Never run
goldsky compose deploy,goldsky turbo apply,goldsky secret create,goldsky compose secret set,git push, orgh repo createwithout showing the exact command first and getting explicit confirmation. WATCHED_WALLETSmust be identical in two places: theWATCHED_WALLETS:env var insideenv.cloud:ofcompose.yaml, AND themaker IN (...)/taker IN (...)lists inside thewatched_fillsSQL transform ofpipeline/polymarket-ctf-events.yaml. Mismatch = fills get indexed but not mirrored, or vice versa. Triple-check the addresses are lowercased, comma-separated, and identical in casing.- Order of operations matters: deploy the Compose app first, because the pipeline YAML's webhook URL contains the Compose app name. If you deploy the pipeline before the app exists (or with a stale app name), every webhook will 404.
- The
PRIVATE_KEYsecret is a real funded EOA on Polygon mainnet. This is not a testnet. Treat the key with care; do not print it, commit it, or log it. Polymarket CLOB orders are signed as this EOA — orders execute for real money. - US geo-blocking: Polymarket's CLOB API blocks US IPs. Compose hosts may be in the US. The default
CLOB_HOSTis a shared Fly.io proxy in the EU. For production, the user should deploy their own proxy (link in README).
Variable handling for agents
When this skill says $FOO, capture the literal value from the prior command's output and substitute it directly into the next command. Do not rely on shell variables persisting between separate Bash tool invocations — each invocation gets a fresh shell with no env carryover from earlier commands.
Preflight
goldskyCLI —goldsky --version.goldskyauthenticated —goldsky project list. If it errors, stop and tell the user: "Please rungoldsky loginin your terminal — browser flow. Tell me to continue when you see the success message." Do not spawngoldsky loginfrom Bash; it requires an interactive browser.node+npm—npm --version. Runnpm installbefore anything else; Compose bundlespackage-lock.jsondeps with esbuild.foundry(optional) —cast --version. Useful for checking USDC.e balance and deriving an address from a private key during setup.
Step 1 — Configuration interview
- "App name?" (default:
copy-trader) → top-levelname:field incompose.yaml. This also becomes a path segment in the pipeline's webhook URL (theurl:undersinks.copy_trade_webhookinpipeline/polymarket-ctf-events.yaml). If you rename it, you'll update both places in Step 4. - "Which wallets do you want to copy?" — one or more Polygon EOAs. Lowercase, comma-separated. These are the "whales" the bot mirrors.
- "Trade size per fill (USD notional)?" (default:
"1", the Polymarket minimum). →TRADE_AMOUNT_USDunderenv.cloud:incompose.yaml. - "Do you want to use the shared Fly.io proxy (default) or your own?" — default is fine for testing. For production, recommend deploying https://github.com/goldsky-io/fly-polymarket-proxy and setting
CLOB_HOSTto that deployment's URL. - "Do you have a Polygon EOA you want to use, or should we generate a fresh one?" — this EOA holds USDC.e and signs CLOB orders. If generating fresh,
cast wallet newproduces(address, private_key). Record both. - "Publish to a new GitHub repo?" — optional.
Step 2 — Install dependencies
npm install
Required for Compose's bundler to resolve imports (viem, etc.).
Step 3 — Edit compose.yaml
Use grep anchors — the keys are unique within the file:
- Top-level
name:→"<app name>" - Under
env.cloud::WATCHED_WALLETS:→"<comma-separated lowercase addresses>" - Under
env.cloud::TRADE_AMOUNT_USD:→"<size>"(keep as"1"for first run) - Under
env.cloud::CLOB_HOST:→ only change if user has their own proxy.
Step 4 — Edit pipeline/polymarket-ctf-events.yaml
- In the
watched_fillsSQL transform, replace the placeholder'0xWALLET_1', '0xWALLET_2'lists in both themaker IN (...)andtaker IN (...)blocks with the lowercased addresses fromWATCHED_WALLETS. The two lists must contain the same addresses. - Under
sinks.copy_trade_webhook, update theurl:path segment if the app name is notcopy-trader:https://api.goldsky.com/api/admin/compose/v1/<app name>/tasks/copy_trade.
Step 5 — Create the PRIVATE_KEY secret
Set the EOA private key (the one funding USDC.e and signing orders).
Avoid putting the key on the command line — --value "<literal>" writes the value into shell history. Stop and ask the user to paste the key into chat (do not type it yourself), then construct the secret-set command with the literal token inlined as a single shell invocation. Or have them set the value through the Goldsky dashboard.
# The value of $EOA_PK never lands in history because it's only set inline:
EOA_PK="0x<paste hex>" goldsky compose secret set PRIVATE_KEY --value "$EOA_PK"; unset EOA_PK
This is a Compose-app-scoped secret declared in the secrets: block of compose.yaml. The 0x prefix is tolerated but optional.
Step 6 — Create the COMPOSE_WEBHOOK_AUTH secret (project-scoped, one-time)
The pipeline needs a bearer token to POST into the Compose app. This is a project-level secret (not per-app), so it only needs to be created once per Goldsky project — if the user has already created it for another pipeline, skip this step.
First, the user needs a Compose API token. There's no CLI command for this — direct them to the Goldsky dashboard (https://app.goldsky.com) to create one.
This step is best run by the user in their own terminal, not by the agent. Reason: putting the token on a command line via the agent's Bash tool ends up in the user's shell history (or the agent's tool log), and agent Bash typically has no TTY so read -s doesn't hide input. Stop, output the command template below, and ask the user to substitute the token and run it themselves:
read -s COMPOSE_TOKEN
# (paste the token, press Enter; the -s flag hides it from the terminal)
goldsky secret create --name COMPOSE_WEBHOOK_AUTH \
--value "{\"type\":\"httpauth\",\"secretKey\":\"Authorization\",\"secretValue\":\"Bearer $COMPOSE_TOKEN\"}"
unset COMPOSE_TOKEN
Referenced as secret_name: COMPOSE_WEBHOOK_AUTH in the sinks.copy_trade_webhook block of pipeline/polymarket-ctf-events.yaml.
Step 7 — Deploy Compose app first, then the pipeline
Order matters — see Non-negotiables.
goldsky compose deploy
Capture the deployed app's base URL from the output (e.g. https://api.goldsky.com/api/admin/compose/v1/<app name>/).
Then:
goldsky turbo apply pipeline/polymarket-ctf-events.yaml
The pipeline starts indexing from latest (real-time fills).
Step 8 — Fund the EOA with USDC.e on Polygon
Derive the EOA address from the private key:
cast wallet address --private-key "0x<hex>"
Call it $EOA_ADDRESS. Send USDC.e — contract 0x2791Bca1f2de4661ED88A30C99A7a9449Aa84174 on Polygon mainnet — to that address. Recommended starting balance: $5–10 (enough for several $1 mirror trades and the $1.10 balance floor the bot enforces).
Do not send MATIC. Compose sponsors gas (sponsorGas: true in the tasks); the EOA only needs USDC.e, not MATIC.
Verify balance:
cast call 0x2791Bca1f2de4661ED88A30C99A7a9449Aa84174 \
"balanceOf(address)(uint256)" $EOA_ADDRESS \
--rpc-url https://polygon-bor-rpc.publicnode.com
Divide by 1e6 to get the USDC value.
Step 9 — Run one-time approvals
The setup_approvals task grants the two exchanges permission to pull USDC and CTF shares from the EOA. Idempotent — safe to re-run.
The task is deployed with authentication: "auth_token" (the setup_approvals task in compose.yaml), so it must be called via HTTP with the token from Step 6. goldsky compose callTask only works against local servers, not the deployed app.
curl -X POST \
-H "Authorization: Bearer $COMPOSE_TOKEN" \
"https://api.goldsky.com/api/admin/compose/v1/<app name>/tasks/setup_approvals"
Expect 4 sponsored on-chain transactions (USDC approval × 2 exchanges, CTF setApprovalForAll × 2 exchanges). Tail goldsky compose logs to confirm.
Step 10 — Optional: publish to a new GitHub repo
git init
git add .
# PRIVATE_KEY for a Polygon mainnet EOA holding USDC.e is real money. Abort
# if anything secret-shaped is staged. Fix .gitignore, `git rm --cached`, retry.
git ls-files --cached | grep -iE '(keypair\.json|\.env|private[._-]?key|\.pem|id_rsa)' && \
{ echo "ABORT: secret-shaped file staged"; exit 1; }
git commit -m "Initial commit: Compose copy-trader"
gh repo create <user's repo name> --<public|private> --source=. --push
Step 11 — Smoke test
Option A — synthetic webhook. Post a fake OrderFillRow to the copy_trade endpoint. Expect a MARKET_NOT_FOUND response (webhook auth passed, market lookup failed on the fake tokenId — this proves auth + WATCHED_WALLETS substitution + market-lookup wiring, but does not test the pipeline → webhook flow). For end-to-end verification of the pipeline path, use Option B. Substitute a real watched wallet for <one watched wallet lowercase> — if you see NO_POSITION instead of MARKET_NOT_FOUND, the maker is not in WATCHED_WALLETS and the test is invalid. Example:
curl -X POST \
-H "Authorization: Bearer $COMPOSE_TOKEN" \
-H "Content-Type: application/json" \
https://api.goldsky.com/api/admin/compose/v1/<app name>/tasks/copy_trade \
-d '{"id":"test-1","block_number":1,"log_index":0,"transaction_hash":"0xtest","block_timestamp":"2026-01-01T00:00:00Z","maker":"<one watched wallet lowercase>","taker":"0x0000000000000000000000000000000000000000","maker_asset_id":"0","taker_asset_id":"999","maker_amount":1,"taker_amount":1,"fee":0}'
# → expect status: "MARKET_NOT_FOUND"
Option B — live test. Tail Compose logs and wait for a real fill on a watched wallet:
goldsky compose logs
Look for [copy_trade] TRADE_EXECUTED: BUY <market> — order <id> or a BALANCE_LOW / MARKET_CLOSED skip reason. Either means the system is working end-to-end.
Also tail pipeline logs to confirm fills are being forwarded:
goldsky turbo logs polymarket-ctf-events
Troubleshooting
- Edits to
compose.yamlor source files don't take effect after redeploy. The local.compose/bundle cache is stale. Runrm -rf .compose/and redeploy. - Webhook returns 401.
COMPOSE_WEBHOOK_AUTHsecret is wrong or missing. Re-create it per Step 6, making sure the token is a valid Compose API token and the JSON is correctly escaped. - Webhook returns 404. Pipeline URL path segment doesn't match the deployed app name. Check pipeline YAML line 76.
copy_tradereturnsBALANCE_LOW. EOA has less than $1.10 USDC.e. Top up.copy_tradereturnsMARKET_NOT_FOUNDfor real fills. Token ID from pipeline doesn't resolve in Gamma — likely a stale or non-CTF token. Check the fill manually on Polymarket.copy_tradereturnsTRADE_FAILED: ... geo-block.CLOB_HOSTis not routing through the EU. Re-check line 20 ofcompose.yaml; try a fresh deploy of a private proxy.- Pipeline runs but no webhooks arrive.
WATCHED_WALLETSaddresses in pipeline YAML don't match on-chain fills — check casing (must be lowercase) and formatting. setup_approvalsruns but trades still revertTRANSFER_FROM_FAILED. The EOA has approvals but the approvals were done on an older wallet address, or the user swappedPRIVATE_KEYafter approvals. Re-runsetup_approvalswith the current key.- Redeem cron doesn't claim winnings. Check logs for
[redeem] N redeemable positions. If 0, there may be no resolved markets with balance; wait. If >0 but txs fail, likely a CTF approval issue — re-runsetup_approvals.
What you should NOT do
- Do not put the private key anywhere other than the
PRIVATE_KEYsecret. Do not add it to.env, do not pass it on the command line except viagoldsky compose secret set, do not log it. - Do not change
CHAIN_IDor theCONTRACTSaddresses insrc/lib/types.ts. Polymarket lives on Polygon mainnet only. - Do not change the
redeemcron cadence (theexpression:under theredeemtask incompose.yaml) to be more frequent than every 5 minutes. Polymarket's data API is the source of truth and responses are cached; hammering it hurts more than it helps. - Do not delete the
COMPOSE_WEBHOOK_AUTHsecret to rotate it — other pipelines in the same project may depend on it. Create a new one with a different name and update pipeline YAMLs individually. - Do not test with US-only proxies or try to run from a US IP without a proxy. Polymarket's CLOB will 403 and the first real fill will fail silently.