Skip to content

Commit

Permalink
Update Superstate EA to allow for response selector
Browse files Browse the repository at this point in the history
  • Loading branch information
mxiao-cll committed Sep 26, 2024
1 parent f012ff4 commit 28219fb
Show file tree
Hide file tree
Showing 6 changed files with 81 additions and 25 deletions.
5 changes: 5 additions & 0 deletions .changeset/metal-knives-move.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@chainlink/superstate-adapter': minor
---

Report assets_under_management
27 changes: 21 additions & 6 deletions packages/sources/superstate/src/endpoint/nav.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,14 +3,29 @@ import { SingleNumberResultResponse } from '@chainlink/external-adapter-framewor
import { config } from '../config'
import { NavTransport } from '../transport/nav'
import { InputParameters } from '@chainlink/external-adapter-framework/validation'
import { NetAssetValue, AssetsUnderManagement } from '../transport/utils'

export const inputParameters = new InputParameters({
fundId: {
description: 'Fund id',
type: 'number',
required: true,
export const inputParameters = new InputParameters(
{
fundId: {
description: 'Fund id',
type: 'number',
required: true,
},
reportValue: {
description: 'Which value to report on',
type: 'string',
default: NetAssetValue,
options: [NetAssetValue, AssetsUnderManagement],
},
},
})
[
{
fundId: 1,
reportValue: NetAssetValue,
},
],
)

export type BaseEndpointTypes = {
Parameters: typeof inputParameters.definition
Expand Down
42 changes: 25 additions & 17 deletions packages/sources/superstate/src/transport/nav.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import {
isBeforeTime,
isInTimeRange,
toTimezoneDate,
AssetsUnderManagement,
} from './utils'

const logger = makeLogger('Superstate')
Expand All @@ -31,6 +32,8 @@ export interface ResponseSchema {

const TZ = 'America/New_York'

type ReportValueType = typeof inputParameters.validated.reportValue

// Custom transport implementation that takes incoming requests, adds them into a SET, and makes requests to DP
// on a specific time every day, after receiving a signal from scheduler.
export class NavTransport implements Transport<BaseEndpointTypes> {
Expand All @@ -39,7 +42,7 @@ export class NavTransport implements Transport<BaseEndpointTypes> {
requester!: Requester
settings!: BaseEndpointTypes['Settings']
endpointName!: string
fundIdsSet!: Set<number>
fundsMap!: Map<string, [number, ReportValueType]>

async initialize(
dependencies: TransportDependencies<BaseEndpointTypes>,
Expand All @@ -52,26 +55,27 @@ export class NavTransport implements Transport<BaseEndpointTypes> {
this.requester = dependencies.requester
this.settings = settings
this.endpointName = endpointName
this.fundIdsSet = new Set()
this.fundsMap = new Map()
this.runScheduler()
}

// registerRequest is invoked on every valid request to EA
// Adds fundId in the request to a SET
async registerRequest(req: AdapterRequest<typeof inputParameters.validated>) {
const { fundId } = req.requestContext.data
if (!this.fundIdsSet.has(fundId)) {
this.fundIdsSet.add(fundId)
logger.info(`Added new fund id - ${fundId}`)
const { fundId, reportValue } = req.requestContext.data
const mapKey = `${fundId}+${reportValue}`
if (!this.fundsMap.has(mapKey)) {
this.fundsMap.set(mapKey, [fundId, reportValue])
logger.info(`Added new fund id - ${fundId} - reportValue ${reportValue}`)
}
}

// foregroundExecute is executed when there is a new request/fundId that is not in the cache
async foregroundExecute(
req: AdapterRequest<typeof inputParameters.validated>,
): Promise<AdapterResponse<BaseEndpointTypes['Response']> | void> {
const { fundId } = req.requestContext.data
return this.execute(fundId)
const { fundId, reportValue } = req.requestContext.data
return this.execute(fundId, reportValue)
}

// Runs 'execute' function every day at 9:09 AM ET (if fundIdsSet is not empty)
Expand All @@ -83,19 +87,17 @@ export class NavTransport implements Transport<BaseEndpointTypes> {

schedule.scheduleJob(rule, () => {
logger.info(
`Scheduled execution started at ${Date.now()}. FundIdSet - ${[...this.fundIdsSet].join(
',',
)}`,
`Scheduled execution started at ${Date.now()}. FundsMap - ${[...this.fundsMap].join(',')}`,
)
;[...this.fundIdsSet].map(async (fundId) => this.execute(fundId))
;[...this.fundsMap].map(async (entry) => this.execute(entry[1][0], entry[1][1]))
})
}

// execute is either called by scheduler or foregroundExecute.
// Makes a request to DP and saves the response in the cache.
// In case the DP returns stale data the function will be executed again several times
// before finalizing and saving the last returned data to a cache.
async execute(fundId: number, retryCount = 0) {
async execute(fundId: number, reportValue: ReportValueType, retryCount = 0) {
const providerDataRequestedUnixMs = Date.now()
const apiResponse = await this.makeRequest(fundId)
const providerDataReceivedUnixMs = Date.now()
Expand All @@ -110,12 +112,15 @@ export class NavTransport implements Transport<BaseEndpointTypes> {
providerIndicatedTimeUnixMs: undefined,
},
}
await this.responseCache.write(this.name, [{ params: { fundId }, response }])
await this.responseCache.write(this.name, [{ params: { fundId, reportValue }, response }])
return
}

const data = apiResponse.data[0]
const result = Number(data.net_asset_value)
let result = Number(data.net_asset_value)
if (reportValue == AssetsUnderManagement) {
result = Number(data.assets_under_management)
}

// DP updates previous working day's price on the next working day at 9:09 AM ET
// If there is no fresh price data, we try to re-fetch the API until 10:30 AM ET
Expand All @@ -135,7 +140,10 @@ export class NavTransport implements Transport<BaseEndpointTypes> {
} ms`,
)
retryCount++
setTimeout(() => this.execute(fundId, retryCount), this.settings.RETRY_INTERVAL_MS)
setTimeout(
() => this.execute(fundId, reportValue, retryCount),
this.settings.RETRY_INTERVAL_MS,
)
// We don't `return` here and let the value be stored in cache on purpose.
// This way the EA will respond with the latest value from DP (even though it's not the value that the EA expects),
// while it tries to get a fresh update.
Expand All @@ -159,7 +167,7 @@ export class NavTransport implements Transport<BaseEndpointTypes> {
providerIndicatedTimeUnixMs: undefined,
},
}
await this.responseCache.write(this.name, [{ params: { fundId }, response }])
await this.responseCache.write(this.name, [{ params: { fundId, reportValue }, response }])
return response
}

Expand Down
3 changes: 3 additions & 0 deletions packages/sources/superstate/src/transport/utils.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
import { formatInTimeZone } from 'date-fns-tz'
import { format, isAfter, isBefore, isSaturday, isSunday, subDays } from 'date-fns'

export const NetAssetValue = 'net_asset_value'
export const AssetsUnderManagement = 'assets_under_management'

// Converts a Date to a formatted string in the given timezone
export const toTimezoneDate = (date: string | Date, timezone: string): string => {
return formatInTimeZone(date, timezone, 'yyyy-MM-dd HH:mm:ss')
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,20 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP

exports[`execute price endpoint should return success 1`] = `
exports[`execute price endpoint should return success - aum 1`] = `
{
"data": {
"result": 88412710.73007037,
},
"result": 88412710.73007037,
"statusCode": 200,
"timestamps": {
"providerDataReceivedUnixMs": 978347471111,
"providerDataRequestedUnixMs": 978347471111,
},
}
`;

exports[`execute price endpoint should return success - nav 1`] = `
{
"data": {
"result": 10.170643,
Expand Down
13 changes: 12 additions & 1 deletion packages/sources/superstate/test/integration/adapter.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ describe('execute', () => {
})

describe('price endpoint', () => {
it('should return success', async () => {
it('should return success - nav', async () => {
const data = {
endpoint: 'reserves',
fundId: 1,
Expand All @@ -57,5 +57,16 @@ describe('execute', () => {
expect(response.statusCode).toBe(200)
expect(response.json()).toMatchSnapshot()
})
it('should return success - aum', async () => {
const data = {
endpoint: 'reserves',
fundId: 1,
reportValue: 'assets_under_management',
}
mockResponseSuccess()
const response = await testAdapter.request(data)
expect(response.statusCode).toBe(200)
expect(response.json()).toMatchSnapshot()
})
})
})

0 comments on commit 28219fb

Please sign in to comment.