@exodus/ethereum-api 8.76.6 → 8.76.7
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 +20 -0
- package/package.json +2 -3
- package/src/create-asset-plugin-factory.js +1 -0
- package/src/create-asset-utils.js +39 -35
- package/src/create-asset.js +21 -14
- package/src/exodus-eth-server/api-coin-nodes.js +11 -84
- package/src/exodus-eth-server/clarity-v2.js +30 -51
- package/src/exodus-eth-server/clarity.js +2 -115
- package/src/exodus-eth-server/errors.js +5 -1
- package/src/exodus-eth-server/eth-like-server-base.js +123 -0
- package/src/exodus-eth-server/fetch-json.js +48 -0
- package/src/gas-estimation.js +19 -4
- package/src/get-balances.js +14 -0
- package/src/index.js +1 -0
- package/src/multicall3/index.js +169 -0
- package/src/simulation/common.js +34 -0
- package/src/simulation/create-simulate-message.js +49 -0
- package/src/simulation/create-simulate-transactions.js +106 -0
- package/src/simulation/estimate-fee.js +14 -0
- package/src/simulation/estimate-simple-transfer.js +15 -0
- package/src/simulation/get-message-type.js +18 -0
- package/src/simulation/simulate-message-api.js +68 -0
- package/src/simulation/simulate-transactions-api.js +265 -0
- package/src/simulation/simulate-transactions.js +16 -0
- package/src/simulation/transactions.js +52 -0
- package/src/simulation/try-estimating-changes-locally.js +26 -0
- package/src/staking/ethereum/staking-utils.js +3 -1
- package/src/staking/matic/matic-staking-utils.js +3 -1
- package/src/tx-log/clarity-truncated-history-monitor.js +34 -0
- package/src/tx-log/ethereum-no-history-monitor.js +2 -23
- package/src/tx-log/monitor-utils/get-batched-rpc-balances.js +28 -0
- package/src/tx-send/broadcast-error-handler.js +7 -2
- package/src/tx-send/tx-send.js +1 -0
- package/src/web3/createSimulateMessage.js +2 -1
- package/src/web3/createSimulateTransactions.js +3 -9
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.76.7](https://github.com/ExodusMovement/assets/compare/@exodus/ethereum-api@8.76.6...@exodus/ethereum-api@8.76.7) (2026-06-26)
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
### Features
|
|
10
|
+
|
|
11
|
+
* clarity native transaction privacy ([#8265](https://github.com/ExodusMovement/assets/issues/8265)) ([c79975e](https://github.com/ExodusMovement/assets/commit/c79975ec0605ef05a4f6eb8c3d1d0fc4531a1f5b))
|
|
12
|
+
* enable multicall3 balance batches for `'no-history'` monitor ([#8276](https://github.com/ExodusMovement/assets/issues/8276)) ([acccb80](https://github.com/ExodusMovement/assets/commit/acccb8062ad5bc9f8c7f80bc14f53943b4421b2a))
|
|
13
|
+
* introduce multicall3 capability ([#8243](https://github.com/ExodusMovement/assets/issues/8243)) ([8fde0b7](https://github.com/ExodusMovement/assets/commit/8fde0b74322854d69d3ea962e5500287d111ec39))
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
### Bug Fixes
|
|
17
|
+
|
|
18
|
+
* **ethereum-api:** retry and surface non-OK responses in ApiCoinNodesServer ([#8278](https://github.com/ExodusMovement/assets/issues/8278)) ([99cff43](https://github.com/ExodusMovement/assets/commit/99cff4317d5bb2b311972681625cd373c0b555af))
|
|
19
|
+
* **ethereum:** clamp negative spendable from in-flight sends ([#8246](https://github.com/ExodusMovement/assets/issues/8246)) ([25dbe72](https://github.com/ExodusMovement/assets/commit/25dbe7216bec02b4adec9362cf46509077ea7d1f))
|
|
20
|
+
* prevent incompatible account state from interfering with clarity… ([#8251](https://github.com/ExodusMovement/assets/issues/8251)) ([04ac9d0](https://github.com/ExodusMovement/assets/commit/04ac9d0658d57718c76831569455ed5c75566c55))
|
|
21
|
+
* prevent retry on nonce-too-low retry ([#8299](https://github.com/ExodusMovement/assets/issues/8299)) ([d7028b4](https://github.com/ExodusMovement/assets/commit/d7028b40add3fb33f3868f55de96939d9f452b34))
|
|
22
|
+
* use eip-7623 calldata pricing for defaultGasLimit ([#8288](https://github.com/ExodusMovement/assets/issues/8288)) ([227efda](https://github.com/ExodusMovement/assets/commit/227efda89a79970cd763805a17dd1b2ba07e0b23))
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
|
|
6
26
|
## [8.76.6](https://github.com/ExodusMovement/assets/compare/@exodus/ethereum-api@8.76.5...@exodus/ethereum-api@8.76.6) (2026-06-11)
|
|
7
27
|
|
|
8
28
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@exodus/ethereum-api",
|
|
3
|
-
"version": "8.76.
|
|
3
|
+
"version": "8.76.7",
|
|
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",
|
|
@@ -40,7 +40,6 @@
|
|
|
40
40
|
"@exodus/simple-retry": "^0.0.6",
|
|
41
41
|
"@exodus/solidity-contract": "^1.3.0",
|
|
42
42
|
"@exodus/traceparent": "^3.0.1",
|
|
43
|
-
"@exodus/web3-ethereum-utils": "^4.7.4",
|
|
44
43
|
"@exodus/web3-utils": "^1.51.2",
|
|
45
44
|
"bn.js": "^5.2.1",
|
|
46
45
|
"delay": "^4.0.1",
|
|
@@ -70,5 +69,5 @@
|
|
|
70
69
|
"type": "git",
|
|
71
70
|
"url": "git+https://github.com/ExodusMovement/assets.git"
|
|
72
71
|
},
|
|
73
|
-
"gitHead": "
|
|
72
|
+
"gitHead": "c704777d72888bf3a94f3a7ddc1b0e57cda3e6dc"
|
|
74
73
|
}
|
|
@@ -5,8 +5,13 @@ import assert from 'minimalistic-assert'
|
|
|
5
5
|
import ms from 'ms'
|
|
6
6
|
|
|
7
7
|
import { EVM_ERROR_REASONS, withErrorReason } from './error-wrapper.js'
|
|
8
|
-
import
|
|
8
|
+
import ClarityServerV2 from './exodus-eth-server/clarity-v2.js'
|
|
9
|
+
import { ValidMonitorTypes } from './exodus-eth-server/index.js'
|
|
9
10
|
import { createEthereumHooks } from './hooks/index.js'
|
|
11
|
+
import {
|
|
12
|
+
EthLikeMulticall3RpcRequestAccumulator,
|
|
13
|
+
EthLikeRpcRequestAccumulator,
|
|
14
|
+
} from './multicall3/index.js'
|
|
10
15
|
import { ClarityMonitor } from './tx-log/clarity-monitor.js'
|
|
11
16
|
import { ClarityTruncatedHistoryMonitor } from './tx-log/clarity-truncated-history-monitor.js'
|
|
12
17
|
import { EthereumMonitor } from './tx-log/ethereum-monitor.js'
|
|
@@ -101,27 +106,6 @@ export const stringifyPrivateTx = (tx) => {
|
|
|
101
106
|
return `0x${tx}`
|
|
102
107
|
}
|
|
103
108
|
|
|
104
|
-
const broadcastPrivateBundleFactory =
|
|
105
|
-
({ privacyServer }) =>
|
|
106
|
-
async ({ txs }) => {
|
|
107
|
-
assert(Array.isArray(txs), 'txs must be an array')
|
|
108
|
-
assert(txs.length > 0, 'txs must be an non-empty array')
|
|
109
|
-
|
|
110
|
-
const sendBundleResult = await privacyServer.sendRequest(
|
|
111
|
-
privacyServer.buildRequest({
|
|
112
|
-
method: 'eth_sendBundle',
|
|
113
|
-
params: [{ txs: txs.map((tx) => stringifyPrivateTx(tx)) }],
|
|
114
|
-
})
|
|
115
|
-
)
|
|
116
|
-
if (typeof sendBundleResult === 'string') return { bundleHash: sendBundleResult }
|
|
117
|
-
|
|
118
|
-
const bundleHash = sendBundleResult?.bundleHash
|
|
119
|
-
if (typeof bundleHash === 'string') return { bundleHash }
|
|
120
|
-
|
|
121
|
-
console.warn('unexpected sendBundleResult shape, cannot determine bundle hash')
|
|
122
|
-
return null
|
|
123
|
-
}
|
|
124
|
-
|
|
125
109
|
const assertSendPrivateTxProps = ({ asset, unsignedTx, walletAccount }) => {
|
|
126
110
|
assert(asset, 'expected asset')
|
|
127
111
|
assert(unsignedTx, 'expected unsignedTx')
|
|
@@ -132,12 +116,10 @@ export const createTransactionPrivacyResult = ({
|
|
|
132
116
|
assetClientInterface,
|
|
133
117
|
broadcastPrivateBundle,
|
|
134
118
|
broadcastPrivateTx,
|
|
135
|
-
privacyServer,
|
|
136
119
|
}) => {
|
|
137
120
|
assert(assetClientInterface, 'expected assetClientInterface')
|
|
138
121
|
assert(typeof broadcastPrivateBundle === 'function')
|
|
139
122
|
assert(typeof broadcastPrivateTx === 'function')
|
|
140
|
-
assert(privacyServer, 'expected privacyServer')
|
|
141
123
|
|
|
142
124
|
const sendPrivateTx = async ({ asset, unsignedTx, walletAccount }) => {
|
|
143
125
|
assertSendPrivateTxProps({ asset, unsignedTx, walletAccount })
|
|
@@ -229,7 +211,6 @@ export const createTransactionPrivacyResult = ({
|
|
|
229
211
|
return {
|
|
230
212
|
broadcastPrivateBundle,
|
|
231
213
|
broadcastPrivateTx,
|
|
232
|
-
privacyServer,
|
|
233
214
|
sendPrivateTx,
|
|
234
215
|
sendPrivateBundle,
|
|
235
216
|
}
|
|
@@ -238,30 +219,53 @@ export const createTransactionPrivacyResult = ({
|
|
|
238
219
|
export const createTransactionPrivacyFactory = ({
|
|
239
220
|
assetClientInterface,
|
|
240
221
|
assetName,
|
|
241
|
-
|
|
222
|
+
server,
|
|
223
|
+
supportsTransactionPrivacy,
|
|
242
224
|
}) => {
|
|
243
225
|
assert(assetClientInterface, 'expected assetClientInterface')
|
|
226
|
+
assert(server, 'expected server')
|
|
244
227
|
|
|
245
|
-
if (!
|
|
228
|
+
if (!supportsTransactionPrivacy) return Object.create(null)
|
|
246
229
|
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
230
|
+
if (!(server instanceof ClarityServerV2)) {
|
|
231
|
+
console.warn(
|
|
232
|
+
`attempted to enable transactionPrivacy for ${assetName} but provided server is incompatible`
|
|
233
|
+
)
|
|
234
|
+
return Object.create(null)
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
const broadcastPrivateBundle = async ({ txs }) => {
|
|
238
|
+
assert(Array.isArray(txs), 'txs must be an array')
|
|
239
|
+
assert(txs.length > 0, 'txs must be a non-empty array')
|
|
240
|
+
|
|
241
|
+
const sendBundleResult = await server.sendBundle({
|
|
242
|
+
txs: txs.map((tx) => stringifyPrivateTx(tx)),
|
|
243
|
+
})
|
|
244
|
+
if (typeof sendBundleResult === 'string') return { bundleHash: sendBundleResult }
|
|
252
245
|
|
|
253
|
-
|
|
246
|
+
const bundleHash = sendBundleResult?.bundleHash
|
|
247
|
+
if (typeof bundleHash === 'string') return { bundleHash }
|
|
254
248
|
|
|
255
|
-
|
|
249
|
+
console.warn('unexpected sendBundleResult shape, cannot determine bundle hash')
|
|
250
|
+
return null
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
const broadcastPrivateTx = (tx) => broadcastPrivateBundle({ txs: [tx] })
|
|
256
254
|
|
|
257
255
|
return createTransactionPrivacyResult({
|
|
258
256
|
assetClientInterface,
|
|
259
257
|
broadcastPrivateBundle,
|
|
260
258
|
broadcastPrivateTx,
|
|
261
|
-
privacyServer,
|
|
262
259
|
})
|
|
263
260
|
}
|
|
264
261
|
|
|
262
|
+
export const createRpcRequestAccumulatorFactory =
|
|
263
|
+
({ multicall3Address }) =>
|
|
264
|
+
() =>
|
|
265
|
+
multicall3Address
|
|
266
|
+
? new EthLikeMulticall3RpcRequestAccumulator({ multicall3Address })
|
|
267
|
+
: new EthLikeRpcRequestAccumulator()
|
|
268
|
+
|
|
265
269
|
export const createHistoryMonitorFactory = ({
|
|
266
270
|
assetName,
|
|
267
271
|
assetClientInterface,
|
package/src/create-asset.js
CHANGED
|
@@ -27,6 +27,7 @@ import { createCheckTx } from './check-tx/index.js'
|
|
|
27
27
|
import {
|
|
28
28
|
createGetBlackListStatus,
|
|
29
29
|
createHistoryMonitorFactory,
|
|
30
|
+
createRpcRequestAccumulatorFactory,
|
|
30
31
|
createSecurityChecks,
|
|
31
32
|
createTransactionPrivacyFactory,
|
|
32
33
|
getNonceFactory,
|
|
@@ -68,6 +69,7 @@ export const createAssetFactory = ({
|
|
|
68
69
|
l1GasOracleAddress, // l1 extra fee for base and optostakingConfiguration = {},
|
|
69
70
|
monitorInterval: defaultMonitorInterval,
|
|
70
71
|
monitorType: defaultMonitorType = 'magnifier',
|
|
72
|
+
multicall3Address: defaultMulticall3Address,
|
|
71
73
|
nfts: defaultNfts = false,
|
|
72
74
|
serverUrl: defaultServerUrl,
|
|
73
75
|
stakingConfiguration = Object.create(null),
|
|
@@ -79,8 +81,9 @@ export const createAssetFactory = ({
|
|
|
79
81
|
supportsCustomFees: defaultSupportsCustomFees = false,
|
|
80
82
|
useAbsoluteBalanceAndNonce = false,
|
|
81
83
|
delisted = false,
|
|
82
|
-
|
|
84
|
+
supportsTransactionPrivacy: defaultSupportsTransactionPrivacy,
|
|
83
85
|
wsGatewayUri: defaultWsGatewayUri,
|
|
86
|
+
eip7623Supported = false,
|
|
84
87
|
eip7702Supported,
|
|
85
88
|
transactionAssessment: defaultTransactionAssessment,
|
|
86
89
|
}) => {
|
|
@@ -105,8 +108,9 @@ export const createAssetFactory = ({
|
|
|
105
108
|
supportsCustomFees: defaultSupportsCustomFees,
|
|
106
109
|
nfts: defaultNfts,
|
|
107
110
|
customTokens: defaultCustomTokens,
|
|
108
|
-
|
|
111
|
+
supportsTransactionPrivacy: defaultSupportsTransactionPrivacy,
|
|
109
112
|
transactionAssessment: defaultTransactionAssessment,
|
|
113
|
+
multicall3Address: defaultMulticall3Address,
|
|
110
114
|
}
|
|
111
115
|
return (
|
|
112
116
|
{
|
|
@@ -126,8 +130,9 @@ export const createAssetFactory = ({
|
|
|
126
130
|
customTokens,
|
|
127
131
|
supportsCustomFees,
|
|
128
132
|
useAbsoluteBalanceAndNonce: overrideUseAbsoluteBalanceAndNonce,
|
|
129
|
-
|
|
133
|
+
supportsTransactionPrivacy,
|
|
130
134
|
transactionAssessment,
|
|
135
|
+
multicall3Address,
|
|
131
136
|
} = configWithOverrides
|
|
132
137
|
|
|
133
138
|
const asset = assets[base.name]
|
|
@@ -202,16 +207,16 @@ export const createAssetFactory = ({
|
|
|
202
207
|
server,
|
|
203
208
|
})
|
|
204
209
|
|
|
205
|
-
const {
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
210
|
+
const { broadcastPrivateBundle, broadcastPrivateTx, sendPrivateTx, sendPrivateBundle } =
|
|
211
|
+
createTransactionPrivacyFactory({
|
|
212
|
+
assetClientInterface,
|
|
213
|
+
assetName: asset.name,
|
|
214
|
+
server,
|
|
215
|
+
supportsTransactionPrivacy,
|
|
216
|
+
})
|
|
217
|
+
|
|
218
|
+
const createRpcRequestAccumulator = createRpcRequestAccumulatorFactory({
|
|
219
|
+
multicall3Address,
|
|
215
220
|
})
|
|
216
221
|
|
|
217
222
|
const features = {
|
|
@@ -228,6 +233,7 @@ export const createAssetFactory = ({
|
|
|
228
233
|
signWithSigner: true,
|
|
229
234
|
signMessageWithSigner: true,
|
|
230
235
|
supportsCustomFees,
|
|
236
|
+
supportsTransactionPrivacy: Boolean(supportsTransactionPrivacy),
|
|
231
237
|
...(supportsStaking && { staking: {} }),
|
|
232
238
|
...(delisted && { delisted }),
|
|
233
239
|
}
|
|
@@ -385,6 +391,7 @@ export const createAssetFactory = ({
|
|
|
385
391
|
monitorType,
|
|
386
392
|
estimateL1DataFee,
|
|
387
393
|
forceGasLimitEstimation,
|
|
394
|
+
eip7623Supported,
|
|
388
395
|
eip7702Supported,
|
|
389
396
|
getEIP7702Delegation: (addr) => getEIP7702Delegation({ address: addr, server }),
|
|
390
397
|
getNonce,
|
|
@@ -393,7 +400,7 @@ export const createAssetFactory = ({
|
|
|
393
400
|
...(fuelThreshold && { fuelThreshold: asset.currency.defaultUnit(fuelThreshold) }),
|
|
394
401
|
broadcastPrivateBundle,
|
|
395
402
|
broadcastPrivateTx,
|
|
396
|
-
|
|
403
|
+
createRpcRequestAccumulator,
|
|
397
404
|
sendPrivateTx,
|
|
398
405
|
sendPrivateBundle,
|
|
399
406
|
}
|
|
@@ -1,22 +1,20 @@
|
|
|
1
|
-
import { bufferToHex } from '@exodus/ethereumjs/util'
|
|
2
1
|
import { safeString } from '@exodus/safe-string'
|
|
3
|
-
import SolidityContract from '@exodus/solidity-contract'
|
|
4
|
-
import EventEmitter from 'events/events.js'
|
|
5
2
|
import lodash from 'lodash'
|
|
6
3
|
|
|
7
4
|
import { fromHexToString } from '../number-utils.js'
|
|
8
5
|
import { errorMessageToSafeHint } from './errors.js'
|
|
6
|
+
import EthLikeServerBase from './eth-like-server-base.js'
|
|
7
|
+
import { fetchJsonRetry } from './fetch-json.js'
|
|
9
8
|
import { getFallbackGasPriceEstimation } from './utils.js'
|
|
10
9
|
|
|
11
10
|
const { isEmpty } = lodash
|
|
12
11
|
|
|
13
|
-
export default class ApiCoinNodesServer extends
|
|
12
|
+
export default class ApiCoinNodesServer extends EthLikeServerBase {
|
|
14
13
|
constructor({ baseAssetName, uri }) {
|
|
15
14
|
super()
|
|
16
15
|
this.baseAssetName = baseAssetName
|
|
17
16
|
this.uri = uri
|
|
18
17
|
this.defaultUri = uri
|
|
19
|
-
this.id = 0
|
|
20
18
|
}
|
|
21
19
|
|
|
22
20
|
setURI(uri) {
|
|
@@ -30,8 +28,7 @@ export default class ApiCoinNodesServer extends EventEmitter {
|
|
|
30
28
|
body: JSON.stringify(body),
|
|
31
29
|
}
|
|
32
30
|
|
|
33
|
-
|
|
34
|
-
return response.json()
|
|
31
|
+
return fetchJsonRetry(this.uri, options)
|
|
35
32
|
}
|
|
36
33
|
|
|
37
34
|
async sendBatchRequest(batch) {
|
|
@@ -60,6 +57,12 @@ export default class ApiCoinNodesServer extends EventEmitter {
|
|
|
60
57
|
|
|
61
58
|
const revisedError = new Error(`Bad rpc response: ${message}`)
|
|
62
59
|
revisedError.hint = safeString`Bad rpc response: ${errorMessageToSafeHint(message)}`
|
|
60
|
+
|
|
61
|
+
const traceId = response?.__traceId
|
|
62
|
+
if (traceId) {
|
|
63
|
+
revisedError.traceId = traceId
|
|
64
|
+
}
|
|
65
|
+
|
|
63
66
|
throw revisedError
|
|
64
67
|
}
|
|
65
68
|
|
|
@@ -71,78 +74,6 @@ export default class ApiCoinNodesServer extends EventEmitter {
|
|
|
71
74
|
return code.length > 2
|
|
72
75
|
}
|
|
73
76
|
|
|
74
|
-
buildRequest({ method, params = [] }) {
|
|
75
|
-
return { jsonrpc: '2.0', id: this.id++, method, params }
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
balanceOfRequest(address, tokenAddress, tag = 'latest') {
|
|
79
|
-
const contract = SolidityContract.simpleErc20(tokenAddress)
|
|
80
|
-
const callData = contract.balanceOf.build(address)
|
|
81
|
-
const data = {
|
|
82
|
-
data: bufferToHex(callData),
|
|
83
|
-
to: tokenAddress,
|
|
84
|
-
}
|
|
85
|
-
return this.ethCallRequest(data, tag)
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
getBalanceRequest(address, tag = 'latest') {
|
|
89
|
-
return this.buildRequest({ method: 'eth_getBalance', params: [address, tag] })
|
|
90
|
-
}
|
|
91
|
-
|
|
92
|
-
gasPriceRequest() {
|
|
93
|
-
return this.buildRequest({ method: 'eth_gasPrice' })
|
|
94
|
-
}
|
|
95
|
-
|
|
96
|
-
estimateGasRequest(data) {
|
|
97
|
-
return this.buildRequest({ method: 'eth_estimateGas', params: [data] })
|
|
98
|
-
}
|
|
99
|
-
|
|
100
|
-
sendRawTransactionRequest(data) {
|
|
101
|
-
const _data = data instanceof Uint8Array ? Buffer.from(data).toString('hex') : data
|
|
102
|
-
const hex = _data.startsWith('0x') ? _data : '0x' + _data
|
|
103
|
-
return this.buildRequest({ method: 'eth_sendRawTransaction', params: [hex] })
|
|
104
|
-
}
|
|
105
|
-
|
|
106
|
-
getCodeRequest(address, tag = 'latest') {
|
|
107
|
-
return this.buildRequest({ method: 'eth_getCode', params: [address, tag] })
|
|
108
|
-
}
|
|
109
|
-
|
|
110
|
-
getStorageAtRequest(address, position, tag = 'latest') {
|
|
111
|
-
return this.buildRequest({ method: 'eth_getStorageAt', params: [address, position, tag] })
|
|
112
|
-
}
|
|
113
|
-
|
|
114
|
-
getTransactionCountRequest(address, tag = 'latest') {
|
|
115
|
-
return this.buildRequest({ method: 'eth_getTransactionCount', params: [address, tag] })
|
|
116
|
-
}
|
|
117
|
-
|
|
118
|
-
getTransactionByHashRequest(hash) {
|
|
119
|
-
return this.buildRequest({ method: 'eth_getTransactionByHash', params: [hash] })
|
|
120
|
-
}
|
|
121
|
-
|
|
122
|
-
getTransactionReceiptRequest(txhash) {
|
|
123
|
-
return this.buildRequest({ method: 'eth_getTransactionReceipt', params: [txhash] })
|
|
124
|
-
}
|
|
125
|
-
|
|
126
|
-
ethCallRequest(data, tag = 'latest') {
|
|
127
|
-
return this.buildRequest({ method: 'eth_call', params: [data, tag] })
|
|
128
|
-
}
|
|
129
|
-
|
|
130
|
-
blockNumberRequest() {
|
|
131
|
-
return this.buildRequest({ method: 'eth_blockNumber' })
|
|
132
|
-
}
|
|
133
|
-
|
|
134
|
-
getBlockByNumberRequest(numberHex, isFullTxs = false) {
|
|
135
|
-
return this.buildRequest({ method: 'eth_getBlockByNumber', params: [numberHex, isFullTxs] })
|
|
136
|
-
}
|
|
137
|
-
|
|
138
|
-
simulateRawTransactionRequest(rawTx, applyPending = true) {
|
|
139
|
-
const replaced = rawTx.replace('0x', '')
|
|
140
|
-
return this.buildRequest({
|
|
141
|
-
method: 'debug_simulateRawTransaction',
|
|
142
|
-
params: [replaced, applyPending],
|
|
143
|
-
})
|
|
144
|
-
}
|
|
145
|
-
|
|
146
77
|
async balanceOf(address, tokenAddress, tag = 'latest') {
|
|
147
78
|
const request = this.balanceOfRequest(address, tokenAddress, tag)
|
|
148
79
|
const result = await this.sendRequest(request)
|
|
@@ -176,11 +107,7 @@ export default class ApiCoinNodesServer extends EventEmitter {
|
|
|
176
107
|
getGasPrice = this.gasPrice
|
|
177
108
|
|
|
178
109
|
async getLatestBlock() {
|
|
179
|
-
|
|
180
|
-
method: 'eth_getBlockByNumber',
|
|
181
|
-
params: ['latest', false],
|
|
182
|
-
})
|
|
183
|
-
return this.sendRequest(request)
|
|
110
|
+
return this.sendRequest(this.getBlockByNumberRequest('latest', false))
|
|
184
111
|
}
|
|
185
112
|
|
|
186
113
|
async getBaseFeePerGas() {
|
|
@@ -1,8 +1,7 @@
|
|
|
1
|
-
import { retry } from '@exodus/simple-retry'
|
|
2
|
-
import { TraceId } from '@exodus/traceparent'
|
|
3
1
|
import assert from 'minimalistic-assert'
|
|
4
2
|
|
|
5
3
|
import ClarityServer, { RPC_REQUEST_TIMEOUT } from './clarity.js'
|
|
4
|
+
import { fetchJsonRetry } from './fetch-json.js'
|
|
6
5
|
|
|
7
6
|
const ASSETS_GATEWAY_URL = 'https://assets-gateway-clarity-api.a.exodus.io/assets'
|
|
8
7
|
|
|
@@ -33,56 +32,12 @@ export const decodeCursor = (cursor) => {
|
|
|
33
32
|
return { blockNumber: BigInt(0) }
|
|
34
33
|
}
|
|
35
34
|
|
|
36
|
-
const
|
|
37
|
-
try {
|
|
38
|
-
const responseBody = await response.text()
|
|
39
|
-
return responseBody.slice(0, 100)
|
|
40
|
-
} catch {
|
|
41
|
-
return ''
|
|
42
|
-
}
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
const fetchJson = async (url, fetchOptions) => {
|
|
46
|
-
const response = await fetch(url, fetchOptions)
|
|
47
|
-
|
|
48
|
-
if (!response.ok) {
|
|
49
|
-
const traceId = TraceId.fromResponse(response)
|
|
50
|
-
const error = new Error(
|
|
51
|
-
`${url} returned ${response.status}: ${
|
|
52
|
-
response.statusText || 'Unknown Status Text'
|
|
53
|
-
}. Body: ${await getTextFromResponse(response)}`
|
|
54
|
-
)
|
|
55
|
-
if (traceId) {
|
|
56
|
-
error.traceId = traceId
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
throw error
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
const json = await response.json()
|
|
63
|
-
|
|
64
|
-
// Only capture trace ID if there's an RPC error in the response
|
|
65
|
-
// (handleJsonRPCResponse will extract it when throwing the error)
|
|
66
|
-
if (json.error) {
|
|
67
|
-
const traceId = TraceId.fromResponse(response)
|
|
68
|
-
if (traceId) {
|
|
69
|
-
json.__traceId = traceId
|
|
70
|
-
}
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
return json
|
|
74
|
-
}
|
|
75
|
-
|
|
76
|
-
async function fetchJsonRetry(url, fetchOptions) {
|
|
77
|
-
const waitTimes = ['3s']
|
|
78
|
-
const fetchWithRetry = retry(fetchJson, { delayTimesMs: waitTimes })
|
|
79
|
-
return fetchWithRetry(url, fetchOptions)
|
|
80
|
-
}
|
|
81
|
-
|
|
82
|
-
const fetchHttpRequest = ({ baseApiPath, path, method, body }) => {
|
|
35
|
+
const fetchHttpRequest = ({ baseApiPath, path, method, body, search }) => {
|
|
83
36
|
assert(typeof baseApiPath === 'string', 'expected string baseApiPath')
|
|
84
37
|
|
|
85
38
|
const url = new URL(`${baseApiPath}${path}`)
|
|
39
|
+
if (search) url.search = search
|
|
40
|
+
|
|
86
41
|
const fetchOptions = {
|
|
87
42
|
method,
|
|
88
43
|
headers: { 'Content-Type': 'application/json' },
|
|
@@ -230,8 +185,8 @@ export default class ClarityServerV2 extends ClarityServer {
|
|
|
230
185
|
}
|
|
231
186
|
}
|
|
232
187
|
|
|
233
|
-
fetchRpcHttpRequest = ({ baseApiPath, body }) => {
|
|
234
|
-
return fetchHttpRequest({ baseApiPath, path
|
|
188
|
+
fetchRpcHttpRequest = ({ baseApiPath, path = '/rpc', body }) => {
|
|
189
|
+
return fetchHttpRequest({ baseApiPath, path, method: 'POST', body })
|
|
235
190
|
}
|
|
236
191
|
|
|
237
192
|
async sendRpcRequest(rpcRequest) {
|
|
@@ -273,4 +228,28 @@ export default class ClarityServerV2 extends ClarityServer {
|
|
|
273
228
|
await this.fetchRpcHttpRequest({ baseApiPath, body: request })
|
|
274
229
|
)
|
|
275
230
|
}
|
|
231
|
+
|
|
232
|
+
getServoTransactionsByBundleHash({ bundleHash }) {
|
|
233
|
+
assert(bundleHash, 'expected bundleHash')
|
|
234
|
+
|
|
235
|
+
const { baseApiPath } = this
|
|
236
|
+
|
|
237
|
+
return fetchHttpRequest({
|
|
238
|
+
baseApiPath,
|
|
239
|
+
path: '/proxy/servo/transactions',
|
|
240
|
+
method: 'GET',
|
|
241
|
+
search: new URLSearchParams({ bundleHash }).toString(),
|
|
242
|
+
})
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
async sendBundle(...params) {
|
|
246
|
+
const { baseApiPath } = this
|
|
247
|
+
return this.handleJsonRPCResponse(
|
|
248
|
+
await this.fetchRpcHttpRequest({
|
|
249
|
+
baseApiPath,
|
|
250
|
+
path: '/proxy/servo',
|
|
251
|
+
body: this.sendBundleRequest(...params),
|
|
252
|
+
})
|
|
253
|
+
)
|
|
254
|
+
}
|
|
276
255
|
}
|
|
@@ -1,17 +1,15 @@
|
|
|
1
|
-
import { bufferToHex } from '@exodus/ethereumjs/util'
|
|
2
1
|
import { safeString } from '@exodus/safe-string'
|
|
3
|
-
import SolidityContract from '@exodus/solidity-contract'
|
|
4
2
|
import { TraceId } from '@exodus/traceparent'
|
|
5
|
-
import EventEmitter from 'events/events.js'
|
|
6
3
|
import io from 'socket.io-client'
|
|
7
4
|
|
|
8
5
|
import { fromHexToString } from '../number-utils.js'
|
|
9
6
|
import { errorMessageToSafeHint } from './errors.js'
|
|
7
|
+
import EthLikeServerBase from './eth-like-server-base.js'
|
|
10
8
|
import { getFallbackGasPriceEstimation } from './utils.js'
|
|
11
9
|
|
|
12
10
|
export const RPC_REQUEST_TIMEOUT = 'RPC_REQUEST_TIMEOUT'
|
|
13
11
|
|
|
14
|
-
export default class ClarityServer extends
|
|
12
|
+
export default class ClarityServer extends EthLikeServerBase {
|
|
15
13
|
constructor({ baseAssetName, uri }) {
|
|
16
14
|
super()
|
|
17
15
|
this.baseAssetName = baseAssetName
|
|
@@ -20,7 +18,6 @@ export default class ClarityServer extends EventEmitter {
|
|
|
20
18
|
this.defaultUri = uri
|
|
21
19
|
this.baseNamespace = `/v1/${this.baseAssetName}`
|
|
22
20
|
this.sockets = Object.create(null)
|
|
23
|
-
this.id = 0
|
|
24
21
|
}
|
|
25
22
|
|
|
26
23
|
setURI(uri) {
|
|
@@ -246,116 +243,6 @@ export default class ClarityServer extends EventEmitter {
|
|
|
246
243
|
return code.length > 2
|
|
247
244
|
}
|
|
248
245
|
|
|
249
|
-
buildRequest({ method, params = [] }) {
|
|
250
|
-
return { jsonrpc: '2.0', id: this.id++, method, params }
|
|
251
|
-
}
|
|
252
|
-
|
|
253
|
-
balanceOfRequest(address, tokenAddress, tag = 'latest') {
|
|
254
|
-
const contract = SolidityContract.simpleErc20(tokenAddress)
|
|
255
|
-
const callData = contract.balanceOf.build(address)
|
|
256
|
-
const data = {
|
|
257
|
-
data: bufferToHex(callData),
|
|
258
|
-
to: tokenAddress,
|
|
259
|
-
}
|
|
260
|
-
return this.ethCallRequest(data, tag)
|
|
261
|
-
}
|
|
262
|
-
|
|
263
|
-
getBalanceRequest(address, tag = 'latest') {
|
|
264
|
-
return this.buildRequest({ method: 'eth_getBalance', params: [address, tag] })
|
|
265
|
-
}
|
|
266
|
-
|
|
267
|
-
gasPriceRequest() {
|
|
268
|
-
return this.buildRequest({ method: 'eth_gasPrice' })
|
|
269
|
-
}
|
|
270
|
-
|
|
271
|
-
estimateGasRequest(data, tag = 'latest') {
|
|
272
|
-
return this.buildRequest({ method: 'eth_estimateGas', params: [data, tag] })
|
|
273
|
-
}
|
|
274
|
-
|
|
275
|
-
sendRawTransactionRequest(data) {
|
|
276
|
-
const _data = data instanceof Uint8Array ? Buffer.from(data).toString('hex') : data
|
|
277
|
-
const hex = _data.startsWith('0x') ? _data : '0x' + _data
|
|
278
|
-
return this.buildRequest({ method: 'eth_sendRawTransaction', params: [hex] })
|
|
279
|
-
}
|
|
280
|
-
|
|
281
|
-
coinbaseRequest() {
|
|
282
|
-
return this.buildRequest({ method: 'eth_coinbase' })
|
|
283
|
-
}
|
|
284
|
-
|
|
285
|
-
getCodeRequest(address, tag = 'latest') {
|
|
286
|
-
return this.buildRequest({ method: 'eth_getCode', params: [address, tag] })
|
|
287
|
-
}
|
|
288
|
-
|
|
289
|
-
getStorageAtRequest(address, position, tag = 'latest') {
|
|
290
|
-
return this.buildRequest({ method: 'eth_getStorageAt', params: [address, position, tag] })
|
|
291
|
-
}
|
|
292
|
-
|
|
293
|
-
getTransactionCountRequest(address, tag = 'latest') {
|
|
294
|
-
return this.buildRequest({ method: 'eth_getTransactionCount', params: [address, tag] })
|
|
295
|
-
}
|
|
296
|
-
|
|
297
|
-
getTransactionByHashRequest(hash) {
|
|
298
|
-
return this.buildRequest({ method: 'eth_getTransactionByHash', params: [hash] })
|
|
299
|
-
}
|
|
300
|
-
|
|
301
|
-
getTransactionReceiptRequest(txhash) {
|
|
302
|
-
return this.buildRequest({ method: 'eth_getTransactionReceipt', params: [txhash] })
|
|
303
|
-
}
|
|
304
|
-
|
|
305
|
-
ethCallRequest(data, tag = 'latest') {
|
|
306
|
-
return this.buildRequest({ method: 'eth_call', params: [data, tag] })
|
|
307
|
-
}
|
|
308
|
-
|
|
309
|
-
blockNumberRequest() {
|
|
310
|
-
return this.buildRequest({ method: 'eth_blockNumber' })
|
|
311
|
-
}
|
|
312
|
-
|
|
313
|
-
getLogsRequest(object) {
|
|
314
|
-
return this.buildRequest({ method: 'eth_getLogs', params: [object] })
|
|
315
|
-
}
|
|
316
|
-
|
|
317
|
-
getBlockByNumberRequest(numberHex, isFullTxs = false) {
|
|
318
|
-
return this.buildRequest({ method: 'eth_getBlockByNumber', params: [numberHex, isFullTxs] })
|
|
319
|
-
}
|
|
320
|
-
|
|
321
|
-
getBlockByHashRequest(blockHash, isFullTxs = false) {
|
|
322
|
-
return this.buildRequest({ method: 'eth_getBlockByHash', params: [blockHash, isFullTxs] })
|
|
323
|
-
}
|
|
324
|
-
|
|
325
|
-
getBlockTransactionCountByHashRequest(blockHash) {
|
|
326
|
-
return this.buildRequest({ method: 'eth_getBlockTransactionCountByHash', params: [blockHash] })
|
|
327
|
-
}
|
|
328
|
-
|
|
329
|
-
getBlockTransactionCountByNumberRequest(quantityOrTag) {
|
|
330
|
-
return this.buildRequest({
|
|
331
|
-
method: 'eth_getBlockTransactionCountByNumber',
|
|
332
|
-
params: [quantityOrTag],
|
|
333
|
-
})
|
|
334
|
-
}
|
|
335
|
-
|
|
336
|
-
getCompilersRequest() {
|
|
337
|
-
return this.buildRequest({ method: 'eth_getCompilers' })
|
|
338
|
-
}
|
|
339
|
-
|
|
340
|
-
simulateV1Request(...params) {
|
|
341
|
-
return this.buildRequest({
|
|
342
|
-
method: 'eth_simulateV1',
|
|
343
|
-
params,
|
|
344
|
-
})
|
|
345
|
-
}
|
|
346
|
-
|
|
347
|
-
getNetVersionRequest() {
|
|
348
|
-
return this.buildRequest({ method: 'net_version' })
|
|
349
|
-
}
|
|
350
|
-
|
|
351
|
-
simulateRawTransactionRequest(rawTx, applyPending = true) {
|
|
352
|
-
const replaced = rawTx.replace('0x', '')
|
|
353
|
-
return this.buildRequest({
|
|
354
|
-
method: 'debug_simulateRawTransaction',
|
|
355
|
-
params: [replaced, applyPending],
|
|
356
|
-
})
|
|
357
|
-
}
|
|
358
|
-
|
|
359
246
|
// Transport: Via sendRequest → WS first → HTTP fallback in ClarityServerV2
|
|
360
247
|
async proxyToCoinNode(params) {
|
|
361
248
|
const request = this.buildRequest(params)
|