@exodus/ethereum-api 8.76.7 → 8.77.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -3,6 +3,15 @@
3
3
  All notable changes to this project will be documented in this file.
4
4
  See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
5
5
 
6
+ ## [8.77.0](https://github.com/ExodusMovement/assets/compare/@exodus/ethereum-api@8.76.7...@exodus/ethereum-api@8.77.0) (2026-06-29)
7
+
8
+
9
+ ### Features
10
+
11
+ * **ethereum:** implement getHistoricalBalance via clarity absolute balance ([#8281](https://github.com/ExodusMovement/assets/issues/8281)) ([be202a9](https://github.com/ExodusMovement/assets/commit/be202a94d9734d7af8ed118d11d4cc38c3b5ef10))
12
+
13
+
14
+
6
15
  ## [8.76.7](https://github.com/ExodusMovement/assets/compare/@exodus/ethereum-api@8.76.6...@exodus/ethereum-api@8.76.7) (2026-06-26)
7
16
 
8
17
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@exodus/ethereum-api",
3
- "version": "8.76.7",
3
+ "version": "8.77.0",
4
4
  "description": "Transaction monitors, fee monitors, RPC with the blockchain node, and other networking code for Ethereum and EVM-based blockchains",
5
5
  "type": "module",
6
6
  "main": "src/index.js",
@@ -69,5 +69,5 @@
69
69
  "type": "git",
70
70
  "url": "git+https://github.com/ExodusMovement/assets.git"
71
71
  },
72
- "gitHead": "c704777d72888bf3a94f3a7ddc1b0e57cda3e6dc"
72
+ "gitHead": "f156b9209a42ef331625b508a908659fd495c3e4"
73
73
  }
@@ -41,6 +41,7 @@ import { createFeeData } from './fee-data/index.js'
41
41
  import { createGetBalanceForAddress } from './get-balance-for-address.js'
42
42
  import { getBalancesFactory } from './get-balances.js'
43
43
  import { getFeeFactory } from './get-fee.js'
44
+ import { getHistoricalBalanceFactory } from './get-historical-balance.js'
44
45
  import { moveFundsFactory } from './move-funds.js'
45
46
  import { estimateL1DataFeeFactory, getL1GetFeeFactory } from './optimism-gas/index.js'
46
47
  import { createSendValidations } from './send-validations.js'
@@ -177,12 +178,20 @@ export const createAssetFactory = ({
177
178
  rpcBalanceAssetNames,
178
179
  })
179
180
 
181
+ const historicalBalanceSupported = monitorType !== 'no-history'
182
+ const getHistoricalBalance = getHistoricalBalanceFactory({
183
+ assetClientInterface,
184
+ getBalances,
185
+ })
186
+
180
187
  const { createToken, getTokens } = createTokenFactory(
181
188
  {
182
189
  address,
183
190
  bip44,
184
191
  keys,
185
192
  getBalances,
193
+ getHistoricalBalance,
194
+ historicalBalance: historicalBalanceSupported,
186
195
  assetClientInterface,
187
196
  server,
188
197
  stakingConfiguration,
@@ -225,6 +234,7 @@ export const createAssetFactory = ({
225
234
  family: ASSET_FAMILY.EVM,
226
235
  feeMonitor: true,
227
236
  feesApi: true,
237
+ historicalBalance: historicalBalanceSupported,
228
238
  isMaxFeeAsset,
229
239
  isTestnet,
230
240
  moveFunds: true,
@@ -336,6 +346,7 @@ export const createAssetFactory = ({
336
346
  features,
337
347
  getActivityTxs,
338
348
  getBalances,
349
+ ...(features.historicalBalance && { getHistoricalBalance }),
339
350
  getBalanceForAddress: createGetBalanceForAddress({ asset, server }),
340
351
  ...(getBlackListStatus && { getBlackListStatus }),
341
352
  getConfirmationsNumber: () => confirmationsNumber,
@@ -37,6 +37,8 @@ const getActivityTxs = ({ txs }) => txs.filter((tx) => !smallbalanceTx(tx))
37
37
  const getCreateBaseToken =
38
38
  ({
39
39
  getBalances,
40
+ getHistoricalBalance,
41
+ historicalBalance,
40
42
  assetClientInterface,
41
43
  server,
42
44
  stakingConfiguration = Object.create(null),
@@ -53,6 +55,7 @@ const getCreateBaseToken =
53
55
  features: tokenFeatures,
54
56
  hasFeature: (feature) => !!tokenFeatures[feature], // @deprecated use api.features instead
55
57
  getBalances,
58
+ ...(historicalBalance && { getHistoricalBalance }),
56
59
  getTxLogFilter: name === 'polygon' ? getPolygonTxLogFilter : getTxLogFilter,
57
60
  }
58
61
 
@@ -0,0 +1,58 @@
1
+ import { TxSet } from '@exodus/models'
2
+ import assert from 'minimalistic-assert'
3
+
4
+ import { getLatestCanonicalAbsoluteBalanceTx } from './tx-log/clarity-utils/index.js'
5
+
6
+ const walkBackFromBalance = ({ txLog, deadline, balance }) => {
7
+ let result = balance
8
+
9
+ for (const tx of txLog) {
10
+ if (new Date(tx.date).getTime() <= deadline) continue
11
+ if (tx.failed || tx.dropped || tx.pending || tx.data?.replacedBy) continue
12
+
13
+ result = result.sub(tx.coinAmount)
14
+ if (tx.feeAmount?.unitType.equals(tx.coinAmount.unitType)) {
15
+ result = result.add(tx.feeAmount)
16
+ }
17
+ }
18
+
19
+ return result.clampLowerZero()
20
+ }
21
+
22
+ /**
23
+ * Builds the Ethereum-like balance as of `date`. Anchors on the latest Clarity
24
+ * absolute-balance snapshot at or before the date, falling back to a walk-back
25
+ * from the current balance when no snapshot is available.
26
+ */
27
+ export const getHistoricalBalanceFactory = ({ assetClientInterface, getBalances }) => {
28
+ assert(assetClientInterface, 'assetClientInterface is required')
29
+ assert(getBalances, 'getBalances is required')
30
+
31
+ return async ({ asset, walletAccount, date }) => {
32
+ assert(asset, 'asset is required')
33
+ assert(walletAccount, 'walletAccount is required')
34
+ assert(date instanceof Date, 'date is required')
35
+
36
+ const txLog = await assetClientInterface.getTxLog({ assetName: asset.name, walletAccount })
37
+ const deadline = date.getTime()
38
+ const anchor = getLatestCanonicalAbsoluteBalanceTx({ reversedTxLog: txLog.reverse(), deadline })
39
+
40
+ if (anchor) {
41
+ const total = asset.currency.baseUnit(anchor.data.balanceChange.to)
42
+ return { total, entries: [{ amount: total, date: deadline }] }
43
+ }
44
+
45
+ const accountState = await assetClientInterface.getAccountState({
46
+ assetName: asset.baseAsset.name,
47
+ walletAccount,
48
+ })
49
+ const confirmedTxLog = TxSet.fromArray([...txLog].filter((tx) => !tx.pending))
50
+ const balance = getBalances({ asset, accountState, txLog: confirmedTxLog }).spendable
51
+ const total = walkBackFromBalance({ txLog: confirmedTxLog, deadline, balance })
52
+
53
+ return {
54
+ total,
55
+ entries: [{ amount: total, date: deadline }],
56
+ }
57
+ }
58
+ }
package/src/index.js CHANGED
@@ -38,6 +38,7 @@ export {
38
38
  } from './tx-log/index.js'
39
39
 
40
40
  export { getStakingHistoryBalance, getBalancesFactory } from './get-balances.js'
41
+ export { getHistoricalBalanceFactory } from './get-historical-balance.js'
41
42
 
42
43
  export {
43
44
  FantomStaking,
@@ -50,12 +50,15 @@ const getLatestCanonicalAbsoluteTx = ({
50
50
  searchDepthMs = DEFAULT_CANONICAL_ABSOLUTE_TX_SEARCH_DEPTH,
51
51
  reversedTxLog,
52
52
  fieldName,
53
+ deadline = Infinity,
53
54
  }) => {
54
55
  assert(reversedTxLog, 'expected reversedTxLog')
55
56
 
56
57
  let latest = null
57
58
 
58
59
  for (const tx of reversedTxLog) {
60
+ if (+tx.date > deadline) continue
61
+
59
62
  if (latest) {
60
63
  const diff = +latest.date - +tx.date
61
64
 
@@ -77,10 +80,11 @@ const getLatestCanonicalAbsoluteTx = ({
77
80
  return latest
78
81
  }
79
82
 
80
- export const getLatestCanonicalAbsoluteBalanceTx = ({ searchDepthMs, reversedTxLog }) =>
83
+ export const getLatestCanonicalAbsoluteBalanceTx = ({ searchDepthMs, reversedTxLog, deadline }) =>
81
84
  getLatestCanonicalAbsoluteTx({
82
85
  searchDepthMs,
83
86
  reversedTxLog,
87
+ deadline,
84
88
  fieldName: ABSOLUTE_FIELD_NAME_BALANCE_CHANGE,
85
89
  })
86
90