@exodus/ethereum-api 8.23.2 → 8.24.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 CHANGED
@@ -3,6 +3,26 @@
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.24.1](https://github.com/ExodusMovement/assets/compare/@exodus/ethereum-api@8.24.0...@exodus/ethereum-api@8.24.1) (2024-12-19)
7
+
8
+
9
+ ### License
10
+
11
+
12
+ * license: re-license under MIT license (#4694)
13
+
14
+
15
+
16
+ ## [8.24.0](https://github.com/ExodusMovement/assets/compare/@exodus/ethereum-api@8.23.2...@exodus/ethereum-api@8.24.0) (2024-12-17)
17
+
18
+
19
+ ### Features
20
+
21
+
22
+ * feat: evm staking send simplification (#4681)
23
+
24
+
25
+
6
26
  ## [8.23.2](https://github.com/ExodusMovement/assets/compare/@exodus/ethereum-api@8.23.1...@exodus/ethereum-api@8.23.2) (2024-12-17)
7
27
 
8
28
 
package/LICENSE ADDED
@@ -0,0 +1,7 @@
1
+ Copyright (c) 2024 Exodus Movement, Inc.
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the “Software”), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
4
+
5
+ The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
6
+
7
+ THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
package/README.md CHANGED
@@ -1,3 +1,121 @@
1
1
  # @exodus/ethereum-api
2
2
 
3
- Transaction monitors, fee monitors, RPC with the blockchain node, and other networking code for Ethereum and EVM-based blockchains. See [Asset Packages](../../docs/asset-packages.md) for more detail on this package's role.
3
+ Transaction monitors, fee monitors, RPC with the blockchain node, and other networking code for Ethereum and EVM-based blockchains. See [Asset Packages](../../docs/asset-packages.md) for more detail on this package`s role.
4
+
5
+ ## Fees
6
+
7
+ This section explains how fees currently work in Exodus and provides guidance on troubleshooting and tuning them via remote configuration. It reverse-engineers the existing unified implementation. The document aims to help readers understand the fee mechanism and suggest ways to improve it further.
8
+
9
+ ### Gas Price
10
+
11
+ Excluding custom fees, the result `gasPrice * gasPriceMultiplier` is used to resolve the transaction's gas price (legacy) and max gas price (EIP-1559).
12
+
13
+ The `gasPrice` value is, in preference order, loaded from:
14
+
15
+ 1. Remote config
16
+ 2. The node via the fee data monitor
17
+ 3. The default configuration.
18
+
19
+ Using remote config is never recommended, as manually updating this value won't keep up with network activity.
20
+
21
+ Fee and remote monitors are executed as soon as the wallet starts. The default fee data fields defined in code would be overridden (if they are set) before the user can interact with the wallet.
22
+
23
+ To tune the fee resolution, we can:
24
+
25
+ - Increase or decrease `gasPriceMultiplier` via remote config. For example, `1.2` would bump the current network gas price by 20%.
26
+ - Set a `min` value via remote config. Gas price will never be lower than this value.
27
+ - Set a `max` value via remote config. Gas price will never be higher than this value.
28
+
29
+ If the `gasPrice` returned by the node is not accurate or lower than the network's, the transaction may get stuck in pending status or may not be broadcast to the blockchain.
30
+
31
+ This is an undesirable situation:
32
+
33
+ - We could increase the nonce for the second transaction, but if the first one drops, the second one will also fail, even if the fees of the second one are high enough.
34
+ - If the wallet assumes the transaction was dropped and the nonce is reused, the network will interpret this unrelated second transaction as attempting to replace the first one (transaction replacement underprice error).
35
+ - The user submitting a second transaction to "retry" could be dangerous. Both transactions might get mined, causing the user to execute the swap twice instead of once.
36
+
37
+ One escape valve could be to update the `min`, `max`, or `gasPriceMultiplier` via remote config. This should be a temporary patch that needs to be rolled back once the node's gas price stabilizes.
38
+
39
+ ### Base Gas Price
40
+
41
+ The base gas price (or base fee) is the minimum amount of gas required to be paid per unit of gas to include a transaction in a block.
42
+
43
+ It can be found in `feeData.baseFeePerGas`. The fee monitor keeps it up to date by monitoring the latest block's information. It could be tuned by remote config, but this is not recommended.
44
+
45
+ The `feeData.baseFeePerGas` is only used when showing an "estimated fee" in the UI and not when sending a transaction.
46
+
47
+ One possible improvement is to define the max gas price based on the base gas price rather than the network's current gas price.
48
+
49
+ ### Gas Limit
50
+
51
+ The `gasLimit` refers to the maximum amount of gas a transaction or block is allowed to consume during its execution.
52
+ Most EVMs assume 21,000 when transferring the main asset. Some EVMs, like Mantle, Arbitrum One, and Arbitrum Nova, use a different gas limit. These gas limits need to be estimated, similar to when performing smart contract calls.
53
+
54
+ For smart contract calls, like sending or swapping, the wallet estimates a gas limit based on the contract, the sender, the receiver, the amounts, etc. The gas limit is then bumped by a 20% `extraPercentage` as a safety net.
55
+
56
+ If the gas limit estimation fails, the code may fall back to a high default value defined per asset (`asset.gasLimit`).
57
+
58
+ Currently, there is no way to tune the gas limit resolution via remote config, and "extra percentage" values are hardcoded in the code.
59
+
60
+ Both the bumped `gasLimit` and the bumped `gasPrice` resolved when showing the fees are reused when sending.
61
+
62
+ The higher the gas limit and the gas price, the higher the max network fee, even though not all that gas may be used. The higher the max network fee, the lower the user's available balance.
63
+
64
+ ### Additional L2 Fees
65
+
66
+ L2 networks may have additional fees. Arbitrum One, Arbitrum Nova, and Mantle include the fees in the gas estimation (`forceGasLimitEstimation`). Base and Aptos need an additional fee estimation to get this extra fee (`estimateL1DataFee`).
67
+
68
+ The L2 extra fee needs to be considered when showing the fee to the user, resolving the available balance, and creating the transaction. The fee may be implicit in the transaction.
69
+
70
+ ### Custom Fees
71
+
72
+ Custom fees allow users to set a custom `gasPrice`. The wallet allows a range, as follows:
73
+
74
+ - `recommended: gasPrice * gasPriceMultiplier`
75
+ - `min: gasPrice * gasPriceMultiplier * gasPriceMinimumRate`
76
+ - `max: gasPrice * gasPriceMultiplier * gasPriceMaximumRate`
77
+
78
+ If the user picks the recommended value, the custom fee is not set, leaving the default fee behavior.
79
+ Custom fees can be disabled by setting `rbfEnabled: false` via remote config. Currently, only Ethereum allows custom fees. Other EVMs could be included in the future.
80
+
81
+ The wallet allows acceleration when custom fees are allowed (and vice versa).
82
+
83
+ If the user picks a different custom fee in that range, the fee is calculated as `customFee * gasLimit`. `gasPriceMultiplier` is not applied to the custom gas price.
84
+
85
+ A transaction created with a low custom fee may be stuck for a while, blocking future transactions, and it may create issues with the nonce, as explained above.
86
+
87
+ Min and max values can be tuned by changing `gasPriceMinimumRate` and `gasPriceMaximumRate` via remote config.
88
+
89
+ ### TX formulas
90
+
91
+ Table shows how the txs are create for both legacy and 1559 logic for major use case:
92
+
93
+ | | Send some main | Send all main | Send token/contract call | Send main with custom fee | Send token/contract with custom fee | Accelerate |
94
+ | ------------------ | ----------------------------- | ----------------------------- | ----------------------------- | ------------------------- | ----------------------------------- | ------------------------------------------------------------ |
95
+ | Legacy Gas Price | gasPrice \* gasPriceMultipler | gasPrice \* gasPriceMultipler | gasPrice \* gasPriceMultipler | customFee | customFee | max(current gasPrice,original tx gasPrice) \* BUMP_RATE 1.2 |
96
+ | Legacy Gas Limit | 21000 | 21000 | estimated + 20% extra | 21000 | estimated + 20% extra | original tx gasLimit |
97
+ | - | - | - | - | - | - | - |
98
+ | 1559 Max gas Price | gasPrice \* gasPriceMultipler | gasPrice \* gasPriceMultipler | gasPrice \* gasPriceMultipler | customFee | customFee | max(current gasPrice, original tx gasPrice) \* BUMP_RATE 1.2 |
99
+ | 1559 Tip gas Price | tipGasPrice | gasPrice \* gasPriceMultipler | tipGasPrice | customFee | tipGasPrice | orginal tx tipGasPrice \* BUMP_RATE 1.2 |
100
+ | 1559 Gas Limit | 21000 | 21000 | estimated + 20% extra | 21000 | estimated + 20% extra | original tx gasLimit |
101
+
102
+ ### Fee Data Example
103
+
104
+ This is the default fee data configuration for Ethereum. Other EVMs have different default fee data.
105
+
106
+ All these values can be tuned via remote config, although it may not be recommended as they are also tuned by the fee monitors:
107
+
108
+ - **`baseFeePerGas`:** `50 Gwei` – Reference value for the base gas price. It is quickly updated by the fee monitor.
109
+ - **`gasPrice`:** `75 Gwei` – Reference value for the network gas price. It is quickly updated by the fee monitor.
110
+ - **`tipGasPrice`:** `2 Gwei` – Controls the `maxPriorityFeePerGas` for all transactions when EIP-1559 is enabled.
111
+ - **`eip1559Enabled`:** `true` – Enables or disables EIP-1559. A value of `false` means legacy fees and transactions.
112
+ - **`rbfEnabled`:** `true` – Enables custom fees and acceleration. Currently, this is only available for Ethereum but can be expanded to other EVMs.
113
+ - **`gasPriceMaximumRate`:** `1.3` – Controls the maximum value in the custom fee slider.
114
+ - **`gasPriceMinimumRate`:** `0.5` – Controls the minimum value in the custom fee slider.
115
+ - **`gasPriceMultiplier`:** `1` – Controls the `gasPrice` and `maxFeePerGas` of transactions, resolved as `current gas price * this multiplier`.
116
+ - **`max`:** `250 Gwei` – Controls the maximum gas price value that can be set.
117
+ - **`min`:** `1 Gwei` – Controls the minimum gas price value that can be set.
118
+ - **`fuelThreshold`:** `0.025 ETH` – If the balance falls below this value and the user holds tokens, a low balance warning is displayed.
119
+ - **`enableFeeDelegation`:** `false` – Not implemented.
120
+ - **`origin`:** `75 Gwei` – Legacy; consider removing.
121
+ - **`swapFee`:** `0.05 ETH` – Legacy; consider removing.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@exodus/ethereum-api",
3
- "version": "8.23.2",
3
+ "version": "8.24.1",
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",
@@ -10,10 +10,10 @@
10
10
  "!src/**/__tests__"
11
11
  ],
12
12
  "author": "Exodus Movement, Inc.",
13
- "license": "UNLICENSED",
13
+ "license": "MIT",
14
14
  "homepage": "https://github.com/ExodusMovement/assets/tree/master/ethereum/ethereum-api",
15
15
  "publishConfig": {
16
- "access": "restricted"
16
+ "access": "public"
17
17
  },
18
18
  "scripts": {
19
19
  "test": "run -T exodus-test --jest",
@@ -28,7 +28,7 @@
28
28
  "@exodus/bip44-constants": "^195.0.0",
29
29
  "@exodus/crypto": "^1.0.0-rc.13",
30
30
  "@exodus/currency": "^6.0.1",
31
- "@exodus/ethereum-lib": "^5.8.1",
31
+ "@exodus/ethereum-lib": "^5.8.2",
32
32
  "@exodus/ethereum-meta": "^2.1.5",
33
33
  "@exodus/ethereumholesky-meta": "^2.0.0",
34
34
  "@exodus/ethereumjs": "^1.0.0",
@@ -38,6 +38,7 @@
38
38
  "@exodus/solidity-contract": "^1.1.3",
39
39
  "@exodus/web3-ethereum-utils": "^3.27.1",
40
40
  "bn.js": "^5.2.1",
41
+ "delay": "^4.0.1",
41
42
  "events": "^1.1.1",
42
43
  "idna-uts46-hx": "^2.3.1",
43
44
  "lodash": "^4.17.15",
@@ -54,8 +55,7 @@
54
55
  "@exodus/ethereumgoerli-meta": "^2.0.0",
55
56
  "@exodus/ethereumsepolia-meta": "^2.0.0",
56
57
  "@exodus/fantommainnet-meta": "^2.0.0",
57
- "@exodus/rootstock-meta": "^2.0.0",
58
- "delay": "4.0.1"
58
+ "@exodus/rootstock-meta": "^2.0.0"
59
59
  },
60
60
  "bugs": {
61
61
  "url": "https://github.com/ExodusMovement/assets/issues?q=is%3Aissue+is%3Aopen+label%3Aethereum-api"
@@ -64,5 +64,5 @@
64
64
  "type": "git",
65
65
  "url": "git+https://github.com/ExodusMovement/assets.git"
66
66
  },
67
- "gitHead": "2036bf64aa16ee26a864a4aa971b050fc4631104"
67
+ "gitHead": "9f590f5439d73cba02054d608fb3d5c62fbdcf34"
68
68
  }
@@ -3,6 +3,7 @@ import { isNumberUnit } from '@exodus/currency'
3
3
  import { getServer } from '../../exodus-eth-server/index.js'
4
4
  import { estimateGasLimit } from '../../gas-estimation.js'
5
5
  import { fromHexToBigInt } from '../../number-utils.js'
6
+ import { createWatchTx as defaultCreateWatch } from '../../watch-tx.js'
6
7
  import { stakingProviderClientFactory } from '../staking-provider-client.js'
7
8
  import { EthereumStaking } from './api.js'
8
9
 
@@ -11,7 +12,7 @@ const extraGasLimit = 20_000 // extra gas Limit to prevent tx failing if somethi
11
12
  export function createEthereumStakingService({
12
13
  asset,
13
14
  assetClientInterface,
14
- createAndBroadcastTX,
15
+ createWatchTx = defaultCreateWatch,
15
16
  }) {
16
17
  const staking = new EthereumStaking(asset)
17
18
  const stakingProvider = stakingProviderClientFactory()
@@ -33,7 +34,7 @@ export function createEthereumStakingService({
33
34
  amount,
34
35
  })
35
36
 
36
- const { gasPrice, gasLimit, fee } = await estimateTxFee(delegatorAddress, to, amount, data)
37
+ const { gasPrice, gasLimit } = await estimateTxFee(delegatorAddress, to, amount, data)
37
38
 
38
39
  console.log(
39
40
  `delegator address ${delegatorAddress} staking ${amount.toDefaultString({
@@ -48,7 +49,6 @@ export function createEthereumStakingService({
48
49
  txData: data,
49
50
  gasPrice,
50
51
  gasLimit,
51
- fee,
52
52
  })
53
53
 
54
54
  // Goerli is not supported
@@ -226,7 +226,7 @@ export function createEthereumStakingService({
226
226
  })
227
227
  if (withdrawRequest) {
228
228
  const { to, data } = withdrawRequest
229
- const { gasPrice, gasLimit, fee } = await estimateTxFee(delegatorAddress, to, null, data)
229
+ const { gasPrice, gasLimit } = await estimateTxFee(delegatorAddress, to, null, data)
230
230
  return prepareAndSendTx({
231
231
  asset,
232
232
  walletAccount,
@@ -234,7 +234,6 @@ export function createEthereumStakingService({
234
234
  txData: data,
235
235
  gasPrice,
236
236
  gasLimit,
237
- fee,
238
237
  })
239
238
  }
240
239
  }
@@ -314,7 +313,6 @@ export function createEthereumStakingService({
314
313
  txData: txInput,
315
314
  gasPrice,
316
315
  gasLimit,
317
- fee,
318
316
  waitForConfirmation = false,
319
317
  } = Object.create(null)
320
318
  ) {
@@ -323,28 +321,27 @@ export function createEthereumStakingService({
323
321
  walletAccount,
324
322
  address: to,
325
323
  amount: amount || asset.currency.ZERO,
326
- // unified tx-send param
327
324
  shouldLog: true,
328
- txInput,
329
- feeAmount: fee,
330
- feeOpts: {
331
- gasPrice,
332
- gasLimit,
333
- },
334
- // mobile: tx-send parms
335
325
  options: {
336
326
  shouldLog: true,
337
327
  txInput,
338
328
  gasPrice,
339
329
  gasLimit,
340
- feeAmount: fee,
341
330
  },
342
- waitForConfirmation,
343
331
  }
344
332
 
345
- console.log('sending staking tx:', sendTxArgs)
333
+ const { txId } = await asset.baseAsset.api.sendTx(sendTxArgs)
346
334
 
347
- const { txId } = await createAndBroadcastTX(sendTxArgs)
335
+ const baseAsset = asset.baseAsset
336
+ if (waitForConfirmation) {
337
+ const getTxLog = (...args) => assetClientInterface.getTxLog(...args)
338
+ const watchTx = createWatchTx({
339
+ walletAccount,
340
+ getTxLog,
341
+ baseAsset,
342
+ })
343
+ await watchTx(txId)
344
+ }
348
345
 
349
346
  return txId
350
347
  }
@@ -3,10 +3,14 @@ import BN from 'bn.js'
3
3
 
4
4
  import { getServer } from '../../exodus-eth-server/index.js'
5
5
  import { estimateGasLimit } from '../../gas-estimation.js'
6
+ import { createWatchTx as defaultCreateWatch } from '../../watch-tx.js'
6
7
  import { stakingProviderClientFactory } from '../staking-provider-client.js'
7
8
  import { MaticStakingApi } from './api.js'
8
9
 
9
- export function createPolygonStakingService({ assetClientInterface, createAndBroadcastTX }) {
10
+ export function createPolygonStakingService({
11
+ assetClientInterface,
12
+ createWatchTx = defaultCreateWatch,
13
+ }) {
10
14
  const stakingApi = new MaticStakingApi()
11
15
  const assetName = 'ethereum'
12
16
  const stakingProvider = stakingProviderClientFactory()
@@ -33,7 +37,7 @@ export function createPolygonStakingService({ assetClientInterface, createAndBro
33
37
  amount = amountToCurrency({ asset, amount })
34
38
 
35
39
  const txApproveData = await stakingApi.approveStakeManager(amount)
36
- let { gasPrice, gasLimit, fee } = await estimateTxFee(
40
+ let { gasPrice, gasLimit } = await estimateTxFee(
37
41
  delegatorAddress,
38
42
  stakingApi.polygonContract.address,
39
43
  txApproveData
@@ -45,11 +49,10 @@ export function createPolygonStakingService({ assetClientInterface, createAndBro
45
49
  txData: txApproveData,
46
50
  gasPrice,
47
51
  gasLimit,
48
- fee,
49
52
  })
50
53
 
51
54
  const txDelegateData = await stakingApi.delegate({ amount })
52
- ;({ gasPrice, gasLimit, fee } = await estimateTxFee(
55
+ ;({ gasPrice, gasLimit } = await estimateTxFee(
53
56
  delegatorAddress,
54
57
  stakingApi.validatorShareContract.address,
55
58
  txDelegateData
@@ -61,7 +64,6 @@ export function createPolygonStakingService({ assetClientInterface, createAndBro
61
64
  txData: txDelegateData,
62
65
  gasPrice,
63
66
  gasLimit,
64
- fee,
65
67
  })
66
68
 
67
69
  await stakingProvider.notifyStaking({
@@ -85,7 +87,7 @@ export function createPolygonStakingService({ assetClientInterface, createAndBro
85
87
  amount = amountToCurrency({ asset, amount })
86
88
 
87
89
  const txUndelegateData = await stakingApi.undelegate({ amount })
88
- const { gasPrice, gasLimit, fee } = await estimateTxFee(
90
+ const { gasPrice, gasLimit } = await estimateTxFee(
89
91
  delegatorAddress.toLowerCase(),
90
92
  stakingApi.validatorShareContract.address,
91
93
  txUndelegateData
@@ -96,7 +98,6 @@ export function createPolygonStakingService({ assetClientInterface, createAndBro
96
98
  txData: txUndelegateData,
97
99
  gasPrice,
98
100
  gasLimit,
99
- fee,
100
101
  })
101
102
  }
102
103
 
@@ -108,7 +109,7 @@ export function createPolygonStakingService({ assetClientInterface, createAndBro
108
109
  const delegatorAddress = address.toLowerCase()
109
110
 
110
111
  const txWithdrawRewardsData = await stakingApi.withdrawRewards()
111
- const { gasPrice, gasLimit, fee } = await estimateTxFee(
112
+ const { gasPrice, gasLimit } = await estimateTxFee(
112
113
  delegatorAddress,
113
114
  stakingApi.validatorShareContract.address,
114
115
  txWithdrawRewardsData
@@ -119,7 +120,6 @@ export function createPolygonStakingService({ assetClientInterface, createAndBro
119
120
  txData: txWithdrawRewardsData,
120
121
  gasPrice,
121
122
  gasLimit,
122
- fee,
123
123
  })
124
124
  }
125
125
 
@@ -267,37 +267,37 @@ export function createPolygonStakingService({ assetClientInterface, createAndBro
267
267
  txData: txInput,
268
268
  gasPrice,
269
269
  gasLimit,
270
- fee,
271
270
  waitForConfirmation = false,
272
271
  } = {}) {
273
- const { ethereum } = await assetClientInterface.getAssetsForNetwork({
272
+ const { ethereum: asset } = await assetClientInterface.getAssetsForNetwork({
274
273
  baseAssetName: 'ethereum',
275
274
  })
276
275
  const sendTxArgs = {
277
- asset: ethereum,
276
+ asset,
278
277
  walletAccount,
279
278
  address: to,
280
- amount: ethereum.currency.ZERO,
281
- // unified tx-send param
282
- shouldLog: true,
283
- txInput,
284
- feeAmount: fee,
285
- feeOpts: {
286
- gasPrice,
287
- gasLimit,
288
- },
289
- // mobile: tx-send parms
279
+ amount: asset.currency.ZERO,
290
280
  options: {
291
281
  shouldLog: true,
292
282
  txInput,
293
283
  gasPrice,
294
284
  gasLimit,
295
- feeAmount: fee,
296
285
  },
297
286
  waitForConfirmation,
298
287
  }
299
288
 
300
- const { txId } = await createAndBroadcastTX(sendTxArgs)
289
+ const { txId } = await asset.baseAsset.api.sendTx(sendTxArgs)
290
+
291
+ const baseAsset = asset.baseAsset
292
+ if (waitForConfirmation) {
293
+ const getTxLog = (...args) => assetClientInterface.getTxLog(...args)
294
+ const watchTx = createWatchTx({
295
+ walletAccount,
296
+ getTxLog,
297
+ baseAsset,
298
+ })
299
+ await watchTx(txId)
300
+ }
301
301
 
302
302
  return txId
303
303
  }
@@ -0,0 +1,32 @@
1
+ import delay from 'delay'
2
+ import ms from 'ms'
3
+
4
+ const WATCH_TX_DELAY = ms('5s')
5
+ const WATCH_TX_TIMEOUT = ms('5m')
6
+
7
+ // this could go to asset-lib
8
+ export const createWatchTx =
9
+ ({
10
+ walletAccount,
11
+ getTxLog,
12
+ baseAsset,
13
+ watchTxDelay = WATCH_TX_DELAY,
14
+ watchTxTimeout = WATCH_TX_TIMEOUT,
15
+ }) =>
16
+ async (txId) => {
17
+ const start = Date.now()
18
+ let txConfirmed = false
19
+ let hasTimedOut = false
20
+
21
+ while (!hasTimedOut && !txConfirmed) {
22
+ await delay(watchTxDelay)
23
+ const baseAssetTxLog = await getTxLog({
24
+ assetName: baseAsset.name,
25
+ walletAccount,
26
+ })
27
+ txConfirmed = baseAssetTxLog.get(txId)?.confirmed
28
+ hasTimedOut = Date.now() - start > watchTxTimeout
29
+ }
30
+
31
+ return { txId, confirmed: txConfirmed, hasTimedOut }
32
+ }