@exodus/ethereum-plugin 3.0.0 → 3.1.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 +18 -0
- package/package.json +5 -5
- package/src/staking/polygon/index.js +3 -0
- package/src/staking/polygon/service.js +148 -146
- package/src/staking/polygon/staking-adapter.js +394 -0
package/CHANGELOG.md
CHANGED
|
@@ -3,6 +3,24 @@
|
|
|
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
|
+
## [3.1.1](https://github.com/ExodusMovement/assets/compare/@exodus/ethereum-plugin@3.1.0...@exodus/ethereum-plugin@3.1.1) (2026-10-05)
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
### Bug Fixes
|
|
10
|
+
|
|
11
|
+
* **ethereum-plugin:** require ethereum-api ^10.1.0 for staking exports ([#8859](https://github.com/ExodusMovement/assets/issues/8859)) ([fecb514](https://github.com/ExodusMovement/assets/commit/fecb5140617cd9700ad28541e720c8dfc2bbd46e))
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
## [3.1.0](https://github.com/ExodusMovement/assets/compare/@exodus/ethereum-plugin@3.0.0...@exodus/ethereum-plugin@3.1.0) (2026-10-05)
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
### Features
|
|
19
|
+
|
|
20
|
+
* **ethereum:** add evm staking api core with Everstake ETH and polygon adapters ([#8775](https://github.com/ExodusMovement/assets/issues/8775)) ([2d90106](https://github.com/ExodusMovement/assets/commit/2d901069989be82eb56aa253d5171e4f6af97356))
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
|
|
6
24
|
## [3.0.0](https://github.com/ExodusMovement/assets/compare/@exodus/ethereum-plugin@2.35.2...@exodus/ethereum-plugin@3.0.0) (2026-10-05)
|
|
7
25
|
|
|
8
26
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@exodus/ethereum-plugin",
|
|
3
|
-
"version": "3.
|
|
3
|
+
"version": "3.1.1",
|
|
4
4
|
"description": "Ethereum plugin for Exodus SDK powered wallets",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "src/index.js",
|
|
@@ -22,7 +22,7 @@
|
|
|
22
22
|
"@exodus/asset-lib": "^5.9.2",
|
|
23
23
|
"@exodus/basic-utils": "^3.0.1",
|
|
24
24
|
"@exodus/currency": "^6.0.1",
|
|
25
|
-
"@exodus/ethereum-api": "^10.
|
|
25
|
+
"@exodus/ethereum-api": "^10.1.0",
|
|
26
26
|
"@exodus/ethereum-lib": "^5.25.0",
|
|
27
27
|
"@exodus/ethereum-meta": "^2.9.0",
|
|
28
28
|
"@exodus/ethereumjs": "^1.0.0",
|
|
@@ -36,10 +36,10 @@
|
|
|
36
36
|
"@exodus/assets": "^11.4.0",
|
|
37
37
|
"@exodus/assets-testing": "^1.0.0",
|
|
38
38
|
"@exodus/crypto": "^1.0.0-rc.13",
|
|
39
|
-
"@exodus/evm-fork-testing": "^0.7.
|
|
39
|
+
"@exodus/evm-fork-testing": "^0.7.3",
|
|
40
40
|
"@exodus/models": "^13.0.0",
|
|
41
41
|
"@exodus/solidity-contract": "^1.3.0",
|
|
42
|
-
"@exodus/web3-ethereum-utils": "^5.0.
|
|
42
|
+
"@exodus/web3-ethereum-utils": "^5.0.2",
|
|
43
43
|
"delay": "^4.0.1",
|
|
44
44
|
"ms": "^2.1.1"
|
|
45
45
|
},
|
|
@@ -51,5 +51,5 @@
|
|
|
51
51
|
"type": "git",
|
|
52
52
|
"url": "git+https://github.com/ExodusMovement/assets.git"
|
|
53
53
|
},
|
|
54
|
-
"gitHead": "
|
|
54
|
+
"gitHead": "a7072c68dd2d7254a2670e0eea6a48660751a585"
|
|
55
55
|
}
|
|
@@ -4,6 +4,7 @@ import { stakingAccountState } from './account-state.js'
|
|
|
4
4
|
import { stakingServerFactory } from './api.js'
|
|
5
5
|
import { mainnetContracts, methodIds } from './contracts/index.js'
|
|
6
6
|
import { stakingServiceFactory } from './service.js'
|
|
7
|
+
import { createPolygonStakingAdapter } from './staking-adapter.js'
|
|
7
8
|
import { txUtilsFactory } from './staking-utils.js'
|
|
8
9
|
|
|
9
10
|
const polygon = tokens.find(({ name: tokenName }) => tokenName === 'polygon')
|
|
@@ -30,4 +31,6 @@ export const polygonStakingDeps = {
|
|
|
30
31
|
txUtilsFactory,
|
|
31
32
|
stakingServiceFactory,
|
|
32
33
|
stakingServerFactory,
|
|
34
|
+
createStakingAdapter: ({ service }) => createPolygonStakingAdapter({ service }),
|
|
35
|
+
stakingApiEnabled: false,
|
|
33
36
|
}
|
|
@@ -1,11 +1,14 @@
|
|
|
1
1
|
import { memoize } from '@exodus/basic-utils'
|
|
2
2
|
import NumberUnit, { isNumberUnit } from '@exodus/currency'
|
|
3
3
|
import {
|
|
4
|
+
createStakingTxs,
|
|
4
5
|
estimateGasLimit,
|
|
5
6
|
EthLikeError,
|
|
6
7
|
EVM_ERROR_TYPES,
|
|
7
8
|
getAggregateTransactionPricing,
|
|
8
9
|
scaleGasLimitEstimate,
|
|
10
|
+
sendStakingTxs,
|
|
11
|
+
STAKING_TX_MODE,
|
|
9
12
|
stakingProviderClientFactory,
|
|
10
13
|
} from '@exodus/ethereum-api'
|
|
11
14
|
import { safeString } from '@exodus/safe-string'
|
|
@@ -189,7 +192,48 @@ export function stakingServiceFactory({ assetClientInterface, server: _server, s
|
|
|
189
192
|
}
|
|
190
193
|
}
|
|
191
194
|
|
|
192
|
-
async
|
|
195
|
+
const planSingleTx = async ({ delegatorAddress, to, txInput, feeData }) => {
|
|
196
|
+
const { feeAsset } = await getStakeAssets()
|
|
197
|
+
const { gasPrice, gasLimit, tipGasPrice, fee } = await estimateTxFee({
|
|
198
|
+
from: delegatorAddress,
|
|
199
|
+
to,
|
|
200
|
+
txInput,
|
|
201
|
+
feeData,
|
|
202
|
+
})
|
|
203
|
+
|
|
204
|
+
return {
|
|
205
|
+
asset: feeAsset,
|
|
206
|
+
delegatorAddress,
|
|
207
|
+
fee,
|
|
208
|
+
feeData,
|
|
209
|
+
gasPrice,
|
|
210
|
+
tipGasPrice,
|
|
211
|
+
steps: [{ to, txInput, gasLimit }],
|
|
212
|
+
mode: STAKING_TX_MODE.single,
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
const sendPlan = async ({ walletAccount, plan }) => {
|
|
217
|
+
const createdTxs = await createStakingTxs({
|
|
218
|
+
asset: plan.asset,
|
|
219
|
+
walletAccount,
|
|
220
|
+
fromAddress: plan.delegatorAddress,
|
|
221
|
+
steps: plan.steps,
|
|
222
|
+
mode: plan.mode,
|
|
223
|
+
gasPrice: plan.gasPrice,
|
|
224
|
+
tipGasPrice: plan.tipGasPrice,
|
|
225
|
+
feeData: plan.feeData,
|
|
226
|
+
})
|
|
227
|
+
const { txIds } = await sendStakingTxs({
|
|
228
|
+
asset: plan.asset,
|
|
229
|
+
walletAccount,
|
|
230
|
+
unsignedTxs: createdTxs.map(({ unsignedTx }) => unsignedTx),
|
|
231
|
+
mode: plan.mode,
|
|
232
|
+
})
|
|
233
|
+
return txIds[txIds.length - 1]
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
async function planApproveDelegateAmount({ walletAccount, amount, feeData } = {}) {
|
|
193
237
|
feeData = await resolveOptionalFeeData({ feeData })
|
|
194
238
|
|
|
195
239
|
const [delegatorAddress, { asset }] = await Promise.all([
|
|
@@ -199,34 +243,20 @@ export function stakingServiceFactory({ assetClientInterface, server: _server, s
|
|
|
199
243
|
|
|
200
244
|
amount = amountToCurrency({ asset, amount })
|
|
201
245
|
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
from: delegatorAddress,
|
|
246
|
+
return planSingleTx({
|
|
247
|
+
delegatorAddress,
|
|
205
248
|
to: stakingServer.polygonContract.address,
|
|
206
|
-
txInput:
|
|
249
|
+
txInput: await stakingServer.approveStakeManager(amount),
|
|
207
250
|
feeData,
|
|
208
251
|
})
|
|
252
|
+
}
|
|
209
253
|
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
to: stakingServer.polygonContract.address,
|
|
214
|
-
txData: txApproveData,
|
|
215
|
-
gasPrice,
|
|
216
|
-
gasLimit,
|
|
217
|
-
tipGasPrice,
|
|
218
|
-
feeData,
|
|
219
|
-
})
|
|
254
|
+
async function approveDelegateAmount(params) {
|
|
255
|
+
const plan = await planApproveDelegateAmount(params)
|
|
256
|
+
return sendPlan({ walletAccount: params.walletAccount, plan })
|
|
220
257
|
}
|
|
221
258
|
|
|
222
|
-
|
|
223
|
-
* Delegate MATIC tokens for staking.
|
|
224
|
-
* @param {walletAccount} params.walletAccount - The walletAccount for the delegator (required).
|
|
225
|
-
* @param {NumberUnit} params.amount - The amount of MATIC to delegate (required).
|
|
226
|
-
* @param {FeeData} params.feeData - Optional feeData to use for the transaction (optional, if not provided will be obtained from the asset client interface).
|
|
227
|
-
* @param {Boolean} params.revertOnSimulationError - Optional boolean to revert on simulation error (optional, default is true). Allows us to quickly react to simulator issues.
|
|
228
|
-
*/
|
|
229
|
-
async function delegate({ walletAccount, amount, feeData, revertOnSimulationError = true } = {}) {
|
|
259
|
+
async function planDelegate({ walletAccount, amount, feeData } = {}) {
|
|
230
260
|
const [delegatorAddress, { asset, feeAsset }, resolvedFeeData] = await Promise.all([
|
|
231
261
|
getDelegatorAddress({ walletAccount }),
|
|
232
262
|
getStakeAssets(),
|
|
@@ -237,12 +267,6 @@ export function stakingServiceFactory({ assetClientInterface, server: _server, s
|
|
|
237
267
|
|
|
238
268
|
amount = amountToCurrency({ asset, amount })
|
|
239
269
|
|
|
240
|
-
const baseNonce = await feeAsset.getNonce({
|
|
241
|
-
asset: feeAsset,
|
|
242
|
-
fromAddress: delegatorAddress.toString(),
|
|
243
|
-
walletAccount,
|
|
244
|
-
})
|
|
245
|
-
|
|
246
270
|
const [txApproveData, txDelegateData] = await Promise.all([
|
|
247
271
|
stakingServer.approveStakeManager(amount),
|
|
248
272
|
stakingServer.delegate({ amount }),
|
|
@@ -264,66 +288,83 @@ export function stakingServiceFactory({ assetClientInterface, server: _server, s
|
|
|
264
288
|
feeData,
|
|
265
289
|
})
|
|
266
290
|
|
|
267
|
-
|
|
291
|
+
return {
|
|
268
292
|
asset: feeAsset,
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
amount
|
|
272
|
-
|
|
293
|
+
stakeAsset: asset,
|
|
294
|
+
delegatorAddress: delegatorAddress.toString(),
|
|
295
|
+
amount,
|
|
296
|
+
fee: gasPrice.mul(approveGasLimit).add(gasPrice.mul(estimatedDelegateGasLimit)),
|
|
297
|
+
feeData,
|
|
273
298
|
gasPrice,
|
|
299
|
+
tipGasPrice,
|
|
300
|
+
steps: [
|
|
301
|
+
{
|
|
302
|
+
to: stakingServer.polygonContract.address,
|
|
303
|
+
txInput: txApproveData,
|
|
304
|
+
gasLimit: approveGasLimit,
|
|
305
|
+
},
|
|
306
|
+
{
|
|
307
|
+
to: stakingServer.validatorShareContract.address,
|
|
308
|
+
txInput: txDelegateData,
|
|
309
|
+
gasLimit: estimatedDelegateGasLimit,
|
|
310
|
+
},
|
|
311
|
+
],
|
|
312
|
+
mode: STAKING_TX_MODE.bundle,
|
|
274
313
|
}
|
|
275
|
-
|
|
276
|
-
feeAsset.api.createTx({
|
|
277
|
-
...createTxBaseArgs,
|
|
278
|
-
toAddress: stakingServer.polygonContract.address,
|
|
279
|
-
txInput: txApproveData,
|
|
280
|
-
gasLimit: approveGasLimit,
|
|
281
|
-
nonce: baseNonce,
|
|
282
|
-
}),
|
|
283
|
-
feeAsset.api.createTx({
|
|
284
|
-
...createTxBaseArgs,
|
|
285
|
-
toAddress: stakingServer.validatorShareContract.address,
|
|
286
|
-
txInput: txDelegateData,
|
|
287
|
-
gasLimit: estimatedDelegateGasLimit,
|
|
288
|
-
nonce: baseNonce + 1,
|
|
289
|
-
}),
|
|
290
|
-
])
|
|
314
|
+
}
|
|
291
315
|
|
|
316
|
+
async function simulateDelegate({ plan, createdTxs, revertOnSimulationError = true }) {
|
|
317
|
+
const [unsignedTxApprove, unsignedTxDelegate] = createdTxs
|
|
292
318
|
await maticDelegateSimulateTransactions({
|
|
293
|
-
asset,
|
|
319
|
+
asset: plan.stakeAsset,
|
|
294
320
|
unsignedTxApprove,
|
|
295
321
|
unsignedTxDelegate,
|
|
296
|
-
senderAddress: delegatorAddress
|
|
322
|
+
senderAddress: plan.delegatorAddress,
|
|
297
323
|
revertOnSimulationError,
|
|
298
324
|
})
|
|
325
|
+
}
|
|
299
326
|
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
327
|
+
/**
|
|
328
|
+
* Delegate MATIC tokens for staking.
|
|
329
|
+
* @param {walletAccount} params.walletAccount - The walletAccount for the delegator (required).
|
|
330
|
+
* @param {NumberUnit} params.amount - The amount of MATIC to delegate (required).
|
|
331
|
+
* @param {FeeData} params.feeData - Optional feeData to use for the transaction (optional, if not provided will be obtained from the asset client interface).
|
|
332
|
+
* @param {Boolean} params.revertOnSimulationError - Optional boolean to revert on simulation error (optional, default is true). Allows us to quickly react to simulator issues.
|
|
333
|
+
*/
|
|
334
|
+
async function delegate({ walletAccount, amount, feeData, revertOnSimulationError = true } = {}) {
|
|
335
|
+
const plan = await planDelegate({ walletAccount, amount, feeData })
|
|
336
|
+
const createdTxs = await createStakingTxs({
|
|
337
|
+
asset: plan.asset,
|
|
338
|
+
walletAccount,
|
|
339
|
+
fromAddress: plan.delegatorAddress,
|
|
340
|
+
steps: plan.steps,
|
|
341
|
+
mode: plan.mode,
|
|
342
|
+
gasPrice: plan.gasPrice,
|
|
343
|
+
tipGasPrice: plan.tipGasPrice,
|
|
344
|
+
})
|
|
345
|
+
|
|
346
|
+
await simulateDelegate({ plan, createdTxs, revertOnSimulationError })
|
|
347
|
+
|
|
348
|
+
const unsignedTxs = createdTxs.map(({ unsignedTx }) => unsignedTx)
|
|
349
|
+
const { txIds } = await sendStakingTxs({
|
|
350
|
+
asset: plan.asset,
|
|
351
|
+
walletAccount,
|
|
352
|
+
unsignedTxs,
|
|
353
|
+
mode: plan.mode,
|
|
313
354
|
})
|
|
314
355
|
|
|
315
356
|
const delegateTxId = txIds[txIds.length - 1]
|
|
316
357
|
|
|
317
358
|
await stakingProvider.notifyStaking({
|
|
318
359
|
txId: delegateTxId,
|
|
319
|
-
asset:
|
|
320
|
-
delegator: delegatorAddress,
|
|
321
|
-
amount: amount.toBaseString(),
|
|
360
|
+
asset: plan.stakeAsset.name,
|
|
361
|
+
delegator: plan.delegatorAddress,
|
|
362
|
+
amount: plan.amount.toBaseString(),
|
|
322
363
|
})
|
|
323
364
|
return delegateTxId
|
|
324
365
|
}
|
|
325
366
|
|
|
326
|
-
async function
|
|
367
|
+
async function planUndelegate({ walletAccount, amount, feeData } = {}) {
|
|
327
368
|
feeData = await resolveOptionalFeeData({ feeData })
|
|
328
369
|
|
|
329
370
|
const [delegatorAddress, { asset }] = await Promise.all([
|
|
@@ -340,49 +381,39 @@ export function stakingServiceFactory({ assetClientInterface, server: _server, s
|
|
|
340
381
|
asset,
|
|
341
382
|
})
|
|
342
383
|
|
|
343
|
-
const
|
|
344
|
-
|
|
345
|
-
from: delegatorAddress.toLowerCase(),
|
|
346
|
-
to: stakingServer.validatorShareContract.address,
|
|
347
|
-
txInput: txUndelegateData,
|
|
348
|
-
feeData,
|
|
349
|
-
})
|
|
350
|
-
return prepareAndSendTx({
|
|
351
|
-
walletAccount,
|
|
384
|
+
const plan = await planSingleTx({
|
|
385
|
+
delegatorAddress: delegatorAddress.toLowerCase(),
|
|
352
386
|
to: stakingServer.validatorShareContract.address,
|
|
353
|
-
|
|
354
|
-
gasPrice,
|
|
355
|
-
gasLimit,
|
|
356
|
-
tipGasPrice,
|
|
357
|
-
waitForConfirmation,
|
|
387
|
+
txInput: await stakingServer.undelegate(undelegateParams),
|
|
358
388
|
feeData,
|
|
359
389
|
})
|
|
390
|
+
return { ...plan, amount }
|
|
360
391
|
}
|
|
361
392
|
|
|
362
|
-
async function
|
|
393
|
+
async function undelegate(params = {}) {
|
|
394
|
+
const plan = await planUndelegate(params)
|
|
395
|
+
return sendPlan({ walletAccount: params.walletAccount, plan })
|
|
396
|
+
}
|
|
397
|
+
|
|
398
|
+
async function planClaimRewards({ walletAccount, feeData } = {}) {
|
|
363
399
|
feeData = await resolveOptionalFeeData({ feeData })
|
|
364
400
|
|
|
365
401
|
const delegatorAddress = await getDelegatorAddress({ walletAccount })
|
|
366
402
|
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
from: delegatorAddress,
|
|
370
|
-
to: stakingServer.validatorShareContract.address,
|
|
371
|
-
txInput: txWithdrawRewardsData,
|
|
372
|
-
feeData,
|
|
373
|
-
})
|
|
374
|
-
return prepareAndSendTx({
|
|
375
|
-
walletAccount,
|
|
403
|
+
return planSingleTx({
|
|
404
|
+
delegatorAddress,
|
|
376
405
|
to: stakingServer.validatorShareContract.address,
|
|
377
|
-
|
|
378
|
-
gasPrice,
|
|
379
|
-
gasLimit,
|
|
380
|
-
tipGasPrice,
|
|
406
|
+
txInput: await stakingServer.withdrawRewards(),
|
|
381
407
|
feeData,
|
|
382
408
|
})
|
|
383
409
|
}
|
|
384
410
|
|
|
385
|
-
async function
|
|
411
|
+
async function claimRewards(params = {}) {
|
|
412
|
+
const plan = await planClaimRewards(params)
|
|
413
|
+
return sendPlan({ walletAccount: params.walletAccount, plan })
|
|
414
|
+
}
|
|
415
|
+
|
|
416
|
+
async function planClaimUndelegatedBalance({ walletAccount, unbondNonce, feeData } = {}) {
|
|
386
417
|
feeData = await resolveOptionalFeeData({ feeData })
|
|
387
418
|
|
|
388
419
|
const [delegatorAddress, { asset }] = await Promise.all([
|
|
@@ -396,28 +427,24 @@ export function stakingServiceFactory({ assetClientInterface, server: _server, s
|
|
|
396
427
|
unbondNonce,
|
|
397
428
|
})
|
|
398
429
|
|
|
399
|
-
const
|
|
400
|
-
|
|
401
|
-
from: delegatorAddress,
|
|
402
|
-
to: stakingServer.validatorShareContract.address,
|
|
403
|
-
txInput: txClaimUndelegatedData,
|
|
404
|
-
feeData,
|
|
405
|
-
})
|
|
406
|
-
const txId = await prepareAndSendTx({
|
|
407
|
-
walletAccount,
|
|
430
|
+
const plan = await planSingleTx({
|
|
431
|
+
delegatorAddress,
|
|
408
432
|
to: stakingServer.validatorShareContract.address,
|
|
409
|
-
|
|
410
|
-
gasPrice,
|
|
411
|
-
gasLimit,
|
|
412
|
-
tipGasPrice,
|
|
433
|
+
txInput: await stakingServer.claimUndelegatedBalance({ unbondNonce }),
|
|
413
434
|
feeData,
|
|
414
435
|
})
|
|
436
|
+
return { ...plan, amount: unclaimedUndelegatedBalance, stakeAsset: asset }
|
|
437
|
+
}
|
|
438
|
+
|
|
439
|
+
async function claimUndelegatedBalance(params = {}) {
|
|
440
|
+
const plan = await planClaimUndelegatedBalance(params)
|
|
441
|
+
const txId = await sendPlan({ walletAccount: params.walletAccount, plan })
|
|
415
442
|
|
|
416
443
|
await stakingProvider.notifyUnstaking({
|
|
417
444
|
txId,
|
|
418
|
-
asset:
|
|
419
|
-
delegator: delegatorAddress,
|
|
420
|
-
amount:
|
|
445
|
+
asset: plan.stakeAsset.name,
|
|
446
|
+
delegator: plan.delegatorAddress,
|
|
447
|
+
amount: plan.amount.toBaseString(),
|
|
421
448
|
})
|
|
422
449
|
|
|
423
450
|
return txId
|
|
@@ -538,46 +565,21 @@ export function stakingServiceFactory({ assetClientInterface, server: _server, s
|
|
|
538
565
|
return { ...fee, gasLimit }
|
|
539
566
|
}
|
|
540
567
|
|
|
541
|
-
async function prepareAndSendTx({
|
|
542
|
-
walletAccount,
|
|
543
|
-
to,
|
|
544
|
-
txData: txInput,
|
|
545
|
-
gasPrice,
|
|
546
|
-
gasLimit,
|
|
547
|
-
tipGasPrice,
|
|
548
|
-
waitForConfirmation = false,
|
|
549
|
-
feeData,
|
|
550
|
-
} = {}) {
|
|
551
|
-
const { feeAsset } = await getStakeAssets()
|
|
552
|
-
const { unsignedTx } = await feeAsset.api.createTx({
|
|
553
|
-
asset: feeAsset,
|
|
554
|
-
walletAccount,
|
|
555
|
-
address: to,
|
|
556
|
-
amount: feeAsset.currency.ZERO,
|
|
557
|
-
txInput,
|
|
558
|
-
gasPrice,
|
|
559
|
-
gasLimit,
|
|
560
|
-
tipGasPrice,
|
|
561
|
-
waitForConfirmation,
|
|
562
|
-
feeData,
|
|
563
|
-
})
|
|
564
|
-
|
|
565
|
-
const { txId } = await feeAsset.api.sendTx({ asset: feeAsset, walletAccount, unsignedTx })
|
|
566
|
-
|
|
567
|
-
return txId
|
|
568
|
-
}
|
|
569
|
-
|
|
570
568
|
const getStakingInfo = getPolygonStakingInfo({ assetClientInterface, stakingServer })
|
|
571
569
|
|
|
572
570
|
return {
|
|
573
|
-
|
|
571
|
+
planDelegate,
|
|
572
|
+
simulateDelegate,
|
|
573
|
+
planUndelegate,
|
|
574
|
+
planClaimRewards,
|
|
575
|
+
planClaimUndelegatedBalance,
|
|
576
|
+
getStakingInfo,
|
|
577
|
+
// @deprecated legacy staking api, remove once evm staking unification is done
|
|
574
578
|
approveStake: approveDelegateAmount,
|
|
575
579
|
stake: delegate,
|
|
576
580
|
unstake: undelegate,
|
|
577
581
|
claimUnstaked: claimUndelegatedBalance,
|
|
578
582
|
claimRewards,
|
|
579
|
-
getStakingInfo,
|
|
580
|
-
// Legacy names (for direct service usage)
|
|
581
583
|
approveDelegateAmount,
|
|
582
584
|
delegate,
|
|
583
585
|
undelegate,
|
|
@@ -0,0 +1,394 @@
|
|
|
1
|
+
import {
|
|
2
|
+
createStakingRejectionError,
|
|
3
|
+
failStakingCheck as fail,
|
|
4
|
+
isPendingPolygonUndelegateTxInEthereumTxLog,
|
|
5
|
+
isPolygonClaimUndelegate,
|
|
6
|
+
isPolygonDelegate,
|
|
7
|
+
isPolygonReward,
|
|
8
|
+
isPolygonTx,
|
|
9
|
+
isPolygonUndelegate,
|
|
10
|
+
isPolygonUndelegateTxInEthereumTxLog,
|
|
11
|
+
passStakingCheck as pass,
|
|
12
|
+
stakingProviderClientFactory,
|
|
13
|
+
sumStakingOperationFees as sumOperationFees,
|
|
14
|
+
warnIfStakingBalanceBelow as warnIfBelow,
|
|
15
|
+
} from '@exodus/ethereum-api'
|
|
16
|
+
import { getMethodIdFromEthTx } from '@exodus/ethereum-lib'
|
|
17
|
+
import assert from 'minimalistic-assert'
|
|
18
|
+
|
|
19
|
+
import { mainnetContracts, methodIds } from './contracts/index.js'
|
|
20
|
+
import { POLYGON_STAKING_PREFLIGHT_ERROR_REASONS } from './service.js'
|
|
21
|
+
|
|
22
|
+
export const POL_STAKING_GAS_LIMITS = Object.freeze({
|
|
23
|
+
stake: 430_000,
|
|
24
|
+
unstake: 450_000,
|
|
25
|
+
claimUnstaked: 350_000,
|
|
26
|
+
claimRewards: 350_000,
|
|
27
|
+
})
|
|
28
|
+
|
|
29
|
+
const POL_UNBONDING_MS = 4 * 24 * 60 * 60 * 1000
|
|
30
|
+
const VALIDATOR_NAME = 'Everstake'
|
|
31
|
+
const DEFAULT_MIN_AMOUNT = '1'
|
|
32
|
+
|
|
33
|
+
const REASON_CODE_BY_PREFLIGHT_REASON = new Map([
|
|
34
|
+
[
|
|
35
|
+
POLYGON_STAKING_PREFLIGHT_ERROR_REASONS.polygonUnstakeAmountInvalid.reason,
|
|
36
|
+
'UNSTAKE_BELOW_MINIMUM',
|
|
37
|
+
],
|
|
38
|
+
[
|
|
39
|
+
POLYGON_STAKING_PREFLIGHT_ERROR_REASONS.polygonUnstakeAmountUnavailable.reason,
|
|
40
|
+
'UNSTAKE_EXCEEDS_STAKED',
|
|
41
|
+
],
|
|
42
|
+
[
|
|
43
|
+
POLYGON_STAKING_PREFLIGHT_ERROR_REASONS.polygonUnstakeClaimNonceInvalid.reason,
|
|
44
|
+
'NOTHING_TO_CLAIM',
|
|
45
|
+
],
|
|
46
|
+
[
|
|
47
|
+
POLYGON_STAKING_PREFLIGHT_ERROR_REASONS.polygonUnstakeClaimUnavailable.reason,
|
|
48
|
+
'NOTHING_TO_CLAIM',
|
|
49
|
+
],
|
|
50
|
+
[
|
|
51
|
+
POLYGON_STAKING_PREFLIGHT_ERROR_REASONS.polygonUnstakeClaimNotReady.reason,
|
|
52
|
+
'UNBONDING_IN_PROGRESS',
|
|
53
|
+
],
|
|
54
|
+
])
|
|
55
|
+
|
|
56
|
+
const OPERATION_BY_VALIDATOR_METHOD_ID = new Map([
|
|
57
|
+
[methodIds.DELEGATE, 'stake'],
|
|
58
|
+
[methodIds.UNDELEGATE, 'unstake'],
|
|
59
|
+
[methodIds.CLAIM_UNDELEGATE, 'claimUnstaked'],
|
|
60
|
+
[methodIds.CLAIM_REWARD, 'claimRewards'],
|
|
61
|
+
])
|
|
62
|
+
|
|
63
|
+
const toAmount = (asset, value) =>
|
|
64
|
+
typeof value === 'string' ? asset.currency.defaultUnit(value) : undefined
|
|
65
|
+
|
|
66
|
+
const isUnstakeTx = (tx) => Boolean(tx.data?.undelegate) || isPolygonUndelegate(tx)
|
|
67
|
+
|
|
68
|
+
const getLatestTxTime = (txLogs, matches) => {
|
|
69
|
+
let latest
|
|
70
|
+
for (const txLog of txLogs) {
|
|
71
|
+
for (const tx of txLog ?? []) {
|
|
72
|
+
if (!matches(tx)) continue
|
|
73
|
+
const time = new Date(tx.date).getTime()
|
|
74
|
+
if (latest === undefined || time > latest) latest = time
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
return latest
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
const resolveUnstakeAmount = ({ state, intent }) =>
|
|
82
|
+
intent.isMax ? state.balances.staked : intent.amount
|
|
83
|
+
|
|
84
|
+
export const createPolygonStakingAdapter = ({
|
|
85
|
+
service,
|
|
86
|
+
validatorAddress = mainnetContracts.VALIDATOR_SHARES_CONTRACT_ADDR,
|
|
87
|
+
stakingProvider = stakingProviderClientFactory(),
|
|
88
|
+
}) => {
|
|
89
|
+
assert(service, '`service` required')
|
|
90
|
+
|
|
91
|
+
const target = { kind: 'validator', id: validatorAddress.toLowerCase(), name: VALIDATOR_NAME }
|
|
92
|
+
|
|
93
|
+
const getCapabilities = ({ asset }) => ({
|
|
94
|
+
participation: 'amount',
|
|
95
|
+
liquid: false,
|
|
96
|
+
positions: 'aggregate',
|
|
97
|
+
operations: {
|
|
98
|
+
stake: { amount: 'required', supportsMax: true, positionSelectable: false, multiTx: true },
|
|
99
|
+
unstake: { amount: 'required', supportsMax: true, positionSelectable: false, multiTx: false },
|
|
100
|
+
claimUnstaked: {
|
|
101
|
+
amount: 'none',
|
|
102
|
+
supportsMax: false,
|
|
103
|
+
positionSelectable: false,
|
|
104
|
+
multiTx: false,
|
|
105
|
+
},
|
|
106
|
+
claimRewards: {
|
|
107
|
+
amount: 'none',
|
|
108
|
+
supportsMax: false,
|
|
109
|
+
positionSelectable: false,
|
|
110
|
+
multiTx: false,
|
|
111
|
+
},
|
|
112
|
+
},
|
|
113
|
+
target: { kind: 'validator', selection: 'fixed', default: target },
|
|
114
|
+
unbonding: { kind: 'epoch', approxDurationMs: POL_UNBONDING_MS },
|
|
115
|
+
claimUnstaked: 'manual',
|
|
116
|
+
rewards: { distribution: 'claimable' },
|
|
117
|
+
feeAssetName: asset.feeAsset.name,
|
|
118
|
+
maxPositions: 1,
|
|
119
|
+
hardwareWallets: ['ledger'],
|
|
120
|
+
})
|
|
121
|
+
|
|
122
|
+
const toPositions = ({ balances, info, txLog, feeAssetTxLog }) => {
|
|
123
|
+
const unstakedAt = getLatestTxTime(
|
|
124
|
+
[txLog, feeAssetTxLog],
|
|
125
|
+
(tx) => isUnstakeTx(tx) || isPolygonUndelegateTxInEthereumTxLog(tx)
|
|
126
|
+
)
|
|
127
|
+
const unbondId = `unbond-${info.unbondNonce ?? 0}`
|
|
128
|
+
const position = (id, status, amount, availableAt) =>
|
|
129
|
+
amount.isPositive ? [{ id, status, amount, target, ...(availableAt && { availableAt }) }] : []
|
|
130
|
+
|
|
131
|
+
return [
|
|
132
|
+
...position('active', 'active', balances.staked),
|
|
133
|
+
...position(
|
|
134
|
+
unbondId,
|
|
135
|
+
'deactivating',
|
|
136
|
+
balances.unstaking,
|
|
137
|
+
unstakedAt === undefined ? undefined : unstakedAt + POL_UNBONDING_MS
|
|
138
|
+
),
|
|
139
|
+
...position(unbondId, 'withdrawable', balances.unstaked),
|
|
140
|
+
]
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
const readState = ({ asset, accountState, txLog, feeAssetTxLog }) => {
|
|
144
|
+
const { currency } = asset
|
|
145
|
+
const zero = currency.ZERO
|
|
146
|
+
const info = accountState.staking?.[asset.name] || {}
|
|
147
|
+
const loaded = Boolean(accountState.stakingSnapshotOwners?.[asset.name])
|
|
148
|
+
const balances = asset.api.getBalances({ asset, accountState, txLog })
|
|
149
|
+
const { feeAsset } = asset
|
|
150
|
+
const feeBalance = feeAssetTxLog
|
|
151
|
+
? feeAsset.api.getBalances({ asset: feeAsset, accountState, txLog: feeAssetTxLog }).spendable
|
|
152
|
+
: undefined
|
|
153
|
+
const hasPendingUndelegate = [...(feeAssetTxLog ?? [])].some((tx) =>
|
|
154
|
+
isPendingPolygonUndelegateTxInEthereumTxLog(tx)
|
|
155
|
+
)
|
|
156
|
+
const liquidRewards = info.liquidRewards || zero
|
|
157
|
+
const { staking, staked, unstaking, unstaked } = balances
|
|
158
|
+
|
|
159
|
+
return {
|
|
160
|
+
loaded,
|
|
161
|
+
isStaking: staked.isPositive,
|
|
162
|
+
hasPosition: [staked, unstaking, unstaked, liquidRewards].some((amount) => amount.isPositive),
|
|
163
|
+
totals: { staking, staked, unstaking, unstaked, rewards: liquidRewards },
|
|
164
|
+
...(loaded && info.rewardsBalance && { earned: info.rewardsBalance }),
|
|
165
|
+
positions: toPositions({ balances, info, txLog, feeAssetTxLog }),
|
|
166
|
+
target,
|
|
167
|
+
...(hasPendingUndelegate && { pendingOperation: 'unstake' }),
|
|
168
|
+
balances,
|
|
169
|
+
feeBalance,
|
|
170
|
+
minAmount: info.minDelegateAmount || currency.defaultUnit(DEFAULT_MIN_AMOUNT),
|
|
171
|
+
minRewardsToWithdraw: info.minRewardsToWithdraw || currency.defaultUnit(DEFAULT_MIN_AMOUNT),
|
|
172
|
+
liquidRewards,
|
|
173
|
+
unbondNonce: Number(info.unbondNonce || 0),
|
|
174
|
+
isUndelegateInProgress: Boolean(info.isUndelegateInProgress),
|
|
175
|
+
canClaimUndelegatedBalance: Boolean(info.canClaimUndelegatedBalance),
|
|
176
|
+
unclaimedUndelegatedBalance: info.unclaimedUndelegatedBalance || zero,
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
const getGasLimit = ({ operation }) => POL_STAKING_GAS_LIMITS[operation]
|
|
181
|
+
|
|
182
|
+
const getLimits = ({ operation, state }) => {
|
|
183
|
+
switch (operation) {
|
|
184
|
+
case 'stake':
|
|
185
|
+
return { min: state.minAmount, max: state.balances.spendable }
|
|
186
|
+
case 'unstake':
|
|
187
|
+
return { max: state.balances.staked }
|
|
188
|
+
case 'claimUnstaked':
|
|
189
|
+
return { max: state.balances.unstaked }
|
|
190
|
+
case 'claimRewards':
|
|
191
|
+
return { min: state.minRewardsToWithdraw, max: state.liquidRewards }
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
const checkFee = ({ asset, state, fee }) =>
|
|
196
|
+
fee && state.feeBalance && state.feeBalance.lt(fee)
|
|
197
|
+
? fail('INSUFFICIENT_FEE_BALANCE', fee.sub(state.feeBalance), asset.feeAsset.name)
|
|
198
|
+
: pass()
|
|
199
|
+
|
|
200
|
+
const warnLaterFee = ({ state, fee, estimateOperationFee, operations, warning }) =>
|
|
201
|
+
warnIfBelow({
|
|
202
|
+
balance: state.feeBalance && fee && state.feeBalance.sub(fee),
|
|
203
|
+
required: sumOperationFees({ estimateOperationFee, operations }),
|
|
204
|
+
warning,
|
|
205
|
+
})
|
|
206
|
+
|
|
207
|
+
const checkStake = ({ asset, intent, state, fee, estimateOperationFee }) => {
|
|
208
|
+
const { spendable } = state.balances
|
|
209
|
+
const amount = intent.isMax ? spendable : intent.amount
|
|
210
|
+
if (!amount || amount.lt(state.minAmount)) {
|
|
211
|
+
return fail('BELOW_MINIMUM', state.minAmount.sub(amount ?? asset.currency.ZERO), asset.name)
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
if (spendable.lt(amount)) return fail('INSUFFICIENT_BALANCE', amount.sub(spendable), asset.name)
|
|
215
|
+
const feeCheck = checkFee({ asset, state, fee })
|
|
216
|
+
if (!feeCheck.ok) return feeCheck
|
|
217
|
+
|
|
218
|
+
return warnLaterFee({
|
|
219
|
+
state,
|
|
220
|
+
fee,
|
|
221
|
+
estimateOperationFee,
|
|
222
|
+
operations: ['unstake', 'claimUnstaked'],
|
|
223
|
+
warning: 'LOW_FEE_BALANCE',
|
|
224
|
+
})
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
const checkUnstake = ({ asset, intent, state, fee, estimateOperationFee }) => {
|
|
228
|
+
const { staked } = state.balances
|
|
229
|
+
if (!staked.isPositive) return fail('NOTHING_TO_UNSTAKE')
|
|
230
|
+
if (state.isUndelegateInProgress || state.unclaimedUndelegatedBalance.isPositive) {
|
|
231
|
+
return fail('MAX_POSITIONS_REACHED')
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
const amount = resolveUnstakeAmount({ state, intent })
|
|
235
|
+
if (!amount?.isPositive) return fail('UNSTAKE_BELOW_MINIMUM')
|
|
236
|
+
if (amount.gt(staked)) return fail('UNSTAKE_EXCEEDS_STAKED', amount.sub(staked), asset.name)
|
|
237
|
+
const feeCheck = checkFee({ asset, state, fee })
|
|
238
|
+
if (!feeCheck.ok) return feeCheck
|
|
239
|
+
|
|
240
|
+
return warnLaterFee({
|
|
241
|
+
state,
|
|
242
|
+
fee,
|
|
243
|
+
estimateOperationFee,
|
|
244
|
+
operations: ['claimUnstaked'],
|
|
245
|
+
warning: 'LOW_FEE_BALANCE_FOR_CLAIM',
|
|
246
|
+
})
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
const checkClaimUnstaked = ({ asset, state, fee }) => {
|
|
250
|
+
if (!state.unclaimedUndelegatedBalance.isPositive) return fail('NOTHING_TO_CLAIM')
|
|
251
|
+
if (!state.canClaimUndelegatedBalance) return fail('UNBONDING_IN_PROGRESS')
|
|
252
|
+
return checkFee({ asset, state, fee })
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
const checkClaimRewards = ({ asset, state, fee }) => {
|
|
256
|
+
const { liquidRewards, minRewardsToWithdraw } = state
|
|
257
|
+
if (!liquidRewards.isPositive) return fail('NO_REWARDS_TO_CLAIM')
|
|
258
|
+
if (liquidRewards.lt(minRewardsToWithdraw)) {
|
|
259
|
+
return fail('REWARDS_BELOW_MINIMUM', minRewardsToWithdraw.sub(liquidRewards), asset.name)
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
return checkFee({ asset, state, fee })
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
const CHECKS = {
|
|
266
|
+
stake: checkStake,
|
|
267
|
+
unstake: checkUnstake,
|
|
268
|
+
claimUnstaked: checkClaimUnstaked,
|
|
269
|
+
claimRewards: checkClaimRewards,
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
const checkOperation = ({ asset, operation, intent, state, fee, estimateOperationFee }) =>
|
|
273
|
+
CHECKS[operation]?.({ asset, intent, state, fee, estimateOperationFee }) ??
|
|
274
|
+
fail('OPERATION_UNSUPPORTED')
|
|
275
|
+
|
|
276
|
+
const planOperation = ({ walletAccount, operation, intent, state, feeData }) => {
|
|
277
|
+
switch (operation) {
|
|
278
|
+
case 'stake':
|
|
279
|
+
return service.planDelegate({
|
|
280
|
+
walletAccount,
|
|
281
|
+
amount: intent.isMax ? state.balances.spendable : intent.amount,
|
|
282
|
+
feeData,
|
|
283
|
+
})
|
|
284
|
+
case 'unstake':
|
|
285
|
+
return service.planUndelegate({
|
|
286
|
+
walletAccount,
|
|
287
|
+
amount: resolveUnstakeAmount({ state, intent }),
|
|
288
|
+
feeData,
|
|
289
|
+
})
|
|
290
|
+
case 'claimUnstaked':
|
|
291
|
+
return service.planClaimUndelegatedBalance({
|
|
292
|
+
walletAccount,
|
|
293
|
+
unbondNonce: state.unbondNonce,
|
|
294
|
+
feeData,
|
|
295
|
+
})
|
|
296
|
+
case 'claimRewards':
|
|
297
|
+
return service
|
|
298
|
+
.planClaimRewards({ walletAccount, feeData })
|
|
299
|
+
.then((plan) => ({ ...plan, amount: state.liquidRewards }))
|
|
300
|
+
default:
|
|
301
|
+
throw new Error(`Unsupported polygon staking operation: ${operation}`)
|
|
302
|
+
}
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
const toRejection = ({ operation, error }) => {
|
|
306
|
+
const reasonCode = REASON_CODE_BY_PREFLIGHT_REASON.get(error.reason)
|
|
307
|
+
if (!reasonCode) return error
|
|
308
|
+
return createStakingRejectionError({ operation, check: fail(reasonCode) })
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
const buildSteps = async ({ asset, walletAccount, operation, intent, state, feeData }) => {
|
|
312
|
+
let plan
|
|
313
|
+
try {
|
|
314
|
+
plan = await planOperation({ walletAccount, operation, intent, state, feeData })
|
|
315
|
+
} catch (error) {
|
|
316
|
+
throw toRejection({ operation, error })
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
const check = checkFee({ asset, state, fee: plan.fee })
|
|
320
|
+
if (!check.ok) throw createStakingRejectionError({ operation, check })
|
|
321
|
+
return plan
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
const simulate = async ({ operation, built, createdTxs }) => {
|
|
325
|
+
if (operation !== 'stake') return
|
|
326
|
+
await service.simulateDelegate({ plan: built, createdTxs })
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
const notify = async ({ asset, plan, txIds, delegator }) => {
|
|
330
|
+
if (plan.operation === 'stake') {
|
|
331
|
+
await stakingProvider.notifyStaking({
|
|
332
|
+
txId: txIds[txIds.length - 1],
|
|
333
|
+
asset: asset.name,
|
|
334
|
+
delegator,
|
|
335
|
+
amount: plan.amount.toBaseString(),
|
|
336
|
+
})
|
|
337
|
+
return
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
if (plan.operation !== 'claimUnstaked') return
|
|
341
|
+
await stakingProvider.notifyUnstaking({
|
|
342
|
+
txId: txIds[0],
|
|
343
|
+
asset: asset.name,
|
|
344
|
+
delegator,
|
|
345
|
+
amount: plan.amount.toBaseString(),
|
|
346
|
+
})
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
const classifyValidatorCall = ({ tx }) => {
|
|
350
|
+
if (tx.to?.toLowerCase() !== target.id) return
|
|
351
|
+
|
|
352
|
+
const operation = OPERATION_BY_VALIDATOR_METHOD_ID.get(
|
|
353
|
+
getMethodIdFromEthTx(tx, methodIds.DELEGATE)
|
|
354
|
+
)
|
|
355
|
+
if (!operation) return
|
|
356
|
+
|
|
357
|
+
const amount = operation === 'stake' && isPolygonTx(tx) ? tx.coinAmount?.abs() : undefined
|
|
358
|
+
return { operation, ...(amount?.isPositive && { amount }) }
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
const classifyPolygonTx = ({ asset, tx }) => {
|
|
362
|
+
const data = tx.data || {}
|
|
363
|
+
const withAmount = (operation, amount) => ({ operation, ...(amount && { amount }) })
|
|
364
|
+
|
|
365
|
+
if (typeof data.delegate === 'string' || isPolygonDelegate(tx)) {
|
|
366
|
+
return withAmount('stake', toAmount(asset, data.delegate))
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
if (isUnstakeTx(tx)) return withAmount('unstake', toAmount(asset, data.undelegate))
|
|
370
|
+
|
|
371
|
+
if (typeof data.claimUndelegate === 'string' || isPolygonClaimUndelegate(tx)) {
|
|
372
|
+
return withAmount('claimUnstaked', toAmount(asset, data.claimUndelegate))
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
if (isPolygonReward(tx)) return withAmount('claimRewards', tx.coinAmount?.abs())
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
const classifyTx = ({ asset, tx }) => {
|
|
379
|
+
if (isPolygonTx(tx)) return classifyPolygonTx({ asset, tx }) ?? classifyValidatorCall({ tx })
|
|
380
|
+
if (tx.coinName === asset.baseAsset.name) return classifyValidatorCall({ tx })
|
|
381
|
+
}
|
|
382
|
+
|
|
383
|
+
return {
|
|
384
|
+
getCapabilities,
|
|
385
|
+
readState,
|
|
386
|
+
getGasLimit,
|
|
387
|
+
getLimits,
|
|
388
|
+
checkOperation,
|
|
389
|
+
buildSteps,
|
|
390
|
+
simulate,
|
|
391
|
+
notify,
|
|
392
|
+
classifyTx,
|
|
393
|
+
}
|
|
394
|
+
}
|