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.
There are three basic components to a given Chainlink integration:
- An External Initiator which monitors the Agoric chain for events indicating an oracle request is being made.
- An External Adapter which accepts requests from the Chainlink node and translates them into Agoric transactions.
- $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.
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:
- HttpGet or
HttpPost, as determined by
the presence of the
get
orpost
parameter respectively. - An optional JsonParse, if
the
path
parameter is defined. Setpath: []
if you want to parse but not extract a specific path. - An optional Multiply, if
the
times
parameter is defined.
If you want to publish a scheduled query on the chain:
- Create a push query in the dapp-oracle server page.
- 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"
}
}
]
}
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
versusORACLE-NODE
. - Testing Chainlink: when you see
PRIVILEGED-NODE
use--hostport=127.0.0.1:7999
, and when you seeORACLE-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.
- Create a public price authority based on an aggregator on the
PRIVILEGED-NODE
, and send invitations to theORACLE-NODE
s (withoutORACLE_NODE_ADDRESSES
, use the current node):
IN_BRAND_LOOKUP='["wallet","brand","BLD"]' \
OUT_BRAND_LOOKUP='["agoricNames","oracleBrand","USD"]' \
agoric deploy api/aggregate.js
- On an
ORACLE-NODE
, find itsagoric1...
(orsim-...
) address:
agoric deploy api/show-my-address.js
- On
PRIVILEGED-NODE
, send an aggregator invitation to the specified oracle:
ORACLE_ADDRESS=agoric1... \
agoric deploy api/invite-oracle.js
- Create a Flux Notifier on an
ORACLE-NODE
. NOTE: You will need to edit parameters at the top ofapi/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-NODE
s as necessary.
- OPTIONAL: You can publish the resulting
PRICE_AUTHORITY_BOARD_ID
to the sim-chain'sagoric.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]}}
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.