| name | compose-bitcoin-oracle |
| description | Build and deploy the Goldsky Compose bitcoin-oracle example under the user's own account — a cron task that fetches BTC/USD from CoinGecko and writes `(timestamp, price)` as `bytes32` values to an on-chain `PriceOracle` contract via a Compose-managed wallet, appending each price to a durable collection. Triggers on: 'build a bitcoin price oracle', 'BTC/USD oracle onchain', 'push a price feed onchain', 'cron price oracle', 'set up / deploy the bitcoin-oracle example', 'compose price oracle'. Recommends the shared fully-unpermissioned oracle on Base Sepolia so there's nothing to deploy. Scaffolds the example from goldsky-io/documentation-examples, walks CLI install, contract choice (reuse shared / deploy own), wiring, optional GitHub publish, and a log-tailing smoke test. For a custom/novel Compose app that isn't this oracle, use /compose. For debugging an already-deployed app, use /compose-doctor. For manifest/CLI/API field lookups, use /compose-reference. |
Build: Compose bitcoin-oracle
Stand up the bitcoin-oracle example under the user's own Goldsky account. A cron task fetches BTC/USD from CoinGecko and writes (timestamp, price * 100) as two bytes32 values to a PriceOracle contract via a Compose-managed wallet. It also appends the price to a Compose collection for historical queries.
This template supplies only what's specific to the bitcoin-oracle app — how it works and its source. The recommended path uses a shared, fully-unpermissioned PriceOracle on Base Sepolia, so the user deploys nothing and the Compose smart wallet is auto-created and gas-sponsored.
Step 0a — Load the base skills first
Before anything else — before you answer, ask a question, scaffold a file, or call deployComposeApp — load the two base skills this template depends on:
Skill(compose) — the always-on Compose guide: the golden rules (never assume anything about the app on the user's behalf; ask when unsure) and general build guidance.
Skill(compose-reference) — the manifest / field / API reference; consult before writing any compose.yaml or task file.
This template deliberately omits those rules and that reference — they are required to build correctly and are not repeated here. Do not proceed until both are loaded.
Mode Detection
Pick the mode from the tools available to you:
-
A deployComposeApp tool is available (Goldsky webapp chatbot) — this is the preferred in-app flow. Do NOT emit goldsky terminal commands or cliCommand cards, and do NOT use Step 0b / degit / forge / goldsky compose deploy. Give a 2-3 sentence plain explanation, then ask with askUser (tag the recommended option with recommendedIndex):
- App name — ask first, before anything else: "What should the app be called? (suggest
bitcoin-oracle)". The name is hard to change later — it scopes named wallets and participates in the CREATE2 salt for every deployContract — so it must be settled before any wallet or contract step. Accept the default bitcoin-oracle on a shrug, and set the chosen name as the top-level name: in the scaffolded compose.yaml.
- Contract — ask this explicitly, do not assume: "Do you have your own
PriceOracle contract, or should we use a shared demo oracle on Base Sepolia to get running quickly?" Options: "Use the shared demo oracle on Base Sepolia (recommended — nothing to deploy)" and "I'll use my own contract." On the shared path, ORACLE_CONTRACT is the HARDCODED address 0x53deB3fF6E6e82A3b5E96f14E185e3Fe66BF5113 on baseSepolia — copy it character-for-character; mention in prose it's demos-only, not production. On the own path, ask the user to paste their contract address and chain and use exactly what they paste (their PriceOracle must let the Compose wallet write).
- Update frequency (recommend every minute,
* * * * *).
The Compose smart wallet is auto-created at runtime and gas-sponsored — never tell the user to create or fund a wallet. After the questions, scaffold the files in-memory (do NOT degit): compose.yaml (a single cron task on the chosen schedule), src/contracts/PriceOracle.json (the verbatim ABI in Step 3), and src/tasks/bitcoin-oracle.ts (fetch BTC/USD from CoinGecko, then evm.contracts.PriceOracle.write(toBytes32(timestamp), toBytes32(Math.round(price*100))) via the gas-sponsored smart wallet, appending each price to a bitcoin_prices collection). ORACLE_CONTRACT is a hardcoded const at the top of the task file, not an env var (for this example we keep it inline; /compose permits manifest env: instead — either is fine, just don't put a plain address in secrets:). Follow /compose-reference for the manifest shape and the sandbox import rule before emitting the files (per the golden rules in /compose — don't synthesize the manifest from memory). Then call deployComposeApp in the SAME turn; don't ask the user to confirm first or emit any goldsky command. In this mode, ignore Steps 0–8 below — they are the CLI/local procedure.
-
Bash is available (local CLI / coding agent): execute the steps below directly, parsing output into later commands.
-
Neither (pure reference Q&A): explain what the app does; for step-by-step help point them at npx skills add goldsky-io/goldsky-agent to run it locally with Bash.
Non-negotiables
- The shared oracle at
0x53deB3fF6E6e82A3b5E96f14E185e3Fe66BF5113 on Base Sepolia is fully unpermissioned — anyone can write to it. It exists for getting started and demos only. Tell the user, in prose, that it must NOT be used in production. It only exists on Base Sepolia.
- Never run
goldsky compose deployContract, goldsky compose deploy, git push, or gh repo create without showing the exact command first and getting explicit confirmation.
- Deploy-your-own path only: the contract's authorized writer must be the Compose wallet, or every
write() reverts. On the shared-oracle path there is no writer restriction, so this does not apply.
- The example ships only
src/contracts/PriceOracle.json (the ABI), not Solidity source. If deploying fresh, use the reference contract in this skill. Write the ABI verbatim — see Step 3.
- Do not touch
src/lib/utils.ts. toBytes32 is coupled to how the contract stores the value.
Steps 0–8 below are the Bash / local-CLI procedure. If a deployComposeApp tool is available (webapp chatbot), do NOT follow them — use the deploy-tool flow in Mode Detection above.
Step 0b — Scaffold the example
Pull just the bitcoin-oracle example into a fresh directory (no git history):
npx -y degit goldsky-io/documentation-examples/compose/bitcoin-oracle bitcoin-oracle
cd bitcoin-oracle
If npx degit is unavailable, fall back to a sparse clone:
git clone --depth 1 --filter=blob:none --sparse https://github.com/goldsky-io/documentation-examples.git
cd documentation-examples && git sparse-checkout set compose/bitcoin-oracle && cd compose/bitcoin-oracle
If the user already cloned the example, skip this step and cd into it.
Preflight
Version (compose 0.8.1). Run goldsky compose --version — it prints goldsky compose 0.8.1. If the version is older than 0.8.1, or deployContract / writeContract are unknown commands, the deploy-your-own path (Branch B in Step 3) won't work — OFFER to run goldsky compose update for the user and re-check --version. (Branch A — the shared oracle — only needs compose deploy, so an older CLI is fine there.)
The goldsky CLI, auth, and deno checks are the standard Compose preflight — see /compose and /auth-setup. Bitcoin-oracle-specific: the deploy-your-own path (Step 3, Branch B) uses goldsky compose deployContract, which bundles its own compiler — Foundry is not required.
Step 1 — Configuration
Per the golden rules in /compose, ask only what you can't derive — and ask the app name first:
- "What should the app be called? (suggest
bitcoin-oracle)" — ask FIRST, before any wallet (Step 2) or contract (Step 3) step. The name is hard to change later: it scopes named wallets and participates in the CREATE2 salt for every deployContract, so it must be settled now. Accept the default bitcoin-oracle on a shrug, and set it as the top-level name: in compose.yaml.
- "Which chain?" — Base Sepolia (recommended) because it has the ready, fully-unpermissioned shared oracle (nothing to deploy). Other options (Base, Arbitrum, Polygon Amoy, etc.) require deploying your own oracle. Use the camelCase form in TS (
baseSepolia).
- "PriceOracle contract?" (ask immediately after the chain) — two options:
- Reuse the shared oracle on Base Sepolia (recommended) — nothing to deploy. Wire
0x53deB3fF6E6e82A3b5E96f14E185e3Fe66BF5113 (mention the address in prose, not in any option label). Demos/getting-started only, not production.
- Deploy my own — see Step 3, Branch B. (Required on any chain other than Base Sepolia.)
- "How often should the cron run?" — Every minute (recommended,
* * * * *), every 5 minutes (*/5 * * * *), or every hour. Set the expression: under the cron trigger in compose.yaml.
Step 2 — Wallet
- Shared-oracle path (recommended): nothing to do. The Compose smart wallet is auto-created at runtime and fully gas-sponsored on Base Sepolia. Do NOT tell the user to create or fund a wallet.
- Deploy-your-own path (Branch B): the wallet is created first, before any deploy, as step (1) of the Branch B ordering in Step 3; capture its address as
$COMPOSE_WALLET. The named wallet bitcoin-oracle-wallet matches evm.wallet({ name: "bitcoin-oracle-wallet" }) in src/tasks/bitcoin-oracle.ts.
Step 3 — Contract
Branch A — Reuse shared oracle (recommended). $CONTRACT_ADDRESS = 0x53deB3fF6E6e82A3b5E96f14E185e3Fe66BF5113 on Base Sepolia. No deploy, no writer authorization — but "nothing to deploy" is not "nothing to wire." The scaffold ships stale defaults (Polygon Amoy + a different oracle address), so the Step 4 wiring is mandatory even on Branch A. Skip to Step 4.
Branch B — Deploy your own. Branch B has a fixed ordering: create the wallet → deploy the contract → codegen → wire (Step 4) → deploy the app (Step 7). Follow steps (1)–(5) below in order. deployContract writes the full compiled ABI to src/contracts/PriceOracle.json for you, so there's nothing to confirm by hand. The JSON below is the subset the task actually touches — write, the two view getters, and the PriceUpdated event (the compiled output also exposes the constructor, writer, setWriter, and OnlyWriter). On the in-app / scaffold-inline path this subset is enough for codegen; never invent ABI fields:
[{"inputs":[{"internalType":"bytes32","name":"timestamp","type":"bytes32"},{"internalType":"bytes32","name":"price","type":"bytes32"}],"name":"write","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"latestTimestamp","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"latestPrice","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"view","type":"function"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"bytes32","name":"timestamp","type":"bytes32"},{"indexed":false,"internalType":"bytes32","name":"price","type":"bytes32"}],"name":"PriceUpdated","type":"event"}]
(1) Create the wallet (works before any deploy) and capture its address as $COMPOSE_WALLET. It matches evm.wallet({ name: "bitcoin-oracle-wallet" }) in src/tasks/bitcoin-oracle.ts:
goldsky compose wallet create bitcoin-oracle-wallet
⚠ Do NOT use wallet create --env local: that's a LOCAL tevm wallet the cloud runtime never signs with, producing a bricked oracle the Troubleshooting section below will not diagnose. Use the default (cloud) wallet.
(2) Write the source, then deploy the contract. Run mkdir -p contracts and write this reference Solidity to contracts/PriceOracle.sol:
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;
contract PriceOracle {
address public writer;
bytes32 public latestTimestamp;
bytes32 public latestPrice;
event PriceUpdated(bytes32 indexed timestamp, bytes32 price);
error OnlyWriter();
constructor(address _writer) { writer = _writer; }
function setWriter(address newWriter) external {
if (msg.sender != writer) revert OnlyWriter();
writer = newWriter;
}
function write(bytes32 timestamp, bytes32 price) external {
if (msg.sender != writer) revert OnlyWriter();
latestTimestamp = timestamp;
latestPrice = price;
emit PriceUpdated(timestamp, price);
}
}
Then deploy through the Compose wallet — deployContract compiles in-CLI and deploys via a CREATE2 proxy signed by the gas-sponsored Compose wallet, so on Base / Base Sepolia it needs no funded EOA and no RPC URL. Every deployContract / writeContract needs -t <project API key> (or a goldsky login session) — make a key at Settings > API Keys in the dashboard. The constructor arg authorizes the wallet from step 1 as the writer:
goldsky compose deployContract contracts/PriceOracle.sol \
--chain-id <CHAIN_ID> \
--constructor-args $COMPOSE_WALLET \
--wallet bitcoin-oracle-wallet
Chain IDs: baseSepolia → 84532, base → 8453, polygonAmoy → 80002, polygon → 137, arbitrum → 42161, optimism → 10. It auto-saves the ABI to src/contracts/PriceOracle.json. Capture the printed contract address as $CONTRACT_ADDRESS.
Non-Base chains (forge fallback). The deployContract cloud path is gas-sponsored on Base and Base Sepolia only today (broader coverage tracked as FOU-991). On any other chain, deploy with a funded EOA via forge create instead — the writer is still $COMPOSE_WALLET, so the task code is unchanged; extract the bare ABI afterward (see the note under the command). Don't ask the user for a private key — OFFER to generate a throwaway funded EOA: run cast wallet new and capture the private key without printing it (write it to a chmod 600 file or an env var the user never sees), then show only cast wallet address / the address line. Point the user at the right-chain faucet for the chosen chain, then OFFER to check funding with cast balance <address> --rpc-url <RPC> before deploying. If they decline, tell them plainly the alternative is deploying with their own funded wallet:
forge create contracts/PriceOracle.sol:PriceOracle \
--rpc-url <RPC_URL> \
--private-key <FUNDED_EOA_PRIVATE_KEY> \
--broadcast \
--constructor-args $COMPOSE_WALLET
--broadcast is required (forge ≥1.0 dry-runs by default and deploys nothing while looking successful), and --constructor-args MUST be the last flag — it is greedy variadic, so placing it before --rpc-url / --private-key makes forge swallow them and dial localhost. The deployed address is on the Deployed to: line — capture it as $CONTRACT_ADDRESS. Then extract the bare ABI (feeding the full {abi, bytecode, ...} artifact breaks codegen silently):
jq .abi out/PriceOracle.sol/PriceOracle.json > src/contracts/PriceOracle.json
(3) Run codegen. deployContract saved the ABI above (or the jq .abi extract did, on the forge path); codegen generates the typed evm.contracts.PriceOracle class the task imports (the CLI's own success output says to):
goldsky compose codegen
(4) Wire the address and chain into the task (Step 4). (5) Deploy the app so the wired task goes live (Step 7). A single deploy, not a redeploy: the wallet was created and contract deployed first, so this takes the wired task live.
Either way (deployContract or the forge create fallback), $COMPOSE_WALLET — the constructor arg — is what authorizes the writer, so there's nothing else to do. (Already have a pre-existing PriceOracle-shaped contract? Grant the Compose wallet write permission via setWriter($COMPOSE_WALLET) from the owner EOA and use its address instead.)
Step 4 — Wire the contract address and chain into the task
Edit src/tasks/bitcoin-oracle.ts — use grep anchors:
- Find
const ORACLE_CONTRACT = "0x..." near the top and replace the address with $CONTRACT_ADDRESS.
- Find the
evm.chains.* reference inside the new evm.contracts.PriceOracle(...) call and set it to evm.chains.<chosen chain in camelCase> (e.g. baseSepolia).
- If you wired a different chain, also update the stale scaffold comment block that still says "Polygon Amoy" so the file's comments match.
If the user changed the cron cadence, edit the expression: under the cron trigger in compose.yaml.
- Set the top-level
name: in compose.yaml to the chosen app name from Step 1 (the degit scaffold may ship a different default — overwrite it so compose deploy and the CREATE2 salt match).
Step 5 — Gas (deploy-your-own, non-sponsored chains only)
Compose-managed wallets default to sponsorGas: true on sponsored chains (Base, Base Sepolia, Polygon, Polygon Amoy, and others). On those chains the wallet needs no funding — skip this step. On a non-sponsored chain, send native gas token to $COMPOSE_WALLET (testnet faucet, or budget for the cron cadence on mainnet: every-minute writes ≈ 1,440 tx/day). Note that this runtime task-gas sponsorship covers far more chains than the deployContract / writeContract cloud deploy path, which is Base / Base Sepolia only for now — on other chains, deploy via the Step 3 forge fallback, then let runtime sponsorship (or a funded wallet) cover the cron writes.
Step 6 — Optional: publish to a new GitHub repo
git init
git add .
if git ls-files --cached | grep -qiE '(keypair\.json|\.env|private[._-]?key|\.pem|id_rsa)'; then
echo "⚠ Secret-shaped file is staged — remove it before committing. Aborting publish."
else
git commit -m "Initial commit: Compose bitcoin-oracle"
gh repo create <user's repo name> --<public|private> --source=. --push
fi
Step 7 — Deploy to Goldsky
goldsky compose deploy
For Branch A (shared oracle) this is your first and only deploy. For Branch B this is also your only deploy: the wallet was created and the contract deployed first (Step 3), so this single deploy takes the wired task live.
Step 8 — Smoke test
Stream logs and wait for the next cron fire (up to 1 minute):
goldsky compose logs -f
-f / --follow streams live so you catch the next cron fire; plain goldsky compose logs is a one-shot dump of the last 100 lines (--tail, default 100) and returns.
Good output is a return payload with success: true and an oracleHash 0x-prefixed tx hash, repeating on cadence with no retries.
Verify on-chain (Base Sepolia explorer: https://sepolia.basescan.org/address/$CONTRACT_ADDRESS#events): you should see a PriceUpdated event per cron fire, and latestPrice() / latestTimestamp() should return recent bytes32 values.
Troubleshooting
- Edits to
compose.yaml or source files don't take effect after redeploy. The local .compose/ bundle cache is stale. Run rm -rf .compose/ and redeploy.
- Every cron run reverts (deploy-your-own only). The Compose wallet isn't the authorized writer. Re-run
setWriter($COMPOSE_WALLET) (Branch A of your own contract) or re-check the deployContract constructor arg. (Shared oracle has no writer restriction, so this can't be the cause there.)
insufficient funds for gas. Only possible on a non-sponsored chain with deploy-your-own. Fund $COMPOSE_WALLET.
- CoinGecko 429 / rate-limited. The default retry config (3 attempts, backoff) handles transient rate-limits. If persistent, reduce cron cadence or switch to a paid API.
- Task runs but no events on-chain. Confirm the
evm.chains.* reference matches the chain where the contract lives. A wallet on the wrong chain signs a tx that never appears on the intended chain.
error: Unknown command "deployContract". Did you mean command "deploy"? (or the writeContract variant). Cause: the CLI is too old (older than 0.8.1). Fix: offer goldsky compose update, then re-check goldsky compose --version.
This contract has already been deployed with this app (re-running deployContract). CREATE2 dedupes on contract + constructor args + app name, so an identical re-run is refused before the tx and prints NO Deploy Block — a rerun can't recover the block. Levers: change a constructor arg, change the source, or use a different app name. PriceOracle takes a constructor arg, so prefer the constructor-arg lever. ⚠ Renaming the app changes every future deploy address and conflicts with the app name you compose deploy under, so avoid it unless intended.
What you should NOT do
- Do not change the
toBytes32 helper in src/lib/utils.ts. The contract reads price as bytes32 and the example scales by 100 (cents); changing either side silently breaks the stored value.
- Do not use the shared Base Sepolia oracle as a production target — it's open for anyone to write.
- Do not invent the
PriceOracle ABI. Use the ABI from Step 3 (the JSON there is the subset the task touches; deployContract writes the full version).
Related
/compose — Build a new/custom Compose app from scratch, or explain what Compose is.
/compose-reference — Manifest, CLI, TaskContext API, wallets, gas sponsorship, codegen.
/compose-doctor — Diagnose and fix a broken Compose app.
/auth-setup — goldsky login walkthrough.