From c5432a58c8bb9956ea963acc2e9f79c49e1045d9 Mon Sep 17 00:00:00 2001 From: Matthew McAllister Date: Fri, 18 Aug 2023 03:04:47 -0400 Subject: [PATCH 01/10] GSR LWBA endpoint --- packages/sources/gsr/src/endpoint/price.ts | 13 ++++- packages/sources/gsr/src/index.ts | 2 +- .../sources/gsr/src/transport/authutils.ts | 52 ++++++++++++++++++ packages/sources/gsr/src/transport/price.ts | 55 ++----------------- 4 files changed, 70 insertions(+), 52 deletions(-) create mode 100644 packages/sources/gsr/src/transport/authutils.ts diff --git a/packages/sources/gsr/src/endpoint/price.ts b/packages/sources/gsr/src/endpoint/price.ts index ea7724d9c2..30aa7d020e 100644 --- a/packages/sources/gsr/src/endpoint/price.ts +++ b/packages/sources/gsr/src/endpoint/price.ts @@ -9,15 +9,24 @@ import { transport } from '../transport/price' const inputParameters = new InputParameters(priceEndpointInputParametersDefinition) +// Additional fields are due to lwba endpoint +type EPResponse = SingleNumberResultResponse & { + Data: { + mid: number + bid: number + ask: number + } +} + export type BaseEndpointTypes = { Parameters: typeof inputParameters.definition Settings: typeof config.settings - Response: SingleNumberResultResponse + Response: EPResponse } export const endpoint = new CryptoPriceEndpoint({ name: 'price', - aliases: ['price-ws', 'crypto'], + aliases: ['price-ws', 'crypto', 'crypto-lwba', 'cryptolwba', 'crypto_lwba'], transport, inputParameters, }) diff --git a/packages/sources/gsr/src/index.ts b/packages/sources/gsr/src/index.ts index e7fb19c0d8..bce6ef7c6d 100644 --- a/packages/sources/gsr/src/index.ts +++ b/packages/sources/gsr/src/index.ts @@ -4,7 +4,7 @@ import { config } from './config' import { price } from './endpoint' export const adapter = new PriceAdapter({ - defaultEndpoint: 'price', + defaultEndpoint: price.name, name: 'GSR', endpoints: [price], config, diff --git a/packages/sources/gsr/src/transport/authutils.ts b/packages/sources/gsr/src/transport/authutils.ts new file mode 100644 index 0000000000..1321b7f2b4 --- /dev/null +++ b/packages/sources/gsr/src/transport/authutils.ts @@ -0,0 +1,52 @@ +import crypto from 'crypto' +import axios from 'axios' +import { config } from '../config' +import { makeLogger } from '@chainlink/external-adapter-framework/util' + +const logger = makeLogger('GSR Auth Token Utils') + +interface TokenError { + success: false + ts: number + error: string +} + +interface TokenSuccess { + success: true + ts: number + token: string + validUntil: string +} + +type AccessTokenResponse = TokenError | TokenSuccess + +const currentTimeNanoSeconds = (): number => new Date(Date.now()).getTime() * 1000000 + +const generateSignature = (userId: string, publicKey: string, privateKey: string, ts: number) => + crypto + .createHmac('sha256', privateKey) + .update(`userId=${userId}&apiKey=${publicKey}&ts=${ts}`) + .digest('hex') + +export const getToken = async (settings: typeof config.settings) => { + logger.debug('Fetching new access token') + + const userId = settings.WS_USER_ID + const publicKey = settings.WS_PUBLIC_KEY + const privateKey = settings.WS_PRIVATE_KEY + const ts = currentTimeNanoSeconds() + const signature = generateSignature(userId, publicKey, privateKey, ts) + const response = await axios.post(`${settings.API_ENDPOINT}/token`, { + apiKey: publicKey, + userId, + ts, + signature, + }) + + if (!response.data.success) { + logger.error('Unable to get access token') + throw new Error(response.data.error) + } + + return response.data.token +} diff --git a/packages/sources/gsr/src/transport/price.ts b/packages/sources/gsr/src/transport/price.ts index b015d844c0..d91d7f5a12 100644 --- a/packages/sources/gsr/src/transport/price.ts +++ b/packages/sources/gsr/src/transport/price.ts @@ -1,9 +1,7 @@ -import crypto from 'crypto' -import { config } from '../config' import { BaseEndpointTypes } from '../endpoint/price' import { WebSocketTransport } from '@chainlink/external-adapter-framework/transports' -import axios from 'axios' import { makeLogger, ProviderResult } from '@chainlink/external-adapter-framework/util' +import { getToken } from './authutils' const logger = makeLogger('GSR WS price') @@ -12,6 +10,8 @@ type WsMessage = { data: { symbol: string price: number + bidPrice: number + askPrice: number ts: number } } @@ -22,52 +22,6 @@ export type WsTransportTypes = BaseEndpointTypes & { } } -export interface TokenError { - success: false - ts: number - error: string -} - -export interface TokenSuccess { - success: true - ts: number - token: string - validUntil: string -} - -export type AccessTokenResponse = TokenError | TokenSuccess - -const currentTimeNanoSeconds = (): number => new Date(Date.now()).getTime() * 1000000 - -const generateSignature = (userId: string, publicKey: string, privateKey: string, ts: number) => - crypto - .createHmac('sha256', privateKey) - .update(`userId=${userId}&apiKey=${publicKey}&ts=${ts}`) - .digest('hex') - -const getToken = async (settings: typeof config.settings) => { - logger.debug('Fetching new access token') - - const userId = settings.WS_USER_ID - const publicKey = settings.WS_PUBLIC_KEY - const privateKey = settings.WS_PRIVATE_KEY - const ts = currentTimeNanoSeconds() - const signature = generateSignature(userId, publicKey, privateKey, ts) - const response = await axios.post(`${settings.API_ENDPOINT}/token`, { - apiKey: publicKey, - userId, - ts, - signature, - }) - - if (!response.data.success) { - logger.error('Unable to get access token') - throw new Error(response.data.error) - } - - return response.data.token -} - export const transport = new WebSocketTransport({ url: (context) => context.adapterSettings.WS_API_ENDPOINT, options: async (context) => ({ @@ -104,6 +58,9 @@ export const transport = new WebSocketTransport({ result: message.data.price, data: { result: message.data.price, + mid: message.data.price, + bid: message.data.bidPrice, + ask: message.data.askPrice, }, timestamps: { providerIndicatedTimeUnixMs: Math.round(message.data.ts / 1e6), // Value from provider is in nanoseconds From 15d3ba922895fa38114373da0d0da6c1a71b9df8 Mon Sep 17 00:00:00 2001 From: Matthew McAllister Date: Fri, 18 Aug 2023 03:09:27 -0400 Subject: [PATCH 02/10] add changeset --- .changeset/rotten-humans-reply.md | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 .changeset/rotten-humans-reply.md diff --git a/.changeset/rotten-humans-reply.md b/.changeset/rotten-humans-reply.md new file mode 100644 index 0000000000..86242c860e --- /dev/null +++ b/.changeset/rotten-humans-reply.md @@ -0,0 +1,5 @@ +--- +'@chainlink/gsr-adapter': minor +--- + +Added LWBA endpoint for GSR EA From 0f6f8bd7acee3a92247b44e908329227a23ab611 Mon Sep 17 00:00:00 2001 From: Matthew McAllister Date: Fri, 18 Aug 2023 14:54:02 -0400 Subject: [PATCH 03/10] update integration test --- .../gsr/test/integration/__snapshots__/adapter.test.ts.snap | 3 +++ packages/sources/gsr/test/integration/fixtures.ts | 4 ++++ 2 files changed, 7 insertions(+) diff --git a/packages/sources/gsr/test/integration/__snapshots__/adapter.test.ts.snap b/packages/sources/gsr/test/integration/__snapshots__/adapter.test.ts.snap index 5254db509a..3383e51069 100644 --- a/packages/sources/gsr/test/integration/__snapshots__/adapter.test.ts.snap +++ b/packages/sources/gsr/test/integration/__snapshots__/adapter.test.ts.snap @@ -3,6 +3,9 @@ exports[`websocket websocket endpoint should return success 1`] = ` { "data": { + "ask": 1235, + "bid": 1233, + "mid": 1234, "result": 1234, }, "result": 1234, diff --git a/packages/sources/gsr/test/integration/fixtures.ts b/packages/sources/gsr/test/integration/fixtures.ts index 976bad5601..3d42d55db2 100644 --- a/packages/sources/gsr/test/integration/fixtures.ts +++ b/packages/sources/gsr/test/integration/fixtures.ts @@ -35,6 +35,8 @@ export const mockTokenSuccess = (): nock.Scope => const base = 'ETH' const quote = 'USD' const price = 1234 +const bidPrice = 1233 +const askPrice = 1235 const time = 1669345393482 export const mockWebSocketServer = (URL: string) => { @@ -47,6 +49,8 @@ export const mockWebSocketServer = (URL: string) => { data: { symbol: `${base.toUpperCase()}.${quote.toUpperCase()}`, price, + bidPrice, + askPrice, ts: time * 1e6, }, }), From 4219093e6d2128af6e368460a3497c0ba3446c46 Mon Sep 17 00:00:00 2001 From: Matthew McAllister Date: Fri, 18 Aug 2023 15:29:06 -0400 Subject: [PATCH 04/10] update tests --- packages/sources/gsr/test-payload.json | 15 +++++++++++---- .../__snapshots__/adapter.test.ts.snap | 18 ++++++++++++++++++ .../gsr/test/integration/adapter.test.ts | 14 ++++++++++++++ 3 files changed, 43 insertions(+), 4 deletions(-) diff --git a/packages/sources/gsr/test-payload.json b/packages/sources/gsr/test-payload.json index d28cef9c1a..955e3c28a7 100644 --- a/packages/sources/gsr/test-payload.json +++ b/packages/sources/gsr/test-payload.json @@ -1,6 +1,13 @@ { - "requests": [{ - "from": "ETH", - "to": "USD" - }] + "requests": [ + { + "from": "ETH", + "to": "USD" + }, + { + "from": "ETH", + "to": "USD", + "endpoint": "crypto-lwba" + } + ] } diff --git a/packages/sources/gsr/test/integration/__snapshots__/adapter.test.ts.snap b/packages/sources/gsr/test/integration/__snapshots__/adapter.test.ts.snap index 3383e51069..0651dfd202 100644 --- a/packages/sources/gsr/test/integration/__snapshots__/adapter.test.ts.snap +++ b/packages/sources/gsr/test/integration/__snapshots__/adapter.test.ts.snap @@ -1,5 +1,23 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP +exports[`websocket websocket endpoint lwba endpoint alias should return success 1`] = ` +{ + "data": { + "ask": 1235, + "bid": 1233, + "mid": 1234, + "result": 1234, + }, + "result": 1234, + "statusCode": 200, + "timestamps": { + "providerDataReceivedUnixMs": Any, + "providerDataStreamEstablishedUnixMs": Any, + "providerIndicatedTimeUnixMs": 1669345393482, + }, +} +`; + exports[`websocket websocket endpoint should return success 1`] = ` { "data": { diff --git a/packages/sources/gsr/test/integration/adapter.test.ts b/packages/sources/gsr/test/integration/adapter.test.ts index 532d12b651..acc36c4835 100644 --- a/packages/sources/gsr/test/integration/adapter.test.ts +++ b/packages/sources/gsr/test/integration/adapter.test.ts @@ -63,6 +63,20 @@ describe('websocket', () => { }) }) + it('lwba endpoint alias should return success', async () => { + const response = await testAdapter.request({ + base: 'ETH', + quote: 'USD', + endpoint: 'crypto-lwba', + }) + expect(response.json()).toMatchSnapshot({ + timestamps: { + providerDataReceivedUnixMs: expect.any(Number), + providerDataStreamEstablishedUnixMs: expect.any(Number), + }, + }) + }) + it('should return error (empty data)', async () => { const response = await testAdapter.request({}) expect(response.statusCode).toEqual(400) From 3507f526e340ff6581349c28b22aa4a9a7251cf1 Mon Sep 17 00:00:00 2001 From: Matthew McAllister Date: Mon, 21 Aug 2023 19:58:02 -0400 Subject: [PATCH 05/10] updates to separate price and lwba endpoints following discussion with GSR & associated changes --- packages/sources/gsr/src/config/index.ts | 26 ++++++ .../sources/gsr/src/endpoint/crypto-lwba.ts | 32 +++++++ packages/sources/gsr/src/endpoint/index.ts | 1 + packages/sources/gsr/src/endpoint/price.ts | 13 +-- packages/sources/gsr/src/index.ts | 4 +- .../sources/gsr/src/transport/authutils.ts | 16 ++-- .../sources/gsr/src/transport/crypto-lwba.ts | 89 +++++++++++++++++++ packages/sources/gsr/src/transport/price.ts | 12 +-- .../__snapshots__/adapter.test.ts.snap | 8 +- .../gsr/test/integration/adapter.test.ts | 32 +++++-- .../sources/gsr/test/integration/fixtures.ts | 26 +++++- 11 files changed, 217 insertions(+), 42 deletions(-) create mode 100644 packages/sources/gsr/src/endpoint/crypto-lwba.ts create mode 100644 packages/sources/gsr/src/transport/crypto-lwba.ts diff --git a/packages/sources/gsr/src/config/index.ts b/packages/sources/gsr/src/config/index.ts index a5ff6b34bb..8215ecb5af 100644 --- a/packages/sources/gsr/src/config/index.ts +++ b/packages/sources/gsr/src/config/index.ts @@ -27,4 +27,30 @@ export const config = new AdapterConfig({ required: true, sensitive: true, }, + LWBA_API_ENDPOINT: { + type: 'string', + description: 'The HTTP API endpoint to use to retrieve tokens for LWBA', + default: 'https://oracle.pre-prod.gsr.io/v1', + }, + LWBA_WS_API_ENDPOINT: { + type: 'string', + description: 'The WS API endpoint to use for LWBA data', + default: 'wss://oracle.pre-prod.gsr.io/oracle', + }, + LWBA_WS_USER_ID: { + type: 'string', + description: 'The user ID used to authenticate to the LWBA_API_ENDPOINT', + required: true, + }, + LWBA_WS_PUBLIC_KEY: { + type: 'string', + description: 'The public key used to authenticate to the LWBA_API_ENDPOINT', + required: true, + }, + LWBA_WS_PRIVATE_KEY: { + type: 'string', + description: 'The private key used to authenticate to the LWBA_API_ENDPOINT', + required: true, + sensitive: true, + }, }) diff --git a/packages/sources/gsr/src/endpoint/crypto-lwba.ts b/packages/sources/gsr/src/endpoint/crypto-lwba.ts new file mode 100644 index 0000000000..650a36f9ee --- /dev/null +++ b/packages/sources/gsr/src/endpoint/crypto-lwba.ts @@ -0,0 +1,32 @@ +import { + AdapterEndpoint, + priceEndpointInputParametersDefinition, +} from '@chainlink/external-adapter-framework/adapter' +import { config } from '../config' +import { transport } from '../transport/crypto-lwba' +import { InputParameters } from '@chainlink/external-adapter-framework/validation' + +export const inputParameters = new InputParameters(priceEndpointInputParametersDefinition) + +// Additional fields are due to lwba endpoint +type EndpointResponse = { + Result: null + Data: { + mid: number + bid: number + ask: number + } +} + +export type BaseEndpointTypes = { + Parameters: typeof inputParameters.definition + Settings: typeof config.settings + Response: EndpointResponse +} + +export const endpoint = new AdapterEndpoint({ + name: 'crypto-lwba', + aliases: ['cryptolwba', 'crypto_lwba'], + transport, + inputParameters: inputParameters, +}) diff --git a/packages/sources/gsr/src/endpoint/index.ts b/packages/sources/gsr/src/endpoint/index.ts index 11a44912b4..18bb9a6c3b 100644 --- a/packages/sources/gsr/src/endpoint/index.ts +++ b/packages/sources/gsr/src/endpoint/index.ts @@ -1 +1,2 @@ export { endpoint as price } from './price' +export { endpoint as crypto_lwba } from './crypto-lwba' diff --git a/packages/sources/gsr/src/endpoint/price.ts b/packages/sources/gsr/src/endpoint/price.ts index 30aa7d020e..ea7724d9c2 100644 --- a/packages/sources/gsr/src/endpoint/price.ts +++ b/packages/sources/gsr/src/endpoint/price.ts @@ -9,24 +9,15 @@ import { transport } from '../transport/price' const inputParameters = new InputParameters(priceEndpointInputParametersDefinition) -// Additional fields are due to lwba endpoint -type EPResponse = SingleNumberResultResponse & { - Data: { - mid: number - bid: number - ask: number - } -} - export type BaseEndpointTypes = { Parameters: typeof inputParameters.definition Settings: typeof config.settings - Response: EPResponse + Response: SingleNumberResultResponse } export const endpoint = new CryptoPriceEndpoint({ name: 'price', - aliases: ['price-ws', 'crypto', 'crypto-lwba', 'cryptolwba', 'crypto_lwba'], + aliases: ['price-ws', 'crypto'], transport, inputParameters, }) diff --git a/packages/sources/gsr/src/index.ts b/packages/sources/gsr/src/index.ts index bce6ef7c6d..424d041d17 100644 --- a/packages/sources/gsr/src/index.ts +++ b/packages/sources/gsr/src/index.ts @@ -1,12 +1,12 @@ import { expose, ServerInstance } from '@chainlink/external-adapter-framework' import { PriceAdapter } from '@chainlink/external-adapter-framework/adapter' import { config } from './config' -import { price } from './endpoint' +import { price, crypto_lwba } from './endpoint' export const adapter = new PriceAdapter({ defaultEndpoint: price.name, name: 'GSR', - endpoints: [price], + endpoints: [price, crypto_lwba], config, }) diff --git a/packages/sources/gsr/src/transport/authutils.ts b/packages/sources/gsr/src/transport/authutils.ts index 1321b7f2b4..f250e4d629 100644 --- a/packages/sources/gsr/src/transport/authutils.ts +++ b/packages/sources/gsr/src/transport/authutils.ts @@ -1,6 +1,5 @@ import crypto from 'crypto' import axios from 'axios' -import { config } from '../config' import { makeLogger } from '@chainlink/external-adapter-framework/util' const logger = makeLogger('GSR Auth Token Utils') @@ -28,15 +27,18 @@ const generateSignature = (userId: string, publicKey: string, privateKey: string .update(`userId=${userId}&apiKey=${publicKey}&ts=${ts}`) .digest('hex') -export const getToken = async (settings: typeof config.settings) => { +// restApiEndpoint is used for token auth +export const getToken = async ( + restApiEndpoint: string, + userId: string, + publicKey: string, + privateKey: string, +) => { logger.debug('Fetching new access token') - const userId = settings.WS_USER_ID - const publicKey = settings.WS_PUBLIC_KEY - const privateKey = settings.WS_PRIVATE_KEY const ts = currentTimeNanoSeconds() const signature = generateSignature(userId, publicKey, privateKey, ts) - const response = await axios.post(`${settings.API_ENDPOINT}/token`, { + const response = await axios.post(`${restApiEndpoint}/token`, { apiKey: publicKey, userId, ts, @@ -44,7 +46,7 @@ export const getToken = async (settings: typeof config.settings) => { }) if (!response.data.success) { - logger.error('Unable to get access token') + logger.error(`Unable to get access token: ${response.data.error}`) throw new Error(response.data.error) } diff --git a/packages/sources/gsr/src/transport/crypto-lwba.ts b/packages/sources/gsr/src/transport/crypto-lwba.ts new file mode 100644 index 0000000000..66b00da07a --- /dev/null +++ b/packages/sources/gsr/src/transport/crypto-lwba.ts @@ -0,0 +1,89 @@ +import { WebSocketTransport } from '@chainlink/external-adapter-framework/transports' +import { BaseEndpointTypes } from '../endpoint/crypto-lwba' +import { getToken } from './authutils' +import { makeLogger, ProviderResult } from '@chainlink/external-adapter-framework/util' + +const logger = makeLogger('GSR WS LWBA price') + +type WsMessage = { + type: string + data: { + symbol: string + price: number + bidPrice: number + askPrice: number + ts: number + } +} + +export type WsTransportTypes = BaseEndpointTypes & { + Provider: { + WsMessage: WsMessage + } +} + +export const transport = new WebSocketTransport({ + url: (context) => context.adapterSettings.LWBA_WS_API_ENDPOINT, + options: async (context) => ({ + headers: { + 'x-auth-token': await getToken( + context.adapterSettings.LWBA_API_ENDPOINT, + context.adapterSettings.LWBA_WS_USER_ID, + context.adapterSettings.LWBA_WS_PUBLIC_KEY, + context.adapterSettings.LWBA_WS_PRIVATE_KEY, + ), + 'x-auth-userid': context.adapterSettings.LWBA_WS_USER_ID, + }, + }), + + handlers: { + open: () => { + return + }, + message(message): ProviderResult[] | undefined { + if (message.type == 'error') { + logger.error(`Got error from DP: ${JSON.stringify(message)}`) + return + } else if (message.type != 'ticker') { + return + } + + const pair = message.data.symbol.split('.') + if (pair.length != 2) { + logger.warn(`Got a price update with an unknown pair: ${message.data.symbol}`) + return + } + + return [ + { + params: { + base: pair[0].toString(), + quote: pair[1].toString(), + }, + response: { + result: null, + data: { + mid: message.data.price, + bid: message.data.bidPrice, + ask: message.data.askPrice, + }, + timestamps: { + providerIndicatedTimeUnixMs: Math.round(message.data.ts / 1e6), // Value from provider is in nanoseconds + }, + }, + }, + ] + }, + }, + + builders: { + subscribeMessage: (params) => ({ + action: 'subscribe', + symbols: [`${params.base.toUpperCase()}.${params.quote.toUpperCase()}`], + }), + unsubscribeMessage: (params) => ({ + action: 'unsubscribe', + symbols: [`${params.base.toUpperCase()}.${params.quote.toUpperCase()}`], + }), + }, +}) diff --git a/packages/sources/gsr/src/transport/price.ts b/packages/sources/gsr/src/transport/price.ts index d91d7f5a12..8e23797a9b 100644 --- a/packages/sources/gsr/src/transport/price.ts +++ b/packages/sources/gsr/src/transport/price.ts @@ -10,8 +10,6 @@ type WsMessage = { data: { symbol: string price: number - bidPrice: number - askPrice: number ts: number } } @@ -26,7 +24,12 @@ export const transport = new WebSocketTransport({ url: (context) => context.adapterSettings.WS_API_ENDPOINT, options: async (context) => ({ headers: { - 'x-auth-token': await getToken(context.adapterSettings), + 'x-auth-token': await getToken( + context.adapterSettings.API_ENDPOINT, + context.adapterSettings.WS_USER_ID, + context.adapterSettings.WS_PUBLIC_KEY, + context.adapterSettings.WS_PRIVATE_KEY, + ), 'x-auth-userid': context.adapterSettings.WS_USER_ID, }, }), @@ -58,9 +61,6 @@ export const transport = new WebSocketTransport({ result: message.data.price, data: { result: message.data.price, - mid: message.data.price, - bid: message.data.bidPrice, - ask: message.data.askPrice, }, timestamps: { providerIndicatedTimeUnixMs: Math.round(message.data.ts / 1e6), // Value from provider is in nanoseconds diff --git a/packages/sources/gsr/test/integration/__snapshots__/adapter.test.ts.snap b/packages/sources/gsr/test/integration/__snapshots__/adapter.test.ts.snap index 0651dfd202..c4af773e7a 100644 --- a/packages/sources/gsr/test/integration/__snapshots__/adapter.test.ts.snap +++ b/packages/sources/gsr/test/integration/__snapshots__/adapter.test.ts.snap @@ -1,14 +1,13 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP -exports[`websocket websocket endpoint lwba endpoint alias should return success 1`] = ` +exports[`websocket websocket endpoint lwba endpoint should return success 1`] = ` { "data": { "ask": 1235, "bid": 1233, "mid": 1234, - "result": 1234, }, - "result": 1234, + "result": null, "statusCode": 200, "timestamps": { "providerDataReceivedUnixMs": Any, @@ -21,9 +20,6 @@ exports[`websocket websocket endpoint lwba endpoint alias should return success exports[`websocket websocket endpoint should return success 1`] = ` { "data": { - "ask": 1235, - "bid": 1233, - "mid": 1234, "result": 1234, }, "result": 1234, diff --git a/packages/sources/gsr/test/integration/adapter.test.ts b/packages/sources/gsr/test/integration/adapter.test.ts index acc36c4835..673a552381 100644 --- a/packages/sources/gsr/test/integration/adapter.test.ts +++ b/packages/sources/gsr/test/integration/adapter.test.ts @@ -1,5 +1,10 @@ import { WebSocketClassProvider } from '@chainlink/external-adapter-framework/transports' -import { mockTokenSuccess, mockWebSocketServer } from './fixtures' +import { + mockTokenSuccess, + mockWebSocketServer, + mockLwbaTokenSuccess, + mockLwbaWebSocketServer, +} from './fixtures' import { TestAdapter, setEnvVariables, @@ -11,13 +16,20 @@ import FakeTimers from '@sinonjs/fake-timers' describe('websocket', () => { let spy: jest.SpyInstance let mockWsServer: MockWebsocketServer | undefined + let mockLwbaWsServer: MockWebsocketServer | undefined let testAdapter: TestAdapter let oldEnv: NodeJS.ProcessEnv const wsEndpoint = 'ws://localhost:9090' + const lwbaWsEndpoint = 'ws://localhost:9091' const data = { base: 'ETH', quote: 'USD', } + const lwbaData = { + base: 'ETH', + quote: 'USD', + endpoint: 'crypto-lwba', + } beforeAll(async () => { oldEnv = JSON.parse(JSON.stringify(process.env)) @@ -25,13 +37,19 @@ describe('websocket', () => { process.env['WS_USER_ID'] = process.env['WS_USER_ID'] || 'test-user-id' process.env['WS_PUBLIC_KEY'] = process.env['WS_PUBLIC_KEY'] || 'test-pub-key' process.env['WS_PRIVATE_KEY'] = process.env['WS_PRIVATE_KEY'] || 'test-priv-key' + process.env['LWBA_WS_API_ENDPOINT'] = lwbaWsEndpoint + process.env['LWBA_WS_USER_ID'] = process.env['LWBA_WS_USER_ID'] || 'test-user-id' + process.env['LWBA_WS_PUBLIC_KEY'] = process.env['LWBA_WS_PUBLIC_KEY'] || 'test-pub-key' + process.env['LWBA_WS_PRIVATE_KEY'] = process.env['LWBA_WS_PRIVATE_KEY'] || 'test-priv-key' const mockDate = new Date('2022-05-10T16:09:27.193Z') spy = jest.spyOn(Date, 'now').mockReturnValue(mockDate.getTime()) mockTokenSuccess() + mockLwbaTokenSuccess() // Start mock web socket server mockWebSocketProvider(WebSocketClassProvider) mockWsServer = mockWebSocketServer(wsEndpoint) + mockLwbaWsServer = mockLwbaWebSocketServer(lwbaWsEndpoint) const adapter = (await import('./../../src')).adapter testAdapter = await TestAdapter.startWithMockedCache(adapter, { @@ -41,13 +59,15 @@ describe('websocket', () => { // Send initial request to start background execute and wait for cache to be filled with results await testAdapter.request(data) - await testAdapter.waitForCache() + await testAdapter.request(lwbaData) + await testAdapter.waitForCache(2) }) afterAll(async () => { spy.mockRestore() setEnvVariables(oldEnv) mockWsServer?.close() + mockLwbaWsServer?.close() testAdapter.clock?.uninstall() await testAdapter.api.close() }) @@ -63,12 +83,8 @@ describe('websocket', () => { }) }) - it('lwba endpoint alias should return success', async () => { - const response = await testAdapter.request({ - base: 'ETH', - quote: 'USD', - endpoint: 'crypto-lwba', - }) + it('lwba endpoint should return success', async () => { + const response = await testAdapter.request(lwbaData) expect(response.json()).toMatchSnapshot({ timestamps: { providerDataReceivedUnixMs: expect.any(Number), diff --git a/packages/sources/gsr/test/integration/fixtures.ts b/packages/sources/gsr/test/integration/fixtures.ts index 3d42d55db2..41b8d104c6 100644 --- a/packages/sources/gsr/test/integration/fixtures.ts +++ b/packages/sources/gsr/test/integration/fixtures.ts @@ -1,8 +1,8 @@ import nock from 'nock' import { MockWebsocketServer } from '@chainlink/external-adapter-framework/util/testing-utils' -export const mockTokenSuccess = (): nock.Scope => - nock('https://oracle.prod.gsr.io', { +const generateMockTokenSuccess = (basePath: string): nock.Scope => + nock(basePath, { encodedQueryParams: true, }) .post('/v1/token', { @@ -32,6 +32,9 @@ export const mockTokenSuccess = (): nock.Scope => ) .persist() +export const mockTokenSuccess = () => generateMockTokenSuccess('https://oracle.prod.gsr.io') +export const mockLwbaTokenSuccess = () => generateMockTokenSuccess('https://oracle.pre-prod.gsr.io') + const base = 'ETH' const quote = 'USD' const price = 1234 @@ -40,6 +43,25 @@ const askPrice = 1235 const time = 1669345393482 export const mockWebSocketServer = (URL: string) => { + const mockWsServer = new MockWebsocketServer(URL, { mock: false }) + mockWsServer.on('connection', (socket) => { + socket.on('message', () => { + socket.send( + JSON.stringify({ + type: 'ticker', + data: { + symbol: `${base.toUpperCase()}.${quote.toUpperCase()}`, + price, + ts: time * 1e6, + }, + }), + ) + }) + }) + return mockWsServer +} + +export const mockLwbaWebSocketServer = (URL: string) => { const mockWsServer = new MockWebsocketServer(URL, { mock: false }) mockWsServer.on('connection', (socket) => { socket.on('message', () => { From cf383fcf83b01d334798253cf3127b3c51b11ff6 Mon Sep 17 00:00:00 2001 From: Matthew McAllister Date: Thu, 24 Aug 2023 18:32:50 -0400 Subject: [PATCH 06/10] back to price endpoint alias due to GSR DP decision --- packages/sources/gsr/src/config/index.ts | 26 ------ .../sources/gsr/src/endpoint/crypto-lwba.ts | 32 ------- packages/sources/gsr/src/endpoint/index.ts | 1 - packages/sources/gsr/src/endpoint/price.ts | 12 ++- packages/sources/gsr/src/index.ts | 4 +- .../sources/gsr/src/transport/crypto-lwba.ts | 89 ------------------- packages/sources/gsr/src/transport/price.ts | 5 ++ .../__snapshots__/adapter.test.ts.snap | 6 +- .../gsr/test/integration/adapter.test.ts | 19 +--- .../sources/gsr/test/integration/fixtures.ts | 20 ----- 10 files changed, 24 insertions(+), 190 deletions(-) delete mode 100644 packages/sources/gsr/src/endpoint/crypto-lwba.ts delete mode 100644 packages/sources/gsr/src/transport/crypto-lwba.ts diff --git a/packages/sources/gsr/src/config/index.ts b/packages/sources/gsr/src/config/index.ts index 8215ecb5af..a5ff6b34bb 100644 --- a/packages/sources/gsr/src/config/index.ts +++ b/packages/sources/gsr/src/config/index.ts @@ -27,30 +27,4 @@ export const config = new AdapterConfig({ required: true, sensitive: true, }, - LWBA_API_ENDPOINT: { - type: 'string', - description: 'The HTTP API endpoint to use to retrieve tokens for LWBA', - default: 'https://oracle.pre-prod.gsr.io/v1', - }, - LWBA_WS_API_ENDPOINT: { - type: 'string', - description: 'The WS API endpoint to use for LWBA data', - default: 'wss://oracle.pre-prod.gsr.io/oracle', - }, - LWBA_WS_USER_ID: { - type: 'string', - description: 'The user ID used to authenticate to the LWBA_API_ENDPOINT', - required: true, - }, - LWBA_WS_PUBLIC_KEY: { - type: 'string', - description: 'The public key used to authenticate to the LWBA_API_ENDPOINT', - required: true, - }, - LWBA_WS_PRIVATE_KEY: { - type: 'string', - description: 'The private key used to authenticate to the LWBA_API_ENDPOINT', - required: true, - sensitive: true, - }, }) diff --git a/packages/sources/gsr/src/endpoint/crypto-lwba.ts b/packages/sources/gsr/src/endpoint/crypto-lwba.ts deleted file mode 100644 index 650a36f9ee..0000000000 --- a/packages/sources/gsr/src/endpoint/crypto-lwba.ts +++ /dev/null @@ -1,32 +0,0 @@ -import { - AdapterEndpoint, - priceEndpointInputParametersDefinition, -} from '@chainlink/external-adapter-framework/adapter' -import { config } from '../config' -import { transport } from '../transport/crypto-lwba' -import { InputParameters } from '@chainlink/external-adapter-framework/validation' - -export const inputParameters = new InputParameters(priceEndpointInputParametersDefinition) - -// Additional fields are due to lwba endpoint -type EndpointResponse = { - Result: null - Data: { - mid: number - bid: number - ask: number - } -} - -export type BaseEndpointTypes = { - Parameters: typeof inputParameters.definition - Settings: typeof config.settings - Response: EndpointResponse -} - -export const endpoint = new AdapterEndpoint({ - name: 'crypto-lwba', - aliases: ['cryptolwba', 'crypto_lwba'], - transport, - inputParameters: inputParameters, -}) diff --git a/packages/sources/gsr/src/endpoint/index.ts b/packages/sources/gsr/src/endpoint/index.ts index 18bb9a6c3b..11a44912b4 100644 --- a/packages/sources/gsr/src/endpoint/index.ts +++ b/packages/sources/gsr/src/endpoint/index.ts @@ -1,2 +1 @@ export { endpoint as price } from './price' -export { endpoint as crypto_lwba } from './crypto-lwba' diff --git a/packages/sources/gsr/src/endpoint/price.ts b/packages/sources/gsr/src/endpoint/price.ts index ea7724d9c2..134672caa9 100644 --- a/packages/sources/gsr/src/endpoint/price.ts +++ b/packages/sources/gsr/src/endpoint/price.ts @@ -9,15 +9,23 @@ import { transport } from '../transport/price' const inputParameters = new InputParameters(priceEndpointInputParametersDefinition) +type EndpointResponse = SingleNumberResultResponse & { + Data: { + mid: number + ask: number + bid: number + } +} + export type BaseEndpointTypes = { Parameters: typeof inputParameters.definition Settings: typeof config.settings - Response: SingleNumberResultResponse + Response: EndpointResponse } export const endpoint = new CryptoPriceEndpoint({ name: 'price', - aliases: ['price-ws', 'crypto'], + aliases: ['price-ws', 'crypto', 'crypto-lwba', 'cryptolwba', 'crypto_lwba'], transport, inputParameters, }) diff --git a/packages/sources/gsr/src/index.ts b/packages/sources/gsr/src/index.ts index 424d041d17..bce6ef7c6d 100644 --- a/packages/sources/gsr/src/index.ts +++ b/packages/sources/gsr/src/index.ts @@ -1,12 +1,12 @@ import { expose, ServerInstance } from '@chainlink/external-adapter-framework' import { PriceAdapter } from '@chainlink/external-adapter-framework/adapter' import { config } from './config' -import { price, crypto_lwba } from './endpoint' +import { price } from './endpoint' export const adapter = new PriceAdapter({ defaultEndpoint: price.name, name: 'GSR', - endpoints: [price, crypto_lwba], + endpoints: [price], config, }) diff --git a/packages/sources/gsr/src/transport/crypto-lwba.ts b/packages/sources/gsr/src/transport/crypto-lwba.ts deleted file mode 100644 index 66b00da07a..0000000000 --- a/packages/sources/gsr/src/transport/crypto-lwba.ts +++ /dev/null @@ -1,89 +0,0 @@ -import { WebSocketTransport } from '@chainlink/external-adapter-framework/transports' -import { BaseEndpointTypes } from '../endpoint/crypto-lwba' -import { getToken } from './authutils' -import { makeLogger, ProviderResult } from '@chainlink/external-adapter-framework/util' - -const logger = makeLogger('GSR WS LWBA price') - -type WsMessage = { - type: string - data: { - symbol: string - price: number - bidPrice: number - askPrice: number - ts: number - } -} - -export type WsTransportTypes = BaseEndpointTypes & { - Provider: { - WsMessage: WsMessage - } -} - -export const transport = new WebSocketTransport({ - url: (context) => context.adapterSettings.LWBA_WS_API_ENDPOINT, - options: async (context) => ({ - headers: { - 'x-auth-token': await getToken( - context.adapterSettings.LWBA_API_ENDPOINT, - context.adapterSettings.LWBA_WS_USER_ID, - context.adapterSettings.LWBA_WS_PUBLIC_KEY, - context.adapterSettings.LWBA_WS_PRIVATE_KEY, - ), - 'x-auth-userid': context.adapterSettings.LWBA_WS_USER_ID, - }, - }), - - handlers: { - open: () => { - return - }, - message(message): ProviderResult[] | undefined { - if (message.type == 'error') { - logger.error(`Got error from DP: ${JSON.stringify(message)}`) - return - } else if (message.type != 'ticker') { - return - } - - const pair = message.data.symbol.split('.') - if (pair.length != 2) { - logger.warn(`Got a price update with an unknown pair: ${message.data.symbol}`) - return - } - - return [ - { - params: { - base: pair[0].toString(), - quote: pair[1].toString(), - }, - response: { - result: null, - data: { - mid: message.data.price, - bid: message.data.bidPrice, - ask: message.data.askPrice, - }, - timestamps: { - providerIndicatedTimeUnixMs: Math.round(message.data.ts / 1e6), // Value from provider is in nanoseconds - }, - }, - }, - ] - }, - }, - - builders: { - subscribeMessage: (params) => ({ - action: 'subscribe', - symbols: [`${params.base.toUpperCase()}.${params.quote.toUpperCase()}`], - }), - unsubscribeMessage: (params) => ({ - action: 'unsubscribe', - symbols: [`${params.base.toUpperCase()}.${params.quote.toUpperCase()}`], - }), - }, -}) diff --git a/packages/sources/gsr/src/transport/price.ts b/packages/sources/gsr/src/transport/price.ts index 8e23797a9b..9753d000c9 100644 --- a/packages/sources/gsr/src/transport/price.ts +++ b/packages/sources/gsr/src/transport/price.ts @@ -10,6 +10,8 @@ type WsMessage = { data: { symbol: string price: number + bidPrice: number + askPrice: number ts: number } } @@ -61,6 +63,9 @@ export const transport = new WebSocketTransport({ result: message.data.price, data: { result: message.data.price, + mid: message.data.price, + bid: message.data.bidPrice, + ask: message.data.askPrice, }, timestamps: { providerIndicatedTimeUnixMs: Math.round(message.data.ts / 1e6), // Value from provider is in nanoseconds diff --git a/packages/sources/gsr/test/integration/__snapshots__/adapter.test.ts.snap b/packages/sources/gsr/test/integration/__snapshots__/adapter.test.ts.snap index c4af773e7a..8fbaf9a4f3 100644 --- a/packages/sources/gsr/test/integration/__snapshots__/adapter.test.ts.snap +++ b/packages/sources/gsr/test/integration/__snapshots__/adapter.test.ts.snap @@ -6,8 +6,9 @@ exports[`websocket websocket endpoint lwba endpoint should return success 1`] = "ask": 1235, "bid": 1233, "mid": 1234, + "result": 1234, }, - "result": null, + "result": 1234, "statusCode": 200, "timestamps": { "providerDataReceivedUnixMs": Any, @@ -20,6 +21,9 @@ exports[`websocket websocket endpoint lwba endpoint should return success 1`] = exports[`websocket websocket endpoint should return success 1`] = ` { "data": { + "ask": 1235, + "bid": 1233, + "mid": 1234, "result": 1234, }, "result": 1234, diff --git a/packages/sources/gsr/test/integration/adapter.test.ts b/packages/sources/gsr/test/integration/adapter.test.ts index 673a552381..8cd9d2f34a 100644 --- a/packages/sources/gsr/test/integration/adapter.test.ts +++ b/packages/sources/gsr/test/integration/adapter.test.ts @@ -1,10 +1,5 @@ import { WebSocketClassProvider } from '@chainlink/external-adapter-framework/transports' -import { - mockTokenSuccess, - mockWebSocketServer, - mockLwbaTokenSuccess, - mockLwbaWebSocketServer, -} from './fixtures' +import { mockTokenSuccess, mockWebSocketServer } from './fixtures' import { TestAdapter, setEnvVariables, @@ -16,11 +11,9 @@ import FakeTimers from '@sinonjs/fake-timers' describe('websocket', () => { let spy: jest.SpyInstance let mockWsServer: MockWebsocketServer | undefined - let mockLwbaWsServer: MockWebsocketServer | undefined let testAdapter: TestAdapter let oldEnv: NodeJS.ProcessEnv const wsEndpoint = 'ws://localhost:9090' - const lwbaWsEndpoint = 'ws://localhost:9091' const data = { base: 'ETH', quote: 'USD', @@ -37,19 +30,13 @@ describe('websocket', () => { process.env['WS_USER_ID'] = process.env['WS_USER_ID'] || 'test-user-id' process.env['WS_PUBLIC_KEY'] = process.env['WS_PUBLIC_KEY'] || 'test-pub-key' process.env['WS_PRIVATE_KEY'] = process.env['WS_PRIVATE_KEY'] || 'test-priv-key' - process.env['LWBA_WS_API_ENDPOINT'] = lwbaWsEndpoint - process.env['LWBA_WS_USER_ID'] = process.env['LWBA_WS_USER_ID'] || 'test-user-id' - process.env['LWBA_WS_PUBLIC_KEY'] = process.env['LWBA_WS_PUBLIC_KEY'] || 'test-pub-key' - process.env['LWBA_WS_PRIVATE_KEY'] = process.env['LWBA_WS_PRIVATE_KEY'] || 'test-priv-key' const mockDate = new Date('2022-05-10T16:09:27.193Z') spy = jest.spyOn(Date, 'now').mockReturnValue(mockDate.getTime()) mockTokenSuccess() - mockLwbaTokenSuccess() // Start mock web socket server mockWebSocketProvider(WebSocketClassProvider) mockWsServer = mockWebSocketServer(wsEndpoint) - mockLwbaWsServer = mockLwbaWebSocketServer(lwbaWsEndpoint) const adapter = (await import('./../../src')).adapter testAdapter = await TestAdapter.startWithMockedCache(adapter, { @@ -59,15 +46,13 @@ describe('websocket', () => { // Send initial request to start background execute and wait for cache to be filled with results await testAdapter.request(data) - await testAdapter.request(lwbaData) - await testAdapter.waitForCache(2) + await testAdapter.waitForCache() }) afterAll(async () => { spy.mockRestore() setEnvVariables(oldEnv) mockWsServer?.close() - mockLwbaWsServer?.close() testAdapter.clock?.uninstall() await testAdapter.api.close() }) diff --git a/packages/sources/gsr/test/integration/fixtures.ts b/packages/sources/gsr/test/integration/fixtures.ts index 41b8d104c6..417162b890 100644 --- a/packages/sources/gsr/test/integration/fixtures.ts +++ b/packages/sources/gsr/test/integration/fixtures.ts @@ -33,7 +33,6 @@ const generateMockTokenSuccess = (basePath: string): nock.Scope => .persist() export const mockTokenSuccess = () => generateMockTokenSuccess('https://oracle.prod.gsr.io') -export const mockLwbaTokenSuccess = () => generateMockTokenSuccess('https://oracle.pre-prod.gsr.io') const base = 'ETH' const quote = 'USD' @@ -43,25 +42,6 @@ const askPrice = 1235 const time = 1669345393482 export const mockWebSocketServer = (URL: string) => { - const mockWsServer = new MockWebsocketServer(URL, { mock: false }) - mockWsServer.on('connection', (socket) => { - socket.on('message', () => { - socket.send( - JSON.stringify({ - type: 'ticker', - data: { - symbol: `${base.toUpperCase()}.${quote.toUpperCase()}`, - price, - ts: time * 1e6, - }, - }), - ) - }) - }) - return mockWsServer -} - -export const mockLwbaWebSocketServer = (URL: string) => { const mockWsServer = new MockWebsocketServer(URL, { mock: false }) mockWsServer.on('connection', (socket) => { socket.on('message', () => { From 07b5fa76119686d4cc08f5933b3861d4ef3e5e13 Mon Sep 17 00:00:00 2001 From: Matthew McAllister Date: Thu, 24 Aug 2023 18:44:06 -0400 Subject: [PATCH 07/10] review fix --- packages/sources/gsr/src/transport/price.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/sources/gsr/src/transport/price.ts b/packages/sources/gsr/src/transport/price.ts index 9753d000c9..9c74d3b2b4 100644 --- a/packages/sources/gsr/src/transport/price.ts +++ b/packages/sources/gsr/src/transport/price.ts @@ -80,11 +80,11 @@ export const transport = new WebSocketTransport({ // after you've already subscribed & unsubscribed to that pair on the same WS connection. subscribeMessage: (params) => ({ action: 'subscribe', - symbols: [`${params.base.toUpperCase()}.${params.quote.toUpperCase()}`], + symbols: [`${params.base}.${params.quote}`.toUpperCase()], }), unsubscribeMessage: (params) => ({ action: 'unsubscribe', - symbols: [`${params.base.toUpperCase()}.${params.quote.toUpperCase()}`], + symbols: [`${params.base}.${params.quote}`.toUpperCase()], }), }, }) From eff77a22105d04247c675e73261bc8ae293a5540 Mon Sep 17 00:00:00 2001 From: Matthew McAllister Date: Fri, 8 Sep 2023 14:44:26 -0400 Subject: [PATCH 08/10] update to use framework LWBA types --- packages/sources/gsr/src/endpoint/price.ts | 14 +++++--------- 1 file changed, 5 insertions(+), 9 deletions(-) diff --git a/packages/sources/gsr/src/endpoint/price.ts b/packages/sources/gsr/src/endpoint/price.ts index 134672caa9..9a40ba4143 100644 --- a/packages/sources/gsr/src/endpoint/price.ts +++ b/packages/sources/gsr/src/endpoint/price.ts @@ -1,5 +1,7 @@ import { CryptoPriceEndpoint, + DEFAULT_LWBA_ALIASES, + LwbaResponseDataFields, priceEndpointInputParametersDefinition, } from '@chainlink/external-adapter-framework/adapter' import { SingleNumberResultResponse } from '@chainlink/external-adapter-framework/util' @@ -9,23 +11,17 @@ import { transport } from '../transport/price' const inputParameters = new InputParameters(priceEndpointInputParametersDefinition) -type EndpointResponse = SingleNumberResultResponse & { - Data: { - mid: number - ask: number - bid: number - } -} +type OmitResultFromLwba = Omit export type BaseEndpointTypes = { Parameters: typeof inputParameters.definition Settings: typeof config.settings - Response: EndpointResponse + Response: OmitResultFromLwba & SingleNumberResultResponse } export const endpoint = new CryptoPriceEndpoint({ name: 'price', - aliases: ['price-ws', 'crypto', 'crypto-lwba', 'cryptolwba', 'crypto_lwba'], + aliases: ['price-ws', 'crypto', ...DEFAULT_LWBA_ALIASES], transport, inputParameters, }) From 680571d86f0d9bedab683afedf76c43409ad8108 Mon Sep 17 00:00:00 2001 From: Matthew McAllister Date: Mon, 11 Sep 2023 15:41:35 -0400 Subject: [PATCH 09/10] update readme endpoints --- packages/sources/gsr/README.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/packages/sources/gsr/README.md b/packages/sources/gsr/README.md index 491c8d563b..2d1c16130a 100644 --- a/packages/sources/gsr/README.md +++ b/packages/sources/gsr/README.md @@ -24,13 +24,13 @@ There are no rate limits for this adapter. ## Input Parameters -| Required? | Name | Description | Type | Options | Default | -| :-------: | :------: | :-----------------: | :----: | :------------------------------------------------------------------------------: | :-----: | -| | endpoint | The endpoint to use | string | [crypto](#price-endpoint), [price-ws](#price-endpoint), [price](#price-endpoint) | `price` | +| Required? | Name | Description | Type | Options | Default | +| :-------: | :------: | :-----------------: | :----: | :-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------: | :-----: | +| | endpoint | The endpoint to use | string | [crypto-lwba](#price-endpoint), [crypto](#price-endpoint), [crypto_lwba](#price-endpoint), [cryptolwba](#price-endpoint), [price-ws](#price-endpoint), [price](#price-endpoint) | `price` | ## Price Endpoint -Supported names for this endpoint are: `crypto`, `price`, `price-ws`. +Supported names for this endpoint are: `crypto`, `crypto-lwba`, `crypto_lwba`, `cryptolwba`, `price`, `price-ws`. ### Input Params From 2f234e4b84bdeac2a02fba0293959473c77970ec Mon Sep 17 00:00:00 2001 From: Matthew McAllister Date: Wed, 8 Nov 2023 12:41:25 -0500 Subject: [PATCH 10/10] number readability --- packages/sources/gsr/src/transport/authutils.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/sources/gsr/src/transport/authutils.ts b/packages/sources/gsr/src/transport/authutils.ts index f250e4d629..cffb78f7e5 100644 --- a/packages/sources/gsr/src/transport/authutils.ts +++ b/packages/sources/gsr/src/transport/authutils.ts @@ -19,7 +19,7 @@ interface TokenSuccess { type AccessTokenResponse = TokenError | TokenSuccess -const currentTimeNanoSeconds = (): number => new Date(Date.now()).getTime() * 1000000 +const currentTimeNanoSeconds = (): number => new Date(Date.now()).getTime() * 1_000_000 const generateSignature = (userId: string, publicKey: string, privateKey: string, ts: number) => crypto