diff --git a/eodhistoricaldata/README.md b/eodhistoricaldata/README.md new file mode 100644 index 0000000000..8a43044a82 --- /dev/null +++ b/eodhistoricaldata/README.md @@ -0,0 +1,30 @@ +# Chainlink EOD Historical Data External Adapter + +## Input Params +- `asset`, `base`, `from` or `symbol`: The symbol to get the price from (required) +- `endpoint`: The endpoint to use (optional) + + +## Output + +```json +{ + "jobRunID":"278c97ffadb54a5bbb93cfec5f7b5503", + "data":{ + "code":"CL.COMM", + "timestamp":1585167540, + "gmtoffset":0, + "open":24.37, + "high":25.24, + "low":22.91, + "close":24.3, + "volume":590048, + "previousClose":24.01, + "change":0.29, + "change_p":1.208, + "result":24.3 + }, + "result":24.3, + "statusCode":200 +} +``` diff --git a/eodhistoricaldata/adapter.js b/eodhistoricaldata/adapter.js new file mode 100644 index 0000000000..e9e280f6da --- /dev/null +++ b/eodhistoricaldata/adapter.js @@ -0,0 +1,45 @@ +const { Requester, Validator } = require('external-adapter') + +const commonKeys = { + N225: 'N225.INDX', + FTSE: 'FTSE.INDX', + BZ: 'BZ.COMM' +} + +const customParams = { + base: ['base', 'asset', 'from', 'symbol'], + endpoint: false +} + +const createRequest = (input, callback) => { + const validator = new Validator(input, customParams, callback) + const jobRunID = validator.validated.id + const endpoint = validator.validated.data.endpoint || 'real-time' + let symbol = validator.validated.data.base.toUpperCase() + if (commonKeys[symbol]) { + symbol = commonKeys[symbol] + } + const url = `https://eodhistoricaldata.com/api/${endpoint}/${symbol}` + const api_token = process.env.API_KEY // eslint-disable-line camelcase + + const qs = { + api_token, + fmt: 'json' + } + + const options = { + url, + qs + } + + Requester.requestRetry(options) + .then(response => { + response.body.result = Requester.validateResult(response.body, ['close']) + callback(response.statusCode, Requester.success(jobRunID, response)) + }) + .catch(error => { + callback(500, Requester.errored(jobRunID, error)) + }) +} + +module.exports.createRequest = createRequest diff --git a/eodhistoricaldata/package.json b/eodhistoricaldata/package.json new file mode 100644 index 0000000000..ec8d1b80f0 --- /dev/null +++ b/eodhistoricaldata/package.json @@ -0,0 +1,8 @@ +{ + "name": "eodhistoricaldata", + "version": "0.1.0", + "license": "MIT", + "scripts": { + "test": "yarn _mocha --timeout 0" + } +} diff --git a/eodhistoricaldata/test/adapter_test.js b/eodhistoricaldata/test/adapter_test.js new file mode 100644 index 0000000000..b31ba798ce --- /dev/null +++ b/eodhistoricaldata/test/adapter_test.js @@ -0,0 +1,48 @@ +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: 'N225.INDX' } } }, + { name: 'base', testData: { id: jobID, data: { base: 'N225' } } }, + { name: 'from', testData: { id: jobID, data: { from: 'N225' } } }, + { name: 'asset', testData: { id: jobID, data: { asset: 'N225' } } } + ] + + 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/fcsapi/README.md b/fcsapi/README.md new file mode 100644 index 0000000000..59043f38bc --- /dev/null +++ b/fcsapi/README.md @@ -0,0 +1,38 @@ +# Chainlink External Adapter for FCS API + +## Input Params + +- `asset`, `base`, `from`: The target currency to query (required) +- `endpoint`: The endpoint to use (optional) + +## Output Format + +```json +{ + "jobRunID": "1", + "data": { + "status": true, + "code": 200, + "msg": "Successfully", + "response": [ + { + "price": "5770.60", + "high": "5770.60", + "low": "5725.00", + "chg": "-56.01", + "chg_percent": "-0.96%", + "dateTime": "2020-04-24 09:10:08", + "id": "529", + "name": "FTSE 100" + } + ], + "info": { + "server_time": "2020-04-24 09:10:58 UTC", + "credit_count": 1 + }, + "result": 5770.6 + }, + "result": 5770.6, + "statusCode": 200 +} +``` diff --git a/fcsapi/adapter.js b/fcsapi/adapter.js new file mode 100644 index 0000000000..28ec04cfd0 --- /dev/null +++ b/fcsapi/adapter.js @@ -0,0 +1,57 @@ +const { Requester, Validator } = require('external-adapter') + +const customError = (body) => { + if (body.msg !== 'Successfully') return true + return false +} + +const commonKeys = { + AUD: { id: '13', endpoint: 'forex/latest' }, + CHF: { id: '466', endpoint: 'forex/latest' }, + EUR: { id: '1', endpoint: 'forex/latest' }, + GBP: { id: '39', endpoint: 'forex/latest' }, + JPY: { id: '1075', endpoint: 'forex/latest' }, + XAU: { id: '1984', endpoint: 'forex/latest' }, + XAG: { id: '1975', endpoint: 'forex/latest' }, + N225: { id: '268', endpoint: 'stock/indices_latest' }, + FTSE: { id: '529', endpoint: 'stock/indices_latest' } +} + +const customParams = { + base: ['base', 'asset', 'from'], + endpoint: false +} + +const createRequest = (input, callback) => { + const validator = new Validator(input, customParams, callback) + const jobRunID = validator.validated.id + let symbol = validator.validated.data.base.toUpperCase() + let endpoint = validator.validated.data.endpoint + if (commonKeys[symbol]) { + endpoint = commonKeys[symbol].endpoint + symbol = commonKeys[symbol].id + } + const url = `https://fcsapi.com/api-v2/${endpoint}` + const access_key = process.env.API_KEY // eslint-disable-line camelcase + + const qs = { + access_key, + id: symbol + } + + const options = { + url, + qs + } + + Requester.requestRetry(options, customError) + .then(response => { + response.body.result = Requester.validateResult(response.body, ['response', 0, 'price']) + callback(response.statusCode, Requester.success(jobRunID, response)) + }) + .catch(error => { + callback(500, Requester.errored(jobRunID, error)) + }) +} + +module.exports.createRequest = createRequest diff --git a/fcsapi/package.json b/fcsapi/package.json new file mode 100644 index 0000000000..f08130d3f5 --- /dev/null +++ b/fcsapi/package.json @@ -0,0 +1,8 @@ +{ + "name": "fcsapi", + "version": "0.1.0", + "license": "MIT", + "scripts": { + "test": "yarn _mocha --timeout 0" + } +} diff --git a/fcsapi/test/adapter_test.js b/fcsapi/test/adapter_test.js new file mode 100644 index 0000000000..3eaf0dd2a0 --- /dev/null +++ b/fcsapi/test/adapter_test.js @@ -0,0 +1,56 @@ +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: 'FTSE' } } }, + { name: 'base', testData: { id: jobID, data: { base: 'N225' } } }, + { name: 'from', testData: { id: jobID, data: { from: 'N225' } } }, + { name: 'asset', testData: { id: jobID, data: { asset: 'N225' } } }, + { name: 'AUD', testData: { id: jobID, data: { asset: 'AUD' } } }, + { name: 'CHF', testData: { id: jobID, data: { asset: 'CHF' } } }, + { name: 'EUR', testData: { id: jobID, data: { asset: 'EUR' } } }, + { name: 'GBP', testData: { id: jobID, data: { asset: 'GBP' } } }, + { name: 'JPY', testData: { id: jobID, data: { asset: 'JPY' } } }, + { name: 'XAU', testData: { id: jobID, data: { asset: 'XAU' } } }, + { name: 'XAG', testData: { id: jobID, data: { asset: 'XAG' } } } + ] + + requests.forEach(req => { + it(`${req.name}`, (done) => { + createRequest(req.testData, (statusCode, data) => { + console.log(JSON.stringify(data, null, 1)) + 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/fmpcloud/README.md b/fmpcloud/README.md new file mode 100644 index 0000000000..47ff610ed6 --- /dev/null +++ b/fmpcloud/README.md @@ -0,0 +1,41 @@ +# Chainlink External Adapter for Fmp Cloud + +## Input Params + +- `asset`, `base`, `from`: The target currency to query (required) +- `endpoint`: The endpoint to use (optional) + +## Output Format + +```json +{ + "jobRunID": "278c97ffadb54a5bbb93cfec5f7b5503", + "data": [ + { + "symbol": "AAPL", + "name": "Apple Inc.", + "price": 245.52, + "changesPercentage": -0.55, + "change": -1.36, + "dayLow": 244.3, + "dayHigh": 258, + "yearHigh": 327.85, + "yearLow": 170.27, + "marketCap": 1074267815936, + "priceAvg50": 287.00342, + "priceAvg200": 269.64044, + "volume": 68684527, + "avgVolume": 47970853, + "exchange": "NASDAQ", + "open": 250.75, + "previousClose": 246.88, + "eps": 12.595, + "pe": 19.49345, + "earningsAnnouncement": "2020-01-28T21:30:00.000+0000", + "sharesOutstanding": 4375479808, + "timestamp": 1585227237 + } + ], + "statusCode": 200 +} +``` diff --git a/fmpcloud/adapter.js b/fmpcloud/adapter.js new file mode 100644 index 0000000000..329e4b9d07 --- /dev/null +++ b/fmpcloud/adapter.js @@ -0,0 +1,52 @@ +const { Requester, Validator } = require('external-adapter') + +const commonKeys = { + N225: '^N225', + FTSE: '^FTSE', + AUD: 'AUDUSD', + CHF: 'CHFUSD', + EUR: 'EURUSD', + GBP: 'GBPUSD', + JPY: 'JPYUSD' +} + +const customError = (body) => { + return body.length === 0 +} + +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' + let symbol = validator.validated.data.base.toUpperCase() + if (commonKeys[symbol]) { + symbol = commonKeys[symbol] + } + const url = `https://fmpcloud.io/api/v3/${endpoint}/${symbol}` + const apikey = process.env.API_KEY + + const qs = { + apikey + } + + const options = { + url, + qs + } + + Requester.requestRetry(options, customError) + .then(response => { + response.body.result = Requester.validateResult(response.body, [0, 'price']) + callback(response.statusCode, Requester.success(jobRunID, response)) + }) + .catch(error => { + callback(500, Requester.errored(jobRunID, error)) + }) +} + +module.exports.createRequest = createRequest diff --git a/fmpcloud/package.json b/fmpcloud/package.json new file mode 100644 index 0000000000..fa6f6da018 --- /dev/null +++ b/fmpcloud/package.json @@ -0,0 +1,8 @@ +{ + "name": "fmpcloud", + "version": "0.1.0", + "license": "MIT", + "scripts": { + "test": "yarn _mocha --timeout 0" + } +} diff --git a/fmpcloud/test/adapter_test.js b/fmpcloud/test/adapter_test.js new file mode 100644 index 0000000000..e70d04cac2 --- /dev/null +++ b/fmpcloud/test/adapter_test.js @@ -0,0 +1,48 @@ +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: '^FTSE' } } }, + { name: 'base', testData: { id: jobID, data: { base: 'N225' } } }, + { name: 'from', testData: { id: jobID, data: { from: 'N225' } } }, + { name: 'asset', testData: { id: jobID, data: { asset: 'N225' } } } + ] + + 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/oilpriceapi/README.md b/oilpriceapi/README.md new file mode 100644 index 0000000000..d2e2a11e85 --- /dev/null +++ b/oilpriceapi/README.md @@ -0,0 +1,28 @@ +# Chainlink OilpriceAPI External Adapter + +## Input Params +- `type`, `base`, `asset`, `from`: The type of oil to get the price from (required) +- `endpoint`: The endpoint to use (optional) + + +## Output + +```json +{ + "jobRunID":"278c97ffadb54a5bbb93cfec5f7b5503", + "data":{ + "status":"success", + "data":{ + "price":26.71, + "formatted":"$26.71", + "currency":"USD", + "code":"BRENT_CRUDE_USD", + "created_at":"2020-03-25T14:10:00.424Z", + "type":"spot_price" + }, + "result":26.71 + }, + "result":26.71, + "statusCode":200 +} +``` diff --git a/oilpriceapi/adapter.js b/oilpriceapi/adapter.js new file mode 100644 index 0000000000..444d00e226 --- /dev/null +++ b/oilpriceapi/adapter.js @@ -0,0 +1,52 @@ +const { Requester, Validator } = require('external-adapter') + +const commonKeys = { + BZ: 'BRENT_CRUDE_USD' +} + +const customParams = { + base: ['type', 'base', 'asset', 'from'], + endpoint: false +} + +const customError = (body) => { + return body.data === null +} + +const createRequest = (input, callback) => { + const validator = new Validator(input, customParams, callback) + const jobRunID = validator.validated.id + const endpoint = validator.validated.data.endpoint || 'prices/latest' + const url = `https://api.oilpriceapi.com/v1/${endpoint}` + // eslint-disable-next-line camelcase + let by_code = validator.validated.data.base.toUpperCase() + if (commonKeys[by_code]) { + // eslint-disable-next-line camelcase + by_code = commonKeys[by_code] + } + + const qs = { + by_code + } + + const headers = { + Authorization: `Token ${process.env.API_KEY}` + } + + const options = { + url, + qs, + headers + } + + Requester.requestRetry(options, customError) + .then(response => { + response.body.result = Requester.validateResult(response.body, ['data', 'price']) + callback(response.statusCode, Requester.success(jobRunID, response)) + }) + .catch(error => { + callback(500, Requester.errored(jobRunID, error)) + }) +} + +module.exports.createRequest = createRequest diff --git a/oilpriceapi/package.json b/oilpriceapi/package.json new file mode 100644 index 0000000000..2f1f180a1e --- /dev/null +++ b/oilpriceapi/package.json @@ -0,0 +1,8 @@ +{ + "name": "oilpriceapi", + "version": "0.1.0", + "license": "MIT", + "scripts": { + "test": "yarn _mocha --timeout 0" + } +} diff --git a/oilpriceapi/test/adapter_test.js b/oilpriceapi/test/adapter_test.js new file mode 100644 index 0000000000..334b360ff2 --- /dev/null +++ b/oilpriceapi/test/adapter_test.js @@ -0,0 +1,49 @@ +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: 'BZ' } } }, + { name: 'base', testData: { id: jobID, data: { base: 'BZ' } } }, + { name: 'from', testData: { id: jobID, data: { from: 'BZ' } } }, + { name: 'asset', testData: { id: jobID, data: { asset: 'BZ' } } }, + { name: 'type', testData: { id: jobID, data: { type: 'BZ' } } } + ] + + 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/package.json b/package.json index 39fcd25e1c..f62ca736b0 100644 --- a/package.json +++ b/package.json @@ -15,9 +15,13 @@ "cryptoapis", "cryptocompare", "currencylayer", + "eodhistoricaldata", + "fcsapi", "finnhub", "fixer", + "fmpcloud", "kaiko", + "oilpriceapi", "openexchangerates", "polygon" ],