Skip to content

Commit

Permalink
Create Tiingo state adapter (#3466)
Browse files Browse the repository at this point in the history
  • Loading branch information
imollov authored Oct 15, 2024
1 parent 0be944b commit 543a1f0
Show file tree
Hide file tree
Showing 19 changed files with 453 additions and 0 deletions.
5 changes: 5 additions & 0 deletions .changeset/slow-cycles-perform.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@chainlink/tiingo-state-adapter': minor
---

Add adapter for Tiingo state.
22 changes: 22 additions & 0 deletions .pnp.cjs

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Empty file.
55 changes: 55 additions & 0 deletions packages/sources/tiingo-state/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
# TIINGO_STATE

![0.0.1](https://img.shields.io/github/package-json/v/smartcontractkit/external-adapters-js?filename=packages/sources/tiingo-state/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 |
| :-------: | :-------------: | :---------------------------: | :----: | :-----: | :--------------------: |
| | WS_API_ENDPOINT | websocket endpoint for tiingo | string | | `wss://api.tiingo.com` |
|| API_KEY | API key for tiingo | string | | |

---

## Data Provider Rate Limits

There are no rate limits for this adapter.

---

## Input Parameters

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

## Price Endpoint

Supported names for this endpoint are: `crypto`, `price`, `state`.

### Input Params

| Required? | Name | Aliases | Description | Type | Options | Default | Depends On | Not Valid With |
| :-------: | :---: | :------------: | :--------------------------------------------: | :----: | :-----: | :-----: | :--------: | :------------: |
|| base | `coin`, `from` | The symbol of symbols of the currency to query | string | | | | |
|| quote | `market`, `to` | The symbol of the currency to convert to | string | | | | |

### Example

Request:

```json
{
"data": {
"endpoint": "price",
"base": "wstETH",
"quote": "ETH"
}
}
```

---

MIT License
42 changes: 42 additions & 0 deletions packages/sources/tiingo-state/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
{
"name": "@chainlink/tiingo-state-adapter",
"version": "0.0.1",
"description": "Chainlink tiingo-state adapter.",
"keywords": [
"Chainlink",
"LINK",
"blockchain",
"oracle",
"tiingo-state"
],
"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": {
"@sinonjs/fake-timers": "9.1.2",
"@types/jest": "27.5.2",
"@types/node": "16.11.68",
"@types/sinonjs__fake-timers": "8.1.5",
"nock": "13.5.4",
"typescript": "5.0.4"
},
"dependencies": {
"@chainlink/external-adapter-framework": "1.4.0",
"tslib": "2.4.1"
}
}
23 changes: 23 additions & 0 deletions packages/sources/tiingo-state/src/config/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import { AdapterConfig } from '@chainlink/external-adapter-framework/config'

export const config = new AdapterConfig(
{
WS_API_ENDPOINT: {
description: 'websocket endpoint for tiingo',
default: 'wss://api.tiingo.com',
type: 'string',
},
API_KEY: {
description: 'API key for tiingo',
type: 'string',
required: true,
sensitive: true,
},
},
{
envDefaultOverrides: {
CACHE_MAX_AGE: 150_000, // see known issues in readme
WS_SUBSCRIPTION_TTL: 180_000,
},
},
)
1 change: 1 addition & 0 deletions packages/sources/tiingo-state/src/endpoint/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export { endpoint as price } from './price'
30 changes: 30 additions & 0 deletions packages/sources/tiingo-state/src/endpoint/price.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
import {
PriceEndpoint,
priceEndpointInputParametersDefinition,
} from '@chainlink/external-adapter-framework/adapter'
import { InputParameters } from '@chainlink/external-adapter-framework/validation'
import { SingleNumberResultResponse } from '@chainlink/external-adapter-framework/util'
import { TransportRoutes } from '@chainlink/external-adapter-framework/transports'
import { wsTransport } from '../transport/price'
import { config } from '../config'

export const inputParameters = new InputParameters(priceEndpointInputParametersDefinition, [
{
base: 'wstETH',
quote: 'ETH',
},
])

export type BaseCryptoEndpointTypes = {
Parameters: typeof inputParameters.definition
Settings: typeof config.settings
Response: SingleNumberResultResponse
}

export const endpoint = new PriceEndpoint({
name: 'price',
aliases: ['crypto', 'state'],
transportRoutes: new TransportRoutes<BaseCryptoEndpointTypes>().register('ws', wsTransport),
defaultTransport: 'ws',
inputParameters: inputParameters,
})
13 changes: 13 additions & 0 deletions packages/sources/tiingo-state/src/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import { expose, ServerInstance } from '@chainlink/external-adapter-framework'
import { Adapter } from '@chainlink/external-adapter-framework/adapter'
import { config } from './config'
import { price } from './endpoint'

export const adapter = new Adapter({
defaultEndpoint: price.name,
name: 'TIINGO_STATE',
config,
endpoints: [price],
})

export const server = (): Promise<ServerInstance | undefined> => expose(adapter)
63 changes: 63 additions & 0 deletions packages/sources/tiingo-state/src/transport/price.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
import { WebSocketTransport } from '@chainlink/external-adapter-framework/transports'
import { BaseCryptoEndpointTypes } from '../endpoint/price'

interface Message {
service: string
messageType: string
data: [string, string, string, string, number]
}

const tickerIndex = 1
const dateIndex = 2
const priceIndex = 4

type WsTransportTypes = BaseCryptoEndpointTypes & {
Provider: {
WsMessage: Message
}
}

export const wsTransport = new WebSocketTransport<WsTransportTypes>({
url: (context) => {
return `${context.adapterSettings.WS_API_ENDPOINT}/crypto-synth-state`
},
handlers: {
message(message) {
if (!message?.data?.length || message.messageType !== 'A' || !message.data[priceIndex]) {
return []
}
const [base, quote] = message.data[tickerIndex].split('/')
return [
{
params: { base, quote },
response: {
data: {
result: message.data[priceIndex],
},
result: message.data[priceIndex],
timestamps: {
providerIndicatedTimeUnixMs: new Date(message.data[dateIndex]).getTime(),
},
},
},
]
},
},

builders: {
subscribeMessage: (params, context) => {
return {
eventName: 'subscribe',
authorization: context.adapterSettings.API_KEY,
eventData: { thresholdLevel: 8, tickers: [`${params.base}/${params.quote}`] },
}
},
unsubscribeMessage: (params, context) => {
return {
eventName: 'unsubscribe',
authorization: context.adapterSettings.API_KEY,
eventData: { thresholdLevel: 8, tickers: [`${params.base}/${params.quote}`] },
}
},
},
})
6 changes: 6 additions & 0 deletions packages/sources/tiingo-state/test-payload.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
{
"requests": [{
"from": "wstETH",
"to": "ETH"
}]
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP

exports[`websocket crypto state endpoint should return error on empty base 1`] = `
{
"error": {
"message": "[Param: base] param is required but no value was provided",
"name": "AdapterError",
},
"status": "errored",
"statusCode": 400,
}
`;

exports[`websocket crypto state endpoint should return error on empty data 1`] = `
{
"error": {
"message": "[Param: base] param is required but no value was provided",
"name": "AdapterError",
},
"status": "errored",
"statusCode": 400,
}
`;

exports[`websocket crypto state endpoint should return error on empty quote 1`] = `
{
"error": {
"message": "[Param: quote] param is required but no value was provided",
"name": "AdapterError",
},
"status": "errored",
"statusCode": 400,
}
`;

exports[`websocket crypto state endpoint should return error on invalid pair 1`] = `
{
"error": {
"message": "The EA has not received any values from the Data Provider for the requested data yet. Retry after a short delay, and if the problem persists raise this issue in the relevant channels.",
"name": "AdapterError",
},
"status": "errored",
"statusCode": 504,
}
`;

exports[`websocket crypto state endpoint should return success 1`] = `
{
"data": {
"result": 1.1807636997924935,
},
"result": 1.1807636997924935,
"statusCode": 200,
"timestamps": {
"providerDataReceivedUnixMs": 1018,
"providerDataStreamEstablishedUnixMs": 1010,
"providerIndicatedTimeUnixMs": 1646249828102,
},
}
`;
Loading

0 comments on commit 543a1f0

Please sign in to comment.