@exodus/ethereum-api 8.59.0 → 8.59.2

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,28 @@
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.59.2](https://github.com/ExodusMovement/assets/compare/@exodus/ethereum-api@8.59.1...@exodus/ethereum-api@8.59.2) (2025-11-19)
7
+
8
+
9
+ ### Bug Fixes
10
+
11
+
12
+ * fix: correct absolute balance logic for deeply nested pending transactions (#6966)
13
+
14
+ * fix: drop pending receive amounts from absolute spendable balances (#6958)
15
+
16
+
17
+
18
+ ## [8.59.1](https://github.com/ExodusMovement/assets/compare/@exodus/ethereum-api@8.59.0...@exodus/ethereum-api@8.59.1) (2025-11-10)
19
+
20
+
21
+ ### Bug Fixes
22
+
23
+
24
+ * Fix: add "send all" pathway to ethereum staking (#6667)
25
+
26
+
27
+
6
28
  ## [8.59.0](https://github.com/ExodusMovement/assets/compare/@exodus/ethereum-api@8.57.0...@exodus/ethereum-api@8.59.0) (2025-11-10)
7
29
 
8
30
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@exodus/ethereum-api",
3
- "version": "8.59.0",
3
+ "version": "8.59.2",
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",
@@ -67,5 +67,5 @@
67
67
  "type": "git",
68
68
  "url": "git+https://github.com/ExodusMovement/assets.git"
69
69
  },
70
- "gitHead": "94c7625c1fb029ef261d0ef61e32c7c66d359812"
70
+ "gitHead": "d1505076f4e06aad8f7962d9fad12354c020a30c"
71
71
  }
@@ -27,6 +27,15 @@ export const getAbsoluteBalance = ({ asset, txLog }) => {
27
27
  break
28
28
  }
29
29
 
30
+ // NOTE: Avoids processing any unconfirmed balances, since
31
+ // `getAbsoluteBalance` only processes a subset of
32
+ // transactions up until the first `balanceChange` event
33
+ // is encountered, but `getBalancesFactory` takes all
34
+ // all pending amounts into consideration (i.e. those
35
+ // at a greater depth than the most recent `balanceChange`
36
+ // event).
37
+ if (!tx.failed && tx.pending) continue
38
+
30
39
  if (!(tx.dropped || tx.data.replacedBy)) {
31
40
  if (!tx.error) {
32
41
  balance = balance.add(tx.coinAmount)
@@ -128,21 +137,29 @@ export const getBalancesFactory = ({ monitorType, useAbsoluteBalance, rpcBalance
128
137
  const unstaking = canClaimUndelegatedBalance ? asset.currency.ZERO : unclaimedUndelegatedBalance
129
138
  const unstaked = canClaimUndelegatedBalance ? unclaimedUndelegatedBalance : asset.currency.ZERO
130
139
 
131
- let total
132
140
  let spendable
133
141
 
134
142
  // Balance from accountState is considered total b/c is fetched from rpc
135
143
  if (rpcBalanceAssetNames.includes(asset.name) || monitorType === 'no-history') {
136
- total = getBalanceFromAccountState({ asset, accountState }).sub(unconfirmedSent)
137
- spendable = total.sub(staked).sub(staking).sub(unstaking).sub(unstaked)
144
+ // NOTE: Balances from a `no-history` monitor work like calls
145
+ // to `balanceOf`/`eth_getBalance` at the latest block tag,
146
+ // therefore there's no need to subtract `unconfirmedReceived`.
147
+ //
148
+ // Likewise, they do not compensate for pending sends, so we
149
+ // must manually subtract the `unconfirmedSent` to produce a
150
+ // pessimistic reading that's safe for consumers.
151
+ spendable = getBalanceFromAccountState({ asset, accountState }).sub(unconfirmedSent)
138
152
  } else {
139
- // Balance from txLog does not include staking rewards
140
- // spendable and total are calculated differently based on staking txs
141
153
  const absoluteBalance = useAbsoluteBalance && getAbsoluteBalance({ asset, txLog })
142
154
 
143
155
  if (absoluteBalance) {
144
- spendable = absoluteBalance
156
+ // NOTE: The returned `absoluteBalance` returns only confirmed
157
+ // values similar to the `'no-history'` case, so we need
158
+ // to reduce this amount by the `unconfirmedSent`.
159
+ spendable = absoluteBalance.sub(unconfirmedSent)
145
160
  } else {
161
+ // Balance from txLog does not include staking rewards
162
+ // spendable and total are calculated differently based on staking txs
146
163
  spendable = getSpendable({
147
164
  asset,
148
165
  balance: getBalanceFromTxLog({ txLog, asset }),
@@ -150,10 +167,9 @@ export const getBalancesFactory = ({ monitorType, useAbsoluteBalance, rpcBalance
150
167
  unconfirmedReceived,
151
168
  })
152
169
  }
153
-
154
- total = spendable.add(staked).add(staking).add(unstaking).add(unstaked)
155
170
  }
156
171
 
172
+ const total = spendable.add(staked).add(staking).add(unstaking).add(unstaked)
157
173
  const stakeable = spendable
158
174
 
159
175
  return {
@@ -60,7 +60,7 @@ export function createEthereumStakingService({
60
60
  return { delegatorAddress, feeData }
61
61
  }
62
62
 
63
- async function delegate({ walletAccount, amount, feeData } = Object.create(null)) {
63
+ async function delegate({ walletAccount, amount, feeData, isDelegateAll } = Object.create(null)) {
64
64
  const asset = await getAsset(assetName)
65
65
  const staking = getStakingApi(asset)
66
66
  amount = amountToCurrency({ asset, amount })
@@ -76,7 +76,7 @@ export function createEthereumStakingService({
76
76
  })}`
77
77
  )
78
78
 
79
- const { gasPrice, gasLimit, tipGasPrice } = await estimateTxFee({
79
+ const { gasPrice, gasLimit, tipGasPrice, fee } = await estimateTxFee({
80
80
  from: delegatorAddress,
81
81
  to,
82
82
  amount,
@@ -84,6 +84,15 @@ export function createEthereumStakingService({
84
84
  feeData,
85
85
  })
86
86
 
87
+ // If we are delegating all, we need to subtract the estimated ETH cost from the amount
88
+ // Better to do it here than from the mobile app where fees can change before the transaction is sent
89
+ if (isDelegateAll) {
90
+ const minAmount = getMinAmount(asset)
91
+ amount = amount.sub(fee)
92
+ // Re-validate that the amount after fee subtraction is still above the minimum
93
+ assert(amount.gte(minAmount), `Min Amount ${minAmount}`)
94
+ }
95
+
87
96
  const txId = await prepareAndSendTx({
88
97
  asset,
89
98
  walletAccount,