From 8c5177dddb1b99534940de0a083ac1dcedb557b1 Mon Sep 17 00:00:00 2001 From: Michael Xiao Date: Tue, 3 Dec 2024 09:34:12 -0500 Subject: [PATCH] BX Digital (#3597) * BX Digital * Check valid number --- .changeset/fifty-penguins-help.md | 5 ++ .pnp.cjs | 20 ++++++ packages/sources/bx-digital/CHANGELOG.md | 0 packages/sources/bx-digital/README.md | 3 + packages/sources/bx-digital/package.json | 40 +++++++++++ .../sources/bx-digital/src/config/index.ts | 15 ++++ .../sources/bx-digital/src/endpoint/index.ts | 1 + .../sources/bx-digital/src/endpoint/price.ts | 33 +++++++++ packages/sources/bx-digital/src/index.ts | 21 ++++++ .../sources/bx-digital/src/transport/price.ts | 69 +++++++++++++++++++ packages/sources/bx-digital/test-payload.json | 5 ++ .../__snapshots__/adapter.test.ts.snap | 42 +++++++++++ .../test/integration/adapter.test.ts | 65 +++++++++++++++++ .../bx-digital/test/integration/fixtures.ts | 26 +++++++ packages/sources/bx-digital/tsconfig.json | 9 +++ .../sources/bx-digital/tsconfig.test.json | 7 ++ packages/tsconfig.json | 3 + packages/tsconfig.test.json | 3 + yarn.lock | 13 ++++ 19 files changed, 380 insertions(+) create mode 100644 .changeset/fifty-penguins-help.md create mode 100644 packages/sources/bx-digital/CHANGELOG.md create mode 100644 packages/sources/bx-digital/README.md create mode 100644 packages/sources/bx-digital/package.json create mode 100644 packages/sources/bx-digital/src/config/index.ts create mode 100644 packages/sources/bx-digital/src/endpoint/index.ts create mode 100644 packages/sources/bx-digital/src/endpoint/price.ts create mode 100644 packages/sources/bx-digital/src/index.ts create mode 100644 packages/sources/bx-digital/src/transport/price.ts create mode 100644 packages/sources/bx-digital/test-payload.json create mode 100644 packages/sources/bx-digital/test/integration/__snapshots__/adapter.test.ts.snap create mode 100644 packages/sources/bx-digital/test/integration/adapter.test.ts create mode 100644 packages/sources/bx-digital/test/integration/fixtures.ts create mode 100644 packages/sources/bx-digital/tsconfig.json create mode 100755 packages/sources/bx-digital/tsconfig.test.json diff --git a/.changeset/fifty-penguins-help.md b/.changeset/fifty-penguins-help.md new file mode 100644 index 0000000000..b37720604a --- /dev/null +++ b/.changeset/fifty-penguins-help.md @@ -0,0 +1,5 @@ +--- +'@chainlink/bx-digital-adapter': major +--- + +Init diff --git a/.pnp.cjs b/.pnp.cjs index 71188604c5..dd104f66a1 100755 --- a/.pnp.cjs +++ b/.pnp.cjs @@ -310,6 +310,10 @@ const RAW_RUNTIME_STATE = "name": "@chainlink/btc.com-adapter",\ "reference": "workspace:packages/sources/btc.com"\ },\ + {\ + "name": "@chainlink/bx-digital-adapter",\ + "reference": "workspace:packages/sources/bx-digital"\ + },\ {\ "name": "@chainlink/cache.gold-adapter",\ "reference": "workspace:packages/sources/cache.gold"\ @@ -977,6 +981,7 @@ const RAW_RUNTIME_STATE = ["@chainlink/bravenewcoin-adapter", ["workspace:packages/sources/bravenewcoin"]],\ ["@chainlink/bsol-price-adapter", ["workspace:packages/composites/bsol-price"]],\ ["@chainlink/btc.com-adapter", ["workspace:packages/sources/btc.com"]],\ + ["@chainlink/bx-digital-adapter", ["workspace:packages/sources/bx-digital"]],\ ["@chainlink/cache.gold-adapter", ["workspace:packages/sources/cache.gold"]],\ ["@chainlink/ccip-read-adapter", ["workspace:packages/sources/ccip-read"]],\ ["@chainlink/celsius-address-list-adapter", ["workspace:packages/sources/celsius-address-list"]],\ @@ -5787,6 +5792,21 @@ const RAW_RUNTIME_STATE = "linkType": "SOFT"\ }]\ ]],\ + ["@chainlink/bx-digital-adapter", [\ + ["workspace:packages/sources/bx-digital", {\ + "packageLocation": "./packages/sources/bx-digital/",\ + "packageDependencies": [\ + ["@chainlink/bx-digital-adapter", "workspace:packages/sources/bx-digital"],\ + ["@chainlink/external-adapter-framework", "npm:1.7.3"],\ + ["@types/jest", "npm:27.5.2"],\ + ["@types/node", "npm:16.18.119"],\ + ["nock", "npm:13.5.5"],\ + ["tslib", "npm:2.4.1"],\ + ["typescript", "patch:typescript@npm%3A5.6.3#optional!builtin::version=5.6.3&hash=8c6c40"]\ + ],\ + "linkType": "SOFT"\ + }]\ + ]],\ ["@chainlink/cache.gold-adapter", [\ ["workspace:packages/sources/cache.gold", {\ "packageLocation": "./packages/sources/cache.gold/",\ diff --git a/packages/sources/bx-digital/CHANGELOG.md b/packages/sources/bx-digital/CHANGELOG.md new file mode 100644 index 0000000000..e69de29bb2 diff --git a/packages/sources/bx-digital/README.md b/packages/sources/bx-digital/README.md new file mode 100644 index 0000000000..878efd1138 --- /dev/null +++ b/packages/sources/bx-digital/README.md @@ -0,0 +1,3 @@ +# Chainlink External Adapter for bx-digital + +This README will be generated automatically when code is merged to `main`. If you would like to generate a preview of the README, please run `yarn generate:readme bx-digital`. diff --git a/packages/sources/bx-digital/package.json b/packages/sources/bx-digital/package.json new file mode 100644 index 0000000000..63ad39cd39 --- /dev/null +++ b/packages/sources/bx-digital/package.json @@ -0,0 +1,40 @@ +{ + "name": "@chainlink/bx-digital-adapter", + "version": "0.0.0", + "description": "Chainlink bx-digital adapter.", + "keywords": [ + "Chainlink", + "LINK", + "blockchain", + "oracle", + "bx-digital" + ], + "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": { + "clean": "rm -rf dist && rm -f tsconfig.tsbuildinfo", + "prepack": "yarn build", + "build": "tsc -b", + "server": "node -e 'require(\"./index.js\").server()'", + "server:dist": "node -e 'require(\"./dist/index.js\").server()'", + "start": "yarn server:dist" + }, + "devDependencies": { + "@types/jest": "27.5.2", + "@types/node": "16.18.119", + "nock": "13.5.5", + "typescript": "5.6.3" + }, + "dependencies": { + "@chainlink/external-adapter-framework": "1.7.3", + "tslib": "2.4.1" + } +} diff --git a/packages/sources/bx-digital/src/config/index.ts b/packages/sources/bx-digital/src/config/index.ts new file mode 100644 index 0000000000..ed1ebbed26 --- /dev/null +++ b/packages/sources/bx-digital/src/config/index.ts @@ -0,0 +1,15 @@ +import { AdapterConfig } from '@chainlink/external-adapter-framework/config' + +export const config = new AdapterConfig({ + API_KEY: { + description: 'An API key for Data Provider', + type: 'string', + required: true, + sensitive: true, + }, + API_ENDPOINT: { + description: 'An API endpoint for Data Provider', + type: 'string', + default: 'https://dev-cdf-stage-k8s.bxdigital.ch/securities', + }, +}) diff --git a/packages/sources/bx-digital/src/endpoint/index.ts b/packages/sources/bx-digital/src/endpoint/index.ts new file mode 100644 index 0000000000..11a44912b4 --- /dev/null +++ b/packages/sources/bx-digital/src/endpoint/index.ts @@ -0,0 +1 @@ +export { endpoint as price } from './price' diff --git a/packages/sources/bx-digital/src/endpoint/price.ts b/packages/sources/bx-digital/src/endpoint/price.ts new file mode 100644 index 0000000000..2e1861be36 --- /dev/null +++ b/packages/sources/bx-digital/src/endpoint/price.ts @@ -0,0 +1,33 @@ +import { AdapterEndpoint } from '@chainlink/external-adapter-framework/adapter' +import { InputParameters } from '@chainlink/external-adapter-framework/validation' +import { SingleNumberResultResponse } from '@chainlink/external-adapter-framework/util' +import { config } from '../config' +import { httpTransport } from '../transport/price' + +export const inputParameters = new InputParameters( + { + securityId: { + required: true, + type: 'string', + description: 'ID of the security to report price on', + }, + }, + [ + { + securityId: 'CH0012032048', + }, + ], +) + +export type BaseEndpointTypes = { + Parameters: typeof inputParameters.definition + Response: SingleNumberResultResponse + Settings: typeof config.settings +} + +export const endpoint = new AdapterEndpoint({ + name: 'price', + aliases: [], + transport: httpTransport, + inputParameters, +}) diff --git a/packages/sources/bx-digital/src/index.ts b/packages/sources/bx-digital/src/index.ts new file mode 100644 index 0000000000..ddd5f7c548 --- /dev/null +++ b/packages/sources/bx-digital/src/index.ts @@ -0,0 +1,21 @@ +import { expose, ServerInstance } from '@chainlink/external-adapter-framework' +import { Adapter } from '@chainlink/external-adapter-framework/adapter' +import { config } from './config' +import { price } from './endpoint' + +export const adapter = new Adapter({ + defaultEndpoint: price.name, + name: 'BX_DIGITAL', + config, + endpoints: [price], + rateLimiting: { + tiers: { + default: { + rateLimit1m: 6, + note: 'Reasonable limits', + }, + }, + }, +}) + +export const server = (): Promise => expose(adapter) diff --git a/packages/sources/bx-digital/src/transport/price.ts b/packages/sources/bx-digital/src/transport/price.ts new file mode 100644 index 0000000000..24e5723bdd --- /dev/null +++ b/packages/sources/bx-digital/src/transport/price.ts @@ -0,0 +1,69 @@ +import { HttpTransport } from '@chainlink/external-adapter-framework/transports' +import { BaseEndpointTypes } from '../endpoint/price' + +export interface ResponseSchema { + securityId: string + lastModifiedTime: number + closingPrice: string +} + +export type HttpTransportTypes = BaseEndpointTypes & { + Provider: { + RequestBody: never + ResponseBody: ResponseSchema[] + } +} +export const httpTransport = new HttpTransport({ + prepareRequests: (params, config) => { + return params.map((param) => { + return { + params: [param], + request: { + baseURL: config.API_ENDPOINT, + headers: { + 'API-key': config.API_KEY, + }, + }, + } + }) + }, + parseResponse: (params, response) => { + if (!response.data) { + return params.map((param) => { + return { + params: param, + response: { + errorMessage: `The data provider didn't return any value`, + statusCode: 502, + }, + } + }) + } + + return params.map((param) => { + const security = response.data.find((r) => r.securityId == param.securityId) + if (security && !isNaN(Number(security?.closingPrice))) { + return { + params: param, + response: { + result: Number(security.closingPrice), + data: { + result: Number(security.closingPrice), + }, + timestamps: { + providerIndicatedTimeUnixMs: security.lastModifiedTime * 1000, + }, + }, + } + } else { + return { + params: param, + response: { + errorMessage: `The data provider didn't return any value for ${param.securityId}`, + statusCode: 502, + }, + } + } + }) + }, +}) diff --git a/packages/sources/bx-digital/test-payload.json b/packages/sources/bx-digital/test-payload.json new file mode 100644 index 0000000000..a53ae99171 --- /dev/null +++ b/packages/sources/bx-digital/test-payload.json @@ -0,0 +1,5 @@ +{ + "requests": [{ + "securityId": "CH0012032048" + }] +} diff --git a/packages/sources/bx-digital/test/integration/__snapshots__/adapter.test.ts.snap b/packages/sources/bx-digital/test/integration/__snapshots__/adapter.test.ts.snap new file mode 100644 index 0000000000..b9e1695683 --- /dev/null +++ b/packages/sources/bx-digital/test/integration/__snapshots__/adapter.test.ts.snap @@ -0,0 +1,42 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`execute price endpoint should return failure for Nan 1`] = ` +{ + "errorMessage": "The data provider didn't return any value for 3", + "statusCode": 502, + "timestamps": { + "providerDataReceivedUnixMs": 978347471111, + "providerDataRequestedUnixMs": 978347471111, + }, +} +`; + +exports[`execute price endpoint should return success for security 1 1`] = ` +{ + "data": { + "result": 111.11, + }, + "result": 111.11, + "statusCode": 200, + "timestamps": { + "providerDataReceivedUnixMs": 978347471111, + "providerDataRequestedUnixMs": 978347471111, + "providerIndicatedTimeUnixMs": 1733155814000, + }, +} +`; + +exports[`execute price endpoint should return success for security 2 1`] = ` +{ + "data": { + "result": 222.22, + }, + "result": 222.22, + "statusCode": 200, + "timestamps": { + "providerDataReceivedUnixMs": 978347471111, + "providerDataRequestedUnixMs": 978347471111, + "providerIndicatedTimeUnixMs": 1733153530000, + }, +} +`; diff --git a/packages/sources/bx-digital/test/integration/adapter.test.ts b/packages/sources/bx-digital/test/integration/adapter.test.ts new file mode 100644 index 0000000000..b91abcd232 --- /dev/null +++ b/packages/sources/bx-digital/test/integration/adapter.test.ts @@ -0,0 +1,65 @@ +import { + TestAdapter, + setEnvVariables, +} from '@chainlink/external-adapter-framework/util/testing-utils' +import * as nock from 'nock' +import { mockResponseSuccess } from './fixtures' + +describe('execute', () => { + let spy: jest.SpyInstance + let testAdapter: TestAdapter + let oldEnv: NodeJS.ProcessEnv + + beforeAll(async () => { + oldEnv = JSON.parse(JSON.stringify(process.env)) + process.env.API_ENDPOINT = 'https://fake-api' + process.env.API_KEY = 'fake-api-key' + + const mockDate = new Date('2001-01-01T11:11:11.111Z') + spy = jest.spyOn(Date, 'now').mockReturnValue(mockDate.getTime()) + + const adapter = (await import('./../../src')).adapter + adapter.rateLimiting = undefined + testAdapter = await TestAdapter.startWithMockedCache(adapter, { + testAdapter: {} as TestAdapter, + }) + }) + + afterAll(async () => { + setEnvVariables(oldEnv) + await testAdapter.api.close() + nock.restore() + nock.cleanAll() + spy.mockRestore() + }) + + describe('price endpoint', () => { + it('should return success for security 1', async () => { + const data = { + securityId: '1', + } + mockResponseSuccess() + const response = await testAdapter.request(data) + expect(response.statusCode).toBe(200) + expect(response.json()).toMatchSnapshot() + }) + it('should return success for security 2', async () => { + const data = { + securityId: '2', + } + mockResponseSuccess() + const response = await testAdapter.request(data) + expect(response.statusCode).toBe(200) + expect(response.json()).toMatchSnapshot() + }) + it('should return failure for Nan', async () => { + const data = { + securityId: '3', + } + mockResponseSuccess() + const response = await testAdapter.request(data) + expect(response.statusCode).toBe(502) + expect(response.json()).toMatchSnapshot() + }) + }) +}) diff --git a/packages/sources/bx-digital/test/integration/fixtures.ts b/packages/sources/bx-digital/test/integration/fixtures.ts new file mode 100644 index 0000000000..f002f3afe1 --- /dev/null +++ b/packages/sources/bx-digital/test/integration/fixtures.ts @@ -0,0 +1,26 @@ +import nock from 'nock' + +export const mockResponseSuccess = (): nock.Scope => + nock('https://fake-api', { + encodedQueryParams: true, + }) + .get('/') + .reply( + 200, + () => [ + { securityId: '1', lastModifiedTime: 1733155814, closingPrice: '111.11' }, + { securityId: '2', lastModifiedTime: 1733153530, closingPrice: '222.22' }, + { securityId: '3', lastModifiedTime: 1733156220, closingPrice: 'lol' }, + ], + [ + 'Content-Type', + 'application/json', + 'Connection', + 'close', + 'Vary', + 'Accept-Encoding', + 'Vary', + 'Origin', + ], + ) + .persist() diff --git a/packages/sources/bx-digital/tsconfig.json b/packages/sources/bx-digital/tsconfig.json new file mode 100644 index 0000000000..f59363fd76 --- /dev/null +++ b/packages/sources/bx-digital/tsconfig.json @@ -0,0 +1,9 @@ +{ + "extends": "../../tsconfig.base.json", + "compilerOptions": { + "outDir": "dist", + "rootDir": "src" + }, + "include": ["src/**/*", "src/**/*.json"], + "exclude": ["dist", "**/*.spec.ts", "**/*.test.ts"] +} diff --git a/packages/sources/bx-digital/tsconfig.test.json b/packages/sources/bx-digital/tsconfig.test.json new file mode 100755 index 0000000000..e3de28cb5c --- /dev/null +++ b/packages/sources/bx-digital/tsconfig.test.json @@ -0,0 +1,7 @@ +{ + "extends": "../../tsconfig.base.json", + "include": ["src/**/*", "**/test", "src/**/*.json"], + "compilerOptions": { + "noEmit": true + } +} diff --git a/packages/tsconfig.json b/packages/tsconfig.json index eea495e9c0..fd3901e308 100644 --- a/packages/tsconfig.json +++ b/packages/tsconfig.json @@ -212,6 +212,9 @@ { "path": "./sources/btc.com" }, + { + "path": "./sources/bx-digital" + }, { "path": "./sources/cache.gold" }, diff --git a/packages/tsconfig.test.json b/packages/tsconfig.test.json index 174d90cfcc..5ee7833203 100644 --- a/packages/tsconfig.test.json +++ b/packages/tsconfig.test.json @@ -212,6 +212,9 @@ { "path": "./sources/btc.com/tsconfig.test.json" }, + { + "path": "./sources/bx-digital/tsconfig.test.json" + }, { "path": "./sources/cache.gold/tsconfig.test.json" }, diff --git a/yarn.lock b/yarn.lock index 9629b5d2b2..62d253ff49 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2812,6 +2812,19 @@ __metadata: languageName: unknown linkType: soft +"@chainlink/bx-digital-adapter@workspace:packages/sources/bx-digital": + version: 0.0.0-use.local + resolution: "@chainlink/bx-digital-adapter@workspace:packages/sources/bx-digital" + dependencies: + "@chainlink/external-adapter-framework": "npm:1.7.3" + "@types/jest": "npm:27.5.2" + "@types/node": "npm:16.18.119" + nock: "npm:13.5.5" + tslib: "npm:2.4.1" + typescript: "npm:5.6.3" + languageName: unknown + linkType: soft + "@chainlink/cache.gold-adapter@workspace:packages/sources/cache.gold": version: 0.0.0-use.local resolution: "@chainlink/cache.gold-adapter@workspace:packages/sources/cache.gold"