Skip to content
This repository has been archived by the owner on Jan 24, 2023. It is now read-only.
/ dapp-oracle Public archive

A generic blockchain oracle implementation

Notifications You must be signed in to change notification settings

Agoric/dapp-oracle

Repository files navigation

Oracle Dapp

This Dapp is a generic way to interact with oracles such as the Chainlink decentralized oracle network. The oracle contract represents a single oracle, whose publicFacet can be published for people to query.

Chainlink Integration

There are three basic components to a given Chainlink integration:

  1. An External Initiator which monitors the Agoric chain for events indicating an oracle request is being made.
  2. An External Adapter which accepts requests from the Chainlink node and translates them into Agoric transactions.
  3. $LINK, a token which secures the oracle network.

The oracle query-only UI is deployed with agoric deploy api/spawn.js.

The "external adapter" is in Javascript and "external initiator" is in Golang.

See the chainlink-agoric subdirectory for more instructions on how to get started with Chainlink nodes.

Running a Local Builtin Oracle

This dapp provides a builtin oracle for testing, with a single configured job that implements a tiny subset of the adapters available via the flexible Chainlink Any API.

Note that using this in production is not recommended: you will have to ensure your ag-solo as always available, or your contracts will not be able to query it. Running a robust oracle is a detailed and time-consuming endeavour, and so we recommend instead that you use the Chainlink oracles already provided as a service on the Agoric chain.

Start with https://agoric.com/documentation/getting-started/before-using-agoric.html

Here is how you can install the builtin oracle on your existing agoric start client:

# Install the needed dependencies.
agoric install
# Deploy the builtin oracle plugin.
agoric deploy --allow-unsafe-plugins api/spawn.js
# Run the UI server.
(cd ui && yarn start)

Go to the oracle client page at http://localhost:3000 Use this oracle client UI to experiment with simple Chainlink HTTP queries while you are defining your contract.

You can modify the sample query to specify different parameters. Leave the jobId as it is to use the builtin oracle. The parameters are taken from:

  1. HttpGet or HttpPost, as determined by the presence of the get or post parameter respectively.
  2. An optional JsonParse, if the path parameter is defined. Set path: [] if you want to parse but not extract a specific path.
  3. An optional Multiply, if the times parameter is defined.

Publishing a scheduled query

If you want to publish a scheduled query on the chain:

  1. Create a push query in the dapp-oracle server page.
  2. Create an external oracle job that posts back to Agoric with the results and the push queryId.

As an example for Chainlink, ensure your "request_id" param is set to the push query's queryId and use the Agoric external adapter to submit your job's results.

{
  "initiators": [
    {
      "type": "cron",
      "params": {
          "schedule": "CRON_TZ=UTC */1 * * * *"
      }
    }
  ],
  "tasks": [
    {
      "type": "HTTPGet",
      "confirmations": 0,
      "params": { "get": "https://bitstamp.net/api/ticker/" }
    },
    {
      "type": "JSONParse",
      "params": { "path": [ "last" ] }
    },
    {
      "type": "Multiply",
      "params": { "times": 100 }
    },
    {
      "type": "Agoric",
      "params": {
        "request_id": <push queryId>,
        "payment": "0"
      }
    }
  ]
}

Publishing a price authority

If you have a jobId that returns a numeric string as the price of a unit of your input issuer, you can create a price authority from it using the Flux Notifier.

There are some different ways to follow these instructions:

  • Just running locally: you don't need to do anything special for PRIVILEGED-NODE versus ORACLE-NODE.
  • Testing Chainlink: when you see PRIVILEGED-NODE use --hostport=127.0.0.1:7999, and when you see ORACLE-NODE, use one of --hostport=127.0.0.1:689<N> (like --hostport=127.0.0.1:6891)
  • Deploying on-chain before Mainnet 3: you must use governance instead of a PRIVILEGED-NODE. Follow the steps in README-gov.md.
  1. Create a public price authority based on an aggregator on the PRIVILEGED-NODE, and send invitations to the ORACLE-NODEs (without ORACLE_NODE_ADDRESSES, use the current node):
IN_BRAND_LOOKUP='["wallet","brand","BLD"]' \
OUT_BRAND_LOOKUP='["agoricNames","oracleBrand","USD"]' \
agoric deploy api/aggregate.js
  1. On an ORACLE-NODE, find its agoric1... (or sim-...) address:
agoric deploy api/show-my-address.js
  1. On PRIVILEGED-NODE, send an aggregator invitation to the specified oracle:
ORACLE_ADDRESS=agoric1... \
agoric deploy api/invite-oracle.js
  1. Create a Flux Notifier on an ORACLE-NODE. NOTE: You will need to edit parameters at the top of api/flux-notifier.js to specify the notifier parameters before running this:
AGGREGATOR_INSTANCE_LOOKUP=<from step 1> \
IN_BRAND_LOOKUP='["wallet","brand","BLD"]' \
OUT_BRAND_LOOKUP='["agoricNames","oracleBrand","USD"]' \
FEE_ISSUER_LOOKUP='["wallet","issuer","RUN"]' \
agoric deploy api/flux-notifier.js

This command will wait until the first query returns valid data, and also add it to the aggregator.

Repeat steps 2 to 4 for as many ORACLE-NODEs as necessary.

  1. OPTIONAL: You can publish the resulting PRICE_AUTHORITY_BOARD_ID to the sim-chain's agoric.priceAuthority.
PRICE_AUTHORITY_BOARD_ID=<boardId of price authority> \
IN_BRAND_LOOKUP='["wallet","brand","BLD"]' \
OUT_BRAND_LOOKUP='["agoricNames","oracleBrand","USD"]' \
agoric deploy api/register.js

If this step fails (such as with local-chain or a public chain), you can use on-chain governance to install the price authority. Remember that this step is optional.

Here is a session testing the priceAuthority:

lookup('agoricNames', 'brand', 'BLD').then(brand => bld = brand)
// -> [Object Alleged: BLD brand]{}
lookup('agoricNames', 'oracleBrand', 'USD').then(brand => usd = brand)
// -> [Object Alleged: USD brand]{}
pa = E(home.board).getValue('<boardId of price authority>')
// -> [Object Alleged: PriceAuthority]{}
E(E(pa).makeQuoteNotifier({ value: 1_000n * 10n ** 6n, brand: bld }, usd)).getUpdateSince()
// -> {"updateCount":2,"value":{"quoteAmount":{"brand":[Object Alleged: quote brand]{},"value":[{"amountIn":{"brand":[Object Alleged: BLD brand]{},"value":1000000000000000000n},"amountOut":{"brand":[Object Alleged: USD brand]{},"value":10000000000000000000000n},"timer":[Object Alleged: timerService]{},"timestamp":1644701445n}]},"quotePayment":[Promise]}}

Single-query Usage

The E(publicFacet).makeQueryInvitation(query) call creates a query invitation, which can be redeemed (by paying any fees) via E(zoe).offer(invitation) for an oracle result.

The E(publicFacet).query(query) call creates an unpaid query.

Queries are answered by the oracle handler provided to the contract. Each query calls E(oracleHandler).onQuery(query, feeAmount) which returns a promise for the { reply, requiredFee }. If the oracle rejects the promise or the caller did not pay the requiredFee, the caller just gets a rejection, and their fee is refunded. Otherwise, the reply is returned to the caller as the result of the query.

About

A generic blockchain oracle implementation

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published