Skip to content

Commit 5474f09

Browse files
committed
Add new package endpoint to anchorage
1 parent 479b592 commit 5474f09

File tree

9 files changed

+293
-9
lines changed

9 files changed

+293
-9
lines changed

.changeset/lazy-scissors-film.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
'@chainlink/anchorage-adapter': minor
3+
---
4+
5+
Add new endpoint

packages/sources/anchorage/src/config/index.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,11 @@ export const config = new AdapterConfig({
77
type: 'string',
88
required: true,
99
},
10+
COLLATERAL_API_KEY: {
11+
description: 'API key for Anchorage collateral_management endpoints',
12+
type: 'string',
13+
sensitive: true,
14+
},
1015
API_LIMIT: {
1116
description: 'The maximum number of results to request from the API',
1217
type: 'number',
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1 +1,2 @@
1+
export { endpoint as packages } from './packages'
12
export { endpoint as wallet } from './wallet'
Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
import { AdapterEndpoint } from '@chainlink/external-adapter-framework/adapter'
2+
import { SingleNumberResultResponse } from '@chainlink/external-adapter-framework/util'
3+
import { InputParameters } from '@chainlink/external-adapter-framework/validation'
4+
import { AdapterInputError } from '@chainlink/external-adapter-framework/validation/error'
5+
import { config } from '../config'
6+
import { packagesTransport } from '../transport/packages'
7+
8+
export const inputParameters = new InputParameters(
9+
{
10+
clientReferenceId: {
11+
required: true,
12+
type: 'string',
13+
description: 'Id of the vault',
14+
},
15+
assetType: {
16+
required: true,
17+
type: 'string',
18+
description: 'Asset ticker name',
19+
},
20+
},
21+
[
22+
{
23+
clientReferenceId: '123456',
24+
assetType: 'BTC',
25+
},
26+
],
27+
)
28+
29+
export type BaseEndpointTypes = {
30+
Parameters: typeof inputParameters.definition
31+
Response: SingleNumberResultResponse
32+
Settings: typeof config.settings
33+
}
34+
35+
export const endpoint = new AdapterEndpoint({
36+
name: 'packages',
37+
transport: packagesTransport,
38+
inputParameters,
39+
customInputValidation: (_, adapterSettings): AdapterInputError | undefined => {
40+
if (!adapterSettings.COLLATERAL_API_KEY) {
41+
throw new AdapterInputError({
42+
message: 'Missing COLLATERAL_API_KEY',
43+
statusCode: 400,
44+
})
45+
}
46+
return
47+
},
48+
})

packages/sources/anchorage/src/index.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,13 @@
11
import { expose, ServerInstance } from '@chainlink/external-adapter-framework'
2-
import { config } from './config'
3-
import { wallet } from './endpoint'
42
import { PoRAdapter } from '@chainlink/external-adapter-framework/adapter/por'
3+
import { config } from './config'
4+
import { packages, wallet } from './endpoint'
55

66
export const adapter = new PoRAdapter({
77
defaultEndpoint: wallet.name,
88
name: 'ANCHORAGE',
99
config,
10-
endpoints: [wallet],
10+
endpoints: [wallet, packages],
1111
rateLimiting: {
1212
tiers: {
1313
default: {
Lines changed: 110 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,110 @@
1+
import { EndpointContext } from '@chainlink/external-adapter-framework/adapter'
2+
import { TransportDependencies } from '@chainlink/external-adapter-framework/transports'
3+
import { SubscriptionTransport } from '@chainlink/external-adapter-framework/transports/abstract/subscription'
4+
import { AdapterResponse, makeLogger, sleep } from '@chainlink/external-adapter-framework/util'
5+
import { Requester } from '@chainlink/external-adapter-framework/util/requester'
6+
import { AdapterError } from '@chainlink/external-adapter-framework/validation/error'
7+
import { BaseEndpointTypes, inputParameters } from '../endpoint/packages'
8+
import { request } from './requester'
9+
10+
const logger = makeLogger('PackageTransport')
11+
12+
type RequestParams = typeof inputParameters.validated
13+
14+
interface PackageResponse {
15+
clientReferenceId: string
16+
collateralAssets: [
17+
{
18+
asset: {
19+
assetType: string
20+
}
21+
quantity: string
22+
},
23+
]
24+
}
25+
26+
export class PackagesTransport extends SubscriptionTransport<BaseEndpointTypes> {
27+
requester!: Requester
28+
settings!: BaseEndpointTypes['Settings']
29+
30+
async initialize(
31+
dependencies: TransportDependencies<BaseEndpointTypes>,
32+
adapterSettings: BaseEndpointTypes['Settings'],
33+
endpointName: string,
34+
transportName: string,
35+
): Promise<void> {
36+
await super.initialize(dependencies, adapterSettings, endpointName, transportName)
37+
this.requester = dependencies.requester
38+
this.settings = adapterSettings
39+
}
40+
41+
async backgroundHandler(context: EndpointContext<BaseEndpointTypes>, entries: RequestParams[]) {
42+
await Promise.all(entries.map(async (param) => this.handleRequest(param)))
43+
await sleep(context.adapterSettings.BACKGROUND_EXECUTE_MS)
44+
}
45+
46+
async handleRequest(param: RequestParams) {
47+
let response: AdapterResponse<BaseEndpointTypes['Response']>
48+
try {
49+
response = await this._handleRequest(param)
50+
} catch (e) {
51+
const errorMessage = e instanceof Error ? e.message : 'Unknown error occurred'
52+
logger.error(e, errorMessage)
53+
response = {
54+
statusCode: (e as AdapterError)?.statusCode || 502,
55+
errorMessage,
56+
timestamps: {
57+
providerDataRequestedUnixMs: 0,
58+
providerDataReceivedUnixMs: 0,
59+
providerIndicatedTimeUnixMs: undefined,
60+
},
61+
}
62+
}
63+
await this.responseCache.write(this.name, [{ params: param, response }])
64+
}
65+
66+
async _handleRequest(
67+
params: RequestParams,
68+
): Promise<AdapterResponse<BaseEndpointTypes['Response']>> {
69+
const providerDataRequestedUnixMs = Date.now()
70+
71+
const response = await request<PackageResponse>(
72+
this.requester,
73+
this.settings.API_ENDPOINT,
74+
'v2/collateral_management/packages',
75+
// Validated not null in endpoint
76+
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
77+
this.settings.COLLATERAL_API_KEY!,
78+
this.settings.API_LIMIT,
79+
)
80+
81+
const assets = response
82+
.filter((r) => r.clientReferenceId.toUpperCase() == params.clientReferenceId.toUpperCase())
83+
.flatMap((r) => r.collateralAssets)
84+
85+
const result = assets
86+
.filter((r) => r.asset.assetType.toUpperCase() == params.assetType.toUpperCase())
87+
.reduce((sum, current) => {
88+
return sum + Number(current.quantity)
89+
}, 0)
90+
91+
return {
92+
data: {
93+
result,
94+
},
95+
statusCode: 200,
96+
result,
97+
timestamps: {
98+
providerDataRequestedUnixMs,
99+
providerDataReceivedUnixMs: Date.now(),
100+
providerIndicatedTimeUnixMs: undefined,
101+
},
102+
}
103+
}
104+
105+
getSubscriptionTtlFromConfig(adapterSettings: BaseEndpointTypes['Settings']): number {
106+
return adapterSettings.WARMUP_SUBSCRIPTION_TTL
107+
}
108+
}
109+
110+
export const packagesTransport = new PackagesTransport()

packages/sources/anchorage/test/integration/__snapshots__/adapter.test.ts.snap

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

3+
exports[`execute packages endpoint should return success 1`] = `
4+
{
5+
"data": {
6+
"result": 2.3,
7+
},
8+
"result": 2.3,
9+
"statusCode": 200,
10+
"timestamps": {
11+
"providerDataReceivedUnixMs": 978347471111,
12+
"providerDataRequestedUnixMs": 978347471111,
13+
},
14+
}
15+
`;
16+
317
exports[`execute wallet endpoint should return success 1`] = `
418
{
519
"data": {

packages/sources/anchorage/test/integration/adapter.test.ts

Lines changed: 22 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -3,19 +3,20 @@ import {
33
setEnvVariables,
44
} from '@chainlink/external-adapter-framework/util/testing-utils'
55
import * as nock from 'nock'
6-
import { mockResponseSuccess } from './fixtures'
6+
import { mockPackagesResponseSuccess, mockResponseSuccess } from './fixtures'
77

88
describe('execute', () => {
99
let spy: jest.SpyInstance
10-
let testAdapter: TestAdapter
10+
let testAdapter: TestAdapter<any>
1111
let oldEnv: NodeJS.ProcessEnv
1212

1313
beforeAll(async () => {
1414
oldEnv = JSON.parse(JSON.stringify(process.env))
15-
process.env.ETHHOL_API_KEY = process.env.ETHHOL_API_KEY ?? 'fake-api-key'
16-
process.env.API_ENDPOINT = process.env.API_ENDPOINT ?? 'https://localhost:3324'
17-
process.env.API_LIMIT = process.env.API_LIMIT ?? '1'
18-
process.env.BACKGROUND_EXECUTE_MS = process.env.BACKGROUND_EXECUTE_MS ?? '0'
15+
process.env.ETHHOL_API_KEY = 'fake-api-key'
16+
process.env.COLLATERAL_API_KEY = 'fake-collateral-api-key'
17+
process.env.API_ENDPOINT = 'https://localhost:3324'
18+
process.env.API_LIMIT = '1'
19+
process.env.BACKGROUND_EXECUTE_MS = '0'
1920
const mockDate = new Date('2001-01-01T11:11:11.111Z')
2021
spy = jest.spyOn(Date, 'now').mockReturnValue(mockDate.getTime())
2122

@@ -49,4 +50,19 @@ describe('execute', () => {
4950
expect(response.json()).toMatchSnapshot()
5051
})
5152
})
53+
54+
describe('packages endpoint', () => {
55+
it('should return success', async () => {
56+
mockPackagesResponseSuccess()
57+
58+
const response = await testAdapter.request({
59+
endpoint: 'packages',
60+
clientReferenceId: '123456',
61+
assetType: 'BTC',
62+
})
63+
64+
expect(response.statusCode).toBe(200)
65+
expect(response.json()).toMatchSnapshot()
66+
})
67+
})
5268
})

packages/sources/anchorage/test/integration/fixtures.ts

Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -155,3 +155,88 @@ export const mockResponseSuccess = (): nock.Scope =>
155155
page: { next: null },
156156
}))
157157
.persist()
158+
159+
export const mockPackagesResponseSuccess = (): nock.Scope =>
160+
nock('https://localhost:3324', {
161+
encodedQueryParams: true,
162+
})
163+
.persist()
164+
.get(`/v2/collateral_management/packages?limit=1`)
165+
.reply(
166+
200,
167+
() => ({
168+
data: [
169+
{
170+
clientReferenceId: '123456',
171+
collateralAssets: [
172+
{
173+
asset: {
174+
assetType: 'BTC',
175+
},
176+
quantity: '1.5',
177+
},
178+
{
179+
asset: {
180+
assetType: 'ETH',
181+
},
182+
quantity: '2.3',
183+
},
184+
],
185+
},
186+
],
187+
page: {
188+
next: '/v2/collateral_management/packages?afterId=123456&limit=1',
189+
},
190+
}),
191+
[
192+
'Content-Type',
193+
'application/json',
194+
'Connection',
195+
'close',
196+
'Vary',
197+
'Accept-Encoding',
198+
'Vary',
199+
'Origin',
200+
],
201+
)
202+
.persist()
203+
.get(`/v2/collateral_management/packages?afterId=123456&limit=1`)
204+
.reply(200, () => ({
205+
data: [
206+
{
207+
clientReferenceId: '123456',
208+
collateralAssets: [
209+
{
210+
asset: {
211+
assetType: 'BTC',
212+
},
213+
quantity: '0.8',
214+
},
215+
],
216+
},
217+
],
218+
page: {
219+
next: '/v2/collateral_management/packages?afterId=789012&limit=1',
220+
},
221+
}))
222+
.persist()
223+
.get(`/v2/collateral_management/packages?afterId=789012&limit=1`)
224+
.reply(200, () => ({
225+
data: [
226+
{
227+
clientReferenceId: '789012',
228+
collateralAssets: [
229+
{
230+
asset: {
231+
assetType: 'BTC',
232+
},
233+
quantity: '5.0',
234+
},
235+
],
236+
},
237+
],
238+
page: {
239+
next: null,
240+
},
241+
}))
242+
.persist()

0 commit comments

Comments
 (0)