Skip to content

Commit 7f06404

Browse files
authored
Added Harris and Trotter adapter (#3036)
1 parent 8cdc5fc commit 7f06404

21 files changed

+464
-0
lines changed

.changeset/eleven-bags-protect.md

+5
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
'@chainlink/harris-and-trotter-adapter': major
3+
---
4+
5+
Initial version of the adapter

.pnp.cjs

+20
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

packages/sources/harris-and-trotter/CHANGELOG.md

Whitespace-only changes.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
# Chainlink External Adapter for harris-and-trotter
2+
3+
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 harris-and-trotter`.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
{
2+
"name": "@chainlink/harris-and-trotter-adapter",
3+
"version": "0.0.0",
4+
"description": "Chainlink harris-and-trotter adapter.",
5+
"keywords": [
6+
"Chainlink",
7+
"LINK",
8+
"blockchain",
9+
"oracle",
10+
"harris-and-trotter"
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": "27.5.2",
32+
"@types/node": "16.11.51",
33+
"nock": "13.2.9",
34+
"typescript": "5.0.4"
35+
},
36+
"dependencies": {
37+
"@chainlink/external-adapter-framework": "0.33.0",
38+
"tslib": "2.4.1"
39+
}
40+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
import { AdapterConfig } from '@chainlink/external-adapter-framework/config'
2+
3+
export const config = new AdapterConfig({
4+
API_KEY: {
5+
description: 'An API key for Data Provider',
6+
type: 'string',
7+
required: true,
8+
sensitive: true,
9+
},
10+
API_ENDPOINT: {
11+
description: 'An API endpoint for Data Provider',
12+
type: 'string',
13+
default: 'https://api.harrisandtrotter.co.uk/api',
14+
},
15+
})
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
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 { httpTransport } from '../transport/balance'
6+
7+
export const inputParameters = new InputParameters(
8+
{
9+
clientName: {
10+
required: true,
11+
type: 'string',
12+
description: 'The name of the client to retrieve balances for.',
13+
},
14+
},
15+
[
16+
{
17+
clientName: 'TUSD',
18+
},
19+
],
20+
)
21+
22+
export type BaseEndpointTypes = {
23+
Parameters: typeof inputParameters.definition
24+
Response: SingleNumberResultResponse
25+
Settings: typeof config.settings
26+
}
27+
28+
export const endpoint = new AdapterEndpoint({
29+
name: 'balance',
30+
transport: httpTransport,
31+
inputParameters,
32+
})
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
export { endpoint as balance } from './balance'
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 { balance } from './endpoint'
5+
6+
export const adapter = new Adapter({
7+
defaultEndpoint: balance.name,
8+
name: 'HARRIS-AND-TROTTER',
9+
config,
10+
endpoints: [balance],
11+
rateLimiting: {
12+
tiers: {
13+
default: {
14+
rateLimit1m: 6,
15+
},
16+
},
17+
},
18+
})
19+
20+
export const server = (): Promise<ServerInstance | undefined> => expose(adapter)
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,93 @@
1+
import { HttpTransport } from '@chainlink/external-adapter-framework/transports'
2+
import { BaseEndpointTypes } from '../endpoint/balance'
3+
4+
export interface ResponseSchema {
5+
accountName: string
6+
totalReserve: number
7+
totalToken: number
8+
timestamp: string
9+
ripcord: boolean
10+
ripcordDetails: {
11+
insufficient_balance: boolean
12+
source_failure: boolean
13+
external_intervention: boolean
14+
}
15+
}
16+
17+
export type HttpTransportTypes = BaseEndpointTypes & {
18+
Provider: {
19+
RequestBody: never
20+
ResponseBody: ResponseSchema
21+
}
22+
}
23+
export const httpTransport = new HttpTransport<HttpTransportTypes>({
24+
prepareRequests: (params, config) => {
25+
return params.map((param) => {
26+
return {
27+
params: [param],
28+
request: {
29+
baseURL: config.API_ENDPOINT,
30+
url: '/balances',
31+
headers: {
32+
'x-functions-key': config.API_KEY,
33+
},
34+
params: {
35+
client_name: param.clientName,
36+
},
37+
},
38+
}
39+
})
40+
},
41+
parseResponse: (params, response) => {
42+
// Return error if ripcord indicator true
43+
if (response.data.ripcord) {
44+
const message = `Ripcord indicator true. Details: ${JSON.stringify(
45+
response.data.ripcordDetails,
46+
)}`
47+
return [
48+
{
49+
params: params[0],
50+
response: {
51+
errorMessage: message,
52+
statusCode: 502,
53+
timestamps: {
54+
providerIndicatedTimeUnixMs: new Date(response.data.timestamp).getTime(),
55+
},
56+
},
57+
},
58+
]
59+
}
60+
61+
const result = response.data.totalReserve
62+
63+
if (typeof result === 'undefined') {
64+
return [
65+
{
66+
params: params[0],
67+
response: {
68+
errorMessage: `Response missing totalReserve`,
69+
statusCode: 502,
70+
timestamps: {
71+
providerIndicatedTimeUnixMs: new Date(response.data.timestamp).getTime(),
72+
},
73+
},
74+
},
75+
]
76+
}
77+
78+
return [
79+
{
80+
params: params[0],
81+
response: {
82+
result,
83+
data: {
84+
result,
85+
},
86+
timestamps: {
87+
providerIndicatedTimeUnixMs: new Date(response.data.timestamp).getTime(),
88+
},
89+
},
90+
},
91+
]
92+
},
93+
})
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
{
2+
"requests": [
3+
{
4+
"clientName": "TUSD",
5+
"endpoint": "balance"
6+
}
7+
]
8+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
// Jest Snapshot v1, https://goo.gl/fbAQLP
2+
3+
exports[`execute balance endpoint should return success 1`] = `
4+
{
5+
"data": {
6+
"result": 999999.99,
7+
},
8+
"result": 999999.99,
9+
"statusCode": 200,
10+
"timestamps": {
11+
"providerDataReceivedUnixMs": 978347471111,
12+
"providerDataRequestedUnixMs": 978347471111,
13+
"providerIndicatedTimeUnixMs": 1696699992777,
14+
},
15+
}
16+
`;
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
// Jest Snapshot v1, https://goo.gl/fbAQLP
2+
3+
exports[`execute balance endpoint should return failure on ripcord true 1`] = `
4+
{
5+
"errorMessage": "Ripcord indicator true. Details: {"insufficient_balance":true,"source_failure":false,"external_intervention":false}",
6+
"statusCode": 502,
7+
"timestamps": {
8+
"providerDataReceivedUnixMs": 978347471111,
9+
"providerDataRequestedUnixMs": 978347471111,
10+
"providerIndicatedTimeUnixMs": 1696699992777,
11+
},
12+
}
13+
`;
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
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+
describe('balance endpoint', () => {
35+
it('should return success', async () => {
36+
const data = {
37+
clientName: 'TUSD',
38+
endpoint: 'balance',
39+
}
40+
mockResponseSuccess()
41+
const response = await testAdapter.request(data)
42+
expect(response.statusCode).toBe(200)
43+
expect(response.json()).toMatchSnapshot()
44+
})
45+
})
46+
})

0 commit comments

Comments
 (0)