diff --git a/.pnp.cjs b/.pnp.cjs index eef0fd263af..6d26bf4ab4e 100644 --- a/.pnp.cjs +++ b/.pnp.cjs @@ -6153,7 +6153,7 @@ const RAW_RUNTIME_STATE = "linkType": "HARD"\ }],\ ["npm:2.9.0", {\ - "packageLocation": "./.yarn/cache/@chainlink-external-adapter-framework-npm-2.9.0-664e8a533b-36152824af.zip/node_modules/@chainlink/external-adapter-framework/",\ + "packageLocation": "./.yarn/unplugged/@chainlink-external-adapter-framework-npm-2.9.0-664e8a533b/node_modules/@chainlink/external-adapter-framework/",\ "packageDependencies": [\ ["@chainlink/external-adapter-framework", "npm:2.9.0"],\ ["ajv", "npm:8.17.1"],\ diff --git a/package.json b/package.json index ed3d28f3206..cdd6f919ca5 100644 --- a/package.json +++ b/package.json @@ -77,5 +77,10 @@ "resolutions": { "ethereum-cryptography@^1.1.2": "patch:ethereum-cryptography@npm%3A1.1.2#./.yarn/patches/ethereum-cryptography-npm-1.1.2-c16cfd7e8a.patch", "ethereum-cryptography@^1.0.3": "patch:ethereum-cryptography@npm%3A1.1.2#./.yarn/patches/ethereum-cryptography-npm-1.1.2-c16cfd7e8a.patch" + }, + "dependenciesMeta": { + "@chainlink/external-adapter-framework@2.9.0": { + "unplugged": true + } } } diff --git a/packages/sources/coinmetrics-lwba/src/index.d.ts b/packages/sources/coinmetrics-lwba/src/index.d.ts new file mode 100644 index 00000000000..ce1f5f10e57 --- /dev/null +++ b/packages/sources/coinmetrics-lwba/src/index.d.ts @@ -0,0 +1,40 @@ +import { ServerInstance } from '@chainlink/external-adapter-framework' +import { Adapter } from '@chainlink/external-adapter-framework/adapter' +export declare const config: import('@chainlink/external-adapter-framework/config').AdapterConfig<{ + API_KEY: { + description: string + type: 'string' + required: true + sensitive: true + } + WS_API_ENDPOINT: { + description: string + type: 'string' + default: string + } + API_ENDPOINT: { + description: string + type: 'string' + default: string + } +}> +export declare const adapter: Adapter<{ + API_KEY: { + description: string + type: 'string' + required: true + sensitive: true + } + WS_API_ENDPOINT: { + description: string + type: 'string' + default: string + } + API_ENDPOINT: { + description: string + type: 'string' + default: string + } +}> +export declare const server: () => Promise +//# sourceMappingURL=index.d.ts.map diff --git a/packages/sources/coinmetrics-lwba/src/index.d.ts.map b/packages/sources/coinmetrics-lwba/src/index.d.ts.map new file mode 100644 index 00000000000..ce20408ea96 --- /dev/null +++ b/packages/sources/coinmetrics-lwba/src/index.d.ts.map @@ -0,0 +1 @@ +{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["index.ts"],"names":[],"mappings":"AAEA,OAAO,EAAU,cAAc,EAAE,MAAM,uCAAuC,CAAA;AAC9E,OAAO,EAAE,OAAO,EAAE,MAAM,+CAA+C,CAAA;AAIvE,eAAO,MAAM,MAAM;;;;;;;;;;;;;;;;;EAOjB,CAAA;AAmBF,eAAO,MAAM,OAAO;;;;;;;;;;;;;;;;;EAelB,CAAA;AAEF,eAAO,MAAM,MAAM,QAAO,OAAO,CAAC,cAAc,GAAG,SAAS,CAAoB,CAAA"} \ No newline at end of file diff --git a/packages/sources/coinmetrics-lwba/src/index.js b/packages/sources/coinmetrics-lwba/src/index.js new file mode 100644 index 00000000000..b7a3bb43e82 --- /dev/null +++ b/packages/sources/coinmetrics-lwba/src/index.js @@ -0,0 +1,53 @@ +'use strict' +Object.defineProperty(exports, '__esModule', { value: true }) +exports.server = exports.adapter = exports.config = void 0 +const config_1 = require('@chainlink/coinmetrics-adapter/config') +const lwba_1 = require('@chainlink/coinmetrics-adapter/endpoint/lwba') +const external_adapter_framework_1 = require('@chainlink/external-adapter-framework') +const adapter_1 = require('@chainlink/external-adapter-framework/adapter') +exports.config = (0, config_1.makeConfig)({ + NAME: 'COINMETRICS_LWBA', + API_ENDPOINT: { + description: 'Unused in LWBA', + type: 'string', + required: false, + }, +}) +const newEndpoint = Object.assign( + Object.create(Object.getPrototypeOf(lwba_1.endpoint)), + lwba_1.endpoint, +) +newEndpoint.aliases.push('crypto', 'price') +const originalValidate = lwba_1.endpoint.customOutputValidation +newEndpoint.customOutputValidation = (resp) => { + const err = originalValidate?.(resp) + if (err) { + return err + } + if (!resp.errorMessage) { + const mid = resp.data?.mid + if (mid !== undefined) { + resp.result = mid + } + } + return undefined // no validation error +} +exports.adapter = new adapter_1.Adapter({ + defaultEndpoint: newEndpoint.name, + name: 'COINMETRICS_LWBA', + config: exports.config, + endpoints: [newEndpoint], + rateLimiting: { + tiers: { + community: { + rateLimit1m: 100, + }, + paid: { + rateLimit1s: 300, + }, + }, + }, +}) +const server = () => (0, external_adapter_framework_1.expose)(exports.adapter) +exports.server = server +//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiaW5kZXguanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyJpbmRleC50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiOzs7QUFBQSxrRUFBa0U7QUFDbEUsdUVBQXVFO0FBQ3ZFLHNGQUE4RTtBQUM5RSwyRUFBdUU7QUFJMUQsUUFBQSxNQUFNLEdBQUcsSUFBQSxtQkFBVSxFQUFDO0lBQy9CLElBQUksRUFBRSxrQkFBa0I7SUFDeEIsWUFBWSxFQUFFO1FBQ1osV0FBVyxFQUFFLGdCQUFnQjtRQUM3QixJQUFJLEVBQUUsUUFBUTtRQUNkLFFBQVEsRUFBRSxLQUFLO0tBQ2hCO0NBQ0YsQ0FBQyxDQUFBO0FBRUYsTUFBTSxXQUFXLEdBQUcsTUFBTSxDQUFDLE1BQU0sQ0FBQyxNQUFNLENBQUMsTUFBTSxDQUFDLE1BQU0sQ0FBQyxjQUFjLENBQUMsZUFBUSxDQUFDLENBQUMsRUFBRSxlQUFRLENBQUMsQ0FBQTtBQUMzRixXQUFXLENBQUMsT0FBTyxDQUFDLElBQUksQ0FBQyxRQUFRLEVBQUUsT0FBTyxDQUFDLENBQUE7QUFDM0MsTUFBTSxnQkFBZ0IsR0FBRyxlQUFRLENBQUMsc0JBQXNCLENBQUE7QUFDeEQsV0FBVyxDQUFDLHNCQUFzQixHQUFHLENBQUMsSUFBcUIsRUFBNEIsRUFBRTtJQUN2RixNQUFNLEdBQUcsR0FBRyxnQkFBZ0IsRUFBRSxDQUFDLElBQUksQ0FBQyxDQUFBO0lBQ3BDLElBQUksR0FBRyxFQUFFLENBQUM7UUFDUixPQUFPLEdBQUcsQ0FBQTtJQUNaLENBQUM7SUFDRCxJQUFJLENBQUMsSUFBSSxDQUFDLFlBQVksRUFBRSxDQUFDO1FBQ3ZCLE1BQU0sR0FBRyxHQUFJLElBQUksQ0FBQyxJQUFZLEVBQUUsR0FBRyxDQUFBO1FBQ25DLElBQUksR0FBRyxLQUFLLFNBQVMsRUFBRSxDQUFDO1lBQ3RCLElBQUksQ0FBQyxNQUFNLEdBQUcsR0FBRyxDQUFBO1FBQ25CLENBQUM7SUFDSCxDQUFDO0lBQ0QsT0FBTyxTQUFTLENBQUEsQ0FBQyxzQkFBc0I7QUFDekMsQ0FBQyxDQUFBO0FBRVksUUFBQSxPQUFPLEdBQUcsSUFBSSxpQkFBTyxDQUFDO0lBQ2pDLGVBQWUsRUFBRSxXQUFXLENBQUMsSUFBSTtJQUNqQyxJQUFJLEVBQUUsa0JBQWtCO0lBQ3hCLE1BQU0sRUFBTixjQUFNO0lBQ04sU0FBUyxFQUFFLENBQUMsV0FBVyxDQUFDO0lBQ3hCLFlBQVksRUFBRTtRQUNaLEtBQUssRUFBRTtZQUNMLFNBQVMsRUFBRTtnQkFDVCxXQUFXLEVBQUUsR0FBRzthQUNqQjtZQUNELElBQUksRUFBRTtnQkFDSixXQUFXLEVBQUUsR0FBRzthQUNqQjtTQUNGO0tBQ0Y7Q0FDRixDQUFDLENBQUE7QUFFSyxNQUFNLE1BQU0sR0FBRyxHQUF3QyxFQUFFLENBQUMsSUFBQSxtQ0FBTSxFQUFDLGVBQU8sQ0FBQyxDQUFBO0FBQW5FLFFBQUEsTUFBTSxVQUE2RCIsInNvdXJjZXNDb250ZW50IjpbImltcG9ydCB7IG1ha2VDb25maWcgfSBmcm9tICdAY2hhaW5saW5rL2NvaW5tZXRyaWNzLWFkYXB0ZXIvY29uZmlnJ1xuaW1wb3J0IHsgZW5kcG9pbnQgfSBmcm9tICdAY2hhaW5saW5rL2NvaW5tZXRyaWNzLWFkYXB0ZXIvZW5kcG9pbnQvbHdiYSdcbmltcG9ydCB7IGV4cG9zZSwgU2VydmVySW5zdGFuY2UgfSBmcm9tICdAY2hhaW5saW5rL2V4dGVybmFsLWFkYXB0ZXItZnJhbWV3b3JrJ1xuaW1wb3J0IHsgQWRhcHRlciB9IGZyb20gJ0BjaGFpbmxpbmsvZXh0ZXJuYWwtYWRhcHRlci1mcmFtZXdvcmsvYWRhcHRlcidcbmltcG9ydCB7IEFkYXB0ZXJSZXNwb25zZSB9IGZyb20gJ0BjaGFpbmxpbmsvZXh0ZXJuYWwtYWRhcHRlci1mcmFtZXdvcmsvdXRpbC90eXBlcydcbmltcG9ydCB7IEFkYXB0ZXJFcnJvciB9IGZyb20gJ0BjaGFpbmxpbmsvZXh0ZXJuYWwtYWRhcHRlci1mcmFtZXdvcmsvdmFsaWRhdGlvbi9lcnJvcidcblxuZXhwb3J0IGNvbnN0IGNvbmZpZyA9IG1ha2VDb25maWcoe1xuICBOQU1FOiAnQ09JTk1FVFJJQ1NfTFdCQScsXG4gIEFQSV9FTkRQT0lOVDoge1xuICAgIGRlc2NyaXB0aW9uOiAnVW51c2VkIGluIExXQkEnLFxuICAgIHR5cGU6ICdzdHJpbmcnLFxuICAgIHJlcXVpcmVkOiBmYWxzZSxcbiAgfSxcbn0pXG5cbmNvbnN0IG5ld0VuZHBvaW50ID0gT2JqZWN0LmFzc2lnbihPYmplY3QuY3JlYXRlKE9iamVjdC5nZXRQcm90b3R5cGVPZihlbmRwb2ludCkpLCBlbmRwb2ludClcbm5ld0VuZHBvaW50LmFsaWFzZXMucHVzaCgnY3J5cHRvJywgJ3ByaWNlJylcbmNvbnN0IG9yaWdpbmFsVmFsaWRhdGUgPSBlbmRwb2ludC5jdXN0b21PdXRwdXRWYWxpZGF0aW9uXG5uZXdFbmRwb2ludC5jdXN0b21PdXRwdXRWYWxpZGF0aW9uID0gKHJlc3A6IEFkYXB0ZXJSZXNwb25zZSk6IEFkYXB0ZXJFcnJvciB8IHVuZGVmaW5lZCA9PiB7XG4gIGNvbnN0IGVyciA9IG9yaWdpbmFsVmFsaWRhdGU/LihyZXNwKVxuICBpZiAoZXJyKSB7XG4gICAgcmV0dXJuIGVyclxuICB9XG4gIGlmICghcmVzcC5lcnJvck1lc3NhZ2UpIHtcbiAgICBjb25zdCBtaWQgPSAocmVzcC5kYXRhIGFzIGFueSk/Lm1pZFxuICAgIGlmIChtaWQgIT09IHVuZGVmaW5lZCkge1xuICAgICAgcmVzcC5yZXN1bHQgPSBtaWRcbiAgICB9XG4gIH1cbiAgcmV0dXJuIHVuZGVmaW5lZCAvLyBubyB2YWxpZGF0aW9uIGVycm9yXG59XG5cbmV4cG9ydCBjb25zdCBhZGFwdGVyID0gbmV3IEFkYXB0ZXIoe1xuICBkZWZhdWx0RW5kcG9pbnQ6IG5ld0VuZHBvaW50Lm5hbWUsXG4gIG5hbWU6ICdDT0lOTUVUUklDU19MV0JBJyxcbiAgY29uZmlnLFxuICBlbmRwb2ludHM6IFtuZXdFbmRwb2ludF0sXG4gIHJhdGVMaW1pdGluZzoge1xuICAgIHRpZXJzOiB7XG4gICAgICBjb21tdW5pdHk6IHtcbiAgICAgICAgcmF0ZUxpbWl0MW06IDEwMCxcbiAgICAgIH0sXG4gICAgICBwYWlkOiB7XG4gICAgICAgIHJhdGVMaW1pdDFzOiAzMDAsXG4gICAgICB9LFxuICAgIH0sXG4gIH0sXG59KVxuXG5leHBvcnQgY29uc3Qgc2VydmVyID0gKCk6IFByb21pc2U8U2VydmVySW5zdGFuY2UgfCB1bmRlZmluZWQ+ID0+IGV4cG9zZShhZGFwdGVyKVxuIl19 diff --git a/packages/sources/coinmetrics-lwba/test/integration/adapter-ws.test.d.ts b/packages/sources/coinmetrics-lwba/test/integration/adapter-ws.test.d.ts new file mode 100644 index 00000000000..cce5ea7ac01 --- /dev/null +++ b/packages/sources/coinmetrics-lwba/test/integration/adapter-ws.test.d.ts @@ -0,0 +1,2 @@ +export {} +//# sourceMappingURL=adapter-ws.test.d.ts.map diff --git a/packages/sources/coinmetrics-lwba/test/integration/adapter-ws.test.d.ts.map b/packages/sources/coinmetrics-lwba/test/integration/adapter-ws.test.d.ts.map new file mode 100644 index 00000000000..b980d75e95a --- /dev/null +++ b/packages/sources/coinmetrics-lwba/test/integration/adapter-ws.test.d.ts.map @@ -0,0 +1 @@ +{"version":3,"file":"adapter-ws.test.d.ts","sourceRoot":"","sources":["adapter-ws.test.ts"],"names":[],"mappings":""} \ No newline at end of file diff --git a/packages/sources/coinmetrics-lwba/test/integration/adapter-ws.test.js b/packages/sources/coinmetrics-lwba/test/integration/adapter-ws.test.js new file mode 100644 index 00000000000..3bafbaf8694 --- /dev/null +++ b/packages/sources/coinmetrics-lwba/test/integration/adapter-ws.test.js @@ -0,0 +1,88 @@ +'use strict' +Object.defineProperty(exports, '__esModule', { value: true }) +const tslib_1 = require('tslib') +const transports_1 = require('@chainlink/external-adapter-framework/transports') +const testing_utils_1 = require('@chainlink/external-adapter-framework/util/testing-utils') +const fake_timers_1 = tslib_1.__importDefault(require('@sinonjs/fake-timers')) +const fixtures_1 = require('./fixtures') +describe('crypto-lwba websocket', () => { + let mockWsServer + let testAdapter + let oldEnv + const wsEndpoint = 'ws://localhost:9090/v4/timeseries-stream/asset-quotes' + const requestPayload = { + endpoint: 'crypto-lwba', + base: 'ETH', + quote: 'USD', + } + beforeAll(async () => { + // snapshot current env and set the ones we need for tests + oldEnv = { ...process.env } + process.env['WS_SUBSCRIPTION_TTL'] = '5000' + process.env['CACHE_MAX_AGE'] = '5000' + process.env['CACHE_POLLING_MAX_RETRIES'] = '0' + process.env['WS_API_ENDPOINT'] = wsEndpoint + process.env['API_KEY'] = 'fake-api-key' + // mock WS provider + server + ;(0, testing_utils_1.mockWebSocketProvider)(transports_1.WebSocketClassProvider) + mockWsServer = (0, fixtures_1.mockCryptoLwbaWebSocketServer)(wsEndpoint) + // start adapter with fake timers + // eslint-disable-next-line @typescript-eslint/no-var-requires + const { adapter } = require('../../src') + testAdapter = await testing_utils_1.TestAdapter.startWithMockedCache(adapter, { + clock: fake_timers_1.default.install(), + }) + // warm the cache once so background execute starts + await testAdapter.request(requestPayload) + await testAdapter.waitForCache(1) + }) + afterAll(async () => { + ;(0, testing_utils_1.setEnvVariables)(oldEnv) + mockWsServer?.close() + testAdapter.clock?.uninstall() + await testAdapter.api.close() + }) + describe('happy path', () => { + it('returns a successful response', async () => { + const response = await testAdapter.request(requestPayload) + expect(response.json()).toMatchSnapshot() + }) + it('returns a successful response for crypto alias', async () => { + requestPayload.endpoint = 'crypto' + const response = await testAdapter.request(requestPayload) + expect(response.json()).toMatchSnapshot() + }) + it('returns a successful response for price alias', async () => { + requestPayload.endpoint = 'price' + const response = await testAdapter.request(requestPayload) + expect(response.json()).toMatchSnapshot() + }) + }) + describe('validation errors', () => { + it('fails on empty body', async () => { + const response = await testAdapter.request({}) + expect(response.statusCode).toEqual(400) + }) + it('fails on empty data', async () => { + const response = await testAdapter.request({ endpoint: 'crypto-lwba' }) + expect(response.statusCode).toEqual(400) + }) + it('fails on missing base', async () => { + const response = await testAdapter.request({ endpoint: 'crypto-lwba', quote: 'USD' }) + expect(response.statusCode).toEqual(400) + }) + it('fails on missing quote', async () => { + const response = await testAdapter.request({ endpoint: 'crypto-lwba', base: 'ETH' }) + expect(response.statusCode).toEqual(400) + }) + }) + describe('invariant violation handling', () => { + it('handles a violation payload from the stream', async () => { + // advance the fake clock so the mocked server pushes the next message + testAdapter.clock.tick(1000) + const response = await testAdapter.request(requestPayload) + expect(response.json()).toMatchSnapshot() + }) + }) +}) +//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiYWRhcHRlci13cy50ZXN0LmpzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXMiOlsiYWRhcHRlci13cy50ZXN0LnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiI7OztBQUFBLGlGQUF5RjtBQUN6Riw0RkFLaUU7QUFDakUsK0VBQTZDO0FBQzdDLHlDQUEwRDtBQUUxRCxRQUFRLENBQUMsdUJBQXVCLEVBQUUsR0FBRyxFQUFFO0lBQ3JDLElBQUksWUFBNkMsQ0FBQTtJQUNqRCxJQUFJLFdBQXdCLENBQUE7SUFDNUIsSUFBSSxNQUF5QixDQUFBO0lBRTdCLE1BQU0sVUFBVSxHQUFHLHVEQUF1RCxDQUFBO0lBRTFFLE1BQU0sY0FBYyxHQUFHO1FBQ3JCLFFBQVEsRUFBRSxhQUFhO1FBQ3ZCLElBQUksRUFBRSxLQUFLO1FBQ1gsS0FBSyxFQUFFLEtBQUs7S0FDYixDQUFBO0lBRUQsU0FBUyxDQUFDLEtBQUssSUFBSSxFQUFFO1FBQ25CLDBEQUEwRDtRQUMxRCxNQUFNLEdBQUcsRUFBRSxHQUFHLE9BQU8sQ0FBQyxHQUFHLEVBQUUsQ0FBQTtRQUMzQixPQUFPLENBQUMsR0FBRyxDQUFDLHFCQUFxQixDQUFDLEdBQUcsTUFBTSxDQUFBO1FBQzNDLE9BQU8sQ0FBQyxHQUFHLENBQUMsZUFBZSxDQUFDLEdBQUcsTUFBTSxDQUFBO1FBQ3JDLE9BQU8sQ0FBQyxHQUFHLENBQUMsMkJBQTJCLENBQUMsR0FBRyxHQUFHLENBQUE7UUFDOUMsT0FBTyxDQUFDLEdBQUcsQ0FBQyxpQkFBaUIsQ0FBQyxHQUFHLFVBQVUsQ0FBQTtRQUMzQyxPQUFPLENBQUMsR0FBRyxDQUFDLFNBQVMsQ0FBQyxHQUFHLGNBQWMsQ0FBQTtRQUV2Qyw0QkFBNEI7UUFDNUIsSUFBQSxxQ0FBcUIsRUFBQyxtQ0FBc0IsQ0FBQyxDQUFBO1FBQzdDLFlBQVksR0FBRyxJQUFBLHdDQUE2QixFQUFDLFVBQVUsQ0FBQyxDQUFBO1FBRXhELGlDQUFpQztRQUNqQyw4REFBOEQ7UUFDOUQsTUFBTSxFQUFFLE9BQU8sRUFBRSxHQUFHLE9BQU8sQ0FBQyxXQUFXLENBQUMsQ0FBQTtRQUN4QyxXQUFXLEdBQUcsTUFBTSwyQkFBVyxDQUFDLG9CQUFvQixDQUFDLE9BQU8sRUFBRTtZQUM1RCxLQUFLLEVBQUUscUJBQVUsQ0FBQyxPQUFPLEVBQUU7U0FDNUIsQ0FBQyxDQUFBO1FBRUYsbURBQW1EO1FBQ25ELE1BQU0sV0FBVyxDQUFDLE9BQU8sQ0FBQyxjQUFjLENBQUMsQ0FBQTtRQUN6QyxNQUFNLFdBQVcsQ0FBQyxZQUFZLENBQUMsQ0FBQyxDQUFDLENBQUE7SUFDbkMsQ0FBQyxDQUFDLENBQUE7SUFFRixRQUFRLENBQUMsS0FBSyxJQUFJLEVBQUU7UUFDbEIsSUFBQSwrQkFBZSxFQUFDLE1BQU0sQ0FBQyxDQUFBO1FBQ3ZCLFlBQVksRUFBRSxLQUFLLEVBQUUsQ0FBQTtRQUNyQixXQUFXLENBQUMsS0FBSyxFQUFFLFNBQVMsRUFBRSxDQUFBO1FBQzlCLE1BQU0sV0FBVyxDQUFDLEdBQUcsQ0FBQyxLQUFLLEVBQUUsQ0FBQTtJQUMvQixDQUFDLENBQUMsQ0FBQTtJQUVGLFFBQVEsQ0FBQyxZQUFZLEVBQUUsR0FBRyxFQUFFO1FBQzFCLEVBQUUsQ0FBQywrQkFBK0IsRUFBRSxLQUFLLElBQUksRUFBRTtZQUM3QyxNQUFNLFFBQVEsR0FBRyxNQUFNLFdBQVcsQ0FBQyxPQUFPLENBQUMsY0FBYyxDQUFDLENBQUE7WUFDMUQsTUFBTSxDQUFDLFFBQVEsQ0FBQyxJQUFJLEVBQUUsQ0FBQyxDQUFDLGVBQWUsRUFBRSxDQUFBO1FBQzNDLENBQUMsQ0FBQyxDQUFBO1FBRUYsRUFBRSxDQUFDLGdEQUFnRCxFQUFFLEtBQUssSUFBSSxFQUFFO1lBQzlELGNBQWMsQ0FBQyxRQUFRLEdBQUcsUUFBUSxDQUFBO1lBQ2xDLE1BQU0sUUFBUSxHQUFHLE1BQU0sV0FBVyxDQUFDLE9BQU8sQ0FBQyxjQUFjLENBQUMsQ0FBQTtZQUMxRCxNQUFNLENBQUMsUUFBUSxDQUFDLElBQUksRUFBRSxDQUFDLENBQUMsZUFBZSxFQUFFLENBQUE7UUFDM0MsQ0FBQyxDQUFDLENBQUE7UUFFRixFQUFFLENBQUMsK0NBQStDLEVBQUUsS0FBSyxJQUFJLEVBQUU7WUFDN0QsY0FBYyxDQUFDLFFBQVEsR0FBRyxPQUFPLENBQUE7WUFDakMsTUFBTSxRQUFRLEdBQUcsTUFBTSxXQUFXLENBQUMsT0FBTyxDQUFDLGNBQWMsQ0FBQyxDQUFBO1lBQzFELE1BQU0sQ0FBQyxRQUFRLENBQUMsSUFBSSxFQUFFLENBQUMsQ0FBQyxlQUFlLEVBQUUsQ0FBQTtRQUMzQyxDQUFDLENBQUMsQ0FBQTtJQUNKLENBQUMsQ0FBQyxDQUFBO0lBRUYsUUFBUSxDQUFDLG1CQUFtQixFQUFFLEdBQUcsRUFBRTtRQUNqQyxFQUFFLENBQUMscUJBQXFCLEVBQUUsS0FBSyxJQUFJLEVBQUU7WUFDbkMsTUFBTSxRQUFRLEdBQUcsTUFBTSxXQUFXLENBQUMsT0FBTyxDQUFDLEVBQUUsQ0FBQyxDQUFBO1lBQzlDLE1BQU0sQ0FBQyxRQUFRLENBQUMsVUFBVSxDQUFDLENBQUMsT0FBTyxDQUFDLEdBQUcsQ0FBQyxDQUFBO1FBQzFDLENBQUMsQ0FBQyxDQUFBO1FBRUYsRUFBRSxDQUFDLHFCQUFxQixFQUFFLEtBQUssSUFBSSxFQUFFO1lBQ25DLE1BQU0sUUFBUSxHQUFHLE1BQU0sV0FBVyxDQUFDLE9BQU8sQ0FBQyxFQUFFLFFBQVEsRUFBRSxhQUFhLEVBQUUsQ0FBQyxDQUFBO1lBQ3ZFLE1BQU0sQ0FBQyxRQUFRLENBQUMsVUFBVSxDQUFDLENBQUMsT0FBTyxDQUFDLEdBQUcsQ0FBQyxDQUFBO1FBQzFDLENBQUMsQ0FBQyxDQUFBO1FBRUYsRUFBRSxDQUFDLHVCQUF1QixFQUFFLEtBQUssSUFBSSxFQUFFO1lBQ3JDLE1BQU0sUUFBUSxHQUFHLE1BQU0sV0FBVyxDQUFDLE9BQU8sQ0FBQyxFQUFFLFFBQVEsRUFBRSxhQUFhLEVBQUUsS0FBSyxFQUFFLEtBQUssRUFBRSxDQUFDLENBQUE7WUFDckYsTUFBTSxDQUFDLFFBQVEsQ0FBQyxVQUFVLENBQUMsQ0FBQyxPQUFPLENBQUMsR0FBRyxDQUFDLENBQUE7UUFDMUMsQ0FBQyxDQUFDLENBQUE7UUFFRixFQUFFLENBQUMsd0JBQXdCLEVBQUUsS0FBSyxJQUFJLEVBQUU7WUFDdEMsTUFBTSxRQUFRLEdBQUcsTUFBTSxXQUFXLENBQUMsT0FBTyxDQUFDLEVBQUUsUUFBUSxFQUFFLGFBQWEsRUFBRSxJQUFJLEVBQUUsS0FBSyxFQUFFLENBQUMsQ0FBQTtZQUNwRixNQUFNLENBQUMsUUFBUSxDQUFDLFVBQVUsQ0FBQyxDQUFDLE9BQU8sQ0FBQyxHQUFHLENBQUMsQ0FBQTtRQUMxQyxDQUFDLENBQUMsQ0FBQTtJQUNKLENBQUMsQ0FBQyxDQUFBO0lBRUYsUUFBUSxDQUFDLDhCQUE4QixFQUFFLEdBQUcsRUFBRTtRQUM1QyxFQUFFLENBQUMsNkNBQTZDLEVBQUUsS0FBSyxJQUFJLEVBQUU7WUFDM0Qsc0VBQXNFO1lBQ3RFLFdBQVcsQ0FBQyxLQUFLLENBQUMsSUFBSSxDQUFDLElBQUksQ0FBQyxDQUFBO1lBQzVCLE1BQU0sUUFBUSxHQUFHLE1BQU0sV0FBVyxDQUFDLE9BQU8sQ0FBQyxjQUFjLENBQUMsQ0FBQTtZQUMxRCxNQUFNLENBQUMsUUFBUSxDQUFDLElBQUksRUFBRSxDQUFDLENBQUMsZUFBZSxFQUFFLENBQUE7UUFDM0MsQ0FBQyxDQUFDLENBQUE7SUFDSixDQUFDLENBQUMsQ0FBQTtBQUNKLENBQUMsQ0FBQyxDQUFBIiwic291cmNlc0NvbnRlbnQiOlsiaW1wb3J0IHsgV2ViU29ja2V0Q2xhc3NQcm92aWRlciB9IGZyb20gJ0BjaGFpbmxpbmsvZXh0ZXJuYWwtYWRhcHRlci1mcmFtZXdvcmsvdHJhbnNwb3J0cydcbmltcG9ydCB7XG4gIG1vY2tXZWJTb2NrZXRQcm92aWRlcixcbiAgTW9ja1dlYnNvY2tldFNlcnZlcixcbiAgc2V0RW52VmFyaWFibGVzLFxuICBUZXN0QWRhcHRlcixcbn0gZnJvbSAnQGNoYWlubGluay9leHRlcm5hbC1hZGFwdGVyLWZyYW1ld29yay91dGlsL3Rlc3RpbmctdXRpbHMnXG5pbXBvcnQgRmFrZVRpbWVycyBmcm9tICdAc2lub25qcy9mYWtlLXRpbWVycydcbmltcG9ydCB7IG1vY2tDcnlwdG9Md2JhV2ViU29ja2V0U2VydmVyIH0gZnJvbSAnLi9maXh0dXJlcydcblxuZGVzY3JpYmUoJ2NyeXB0by1sd2JhIHdlYnNvY2tldCcsICgpID0+IHtcbiAgbGV0IG1vY2tXc1NlcnZlcjogTW9ja1dlYnNvY2tldFNlcnZlciB8IHVuZGVmaW5lZFxuICBsZXQgdGVzdEFkYXB0ZXI6IFRlc3RBZGFwdGVyXG4gIGxldCBvbGRFbnY6IE5vZGVKUy5Qcm9jZXNzRW52XG5cbiAgY29uc3Qgd3NFbmRwb2ludCA9ICd3czovL2xvY2FsaG9zdDo5MDkwL3Y0L3RpbWVzZXJpZXMtc3RyZWFtL2Fzc2V0LXF1b3RlcydcblxuICBjb25zdCByZXF1ZXN0UGF5bG9hZCA9IHtcbiAgICBlbmRwb2ludDogJ2NyeXB0by1sd2JhJyxcbiAgICBiYXNlOiAnRVRIJyxcbiAgICBxdW90ZTogJ1VTRCcsXG4gIH1cblxuICBiZWZvcmVBbGwoYXN5bmMgKCkgPT4ge1xuICAgIC8vIHNuYXBzaG90IGN1cnJlbnQgZW52IGFuZCBzZXQgdGhlIG9uZXMgd2UgbmVlZCBmb3IgdGVzdHNcbiAgICBvbGRFbnYgPSB7IC4uLnByb2Nlc3MuZW52IH1cbiAgICBwcm9jZXNzLmVudlsnV1NfU1VCU0NSSVBUSU9OX1RUTCddID0gJzUwMDAnXG4gICAgcHJvY2Vzcy5lbnZbJ0NBQ0hFX01BWF9BR0UnXSA9ICc1MDAwJ1xuICAgIHByb2Nlc3MuZW52WydDQUNIRV9QT0xMSU5HX01BWF9SRVRSSUVTJ10gPSAnMCdcbiAgICBwcm9jZXNzLmVudlsnV1NfQVBJX0VORFBPSU5UJ10gPSB3c0VuZHBvaW50XG4gICAgcHJvY2Vzcy5lbnZbJ0FQSV9LRVknXSA9ICdmYWtlLWFwaS1rZXknXG5cbiAgICAvLyBtb2NrIFdTIHByb3ZpZGVyICsgc2VydmVyXG4gICAgbW9ja1dlYlNvY2tldFByb3ZpZGVyKFdlYlNvY2tldENsYXNzUHJvdmlkZXIpXG4gICAgbW9ja1dzU2VydmVyID0gbW9ja0NyeXB0b0x3YmFXZWJTb2NrZXRTZXJ2ZXIod3NFbmRwb2ludClcblxuICAgIC8vIHN0YXJ0IGFkYXB0ZXIgd2l0aCBmYWtlIHRpbWVyc1xuICAgIC8vIGVzbGludC1kaXNhYmxlLW5leHQtbGluZSBAdHlwZXNjcmlwdC1lc2xpbnQvbm8tdmFyLXJlcXVpcmVzXG4gICAgY29uc3QgeyBhZGFwdGVyIH0gPSByZXF1aXJlKCcuLi8uLi9zcmMnKVxuICAgIHRlc3RBZGFwdGVyID0gYXdhaXQgVGVzdEFkYXB0ZXIuc3RhcnRXaXRoTW9ja2VkQ2FjaGUoYWRhcHRlciwge1xuICAgICAgY2xvY2s6IEZha2VUaW1lcnMuaW5zdGFsbCgpLFxuICAgIH0pXG5cbiAgICAvLyB3YXJtIHRoZSBjYWNoZSBvbmNlIHNvIGJhY2tncm91bmQgZXhlY3V0ZSBzdGFydHNcbiAgICBhd2FpdCB0ZXN0QWRhcHRlci5yZXF1ZXN0KHJlcXVlc3RQYXlsb2FkKVxuICAgIGF3YWl0IHRlc3RBZGFwdGVyLndhaXRGb3JDYWNoZSgxKVxuICB9KVxuXG4gIGFmdGVyQWxsKGFzeW5jICgpID0+IHtcbiAgICBzZXRFbnZWYXJpYWJsZXMob2xkRW52KVxuICAgIG1vY2tXc1NlcnZlcj8uY2xvc2UoKVxuICAgIHRlc3RBZGFwdGVyLmNsb2NrPy51bmluc3RhbGwoKVxuICAgIGF3YWl0IHRlc3RBZGFwdGVyLmFwaS5jbG9zZSgpXG4gIH0pXG5cbiAgZGVzY3JpYmUoJ2hhcHB5IHBhdGgnLCAoKSA9PiB7XG4gICAgaXQoJ3JldHVybnMgYSBzdWNjZXNzZnVsIHJlc3BvbnNlJywgYXN5bmMgKCkgPT4ge1xuICAgICAgY29uc3QgcmVzcG9uc2UgPSBhd2FpdCB0ZXN0QWRhcHRlci5yZXF1ZXN0KHJlcXVlc3RQYXlsb2FkKVxuICAgICAgZXhwZWN0KHJlc3BvbnNlLmpzb24oKSkudG9NYXRjaFNuYXBzaG90KClcbiAgICB9KVxuXG4gICAgaXQoJ3JldHVybnMgYSBzdWNjZXNzZnVsIHJlc3BvbnNlIGZvciBjcnlwdG8gYWxpYXMnLCBhc3luYyAoKSA9PiB7XG4gICAgICByZXF1ZXN0UGF5bG9hZC5lbmRwb2ludCA9ICdjcnlwdG8nXG4gICAgICBjb25zdCByZXNwb25zZSA9IGF3YWl0IHRlc3RBZGFwdGVyLnJlcXVlc3QocmVxdWVzdFBheWxvYWQpXG4gICAgICBleHBlY3QocmVzcG9uc2UuanNvbigpKS50b01hdGNoU25hcHNob3QoKVxuICAgIH0pXG5cbiAgICBpdCgncmV0dXJucyBhIHN1Y2Nlc3NmdWwgcmVzcG9uc2UgZm9yIHByaWNlIGFsaWFzJywgYXN5bmMgKCkgPT4ge1xuICAgICAgcmVxdWVzdFBheWxvYWQuZW5kcG9pbnQgPSAncHJpY2UnXG4gICAgICBjb25zdCByZXNwb25zZSA9IGF3YWl0IHRlc3RBZGFwdGVyLnJlcXVlc3QocmVxdWVzdFBheWxvYWQpXG4gICAgICBleHBlY3QocmVzcG9uc2UuanNvbigpKS50b01hdGNoU25hcHNob3QoKVxuICAgIH0pXG4gIH0pXG5cbiAgZGVzY3JpYmUoJ3ZhbGlkYXRpb24gZXJyb3JzJywgKCkgPT4ge1xuICAgIGl0KCdmYWlscyBvbiBlbXB0eSBib2R5JywgYXN5bmMgKCkgPT4ge1xuICAgICAgY29uc3QgcmVzcG9uc2UgPSBhd2FpdCB0ZXN0QWRhcHRlci5yZXF1ZXN0KHt9KVxuICAgICAgZXhwZWN0KHJlc3BvbnNlLnN0YXR1c0NvZGUpLnRvRXF1YWwoNDAwKVxuICAgIH0pXG5cbiAgICBpdCgnZmFpbHMgb24gZW1wdHkgZGF0YScsIGFzeW5jICgpID0+IHtcbiAgICAgIGNvbnN0IHJlc3BvbnNlID0gYXdhaXQgdGVzdEFkYXB0ZXIucmVxdWVzdCh7IGVuZHBvaW50OiAnY3J5cHRvLWx3YmEnIH0pXG4gICAgICBleHBlY3QocmVzcG9uc2Uuc3RhdHVzQ29kZSkudG9FcXVhbCg0MDApXG4gICAgfSlcblxuICAgIGl0KCdmYWlscyBvbiBtaXNzaW5nIGJhc2UnLCBhc3luYyAoKSA9PiB7XG4gICAgICBjb25zdCByZXNwb25zZSA9IGF3YWl0IHRlc3RBZGFwdGVyLnJlcXVlc3QoeyBlbmRwb2ludDogJ2NyeXB0by1sd2JhJywgcXVvdGU6ICdVU0QnIH0pXG4gICAgICBleHBlY3QocmVzcG9uc2Uuc3RhdHVzQ29kZSkudG9FcXVhbCg0MDApXG4gICAgfSlcblxuICAgIGl0KCdmYWlscyBvbiBtaXNzaW5nIHF1b3RlJywgYXN5bmMgKCkgPT4ge1xuICAgICAgY29uc3QgcmVzcG9uc2UgPSBhd2FpdCB0ZXN0QWRhcHRlci5yZXF1ZXN0KHsgZW5kcG9pbnQ6ICdjcnlwdG8tbHdiYScsIGJhc2U6ICdFVEgnIH0pXG4gICAgICBleHBlY3QocmVzcG9uc2Uuc3RhdHVzQ29kZSkudG9FcXVhbCg0MDApXG4gICAgfSlcbiAgfSlcblxuICBkZXNjcmliZSgnaW52YXJpYW50IHZpb2xhdGlvbiBoYW5kbGluZycsICgpID0+IHtcbiAgICBpdCgnaGFuZGxlcyBhIHZpb2xhdGlvbiBwYXlsb2FkIGZyb20gdGhlIHN0cmVhbScsIGFzeW5jICgpID0+IHtcbiAgICAgIC8vIGFkdmFuY2UgdGhlIGZha2UgY2xvY2sgc28gdGhlIG1vY2tlZCBzZXJ2ZXIgcHVzaGVzIHRoZSBuZXh0IG1lc3NhZ2VcbiAgICAgIHRlc3RBZGFwdGVyLmNsb2NrLnRpY2soMTAwMClcbiAgICAgIGNvbnN0IHJlc3BvbnNlID0gYXdhaXQgdGVzdEFkYXB0ZXIucmVxdWVzdChyZXF1ZXN0UGF5bG9hZClcbiAgICAgIGV4cGVjdChyZXNwb25zZS5qc29uKCkpLnRvTWF0Y2hTbmFwc2hvdCgpXG4gICAgfSlcbiAgfSlcbn0pXG4iXX0= diff --git a/packages/sources/coinmetrics-lwba/test/integration/fixtures.d.ts b/packages/sources/coinmetrics-lwba/test/integration/fixtures.d.ts new file mode 100644 index 00000000000..1d30922f3ec --- /dev/null +++ b/packages/sources/coinmetrics-lwba/test/integration/fixtures.d.ts @@ -0,0 +1,3 @@ +import { MockWebsocketServer } from '@chainlink/external-adapter-framework/util/testing-utils' +export declare const mockCryptoLwbaWebSocketServer: (URL: string) => MockWebsocketServer +//# sourceMappingURL=fixtures.d.ts.map diff --git a/packages/sources/coinmetrics-lwba/test/integration/fixtures.d.ts.map b/packages/sources/coinmetrics-lwba/test/integration/fixtures.d.ts.map new file mode 100644 index 00000000000..3f8bfef472d --- /dev/null +++ b/packages/sources/coinmetrics-lwba/test/integration/fixtures.d.ts.map @@ -0,0 +1 @@ +{"version":3,"file":"fixtures.d.ts","sourceRoot":"","sources":["fixtures.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,mBAAmB,EAAE,MAAM,0DAA0D,CAAA;AAa9F,eAAO,MAAM,6BAA6B,GAAI,KAAK,MAAM,wBAexD,CAAA"} \ No newline at end of file diff --git a/packages/sources/coinmetrics-lwba/test/integration/fixtures.js b/packages/sources/coinmetrics-lwba/test/integration/fixtures.js new file mode 100644 index 00000000000..4297310f90a --- /dev/null +++ b/packages/sources/coinmetrics-lwba/test/integration/fixtures.js @@ -0,0 +1,32 @@ +'use strict' +Object.defineProperty(exports, '__esModule', { value: true }) +exports.mockCryptoLwbaWebSocketServer = void 0 +const testing_utils_1 = require('@chainlink/external-adapter-framework/util/testing-utils') +const wsLwbaResponseBody = { + pair: 'eth-usd', + time: '2023-03-08T04:04:33.750000000Z', + ask_price: '1562.4083581615457', + ask_size: '31.63132041', + bid_price: '1562.3384315992228', + bid_size: '64.67517577', + mid_price: '1562.3733948803842', + spread: '0.000044756626394287605', + cm_sequence_id: '282', +} +const mockCryptoLwbaWebSocketServer = (URL) => { + const mockWsServer = new testing_utils_1.MockWebsocketServer(URL, { mock: false }) + mockWsServer.on('connection', (socket) => { + const parseMessage = () => { + setTimeout(() => socket.send(JSON.stringify(wsLwbaResponseBody)), 10) + const wsLwbaResponseBodyInvariantViolation = { + ...wsLwbaResponseBody, + ask_price: Number(wsLwbaResponseBody.mid_price) - 0.1, + } + setTimeout(() => socket.send(JSON.stringify(wsLwbaResponseBodyInvariantViolation)), 50) + } + parseMessage() + }) + return mockWsServer +} +exports.mockCryptoLwbaWebSocketServer = mockCryptoLwbaWebSocketServer +//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiZml4dHVyZXMuanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyJmaXh0dXJlcy50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiOzs7QUFDQSw0RkFBOEY7QUFFOUYsTUFBTSxrQkFBa0IsR0FBZ0M7SUFDdEQsSUFBSSxFQUFFLFNBQVM7SUFDZixJQUFJLEVBQUUsZ0NBQWdDO0lBQ3RDLFNBQVMsRUFBRSxvQkFBb0I7SUFDL0IsUUFBUSxFQUFFLGFBQWE7SUFDdkIsU0FBUyxFQUFFLG9CQUFvQjtJQUMvQixRQUFRLEVBQUUsYUFBYTtJQUN2QixTQUFTLEVBQUUsb0JBQW9CO0lBQy9CLE1BQU0sRUFBRSx5QkFBeUI7SUFDakMsY0FBYyxFQUFFLEtBQUs7Q0FDdEIsQ0FBQTtBQUNNLE1BQU0sNkJBQTZCLEdBQUcsQ0FBQyxHQUFXLEVBQUUsRUFBRTtJQUMzRCxNQUFNLFlBQVksR0FBRyxJQUFJLG1DQUFtQixDQUFDLEdBQUcsRUFBRSxFQUFFLElBQUksRUFBRSxLQUFLLEVBQUUsQ0FBQyxDQUFBO0lBQ2xFLFlBQVksQ0FBQyxFQUFFLENBQUMsWUFBWSxFQUFFLENBQUMsTUFBTSxFQUFFLEVBQUU7UUFDdkMsTUFBTSxZQUFZLEdBQUcsR0FBRyxFQUFFO1lBQ3hCLFVBQVUsQ0FBQyxHQUFHLEVBQUUsQ0FBQyxNQUFNLENBQUMsSUFBSSxDQUFDLElBQUksQ0FBQyxTQUFTLENBQUMsa0JBQWtCLENBQUMsQ0FBQyxFQUFFLEVBQUUsQ0FBQyxDQUFBO1lBRXJFLE1BQU0sb0NBQW9DLEdBQUc7Z0JBQzNDLEdBQUcsa0JBQWtCO2dCQUNyQixTQUFTLEVBQUUsTUFBTSxDQUFDLGtCQUFrQixDQUFDLFNBQVMsQ0FBQyxHQUFHLEdBQUc7YUFDdEQsQ0FBQTtZQUNELFVBQVUsQ0FBQyxHQUFHLEVBQUUsQ0FBQyxNQUFNLENBQUMsSUFBSSxDQUFDLElBQUksQ0FBQyxTQUFTLENBQUMsb0NBQW9DLENBQUMsQ0FBQyxFQUFFLEVBQUUsQ0FBQyxDQUFBO1FBQ3pGLENBQUMsQ0FBQTtRQUNELFlBQVksRUFBRSxDQUFBO0lBQ2hCLENBQUMsQ0FBQyxDQUFBO0lBQ0YsT0FBTyxZQUFZLENBQUE7QUFDckIsQ0FBQyxDQUFBO0FBZlksUUFBQSw2QkFBNkIsaUNBZXpDIiwic291cmNlc0NvbnRlbnQiOlsiaW1wb3J0IHsgV3NDcnlwdG9Md2JhU3VjY2Vzc1Jlc3BvbnNlIH0gZnJvbSAnQGNoYWlubGluay9jb2lubWV0cmljcy1hZGFwdGVyL3RyYW5zcG9ydC9sd2JhJ1xuaW1wb3J0IHsgTW9ja1dlYnNvY2tldFNlcnZlciB9IGZyb20gJ0BjaGFpbmxpbmsvZXh0ZXJuYWwtYWRhcHRlci1mcmFtZXdvcmsvdXRpbC90ZXN0aW5nLXV0aWxzJ1xuXG5jb25zdCB3c0x3YmFSZXNwb25zZUJvZHk6IFdzQ3J5cHRvTHdiYVN1Y2Nlc3NSZXNwb25zZSA9IHtcbiAgcGFpcjogJ2V0aC11c2QnLFxuICB0aW1lOiAnMjAyMy0wMy0wOFQwNDowNDozMy43NTAwMDAwMDBaJyxcbiAgYXNrX3ByaWNlOiAnMTU2Mi40MDgzNTgxNjE1NDU3JyxcbiAgYXNrX3NpemU6ICczMS42MzEzMjA0MScsXG4gIGJpZF9wcmljZTogJzE1NjIuMzM4NDMxNTk5MjIyOCcsXG4gIGJpZF9zaXplOiAnNjQuNjc1MTc1NzcnLFxuICBtaWRfcHJpY2U6ICcxNTYyLjM3MzM5NDg4MDM4NDInLFxuICBzcHJlYWQ6ICcwLjAwMDA0NDc1NjYyNjM5NDI4NzYwNScsXG4gIGNtX3NlcXVlbmNlX2lkOiAnMjgyJyxcbn1cbmV4cG9ydCBjb25zdCBtb2NrQ3J5cHRvTHdiYVdlYlNvY2tldFNlcnZlciA9IChVUkw6IHN0cmluZykgPT4ge1xuICBjb25zdCBtb2NrV3NTZXJ2ZXIgPSBuZXcgTW9ja1dlYnNvY2tldFNlcnZlcihVUkwsIHsgbW9jazogZmFsc2UgfSlcbiAgbW9ja1dzU2VydmVyLm9uKCdjb25uZWN0aW9uJywgKHNvY2tldCkgPT4ge1xuICAgIGNvbnN0IHBhcnNlTWVzc2FnZSA9ICgpID0+IHtcbiAgICAgIHNldFRpbWVvdXQoKCkgPT4gc29ja2V0LnNlbmQoSlNPTi5zdHJpbmdpZnkod3NMd2JhUmVzcG9uc2VCb2R5KSksIDEwKVxuXG4gICAgICBjb25zdCB3c0x3YmFSZXNwb25zZUJvZHlJbnZhcmlhbnRWaW9sYXRpb24gPSB7XG4gICAgICAgIC4uLndzTHdiYVJlc3BvbnNlQm9keSxcbiAgICAgICAgYXNrX3ByaWNlOiBOdW1iZXIod3NMd2JhUmVzcG9uc2VCb2R5Lm1pZF9wcmljZSkgLSAwLjEsXG4gICAgICB9XG4gICAgICBzZXRUaW1lb3V0KCgpID0+IHNvY2tldC5zZW5kKEpTT04uc3RyaW5naWZ5KHdzTHdiYVJlc3BvbnNlQm9keUludmFyaWFudFZpb2xhdGlvbikpLCA1MClcbiAgICB9XG4gICAgcGFyc2VNZXNzYWdlKClcbiAgfSlcbiAgcmV0dXJuIG1vY2tXc1NlcnZlclxufVxuIl19 diff --git a/packages/sources/layer2-sequencer-health/src/config/index.ts b/packages/sources/layer2-sequencer-health/src/config/index.ts index e842aeec084..72a106594c4 100644 --- a/packages/sources/layer2-sequencer-health/src/config/index.ts +++ b/packages/sources/layer2-sequencer-health/src/config/index.ts @@ -48,7 +48,7 @@ export const ENV_KATANA_RPC_ENDPOINT = 'KATANA_RPC_ENDPOINT' export const ENV_ARBITRUM_CHAIN_ID = 'ARBITRUM_CHAIN_ID' export const ENV_OPTIMISM_CHAIN_ID = 'OPTIMISM_CHAIN_ID' export const ENV_BASE_CHAIN_ID = 'BASE_CHAIN_ID' -export const ENV_LINEA_CHAIN_ID = 'BASE_CHAIN_ID' +export const ENV_LINEA_CHAIN_ID = 'LINEA_CHAIN_ID' export const ENV_METIS_CHAIN_ID = 'METIS_CHAIN_ID' export const ENV_SCROLL_CHAIN_ID = 'SCROLL_CHAIN_ID' export const ENV_ZKSYNC_CHAIN_ID = 'ZKSYNC_CHAIN_ID' @@ -202,7 +202,7 @@ export const CHAIN_DELTA: Record = { const DEFAULT_METIS_HEALTH_ENDPOINT = 'https://andromeda-healthy.metisdevops.link/health' const DEFAULT_SCROLL_HEALTH_ENDPOINT = 'https://venus.scroll.io/v1/sequencer/status' -export type HeathEndpoints = Record< +export type HealthEndpoints = Record< Networks, { endpoint: string | undefined @@ -211,7 +211,7 @@ export type HeathEndpoints = Record< } > -export const HEALTH_ENDPOINTS: HeathEndpoints = { +export const HEALTH_ENDPOINTS: HealthEndpoints = { [Networks.Arbitrum]: { endpoint: util.getEnv('ARBITRUM_HEALTH_ENDPOINT'), responsePath: [], diff --git a/packages/sources/layer2-sequencer-health/src/endpoint/health.ts b/packages/sources/layer2-sequencer-health/src/endpoint/health.ts index 54299a9a79a..e8453e2ec75 100644 --- a/packages/sources/layer2-sequencer-health/src/endpoint/health.ts +++ b/packages/sources/layer2-sequencer-health/src/endpoint/health.ts @@ -110,7 +110,7 @@ export const execute: ExecuteWithConfig = async (request, _, con ) return false } - } catch (e: any) { + } catch (e: unknown) { const error = e as Error Logger.error( `[${network}] Method ${fn.name} failed: ${error.message}. Network ${network} considered unhealthy`, @@ -137,11 +137,12 @@ export const execute: ExecuteWithConfig = async (request, _, con let isHealthyByTransaction try { isHealthyByTransaction = await getStatusByTransaction(network, config) - } catch (e: any) { + } catch (e: unknown) { + const error = e as { code?: number; message?: string } throw new AdapterDataProviderError({ network, - message: util.mapRPCErrorMessage(e?.code, e?.message), - cause: e, + message: util.mapRPCErrorMessage(String(error?.code ?? ''), error?.message ?? ''), + cause: e instanceof Error ? e : undefined, }) } if (isHealthyByTransaction) { diff --git a/packages/sources/layer2-sequencer-health/src/evm.ts b/packages/sources/layer2-sequencer-health/src/evm.ts index cb41007e553..4ae68feb383 100644 --- a/packages/sources/layer2-sequencer-health/src/evm.ts +++ b/packages/sources/layer2-sequencer-health/src/evm.ts @@ -125,6 +125,32 @@ export const sendEVMDummyTransaction = async ( }) } +// Pure functions for block validation - exported for unit testing +export const isPastBlock = (block: number, lastSeenBlockNumber: number): boolean => + block <= lastSeenBlockNumber + +export const isStaleBlock = ( + block: number, + lastSeenBlockNumber: number, + lastSeenTimestamp: number, + delta: number, +): boolean => { + return isPastBlock(block, lastSeenBlockNumber) && Date.now() - lastSeenTimestamp >= delta +} + +export const isValidBlock = ( + block: number, + lastSeenBlockNumber: number, + deltaBlocks: number, +): boolean => lastSeenBlockNumber - block <= deltaBlocks + +export const parseHexBlockNumber = (hexBlock: string | number): number => { + if (!hexBlock) { + throw new Error('Block number is empty or undefined') + } + return BigNumber.from(hexBlock).toNumber() +} + const lastSeenBlock: Record = { [Networks.Arbitrum]: { block: 0, @@ -191,13 +217,6 @@ const lastSeenBlock: Record = export const checkOptimisticRollupBlockHeight = ( network: EVMNetworks, ): ((config: ExtendedConfig) => Promise) => { - const _isPastBlock = (block: number) => block <= lastSeenBlock[network].block - const _isStaleBlock = (block: number, delta: number): boolean => { - return _isPastBlock(block) && Date.now() - lastSeenBlock[network].timestamp >= delta - } - // If the request hit a replica node that fell behind, the block could be previous to the last seen. Including a deltaBlocks range to consider this case. - const _isValidBlock = (block: number, deltaBlocks: number) => - lastSeenBlock[network].block - block <= deltaBlocks const _updateLastSeenBlock = (block: number): void => { lastSeenBlock[network] = { block, @@ -212,17 +231,19 @@ export const checkOptimisticRollupBlockHeight = ( promise: async () => await requestBlockHeight(network), retryConfig, }) - if (!_isValidBlock(block, deltaBlocks)) + if (!isValidBlock(block, lastSeenBlock[network].block, deltaBlocks)) throw new AdapterResponseInvalidError({ message: `Block found #${block} is previous to last seen #${lastSeenBlock[network].block} with more than ${deltaBlocks} difference`, }) - if (!_isStaleBlock(block, delta)) { + if ( + !isStaleBlock(block, lastSeenBlock[network].block, lastSeenBlock[network].timestamp, delta) + ) { Logger.info( `[${network}] Block #${block} is not considered stale at ${Date.now()}. Last seen block #${ lastSeenBlock[network].block } was at ${lastSeenBlock[network].timestamp}`, ) - if (!_isPastBlock(block)) _updateLastSeenBlock(block) + if (!isPastBlock(block, lastSeenBlock[network].block)) _updateLastSeenBlock(block) return true } Logger.warn( diff --git a/packages/sources/layer2-sequencer-health/src/network.ts b/packages/sources/layer2-sequencer-health/src/network.ts index 902e4f33eb8..f072502c961 100644 --- a/packages/sources/layer2-sequencer-health/src/network.ts +++ b/packages/sources/layer2-sequencer-health/src/network.ts @@ -11,7 +11,7 @@ const NO_ISSUE_MSG = 'This is an error that the EA uses to determine whether or not the L2 Sequencer is healthy. It does not mean that there is an issue with the EA.' // These errors come from the Sequencer when submitting an empty transaction -const sequencerOnlineErrors: Record = { +export const sequencerOnlineErrors: Record = { [Networks.Arbitrum]: ['gas price too low', 'forbidden sender address', 'intrinsic gas too low'], // TODO: Optimism error needs to be confirmed by their team [Networks.Optimism]: ['cannot accept 0 gas price transaction'], @@ -91,43 +91,53 @@ const sendEmptyTransaction = async (network: Networks, config: ExtendedConfig): } } -const isExpectedErrorMessage = (network: Networks, error: Error) => { - const _getErrorMessage = (error: Error): string => { - const paths: Record = { - [Networks.Arbitrum]: ['error', 'message'], - [Networks.Optimism]: ['error', 'message'], - [Networks.Base]: ['error', 'message'], - [Networks.Linea]: ['error', 'message'], - [Networks.Metis]: ['error', 'message'], - [Networks.Scroll]: ['error', 'error', 'message'], - [Networks.Starkware]: ['message'], - [Networks.zkSync]: ['error', 'message'], - [Networks.Ink]: ['error', 'message'], - [Networks.Mantle]: ['error', 'message'], - [Networks.Unichain]: ['error', 'message'], - [Networks.Soneium]: ['error', 'message'], - [Networks.Celo]: ['error', 'message'], - [Networks.Xlayer]: ['error', 'message'], - [Networks.Megaeth]: ['error', 'message'], - [Networks.Katana]: ['error', 'message'], - } - return (Requester.getResult(error, paths[network]) as string) || '' - } - const actualError = _getErrorMessage(error) - for (const expectedError of sequencerOnlineErrors[network]) { +export const errorMessagePaths: Record = { + [Networks.Arbitrum]: ['error', 'message'], + [Networks.Optimism]: ['error', 'message'], + [Networks.Base]: ['error', 'message'], + [Networks.Linea]: ['error', 'message'], + [Networks.Metis]: ['error', 'message'], + [Networks.Scroll]: ['error', 'error', 'message'], + [Networks.Starkware]: ['message'], + [Networks.zkSync]: ['error', 'message'], + [Networks.Ink]: ['error', 'message'], + [Networks.Mantle]: ['error', 'message'], + [Networks.Unichain]: ['error', 'message'], + [Networks.Soneium]: ['error', 'message'], + [Networks.Celo]: ['error', 'message'], + [Networks.Xlayer]: ['error', 'message'], + [Networks.Megaeth]: ['error', 'message'], + [Networks.Katana]: ['error', 'message'], +} + +export const getErrorMessageFromPath = (error: unknown, path: string[]): string => { + return (Requester.getResult(error, path) as string) || '' +} + +export const matchesExpectedError = (actualError: string, expectedErrors: string[]): boolean => { + for (const expectedError of expectedErrors) { if (actualError.includes(expectedError)) { - Logger.debug( - `[${network}] Transaction submission failed with an expected error ${actualError}.`, - ) return true } } - Logger.error( - `[${network}] Transaction submission failed with an unexpected error. ${NO_ISSUE_MSG} Error Message: ${error.message}`, - ) return false } +const isExpectedErrorMessage = (network: Networks, error: Error) => { + const actualError = getErrorMessageFromPath(error, errorMessagePaths[network]) + const isExpected = matchesExpectedError(actualError, sequencerOnlineErrors[network]) + if (isExpected) { + Logger.debug( + `[${network}] Transaction submission failed with an expected error ${actualError}.`, + ) + } else { + Logger.error( + `[${network}] Transaction submission failed with an unexpected error. ${NO_ISSUE_MSG} Error Message: ${error.message}`, + ) + } + return isExpected +} + export const checkNetworkProgress: NetworkHealthCheck = ( network: Networks, config: ExtendedConfig, diff --git a/packages/sources/layer2-sequencer-health/src/starkware.ts b/packages/sources/layer2-sequencer-health/src/starkware.ts index 56e417a58eb..c6e7255e68d 100644 --- a/packages/sources/layer2-sequencer-health/src/starkware.ts +++ b/packages/sources/layer2-sequencer-health/src/starkware.ts @@ -1,6 +1,6 @@ import { Logger } from '@chainlink/ea-bootstrap' +import { Account, BlockWithTxHashes, ec, InvokeFunctionResponse } from 'starknet' import { DEFAULT_PRIVATE_KEY, ExtendedConfig, Networks } from './config' -import { ec, Account, InvokeFunctionResponse, BlockWithTxHashes } from 'starknet' import { race, retry } from './network' interface StarkwareState { @@ -91,10 +91,11 @@ const getPendingBlockFromGateway = async ( promise: async () => config.starkwareConfig.provider.getBlockWithTxHashes('pending'), retryConfig: config.retryConfig, }) - } catch (e: any) { - if (e.providerStatusCode === 504) { + } catch (e: unknown) { + const error = e as { providerStatusCode?: number } + if (error.providerStatusCode === 504) { Logger.warn( - `[starkware] Request to fetch pending block timed out. Status Code: ${e.providerStatusCode}. Sequencer: UNHEALTHY`, + `[starkware] Request to fetch pending block timed out. Status Code: ${error.providerStatusCode}. Sequencer: UNHEALTHY`, ) } else { throw e @@ -105,7 +106,7 @@ const getPendingBlockFromGateway = async ( } } -const checkBatcherHealthy = ( +export const checkBatcherHealthy = ( previousBlock: BlockWithTxHashes | null, currentBlock: BlockWithTxHashes, ): boolean => { @@ -121,8 +122,7 @@ const checkBatcherHealthy = ( Logger.info( `[starkware] Pending Starkware block still has parent hash of ${currentBlock.parent_hash}. Checking to see if it is still processing transactions...`, ) - const hasNewTxns = - Object.keys(currentBlock.transactions).length > previousBlock.transactions.length + const hasNewTxns = currentBlock.transactions.length > previousBlock.transactions.length if (hasNewTxns) { Logger.info(`[starkware] Found new transactions in pending block. Sequencer: HEALTHY`) } else { diff --git a/packages/sources/layer2-sequencer-health/test/integration/__snapshots__/starkware.test.ts.snap b/packages/sources/layer2-sequencer-health/test/integration/__snapshots__/starkware.test.ts.snap new file mode 100644 index 00000000000..2467ee252aa --- /dev/null +++ b/packages/sources/layer2-sequencer-health/test/integration/__snapshots__/starkware.test.ts.snap @@ -0,0 +1,23 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`execute - starkware network starkware network healthy should return success when pending block check succeeds 1`] = ` +{ + "data": { + "result": 0, + }, + "jobRunID": "1", + "result": 0, + "statusCode": 200, +} +`; + +exports[`execute - starkware network starkware network healthy should return success when transaction submission returns expected error 1`] = ` +{ + "data": { + "result": 0, + }, + "jobRunID": "1", + "result": 0, + "statusCode": 200, +} +`; diff --git a/packages/sources/layer2-sequencer-health/test/integration/__snapshots__/starkwareUnhealthy.test.ts.snap b/packages/sources/layer2-sequencer-health/test/integration/__snapshots__/starkwareUnhealthy.test.ts.snap new file mode 100644 index 00000000000..8425809d4c6 --- /dev/null +++ b/packages/sources/layer2-sequencer-health/test/integration/__snapshots__/starkwareUnhealthy.test.ts.snap @@ -0,0 +1,23 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`execute - starkware network unhealthy starkware network unhealthy should return unhealthy when gateway times out and transaction fails with unknown error 1`] = ` +{ + "data": { + "result": 1, + }, + "jobRunID": "1", + "result": 1, + "statusCode": 200, +} +`; + +exports[`execute - starkware network unhealthy starkware network unhealthy should return unhealthy when requireTxFailure is true and tx fails with unknown error 1`] = ` +{ + "data": { + "result": 1, + }, + "jobRunID": "1", + "result": 1, + "statusCode": 200, +} +`; diff --git a/packages/sources/layer2-sequencer-health/test/integration/__snapshots__/validation.test.ts.snap b/packages/sources/layer2-sequencer-health/test/integration/__snapshots__/validation.test.ts.snap new file mode 100644 index 00000000000..3d97d957344 --- /dev/null +++ b/packages/sources/layer2-sequencer-health/test/integration/__snapshots__/validation.test.ts.snap @@ -0,0 +1,64 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`execute - validation errors invalid parameter values should return error for empty network value 1`] = ` +{ + "error": { + "feedID": "{"data":{"network":"","endpoint":"health"}}", + "message": "Required parameter network must be non-null and non-empty", + "name": "AdapterError", + }, + "jobRunID": "1", + "status": "errored", + "statusCode": 400, +} +`; + +exports[`execute - validation errors invalid parameter values should return error for invalid network value 1`] = ` +{ + "error": { + "feedID": "{"data":{"network":"invalid-network","endpoint":"health"}}", + "message": "network parameter 'invalid-network' is not in the set of available options: arbitrum,base,linea,metis,optimism,scroll,starkware,zksync,ink,mantle,unichain,soneium,celo,xlayer,megaeth,katana", + "name": "AdapterError", + }, + "jobRunID": "1", + "status": "errored", + "statusCode": 400, +} +`; + +exports[`execute - validation errors invalid request format should handle request with no id 1`] = ` +{ + "data": { + "result": 0, + }, + "jobRunID": "1", + "result": 0, + "statusCode": 200, +} +`; + +exports[`execute - validation errors missing required parameters should return error when data is empty 1`] = ` +{ + "error": { + "feedID": "{"data":{"endpoint":"health"}}", + "message": "Required parameter network must be non-null and non-empty", + "name": "AdapterError", + }, + "jobRunID": "1", + "status": "errored", + "statusCode": 400, +} +`; + +exports[`execute - validation errors missing required parameters should return error when network is missing 1`] = ` +{ + "error": { + "feedID": "{"data":{"endpoint":"health"}}", + "message": "Required parameter network must be non-null and non-empty", + "name": "AdapterError", + }, + "jobRunID": "1", + "status": "errored", + "statusCode": 400, +} +`; diff --git a/packages/sources/layer2-sequencer-health/test/unit/health.test.ts b/packages/sources/layer2-sequencer-health/test/integration/health.test.ts similarity index 100% rename from packages/sources/layer2-sequencer-health/test/unit/health.test.ts rename to packages/sources/layer2-sequencer-health/test/integration/health.test.ts index cdc61a7c683..160a71be595 100644 --- a/packages/sources/layer2-sequencer-health/test/unit/health.test.ts +++ b/packages/sources/layer2-sequencer-health/test/integration/health.test.ts @@ -1,9 +1,9 @@ -import { DEFAULT_DELTA_TIME } from '../../src/config' +import { AdapterRequest } from '@chainlink/ea-bootstrap' import { useFakeTimers } from 'sinon' -import * as network from '../../src/network' import { makeExecute } from '../../src/adapter' +import { DEFAULT_DELTA_TIME } from '../../src/config' import { TInputParameters } from '../../src/endpoint' -import { AdapterRequest } from '@chainlink/ea-bootstrap' +import * as network from '../../src/network' describe('adapter', () => { describe('Adapter health check', () => { diff --git a/packages/sources/layer2-sequencer-health/test/integration/starkware.test.ts b/packages/sources/layer2-sequencer-health/test/integration/starkware.test.ts new file mode 100644 index 00000000000..0b0d35e9b32 --- /dev/null +++ b/packages/sources/layer2-sequencer-health/test/integration/starkware.test.ts @@ -0,0 +1,100 @@ +import { AdapterRequest, FastifyInstance } from '@chainlink/ea-bootstrap' +import { setEnvVariables } from '@chainlink/ea-test-helpers' +import { AddressInfo } from 'net' +import * as nock from 'nock' +import * as process from 'process' +import request, { SuperTest, Test } from 'supertest' +import { server as startServer } from '../../src' + +const STARKWARE_RPC_ENDPOINT = 'https://starknet-mainnet.public.blastapi.io' + +jest.mock('starknet', () => { + const originalModule = jest.requireActual('starknet') + return { + ...originalModule, + RpcProvider: jest.fn().mockImplementation(() => ({ + getBlockWithTxHashes: jest.fn().mockResolvedValue({ + parent_hash: 'mock-parent-hash', + transactions: ['tx1', 'tx2', 'tx3'], + }), + })), + Account: jest.fn().mockImplementation(() => ({ + execute: jest.fn().mockRejectedValue({ + message: 'Contract not found', + }), + })), + ec: originalModule.ec, + } +}) + +describe('execute - starkware network', () => { + const id = '1' + let fastify: FastifyInstance + let req: SuperTest + let oldEnv: NodeJS.ProcessEnv + + beforeAll(async () => { + oldEnv = JSON.parse(JSON.stringify(process.env)) + + process.env.CACHE_ENABLED = 'false' + process.env.STARKWARE_RPC_ENDPOINT = STARKWARE_RPC_ENDPOINT + + if (process.env.RECORD) { + nock.recorder.rec() + } + }) + + afterAll(() => { + setEnvVariables(oldEnv) + nock.restore() + nock.cleanAll() + nock.enableNetConnect() + if (process.env.RECORD) { + nock.recorder.play() + } + }) + + beforeEach(async () => { + fastify = await startServer() + req = request(`localhost:${(fastify.server.address() as AddressInfo).port}`) + }) + + afterEach((done) => { + fastify.close(done) + }) + + async function sendRequestAndExpectStatus(data: AdapterRequest, status: number) { + const response = await req + .post('/') + .send(data) + .set('Accept', '*/*') + .set('Content-Type', 'application/json') + .expect('Content-Type', /json/) + .expect(200) + expect(response.body.result).toEqual(status) + expect(response.body).toMatchSnapshot() + } + + describe('starkware network healthy', () => { + it('should return success when pending block check succeeds', async () => { + const data: AdapterRequest = { + id, + data: { + network: 'starkware', + }, + } + await sendRequestAndExpectStatus(data, 0) + }) + + it('should return success when transaction submission returns expected error', async () => { + const data: AdapterRequest = { + id, + data: { + network: 'starkware', + requireTxFailure: true, + }, + } + await sendRequestAndExpectStatus(data, 0) + }) + }) +}) diff --git a/packages/sources/layer2-sequencer-health/test/integration/starkwareUnhealthy.test.ts b/packages/sources/layer2-sequencer-health/test/integration/starkwareUnhealthy.test.ts new file mode 100644 index 00000000000..6adc7c67d53 --- /dev/null +++ b/packages/sources/layer2-sequencer-health/test/integration/starkwareUnhealthy.test.ts @@ -0,0 +1,102 @@ +import { AdapterRequest, FastifyInstance } from '@chainlink/ea-bootstrap' +import { setEnvVariables } from '@chainlink/ea-test-helpers' +import { AddressInfo } from 'net' +import * as nock from 'nock' +import * as process from 'process' +import request, { SuperTest, Test } from 'supertest' +import { server as startServer } from '../../src' + +const STARKWARE_RPC_ENDPOINT = 'https://starknet-mainnet.public.blastapi.io' + +// Mock starknet to simulate unhealthy conditions +jest.mock('starknet', () => { + const originalModule = jest.requireActual('starknet') + return { + ...originalModule, + RpcProvider: jest.fn().mockImplementation(() => ({ + getBlockWithTxHashes: jest.fn().mockRejectedValue({ + providerStatusCode: 504, + message: 'Gateway timeout', + }), + })), + Account: jest.fn().mockImplementation(() => ({ + execute: jest.fn().mockRejectedValue({ + message: 'Unknown error from sequencer', + }), + })), + ec: originalModule.ec, + } +}) + +describe('execute - starkware network unhealthy', () => { + const id = '1' + let fastify: FastifyInstance + let req: SuperTest + let oldEnv: NodeJS.ProcessEnv + + beforeAll(async () => { + oldEnv = JSON.parse(JSON.stringify(process.env)) + + process.env.CACHE_ENABLED = 'false' + process.env.STARKWARE_RPC_ENDPOINT = STARKWARE_RPC_ENDPOINT + + if (process.env.RECORD) { + nock.recorder.rec() + } + }) + + afterAll(() => { + setEnvVariables(oldEnv) + nock.restore() + nock.cleanAll() + nock.enableNetConnect() + if (process.env.RECORD) { + nock.recorder.play() + } + }) + + beforeEach(async () => { + fastify = await startServer() + req = request(`localhost:${(fastify.server.address() as AddressInfo).port}`) + }) + + afterEach((done) => { + fastify.close(done) + }) + + async function sendRequestAndExpectStatus(data: AdapterRequest, status: number) { + const response = await req + .post('/') + .send(data) + .set('Accept', '*/*') + .set('Content-Type', 'application/json') + .expect('Content-Type', /json/) + .expect(200) + expect(response.body.result).toEqual(status) + expect(response.body).toMatchSnapshot() + } + + describe('starkware network unhealthy', () => { + it('should return unhealthy when gateway times out and transaction fails with unknown error', async () => { + const data: AdapterRequest = { + id, + data: { + network: 'starkware', + }, + } + // Starkware defaults to requireTxFailure: true + await sendRequestAndExpectStatus(data, 1) + }) + + it('should return unhealthy when requireTxFailure is true and tx fails with unknown error', async () => { + const data: AdapterRequest = { + id, + data: { + network: 'starkware', + requireTxFailure: true, + }, + } + await sendRequestAndExpectStatus(data, 1) + }) + }) +}) diff --git a/packages/sources/layer2-sequencer-health/test/integration/validation.test.ts b/packages/sources/layer2-sequencer-health/test/integration/validation.test.ts new file mode 100644 index 00000000000..0578a7d20f7 --- /dev/null +++ b/packages/sources/layer2-sequencer-health/test/integration/validation.test.ts @@ -0,0 +1,147 @@ +import { AdapterRequest, FastifyInstance } from '@chainlink/ea-bootstrap' +import { setEnvVariables } from '@chainlink/ea-test-helpers' +import { AddressInfo } from 'net' +import * as nock from 'nock' +import * as process from 'process' +import request, { SuperTest, Test } from 'supertest' +import { server as startServer } from '../../src' + +describe('execute - validation errors', () => { + const id = '1' + let fastify: FastifyInstance + let req: SuperTest + let oldEnv: NodeJS.ProcessEnv + + beforeAll(async () => { + oldEnv = JSON.parse(JSON.stringify(process.env)) + + process.env.CACHE_ENABLED = 'false' + + if (process.env.RECORD) { + nock.recorder.rec() + } + }) + + afterAll(() => { + setEnvVariables(oldEnv) + nock.restore() + nock.cleanAll() + nock.enableNetConnect() + if (process.env.RECORD) { + nock.recorder.play() + } + }) + + beforeEach(async () => { + fastify = await startServer() + req = request(`localhost:${(fastify.server.address() as AddressInfo).port}`) + }) + + afterEach((done) => { + fastify.close(done) + }) + + describe('missing required parameters', () => { + it('should return error when network is missing', async () => { + const data: AdapterRequest = { + id, + data: {}, + } + + const response = await req + .post('/') + .send(data) + .set('Accept', '*/*') + .set('Content-Type', 'application/json') + .expect('Content-Type', /json/) + .expect(400) + + expect(response.body.status).toBe('errored') + expect(response.body.error).toBeDefined() + expect(response.body).toMatchSnapshot() + }) + + it('should return error when data is empty', async () => { + const data: AdapterRequest = { + id, + data: {}, + } + + const response = await req + .post('/') + .send(data) + .set('Accept', '*/*') + .set('Content-Type', 'application/json') + .expect('Content-Type', /json/) + .expect(400) + + expect(response.body.status).toBe('errored') + expect(response.body).toMatchSnapshot() + }) + }) + + describe('invalid parameter values', () => { + it('should return error for invalid network value', async () => { + const data: AdapterRequest = { + id, + data: { + network: 'invalid-network', + }, + } + + const response = await req + .post('/') + .send(data) + .set('Accept', '*/*') + .set('Content-Type', 'application/json') + .expect('Content-Type', /json/) + .expect(400) + + expect(response.body.status).toBe('errored') + expect(response.body.error).toBeDefined() + expect(response.body).toMatchSnapshot() + }) + + it('should return error for empty network value', async () => { + const data: AdapterRequest = { + id, + data: { + network: '', + }, + } + + const response = await req + .post('/') + .send(data) + .set('Accept', '*/*') + .set('Content-Type', 'application/json') + .expect('Content-Type', /json/) + .expect(400) + + expect(response.body.status).toBe('errored') + expect(response.body.error).toBeDefined() + expect(response.body).toMatchSnapshot() + }) + }) + + describe('invalid request format', () => { + it('should handle request with no id', async () => { + const data = { + data: { + network: 'arbitrum', + }, + } + + const response = await req + .post('/') + .send(data) + .set('Accept', '*/*') + .set('Content-Type', 'application/json') + .expect('Content-Type', /json/) + + // Should either succeed with default id or return appropriate error + expect(response.body).toBeDefined() + expect(response.body).toMatchSnapshot() + }) + }) +}) diff --git a/packages/sources/layer2-sequencer-health/test/unit/config.test.ts b/packages/sources/layer2-sequencer-health/test/unit/config.test.ts new file mode 100644 index 00000000000..1601165ecc8 --- /dev/null +++ b/packages/sources/layer2-sequencer-health/test/unit/config.test.ts @@ -0,0 +1,78 @@ +import { HEALTH_ENDPOINTS, Networks } from '../../src/config' + +describe('config', () => { + describe('HEALTH_ENDPOINTS processResponse', () => { + describe('Scroll', () => { + const processResponse = HEALTH_ENDPOINTS[Networks.Scroll].processResponse + + it('returns true when data.health equals 1', () => { + const data = { data: { health: 1 } } + expect(processResponse(data)).toBe(true) + }) + + it('returns false when data.health equals 0', () => { + const data = { data: { health: 0 } } + expect(processResponse(data)).toBe(false) + }) + + it('returns false when data.health is missing', () => { + const data = { data: {} } + expect(processResponse(data)).toBe(false) + }) + + it('returns false when data is empty', () => { + const data = {} + expect(processResponse(data)).toBe(false) + }) + }) + + describe('Metis', () => { + const processResponse = HEALTH_ENDPOINTS[Networks.Metis].processResponse + + it('returns true when healthy is truthy', () => { + const data = { healthy: true } + expect(processResponse(data)).toBe(true) + }) + + it('returns true when healthy is a truthy string', () => { + const data = { healthy: 'yes' } + expect(processResponse(data)).toBe(true) + }) + + it('returns false when healthy is false', () => { + const data = { healthy: false } + expect(processResponse(data)).toBe(false) + }) + + it('returns false when healthy is missing', () => { + const data = {} + expect(processResponse(data)).toBe(false) + }) + }) + + describe('networks without health endpoints', () => { + const networksWithoutEndpoints = [ + Networks.Arbitrum, + Networks.Optimism, + Networks.Base, + Networks.Linea, + Networks.Starkware, + Networks.zkSync, + Networks.Ink, + Networks.Mantle, + Networks.Unichain, + Networks.Soneium, + Networks.Celo, + Networks.Xlayer, + Networks.Megaeth, + Networks.Katana, + ] + + networksWithoutEndpoints.forEach((network) => { + it(`${network} processResponse returns undefined`, () => { + expect(HEALTH_ENDPOINTS[network].processResponse({})).toBe(undefined) + }) + }) + }) + }) +}) diff --git a/packages/sources/layer2-sequencer-health/test/unit/evm-utils.test.ts b/packages/sources/layer2-sequencer-health/test/unit/evm-utils.test.ts new file mode 100644 index 00000000000..6e479e993e5 --- /dev/null +++ b/packages/sources/layer2-sequencer-health/test/unit/evm-utils.test.ts @@ -0,0 +1,111 @@ +import { useFakeTimers } from 'sinon' +import * as evm from '../../src/evm' + +describe('evm utils', () => { + describe('isPastBlock', () => { + it('returns true when block equals lastSeenBlockNumber', () => { + expect(evm.isPastBlock(100, 100)).toBe(true) + }) + + it('returns true when block is less than lastSeenBlockNumber', () => { + expect(evm.isPastBlock(99, 100)).toBe(true) + }) + + it('returns false when block is greater than lastSeenBlockNumber', () => { + expect(evm.isPastBlock(101, 100)).toBe(false) + }) + + it('handles zero values', () => { + expect(evm.isPastBlock(0, 0)).toBe(true) + expect(evm.isPastBlock(0, 1)).toBe(true) + expect(evm.isPastBlock(1, 0)).toBe(false) + }) + }) + + describe('isValidBlock', () => { + it('returns true when block difference is within deltaBlocks', () => { + expect(evm.isValidBlock(95, 100, 5)).toBe(true) + }) + + it('returns true when block difference equals deltaBlocks', () => { + expect(evm.isValidBlock(95, 100, 5)).toBe(true) + }) + + it('returns false when block difference exceeds deltaBlocks', () => { + expect(evm.isValidBlock(94, 100, 5)).toBe(false) + }) + + it('returns true when current block is ahead of lastSeen', () => { + expect(evm.isValidBlock(105, 100, 5)).toBe(true) + }) + + it('handles deltaBlocks of zero', () => { + expect(evm.isValidBlock(100, 100, 0)).toBe(true) + expect(evm.isValidBlock(99, 100, 0)).toBe(false) + }) + }) + + describe('isStaleBlock', () => { + let clock: ReturnType + + beforeEach(() => { + clock = useFakeTimers() + }) + + afterEach(() => { + clock.restore() + }) + + it('returns false when block is not past lastSeenBlockNumber', () => { + clock.tick(10000) + expect(evm.isStaleBlock(101, 100, 0, 5000)).toBe(false) + }) + + it('returns false when time elapsed is less than delta', () => { + clock.tick(4000) + expect(evm.isStaleBlock(100, 100, 0, 5000)).toBe(false) + }) + + it('returns true when block is past and time elapsed exceeds delta', () => { + clock.tick(6000) + expect(evm.isStaleBlock(100, 100, 0, 5000)).toBe(true) + }) + + it('returns true when time elapsed equals delta exactly', () => { + clock.tick(5000) + expect(evm.isStaleBlock(99, 100, 0, 5000)).toBe(true) + }) + + it('handles timestamp in the past relative to current time', () => { + clock.tick(10000) + const pastTimestamp = 5000 + expect(evm.isStaleBlock(99, 100, pastTimestamp, 3000)).toBe(true) + }) + }) + + describe('parseHexBlockNumber', () => { + it('parses hex string to number', () => { + expect(evm.parseHexBlockNumber('0x1')).toBe(1) + expect(evm.parseHexBlockNumber('0xa')).toBe(10) + expect(evm.parseHexBlockNumber('0x10')).toBe(16) + expect(evm.parseHexBlockNumber('0xff')).toBe(255) + }) + + it('parses large hex block numbers', () => { + expect(evm.parseHexBlockNumber('0x100000')).toBe(1048576) + expect(evm.parseHexBlockNumber('0xffffff')).toBe(16777215) + }) + + it('parses decimal numbers', () => { + expect(evm.parseHexBlockNumber(100)).toBe(100) + }) + + it('throws error for empty string', () => { + expect(() => evm.parseHexBlockNumber('')).toThrow('Block number is empty or undefined') + }) + + it('throws error for zero value that is falsy', () => { + expect(() => evm.parseHexBlockNumber(0)).toThrow('Block number is empty or undefined') + }) + }) +}) diff --git a/packages/sources/layer2-sequencer-health/test/unit/evm.test.ts b/packages/sources/layer2-sequencer-health/test/unit/evm.test.ts deleted file mode 100644 index bb367b6b59f..00000000000 --- a/packages/sources/layer2-sequencer-health/test/unit/evm.test.ts +++ /dev/null @@ -1,130 +0,0 @@ -import { AxiosResponse, Requester } from '@chainlink/ea-bootstrap' -import { useFakeTimers } from 'sinon' -import { ExtendedConfig, makeConfig, Networks } from '../../src/config' -import * as evm from '../../src/evm' - -const getMockAxiosResponse = (response: unknown): AxiosResponse => - ({ - status: 204, - statusText: 'success', - headers: {}, - config: {}, - data: response, - } as AxiosResponse) - -const deltaChain = { - [Networks.Arbitrum]: 30000, - [Networks.Optimism]: 30000, -} - -describe('evm', () => { - describe('L2 Network health check', () => { - let clock: any - let config: ExtendedConfig - - beforeEach(() => { - clock = useFakeTimers() - config = makeConfig() - }) - - afterEach(() => { - clock.restore() - }) - - it('Stale blocks are unhealthy after Delta seconds', async () => { - jest.spyOn(Requester, 'request').mockReturnValue( - Promise.resolve( - getMockAxiosResponse({ - result: '0x1', - }), - ), - ) - const checkBlockHeight = evm.checkOptimisticRollupBlockHeight(Networks.Arbitrum) - config.deltaChain = deltaChain as Record - const delta = config.deltaChain[Networks.Arbitrum] - const timeBetweenCalls = 10 * 1000 - // During first two minutes of the block is not considered stale - for (let i = 0; i < delta / timeBetweenCalls; i++) { - expect(await checkBlockHeight(config)).toBe(true) - clock.tick(timeBetweenCalls) - } - // After delta time passed, is considered stale - expect(await checkBlockHeight(config)).toBe(false) - }) - - it('Blocks are healthy after Delta seconds if blocks change', async () => { - const checkBlockHeight = evm.checkOptimisticRollupBlockHeight(Networks.Optimism) - config.deltaBlocks = 0 - config.deltaChain = deltaChain as Record - const delta = config.deltaChain[Networks.Optimism] - const timeBetweenCalls = 10 * 1000 - // If blocks change, is not considered stale - for (let i = 0; i < delta / timeBetweenCalls; i++) { - jest.spyOn(Requester, 'request').mockReturnValue( - Promise.resolve( - getMockAxiosResponse({ - result: i.toString(16), - }), - ), - ) - expect(await checkBlockHeight(config)).toBe(true) - clock.tick(timeBetweenCalls) - } - // After delta time passed the current block should be considered healthy - expect(await checkBlockHeight(config)).toBe(true) - clock.tick(timeBetweenCalls) - expect(await checkBlockHeight(config)).toBe(true) - }) - - it('Blocks are healthy if current is previous to the last seen within a delta difference', async () => { - const checkBlockHeight = evm.checkOptimisticRollupBlockHeight(Networks.Arbitrum) - config.deltaBlocks = 5 - config.deltaChain = deltaChain as Record - jest.spyOn(Requester, 'request').mockReturnValue( - Promise.resolve( - getMockAxiosResponse({ - result: '0xa', - }), - ), - ) - - expect(await checkBlockHeight(config)).toBe(true) - - jest.spyOn(Requester, 'request').mockReturnValue( - Promise.resolve( - getMockAxiosResponse({ - result: '0x6', - }), - ), - ) - expect(await checkBlockHeight(config)).toBe(true) - - jest.spyOn(Requester, 'request').mockReturnValue( - Promise.resolve( - getMockAxiosResponse({ - result: '0x5', - }), - ), - ) - expect(await checkBlockHeight(config)).toBe(true) - - jest.spyOn(Requester, 'request').mockReturnValue( - Promise.resolve( - getMockAxiosResponse({ - result: '0x4', - }), - ), - ) - await expect(checkBlockHeight(config)).rejects.toThrow() - - jest.spyOn(Requester, 'request').mockReturnValue( - Promise.resolve( - getMockAxiosResponse({ - result: '0x3', - }), - ), - ) - await expect(checkBlockHeight(config)).rejects.toThrow() - }) - }) -}) diff --git a/packages/sources/layer2-sequencer-health/test/unit/network-utils.test.ts b/packages/sources/layer2-sequencer-health/test/unit/network-utils.test.ts new file mode 100644 index 00000000000..ee5ee9e834d --- /dev/null +++ b/packages/sources/layer2-sequencer-health/test/unit/network-utils.test.ts @@ -0,0 +1,157 @@ +import { Networks } from '../../src/config' +import { + errorMessagePaths, + getErrorMessageFromPath, + matchesExpectedError, + race, + sequencerOnlineErrors, +} from '../../src/network' + +describe('network utils', () => { + describe('matchesExpectedError', () => { + it('returns true when actual error contains expected error', () => { + expect(matchesExpectedError('gas price too low: 123', ['gas price too low'])).toBe(true) + }) + + it('returns true when actual error matches one of multiple expected errors', () => { + const expectedErrors = ['error1', 'error2', 'error3'] + expect(matchesExpectedError('prefix error2 suffix', expectedErrors)).toBe(true) + }) + + it('returns false when actual error does not contain any expected error', () => { + expect(matchesExpectedError('unknown error', ['gas price too low'])).toBe(false) + }) + + it('returns false for empty actual error', () => { + expect(matchesExpectedError('', ['gas price too low'])).toBe(false) + }) + + it('returns false for empty expected errors array', () => { + expect(matchesExpectedError('gas price too low', [])).toBe(false) + }) + + it('is case sensitive', () => { + expect(matchesExpectedError('GAS PRICE TOO LOW', ['gas price too low'])).toBe(false) + }) + }) + + describe('getErrorMessageFromPath', () => { + it('extracts message from nested error object', () => { + const error = { error: { message: 'test error' } } + expect(getErrorMessageFromPath(error, ['error', 'message'])).toBe('test error') + }) + + it('extracts message from deeply nested path', () => { + const error = { error: { error: { message: 'deep error' } } } + expect(getErrorMessageFromPath(error, ['error', 'error', 'message'])).toBe('deep error') + }) + + it('returns empty string for non-existent path', () => { + const error = { error: { code: 123 } } + expect(getErrorMessageFromPath(error, ['error', 'message'])).toBe('') + }) + + it('extracts message from top-level', () => { + const error = { message: 'top level error' } + expect(getErrorMessageFromPath(error, ['message'])).toBe('top level error') + }) + + it('returns empty string for null or undefined', () => { + expect(getErrorMessageFromPath(null, ['message'])).toBe('') + expect(getErrorMessageFromPath(undefined, ['message'])).toBe('') + }) + }) + + describe('sequencerOnlineErrors', () => { + it('has expected errors defined for Arbitrum', () => { + expect(sequencerOnlineErrors[Networks.Arbitrum]).toContain('gas price too low') + expect(sequencerOnlineErrors[Networks.Arbitrum]).toContain('forbidden sender address') + expect(sequencerOnlineErrors[Networks.Arbitrum]).toContain('intrinsic gas too low') + }) + + it('has expected errors defined for Optimism', () => { + expect(sequencerOnlineErrors[Networks.Optimism]).toContain( + 'cannot accept 0 gas price transaction', + ) + }) + + it('has expected errors defined for Starkware', () => { + expect(sequencerOnlineErrors[Networks.Starkware]).toContain('Contract not found') + expect(sequencerOnlineErrors[Networks.Starkware]).toContain('Known(OutOfRangeFee)') + }) + + it('has expected errors defined for all networks', () => { + const networks = Object.values(Networks) + networks.forEach((network) => { + expect(sequencerOnlineErrors[network]).toBeDefined() + expect(Array.isArray(sequencerOnlineErrors[network])).toBe(true) + expect(sequencerOnlineErrors[network].length).toBeGreaterThan(0) + }) + }) + }) + + describe('errorMessagePaths', () => { + it('defines standard path for most EVM networks', () => { + const evmNetworks = [ + Networks.Arbitrum, + Networks.Optimism, + Networks.Base, + Networks.Linea, + Networks.Metis, + Networks.zkSync, + Networks.Ink, + Networks.Mantle, + Networks.Unichain, + Networks.Soneium, + Networks.Celo, + Networks.Xlayer, + Networks.Megaeth, + Networks.Katana, + ] + evmNetworks.forEach((network) => { + expect(errorMessagePaths[network]).toEqual(['error', 'message']) + }) + }) + + it('defines nested path for Scroll', () => { + expect(errorMessagePaths[Networks.Scroll]).toEqual(['error', 'error', 'message']) + }) + + it('defines top-level path for Starkware', () => { + expect(errorMessagePaths[Networks.Starkware]).toEqual(['message']) + }) + }) + + describe('race', () => { + it('resolves with promise value when promise resolves before timeout', async () => { + const result = await race({ + promise: Promise.resolve('success'), + timeout: 1000, + error: 'Timeout error', + }) + expect(result).toBe('success') + }) + + it('rejects with error message when timeout occurs', async () => { + const slowPromise = new Promise((resolve) => setTimeout(resolve, 1000, 'slow')) + await expect( + race({ + promise: slowPromise, + timeout: 10, + error: 'Timeout error', + }), + ).rejects.toBe('Timeout error') + }) + + it('propagates promise rejection', async () => { + const failingPromise = Promise.reject(new Error('Promise failed')) + await expect( + race({ + promise: failingPromise, + timeout: 1000, + error: 'Timeout error', + }), + ).rejects.toThrow('Promise failed') + }) + }) +}) diff --git a/packages/sources/layer2-sequencer-health/test/unit/network.test.ts b/packages/sources/layer2-sequencer-health/test/unit/network.test.ts deleted file mode 100644 index 35f8c3be41f..00000000000 --- a/packages/sources/layer2-sequencer-health/test/unit/network.test.ts +++ /dev/null @@ -1,49 +0,0 @@ -import { ExtendedConfig, makeConfig, Networks } from '../../src/config' -import * as network from '../../src/network' -import * as starkware from '../../src/starkware' - -describe('network', () => { - let config: ExtendedConfig - - beforeEach(async () => { - config = makeConfig() - }) - - describe('#getStatusByTransaction', () => { - describe('when fetching Starkware Sequencer status', () => { - describe('when dummy contract initialized', () => { - it('returns true', async () => { - jest.spyOn(starkware, 'sendDummyStarkwareTransaction').mockRejectedValue({ - message: - 'RPC: starknet_addInvokeTransaction with params {"invoke_transaction":{"sender_address":"0x009cf509ef7a55ee8e487787003d47a704b4c7b6cc5469d7cd319d27bd753566","calldata":["0x1","0x9cf509ef7a55ee8e487787003d47a704b4c7b6cc5469d7cd319d27bd753566","0x79dc0da7c54b95f10aa182ad0a46400db63156920adb65eca2654c0945a463","0x2","0x1ef15c18599971b7beced415a40f0c7deacfd9b0d1819e03d723d8bc943cfca","0x0"],"type":"INVOKE","max_fee":"0x0","version":"0x1","signature":["0x1034048c548de23a36d5da5ba0f8fa125d1cede29f7ffa166680aee25165cc","0x7ccd1ffdf427cfa2e4e760c58455e5dcecc7246ef2fd8d3b0c095ddb69a849f"],"nonce":"0x3"}}\n An unexpected error occurred: {"error":"StarknetError { code: Known(OutOfRangeFee), message: "Transaction must commit to pay a positive amount on fee." }"}', - }) - expect(await network.getStatusByTransaction(Networks.Starkware, config)).toBe(true) - }) - }) - - describe('when dummy contract not initialized', () => { - it('returns true', async () => { - jest.spyOn(starkware, 'sendDummyStarkwareTransaction').mockRejectedValue({ - message: - 'RPC: starknet_getNonce with params {"contract_address":"0x1","block_id":"pending"}\n 20: Contract not found: undefined', - }) - expect(await network.getStatusByTransaction(Networks.Starkware, config)).toBe(true) - }) - }) - - describe('when transaction fails with unexpected error', () => { - it('returns false', async () => { - jest.spyOn(starkware, 'sendDummyStarkwareTransaction').mockRejectedValue({ - errorCode: 'Unexpected error', - }) - expect(await network.getStatusByTransaction(Networks.Starkware, config)).toBe(false) - }) - }) - }) - - /** - * TO_BE_IMPLEMENTED - * describe('when fetching EVM Sequencer status', () => {}) - * */ - }) -}) diff --git a/packages/sources/layer2-sequencer-health/test/unit/retry.test.ts b/packages/sources/layer2-sequencer-health/test/unit/retry.test.ts new file mode 100644 index 00000000000..548c86f0c7f --- /dev/null +++ b/packages/sources/layer2-sequencer-health/test/unit/retry.test.ts @@ -0,0 +1,90 @@ +import { retry } from '../../src/network' + +describe('retry', () => { + const retryConfig = { + numRetries: 3, + retryInterval: 10, + } + + it('returns result on first successful attempt', async () => { + const promise = jest.fn().mockResolvedValue('success') + + const result = await retry({ + promise, + retryConfig, + }) + + expect(result).toBe('success') + expect(promise).toHaveBeenCalledTimes(1) + }) + + it('retries and returns result on eventual success', async () => { + const promise = jest + .fn() + .mockRejectedValueOnce(new Error('fail1')) + .mockRejectedValueOnce(new Error('fail2')) + .mockResolvedValue('success') + + const result = await retry({ + promise, + retryConfig, + }) + + expect(result).toBe('success') + expect(promise).toHaveBeenCalledTimes(3) + }) + + it('throws last error after all retries exhausted', async () => { + const promise = jest.fn().mockRejectedValue(new Error('always fails')) + + await expect( + retry({ + promise, + retryConfig, + }), + ).rejects.toThrow('always fails') + + expect(promise).toHaveBeenCalledTimes(3) + }) + + it('throws the last error not the first', async () => { + const promise = jest + .fn() + .mockRejectedValueOnce(new Error('first error')) + .mockRejectedValueOnce(new Error('second error')) + .mockRejectedValue(new Error('last error')) + + await expect( + retry({ + promise, + retryConfig, + }), + ).rejects.toThrow('last error') + }) + + it('respects numRetries configuration', async () => { + const promise = jest.fn().mockRejectedValue(new Error('always fails')) + + await expect( + retry({ + promise, + retryConfig: { numRetries: 5, retryInterval: 1 }, + }), + ).rejects.toThrow() + + expect(promise).toHaveBeenCalledTimes(5) + }) + + it('handles numRetries of 1', async () => { + const promise = jest.fn().mockRejectedValue(new Error('fail')) + + await expect( + retry({ + promise, + retryConfig: { numRetries: 1, retryInterval: 1 }, + }), + ).rejects.toThrow() + + expect(promise).toHaveBeenCalledTimes(1) + }) +}) diff --git a/packages/sources/layer2-sequencer-health/test/unit/starkware-utils.test.ts b/packages/sources/layer2-sequencer-health/test/unit/starkware-utils.test.ts new file mode 100644 index 00000000000..7a6f794a364 --- /dev/null +++ b/packages/sources/layer2-sequencer-health/test/unit/starkware-utils.test.ts @@ -0,0 +1,66 @@ +import { BlockWithTxHashes } from 'starknet' +import { checkBatcherHealthy } from '../../src/starkware' + +const createMockBlock = (parentHash: string, transactions: string[]): BlockWithTxHashes => + ({ + parent_hash: parentHash, + transactions, + } as unknown as BlockWithTxHashes) + +describe('starkware utils', () => { + describe('checkBatcherHealthy', () => { + describe('when previousBlock is null', () => { + it('returns true when currentBlock has transactions', () => { + const currentBlock = createMockBlock('hash1', ['tx1', 'tx2']) + expect(checkBatcherHealthy(null, currentBlock)).toBe(true) + }) + + it('returns false when currentBlock has no transactions', () => { + const currentBlock = createMockBlock('hash1', []) + expect(checkBatcherHealthy(null, currentBlock)).toBe(false) + }) + }) + + describe('when previousBlock exists', () => { + describe('when parent_hash changed', () => { + it('returns true indicating new block', () => { + const previousBlock = createMockBlock('hash1', ['tx1']) + const currentBlock = createMockBlock('hash2', ['tx1']) + expect(checkBatcherHealthy(previousBlock, currentBlock)).toBe(true) + }) + + it('returns true even if no new transactions', () => { + const previousBlock = createMockBlock('hash1', ['tx1', 'tx2']) + const currentBlock = createMockBlock('hash2', []) + expect(checkBatcherHealthy(previousBlock, currentBlock)).toBe(true) + }) + }) + + describe('when parent_hash is the same', () => { + it('returns true when currentBlock has more transactions', () => { + const previousBlock = createMockBlock('hash1', ['tx1', 'tx2']) + const currentBlock = createMockBlock('hash1', ['tx1', 'tx2', 'tx3']) + expect(checkBatcherHealthy(previousBlock, currentBlock)).toBe(true) + }) + + it('returns false when currentBlock has same number of transactions', () => { + const previousBlock = createMockBlock('hash1', ['tx1', 'tx2']) + const currentBlock = createMockBlock('hash1', ['tx1', 'tx2']) + expect(checkBatcherHealthy(previousBlock, currentBlock)).toBe(false) + }) + + it('returns false when currentBlock has fewer transactions', () => { + const previousBlock = createMockBlock('hash1', ['tx1', 'tx2', 'tx3']) + const currentBlock = createMockBlock('hash1', ['tx1', 'tx2']) + expect(checkBatcherHealthy(previousBlock, currentBlock)).toBe(false) + }) + + it('returns false when both have empty transactions', () => { + const previousBlock = createMockBlock('hash1', []) + const currentBlock = createMockBlock('hash1', []) + expect(checkBatcherHealthy(previousBlock, currentBlock)).toBe(false) + }) + }) + }) + }) +}) diff --git a/packages/sources/layer2-sequencer-health/test/unit/starkware.test.ts b/packages/sources/layer2-sequencer-health/test/unit/starkware.test.ts deleted file mode 100644 index 52958c32a08..00000000000 --- a/packages/sources/layer2-sequencer-health/test/unit/starkware.test.ts +++ /dev/null @@ -1,98 +0,0 @@ -import { CHAIN_DELTA, ExtendedConfig, Networks, makeConfig } from '../../src/config' -import * as starkware from '../../src/starkware' -import * as network from '../../src/network' -import { useFakeTimers } from 'sinon' - -describe('starkware', () => { - let config: ExtendedConfig - let clock: any - - beforeEach(async () => { - config = makeConfig() - clock = useFakeTimers() - }) - - afterEach(() => { - clock.restore() - }) - - describe('#checkStarkwareSequencerPendingTransactions', () => { - describe('when request to fetch pending block from gateway fails', () => { - it('returns false', async () => { - jest.spyOn(network, 'retry').mockRejectedValue({ - providerStatusCode: 504, - }) - const fn = starkware.checkStarkwareSequencerPendingTransactions() - expect(await fn(config)).toBe(false) - }) - }) - - describe('when request to fetch pending block from gateway succeeds', () => { - describe('when there is a new pending block within the max interval', () => { - it('returns true', async () => { - const fn = starkware.checkStarkwareSequencerPendingTransactions() - jest.spyOn(network, 'retry').mockReturnValueOnce( - Promise.resolve({ - parent_hash: 'hash-one', - transactions: ['tx1', 'tx2'], - }), - ) - - expect(await fn(config)).toBe(true) - const timeToNextCall = CHAIN_DELTA[Networks.Starkware] - 10 * 1000 - clock.tick(timeToNextCall) - jest.spyOn(network, 'retry').mockReturnValueOnce( - Promise.resolve({ - parent_hash: 'hash-two', - transactions: ['tx1', 'tx2'], - }), - ) - expect(await fn(config)).toBe(true) - }) - }) - - describe('when there is no new pending block within the max interval', () => { - let fn: (config: ExtendedConfig) => Promise - - beforeEach(async () => { - fn = starkware.checkStarkwareSequencerPendingTransactions() - jest.spyOn(network, 'retry').mockReturnValueOnce( - Promise.resolve({ - parent_hash: 'hash-one', - transactions: ['tx1', 'tx2'], - }), - ) - await fn(config) - }) - - describe('when there are new transactions', () => { - it('returns true', async () => { - const timeToNextCall = CHAIN_DELTA[Networks.Starkware] - 10 * 1000 - clock.tick(timeToNextCall) - jest.spyOn(network, 'retry').mockReturnValueOnce( - Promise.resolve({ - parent_hash: 'hash-one', - transactions: ['tx1', 'tx2', 'tx3'], - }), - ) - expect(await fn(config)).toBe(true) - }) - }) - - describe('when there are no new transactons', () => { - it('returns false', async () => { - const timeToNextCall = CHAIN_DELTA[Networks.Starkware] - 10 * 1000 - clock.tick(timeToNextCall) - jest.spyOn(network, 'retry').mockReturnValueOnce( - Promise.resolve({ - parent_hash: 'hash-one', - transactions: ['tx1', 'tx2'], - }), - ) - expect(await fn(config)).toBe(false) - }) - }) - }) - }) - }) -}) diff --git a/packages/sources/matrixdock/src/config/index.d.ts b/packages/sources/matrixdock/src/config/index.d.ts new file mode 100644 index 00000000000..d603ea6d8a5 --- /dev/null +++ b/packages/sources/matrixdock/src/config/index.d.ts @@ -0,0 +1,21 @@ +import { AdapterConfig } from '@chainlink/external-adapter-framework/config' +export declare const config: AdapterConfig<{ + API_KEY: { + description: string + type: 'string' + required: true + sensitive: true + } + API_SECRET: { + description: string + type: 'string' + required: true + sensitive: true + } + API_ENDPOINT: { + description: string + type: 'string' + default: string + } +}> +//# sourceMappingURL=index.d.ts.map diff --git a/packages/sources/matrixdock/src/config/index.d.ts.map b/packages/sources/matrixdock/src/config/index.d.ts.map new file mode 100644 index 00000000000..4466400246a --- /dev/null +++ b/packages/sources/matrixdock/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,MAAM;;;;;;;;;;;;;;;;;;EAkBjB,CAAA"} \ No newline at end of file diff --git a/packages/sources/matrixdock/src/config/index.js b/packages/sources/matrixdock/src/config/index.js new file mode 100644 index 00000000000..f470196bfec --- /dev/null +++ b/packages/sources/matrixdock/src/config/index.js @@ -0,0 +1,24 @@ +'use strict' +Object.defineProperty(exports, '__esModule', { value: true }) +exports.config = void 0 +const config_1 = require('@chainlink/external-adapter-framework/config') +exports.config = new config_1.AdapterConfig({ + API_KEY: { + description: 'An API key for Matrixdock', + type: 'string', + required: true, + sensitive: true, + }, + API_SECRET: { + description: 'An API secret for Matrixdock used to sign requests', + type: 'string', + required: true, + sensitive: true, + }, + API_ENDPOINT: { + description: 'An API endpoint for Matrixdock', + type: 'string', + default: 'https://mapi.matrixport.com', + }, +}) +//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiaW5kZXguanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyJpbmRleC50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiOzs7QUFBQSx5RUFBNEU7QUFFL0QsUUFBQSxNQUFNLEdBQUcsSUFBSSxzQkFBYSxDQUFDO0lBQ3RDLE9BQU8sRUFBRTtRQUNQLFdBQVcsRUFBRSwyQkFBMkI7UUFDeEMsSUFBSSxFQUFFLFFBQVE7UUFDZCxRQUFRLEVBQUUsSUFBSTtRQUNkLFNBQVMsRUFBRSxJQUFJO0tBQ2hCO0lBQ0QsVUFBVSxFQUFFO1FBQ1YsV0FBVyxFQUFFLG9EQUFvRDtRQUNqRSxJQUFJLEVBQUUsUUFBUTtRQUNkLFFBQVEsRUFBRSxJQUFJO1FBQ2QsU0FBUyxFQUFFLElBQUk7S0FDaEI7SUFDRCxZQUFZLEVBQUU7UUFDWixXQUFXLEVBQUUsZ0NBQWdDO1FBQzdDLElBQUksRUFBRSxRQUFRO1FBQ2QsT0FBTyxFQUFFLDZCQUE2QjtLQUN2QztDQUNGLENBQUMsQ0FBQSIsInNvdXJjZXNDb250ZW50IjpbImltcG9ydCB7IEFkYXB0ZXJDb25maWcgfSBmcm9tICdAY2hhaW5saW5rL2V4dGVybmFsLWFkYXB0ZXItZnJhbWV3b3JrL2NvbmZpZydcblxuZXhwb3J0IGNvbnN0IGNvbmZpZyA9IG5ldyBBZGFwdGVyQ29uZmlnKHtcbiAgQVBJX0tFWToge1xuICAgIGRlc2NyaXB0aW9uOiAnQW4gQVBJIGtleSBmb3IgTWF0cml4ZG9jaycsXG4gICAgdHlwZTogJ3N0cmluZycsXG4gICAgcmVxdWlyZWQ6IHRydWUsXG4gICAgc2Vuc2l0aXZlOiB0cnVlLFxuICB9LFxuICBBUElfU0VDUkVUOiB7XG4gICAgZGVzY3JpcHRpb246ICdBbiBBUEkgc2VjcmV0IGZvciBNYXRyaXhkb2NrIHVzZWQgdG8gc2lnbiByZXF1ZXN0cycsXG4gICAgdHlwZTogJ3N0cmluZycsXG4gICAgcmVxdWlyZWQ6IHRydWUsXG4gICAgc2Vuc2l0aXZlOiB0cnVlLFxuICB9LFxuICBBUElfRU5EUE9JTlQ6IHtcbiAgICBkZXNjcmlwdGlvbjogJ0FuIEFQSSBlbmRwb2ludCBmb3IgTWF0cml4ZG9jaycsXG4gICAgdHlwZTogJ3N0cmluZycsXG4gICAgZGVmYXVsdDogJ2h0dHBzOi8vbWFwaS5tYXRyaXhwb3J0LmNvbScsXG4gIH0sXG59KVxuIl19 diff --git a/packages/sources/matrixdock/src/endpoint/index.d.ts b/packages/sources/matrixdock/src/endpoint/index.d.ts new file mode 100644 index 00000000000..6f067f21254 --- /dev/null +++ b/packages/sources/matrixdock/src/endpoint/index.d.ts @@ -0,0 +1,2 @@ +export { endpoint as nav } from './nav' +//# sourceMappingURL=index.d.ts.map diff --git a/packages/sources/matrixdock/src/endpoint/index.d.ts.map b/packages/sources/matrixdock/src/endpoint/index.d.ts.map new file mode 100644 index 00000000000..074e0486e3a --- /dev/null +++ b/packages/sources/matrixdock/src/endpoint/index.d.ts.map @@ -0,0 +1 @@ +{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,IAAI,GAAG,EAAE,MAAM,OAAO,CAAA"} \ No newline at end of file diff --git a/packages/sources/matrixdock/src/endpoint/index.js b/packages/sources/matrixdock/src/endpoint/index.js new file mode 100644 index 00000000000..1b347620f31 --- /dev/null +++ b/packages/sources/matrixdock/src/endpoint/index.js @@ -0,0 +1,11 @@ +'use strict' +Object.defineProperty(exports, '__esModule', { value: true }) +exports.nav = void 0 +var nav_1 = require('./nav') +Object.defineProperty(exports, 'nav', { + enumerable: true, + get: function () { + return nav_1.endpoint + }, +}) +//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiaW5kZXguanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyJpbmRleC50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiOzs7QUFBQSw2QkFBdUM7QUFBOUIsMEZBQUEsUUFBUSxPQUFPIiwic291cmNlc0NvbnRlbnQiOlsiZXhwb3J0IHsgZW5kcG9pbnQgYXMgbmF2IH0gZnJvbSAnLi9uYXYnXG4iXX0= diff --git a/packages/sources/matrixdock/src/endpoint/nav.d.ts b/packages/sources/matrixdock/src/endpoint/nav.d.ts new file mode 100644 index 00000000000..97c01ebdbed --- /dev/null +++ b/packages/sources/matrixdock/src/endpoint/nav.d.ts @@ -0,0 +1,18 @@ +import { AdapterEndpoint } from '@chainlink/external-adapter-framework/adapter' +import { SingleNumberResultResponse } from '@chainlink/external-adapter-framework/util' +import { InputParameters } from '@chainlink/external-adapter-framework/validation' +import { config } from '../config' +export declare const inputParameters: InputParameters<{ + readonly symbol: { + readonly type: 'string' + readonly description: 'The symbol to query (e.g., XAUM)' + readonly required: true + } +}> +export type BaseEndpointTypes = { + Parameters: typeof inputParameters.definition + Response: SingleNumberResultResponse + Settings: typeof config.settings +} +export declare const endpoint: AdapterEndpoint +//# sourceMappingURL=nav.d.ts.map diff --git a/packages/sources/matrixdock/src/endpoint/nav.d.ts.map b/packages/sources/matrixdock/src/endpoint/nav.d.ts.map new file mode 100644 index 00000000000..4ff60efb17e --- /dev/null +++ b/packages/sources/matrixdock/src/endpoint/nav.d.ts.map @@ -0,0 +1 @@ +{"version":3,"file":"nav.d.ts","sourceRoot":"","sources":["nav.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,eAAe,EAAE,MAAM,+CAA+C,CAAA;AAC/E,OAAO,EAAE,0BAA0B,EAAE,MAAM,4CAA4C,CAAA;AACvF,OAAO,EAAE,eAAe,EAAE,MAAM,kDAAkD,CAAA;AAClF,OAAO,EAAE,MAAM,EAAE,MAAM,WAAW,CAAA;AAGlC,eAAO,MAAM,eAAe;;;;;;EAa3B,CAAA;AAED,MAAM,MAAM,iBAAiB,GAAG;IAC9B,UAAU,EAAE,OAAO,eAAe,CAAC,UAAU,CAAA;IAC7C,QAAQ,EAAE,0BAA0B,CAAA;IACpC,QAAQ,EAAE,OAAO,MAAM,CAAC,QAAQ,CAAA;CACjC,CAAA;AAED,eAAO,MAAM,QAAQ,gEAInB,CAAA"} \ No newline at end of file diff --git a/packages/sources/matrixdock/src/endpoint/nav.js b/packages/sources/matrixdock/src/endpoint/nav.js new file mode 100644 index 00000000000..c96808d3a18 --- /dev/null +++ b/packages/sources/matrixdock/src/endpoint/nav.js @@ -0,0 +1,26 @@ +'use strict' +Object.defineProperty(exports, '__esModule', { value: true }) +exports.endpoint = exports.inputParameters = void 0 +const adapter_1 = require('@chainlink/external-adapter-framework/adapter') +const validation_1 = require('@chainlink/external-adapter-framework/validation') +const nav_1 = require('../transport/nav') +exports.inputParameters = new validation_1.InputParameters( + { + symbol: { + type: 'string', + description: 'The symbol to query (e.g., XAUM)', + required: true, + }, + }, + [ + { + symbol: 'XAUM', + }, + ], +) +exports.endpoint = new adapter_1.AdapterEndpoint({ + name: 'nav', + transport: nav_1.httpTransport, + inputParameters: exports.inputParameters, +}) +//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoibmF2LmpzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXMiOlsibmF2LnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiI7OztBQUFBLDJFQUErRTtBQUUvRSxpRkFBa0Y7QUFFbEYsMENBQWdEO0FBRW5DLFFBQUEsZUFBZSxHQUFHLElBQUksNEJBQWUsQ0FDaEQ7SUFDRSxNQUFNLEVBQUU7UUFDTixJQUFJLEVBQUUsUUFBUTtRQUNkLFdBQVcsRUFBRSxrQ0FBa0M7UUFDL0MsUUFBUSxFQUFFLElBQUk7S0FDZjtDQUNGLEVBQ0Q7SUFDRTtRQUNFLE1BQU0sRUFBRSxNQUFNO0tBQ2Y7Q0FDRixDQUNGLENBQUE7QUFRWSxRQUFBLFFBQVEsR0FBRyxJQUFJLHlCQUFlLENBQUM7SUFDMUMsSUFBSSxFQUFFLEtBQUs7SUFDWCxTQUFTLEVBQUUsbUJBQWE7SUFDeEIsZUFBZSxFQUFmLHVCQUFlO0NBQ2hCLENBQUMsQ0FBQSIsInNvdXJjZXNDb250ZW50IjpbImltcG9ydCB7IEFkYXB0ZXJFbmRwb2ludCB9IGZyb20gJ0BjaGFpbmxpbmsvZXh0ZXJuYWwtYWRhcHRlci1mcmFtZXdvcmsvYWRhcHRlcidcbmltcG9ydCB7IFNpbmdsZU51bWJlclJlc3VsdFJlc3BvbnNlIH0gZnJvbSAnQGNoYWlubGluay9leHRlcm5hbC1hZGFwdGVyLWZyYW1ld29yay91dGlsJ1xuaW1wb3J0IHsgSW5wdXRQYXJhbWV0ZXJzIH0gZnJvbSAnQGNoYWlubGluay9leHRlcm5hbC1hZGFwdGVyLWZyYW1ld29yay92YWxpZGF0aW9uJ1xuaW1wb3J0IHsgY29uZmlnIH0gZnJvbSAnLi4vY29uZmlnJ1xuaW1wb3J0IHsgaHR0cFRyYW5zcG9ydCB9IGZyb20gJy4uL3RyYW5zcG9ydC9uYXYnXG5cbmV4cG9ydCBjb25zdCBpbnB1dFBhcmFtZXRlcnMgPSBuZXcgSW5wdXRQYXJhbWV0ZXJzKFxuICB7XG4gICAgc3ltYm9sOiB7XG4gICAgICB0eXBlOiAnc3RyaW5nJyxcbiAgICAgIGRlc2NyaXB0aW9uOiAnVGhlIHN5bWJvbCB0byBxdWVyeSAoZS5nLiwgWEFVTSknLFxuICAgICAgcmVxdWlyZWQ6IHRydWUsXG4gICAgfSxcbiAgfSxcbiAgW1xuICAgIHtcbiAgICAgIHN5bWJvbDogJ1hBVU0nLFxuICAgIH0sXG4gIF0sXG4pXG5cbmV4cG9ydCB0eXBlIEJhc2VFbmRwb2ludFR5cGVzID0ge1xuICBQYXJhbWV0ZXJzOiB0eXBlb2YgaW5wdXRQYXJhbWV0ZXJzLmRlZmluaXRpb25cbiAgUmVzcG9uc2U6IFNpbmdsZU51bWJlclJlc3VsdFJlc3BvbnNlXG4gIFNldHRpbmdzOiB0eXBlb2YgY29uZmlnLnNldHRpbmdzXG59XG5cbmV4cG9ydCBjb25zdCBlbmRwb2ludCA9IG5ldyBBZGFwdGVyRW5kcG9pbnQoe1xuICBuYW1lOiAnbmF2JyxcbiAgdHJhbnNwb3J0OiBodHRwVHJhbnNwb3J0LFxuICBpbnB1dFBhcmFtZXRlcnMsXG59KVxuIl19 diff --git a/packages/sources/matrixdock/src/index.d.ts b/packages/sources/matrixdock/src/index.d.ts new file mode 100644 index 00000000000..15a2e8022dc --- /dev/null +++ b/packages/sources/matrixdock/src/index.d.ts @@ -0,0 +1,23 @@ +import { ServerInstance } from '@chainlink/external-adapter-framework' +import { Adapter } from '@chainlink/external-adapter-framework/adapter' +export declare const adapter: Adapter<{ + API_KEY: { + description: string + type: 'string' + required: true + sensitive: true + } + API_SECRET: { + description: string + type: 'string' + required: true + sensitive: true + } + API_ENDPOINT: { + description: string + type: 'string' + default: string + } +}> +export declare const server: () => Promise +//# sourceMappingURL=index.d.ts.map diff --git a/packages/sources/matrixdock/src/index.d.ts.map b/packages/sources/matrixdock/src/index.d.ts.map new file mode 100644 index 00000000000..3b8f474105e --- /dev/null +++ b/packages/sources/matrixdock/src/index.d.ts.map @@ -0,0 +1 @@ +{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAU,cAAc,EAAE,MAAM,uCAAuC,CAAA;AAC9E,OAAO,EAAE,OAAO,EAAE,MAAM,+CAA+C,CAAA;AAIvE,eAAO,MAAM,OAAO;;;;;;;;;;;;;;;;;;EAYlB,CAAA;AAEF,eAAO,MAAM,MAAM,QAAO,OAAO,CAAC,cAAc,GAAG,SAAS,CAAoB,CAAA"} \ No newline at end of file diff --git a/packages/sources/matrixdock/src/index.js b/packages/sources/matrixdock/src/index.js new file mode 100644 index 00000000000..0d015354ae1 --- /dev/null +++ b/packages/sources/matrixdock/src/index.js @@ -0,0 +1,23 @@ +'use strict' +Object.defineProperty(exports, '__esModule', { value: true }) +exports.server = exports.adapter = void 0 +const external_adapter_framework_1 = require('@chainlink/external-adapter-framework') +const adapter_1 = require('@chainlink/external-adapter-framework/adapter') +const config_1 = require('./config') +const nav_1 = require('./endpoint/nav') +exports.adapter = new adapter_1.Adapter({ + defaultEndpoint: nav_1.endpoint.name, + name: 'MATRIXDOCK', + config: config_1.config, + endpoints: [nav_1.endpoint], + rateLimiting: { + tiers: { + default: { + rateLimit1s: 5, + }, + }, + }, +}) +const server = () => (0, external_adapter_framework_1.expose)(exports.adapter) +exports.server = server +//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiaW5kZXguanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyJpbmRleC50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiOzs7QUFBQSxzRkFBOEU7QUFDOUUsMkVBQXVFO0FBQ3ZFLHFDQUFpQztBQUNqQyx3Q0FBd0Q7QUFFM0MsUUFBQSxPQUFPLEdBQUcsSUFBSSxpQkFBTyxDQUFDO0lBQ2pDLGVBQWUsRUFBRSxjQUFXLENBQUMsSUFBSTtJQUNqQyxJQUFJLEVBQUUsWUFBWTtJQUNsQixNQUFNLEVBQU4sZUFBTTtJQUNOLFNBQVMsRUFBRSxDQUFDLGNBQVcsQ0FBQztJQUN4QixZQUFZLEVBQUU7UUFDWixLQUFLLEVBQUU7WUFDTCxPQUFPLEVBQUU7Z0JBQ1AsV0FBVyxFQUFFLENBQUM7YUFDZjtTQUNGO0tBQ0Y7Q0FDRixDQUFDLENBQUE7QUFFSyxNQUFNLE1BQU0sR0FBRyxHQUF3QyxFQUFFLENBQUMsSUFBQSxtQ0FBTSxFQUFDLGVBQU8sQ0FBQyxDQUFBO0FBQW5FLFFBQUEsTUFBTSxVQUE2RCIsInNvdXJjZXNDb250ZW50IjpbImltcG9ydCB7IGV4cG9zZSwgU2VydmVySW5zdGFuY2UgfSBmcm9tICdAY2hhaW5saW5rL2V4dGVybmFsLWFkYXB0ZXItZnJhbWV3b3JrJ1xuaW1wb3J0IHsgQWRhcHRlciB9IGZyb20gJ0BjaGFpbmxpbmsvZXh0ZXJuYWwtYWRhcHRlci1mcmFtZXdvcmsvYWRhcHRlcidcbmltcG9ydCB7IGNvbmZpZyB9IGZyb20gJy4vY29uZmlnJ1xuaW1wb3J0IHsgZW5kcG9pbnQgYXMgbmF2RW5kcG9pbnQgfSBmcm9tICcuL2VuZHBvaW50L25hdidcblxuZXhwb3J0IGNvbnN0IGFkYXB0ZXIgPSBuZXcgQWRhcHRlcih7XG4gIGRlZmF1bHRFbmRwb2ludDogbmF2RW5kcG9pbnQubmFtZSxcbiAgbmFtZTogJ01BVFJJWERPQ0snLFxuICBjb25maWcsXG4gIGVuZHBvaW50czogW25hdkVuZHBvaW50XSxcbiAgcmF0ZUxpbWl0aW5nOiB7XG4gICAgdGllcnM6IHtcbiAgICAgIGRlZmF1bHQ6IHtcbiAgICAgICAgcmF0ZUxpbWl0MXM6IDUsXG4gICAgICB9LFxuICAgIH0sXG4gIH0sXG59KVxuXG5leHBvcnQgY29uc3Qgc2VydmVyID0gKCk6IFByb21pc2U8U2VydmVySW5zdGFuY2UgfCB1bmRlZmluZWQ+ID0+IGV4cG9zZShhZGFwdGVyKVxuIl19 diff --git a/packages/sources/matrixdock/src/transport/authentication.d.ts b/packages/sources/matrixdock/src/transport/authentication.d.ts new file mode 100644 index 00000000000..21c6e112ec7 --- /dev/null +++ b/packages/sources/matrixdock/src/transport/authentication.d.ts @@ -0,0 +1,32 @@ +export interface GetRequestHeadersParams { + method: string + path: string + queryString: string + apiKey: string + secret: string + timestamp: number +} +/** + * Generate the HMAC-SHA256 signature for Matrixdock API requests using AuthenticationV2. + * + * The prehash string is constructed as: + * {timestamp}{method}{api_path}&{query_string} + * + * Where: + * - timestamp: Current UTC timestamp in milliseconds + * - method: HTTP method in uppercase (e.g., "GET") + * - api_path: Request path (e.g., "/rwa/api/v1/quote/price") + * - query_string: Query parameters as a string (e.g., "symbol=XAUM") + * + * Example prehash: + * 1731931956000GET/mapi/v1/wallet/withdrawals¤cy=BTC&limit=50 + */ +export declare const getRequestHeaders: ({ + method, + path, + queryString, + apiKey, + secret, + timestamp, +}: GetRequestHeadersParams) => Record +//# sourceMappingURL=authentication.d.ts.map diff --git a/packages/sources/matrixdock/src/transport/authentication.d.ts.map b/packages/sources/matrixdock/src/transport/authentication.d.ts.map new file mode 100644 index 00000000000..90b6b43e47b --- /dev/null +++ b/packages/sources/matrixdock/src/transport/authentication.d.ts.map @@ -0,0 +1 @@ +{"version":3,"file":"authentication.d.ts","sourceRoot":"","sources":["authentication.ts"],"names":[],"mappings":"AAEA,MAAM,WAAW,uBAAuB;IACtC,MAAM,EAAE,MAAM,CAAA;IACd,IAAI,EAAE,MAAM,CAAA;IACZ,WAAW,EAAE,MAAM,CAAA;IACnB,MAAM,EAAE,MAAM,CAAA;IACd,MAAM,EAAE,MAAM,CAAA;IACd,SAAS,EAAE,MAAM,CAAA;CAClB;AAED;;;;;;;;;;;;;;GAcG;AACH,eAAO,MAAM,iBAAiB,GAAI,2DAO/B,uBAAuB,KAAG,MAAM,CAAC,MAAM,EAAE,MAAM,CAajD,CAAA"} \ No newline at end of file diff --git a/packages/sources/matrixdock/src/transport/authentication.js b/packages/sources/matrixdock/src/transport/authentication.js new file mode 100644 index 00000000000..943f2e7d565 --- /dev/null +++ b/packages/sources/matrixdock/src/transport/authentication.js @@ -0,0 +1,36 @@ +'use strict' +Object.defineProperty(exports, '__esModule', { value: true }) +exports.getRequestHeaders = void 0 +const tslib_1 = require('tslib') +const crypto_js_1 = tslib_1.__importDefault(require('crypto-js')) +/** + * Generate the HMAC-SHA256 signature for Matrixdock API requests using AuthenticationV2. + * + * The prehash string is constructed as: + * {timestamp}{method}{api_path}&{query_string} + * + * Where: + * - timestamp: Current UTC timestamp in milliseconds + * - method: HTTP method in uppercase (e.g., "GET") + * - api_path: Request path (e.g., "/rwa/api/v1/quote/price") + * - query_string: Query parameters as a string (e.g., "symbol=XAUM") + * + * Example prehash: + * 1731931956000GET/mapi/v1/wallet/withdrawals¤cy=BTC&limit=50 + */ +const getRequestHeaders = ({ method, path, queryString, apiKey, secret, timestamp }) => { + // Construct prehash: timestamp + method + api_path + '&' + query_string + const prehash = `${timestamp}${method.toUpperCase()}${path}&${queryString}` + // signature = hex(hmac_sha256(prehash, secret_key)) + const signature = crypto_js_1.default + .HmacSHA256(prehash, secret) + .toString(crypto_js_1.default.enc.Hex) + return { + 'X-MatrixPort-Access-Key': apiKey, + 'X-Signature': signature, + 'X-Timestamp': timestamp.toString(), + 'X-Auth-Version': 'v2', + } +} +exports.getRequestHeaders = getRequestHeaders +//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiYXV0aGVudGljYXRpb24uanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyJhdXRoZW50aWNhdGlvbi50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiOzs7O0FBQUEsa0VBQWdDO0FBV2hDOzs7Ozs7Ozs7Ozs7OztHQWNHO0FBQ0ksTUFBTSxpQkFBaUIsR0FBRyxDQUFDLEVBQ2hDLE1BQU0sRUFDTixJQUFJLEVBQ0osV0FBVyxFQUNYLE1BQU0sRUFDTixNQUFNLEVBQ04sU0FBUyxHQUNlLEVBQTBCLEVBQUU7SUFDcEQsd0VBQXdFO0lBQ3hFLE1BQU0sT0FBTyxHQUFHLEdBQUcsU0FBUyxHQUFHLE1BQU0sQ0FBQyxXQUFXLEVBQUUsR0FBRyxJQUFJLElBQUksV0FBVyxFQUFFLENBQUE7SUFFM0Usb0RBQW9EO0lBQ3BELE1BQU0sU0FBUyxHQUFHLG1CQUFRLENBQUMsVUFBVSxDQUFDLE9BQU8sRUFBRSxNQUFNLENBQUMsQ0FBQyxRQUFRLENBQUMsbUJBQVEsQ0FBQyxHQUFHLENBQUMsR0FBRyxDQUFDLENBQUE7SUFFakYsT0FBTztRQUNMLHlCQUF5QixFQUFFLE1BQU07UUFDakMsYUFBYSxFQUFFLFNBQVM7UUFDeEIsYUFBYSxFQUFFLFNBQVMsQ0FBQyxRQUFRLEVBQUU7UUFDbkMsZ0JBQWdCLEVBQUUsSUFBSTtLQUN2QixDQUFBO0FBQ0gsQ0FBQyxDQUFBO0FBcEJZLFFBQUEsaUJBQWlCLHFCQW9CN0IiLCJzb3VyY2VzQ29udGVudCI6WyJpbXBvcnQgQ3J5cHRvSlMgZnJvbSAnY3J5cHRvLWpzJ1xuXG5leHBvcnQgaW50ZXJmYWNlIEdldFJlcXVlc3RIZWFkZXJzUGFyYW1zIHtcbiAgbWV0aG9kOiBzdHJpbmdcbiAgcGF0aDogc3RyaW5nXG4gIHF1ZXJ5U3RyaW5nOiBzdHJpbmdcbiAgYXBpS2V5OiBzdHJpbmdcbiAgc2VjcmV0OiBzdHJpbmdcbiAgdGltZXN0YW1wOiBudW1iZXJcbn1cblxuLyoqXG4gKiBHZW5lcmF0ZSB0aGUgSE1BQy1TSEEyNTYgc2lnbmF0dXJlIGZvciBNYXRyaXhkb2NrIEFQSSByZXF1ZXN0cyB1c2luZyBBdXRoZW50aWNhdGlvblYyLlxuICpcbiAqIFRoZSBwcmVoYXNoIHN0cmluZyBpcyBjb25zdHJ1Y3RlZCBhczpcbiAqIHt0aW1lc3RhbXB9e21ldGhvZH17YXBpX3BhdGh9JntxdWVyeV9zdHJpbmd9XG4gKlxuICogV2hlcmU6XG4gKiAtIHRpbWVzdGFtcDogQ3VycmVudCBVVEMgdGltZXN0YW1wIGluIG1pbGxpc2Vjb25kc1xuICogLSBtZXRob2Q6IEhUVFAgbWV0aG9kIGluIHVwcGVyY2FzZSAoZS5nLiwgXCJHRVRcIilcbiAqIC0gYXBpX3BhdGg6IFJlcXVlc3QgcGF0aCAoZS5nLiwgXCIvcndhL2FwaS92MS9xdW90ZS9wcmljZVwiKVxuICogLSBxdWVyeV9zdHJpbmc6IFF1ZXJ5IHBhcmFtZXRlcnMgYXMgYSBzdHJpbmcgKGUuZy4sIFwic3ltYm9sPVhBVU1cIilcbiAqXG4gKiBFeGFtcGxlIHByZWhhc2g6XG4gKiAxNzMxOTMxOTU2MDAwR0VUL21hcGkvdjEvd2FsbGV0L3dpdGhkcmF3YWxzJmN1cnJlbmN5PUJUQyZsaW1pdD01MFxuICovXG5leHBvcnQgY29uc3QgZ2V0UmVxdWVzdEhlYWRlcnMgPSAoe1xuICBtZXRob2QsXG4gIHBhdGgsXG4gIHF1ZXJ5U3RyaW5nLFxuICBhcGlLZXksXG4gIHNlY3JldCxcbiAgdGltZXN0YW1wLFxufTogR2V0UmVxdWVzdEhlYWRlcnNQYXJhbXMpOiBSZWNvcmQ8c3RyaW5nLCBzdHJpbmc+ID0+IHtcbiAgLy8gQ29uc3RydWN0IHByZWhhc2g6IHRpbWVzdGFtcCArIG1ldGhvZCArIGFwaV9wYXRoICsgJyYnICsgcXVlcnlfc3RyaW5nXG4gIGNvbnN0IHByZWhhc2ggPSBgJHt0aW1lc3RhbXB9JHttZXRob2QudG9VcHBlckNhc2UoKX0ke3BhdGh9JiR7cXVlcnlTdHJpbmd9YFxuXG4gIC8vIHNpZ25hdHVyZSA9IGhleChobWFjX3NoYTI1NihwcmVoYXNoLCBzZWNyZXRfa2V5KSlcbiAgY29uc3Qgc2lnbmF0dXJlID0gQ3J5cHRvSlMuSG1hY1NIQTI1NihwcmVoYXNoLCBzZWNyZXQpLnRvU3RyaW5nKENyeXB0b0pTLmVuYy5IZXgpXG5cbiAgcmV0dXJuIHtcbiAgICAnWC1NYXRyaXhQb3J0LUFjY2Vzcy1LZXknOiBhcGlLZXksXG4gICAgJ1gtU2lnbmF0dXJlJzogc2lnbmF0dXJlLFxuICAgICdYLVRpbWVzdGFtcCc6IHRpbWVzdGFtcC50b1N0cmluZygpLFxuICAgICdYLUF1dGgtVmVyc2lvbic6ICd2MicsXG4gIH1cbn1cbiJdfQ== diff --git a/packages/sources/matrixdock/src/transport/nav.d.ts b/packages/sources/matrixdock/src/transport/nav.d.ts new file mode 100644 index 00000000000..76e0f0e65ec --- /dev/null +++ b/packages/sources/matrixdock/src/transport/nav.d.ts @@ -0,0 +1,21 @@ +import { HttpTransport } from '@chainlink/external-adapter-framework/transports' +import { BaseEndpointTypes } from '../endpoint/nav' +export interface ResponseSchema { + code: number + message: string + data: { + round_id: string + last_updated_timestamp: number + symbol: string + issue_price: string + redeem_price: string + } | null +} +export type HttpTransportTypes = BaseEndpointTypes & { + Provider: { + RequestBody: never + ResponseBody: ResponseSchema + } +} +export declare const httpTransport: HttpTransport +//# sourceMappingURL=nav.d.ts.map diff --git a/packages/sources/matrixdock/src/transport/nav.d.ts.map b/packages/sources/matrixdock/src/transport/nav.d.ts.map new file mode 100644 index 00000000000..385c4d537f9 --- /dev/null +++ b/packages/sources/matrixdock/src/transport/nav.d.ts.map @@ -0,0 +1 @@ +{"version":3,"file":"nav.d.ts","sourceRoot":"","sources":["nav.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAAE,MAAM,kDAAkD,CAAA;AAChF,OAAO,EAAE,iBAAiB,EAAE,MAAM,iBAAiB,CAAA;AAGnD,MAAM,WAAW,cAAc;IAC7B,IAAI,EAAE,MAAM,CAAA;IACZ,OAAO,EAAE,MAAM,CAAA;IACf,IAAI,EAAE;QACJ,QAAQ,EAAE,MAAM,CAAA;QAChB,sBAAsB,EAAE,MAAM,CAAA;QAC9B,MAAM,EAAE,MAAM,CAAA;QACd,WAAW,EAAE,MAAM,CAAA;QACnB,YAAY,EAAE,MAAM,CAAA;KACrB,GAAG,IAAI,CAAA;CACT;AAED,MAAM,MAAM,kBAAkB,GAAG,iBAAiB,GAAG;IACnD,QAAQ,EAAE;QACR,WAAW,EAAE,KAAK,CAAA;QAClB,YAAY,EAAE,cAAc,CAAA;KAC7B,CAAA;CACF,CAAA;AAED,eAAO,MAAM,aAAa,mCA0DxB,CAAA"} \ No newline at end of file diff --git a/packages/sources/matrixdock/src/transport/nav.js b/packages/sources/matrixdock/src/transport/nav.js new file mode 100644 index 00000000000..ee814fb7d38 --- /dev/null +++ b/packages/sources/matrixdock/src/transport/nav.js @@ -0,0 +1,60 @@ +'use strict' +Object.defineProperty(exports, '__esModule', { value: true }) +exports.httpTransport = void 0 +const transports_1 = require('@chainlink/external-adapter-framework/transports') +const authentication_1 = require('./authentication') +exports.httpTransport = new transports_1.HttpTransport({ + prepareRequests: (params, config) => { + return params.map((param) => { + const method = 'GET' + const path = '/rwa/api/v1/quote/price' + const timestamp = Date.now() + const queryString = `symbol=${param.symbol}` + const headers = (0, authentication_1.getRequestHeaders)({ + method, + path, + queryString, + apiKey: config.API_KEY, + secret: config.API_SECRET, + timestamp, + }) + return { + params: [param], + request: { + baseURL: config.API_ENDPOINT, + url: path, + params: { symbol: param.symbol }, + headers, + }, + } + }) + }, + parseResponse: (params, response) => { + return params.map((param) => { + const apiResponse = response.data + if (apiResponse.code !== 0 || !apiResponse.data) { + return { + params: param, + response: { + errorMessage: apiResponse.message || 'Unknown error from Matrixdock API', + statusCode: 502, + }, + } + } + const result = Number(apiResponse.data.issue_price) + return { + params: param, + response: { + result, + data: { + result, + }, + timestamps: { + providerIndicatedTimeUnixMs: apiResponse.data.last_updated_timestamp, + }, + }, + } + }) + }, +}) +//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoibmF2LmpzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXMiOlsibmF2LnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiI7OztBQUFBLGlGQUFnRjtBQUVoRixxREFBb0Q7QUFxQnZDLFFBQUEsYUFBYSxHQUFHLElBQUksMEJBQWEsQ0FBcUI7SUFDakUsZUFBZSxFQUFFLENBQUMsTUFBTSxFQUFFLE1BQU0sRUFBRSxFQUFFO1FBQ2xDLE9BQU8sTUFBTSxDQUFDLEdBQUcsQ0FBQyxDQUFDLEtBQUssRUFBRSxFQUFFO1lBQzFCLE1BQU0sTUFBTSxHQUFHLEtBQUssQ0FBQTtZQUNwQixNQUFNLElBQUksR0FBRyx5QkFBeUIsQ0FBQTtZQUN0QyxNQUFNLFNBQVMsR0FBRyxJQUFJLENBQUMsR0FBRyxFQUFFLENBQUE7WUFDNUIsTUFBTSxXQUFXLEdBQUcsVUFBVSxLQUFLLENBQUMsTUFBTSxFQUFFLENBQUE7WUFFNUMsTUFBTSxPQUFPLEdBQUcsSUFBQSxrQ0FBaUIsRUFBQztnQkFDaEMsTUFBTTtnQkFDTixJQUFJO2dCQUNKLFdBQVc7Z0JBQ1gsTUFBTSxFQUFFLE1BQU0sQ0FBQyxPQUFPO2dCQUN0QixNQUFNLEVBQUUsTUFBTSxDQUFDLFVBQVU7Z0JBQ3pCLFNBQVM7YUFDVixDQUFDLENBQUE7WUFFRixPQUFPO2dCQUNMLE1BQU0sRUFBRSxDQUFDLEtBQUssQ0FBQztnQkFDZixPQUFPLEVBQUU7b0JBQ1AsT0FBTyxFQUFFLE1BQU0sQ0FBQyxZQUFZO29CQUM1QixHQUFHLEVBQUUsSUFBSTtvQkFDVCxNQUFNLEVBQUUsRUFBRSxNQUFNLEVBQUUsS0FBSyxDQUFDLE1BQU0sRUFBRTtvQkFDaEMsT0FBTztpQkFDUjthQUNGLENBQUE7UUFDSCxDQUFDLENBQUMsQ0FBQTtJQUNKLENBQUM7SUFDRCxhQUFhLEVBQUUsQ0FBQyxNQUFNLEVBQUUsUUFBUSxFQUFFLEVBQUU7UUFDbEMsT0FBTyxNQUFNLENBQUMsR0FBRyxDQUFDLENBQUMsS0FBSyxFQUFFLEVBQUU7WUFDMUIsTUFBTSxXQUFXLEdBQUcsUUFBUSxDQUFDLElBQUksQ0FBQTtZQUVqQyxJQUFJLFdBQVcsQ0FBQyxJQUFJLEtBQUssQ0FBQyxJQUFJLENBQUMsV0FBVyxDQUFDLElBQUksRUFBRSxDQUFDO2dCQUNoRCxPQUFPO29CQUNMLE1BQU0sRUFBRSxLQUFLO29CQUNiLFFBQVEsRUFBRTt3QkFDUixZQUFZLEVBQUUsV0FBVyxDQUFDLE9BQU8sSUFBSSxtQ0FBbUM7d0JBQ3hFLFVBQVUsRUFBRSxHQUFHO3FCQUNoQjtpQkFDRixDQUFBO1lBQ0gsQ0FBQztZQUVELE1BQU0sTUFBTSxHQUFHLE1BQU0sQ0FBQyxXQUFXLENBQUMsSUFBSSxDQUFDLFdBQVcsQ0FBQyxDQUFBO1lBRW5ELE9BQU87Z0JBQ0wsTUFBTSxFQUFFLEtBQUs7Z0JBQ2IsUUFBUSxFQUFFO29CQUNSLE1BQU07b0JBQ04sSUFBSSxFQUFFO3dCQUNKLE1BQU07cUJBQ1A7b0JBQ0QsVUFBVSxFQUFFO3dCQUNWLDJCQUEyQixFQUFFLFdBQVcsQ0FBQyxJQUFJLENBQUMsc0JBQXNCO3FCQUNyRTtpQkFDRjthQUNGLENBQUE7UUFDSCxDQUFDLENBQUMsQ0FBQTtJQUNKLENBQUM7Q0FDRixDQUFDLENBQUEiLCJzb3VyY2VzQ29udGVudCI6WyJpbXBvcnQgeyBIdHRwVHJhbnNwb3J0IH0gZnJvbSAnQGNoYWlubGluay9leHRlcm5hbC1hZGFwdGVyLWZyYW1ld29yay90cmFuc3BvcnRzJ1xuaW1wb3J0IHsgQmFzZUVuZHBvaW50VHlwZXMgfSBmcm9tICcuLi9lbmRwb2ludC9uYXYnXG5pbXBvcnQgeyBnZXRSZXF1ZXN0SGVhZGVycyB9IGZyb20gJy4vYXV0aGVudGljYXRpb24nXG5cbmV4cG9ydCBpbnRlcmZhY2UgUmVzcG9uc2VTY2hlbWEge1xuICBjb2RlOiBudW1iZXJcbiAgbWVzc2FnZTogc3RyaW5nXG4gIGRhdGE6IHtcbiAgICByb3VuZF9pZDogc3RyaW5nXG4gICAgbGFzdF91cGRhdGVkX3RpbWVzdGFtcDogbnVtYmVyXG4gICAgc3ltYm9sOiBzdHJpbmdcbiAgICBpc3N1ZV9wcmljZTogc3RyaW5nXG4gICAgcmVkZWVtX3ByaWNlOiBzdHJpbmdcbiAgfSB8IG51bGxcbn1cblxuZXhwb3J0IHR5cGUgSHR0cFRyYW5zcG9ydFR5cGVzID0gQmFzZUVuZHBvaW50VHlwZXMgJiB7XG4gIFByb3ZpZGVyOiB7XG4gICAgUmVxdWVzdEJvZHk6IG5ldmVyXG4gICAgUmVzcG9uc2VCb2R5OiBSZXNwb25zZVNjaGVtYVxuICB9XG59XG5cbmV4cG9ydCBjb25zdCBodHRwVHJhbnNwb3J0ID0gbmV3IEh0dHBUcmFuc3BvcnQ8SHR0cFRyYW5zcG9ydFR5cGVzPih7XG4gIHByZXBhcmVSZXF1ZXN0czogKHBhcmFtcywgY29uZmlnKSA9PiB7XG4gICAgcmV0dXJuIHBhcmFtcy5tYXAoKHBhcmFtKSA9PiB7XG4gICAgICBjb25zdCBtZXRob2QgPSAnR0VUJ1xuICAgICAgY29uc3QgcGF0aCA9ICcvcndhL2FwaS92MS9xdW90ZS9wcmljZSdcbiAgICAgIGNvbnN0IHRpbWVzdGFtcCA9IERhdGUubm93KClcbiAgICAgIGNvbnN0IHF1ZXJ5U3RyaW5nID0gYHN5bWJvbD0ke3BhcmFtLnN5bWJvbH1gXG5cbiAgICAgIGNvbnN0IGhlYWRlcnMgPSBnZXRSZXF1ZXN0SGVhZGVycyh7XG4gICAgICAgIG1ldGhvZCxcbiAgICAgICAgcGF0aCxcbiAgICAgICAgcXVlcnlTdHJpbmcsXG4gICAgICAgIGFwaUtleTogY29uZmlnLkFQSV9LRVksXG4gICAgICAgIHNlY3JldDogY29uZmlnLkFQSV9TRUNSRVQsXG4gICAgICAgIHRpbWVzdGFtcCxcbiAgICAgIH0pXG5cbiAgICAgIHJldHVybiB7XG4gICAgICAgIHBhcmFtczogW3BhcmFtXSxcbiAgICAgICAgcmVxdWVzdDoge1xuICAgICAgICAgIGJhc2VVUkw6IGNvbmZpZy5BUElfRU5EUE9JTlQsXG4gICAgICAgICAgdXJsOiBwYXRoLFxuICAgICAgICAgIHBhcmFtczogeyBzeW1ib2w6IHBhcmFtLnN5bWJvbCB9LFxuICAgICAgICAgIGhlYWRlcnMsXG4gICAgICAgIH0sXG4gICAgICB9XG4gICAgfSlcbiAgfSxcbiAgcGFyc2VSZXNwb25zZTogKHBhcmFtcywgcmVzcG9uc2UpID0+IHtcbiAgICByZXR1cm4gcGFyYW1zLm1hcCgocGFyYW0pID0+IHtcbiAgICAgIGNvbnN0IGFwaVJlc3BvbnNlID0gcmVzcG9uc2UuZGF0YVxuXG4gICAgICBpZiAoYXBpUmVzcG9uc2UuY29kZSAhPT0gMCB8fCAhYXBpUmVzcG9uc2UuZGF0YSkge1xuICAgICAgICByZXR1cm4ge1xuICAgICAgICAgIHBhcmFtczogcGFyYW0sXG4gICAgICAgICAgcmVzcG9uc2U6IHtcbiAgICAgICAgICAgIGVycm9yTWVzc2FnZTogYXBpUmVzcG9uc2UubWVzc2FnZSB8fCAnVW5rbm93biBlcnJvciBmcm9tIE1hdHJpeGRvY2sgQVBJJyxcbiAgICAgICAgICAgIHN0YXR1c0NvZGU6IDUwMixcbiAgICAgICAgICB9LFxuICAgICAgICB9XG4gICAgICB9XG5cbiAgICAgIGNvbnN0IHJlc3VsdCA9IE51bWJlcihhcGlSZXNwb25zZS5kYXRhLmlzc3VlX3ByaWNlKVxuXG4gICAgICByZXR1cm4ge1xuICAgICAgICBwYXJhbXM6IHBhcmFtLFxuICAgICAgICByZXNwb25zZToge1xuICAgICAgICAgIHJlc3VsdCxcbiAgICAgICAgICBkYXRhOiB7XG4gICAgICAgICAgICByZXN1bHQsXG4gICAgICAgICAgfSxcbiAgICAgICAgICB0aW1lc3RhbXBzOiB7XG4gICAgICAgICAgICBwcm92aWRlckluZGljYXRlZFRpbWVVbml4TXM6IGFwaVJlc3BvbnNlLmRhdGEubGFzdF91cGRhdGVkX3RpbWVzdGFtcCxcbiAgICAgICAgICB9LFxuICAgICAgICB9LFxuICAgICAgfVxuICAgIH0pXG4gIH0sXG59KVxuIl19 diff --git a/packages/sources/matrixdock/test/integration/adapter.test.d.ts b/packages/sources/matrixdock/test/integration/adapter.test.d.ts new file mode 100644 index 00000000000..cc9ee54bc69 --- /dev/null +++ b/packages/sources/matrixdock/test/integration/adapter.test.d.ts @@ -0,0 +1,2 @@ +export {} +//# sourceMappingURL=adapter.test.d.ts.map diff --git a/packages/sources/matrixdock/test/integration/adapter.test.d.ts.map b/packages/sources/matrixdock/test/integration/adapter.test.d.ts.map new file mode 100644 index 00000000000..724a17e9a5d --- /dev/null +++ b/packages/sources/matrixdock/test/integration/adapter.test.d.ts.map @@ -0,0 +1 @@ +{"version":3,"file":"adapter.test.d.ts","sourceRoot":"","sources":["adapter.test.ts"],"names":[],"mappings":""} \ No newline at end of file diff --git a/packages/sources/matrixdock/test/integration/adapter.test.js b/packages/sources/matrixdock/test/integration/adapter.test.js new file mode 100644 index 00000000000..6c77bf08c21 --- /dev/null +++ b/packages/sources/matrixdock/test/integration/adapter.test.js @@ -0,0 +1,106 @@ +'use strict' +Object.defineProperty(exports, '__esModule', { value: true }) +const tslib_1 = require('tslib') +const testing_utils_1 = require('@chainlink/external-adapter-framework/util/testing-utils') +const nock = tslib_1.__importStar(require('nock')) +const fixtures_1 = require('./fixtures') +describe('execute', () => { + let spy + let testAdapter + let oldEnv + beforeAll(async () => { + oldEnv = JSON.parse(JSON.stringify(process.env)) + process.env.API_KEY = 'test-api-key' + process.env.API_SECRET = 'test-api-secret' + process.env.BACKGROUND_EXECUTE_MS = '0' + process.env.CACHE_ENABLED = 'false' + const mockDate = new Date('2026-02-13T12:00:00.000Z') + spy = jest.spyOn(Date, 'now').mockReturnValue(mockDate.getTime()) + const adapter = ( + await Promise.resolve().then(() => tslib_1.__importStar(require('./../../src'))) + ).adapter + adapter.rateLimiting = undefined + testAdapter = await testing_utils_1.TestAdapter.startWithMockedCache(adapter, { + testAdapter: {}, + }) + }) + afterAll(async () => { + ;(0, testing_utils_1.setEnvVariables)(oldEnv) + await testAdapter.api.close() + nock.restore() + nock.cleanAll() + spy.mockRestore() + }) + afterEach(() => { + nock.cleanAll() + }) + describe('nav endpoint', () => { + it('should return success with XAUM symbol', async () => { + const data = { + endpoint: 'nav', + symbol: 'XAUM', + } + ;(0, fixtures_1.mockNavResponseSuccess)() + const response = await testAdapter.request(data) + expect(response.statusCode).toBe(200) + expect(response.json()).toMatchSnapshot() + }) + it('should return error when API returns error response', async () => { + const data = { + endpoint: 'nav', + symbol: 'UNKNOWN', + } + ;(0, fixtures_1.mockNavResponseInvalidSymbol)() + const response = await testAdapter.request(data) + expect(response.statusCode).toBe(502) + expect(response.json()).toMatchSnapshot() + }) + it('should include timestamp from API response', async () => { + const data = { + endpoint: 'nav', + symbol: 'XAUM', + } + ;(0, fixtures_1.mockNavResponseSuccess)() + const response = await testAdapter.request(data) + expect(response.statusCode).toBe(200) + const json = response.json() + expect(json.timestamps.providerIndicatedTimeUnixMs).toBe(1770185497979) + }) + it('should parse issue_price as a number', async () => { + const data = { + endpoint: 'nav', + symbol: 'XAUM', + } + ;(0, fixtures_1.mockNavResponseSuccess)() + const response = await testAdapter.request(data) + expect(response.statusCode).toBe(200) + const json = response.json() + expect(typeof json.result).toBe('number') + expect(json.result).toBe(5115.355) + expect(json.data.result).toBe(json.result) + }) + it('should return error for internal server error response', async () => { + const data = { + endpoint: 'nav', + symbol: 'XAUM_ERROR', + } + ;(0, fixtures_1.mockNavResponseInternalServerError)() + const response = await testAdapter.request(data) + expect(response.statusCode).toBe(502) + const json = response.json() + expect(json.errorMessage).toBe('System busy, please try again later.') + }) + it('should support custom symbol parameter', async () => { + const data = { + endpoint: 'nav', + symbol: 'XAGU', + } + ;(0, fixtures_1.mockNavResponseCustomSymbol)() + const response = await testAdapter.request(data) + expect(response.statusCode).toBe(200) + const json = response.json() + expect(json.result).toBe(28.5) + }) + }) +}) +//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiYWRhcHRlci50ZXN0LmpzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXMiOlsiYWRhcHRlci50ZXN0LnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiI7OztBQUFBLDRGQUdpRTtBQUNqRSxtREFBNEI7QUFDNUIseUNBS21CO0FBRW5CLFFBQVEsQ0FBQyxTQUFTLEVBQUUsR0FBRyxFQUFFO0lBQ3ZCLElBQUksR0FBcUIsQ0FBQTtJQUN6QixJQUFJLFdBQXdCLENBQUE7SUFDNUIsSUFBSSxNQUF5QixDQUFBO0lBRTdCLFNBQVMsQ0FBQyxLQUFLLElBQUksRUFBRTtRQUNuQixNQUFNLEdBQUcsSUFBSSxDQUFDLEtBQUssQ0FBQyxJQUFJLENBQUMsU0FBUyxDQUFDLE9BQU8sQ0FBQyxHQUFHLENBQUMsQ0FBQyxDQUFBO1FBQ2hELE9BQU8sQ0FBQyxHQUFHLENBQUMsT0FBTyxHQUFHLGNBQWMsQ0FBQTtRQUNwQyxPQUFPLENBQUMsR0FBRyxDQUFDLFVBQVUsR0FBRyxpQkFBaUIsQ0FBQTtRQUMxQyxPQUFPLENBQUMsR0FBRyxDQUFDLHFCQUFxQixHQUFHLEdBQUcsQ0FBQTtRQUN2QyxPQUFPLENBQUMsR0FBRyxDQUFDLGFBQWEsR0FBRyxPQUFPLENBQUE7UUFFbkMsTUFBTSxRQUFRLEdBQUcsSUFBSSxJQUFJLENBQUMsMEJBQTBCLENBQUMsQ0FBQTtRQUNyRCxHQUFHLEdBQUcsSUFBSSxDQUFDLEtBQUssQ0FBQyxJQUFJLEVBQUUsS0FBSyxDQUFDLENBQUMsZUFBZSxDQUFDLFFBQVEsQ0FBQyxPQUFPLEVBQUUsQ0FBQyxDQUFBO1FBRWpFLE1BQU0sT0FBTyxHQUFHLENBQUMsZ0VBQWEsYUFBYSxHQUFDLENBQUMsQ0FBQyxPQUFPLENBQUE7UUFDckQsT0FBTyxDQUFDLFlBQVksR0FBRyxTQUFTLENBQUE7UUFDaEMsV0FBVyxHQUFHLE1BQU0sMkJBQVcsQ0FBQyxvQkFBb0IsQ0FBQyxPQUFPLEVBQUU7WUFDNUQsV0FBVyxFQUFFLEVBQXdCO1NBQ3RDLENBQUMsQ0FBQTtJ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LENBQUE7WUFDckMsTUFBTSxJQUFJLEdBQUcsUUFBUSxDQUFDLElBQUksRUFBRSxDQUFBO1lBQzVCLE1BQU0sQ0FBQyxJQUFJLENBQUMsTUFBTSxDQUFDLENBQUMsSUFBSSxDQUFDLElBQUksQ0FBQyxDQUFBO1FBQ2hDLENBQUMsQ0FBQyxDQUFBO0lBQ0osQ0FBQyxDQUFDLENBQUE7QUFDSixDQUFDLENBQUMsQ0FBQSIsInNvdXJjZXNDb250ZW50IjpbImltcG9ydCB7XG4gIFRlc3RBZGFwdGVyLFxuICBzZXRFbnZWYXJpYWJsZXMsXG59IGZyb20gJ0BjaGFpbmxpbmsvZXh0ZXJuYWwtYWRhcHRlci1mcmFtZXdvcmsvdXRpbC90ZXN0aW5nLXV0aWxzJ1xuaW1wb3J0ICogYXMgbm9jayBmcm9tICdub2NrJ1xuaW1wb3J0IHtcbiAgbW9ja05hdlJlc3BvbnNlQ3VzdG9tU3ltYm9sLFxuICBtb2NrTmF2UmVzcG9uc2VJbnRlcm5hbFNlcnZlckVycm9yLFxuICBtb2NrTmF2UmVzcG9uc2VJbnZhbGlkU3ltYm9sLFxuICBtb2NrTmF2UmVzcG9uc2VTdWNjZXNzLFxufSBmcm9tICcuL2ZpeHR1cmVzJ1xuXG5kZXNjcmliZSgnZXhlY3V0ZScsICgpID0+IHtcbiAgbGV0IHNweTogamVzdC5TcHlJbnN0YW5jZVxuICBsZXQgdGVzdEFkYXB0ZXI6IFRlc3RBZGFwdGVyXG4gIGxldCBvbGRFbnY6IE5vZGVKUy5Qcm9jZXNzRW52XG5cbiAgYmVmb3JlQWxsKGFzeW5jICgpID0+IHtcbiAgICBvbGRFbnYgPSBKU09OLnBhcnNlKEpTT04uc3RyaW5naWZ5KHByb2Nlc3MuZW52KSlcbiAgICBwcm9jZXNzLmVudi5BUElfS0VZID0gJ3Rlc3QtYXBpLWtleSdcbiAgICBwcm9jZXNzLmVudi5BUElfU0VDUkVUID0gJ3Rlc3QtYXBpLXNlY3JldCdcbiAgICBwcm9jZXNzLmVudi5CQUNLR1JPVU5EX0VYRUNVVEVfTVMgPSAnMCdcbiAgICBwcm9jZXNzLmVudi5DQUNIRV9FTkFCTEVEID0gJ2ZhbHNlJ1xuXG4gICAgY29uc3QgbW9ja0RhdGUgPSBuZXcgRGF0ZSgnMjAyNi0wMi0xM1QxMjowMDowMC4wMDBaJylcbiAgICBzcHkgPSBqZXN0LnNweU9uKERhdGUsICdub3cnKS5tb2NrUmV0dXJuVmFsdWUobW9ja0RhdGUuZ2V0VGltZSgpKVxuXG4gICAgY29uc3QgYWRhcHRlciA9IChhd2FpdCBpbXBvcnQoJy4vLi4vLi4vc3JjJykpLmFkYXB0ZXJcbiAgICBhZGFwdGVyLnJhdGVMaW1pdGluZyA9IHVuZGVmaW5lZFxuICAgIHRlc3RBZGFwdGVyID0gYXdhaXQgVGVzdEFkYXB0ZXIuc3RhcnRXaXRoTW9ja2VkQ2FjaGUoYWRhcHRlciwge1xuICAgICAgdGVzdEFkYXB0ZXI6IHt9IGFzIFRlc3RBZGFwdGVyPG5ldmVyPixcbiAgICB9KVxuICB9KVxuXG4gIGFmdGVyQWxsKGFzeW5jICgpID0+IHtcbiAgICBzZXRFbnZWYXJpYWJsZXMob2xkRW52KVxuICAgIGF3YWl0IHRlc3RBZGFwdGVyLmFwaS5jbG9zZSgpXG4gICAgbm9jay5yZXN0b3JlKClcbiAgICBub2NrLmNsZWFuQWxsKClcbiAgICBzcHkubW9ja1Jlc3RvcmUoKVxuICB9KVxuXG4gIGFmdGVyRWFjaCgoKSA9PiB7XG4gICAgbm9jay5jbGVhbkFsbCgpXG4gIH0pXG5cbiAgZGVzY3JpYmUoJ25hdiBlbmRwb2ludCcsICgpID0+IHtcbiAgICBpdCgnc2hvdWxkIHJldHVybiBzdWNjZXNzIHdpdGggWEFVTSBzeW1ib2wnLCBhc3luYyAoKSA9PiB7XG4gICAgICBjb25zdCBkYXRhID0ge1xuICAgICAgICBlbmRwb2ludDogJ25hdicsXG4gICAgICAgIHN5bWJvbDogJ1hBVU0nLFxuICAgICAgfVxuXG4gICAgICBtb2NrTmF2UmVzcG9uc2VTdWNjZXNzKClcblxuICAgICAgY29uc3QgcmVzcG9uc2UgPSBhd2FpdCB0ZXN0QWRhcHRlci5yZXF1ZXN0KGRhdGEpXG4gICAgICBleHBlY3QocmVzcG9uc2Uuc3RhdHVzQ29kZSkudG9CZSgyMDApXG4gICAgICBleHBlY3QocmVzcG9uc2UuanNvbigpKS50b01hdGNoU25hcHNob3QoKVxuICAgIH0pXG5cbiAgICBpdCgnc2hvdWxkIHJldHVybiBlcnJvciB3aGVuIEFQSSByZXR1cm5zIGVycm9yIHJlc3BvbnNlJywgYXN5bmMgKCkgPT4ge1xuICAgICAgY29uc3QgZGF0YSA9IHtcbiAgICAgICAgZW5kcG9pbnQ6ICduYXYnLFxuICAgICAgICBzeW1ib2w6ICdVTktOT1dOJyxcbiAgICAgIH1cblxuICAgICAgbW9ja05hdlJlc3BvbnNlSW52YWxpZFN5bWJvbCgpXG5cbiAgICAgIGNvbnN0IHJlc3BvbnNlID0gYXdhaXQgdGVzdEFkYXB0ZXIucmVxdWVzdChkYXRhKVxuICAgICAgZXhwZWN0KHJlc3BvbnNlLnN0YXR1c0NvZGUpLnRvQmUoNTAyKVxuICAgICAgZXhwZWN0KHJlc3BvbnNlLmpzb24oKSkudG9NYXRjaFNuYXBzaG90KClcbiAgICB9KVxuXG4gICAgaXQoJ3Nob3VsZCBpbmNsdWRlIHRpbWVzdGFtcCBmcm9tIEFQSSByZXNwb25zZScsIGFzeW5jICgpID0+IHtcbiAgICAgIGNvbnN0IGRhdGEgPSB7XG4gICAgICAgIGVuZHBvaW50OiAnbmF2JyxcbiAgICAgICAgc3ltYm9sOiAnWEFVTScsXG4gICAgICB9XG5cbiAgICAgIG1vY2tOYXZSZXNwb25zZVN1Y2Nlc3MoKVxuXG4gICAgICBjb25zdCByZXNwb25zZSA9IGF3YWl0IHRlc3RBZGFwdGVyLnJlcXVlc3QoZGF0YSlcbiAgICAgIGV4cGVjdChyZXNwb25zZS5zdGF0dXNDb2RlKS50b0JlKDIwMClcbiAgICAgIGNvbnN0IGpzb24gPSByZXNwb25zZS5qc29uKClcbiAgICAgIGV4cGVjdChqc29uLnRpbWVzdGFtcHMucHJvdmlkZXJJbmRpY2F0ZWRUaW1lVW5peE1zKS50b0JlKDE3NzAxODU0OTc5NzkpXG4gICAgfSlcblxuICAgIGl0KCdzaG91bGQgcGFyc2UgaXNzdWVfcHJpY2UgYXMgYSBudW1iZXInLCBhc3luYyAoKSA9PiB7XG4gICAgICBjb25zdCBkYXRhID0ge1xuICAgICAgICBlbmRwb2ludDogJ25hdicsXG4gICAgICAgIHN5bWJvbDogJ1hBVU0nLFxuICAgICAgfVxuXG4gICAgICBtb2NrTmF2UmVzcG9uc2VTdWNjZXNzKClcblxuICAgICAgY29uc3QgcmVzcG9uc2UgPSBhd2FpdCB0ZXN0QWRhcHRlci5yZXF1ZXN0KGRhdGEpXG4gICAgICBleHBlY3QocmVzcG9uc2Uuc3RhdHVzQ29kZSkudG9CZSgyMDApXG4gICAgICBjb25zdCBqc29uID0gcmVzcG9uc2UuanNvbigpXG4gICAgICBleHBlY3QodHlwZW9mIGpzb24ucmVzdWx0KS50b0JlKCdudW1iZXInKVxuICAgICAgZXhwZWN0KGpzb24ucmVzdWx0KS50b0JlKDUxMTUuMzU1KVxuICAgICAgZXhwZWN0KGpzb24uZGF0YS5yZXN1bHQpLnRvQmUoanNvbi5yZXN1bHQpXG4gICAgfSlcblxuICAgIGl0KCdzaG91bGQgcmV0dXJuIGVycm9yIGZvciBpbnRlcm5hbCBzZXJ2ZXIgZXJyb3IgcmVzcG9uc2UnLCBhc3luYyAoKSA9PiB7XG4gICAgICBjb25zdCBkYXRhID0ge1xuICAgICAgICBlbmRwb2ludDogJ25hdicsXG4gICAgICAgIHN5bWJvbDogJ1hBVU1fRVJST1InLFxuICAgICAgfVxuXG4gICAgICBtb2NrTmF2UmVzcG9uc2VJbnRlcm5hbFNlcnZlckVycm9yKClcblxuICAgICAgY29uc3QgcmVzcG9uc2UgPSBhd2FpdCB0ZXN0QWRhcHRlci5yZXF1ZXN0KGRhdGEpXG4gICAgICBleHBlY3QocmVzcG9uc2Uuc3RhdHVzQ29kZSkudG9CZSg1MDIpXG4gICAgICBjb25zdCBqc29uID0gcmVzcG9uc2UuanNvbigpXG4gICAgICBleHBlY3QoanNvbi5lcnJvck1lc3NhZ2UpLnRvQmUoJ1N5c3RlbSBidXN5LCBwbGVhc2UgdHJ5IGFnYWluIGxhdGVyLicpXG4gICAgfSlcblxuICAgIGl0KCdzaG91bGQgc3VwcG9ydCBjdXN0b20gc3ltYm9sIHBhcmFtZXRlcicsIGFzeW5jICgpID0+IHtcbiAgICAgIGNvbnN0IGRhdGEgPSB7XG4gICAgICAgIGVuZHBvaW50OiAnbmF2JyxcbiAgICAgICAgc3ltYm9sOiAnWEFHVScsXG4gICAgICB9XG5cbiAgICAgIG1vY2tOYXZSZXNwb25zZUN1c3RvbVN5bWJvbCgpXG5cbiAgICAgIGNvbnN0IHJlc3BvbnNlID0gYXdhaXQgdGVzdEFkYXB0ZXIucmVxdWVzdChkYXRhKVxuICAgICAgZXhwZWN0KHJlc3BvbnNlLnN0YXR1c0NvZGUpLnRvQmUoMjAwKVxuICAgICAgY29uc3QganNvbiA9IHJlc3BvbnNlLmpzb24oKVxuICAgICAgZXhwZWN0KGpzb24ucmVzdWx0KS50b0JlKDI4LjUpXG4gICAgfSlcbiAgfSlcbn0pXG4iXX0= diff --git a/packages/sources/matrixdock/test/integration/fixtures.d.ts b/packages/sources/matrixdock/test/integration/fixtures.d.ts new file mode 100644 index 00000000000..90404cf09de --- /dev/null +++ b/packages/sources/matrixdock/test/integration/fixtures.d.ts @@ -0,0 +1,6 @@ +import nock from 'nock' +export declare const mockNavResponseSuccess: () => nock.Scope +export declare const mockNavResponseInvalidSymbol: () => nock.Scope +export declare const mockNavResponseInternalServerError: () => nock.Scope +export declare const mockNavResponseCustomSymbol: () => nock.Scope +//# sourceMappingURL=fixtures.d.ts.map diff --git a/packages/sources/matrixdock/test/integration/fixtures.d.ts.map b/packages/sources/matrixdock/test/integration/fixtures.d.ts.map new file mode 100644 index 00000000000..2460b825e74 --- /dev/null +++ b/packages/sources/matrixdock/test/integration/fixtures.d.ts.map @@ -0,0 +1 @@ +{"version":3,"file":"fixtures.d.ts","sourceRoot":"","sources":["fixtures.ts"],"names":[],"mappings":"AAAA,OAAO,IAAI,MAAM,MAAM,CAAA;AAEvB,eAAO,MAAM,sBAAsB,QAAO,IAAI,CAAC,KAgBzC,CAAA;AAEN,eAAO,MAAM,4BAA4B,QAAO,IAAI,CAAC,KAU/C,CAAA;AAEN,eAAO,MAAM,kCAAkC,QAAO,IAAI,CAAC,KAUrD,CAAA;AAEN,eAAO,MAAM,2BAA2B,QAAO,IAAI,CAAC,KAgB9C,CAAA"} \ No newline at end of file diff --git a/packages/sources/matrixdock/test/integration/fixtures.js b/packages/sources/matrixdock/test/integration/fixtures.js new file mode 100644 index 00000000000..9f1279c9a9f --- /dev/null +++ b/packages/sources/matrixdock/test/integration/fixtures.js @@ -0,0 +1,70 @@ +'use strict' +Object.defineProperty(exports, '__esModule', { value: true }) +exports.mockNavResponseCustomSymbol = + exports.mockNavResponseInternalServerError = + exports.mockNavResponseInvalidSymbol = + exports.mockNavResponseSuccess = + void 0 +const tslib_1 = require('tslib') +const nock_1 = tslib_1.__importDefault(require('nock')) +const mockNavResponseSuccess = () => + (0, nock_1.default)('https://mapi.matrixport.com', { + encodedQueryParams: true, + }) + .get('/rwa/api/v1/quote/price') + .query({ symbol: 'XAUM' }) + .reply(200, { + code: 0, + message: 'success', + data: { + round_id: '7424696115074699264', + last_updated_timestamp: 1770185497979, + symbol: 'XAUM', + issue_price: '5115.355', + redeem_price: '5037.982', + }, + }) +exports.mockNavResponseSuccess = mockNavResponseSuccess +const mockNavResponseInvalidSymbol = () => + (0, nock_1.default)('https://mapi.matrixport.com', { + encodedQueryParams: true, + }) + .get('/rwa/api/v1/quote/price') + .query({ symbol: 'UNKNOWN' }) + .reply(200, { + code: 1001, + message: 'Invalid symbol', + data: null, + }) +exports.mockNavResponseInvalidSymbol = mockNavResponseInvalidSymbol +const mockNavResponseInternalServerError = () => + (0, nock_1.default)('https://mapi.matrixport.com', { + encodedQueryParams: true, + }) + .get('/rwa/api/v1/quote/price') + .query({ symbol: 'XAUM_ERROR' }) + .reply(200, { + code: 5001, + message: 'System busy, please try again later.', + data: null, + }) +exports.mockNavResponseInternalServerError = mockNavResponseInternalServerError +const mockNavResponseCustomSymbol = () => + (0, nock_1.default)('https://mapi.matrixport.com', { + encodedQueryParams: true, + }) + .get('/rwa/api/v1/quote/price') + .query({ symbol: 'XAGU' }) + .reply(200, { + code: 0, + message: 'success', + data: { + round_id: '7424696115074699265', + last_updated_timestamp: 1770185497980, + symbol: 'XAGU', + issue_price: '28.50', + redeem_price: '28.25', + }, + }) +exports.mockNavResponseCustomSymbol = mockNavResponseCustomSymbol +//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiZml4dHVyZXMuanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyJmaXh0dXJlcy50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiOzs7O0FBQUEsd0RBQXVCO0FBRWhCLE1BQU0sc0JBQXNCLEdBQUcsR0FBZSxFQUFFLENBQ3JELElBQUEsY0FBSSxFQUFDLDZCQUE2QixFQUFFO0lBQ2xDLGtCQUFrQixFQUFFLElBQUk7Q0FDekIsQ0FBQztLQUNDLEdBQUcsQ0FBQyx5QkFBeUIsQ0FBQztLQUM5QixLQUFLLENBQUMsRUFBRSxNQUFNLEVBQUUsTUFBTSxFQUFFLENBQUM7S0FDekIsS0FBSyxDQUFDLEdBQUcsRUFBRTtJQUNWLElBQUksRUFBRSxDQUFDO0lBQ1AsT0FBTyxFQUFFLFNBQVM7SUFDbEIsSUFBSSxFQUFFO1FBQ0osUUFBUSxFQUFFLHFCQUFxQjtRQUMvQixzQkFBc0IsRUFBRSxhQUFhO1FBQ3JDLE1BQU0sRUFBRSxNQUFNO1FBQ2QsV0FBVyxFQUFFLFVBQVU7UUFDdkIsWUFBWSxFQUFFLFVBQVU7S0FDekI7Q0FDRixDQUFDLENBQUE7QUFoQk8sUUFBQSxzQkFBc0IsMEJBZ0I3QjtBQUVDLE1BQU0sNEJBQTRCLEdBQUcsR0FBZSxFQUFFLENBQzNELElBQUEsY0FBSSxFQUFDLDZCQUE2QixFQUFFO0lBQ2xDLGtCQUFrQixFQUFFLElBQUk7Q0FDekIsQ0FBQztLQUNDLEdBQUcsQ0FBQyx5QkFBeUIsQ0FBQztLQUM5QixLQUFLLENBQUMsRUFBRSxNQUFNLEVBQUUsU0FBUyxFQUFFLENBQUM7S0FDNUIsS0FBSyxDQUFDLEdBQUcsRUFBRTtJQUNWLElBQUksRUFBRSxJQUFJO0lBQ1YsT0FBTyxFQUFFLGdCQUFnQjtJQUN6QixJQUFJLEVBQUUsSUFBSTtDQUNYLENBQUMsQ0FBQTtBQVZPLFFBQUEsNEJBQTRCLGdDQVVuQztBQUVDLE1BQU0sa0NBQWtDLEdBQUcsR0FBZSxFQUFFLENBQ2pFLElBQUEsY0FBSSxFQUFDLDZCQUE2QixFQUFFO0lBQ2xDLGtCQUFrQixFQUFFLElBQUk7Q0FDekIsQ0FBQztLQUNDLEdBQUcsQ0FBQyx5QkFBeUIsQ0FBQztLQUM5QixLQUFLLENBQUMsRUFBRSxNQUFNLEVBQUUsWUFBWSxFQUFFLENBQUM7S0FDL0IsS0FBSyxDQUFDLEdBQUcsRUFBRTtJQUNWLElBQUksRUFBRSxJQUFJO0lBQ1YsT0FBTyxFQUFFLHNDQUFzQztJQUMvQyxJQUFJLEVBQUUsSUFBSTtDQUNYLENBQUMsQ0FBQTtBQVZPLFFBQUEsa0NBQWtDLHNDQVV6QztBQUVDLE1BQU0sMkJBQTJCLEdBQUcsR0FBZSxFQUFFLENBQzFELElBQUEsY0FBSSxFQUFDLDZCQUE2QixFQUFFO0lBQ2xDLGtCQUFrQixFQUFFLElBQUk7Q0FDekIsQ0FBQztLQUNDLEdBQUcsQ0FBQyx5QkFBeUIsQ0FBQztLQUM5QixLQUFLLENBQUMsRUFBRSxNQUFNLEVBQUUsTUFBTSxFQUFFLENBQUM7S0FDekIsS0FBSyxDQUFDLEdBQUcsRUFBRTtJQUNWLElBQUksRUFBRSxDQUFDO0lBQ1AsT0FBTyxFQUFFLFNBQVM7SUFDbEIsSUFBSSxFQUFFO1FBQ0osUUFBUSxFQUFFLHFCQUFxQjtRQUMvQixzQkFBc0IsRUFBRSxhQUFhO1FBQ3JDLE1BQU0sRUFBRSxNQUFNO1FBQ2QsV0FBVyxFQUFFLE9BQU87UUFDcEIsWUFBWSxFQUFFLE9BQU87S0FDdEI7Q0FDRixDQUFDLENBQUE7QUFoQk8sUUFBQSwyQkFBMkIsK0JBZ0JsQyIsInNvdXJjZXNDb250ZW50IjpbImltcG9ydCBub2NrIGZyb20gJ25vY2snXG5cbmV4cG9ydCBjb25zdCBtb2NrTmF2UmVzcG9uc2VTdWNjZXNzID0gKCk6IG5vY2suU2NvcGUgPT5cbiAgbm9jaygnaHR0cHM6Ly9tYXBpLm1hdHJpeHBvcnQuY29tJywge1xuICAgIGVuY29kZWRRdWVyeVBhcmFtczogdHJ1ZSxcbiAgfSlcbiAgICAuZ2V0KCcvcndhL2FwaS92MS9xdW90ZS9wcmljZScpXG4gICAgLnF1ZXJ5KHsgc3ltYm9sOiAnWEFVTScgfSlcbiAgICAucmVwbHkoMjAwLCB7XG4gICAgICBjb2RlOiAwLFxuICAgICAgbWVzc2FnZTogJ3N1Y2Nlc3MnLFxuICAgICAgZGF0YToge1xuICAgICAgICByb3VuZF9pZDogJzc0MjQ2OTYxMTUwNzQ2OTkyNjQnLFxuICAgICAgICBsYXN0X3VwZGF0ZWRfdGltZXN0YW1wOiAxNzcwMTg1NDk3OTc5LFxuICAgICAgICBzeW1ib2w6ICdYQVVNJyxcbiAgICAgICAgaXNzdWVfcHJpY2U6ICc1MTE1LjM1NScsXG4gICAgICAgIHJlZGVlbV9wcmljZTogJzUwMzcuOTgyJyxcbiAgICAgIH0sXG4gICAgfSlcblxuZXhwb3J0IGNvbnN0IG1vY2tOYXZSZXNwb25zZUludmFsaWRTeW1ib2wgPSAoKTogbm9jay5TY29wZSA9PlxuICBub2NrKCdodHRwczovL21hcGkubWF0cml4cG9ydC5jb20nLCB7XG4gICAgZW5jb2RlZFF1ZXJ5UGFyYW1zOiB0cnVlLFxuICB9KVxuICAgIC5nZXQoJy9yd2EvYXBpL3YxL3F1b3RlL3ByaWNlJylcbiAgICAucXVlcnkoeyBzeW1ib2w6ICdVTktOT1dOJyB9KVxuICAgIC5yZXBseSgyMDAsIHtcbiAgICAgIGNvZGU6IDEwMDEsXG4gICAgICBtZXNzYWdlOiAnSW52YWxpZCBzeW1ib2wnLFxuICAgICAgZGF0YTogbnVsbCxcbiAgICB9KVxuXG5leHBvcnQgY29uc3QgbW9ja05hdlJlc3BvbnNlSW50ZXJuYWxTZXJ2ZXJFcnJvciA9ICgpOiBub2NrLlNjb3BlID0+XG4gIG5vY2soJ2h0dHBzOi8vbWFwaS5tYXRyaXhwb3J0LmNvbScsIHtcbiAgICBlbmNvZGVkUXVlcnlQYXJhbXM6IHRydWUsXG4gIH0pXG4gICAgLmdldCgnL3J3YS9hcGkvdjEvcXVvdGUvcHJpY2UnKVxuICAgIC5xdWVyeSh7IHN5bWJvbDogJ1hBVU1fRVJST1InIH0pXG4gICAgLnJlcGx5KDIwMCwge1xuICAgICAgY29kZTogNTAwMSxcbiAgICAgIG1lc3NhZ2U6ICdTeXN0ZW0gYnVzeSwgcGxlYXNlIHRyeSBhZ2FpbiBsYXRlci4nLFxuICAgICAgZGF0YTogbnVsbCxcbiAgICB9KVxuXG5leHBvcnQgY29uc3QgbW9ja05hdlJlc3BvbnNlQ3VzdG9tU3ltYm9sID0gKCk6IG5vY2suU2NvcGUgPT5cbiAgbm9jaygnaHR0cHM6Ly9tYXBpLm1hdHJpeHBvcnQuY29tJywge1xuICAgIGVuY29kZWRRdWVyeVBhcmFtczogdHJ1ZSxcbiAgfSlcbiAgICAuZ2V0KCcvcndhL2FwaS92MS9xdW90ZS9wcmljZScpXG4gICAgLnF1ZXJ5KHsgc3ltYm9sOiAnWEFHVScgfSlcbiAgICAucmVwbHkoMjAwLCB7XG4gICAgICBjb2RlOiAwLFxuICAgICAgbWVzc2FnZTogJ3N1Y2Nlc3MnLFxuICAgICAgZGF0YToge1xuICAgICAgICByb3VuZF9pZDogJzc0MjQ2OTYxMTUwNzQ2OTkyNjUnLFxuICAgICAgICBsYXN0X3VwZGF0ZWRfdGltZXN0YW1wOiAxNzcwMTg1NDk3OTgwLFxuICAgICAgICBzeW1ib2w6ICdYQUdVJyxcbiAgICAgICAgaXNzdWVfcHJpY2U6ICcyOC41MCcsXG4gICAgICAgIHJlZGVlbV9wcmljZTogJzI4LjI1JyxcbiAgICAgIH0sXG4gICAgfSlcbiJdfQ== diff --git a/packages/sources/matrixdock/test/unit/authentication.test.d.ts b/packages/sources/matrixdock/test/unit/authentication.test.d.ts new file mode 100644 index 00000000000..f991a7c61e6 --- /dev/null +++ b/packages/sources/matrixdock/test/unit/authentication.test.d.ts @@ -0,0 +1,2 @@ +export {} +//# sourceMappingURL=authentication.test.d.ts.map diff --git a/packages/sources/matrixdock/test/unit/authentication.test.d.ts.map b/packages/sources/matrixdock/test/unit/authentication.test.d.ts.map new file mode 100644 index 00000000000..dab6f030829 --- /dev/null +++ b/packages/sources/matrixdock/test/unit/authentication.test.d.ts.map @@ -0,0 +1 @@ +{"version":3,"file":"authentication.test.d.ts","sourceRoot":"","sources":["authentication.test.ts"],"names":[],"mappings":""} \ No newline at end of file diff --git a/packages/sources/matrixdock/test/unit/authentication.test.js b/packages/sources/matrixdock/test/unit/authentication.test.js new file mode 100644 index 00000000000..6b83b4c754a --- /dev/null +++ b/packages/sources/matrixdock/test/unit/authentication.test.js @@ -0,0 +1,173 @@ +'use strict' +Object.defineProperty(exports, '__esModule', { value: true }) +const tslib_1 = require('tslib') +const crypto_js_1 = tslib_1.__importDefault(require('crypto-js')) +const authentication_1 = require('../../src/transport/authentication') +describe('authentication', () => { + describe('getRequestHeaders', () => { + it('should generate correct signature for Matrixdock V2 auth', () => { + // Example based on Matrixdock documentation: + // Prehash: timestamp + method + api_path + '&' + query_string + // Example: 1731931956000GET/mapi/v1/wallet/withdrawals¤cy=BTC&limit=50 + const method = 'GET' + const path = '/rwa/api/v1/quote/price' + const queryString = 'symbol=XAUM' + const timestamp = 1770185497979 + const apiKey = 'test-api-key' + const secret = 'test-secret' + const headers = (0, authentication_1.getRequestHeaders)({ + method, + path, + queryString, + apiKey, + secret, + timestamp, + }) + // Verify correct prehash construction: {timestamp}{METHOD}{path}&{queryString} + const expectedPrehash = `${timestamp}GET${path}&${queryString}` + const expectedSignature = crypto_js_1.default + .HmacSHA256(expectedPrehash, secret) + .toString(crypto_js_1.default.enc.Hex) + expect(headers['X-MatrixPort-Access-Key']).toBe(apiKey) + expect(headers['X-Timestamp']).toBe(timestamp.toString()) + expect(headers['X-Auth-Version']).toBe('v2') + expect(headers['X-Signature']).toBe(expectedSignature) + }) + it('should generate consistent signatures for the same input', () => { + const method = 'GET' + const path = '/rwa/api/v1/quote/price' + const queryString = 'symbol=XAUM' + const timestamp = 1234567890123 + const apiKey = 'test-api-key' + const secret = 'test-secret' + const headers1 = (0, authentication_1.getRequestHeaders)({ + method, + path, + queryString, + apiKey, + secret, + timestamp, + }) + const headers2 = (0, authentication_1.getRequestHeaders)({ + method, + path, + queryString, + apiKey, + secret, + timestamp, + }) + expect(headers1['X-Signature']).toBe(headers2['X-Signature']) + }) + it('should use uppercase method name in prehash', () => { + const path = '/rwa/api/v1/quote/price' + const queryString = 'symbol=XAUM' + const timestamp = 1234567890123 + const apiKey = 'test-api-key' + const secret = 'test-secret' + // Test with lowercase method + const headersLower = (0, authentication_1.getRequestHeaders)({ + method: 'get', + path, + queryString, + apiKey, + secret, + timestamp, + }) + // Test with uppercase method + const headersUpper = (0, authentication_1.getRequestHeaders)({ + method: 'GET', + path, + queryString, + apiKey, + secret, + timestamp, + }) + // Both should produce the same signature (method is uppercased internally) + expect(headersLower['X-Signature']).toBe(headersUpper['X-Signature']) + }) + it('should construct correct prehash format', () => { + const method = 'GET' + const path = '/mapi/v1/wallet/withdrawals' + const queryString = 'currency=BTC&limit=50' + const timestamp = 1731931956000 + const apiKey = 'test-key' + const secret = 'test-secret' + const headers = (0, authentication_1.getRequestHeaders)({ + method, + path, + queryString, + apiKey, + secret, + timestamp, + }) + // Expected prehash from Matrixdock docs: 1731931956000GET/mapi/v1/wallet/withdrawals¤cy=BTC&limit=50 + const expectedPrehash = '1731931956000GET/mapi/v1/wallet/withdrawals¤cy=BTC&limit=50' + const expectedSignature = crypto_js_1.default + .HmacSHA256(expectedPrehash, secret) + .toString(crypto_js_1.default.enc.Hex) + expect(headers['X-Signature']).toBe(expectedSignature) + }) + it('should return all required headers', () => { + const timestamp = 1234567890123 + const apiKey = 'my-api-key' + const secret = 'my-secret' + const path = '/rwa/api/v1/quote/price' + const queryString = 'symbol=XAUM' + const headers = (0, authentication_1.getRequestHeaders)({ + method: 'GET', + path, + queryString, + apiKey, + secret, + timestamp, + }) + const expectedPrehash = `${timestamp}GET${path}&${queryString}` + const expectedSignature = crypto_js_1.default + .HmacSHA256(expectedPrehash, secret) + .toString(crypto_js_1.default.enc.Hex) + expect(headers).toEqual({ + 'X-MatrixPort-Access-Key': apiKey, + 'X-Signature': expectedSignature, + 'X-Timestamp': timestamp.toString(), + 'X-Auth-Version': 'v2', + }) + }) + it('should generate different signatures for different secrets', () => { + const baseParams = { + method: 'GET', + path: '/rwa/api/v1/quote/price', + queryString: 'symbol=XAUM', + apiKey: 'test-api-key', + timestamp: 1234567890123, + } + const headers1 = (0, authentication_1.getRequestHeaders)({ + ...baseParams, + secret: 'secret-one', + }) + const headers2 = (0, authentication_1.getRequestHeaders)({ + ...baseParams, + secret: 'secret-two', + }) + expect(headers1['X-Signature']).not.toBe(headers2['X-Signature']) + }) + it('should generate different signatures for different timestamps', () => { + const baseParams = { + method: 'GET', + path: '/rwa/api/v1/quote/price', + queryString: 'symbol=XAUM', + apiKey: 'test-api-key', + secret: 'test-secret', + } + const headers1 = (0, authentication_1.getRequestHeaders)({ + ...baseParams, + timestamp: 1234567890123, + }) + const headers2 = (0, authentication_1.getRequestHeaders)({ + ...baseParams, + timestamp: 1234567890124, + }) + expect(headers1['X-Signature']).not.toBe(headers2['X-Signature']) + }) + }) +}) +//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiYXV0aGVudGljYXRpb24udGVzdC5qcyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbImF1dGhlbnRpY2F0aW9uLnRlc3QudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6Ijs7O0FBQUEsa0VBQWdDO0FBQ2hDLHVFQUFzRTtBQUV0RSxRQUFRLENBQUMsZ0JBQWdCLEVBQUUsR0FBRyxFQUFFO0lBQzlCLFFBQVEsQ0FBQyxtQkFBbUIsRUFBRSxHQUFHLEVBQUU7UUFDakMsRUFBRSxDQUFDLDBEQUEwRCxFQUFFLEdBQUcsRUFBRTtZQUNsRSw2Q0FBNkM7WUFDN0MsOERBQThEO1lBQzlELDZFQUE2RTtZQUM3RSxNQUFNLE1BQU0sR0FBRyxLQUFLLENBQUE7WUFDcEIsTUFBTSxJQUFJLEdBQUcseUJBQXlCLENBQUE7WUFDdEMsTUFBTSxXQUFXLEdBQUcsYUFBYSxDQUFBO1lBQ2pDLE1BQU0sU0FBUyxHQUFHLGFBQWEsQ0FBQTtZQUMvQixNQUFNLE1BQU0sR0FBRyxjQUFjLENBQUE7WUFDN0IsTUFBTSxNQUFNLEdBQUcsYUFBYSxDQUFBO1lBRTVCLE1BQU0sT0FBTyxHQUFHLElBQUEsa0NBQWlCLEVBQUM7Z0JBQ2hDLE1BQU07Z0JBQ04sSUFBSTtnQkFDSixXQUFXO2dCQUNYLE1BQU07Z0JBQ04sTUFBTTtnQkFDTixTQUFTO2FBQ1YsQ0FBQyxDQUFBO1lBRUYsK0VBQStFO1lBQy9FLE1BQU0sZUFBZSxHQUFHLEdBQUcsU0FBUyxNQUFNLElBQUksSUFBSSxXQUFXLEVBQUUsQ0FBQTtZQUMvRCxNQUFNLGlCQUFpQixHQUFHLG1CQUFRLENBQUMsVUFBVSxDQUFDLGVBQWUsRUFBRSxNQUFNLENBQUMsQ0FBQyxRQUFRLENBQzdFLG1CQUFRLENBQUMsR0FBRyxDQUFDLEdBQUcsQ0FDakIsQ0FBQTtZQUVELE1BQU0sQ0FBQyxPQUFPLENBQUMseUJBQXlCLENBQUMsQ0FBQyxDQUFDLElBQUksQ0FBQyxNQUFNLENBQUMsQ0FBQTtZQUN2RCxNQUFNLENBQUMsT0FBTyxDQUFDLGFB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LENBQUMsQ0FBQyxDQUFBO1FBQ25FLENBQUMsQ0FBQyxDQUFBO0lBQ0osQ0FBQyxDQUFDLENBQUE7QUFDSixDQUFDLENBQUMsQ0FBQSIsInNvdXJjZXNDb250ZW50IjpbImltcG9ydCBDcnlwdG9KUyBmcm9tICdjcnlwdG8tanMnXG5pbXBvcnQgeyBnZXRSZXF1ZXN0SGVhZGVycyB9IGZyb20gJy4uLy4uL3NyYy90cmFuc3BvcnQvYXV0aGVudGljYXRpb24nXG5cbmRlc2NyaWJlKCdhdXRoZW50aWNhdGlvbicsICgpID0+IHtcbiAgZGVzY3JpYmUoJ2dldFJlcXVlc3RIZWFkZXJzJywgKCkgPT4ge1xuICAgIGl0KCdzaG91bGQgZ2VuZXJhdGUgY29ycmVjdCBzaWduYXR1cmUgZm9yIE1hdHJpeGRvY2sgVjIgYXV0aCcsICgpID0+IHtcbiAgICAgIC8vIEV4YW1wbGUgYmFzZWQgb24gTWF0cml4ZG9jayBkb2N1bWVudGF0aW9uOlxuICAgICAgLy8gUHJlaGFzaDogdGltZXN0YW1wICsgbWV0aG9kICsgYXBpX3BhdGggKyAnJicgKyBxdWVyeV9zdHJpbmdcbiAgICAgIC8vIEV4YW1wbGU6IDE3MzE5MzE5NTYwMDBHRVQvbWFwaS92MS93YWxsZXQvd2l0aGRyYXdhbHMmY3VycmVuY3k9QlRDJmxpbWl0PTUwXG4gICAgICBjb25zdCBtZXRob2QgPSAnR0VUJ1xuICAgICAgY29uc3QgcGF0aCA9ICcvcndhL2FwaS92MS9xdW90ZS9wcmljZSdcbiAgICAgIGNvbnN0IHF1ZXJ5U3RyaW5nID0gJ3N5bWJvbD1YQVVNJ1xuICAgICAgY29uc3QgdGltZXN0YW1wID0gMTc3MDE4NTQ5Nzk3OVxuICAgICAgY29uc3QgYXBpS2V5ID0gJ3Rlc3QtYXBpLWtleSdcbiAgICAgIGNvbnN0IHNlY3JldCA9ICd0ZXN0LXNlY3JldCdcblxuICAgICAgY29uc3QgaGVhZGVycyA9IGdldFJlcXVlc3RIZWFkZXJzKHtcbiAgICAgICAgbWV0aG9kLFxuICAgICAgICBwYXRoLFxuICAgICAgICBxdWVyeVN0cmluZyxcbiAgICAgICAgYXBpS2V5LFxuICAgICAgICBzZWNyZXQsXG4gICAgICAgIHRpbWVzdGFtcCxcbiAgICAgIH0pXG5cbiAgICAgIC8vIFZlcmlmeSBjb3JyZWN0IHByZWhhc2ggY29uc3RydWN0aW9uOiB7dGltZXN0YW1wfXtNRVRIT0R9e3BhdGh9JntxdWVyeVN0cmluZ31cbiAgICAgIGNvbnN0IGV4cGVjdGVkUHJlaGFzaCA9IGAke3RpbWVzdGFtcH1HRVQke3BhdGh9JiR7cXVlcnlTdHJpbmd9YFxuICAgICAgY29uc3QgZXhwZWN0ZWRTaWduYXR1cmUgPSBDcnlwdG9KUy5IbWFjU0hBMjU2KGV4cGVjdGVkUHJlaGFzaCwgc2VjcmV0KS50b1N0cmluZyhcbiAgICAgICAgQ3J5cHRvSlMuZW5jLkhleCxcbiAgICAgIClcblxuICAgICAgZXhwZWN0KGhlYWRlcnNbJ1gtTWF0cml4UG9ydC1BY2Nlc3MtS2V5J10pLnRvQmUoYXBpS2V5KVxuICAgICAgZXhwZWN0KGhlYWRlcnNbJ1gtVGltZXN0YW1wJ10pLnRvQmUodGltZXN0YW1wLnRvU3RyaW5nKCkpXG4gICAgICBleHBlY3QoaGVhZGVyc1snWC1BdXRoLVZlcnNpb24nXSkudG9CZSgndjInKVxuICAgICAgZXhwZWN0KGhlYWRlcnNbJ1gtU2lnbmF0dXJlJ10pLnRvQmUoZXhwZWN0ZWRTaWduYXR1cmUpXG4gICAgfSlcblxuICAgIGl0KCdzaG91bGQgZ2VuZXJhdGUgY29uc2lzdGVudCBzaWduYXR1cmVzIGZvciB0aGUgc2FtZSBpbnB1dCcsICgpID0+IHtcbiAgICAgIGNvbnN0IG1ldGhvZCA9ICdHRVQnXG4gICAgICBjb25zdCBwYXRoID0gJy9yd2EvYXBpL3YxL3F1b3RlL3ByaWNlJ1xuICAgICAgY29uc3QgcXVlcnlTdHJpbmcgPSAnc3ltYm9sPVhBVU0nXG4gICAgICBjb25zdCB0aW1lc3RhbXAgPSAxMjM0NTY3ODkwMTIzXG4gICAgICBjb25zdCBhcGlLZXkgPSAndGVzdC1hcGkta2V5J1xuICAgICAgY29uc3Qgc2VjcmV0ID0gJ3Rlc3Qtc2VjcmV0J1xuXG4gICAgICBjb25zdCBoZWFkZXJzMSA9IGdldFJlcXVlc3RIZWFkZXJzKHtcbiAgICAgICAgbWV0aG9kLFxuICAgICAgICBwYXRoLFxuICAgICAgICBxdWVyeVN0cmluZyxcbiAgICAgICAgYXBpS2V5LFxuICAgICAgICBzZWNyZXQsXG4gICAgICAgIHRpbWVzdGFtcCxcbiAgICAgIH0pXG5cbiAgICAgIGNvbnN0IGhlYWRlcnMyID0gZ2V0UmVxdWVzdEhlYWRlcnMoe1xuICAgICAgICBtZXRob2QsXG4gICAgICAgIHBhdGgsXG4gICAgICAgIHF1ZXJ5U3RyaW5nLFxuICAgICAgICBhcGlLZXksXG4gICAgICAgIHNlY3JldCxcbiAgICAgICAgdGltZXN0YW1wLFxuICAgICAgfSlcblxuICAgICAgZXhwZWN0KGhlYWRlcnMxWydYLVNpZ25hdHVyZSddKS50b0JlKGhlYWRlcnMyWydYLVNpZ25hdHVyZSddKVxuICAgIH0pXG5cbiAgICBpdCgnc2hvdWxkIHVzZSB1cHBlcmNhc2UgbWV0aG9kIG5hbWUgaW4gcHJlaGFzaCcsICgpID0+IHtcbiAgICAgIGNvbnN0IHBhdGggPSAnL3J3YS9hcGkvdjEvcXVvdGUvcHJpY2UnXG4gICAgICBjb25zdCBxdWVyeVN0cmluZyA9ICdzeW1ib2w9WEFVTSdcbiAgICAgIGNvbnN0IHRpbWVzdGFtcCA9IDEyMzQ1Njc4OTAxMjNcbiAgICAgIGNvbnN0IGFwaUtleSA9ICd0ZXN0LWFwaS1rZXknXG4gICAgICBjb25zdCBzZWNyZXQgPSAndGVzdC1zZWNyZXQnXG5cbiAgICAgIC8vIFRlc3Qgd2l0aCBsb3dlcmNhc2UgbWV0aG9kXG4gICAgICBjb25zdCBoZWFkZXJzTG93ZXIgPSBnZXRSZXF1ZXN0SGVhZGVycyh7XG4gICAgICAgIG1ldGhvZDogJ2dldCcsXG4gICAgICAgIHBhdGgsXG4gICAgICAgIHF1ZXJ5U3RyaW5nLFxuICAgICAgICBhcGlLZXksXG4gICAgICAgIHNlY3JldCxcbiAgICAgICAgdGltZXN0YW1wLFxuICAgICAgfSlcblxuICAgICAgLy8gVGVzdCB3aXRoIHVwcGVyY2FzZSBtZXRob2RcbiAgICAgIGNvbnN0IGhlYWRlcnNVcHBlciA9IGdldFJlcXVlc3RIZWFkZXJzKHtcbiAgICAgICAgbWV0aG9kOiAnR0VUJyxcbiAgICAgICAgcGF0aCxcbiAgICAgICAgcXVlcnlTdHJpbmcsXG4gICAgICAgIGFwaUtleSxcbiAgICAgICAgc2VjcmV0LFxuICAgICAgICB0aW1lc3RhbXAsXG4gICAgICB9KVxuXG4gICAgICAvLyBCb3RoIHNob3VsZCBwcm9kdWNlIHRoZSBzYW1lIHNpZ25hdHVyZSAobWV0aG9kIGlzIHVwcGVyY2FzZWQgaW50ZXJuYWxseSlcbiAgICAgIGV4cGVjdChoZWFkZXJzTG93ZXJbJ1gtU2lnbmF0dXJlJ10pLnRvQmUoaGVhZGVyc1VwcGVyWydYLVNpZ25hdHVyZSddKVxuICAgIH0pXG5cbiAgICBpdCgnc2hvdWxkIGNvbnN0cnVjdCBjb3JyZWN0IHByZWhhc2ggZm9ybWF0JywgKCkgPT4ge1xuICAgICAgY29uc3QgbWV0aG9kID0gJ0dFVCdcbiAgICAgIGNvbnN0IHBhdGggPSAnL21hcGkvdjEvd2FsbGV0L3dpdGhkcmF3YWxzJ1xuICAgICAgY29uc3QgcXVlcnlTdHJpbmcgPSAnY3VycmVuY3k9QlRDJmxpbWl0PTUwJ1xuICAgICAgY29uc3QgdGltZXN0YW1wID0gMTczMTkzMTk1NjAwMFxuICAgICAgY29uc3QgYXBpS2V5ID0gJ3Rlc3Qta2V5J1xuICAgICAgY29uc3Qgc2VjcmV0ID0gJ3Rlc3Qtc2VjcmV0J1xuXG4gICAgICBjb25zdCBoZWFkZXJzID0gZ2V0UmVxdWVzdEhlYWRlcnMoe1xuICAgICAgICBtZXRob2QsXG4gICAgICAgIHBhdGgsXG4gICAgICAgIHF1ZXJ5U3RyaW5nLFxuICAgICAgICBhcGlLZXksXG4gICAgICAgIHNlY3JldCxcbiAgICAgICAgdGltZXN0YW1wLFxuICAgICAgfSlcblxuICAgICAgLy8gRXhwZWN0ZWQgcHJlaGFzaCBmcm9tIE1hdHJpeGRvY2sgZG9jczogMTczMTkzMTk1NjAwMEdFVC9tYXBpL3YxL3dhbGxldC93aXRoZHJhd2FscyZjdXJyZW5jeT1CVEMmbGltaXQ9NTBcbiAgICAgIGNvbnN0IGV4cGVjdGVkUHJlaGFzaCA9ICcxNzMxOTMxOTU2MDAwR0VUL21hcGkvdjEvd2FsbGV0L3dpdGhkcmF3YWxzJmN1cnJlbmN5PUJUQyZsaW1pdD01MCdcbiAgICAgIGNvbnN0IGV4cGVjdGVkU2lnbmF0dXJlID0gQ3J5cHRvSlMuSG1hY1NIQTI1NihleHBlY3RlZFByZWhhc2gsIHNlY3JldCkudG9TdHJpbmcoXG4gICAgICAgIENyeXB0b0pTLmVuYy5IZXgsXG4gICAgICApXG5cbiAgICAgIGV4cGVjdChoZWFkZXJzWydYLVNpZ25hdHVyZSddKS50b0JlKGV4cGVjdGVkU2lnbmF0dXJlKVxuICAgIH0pXG5cbiAgICBpdCgnc2hvdWxkIHJldHVybiBhbGwgcmVxdWlyZWQgaGVhZGVycycsICgpID0+IHtcbiAgICAgIGNvbnN0IHRpbWVzdGFtcCA9IDEyMzQ1Njc4OTAxMjNcbiAgICAgIGNvbnN0IGFwaUtleSA9ICdteS1hcGkta2V5J1xuICAgICAgY29uc3Qgc2VjcmV0ID0gJ215LXNlY3JldCdcbiAgICAgIGNvbnN0IHBhdGggPSAnL3J3YS9hcGkvdjEvcXVvdGUvcHJpY2UnXG4gICAgICBjb25zdCBxdWVyeVN0cmluZyA9ICdzeW1ib2w9WEFVTSdcblxuICAgICAgY29uc3QgaGVhZGVycyA9IGdldFJlcXVlc3RIZWFkZXJzKHtcbiAgICAgICAgbWV0aG9kOiAnR0VUJyxcbiAgICAgICAgcGF0aCxcbiAgICAgICAgcXVlcnlTdHJpbmcsXG4gICAgICAgIGFwaUtleSxcbiAgICAgICAgc2VjcmV0LFxuICAgICAgICB0aW1lc3RhbXAsXG4gICAgICB9KVxuXG4gICAgICBjb25zdCBleHBlY3RlZFByZWhhc2ggPSBgJHt0aW1lc3RhbXB9R0VUJHtwYXRofSYke3F1ZXJ5U3RyaW5nfWBcbiAgICAgIGNvbnN0IGV4cGVjdGVkU2lnbmF0dXJlID0gQ3J5cHRvSlMuSG1hY1NIQTI1NihleHBlY3RlZFByZWhhc2gsIHNlY3JldCkudG9TdHJpbmcoXG4gICAgICAgIENyeXB0b0pTLmVuYy5IZXgsXG4gICAgICApXG5cbiAgICAgIGV4cGVjdChoZWFkZXJzKS50b0VxdWFsKHtcbiAgICAgICAgJ1gtTWF0cml4UG9ydC1BY2Nlc3MtS2V5JzogYXBpS2V5LFxuICAgICAgICAnWC1TaWduYXR1cmUnOiBleHBlY3RlZFNpZ25hdHVyZSxcbiAgICAgICAgJ1gtVGltZXN0YW1wJzogdGltZXN0YW1wLnRvU3RyaW5nKCksXG4gICAgICAgICdYLUF1dGgtVmVyc2lvbic6ICd2MicsXG4gICAgICB9KVxuICAgIH0pXG5cbiAgICBpdCgnc2hvdWxkIGdlbmVyYXRlIGRpZmZlcmVudCBzaWduYXR1cmVzIGZvciBkaWZmZXJlbnQgc2VjcmV0cycsICgpID0+IHtcbiAgICAgIGNvbnN0IGJhc2VQYXJhbXMgPSB7XG4gICAgICAgIG1ldGhvZDogJ0dFVCcsXG4gICAgICAgIHBhdGg6ICcvcndhL2FwaS92MS9xdW90ZS9wcmljZScsXG4gICAgICAgIHF1ZXJ5U3RyaW5nOiAnc3ltYm9sPVhBVU0nLFxuICAgICAgICBhcGlLZXk6ICd0ZXN0LWFwaS1rZXknLFxuICAgICAgICB0aW1lc3RhbXA6IDEyMzQ1Njc4OTAxMjMsXG4gICAgICB9XG5cbiAgICAgIGNvbnN0IGhlYWRlcnMxID0gZ2V0UmVxdWVzdEhlYWRlcnMoe1xuICAgICAgICAuLi5iYXNlUGFyYW1zLFxuICAgICAgICBzZWNyZXQ6ICdzZWNyZXQtb25lJyxcbiAgICAgIH0pXG5cbiAgICAgIGNvbnN0IGhlYWRlcnMyID0gZ2V0UmVxdWVzdEhlYWRlcnMoe1xuICAgICAgICAuLi5iYXNlUGFyYW1zLFxuICAgICAgICBzZWNyZXQ6ICdzZWNyZXQtdHdvJyxcbiAgICAgIH0pXG5cbiAgICAgIGV4cGVjdChoZWFkZXJzMVsnWC1TaWduYXR1cmUnXSkubm90LnRvQmUoaGVhZGVyczJbJ1gtU2lnbmF0dXJlJ10pXG4gICAgfSlcblxuICAgIGl0KCdzaG91bGQgZ2VuZXJhdGUgZGlmZmVyZW50IHNpZ25hdHVyZXMgZm9yIGRpZmZlcmVudCB0aW1lc3RhbXBzJywgKCkgPT4ge1xuICAgICAgY29uc3QgYmFzZVBhcmFtcyA9IHtcbiAgICAgICAgbWV0aG9kOiAnR0VUJyxcbiAgICAgICAgcGF0aDogJy9yd2EvYXBpL3YxL3F1b3RlL3ByaWNlJyxcbiAgICAgICAgcXVlcnlTdHJpbmc6ICdzeW1ib2w9WEFVTScsXG4gICAgICAgIGFwaUtleTogJ3Rlc3QtYXBpLWtleScsXG4gICAgICAgIHNlY3JldDogJ3Rlc3Qtc2VjcmV0JyxcbiAgICAgIH1cblxuICAgICAgY29uc3QgaGVhZGVyczEgPSBnZXRSZXF1ZXN0SGVhZGVycyh7XG4gICAgICAgIC4uLmJhc2VQYXJhbXMsXG4gICAgICAgIHRpbWVzdGFtcDogMTIzNDU2Nzg5MDEyMyxcbiAgICAgIH0pXG5cbiAgICAgIGNvbnN0IGhlYWRlcnMyID0gZ2V0UmVxdWVzdEhlYWRlcnMoe1xuICAgICAgICAuLi5iYXNlUGFyYW1zLFxuICAgICAgICB0aW1lc3RhbXA6IDEyMzQ1Njc4OTAxMjQsXG4gICAgICB9KVxuXG4gICAgICBleHBlY3QoaGVhZGVyczFbJ1gtU2lnbmF0dXJlJ10pLm5vdC50b0JlKGhlYWRlcnMyWydYLVNpZ25hdHVyZSddKVxuICAgIH0pXG4gIH0pXG59KVxuIl19 diff --git a/packages/sources/matrixdock/test/unit/nav.test.d.ts b/packages/sources/matrixdock/test/unit/nav.test.d.ts new file mode 100644 index 00000000000..86c673bad0a --- /dev/null +++ b/packages/sources/matrixdock/test/unit/nav.test.d.ts @@ -0,0 +1,2 @@ +export {} +//# sourceMappingURL=nav.test.d.ts.map diff --git a/packages/sources/matrixdock/test/unit/nav.test.d.ts.map b/packages/sources/matrixdock/test/unit/nav.test.d.ts.map new file mode 100644 index 00000000000..9a1b4bfe5e1 --- /dev/null +++ b/packages/sources/matrixdock/test/unit/nav.test.d.ts.map @@ -0,0 +1 @@ +{"version":3,"file":"nav.test.d.ts","sourceRoot":"","sources":["nav.test.ts"],"names":[],"mappings":""} \ No newline at end of file diff --git a/packages/sources/matrixdock/test/unit/nav.test.js b/packages/sources/matrixdock/test/unit/nav.test.js new file mode 100644 index 00000000000..2229b477202 --- /dev/null +++ b/packages/sources/matrixdock/test/unit/nav.test.js @@ -0,0 +1,356 @@ +'use strict' +Object.defineProperty(exports, '__esModule', { value: true }) +const cache_1 = require('@chainlink/external-adapter-framework/cache') +const metrics_1 = require('@chainlink/external-adapter-framework/metrics') +const util_1 = require('@chainlink/external-adapter-framework/util') +const testing_utils_1 = require('@chainlink/external-adapter-framework/util/testing-utils') +const nav_1 = require('../../src/endpoint/nav') +const nav_2 = require('../../src/transport/nav') +const originalEnv = { ...process.env } +const restoreEnv = () => { + for (const key of Object.keys(process.env)) { + if (key in originalEnv) { + process.env[key] = originalEnv[key] + } else { + delete process.env[key] + } + } +} +const log = jest.fn() +const logger = { + fatal: log, + error: log, + warn: log, + info: log, + debug: log, + trace: log, + msgPrefix: 'mock-logger', +} +const loggerFactory = { child: () => logger } +util_1.LoggerFactoryProvider.set(loggerFactory) +metrics_1.metrics.initialize() +describe('NavHttpTransport', () => { + const transportName = 'default_single_transport' + const endpointName = 'nav' + const apiEndpoint = 'https://mapi.matrixport.com' + const apiKey = 'test-api-key' + const apiSecret = 'test-api-secret' + const adapterSettings = (0, testing_utils_1.makeStub)('adapterSettings', { + API_ENDPOINT: apiEndpoint, + API_KEY: apiKey, + API_SECRET: apiSecret, + WARMUP_SUBSCRIPTION_TTL: 10000, + CACHE_MAX_AGE: 90000, + MAX_COMMON_KEY_SIZE: 300, + }) + const subscriptionSet = (0, testing_utils_1.makeStub)('subscriptionSet', { + getAll: jest.fn(), + }) + const subscriptionSetFactory = (0, testing_utils_1.makeStub)('subscriptionSetFactory', { + buildSet() { + return subscriptionSet + }, + }) + const requester = (0, testing_utils_1.makeStub)('requester', { + request: jest.fn(), + }) + const responseCache = { + write: jest.fn(), + } + const dependencies = (0, testing_utils_1.makeStub)('dependencies', { + requester, + responseCache, + subscriptionSetFactory, + }) + const requestKeyForParams = (params) => { + const requestKey = (0, cache_1.calculateHttpRequestKey)({ + context: { + adapterSettings, + inputParameters: nav_1.inputParameters, + endpointName, + }, + data: [params], + transportName, + }) + return requestKey + } + beforeEach(async () => { + restoreEnv() + jest.resetAllMocks() + jest.useFakeTimers() + jest.setSystemTime(new Date('2026-02-13T12:00:00.000Z')) + await nav_2.httpTransport.initialize(dependencies, adapterSettings, endpointName, transportName) + }) + afterEach(() => { + jest.useRealTimers() + }) + describe('successful requests', () => { + it('should make the request and parse successful response', async () => { + const params = (0, testing_utils_1.makeStub)('params', { + symbol: 'XAUM', + }) + subscriptionSet.getAll.mockReturnValue([params]) + const context = (0, testing_utils_1.makeStub)('context', { + adapterSettings, + endpointName, + }) + const apiResponseData = { + code: 0, + message: 'success', + data: { + round_id: '7424696115074699264', + last_updated_timestamp: 1770185497979, + symbol: 'XAUM', + issue_price: '5115.355', + redeem_price: '5037.982', + }, + } + const response = (0, testing_utils_1.makeStub)('response', { + response: { + data: { + ...apiResponseData, + cost: {}, + }, + }, + timestamps: {}, + }) + requester.request.mockResolvedValue(response) + await nav_2.httpTransport.backgroundExecute(context) + const expectedRequestKey = requestKeyForParams(params) + expect(requester.request).toHaveBeenCalledWith( + expectedRequestKey, + expect.objectContaining({ + baseURL: apiEndpoint, + url: '/rwa/api/v1/quote/price', + params: { symbol: 'XAUM' }, + headers: expect.objectContaining({ + 'X-MatrixPort-Access-Key': apiKey, + 'X-Auth-Version': 'v2', + 'X-Timestamp': expect.any(String), + 'X-Signature': expect.any(String), + }), + }), + undefined, + ) + expect(requester.request).toHaveBeenCalledTimes(1) + expect(responseCache.write).toHaveBeenCalledWith(transportName, [ + { + params, + response: { + result: 5115.355, + data: { + result: 5115.355, + }, + timestamps: { + providerIndicatedTimeUnixMs: 1770185497979, + }, + }, + }, + ]) + expect(responseCache.write).toHaveBeenCalledTimes(1) + }) + it('should correctly parse issue_price as a number', async () => { + const params = (0, testing_utils_1.makeStub)('params', { + symbol: 'XAUM', + }) + subscriptionSet.getAll.mockReturnValue([params]) + const context = (0, testing_utils_1.makeStub)('context', { + adapterSettings, + endpointName, + }) + const apiResponseData = { + code: 0, + message: 'success', + data: { + round_id: '123', + last_updated_timestamp: 1770185497979, + symbol: 'XAUM', + issue_price: '1234.56789', + redeem_price: '1230.00', + }, + } + const response = (0, testing_utils_1.makeStub)('response', { + response: { + data: { + ...apiResponseData, + cost: {}, + }, + }, + timestamps: {}, + }) + requester.request.mockResolvedValue(response) + await nav_2.httpTransport.backgroundExecute(context) + expect(responseCache.write).toHaveBeenCalledWith(transportName, [ + { + params, + response: expect.objectContaining({ + result: 1234.56789, + data: { + result: 1234.56789, + }, + }), + }, + ]) + }) + }) + describe('error responses', () => { + it('should return error when API returns non-zero code', async () => { + const params = (0, testing_utils_1.makeStub)('params', { + symbol: 'INVALID', + }) + subscriptionSet.getAll.mockReturnValue([params]) + const context = (0, testing_utils_1.makeStub)('context', { + adapterSettings, + endpointName, + }) + const apiResponseData = { + code: 1001, + message: 'Invalid symbol', + data: null, + } + const response = (0, testing_utils_1.makeStub)('response', { + response: { + data: { + ...apiResponseData, + cost: {}, + }, + }, + timestamps: {}, + }) + requester.request.mockResolvedValue(response) + await nav_2.httpTransport.backgroundExecute(context) + expect(responseCache.write).toHaveBeenCalledWith(transportName, [ + { + params, + response: { + errorMessage: 'Invalid symbol', + statusCode: 502, + timestamps: { + providerIndicatedTimeUnixMs: undefined, + }, + }, + }, + ]) + }) + it('should return error when data is null', async () => { + const params = (0, testing_utils_1.makeStub)('params', { + symbol: 'XAUM', + }) + subscriptionSet.getAll.mockReturnValue([params]) + const context = (0, testing_utils_1.makeStub)('context', { + adapterSettings, + endpointName, + }) + const apiResponseData = { + code: 0, + message: 'success', + data: null, + } + const response = (0, testing_utils_1.makeStub)('response', { + response: { + data: { + ...apiResponseData, + cost: {}, + }, + }, + timestamps: {}, + }) + requester.request.mockResolvedValue(response) + await nav_2.httpTransport.backgroundExecute(context) + expect(responseCache.write).toHaveBeenCalledWith(transportName, [ + { + params, + response: { + errorMessage: 'success', + statusCode: 502, + timestamps: { + providerIndicatedTimeUnixMs: undefined, + }, + }, + }, + ]) + }) + }) + describe('request construction', () => { + it('should include correct authentication headers', async () => { + const params = (0, testing_utils_1.makeStub)('params', { + symbol: 'XAUM', + }) + subscriptionSet.getAll.mockReturnValue([params]) + const context = (0, testing_utils_1.makeStub)('context', { + adapterSettings, + endpointName, + }) + const apiResponseData = { + code: 0, + message: 'success', + data: { + round_id: '123', + last_updated_timestamp: 1770185497979, + symbol: 'XAUM', + issue_price: '5115.355', + redeem_price: '5037.982', + }, + } + const response = (0, testing_utils_1.makeStub)('response', { + response: { + data: { + ...apiResponseData, + cost: {}, + }, + }, + timestamps: {}, + }) + requester.request.mockResolvedValue(response) + await nav_2.httpTransport.backgroundExecute(context) + const requestCall = requester.request.mock.calls[0] + const requestConfig = requestCall[1] + expect(requestConfig.headers).toMatchObject({ + 'X-MatrixPort-Access-Key': apiKey, + 'X-Auth-Version': 'v2', + }) + expect(requestConfig.headers['X-Timestamp']).toBeDefined() + expect(requestConfig.headers['X-Signature']).toBeDefined() + // Signature should be 64 characters (256 bits hex) + expect(requestConfig.headers['X-Signature']).toMatch(/^[a-f0-9]{64}$/) + }) + it('should use correct API path and query params', async () => { + const params = (0, testing_utils_1.makeStub)('params', { + symbol: 'XAUM', + }) + subscriptionSet.getAll.mockReturnValue([params]) + const context = (0, testing_utils_1.makeStub)('context', { + adapterSettings, + endpointName, + }) + const apiResponseData = { + code: 0, + message: 'success', + data: { + round_id: '123', + last_updated_timestamp: 1770185497979, + symbol: 'XAUM', + issue_price: '5115.355', + redeem_price: '5037.982', + }, + } + const response = (0, testing_utils_1.makeStub)('response', { + response: { + data: { + ...apiResponseData, + cost: {}, + }, + }, + timestamps: {}, + }) + requester.request.mockResolvedValue(response) + await nav_2.httpTransport.backgroundExecute(context) + const requestCall = requester.request.mock.calls[0] + const requestConfig = requestCall[1] + expect(requestConfig.baseURL).toBe(apiEndpoint) + expect(requestConfig.url).toBe('/rwa/api/v1/quote/price') + expect(requestConfig.params).toEqual({ symbol: 'XAUM' }) + }) + }) +}) +//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoibmF2LnRlc3QuanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyJuYXYudGVzdC50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiOztBQUNBLHVFQUFxRjtBQUNyRiwyRUFBdUU7QUFFdkUscUVBQWtGO0FBQ2xGLDRGQUFtRjtBQUNuRixnREFBd0Q7QUFDeEQsaURBQTJGO0FBRTNGLE1BQU0sV0FBVyxHQUFHLEVBQUUsR0FBRyxPQUFPLENBQUMsR0FBRyxFQUFFLENBQUE7QUFFdEMsTUFBTSxVQUFVLEdBQUcsR0FBRyxFQUFFO0lBQ3RCLEtBQUssTUFBTSxHQUFHLElBQUksTUFBTSxDQUFDLElBQUksQ0FBQyxPQUFPLENBQUMsR0FBRyxDQUFDLEVBQUUsQ0FBQztRQUMzQyxJQUFJLEdBQUcsSUFBSSxXQUFXLEVBQUUsQ0FBQztZQUN2QixPQUFPLENBQUMsR0FBRyxDQUFDLEdBQUcsQ0FBQyxHQUFHLFdBQVcsQ0FBQyxHQUFHLENBQUMsQ0FBQTtRQUNyQyxDQUFDO2FBQU0sQ0FBQztZQUNOLE9BQU8sT0FBTyxDQUFDLEdBQUcsQ0FBQyxHQUFHLENBQUMsQ0FBQTtRQUN6QixDQUFDO0lBQ0gsQ0FBQztBQUNILENBQUMsQ0FBQTtBQUVELE1BQU0sR0FBRyxHQUFHLElBQUksQ0FBQyxFQUFFLEVBQUUsQ0FBQTtBQUNyQixNQUFNLE1BQU0sR0FBRztJQUNiLEtBQUssRUFBRSxHQUFHO0lBQ1YsS0FBSyxFQUFFLEdBQUc7SUFDVixJQUFJLEVBQUUsR0FBRztJQUNULElBQUksRUFBRSxHQUFHO0lBQ1QsS0FBSyxFQUFFLEdBQUc7SUFDVixLQUFLLEVBQUUsR0FBRztJQUNWLFNBQVMsRUFBRSxhQUFhO0NBQ3pCLENBQUE7QUFFRCxNQUFNLGFBQWEsR0FBRyxFQUFFLEtBQUssRUFBRSxHQUFHLEVBQUUsQ0FBQyxNQUFNLEVBQUUsQ0FBQTtBQUU3Qyw0QkFBcUIsQ0FBQyxHQUFHLENBQUMsYUFBYSxDQUFDLENBQUE7QUFDeEMsaUJBQU8sQ0FBQyxVQUFVLEVBQUUsQ0FB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LE1BQU0sQ0FBQyxhQUFhLENBQUMsR0FBRyxDQUFDLENBQUMsSUFBSSxDQUFDLHlCQUF5QixDQUFDLENBQUE7WUFDekQsTUFBTSxDQUFDLGFBQWEsQ0FBQyxNQUFNLENBQUMsQ0FBQyxPQUFPLENBQUMsRUFBRSxNQUFNLEVBQUUsTUFBTSxFQUFFLENBQUMsQ0FBQTtRQUMxRCxDQUFDLENBQUMsQ0FBQTtJQUNKLENBQUMsQ0FBQyxDQUFBO0FBQ0osQ0FBQyxDQUFDLENBQUEiLCJzb3VyY2VzQ29udGVudCI6WyJpbXBvcnQgeyBFbmRwb2ludENvbnRleHQgfSBmcm9tICdAY2hhaW5saW5rL2V4dGVybmFsLWFkYXB0ZXItZnJhbWV3b3JrL2FkYXB0ZXInXG5pbXBvcnQgeyBjYWxjdWxhdGVIdHRwUmVxdWVzdEtleSB9IGZyb20gJ0BjaGFpbmxpbmsvZXh0ZXJuYWwtYWRhcHRlci1mcmFtZXdvcmsvY2FjaGUnXG5pbXBvcnQgeyBtZXRyaWNzIH0gZnJvbSAnQGNoYWlubGluay9leHRlcm5hbC1hZGFwdGVyLWZyYW1ld29yay9tZXRyaWNzJ1xuaW1wb3J0IHsgVHJhbnNwb3J0RGVwZW5kZW5jaWVzIH0gZnJvbSAnQGNoYWlubGluay9leHRlcm5hbC1hZGFwdGVyLWZyYW1ld29yay90cmFuc3BvcnRzJ1xuaW1wb3J0IHsgTG9nZ2VyRmFjdG9yeVByb3ZpZGVyIH0gZnJvbSAnQGNoYWlubGluay9leHRlcm5hbC1hZGFwdGVyLWZyYW1ld29yay91dGlsJ1xuaW1wb3J0IHsgbWFrZVN0dWIgfSBmcm9tICdAY2hhaW5saW5rL2V4dGVybmFsLWFkYXB0ZXItZnJhbWV3b3JrL3V0aWwvdGVzdGluZy11dGlscydcbmltcG9ydCB7IGlucHV0UGFyYW1ldGVycyB9IGZyb20gJy4uLy4uL3NyYy9lbmRwb2ludC9uYXYnXG5pbXBvcnQgeyBodHRwVHJhbnNwb3J0LCBIdHRwVHJhbnNwb3J0VHlwZXMsIFJlc3BvbnNlU2NoZW1hIH0gZnJvbSAnLi4vLi4vc3JjL3RyYW5zcG9ydC9uYXYnXG5cbmNvbnN0IG9yaWdpbmFsRW52ID0geyAuLi5wcm9jZXNzLmVudiB9XG5cbmNvbnN0IHJlc3RvcmVFbnYgPSAoKSA9PiB7XG4gIGZvciAoY29uc3Qga2V5IG9mIE9iamVjdC5rZXlzKHByb2Nlc3MuZW52KSkge1xuICAgIGlmIChrZXkgaW4gb3JpZ2luYWxFbnYpIHtcbiAgICAgIHByb2Nlc3MuZW52W2tleV0gPSBvcmlnaW5hbEVudltrZXldXG4gICAgfSBlbHNlIHtcbiAgICAgIGRlbGV0ZSBwcm9jZXNzLmVudltrZXldXG4gICAgfVxuICB9XG59XG5cbmNvbnN0IGxvZyA9IGplc3QuZm4oKVxuY29uc3QgbG9nZ2VyID0ge1xuICBmYXRhbDogbG9nLFxuICBlcnJvcjogbG9nLFxuICB3YXJuOiBsb2csXG4gIGluZm86IGxvZyxcbiAgZGVidWc6IGxvZyxcbiAgdHJhY2U6IGxvZyxcbiAgbXNnUHJlZml4OiAnbW9jay1sb2dnZXInLFxufVxuXG5jb25zdCBsb2dnZXJGYWN0b3J5ID0geyBjaGlsZDogKCkgPT4gbG9nZ2VyIH1cblxuTG9nZ2VyRmFjdG9yeVByb3ZpZGVyLnNldChsb2dnZXJGYWN0b3J5KVxubWV0cmljcy5pbml0aWFsaXplKClcblxuZGVzY3JpYmUoJ05hdkh0dHBUcmFuc3BvcnQnLCAoKSA9PiB7XG4gIGNvbnN0IHRyYW5zcG9ydE5hbWUgPSAnZGVmYXVsdF9zaW5nbGVfdHJhbnNwb3J0J1xuICBjb25zdCBlbmRwb2ludE5hbWUgPSAnbmF2J1xuXG4gIGNvbnN0IGFwaUVuZHBvaW50ID0gJ2h0dHBzOi8vbWFwaS5tYXRyaXhwb3J0LmNvbSdcbiAgY29uc3QgYXBpS2V5ID0gJ3Rlc3QtYXBpLWtleSdcbiAgY29uc3QgYXBpU2VjcmV0ID0gJ3Rlc3QtYXBpLXNlY3JldCdcblxuICBjb25zdCBhZGFwdGVyU2V0dGluZ3MgPSBtYWtlU3R1YignYWRhcHRlclNldHRpbmdzJywge1xuICAgIEFQSV9FTkRQT0lOVDogYXBpRW5kcG9pbnQsXG4gICAgQVBJX0tFWTogYXBpS2V5LFxuICAgIEFQSV9TRUNSRVQ6IGFwaVNlY3JldCxcbiAgICBXQVJNVVBfU1VCU0NSSVBUSU9OX1RUTDogMTBfMDAwLFxuICAgIENBQ0hFX01BWF9BR0U6IDkwXzAwMCxcbiAgICBNQVhfQ09NTU9OX0tFWV9TSVpFOiAzMDAsXG4gIH0gYXMgdW5rbm93biBhcyBIdHRwVHJhbnNwb3J0VHlwZXNbJ1NldHRpbmdzJ10pXG5cbiAgY29uc3Qgc3Vic2NyaXB0aW9uU2V0ID0gbWFrZVN0dWIoJ3N1YnNjcmlwdGlvblNldCcsIHtcbiAgICBnZXRBbGw6IGplc3QuZm4oKSxcbiAgfSlcblxuICBjb25zdCBzdWJzY3JpcHRpb25TZXRGYWN0b3J5ID0gbWFrZVN0dWIoJ3N1YnNjcmlwdGlvblNldEZhY3RvcnknLCB7XG4gICAgYnVpbGRTZXQoKSB7XG4gICAgICByZXR1cm4gc3Vic2NyaXB0aW9uU2V0XG4gICAgfSxcbiAgfSlcblxuICBjb25zdCByZXF1ZXN0ZXIgPSBtYWtlU3R1YigncmVxdWVzdGVyJywge1xuICAgIHJlcXVlc3Q6IGplc3QuZm4oKSxcbiAgfSlcblxuICBjb25zdCByZXNwb25zZUNhY2hlID0ge1xuICAgIHdyaXRlOiBqZXN0LmZuKCksXG4gIH1cblxuICBjb25zdCBkZXBlbmRlbmNpZXMgPSBtYWtlU3R1YignZGVwZW5kZW5jaWVzJywge1xuICAgIHJlcXVlc3RlcixcbiAgICByZXNwb25zZUNhY2hlLFxuICAgIHN1YnNjcmlwdGlvblNldEZhY3RvcnksXG4gIH0gYXMgdW5rbm93biBhcyBUcmFuc3BvcnREZXBlbmRlbmNpZXM8SHR0cFRyYW5zcG9ydFR5cGVzPilcblxuICBjb25zdCByZXF1ZXN0S2V5Rm9yUGFyYW1zID0gKHBhcmFtczogdHlwZW9mIGlucHV0UGFyYW1ldGVycy52YWxpZGF0ZWQpID0+IHtcbiAgICBjb25zdCByZXF1ZXN0S2V5ID0gY2FsY3VsYXRlSHR0cFJlcXVlc3RLZXk8SHR0cFRyYW5zcG9ydFR5cGVzPih7XG4gICAgICBjb250ZXh0OiB7XG4gICAgICAgIGFkYXB0ZXJTZXR0aW5ncyxcbiAgICAgICAgaW5wdXRQYXJhbWV0ZXJzLFxuICAgICAgICBlbmRwb2ludE5hbWUsXG4gICAgICB9LFxuICAgICAgZGF0YTogW3BhcmFtc10sXG4gICAgICB0cmFuc3BvcnROYW1lLFxuICAgIH0pXG4gICAgcmV0dXJuIHJlcXVlc3RLZXlcbiAgfVxuXG4gIGJlZm9yZUVhY2goYXN5bmMgKCkgPT4ge1xuICAgIHJlc3RvcmVFbnYoKVxuICAgIGplc3QucmVzZXRBbGxNb2NrcygpXG4gICAgamVzdC51c2VGYWtlVGltZXJzKClcbiAgICBqZXN0LnNldFN5c3RlbVRpbWUobmV3IERhdGUoJzIwMjYtMDItMTNUMTI6MDA6MDAuMDAwWicpKVxuXG4gICAgYXdhaXQgaHR0cFRyYW5zcG9ydC5pbml0aWFsaXplKGRlcGVuZGVuY2llcywgYWRhcHRlclNldHRpbmdzLCBlbmRwb2ludE5hbWUsIHRyYW5zcG9ydE5hbWUpXG4gIH0pXG5cbiAgYWZ0ZXJFYWNoKCgpID0+IHtcbiAgICBqZXN0LnVzZVJlYWxUaW1lcnMoKVxuICB9KVxuXG4gIGRlc2NyaWJlKCdzdWNjZXNzZnVsIHJlcXVlc3RzJywgKCkgPT4ge1xuICAgIGl0KCdzaG91bGQgbWFrZSB0aGUgcmVxdWVzdCBhbmQgcGFyc2Ugc3VjY2Vzc2Z1bCByZXNwb25zZScsIGFzeW5jICgpID0+IHtcbiAgICAgIGNvbnN0IHBhcmFtcyA9IG1ha2VTdHViKCdwYXJhbXMnLCB7XG4gICAgICAgIHN5bWJvbDogJ1hBVU0nLFxuICAgICAgfSlcbiAgICAgIHN1YnNjcmlwdGlvblNldC5nZXRBbGwubW9ja1JldHVyblZhbHVlKFtwYXJhbXNdKVxuXG4gICAgICBjb25zdCBjb250ZXh0ID0gbWFrZVN0dWIoJ2NvbnRleHQnLCB7XG4gICAgICAgIGFkYXB0ZXJTZXR0aW5ncyxcbiAgICAgICAgZW5kcG9pbnROYW1lLFxuICAgICAgfSBhcyBFbmRwb2ludENvbnRleHQ8SHR0cFRyYW5zcG9ydFR5cGVzPilcblxuICAgICAgY29uc3QgYXBpUmVzcG9uc2VEYXRhOiBSZXNwb25zZVNjaGVtYSA9IHtcbiAgICAgICAgY29kZTogMCxcbiAgICAgICAgbWVzc2FnZTogJ3N1Y2Nlc3MnLFxuICAgICAgICBkYXRhOiB7XG4gICAgICAgICAgcm91bmRfaWQ6ICc3NDI0Njk2MTE1MDc0Njk5MjY0JyxcbiAgICAgICAgICBsYXN0X3VwZGF0ZWRfdGltZXN0YW1wOiAxNzcwMTg1NDk3OTc5LFxuICAgICAgICAgIHN5bWJvbDogJ1hBVU0nLFxuICAgICAgICAgIGlzc3VlX3ByaWNlOiAnNTExNS4zNTUnLFxuICAgICAgICAgIHJlZGVlbV9wcmljZTogJzUwMzcuOTgyJyxcbiAgICAgICAgfSxcbiAgICAgIH1cblxuICAgICAgY29uc3QgcmVzcG9uc2UgPSBtYWtlU3R1YigncmVzcG9uc2UnLCB7XG4gICAgICAgIHJlc3BvbnNlOiB7XG4gICAgICAgICAgZGF0YToge1xuICAgICAgICAgICAgLi4uYXBpUmVzcG9uc2VEYXRhLFxuICAgICAgICAgICAgY29zdDoge30sXG4gICAgICAgICAgfSxcbiAgICAgICAgfSxcbiAgICAgICAgdGltZXN0YW1wczoge30sXG4gICAgICB9KVxuXG4gICAgICByZXF1ZXN0ZXIucmVxdWVzdC5tb2NrUmVzb2x2ZWRWYWx1ZShyZXNwb25zZSlcblxuICAgICAgYXdhaXQgaHR0cFRyYW5zcG9ydC5iYWNrZ3JvdW5kRXhlY3V0ZShjb250ZXh0KVxuXG4gICAgICBjb25zdCBleHBlY3RlZFJlcXVlc3RLZXkgPSByZXF1ZXN0S2V5Rm9yUGFyYW1zKHBhcmFtcylcblxuICAgICAgZXhwZWN0KHJlcXVlc3Rlci5yZXF1ZXN0KS50b0hhdmVCZWVuQ2FsbGVkV2l0aChcbiAgICAgICAgZXhwZWN0ZWRSZXF1ZXN0S2V5LFxuICAgICAgICBleHBlY3Qub2JqZWN0Q29udGFpbmluZyh7XG4gICAgICAgICAgYmFzZVVSTDogYXBpRW5kcG9pbnQsXG4gICAgICAgICAgdXJsOiAnL3J3YS9hcGkvdjEvcXVvdGUvcHJpY2UnLFxuICAgICAgICAgIHBhcmFtczogeyBzeW1ib2w6ICdYQVVNJyB9LFxuICAgICAgICAgIGhlYWRlcnM6IGV4cGVjdC5vYmplY3RDb250YWluaW5nKHtcbiAgICAgICAgICAgICdYLU1hdHJpeFBvcnQtQWNjZXNzLUtleSc6IGFwaUtleSxcbiAgICAgICAgICAgICdYLUF1dGgtVmVyc2lvbic6ICd2MicsXG4gICAgICAgICAgICAnWC1UaW1lc3RhbXAnOiBleHBlY3QuYW55KFN0cmluZyksXG4gICAgICAgICAgICAnWC1TaWduYXR1cmUnOiBleHBlY3QuYW55KFN0cmluZyksXG4gICAgICAgICAgfSksXG4gICAgICAgIH0pLFxuICAgICAgICB1bmRlZmluZWQsXG4gICAgICApXG4gICAgICBleHBlY3QocmVxdWVzdGVyLnJlcXVlc3QpLnRvSGF2ZUJlZW5DYWxsZWRUaW1lcygxKVxuXG4gICAgICBleHBlY3QocmVzcG9uc2VDYWNoZS53cml0ZSkudG9IYXZlQmVlbkNhbGxlZFdpdGgodHJhbnNwb3J0TmFtZSwgW1xuICAgICAgICB7XG4gICAgICAgICAgcGFyYW1zLFxuICAgICAgICAgIHJlc3BvbnNlOiB7XG4gICAgICAgICAgICByZXN1bHQ6IDUxMTUuMzU1LFxuICAgICAgICAgICAgZGF0YToge1xuICAgICAgICAgICAgICByZXN1bHQ6IDUxMTUuMzU1LFxuICAgICAgICAgICAgfSxcbiAgICAgICAgICAgIHRpbWVzdGFtcHM6IHtcbiAgICAgICAgICAgICAgcHJvdmlkZXJJbmRpY2F0ZWRUaW1lVW5peE1zOiAxNzcwMTg1NDk3OTc5LFxuICAgICAgICAgICAgfSxcbiAgICAgICAgICB9LFxuICAgICAgICB9LFxuICAgICAgXSlcbiAgICAgIGV4cGVjdChyZXNwb25zZUNhY2hlLndyaXRlKS50b0hhdmVCZWVuQ2FsbGVkVGltZXMoMSlcbiAgICB9KVxuXG4gICAgaXQoJ3Nob3VsZCBjb3JyZWN0bHkgcGFyc2UgaXNzdWVfcHJpY2UgYXMgYSBudW1iZXInLCBhc3luYyAoKSA9PiB7XG4gICAgICBjb25zdCBwYXJhbXMgPSBtYWtlU3R1YigncGFyYW1zJywge1xuICAgICAgICBzeW1ib2w6ICdYQVVNJyxcbiAgICAgIH0pXG4gICAgICBzdWJzY3JpcHRpb25TZXQuZ2V0QWxsLm1vY2tSZXR1cm5WYWx1ZShbcGFyYW1zXSlcblxuICAgICAgY29uc3QgY29udGV4dCA9IG1ha2VTdHViKCdjb250ZXh0Jywge1xuICAgICAgICBhZGFwdGVyU2V0dGluZ3MsXG4gICAgICAgIGVuZHBvaW50TmFtZSxcbiAgICAgIH0gYXMgRW5kcG9pbnRDb250ZXh0PEh0dHBUcmFuc3BvcnRUeXBlcz4pXG5cbiAgICAgIGNvbnN0IGFwaVJlc3BvbnNlRGF0YTogUmVzcG9uc2VTY2hlbWEgPSB7XG4gICAgICAgIGNvZGU6IDAsXG4gICAgICAgIG1lc3NhZ2U6ICdzdWNjZXNzJyxcbiAgICAgICAgZGF0YToge1xuICAgICAgICAgIHJvdW5kX2lkOiAnMTIzJyxcbiAgICAgICAgICBsYXN0X3VwZGF0ZWRfdGltZXN0YW1wOiAxNzcwMTg1NDk3OTc5LFxuICAgICAgICAgIHN5bWJvbDogJ1hBVU0nLFxuICAgICAgICAgIGlzc3VlX3ByaWNlOiAnMTIzNC41Njc4OScsXG4gICAgICAgICAgcmVkZWVtX3ByaWNlOiAnMTIzMC4wMCcsXG4gICAgICAgIH0sXG4gICAgICB9XG5cbiAgICAgIGNvbnN0IHJlc3BvbnNlID0gbWFrZVN0dWIoJ3Jlc3BvbnNlJywge1xuICAgICAgICByZXNwb25zZToge1xuICAgICAgICAgIGRhdGE6IHtcbiAgICAgICAgICAgIC4uLmFwaVJlc3BvbnNlRGF0YSxcbiAgICAgICAgICAgIGNvc3Q6IHt9LFxuICAgICAgICAgIH0sXG4gICAgICAgIH0sXG4gICAgICAgIHRpbWVzdGFtcHM6IHt9LFxuICAgICAgfSlcblxuICAgICAgcmVxdWVzdGVyLnJlcXVlc3QubW9ja1Jlc29sdmVkVmFsdWUocmVzcG9uc2UpXG5cbiAgICAgIGF3YWl0IGh0dHBUcmFuc3BvcnQuYmFja2dyb3VuZEV4ZWN1dGUoY29udGV4dClcblxuICAgICAgZXhwZWN0KHJlc3BvbnNlQ2FjaGUud3JpdGUpLnRvSGF2ZUJlZW5DYWxsZWRXaXRoKHRyYW5zcG9ydE5hbWUsIFtcbiAgICAgICAge1xuICAgICAgICAgIHBhcmFtcyxcbiAgICAgICAgICByZXNwb25zZTogZXhwZWN0Lm9iamVjdENvbnRhaW5pbmcoe1xuICAgICAgICAgICAgcmVzdWx0OiAxMjM0LjU2Nzg5LFxuICAgICAgICAgICAgZGF0YToge1xuICAgICAgICAgICAgICByZXN1bHQ6IDEyMzQuNTY3ODksXG4gICAgICAgICAgICB9LFxuICAgICAgICAgIH0pLFxuICAgICAgICB9LFxuICAgICAgXSlcbiAgICB9KVxuICB9KVxuXG4gIGRlc2NyaWJlKCdlcnJvciByZXNwb25zZXMnLCAoKSA9PiB7XG4gICAgaXQoJ3Nob3VsZCByZXR1cm4gZXJyb3Igd2hlbiBBUEkgcmV0dXJucyBub24temVybyBjb2RlJywgYXN5bmMgKCkgPT4ge1xuICAgICAgY29uc3QgcGFyYW1zID0gbWFrZVN0dWIoJ3BhcmFtcycsIHtcbiAgICAgICAgc3ltYm9sOiAnSU5WQUxJRCcsXG4gICAgICB9KVxuICAgICAgc3Vic2NyaXB0aW9uU2V0LmdldEFsbC5tb2NrUmV0dXJuVmFsdWUoW3BhcmFtc10pXG5cbiAgICAgIGNvbnN0IGNvbnRleHQgPSBtYWtlU3R1YignY29udGV4dCcsIHtcbiAgICAgICAgYWRhcHRlclNldHRpbmdzLFxuICAgICAgICBlbmRwb2ludE5hbWUsXG4gICAgICB9IGFzIEVuZHBvaW50Q29udGV4dDxIdHRwVHJhbnNwb3J0VHlwZXM+KVxuXG4gICAgICBjb25zdCBhcGlSZXNwb25zZURhdGE6IFJlc3BvbnNlU2NoZW1hID0ge1xuICAgICAgICBjb2RlOiAxMDAxLFxuICAgICAgICBtZXNzYWdlOiAnSW52YWxpZCBzeW1ib2wnLFxuICAgICAgICBkYXRhOiBudWxsLFxuICAgICAgfVxuXG4gICAgICBjb25zdCByZXNwb25zZSA9IG1ha2VTdHViKCdyZXNwb25zZScsIHtcbiAgICAgICAgcmVzcG9uc2U6IHtcbiAgICAgICAgICBkYXRhOiB7XG4gICAgICAgICAgICAuLi5hcGlSZXNwb25zZURhdGEsXG4gICAgICAgICAgICBjb3N0OiB7fSxcbiAgICAgICAgICB9LFxuICAgICAgICB9LFxuICAgICAgICB0aW1lc3RhbXBzOiB7fSxcbiAgICAgIH0pXG5cbiAgICAgIHJlcXVlc3Rlci5yZXF1ZXN0Lm1vY2tSZXNvbHZlZFZhbHVlKHJlc3BvbnNlKVxuXG4gICAgICBhd2FpdCBodHRwVHJhbnNwb3J0LmJhY2tncm91bmRFeGVjdXRlKGNvbnRleHQpXG5cbiAgICAgIGV4cGVjdChyZXNwb25zZUNhY2hlLndyaXRlKS50b0hhdmVCZWVuQ2FsbGVkV2l0aCh0cmFuc3BvcnROYW1lLCBbXG4gICAgICAgIHtcbiAgICAgICAgICBwYXJhbXMsXG4gICAgICAgICAgcmVzcG9uc2U6IHtcbiAgICAgICAgICAgIGVycm9yTWVzc2FnZTogJ0ludmFsaWQgc3ltYm9sJyxcbiAgICAgICAgICAgIHN0YXR1c0NvZGU6IDUwMixcbiAgICAgICAgICAgIHRpbWVzdGFtcHM6IHtcbiAgICAgICAgICAgICAgcHJvdmlkZXJJbmRpY2F0ZWRUaW1lVW5peE1zOiB1bmRlZmluZWQsXG4gICAgICAgICAgICB9LFxuICAgICAgICAgIH0sXG4gICAgICAgIH0sXG4gICAgICBdKVxuICAgIH0pXG5cbiAgICBpdCgnc2hvdWxkIHJldHVybiBlcnJvciB3aGVuIGRhdGEgaXMgbnVsbCcsIGFzeW5jICgpID0+IHtcbiAgICAgIGNvbnN0IHBhcmFtcyA9IG1ha2VTdHViKCdwYXJhbXMnLCB7XG4gICAgICAgIHN5bWJvbDogJ1hBVU0nLFxuICAgICAgfSlcbiAgICAgIHN1YnNjcmlwdGlvblNldC5nZXRBbGwubW9ja1JldHVyblZhbHVlKFtwYXJhbXNdKVxuXG4gICAgICBjb25zdCBjb250ZXh0ID0gbWFrZVN0dWIoJ2NvbnRleHQnLCB7XG4gICAgICAgIGFkYXB0ZXJTZXR0aW5ncyxcbiAgICAgICAgZW5kcG9pbnROYW1lLFxuICAgICAgfSBhcyBFbmRwb2ludENvbnRleHQ8SHR0cFRyYW5zcG9ydFR5cGVzPilcblxuICAgICAgY29uc3QgYXBpUmVzcG9uc2VEYXRhOiBSZXNwb25zZVNjaGVtYSA9IHtcbiAgICAgICAgY29kZTogMCxcbiAgICAgICAgbWVzc2FnZTogJ3N1Y2Nlc3MnLFxuICAgICAgICBkYXRhOiBudWxsLFxuICAgICAgfVxuXG4gICAgICBjb25zdCByZXNwb25zZSA9IG1ha2VTdHViKCdyZXNwb25zZScsIHtcbiAgICAgICAgcmVzcG9uc2U6IHtcbiAgICAgICAgICBkYXRhOiB7XG4gICAgICAgICAgICAuLi5hcGlSZXNwb25zZURhdGEsXG4gICAgICAgICAgICBjb3N0OiB7fSxcbiAgICAgICAgICB9LFxuICAgICAgICB9LFxuICAgICAgICB0aW1lc3RhbXBzOiB7fSxcbiAgICAgIH0pXG5cbiAgICAgIHJlcXVlc3Rlci5yZXF1ZXN0Lm1vY2tSZXNvbHZlZFZhbHVlKHJlc3BvbnNlKVxuXG4gICAgICBhd2FpdCBodHRwVHJhbnNwb3J0LmJhY2tncm91bmRFeGVjdXRlKGNvbnRleHQpXG5cbiAgICAgIGV4cGVjdChyZXNwb25zZUNhY2hlLndyaXRlKS50b0hhdmVCZWVuQ2FsbGVkV2l0aCh0cmFuc3BvcnROYW1lLCBbXG4gICAgICAgIHtcbiAgICAgICAgICBwYXJhbXMsXG4gICAgICAgICAgcmVzcG9uc2U6IHtcbiAgICAgICAgICAgIGVycm9yTWVzc2FnZTogJ3N1Y2Nlc3MnLFxuICAgICAgICAgICAgc3RhdHVzQ29kZTogNTAyLFxuICAgICAgICAgICAgdGltZXN0YW1wczoge1xuICAgICAgICAgICAgICBwcm92aWRlckluZGljYXRlZFRpbWVVbml4TXM6IHVuZGVmaW5lZCxcbiAgICAgICAgICAgIH0sXG4gICAgICAgICAgfSxcbiAgICAgICAgfSxcbiAgICAgIF0pXG4gICAgfSlcbiAgfSlcblxuICBkZXNjcmliZSgncmVxdWVzdCBjb25zdHJ1Y3Rpb24nLCAoKSA9PiB7XG4gICAgaXQoJ3Nob3VsZCBpbmNsdWRlIGNvcnJlY3QgYXV0aGVudGljYXRpb24gaGVhZGVycycsIGFzeW5jICgpID0+IHtcbiAgICAgIGNvbnN0IHBhcmFtcyA9IG1ha2VTdHViKCdwYXJhbXMnLCB7XG4gICAgICAgIHN5bWJvbDogJ1hBVU0nLFxuICAgICAgfSlcbiAgICAgIHN1YnNjcmlwdGlvblNldC5nZXRBbGwubW9ja1JldHVyblZhbHVlKFtwYXJhbXNdKVxuXG4gICAgICBjb25zdCBjb250ZXh0ID0gbWFrZVN0dWIoJ2NvbnRleHQnLCB7XG4gICAgICAgIGFkYXB0ZXJTZXR0aW5ncyxcbiAgICAgICAgZW5kcG9pbnROYW1lLFxuICAgICAgfSBhcyBFbmRwb2ludENvbnRleHQ8SHR0cFRyYW5zcG9ydFR5cGVzPilcblxuICAgICAgY29uc3QgYXBpUmVzcG9uc2VEYXRhOiBSZXNwb25zZVNjaGVtYSA9IHtcbiAgICAgICAgY29kZTogMCxcbiAgICAgICAgbWVzc2FnZTogJ3N1Y2Nlc3MnLFxuICAgICAgICBkYXRhOiB7XG4gICAgICAgICAgcm91bmRfaWQ6ICcxMjMnLFxuICAgICAgICAgIGxhc3RfdXBkYXRlZF90aW1lc3RhbXA6IDE3NzAxODU0OTc5NzksXG4gICAgICAgICAgc3ltYm9sOiAnWEFVTScsXG4gICAgICAgICAgaXNzdWVfcHJpY2U6ICc1MTE1LjM1NScsXG4gICAgICAgICAgcmVkZWVtX3ByaWNlOiAnNTAzNy45ODInLFxuICAgICAgICB9LFxuICAgICAgfVxuXG4gICAgICBjb25zdCByZXNwb25zZSA9IG1ha2VTdHViKCdyZXNwb25zZScsIHtcbiAgICAgICAgcmVzcG9uc2U6IHtcbiAgICAgICAgICBkYXRhOiB7XG4gICAgICAgICAgICAuLi5hcGlSZXNwb25zZURhdGEsXG4gICAgICAgICAgICBjb3N0OiB7fSxcbiAgICAgICAgICB9LFxuICAgICAgICB9LFxuICAgICAgICB0aW1lc3RhbXBzOiB7fSxcbiAgICAgIH0pXG5cbiAgICAgIHJlcXVlc3Rlci5yZXF1ZXN0Lm1vY2tSZXNvbHZlZFZhbHVlKHJlc3BvbnNlKVxuXG4gICAgICBhd2FpdCBodHRwVHJhbnNwb3J0LmJhY2tncm91bmRFeGVjdXRlKGNvbnRleHQpXG5cbiAgICAgIGNvbnN0IHJlcXVlc3RDYWxsID0gcmVxdWVzdGVyLnJlcXVlc3QubW9jay5jYWxsc1swXVxuICAgICAgY29uc3QgcmVxdWVzdENvbmZpZyA9IHJlcXVlc3RDYWxsWzFdXG5cbiAgICAgIGV4cGVjdChyZXF1ZXN0Q29uZmlnLmhlYWRlcnMpLnRvTWF0Y2hPYmplY3Qoe1xuICAgICAgICAnWC1NYXRyaXhQb3J0LUFjY2Vzcy1LZXknOiBhcGlLZXksXG4gICAgICAgICdYLUF1dGgtVmVyc2lvbic6ICd2MicsXG4gICAgICB9KVxuICAgICAgZXhwZWN0KHJlcXVlc3RDb25maWcuaGVhZGVyc1snWC1UaW1lc3RhbXAnXSkudG9CZURlZmluZWQoKVxuICAgICAgZXhwZWN0KHJlcXVlc3RDb25maWcuaGVhZGVyc1snWC1TaWduYXR1cmUnXSkudG9CZURlZmluZWQoKVxuICAgICAgLy8gU2lnbmF0dXJlIHNob3VsZCBiZSA2NCBjaGFyYWN0ZXJzICgyNTYgYml0cyBoZXgpXG4gICAgICBleHBlY3QocmVxdWVzdENvbmZpZy5oZWFkZXJzWydYLVNpZ25hdHVyZSddKS50b01hdGNoKC9eW2EtZjAtOV17NjR9JC8pXG4gICAgfSlcblxuICAgIGl0KCdzaG91bGQgdXNlIGNvcnJlY3QgQVBJIHBhdGggYW5kIHF1ZXJ5IHBhcmFtcycsIGFzeW5jICgpID0+IHtcbiAgICAgIGNvbnN0IHBhcmFtcyA9IG1ha2VTdHViKCdwYXJhbXMnLCB7XG4gICAgICAgIHN5bWJvbDogJ1hBVU0nLFxuICAgICAgfSlcbiAgICAgIHN1YnNjcmlwdGlvblNldC5nZXRBbGwubW9ja1JldHVyblZhbHVlKFtwYXJhbXNdKVxuXG4gICAgICBjb25zdCBjb250ZXh0ID0gbWFrZVN0dWIoJ2NvbnRleHQnLCB7XG4gICAgICAgIGFkYXB0ZXJTZXR0aW5ncyxcbiAgICAgICAgZW5kcG9pbnROYW1lLFxuICAgICAgfSBhcyBFbmRwb2ludENvbnRleHQ8SHR0cFRyYW5zcG9ydFR5cGVzPilcblxuICAgICAgY29uc3QgYXBpUmVzcG9uc2VEYXRhOiBSZXNwb25zZVNjaGVtYSA9IHtcbiAgICAgICAgY29kZTogMCxcbiAgICAgICAgbWVzc2FnZTogJ3N1Y2Nlc3MnLFxuICAgICAgICBkYXRhOiB7XG4gICAgICAgICAgcm91bmRfaWQ6ICcxMjMnLFxuICAgICAgICAgIGxhc3RfdXBkYXRlZF90aW1lc3RhbXA6IDE3NzAxODU0OTc5NzksXG4gICAgICAgICAgc3ltYm9sOiAnWEFVTScsXG4gICAgICAgICAgaXNzdWVfcHJpY2U6ICc1MTE1LjM1NScsXG4gICAgICAgICAgcmVkZWVtX3ByaWNlOiAnNTAzNy45ODInLFxuICAgICAgICB9LFxuICAgICAgfVxuXG4gICAgICBjb25zdCByZXNwb25zZSA9IG1ha2VTdHViKCdyZXNwb25zZScsIHtcbiAgICAgICAgcmVzcG9uc2U6IHtcbiAgICAgICAgICBkYXRhOiB7XG4gICAgICAgICAgICAuLi5hcGlSZXNwb25zZURhdGEsXG4gICAgICAgICAgICBjb3N0OiB7fSxcbiAgICAgICAgICB9LFxuICAgICAgICB9LFxuICAgICAgICB0aW1lc3RhbXBzOiB7fSxcbiAgICAgIH0pXG5cbiAgICAgIHJlcXVlc3Rlci5yZXF1ZXN0Lm1vY2tSZXNvbHZlZFZhbHVlKHJlc3BvbnNlKVxuXG4gICAgICBhd2FpdCBodHRwVHJhbnNwb3J0LmJhY2tncm91bmRFeGVjdXRlKGNvbnRleHQpXG5cbiAgICAgIGNvbnN0IHJlcXVlc3RDYWxsID0gcmVxdWVzdGVyLnJlcXVlc3QubW9jay5jYWxsc1swXVxuICAgICAgY29uc3QgcmVxdWVzdENvbmZpZyA9IHJlcXVlc3RDYWxsWzFdXG5cbiAgICAgIGV4cGVjdChyZXF1ZXN0Q29uZmlnLmJhc2VVUkwpLnRvQmUoYXBpRW5kcG9pbnQpXG4gICAgICBleHBlY3QocmVxdWVzdENvbmZpZy51cmwpLnRvQmUoJy9yd2EvYXBpL3YxL3F1b3RlL3ByaWNlJylcbiAgICAgIGV4cGVjdChyZXF1ZXN0Q29uZmlnLnBhcmFtcykudG9FcXVhbCh7IHN5bWJvbDogJ1hBVU0nIH0pXG4gICAgfSlcbiAgfSlcbn0pXG4iXX0= diff --git a/packages/sources/r25/src/config/index.d.ts b/packages/sources/r25/src/config/index.d.ts new file mode 100644 index 00000000000..d603ea6d8a5 --- /dev/null +++ b/packages/sources/r25/src/config/index.d.ts @@ -0,0 +1,21 @@ +import { AdapterConfig } from '@chainlink/external-adapter-framework/config' +export declare const config: AdapterConfig<{ + API_KEY: { + description: string + type: 'string' + required: true + sensitive: true + } + API_SECRET: { + description: string + type: 'string' + required: true + sensitive: true + } + API_ENDPOINT: { + description: string + type: 'string' + default: string + } +}> +//# sourceMappingURL=index.d.ts.map diff --git a/packages/sources/r25/src/config/index.d.ts.map b/packages/sources/r25/src/config/index.d.ts.map new file mode 100644 index 00000000000..4466400246a --- /dev/null +++ b/packages/sources/r25/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,MAAM;;;;;;;;;;;;;;;;;;EAkBjB,CAAA"} \ No newline at end of file diff --git a/packages/sources/r25/src/config/index.js b/packages/sources/r25/src/config/index.js new file mode 100644 index 00000000000..ae948abcfb2 --- /dev/null +++ b/packages/sources/r25/src/config/index.js @@ -0,0 +1,24 @@ +'use strict' +Object.defineProperty(exports, '__esModule', { value: true }) +exports.config = void 0 +const config_1 = require('@chainlink/external-adapter-framework/config') +exports.config = new config_1.AdapterConfig({ + API_KEY: { + description: 'An API key for R25', + type: 'string', + required: true, + sensitive: true, + }, + API_SECRET: { + description: 'An API secret for R25 used to sign requests', + type: 'string', + required: true, + sensitive: true, + }, + API_ENDPOINT: { + description: 'An API endpoint for R25', + type: 'string', + default: 'https://app.r25.xyz', + }, +}) +//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiaW5kZXguanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyJpbmRleC50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiOzs7QUFBQSx5RUFBNEU7QUFFL0QsUUFBQSxNQUFNLEdBQUcsSUFBSSxzQkFBYSxDQUFDO0lBQ3RDLE9BQU8sRUFBRTtRQUNQLFdBQVcsRUFBRSxvQkFBb0I7UUFDakMsSUFBSSxFQUFFLFFBQVE7UUFDZCxRQUFRLEVBQUUsSUFBSTtRQUNkLFNBQVMsRUFBRSxJQUFJO0tBQ2hCO0lBQ0QsVUFBVSxFQUFFO1FBQ1YsV0FBVyxFQUFFLDZDQUE2QztRQUMxRCxJQUFJLEVBQUUsUUFBUTtRQUNkLFFBQVEsRUFBRSxJQUFJO1FBQ2QsU0FBUyxFQUFFLElBQUk7S0FDaEI7SUFDRCxZQUFZLEVBQUU7UUFDWixXQUFXLEVBQUUseUJBQXlCO1FBQ3RDLElBQUksRUFBRSxRQUFRO1FBQ2QsT0FBTyxFQUFFLHFCQUFxQjtLQUMvQjtDQUNGLENBQUMsQ0FBQSIsInNvdXJjZXNDb250ZW50IjpbImltcG9ydCB7IEFkYXB0ZXJDb25maWcgfSBmcm9tICdAY2hhaW5saW5rL2V4dGVybmFsLWFkYXB0ZXItZnJhbWV3b3JrL2NvbmZpZydcblxuZXhwb3J0IGNvbnN0IGNvbmZpZyA9IG5ldyBBZGFwdGVyQ29uZmlnKHtcbiAgQVBJX0tFWToge1xuICAgIGRlc2NyaXB0aW9uOiAnQW4gQVBJIGtleSBmb3IgUjI1JyxcbiAgICB0eXBlOiAnc3RyaW5nJyxcbiAgICByZXF1aXJlZDogdHJ1ZSxcbiAgICBzZW5zaXRpdmU6IHRydWUsXG4gIH0sXG4gIEFQSV9TRUNSRVQ6IHtcbiAgICBkZXNjcmlwdGlvbjogJ0FuIEFQSSBzZWNyZXQgZm9yIFIyNSB1c2VkIHRvIHNpZ24gcmVxdWVzdHMnLFxuICAgIHR5cGU6ICdzdHJpbmcnLFxuICAgIHJlcXVpcmVkOiB0cnVlLFxuICAgIHNlbnNpdGl2ZTogdHJ1ZSxcbiAgfSxcbiAgQVBJX0VORFBPSU5UOiB7XG4gICAgZGVzY3JpcHRpb246ICdBbiBBUEkgZW5kcG9pbnQgZm9yIFIyNScsXG4gICAgdHlwZTogJ3N0cmluZycsXG4gICAgZGVmYXVsdDogJ2h0dHBzOi8vYXBwLnIyNS54eXonLFxuICB9LFxufSlcbiJdfQ== diff --git a/packages/sources/r25/src/endpoint/index.d.ts b/packages/sources/r25/src/endpoint/index.d.ts new file mode 100644 index 00000000000..6f067f21254 --- /dev/null +++ b/packages/sources/r25/src/endpoint/index.d.ts @@ -0,0 +1,2 @@ +export { endpoint as nav } from './nav' +//# sourceMappingURL=index.d.ts.map diff --git a/packages/sources/r25/src/endpoint/index.d.ts.map b/packages/sources/r25/src/endpoint/index.d.ts.map new file mode 100644 index 00000000000..074e0486e3a --- /dev/null +++ b/packages/sources/r25/src/endpoint/index.d.ts.map @@ -0,0 +1 @@ +{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,IAAI,GAAG,EAAE,MAAM,OAAO,CAAA"} \ No newline at end of file diff --git a/packages/sources/r25/src/endpoint/index.js b/packages/sources/r25/src/endpoint/index.js new file mode 100644 index 00000000000..1b347620f31 --- /dev/null +++ b/packages/sources/r25/src/endpoint/index.js @@ -0,0 +1,11 @@ +'use strict' +Object.defineProperty(exports, '__esModule', { value: true }) +exports.nav = void 0 +var nav_1 = require('./nav') +Object.defineProperty(exports, 'nav', { + enumerable: true, + get: function () { + return nav_1.endpoint + }, +}) +//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiaW5kZXguanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyJpbmRleC50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiOzs7QUFBQSw2QkFBdUM7QUFBOUIsMEZBQUEsUUFBUSxPQUFPIiwic291cmNlc0NvbnRlbnQiOlsiZXhwb3J0IHsgZW5kcG9pbnQgYXMgbmF2IH0gZnJvbSAnLi9uYXYnXG4iXX0= diff --git a/packages/sources/r25/src/endpoint/nav.d.ts b/packages/sources/r25/src/endpoint/nav.d.ts new file mode 100644 index 00000000000..6cc6a089f1c --- /dev/null +++ b/packages/sources/r25/src/endpoint/nav.d.ts @@ -0,0 +1,29 @@ +import { AdapterEndpoint } from '@chainlink/external-adapter-framework/adapter' +import { SingleNumberResultResponse } from '@chainlink/external-adapter-framework/util' +import { InputParameters } from '@chainlink/external-adapter-framework/validation' +import { config } from '../config' +export declare const inputParameters: InputParameters<{ + readonly chainType: { + readonly type: 'string' + readonly description: 'The chain type (e.g., polygon, sui)' + readonly required: true + } + readonly tokenName: { + readonly type: 'string' + readonly description: 'The token name (e.g., rcusdp)' + readonly required: true + } +}> +export type BaseEndpointTypes = { + Parameters: typeof inputParameters.definition + Response: SingleNumberResultResponse & { + Data: { + navPerShare: number + aum: number + navDate: string + } + } + Settings: typeof config.settings +} +export declare const endpoint: AdapterEndpoint +//# sourceMappingURL=nav.d.ts.map diff --git a/packages/sources/r25/src/endpoint/nav.d.ts.map b/packages/sources/r25/src/endpoint/nav.d.ts.map new file mode 100644 index 00000000000..42f8c87d952 --- /dev/null +++ b/packages/sources/r25/src/endpoint/nav.d.ts.map @@ -0,0 +1 @@ +{"version":3,"file":"nav.d.ts","sourceRoot":"","sources":["nav.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,eAAe,EAAE,MAAM,+CAA+C,CAAA;AAC/E,OAAO,EAAE,0BAA0B,EAAE,MAAM,4CAA4C,CAAA;AACvF,OAAO,EAAE,eAAe,EAAE,MAAM,kDAAkD,CAAA;AAClF,OAAO,EAAE,MAAM,EAAE,MAAM,WAAW,CAAA;AAGlC,eAAO,MAAM,eAAe;;;;;;;;;;;EAmB3B,CAAA;AAED,MAAM,MAAM,iBAAiB,GAAG;IAC9B,UAAU,EAAE,OAAO,eAAe,CAAC,UAAU,CAAA;IAC7C,QAAQ,EAAE,0BAA0B,GAAG;QACrC,IAAI,EAAE;YACJ,WAAW,EAAE,MAAM,CAAA;YACnB,GAAG,EAAE,MAAM,CAAA;YACX,OAAO,EAAE,MAAM,CAAA;SAChB,CAAA;KACF,CAAA;IACD,QAAQ,EAAE,OAAO,MAAM,CAAC,QAAQ,CAAA;CACjC,CAAA;AAED,eAAO,MAAM,QAAQ,gEAInB,CAAA"} \ No newline at end of file diff --git a/packages/sources/r25/src/endpoint/nav.js b/packages/sources/r25/src/endpoint/nav.js new file mode 100644 index 00000000000..e6e4d7f9543 --- /dev/null +++ b/packages/sources/r25/src/endpoint/nav.js @@ -0,0 +1,32 @@ +'use strict' +Object.defineProperty(exports, '__esModule', { value: true }) +exports.endpoint = exports.inputParameters = void 0 +const adapter_1 = require('@chainlink/external-adapter-framework/adapter') +const validation_1 = require('@chainlink/external-adapter-framework/validation') +const nav_1 = require('../transport/nav') +exports.inputParameters = new validation_1.InputParameters( + { + chainType: { + type: 'string', + description: 'The chain type (e.g., polygon, sui)', + required: true, + }, + tokenName: { + type: 'string', + description: 'The token name (e.g., rcusdp)', + required: true, + }, + }, + [ + { + chainType: 'polygon', + tokenName: 'rcusdp', + }, + ], +) +exports.endpoint = new adapter_1.AdapterEndpoint({ + name: 'nav', + transport: nav_1.httpTransport, + inputParameters: exports.inputParameters, +}) +//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoibmF2LmpzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXMiOlsibmF2LnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiI7OztBQUFBLDJFQUErRTtBQUUvRSxpRkFBa0Y7QUFFbEYsMENBQWdEO0FBRW5DLFFBQUEsZUFBZSxHQUFHLElBQUksNEJBQWUsQ0FDaEQ7SUFDRSxTQUFTLEVBQUU7UUFDVCxJQUFJLEVBQUUsUUFBUTtRQUNkLFdBQVcsRUFBRSxxQ0FBcUM7UUFDbEQsUUFBUSxFQUFFLElBQUk7S0FDZjtJQUNELFNBQVMsRUFBRTtRQUNULElBQUksRUFBRSxRQUFRO1FBQ2QsV0FBVyxFQUFFLCtCQUErQjtRQUM1QyxRQUFRLEVBQUUsSUFBSTtLQUNmO0NBQ0YsRUFDRDtJQUNFO1FBQ0UsU0FBUyxFQUFFLFNBQVM7UUFDcEIsU0FBUyxFQUFFLFFBQVE7S0FDcEI7Q0FDRixDQUNGLENBQUE7QUFjWSxRQUFBLFFBQVEsR0FBRyxJQUFJLHlCQUFlLENBQUM7SUFDMUMsSUFBSSxFQUFFLEtBQUs7SUFDWCxTQUFTLEVBQUUsbUJBQWE7SUFDeEIsZUFBZSxFQUFmLHVCQUFlO0NBQ2hCLENBQUMsQ0FBQSIsInNvdXJjZXNDb250ZW50IjpbImltcG9ydCB7IEFkYXB0ZXJFbmRwb2ludCB9IGZyb20gJ0BjaGFpbmxpbmsvZXh0ZXJuYWwtYWRhcHRlci1mcmFtZXdvcmsvYWRhcHRlcidcbmltcG9ydCB7IFNpbmdsZU51bWJlclJlc3VsdFJlc3BvbnNlIH0gZnJvbSAnQGNoYWlubGluay9leHRlcm5hbC1hZGFwdGVyLWZyYW1ld29yay91dGlsJ1xuaW1wb3J0IHsgSW5wdXRQYXJhbWV0ZXJzIH0gZnJvbSAnQGNoYWlubGluay9leHRlcm5hbC1hZGFwdGVyLWZyYW1ld29yay92YWxpZGF0aW9uJ1xuaW1wb3J0IHsgY29uZmlnIH0gZnJvbSAnLi4vY29uZmlnJ1xuaW1wb3J0IHsgaHR0cFRyYW5zcG9ydCB9IGZyb20gJy4uL3RyYW5zcG9ydC9uYXYnXG5cbmV4cG9ydCBjb25zdCBpbnB1dFBhcmFtZXRlcnMgPSBuZXcgSW5wdXRQYXJhbWV0ZXJzKFxuICB7XG4gICAgY2hhaW5UeXBlOiB7XG4gICAgICB0eXBlOiAnc3RyaW5nJyxcbiAgICAgIGRlc2NyaXB0aW9uOiAnVGhlIGNoYWluIHR5cGUgKGUuZy4sIHBvbHlnb24sIHN1aSknLFxuICAgICAgcmVxdWlyZWQ6IHRydWUsXG4gICAgfSxcbiAgICB0b2tlbk5hbWU6IHtcbiAgICAgIHR5cGU6ICdzdHJpbmcnLFxuICAgICAgZGVzY3JpcHRpb246ICdUaGUgdG9rZW4gbmFtZSAoZS5nLiwgcmN1c2RwKScsXG4gICAgICByZXF1aXJlZDogdHJ1ZSxcbiAgICB9LFxuICB9LFxuICBbXG4gICAge1xuICAgICAgY2hhaW5UeXBlOiAncG9seWdvbicsXG4gICAgICB0b2tlbk5hbWU6ICdyY3VzZHAnLFxuICAgIH0sXG4gIF0sXG4pXG5cbmV4cG9ydCB0eXBlIEJhc2VFbmRwb2ludFR5cGVzID0ge1xuICBQYXJhbWV0ZXJzOiB0eXBlb2YgaW5wdXRQYXJhbWV0ZXJzLmRlZmluaXRpb25cbiAgUmVzcG9uc2U6IFNpbmdsZU51bWJlclJlc3VsdFJlc3BvbnNlICYge1xuICAgIERhdGE6IHtcbiAgICAgIG5hdlBlclNoYXJlOiBudW1iZXJcbiAgICAgIGF1bTogbnVtYmVyXG4gICAgICBuYXZEYXRlOiBzdHJpbmdcbiAgICB9XG4gIH1cbiAgU2V0dGluZ3M6IHR5cGVvZiBjb25maWcuc2V0dGluZ3Ncbn1cblxuZXhwb3J0IGNvbnN0IGVuZHBvaW50ID0gbmV3IEFkYXB0ZXJFbmRwb2ludCh7XG4gIG5hbWU6ICduYXYnLFxuICB0cmFuc3BvcnQ6IGh0dHBUcmFuc3BvcnQsXG4gIGlucHV0UGFyYW1ldGVycyxcbn0pXG4iXX0= diff --git a/packages/sources/r25/src/index.d.ts b/packages/sources/r25/src/index.d.ts new file mode 100644 index 00000000000..15a2e8022dc --- /dev/null +++ b/packages/sources/r25/src/index.d.ts @@ -0,0 +1,23 @@ +import { ServerInstance } from '@chainlink/external-adapter-framework' +import { Adapter } from '@chainlink/external-adapter-framework/adapter' +export declare const adapter: Adapter<{ + API_KEY: { + description: string + type: 'string' + required: true + sensitive: true + } + API_SECRET: { + description: string + type: 'string' + required: true + sensitive: true + } + API_ENDPOINT: { + description: string + type: 'string' + default: string + } +}> +export declare const server: () => Promise +//# sourceMappingURL=index.d.ts.map diff --git a/packages/sources/r25/src/index.d.ts.map b/packages/sources/r25/src/index.d.ts.map new file mode 100644 index 00000000000..3b8f474105e --- /dev/null +++ b/packages/sources/r25/src/index.d.ts.map @@ -0,0 +1 @@ +{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAU,cAAc,EAAE,MAAM,uCAAuC,CAAA;AAC9E,OAAO,EAAE,OAAO,EAAE,MAAM,+CAA+C,CAAA;AAIvE,eAAO,MAAM,OAAO;;;;;;;;;;;;;;;;;;EAYlB,CAAA;AAEF,eAAO,MAAM,MAAM,QAAO,OAAO,CAAC,cAAc,GAAG,SAAS,CAAoB,CAAA"} \ No newline at end of file diff --git a/packages/sources/r25/src/index.js b/packages/sources/r25/src/index.js new file mode 100644 index 00000000000..e764b7fc031 --- /dev/null +++ b/packages/sources/r25/src/index.js @@ -0,0 +1,23 @@ +'use strict' +Object.defineProperty(exports, '__esModule', { value: true }) +exports.server = exports.adapter = void 0 +const external_adapter_framework_1 = require('@chainlink/external-adapter-framework') +const adapter_1 = require('@chainlink/external-adapter-framework/adapter') +const config_1 = require('./config') +const nav_1 = require('./endpoint/nav') +exports.adapter = new adapter_1.Adapter({ + defaultEndpoint: nav_1.endpoint.name, + name: 'R25', + config: config_1.config, + endpoints: [nav_1.endpoint], + rateLimiting: { + tiers: { + default: { + rateLimit1s: 5, //5 requests per second + }, + }, + }, +}) +const server = () => (0, external_adapter_framework_1.expose)(exports.adapter) +exports.server = server +//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiaW5kZXguanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyJpbmRleC50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiOzs7QUFBQSxzRkFBOEU7QUFDOUUsMkVBQXVFO0FBQ3ZFLHFDQUFpQztBQUNqQyx3Q0FBd0Q7QUFFM0MsUUFBQSxPQUFPLEdBQUcsSUFBSSxpQkFBTyxDQUFDO0lBQ2pDLGVBQWUsRUFBRSxjQUFXLENBQUMsSUFBSTtJQUNqQyxJQUFJLEVBQUUsS0FBSztJQUNYLE1BQU0sRUFBTixlQUFNO0lBQ04sU0FBUyxFQUFFLENBQUMsY0FBVyxDQUFDO0lBQ3hCLFlBQVksRUFBRTtRQUNaLEtBQUssRUFBRTtZQUNMLE9BQU8sRUFBRTtnQkFDUCxXQUFXLEVBQUUsQ0FBQyxFQUFFLHVCQUF1QjthQUN4QztTQUNGO0tBQ0Y7Q0FDRixDQUFDLENBQUE7QUFFSyxNQUFNLE1BQU0sR0FBRyxHQUF3QyxFQUFFLENBQUMsSUFBQSxtQ0FBTSxFQUFDLGVBQU8sQ0FBQyxDQUFBO0FBQW5FLFFBQUEsTUFBTSxVQUE2RCIsInNvdXJjZXNDb250ZW50IjpbImltcG9ydCB7IGV4cG9zZSwgU2VydmVySW5zdGFuY2UgfSBmcm9tICdAY2hhaW5saW5rL2V4dGVybmFsLWFkYXB0ZXItZnJhbWV3b3JrJ1xuaW1wb3J0IHsgQWRhcHRlciB9IGZyb20gJ0BjaGFpbmxpbmsvZXh0ZXJuYWwtYWRhcHRlci1mcmFtZXdvcmsvYWRhcHRlcidcbmltcG9ydCB7IGNvbmZpZyB9IGZyb20gJy4vY29uZmlnJ1xuaW1wb3J0IHsgZW5kcG9pbnQgYXMgbmF2RW5kcG9pbnQgfSBmcm9tICcuL2VuZHBvaW50L25hdidcblxuZXhwb3J0IGNvbnN0IGFkYXB0ZXIgPSBuZXcgQWRhcHRlcih7XG4gIGRlZmF1bHRFbmRwb2ludDogbmF2RW5kcG9pbnQubmFtZSxcbiAgbmFtZTogJ1IyNScsXG4gIGNvbmZpZyxcbiAgZW5kcG9pbnRzOiBbbmF2RW5kcG9pbnRdLFxuICByYXRlTGltaXRpbmc6IHtcbiAgICB0aWVyczoge1xuICAgICAgZGVmYXVsdDoge1xuICAgICAgICByYXRlTGltaXQxczogNSwgLy81IHJlcXVlc3RzIHBlciBzZWNvbmRcbiAgICAgIH0sXG4gICAgfSxcbiAgfSxcbn0pXG5cbmV4cG9ydCBjb25zdCBzZXJ2ZXIgPSAoKTogUHJvbWlzZTxTZXJ2ZXJJbnN0YW5jZSB8IHVuZGVmaW5lZD4gPT4gZXhwb3NlKGFkYXB0ZXIpXG4iXX0= diff --git a/packages/sources/r25/src/transport/authentication.d.ts b/packages/sources/r25/src/transport/authentication.d.ts new file mode 100644 index 00000000000..4379ce0e833 --- /dev/null +++ b/packages/sources/r25/src/transport/authentication.d.ts @@ -0,0 +1,25 @@ +export interface GetRequestHeadersParams { + method: string + path: string + params: Record + apiKey: string + secret: string + timestamp: number +} +/** + * Generate the HMAC-SHA256 signature for R25 API requests. + * + * The signature string is constructed as: + * {method}\n{path}\n{sorted_params}\n{timestamp}\n{api_key} + * + * Where: + * - method: HTTP method in lowercase (e.g., "get") + * - path: Request path (e.g., "/api/public/current/nav") + * - sorted_params: Query parameters sorted by key, formatted as key=value, joined with & + * - timestamp: Current UTC timestamp in milliseconds + * - api_key: API key + */ +export declare const getRequestHeaders: ( + getRequestHeadersParams: GetRequestHeadersParams, +) => Record +//# sourceMappingURL=authentication.d.ts.map diff --git a/packages/sources/r25/src/transport/authentication.d.ts.map b/packages/sources/r25/src/transport/authentication.d.ts.map new file mode 100644 index 00000000000..7f4be1f78f4 --- /dev/null +++ b/packages/sources/r25/src/transport/authentication.d.ts.map @@ -0,0 +1 @@ +{"version":3,"file":"authentication.d.ts","sourceRoot":"","sources":["authentication.ts"],"names":[],"mappings":"AAEA,MAAM,WAAW,uBAAuB;IACtC,MAAM,EAAE,MAAM,CAAA;IACd,IAAI,EAAE,MAAM,CAAA;IACZ,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAA;IAC9B,MAAM,EAAE,MAAM,CAAA;IACd,MAAM,EAAE,MAAM,CAAA;IACd,SAAS,EAAE,MAAM,CAAA;CAClB;AAED;;;;;;;;;;;;GAYG;AACH,eAAO,MAAM,iBAAiB,GAC5B,yBAAyB,uBAAuB,KAC/C,MAAM,CAAC,MAAM,EAAE,MAAM,CAwBvB,CAAA"} \ No newline at end of file diff --git a/packages/sources/r25/src/transport/authentication.js b/packages/sources/r25/src/transport/authentication.js new file mode 100644 index 00000000000..08242e25037 --- /dev/null +++ b/packages/sources/r25/src/transport/authentication.js @@ -0,0 +1,43 @@ +'use strict' +Object.defineProperty(exports, '__esModule', { value: true }) +exports.getRequestHeaders = void 0 +const tslib_1 = require('tslib') +const crypto_js_1 = tslib_1.__importDefault(require('crypto-js')) +/** + * Generate the HMAC-SHA256 signature for R25 API requests. + * + * The signature string is constructed as: + * {method}\n{path}\n{sorted_params}\n{timestamp}\n{api_key} + * + * Where: + * - method: HTTP method in lowercase (e.g., "get") + * - path: Request path (e.g., "/api/public/current/nav") + * - sorted_params: Query parameters sorted by key, formatted as key=value, joined with & + * - timestamp: Current UTC timestamp in milliseconds + * - api_key: API key + */ +const getRequestHeaders = (getRequestHeadersParams) => { + const { method, path, params, apiKey, secret, timestamp } = getRequestHeadersParams + // Sort parameters by key in lexicographical order and format as key=value + const sortedParams = Object.keys(params) + .sort() + .map((key) => `${key}=${params[key]}`) + .join('&') + const stringToSign = [ + method.toLowerCase(), + path, + sortedParams, + timestamp.toString(), + apiKey, + ].join('\n') + const signature = crypto_js_1.default + .HmacSHA256(stringToSign, secret) + .toString(crypto_js_1.default.enc.Hex) + return { + 'x-api-key': apiKey, + 'x-utc-timestamp': timestamp.toString(), + 'x-signature': signature, + } +} +exports.getRequestHeaders = getRequestHeaders +//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiYXV0aGVudGljYXRpb24uanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyJhdXRoZW50aWNhdGlvbi50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiOzs7O0FBQUEsa0VBQWdDO0FBV2hDOzs7Ozs7Ozs7Ozs7R0FZRztBQUNJLE1BQU0saUJBQWlCLEdBQUcsQ0FDL0IsdUJBQWdELEVBQ3hCLEVBQUU7SUFDMUIsTUFBTSxFQUFFLE1BQU0sRUFBRSxJQUFJLEVBQUUsTUFBTSxFQUFFLE1BQU0sRUFBRSxNQUFNLEVBQUUsU0FBUyxFQUFFLEdBQUcsdUJBQXVCLENBQUE7SUFFbkYsMEVBQTBFO0lBQzFFLE1BQU0sWUFBWSxHQUFHLE1BQU0sQ0FBQyxJQUFJLENBQUMsTUFBTSxDQUFDO1NBQ3JDLElBQUksRUFBRTtTQUNOLEdBQUcsQ0FBQyxDQUFDLEdBQUcsRUFBRSxFQUFFLENBQUMsR0FBRyxHQUFHLElBQUksTUFBTSxDQUFDLEdBQUcsQ0FBQyxFQUFFLENBQUM7U0FDckMsSUFBSSxDQUFDLEdBQUcsQ0FBQyxDQUFBO0lBRVosTUFBTSxZQUFZLEdBQUc7UUFDbkIsTUFBTSxDQUFDLFdBQVcsRUFBRTtRQUNwQixJQUFJO1FBQ0osWUFBWTtRQUNaLFNBQVMsQ0FBQyxRQUFRLEVBQUU7UUFDcEIsTUFBTTtLQUNQLENBQUMsSUFBSSxDQUFDLElBQUksQ0FBQyxDQUFBO0lBRVosTUFBTSxTQUFTLEdBQUcsbUJBQVEsQ0FBQyxVQUFVLENBQUMsWUFBWSxFQUFFLE1BQU0sQ0FBQyxDQUFDLFFBQVEsQ0FBQyxtQkFBUSxDQUFDLEdBQUcsQ0FBQyxHQUFHLENBQUMsQ0FBQTtJQUV0RixPQUFPO1FBQ0wsV0FBVyxFQUFFLE1BQU07UUFDbkIsaUJBQWlCLEVBQUUsU0FBUyxDQUFDLFFBQVEsRUFBRTtRQUN2QyxhQUFhLEVBQUUsU0FBUztLQUN6QixDQUFBO0FBQ0gsQ0FBQyxDQUFBO0FBMUJZLFFBQUEsaUJBQWlCLHFCQTBCN0IiLCJzb3VyY2VzQ29udGVudCI6WyJpbXBvcnQgQ3J5cHRvSlMgZnJvbSAnY3J5cHRvLWpzJ1xuXG5leHBvcnQgaW50ZXJmYWNlIEdldFJlcXVlc3RIZWFkZXJzUGFyYW1zIHtcbiAgbWV0aG9kOiBzdHJpbmdcbiAgcGF0aDogc3RyaW5nXG4gIHBhcmFtczogUmVjb3JkPHN0cmluZywgc3RyaW5nPlxuICBhcGlLZXk6IHN0cmluZ1xuICBzZWNyZXQ6IHN0cmluZ1xuICB0aW1lc3RhbXA6IG51bWJlclxufVxuXG4vKipcbiAqIEdlbmVyYXRlIHRoZSBITUFDLVNIQTI1NiBzaWduYXR1cmUgZm9yIFIyNSBBUEkgcmVxdWVzdHMuXG4gKlxuICogVGhlIHNpZ25hdHVyZSBzdHJpbmcgaXMgY29uc3RydWN0ZWQgYXM6XG4gKiB7bWV0aG9kfVxcbntwYXRofVxcbntzb3J0ZWRfcGFyYW1zfVxcbnt0aW1lc3RhbXB9XFxue2FwaV9rZXl9XG4gKlxuICogV2hlcmU6XG4gKiAtIG1ldGhvZDogSFRUUCBtZXRob2QgaW4gbG93ZXJjYXNlIChlLmcuLCBcImdldFwiKVxuICogLSBwYXRoOiBSZXF1ZXN0IHBhdGggKGUuZy4sIFwiL2FwaS9wdWJsaWMvY3VycmVudC9uYXZcIilcbiAqIC0gc29ydGVkX3BhcmFtczogUXVlcnkgcGFyYW1ldGVycyBzb3J0ZWQgYnkga2V5LCBmb3JtYXR0ZWQgYXMga2V5PXZhbHVlLCBqb2luZWQgd2l0aCAmXG4gKiAtIHRpbWVzdGFtcDogQ3VycmVudCBVVEMgdGltZXN0YW1wIGluIG1pbGxpc2Vjb25kc1xuICogLSBhcGlfa2V5OiBBUEkga2V5XG4gKi9cbmV4cG9ydCBjb25zdCBnZXRSZXF1ZXN0SGVhZGVycyA9IChcbiAgZ2V0UmVxdWVzdEhlYWRlcnNQYXJhbXM6IEdldFJlcXVlc3RIZWFkZXJzUGFyYW1zLFxuKTogUmVjb3JkPHN0cmluZywgc3RyaW5nPiA9PiB7XG4gIGNvbnN0IHsgbWV0aG9kLCBwYXRoLCBwYXJhbXMsIGFwaUtleSwgc2VjcmV0LCB0aW1lc3RhbXAgfSA9IGdldFJlcXVlc3RIZWFkZXJzUGFyYW1zXG5cbiAgLy8gU29ydCBwYXJhbWV0ZXJzIGJ5IGtleSBpbiBsZXhpY29ncmFwaGljYWwgb3JkZXIgYW5kIGZvcm1hdCBhcyBrZXk9dmFsdWVcbiAgY29uc3Qgc29ydGVkUGFyYW1zID0gT2JqZWN0LmtleXMocGFyYW1zKVxuICAgIC5zb3J0KClcbiAgICAubWFwKChrZXkpID0+IGAke2tleX09JHtwYXJhbXNba2V5XX1gKVxuICAgIC5qb2luKCcmJylcblxuICBjb25zdCBzdHJpbmdUb1NpZ24gPSBbXG4gICAgbWV0aG9kLnRvTG93ZXJDYXNlKCksXG4gICAgcGF0aCxcbiAgICBzb3J0ZWRQYXJhbXMsXG4gICAgdGltZXN0YW1wLnRvU3RyaW5nKCksXG4gICAgYXBpS2V5LFxuICBdLmpvaW4oJ1xcbicpXG5cbiAgY29uc3Qgc2lnbmF0dXJlID0gQ3J5cHRvSlMuSG1hY1NIQTI1NihzdHJpbmdUb1NpZ24sIHNlY3JldCkudG9TdHJpbmcoQ3J5cHRvSlMuZW5jLkhleClcblxuICByZXR1cm4ge1xuICAgICd4LWFwaS1rZXknOiBhcGlLZXksXG4gICAgJ3gtdXRjLXRpbWVzdGFtcCc6IHRpbWVzdGFtcC50b1N0cmluZygpLFxuICAgICd4LXNpZ25hdHVyZSc6IHNpZ25hdHVyZSxcbiAgfVxufVxuIl19 diff --git a/packages/sources/r25/src/transport/nav.d.ts b/packages/sources/r25/src/transport/nav.d.ts new file mode 100644 index 00000000000..34a09cb06e2 --- /dev/null +++ b/packages/sources/r25/src/transport/nav.d.ts @@ -0,0 +1,26 @@ +import { HttpTransport } from '@chainlink/external-adapter-framework/transports' +import { BaseEndpointTypes } from '../endpoint/nav' +export interface ResponseSchema { + code: string + success: boolean + message: string + data: { + lastUpdate: string + tokenName: string + chainType: string + totalSupply: number + totalAsset: number + currentNav: string + } | null +} +export interface ErrorResponseSchema { + error: string +} +export type HttpTransportTypes = BaseEndpointTypes & { + Provider: { + RequestBody: never + ResponseBody: ResponseSchema | ErrorResponseSchema + } +} +export declare const httpTransport: HttpTransport +//# sourceMappingURL=nav.d.ts.map diff --git a/packages/sources/r25/src/transport/nav.d.ts.map b/packages/sources/r25/src/transport/nav.d.ts.map new file mode 100644 index 00000000000..884c159f50b --- /dev/null +++ b/packages/sources/r25/src/transport/nav.d.ts.map @@ -0,0 +1 @@ +{"version":3,"file":"nav.d.ts","sourceRoot":"","sources":["nav.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAAE,MAAM,kDAAkD,CAAA;AAChF,OAAO,EAAE,iBAAiB,EAAE,MAAM,iBAAiB,CAAA;AAGnD,MAAM,WAAW,cAAc;IAC7B,IAAI,EAAE,MAAM,CAAA;IACZ,OAAO,EAAE,OAAO,CAAA;IAChB,OAAO,EAAE,MAAM,CAAA;IACf,IAAI,EAAE;QACJ,UAAU,EAAE,MAAM,CAAA;QAClB,SAAS,EAAE,MAAM,CAAA;QACjB,SAAS,EAAE,MAAM,CAAA;QACjB,WAAW,EAAE,MAAM,CAAA;QACnB,UAAU,EAAE,MAAM,CAAA;QAClB,UAAU,EAAE,MAAM,CAAA;KACnB,GAAG,IAAI,CAAA;CACT;AAED,MAAM,WAAW,mBAAmB;IAClC,KAAK,EAAE,MAAM,CAAA;CACd;AAED,MAAM,MAAM,kBAAkB,GAAG,iBAAiB,GAAG;IACnD,QAAQ,EAAE;QACR,WAAW,EAAE,KAAK,CAAA;QAClB,YAAY,EAAE,cAAc,GAAG,mBAAmB,CAAA;KACnD,CAAA;CACF,CAAA;AAED,eAAO,MAAM,aAAa,mCAgExB,CAAA"} \ No newline at end of file diff --git a/packages/sources/r25/src/transport/nav.js b/packages/sources/r25/src/transport/nav.js new file mode 100644 index 00000000000..55b16537b4c --- /dev/null +++ b/packages/sources/r25/src/transport/nav.js @@ -0,0 +1,67 @@ +'use strict' +Object.defineProperty(exports, '__esModule', { value: true }) +exports.httpTransport = void 0 +const transports_1 = require('@chainlink/external-adapter-framework/transports') +const authentication_1 = require('./authentication') +exports.httpTransport = new transports_1.HttpTransport({ + prepareRequests: (params, config) => { + return params.map((param) => { + const method = 'GET' + const path = '/api/public/current/nav' + const timestamp = Date.now() + const queryParams = { + chainType: param.chainType, + tokenName: param.tokenName, + } + const headers = (0, authentication_1.getRequestHeaders)({ + method, + path, + params: queryParams, + apiKey: config.API_KEY, + secret: config.API_SECRET, + timestamp, + }) + return { + params: [param], + request: { + baseURL: config.API_ENDPOINT, + url: path, + params: queryParams, + headers, + }, + } + }) + }, + parseResponse: (params, response) => { + return params.map((param) => { + const apiResponse = response.data + if ('error' in response.data || !apiResponse.success || !apiResponse.data) { + const errorResponse = response.data + return { + params: param, + response: { + errorMessage: apiResponse.message || errorResponse.error, + statusCode: 502, + }, + } + } + const result = Number(apiResponse.data.currentNav) + return { + params: param, + response: { + result, + data: { + result, + navPerShare: result, + aum: apiResponse.data.totalAsset, + navDate: apiResponse.data.lastUpdate, + }, + timestamps: { + providerIndicatedTimeUnixMs: new Date(apiResponse.data.lastUpdate).getTime(), + }, + }, + } + }) + }, +}) +//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoibmF2LmpzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXMiOlsibmF2LnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiI7OztBQUFBLGlGQUFnRjtBQUVoRixxREFBb0Q7QUEyQnZDLFFBQUEsYUFBYSxHQUFHLElBQUksMEJBQWEsQ0FBcUI7SUFDakUsZUFBZSxFQUFFLENBQUMsTUFBTSxFQUFFLE1BQU0sRUFBRSxFQUFFO1FBQ2xDLE9BQU8sTUFBTSxDQUFDLEdBQUcsQ0FBQyxDQUFDLEtBQUssRUFBRSxFQUFFO1lBQzFCLE1BQU0sTUFBTSxHQUFHLEtBQUssQ0FBQTtZQUNwQixNQUFNLElBQUksR0FBRyx5QkFBeUIsQ0FBQTtZQUN0QyxNQUFNLFNBQVMsR0FBRyxJQUFJLENBQUMsR0FBRyxFQUFFLENBQUE7WUFFNUIsTUFBTSxXQUFXLEdBQUc7Z0JBQ2xCLFNBQVMsRUFBRSxLQUFLLENBQUMsU0FBUztnQkFDMUIsU0FBUyxFQUFFLEtBQUssQ0FBQyxTQUFTO2FBQzNCLENBQUE7WUFFRCxNQUFNLE9BQU8sR0FBRyxJQUFBLGtDQUFpQixFQUFDO2dCQUNoQyxNQUFNO2dCQUNOLElBQUk7Z0JBQ0osTUFBTSxFQUFFLFdBQVc7Z0JBQ25CLE1BQU0sRUFBRSxNQUFNLENBQUMsT0FBTztnQkFDdEIsTUFBTSxFQUFFLE1BQU0sQ0FBQyxVQUFVO2dCQUN6QixTQUFTO2FBQ1YsQ0FBQyxDQUFBO1lBRUYsT0FBTztnQkFDTCxNQUFNLEVBQUUsQ0FBQyxLQUFLLENBQUM7Z0JBQ2YsT0FBTyxFQUFFO29CQUNQLE9BQU8sRUFBRSxNQUFNLENBQUMsWUFBWTtvQkFDNUIsR0FBRyxFQUFFLElBQUk7b0JBQ1QsTUFBTSxFQUFFLFdBQVc7b0JBQ25CLE9BQU87aUJBQ1I7YUFDRixDQUFBO1FBQ0gsQ0FBQyxDQUFDLENBQUE7SUFDSixDQUFDO0lBQ0QsYUFBYSxFQUFFLENBQUMsTUFBTSxFQUFFLFFBQVEsRUFBRSxFQUFFO1FBQ2xDLE9BQU8sTUFBTSxDQUFDLEdBQUcsQ0FBQyxDQUFDLEtBQUssRUFBRSxFQUFFO1lBQzFCLE1BQU0sV0FBVyxHQUFHLFFBQVEsQ0FBQyxJQUFzQixDQUFBO1lBQ25ELElBQUksT0FBTyxJQUFJLFFBQVEsQ0FBQyxJQUFJLElBQUksQ0FBQyxXQUFXLENBQUMsT0FBTyxJQUFJLENBQUMsV0FBVyxDQUFDLElBQUksRUFBRSxDQUFDO2dCQUMxRSxNQUFNLGFBQWEsR0FBRyxRQUFRLENBQUMsSUFBMkIsQ0FBQTtnQkFDMUQsT0FBTztvQkFDTCxNQUFNLEVBQUUsS0FBSztvQkFDYixRQUFRLEVBQUU7d0JBQ1IsWUFBWSxFQUFFLFdBQVcsQ0FBQyxPQUFPLElBQUksYUFBYSxDQUFDLEtBQUs7d0JBQ3hELFVBQVUsRUFBRSxHQUFHO3FCQUNoQjtpQkFDRixDQUFBO1lBQ0gsQ0FBQztZQUVELE1BQU0sTUFBTSxHQUFHLE1BQU0sQ0FBQyxXQUFXLENBQUMsSUFBSSxDQUFDLFVBQVUsQ0FBQyxDQUFBO1lBQ2xELE9BQU87Z0JBQ0wsTUFBTSxFQUFFLEtBQUs7Z0JBQ2IsUUFBUSxFQUFFO29CQUNSLE1BQU07b0JBQ04sSUFBSSxFQUFFO3dCQUNKLE1BQU07d0JBQ04sV0FBVyxFQUFFLE1BQU07d0JBQ25CLEdBQUcsRUFBRSxXQUFXLENBQUMsSUFBSSxDQUFDLFVBQVU7d0JBQ2hDLE9BQU8sRUFBRSxXQUFXLENBQUMsSUFBSSxDQUFDLFVBQVU7cUJBQ3JDO29CQUNELFVBQVUsRUFBRTt3QkFDViwyQkFBMkIsRUFBRSxJQUFJLElBQUksQ0FBQyxXQUFXLENBQUMsSUFBSSxDQUFDLFVBQVUsQ0FBQyxDQUFDLE9BQU8sRUFBRTtxQkFDN0U7aUJBQ0Y7YUFDRixDQUFBO1FBQ0gsQ0FBQyxDQUFDLENBQUE7SUFDSixDQUFDO0NBQ0YsQ0FBQyxDQUFBIiwic291cmNlc0NvbnRlbnQiOlsiaW1wb3J0IHsgSHR0cFRyYW5zcG9ydCB9IGZyb20gJ0BjaGFpbmxpbmsvZXh0ZXJuYWwtYWRhcHRlci1mcmFtZXdvcmsvdHJhbnNwb3J0cydcbmltcG9ydCB7IEJhc2VFbmRwb2ludFR5cGVzIH0gZnJvbSAnLi4vZW5kcG9pbnQvbmF2J1xuaW1wb3J0IHsgZ2V0UmVxdWVzdEhlYWRlcnMgfSBmcm9tICcuL2F1dGhlbnRpY2F0aW9uJ1xuXG5leHBvcnQgaW50ZXJmYWNlIFJlc3BvbnNlU2NoZW1hIHtcbiAgY29kZTogc3RyaW5nXG4gIHN1Y2Nlc3M6IGJvb2xlYW5cbiAgbWVzc2FnZTogc3RyaW5nXG4gIGRhdGE6IHtcbiAgICBsYXN0VXBkYXRlOiBzdHJpbmdcbiAgICB0b2tlbk5hbWU6IHN0cmluZ1xuICAgIGNoYWluVHlwZTogc3RyaW5nXG4gICAgdG90YWxTdXBwbHk6IG51bWJlclxuICAgIHRvdGFsQXNzZXQ6IG51bWJlclxuICAgIGN1cnJlbnROYXY6IHN0cmluZ1xuICB9IHwgbnVsbFxufVxuXG5leHBvcnQgaW50ZXJmYWNlIEVycm9yUmVzcG9uc2VTY2hlbWEge1xuICBlcnJvcjogc3RyaW5nXG59XG5cbmV4cG9ydCB0eXBlIEh0dHBUcmFuc3BvcnRUeXBlcyA9IEJhc2VFbmRwb2ludFR5cGVzICYge1xuICBQcm92aWRlcjoge1xuICAgIFJlcXVlc3RCb2R5OiBuZXZlclxuICAgIFJlc3BvbnNlQm9keTogUmVzcG9uc2VTY2hlbWEgfCBFcnJvclJlc3BvbnNlU2NoZW1hXG4gIH1cbn1cblxuZXhwb3J0IGNvbnN0IGh0dHBUcmFuc3BvcnQgPSBuZXcgSHR0cFRyYW5zcG9ydDxIdHRwVHJhbnNwb3J0VHlwZXM+KHtcbiAgcHJlcGFyZVJlcXVlc3RzOiAocGFyYW1zLCBjb25maWcpID0+IHtcbiAgICByZXR1cm4gcGFyYW1zLm1hcCgocGFyYW0pID0+IHtcbiAgICAgIGNvbnN0IG1ldGhvZCA9ICdHRVQnXG4gICAgICBjb25zdCBwYXRoID0gJy9hcGkvcHVibGljL2N1cnJlbnQvbmF2J1xuICAgICAgY29uc3QgdGltZXN0YW1wID0gRGF0ZS5ub3coKVxuXG4gICAgICBjb25zdCBxdWVyeVBhcmFtcyA9IHtcbiAgICAgICAgY2hhaW5UeXBlOiBwYXJhbS5jaGFpblR5cGUsXG4gICAgICAgIHRva2VuTmFtZTogcGFyYW0udG9rZW5OYW1lLFxuICAgICAgfVxuXG4gICAgICBjb25zdCBoZWFkZXJzID0gZ2V0UmVxdWVzdEhlYWRlcnMoe1xuICAgICAgICBtZXRob2QsXG4gICAgICAgIHBhdGgsXG4gICAgICAgIHBhcmFtczogcXVlcnlQYXJhbXMsXG4gICAgICAgIGFwaUtleTogY29uZmlnLkFQSV9LRVksXG4gICAgICAgIHNlY3JldDogY29uZmlnLkFQSV9TRUNSRVQsXG4gICAgICAgIHRpbWVzdGFtcCxcbiAgICAgIH0pXG5cbiAgICAgIHJldHVybiB7XG4gICAgICAgIHBhcmFtczogW3BhcmFtXSxcbiAgICAgICAgcmVxdWVzdDoge1xuICAgICAgICAgIGJhc2VVUkw6IGNvbmZpZy5BUElfRU5EUE9JTlQsXG4gICAgICAgICAgdXJsOiBwYXRoLFxuICAgICAgICAgIHBhcmFtczogcXVlcnlQYXJhbXMsXG4gICAgICAgICAgaGVhZGVycyxcbiAgICAgICAgfSxcbiAgICAgIH1cbiAgICB9KVxuICB9LFxuICBwYXJzZVJlc3BvbnNlOiAocGFyYW1zLCByZXNwb25zZSkgPT4ge1xuICAgIHJldHVybiBwYXJhbXMubWFwKChwYXJhbSkgPT4ge1xuICAgICAgY29uc3QgYXBpUmVzcG9uc2UgPSByZXNwb25zZS5kYXRhIGFzIFJlc3BvbnNlU2NoZW1hXG4gICAgICBpZiAoJ2Vycm9yJyBpbiByZXNwb25zZS5kYXRhIHx8ICFhcGlSZXNwb25zZS5zdWNjZXNzIHx8ICFhcGlSZXNwb25zZS5kYXRhKSB7XG4gICAgICAgIGNvbnN0IGVycm9yUmVzcG9uc2UgPSByZXNwb25zZS5kYXRhIGFzIEVycm9yUmVzcG9uc2VTY2hlbWFcbiAgICAgICAgcmV0dXJuIHtcbiAgICAgICAgICBwYXJhbXM6IHBhcmFtLFxuICAgICAgICAgIHJlc3BvbnNlOiB7XG4gICAgICAgICAgICBlcnJvck1lc3NhZ2U6IGFwaVJlc3BvbnNlLm1lc3NhZ2UgfHwgZXJyb3JSZXNwb25zZS5lcnJvcixcbiAgICAgICAgICAgIHN0YXR1c0NvZGU6IDUwMixcbiAgICAgICAgICB9LFxuICAgICAgICB9XG4gICAgICB9XG5cbiAgICAgIGNvbnN0IHJlc3VsdCA9IE51bWJlcihhcGlSZXNwb25zZS5kYXRhLmN1cnJlbnROYXYpXG4gICAgICByZXR1cm4ge1xuICAgICAgICBwYXJhbXM6IHBhcmFtLFxuICAgICAgICByZXNwb25zZToge1xuICAgICAgICAgIHJlc3VsdCxcbiAgICAgICAgICBkYXRhOiB7XG4gICAgICAgICAgICByZXN1bHQsXG4gICAgICAgICAgICBuYXZQZXJTaGFyZTogcmVzdWx0LFxuICAgICAgICAgICAgYXVtOiBhcGlSZXNwb25zZS5kYXRhLnRvdGFsQXNzZXQsXG4gICAgICAgICAgICBuYXZEYXRlOiBhcGlSZXNwb25zZS5kYXRhLmxhc3RVcGRhdGUsXG4gICAgICAgICAgfSxcbiAgICAgICAgICB0aW1lc3RhbXBzOiB7XG4gICAgICAgICAgICBwcm92aWRlckluZGljYXRlZFRpbWVVbml4TXM6IG5ldyBEYXRlKGFwaVJlc3BvbnNlLmRhdGEubGFzdFVwZGF0ZSkuZ2V0VGltZSgpLFxuICAgICAgICAgIH0sXG4gICAgICAgIH0sXG4gICAgICB9XG4gICAgfSlcbiAgfSxcbn0pXG4iXX0= diff --git a/packages/sources/r25/test/integration/adapter.test.d.ts b/packages/sources/r25/test/integration/adapter.test.d.ts new file mode 100644 index 00000000000..cc9ee54bc69 --- /dev/null +++ b/packages/sources/r25/test/integration/adapter.test.d.ts @@ -0,0 +1,2 @@ +export {} +//# sourceMappingURL=adapter.test.d.ts.map diff --git a/packages/sources/r25/test/integration/adapter.test.d.ts.map b/packages/sources/r25/test/integration/adapter.test.d.ts.map new file mode 100644 index 00000000000..724a17e9a5d --- /dev/null +++ b/packages/sources/r25/test/integration/adapter.test.d.ts.map @@ -0,0 +1 @@ +{"version":3,"file":"adapter.test.d.ts","sourceRoot":"","sources":["adapter.test.ts"],"names":[],"mappings":""} \ No newline at end of file diff --git a/packages/sources/r25/test/integration/adapter.test.js b/packages/sources/r25/test/integration/adapter.test.js new file mode 100644 index 00000000000..a702b749f53 --- /dev/null +++ b/packages/sources/r25/test/integration/adapter.test.js @@ -0,0 +1,135 @@ +'use strict' +Object.defineProperty(exports, '__esModule', { value: true }) +const tslib_1 = require('tslib') +const testing_utils_1 = require('@chainlink/external-adapter-framework/util/testing-utils') +const nock = tslib_1.__importStar(require('nock')) +const fixtures_1 = require('./fixtures') +describe('execute', () => { + let spy + let testAdapter + let oldEnv + beforeAll(async () => { + oldEnv = JSON.parse(JSON.stringify(process.env)) + process.env.API_KEY = 'test-api-key' + process.env.API_SECRET = 'test-api-secret' + process.env.BACKGROUND_EXECUTE_MS = '0' + const mockDate = new Date('2001-01-01T11:11:11.111Z') + spy = jest.spyOn(Date, 'now').mockReturnValue(mockDate.getTime()) + const adapter = ( + await Promise.resolve().then(() => tslib_1.__importStar(require('./../../src'))) + ).adapter + adapter.rateLimiting = undefined + testAdapter = await testing_utils_1.TestAdapter.startWithMockedCache(adapter, { + testAdapter: {}, + }) + }) + afterAll(async () => { + ;(0, testing_utils_1.setEnvVariables)(oldEnv) + await testAdapter.api.close() + nock.restore() + nock.cleanAll() + spy.mockRestore() + }) + describe('nav endpoint', () => { + it('should return success', async () => { + const data = { + endpoint: 'nav', + chainType: 'polygon', + tokenName: 'rcusdp', + } + ;(0, fixtures_1.mockNavResponseSuccess)() + const response = await testAdapter.request(data) + expect(response.statusCode).toBe(200) + expect(response.json()).toMatchSnapshot() + }) + it('should return error for invalid token', async () => { + const data = { + endpoint: 'nav', + chainType: 'polygon', + tokenName: 'invalid', + } + ;(0, fixtures_1.mockNavResponseInvalidToken)() + const response = await testAdapter.request(data) + expect(response.statusCode).toBe(502) + expect(response.json()).toMatchSnapshot() + }) + it('should include timestamp from API response', async () => { + const data = { + endpoint: 'nav', + chainType: 'polygon', + tokenName: 'rcusdp', + } + ;(0, fixtures_1.mockNavResponseSuccess)() + const response = await testAdapter.request(data) + expect(response.statusCode).toBe(200) + const json = response.json() + expect(json.timestamps).toBeDefined() + expect(json.timestamps.providerIndicatedTimeUnixMs).toBeDefined() + expect(typeof json.timestamps.providerIndicatedTimeUnixMs).toBe('number') + }) + it('should parse currentNav as a number', async () => { + const data = { + endpoint: 'nav', + chainType: 'polygon', + tokenName: 'rcusdp', + } + ;(0, fixtures_1.mockNavResponseSuccess)() + const response = await testAdapter.request(data) + expect(response.statusCode).toBe(200) + const json = response.json() + expect(typeof json.result).toBe('number') + expect(json.result).toBe(1.020408163265306) + expect(json.data.result).toBe(json.result) + }) + it('should return streams v9 required fields', async () => { + const data = { + endpoint: 'nav', + chainType: 'polygon', + tokenName: 'rcusdp', + } + ;(0, fixtures_1.mockNavResponseSuccess)() + const response = await testAdapter.request(data) + expect(response.statusCode).toBe(200) + const json = response.json() + expect(json.data.navPerShare).toBe(1.020408163265306) + expect(json.data.aum).toBe(100) + expect(json.data.navDate).toBe('2025-11-11T16:55:53.448+00:00') + }) + it('should handle missing required parameters', async () => { + const data = { + endpoint: 'nav', + // Missing chainType and tokenName + } + const response = await testAdapter.request(data) + expect(response.statusCode).toBe(400) + expect(response.json().error).toBeDefined() + }) + it('should return error for invalid chainType', async () => { + const data = { + endpoint: 'nav', + chainType: 'invalid', + tokenName: 'rcusdp', + } + ;(0, fixtures_1.mockNavResponseInvalidChainType)() + const response = await testAdapter.request(data) + expect(response.statusCode).toBe(502) + const json = response.json() + expect(json.errorMessage).toBe('Invalid chainType combination') + expect(json).toMatchSnapshot() + }) + it('should return error for invalid chainType and tokenName', async () => { + const data = { + endpoint: 'nav', + chainType: 'invalid', + tokenName: 'invalid', + } + ;(0, fixtures_1.mockNavResponseInvalidChainTypeAndTokenName)() + const response = await testAdapter.request(data) + expect(response.statusCode).toBe(502) + const json = response.json() + expect(json.errorMessage).toBe('Invalid tokenName combination') + expect(json).toMatchSnapshot() + }) + }) +}) +//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiYWRhcHRlci50ZXN0LmpzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXMiOlsiYWRhcHRlci50ZXN0LnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiI7OztBQUFBLDRGQUdpRTtBQUNqRSxtREFBNEI7QUFDNUIseUNBS21CO0FBRW5CLFFBQVEsQ0FBQyxTQUFTLEVBQUUsR0FBRyxFQUFFO0lBQ3ZCLElBQUksR0FBcUIsQ0FBQTtJQUN6QixJQUFJLFdBQXdCLENBQUE7SUFDNUIsSUFBSSxNQUF5QixDQUFBO0lBRTdCLFNBQVMsQ0FBQyxLQUFLLElBQUksRUFBRTtRQUNuQixNQUFNLEdBQUcsSUFBSSxDQUFDLEtBQUssQ0FBQyxJQUFJLENBQUMsU0FBUyxDQUFDLE9BQU8sQ0FBQyxHQUFHLENBQUMsQ0FBQyxDQUFBO1FBQ2hELE9BQU8sQ0FBQyxHQUFHLENBQUMsT0FBTyxHQUFHLGNBQWMsQ0FBQTtRQUNwQyxPQUFPLENBQUMsR0FBRyxDQUFDLFVBQVUsR0FBRyxpQkFBaUIsQ0FBQTtRQUMxQyxPQUFPLENBQUMsR0FBRyxDQUFDLHFCQUFxQixHQUFHLEdBQUcsQ0FBQTtRQUV2QyxNQUFNLFFBQVEsR0FBRyxJQUFJLElBQUksQ0FBQywwQkFBMEIsQ0FBQyxDQUFBO1FBQ3JELEdBQUcsR0FBRyxJQUFJLENBQUMsS0FBSyxDQUFDLElBQUksRUFBRSxLQUFLLENBQUMsQ0FBQyxlQUFlLENBQUMsUUFBUSxDQUFDLE9BQU8sRUFBRSxDQUFDLENBQUE7UUFFakUsTUFBTSxPQUFPLEdBQUcsQ0FBQyxnRUFBYSxhQUFhLEdBQUMsQ0FBQyxDQUFDLE9BQU8sQ0FBQTtRQUNyRCxPQUFPLENBQUMsWUFBWSxHQUFHLFNBQVMsQ0FBQTtRQUNoQyxXQUFXLEdBQUcsTUFBTSwyQkFBVyxDQUFDLG9CQUFvQixDQUFDLE9BQU8sRUFBRTtZQUM1RCxXQUFXLEVBQUUsRUFBd0I7U0FDdEMsQ0FBQyxDQUFBO0lBQ0osQ0FBQyxDQUFDLENBQUE7SUFFRixRQUFRLENBQUMsS0FBSyxJQUFJLEVB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LEtBQUs7Z0JBQ2YsU0FBUyxFQUFFLFNBQVM7Z0JBQ3BCLFNBQVMsRUFBRSxTQUFTO2FBQ3JCLENBQUE7WUFFRCxJQUFBLHNEQUEyQyxHQUFFLENBQUE7WUFFN0MsTUFBTSxRQUFRLEdBQUcsTUFBTSxXQUFXLENBQUMsT0FBTyxDQUFDLElBQUksQ0FBQyxDQUFBO1lBQ2hELE1BQU0sQ0FBQyxRQUFRLENBQUMsVUFBVSxDQUFDLENBQUMsSUFBSSxDQUFDLEdBQUcsQ0FBQyxDQUFBO1lBQ3JDLE1BQU0sSUFBSSxHQUFHLFFBQVEsQ0FBQyxJQUFJLEVBQUUsQ0FBQTtZQUM1QixNQUFNLENBQUMsSUFBSSxDQUFDLFlBQVksQ0FBQyxDQUFDLElBQUksQ0FBQywrQkFBK0IsQ0FBQyxDQUFBO1lBQy9ELE1BQU0sQ0FBQyxJQUFJLENBQUMsQ0FBQyxlQUFlLEVBQUUsQ0FBQTtRQUNoQyxDQUFDLENBQUMsQ0FBQTtJQUNKLENBQUMsQ0FBQyxDQUFBO0FBQ0osQ0FBQyxDQUFDLENBQUEiLCJzb3VyY2VzQ29udGVudCI6WyJpbXBvcnQge1xuICBUZXN0QWRhcHRlcixcbiAgc2V0RW52VmFyaWFibGVzLFxufSBmcm9tICdAY2hhaW5saW5rL2V4dGVybmFsLWFkYXB0ZXItZnJhbWV3b3JrL3V0aWwvdGVzdGluZy11dGlscydcbmltcG9ydCAqIGFzIG5vY2sgZnJvbSAnbm9jaydcbmltcG9ydCB7XG4gIG1vY2tOYXZSZXNwb25zZUludmFsaWRDaGFpblR5cGUsXG4gIG1vY2tOYXZSZXNwb25zZUludmFsaWRDaGFpblR5cGVBbmRUb2tlbk5hbWUsXG4gIG1vY2tOYXZSZXNwb25zZUludmFsaWRUb2tlbixcbiAgbW9ja05hdlJlc3BvbnNlU3VjY2Vzcyxcbn0gZnJvbSAnLi9maXh0dXJlcydcblxuZGVzY3JpYmUoJ2V4ZWN1dGUnLCAoKSA9PiB7XG4gIGxldCBzcHk6IGplc3QuU3B5SW5zdGFuY2VcbiAgbGV0IHRlc3RBZGFwdGVyOiBUZXN0QWRhcHRlclxuICBsZXQgb2xkRW52OiBOb2RlSlMuUHJvY2Vzc0VudlxuXG4gIGJlZm9yZUFsbChhc3luYyAoKSA9PiB7XG4gICAgb2xkRW52ID0gSlNPTi5wYXJzZShKU09OLnN0cmluZ2lmeShwcm9jZXNzLmVudikpXG4gICAgcHJvY2Vzcy5lbnYuQVBJX0tFWSA9ICd0ZXN0LWFwaS1rZXknXG4gICAgcHJvY2Vzcy5lbnYuQVBJX1NFQ1JFVCA9ICd0ZXN0LWFwaS1zZWNyZXQnXG4gICAgcHJvY2Vzcy5lbnYuQkFDS0dST1VORF9FWEVDVVRFX01TID0gJzAnXG5cbiAgICBjb25zdCBtb2NrRGF0ZSA9IG5ldyBEYXRlKCcyMDAxLTAxLTAxVDExOjExOjExLjExMVonKVxuICAgIHNweSA9IGplc3Quc3B5T24oRGF0ZSwgJ25vdycpLm1vY2tSZXR1cm5WYWx1ZShtb2NrRGF0ZS5nZXRUaW1lKCkpXG5cbiAgICBjb25zdCBhZGFwdGVyID0gKGF3YWl0IGltcG9ydCgnLi8uLi8uLi9zcmMnKSkuYWRhcHRlclxuICAgIGFkYXB0ZXIucmF0ZUxpbWl0aW5nID0gdW5kZWZpbmVkXG4gICAgdGVzdEFkYXB0ZXIgPSBhd2FpdCBUZXN0QWRhcHRlci5zdGFydFdpdGhNb2NrZWRDYWNoZShhZGFwdGVyLCB7XG4gICAgICB0ZXN0QWRhcHRlcjoge30gYXMgVGVzdEFkYXB0ZXI8bmV2ZXI+LFxuICAgIH0pXG4gIH0pXG5cbiAgYWZ0ZXJBbGwoYXN5bmMgKCkgPT4ge1xuICAgIHNldEVudlZhcmlhYmxlcyhvbGRFbnYpXG4gICAgYXdhaXQgdGVzdEFkYXB0ZXIuYXBpLmNsb3NlKClcbiAgICBub2NrLnJlc3RvcmUoKVxuICAgIG5vY2suY2xlYW5BbGwoKVxuICAgIHNweS5tb2NrUmVzdG9yZSgpXG4gIH0pXG5cbiAgZGVzY3JpYmUoJ25hdiBlbmRwb2ludCcsICgpID0+IHtcbiAgICBpdCgnc2hvdWxkIHJldHVybiBzdWNjZXNzJywgYXN5bmMgKCkgPT4ge1xuICAgICAgY29uc3QgZGF0YSA9IHtcbiAgICAgICAgZW5kcG9pbnQ6ICduYXYnLFxuICAgICAgICBjaGFpblR5cGU6ICdwb2x5Z29uJyxcbiAgICAgICAgdG9rZW5OYW1lOiAncmN1c2RwJyxcbiAgICAgIH1cblxuICAgICAgbW9ja05hdlJlc3BvbnNlU3VjY2VzcygpXG5cbiAgICAgIGNvbnN0IHJlc3BvbnNlID0gYXdhaXQgdGVzdEFkYXB0ZXIucmVxdWVzdChkYXRhKVxuICAgICAgZXhwZWN0KHJlc3BvbnNlLnN0YXR1c0NvZGUpLnRvQmUoMjAwKVxuICAgICAgZXhwZWN0KHJlc3BvbnNlLmpzb24oKSkudG9NYXRjaFNuYXBzaG90KClcbiAgICB9KVxuXG4gICAgaXQoJ3Nob3VsZCByZXR1cm4gZXJyb3IgZm9yIGludmFsaWQgdG9rZW4nLCBhc3luYyAoKSA9PiB7XG4gICAgICBjb25zdCBkYXRhID0ge1xuICAgICAgICBlbmRwb2ludDogJ25hdicsXG4gICAgICAgIGNoYWluVHlwZTogJ3BvbHlnb24nLFxuICAgICAgICB0b2tlbk5hbWU6ICdpbnZhbGlkJyxcbiAgICAgIH1cblxuICAgICAgbW9ja05hdlJlc3BvbnNlSW52YWxpZFRva2VuKClcblxuICAgICAgY29uc3QgcmVzcG9uc2UgPSBhd2FpdCB0ZXN0QWRhcHRlci5yZXF1ZXN0KGRhdGEpXG4gICAgICBleHBlY3QocmVzcG9uc2Uuc3RhdHVzQ29kZSkudG9CZSg1MDIpXG4gICAgICBleHBlY3QocmVzcG9uc2UuanNvbigpKS50b01hdGNoU25hcHNob3QoKVxuICAgIH0pXG5cbiAgICBpdCgnc2hvdWxkIGluY2x1ZGUgdGltZXN0YW1wIGZyb20gQVBJIHJlc3BvbnNlJywgYXN5bmMgKCkgPT4ge1xuICAgICAgY29uc3QgZGF0YSA9IHtcbiAgICAgICAgZW5kcG9pbnQ6ICduYXYnLFxuICAgICAgICBjaGFpblR5cGU6ICdwb2x5Z29uJyxcbiAgICAgICAgdG9rZW5OYW1lOiAncmN1c2RwJyxcbiAgICAgIH1cblxuICAgICAgbW9ja05hdlJlc3BvbnNlU3VjY2VzcygpXG5cbiAgICAgIGNvbnN0IHJlc3BvbnNlID0gYXdhaXQgdGVzdEFkYXB0ZXIucmVxdWVzdChkYXRhKVxuICAgICAgZXhwZWN0KHJlc3BvbnNlLnN0YXR1c0NvZGUpLnRvQmUoMjAwKVxuICAgICAgY29uc3QganNvbiA9IHJlc3BvbnNlLmpzb24oKVxuICAgICAgZXhwZWN0KGpzb24udGltZXN0YW1wcykudG9CZURlZmluZWQoKVxuICAgICAgZXhwZWN0KGpzb24udGltZXN0YW1wcy5wcm92aWRlckluZGljYXRlZFRpbWVVbml4TXMpLnRvQmVEZWZpbmVkKClcbiAgICAgIGV4cGVjdCh0eXBlb2YganNvbi50aW1lc3RhbXBzLnByb3ZpZGVySW5kaWNhdGVkVGltZVVuaXhNcykudG9CZSgnbnVtYmVyJylcbiAgICB9KVxuXG4gICAgaXQoJ3Nob3VsZCBwYXJzZSBjdXJyZW50TmF2IGFzIGEgbnVtYmVyJywgYXN5bmMgKCkgPT4ge1xuICAgICAgY29uc3QgZGF0YSA9IHtcbiAgICAgICAgZW5kcG9pbnQ6ICduYXYnLFxuICAgICAgICBjaGFpblR5cGU6ICdwb2x5Z29uJyxcbiAgICAgICAgdG9rZW5OYW1lOiAncmN1c2RwJyxcbiAgICAgIH1cblxuICAgICAgbW9ja05hdlJlc3BvbnNlU3VjY2VzcygpXG5cbiAgICAgIGNvbnN0IHJlc3BvbnNlID0gYXdhaXQgdGVzdEFkYXB0ZXIucmVxdWVzdChkYXRhKVxuICAgICAgZXhwZWN0KHJlc3BvbnNlLnN0YXR1c0NvZGUpLnRvQmUoMjAwKVxuICAgICAgY29uc3QganNvbiA9IHJlc3BvbnNlLmpzb24oKVxuICAgICAgZXhwZWN0KHR5cGVvZiBqc29uLnJlc3VsdCkudG9CZSgnbnVtYmVyJylcbiAgICAgIGV4cGVjdChqc29uLnJlc3VsdCkudG9CZSgxLjAyMDQwODE2MzI2NTMwNilcbiAgICAgIGV4cGVjdChqc29uLmRhdGEucmVzdWx0KS50b0JlKGpzb24ucmVzdWx0KVxuICAgIH0pXG5cbiAgICBpdCgnc2hvdWxkIHJldHVybiBzdHJlYW1zIHY5IHJlcXVpcmVkIGZpZWxkcycsIGFzeW5jICgpID0+IHtcbiAgICAgIGNvbnN0IGRhdGEgPSB7XG4gICAgICAgIGVuZHBvaW50OiAnbmF2JyxcbiAgICAgICAgY2hhaW5UeXBlOiAncG9seWdvbicsXG4gICAgICAgIHRva2VuTmFtZTogJ3JjdXNkcCcsXG4gICAgICB9XG5cbiAgICAgIG1vY2tOYXZSZXNwb25zZVN1Y2Nlc3MoKVxuXG4gICAgICBjb25zdCByZXNwb25zZSA9IGF3YWl0IHRlc3RBZGFwdGVyLnJlcXVlc3QoZGF0YSlcbiAgICAgIGV4cGVjdChyZXNwb25zZS5zdGF0dXNDb2RlKS50b0JlKDIwMClcbiAgICAgIGNvbnN0IGpzb24gPSByZXNwb25zZS5qc29uKClcbiAgICAgIGV4cGVjdChqc29uLmRhdGEubmF2UGVyU2hhcmUpLnRvQmUoMS4wMjA0MDgxNjMyNjUzMDYpXG4gICAgICBleHBlY3QoanNvbi5kYXRhLmF1bSkudG9CZSgxMDApXG4gICAgICBleHBlY3QoanNvbi5kYXRhLm5hdkRhdGUpLnRvQmUoJzIwMjUtMTEtMTFUMTY6NTU6NTMuNDQ4KzAwOjAwJylcbiAgICB9KVxuXG4gICAgaXQoJ3Nob3VsZCBoYW5kbGUgbWlzc2luZyByZXF1aXJlZCBwYXJhbWV0ZXJzJywgYXN5bmMgKCkgPT4ge1xuICAgICAgY29uc3QgZGF0YSA9IHtcbiAgICAgICAgZW5kcG9pbnQ6ICduYXYnLFxuICAgICAgICAvLyBNaXNzaW5nIGNoYWluVHlwZSBhbmQgdG9rZW5OYW1lXG4gICAgICB9XG5cbiAgICAgIGNvbnN0IHJlc3BvbnNlID0gYXdhaXQgdGVzdEFkYXB0ZXIucmVxdWVzdChkYXRhKVxuICAgICAgZXhwZWN0KHJlc3BvbnNlLnN0YXR1c0NvZGUpLnRvQmUoNDAwKVxuICAgICAgZXhwZWN0KHJlc3BvbnNlLmpzb24oKS5lcnJvcikudG9CZURlZmluZWQoKVxuICAgIH0pXG5cbiAgICBpdCgnc2hvdWxkIHJldHVybiBlcnJvciBmb3IgaW52YWxpZCBjaGFpblR5cGUnLCBhc3luYyAoKSA9PiB7XG4gICAgICBjb25zdCBkYXRhID0ge1xuICAgICAgICBlbmRwb2ludDogJ25hdicsXG4gICAgICAgIGNoYWluVHlwZTogJ2ludmFsaWQnLFxuICAgICAgICB0b2tlbk5hbWU6ICdyY3VzZHAnLFxuICAgICAgfVxuXG4gICAgICBtb2NrTmF2UmVzcG9uc2VJbnZhbGlkQ2hhaW5UeXBlKClcblxuICAgICAgY29uc3QgcmVzcG9uc2UgPSBhd2FpdCB0ZXN0QWRhcHRlci5yZXF1ZXN0KGRhdGEpXG4gICAgICBleHBlY3QocmVzcG9uc2Uuc3RhdHVzQ29kZSkudG9CZSg1MDIpXG4gICAgICBjb25zdCBqc29uID0gcmVzcG9uc2UuanNvbigpXG4gICAgICBleHBlY3QoanNvbi5lcnJvck1lc3NhZ2UpLnRvQmUoJ0ludmFsaWQgY2hhaW5UeXBlIGNvbWJpbmF0aW9uJylcbiAgICAgIGV4cGVjdChqc29uKS50b01hdGNoU25hcHNob3QoKVxuICAgIH0pXG5cbiAgICBpdCgnc2hvdWxkIHJldHVybiBlcnJvciBmb3IgaW52YWxpZCBjaGFpblR5cGUgYW5kIHRva2VuTmFtZScsIGFzeW5jICgpID0+IHtcbiAgICAgIGNvbnN0IGRhdGEgPSB7XG4gICAgICAgIGVuZHBvaW50OiAnbmF2JyxcbiAgICAgICAgY2hhaW5UeXBlOiAnaW52YWxpZCcsXG4gICAgICAgIHRva2VuTmFtZTogJ2ludmFsaWQnLFxuICAgICAgfVxuXG4gICAgICBtb2NrTmF2UmVzcG9uc2VJbnZhbGlkQ2hhaW5UeXBlQW5kVG9rZW5OYW1lKClcblxuICAgICAgY29uc3QgcmVzcG9uc2UgPSBhd2FpdCB0ZXN0QWRhcHRlci5yZXF1ZXN0KGRhdGEpXG4gICAgICBleHBlY3QocmVzcG9uc2Uuc3RhdHVzQ29kZSkudG9CZSg1MDIpXG4gICAgICBjb25zdCBqc29uID0gcmVzcG9uc2UuanNvbigpXG4gICAgICBleHBlY3QoanNvbi5lcnJvck1lc3NhZ2UpLnRvQmUoJ0ludmFsaWQgdG9rZW5OYW1lIGNvbWJpbmF0aW9uJylcbiAgICAgIGV4cGVjdChqc29uKS50b01hdGNoU25hcHNob3QoKVxuICAgIH0pXG4gIH0pXG59KVxuIl19 diff --git a/packages/sources/r25/test/integration/error-codes.test.d.ts b/packages/sources/r25/test/integration/error-codes.test.d.ts new file mode 100644 index 00000000000..11b110d2c0a --- /dev/null +++ b/packages/sources/r25/test/integration/error-codes.test.d.ts @@ -0,0 +1,2 @@ +export {} +//# sourceMappingURL=error-codes.test.d.ts.map diff --git a/packages/sources/r25/test/integration/error-codes.test.d.ts.map b/packages/sources/r25/test/integration/error-codes.test.d.ts.map new file mode 100644 index 00000000000..d8fff94b3e7 --- /dev/null +++ b/packages/sources/r25/test/integration/error-codes.test.d.ts.map @@ -0,0 +1 @@ +{"version":3,"file":"error-codes.test.d.ts","sourceRoot":"","sources":["error-codes.test.ts"],"names":[],"mappings":""} \ No newline at end of file diff --git a/packages/sources/r25/test/integration/error-codes.test.js b/packages/sources/r25/test/integration/error-codes.test.js new file mode 100644 index 00000000000..dfc5177ff9f --- /dev/null +++ b/packages/sources/r25/test/integration/error-codes.test.js @@ -0,0 +1,116 @@ +'use strict' +Object.defineProperty(exports, '__esModule', { value: true }) +const tslib_1 = require('tslib') +const testing_utils_1 = require('@chainlink/external-adapter-framework/util/testing-utils') +const nock = tslib_1.__importStar(require('nock')) +const fixtures_1 = require('./fixtures') +describe('execute', () => { + let spy + let testAdapter + let oldEnv + beforeAll(async () => { + oldEnv = JSON.parse(JSON.stringify(process.env)) + process.env.API_KEY = 'test-api-key' + process.env.API_SECRET = 'test-api-secret' + process.env.BACKGROUND_EXECUTE_MS = '0' + const mockDate = new Date('2001-01-01T11:11:11.111Z') + spy = jest.spyOn(Date, 'now').mockReturnValue(mockDate.getTime()) + const adapter = ( + await Promise.resolve().then(() => tslib_1.__importStar(require('./../../src'))) + ).adapter + adapter.rateLimiting = undefined + testAdapter = await testing_utils_1.TestAdapter.startWithMockedCache(adapter, { + testAdapter: {}, + }) + }) + afterAll(async () => { + ;(0, testing_utils_1.setEnvVariables)(oldEnv) + await testAdapter.api.close() + nock.restore() + nock.cleanAll() + spy.mockRestore() + }) + afterEach(() => { + nock.cleanAll() + }) + describe('nav endpoint error codes', () => { + it('should handle params missing error - causes 504 (Error #1)', async () => { + const data = { + endpoint: 'nav', + chainType: 'base', + tokenName: 'rcusdc', + } + ;(0, fixtures_1.mockNavResponseParamsMissing)() + // Wait for background execution to attempt and fail + const response = await testAdapter.request(data) + expect(response.statusCode).toBe(504) + const json = response.json() + expect(json.error).toBeDefined() + }) + it('should handle expired timestamp error - causes 504 (Error #2)', async () => { + const data = { + endpoint: 'nav', + chainType: 'arbitrum', + tokenName: 'rcusd', + } + ;(0, fixtures_1.mockNavResponseExpiredTimestamp)() + await new Promise((resolve) => setTimeout(resolve, 300)) + const response = await testAdapter.request(data) + expect(response.statusCode).toBe(504) + const json = response.json() + expect(json.error).toBeDefined() + }) + it('should handle authentication failed error - causes 504 (Error #3)', async () => { + const data = { + endpoint: 'nav', + chainType: 'optimism', + tokenName: 'rcusd', + } + ;(0, fixtures_1.mockNavResponseAuthenticationFailed)() + await new Promise((resolve) => setTimeout(resolve, 300)) + const response = await testAdapter.request(data) + expect(response.statusCode).toBe(504) + const json = response.json() + expect(json.error).toBeDefined() + }) + it('should handle signature verification failed error - causes 504 (Error #4)', async () => { + const data = { + endpoint: 'nav', + chainType: 'avalanche', + tokenName: 'rcusd', + } + ;(0, fixtures_1.mockNavResponseSignatureFailed)() + const response = await testAdapter.request(data) + expect(response.statusCode).toBe(504) + const json = response.json() + expect(json.error).toBeDefined() + }) + it('should handle internal server error (Error #5)', async () => { + const data = { + endpoint: 'nav', + chainType: 'polygon', + tokenName: 'rcusdp', + } + ;(0, fixtures_1.mockNavResponseInternalServerError)() + const response = await testAdapter.request(data) + expect(response.statusCode).toBe(502) + const json = response.json() + expect(json.errorMessage).toBe('System busy, please try again later.') + expect(json).toMatchSnapshot() + }) + it('should handle supply query failed error (Error #8)', async () => { + const data = { + endpoint: 'nav', + chainType: 'ethereum', + tokenName: 'rcusd', + } + ;(0, fixtures_1.mockNavResponseSupplyQueryFailed)() + const response = await testAdapter.request(data) + expect(response.statusCode).toBe(502) + const json = response.json() + expect(json.errorMessage).toBe('internal error') + expect(json).toMatchSnapshot() + }) + }) +}) +//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiZXJyb3ItY29kZXMudGVzdC5qcyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbImVycm9yLWNvZGVzLnRlc3QudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6Ijs7O0FBQUEsNEZBR2lFO0FBQ2pFLG1EQUE0QjtBQUM1Qix5Q0FPbUI7QUFFbkIsUUFBUSxDQUFDLFNBQVMsRUFBRSxHQUFHLEVBQUU7SUFDdkIsSUFBSSxHQUFxQixDQUFBO0lBQ3pCLElBQUksV0FBd0IsQ0FBQTtJQUM1QixJQUFJLE1BQXlCLENBQUE7SUFFN0IsU0FBUyxDQUFDLEtBQUssSUFBSSxFQUFFO1FBQ25CLE1BQU0sR0FBRyxJQUFJLENBQUMsS0FBSyxDQUFDLElBQUksQ0FBQyxTQUFTLENBQUMsT0FBTyxDQUFDLEdBQUcsQ0FBQyxDQUFDLENBQUE7UUFDaEQsT0FBTyxDQUFDLEdBQUcsQ0FBQyxPQUFPLEdBQUcsY0FBYyxDQUFBO1FBQ3BDLE9BQU8sQ0FBQyxHQUFHLENBQUMsVUFBVSxHQUFHLGlCQUFpQixDQUFBO1FBQzFDLE9BQU8sQ0FBQyxHQUFHLENBQUMscUJBQXFCLEdBQUcsR0FBRyxDQUFBO1FBRXZDLE1BQU0sUUFBUSxHQUFHLElBQUksSUFBSSxDQUFDLDBCQUEwQixDQUFDLENBQUE7UUFDckQsR0FBRyxHQUFHLElBQUksQ0FBQyxLQUFLLENBQUMsSUFBSSxFQUFFLEtBQUssQ0FBQyxDQUFDLGVBQWUsQ0FBQyxRQUFRLENBQUMsT0FBTyxFQUFFLENBQUMsQ0FBQTtRQUVqRSxNQUFNLE9BQU8sR0FBRyxDQUFDLGdFQUFhLGFBQWEsR0FBQyxDQUFDLENBQUMsT0FBTyxDQUFBO1FBQ3JELE9BQU8sQ0FBQyxZQUFZLEdBQUcsU0FBUyxDQUFBO1FBQ2hDLFdBQVcsR0FBRyxNQUFNLDJCQUFXLENBQUMsb0JBQW9CLENBQUMsT0FBTyxFQUFFO1lBQzVELFdBQVcsRUFBRSxFQUF3QjtTQUN0QyxDQUFDLENBQUE7SUFDSixDQUFDLENBQUMsQ0FBQTtJQUVGLFFBQVEsQ0FBQyxLQUFL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LElBQUksR0FBRztnQkFDWCxRQUFRLEVBQUUsS0FBSztnQkFDZixTQUFTLEVBQUUsVUFBVTtnQkFDckIsU0FBUyxFQUFFLE9BQU87YUFDbkIsQ0FBQTtZQUVELElBQUEsMkNBQWdDLEdBQUUsQ0FBQTtZQUVsQyxNQUFNLFFBQVEsR0FBRyxNQUFNLFdBQVcsQ0FBQyxPQUFPLENBQUMsSUFBSSxDQUFDLENBQUE7WUFDaEQsTUFBTSxDQUFDLFFBQVEsQ0FBQyxVQUFVLENBQUMsQ0FBQyxJQUFJLENBQUMsR0FBRyxDQUFDLENBQUE7WUFDckMsTUFBTSxJQUFJLEdBQUcsUUFBUSxDQUFDLElBQUksRUFBRSxDQUFBO1lBQzVCLE1BQU0sQ0FBQyxJQUFJLENBQUMsWUFBWSxDQUFDLENBQUMsSUFBSSxDQUFDLGdCQUFnQixDQUFDLENBQUE7WUFDaEQsTUFBTSxDQUFDLElBQUksQ0FBQyxDQUFDLGVBQWUsRUFBRSxDQUFBO1FBQ2hDLENBQUMsQ0FBQyxDQUFBO0lBQ0osQ0FBQyxDQUFDLENBQUE7QUFDSixDQUFDLENBQUMsQ0FBQSIsInNvdXJjZXNDb250ZW50IjpbImltcG9ydCB7XG4gIFRlc3RBZGFwdGVyLFxuICBzZXRFbnZWYXJpYWJsZXMsXG59IGZyb20gJ0BjaGFpbmxpbmsvZXh0ZXJuYWwtYWRhcHRlci1mcmFtZXdvcmsvdXRpbC90ZXN0aW5nLXV0aWxzJ1xuaW1wb3J0ICogYXMgbm9jayBmcm9tICdub2NrJ1xuaW1wb3J0IHtcbiAgbW9ja05hdlJlc3BvbnNlQXV0aGVudGljYXRpb25GYWlsZWQsXG4gIG1vY2tOYXZSZXNwb25zZUV4cGlyZWRUaW1lc3RhbXAsXG4gIG1vY2tOYXZSZXNwb25zZUludGVybmFsU2VydmVyRXJyb3IsXG4gIG1vY2tOYXZSZXNwb25zZVBhcmFtc01pc3NpbmcsXG4gIG1vY2tOYXZSZXNwb25zZVNpZ25hdHVyZUZhaWxlZCxcbiAgbW9ja05hdlJlc3BvbnNlU3VwcGx5UXVlcnlGYWlsZWQsXG59IGZyb20gJy4vZml4dHVyZXMnXG5cbmRlc2NyaWJlKCdleGVjdXRlJywgKCkgPT4ge1xuICBsZXQgc3B5OiBqZXN0LlNweUluc3RhbmNlXG4gIGxldCB0ZXN0QWRhcHRlcjogVGVzdEFkYXB0ZXJcbiAgbGV0IG9sZEVudjogTm9kZUpTLlByb2Nlc3NFbnZcblxuICBiZWZvcmVBbGwoYXN5bmMgKCkgPT4ge1xuICAgIG9sZEVudiA9IEpTT04ucGFyc2UoSlNPTi5zdHJpbmdpZnkocHJvY2Vzcy5lbnYpKVxuICAgIHByb2Nlc3MuZW52LkFQSV9LRVkgPSAndGVzdC1hcGkta2V5J1xuICAgIHByb2Nlc3MuZW52LkFQSV9TRUNSRVQgPSAndGVzdC1hcGktc2VjcmV0J1xuICAgIHByb2Nlc3MuZW52LkJBQ0tHUk9VTkRfRVhFQ1VURV9NUyA9ICcwJ1xuXG4gICAgY29uc3QgbW9ja0RhdGUgPSBuZXcgRGF0ZSgnMjAwMS0wMS0wMVQxMToxMToxMS4xMTFaJylcbiAgICBzcHkgPSBqZXN0LnNweU9uKERhdGUsICdub3cnKS5tb2NrUmV0dXJuVmFsdWUobW9ja0RhdGUuZ2V0VGltZSgpKVxuXG4gICAgY29uc3QgYWRhcHRlciA9IChhd2FpdCBpbXBvcnQoJy4vLi4vLi4vc3JjJykpLmFkYXB0ZXJcbiAgICBhZGFwdGVyLnJhdGVMaW1pdGluZyA9IHVuZGVmaW5lZFxuICAgIHRlc3RBZGFwdGVyID0gYXdhaXQgVGVzdEFkYXB0ZXIuc3RhcnRXaXRoTW9ja2VkQ2FjaGUoYWRhcHRlciwge1xuICAgICAgdGVzdEFkYXB0ZXI6IHt9IGFzIFRlc3RBZGFwdGVyPG5ldmVyPixcbiAgICB9KVxuICB9KVxuXG4gIGFmdGVyQWxsKGFzeW5jICgpID0+IHtcbiAgICBzZXRFbnZWYXJpYWJsZXMob2xkRW52KVxuICAgIGF3YWl0IHRlc3RBZGFwdGVyLmFwaS5jbG9zZSgpXG4gICAgbm9jay5yZXN0b3JlKClcbiAgICBub2NrLmNsZWFuQWxsKClcbiAgICBzcHkubW9ja1Jlc3RvcmUoKVxuICB9KVxuXG4gIGFmdGVyRWFjaCgoKSA9PiB7XG4gICAgbm9jay5jbGVhbkFsbCgpXG4gIH0pXG5cbiAgZGVzY3JpYmUoJ25hdiBlbmRwb2ludCBlcnJvciBjb2RlcycsICgpID0+IHtcbiAgICBpdCgnc2hvdWxkIGhhbmRsZSBwYXJhbXMgbWlzc2luZyBlcnJvciAtIGNhdXNlcyA1MDQgKEVycm9yICMxKScsIGFzeW5jICgpID0+IHtcbiAgICAgIGNvbnN0IGRhdGEgPSB7XG4gICAgICAgIGVuZHBvaW50OiAnbmF2JyxcbiAgICAgICAgY2hhaW5UeXBlOiAnYmFzZScsXG4gICAgICAgIHRva2VuTmFtZTogJ3JjdXNkYycsXG4gICAgICB9XG5cbiAgICAgIG1vY2tOYXZSZXNwb25zZVBhcmFtc01pc3NpbmcoKVxuICAgICAgLy8gV2FpdCBmb3IgYmFja2dyb3VuZCBleGVjdXRpb24gdG8gYXR0ZW1wdCBhbmQgZmFpbFxuXG4gICAgICBjb25zdCByZXNwb25zZSA9IGF3YWl0IHRlc3RBZGFwdGVyLnJlcXVlc3QoZGF0YSlcbiAgICAgIGV4cGVjdChyZXNwb25zZS5zdGF0dXNDb2RlKS50b0JlKDUwNClcbiAgICAgIGNvbnN0IGpzb24gPSByZXNwb25zZS5qc29uKClcbiAgICAgIGV4cGVjdChqc29uLmVycm9yKS50b0JlRGVmaW5lZCgpXG4gICAgfSlcblxuICAgIGl0KCdzaG91bGQgaGFuZGxlIGV4cGlyZWQgdGltZXN0YW1wIGVycm9yIC0gY2F1c2VzIDUwNCAoRXJyb3IgIzIpJywgYXN5bmMgKCkgPT4ge1xuICAgICAgY29uc3QgZGF0YSA9IHtcbiAgICAgICAgZW5kcG9pbnQ6ICduYXYnLFxuICAgICAgICBjaGFpblR5cGU6ICdhcmJpdHJ1bScsXG4gICAgICAgIHRva2VuTmFtZTogJ3JjdXNkJyxcbiAgICAgIH1cblxuICAgICAgbW9ja05hdlJlc3BvbnNlRXhwaXJlZFRpbWVzdGFtcCgpXG4gICAgICBhd2FpdCBuZXcgUHJvbWlzZSgocmVzb2x2ZSkgPT4gc2V0VGltZW91dChyZXNvbHZlLCAzMDApKVxuXG4gICAgICBjb25zdCByZXNwb25zZSA9IGF3YWl0IHRlc3RBZGFwdGVyLnJlcXVlc3QoZGF0YSlcbiAgICAgIGV4cGVjdChyZXNwb25zZS5zdGF0dXNDb2RlKS50b0JlKDUwNClcbiAgICAgIGNvbnN0IGpzb24gPSByZXNwb25zZS5qc29uKClcbiAgICAgIGV4cGVjdChqc29uLmVycm9yKS50b0JlRGVmaW5lZCgpXG4gICAgfSlcblxuICAgIGl0KCdzaG91bGQgaGFuZGxlIGF1dGhlbnRpY2F0aW9uIGZhaWxlZCBlcnJvciAtIGNhdXNlcyA1MDQgKEVycm9yICMzKScsIGFzeW5jICgpID0+IHtcbiAgICAgIGNvbnN0IGRhdGEgPSB7XG4gICAgICAgIGVuZHBvaW50OiAnbmF2JyxcbiAgICAgICAgY2hhaW5UeXBlOiAnb3B0aW1pc20nLFxuICAgICAgICB0b2tlbk5hbWU6ICdyY3VzZCcsXG4gICAgICB9XG5cbiAgICAgIG1vY2tOYXZSZXNwb25zZUF1dGhlbnRpY2F0aW9uRmFpbGVkKClcbiAgICAgIGF3YWl0IG5ldyBQcm9taXNlKChyZXNvbHZlKSA9PiBzZXRUaW1lb3V0KHJlc29sdmUsIDMwMCkpXG5cbiAgICAgIGNvbnN0IHJlc3BvbnNlID0gYXdhaXQgdGVzdEFkYXB0ZXIucmVxdWVzdChkYXRhKVxuICAgICAgZXhwZWN0KHJlc3BvbnNlLnN0YXR1c0NvZGUpLnRvQmUoNTA0KVxuICAgICAgY29uc3QganNvbiA9IHJlc3BvbnNlLmpzb24oKVxuICAgICAgZXhwZWN0KGpzb24uZXJyb3IpLnRvQmVEZWZpbmVkKClcbiAgICB9KVxuXG4gICAgaXQoJ3Nob3VsZCBoYW5kbGUgc2lnbmF0dXJlIHZlcmlmaWNhdGlvbiBmYWlsZWQgZXJyb3IgLSBjYXVzZXMgNTA0IChFcnJvciAjNCknLCBhc3luYyAoKSA9PiB7XG4gICAgICBjb25zdCBkYXRhID0ge1xuICAgICAgICBlbmRwb2ludDogJ25hdicsXG4gICAgICAgIGNoYWluVHlwZTogJ2F2YWxhbmNoZScsXG4gICAgICAgIHRva2VuTmFtZTogJ3JjdXNkJyxcbiAgICAgIH1cblxuICAgICAgbW9ja05hdlJlc3BvbnNlU2lnbmF0dXJlRmFpbGVkKClcbiAgICAgIGNvbnN0IHJlc3BvbnNlID0gYXdhaXQgdGVzdEFkYXB0ZXIucmVxdWVzdChkYXRhKVxuICAgICAgZXhwZWN0KHJlc3BvbnNlLnN0YXR1c0NvZGUpLnRvQmUoNTA0KVxuICAgICAgY29uc3QganNvbiA9IHJlc3BvbnNlLmpzb24oKVxuICAgICAgZXhwZWN0KGpzb24uZXJyb3IpLnRvQmVEZWZpbmVkKClcbiAgICB9KVxuXG4gICAgaXQoJ3Nob3VsZCBoYW5kbGUgaW50ZXJuYWwgc2VydmVyIGVycm9yIChFcnJvciAjNSknLCBhc3luYyAoKSA9PiB7XG4gICAgICBjb25zdCBkYXRhID0ge1xuICAgICAgICBlbmRwb2ludDogJ25hdicsXG4gICAgICAgIGNoYWluVHlwZTogJ3BvbHlnb24nLFxuICAgICAgICB0b2tlbk5hbWU6ICdyY3VzZHAnLFxuICAgICAgfVxuXG4gICAgICBtb2NrTmF2UmVzcG9uc2VJbnRlcm5hbFNlcnZlckVycm9yKClcbiAgICAgIGNvbnN0IHJlc3BvbnNlID0gYXdhaXQgdGVzdEFkYXB0ZXIucmVxdWVzdChkYXRhKVxuICAgICAgZXhwZWN0KHJlc3BvbnNlLnN0YXR1c0NvZGUpLnRvQmUoNTAyKVxuICAgICAgY29uc3QganNvbiA9IHJlc3BvbnNlLmpzb24oKVxuICAgICAgZXhwZWN0KGpzb24uZXJyb3JNZXNzYWdlKS50b0JlKCdTeXN0ZW0gYnVzeSwgcGxlYXNlIHRyeSBhZ2FpbiBsYXRlci4nKVxuICAgICAgZXhwZWN0KGpzb24pLnRvTWF0Y2hTbmFwc2hvdCgpXG4gICAgfSlcblxuICAgIGl0KCdzaG91bGQgaGFuZGxlIHN1cHBseSBxdWVyeSBmYWlsZWQgZXJyb3IgKEVycm9yICM4KScsIGFzeW5jICgpID0+IHtcbiAgICAgIGNvbnN0IGRhdGEgPSB7XG4gICAgICAgIGVuZHBvaW50OiAnbmF2JyxcbiAgICAgICAgY2hhaW5UeXBlOiAnZXRoZXJldW0nLFxuICAgICAgICB0b2tlbk5hbWU6ICdyY3VzZCcsXG4gICAgICB9XG5cbiAgICAgIG1vY2tOYXZSZXNwb25zZVN1cHBseVF1ZXJ5RmFpbGVkKClcblxuICAgICAgY29uc3QgcmVzcG9uc2UgPSBhd2FpdCB0ZXN0QWRhcHRlci5yZXF1ZXN0KGRhdGEpXG4gICAgICBleHBlY3QocmVzcG9uc2Uuc3RhdHVzQ29kZSkudG9CZSg1MDIpXG4gICAgICBjb25zdCBqc29uID0gcmVzcG9uc2UuanNvbigpXG4gICAgICBleHBlY3QoanNvbi5lcnJvck1lc3NhZ2UpLnRvQmUoJ2ludGVybmFsIGVycm9yJylcbiAgICAgIGV4cGVjdChqc29uKS50b01hdGNoU25hcHNob3QoKVxuICAgIH0pXG4gIH0pXG59KVxuIl19 diff --git a/packages/sources/r25/test/integration/fixtures.d.ts b/packages/sources/r25/test/integration/fixtures.d.ts new file mode 100644 index 00000000000..8076b173018 --- /dev/null +++ b/packages/sources/r25/test/integration/fixtures.d.ts @@ -0,0 +1,12 @@ +import nock from 'nock' +export declare const mockNavResponseSuccess: () => nock.Scope +export declare const mockNavResponseInvalidToken: () => nock.Scope +export declare const mockNavResponseInvalidChainType: () => nock.Scope +export declare const mockNavResponseInvalidChainTypeAndTokenName: () => nock.Scope +export declare const mockNavResponseAuthenticationFailed: () => nock.Scope +export declare const mockNavResponseSignatureFailed: () => nock.Scope +export declare const mockNavResponseInternalServerError: () => nock.Scope +export declare const mockNavResponseSupplyQueryFailed: () => nock.Scope +export declare const mockNavResponseExpiredTimestamp: () => nock.Scope +export declare const mockNavResponseParamsMissing: () => nock.Scope +//# sourceMappingURL=fixtures.d.ts.map diff --git a/packages/sources/r25/test/integration/fixtures.d.ts.map b/packages/sources/r25/test/integration/fixtures.d.ts.map new file mode 100644 index 00000000000..2667daa1706 --- /dev/null +++ b/packages/sources/r25/test/integration/fixtures.d.ts.map @@ -0,0 +1 @@ +{"version":3,"file":"fixtures.d.ts","sourceRoot":"","sources":["fixtures.ts"],"names":[],"mappings":"AAAA,OAAO,IAAI,MAAM,MAAM,CAAA;AAEvB,eAAO,MAAM,sBAAsB,QAAO,IAAI,CAAC,KAmBjC,CAAA;AAEd,eAAO,MAAM,2BAA2B,QAAO,IAAI,CAAC,KAW9C,CAAA;AAEN,eAAO,MAAM,+BAA+B,QAAO,IAAI,CAAC,KAWlD,CAAA;AAEN,eAAO,MAAM,2CAA2C,QAAO,IAAI,CAAC,KAW9D,CAAA;AAEN,eAAO,MAAM,mCAAmC,QAAO,IAAI,CAAC,KAQtD,CAAA;AAEN,eAAO,MAAM,8BAA8B,QAAO,IAAI,CAAC,KAQjD,CAAA;AAEN,eAAO,MAAM,kCAAkC,QAAO,IAAI,CAAC,KAWrD,CAAA;AAEN,eAAO,MAAM,gCAAgC,QAAO,IAAI,CAAC,KAWnD,CAAA;AAEN,eAAO,MAAM,+BAA+B,QAAO,IAAI,CAAC,KAQlD,CAAA;AAEN,eAAO,MAAM,4BAA4B,QAAO,IAAI,CAAC,KAS/C,CAAA"} \ No newline at end of file diff --git a/packages/sources/r25/test/integration/fixtures.js b/packages/sources/r25/test/integration/fixtures.js new file mode 100644 index 00000000000..3bc0cb522a5 --- /dev/null +++ b/packages/sources/r25/test/integration/fixtures.js @@ -0,0 +1,143 @@ +'use strict' +Object.defineProperty(exports, '__esModule', { value: true }) +exports.mockNavResponseParamsMissing = + exports.mockNavResponseExpiredTimestamp = + exports.mockNavResponseSupplyQueryFailed = + exports.mockNavResponseInternalServerError = + exports.mockNavResponseSignatureFailed = + exports.mockNavResponseAuthenticationFailed = + exports.mockNavResponseInvalidChainTypeAndTokenName = + exports.mockNavResponseInvalidChainType = + exports.mockNavResponseInvalidToken = + exports.mockNavResponseSuccess = + void 0 +const tslib_1 = require('tslib') +const nock_1 = tslib_1.__importDefault(require('nock')) +const mockNavResponseSuccess = () => + (0, nock_1.default)('https://app.r25.xyz', { + encodedQueryParams: true, + }) + .get('/api/public/current/nav') + .query({ chainType: 'polygon', tokenName: 'rcusdp' }) + .reply(200, { + code: 'R9999_9999', + success: true, + message: 'Success', + data: { + lastUpdate: '2025-11-11T16:55:53.448+00:00', + tokenName: 'rcusd', + chainType: 'chain', + totalSupply: 98, + totalAsset: 100, + currentNav: '1.020408163265306', + }, + }) + .persist() +exports.mockNavResponseSuccess = mockNavResponseSuccess +const mockNavResponseInvalidToken = () => + (0, nock_1.default)('https://app.r25.xyz', { + encodedQueryParams: true, + }) + .get('/api/public/current/nav') + .query({ chainType: 'polygon', tokenName: 'invalid' }) + .reply(200, { + code: 'R9999_0001', + success: false, + message: 'Invalid tokenName combination', + data: {}, + }) +exports.mockNavResponseInvalidToken = mockNavResponseInvalidToken +const mockNavResponseInvalidChainType = () => + (0, nock_1.default)('https://app.r25.xyz', { + encodedQueryParams: true, + }) + .get('/api/public/current/nav') + .query({ chainType: 'invalid', tokenName: 'rcusdp' }) + .reply(200, { + code: 'R9999_0002', + success: false, + message: 'Invalid chainType combination', + data: {}, + }) +exports.mockNavResponseInvalidChainType = mockNavResponseInvalidChainType +const mockNavResponseInvalidChainTypeAndTokenName = () => + (0, nock_1.default)('https://app.r25.xyz', { + encodedQueryParams: true, + }) + .get('/api/public/current/nav') + .query({ chainType: 'invalid', tokenName: 'invalid' }) + .reply(200, { + code: 'R9999_0001', + success: false, + message: 'Invalid tokenName combination', + data: {}, + }) +exports.mockNavResponseInvalidChainTypeAndTokenName = mockNavResponseInvalidChainTypeAndTokenName +const mockNavResponseAuthenticationFailed = () => + (0, nock_1.default)('https://app.r25.xyz', { + encodedQueryParams: true, + }) + .get('/api/public/current/nav') + .query({ chainType: 'optimism', tokenName: 'rcusd' }) + .reply(401, { + error: 'authentication failed', + }) +exports.mockNavResponseAuthenticationFailed = mockNavResponseAuthenticationFailed +const mockNavResponseSignatureFailed = () => + (0, nock_1.default)('https://app.r25.xyz', { + encodedQueryParams: true, + }) + .get('/api/public/current/nav') + .query({ chainType: 'avalanche', tokenName: 'rcusd' }) + .reply(401, { + error: 'signature failed', + }) +exports.mockNavResponseSignatureFailed = mockNavResponseSignatureFailed +const mockNavResponseInternalServerError = () => + (0, nock_1.default)('https://app.r25.xyz', { + encodedQueryParams: true, + }) + .get('/api/public/current/nav') + .query({ chainType: 'polygon', tokenName: 'rcusdp' }) + .reply(200, { + code: 'R0005_00001', + success: false, + message: 'System busy, please try again later.', + data: null, + }) +exports.mockNavResponseInternalServerError = mockNavResponseInternalServerError +const mockNavResponseSupplyQueryFailed = () => + (0, nock_1.default)('https://app.r25.xyz', { + encodedQueryParams: true, + }) + .get('/api/public/current/nav') + .query({ chainType: 'ethereum', tokenName: 'rcusd' }) + .reply(200, { + code: 'R0000_00001', + success: false, + message: 'internal error', + data: null, + }) +exports.mockNavResponseSupplyQueryFailed = mockNavResponseSupplyQueryFailed +const mockNavResponseExpiredTimestamp = () => + (0, nock_1.default)('https://app.r25.xyz', { + encodedQueryParams: true, + }) + .get('/api/public/current/nav') + .query({ chainType: 'arbitrum', tokenName: 'rcusd' }) + .reply(400, { + error: 'expired timestamp', + }) +exports.mockNavResponseExpiredTimestamp = mockNavResponseExpiredTimestamp +const mockNavResponseParamsMissing = () => + (0, nock_1.default)('https://app.r25.xyz', { + encodedQueryParams: true, + badheaders: ['x-api-key'], + }) + .get('/api/public/current/nav') + .query({ chainType: 'base', tokenName: 'rcusdc' }) + .reply(400, { + error: 'params missing', + }) +exports.mockNavResponseParamsMissing = mockNavResponseParamsMissing +//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiZml4dHVyZXMuanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyJmaXh0dXJlcy50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiOzs7O0FBQUEsd0RBQXVCO0FBRWhCLE1BQU0sc0JBQXNCLEdBQUcsR0FBZSxFQUFFLENBQ3JELElBQUEsY0FBSSxFQUFDLHFCQUFxQixFQUFFO0lBQzFCLGtCQUFrQixFQUFFLElBQUk7Q0FDekIsQ0FBQztLQUNDLEdBQUcsQ0FBQyx5QkFBeUIsQ0FBQztLQUM5QixLQUFLLENBQUMsRUFBRSxTQUFTLEVBQUUsU0FBUyxFQUFFLFNBQVMsRUFBRSxRQUFRLEVBQUUsQ0FBQztLQUNwRCxLQUFLLENBQUMsR0FBRyxFQUFFO0lBQ1YsSUFBSSxFQUFFLFlBQVk7SUFDbEIsT0FBTyxFQUFFLElBQUk7SUFDYixPQUFPLEVBQUUsU0FBUztJQUNsQixJQUFJLEVBQUU7UUFDSixVQUFVLEVBQUUsK0JBQStCO1FBQzNDLFNBQVMsRUFBRSxPQUFPO1FBQ2xCLFNBQVMsRUFBRSxPQUFPO1FBQ2xCLFdBQVcsRUFBRSxFQUFFO1FBQ2YsVUFBVSxFQUFFLEdBQUc7UUFDZixVQUFVLEVBQUUsbUJBQW1CO0tBQ2hDO0NBQ0YsQ0FBQztLQUNELE9BQU8sRUFBRSxDQUFBO0FBbkJELFFBQUEsc0JBQXNCLDBCQW1CckI7QUFFUCxNQUFNLDJCQUEyQixHQUFHLEdBQWUsRUFBRSxDQUMxRCxJQUFBLGNBQUksRUFBQyxxQkFBcUIsRUFBRTtJQUMxQixrQkFBa0IsRUFBRSxJQUFJO0NBQ3pCLENBQUM7S0FDQyxHQUFHLENBQUMseUJBQXlCLENBQUM7S0FDOUIsS0FBSyxDQUFDLEVBQUUsU0FBUyxFQUFFLFNBQVMsRUFBRSxTQUFTLEVBQUUsU0FBUyxFQUFFLENBQUM7S0FDckQsS0FBSyxDQUFDLEdBQUcsRUFBRTtJQUNWLElBQUksRUFBRSxZQUFZO0lBQ2xCLE9BQU8sRUFBRSxLQUFLO0lBQ2QsT0FBTyxFQUFFLCtCQUErQjtJQUN4QyxJQUFJLEVBQUUsRUFB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LE9BQU8sRUFBRSxDQUFDO0tBQ3BELEtBQUssQ0FBQyxHQUFHLEVBQUU7SUFDVixJQUFJLEVBQUUsYUFBYTtJQUNuQixPQUFPLEVBQUUsS0FBSztJQUNkLE9BQU8sRUFBRSxnQkFBZ0I7SUFDekIsSUFBSSxFQUFFLElBQUk7Q0FDWCxDQUFDLENBQUE7QUFYTyxRQUFBLGdDQUFnQyxvQ0FXdkM7QUFFQyxNQUFNLCtCQUErQixHQUFHLEdBQWUsRUFBRSxDQUM5RCxJQUFBLGNBQUksRUFBQyxxQkFBcUIsRUFBRTtJQUMxQixrQkFBa0IsRUFBRSxJQUFJO0NBQ3pCLENBQUM7S0FDQyxHQUFHLENBQUMseUJBQXlCLENBQUM7S0FDOUIsS0FBSyxDQUFDLEVBQUUsU0FBUyxFQUFFLFVBQVUsRUFBRSxTQUFTLEVBQUUsT0FBTyxFQUFFLENBQUM7S0FDcEQsS0FBSyxDQUFDLEdBQUcsRUFBRTtJQUNWLEtBQUssRUFBRSxtQkFBbUI7Q0FDM0IsQ0FBQyxDQUFBO0FBUk8sUUFBQSwrQkFBK0IsbUNBUXRDO0FBRUMsTUFBTSw0QkFBNEIsR0FBRyxHQUFlLEVBQUUsQ0FDM0QsSUFBQSxjQUFJLEVBQUMscUJBQXFCLEVBQUU7SUFDMUIsa0JBQWtCLEVBQUUsSUFBSTtJQUN4QixVQUFVLEVBQUUsQ0FBQyxXQUFXLENBQUM7Q0FDMUIsQ0FBQztLQUNDLEdBQUcsQ0FBQyx5QkFBeUIsQ0FBQztLQUM5QixLQUFLLENBQUMsRUFBRSxTQUFTLEVBQUUsTUFBTSxFQUFFLFNBQVMsRUFBRSxRQUFRLEVBQUUsQ0FBQztLQUNqRCxLQUFLLENBQUMsR0FBRyxFQUFFO0lBQ1YsS0FBSyxFQUFFLGdCQUFnQjtDQUN4QixDQUFDLENBQUE7QUFUTyxRQUFBLDRCQUE0QixnQ0FTbkMiLCJzb3VyY2VzQ29udGVudCI6WyJpbXBvcnQgbm9jayBmcm9tICdub2NrJ1xuXG5leHBvcnQgY29uc3QgbW9ja05hdlJlc3BvbnNlU3VjY2VzcyA9ICgpOiBub2NrLlNjb3BlID0+XG4gIG5vY2soJ2h0dHBzOi8vYXBwLnIyNS54eXonLCB7XG4gICAgZW5jb2RlZFF1ZXJ5UGFyYW1zOiB0cnVlLFxuICB9KVxuICAgIC5nZXQoJy9hcGkvcHVibGljL2N1cnJlbnQvbmF2JylcbiAgICAucXVlcnkoeyBjaGFpblR5cGU6ICdwb2x5Z29uJywgdG9rZW5OYW1lOiAncmN1c2RwJyB9KVxuICAgIC5yZXBseSgyMDAsIHtcbiAgICAgIGNvZGU6ICdSOTk5OV85OTk5JyxcbiAgICAgIHN1Y2Nlc3M6IHRydWUsXG4gICAgICBtZXNzYWdlOiAnU3VjY2VzcycsXG4gICAgICBkYXRhOiB7XG4gICAgICAgIGxhc3RVcGRhdGU6ICcyMDI1LTExLTExVDE2OjU1OjUzLjQ0OCswMDowMCcsXG4gICAgICAgIHRva2VuTmFtZTogJ3JjdXNkJyxcbiAgICAgICAgY2hhaW5UeXBlOiAnY2hhaW4nLFxuICAgICAgICB0b3RhbFN1cHBseTogOTgsXG4gICAgICAgIHRvdGFsQXNzZXQ6IDEwMCxcbiAgICAgICAgY3VycmVudE5hdjogJzEuMDIwNDA4MTYzMjY1MzA2JyxcbiAgICAgIH0sXG4gICAgfSlcbiAgICAucGVyc2lzdCgpXG5cbmV4cG9ydCBjb25zdCBtb2NrTmF2UmVzcG9uc2VJbnZhbGlkVG9rZW4gPSAoKTogbm9jay5TY29wZSA9PlxuICBub2NrKCdodHRwczovL2FwcC5yMjUueHl6Jywge1xuICAgIGVuY29kZWRRdWVyeVBhcmFtczogdHJ1ZSxcbiAgfSlcbiAgICAuZ2V0KCcvYXBpL3B1YmxpYy9jdXJyZW50L25hdicpXG4gICAgLnF1ZXJ5KHsgY2hhaW5UeXBlOiAncG9seWdvbicsIHRva2VuTmFtZTogJ2ludmFsaWQnIH0pXG4gICAgLnJlcGx5KDIwMCwge1xuICAgICAgY29kZTogJ1I5OTk5XzAwMDEnLFxuICAgICAgc3VjY2VzczogZmFsc2UsXG4gICAgICBtZXNzYWdlOiAnSW52YWxpZCB0b2tlbk5hbWUgY29tYmluYXRpb24nLFxuICAgICAgZGF0YToge30sXG4gICAgfSlcblxuZXhwb3J0IGNvbnN0IG1vY2tOYXZSZXNwb25zZUludmFsaWRDaGFpblR5cGUgPSAoKTogbm9jay5TY29wZSA9PlxuICBub2NrKCdodHRwczovL2FwcC5yMjUueHl6Jywge1xuICAgIGVuY29kZWRRdWVyeVBhcmFtczogdHJ1ZSxcbiAgfSlcbiAgICAuZ2V0KCcvYXBpL3B1YmxpYy9jdXJyZW50L25hdicpXG4gICAgLnF1ZXJ5KHsgY2hhaW5UeXBlOiAnaW52YWxpZCcsIHRva2VuTmFtZTogJ3JjdXNkcCcgfSlcbiAgICAucmVwbHkoMjAwLCB7XG4gICAgICBjb2RlOiAnUjk5OTlfMDAwMicsXG4gICAgICBzdWNjZXNzOiBmYWxzZSxcbiAgICAgIG1lc3NhZ2U6ICdJbnZhbGlkIGNoYWluVHlwZSBjb21iaW5hdGlvbicsXG4gICAgICBkYXRhOiB7fSxcbiAgICB9KVxuXG5leHBvcnQgY29uc3QgbW9ja05hdlJlc3BvbnNlSW52YWxpZENoYWluVHlwZUFuZFRva2VuTmFtZSA9ICgpOiBub2NrLlNjb3BlID0+XG4gIG5vY2soJ2h0dHBzOi8vYXBwLnIyNS54eXonLCB7XG4gICAgZW5jb2RlZFF1ZXJ5UGFyYW1zOiB0cnVlLFxuICB9KVxuICAgIC5nZXQoJy9hcGkvcHVibGljL2N1cnJlbnQvbmF2JylcbiAgICAucXVlcnkoeyBjaGFpblR5cGU6ICdpbnZhbGlkJywgdG9rZW5OYW1lOiAnaW52YWxpZCcgfSlcbiAgICAucmVwbHkoMjAwLCB7XG4gICAgICBjb2RlOiAnUjk5OTlfMDAwMScsXG4gICAgICBzdWNjZXNzOiBmYWxzZSxcbiAgICAgIG1lc3NhZ2U6ICdJbnZhbGlkIHRva2VuTmFtZSBjb21iaW5hdGlvbicsXG4gICAgICBkYXRhOiB7fSxcbiAgICB9KVxuXG5leHBvcnQgY29uc3QgbW9ja05hdlJlc3BvbnNlQXV0aGVudGljYXRpb25GYWlsZWQgPSAoKTogbm9jay5TY29wZSA9PlxuICBub2NrKCdodHRwczovL2FwcC5yMjUueHl6Jywge1xuICAgIGVuY29kZWRRdWVyeVBhcmFtczogdHJ1ZSxcbiAgfSlcbiAgICAuZ2V0KCcvYXBpL3B1YmxpYy9jdXJyZW50L25hdicpXG4gICAgLnF1ZXJ5KHsgY2hhaW5UeXBlOiAnb3B0aW1pc20nLCB0b2tlbk5hbWU6ICdyY3VzZCcgfSlcbiAgICAucmVwbHkoNDAxLCB7XG4gICAgICBlcnJvcjogJ2F1dGhlbnRpY2F0aW9uIGZhaWxlZCcsXG4gICAgfSlcblxuZXhwb3J0IGNvbnN0IG1vY2tOYXZSZXNwb25zZVNpZ25hdHVyZUZhaWxlZCA9ICgpOiBub2NrLlNjb3BlID0+XG4gIG5vY2soJ2h0dHBzOi8vYXBwLnIyNS54eXonLCB7XG4gICAgZW5jb2RlZFF1ZXJ5UGFyYW1zOiB0cnVlLFxuICB9KVxuICAgIC5nZXQoJy9hcGkvcHVibGljL2N1cnJlbnQvbmF2JylcbiAgICAucXVlcnkoeyBjaGFpblR5cGU6ICdhdmFsYW5jaGUnLCB0b2tlbk5hbWU6ICdyY3VzZCcgfSlcbiAgICAucmVwbHkoNDAxLCB7XG4gICAgICBlcnJvcjogJ3NpZ25hdHVyZSBmYWlsZWQnLFxuICAgIH0pXG5cbmV4cG9ydCBjb25zdCBtb2NrTmF2UmVzcG9uc2VJbnRlcm5hbFNlcnZlckVycm9yID0gKCk6IG5vY2suU2NvcGUgPT5cbiAgbm9jaygnaHR0cHM6Ly9hcHAucjI1Lnh5eicsIHtcbiAgICBlbmNvZGVkUXVlcnlQYXJhbXM6IHRydWUsXG4gIH0pXG4gICAgLmdldCgnL2FwaS9wdWJsaWMvY3VycmVudC9uYXYnKVxuICAgIC5xdWVyeSh7IGNoYWluVHlwZTogJ3BvbHlnb24nLCB0b2tlbk5hbWU6ICdyY3VzZHAnIH0pXG4gICAgLnJlcGx5KDIwMCwge1xuICAgICAgY29kZTogJ1IwMDA1XzAwMDAxJyxcbiAgICAgIHN1Y2Nlc3M6IGZhbHNlLFxuICAgICAgbWVzc2FnZTogJ1N5c3RlbSBidXN5LCBwbGVhc2UgdHJ5IGFnYWluIGxhdGVyLicsXG4gICAgICBkYXRhOiBudWxsLFxuICAgIH0pXG5cbmV4cG9ydCBjb25zdCBtb2NrTmF2UmVzcG9uc2VTdXBwbHlRdWVyeUZhaWxlZCA9ICgpOiBub2NrLlNjb3BlID0+XG4gIG5vY2soJ2h0dHBzOi8vYXBwLnIyNS54eXonLCB7XG4gICAgZW5jb2RlZFF1ZXJ5UGFyYW1zOiB0cnVlLFxuICB9KVxuICAgIC5nZXQoJy9hcGkvcHVibGljL2N1cnJlbnQvbmF2JylcbiAgICAucXVlcnkoeyBjaGFpblR5cGU6ICdldGhlcmV1bScsIHRva2VuTmFtZTogJ3JjdXNkJyB9KVxuICAgIC5yZXBseSgyMDAsIHtcbiAgICAgIGNvZGU6ICdSMDAwMF8wMDAwMScsXG4gICAgICBzdWNjZXNzOiBmYWxzZSxcbiAgICAgIG1lc3NhZ2U6ICdpbnRlcm5hbCBlcnJvcicsXG4gICAgICBkYXRhOiBudWxsLFxuICAgIH0pXG5cbmV4cG9ydCBjb25zdCBtb2NrTmF2UmVzcG9uc2VFeHBpcmVkVGltZXN0YW1wID0gKCk6IG5vY2suU2NvcGUgPT5cbiAgbm9jaygnaHR0cHM6Ly9hcHAucjI1Lnh5eicsIHtcbiAgICBlbmNvZGVkUXVlcnlQYXJhbXM6IHRydWUsXG4gIH0pXG4gICAgLmdldCgnL2FwaS9wdWJsaWMvY3VycmVudC9uYXYnKVxuICAgIC5xdWVyeSh7IGNoYWluVHlwZTogJ2FyYml0cnVtJywgdG9rZW5OYW1lOiAncmN1c2QnIH0pXG4gICAgLnJlcGx5KDQwMCwge1xuICAgICAgZXJyb3I6ICdleHBpcmVkIHRpbWVzdGFtcCcsXG4gICAgfSlcblxuZXhwb3J0IGNvbnN0IG1vY2tOYXZSZXNwb25zZVBhcmFtc01pc3NpbmcgPSAoKTogbm9jay5TY29wZSA9PlxuICBub2NrKCdodHRwczovL2FwcC5yMjUueHl6Jywge1xuICAgIGVuY29kZWRRdWVyeVBhcmFtczogdHJ1ZSxcbiAgICBiYWRoZWFkZXJzOiBbJ3gtYXBpLWtleSddLFxuICB9KVxuICAgIC5nZXQoJy9hcGkvcHVibGljL2N1cnJlbnQvbmF2JylcbiAgICAucXVlcnkoeyBjaGFpblR5cGU6ICdiYXNlJywgdG9rZW5OYW1lOiAncmN1c2RjJyB9KVxuICAgIC5yZXBseSg0MDAsIHtcbiAgICAgIGVycm9yOiAncGFyYW1zIG1pc3NpbmcnLFxuICAgIH0pXG4iXX0= diff --git a/packages/sources/r25/test/unit/authentication.test.d.ts b/packages/sources/r25/test/unit/authentication.test.d.ts new file mode 100644 index 00000000000..f991a7c61e6 --- /dev/null +++ b/packages/sources/r25/test/unit/authentication.test.d.ts @@ -0,0 +1,2 @@ +export {} +//# sourceMappingURL=authentication.test.d.ts.map diff --git a/packages/sources/r25/test/unit/authentication.test.d.ts.map b/packages/sources/r25/test/unit/authentication.test.d.ts.map new file mode 100644 index 00000000000..dab6f030829 --- /dev/null +++ b/packages/sources/r25/test/unit/authentication.test.d.ts.map @@ -0,0 +1 @@ +{"version":3,"file":"authentication.test.d.ts","sourceRoot":"","sources":["authentication.test.ts"],"names":[],"mappings":""} \ No newline at end of file diff --git a/packages/sources/r25/test/unit/authentication.test.js b/packages/sources/r25/test/unit/authentication.test.js new file mode 100644 index 00000000000..f8b9b1e6741 --- /dev/null +++ b/packages/sources/r25/test/unit/authentication.test.js @@ -0,0 +1,129 @@ +'use strict' +Object.defineProperty(exports, '__esModule', { value: true }) +const tslib_1 = require('tslib') +const crypto_js_1 = tslib_1.__importDefault(require('crypto-js')) +const authentication_1 = require('../../src/transport/authentication') +describe('authentication', () => { + describe('getRequestHeaders', () => { + it('should generate correct signature for example from documentation', () => { + // Example from R25 API documentation + const method = 'GET' + const path = '/api/public/current/nav' + const params = { + chainType: 'chain', + tokenName: 'rcusd', + } + const timestamp = 1731344153448 + const apiKey = 'xxx' + const secret = 'xxxxxxxx' + // Expected signature string from documentation: + const expectedSignature = '208966c881a8194fd63b6107c7b9cdbf4c49cc4e0b29b68bcbe18cf7c273bcf7' + const headers = (0, authentication_1.getRequestHeaders)({ + method, + path, + params, + apiKey, + secret, + timestamp, + }) + expect(headers['x-api-key']).toBe(apiKey) + expect(headers['x-utc-timestamp']).toBe(timestamp.toString()) + expect(headers['x-signature']).toBe(expectedSignature) + }) + it('should generate consistent signatures for the same input', () => { + const method = 'GET' + const path = '/api/public/current/nav' + const params = { + chainType: 'polygon', + tokenName: 'rcusdp', + } + const timestamp = 1234567890123 + const apiKey = 'test-api-key' + const secret = 'test-secret' + const headers1 = (0, authentication_1.getRequestHeaders)({ + method, + path, + params, + apiKey, + secret, + timestamp, + }) + const headers2 = (0, authentication_1.getRequestHeaders)({ + method, + path, + params, + apiKey, + secret, + timestamp, + }) + expect(headers1['x-signature']).toBe(headers2['x-signature']) + }) + it('should sort query parameters alphabetically', () => { + const method = 'GET' + const path = '/api/public/current/nav' + // Intentionally unsorted parameters + const params = { + tokenName: 'rcusdp', + chainType: 'polygon', + } + const timestamp = 1234567890123 + const apiKey = 'test-api-key' + const secret = 'test-secret' + // The signature should be the same regardless of the order params are provided + const headers1 = (0, authentication_1.getRequestHeaders)({ + method, + path, + params, + apiKey, + secret, + timestamp, + }) + // Try with sorted params + const sortedParams = { + chainType: 'polygon', + tokenName: 'rcusdp', + } + const headers2 = (0, authentication_1.getRequestHeaders)({ + method, + path, + params: sortedParams, + apiKey, + secret, + timestamp, + }) + expect(headers1['x-signature']).toBe(headers2['x-signature']) + }) + it('should use lowercase method name', () => { + const method = 'GET' + const path = '/api/public/current/nav' + const params = { + chainType: 'polygon', + tokenName: 'rcusdp', + } + const timestamp = 1234567890123 + const apiKey = 'test-api-key' + const secret = 'test-secret' + const headers = (0, authentication_1.getRequestHeaders)({ + method, + path, + params, + apiKey, + secret, + timestamp, + }) + // Verify it's using lowercase by checking signature matches expected + const expectedStringToSign = [ + 'get', // lowercase + path, + 'chainType=polygon&tokenName=rcusdp', + timestamp.toString(), + apiKey, + ].join('\n') + const expectedSignature = crypto_js_1.default + .HmacSHA256(expectedStringToSign, secret) + .toString(crypto_js_1.default.enc.Hex) + expect(headers['x-signature']).toBe(expectedSignature) + }) + }) +}) +//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiYXV0aGVudGljYXRpb24udGVzdC5qcyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbImF1dGhlbnRpY2F0aW9uLnRlc3QudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6Ijs7O0FBQUEsa0VBQWdDO0FBQ2hDLHVFQUFzRTtBQUV0RSxRQUFRLENBQUMsZ0JBQWdCLEVBQUUsR0FBRyxFQUFFO0lBQzlCLFFBQVEsQ0FBQyxtQkFBbUIsRUFBRSxHQUFHLEVBQUU7UUFDakMsRUFBRSxDQUFDLGtFQUFrRSxFQUFFLEdBQUcsRUFBRTtZQUMxRSxxQ0FBcUM7WUFDckMsTUFBTSxNQUFNLEdBQUcsS0FBSyxDQUFBO1lBQ3BCLE1BQU0sSUFBSSxHQUFHLHlCQUF5QixDQUFBO1lBQ3RDLE1BQU0sTUFBTSxHQUFHO2dCQUNiLFNBQVMsRUFBRSxPQUFPO2dCQUNsQixTQUFTLEVBQUUsT0FBTzthQUNuQixDQUFBO1lBQ0QsTUFBTSxTQUFTLEdBQUcsYUFBYSxDQUFBO1lBQy9CLE1BQU0sTUFBTSxHQUFHLEtBQUssQ0FBQTtZQUNwQixNQUFNLE1BQU0sR0FBRyxVQUFVLENBQUE7WUFFekIsZ0RBQWdEO1lBQ2hELE1BQU0saUJBQWlCLEdBQUcsa0VBQWtFLENBQUE7WUFDNUYsTUFBTSxPQUFPLEdBQUcsSUFBQSxrQ0FBaUIsRUFBQztnQkFDaEMsTUFBTTtnQkFDTixJQUFJO2dCQUNKLE1BQU07Z0JBQ04sTUFBTTtnQkFDTixNQUFNO2dCQUNOLFNBQVM7YUFDVixDQUFDLENBQUE7WUFFRixNQUFNLENBQUMsT0FBTyxDQUFDLFdBQVcsQ0FBQyxDQUFDLENBQUMsSUFBSSxDQUFDLE1BQU0sQ0FBQyxDQUFBO1lBQ3pDLE1BQU0sQ0FBQyxPQUFPLENBQUMsaUJBQWlCLENBQUMsQ0FBQyxDQUFDLElBQUksQ0FBQyxTQUFTLENBQUMsUUFBUSxFQUFFLENBQUMsQ0FBQTtZQUM3RCxNQUFNLENBQUMsT0FBTyxDQUFDLGFBQWEsQ0FBQyxDQUFDLENBQUMsSUFBSSxDQUFDLGlCQUFpQixDQUFD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O2dCQUMzQixLQUFLLEVBQUUsWUFBWTtnQkFDbkIsSUFBSTtnQkFDSixvQ0FBb0M7Z0JBQ3BDLFNBQVMsQ0FBQyxRQUFRLEVBQUU7Z0JBQ3BCLE1BQU07YUFDUCxDQUFDLElBQUksQ0FBQyxJQUFJLENBQUMsQ0FBQTtZQUVaLE1BQU0saUJBQWlCLEdBQUcsbUJBQVEsQ0FBQyxVQUFVLENBQUMsb0JBQW9CLEVBQUUsTUFBTSxDQUFDLENBQUMsUUFBUSxDQUNsRixtQkFBUSxDQUFDLEdBQUcsQ0FBQyxHQUFHLENBQ2pCLENBQUE7WUFFRCxNQUFNLENBQUMsT0FBTyxDQUFDLGFBQWEsQ0FBQyxDQUFDLENBQUMsSUFBSSxDQUFDLGlCQUFpQixDQUFDLENBQUE7UUFDeEQsQ0FBQyxDQUFDLENBQUE7SUFDSixDQUFDLENBQUMsQ0FBQTtBQUNKLENBQUMsQ0FBQyxDQUFBIiwic291cmNlc0NvbnRlbnQiOlsiaW1wb3J0IENyeXB0b0pTIGZyb20gJ2NyeXB0by1qcydcbmltcG9ydCB7IGdldFJlcXVlc3RIZWFkZXJzIH0gZnJvbSAnLi4vLi4vc3JjL3RyYW5zcG9ydC9hdXRoZW50aWNhdGlvbidcblxuZGVzY3JpYmUoJ2F1dGhlbnRpY2F0aW9uJywgKCkgPT4ge1xuICBkZXNjcmliZSgnZ2V0UmVxdWVzdEhlYWRlcnMnLCAoKSA9PiB7XG4gICAgaXQoJ3Nob3VsZCBnZW5lcmF0ZSBjb3JyZWN0IHNpZ25hdHVyZSBmb3IgZXhhbXBsZSBmcm9tIGRvY3VtZW50YXRpb24nLCAoKSA9PiB7XG4gICAgICAvLyBFeGFtcGxlIGZyb20gUjI1IEFQSSBkb2N1bWVudGF0aW9uXG4gICAgICBjb25zdCBtZXRob2QgPSAnR0VUJ1xuICAgICAgY29uc3QgcGF0aCA9ICcvYXBpL3B1YmxpYy9jdXJyZW50L25hdidcbiAgICAgIGNvbnN0IHBhcmFtcyA9IHtcbiAgICAgICAgY2hhaW5UeXBlOiAnY2hhaW4nLFxuICAgICAgICB0b2tlbk5hbWU6ICdyY3VzZCcsXG4gICAgICB9XG4gICAgICBjb25zdCB0aW1lc3RhbXAgPSAxNzMxMzQ0MTUzNDQ4XG4gICAgICBjb25zdCBhcGlLZXkgPSAneHh4J1xuICAgICAgY29uc3Qgc2VjcmV0ID0gJ3h4eHh4eHh4J1xuXG4gICAgICAvLyBFeHBlY3RlZCBzaWduYXR1cmUgc3RyaW5nIGZyb20gZG9jdW1lbnRhdGlvbjpcbiAgICAgIGNvbnN0IGV4cGVjdGVkU2lnbmF0dXJlID0gJzIwODk2NmM4ODFhODE5NGZkNjNiNjEwN2M3YjljZGJmNGM0OWNjNGUwYjI5YjY4YmNiZTE4Y2Y3YzI3M2JjZjcnXG4gICAgICBjb25zdCBoZWFkZXJzID0gZ2V0UmVxdWVzdEhlYWRlcnMoe1xuICAgICAgICBtZXRob2QsXG4gICAgICAgIHBhdGgsXG4gICAgICAgIHBhcmFtcyxcbiAgICAgICAgYXBpS2V5LFxuICAgICAgICBzZWNyZXQsXG4gICAgICAgIHRpbWVzdGFtcCxcbiAgICAgIH0pXG5cbiAgICAgIGV4cGVjdChoZWFkZXJzWyd4LWFwaS1rZXknXSkudG9CZShhcGlLZXkpXG4gICAgICBleHBlY3QoaGVhZGVyc1sneC11dGMtdGltZXN0YW1wJ10pLnRvQmUodGltZXN0YW1wLnRvU3RyaW5nKCkpXG4gICAgICBleHBlY3QoaGVhZGVyc1sneC1zaWduYXR1cmUnXSkudG9CZShleHBlY3RlZFNpZ25hdHVyZSlcbiAgICB9KVxuXG4gICAgaXQoJ3Nob3VsZCBnZW5lcmF0ZSBjb25zaXN0ZW50IHNpZ25hdHVyZXMgZm9yIHRoZSBzYW1lIGlucHV0JywgKCkgPT4ge1xuICAgICAgY29uc3QgbWV0aG9kID0gJ0dFVCdcbiAgICAgIGNvbnN0IHBhdGggPSAnL2FwaS9wdWJsaWMvY3VycmVudC9uYXYnXG4gICAgICBjb25zdCBwYXJhbXMgPSB7XG4gICAgICAgIGNoYWluVHlwZTogJ3BvbHlnb24nLFxuICAgICAgICB0b2tlbk5hbWU6ICdyY3VzZHAnLFxuICAgICAgfVxuICAgICAgY29uc3QgdGltZXN0YW1wID0gMTIzNDU2Nzg5MDEyM1xuICAgICAgY29uc3QgYXBpS2V5ID0gJ3Rlc3QtYXBpLWtleSdcbiAgICAgIGNvbnN0IHNlY3JldCA9ICd0ZXN0LXNlY3JldCdcblxuICAgICAgY29uc3QgaGVhZGVyczEgPSBnZXRSZXF1ZXN0SGVhZGVycyh7XG4gICAgICAgIG1ldGhvZCxcbiAgICAgICAgcGF0aCxcbiAgICAgICAgcGFyYW1zLFxuICAgICAgICBhcGlLZXksXG4gICAgICAgIHNlY3JldCxcbiAgICAgICAgdGltZXN0YW1wLFxuICAgICAgfSlcblxuICAgICAgY29uc3QgaGVhZGVyczIgPSBnZXRSZXF1ZXN0SGVhZGVycyh7XG4gICAgICAgIG1ldGhvZCxcbiAgICAgICAgcGF0aCxcbiAgICAgICAgcGFyYW1zLFxuICAgICAgICBhcGlLZXksXG4gICAgICAgIHNlY3JldCxcbiAgICAgICAgdGltZXN0YW1wLFxuICAgICAgfSlcblxuICAgICAgZXhwZWN0KGhlYWRlcnMxWyd4LXNpZ25hdHVyZSddKS50b0JlKGhlYWRlcnMyWyd4LXNpZ25hdHVyZSddKVxuICAgIH0pXG5cbiAgICBpdCgnc2hvdWxkIHNvcnQgcXVlcnkgcGFyYW1ldGVycyBhbHBoYWJldGljYWxseScsICgpID0+IHtcbiAgICAgIGNvbnN0IG1ldGhvZCA9ICdHRVQnXG4gICAgICBjb25zdCBwYXRoID0gJy9hcGkvcHVibGljL2N1cnJlbnQvbmF2J1xuICAgICAgLy8gSW50ZW50aW9uYWxseSB1bnNvcnRlZCBwYXJhbWV0ZXJzXG4gICAgICBjb25zdCBwYXJhbXMgPSB7XG4gICAgICAgIHRva2VuTmFtZTogJ3JjdXNkcCcsXG4gICAgICAgIGNoYWluVHlwZTogJ3BvbHlnb24nLFxuICAgICAgfVxuICAgICAgY29uc3QgdGltZXN0YW1wID0gMTIzNDU2Nzg5MDEyM1xuICAgICAgY29uc3QgYXBpS2V5ID0gJ3Rlc3QtYXBpLWtleSdcbiAgICAgIGNvbnN0IHNlY3JldCA9ICd0ZXN0LXNlY3JldCdcblxuICAgICAgLy8gVGhlIHNpZ25hdHVyZSBzaG91bGQgYmUgdGhlIHNhbWUgcmVnYXJkbGVzcyBvZiB0aGUgb3JkZXIgcGFyYW1zIGFyZSBwcm92aWRlZFxuICAgICAgY29uc3QgaGVhZGVyczEgPSBnZXRSZXF1ZXN0SGVhZGVycyh7XG4gICAgICAgIG1ldGhvZCxcbiAgICAgICAgcGF0aCxcbiAgICAgICAgcGFyYW1zLFxuICAgICAgICBhcGlLZXksXG4gICAgICAgIHNlY3JldCxcbiAgICAgICAgdGltZXN0YW1wLFxuICAgICAgfSlcblxuICAgICAgLy8gVHJ5IHdpdGggc29ydGVkIHBhcmFtc1xuICAgICAgY29uc3Qgc29ydGVkUGFyYW1zID0ge1xuICAgICAgICBjaGFpblR5cGU6ICdwb2x5Z29uJyxcbiAgICAgICAgdG9rZW5OYW1lOiAncmN1c2RwJyxcbiAgICAgIH1cblxuICAgICAgY29uc3QgaGVhZGVyczIgPSBnZXRSZXF1ZXN0SGVhZGVycyh7XG4gICAgICAgIG1ldGhvZCxcbiAgICAgICAgcGF0aCxcbiAgICAgICAgcGFyYW1zOiBzb3J0ZWRQYXJhbXMsXG4gICAgICAgIGFwaUtleSxcbiAgICAgICAgc2VjcmV0LFxuICAgICAgICB0aW1lc3RhbXAsXG4gICAgICB9KVxuXG4gICAgICBleHBlY3QoaGVhZGVyczFbJ3gtc2lnbmF0dXJlJ10pLnRvQmUoaGVhZGVyczJbJ3gtc2lnbmF0dXJlJ10pXG4gICAgfSlcblxuICAgIGl0KCdzaG91bGQgdXNlIGxvd2VyY2FzZSBtZXRob2QgbmFtZScsICgpID0+IHtcbiAgICAgIGNvbnN0IG1ldGhvZCA9ICdHRVQnXG4gICAgICBjb25zdCBwYXRoID0gJy9hcGkvcHVibGljL2N1cnJlbnQvbmF2J1xuICAgICAgY29uc3QgcGFyYW1zID0ge1xuICAgICAgICBjaGFpblR5cGU6ICdwb2x5Z29uJyxcbiAgICAgICAgdG9rZW5OYW1lOiAncmN1c2RwJyxcbiAgICAgIH1cbiAgICAgIGNvbnN0IHRpbWVzdGFtcCA9IDEyMzQ1Njc4OTAxMjNcbiAgICAgIGNvbnN0IGFwaUtleSA9ICd0ZXN0LWFwaS1rZXknXG4gICAgICBjb25zdCBzZWNyZXQgPSAndGVzdC1zZWNyZXQnXG5cbiAgICAgIGNvbnN0IGhlYWRlcnMgPSBnZXRSZXF1ZXN0SGVhZGVycyh7XG4gICAgICAgIG1ldGhvZCxcbiAgICAgICAgcGF0aCxcbiAgICAgICAgcGFyYW1zLFxuICAgICAgICBhcGlLZXksXG4gICAgICAgIHNlY3JldCxcbiAgICAgICAgdGltZXN0YW1wLFxuICAgICAgfSlcblxuICAgICAgLy8gVmVyaWZ5IGl0J3MgdXNpbmcgbG93ZXJjYXNlIGJ5IGNoZWNraW5nIHNpZ25hdHVyZSBtYXRjaGVzIGV4cGVjdGVkXG4gICAgICBjb25zdCBleHBlY3RlZFN0cmluZ1RvU2lnbiA9IFtcbiAgICAgICAgJ2dldCcsIC8vIGxvd2VyY2FzZVxuICAgICAgICBwYXRoLFxuICAgICAgICAnY2hhaW5UeXBlPXBvbHlnb24mdG9rZW5OYW1lPXJjdXNkcCcsXG4gICAgICAgIHRpbWVzdGFtcC50b1N0cmluZygpLFxuICAgICAgICBhcGlLZXksXG4gICAgICBdLmpvaW4oJ1xcbicpXG5cbiAgICAgIGNvbnN0IGV4cGVjdGVkU2lnbmF0dXJlID0gQ3J5cHRvSlMuSG1hY1NIQTI1NihleHBlY3RlZFN0cmluZ1RvU2lnbiwgc2VjcmV0KS50b1N0cmluZyhcbiAgICAgICAgQ3J5cHRvSlMuZW5jLkhleCxcbiAgICAgIClcblxuICAgICAgZXhwZWN0KGhlYWRlcnNbJ3gtc2lnbmF0dXJlJ10pLnRvQmUoZXhwZWN0ZWRTaWduYXR1cmUpXG4gICAgfSlcbiAgfSlcbn0pXG4iXX0= diff --git a/yarn.lock b/yarn.lock index 91d0f297f83..3850546dc91 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3613,6 +3613,9 @@ __metadata: ts-node: "npm:10.9.2" typescript: "npm:5.8.3" yo: "npm:4.3.1" + dependenciesMeta: + "@chainlink/external-adapter-framework@2.9.0": + unplugged: true languageName: unknown linkType: soft