@exodus/ethereum-api 8.64.2 → 8.64.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 +18 -0
- package/package.json +2 -2
- package/src/create-asset.js +24 -4
- package/src/create-token-factory.js +33 -14
- package/src/get-balances.js +1 -1
- package/src/hooks/monitor.js +4 -9
- package/src/index.js +10 -3
- package/src/staking/api/index.js +6 -5
- package/src/staking/ethereum/api.js +6 -5
- package/src/staking/ethereum/deps.js +33 -0
- package/src/staking/ethereum/index.js +1 -0
- package/src/staking/ethereum/service.js +3 -3
- package/src/staking/matic/index.js +0 -1
- package/src/tx-log/clarity-utils/get-log-items-from-server-tx.js +1 -1
- package/src/tx-log-staking-processor/utils.js +12 -2
- package/src/staking/matic/service.js +0 -599
- package/src/staking-api.js +0 -5
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
|
+
## [8.64.4](https://github.com/ExodusMovement/assets/compare/@exodus/ethereum-api@8.64.3...@exodus/ethereum-api@8.64.4) (2026-02-09)
|
|
7
|
+
|
|
8
|
+
**Note:** Version bump only for package @exodus/ethereum-api
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
## [8.64.3](https://github.com/ExodusMovement/assets/compare/@exodus/ethereum-api@8.64.2...@exodus/ethereum-api@8.64.3) (2026-01-26)
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
### Bug Fixes
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
* fix: drop third party tx input from clarity tx log items (#7317)
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
|
|
6
24
|
## [8.64.2](https://github.com/ExodusMovement/assets/compare/@exodus/ethereum-api@8.64.1...@exodus/ethereum-api@8.64.2) (2026-01-23)
|
|
7
25
|
|
|
8
26
|
**Note:** Version bump only for package @exodus/ethereum-api
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@exodus/ethereum-api",
|
|
3
|
-
"version": "8.64.
|
|
3
|
+
"version": "8.64.4",
|
|
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",
|
|
@@ -67,5 +67,5 @@
|
|
|
67
67
|
"type": "git",
|
|
68
68
|
"url": "git+https://github.com/ExodusMovement/assets.git"
|
|
69
69
|
},
|
|
70
|
-
"gitHead": "
|
|
70
|
+
"gitHead": "ebcc3fe022dfc1e21e3ccdc00faf0769e8fb3092"
|
|
71
71
|
}
|
package/src/create-asset.js
CHANGED
|
@@ -37,7 +37,7 @@ import { getBalancesFactory } from './get-balances.js'
|
|
|
37
37
|
import { getFeeFactory } from './get-fee.js'
|
|
38
38
|
import { estimateL1DataFeeFactory, getL1GetFeeFactory } from './optimism-gas/index.js'
|
|
39
39
|
import { serverBasedFeeMonitorFactoryFactory } from './server-based-fee-monitor.js'
|
|
40
|
-
import {
|
|
40
|
+
import { stakingApiFactory } from './staking/api/index.js'
|
|
41
41
|
import { createTxFactory } from './tx-create.js'
|
|
42
42
|
import { txSendFactory } from './tx-send/index.js'
|
|
43
43
|
import { createWeb3API } from './web3/index.js'
|
|
@@ -63,7 +63,8 @@ export const createAssetFactory = ({
|
|
|
63
63
|
monitorType: defaultMonitorType = 'magnifier',
|
|
64
64
|
nfts: defaultNfts = false,
|
|
65
65
|
serverUrl: defaultServerUrl,
|
|
66
|
-
stakingConfiguration =
|
|
66
|
+
stakingConfiguration = Object.create(null),
|
|
67
|
+
stakingDependencies = Object.create(null),
|
|
67
68
|
useEip1191ChainIdChecksum = false,
|
|
68
69
|
forceGasLimitEstimation = false,
|
|
69
70
|
rpcBalanceAssetNames = [],
|
|
@@ -156,7 +157,16 @@ export const createAssetFactory = ({
|
|
|
156
157
|
})
|
|
157
158
|
|
|
158
159
|
const { createToken, getTokens } = createTokenFactory(
|
|
159
|
-
{
|
|
160
|
+
{
|
|
161
|
+
address,
|
|
162
|
+
bip44,
|
|
163
|
+
keys,
|
|
164
|
+
getBalances,
|
|
165
|
+
assetClientInterface,
|
|
166
|
+
server,
|
|
167
|
+
stakingConfiguration,
|
|
168
|
+
stakingDependencies,
|
|
169
|
+
},
|
|
160
170
|
assets
|
|
161
171
|
)
|
|
162
172
|
|
|
@@ -289,7 +299,17 @@ export const createAssetFactory = ({
|
|
|
289
299
|
signHardware: signHardwareFactory({ baseAssetName: asset.name }),
|
|
290
300
|
signMessage: ({ message, privateKey, signer }) =>
|
|
291
301
|
signer ? signMessageWithSigner({ message, signer }) : signMessage({ privateKey, message }),
|
|
292
|
-
...(supportsStaking &&
|
|
302
|
+
...(supportsStaking &&
|
|
303
|
+
stakingDependencies[asset.name] && {
|
|
304
|
+
staking: stakingApiFactory({
|
|
305
|
+
assetName: asset.name,
|
|
306
|
+
currency: asset.currency,
|
|
307
|
+
assetClientInterface,
|
|
308
|
+
server,
|
|
309
|
+
stakingConfiguration: stakingConfiguration[asset.name],
|
|
310
|
+
stakingDependencies: stakingDependencies[asset.name],
|
|
311
|
+
}),
|
|
312
|
+
}),
|
|
293
313
|
validateAssetId: address.validate,
|
|
294
314
|
web3: createWeb3API({ asset }),
|
|
295
315
|
}
|
|
@@ -2,12 +2,12 @@ import { ASSET_FAMILY } from '@exodus/assets'
|
|
|
2
2
|
import { createContract } from '@exodus/ethereum-lib'
|
|
3
3
|
import assert from 'minimalistic-assert'
|
|
4
4
|
|
|
5
|
+
import { stakingApiFactory } from './staking/api/index.js'
|
|
5
6
|
import {
|
|
6
7
|
isPolygonClaimUndelegate,
|
|
7
8
|
isPolygonDelegate,
|
|
8
9
|
isPolygonUndelegate,
|
|
9
10
|
} from './staking/matic/index.js'
|
|
10
|
-
import { createStakingApi } from './staking-api.js'
|
|
11
11
|
|
|
12
12
|
const defaultTokenFeatures = {
|
|
13
13
|
family: ASSET_FAMILY.EVM,
|
|
@@ -35,31 +35,50 @@ const getPolygonActivityTxs = ({ txs }) =>
|
|
|
35
35
|
const getActivityTxs = ({ txs }) => txs.filter((tx) => !smallbalanceTx(tx))
|
|
36
36
|
|
|
37
37
|
const getCreateBaseToken =
|
|
38
|
-
({
|
|
38
|
+
({
|
|
39
|
+
getBalances,
|
|
40
|
+
assetClientInterface,
|
|
41
|
+
server,
|
|
42
|
+
stakingConfiguration = Object.create(null),
|
|
43
|
+
stakingDependencies = Object.create(null),
|
|
44
|
+
...props
|
|
45
|
+
}) =>
|
|
39
46
|
({ name, contract, features, ...tokenDef }) => {
|
|
40
47
|
assert(getBalances, 'getBalances is required')
|
|
41
48
|
const tokenSpecificFeatures = name === 'polygon' ? { staking: {} } : {}
|
|
42
|
-
const tokenSpecificApiFunctions =
|
|
43
|
-
name === 'polygon' ? { staking: createStakingApi({ network: name }) } : {}
|
|
44
49
|
|
|
45
50
|
const tokenFeatures = { ...defaultTokenFeatures, ...features, ...tokenSpecificFeatures }
|
|
46
|
-
|
|
51
|
+
const baseApi = {
|
|
52
|
+
getActivityTxs: name === 'polygon' ? getPolygonActivityTxs : getActivityTxs,
|
|
53
|
+
features: tokenFeatures,
|
|
54
|
+
hasFeature: (feature) => !!tokenFeatures[feature], // @deprecated use api.features instead
|
|
55
|
+
getBalances,
|
|
56
|
+
getTxLogFilter: name === 'polygon' ? getPolygonTxLogFilter : getTxLogFilter,
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
const tokenAsset = {
|
|
47
60
|
...tokenDef,
|
|
48
61
|
addresses: contract,
|
|
49
62
|
assetId: contract.current.toLowerCase(),
|
|
50
63
|
contract: createContract(contract.current, name),
|
|
51
64
|
gasLimit: 120_000,
|
|
52
65
|
name,
|
|
53
|
-
api:
|
|
54
|
-
|
|
55
|
-
features: tokenFeatures,
|
|
56
|
-
hasFeature: (feature) => !!tokenFeatures[feature], // @deprecated use api.features instead
|
|
57
|
-
getBalances,
|
|
58
|
-
getTxLogFilter: name === 'polygon' ? getPolygonTxLogFilter : getTxLogFilter,
|
|
59
|
-
...tokenSpecificApiFunctions,
|
|
60
|
-
},
|
|
61
|
-
...props, // override props above, add new props
|
|
66
|
+
api: baseApi,
|
|
67
|
+
...props,
|
|
62
68
|
}
|
|
69
|
+
|
|
70
|
+
if (name === 'polygon' && stakingDependencies[name]) {
|
|
71
|
+
baseApi.staking = stakingApiFactory({
|
|
72
|
+
assetName: tokenAsset.name,
|
|
73
|
+
currency: tokenAsset.currency,
|
|
74
|
+
assetClientInterface,
|
|
75
|
+
server,
|
|
76
|
+
stakingConfiguration: stakingConfiguration[name],
|
|
77
|
+
stakingDependencies: stakingDependencies[name],
|
|
78
|
+
})
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
return tokenAsset
|
|
63
82
|
}
|
|
64
83
|
|
|
65
84
|
export const createTokenFactory = (props, assets) => {
|
package/src/get-balances.js
CHANGED
|
@@ -14,7 +14,7 @@ import { getLatestCanonicalAbsoluteBalanceTx } from './tx-log/clarity-utils/inde
|
|
|
14
14
|
* Missing fields that should be added to getBalancesFactory return value:
|
|
15
15
|
*
|
|
16
16
|
* 1. `rewards` / `liquidRewards` - Currently, accountState.staking[asset.name] contains
|
|
17
|
-
* rewardsBalance and liquidRewards (from
|
|
17
|
+
* rewardsBalance and liquidRewards (from asset.api.staking.getStakingInfo → accountState.staking),
|
|
18
18
|
* but getBalancesFactory doesn't read or expose them. Need a getRewards() helper similar
|
|
19
19
|
* to getStaked() and getStaking().
|
|
20
20
|
*
|
package/src/hooks/monitor.js
CHANGED
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
import assert from 'minimalistic-assert'
|
|
2
2
|
|
|
3
|
-
import { getEthereumStakingInfo, getPolygonStakingInfo } from '../staking/index.js'
|
|
4
3
|
import processTxLog from '../tx-log-staking-processor/index.js'
|
|
5
4
|
|
|
6
5
|
export const createEthereumHooks = ({
|
|
@@ -32,17 +31,13 @@ export const createEthereumHooks = ({
|
|
|
32
31
|
}
|
|
33
32
|
|
|
34
33
|
for (const asset of stakingAssets) {
|
|
35
|
-
const
|
|
36
|
-
|
|
37
|
-
stakingAssetName === 'polygon' ? getPolygonStakingInfo : getEthereumStakingInfo
|
|
34
|
+
const stakingApi = asset.api?.staking
|
|
35
|
+
if (!stakingApi?.getStakingInfo) continue
|
|
38
36
|
|
|
39
|
-
|
|
40
|
-
const assetStakingInfo = await getStakingInfo({
|
|
37
|
+
const assetStakingInfo = await stakingApi.getStakingInfo({
|
|
41
38
|
address: userAddress.toString(),
|
|
42
|
-
asset,
|
|
43
|
-
server,
|
|
44
39
|
})
|
|
45
|
-
stakingInfo.staking[
|
|
40
|
+
stakingInfo.staking[asset.name] = assetStakingInfo
|
|
46
41
|
}
|
|
47
42
|
|
|
48
43
|
const batch = assetClientInterface.createOperationsBatch()
|
package/src/index.js
CHANGED
|
@@ -26,17 +26,24 @@ export {
|
|
|
26
26
|
DEFAULT_GAS_LIMIT_MULTIPLIER,
|
|
27
27
|
estimateGasLimit,
|
|
28
28
|
fetchGasLimit,
|
|
29
|
+
scaleGasLimitEstimate,
|
|
29
30
|
} from './gas-estimation.js'
|
|
30
31
|
|
|
31
32
|
export { createEvmServer, getServer } from './exodus-eth-server/index.js'
|
|
32
33
|
|
|
33
|
-
export {
|
|
34
|
+
export {
|
|
35
|
+
EthereumMonitor,
|
|
36
|
+
EthereumNoHistoryMonitor,
|
|
37
|
+
ClarityMonitor,
|
|
38
|
+
getOptimisticTxLogEffects,
|
|
39
|
+
} from './tx-log/index.js'
|
|
34
40
|
|
|
35
41
|
export { getStakingHistoryBalance, getBalancesFactory } from './get-balances.js'
|
|
36
42
|
|
|
37
43
|
export {
|
|
38
44
|
FantomStaking,
|
|
39
45
|
stakingProviderClientFactory,
|
|
46
|
+
ethereumStakingDeps,
|
|
40
47
|
getEthereumStakingInfo,
|
|
41
48
|
createEthereumStakingService,
|
|
42
49
|
EthereumStaking,
|
|
@@ -46,8 +53,6 @@ export {
|
|
|
46
53
|
isEthereumUndelegate,
|
|
47
54
|
isEthereumClaimUndelegate,
|
|
48
55
|
MaticStakingApi,
|
|
49
|
-
createPolygonStakingService,
|
|
50
|
-
getPolygonStakingInfo,
|
|
51
56
|
isPolygonTx,
|
|
52
57
|
isPolygonDelegate,
|
|
53
58
|
isPolygonUndelegate,
|
|
@@ -71,6 +76,8 @@ export {
|
|
|
71
76
|
|
|
72
77
|
export { estimateL1DataFeeFactory, getL1GetFeeFactory } from './optimism-gas/index.js'
|
|
73
78
|
|
|
79
|
+
export { getAggregateTransactionPricing } from './get-fee.js'
|
|
80
|
+
|
|
74
81
|
export {
|
|
75
82
|
fromHexToString,
|
|
76
83
|
fromHexToBigInt,
|
package/src/staking/api/index.js
CHANGED
|
@@ -1,14 +1,15 @@
|
|
|
1
|
-
import { pick } from '@exodus/basic-utils'
|
|
2
1
|
import assert from 'minimalistic-assert'
|
|
3
2
|
|
|
4
3
|
export const stakingApiFactory = ({
|
|
5
|
-
|
|
4
|
+
assetName,
|
|
5
|
+
currency,
|
|
6
6
|
assetClientInterface,
|
|
7
7
|
server, // coin node server
|
|
8
8
|
stakingConfiguration,
|
|
9
9
|
stakingDependencies,
|
|
10
10
|
}) => {
|
|
11
|
-
assert(
|
|
11
|
+
assert(assetName, '"assetName" is required')
|
|
12
|
+
assert(currency, '"currency" is required')
|
|
12
13
|
assert(assetClientInterface, '"assetClientInterface" is required')
|
|
13
14
|
assert(server, '"server" is required')
|
|
14
15
|
assert(stakingConfiguration, '"stakingConfiguration" is required')
|
|
@@ -17,7 +18,7 @@ export const stakingApiFactory = ({
|
|
|
17
18
|
const { contracts, minAmount } = stakingConfiguration
|
|
18
19
|
const { stakingServerFactory, stakingServiceFactory } = stakingDependencies
|
|
19
20
|
|
|
20
|
-
const stakingServer = stakingServerFactory({
|
|
21
|
+
const stakingServer = stakingServerFactory({ assetName, currency, server, minAmount, contracts })
|
|
21
22
|
|
|
22
23
|
const stakingService = stakingServiceFactory({
|
|
23
24
|
assetClientInterface,
|
|
@@ -26,9 +27,9 @@ export const stakingApiFactory = ({
|
|
|
26
27
|
})
|
|
27
28
|
|
|
28
29
|
return {
|
|
30
|
+
...stakingService,
|
|
29
31
|
isStaking: async ({ isDelegating }) => isDelegating,
|
|
30
32
|
isUnstaking: async ({ isUndelegateInProgress }) => isUndelegateInProgress,
|
|
31
33
|
isUnstaked: async ({ canClaimUndelegateBalance }) => canClaimUndelegateBalance,
|
|
32
|
-
...pick(stakingService, ['approveStake', 'stake', 'unstake', 'claimUnstaked', 'claimRewards']),
|
|
33
34
|
}
|
|
34
35
|
}
|
|
@@ -42,20 +42,21 @@ export class EthereumStaking {
|
|
|
42
42
|
}
|
|
43
43
|
|
|
44
44
|
constructor(
|
|
45
|
-
|
|
45
|
+
assetName, // ethereum or ethereumholesky for testnet
|
|
46
|
+
currency,
|
|
46
47
|
minAmount = MIN_AMOUNT,
|
|
47
48
|
server
|
|
48
49
|
) {
|
|
49
|
-
this.asset =
|
|
50
|
+
this.asset = { name: assetName, currency }
|
|
50
51
|
const accountingAddress =
|
|
51
|
-
EthereumStaking.addresses[
|
|
52
|
-
const poolAddress = EthereumStaking.addresses[
|
|
52
|
+
EthereumStaking.addresses[assetName].EVERSTAKE_ADDRESS_CONTRACT_ACCOUNTING
|
|
53
|
+
const poolAddress = EthereumStaking.addresses[assetName].EVERSTAKE_ADDRESS_CONTRACT_POOL
|
|
53
54
|
|
|
54
55
|
this.contractAccounting = createContract(accountingAddress, 'ethStakingAccounting')
|
|
55
56
|
this.contractPool = createContract(poolAddress, 'ethStakingPool')
|
|
56
57
|
this.accountingAddress = accountingAddress
|
|
57
58
|
this.poolAddress = poolAddress
|
|
58
|
-
this.minAmount =
|
|
59
|
+
this.minAmount = currency.defaultUnit(minAmount)
|
|
59
60
|
this.server = server
|
|
60
61
|
}
|
|
61
62
|
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { EthereumStaking } from './api.js'
|
|
2
|
+
import { createEthereumStakingService, getEthereumStakingInfo } from './service.js'
|
|
3
|
+
|
|
4
|
+
/** Staking server factory for ethereum (used by stakingApiFactory). */
|
|
5
|
+
export const stakingServerFactory = ({ assetName, currency, server, minAmount }) =>
|
|
6
|
+
new EthereumStaking(assetName, currency, minAmount, server)
|
|
7
|
+
|
|
8
|
+
/** Staking service factory for ethereum (used by stakingApiFactory). */
|
|
9
|
+
export const stakingServiceFactory = ({ assetClientInterface, server, stakingServer }) => {
|
|
10
|
+
const service = createEthereumStakingService({
|
|
11
|
+
assetName: stakingServer.asset.name,
|
|
12
|
+
assetClientInterface,
|
|
13
|
+
})
|
|
14
|
+
|
|
15
|
+
return {
|
|
16
|
+
...service,
|
|
17
|
+
getStakingInfo: ({ address }) =>
|
|
18
|
+
getEthereumStakingInfo({
|
|
19
|
+
address,
|
|
20
|
+
asset: stakingServer.asset,
|
|
21
|
+
server,
|
|
22
|
+
}),
|
|
23
|
+
stake: service.delegate,
|
|
24
|
+
unstake: service.undelegate,
|
|
25
|
+
claimUnstaked: service.claimUndelegatedBalance,
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/** Staking deps for ethereum base asset (plugin can pass as stakingDependencies.ethereum). */
|
|
30
|
+
export const ethereumStakingDeps = {
|
|
31
|
+
stakingServerFactory,
|
|
32
|
+
stakingServiceFactory,
|
|
33
|
+
}
|
|
@@ -32,7 +32,7 @@ const STAKING_OPERATION_FALLBACK_FEE_ESTIMATES = {
|
|
|
32
32
|
}
|
|
33
33
|
|
|
34
34
|
const getStakingApi = memoize(
|
|
35
|
-
(asset) => new EthereumStaking(asset, undefined, asset.server),
|
|
35
|
+
(asset) => new EthereumStaking(asset.name, asset.currency, undefined, asset.server),
|
|
36
36
|
(asset) => asset.name
|
|
37
37
|
)
|
|
38
38
|
|
|
@@ -569,9 +569,9 @@ export function createEthereumStakingService({
|
|
|
569
569
|
}
|
|
570
570
|
|
|
571
571
|
export async function getEthereumStakingInfo({ address, asset, server }) {
|
|
572
|
-
const { currency } = asset
|
|
572
|
+
const { name: assetName, currency } = asset
|
|
573
573
|
const delegator = address.toLowerCase()
|
|
574
|
-
const staking = new EthereumStaking(
|
|
574
|
+
const staking = new EthereumStaking(assetName, currency, undefined, server)
|
|
575
575
|
|
|
576
576
|
const [
|
|
577
577
|
activeStakedBalance,
|
|
@@ -39,7 +39,7 @@ export default function getLogItemsFromServerTx({
|
|
|
39
39
|
const methodId = serverTx.input && {
|
|
40
40
|
methodId: serverTx.input.slice(0, Math.max(0, METHOD_ID_LENGTH)),
|
|
41
41
|
}
|
|
42
|
-
const data = serverTx.input || '0x'
|
|
42
|
+
const data = (ourWalletWasSender ? serverTx.input : methodId?.methodId) || '0x'
|
|
43
43
|
const walletUpdates = getWalletUpdates(ourWalletAddress, serverTx.walletChanges || [])
|
|
44
44
|
const { baseBalanceUpdate, nonceUpdate, tokenBalancesUpdate } = walletUpdates
|
|
45
45
|
|
|
@@ -6,8 +6,18 @@ import { isEthereumStakingTx } from '../staking/ethereum/staking-utils.js'
|
|
|
6
6
|
import { MaticStakingApi } from '../staking/matic/api.js'
|
|
7
7
|
|
|
8
8
|
const polygonStakingApi = new MaticStakingApi()
|
|
9
|
-
const ethereumStakingApi = new EthereumStaking(
|
|
10
|
-
|
|
9
|
+
const ethereumStakingApi = new EthereumStaking(
|
|
10
|
+
ethereum.name,
|
|
11
|
+
ethereum.currency,
|
|
12
|
+
undefined,
|
|
13
|
+
undefined
|
|
14
|
+
)
|
|
15
|
+
const ethereumHoleskyStakingApi = new EthereumStaking(
|
|
16
|
+
ethereumholesky.name,
|
|
17
|
+
ethereumholesky.currency,
|
|
18
|
+
undefined,
|
|
19
|
+
undefined
|
|
20
|
+
)
|
|
11
21
|
|
|
12
22
|
export const decodePolygonStakingTxInputAmount = (tx) => {
|
|
13
23
|
const {
|
|
@@ -1,599 +0,0 @@
|
|
|
1
|
-
import { memoize } from '@exodus/basic-utils'
|
|
2
|
-
|
|
3
|
-
import { estimateGasLimit, scaleGasLimitEstimate } from '../../gas-estimation.js'
|
|
4
|
-
import { getAggregateTransactionPricing } from '../../get-fee.js'
|
|
5
|
-
import { getOptimisticTxLogEffects } from '../../tx-log/get-optimistic-txlog-effects.js'
|
|
6
|
-
import { createWatchTx as defaultCreateWatch } from '../../watch-tx.js'
|
|
7
|
-
import { stakingProviderClientFactory } from '../staking-provider-client.js'
|
|
8
|
-
import {
|
|
9
|
-
amountToCurrency,
|
|
10
|
-
DISABLE_BALANCE_CHECKS,
|
|
11
|
-
resolveFeeData as defaultResolveFeeData,
|
|
12
|
-
} from '../utils/index.js'
|
|
13
|
-
import { MaticStakingApi } from './api.js'
|
|
14
|
-
import { maticDelegateSimulateTransactions } from './matic-staking-utils.js'
|
|
15
|
-
|
|
16
|
-
const createStakingApiForFeeAsset = ({ feeAsset: { server } }) =>
|
|
17
|
-
new MaticStakingApi(undefined, undefined, server)
|
|
18
|
-
|
|
19
|
-
// TODO: This should be `createMaticStakingService` to avoid confusion with Polygon staking.
|
|
20
|
-
export function createPolygonStakingService({
|
|
21
|
-
assetClientInterface,
|
|
22
|
-
createWatchTx = defaultCreateWatch,
|
|
23
|
-
stakingProvider = stakingProviderClientFactory(),
|
|
24
|
-
}) {
|
|
25
|
-
const assetName = 'ethereum'
|
|
26
|
-
|
|
27
|
-
const getStakeAssets = memoize(async () => {
|
|
28
|
-
const { polygon: asset, [assetName]: feeAsset } =
|
|
29
|
-
await assetClientInterface.getAssetsForNetwork({
|
|
30
|
-
baseAssetName: assetName,
|
|
31
|
-
})
|
|
32
|
-
return { asset, feeAsset }
|
|
33
|
-
})
|
|
34
|
-
|
|
35
|
-
const createStakingApi = memoize(async () => {
|
|
36
|
-
const { asset, feeAsset } = await getStakeAssets()
|
|
37
|
-
return { asset, feeAsset, stakingApi: createStakingApiForFeeAsset({ feeAsset }) }
|
|
38
|
-
})
|
|
39
|
-
|
|
40
|
-
// Helper function which selects the correct `asset` to use for when
|
|
41
|
-
// determining `feeData` for MATIC staking.
|
|
42
|
-
const resolveFeeData = async ({ feeData }) => {
|
|
43
|
-
const { feeAsset } = await getStakeAssets()
|
|
44
|
-
return defaultResolveFeeData({ asset: feeAsset, assetClientInterface, feeData })
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
const getLatestFeeData = async () => {
|
|
48
|
-
const { feeAsset } = await getStakeAssets()
|
|
49
|
-
return assetClientInterface.getFeeData({ assetName: feeAsset.name })
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
const resolveOptionalFeeData = async ({ feeData }) => {
|
|
53
|
-
// If the caller provides truthy `feeData`, we can continue
|
|
54
|
-
// as normal.
|
|
55
|
-
if (feeData) return feeData
|
|
56
|
-
|
|
57
|
-
// If the caller specifically omits `feeData`, then we
|
|
58
|
-
// provide a backup without warning. This is useful for
|
|
59
|
-
// calls with no expectations on the caller to provide
|
|
60
|
-
// `feeData`, i.e. one-shot transactions, or transactions
|
|
61
|
-
// which do not render a fee estimation.
|
|
62
|
-
return getLatestFeeData()
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
const getDelegatorAddress = async ({ walletAccount }) => {
|
|
66
|
-
const address = await assetClientInterface.getReceiveAddress({
|
|
67
|
-
assetName,
|
|
68
|
-
walletAccount,
|
|
69
|
-
})
|
|
70
|
-
return address.toLowerCase()
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
/**
|
|
74
|
-
* Delegate MATIC tokens for staking.
|
|
75
|
-
* @param {walletAccount} params.walletAccount - The walletAccount for the delegator (required).
|
|
76
|
-
* @param {NumberUnit} params.amount - The amount of MATIC to delegate (required).
|
|
77
|
-
* @param {FeeData} params.feeData - Optional feeData to use for the transaction (optional, if not provided will be obtained from the asset client interface).
|
|
78
|
-
* @param {Boolean} params.revertOnSimulationError - Optional boolean to revert on simulation error (optional, default is true). Allows us to quickly react to simulator issues.
|
|
79
|
-
*/
|
|
80
|
-
async function delegate({ walletAccount, amount, feeData, revertOnSimulationError = true }) {
|
|
81
|
-
const [delegatorAddress, { asset, stakingApi }, resolvedFeeData] = await Promise.all([
|
|
82
|
-
getDelegatorAddress({ walletAccount }),
|
|
83
|
-
createStakingApi(),
|
|
84
|
-
resolveFeeData({ feeData }),
|
|
85
|
-
])
|
|
86
|
-
|
|
87
|
-
feeData = resolvedFeeData
|
|
88
|
-
|
|
89
|
-
const baseNonce = await asset.baseAsset.getNonce({
|
|
90
|
-
asset,
|
|
91
|
-
fromAddress: delegatorAddress.toString(),
|
|
92
|
-
walletAccount,
|
|
93
|
-
})
|
|
94
|
-
|
|
95
|
-
const [txApproveData, txDelegateData] = await Promise.all([
|
|
96
|
-
stakingApi.approveStakeManager(amount),
|
|
97
|
-
stakingApi.delegate({ amount }),
|
|
98
|
-
])
|
|
99
|
-
|
|
100
|
-
const {
|
|
101
|
-
gasPrice,
|
|
102
|
-
gasLimit: approveGasLimit,
|
|
103
|
-
tipGasPrice,
|
|
104
|
-
} = await estimateTxFee({
|
|
105
|
-
from: delegatorAddress,
|
|
106
|
-
to: stakingApi.polygonContract.address,
|
|
107
|
-
txInput: txApproveData,
|
|
108
|
-
feeData,
|
|
109
|
-
})
|
|
110
|
-
|
|
111
|
-
// Estimate based on the average gas usage for the delegate transaction. 250000 is the average.
|
|
112
|
-
const { gasLimit: estimatedDelegateGasLimit } = await estimateDelegateTxFee({
|
|
113
|
-
feeData,
|
|
114
|
-
})
|
|
115
|
-
|
|
116
|
-
const createTxBaseArgs = {
|
|
117
|
-
asset: asset.baseAsset,
|
|
118
|
-
walletAccount,
|
|
119
|
-
fromAddress: delegatorAddress.toString(),
|
|
120
|
-
amount: asset.baseAsset.currency.ZERO,
|
|
121
|
-
tipGasPrice,
|
|
122
|
-
gasPrice,
|
|
123
|
-
}
|
|
124
|
-
const [unsignedTxApprove, unsignedTxDelegate] = await Promise.all([
|
|
125
|
-
asset.baseAsset.api.createTx({
|
|
126
|
-
...createTxBaseArgs,
|
|
127
|
-
toAddress: stakingApi.polygonContract.address,
|
|
128
|
-
txInput: txApproveData,
|
|
129
|
-
gasLimit: approveGasLimit,
|
|
130
|
-
nonce: baseNonce,
|
|
131
|
-
}),
|
|
132
|
-
asset.baseAsset.api.createTx({
|
|
133
|
-
...createTxBaseArgs,
|
|
134
|
-
toAddress: stakingApi.validatorShareContract.address,
|
|
135
|
-
txInput: txDelegateData,
|
|
136
|
-
gasLimit: estimatedDelegateGasLimit,
|
|
137
|
-
nonce: baseNonce + 1,
|
|
138
|
-
}),
|
|
139
|
-
])
|
|
140
|
-
|
|
141
|
-
await maticDelegateSimulateTransactions({
|
|
142
|
-
asset,
|
|
143
|
-
unsignedTxApprove,
|
|
144
|
-
unsignedTxDelegate,
|
|
145
|
-
senderAddress: delegatorAddress.toString(),
|
|
146
|
-
revertOnSimulationError,
|
|
147
|
-
})
|
|
148
|
-
|
|
149
|
-
// Sign transactions
|
|
150
|
-
const [approveSigned, delegateSigned] = await Promise.all([
|
|
151
|
-
assetClientInterface.signTransaction({
|
|
152
|
-
assetName: asset.baseAsset.name,
|
|
153
|
-
unsignedTx: unsignedTxApprove.unsignedTx,
|
|
154
|
-
walletAccount,
|
|
155
|
-
}),
|
|
156
|
-
assetClientInterface.signTransaction({
|
|
157
|
-
assetName: asset.baseAsset.name,
|
|
158
|
-
unsignedTx: unsignedTxDelegate.unsignedTx,
|
|
159
|
-
walletAccount,
|
|
160
|
-
}),
|
|
161
|
-
])
|
|
162
|
-
|
|
163
|
-
// Pre-compute txIDs
|
|
164
|
-
const approveTxId = `0x${approveSigned.txId.toString('hex')}`
|
|
165
|
-
const delegateTxId = `0x${delegateSigned.txId.toString('hex')}`
|
|
166
|
-
|
|
167
|
-
const bundleResponse = await asset.baseAsset.broadcastPrivateBundle({
|
|
168
|
-
txs: [approveSigned, delegateSigned].map(({ rawTx }) => rawTx),
|
|
169
|
-
})
|
|
170
|
-
const bundleHash = bundleResponse?.bundleHash
|
|
171
|
-
|
|
172
|
-
const { optimisticTxLogEffects: approveOptimisticTxLogEffects } =
|
|
173
|
-
await getOptimisticTxLogEffects({
|
|
174
|
-
asset: asset.baseAsset,
|
|
175
|
-
assetClientInterface,
|
|
176
|
-
fromAddress: delegatorAddress.toString(),
|
|
177
|
-
txId: approveTxId,
|
|
178
|
-
unsignedTx: unsignedTxApprove.unsignedTx,
|
|
179
|
-
walletAccount,
|
|
180
|
-
bundleId: bundleHash ?? undefined,
|
|
181
|
-
})
|
|
182
|
-
|
|
183
|
-
const { optimisticTxLogEffects: delegateOptimisticTxLogEffects } =
|
|
184
|
-
await getOptimisticTxLogEffects({
|
|
185
|
-
asset: asset.baseAsset,
|
|
186
|
-
assetClientInterface,
|
|
187
|
-
fromAddress: delegatorAddress.toString(),
|
|
188
|
-
txId: delegateTxId,
|
|
189
|
-
unsignedTx: unsignedTxDelegate.unsignedTx,
|
|
190
|
-
walletAccount,
|
|
191
|
-
bundleId: bundleHash ?? undefined,
|
|
192
|
-
})
|
|
193
|
-
// Combine optimistic effects from both transactions
|
|
194
|
-
const tokenOptimisticEffects = [
|
|
195
|
-
...approveOptimisticTxLogEffects,
|
|
196
|
-
...delegateOptimisticTxLogEffects,
|
|
197
|
-
]
|
|
198
|
-
|
|
199
|
-
for (const optimisticEffect of tokenOptimisticEffects) {
|
|
200
|
-
await assetClientInterface.updateTxLogAndNotify(optimisticEffect)
|
|
201
|
-
}
|
|
202
|
-
|
|
203
|
-
await stakingProvider.notifyStaking({
|
|
204
|
-
txId: delegateTxId,
|
|
205
|
-
asset: asset.name,
|
|
206
|
-
delegator: delegatorAddress,
|
|
207
|
-
amount: amount.toBaseString(),
|
|
208
|
-
})
|
|
209
|
-
return delegateTxId
|
|
210
|
-
}
|
|
211
|
-
|
|
212
|
-
async function undelegate({ walletAccount, amount, feeData, waitForConfirmation = false }) {
|
|
213
|
-
feeData = await resolveOptionalFeeData({ feeData })
|
|
214
|
-
|
|
215
|
-
const [delegatorAddress, { asset, stakingApi }] = await Promise.all([
|
|
216
|
-
getDelegatorAddress({ walletAccount }),
|
|
217
|
-
createStakingApi(),
|
|
218
|
-
])
|
|
219
|
-
|
|
220
|
-
amount = amountToCurrency({ asset, amount })
|
|
221
|
-
|
|
222
|
-
const txUndelegateData = await stakingApi.undelegate({ amount })
|
|
223
|
-
const { gasPrice, gasLimit, tipGasPrice } = await estimateTxFee({
|
|
224
|
-
from: delegatorAddress.toLowerCase(),
|
|
225
|
-
to: stakingApi.validatorShareContract.address,
|
|
226
|
-
txInput: txUndelegateData,
|
|
227
|
-
feeData,
|
|
228
|
-
})
|
|
229
|
-
|
|
230
|
-
return prepareAndSendTx({
|
|
231
|
-
walletAccount,
|
|
232
|
-
to: stakingApi.validatorShareContract.address,
|
|
233
|
-
txData: txUndelegateData,
|
|
234
|
-
gasPrice,
|
|
235
|
-
gasLimit,
|
|
236
|
-
tipGasPrice,
|
|
237
|
-
feeData,
|
|
238
|
-
waitForConfirmation,
|
|
239
|
-
})
|
|
240
|
-
}
|
|
241
|
-
|
|
242
|
-
async function claimRewards({ walletAccount, feeData }) {
|
|
243
|
-
feeData = await resolveOptionalFeeData({ feeData })
|
|
244
|
-
|
|
245
|
-
const [delegatorAddress, { stakingApi }] = await Promise.all([
|
|
246
|
-
getDelegatorAddress({ walletAccount }),
|
|
247
|
-
createStakingApi(),
|
|
248
|
-
])
|
|
249
|
-
|
|
250
|
-
const txWithdrawRewardsData = await stakingApi.withdrawRewards()
|
|
251
|
-
const { gasPrice, gasLimit, tipGasPrice } = await estimateTxFee({
|
|
252
|
-
from: delegatorAddress,
|
|
253
|
-
to: stakingApi.validatorShareContract.address,
|
|
254
|
-
txInput: txWithdrawRewardsData,
|
|
255
|
-
feeData,
|
|
256
|
-
})
|
|
257
|
-
return prepareAndSendTx({
|
|
258
|
-
walletAccount,
|
|
259
|
-
to: stakingApi.validatorShareContract.address,
|
|
260
|
-
txData: txWithdrawRewardsData,
|
|
261
|
-
gasPrice,
|
|
262
|
-
gasLimit,
|
|
263
|
-
tipGasPrice,
|
|
264
|
-
feeData,
|
|
265
|
-
})
|
|
266
|
-
}
|
|
267
|
-
|
|
268
|
-
async function claimUndelegatedBalance({ walletAccount, unbondNonce, feeData }) {
|
|
269
|
-
feeData = await resolveOptionalFeeData({ feeData })
|
|
270
|
-
|
|
271
|
-
const [delegatorAddress, { asset, stakingApi }] = await Promise.all([
|
|
272
|
-
getDelegatorAddress({ walletAccount }),
|
|
273
|
-
createStakingApi(),
|
|
274
|
-
])
|
|
275
|
-
|
|
276
|
-
const { currency } = asset
|
|
277
|
-
const unstakedClaimInfo = await fetchUnstakedClaimInfo({
|
|
278
|
-
stakingApi,
|
|
279
|
-
delegator: delegatorAddress,
|
|
280
|
-
})
|
|
281
|
-
|
|
282
|
-
const { unclaimedUndelegatedBalance } = await getUnstakedUnclaimedInfo({
|
|
283
|
-
stakingApi,
|
|
284
|
-
currency,
|
|
285
|
-
delegator: delegatorAddress,
|
|
286
|
-
...unstakedClaimInfo,
|
|
287
|
-
})
|
|
288
|
-
|
|
289
|
-
const txClaimUndelegatedData = await stakingApi.claimUndelegatedBalance({ unbondNonce })
|
|
290
|
-
const { gasPrice, gasLimit, tipGasPrice } = await estimateTxFee({
|
|
291
|
-
from: delegatorAddress,
|
|
292
|
-
to: stakingApi.validatorShareContract.address,
|
|
293
|
-
txInput: txClaimUndelegatedData,
|
|
294
|
-
feeData,
|
|
295
|
-
})
|
|
296
|
-
const txId = await prepareAndSendTx({
|
|
297
|
-
walletAccount,
|
|
298
|
-
to: stakingApi.validatorShareContract.address,
|
|
299
|
-
txData: txClaimUndelegatedData,
|
|
300
|
-
gasPrice,
|
|
301
|
-
gasLimit,
|
|
302
|
-
tipGasPrice,
|
|
303
|
-
feeData,
|
|
304
|
-
})
|
|
305
|
-
|
|
306
|
-
await stakingProvider.notifyUnstaking({
|
|
307
|
-
txId,
|
|
308
|
-
asset: asset.name,
|
|
309
|
-
delegator: delegatorAddress,
|
|
310
|
-
amount: unclaimedUndelegatedBalance.toBaseString(),
|
|
311
|
-
})
|
|
312
|
-
|
|
313
|
-
return txId
|
|
314
|
-
}
|
|
315
|
-
|
|
316
|
-
async function estimateDelegateOperation({
|
|
317
|
-
walletAccount,
|
|
318
|
-
operation,
|
|
319
|
-
args,
|
|
320
|
-
// NOTE: When estimating transactions, ideally we'd expect the `feeData`
|
|
321
|
-
// that we intend to send the transaction using. If this is not
|
|
322
|
-
// defined, we'll fallback to a default (with a warning).
|
|
323
|
-
feeData,
|
|
324
|
-
}) {
|
|
325
|
-
// HACK: For delegation transactions, we must fall back to the
|
|
326
|
-
// custom implementation, since we can't currently estimate
|
|
327
|
-
// the transaction due to a dependence upon approvals.
|
|
328
|
-
if (operation === 'delegate') return estimateDelegateTxFee({ feeData })
|
|
329
|
-
|
|
330
|
-
const [delegatorAddress, { asset, stakingApi }] = await Promise.all([
|
|
331
|
-
getDelegatorAddress({ walletAccount }),
|
|
332
|
-
createStakingApi(),
|
|
333
|
-
])
|
|
334
|
-
|
|
335
|
-
feeData = await resolveFeeData({ feeData })
|
|
336
|
-
|
|
337
|
-
const delegateOperation = stakingApi[operation]
|
|
338
|
-
if (!delegateOperation) return
|
|
339
|
-
|
|
340
|
-
const { amount } = args
|
|
341
|
-
if (amount) {
|
|
342
|
-
args = { ...args, amount: amountToCurrency({ asset, amount }) }
|
|
343
|
-
}
|
|
344
|
-
|
|
345
|
-
const operationTxData = await delegateOperation({ ...args, walletAccount })
|
|
346
|
-
const { fee } = await estimateTxFee({
|
|
347
|
-
from: delegatorAddress,
|
|
348
|
-
to: stakingApi.validatorShareContract.address,
|
|
349
|
-
txInput: operationTxData,
|
|
350
|
-
feeData,
|
|
351
|
-
})
|
|
352
|
-
|
|
353
|
-
return fee
|
|
354
|
-
}
|
|
355
|
-
|
|
356
|
-
/**
|
|
357
|
-
* Estimating delegete tx using {estimateGasLimit} function does not work
|
|
358
|
-
* as the execution reverts (due to missing MATIC approval).
|
|
359
|
-
* Instead, a fixed gas limit is use, which is:
|
|
360
|
-
*
|
|
361
|
-
* delegateGasLimit = ERC20ApproveGas + delegateGas
|
|
362
|
-
*
|
|
363
|
-
* This is just for displaying purposes and it's just an aproximation of the delegate gas cost,
|
|
364
|
-
* NOT the real fee cost
|
|
365
|
-
*/
|
|
366
|
-
async function estimateDelegateTxFee({ feeData } = Object.create(null)) {
|
|
367
|
-
// approx gas limits
|
|
368
|
-
const { ethereum } = await assetClientInterface.getAssetsForNetwork({
|
|
369
|
-
baseAssetName: 'ethereum',
|
|
370
|
-
})
|
|
371
|
-
|
|
372
|
-
feeData = await resolveFeeData({ feeData })
|
|
373
|
-
|
|
374
|
-
// TODO: update estimation to use a mock source address for
|
|
375
|
-
// deposits so we can simulate the necessary approvals,
|
|
376
|
-
// we shouldn't maintain constants like these
|
|
377
|
-
const erc20ApproveGas = 80_000
|
|
378
|
-
const delegateGas = 250_000
|
|
379
|
-
|
|
380
|
-
const { gasPrice } = getAggregateTransactionPricing({ baseAsset: ethereum, feeData })
|
|
381
|
-
|
|
382
|
-
const gasLimitWithBuffer = scaleGasLimitEstimate({
|
|
383
|
-
estimatedGasLimit: BigInt(erc20ApproveGas + delegateGas),
|
|
384
|
-
})
|
|
385
|
-
|
|
386
|
-
const fee = BigInt(gasLimitWithBuffer) * BigInt(gasPrice.toBaseNumber())
|
|
387
|
-
|
|
388
|
-
return {
|
|
389
|
-
gasLimit: gasLimitWithBuffer,
|
|
390
|
-
gasPrice,
|
|
391
|
-
fee: ethereum.currency.baseUnit(fee.toString()),
|
|
392
|
-
}
|
|
393
|
-
}
|
|
394
|
-
|
|
395
|
-
async function estimateTxFee({ from, to, txInput, feeData }) {
|
|
396
|
-
const { ethereum } = await assetClientInterface.getAssetsForNetwork({
|
|
397
|
-
baseAssetName: 'ethereum',
|
|
398
|
-
})
|
|
399
|
-
|
|
400
|
-
const amount = ethereum.currency.ZERO
|
|
401
|
-
|
|
402
|
-
const gasLimit = await estimateGasLimit({
|
|
403
|
-
asset: ethereum,
|
|
404
|
-
fromAddress: from,
|
|
405
|
-
toAddress: to,
|
|
406
|
-
amount, // staking contracts does not require ETH amount to interact with
|
|
407
|
-
data: txInput,
|
|
408
|
-
gasPrice: DISABLE_BALANCE_CHECKS,
|
|
409
|
-
})
|
|
410
|
-
|
|
411
|
-
const fee = ethereum.api.getFee({ asset: ethereum, feeData, gasLimit, amount })
|
|
412
|
-
return { ...fee, gasLimit }
|
|
413
|
-
}
|
|
414
|
-
|
|
415
|
-
async function prepareAndSendTx({
|
|
416
|
-
walletAccount,
|
|
417
|
-
to,
|
|
418
|
-
txData: txInput,
|
|
419
|
-
gasPrice,
|
|
420
|
-
gasLimit,
|
|
421
|
-
tipGasPrice,
|
|
422
|
-
waitForConfirmation = false,
|
|
423
|
-
feeData,
|
|
424
|
-
}) {
|
|
425
|
-
const { ethereum: asset } = await assetClientInterface.getAssetsForNetwork({
|
|
426
|
-
baseAssetName: 'ethereum',
|
|
427
|
-
})
|
|
428
|
-
const sendTxArgs = {
|
|
429
|
-
asset,
|
|
430
|
-
walletAccount,
|
|
431
|
-
address: to,
|
|
432
|
-
amount: asset.currency.ZERO,
|
|
433
|
-
txInput,
|
|
434
|
-
gasPrice,
|
|
435
|
-
gasLimit,
|
|
436
|
-
tipGasPrice,
|
|
437
|
-
waitForConfirmation,
|
|
438
|
-
feeData,
|
|
439
|
-
}
|
|
440
|
-
|
|
441
|
-
const { txId } = await asset.baseAsset.api.sendTx(sendTxArgs)
|
|
442
|
-
|
|
443
|
-
const baseAsset = asset.baseAsset
|
|
444
|
-
if (waitForConfirmation) {
|
|
445
|
-
const getTxLog = (...args) => assetClientInterface.getTxLog(...args)
|
|
446
|
-
const watchTx = createWatchTx({
|
|
447
|
-
walletAccount,
|
|
448
|
-
getTxLog,
|
|
449
|
-
baseAsset,
|
|
450
|
-
})
|
|
451
|
-
await watchTx(txId)
|
|
452
|
-
}
|
|
453
|
-
|
|
454
|
-
return txId
|
|
455
|
-
}
|
|
456
|
-
|
|
457
|
-
return {
|
|
458
|
-
delegate,
|
|
459
|
-
undelegate,
|
|
460
|
-
claimRewards,
|
|
461
|
-
claimUndelegatedBalance,
|
|
462
|
-
getPolygonStakingInfo,
|
|
463
|
-
estimateDelegateTxFee,
|
|
464
|
-
estimateDelegateOperation,
|
|
465
|
-
}
|
|
466
|
-
}
|
|
467
|
-
|
|
468
|
-
async function fetchUnstakedClaimInfo({ stakingApi, delegator }) {
|
|
469
|
-
const [unbondNonce, withdrawalDelay, currentEpoch, withdrawExchangeRate] = await Promise.all([
|
|
470
|
-
stakingApi.getCurrentUnbondNonce(delegator),
|
|
471
|
-
stakingApi.getWithdrawalDelay(),
|
|
472
|
-
stakingApi.getCurrentCheckpoint(),
|
|
473
|
-
stakingApi.getWithdrawExchangeRate(),
|
|
474
|
-
])
|
|
475
|
-
|
|
476
|
-
return {
|
|
477
|
-
unbondNonce,
|
|
478
|
-
withdrawalDelay,
|
|
479
|
-
currentEpoch,
|
|
480
|
-
withdrawExchangeRate,
|
|
481
|
-
}
|
|
482
|
-
}
|
|
483
|
-
|
|
484
|
-
function calculateUnclaimedTokens({
|
|
485
|
-
currency,
|
|
486
|
-
exchangeRatePrecision,
|
|
487
|
-
withdrawExchangeRate,
|
|
488
|
-
shares,
|
|
489
|
-
canClaimUndelegatedBalance,
|
|
490
|
-
isUndelegateInProgress,
|
|
491
|
-
}) {
|
|
492
|
-
// see contract implementation
|
|
493
|
-
// https://github.com/maticnetwork/contracts/blob/1eb6960e511a967c15d4936904570a890d134fa6/contracts/staking/validatorShare/ValidatorShare.sol#L304
|
|
494
|
-
if (canClaimUndelegatedBalance || isUndelegateInProgress) {
|
|
495
|
-
const unclaimedTokens = withdrawExchangeRate
|
|
496
|
-
.mul(shares) // shares === validator tokens
|
|
497
|
-
.div(exchangeRatePrecision)
|
|
498
|
-
.toString()
|
|
499
|
-
|
|
500
|
-
return currency.baseUnit(unclaimedTokens)
|
|
501
|
-
}
|
|
502
|
-
|
|
503
|
-
return currency.ZERO
|
|
504
|
-
}
|
|
505
|
-
|
|
506
|
-
function canClaimUndelegatedBalance({ shares, withdrawEpoch, isUndelegateInProgress }) {
|
|
507
|
-
const undelegateNotStarted = shares.isZero() && withdrawEpoch.isZero()
|
|
508
|
-
return !(isUndelegateInProgress || undelegateNotStarted)
|
|
509
|
-
}
|
|
510
|
-
|
|
511
|
-
async function getUnstakedUnclaimedInfo({
|
|
512
|
-
stakingApi,
|
|
513
|
-
currency,
|
|
514
|
-
delegator,
|
|
515
|
-
unbondNonce,
|
|
516
|
-
currentEpoch,
|
|
517
|
-
withdrawalDelay,
|
|
518
|
-
withdrawExchangeRate,
|
|
519
|
-
}) {
|
|
520
|
-
const { withdrawEpoch, shares } = await stakingApi.getUnboundInfo(delegator, unbondNonce)
|
|
521
|
-
const exchangeRatePrecision = stakingApi.EXCHANGE_RATE_PRECISION
|
|
522
|
-
const isUndelegateInProgress =
|
|
523
|
-
!withdrawEpoch.isZero() && withdrawEpoch.add(withdrawalDelay).gte(currentEpoch)
|
|
524
|
-
const isUndelegatedBalanceClaimable = canClaimUndelegatedBalance({
|
|
525
|
-
shares,
|
|
526
|
-
withdrawEpoch,
|
|
527
|
-
isUndelegateInProgress,
|
|
528
|
-
})
|
|
529
|
-
const unclaimedUndelegatedBalance = calculateUnclaimedTokens({
|
|
530
|
-
currency,
|
|
531
|
-
exchangeRatePrecision,
|
|
532
|
-
withdrawExchangeRate,
|
|
533
|
-
shares,
|
|
534
|
-
canClaimUndelegatedBalance: isUndelegatedBalanceClaimable,
|
|
535
|
-
isUndelegateInProgress,
|
|
536
|
-
})
|
|
537
|
-
return {
|
|
538
|
-
isUndelegateInProgress,
|
|
539
|
-
canClaimUndelegatedBalance: isUndelegatedBalanceClaimable,
|
|
540
|
-
unclaimedUndelegatedBalance,
|
|
541
|
-
}
|
|
542
|
-
}
|
|
543
|
-
|
|
544
|
-
async function fetchRewardsInfo({ stakingApi, delegator, currency }) {
|
|
545
|
-
const [minRewardsToWithdraw, lastRewards, rewardsBalance] = await Promise.all([
|
|
546
|
-
stakingApi.getMinRewardsToWithdraw(),
|
|
547
|
-
stakingApi.getLiquidRewards(delegator),
|
|
548
|
-
stakingApi.getTotalRewards(delegator),
|
|
549
|
-
])
|
|
550
|
-
const withdrawable = lastRewards.sub(minRewardsToWithdraw).gte(currency.ZERO)
|
|
551
|
-
? lastRewards
|
|
552
|
-
: currency.ZERO
|
|
553
|
-
|
|
554
|
-
return {
|
|
555
|
-
rewardsBalance: rewardsBalance.add(lastRewards), // all time accrued rewards
|
|
556
|
-
liquidRewards: lastRewards, // current pending rewards (on-chain)
|
|
557
|
-
minRewardsToWithdraw,
|
|
558
|
-
withdrawable, // unclaimed rewards
|
|
559
|
-
}
|
|
560
|
-
}
|
|
561
|
-
|
|
562
|
-
export async function getPolygonStakingInfo({ address, asset: { currency, baseAsset: feeAsset } }) {
|
|
563
|
-
const stakingApi = createStakingApiForFeeAsset({ feeAsset })
|
|
564
|
-
const delegator = address.toLowerCase()
|
|
565
|
-
const [
|
|
566
|
-
delegatedBalance,
|
|
567
|
-
{ rewardsBalance, liquidRewards, minRewardsToWithdraw, withdrawable },
|
|
568
|
-
{ unbondNonce, withdrawalDelay, currentEpoch, withdrawExchangeRate },
|
|
569
|
-
] = await Promise.all([
|
|
570
|
-
stakingApi.getTotalStake(delegator),
|
|
571
|
-
fetchRewardsInfo({ stakingApi, delegator, currency }),
|
|
572
|
-
fetchUnstakedClaimInfo({ stakingApi, delegator }),
|
|
573
|
-
])
|
|
574
|
-
const minDelegateAmount = currency.defaultUnit(1)
|
|
575
|
-
const isDelegating = !delegatedBalance.isZero
|
|
576
|
-
|
|
577
|
-
const unclaimedUndelegatedInfo = await getUnstakedUnclaimedInfo({
|
|
578
|
-
stakingApi,
|
|
579
|
-
currency,
|
|
580
|
-
delegator,
|
|
581
|
-
unbondNonce,
|
|
582
|
-
currentEpoch,
|
|
583
|
-
withdrawalDelay,
|
|
584
|
-
withdrawExchangeRate,
|
|
585
|
-
})
|
|
586
|
-
|
|
587
|
-
return {
|
|
588
|
-
rewardsBalance,
|
|
589
|
-
liquidRewards,
|
|
590
|
-
withdrawable,
|
|
591
|
-
unbondNonce,
|
|
592
|
-
isDelegating,
|
|
593
|
-
delegatedBalance,
|
|
594
|
-
activeStakedBalance: delegatedBalance,
|
|
595
|
-
minRewardsToWithdraw,
|
|
596
|
-
minDelegateAmount,
|
|
597
|
-
...unclaimedUndelegatedInfo,
|
|
598
|
-
}
|
|
599
|
-
}
|