Skip to content

External Adapters

Steve Ellis edited this page Apr 27, 2020 · 12 revisions

External adapters are how Chainlink allows for easy integration of custom computations and specialized APIs. External adapters are adapters that run as services which the core of the Chainlink node communicates with via a simple API.

The most minimal response just has a response key/value nested in a data object:

{
      data: {
               result: "42" // Edit this field to change what the External Adapter reports
       }
}

Defining External Adapters

External adapters are added to the Chainlink node first by creating a bridge type. Bridges define the task type's name and URL of the external adapter which will run that task type. When a task type is received that is not one of the core adapters, the node will search for a bridge type with that name, creating a bridge to your external adapter. Task type names are case insensitive.

Example JSON for creating a bridge type:

{ "name": "randomNumber", "url": "http://localhost:3000/randomNumber" }

Example JSON for running a bridge type:

{
  "initiators": [{"type": "runLog"}],
  "tasks": [{"type": "randomNumber"}]
}

Using the Copy adapter with an External Adapter

The Copy adapter allows for the same functionality of the JSONParse adapter but for getting data from the external adapter's response.

For example, if an adapter returns JSON data like what is below:

{
    "firstValue": "SomeValue",
    "details": {
        "close": "100",
        "open": "110",
        "current": "111"
    },
    "other": "GetData"
}

And you wanted the value in the field "open", you could have a spec like the one below, and the value at "open" will be passed on to the next adapter in the task pipeline.

{
    "initiators": [
        { "type": "RunLog" }
    ],
    "tasks": [
        { "type": "MyExternalAdapter" },
        { "type": "copy",
          "copyPath": ["details", "open"] },
        { "type": "EthInt256" },
        { "type": "EthTx" }
    ]
}

Adding an External Adapter via API

Run the following command:

chainlink bridge '{"name":"randomNumber","url":"http://localhost:3000/randomNumber"}'

"name" should be unique to the local node, and "url" should be the URL of your external adapter, whether local or on a separate machine.

Output should return the JSON given:

{"name":"randomnumber","url":"http://localhost:3000/randomNumber"}

And the node will log the following:

{"level":"info","ts":1518531822.179224,"caller":"web/router.go:50","msg":"Web request","method":"POST","status":200,"path":"/v2/bridge_types","query":"","body":"{\"name\":\"randomNumber\",\"url\":\"http://localhost:3000/randomNumber\"}","clientIP":"127.0.0.1","comment":"","servedAt":"2018/02/13 - 14:23:42","latency":"1.623398ms"}

Notes on External Adapter Development

Requesting Data

When an external adapter receives a request from the Chainlink node, the JSON payload will include the JobRunID and a data object.

{"id":"278c97ffadb54a5bbb93cfec5f7b5503","data":{}}

Additional data may be specified in the spec to be utilized by the adapter. This can be useful for requesting data from a REST endpoint where the keys and values can be specified by the requester. For example, if the REST endpoint supports the following:

https://example.com/api/:parent/:child

Then the payload to the external adapter would need:

{"id":"278c97ffadb54a5bbb93cfec5f7b5503","data":{"parent":"myParentValue","child":"myChildValue"}}

The values for :parent and :child can be used within the adapter to dynamically build the URL for the request. This same concept can also be applied to URLs with query values. For example:

https://example.com/api/?parent=myParentValue&child=myChildValue

Returning Data

When the external adapter has a response payload, it will need to include it with the given JobRunID back to the node.

An example of the response data can look like:

{
  "jobRunID": "278c97ffadb54a5bbb93cfec5f7b5503",
  "data": {
    "symbol": "ETH-USD",
    "last": {
      "price": 467.85,
      "size": 0.01816561,
      "timestamp": 1528926483463
    }
  },
  "status": "completed",
  "error": null,
  "pending": false
}

You'll also notice some additional fields: status, error, and pending. An external adapter may mark the JobRun as pending if the answer needs to be returned at a specified time, or when a desired result is found. The pending field should also be set to true if this is the case. When the external adapter calls back to the node to update the JobRun, this should be done with an HTTP PATCH request, see the REST API documentation page for details.

Minimal External Adapter

Base

Write in app.js:

const express = require('express')
const bodyParser = require('body-parser')

const app = express()
app.use(bodyParser.json())

app.post('/', (req, res) => {
  console.log('POST Data: ', req)
  res.status(200).json({
    jobRunID: req.body.id,
    data: {
       result: 100.0 // Edit this field to change what the External Adapter reports
    },
    error: null
  })
})

const port = process.env.EA_PORT || 3000
app.listen(port, () => console.log(`Listening on port ${port}!`))

Run It

$ node app.js

Customize It

The main part to customize is what gets written into the data field, although you can also fill in error if something goes wrong.

Clone this wiki locally