Skip to main content

Documentation Index

Fetch the complete documentation index at: https://docs.goldsky.com/llms.txt

Use this file to discover all available pages before exploring further.

This guide walks you through building a Bitcoin price oracle that fetches the BTC/USD price every minute and writes it on-chain. It demonstrates cron triggers, contract codegen, external API calls, and collection storage.

How it works

  1. Cron trigger fires every minute
  2. CoinGecko API provides the current BTC/USD price
  3. On-chain contract receives the price via a typed contract class
  4. Collection stores the price for historical queries

Prerequisites

Project structure

bitcoin-oracle/
├── compose.yaml                    # Compose configuration
├── tsconfig.json                   # TypeScript config
├── src/
│   ├── contracts/
│   │   └── PriceOracle.json        # Contract ABI (generates typed class)
│   ├── lib/
│   │   └── utils.ts                # toBytes32 helper
│   └── tasks/
│       └── bitcoin-oracle.ts       # Main task

Step 1: Set up the project

Clone the example repository:
git clone https://github.com/goldsky-io/documentation-examples.git
cd documentation-examples/compose/bitcoin-oracle

Step 2: Generate contract types

The project includes a PriceOracle.json ABI in src/contracts/. Generate the typed contract class:
goldsky compose codegen
This creates a typed PriceOracle class in .compose/generated/ that provides type-safe contract interaction.

Step 3: Understand the task

The bitcoin-oracle.ts task handles everything:
import { TaskContext } from "compose";
import { toBytes32 } from "../lib/utils";

const ORACLE_CONTRACT = "0x34a264BCD26e114eD6C46a15d0A3Ba1873CaA708";

export async function main(context: TaskContext) {
  const { fetch, evm, collection } = context;

  const wallet = await evm.wallet({ name: "bitcoin-oracle-wallet" });

  // Instantiate the typed contract (generated from src/contracts/PriceOracle.json)
  const oracle = new evm.contracts.PriceOracle(
    ORACLE_CONTRACT,
    evm.chains.polygonAmoy,
    wallet
  );

  // Fetch Bitcoin price from CoinGecko API
  const response = await fetch<{ bitcoin: { usd: number } }>(
    "https://api.coingecko.com/api/v3/simple/price?ids=bitcoin&vs_currencies=usd",
    {
      max_attempts: 3,
      initial_interval_ms: 1000,
      backoff_factor: 2,
    }
  );

  if (!response) {
    throw new Error("Failed to fetch Bitcoin price");
  }

  const bitcoinPrice = response.bitcoin.usd;
  const timestamp = Date.now();

  // Convert to bytes32 format and write on-chain
  const timestampAsBytes32 = toBytes32(timestamp);
  const priceAsBytes32 = toBytes32(Math.round(bitcoinPrice * 100));

  const { hash, receipt } = await oracle.write(timestampAsBytes32, priceAsBytes32);

  // Store in a collection for historical queries
  const priceHistory = await collection("bitcoin_prices");
  const { id } = await priceHistory.insertOne({
    price: bitcoinPrice,
    timestamp: timestamp,
  });

  return {
    success: true,
    oracleHash: hash,
    price: bitcoinPrice,
    timestamp,
    priceId: id,
  };
}

Key Compose features used

  • context.fetch — HTTP requests with built-in retry and backoff
  • evm.wallet — managed wallet with gas sponsorship
  • evm.contracts.PriceOracle — typed contract class generated from ABI JSON via compose codegen
  • collection — persistent document storage for price history

Step 4: Configure the Compose app

The compose.yaml uses a cron trigger to run every minute:
name: "bitcoin-oracle"
api_version: "stable"
tasks:
  - path: "./src/tasks/bitcoin-oracle.ts"
    name: "bitcoin_oracle"
    triggers:
      - type: "cron"
        expression: "* * * * *"
    retry_config:
      max_attempts: 2
      initial_interval_ms: 1000
      backoff_factor: 1

Step 5: Run locally

goldsky compose start --fork-chains
--fork-chains allows you to run a smart wallet locally. You can also use a private key wallet for your local Compose app. See more here. The task runs immediately and then every minute. You should see logs showing the fetched price and transaction hash.

Step 6: Deploy to Goldsky

goldsky compose deploy

Customization

Change the price source

Replace the CoinGecko URL with any API that returns a JSON price:
const response = await fetch<{ price: number }>(
  "https://your-api.com/price",
  { max_attempts: 3, initial_interval_ms: 1000, backoff_factor: 2 }
);

Use your own contract

  1. Drop your contract’s ABI JSON into src/contracts/MyContract.json
  2. Run goldsky compose codegen to generate the typed class
  3. Use it in your task:
const myContract = new evm.contracts.MyContract(
  "0xYOUR_CONTRACT_ADDRESS",
  evm.chains.baseSepolia,
  wallet
);
await myContract.yourMethod(arg1, arg2);

Change the cron schedule

triggers:
  - type: "cron"
    expression: "*/5 * * * *"  # every 5 minutes

Resources