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
getorpostparameter respectively. - An optional JsonParse, if
the
pathparameter is defined. Setpath: []if you want to parse but not extract a specific path. - An optional Multiply, if
the
timesparameter 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-NODEversusORACLE-NODE. - Testing Chainlink: when you see
PRIVILEGED-NODEuse--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-NODEs (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.jsto 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.jsThis 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.
- OPTIONAL: You can publish the resulting
PRICE_AUTHORITY_BOARD_IDto 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.jsIf 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.