Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add "agoric" adapter #114

Merged
Merged
Show file tree
Hide file tree
Changes from 25 commits
Commits
Show all changes
26 commits
Select commit Hold shift + click to select a range
63569ef
feat: add Agoric adapter
michaelfig Oct 19, 2020
009615d
fix: use agoric_oracle_query_id
michaelfig Oct 21, 2020
7d14c91
fix: make more explicit
michaelfig Oct 22, 2020
0e41f3b
test: remove agoric/test
michaelfig Oct 22, 2020
9c64428
fix: another attempt to plumb through the 'Task Run Data'
michaelfig Oct 22, 2020
161914f
fix: use the promise-based API
michaelfig Oct 22, 2020
d232abc
fix: make request_id numeric
michaelfig Oct 22, 2020
1881af8
fix: cast payment from number to string
michaelfig Oct 22, 2020
0f6ac66
fix: properly export the Agoric async adapter and string queryId
michaelfig Nov 4, 2020
aa83764
refactor: separate concerns and add tests
michaelfig Nov 10, 2020
94f9d46
fix: more robust adapter; send errors to the oracleServer
michaelfig Nov 10, 2020
2bb6e4f
feat!: surface errors from the oracle backend
michaelfig Nov 10, 2020
e376678
fix: match with actual POST reply from the ag-solo
michaelfig Nov 11, 2020
4b3b92e
fix: use AdapterErrors to surface errors to the node operator
michaelfig Nov 11, 2020
90bca32
test: add integration test
michaelfig Nov 13, 2020
821c0ad
fix: use Requester instead of axios
michaelfig Nov 13, 2020
f7cd15b
chore: rename package to @chainlink/agoric
michaelfig Nov 13, 2020
728b36e
ci: add "agoric" to adapters.json
michaelfig Nov 16, 2020
6e3113e
refactor: clarify implementation according to review comments
michaelfig Nov 24, 2020
8986f8a
refactor: use the adapter-test-helpers
michaelfig Nov 25, 2020
ef3cf33
ci: fix the Agoric build process
michaelfig Dec 4, 2020
5740690
fix: address review comments
michaelfig Feb 15, 2021
1802067
refactor: standardize code based on example adapter
michaelfig Feb 26, 2021
c4c8d8c
fix: import makeConfig
michaelfig Feb 26, 2021
302eaa6
fix: correct the default agoric adapter parameters
michaelfig Mar 1, 2021
67c9767
chore: remove dependency on bn.js
michaelfig Mar 1, 2021
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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

michaelfig marked this conversation as resolved.
Show resolved Hide resolved
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
}
```
47 changes: 47 additions & 0 deletions agoric/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
{
"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"
},
"dependencies": {
"bn.js": "^4.11.9"
michaelfig marked this conversation as resolved.
Show resolved Hide resolved
michaelfig marked this conversation as resolved.
Show resolved Hide resolved
}
}
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 => {
michaelfig marked this conversation as resolved.
Show resolved Hide resolved
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