Skip to content

Commit

Permalink
Multi Chain PoR Address List EA (#3561)
Browse files Browse the repository at this point in the history
  • Loading branch information
mxiao-cll authored Nov 15, 2024
1 parent 8cc19c5 commit 5f0ae26
Show file tree
Hide file tree
Showing 13 changed files with 405 additions and 10 deletions.
5 changes: 5 additions & 0 deletions .changeset/tame-pans-drive.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@chainlink/por-address-list-adapter': minor
---

Add multichainAddress endpoint
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
[
{
"inputs": [
{ "internalType": "uint256", "name": "startIndex", "type": "uint256" },
{ "internalType": "uint256", "name": "endIndex", "type": "uint256" }
],
"name": "getPoRAddressList",
"outputs": [
{
"components": [
{ "internalType": "string", "name": "chain", "type": "string" },
{ "internalType": "uint256", "name": "chainId", "type": "uint256" },
{ "internalType": "string", "name": "tokenSymbol", "type": "string" },
{ "internalType": "address", "name": "tokenAddress", "type": "address" },
{ "internalType": "address", "name": "vaultAddress", "type": "address" }
],
"internalType": "struct IMultiEVMPoRAddressList.PoRInfo[]",
"name": "",
"type": "tuple[]"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "getPoRAddressListLength",
"outputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }],
"stateMutability": "view",
"type": "function"
}
]
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
[
{
"inputs": [
{ "internalType": "uint256", "name": "startIndex_", "type": "uint256" },
{ "internalType": "uint256", "name": "endIndex_", "type": "uint256" }
],
"name": "getPoRAddressList",
"outputs": [
{
"components": [
{ "internalType": "string", "name": "tokenSymbol", "type": "string" },
{ "internalType": "string", "name": "chain", "type": "string" },
{ "internalType": "uint64", "name": "chainId", "type": "uint64" },
{ "internalType": "address", "name": "tokenAddress", "type": "address" },
{ "internalType": "address", "name": "vaultAddress", "type": "address" }
],
"internalType": "struct IPoRAddressListMulti.TokenVaultInfo[]",
"name": "tokenVaultInfos_",
"type": "tuple[]"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "getPoRAddressListLength",
"outputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }],
"stateMutability": "view",
"type": "function"
}
]
1 change: 1 addition & 0 deletions packages/sources/por-address-list/src/endpoint/index.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
export { endpoint as address } from './address'
export { endpoint as solvBTC } from './solvBTC'
export { endpoint as bedrockBTC } from './bedrockBTC'
export { endpoint as multichainAddress } from './multichainAddress'
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
import {
PoRTokenAddressEndpoint,
PoRTokenAddressResponse,
} from '@chainlink/external-adapter-framework/adapter/por'
import { InputParameters } from '@chainlink/external-adapter-framework/validation'
import { config } from '../config'
import { addressTransport } from '../transport/multichainAddress'

export const inputParameters = new InputParameters(
{
contractAddress: {
description: 'The contract address holding the custodial addresses',
type: 'string',
required: true,
},
contractAddressNetwork: {
description:
'The network of the contract, used to match {NETWORK}_RPC_URL and {NETWORK}_RPC_CHAIN_ID in env var',
type: 'string',
required: true,
},
confirmations: {
description: 'The number of confirmations to query data from',
type: 'number',
default: 0,
},
batchSize: {
description: 'The number of addresses to fetch from the contract at a time',
type: 'number',
default: 10,
},
},
[
{
contractAddress: '0xb7C0817Dd23DE89E4204502dd2C2EF7F57d3A3B8',
contractAddressNetwork: 'BINANCE',
confirmations: 0,
batchSize: 10,
},
],
)

type ResponseSchema = PoRTokenAddressResponse

export type BaseEndpointTypes = {
Parameters: typeof inputParameters.definition
Response: ResponseSchema
Settings: typeof config.settings
}

export const endpoint = new PoRTokenAddressEndpoint({
name: 'multichainAddress',
transport: addressTransport,
inputParameters,
})
4 changes: 2 additions & 2 deletions packages/sources/por-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 { PoRAdapter } from '@chainlink/external-adapter-framework/adapter/por'
import { config } from './config'
import { address, solvBTC, bedrockBTC } from './endpoint'
import { address, solvBTC, bedrockBTC, multichainAddress } from './endpoint'

export const adapter = new PoRAdapter({
defaultEndpoint: address.name,
name: 'POR_ADDRESS_LIST',
config,
endpoints: [address, solvBTC, bedrockBTC],
endpoints: [address, solvBTC, bedrockBTC, multichainAddress],
})

export const server = (): Promise<ServerInstance | undefined> => expose(adapter)
2 changes: 1 addition & 1 deletion packages/sources/por-address-list/src/transport/address.ts
Original file line number Diff line number Diff line change
Expand Up @@ -78,7 +78,7 @@ export class AddressTransport extends SubscriptionTransport<AddressTransportType
const latestBlockNum = await provider.getBlockNumber()

const providerDataRequestedUnixMs = Date.now()
const addressList = await fetchAddressList(
const addressList = await fetchAddressList<string>(
addressManager,
latestBlockNum,
confirmations,
Expand Down
125 changes: 125 additions & 0 deletions packages/sources/por-address-list/src/transport/multichainAddress.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,125 @@
import { SubscriptionTransport } from '@chainlink/external-adapter-framework/transports/abstract/subscription'
import { EndpointContext } from '@chainlink/external-adapter-framework/adapter'
import { TransportDependencies } from '@chainlink/external-adapter-framework/transports'
import { AdapterResponse, sleep } from '@chainlink/external-adapter-framework/util'
import ABI from '../config/PoRAddressListMulti.json'
import PolygonABI from '../config/MultiEVMPoRAddressList.json'
import { BaseEndpointTypes, inputParameters } from '../endpoint/multichainAddress'
import { ethers } from 'ethers'
import { fetchAddressList, addProvider, getProvider } from './utils'

export type AddressTransportTypes = BaseEndpointTypes

type RequestParams = typeof inputParameters.validated

interface ResponseSchema {
tokenSymbol: string
chain: string
chainId: bigint
tokenAddress: string
vaultAddress: string
}

export class AddressTransport extends SubscriptionTransport<AddressTransportTypes> {
providersMap: Record<string, ethers.providers.JsonRpcProvider> = {}
settings!: AddressTransportTypes['Settings']

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

async backgroundHandler(
context: EndpointContext<AddressTransportTypes>,
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'
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<AddressTransportTypes['Response']>> {
const { confirmations, contractAddress, contractAddressNetwork, batchSize } = param

this.providersMap = addProvider(contractAddressNetwork, this.providersMap)
const provider = getProvider(contractAddressNetwork, this.providersMap)

const addressManager = new ethers.Contract(
contractAddress,
contractAddressNetwork == 'POLYGON' ? PolygonABI : ABI,
provider,
)
const latestBlockNum = await provider.getBlockNumber()

const providerDataRequestedUnixMs = Date.now()
const addressList = await fetchAddressList<ResponseSchema>(
addressManager,
latestBlockNum,
confirmations,
batchSize,
this.settings.GROUP_SIZE,
)

const addressByChain = Map.groupBy(
addressList,
(address) => address.chainId.toString() + address.tokenAddress,
)

const response = Array.from(
new Map(
Array.from(addressByChain, ([k, v]) => [
k,
{
chainId: v[0].chainId.toString(),
contractAddress: v[0].tokenAddress,
wallets: v.map((v) => v.vaultAddress),
},
]),
).values(),
).sort()

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

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

export const addressTransport = new AddressTransport()
20 changes: 14 additions & 6 deletions packages/sources/por-address-list/src/transport/utils.ts
Original file line number Diff line number Diff line change
@@ -1,23 +1,24 @@
import { ethers } from 'ethers'
import { makeLogger } from '@chainlink/external-adapter-framework/util'
import { AdapterInputError } from '@chainlink/external-adapter-framework/validation/error'

const logger = makeLogger('utils')

export const fetchAddressList = async (
export const fetchAddressList = async <T>(
addressManager: ethers.Contract,
latestBlockNum: number,
confirmations = 0,
batchSize = 10,
batchGroupSize = 10,
): Promise<string[]> => {
): Promise<T[]> => {
const blockTag = latestBlockNum - confirmations
const numAddresses = await addressManager.getPoRAddressListLength({
blockTag,
})
let totalRequestedAddressesCount = 0
let startIdx = ethers.BigNumber.from(0)
const addresses: string[] = []
let batchRequests: Promise<string[]>[] = []
const addresses: T[] = []
let batchRequests: Promise<T[]>[] = []

while (totalRequestedAddressesCount < numAddresses.toNumber()) {
const nextEndIdx = startIdx.add(batchSize)
Expand Down Expand Up @@ -68,10 +69,17 @@ export const addProvider = (
export const getProvider = (
networkName: string,
providers: Record<string, ethers.providers.JsonRpcProvider>,
provider: ethers.providers.JsonRpcProvider,
provider?: ethers.providers.JsonRpcProvider,
) => {
if (!providers[networkName]) {
return provider
if (provider) {
return provider
} else {
throw new AdapterInputError({
statusCode: 400,
message: `Missing ${networkName}_RPC_URL or ${networkName}_RPC_URL environment variables`,
})
}
} else {
return providers[networkName]
}
Expand Down
4 changes: 4 additions & 0 deletions packages/sources/por-address-list/test-payload.json
Original file line number Diff line number Diff line change
Expand Up @@ -14,5 +14,9 @@
"endpoint": "bedrockBtcAddress"
}, {
"endpoint": "solvBtcAddress"
}, {
"endpoint": "multichainAddress",
"contractAddress": "0xb7C0817Dd23DE89E4204502dd2C2EF7F57d3A3B8",
"contractAddressNetwork": "BINANCE"
}]
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP

exports[`execute multichainAddress endpoint should return success 1`] = `
{
"data": {
"result": [
{
"chainId": "56",
"contractAddress": "token1",
"wallets": [
"vault1",
"vault2",
],
},
{
"chainId": "223",
"contractAddress": "token3",
"wallets": [
"vault3",
],
},
],
},
"result": null,
"statusCode": 200,
"timestamps": {
"providerDataReceivedUnixMs": 978347471111,
"providerDataRequestedUnixMs": 978347471111,
},
}
`;
Loading

0 comments on commit 5f0ae26

Please sign in to comment.