Skip to content
Open
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/swift-shoes-smell.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@chainlink/xusd-usd-exchange-rate-adapter': minor
---

Add xusd-usd-exchange-rate adapter
22 changes: 21 additions & 1 deletion .pnp.cjs

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

50 changes: 50 additions & 0 deletions packages/sources/xusd-usd-exchange-rate/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
# XUSD_USD_EXCHANGE_RATE

![0.0.0](https://img.shields.io/github/package-json/v/smartcontractkit/external-adapters-js?filename=packages/sources/xusd-usd-exchange-rate/package.json) ![v3](https://img.shields.io/badge/framework%20version-v3-blueviolet)

This document was generated automatically. Please see [README Generator](../../scripts#readme-generator) for more info.

## Environment Variables

| Required? | Name | Description | Type | Options | Default |
| :-------: | :-------------------: | :---------------------------------------------------------------------------------------: | :----: | :-----: | :-----: |
| ✅ | ETHEREUM_RPC_URL | Ethereum JSON-RPC endpoint URL provided by the node operator | string | | |
| | 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 | [round](#round-endpoint) | `round` |

## Round Endpoint

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

### Input Params

There are no input parameters for this endpoint.

### Example

Request:

```json
{
"data": {
"endpoint": "round"
}
}
```

---

MIT License
40 changes: 40 additions & 0 deletions packages/sources/xusd-usd-exchange-rate/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
{
"name": "@chainlink/xusd-usd-exchange-rate-adapter",
"version": "0.0.0",
"description": "Chainlink XUSD-USD Exchange Rate adapter for fetching the round value from the XUSD contract on Ethereum.",
"keywords": [
"Chainlink",
"LINK",
"blockchain",
"oracle",
"xusd-usd-exchange-rate"
],
"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": "^29.5.14",
"@types/node": "22.14.1",
"nock": "13.5.6",
"typescript": "5.8.3"
},
"dependencies": {
"@chainlink/external-adapter-framework": "2.11.2",
"tslib": "2.4.1"
}
}
15 changes: 15 additions & 0 deletions packages/sources/xusd-usd-exchange-rate/src/config/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import { AdapterConfig } from '@chainlink/external-adapter-framework/config'

export const config = new AdapterConfig({
ETHEREUM_RPC_URL: {
description: 'Ethereum JSON-RPC endpoint URL provided by the node operator',
type: 'string',
required: true,
},
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 @@
export { endpoint as round } from './round'
19 changes: 19 additions & 0 deletions packages/sources/xusd-usd-exchange-rate/src/endpoint/round.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import { AdapterEndpoint } from '@chainlink/external-adapter-framework/adapter'
import { SingleNumberResultResponse } from '@chainlink/external-adapter-framework/util'
import { InputParameters } from '@chainlink/external-adapter-framework/validation'
import { config } from '../config'
import { httpTransport } from '../transport/round'

export const inputParameters = new InputParameters({}, [{}])
Copy link
Contributor

Choose a reason for hiding this comment

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

There is an EmptyInputParameters from framework


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

export const endpoint = new AdapterEndpoint({
name: 'round',
transport: httpTransport,
inputParameters,
})
13 changes: 13 additions & 0 deletions packages/sources/xusd-usd-exchange-rate/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 { round } from './endpoint'

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

export const server = (): Promise<ServerInstance | undefined> => expose(adapter)
109 changes: 109 additions & 0 deletions packages/sources/xusd-usd-exchange-rate/src/transport/round.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
import { HttpTransport } from '@chainlink/external-adapter-framework/transports'
import { BaseEndpointTypes } from '../endpoint/round'

export const XUSD_CONTRACT_ADDRESS = '0xE2Fc85BfB48C4cF147921fBE110cf92Ef9f26F94'
export const ROUND_FUNCTION_SELECTOR = '0x146ca531'
Comment on lines +4 to +5
Copy link
Contributor

Choose a reason for hiding this comment

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

These should come from input


export interface JsonRpcRequest {
jsonrpc: string
method: string
params: [{ to: string; data: string }, string]
id: number
}

export interface JsonRpcResponse {
jsonrpc: string
id: number
result?: string
error?: {
code: number
message: string
}
}

export type HttpTransportTypes = BaseEndpointTypes & {
Provider: {
RequestBody: JsonRpcRequest
ResponseBody: JsonRpcResponse
}
}

export type RequestParams = Record<string, never>

export interface PreparedRequest {
params: RequestParams[]
request: {
baseURL: string
url: string
method: string
headers: Record<string, string>
data: JsonRpcRequest
}
}

export const buildEthCallRequest = (rpcUrl: string): PreparedRequest['request'] => ({
baseURL: rpcUrl,
url: '',
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
data: {
jsonrpc: '2.0',
method: 'eth_call',
params: [
{
to: XUSD_CONTRACT_ADDRESS,
data: ROUND_FUNCTION_SELECTOR,
},
'latest',
],
id: 1,
Copy link
Contributor

Choose a reason for hiding this comment

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

Are we hardcoding this to 1 everytime?

},
})

export const parseHexToInt = (hexValue: string): number => {
return parseInt(hexValue, 16)
}

export const buildErrorResponse = (param: RequestParams, errorMessage: string) => ({
params: param,
response: {
errorMessage,
statusCode: 502,
},
})

export const buildSuccessResponse = (param: RequestParams, result: number) => ({
params: param,
response: {
result,
data: {
result,
},
},
})

export const httpTransport = new HttpTransport<HttpTransportTypes>({
prepareRequests: (params, config) => {
return params.map((param) => ({
params: [param],
request: buildEthCallRequest(config.ETHEREUM_RPC_URL),
}))
},
Comment on lines +88 to +93
Copy link
Contributor

Choose a reason for hiding this comment

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

This is wrong, you are building the same request over and over again. Just need to return 1 request here

parseResponse: (params, response) => {
if (response.data.error) {
return params.map((param) =>
buildErrorResponse(param, `RPC error: ${response.data.error?.message}`),
)
}

if (!response.data.result) {
return params.map((param) => buildErrorResponse(param, 'No result returned from eth_call'))
}

const result = parseHexToInt(response.data.result)

return params.map((param) => buildSuccessResponse(param, result))
},
})
7 changes: 7 additions & 0 deletions packages/sources/xusd-usd-exchange-rate/test-payload.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
{
"requests": [
{
"endpoint": "round"
}
]
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP

exports[`execute round endpoint happy path should return success 1`] = `
{
"data": {
"result": 289,
},
"result": 289,
"statusCode": 200,
"timestamps": {
"providerDataReceivedUnixMs": 978347471111,
"providerDataRequestedUnixMs": 978347471111,
},
}
`;

exports[`execute round endpoint happy path should return success with empty request body (default endpoint) 1`] = `
{
"data": {
"result": 289,
},
"result": 289,
"statusCode": 200,
"timestamps": {
"providerDataReceivedUnixMs": 978347471111,
"providerDataRequestedUnixMs": 978347471111,
},
}
`;

exports[`execute round endpoint upstream failures should handle RPC error response 1`] = `
{
"errorMessage": "RPC error: execution reverted",
"statusCode": 502,
"timestamps": {
"providerDataReceivedUnixMs": 978347471111,
"providerDataRequestedUnixMs": 978347471111,
},
}
`;

exports[`execute round endpoint upstream failures should handle response with no result 1`] = `
{
"errorMessage": "No result returned from eth_call",
"statusCode": 502,
"timestamps": {
"providerDataReceivedUnixMs": 978347471111,
"providerDataRequestedUnixMs": 978347471111,
},
}
`;

exports[`execute round endpoint upstream failures should handle server error (5xx) 1`] = `
{
"errorMessage": "Provider request failed with status 500: "Internal Server Error"",
"statusCode": 502,
"timestamps": {
"providerDataReceivedUnixMs": 978347471111,
"providerDataRequestedUnixMs": 978347471111,
},
}
`;

exports[`execute round endpoint validation errors should return error for invalid endpoint 1`] = `
{
"error": {
"message": "Adapter does not have a "invalid-endpoint" endpoint.",
"name": "AdapterError",
},
"status": "errored",
"statusCode": 404,
}
`;
Loading
Loading