> ## 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.

# Build a Bitcoin price oracle

> Build an on-chain Bitcoin price oracle using Compose, CoinGecko, and contract codegen

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

```mermaid theme={null}
flowchart LR
    A[Cron Trigger] -->|"every minute"| B[Compose Task]
    B -->|"fetch price"| C[CoinGecko API]
    C -->|"BTC/USD price"| B
    B -->|"oracle.write(timestamp, price)"| D[On-chain Contract]
    B -->|"insertOne"| E[Collection]
```

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

* [Goldsky CLI installed](/installation)

## Project structure

```text theme={null}
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:

```bash theme={null}
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:

```bash theme={null}
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:

```typescript theme={null}
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:

```yaml theme={null}
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

```bash theme={null}
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](/compose/secrets).

The task runs immediately and then every minute. You should see logs showing the fetched price and transaction hash.

## Step 6: Deploy to Goldsky

```bash theme={null}
goldsky compose deploy
```

## Customization

### Change the price source

Replace the CoinGecko URL with any API that returns a JSON price:

```typescript theme={null}
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:

```typescript theme={null}
const myContract = new evm.contracts.MyContract(
  "0xYOUR_CONTRACT_ADDRESS",
  evm.chains.baseSepolia,
  wallet
);
await myContract.yourMethod(arg1, arg2);
```

### Change the cron schedule

```yaml theme={null}
triggers:
  - type: "cron"
    expression: "*/5 * * * *"  # every 5 minutes
```

## Resources

* [Compose introduction](/compose/introduction)
* [Contract codegen](/compose/context/evm/contracts)
* [Task triggers](/compose/task-triggers)
* [Collections](/compose/context/collections)
* [CoinGecko API](https://www.coingecko.com/en/api)
* [GitHub repository](https://github.com/goldsky-io/documentation-examples/tree/main/compose/bitcoin-oracle)
