Skip to content

Commit 72382d5

Browse files
authored
Hashnote EA (#3913)
* Result of yarn new * implementation * changeset
1 parent 734d52b commit 72382d5

File tree

18 files changed

+542
-0
lines changed

18 files changed

+542
-0
lines changed

.changeset/early-gifts-appear.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
'@chainlink/hashnote-adapter': major
3+
---
4+
5+
Hashnote EA initial release

.pnp.cjs

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

packages/sources/hashnote/CHANGELOG.md

Whitespace-only changes.

packages/sources/hashnote/README.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
# Chainlink External Adapter for hashnote
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 hashnote`.
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
{
2+
"name": "@chainlink/hashnote-adapter",
3+
"version": "0.0.0",
4+
"description": "Chainlink hashnote adapter.",
5+
"keywords": [
6+
"Chainlink",
7+
"LINK",
8+
"blockchain",
9+
"oracle",
10+
"hashnote"
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+
"nock": "13.5.6",
34+
"typescript": "5.8.3"
35+
},
36+
"dependencies": {
37+
"@chainlink/external-adapter-framework": "2.6.0",
38+
"axios": "1.9.0",
39+
"tslib": "2.4.1"
40+
}
41+
}
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
import { AdapterConfig } from '@chainlink/external-adapter-framework/config'
2+
3+
export const config = new AdapterConfig({
4+
USYC_API_ENDPOINT: {
5+
description: 'URL for the USYC price report API',
6+
type: 'string',
7+
default: 'https://usyc.hashnote.com/api/price-reports',
8+
},
9+
})
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
export { endpoint as price } from './price'
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
import { AdapterEndpoint } from '@chainlink/external-adapter-framework/adapter'
2+
import { InputParameters } from '@chainlink/external-adapter-framework/validation'
3+
import { config } from '../config'
4+
import { httpTransport } from '../transport/price'
5+
6+
export const inputParameters = new InputParameters(
7+
{
8+
token: {
9+
type: 'string',
10+
default: 'USYC',
11+
description: 'The token to get the price report for. Currently only USYC is supported.',
12+
},
13+
},
14+
[
15+
{
16+
token: 'USYC',
17+
},
18+
],
19+
)
20+
21+
export type BaseEndpointTypes = {
22+
Parameters: typeof inputParameters.definition
23+
Response: {
24+
Result: string
25+
Data: {
26+
result: string
27+
}
28+
}
29+
Settings: typeof config.settings
30+
}
31+
32+
export const endpoint = new AdapterEndpoint({
33+
name: 'price',
34+
aliases: [],
35+
transport: httpTransport,
36+
inputParameters,
37+
})
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -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 { price } from './endpoint'
5+
6+
export const adapter = new Adapter({
7+
defaultEndpoint: price.name,
8+
name: 'HASHNOTE',
9+
config,
10+
endpoints: [price],
11+
rateLimiting: {
12+
tiers: {
13+
default: {
14+
rateLimit1m: 1,
15+
note: 'API only updates once per day',
16+
},
17+
},
18+
},
19+
})
20+
21+
export const server = (): Promise<ServerInstance | undefined> => expose(adapter)
Lines changed: 105 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,105 @@
1+
import { HttpTransport } from '@chainlink/external-adapter-framework/transports'
2+
import { AxiosResponse } from 'axios'
3+
import { BaseEndpointTypes, inputParameters } from '../endpoint/price'
4+
5+
export interface PriceReport {
6+
roundId: string
7+
principal: string
8+
interest: string
9+
balance: string
10+
price: string
11+
nextPrice: string
12+
totalSupply: string
13+
decimals: number
14+
fee: string
15+
timestamp: string
16+
txhash: string
17+
}
18+
19+
export interface ResponseSchema {
20+
entity: string
21+
data: PriceReport[]
22+
}
23+
24+
export type HttpTransportTypes = BaseEndpointTypes & {
25+
Provider: {
26+
RequestBody: never
27+
ResponseBody: ResponseSchema
28+
}
29+
}
30+
31+
export type RequestParams = typeof inputParameters.validated
32+
33+
export type Config = BaseEndpointTypes['Settings']
34+
35+
const getApiUrl = (param: RequestParams, config: Config) => {
36+
const token = param.token.toLowerCase()
37+
switch (token) {
38+
case 'usyc':
39+
return config.USYC_API_ENDPOINT
40+
default:
41+
throw new Error(`Unsupported token: ${token}`)
42+
}
43+
}
44+
45+
const getNewestPriceReport = (reports: PriceReport[]): PriceReport => {
46+
if (reports.length === 0) {
47+
throw new Error('No price reports available')
48+
}
49+
let newestReport = reports[0]
50+
let newestTimestamp = Number(newestReport.timestamp)
51+
for (const report of reports) {
52+
const timestamp = Number(report.timestamp)
53+
if (timestamp > newestTimestamp) {
54+
newestReport = report
55+
newestTimestamp = timestamp
56+
}
57+
}
58+
return newestReport
59+
}
60+
61+
// Exported for testing
62+
export const prepareRequests = (params: RequestParams[], config: Config) => {
63+
return params.map((param) => {
64+
const baseURL = getApiUrl(param, config)
65+
return {
66+
params: [param],
67+
request: {
68+
baseURL,
69+
},
70+
}
71+
})
72+
}
73+
74+
// Exported for testing
75+
export const parseResponse = (params: RequestParams[], response: AxiosResponse<ResponseSchema>) => {
76+
if (!response.data) {
77+
return params.map((param) => {
78+
return {
79+
params: param,
80+
response: {
81+
errorMessage: `The data provider didn't return any value for ${param.token}`,
82+
statusCode: 502,
83+
},
84+
}
85+
})
86+
}
87+
88+
return params.map((param) => {
89+
const result = getNewestPriceReport(response.data.data).price
90+
return {
91+
params: param,
92+
response: {
93+
result,
94+
data: {
95+
result,
96+
},
97+
},
98+
}
99+
})
100+
}
101+
102+
export const httpTransport = new HttpTransport<HttpTransportTypes>({
103+
prepareRequests,
104+
parseResponse,
105+
})

0 commit comments

Comments
 (0)