@exodus/ethereum-plugin 2.34.1 → 2.35.1
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 +42 -0
- package/package.json +3 -5
- package/src/index.js +3 -14
- package/src/staking/polygon/api.js +83 -6
- package/src/staking/polygon/service.js +20 -44
- package/src/staking/polygon/staking-utils.js +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -3,6 +3,48 @@
|
|
|
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
|
+
## [2.35.1](https://github.com/ExodusMovement/assets/compare/@exodus/ethereum-plugin@2.33.0...@exodus/ethereum-plugin@2.35.1) (2026-09-09)
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
### Features
|
|
10
|
+
|
|
11
|
+
* source eip7702 delegation whitelist from remote config ([#8356](https://github.com/ExodusMovement/assets/issues/8356)) ([1a0966b](https://github.com/ExodusMovement/assets/commit/1a0966b08befb6e9310993f323c21d003dea2754))
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
### Bug Fixes
|
|
15
|
+
|
|
16
|
+
* disable @exodus/hydra/no-param-reassign-prop-only warnings ([#8531](https://github.com/ExodusMovement/assets/issues/8531)) ([ddae556](https://github.com/ExodusMovement/assets/commit/ddae556361285919fe82cd10a81cfeae79ccc6b5))
|
|
17
|
+
* **ethereum-api:** preserve live pending txs across refresh to prevent nonce reuse ([#8430](https://github.com/ExodusMovement/assets/issues/8430)) ([a24ad82](https://github.com/ExodusMovement/assets/commit/a24ad82d303ce50606143bc5d1d917fbbad45ef1))
|
|
18
|
+
* **integration-tests:** solana safe report, foundry setup, anvil 1.8 fork-test races ([#8574](https://github.com/ExodusMovement/assets/issues/8574)) ([a8268b8](https://github.com/ExodusMovement/assets/commit/a8268b875ec85dd7818e3b8e5795aad02d1bcaaa))
|
|
19
|
+
* **lint:** add braces for multi-line if statements ([#8515](https://github.com/ExodusMovement/assets/issues/8515)) ([3891645](https://github.com/ExodusMovement/assets/commit/38916457ad0651163bd37843fc12d16656e214df))
|
|
20
|
+
* **lint:** fix easy auto-fixable oxlint warnings ([#8491](https://github.com/ExodusMovement/assets/issues/8491)) ([23407c5](https://github.com/ExodusMovement/assets/commit/23407c56aabd9f6968c5477306910f6702d59c39))
|
|
21
|
+
* **lint:** fix no-await-expression-member warnings and upgrade to error ([#8503](https://github.com/ExodusMovement/assets/issues/8503)) ([a7fb2c3](https://github.com/ExodusMovement/assets/commit/a7fb2c3bcb4e6b9e97e7f8bb3b8434b78036fe68))
|
|
22
|
+
* **lint:** fix no-throw-literal warnings and upgrade to error ([#8498](https://github.com/ExodusMovement/assets/issues/8498)) ([8fc68eb](https://github.com/ExodusMovement/assets/commit/8fc68eb65682e0b5a7da9415410ee1dedf309696))
|
|
23
|
+
* lower ETH min staking amount ([#8319](https://github.com/ExodusMovement/assets/issues/8319)) ([461e4c4](https://github.com/ExodusMovement/assets/commit/461e4c4f46e58e3750363edb9d0b6cddbfae5478))
|
|
24
|
+
* missing liquidRewards prop for polygon staking ([#8382](https://github.com/ExodusMovement/assets/issues/8382)) ([7d7d8ba](https://github.com/ExodusMovement/assets/commit/7d7d8ba62570fc3bf8ea9c58d514e0fc4dc70697))
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
### Performance Improvements
|
|
28
|
+
|
|
29
|
+
* **ethereum:** reduce background staking request volume ([#8617](https://github.com/ExodusMovement/assets/issues/8617)) ([a9746d9](https://github.com/ExodusMovement/assets/commit/a9746d98ee0a2a04f5fd0131d3f1228cbc0aacf2))
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
## [2.35.0](https://github.com/ExodusMovement/assets/compare/@exodus/ethereum-plugin@2.33.0...@exodus/ethereum-plugin@2.35.0) (2026-08-04)
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
### Features
|
|
37
|
+
|
|
38
|
+
* source eip7702 delegation whitelist from remote config ([#8356](https://github.com/ExodusMovement/assets/issues/8356)) ([1a0966b](https://github.com/ExodusMovement/assets/commit/1a0966b08befb6e9310993f323c21d003dea2754))
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
### Bug Fixes
|
|
42
|
+
|
|
43
|
+
* lower ETH min staking amount ([#8319](https://github.com/ExodusMovement/assets/issues/8319)) ([461e4c4](https://github.com/ExodusMovement/assets/commit/461e4c4f46e58e3750363edb9d0b6cddbfae5478))
|
|
44
|
+
* missing liquidRewards prop for polygon staking ([#8382](https://github.com/ExodusMovement/assets/issues/8382)) ([7d7d8ba](https://github.com/ExodusMovement/assets/commit/7d7d8ba62570fc3bf8ea9c58d514e0fc4dc70697))
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
|
|
6
48
|
## [2.34.1](https://github.com/ExodusMovement/assets/compare/@exodus/ethereum-plugin@2.33.0...@exodus/ethereum-plugin@2.34.1) (2026-07-14)
|
|
7
49
|
|
|
8
50
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@exodus/ethereum-plugin",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.35.1",
|
|
4
4
|
"description": "Ethereum plugin for Exodus SDK powered wallets",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "src/index.js",
|
|
@@ -16,9 +16,7 @@
|
|
|
16
16
|
"provenance": false
|
|
17
17
|
},
|
|
18
18
|
"scripts": {
|
|
19
|
-
"test": "
|
|
20
|
-
"lint": "run -T eslintc .",
|
|
21
|
-
"lint:fix": "yarn lint --fix"
|
|
19
|
+
"test": "exodus-test --jest"
|
|
22
20
|
},
|
|
23
21
|
"dependencies": {
|
|
24
22
|
"@exodus/asset-lib": "^5.9.2",
|
|
@@ -53,5 +51,5 @@
|
|
|
53
51
|
"type": "git",
|
|
54
52
|
"url": "git+https://github.com/ExodusMovement/assets.git"
|
|
55
53
|
},
|
|
56
|
-
"gitHead": "
|
|
54
|
+
"gitHead": "53bdb90b8e90413468dc13cf8960ef2aa64dac4b"
|
|
57
55
|
}
|
package/src/index.js
CHANGED
|
@@ -59,20 +59,9 @@ const createAsset = createAssetFactory({
|
|
|
59
59
|
confirmationsNumber: 30,
|
|
60
60
|
// https://ethereum.org/roadmap/pectra/
|
|
61
61
|
eip7623Supported: true,
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
{ address: '0x5A7FC11397E9a8AD41BF10bf13F22B0a63f96f6d', name: 'Ambire' },
|
|
66
|
-
{ address: '0x000000009B1D0aF20D8C6d0A44e162d11F9b8f00', name: 'Uniswap' },
|
|
67
|
-
{ address: '0xa845C74344Fc9405b1Fcf712f04668979573c1bf', name: 'Bitget' },
|
|
68
|
-
{ address: '0xe6Cae83BdE06E4c305530e199D7217f42808555B', name: 'Pimlico' },
|
|
69
|
-
{ address: '0xb15Bed8FC30D3E82672bF7cD75417B414983934B', name: 'SafePal' },
|
|
70
|
-
{ address: '0x000000004F43C49e93C970E84001853a70923B03', name: 'Biconomy' },
|
|
71
|
-
{ address: '0x80296FF8D1ED46f8e3C7992664D13B833504c2Bb', name: 'OKX Wallet' },
|
|
72
|
-
{ address: '0xd6CEDDe84be40893d153Be9d467CD6aD37875b28', name: 'ZeroDev' },
|
|
73
|
-
{ address: '0x4428a93B478fa76A5BD9c7641F54EC6373855433', name: 'Bitdev 2' },
|
|
74
|
-
{ address: '0x7702cb554e6bFb442cb743A7dF23154544a7176C', name: 'Coinbase Wallet' },
|
|
75
|
-
],
|
|
62
|
+
// Whitelist is populated exclusively from remote config (eip7702Whitelist),
|
|
63
|
+
// delivered through the history monitor's setServer. Empty until then.
|
|
64
|
+
eip7702Supported: [],
|
|
76
65
|
transactionAssessment: {
|
|
77
66
|
enabled: true,
|
|
78
67
|
},
|
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
import {
|
|
2
|
+
createCachedStakingRead,
|
|
3
|
+
EthLikeRpcRequestAccumulator,
|
|
2
4
|
EVM_ERROR_REASONS,
|
|
3
5
|
fromHexToBN,
|
|
4
6
|
fromHexToString,
|
|
@@ -16,7 +18,15 @@ import { mainnetContracts } from './contracts/index.js'
|
|
|
16
18
|
const RETRY_DELAYS = ['10s']
|
|
17
19
|
|
|
18
20
|
class StakingServer {
|
|
19
|
-
constructor({ asset, server, contracts = mainnetContracts }) {
|
|
21
|
+
constructor({ asset, server, contracts = mainnetContracts, createRpcRequestAccumulator }) {
|
|
22
|
+
// Only the reviewed mainnet getters are caller-independent. Custom contracts
|
|
23
|
+
// retain isolated JSON-RPC execution rather than inheriting that assumption.
|
|
24
|
+
this.createRpcRequestAccumulator =
|
|
25
|
+
Object.entries(mainnetContracts).every(
|
|
26
|
+
([key, address]) => contracts[key]?.toLowerCase() === address
|
|
27
|
+
) && createRpcRequestAccumulator
|
|
28
|
+
? createRpcRequestAccumulator
|
|
29
|
+
: () => new EthLikeRpcRequestAccumulator()
|
|
20
30
|
this.asset = asset
|
|
21
31
|
// harcoded exchange rate from the validator share contract
|
|
22
32
|
// in order to calculate claim unstake amount off-chain
|
|
@@ -54,6 +64,51 @@ class StakingServer {
|
|
|
54
64
|
})
|
|
55
65
|
}
|
|
56
66
|
|
|
67
|
+
// Reviewed ValidatorShare getters use explicit user arguments, public storage
|
|
68
|
+
// getters and caller-independent exchange-rate/reward calculations. Unlike
|
|
69
|
+
// transaction simulation, none requires the original eth_call msg.sender.
|
|
70
|
+
// aggregate is all-or-nothing: a reverted/malformed read must reject the state.
|
|
71
|
+
getStakingState = async (address) => {
|
|
72
|
+
const read = () => {
|
|
73
|
+
const requests = this.createRpcRequestAccumulator()
|
|
74
|
+
for (const [contract, method, ...args] of [
|
|
75
|
+
[this.validatorShareContract, 'getTotalStake', address],
|
|
76
|
+
[this.validatorShareContract, 'minAmount'],
|
|
77
|
+
[this.validatorShareContract, 'getLiquidRewards', address],
|
|
78
|
+
[this.validatorShareContract, 'unbondNonces', address],
|
|
79
|
+
[this.stakingManagerContract, 'withdrawalDelay'],
|
|
80
|
+
[this.stakingManagerContract, 'epoch'],
|
|
81
|
+
[this.validatorShareContract, 'withdrawExchangeRate'],
|
|
82
|
+
]) {
|
|
83
|
+
requests.contractStaticCallRequest(contract, method, ...args)
|
|
84
|
+
}
|
|
85
|
+
return requests.flush(this.server)
|
|
86
|
+
}
|
|
87
|
+
const [[stake], [minimum], [liquid], [nonce], [delay], [epoch], [rate]] = await withErrorReason(
|
|
88
|
+
{
|
|
89
|
+
promise: retry(read, { delayTimesMs: RETRY_DELAYS })(),
|
|
90
|
+
errorReasonInfo: EVM_ERROR_REASONS.ethCallFailed,
|
|
91
|
+
hint: safeString`stakingPolygonReadFunctionContract`,
|
|
92
|
+
baseAssetName: this.server.baseAssetName,
|
|
93
|
+
}
|
|
94
|
+
)
|
|
95
|
+
const unbondNonce = Number(nonce)
|
|
96
|
+
if (!Number.isSafeInteger(unbondNonce)) throw new Error('Invalid Polygon unbond nonce')
|
|
97
|
+
return {
|
|
98
|
+
delegatedBalance: this.asset.currency.baseUnit(stake),
|
|
99
|
+
minRewardsToWithdraw: this.asset.currency.baseUnit(minimum),
|
|
100
|
+
liquidRewards: this.asset.currency.baseUnit(liquid),
|
|
101
|
+
unbondNonce,
|
|
102
|
+
withdrawalDelay: new BN(delay),
|
|
103
|
+
currentEpoch: new BN(epoch),
|
|
104
|
+
withdrawExchangeRate: new BN(rate),
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
getCachedTotalRewards = createCachedStakingRead({
|
|
109
|
+
read: (address) => this.getTotalRewards(address, { throwOnError: true }),
|
|
110
|
+
})
|
|
111
|
+
|
|
57
112
|
getWithdrawalDelay = async () => {
|
|
58
113
|
const withdrawalDelay = await this.#callReadFunctionContract(
|
|
59
114
|
this.stakingManagerContract,
|
|
@@ -127,7 +182,7 @@ class StakingServer {
|
|
|
127
182
|
return this.asset.currency.baseUnit(liquidRewards)
|
|
128
183
|
}
|
|
129
184
|
|
|
130
|
-
getTotalRewards = async (address) => {
|
|
185
|
+
getTotalRewards = async (address, { throwOnError = false } = {}) => {
|
|
131
186
|
// use external everstake API to get total rewards
|
|
132
187
|
const EVERSTAKE_API_URL = 'https://polygon-clarity.a.exodus.io/everstake-rewards'
|
|
133
188
|
const url = new URL(EVERSTAKE_API_URL)
|
|
@@ -142,11 +197,22 @@ class StakingServer {
|
|
|
142
197
|
|
|
143
198
|
try {
|
|
144
199
|
const response = await fetch(url.toString(), options)
|
|
200
|
+
if (!response.ok) throw new Error('Failed to fetch Polygon total rewards')
|
|
145
201
|
const res = await response.json()
|
|
146
|
-
if (res === 'address'
|
|
147
|
-
|
|
202
|
+
if (res === 'address') return this.asset.currency.ZERO // address not found
|
|
203
|
+
const rewards = res?.rewards
|
|
204
|
+
if (
|
|
205
|
+
!(
|
|
206
|
+
(typeof rewards === 'string' && /^\d+$/.test(rewards)) ||
|
|
207
|
+
(Number.isSafeInteger(rewards) && rewards >= 0)
|
|
208
|
+
)
|
|
209
|
+
) {
|
|
210
|
+
throw new Error('Malformed Polygon total rewards')
|
|
211
|
+
}
|
|
212
|
+
return this.asset.currency.baseUnit(rewards)
|
|
148
213
|
} catch (error) {
|
|
149
214
|
console.warn('Error fetching Polygon total rewards:', error)
|
|
215
|
+
if (throwOnError) throw error
|
|
150
216
|
return this.asset.currency.ZERO
|
|
151
217
|
}
|
|
152
218
|
}
|
|
@@ -218,5 +284,16 @@ class StakingServer {
|
|
|
218
284
|
}
|
|
219
285
|
}
|
|
220
286
|
|
|
221
|
-
export const stakingServerFactory = ({
|
|
222
|
-
|
|
287
|
+
export const stakingServerFactory = ({
|
|
288
|
+
assetName,
|
|
289
|
+
currency,
|
|
290
|
+
contracts,
|
|
291
|
+
server,
|
|
292
|
+
createRpcRequestAccumulator,
|
|
293
|
+
}) =>
|
|
294
|
+
new StakingServer({
|
|
295
|
+
asset: { name: assetName, currency },
|
|
296
|
+
contracts,
|
|
297
|
+
server,
|
|
298
|
+
createRpcRequestAccumulator,
|
|
299
|
+
})
|
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
import { memoize } from '@exodus/basic-utils'
|
|
2
|
-
// eslint-disable-next-line @exodus/import/no-deprecated
|
|
3
2
|
import NumberUnit, { isNumberUnit } from '@exodus/currency'
|
|
4
3
|
import {
|
|
5
4
|
estimateGasLimit,
|
|
@@ -42,7 +41,6 @@ export function stakingServiceFactory({ assetClientInterface, server: _server, s
|
|
|
42
41
|
const assetName = 'ethereum'
|
|
43
42
|
|
|
44
43
|
function amountToCurrency({ asset, amount }) {
|
|
45
|
-
// eslint-disable-next-line @exodus/import/no-deprecated
|
|
46
44
|
return isNumberUnit(amount) ? amount : asset.currency.parse(amount)
|
|
47
45
|
}
|
|
48
46
|
|
|
@@ -589,22 +587,6 @@ export function stakingServiceFactory({ assetClientInterface, server: _server, s
|
|
|
589
587
|
}
|
|
590
588
|
}
|
|
591
589
|
|
|
592
|
-
async function fetchUnstakedClaimInfo({ stakingServer, delegator }) {
|
|
593
|
-
const [unbondNonce, withdrawalDelay, currentEpoch, withdrawExchangeRate] = await Promise.all([
|
|
594
|
-
stakingServer.getCurrentUnbondNonce(delegator),
|
|
595
|
-
stakingServer.getWithdrawalDelay(),
|
|
596
|
-
stakingServer.getCurrentCheckpoint(),
|
|
597
|
-
stakingServer.getWithdrawExchangeRate(),
|
|
598
|
-
])
|
|
599
|
-
|
|
600
|
-
return {
|
|
601
|
-
unbondNonce,
|
|
602
|
-
withdrawalDelay,
|
|
603
|
-
currentEpoch,
|
|
604
|
-
withdrawExchangeRate,
|
|
605
|
-
}
|
|
606
|
-
}
|
|
607
|
-
|
|
608
590
|
function calculateUnclaimedTokens({
|
|
609
591
|
currency,
|
|
610
592
|
exchangeRatePrecision,
|
|
@@ -665,40 +647,34 @@ async function getUnstakedUnclaimedInfo({
|
|
|
665
647
|
}
|
|
666
648
|
}
|
|
667
649
|
|
|
668
|
-
async function fetchRewardsInfo({ stakingServer, delegator, currency }) {
|
|
669
|
-
const [minRewardsToWithdraw, lastRewards, rewardsBalance] = await Promise.all([
|
|
670
|
-
stakingServer.getMinRewardsToWithdraw(),
|
|
671
|
-
stakingServer.getLiquidRewards(delegator),
|
|
672
|
-
stakingServer.getTotalRewards(delegator),
|
|
673
|
-
])
|
|
674
|
-
const withdrawable = lastRewards.sub(minRewardsToWithdraw).gte(currency.ZERO)
|
|
675
|
-
? lastRewards
|
|
676
|
-
: currency.ZERO
|
|
677
|
-
|
|
678
|
-
return {
|
|
679
|
-
rewardsBalance: rewardsBalance.add(lastRewards), // all time accrued rewards
|
|
680
|
-
liquidRewards: lastRewards, // current pending rewards (on-chain)
|
|
681
|
-
minRewardsToWithdraw,
|
|
682
|
-
withdrawable, // unclaimed rewards
|
|
683
|
-
}
|
|
684
|
-
}
|
|
685
|
-
|
|
686
650
|
export function getPolygonStakingInfo({ assetClientInterface, stakingServer }) {
|
|
687
|
-
return async function ({ address }) {
|
|
651
|
+
return async function ({ address, useCache = false, background = false }) {
|
|
688
652
|
const { polygon: asset } = await assetClientInterface.getAssetsForNetwork({
|
|
689
653
|
baseAssetName: 'ethereum', // Polygon token lives in ETH network
|
|
690
654
|
})
|
|
691
655
|
const { currency } = asset
|
|
692
656
|
const delegator = address.toLowerCase()
|
|
693
657
|
const [
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
658
|
+
{
|
|
659
|
+
delegatedBalance,
|
|
660
|
+
liquidRewards,
|
|
661
|
+
minRewardsToWithdraw,
|
|
662
|
+
unbondNonce,
|
|
663
|
+
withdrawalDelay,
|
|
664
|
+
currentEpoch,
|
|
665
|
+
withdrawExchangeRate,
|
|
666
|
+
},
|
|
667
|
+
totalRewards,
|
|
697
668
|
] = await Promise.all([
|
|
698
|
-
stakingServer.
|
|
699
|
-
|
|
700
|
-
|
|
669
|
+
stakingServer.getStakingState(delegator),
|
|
670
|
+
// Preserve rewards metadata on provider failure without blocking discovery
|
|
671
|
+
// of real on-chain stake during restore.
|
|
672
|
+
stakingServer
|
|
673
|
+
.getCachedTotalRewards(delegator, { useCache })
|
|
674
|
+
.catch(() => (background ? null : currency.ZERO)),
|
|
701
675
|
])
|
|
676
|
+
const rewardsBalance = totalRewards?.add(liquidRewards)
|
|
677
|
+
const withdrawable = liquidRewards.gte(minRewardsToWithdraw) ? liquidRewards : currency.ZERO
|
|
702
678
|
const minDelegateAmount = currency.defaultUnit(1)
|
|
703
679
|
const isDelegating = !delegatedBalance.isZero
|
|
704
680
|
|
|
@@ -713,7 +689,7 @@ export function getPolygonStakingInfo({ assetClientInterface, stakingServer }) {
|
|
|
713
689
|
})
|
|
714
690
|
|
|
715
691
|
return {
|
|
716
|
-
rewardsBalance,
|
|
692
|
+
...(rewardsBalance !== undefined && { rewardsBalance }),
|
|
717
693
|
liquidRewards,
|
|
718
694
|
withdrawable,
|
|
719
695
|
unbondNonce,
|
|
@@ -177,7 +177,7 @@ const calculateRewardsFromStakeTxFactory =
|
|
|
177
177
|
|
|
178
178
|
if (stakeTxContainsReward) {
|
|
179
179
|
const txAmount = stakedAmount.sub(tx.coinAmount.abs()).abs()
|
|
180
|
-
// eslint-disable-next-line @exodus/
|
|
180
|
+
// eslint-disable-next-line @exodus/hydra/no-param-reassign-prop-only -- TODO: Fix this the next time the file is edited.
|
|
181
181
|
return txAmount.toDefaultString()
|
|
182
182
|
}
|
|
183
183
|
}
|