@exodus/ethereum-api 9.0.0-alpha.2 → 9.0.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 +2121 -0
- package/LICENSE +7 -0
- package/README.md +124 -0
- package/package.json +34 -33
- package/src/address-has-history.js +6 -2
- package/src/allowance/constants.js +4 -5
- package/src/allowance/index.js +159 -40
- package/src/check-tx/create-assess-transaction.js +23 -0
- package/src/check-tx/create-check-tx.js +104 -0
- package/src/check-tx/index.js +1 -0
- package/src/create-asset-plugin-factory.js +115 -0
- package/src/create-asset-utils.js +520 -0
- package/src/create-asset.js +272 -97
- package/src/create-token-factory.js +47 -17
- package/src/custom-fees.js +57 -0
- package/src/eip7702-whitelist.js +62 -0
- package/src/ens/index.js +12 -17
- package/src/error-wrapper.js +383 -0
- package/src/eth-like-util.js +172 -33
- package/src/exodus-eth-server/api-coin-nodes.js +27 -196
- package/src/exodus-eth-server/api.js +23 -14
- package/src/exodus-eth-server/clarity-v2.js +255 -0
- package/src/exodus-eth-server/clarity.js +67 -265
- package/src/exodus-eth-server/errors.js +40 -0
- package/src/exodus-eth-server/eth-like-server.js +385 -0
- package/src/exodus-eth-server/fetch-json.js +48 -0
- package/src/exodus-eth-server/index.js +77 -0
- package/src/exodus-eth-server/utils.js +31 -0
- package/src/exodus-eth-server/ws-gateway.js +306 -0
- package/src/exodus-eth-server/ws.js +1 -2
- package/src/fee-data/EthLikeFeeData.js +81 -0
- package/src/fee-data/index.js +54 -0
- package/src/fee-utils.js +229 -0
- package/src/gas-estimation.js +183 -66
- package/src/get-balance-for-address.js +1 -1
- package/src/get-balances.js +285 -27
- package/src/get-fee.js +92 -56
- package/src/get-historical-balance.js +58 -0
- package/src/hooks/index.js +1 -1
- package/src/hooks/monitor.js +182 -43
- package/src/hooks/staking-freshness.js +58 -0
- package/src/index.js +118 -12
- package/src/move-funds.js +153 -0
- package/src/multicall3/index.js +212 -0
- package/src/multisig-address.js +146 -0
- package/src/nft-utils.js +60 -0
- package/src/number-utils.js +34 -2
- package/src/optimism-gas/index.js +13 -5
- package/src/send-validations.js +58 -0
- package/src/server-based-fee-monitor.js +12 -19
- package/src/simulate-tx/fetch-tx-preview.js +0 -2
- package/src/simulate-tx/index.js +2 -2
- package/src/simulate-tx/simulate-eth-tx.js +24 -11
- package/src/simulation/common.js +34 -0
- package/src/simulation/create-simulate-message.js +49 -0
- package/src/simulation/create-simulate-transactions.js +108 -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 +266 -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/api/index.js +43 -0
- package/src/staking/cached-read.js +33 -0
- package/src/staking/ethereum/api.js +166 -34
- package/src/staking/ethereum/deps.js +37 -0
- package/src/staking/ethereum/everstake.js +113 -0
- package/src/staking/ethereum/index.js +4 -3
- package/src/staking/ethereum/service.js +682 -181
- package/src/staking/ethereum/staking-utils.js +275 -4
- package/src/staking/fantom-staking.js +9 -9
- package/src/staking/index.js +4 -4
- package/src/staking/matic/addresses.js +2 -0
- package/src/staking/matic/api.js +69 -49
- package/src/staking/matic/index.js +3 -3
- package/src/staking/matic/matic-staking-utils.js +243 -5
- package/src/staking/matic/matic-staking.md +53 -0
- package/src/staking/staking-provider-client.js +1 -1
- package/src/staking/utils/index.js +39 -0
- package/src/tx-create.js +666 -0
- package/src/tx-log/clarity-monitor.js +273 -103
- package/src/tx-log/clarity-truncated-history-monitor.js +109 -0
- package/src/tx-log/clarity-truncated-history-monitor.md +219 -0
- package/src/tx-log/clarity-utils/absolute.js +96 -0
- package/src/tx-log/clarity-utils/get-log-items-from-server-tx.js +94 -31
- package/src/tx-log/clarity-utils/get-names-of-tokens-transferred-by-server-tx.js +3 -2
- package/src/tx-log/clarity-utils/index.js +9 -3
- package/src/tx-log/clarity-utils/is-spam-pending-tx.js +13 -0
- package/src/tx-log/clarity-utils/normalize-transactions-response.js +65 -0
- package/src/tx-log/ethereum-monitor.js +93 -47
- package/src/tx-log/ethereum-no-history-monitor.js +52 -41
- package/src/tx-log/get-optimistic-txlog-effects.js +379 -0
- package/src/tx-log/index.js +5 -3
- package/src/tx-log/monitor-utils/check-pending-transactions.js +14 -5
- package/src/tx-log/monitor-utils/exclude-unchanged-token-balances.js +1 -1
- package/src/tx-log/monitor-utils/extract-balance-from-clarity-account-info.js +108 -0
- package/src/tx-log/monitor-utils/get-balance-updates.js +44 -0
- package/src/tx-log/monitor-utils/get-batched-rpc-balances.js +47 -0
- package/src/tx-log/monitor-utils/get-current-blacklist-status.js +38 -0
- package/src/tx-log/monitor-utils/get-current-eip7702-delegation.js +79 -0
- package/src/tx-log/monitor-utils/get-derive-transactions-to-check.js +10 -5
- package/src/tx-log/monitor-utils/get-fee-amount.js +1 -1
- package/src/tx-log/monitor-utils/get-history-from-server.js +1 -1
- package/src/tx-log/monitor-utils/get-log-items-from-server-tx.js +75 -33
- package/src/tx-log/monitor-utils/get-names-of-tokens-transferred-by-server-tx.js +3 -2
- package/src/tx-log/monitor-utils/get-raw-transfer-amount.js +29 -0
- package/src/tx-log/monitor-utils/get-transfers-by-token-name.js +3 -2
- package/src/tx-log/monitor-utils/get-value-of-transfers.js +10 -12
- package/src/tx-log/monitor-utils/index.js +12 -7
- package/src/tx-log/monitor-utils/is-unverifiable-pending-tx-expired.js +19 -0
- package/src/tx-log/monitor-utils/verify-pending-tx-status-rpc.js +90 -0
- package/src/tx-log-staking-processor/asset-staking-tx-data.js +82 -24
- package/src/tx-log-staking-processor/index.js +28 -14
- package/src/tx-log-staking-processor/utils.js +21 -20
- package/src/tx-send/broadcast-error-handler.js +63 -0
- package/src/tx-send/index.js +1 -2
- package/src/tx-send/nonce-utils.js +97 -0
- package/src/tx-send/tx-send.js +94 -288
- package/src/tx-sign/index.js +17 -0
- package/src/tx-type/index.js +280 -0
- package/src/watch-tx.js +36 -0
- package/src/web3/createSimulateMessage.js +3 -2
- package/src/web3/createSimulateTransactions.js +11 -0
- package/src/web3/index.js +3 -1
- package/src/etherscan/account.js +0 -49
- package/src/etherscan/index.js +0 -28
- package/src/etherscan/logs.js +0 -16
- package/src/etherscan/proxy.js +0 -47
- package/src/etherscan/request.js +0 -26
- package/src/etherscan/ws.js +0 -88
- package/src/exodus-eth-server/server-factory.js +0 -28
- package/src/exodus-eth-server/server-map.js +0 -21
- package/src/get-fee-async.js +0 -97
- package/src/staking/matic/service.js +0 -437
- package/src/staking-api.js +0 -5
- package/src/tx-log/clarity-utils/get-derive-data-needed-for-tick.js +0 -12
- package/src/tx-log-staking-processor/get-asset-tx-amount.js +0 -26
- package/src/tx-send/get-fee-info.js +0 -41
- package/src/websocket/index.android.js +0 -2
- package/src/websocket/index.ios.js +0 -2
- package/src/websocket/index.js +0 -21
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import { safeString } from '@exodus/safe-string'
|
|
2
|
+
|
|
3
|
+
// Transplated from: https://github.com/ethereum/go-ethereum/blob/master/core/txpool/errors.go.
|
|
4
|
+
const TxPoolErrors = {
|
|
5
|
+
ALREADY_KNOWN: safeString`already known`,
|
|
6
|
+
INVALID_SENDER: safeString`invalid sender`,
|
|
7
|
+
UNDERPRICED: safeString`transaction underpriced`,
|
|
8
|
+
REPLACE_UNDERPRICED: safeString`replacement transaction underpriced`,
|
|
9
|
+
TX_GAS_PRICE_TOO_LOW: safeString`transaction gas price below minimum`,
|
|
10
|
+
ACCOUNT_LIMIT_EXCEEDED: safeString`account limit exceeded`,
|
|
11
|
+
GAS_LIMIT: safeString`exceeds block gas limit`,
|
|
12
|
+
NEGATIVE_VALUE: safeString`negative value`,
|
|
13
|
+
OVERSIZED_DATA: safeString`oversized data`,
|
|
14
|
+
TX_BLOB_LIMIT_EXCEEDED: safeString`transaction blob limit exceeded`,
|
|
15
|
+
ALREADY_RESERVED: safeString`address already reserved`,
|
|
16
|
+
INFLIGHT_TX_LIMIT_REACHED: safeString`in-flight transaction limit reached for delegated accounts`,
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
const DYNAMIC_RPC_ERRORS = {
|
|
20
|
+
TRANSACTION_TYPE_NOT_SUPPORTED: safeString`transaction type not supported`,
|
|
21
|
+
ONLY_REPLAY_PROTECTED_TRANSACTIONS: safeString`only replay-protected (EIP-155) transactions allowed over RPC`,
|
|
22
|
+
TRANSACTION_GAS_PRICE_BELOW_MINIMUM: safeString`transaction gas price below minimum`,
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
const SERVO_ERRORS = {
|
|
26
|
+
EMPTY_BUNDLE: safeString`numTxs=0: invalid num txs`,
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export const KNOWN_RPC_ERRORS = { ...TxPoolErrors, ...DYNAMIC_RPC_ERRORS, ...SERVO_ERRORS }
|
|
30
|
+
|
|
31
|
+
export const tryMappingToRpcErrors = (errorMessage) => {
|
|
32
|
+
if (typeof errorMessage !== 'string') return
|
|
33
|
+
|
|
34
|
+
return Object.values(KNOWN_RPC_ERRORS)
|
|
35
|
+
.sort((a, b) => b.length - a.length)
|
|
36
|
+
.find((knownRpcError) => errorMessage.includes(knownRpcError))
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
export const errorMessageToSafeHint = (messageOrCode) =>
|
|
40
|
+
tryMappingToRpcErrors(messageOrCode) || messageOrCode
|
|
@@ -0,0 +1,385 @@
|
|
|
1
|
+
import { bufferToHex } from '@exodus/ethereumjs/util'
|
|
2
|
+
import SolidityContract from '@exodus/solidity-contract'
|
|
3
|
+
import EventEmitter from 'events/events.js'
|
|
4
|
+
import assert from 'minimalistic-assert'
|
|
5
|
+
|
|
6
|
+
import { fromHexToString } from '../number-utils.js'
|
|
7
|
+
import { getFallbackGasPriceEstimation } from './utils.js'
|
|
8
|
+
|
|
9
|
+
export class EthLikeServerBase extends EventEmitter {
|
|
10
|
+
id = 0
|
|
11
|
+
|
|
12
|
+
buildRequest({ method, params = [] }) {
|
|
13
|
+
return { jsonrpc: '2.0', id: this.id++, method, params }
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
balanceOfRequest(address, tokenAddress, tag = 'latest') {
|
|
17
|
+
const contract = SolidityContract.simpleErc20(tokenAddress)
|
|
18
|
+
const callData = contract.balanceOf.build(address)
|
|
19
|
+
const data = {
|
|
20
|
+
data: bufferToHex(callData),
|
|
21
|
+
to: tokenAddress,
|
|
22
|
+
}
|
|
23
|
+
return this.ethCallRequest(data, tag)
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
getBalanceRequest(address, tag = 'latest') {
|
|
27
|
+
return this.buildRequest({ method: 'eth_getBalance', params: [address, tag] })
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
gasPriceRequest() {
|
|
31
|
+
return this.buildRequest({ method: 'eth_gasPrice' })
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
estimateGasRequest(data, tag = 'latest') {
|
|
35
|
+
return this.buildRequest({ method: 'eth_estimateGas', params: [data, tag] })
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
sendRawTransactionRequest(data) {
|
|
39
|
+
const _data = data instanceof Uint8Array ? Buffer.from(data).toString('hex') : data
|
|
40
|
+
const hex = _data.startsWith('0x') ? _data : '0x' + _data
|
|
41
|
+
return this.buildRequest({ method: 'eth_sendRawTransaction', params: [hex] })
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
// @deprecated
|
|
45
|
+
coinbaseRequest() {
|
|
46
|
+
return this.buildRequest({ method: 'eth_coinbase' })
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
getCodeRequest(address, tag = 'latest') {
|
|
50
|
+
return this.buildRequest({ method: 'eth_getCode', params: [address, tag] })
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
getStorageAtRequest(address, position, tag = 'latest') {
|
|
54
|
+
return this.buildRequest({ method: 'eth_getStorageAt', params: [address, position, tag] })
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
getTransactionCountRequest(address, tag = 'latest') {
|
|
58
|
+
return this.buildRequest({ method: 'eth_getTransactionCount', params: [address, tag] })
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
getTransactionByHashRequest(hash) {
|
|
62
|
+
return this.buildRequest({ method: 'eth_getTransactionByHash', params: [hash] })
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
getTransactionReceiptRequest(txhash) {
|
|
66
|
+
return this.buildRequest({ method: 'eth_getTransactionReceipt', params: [txhash] })
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
ethCallRequest(data, tag = 'latest') {
|
|
70
|
+
return this.buildRequest({ method: 'eth_call', params: [data, tag] })
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
blockNumberRequest() {
|
|
74
|
+
return this.buildRequest({ method: 'eth_blockNumber' })
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
getLogsRequest(object) {
|
|
78
|
+
return this.buildRequest({ method: 'eth_getLogs', params: [object] })
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
getBlockByNumberRequest(numberHex, isFullTxs = false) {
|
|
82
|
+
return this.buildRequest({ method: 'eth_getBlockByNumber', params: [numberHex, isFullTxs] })
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
getBlockByHashRequest(blockHash, isFullTxs = false) {
|
|
86
|
+
return this.buildRequest({ method: 'eth_getBlockByHash', params: [blockHash, isFullTxs] })
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
getBlockTransactionCountByHashRequest(blockHash) {
|
|
90
|
+
return this.buildRequest({ method: 'eth_getBlockTransactionCountByHash', params: [blockHash] })
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
getBlockTransactionCountByNumberRequest(quantityOrTag) {
|
|
94
|
+
return this.buildRequest({
|
|
95
|
+
method: 'eth_getBlockTransactionCountByNumber',
|
|
96
|
+
params: [quantityOrTag],
|
|
97
|
+
})
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
// @deprectated
|
|
101
|
+
getCompilersRequest() {
|
|
102
|
+
return this.buildRequest({ method: 'eth_getCompilers' })
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
// TODO: Expose on Clarity
|
|
106
|
+
// maxPriorityFeePerGasRequest() {
|
|
107
|
+
// return this.buildRequest({ method: 'eth_maxPriorityFeePerGas' })
|
|
108
|
+
// }
|
|
109
|
+
|
|
110
|
+
// TODO: Expose on Clarity
|
|
111
|
+
// feeHistoryRequest(blockCount, newestBlock, rewardPercentiles) {
|
|
112
|
+
// return this.buildRequest({
|
|
113
|
+
// method: 'eth_feeHistory',
|
|
114
|
+
// params: [blockCount, newestBlock, rewardPercentiles],
|
|
115
|
+
// })
|
|
116
|
+
// }
|
|
117
|
+
|
|
118
|
+
simulateV1Request(...params) {
|
|
119
|
+
return this.buildRequest({
|
|
120
|
+
method: 'eth_simulateV1',
|
|
121
|
+
params,
|
|
122
|
+
})
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
getNetVersionRequest() {
|
|
126
|
+
return this.buildRequest({ method: 'net_version' })
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
// @deprecated
|
|
130
|
+
simulateRawTransactionRequest(rawTx, applyPending = true) {
|
|
131
|
+
const replaced = rawTx.replace('0x', '')
|
|
132
|
+
return this.buildRequest({
|
|
133
|
+
method: 'debug_simulateRawTransaction',
|
|
134
|
+
params: [replaced, applyPending],
|
|
135
|
+
})
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
contractStaticCallFromRequest(contract, functionName, from, ...args) {
|
|
139
|
+
assert(contract instanceof SolidityContract, 'expected SolidityContract contract')
|
|
140
|
+
assert(typeof functionName === 'string', 'expected string functionName')
|
|
141
|
+
|
|
142
|
+
const requestProps = {
|
|
143
|
+
to: contract.address,
|
|
144
|
+
data: bufferToHex(contract[functionName].build(...args)),
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
if (from) requestProps.from = from
|
|
148
|
+
|
|
149
|
+
return this.ethCallRequest(requestProps)
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
sendBundleRequest({ txs }) {
|
|
153
|
+
assert(Array.isArray(txs), 'expected array txs')
|
|
154
|
+
return this.buildRequest({ method: 'eth_sendBundle', params: [{ txs }] })
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
const __MISSING_FUNCTION_IMPLEMENTATION__ = () => assert(false)
|
|
159
|
+
|
|
160
|
+
export class EthLikeServer extends EthLikeServerBase {
|
|
161
|
+
/**
|
|
162
|
+
* Validates a JSON-RPC batch response, returning each `result` ordered to
|
|
163
|
+
* match `batch` (correlated by `id`, since responses can arrive out of order).
|
|
164
|
+
*
|
|
165
|
+
* @param {Array<{ id: number|string }>} args.batch - Requests sent, in the order results are expected.
|
|
166
|
+
* @param {unknown} args.responses - Raw transport payload; expected `[{ id, result }]` but treated as untrusted (gateways may return `{ message }` on error).
|
|
167
|
+
* @param {string} args.errorMessage - Thrown-error prefix per transport (e.g. `'Bad rpc batch response'`, `'Bad Response'`).
|
|
168
|
+
* @param {boolean} [args.tolerant] - When true, a request without a valid entry (JSON-RPC error entry, non-numeric `id`, duplicated or out-of-batch `id`, missing from a short response) maps to `undefined` instead of failing the whole batch.
|
|
169
|
+
* @returns {unknown[]} Each request's `result`, ordered to match `batch`. Entries with a duplicated or out-of-batch `id` are discarded in both modes, so an affected request maps to `undefined` rather than another request's result.
|
|
170
|
+
* @throws {Error} When `responses` isn't an array; in strict mode also when length mismatches `batch` or any entry lacks a numeric `id` / defined `result`.
|
|
171
|
+
*/
|
|
172
|
+
parseBatchResponse({ batch, responses, errorMessage, tolerant = false }) {
|
|
173
|
+
// Gateways can respond with a JSON object (e.g. `{ message }`) instead of a batch array.
|
|
174
|
+
if (!Array.isArray(responses)) {
|
|
175
|
+
// Untrusted server text: keep only a short string preview, developer-facing (no `hint`).
|
|
176
|
+
const message = [responses?.message, responses?.error?.message].find(
|
|
177
|
+
(value) => typeof value === 'string'
|
|
178
|
+
)
|
|
179
|
+
const preview = message ? `: ${message.slice(0, 100)}` : ''
|
|
180
|
+
throw new Error(`${errorMessage}${preview}`)
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
const isValidEntry = (response) => !isNaN(response?.id) && response?.result !== undefined
|
|
184
|
+
|
|
185
|
+
if (!tolerant && (responses.length !== batch.length || !responses.every(isValidEntry))) {
|
|
186
|
+
throw new Error(errorMessage)
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
// Safe against prototype keys: only entries with a validated numeric `id` are kept.
|
|
190
|
+
const keyed = Object.fromEntries(
|
|
191
|
+
responses
|
|
192
|
+
.filter(isValidEntry)
|
|
193
|
+
.filter(({ id }, _i, all) => {
|
|
194
|
+
const key = `${id}`
|
|
195
|
+
const inBatch = batch.some((request) => `${request.id}` === key)
|
|
196
|
+
const unique = all.filter(({ id: other }) => `${other}` === key).length === 1
|
|
197
|
+
return inBatch && unique
|
|
198
|
+
})
|
|
199
|
+
.map((response) => [`${response.id}`, response.result])
|
|
200
|
+
)
|
|
201
|
+
return batch.map((request) => keyed[`${request.id}`])
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
async getCode(...params) {
|
|
205
|
+
const request = this.getCodeRequest(...params)
|
|
206
|
+
return this.sendRequest(request)
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
async estimateGas(...params) {
|
|
210
|
+
const request = this.estimateGasRequest(...params)
|
|
211
|
+
return this.sendRequest(request)
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
async sendRawTransaction(...params) {
|
|
215
|
+
const request = this.sendRawTransactionRequest(...params)
|
|
216
|
+
return this.sendRequest(request)
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
async getStorageAt(...params) {
|
|
220
|
+
const request = this.getStorageAtRequest(...params)
|
|
221
|
+
return this.sendRequest(request)
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
async getTransactionCount(...params) {
|
|
225
|
+
const request = this.getTransactionCountRequest(...params)
|
|
226
|
+
return this.sendRequest(request)
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
async getTransactionByHash(...params) {
|
|
230
|
+
const request = this.getTransactionByHashRequest(...params)
|
|
231
|
+
return this.sendRequest(request)
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
async getTransactionReceipt(...params) {
|
|
235
|
+
const request = this.getTransactionReceiptRequest(...params)
|
|
236
|
+
return this.sendRequest(request)
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
async ethCall(...params) {
|
|
240
|
+
const request = this.ethCallRequest(...params)
|
|
241
|
+
return this.sendRequest(request)
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
async blockNumber(...params) {
|
|
245
|
+
const request = this.blockNumberRequest(...params)
|
|
246
|
+
return this.sendRequest(request)
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
async getBlockByNumber(...params) {
|
|
250
|
+
const request = this.getBlockByNumberRequest(...params)
|
|
251
|
+
return this.sendRequest(request)
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
async isContract(address) {
|
|
255
|
+
const code = await this.getCode(address)
|
|
256
|
+
return code.length > 2
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
// @deprecated
|
|
260
|
+
async simulateRawTransaction(...params) {
|
|
261
|
+
const request = this.simulateRawTransactionRequest(...params)
|
|
262
|
+
return this.sendRequest(request)
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
async getBaseFeePerGas() {
|
|
266
|
+
const response = await this.getLatestBlock()
|
|
267
|
+
if (response.baseFeePerGas) {
|
|
268
|
+
return fromHexToString(response.baseFeePerGas)
|
|
269
|
+
}
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
async getLatestBlock() {
|
|
273
|
+
return this.getBlockByNumber('latest')
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
async gasPrice(...params) {
|
|
277
|
+
const request = this.gasPriceRequest(...params)
|
|
278
|
+
return this.sendRequest(request)
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
// for fee monitor
|
|
282
|
+
async getGasPrice() {
|
|
283
|
+
return this.gasPrice()
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
async getBalance(...params) {
|
|
287
|
+
const request = this.getBalanceRequest(...params)
|
|
288
|
+
return this.sendRequest(request)
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
async getBalanceProxied(...params) {
|
|
292
|
+
return this.getBalance(...params)
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
async getBlockByHash(...params) {
|
|
296
|
+
const request = this.getBlockByHashRequest(...params)
|
|
297
|
+
return this.sendRequest(request)
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
async getBlockTransactionCountByNumber(...params) {
|
|
301
|
+
const request = this.getBlockTransactionCountByNumberRequest(...params)
|
|
302
|
+
return this.sendRequest(request)
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
async getBlockTransactionCountByHash(...params) {
|
|
306
|
+
const request = this.getBlockTransactionCountByHashRequest(...params)
|
|
307
|
+
return this.sendRequest(request)
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
async simulateV1(...params) {
|
|
311
|
+
const request = this.simulateV1Request(...params)
|
|
312
|
+
return this.sendRequest(request)
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
// @deprecated
|
|
316
|
+
async getCoinbase() {
|
|
317
|
+
const request = this.coinbaseRequest()
|
|
318
|
+
return this.sendRequest(request)
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
// @deprecated
|
|
322
|
+
async getCompilers() {
|
|
323
|
+
const request = this.getCompilersRequest()
|
|
324
|
+
return this.sendRequest(request)
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
// TODO: Expose on Clarity
|
|
328
|
+
// async getMaxPriorityFeePerGas() {
|
|
329
|
+
// const request = this.maxPriorityFeePerGasRequest()
|
|
330
|
+
// return this.sendRequest(request)
|
|
331
|
+
// }
|
|
332
|
+
|
|
333
|
+
// TODO: Expose on Clarity
|
|
334
|
+
// async getFeeHistory(...params) {
|
|
335
|
+
// const request = this.feeHistoryRequest(...params)
|
|
336
|
+
// return this.sendRequest(request)
|
|
337
|
+
// }
|
|
338
|
+
|
|
339
|
+
async getNetVersion() {
|
|
340
|
+
const request = this.getNetVersionRequest()
|
|
341
|
+
return this.sendRequest(request)
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
async getLogs(...params) {
|
|
345
|
+
const request = this.getLogsRequest(...params)
|
|
346
|
+
return this.sendRequest(request)
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
async proxyToCoinNode(params) {
|
|
350
|
+
const request = this.buildRequest(params)
|
|
351
|
+
return this.sendRequest(request)
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
async getGasPriceEstimation() {
|
|
355
|
+
return getFallbackGasPriceEstimation({ server: this })
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
async balanceOf(address, tokenAddress, tag = 'latest') {
|
|
359
|
+
const request = this.balanceOfRequest(address, tokenAddress, tag)
|
|
360
|
+
const result = await this.sendRequest(request)
|
|
361
|
+
const balance = fromHexToString(result)
|
|
362
|
+
return {
|
|
363
|
+
confirmed: {
|
|
364
|
+
[tokenAddress]: balance,
|
|
365
|
+
},
|
|
366
|
+
}
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
async contractStaticCallFrom(contract, functionName, ...params) {
|
|
370
|
+
const request = this.contractStaticCallFromRequest(contract, functionName, ...params)
|
|
371
|
+
return contract[functionName].parse(await this.sendRequest(request))
|
|
372
|
+
}
|
|
373
|
+
|
|
374
|
+
async contractStaticCall(contract, functionName, ...params) {
|
|
375
|
+
return this.contractStaticCallFrom(contract, functionName, undefined, ...params)
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
sendRequest() {
|
|
379
|
+
return __MISSING_FUNCTION_IMPLEMENTATION__()
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
sendBatchRequest() {
|
|
383
|
+
return __MISSING_FUNCTION_IMPLEMENTATION__()
|
|
384
|
+
}
|
|
385
|
+
}
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import { retry } from '@exodus/simple-retry'
|
|
2
|
+
import { TraceId } from '@exodus/traceparent'
|
|
3
|
+
|
|
4
|
+
const getTextFromResponse = async (response) => {
|
|
5
|
+
try {
|
|
6
|
+
const responseBody = await response.text()
|
|
7
|
+
return responseBody.slice(0, 100)
|
|
8
|
+
} catch {
|
|
9
|
+
return ''
|
|
10
|
+
}
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export const fetchJson = async (url, fetchOptions) => {
|
|
14
|
+
const response = await fetch(url, fetchOptions)
|
|
15
|
+
|
|
16
|
+
if (!response.ok) {
|
|
17
|
+
const traceId = TraceId.fromResponse(response)
|
|
18
|
+
const error = new Error(
|
|
19
|
+
`${url} returned ${response.status}: ${
|
|
20
|
+
response.statusText || 'Unknown Status Text'
|
|
21
|
+
}. Body: ${await getTextFromResponse(response)}`
|
|
22
|
+
)
|
|
23
|
+
if (traceId) {
|
|
24
|
+
error.traceId = traceId
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
throw error
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
const json = await response.json()
|
|
31
|
+
|
|
32
|
+
// Only capture trace ID if there's an RPC error in the response
|
|
33
|
+
// (consumers extract it via __traceId when throwing the error)
|
|
34
|
+
if (json.error) {
|
|
35
|
+
const traceId = TraceId.fromResponse(response)
|
|
36
|
+
if (traceId) {
|
|
37
|
+
json.__traceId = traceId
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
return json
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
export const fetchJsonRetry = (url, fetchOptions) => {
|
|
45
|
+
const waitTimes = ['3s']
|
|
46
|
+
const fetchWithRetry = retry(fetchJson, { delayTimesMs: waitTimes })
|
|
47
|
+
return fetchWithRetry(url, fetchOptions)
|
|
48
|
+
}
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
import assert from 'minimalistic-assert'
|
|
2
|
+
|
|
3
|
+
import { create } from './api.js'
|
|
4
|
+
import ApiCoinNodesServer from './api-coin-nodes.js'
|
|
5
|
+
import ClarityServer from './clarity.js'
|
|
6
|
+
import ClarityServerV2 from './clarity-v2.js'
|
|
7
|
+
import WsGateway from './ws-gateway.js'
|
|
8
|
+
|
|
9
|
+
export const ValidMonitorTypes = [
|
|
10
|
+
'no-history',
|
|
11
|
+
'clarity',
|
|
12
|
+
'clarity-v2',
|
|
13
|
+
'clarity-truncated-history',
|
|
14
|
+
'clarity-v3',
|
|
15
|
+
'magnifier',
|
|
16
|
+
]
|
|
17
|
+
|
|
18
|
+
export function createEvmServer({ assetName, serverUrl, monitorType }) {
|
|
19
|
+
assert(assetName, 'assetName is required')
|
|
20
|
+
assert(serverUrl, 'serverUrl is required')
|
|
21
|
+
assert(monitorType, 'monitorType is required')
|
|
22
|
+
|
|
23
|
+
// Truncated-history uses the same Clarity v2 HTTP/WS server; only the monitor class differs.
|
|
24
|
+
switch (monitorType) {
|
|
25
|
+
case 'no-history':
|
|
26
|
+
return new ApiCoinNodesServer({ baseAssetName: assetName, uri: serverUrl })
|
|
27
|
+
case 'clarity':
|
|
28
|
+
return new ClarityServer({ baseAssetName: assetName, uri: serverUrl })
|
|
29
|
+
case 'clarity-v2':
|
|
30
|
+
case 'clarity-truncated-history':
|
|
31
|
+
case 'clarity-v3':
|
|
32
|
+
return new ClarityServerV2({ baseAssetName: assetName, uri: serverUrl })
|
|
33
|
+
case 'magnifier':
|
|
34
|
+
return create(serverUrl, assetName)
|
|
35
|
+
default:
|
|
36
|
+
throw new Error(`No server API found for ${assetName} and type ${monitorType}`)
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
const gatewayUriToWsGateway = Object.create(null)
|
|
41
|
+
|
|
42
|
+
export const createWsGateway = ({ uri } = Object.create(null)) => {
|
|
43
|
+
assert(typeof uri === 'string', 'expected string uri')
|
|
44
|
+
|
|
45
|
+
uri = new URL(uri).toString()
|
|
46
|
+
|
|
47
|
+
const wsGateway = gatewayUriToWsGateway[uri]
|
|
48
|
+
if (wsGateway) return wsGateway
|
|
49
|
+
|
|
50
|
+
// NOTE: We disable the ability to `setServer` on the
|
|
51
|
+
// `WsGateway`, since we assume a mapping between
|
|
52
|
+
// `gatewayUri -> WsGateway`.
|
|
53
|
+
//
|
|
54
|
+
// This is a safe modification since a `WsGateway`
|
|
55
|
+
// monitor isn't rigged up to the same life cycle
|
|
56
|
+
// as other servers.
|
|
57
|
+
class FrozenWsGateway extends WsGateway {
|
|
58
|
+
constructor() {
|
|
59
|
+
super({ uri })
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
setServer() {
|
|
63
|
+
assert(false, 'setServer is disabled')
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
const frozenWsGateway = new FrozenWsGateway()
|
|
68
|
+
gatewayUriToWsGateway[uri] = frozenWsGateway
|
|
69
|
+
|
|
70
|
+
return frozenWsGateway
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
export function getServer(asset) {
|
|
74
|
+
const { name, server } = asset.baseAsset
|
|
75
|
+
assert(server, `${name} server is not set on asset.baseAsset.server`)
|
|
76
|
+
return server
|
|
77
|
+
}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import assert from 'minimalistic-assert'
|
|
2
|
+
|
|
3
|
+
const desc = (a, b) => (a > b ? -1 : b > a ? 1 : 0)
|
|
4
|
+
|
|
5
|
+
export const getFallbackGasPriceEstimation = async ({ server }) => {
|
|
6
|
+
const [latestBlock, gasPrice] = await Promise.all([server.getLatestBlock(), server.getGasPrice()])
|
|
7
|
+
|
|
8
|
+
assert(latestBlock, 'expected latestBlock')
|
|
9
|
+
assert(gasPrice, 'expected gasPrice')
|
|
10
|
+
|
|
11
|
+
const baseFeePerGas = latestBlock.baseFeePerGas
|
|
12
|
+
if (!baseFeePerGas) return { gasPrice }
|
|
13
|
+
|
|
14
|
+
const [max, min] = [BigInt(gasPrice), BigInt(baseFeePerGas)].sort(desc)
|
|
15
|
+
|
|
16
|
+
const toHex = (b) => `0x${b.toString(16)}`
|
|
17
|
+
|
|
18
|
+
// TODO: Use `eth_feeHistory` or `eth_maxPriorityFeePerGas`
|
|
19
|
+
// instead (requires allowlist at the RPC).
|
|
20
|
+
// HACK: Infer the RPC's implicit `tipGasPrice`:
|
|
21
|
+
// https://github.com/ethereum/go-ethereum/blob/d3dd48e59db28ea04bd92e4337cdd488ccb8fbec/internal/ethapi/api.go#L69C1-L79C2
|
|
22
|
+
const maxPriorityFeePerGas50Percentile = max - min
|
|
23
|
+
|
|
24
|
+
const rewardPercentiles = {
|
|
25
|
+
25: toHex(maxPriorityFeePerGas50Percentile / BigInt(2)),
|
|
26
|
+
50: toHex(maxPriorityFeePerGas50Percentile),
|
|
27
|
+
75: toHex((maxPriorityFeePerGas50Percentile * BigInt(3)) / BigInt(2)),
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
return { gasPrice, baseFeePerGas, rewardPercentiles }
|
|
31
|
+
}
|