diff --git a/1forge/README.md b/1forge/README.md new file mode 100644 index 0000000000..c352dd06d2 --- /dev/null +++ b/1forge/README.md @@ -0,0 +1,23 @@ +# Chainlink 1Forge External Adapter + +## Input Params + +- `base` or `to`: The target currency to query (required) +- `quote` or `from`: The currency to convert to (required) +- `endpoint`: The endpoint to call (optional) + +## Output + +```json +{ + "jobRunID": "1", + "data": { + "value": 1.22687, + "text": "1.0 GBP is worth 1.22687 USD", + "timestamp": 1587489920, + "result": 1.22687 + }, + "result": 1.22687, + "statusCode": 200 +} +``` diff --git a/1forge/adapter.js b/1forge/adapter.js new file mode 100644 index 0000000000..69459eb0b1 --- /dev/null +++ b/1forge/adapter.js @@ -0,0 +1,42 @@ +const { Requester, Validator } = require('external-adapter') + +const customParams = { + base: ['base', 'from'], + quote: ['quote', 'to'], + endpoint: false, + quantity: false +} + +const createRequest = (input, callback) => { + const validator = new Validator(input, customParams, callback) + const jobRunID = validator.validated.id + const endpoint = validator.validated.data.endpoint || 'convert' + const url = `https://api.1forge.com/${endpoint}` + const from = validator.validated.data.base.toUpperCase() + const to = validator.validated.data.quote.toUpperCase() + const quantity = validator.validated.data.quantity || 1 + const api_key = process.env.API_KEY // eslint-disable-line camelcase + + const qs = { + from, + to, + quantity, + api_key + } + + const options = { + url, + qs + } + + Requester.requestRetry(options) + .then(response => { + response.body.result = Requester.validateResult(response.body, ['value']) + callback(response.statusCode, Requester.success(jobRunID, response)) + }) + .catch(error => { + callback(500, Requester.errored(jobRunID, error)) + }) +} + +module.exports.createRequest = createRequest diff --git a/1forge/package.json b/1forge/package.json new file mode 100644 index 0000000000..bb4c51063d --- /dev/null +++ b/1forge/package.json @@ -0,0 +1,8 @@ +{ + "name": "1forge", + "version": "0.1.0", + "license": "MIT", + "scripts": { + "test": "yarn _mocha --timeout 0" + } +} diff --git a/1forge/test/adapter_test.js b/1forge/test/adapter_test.js new file mode 100644 index 0000000000..2e6ee7ef74 --- /dev/null +++ b/1forge/test/adapter_test.js @@ -0,0 +1,50 @@ +const assert = require('chai').assert +const createRequest = require('../adapter').createRequest + +describe('createRequest', () => { + const jobID = '1' + + context('successful calls', () => { + const requests = [ + { name: 'id not supplied', testData: { data: { base: 'GBP', quote: 'USD' } } }, + { name: 'base/quote', testData: { id: jobID, data: { base: 'GBP', quote: 'USD' } } }, + { name: 'from/to', testData: { id: jobID, data: { from: 'GBP', to: 'USD' } } } + ] + + requests.forEach(req => { + it(`${req.name}`, (done) => { + createRequest(req.testData, (statusCode, data) => { + assert.equal(statusCode, 200) + assert.equal(data.jobRunID, jobID) + assert.isNotEmpty(data.data) + assert.isAbove(Number(data.result), 0) + assert.isAbove(Number(data.data.result), 0) + done() + }) + }) + }) + }) + + context('error calls', () => { + const requests = [ + { name: 'empty body', testData: {} }, + { name: 'empty data', testData: { data: {} } }, + { name: 'base not supplied', testData: { id: jobID, data: { quote: 'USD' } } }, + { name: 'quote not supplied', testData: { id: jobID, data: { base: 'GBP' } } }, + { name: 'unknown base', testData: { id: jobID, data: { base: 'not_real', quote: 'USD' } } }, + { name: 'unknown quote', testData: { id: jobID, data: { base: 'GBP', quote: 'not_real' } } } + ] + + requests.forEach(req => { + it(`${req.name}`, (done) => { + createRequest(req.testData, (statusCode, data) => { + assert.equal(statusCode, 500) + assert.equal(data.jobRunID, jobID) + assert.equal(data.status, 'errored') + assert.isNotEmpty(data.error) + done() + }) + }) + }) + }) +}) diff --git a/alphavantage/README.md b/alphavantage/README.md new file mode 100644 index 0000000000..2f2a7aa47c --- /dev/null +++ b/alphavantage/README.md @@ -0,0 +1,33 @@ +# Chainlink External Adapter for AlphaVantage + +Use this adapter for connecting to AlphaVantage's API from a Chainlink node. + +## Input params + +- `function`: (Optional) The function to call (defaults to CURRENCY_EXCHANGE_RATE) +- `base`, `from`, or `coin`: The asset to query +- `quote`, `to`, or `market`: The currency to convert to + +## Output + +```json +{ + "jobRunID": "1", + "data": { + "Realtime Currency Exchange Rate": { + "1. From_Currency Code": "ETH", + "2. From_Currency Name": "Ethereum", + "3. To_Currency Code": "USD", + "4. To_Currency Name": "United States Dollar", + "5. Exchange Rate": "170.88000000", + "6. Last Refreshed": "2020-04-16 19:15:01", + "7. Time Zone": "UTC", + "8. Bid Price": "170.84000000", + "9. Ask Price": "170.88000000" + }, + "result": 170.88 + }, + "result": 170.88, + "statusCode": 200 +} +``` diff --git a/alphavantage/adapter.js b/alphavantage/adapter.js new file mode 100644 index 0000000000..637e961aee --- /dev/null +++ b/alphavantage/adapter.js @@ -0,0 +1,48 @@ +const { Requester, Validator } = require('external-adapter') + +const customError = (body) => { + if (body['Error Message']) return true + return false +} + +const customParams = { + base: ['base', 'from', 'coin'], + quote: ['quote', 'to', 'market'], + function: false +} + +const createRequest = (input, callback) => { + const validator = new Validator(input, customParams, callback) + const url = 'https://www.alphavantage.co/query' + const jobRunID = validator.validated.id + const func = validator.validated.data.function || 'CURRENCY_EXCHANGE_RATE' + const from = validator.validated.data.base + const to = validator.validated.data.quote + + const qs = { + function: func, + from_currency: from, + to_currency: to, + from_symbol: from, + to_symbol: to, + symbol: from, + market: to, + apikey: process.env.API_KEY + } + + const options = { + url, + qs + } + Requester.requestRetry(options, customError) + .then(response => { + response.body.result = JSON.parse(Requester.validateResult( + response.body, ['Realtime Currency Exchange Rate', '5. Exchange Rate'])) + callback(response.statusCode, Requester.success(jobRunID, response)) + }) + .catch(error => { + callback(500, Requester.errored(jobRunID, error)) + }) +} + +module.exports.createRequest = createRequest diff --git a/alphavantage/package.json b/alphavantage/package.json new file mode 100644 index 0000000000..c10ae35206 --- /dev/null +++ b/alphavantage/package.json @@ -0,0 +1,8 @@ +{ + "name": "alphavantage", + "version": "0.1.0", + "license": "MIT", + "scripts": { + "test": "yarn _mocha --timeout 0" + } +} diff --git a/alphavantage/test/adapter_test.js b/alphavantage/test/adapter_test.js new file mode 100644 index 0000000000..b46ef677ef --- /dev/null +++ b/alphavantage/test/adapter_test.js @@ -0,0 +1,51 @@ +const assert = require('chai').assert +const createRequest = require('../adapter').createRequest + +describe('createRequest', () => { + const jobID = '1' + + context('successful calls', () => { + const requests = [ + { name: 'id not supplied', testData: { data: { base: 'ETH', quote: 'USD' } } }, + { name: 'base/quote', testData: { id: jobID, data: { base: 'ETH', quote: 'USD' } } }, + { name: 'from/to', testData: { id: jobID, data: { from: 'ETH', to: 'USD' } } }, + { name: 'coin/market', testData: { id: jobID, data: { coin: 'ETH', market: 'USD' } } } + ] + + requests.forEach(req => { + it(`${req.name}`, (done) => { + createRequest(req.testData, (statusCode, data) => { + assert.equal(statusCode, 200) + assert.equal(data.jobRunID, jobID) + assert.isNotEmpty(data.data) + assert.isAbove(Number(data.result), 0) + assert.isAbove(Number(data.data.result), 0) + done() + }) + }) + }) + }) + + context('error calls', () => { + const requests = [ + { name: 'empty body', testData: {} }, + { name: 'empty data', testData: { data: {} } }, + { name: 'base not supplied', testData: { id: jobID, data: { quote: 'USD' } } }, + { name: 'quote not supplied', testData: { id: jobID, data: { base: 'ETH' } } }, + { name: 'unknown base', testData: { id: jobID, data: { base: 'not_real', quote: 'USD' } } }, + { name: 'unknown quote', testData: { id: jobID, data: { base: 'ETH', quote: 'not_real' } } } + ] + + requests.forEach(req => { + it(`${req.name}`, (done) => { + createRequest(req.testData, (statusCode, data) => { + assert.equal(statusCode, 500) + assert.equal(data.jobRunID, jobID) + assert.equal(data.status, 'errored') + assert.isNotEmpty(data.error) + done() + }) + }) + }) + }) +}) diff --git a/amberdata/test/index_test.js b/amberdata/test/adapter_test.js similarity index 100% rename from amberdata/test/index_test.js rename to amberdata/test/adapter_test.js diff --git a/bravenewcoin/test/index_test.js b/bravenewcoin/test/adapter_test.js similarity index 100% rename from bravenewcoin/test/index_test.js rename to bravenewcoin/test/adapter_test.js diff --git a/coinapi/test/index_test.js b/coinapi/test/adapter_test.js similarity index 100% rename from coinapi/test/index_test.js rename to coinapi/test/adapter_test.js diff --git a/coingecko/test/index_test.js b/coingecko/test/adapter_test.js similarity index 100% rename from coingecko/test/index_test.js rename to coingecko/test/adapter_test.js diff --git a/coinmarketcap/test/index_test.js b/coinmarketcap/test/adapter_test.js similarity index 100% rename from coinmarketcap/test/index_test.js rename to coinmarketcap/test/adapter_test.js diff --git a/coinpaprika/test/index_test.js b/coinpaprika/test/adapter_test.js similarity index 100% rename from coinpaprika/test/index_test.js rename to coinpaprika/test/adapter_test.js diff --git a/cryptoapis/test/index_test.js b/cryptoapis/test/adapter_test.js similarity index 100% rename from cryptoapis/test/index_test.js rename to cryptoapis/test/adapter_test.js diff --git a/cryptocompare/test/index_test.js b/cryptocompare/test/adapter_test.js similarity index 100% rename from cryptocompare/test/index_test.js rename to cryptocompare/test/adapter_test.js diff --git a/currencylayer/README.md b/currencylayer/README.md new file mode 100644 index 0000000000..8f08514907 --- /dev/null +++ b/currencylayer/README.md @@ -0,0 +1,33 @@ +# Chainlink CurrencyLayer External Adapter + +## Input Params: + +- `base` or `from`: Specify the currency to convert from (required) +- `quote` or `to`: Specify the currency to convert to (required) +- `endpoint`: The endpoint to call (optional) +- `amount`: Specify the amount to convert (optional) + +## Output + +```json +{ + "jobRunID": "1", + "data": { + "success": true, + "query": { + "from": "GBP", + "to": "JPY", + "amount": 1 + }, + "info": { + "timestamp": 1519328414, + "rate": 148.972231 + }, + "historical": "", + "date": "2018-02-22", + "result": 148.972231 + }, + "result": 148.972231, + "statusCode": 200 +} +``` diff --git a/currencylayer/adapter.js b/currencylayer/adapter.js new file mode 100644 index 0000000000..0b792ad2e3 --- /dev/null +++ b/currencylayer/adapter.js @@ -0,0 +1,42 @@ +const { Requester, Validator } = require('external-adapter') + +const customParams = { + base: ['base', 'from'], + quote: ['quote', 'to'], + endpoint: false, + amount: false +} + +const createRequest = (input, callback) => { + const validator = new Validator(input, customParams, callback) + const jobRunID = validator.validated.id + const endpoint = validator.validated.data.endpoint || 'convert' + const url = `https://apilayer.net/api/${endpoint}` + const from = validator.validated.data.base.toUpperCase() + const to = validator.validated.data.quote.toUpperCase() + const amount = validator.validated.data.amount || 1 + const access_key = process.env.API_KEY // eslint-disable-line camelcase + + const qs = { + from, + to, + amount, + access_key + } + + const options = { + url, + qs + } + + Requester.requestRetry(options) + .then(response => { + response.body.result = Requester.validateResult(response.body, ['result']) + callback(response.statusCode, Requester.success(jobRunID, response)) + }) + .catch(error => { + callback(500, Requester.errored(jobRunID, error)) + }) +} + +module.exports.createRequest = createRequest diff --git a/currencylayer/package.json b/currencylayer/package.json new file mode 100644 index 0000000000..768ae3b84f --- /dev/null +++ b/currencylayer/package.json @@ -0,0 +1,8 @@ +{ + "name": "currencylayer", + "version": "0.1.0", + "license": "MIT", + "scripts": { + "test": "yarn _mocha --timeout 0" + } +} diff --git a/currencylayer/test/adapter_test.js b/currencylayer/test/adapter_test.js new file mode 100644 index 0000000000..2e6ee7ef74 --- /dev/null +++ b/currencylayer/test/adapter_test.js @@ -0,0 +1,50 @@ +const assert = require('chai').assert +const createRequest = require('../adapter').createRequest + +describe('createRequest', () => { + const jobID = '1' + + context('successful calls', () => { + const requests = [ + { name: 'id not supplied', testData: { data: { base: 'GBP', quote: 'USD' } } }, + { name: 'base/quote', testData: { id: jobID, data: { base: 'GBP', quote: 'USD' } } }, + { name: 'from/to', testData: { id: jobID, data: { from: 'GBP', to: 'USD' } } } + ] + + requests.forEach(req => { + it(`${req.name}`, (done) => { + createRequest(req.testData, (statusCode, data) => { + assert.equal(statusCode, 200) + assert.equal(data.jobRunID, jobID) + assert.isNotEmpty(data.data) + assert.isAbove(Number(data.result), 0) + assert.isAbove(Number(data.data.result), 0) + done() + }) + }) + }) + }) + + context('error calls', () => { + const requests = [ + { name: 'empty body', testData: {} }, + { name: 'empty data', testData: { data: {} } }, + { name: 'base not supplied', testData: { id: jobID, data: { quote: 'USD' } } }, + { name: 'quote not supplied', testData: { id: jobID, data: { base: 'GBP' } } }, + { name: 'unknown base', testData: { id: jobID, data: { base: 'not_real', quote: 'USD' } } }, + { name: 'unknown quote', testData: { id: jobID, data: { base: 'GBP', quote: 'not_real' } } } + ] + + requests.forEach(req => { + it(`${req.name}`, (done) => { + createRequest(req.testData, (statusCode, data) => { + assert.equal(statusCode, 500) + assert.equal(data.jobRunID, jobID) + assert.equal(data.status, 'errored') + assert.isNotEmpty(data.error) + done() + }) + }) + }) + }) +}) diff --git a/finnhub/README.md b/finnhub/README.md new file mode 100644 index 0000000000..1a34508c4a --- /dev/null +++ b/finnhub/README.md @@ -0,0 +1,27 @@ +# Chainlink External Adapter for Finnhub + +This external adapter is used to connect to [Finnhub's](https://finnhub.io/docs/api) API for stock data. + +## Input Params + +- `base`, `asset` or `from`: The target currency to query (required) +- `endpoint`: The endpoint to call (optional) + +## Output Format + +```json +{ + "jobRunID": "278c97ffadb54a5bbb93cfec5f7b5503", + "data": { + "c": 244.59, + "h": 258.25, + "l": 244.3, + "o": 250.75, + "pc": 246.88, + "t": 1585143000, + "result": 244.59 + }, + "result": 244.59, + "statusCode": 200 +} +``` diff --git a/finnhub/adapter.js b/finnhub/adapter.js new file mode 100644 index 0000000000..f240927ce6 --- /dev/null +++ b/finnhub/adapter.js @@ -0,0 +1,50 @@ +const { Requester, Validator } = require('external-adapter') + +const commonKeys = { + N225: '^N225', + FTSE: '^FTSE', + XAU: 'OANDA:XAU_USD', + XAG: 'OANDA:XAG_USD', + AUD: 'OANDA:AUD_USD', + EUR: 'OANDA:EUR_USD', + GBP: 'OANDA:GBP_USD' + // CHF & JPY are not supported +} + +const customParams = { + base: ['base', 'asset', 'from'], + endpoint: false +} + +const createRequest = (input, callback) => { + const validator = new Validator(input, customParams, callback) + const jobRunID = validator.validated.id + const endpoint = validator.validated.data.endpoint || 'quote' + const url = `https://finnhub.io/api/v1/${endpoint}` + let symbol = validator.validated.data.base.toUpperCase() + if (commonKeys[symbol]) { + symbol = commonKeys[symbol] + } + const token = process.env.API_KEY + + const qs = { + symbol, + token + } + + const options = { + url, + qs + } + + Requester.requestRetry(options) + .then(response => { + response.body.result = Requester.validateResult(response.body, ['c']) + callback(response.statusCode, Requester.success(jobRunID, response)) + }) + .catch(error => { + callback(500, Requester.errored(jobRunID, error)) + }) +} + +module.exports.createRequest = createRequest diff --git a/finnhub/package.json b/finnhub/package.json new file mode 100644 index 0000000000..bc96514e93 --- /dev/null +++ b/finnhub/package.json @@ -0,0 +1,8 @@ +{ + "name": "finnhub", + "version": "0.1.0", + "license": "MIT", + "scripts": { + "test": "yarn _mocha --timeout 0" + } +} diff --git a/finnhub/test/adapter_test.js b/finnhub/test/adapter_test.js new file mode 100644 index 0000000000..a0ca82c14e --- /dev/null +++ b/finnhub/test/adapter_test.js @@ -0,0 +1,81 @@ +const assert = require('chai').assert +const createRequest = require('../adapter').createRequest + +describe('createRequest', () => { + const jobID = '1' + + context('successful calls', () => { + const requests = [ + { + name: 'id not supplied', + testData: { data: { base: 'XAU' } } + }, + { + name: 'base', + testData: { + id: jobID, + data: { base: 'XAU' } + } + }, + { + name: 'from', + testData: { + id: jobID, + data: { from: 'XAU' } + } + }, + { + name: 'asset', + testData: { + id: jobID, + data: { asset: 'XAU' } + } + } + ] + + requests.forEach(req => { + it(`${req.name}`, (done) => { + createRequest(req.testData, (statusCode, data) => { + assert.equal(statusCode, 200) + assert.equal(data.jobRunID, jobID) + assert.isNotEmpty(data.data) + assert.isAbove(Number(data.result), 0) + assert.isAbove(Number(data.data.result), 0) + done() + }) + }) + }) + }) + + context('error calls', () => { + const requests = [ + { + name: 'empty body', + testData: {} + }, + { + name: 'empty data', + testData: { data: {} } + }, + { + name: 'unknown base', + testData: { + id: jobID, + data: { base: 'not_real' } + } + } + ] + + requests.forEach(req => { + it(`${req.name}`, (done) => { + createRequest(req.testData, (statusCode, data) => { + assert.equal(statusCode, 500) + assert.equal(data.jobRunID, jobID) + assert.equal(data.status, 'errored') + assert.isNotEmpty(data.error) + done() + }) + }) + }) + }) +}) diff --git a/fixer/README.md b/fixer/README.md new file mode 100644 index 0000000000..7ceb40c9e7 --- /dev/null +++ b/fixer/README.md @@ -0,0 +1,35 @@ +# Chainlink Fixer External Adapter + +This adapter is for [Fixer.io](https://fixer.io/) and supports the convert endpoint. + +## Input Params + +- `base` or `from`: The target currency to query (required) +- `quote` or `to`: The currency to convert to (required) +- `endpoint`: The endpoint to call (optional) +- `amount`: The amount to convert (optional) + +## Output + +```json +{ + "jobRunID": "1", + "data": { + "success": true, + "query": { + "from": "GBP", + "to": "JPY", + "amount": 1 + }, + "info": { + "timestamp": 1519328414, + "rate": 148.972231 + }, + "historical": "", + "date": "2018-02-22", + "result": 148.972231 + }, + "result": 148.972231, + "statusCode": 200 +} +``` diff --git a/fixer/adapter.js b/fixer/adapter.js new file mode 100644 index 0000000000..99c241e648 --- /dev/null +++ b/fixer/adapter.js @@ -0,0 +1,42 @@ +const { Requester, Validator } = require('external-adapter') + +const customParams = { + base: ['base', 'from'], + quote: ['quote', 'to'], + endpoint: false, + amount: false +} + +const createRequest = (input, callback) => { + const validator = new Validator(input, customParams, callback) + const jobRunID = validator.validated.id + const endpoint = validator.validated.data.endpoint || 'convert' + const url = `https://data.fixer.io/api/${endpoint}` + const from = validator.validated.data.base.toUpperCase() + const to = validator.validated.data.quote.toUpperCase() + const amount = validator.validated.data.amount || 1 + const access_key = process.env.API_KEY // eslint-disable-line camelcase + + const qs = { + from, + to, + amount, + access_key + } + + const options = { + url, + qs + } + + Requester.requestRetry(options) + .then(response => { + response.body.result = Requester.validateResult(response.body, ['result']) + callback(response.statusCode, Requester.success(jobRunID, response)) + }) + .catch(error => { + callback(500, Requester.errored(jobRunID, error)) + }) +} + +module.exports.createRequest = createRequest diff --git a/fixer/package.json b/fixer/package.json new file mode 100644 index 0000000000..08d1da4f06 --- /dev/null +++ b/fixer/package.json @@ -0,0 +1,8 @@ +{ + "name": "fixer", + "version": "0.1.0", + "license": "MIT", + "scripts": { + "test": "yarn _mocha --timeout 0" + } +} diff --git a/fixer/test/adapter_test.js b/fixer/test/adapter_test.js new file mode 100644 index 0000000000..3b5de4ced3 --- /dev/null +++ b/fixer/test/adapter_test.js @@ -0,0 +1,112 @@ +const assert = require('chai').assert +const createRequest = require('../adapter').createRequest + +describe('createRequest', () => { + const jobID = '1' + + context('successful calls', () => { + const requests = [ + { + name: 'id not supplied', + testData: { + data: { + base: 'GBP', + quote: 'USD' + } + } + }, + { + name: 'base/quote', + testData: { + id: jobID, + data: { + base: 'GBP', + quote: 'USD' + } + } + }, + { + name: 'from/to', + testData: { + id: jobID, + data: { + from: 'GBP', + to: 'USD' + } + } + } + ] + + requests.forEach(req => { + it(`${req.name}`, (done) => { + createRequest(req.testData, (statusCode, data) => { + assert.equal(statusCode, 200) + assert.equal(data.jobRunID, jobID) + assert.isNotEmpty(data.data) + assert.isAbove(Number(data.result), 0) + assert.isAbove(Number(data.data.result), 0) + done() + }) + }) + }) + }) + + context('error calls', () => { + const requests = [ + { + name: 'empty body', + testData: {} + }, + { + name: 'empty data', + testData: { data: {} } + }, + { + name: 'base not supplied', + testData: { + id: jobID, + data: { quote: 'USD' } + } + }, + { + name: 'quote not supplied', + testData: { + id: jobID, + data: { base: 'GBP' } + } + }, + { + name: 'unknown base', + testData: { + id: jobID, + data: { + base: 'not_real', + quote: 'USD' + } + } + }, + { + name: 'unknown quote', + testData: { + id: jobID, + data: { + base: 'GBP', + quote: 'not_real' + } + } + } + ] + + requests.forEach(req => { + it(`${req.name}`, (done) => { + createRequest(req.testData, (statusCode, data) => { + assert.equal(statusCode, 500) + assert.equal(data.jobRunID, jobID) + assert.equal(data.status, 'errored') + assert.isNotEmpty(data.error) + done() + }) + }) + }) + }) +}) diff --git a/kaiko/test/index_test.js b/kaiko/test/adapter_test.js similarity index 100% rename from kaiko/test/index_test.js rename to kaiko/test/adapter_test.js diff --git a/openexchangerates/README.md b/openexchangerates/README.md new file mode 100644 index 0000000000..eab8036587 --- /dev/null +++ b/openexchangerates/README.md @@ -0,0 +1,197 @@ +# Chainlink Open Exchange Rates External Adapter + +## Input Params +- `base` or `from`: The currency symbol to convert from +- `quote` or `to`: The currency symbol to convert to +- `endpoint`: The endpoint to use (default: latest.json) + + +## Output + +```json +{ + "jobRunID":"278c97ffadb54a5bbb93cfec5f7b5503", + "data":{ + "disclaimer":"Usage subject to terms: https://openexchangerates.org/terms", + "license":"https://openexchangerates.org/license", + "timestamp":1585137600, + "base":"XAU", + "rates":{ + "AED":5922.474149, + "AFN":123112.658665, + "ALL":184991.213445, + "AMD":802709.63406, + "ANG":2901.530234, + "AOA":836259.140313, + "ARS":102667.743512, + "AUD":2684.710984, + "AWG":2902.459821, + "AZN":2745.243241, + "BAM":2918.095976, + "BBD":3224.955349, + "BDT":137506.657469, + "BGN":2915.13792, + "BHD":609.982414, + "BIF":3078234.254974, + "BMD":1612.477674, + "BND":2347.128394, + "BOB":11152.243041, + "BRL":8224.119148, + "BSD":1612.477674, + "BTC":0.24688627452, + "BTN":123107.944016, + "BWP":19285.874737, + "BYN":4213.404703, + "BZD":3267.298075, + "CAD":2321.452762, + "CDF":2770225.961535, + "CHF":1580.832838, + "CLF":50.343165, + "CLP":1365929.838, + "CNH":11477.390301, + "CNY":11453.267674, + "COP":6768118.351572, + "CRC":934340.922105, + "CUC":1612.477674, + "CUP":41521.300116, + "CVE":165278.961628, + "CZK":40918.174897, + "DJF":287101.64993, + "DKK":11137.552685, + "DOP":87235.042186, + "DZD":198370.816262, + "EGP":25396.845868, + "ERN":24186.400802, + "ETB":52760.292787, + "EUR":1491.584761, + "FJD":3751.832252, + "FKP":1356.990415, + "GBP":1356.990415, + "GEL":5264.739607, + "GGP":1356.990415, + "GHS":9166.541036, + "GIP":1356.990415, + "GMD":82042.864074, + "GNF":15466572.446964, + "GTQ":12508.171537, + "GYD":338401.830362, + "HKD":12502.4584, + "HNL":40071.351669, + "HRK":11350.626407, + "HTG":153222.209937, + "HUF":528197.225126, + "IDR":26025389.665116, + "ILS":5870.547122, + "IMP":1356.990415, + "INR":122624.903147, + "IQD":1935122.585658, + "IRR":67892808.07551, + "ISK":226569.216085, + "JEP":1356.990415, + "JMD":220066.403249, + "JOD":1143.24666, + "JPY":179406.68477233, + "KES":171406.890411, + "KGS":127194.818922, + "KHR":6570287.311425, + "KMF":736902.378599, + "KPW":1451229.906977, + "KRW":1987910.851353, + "KWD":508.327639, + "KYD":1350.675225, + "KZT":726378.20662, + "LAK":14483061.146746, + "LBP":2438658.70658, + "LKR":303199.150179, + "LRD":318867.497986, + "LSL":28513.325365, + "LYD":2326.004598, + "MAD":15877.701879, + "MDL":29019.811659, + "MGA":6067431.268965, + "MKD":92048.861949, + "MMK":2274195.899335, + "MNT":4452579.487301, + "MOP":12947.386963, + "MRO":575654.529767, + "MRU":60161.542033, + "MUR":63541.803169, + "MVR":24832.156932, + "MWK":1192336.768263, + "MXN":39596.146896, + "MYR":7076.518708, + "MZN":107278.139679, + "NAD":28444.106177, + "NGN":616782.740714, + "NIO":55154.501403, + "NOK":17493.780596, + "NPR":196976.740973, + "NZD":2762.440124, + "OMR":620.410227, + "PAB":1612.477674, + "PEN":5727.686985, + "PGK":5624.491346, + "PHP":82406.47779, + "PKR":256839.884691, + "PLN":6816.099795, + "PYG":10733809.779324, + "QAR":5871.031259, + "RON":7215.999205, + "RSD":175123.13783, + "RUB":126877.322068, + "RWF":1541389.985491, + "SAR":6056.149293, + "SBD":13331.95314, + "SCR":22107.565103, + "SDG":89170.015395, + "SEK":16334.98386, + "SGD":2330.836524, + "SHP":1356.990415, + "SLL":12252946.693661, + "SOS":937737.563228, + "SRD":12025.858496, + "SSP":210041.34187, + "STD":35691821.348843, + "STN":36925.738744, + "SVC":14183.071558, + "SYP":829699.697232, + "SZL":28514.220513, + "THB":52873.142944, + "TJS":16533.882279, + "TMT":5643.671498, + "TND":4682.23197, + "TOP":3862.943661, + "TRY":10389.766033, + "TTD":10965.689844, + "TWD":48794.381475, + "TZS":3736411.049722, + "UAH":44970.362629, + "UGX":6334718.44215, + "USD":1612.477674, + "UYU":72252.687943, + "UZS":15438008.259435, + "VEF":400680775.481912, + "VES":113510838.765289, + "VND":38226386.958602, + "VUV":195538.132337, + "WST":4443.651463, + "XAF":978415.464819, + "XAG":113.76307129, + "XAU":1, + "XCD":4357.801539, + "XDR":1196.129489, + "XOF":978415.464819, + "XPD":0.80909992, + "XPF":177993.408187, + "XPT":2.25160695, + "YER":403603.247384, + "ZAR":28083.389119, + "ZMW":28348.013, + "ZWL":519217.812775 + }, + "result":1612.477674 + }, + "result":1612.477674, + "statusCode":200 +} +``` diff --git a/openexchangerates/adapter.js b/openexchangerates/adapter.js new file mode 100644 index 0000000000..c27abc5244 --- /dev/null +++ b/openexchangerates/adapter.js @@ -0,0 +1,37 @@ +const { Requester, Validator } = require('external-adapter') + +const customParams = { + base: ['base', 'from'], + quote: ['quote', 'to'], + endpoint: false +} + +const createRequest = (input, callback) => { + const validator = new Validator(input, customParams, callback) + const jobRunID = validator.validated.id + const endpoint = validator.validated.data.endpoint || 'latest.json' + const url = `https://openexchangerates.org/api/${endpoint}` + const base = validator.validated.data.base.toUpperCase() + const to = validator.validated.data.quote.toUpperCase() + + const qs = { + base, + app_id: process.env.API_KEY + } + + const options = { + url, + qs + } + + Requester.requestRetry(options) + .then(response => { + response.body.result = Requester.validateResult(response.body, ['rates', to]) + callback(response.statusCode, Requester.success(jobRunID, response)) + }) + .catch(error => { + callback(500, Requester.errored(jobRunID, error)) + }) +} + +module.exports.createRequest = createRequest diff --git a/openexchangerates/package.json b/openexchangerates/package.json new file mode 100644 index 0000000000..8398a457cf --- /dev/null +++ b/openexchangerates/package.json @@ -0,0 +1,8 @@ +{ + "name": "openexchangerates", + "version": "0.1.0", + "license": "MIT", + "scripts": { + "test": "yarn _mocha --timeout 0" + } +} diff --git a/openexchangerates/test/adapter_test.js b/openexchangerates/test/adapter_test.js new file mode 100644 index 0000000000..2e6ee7ef74 --- /dev/null +++ b/openexchangerates/test/adapter_test.js @@ -0,0 +1,50 @@ +const assert = require('chai').assert +const createRequest = require('../adapter').createRequest + +describe('createRequest', () => { + const jobID = '1' + + context('successful calls', () => { + const requests = [ + { name: 'id not supplied', testData: { data: { base: 'GBP', quote: 'USD' } } }, + { name: 'base/quote', testData: { id: jobID, data: { base: 'GBP', quote: 'USD' } } }, + { name: 'from/to', testData: { id: jobID, data: { from: 'GBP', to: 'USD' } } } + ] + + requests.forEach(req => { + it(`${req.name}`, (done) => { + createRequest(req.testData, (statusCode, data) => { + assert.equal(statusCode, 200) + assert.equal(data.jobRunID, jobID) + assert.isNotEmpty(data.data) + assert.isAbove(Number(data.result), 0) + assert.isAbove(Number(data.data.result), 0) + done() + }) + }) + }) + }) + + context('error calls', () => { + const requests = [ + { name: 'empty body', testData: {} }, + { name: 'empty data', testData: { data: {} } }, + { name: 'base not supplied', testData: { id: jobID, data: { quote: 'USD' } } }, + { name: 'quote not supplied', testData: { id: jobID, data: { base: 'GBP' } } }, + { name: 'unknown base', testData: { id: jobID, data: { base: 'not_real', quote: 'USD' } } }, + { name: 'unknown quote', testData: { id: jobID, data: { base: 'GBP', quote: 'not_real' } } } + ] + + requests.forEach(req => { + it(`${req.name}`, (done) => { + createRequest(req.testData, (statusCode, data) => { + assert.equal(statusCode, 500) + assert.equal(data.jobRunID, jobID) + assert.equal(data.status, 'errored') + assert.isNotEmpty(data.error) + done() + }) + }) + }) + }) +}) diff --git a/package.json b/package.json index d25b59256d..39fcd25e1c 100644 --- a/package.json +++ b/package.json @@ -4,6 +4,8 @@ "license": "MIT", "private": true, "workspaces": [ + "1forge", + "alphavantage", "amberdata", "bravenewcoin", "coinapi", @@ -12,7 +14,12 @@ "coinpaprika", "cryptoapis", "cryptocompare", - "kaiko" + "currencylayer", + "finnhub", + "fixer", + "kaiko", + "openexchangerates", + "polygon" ], "scripts": { "lint": "yarn eslint ." diff --git a/polygon/README.md b/polygon/README.md new file mode 100644 index 0000000000..33520a1903 --- /dev/null +++ b/polygon/README.md @@ -0,0 +1,33 @@ +# Chainlink Polygon External Adapter + +This adapter is for [Polygon.io](https://polygon.io/) and supports the conversion endpoint. + +## Input params + +- `base` or `from`: The asset to query +- `quote` or `to`: The currency to conver to +- `endpoint`: The endpoint to query (default: conversion) + +## Output + +```json +{ + "jobRunID": "1", + "data": { + "status": "success", + "last": { + "bid": 0.8131, + "ask": 0.8133, + "exchange": 48, + "timestamp": 1587501544000 + }, + "from": "GBP", + "to": "USD", + "initialAmount": 1, + "converted": 1.2299, + "result": 1.2299 + }, + "result": 1.2299, + "statusCode": 200 +} +``` diff --git a/polygon/adapter.js b/polygon/adapter.js new file mode 100644 index 0000000000..07eb507664 --- /dev/null +++ b/polygon/adapter.js @@ -0,0 +1,47 @@ +const { Requester, Validator } = require('external-adapter') + +const customError = (body) => { + return body.status === 'ERROR' +} + +const customParams = { + base: ['base', 'from'], + quote: ['quote', 'to'], + endpoint: false, + amount: false, + precision: false +} + +const createRequest = (input, callback) => { + const validator = new Validator(input, customParams, callback) + const jobRunID = validator.validated.id + const endpoint = validator.validated.data.endpoint || 'conversion' + const from = validator.validated.data.base.toUpperCase() + const to = validator.validated.data.quote.toUpperCase() + const url = `https://api.polygon.io/v1/${endpoint}/${from}/${to}` + const amount = validator.validated.data.amount || 1 + const precision = validator.validated.data.precision || 4 + const apikey = process.env.API_KEY + + const qs = { + amount, + precision, + apikey + } + + const options = { + url, + qs + } + + Requester.requestRetry(options, customError) + .then(response => { + response.body.result = Requester.validateResult(response.body, ['converted']) + callback(response.statusCode, Requester.success(jobRunID, response)) + }) + .catch(error => { + callback(500, Requester.errored(jobRunID, error)) + }) +} + +module.exports.createRequest = createRequest diff --git a/polygon/package.json b/polygon/package.json new file mode 100644 index 0000000000..c7fd3e7428 --- /dev/null +++ b/polygon/package.json @@ -0,0 +1,8 @@ +{ + "name": "polygon", + "version": "0.1.0", + "license": "MIT", + "scripts": { + "test": "yarn _mocha --timeout 0" + } +} diff --git a/polygon/test/adapter_test.js b/polygon/test/adapter_test.js new file mode 100644 index 0000000000..2e6ee7ef74 --- /dev/null +++ b/polygon/test/adapter_test.js @@ -0,0 +1,50 @@ +const assert = require('chai').assert +const createRequest = require('../adapter').createRequest + +describe('createRequest', () => { + const jobID = '1' + + context('successful calls', () => { + const requests = [ + { name: 'id not supplied', testData: { data: { base: 'GBP', quote: 'USD' } } }, + { name: 'base/quote', testData: { id: jobID, data: { base: 'GBP', quote: 'USD' } } }, + { name: 'from/to', testData: { id: jobID, data: { from: 'GBP', to: 'USD' } } } + ] + + requests.forEach(req => { + it(`${req.name}`, (done) => { + createRequest(req.testData, (statusCode, data) => { + assert.equal(statusCode, 200) + assert.equal(data.jobRunID, jobID) + assert.isNotEmpty(data.data) + assert.isAbove(Number(data.result), 0) + assert.isAbove(Number(data.data.result), 0) + done() + }) + }) + }) + }) + + context('error calls', () => { + const requests = [ + { name: 'empty body', testData: {} }, + { name: 'empty data', testData: { data: {} } }, + { name: 'base not supplied', testData: { id: jobID, data: { quote: 'USD' } } }, + { name: 'quote not supplied', testData: { id: jobID, data: { base: 'GBP' } } }, + { name: 'unknown base', testData: { id: jobID, data: { base: 'not_real', quote: 'USD' } } }, + { name: 'unknown quote', testData: { id: jobID, data: { base: 'GBP', quote: 'not_real' } } } + ] + + requests.forEach(req => { + it(`${req.name}`, (done) => { + createRequest(req.testData, (statusCode, data) => { + assert.equal(statusCode, 500) + assert.equal(data.jobRunID, jobID) + assert.equal(data.status, 'errored') + assert.isNotEmpty(data.error) + done() + }) + }) + }) + }) +})