Skip to content

Commit

Permalink
Completely remove the JSON bridge implementation, rewrite the README …
Browse files Browse the repository at this point in the history
…to reflect
  • Loading branch information
jleeh committed Oct 22, 2019
1 parent d6b9c9c commit 38c4999
Show file tree
Hide file tree
Showing 18 changed files with 175 additions and 558 deletions.
303 changes: 130 additions & 173 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,222 +9,179 @@

Bridges is a Chainlink adaptor framework, lowering the barrier of entry for anyone to create their own:

- Bridges CLI application, allowing you to quickly run an adaptor.
- Create adaptors with an easy to interpret JSON schema.
- Simple interface to implement your own custom adaptors that can do anything.
- Supports running in serverless environments such as AWS Lambda & GCP functions.
- A tested hardened library that removes the need to build your own HTTP server, allowing you to just focus on
adapter requirements.
- Simple interface to allow you to build an adapter that confides to Chainlink schema.
- Kept up to date with any changes, meaning no extra work for existing adapters to support new schema changes or
features.
- Supports running in serverless environments such as AWS Lambda & GCP functions with minimal effort.

## Contents
1. [Install](#install)
2. [Usage](#usage)
3. [Managing API Keys](#managing-api-keys)
4. [Testing your Bridge](#testing-your-bridge)
5. [Chainlink Integration](#chainlink-integration)
6. [Bridge JSON](#bridge-json)
7. [Examples](#examples)
1. [Code Examples](#code-examples)
2. [Running in AWS Lambda](#running-in-aws-lambda)
3. [Running in GCP Functions](#running-in-gcp-functions)
4. [Example Implementations](#example-implementations)
- [Basic](#basic)
- [Unauthenticated HTTP Calls](#unauthenticated-http-calls)
- [Authenticated HTTP Calls](#authenticated-http-calls)

## Install
## Code Examples

View the [releases page](https://github.com/linkpoolio/bridges/releases) and download the latest version for your
operating system, then add it to PATH.

## Usage

### Quick Usage

For the simplest adaptor, run the following:
```
bridges -b https://s3.linkpool.io/bridges/cryptocompare.json
```
Once running, the adaptor will be started on port 8080.

### Usage
```
Usage of bridges:
-b, --bridge string Filepath/URL of bridge JSON file
-p, --port int Server port (default 8080)
- [CryptoCompare](examples/cryptocompare): Simplest example.
- [API Aggregator](examples/apiaggregator): Aggregates multiple endpoints using mean/median/mode.
- [Wolfram Alpha](examples/wolframalpha): Short answers API, non-JSON, uses string splitting.
- [Gas Station](examples/gasstation): Single answer response, no authentication.
- [Asset Price](https://github.com/linkpoolio/asset-price-cl-ea): A more complex example that aggregates crypto asset
prices from multiple exchanges by weighted volume.

## Running in Docker
After implementing your bridge, if you'd like to run it in Docker, you can reference the Dockerfiles in
[examples](examples/cryptocompare/Dockerfile) to then use as a template for your own Dockerfile.

## Running in AWS Lambda
After you've completed implementing your bridge, you can then test it in AWS Lambda. To do so:

1. Build the executable:
```bash
GO111MODULE=on go build -o bridge
```
2. Add the file to a ZIP archive:
```bash
zip bridge.zip ./bridge
```
3. Upload the the zip file into AWS and then use `bridge` as the
handler.
4. Set the `LAMBDA` environment variable to `true` in AWS for
the adaptor to be compatible with Lambda.

## Running in GCP Functions
To support GCP within your bridge, you need to add an extra function into your bridge as the entrypoint:
```go
func Handler(w http.ResponseWriter, r *http.Request) {
bridges.NewServer(&Example{}).Handler(w, r)
}
```

With the `-b` flag, either URLs or relative file paths can be specified, for example:
```
bridges -b https://s3.linkpool.io/bridges/rapidapi.json
```
is equal to
```
bridges -b json/rapidapi.json
You can then use the gcloud CLI tool to deploy it, for example:
```bash
gcloud functions deploy bridge --runtime go111 --entry-point Handler
```

## Managing API Keys
## Example Implementations

Bridges supports passing in API keys on the bridge http calls. These api keys are fed in as environment variables
on running bridges. For example:
### Basic
Bridges works by providing a simple interface to confide to. The interface contains two functions, `Run` and `Opts`.
The `Run` function is called on each HTTP request, `Opts` is called on start-up. Below is a very basic implementation
that returns the `value` as passed in by Chainlink, set back as `newValue` in the response:

```
API_KEY=my-api-key bridges -b https://s3.linkpool.io/bridges/rapidapi.json
```

It's recommended to use secret managers for storing API keys.

#### Considerations
**API key environment variables may not be named `API_KEY`.** Refer to the JSON file for each variable name,
in `auth.env`.

Custom implementations of the bridges interface may also completely differ and not use environment variables.
```go
package main
### Lambda Usage
import (
"github.com/linkpoolio/bridges"
)
View the [releases page](https://github.com/linkpoolio/bridges/releases) and download the Linux x86-64 zip. Upload the
zip into Lambda and set the handler as `bridges`.
type MyAdapter struct{}
Then set the following environment variables:
func (ma *MyAdapter) Run(h *bridge.Helper) (interface{}, error) {
return map[string]string{"newValue": h.GetParam("value")}, nil
}
- `LAMBDA=true`
- `BRIDGE=<your bridge url>`

Since bridges queries the bridge URL each call, it's recommend to host your own JSON files in S3 for latency and
your own redundancy. This is not the case when running locally or using docker.

### Docker Usage
func (ma *MyAdapter) Opts() *bridge.Opts {
return &bridge.Opts{
Name: "MyAdapter",
Lambda: true,
}
}
Run by either appending arguments or setting environment variables:
```
docker run -it linkpool/bridges:latest -b https://s3.linkpool.io/bridges/rapidapi.json
func main() {
bridge.NewServer(&MyAdaptor{}).Start(8080)
}
```

## Testing your Bridge
### Unauthenticated HTTP Calls
The bridges library provides a helper object that intends to make actions like performing HTTP calls simpler, removing
the need to write extensive error handling or the need to have the knowledge of Go's in-built http libraries.
To test a bridge, you need to send a `POST` request to it in the Chainlink `RunResult` type. For example:
For example, this below implementation uses the `HTTPCall` function to make a simple unauthenticated call to ETH Gas
Station:
```go
package main
Start your bridge:
```
bridges -b https://s3.linkpool.io/bridges/cryptocompare.json
```
import (
"github.com/linkpoolio/bridges"
)
Call it:
```
curl -X POST -d "{\"jobRunId\":\"1234\",\"data\":{\"key\":\"value\"}}" http://localhost:8080
```
type GasStation struct{}
Result:
```json
{
"jobRunId":"1234",
"status":"completed",
"error":null,
"pending":false,
"data":{
"EUR":140.88,
"JPY":17717.05,
"USD":159.77,
"key":"value"
}
func (gs *GasStation) Run(h *bridges.Helper) (interface{}, error) {
obj := make(map[string]interface{})
err := h.HTTPCall(
http.MethodGet,
"https://ethgasstation.info/json/ethgasAPI.json",
&obj,
)
return obj, err
}
```
## Chainlink Integration

Once you have a running bridge, you can then add the URL of the running bridge to your Chainlink node in the UI.

1. Login to your Chainlink node
2. Click "Bridges"
3. Add a new bridge
4. Enter your bridges URL, for example: `http://localhost:8080/`

If your bridge has multiple paths or specifies a path other than `/`, you'll need to take that into account when adding
your bridge in Chainlink. For example, with the [RapidAPI](json/rapidapi.json) example, you'd have two URLs:

- `http://localhost:8080/get`
- `http://localhost:8080/post`

## Bridge JSON

Example JSON file below with all the fields set:
```json
[
{
"name": "Example",
"method": "POST",
"url": "http://exampleapi.com/endpoint",
"path": "/",
"auth": {
"type": "header",
"key": "X-API-KEY",
"env": "API_KEY"
},
"opts": {
"queryPassthrough": false,
"query": {
"key": "value"
},
"body": "{\"message\":\"Hello\"}",
"expectedCode": 200
}
}
]
```
func (gs *GasStation) Opts() *bridges.Opts {
return &bridges.Opts{
Name: "GasStation",
Lambda: true,
}
}
To then use this save it to file, for example `bridge.json`, then run:
```
bridges -b bridge.json
func main() {
bridges.NewServer(&GasStation{}).Start(8080)
}
```
The resulting adaptor will perform the following API call, when called on `POST http://localhost:8080/`:

- HTTP Method: `POST`
- Header X-API-KEY: From environment variable `API_KEY`
- URL: `http://exampleapi.com/endpoint?key=value`
- Body: `{"message":"Hello"}`

It will then check to see if the status code returned was 200.

## Examples

JSON:

- [CryptoCompare](json/cryptocompare.json): Simplest example.
- [AlphaVantage](json/alphavantage.json): Uses GET param authentication, param passthrough.
- [RapidAPI](json/rapidapi.json): Two adaptors specified, header authentication and param & form passthrough.

Interface implementations:

- [CryptoCompare](examples/cryptocompare): Simplest example.
- [API Aggregator](examples/apiaggregator): Aggregates multiple endpoints using mean/median/mode.
- [Wolfram Alpha](examples/wolframalpha): Short answers API, non-JSON, uses string splitting.
- [Asset Price](https://github.com/linkpoolio/asset-price-cl-ea): A more complex real example that aggregates crypto asset prices from multiple exchanges by weighted volume.

## Implement your own
### Authenticated HTTP Calls
Bridges also provides an interface to support authentication methods when making HTTP requests to external sources. By
default, bridges supports authentication via HTTP headers or GET parameters.
Below is a modified version of the WolframAlpha adapter, showing authentication setting the `appid` header from the
`APP_ID` environment variable:
```go
package main
import (
"github.com/linkpoolio/bridges/bridge"
"errors"
"fmt"
"github.com/linkpoolio/bridges"
"net/http"
"os"
"strings"
)
type MyAdaptor struct{}

func (ma *MyAdaptor) Run(h *bridge.Helper) (interface{}, error) {
return map[string]string{"hello": "world"}, nil
type WolframAlpha struct{}
func (cc *WolframAlpha) Run(h *bridges.Helper) (interface{}, error) {
b, err := h.HTTPCallRawWithOpts(
http.MethodGet,
"https://api.wolframalpha.com/v1/result",
bridges.CallOpts{
Auth: bridges.NewAuth(bridges.AuthParam, "appid", os.Getenv("APP_ID")),
Query: map[string]interface{}{
"i": h.GetParam("query"),
},
},
)
return fmt.Sprint(b), err
}
func (ma *MyAdaptor) Opts() *bridge.Opts {
return &bridge.Opts{
Name: "MyAdaptor",
func (cc *WolframAlpha) Opts() *bridges.Opts {
return &bridges.Opts{
Name: "WolframAlpha",
Lambda: true,
}
}
func main() {
bridge.NewServer(&MyAdaptor{}).Start(8080)
bridges.NewServer(&WolframAlpha{}).Start(8080)
}
```
### TODO

- [ ] Increase test coverage
- [ ] Support S3 urls for adaptor fetching
- [ ] Look at the validity of doing a Docker Hub style adaptor repository

### Contributing
We welcome all contributors, please raise any issues for any feature request, issue or suggestion you may have.
4 changes: 2 additions & 2 deletions bridge/bridge.go → bridge.go
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package bridge
package bridges

import (
"bytes"
Expand Down Expand Up @@ -389,4 +389,4 @@ type Header struct {
// Authenticate takes the key and value given and sets it as a header
func (p *Header) Authenticate(r *http.Request) {
r.Header.Add(p.Key, p.Value)
}
}
2 changes: 1 addition & 1 deletion bridge/bridge_test.go → bridge_test.go
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package bridge
package bridges

import (
"bytes"
Expand Down
Loading

0 comments on commit 38c4999

Please sign in to comment.