Skip to content

Commit 861f5a7

Browse files
Generic API EA (#4134)
* Generated boilerplate * generic-api EA * address comments * ripcordDisabledValue description * Generate readme * Manually change readme * Make auth header optional * Remove known-issues.md * Blacklist README generation --------- Co-authored-by: app-token-issuer-data-feeds[bot] <134377064+app-token-issuer-data-feeds[bot]@users.noreply.github.com>
1 parent 1f72e12 commit 861f5a7

File tree

20 files changed

+954
-0
lines changed

20 files changed

+954
-0
lines changed

.changeset/hot-rabbits-shout.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
'@chainlink/generic-api-adapter': major
3+
---
4+
5+
Initial release of the generic-api adapter.

.pnp.cjs

Lines changed: 22 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

packages/scripts/src/generate-readme/readmeBlacklist.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@
2222
"dlc-btc-por",
2323
"dxdao",
2424
"dydx-stark",
25+
"generic-api",
2526
"google-bigquery",
2627
"google-weather",
2728
"graphql",

packages/sources/generic-api/CHANGELOG.md

Whitespace-only changes.
Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
# GENERIC_API
2+
3+
![0.0.0](https://img.shields.io/github/package-json/v/smartcontractkit/external-adapters-js?filename=packages/sources/generic-api/package.json) ![v3](https://img.shields.io/badge/framework%20version-v3-blueviolet)
4+
5+
This document maintained manually.
6+
7+
## Variable env vars
8+
9+
To support a specific value for input parameter `apiName`, the environment variable `<API_NAME>_API_URL`, and optionally `<API_NAME>_AUTH_HEADER` and `<API_NAME>_AUTH_HEADER_VALUE` must be set, where `<API_NAME>` is the upper-snake-case version of the value provided for the `apiName` parameter.
10+
11+
## Environment Variables
12+
13+
| Required? | Name | Description | Type |
14+
| :-------: | :---------------------------: | :---------------------------------------------------: | :----: |
15+
|| {API_NAME}\_API_URL | The API URL to use for a given `apiUrl`. | string |
16+
| | {API_NAME}\_AUTH_HEADER | The header to pass the authentication credentials on. | string |
17+
| | {API_NAME}\_AUTH_HEADER_VALUE | The credentials to pass on the authentcation header. | string |
18+
19+
---
20+
21+
## Data Provider Rate Limits
22+
23+
| Name | Requests/credits per second | Requests/credits per minute | Requests/credits per hour | Note |
24+
| :-----: | :-------------------------: | :-------------------------: | :-----------------------: | :--: |
25+
| default | | 20 | | |
26+
27+
---
28+
29+
## Input Parameters
30+
31+
| Required? | Name | Description | Type | Options | Default |
32+
| :-------: | :------: | :-----------------: | :----: | :--------------------: | :-----: |
33+
| | endpoint | The endpoint to use | string | [http](#http-endpoint) | `http` |
34+
35+
## Http Endpoint
36+
37+
`http` is the only supported name for this endpoint.
38+
39+
### Input Params
40+
41+
| Required? | Name | Aliases | Description | Type | Options | Default | Depends On | Not Valid With |
42+
| :-------: | :------------------: | :-----: | :------------------------------------------------------------------------------------: | :----: | :-----: | :-----: | :--------: | :------------: |
43+
|| apiName | | Used as prefix for environment variables to find API config | string | | | | |
44+
|| dataPath | | The path to the field containing the data to return | string | | | | |
45+
| | ripcordPath | | The path to the ripcord field if expected | string | | | | |
46+
| | ripcordDisabledValue | | The value the ripcord field should have during normal operation, converted to a string | string | | `false` | | |
47+
48+
### Example
49+
50+
Request:
51+
52+
```json
53+
{
54+
"data": {
55+
"endpoint": "http",
56+
"apiName": "client-name",
57+
"dataPath": "PoR",
58+
"ripcordPath": "ripcord",
59+
"ripcordDisabledValue": "false"
60+
}
61+
}
62+
```
63+
64+
---
65+
66+
MIT License
Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
{
2+
"name": "@chainlink/generic-api-adapter",
3+
"version": "0.0.0",
4+
"description": "Chainlink generic-api adapter.",
5+
"keywords": [
6+
"Chainlink",
7+
"LINK",
8+
"blockchain",
9+
"oracle",
10+
"generic-api"
11+
],
12+
"main": "dist/index.js",
13+
"types": "dist/index.d.ts",
14+
"files": [
15+
"dist"
16+
],
17+
"repository": {
18+
"url": "https://github.com/smartcontractkit/external-adapters-js",
19+
"type": "git"
20+
},
21+
"license": "MIT",
22+
"scripts": {
23+
"clean": "rm -rf dist && rm -f tsconfig.tsbuildinfo",
24+
"prepack": "yarn build",
25+
"build": "tsc -b",
26+
"server": "node -e 'require(\"./index.js\").server()'",
27+
"server:dist": "node -e 'require(\"./dist/index.js\").server()'",
28+
"start": "yarn server:dist"
29+
},
30+
"devDependencies": {
31+
"@types/jest": "^29.5.14",
32+
"@types/node": "22.14.1",
33+
"@types/object-path": "0.11.4",
34+
"nock": "13.5.6",
35+
"typescript": "5.8.3"
36+
},
37+
"dependencies": {
38+
"@chainlink/external-adapter-framework": "2.8.0",
39+
"object-path": "^0.11.5",
40+
"tslib": "2.4.1"
41+
}
42+
}
Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
import { AdapterConfig } from '@chainlink/external-adapter-framework/config'
2+
import { AdapterInputError } from '@chainlink/external-adapter-framework/validation/error'
3+
4+
export const config = new AdapterConfig({})
5+
6+
const sanitizeEnvVarName = (name: string): string => {
7+
return name.toUpperCase().replace(/[^a-zA-Z0-9]/g, '_')
8+
}
9+
10+
const getEnvVarName = (prefix: string, name: string): string => {
11+
return sanitizeEnvVarName(`${prefix}_${name}`)
12+
}
13+
14+
const getEnvVar = (prefix: string, name: string, required: boolean): string | undefined => {
15+
const envVarName = getEnvVarName(prefix, name)
16+
if (required && !(envVarName in process.env)) {
17+
throw new AdapterInputError({
18+
message: `Missing required environment variable '${envVarName}'.`,
19+
statusCode: 400,
20+
})
21+
}
22+
return process.env[envVarName] as string
23+
}
24+
25+
export const getApiConfig = (apiName: string) => {
26+
const url = getEnvVar(apiName, 'API_URL', true) as string
27+
const authHeader = getEnvVar(apiName, 'AUTH_HEADER', false)
28+
const authHeaderValue = getEnvVar(apiName, 'AUTH_HEADER_VALUE', false)
29+
if (!!authHeader !== !!authHeaderValue) {
30+
const authHeadVarName = getEnvVarName(apiName, 'AUTH_HEADER')
31+
const authHeadValueVarName = getEnvVarName(apiName, 'AUTH_HEADER_VALUE')
32+
throw new AdapterInputError({
33+
message: `If one of ${authHeadVarName} or ${authHeadValueVarName} is set, both must be set.`,
34+
statusCode: 500,
35+
})
36+
}
37+
return {
38+
url,
39+
authHeader,
40+
authHeaderValue,
41+
}
42+
}
Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
import { AdapterEndpoint } from '@chainlink/external-adapter-framework/adapter'
2+
import { InputParameters } from '@chainlink/external-adapter-framework/validation'
3+
import { AdapterError } from '@chainlink/external-adapter-framework/validation/error'
4+
import { config, getApiConfig } from '../config'
5+
import { httpTransport } from '../transport/http'
6+
7+
export const inputParameters = new InputParameters(
8+
{
9+
apiName: {
10+
required: true,
11+
type: 'string',
12+
description: 'Used as prefix for environment variables to find API config',
13+
},
14+
dataPath: {
15+
required: true,
16+
type: 'string',
17+
description: 'The path to the field containing the data to return',
18+
},
19+
ripcordPath: {
20+
required: false,
21+
type: 'string',
22+
description: 'The path to the ripcord field if expected',
23+
},
24+
ripcordDisabledValue: {
25+
default: 'false',
26+
type: 'string',
27+
description:
28+
'If the ripcord field has a different value than this, the adapter will return an error.',
29+
},
30+
},
31+
[
32+
{
33+
apiName: 'client-name',
34+
dataPath: 'PoR',
35+
ripcordPath: 'ripcord',
36+
ripcordDisabledValue: 'false',
37+
},
38+
],
39+
)
40+
41+
export type BaseEndpointTypes = {
42+
Parameters: typeof inputParameters.definition
43+
Response: {
44+
Result: string
45+
Data: {
46+
result: string
47+
}
48+
}
49+
Settings: typeof config.settings
50+
}
51+
52+
export const endpoint = new AdapterEndpoint({
53+
name: 'http',
54+
aliases: [],
55+
transport: httpTransport,
56+
inputParameters,
57+
customInputValidation: (request): AdapterError | undefined => {
58+
getApiConfig(request.requestContext.data.apiName)
59+
return
60+
},
61+
})
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
export { endpoint as http } from './http'
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
import { expose, ServerInstance } from '@chainlink/external-adapter-framework'
2+
import { Adapter } from '@chainlink/external-adapter-framework/adapter'
3+
import { config } from './config'
4+
import { http } from './endpoint'
5+
6+
export const adapter = new Adapter({
7+
defaultEndpoint: http.name,
8+
name: 'GENERIC_API',
9+
config,
10+
endpoints: [http],
11+
rateLimiting: {
12+
tiers: {
13+
default: {
14+
rateLimit1m: 20,
15+
},
16+
},
17+
},
18+
})
19+
20+
export const server = (): Promise<ServerInstance | undefined> => expose(adapter)

0 commit comments

Comments
 (0)