@exodus/ethereum-api 8.21.2 → 8.21.3
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 +12 -0
- package/README.md +3 -0
- package/package.json +3 -3
- package/src/exodus-eth-server/clarity-v2.js +3 -1
- package/src/exodus-eth-server/clarity.js +13 -7
- package/src/tx-log/ethereum-no-history-monitor.js +3 -2
- package/src/tx-send/nonce-utils.js +18 -0
- package/src/tx-send/tx-send.js +28 -28
package/CHANGELOG.md
CHANGED
|
@@ -3,6 +3,18 @@
|
|
|
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.21.3](https://github.com/ExodusMovement/assets/compare/@exodus/ethereum-api@8.21.2...@exodus/ethereum-api@8.21.3) (2024-11-27)
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
### Bug Fixes
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
* fix: handling JSON RPC responses in Clarity V2 (#4587)
|
|
13
|
+
|
|
14
|
+
* fix: improve nonce calculation for EVMs and fix drop txs in no history (#4583)
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
|
|
6
18
|
## [8.21.2](https://github.com/ExodusMovement/assets/compare/@exodus/ethereum-api@8.21.1...@exodus/ethereum-api@8.21.2) (2024-11-15)
|
|
7
19
|
|
|
8
20
|
**Note:** Version bump only for package @exodus/ethereum-api
|
package/README.md
ADDED
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@exodus/ethereum-api",
|
|
3
|
-
"version": "8.21.
|
|
4
|
-
"description": "Ethereum
|
|
3
|
+
"version": "8.21.3",
|
|
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",
|
|
7
7
|
"files": [
|
|
@@ -64,5 +64,5 @@
|
|
|
64
64
|
"type": "git",
|
|
65
65
|
"url": "git+https://github.com/ExodusMovement/assets.git"
|
|
66
66
|
},
|
|
67
|
-
"gitHead": "
|
|
67
|
+
"gitHead": "391020017026fc2d06ff3d7f164a5c91a6922c3f"
|
|
68
68
|
}
|
|
@@ -108,7 +108,9 @@ export default class ClarityServerV2 extends ClarityServer {
|
|
|
108
108
|
fetchOptions.body = JSON.stringify(body)
|
|
109
109
|
}
|
|
110
110
|
|
|
111
|
-
|
|
111
|
+
const response = await fetchJson(url, fetchOptions)
|
|
112
|
+
|
|
113
|
+
return this.handleJsonRPCResponse(response)
|
|
112
114
|
}
|
|
113
115
|
|
|
114
116
|
async sendRawTransaction(...params) {
|
|
@@ -86,6 +86,18 @@ export default class ClarityServer extends EventEmitter {
|
|
|
86
86
|
}
|
|
87
87
|
}
|
|
88
88
|
|
|
89
|
+
// See https://www.jsonrpc.org/specification#response_object for details.
|
|
90
|
+
handleJsonRPCResponse(response) {
|
|
91
|
+
const result = response?.result
|
|
92
|
+
const error = response?.error
|
|
93
|
+
if (error || result === undefined) {
|
|
94
|
+
const message = error?.message || error?.code || 'no result'
|
|
95
|
+
throw new Error(`Bad rpc response: ${message}`)
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
return result
|
|
99
|
+
}
|
|
100
|
+
|
|
89
101
|
async getAllTransactions(params) {
|
|
90
102
|
const transactions = { pending: [], confirmed: [] }
|
|
91
103
|
const cursor = await this.getTransactions({
|
|
@@ -176,14 +188,8 @@ export default class ClarityServer extends EventEmitter {
|
|
|
176
188
|
|
|
177
189
|
async sendRequest(request) {
|
|
178
190
|
const response = await this.sendRpcRequest(request)
|
|
179
|
-
const result = response?.result
|
|
180
|
-
const error = response?.error
|
|
181
|
-
if (error || result === undefined) {
|
|
182
|
-
const message = error?.message || error?.code || 'no result'
|
|
183
|
-
throw new Error(`Bad rpc response: ${message}`)
|
|
184
|
-
}
|
|
185
191
|
|
|
186
|
-
return
|
|
192
|
+
return this.handleJsonRPCResponse(response)
|
|
187
193
|
}
|
|
188
194
|
|
|
189
195
|
async isContract(address) {
|
|
@@ -137,12 +137,13 @@ export class EthereumNoHistoryMonitor extends BaseMonitor {
|
|
|
137
137
|
|
|
138
138
|
for (const { tx, assetName } of pendingTransactions) {
|
|
139
139
|
const txFromNode = pendingTxsFromNode[tx.txId]
|
|
140
|
-
|
|
140
|
+
const isConfirmed = Boolean(txFromNode?.blockHash)
|
|
141
|
+
if (now - tx.date.getTime() > UNCONFIRMED_TX_LIMIT && !isConfirmed) {
|
|
141
142
|
txsToRemove.push({
|
|
142
143
|
tx,
|
|
143
144
|
assetSource: { asset: assetName, walletAccount },
|
|
144
145
|
})
|
|
145
|
-
} else if (
|
|
146
|
+
} else if (isConfirmed) {
|
|
146
147
|
txsToUpdate.push({
|
|
147
148
|
tx: { ...tx, confirmations: 1 },
|
|
148
149
|
assetSource: { asset: assetName, walletAccount },
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { getNonce } from '../eth-like-util.js'
|
|
2
|
+
|
|
3
|
+
export const resolveNonce = async ({ asset, fromAddress, providedNonce, txLog, triedNonce }) => {
|
|
4
|
+
const nonceFromNode = asset.baseAsset?.api?.features?.noHistory
|
|
5
|
+
? await getNonce({ asset: asset.baseAsset, address: fromAddress, tag: 'latest' }) // maybe 'pending' to unconfirmed txs
|
|
6
|
+
: 0
|
|
7
|
+
|
|
8
|
+
const nonceFromLog = [...txLog]
|
|
9
|
+
.filter((tx) => tx.sent && !tx.dropped && tx.data.nonce != null)
|
|
10
|
+
.reduce((nonce, tx) => Math.max(tx.data.nonce + 1, nonce), 0)
|
|
11
|
+
|
|
12
|
+
return Math.max(
|
|
13
|
+
nonceFromNode,
|
|
14
|
+
nonceFromLog,
|
|
15
|
+
providedNonce ?? 0,
|
|
16
|
+
triedNonce === undefined ? 0 : triedNonce + 1
|
|
17
|
+
)
|
|
18
|
+
}
|
package/src/tx-send/tx-send.js
CHANGED
|
@@ -4,9 +4,10 @@ import { calculateBumpedGasPrice, isEthereumLikeToken, normalizeTxId } from '@ex
|
|
|
4
4
|
import assert from 'minimalistic-assert'
|
|
5
5
|
|
|
6
6
|
import * as ErrorWrapper from '../error-wrapper.js'
|
|
7
|
-
import {
|
|
7
|
+
import { transactionExists } from '../eth-like-util.js'
|
|
8
8
|
import { getNftArguments } from '../nft-utils.js'
|
|
9
9
|
import getFeeInfo from './get-fee-info.js'
|
|
10
|
+
import { resolveNonce } from './nonce-utils.js'
|
|
10
11
|
|
|
11
12
|
const txSendFactory = ({ assetClientInterface, createUnsignedTx }) => {
|
|
12
13
|
assert(assetClientInterface, 'assetClientInterface is required')
|
|
@@ -21,7 +22,7 @@ const txSendFactory = ({ assetClientInterface, createUnsignedTx }) => {
|
|
|
21
22
|
shouldLog = true,
|
|
22
23
|
keepTxInput,
|
|
23
24
|
txInput,
|
|
24
|
-
nonce:
|
|
25
|
+
nonce: providedNonce,
|
|
25
26
|
bumpTxId,
|
|
26
27
|
customFee,
|
|
27
28
|
isSendAll,
|
|
@@ -56,17 +57,18 @@ const txSendFactory = ({ assetClientInterface, createUnsignedTx }) => {
|
|
|
56
57
|
amount = asset.baseAsset.currency.ZERO
|
|
57
58
|
}
|
|
58
59
|
|
|
59
|
-
let
|
|
60
|
+
let bumpNonce
|
|
60
61
|
|
|
61
62
|
let eip1559Enabled = feeData.eip1559Enabled
|
|
62
63
|
|
|
64
|
+
const baseAssetTxLog = await assetClientInterface.getTxLog({
|
|
65
|
+
assetName: baseAsset.name,
|
|
66
|
+
walletAccount,
|
|
67
|
+
})
|
|
68
|
+
|
|
63
69
|
// `replacedTx` is always an ETH/ETC transaction (not a token)
|
|
64
70
|
let replacedTx, replacedTokenTx
|
|
65
71
|
if (bumpTxId) {
|
|
66
|
-
const baseAssetTxLog = await assetClientInterface.getTxLog({
|
|
67
|
-
assetName: baseAsset.name,
|
|
68
|
-
walletAccount,
|
|
69
|
-
})
|
|
70
72
|
replacedTx = baseAssetTxLog.get(bumpTxId)
|
|
71
73
|
if (!replacedTx || !replacedTx.pending) {
|
|
72
74
|
throw new Error(`Cannot bump transaction ${bumpTxId}: not found or confirmed`)
|
|
@@ -98,21 +100,25 @@ const txSendFactory = ({ assetClientInterface, createUnsignedTx }) => {
|
|
|
98
100
|
feeOpts.gasPrice = bumpedGasPrice
|
|
99
101
|
feeOpts.tipGasPrice = bumpedTipGasPrice
|
|
100
102
|
eip1559Enabled = feeData.eip1559Enabled && feeOpts.tipGasPrice
|
|
101
|
-
|
|
103
|
+
bumpNonce = replacedTx.data.nonce
|
|
102
104
|
txInput = replacedTokenTx ? null : replacedTx.data.data || '0x'
|
|
103
105
|
feeAmount = feeOpts.gasPrice.mul(feeOpts.gasLimit)
|
|
104
|
-
if (
|
|
106
|
+
if (bumpNonce === undefined) {
|
|
105
107
|
throw new Error(`Cannot bump transaction ${bumpTxId}: data object seems to be corrupted`)
|
|
106
108
|
}
|
|
107
109
|
}
|
|
108
110
|
|
|
111
|
+
const resolvedNonce =
|
|
112
|
+
bumpNonce ??
|
|
113
|
+
(await resolveNonce({ asset, fromAddress, providedNonce, txLog: baseAssetTxLog }))
|
|
114
|
+
|
|
109
115
|
const createTxParams = {
|
|
110
116
|
assetClientInterface,
|
|
111
117
|
asset,
|
|
112
118
|
walletAccount,
|
|
113
119
|
toAddress: contractAddress || address,
|
|
114
120
|
amount,
|
|
115
|
-
nonce:
|
|
121
|
+
nonce: resolvedNonce,
|
|
116
122
|
fromAddress,
|
|
117
123
|
eip1559Enabled,
|
|
118
124
|
customFee,
|
|
@@ -157,8 +163,13 @@ const txSendFactory = ({ assetClientInterface, createUnsignedTx }) => {
|
|
|
157
163
|
} else if (nonceTooLowErr) {
|
|
158
164
|
console.info('trying to send again...') // inject logger factory from platform
|
|
159
165
|
// let's try to fix the nonce issue
|
|
160
|
-
|
|
161
|
-
|
|
166
|
+
nonce = await resolveNonce({
|
|
167
|
+
asset,
|
|
168
|
+
fromAddress,
|
|
169
|
+
providedNonce,
|
|
170
|
+
txLog: baseAssetTxLog,
|
|
171
|
+
triedNonce: nonce,
|
|
172
|
+
})
|
|
162
173
|
;({ txId, rawTx } = await createTx({ ...createTxParams, nonce }))
|
|
163
174
|
|
|
164
175
|
try {
|
|
@@ -247,7 +258,7 @@ const txSendFactory = ({ assetClientInterface, createUnsignedTx }) => {
|
|
|
247
258
|
})
|
|
248
259
|
}
|
|
249
260
|
|
|
250
|
-
return { txId }
|
|
261
|
+
return { txId, nonce }
|
|
251
262
|
}
|
|
252
263
|
}
|
|
253
264
|
|
|
@@ -268,6 +279,10 @@ const createTx = async ({
|
|
|
268
279
|
feeOpts,
|
|
269
280
|
createUnsignedTx,
|
|
270
281
|
}) => {
|
|
282
|
+
assert(
|
|
283
|
+
nonce !== undefined && typeof nonce === 'number',
|
|
284
|
+
'Nonce must be provided when creating a tx'
|
|
285
|
+
)
|
|
271
286
|
const isToken = isEthereumLikeToken(asset)
|
|
272
287
|
|
|
273
288
|
if (txInput && isToken && !keepTxInput)
|
|
@@ -309,21 +324,6 @@ const createTx = async ({
|
|
|
309
324
|
// gasLimit = customGasLimit
|
|
310
325
|
}
|
|
311
326
|
|
|
312
|
-
if (asset.baseAsset?.api?.hasFeature?.('noHistory')) {
|
|
313
|
-
nonce = await getNonce({ asset: asset.baseAsset, address: fromAddress })
|
|
314
|
-
}
|
|
315
|
-
|
|
316
|
-
if (nonce === undefined) {
|
|
317
|
-
// Calculate latest nonce from base asset's TX log
|
|
318
|
-
const baseAssetTxLog = await assetClientInterface.getTxLog({
|
|
319
|
-
assetName: asset.baseAsset.name,
|
|
320
|
-
walletAccount,
|
|
321
|
-
})
|
|
322
|
-
nonce = [...baseAssetTxLog]
|
|
323
|
-
.filter((tx) => tx.sent && !tx.dropped && tx.data.nonce != null)
|
|
324
|
-
.reduce((nonce, tx) => Math.max(tx.data.nonce + 1, nonce), 0)
|
|
325
|
-
}
|
|
326
|
-
|
|
327
327
|
const unsignedTx = await createUnsignedTx({
|
|
328
328
|
asset,
|
|
329
329
|
walletAccount,
|