# Tutorial 7: Oracles

> Use an oracle when your smart contract needs to consume data from the outside world.

Canonical URL: https://docs.minaprotocol.com/zkapps/tutorials/oracle

You can use an oracle when your smart contract needs to consume data from the outside world.

Learn about zkOracles in this 5-minute video:


## Prerequisites

- Make sure you have the zkApp CLI installed:

  ```sh
  $ npm install -g zkapp-cli
  ```

- Ensure your environment meets the [Prerequisites](/zkapps/tutorials#prerequisites) for zkApp Developer Tutorials.

- Use Node.js 22.19.5 or later. o1js 3 requires it.

This tutorial has been tested with:

- [o1js](https://www.npmjs.com/package/o1js) version `3.0.0`
- Node.js versions `22` and `24`

The example project is built and tested in CI: the smart contract tests run against a local blockchain, and the oracle's signing logic is tested offline with o1js.

## High-Level Overview

In this tutorial, you are going to build an oracle that retrieves data from the REST API and you also are going to write the smart contract that consumes information from this oracle.

1. Retrieve data from the REST API that provides mock credit score information for two users: one with a high credit score (user with `id=1`) and one with a low credit score (users with `id > 1`).
1. The smart contract consumes this information and allows users to prove their credit score is above a certain threshold (for example, higher than 700).

Using the smart contract, users can generate an attestation that their credit score is above a certain value. To maintain their privacy, users can prove this fact to a third party without sharing the exact credit score or other personal information.

This tutorial uses a mock credit score API as the data source and provides a foundation to create an oracle for any type of data. Just alter the code to query data from whatever source you need, any other REST API, for example.

## How Oracles Work

Oracles connect blockchain smart contracts with the outside world to get data on chain.

Mina smart contract computation run off-chain and make it possible to prove that the expected computation was run on private data without revealing the data itself. When the smart contract consumes data from a third-party source, you want to verify that this data is authentic and was provided by the expected source.

The [Mina roadmap](https://minaprotocol.com/mina-roadmap) includes `zkOracles` to allow a zkApp to consume data trustlessly from any HTTPS data source. The oracle design described in this tutorial is typically operated by the zkApp developer. The oracle fetches and signs the desired data, and then a zkApp can consume this data and verify the signature to ensure that the data was provided by the expected source.

Data providers can also operate as response signers like the one described to provide users with an oracle that does not require them to trust an intermediary. In other words, if a credit score or other data provider chooses to sign response data themselves, users can consume data from that source without trusting anybody besides the data provider they already trust to provide correct data.

### Design

This simple oracle design:

- Fetches data from the desired REST API source
- Signs it using a Mina-compatible private key
- Returns the data, signature, and public key associated with the private key
- Allows the signature to be verified by the zkApp

### Code

The oracle is a `Next.js` project: see the [complete project](https://github.com/MinaProtocol/docs2/tree/main/examples/zkapps/07-oracles/oracle). It runs as a [Vercel Function](https://vercel.com/docs/functions).

The route handler reads the `user` query parameter and returns the signed response:

```ts title="app/api/credit-score/route.ts"
export function GET(request: NextRequest) {
  const searchParams = new URLSearchParams(request.nextUrl.search);
  return NextResponse.json(
    getSignedCreditScore(+(searchParams.get('user') ?? 0)),
    { status: 200 }
  );
}
```

The signing logic gets the credit score of the user and signs the user id and the credit score, in that order, with the oracle's private key:

```ts title="lib/signCreditScore.ts"
export function getSignedCreditScore(userId: number) {
  // The private key of our account. When running locally the hardcoded key will
  // be used. In production the key will be loaded from a Vercel environment
  // variable. The public key of the hardcoded key is
  // B62qphyUJg3TjMKi74T2rF8Yer5rQjBr1UyEG7Wg9XEYAHjaSiSqFv1, which is not the
  // key of the demo oracle that the smart contract trusts.
  let privateKey =
    process.env.PRIVATE_KEY ??
    'EKF65JKw9Q1XWLDZyZNGysBbYG21QbJf3a4xnEoZPZ28LKYGMw53';

  // We get the users credit score. In this case it's 787 for user 1, and 536
  // for anybody else :)
  const knownCreditScore = (userId: number) => (userId === 1 ? 787 : 536);

  // Compute the users credit score
  const creditScore = knownCreditScore(userId);

  // Use our private key to sign an array of numbers containing the users id and
  // credit score
  const signature = client.signFields(
    [BigInt(userId), BigInt(creditScore)],
    privateKey
  );

  return {
    data: { id: userId, creditScore: creditScore },
    signature: signature.signature,
    publicKey: signature.publicKey,
  };
}
```

The smart contract verifies the signature on the same two fields, in the same order. If the oracle signs different fields, or the same fields in a different order, the smart contract rejects the signature.

You can adapt this code to create oracles for other API sources. For example, if you want your smart contract to ingest price feed data from an exchange, query the exchange API, sign the results, and return a response in the following response format.

### Response Format

The oracle returns a JSON-formatted response with these top-level properties:

- `data`: An object of the information you are interested in and can have any form.
- `signature`: A signature for the `data` object created using the oracle operator's private key. Smart contracts use this signature to verify that data was provided by the expected source.
- `publicKey`: The public key of the oracle is the same for all requests to this oracle.

The following example is a response from the oracle for the user with the `id` of `1`. In the real world, this `id` might be a social security number or a similar identifier. Notice that the data property contains their credit score and user id.

The demo oracle for user with id `1` is available at https://07-oracles.vercel.app/api/credit-score?user=1 and shows this response:

```json
{
  "data": { "id": 1, "creditScore": 787 },
  "signature": "7mXGPCbSJUiYgZnGioezZm7GCy46CEUbgcCH9nrJYXQQiwwVrA5wemBX4T1XFHUw62oR2324QNnkUVXW6yYQLsPsqxZ3nsYR",
  "publicKey": "B62qoAE4rBRuTgC42vqvEyUqCGhaZsW58SKVW4Ht8aYqP9UTvxFWBgy"
}
```

The user with an `id` of `2` has a credit score that is below the threshold specified in the smart contract.
The demo oracle for user with id `2` with a lower credit score is available at https://07-oracles.vercel.app/api/credit-score?user=2 and shows this response:

```json
{
  "data": { "id": 2, "creditScore": 536 },
  "signature": "7mXXnqMx6YodEkySD3yQ5WK7CCqRL1MBRTASNhrm48oR4EPmenD2NjJqWpFNZnityFTZX5mWuHS1WhRnbdxSTPzytuCgMGuL",
  "publicKey": "B62qoAE4rBRuTgC42vqvEyUqCGhaZsW58SKVW4Ht8aYqP9UTvxFWBgy"
}
```

While the first user has a credit score of `787`, the second user has a credit score of `536`. The `signature` is also changed. This makes sense because the payload is different from what is received in the first response. Finally, notice that the `publicKey` is the same because in each case we are querying data from the same provider.

## Generate a key pair for your oracle

To generate a Mina-compatible public/private key pair for your oracle, run this command in the `oracle` directory of the example project:

```sh
npm run keygen
```

This command runs the code in the [keygen.js](https://github.com/MinaProtocol/docs2/blob/main/examples/zkapps/07-oracles/oracle/scripts/keygen.js) file of the oracle `Next.js` project.

The demo oracle at `https://07-oracles.vercel.app` signs with its own private key. Its public key is `B62qoAE4rBRuTgC42vqvEyUqCGhaZsW58SKVW4Ht8aYqP9UTvxFWBgy`, and the smart contract in this tutorial trusts only this key.

When you run the oracle yourself (`npm run dev`) without the `PRIVATE_KEY` environment variable, it signs with a development key. The public key of the development key is `B62qphyUJg3TjMKi74T2rF8Yer5rQjBr1UyEG7Wg9XEYAHjaSiSqFv1`, so the smart contract rejects its signatures. To use your own oracle:

1. Generate a key pair with `npm run keygen`.
1. Set the `PRIVATE_KEY` environment variable of the oracle to the private key.
1. Set `ORACLE_PUBLIC_KEY` in the smart contract to the public key.

## Smart Contract

Now that you have an oracle that returns signed data, you can write a smart contract that uses this data.

### Create a project

1. Create or change to a directory where you have write privileges.

1. Create a project by using the `zk project` command:

   ```sh
   $ zk project --ui none 07-oracles
   ```

   This command creates the zkApp project without a UI, because this tutorial does not need one.

1. Change into the `07-oracles` directory.

1. The zkApp CLI can create the project with an earlier o1js. In `package.json`, set the `o1js` version in `peerDependencies` to `^3.0.0`, then run:

   ```sh
   $ npm install
   ```

For this tutorial, you run commands from the root of the `07-oracles` directory.

Each time you make changes, then build or deploy, the TypeScript code is compiled into JavaScript in the `build` directory.

### Prepare the project

The files in the `src` directory contain the TypeScript code for the smart contract.

1. Delete the default generated files by running:

   ```sh
   $ rm src/Add.ts
   $ rm src/Add.test.ts
   $ rm src/AddZkProgram.ts
   $ rm src/interact.ts
   ```

   If your version of the zkApp CLI does not create `src/AddZkProgram.ts`, skip that command.

1. Create the `OracleExample.ts` file and generate the corresponding test file:

   ```sh
   $ zk file OracleExample
   ```

1. Change `src/index.ts` to:

   ```ts title="src/index.ts"
   import { OracleExample } from './OracleExample.js';

   export { OracleExample };
   ```

### Write the smart contract

You can find the complete code for this smart contract [here](https://github.com/MinaProtocol/docs2/blob/main/examples/zkapps/07-oracles/contracts/src/OracleExample.ts).

Paste the following content into the `src/OracleExample.ts` file:

```ts title="src/OracleExample.ts"
import {
  Field,
  SmartContract,
  state,
  State,
  method,
  PublicKey,
  Signature,
} from 'o1js';

// The public key of our trusted data provider
const ORACLE_PUBLIC_KEY =
  'B62qoAE4rBRuTgC42vqvEyUqCGhaZsW58SKVW4Ht8aYqP9UTvxFWBgy';

export class OracleExample extends SmartContract {
  // Define zkApp state
  // Define zkApp events

  init() {
    // Initialize zkApp state
    super.init();
  }

  @method async verify(id: Field, creditScore: Field, signature: Signature) {
    // Get the oracle public key from the zkApp state
    // Evaluate whether the signature is valid for the provided data
    // Check that the signature is valid
    // Check that the provided credit score is 700 or higher
    // Emit an event containing the verified user's id
  }
}
```

This completes the basic setup for the smart contract. For details on the `init()` method, see [Tutorial 1: Hello World](hello-world).

### On-Chain State

The smart contract stores the public key for the oracle that you retrieve data from as the on-chain state. This makes the public key available when end users run the smart contract. The smart contract then uses this public key to verify the signature of the data to confirm it came from the expected source.

In the `src/OracleExample.ts` file:

```ts
// Define zkApp state
@state(PublicKey) oraclePublicKey = State<PublicKey>();
```

Use the `init` method to initialize the `oraclePublicKey` to the credit score oracle's public key.

```ts
init() {
  // Initialize zkApp state
  super.init();
  // Set the oracle public key as zkApp on-chain state
  this.oraclePublicKey.set(PublicKey.fromBase58(ORACLE_PUBLIC_KEY));
}
```

### Emit Events

The smart contract checks that a user has a credit score above a certain threshold. But, how can the user prove it?

To expose the result to the outside world, you can emit events. Events allow smart contracts to publish arbitrary messages that anybody can verify without requiring them to be stored in the state of a zkApp account. This property makes events ideal for communication with other parties of your application that don't live on-chain, like the UI or even an external service.

This code adds an `events` object to the smart contract class to define the names and types of the events it can emit:

```ts
// Define zkApp events
events = {
  verified: Field,
};
```

### Define the verify() method

Next you would like to verify that user's credit score is above `700`.

The `verify()` method is defined like any other TypeScript method, except that it must have the `@method` decorator in front of it that tells o1js that this method can be invoked by users when they interact with the smart contract.

```ts
@method async verify(id: Field, creditScore: Field, signature: Signature) {
  ...
}
```

Pass in these arguments:

- `id`: The id of the user whose credit score is requested to prevent bad actors from querying somebody else's data and claiming it as their own.
- `creditScore`: The credit score of the user that is a number between 350 and 800 (this tutorial uses mock credit scores).
- `signature`: A cryptographic signature of oracle's `data` object (`id` and `creditScore`). This is what the smart contract uses to verify that the data was provided by the expected source.

The `verify()` method does not return any values or change any contract state. It only emits a `verified` event with the user's id if their credit score is above 700.

### Fetch the oracle's public key

To get the oracle's public key from the on-chain state, verify the signature of data from the oracle:

```ts
// Get the oracle public key from the zkApp state
const oraclePublicKey = this.oraclePublicKey.get();
this.oraclePublicKey.requireEquals(oraclePublicKey);
```

The `requireEquals()` method invocation ensures that the public key that is retrieved at execution time is the same as the public key that exists within the zkApp account on the Mina network when the transaction is processed by the network.

### Verify the signature

To ensure that the signature was from our expected source, verify that the signature on the oracle's `data` object (`id` and `creditScore`) is valid for the expected public key. This code returns true if the signature is valid, and false if it is not.

```ts
// Evaluate whether the signature is valid for the provided data
const validSignature = signature.verify(oraclePublicKey, [id, creditScore]);
```

You always want to make it impossible to generate a valid zero knowledge proof if `validSignature` is false. You can do this with `assertTrue()`. If the signature is invalid, this throws an exception and makes it impossible to generate a valid zero knowledge proof and proceed with transaction creation.

```ts
// Check that the signature is valid
validSignature.assertTrue();
```

### Verify the credit score is 700 or higher

You want the `verify()` method to emit an event only if the user's credit score is 700 or higher. To ensure that this condition is met, call `assertGreaterThanOrEqual()` (assert greater than or equal to) on `creditScore`.

```ts
// Check that the provided credit score is 700 or higher
creditScore.assertGreaterThanOrEqual(Field(700));
```

These assert methods create a constraint that makes it impossible for users to generate a valid zero knowledge proof unless their condition is met. Without a valid zero knowledge proof (or a signature) it's impossible to generate a valid Mina transaction. Users can call the smart contract method and send a valid transaction only if they have a valid signature from the expected oracle and a credit score 700 or above.

### Emit a verified event

With this foundation, you can emit a verified event.

- The first argument to `emitEvent()` is an arbitrary string name, because a smart contract could emit more than one type of event.
- The second argument can be any value, as long as it matches the type defined for the event.

In this case, the event has the `Field` type, but it could be a more complicated type built on `Fields`, if the situation called for it.
Emitted events can be fetched using the [Archive-Node-API](/zkapps/writing-a-zkapp/feature-overview/fetch-events-and-actions).

```ts
// Emit an event containing the verified user's id
this.emitEvent('verified', id);
```

The complete smart contract:

```ts title="src/OracleExample.ts"
import {
  Field,
  SmartContract,
  state,
  State,
  method,
  PublicKey,
  Signature,
} from 'o1js';

// The public key of our trusted data provider
const ORACLE_PUBLIC_KEY =
  'B62qoAE4rBRuTgC42vqvEyUqCGhaZsW58SKVW4Ht8aYqP9UTvxFWBgy';

export class OracleExample extends SmartContract {
  // Define zkApp state
  @state(PublicKey) oraclePublicKey = State<PublicKey>();

  // Define zkApp events
  events = {
    verified: Field,
  };

  init() {
    // Initialize zkApp state
    super.init();
    // Set the oracle public key as zkApp on-chain state
    this.oraclePublicKey.set(PublicKey.fromBase58(ORACLE_PUBLIC_KEY));
  }

  @method async verify(id: Field, creditScore: Field, signature: Signature) {
    // Get the oracle public key from the zkApp state
    const oraclePublicKey = this.oraclePublicKey.get();
    this.oraclePublicKey.requireEquals(oraclePublicKey);
    // Evaluate whether the signature is valid for the provided data
    const validSignature = signature.verify(oraclePublicKey, [id, creditScore]);
    // Check that the signature is valid
    validSignature.assertTrue();
    // Check that the provided credit score is 700 or higher
    creditScore.assertGreaterThanOrEqual(Field(700));
    // Emit an event containing the verified user's id
    this.emitEvent('verified', id);
  }
}
```

## Test your smart contract

You can find the complete code for the smart contract tests [here](https://github.com/MinaProtocol/docs2/blob/main/examples/zkapps/07-oracles/contracts/src/OracleExample.test.ts).

When you ran the `zk file OracleExample` command, the zkApp CLI automatically generated a test file `src/OracleExample.test.ts`.

To add tests, paste the following code in the `OracleExample.test.ts` file:

```ts title="src/OracleExample.test.ts"
import { OracleExample } from './OracleExample';
import {
  Field,
  Mina,
  PrivateKey,
  PublicKey,
  AccountUpdate,
  Signature,
} from 'o1js';

let proofsEnabled = false;

// The public key of our trusted data provider
const ORACLE_PUBLIC_KEY =
  'B62qoAE4rBRuTgC42vqvEyUqCGhaZsW58SKVW4Ht8aYqP9UTvxFWBgy';

describe('OracleExample', () => {
  let deployerAccount: Mina.TestPublicKey,
    deployerKey: PrivateKey,
    senderAccount: Mina.TestPublicKey,
    senderKey: PrivateKey,
    zkAppAddress: PublicKey,
    zkAppPrivateKey: PrivateKey,
    zkApp: OracleExample;

  beforeAll(async () => {
    if (proofsEnabled) await OracleExample.compile();
  });

  beforeEach(async () => {
    const Local = await Mina.LocalBlockchain({ proofsEnabled });
    Mina.setActiveInstance(Local);
    deployerAccount = Local.testAccounts[0];
    deployerKey = deployerAccount.key;
    senderAccount = Local.testAccounts[1];
    senderKey = senderAccount.key;
    zkAppPrivateKey = PrivateKey.random();
    zkAppAddress = zkAppPrivateKey.toPublicKey();
    zkApp = new OracleExample(zkAppAddress);
  });

  async function localDeploy() {
    const txn = await Mina.transaction(deployerAccount, async () => {
      AccountUpdate.fundNewAccount(deployerAccount);
      await zkApp.deploy();
    });
    await txn.prove();
    // this tx needs .sign(), because `deploy()` adds an account update that requires signature authorization
    await txn.sign([deployerKey, zkAppPrivateKey]).send();
  }

  it('generates and deploys the `OracleExample` smart contract', async () => {
    await localDeploy();
    const oraclePublicKey = zkApp.oraclePublicKey.get();
    expect(oraclePublicKey).toEqual(PublicKey.fromBase58(ORACLE_PUBLIC_KEY));
  });

  describe('hardcoded values', () => {
    it('emits an `id` event containing the users id if their credit score is above 700 and the provided signature is valid', async () => {
      await localDeploy();

      const id = Field(1);
      const creditScore = Field(787);
      const signature = Signature.fromBase58(
        '7mXGPCbSJUiYgZnGioezZm7GCy46CEUbgcCH9nrJYXQQiwwVrA5wemBX4T1XFHUw62oR2324QNnkUVXW6yYQLsPsqxZ3nsYR'
      );

      const txn = await Mina.transaction(senderAccount, async () => {
        await zkApp.verify(id, creditScore, signature);
      });
      await txn.prove();
      await txn.sign([senderKey]).send();

      const events = await zkApp.fetchEvents();
      const verifiedEventValue = events[0].event.data.toFields(null)[0];
      expect(verifiedEventValue).toEqual(id);
    });

    it('throws an error if the credit score is below 700 even if the provided signature is valid', async () => {
      await localDeploy();

      const id = Field(2);
      const creditScore = Field(536);
      const signature = Signature.fromBase58(
        '7mXXnqMx6YodEkySD3yQ5WK7CCqRL1MBRTASNhrm48oR4EPmenD2NjJqWpFNZnityFTZX5mWuHS1WhRnbdxSTPzytuCgMGuL'
      );

      await expect(
        Mina.transaction(senderAccount, async () => {
          await zkApp.verify(id, creditScore, signature);
        })
      ).rejects.toThrow('expected 700 <= 536');
    });

    it('throws an error if the credit score is above 700 and the provided signature is invalid', async () => {
      await localDeploy();

      const id = Field(1);
      const creditScore = Field(787);
      const signature = Signature.fromBase58(
        '7mXPv97hRN7AiUxBjuHgeWjzoSgL3z61a5QZacVgd1PEGain6FmyxQ8pbAYd5oycwLcAbqJLdezY7PRAUVtokFaQP8AJDEGX'
      );

      await expect(
        Mina.transaction(senderAccount, async () => {
          await zkApp.verify(id, creditScore, signature);
        })
      ).rejects.toThrow('Bool.assertTrue');
    });
  });

  describe('actual API requests', () => {
    it('emits an `id` event containing the users id if their credit score is above 700 and the provided signature is valid', async () => {
      await localDeploy();

      const response = await fetch(
        'https://07-oracles.vercel.app/api/credit-score?user=1'
      );
      const data = await response.json();

      const id = Field(data.data.id);
      const creditScore = Field(data.data.creditScore);
      const signature = Signature.fromBase58(data.signature);

      const txn = await Mina.transaction(senderAccount, async () => {
        await zkApp.verify(id, creditScore, signature);
      });
      await txn.prove();
      await txn.sign([senderKey]).send();

      const events = await zkApp.fetchEvents();
      const verifiedEventValue = events[0].event.data.toFields(null)[0];
      expect(verifiedEventValue).toEqual(id);
    });

    it('throws an error if the credit score is below 700 even if the provided signature is valid', async () => {
      await localDeploy();

      const response = await fetch(
        'https://07-oracles.vercel.app/api/credit-score?user=2'
      );
      const data = await response.json();

      const id = Field(data.data.id);
      const creditScore = Field(data.data.creditScore);
      const signature = Signature.fromBase58(data.signature);

      await expect(
        Mina.transaction(senderAccount, async () => {
          await zkApp.verify(id, creditScore, signature);
        })
      ).rejects.toThrow('expected 700 <= 536');
    });
  });
});
```

Each failure test waits for the rejected transaction with `await expect(...).rejects.toThrow(...)` and checks the error message. Without `await` and a matcher such as `toThrow()`, the test checks nothing and passes even when the transaction succeeds.

To run the tests:

1. Save the `OracleExample.test.ts` file.
1. Run `npm install`.
1. Run `npm run test`.

The tests in the `actual API requests` group call the demo oracle at `https://07-oracles.vercel.app`, so they need network access. Writing a test that calls an API is generally not a best practice, but it's convenient for the sake of this tutorial. You can also mock your HTTP requests.

Congratulations! You have just built a simple oracle using o1js and the Mina blockchain.
You can find the complete code for this example [here](https://github.com/MinaProtocol/docs2/tree/main/examples/zkapps/07-oracles).

Check out [Tutorial 8: Custom Tokens](custom-tokens) to learn how to create and manage a token of your own.
