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/two-numbers-confess.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@chainlink/multi-address-list-adapter': minor
---

added address-debug endpoint
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import { PoRAddressEndpoint } from '@chainlink/external-adapter-framework/adapter/por'
import { addressDebugTransport } from '../transport/address-debug'
import { customInputValidation, inputParameters } from './address'

/**
* This endpoint is meant to be used for debug/diagnostic
* purposes and not for production feeds.
* Additionally, this endpoint will not contain a
* meta field in its response.
*/
export const endpoint = new PoRAddressEndpoint({
name: 'address-debug',
transport: addressDebugTransport,
inputParameters,
customInputValidation,
})
58 changes: 35 additions & 23 deletions packages/composites/multi-address-list/src/endpoint/address.ts
Original file line number Diff line number Diff line change
@@ -1,14 +1,14 @@
import { InputParameters } from '@chainlink/external-adapter-framework/validation'
import { config } from '../config'
import { addressListTransport } from '../transport/address'
import { walletParameters as anchorageParams } from '@chainlink/anchorage-adapter'
import { walletParameters as bitGoParams } from '@chainlink/bitgo-adapter'
import { walletParameters as coinbasePrimeParams } from '@chainlink/coinbase-prime-adapter'
import {
PoRAddressEndpoint,
PoRAddressResponse,
} from '@chainlink/external-adapter-framework/adapter/por'
import { walletParameters as anchorageParams } from '@chainlink/anchorage-adapter'
import { walletParameters as coinbasePrimeParams } from '@chainlink/coinbase-prime-adapter'
import { walletParameters as bitGoParams } from '@chainlink/bitgo-adapter'
import { InputParameters } from '@chainlink/external-adapter-framework/validation'
import { AdapterInputError } from '@chainlink/external-adapter-framework/validation/error'
import { config } from '../config'
import { addressListTransport } from '../transport/address'

export const inputParameters = new InputParameters({
chainId: {
Expand Down Expand Up @@ -63,25 +63,37 @@ export type BaseEndpointTypes = {
Settings: typeof config.settings
}

type RequestType = {
requestContext: {
data: typeof inputParameters.validated
}
}

export const customInputValidation = (
request: RequestType,
adapterSettings: BaseEndpointTypes['Settings'],
): AdapterInputError | undefined => {
// Check if the required environment variables for source EAs are set.
// eslint-disable-next-line @typescript-eslint/no-unused-vars
const { chainId, network, ...sources } = request.requestContext.data

Object.keys(sources).forEach((source) => {
const envName = `${source.toUpperCase()}_ADAPTER_URL` as keyof typeof adapterSettings
const params = sources[source as keyof typeof sources]
if (params && !adapterSettings[envName]) {
throw new AdapterInputError({
statusCode: 400,
message: `Error: missing environment variable ${envName}`,
})
}
return
})
return
}

export const endpoint = new PoRAddressEndpoint({
name: 'address',
transport: addressListTransport,
inputParameters,
customInputValidation: (request, adapterSettings): AdapterInputError | undefined => {
// Check if the required environment variables for source EAs are set.
const { chainId, network, ...sources } = request.requestContext.data

Object.keys(sources).forEach((source) => {
const envName = `${source.toUpperCase()}_ADAPTER_URL` as keyof typeof adapterSettings
const params = sources[source as keyof typeof sources]
if (params && !adapterSettings[envName]) {
throw new AdapterInputError({
statusCode: 400,
message: `Error: missing environment variable ${envName}`,
})
}
return
})
return
},
customInputValidation,
})
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
export { endpoint as address } from './address'
export { endpoint as addressDebug } from './address-debug'
6 changes: 3 additions & 3 deletions packages/composites/multi-address-list/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 { address } from './endpoint'
import { PoRAdapter } from '@chainlink/external-adapter-framework/adapter/por'
import { config } from './config'
import { address, addressDebug } from './endpoint'

export const adapter = new PoRAdapter({
defaultEndpoint: address.name,
name: 'MULTI_ADDRESS_LIST',
config,
endpoints: [address],
endpoints: [address, addressDebug],
})

export const server = (): Promise<ServerInstance | undefined> => expose(adapter)
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
import { ResponseCache } from '@chainlink/external-adapter-framework/cache/response'
import { Transport, TransportDependencies } from '@chainlink/external-adapter-framework/transports'
import {
AdapterRequest,
AdapterResponse,
makeLogger,
} from '@chainlink/external-adapter-framework/util'
import { Requester } from '@chainlink/external-adapter-framework/util/requester'
import { BaseEndpointTypes, inputParameters } from '../endpoint/address'
import { AddressListTransportTypes, getAggregatedAddressList, RequestParams } from './common'

const logger = makeLogger('BaseAddressListTransport')

export class AddressDebugTransport implements Transport<AddressListTransportTypes> {
name!: string
responseCache!: ResponseCache<AddressListTransportTypes>
requester!: Requester
settings!: AddressListTransportTypes['Settings']
activeParams: RequestParams[] = []

async initialize(
dependencies: TransportDependencies<AddressListTransportTypes>,
adapterSettings: AddressListTransportTypes['Settings'],
_endpointName: string,
transportName: string,
): Promise<void> {
this.requester = dependencies.requester
this.responseCache = dependencies.responseCache
this.settings = adapterSettings
this.name = transportName
}

async foregroundExecute(
req: AdapterRequest<typeof inputParameters.validated>,
): Promise<AdapterResponse<BaseEndpointTypes['Response']>> {
const entries = req.requestContext.data
return await this.handleRequest(entries)
}

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: 502,
errorMessage,
timestamps: {
providerDataRequestedUnixMs: 0,
providerDataReceivedUnixMs: 0,
providerIndicatedTimeUnixMs: undefined,
},
}
}
return response
}

async _handleRequest(
param: RequestParams,
): Promise<AdapterResponse<BaseEndpointTypes['Response']>> {
return getAggregatedAddressList(param, this.requester, this.settings)
}
}

export const addressDebugTransport = new AddressDebugTransport()
79 changes: 6 additions & 73 deletions packages/composites/multi-address-list/src/transport/address.ts
Original file line number Diff line number Diff line change
@@ -1,34 +1,14 @@
import { TransportDependencies } from '@chainlink/external-adapter-framework/transports'
import { EndpointContext } from '@chainlink/external-adapter-framework/adapter'
import { ResponseCache } from '@chainlink/external-adapter-framework/cache/response'
import { Requester } from '@chainlink/external-adapter-framework/util/requester'
import { TransportDependencies } from '@chainlink/external-adapter-framework/transports'
import { SubscriptionTransport } from '@chainlink/external-adapter-framework/transports/abstract/subscription'
import { makeLogger, sleep } from '@chainlink/external-adapter-framework/util'
import { BaseEndpointTypes, inputParameters } from '../endpoint/address'
import { Requester } from '@chainlink/external-adapter-framework/util/requester'
import schedule from 'node-schedule'
import { SubscriptionTransport } from '@chainlink/external-adapter-framework/transports/abstract/subscription'
import { EndpointContext } from '@chainlink/external-adapter-framework/adapter'
import { AddressListTransportTypes, getAggregatedAddressList, RequestParams } from './common'

const logger = makeLogger('AddressListTransport')

export type AddressListTransportTypes = BaseEndpointTypes

type RequestParams = typeof inputParameters.validated

interface PoRAdapterResponse {
data: {
result: {
network: string
chainId: string
address: string
}[]
}
statusCode: number
result: null
timestamps: {
providerDataRequestedUnixMs: number
providerDataReceivedUnixMs: number
}
}

export class AddressListTransport extends SubscriptionTransport<AddressListTransportTypes> {
name!: string
responseCache!: ResponseCache<AddressListTransportTypes>
Expand Down Expand Up @@ -76,29 +56,13 @@ export class AddressListTransport extends SubscriptionTransport<AddressListTrans
}

async execute(params: RequestParams, retryCount = 0) {
const providerDataRequestedUnixMs = Date.now()

if (retryCount >= this.settings.MAX_RETRIES) {
logger.error(`Max retry count reached for params: ${JSON.stringify(params)}`)
return
}

try {
const addresses = await this.fetchSourceAddresses(params)
logger.info(`Fetched ${addresses.length} addresses`)

const response = {
data: {
result: addresses,
},
statusCode: 200,
result: null,
timestamps: {
providerDataRequestedUnixMs,
providerDataReceivedUnixMs: Date.now(),
providerIndicatedTimeUnixMs: undefined,
},
}
const response = await getAggregatedAddressList(params, this.requester, this.settings)
await this.responseCache.write(this.name, [
{
params,
Expand All @@ -112,37 +76,6 @@ export class AddressListTransport extends SubscriptionTransport<AddressListTrans
}
}

async fetchSourceAddresses(params: RequestParams) {
const { chainId, network, ...sources } = params

const promises = Object.entries(sources)
.filter(([_, sourceParams]) => sourceParams)
.map(async ([sourceName, sourceParams]) => {
// customInputValidation ensures that if the source EA is present in the input params, the corresponding env variable is also present
const adapterUrl = `${sourceName.toUpperCase()}_ADAPTER_URL` as keyof typeof this.settings
const requestConfig = {
url: this.settings[adapterUrl] as string,
method: 'POST',
data: {
data: {
...sourceParams,
chainId: params.chainId,
network: params.network,
},
},
}

const sourceResponse = await this.requester.request<PoRAdapterResponse>(
JSON.stringify(requestConfig),
requestConfig,
)
return sourceResponse.response.data.data.result
})

const addresses = await Promise.all(promises)
return addresses.flat()
}

getSubscriptionTtlFromConfig(adapterSettings: AddressListTransportTypes['Settings']): number {
return adapterSettings.WARMUP_SUBSCRIPTION_TTL
}
Expand Down
85 changes: 85 additions & 0 deletions packages/composites/multi-address-list/src/transport/common.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
import { AdapterSettings } from '@chainlink/external-adapter-framework/config'
import { makeLogger } from '@chainlink/external-adapter-framework/util'
import { Requester } from '@chainlink/external-adapter-framework/util/requester'
import { BaseEndpointTypes, inputParameters } from '../endpoint/address'

const logger = makeLogger('BaseAddressListTransport')

export type AddressListTransportTypes = BaseEndpointTypes
export type RequestParams = typeof inputParameters.validated

interface PoRAdapterResponse {
data: {
result: {
network: string
chainId: string
address: string
}[]
}
statusCode: number
result: null
timestamps: {
providerDataRequestedUnixMs: number
providerDataReceivedUnixMs: number
}
}

export async function getAggregatedAddressList(
params: RequestParams,
requester: Requester,
settings: AdapterSettings,
) {
const providerDataRequestedUnixMs = Date.now()

const addresses = await fetchSourceAddresses(params, requester, settings)
logger.info(`Fetched ${addresses.length} addresses`)

const response = {
data: {
result: addresses,
},
statusCode: 200,
result: null,
timestamps: {
providerDataRequestedUnixMs,
providerDataReceivedUnixMs: Date.now(),
providerIndicatedTimeUnixMs: undefined,
},
}
return response
}

async function fetchSourceAddresses(
params: RequestParams,
requester: Requester,
settings: AdapterSettings,
) {
const { chainId, network, ...sources } = params

const promises = Object.entries(sources)
.filter(([_, sourceParams]) => sourceParams)
.map(async ([sourceName, sourceParams]) => {
// customInputValidation ensures that if the source EA is present in the input params, the corresponding env variable is also present
const adapterUrl = `${sourceName.toUpperCase()}_ADAPTER_URL` as keyof typeof settings
const requestConfig = {
url: settings[adapterUrl] as string,
method: 'POST',
data: {
data: {
...sourceParams,
chainId,
network,
},
},
}

const sourceResponse = await requester.request<PoRAdapterResponse>(
JSON.stringify(requestConfig),
requestConfig,
)
return sourceResponse.response.data.data.result
})

const addresses = await Promise.all(promises)
return addresses.flat()
}
Loading
Loading