diff --git a/.pnp.cjs b/.pnp.cjs index 953315d600..7b84d0fa63 100644 --- a/.pnp.cjs +++ b/.pnp.cjs @@ -6355,6 +6355,30 @@ const RAW_RUNTIME_STATE = "linkType": "SOFT"\ }]\ ]],\ + ["@chainlink/data-streams-sdk", [\ + ["npm:1.0.3", {\ + "packageLocation": "./.yarn/cache/@chainlink-data-streams-sdk-npm-1.0.3-ec952b4534-5e2758fba7.zip/node_modules/@chainlink/data-streams-sdk/",\ + "packageDependencies": [\ + ["@chainlink/data-streams-sdk", "npm:1.0.3"]\ + ],\ + "linkType": "SOFT"\ + }],\ + ["virtual:ae8909ca714dfccddce55e692438c1aab534e848b471e879b1234c3e17eef91de1fe1b27a2961f29f7cfc94f0602985f9fc91d7df3226b103ff5a32d1e571dbc#npm:1.0.3", {\ + "packageLocation": "./.yarn/__virtual__/@chainlink-data-streams-sdk-virtual-169c9673df/0/cache/@chainlink-data-streams-sdk-npm-1.0.3-ec952b4534-5e2758fba7.zip/node_modules/@chainlink/data-streams-sdk/",\ + "packageDependencies": [\ + ["@chainlink/data-streams-sdk", "virtual:ae8909ca714dfccddce55e692438c1aab534e848b471e879b1234c3e17eef91de1fe1b27a2961f29f7cfc94f0602985f9fc91d7df3226b103ff5a32d1e571dbc#npm:1.0.3"],\ + ["@types/dotenv", null],\ + ["dotenv", null],\ + ["ethers", "npm:6.15.0"],\ + ["ws", "virtual:bdc244f853fb22ebac7b81f50917f9b470cc7237095ba56eae0d97416db6fb294de2dfb5b3ab323b141006d4a3cdee50bddf11794531ca39f6010716210e02c8#npm:8.18.3"]\ + ],\ + "packagePeers": [\ + "@types/dotenv",\ + "dotenv"\ + ],\ + "linkType": "HARD"\ + }]\ + ]],\ ["@chainlink/deep-blue-adapter", [\ ["workspace:packages/sources/deep-blue", {\ "packageLocation": "./packages/sources/deep-blue/",\ @@ -7301,11 +7325,12 @@ const RAW_RUNTIME_STATE = "packageLocation": "./packages/composites/glv-token/",\ "packageDependencies": [\ ["@chainlink/glv-token-adapter", "workspace:packages/composites/glv-token"],\ + ["@chainlink/data-streams-sdk", "virtual:ae8909ca714dfccddce55e692438c1aab534e848b471e879b1234c3e17eef91de1fe1b27a2961f29f7cfc94f0602985f9fc91d7df3226b103ff5a32d1e571dbc#npm:1.0.3"],\ ["@chainlink/external-adapter-framework", "npm:2.7.0"],\ ["@types/jest", "npm:29.5.14"],\ ["@types/node", "npm:22.14.1"],\ ["decimal.js", "npm:10.4.3"],\ - ["ethers", "npm:5.8.0"],\ + ["ethers", "npm:6.15.0"],\ ["nock", "npm:13.5.6"],\ ["tslib", "npm:2.4.1"],\ ["typescript", "patch:typescript@npm%3A5.8.3#optional!builtin::version=5.8.3&hash=5786d5"]\ @@ -22338,7 +22363,7 @@ const RAW_RUNTIME_STATE = ["@socket.io/component-emitter", "npm:3.1.2"],\ ["debug", "virtual:e376c6d25689d1413f13b759a5649fe969efab30320e886cab81ece2b6daf8c4c74f642faff7228a9a286b4b82bc7bac5773e45f1085910307cd111b19a8cd17#npm:4.3.7"],\ ["engine.io-parser", "npm:5.2.3"],\ - ["ws", "virtual:f6f30c4272f844f402d8d213d035455fc44a62513f59bd32b98e4d10a455319bfdb6af196858aad2a4a77298527df147593f20513687bbb4b6d88888784cc3b0#npm:8.17.1"],\ + ["ws", "virtual:f266964bbf0a973b765b066fe1b1828807981016fc49075d7d14462508ec0b4c518650d9ae747c8b805b7e3e20b5b050695db51ba47ef5e8e240f1bec894a15f#npm:8.17.1"],\ ["xmlhttprequest-ssl", "npm:2.1.2"]\ ],\ "linkType": "HARD"\ @@ -23109,7 +23134,7 @@ const RAW_RUNTIME_STATE = ["@types/node", "npm:22.7.5"],\ ["aes-js", "npm:4.0.0-beta.5"],\ ["tslib", "npm:2.7.0"],\ - ["ws", "virtual:f6f30c4272f844f402d8d213d035455fc44a62513f59bd32b98e4d10a455319bfdb6af196858aad2a4a77298527df147593f20513687bbb4b6d88888784cc3b0#npm:8.17.1"]\ + ["ws", "virtual:f266964bbf0a973b765b066fe1b1828807981016fc49075d7d14462508ec0b4c518650d9ae747c8b805b7e3e20b5b050695db51ba47ef5e8e240f1bec894a15f#npm:8.17.1"]\ ],\ "linkType": "HARD"\ }],\ @@ -23123,7 +23148,7 @@ const RAW_RUNTIME_STATE = ["@types/node", "npm:22.7.5"],\ ["aes-js", "npm:4.0.0-beta.5"],\ ["tslib", "npm:2.7.0"],\ - ["ws", "virtual:f6f30c4272f844f402d8d213d035455fc44a62513f59bd32b98e4d10a455319bfdb6af196858aad2a4a77298527df147593f20513687bbb4b6d88888784cc3b0#npm:8.17.1"]\ + ["ws", "virtual:f266964bbf0a973b765b066fe1b1828807981016fc49075d7d14462508ec0b4c518650d9ae747c8b805b7e3e20b5b050695db51ba47ef5e8e240f1bec894a15f#npm:8.17.1"]\ ],\ "linkType": "HARD"\ }],\ @@ -23137,7 +23162,7 @@ const RAW_RUNTIME_STATE = ["@types/node", "npm:22.7.5"],\ ["aes-js", "npm:4.0.0-beta.5"],\ ["tslib", "npm:2.7.0"],\ - ["ws", "virtual:f6f30c4272f844f402d8d213d035455fc44a62513f59bd32b98e4d10a455319bfdb6af196858aad2a4a77298527df147593f20513687bbb4b6d88888784cc3b0#npm:8.17.1"]\ + ["ws", "virtual:f266964bbf0a973b765b066fe1b1828807981016fc49075d7d14462508ec0b4c518650d9ae747c8b805b7e3e20b5b050695db51ba47ef5e8e240f1bec894a15f#npm:8.17.1"]\ ],\ "linkType": "HARD"\ }],\ @@ -23151,7 +23176,7 @@ const RAW_RUNTIME_STATE = ["@types/node", "npm:22.7.5"],\ ["aes-js", "npm:4.0.0-beta.5"],\ ["tslib", "npm:2.7.0"],\ - ["ws", "virtual:f6f30c4272f844f402d8d213d035455fc44a62513f59bd32b98e4d10a455319bfdb6af196858aad2a4a77298527df147593f20513687bbb4b6d88888784cc3b0#npm:8.17.1"]\ + ["ws", "virtual:f266964bbf0a973b765b066fe1b1828807981016fc49075d7d14462508ec0b4c518650d9ae747c8b805b7e3e20b5b050695db51ba47ef5e8e240f1bec894a15f#npm:8.17.1"]\ ],\ "linkType": "HARD"\ }]\ @@ -39401,10 +39426,10 @@ const RAW_RUNTIME_STATE = ],\ "linkType": "HARD"\ }],\ - ["virtual:f6e474cf40bbabeb7f1fe6c393b6955df38a58db2d59ee7489b1a74f73c369a1f22a33bd1aedfcae125bddf304fd7dc4ee8d6311eaf92ab0350b94d49e9a172e#npm:7.5.10", {\ - "packageLocation": "./.yarn/__virtual__/ws-virtual-786bd16cba/0/cache/ws-npm-7.5.10-878ccb886b-9c796b84ba.zip/node_modules/ws/",\ + ["virtual:f266964bbf0a973b765b066fe1b1828807981016fc49075d7d14462508ec0b4c518650d9ae747c8b805b7e3e20b5b050695db51ba47ef5e8e240f1bec894a15f#npm:8.17.1", {\ + "packageLocation": "./.yarn/__virtual__/ws-virtual-d0741043a0/0/cache/ws-npm-8.17.1-f57fb24a2c-4264ae92c0.zip/node_modules/ws/",\ "packageDependencies": [\ - ["ws", "virtual:f6e474cf40bbabeb7f1fe6c393b6955df38a58db2d59ee7489b1a74f73c369a1f22a33bd1aedfcae125bddf304fd7dc4ee8d6311eaf92ab0350b94d49e9a172e#npm:7.5.10"],\ + ["ws", "virtual:f266964bbf0a973b765b066fe1b1828807981016fc49075d7d14462508ec0b4c518650d9ae747c8b805b7e3e20b5b050695db51ba47ef5e8e240f1bec894a15f#npm:8.17.1"],\ ["@types/bufferutil", null],\ ["@types/utf-8-validate", null],\ ["bufferutil", null],\ @@ -39418,10 +39443,10 @@ const RAW_RUNTIME_STATE = ],\ "linkType": "HARD"\ }],\ - ["virtual:f6f30c4272f844f402d8d213d035455fc44a62513f59bd32b98e4d10a455319bfdb6af196858aad2a4a77298527df147593f20513687bbb4b6d88888784cc3b0#npm:8.17.1", {\ - "packageLocation": "./.yarn/__virtual__/ws-virtual-271f762cd3/0/cache/ws-npm-8.17.1-f57fb24a2c-4264ae92c0.zip/node_modules/ws/",\ + ["virtual:f6e474cf40bbabeb7f1fe6c393b6955df38a58db2d59ee7489b1a74f73c369a1f22a33bd1aedfcae125bddf304fd7dc4ee8d6311eaf92ab0350b94d49e9a172e#npm:7.5.10", {\ + "packageLocation": "./.yarn/__virtual__/ws-virtual-786bd16cba/0/cache/ws-npm-7.5.10-878ccb886b-9c796b84ba.zip/node_modules/ws/",\ "packageDependencies": [\ - ["ws", "virtual:f6f30c4272f844f402d8d213d035455fc44a62513f59bd32b98e4d10a455319bfdb6af196858aad2a4a77298527df147593f20513687bbb4b6d88888784cc3b0#npm:8.17.1"],\ + ["ws", "virtual:f6e474cf40bbabeb7f1fe6c393b6955df38a58db2d59ee7489b1a74f73c369a1f22a33bd1aedfcae125bddf304fd7dc4ee8d6311eaf92ab0350b94d49e9a172e#npm:7.5.10"],\ ["@types/bufferutil", null],\ ["@types/utf-8-validate", null],\ ["bufferutil", null],\ diff --git a/.yarn/cache/@chainlink-data-streams-sdk-npm-1.0.3-ec952b4534-5e2758fba7.zip b/.yarn/cache/@chainlink-data-streams-sdk-npm-1.0.3-ec952b4534-5e2758fba7.zip new file mode 100644 index 0000000000..d59ccce441 Binary files /dev/null and b/.yarn/cache/@chainlink-data-streams-sdk-npm-1.0.3-ec952b4534-5e2758fba7.zip differ diff --git a/packages/composites/glv-token/package.json b/packages/composites/glv-token/package.json index 1e2e4cfb32..9e3b0ade88 100644 --- a/packages/composites/glv-token/package.json +++ b/packages/composites/glv-token/package.json @@ -34,9 +34,10 @@ "typescript": "5.8.3" }, "dependencies": { + "@chainlink/data-streams-sdk": "^1.0.3", "@chainlink/external-adapter-framework": "2.7.0", "decimal.js": "^10.3.1", - "ethers": "^5.4.6", + "ethers": "^6.15.0", "tslib": "2.4.1" } } diff --git a/packages/composites/glv-token/src/config/index.ts b/packages/composites/glv-token/src/config/index.ts index 68ef164bc5..d8553d4e2f 100644 --- a/packages/composites/glv-token/src/config/index.ts +++ b/packages/composites/glv-token/src/config/index.ts @@ -26,18 +26,18 @@ export const config = new AdapterConfig( required: true, default: '0x6a9505D0B44cFA863d9281EA5B0b34cB36243b45', }, - TIINGO_ADAPTER_URL: { - description: 'URL of Tiingo EA', + DATA_ENGINE_BASE_URL: { + description: 'URL of DataEngine', type: 'string', required: true, }, - NCFX_ADAPTER_URL: { - description: 'URL of NCFX EA', + DATA_ENGINE_USER_ID: { + description: 'User ID of DataEngine', type: 'string', required: true, }, - COINMETRICS_ADAPTER_URL: { - description: 'URL of Coinmetrics EA', + DATA_ENGINE_USER_SECRET: { + description: 'Secret key for DataEngine', type: 'string', required: true, }, @@ -45,7 +45,7 @@ export const config = new AdapterConfig( description: 'Minimum number of source EAs that need to successfully return a value.', type: 'number', required: true, - default: 2, + default: 1, validate: validator.integer({ min: 1, max: 3 }), }, MARKET_INFO_API: { diff --git a/packages/composites/glv-token/src/transport/base.ts b/packages/composites/glv-token/src/transport/base.ts index e79a7ea28b..a58053bba9 100644 --- a/packages/composites/glv-token/src/transport/base.ts +++ b/packages/composites/glv-token/src/transport/base.ts @@ -1,19 +1,24 @@ -import { ethers, utils } from 'ethers' -import { SubscriptionTransport } from '@chainlink/external-adapter-framework/transports/abstract/subscription' -import { TransportDependencies } from '@chainlink/external-adapter-framework/transports' -import { ResponseCache } from '@chainlink/external-adapter-framework/cache/response' -import { Requester } from '@chainlink/external-adapter-framework/util/requester' import { - EndpointContext, - LwbaResponseDataFields, -} from '@chainlink/external-adapter-framework/adapter' + createClient, + DataStreamsClient, + DecodedV3Report, + decodeReport, + LogLevel, +} from '@chainlink/data-streams-sdk' +import { EndpointContext } from '@chainlink/external-adapter-framework/adapter' +import { ResponseCache } from '@chainlink/external-adapter-framework/cache/response' +import { TransportDependencies } from '@chainlink/external-adapter-framework/transports' +import { SubscriptionTransport } from '@chainlink/external-adapter-framework/transports/abstract/subscription' import { AdapterResponse, makeLogger } from '@chainlink/external-adapter-framework/util' +import { Requester } from '@chainlink/external-adapter-framework/util/requester' import { AdapterDataProviderError } from '@chainlink/external-adapter-framework/validation/error' +import { TypeFromDefinition } from '@chainlink/external-adapter-framework/validation/input-params' +import { ethers } from 'ethers' import glvAbi from '../config/glvReaderAbi.json' -import { BaseEndpointTypes, inputParameters } from '../endpoint/price' import { BaseEndpointTypesLwba } from '../endpoint/lwba' +import { BaseEndpointTypes, inputParameters } from '../endpoint/price' +import { dataStreamIdKey } from './gmx-keys' import { - mapParameter, mapSymbol, Market, median, @@ -23,8 +28,6 @@ import { toFixed, Token, } from './utils' -import { TypeFromDefinition } from '@chainlink/external-adapter-framework/validation/input-params' - const logger = makeLogger('GlvBaseTransport') interface glvInformation { @@ -54,13 +57,15 @@ export abstract class BaseGlvTransport< name!: string responseCache!: ResponseCache requester!: Requester - provider!: ethers.providers.JsonRpcProvider + provider!: ethers.JsonRpcProvider glvReaderContract!: ethers.Contract + dataStoreContract!: ethers.Contract settings!: T['Settings'] - + dataStreamsClient!: DataStreamsClient tokensMap: Record = {} marketsMap: Record = {} decimals: Record = {} + symbolToAddressMap: Record = {} async initialize( dependencies: TransportDependencies, @@ -70,7 +75,7 @@ export abstract class BaseGlvTransport< ): Promise { await super.initialize(dependencies, adapterSettings, endpointName, transportName) this.settings = adapterSettings - this.provider = new ethers.providers.JsonRpcProvider( + this.provider = new ethers.JsonRpcProvider( adapterSettings.ARBITRUM_RPC_URL, adapterSettings.ARBITRUM_CHAIN_ID, ) @@ -81,10 +86,23 @@ export abstract class BaseGlvTransport< glvAbi, this.provider, ) - + this.dataStoreContract = new ethers.Contract( + adapterSettings.DATASTORE_CONTRACT_ADDRESS, + ['function getBytes32(bytes32 key) view returns (bytes32)'], + this.provider, + ) await this.tokenInfo() await this.marketInfo() - + this.dataStreamsClient = createClient({ + apiKey: adapterSettings.DATA_ENGINE_USER_ID, + userSecret: adapterSettings.DATA_ENGINE_USER_SECRET, + endpoint: adapterSettings.DATA_ENGINE_BASE_URL, + wsEndpoint: 'wss://ws.dataengine.chain.link', + logging: { + logger: logger, + logLevel: LogLevel.DEBUG, + }, + }) if (this.settings.METADATA_REFRESH_INTERVAL_MS > 0) { setInterval(() => { this.tokenInfo() @@ -122,6 +140,7 @@ export abstract class BaseGlvTransport< data.map((token) => { this.tokensMap[token.address] = token this.decimals[token.symbol] = token.decimals + this.symbolToAddressMap[token.symbol] = token.address }) } @@ -171,17 +190,19 @@ export abstract class BaseGlvTransport< assets.sort() const priceResult = await this.fetchPrices([...new Set(assets)], providerDataRequestedUnixMs) + logger.info(`Price result: ${JSON.stringify(priceResult)}`) const indexTokensPrices: Array[] = [] Object.keys(glv.markets).forEach((m) => { const symbol = mapSymbol(glv.markets[m].indexToken, this.tokensMap).symbol indexTokensPrices.push([priceResult.prices[symbol].bid, priceResult.prices[symbol].ask]) }) + logger.info(`Index tokens prices: ${JSON.stringify(indexTokensPrices)}`) const glvTokenPriceContractParams = [ this.settings.DATASTORE_CONTRACT_ADDRESS, - glvInfo.markets, - indexTokensPrices, + Array.from(glvInfo.markets), + indexTokensPrices.map(([a, b]) => [a, b]), [priceResult.prices[glv.longToken.symbol].bid, priceResult.prices[glv.longToken.symbol].ask], [ priceResult.prices[glv.shortToken.symbol].bid, @@ -190,13 +211,15 @@ export abstract class BaseGlvTransport< glv_address, ] + logger.info(`glvTokenPriceContractParams: ${JSON.stringify(glvTokenPriceContractParams)}`) const [[maximizedPriceRaw], [minimizedPriceRaw]] = await Promise.all([ this.glvReaderContract.getGlvTokenPrice(...glvTokenPriceContractParams, true), this.glvReaderContract.getGlvTokenPrice(...glvTokenPriceContractParams, false), ]) + logger.info(`Fetched prices ${minimizedPriceRaw} and ${maximizedPriceRaw}`) - const maximizedPrice = Number(utils.formatUnits(maximizedPriceRaw, SIGNED_PRICE_DECIMALS)) - const minimizedPrice = Number(utils.formatUnits(minimizedPriceRaw, SIGNED_PRICE_DECIMALS)) + const maximizedPrice = Number(ethers.formatUnits(maximizedPriceRaw, SIGNED_PRICE_DECIMALS)) + const minimizedPrice = Number(ethers.formatUnits(minimizedPriceRaw, SIGNED_PRICE_DECIMALS)) const result = median([minimizedPrice, maximizedPrice]) const timestamps = { @@ -217,74 +240,92 @@ export abstract class BaseGlvTransport< private async fetchPrices(assets: string[], dataRequestedTimestamp: number) { const priceData = {} as PriceData - const sources = [ - { url: this.settings.TIINGO_ADAPTER_URL, name: 'tiingo' }, - { url: this.settings.COINMETRICS_ADAPTER_URL, name: 'coinmetrics' }, - { url: this.settings.NCFX_ADAPTER_URL, name: 'ncfx' }, - ] - const priceProviders: Record = {} - const promises = [] - - for (let i = 0; i < sources.length; i++) { - const source = sources[i] - const assetPromises = assets.map(async (asset) => { - const mappedAsset = mapParameter(source.name, asset) - const base = this.unwrapAsset(mappedAsset) - const requestConfig = { - url: source.url, - method: 'POST', - data: { - data: { - endpoint: 'crypto-lwba', - base, - quote: 'USD', + const sources: Source[] = [{ name: 'data-streams', url: this.settings.DATA_ENGINE_BASE_URL }] + + await Promise.all( + assets.map(async (asset) => { + const tokenAddress = this.symbolToAddressMap[asset] + if (!tokenAddress) { + throw new AdapterDataProviderError( + { statusCode: 400, message: `Unknown token symbol '${asset}'` }, + { + providerDataRequestedUnixMs: dataRequestedTimestamp, + providerDataReceivedUnixMs: Date.now(), + providerIndicatedTimeUnixMs: undefined, }, - }, + ) } + // compute GMX key & read feedId from DataStore + const key = dataStreamIdKey(tokenAddress) // bytes32 + const feedId: string = await this.dataStoreContract.getBytes32(key) + const report = await this.dataStreamsClient.getLatestReport(feedId) + // Decode (V3 expected: price, bid, ask; decoder handles V2–V10) + let decoded try { - const response = await this.requester.request<{ data: LwbaResponseDataFields['Data'] }>( - JSON.stringify(requestConfig), - requestConfig, + // logger.error(`Decoding report for ${asset} with feedId ${report.feedID}`) + decoded = decodeReport(report.fullReport, report.feedID) as DecodedV3Report + logger.info( + `Decoded report for ${asset} with feedId ${report.feedID} and value ${decoded.bid}`, ) - const { bid, ask } = response.response.data.data - - priceData[asset] = { - bids: [...(priceData[asset]?.bids || []), bid], - asks: [...(priceData[asset]?.asks || []), ask], - } - - priceProviders[asset] = priceProviders[asset] - ? [...new Set([...priceProviders[asset], source.name])] - : [source.name] - } catch (error) { - const e = error as Error - logger.error( - `Error fetching data for ${asset} from ${source.name}, url - ${source.url}: ${e.message}`, + } catch (e) { + logger.error(e, `Error decoding report for ${asset}`) + } + const DATA_STREAM_DECIMALS = 18 + const DATA_STREAM_SCALE = 10 ** DATA_STREAM_DECIMALS + const toNumFromDS = (x?: bigint) => (x == null ? undefined : Number(x) / DATA_STREAM_SCALE) + + const v3Bid = (decoded as any).bid + const v3Ask = (decoded as any).ask + const v3Price = (decoded as any).price + logger.info(`For ${asset}, bid: ${v3Ask}, ask: ${v3Bid}`) + + const bidNum = toNumFromDS(v3Bid ?? v3Price) + const askNum = toNumFromDS(v3Ask ?? v3Price) + + if (bidNum === undefined || askNum === undefined) { + throw new AdapterDataProviderError( + { statusCode: 502, message: `Could not decode bid/ask for ${asset}` }, + { + providerDataRequestedUnixMs: dataRequestedTimestamp, + providerDataReceivedUnixMs: Date.now(), + providerIndicatedTimeUnixMs: undefined, + }, ) } - }) - promises.push(...assetPromises) - } + // Store raw numbers for median calc + priceData[asset] = { + bids: [...(priceData[asset]?.bids || []), bidNum], + asks: [...(priceData[asset]?.asks || []), askNum], + } - await Promise.all(promises) + // Track that Data Streams responded for this *base* key + priceProviders[asset] = priceProviders[asset] ? priceProviders[asset] : [] + if (!priceProviders[asset].includes(sources[0].name)) { + priceProviders[asset].push(sources[0].name) + } + }), + ) this.validateRequiredResponses(priceProviders, sources, assets, dataRequestedTimestamp) const medianValues = this.calculateMedian(assets, priceData) - const prices: Record> = {} + logger.info(`Median values: ${JSON.stringify(medianValues)}`) + logger.info(`Decimals map: ${JSON.stringify(this.decimals)}`) - medianValues.map( - (v) => - (prices[v.asset] = { - ...v, - ask: toFixed(v.ask, this.decimals[v.asset as keyof typeof this.decimals]), - bid: toFixed(v.bid, this.decimals[v.asset as keyof typeof this.decimals]), - }), - ) + medianValues.forEach((v) => { + if (this.decimals[v.asset as keyof typeof this.decimals] == null) { + logger.error(`No decimals found for asset ${v.asset}`) + } + prices[v.asset] = { + ...v, + ask: toFixed(v.ask, this.decimals[v.asset as keyof typeof this.decimals]), + bid: toFixed(v.bid, this.decimals[v.asset as keyof typeof this.decimals]), + } + }) return { prices, @@ -300,16 +341,6 @@ export abstract class BaseGlvTransport< }) } - private unwrapAsset(asset: string) { - if (asset === 'WBTC.b') { - return 'BTC' - } - if (asset === 'WETH') { - return 'ETH' - } - return asset - } - private validateRequiredResponses( priceProviders: Record = {}, sources: Source[], @@ -332,8 +363,7 @@ export abstract class BaseGlvTransport< } assets.forEach((asset) => { - const base = this.unwrapAsset(asset) - const respondedSources = priceProviders[base] + const respondedSources = priceProviders[asset] if (respondedSources.length < this.settings.MIN_REQUIRED_SOURCE_SUCCESS) { const missingSources = allSource.filter((s) => !respondedSources.includes(s)) diff --git a/packages/composites/glv-token/src/transport/gmx-keys.ts b/packages/composites/glv-token/src/transport/gmx-keys.ts new file mode 100644 index 0000000000..68388a6fac --- /dev/null +++ b/packages/composites/glv-token/src/transport/gmx-keys.ts @@ -0,0 +1,18 @@ +// src/gmx/keys.ts (vendored) +// Source: https://github.com/gmx-io/gmx-synthetics/blob/main/utils/keys.ts +// Source: https://github.com/gmx-io/gmx-synthetics/blob/main/utils/hash.ts + +import { AbiCoder, getAddress, keccak256 } from 'ethers' + +const abi = AbiCoder.defaultAbiCoder() + +// bytes32 constant: keccak256(abi.encode("DATA_STREAM_ID")) +export const DATA_STREAM_ID = keccak256(abi.encode(['string'], ['DATA_STREAM_ID'])) + +export function hashData(types: string[], values: unknown[]): string { + return keccak256(abi.encode(types, values)) +} + +export function dataStreamIdKey(token: string): string { + return hashData(['bytes32', 'address'], [DATA_STREAM_ID, getAddress(token)]) +} diff --git a/packages/composites/glv-token/src/transport/price.ts b/packages/composites/glv-token/src/transport/price.ts index 02b25f4125..c2f00fe004 100644 --- a/packages/composites/glv-token/src/transport/price.ts +++ b/packages/composites/glv-token/src/transport/price.ts @@ -1,8 +1,8 @@ -import { BaseEndpointTypes } from '../endpoint/price' -import { BaseGlvTransport } from './base' import { EndpointContext } from '@chainlink/external-adapter-framework/adapter' -import { TypeFromDefinition } from '@chainlink/external-adapter-framework/validation/input-params' import { AdapterResponse, sleep } from '@chainlink/external-adapter-framework/util' +import { TypeFromDefinition } from '@chainlink/external-adapter-framework/validation/input-params' +import { BaseEndpointTypes } from '../endpoint/price' +import { BaseGlvTransport } from './base' export class GlvPriceTransport extends BaseGlvTransport { async backgroundHandler( @@ -18,6 +18,7 @@ export class GlvPriceTransport extends BaseGlvTransport { try { response = await this._handleRequest(param) } catch (e) { + console.error(e) response = this.handleError(e) } await this.responseCache.write(this.name, [{ params: param, response }]) diff --git a/packages/composites/glv-token/src/transport/utils.ts b/packages/composites/glv-token/src/transport/utils.ts index e40020fdcb..ab4f36d292 100644 --- a/packages/composites/glv-token/src/transport/utils.ts +++ b/packages/composites/glv-token/src/transport/utils.ts @@ -52,24 +52,3 @@ export interface Market { export function mapSymbol(address: string, symbolMap: Record) { return symbolMap[address] } - -const adapterParamOverride: Record> = { - coinmetrics: { - TAO: 'tao_bittensor', - SPX6900: 'spx', - }, - tiingo: { - FLOKI: 'floki2', - SPX6900: 'spx', - }, - ncfx: { - SPX6900: 'spx', - }, -} - -export function mapParameter(source: string, param: string) { - if (source in adapterParamOverride && param in adapterParamOverride[source]) { - return adapterParamOverride[source][param] - } - return param -} diff --git a/packages/composites/glv-token/test/integration/adapter.test.ts b/packages/composites/glv-token/test/integration/adapter.test.ts index 5140f9247e..29a542bca9 100644 --- a/packages/composites/glv-token/test/integration/adapter.test.ts +++ b/packages/composites/glv-token/test/integration/adapter.test.ts @@ -1,25 +1,25 @@ +import { + LwbaResponseDataFields, + validateLwbaResponse, +} from '@chainlink/external-adapter-framework/adapter' import { TestAdapter, setEnvVariables, } from '@chainlink/external-adapter-framework/util/testing-utils' +import { ethers } from 'ethers' import * as nock from 'nock' import { mockCoinmetricsEAResponseSuccess, mockNCFXEAResponseSuccess, mockTiingoEAResponseSuccess, } from './fixtures' -import { ethers } from 'ethers' -import { - LwbaResponseDataFields, - validateLwbaResponse, -} from '@chainlink/external-adapter-framework/adapter' jest.mock('ethers', () => ({ ...jest.requireActual('ethers'), ethers: { providers: { - JsonRpcProvider: function (): ethers.providers.JsonRpcProvider { - return {} as ethers.providers.JsonRpcProvider + JsonRpcProvider: function (): ethers.JsonRpcProvider { + return {} as ethers.JsonRpcProvider }, }, Contract: function () { diff --git a/yarn.lock b/yarn.lock index e5b8d8878c..296c434f74 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3458,6 +3458,21 @@ __metadata: languageName: unknown linkType: soft +"@chainlink/data-streams-sdk@npm:^1.0.3": + version: 1.0.3 + resolution: "@chainlink/data-streams-sdk@npm:1.0.3" + dependencies: + ethers: "npm:^6.15.0" + ws: "npm:^8.18.3" + peerDependencies: + dotenv: ^16.6.1 + peerDependenciesMeta: + dotenv: + optional: true + checksum: 10/5e2758fba7a9484238ba6149fbc94d3cc57073f84d099d55f6e0adce7489cc02db0a740cfb2eb6667ac76d3b5d58a23e12f9444117f758b2c6ec6372c6653d03 + languageName: node + linkType: hard + "@chainlink/deep-blue-adapter@workspace:packages/sources/deep-blue": version: 0.0.0-use.local resolution: "@chainlink/deep-blue-adapter@workspace:packages/sources/deep-blue" @@ -4308,11 +4323,12 @@ __metadata: version: 0.0.0-use.local resolution: "@chainlink/glv-token-adapter@workspace:packages/composites/glv-token" dependencies: + "@chainlink/data-streams-sdk": "npm:^1.0.3" "@chainlink/external-adapter-framework": "npm:2.7.0" "@types/jest": "npm:^29.5.14" "@types/node": "npm:22.14.1" decimal.js: "npm:^10.3.1" - ethers: "npm:^5.4.6" + ethers: "npm:^6.15.0" nock: "npm:13.5.6" tslib: "npm:2.4.1" typescript: "npm:5.8.3" @@ -32949,7 +32965,7 @@ __metadata: languageName: node linkType: hard -"ws@npm:8.18.3": +"ws@npm:8.18.3, ws@npm:^8.18.3": version: 8.18.3 resolution: "ws@npm:8.18.3" peerDependencies: