diff --git a/src/typescript/frontend/src/app/candlesticks/route.ts b/src/typescript/frontend/src/app/candlesticks/route.ts index dfcd22274..5a683e67d 100644 --- a/src/typescript/frontend/src/app/candlesticks/route.ts +++ b/src/typescript/frontend/src/app/candlesticks/route.ts @@ -1,110 +1,34 @@ -// cspell:word timespan - -import { type AnyNumberString, getPeriodStartTimeFromTime, toPeriod } from "@sdk/index"; +import { type Period, toPeriod } from "@sdk/index"; import { parseInt } from "lodash"; import { type NextRequest } from "next/server"; import { type CandlesticksSearchParams, - type GetCandlesticksParams, getPeriodDurationSeconds, HISTORICAL_CACHE_DURATION, - indexToParcelEndDate, - indexToParcelStartDate, isValidCandlesticksSearchParams, - jsonStrAppend, NORMAL_CACHE_DURATION, - PARCEL_SIZE, - toIndex, } from "./utils"; -import { unstable_cache } from "next/cache"; import { getLatestProcessedEmojicoinTimestamp } from "@sdk/indexer-v2/queries/utils"; -import { parseJSON, stringifyJSON } from "utils"; -import { fetchMarketRegistration, fetchPeriodicEventsSince } from "@/queries/market"; - -/** - * @property `data` the stringified version of {@link CandlesticksDataType}. - * @property `count` the number of rows returned. - */ -type GetCandlesticksResponse = { - data: string; - count: number; -}; +import { fetchPeriodicEventsTo, tryFetchMarketRegistration } from "@/queries/market"; +import { Parcel } from "lib/parcel"; -type CandlesticksDataType = Awaited>; +type CandlesticksDataType = Awaited>; -const getCandlesticks = async (params: GetCandlesticksParams) => { - const { marketID, index, period } = params; +const getCandlesticksParcel = async ( + { to, count }: { to: number; count: number }, + query: { marketID: number; period: Period } +) => { + const endDate = new Date(to * 1000); - const start = indexToParcelStartDate(index, period); - - const periodDurationMilliseconds = getPeriodDurationSeconds(period) * 1000; - const timespan = periodDurationMilliseconds * PARCEL_SIZE; - const end = new Date(start.getTime() + timespan); - - // PARCEL_SIZE determines the max number of rows, so we don't need to pass a `LIMIT` value. - // `start` and `end` determine the level of pagination, so no need to specify `offset` either. - const data = await fetchPeriodicEventsSince({ - marketID, - period, - start, - end, + const data = await fetchPeriodicEventsTo({ + ...query, + end: endDate, + amount: count, }); - return { - data: stringifyJSON(data), - count: data.length, - }; + return data; }; -/** - * Returns the market registration event for a market if it exists. - * - * If it doesn't exist, it throws an error so that the value isn't cached in the - * `unstable_cache` call. - * - * @see {@link getCachedMarketRegistrationMs} - */ -const getMarketRegistrationMs = async (marketID: AnyNumberString) => - fetchMarketRegistration({ marketID }).then((res) => { - if (res) { - return Number(res.market.time / 1000n); - } - throw new Error("Market is not yet registered."); - }); - -const getCachedMarketRegistrationMs = unstable_cache( - getMarketRegistrationMs, - ["market-registrations"], - { - revalidate: HISTORICAL_CACHE_DURATION, - } -); - -/** - * Fetch all of the parcels of candlesticks that have completely ended. - * The only difference between this and {@link getNormalCachedCandlesticks} is the cache tag and - * thus how long the data is cached for. - */ -const getHistoricCachedCandlesticks = unstable_cache(getCandlesticks, ["candlesticks-historic"], { - revalidate: HISTORICAL_CACHE_DURATION, -}); - -/** - * Fetch all candlestick parcels that haven't completed yet. - * The only difference between this and {@link getHistoricCachedCandlesticks} is the cache tag and - * thus how long the data is cached for. - */ -const getNormalCachedCandlesticks = unstable_cache(getCandlesticks, ["candlesticks"], { - revalidate: NORMAL_CACHE_DURATION, -}); - -const getCachedLatestProcessedEmojicoinTimestamp = unstable_cache( - getLatestProcessedEmojicoinTimestamp, - ["processor-timestamp"], - { revalidate: 5 } -); - -/* eslint-disable-next-line import/no-unused-modules */ export async function GET(request: NextRequest) { const searchParams = request.nextUrl.searchParams; const params: CandlesticksSearchParams = { @@ -122,70 +46,26 @@ export async function GET(request: NextRequest) { const to = parseInt(params.to); const period = toPeriod(params.period); const countBack = parseInt(params.countBack); - const numParcels = parseInt(params.amount); - - const index = toIndex(to, period); - - // Ensure that the last start date as calculated per the search params is valid. - // This is specifically the last parcel's start date- aka the last parcel's first candlestick's - // start time. - const lastParcelStartDate = indexToParcelStartDate(index + numParcels - 1, period); - if (lastParcelStartDate > new Date()) { - return new Response("The last parcel's start date cannot be later than the current time.", { - status: 400, - }); - } - - let data: string = "[]"; - - const processorTimestamp = new Date(await getCachedLatestProcessedEmojicoinTimestamp()); - let totalCount = 0; - let i = 0; + const queryHelper = new Parcel< + CandlesticksDataType[number], + { marketID: number; period: Period } + >({ + parcelSize: 500, + normalRevalidate: NORMAL_CACHE_DURATION, + historicRevalidate: HISTORICAL_CACHE_DURATION, + fetchHistoricThreshold: () => getLatestProcessedEmojicoinTimestamp().then((r) => r.getTime()), + fetchFirst: (query) => tryFetchMarketRegistration(query.marketID), + cacheKey: "candlesticks", + getKey: (s) => Number(s.periodicMetadata.startTime / 1000n / 1000n), + fetchFn: getCandlesticksParcel, + step: getPeriodDurationSeconds(period), + }); - let registrationPeriodBoundaryStart: Date; try { - registrationPeriodBoundaryStart = await getCachedMarketRegistrationMs(marketID).then( - (time) => new Date(Number(getPeriodStartTimeFromTime(time, period))) - ); - } catch { - return new Response("Market has not been registered yet.", { status: 400 }); + const data = await queryHelper.getUnparsedData(to, countBack, { marketID, period }); + return new Response(data); + } catch (e) { + return new Response(e as string, { status: 400 }); } - - while (totalCount <= countBack) { - const localIndex = index - i; - const endDate = indexToParcelEndDate(localIndex, period); - let res: GetCandlesticksResponse; - if (endDate < processorTimestamp) { - res = await getHistoricCachedCandlesticks({ - marketID, - index: localIndex, - period, - }); - } else { - res = await getNormalCachedCandlesticks({ - marketID, - index: localIndex, - period, - }); - } - - if (i == 0) { - const parsed = parseJSON(res.data); - const filtered = parsed.filter( - (val) => val.periodicMetadata.startTime < BigInt(to) * 1_000_000n - ); - totalCount += filtered.length; - data = jsonStrAppend(data, stringifyJSON(filtered)); - } else { - totalCount += res.count; - data = jsonStrAppend(data, res.data); - } - if (endDate < registrationPeriodBoundaryStart) { - break; - } - i++; - } - - return new Response(data); } diff --git a/src/typescript/frontend/src/app/candlesticks/utils.ts b/src/typescript/frontend/src/app/candlesticks/utils.ts index e96bd5a07..7807dd941 100644 --- a/src/typescript/frontend/src/app/candlesticks/utils.ts +++ b/src/typescript/frontend/src/app/candlesticks/utils.ts @@ -1,47 +1,9 @@ import { isPeriod, type Period, PeriodDuration, periodEnumToRawDuration } from "@sdk/index"; import { isNumber } from "utils"; -/** - * Parcel size is the amount of candlestick periods that will be in a single parcel. - * That is, a parcel for 1m candlesticks will be `PARCEL_SIZE` minutes of time. - * - * Note that this is *NOT* the number of candlesticks in the database- as there may be gaps in the - * on-chain data (and thus the database). - * - * More specifically, each parcel will have anywhere from 0 to PARCEL_SIZE number of candlesticks - * and will always span `PARCEL_SIZE` candlesticks/periods worth of time. - */ -export const PARCEL_SIZE = 500; - -export const indexToParcelStartDate = (index: number, period: Period): Date => - new Date((PARCEL_SIZE * (index * periodEnumToRawDuration(period))) / 1000); -export const indexToParcelEndDate = (index: number, period: Period): Date => - new Date((PARCEL_SIZE * ((index + 1) * periodEnumToRawDuration(period))) / 1000); - export const getPeriodDurationSeconds = (period: Period) => (periodEnumToRawDuration(period) / PeriodDuration.PERIOD_1M) * 60; -export const toIndex = (end: number, period: Period): number => { - const periodDuration = getPeriodDurationSeconds(period); - const parcelDuration = periodDuration * PARCEL_SIZE; - - const index = Math.floor(end / parcelDuration); - - return index; -}; - -export const jsonStrAppend = (a: string, b: string): string => { - if (a === "[]") return b; - if (b === "[]") return a; - return `${a.substring(0, a.length - 1)},${b.substring(1)}`; -}; - -export type GetCandlesticksParams = { - marketID: number; - index: number; - period: Period; -}; - /** * The search params used in the `GET` request at `candlesticks/api`. * diff --git a/src/typescript/frontend/src/app/chats/route.ts b/src/typescript/frontend/src/app/chats/route.ts new file mode 100644 index 000000000..7af077a61 --- /dev/null +++ b/src/typescript/frontend/src/app/chats/route.ts @@ -0,0 +1,59 @@ +import { fetchChatEvents, tryFetchFirstChatEvent } from "@/queries/market"; +import { Parcel } from "lib/parcel"; +import type { NextRequest } from "next/server"; +import { isNumber } from "utils"; + +type ChatSearchParams = { + marketID: string | null; + toMarketNonce: string | null; +}; + +export type ValidChatSearchParams = { + marketID: string; + toMarketNonce: string; +}; + +const isValidChatSearchParams = (params: ChatSearchParams): params is ValidChatSearchParams => { + const { marketID, toMarketNonce } = params; + // prettier-ignore + return ( + marketID !== null && isNumber(marketID) && + toMarketNonce !== null && isNumber(toMarketNonce) + ); +}; + +type Chat = Awaited>[number]; + +export async function GET(request: NextRequest) { + const searchParams = request.nextUrl.searchParams; + const params: ChatSearchParams = { + marketID: searchParams.get("marketID"), + toMarketNonce: searchParams.get("toMarketNonce"), + }; + + if (!isValidChatSearchParams(params)) { + return new Response("Invalid chat search params.", { status: 400 }); + } + + const marketID = Number(params.marketID); + const toMarketNonce = Number(params.toMarketNonce); + + const queryHelper = new Parcel({ + parcelSize: 20, + normalRevalidate: 5, + historicRevalidate: 365 * 24 * 60 * 60, + fetchHistoricThreshold: (query) => + fetchChatEvents({ marketID: query.marketID, amount: 1 }).then((r) => + Number(r[0].market.marketNonce) + ), + fetchFirst: (query) => tryFetchFirstChatEvent(query.marketID), + cacheKey: "chats", + getKey: (s) => Number(s.market.marketNonce), + fetchFn: ({ to, count }, { marketID }) => + fetchChatEvents({ marketID, toMarketNonce: to, amount: count }), + }); + + const res = await queryHelper.getUnparsedData(toMarketNonce, 50, { marketID }); + + return new Response(res); +} diff --git a/src/typescript/frontend/src/app/trades/route.ts b/src/typescript/frontend/src/app/trades/route.ts new file mode 100644 index 000000000..b91d06158 --- /dev/null +++ b/src/typescript/frontend/src/app/trades/route.ts @@ -0,0 +1,59 @@ +import { fetchSwapEvents, tryFetchFirstSwapEvent } from "@/queries/market"; +import { Parcel } from "lib/parcel"; +import type { NextRequest } from "next/server"; +import { isNumber } from "utils"; + +type SwapSearchParams = { + marketID: string | null; + toMarketNonce: string | null; +}; + +export type ValidSwapSearchParams = { + marketID: string; + toMarketNonce: string; +}; + +const isValidSwapSearchParams = (params: SwapSearchParams): params is ValidSwapSearchParams => { + const { marketID, toMarketNonce } = params; + // prettier-ignore + return ( + marketID !== null && isNumber(marketID) && + toMarketNonce !== null && isNumber(toMarketNonce) + ); +}; + +type Swap = Awaited>[number]; + +export async function GET(request: NextRequest) { + const searchParams = request.nextUrl.searchParams; + const params: SwapSearchParams = { + marketID: searchParams.get("marketID"), + toMarketNonce: searchParams.get("toMarketNonce"), + }; + + if (!isValidSwapSearchParams(params)) { + return new Response("Invalid swap search params.", { status: 400 }); + } + + const marketID = Number(params.marketID); + const toMarketNonce = Number(params.toMarketNonce); + + const queryHelper = new Parcel({ + parcelSize: 20, + normalRevalidate: 5, + historicRevalidate: 365 * 24 * 60 * 60, + fetchHistoricThreshold: (query) => + fetchSwapEvents({ marketID: query.marketID, amount: 1 }).then((r) => + Number(r[0].market.marketNonce) + ), + fetchFirst: (query) => tryFetchFirstSwapEvent(query.marketID), + cacheKey: "swaps", + getKey: (s) => Number(s.market.marketNonce), + fetchFn: ({ to, count }, { marketID }) => + fetchSwapEvents({ marketID, toMarketNonce: to, amount: count }), + }); + + const res = await queryHelper.getUnparsedData(toMarketNonce, 20, { marketID }); + + return new Response(res); +} diff --git a/src/typescript/frontend/src/lib/parcel.ts b/src/typescript/frontend/src/lib/parcel.ts new file mode 100644 index 000000000..9708657af --- /dev/null +++ b/src/typescript/frontend/src/lib/parcel.ts @@ -0,0 +1,151 @@ +import { unstable_cache } from "next/cache"; +import { parseJSON, stringifyJSON } from "utils"; + +const jsonStrAppend = (a: string, b: string): string => { + if (a === "[]") return b; + if (b === "[]") return a; + return `${a.substring(0, a.length - 1)},${b.substring(1)}`; +}; + +export type ParcelQueryParameters = { + count: number; + to: number; +}; + +type CachedWrapperReturn = { + stringifiedData: string; + length: number; + first: number; +}; + +const cachedWrapper = async ( + params: ParcelQueryParameters, + query: Q, + fetchFn: (params: ParcelQueryParameters, query: Q) => Promise, + getKey: (s: S) => number +): Promise => { + const data = await fetchFn(params, query); + return { + stringifiedData: stringifyJSON(data), + length: data.length, + first: getKey(data[data.length - 1]), + }; +}; + +// Parcel data query helper. +// +// This class is a query helper to query cached parcels. +// +// The helper separates data in parcels, making data easier to cache and query in batch. +export class Parcel { + private _parcelSize: number; + private _normalFetch: (params: ParcelQueryParameters, query: Q) => Promise; + private _historicFetch: (params: ParcelQueryParameters, query: Q) => Promise; + private _fetchHistoricThreshold: (query: Q) => Promise; + private _fetchFirst: (query: Q) => Promise; + private _getKey: (s: S) => number; + private _step: number; + private _cacheKey: string; + + constructor({ + parcelSize, + cacheKey, + fetchFn, + normalRevalidate, + historicRevalidate, + fetchHistoricThreshold, + fetchFirst, + getKey, + step, + }: { + parcelSize: number; + cacheKey: string; + fetchFn: (params: ParcelQueryParameters, query: Q) => Promise; + normalRevalidate: number; + historicRevalidate: number; + fetchHistoricThreshold: (query: Q) => Promise; + fetchFirst: (query: Q) => Promise; + getKey: (s: S) => number; + step?: number; + }) { + this._parcelSize = parcelSize; + this._normalFetch = unstable_cache( + (params: ParcelQueryParameters, query: Q) => cachedWrapper(params, query, fetchFn, getKey), + ["parcel", cacheKey, "normal", parcelSize.toString()], + { revalidate: normalRevalidate } + ); + this._historicFetch = unstable_cache( + (params: ParcelQueryParameters, query: Q) => cachedWrapper(params, query, fetchFn, getKey), + ["parcel", cacheKey, "historic", parcelSize.toString()], + { revalidate: historicRevalidate } + ); + this._fetchHistoricThreshold = unstable_cache( + (query: Q) => fetchHistoricThreshold(query), + ["parcel", cacheKey, "threshold"], + { revalidate: 2 } + ); + this._fetchFirst = unstable_cache( + (query: Q) => fetchFirst(query), + ["parcel", cacheKey, "first"], + { revalidate: 365 * 24 * 60 * 60 } + ); + this._getKey = getKey; + this._step = step ?? 1; + this._cacheKey = cacheKey; + } + + private parcelize(to: number): number { + return Math.floor(to / this._step / this._parcelSize); + } + + private unparcelize(parcel: number): number { + return parcel * this._step * this._parcelSize; + } + + async getData(to: number, count: number, query: Q): Promise { + return parseJSON(await this.getUnparsedData(to, count, query)); + } + + async getUnparsedData(to: number, count: number, query: Q): Promise { + let first: number; + try { + first = await this._fetchFirst(query); + } catch (e) { + console.warn( + "Could not get first event. This either means that no events have yet been emmited for this data type, or that the fetch first event function is wrong.", + e + ); + return "[]"; + } + if (to < first) { + return "[]"; + } + const lastParcel = this.parcelize(to); + let dataCount = 0; + const historicThreshold = await this._fetchHistoricThreshold(query); + let lastParcelData: CachedWrapperReturn; + const end = this.unparcelize(lastParcel + 1); + if (this.unparcelize(lastParcel + 1) > historicThreshold) { + lastParcelData = await this._normalFetch({ to: end, count: this._parcelSize }, query); + } else { + lastParcelData = await this._historicFetch({ to: end, count: this._parcelSize }, query); + } + const parsedLastParcel = parseJSON(lastParcelData.stringifiedData); + const relevantData = parsedLastParcel.filter((s) => this._getKey(s) < to); + dataCount += relevantData.length; + let dataString = lastParcelData.stringifiedData; + let parcel = this.parcelize(lastParcelData.first) - 1; + for (; dataCount < count && this.unparcelize(parcel) > first; ) { + const start = this.unparcelize(parcel); + const params = { + to: start + this._parcelSize, + count: this._parcelSize, + }; + const parcelData = await this._historicFetch(params, query); + dataCount += parcelData.length; + dataString = jsonStrAppend(dataString, parcelData.stringifiedData); + parcel = this.parcelize(parcelData.first) - 1; + } + return dataString; + } +} diff --git a/src/typescript/sdk/src/indexer-v2/queries/app/market.ts b/src/typescript/sdk/src/indexer-v2/queries/app/market.ts index 2afd7c107..8141a6e12 100644 --- a/src/typescript/sdk/src/indexer-v2/queries/app/market.ts +++ b/src/typescript/sdk/src/indexer-v2/queries/app/market.ts @@ -2,7 +2,7 @@ if (process.env.NODE_ENV !== "test") { require("server-only"); } -import { LIMIT, ORDER_BY } from "../../../queries"; +import { ORDER_BY } from "../../../queries"; import { type AnyNumberString } from "../../../types"; import { TableName } from "../../types/json-types"; import { postgrest, toQueryArray } from "../client"; @@ -14,32 +14,103 @@ import { toPeriodicStateEventModel, toSwapEventModel, } from "../../types"; -import { type PeriodicStateEventQueryArgs, type MarketStateQueryArgs } from "../../types/common"; +import type { + PeriodicStateEventToQueryArgs, + PeriodicStateEventQueryArgs, +} from "../../types/common"; import { type SymbolEmoji } from "../../../emoji_data/types"; const selectSwapsByMarketID = ({ marketID, - page = 1, - pageSize = LIMIT, -}: { marketID: AnyNumberString } & MarketStateQueryArgs) => - postgrest + toMarketNonce = null, + amount = 20, + order = "DESC", +}: { + marketID: AnyNumberString; + toMarketNonce?: number | null; + amount?: number; + order?: keyof typeof ORDER_BY; +}) => { + if (toMarketNonce !== null) { + return postgrest + .from(TableName.SwapEvents) + .select("*") + .eq("market_id", marketID) + .lte("market_nonce", toMarketNonce) + .order("market_nonce", ORDER_BY[order]) + .limit(amount); + } + return postgrest + .from(TableName.SwapEvents) + .select("*") + .eq("market_id", marketID) + .order("market_nonce", ORDER_BY[order]) + .limit(amount); +}; + +const selectSwapsByNonce = ({ + marketID, + fromMarketNonce, + toMarketNonce, +}: { + marketID: AnyNumberString; + fromMarketNonce: number; + toMarketNonce: number; +}) => { + return postgrest .from(TableName.SwapEvents) .select("*") + .lt("market_nonce", toMarketNonce) + .gte("market_nonce", fromMarketNonce) .eq("market_id", marketID) - .order("market_nonce", ORDER_BY.DESC) - .range((page - 1) * pageSize, page * pageSize - 1); + .order("market_nonce", ORDER_BY.DESC); +}; const selectChatsByMarketID = ({ marketID, - page = 1, - pageSize = LIMIT, -}: { marketID: AnyNumberString } & MarketStateQueryArgs) => - postgrest + toMarketNonce = null, + amount = 20, + order = "DESC", +}: { + marketID: AnyNumberString; + toMarketNonce?: number | null; + amount?: number; + order?: keyof typeof ORDER_BY; +}) => { + if (toMarketNonce !== null) { + return postgrest + .from(TableName.ChatEvents) + .select("*") + .eq("market_id", marketID) + .lte("market_nonce", toMarketNonce) + .order("market_nonce", ORDER_BY[order]) + .limit(amount); + } + return postgrest + .from(TableName.ChatEvents) + .select("*") + .eq("market_id", marketID) + .order("market_nonce", ORDER_BY[order]) + .limit(amount); +}; + +const selectChatsByNonce = ({ + marketID, + fromMarketNonce, + toMarketNonce, +}: { + marketID: AnyNumberString; + fromMarketNonce: number; + toMarketNonce: number; +}) => { + return postgrest .from(TableName.ChatEvents) .select("*") + .lt("market_nonce", toMarketNonce) + .gte("market_nonce", fromMarketNonce) .eq("market_id", marketID) - .order("market_nonce", ORDER_BY.DESC) - .range((page - 1) * pageSize, page * pageSize - 1); + .order("market_nonce", ORDER_BY.DESC); +}; // This query uses `offset` instead of `page` because the periodic state events query requires // more granular pagination due to the requirements of the private TradingView charting library. @@ -60,6 +131,23 @@ const selectPeriodicEventsSince = ({ return query; }; +const selectPeriodicEventsTo = ({ + marketID, + period, + end, + amount, +}: PeriodicStateEventToQueryArgs) => { + const query = postgrest + .from(TableName.PeriodicStateEvents) + .select("*") + .eq("market_id", marketID) + .eq("period", period) + .lt("start_time", end.toISOString()) + .limit(amount) + .order("start_time", ORDER_BY.DESC); + return query; +}; + const selectMarketState = ({ searchEmojis }: { searchEmojis: SymbolEmoji[] }) => postgrest .from(TableName.MarketState) @@ -77,13 +165,40 @@ const selectMarketRegistration = ({ marketID }: { marketID: AnyNumberString }) = .single(); export const fetchSwapEvents = queryHelper(selectSwapsByMarketID, toSwapEventModel); +export const fetchSwapEventsByNonce = queryHelper(selectSwapsByNonce, toSwapEventModel); export const fetchChatEvents = queryHelper(selectChatsByMarketID, toChatEventModel); +export const fetchChatEventsByNonce = queryHelper(selectChatsByNonce, toChatEventModel); export const fetchPeriodicEventsSince = queryHelper( selectPeriodicEventsSince, toPeriodicStateEventModel ); +export const fetchPeriodicEventsTo = queryHelper(selectPeriodicEventsTo, toPeriodicStateEventModel); export const fetchMarketState = queryHelperSingle(selectMarketState, toMarketStateModel); export const fetchMarketRegistration = queryHelperSingle( selectMarketRegistration, toMarketRegistrationEventModel ); + +export const tryFetchMarketRegistration = async (marketID: AnyNumberString) => + fetchMarketRegistration({ marketID }).then((res) => { + if (res) { + return Number(res.market.time / 1000n / 1000n); + } + throw new Error("Market is not yet registered."); + }); + +export const tryFetchFirstChatEvent = async (marketID: AnyNumberString) => + fetchChatEvents({ marketID, amount: 1, order: "ASC" }).then((res) => { + if (res && res[0]) { + return Number(res[0].market.marketNonce); + } + throw new Error("Market is not yet registered."); + }); + +export const tryFetchFirstSwapEvent = async (marketID: AnyNumberString) => + fetchSwapEvents({ marketID, amount: 1, order: "ASC" }).then((res) => { + if (res && res[0]) { + return Number(res[0].market.marketNonce); + } + throw new Error("Market is not yet registered."); + }); diff --git a/src/typescript/sdk/src/indexer-v2/types/common.ts b/src/typescript/sdk/src/indexer-v2/types/common.ts index 4f4ed1679..d8a20e567 100644 --- a/src/typescript/sdk/src/indexer-v2/types/common.ts +++ b/src/typescript/sdk/src/indexer-v2/types/common.ts @@ -30,3 +30,10 @@ export type PeriodicStateEventQueryArgs = { end: Date; period: Period; } & Omit; + +export type PeriodicStateEventToQueryArgs = { + marketID: AnyNumberString; + amount: number; + end: Date; + period: Period; +} & Omit;