Skip to content

Merc6933 GMCI EA - Data Link Feed #3919

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 43 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
43 commits
Select commit Hold shift + click to select a range
26760dc
MERC6933-GMCI EA
Subarna-Singh Jun 19, 2025
8d5f2a8
GMCI EA
Subarna-Singh Jun 23, 2025
9f4b9f2
Merge branch 'main' of github.com:smartcontractkit/external-adapters-…
Subarna-Singh Jun 25, 2025
59231d6
Handle Response
Subarna-Singh Jun 25, 2025
c06fdd5
Changeset added
Subarna-Singh Jun 25, 2025
3c6f4b5
Add Readme file; modify tsconfig
Subarna-Singh Jun 25, 2025
60a33d5
GMCI EA - parsing price and rebalance messages and producing a result…
Subarna-Singh Jul 9, 2025
17a621f
upgrade version of dependency packages
Subarna-Singh Jul 11, 2025
d98103a
remove override functionality
Subarna-Singh Jul 11, 2025
7c0c771
Add new changeset
Subarna-Singh Jul 11, 2025
4fe40f2
Review work: fix data types and hard coded values
Subarna-Singh Jul 13, 2025
000a15e
Integration testing added
Subarna-Singh Jul 13, 2025
ff19d55
Remove failure test
Subarna-Singh Jul 13, 2025
2e4d6fd
Correction to pass: Compile test for changed packages workflow
Subarna-Singh Jul 13, 2025
4f55e23
Remove hardcoded values: adapter name
Subarna-Singh Jul 13, 2025
f4431aa
Add gmci to tsconfig files
Subarna-Singh Jul 13, 2025
e8c8c2b
Merge branch 'main' into MERC6933-GMCI-EA-Data_Link_Feed
Subarna-Singh Jul 13, 2025
66174dc
Testing: add unit tests; add more integration tests
Subarna-Singh Jul 15, 2025
9a5e303
Remove: commented import
Subarna-Singh Jul 15, 2025
c5659a7
Merge branch 'MERC6933-GMCI-EA-Data_Link_Feed' of github.com:smartcon…
Subarna-Singh Jul 15, 2025
0b8a763
Alternate implementation for price transport
Subarna-Singh Jul 15, 2025
da09e6c
Refactor: add async method processMessage to pull data from local cache
Subarna-Singh Jul 16, 2025
a80b239
Rework: Price transport
Subarna-Singh Jul 16, 2025
c727a96
Remove: old price transport
Subarna-Singh Jul 16, 2025
50d4d42
Modify: unit test to reflect new price transport
Subarna-Singh Jul 16, 2025
437bfb0
Fix: uppercase to camelcase
Subarna-Singh Jul 16, 2025
b9247df
Refactor: make processMessage part of wstransport; move message handl…
Subarna-Singh Jul 16, 2025
f80198a
Unit test: for price transport
Subarna-Singh Jul 16, 2025
0912899
Delete .changeset/itchy-pianos-wave.md
Subarna-Singh Jul 16, 2025
30b8375
Remove: support for rebalance_status message; merging of price and re…
Subarna-Singh Jul 17, 2025
6edb102
Merge branch 'MERC6933-GMCI-EA-Data_Link_Feed' of github.com:smartcon…
Subarna-Singh Jul 17, 2025
e126611
Remove: default API endpoint; Modify: WsResponse to include Price and…
Subarna-Singh Jul 17, 2025
bf6b59d
Add unit test for message handler
Subarna-Singh Jul 17, 2025
418ed2f
Change: index to symbol; make WS_API_ENDPOINT required
Subarna-Singh Jul 17, 2025
d4d4720
Change: tests to reflect symbol instead of index
Subarna-Singh Jul 17, 2025
871b36d
Change: test payload
Subarna-Singh Jul 18, 2025
dd06ecd
Merge branch 'main' into MERC6933-GMCI-EA-Data_Link_Feed
Subarna-Singh Jul 18, 2025
9916d51
Fix: Failing workflow - missing argument in transport
Subarna-Singh Jul 18, 2025
2292dcd
Merge branch 'MERC6933-GMCI-EA-Data_Link_Feed' of github.com:smartcon…
Subarna-Singh Jul 18, 2025
90ae121
Fix mockContext
Subarna-Singh Jul 18, 2025
e28958d
Add: message response type
Subarna-Singh Jul 18, 2025
3833941
Add: unit test for rebalance message
Subarna-Singh Jul 18, 2025
81abf28
update readme
Subarna-Singh Jul 18, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/proud-masks-film.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@chainlink/gmci-adapter': major
---

Version 1.0.0 of GMCI EA
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.
53 changes: 53 additions & 0 deletions packages/sources/gmci/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
# GMCI

![0.0.0](https://img.shields.io/github/package-json/v/smartcontractkit/external-adapters-js?filename=packages/sources/gmci/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 |
| :-------: | :-------------: | :--------------------------------: | :----: | :-----: | :-----: |
| ✅ | API_KEY | An API key for Data Provider | string | | |
| | WS_API_ENDPOINT | WS endpoint for GMCI Data Provider | 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 | [price](#price-endpoint) | `price` |

## Price Endpoint

`price` is the only supported name for this endpoint.

### Input Params

| Required? | Name | Aliases | Description | Type | Options | Default | Depends On | Not Valid With |
| :-------: | :----: | :-----: | :---------: | :----: | :-----: | :-----: | :--------: | :------------: |
| ✅ | symbol | | Index name | string | | | | |

### Example

Request:

```json
{
"data": {
"endpoint": "price",
"symbol": "GMCI30"
}
}
```

---

MIT License
42 changes: 42 additions & 0 deletions packages/sources/gmci/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
{
"name": "@chainlink/gmci-adapter",
"version": "0.0.0",
"description": "Chainlink gmci adapter.",
"keywords": [
"Chainlink",
"LINK",
"blockchain",
"oracle",
"gmci"
],
"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": "29.5.14",
"@types/node": "22.14.1",
"@types/sinonjs__fake-timers": "8.1.5",
"nock": "13.5.6",
"typescript": "5.8.3"
},
"dependencies": {
"@chainlink/external-adapter-framework": "2.6.0",
"tslib": "2.4.1"
}
}
15 changes: 15 additions & 0 deletions packages/sources/gmci/src/config/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import { AdapterConfig } from '@chainlink/external-adapter-framework/config'

export const config = new AdapterConfig({
API_KEY: {
description: 'An API key for Data Provider',
type: 'string',
required: true,
sensitive: true,
},
WS_API_ENDPOINT: {
description: 'WS endpoint for GMCI Data Provider',
type: 'string',
required: true,
},
})
1 change: 1 addition & 0 deletions packages/sources/gmci/src/endpoint/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export { endpoint as price } from './price'
40 changes: 40 additions & 0 deletions packages/sources/gmci/src/endpoint/price.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
import { AdapterEndpoint } from '@chainlink/external-adapter-framework/adapter'
import { InputParameters } from '@chainlink/external-adapter-framework/validation'
import { config } from '../config'
import { transport } from '../transport/price'

export const inputParameters = new InputParameters(
{
symbol: {
required: true,
type: 'string',
description: 'Index name',
},
},
[
{
symbol: 'GMCI30',
},
],
)

export type GMCIResultResponse = {
Result: number
Data: {
symbol: string
result: number
}
}

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

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

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

export interface PriceMessage {
last_updated: string
price: number
symbol: string
}

export interface RebalanceMessage {
end_time: string
start_time: string
status: string
symbol: string
}

interface WsPriceResponse {
success: boolean
data: Array<PriceMessage>
topic: 'price'
}

interface WsRebalanceResponse {
success: boolean
data: Array<RebalanceMessage>
topic: 'rebalance_status'
}

export type WsResponse = WsPriceResponse | WsRebalanceResponse

export type WsTransportTypes = BaseEndpointTypes & {
Provider: {
WsMessage: WsResponse
}
}

const logger = makeLogger('GmciTransport')

export const options: WebSocketTransportConfig<WsTransportTypes> = {
url: (context: EndpointContext<WsTransportTypes>) => context.adapterSettings.WS_API_ENDPOINT,
options: async (context: EndpointContext<WsTransportTypes>) => ({
headers: {
'X-GMCI-API-KEY': context.adapterSettings.API_KEY,
},
}),

handlers: {
message(message: WsResponse) {
if (message.success === false) {
logger.info(message)
return
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we expect unsuccessful messages that we don't care about?
Or should we log something?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We should expect unsuccessful message. Added log for this.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't see a log.

}

const results = []

if (message.topic === 'price') {
for (const item of message.data) {
results.push({
params: { symbol: item.symbol },
response: {
result: item.price,
data: {
result: item.price,
symbol: item.symbol,
},
timestamps: {
providerIndicatedTimeUnixMs: convertTimetoUnixMs(item.last_updated),
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Weren't there multiple different timestamps before? How do we decide which timestamps to provide?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The others get written here.
Since we had write to the cache ourselves, we have to this bit of work in the previous version.

},
},
})
}
}
return results
},
},

builders: {
subscribeMessage: (params) => {
return {
op: 'subscribe',
args: [`price.${params.symbol}`.toLowerCase()],
}
},

unsubscribeMessage: (params) => {
return {
op: 'unsubscribe',
args: [`price.${params.symbol}`.toLowerCase()],
}
},
},
}

export const transport = new WebSocketTransport(options)
3 changes: 3 additions & 0 deletions packages/sources/gmci/src/transport/util.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
export function convertTimetoUnixMs(isoTime: string) {
return Date.parse(isoTime)
}
5 changes: 5 additions & 0 deletions packages/sources/gmci/test-payload.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
{
"requests": [{
"symbol": "GMCI30"
}]
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP

exports[`websocket price endpoint should return success 1`] = `
{
"data": {
"result": 183.7141917913536,
"symbol": "GMCI30",
},
"result": 183.7141917913536,
"statusCode": 200,
"timestamps": {
"providerDataReceivedUnixMs": 1018,
"providerDataStreamEstablishedUnixMs": 1010,
"providerIndicatedTimeUnixMs": 1752394072746,
},
}
`;
Loading
Loading