Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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/lazy-scissors-film.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@chainlink/anchorage-adapter': minor
---

Add new endpoint
1 change: 1 addition & 0 deletions .pnp.cjs

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions packages/sources/anchorage/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@
"devDependencies": {
"@types/jest": "^29.5.14",
"@types/node": "22.14.1",
"decimal.js": "^10.3.1",
"nock": "13.5.6",
"typescript": "5.8.3"
},
Expand Down
5 changes: 5 additions & 0 deletions packages/sources/anchorage/src/config/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,11 @@ export const config = new AdapterConfig({
type: 'string',
required: true,
},
COLLATERAL_API_KEY: {
description: 'API key for Anchorage collateral_management endpoints',
type: 'string',
sensitive: true,
},
API_LIMIT: {
description: 'The maximum number of results to request from the API',
type: 'number',
Expand Down
1 change: 1 addition & 0 deletions packages/sources/anchorage/src/endpoint/index.ts
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
export { endpoint as packages } from './packages'
export { endpoint as wallet } from './wallet'
58 changes: 58 additions & 0 deletions packages/sources/anchorage/src/endpoint/packages.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
import { AdapterEndpoint } from '@chainlink/external-adapter-framework/adapter'
import { InputParameters } from '@chainlink/external-adapter-framework/validation'
import { AdapterInputError } from '@chainlink/external-adapter-framework/validation/error'
import { config } from '../config'
import { packagesTransport } from '../transport/packages'

export const inputParameters = new InputParameters(
{
clientReferenceId: {
required: true,
type: 'string',
description: 'Id of the vault',
},
assetType: {
required: true,
type: 'string',
description: 'Asset ticker name',
},
},
[
{
clientReferenceId: '123456',
assetType: 'BTC',
},
],
)

export type BaseEndpointTypes = {
Parameters: typeof inputParameters.definition
Response: {
Result: string
Data: {
result: string
assets: {
asset: {
assetType: string
}
quantity: string
}[]
}
}
Settings: typeof config.settings
}

export const endpoint = new AdapterEndpoint({
name: 'packages',
transport: packagesTransport,
inputParameters,
customInputValidation: (_, adapterSettings): AdapterInputError | undefined => {
if (!adapterSettings.COLLATERAL_API_KEY) {
throw new AdapterInputError({
message: 'Missing COLLATERAL_API_KEY',
statusCode: 400,
})
}
return
},
})
6 changes: 3 additions & 3 deletions packages/sources/anchorage/src/index.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
import { expose, ServerInstance } from '@chainlink/external-adapter-framework'
import { config } from './config'
import { wallet } from './endpoint'
import { PoRAdapter } from '@chainlink/external-adapter-framework/adapter/por'
import { config } from './config'
import { packages, wallet } from './endpoint'

export const adapter = new PoRAdapter({
defaultEndpoint: wallet.name,
name: 'ANCHORAGE',
config,
endpoints: [wallet],
endpoints: [wallet, packages],
rateLimiting: {
tiers: {
default: {
Expand Down
112 changes: 112 additions & 0 deletions packages/sources/anchorage/src/transport/packages.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,112 @@
import { EndpointContext } from '@chainlink/external-adapter-framework/adapter'
import { TransportDependencies } from '@chainlink/external-adapter-framework/transports'
import { SubscriptionTransport } from '@chainlink/external-adapter-framework/transports/abstract/subscription'
import { AdapterResponse, makeLogger, sleep } from '@chainlink/external-adapter-framework/util'
import { Requester } from '@chainlink/external-adapter-framework/util/requester'
import { AdapterError } from '@chainlink/external-adapter-framework/validation/error'
import Decimal from 'decimal.js'
import { BaseEndpointTypes, inputParameters } from '../endpoint/packages'
import { request } from './requester'

const logger = makeLogger('PackageTransport')

type RequestParams = typeof inputParameters.validated

interface PackageResponse {
clientReferenceId: string
collateralAssets: [
{
asset: {
assetType: string
}
quantity: string
},
]
}

export class PackagesTransport extends SubscriptionTransport<BaseEndpointTypes> {
requester!: Requester
settings!: BaseEndpointTypes['Settings']

async initialize(
dependencies: TransportDependencies<BaseEndpointTypes>,
adapterSettings: BaseEndpointTypes['Settings'],
endpointName: string,
transportName: string,
): Promise<void> {
await super.initialize(dependencies, adapterSettings, endpointName, transportName)
this.requester = dependencies.requester
this.settings = adapterSettings
}

async backgroundHandler(context: EndpointContext<BaseEndpointTypes>, entries: RequestParams[]) {
await Promise.all(entries.map(async (param) => this.handleRequest(param)))
await sleep(context.adapterSettings.BACKGROUND_EXECUTE_MS)
}

async handleRequest(param: RequestParams) {
let response: AdapterResponse<BaseEndpointTypes['Response']>
try {
response = await this._handleRequest(param)
} catch (e) {
const errorMessage = e instanceof Error ? e.message : 'Unknown error occurred'
logger.error(e, errorMessage)
response = {
statusCode: (e as AdapterError)?.statusCode || 502,
errorMessage,
timestamps: {
providerDataRequestedUnixMs: 0,
providerDataReceivedUnixMs: 0,
providerIndicatedTimeUnixMs: undefined,
},
}
}
await this.responseCache.write(this.name, [{ params: param, response }])
}

async _handleRequest(
params: RequestParams,
): Promise<AdapterResponse<BaseEndpointTypes['Response']>> {
const providerDataRequestedUnixMs = Date.now()

const response = await request<PackageResponse>(
this.requester,
this.settings.API_ENDPOINT,
'v2/collateral_management/packages',
// Validated not null in endpoint
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
this.settings.COLLATERAL_API_KEY!,
this.settings.API_LIMIT,
)

const assets = response
.filter((r) => r.clientReferenceId.toUpperCase() == params.clientReferenceId.toUpperCase())
.flatMap((r) => r.collateralAssets)

const result = assets
.filter((r) => r.asset.assetType.toUpperCase() == params.assetType.toUpperCase())
.reduce((sum, current) => {
return sum.add(new Decimal(current.quantity))
}, new Decimal(0))

return {
data: {
result: result.toString(),
assets,
},
statusCode: 200,
result: result.toString(),
timestamps: {
providerDataRequestedUnixMs,
providerDataReceivedUnixMs: Date.now(),
providerIndicatedTimeUnixMs: undefined,
},
}
}

getSubscriptionTtlFromConfig(adapterSettings: BaseEndpointTypes['Settings']): number {
return adapterSettings.WARMUP_SUBSCRIPTION_TTL
}
}

export const packagesTransport = new PackagesTransport()
Original file line number Diff line number Diff line change
@@ -1,5 +1,39 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP

exports[`execute packages endpoint should return success 1`] = `
{
"data": {
"assets": [
{
"asset": {
"assetType": "BTC",
},
"quantity": "1.5",
},
{
"asset": {
"assetType": "ETH",
},
"quantity": "2.3",
},
{
"asset": {
"assetType": "BTC",
},
"quantity": "0.8",
},
],
"result": "2.3",
},
"result": "2.3",
"statusCode": 200,
"timestamps": {
"providerDataReceivedUnixMs": 978347471111,
"providerDataRequestedUnixMs": 978347471111,
},
}
`;

exports[`execute wallet endpoint should return success 1`] = `
{
"data": {
Expand Down
28 changes: 22 additions & 6 deletions packages/sources/anchorage/test/integration/adapter.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,19 +3,20 @@ import {
setEnvVariables,
} from '@chainlink/external-adapter-framework/util/testing-utils'
import * as nock from 'nock'
import { mockResponseSuccess } from './fixtures'
import { mockPackagesResponseSuccess, mockResponseSuccess } from './fixtures'

describe('execute', () => {
let spy: jest.SpyInstance
let testAdapter: TestAdapter
let testAdapter: TestAdapter<any>
let oldEnv: NodeJS.ProcessEnv

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

Expand Down Expand Up @@ -49,4 +50,19 @@ describe('execute', () => {
expect(response.json()).toMatchSnapshot()
})
})

describe('packages endpoint', () => {
it('should return success', async () => {
mockPackagesResponseSuccess()

const response = await testAdapter.request({
endpoint: 'packages',
clientReferenceId: '123456',
assetType: 'BTC',
})

expect(response.statusCode).toBe(200)
expect(response.json()).toMatchSnapshot()
})
})
})
Loading
Loading