@chainlink/external-adapter-framework 0.30.1 → 0.30.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -1
- package/adapter/basic.d.ts +2 -0
- package/adapter/basic.js +16 -1
- package/adapter/basic.js.map +1 -1
- package/adapter/price.js +2 -1
- package/adapter/price.js.map +1 -1
- package/adapter-generator.js +9 -0
- package/cache/redis.js +4 -0
- package/cache/redis.js.map +1 -1
- package/config/index.d.ts +5 -0
- package/config/index.js +5 -0
- package/config/index.js.map +1 -1
- package/generator-adapter/generators/app/index.js +337 -0
- package/generator-adapter/generators/app/templates/CHANGELOG.md +0 -0
- package/generator-adapter/generators/app/templates/README.md +3 -0
- package/generator-adapter/generators/app/templates/babel.config.js +3 -0
- package/generator-adapter/generators/app/templates/jest.config.js +3 -0
- package/generator-adapter/generators/app/templates/package.json +30 -0
- package/generator-adapter/generators/app/templates/src/config/index.ts +25 -0
- package/generator-adapter/generators/app/templates/src/config/overrides.json +3 -0
- package/generator-adapter/generators/app/templates/src/endpoint/base.ts.ejs +21 -0
- package/generator-adapter/generators/app/templates/src/endpoint/endpoint-router.ts.ejs +33 -0
- package/generator-adapter/generators/app/templates/src/endpoint/endpoint.ts.ejs +26 -0
- package/generator-adapter/generators/app/templates/src/endpoint/index.ts.ejs +1 -0
- package/generator-adapter/generators/app/templates/src/index.ts.ejs +21 -0
- package/generator-adapter/generators/app/templates/src/transport/custom.ts.ejs +87 -0
- package/generator-adapter/generators/app/templates/src/transport/http.ts.ejs +98 -0
- package/generator-adapter/generators/app/templates/src/transport/ws.ts.ejs +94 -0
- package/generator-adapter/generators/app/templates/test/adapter-ws.test.ts.ejs +58 -0
- package/generator-adapter/generators/app/templates/test/adapter.test.ts.ejs +50 -0
- package/generator-adapter/generators/app/templates/test/fixtures.ts.ejs +44 -0
- package/generator-adapter/generators/app/templates/test-payload.json +6 -0
- package/generator-adapter/generators/app/templates/tsconfig.base.json +40 -0
- package/generator-adapter/generators/app/templates/tsconfig.json +9 -0
- package/generator-adapter/generators/app/templates/tsconfig.test.json +7 -0
- package/generator-adapter/package.json +12 -0
- package/index.js +16 -0
- package/index.js.map +1 -1
- package/package.json +10 -4
- package/util/testing-utils.d.ts +1 -0
- package/util/testing-utils.js +5 -0
- package/util/testing-utils.js.map +1 -1
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@chainlink/<%= adapterName %>-adapter",
|
|
3
|
+
"version": "0.0.0",
|
|
4
|
+
"description": "Chainlink <%= adapterName %> adapter.",
|
|
5
|
+
"keywords": [
|
|
6
|
+
"Chainlink",
|
|
7
|
+
"LINK",
|
|
8
|
+
"blockchain",
|
|
9
|
+
"oracle",
|
|
10
|
+
"<%= adapterName %>"
|
|
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
|
+
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { AdapterConfig } from '@chainlink/external-adapter-framework/config'
|
|
2
|
+
|
|
3
|
+
export const config = new AdapterConfig(
|
|
4
|
+
{
|
|
5
|
+
API_KEY: {
|
|
6
|
+
description:
|
|
7
|
+
'An API key for Data Provider',
|
|
8
|
+
type: 'string',
|
|
9
|
+
required: true,
|
|
10
|
+
sensitive: true,
|
|
11
|
+
},
|
|
12
|
+
API_ENDPOINT: {
|
|
13
|
+
description:
|
|
14
|
+
'An API endpoint for Data Provider',
|
|
15
|
+
type: 'string',
|
|
16
|
+
default: 'https://dataproviderapi.com',
|
|
17
|
+
},
|
|
18
|
+
WS_API_ENDPOINT: {
|
|
19
|
+
description:
|
|
20
|
+
'WS endpoint for Data Provider',
|
|
21
|
+
type: 'string',
|
|
22
|
+
default: 'ws://localhost:9090',
|
|
23
|
+
},
|
|
24
|
+
},
|
|
25
|
+
)
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
<% if (includeComments) { %>// Input parameters define the structure of the request expected by the endpoint.<% } %>
|
|
2
|
+
export const inputParameters = new InputParameters({
|
|
3
|
+
base: {
|
|
4
|
+
aliases: ['from', 'coin', 'symbol', 'market'],
|
|
5
|
+
required: true,
|
|
6
|
+
type: 'string',
|
|
7
|
+
description: 'The symbol of symbols of the currency to query',
|
|
8
|
+
},
|
|
9
|
+
quote: {
|
|
10
|
+
aliases: ['to', 'convert'],
|
|
11
|
+
required: true,
|
|
12
|
+
type: 'string',
|
|
13
|
+
description: 'The symbol of the currency to convert to',
|
|
14
|
+
},
|
|
15
|
+
})
|
|
16
|
+
<% if (includeComments) { %>// Endpoints contain a type parameter that allows specifying relevant types of an endpoint, for example, request payload type, Adapter response type and Adapter configuration (environment variables) type<% } %>
|
|
17
|
+
export type BaseEndpointTypes = {
|
|
18
|
+
Parameters: typeof inputParameters.definition
|
|
19
|
+
Response: SingleNumberResultResponse
|
|
20
|
+
Settings: typeof config.settings
|
|
21
|
+
}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { AdapterEndpoint } from '@chainlink/external-adapter-framework/adapter'
|
|
2
|
+
import { InputParameters } from '@chainlink/external-adapter-framework/validation'
|
|
3
|
+
import { SingleNumberResultResponse } from '@chainlink/external-adapter-framework/util'
|
|
4
|
+
import { TransportRoutes } from '@chainlink/external-adapter-framework/transports'
|
|
5
|
+
import { config } from '../config'
|
|
6
|
+
import overrides from '../config/overrides.json'
|
|
7
|
+
<% for(let i=0; i<inputTransports.length; i++) {%>
|
|
8
|
+
import { <%= inputTransports[i].name %> } from '../transport/<%= inputEndpointName %>-<%= inputTransports[i].type %>' <% }
|
|
9
|
+
%>
|
|
10
|
+
|
|
11
|
+
<%- include ./base.ts.ejs %>
|
|
12
|
+
|
|
13
|
+
export const endpoint = new AdapterEndpoint({
|
|
14
|
+
<% if (includeComments) { -%>
|
|
15
|
+
// Endpoint name
|
|
16
|
+
<% } -%><%= ' ' %> name: '<%= inputEndpointName %>',
|
|
17
|
+
<% if (includeComments) { -%>
|
|
18
|
+
// Alternative endpoint names for this endpoint
|
|
19
|
+
<% } -%><%= ' ' %> aliases: <%- endpointAliases.length ? JSON.stringify(endpointAliases) : JSON.stringify([]) -%>,
|
|
20
|
+
<% if (includeComments) { -%>
|
|
21
|
+
// Transport handles incoming requests, data processing and communication for this endpoint.
|
|
22
|
+
// In case endpoint supports multiple transports (i.e. http and websocket) TransportRoutes is used to register all supported transports.
|
|
23
|
+
// To use specific transport, provide `transport: [transportName]` in the request
|
|
24
|
+
<% } -%><%= ' ' %> transportRoutes: new TransportRoutes<BaseEndpointTypes>()
|
|
25
|
+
<% for(let i=0; i<inputTransports.length; i++) {-%>
|
|
26
|
+
.register('<%- inputTransports[i].type === "http" ? `rest` : inputTransports[i].type %>', <%- inputTransports[i].name %>)<%}%>,
|
|
27
|
+
<% if (includeComments) { -%>
|
|
28
|
+
// Supported input parameters for this endpoint
|
|
29
|
+
<% } -%><%= ' ' %> inputParameters,
|
|
30
|
+
<% if (includeComments) { -%>
|
|
31
|
+
// Overrides are defined in the `/config/overrides.json` file. They allow input parameters to be overriden from a generic symbol to something more specific for the data provider such as an ID.
|
|
32
|
+
<% } -%><%= ' ' %> overrides: overrides['<%= adapterName %>']
|
|
33
|
+
})
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { AdapterEndpoint } from '@chainlink/external-adapter-framework/adapter'
|
|
2
|
+
import { InputParameters } from '@chainlink/external-adapter-framework/validation'
|
|
3
|
+
import { SingleNumberResultResponse } from '@chainlink/external-adapter-framework/util'
|
|
4
|
+
import { config } from '../config'
|
|
5
|
+
import overrides from '../config/overrides.json'
|
|
6
|
+
import { <%= inputTransports[0].name %> } from '../transport/<%= inputEndpointName %>'
|
|
7
|
+
|
|
8
|
+
<%- include ./base.ts.ejs %>
|
|
9
|
+
|
|
10
|
+
export const endpoint = new AdapterEndpoint({
|
|
11
|
+
<% if (includeComments) { -%>
|
|
12
|
+
// Endpoint name
|
|
13
|
+
<% } -%><%= ' ' %> name: '<%= inputEndpointName %>',
|
|
14
|
+
<% if (includeComments) { -%>
|
|
15
|
+
// Alternative endpoint names for this endpoint
|
|
16
|
+
<% } -%><%= ' ' %> aliases: <%- endpointAliases.length ? JSON.stringify(endpointAliases) : JSON.stringify([]) -%>,
|
|
17
|
+
<% if (includeComments) { -%>
|
|
18
|
+
// Transport handles incoming requests, data processing and communication for this endpoint
|
|
19
|
+
<% } -%><%= ' ' %> transport: <%= inputTransports[0].name %>,
|
|
20
|
+
<% if (includeComments) { -%>
|
|
21
|
+
// Supported input parameters for this endpoint
|
|
22
|
+
<% } -%><%= ' ' %> inputParameters,
|
|
23
|
+
<% if (includeComments) { -%>
|
|
24
|
+
// Overrides are defined in the `/config/overrides.json` file. They allow input parameters to be overriden from a generic symbol to something more specific for the data provider such as an ID.
|
|
25
|
+
<% } -%><%= ' ' %> overrides: overrides['<%= adapterName %>']
|
|
26
|
+
})
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
<% for(let i=0; i<endpoints.length; i++) {%>export { endpoint as <%- endpoints[i].normalizedEndpointName %> } from './<%= endpoints[i].inputEndpointName %>' <%- '\n' %><%}%>
|
|
@@ -0,0 +1,21 @@
|
|
|
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 { <%= endpointNames %> } from './endpoint'
|
|
5
|
+
|
|
6
|
+
export const adapter = new Adapter({
|
|
7
|
+
<% if (includeComments) { -%>
|
|
8
|
+
//Requests will direct to this endpoint if the `endpoint` input parameter is not specified.
|
|
9
|
+
<% } -%><%= ' ' %> defaultEndpoint: <%= defaultEndpoint.normalizedEndpointName %>.name,
|
|
10
|
+
<% if (includeComments) { -%>
|
|
11
|
+
// Adapter name
|
|
12
|
+
<% } -%><%= ' ' %> name: '<%= adapterName.toUpperCase() %>',
|
|
13
|
+
<% if (includeComments) { -%>
|
|
14
|
+
// Adapter configuration (environment variables)
|
|
15
|
+
<% } -%><%= ' ' %> config,
|
|
16
|
+
<% if (includeComments) { -%>
|
|
17
|
+
// List of supported endpoints
|
|
18
|
+
<% } -%><%= ' ' %> endpoints: [<%= endpointNames %>],
|
|
19
|
+
})
|
|
20
|
+
|
|
21
|
+
export const server = (): Promise<ServerInstance | undefined> => expose(adapter)
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
import { Transport, TransportDependencies } from '@chainlink/external-adapter-framework/transports'
|
|
2
|
+
import { ResponseCache } from '@chainlink/external-adapter-framework/cache/response'
|
|
3
|
+
import { Requester } from '@chainlink/external-adapter-framework/util/requester'
|
|
4
|
+
import {
|
|
5
|
+
AdapterRequest,
|
|
6
|
+
AdapterResponse,
|
|
7
|
+
} from '@chainlink/external-adapter-framework/util'
|
|
8
|
+
import { TypeFromDefinition } from '@chainlink/external-adapter-framework/validation/input-params'
|
|
9
|
+
import { BaseEndpointTypes } from '../endpoint/<%= inputEndpointName %>'
|
|
10
|
+
|
|
11
|
+
<% if (includeComments) { -%>
|
|
12
|
+
// CustomTransport extends base types from endpoint and adds additional, Provider-specific types (if needed).
|
|
13
|
+
<% } -%>
|
|
14
|
+
export type CustomTransportTypes = BaseEndpointTypes & {
|
|
15
|
+
Provider: {
|
|
16
|
+
RequestBody: never
|
|
17
|
+
ResponseBody: any
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
<% if (includeComments) { -%>
|
|
21
|
+
// CustomTransport is used to perform custom data fetching and processing from a Provider. The framework provides built-in transports to
|
|
22
|
+
// fetch data from a Provider using several protocols, including `http`, `websocket`, and `sse`. Use CustomTransport when the Provider uses
|
|
23
|
+
// different protocol, or you need custom functionality that built-in transports don't support. For example, custom, multistep authentication
|
|
24
|
+
// for requests, paginated requests, on-chain data retrieval using third party libraries, and so on.
|
|
25
|
+
<% } -%>
|
|
26
|
+
export class CustomTransport<T extends CustomTransportTypes> implements Transport<T> {
|
|
27
|
+
<% if (includeComments) { -%>
|
|
28
|
+
// name of the transport, used for logging
|
|
29
|
+
<% } -%>
|
|
30
|
+
name!: string
|
|
31
|
+
<% if (includeComments) { -%>
|
|
32
|
+
// cache instance for caching responses from provider
|
|
33
|
+
<% } -%>
|
|
34
|
+
responseCache!: ResponseCache<T>
|
|
35
|
+
<% if (includeComments) { -%>
|
|
36
|
+
// instance of Requester to be used for data fetching. Use this instance to perform http calls
|
|
37
|
+
<% } -%>
|
|
38
|
+
requester!: Requester
|
|
39
|
+
|
|
40
|
+
<% if (includeComments) { -%>
|
|
41
|
+
// REQUIRED. Transport will be automatically initialized by the framework using this method. It will be called with transport
|
|
42
|
+
// dependencies, adapter settings, endpoint name, and transport name as arguments. Use this method to initialize transport state
|
|
43
|
+
<% } -%>
|
|
44
|
+
async initialize(dependencies: TransportDependencies<T>, _adapterSettings: CustomTransportTypes['Settings'], _endpointName: string, transportName: string): Promise<void> {
|
|
45
|
+
this.responseCache = dependencies.responseCache
|
|
46
|
+
this.requester = dependencies.requester
|
|
47
|
+
this.name = transportName
|
|
48
|
+
}
|
|
49
|
+
<% if (includeComments) { -%>
|
|
50
|
+
// 'foregroundExecute' performs synchronous fetch/processing of information within the lifecycle of an incoming request. It takes
|
|
51
|
+
// request object (adapter request, which is wrapper around fastify request) and adapter settings. Use this method to handle the incoming
|
|
52
|
+
// request, process it,save it in the cache and return to user.
|
|
53
|
+
<% } -%>
|
|
54
|
+
async foregroundExecute(
|
|
55
|
+
req: AdapterRequest<TypeFromDefinition<T['Parameters']>>,
|
|
56
|
+
): Promise<AdapterResponse<CustomTransportTypes['Response']>> {
|
|
57
|
+
|
|
58
|
+
// Custom transport logic
|
|
59
|
+
|
|
60
|
+
const response = {
|
|
61
|
+
data: {
|
|
62
|
+
result: 100,
|
|
63
|
+
},
|
|
64
|
+
statusCode: 200,
|
|
65
|
+
result: 100,
|
|
66
|
+
timestamps: {
|
|
67
|
+
providerDataRequestedUnixMs: Date.now(),
|
|
68
|
+
providerDataReceivedUnixMs: Date.now(),
|
|
69
|
+
providerIndicatedTimeUnixMs: undefined,
|
|
70
|
+
},
|
|
71
|
+
}
|
|
72
|
+
<% if (includeComments) { -%>
|
|
73
|
+
// Once the response object is ready, write it to the cache. Use the transport name, request payload and constructed response. Once cache is
|
|
74
|
+
// saved, return the response to the user.
|
|
75
|
+
<% } -%>
|
|
76
|
+
await this.responseCache.write(this.name, [
|
|
77
|
+
{
|
|
78
|
+
params: req.requestContext.data,
|
|
79
|
+
response,
|
|
80
|
+
},
|
|
81
|
+
])
|
|
82
|
+
|
|
83
|
+
return response
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
export const customTransport = new CustomTransport()
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
import { HttpTransport } from '@chainlink/external-adapter-framework/transports'
|
|
2
|
+
import { BaseEndpointTypes } from '../endpoint/<%= inputEndpointName %>'
|
|
3
|
+
|
|
4
|
+
export interface ResponseSchema {
|
|
5
|
+
[key: string]: {
|
|
6
|
+
price: number
|
|
7
|
+
errorMessage?: string
|
|
8
|
+
}
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
<% if (includeComments) { -%>
|
|
12
|
+
// HttpTransport extends base types from endpoint and adds additional, Provider-specific types like 'RequestBody', which is the type of
|
|
13
|
+
// request body (not the request to adapter, but the request that adapter sends to Data Provider), and 'ResponseBody' which is
|
|
14
|
+
// the type of raw response from Data Provider
|
|
15
|
+
<% } -%>
|
|
16
|
+
export type HttpTransportTypes = BaseEndpointTypes & {
|
|
17
|
+
Provider: {
|
|
18
|
+
RequestBody: never
|
|
19
|
+
ResponseBody: ResponseSchema
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
<% if (includeComments) { -%>
|
|
23
|
+
// HttpTransport is used to fetch and process data from a Provider using HTTP(S) protocol. It usually needs two methods
|
|
24
|
+
// `prepareRequests` and `parseResponse`
|
|
25
|
+
<% } -%>
|
|
26
|
+
export const httpTransport = new HttpTransport<HttpTransportTypes>({
|
|
27
|
+
<% if (includeComments) { -%>
|
|
28
|
+
// `prepareRequests` method receives request payloads sent to associated endpoint alongside adapter config(environment variables)
|
|
29
|
+
// and should return 'request information' to the Data Provider. Use this method to construct one or many requests, and the framework
|
|
30
|
+
// will send them to Data Provider
|
|
31
|
+
<% } -%>
|
|
32
|
+
prepareRequests: (params, config) => {
|
|
33
|
+
return params.map((param) => {
|
|
34
|
+
return {
|
|
35
|
+
<% if (includeComments) { -%>
|
|
36
|
+
// `params` are parameters associated to this single request and will also be available in the 'parseResponse' method.
|
|
37
|
+
<% } -%>
|
|
38
|
+
params: [param],
|
|
39
|
+
<% if (includeComments) { -%>
|
|
40
|
+
// `request` contains any valid axios request configuration
|
|
41
|
+
<% } -%>
|
|
42
|
+
request: {
|
|
43
|
+
baseURL: config.API_ENDPOINT,
|
|
44
|
+
url: '/cryptocurrency/price',
|
|
45
|
+
headers: {
|
|
46
|
+
'X_API_KEY': config.API_KEY,
|
|
47
|
+
},
|
|
48
|
+
params: {
|
|
49
|
+
symbol: param.base.toUpperCase(),
|
|
50
|
+
convert: param.quote.toUpperCase(),
|
|
51
|
+
},
|
|
52
|
+
},
|
|
53
|
+
}
|
|
54
|
+
})
|
|
55
|
+
},
|
|
56
|
+
<% if (includeComments) { -%>
|
|
57
|
+
// `parseResponse` takes the 'params' specified in the `prepareRequests` and the 'response' from Data Provider and should return
|
|
58
|
+
// an array of response objects to be stored in cache. Use this method to construct a list of response objects for every parameter in 'params'
|
|
59
|
+
// and the framework will save them in cache and return to user
|
|
60
|
+
<% } -%>
|
|
61
|
+
parseResponse: (params, response) => {
|
|
62
|
+
<% if (includeComments) { -%>
|
|
63
|
+
// In case error was received, it's a good practice to return meaningful information to user
|
|
64
|
+
<% } -%>
|
|
65
|
+
if (!response.data) {
|
|
66
|
+
return params.map((param) => {
|
|
67
|
+
return {
|
|
68
|
+
params: param,
|
|
69
|
+
response: {
|
|
70
|
+
errorMessage: `The data provider didn't return any value for ${param.base}/${param.quote}`,
|
|
71
|
+
statusCode: 502,
|
|
72
|
+
},
|
|
73
|
+
}
|
|
74
|
+
})
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
<% if (includeComments) { -%>
|
|
78
|
+
// For successful responses for each 'param' a new response object is created and returned as an array
|
|
79
|
+
<% } -%>
|
|
80
|
+
return params.map((param) => {
|
|
81
|
+
const result = response.data[param.base.toUpperCase()].price
|
|
82
|
+
<% if (includeComments) { -%>
|
|
83
|
+
// Response objects, whether successful or errors, contain two properties, 'params' and 'response'. 'response' is what will be
|
|
84
|
+
// stored in the cache and returned as adapter response and 'params' determines the identifier so that the next request with same 'params'
|
|
85
|
+
// will immediately return the response from the cache
|
|
86
|
+
<% } -%>
|
|
87
|
+
return {
|
|
88
|
+
params: param,
|
|
89
|
+
response: {
|
|
90
|
+
result,
|
|
91
|
+
data: {
|
|
92
|
+
result
|
|
93
|
+
}
|
|
94
|
+
},
|
|
95
|
+
}
|
|
96
|
+
})
|
|
97
|
+
},
|
|
98
|
+
})
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
import { WebSocketTransport } from '@chainlink/external-adapter-framework/transports'
|
|
2
|
+
import { BaseEndpointTypes } from '../endpoint/<%= inputEndpointName %>'
|
|
3
|
+
|
|
4
|
+
export interface WSResponse {
|
|
5
|
+
success: boolean
|
|
6
|
+
price: number
|
|
7
|
+
base: string
|
|
8
|
+
quote: string
|
|
9
|
+
time: number
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
<% if (includeComments) { -%>
|
|
13
|
+
// WsTransport extends base types from endpoint and adds additional, Provider-specific types like 'WsMessage', which is the type of
|
|
14
|
+
// websocket received message
|
|
15
|
+
<% } -%>
|
|
16
|
+
export type WsTransportTypes = BaseEndpointTypes & {
|
|
17
|
+
Provider: {
|
|
18
|
+
WsMessage: WSResponse
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
<% if (includeComments) { -%>
|
|
22
|
+
// WebSocketTransport is used to fetch and process data from a Provider using Websocket protocol.
|
|
23
|
+
<% } -%>
|
|
24
|
+
export const wsTransport = new WebSocketTransport<WsTransportTypes>({
|
|
25
|
+
<% if (includeComments) { -%>
|
|
26
|
+
// use `url` method to provide connection url. It accepts adapter context, so you have access to adapter config(environment variables) and
|
|
27
|
+
// request payload if needed
|
|
28
|
+
<% } -%>
|
|
29
|
+
url: (context) => context.adapterSettings.WS_API_ENDPOINT,
|
|
30
|
+
<% if (includeComments) { -%>
|
|
31
|
+
// 'handler' contains two helpful methods. one of them is `message`. This method is called when there is a new websocket message.
|
|
32
|
+
// The other one is 'open' method. It is called when the websocket connection is successfully opened. Use this method to execute some logic
|
|
33
|
+
// when the connection is established (custom authentication, logging, ...)
|
|
34
|
+
<% } -%>
|
|
35
|
+
handlers: {
|
|
36
|
+
<% if (includeComments) { -%>
|
|
37
|
+
// 'message' handler receives a raw websocket message as first argument and adapter context as second and should return an array of
|
|
38
|
+
// response objects. Use this method to construct a list of response objects, and the framework will save them in cache and return to user
|
|
39
|
+
<% } -%>
|
|
40
|
+
message(message) {
|
|
41
|
+
<% if (includeComments) { -%>
|
|
42
|
+
// in cases when error or unknown message is received, use 'return' to skip the iteration.
|
|
43
|
+
<% } -%>
|
|
44
|
+
if (message.success === false) {
|
|
45
|
+
return
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
<% if (includeComments) { -%>
|
|
49
|
+
// Response objects, whether successful or errors (if not skipped), contain two properties, 'params' and 'response'. 'response' is what
|
|
50
|
+
// will be stored in the cache and returned as adapter response and 'params' determines the identifier so that the next request with
|
|
51
|
+
// same 'params' will immediately return the response from the cache
|
|
52
|
+
<% } -%>
|
|
53
|
+
return [
|
|
54
|
+
{
|
|
55
|
+
params: { base: message.base, quote: message.quote },
|
|
56
|
+
response: {
|
|
57
|
+
result: message.price,
|
|
58
|
+
data: {
|
|
59
|
+
result: message.price
|
|
60
|
+
},
|
|
61
|
+
timestamps: {
|
|
62
|
+
providerIndicatedTimeUnixMs: message.time,
|
|
63
|
+
},
|
|
64
|
+
},
|
|
65
|
+
},
|
|
66
|
+
]
|
|
67
|
+
},
|
|
68
|
+
},
|
|
69
|
+
<% if (includeComments) { -%>
|
|
70
|
+
// `builders` are builder methods, that will be used to prepare specific WS messages to be sent to Data Provider
|
|
71
|
+
<% } -%>
|
|
72
|
+
builders: {
|
|
73
|
+
<% if (includeComments) { -%>
|
|
74
|
+
// `subscribeMessage` accepts request parameters and should construct and return a payload that will be sent to Data Provider
|
|
75
|
+
// Use this method to subscribe to live feeds
|
|
76
|
+
<% } -%>
|
|
77
|
+
subscribeMessage: (params) => {
|
|
78
|
+
return {
|
|
79
|
+
type: 'subscribe',
|
|
80
|
+
symbols: `${params.base}/${params.quote}`.toUpperCase()
|
|
81
|
+
}
|
|
82
|
+
},
|
|
83
|
+
<% if (includeComments) { -%>
|
|
84
|
+
// `unsubscribeMessage` accepts request parameters and should construct and return a payload that will be sent to Data Provider
|
|
85
|
+
// Use this method to unsubscribe from live feeds
|
|
86
|
+
<% } -%>
|
|
87
|
+
unsubscribeMessage: (params) => {
|
|
88
|
+
return {
|
|
89
|
+
type: 'unsubscribe',
|
|
90
|
+
symbols: `${params.base}/${params.quote}`.toUpperCase()
|
|
91
|
+
}
|
|
92
|
+
},
|
|
93
|
+
},
|
|
94
|
+
})
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import { WebSocketClassProvider } from '@chainlink/external-adapter-framework/transports'
|
|
2
|
+
import {
|
|
3
|
+
TestAdapter,
|
|
4
|
+
setEnvVariables,
|
|
5
|
+
mockWebSocketProvider,
|
|
6
|
+
MockWebsocketServer,
|
|
7
|
+
} from '@chainlink/external-adapter-framework/util/testing-utils'
|
|
8
|
+
import FakeTimers from '@sinonjs/fake-timers'
|
|
9
|
+
import { mockWebsocketServer } from './fixtures'
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
describe('websocket', () => {
|
|
13
|
+
let mockWsServer: MockWebsocketServer | undefined
|
|
14
|
+
let testAdapter: TestAdapter
|
|
15
|
+
const wsEndpoint = 'ws://localhost:9090'
|
|
16
|
+
let oldEnv: NodeJS.ProcessEnv
|
|
17
|
+
<% for(let i=0; i<endpoints.length; i++) {%>
|
|
18
|
+
const data<%- endpoints[i].normalizedEndpointNameCap %> = {
|
|
19
|
+
base: 'ETH',
|
|
20
|
+
quote: 'USD',
|
|
21
|
+
endpoint: '<%- endpoints[i].inputEndpointName %>',
|
|
22
|
+
transport: 'ws'
|
|
23
|
+
}
|
|
24
|
+
<% } %>
|
|
25
|
+
beforeAll(async () => {
|
|
26
|
+
oldEnv = JSON.parse(JSON.stringify(process.env))
|
|
27
|
+
process.env['WS_API_ENDPOINT'] = wsEndpoint
|
|
28
|
+
process.env['API_KEY'] = 'fake-api-key'
|
|
29
|
+
mockWebSocketProvider(WebSocketClassProvider)
|
|
30
|
+
mockWsServer = mockWebsocketServer(wsEndpoint)
|
|
31
|
+
|
|
32
|
+
const adapter = (await import('./../../src')).adapter
|
|
33
|
+
testAdapter = await TestAdapter.startWithMockedCache(adapter, {
|
|
34
|
+
clock: FakeTimers.install(),
|
|
35
|
+
testAdapter: {} as TestAdapter<never>,
|
|
36
|
+
})
|
|
37
|
+
|
|
38
|
+
// Send initial request to start background execute and wait for cache to be filled with results
|
|
39
|
+
<% for(var i=0; i<endpoints.length; i++) {%>
|
|
40
|
+
await testAdapter.request(data<%- endpoints[i].normalizedEndpointNameCap %>) <% } %>
|
|
41
|
+
await testAdapter.waitForCache(<%- endpoints.length %>)
|
|
42
|
+
})
|
|
43
|
+
|
|
44
|
+
afterAll(async () => {
|
|
45
|
+
setEnvVariables(oldEnv)
|
|
46
|
+
mockWsServer?.close()
|
|
47
|
+
testAdapter.clock?.uninstall()
|
|
48
|
+
await testAdapter.api.close()
|
|
49
|
+
})
|
|
50
|
+
<% for(var i=0; i<endpoints.length; i++) {%>
|
|
51
|
+
describe('<%= endpoints[i].inputEndpointName %> endpoint', () => {
|
|
52
|
+
it('should return success', async () => {
|
|
53
|
+
const response = await testAdapter.request(data<%- endpoints[i].normalizedEndpointNameCap %>)
|
|
54
|
+
expect(response.json()).toMatchSnapshot()
|
|
55
|
+
})
|
|
56
|
+
})
|
|
57
|
+
<% } %>
|
|
58
|
+
})
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import {
|
|
2
|
+
TestAdapter,
|
|
3
|
+
setEnvVariables,
|
|
4
|
+
} from '@chainlink/external-adapter-framework/util/testing-utils'
|
|
5
|
+
import * as nock from 'nock'
|
|
6
|
+
import { mockResponseSuccess } from './fixtures'
|
|
7
|
+
|
|
8
|
+
describe('execute', () => {
|
|
9
|
+
let spy: jest.SpyInstance
|
|
10
|
+
let testAdapter: TestAdapter
|
|
11
|
+
let oldEnv: NodeJS.ProcessEnv
|
|
12
|
+
|
|
13
|
+
beforeAll(async () => {
|
|
14
|
+
oldEnv = JSON.parse(JSON.stringify(process.env))
|
|
15
|
+
process.env.API_KEY = process.env.API_KEY ?? 'fake-api-key'
|
|
16
|
+
const mockDate = new Date('2001-01-01T11:11:11.111Z')
|
|
17
|
+
spy = jest.spyOn(Date, 'now').mockReturnValue(mockDate.getTime())
|
|
18
|
+
|
|
19
|
+
const adapter = (await import('./../../src')).adapter
|
|
20
|
+
adapter.rateLimiting = undefined
|
|
21
|
+
testAdapter = await TestAdapter.startWithMockedCache(adapter, {
|
|
22
|
+
testAdapter: {} as TestAdapter<never>,
|
|
23
|
+
})
|
|
24
|
+
})
|
|
25
|
+
|
|
26
|
+
afterAll(async () => {
|
|
27
|
+
setEnvVariables(oldEnv)
|
|
28
|
+
await testAdapter.api.close()
|
|
29
|
+
nock.restore()
|
|
30
|
+
nock.cleanAll()
|
|
31
|
+
spy.mockRestore()
|
|
32
|
+
})
|
|
33
|
+
|
|
34
|
+
<% for(var i=0; i<endpoints.length; i++) {%>
|
|
35
|
+
describe('<%= endpoints[i].inputEndpointName %> endpoint', () => {
|
|
36
|
+
it('should return success', async () => {
|
|
37
|
+
const data = {
|
|
38
|
+
base: 'ETH',
|
|
39
|
+
quote: 'USD',
|
|
40
|
+
endpoint: '<%= endpoints[i].inputEndpointName %>',
|
|
41
|
+
transport: '<%= transportName %>'
|
|
42
|
+
}
|
|
43
|
+
mockResponseSuccess()
|
|
44
|
+
const response = await testAdapter.request(data)
|
|
45
|
+
expect(response.statusCode).toBe(200)
|
|
46
|
+
expect(response.json()).toMatchSnapshot()
|
|
47
|
+
})
|
|
48
|
+
})
|
|
49
|
+
<% } %>
|
|
50
|
+
})
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
<% if (includeHttpFixtures) { %>import nock from 'nock'<% } %>
|
|
2
|
+
<% if (includeWsFixtures) { %>import { MockWebsocketServer } from '@chainlink/external-adapter-framework/util/testing-utils'<% } %>
|
|
3
|
+
<% if (includeHttpFixtures) { %>
|
|
4
|
+
export const mockResponseSuccess = (): nock.Scope =>
|
|
5
|
+
nock('https://dataproviderapi.com', {
|
|
6
|
+
encodedQueryParams: true,
|
|
7
|
+
})
|
|
8
|
+
.get('/cryptocurrency/price')
|
|
9
|
+
.query({
|
|
10
|
+
symbol: 'ETH',
|
|
11
|
+
convert: 'USD',
|
|
12
|
+
})
|
|
13
|
+
.reply(200, () => ({ ETH: { price: 10000 } }), [
|
|
14
|
+
'Content-Type',
|
|
15
|
+
'application/json',
|
|
16
|
+
'Connection',
|
|
17
|
+
'close',
|
|
18
|
+
'Vary',
|
|
19
|
+
'Accept-Encoding',
|
|
20
|
+
'Vary',
|
|
21
|
+
'Origin',
|
|
22
|
+
])
|
|
23
|
+
.persist()
|
|
24
|
+
<% } %>
|
|
25
|
+
<% if (includeWsFixtures) { %>
|
|
26
|
+
export const mockWebsocketServer = (URL: string): MockWebsocketServer => {
|
|
27
|
+
const mockWsServer = new MockWebsocketServer(URL, { mock: false })
|
|
28
|
+
mockWsServer.on('connection', (socket) => {
|
|
29
|
+
socket.on('message', (message) => {
|
|
30
|
+
return socket.send(
|
|
31
|
+
JSON.stringify({
|
|
32
|
+
success: true,
|
|
33
|
+
price: 1000,
|
|
34
|
+
base: 'ETH',
|
|
35
|
+
quote: 'USD',
|
|
36
|
+
time: '1999999'
|
|
37
|
+
}),
|
|
38
|
+
)
|
|
39
|
+
})
|
|
40
|
+
})
|
|
41
|
+
|
|
42
|
+
return mockWsServer
|
|
43
|
+
}
|
|
44
|
+
<% } %>
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
{
|
|
2
|
+
"compilerOptions": {
|
|
3
|
+
/* Basic Options */
|
|
4
|
+
"incremental": true /* Enable incremental compilation */,
|
|
5
|
+
"target": "ES2020" /* Specify ECMAScript target version: 'ES3' (default), 'ES5', 'ES2015', 'ES2016', 'ES2017', 'ES2018', 'ES2019' or 'ESNEXT'. */,
|
|
6
|
+
"module": "commonjs" /* Specify module code generation: 'none', 'commonjs', 'amd', 'system', 'umd', 'es2015', or 'ESNext'. */,
|
|
7
|
+
"composite": true /* Enable project compilation */,
|
|
8
|
+
"declaration": true /* Generates corresponding '.d.ts' file. */,
|
|
9
|
+
"declarationMap": true /* Generates a sourcemap for each corresponding '.d.ts' file. */,
|
|
10
|
+
"noEmit": false /* Do not emit outputs. */,
|
|
11
|
+
"noErrorTruncation": true /* Do not truncate error messages */,
|
|
12
|
+
"skipLibCheck": true /* Skip type checking of declaration files. Requires TypeScript version 2.0 or later. */,
|
|
13
|
+
"importHelpers": true /* Import emit helpers from 'tslib'. */,
|
|
14
|
+
|
|
15
|
+
/* Strict Type-Checking Options */
|
|
16
|
+
"strict": true /* Enable all strict type-checking options. */,
|
|
17
|
+
|
|
18
|
+
/* Additional Checks */
|
|
19
|
+
"noUnusedLocals": true /* Report errors on unused locals. */,
|
|
20
|
+
"noUnusedParameters": true /* Report errors on unused parameters. */,
|
|
21
|
+
"noImplicitReturns": true /* Report error when not all code paths in function return a value. */,
|
|
22
|
+
"noFallthroughCasesInSwitch": true /* Report errors for fallthrough cases in switch statement. */,
|
|
23
|
+
"forceConsistentCasingInFileNames": true /* Disallow inconsistently-cased references to the same file. */,
|
|
24
|
+
|
|
25
|
+
/* Module Resolution Options */
|
|
26
|
+
"moduleResolution": "node" /* Specify module resolution strategy: 'node' (Node.js) or 'classic' (TypeScript pre-1.6). */,
|
|
27
|
+
"allowSyntheticDefaultImports": true /* Allow default imports from modules with no default export. This does not affect code emit, just typechecking. */,
|
|
28
|
+
"esModuleInterop": true /* Enables emit interoperability between CommonJS and ES Modules via creation of namespace objects for all imports. Implies 'allowSyntheticDefaultImports'. */,
|
|
29
|
+
|
|
30
|
+
/* Source Map Options */
|
|
31
|
+
"inlineSourceMap": true /* Emit a single file with source maps instead of having a separate file. */,
|
|
32
|
+
"inlineSources": true /* Emit the source alongside the sourcemaps within a single file; requires '--inlineSourceMap' or '--sourceMap' to be set. */,
|
|
33
|
+
|
|
34
|
+
/* Experimental Options */
|
|
35
|
+
"experimentalDecorators": true /* Enables experimental support for ES7 decorators. */,
|
|
36
|
+
"emitDecoratorMetadata": true /* Enables experimental support for emitting type metadata for decorators. */,
|
|
37
|
+
|
|
38
|
+
"resolveJsonModule": true /* Allows importing modules with a ‘.json’ extension */
|
|
39
|
+
}
|
|
40
|
+
}
|