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
27 changes: 25 additions & 2 deletions .pnp.cjs

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

5 changes: 5 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -77,5 +77,10 @@
"resolutions": {
"ethereum-cryptography@^1.1.2": "patch:ethereum-cryptography@npm%3A1.1.2#./.yarn/patches/ethereum-cryptography-npm-1.1.2-c16cfd7e8a.patch",
"ethereum-cryptography@^1.0.3": "patch:ethereum-cryptography@npm%3A1.1.2#./.yarn/patches/ethereum-cryptography-npm-1.1.2-c16cfd7e8a.patch"
},
"dependenciesMeta": {
"@chainlink/external-adapter-framework@2.9.0": {
"unplugged": true
}
}
}
Empty file.
3 changes: 3 additions & 0 deletions packages/sources/xusd-usd-exchange-rate/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
# Chainlink External Adapter for example-adapter

This README will be generated automatically when code is merged to `main`. If you would like to generate a preview of the README, please run `yarn generate:readme example-adapter`.
43 changes: 43 additions & 0 deletions packages/sources/xusd-usd-exchange-rate/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
{
"name": "@chainlink/xusd-usd-exchange-rate-adapter",
"version": "0.0.0",
"description": "External Adapter for fetching the round value from the XUSD contract on Ethereum for XUSD-USD exchange rate",
"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": {
"@sinonjs/fake-timers": "9.1.2",
"@types/jest": "^29.5.14",
"@types/node": "22.14.1",
"@types/sinonjs__fake-timers": "8.1.5",
"nock": "13.5.6",
"typescript": "5.8.3"
},
"dependencies": {
"@chainlink/external-adapter-framework": "2.11.4",
"ethers": "^6.13.2",
"tslib": "2.4.1"
}
}
20 changes: 20 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,20 @@
import { AdapterConfig } from '@chainlink/external-adapter-framework/config'

export const config = new AdapterConfig({
ETHEREUM_RPC_URL: {
description: 'Ethereum JSON-RPC endpoint URL',
type: 'string',
required: true,
},
ETHEREUM_CHAIN_ID: {
description: 'The chain id to connect to',
type: 'number',
default: 1,
},
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 roundEndpoint } from './round'
20 changes: 20 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,20 @@
import { AdapterEndpoint } from '@chainlink/external-adapter-framework/adapter'
import { EmptyInputParameters } from '@chainlink/external-adapter-framework/validation/input-params'
import { config } from '../config'
import { roundTransport } from '../transport/round'

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

export const endpoint = new AdapterEndpoint({
name: 'round',
transport: roundTransport,
})
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 { roundEndpoint } from './endpoint'

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

export const server = (): Promise<ServerInstance | undefined> => expose(adapter)
92 changes: 92 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,92 @@
import { EndpointContext } from '@chainlink/external-adapter-framework/adapter'
import { TransportDependencies } from '@chainlink/external-adapter-framework/transports'
import { SubscriptionTransport } from '@chainlink/external-adapter-framework/transports/abstract/subscription'
import { AdapterResponse, makeLogger, sleep } from '@chainlink/external-adapter-framework/util'
import { ethers } from 'ethers'
import { BaseEndpointTypes } from '../endpoint/round'

const logger = makeLogger('XUSD USD Exchange Rate')

const XUSD_CONTRACT_ADDRESS = '0xE2Fc85BfB48C4cF147921fBE110cf92Ef9f26F94'
const ROUND_FUNCTION_SELECTOR = '0x146ca531'

export type RoundTransportTypes = BaseEndpointTypes

export function hexToDecimalString(resultHex: string): string {
return BigInt(resultHex).toString()
}

export class RoundTransport extends SubscriptionTransport<RoundTransportTypes> {
provider!: ethers.JsonRpcProvider

async initialize(
dependencies: TransportDependencies<RoundTransportTypes>,
adapterSettings: RoundTransportTypes['Settings'],
endpointName: string,
transportName: string,
): Promise<void> {
await super.initialize(dependencies, adapterSettings, endpointName, transportName)
this.provider = new ethers.JsonRpcProvider(
adapterSettings.ETHEREUM_RPC_URL,
adapterSettings.ETHEREUM_CHAIN_ID,
)
}

async backgroundHandler(
context: EndpointContext<RoundTransportTypes>,
_entries: RoundTransportTypes['Parameters'][],
): Promise<void> {
await this.handleRequest()
await sleep(context.adapterSettings.BACKGROUND_EXECUTE_MS)
}

async handleRequest(): Promise<void> {
let response: AdapterResponse<RoundTransportTypes['Response']>
try {
response = await this._handleRequest()
} catch (e) {
logger.error(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: {}, response }])
}

async _handleRequest(): Promise<AdapterResponse<RoundTransportTypes['Response']>> {
const providerDataRequestedUnixMs = Date.now()

const resultHex = await this.provider.call({
to: XUSD_CONTRACT_ADDRESS,
data: ROUND_FUNCTION_SELECTOR,
})

const result = hexToDecimalString(resultHex)

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

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

export const roundTransport = new RoundTransport()
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,51 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP

exports[`execute round endpoint happy path should return success for default endpoint (no endpoint specified) 1`] = `
{
"data": {
"result": "1000000000000000000",
},
"result": "1000000000000000000",
"statusCode": 200,
"timestamps": {
"providerDataReceivedUnixMs": 978347471111,
"providerDataRequestedUnixMs": 978347471111,
},
}
`;

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

exports[`execute round endpoint upstream failures should handle RPC endpoint failure with 502 1`] = `
{
"errorMessage": "server response 500 (request={ }, response={ }, error=null, info={ "requestUrl": "[ETHEREUM_RPC_URL REDACTED]", "responseBody": "{\\"error\\":\\"Internal Server Error\\"}", "responseStatus": "500 " }, code=SERVER_ERROR, version=6.15.0)",
"statusCode": 502,
"timestamps": {
"providerDataReceivedUnixMs": 0,
"providerDataRequestedUnixMs": 0,
},
}
`;

exports[`execute round endpoint upstream failures should handle contract execution revert 1`] = `
{
"errorMessage": "missing revert data (action="call", data=null, reason=null, transaction={ "data": "0x146ca531", "to": "0xE2Fc85BfB48C4cF147921fBE110cf92Ef9f26F94" }, invocation=null, revert=null, code=CALL_EXCEPTION, version=6.15.0)",
"statusCode": 502,
"timestamps": {
"providerDataReceivedUnixMs": 0,
"providerDataRequestedUnixMs": 0,
},
}
`;
Loading
Loading