@exodus/ethereum-api 8.35.0 → 8.36.0
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 +29 -1
- package/README.md +5 -5
- package/package.json +4 -4
- package/src/create-asset.js +8 -0
- package/src/exodus-eth-server/clarity-v2.js +10 -0
- package/src/fee-utils.js +2 -2
- package/src/gas-estimation.js +15 -12
- package/src/get-balances.js +56 -7
- package/src/get-fee-async.js +17 -5
- package/src/get-fee.js +2 -23
- package/src/tx-log/clarity-utils/get-log-items-from-server-tx.js +16 -5
- package/src/tx-log/monitor-utils/get-balance-updates.js +44 -0
- package/src/tx-send/nonce-utils.js +19 -3
- package/src/tx-send/tx-send.js +2 -1
package/CHANGELOG.md
CHANGED
|
@@ -3,6 +3,34 @@
|
|
|
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.36.0](https://github.com/ExodusMovement/assets/compare/@exodus/ethereum-api@8.35.1...@exodus/ethereum-api@8.36.0) (2025-06-11)
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
### Features
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
* feat: ethereum api use balances and nonces data (#5727)
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
### Bug Fixes
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
* fix: ethereum api cleaning up (#5832)
|
|
19
|
+
|
|
20
|
+
* fix: improve evm gas estimation when using arbitrary addresses (#5842)
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
## [8.35.1](https://github.com/ExodusMovement/assets/compare/@exodus/ethereum-api@8.35.0...@exodus/ethereum-api@8.35.1) (2025-06-06)
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
### Bug Fixes
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
* fix: hardcode gasLimit in client (#5815)
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
|
|
6
34
|
## [8.35.0](https://github.com/ExodusMovement/assets/compare/@exodus/ethereum-api@8.34.4...@exodus/ethereum-api@8.35.0) (2025-05-28)
|
|
7
35
|
|
|
8
36
|
|
|
@@ -399,7 +427,7 @@ See [Conventional Commits](https://conventionalcommits.org) for commit guideline
|
|
|
399
427
|
### Bug Fixes
|
|
400
428
|
|
|
401
429
|
|
|
402
|
-
* fix: use
|
|
430
|
+
* fix: use gasPriceMultiplier when setting up min/max/recommended (#4700)
|
|
403
431
|
|
|
404
432
|
|
|
405
433
|
|
package/README.md
CHANGED
|
@@ -13,7 +13,7 @@ Excluding custom fees, `feeData.gasPrice` is used to resolve the transaction's g
|
|
|
13
13
|
The `gasPrice` value is, in preference order, loaded from:
|
|
14
14
|
|
|
15
15
|
1. Remote config
|
|
16
|
-
2. The node via the fee data monitor and web socket. `gasPrice = baseFeePerGas *
|
|
16
|
+
2. The node via the fee data monitor and web socket. `gasPrice = baseFeePerGas * gasPriceMultiplier + tipGasPrice`
|
|
17
17
|
3. The default configuration.
|
|
18
18
|
|
|
19
19
|
Using remote config is never recommended, as manually updating this value won't keep up with network activity.
|
|
@@ -42,7 +42,7 @@ The base gas price (or base fee) is the minimum amount of gas required to be pai
|
|
|
42
42
|
|
|
43
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
44
|
|
|
45
|
-
The `feeData.baseFeePerGas` is used when showing an "estimated fee" in the UI and to calculate the max gas price. `gasPrice = baseFeePerGas *
|
|
45
|
+
The `feeData.baseFeePerGas` is used when showing an "estimated fee" in the UI and to calculate the max gas price. `gasPrice = baseFeePerGas * gasPriceMultiplier + tipGasPrice`
|
|
46
46
|
|
|
47
47
|
### Gas Limit
|
|
48
48
|
|
|
@@ -69,7 +69,7 @@ The L2 extra fee needs to be considered when showing the fee to the user, resolv
|
|
|
69
69
|
|
|
70
70
|
Custom fees allow users to set a custom `gasPrice`. The wallet allows a range, as follows:
|
|
71
71
|
|
|
72
|
-
Note: `gasPrice = baseFeePerGas *
|
|
72
|
+
Note: `gasPrice = baseFeePerGas * gasPriceMultiplier + tipGasPrice`
|
|
73
73
|
|
|
74
74
|
- `recommended: gasPrice`
|
|
75
75
|
- `min: gasPrice * gasPriceMinimumRate`
|
|
@@ -88,7 +88,7 @@ Min and max values can be tuned by changing `gasPriceMinimumRate` and `gasPriceM
|
|
|
88
88
|
|
|
89
89
|
### TX formulas
|
|
90
90
|
|
|
91
|
-
Note: `gasPrice = baseFeePerGas *
|
|
91
|
+
Note: `gasPrice = baseFeePerGas * gasPriceMultiplier + tipGasPrice`
|
|
92
92
|
|
|
93
93
|
Table shows how the txs are create for both legacy and 1559 logic for major use case:
|
|
94
94
|
|
|
@@ -108,7 +108,7 @@ This is the default fee data configuration for Ethereum. Other EVMs have differe
|
|
|
108
108
|
All these values can be tuned via remote config, although it may not be recommended as they are also tuned by the fee monitors:
|
|
109
109
|
|
|
110
110
|
- **`baseFeePerGas`:** `50 Gwei` – Reference value for the base gas price. It is quickly updated by the fee monitor.
|
|
111
|
-
- **`gasPrice`:** `77 Gwei` – `baseFeePerGas *
|
|
111
|
+
- **`gasPrice`:** `77 Gwei` – `baseFeePerGas * gasPriceMultiplier + tipGasPrice`
|
|
112
112
|
- **`severGasPrice`:** `75 Gwei` – Reference value for the network gas price. It is quickly updated by the fee monitor.
|
|
113
113
|
- **`tipGasPrice`:** `2 Gwei` – Controls the `maxPriorityFeePerGas` for all transactions when EIP-1559 is enabled.
|
|
114
114
|
- **`eip1559Enabled`:** `true` – Enables or disables EIP-1559. A value of `false` means legacy fees and transactions.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@exodus/ethereum-api",
|
|
3
|
-
"version": "8.
|
|
3
|
+
"version": "8.36.0",
|
|
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",
|
|
@@ -28,12 +28,12 @@
|
|
|
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.
|
|
31
|
+
"@exodus/ethereum-lib": "^5.12.0",
|
|
32
32
|
"@exodus/ethereum-meta": "^2.5.0",
|
|
33
33
|
"@exodus/ethereumholesky-meta": "^2.0.2",
|
|
34
34
|
"@exodus/ethereumjs": "^1.0.0",
|
|
35
35
|
"@exodus/fetch": "^1.3.0",
|
|
36
|
-
"@exodus/models": "^12.0
|
|
36
|
+
"@exodus/models": "^12.13.0",
|
|
37
37
|
"@exodus/simple-retry": "^0.0.6",
|
|
38
38
|
"@exodus/solidity-contract": "^1.1.3",
|
|
39
39
|
"@exodus/web3-ethereum-utils": "^4.2.1",
|
|
@@ -64,5 +64,5 @@
|
|
|
64
64
|
"type": "git",
|
|
65
65
|
"url": "git+https://github.com/ExodusMovement/assets.git"
|
|
66
66
|
},
|
|
67
|
-
"gitHead": "
|
|
67
|
+
"gitHead": "2172464a34a03cb2c2401f80db89d4609cb0528f"
|
|
68
68
|
}
|
package/src/create-asset.js
CHANGED
|
@@ -65,6 +65,7 @@ export const createAssetFactory = ({
|
|
|
65
65
|
useEip1191ChainIdChecksum = false,
|
|
66
66
|
forceGasLimitEstimation = false,
|
|
67
67
|
supportsCustomFees: defaultSupportsCustomFees = false,
|
|
68
|
+
useAbsoluteBalanceAndNonce = false,
|
|
68
69
|
}) => {
|
|
69
70
|
assert(assetsList, 'assetsList is required')
|
|
70
71
|
assert(providedFeeData || feeDataConfig, 'feeData or feeDataConfig is required')
|
|
@@ -106,6 +107,7 @@ export const createAssetFactory = ({
|
|
|
106
107
|
monitorType: overrideMonitorType,
|
|
107
108
|
serverUrl: overrideServerUrl,
|
|
108
109
|
monitorInterval: overrideMonitorInterval,
|
|
110
|
+
useAbsoluteBalanceAndNonce: overrideUseAbsoluteBalanceAndNonce,
|
|
109
111
|
} = {
|
|
110
112
|
...defaultConfig,
|
|
111
113
|
...config,
|
|
@@ -126,6 +128,10 @@ export const createAssetFactory = ({
|
|
|
126
128
|
}
|
|
127
129
|
}
|
|
128
130
|
|
|
131
|
+
if (overrideUseAbsoluteBalanceAndNonce !== undefined) {
|
|
132
|
+
useAbsoluteBalanceAndNonce = overrideUseAbsoluteBalanceAndNonce
|
|
133
|
+
}
|
|
134
|
+
|
|
129
135
|
monitorType = overrideMonitorType || monitorType
|
|
130
136
|
const server = createEvmServer({ assetName: asset.name, serverUrl, monitorType })
|
|
131
137
|
|
|
@@ -149,6 +155,7 @@ export const createAssetFactory = ({
|
|
|
149
155
|
const getBalances = getBalancesFactory({
|
|
150
156
|
monitorType,
|
|
151
157
|
config,
|
|
158
|
+
useAbsoluteBalance: useAbsoluteBalanceAndNonce,
|
|
152
159
|
})
|
|
153
160
|
|
|
154
161
|
const { createToken, getTokens } = createTokenFactory(
|
|
@@ -254,6 +261,7 @@ export const createAssetFactory = ({
|
|
|
254
261
|
const sendTx = txSendFactory({
|
|
255
262
|
assetClientInterface,
|
|
256
263
|
createUnsignedTx,
|
|
264
|
+
useAbsoluteBalanceAndNonce,
|
|
257
265
|
})
|
|
258
266
|
|
|
259
267
|
const estimateL1DataFee = l1GasOracleAddress
|
|
@@ -130,4 +130,14 @@ export default class ClarityServerV2 extends ClarityServer {
|
|
|
130
130
|
body: request,
|
|
131
131
|
})
|
|
132
132
|
}
|
|
133
|
+
|
|
134
|
+
async getTransactionCount(...params) {
|
|
135
|
+
// nonce is called during tx send, use it in rest api
|
|
136
|
+
const request = this.getTransactionCountRequest(...params)
|
|
137
|
+
return this.sendHttpRequest({
|
|
138
|
+
path: '/rpc',
|
|
139
|
+
method: 'POST',
|
|
140
|
+
body: request,
|
|
141
|
+
})
|
|
142
|
+
}
|
|
133
143
|
}
|
package/src/fee-utils.js
CHANGED
|
@@ -3,8 +3,8 @@ import assert from 'minimalistic-assert'
|
|
|
3
3
|
const applyMultiplierToPriceInWei = ({ feeAsset, feeData, priceInWei }) => {
|
|
4
4
|
assert(typeof priceInWei === 'string', 'gasPriceInWei should be a string')
|
|
5
5
|
|
|
6
|
-
const
|
|
7
|
-
return feeAsset.currency.parse(priceInWei).mul(
|
|
6
|
+
const gasPriceMultiplier = feeData.gasPriceMultiplier || 1
|
|
7
|
+
return feeAsset.currency.parse(priceInWei).mul(gasPriceMultiplier).toBaseString({ unit: true })
|
|
8
8
|
}
|
|
9
9
|
|
|
10
10
|
/**
|
package/src/gas-estimation.js
CHANGED
|
@@ -15,6 +15,15 @@ export const DEFAULT_CONTRACT_GAS_LIMIT = 1e6
|
|
|
15
15
|
export const scaleGasLimitEstimate = ({ estimatedGasLimit, extraPercentage = EXTRA_PERCENTAGE }) =>
|
|
16
16
|
Number((BigInt(estimatedGasLimit) * BigInt(100 + extraPercentage)) / BigInt(100))
|
|
17
17
|
|
|
18
|
+
const hardcodedGasLimits = new Map([
|
|
19
|
+
['amp', 151_000],
|
|
20
|
+
['tetherusd', 70_000],
|
|
21
|
+
['usdcoin', 70_000],
|
|
22
|
+
['snx', 220_000],
|
|
23
|
+
['geminidollar', 75_000],
|
|
24
|
+
['aave', 250_000],
|
|
25
|
+
])
|
|
26
|
+
|
|
18
27
|
// Starting with geth v1.9.14, if gasPrice is set for eth_estimateGas call, the call allowance will
|
|
19
28
|
// be calculated with account's balance divided by gasPrice. If user's balance is too low,
|
|
20
29
|
// the gasEstimation will fail. If gasPrice is set to '0x0', the account's balance is not
|
|
@@ -28,6 +37,11 @@ export async function estimateGasLimit(
|
|
|
28
37
|
gasPrice = '0x',
|
|
29
38
|
extraPercentage
|
|
30
39
|
) {
|
|
40
|
+
// move this to remote config
|
|
41
|
+
if (hardcodedGasLimits.has(asset.name)) {
|
|
42
|
+
return hardcodedGasLimits.get(asset.name)
|
|
43
|
+
}
|
|
44
|
+
|
|
31
45
|
const opts = {
|
|
32
46
|
from: fromAddress,
|
|
33
47
|
to: toAddress,
|
|
@@ -74,18 +88,7 @@ export async function fetchGasLimit({
|
|
|
74
88
|
if (isEthereumLikeToken(asset)) {
|
|
75
89
|
amount = asset.baseAsset.currency.ZERO
|
|
76
90
|
toAddress = asset.contract.address
|
|
77
|
-
} else if (
|
|
78
|
-
!isContract &&
|
|
79
|
-
!asset.forceGasLimitEstimation &&
|
|
80
|
-
![
|
|
81
|
-
// back compatiblity until all plugins are updated. Then remove this array
|
|
82
|
-
'ethereumarbone',
|
|
83
|
-
'ethereumarbonesepolia',
|
|
84
|
-
'ethereumarbnova',
|
|
85
|
-
'mantle',
|
|
86
|
-
'mantlesepolia',
|
|
87
|
-
].includes(asset.name)
|
|
88
|
-
) {
|
|
91
|
+
} else if (!isContract && !asset.forceGasLimitEstimation) {
|
|
89
92
|
return defaultGasLimit()
|
|
90
93
|
}
|
|
91
94
|
|
package/src/get-balances.js
CHANGED
|
@@ -7,6 +7,44 @@ import {
|
|
|
7
7
|
import { isRpcBalanceAsset } from '@exodus/ethereum-lib'
|
|
8
8
|
import assert from 'minimalistic-assert'
|
|
9
9
|
|
|
10
|
+
export const getAbsoluteBalance = ({ asset, txLog }) => {
|
|
11
|
+
assert(asset, 'asset is required')
|
|
12
|
+
assert(txLog, 'txLog is required')
|
|
13
|
+
|
|
14
|
+
if (txLog.size === 0) {
|
|
15
|
+
return asset.currency.ZERO
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
let balance = asset.currency.ZERO
|
|
19
|
+
let hasAbsoluteBalance = false
|
|
20
|
+
|
|
21
|
+
for (const tx of txLog.reverse()) {
|
|
22
|
+
if (tx.data.balanceChange) {
|
|
23
|
+
hasAbsoluteBalance = true
|
|
24
|
+
balance = balance.add(asset.currency.baseUnit(tx.data.balanceChange.to))
|
|
25
|
+
|
|
26
|
+
break
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
if (!(tx.dropped || tx.data.replacedBy)) {
|
|
30
|
+
if (!tx.error) {
|
|
31
|
+
balance = balance.add(tx.coinAmount)
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
if (tx.feeAmount?.unitType.equals(tx.coinAmount.unitType)) {
|
|
35
|
+
balance = balance.sub(tx.feeAmount)
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
if (!hasAbsoluteBalance) {
|
|
41
|
+
console.warn('No absolute balance found', { assetName: asset.name, txLogSize: txLog.size })
|
|
42
|
+
return
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
return balance
|
|
46
|
+
}
|
|
47
|
+
|
|
10
48
|
const getStaked = ({ accountState, asset }) => {
|
|
11
49
|
return accountState?.staking?.[asset.name]?.activeStakedBalance || asset.currency.ZERO
|
|
12
50
|
}
|
|
@@ -73,7 +111,11 @@ const getSpendable = ({ asset, balance, txLog, unconfirmedReceived }) => {
|
|
|
73
111
|
* @param accountState the account state when the balance is loaded from RPC
|
|
74
112
|
* @returns {{balance}|null} an object with the balance or null if the balance is unknown
|
|
75
113
|
*/
|
|
76
|
-
export const getBalancesFactory = ({
|
|
114
|
+
export const getBalancesFactory = ({
|
|
115
|
+
monitorType,
|
|
116
|
+
config = Object.create(null),
|
|
117
|
+
useAbsoluteBalance,
|
|
118
|
+
}) => {
|
|
77
119
|
const { useAccountStateBalanceOnly } = config
|
|
78
120
|
|
|
79
121
|
assert(monitorType, 'monitorType is required')
|
|
@@ -101,12 +143,19 @@ export const getBalancesFactory = ({ monitorType, config = Object.create(null) }
|
|
|
101
143
|
} else {
|
|
102
144
|
// Balance from txLog does not include staking rewards
|
|
103
145
|
// spendable and total are calculated differently based on staking txs
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
146
|
+
const absoluteBalance = useAbsoluteBalance && getAbsoluteBalance({ asset, txLog })
|
|
147
|
+
|
|
148
|
+
if (absoluteBalance) {
|
|
149
|
+
spendable = absoluteBalance
|
|
150
|
+
} else {
|
|
151
|
+
spendable = getSpendable({
|
|
152
|
+
asset,
|
|
153
|
+
balance: getBalanceFromTxLog({ txLog, asset }),
|
|
154
|
+
txLog,
|
|
155
|
+
unconfirmedReceived,
|
|
156
|
+
})
|
|
157
|
+
}
|
|
158
|
+
|
|
110
159
|
total = spendable.add(staked).add(staking).add(unstaking).add(unstaked)
|
|
111
160
|
}
|
|
112
161
|
|
package/src/get-fee-async.js
CHANGED
|
@@ -31,7 +31,22 @@ export async function resolveExtraPercentage({ asset, fromAddress, toAddress })
|
|
|
31
31
|
return EXTRA_PERCENTAGE
|
|
32
32
|
}
|
|
33
33
|
|
|
34
|
-
|
|
34
|
+
// HACK: If a recipient address is not defined, we usually fall back to
|
|
35
|
+
// default address so gas estimation can still complete successfully
|
|
36
|
+
// without knowledge of which accounts are involved.
|
|
37
|
+
//
|
|
38
|
+
// However, we must be careful to select addresses which are unlikely
|
|
39
|
+
// to have existing obligations, such as popular dead addresses or the
|
|
40
|
+
// reserved addresses of precompiles, since these can influence gas
|
|
41
|
+
// estimation.
|
|
42
|
+
//
|
|
43
|
+
// Here, we use an address which is mostly all `1`s to make sure we can
|
|
44
|
+
// exaggerate the worst-case calldata cost (which is priced per high bit)
|
|
45
|
+
// whilst being unlikely to have any token balances.
|
|
46
|
+
//
|
|
47
|
+
// Unfortunately, we can't use `0xffffffffffffffffffffffffffffffffffffffff`,
|
|
48
|
+
// since this address is a whale.
|
|
49
|
+
const ARBITRARY_ADDRESS = '0xfffFfFfFfFfFFFFFfeFfFFFffFffFFFFfFFFFFFF'.toLowerCase()
|
|
35
50
|
|
|
36
51
|
const getFeeAsyncFactory = ({
|
|
37
52
|
assetClientInterface,
|
|
@@ -51,8 +66,8 @@ const getFeeAsyncFactory = ({
|
|
|
51
66
|
txInput: txInputPram,
|
|
52
67
|
isExchange,
|
|
53
68
|
customFee,
|
|
54
|
-
calculateEffectiveFee,
|
|
55
69
|
gasLimit: providedGasLimit,
|
|
70
|
+
feeData,
|
|
56
71
|
}) => {
|
|
57
72
|
const fromAddress = providedFromAddress || ARBITRARY_ADDRESS // sending from a random address
|
|
58
73
|
const toAddress = providedToAddress || ARBITRARY_ADDRESS // sending to a random address,
|
|
@@ -86,15 +101,12 @@ const getFeeAsyncFactory = ({
|
|
|
86
101
|
|
|
87
102
|
const { txInput, gasLimit, contractAddress } = await resolveGasLimit()
|
|
88
103
|
|
|
89
|
-
const feeData = await assetClientInterface.getFeeConfig({ assetName: asset.baseAsset.name })
|
|
90
|
-
|
|
91
104
|
const { fee, gasPrice, ...rest } = getFee({
|
|
92
105
|
asset,
|
|
93
106
|
feeData,
|
|
94
107
|
gasLimit,
|
|
95
108
|
isExchange,
|
|
96
109
|
amount,
|
|
97
|
-
calculateEffectiveFee,
|
|
98
110
|
customFee,
|
|
99
111
|
})
|
|
100
112
|
|
package/src/get-fee.js
CHANGED
|
@@ -41,17 +41,9 @@ export const getFeeFactoryGasPrices = ({ customFee, feeData }) => {
|
|
|
41
41
|
|
|
42
42
|
export const getFeeFactory =
|
|
43
43
|
({ gasLimit: defaultGasLimit }) =>
|
|
44
|
-
({
|
|
45
|
-
asset,
|
|
46
|
-
feeData,
|
|
47
|
-
customFee,
|
|
48
|
-
gasLimit: providedGasLimit,
|
|
49
|
-
isExchange,
|
|
50
|
-
amount,
|
|
51
|
-
calculateEffectiveFee,
|
|
52
|
-
}) => {
|
|
44
|
+
({ asset, feeData, customFee, gasLimit: providedGasLimit, isExchange, amount }) => {
|
|
53
45
|
const {
|
|
54
|
-
feeData: {
|
|
46
|
+
feeData: { tipGasPrice, eip1559Enabled },
|
|
55
47
|
gasPrice,
|
|
56
48
|
} = getFeeFactoryGasPrices({
|
|
57
49
|
customFee,
|
|
@@ -65,19 +57,6 @@ export const getFeeFactory =
|
|
|
65
57
|
const maybeReturnTipGasPrice = eip1559Enabled ? { tipGasPrice } : null
|
|
66
58
|
|
|
67
59
|
const extraFeeData = getExtraFeeData({ asset, amount })
|
|
68
|
-
if (calculateEffectiveFee && eip1559Enabled) {
|
|
69
|
-
const maxFeePerGas = gasPrice
|
|
70
|
-
// effective_gas_price = min(base_fee_per_gas + tip_gas_price, max_fee_per_gas)
|
|
71
|
-
const feePerGas = baseFeePerGas.add(tipGasPrice)
|
|
72
|
-
const effectiveGasPrice = feePerGas.lt(maxFeePerGas) ? feePerGas : maxFeePerGas
|
|
73
|
-
|
|
74
|
-
return {
|
|
75
|
-
...maybeReturnTipGasPrice,
|
|
76
|
-
fee: effectiveGasPrice.mul(gasLimit),
|
|
77
|
-
gasPrice,
|
|
78
|
-
extraFeeData,
|
|
79
|
-
}
|
|
80
|
-
}
|
|
81
60
|
|
|
82
61
|
const fee = gasPrice.mul(gasLimit)
|
|
83
62
|
return { ...maybeReturnTipGasPrice, fee, gasPrice, extraFeeData }
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import lodash from 'lodash'
|
|
2
2
|
|
|
3
|
+
import { getWalletUpdates } from '../monitor-utils/get-balance-updates.js'
|
|
3
4
|
import getFeeAmount from '../monitor-utils/get-fee-amount.js'
|
|
4
5
|
import getTransfersByTokenName from '../monitor-utils/get-transfers-by-token-name.js'
|
|
5
6
|
import getValueOfTransfers from '../monitor-utils/get-value-of-transfers.js'
|
|
@@ -36,6 +37,8 @@ export default function getLogItemsFromServerTx({
|
|
|
36
37
|
methodId: serverTx.input.slice(0, Math.max(0, METHOD_ID_LENGTH)),
|
|
37
38
|
}
|
|
38
39
|
const data = serverTx.input || '0x'
|
|
40
|
+
const walletUpdates = getWalletUpdates(ourWalletAddress, serverTx.walletChanges || [])
|
|
41
|
+
const { baseBalanceUpdate, nonceUpdate, tokenBalancesUpdate } = walletUpdates
|
|
39
42
|
|
|
40
43
|
const logItemCommonProperties = {
|
|
41
44
|
confirmations,
|
|
@@ -51,7 +54,10 @@ export default function getLogItemsFromServerTx({
|
|
|
51
54
|
const sendingTransferPresent = ethereumTransfers.some(({ from }) => from === ourWalletAddress)
|
|
52
55
|
const receivingTransferPresent = ethereumTransfers.some(({ to }) => to === ourWalletAddress)
|
|
53
56
|
|
|
54
|
-
|
|
57
|
+
const shouldAttachTx =
|
|
58
|
+
sendingTransferPresent || receivingTransferPresent || baseBalanceUpdate || nonceUpdate // need to also include if there's a base balance change
|
|
59
|
+
|
|
60
|
+
if (shouldAttachTx) {
|
|
55
61
|
const coinAmount = getValueOfTransfers(ourWalletAddress, asset, ethereumTransfers)
|
|
56
62
|
const selfSend = isSelfSendTx({
|
|
57
63
|
coinAmount,
|
|
@@ -81,6 +87,8 @@ export default function getLogItemsFromServerTx({
|
|
|
81
87
|
data,
|
|
82
88
|
nonce,
|
|
83
89
|
gasLimit,
|
|
90
|
+
balanceChange: baseBalanceUpdate,
|
|
91
|
+
nonceChange: nonceUpdate,
|
|
84
92
|
...methodId,
|
|
85
93
|
...(sent?.length > 0 ? { sent } : undefined),
|
|
86
94
|
},
|
|
@@ -109,12 +117,15 @@ export default function getLogItemsFromServerTx({
|
|
|
109
117
|
Object.entries(tokenTransfersByTokenName).forEach(([tokenName, tokenTransfers]) => {
|
|
110
118
|
const sendingTransferPresent = tokenTransfers.some(({ from }) => from === ourWalletAddress)
|
|
111
119
|
const receivingTransferPresent = tokenTransfers.some(({ to }) => to === ourWalletAddress)
|
|
112
|
-
if (!sendingTransferPresent && !receivingTransferPresent) return
|
|
113
|
-
|
|
114
120
|
const token = assets[tokenName]
|
|
115
|
-
const
|
|
121
|
+
const tokenAddress = token.addresses.current.toLowerCase()
|
|
122
|
+
const balanceChange = tokenBalancesUpdate[tokenAddress]
|
|
116
123
|
|
|
124
|
+
if (!sendingTransferPresent && !receivingTransferPresent && !balanceChange) return
|
|
125
|
+
|
|
126
|
+
const tokenTransferToAddress = tryFindExternalRecipient(tokenTransfers, ourWalletAddress)
|
|
117
127
|
const coinAmount = getValueOfTransfers(ourWalletAddress, token, tokenTransfers)
|
|
128
|
+
|
|
118
129
|
const tokenFromAddresses = lodash.uniq(
|
|
119
130
|
tokenTransfers.filter(({ to }) => to === ourWalletAddress).map(({ from }) => from)
|
|
120
131
|
)
|
|
@@ -138,7 +149,7 @@ export default function getLogItemsFromServerTx({
|
|
|
138
149
|
...logItemCommonProperties,
|
|
139
150
|
coinAmount,
|
|
140
151
|
coinName: tokenName,
|
|
141
|
-
data: { data, nonce, gasLimit, ...methodId },
|
|
152
|
+
data: { data, nonce, gasLimit, balanceChange, ...methodId },
|
|
142
153
|
...(isConsideredSent
|
|
143
154
|
? { from: [], to: tokenTransferToAddress, feeAmount, feeCoinName: token.feeAsset.name }
|
|
144
155
|
: { from: tokenFromAddresses }),
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
export const getWalletUpdates = (ourWalletAddress, walletChanges) => {
|
|
2
|
+
return walletChanges.reduce(
|
|
3
|
+
(acc, walletChange) => {
|
|
4
|
+
const { wallet: walletAddress, type, from, to, contract: contractAddress } = walletChange
|
|
5
|
+
|
|
6
|
+
if (walletAddress !== ourWalletAddress) {
|
|
7
|
+
return acc
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
switch (type) {
|
|
11
|
+
case 'balance':
|
|
12
|
+
acc.baseBalanceUpdate = {
|
|
13
|
+
from,
|
|
14
|
+
to,
|
|
15
|
+
}
|
|
16
|
+
break
|
|
17
|
+
case 'nonce':
|
|
18
|
+
acc.nonceUpdate = {
|
|
19
|
+
from,
|
|
20
|
+
to,
|
|
21
|
+
}
|
|
22
|
+
break
|
|
23
|
+
case 'token':
|
|
24
|
+
acc.tokenBalancesUpdate = {
|
|
25
|
+
...acc.tokenBalancesUpdate,
|
|
26
|
+
[contractAddress]: {
|
|
27
|
+
from,
|
|
28
|
+
to,
|
|
29
|
+
},
|
|
30
|
+
}
|
|
31
|
+
break
|
|
32
|
+
default:
|
|
33
|
+
break
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
return acc
|
|
37
|
+
},
|
|
38
|
+
{
|
|
39
|
+
baseBalanceUpdate: undefined,
|
|
40
|
+
nonceUpdate: undefined,
|
|
41
|
+
tokenBalancesUpdate: Object.create(null),
|
|
42
|
+
}
|
|
43
|
+
)
|
|
44
|
+
}
|
|
@@ -8,15 +8,14 @@ export const resolveNonce = async ({
|
|
|
8
8
|
txLog,
|
|
9
9
|
triedNonce,
|
|
10
10
|
tag = 'latest', // use 'pending' for unconfirmed txs
|
|
11
|
+
useAbsoluteNonce,
|
|
11
12
|
}) => {
|
|
12
13
|
const nonceFromNode =
|
|
13
14
|
asset.baseAsset?.api?.features?.noHistory || forceFromNode
|
|
14
15
|
? await getNonce({ asset: asset.baseAsset, address: fromAddress, tag })
|
|
15
16
|
: 0
|
|
16
17
|
|
|
17
|
-
const nonceFromLog =
|
|
18
|
-
.filter((tx) => tx.sent && !tx.dropped && tx.data.nonce != null)
|
|
19
|
-
.reduce((nonce, tx) => Math.max(tx.data.nonce + 1, nonce), 0)
|
|
18
|
+
const nonceFromLog = getNonceFromTxLog({ txLog, useAbsoluteNonce })
|
|
20
19
|
|
|
21
20
|
return Math.max(
|
|
22
21
|
nonceFromNode,
|
|
@@ -25,3 +24,20 @@ export const resolveNonce = async ({
|
|
|
25
24
|
triedNonce === undefined ? 0 : triedNonce + 1
|
|
26
25
|
)
|
|
27
26
|
}
|
|
27
|
+
|
|
28
|
+
export const getNonceFromTxLog = ({ txLog, useAbsoluteNonce }) => {
|
|
29
|
+
let absoluteNonce = 0
|
|
30
|
+
if (useAbsoluteNonce) {
|
|
31
|
+
for (const tx of txLog.reverse()) {
|
|
32
|
+
if (tx.data.nonceChange) {
|
|
33
|
+
absoluteNonce = parseInt(tx.data.nonceChange.to, 10)
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
const nonceFromLog = [...txLog]
|
|
39
|
+
.filter((tx) => tx.sent && !tx.dropped && tx.data.nonce != null)
|
|
40
|
+
.reduce((nonce, tx) => Math.max(tx.data.nonce + 1, nonce), 0)
|
|
41
|
+
|
|
42
|
+
return Math.max(absoluteNonce, nonceFromLog)
|
|
43
|
+
}
|
package/src/tx-send/tx-send.js
CHANGED
|
@@ -67,7 +67,7 @@ export const HACK_maybeRefineSendAllAmount = async ({
|
|
|
67
67
|
}
|
|
68
68
|
}
|
|
69
69
|
|
|
70
|
-
const txSendFactory = ({ assetClientInterface, createUnsignedTx }) => {
|
|
70
|
+
const txSendFactory = ({ assetClientInterface, createUnsignedTx, useAbsoluteBalanceAndNonce }) => {
|
|
71
71
|
assert(assetClientInterface, 'assetClientInterface is required')
|
|
72
72
|
assert(createUnsignedTx, 'createUnsignedTx is required')
|
|
73
73
|
return async ({ asset, walletAccount, address, amount, feeData: maybeFeeData, options = {} }) => {
|
|
@@ -207,6 +207,7 @@ const txSendFactory = ({ assetClientInterface, createUnsignedTx }) => {
|
|
|
207
207
|
// we'll fall back to the `TxLog` since this also has a knowledge
|
|
208
208
|
// of which transactions are currently in pending.
|
|
209
209
|
tag: 'pending',
|
|
210
|
+
useAbsoluteNonce: useAbsoluteBalanceAndNonce,
|
|
210
211
|
}))
|
|
211
212
|
|
|
212
213
|
const createTxParams = {
|