Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feature/df 18971 gsr lwba endpoint #2938

Merged
merged 41 commits into from
Jan 11, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
41 commits
Select commit Hold shift + click to select a range
c5432a5
GSR LWBA endpoint
mmcallister-cll Aug 18, 2023
15d3ba9
add changeset
mmcallister-cll Aug 18, 2023
0f6f8bd
update integration test
mmcallister-cll Aug 18, 2023
4219093
update tests
mmcallister-cll Aug 18, 2023
a880d4d
Merge branch 'main' into feature/DF-18971-gsr-lwba-endpoint
mmcallister-cll Aug 18, 2023
3507f52
updates to separate price and lwba endpoints following discussion wit…
mmcallister-cll Aug 21, 2023
cf383fc
back to price endpoint alias due to GSR DP decision
mmcallister-cll Aug 24, 2023
07b5fa7
review fix
mmcallister-cll Aug 24, 2023
fb7c0e4
Merge branch 'main' into feature/DF-18971-gsr-lwba-endpoint
mmcallister-cll Aug 24, 2023
2a288ab
Merge branch 'main' into feature/DF-18971-gsr-lwba-endpoint
cl-ea Aug 25, 2023
dac0e36
Merge branch 'main' into feature/DF-18971-gsr-lwba-endpoint
cl-ea Aug 28, 2023
1cd834c
Merge branch 'main' into feature/DF-18971-gsr-lwba-endpoint
cl-ea Aug 29, 2023
d057635
Merge branch 'main' into feature/DF-18971-gsr-lwba-endpoint
cl-ea Aug 29, 2023
86d7906
Merge branch 'main' into feature/DF-18971-gsr-lwba-endpoint
cl-ea Aug 29, 2023
60ab992
Merge branch 'main' into feature/DF-18971-gsr-lwba-endpoint
cl-ea Aug 29, 2023
027f9d3
Merge branch 'main' into feature/DF-18971-gsr-lwba-endpoint
cl-ea Aug 29, 2023
d87c4ec
Merge branch 'main' into feature/DF-18971-gsr-lwba-endpoint
cl-ea Aug 30, 2023
392f520
Merge branch 'main' into feature/DF-18971-gsr-lwba-endpoint
cl-ea Aug 30, 2023
fb12735
Merge branch 'main' into feature/DF-18971-gsr-lwba-endpoint
cl-ea Aug 30, 2023
ec31575
Merge branch 'main' into feature/DF-18971-gsr-lwba-endpoint
cl-ea Aug 30, 2023
5e91bb9
Merge branch 'main' into feature/DF-18971-gsr-lwba-endpoint
cl-ea Aug 31, 2023
761b1ef
Merge branch 'main' into feature/DF-18971-gsr-lwba-endpoint
cl-ea Aug 31, 2023
1fa9f31
Merge branch 'main' into feature/DF-18971-gsr-lwba-endpoint
cl-ea Sep 1, 2023
6abcee1
Merge branch 'main' into feature/DF-18971-gsr-lwba-endpoint
cl-ea Sep 1, 2023
63d1c6a
Merge branch 'main' into feature/DF-18971-gsr-lwba-endpoint
cl-ea Sep 1, 2023
e04fcbd
Merge branch 'main' into feature/DF-18971-gsr-lwba-endpoint
cl-ea Sep 4, 2023
ecbff2b
Merge branch 'main' into feature/DF-18971-gsr-lwba-endpoint
cl-ea Sep 4, 2023
26270a5
Merge branch 'main' into feature/DF-18971-gsr-lwba-endpoint
cl-ea Sep 4, 2023
06b94ba
Merge branch 'main' into feature/DF-18971-gsr-lwba-endpoint
mmcallister-cll Sep 5, 2023
ac4d2b7
Merge branch 'main' into feature/DF-18971-gsr-lwba-endpoint
mmcallister-cll Sep 8, 2023
eff77a2
update to use framework LWBA types
mmcallister-cll Sep 8, 2023
348bb52
Merge branch 'main' into feature/DF-18971-gsr-lwba-endpoint
mmcallister-cll Sep 11, 2023
680571d
update readme endpoints
mmcallister-cll Sep 11, 2023
f2df764
Merge branch 'main' into feature/DF-18971-gsr-lwba-endpoint
mmcallister-cll Oct 25, 2023
6360e0f
Merge branch 'main' into feature/DF-18971-gsr-lwba-endpoint
mmcallister-cll Oct 27, 2023
d869b23
Merge branch 'main' into feature/DF-18971-gsr-lwba-endpoint
mmcallister-cll Nov 2, 2023
79208f6
Merge branch 'main' into feature/DF-18971-gsr-lwba-endpoint
mmcallister-cll Nov 7, 2023
2f234e4
number readability
mmcallister-cll Nov 8, 2023
3a230bb
Merge branch 'main' into feature/DF-18971-gsr-lwba-endpoint
mmcallister-cll Jan 8, 2024
142a380
Merge branch 'main' into feature/DF-18971-gsr-lwba-endpoint
mmcallister-cll Jan 9, 2024
8f825ca
Merge branch 'main' into feature/DF-18971-gsr-lwba-endpoint
mmcallister-cll Jan 11, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/rotten-humans-reply.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@chainlink/gsr-adapter': minor
---

Added LWBA endpoint for GSR EA
8 changes: 4 additions & 4 deletions packages/sources/gsr/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
8 changes: 6 additions & 2 deletions packages/sources/gsr/src/endpoint/price.ts
Original file line number Diff line number Diff line change
@@ -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'
Expand All @@ -14,15 +16,17 @@ const inputParameters = new InputParameters(priceEndpointInputParametersDefiniti
},
])

type OmitResultFromLwba = Omit<LwbaResponseDataFields, 'Result'>

export type BaseEndpointTypes = {
Parameters: typeof inputParameters.definition
Settings: typeof config.settings
Response: SingleNumberResultResponse
Response: OmitResultFromLwba & SingleNumberResultResponse
}

export const endpoint = new CryptoPriceEndpoint({
name: 'price',
aliases: ['price-ws', 'crypto'],
aliases: ['price-ws', 'crypto', ...DEFAULT_LWBA_ALIASES],
transport,
inputParameters,
})
2 changes: 1 addition & 1 deletion packages/sources/gsr/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
54 changes: 54 additions & 0 deletions packages/sources/gsr/src/transport/authutils.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
import crypto from 'crypto'
import axios from 'axios'
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() * 1_000_000

const generateSignature = (userId: string, publicKey: string, privateKey: string, ts: number) =>
crypto
.createHmac('sha256', privateKey)
.update(`userId=${userId}&apiKey=${publicKey}&ts=${ts}`)
.digest('hex')

// 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 ts = currentTimeNanoSeconds()
const signature = generateSignature(userId, publicKey, privateKey, ts)
const response = await axios.post<AccessTokenResponse>(`${restApiEndpoint}/token`, {
apiKey: publicKey,
userId,
ts,
signature,
})

if (!response.data.success) {
logger.error(`Unable to get access token: ${response.data.error}`)
throw new Error(response.data.error)
}

return response.data.token
}
66 changes: 14 additions & 52 deletions packages/sources/gsr/src/transport/price.ts
Original file line number Diff line number Diff line change
@@ -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')

Expand All @@ -12,6 +10,8 @@ type WsMessage = {
data: {
symbol: string
price: number
bidPrice: number
askPrice: number
ts: number
}
}
Expand All @@ -22,57 +22,16 @@ 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<AccessTokenResponse>(`${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<WsTransportTypes>({
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,
},
}),
Expand Down Expand Up @@ -104,6 +63,9 @@ export const transport = new WebSocketTransport<WsTransportTypes>({
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
Expand All @@ -118,11 +80,11 @@ export const transport = new WebSocketTransport<WsTransportTypes>({
// 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()],
}),
},
})
15 changes: 11 additions & 4 deletions packages/sources/gsr/test-payload.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,13 @@
{
"requests": [{
"from": "ETH",
"to": "USD"
}]
"requests": [
{
"from": "ETH",
"to": "USD"
},
{
"from": "ETH",
"to": "USD",
"endpoint": "crypto-lwba"
}
]
}
Original file line number Diff line number Diff line change
@@ -1,8 +1,29 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP

exports[`websocket websocket endpoint lwba endpoint should return success 1`] = `
{
"data": {
"ask": 1235,
"bid": 1233,
"mid": 1234,
"result": 1234,
},
"result": 1234,
"statusCode": 200,
"timestamps": {
"providerDataReceivedUnixMs": Any<Number>,
"providerDataStreamEstablishedUnixMs": Any<Number>,
"providerIndicatedTimeUnixMs": 1669345393482,
},
}
`;

exports[`websocket websocket endpoint should return success 1`] = `
{
"data": {
"ask": 1235,
"bid": 1233,
"mid": 1234,
"result": 1234,
},
"result": 1234,
Expand Down
15 changes: 15 additions & 0 deletions packages/sources/gsr/test/integration/adapter.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,11 @@ describe('websocket', () => {
base: 'ETH',
quote: 'USD',
}
const lwbaData = {
base: 'ETH',
quote: 'USD',
endpoint: 'crypto-lwba',
}

beforeAll(async () => {
oldEnv = JSON.parse(JSON.stringify(process.env))
Expand Down Expand Up @@ -63,6 +68,16 @@ describe('websocket', () => {
})
})

it('lwba endpoint should return success', async () => {
const response = await testAdapter.request(lwbaData)
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)
Expand Down
10 changes: 8 additions & 2 deletions packages/sources/gsr/test/integration/fixtures.ts
Original file line number Diff line number Diff line change
@@ -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', {
Expand Down Expand Up @@ -32,9 +32,13 @@ export const mockTokenSuccess = (): nock.Scope =>
)
.persist()

export const mockTokenSuccess = () => generateMockTokenSuccess('https://oracle.prod.gsr.io')

const base = 'ETH'
const quote = 'USD'
const price = 1234
const bidPrice = 1233
const askPrice = 1235
const time = 1669345393482

export const mockWebSocketServer = (URL: string) => {
Expand All @@ -47,6 +51,8 @@ export const mockWebSocketServer = (URL: string) => {
data: {
symbol: `${base.toUpperCase()}.${quote.toUpperCase()}`,
price,
bidPrice,
askPrice,
ts: time * 1e6,
},
}),
Expand Down
Loading