@exodus/ethereum-plugin 2.30.2 → 2.30.4
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 +19 -0
- package/package.json +4 -4
- package/src/index.js +2 -0
- package/src/staking/polygon/service.js +160 -14
- package/src/staking/polygon/staking-utils.js +108 -0
package/CHANGELOG.md
CHANGED
|
@@ -3,6 +3,25 @@
|
|
|
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.30.4](https://github.com/ExodusMovement/assets/compare/@exodus/ethereum-plugin@2.30.3...@exodus/ethereum-plugin@2.30.4) (2026-05-12)
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
### Bug Fixes
|
|
10
|
+
|
|
11
|
+
* validate polygon unstake state before gas estimation ([#8002](https://github.com/ExodusMovement/assets/issues/8002)) ([f63a244](https://github.com/ExodusMovement/assets/commit/f63a2441868c6226fe14085c0f7d9587131e69e0))
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
## [2.30.3](https://github.com/ExodusMovement/assets/compare/@exodus/ethereum-plugin@2.30.2...@exodus/ethereum-plugin@2.30.3) (2026-05-11)
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
### Bug Fixes
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
* fix: correct `getTokenBalance` eth-like-util (#8047)
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
|
|
6
25
|
## [2.30.2](https://github.com/ExodusMovement/assets/compare/@exodus/ethereum-plugin@2.30.1...@exodus/ethereum-plugin@2.30.2) (2026-05-11)
|
|
7
26
|
|
|
8
27
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@exodus/ethereum-plugin",
|
|
3
|
-
"version": "2.30.
|
|
3
|
+
"version": "2.30.4",
|
|
4
4
|
"description": "Ethereum plugin for Exodus SDK powered wallets",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "src/index.js",
|
|
@@ -24,8 +24,8 @@
|
|
|
24
24
|
"@exodus/asset-lib": "^5.9.0",
|
|
25
25
|
"@exodus/basic-utils": "^3.0.1",
|
|
26
26
|
"@exodus/currency": "^6.0.1",
|
|
27
|
-
"@exodus/ethereum-api": "^8.
|
|
28
|
-
"@exodus/ethereum-lib": "^5.24.
|
|
27
|
+
"@exodus/ethereum-api": "^8.74.1",
|
|
28
|
+
"@exodus/ethereum-lib": "^5.24.1",
|
|
29
29
|
"@exodus/ethereum-meta": "^2.9.0",
|
|
30
30
|
"@exodus/ethereumjs": "^1.0.0",
|
|
31
31
|
"@exodus/safe-string": "^1.4.0",
|
|
@@ -53,5 +53,5 @@
|
|
|
53
53
|
"type": "git",
|
|
54
54
|
"url": "git+https://github.com/ExodusMovement/assets.git"
|
|
55
55
|
},
|
|
56
|
-
"gitHead": "
|
|
56
|
+
"gitHead": "7f184588091acba3b358e4af13783ee1e2bab4ba"
|
|
57
57
|
}
|
package/src/index.js
CHANGED
|
@@ -5,6 +5,8 @@ import assetsList from '@exodus/ethereum-meta'
|
|
|
5
5
|
import { tetherusdBlackListCheckFactory, usdcoinBlackListCheckFactory } from './blacklist-checks.js'
|
|
6
6
|
import { stakingConfiguration, stakingDependencies } from './staking.js'
|
|
7
7
|
|
|
8
|
+
export { POLYGON_STAKING_PREFLIGHT_ERROR_REASONS } from './staking/polygon/service.js'
|
|
9
|
+
|
|
8
10
|
const createAsset = createAssetFactory({
|
|
9
11
|
assetsList,
|
|
10
12
|
feeDataConfig: {
|
|
@@ -1,15 +1,42 @@
|
|
|
1
1
|
import { memoize } from '@exodus/basic-utils'
|
|
2
2
|
// eslint-disable-next-line @exodus/import/no-deprecated
|
|
3
|
-
import { isNumberUnit } from '@exodus/currency'
|
|
3
|
+
import NumberUnit, { isNumberUnit } from '@exodus/currency'
|
|
4
4
|
import {
|
|
5
5
|
estimateGasLimit,
|
|
6
|
+
EthLikeError,
|
|
7
|
+
EVM_ERROR_TYPES,
|
|
6
8
|
getAggregateTransactionPricing,
|
|
7
9
|
getOptimisticTxLogEffects,
|
|
8
10
|
scaleGasLimitEstimate,
|
|
9
11
|
stakingProviderClientFactory,
|
|
10
12
|
} from '@exodus/ethereum-api'
|
|
13
|
+
import { safeString } from '@exodus/safe-string'
|
|
11
14
|
|
|
12
15
|
import { maticDelegateSimulateTransactions } from './matic-staking-utils.js'
|
|
16
|
+
import { getPendingUndelegateAmountFromEthereumTxLog } from './staking-utils.js'
|
|
17
|
+
|
|
18
|
+
export const POLYGON_STAKING_PREFLIGHT_ERROR_REASONS = {
|
|
19
|
+
polygonUnstakeAmountInvalid: {
|
|
20
|
+
reason: safeString`polygon unstake amount invalid`,
|
|
21
|
+
type: EVM_ERROR_TYPES.PREFLIGHT_VALIDATION,
|
|
22
|
+
},
|
|
23
|
+
polygonUnstakeAmountUnavailable: {
|
|
24
|
+
reason: safeString`polygon unstake amount unavailable`,
|
|
25
|
+
type: EVM_ERROR_TYPES.PREFLIGHT_VALIDATION,
|
|
26
|
+
},
|
|
27
|
+
polygonUnstakeClaimNonceInvalid: {
|
|
28
|
+
reason: safeString`polygon unstake claim nonce invalid`,
|
|
29
|
+
type: EVM_ERROR_TYPES.PREFLIGHT_VALIDATION,
|
|
30
|
+
},
|
|
31
|
+
polygonUnstakeClaimUnavailable: {
|
|
32
|
+
reason: safeString`polygon unstake claim unavailable`,
|
|
33
|
+
type: EVM_ERROR_TYPES.PREFLIGHT_VALIDATION,
|
|
34
|
+
},
|
|
35
|
+
polygonUnstakeClaimNotReady: {
|
|
36
|
+
reason: safeString`polygon unstake claim not ready`,
|
|
37
|
+
type: EVM_ERROR_TYPES.PREFLIGHT_VALIDATION,
|
|
38
|
+
},
|
|
39
|
+
}
|
|
13
40
|
|
|
14
41
|
export function stakingServiceFactory({ assetClientInterface, server: _server, stakingServer }) {
|
|
15
42
|
const stakingProvider = stakingProviderClientFactory()
|
|
@@ -66,6 +93,105 @@ export function stakingServiceFactory({ assetClientInterface, server: _server, s
|
|
|
66
93
|
return address.toLowerCase()
|
|
67
94
|
}
|
|
68
95
|
|
|
96
|
+
const getPendingUndelegateAmount = async ({ walletAccount, asset }) => {
|
|
97
|
+
if (typeof assetClientInterface.getTxLog !== 'function') return asset.currency.ZERO
|
|
98
|
+
|
|
99
|
+
const ethereumTxLog = await assetClientInterface.getTxLog({ assetName, walletAccount })
|
|
100
|
+
return getPendingUndelegateAmountFromEthereumTxLog({
|
|
101
|
+
ethereumTxLog,
|
|
102
|
+
currency: asset.currency,
|
|
103
|
+
validatorShareContract: stakingServer.validatorShareContract,
|
|
104
|
+
})
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
const getUndelegateParams = async ({ walletAccount, amount, delegatorAddress, asset }) => {
|
|
108
|
+
if (!(amount instanceof NumberUnit) || !amount.gt(asset.currency.ZERO)) {
|
|
109
|
+
throw new EthLikeError({
|
|
110
|
+
message: 'Undelegate amount must be positive',
|
|
111
|
+
errorReasonInfo: POLYGON_STAKING_PREFLIGHT_ERROR_REASONS.polygonUnstakeAmountInvalid,
|
|
112
|
+
hint: safeString`polygonStaking:undelegate:amount`,
|
|
113
|
+
baseAssetName: asset.baseAsset.name,
|
|
114
|
+
})
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
const [delegatedBalance, pendingUndelegateAmount] = await Promise.all([
|
|
118
|
+
stakingServer.getTotalStake(delegatorAddress),
|
|
119
|
+
getPendingUndelegateAmount({ walletAccount, asset }),
|
|
120
|
+
])
|
|
121
|
+
|
|
122
|
+
if (delegatedBalance.isZero) {
|
|
123
|
+
throw new EthLikeError({
|
|
124
|
+
message: 'No active Polygon stake to undelegate',
|
|
125
|
+
errorReasonInfo: POLYGON_STAKING_PREFLIGHT_ERROR_REASONS.polygonUnstakeAmountUnavailable,
|
|
126
|
+
hint: safeString`polygonStaking:undelegate:activeStake`,
|
|
127
|
+
baseAssetName: asset.baseAsset.name,
|
|
128
|
+
})
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
const availableActiveStake = delegatedBalance.sub(pendingUndelegateAmount)
|
|
132
|
+
|
|
133
|
+
if (amount.gt(availableActiveStake)) {
|
|
134
|
+
throw new EthLikeError({
|
|
135
|
+
message: 'Undelegate amount exceeds active Polygon stake',
|
|
136
|
+
errorReasonInfo: POLYGON_STAKING_PREFLIGHT_ERROR_REASONS.polygonUnstakeAmountUnavailable,
|
|
137
|
+
hint: safeString`polygonStaking:undelegate:availableStake`,
|
|
138
|
+
baseAssetName: asset.baseAsset.name,
|
|
139
|
+
})
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
return { amount }
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
const getClaimUndelegatedParams = async ({ asset, delegatorAddress, unbondNonce }) => {
|
|
146
|
+
const { currency } = asset
|
|
147
|
+
|
|
148
|
+
if (!Number.isInteger(unbondNonce) || unbondNonce <= 0) {
|
|
149
|
+
throw new EthLikeError({
|
|
150
|
+
message: 'Polygon unstake claim nonce is invalid',
|
|
151
|
+
errorReasonInfo: POLYGON_STAKING_PREFLIGHT_ERROR_REASONS.polygonUnstakeClaimNonceInvalid,
|
|
152
|
+
hint: safeString`polygonStaking:claimUnstaked:nonce`,
|
|
153
|
+
baseAssetName: asset.baseAsset.name,
|
|
154
|
+
})
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
const [{ shares, withdrawEpoch }, withdrawalDelay, currentEpoch, withdrawExchangeRate] =
|
|
158
|
+
await Promise.all([
|
|
159
|
+
stakingServer.getUnboundInfo(delegatorAddress, unbondNonce),
|
|
160
|
+
stakingServer.getWithdrawalDelay(),
|
|
161
|
+
stakingServer.getCurrentCheckpoint(),
|
|
162
|
+
stakingServer.getWithdrawExchangeRate(),
|
|
163
|
+
])
|
|
164
|
+
|
|
165
|
+
if (shares.isZero()) {
|
|
166
|
+
throw new EthLikeError({
|
|
167
|
+
message: 'No Polygon unstake claim available',
|
|
168
|
+
errorReasonInfo: POLYGON_STAKING_PREFLIGHT_ERROR_REASONS.polygonUnstakeClaimUnavailable,
|
|
169
|
+
hint: safeString`polygonStaking:claimUnstaked:shares`,
|
|
170
|
+
baseAssetName: asset.baseAsset.name,
|
|
171
|
+
})
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
if (withdrawEpoch.add(withdrawalDelay).gt(currentEpoch)) {
|
|
175
|
+
throw new EthLikeError({
|
|
176
|
+
message: 'Polygon unstake claim is not ready',
|
|
177
|
+
errorReasonInfo: POLYGON_STAKING_PREFLIGHT_ERROR_REASONS.polygonUnstakeClaimNotReady,
|
|
178
|
+
hint: safeString`polygonStaking:claimUnstaked:withdrawalDelay`,
|
|
179
|
+
baseAssetName: asset.baseAsset.name,
|
|
180
|
+
})
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
return {
|
|
184
|
+
unclaimedUndelegatedBalance: calculateUnclaimedTokens({
|
|
185
|
+
currency,
|
|
186
|
+
exchangeRatePrecision: stakingServer.EXCHANGE_RATE_PRECISION,
|
|
187
|
+
withdrawExchangeRate,
|
|
188
|
+
shares,
|
|
189
|
+
canClaimUndelegatedBalance: true,
|
|
190
|
+
isUndelegateInProgress: false,
|
|
191
|
+
}),
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
|
|
69
195
|
async function approveDelegateAmount({ walletAccount, amount, feeData } = {}) {
|
|
70
196
|
feeData = await resolveOptionalFeeData({ feeData })
|
|
71
197
|
|
|
@@ -247,7 +373,14 @@ export function stakingServiceFactory({ assetClientInterface, server: _server, s
|
|
|
247
373
|
|
|
248
374
|
amount = amountToCurrency({ asset, amount })
|
|
249
375
|
|
|
250
|
-
const
|
|
376
|
+
const undelegateParams = await getUndelegateParams({
|
|
377
|
+
walletAccount,
|
|
378
|
+
amount,
|
|
379
|
+
delegatorAddress,
|
|
380
|
+
asset,
|
|
381
|
+
})
|
|
382
|
+
|
|
383
|
+
const txUndelegateData = await stakingServer.undelegate(undelegateParams)
|
|
251
384
|
const { gasPrice, gasLimit, tipGasPrice } = await estimateTxFee({
|
|
252
385
|
from: delegatorAddress.toLowerCase(),
|
|
253
386
|
to: stakingServer.validatorShareContract.address,
|
|
@@ -297,17 +430,10 @@ export function stakingServiceFactory({ assetClientInterface, server: _server, s
|
|
|
297
430
|
getStakeAssets(),
|
|
298
431
|
])
|
|
299
432
|
|
|
300
|
-
const {
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
})
|
|
305
|
-
|
|
306
|
-
const { unclaimedUndelegatedBalance } = await getUnstakedUnclaimedInfo({
|
|
307
|
-
stakingServer,
|
|
308
|
-
currency,
|
|
309
|
-
delegator: delegatorAddress,
|
|
310
|
-
...unstakedClaimInfo,
|
|
433
|
+
const { unclaimedUndelegatedBalance } = await getClaimUndelegatedParams({
|
|
434
|
+
asset,
|
|
435
|
+
delegatorAddress,
|
|
436
|
+
unbondNonce,
|
|
311
437
|
})
|
|
312
438
|
|
|
313
439
|
const txClaimUndelegatedData = await stakingServer.claimUndelegatedBalance({ unbondNonce })
|
|
@@ -366,6 +492,26 @@ export function stakingServiceFactory({ assetClientInterface, server: _server, s
|
|
|
366
492
|
args = { ...args, amount: amountToCurrency({ asset, amount }) }
|
|
367
493
|
}
|
|
368
494
|
|
|
495
|
+
if (operation === 'undelegate') {
|
|
496
|
+
args = {
|
|
497
|
+
...args,
|
|
498
|
+
...(await getUndelegateParams({
|
|
499
|
+
walletAccount,
|
|
500
|
+
amount: args.amount,
|
|
501
|
+
delegatorAddress,
|
|
502
|
+
asset,
|
|
503
|
+
})),
|
|
504
|
+
}
|
|
505
|
+
}
|
|
506
|
+
|
|
507
|
+
if (operation === 'claimUndelegatedBalance') {
|
|
508
|
+
await getClaimUndelegatedParams({
|
|
509
|
+
asset,
|
|
510
|
+
delegatorAddress,
|
|
511
|
+
unbondNonce: args.unbondNonce,
|
|
512
|
+
})
|
|
513
|
+
}
|
|
514
|
+
|
|
369
515
|
const operationTxData = await delegateOperation({ ...args, walletAccount })
|
|
370
516
|
const { fee } = await estimateTxFee({
|
|
371
517
|
from: delegatorAddress,
|
|
@@ -536,7 +682,7 @@ async function getUnstakedUnclaimedInfo({
|
|
|
536
682
|
const { withdrawEpoch, shares } = await stakingServer.getUnboundInfo(delegator, unbondNonce)
|
|
537
683
|
const exchangeRatePrecision = stakingServer.EXCHANGE_RATE_PRECISION
|
|
538
684
|
const isUndelegateInProgress =
|
|
539
|
-
!withdrawEpoch.isZero() && withdrawEpoch.add(withdrawalDelay).
|
|
685
|
+
!withdrawEpoch.isZero() && withdrawEpoch.add(withdrawalDelay).gt(currentEpoch)
|
|
540
686
|
const isUndelegatedBalanceClaimable = canClaimUndelegatedBalance({
|
|
541
687
|
shares,
|
|
542
688
|
withdrawEpoch,
|
|
@@ -1,8 +1,116 @@
|
|
|
1
|
+
import { getMethodIdFromEthTx, isConfirmedTxInLog, isPendingTxInLog } from '@exodus/ethereum-lib'
|
|
1
2
|
import assert from 'minimalistic-assert'
|
|
2
3
|
|
|
3
4
|
import { mainnetContracts, methodIds as methodIds_ } from './contracts/index.js'
|
|
4
5
|
import { txFiltersFactory } from './tx-filters/index.js'
|
|
5
6
|
|
|
7
|
+
// Every ethereum txLog writer stores the nonce at `tx.data.nonce` as an
|
|
8
|
+
// integer (the optimistic outgoing-tx effects, the clarity + monitor-utils
|
|
9
|
+
// server-tx normalisers, and the staking optimistic side-effect builders
|
|
10
|
+
// all converge on this shape). There is no top-level `tx.nonce` field on
|
|
11
|
+
// the Tx model, so we only read from `tx.data.nonce`.
|
|
12
|
+
const getTxNonce = (tx) => (Number.isInteger(tx.data?.nonce) ? tx.data.nonce : undefined)
|
|
13
|
+
|
|
14
|
+
// Identifies txs sent by our wallet. Every ethereum txLog writer produces
|
|
15
|
+
// `from: []` for an outgoing tx: the optimistic outgoing-tx effects set it
|
|
16
|
+
// explicitly, both server-tx normalisers (clarity-utils and monitor-utils)
|
|
17
|
+
// collapse to `from: []` when `serverTx.from === ourWalletAddress`, and the
|
|
18
|
+
// staking optimistic side-effect builders omit `from` so the Tx constructor
|
|
19
|
+
// defaults it to `[]`. Incoming txs always carry `from: [theirAddress]`. We
|
|
20
|
+
// do not check the element against `ourAddress` because no writer produces
|
|
21
|
+
// `from: [ourAddress]` for an outgoing tx — the empty-array convention is
|
|
22
|
+
// the contract.
|
|
23
|
+
const isOurOutgoingTx = (tx) => Array.isArray(tx.from) && tx.from.length === 0
|
|
24
|
+
|
|
25
|
+
const isPolygonUndelegateTx = ({ tx, validatorShareAddress, methodIds }) =>
|
|
26
|
+
tx.to?.toLowerCase() === validatorShareAddress &&
|
|
27
|
+
getMethodIdFromEthTx(tx, methodIds.UNDELEGATE) === methodIds.UNDELEGATE.toLowerCase()
|
|
28
|
+
|
|
29
|
+
const decodePendingUndelegateAmount = ({ tx, currency, validatorShareContract }) => {
|
|
30
|
+
const {
|
|
31
|
+
values: [pendingUndelegateAmount],
|
|
32
|
+
} = validatorShareContract.decodeInput(tx.data.data)
|
|
33
|
+
|
|
34
|
+
return currency.baseUnit(pendingUndelegateAmount)
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export const getPendingUndelegateAmountFromEthereumTxLog = ({
|
|
38
|
+
ethereumTxLog = [],
|
|
39
|
+
currency,
|
|
40
|
+
validatorShareContract,
|
|
41
|
+
methodIds = methodIds_,
|
|
42
|
+
}) => {
|
|
43
|
+
const validatorShareAddress = validatorShareContract.address.toLowerCase()
|
|
44
|
+
const txsByNonce = new Map()
|
|
45
|
+
let pendingAmount = currency.ZERO
|
|
46
|
+
|
|
47
|
+
// Only our own txs can affect our delegated stake, so filter early. Bucket
|
|
48
|
+
// by nonce alone because all surviving txs share a single sender and only
|
|
49
|
+
// one tx per nonce can ever mine. The txLog is already scoped to the
|
|
50
|
+
// active wallet account by assetClientInterface.getTxLog, so within that
|
|
51
|
+
// scope `from: []` uniquely identifies our outgoing txs.
|
|
52
|
+
for (const tx of ethereumTxLog ?? []) {
|
|
53
|
+
if (!(isPendingTxInLog(tx) || isConfirmedTxInLog(tx))) continue
|
|
54
|
+
if (!isOurOutgoingTx(tx)) continue
|
|
55
|
+
|
|
56
|
+
const nonce = getTxNonce(tx)
|
|
57
|
+
if (!Number.isInteger(nonce)) continue
|
|
58
|
+
|
|
59
|
+
const txs = txsByNonce.get(nonce) || []
|
|
60
|
+
txs.push(tx)
|
|
61
|
+
txsByNonce.set(nonce, txs)
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
for (const txs of txsByNonce.values()) {
|
|
65
|
+
// Confirmed same-nonce txs mean the pending attempt can no longer execute.
|
|
66
|
+
if (txs.some(isConfirmedTxInLog)) continue
|
|
67
|
+
|
|
68
|
+
const pendingUndelegateTxs = txs.filter(
|
|
69
|
+
(tx) =>
|
|
70
|
+
isPendingTxInLog(tx) && isPolygonUndelegateTx({ tx, validatorShareAddress, methodIds })
|
|
71
|
+
)
|
|
72
|
+
|
|
73
|
+
// If the bucket has no pending undelegate, nothing here can reduce our
|
|
74
|
+
// stake — whatever else lives at this nonce (a cancel, an unrelated tx)
|
|
75
|
+
// is someone else's problem.
|
|
76
|
+
//
|
|
77
|
+
// If there IS a pending undelegate, reserve it even when it shares the
|
|
78
|
+
// bucket with a cancel/replacement. The cancel almost always wins
|
|
79
|
+
// (higher fee is a precondition for tx-acceleration), but in the rare
|
|
80
|
+
// race where the original undelegate mines first we'd otherwise let the
|
|
81
|
+
// user broadcast a follow-up unstake that would revert on-chain. The
|
|
82
|
+
// cost of being conservative is a ~1-block UX delay after a successful
|
|
83
|
+
// cancel until clarity-monitor evicts the dead undelegate from the
|
|
84
|
+
// txLog.
|
|
85
|
+
if (pendingUndelegateTxs.length === 0) continue
|
|
86
|
+
|
|
87
|
+
// Reserve the largest decoded amount in the bucket. Same-nonce attempts
|
|
88
|
+
// are racing for the same on-chain slot, so at most one will mine — but we
|
|
89
|
+
// don't know which. Picking the max means the preflight stays safe even
|
|
90
|
+
// if an accelerated/replaced tx somehow carries a larger claim than the
|
|
91
|
+
// original, and it preserves the known-good amount when one of several
|
|
92
|
+
// attempts has corrupt input data.
|
|
93
|
+
let amountForNonce
|
|
94
|
+
|
|
95
|
+
for (const tx of pendingUndelegateTxs) {
|
|
96
|
+
try {
|
|
97
|
+
const amount = decodePendingUndelegateAmount({ tx, currency, validatorShareContract })
|
|
98
|
+
if (!amountForNonce || amount.gt(amountForNonce)) {
|
|
99
|
+
amountForNonce = amount
|
|
100
|
+
}
|
|
101
|
+
} catch (err) {
|
|
102
|
+
console.warn('Could not decode pending Polygon unstake amount', tx.txId, err)
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
if (!amountForNonce) continue
|
|
107
|
+
|
|
108
|
+
pendingAmount = pendingAmount.add(amountForNonce)
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
return pendingAmount
|
|
112
|
+
}
|
|
113
|
+
|
|
6
114
|
/**
|
|
7
115
|
* Used in ethereum-hooks to extract the final tx amount and the staking type
|
|
8
116
|
*
|