From fd04d6b8a0fc95c7875c5018e5a8dc4ba8e5d661 Mon Sep 17 00:00:00 2001 From: Mariusz Jasuwienas Date: Mon, 10 Nov 2025 14:54:37 +0100 Subject: [PATCH 1/4] feat: transactions data is not always needed when querying for balance Signed-off-by: Mariusz Jasuwienas --- packages/relay/src/lib/clients/mirrorNodeClient.ts | 8 ++++++++ .../ethService/accountService/AccountService.ts | 2 +- packages/relay/tests/lib/eth/eth_getBalance.spec.ts | 13 +++++++------ packages/relay/tests/lib/relay.spec.ts | 2 +- 4 files changed, 17 insertions(+), 8 deletions(-) diff --git a/packages/relay/src/lib/clients/mirrorNodeClient.ts b/packages/relay/src/lib/clients/mirrorNodeClient.ts index f5396af534..a7608713f3 100644 --- a/packages/relay/src/lib/clients/mirrorNodeClient.ts +++ b/packages/relay/src/lib/clients/mirrorNodeClient.ts @@ -563,6 +563,14 @@ export class MirrorNodeClient { ); } + public async getAccountWithoutTransactions(idOrAliasOrEvmAddress: string, requestDetails: RequestDetails) { + return this.get( + `${MirrorNodeClient.GET_ACCOUNTS_BY_ID_ENDPOINT}${idOrAliasOrEvmAddress}?transactions=false`, + MirrorNodeClient.GET_ACCOUNTS_BY_ID_ENDPOINT, + requestDetails, + ); + } + /** * To be used to make paginated calls for the account information when the * transaction count exceeds the constant `MIRROR_NODE_QUERY_LIMIT`. diff --git a/packages/relay/src/lib/services/ethService/accountService/AccountService.ts b/packages/relay/src/lib/services/ethService/accountService/AccountService.ts index dfd55f7f8e..7cd4690cb4 100644 --- a/packages/relay/src/lib/services/ethService/accountService/AccountService.ts +++ b/packages/relay/src/lib/services/ethService/accountService/AccountService.ts @@ -151,7 +151,7 @@ export class AccountService implements IAccountService { if (!balanceFound && !mirrorAccount) { // If no balance and no account, then we need to make a request to the mirror node for the account. - mirrorAccount = await this.mirrorNodeClient.getAccountPageLimit(account, requestDetails); + mirrorAccount = await this.mirrorNodeClient.getAccountWithoutTransactions(account, requestDetails); // Test if exists here if (mirrorAccount !== null && mirrorAccount !== undefined) { balanceFound = true; diff --git a/packages/relay/tests/lib/eth/eth_getBalance.spec.ts b/packages/relay/tests/lib/eth/eth_getBalance.spec.ts index 5da0055efe..720c0effc5 100644 --- a/packages/relay/tests/lib/eth/eth_getBalance.spec.ts +++ b/packages/relay/tests/lib/eth/eth_getBalance.spec.ts @@ -49,7 +49,7 @@ describe('@ethGetBalance using MirrorNode', async function () { it('should return balance from mirror node', async () => { restMock.onGet(BLOCKS_LIMIT_ORDER_URL).reply(200, JSON.stringify(MOCK_BLOCK_NUMBER_1000_RES)); - restMock.onGet(`accounts/${CONTRACT_ADDRESS_1}?limit=100`).reply(200, JSON.stringify(MOCK_BALANCE_RES)); + restMock.onGet(`accounts/${CONTRACT_ADDRESS_1}?transactions=false`).reply(200, JSON.stringify(MOCK_BALANCE_RES)); const resBalance = await ethImpl.getBalance(CONTRACT_ADDRESS_1, 'latest', requestDetails); expect(resBalance).to.equal(DEF_HEX_BALANCE); @@ -58,7 +58,7 @@ describe('@ethGetBalance using MirrorNode', async function () { it('should return balance from mirror node with block number passed as param the same as latest', async () => { const blockNumber = '0x2710'; restMock.onGet(BLOCKS_LIMIT_ORDER_URL).reply(200, JSON.stringify(MOCK_BLOCKS_FOR_BALANCE_RES)); - restMock.onGet(`accounts/${CONTRACT_ADDRESS_1}?limit=100`).reply(200, JSON.stringify(MOCK_BALANCE_RES)); + restMock.onGet(`accounts/${CONTRACT_ADDRESS_1}?transactions=false`).reply(200, JSON.stringify(MOCK_BALANCE_RES)); const resBalance = await ethImpl.getBalance(CONTRACT_ADDRESS_1, blockNumber, requestDetails); expect(resBalance).to.equal(DEF_HEX_BALANCE); @@ -73,7 +73,7 @@ describe('@ethGetBalance using MirrorNode', async function () { number: 10000, }), ); - restMock.onGet(`accounts/${CONTRACT_ADDRESS_1}?limit=100`).reply(200, JSON.stringify(MOCK_BALANCE_RES)); + restMock.onGet(`accounts/${CONTRACT_ADDRESS_1}?transactions=false`).reply(200, JSON.stringify(MOCK_BALANCE_RES)); const resBalance = await ethImpl.getBalance(CONTRACT_ADDRESS_1, blockHash, requestDetails); expect(resBalance).to.equal(DEF_HEX_BALANCE); @@ -82,7 +82,7 @@ describe('@ethGetBalance using MirrorNode', async function () { it('should return balance from mirror node with block number passed as param, one behind latest', async () => { const blockNumber = '0x270F'; restMock.onGet(BLOCKS_LIMIT_ORDER_URL).reply(200, JSON.stringify(MOCK_BLOCKS_FOR_BALANCE_RES)); - restMock.onGet(`accounts/${CONTRACT_ADDRESS_1}?limit=100`).reply(200, JSON.stringify(MOCK_BALANCE_RES)); + restMock.onGet(`accounts/${CONTRACT_ADDRESS_1}?transactions=false`).reply(200, JSON.stringify(MOCK_BALANCE_RES)); const resBalance = await ethImpl.getBalance(CONTRACT_ADDRESS_1, blockNumber, requestDetails); expect(resBalance).to.equal(DEF_HEX_BALANCE); @@ -120,7 +120,7 @@ describe('@ethGetBalance using MirrorNode', async function () { it('should return balance from consensus node', async () => { restMock.onGet(BLOCKS_LIMIT_ORDER_URL).reply(200, JSON.stringify(MOCK_BLOCK_NUMBER_1000_RES)); restMock.onGet(`contracts/${CONTRACT_ADDRESS_1}`).reply(200, JSON.stringify(null)); - restMock.onGet(`accounts/${CONTRACT_ADDRESS_1}?limit=100`).reply(404, JSON.stringify(NOT_FOUND_RES)); + restMock.onGet(`accounts/${CONTRACT_ADDRESS_1}?transactions=false`).reply(404, JSON.stringify(NOT_FOUND_RES)); const resBalance = await ethImpl.getBalance(CONTRACT_ADDRESS_1, 'latest', requestDetails); expect(resBalance).to.equal(constants.ZERO_HEX); @@ -218,7 +218,7 @@ describe('@ethGetBalance using MirrorNode', async function () { restMock.onGet(`blocks/2`).reply(200, JSON.stringify(recentBlock)); restMock.onGet(`blocks/1`).reply(200, JSON.stringify(earlierBlock)); - restMock.onGet(`accounts/${CONTRACT_ID_1}?limit=100`).reply( + restMock.onGet(`accounts/${CONTRACT_ID_1}?transactions=false`).reply( 200, JSON.stringify({ account: CONTRACT_ID_1, @@ -698,6 +698,7 @@ describe('@ethGetBalance using MirrorNode', async function () { }, }; restMock.onGet(`blocks/2`).reply(200, JSON.stringify(recentBlockWithinLastfifteen)); + restMock.onGet(`accounts/${notFoundEvmAddress}?transactions=false`).reply(404, JSON.stringify(NOT_FOUND_RES)); restMock.onGet(`accounts/${notFoundEvmAddress}?limit=100`).reply(404, JSON.stringify(NOT_FOUND_RES)); const resBalance = await ethImpl.getBalance(notFoundEvmAddress, '2', requestDetails); diff --git a/packages/relay/tests/lib/relay.spec.ts b/packages/relay/tests/lib/relay.spec.ts index 48b2a932f5..d4c151aceb 100644 --- a/packages/relay/tests/lib/relay.spec.ts +++ b/packages/relay/tests/lib/relay.spec.ts @@ -124,7 +124,7 @@ describe('Relay', () => { beforeEach(() => { // @ts-expect-error: Property 'operatorAccountId' is private and only accessible within class 'Relay'. operatorId = relay.operatorAccountId!.toString(); - getAccountPageLimitStub = sinon.stub(MirrorNodeClient.prototype, 'getAccountPageLimit'); + getAccountPageLimitStub = sinon.stub(MirrorNodeClient.prototype, 'getAccountWithoutTransactions'); }); afterEach(() => { From ef21a71227afc634315fdb2ae9cc0347c8c82c54 Mon Sep 17 00:00:00 2001 From: Mariusz Jasuwienas Date: Tue, 11 Nov 2025 10:33:47 +0100 Subject: [PATCH 2/4] test: use get account with configurable params instead of multiple similar methods (#4598) Signed-off-by: Mariusz Jasuwienas --- .../relay/src/lib/clients/mirrorNodeClient.ts | 32 +++++++------------ .../accountService/AccountService.ts | 4 +-- packages/relay/src/lib/types/mirrorNode.ts | 5 +++ packages/relay/tests/lib/relay.spec.ts | 2 +- 4 files changed, 20 insertions(+), 23 deletions(-) diff --git a/packages/relay/src/lib/clients/mirrorNodeClient.ts b/packages/relay/src/lib/clients/mirrorNodeClient.ts index a7608713f3..597da865a8 100644 --- a/packages/relay/src/lib/clients/mirrorNodeClient.ts +++ b/packages/relay/src/lib/clients/mirrorNodeClient.ts @@ -17,6 +17,7 @@ import { MirrorNodeClientError } from '../errors/MirrorNodeClientError'; import { SDKClientError } from '../errors/SDKClientError'; import { CacheService } from '../services/cacheService/cacheService'; import { + IAccountRequestParams, IContractCallRequest, IContractCallResponse, IContractLogsResultsParams, @@ -514,13 +515,13 @@ export class MirrorNodeClient { public async getAccount( idOrAliasOrEvmAddress: string, requestDetails: RequestDetails, + queryParamObject: IAccountRequestParams & ILimitOrderParams = { transactions: false }, retries?: number, - timestamp?: string, ) { - const queryParamObject = {}; - this.setQueryParam(queryParamObject, 'timestamp', timestamp); - this.setQueryParam(queryParamObject, 'transactions', 'false'); - const queryParams = this.getQueryParams(queryParamObject); + const queryParamsFiltered = Object.fromEntries( + Object.entries(queryParamObject).filter(([_, value]) => value !== undefined && value !== ''), + ); + const queryParams = this.getQueryParams(queryParamsFiltered); return this.get( `${MirrorNodeClient.GET_ACCOUNTS_BY_ID_ENDPOINT}${idOrAliasOrEvmAddress}${queryParams}`, MirrorNodeClient.GET_ACCOUNTS_BY_ID_ENDPOINT, @@ -555,20 +556,11 @@ export class MirrorNodeClient { ); } - public async getAccountPageLimit(idOrAliasOrEvmAddress: string, requestDetails: RequestDetails) { - return this.get( - `${MirrorNodeClient.GET_ACCOUNTS_BY_ID_ENDPOINT}${idOrAliasOrEvmAddress}?limit=${constants.MIRROR_NODE_QUERY_LIMIT}`, - MirrorNodeClient.GET_ACCOUNTS_BY_ID_ENDPOINT, - requestDetails, - ); - } - - public async getAccountWithoutTransactions(idOrAliasOrEvmAddress: string, requestDetails: RequestDetails) { - return this.get( - `${MirrorNodeClient.GET_ACCOUNTS_BY_ID_ENDPOINT}${idOrAliasOrEvmAddress}?transactions=false`, - MirrorNodeClient.GET_ACCOUNTS_BY_ID_ENDPOINT, - requestDetails, - ); + public async getAccountWithTransactions(idOrAliasOrEvmAddress: string, requestDetails: RequestDetails) { + return this.getAccount(idOrAliasOrEvmAddress, requestDetails, { + limit: constants.MIRROR_NODE_QUERY_LIMIT, + transactions: true, + }); } /** @@ -1420,7 +1412,7 @@ export class MirrorNodeClient { const promises = [ searchableTypes.includes(constants.TYPE_ACCOUNT) ? buildPromise( - this.getAccount(entityIdentifier, requestDetails, retries, timestamp).catch(() => { + this.getAccount(entityIdentifier, requestDetails, { timestamp }, retries).catch(() => { return null; }), ) diff --git a/packages/relay/src/lib/services/ethService/accountService/AccountService.ts b/packages/relay/src/lib/services/ethService/accountService/AccountService.ts index 7cd4690cb4..65d0c38920 100644 --- a/packages/relay/src/lib/services/ethService/accountService/AccountService.ts +++ b/packages/relay/src/lib/services/ethService/accountService/AccountService.ts @@ -151,7 +151,7 @@ export class AccountService implements IAccountService { if (!balanceFound && !mirrorAccount) { // If no balance and no account, then we need to make a request to the mirror node for the account. - mirrorAccount = await this.mirrorNodeClient.getAccountWithoutTransactions(account, requestDetails); + mirrorAccount = await this.mirrorNodeClient.getAccount(account, requestDetails); // Test if exists here if (mirrorAccount !== null && mirrorAccount !== undefined) { balanceFound = true; @@ -265,7 +265,7 @@ export class AccountService implements IAccountService { else { let currentBalance = 0; let balanceFromTxs = 0; - mirrorAccount = await this.mirrorNodeClient.getAccountPageLimit(account, requestDetails); + mirrorAccount = await this.mirrorNodeClient.getAccountWithTransactions(account, requestDetails); if (mirrorAccount) { if (mirrorAccount.balance) { currentBalance = mirrorAccount.balance.balance; diff --git a/packages/relay/src/lib/types/mirrorNode.ts b/packages/relay/src/lib/types/mirrorNode.ts index f3565b16a5..999024a12b 100644 --- a/packages/relay/src/lib/types/mirrorNode.ts +++ b/packages/relay/src/lib/types/mirrorNode.ts @@ -27,6 +27,11 @@ export interface ILimitOrderParams { order?: string; } +export interface IAccountRequestParams { + timestamp?: string; + transactions?: boolean; +} + export interface IContractResultsParams { blockHash?: string; blockNumber?: number; diff --git a/packages/relay/tests/lib/relay.spec.ts b/packages/relay/tests/lib/relay.spec.ts index d4c151aceb..e4149cf08e 100644 --- a/packages/relay/tests/lib/relay.spec.ts +++ b/packages/relay/tests/lib/relay.spec.ts @@ -124,7 +124,7 @@ describe('Relay', () => { beforeEach(() => { // @ts-expect-error: Property 'operatorAccountId' is private and only accessible within class 'Relay'. operatorId = relay.operatorAccountId!.toString(); - getAccountPageLimitStub = sinon.stub(MirrorNodeClient.prototype, 'getAccountWithoutTransactions'); + getAccountPageLimitStub = sinon.stub(MirrorNodeClient.prototype, 'getAccount'); }); afterEach(() => { From 74ab138a46dbb873e8a773b9936ae9267cac9026 Mon Sep 17 00:00:00 2001 From: Mariusz Jasuwienas Date: Tue, 11 Nov 2025 10:40:40 +0100 Subject: [PATCH 3/4] test: no need to submit transactions key when its equal to true (#4598) Signed-off-by: Mariusz Jasuwienas --- packages/relay/src/lib/clients/mirrorNodeClient.ts | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/packages/relay/src/lib/clients/mirrorNodeClient.ts b/packages/relay/src/lib/clients/mirrorNodeClient.ts index 597da865a8..abebacd3f5 100644 --- a/packages/relay/src/lib/clients/mirrorNodeClient.ts +++ b/packages/relay/src/lib/clients/mirrorNodeClient.ts @@ -519,7 +519,10 @@ export class MirrorNodeClient { retries?: number, ) { const queryParamsFiltered = Object.fromEntries( - Object.entries(queryParamObject).filter(([_, value]) => value !== undefined && value !== ''), + Object.entries(queryParamObject).filter(([key, value]) => { + if (key === 'transactions' && value) return false; + return value !== undefined && value !== ''; + }), ); const queryParams = this.getQueryParams(queryParamsFiltered); return this.get( From 244955c09cea7c969e3941428119007356772a10 Mon Sep 17 00:00:00 2001 From: Mariusz Jasuwienas Date: Tue, 11 Nov 2025 11:29:18 +0100 Subject: [PATCH 4/4] test: using new implementation of getaccounts whenever possible (#4598) Signed-off-by: Mariusz Jasuwienas --- .../relay/src/lib/clients/mirrorNodeClient.ts | 33 ++++--------------- .../accountService/AccountService.ts | 5 ++- packages/relay/src/lib/types/mirrorNode.ts | 1 + 3 files changed, 12 insertions(+), 27 deletions(-) diff --git a/packages/relay/src/lib/clients/mirrorNodeClient.ts b/packages/relay/src/lib/clients/mirrorNodeClient.ts index abebacd3f5..1fad449d85 100644 --- a/packages/relay/src/lib/clients/mirrorNodeClient.ts +++ b/packages/relay/src/lib/clients/mirrorNodeClient.ts @@ -44,7 +44,7 @@ export class MirrorNodeClient { private static readonly GET_CONTRACT_ENDPOINT = 'contracts/'; private static readonly CONTRACT_RESULT_LOGS_PROPERTY = 'logs'; private static readonly CONTRACT_ID_PLACEHOLDER = '{contractId}'; - private static readonly ACCOUNT_TIMESTAMP_PROPERTY = 'timestamp'; + private static readonly ACCOUNT_TRANSACTIONS_PROPERTY = 'transactions'; private static readonly CONTRACT_CALL_ENDPOINT = 'contracts/call'; private static readonly GET_ACCOUNTS_BY_ID_ENDPOINT = 'accounts/'; private static readonly GET_NETWORK_FEES_ENDPOINT = 'network/fees'; @@ -52,7 +52,6 @@ export class MirrorNodeClient { private static readonly TRANSACTION_ID_PLACEHOLDER = '{transactionId}'; private static readonly GET_CONTRACT_RESULT_ENDPOINT = 'contracts/results/'; private static readonly GET_CONTRACT_RESULTS_ENDPOINT = 'contracts/results'; - private static readonly ACCOUNT_TRANSACTION_TYPE_PROPERTY = 'transactiontype'; private static readonly GET_NETWORK_EXCHANGERATE_ENDPOINT = 'network/exchangerate'; private static readonly GET_CONTRACT_RESULT_LOGS_ENDPOINT = 'contracts/results/logs'; private static readonly CONTRACT_ADDRESS_STATE_ENDPOINT = `contracts/${MirrorNodeClient.ADDRESS_PLACEHOLDER}/state`; @@ -520,7 +519,7 @@ export class MirrorNodeClient { ) { const queryParamsFiltered = Object.fromEntries( Object.entries(queryParamObject).filter(([key, value]) => { - if (key === 'transactions' && value) return false; + if (key === MirrorNodeClient.ACCOUNT_TRANSACTIONS_PROPERTY && value) return false; return value !== undefined && value !== ''; }), ); @@ -539,30 +538,11 @@ export class MirrorNodeClient { requestDetails: RequestDetails, numberOfTransactions: number = 1, ) { - const queryParamObject = {}; - this.setQueryParam( - queryParamObject, - MirrorNodeClient.ACCOUNT_TRANSACTION_TYPE_PROPERTY, - MirrorNodeClient.ETHEREUM_TRANSACTION_TYPE, - ); - this.setQueryParam(queryParamObject, MirrorNodeClient.ACCOUNT_TIMESTAMP_PROPERTY, `lte:${timestampTo}`); - this.setLimitOrderParams( - queryParamObject, - this.getLimitOrderQueryParam(numberOfTransactions, constants.ORDER.DESC), - ); // get latest 2 transactions to infer for single case - const queryParams = this.getQueryParams(queryParamObject); - - return this.get( - `${MirrorNodeClient.GET_ACCOUNTS_BY_ID_ENDPOINT}${idOrAliasOrEvmAddress}${queryParams}`, - MirrorNodeClient.GET_ACCOUNTS_BY_ID_ENDPOINT, - requestDetails, - ); - } - - public async getAccountWithTransactions(idOrAliasOrEvmAddress: string, requestDetails: RequestDetails) { return this.getAccount(idOrAliasOrEvmAddress, requestDetails, { - limit: constants.MIRROR_NODE_QUERY_LIMIT, + transactiontype: MirrorNodeClient.ETHEREUM_TRANSACTION_TYPE, + timestamp: `lte:${timestampTo}`, transactions: true, + ...this.getLimitOrderQueryParam(numberOfTransactions, constants.ORDER.DESC), }); } @@ -1412,10 +1392,11 @@ export class MirrorNodeClient { let data; try { + const params = { timestamp, transactions: false }; const promises = [ searchableTypes.includes(constants.TYPE_ACCOUNT) ? buildPromise( - this.getAccount(entityIdentifier, requestDetails, { timestamp }, retries).catch(() => { + this.getAccount(entityIdentifier, requestDetails, params, retries).catch(() => { return null; }), ) diff --git a/packages/relay/src/lib/services/ethService/accountService/AccountService.ts b/packages/relay/src/lib/services/ethService/accountService/AccountService.ts index 65d0c38920..e4bd696648 100644 --- a/packages/relay/src/lib/services/ethService/accountService/AccountService.ts +++ b/packages/relay/src/lib/services/ethService/accountService/AccountService.ts @@ -265,7 +265,10 @@ export class AccountService implements IAccountService { else { let currentBalance = 0; let balanceFromTxs = 0; - mirrorAccount = await this.mirrorNodeClient.getAccountWithTransactions(account, requestDetails); + mirrorAccount = await this.mirrorNodeClient.getAccount(account, requestDetails, { + limit: constants.MIRROR_NODE_QUERY_LIMIT, + transactions: true, + }); if (mirrorAccount) { if (mirrorAccount.balance) { currentBalance = mirrorAccount.balance.balance; diff --git a/packages/relay/src/lib/types/mirrorNode.ts b/packages/relay/src/lib/types/mirrorNode.ts index 999024a12b..43a92be1e7 100644 --- a/packages/relay/src/lib/types/mirrorNode.ts +++ b/packages/relay/src/lib/types/mirrorNode.ts @@ -30,6 +30,7 @@ export interface ILimitOrderParams { export interface IAccountRequestParams { timestamp?: string; transactions?: boolean; + [key: string]: string | boolean | number | undefined; // Allow dynamic params } export interface IContractResultsParams {