@exodus/ethereum-api 8.73.1 → 8.73.3

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,30 @@
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.73.3](https://github.com/ExodusMovement/assets/compare/@exodus/ethereum-api@8.73.2...@exodus/ethereum-api@8.73.3) (2026-05-06)
7
+
8
+
9
+ ### Bug Fixes
10
+
11
+
12
+ * fix: correct misleading assertion and error messages (#7965)
13
+
14
+ * fix(ethereum-api): avoid mutating caller's txLog in resolveNonce (#7996)
15
+
16
+ * fix(ethereum-api): filter hints before pushing to stack in EthLikeError (#7991)
17
+
18
+ * fix: missing matic claim unstake transactions (#8006)
19
+
20
+
21
+
22
+ ## [8.73.2](https://github.com/ExodusMovement/assets/compare/@exodus/ethereum-api@8.73.1...@exodus/ethereum-api@8.73.2) (2026-05-05)
23
+
24
+ **Note:** Version bump only for package @exodus/ethereum-api
25
+
26
+
27
+
28
+
29
+
6
30
  ## [8.73.1](https://github.com/ExodusMovement/assets/compare/@exodus/ethereum-api@8.73.0...@exodus/ethereum-api@8.73.1) (2026-05-04)
7
31
 
8
32
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@exodus/ethereum-api",
3
- "version": "8.73.1",
3
+ "version": "8.73.3",
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",
@@ -39,7 +39,7 @@
39
39
  "@exodus/simple-retry": "^0.0.6",
40
40
  "@exodus/solidity-contract": "^1.3.0",
41
41
  "@exodus/traceparent": "^3.0.1",
42
- "@exodus/web3-ethereum-utils": "^4.6.0",
42
+ "@exodus/web3-ethereum-utils": "^4.7.4",
43
43
  "bn.js": "^5.2.1",
44
44
  "delay": "^4.0.1",
45
45
  "eventemitter3": "^4.0.7",
@@ -49,8 +49,7 @@
49
49
  "make-concurrent": "^4.0.0",
50
50
  "minimalistic-assert": "^1.0.1",
51
51
  "ms": "^2.1.1",
52
- "socket.io-client": "^2.1.1",
53
- "ws": "^6.1.0"
52
+ "socket.io-client": "^2.1.1"
54
53
  },
55
54
  "devDependencies": {
56
55
  "@exodus/assets-testing": "^1.0.0",
@@ -68,5 +67,5 @@
68
67
  "type": "git",
69
68
  "url": "git+https://github.com/ExodusMovement/assets.git"
70
69
  },
71
- "gitHead": "f6149536adebc3030321af18ba3c986a5d8a1bef"
70
+ "gitHead": "fe7c27f31aa14bf777660af02300c92def4b4a8c"
72
71
  }
@@ -293,9 +293,12 @@ export class EthLikeError extends Error {
293
293
  constructor({ message, errorReasonInfo, hint, traceId, baseAssetName }) {
294
294
  super(message)
295
295
  this.name = safeString`EthLikeError`
296
- this.#hintStack = [hint] // NOTE: we can add more hints to the stack
296
+ const filteredHint = this.#extractHint(hint)
297
+ // Only filtered hints are kept on the stack so that subsequent joins via
298
+ // `addHint` cannot leak sensitive info or untruncated content.
299
+ this.#hintStack = filteredHint ? [filteredHint] : []
297
300
  this.reason = errorReasonInfo.reason
298
- this.hint = this.#extractHint(hint)
301
+ this.hint = filteredHint
299
302
  this.type = errorReasonInfo.type
300
303
  this.traceId = traceId
301
304
  this.baseAssetName = baseAssetName
@@ -307,8 +310,8 @@ export class EthLikeError extends Error {
307
310
  return this
308
311
  }
309
312
 
310
- this.#hintStack.push(hint)
311
- this.hint = `${this.#hintStack.join(':')}`
313
+ this.#hintStack.push(filteredHint)
314
+ this.hint = this.#hintStack.join(':')
312
315
  return this
313
316
  }
314
317
 
@@ -12,6 +12,11 @@ const RETRY_DELAYS = ['10s']
12
12
 
13
13
  const EVERSTAKE_API_URL = 'https://eth-clarity.a.exodus.io/everstake-rewards'
14
14
 
15
+ export const UNSTAKE_DEFAULTS = {
16
+ allowedInterchangeNum: 0,
17
+ source: '2',
18
+ }
19
+
15
20
  export class EthereumStaking {
16
21
  static addresses = {
17
22
  ethereum: {
@@ -229,8 +234,22 @@ export class EthereumStaking {
229
234
  throw new Error(`Min Amount ${this.minAmount}`)
230
235
  }
231
236
 
232
- /* Unstake funds that are staked into the validator. Once unstaked they need to be claimed (once ready to withdraw) */
233
- async unstake({ address, amount, allowedInterchangeNum = 0, source = '2' }) {
237
+ /**
238
+ * Unstake funds from the validator. The unstaked amount enters a withdrawal
239
+ * queue and must be claimed via `claimWithdrawRequest` after the validator exits.
240
+ *
241
+ * @param {number} allowedInterchangeNum - Max number of incoming stake requests
242
+ * the contract may use to immediately return ETH to the unstaker (interchange).
243
+ * When 0 (default), no interchange occurs and the full amount is queued.
244
+ * When > 0, the contract may match up to N new stakers' deposits against this
245
+ * unstake, returning ETH immediately without waiting for validator exit.
246
+ */
247
+ async unstake({
248
+ address,
249
+ amount,
250
+ allowedInterchangeNum = UNSTAKE_DEFAULTS.allowedInterchangeNum,
251
+ source = UNSTAKE_DEFAULTS.source,
252
+ }) {
234
253
  const amountWei = amount.toBaseString()
235
254
  const balance = await this.autocompoundBalanceOf(address) // amount staked into the validator (active balance)
236
255
 
@@ -281,7 +300,7 @@ export class EthereumStaking {
281
300
  throw new Error(err)
282
301
  }
283
302
  } else {
284
- throw new Error(`Min Amount ${this.minAmount}`)
303
+ throw new Error(`Max Amount for unstakePending ${pendingBalance}`)
285
304
  }
286
305
  }
287
306
  }
@@ -2,7 +2,10 @@ import { fetch as exodusFetch } from '@exodus/fetch'
2
2
  import { TraceId } from '@exodus/traceparent'
3
3
  import assert from 'minimalistic-assert'
4
4
 
5
+ import { UNSTAKE_DEFAULTS } from './api.js'
6
+
5
7
  const BASE_URL = 'https://eth-clarity.a.exodus.io/api/v2/ethereum/proxy/everstake/'
8
+ const WALLET_SDK_BASE_URL = 'https://wallet-sdk-api.everstake.one/'
6
9
 
7
10
  const fetch = async (path, config = Object.create(null)) => {
8
11
  const url = new URL(path, BASE_URL).toString()
@@ -26,6 +29,56 @@ const fetch = async (path, config = Object.create(null)) => {
26
29
 
27
30
  const isFiniteInteger = (e) => Number.isInteger(e) && Number.isFinite(e)
28
31
 
32
+ // Simulate an unstake to predict how much ETH the interchange pool returns instantly.
33
+ // https://wallet-sdk-api.everstake.one/swagger/#/Ethereum/post_ethereum_pool_simulate_unstake
34
+ //
35
+ // Inputs:
36
+ // address: staker address (0x...)
37
+ // amount: unstake amount in ETH (default units), e.g. "1.5" — NOT wei
38
+ // allowedInterchangeNum: interchange pool parameter (default from UNSTAKE_DEFAULTS)
39
+ // source: source identifier (default from UNSTAKE_DEFAULTS)
40
+ //
41
+ // Success (200): { result: <number> } — ETH returned instantly (capped by pool liquidity)
42
+ // Error (500): { "Internal Server Error": "Error: Max Amount For Unstake <max_eth>" }
43
+ // when amount exceeds the staker's active balance
44
+ export const simulateEverstakeUnstake = async ({
45
+ address,
46
+ amount,
47
+ allowedInterchangeNum = UNSTAKE_DEFAULTS.allowedInterchangeNum,
48
+ source = UNSTAKE_DEFAULTS.source,
49
+ }) => {
50
+ try {
51
+ assert(typeof address === 'string' && address.startsWith('0x'), 'expected valid address')
52
+ assert(typeof amount === 'string' && amount.length > 0, 'expected amount in ETH as string')
53
+
54
+ const url = new URL('ethereum/pool/simulate_unstake', WALLET_SDK_BASE_URL).toString()
55
+
56
+ const response = await exodusFetch(url, {
57
+ method: 'POST',
58
+ headers: { 'Content-Type': 'application/json' },
59
+ body: JSON.stringify({ address, amount, allowedInterchangeNum, source }),
60
+ })
61
+
62
+ if (!response.ok) {
63
+ const body = await response.json().catch(() => null)
64
+ const serverMsg = body?.['Internal Server Error'] || response.statusText
65
+ console.warn(`simulate_unstake failed: ${serverMsg}`)
66
+ return null
67
+ }
68
+
69
+ const data = await response.json()
70
+ if (!data || typeof data.result !== 'number') {
71
+ console.warn('simulate_unstake: malformed response', data)
72
+ return null
73
+ }
74
+
75
+ return { instantReturnEth: data.result }
76
+ } catch (e) {
77
+ console.warn('simulate_unstake error:', e.message)
78
+ return null
79
+ }
80
+ }
81
+
29
82
  export const getEverstakeValidatorsQueue = async () => {
30
83
  // https://swagger.eth-api-b2c.everstake.one/#/Staking/validatorsQueue
31
84
  const result = await fetch('v1/validators/queue')