Skip to content

Commit 72544e2

Browse files
authored
Update bitgo-reserves (#3903)
1 parent c619154 commit 72544e2

File tree

12 files changed

+307
-94
lines changed

12 files changed

+307
-94
lines changed

.changeset/sixty-beds-compare.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
---
2+
'@chainlink/bitgo-reserves-test-adapter': minor
3+
'@chainlink/bitgo-reserves-adapter': minor
4+
---
5+
6+
Improve compatibility of API

packages/sources/bitgo-reserves-test/src/config/index.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ export const config = new AdapterConfig({
88
},
99
STAGING_PUBKEY: {
1010
description:
11-
'Public RSA key used for verifying data signature. Expected to be formatted as a single line eg: "-----BEGIN PUBLIC KEY-----\\n...contents...\\n-----END PUBLIC KEY-----"',
11+
'Public RSA key used for verifying data signature. Expected to be formatted as a single line eg: "-----BEGIN PUBLIC KEY-----\n...contents...\n-----END PUBLIC KEY-----"',
1212
type: 'string',
1313
required: true,
1414
},
@@ -19,7 +19,7 @@ export const config = new AdapterConfig({
1919
},
2020
TEST_PUBKEY: {
2121
description:
22-
'Public RSA key used for verifying data signature. Expected to be formatted as a single line eg: "-----BEGIN PUBLIC KEY-----\\n...contents...\\n-----END PUBLIC KEY-----"',
22+
'Public RSA key used for verifying data signature. Expected to be formatted as a single line eg: "-----BEGIN PUBLIC KEY-----\n...contents...\n-----END PUBLIC KEY-----"',
2323
type: 'string',
2424
required: true,
2525
},

packages/sources/bitgo-reserves-test/src/index.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,13 @@ export const adapter = new Adapter({
88
name: 'BITGO_RESERVES-TEST',
99
config,
1010
endpoints: [reservesStaging, reservesTest],
11+
rateLimiting: {
12+
tiers: {
13+
default: {
14+
rateLimit1m: 10, // setting a rate limit so we don't blast the server as fast as possible
15+
},
16+
},
17+
},
1118
})
1219

1320
export const server = (): Promise<ServerInstance | undefined> => expose(adapter)

packages/sources/bitgo-reserves-test/src/transport/util.ts

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ export interface DataSchema {
1010
export interface ResponseSchema {
1111
data: string // formatted & escaped DataSchema
1212
dataSignature: string
13-
ripcord: boolean
13+
ripcord: boolean | string
1414
}
1515

1616
export function createRequest(params: { [key: string]: string }[], endpoint: string) {
@@ -41,7 +41,10 @@ export function parseResponse(
4141
})
4242
}
4343

44-
if (payload.ripcord) {
44+
if (
45+
(typeof payload.ripcord === 'boolean' && payload.ripcord) ||
46+
(typeof payload.ripcord === 'string' && payload.ripcord.toUpperCase() === 'TRUE')
47+
) {
4548
return [
4649
{
4750
params: params[0],

packages/sources/bitgo-reserves-test/test/integration/__snapshots__/adapter.test.ts.snap

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,45 @@ exports[`execute reserves-staging endpoint should return success 1`] = `
1515
}
1616
`;
1717

18+
exports[`execute reserves-test endpoint should return ripcord - string 1`] = `
19+
{
20+
"errorMessage": "Ripcord indicator true",
21+
"ripcord": "True",
22+
"statusCode": 502,
23+
"timestamps": {
24+
"providerDataReceivedUnixMs": 978347471111,
25+
"providerDataRequestedUnixMs": 978347471111,
26+
},
27+
}
28+
`;
29+
30+
exports[`execute reserves-test endpoint should return ripcord 1`] = `
31+
{
32+
"errorMessage": "Ripcord indicator true",
33+
"ripcord": true,
34+
"statusCode": 502,
35+
"timestamps": {
36+
"providerDataReceivedUnixMs": 978347471111,
37+
"providerDataRequestedUnixMs": 978347471111,
38+
},
39+
}
40+
`;
41+
42+
exports[`execute reserves-test endpoint should return success - string ripcord 1`] = `
43+
{
44+
"data": {
45+
"result": 12345678.9,
46+
},
47+
"result": 12345678.9,
48+
"statusCode": 200,
49+
"timestamps": {
50+
"providerDataReceivedUnixMs": 978347471111,
51+
"providerDataRequestedUnixMs": 978347471111,
52+
"providerIndicatedTimeUnixMs": 1733793825000,
53+
},
54+
}
55+
`;
56+
1857
exports[`execute reserves-test endpoint should return success 1`] = `
1958
{
2059
"data": {

packages/sources/bitgo-reserves-test/test/integration/adapter.test.ts

Lines changed: 39 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,13 @@ import {
33
setEnvVariables,
44
} from '@chainlink/external-adapter-framework/util/testing-utils'
55
import * as nock from 'nock'
6-
import { mockStagingResponseSuccess, mockTestResponseSuccess } from './fixtures'
6+
import {
7+
mockStagingResponseSuccess,
8+
mockTestResponseRipcord,
9+
mockTestResponseStringRipcord,
10+
mockTestResponseSuccess,
11+
mockTestResponseSuccessStringFalse,
12+
} from './fixtures'
713

814
describe('execute', () => {
915
let spy: jest.SpyInstance
@@ -33,6 +39,11 @@ describe('execute', () => {
3339
})
3440
})
3541

42+
afterEach(() => {
43+
nock.cleanAll()
44+
testAdapter.mockCache?.cache.clear()
45+
})
46+
3647
afterAll(async () => {
3748
setEnvVariables(oldEnv)
3849
await testAdapter.api.close()
@@ -62,6 +73,33 @@ describe('execute', () => {
6273
expect(response.statusCode).toBe(200)
6374
expect(response.json()).toMatchSnapshot()
6475
})
76+
it('should return success - string ripcord', async () => {
77+
const data = {
78+
endpoint: 'reserves-test',
79+
}
80+
mockTestResponseSuccessStringFalse()
81+
const response = await testAdapter.request(data)
82+
expect(response.statusCode).toBe(200)
83+
expect(response.json()).toMatchSnapshot()
84+
})
85+
it('should return ripcord', async () => {
86+
const data = {
87+
endpoint: 'reserves-test',
88+
}
89+
mockTestResponseRipcord()
90+
const response = await testAdapter.request(data)
91+
expect(response.statusCode).toBe(502)
92+
expect(response.json()).toMatchSnapshot()
93+
})
94+
it('should return ripcord - string', async () => {
95+
const data = {
96+
endpoint: 'reserves-test',
97+
}
98+
mockTestResponseStringRipcord()
99+
const response = await testAdapter.request(data)
100+
expect(response.statusCode).toBe(502)
101+
expect(response.json()).toMatchSnapshot()
102+
})
65103
})
66104
// Note: issues with mock verifier prevent further tests
67105
})

packages/sources/bitgo-reserves-test/test/integration/fixtures.ts

Lines changed: 48 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -5,47 +5,57 @@ export const mockTestResponseSuccess = (): nock.Scope =>
55
encodedQueryParams: true,
66
})
77
.get('/')
8-
.reply(
9-
200,
10-
() => ({
11-
data: '{"totalReserve":"12345678.90","cashReserve":"2345678.90","investedReserve":"10000000.00","lastUpdated":"2024-12-10T01:23:45Z"}',
12-
dataSignature: 'testsig',
13-
lastUpdated: '2024-10-01T01:23:45Z',
14-
}),
15-
[
16-
'Content-Type',
17-
'application/json',
18-
'Connection',
19-
'close',
20-
'Vary',
21-
'Accept-Encoding',
22-
'Vary',
23-
'Origin',
24-
],
25-
)
26-
.persist()
8+
.reply(200, () => ({
9+
data: '{"totalReserve":"12345678.90","cashReserve":"2345678.90","investedReserve":"10000000.00","lastUpdated":"2024-12-10T01:23:45Z"}',
10+
dataSignature: 'testsig',
11+
lastUpdated: '2024-10-01T01:23:45Z',
12+
ripcord: false,
13+
}))
14+
15+
export const mockTestResponseRipcord = (): nock.Scope =>
16+
nock('http://test-endpoint.com', {
17+
encodedQueryParams: true,
18+
})
19+
.get('/')
20+
.reply(200, () => ({
21+
data: '{"totalReserve":"12345678.90","cashReserve":"2345678.90","investedReserve":"10000000.00","lastUpdated":"2024-12-10T01:23:45Z"}',
22+
dataSignature: 'testsig',
23+
lastUpdated: '2024-10-01T01:23:45Z',
24+
ripcord: true,
25+
}))
26+
27+
export const mockTestResponseSuccessStringFalse = (): nock.Scope =>
28+
nock('http://test-endpoint.com', {
29+
encodedQueryParams: true,
30+
})
31+
.get('/')
32+
.reply(200, () => ({
33+
data: '{"totalReserve":"12345678.90","cashReserve":"2345678.90","investedReserve":"10000000.00","lastUpdated":"2024-12-10T01:23:45Z"}',
34+
dataSignature: 'testsig',
35+
lastUpdated: '2024-10-01T01:23:45Z',
36+
ripcord: 'False',
37+
}))
38+
39+
export const mockTestResponseStringRipcord = (): nock.Scope =>
40+
nock('http://test-endpoint.com', {
41+
encodedQueryParams: true,
42+
})
43+
.get('/')
44+
.reply(200, () => ({
45+
data: '{"totalReserve":"12345678.90","cashReserve":"2345678.90","investedReserve":"10000000.00","lastUpdated":"2024-12-10T01:23:45Z"}',
46+
dataSignature: 'testsig',
47+
lastUpdated: '2024-10-01T01:23:45Z',
48+
ripcord: 'True',
49+
}))
2750

2851
export const mockStagingResponseSuccess = (): nock.Scope =>
2952
nock('http://staging-endpoint.com', {
3053
encodedQueryParams: true,
3154
})
3255
.get('/')
33-
.reply(
34-
200,
35-
() => ({
36-
data: '{"totalReserve":"98765432.10","cashReserve":"8765432.10","investedReserve":"20000000.00","lastUpdated":"2024-12-10T01:23:46Z"}',
37-
dataSignature: 'stagingsig',
38-
lastUpdated: '2024-10-01T01:23:46Z',
39-
}),
40-
[
41-
'Content-Type',
42-
'application/json',
43-
'Connection',
44-
'close',
45-
'Vary',
46-
'Accept-Encoding',
47-
'Vary',
48-
'Origin',
49-
],
50-
)
51-
.persist()
56+
.reply(200, () => ({
57+
data: '{"totalReserve":"98765432.10","cashReserve":"8765432.10","investedReserve":"20000000.00","lastUpdated":"2024-12-10T01:23:46Z"}',
58+
dataSignature: 'stagingsig',
59+
lastUpdated: '2024-10-01T01:23:46Z',
60+
ripcord: false,
61+
}))

packages/sources/bitgo-reserves/src/config/index.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ export const config = new AdapterConfig({
88
},
99
VERIFICATION_PUBKEY: {
1010
description:
11-
'Public RSA key used for verifying data signature for Go USD. Expected to be formatted as a single line eg: "-----BEGIN PUBLIC KEY-----\\n...contents...\\n-----END PUBLIC KEY-----"',
11+
'Public RSA key used for verifying data signature for Go USD. Expected to be formatted as a single line eg: "-----BEGIN PUBLIC KEY-----\n...contents...\n-----END PUBLIC KEY-----"',
1212
type: 'string',
1313
default: '',
1414
},

packages/sources/bitgo-reserves/src/transport/reserves.ts

Lines changed: 31 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -3,9 +3,12 @@ import {
33
HttpTransportConfig,
44
TransportDependencies,
55
} from '@chainlink/external-adapter-framework/transports'
6-
import { BaseEndpointTypes } from '../endpoint/reserves'
7-
import * as crypto from 'crypto'
6+
import { makeLogger } from '@chainlink/external-adapter-framework/util'
87
import { AdapterInputError } from '@chainlink/external-adapter-framework/validation/error'
8+
import * as crypto from 'crypto'
9+
import { BaseEndpointTypes } from '../endpoint/reserves'
10+
11+
const logger = makeLogger('ReservesTransport')
912

1013
export interface DataSchema {
1114
totalReserve: string
@@ -17,7 +20,7 @@ export interface DataSchema {
1720
export interface ResponseSchema {
1821
data: string // formatted & escaped DataSchema
1922
dataSignature: string
20-
ripcord: boolean
23+
ripcord: string | boolean
2124
}
2225

2326
export type HttpTransportTypes = BaseEndpointTypes & {
@@ -99,7 +102,10 @@ export const httpTransport = new ReservesHttpTransport({
99102
})
100103
}
101104

102-
if (payload.ripcord) {
105+
if (
106+
(typeof payload.ripcord === 'boolean' && payload.ripcord) ||
107+
(typeof payload.ripcord === 'string' && payload.ripcord.toUpperCase() === 'TRUE')
108+
) {
103109
return [
104110
{
105111
params: params[0],
@@ -114,17 +120,31 @@ export const httpTransport = new ReservesHttpTransport({
114120

115121
const verifier = crypto.createVerify('sha256')
116122
verifier.update(payload.data)
117-
const verified = verifier.verify(
118-
getCreds(params[0].client, httpTransport.endpoint, httpTransport.pubkey).key,
119-
payload.dataSignature,
120-
'base64',
121-
)
122-
if (!verified) {
123+
124+
try {
125+
const verified = verifier.verify(
126+
getCreds(params[0].client, httpTransport.endpoint, httpTransport.pubkey).key,
127+
payload.dataSignature,
128+
'base64',
129+
)
130+
if (!verified) {
131+
return params.map((param) => {
132+
return {
133+
params: param,
134+
response: {
135+
errorMessage: `Data verification failed`,
136+
statusCode: 502,
137+
},
138+
}
139+
})
140+
}
141+
} catch (e) {
142+
logger.error(e)
123143
return params.map((param) => {
124144
return {
125145
params: param,
126146
response: {
127-
errorMessage: `Data verification failed`,
147+
errorMessage: `Data verification failed ${e}`,
128148
statusCode: 502,
129149
},
130150
}

packages/sources/bitgo-reserves/test/integration/__snapshots__/adapter.test.ts.snap

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,29 @@
11
// Jest Snapshot v1, https://goo.gl/fbAQLP
22

3+
exports[`execute reserves endpoint should return fail - ripcord 1`] = `
4+
{
5+
"errorMessage": "Ripcord indicator true",
6+
"ripcord": true,
7+
"statusCode": 502,
8+
"timestamps": {
9+
"providerDataReceivedUnixMs": 978347471111,
10+
"providerDataRequestedUnixMs": 978347471111,
11+
},
12+
}
13+
`;
14+
15+
exports[`execute reserves endpoint should return fail - string ripcord 1`] = `
16+
{
17+
"errorMessage": "Ripcord indicator true",
18+
"ripcord": "True",
19+
"statusCode": 502,
20+
"timestamps": {
21+
"providerDataReceivedUnixMs": 978347471111,
22+
"providerDataRequestedUnixMs": 978347471111,
23+
},
24+
}
25+
`;
26+
327
exports[`execute reserves endpoint should return failure for non exist client 1`] = `
428
{
529
"error": {
@@ -11,6 +35,21 @@ exports[`execute reserves endpoint should return failure for non exist client 1`
1135
}
1236
`;
1337

38+
exports[`execute reserves endpoint should return success - string ripcord 1`] = `
39+
{
40+
"data": {
41+
"result": 12345678.9,
42+
},
43+
"result": 12345678.9,
44+
"statusCode": 200,
45+
"timestamps": {
46+
"providerDataReceivedUnixMs": 978347471111,
47+
"providerDataRequestedUnixMs": 978347471111,
48+
"providerIndicatedTimeUnixMs": 1733793825000,
49+
},
50+
}
51+
`;
52+
1453
exports[`execute reserves endpoint should return success 1`] = `
1554
{
1655
"data": {

0 commit comments

Comments
 (0)