From 555f33e7aa5127215d5227b8608c9930065a5c7c Mon Sep 17 00:00:00 2001 From: Austin Woetzel <30289932+AustinWoetzel@users.noreply.github.com> Date: Thu, 18 Apr 2024 09:48:54 -0500 Subject: [PATCH] Batch Query - node health validation by block height (#128) * test: mocks troubleshooting * feat: got something working * test: batch query tests include node health check * chore: resolve type issues related to block height batch query * feat: clean up types and add to docs * docs: notes on callback function * chore: missed a comma * feat: batch query height implementation * feat: node health validation on stkd scrt query * feat: remove unneeded check due to typescript requiring property * chore: add changeset * refactor: change to min block height validation options name * docs: update docs site for name change * docs: missed save on one line for last commit * feat: callback fires only once, remove extra first --- .changeset/rare-jobs-enjoy.md | 5 ++ docs/contracts.md | 2 +- docs/queries/batch-query.md | 22 ++++- src/contracts/services/batchQuery.test.ts | 90 +++++++++++++++++++ src/contracts/services/batchQuery.ts | 78 ++++++++++++---- src/contracts/services/derivativeScrt.test.ts | 5 ++ src/contracts/services/derivativeScrt.ts | 11 ++- src/contracts/services/oracle.ts | 13 ++- src/contracts/services/snip20.ts | 8 ++ src/contracts/services/swap.ts | 22 +++++ ...atchIndividualPricesWithErrorResponse.json | 1 + .../mocks/batchQuery/batchPairConfigParsed.ts | 2 + .../batchQuery/batchPairConfigResponse.json | 1 + .../batchQuery/batchPricesWithErrorParsed.ts | 2 + src/test/mocks/oracle/batchPricesParsed.ts | 2 + src/test/mocks/oracle/pricesParsed.ts | 1 + .../snip20/batchQueryTokensInfoParsed.ts | 2 + .../snip20/batchQueryTokensInfoUnparsed.ts | 2 + src/test/mocks/swap/batchPairsConfigParsed.ts | 2 + .../swap/batchPairsConfigResponseUnparsed.ts | 2 + src/test/mocks/swap/batchPairsInfoParsed.ts | 2 + .../swap/batchPairsInfoResponseUnparsed.ts | 2 + .../mocks/swap/batchStakingConfigParsed.ts | 2 + .../mocks/swap/batchStakingConfigUnparsed.ts | 2 + src/test/mocks/swap/router.ts | 29 ++++++ src/types/contracts/batchQuery/index.ts | 1 + src/types/contracts/batchQuery/model.ts | 1 + src/types/contracts/batchQuery/response.ts | 1 + src/types/contracts/batchQuery/service.ts | 9 ++ src/types/contracts/derivativeScrt/model.ts | 1 + src/types/contracts/oracle/model.ts | 3 +- src/types/contracts/snip20/model.ts | 1 + src/types/contracts/swap/model.ts | 3 + 33 files changed, 307 insertions(+), 23 deletions(-) create mode 100644 .changeset/rare-jobs-enjoy.md create mode 100644 src/types/contracts/batchQuery/service.ts diff --git a/.changeset/rare-jobs-enjoy.md b/.changeset/rare-jobs-enjoy.md new file mode 100644 index 0000000..0e8e989 --- /dev/null +++ b/.changeset/rare-jobs-enjoy.md @@ -0,0 +1,5 @@ +--- +"@shadeprotocol/shadejs": minor +--- + +add node health validation to query router and functions using the query router diff --git a/docs/contracts.md b/docs/contracts.md index 947014d..1e73146 100644 --- a/docs/contracts.md +++ b/docs/contracts.md @@ -7,7 +7,7 @@ This page contains a list of deployed contracts. |-------------------- |----------------------------------------------- |------------------------------------------------------------------ | | ShadeSwap Factory | secret1ja0hcwvy76grqkpgwznxukgd7t8a8anmmx05pp | 2ad4ed2a4a45fd6de3daca9541ba82c26bb66c76d1c3540de39b509abd26538e | | ShadeSwap Router | secret1pjhdug87nxzv0esxasmeyfsucaj98pw4334wyc | 448e3f6d801e453e838b7a5fbaa4dd93b84d0f1011245f0d5745366dadaf3e85 | -| Batch Query Router | secret17gnlxnwux0szd7qhl90ym8lw22qvedjz4v09dm | 72a09535b77b76862f7b568baf1ddbe158a2e4bbd0f0879c69ada9b398e31c1f | +| Batch Query Router | secret15mkmad8ac036v4nrpcc7nk8wyr578egt077syt | 1c7e86ba4fdb6760e70bf08a7df7f44b53eb0b23290e3e69ca96140810d4f432 | | Oracle | secret10n2xl5jmez6r9umtdrth78k0vwmce0l5m9f5dm | 32c4710842b97a526c243a68511b15f58d6e72a388af38a7221ff3244c754e91 | | stkd-scrt | secret1k6u0cy4feepm6pehnz804zmwakuwdapm69tuc4 | f6be719b3c6feb498d3554ca0398eb6b7e7db262acb33f84a8f12106da6bbb09 | | Shade Staking | secret1y6px5x7jzrk8hyvy67f06ytn8v0jwculypwxws | 2a1ae7fd2be82931cb11d0ce82b2e243507f2006074e2f316da661beb1abe3c3 | diff --git a/docs/queries/batch-query.md b/docs/queries/batch-query.md index bf750f6..3d44091 100644 --- a/docs/queries/batch-query.md +++ b/docs/queries/batch-query.md @@ -22,6 +22,22 @@ type BatchQueryParams = { queryMsg: any, } +// MinBlockHeightValidationOptions is an optional property that is used to validate the +// accuracy of the data in the batch response using an expected minimum +// block height associated with the data. The query will be retried until +// non-stale data is found (up to a max number of retries before error is thrown). +// The assumption is that you are working with a node cluster where one or more +// stale nodes are mixed into healthy nodes, and eventually the query will be +// tried with a healthy node and meet the minimum block height threshold. +// onStaleNodeDetected is a callback function for when stale nodes are found. This +// can be useful for error/node monitoring services. +type MinBlockHeightValidationOptions = { + minBlockHeight: number, // data must come from this block height or newer block + maxRetries: number, + onStaleNodeDetected?: () => void +} + + async function batchQuery({ contractAddress, codeHash, @@ -29,6 +45,7 @@ async function batchQuery({ chainId, queries, batchSize, // defaults to all queries in single batch + minBlockHeightValidationOptions, }:{ contractAddress: string, codeHash?: string, @@ -36,6 +53,7 @@ async function batchQuery({ chainId?: string, queries: BatchQueryParams[], batchSize?: number, + minBlockHeightValidationOptions?: MinBlockHeightValidationOptions, }): Promise ``` @@ -52,7 +70,8 @@ enum BatchItemResponseStatus { type BatchQueryParsedResponseItem = { id: string | number, response: any, - status?: BatchItemResponseStatus + status?: BatchItemResponseStatus, + blockHeight: number, // the block height that the data is from } @@ -121,5 +140,6 @@ console.log(output) }, }, }, + blockHeight: 123456789, }]; ``` \ No newline at end of file diff --git a/src/contracts/services/batchQuery.test.ts b/src/contracts/services/batchQuery.test.ts index 7bf3209..c808614 100644 --- a/src/contracts/services/batchQuery.test.ts +++ b/src/contracts/services/batchQuery.test.ts @@ -65,6 +65,7 @@ test('it can call the single batch query service', async () => { queries: ['BATCH_QUERY' as unknown as BatchQueryParams], client: 'SECRET_CLIENT' as unknown as SecretNetworkClient, }; + // observables function sendSecretClientContractQuery$.mockReturnValueOnce(of(batchPairConfigResponse)); @@ -80,11 +81,100 @@ test('it can call the single batch query service', async () => { // async/await function sendSecretClientContractQuery$.mockReturnValueOnce(of(batchPairConfigResponse)); + + const response = await batchQuery(input); + expect(msgBatchQuery).toHaveBeenNthCalledWith(2, input.queries); + expect(response).toStrictEqual(batchPairConfigParsed); +}); + +test('it can call the single batch query service and retry on stale node found', async () => { + const input = { + contractAddress: 'CONTRACT_ADDRESS', + codeHash: 'CODE_HASH', + queries: ['BATCH_QUERY' as unknown as BatchQueryParams], + client: 'SECRET_CLIENT' as unknown as SecretNetworkClient, + minBlockHeightValidationOptions: { + minBlockHeight: 3, + maxRetries: 3, + }, + }; + + const batchPairResponse1 = { + batch: { + ...batchPairConfigResponse.batch, + block_height: 2, // simulate stale node + }, + }; + + // observables function + sendSecretClientContractQuery$.mockReturnValueOnce( + of(batchPairResponse1), + ).mockReturnValueOnce( + of(batchPairResponse1), + ).mockReturnValueOnce( + of(batchPairResponse1), + ).mockReturnValueOnce( + of(batchPairConfigResponse), + ); + + let output; + batchQuerySingleBatch$(input).subscribe({ + next: (response) => { + output = response; + }, + }); + + expect(msgBatchQuery).toHaveBeenNthCalledWith(1, input.queries); + expect(output).toStrictEqual(batchPairConfigParsed); + + // async/await function + sendSecretClientContractQuery$.mockReturnValueOnce( + of(batchPairResponse1), + ).mockReturnValueOnce( + of(batchPairResponse1), + ).mockReturnValueOnce( + of(batchPairResponse1), + ).mockReturnValueOnce( + of(batchPairConfigResponse), + ); const response = await batchQuery(input); expect(msgBatchQuery).toHaveBeenNthCalledWith(2, input.queries); expect(response).toStrictEqual(batchPairConfigParsed); }); +test('it can call the single batch query service and detect query retry limit exceeded', async () => { + const input = { + contractAddress: 'CONTRACT_ADDRESS', + codeHash: 'CODE_HASH', + queries: ['BATCH_QUERY' as unknown as BatchQueryParams], + client: 'SECRET_CLIENT' as unknown as SecretNetworkClient, + minBlockHeightValidationOptions: { + minBlockHeight: 3, + maxRetries: 2, + }, + }; + + const batchPairResponse1 = { + batch: { + ...batchPairConfigResponse.batch, + block_height: 2, // simulate stale node + }, + }; + + // async/await function + sendSecretClientContractQuery$.mockReturnValueOnce( + of(batchPairResponse1), + ).mockReturnValueOnce( + of(batchPairResponse1), + ).mockReturnValueOnce( + of(batchPairResponse1), + ).mockReturnValueOnce( + of(batchPairConfigResponse), // will never reach final case due to retry limit + ); + + await expect(() => batchQuery(input)).rejects.toThrowError('Reached maximum retry attempts for stale node error.'); +}); + test('it can call the multi-batch query service on a single batch', async () => { const input = { contractAddress: 'CONTRACT_ADDRESS', diff --git a/src/contracts/services/batchQuery.ts b/src/contracts/services/batchQuery.ts index bfeaa8e..a6f2c9e 100644 --- a/src/contracts/services/batchQuery.ts +++ b/src/contracts/services/batchQuery.ts @@ -7,6 +7,8 @@ import { concatAll, reduce, catchError, + of, + throwError, } from 'rxjs'; import { sendSecretClientContractQuery$ } from '~/client/services/clientServices'; import { getActiveQueryClient$ } from '~/client'; @@ -20,6 +22,7 @@ import { import { BatchQueryResponse } from '~/types/contracts/batchQuery/response'; import { decodeB64ToJson } from '~/lib/utils'; import { SecretNetworkClient } from 'secretjs'; +import { MinBlockHeightValidationOptions } from '~/types/contracts/batchQuery/service'; /** * a parses the batch query response into a usable data model @@ -33,6 +36,7 @@ function parseBatchQuery(response: BatchQueryResponse): BatchQueryParsedResponse id: decodeB64ToJson(item.id), response: item.response.system_err, // response is not B64 encoded status: BatchItemResponseStatus.ERROR, + blockHeight: response.batch.block_height, }; } @@ -43,6 +47,7 @@ function parseBatchQuery(response: BatchQueryResponse): BatchQueryParsedResponse // a response available. response: decodeB64ToJson(item.response.response!), status: BatchItemResponseStatus.SUCCESS, + blockHeight: response.batch.block_height, }; }); } @@ -64,27 +69,60 @@ const batchQuerySingleBatch$ = ({ codeHash, queries, client, + minBlockHeightValidationOptions, }:{ contractAddress: string, codeHash?: string, queries: BatchQueryParams[], - client: SecretNetworkClient -}) => sendSecretClientContractQuery$({ - queryMsg: msgBatchQuery(queries), - client, - contractAddress, - codeHash, -}).pipe( - map((response) => parseBatchQuery(response as BatchQueryResponse)), - first(), - catchError((err) => { - if (err.message.includes('{wasm contract}')) { - throw new Error('{wasm contract} error that typically occurs when batch size is too large and node gas query limits are exceeded. Consider reducing the batch size.'); - } else { - throw new Error(err); - } - }), -); + client: SecretNetworkClient, + minBlockHeightValidationOptions?: MinBlockHeightValidationOptions, +}) => { + let retryCount = 0; + return of(1).pipe( // placeholder observable of(1) used here so that we can start a data stream + // and retry from this level when certain error conditions are reached + switchMap(() => sendSecretClientContractQuery$({ + queryMsg: msgBatchQuery(queries), + client, + contractAddress, + codeHash, + }).pipe( + map((response) => response as BatchQueryResponse), // map used for typecast only + switchMap((response) => { + // create an error if stale node is detected + if (minBlockHeightValidationOptions + && response.batch.block_height < minBlockHeightValidationOptions.minBlockHeight + ) { + // callback for when stale node is detected. Useful for error logging. + // check the retryCount to ensure that this only fires one time + if (retryCount === 0 + && typeof minBlockHeightValidationOptions.onStaleNodeDetected === 'function') { + minBlockHeightValidationOptions.onStaleNodeDetected(); + } + return throwError(() => new Error('Stale node detected')); + } + return of(response); + }), + map(parseBatchQuery), + )), + first(), + catchError((error, caught) => { + if (error.message === 'Stale node detected') { + retryCount += 1; + if ( + minBlockHeightValidationOptions + && retryCount <= minBlockHeightValidationOptions?.maxRetries + ) { + // retry the query + return caught; + } + return throwError(() => new Error('Reached maximum retry attempts for stale node error.')); + } if (error.message.includes('{wasm contract}')) { + return throwError(() => new Error('{wasm contract} error that typically occurs when batch size is too large and node gas query limits are exceeded. Consider reducing the batch size.')); + } + return throwError(() => error); + }), + ); +}; /** * batch query of multiple contracts/message at a time @@ -98,6 +136,7 @@ const batchQuery$ = ({ chainId, queries, batchSize, + minBlockHeightValidationOptions, }:{ contractAddress: string, codeHash?: string, @@ -105,6 +144,7 @@ const batchQuery$ = ({ chainId?: string, queries: BatchQueryParams[], batchSize?: number, + minBlockHeightValidationOptions?: MinBlockHeightValidationOptions, }) => { // if batch size is passed in, convert single batch into multiple batches, // otherwise process all data in a single batch @@ -119,6 +159,7 @@ const batchQuery$ = ({ codeHash, queries: batch, client, + minBlockHeightValidationOptions, })), ).pipe( concatAll(), @@ -143,6 +184,7 @@ async function batchQuery({ chainId, queries, batchSize, + minBlockHeightValidationOptions, }:{ contractAddress: string, codeHash?: string, @@ -150,6 +192,7 @@ async function batchQuery({ chainId?: string, queries: BatchQueryParams[], batchSize?: number, + minBlockHeightValidationOptions?: MinBlockHeightValidationOptions, }) { return lastValueFrom(batchQuery$({ contractAddress, @@ -158,6 +201,7 @@ async function batchQuery({ chainId, queries, batchSize, + minBlockHeightValidationOptions, })); } diff --git a/src/contracts/services/derivativeScrt.test.ts b/src/contracts/services/derivativeScrt.test.ts index f3ba86d..e0d4a1b 100644 --- a/src/contracts/services/derivativeScrt.test.ts +++ b/src/contracts/services/derivativeScrt.test.ts @@ -21,10 +21,12 @@ const batchQueryResponse = [ { id: BatchRouterKeys.STAKING_INFO, response: stakingInfoResponse, + blockHeight: 1, }, { id: BatchRouterKeys.FEE_INFO, response: feeInfoResponse, + blockHeight: 1, }, ]; @@ -55,6 +57,7 @@ test('it can parse the batch query resonse', () => { )).toStrictEqual({ ...feeInfoResponseParsed, ...stakingInfoResponseParsed, + blockHeight: 1, }); }); @@ -78,6 +81,7 @@ test('it can call the query all info service', async () => { expect(output).toStrictEqual({ ...feeInfoResponseParsed, ...stakingInfoResponseParsed, + blockHeight: 1, }); // async/await function @@ -87,5 +91,6 @@ test('it can call the query all info service', async () => { expect(output2).toStrictEqual({ ...feeInfoResponseParsed, ...stakingInfoResponseParsed, + blockHeight: 1, }); }); diff --git a/src/contracts/services/derivativeScrt.ts b/src/contracts/services/derivativeScrt.ts index f568188..9b973c4 100644 --- a/src/contracts/services/derivativeScrt.ts +++ b/src/contracts/services/derivativeScrt.ts @@ -22,6 +22,7 @@ import { BatchQueryParsedResponse, BatchQueryParsedResponseItem, } from '~/types/contracts/batchQuery/model'; +import { MinBlockHeightValidationOptions } from '~/types'; import { batchQuery$ } from './batchQuery'; // Contract returns price as a rate of stkd-SCRT/SCRT with 6 decimals @@ -89,6 +90,9 @@ const parseDerivativeScrtInfo = ( return { ...parseDerivativeScrtStakingInfo(stakingInfoResponse.response), ...parseDerivativeScrtFeeInfo(feeInfoResponse.response), + // we are assuming that both responses will come from the same block + // because they are queried in the same batch. + blockHeight: stakingInfoResponse.blockHeight, }; }; @@ -107,6 +111,7 @@ const queryDerivativeScrtInfo$ = ({ lcdEndpoint, chainId, queryTimeSeconds, + minBlockHeightValidationOptions, }: { queryRouterContractAddress: string, queryRouterCodeHash?: string, @@ -115,6 +120,7 @@ const queryDerivativeScrtInfo$ = ({ lcdEndpoint?: string, chainId?: string, queryTimeSeconds?: number, + minBlockHeightValidationOptions?: MinBlockHeightValidationOptions, }) => batchQuery$({ queries: [ { @@ -134,13 +140,13 @@ const queryDerivativeScrtInfo$ = ({ codeHash, }, queryMsg: msgQueryScrtDerivativeFees(), - }, ], lcdEndpoint, contractAddress: queryRouterContractAddress, codeHash: queryRouterCodeHash, chainId, + minBlockHeightValidationOptions, }).pipe( map((response: any) => parseDerivativeScrtInfo(response as BatchQueryParsedResponse)), first(), @@ -161,6 +167,7 @@ async function queryDerivativeScrtInfo({ lcdEndpoint, chainId, queryTimeSeconds, + minBlockHeightValidationOptions, }: { queryRouterContractAddress: string, queryRouterCodeHash?: string, @@ -169,6 +176,7 @@ async function queryDerivativeScrtInfo({ lcdEndpoint?: string, chainId?: string, queryTimeSeconds?: number, + minBlockHeightValidationOptions?: MinBlockHeightValidationOptions, }) { return lastValueFrom(queryDerivativeScrtInfo$({ queryRouterContractAddress, @@ -178,6 +186,7 @@ async function queryDerivativeScrtInfo({ lcdEndpoint, chainId, queryTimeSeconds, + minBlockHeightValidationOptions, })); } diff --git a/src/contracts/services/oracle.ts b/src/contracts/services/oracle.ts index ae8e992..9f4d37e 100644 --- a/src/contracts/services/oracle.ts +++ b/src/contracts/services/oracle.ts @@ -20,17 +20,22 @@ import { BatchItemResponseStatus, BatchQueryParams, BatchQueryParsedResponse, + MinBlockHeightValidationOptions, } from '~/types'; import { batchQuery$ } from './batchQuery'; /** * Parses the contract price query into the app data model */ -const parsePriceFromContract = (response: OraclePriceResponse): ParsedOraclePriceResponse => ({ +const parsePriceFromContract = ( + response: OraclePriceResponse, + blockHeight?: number, +): ParsedOraclePriceResponse => ({ oracleKey: response.key, rate: response.data.rate, lastUpdatedBase: response.data.last_updated_base, lastUpdatedQuote: response.data.last_updated_quote, + blockHeight, }); /** @@ -74,12 +79,13 @@ const parseBatchQueryIndividualPrices = ( type: errorType, msg: item.response, }, + blockHeight: item.blockHeight, }, }; } return { ...prev, - [item.id as string]: parsePriceFromContract(item.response), + [item.id as string]: parsePriceFromContract(item.response, item.blockHeight), }; }, {}); @@ -202,6 +208,7 @@ function batchQueryIndividualPrices$({ oracleContractAddress, oracleCodeHash, oracleKeys, + minBlockHeightValidationOptions, }:{ queryRouterContractAddress: string, queryRouterCodeHash?: string, @@ -210,6 +217,7 @@ function batchQueryIndividualPrices$({ oracleContractAddress: string oracleCodeHash: string oracleKeys: string[], + minBlockHeightValidationOptions?: MinBlockHeightValidationOptions, }) { const queries:BatchQueryParams[] = oracleKeys.map((key) => ({ id: key, @@ -225,6 +233,7 @@ function batchQueryIndividualPrices$({ lcdEndpoint, chainId, queries, + minBlockHeightValidationOptions, }).pipe( map(parseBatchQueryIndividualPrices), first(), diff --git a/src/contracts/services/snip20.ts b/src/contracts/services/snip20.ts index 03a31fb..e7bd816 100644 --- a/src/contracts/services/snip20.ts +++ b/src/contracts/services/snip20.ts @@ -16,6 +16,7 @@ import { Contract, BatchQueryParams, BatchQueryParsedResponse, + MinBlockHeightValidationOptions, } from '~/types'; import { batchQuery$ } from './batchQuery'; @@ -35,6 +36,7 @@ const parseBatchQueryTokensInfo = ( ): BatchTokensInfo => response.map((item) => ({ tokenContractAddress: item.id as string, tokenInfo: parseTokenInfo(item.response), + blockHeight: item.blockHeight, })); const parseBalance = (response: BalanceResponse): string => response.balance.amount; @@ -94,12 +96,14 @@ function batchQuerySnip20TokensInfo$({ lcdEndpoint, chainId, tokenContracts, + minBlockHeightValidationOptions, }:{ queryRouterContractAddress: string, queryRouterCodeHash?: string, lcdEndpoint?: string, chainId?: string, tokenContracts: Contract[] + minBlockHeightValidationOptions?: MinBlockHeightValidationOptions, }) { const queries:BatchQueryParams[] = tokenContracts.map((contract) => ({ id: contract.address, @@ -115,6 +119,7 @@ function batchQuerySnip20TokensInfo$({ lcdEndpoint, chainId, queries, + minBlockHeightValidationOptions, }).pipe( map(parseBatchQueryTokensInfo), first(), @@ -130,12 +135,14 @@ function batchQuerySnip20TokensInfo({ lcdEndpoint, chainId, tokenContracts, + minBlockHeightValidationOptions, }:{ queryRouterContractAddress: string, queryRouterCodeHash?: string, lcdEndpoint?: string, chainId?: string, tokenContracts: Contract[] + minBlockHeightValidationOptions?: MinBlockHeightValidationOptions, }) { return lastValueFrom(batchQuerySnip20TokensInfo$({ queryRouterContractAddress, @@ -143,6 +150,7 @@ function batchQuerySnip20TokensInfo({ lcdEndpoint, chainId, tokenContracts, + minBlockHeightValidationOptions, })); } diff --git a/src/contracts/services/swap.ts b/src/contracts/services/swap.ts index 761e158..d272df2 100644 --- a/src/contracts/services/swap.ts +++ b/src/contracts/services/swap.ts @@ -38,6 +38,7 @@ import { } from '~/types/contracts/batchQuery/model'; import { TxResponse } from 'secretjs'; import { Attribute } from 'secretjs/dist/protobuf/cosmos/base/abci/v1beta1/abci'; +import { MinBlockHeightValidationOptions } from '~/types'; import { batchQuery$ } from './batchQuery'; import { SERVICE_BATCH_SIZE } from './config'; @@ -236,6 +237,7 @@ const parseBatchQueryPairInfoResponse = ( ): BatchPairsInfo => response.map((item) => ({ pairContractAddress: item.id as string, pairInfo: parsePairInfo(item.response), + blockHeight: item.blockHeight, })); /** @@ -247,6 +249,7 @@ const parseBatchQueryPairConfigResponse = ( ): BatchPairsConfig => response.map((item) => ({ pairContractAddress: item.id as string, pairConfig: parsePairConfig(item.response), + blockHeight: item.blockHeight, })); /** * parses the single staking info response @@ -298,6 +301,7 @@ const parseBatchQueryStakingInfoResponse = ( ): BatchStakingInfo => response.map((item) => ({ stakingContractAddress: item.id as string, stakingInfo: parseStakingInfo(item.response), + blockHeight: item.blockHeight, })); /** @@ -511,6 +515,7 @@ function batchQueryPairsInfo$({ chainId, pairsContracts, batchSize = SERVICE_BATCH_SIZE.PAIR_INFO, + minBlockHeightValidationOptions, }:{ queryRouterContractAddress: string, queryRouterCodeHash?: string, @@ -518,6 +523,7 @@ function batchQueryPairsInfo$({ chainId?: string, pairsContracts: Contract[], batchSize?: number, + minBlockHeightValidationOptions?: MinBlockHeightValidationOptions, }) { const queries:BatchQueryParams[] = pairsContracts.map((contract) => ({ id: contract.address, @@ -534,6 +540,7 @@ function batchQueryPairsInfo$({ chainId, queries, batchSize, + minBlockHeightValidationOptions, }).pipe( map(parseBatchQueryPairInfoResponse), first(), @@ -550,6 +557,7 @@ async function batchQueryPairsInfo({ chainId, pairsContracts, batchSize, + minBlockHeightValidationOptions, }:{ queryRouterContractAddress: string, queryRouterCodeHash?: string, @@ -557,6 +565,7 @@ async function batchQueryPairsInfo({ chainId?: string, pairsContracts: Contract[], batchSize?: number, + minBlockHeightValidationOptions?: MinBlockHeightValidationOptions, }) { return lastValueFrom(batchQueryPairsInfo$({ queryRouterContractAddress, @@ -565,6 +574,7 @@ async function batchQueryPairsInfo({ chainId, pairsContracts, batchSize, + minBlockHeightValidationOptions, })); } @@ -578,6 +588,7 @@ function batchQueryPairsConfig$({ chainId, pairsContracts, batchSize = SERVICE_BATCH_SIZE.PAIR_CONFIG, + minBlockHeightValidationOptions, }:{ queryRouterContractAddress: string, queryRouterCodeHash?: string, @@ -585,6 +596,7 @@ function batchQueryPairsConfig$({ chainId?: string, pairsContracts: Contract[], batchSize?: number, + minBlockHeightValidationOptions?: MinBlockHeightValidationOptions, }) { const queries:BatchQueryParams[] = pairsContracts.map((contract) => ({ id: contract.address, @@ -601,6 +613,7 @@ function batchQueryPairsConfig$({ chainId, queries, batchSize, + minBlockHeightValidationOptions, }).pipe( map(parseBatchQueryPairConfigResponse), first(), @@ -617,6 +630,7 @@ async function batchQueryPairsConfig({ chainId, pairsContracts, batchSize, + minBlockHeightValidationOptions, }:{ queryRouterContractAddress: string, queryRouterCodeHash?: string, @@ -624,6 +638,7 @@ async function batchQueryPairsConfig({ chainId?: string, pairsContracts: Contract[], batchSize?: number, + minBlockHeightValidationOptions?: MinBlockHeightValidationOptions, }) { return lastValueFrom(batchQueryPairsConfig$({ queryRouterContractAddress, @@ -632,6 +647,7 @@ async function batchQueryPairsConfig({ chainId, pairsContracts, batchSize, + minBlockHeightValidationOptions, })); } @@ -644,12 +660,14 @@ function batchQueryStakingInfo$({ lcdEndpoint, chainId, stakingContracts, + minBlockHeightValidationOptions, }:{ queryRouterContractAddress: string, queryRouterCodeHash?: string, lcdEndpoint?: string, chainId?: string, stakingContracts: Contract[] + minBlockHeightValidationOptions?: MinBlockHeightValidationOptions, }) { const queries:BatchQueryParams[] = stakingContracts.map((contract) => ({ id: contract.address, @@ -665,6 +683,7 @@ function batchQueryStakingInfo$({ lcdEndpoint, chainId, queries, + minBlockHeightValidationOptions, }).pipe( map(parseBatchQueryStakingInfoResponse), first(), @@ -680,12 +699,14 @@ async function batchQueryStakingInfo({ lcdEndpoint, chainId, stakingContracts, + minBlockHeightValidationOptions, }:{ queryRouterContractAddress: string, queryRouterCodeHash?: string, lcdEndpoint?: string, chainId?: string, stakingContracts: Contract[] + minBlockHeightValidationOptions?: MinBlockHeightValidationOptions, }) { return lastValueFrom(batchQueryStakingInfo$({ queryRouterContractAddress, @@ -693,6 +714,7 @@ async function batchQueryStakingInfo({ lcdEndpoint, chainId, stakingContracts, + minBlockHeightValidationOptions, })); } diff --git a/src/test/mocks/batchQuery/batchIndividualPricesWithErrorResponse.json b/src/test/mocks/batchQuery/batchIndividualPricesWithErrorResponse.json index 336e1be..581dd5e 100644 --- a/src/test/mocks/batchQuery/batchIndividualPricesWithErrorResponse.json +++ b/src/test/mocks/batchQuery/batchIndividualPricesWithErrorResponse.json @@ -1,5 +1,6 @@ { "batch": { + "block_height": 3, "responses": [ { "id": "IkJUQyI=", diff --git a/src/test/mocks/batchQuery/batchPairConfigParsed.ts b/src/test/mocks/batchQuery/batchPairConfigParsed.ts index 38d28a5..352e36f 100644 --- a/src/test/mocks/batchQuery/batchPairConfigParsed.ts +++ b/src/test/mocks/batchQuery/batchPairConfigParsed.ts @@ -44,6 +44,7 @@ const batchPairConfigParsed: BatchQueryParsedResponse = [{ }, }, status: BatchItemResponseStatus.SUCCESS, + blockHeight: 3, }, { id: 2, @@ -89,6 +90,7 @@ const batchPairConfigParsed: BatchQueryParsedResponse = [{ }, }, status: BatchItemResponseStatus.SUCCESS, + blockHeight: 3, }]; export { diff --git a/src/test/mocks/batchQuery/batchPairConfigResponse.json b/src/test/mocks/batchQuery/batchPairConfigResponse.json index ff6cbdc..faf5695 100644 --- a/src/test/mocks/batchQuery/batchPairConfigResponse.json +++ b/src/test/mocks/batchQuery/batchPairConfigResponse.json @@ -1,5 +1,6 @@ { "batch": { + "block_height": 3, "responses": [ { "id": "MQ==", diff --git a/src/test/mocks/batchQuery/batchPricesWithErrorParsed.ts b/src/test/mocks/batchQuery/batchPricesWithErrorParsed.ts index 1ecb818..6812908 100644 --- a/src/test/mocks/batchQuery/batchPricesWithErrorParsed.ts +++ b/src/test/mocks/batchQuery/batchPricesWithErrorParsed.ts @@ -12,11 +12,13 @@ const batchPricesWithErrorParsed: BatchQueryParsedResponse = [{ }, }, status: BatchItemResponseStatus.SUCCESS, + blockHeight: 3, }, { id: 'Quicksilver ATOM', response: 'Cannot parse response: expected value at line 1 column 1 in: Generic error: Querier system error: Cannot parse response: expected value at line 1 column 1 in: Generic error: Derivative rate is stale. Last updated 1704806636. Current time 1707858606.', status: BatchItemResponseStatus.ERROR, + blockHeight: 3, }]; export { diff --git a/src/test/mocks/oracle/batchPricesParsed.ts b/src/test/mocks/oracle/batchPricesParsed.ts index 7c30b3e..4f5a33b 100644 --- a/src/test/mocks/oracle/batchPricesParsed.ts +++ b/src/test/mocks/oracle/batchPricesParsed.ts @@ -7,6 +7,7 @@ const batchPricesWithErrorParsedResponse: ParsedOraclePricesResponse = { lastUpdatedBase: 1707858482, // eslint-disable-next-line lastUpdatedQuote: 18446744073709551615, + blockHeight: 3, }, 'Quicksilver ATOM': { oracleKey: 'Quicksilver ATOM', @@ -14,6 +15,7 @@ const batchPricesWithErrorParsedResponse: ParsedOraclePricesResponse = { type: OracleErrorType.STALE_DERIVATIVE_RATE, msg: 'Cannot parse response: expected value at line 1 column 1 in: Generic error: Querier system error: Cannot parse response: expected value at line 1 column 1 in: Generic error: Derivative rate is stale. Last updated 1704806636. Current time 1707858606.', }, + blockHeight: 3, }, }; diff --git a/src/test/mocks/oracle/pricesParsed.ts b/src/test/mocks/oracle/pricesParsed.ts index ea395ce..9321ea2 100644 --- a/src/test/mocks/oracle/pricesParsed.ts +++ b/src/test/mocks/oracle/pricesParsed.ts @@ -3,6 +3,7 @@ const priceParsed = { rate: '27917207155600000000000', lastUpdatedBase: 1696644063, lastUpdatedQuote: 18446744073709552000, + blockHeight: undefined, }; const pricesParsed = { diff --git a/src/test/mocks/snip20/batchQueryTokensInfoParsed.ts b/src/test/mocks/snip20/batchQueryTokensInfoParsed.ts index ecd3c91..2828395 100644 --- a/src/test/mocks/snip20/batchQueryTokensInfoParsed.ts +++ b/src/test/mocks/snip20/batchQueryTokensInfoParsed.ts @@ -9,6 +9,7 @@ const batchTokensInfoParsed: BatchTokensInfo = [ decimals: 8, totalSupply: '248115955993665', }, + blockHeight: 3, }, { tokenContractAddress: 'secret1fl449muk5yq8dlad7a22nje4p5d2pnsgymhjfd', @@ -18,6 +19,7 @@ const batchTokensInfoParsed: BatchTokensInfo = [ decimals: 6, totalSupply: '3688426236358', }, + blockHeight: 3, }, ]; diff --git a/src/test/mocks/snip20/batchQueryTokensInfoUnparsed.ts b/src/test/mocks/snip20/batchQueryTokensInfoUnparsed.ts index 657b079..bed51fe 100644 --- a/src/test/mocks/snip20/batchQueryTokensInfoUnparsed.ts +++ b/src/test/mocks/snip20/batchQueryTokensInfoUnparsed.ts @@ -9,6 +9,7 @@ const batchTokensInfoUnparsed = [ total_supply: '248115955993665', }, }, + blockHeight: 3, }, { id: 'secret1fl449muk5yq8dlad7a22nje4p5d2pnsgymhjfd', @@ -20,6 +21,7 @@ const batchTokensInfoUnparsed = [ total_supply: '3688426236358', }, }, + blockHeight: 3, }, ]; diff --git a/src/test/mocks/swap/batchPairsConfigParsed.ts b/src/test/mocks/swap/batchPairsConfigParsed.ts index 2233bad..371839f 100644 --- a/src/test/mocks/swap/batchPairsConfigParsed.ts +++ b/src/test/mocks/swap/batchPairsConfigParsed.ts @@ -25,6 +25,7 @@ const batchPairsConfigParsed = [ isStable: false, fee: null, }, + blockHeight: 1, }, { pairContractAddress: 'secret1l34fyc9g23fnlk896693nw57phevnyha7pt6gj', @@ -55,6 +56,7 @@ const batchPairsConfigParsed = [ daoFee: 0.003, }, }, + blockHeight: 1, }, ]; diff --git a/src/test/mocks/swap/batchPairsConfigResponseUnparsed.ts b/src/test/mocks/swap/batchPairsConfigResponseUnparsed.ts index f6bcafa..a2130f2 100644 --- a/src/test/mocks/swap/batchPairsConfigResponseUnparsed.ts +++ b/src/test/mocks/swap/batchPairsConfigResponseUnparsed.ts @@ -33,6 +33,7 @@ const batchPairsConfigResponseUnparsed = [ custom_fee: null, }, }, + blockHeight: 1, }, { id: 'secret1l34fyc9g23fnlk896693nw57phevnyha7pt6gj', @@ -77,6 +78,7 @@ const batchPairsConfigResponseUnparsed = [ }, }, }, + blockHeight: 1, }, ]; diff --git a/src/test/mocks/swap/batchPairsInfoParsed.ts b/src/test/mocks/swap/batchPairsInfoParsed.ts index ee25486..41c5aa4 100644 --- a/src/test/mocks/swap/batchPairsInfoParsed.ts +++ b/src/test/mocks/swap/batchPairsInfoParsed.ts @@ -28,6 +28,7 @@ const pairsInfoParsed = [ stableParams: null, contractVersion: 1, }, + blockHeight: 1, }, { pairContractAddress: 'secret1wn9tdlvut2nz0cpv28qtv74pqx20p847j8gx3w', @@ -58,6 +59,7 @@ const pairsInfoParsed = [ stableParams: null, contractVersion: 1, }, + blockHeight: 1, }, ]; diff --git a/src/test/mocks/swap/batchPairsInfoResponseUnparsed.ts b/src/test/mocks/swap/batchPairsInfoResponseUnparsed.ts index eb733f3..f7434ad 100644 --- a/src/test/mocks/swap/batchPairsInfoResponseUnparsed.ts +++ b/src/test/mocks/swap/batchPairsInfoResponseUnparsed.ts @@ -52,6 +52,7 @@ const pairsInfoResponseUnparsed = [ stable_info: null, }, }, + blockHeight: 1, }, { id: 'secret1wn9tdlvut2nz0cpv28qtv74pqx20p847j8gx3w', @@ -106,6 +107,7 @@ const pairsInfoResponseUnparsed = [ stable_info: null, }, }, + blockHeight: 1, }, ]; diff --git a/src/test/mocks/swap/batchStakingConfigParsed.ts b/src/test/mocks/swap/batchStakingConfigParsed.ts index c734991..8d8c13d 100644 --- a/src/test/mocks/swap/batchStakingConfigParsed.ts +++ b/src/test/mocks/swap/batchStakingConfigParsed.ts @@ -29,6 +29,7 @@ const batchStakingConfigParsed = [ }, ], }, + blockHeight: 1, }, { stakingContractAddress: 'secret1vgtmfvzdn7ztn7kcrqd7p6f2z97wvauavp3udh', @@ -130,6 +131,7 @@ const batchStakingConfigParsed = [ }, ], }, + blockHeight: 1, }, ]; diff --git a/src/test/mocks/swap/batchStakingConfigUnparsed.ts b/src/test/mocks/swap/batchStakingConfigUnparsed.ts index dd3ede5..f30e2a1 100644 --- a/src/test/mocks/swap/batchStakingConfigUnparsed.ts +++ b/src/test/mocks/swap/batchStakingConfigUnparsed.ts @@ -30,6 +30,7 @@ const batchStakingConfigUnparsed = [ }, ], }, + blockHeight: 1, }, { id: 'secret1vgtmfvzdn7ztn7kcrqd7p6f2z97wvauavp3udh', @@ -139,6 +140,7 @@ const batchStakingConfigUnparsed = [ }, ], }, + blockHeight: 1, }, ]; diff --git a/src/test/mocks/swap/router.ts b/src/test/mocks/swap/router.ts index b47ee20..afa7f55 100644 --- a/src/test/mocks/swap/router.ts +++ b/src/test/mocks/swap/router.ts @@ -28,6 +28,7 @@ const batchPairsInfoMock: BatchPairsInfo = [ stableParams: null, contractVersion: 1, }, + blockHeight: 1, }, { pairContractAddress: 'CONTRACT_ADDRESS_PAIR_2', @@ -55,6 +56,7 @@ const batchPairsInfoMock: BatchPairsInfo = [ stableParams: null, contractVersion: 1, }, + blockHeight: 1, }, { pairContractAddress: 'CONTRACT_ADDRESS_PAIR_3', @@ -82,6 +84,7 @@ const batchPairsInfoMock: BatchPairsInfo = [ stableParams: null, contractVersion: 1, }, + blockHeight: 1, }, { pairContractAddress: 'CONTRACT_ADDRESS_PAIR_4', @@ -130,6 +133,7 @@ const batchPairsInfoMock: BatchPairsInfo = [ }, contractVersion: 1, }, + blockHeight: 1, }, { pairContractAddress: 'CONTRACT_ADDRESS_PAIR_5', @@ -178,6 +182,7 @@ const batchPairsInfoMock: BatchPairsInfo = [ }, contractVersion: 1, }, + blockHeight: 1, }, { pairContractAddress: 'CONTRACT_ADDRESS_PAIR_6', @@ -226,6 +231,7 @@ const batchPairsInfoMock: BatchPairsInfo = [ }, contractVersion: 1, }, + blockHeight: 1, }, { pairContractAddress: 'CONTRACT_ADDRESS_PAIR_7', @@ -253,6 +259,7 @@ const batchPairsInfoMock: BatchPairsInfo = [ stableParams: null, contractVersion: 1, }, + blockHeight: 1, }, { pairContractAddress: 'CONTRACT_ADDRESS_PAIR_8', @@ -280,6 +287,7 @@ const batchPairsInfoMock: BatchPairsInfo = [ stableParams: null, contractVersion: 1, }, + blockHeight: 1, }, { pairContractAddress: 'CONTRACT_ADDRESS_PAIR_9', @@ -307,6 +315,7 @@ const batchPairsInfoMock: BatchPairsInfo = [ stableParams: null, contractVersion: 1, }, + blockHeight: 1, }, { pairContractAddress: 'CONTRACT_ADDRESS_PAIR_10', @@ -355,6 +364,7 @@ const batchPairsInfoMock: BatchPairsInfo = [ }, contractVersion: 1, }, + blockHeight: 1, }, { pairContractAddress: 'CONTRACT_ADDRESS_PAIR_11', @@ -403,6 +413,7 @@ const batchPairsInfoMock: BatchPairsInfo = [ }, contractVersion: 1, }, + blockHeight: 1, }, { pairContractAddress: 'CONTRACT_ADDRESS_PAIR_12', @@ -451,6 +462,7 @@ const batchPairsInfoMock: BatchPairsInfo = [ }, contractVersion: 1, }, + blockHeight: 1, }, { pairContractAddress: 'CONTRACT_ADDRESS_PAIR_13', @@ -499,6 +511,7 @@ const batchPairsInfoMock: BatchPairsInfo = [ }, contractVersion: 1, }, + blockHeight: 1, }, { pairContractAddress: 'CONTRACT_ADDRESS_PAIR_14', @@ -527,6 +540,7 @@ const batchPairsInfoMock: BatchPairsInfo = [ contractVersion: 1, }, + blockHeight: 1, }, { pairContractAddress: 'CONTRACT_ADDRESS_PAIR_15', @@ -575,6 +589,7 @@ const batchPairsInfoMock: BatchPairsInfo = [ }, contractVersion: 1, }, + blockHeight: 1, }, { pairContractAddress: 'CONTRACT_ADDRESS_PAIR_16', @@ -623,6 +638,7 @@ const batchPairsInfoMock: BatchPairsInfo = [ }, contractVersion: 1, }, + blockHeight: 1, }, { pairContractAddress: 'CONTRACT_ADDRESS_PAIR_17', @@ -671,6 +687,7 @@ const batchPairsInfoMock: BatchPairsInfo = [ }, contractVersion: 1, }, + blockHeight: 1, }, { pairContractAddress: 'CONTRACT_ADDRESS_PAIR_18', @@ -719,6 +736,7 @@ const batchPairsInfoMock: BatchPairsInfo = [ }, contractVersion: 1, }, + blockHeight: 1, }, ]; @@ -749,6 +767,7 @@ const batchPairsInfoMockForComplexRoute: BatchPairsInfo = [ stableParams: null, contractVersion: 1, }, + blockHeight: 1, }, { pairContractAddress: 'CONTRACT_ADDRESS_PAIR_2', @@ -776,6 +795,7 @@ const batchPairsInfoMockForComplexRoute: BatchPairsInfo = [ stableParams: null, contractVersion: 1, }, + blockHeight: 1, }, { pairContractAddress: 'CONTRACT_ADDRESS_PAIR_3', @@ -803,6 +823,7 @@ const batchPairsInfoMockForComplexRoute: BatchPairsInfo = [ stableParams: null, contractVersion: 1, }, + blockHeight: 1, }, { pairContractAddress: 'CONTRACT_ADDRESS_PAIR_4', @@ -851,6 +872,7 @@ const batchPairsInfoMockForComplexRoute: BatchPairsInfo = [ }, contractVersion: 1, }, + blockHeight: 1, }, { pairContractAddress: 'CONTRACT_ADDRESS_PAIR_5', @@ -899,6 +921,7 @@ const batchPairsInfoMockForComplexRoute: BatchPairsInfo = [ }, contractVersion: 1, }, + blockHeight: 1, }, { pairContractAddress: 'CONTRACT_ADDRESS_PAIR_6', @@ -947,6 +970,7 @@ const batchPairsInfoMockForComplexRoute: BatchPairsInfo = [ }, contractVersion: 1, }, + blockHeight: 1, }, { pairContractAddress: 'CONTRACT_ADDRESS_PAIR_7', @@ -974,6 +998,7 @@ const batchPairsInfoMockForComplexRoute: BatchPairsInfo = [ stableParams: null, contractVersion: 1, }, + blockHeight: 1, }, { pairContractAddress: 'CONTRACT_ADDRESS_PAIR_8', @@ -1001,6 +1026,7 @@ const batchPairsInfoMockForComplexRoute: BatchPairsInfo = [ stableParams: null, contractVersion: 1, }, + blockHeight: 1, }, { pairContractAddress: 'CONTRACT_ADDRESS_PAIR_9', @@ -1028,6 +1054,7 @@ const batchPairsInfoMockForComplexRoute: BatchPairsInfo = [ stableParams: null, contractVersion: 1, }, + blockHeight: 1, }, { pairContractAddress: 'CONTRACT_ADDRESS_PAIR_10', @@ -1076,6 +1103,7 @@ const batchPairsInfoMockForComplexRoute: BatchPairsInfo = [ }, contractVersion: 1, }, + blockHeight: 1, }, { pairContractAddress: 'CONTRACT_ADDRESS_PAIR_11', @@ -1124,6 +1152,7 @@ const batchPairsInfoMockForComplexRoute: BatchPairsInfo = [ }, contractVersion: 1, }, + blockHeight: 1, }, ]; diff --git a/src/types/contracts/batchQuery/index.ts b/src/types/contracts/batchQuery/index.ts index ed9aa71..2df5def 100644 --- a/src/types/contracts/batchQuery/index.ts +++ b/src/types/contracts/batchQuery/index.ts @@ -1,2 +1,3 @@ export * from './model'; export * from './response'; +export * from './service'; diff --git a/src/types/contracts/batchQuery/model.ts b/src/types/contracts/batchQuery/model.ts index 325e180..c3f6e8a 100644 --- a/src/types/contracts/batchQuery/model.ts +++ b/src/types/contracts/batchQuery/model.ts @@ -16,6 +16,7 @@ type BatchQueryParsedResponseItem = { id: string | number, response: any, status?: BatchItemResponseStatus + blockHeight: number, } type BatchQueryParsedResponse = BatchQueryParsedResponseItem[] diff --git a/src/types/contracts/batchQuery/response.ts b/src/types/contracts/batchQuery/response.ts index ee4ab55..9c45de2 100644 --- a/src/types/contracts/batchQuery/response.ts +++ b/src/types/contracts/batchQuery/response.ts @@ -12,6 +12,7 @@ type BatchQueryResponseItem = { type BatchQueryResponse = { batch: { + block_height: number, responses: BatchQueryResponseItem[], } } diff --git a/src/types/contracts/batchQuery/service.ts b/src/types/contracts/batchQuery/service.ts new file mode 100644 index 0000000..ab4967a --- /dev/null +++ b/src/types/contracts/batchQuery/service.ts @@ -0,0 +1,9 @@ +type MinBlockHeightValidationOptions = { + minBlockHeight: number, + maxRetries: number, + onStaleNodeDetected?: () => void +} + +export type { + MinBlockHeightValidationOptions, +}; diff --git a/src/types/contracts/derivativeScrt/model.ts b/src/types/contracts/derivativeScrt/model.ts index 93100e6..5df8577 100644 --- a/src/types/contracts/derivativeScrt/model.ts +++ b/src/types/contracts/derivativeScrt/model.ts @@ -26,6 +26,7 @@ type DerivativeScrtInfo = { nextUnbondingBatchEstimatedTime: number, depositFee: number, withdrawFee: number, + blockHeight: number, } enum BatchRouterKeys { diff --git a/src/types/contracts/oracle/model.ts b/src/types/contracts/oracle/model.ts index 36d3a58..4e37daf 100644 --- a/src/types/contracts/oracle/model.ts +++ b/src/types/contracts/oracle/model.ts @@ -11,7 +11,8 @@ type ParsedOraclePriceResponse = { error?: { type: OracleErrorType, msg: any, - } + }, + blockHeight?: number // block height is only available when using a batch query router } type ParsedOraclePricesResponse = { diff --git a/src/types/contracts/snip20/model.ts b/src/types/contracts/snip20/model.ts index 5c6b7a7..aaaf23f 100644 --- a/src/types/contracts/snip20/model.ts +++ b/src/types/contracts/snip20/model.ts @@ -20,6 +20,7 @@ type TokenInfo = { type BatchTokensInfoItem = { tokenContractAddress: string, tokenInfo: TokenInfo, + blockHeight: number, } type BatchTokensInfo = BatchTokensInfoItem[] diff --git a/src/types/contracts/swap/model.ts b/src/types/contracts/swap/model.ts index 7f4d3c4..cabfab8 100644 --- a/src/types/contracts/swap/model.ts +++ b/src/types/contracts/swap/model.ts @@ -92,6 +92,7 @@ type PairInfo = { type BatchPairInfo = { pairContractAddress: string, pairInfo: PairInfo, + blockHeight: number, } type BatchPairsInfo = BatchPairInfo[] @@ -99,6 +100,7 @@ type BatchPairsInfo = BatchPairInfo[] type BatchPairConfig = { pairContractAddress: string, pairConfig: PairConfig, + blockHeight: number, } type BatchPairsConfig = BatchPairConfig[] @@ -123,6 +125,7 @@ type StakingInfo = { type BatchSingleStakingInfo = { stakingContractAddress: string, stakingInfo: StakingInfo, + blockHeight: number, } type BatchStakingInfo = BatchSingleStakingInfo[]