@exodus/ethereum-api 8.66.0 → 8.67.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,21 +3,29 @@
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.66.0](https://github.com/ExodusMovement/assets/compare/@exodus/ethereum-api@8.64.5...@exodus/ethereum-api@8.66.0) (2026-03-04)
6
+ ## [8.67.0](https://github.com/ExodusMovement/assets/compare/@exodus/ethereum-api@8.66.0...@exodus/ethereum-api@8.67.0) (2026-03-09)
7
7
 
8
8
 
9
9
  ### Features
10
10
 
11
11
 
12
- * feat: add USDT blacklist status APIs for ETH and TRX and expose TRX raw account (#7444)
12
+ * feat: add hasLostPermission api method to asset plugins (#7524)
13
13
 
14
14
 
15
15
  ### Bug Fixes
16
16
 
17
17
 
18
- * fix: avoid race conditions resulting in a `tipGasPrice` of `0` where possible (#7458)
18
+ * fix: queued/invalid Ethereum transactions being offered for RBF acceleration (#7463)
19
19
 
20
- * fix: harden EthlikeError and improve error handling in tx-send (#7308)
20
+
21
+
22
+ ## [8.66.0](https://github.com/ExodusMovement/assets/compare/@exodus/ethereum-api@8.65.0...@exodus/ethereum-api@8.66.0) (2026-03-04)
23
+
24
+
25
+ ### Features
26
+
27
+
28
+ * feat: add USDT blacklist status APIs for ETH and TRX and expose TRX raw account (#7444)
21
29
 
22
30
 
23
31
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@exodus/ethereum-api",
3
- "version": "8.66.0",
3
+ "version": "8.67.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": "c715aabef7146610ab562c184a971c675f6d088f"
72
+ "gitHead": "f27651709a0a2c70f3c0867afc543fcfa5cfd5c2"
73
73
  }
@@ -272,6 +272,11 @@ export const createAssetFactory = ({
272
272
  addressHasHistory,
273
273
  broadcastTx: (...args) => server.sendRawTransaction(...args),
274
274
  createAccountState: () => accountStateClass,
275
+ hasLostPermission: ({ accountState }) => {
276
+ if (!eip7702Supported) return false
277
+ const delegation = accountState?.eip7702Delegation
278
+ return Boolean(delegation?.isDelegated) && !delegation?.isWhitelisted
279
+ },
275
280
  createFeeMonitor,
276
281
  createHistoryMonitor,
277
282
  createToken,
package/src/tx-create.js CHANGED
@@ -159,6 +159,63 @@ const resolveTxFactoryGasPrices = async ({
159
159
  }
160
160
  }
161
161
 
162
+ // Pre-broadcast safety check for RBF replacements.
163
+ //
164
+ // Clarity can lag behind the node: it may still report a tx as pending after
165
+ // the node has evicted it, or show a queued (gapped) tx as acceleratable.
166
+ // Broadcasting an RBF for a gapped tx — one whose predecessor slot is empty in
167
+ // the node's mempool — produces ErrFutureReplacePending.
168
+ //
169
+ // This check is best-effort: if either RPC call fails (network error, unsupported
170
+ // endpoint, etc.) we fall through and let the broadcast surface the error.
171
+ async function verifyBumpTxCanReplace({ baseAsset, bumpTxId, fromAddress, nonce }) {
172
+ let rpcTx, pendingNonceHex
173
+
174
+ try {
175
+ ;[rpcTx, pendingNonceHex] = await Promise.all([
176
+ baseAsset.server.getTransactionByHash(bumpTxId),
177
+ baseAsset.server.getTransactionCount(fromAddress, 'pending'),
178
+ ])
179
+ } catch (err) {
180
+ console.warn(
181
+ `verifyBumpTxCanReplace: pre-broadcast RPC check failed for ${bumpTxId}, falling through`,
182
+ err.message
183
+ )
184
+ return
185
+ }
186
+
187
+ if (!rpcTx) {
188
+ // The tx was dropped from the node's mempool — Clarity hasn't caught up yet.
189
+ // TODO: eagerly mark this tx as dropped in the tx log so the UI updates
190
+ // immediately instead of waiting for Clarity's next polling cycle.
191
+ throw new Error(
192
+ `ERR_BUMP_TX_DROPPED: Cannot bump transaction ${bumpTxId}: transaction was dropped from the network`
193
+ )
194
+ }
195
+
196
+ // pendingNonce = 4
197
+ // [0][1][2][3]{4}{5}{6}...
198
+ // ^--- next empty slot (pendingNonce)
199
+ //
200
+ // Nonce to replace must be < pendingNonce (i.e. slot is already accounted for).
201
+ // If nonce >= pendingNonce, the slot is empty or gapped — not safe to replace.
202
+ const pendingNonce = parseInt(pendingNonceHex, 16)
203
+ if (pendingNonce <= nonce) {
204
+ // A predecessor tx was dropped by the node, leaving a gap below this tx.
205
+ // The tx itself is still in the node's queued pool but can't execute until
206
+ // the missing nonce slot is filled.
207
+ // TODO: identify the dropped predecessor (scan nonces from pendingNonce to
208
+ // nonce - 1), mark it as dropped in the tx log, and surface a clear message
209
+ // to the user: "a previous transaction was dropped — send a new transaction
210
+ // first, then you'll be able to accelerate this one."
211
+ throw new Error(
212
+ `ERR_BUMP_TX_NONCE_GAP: Cannot bump transaction ${bumpTxId}: nonce gap detected — ` +
213
+ `node's pending nonce (${pendingNonce}) ≤ tx nonce (${nonce}), ` +
214
+ `broadcasting would produce ErrFutureReplacePending`
215
+ )
216
+ }
217
+ }
218
+
162
219
  const createBumpUnsignedTx = async ({
163
220
  fromAddress,
164
221
  chainId,
@@ -223,6 +280,8 @@ const createBumpUnsignedTx = async ({
223
280
 
224
281
  const nonce = maybeProvidedNonce ?? replacedTxNonce
225
282
 
283
+ await verifyBumpTxCanReplace({ baseAsset, bumpTxId, fromAddress, nonce })
284
+
226
285
  const resolvedTxAttributes = await resolveTxAttributesByTxType({
227
286
  asset,
228
287
  assetClientInterface,