From 5bb403887db91846f2dde421f58f3f6dee355f86 Mon Sep 17 00:00:00 2001 From: mmcallister-cll <139181225+mmcallister-cll@users.noreply.github.com> Date: Mon, 5 Feb 2024 09:31:07 -0500 Subject: [PATCH] Feat/PDI-2301 moore-hk (#3165) * PDI-2301 moore-hk adapter for trueusd PoR * add changeset * cleanup * review fixes * update readme --- .changeset/eight-eggs-perform.md | 5 ++ .pnp.cjs | 20 +++++ packages/sources/moore-hk/CHANGELOG.md | 0 packages/sources/moore-hk/README.md | 54 ++++++++++++ packages/sources/moore-hk/package.json | 40 +++++++++ packages/sources/moore-hk/src/config/index.ts | 9 ++ .../sources/moore-hk/src/endpoint/index.ts | 1 + .../sources/moore-hk/src/endpoint/trueusd.ts | 32 +++++++ packages/sources/moore-hk/src/index.ts | 20 +++++ .../sources/moore-hk/src/transport/trueusd.ts | 83 +++++++++++++++++++ packages/sources/moore-hk/test-payload.json | 5 ++ .../__snapshots__/adapter.test.ts.snap | 39 +++++++++ .../moore-hk/test/integration/adapter.test.ts | 68 +++++++++++++++ .../moore-hk/test/integration/fixtures.ts | 79 ++++++++++++++++++ packages/sources/moore-hk/tsconfig.json | 9 ++ packages/sources/moore-hk/tsconfig.test.json | 7 ++ packages/tsconfig.json | 3 + packages/tsconfig.test.json | 3 + yarn.lock | 13 +++ 19 files changed, 490 insertions(+) create mode 100644 .changeset/eight-eggs-perform.md create mode 100644 packages/sources/moore-hk/CHANGELOG.md create mode 100644 packages/sources/moore-hk/README.md create mode 100644 packages/sources/moore-hk/package.json create mode 100644 packages/sources/moore-hk/src/config/index.ts create mode 100644 packages/sources/moore-hk/src/endpoint/index.ts create mode 100644 packages/sources/moore-hk/src/endpoint/trueusd.ts create mode 100644 packages/sources/moore-hk/src/index.ts create mode 100644 packages/sources/moore-hk/src/transport/trueusd.ts create mode 100644 packages/sources/moore-hk/test-payload.json create mode 100644 packages/sources/moore-hk/test/integration/__snapshots__/adapter.test.ts.snap create mode 100644 packages/sources/moore-hk/test/integration/adapter.test.ts create mode 100644 packages/sources/moore-hk/test/integration/fixtures.ts create mode 100644 packages/sources/moore-hk/tsconfig.json create mode 100755 packages/sources/moore-hk/tsconfig.test.json diff --git a/.changeset/eight-eggs-perform.md b/.changeset/eight-eggs-perform.md new file mode 100644 index 0000000000..8b32f03071 --- /dev/null +++ b/.changeset/eight-eggs-perform.md @@ -0,0 +1,5 @@ +--- +'@chainlink/moore-hk-adapter': major +--- + +moore-hk initial EA release diff --git a/.pnp.cjs b/.pnp.cjs index 206c3e353e..b5642ab5e5 100644 --- a/.pnp.cjs +++ b/.pnp.cjs @@ -611,6 +611,10 @@ function $$SETUP_STATE(hydrateRuntimeState, basePath) { "name": "@chainlink/moonbeam-address-list-adapter",\ "reference": "workspace:packages/sources/moonbeam-address-list"\ },\ + {\ + "name": "@chainlink/moore-hk-adapter",\ + "reference": "workspace:packages/sources/moore-hk"\ + },\ {\ "name": "@chainlink/mycryptoapi-adapter",\ "reference": "workspace:packages/sources/mycryptoapi"\ @@ -973,6 +977,7 @@ function $$SETUP_STATE(hydrateRuntimeState, basePath) { ["@chainlink/metalsapi-adapter", ["workspace:packages/sources/metalsapi"]],\ ["@chainlink/mock-ea-adapter", ["workspace:packages/sources/mock-ea"]],\ ["@chainlink/moonbeam-address-list-adapter", ["workspace:packages/sources/moonbeam-address-list"]],\ + ["@chainlink/moore-hk-adapter", ["workspace:packages/sources/moore-hk"]],\ ["@chainlink/mycryptoapi-adapter", ["workspace:packages/sources/mycryptoapi"]],\ ["@chainlink/ncfx-adapter", ["workspace:packages/sources/ncfx"]],\ ["@chainlink/nft-blue-chip-adapter", ["workspace:packages/sources/nft-blue-chip"]],\ @@ -7292,6 +7297,21 @@ function $$SETUP_STATE(hydrateRuntimeState, basePath) { "linkType": "SOFT"\ }]\ ]],\ + ["@chainlink/moore-hk-adapter", [\ + ["workspace:packages/sources/moore-hk", {\ + "packageLocation": "./packages/sources/moore-hk/",\ + "packageDependencies": [\ + ["@chainlink/moore-hk-adapter", "workspace:packages/sources/moore-hk"],\ + ["@chainlink/external-adapter-framework", "npm:0.33.2"],\ + ["@types/jest", "npm:27.5.2"],\ + ["@types/node", "npm:16.11.68"],\ + ["nock", "npm:13.2.9"],\ + ["tslib", "npm:2.4.1"],\ + ["typescript", "patch:typescript@npm%3A5.0.4#~builtin::version=5.0.4&hash=b5f058"]\ + ],\ + "linkType": "SOFT"\ + }]\ + ]],\ ["@chainlink/mycryptoapi-adapter", [\ ["workspace:packages/sources/mycryptoapi", {\ "packageLocation": "./packages/sources/mycryptoapi/",\ diff --git a/packages/sources/moore-hk/CHANGELOG.md b/packages/sources/moore-hk/CHANGELOG.md new file mode 100644 index 0000000000..e69de29bb2 diff --git a/packages/sources/moore-hk/README.md b/packages/sources/moore-hk/README.md new file mode 100644 index 0000000000..894b37bdaf --- /dev/null +++ b/packages/sources/moore-hk/README.md @@ -0,0 +1,54 @@ +# MOORE-HK + +![0.0.0](https://img.shields.io/github/package-json/v/smartcontractkit/external-adapters-js?filename=packages/sources/moore-hk/package.json) ![v3](https://img.shields.io/badge/framework%20version-v3-blueviolet) + +This document was generated automatically. Please see [README Generator](../../scripts#readme-generator) for more info. + +## Environment Variables + +| Required? | Name | Description | Type | Options | Default | +| :-------: | :----------: | :-----------------------------------: | :----: | :-----: | :-----------------------------------------------: | +| | API_ENDPOINT | An API endpoint for the Data Provider | string | | `https://api.real-time-reserves.verinumus.io/v1/` | + +--- + +## Data Provider Rate Limits + +| Name | Requests/credits per second | Requests/credits per minute | Requests/credits per hour | Note | +| :-----: | :-------------------------: | :-------------------------: | :-----------------------: | :--: | +| default | | 6 | | | + +--- + +## Input Parameters + +| Required? | Name | Description | Type | Options | Default | +| :-------: | :------: | :-----------------: | :----: | :--------------------------: | :-------: | +| | endpoint | The endpoint to use | string | [trueusd](#trueusd-endpoint) | `trueusd` | + +## Trueusd Endpoint + +`trueusd` is the only supported name for this endpoint. + +### Input Params + +| Required? | Name | Aliases | Description | Type | Options | Default | Depends On | Not Valid With | +| :-------: | :---: | :-----: | :------------------------------------------------------: | :----: | :-----: | :----------: | :--------: | :------------: | +| | field | | The object-path string to parse a single `result` value. | string | | `totalTrust` | | | + +### Example + +Request: + +```json +{ + "data": { + "endpoint": "trueusd", + "field": "totalTrust" + } +} +``` + +--- + +MIT License diff --git a/packages/sources/moore-hk/package.json b/packages/sources/moore-hk/package.json new file mode 100644 index 0000000000..f36bcf2a68 --- /dev/null +++ b/packages/sources/moore-hk/package.json @@ -0,0 +1,40 @@ +{ + "name": "@chainlink/moore-hk-adapter", + "version": "0.0.0", + "description": "Chainlink moore-hk adapter.", + "keywords": [ + "Chainlink", + "LINK", + "blockchain", + "oracle", + "moore-hk" + ], + "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.11.68", + "nock": "13.2.9", + "typescript": "5.0.4" + }, + "dependencies": { + "@chainlink/external-adapter-framework": "0.33.2", + "tslib": "2.4.1" + } +} diff --git a/packages/sources/moore-hk/src/config/index.ts b/packages/sources/moore-hk/src/config/index.ts new file mode 100644 index 0000000000..9ed58935e3 --- /dev/null +++ b/packages/sources/moore-hk/src/config/index.ts @@ -0,0 +1,9 @@ +import { AdapterConfig } from '@chainlink/external-adapter-framework/config' + +export const config = new AdapterConfig({ + API_ENDPOINT: { + description: 'An API endpoint for the Data Provider', + type: 'string', + default: 'https://api.real-time-reserves.verinumus.io/v1/', + }, +}) diff --git a/packages/sources/moore-hk/src/endpoint/index.ts b/packages/sources/moore-hk/src/endpoint/index.ts new file mode 100644 index 0000000000..6bdbcee4b7 --- /dev/null +++ b/packages/sources/moore-hk/src/endpoint/index.ts @@ -0,0 +1 @@ +export { endpoint as trueusd } from './trueusd' diff --git a/packages/sources/moore-hk/src/endpoint/trueusd.ts b/packages/sources/moore-hk/src/endpoint/trueusd.ts new file mode 100644 index 0000000000..e3c9b960b1 --- /dev/null +++ b/packages/sources/moore-hk/src/endpoint/trueusd.ts @@ -0,0 +1,32 @@ +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/trueusd' + +export const inputParameters = new InputParameters( + { + field: { + default: 'totalTrust', + description: 'The object-path string to parse a single `result` value.', + type: 'string', + }, + }, + [ + { + field: 'totalTrust', + }, + ], +) + +export type BaseEndpointTypes = { + Parameters: typeof inputParameters.definition + Response: SingleNumberResultResponse + Settings: typeof config.settings +} + +export const endpoint = new AdapterEndpoint({ + name: 'trueusd', + transport: httpTransport, + inputParameters, +}) diff --git a/packages/sources/moore-hk/src/index.ts b/packages/sources/moore-hk/src/index.ts new file mode 100644 index 0000000000..0a5d0679f7 --- /dev/null +++ b/packages/sources/moore-hk/src/index.ts @@ -0,0 +1,20 @@ +import { expose, ServerInstance } from '@chainlink/external-adapter-framework' +import { Adapter } from '@chainlink/external-adapter-framework/adapter' +import { config } from './config' +import { trueusd } from './endpoint' + +export const adapter = new Adapter({ + defaultEndpoint: trueusd.name, + name: 'MOORE-HK', + config, + endpoints: [trueusd], + rateLimiting: { + tiers: { + default: { + rateLimit1m: 6, + }, + }, + }, +}) + +export const server = (): Promise => expose(adapter) diff --git a/packages/sources/moore-hk/src/transport/trueusd.ts b/packages/sources/moore-hk/src/transport/trueusd.ts new file mode 100644 index 0000000000..36381bab53 --- /dev/null +++ b/packages/sources/moore-hk/src/transport/trueusd.ts @@ -0,0 +1,83 @@ +import { HttpTransport } from '@chainlink/external-adapter-framework/transports' +import { BaseEndpointTypes } from '../endpoint/trueusd' + +interface ResponseSchema { + accountName: string + totalTrust: number + totalToken: number + updatedAt: string + token: { + tokenName: string + totalTokenByChain: number + }[] + ripcord: boolean + ripcordDetails: 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, + url: '/chainlink/proof-of-reserves/TrueUSD', + }, + } + }) + }, + parseResponse: (params, response) => { + return params.map((param) => { + // Return error if ripcord indicator true + const providerIndicatedTimeUnixMs = new Date(response.data.updatedAt).getTime() + if (response.data.ripcord) { + const message = `Ripcord indicator true. Details: ${response.data.ripcordDetails.join( + ', ', + )}` + return { + params: param, + response: { + errorMessage: message, + statusCode: 502, + timestamps: { + providerIndicatedTimeUnixMs, + }, + }, + } + } + + const resultPath = param.field || '' + const result = response.data[resultPath as keyof typeof response.data] + + if (isNaN(result as number)) { + return { + params: param, + response: { + errorMessage: `Value for '${resultPath}' is not a number.`, + statusCode: 502, + }, + } + } + + return { + params: param, + response: { + result: result as number, + data: { + result: result as number, + }, + timestamps: { + providerIndicatedTimeUnixMs, + }, + }, + } + }) + }, +}) diff --git a/packages/sources/moore-hk/test-payload.json b/packages/sources/moore-hk/test-payload.json new file mode 100644 index 0000000000..09a8358228 --- /dev/null +++ b/packages/sources/moore-hk/test-payload.json @@ -0,0 +1,5 @@ +{ + "requests": [{ + "field": "totalTrust" + }] +} diff --git a/packages/sources/moore-hk/test/integration/__snapshots__/adapter.test.ts.snap b/packages/sources/moore-hk/test/integration/__snapshots__/adapter.test.ts.snap new file mode 100644 index 0000000000..561b4b4e22 --- /dev/null +++ b/packages/sources/moore-hk/test/integration/__snapshots__/adapter.test.ts.snap @@ -0,0 +1,39 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`execute trueusd endpoint should return 502 when field dne 1`] = ` +{ + "errorMessage": "Value for 'invalidField' is not a number.", + "statusCode": 502, + "timestamps": { + "providerDataReceivedUnixMs": 978347471111, + "providerDataRequestedUnixMs": 978347471111, + }, +} +`; + +exports[`execute trueusd endpoint should return 502 when ripcord: true 1`] = ` +{ + "errorMessage": "Ripcord indicator true. Details: Details1, Details2", + "statusCode": 502, + "timestamps": { + "providerDataReceivedUnixMs": 978347471111, + "providerDataRequestedUnixMs": 978347471111, + "providerIndicatedTimeUnixMs": 1706162197155, + }, +} +`; + +exports[`execute trueusd endpoint should return success 1`] = ` +{ + "data": { + "result": 1888313215.57, + }, + "result": 1888313215.57, + "statusCode": 200, + "timestamps": { + "providerDataReceivedUnixMs": 978347471111, + "providerDataRequestedUnixMs": 978347471111, + "providerIndicatedTimeUnixMs": 1706162197155, + }, +} +`; diff --git a/packages/sources/moore-hk/test/integration/adapter.test.ts b/packages/sources/moore-hk/test/integration/adapter.test.ts new file mode 100644 index 0000000000..79c8e340a3 --- /dev/null +++ b/packages/sources/moore-hk/test/integration/adapter.test.ts @@ -0,0 +1,68 @@ +import { + TestAdapter, + setEnvVariables, +} from '@chainlink/external-adapter-framework/util/testing-utils' +import * as nock from 'nock' +import { mockResponseRipcordTrue, 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)) + 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('trueusd endpoint', () => { + it('should return success', async () => { + mockResponseSuccess() + const data = { + endpoint: 'trueusd', + field: 'totalTrust', + } + const response = await testAdapter.request(data) + expect(response.statusCode).toBe(200) + expect(response.json()).toMatchSnapshot() + }) + + it('should return 502 when field dne', async () => { + mockResponseSuccess() + const data = { + endpoint: 'trueusd', + field: 'invalidField', + } + const response = await testAdapter.request(data) + expect(response.statusCode).toBe(502) + expect(response.json()).toMatchSnapshot() + }) + + it('should return 502 when ripcord: true', async () => { + nock.cleanAll() + mockResponseRipcordTrue() + const data = { + endpoint: 'trueusd', + field: 'totalToken', + } + const response = await testAdapter.request(data) + expect(response.statusCode).toBe(502) + expect(response.json()).toMatchSnapshot() + }) + }) +}) diff --git a/packages/sources/moore-hk/test/integration/fixtures.ts b/packages/sources/moore-hk/test/integration/fixtures.ts new file mode 100644 index 0000000000..9abbe5db6e --- /dev/null +++ b/packages/sources/moore-hk/test/integration/fixtures.ts @@ -0,0 +1,79 @@ +import nock from 'nock' + +const BASE_URL = 'https://api.real-time-reserves.verinumus.io/v1/' +const PATH = '/chainlink/proof-of-reserves/TrueUSD' + +export const mockResponseSuccess = (): nock.Scope => + nock(BASE_URL) + .get(PATH) + .reply( + 200, + () => ({ + accountName: 'TrueUSD', + totalTrust: 1888313215.57, + totalToken: 1864056156.7396348, + updatedAt: '2024-01-25T05:56:37.155Z', + token: [ + { + tokenName: 'TUSD (ETH)', + totalTokenByChain: 389998406.37, + }, + { + tokenName: 'TUSD (AVAX)', + totalTokenByChain: 2984455.56, + }, + { + tokenName: 'TUSD (BNB)', + totalTokenByChain: 145015.86963478, + }, + { + tokenName: 'TUSD (TRON)', + totalTokenByChain: 1440897915.06, + }, + { + tokenName: 'TUSD (BSC)', + totalTokenByChain: 30030363.88, + }, + ], + ripcord: false, + ripcordDetails: [], + }), + [ + 'Content-Type', + 'application/json', + 'Connection', + 'close', + 'Vary', + 'Accept-Encoding', + 'Vary', + 'Origin', + ], + ) + .persist() + +export const mockResponseRipcordTrue = (): nock.Scope => + nock(BASE_URL) + .get(PATH) + .reply( + 200, + () => ({ + accountName: 'TrueUSD', + totalTrust: 1888313215.57, + totalToken: 1864056156.7396348, + updatedAt: '2024-01-25T05:56:37.155Z', + token: [], + ripcord: true, + ripcordDetails: ['Details1', 'Details2'], + }), + [ + 'Content-Type', + 'application/json', + 'Connection', + 'close', + 'Vary', + 'Accept-Encoding', + 'Vary', + 'Origin', + ], + ) + .persist() diff --git a/packages/sources/moore-hk/tsconfig.json b/packages/sources/moore-hk/tsconfig.json new file mode 100644 index 0000000000..f59363fd76 --- /dev/null +++ b/packages/sources/moore-hk/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/moore-hk/tsconfig.test.json b/packages/sources/moore-hk/tsconfig.test.json new file mode 100755 index 0000000000..e3de28cb5c --- /dev/null +++ b/packages/sources/moore-hk/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 bc0385a40e..69f3ab563c 100644 --- a/packages/tsconfig.json +++ b/packages/tsconfig.json @@ -437,6 +437,9 @@ { "path": "./sources/moonbeam-address-list" }, + { + "path": "./sources/moore-hk" + }, { "path": "./sources/mycryptoapi" }, diff --git a/packages/tsconfig.test.json b/packages/tsconfig.test.json index 96a35caf00..a727d1da0a 100644 --- a/packages/tsconfig.test.json +++ b/packages/tsconfig.test.json @@ -437,6 +437,9 @@ { "path": "./sources/moonbeam-address-list/tsconfig.test.json" }, + { + "path": "./sources/moore-hk/tsconfig.test.json" + }, { "path": "./sources/mycryptoapi/tsconfig.test.json" }, diff --git a/yarn.lock b/yarn.lock index 3cc0b256f8..0f3586ded6 100644 --- a/yarn.lock +++ b/yarn.lock @@ -4461,6 +4461,19 @@ __metadata: languageName: unknown linkType: soft +"@chainlink/moore-hk-adapter@workspace:packages/sources/moore-hk": + version: 0.0.0-use.local + resolution: "@chainlink/moore-hk-adapter@workspace:packages/sources/moore-hk" + dependencies: + "@chainlink/external-adapter-framework": 0.33.2 + "@types/jest": 27.5.2 + "@types/node": 16.11.68 + nock: 13.2.9 + tslib: 2.4.1 + typescript: 5.0.4 + languageName: unknown + linkType: soft + "@chainlink/mycryptoapi-adapter@workspace:packages/sources/mycryptoapi": version: 0.0.0-use.local resolution: "@chainlink/mycryptoapi-adapter@workspace:packages/sources/mycryptoapi"