Skip to content

Commit 440d4ba

Browse files
authored
Add new package endpoint to anchorage (#4187)
* Add new package endpoint to anchorage * Comments
1 parent 092b55b commit 440d4ba

File tree

12 files changed

+328
-9
lines changed

12 files changed

+328
-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

.pnp.cjs

Lines changed: 1 addition & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

packages/sources/anchorage/package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@
3030
"devDependencies": {
3131
"@types/jest": "^29.5.14",
3232
"@types/node": "22.14.1",
33+
"decimal.js": "^10.3.1",
3334
"nock": "13.5.6",
3435
"typescript": "5.8.3"
3536
},

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: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
import { AdapterEndpoint } from '@chainlink/external-adapter-framework/adapter'
2+
import { InputParameters } from '@chainlink/external-adapter-framework/validation'
3+
import { AdapterInputError } from '@chainlink/external-adapter-framework/validation/error'
4+
import { config } from '../config'
5+
import { packagesTransport } from '../transport/packages'
6+
7+
export const inputParameters = new InputParameters(
8+
{
9+
clientReferenceId: {
10+
required: true,
11+
type: 'string',
12+
description: 'Id of the vault',
13+
},
14+
assetType: {
15+
required: true,
16+
type: 'string',
17+
description: 'Asset ticker name',
18+
},
19+
},
20+
[
21+
{
22+
clientReferenceId: '123456',
23+
assetType: 'BTC',
24+
},
25+
],
26+
)
27+
28+
export type BaseEndpointTypes = {
29+
Parameters: typeof inputParameters.definition
30+
Response: {
31+
Result: string
32+
Data: {
33+
result: string
34+
assets: {
35+
asset: {
36+
assetType: string
37+
}
38+
quantity: string
39+
}[]
40+
}
41+
}
42+
Settings: typeof config.settings
43+
}
44+
45+
export const endpoint = new AdapterEndpoint({
46+
name: 'packages',
47+
transport: packagesTransport,
48+
inputParameters,
49+
customInputValidation: (_, adapterSettings): AdapterInputError | undefined => {
50+
if (!adapterSettings.COLLATERAL_API_KEY) {
51+
throw new AdapterInputError({
52+
message: 'Missing COLLATERAL_API_KEY',
53+
statusCode: 400,
54+
})
55+
}
56+
return
57+
},
58+
})

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

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

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

3+
exports[`execute packages endpoint should return success 1`] = `
4+
{
5+
"data": {
6+
"assets": [
7+
{
8+
"asset": {
9+
"assetType": "BTC",
10+
},
11+
"quantity": "1.5",
12+
},
13+
{
14+
"asset": {
15+
"assetType": "ETH",
16+
},
17+
"quantity": "2.3",
18+
},
19+
{
20+
"asset": {
21+
"assetType": "BTC",
22+
},
23+
"quantity": "0.8",
24+
},
25+
],
26+
"result": "2.3",
27+
},
28+
"result": "2.3",
29+
"statusCode": 200,
30+
"timestamps": {
31+
"providerDataReceivedUnixMs": 978347471111,
32+
"providerDataRequestedUnixMs": 978347471111,
33+
},
34+
}
35+
`;
36+
337
exports[`execute wallet endpoint should return success 1`] = `
438
{
539
"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
})

0 commit comments

Comments
 (0)