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/orange-cats-rhyme.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@chainlink/accountable-adapter': major
---

Accountable EA
20 changes: 20 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/sources/accountable/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
# @chainlink/accountable-adapter
54 changes: 54 additions & 0 deletions packages/sources/accountable/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
# ACCOUNTABLE

![1.5.3](https://img.shields.io/github/package-json/v/smartcontractkit/external-adapters-js?filename=packages/sources/accountable/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 |
| :-------: | :----------: | :-----------------: | :----: | :-----: | :-----: |
| ✅ | API_ENDPOINT | API Endpoint to use | string | | |

---

## Data Provider Rate Limits

| Name | Requests/credits per second | Requests/credits per minute | Requests/credits per hour | Note |
| :-----: | :-------------------------: | :-------------------------: | :-----------------------: | :--: |
| default | | 30 | | |

---

## Input Parameters

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

## Reserve Endpoint

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

### Input Params

| Required? | Name | Aliases | Description | Type | Options | Default | Depends On | Not Valid With |
| :-------: | :----: | :-----: | :------------------------------------------------: | :----: | :-----: | :-----: | :--------: | :------------: |
| ✅ | client | | The name of the Accountable client to consume from | string | | | | |

### Example

Request:

```json
{
"data": {
"endpoint": "reserve",
"client": "axis"
}
}
```

---

MIT License
40 changes: 40 additions & 0 deletions packages/sources/accountable/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
{
"name": "@chainlink/accountable-adapter",
"version": "1.0.0",
"description": "Chainlink's Accountable adapter.",
"keywords": [
"Chainlink",
"LINK",
"blockchain",
"oracle",
"accountable"
],
"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.0",
"tslib": "2.4.1"
}
}
9 changes: 9 additions & 0 deletions packages/sources/accountable/src/config/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import { AdapterConfig } from '@chainlink/external-adapter-framework/config'

export const config = new AdapterConfig({
API_ENDPOINT: {
description: 'API Endpoint to use',
type: 'string',
required: true,
},
})
1 change: 1 addition & 0 deletions packages/sources/accountable/src/endpoint/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export { endpoint as reserve } from './reserve'
42 changes: 42 additions & 0 deletions packages/sources/accountable/src/endpoint/reserve.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
import {
PoRProviderEndpoint,
PoRProviderResponse,
} from '@chainlink/external-adapter-framework/adapter/por'
import { InputParameters } from '@chainlink/external-adapter-framework/validation'
import { AdapterError } from '@chainlink/external-adapter-framework/validation/error'
import { config } from '../config'
import { getApiKey, httpTransport } from '../transport/reserve'

export const inputParameters = new InputParameters(
{
client: {
type: 'string',
required: true,
description:
'The name of the Accountable client to consume from. Used to match {client}_API_KEY env variable',
},
},
[
{
client: 'axis',
},
],
)

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

export const endpoint = new PoRProviderEndpoint({
name: 'reserve',
transport: httpTransport,
inputParameters,
customInputValidation: (request): AdapterError | undefined => {
if (request.requestContext.data.client) {
getApiKey(request.requestContext.data.client)
}
return
},
})
20 changes: 20 additions & 0 deletions packages/sources/accountable/src/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import { expose, ServerInstance } from '@chainlink/external-adapter-framework'
import { PoRAdapter } from '@chainlink/external-adapter-framework/adapter/por'
import { config } from './config'
import { reserve } from './endpoint'

export const adapter = new PoRAdapter({
defaultEndpoint: reserve.name,
name: 'ACCOUNTABLE',
config,
endpoints: [reserve],
rateLimiting: {
tiers: {
default: {
rateLimit1m: 30,
},
},
},
})

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

export interface ResponseSchema {
client: string
totalReserve: number
totalSupply: number
underlyingAssets: {
name: string
value: number
}[]
collateralization: number
}

export type HttpTransportTypes = BaseEndpointTypes & {
Provider: {
RequestBody: never
ResponseBody: ResponseSchema
}
}

export const getApiKey = (client: string) => {
const apiKeyName = `${client.replace(/-/g, '_').toUpperCase()}_API_KEY`
const apiKeyValue = process.env[apiKeyName]

if (!apiKeyValue) {
throw new AdapterInputError({
statusCode: 400,
message: `Missing '${apiKeyName}' environment variable.`,
})
}

return apiKeyValue
}

export const httpTransport = new HttpTransport<HttpTransportTypes>({
prepareRequests: (params, config) => {
return params.map((param) => {
const client = param.client
return {
params: [param],
request: {
baseURL: config.API_ENDPOINT,
params: {
client: client,
},
headers: {
'Content-Type': 'application/json',
Authorization: `Bearer ${getApiKey(client)}`,
},
},
}
})
},
parseResponse: (params, response) => {
return params.map((param) => {
const result = response.data.totalReserve
const totalReserve = Number(result)
return {
params: param,
response: {
result,
data: {
result,
totalReserve,
ripcord: false,
},
},
}
})
},
})
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP

exports[`execute reserve endpoint should fail if client name is not present or wrong 1`] = `
{
"error": {
"message": "[Param: client] param is required but no value was provided",
"name": "AdapterError",
},
"status": "errored",
"statusCode": 400,
}
`;

exports[`execute reserve endpoint should return success for axis 1`] = `
{
"data": {
"result": "40438382.35",
"ripcord": false,
"totalReserve": 40438382.35,
},
"result": "40438382.35",
"statusCode": 200,
"timestamps": {
"providerDataReceivedUnixMs": 978347471111,
"providerDataRequestedUnixMs": 978347471111,
},
}
`;

exports[`execute reserve endpoint should return success for unitas 1`] = `
{
"data": {
"result": "25559832.85",
"ripcord": false,
"totalReserve": 25559832.85,
},
"result": "25559832.85",
"statusCode": 200,
"timestamps": {
"providerDataReceivedUnixMs": 978347471111,
"providerDataRequestedUnixMs": 978347471111,
},
}
`;
41 changes: 41 additions & 0 deletions packages/sources/accountable/test/integration/fixtures.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
import nock from 'nock'

export const mockReserveAxisResponseSuccess = (): nock.Scope =>
nock('http://test-endpoint-new?client=axis', {
encodedQueryParams: true,
})
.get('/')
.query({ client: 'axis' })
.reply(200, {
client: 'axis',
totalReserve: '40438382.35',
totalSupply: '40355393.07',
underlyingAssets: [
{ name: 'Copper', value: 20000000 },
{ name: 'Fireblocks', value: 20438382.35 },
{ name: 'Insurance Fund', value: 630502.74 },
{ name: 'Ethereum Chain', value: 76414.71688973988 },
{ name: 'Binance', value: 3095.5639951004273 },
{ name: 'BNB Smart Chain', value: 7.3854242495045 },
],
collateralization: 1.002056,
})
.persist()

export const mockReserveUnitasResponseSuccess = (): nock.Scope =>
nock('http://test-endpoint-new?client=unitas', {
encodedQueryParams: true,
})
.get('/')
.query({ client: 'unitas' })
.reply(200, {
client: 'unitas',
totalReserve: '25559832.85',
totalSupply: '25559832.85',
underlyingAssets: [
{ name: 'Solana', value: 18440848.73 },
{ name: 'Binance', value: 7118984.11 },
],
collateralization: 1.00416,
})
.persist()
Loading
Loading