Skip to content

Commit

Permalink
Added new view-function-multi-chain EA (#3217)
Browse files Browse the repository at this point in the history
* add view-function-multi-chain EA

* update test-payload
  • Loading branch information
karen-stepanyan authored Mar 8, 2024
1 parent 7324bac commit 240f191
Show file tree
Hide file tree
Showing 20 changed files with 649 additions and 0 deletions.
5 changes: 5 additions & 0 deletions .changeset/dry-laws-hang.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@chainlink/view-function-multi-chain-adapter': major
---

Initial version of the EA
21 changes: 21 additions & 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/scripts/src/generate-readme/readmeBlacklist.json
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,7 @@
"the-graph",
"token-allocation",
"vesper",
"view-function-multi-chain",
"xsushi-price"
]
}
Empty file.
57 changes: 57 additions & 0 deletions packages/sources/view-function-multi-chain/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
# VIEW_FUNCTION_MULTI_CHAIN

![v3](https://img.shields.io/badge/framework%20version-v3-blueviolet)

## Environment Variables

| Required? | Name | Description | Type | Options | Default |
| :-------: | :-------------------: | :---------------------------------------------------------------------------------------: | :----: | :-----: | :-----: |
|| {NETWORK}\_RPC_URL | RPC url for a NETWORK. NETWORK is the value of `network` input param | string | | |
|| {NETWORK}\_CHAIN_ID | Chain id for a NETWORK. NETWORK is the value of `network` input param | number | | |
| | BACKGROUND_EXECUTE_MS | The amount of time the background execute should sleep before performing the next request | number | | `10000` |

---

## Data Provider Rate Limits

There are no rate limits for this adapter.

---

## Input Parameters

| Required? | Name | Description | Type | Options | Default |
| :-------: | :------: | :-----------------: | :----: | :----------------------------: | :--------: |
| | endpoint | The endpoint to use | string | [function](#function-endpoint) | `function` |

## Function Endpoint

`function` is the only supported name for this endpoint.

### Input Params

| Required? | Name | Aliases | Description | Type | Options | Default | Depends On | Not Valid With |
| :-------: | :---------: | :--------: | :---------------------------------------------------------------------------------------------------------------------------------------------------------: | :------: | :-----: | :-----: | :--------: | :------------: |
|| signature | `function` | Function signature. Should be formatted as [human readable ABI](https://docs.ethers.io/v5/single-page/#/v5/getting-started/-%23-getting-started--contracts) | string | | | | |
|| address | `contract` | Address of the contract | string | | | | |
| | inputParams | | Array of function parameters in order | string[] | | | | |
|| network | | RPC network name | string | | | | |

### Example

Request:

```json
{
"data": {
"endpoint": "function",
"signature": "function latestAnswer() view returns (int256)",
"address": "0x779877A7B0D9E8603169DdbD7836e478b4624789",
"network": "ETHEREUM_GOERLI"
}
}
```

---

MIT License
41 changes: 41 additions & 0 deletions packages/sources/view-function-multi-chain/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
{
"name": "@chainlink/view-function-multi-chain-adapter",
"version": "0.0.0",
"description": "Chainlink view-function-multi-chain adapter.",
"keywords": [
"Chainlink",
"LINK",
"blockchain",
"oracle",
"view-function-multi-chain"
],
"main": "dist/index.js",
"types": "dist/index.d.ts",
"files": [
"dist"
],
"repository": {
"url": "https://github.com/smartcontractkit/external-adapters-js",
"type": "git"
},
"license": "MIT",
"scripts": {
"clean": "rm -rf dist && rm -f tsconfig.tsbuildinfo",
"prepack": "yarn build",
"build": "tsc -b",
"server": "node -e 'require(\"./index.js\").server()'",
"server:dist": "node -e 'require(\"./dist/index.js\").server()'",
"start": "yarn server:dist"
},
"devDependencies": {
"@types/jest": "27.5.2",
"@types/node": "16.11.68",
"nock": "13.2.9",
"typescript": "5.0.4"
},
"dependencies": {
"@chainlink/external-adapter-framework": "0.33.4",
"ethers": "^5.4.6",
"tslib": "2.4.1"
}
}
10 changes: 10 additions & 0 deletions packages/sources/view-function-multi-chain/src/config/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import { AdapterConfig } from '@chainlink/external-adapter-framework/config'

export const config = new AdapterConfig({
BACKGROUND_EXECUTE_MS: {
description:
'The amount of time the background execute should sleep before performing the next request',
type: 'number',
default: 10_000,
},
})
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
import { AdapterEndpoint } from '@chainlink/external-adapter-framework/adapter'
import { InputParameters } from '@chainlink/external-adapter-framework/validation'
import { config } from '../config'
import { multiChainFunctionTransport } from '../transport/function'

export const inputParameters = new InputParameters({
signature: {
type: 'string',
aliases: ['function'],
required: true,
description:
'Function signature. Should be formatted as [human readable ABI](https://docs.ethers.io/v5/single-page/#/v5/getting-started/-%23-getting-started--contracts)',
},
address: {
aliases: ['contract'],
required: true,
description: 'Address of the contract',
type: 'string',
},
inputParams: {
array: true,
description: 'Array of function parameters in order',
type: 'string',
},
network: {
required: true,
description: 'RPC network name',
type: 'string',
},
})

export type BaseEndpointTypes = {
Parameters: typeof inputParameters.definition
Response: {
Data: {
result: string
}
Result: string
}
Settings: typeof config.settings
}

export const endpoint = new AdapterEndpoint({
name: 'function',
transport: multiChainFunctionTransport,
inputParameters,
})
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export { endpoint as functionEndpoint } from './function'
13 changes: 13 additions & 0 deletions packages/sources/view-function-multi-chain/src/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import { expose, ServerInstance } from '@chainlink/external-adapter-framework'
import { Adapter } from '@chainlink/external-adapter-framework/adapter'
import { config } from './config'
import { functionEndpoint } from './endpoint'

export const adapter = new Adapter({
defaultEndpoint: functionEndpoint.name,
name: 'VIEW_FUNCTION_MULTI_CHAIN',
config,
endpoints: [functionEndpoint],
})

export const server = (): Promise<ServerInstance | undefined> => expose(adapter)
110 changes: 110 additions & 0 deletions packages/sources/view-function-multi-chain/src/transport/function.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
import { TransportDependencies } from '@chainlink/external-adapter-framework/transports'
import { AdapterResponse, makeLogger, sleep } from '@chainlink/external-adapter-framework/util'
import { SubscriptionTransport } from '@chainlink/external-adapter-framework/transports/abstract/subscription'
import { EndpointContext } from '@chainlink/external-adapter-framework/adapter'
import { ethers, utils } from 'ethers'
import { BaseEndpointTypes, inputParameters } from '../endpoint/function'
import { AdapterInputError } from '@chainlink/external-adapter-framework/validation/error'

const logger = makeLogger('View Function Multi Chain')

export type MultiChainFunctionTransportTypes = BaseEndpointTypes

type RequestParams = typeof inputParameters.validated

export class MultiChainFunctionTransport extends SubscriptionTransport<MultiChainFunctionTransportTypes> {
providers: Record<string, ethers.providers.JsonRpcProvider> = {}

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

async backgroundHandler(
context: EndpointContext<MultiChainFunctionTransportTypes>,
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<MultiChainFunctionTransportTypes['Response']>
try {
response = await this._handleRequest(param)
} catch (e: unknown) {
const errorMessage = e instanceof Error ? e.message : 'Unknown error occurred'
logger.error(e, errorMessage)
response = {
statusCode: (e as AdapterInputError)?.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<MultiChainFunctionTransportTypes['Response']>> {
const { address, signature, inputParams, network } = param

const networkName = network.toUpperCase()
const networkEnvName = `${networkName}_RPC_URL`
const chainIdEnvName = `${networkName}_CHAIN_ID`

const rpcUrl = process.env[networkEnvName]
const chainId = Number(process.env[chainIdEnvName])

if (!rpcUrl || isNaN(chainId)) {
throw new AdapterInputError({
statusCode: 400,
message: `Missing '${networkEnvName}' or '${chainIdEnvName}' environment variables.`,
})
}

if (!this.providers[networkName]) {
this.providers[networkName] = new ethers.providers.JsonRpcProvider(rpcUrl, chainId)
}

const iface = new utils.Interface([signature])
const fnName = iface.functions[Object.keys(iface.functions)[0]].name

const encoded = iface.encodeFunctionData(fnName, [...(inputParams || [])])

const providerDataRequestedUnixMs = Date.now()
const result = await this.providers[networkName].call({
to: address,
data: encoded,
})

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

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

export const multiChainFunctionTransport = new MultiChainFunctionTransport()
7 changes: 7 additions & 0 deletions packages/sources/view-function-multi-chain/test-payload.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
{
"requests": [{
"contract": "0x2c1d072e956AFFC0D435Cb7AC38EF18d24d9127c",
"function": "function latestAnswer() view returns (int256)",
"network": "ETHEREUM_MAINNET"
}]
}
Loading

0 comments on commit 240f191

Please sign in to comment.