Skip to content
Draft
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,14 @@
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.
*/
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,64 @@
import { EndpointContext } from '@chainlink/external-adapter-framework/adapter'
import { ResponseCache } from '@chainlink/external-adapter-framework/cache/response'
import { TransportDependencies } from '@chainlink/external-adapter-framework/transports'
import { AdapterResponse, makeLogger, sleep } from '@chainlink/external-adapter-framework/util'
import { Requester } from '@chainlink/external-adapter-framework/util/requester'
import { BaseEndpointTypes } from '../endpoint/address'
import { AddressListTransportTypes, BaseAddressListTransport, RequestParams } from './common'

const logger = makeLogger('BaseAddressListTransport')

export class AddressDebugTransport extends BaseAddressListTransport {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think we should make this a subscription transport if it is just for debug

Copy link
Contributor Author

@mmcallister-cll mmcallister-cll Nov 14, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So foreground? If yes then i think I'll probably redo this PR a different way

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ok gonna move this back to draft then since it was just something i was trying to get in off-sprint for on call

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> {
await super.initialize(dependencies, adapterSettings, endpointName, transportName)
this.requester = dependencies.requester
this.settings = adapterSettings
}

async backgroundHandler(
context: EndpointContext<AddressListTransportTypes>,
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: 502,
errorMessage,
timestamps: {
providerDataRequestedUnixMs: 0,
providerDataReceivedUnixMs: 0,
providerIndicatedTimeUnixMs: undefined,
},
}
}
await this.responseCache.write(this.name, [{ params: param, response }])
}

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

export const addressDebugTransport = new AddressDebugTransport()
84 changes: 6 additions & 78 deletions packages/composites/multi-address-list/src/transport/address.ts
Original file line number Diff line number Diff line change
@@ -1,35 +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 { 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, BaseAddressListTransport, 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> {
export class AddressListTransport extends BaseAddressListTransport {
name!: string
responseCache!: ResponseCache<AddressListTransportTypes>
requester!: Requester
Expand Down Expand Up @@ -76,29 +55,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 this.getAggregatedAddressList(params)
await this.responseCache.write(this.name, [
{
params,
Expand All @@ -111,41 +74,6 @@ export class AddressListTransport extends SubscriptionTransport<AddressListTrans
setTimeout(() => this.execute(params, retryCount), this.settings.RETRY_INTERVAL_MS)
}
}

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
}
}

export const addressListTransport = new AddressListTransport()
90 changes: 90 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,90 @@
import { ResponseCache } from '@chainlink/external-adapter-framework/cache/response'
import { SubscriptionTransport } from '@chainlink/external-adapter-framework/transports/abstract/subscription'
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 abstract class BaseAddressListTransport extends SubscriptionTransport<AddressListTransportTypes> {
name!: string
responseCache!: ResponseCache<AddressListTransportTypes>
requester!: Requester
settings!: AddressListTransportTypes['Settings']
activeParams: RequestParams[] = []

async getAggregatedAddressList(params: RequestParams) {
const providerDataRequestedUnixMs = Date.now()

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,
},
}
return response
}

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,
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
}
}
Loading
Loading