diff --git a/.pnp.cjs b/.pnp.cjs index 983d919a20..66ac472519 100644 --- a/.pnp.cjs +++ b/.pnp.cjs @@ -95,6 +95,10 @@ function $$SETUP_STATE(hydrateRuntimeState, basePath) { "name": "@chainlink/nftx-adapter",\ "reference": "workspace:packages/composites/nftx"\ },\ + {\ + "name": "@chainlink/ondo-gm-tokenized-adapter",\ + "reference": "workspace:packages/composites/ondo-gm-tokenized"\ + },\ {\ "name": "@chainlink/outlier-detection-adapter",\ "reference": "workspace:packages/composites/outlier-detection"\ @@ -973,6 +977,7 @@ function $$SETUP_STATE(hydrateRuntimeState, basePath) { ["@chainlink/observation", ["workspace:packages/observation"]],\ ["@chainlink/oilpriceapi-adapter", ["workspace:packages/sources/oilpriceapi"]],\ ["@chainlink/onchain-gas-adapter", ["workspace:packages/sources/onchain-gas"]],\ + ["@chainlink/ondo-gm-tokenized-adapter", ["workspace:packages/composites/ondo-gm-tokenized"]],\ ["@chainlink/openexchangerates-adapter", ["workspace:packages/sources/openexchangerates"]],\ ["@chainlink/orchid-bandwidth-adapter", ["workspace:packages/sources/orchid-bandwidth"]],\ ["@chainlink/outlier-detection-adapter", ["workspace:packages/composites/outlier-detection"]],\ @@ -8245,6 +8250,21 @@ function $$SETUP_STATE(hydrateRuntimeState, basePath) { "linkType": "SOFT"\ }]\ ]],\ + ["@chainlink/ondo-gm-tokenized-adapter", [\ + ["workspace:packages/composites/ondo-gm-tokenized", {\ + "packageLocation": "./packages/composites/ondo-gm-tokenized/",\ + "packageDependencies": [\ + ["@chainlink/ondo-gm-tokenized-adapter", "workspace:packages/composites/ondo-gm-tokenized"],\ + ["@chainlink/ea-bootstrap", "workspace:packages/core/bootstrap"],\ + ["@chainlink/example-source-adapter", "workspace:packages/examples/source"],\ + ["@types/jest", "npm:27.5.2"],\ + ["@types/node", "npm:16.11.51"],\ + ["tslib", "npm:2.4.1"],\ + ["typescript", "patch:typescript@npm%3A5.0.4#~builtin::version=5.0.4&hash=b5f058"]\ + ],\ + "linkType": "SOFT"\ + }]\ + ]],\ ["@chainlink/openexchangerates-adapter", [\ ["workspace:packages/sources/openexchangerates", {\ "packageLocation": "./packages/sources/openexchangerates/",\ diff --git a/.yarn/cache/fsevents-patch-6b67494872-10.zip b/.yarn/cache/fsevents-patch-6b67494872-10.zip new file mode 100644 index 0000000000..9887ada72d Binary files /dev/null and b/.yarn/cache/fsevents-patch-6b67494872-10.zip differ diff --git a/.yarn/cache/fsevents-patch-afc6995412-10.zip b/.yarn/cache/fsevents-patch-afc6995412-10.zip new file mode 100644 index 0000000000..34871c571d Binary files /dev/null and b/.yarn/cache/fsevents-patch-afc6995412-10.zip differ diff --git a/packages/composites/ondo-gm-tokenized/CHANGELOG.md b/packages/composites/ondo-gm-tokenized/CHANGELOG.md new file mode 100644 index 0000000000..3cb3a9f5ed --- /dev/null +++ b/packages/composites/ondo-gm-tokenized/CHANGELOG.md @@ -0,0 +1,2 @@ +# @chainlink/ondo-gm-tokenized-adapter + diff --git a/packages/composites/ondo-gm-tokenized/README.md b/packages/composites/ondo-gm-tokenized/README.md new file mode 100644 index 0000000000..134300a05c --- /dev/null +++ b/packages/composites/ondo-gm-tokenized/README.md @@ -0,0 +1,130 @@ +# Chainlink Ondo GM Tokenized Composite Adapter + +A composite adapter that calculates tokenized prices for Ondo GM tokens (e.g., TSLAon, SPYon, QQQon) by combining: + +1. **Underlying asset prices** from Chainlink Equities Price Feeds (Data Streams) +2. **Multiplier data** from Ondo Finance API + +The adapter returns `tokenizedPrice = underlyingPrice × activeMultiplier` with 8 decimal precision. + +## How It Works + +Ondo GM tokens are total-return trackers that track underlying equities with adjustable multipliers. This adapter: + +1. Fetches the latest underlying price from Chainlink Data Streams using the provided `feedId` +2. Retrieves multiplier information from Ondo's API +3. Calculates the active multiplier based on activation logic (supports pending multiplier changes) +4. Returns the tokenized price with 8 decimal precision + +## Configuration + +The adapter requires the following environment variables: + +| Required? | Name | Description | Options | Defaults to | +| :-------: | :----------------: | :------------------------------------------------: | :-----: | :---------: | +| ✅ | `ONDO_API_KEY` | API key for Ondo Finance API | | | +| | `STREAMS_API_KEY` | API key for Chainlink Data Streams | | | +| | `STREAMS_API_SECRET` | API secret for Chainlink Data Streams | | | +| | `ONDO_BASE_URL` | Base URL for Ondo Finance API | | `https://api.gm.ondo.finance` | + +## Running + +See the [Composite Adapter README](../README.md) for more information on how to get started. + +### Input Parameters + +| Required? | Name | Description | Options | Defaults to | +| :-------: | :----------: | :--------------------------------------------------------------------: | :--------------------------: | :---------: | +| ✅ | `symbol` | GM token symbol to get price for | `TSLAon`, `SPYon`, `QQQon` | | +| | `underlying` | Base ticker (if not provided, derived from symbol) | `TSLA`, `SPY`, `QQQ` | | +| | `feedId` | Data Streams feed ID (if not provided, derived from symbol/underlying)| `equities:TSLA:mid`, etc. | | + +### Supported Symbols + +The adapter includes built-in support for: + +- **TSLAon** → underlying: TSLA, feedId: equities:TSLA:mid +- **SPYon** → underlying: SPY, feedId: equities:SPY:mid +- **QQQon** → underlying: QQQ, feedId: equities:QQQ:mid + +Additional symbols can be supported by providing `underlying` and `feedId` parameters in the request. + +### Sample Input + +```json +{ + "id": "1", + "data": { + "symbol": "TSLAon" + } +} +``` + +### Sample Output + +```json +{ + "jobRunID": "1", + "data": { + "tokenizedPrice": "239.12345678", + "underlyingPrice": 250.75, + "activeMultiplier": 0.95321, + "symbol": "TSLAon", + "feedId": "equities:TSLA:mid" + }, + "result": "239.12345678", + "statusCode": 200 +} +``` + +## Multiplier Activation Logic + +The adapter supports automatic multiplier transitions: + +1. **Current state**: Uses `sharesMultiplier` from Ondo API +2. **Pending changes**: If `newMultiplier` and `activationDateTime` are present: + - Before activation: uses current multiplier + - After activation: uses new multiplier +3. **Calculation**: `activeMultiplier = (nowUTC >= activationDateTime) ? newMultiplier : currentMultiplier` + +## Data Sources + +- **Underlying Prices**: Chainlink Equities Price Feeds via Data Streams +- **Multipliers**: Ondo Finance API endpoint `GET /v1/assets/{symbol}/market` + +## API Endpoints + +### Ondo Finance API +- **URL**: `https://api.gm.ondo.finance/v1/assets/{symbol}/market` +- **Authentication**: Bearer token via `ONDO_API_KEY` +- **Response**: Contains `primaryMarket.sharesMultiplier` and optional `newMultiplier`/`activationDateTime` + +### Chainlink Data Streams +- **Authentication**: API key/secret via `STREAMS_API_KEY`/`STREAMS_API_SECRET` +- **Data**: Latest equity prices by `feedId` + +## Error Handling + +The adapter includes comprehensive error handling for: +- Invalid or unknown symbols +- Missing environment variables +- API communication failures +- Data validation errors +- Network timeouts and retries + +## Extensibility + +To add new GM tokens: +1. Add symbol mapping to `ASSET_CONFIG` in the code, or +2. Provide `underlying` and `feedId` parameters in requests + +Example for custom symbol: +```json +{ + "data": { + "symbol": "AAPLon", + "underlying": "AAPL", + "feedId": "equities:AAPL:mid" + } +} +``` diff --git a/packages/composites/ondo-gm-tokenized/package.json b/packages/composites/ondo-gm-tokenized/package.json new file mode 100644 index 0000000000..615adf07c4 --- /dev/null +++ b/packages/composites/ondo-gm-tokenized/package.json @@ -0,0 +1,44 @@ +{ + "name": "@chainlink/ondo-gm-tokenized-adapter", + "version": "0.0.0", + "description": "Chainlink Ondo GM Tokenized composite adapter. Calculates tokenized price by combining underlying asset prices from Chainlink Data Streams with multipliers from Ondo API.", + "keywords": [ + "Chainlink", + "LINK", + "Ondo", + "GM Tokens", + "Tokenized Assets", + "Equities", + "Data Streams", + "blockchain", + "oracle", + "composite" + ], + "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" + }, + "dependencies": { + "@chainlink/ea-bootstrap": "workspace:*", + "tslib": "^2.3.1" + }, + "devDependencies": { + "@types/jest": "27.5.2", + "@types/node": "16.11.51", + "typescript": "5.0.4" + } +} diff --git a/packages/composites/ondo-gm-tokenized/schemas/env.json b/packages/composites/ondo-gm-tokenized/schemas/env.json new file mode 100644 index 0000000000..650db17b7d --- /dev/null +++ b/packages/composites/ondo-gm-tokenized/schemas/env.json @@ -0,0 +1,21 @@ +{ + "$id": "https://external-adapters.chainlinklabs.com/schemas/example-composite-adapter.json", + "title": "Chainlink Composite External Adapter for Example Data Provider", + "required": ["ETHEREUM_RPC_URL"], + "type": "object", + "properties": { + "ETHEREUM_RPC_URL": { + "type": "string", + "format": "uri" + }, + "OPTION": { + "default": true, + "type": "boolean" + } + }, + "allOf": [ + { + "$ref": "https://external-adapters.chainlinklabs.com/schemas/ea-bootstrap.json" + } + ] +} diff --git a/packages/composites/ondo-gm-tokenized/src/adapter.ts b/packages/composites/ondo-gm-tokenized/src/adapter.ts new file mode 100644 index 0000000000..2b49856aae --- /dev/null +++ b/packages/composites/ondo-gm-tokenized/src/adapter.ts @@ -0,0 +1,31 @@ +import { + APIEndpoint, + Builder, + ExecuteFactory, + ExecuteWithConfig, +} from '@chainlink/ea-bootstrap' +import { AdapterRequest } from '@chainlink/ea-bootstrap' +import { makeConfig, Config } from './config' +import * as endpoints from './endpoint' + +export const execute: ExecuteWithConfig = async ( + request, + context, + config, +) => { + return Builder.buildSelector( + request, + context, + config, + endpoints, + ) +} + +export const endpointSelector = ( + request: AdapterRequest, +): APIEndpoint => + Builder.selectEndpoint(request, makeConfig(), endpoints) + +export const makeExecute: ExecuteFactory = (config) => { + return async (request, context) => execute(request, context, config || makeConfig()) +} diff --git a/packages/composites/ondo-gm-tokenized/src/config/index.ts b/packages/composites/ondo-gm-tokenized/src/config/index.ts new file mode 100644 index 0000000000..5dc3e474b8 --- /dev/null +++ b/packages/composites/ondo-gm-tokenized/src/config/index.ts @@ -0,0 +1,26 @@ +import { Config as BaseConfig, Requester, util } from '@chainlink/ea-bootstrap' + +export const NAME = 'ONDO_GM_TOKENIZED' +export const DEFAULT_ENDPOINT = 'ondo_gm_tokenized' + +export interface Config extends BaseConfig { + ondoApiKey: string + streamsApiKey?: string + streamsApiSecret?: string + baseUrl: string + streamsBaseUrl?: string +} + +export const makeConfig = (prefix?: string): Config => { + const config = Requester.getDefaultConfig(prefix) + + return { + ...config, + defaultEndpoint: DEFAULT_ENDPOINT, + ondoApiKey: util.getRequiredEnv('ONDO_API_KEY'), + streamsApiKey: util.getRequiredEnv('STREAMS_API_KEY'), + streamsApiSecret: util.getRequiredEnv('STREAMS_API_SECRET'), + baseUrl: util.getEnv('ONDO_BASE_URL') || 'https://api.gm.ondo.finance', + streamsBaseUrl: util.getEnv('STREAMS_BASE_URL') || 'https://api.dataengine.chain.link', + } +} diff --git a/packages/composites/ondo-gm-tokenized/src/dataProvider.ts b/packages/composites/ondo-gm-tokenized/src/dataProvider.ts new file mode 100644 index 0000000000..8187440d77 --- /dev/null +++ b/packages/composites/ondo-gm-tokenized/src/dataProvider.ts @@ -0,0 +1,215 @@ +import { Requester, Logger } from '@chainlink/ea-bootstrap' +import { Config } from './config' +import { AxiosRequestConfig } from '@chainlink/ea-bootstrap' +import crypto from 'crypto' + +/** + * Single report data structure + */ +interface SingleReport { + feedID: string + validFromTimestamp: number + observationsTimestamp: number + fullReport: string +} + +/** + * SingleReportResponse is the response structure for a single report + */ +interface SingleReportResponse { + report: SingleReport +} + +// Types for Ondo API response +export interface OndoMarketData { + primaryMarket: { + sharesMultiplier: string + newMultiplier?: string + activationDateTime?: number + } +} + +export interface DataStreamsResponse { + price: string + timestamp: number +} + +/** + * Fetch multiplier data from Ondo API + */ +export const getOndoMultiplierData = async ( + symbol: string, + config: Config, +): Promise => { + const url = `${config.baseUrl}/v1/assets/${symbol}/market` + console.log(config.ondoApiKey) + const requestConfig: AxiosRequestConfig = { + ...config.api, + url, + method: 'GET', + headers: { + ...config.api?.headers, + 'x-api-key': config.ondoApiKey, + 'Content-Type': 'application/json', + }, + } + + Logger.info(`Fetching Ondo multiplier data for ${symbol} from ${url}`) + + const response = await Requester.request(requestConfig) + Logger.info(`Received Ondo response: ${JSON.stringify(response.data)}`) + return response.data +} + +/** + * Calculate the active multiplier based on current time and activation logic + */ +export const calculateActiveMultiplier = (marketData: OndoMarketData): number => { + const { sharesMultiplier, newMultiplier, activationDateTime } = marketData.primaryMarket + + // Current multiplier (always available) + const currentMultiplier = parseFloat(sharesMultiplier) + + // If no new multiplier or activation time, use current + if (!newMultiplier || !activationDateTime) { + Logger.debug(`Using current multiplier: ${currentMultiplier}`) + return currentMultiplier + } + + // Check if activation time has passed + const nowUTC = Math.floor(Date.now() / 1000) // Current time in epoch seconds + const shouldActivate = nowUTC >= activationDateTime + + const activeMultiplier = shouldActivate ? parseFloat(newMultiplier) : currentMultiplier + + Logger.debug( + `Activation check: now=${nowUTC}, activation=${activationDateTime}, shouldActivate=${shouldActivate}`, + ) + Logger.debug(`Using ${shouldActivate ? 'new' : 'current'} multiplier: ${activeMultiplier}`) + + return activeMultiplier +} + +/** + * Fetch underlying price from Chainlink Data Streams + * Note: This is a placeholder implementation. In practice, you would integrate with + * the actual Chainlink Data Streams API using the feedId. + */ +export const getDataStreamsPrice = async (feedId: string, config: Config): Promise => { + // API connection details + const method = 'GET' + const host = 'api.dataengine.chain.link' + const path = '/api/v1/reports/latest' + const queryString = `?feedID=${feedId}` + const fullPath = path + queryString + const fullUrl = `https://${host}${fullPath}` + const streamsApiKey = config.streamsApiKey + const streamsApiSecret = config.streamsApiSecret + + if (!streamsApiKey || !streamsApiSecret) { + throw new Error( + 'STREAMS_API_KEY and STREAMS_API_SECRET must be set in the environment variables', + ) + } + + const requestConfig: AxiosRequestConfig = { + url: fullUrl, + method, + headers: { + ...generateAuthHeaders(method, fullPath, streamsApiKey, streamsApiSecret), + // Add any other config.api options if needed + }, + ...config, + } + + Logger.info(`Requesting Data Streams price for feedId: ${feedId} from ${fullUrl}`) + + try { + const response = await Requester.request(requestConfig) + // Parse the response + const result = response.data + console.log(result) + // TODO Parse price value from result + return 1 + } catch (error) { + Logger.error(`Error fetching Data Streams price for feedId ${feedId}: ${error}`) + throw error + } +} + +/** + * Calculate the final tokenized price with 8 decimal precision + */ +export const calculateTokenizedPrice = ( + underlyingPrice: number, + activeMultiplier: number, +): string => { + const tokenizedPrice = underlyingPrice * activeMultiplier + + // Round to 8 decimal places and return as string + const result = tokenizedPrice.toFixed(8) + + Logger.debug(`Calculation: ${underlyingPrice} × ${activeMultiplier} = ${result}`) + + return result +} + +/** + * Generates authentication headers for API requests + * @param method - HTTP method + * @param path - Request path with query parameters + * @param apiKey - API key + * @param apiSecret - API secret + * @returns Headers object for the request + */ +function generateAuthHeaders( + method: string, + path: string, + apiKey: string, + apiSecret: string, +): Record { + const { signature, timestamp } = generateHMAC(method, path, '', apiKey, apiSecret) + + return { + Authorization: apiKey, + 'X-Authorization-Timestamp': timestamp.toString(), + 'X-Authorization-Signature-SHA256': signature, + } +} + +/** + * Generates HMAC signature for API authentication + * @param method - HTTP method (GET, POST, etc.) + * @param path - Request path including query parameters + * @param body - Request body (empty string for GET) + * @param apiKey - API key for authentication + * @param apiSecret - API secret for signature generation + * @returns Object containing signature and timestamp + */ +function generateHMAC( + method: string, + path: string, + body: string | Buffer, + apiKey: string, + apiSecret: string, +): { signature: string; timestamp: number } { + // Generate timestamp (milliseconds since Unix epoch) + const timestamp: number = Date.now() + + // Create body hash (empty for GET request) + const bodyHash: string = crypto + .createHash('sha256') + .update(body || '') + .digest('hex') + + // Create string to sign + const stringToSign: string = `${method} ${path} ${bodyHash} ${apiKey} ${timestamp}` + + // Generate HMAC-SHA256 signature + const signature: string = crypto + .createHmac('sha256', apiSecret) + .update(stringToSign) + .digest('hex') + + return { signature, timestamp } +} diff --git a/packages/composites/ondo-gm-tokenized/src/endpoint/index.ts b/packages/composites/ondo-gm-tokenized/src/endpoint/index.ts new file mode 100644 index 0000000000..a1b13d222f --- /dev/null +++ b/packages/composites/ondo-gm-tokenized/src/endpoint/index.ts @@ -0,0 +1,5 @@ +import type { TInputParameters as TokenizedInputParameters } from './tokenized' + +export type TInputParameters = TokenizedInputParameters + +export * as tokenized from './tokenized' diff --git a/packages/composites/ondo-gm-tokenized/src/endpoint/tokenized.ts b/packages/composites/ondo-gm-tokenized/src/endpoint/tokenized.ts new file mode 100644 index 0000000000..4d7f827881 --- /dev/null +++ b/packages/composites/ondo-gm-tokenized/src/endpoint/tokenized.ts @@ -0,0 +1,104 @@ +import { ExecuteWithConfig, InputParameters, Requester, Validator } from '@chainlink/ea-bootstrap' +import { Config } from '../config' +import { + getOndoMultiplierData, + calculateActiveMultiplier, + getDataStreamsPrice, + calculateTokenizedPrice, +} from '../dataProvider' + +export const supportedEndpoints = ['tokenized'] + +export const endpointResultPaths = { + tokenizedPrice: 'tokenizedPrice', +} + +export interface ResponseSchema { + tokenizedPrice: string + underlyingPrice: number + activeMultiplier: number + symbol: string + feedId: string +} + +export type TInputParameters = { + symbol: string + underlying?: string + feedId?: string +} + +export const inputParameters: InputParameters = { + symbol: { + description: 'GM token symbol (e.g., TSLAon, SPYon, QQQon)', + required: true, + type: 'string', + }, + underlying: { + description: + 'The base ticker (e.g., TSLA, SPY, QQQ). If not provided, will be derived from symbol', + required: false, + type: 'string', + }, + feedId: { + description: + 'Data Streams feed ID for the underlying (e.g., equities:TSLA:mid). If not provided, will be derived from symbol', + required: false, + type: 'string', + }, +} + +export const execute: ExecuteWithConfig = async (request, _context, config) => { + const validator = new Validator(request, inputParameters) + + const jobRunID = validator.validated.id + const symbol = validator.validated.data.symbol + + const underlying = validator.validated.data.underlying + const feedId = validator.validated.data.feedId + + if (!underlying || !feedId) { + throw new Error('Both underlying and feedId must be provided or derivable from symbol') + } + + try { + // Step 1: Fetch multiplier data from Ondo API + console.log(`Fetching multiplier data for symbol: ${symbol}`) + const marketData = await getOndoMultiplierData(symbol, config) + + // Step 2: Calculate active multiplier + console.log(`Calculating active multiplier for symbol: ${symbol}`) + const activeMultiplier = calculateActiveMultiplier(marketData) + + // Step 3: Fetch underlying price from Data Streams + console.log(`Fetching underlying price for feedId: ${feedId}`) + const underlyingPrice = await getDataStreamsPrice(feedId, config) + + // Step 4: Calculate tokenized price + console.log(`Calculating tokenized price for symbol: ${symbol}`) + const tokenizedPrice = calculateTokenizedPrice(underlyingPrice, activeMultiplier) + + const responseData: ResponseSchema = { + tokenizedPrice, + underlyingPrice, + activeMultiplier, + symbol, + feedId, + } + + const response = { + data: responseData, + status: 200, + statusText: 'OK', + headers: {}, + config: {}, + } + + return Requester.success( + jobRunID, + Requester.withResult(response, tokenizedPrice), + config.verbose, + ) + } catch (error) { + throw new Error(`Failed to process tokenized price for ${symbol}: ${error}`) + } +} diff --git a/packages/composites/ondo-gm-tokenized/src/index.ts b/packages/composites/ondo-gm-tokenized/src/index.ts new file mode 100644 index 0000000000..f66570010f --- /dev/null +++ b/packages/composites/ondo-gm-tokenized/src/index.ts @@ -0,0 +1,8 @@ +import { expose } from '@chainlink/ea-bootstrap' +import { makeExecute } from './adapter' +import { makeConfig, NAME } from './config' + +const adapterContext = { name: NAME } + +const { server } = expose(adapterContext, makeExecute()) +export { NAME, makeExecute, makeConfig, server } diff --git a/packages/composites/ondo-gm-tokenized/test-payload.json b/packages/composites/ondo-gm-tokenized/test-payload.json new file mode 100644 index 0000000000..abaf1fdd18 --- /dev/null +++ b/packages/composites/ondo-gm-tokenized/test-payload.json @@ -0,0 +1,30 @@ +{ + "requests": [ + { + "id": "1", + "data": { + "symbol": "TSLAon" + } + }, + { + "id": "2", + "data": { + "symbol": "SPYon" + } + }, + { + "id": "3", + "data": { + "symbol": "QQQon" + } + }, + { + "id": "4", + "data": { + "symbol": "CustomToken", + "underlying": "AAPL", + "feedId": "equities:AAPL:mid" + } + } + ] +} diff --git a/packages/composites/ondo-gm-tokenized/test/integration/README.md b/packages/composites/ondo-gm-tokenized/test/integration/README.md new file mode 100644 index 0000000000..addcb65f5c --- /dev/null +++ b/packages/composites/ondo-gm-tokenized/test/integration/README.md @@ -0,0 +1 @@ +See `packages/source/curve/test/integration` and `packages/composite/synth-index/test/integration` for reference diff --git a/packages/composites/ondo-gm-tokenized/tsconfig.json b/packages/composites/ondo-gm-tokenized/tsconfig.json new file mode 100644 index 0000000000..bc3e82c0b0 --- /dev/null +++ b/packages/composites/ondo-gm-tokenized/tsconfig.json @@ -0,0 +1,13 @@ +{ + "extends": "../../tsconfig.base.json", + "compilerOptions": { + "outDir": "dist", + "rootDir": "src" + }, + "include": ["src/**/*"], + "exclude": ["dist", "**/*.spec.ts", "**/*.test.ts"], + "references": [ + { "path": "../../core/test-helpers" }, + { "path": "../../core/bootstrap" } + ] +} diff --git a/packages/composites/ondo-gm-tokenized/tsconfig.test.json b/packages/composites/ondo-gm-tokenized/tsconfig.test.json new file mode 100644 index 0000000000..e3de28cb5c --- /dev/null +++ b/packages/composites/ondo-gm-tokenized/tsconfig.test.json @@ -0,0 +1,7 @@ +{ + "extends": "../../tsconfig.base.json", + "include": ["src/**/*", "**/test", "src/**/*.json"], + "compilerOptions": { + "noEmit": true + } +} diff --git a/packages/tsconfig.json b/packages/tsconfig.json index 04b7005666..5817251912 100644 --- a/packages/tsconfig.json +++ b/packages/tsconfig.json @@ -62,6 +62,9 @@ { "path": "./composites/nftx" }, + { + "path": "./composites/ondo-gm-tokenized" + }, { "path": "./composites/outlier-detection" }, diff --git a/packages/tsconfig.test.json b/packages/tsconfig.test.json index 31374c3fa2..c6859557ce 100644 --- a/packages/tsconfig.test.json +++ b/packages/tsconfig.test.json @@ -62,6 +62,9 @@ { "path": "./composites/nftx/tsconfig.test.json" }, + { + "path": "./composites/ondo-gm-tokenized/tsconfig.test.json" + }, { "path": "./composites/outlier-detection/tsconfig.test.json" }, diff --git a/test-date-calc.js b/test-date-calc.js new file mode 100644 index 0000000000..d629d72668 --- /dev/null +++ b/test-date-calc.js @@ -0,0 +1,53 @@ +// Simple test to see what dates the adapter calculates +function calculateTargetDate() { + // Get current date in London time + const now = new Date() + const londonTime = new Date(now.toLocaleString('en-US', { timeZone: 'Europe/London' })) + + // Check if it's before 4 PM London time (16:00) + const isBeforeFileGeneration = londonTime.getHours() < 16 + + // Start with current London date, but go back one day if before 4 PM + const targetDate = new Date(londonTime) + if (isBeforeFileGeneration) { + targetDate.setDate(londonTime.getDate() - 1) + } + + // Get the day of the week for the target date (0 = Sunday, 1 = Monday, ..., 6 = Saturday) + const dayOfWeek = targetDate.getDay() + + // If target date falls on Saturday (6) or Sunday (0), fall back to Friday's date + if (dayOfWeek === 0) { + // Sunday + targetDate.setDate(targetDate.getDate() - 2) // Go back 2 days to Friday + } else if (dayOfWeek === 6) { + // Saturday + targetDate.setDate(targetDate.getDate() - 1) // Go back 1 day to Friday + } + + // Format day, month, and year with leading zeros + const currentDay = targetDate.getDate().toString().padStart(2, '0') + const currentMonth = (targetDate.getMonth() + 1).toString().padStart(2, '0') // getMonth() returns 0-11 + const currentYear = targetDate.getFullYear().toString().slice(-2) // Get last 2 digits of year + + console.log('Current time:', now.toISOString()) + console.log('London time:', londonTime.toISOString()) + console.log('Target date:', targetDate.toISOString()) + console.log('Day of week:', dayOfWeek) + console.log('Is before 4PM:', isBeforeFileGeneration) + console.log('Date parts:', { currentDay, currentMonth, currentYear }) + + // Build file names + const ftseFile = `ukallv${currentDay}${currentMonth}.csv` + const russellFile = `daily_values_russell_${currentYear}${currentMonth}${currentDay}.CSV` + + console.log('FTSE file:', ftseFile) + console.log('Russell file:', russellFile) + + return { + ftse: `/data/valuation/uk_all_share/${ftseFile}`, + russell: `/data/Returns_and_Values/Russell_US_Indexes_Daily_Index_Values_Real_Time_TXT/${russellFile}`, + } +} + +calculateTargetDate() diff --git a/yarn.lock b/yarn.lock index 0f1e03625f..e877b8d943 100644 --- a/yarn.lock +++ b/yarn.lock @@ -4400,6 +4400,19 @@ __metadata: languageName: unknown linkType: soft +"@chainlink/ondo-gm-tokenized-adapter@workspace:packages/composites/ondo-gm-tokenized": + version: 0.0.0-use.local + resolution: "@chainlink/ondo-gm-tokenized-adapter@workspace:packages/composites/ondo-gm-tokenized" + dependencies: + "@chainlink/ea-bootstrap": "workspace:*" + "@chainlink/example-source-adapter": "workspace:*" + "@types/jest": 27.5.2 + "@types/node": 16.11.51 + tslib: ^2.3.1 + typescript: 5.0.4 + languageName: unknown + linkType: soft + "@chainlink/openexchangerates-adapter@workspace:packages/sources/openexchangerates": version: 0.0.0-use.local resolution: "@chainlink/openexchangerates-adapter@workspace:packages/sources/openexchangerates"