@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
package/src/eth-like-util.js
CHANGED
|
@@ -1,12 +1,25 @@
|
|
|
1
|
-
import assert from 'minimalistic-assert'
|
|
2
|
-
import { normalizeTxId, isEthereumLikeAsset, isEthereumLikeToken, ABI } from '@exodus/ethereum-lib'
|
|
3
1
|
import { memoizeLruCache } from '@exodus/asset-lib'
|
|
2
|
+
import { ABI, isEthereumLikeAsset, isEthereumLikeToken, normalizeTxId } from '@exodus/ethereum-lib'
|
|
3
|
+
import { safeString } from '@exodus/safe-string'
|
|
4
4
|
import SolidityContract from '@exodus/solidity-contract'
|
|
5
|
-
import
|
|
6
|
-
|
|
5
|
+
import assert from 'minimalistic-assert'
|
|
6
|
+
|
|
7
|
+
import { EthLikeError, EVM_ERROR_REASONS, withErrorReason } from './error-wrapper.js'
|
|
8
|
+
import { getServer } from './exodus-eth-server/index.js'
|
|
9
|
+
import { fromHexToString } from './number-utils.js'
|
|
10
|
+
|
|
11
|
+
export const FORWARDER_CONTRACT_CODE =
|
|
12
|
+
'0x5836818037808036817364b29dc43e817817cf77468c8dda63d98ce08fb25af43d91908282803e602b57fd5bf3'
|
|
13
|
+
|
|
14
|
+
export const EIP7702_DELEGATION_DESIGNATOR = '0xef0100'
|
|
7
15
|
|
|
8
16
|
export async function isContractAddress({ asset, address }) {
|
|
9
|
-
return
|
|
17
|
+
return withErrorReason({
|
|
18
|
+
promise: getServer(asset).isContract(address),
|
|
19
|
+
errorReasonInfo: EVM_ERROR_REASONS.getCodeFailed,
|
|
20
|
+
hint: safeString`isContractAddress`,
|
|
21
|
+
baseAssetName: asset.baseAsset.name,
|
|
22
|
+
})
|
|
10
23
|
}
|
|
11
24
|
|
|
12
25
|
export const isContractAddressCached = memoizeLruCache(
|
|
@@ -16,11 +29,14 @@ export const isContractAddressCached = memoizeLruCache(
|
|
|
16
29
|
)
|
|
17
30
|
|
|
18
31
|
export async function isForwarderContract({ asset, address }) {
|
|
19
|
-
const
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
32
|
+
const server = getServer(asset)
|
|
33
|
+
const contractCode = await withErrorReason({
|
|
34
|
+
promise: server.getCode(address),
|
|
35
|
+
errorReasonInfo: EVM_ERROR_REASONS.getCodeFailed,
|
|
36
|
+
hint: safeString`isForwarderContract`,
|
|
37
|
+
baseAssetName: asset.baseAsset.name,
|
|
38
|
+
})
|
|
39
|
+
return contractCode === FORWARDER_CONTRACT_CODE
|
|
24
40
|
}
|
|
25
41
|
|
|
26
42
|
export const isForwarderContractCached = memoizeLruCache(
|
|
@@ -31,62 +47,103 @@ export const isForwarderContractCached = memoizeLruCache(
|
|
|
31
47
|
|
|
32
48
|
export async function getNonce({ asset, address, tag = 'latest' }) {
|
|
33
49
|
const server = getServer(asset)
|
|
34
|
-
const nonce = await
|
|
50
|
+
const nonce = await withErrorReason({
|
|
51
|
+
promise: server.getTransactionCount(address, tag),
|
|
52
|
+
errorReasonInfo: EVM_ERROR_REASONS.nonceFetchFailed,
|
|
53
|
+
hint: safeString`getNonce`,
|
|
54
|
+
baseAssetName: asset.baseAsset.name,
|
|
55
|
+
})
|
|
56
|
+
|
|
35
57
|
return parseInt(nonce, 16)
|
|
36
58
|
}
|
|
37
59
|
|
|
38
60
|
export async function estimateGas({ asset, ...args }) {
|
|
39
|
-
return
|
|
61
|
+
return withErrorReason({
|
|
62
|
+
promise: getServer(asset).estimateGas(args),
|
|
63
|
+
errorReasonInfo: EVM_ERROR_REASONS.fetchGasLimitFailed,
|
|
64
|
+
hint: safeString`estimateGas`,
|
|
65
|
+
baseAssetName: asset.baseAsset.name,
|
|
66
|
+
})
|
|
40
67
|
}
|
|
41
68
|
|
|
42
69
|
// Only base assets, not tokens
|
|
43
70
|
export async function getBalance({ asset, address }) {
|
|
44
71
|
if (!isEthereumLikeAsset(asset)) throw new Error(`unsupported asset ${asset.name}`)
|
|
72
|
+
|
|
45
73
|
const server = getServer(asset)
|
|
46
|
-
const balances = await
|
|
74
|
+
const balances = await withErrorReason({
|
|
75
|
+
promise: server.getBalance(address),
|
|
76
|
+
errorReasonInfo: EVM_ERROR_REASONS.balanceFetchFailed,
|
|
77
|
+
hint: safeString`getBalance`,
|
|
78
|
+
baseAssetName: asset.baseAsset.name,
|
|
79
|
+
})
|
|
47
80
|
return balances?.confirmed?.value || '0'
|
|
48
81
|
}
|
|
49
82
|
|
|
50
83
|
// Only base assets, not tokens
|
|
51
84
|
export async function getBalanceProxied({ asset, address, tag = 'latest' }) {
|
|
52
85
|
if (!isEthereumLikeAsset(asset)) throw new Error(`unsupported asset ${asset.name}`)
|
|
53
|
-
|
|
86
|
+
|
|
87
|
+
const result = await withErrorReason({
|
|
88
|
+
promise: getServer(asset).getBalanceProxied(address),
|
|
89
|
+
errorReasonInfo: EVM_ERROR_REASONS.balanceFetchFailed,
|
|
90
|
+
hint: safeString`getBalanceProxied`,
|
|
91
|
+
baseAssetName: asset.baseAsset.name,
|
|
92
|
+
})
|
|
54
93
|
return fromHexToString(result)
|
|
55
94
|
}
|
|
56
95
|
|
|
57
96
|
// Only ETH-like assets with token support
|
|
97
|
+
// How would this return token balances? It does the same as getBalanceProxied... getting ETH balance
|
|
58
98
|
export async function getTokenBalance({ asset, address }) {
|
|
59
99
|
if (!isEthereumLikeToken(asset)) throw new Error(`unsupported ETH-like token ${asset.name}`)
|
|
60
|
-
|
|
61
|
-
const balances = await server.getBalance(address)
|
|
100
|
+
|
|
62
101
|
const contractAddress = asset.contract.address.toLowerCase()
|
|
102
|
+
const server = getServer(asset)
|
|
103
|
+
const balances = await withErrorReason({
|
|
104
|
+
promise: server.balanceOf(address, contractAddress),
|
|
105
|
+
errorReasonInfo: EVM_ERROR_REASONS.balanceFetchFailed,
|
|
106
|
+
hint: safeString`getTokenBalance`,
|
|
107
|
+
baseAssetName: asset.baseAsset.name,
|
|
108
|
+
})
|
|
63
109
|
return balances?.confirmed?.[contractAddress] || '0'
|
|
64
110
|
}
|
|
65
111
|
|
|
66
112
|
export async function getTokenBalanceFromNode({ asset, address }) {
|
|
67
113
|
if (!isEthereumLikeToken(asset)) throw new Error(`unsupported ETH-like token ${asset.name}`)
|
|
114
|
+
|
|
68
115
|
const server = getServer(asset)
|
|
69
116
|
const contractAddress = asset.contract.address.toLowerCase()
|
|
70
|
-
const balances = await
|
|
117
|
+
const balances = await withErrorReason({
|
|
118
|
+
promise: server.balanceOf(address, contractAddress),
|
|
119
|
+
errorReasonInfo: EVM_ERROR_REASONS.ethCallErc20Failed,
|
|
120
|
+
hint: safeString`getTokenBalanceFromNode`,
|
|
121
|
+
baseAssetName: asset.baseAsset.name,
|
|
122
|
+
})
|
|
71
123
|
return balances?.confirmed?.[contractAddress] || '0'
|
|
72
124
|
}
|
|
73
125
|
|
|
74
|
-
// Returns function for supplied asset
|
|
75
|
-
export function sendRawTransaction(asset) {
|
|
76
|
-
return getServer(asset).sendRawTransaction
|
|
77
|
-
}
|
|
78
|
-
|
|
79
126
|
export async function transactionExists({ asset, txId }) {
|
|
80
127
|
const server = getServer(asset)
|
|
81
128
|
txId = normalizeTxId(txId)
|
|
82
|
-
const txResult = await
|
|
83
|
-
|
|
129
|
+
const txResult = await withErrorReason({
|
|
130
|
+
promise: server.getTransactionByHash(txId),
|
|
131
|
+
errorReasonInfo: EVM_ERROR_REASONS.getTransactionByHashFailed,
|
|
132
|
+
hint: safeString`transactionExists`,
|
|
133
|
+
baseAssetName: asset.baseAsset.name,
|
|
134
|
+
})
|
|
135
|
+
return Boolean(txResult && txResult.hash === txId)
|
|
84
136
|
}
|
|
85
137
|
|
|
86
138
|
export async function getTransaction({ asset, txId }) {
|
|
87
139
|
const server = getServer(asset)
|
|
88
140
|
txId = normalizeTxId(txId)
|
|
89
|
-
return
|
|
141
|
+
return withErrorReason({
|
|
142
|
+
promise: server.getTransactionByHash(txId),
|
|
143
|
+
errorReasonInfo: EVM_ERROR_REASONS.getTransactionByHashFailed,
|
|
144
|
+
hint: safeString`getTransaction`,
|
|
145
|
+
baseAssetName: asset.baseAsset.name,
|
|
146
|
+
})
|
|
90
147
|
}
|
|
91
148
|
|
|
92
149
|
export const getIsForwarderContract = memoizeLruCache(
|
|
@@ -97,8 +154,9 @@ export const getIsForwarderContract = memoizeLruCache(
|
|
|
97
154
|
|
|
98
155
|
const ERC20 = new SolidityContract(ABI.erc20)
|
|
99
156
|
const ERC20BytesParams = new SolidityContract(ABI.erc20BytesParams)
|
|
157
|
+
const ERC721 = new SolidityContract(ABI.erc721)
|
|
100
158
|
const DEFAULT_PARAM_NAMES = ['decimals', 'name', 'symbol']
|
|
101
|
-
const erc20ParamsCache =
|
|
159
|
+
const erc20ParamsCache = Object.create(null)
|
|
102
160
|
|
|
103
161
|
export const getERC20Params = async ({ asset, address, paramNames = DEFAULT_PARAM_NAMES } = {}) => {
|
|
104
162
|
assert(asset, 'getERC20Params(): asset required')
|
|
@@ -117,13 +175,13 @@ export const getERC20Params = async ({ asset, address, paramNames = DEFAULT_PARA
|
|
|
117
175
|
try {
|
|
118
176
|
callResponse = await server.ethCall({ to: address, data: ERC20[method].methodId })
|
|
119
177
|
} catch (err) {
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
178
|
+
throw new EthLikeError({
|
|
179
|
+
message: err.message,
|
|
180
|
+
errorReasonInfo: EVM_ERROR_REASONS.ethCallErc20Failed,
|
|
181
|
+
hint: safeString`getERC20Params`,
|
|
182
|
+
traceId: err.traceId,
|
|
183
|
+
baseAssetName: asset.baseAsset.name,
|
|
184
|
+
})
|
|
127
185
|
}
|
|
128
186
|
|
|
129
187
|
if (method === 'decimals') return parseInt(callResponse)
|
|
@@ -150,3 +208,84 @@ export const getERC20Params = async ({ asset, address, paramNames = DEFAULT_PARA
|
|
|
150
208
|
|
|
151
209
|
return response
|
|
152
210
|
}
|
|
211
|
+
|
|
212
|
+
// ERC-165 introspection used to drive Ledger's clear-signing for contract calls:
|
|
213
|
+
// it tells the device whether the target is an NFT (ERC-721/1155) or a fungible
|
|
214
|
+
// token. See `signHardwareFactory` in ethereum-lib for how this feeds the device's
|
|
215
|
+
// `isNft` hint. Display-only: never affects the signed transaction.
|
|
216
|
+
const buildSupportsInterfaceData = (interfaceId) => {
|
|
217
|
+
const erc165InterfaceId = `0x${interfaceId}`
|
|
218
|
+
return `0x${ERC721.supportsInterface.build(erc165InterfaceId).toString('hex')}`
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
const decodeBool = (result) => {
|
|
222
|
+
return ERC721.decodeOutput({ method: 'supportsInterface', data: result })[0]
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
// true = supported, false = not supported / reverted, undefined = transport failure
|
|
226
|
+
const probeSupportsInterface = async ({ server, address, interfaceId }) => {
|
|
227
|
+
try {
|
|
228
|
+
const result = await server.ethCall({
|
|
229
|
+
to: address,
|
|
230
|
+
data: buildSupportsInterfaceData(interfaceId),
|
|
231
|
+
})
|
|
232
|
+
return decodeBool(result)
|
|
233
|
+
} catch (err) {
|
|
234
|
+
// Empirically, a contract without supportsInterface comes through Clarity as
|
|
235
|
+
// "Bad rpc response: execution reverted". Unknown errors stay undefined so
|
|
236
|
+
// we don't force a wrong hint.
|
|
237
|
+
const EXECUTION_REVERT_MESSAGE = 'execution reverted'
|
|
238
|
+
const errorMessage = err?.message || String(err || '')
|
|
239
|
+
return errorMessage.toLowerCase().includes(EXECUTION_REVERT_MESSAGE) ? false : undefined
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
// Resolves whether `address` is an NFT contract (ERC-721 or ERC-1155) via ERC-165.
|
|
244
|
+
// Returns true (NFT), false (not an NFT), or undefined (couldn't determine).
|
|
245
|
+
export async function getIsNftContract({ server, address }) {
|
|
246
|
+
assert(server, 'getIsNftContract(): server required')
|
|
247
|
+
assert(address, 'getIsNftContract(): address required')
|
|
248
|
+
|
|
249
|
+
const ERC721_INTERFACE_ID = '80ac58cd'
|
|
250
|
+
const ERC1155_INTERFACE_ID = 'd9b67a26'
|
|
251
|
+
|
|
252
|
+
const [isErc721, isErc1155] = await Promise.all([
|
|
253
|
+
probeSupportsInterface({ server, address, interfaceId: ERC721_INTERFACE_ID }),
|
|
254
|
+
probeSupportsInterface({ server, address, interfaceId: ERC1155_INTERFACE_ID }),
|
|
255
|
+
])
|
|
256
|
+
|
|
257
|
+
if (isErc721 === true || isErc1155 === true) {
|
|
258
|
+
return true
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
if (isErc721 === false && isErc1155 === false) return false
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
export async function getEIP7702Delegation({ address, server }) {
|
|
265
|
+
const code = await withErrorReason({
|
|
266
|
+
promise: server.getCode(address),
|
|
267
|
+
errorReasonInfo: EVM_ERROR_REASONS.getCodeFailed,
|
|
268
|
+
hint: safeString`getEIP7702Delegation`,
|
|
269
|
+
baseAssetName: server.baseAssetName,
|
|
270
|
+
})
|
|
271
|
+
|
|
272
|
+
// No code at all
|
|
273
|
+
if (!code || code === '0x' || code === '0x0') {
|
|
274
|
+
return { isDelegated: false, delegatedAddress: null }
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
// EIP-7702 delegation code must be exactly 23 bytes (46 hex chars + "0x" prefix = 48 chars)
|
|
278
|
+
// Format: 0xef0100 (3 bytes) + address (20 bytes)
|
|
279
|
+
if (code.length !== 48) {
|
|
280
|
+
return { isDelegated: false, delegatedAddress: null }
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
// Check for EIP-7702 delegation designator prefix
|
|
284
|
+
if (!code.toLowerCase().startsWith(EIP7702_DELEGATION_DESIGNATOR)) {
|
|
285
|
+
return { isDelegated: false, delegatedAddress: null }
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
// Extract the 20-byte delegated address
|
|
289
|
+
const delegatedAddress = '0x' + code.slice(8, 48)
|
|
290
|
+
return { isDelegated: true, delegatedAddress }
|
|
291
|
+
}
|
|
@@ -1,16 +1,18 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import
|
|
3
|
-
import EventEmitter from 'events'
|
|
4
|
-
import { isEmpty } from 'lodash'
|
|
5
|
-
import { fetch } from '@exodus/fetch'
|
|
6
|
-
import { fromHexToString } from '../number-utils'
|
|
1
|
+
import { safeString } from '@exodus/safe-string'
|
|
2
|
+
import lodash from 'lodash'
|
|
7
3
|
|
|
8
|
-
|
|
9
|
-
|
|
4
|
+
import { errorMessageToSafeHint } from './errors.js'
|
|
5
|
+
import { EthLikeServer } from './eth-like-server.js'
|
|
6
|
+
import { fetchJsonRetry } from './fetch-json.js'
|
|
7
|
+
|
|
8
|
+
const { isEmpty } = lodash
|
|
9
|
+
|
|
10
|
+
export default class ApiCoinNodesServer extends EthLikeServer {
|
|
11
|
+
constructor({ baseAssetName, uri }) {
|
|
10
12
|
super()
|
|
13
|
+
this.baseAssetName = baseAssetName
|
|
11
14
|
this.uri = uri
|
|
12
15
|
this.defaultUri = uri
|
|
13
|
-
this.id = 0
|
|
14
16
|
}
|
|
15
17
|
|
|
16
18
|
setURI(uri) {
|
|
@@ -24,24 +26,18 @@ export default class ApiCoinNodesServer extends EventEmitter {
|
|
|
24
26
|
body: JSON.stringify(body),
|
|
25
27
|
}
|
|
26
28
|
|
|
27
|
-
|
|
28
|
-
return response.json()
|
|
29
|
+
return fetchJsonRetry(this.uri, options)
|
|
29
30
|
}
|
|
30
31
|
|
|
31
|
-
async sendBatchRequest(batch) {
|
|
32
|
+
async sendBatchRequest(batch, { tolerant } = {}) {
|
|
32
33
|
if (isEmpty(batch)) return batch
|
|
33
34
|
const responses = await this.request(batch)
|
|
34
|
-
|
|
35
|
-
|
|
35
|
+
return this.parseBatchResponse({
|
|
36
|
+
batch,
|
|
37
|
+
responses,
|
|
38
|
+
errorMessage: 'Bad rpc batch response',
|
|
39
|
+
tolerant,
|
|
36
40
|
})
|
|
37
|
-
if (responses.length !== batch.length || !isValid) {
|
|
38
|
-
throw new Error('Bad rpc batch response')
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
const keyed = responses.reduce((acc, response) => {
|
|
42
|
-
return { ...acc, [`${response.id}`]: response.result }
|
|
43
|
-
}, Object.create(null))
|
|
44
|
-
return batch.map((request) => keyed[`${request.id}`])
|
|
45
41
|
}
|
|
46
42
|
|
|
47
43
|
async sendRequest(request) {
|
|
@@ -50,185 +46,20 @@ export default class ApiCoinNodesServer extends EventEmitter {
|
|
|
50
46
|
const result = response?.result
|
|
51
47
|
const error = response?.error
|
|
52
48
|
if (error || result === undefined) {
|
|
53
|
-
const message = error?.message || error?.code ||
|
|
54
|
-
throw new Error(`Bad rpc response: ${message}`)
|
|
55
|
-
}
|
|
49
|
+
const message = error?.message || error?.code || safeString`no result`
|
|
56
50
|
|
|
57
|
-
|
|
58
|
-
|
|
51
|
+
const revisedError = new Error(`Bad rpc response: ${message}`)
|
|
52
|
+
revisedError.hint = safeString`Bad rpc response: ${errorMessageToSafeHint(message)}`
|
|
59
53
|
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
buildRequest({ method, params = [] }) {
|
|
66
|
-
return { jsonrpc: '2.0', id: this.id++, method, params }
|
|
67
|
-
}
|
|
54
|
+
const traceId = response?.__traceId
|
|
55
|
+
if (traceId) {
|
|
56
|
+
revisedError.traceId = traceId
|
|
57
|
+
}
|
|
68
58
|
|
|
69
|
-
|
|
70
|
-
const contract = SolidityContract.simpleErc20(tokenAddress)
|
|
71
|
-
const callData = contract.balanceOf.build(address)
|
|
72
|
-
const data = {
|
|
73
|
-
data: bufferToHex(callData),
|
|
74
|
-
to: tokenAddress,
|
|
59
|
+
throw revisedError
|
|
75
60
|
}
|
|
76
|
-
return this.ethCallRequest(data, tag)
|
|
77
|
-
}
|
|
78
|
-
|
|
79
|
-
getBalanceRequest(address, tag = 'latest') {
|
|
80
|
-
return this.buildRequest({ method: 'eth_getBalance', params: [address, tag] })
|
|
81
|
-
}
|
|
82
|
-
|
|
83
|
-
gasPriceRequest() {
|
|
84
|
-
return this.buildRequest({ method: 'eth_gasPrice' })
|
|
85
|
-
}
|
|
86
|
-
|
|
87
|
-
estimateGasRequest(data) {
|
|
88
|
-
return this.buildRequest({ method: 'eth_estimateGas', params: [data] })
|
|
89
|
-
}
|
|
90
|
-
|
|
91
|
-
sendRawTransactionRequest(data) {
|
|
92
|
-
const hex = data.startsWith('0x') ? data : '0x' + data
|
|
93
|
-
return this.buildRequest({ method: 'eth_sendRawTransaction', params: [hex] })
|
|
94
|
-
}
|
|
95
|
-
|
|
96
|
-
getCodeRequest(address, tag = 'latest') {
|
|
97
|
-
return this.buildRequest({ method: 'eth_getCode', params: [address, tag] })
|
|
98
|
-
}
|
|
99
|
-
|
|
100
|
-
getStorageAtRequest(address, position, tag = 'latest') {
|
|
101
|
-
return this.buildRequest({ method: 'eth_getStorageAt', params: [address, position, tag] })
|
|
102
|
-
}
|
|
103
|
-
|
|
104
|
-
getTransactionCountRequest(address, tag = 'latest') {
|
|
105
|
-
return this.buildRequest({ method: 'eth_getTransactionCount', params: [address, tag] })
|
|
106
|
-
}
|
|
107
|
-
|
|
108
|
-
getTransactionByHashRequest(hash) {
|
|
109
|
-
return this.buildRequest({ method: 'eth_getTransactionByHash', params: [hash] })
|
|
110
|
-
}
|
|
111
|
-
|
|
112
|
-
getTransactionReceiptRequest(txhash) {
|
|
113
|
-
return this.buildRequest({ method: 'eth_getTransactionReceipt', params: [txhash] })
|
|
114
|
-
}
|
|
115
|
-
|
|
116
|
-
ethCallRequest(data, tag = 'latest') {
|
|
117
|
-
return this.buildRequest({ method: 'eth_call', params: [data, tag] })
|
|
118
|
-
}
|
|
119
|
-
|
|
120
|
-
blockNumberRequest() {
|
|
121
|
-
return this.buildRequest({ method: 'eth_blockNumber' })
|
|
122
|
-
}
|
|
123
|
-
|
|
124
|
-
getBlockByNumberRequest(numberHex, isFullTxs = false) {
|
|
125
|
-
return this.buildRequest({ method: 'eth_getBlockByNumber', params: [numberHex, isFullTxs] })
|
|
126
|
-
}
|
|
127
|
-
|
|
128
|
-
simulateRawTransactionRequest(rawTx, applyPending = true) {
|
|
129
|
-
const replaced = rawTx.replace('0x', '')
|
|
130
|
-
return this.buildRequest({
|
|
131
|
-
method: 'debug_simulateRawTransaction',
|
|
132
|
-
params: [replaced, applyPending],
|
|
133
|
-
})
|
|
134
|
-
}
|
|
135
61
|
|
|
136
|
-
|
|
137
|
-
const request = this.balanceOfRequest(address, tokenAddress, tag)
|
|
138
|
-
const result = await this.sendRequest(request)
|
|
139
|
-
const balance = fromHexToString(result)
|
|
140
|
-
return {
|
|
141
|
-
confirmed: {
|
|
142
|
-
[tokenAddress]: balance,
|
|
143
|
-
},
|
|
144
|
-
}
|
|
145
|
-
}
|
|
146
|
-
|
|
147
|
-
async getBalance(...params) {
|
|
148
|
-
const request = this.getBalanceRequest(...params)
|
|
149
|
-
return this.sendRequest(request)
|
|
150
|
-
}
|
|
151
|
-
|
|
152
|
-
async getBalanceProxied(...params) {
|
|
153
|
-
return this.getBalance(...params)
|
|
154
|
-
}
|
|
155
|
-
|
|
156
|
-
async gasPrice(...params) {
|
|
157
|
-
const request = this.gasPriceRequest(...params)
|
|
158
|
-
return this.sendRequest(request)
|
|
159
|
-
}
|
|
160
|
-
|
|
161
|
-
// for fee monitor
|
|
162
|
-
getGasPrice = this.gasPrice
|
|
163
|
-
|
|
164
|
-
async getLatestBlock() {
|
|
165
|
-
const request = this.buildRequest({
|
|
166
|
-
method: 'eth_getBlockByNumber',
|
|
167
|
-
params: ['latest', false],
|
|
168
|
-
})
|
|
169
|
-
return this.sendRequest(request)
|
|
170
|
-
}
|
|
171
|
-
|
|
172
|
-
async getBaseFeePerGas() {
|
|
173
|
-
const response = await this.getLatestBlock()
|
|
174
|
-
if (response.baseFeePerGas) {
|
|
175
|
-
return fromHexToString(response.baseFeePerGas)
|
|
176
|
-
}
|
|
177
|
-
}
|
|
178
|
-
|
|
179
|
-
async estimateGas(...params) {
|
|
180
|
-
const request = this.estimateGasRequest(...params)
|
|
181
|
-
return this.sendRequest(request)
|
|
182
|
-
}
|
|
183
|
-
|
|
184
|
-
async sendRawTransaction(...params) {
|
|
185
|
-
const request = this.sendRawTransactionRequest(...params)
|
|
186
|
-
return this.sendRequest(request)
|
|
187
|
-
}
|
|
188
|
-
|
|
189
|
-
async getCode(...params) {
|
|
190
|
-
const request = this.getCodeRequest(...params)
|
|
191
|
-
return this.sendRequest(request)
|
|
192
|
-
}
|
|
193
|
-
|
|
194
|
-
async getStorageAt(...params) {
|
|
195
|
-
const request = this.getStorageAtRequest(...params)
|
|
196
|
-
return this.sendRequest(request)
|
|
197
|
-
}
|
|
198
|
-
|
|
199
|
-
async getTransactionCount(...params) {
|
|
200
|
-
const request = this.getTransactionCountRequest(...params)
|
|
201
|
-
return this.sendRequest(request)
|
|
202
|
-
}
|
|
203
|
-
|
|
204
|
-
async getTransactionByHash(...params) {
|
|
205
|
-
const request = this.getTransactionByHashRequest(...params)
|
|
206
|
-
return this.sendRequest(request)
|
|
207
|
-
}
|
|
208
|
-
|
|
209
|
-
async getTransactionReceipt(...params) {
|
|
210
|
-
const request = this.getTransactionReceiptRequest(...params)
|
|
211
|
-
return this.sendRequest(request)
|
|
212
|
-
}
|
|
213
|
-
|
|
214
|
-
async ethCall(...params) {
|
|
215
|
-
const request = this.ethCallRequest(...params)
|
|
216
|
-
return this.sendRequest(request)
|
|
217
|
-
}
|
|
218
|
-
|
|
219
|
-
async blockNumber(...params) {
|
|
220
|
-
const request = this.blockNumberRequest(...params)
|
|
221
|
-
return this.sendRequest(request)
|
|
222
|
-
}
|
|
223
|
-
|
|
224
|
-
async getBlockByNumber(...params) {
|
|
225
|
-
const request = this.getBlockByNumberRequest(...params)
|
|
226
|
-
return this.sendRequest(request)
|
|
227
|
-
}
|
|
228
|
-
|
|
229
|
-
async simulateRawTransaction(...params) {
|
|
230
|
-
const request = this.simulateRawTransactionRequest(...params)
|
|
231
|
-
return this.sendRequest(request)
|
|
62
|
+
return result
|
|
232
63
|
}
|
|
233
64
|
|
|
234
65
|
stop() {
|
|
@@ -1,11 +1,13 @@
|
|
|
1
|
-
import
|
|
2
|
-
import
|
|
1
|
+
import { randomUUID } from '@exodus/crypto/randomUUID'
|
|
2
|
+
import { bufferToHex } from '@exodus/ethereumjs/util'
|
|
3
3
|
import fetchival from '@exodus/fetch/experimental/fetchival'
|
|
4
4
|
import { retry } from '@exodus/simple-retry'
|
|
5
5
|
import SolidityContract from '@exodus/solidity-contract'
|
|
6
|
-
import
|
|
7
|
-
|
|
8
|
-
import { fromHexToString } from '../number-utils'
|
|
6
|
+
import ms from 'ms'
|
|
7
|
+
|
|
8
|
+
import { fromHexToString } from '../number-utils.js'
|
|
9
|
+
import { getFallbackGasPriceEstimation } from './utils.js'
|
|
10
|
+
import createWebSocket from './ws.js'
|
|
9
11
|
|
|
10
12
|
const RETRY_DELAYS = ['10s']
|
|
11
13
|
|
|
@@ -29,7 +31,7 @@ export function create(defaultURL, ensAssetName) {
|
|
|
29
31
|
)
|
|
30
32
|
} catch (err) {
|
|
31
33
|
let nerr = err
|
|
32
|
-
if (err.response && err.response.status === 500) {
|
|
34
|
+
if (err.response && (err.response.status === 500 || err.response.status === 400)) {
|
|
33
35
|
try {
|
|
34
36
|
const data = await err.response.json()
|
|
35
37
|
const msg = data.error.replace(/^RPC error \(code: -\d+\): /, '')
|
|
@@ -63,9 +65,7 @@ export function create(defaultURL, ensAssetName) {
|
|
|
63
65
|
},
|
|
64
66
|
|
|
65
67
|
stop() {
|
|
66
|
-
|
|
67
|
-
ws.close()
|
|
68
|
-
}
|
|
68
|
+
ws.close()
|
|
69
69
|
},
|
|
70
70
|
|
|
71
71
|
ws,
|
|
@@ -121,6 +121,10 @@ export function create(defaultURL, ensAssetName) {
|
|
|
121
121
|
return requestWithRetry('proxy', { method: 'eth_gasPrice' })
|
|
122
122
|
},
|
|
123
123
|
|
|
124
|
+
async getGasPriceEstimation() {
|
|
125
|
+
return getFallbackGasPriceEstimation({ server: this })
|
|
126
|
+
},
|
|
127
|
+
|
|
124
128
|
// for fee monitor
|
|
125
129
|
async getGasPrice() {
|
|
126
130
|
return requestWithRetry('proxy', { method: 'eth_gasPrice' })
|
|
@@ -163,8 +167,13 @@ export function create(defaultURL, ensAssetName) {
|
|
|
163
167
|
},
|
|
164
168
|
|
|
165
169
|
async sendRawTransaction(data) {
|
|
166
|
-
const
|
|
167
|
-
|
|
170
|
+
const _data = data instanceof Uint8Array ? Buffer.from(data).toString('hex') : data
|
|
171
|
+
const hex = _data.startsWith('0x') ? _data : '0x' + _data
|
|
172
|
+
return requestWithRetry(
|
|
173
|
+
'proxy',
|
|
174
|
+
{ method: 'eth_sendRawTransaction', params: [hex] },
|
|
175
|
+
{ version: 'v2', method: 'post' }
|
|
176
|
+
)
|
|
168
177
|
},
|
|
169
178
|
|
|
170
179
|
async estimateGas(data, tag = 'latest') {
|
|
@@ -254,11 +263,11 @@ export function create(defaultURL, ensAssetName) {
|
|
|
254
263
|
},
|
|
255
264
|
|
|
256
265
|
async proxyToCoinNode(requestData) {
|
|
257
|
-
// eslint-disable-next-line @exodus/
|
|
266
|
+
// eslint-disable-next-line @exodus/hydra/no-param-reassign-prop-only
|
|
258
267
|
if (!requestData.jsonrpc) requestData.jsonrpc = '2.0'
|
|
259
|
-
// eslint-disable-next-line @exodus/
|
|
268
|
+
// eslint-disable-next-line @exodus/hydra/no-param-reassign-prop-only
|
|
260
269
|
if (!requestData.id) requestData.id = randomUUID()
|
|
261
|
-
// eslint-disable-next-line @exodus/
|
|
270
|
+
// eslint-disable-next-line @exodus/hydra/no-param-reassign-prop-only
|
|
262
271
|
if (!requestData.params) requestData.params = []
|
|
263
272
|
|
|
264
273
|
return request('proxy', requestData, { version: 'v2', method: 'post' })
|