Oracle Example Guide

The Oracle hook allows you to manage and update data programmatically using a combination of binary models and C hooks. Here's a quickstart guide to get you up and running with the Oracle hook.

Create the Binary Models

Binary models are used to define the structure of the data that you want to manage with your Oracle. You'll need to create models that represent the data you're interested in.

Oracle Model

This model represents an individual oracle entry with an issuer, currency, and value.

import {
  BaseModel,
  Metadata,
  Currency,
  XRPAddress,
  XFL,
} from '@transia/hooks-toolkit/dist/npm/src/libs/binary-models'

export class OracleModel extends BaseModel {
  issuer: XRPAddress
  currency: Currency
  value: XFL

  // 48 bytes
  constructor(
    issuer: XRPAddress, // 20 byte / 0
    currency: Currency, // 20 byte / 20
    value: XFL // 8 byte / 40
  ) {
    super()
    this.issuer = issuer
    this.currency = currency
    this.value = value
  }

  getMetadata(): Metadata {
    return [
      { field: 'issuer', type: 'xrpAddress' },
      { field: 'currency', type: 'currency' },
      { field: 'value', type: 'xfl' },
    ]
  }

  toJSON() {
    return {
      issuer: this.issuer,
      currency: this.currency,
      value: this.value,
    }
  }
}

Oracle Array Model

This model represents an array of OracleModel entries.

import {
  BaseModel,
  Metadata,
} from '@transia/hooks-toolkit/dist/npm/src/libs/binary-models'
import { OracleModel } from './OracleModel'

export class OracleArrayModel extends BaseModel {
  oracles: OracleModel[]

  constructor(oracles: OracleModel[]) {
    super()
    this.oracles = oracles
  }

  getMetadata(): Metadata {
    return [
      {
        field: 'oracles',
        type: 'varModelArray',
        modelClass: OracleModel,
        maxArrayLength: 100,
      },
    ]
  }

  toJSON() {
    return {
      oracles: this.oracles,
    }
  }
}

Create the C Hook

The C hook is responsible for processing the data sent to the Oracle and updating the state accordingly.

//------------------------------------------------------------------------------
/*
    Copyright (c) 2024 Transia, LLC

    This financial product is intended for use by individuals or entities who
    possess the necessary licenses and qualifications to solicit and utilize
    such products in accordance with applicable laws and regulations.
    Unauthorized use or distribution of this product may be subject to legal
    consequences.

    The information provided in this financial product is for informational
    purposes only and should not be considered as financial advice or a
    recommendation to engage in any specific investment or financial strategy.
    It is important to consult with a qualified professional or financial
    advisor before making any investment decisions.
*/
//==============================================================================

#include "hookapi.h"

int64_t hook(uint32_t reserved)
{

    TRACESTR("oracle.c: Called.");

    // ACCOUNT: Hook Account
    uint8_t hook_accid[20];
    hook_account(hook_accid, 20);

    // ACCOUNT: OTXN Account
    uint8_t otxn_accid[20];
    otxn_field(otxn_accid, 20, sfAccount);

    if (!BUFFER_EQUAL_20(hook_accid, otxn_accid))
    {
        rollback(SBUF("oracle.c: Invalid OTXN `Account`"), __LINE__);
    }

    uint8_t txn_id[32];
    ASSERT(otxn_id(txn_id, 32, 0) == 32);

    ASSERT(otxn_slot(1) == 1);
    ASSERT(slot_subfield(1, sfBlob, 2) == 2);
    
    uint8_t buffer[676];

    ASSERT(slot(SBUF(buffer), 2) > 0);

    uint16_t len = (uint16_t)buffer[0];
    uint8_t* ptr = buffer + 1;
    if (len > 192)
    {
        len = 193 + ((len - 193) * 256) + ((uint16_t)(buffer[1]));
        ptr++;
    }

    uint8_t* end = ptr + len;
    while (ptr < end)
    {
        GUARD(48);
        uint8_t hash[32];

        util_sha512h(SBUF(hash), ptr, 40);
        ASSERT(state_set(ptr + 40, 8, hash, 32) == 8);
        ptr += 48;
    }

    accept(SBUF("oracle.c: Updated."), __LINE__);
    _g(1, 1);
    return 0;
}

Create the main() function file

The main() function file is where you'll use the models you've created to encode data and send it to the Oracle.

// xrpl
import { Invoke, SetHookFlags, TransactionMetadata } from '@transia/xrpl'
// xrpl-helpers
import {
  XrplIntegrationTestContext,
  setupClient,
  teardownClient,
  serverUrl,
} from '@transia/hooks-toolkit/dist/npm/src/libs/xrpl-helpers'
// src
import {
  Xrpld,
  SetHookParams,
  setHooksV3,
  createHookPayload,
  ExecutionUtility,
  generateHash,
  StateUtility,
  hexNamespace,
  clearHookStateV3,
  clearAllHooksV3,
  iHook,
} from '@transia/hooks-toolkit'
import { OracleModel } from './models/OracleModel'
import { OracleArrayModel } from './models/OracleArrayModel'
import { hexToXfl } from '@transia/hooks-toolkit/dist/npm/src/libs/binary-models'

async function main() {
  const testContext: XrplIntegrationTestContext = await setupClient(serverUrl)

  const hookWallet = testContext.hook1
  const acct1hook1 = createHookPayload({
    version: 0,
    createFile: 'oracle',
    namespace: 'oracle',
    flags: SetHookFlags.hsfOverride,
    hookOnArray: ['Invoke'],
  })
  await setHooksV3({
    client: testContext.client,
    seed: hookWallet.seed,
    hooks: [{ Hook: acct1hook1 }],
  } as SetHookParams)

  // Invoke - Update the oracle
  const oracleModel: OracleModel = new OracleModel(
    testContext.ic.issuer,
    testContext.ic.currency,
    1.2
  )
  const oracleArray: OracleArrayModel = new OracleArrayModel([oracleModel])

  const builtTx1: Invoke = {
    TransactionType: 'Invoke',
    Account: hookWallet.classicAddress,
    Blob: oracleArray.encode().slice(2, oracleArray.encode().length),
  }

  const result1 = await Xrpld.submit(testContext.client, {
    wallet: hookWallet,
    tx: builtTx1,
  })
  const hookExecutions1 = await ExecutionUtility.getHookExecutionsFromMeta(
    testContext.client,
    result1.meta as TransactionMetadata
  )
  const ns = generateHash(
    Buffer.from(
      oracleArray.encode().slice(2, oracleArray.encode().length - 16),
      'hex'
    )
  )
  const state = await StateUtility.getHookState(
    testContext.client,
    hookWallet.classicAddress,
    ns,
    hexNamespace('oracle')
  )
  console.log(state)
}

main()

Build the hook and run the main function

Finally, you'll need to compile the C hook and deploy it to the Xahau network. Then, run the main() function to invoke the Oracle with your data.

  1. Compile the C hook using the c2wasm-cli.
  2. Execute the main() function to deploy the hook and update the Oracle.

Was this page helpful?