Skip to content

Commit

Permalink
Add "agoric" adapter (#114)
Browse files Browse the repository at this point in the history
* feat: add Agoric adapter

* fix: use agoric_oracle_query_id

* fix: make more explicit

* test: remove agoric/test

* fix: another attempt to plumb through the 'Task Run Data'

* fix: use the promise-based API

* fix: make request_id numeric

* fix: cast payment from number to string

* fix: properly export the Agoric async adapter and string queryId

* refactor: separate concerns and add tests

* fix: more robust adapter; send errors to the oracleServer

* feat!: surface errors from the oracle backend

* fix: match with actual POST reply from the ag-solo

* fix: use AdapterErrors to surface errors to the node operator

* test: add integration test

* fix: use Requester instead of axios

* chore: rename package to @chainlink/agoric

* ci: add "agoric" to adapters.json

* refactor: clarify implementation according to review comments

* refactor: use the adapter-test-helpers

* ci: fix the Agoric build process

* fix: address review comments

* refactor: standardize code based on example adapter

* fix: import makeConfig

* fix: correct the default agoric adapter parameters

* chore: remove dependency on bn.js
  • Loading branch information
michaelfig authored Mar 1, 2021
1 parent 1a39885 commit 04d9264
Show file tree
Hide file tree
Showing 12 changed files with 402 additions and 1 deletion.
1 change: 1 addition & 0 deletions .github/strategy/adapters.json
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
"image_name": "__ADAPTER__-adapter",
"adapter": [
"1forge",
"agoric",
"alphachain",
"alphavantage",
"amberdata",
Expand Down
5 changes: 5 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,11 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),

## [Unreleased]

### Added

- New adapters:
- `agoric` to push results to the Agoric blockchain

## [0.2.0-rc.1] - 2021-2-4

### Added
Expand Down
3 changes: 3 additions & 0 deletions agoric/.eslintrc.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
module.exports = {
...require('../.eslintrc.ts.js'),
}
39 changes: 39 additions & 0 deletions agoric/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
# Chainlink External Adapter for Agoric

This adapter posts a result to the [Agoric blockchain](https://agoric.com). See
the [Agoric Chainlink Oracle
integration](https://github.com/Agoric/dapp-oracle/tree/master/chainlink-agoric)
for details on how to use it with your Chainlink node.


| Required? | Name | Description | Options | Defaults to |
| :-------: | :------: | :-----------------: | :--------------------------: | :---------: |
| | endpoint | The endpoint to use | [agoric](#Agoric-endpoint) | agoric |

---

## Agoric endpoint

This is the endpoint exposed by your local `ag-solo` after installing the
[Agoric Chainlink Oracle
integration](https://github.com/Agoric/dapp-oracle/tree/master/chainlink-agoric).

The default is http://localhost:8000/api/oracle

### Input Params

| Required? | Name | Description | Options | Defaults to |
| :-------: | :------------------------: | :--------------------------------------: | :-----------------: | :---------: |
|| `request_id` | The Agoric oracle queryId | string | request_id from Agoric External Initiator |
| | `payment` | How much $LINK the Chainlink node would like to collect as a fee | number as a string | the whole fee the user offered |
|| `result` | The result to return to the Agoric oracle contract | string | |

## Output

```json
{
"jobRunID": "278c97ffadb54a5bbb93cfec5f7b5503",
"data": { "result": "..." },
"statusCode": 200
}
```
44 changes: 44 additions & 0 deletions agoric/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
{
"name": "@chainlink/agoric-adapter",
"version": "0.0.1",
"description": "Chainlink adapter to post to the Agoric blockchain",
"keywords": [
"Chainlink",
"LINK",
"blockchain",
"oracle"
],
"main": "dist/index.js",
"types": "dist/index.d.ts",
"files": [
"dist"
],
"repository": {
"url": "https://github.com/smartcontractkit/external-adapters-js",
"type": "git"
},
"license": "MIT",
"scripts": {
"prepublishOnly": "yarn build && yarn test:unit",
"setup": "yarn build",
"build": "tsc -b",
"lint": "eslint --ignore-path ../.eslintignore . --ext .js,.jsx,.ts,.tsx",
"lint:fix": "eslint --ignore-path ../.eslintignore . --ext .js,.jsx,.ts,.tsx --fix",
"test": "mocha --exit --timeout 3000 -r ts-node/register 'test/**/*.test.ts'",
"test:unit": "mocha --exit --grep @integration --invert -r ts-node/register 'test/**/*.test.ts'",
"test:integration": "mocha --exit --timeout 3000 --grep @integration -r ts-node/register 'test/**/*.test.ts'",
"server": "node -e 'require(\"./index.js\").server()'",
"server:dist": "node -e 'require(\"./dist/index.js\").server()'",
"start": "yarn server:dist"
},
"devDependencies": {
"@types/chai": "^4.2.11",
"@types/express": "^4.17.6",
"@types/mocha": "^7.0.2",
"@types/node": "^14.0.13",
"@typescript-eslint/eslint-plugin": "^3.9.0",
"@typescript-eslint/parser": "^3.9.0",
"ts-node": "^8.10.2",
"typescript": "^3.9.7"
}
}
117 changes: 117 additions & 0 deletions agoric/src/adapter.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,117 @@
import { BigNumber } from 'ethers'

import { Config, ExecuteWithConfig, ExecuteFactory } from '@chainlink/types'
import { Requester, Validator, AdapterError } from '@chainlink/external-adapter'

import { makeConfig } from './config'

// We're on localhost, so retries just confuse the oracle state.
const NUM_RETRIES = 1

export interface Action {
type: string
data: unknown
}

const inputParams = {
request_id: ['request_id'],
result: ['result'],
payment: ['payment'],
}

// FIXME: Ideally, these would be the same.
const LINK_UNIT = BigNumber.from(10).pow(BigNumber.from(18))
const LINK_AGORIC_UNIT = BigNumber.from(10).pow(BigNumber.from(6))

// Convert the payment in $LINK into Agoric's pegged $LINK token.
export const getRequiredFee = (value: string | number): number => {
const paymentCL = BigNumber.from(value)
const paymentAgoricLink = paymentCL.mul(LINK_AGORIC_UNIT).div(LINK_UNIT)
return paymentAgoricLink.toNumber()
}

export interface PostReply {
ok: boolean
res?: unknown
rej?: unknown
}

const executeImpl: ExecuteWithConfig<Config> = async (request, config) => {
const validator = new Validator(request, inputParams)
if (validator.error) {
throw validator.error
}

Requester.logConfig(config)

const jobRunID = validator.validated.id
const { request_id: queryId, result, payment } = validator.validated.data
const requiredFee = getRequiredFee(payment)

const obj = {
type: 'oracleServer/reply',
data: { queryId, reply: result, requiredFee },
}

const response = await Requester.request(
{
...config.api,
method: 'POST',
data: obj,
},
undefined,
NUM_RETRIES,
)

const pr = response.data as PostReply
if (!pr.ok) {
throw Error(`${obj.type} response failed: ${pr.rej}`)
}

return Requester.success(jobRunID, {
data: { result },
result,
status: 200,
})
}

const tryExecuteLogError = (
execute: ExecuteWithConfig<Config>,
): ExecuteWithConfig<Config> => async (request, config) => {
try {
return await execute(request, config)
} catch (e) {
const queryId = request.data?.request_id
const rest = { queryId }

await Requester.request(
{
...config.api,
method: 'POST',
data: {
type: 'oracleServer/error',
data: { error: `${(e && e.message) || e}`, ...(queryId && rest) },
},
},
undefined,
NUM_RETRIES,
).catch((e2: Error) => console.error(`Cannot reflect error to caller:`, e2))

// See https://github.com/smartcontractkit/external-adapters-js/issues/204
// for discussion of why this code is necessary.
if (e instanceof AdapterError) {
throw e
}
throw new AdapterError({
jobRunID: request.id,
statusCode: 500,
message: `${(e && e.message) || e}`,
cause: e,
})
}
}

export const execute = tryExecuteLogError(executeImpl)
export const makeExecute: ExecuteFactory<Config> = (config) => {
return async (request) => execute(request, config || makeConfig())
}
17 changes: 17 additions & 0 deletions agoric/src/config.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import { Requester } from '@chainlink/external-adapter'
import { Config } from '@chainlink/types'
import { util } from '@chainlink/ea-bootstrap'

export const DEFAULT_API_ENDPOINT = 'http://localhost:8000/api/oracle'

// This environment variable is needed for the Hack the Orb oracle
// instructions to remain correct.
const LEGACY_API_ENDPOINT_ENV = 'AG_SOLO_ORACLE_URL'

export const makeConfig = (prefix?: string): Config => {
const config = Requester.getDefaultConfig(prefix)
config.api.baseURL =
config.api.baseURL || util.getEnv(LEGACY_API_ENDPOINT_ENV) || DEFAULT_API_ENDPOINT
config.apiKey = config.apiKey || 'not required'
return config
}
7 changes: 7 additions & 0 deletions agoric/src/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
import { expose } from '@chainlink/ea-bootstrap'
import { makeExecute } from './adapter'
import { makeConfig } from './config'

const NAME = 'Agoric'

export = { NAME, makeExecute, makeConfig, ...expose(makeExecute()) }
Loading

0 comments on commit 04d9264

Please sign in to comment.