Setup: Compose prediction-market
This skill makes the cloned prediction-market example runnable under the user's own Goldsky account. A cron orchestrator fires every 5 minutes, calls a helper task to fetch BTC/USD from CoinGecko, resolves any overdue markets by reporting payouts on the Gnosis ConditionalTokens (CTF) contract, and prepares a fresh market for the current 5-minute bucket. The Compose-managed wallet is the sole oracle for every market it launches.
This is the lowest-setup-cost example in the repo: no contracts to deploy, no secrets, no funding. Most of the work is editing constants.
Non-negotiables
- Never run
goldsky compose deploy,git push, orgh repo createwithout showing the exact command first and getting explicit confirmation. SALTinsrc/lib/constants.tsis a domain separator mixed into every question ID. If the user changes it after markets are live, all existing markets become orphaned on-chain (differentconditionId, unreachable by the orchestrator). Only changeSALTduring initial setup, never afterward.- The oracle wallet is determined at first deploy. The
conditionIdof every market is deterministically derived from(oracle, questionId, 2). If the oracle wallet changes (e.g. user renamesORACLE_WALLET_NAMEinsrc/lib/constants.ts), every prior market is orphaned. Pick the name once. CTF_ADDRESSinsrc/lib/constants.tsis chain-specific. The Gnosis CTF is deployed at different addresses on different chains. Using the Base Sepolia address on Polygon will silently produce transactions that revert or hit an EOA. Always verify the address for the user's target chain.ASSET_PAIRandPRICE_URL(both insrc/lib/constants.ts) are coupled. If the user wants ETH/USD instead of BTC/USD, they must update both constants and the response parser insidemain()ofsrc/tasks/market-data.ts(currently readsresponse.bitcoin.usd— needs to change toresponse.ethereum.usd, and the corresponding TypeScript type on thecontext.fetch<...>(...)call).
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.deno—deno --version.
That's it. No Foundry, no npm, no Solana tooling.
Step 1 — Configuration interview
- "App name?" (default:
prediction-market) → top-levelname:field incompose.yaml. - "Which chain?" (default:
baseSepolia) → theCHAINconst insrc/lib/constants.ts. The Gnosis CTF is deployed on most major chains; common options includebaseSepolia,base,polygon,ethereum. The user must give you the CTF contract address for their chosen chain; defaults are only known for Base Sepolia. - "Which asset pair?" (default:
BTC_USD) — any asset CoinGecko supports. If the user picks something else (e.g.ETH_USD,SOL_USD), you'll update three places in Step 3. - "Market duration?" (default: 5 minutes,
DURATION_SEC = 300) — longer durations produce fewer, larger markets. The cron cadence (theexpression:under theorchestratortask incompose.yaml) andDURATION_SECshould usually match; if the user wants hourly markets, change both. - "Domain salt?" (default:
"GOLDSKY_COMPOSE_DEMO") — only needs to be changed if they want a fresh namespace (e.g. running multiple instances in the same Goldsky project). Once chosen, do not change. - "Publish to a new GitHub repo?" — optional.
Step 2 — Determine the CTF contract address for the chosen chain
If the user picked baseSepolia, the default CTF_ADDRESS in src/lib/constants.ts is correct: 0xb04639fB29CC8D27e13727c249EbcAb0CDA92331.
Otherwise, the user must provide the Gnosis ConditionalTokens address for their chain. Canonical sources:
- Gnosis Conditional Tokens docs: https://docs.gnosis.io/conditionaltokens/docs/ethereum/
- Polymarket uses
0x4D97DCd97eC945f40cF65F87097ACe5EA0476045on Polygon (mainnet). - Base mainnet, Ethereum mainnet, etc. — look up the deployment on the relevant block explorer.
Call the address $CTF.
Step 3 — Edit constants
In src/lib/constants.ts — use grep anchors, locate each export const X = ... and replace the value:
CHAIN→"<camelCase chain>" as constCTF_ADDRESS→"$CTF"(typedAddress)ORACLE_WALLET_NAME→"<stable name>"— only change if the user wants a custom name. Once deployed, do not rename.ASSET_PAIR→"<e.g. ETH_USD>" as const(only if asset changed)DURATION_SEC→<N>(only if duration changed;DURATION_MSis derived and updates automatically)SALT→"<unique string>"(only if user wants a custom namespace)PRICE_URL→"<CoinGecko URL for chosen asset>"(only if asset changed)
If ASSET_PAIR changed, also edit src/tasks/market-data.ts — find the const priceUsd = response?.bitcoin?.usd; line inside main() and change bitcoin to the CoinGecko id matching the new asset. Example for ETH:
const priceUsd = response?.ethereum?.usd;
Update the TypeScript type on the context.fetch<...>(...) call directly above to match ({ ethereum?: { usd?: number } }).
If market duration changed, also edit the cron expression: under the orchestrator task in compose.yaml — the cadence should match DURATION_SEC. Compose uses 6-field cron with seconds (sec min hr day mon dow), not the standard 5-field format. For 5-min markets: "10 */5 * * * *" (10s offset into each 5-min boundary). For hourly markets: "0 0 * * * *" (offset 0s, minute 0, every hour). Do not use 5-field cron expressions here — they will fail to parse.
Step 4 — Optional: publish to a new GitHub repo
git init
git add .
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 prediction-market"
gh repo create <user's repo name> --<public|private> --source=. --push
Step 5 — Deploy to Goldsky
goldsky compose deploy
Gas is sponsored by default (context.evm.wallet({ name: ORACLE_WALLET_NAME }) without explicit gas config uses Goldsky's sponsor) — the oracle wallet needs no funding.
Step 6 — Smoke test
Get the oracle wallet address:
goldsky compose wallet create <ORACLE_WALLET_NAME from constants.ts>
Substitute the value the user chose for ORACLE_WALLET_NAME (default: prediction-market-oracle). The command prints the address — save as $ORACLE. If the wallet already exists (e.g. from the deploy having run one cycle), it prints the existing address.
Tail logs and wait for the next cron fire. Cron is 10 */5 * * * * — fires at :10, :15, :20, etc. of each hour (with a 10-second offset from the boundary to let CoinGecko settle).
goldsky compose logs
Good output on first run:
cycle complete: price=<BTC/USD> resolved=0/0 launched=true resolveErrors=0 launchError=false
On the second and subsequent cycles, resolved should start showing 1/1 as the previous market becomes overdue.
Verify on the explorer. For Base Sepolia:
https://sepolia.basescan.org/address/$CTF#events
Filter events to the oracle address ($ORACLE) — topic[2] of ConditionPreparation and ConditionResolution. Each cron cycle should produce one ConditionPreparation (launch) and, after the first cycle, one ConditionResolution (resolve).
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. cycle completenever logs. Check that the deploy succeeded and the cron trigger is active:goldsky compose status.Unexpected CoinGecko response shape. The asset field in the response doesn't match. Re-check theresponse?.<asset>?.usdaccess insrc/tasks/market-data.tsand ensure the field matches the asset inPRICE_URL(e.g.ids=ethereum→response.ethereum.usd).resolveErrors=1or higher. Checkgoldsky compose logsfor the underlying error. Two benign cases (the code catches them): "condition already prepared" and "payout denominator already set" — both mean a retry hit a tx that landed but the client didn't know yet. Errors other than those are real problems.- No events on the explorer. Verify
CTF_ADDRESSpoints at the real Gnosis CTF on the chosen chain. An EOA or unrelated contract at the address will produce no events even though the tx succeeds.
What you should NOT do
- Do not rename
ORACLE_WALLET_NAMEafter deploying. Every prior market'sconditionIdis bound to the old wallet. - Do not change
SALTafter deploying. Same orphaning problem. - Do not change the hardcoded outcome count
"2"in theprepareCondition(...)call insidesrc/tasks/launch-market.tsto add more outcomes without also updating the payout logic inresolve-market.tsand theOutcometype. Out of scope for setup. - Do not remove the
catchblock for "condition already prepared" / "payout denominator already set" inlaunch-market.tsorresolve-market.ts. These are idempotency guards that keep retries safe. - Do not fund the oracle wallet — it's a gas-sponsored managed wallet. Sending ETH or tokens to it is just lost money; the orchestrator doesn't read balances.