Skip to content

Commit

Permalink
Create EA for Mobula for state pricing (#3437)
Browse files Browse the repository at this point in the history
  • Loading branch information
mxiao-cll committed Sep 19, 2024
1 parent 9a4d510 commit e93bc76
Show file tree
Hide file tree
Showing 19 changed files with 355 additions and 6 deletions.
5 changes: 5 additions & 0 deletions .changeset/ten-ravens-tease.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@chainlink/mobula-state-adapter': major
---

Create EA for Mobula for state pricing
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.
3 changes: 3 additions & 0 deletions packages/sources/mobula-state/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
# Chainlink External Adapter for mobula-state

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 mobula-state`.
42 changes: 42 additions & 0 deletions packages/sources/mobula-state/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
{
"name": "@chainlink/mobula-state-adapter",
"version": "0.0.0",
"description": "Chainlink mobula-state adapter.",
"keywords": [
"Chainlink",
"LINK",
"blockchain",
"oracle",
"mobula-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.3.2",
"tslib": "2.4.1"
}
}
9 changes: 9 additions & 0 deletions packages/sources/mobula-state/src/config/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import { AdapterConfig } from '@chainlink/external-adapter-framework/config'

export const config = new AdapterConfig({
WS_API_ENDPOINT: {
description: 'WS endpoint for Data Provider',
type: 'string',
default: 'wss://feed.zobula.xyz',
},
})
1 change: 1 addition & 0 deletions packages/sources/mobula-state/src/endpoint/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export { endpoint as price } from './price'
41 changes: 41 additions & 0 deletions packages/sources/mobula-state/src/endpoint/price.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
import { AdapterEndpoint } from '@chainlink/external-adapter-framework/adapter'
import { InputParameters } from '@chainlink/external-adapter-framework/validation'
import { SingleNumberResultResponse } from '@chainlink/external-adapter-framework/util'
import { config } from '../config'
import { wsTransport } from '../transport/price'

export const inputParameters = new InputParameters(
{
base: {
aliases: ['from', 'coin', 'symbol', 'market'],
required: true,
type: 'string',
description: 'The symbol of symbols of the currency to query',
},
quote: {
aliases: ['to', 'convert'],
required: true,
type: 'string',
description: 'The symbol of the currency to convert to',
},
},
[
{
base: 'ETH',
quote: 'USD',
},
],
)

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

export const endpoint = new AdapterEndpoint({
name: 'price',
aliases: ['state'],
transport: wsTransport,
inputParameters,
})
13 changes: 13 additions & 0 deletions packages/sources/mobula-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: 'MOBULA_STATE',
config,
endpoints: [price],
})

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

export interface WSResponse {
timestamp: number
price: number
marketDepthUSDUp: number
marketDepthUSDDown: number
volume24h: number
baseSymbol: string
quoteSymbol: string
}

export type WsTransportTypes = BaseEndpointTypes & {
Provider: {
WsMessage: WSResponse
}
}
export const wsTransport = new WebSocketTransport<WsTransportTypes>({
url: (context) => context.adapterSettings.WS_API_ENDPOINT,
handlers: {
open: (connection) => {
connection.send(JSON.stringify({ type: 'feed' }))
},
message(message) {
if (!message.price) {
return [
{
params: {
base: message.baseSymbol,
quote: message.quoteSymbol,
},
response: {
errorMessage: 'No price in message',
statusCode: 500,
},
},
]
}

return [
{
params: { base: message.baseSymbol, quote: message.quoteSymbol },
response: {
result: message.price,
data: {
result: message.price,
},
timestamps: {
providerIndicatedTimeUnixMs: message.timestamp,
},
},
},
]
},
},
})
6 changes: 6 additions & 0 deletions packages/sources/mobula-state/test-payload.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
{
"requests": [{
"from": "ETH",
"to": "USD"
}]
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP

exports[`websocket price endpoint have data should return success 1`] = `
{
"data": {
"result": 2325.847186068699,
},
"result": 2325.847186068699,
"statusCode": 200,
"timestamps": {
"providerDataReceivedUnixMs": 1018,
"providerDataStreamEstablishedUnixMs": 1010,
"providerIndicatedTimeUnixMs": 1726648165000,
},
}
`;

exports[`websocket price endpoint no data should return failure 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,
}
`;
64 changes: 64 additions & 0 deletions packages/sources/mobula-state/test/integration/adapter.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
import { WebSocketClassProvider } from '@chainlink/external-adapter-framework/transports'
import {
TestAdapter,
setEnvVariables,
mockWebSocketProvider,
MockWebsocketServer,
} from '@chainlink/external-adapter-framework/util/testing-utils'
import FakeTimers from '@sinonjs/fake-timers'
import { mockWebsocketServer } from './fixtures'

describe('websocket', () => {
let mockWsServer: MockWebsocketServer | undefined
let testAdapter: TestAdapter
const wsEndpoint = 'ws://localhost:9090'
let oldEnv: NodeJS.ProcessEnv

const dataPrice = {
base: 'ETH',
quote: 'USD',
endpoint: 'price',
transport: 'ws',
}

beforeAll(async () => {
oldEnv = JSON.parse(JSON.stringify(process.env))
process.env['WS_API_ENDPOINT'] = wsEndpoint
mockWebSocketProvider(WebSocketClassProvider)
mockWsServer = mockWebsocketServer(wsEndpoint)

const adapter = (await import('./../../src')).adapter
testAdapter = await TestAdapter.startWithMockedCache(adapter, {
clock: FakeTimers.install(),
testAdapter: {} as TestAdapter<never>,
})

// Send initial request to start background execute and wait for cache to be filled with results
await testAdapter.request(dataPrice)
await testAdapter.waitForCache(1)
})

afterAll(async () => {
setEnvVariables(oldEnv)
mockWsServer?.close()
testAdapter.clock?.uninstall()
await testAdapter.api.close()
})

describe('price endpoint', () => {
it('have data should return success', async () => {
const response = await testAdapter.request(dataPrice)
expect(response.json()).toMatchSnapshot()
})

it('no data should return failure', async () => {
const response = await testAdapter.request({
base: 'ETH',
quote: 'EUR',
endpoint: 'price',
transport: 'ws',
})
expect(response.json()).toMatchSnapshot()
})
})
})
22 changes: 22 additions & 0 deletions packages/sources/mobula-state/test/integration/fixtures.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import { MockWebsocketServer } from '@chainlink/external-adapter-framework/util/testing-utils'

export const mockWebsocketServer = (URL: string): MockWebsocketServer => {
const mockWsServer = new MockWebsocketServer(URL, { mock: false })
mockWsServer.on('connection', (socket) => {
socket.on('message', () => {
return socket.send(
JSON.stringify({
timestamp: 1726648165000,
price: 2325.847186068699,
marketDepthUSDUp: 1097741407.1171298,
marketDepthUSDDown: 1032495335.1741029,
volume24h: 230120379.9751866,
baseSymbol: 'ETH',
quoteSymbol: 'USD',
}),
)
})
})

return mockWsServer
}
9 changes: 9 additions & 0 deletions packages/sources/mobula-state/tsconfig.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
{
"extends": "../../tsconfig.base.json",
"compilerOptions": {
"outDir": "dist",
"rootDir": "src"
},
"include": ["src/**/*", "src/**/*.json"],
"exclude": ["dist", "**/*.spec.ts", "**/*.test.ts"]
}
7 changes: 7 additions & 0 deletions packages/sources/mobula-state/tsconfig.test.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
{
"extends": "../../tsconfig.base.json",
"include": ["src/**/*", "**/test", "src/**/*.json"],
"compilerOptions": {
"noEmit": true
}
}
9 changes: 6 additions & 3 deletions packages/tsconfig.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,6 @@
"files": [],
"include": [],
"references": [
{
"path": "./composites/multi-address-list"
},
{
"path": "./composites/anchor"
},
Expand Down Expand Up @@ -71,6 +68,9 @@
{
"path": "./composites/medianizer"
},
{
"path": "./composites/multi-address-list"
},
{
"path": "./composites/nftx"
},
Expand Down Expand Up @@ -452,6 +452,9 @@
{
"path": "./sources/metalsapi"
},
{
"path": "./sources/mobula-state"
},
{
"path": "./sources/mock-ea"
},
Expand Down
Loading

0 comments on commit e93bc76

Please sign in to comment.