From e28136e0f168823373a85ade93859be2cda8ba3a Mon Sep 17 00:00:00 2001 From: Dennis O'Connell Date: Wed, 27 Nov 2024 16:02:09 -0500 Subject: [PATCH 01/14] nav and reserve build out --- .../sources/peregrine-fund-admin/CHANGELOG.md | 0 .../sources/peregrine-fund-admin/README.md | 3 + .../sources/peregrine-fund-admin/package.json | 40 +++++++++ .../peregrine-fund-admin/src/config/index.ts | 15 ++++ .../src/config/overrides.json | 3 + .../src/endpoint/index.ts | 2 + .../peregrine-fund-admin/src/endpoint/nav.ts | 35 ++++++++ .../src/endpoint/reserve.ts | 35 ++++++++ .../sources/peregrine-fund-admin/src/index.ts | 17 ++++ .../peregrine-fund-admin/src/transport/nav.ts | 88 +++++++++++++++++++ .../src/transport/reserve.ts | 81 +++++++++++++++++ .../peregrine-fund-admin/test-payload.json | 6 ++ .../test/integration/adapter.test.ts | 64 ++++++++++++++ .../test/integration/fixtures.ts | 22 +++++ .../peregrine-fund-admin/tsconfig.json | 9 ++ .../peregrine-fund-admin/tsconfig.test.json | 7 ++ 16 files changed, 427 insertions(+) create mode 100644 packages/sources/peregrine-fund-admin/CHANGELOG.md create mode 100644 packages/sources/peregrine-fund-admin/README.md create mode 100644 packages/sources/peregrine-fund-admin/package.json create mode 100644 packages/sources/peregrine-fund-admin/src/config/index.ts create mode 100644 packages/sources/peregrine-fund-admin/src/config/overrides.json create mode 100644 packages/sources/peregrine-fund-admin/src/endpoint/index.ts create mode 100644 packages/sources/peregrine-fund-admin/src/endpoint/nav.ts create mode 100644 packages/sources/peregrine-fund-admin/src/endpoint/reserve.ts create mode 100644 packages/sources/peregrine-fund-admin/src/index.ts create mode 100644 packages/sources/peregrine-fund-admin/src/transport/nav.ts create mode 100644 packages/sources/peregrine-fund-admin/src/transport/reserve.ts create mode 100644 packages/sources/peregrine-fund-admin/test-payload.json create mode 100644 packages/sources/peregrine-fund-admin/test/integration/adapter.test.ts create mode 100644 packages/sources/peregrine-fund-admin/test/integration/fixtures.ts create mode 100644 packages/sources/peregrine-fund-admin/tsconfig.json create mode 100755 packages/sources/peregrine-fund-admin/tsconfig.test.json diff --git a/packages/sources/peregrine-fund-admin/CHANGELOG.md b/packages/sources/peregrine-fund-admin/CHANGELOG.md new file mode 100644 index 0000000000..e69de29bb2 diff --git a/packages/sources/peregrine-fund-admin/README.md b/packages/sources/peregrine-fund-admin/README.md new file mode 100644 index 0000000000..cb4ce772e0 --- /dev/null +++ b/packages/sources/peregrine-fund-admin/README.md @@ -0,0 +1,3 @@ +# Chainlink External Adapter for peregrine-fund-admin + +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 peregrine-fund-admin`. diff --git a/packages/sources/peregrine-fund-admin/package.json b/packages/sources/peregrine-fund-admin/package.json new file mode 100644 index 0000000000..d41632da96 --- /dev/null +++ b/packages/sources/peregrine-fund-admin/package.json @@ -0,0 +1,40 @@ +{ + "name": "@chainlink/peregrine-fund-admin-adapter", + "version": "0.0.0", + "description": "Chainlink peregrine-fund-admin adapter.", + "keywords": [ + "Chainlink", + "LINK", + "blockchain", + "oracle", + "peregrine-fund-admin" + ], + "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.115", + "nock": "13.5.4", + "typescript": "5.5.4" + }, + "dependencies": { + "@chainlink/external-adapter-framework": "1.7.1", + "tslib": "2.4.1" + } +} diff --git a/packages/sources/peregrine-fund-admin/src/config/index.ts b/packages/sources/peregrine-fund-admin/src/config/index.ts new file mode 100644 index 0000000000..a4cdabebfe --- /dev/null +++ b/packages/sources/peregrine-fund-admin/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://fund-admin-data-adapter-v1-960005989691.europe-west2.run.app', + }, +}) diff --git a/packages/sources/peregrine-fund-admin/src/config/overrides.json b/packages/sources/peregrine-fund-admin/src/config/overrides.json new file mode 100644 index 0000000000..3114406669 --- /dev/null +++ b/packages/sources/peregrine-fund-admin/src/config/overrides.json @@ -0,0 +1,3 @@ +{ + "peregrine-fund-admin": {} +} diff --git a/packages/sources/peregrine-fund-admin/src/endpoint/index.ts b/packages/sources/peregrine-fund-admin/src/endpoint/index.ts new file mode 100644 index 0000000000..33cde12241 --- /dev/null +++ b/packages/sources/peregrine-fund-admin/src/endpoint/index.ts @@ -0,0 +1,2 @@ +export { endpoint as nav } from './nav' +export { endpoint as reserve } from './reserve' diff --git a/packages/sources/peregrine-fund-admin/src/endpoint/nav.ts b/packages/sources/peregrine-fund-admin/src/endpoint/nav.ts new file mode 100644 index 0000000000..34a2f81fbb --- /dev/null +++ b/packages/sources/peregrine-fund-admin/src/endpoint/nav.ts @@ -0,0 +1,35 @@ +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 overrides from '../config/overrides.json' +import { httpTransport } from '../transport/nav' + +// Input parameters define the structure of the request expected by the endpoint. The second parameter defines example input data that will be used in EA readme +export const inputParameters = new InputParameters({ + assetId: { + aliases: ['assetId'], + required: true, + type: 'number', + description: 'The identifying number for the requested asset', + }, +}) +// Endpoints contain a type parameter that allows specifying relevant types of an endpoint, for example, request payload type, Adapter response type and Adapter configuration (environment variables) type +export type BaseEndpointTypes = { + Parameters: typeof inputParameters.definition + Response: SingleNumberResultResponse + Settings: typeof config.settings +} + +export const endpoint = new AdapterEndpoint({ + // Endpoint name + name: 'nav', + // Alternative endpoint names for this endpoint + aliases: [], + // Transport handles incoming requests, data processing and communication for this endpoint + transport: httpTransport, + // Supported input parameters for this endpoint + inputParameters, + // Overrides are defined in the `/config/overrides.json` file. They allow input parameters to be overriden from a generic symbol to something more specific for the data provider such as an ID. + overrides: overrides['peregrine-fund-admin'], +}) diff --git a/packages/sources/peregrine-fund-admin/src/endpoint/reserve.ts b/packages/sources/peregrine-fund-admin/src/endpoint/reserve.ts new file mode 100644 index 0000000000..69ac0e373d --- /dev/null +++ b/packages/sources/peregrine-fund-admin/src/endpoint/reserve.ts @@ -0,0 +1,35 @@ +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 overrides from '../config/overrides.json' +import { httpTransport } from '../transport/reserve' + +// Input parameters define the structure of the request expected by the endpoint. The second parameter defines example input data that will be used in EA readme +export const inputParameters = new InputParameters({ + assetId: { + aliases: ['assetId'], + required: true, + type: 'number', + description: 'The identifying number for the requested asset', + }, +}) +// Endpoints contain a type parameter that allows specifying relevant types of an endpoint, for example, request payload type, Adapter response type and Adapter configuration (environment variables) type +export type BaseEndpointTypes = { + Parameters: typeof inputParameters.definition + Response: SingleNumberResultResponse + Settings: typeof config.settings +} + +export const endpoint = new AdapterEndpoint({ + // Endpoint name + name: 'reserve', + // Alternative endpoint names for this endpoint + aliases: [], + // Transport handles incoming requests, data processing and communication for this endpoint + transport: httpTransport, + // Supported input parameters for this endpoint + inputParameters, + // Overrides are defined in the `/config/overrides.json` file. They allow input parameters to be overriden from a generic symbol to something more specific for the data provider such as an ID. + overrides: overrides['peregrine-fund-admin'], +}) diff --git a/packages/sources/peregrine-fund-admin/src/index.ts b/packages/sources/peregrine-fund-admin/src/index.ts new file mode 100644 index 0000000000..b8a45fbe9d --- /dev/null +++ b/packages/sources/peregrine-fund-admin/src/index.ts @@ -0,0 +1,17 @@ +import { expose, ServerInstance } from '@chainlink/external-adapter-framework' +import { Adapter } from '@chainlink/external-adapter-framework/adapter' +import { config } from './config' +import { nav, reserve } from './endpoint' + +export const adapter = new Adapter({ + //Requests will direct to this endpoint if the `endpoint` input parameter is not specified. + defaultEndpoint: nav.name, + // Adapter name + name: 'PEREGRINE_FUND-ADMIN', + // Adapter configuration (environment variables) + config, + // List of supported endpoints + endpoints: [nav, reserve], +}) + +export const server = (): Promise => expose(adapter) diff --git a/packages/sources/peregrine-fund-admin/src/transport/nav.ts b/packages/sources/peregrine-fund-admin/src/transport/nav.ts new file mode 100644 index 0000000000..96ef27a81b --- /dev/null +++ b/packages/sources/peregrine-fund-admin/src/transport/nav.ts @@ -0,0 +1,88 @@ +import { HttpTransport } from '@chainlink/external-adapter-framework/transports' +import { BaseEndpointTypes } from '../endpoint/nav' + +export interface ResponseSchema { + Id: string | null + assetId: string + seniorNAV: string + juniorNav: string + equityNav: string + totalLiability: string + totalAccounts: string + totalCollateral: string + collateral: number[] + accounts: number[] + updateTimestamp: string + id: string | null +} + +// HttpTransport extends base types from endpoint and adds additional, Provider-specific types like 'RequestBody', which is the type of +// request body (not the request to adapter, but the request that adapter sends to Data Provider), and 'ResponseBody' which is +// the type of raw response from Data Provider +export type HttpTransportTypes = BaseEndpointTypes & { + Provider: { + RequestBody: never + ResponseBody: ResponseSchema + } +} +// HttpTransport is used to fetch and process data from a Provider using HTTP(S) protocol. It usually needs two methods +// `prepareRequests` and `parseResponse` +export const httpTransport = new HttpTransport({ + // `prepareRequests` method receives request payloads sent to associated endpoint alongside adapter config(environment variables) + // and should return 'request information' to the Data Provider. Use this method to construct one or many requests, and the framework + // will send them to Data Provider + prepareRequests: (params, config) => { + return params.map((param) => { + return { + // `params` are parameters associated to this single request and will also be available in the 'parseResponse' method. + params: [param], + // `request` contains any valid axios request configuration + request: { + baseURL: config.API_ENDPOINT, + url: '/api/v1/nav/', + headers: { + X_API_KEY: config.API_KEY, + }, + params: { + symbol: param.base.toUpperCase(), + convert: param.quote.toUpperCase(), + }, + }, + } + }) + }, + // `parseResponse` takes the 'params' specified in the `prepareRequests` and the 'response' from Data Provider and should return + // an array of response objects to be stored in cache. Use this method to construct a list of response objects for every parameter in 'params' + // and the framework will save them in cache and return to user + parseResponse: (params, response) => { + // In case error was received, it's a good practice to return meaningful information to user + if (!response.data) { + return params.map((param) => { + return { + params: param, + response: { + errorMessage: `The data provider didn't return any value for ${param.base}/${param.quote}`, + statusCode: 502, + }, + } + }) + } + + // For successful responses for each 'param' a new response object is created and returned as an array + return params.map((param) => { + const result = response.data.equityNav + // Response objects, whether successful or errors, contain two properties, 'params' and 'response'. 'response' is what will be + // stored in the cache and returned as adapter response and 'params' determines the identifier so that the next request with same 'params' + // will immediately return the response from the cache + return { + params: param, + response: { + result, + data: { + result, + }, + }, + } + }) + }, +}) diff --git a/packages/sources/peregrine-fund-admin/src/transport/reserve.ts b/packages/sources/peregrine-fund-admin/src/transport/reserve.ts new file mode 100644 index 0000000000..dd4976a780 --- /dev/null +++ b/packages/sources/peregrine-fund-admin/src/transport/reserve.ts @@ -0,0 +1,81 @@ +import { HttpTransport } from '@chainlink/external-adapter-framework/transports' +import { BaseEndpointTypes } from '../endpoint/reserve' + +export interface ResponseSchema { + assetId: number + totalValue: string + currencyBase: string + accountIds: number[] + updateDateTime: string +} + +// HttpTransport extends base types from endpoint and adds additional, Provider-specific types like 'RequestBody', which is the type of +// request body (not the request to adapter, but the request that adapter sends to Data Provider), and 'ResponseBody' which is +// the type of raw response from Data Provider +export type HttpTransportTypes = BaseEndpointTypes & { + Provider: { + RequestBody: never + ResponseBody: ResponseSchema + } +} +// HttpTransport is used to fetch and process data from a Provider using HTTP(S) protocol. It usually needs two methods +// `prepareRequests` and `parseResponse` +export const httpTransport = new HttpTransport({ + // `prepareRequests` method receives request payloads sent to associated endpoint alongside adapter config(environment variables) + // and should return 'request information' to the Data Provider. Use this method to construct one or many requests, and the framework + // will send them to Data Provider + prepareRequests: (params, config) => { + return params.map((param) => { + return { + // `params` are parameters associated to this single request and will also be available in the 'parseResponse' method. + params: [param], + // `request` contains any valid axios request configuration + request: { + baseURL: config.API_ENDPOINT, + url: '/api/v1/reserves/', + headers: { + X_API_KEY: config.API_KEY, + }, + params: { + symbol: param.base.toUpperCase(), + convert: param.quote.toUpperCase(), + }, + }, + } + }) + }, + // `parseResponse` takes the 'params' specified in the `prepareRequests` and the 'response' from Data Provider and should return + // an array of response objects to be stored in cache. Use this method to construct a list of response objects for every parameter in 'params' + // and the framework will save them in cache and return to user + parseResponse: (params, response) => { + // In case error was received, it's a good practice to return meaningful information to user + if (!response.data) { + return params.map((param) => { + return { + params: param, + response: { + errorMessage: `The data provider didn't return any value for ${param.base}/${param.quote}`, + statusCode: 502, + }, + } + }) + } + + // For successful responses for each 'param' a new response object is created and returned as an array + return params.map((param) => { + const result = response.data[param.base.toUpperCase()].price + // Response objects, whether successful or errors, contain two properties, 'params' and 'response'. 'response' is what will be + // stored in the cache and returned as adapter response and 'params' determines the identifier so that the next request with same 'params' + // will immediately return the response from the cache + return { + params: param, + response: { + result, + data: { + result, + }, + }, + } + }) + }, +}) diff --git a/packages/sources/peregrine-fund-admin/test-payload.json b/packages/sources/peregrine-fund-admin/test-payload.json new file mode 100644 index 0000000000..7930bcfa20 --- /dev/null +++ b/packages/sources/peregrine-fund-admin/test-payload.json @@ -0,0 +1,6 @@ +{ + "requests": [{ + "from": "BTC", + "to": "USD" + }] +} diff --git a/packages/sources/peregrine-fund-admin/test/integration/adapter.test.ts b/packages/sources/peregrine-fund-admin/test/integration/adapter.test.ts new file mode 100644 index 0000000000..31d6b00153 --- /dev/null +++ b/packages/sources/peregrine-fund-admin/test/integration/adapter.test.ts @@ -0,0 +1,64 @@ +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_KEY = 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('nav endpoint', () => { + it('should return success', async () => { + const data = { + base: 'ETH', + quote: 'USD', + endpoint: 'nav', + transport: 'rest', + } + mockResponseSuccess() + const response = await testAdapter.request(data) + expect(response.statusCode).toBe(200) + expect(response.json()).toMatchSnapshot() + }) + }) + + describe('reserve endpoint', () => { + it('should return success', async () => { + const data = { + base: 'ETH', + quote: 'USD', + endpoint: 'reserve', + transport: 'rest', + } + mockResponseSuccess() + const response = await testAdapter.request(data) + expect(response.statusCode).toBe(200) + expect(response.json()).toMatchSnapshot() + }) + }) +}) diff --git a/packages/sources/peregrine-fund-admin/test/integration/fixtures.ts b/packages/sources/peregrine-fund-admin/test/integration/fixtures.ts new file mode 100644 index 0000000000..3b7e58e409 --- /dev/null +++ b/packages/sources/peregrine-fund-admin/test/integration/fixtures.ts @@ -0,0 +1,22 @@ +import nock from 'nock' + +export const mockResponseSuccess = (): nock.Scope => + nock('https://dataproviderapi.com', { + encodedQueryParams: true, + }) + .get('/cryptocurrency/price') + .query({ + symbol: 'ETH', + convert: 'USD', + }) + .reply(200, () => ({ ETH: { price: 10000 } }), [ + 'Content-Type', + 'application/json', + 'Connection', + 'close', + 'Vary', + 'Accept-Encoding', + 'Vary', + 'Origin', + ]) + .persist() diff --git a/packages/sources/peregrine-fund-admin/tsconfig.json b/packages/sources/peregrine-fund-admin/tsconfig.json new file mode 100644 index 0000000000..f59363fd76 --- /dev/null +++ b/packages/sources/peregrine-fund-admin/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/peregrine-fund-admin/tsconfig.test.json b/packages/sources/peregrine-fund-admin/tsconfig.test.json new file mode 100755 index 0000000000..e3de28cb5c --- /dev/null +++ b/packages/sources/peregrine-fund-admin/tsconfig.test.json @@ -0,0 +1,7 @@ +{ + "extends": "../../tsconfig.base.json", + "include": ["src/**/*", "**/test", "src/**/*.json"], + "compilerOptions": { + "noEmit": true + } +} From d1b0993c817335e280f3be5e278d9a6f67f8db8d Mon Sep 17 00:00:00 2001 From: Dennis O'Connell Date: Fri, 29 Nov 2024 18:37:39 -0500 Subject: [PATCH 02/14] refactor and pull request updates --- .../sources/peregrine-fund-admin/package.json | 4 +- .../peregrine-fund-admin/src/config/index.ts | 38 +++++++++- .../src/config/overrides.json | 3 - .../peregrine-fund-admin/src/endpoint/nav.ts | 16 ++--- .../src/endpoint/reserve.ts | 18 ++--- .../sources/peregrine-fund-admin/src/index.ts | 2 +- .../peregrine-fund-admin/src/transport/nav.ts | 55 +++++++-------- .../src/transport/reserve.ts | 38 +++++----- .../peregrine-fund-admin/test-payload.json | 6 -- .../test/integration/adapter.test.ts | 12 ++-- .../test/integration/fixtures.ts | 70 ++++++++++++++----- 11 files changed, 151 insertions(+), 111 deletions(-) delete mode 100644 packages/sources/peregrine-fund-admin/src/config/overrides.json delete mode 100644 packages/sources/peregrine-fund-admin/test-payload.json diff --git a/packages/sources/peregrine-fund-admin/package.json b/packages/sources/peregrine-fund-admin/package.json index d41632da96..0a6625db03 100644 --- a/packages/sources/peregrine-fund-admin/package.json +++ b/packages/sources/peregrine-fund-admin/package.json @@ -1,6 +1,6 @@ { "name": "@chainlink/peregrine-fund-admin-adapter", - "version": "0.0.0", + "version": "0.0.1", "description": "Chainlink peregrine-fund-admin adapter.", "keywords": [ "Chainlink", @@ -31,7 +31,7 @@ "@types/jest": "27.5.2", "@types/node": "16.18.115", "nock": "13.5.4", - "typescript": "5.5.4" + "typescript": "5.6.3" }, "dependencies": { "@chainlink/external-adapter-framework": "1.7.1", diff --git a/packages/sources/peregrine-fund-admin/src/config/index.ts b/packages/sources/peregrine-fund-admin/src/config/index.ts index a4cdabebfe..50fa385649 100644 --- a/packages/sources/peregrine-fund-admin/src/config/index.ts +++ b/packages/sources/peregrine-fund-admin/src/config/index.ts @@ -1,5 +1,15 @@ import { AdapterConfig } from '@chainlink/external-adapter-framework/config' +export const NAME = 'PEREGRINE_FUND_ADMIN' +export const DEFAULT_API_VERSION = 'api/v1' +export const DEFAULT_NAV_ENDPOINT = 'nav' +export const DEFAULT_RESERVE_ENDPOINT = 'reserve' +export const DEFAULT_PARAM_ASSET_ID = 100 +export const DEFAULT_BASE_URL_VALUE = + 'https://fund-admin-data-adapter-v1-960005989691.europe-west2.run.app' +export const DEFAULT_NAV_URL_VALUE = '/api/v1/nav/' +export const DEFAULT_RESERVE_URL_VALUE = '/api/v1/reserve/' + export const config = new AdapterConfig({ API_KEY: { description: 'An API key for Data Provider', @@ -7,9 +17,31 @@ export const config = new AdapterConfig({ required: true, sensitive: true, }, - API_ENDPOINT: { - description: 'An API endpoint for Data Provider', + DEFAULT_BASE_URL: { + description: 'Base URL to Fund Admin Server Endpoint', + type: 'string', + default: DEFAULT_BASE_URL_VALUE, + }, + DEFAULT_NAV_URL: { + description: + 'An API endpoint for the latest Net Asset Value (NAV) calculation for a given asset', + type: 'string', + default: DEFAULT_NAV_URL_VALUE, + }, + DEFAULT_RESERVE_URL: { + description: 'An API endpoint for the latest Proof of Reserve (PoR) for a given asset', + type: 'string', + default: DEFAULT_RESERVE_URL_VALUE, + }, + API_NAV_ENDPOINT: { + description: + 'An API endpoint for the latest Net Asset Value (NAV) calculation for a given asset', + type: 'string', + default: `${DEFAULT_BASE_URL_VALUE}/${DEFAULT_API_VERSION}/${DEFAULT_NAV_ENDPOINT}/${DEFAULT_PARAM_ASSET_ID}/`, + }, + API_RESERVE_ENDPOINT: { + description: 'API Endpoint to get the latest Proof of Reserves for a given asset', type: 'string', - default: 'https://fund-admin-data-adapter-v1-960005989691.europe-west2.run.app', + default: `${DEFAULT_BASE_URL_VALUE}/${DEFAULT_API_VERSION}/${DEFAULT_RESERVE_ENDPOINT}/${DEFAULT_PARAM_ASSET_ID}/`, }, }) diff --git a/packages/sources/peregrine-fund-admin/src/config/overrides.json b/packages/sources/peregrine-fund-admin/src/config/overrides.json deleted file mode 100644 index 3114406669..0000000000 --- a/packages/sources/peregrine-fund-admin/src/config/overrides.json +++ /dev/null @@ -1,3 +0,0 @@ -{ - "peregrine-fund-admin": {} -} diff --git a/packages/sources/peregrine-fund-admin/src/endpoint/nav.ts b/packages/sources/peregrine-fund-admin/src/endpoint/nav.ts index 34a2f81fbb..eb523d4513 100644 --- a/packages/sources/peregrine-fund-admin/src/endpoint/nav.ts +++ b/packages/sources/peregrine-fund-admin/src/endpoint/nav.ts @@ -1,19 +1,17 @@ import { AdapterEndpoint } from '@chainlink/external-adapter-framework/adapter' -import { InputParameters } from '@chainlink/external-adapter-framework/validation' +import { navHttpTransport } from '../transport/nav' import { SingleNumberResultResponse } from '@chainlink/external-adapter-framework/util' +import { InputParameters } from '@chainlink/external-adapter-framework/validation' import { config } from '../config' -import overrides from '../config/overrides.json' -import { httpTransport } from '../transport/nav' -// Input parameters define the structure of the request expected by the endpoint. The second parameter defines example input data that will be used in EA readme export const inputParameters = new InputParameters({ assetId: { - aliases: ['assetId'], required: true, - type: 'number', + type: 'string', description: 'The identifying number for the requested asset', }, }) + // Endpoints contain a type parameter that allows specifying relevant types of an endpoint, for example, request payload type, Adapter response type and Adapter configuration (environment variables) type export type BaseEndpointTypes = { Parameters: typeof inputParameters.definition @@ -24,12 +22,8 @@ export type BaseEndpointTypes = { export const endpoint = new AdapterEndpoint({ // Endpoint name name: 'nav', - // Alternative endpoint names for this endpoint - aliases: [], // Transport handles incoming requests, data processing and communication for this endpoint - transport: httpTransport, + transport: navHttpTransport, // Supported input parameters for this endpoint inputParameters, - // Overrides are defined in the `/config/overrides.json` file. They allow input parameters to be overriden from a generic symbol to something more specific for the data provider such as an ID. - overrides: overrides['peregrine-fund-admin'], }) diff --git a/packages/sources/peregrine-fund-admin/src/endpoint/reserve.ts b/packages/sources/peregrine-fund-admin/src/endpoint/reserve.ts index 69ac0e373d..d447610b63 100644 --- a/packages/sources/peregrine-fund-admin/src/endpoint/reserve.ts +++ b/packages/sources/peregrine-fund-admin/src/endpoint/reserve.ts @@ -1,19 +1,17 @@ import { AdapterEndpoint } from '@chainlink/external-adapter-framework/adapter' -import { InputParameters } from '@chainlink/external-adapter-framework/validation' +import { httpReserveTransport } from '../transport/reserve' import { SingleNumberResultResponse } from '@chainlink/external-adapter-framework/util' +import { InputParameters } from '@chainlink/external-adapter-framework/validation' import { config } from '../config' -import overrides from '../config/overrides.json' -import { httpTransport } from '../transport/reserve' -// Input parameters define the structure of the request expected by the endpoint. The second parameter defines example input data that will be used in EA readme export const inputParameters = new InputParameters({ assetId: { - aliases: ['assetId'], required: true, - type: 'number', + type: 'string', description: 'The identifying number for the requested asset', }, }) + // Endpoints contain a type parameter that allows specifying relevant types of an endpoint, for example, request payload type, Adapter response type and Adapter configuration (environment variables) type export type BaseEndpointTypes = { Parameters: typeof inputParameters.definition @@ -24,12 +22,8 @@ export type BaseEndpointTypes = { export const endpoint = new AdapterEndpoint({ // Endpoint name name: 'reserve', - // Alternative endpoint names for this endpoint - aliases: [], - // Transport handles incoming requests, data processing and communication for this endpoint - transport: httpTransport, // Supported input parameters for this endpoint inputParameters, - // Overrides are defined in the `/config/overrides.json` file. They allow input parameters to be overriden from a generic symbol to something more specific for the data provider such as an ID. - overrides: overrides['peregrine-fund-admin'], + // Transport handles incoming requests, data processing and communication for this endpoint + transport: httpReserveTransport, }) diff --git a/packages/sources/peregrine-fund-admin/src/index.ts b/packages/sources/peregrine-fund-admin/src/index.ts index b8a45fbe9d..b3d3c4f5ff 100644 --- a/packages/sources/peregrine-fund-admin/src/index.ts +++ b/packages/sources/peregrine-fund-admin/src/index.ts @@ -7,7 +7,7 @@ export const adapter = new Adapter({ //Requests will direct to this endpoint if the `endpoint` input parameter is not specified. defaultEndpoint: nav.name, // Adapter name - name: 'PEREGRINE_FUND-ADMIN', + name: 'PEREGRINE_FUND_ADMIN', // Adapter configuration (environment variables) config, // List of supported endpoints diff --git a/packages/sources/peregrine-fund-admin/src/transport/nav.ts b/packages/sources/peregrine-fund-admin/src/transport/nav.ts index 96ef27a81b..bab9075d63 100644 --- a/packages/sources/peregrine-fund-admin/src/transport/nav.ts +++ b/packages/sources/peregrine-fund-admin/src/transport/nav.ts @@ -4,30 +4,35 @@ import { BaseEndpointTypes } from '../endpoint/nav' export interface ResponseSchema { Id: string | null assetId: string - seniorNAV: string - juniorNav: string - equityNav: string - totalLiability: string + seniorNAV: number + juniorNav: number + equityNav: number + totalLiability: number totalAccounts: string - totalCollateral: string + totalCollateral: number collateral: number[] accounts: number[] updateTimestamp: string id: string | null } -// HttpTransport extends base types from endpoint and adds additional, Provider-specific types like 'RequestBody', which is the type of -// request body (not the request to adapter, but the request that adapter sends to Data Provider), and 'ResponseBody' which is -// the type of raw response from Data Provider +// export type HttpTransportTypes = BaseEndpointTypes & { +// Provider: { +// RequestBody: never +// ResponseBody: ResponseSchema +// } +// } + export type HttpTransportTypes = BaseEndpointTypes & { Provider: { RequestBody: never ResponseBody: ResponseSchema } } + // HttpTransport is used to fetch and process data from a Provider using HTTP(S) protocol. It usually needs two methods // `prepareRequests` and `parseResponse` -export const httpTransport = new HttpTransport({ +export const navHttpTransport = new HttpTransport({ // `prepareRequests` method receives request payloads sent to associated endpoint alongside adapter config(environment variables) // and should return 'request information' to the Data Provider. Use this method to construct one or many requests, and the framework // will send them to Data Provider @@ -38,14 +43,13 @@ export const httpTransport = new HttpTransport({ params: [param], // `request` contains any valid axios request configuration request: { - baseURL: config.API_ENDPOINT, - url: '/api/v1/nav/', + baseURL: config.DEFAULT_BASE_URL, + url: config.DEFAULT_NAV_URL, headers: { X_API_KEY: config.API_KEY, }, params: { - symbol: param.base.toUpperCase(), - convert: param.quote.toUpperCase(), + assetId: params, }, }, } @@ -55,32 +59,27 @@ export const httpTransport = new HttpTransport({ // an array of response objects to be stored in cache. Use this method to construct a list of response objects for every parameter in 'params' // and the framework will save them in cache and return to user parseResponse: (params, response) => { - // In case error was received, it's a good practice to return meaningful information to user - if (!response.data) { - return params.map((param) => { + return params.map((param) => { + const resData = response.data as ResponseSchema + + if (!resData || !resData.equityNav) { return { params: param, response: { - errorMessage: `The data provider didn't return any value for ${param.base}/${param.quote}`, - statusCode: 502, + errorMessage: 'Missing equityNav in the response', + statusCode: 500, }, } - }) - } + } - // For successful responses for each 'param' a new response object is created and returned as an array - return params.map((param) => { - const result = response.data.equityNav - // Response objects, whether successful or errors, contain two properties, 'params' and 'response'. 'response' is what will be - // stored in the cache and returned as adapter response and 'params' determines the identifier so that the next request with same 'params' - // will immediately return the response from the cache return { params: param, response: { - result, data: { - result, + result: resData.equityNav, }, + result: resData.equityNav, + statusCode: response.status, }, } }) diff --git a/packages/sources/peregrine-fund-admin/src/transport/reserve.ts b/packages/sources/peregrine-fund-admin/src/transport/reserve.ts index dd4976a780..5a403362d1 100644 --- a/packages/sources/peregrine-fund-admin/src/transport/reserve.ts +++ b/packages/sources/peregrine-fund-admin/src/transport/reserve.ts @@ -2,8 +2,8 @@ import { HttpTransport } from '@chainlink/external-adapter-framework/transports' import { BaseEndpointTypes } from '../endpoint/reserve' export interface ResponseSchema { - assetId: number - totalValue: string + assetId: string + totalValue: number currencyBase: string accountIds: number[] updateDateTime: string @@ -20,7 +20,7 @@ export type HttpTransportTypes = BaseEndpointTypes & { } // HttpTransport is used to fetch and process data from a Provider using HTTP(S) protocol. It usually needs two methods // `prepareRequests` and `parseResponse` -export const httpTransport = new HttpTransport({ +export const httpReserveTransport = new HttpTransport({ // `prepareRequests` method receives request payloads sent to associated endpoint alongside adapter config(environment variables) // and should return 'request information' to the Data Provider. Use this method to construct one or many requests, and the framework // will send them to Data Provider @@ -31,14 +31,13 @@ export const httpTransport = new HttpTransport({ params: [param], // `request` contains any valid axios request configuration request: { - baseURL: config.API_ENDPOINT, - url: '/api/v1/reserves/', + baseURL: config.DEFAULT_BASE_URL, + url: config.DEFAULT_RESERVE_URL, headers: { X_API_KEY: config.API_KEY, }, params: { - symbol: param.base.toUpperCase(), - convert: param.quote.toUpperCase(), + assetId: param.assetId, }, }, } @@ -48,32 +47,27 @@ export const httpTransport = new HttpTransport({ // an array of response objects to be stored in cache. Use this method to construct a list of response objects for every parameter in 'params' // and the framework will save them in cache and return to user parseResponse: (params, response) => { - // In case error was received, it's a good practice to return meaningful information to user - if (!response.data) { - return params.map((param) => { + return params.map((param) => { + const resData = response.data as ResponseSchema + + if (!resData || !resData.totalValue) { return { params: param, response: { - errorMessage: `The data provider didn't return any value for ${param.base}/${param.quote}`, - statusCode: 502, + errorMessage: 'Missing totalValue in the response', + statusCode: 500, }, } - }) - } + } - // For successful responses for each 'param' a new response object is created and returned as an array - return params.map((param) => { - const result = response.data[param.base.toUpperCase()].price - // Response objects, whether successful or errors, contain two properties, 'params' and 'response'. 'response' is what will be - // stored in the cache and returned as adapter response and 'params' determines the identifier so that the next request with same 'params' - // will immediately return the response from the cache return { params: param, response: { - result, data: { - result, + result: resData.totalValue, }, + result: resData.totalValue, + statusCode: response.status, }, } }) diff --git a/packages/sources/peregrine-fund-admin/test-payload.json b/packages/sources/peregrine-fund-admin/test-payload.json deleted file mode 100644 index 7930bcfa20..0000000000 --- a/packages/sources/peregrine-fund-admin/test-payload.json +++ /dev/null @@ -1,6 +0,0 @@ -{ - "requests": [{ - "from": "BTC", - "to": "USD" - }] -} diff --git a/packages/sources/peregrine-fund-admin/test/integration/adapter.test.ts b/packages/sources/peregrine-fund-admin/test/integration/adapter.test.ts index 31d6b00153..441f2b6894 100644 --- a/packages/sources/peregrine-fund-admin/test/integration/adapter.test.ts +++ b/packages/sources/peregrine-fund-admin/test/integration/adapter.test.ts @@ -3,7 +3,7 @@ import { setEnvVariables, } from '@chainlink/external-adapter-framework/util/testing-utils' import * as nock from 'nock' -import { mockResponseSuccess } from './fixtures' +import { mockNAVResponseSuccess, mockReserveResponseSuccess } from './fixtures' describe('execute', () => { let spy: jest.SpyInstance @@ -35,12 +35,11 @@ describe('execute', () => { describe('nav endpoint', () => { it('should return success', async () => { const data = { - base: 'ETH', - quote: 'USD', + assetId: 100, endpoint: 'nav', transport: 'rest', } - mockResponseSuccess() + mockNAVResponseSuccess() const response = await testAdapter.request(data) expect(response.statusCode).toBe(200) expect(response.json()).toMatchSnapshot() @@ -50,12 +49,11 @@ describe('execute', () => { describe('reserve endpoint', () => { it('should return success', async () => { const data = { - base: 'ETH', - quote: 'USD', + assetId: 100, endpoint: 'reserve', transport: 'rest', } - mockResponseSuccess() + mockReserveResponseSuccess() const response = await testAdapter.request(data) expect(response.statusCode).toBe(200) expect(response.json()).toMatchSnapshot() diff --git a/packages/sources/peregrine-fund-admin/test/integration/fixtures.ts b/packages/sources/peregrine-fund-admin/test/integration/fixtures.ts index 3b7e58e409..f3de8e6e93 100644 --- a/packages/sources/peregrine-fund-admin/test/integration/fixtures.ts +++ b/packages/sources/peregrine-fund-admin/test/integration/fixtures.ts @@ -1,22 +1,60 @@ import nock from 'nock' -export const mockResponseSuccess = (): nock.Scope => - nock('https://dataproviderapi.com', { +export const mockNAVResponseSuccess = (): nock.Scope => + nock('https://fund-admin-data-adapter-v1-960005989691.europe-west2.run.app', { encodedQueryParams: true, }) - .get('/cryptocurrency/price') + .get('/api/v1/nav/') .query({ - symbol: 'ETH', - convert: 'USD', - }) - .reply(200, () => ({ ETH: { price: 10000 } }), [ - 'Content-Type', - 'application/json', - 'Connection', - 'close', - 'Vary', - 'Accept-Encoding', - 'Vary', - 'Origin', - ]) + assetId: 100, + }) // Endpoint to mock + .reply( + 200, + { + Id: null, + assetId: '100', + seniorNAV: '0', + juniorNav: '0', + equityNav: '4377000000000000000000', + totalLiability: '0', + totalAccounts: '100000000000000000000000', + totalCollateral: '4277000000000000000000000', + collateral: [52, 53, 54, 55, 56, 57, 58, 59, 60], + accounts: [102], + updateTimestamp: '1731670136000000000000000000', + id: null, + }, + { + // Mocked headers + 'Content-Type': 'application/json', + 'X-Custom-Header': 'CustomHeaderValue', + 'Cache-Control': 'no-cache', + }, + ) + .persist() + +export const mockReserveResponseSuccess = (): nock.Scope => + nock('https://fund-admin-data-adapter-v1-960005989691.europe-west2.run.app', { + encodedQueryParams: true, + }) + .get('/api/v1/reservers/') + .query({ + assetId: 100, + }) // Endpoint to mock + .reply( + 200, + { + assetId: 100, + totalValue: '100000000000000000000000', + currencyBase: 'USD', + accountIds: [102], + updateDateTime: '2024-11-29T15:31:29.674790480', + }, + { + // Mocked headers + 'Content-Type': 'application/json', + 'X-Custom-Header': 'CustomHeaderValue', + 'Cache-Control': 'no-cache', + }, + ) .persist() From 0b7838fe2281973f494dec28e03e7f5541037f95 Mon Sep 17 00:00:00 2001 From: Dennis O'Connell Date: Mon, 2 Dec 2024 16:09:28 -0500 Subject: [PATCH 03/14] updated PR comments --- .../peregrine-fund-admin/src/config/index.ts | 13 +------------ .../peregrine-fund-admin/src/endpoint/nav.ts | 4 ++-- .../peregrine-fund-admin/src/transport/nav.ts | 15 ++++----------- .../peregrine-fund-admin/src/transport/reserve.ts | 6 +++--- .../test/{integration => }/adapter.test.ts | 2 +- .../test/{integration => }/fixtures.ts | 10 ++-------- 6 files changed, 13 insertions(+), 37 deletions(-) rename packages/sources/peregrine-fund-admin/test/{integration => }/adapter.test.ts (96%) rename packages/sources/peregrine-fund-admin/test/{integration => }/fixtures.ts (89%) diff --git a/packages/sources/peregrine-fund-admin/src/config/index.ts b/packages/sources/peregrine-fund-admin/src/config/index.ts index 50fa385649..8537c51ffc 100644 --- a/packages/sources/peregrine-fund-admin/src/config/index.ts +++ b/packages/sources/peregrine-fund-admin/src/config/index.ts @@ -17,22 +17,11 @@ export const config = new AdapterConfig({ required: true, sensitive: true, }, - DEFAULT_BASE_URL: { + API_BASE_URL: { description: 'Base URL to Fund Admin Server Endpoint', type: 'string', default: DEFAULT_BASE_URL_VALUE, }, - DEFAULT_NAV_URL: { - description: - 'An API endpoint for the latest Net Asset Value (NAV) calculation for a given asset', - type: 'string', - default: DEFAULT_NAV_URL_VALUE, - }, - DEFAULT_RESERVE_URL: { - description: 'An API endpoint for the latest Proof of Reserve (PoR) for a given asset', - type: 'string', - default: DEFAULT_RESERVE_URL_VALUE, - }, API_NAV_ENDPOINT: { description: 'An API endpoint for the latest Net Asset Value (NAV) calculation for a given asset', diff --git a/packages/sources/peregrine-fund-admin/src/endpoint/nav.ts b/packages/sources/peregrine-fund-admin/src/endpoint/nav.ts index eb523d4513..f90aed7b04 100644 --- a/packages/sources/peregrine-fund-admin/src/endpoint/nav.ts +++ b/packages/sources/peregrine-fund-admin/src/endpoint/nav.ts @@ -1,5 +1,5 @@ import { AdapterEndpoint } from '@chainlink/external-adapter-framework/adapter' -import { navHttpTransport } from '../transport/nav' +import { httpTransport } from '../transport/nav' import { SingleNumberResultResponse } from '@chainlink/external-adapter-framework/util' import { InputParameters } from '@chainlink/external-adapter-framework/validation' import { config } from '../config' @@ -23,7 +23,7 @@ export const endpoint = new AdapterEndpoint({ // Endpoint name name: 'nav', // Transport handles incoming requests, data processing and communication for this endpoint - transport: navHttpTransport, + transport: httpTransport, // Supported input parameters for this endpoint inputParameters, }) diff --git a/packages/sources/peregrine-fund-admin/src/transport/nav.ts b/packages/sources/peregrine-fund-admin/src/transport/nav.ts index bab9075d63..b38f09beb6 100644 --- a/packages/sources/peregrine-fund-admin/src/transport/nav.ts +++ b/packages/sources/peregrine-fund-admin/src/transport/nav.ts @@ -16,13 +16,6 @@ export interface ResponseSchema { id: string | null } -// export type HttpTransportTypes = BaseEndpointTypes & { -// Provider: { -// RequestBody: never -// ResponseBody: ResponseSchema -// } -// } - export type HttpTransportTypes = BaseEndpointTypes & { Provider: { RequestBody: never @@ -32,7 +25,7 @@ export type HttpTransportTypes = BaseEndpointTypes & { // HttpTransport is used to fetch and process data from a Provider using HTTP(S) protocol. It usually needs two methods // `prepareRequests` and `parseResponse` -export const navHttpTransport = new HttpTransport({ +export const httpTransport = new HttpTransport({ // `prepareRequests` method receives request payloads sent to associated endpoint alongside adapter config(environment variables) // and should return 'request information' to the Data Provider. Use this method to construct one or many requests, and the framework // will send them to Data Provider @@ -43,8 +36,8 @@ export const navHttpTransport = new HttpTransport({ params: [param], // `request` contains any valid axios request configuration request: { - baseURL: config.DEFAULT_BASE_URL, - url: config.DEFAULT_NAV_URL, + baseURL: config.API_BASE_URL, + url: config.API_NAV_ENDPOINT, headers: { X_API_KEY: config.API_KEY, }, @@ -67,7 +60,7 @@ export const navHttpTransport = new HttpTransport({ params: param, response: { errorMessage: 'Missing equityNav in the response', - statusCode: 500, + statusCode: 502, }, } } diff --git a/packages/sources/peregrine-fund-admin/src/transport/reserve.ts b/packages/sources/peregrine-fund-admin/src/transport/reserve.ts index 5a403362d1..c0bc84ede4 100644 --- a/packages/sources/peregrine-fund-admin/src/transport/reserve.ts +++ b/packages/sources/peregrine-fund-admin/src/transport/reserve.ts @@ -20,7 +20,7 @@ export type HttpTransportTypes = BaseEndpointTypes & { } // HttpTransport is used to fetch and process data from a Provider using HTTP(S) protocol. It usually needs two methods // `prepareRequests` and `parseResponse` -export const httpReserveTransport = new HttpTransport({ +export const httpTransport = new HttpTransport({ // `prepareRequests` method receives request payloads sent to associated endpoint alongside adapter config(environment variables) // and should return 'request information' to the Data Provider. Use this method to construct one or many requests, and the framework // will send them to Data Provider @@ -31,8 +31,8 @@ export const httpReserveTransport = new HttpTransport({ params: [param], // `request` contains any valid axios request configuration request: { - baseURL: config.DEFAULT_BASE_URL, - url: config.DEFAULT_RESERVE_URL, + baseURL: config.API_BASE_URL, + url: config.API_RESERVE_ENDPOINT, headers: { X_API_KEY: config.API_KEY, }, diff --git a/packages/sources/peregrine-fund-admin/test/integration/adapter.test.ts b/packages/sources/peregrine-fund-admin/test/adapter.test.ts similarity index 96% rename from packages/sources/peregrine-fund-admin/test/integration/adapter.test.ts rename to packages/sources/peregrine-fund-admin/test/adapter.test.ts index 441f2b6894..f32e6b1fc7 100644 --- a/packages/sources/peregrine-fund-admin/test/integration/adapter.test.ts +++ b/packages/sources/peregrine-fund-admin/test/adapter.test.ts @@ -17,7 +17,7 @@ describe('execute', () => { const mockDate = new Date('2001-01-01T11:11:11.111Z') spy = jest.spyOn(Date, 'now').mockReturnValue(mockDate.getTime()) - const adapter = (await import('./../../src')).adapter + const adapter = (await import('../src')).adapter adapter.rateLimiting = undefined testAdapter = await TestAdapter.startWithMockedCache(adapter, { testAdapter: {} as TestAdapter, diff --git a/packages/sources/peregrine-fund-admin/test/integration/fixtures.ts b/packages/sources/peregrine-fund-admin/test/fixtures.ts similarity index 89% rename from packages/sources/peregrine-fund-admin/test/integration/fixtures.ts rename to packages/sources/peregrine-fund-admin/test/fixtures.ts index f3de8e6e93..b5f186c9c8 100644 --- a/packages/sources/peregrine-fund-admin/test/integration/fixtures.ts +++ b/packages/sources/peregrine-fund-admin/test/fixtures.ts @@ -4,10 +4,7 @@ export const mockNAVResponseSuccess = (): nock.Scope => nock('https://fund-admin-data-adapter-v1-960005989691.europe-west2.run.app', { encodedQueryParams: true, }) - .get('/api/v1/nav/') - .query({ - assetId: 100, - }) // Endpoint to mock + .get('/api/v1/nav/100') .reply( 200, { @@ -37,10 +34,7 @@ export const mockReserveResponseSuccess = (): nock.Scope => nock('https://fund-admin-data-adapter-v1-960005989691.europe-west2.run.app', { encodedQueryParams: true, }) - .get('/api/v1/reservers/') - .query({ - assetId: 100, - }) // Endpoint to mock + .get('/api/v1/reservers/100') .reply( 200, { From 3a6029346ed2104cfd1bf08904d06d5d90ebaf21 Mon Sep 17 00:00:00 2001 From: Dennis O'Connell Date: Mon, 2 Dec 2024 16:23:38 -0500 Subject: [PATCH 04/14] updated PR --- .../peregrine-fund-admin/src/endpoint/nav.ts | 19 ++++++++++++++++--- .../src/endpoint/reserve.ts | 14 +++++++++++--- .../peregrine-fund-admin/src/transport/nav.ts | 16 +--------------- .../src/transport/reserve.ts | 11 ++--------- .../peregrine-fund-admin/test/fixtures.ts | 2 +- 5 files changed, 31 insertions(+), 31 deletions(-) diff --git a/packages/sources/peregrine-fund-admin/src/endpoint/nav.ts b/packages/sources/peregrine-fund-admin/src/endpoint/nav.ts index f90aed7b04..db1f578ad9 100644 --- a/packages/sources/peregrine-fund-admin/src/endpoint/nav.ts +++ b/packages/sources/peregrine-fund-admin/src/endpoint/nav.ts @@ -12,10 +12,25 @@ export const inputParameters = new InputParameters({ }, }) +export interface ResponseSchema { + Id: string | null + assetId: string + seniorNAV: number + juniorNav: number + equityNav: number + totalLiability: number + totalAccounts: string + totalCollateral: number + collateral: number[] + accounts: number[] + updateTimestamp: string + id: string | null +} + // Endpoints contain a type parameter that allows specifying relevant types of an endpoint, for example, request payload type, Adapter response type and Adapter configuration (environment variables) type export type BaseEndpointTypes = { Parameters: typeof inputParameters.definition - Response: SingleNumberResultResponse + Response: ResponseSchema Settings: typeof config.settings } @@ -24,6 +39,4 @@ export const endpoint = new AdapterEndpoint({ name: 'nav', // Transport handles incoming requests, data processing and communication for this endpoint transport: httpTransport, - // Supported input parameters for this endpoint - inputParameters, }) diff --git a/packages/sources/peregrine-fund-admin/src/endpoint/reserve.ts b/packages/sources/peregrine-fund-admin/src/endpoint/reserve.ts index d447610b63..2500a0e297 100644 --- a/packages/sources/peregrine-fund-admin/src/endpoint/reserve.ts +++ b/packages/sources/peregrine-fund-admin/src/endpoint/reserve.ts @@ -1,5 +1,5 @@ import { AdapterEndpoint } from '@chainlink/external-adapter-framework/adapter' -import { httpReserveTransport } from '../transport/reserve' +import { httpTransport } from '../transport/reserve' import { SingleNumberResultResponse } from '@chainlink/external-adapter-framework/util' import { InputParameters } from '@chainlink/external-adapter-framework/validation' import { config } from '../config' @@ -12,10 +12,18 @@ export const inputParameters = new InputParameters({ }, }) +export interface ResponseSchema { + assetId: string + totalValue: number + currencyBase: string + accountIds: number[] + updateDateTime: string +} + // Endpoints contain a type parameter that allows specifying relevant types of an endpoint, for example, request payload type, Adapter response type and Adapter configuration (environment variables) type export type BaseEndpointTypes = { Parameters: typeof inputParameters.definition - Response: SingleNumberResultResponse + Response: typeof ResponseSchema.class Settings: typeof config.settings } @@ -25,5 +33,5 @@ export const endpoint = new AdapterEndpoint({ // Supported input parameters for this endpoint inputParameters, // Transport handles incoming requests, data processing and communication for this endpoint - transport: httpReserveTransport, + transport: httpTransport, }) diff --git a/packages/sources/peregrine-fund-admin/src/transport/nav.ts b/packages/sources/peregrine-fund-admin/src/transport/nav.ts index b38f09beb6..e47da66c03 100644 --- a/packages/sources/peregrine-fund-admin/src/transport/nav.ts +++ b/packages/sources/peregrine-fund-admin/src/transport/nav.ts @@ -1,20 +1,6 @@ import { HttpTransport } from '@chainlink/external-adapter-framework/transports' import { BaseEndpointTypes } from '../endpoint/nav' - -export interface ResponseSchema { - Id: string | null - assetId: string - seniorNAV: number - juniorNav: number - equityNav: number - totalLiability: number - totalAccounts: string - totalCollateral: number - collateral: number[] - accounts: number[] - updateTimestamp: string - id: string | null -} +import { ResponseSchema } from '../endpoint/nav' export type HttpTransportTypes = BaseEndpointTypes & { Provider: { diff --git a/packages/sources/peregrine-fund-admin/src/transport/reserve.ts b/packages/sources/peregrine-fund-admin/src/transport/reserve.ts index c0bc84ede4..6cd9eae6e6 100644 --- a/packages/sources/peregrine-fund-admin/src/transport/reserve.ts +++ b/packages/sources/peregrine-fund-admin/src/transport/reserve.ts @@ -1,13 +1,6 @@ import { HttpTransport } from '@chainlink/external-adapter-framework/transports' import { BaseEndpointTypes } from '../endpoint/reserve' - -export interface ResponseSchema { - assetId: string - totalValue: number - currencyBase: string - accountIds: number[] - updateDateTime: string -} +import { ResponseSchema } from '../endpoint/reserve' // HttpTransport extends base types from endpoint and adds additional, Provider-specific types like 'RequestBody', which is the type of // request body (not the request to adapter, but the request that adapter sends to Data Provider), and 'ResponseBody' which is @@ -55,7 +48,7 @@ export const httpTransport = new HttpTransport({ params: param, response: { errorMessage: 'Missing totalValue in the response', - statusCode: 500, + statusCode: 502, }, } } diff --git a/packages/sources/peregrine-fund-admin/test/fixtures.ts b/packages/sources/peregrine-fund-admin/test/fixtures.ts index b5f186c9c8..373ee6351d 100644 --- a/packages/sources/peregrine-fund-admin/test/fixtures.ts +++ b/packages/sources/peregrine-fund-admin/test/fixtures.ts @@ -34,7 +34,7 @@ export const mockReserveResponseSuccess = (): nock.Scope => nock('https://fund-admin-data-adapter-v1-960005989691.europe-west2.run.app', { encodedQueryParams: true, }) - .get('/api/v1/reservers/100') + .get('/api/v1/reserves/100') .reply( 200, { From 199a8a3547af334bde4ac3200e850cdb759b7321 Mon Sep 17 00:00:00 2001 From: Dennis O'Connell Date: Mon, 2 Dec 2024 16:39:32 -0500 Subject: [PATCH 05/14] update response and parse --- .../peregrine-fund-admin/src/config/index.ts | 9 ++------ .../peregrine-fund-admin/src/endpoint/nav.ts | 3 ++- .../src/endpoint/reserve.ts | 11 ++++++++-- .../peregrine-fund-admin/src/transport/nav.ts | 22 +++++++++++-------- .../src/transport/reserve.ts | 22 ++++++++++--------- 5 files changed, 38 insertions(+), 29 deletions(-) diff --git a/packages/sources/peregrine-fund-admin/src/config/index.ts b/packages/sources/peregrine-fund-admin/src/config/index.ts index 8537c51ffc..695469b1de 100644 --- a/packages/sources/peregrine-fund-admin/src/config/index.ts +++ b/packages/sources/peregrine-fund-admin/src/config/index.ts @@ -1,10 +1,5 @@ import { AdapterConfig } from '@chainlink/external-adapter-framework/config' -export const NAME = 'PEREGRINE_FUND_ADMIN' -export const DEFAULT_API_VERSION = 'api/v1' -export const DEFAULT_NAV_ENDPOINT = 'nav' -export const DEFAULT_RESERVE_ENDPOINT = 'reserve' -export const DEFAULT_PARAM_ASSET_ID = 100 export const DEFAULT_BASE_URL_VALUE = 'https://fund-admin-data-adapter-v1-960005989691.europe-west2.run.app' export const DEFAULT_NAV_URL_VALUE = '/api/v1/nav/' @@ -26,11 +21,11 @@ export const config = new AdapterConfig({ description: 'An API endpoint for the latest Net Asset Value (NAV) calculation for a given asset', type: 'string', - default: `${DEFAULT_BASE_URL_VALUE}/${DEFAULT_API_VERSION}/${DEFAULT_NAV_ENDPOINT}/${DEFAULT_PARAM_ASSET_ID}/`, + default: DEFAULT_NAV_URL_VALUE, }, API_RESERVE_ENDPOINT: { description: 'API Endpoint to get the latest Proof of Reserves for a given asset', type: 'string', - default: `${DEFAULT_BASE_URL_VALUE}/${DEFAULT_API_VERSION}/${DEFAULT_RESERVE_ENDPOINT}/${DEFAULT_PARAM_ASSET_ID}/`, + default: DEFAULT_RESERVE_URL_VALUE, }, }) diff --git a/packages/sources/peregrine-fund-admin/src/endpoint/nav.ts b/packages/sources/peregrine-fund-admin/src/endpoint/nav.ts index db1f578ad9..f6d7a3e4b3 100644 --- a/packages/sources/peregrine-fund-admin/src/endpoint/nav.ts +++ b/packages/sources/peregrine-fund-admin/src/endpoint/nav.ts @@ -1,6 +1,5 @@ import { AdapterEndpoint } from '@chainlink/external-adapter-framework/adapter' import { httpTransport } from '../transport/nav' -import { SingleNumberResultResponse } from '@chainlink/external-adapter-framework/util' import { InputParameters } from '@chainlink/external-adapter-framework/validation' import { config } from '../config' @@ -39,4 +38,6 @@ export const endpoint = new AdapterEndpoint({ name: 'nav', // Transport handles incoming requests, data processing and communication for this endpoint transport: httpTransport, + //input params, + inputParameters, }) diff --git a/packages/sources/peregrine-fund-admin/src/endpoint/reserve.ts b/packages/sources/peregrine-fund-admin/src/endpoint/reserve.ts index 2500a0e297..5a86adc28b 100644 --- a/packages/sources/peregrine-fund-admin/src/endpoint/reserve.ts +++ b/packages/sources/peregrine-fund-admin/src/endpoint/reserve.ts @@ -1,6 +1,5 @@ import { AdapterEndpoint } from '@chainlink/external-adapter-framework/adapter' import { httpTransport } from '../transport/reserve' -import { SingleNumberResultResponse } from '@chainlink/external-adapter-framework/util' import { InputParameters } from '@chainlink/external-adapter-framework/validation' import { config } from '../config' @@ -23,7 +22,13 @@ export interface ResponseSchema { // Endpoints contain a type parameter that allows specifying relevant types of an endpoint, for example, request payload type, Adapter response type and Adapter configuration (environment variables) type export type BaseEndpointTypes = { Parameters: typeof inputParameters.definition - Response: typeof ResponseSchema.class + Response: { + Result: number + Data: { + totalValue: number + updateDateTime: string + } + } Settings: typeof config.settings } @@ -34,4 +39,6 @@ export const endpoint = new AdapterEndpoint({ inputParameters, // Transport handles incoming requests, data processing and communication for this endpoint transport: httpTransport, + // Input params + inputParameters, }) diff --git a/packages/sources/peregrine-fund-admin/src/transport/nav.ts b/packages/sources/peregrine-fund-admin/src/transport/nav.ts index e47da66c03..699384be04 100644 --- a/packages/sources/peregrine-fund-admin/src/transport/nav.ts +++ b/packages/sources/peregrine-fund-admin/src/transport/nav.ts @@ -1,11 +1,18 @@ import { HttpTransport } from '@chainlink/external-adapter-framework/transports' import { BaseEndpointTypes } from '../endpoint/nav' -import { ResponseSchema } from '../endpoint/nav' export type HttpTransportTypes = BaseEndpointTypes & { Provider: { RequestBody: never - ResponseBody: ResponseSchema + ResponseBody: { + Result: number + Data: { + seniorNAV: number + juniorNav: number + equityNav: number + updateDateTime: string + } + } } } @@ -39,9 +46,9 @@ export const httpTransport = new HttpTransport({ // and the framework will save them in cache and return to user parseResponse: (params, response) => { return params.map((param) => { - const resData = response.data as ResponseSchema + const resData = response.data - if (!resData || !resData.equityNav) { + if (!resData || !resData.Data) { return { params: param, response: { @@ -50,14 +57,11 @@ export const httpTransport = new HttpTransport({ }, } } - return { params: param, response: { - data: { - result: resData.equityNav, - }, - result: resData.equityNav, + data: resData.Data, + result: resData.Result, statusCode: response.status, }, } diff --git a/packages/sources/peregrine-fund-admin/src/transport/reserve.ts b/packages/sources/peregrine-fund-admin/src/transport/reserve.ts index 6cd9eae6e6..6da54b4d0c 100644 --- a/packages/sources/peregrine-fund-admin/src/transport/reserve.ts +++ b/packages/sources/peregrine-fund-admin/src/transport/reserve.ts @@ -1,6 +1,5 @@ import { HttpTransport } from '@chainlink/external-adapter-framework/transports' import { BaseEndpointTypes } from '../endpoint/reserve' -import { ResponseSchema } from '../endpoint/reserve' // HttpTransport extends base types from endpoint and adds additional, Provider-specific types like 'RequestBody', which is the type of // request body (not the request to adapter, but the request that adapter sends to Data Provider), and 'ResponseBody' which is @@ -8,7 +7,13 @@ import { ResponseSchema } from '../endpoint/reserve' export type HttpTransportTypes = BaseEndpointTypes & { Provider: { RequestBody: never - ResponseBody: ResponseSchema + ResponseBody: { + Result: number + Data: { + totalValue: number + updateDateTime: string + } + } } } // HttpTransport is used to fetch and process data from a Provider using HTTP(S) protocol. It usually needs two methods @@ -41,25 +46,22 @@ export const httpTransport = new HttpTransport({ // and the framework will save them in cache and return to user parseResponse: (params, response) => { return params.map((param) => { - const resData = response.data as ResponseSchema + const resData = response.data - if (!resData || !resData.totalValue) { + if (!resData || !resData.Data) { return { params: param, response: { - errorMessage: 'Missing totalValue in the response', + errorMessage: 'Missing equityNav in the response', statusCode: 502, }, } } - return { params: param, response: { - data: { - result: resData.totalValue, - }, - result: resData.totalValue, + data: resData.Data, + result: resData.Result, statusCode: response.status, }, } From 82605d88bcdbaec99ac232839ed393cca116037e Mon Sep 17 00:00:00 2001 From: Dennis O'Connell Date: Mon, 2 Dec 2024 17:15:14 -0500 Subject: [PATCH 06/14] updated transport parse --- .../peregrine-fund-admin/src/endpoint/nav.ts | 3 +- .../src/endpoint/reserve.ts | 20 ++----------- .../peregrine-fund-admin/src/transport/nav.ts | 30 +++++++++++-------- .../src/transport/reserve.ts | 24 ++++++++------- 4 files changed, 36 insertions(+), 41 deletions(-) diff --git a/packages/sources/peregrine-fund-admin/src/endpoint/nav.ts b/packages/sources/peregrine-fund-admin/src/endpoint/nav.ts index f6d7a3e4b3..4246cc8d53 100644 --- a/packages/sources/peregrine-fund-admin/src/endpoint/nav.ts +++ b/packages/sources/peregrine-fund-admin/src/endpoint/nav.ts @@ -1,6 +1,7 @@ import { AdapterEndpoint } from '@chainlink/external-adapter-framework/adapter' import { httpTransport } from '../transport/nav' import { InputParameters } from '@chainlink/external-adapter-framework/validation' +import { SingleNumberResultResponse } from '@chainlink/external-adapter-framework/util' import { config } from '../config' export const inputParameters = new InputParameters({ @@ -29,7 +30,7 @@ export interface ResponseSchema { // Endpoints contain a type parameter that allows specifying relevant types of an endpoint, for example, request payload type, Adapter response type and Adapter configuration (environment variables) type export type BaseEndpointTypes = { Parameters: typeof inputParameters.definition - Response: ResponseSchema + Response: SingleNumberResultResponse Settings: typeof config.settings } diff --git a/packages/sources/peregrine-fund-admin/src/endpoint/reserve.ts b/packages/sources/peregrine-fund-admin/src/endpoint/reserve.ts index 5a86adc28b..88c9eae4b6 100644 --- a/packages/sources/peregrine-fund-admin/src/endpoint/reserve.ts +++ b/packages/sources/peregrine-fund-admin/src/endpoint/reserve.ts @@ -1,6 +1,7 @@ import { AdapterEndpoint } from '@chainlink/external-adapter-framework/adapter' import { httpTransport } from '../transport/reserve' import { InputParameters } from '@chainlink/external-adapter-framework/validation' +import { SingleNumberResultResponse } from '@chainlink/external-adapter-framework/util' import { config } from '../config' export const inputParameters = new InputParameters({ @@ -11,24 +12,9 @@ export const inputParameters = new InputParameters({ }, }) -export interface ResponseSchema { - assetId: string - totalValue: number - currencyBase: string - accountIds: number[] - updateDateTime: string -} - -// Endpoints contain a type parameter that allows specifying relevant types of an endpoint, for example, request payload type, Adapter response type and Adapter configuration (environment variables) type export type BaseEndpointTypes = { Parameters: typeof inputParameters.definition - Response: { - Result: number - Data: { - totalValue: number - updateDateTime: string - } - } + Response: SingleNumberResultResponse Settings: typeof config.settings } @@ -39,6 +25,4 @@ export const endpoint = new AdapterEndpoint({ inputParameters, // Transport handles incoming requests, data processing and communication for this endpoint transport: httpTransport, - // Input params - inputParameters, }) diff --git a/packages/sources/peregrine-fund-admin/src/transport/nav.ts b/packages/sources/peregrine-fund-admin/src/transport/nav.ts index 699384be04..de46ade56a 100644 --- a/packages/sources/peregrine-fund-admin/src/transport/nav.ts +++ b/packages/sources/peregrine-fund-admin/src/transport/nav.ts @@ -1,18 +1,20 @@ import { HttpTransport } from '@chainlink/external-adapter-framework/transports' import { BaseEndpointTypes } from '../endpoint/nav' +export interface ResponseSchema { + Result: number + Data: { + equityNav: number + seniorNAV: number + juniorNav: number + providerIndicatedTimeUnixMs: undefined + } +} + export type HttpTransportTypes = BaseEndpointTypes & { Provider: { RequestBody: never - ResponseBody: { - Result: number - Data: { - seniorNAV: number - juniorNav: number - equityNav: number - updateDateTime: string - } - } + ResponseBody: ResponseSchema } } @@ -60,9 +62,13 @@ export const httpTransport = new HttpTransport({ return { params: param, response: { - data: resData.Data, - result: resData.Result, - statusCode: response.status, + Result: 200, + Data: { + equityNav: resData.Data.equityNav, + seniorNAV: resData.Data.seniorNAV, + juniorNav: resData.Data.juniorNav, + providerIndicatedTimeUnixMs: Date.now(), + }, }, } }) diff --git a/packages/sources/peregrine-fund-admin/src/transport/reserve.ts b/packages/sources/peregrine-fund-admin/src/transport/reserve.ts index 6da54b4d0c..3108518b92 100644 --- a/packages/sources/peregrine-fund-admin/src/transport/reserve.ts +++ b/packages/sources/peregrine-fund-admin/src/transport/reserve.ts @@ -1,19 +1,21 @@ import { HttpTransport } from '@chainlink/external-adapter-framework/transports' import { BaseEndpointTypes } from '../endpoint/reserve' +export interface ResponseSchema { + Result: number + Data: { + totalValue: number + providerIndicatedTimeUnixMs: undefined + } +} + // HttpTransport extends base types from endpoint and adds additional, Provider-specific types like 'RequestBody', which is the type of // request body (not the request to adapter, but the request that adapter sends to Data Provider), and 'ResponseBody' which is // the type of raw response from Data Provider export type HttpTransportTypes = BaseEndpointTypes & { Provider: { RequestBody: never - ResponseBody: { - Result: number - Data: { - totalValue: number - updateDateTime: string - } - } + ResponseBody: ResponseSchema } } // HttpTransport is used to fetch and process data from a Provider using HTTP(S) protocol. It usually needs two methods @@ -60,9 +62,11 @@ export const httpTransport = new HttpTransport({ return { params: param, response: { - data: resData.Data, - result: resData.Result, - statusCode: response.status, + Result: 200, + Data: { + totalValue: resData.Data.totalValue, + providerIndicatedTimeUnixMs: Date.now(), + }, }, } }) From 6898571def8d541f7395e59b3df959971804e32a Mon Sep 17 00:00:00 2001 From: Dennis O'Connell Date: Mon, 2 Dec 2024 17:18:55 -0500 Subject: [PATCH 07/14] updated prepareresponse with injected params into string --- packages/sources/peregrine-fund-admin/src/transport/nav.ts | 2 +- packages/sources/peregrine-fund-admin/src/transport/reserve.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/sources/peregrine-fund-admin/src/transport/nav.ts b/packages/sources/peregrine-fund-admin/src/transport/nav.ts index de46ade56a..7e3a12bacd 100644 --- a/packages/sources/peregrine-fund-admin/src/transport/nav.ts +++ b/packages/sources/peregrine-fund-admin/src/transport/nav.ts @@ -32,7 +32,7 @@ export const httpTransport = new HttpTransport({ // `request` contains any valid axios request configuration request: { baseURL: config.API_BASE_URL, - url: config.API_NAV_ENDPOINT, + url: `${config.API_NAV_ENDPOINT}${param}`, headers: { X_API_KEY: config.API_KEY, }, diff --git a/packages/sources/peregrine-fund-admin/src/transport/reserve.ts b/packages/sources/peregrine-fund-admin/src/transport/reserve.ts index 3108518b92..5cca8490ae 100644 --- a/packages/sources/peregrine-fund-admin/src/transport/reserve.ts +++ b/packages/sources/peregrine-fund-admin/src/transport/reserve.ts @@ -32,7 +32,7 @@ export const httpTransport = new HttpTransport({ // `request` contains any valid axios request configuration request: { baseURL: config.API_BASE_URL, - url: config.API_RESERVE_ENDPOINT, + url: `${config.API_RESERVE_ENDPOINT}${param}`, headers: { X_API_KEY: config.API_KEY, }, From 3cecc244966e5841fc2ef28ba300ea0bbb21b48b Mon Sep 17 00:00:00 2001 From: Dennis O'Connell Date: Mon, 2 Dec 2024 19:24:52 -0500 Subject: [PATCH 08/14] ran yarn changeset --- .changeset/hip-cows-serve.md | 5 ++ .pnp.cjs | 20 ++++++ .../src/config/index.d.ts | 29 +++++++++ .../src/config/index.d.ts.map | 1 + .../peregrine-fund-admin/src/config/index.js | 37 +++++++++++ .../peregrine-fund-admin/src/endpoint/nav.ts | 19 ++++-- .../src/endpoint/reserve.ts | 19 ++++-- .../peregrine-fund-admin/src/transport/nav.ts | 62 +++++++++++-------- .../src/transport/reserve.ts | 55 +++++++++------- .../test/{ => integration}/adapter.test.ts | 2 +- .../test/{ => integration}/fixtures.ts | 0 .../peregrine-fund-admin/tsconfig.json | 1 + packages/tsconfig.json | 9 ++- packages/tsconfig.test.json | 9 ++- yarn.lock | 13 ++++ 15 files changed, 214 insertions(+), 67 deletions(-) create mode 100644 .changeset/hip-cows-serve.md create mode 100644 packages/sources/peregrine-fund-admin/src/config/index.d.ts create mode 100644 packages/sources/peregrine-fund-admin/src/config/index.d.ts.map create mode 100644 packages/sources/peregrine-fund-admin/src/config/index.js rename packages/sources/peregrine-fund-admin/test/{ => integration}/adapter.test.ts (96%) rename packages/sources/peregrine-fund-admin/test/{ => integration}/fixtures.ts (100%) diff --git a/.changeset/hip-cows-serve.md b/.changeset/hip-cows-serve.md new file mode 100644 index 0000000000..9e94e59f21 --- /dev/null +++ b/.changeset/hip-cows-serve.md @@ -0,0 +1,5 @@ +--- +'@chainlink/peregrine-fund-admin-adapter': patch +--- + +Peregrine Fund Admin Init diff --git a/.pnp.cjs b/.pnp.cjs index b2bd4d2bbd..d7efcf28b6 100755 --- a/.pnp.cjs +++ b/.pnp.cjs @@ -726,6 +726,10 @@ const RAW_RUNTIME_STATE = "name": "@chainlink/paypal-adapter",\ "reference": "workspace:packages/sources/paypal"\ },\ + {\ + "name": "@chainlink/peregrine-fund-admin-adapter",\ + "reference": "workspace:packages/sources/peregrine-fund-admin"\ + },\ {\ "name": "@chainlink/polkadot-balance-adapter",\ "reference": "workspace:packages/sources/polkadot-balance"\ @@ -1108,6 +1112,7 @@ const RAW_RUNTIME_STATE = ["@chainlink/outlier-detection-adapter", ["workspace:packages/composites/outlier-detection"]],\ ["@chainlink/paxos-adapter", ["workspace:packages/sources/paxos"]],\ ["@chainlink/paypal-adapter", ["workspace:packages/sources/paypal"]],\ + ["@chainlink/peregrine-fund-admin-adapter", ["workspace:packages/sources/peregrine-fund-admin"]],\ ["@chainlink/polkadot-balance-adapter", ["workspace:packages/sources/polkadot-balance"]],\ ["@chainlink/polygon-adapter", ["workspace:packages/sources/polygon"]],\ ["@chainlink/por-address-list-adapter", ["workspace:packages/sources/por-address-list"]],\ @@ -8291,6 +8296,21 @@ const RAW_RUNTIME_STATE = "linkType": "SOFT"\ }]\ ]],\ + ["@chainlink/peregrine-fund-admin-adapter", [\ + ["workspace:packages/sources/peregrine-fund-admin", {\ + "packageLocation": "./packages/sources/peregrine-fund-admin/",\ + "packageDependencies": [\ + ["@chainlink/peregrine-fund-admin-adapter", "workspace:packages/sources/peregrine-fund-admin"],\ + ["@chainlink/external-adapter-framework", "npm:1.7.1"],\ + ["@types/jest", "npm:27.5.2"],\ + ["@types/node", "npm:16.18.115"],\ + ["nock", "npm:13.5.4"],\ + ["tslib", "npm:2.4.1"],\ + ["typescript", "patch:typescript@npm%3A5.6.3#optional!builtin::version=5.6.3&hash=8c6c40"]\ + ],\ + "linkType": "SOFT"\ + }]\ + ]],\ ["@chainlink/polkadot-balance-adapter", [\ ["workspace:packages/sources/polkadot-balance", {\ "packageLocation": "./packages/sources/polkadot-balance/",\ diff --git a/packages/sources/peregrine-fund-admin/src/config/index.d.ts b/packages/sources/peregrine-fund-admin/src/config/index.d.ts new file mode 100644 index 0000000000..518dde472c --- /dev/null +++ b/packages/sources/peregrine-fund-admin/src/config/index.d.ts @@ -0,0 +1,29 @@ +import { AdapterConfig } from '@chainlink/external-adapter-framework/config' +export declare const DEFAULT_BASE_URL_VALUE = + 'https://fund-admin-data-adapter-v1-960005989691.europe-west2.run.app' +export declare const DEFAULT_NAV_URL_VALUE = '/api/v1/nav/' +export declare const DEFAULT_RESERVE_URL_VALUE = '/api/v1/reserve/' +export declare const config: AdapterConfig<{ + API_KEY: { + description: string + type: 'string' + required: true + sensitive: true + } + API_BASE_URL: { + description: string + type: 'string' + default: string + } + API_NAV_ENDPOINT: { + description: string + type: 'string' + default: string + } + API_RESERVE_ENDPOINT: { + description: string + type: 'string' + default: string + } +}> +//# sourceMappingURL=index.d.ts.map diff --git a/packages/sources/peregrine-fund-admin/src/config/index.d.ts.map b/packages/sources/peregrine-fund-admin/src/config/index.d.ts.map new file mode 100644 index 0000000000..89cfa23ea5 --- /dev/null +++ b/packages/sources/peregrine-fund-admin/src/config/index.d.ts.map @@ -0,0 +1 @@ +{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAAE,MAAM,8CAA8C,CAAA;AAE5E,eAAO,MAAM,sBAAsB,yEACqC,CAAA;AACxE,eAAO,MAAM,qBAAqB,iBAAiB,CAAA;AACnD,eAAO,MAAM,yBAAyB,qBAAqB,CAAA;AAE3D,eAAO,MAAM,MAAM;;;;;;;;;;;;;;;;;;;;;;EAuBjB,CAAA"} \ No newline at end of file diff --git a/packages/sources/peregrine-fund-admin/src/config/index.js b/packages/sources/peregrine-fund-admin/src/config/index.js new file mode 100644 index 0000000000..00511cd962 --- /dev/null +++ b/packages/sources/peregrine-fund-admin/src/config/index.js @@ -0,0 +1,37 @@ +'use strict' +Object.defineProperty(exports, '__esModule', { value: true }) +exports.config = + exports.DEFAULT_RESERVE_URL_VALUE = + exports.DEFAULT_NAV_URL_VALUE = + exports.DEFAULT_BASE_URL_VALUE = + void 0 +const config_1 = require('@chainlink/external-adapter-framework/config') +exports.DEFAULT_BASE_URL_VALUE = + 'https://fund-admin-data-adapter-v1-960005989691.europe-west2.run.app' +exports.DEFAULT_NAV_URL_VALUE = '/api/v1/nav/' +exports.DEFAULT_RESERVE_URL_VALUE = '/api/v1/reserve/' +exports.config = new config_1.AdapterConfig({ + API_KEY: { + description: 'An API key for Data Provider', + type: 'string', + required: true, + sensitive: true, + }, + API_BASE_URL: { + description: 'Base URL to Fund Admin Server Endpoint', + type: 'string', + default: exports.DEFAULT_BASE_URL_VALUE, + }, + API_NAV_ENDPOINT: { + description: + 'An API endpoint for the latest Net Asset Value (NAV) calculation for a given asset', + type: 'string', + default: exports.DEFAULT_NAV_URL_VALUE, + }, + API_RESERVE_ENDPOINT: { + description: 'API Endpoint to get the latest Proof of Reserves for a given asset', + type: 'string', + default: exports.DEFAULT_RESERVE_URL_VALUE, + }, +}) +//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiaW5kZXguanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyJpbmRleC50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiOzs7QUFBQSx5RUFBNEU7QUFFL0QsUUFBQSxzQkFBc0IsR0FDakMsc0VBQXNFLENBQUE7QUFDM0QsUUFBQSxxQkFBcUIsR0FBRyxjQUFjLENBQUE7QUFDdEMsUUFBQSx5QkFBeUIsR0FBRyxrQkFBa0IsQ0FBQTtBQUU5QyxRQUFBLE1BQU0sR0FBRyxJQUFJLHNCQUFhLENBQUM7SUFDdEMsT0FBTyxFQUFFO1FBQ1AsV0FBVyxFQUFFLDhCQUE4QjtRQUMzQyxJQUFJLEVBQUUsUUFBUTtRQUNkLFFBQVEsRUFBRSxJQUFJO1FBQ2QsU0FBUyxFQUFFLElBQUk7S0FDaEI7SUFDRCxZQUFZLEVBQUU7UUFDWixXQUFXLEVBQUUsd0NBQXdDO1FBQ3JELElBQUksRUFBRSxRQUFRO1FBQ2QsT0FBTyxFQUFFLDhCQUFzQjtLQUNoQztJQUNELGdCQUFnQixFQUFFO1FBQ2hCLFdBQVcsRUFDVCxvRkFBb0Y7UUFDdEYsSUFBSSxFQUFFLFFBQVE7UUFDZCxPQUFPLEVBQUUsNkJBQXFCO0tBQy9CO0lBQ0Qsb0JBQW9CLEVBQUU7UUFDcEIsV0FBVyxFQUFFLG9FQUFvRTtRQUNqRixJQUFJLEVBQUUsUUFBUTtRQUNkLE9BQU8sRUFBRSxpQ0FBeUI7S0FDbkM7Q0FDRixDQUFDLENBQUEiLCJzb3VyY2VzQ29udGVudCI6WyJpbXBvcnQgeyBBZGFwdGVyQ29uZmlnIH0gZnJvbSAnQGNoYWlubGluay9leHRlcm5hbC1hZGFwdGVyLWZyYW1ld29yay9jb25maWcnXG5cbmV4cG9ydCBjb25zdCBERUZBVUxUX0JBU0VfVVJMX1ZBTFVFID1cbiAgJ2h0dHBzOi8vZnVuZC1hZG1pbi1kYXRhLWFkYXB0ZXItdjEtOTYwMDA1OTg5NjkxLmV1cm9wZS13ZXN0Mi5ydW4uYXBwJ1xuZXhwb3J0IGNvbnN0IERFRkFVTFRfTkFWX1VSTF9WQUxVRSA9ICcvYXBpL3YxL25hdi8nXG5leHBvcnQgY29uc3QgREVGQVVMVF9SRVNFUlZFX1VSTF9WQUxVRSA9ICcvYXBpL3YxL3Jlc2VydmUvJ1xuXG5leHBvcnQgY29uc3QgY29uZmlnID0gbmV3IEFkYXB0ZXJDb25maWcoe1xuICBBUElfS0VZOiB7XG4gICAgZGVzY3JpcHRpb246ICdBbiBBUEkga2V5IGZvciBEYXRhIFByb3ZpZGVyJyxcbiAgICB0eXBlOiAnc3RyaW5nJyxcbiAgICByZXF1aXJlZDogdHJ1ZSxcbiAgICBzZW5zaXRpdmU6IHRydWUsXG4gIH0sXG4gIEFQSV9CQVNFX1VSTDoge1xuICAgIGRlc2NyaXB0aW9uOiAnQmFzZSBVUkwgdG8gRnVuZCBBZG1pbiBTZXJ2ZXIgRW5kcG9pbnQnLFxuICAgIHR5cGU6ICdzdHJpbmcnLFxuICAgIGRlZmF1bHQ6IERFRkFVTFRfQkFTRV9VUkxfVkFMVUUsXG4gIH0sXG4gIEFQSV9OQVZfRU5EUE9JTlQ6IHtcbiAgICBkZXNjcmlwdGlvbjpcbiAgICAgICdBbiBBUEkgZW5kcG9pbnQgZm9yIHRoZSBsYXRlc3QgTmV0IEFzc2V0IFZhbHVlIChOQVYpIGNhbGN1bGF0aW9uIGZvciBhIGdpdmVuIGFzc2V0JyxcbiAgICB0eXBlOiAnc3RyaW5nJyxcbiAgICBkZWZhdWx0OiBERUZBVUxUX05BVl9VUkxfVkFMVUUsXG4gIH0sXG4gIEFQSV9SRVNFUlZFX0VORFBPSU5UOiB7XG4gICAgZGVzY3JpcHRpb246ICdBUEkgRW5kcG9pbnQgdG8gZ2V0IHRoZSBsYXRlc3QgUHJvb2Ygb2YgUmVzZXJ2ZXMgZm9yIGEgZ2l2ZW4gYXNzZXQnLFxuICAgIHR5cGU6ICdzdHJpbmcnLFxuICAgIGRlZmF1bHQ6IERFRkFVTFRfUkVTRVJWRV9VUkxfVkFMVUUsXG4gIH0sXG59KVxuIl19 diff --git a/packages/sources/peregrine-fund-admin/src/endpoint/nav.ts b/packages/sources/peregrine-fund-admin/src/endpoint/nav.ts index 4246cc8d53..198ec7ac0c 100644 --- a/packages/sources/peregrine-fund-admin/src/endpoint/nav.ts +++ b/packages/sources/peregrine-fund-admin/src/endpoint/nav.ts @@ -4,13 +4,20 @@ import { InputParameters } from '@chainlink/external-adapter-framework/validatio import { SingleNumberResultResponse } from '@chainlink/external-adapter-framework/util' import { config } from '../config' -export const inputParameters = new InputParameters({ - assetId: { - required: true, - type: 'string', - description: 'The identifying number for the requested asset', +export const inputParameters = new InputParameters( + { + assetId: { + required: true, + type: 'string', + description: 'The identifying number for the requested asset', + }, }, -}) + [ + { + assetId: '100', + }, + ], +) export interface ResponseSchema { Id: string | null diff --git a/packages/sources/peregrine-fund-admin/src/endpoint/reserve.ts b/packages/sources/peregrine-fund-admin/src/endpoint/reserve.ts index 88c9eae4b6..735bb7be3c 100644 --- a/packages/sources/peregrine-fund-admin/src/endpoint/reserve.ts +++ b/packages/sources/peregrine-fund-admin/src/endpoint/reserve.ts @@ -4,13 +4,20 @@ import { InputParameters } from '@chainlink/external-adapter-framework/validatio import { SingleNumberResultResponse } from '@chainlink/external-adapter-framework/util' import { config } from '../config' -export const inputParameters = new InputParameters({ - assetId: { - required: true, - type: 'string', - description: 'The identifying number for the requested asset', +export const inputParameters = new InputParameters( + { + assetId: { + required: true, + type: 'string', + description: 'The identifying number for the requested asset', + }, }, -}) + [ + { + assetId: '100', + }, + ], +) export type BaseEndpointTypes = { Parameters: typeof inputParameters.definition diff --git a/packages/sources/peregrine-fund-admin/src/transport/nav.ts b/packages/sources/peregrine-fund-admin/src/transport/nav.ts index 7e3a12bacd..183aa93f38 100644 --- a/packages/sources/peregrine-fund-admin/src/transport/nav.ts +++ b/packages/sources/peregrine-fund-admin/src/transport/nav.ts @@ -2,13 +2,14 @@ import { HttpTransport } from '@chainlink/external-adapter-framework/transports' import { BaseEndpointTypes } from '../endpoint/nav' export interface ResponseSchema { - Result: number - Data: { - equityNav: number - seniorNAV: number - juniorNav: number - providerIndicatedTimeUnixMs: undefined - } + equityNav: number + seniorNAV: number + juniorNav: number + totalCollateral: number + totalAccounts: number + totalLiability: number + updateDateTime: string + assetId: string } export type HttpTransportTypes = BaseEndpointTypes & { @@ -36,9 +37,6 @@ export const httpTransport = new HttpTransport({ headers: { X_API_KEY: config.API_KEY, }, - params: { - assetId: params, - }, }, } }) @@ -47,29 +45,41 @@ export const httpTransport = new HttpTransport({ // an array of response objects to be stored in cache. Use this method to construct a list of response objects for every parameter in 'params' // and the framework will save them in cache and return to user parseResponse: (params, response) => { - return params.map((param) => { - const resData = response.data - - if (!resData || !resData.Data) { + if (!response.data) { + return params.map((param) => { return { params: param, response: { - errorMessage: 'Missing equityNav in the response', + errorMessage: `The data provider didn't return any value`, statusCode: 502, }, } - } - return { - params: param, - response: { - Result: 200, - Data: { - equityNav: resData.Data.equityNav, - seniorNAV: resData.Data.seniorNAV, - juniorNav: resData.Data.juniorNav, - providerIndicatedTimeUnixMs: Date.now(), + }) + } + + return params.map((param) => { + const equityNav = response.data.equityNav + if (equityNav) { + return { + params: param, + response: { + result: Number(response.data.equityNav), + data: { + result: Number(response.data.equityNav), + timestamps: { + providerIndicatedTimeUnixMs: Number(response.data.updateDateTime) * 1000, + }, + }, }, - }, + } + } else { + return { + params: param, + response: { + errorMessage: `The data provider didn't return any value for asset id: ${param}`, + statusCode: 502, + }, + } } }) }, diff --git a/packages/sources/peregrine-fund-admin/src/transport/reserve.ts b/packages/sources/peregrine-fund-admin/src/transport/reserve.ts index 5cca8490ae..35aa2afb67 100644 --- a/packages/sources/peregrine-fund-admin/src/transport/reserve.ts +++ b/packages/sources/peregrine-fund-admin/src/transport/reserve.ts @@ -2,11 +2,11 @@ import { HttpTransport } from '@chainlink/external-adapter-framework/transports' import { BaseEndpointTypes } from '../endpoint/reserve' export interface ResponseSchema { - Result: number - Data: { - totalValue: number - providerIndicatedTimeUnixMs: undefined - } + assetId: string + totalValue: number + currencyBase: string + accountIds: number[] + updateDateTime: string } // HttpTransport extends base types from endpoint and adds additional, Provider-specific types like 'RequestBody', which is the type of @@ -36,9 +36,6 @@ export const httpTransport = new HttpTransport({ headers: { X_API_KEY: config.API_KEY, }, - params: { - assetId: param.assetId, - }, }, } }) @@ -47,27 +44,41 @@ export const httpTransport = new HttpTransport({ // an array of response objects to be stored in cache. Use this method to construct a list of response objects for every parameter in 'params' // and the framework will save them in cache and return to user parseResponse: (params, response) => { - return params.map((param) => { - const resData = response.data - - if (!resData || !resData.Data) { + if (!response.data) { + return params.map((param) => { return { params: param, response: { - errorMessage: 'Missing equityNav in the response', + errorMessage: `The data provider didn't return any value`, statusCode: 502, }, } - } - return { - params: param, - response: { - Result: 200, - Data: { - totalValue: resData.Data.totalValue, - providerIndicatedTimeUnixMs: Date.now(), + }) + } + + return params.map((param) => { + const totalValue = response.data.totalValue + if (totalValue) { + return { + params: param, + response: { + result: Number(response.data.totalValue), + data: { + result: Number(response.data.totalValue), + }, + timestamps: { + providerIndicatedTimeUnixMs: Number(response.data.updateDateTime) * 1000, + }, }, - }, + } + } else { + return { + params: param, + response: { + errorMessage: `The data provider didn't return any value for asset id: ${param}`, + statusCode: 502, + }, + } } }) }, diff --git a/packages/sources/peregrine-fund-admin/test/adapter.test.ts b/packages/sources/peregrine-fund-admin/test/integration/adapter.test.ts similarity index 96% rename from packages/sources/peregrine-fund-admin/test/adapter.test.ts rename to packages/sources/peregrine-fund-admin/test/integration/adapter.test.ts index f32e6b1fc7..53a1c75dcc 100644 --- a/packages/sources/peregrine-fund-admin/test/adapter.test.ts +++ b/packages/sources/peregrine-fund-admin/test/integration/adapter.test.ts @@ -17,7 +17,7 @@ describe('execute', () => { const mockDate = new Date('2001-01-01T11:11:11.111Z') spy = jest.spyOn(Date, 'now').mockReturnValue(mockDate.getTime()) - const adapter = (await import('../src')).adapter + const adapter = (await import('../../src')).adapter adapter.rateLimiting = undefined testAdapter = await TestAdapter.startWithMockedCache(adapter, { testAdapter: {} as TestAdapter, diff --git a/packages/sources/peregrine-fund-admin/test/fixtures.ts b/packages/sources/peregrine-fund-admin/test/integration/fixtures.ts similarity index 100% rename from packages/sources/peregrine-fund-admin/test/fixtures.ts rename to packages/sources/peregrine-fund-admin/test/integration/fixtures.ts diff --git a/packages/sources/peregrine-fund-admin/tsconfig.json b/packages/sources/peregrine-fund-admin/tsconfig.json index f59363fd76..522ed78996 100644 --- a/packages/sources/peregrine-fund-admin/tsconfig.json +++ b/packages/sources/peregrine-fund-admin/tsconfig.json @@ -4,6 +4,7 @@ "outDir": "dist", "rootDir": "src" }, + "include": ["src/**/*", "src/**/*.json"], "exclude": ["dist", "**/*.spec.ts", "**/*.test.ts"] } diff --git a/packages/tsconfig.json b/packages/tsconfig.json index 150a95dbb2..dcb62ec46d 100644 --- a/packages/tsconfig.json +++ b/packages/tsconfig.json @@ -419,6 +419,9 @@ { "path": "./sources/ignition-address-list" }, + { + "path": "./sources/instruxi-sxt" + }, { "path": "./sources/intrinio" }, @@ -434,9 +437,6 @@ { "path": "./sources/json-rpc" }, - { - "path": "./sources/instruxi-sxt" - }, { "path": "./sources/kaiko" }, @@ -524,6 +524,9 @@ { "path": "./sources/paypal" }, + { + "path": "./sources/peregrine-fund-admin" + }, { "path": "./sources/polkadot-balance" }, diff --git a/packages/tsconfig.test.json b/packages/tsconfig.test.json index 9cfd5d8ae1..14a1744843 100644 --- a/packages/tsconfig.test.json +++ b/packages/tsconfig.test.json @@ -230,9 +230,6 @@ { "path": "./sources/coinapi/tsconfig.test.json" }, - { - "path": "./sources/instruxi-sxt/tsconfig.test.json" - }, { "path": "./sources/coinbase/tsconfig.test.json" }, @@ -422,6 +419,9 @@ { "path": "./sources/ignition-address-list/tsconfig.test.json" }, + { + "path": "./sources/instruxi-sxt/tsconfig.test.json" + }, { "path": "./sources/intrinio/tsconfig.test.json" }, @@ -524,6 +524,9 @@ { "path": "./sources/paypal/tsconfig.test.json" }, + { + "path": "./sources/peregrine-fund-admin/tsconfig.test.json" + }, { "path": "./sources/polkadot-balance/tsconfig.test.json" }, diff --git a/yarn.lock b/yarn.lock index 4849973c66..bb2cb13978 100644 --- a/yarn.lock +++ b/yarn.lock @@ -5045,6 +5045,19 @@ __metadata: languageName: unknown linkType: soft +"@chainlink/peregrine-fund-admin-adapter@workspace:packages/sources/peregrine-fund-admin": + version: 0.0.0-use.local + resolution: "@chainlink/peregrine-fund-admin-adapter@workspace:packages/sources/peregrine-fund-admin" + dependencies: + "@chainlink/external-adapter-framework": "npm:1.7.1" + "@types/jest": "npm:27.5.2" + "@types/node": "npm:16.18.115" + nock: "npm:13.5.4" + tslib: "npm:2.4.1" + typescript: "npm:5.6.3" + languageName: unknown + linkType: soft + "@chainlink/polkadot-balance-adapter@workspace:*, @chainlink/polkadot-balance-adapter@workspace:packages/sources/polkadot-balance": version: 0.0.0-use.local resolution: "@chainlink/polkadot-balance-adapter@workspace:packages/sources/polkadot-balance" From 53d98b9e04bf7108b0cc82512fc5f72c381f53d4 Mon Sep 17 00:00:00 2001 From: Dennis O'Connell Date: Mon, 2 Dec 2024 20:38:53 -0500 Subject: [PATCH 09/14] passing integration tests --- .pnp.cjs | 75 ++++++++++- ...mework-npm-1.7.3-ddd4342448-ff3e533079.zip | Bin 0 -> 8584244 bytes ...ry-api-npm-1.9.0-7d0560d0dd-a607f0eef9.zip | Bin 0 -> 637335 bytes .../axios-npm-1.7.8-b20f5c399f-7ddcde1880.zip | Bin 0 -> 575067 bytes ...elp-me-npm-5.0.0-6239bd310f-5f99bd91da.zip | Bin 0 -> 9161 bytes ...retty-npm-13.0.0-70bb86dd46-9861fdbe88.zip | Bin 0 -> 88394 bytes ...lient-npm-15.1.3-2fca26d488-eba75e15ab.zip | Bin 0 -> 48360 bytes .../sources/peregrine-fund-admin/package.json | 2 +- .../src/config/index.d.ts | 29 ----- .../src/config/index.d.ts.map | 1 - .../peregrine-fund-admin/src/config/index.js | 37 ------ .../peregrine-fund-admin/src/config/index.ts | 8 -- .../peregrine-fund-admin/src/endpoint/nav.ts | 15 --- .../sources/peregrine-fund-admin/src/index.ts | 7 ++ .../peregrine-fund-admin/src/transport/nav.ts | 2 +- .../src/transport/reserve.ts | 2 +- .../__snapshots__/adapter.test.ts.snap | 33 +++++ .../test/integration/adapter.test.ts | 7 +- .../test/integration/fixtures.ts | 46 +++---- yarn.lock | 119 +++++++++++++++--- 20 files changed, 246 insertions(+), 137 deletions(-) create mode 100644 .yarn/cache/@chainlink-external-adapter-framework-npm-1.7.3-ddd4342448-ff3e533079.zip create mode 100644 .yarn/cache/@opentelemetry-api-npm-1.9.0-7d0560d0dd-a607f0eef9.zip create mode 100644 .yarn/cache/axios-npm-1.7.8-b20f5c399f-7ddcde1880.zip create mode 100644 .yarn/cache/help-me-npm-5.0.0-6239bd310f-5f99bd91da.zip create mode 100644 .yarn/cache/pino-pretty-npm-13.0.0-70bb86dd46-9861fdbe88.zip create mode 100644 .yarn/cache/prom-client-npm-15.1.3-2fca26d488-eba75e15ab.zip delete mode 100644 packages/sources/peregrine-fund-admin/src/config/index.d.ts delete mode 100644 packages/sources/peregrine-fund-admin/src/config/index.d.ts.map delete mode 100644 packages/sources/peregrine-fund-admin/src/config/index.js create mode 100644 packages/sources/peregrine-fund-admin/test/integration/__snapshots__/adapter.test.ts.snap diff --git a/.pnp.cjs b/.pnp.cjs index d7efcf28b6..1e12f8a653 100755 --- a/.pnp.cjs +++ b/.pnp.cjs @@ -6981,6 +6981,24 @@ const RAW_RUNTIME_STATE = ["yeoman-generator", "npm:3.2.0"]\ ],\ "linkType": "HARD"\ + }],\ + ["npm:1.7.3", {\ + "packageLocation": "./.yarn/cache/@chainlink-external-adapter-framework-npm-1.7.3-ddd4342448-ff3e533079.zip/node_modules/@chainlink/external-adapter-framework/",\ + "packageDependencies": [\ + ["@chainlink/external-adapter-framework", "npm:1.7.3"],\ + ["ajv", "npm:8.17.1"],\ + ["axios", "npm:1.7.8"],\ + ["eventsource", "npm:2.0.2"],\ + ["fastify", "npm:4.28.1"],\ + ["ioredis", "npm:5.4.1"],\ + ["mock-socket", "npm:9.3.1"],\ + ["pino", "npm:9.5.0"],\ + ["pino-pretty", "npm:13.0.0"],\ + ["prom-client", "npm:15.1.3"],\ + ["redlock", "npm:5.0.0-beta.2"],\ + ["ws", "virtual:8dd7f0280a91f3456aff0f4884238a2de6f4621c6af864cf648871636777a1ef89241ef7f027320041482e75bf65944c572871182dad4dd35413b07acefb63d5#npm:8.18.0"]\ + ],\ + "linkType": "HARD"\ }]\ ]],\ ["@chainlink/external-adapters-js", [\ @@ -8301,7 +8319,7 @@ const RAW_RUNTIME_STATE = "packageLocation": "./packages/sources/peregrine-fund-admin/",\ "packageDependencies": [\ ["@chainlink/peregrine-fund-admin-adapter", "workspace:packages/sources/peregrine-fund-admin"],\ - ["@chainlink/external-adapter-framework", "npm:1.7.1"],\ + ["@chainlink/external-adapter-framework", "npm:1.7.3"],\ ["@types/jest", "npm:27.5.2"],\ ["@types/node", "npm:16.18.115"],\ ["nock", "npm:13.5.4"],\ @@ -13432,6 +13450,15 @@ const RAW_RUNTIME_STATE = "linkType": "HARD"\ }]\ ]],\ + ["@opentelemetry/api", [\ + ["npm:1.9.0", {\ + "packageLocation": "./.yarn/cache/@opentelemetry-api-npm-1.9.0-7d0560d0dd-a607f0eef9.zip/node_modules/@opentelemetry/api/",\ + "packageDependencies": [\ + ["@opentelemetry/api", "npm:1.9.0"]\ + ],\ + "linkType": "HARD"\ + }]\ + ]],\ ["@openzeppelin/contracts", [\ ["npm:3.4.2", {\ "packageLocation": "./.yarn/cache/@openzeppelin-contracts-npm-3.4.2-d16cc26fbd-e803e322bd.zip/node_modules/@openzeppelin/contracts/",\ @@ -18139,6 +18166,16 @@ const RAW_RUNTIME_STATE = ["proxy-from-env", "npm:1.1.0"]\ ],\ "linkType": "HARD"\ + }],\ + ["npm:1.7.8", {\ + "packageLocation": "./.yarn/cache/axios-npm-1.7.8-b20f5c399f-7ddcde1880.zip/node_modules/axios/",\ + "packageDependencies": [\ + ["axios", "npm:1.7.8"],\ + ["follow-redirects", "virtual:cfbedc233d4c16068d815547ad303dec1092fdb3b8bb4ec9ab9c56bdd55b4e87650c7a525a88805756f4d2819c03abfd96a9983cfa927fedf995d1b8b879db38#npm:1.15.9"],\ + ["form-data", "npm:4.0.1"],\ + ["proxy-from-env", "npm:1.1.0"]\ + ],\ + "linkType": "HARD"\ }]\ ]],\ ["axios-mock-adapter", [\ @@ -25503,6 +25540,13 @@ const RAW_RUNTIME_STATE = ["readable-stream", "npm:3.6.2"]\ ],\ "linkType": "HARD"\ + }],\ + ["npm:5.0.0", {\ + "packageLocation": "./.yarn/cache/help-me-npm-5.0.0-6239bd310f-5f99bd91da.zip/node_modules/help-me/",\ + "packageDependencies": [\ + ["help-me", "npm:5.0.0"]\ + ],\ + "linkType": "HARD"\ }]\ ]],\ ["hexoid", [\ @@ -34078,6 +34122,26 @@ const RAW_RUNTIME_STATE = }]\ ]],\ ["pino-pretty", [\ + ["npm:13.0.0", {\ + "packageLocation": "./.yarn/cache/pino-pretty-npm-13.0.0-70bb86dd46-9861fdbe88.zip/node_modules/pino-pretty/",\ + "packageDependencies": [\ + ["pino-pretty", "npm:13.0.0"],\ + ["colorette", "npm:2.0.20"],\ + ["dateformat", "npm:4.6.3"],\ + ["fast-copy", "npm:3.0.2"],\ + ["fast-safe-stringify", "npm:2.1.1"],\ + ["help-me", "npm:5.0.0"],\ + ["joycon", "npm:3.1.1"],\ + ["minimist", "npm:1.2.8"],\ + ["on-exit-leak-free", "npm:2.1.2"],\ + ["pino-abstract-transport", "npm:2.0.0"],\ + ["pump", "npm:3.0.2"],\ + ["secure-json-parse", "npm:2.7.0"],\ + ["sonic-boom", "npm:4.2.0"],\ + ["strip-json-comments", "npm:3.1.1"]\ + ],\ + "linkType": "HARD"\ + }],\ ["npm:4.8.0", {\ "packageLocation": "./.yarn/cache/pino-pretty-npm-4.8.0-0c822e28cb-9beed2f59c.zip/node_modules/pino-pretty/",\ "packageDependencies": [\ @@ -34419,6 +34483,15 @@ const RAW_RUNTIME_STATE = ["tdigest", "npm:0.1.2"]\ ],\ "linkType": "HARD"\ + }],\ + ["npm:15.1.3", {\ + "packageLocation": "./.yarn/cache/prom-client-npm-15.1.3-2fca26d488-eba75e15ab.zip/node_modules/prom-client/",\ + "packageDependencies": [\ + ["prom-client", "npm:15.1.3"],\ + ["@opentelemetry/api", "npm:1.9.0"],\ + ["tdigest", "npm:0.1.2"]\ + ],\ + "linkType": "HARD"\ }]\ ]],\ ["prometheus-gc-stats", [\ diff --git a/.yarn/cache/@chainlink-external-adapter-framework-npm-1.7.3-ddd4342448-ff3e533079.zip b/.yarn/cache/@chainlink-external-adapter-framework-npm-1.7.3-ddd4342448-ff3e533079.zip new file mode 100644 index 0000000000000000000000000000000000000000..fdad41b0e84cc729328d55d78151c8d56be38628 GIT binary patch literal 8584244 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�U`MU(~`Iwl{1PoE|#S?mlpF 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