From 48fa4ad1f4c262a98f43298cffb326fc0ab8b35d Mon Sep 17 00:00:00 2001 From: Denis Orlov Date: Fri, 27 Feb 2026 18:59:21 +0300 Subject: [PATCH 1/2] [etherscan] Filter tiny movements to avoid TSN validation errors --- .../converters/token-transactions.test.ts | 54 +++++++++++++++++++ src/plugins/etherscan/ether/converters.ts | 8 ++- src/plugins/etherscan/tokens/converters.ts | 8 ++- 3 files changed, 68 insertions(+), 2 deletions(-) create mode 100644 src/plugins/etherscan/__tests__/converters/token-transactions.test.ts diff --git a/src/plugins/etherscan/__tests__/converters/token-transactions.test.ts b/src/plugins/etherscan/__tests__/converters/token-transactions.test.ts new file mode 100644 index 000000000..f8b1267bb --- /dev/null +++ b/src/plugins/etherscan/__tests__/converters/token-transactions.test.ts @@ -0,0 +1,54 @@ +import { convertTransactions } from '../../tokens/converters' +import { ETHER_MAINNET } from '../../common/config' +import type { TokenAccount, TokenTransaction } from '../../tokens/types' + +const account: TokenAccount = { + id: '0xaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa', + balance: 0, + contractAddress: '0xdac17f958d2ee523a2206206994597c13d831ec7' +} + +const baseTransaction: TokenTransaction = { + blockNumber: '1', + timeStamp: '1658608646', + hash: '0x90bb0dcbe8fa38387145aa17d6ad99f57da91d4c6d4b65b5f7cf56454f73234b', + nonce: '1', + blockHash: '0x1', + from: '0x1111111111111111111111111111111111111111', + contractAddress: '0xdac17f958d2ee523a2206206994597c13d831ec7', + to: account.id, + value: '1000000', + tokenName: 'Tether USD', + tokenSymbol: 'USDT', + tokenDecimal: '6', + transactionIndex: '1', + gas: '21000', + gasPrice: '1000000000', + gasUsed: '21000', + cumulativeGasUsed: '21000', + input: 'deprecated', + confirmations: '1' +} + +describe('token convertTransactions', () => { + it('filters out movements with absolute value below 0.01', () => { + const list = convertTransactions( + account, + [{ ...baseTransaction, value: '1' }], + ETHER_MAINNET + ) + + expect(list).toHaveLength(0) + }) + + it('keeps movements with absolute value at least 0.01', () => { + const list = convertTransactions( + account, + [{ ...baseTransaction, value: '10000' }], + ETHER_MAINNET + ) + + expect(list).toHaveLength(1) + expect(list[0].movements[0].sum).toBe(0.01) + }) +}) diff --git a/src/plugins/etherscan/ether/converters.ts b/src/plugins/etherscan/ether/converters.ts index c62bd69c9..b93dc2635 100644 --- a/src/plugins/etherscan/ether/converters.ts +++ b/src/plugins/etherscan/ether/converters.ts @@ -6,6 +6,8 @@ import { import { ETHER_MAINNET, Instruments } from '../common/config' import type { EthereumAccount, EthereumTransaction } from './types' +const MIN_MOVEMENT_SUM = 0.01 + function convertWeiToUETH (value: number): number { return Math.round(value / 10 ** 12) } @@ -85,7 +87,11 @@ export function convertTransactions ( const list = transactions .map((transaction) => convertTransaction(account, transaction)) .filter((transaction): transaction is Transaction => - Boolean(transaction?.movements.some((movement) => movement.sum !== 0)) + Boolean( + transaction?.movements.some((movement) => + movement.sum !== null && Math.abs(movement.sum) >= MIN_MOVEMENT_SUM + ) + ) ) return list diff --git a/src/plugins/etherscan/tokens/converters.ts b/src/plugins/etherscan/tokens/converters.ts index 5b8c1af78..91dc15dbb 100644 --- a/src/plugins/etherscan/tokens/converters.ts +++ b/src/plugins/etherscan/tokens/converters.ts @@ -10,6 +10,8 @@ import { ETHER_MAINNET } from '../common/config' import { getTransactionFee } from '../ether/converters' import type { EthereumTransaction } from '../ether/types' +const MIN_MOVEMENT_SUM = 0.01 + function convertAccount (account: TokenAccount, chain: Chain): Account | null { const token = SUPPORTED_TOKENS[chain].find( (token) => token.contractAddress === account.contractAddress @@ -128,7 +130,11 @@ export function convertTransactions ( const list = transactions .flatMap((transaction) => convertTransaction(account, transaction, chain)) .filter((transaction): transaction is Transaction => - Boolean(transaction?.movements.some((movement) => movement.sum !== 0)) + Boolean( + transaction?.movements.some((movement) => + movement.sum !== null && Math.abs(movement.sum) >= MIN_MOVEMENT_SUM + ) + ) ) return list From af807dbb1ada3629e17a41ded004619f7603ba72 Mon Sep 17 00:00:00 2001 From: Denis Orlov Date: Fri, 27 Feb 2026 21:47:28 +0300 Subject: [PATCH 2/2] [etherscan] Avoid fee-only records for tiny token payments --- .../converters/token-transactions.test.ts | 15 +++++++++++++++ src/plugins/etherscan/tokens/converters.ts | 4 ++++ 2 files changed, 19 insertions(+) diff --git a/src/plugins/etherscan/__tests__/converters/token-transactions.test.ts b/src/plugins/etherscan/__tests__/converters/token-transactions.test.ts index f8b1267bb..33ec7b6fd 100644 --- a/src/plugins/etherscan/__tests__/converters/token-transactions.test.ts +++ b/src/plugins/etherscan/__tests__/converters/token-transactions.test.ts @@ -41,6 +41,21 @@ describe('token convertTransactions', () => { expect(list).toHaveLength(0) }) + it('does not leave fee-only transaction when payment amount is below 0.01', () => { + const list = convertTransactions( + account, + [{ + ...baseTransaction, + from: account.id, + to: '0xbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb', + value: '1' + }], + ETHER_MAINNET + ) + + expect(list).toHaveLength(0) + }) + it('keeps movements with absolute value at least 0.01', () => { const list = convertTransactions( account, diff --git a/src/plugins/etherscan/tokens/converters.ts b/src/plugins/etherscan/tokens/converters.ts index 91dc15dbb..5e0b93185 100644 --- a/src/plugins/etherscan/tokens/converters.ts +++ b/src/plugins/etherscan/tokens/converters.ts @@ -66,6 +66,10 @@ export function convertTransaction ( const operationValue = token.convertBalance(Number(transaction.value)) const { gas, hash, timeStamp } = transaction + if (Math.abs(operationValue) < MIN_MOVEMENT_SUM) { + return null + } + const transactions = [] const TokenTransaction: Transaction = { hold: null,