@exodus/ethereum-api 8.34.6 → 8.35.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +50 -0
- package/package.json +3 -3
- package/src/create-asset.js +1 -2
- package/src/custom-fees.js +40 -10
- package/src/etherscan/ws.js +1 -2
- package/src/exodus-eth-server/ws.js +1 -2
- package/src/fee-utils.js +17 -3
- package/src/gas-estimation.js +25 -6
- package/src/get-fee.js +29 -35
- package/src/index.js +2 -0
- package/src/staking/ethereum/api.js +1 -0
- package/src/staking/ethereum/service.js +116 -92
- package/src/staking/matic/matic-staking.md +53 -0
- package/src/staking/matic/service.js +189 -102
- package/src/staking/utils/index.js +26 -60
- package/src/tx-log/clarity-monitor.js +26 -3
- package/src/tx-send/get-fee-info.js +34 -9
- package/src/tx-send/nonce-utils.js +2 -1
- package/src/tx-send/tx-send.js +73 -31
- package/src/websocket/index.android.js +0 -2
- package/src/websocket/index.ios.js +0 -2
- package/src/websocket/index.js +0 -2
- package/src/websocket/websocket.cjs +0 -21
|
@@ -1,71 +1,140 @@
|
|
|
1
|
-
import
|
|
1
|
+
import { memoize } from '@exodus/basic-utils'
|
|
2
2
|
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
3
|
+
import { estimateGasLimit, scaleGasLimitEstimate } from '../../gas-estimation.js'
|
|
4
|
+
import { getFeeFactoryGasPrices } from '../../get-fee.js'
|
|
5
5
|
import { createWatchTx as defaultCreateWatch } from '../../watch-tx.js'
|
|
6
6
|
import { stakingProviderClientFactory } from '../staking-provider-client.js'
|
|
7
|
-
import {
|
|
7
|
+
import {
|
|
8
|
+
amountToCurrency,
|
|
9
|
+
DISABLE_BALANCE_CHECKS,
|
|
10
|
+
resolveFeeData as defaultResolveFeeData,
|
|
11
|
+
} from '../utils/index.js'
|
|
8
12
|
import { MaticStakingApi } from './api.js'
|
|
9
13
|
|
|
10
|
-
const
|
|
11
|
-
|
|
14
|
+
const createStakingApiForFeeAsset = ({ feeAsset: { server } }) =>
|
|
15
|
+
new MaticStakingApi(undefined, undefined, server)
|
|
12
16
|
|
|
17
|
+
// TODO: This should be `createMaticStakingService` to avoid confusion with Polygon staking.
|
|
13
18
|
export function createPolygonStakingService({
|
|
14
19
|
assetClientInterface,
|
|
15
20
|
createWatchTx = defaultCreateWatch,
|
|
21
|
+
stakingProvider = stakingProviderClientFactory(),
|
|
16
22
|
}) {
|
|
17
|
-
const stakingApi = new MaticStakingApi()
|
|
18
23
|
const assetName = 'ethereum'
|
|
19
|
-
const stakingProvider = stakingProviderClientFactory()
|
|
20
24
|
|
|
21
|
-
|
|
22
|
-
const { polygon: asset,
|
|
23
|
-
|
|
24
|
-
|
|
25
|
+
const getStakeAssets = memoize(async () => {
|
|
26
|
+
const { polygon: asset, [assetName]: feeAsset } =
|
|
27
|
+
await assetClientInterface.getAssetsForNetwork({
|
|
28
|
+
baseAssetName: assetName,
|
|
29
|
+
})
|
|
25
30
|
return { asset, feeAsset }
|
|
31
|
+
})
|
|
32
|
+
|
|
33
|
+
const createStakingApi = memoize(async () => {
|
|
34
|
+
const { asset, feeAsset } = await getStakeAssets()
|
|
35
|
+
return { asset, feeAsset, stakingApi: createStakingApiForFeeAsset({ feeAsset }) }
|
|
36
|
+
})
|
|
37
|
+
|
|
38
|
+
// Helper function which selects the correct `asset` to use for when
|
|
39
|
+
// determining `feeData` for MATIC staking.
|
|
40
|
+
const resolveFeeData = async ({ feeData }) => {
|
|
41
|
+
const { feeAsset } = await getStakeAssets()
|
|
42
|
+
return defaultResolveFeeData({ asset: feeAsset, assetClientInterface, feeData })
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
const getLatestFeeData = async () => {
|
|
46
|
+
const { feeAsset } = await getStakeAssets()
|
|
47
|
+
return assetClientInterface.getFeeData({ assetName: feeAsset.name })
|
|
26
48
|
}
|
|
27
49
|
|
|
28
|
-
async
|
|
50
|
+
const resolveOptionalFeeData = async ({ feeData }) => {
|
|
51
|
+
// If the caller provides truthy `feeData`, we can continue
|
|
52
|
+
// as normal.
|
|
53
|
+
if (feeData) return feeData
|
|
54
|
+
|
|
55
|
+
// If the caller specifically omits `feeData`, then we
|
|
56
|
+
// provide a backup without warning. This is useful for
|
|
57
|
+
// calls with no expectations on the caller to provide
|
|
58
|
+
// `feeData`, i.e. one-shot transactions, or transactions
|
|
59
|
+
// which do not render a fee estimation.
|
|
60
|
+
return getLatestFeeData()
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
const getDelegatorAddress = async ({ walletAccount }) => {
|
|
29
64
|
const address = await assetClientInterface.getReceiveAddress({
|
|
30
65
|
assetName,
|
|
31
66
|
walletAccount,
|
|
32
67
|
})
|
|
33
|
-
|
|
68
|
+
return address.toLowerCase()
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
async function delegate({ walletAccount, amount, feeData, waitForConfirmation = true } = {}) {
|
|
72
|
+
const [delegatorAddress, { asset, stakingApi }] = await Promise.all([
|
|
73
|
+
getDelegatorAddress({ walletAccount }),
|
|
74
|
+
createStakingApi(),
|
|
75
|
+
])
|
|
76
|
+
|
|
77
|
+
// NOTE: We do not provide a silent fallback for `feeData` omission
|
|
78
|
+
// for a delegation transaction, since `feeData` is expected
|
|
79
|
+
// to have been provided during `estimateDelegateTxFee`.
|
|
80
|
+
feeData = await resolveFeeData({ feeData })
|
|
34
81
|
|
|
35
|
-
const { asset } = await getStakeAssets()
|
|
36
82
|
amount = amountToCurrency({ asset, amount })
|
|
37
83
|
|
|
38
84
|
const txApproveData = await stakingApi.approveStakeManager(amount)
|
|
39
|
-
let { gasPrice, gasLimit, tipGasPrice } = await estimateTxFee(
|
|
40
|
-
delegatorAddress,
|
|
41
|
-
stakingApi.polygonContract.address,
|
|
42
|
-
txApproveData
|
|
43
|
-
|
|
85
|
+
let { gasPrice, gasLimit, tipGasPrice } = await estimateTxFee({
|
|
86
|
+
from: delegatorAddress,
|
|
87
|
+
to: stakingApi.polygonContract.address,
|
|
88
|
+
txInput: txApproveData,
|
|
89
|
+
feeData,
|
|
90
|
+
})
|
|
44
91
|
await prepareAndSendTx({
|
|
45
92
|
walletAccount,
|
|
46
|
-
waitForConfirmation
|
|
93
|
+
waitForConfirmation,
|
|
47
94
|
to: stakingApi.polygonContract.address,
|
|
48
95
|
txData: txApproveData,
|
|
49
96
|
gasPrice,
|
|
50
97
|
gasLimit,
|
|
51
98
|
tipGasPrice,
|
|
99
|
+
feeData,
|
|
52
100
|
})
|
|
53
101
|
|
|
102
|
+
// NOTE: Whenever we `waitForConfirmation`, we must recalculate
|
|
103
|
+
// the `feeData` as this may have changed since our last
|
|
104
|
+
// transaction was mined.
|
|
105
|
+
//
|
|
106
|
+
// In most cases, this shouldn't affect previous estimates,
|
|
107
|
+
// since the amount we aren't trying to stake using the
|
|
108
|
+
// base currency; however for users with marginal balances,
|
|
109
|
+
// an increase in `gasPrice` could result in transaction
|
|
110
|
+
// failure.
|
|
111
|
+
//
|
|
112
|
+
// HACK: This will invaldiate the original estimate in calculated
|
|
113
|
+
// in `estimateDelegateTxFee`!
|
|
114
|
+
// TODO: Submit these transactions as an atomic bundle.
|
|
115
|
+
if (waitForConfirmation) {
|
|
116
|
+
feeData = await getLatestFeeData()
|
|
117
|
+
}
|
|
118
|
+
|
|
54
119
|
const txDelegateData = await stakingApi.delegate({ amount })
|
|
55
120
|
|
|
56
|
-
;({ gasPrice, gasLimit, tipGasPrice } = await estimateTxFee(
|
|
57
|
-
delegatorAddress,
|
|
58
|
-
stakingApi.validatorShareContract.address,
|
|
59
|
-
txDelegateData
|
|
60
|
-
|
|
121
|
+
;({ gasPrice, gasLimit, tipGasPrice } = await estimateTxFee({
|
|
122
|
+
from: delegatorAddress,
|
|
123
|
+
to: stakingApi.validatorShareContract.address,
|
|
124
|
+
txInput: txDelegateData,
|
|
125
|
+
feeData,
|
|
126
|
+
}))
|
|
61
127
|
|
|
62
128
|
const txId = await prepareAndSendTx({
|
|
63
129
|
walletAccount,
|
|
130
|
+
// TOOD: Why shouldn't we wait for confirmation here? Is it because we want to `notifyStaking`?
|
|
131
|
+
waitForConfirmation: false,
|
|
64
132
|
to: stakingApi.validatorShareContract.address,
|
|
65
133
|
txData: txDelegateData,
|
|
66
134
|
gasPrice,
|
|
67
135
|
gasLimit,
|
|
68
136
|
tipGasPrice,
|
|
137
|
+
feeData,
|
|
69
138
|
})
|
|
70
139
|
|
|
71
140
|
await stakingProvider.notifyStaking({
|
|
@@ -78,22 +147,24 @@ export function createPolygonStakingService({
|
|
|
78
147
|
return txId
|
|
79
148
|
}
|
|
80
149
|
|
|
81
|
-
async function undelegate({ walletAccount, amount } = {}) {
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
150
|
+
async function undelegate({ walletAccount, amount, feeData, waitForConfirmation = false } = {}) {
|
|
151
|
+
feeData = await resolveOptionalFeeData({ feeData })
|
|
152
|
+
|
|
153
|
+
const [delegatorAddress, { asset, stakingApi }] = await Promise.all([
|
|
154
|
+
getDelegatorAddress({ walletAccount }),
|
|
155
|
+
createStakingApi(),
|
|
156
|
+
])
|
|
87
157
|
|
|
88
|
-
const { asset } = await getStakeAssets()
|
|
89
158
|
amount = amountToCurrency({ asset, amount })
|
|
90
159
|
|
|
91
160
|
const txUndelegateData = await stakingApi.undelegate({ amount })
|
|
92
|
-
const { gasPrice, gasLimit, tipGasPrice } = await estimateTxFee(
|
|
93
|
-
delegatorAddress.toLowerCase(),
|
|
94
|
-
stakingApi.validatorShareContract.address,
|
|
95
|
-
txUndelegateData
|
|
96
|
-
|
|
161
|
+
const { gasPrice, gasLimit, tipGasPrice } = await estimateTxFee({
|
|
162
|
+
from: delegatorAddress.toLowerCase(),
|
|
163
|
+
to: stakingApi.validatorShareContract.address,
|
|
164
|
+
txInput: txUndelegateData,
|
|
165
|
+
feeData,
|
|
166
|
+
})
|
|
167
|
+
|
|
97
168
|
return prepareAndSendTx({
|
|
98
169
|
walletAccount,
|
|
99
170
|
to: stakingApi.validatorShareContract.address,
|
|
@@ -101,22 +172,26 @@ export function createPolygonStakingService({
|
|
|
101
172
|
gasPrice,
|
|
102
173
|
gasLimit,
|
|
103
174
|
tipGasPrice,
|
|
175
|
+
feeData,
|
|
176
|
+
waitForConfirmation,
|
|
104
177
|
})
|
|
105
178
|
}
|
|
106
179
|
|
|
107
|
-
async function claimRewards({ walletAccount } = {}) {
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
180
|
+
async function claimRewards({ walletAccount, feeData } = {}) {
|
|
181
|
+
feeData = await resolveOptionalFeeData({ feeData })
|
|
182
|
+
|
|
183
|
+
const [delegatorAddress, { stakingApi }] = await Promise.all([
|
|
184
|
+
getDelegatorAddress({ walletAccount }),
|
|
185
|
+
createStakingApi(),
|
|
186
|
+
])
|
|
113
187
|
|
|
114
188
|
const txWithdrawRewardsData = await stakingApi.withdrawRewards()
|
|
115
|
-
const { gasPrice, gasLimit, tipGasPrice } = await estimateTxFee(
|
|
116
|
-
delegatorAddress,
|
|
117
|
-
stakingApi.validatorShareContract.address,
|
|
118
|
-
txWithdrawRewardsData
|
|
119
|
-
|
|
189
|
+
const { gasPrice, gasLimit, tipGasPrice } = await estimateTxFee({
|
|
190
|
+
from: delegatorAddress,
|
|
191
|
+
to: stakingApi.validatorShareContract.address,
|
|
192
|
+
txInput: txWithdrawRewardsData,
|
|
193
|
+
feeData,
|
|
194
|
+
})
|
|
120
195
|
return prepareAndSendTx({
|
|
121
196
|
walletAccount,
|
|
122
197
|
to: stakingApi.validatorShareContract.address,
|
|
@@ -124,17 +199,18 @@ export function createPolygonStakingService({
|
|
|
124
199
|
gasPrice,
|
|
125
200
|
gasLimit,
|
|
126
201
|
tipGasPrice,
|
|
202
|
+
feeData,
|
|
127
203
|
})
|
|
128
204
|
}
|
|
129
205
|
|
|
130
|
-
async function claimUndelegatedBalance({ walletAccount, unbondNonce } = {}) {
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
206
|
+
async function claimUndelegatedBalance({ walletAccount, unbondNonce, feeData } = {}) {
|
|
207
|
+
feeData = await resolveOptionalFeeData({ feeData })
|
|
208
|
+
|
|
209
|
+
const [delegatorAddress, { asset, stakingApi }] = await Promise.all([
|
|
210
|
+
getDelegatorAddress({ walletAccount }),
|
|
211
|
+
createStakingApi(),
|
|
212
|
+
])
|
|
136
213
|
|
|
137
|
-
const { asset } = await getStakeAssets()
|
|
138
214
|
const { currency } = asset
|
|
139
215
|
const unstakedClaimInfo = await fetchUnstakedClaimInfo({
|
|
140
216
|
stakingApi,
|
|
@@ -149,11 +225,12 @@ export function createPolygonStakingService({
|
|
|
149
225
|
})
|
|
150
226
|
|
|
151
227
|
const txClaimUndelegatedData = await stakingApi.claimUndelegatedBalance({ unbondNonce })
|
|
152
|
-
const { gasPrice, gasLimit, tipGasPrice
|
|
153
|
-
delegatorAddress,
|
|
154
|
-
stakingApi.validatorShareContract.address,
|
|
155
|
-
txClaimUndelegatedData
|
|
156
|
-
|
|
228
|
+
const { gasPrice, gasLimit, tipGasPrice } = await estimateTxFee({
|
|
229
|
+
from: delegatorAddress,
|
|
230
|
+
to: stakingApi.validatorShareContract.address,
|
|
231
|
+
txInput: txClaimUndelegatedData,
|
|
232
|
+
feeData,
|
|
233
|
+
})
|
|
157
234
|
const txId = await prepareAndSendTx({
|
|
158
235
|
walletAccount,
|
|
159
236
|
to: stakingApi.validatorShareContract.address,
|
|
@@ -161,7 +238,7 @@ export function createPolygonStakingService({
|
|
|
161
238
|
gasPrice,
|
|
162
239
|
gasLimit,
|
|
163
240
|
tipGasPrice,
|
|
164
|
-
|
|
241
|
+
feeData,
|
|
165
242
|
})
|
|
166
243
|
|
|
167
244
|
await stakingProvider.notifyUnstaking({
|
|
@@ -174,31 +251,42 @@ export function createPolygonStakingService({
|
|
|
174
251
|
return txId
|
|
175
252
|
}
|
|
176
253
|
|
|
177
|
-
async function estimateDelegateOperation({
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
254
|
+
async function estimateDelegateOperation({
|
|
255
|
+
walletAccount,
|
|
256
|
+
operation,
|
|
257
|
+
args,
|
|
258
|
+
// NOTE: When estimating transactions, ideally we'd expect the `feeData`
|
|
259
|
+
// that we intend to send the transaction using. If this is not
|
|
260
|
+
// defined, we'll fallback to a default (with a warning).
|
|
261
|
+
feeData,
|
|
262
|
+
}) {
|
|
263
|
+
// HACK: For delegation transactions, we must fall back to the
|
|
264
|
+
// custom implementation, since we can't currently estimate
|
|
265
|
+
// the transaction due to a dependence upon approvals.
|
|
266
|
+
if (operation === 'delegate') return estimateDelegateTxFee({ feeData })
|
|
267
|
+
|
|
268
|
+
const [delegatorAddress, { asset, stakingApi }] = await Promise.all([
|
|
269
|
+
getDelegatorAddress({ walletAccount }),
|
|
270
|
+
createStakingApi(),
|
|
271
|
+
])
|
|
272
|
+
|
|
273
|
+
feeData = await resolveFeeData({ feeData })
|
|
183
274
|
|
|
184
|
-
const
|
|
185
|
-
|
|
186
|
-
walletAccount,
|
|
187
|
-
})
|
|
188
|
-
const delegatorAddress = address.toLowerCase()
|
|
275
|
+
const delegateOperation = stakingApi[operation]
|
|
276
|
+
if (!delegateOperation) return
|
|
189
277
|
|
|
190
278
|
const { amount } = args
|
|
191
279
|
if (amount) {
|
|
192
|
-
const { asset } = await getStakeAssets()
|
|
193
280
|
args = { ...args, amount: amountToCurrency({ asset, amount }) }
|
|
194
281
|
}
|
|
195
282
|
|
|
196
283
|
const operationTxData = await delegateOperation({ ...args, walletAccount })
|
|
197
|
-
const { fee } = await estimateTxFee(
|
|
198
|
-
delegatorAddress,
|
|
199
|
-
stakingApi.validatorShareContract.address,
|
|
200
|
-
operationTxData
|
|
201
|
-
|
|
284
|
+
const { fee } = await estimateTxFee({
|
|
285
|
+
from: delegatorAddress,
|
|
286
|
+
to: stakingApi.validatorShareContract.address,
|
|
287
|
+
txInput: operationTxData,
|
|
288
|
+
feeData,
|
|
289
|
+
})
|
|
202
290
|
|
|
203
291
|
return fee
|
|
204
292
|
}
|
|
@@ -213,23 +301,27 @@ export function createPolygonStakingService({
|
|
|
213
301
|
* This is just for displaying purposes and it's just an aproximation of the delegate gas cost,
|
|
214
302
|
* NOT the real fee cost
|
|
215
303
|
*/
|
|
216
|
-
async function estimateDelegateTxFee() {
|
|
304
|
+
async function estimateDelegateTxFee({ feeData } = {}) {
|
|
217
305
|
// approx gas limits
|
|
218
306
|
const { ethereum } = await assetClientInterface.getAssetsForNetwork({
|
|
219
307
|
baseAssetName: 'ethereum',
|
|
220
308
|
})
|
|
221
|
-
const erc20ApproveGas = 4900
|
|
222
|
-
const delegateGas = 240_000
|
|
223
|
-
const gasPrice = parseInt(await getServer(ethereum).gasPrice(), 16)
|
|
224
|
-
const extraPercentage = 20
|
|
225
|
-
|
|
226
|
-
const gasLimit = erc20ApproveGas + delegateGas
|
|
227
|
-
const gasLimitWithBuffer = new BN(gasLimit)
|
|
228
|
-
.imuln(100 + extraPercentage)
|
|
229
|
-
.idivn(100)
|
|
230
|
-
.toString()
|
|
231
309
|
|
|
232
|
-
|
|
310
|
+
feeData = await resolveFeeData({ feeData })
|
|
311
|
+
|
|
312
|
+
// TODO: update estimation to use a mock source address for
|
|
313
|
+
// deposits so we can simulate the necessary approvals,
|
|
314
|
+
// we shouldn't maintain constants like these
|
|
315
|
+
const erc20ApproveGas = 80_000
|
|
316
|
+
const delegateGas = 250_000
|
|
317
|
+
|
|
318
|
+
const { gasPrice } = getFeeFactoryGasPrices({ feeData })
|
|
319
|
+
|
|
320
|
+
const gasLimitWithBuffer = scaleGasLimitEstimate({
|
|
321
|
+
estimatedGasLimit: BigInt(erc20ApproveGas + delegateGas),
|
|
322
|
+
})
|
|
323
|
+
|
|
324
|
+
const fee = BigInt(gasLimitWithBuffer) * BigInt(gasPrice.toBaseNumber())
|
|
233
325
|
|
|
234
326
|
return {
|
|
235
327
|
gasLimit: gasLimitWithBuffer,
|
|
@@ -238,7 +330,7 @@ export function createPolygonStakingService({
|
|
|
238
330
|
}
|
|
239
331
|
}
|
|
240
332
|
|
|
241
|
-
async function estimateTxFee(from, to, txInput) {
|
|
333
|
+
async function estimateTxFee({ from, to, txInput, feeData }) {
|
|
242
334
|
const { ethereum } = await assetClientInterface.getAssetsForNetwork({
|
|
243
335
|
baseAssetName: 'ethereum',
|
|
244
336
|
})
|
|
@@ -254,13 +346,7 @@ export function createPolygonStakingService({
|
|
|
254
346
|
DISABLE_BALANCE_CHECKS
|
|
255
347
|
)
|
|
256
348
|
|
|
257
|
-
return
|
|
258
|
-
amount,
|
|
259
|
-
asset: ethereum,
|
|
260
|
-
assetClientInterface,
|
|
261
|
-
maxPriorityFeePerGas: MAX_PRIORITY_FEE_PER_GAS,
|
|
262
|
-
gasLimit,
|
|
263
|
-
})
|
|
349
|
+
return ethereum.api.getFee({ asset: ethereum, feeData, gasLimit, amount })
|
|
264
350
|
}
|
|
265
351
|
|
|
266
352
|
async function prepareAndSendTx({
|
|
@@ -271,6 +357,7 @@ export function createPolygonStakingService({
|
|
|
271
357
|
gasLimit,
|
|
272
358
|
tipGasPrice,
|
|
273
359
|
waitForConfirmation = false,
|
|
360
|
+
feeData,
|
|
274
361
|
} = {}) {
|
|
275
362
|
const { ethereum: asset } = await assetClientInterface.getAssetsForNetwork({
|
|
276
363
|
baseAssetName: 'ethereum',
|
|
@@ -288,6 +375,7 @@ export function createPolygonStakingService({
|
|
|
288
375
|
tipGasPrice,
|
|
289
376
|
},
|
|
290
377
|
waitForConfirmation,
|
|
378
|
+
feeData,
|
|
291
379
|
}
|
|
292
380
|
|
|
293
381
|
const { txId } = await asset.baseAsset.api.sendTx(sendTxArgs)
|
|
@@ -409,9 +497,8 @@ async function fetchRewardsInfo({ stakingApi, delegator, currency }) {
|
|
|
409
497
|
}
|
|
410
498
|
}
|
|
411
499
|
|
|
412
|
-
export async function getPolygonStakingInfo({ address, asset,
|
|
413
|
-
const {
|
|
414
|
-
const stakingApi = new MaticStakingApi(undefined, undefined, server)
|
|
500
|
+
export async function getPolygonStakingInfo({ address, asset: { currency, baseAsset: feeAsset } }) {
|
|
501
|
+
const stakingApi = createStakingApiForFeeAsset({ feeAsset })
|
|
415
502
|
const delegator = address.toLowerCase()
|
|
416
503
|
const [
|
|
417
504
|
delegatedBalance,
|
|
@@ -1,73 +1,39 @@
|
|
|
1
1
|
import { isNumberUnit } from '@exodus/currency'
|
|
2
2
|
|
|
3
|
+
export const DISABLE_BALANCE_CHECKS = '0x0'
|
|
4
|
+
|
|
3
5
|
export function amountToCurrency({ asset, amount }) {
|
|
4
6
|
return isNumberUnit(amount) ? amount : asset.currency.parse(amount)
|
|
5
7
|
}
|
|
6
8
|
|
|
7
|
-
// HACK: Empirically, we can observe that a `feeData` object uses
|
|
8
|
-
// stringified values for gas i.e. `baseFeePerGas: "10 gwei"`,
|
|
9
|
-
// however to hook into `asset.api.getFees()`, these values
|
|
10
|
-
// must be expressed using currency objects.
|
|
11
|
-
function maybeNormalizeFeeData({ asset, feeData }) {
|
|
12
|
-
const { baseFeePerGas, tipGasPrice, serverGasPrice, gasPrice, ...extras } = feeData
|
|
13
|
-
|
|
14
|
-
const maybeNormalizeAmount = (amount) =>
|
|
15
|
-
typeof amount === 'string' ? amountToCurrency({ amount, asset }) : amount
|
|
16
|
-
|
|
17
|
-
return {
|
|
18
|
-
...extras,
|
|
19
|
-
gasPrice: maybeNormalizeAmount(gasPrice),
|
|
20
|
-
baseFeePerGas: maybeNormalizeAmount(baseFeePerGas),
|
|
21
|
-
tipGasPrice: maybeNormalizeAmount(tipGasPrice),
|
|
22
|
-
serverGasPrice: maybeNormalizeAmount(serverGasPrice),
|
|
23
|
-
}
|
|
24
|
-
}
|
|
25
|
-
|
|
26
9
|
/**
|
|
27
|
-
*
|
|
28
|
-
*
|
|
29
|
-
*
|
|
30
|
-
*
|
|
10
|
+
* Previously, EVM staking services would determine `feeData`
|
|
11
|
+
* dynamically on behalf of callers, however, this risks invalidating
|
|
12
|
+
* fee estimation over the course of a sequence of multiple staking
|
|
13
|
+
* transactions.
|
|
14
|
+
*
|
|
15
|
+
* For example, when determining the amount for a `sendAll`
|
|
16
|
+
* transaction, if the gas price were to increase between estimation
|
|
17
|
+
* and execution, this would invalidate the previous estimation.
|
|
18
|
+
*
|
|
19
|
+
* In future usage, we would want assets code to fetch FeeData from ACI,
|
|
20
|
+
* construct a transaction including determining the fee and then report
|
|
21
|
+
* back to the client what the fee is going to be.
|
|
22
|
+
*
|
|
23
|
+
* Importantly, the provided `feeData` must be sufficiently
|
|
24
|
+
* up-to-date; else, we risk the transactions not being included at all.
|
|
31
25
|
*/
|
|
32
|
-
export async
|
|
33
|
-
|
|
26
|
+
export const resolveFeeData = async ({
|
|
27
|
+
// TODO: maybe asset name would be better
|
|
34
28
|
asset,
|
|
35
29
|
assetClientInterface,
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
const defaultFeeData = await assetClientInterface.getFeeData({
|
|
40
|
-
assetName: asset.name,
|
|
41
|
-
})
|
|
42
|
-
|
|
43
|
-
// HACK: We wish to explicitly enable `useBaseGasPrice`
|
|
44
|
-
// since we're using a custom `tipGasPrice`. This
|
|
45
|
-
// will be compatible during the transition from
|
|
46
|
-
// Magnifier to Clarity, and can be safely removed
|
|
47
|
-
// once `useBaseGasPrice` is the default behaviour
|
|
48
|
-
// (alongside the custom tip).
|
|
49
|
-
const { eip1559Enabled } = defaultFeeData
|
|
50
|
-
const useBaseGasPrice = Boolean(eip1559Enabled) && Boolean(maybeMaxPriorityFeePerGas)
|
|
30
|
+
feeData,
|
|
31
|
+
}) => {
|
|
32
|
+
if (feeData) return feeData
|
|
51
33
|
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
...defaultFeeData,
|
|
56
|
-
// HACK: The backend currently exports a very large `tipGasPrice` that is
|
|
57
|
-
// compatible with Magnifier's legacy `gasPrice`, but it would
|
|
58
|
-
// be incompatible with EIP-1559, since this would evaluate into
|
|
59
|
-
// a very large `maxPriorityFeePerGas`.
|
|
60
|
-
...(useBaseGasPrice ? { tipGasPrice: maybeMaxPriorityFeePerGas } : null),
|
|
61
|
-
useBaseGasPrice,
|
|
62
|
-
},
|
|
63
|
-
})
|
|
34
|
+
console.warn(
|
|
35
|
+
'The evm staking service was not explicitly passed `feeData`. This can result in transaction nondeterminism.'
|
|
36
|
+
)
|
|
64
37
|
|
|
65
|
-
|
|
66
|
-
// if the config defines we should `useBaseGasPrice`.
|
|
67
|
-
return asset.api.getFee({
|
|
68
|
-
asset,
|
|
69
|
-
feeData: normalizedFeeData,
|
|
70
|
-
gasLimit,
|
|
71
|
-
amount,
|
|
72
|
-
})
|
|
38
|
+
return assetClientInterface.getFeeData({ assetName: asset.name })
|
|
73
39
|
}
|
|
@@ -156,13 +156,36 @@ export class ClarityMonitor extends BaseMonitor {
|
|
|
156
156
|
currentTokenBalances: derivedData.currentAccountState?.tokenBalances,
|
|
157
157
|
ourWalletAddress: derivedData.ourWalletAddress,
|
|
158
158
|
})
|
|
159
|
-
|
|
159
|
+
|
|
160
|
+
const batch = this.aci.createOperationsBatch()
|
|
161
|
+
|
|
162
|
+
this.aci.removeTxLogBatch({
|
|
163
|
+
assetName,
|
|
164
|
+
walletAccount,
|
|
165
|
+
txs: txsToRemove,
|
|
166
|
+
batch,
|
|
167
|
+
})
|
|
168
|
+
|
|
169
|
+
for (const [assetName, txs] of Object.entries(logItemsByAsset)) {
|
|
170
|
+
this.aci.updateTxLogAndNotifyBatch({
|
|
171
|
+
assetName,
|
|
172
|
+
walletAccount,
|
|
173
|
+
txs,
|
|
174
|
+
refresh,
|
|
175
|
+
batch,
|
|
176
|
+
})
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
this.aci.updateAccountStateBatch({
|
|
180
|
+
assetName,
|
|
160
181
|
walletAccount,
|
|
182
|
+
accountState,
|
|
161
183
|
newData: { clarityCursor: response.cursor, ...accountState },
|
|
184
|
+
batch,
|
|
162
185
|
})
|
|
163
186
|
|
|
164
|
-
await this.
|
|
165
|
-
|
|
187
|
+
await this.aci.executeOperationsBatch(batch)
|
|
188
|
+
|
|
166
189
|
if (refresh || hasNewTxs) {
|
|
167
190
|
const unknownTokenAddresses = this.getUnknownTokenAddresses({
|
|
168
191
|
transactions: allTxs,
|
|
@@ -1,4 +1,6 @@
|
|
|
1
|
+
import { ensureSaneEip1559GasPriceForTipGasPrice } from '../fee-utils.js'
|
|
1
2
|
import { fetchGasLimit } from '../gas-estimation.js'
|
|
3
|
+
import { getFeeFactoryGasPrices } from '../get-fee.js'
|
|
2
4
|
|
|
3
5
|
const getFeeInfo = async function getFeeInfo({
|
|
4
6
|
assetClientInterface,
|
|
@@ -8,25 +10,48 @@ const getFeeInfo = async function getFeeInfo({
|
|
|
8
10
|
amount,
|
|
9
11
|
txInput,
|
|
10
12
|
feeOpts = {},
|
|
13
|
+
feeData,
|
|
14
|
+
customFee,
|
|
11
15
|
}) {
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
16
|
+
// HACK: Previously, calls `getFeeInfo` were not provided a reference
|
|
17
|
+
// to `feeData`. For backwards compatibility, we'll revert to
|
|
18
|
+
// legacy behaviour.
|
|
19
|
+
// NOTE: This shouldn't actually be used outside of the `assets` repo;
|
|
20
|
+
// this is done just for safety.
|
|
21
|
+
if (!feeData) {
|
|
22
|
+
console.warn('`getFeeInfo` was not explicitly passed a `feeData` object.')
|
|
23
|
+
const { name: assetName } = asset
|
|
24
|
+
feeData = await assetClientInterface.getFeeData({ assetName })
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
const {
|
|
28
|
+
gasPrice: gasPrice_,
|
|
29
|
+
feeData: { tipGasPrice: tipGasPrice_, eip1559Enabled },
|
|
30
|
+
} = getFeeFactoryGasPrices({ customFee, feeData })
|
|
31
|
+
|
|
32
|
+
const tipGasPrice = feeOpts.tipGasPrice || tipGasPrice_
|
|
15
33
|
|
|
16
|
-
|
|
34
|
+
const maybeGasPrice = feeOpts.gasPrice || gasPrice_
|
|
17
35
|
|
|
18
|
-
|
|
19
|
-
|
|
36
|
+
// HACK: If we've received an invalid combination of `tipGasPrice`
|
|
37
|
+
// (maxPriorityFeePerGas) and `gasPrice` (maxFeePerGas), then
|
|
38
|
+
// we must normalize these before returning.
|
|
39
|
+
const gasPrice = eip1559Enabled
|
|
40
|
+
? ensureSaneEip1559GasPriceForTipGasPrice({ gasPrice: maybeGasPrice, tipGasPrice })
|
|
41
|
+
: maybeGasPrice
|
|
42
|
+
|
|
43
|
+
const gasLimit =
|
|
44
|
+
feeOpts.gasLimit ||
|
|
45
|
+
(await fetchGasLimit({
|
|
20
46
|
asset,
|
|
21
47
|
fromAddress,
|
|
22
48
|
toAddress,
|
|
23
49
|
amount,
|
|
24
50
|
txInput,
|
|
25
51
|
throwOnError: false,
|
|
26
|
-
})
|
|
27
|
-
}
|
|
52
|
+
}))
|
|
28
53
|
|
|
29
|
-
return { gasPrice, gasLimit, tipGasPrice }
|
|
54
|
+
return { gasPrice, gasLimit, tipGasPrice, eip1559Enabled }
|
|
30
55
|
}
|
|
31
56
|
|
|
32
57
|
export default getFeeInfo
|
|
@@ -7,10 +7,11 @@ export const resolveNonce = async ({
|
|
|
7
7
|
providedNonce,
|
|
8
8
|
txLog,
|
|
9
9
|
triedNonce,
|
|
10
|
+
tag = 'latest', // use 'pending' for unconfirmed txs
|
|
10
11
|
}) => {
|
|
11
12
|
const nonceFromNode =
|
|
12
13
|
asset.baseAsset?.api?.features?.noHistory || forceFromNode
|
|
13
|
-
? await getNonce({ asset: asset.baseAsset, address: fromAddress, tag
|
|
14
|
+
? await getNonce({ asset: asset.baseAsset, address: fromAddress, tag })
|
|
14
15
|
: 0
|
|
15
16
|
|
|
16
17
|
const nonceFromLog = [...txLog]
|