@exodus/ethereum-api 9.0.0-alpha.3 → 9.0.1
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 +2139 -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 +273 -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 +276 -103
- package/src/tx-log/clarity-truncated-history-monitor.js +130 -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,62 @@
|
|
|
1
|
+
const ADDRESS_REGEX = /^0x[\dA-Fa-f]{40}$/
|
|
2
|
+
|
|
3
|
+
const isValidWhitelistEntry = (entry) =>
|
|
4
|
+
typeof entry?.address === 'string' &&
|
|
5
|
+
ADDRESS_REGEX.test(entry.address) &&
|
|
6
|
+
typeof entry.name === 'string' &&
|
|
7
|
+
entry.name.length > 0
|
|
8
|
+
|
|
9
|
+
export const isValidEip7702Whitelist = (whitelist) =>
|
|
10
|
+
Array.isArray(whitelist) && whitelist.every((entry) => isValidWhitelistEntry(entry))
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Mutable holder for the EIP-7702 delegation whitelist, shared between the
|
|
14
|
+
* history monitors, security checks and the asset itself. The whitelist data
|
|
15
|
+
* comes exclusively from remote config; plugins pass `eip7702Supported: []`
|
|
16
|
+
* as the chain-level feature flag, and the list stays empty until remote
|
|
17
|
+
* config delivers it. Only chains created with an array accept updates.
|
|
18
|
+
*
|
|
19
|
+
* @param {Array<{address: string, name: string}>} [defaultWhitelist] - The
|
|
20
|
+
* in-code value, `[]` for supported chains. A non-array means the chain
|
|
21
|
+
* does not support EIP-7702.
|
|
22
|
+
*/
|
|
23
|
+
export const createEip7702Whitelist = (defaultWhitelist) => {
|
|
24
|
+
const supported = Array.isArray(defaultWhitelist)
|
|
25
|
+
// The array identity is stable across updates (mutated in place), so
|
|
26
|
+
// consumers holding a reference (e.g. asset.eip7702Supported, which gets
|
|
27
|
+
// spread-copied into the wallet's asset registry) observe updates too.
|
|
28
|
+
const current = supported
|
|
29
|
+
? defaultWhitelist.map(({ address, name }) => ({ address, name }))
|
|
30
|
+
: undefined
|
|
31
|
+
|
|
32
|
+
return {
|
|
33
|
+
isSupported: () => supported,
|
|
34
|
+
get: () => current,
|
|
35
|
+
/**
|
|
36
|
+
* Replaces the whitelist with the remote-config value. Returns whether the
|
|
37
|
+
* update was applied. Invalid payloads and updates on chains without
|
|
38
|
+
* EIP-7702 support are ignored, keeping the last known good whitelist.
|
|
39
|
+
*/
|
|
40
|
+
update: (remoteWhitelist) => {
|
|
41
|
+
if (!supported) return false
|
|
42
|
+
if (!isValidEip7702Whitelist(remoteWhitelist)) return false
|
|
43
|
+
|
|
44
|
+
current.length = 0
|
|
45
|
+
current.push(...remoteWhitelist.map(({ address, name }) => ({ address, name })))
|
|
46
|
+
return true
|
|
47
|
+
},
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* Applies the `eip7702Whitelist` field of a per-asset remote config payload,
|
|
53
|
+
* as delivered to the monitors through `monitor.setServer(remoteConfig)`.
|
|
54
|
+
*/
|
|
55
|
+
export const applyRemoteConfigEip7702Whitelist = ({ eip7702Whitelist, remoteConfig, logger }) => {
|
|
56
|
+
const remoteWhitelist = remoteConfig?.eip7702Whitelist
|
|
57
|
+
if (remoteWhitelist === undefined) return
|
|
58
|
+
|
|
59
|
+
if (!eip7702Whitelist.update(remoteWhitelist)) {
|
|
60
|
+
logger?.warn('ignoring invalid eip7702Whitelist from remote config')
|
|
61
|
+
}
|
|
62
|
+
}
|
package/src/ens/index.js
CHANGED
|
@@ -1,9 +1,11 @@
|
|
|
1
|
-
import
|
|
2
|
-
import { toUnicode } from 'idna-uts46-hx'
|
|
3
|
-
import { keccak256 } from 'js-sha3'
|
|
1
|
+
import { hashSync } from '@exodus/crypto/hash'
|
|
4
2
|
import { ABI } from '@exodus/ethereum-lib'
|
|
3
|
+
import SolidityContract from '@exodus/solidity-contract'
|
|
4
|
+
import idna from 'idna-uts46-hx'
|
|
5
|
+
|
|
6
|
+
import ENS_REGISTRY_ADDRESSES from './addresses.js'
|
|
5
7
|
|
|
6
|
-
|
|
8
|
+
const { toUnicode } = idna
|
|
7
9
|
|
|
8
10
|
const registry = new SolidityContract(ABI.ensRegistry)
|
|
9
11
|
const resolver = new SolidityContract(ABI.ensResolver)
|
|
@@ -17,24 +19,16 @@ function namehash(inputName) {
|
|
|
17
19
|
// Implementation based on @ensdomains/eth-ens-namehash package
|
|
18
20
|
|
|
19
21
|
// Reject empty names:
|
|
20
|
-
|
|
21
|
-
var i
|
|
22
|
-
for (i = 0; i < 32; i++) {
|
|
23
|
-
node += '00'
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
var name = normalize(inputName)
|
|
22
|
+
let node = Buffer.alloc(32)
|
|
27
23
|
|
|
24
|
+
const name = normalize(inputName)
|
|
28
25
|
if (name) {
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
for (i = labels.length - 1; i >= 0; i--) {
|
|
32
|
-
var labelSha = keccak256(labels[i])
|
|
33
|
-
node = keccak256(Buffer.from(node + labelSha, 'hex'))
|
|
26
|
+
for (const label of name.split('.').reverse()) {
|
|
27
|
+
node = hashSync('keccak256', [node, hashSync('keccak256', label, 'uint8')], 'buffer')
|
|
34
28
|
}
|
|
35
29
|
}
|
|
36
30
|
|
|
37
|
-
return '0x' + node
|
|
31
|
+
return '0x' + node.toString('hex')
|
|
38
32
|
}
|
|
39
33
|
|
|
40
34
|
// ENS only available on mainnet atm
|
|
@@ -70,4 +64,5 @@ const resolveEnsAddress = async (address, server) => {
|
|
|
70
64
|
return resolver.decodeOutput({ data, method: 'name' })[0]
|
|
71
65
|
}
|
|
72
66
|
|
|
67
|
+
// This doesn't seem to be used anywhere.
|
|
73
68
|
export { resolveEnsAddress, resolveEnsName }
|
|
@@ -0,0 +1,383 @@
|
|
|
1
|
+
import { safeString } from '@exodus/safe-string'
|
|
2
|
+
|
|
3
|
+
// Error types for categorizing EVM errors
|
|
4
|
+
export const EVM_ERROR_TYPES = {
|
|
5
|
+
NODE_STATE_READ: safeString`NODE_STATE_READ`, // RPC-level read operations
|
|
6
|
+
CONTRACT_CALL: safeString`CONTRACT_CALL`, // Smart contract calls (eth_call, estimateGas)
|
|
7
|
+
BROADCAST: safeString`BROADCAST`, // Transaction broadcast errors (includes reverts)
|
|
8
|
+
PREFLIGHT_VALIDATION: safeString`PREFLIGHT_VALIDATION`, // Logic/state validation before contract gasEstimation & broadcast
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
// Operation-specific reasons
|
|
12
|
+
export const EVM_ERROR_REASONS = {
|
|
13
|
+
// RPC-level read operations
|
|
14
|
+
balanceFetchFailed: {
|
|
15
|
+
reason: safeString`balance fetch failed`,
|
|
16
|
+
type: EVM_ERROR_TYPES.NODE_STATE_READ,
|
|
17
|
+
},
|
|
18
|
+
nonceFetchFailed: {
|
|
19
|
+
reason: safeString`nonce fetch failed`,
|
|
20
|
+
type: EVM_ERROR_TYPES.NODE_STATE_READ,
|
|
21
|
+
},
|
|
22
|
+
getTransactionByHashFailed: {
|
|
23
|
+
reason: safeString`get transaction by hash failed`,
|
|
24
|
+
type: EVM_ERROR_TYPES.NODE_STATE_READ,
|
|
25
|
+
},
|
|
26
|
+
getCodeFailed: {
|
|
27
|
+
reason: safeString`get code failed`,
|
|
28
|
+
type: EVM_ERROR_TYPES.NODE_STATE_READ,
|
|
29
|
+
},
|
|
30
|
+
getGasPriceFailed: {
|
|
31
|
+
reason: safeString`get gas price failed`,
|
|
32
|
+
type: EVM_ERROR_TYPES.NODE_STATE_READ,
|
|
33
|
+
},
|
|
34
|
+
getBaseFeePerGasFailed: {
|
|
35
|
+
reason: safeString`get base fee per gas failed`,
|
|
36
|
+
type: EVM_ERROR_TYPES.NODE_STATE_READ,
|
|
37
|
+
},
|
|
38
|
+
|
|
39
|
+
// Generic smart contract call failure.
|
|
40
|
+
ethCallFailed: {
|
|
41
|
+
reason: safeString`eth call failed`,
|
|
42
|
+
type: EVM_ERROR_TYPES.CONTRACT_CALL,
|
|
43
|
+
},
|
|
44
|
+
ethCallErc20Failed: {
|
|
45
|
+
reason: safeString`eth call erc20 failed`,
|
|
46
|
+
type: EVM_ERROR_TYPES.CONTRACT_CALL,
|
|
47
|
+
},
|
|
48
|
+
|
|
49
|
+
// Gas Usage Simulation (checks contract reverts)
|
|
50
|
+
fetchGasLimitFailed: {
|
|
51
|
+
reason: safeString`fetch gas limit failed`,
|
|
52
|
+
type: EVM_ERROR_TYPES.CONTRACT_CALL,
|
|
53
|
+
},
|
|
54
|
+
|
|
55
|
+
// Write operations (checks sender account state/ability to transact)
|
|
56
|
+
// Generic failure, no info about a particular smart contract revert.
|
|
57
|
+
broadcastTxFailed: {
|
|
58
|
+
reason: safeString`broadcast tx failed`,
|
|
59
|
+
type: EVM_ERROR_TYPES.BROADCAST,
|
|
60
|
+
},
|
|
61
|
+
|
|
62
|
+
// Reversion reasons
|
|
63
|
+
executionReverted: {
|
|
64
|
+
reason: safeString`execution reverted`,
|
|
65
|
+
type: EVM_ERROR_TYPES.BROADCAST,
|
|
66
|
+
},
|
|
67
|
+
invalidOpcode: {
|
|
68
|
+
reason: safeString`invalid opcode`,
|
|
69
|
+
type: EVM_ERROR_TYPES.BROADCAST,
|
|
70
|
+
},
|
|
71
|
+
insufficientFundsForGas: {
|
|
72
|
+
reason: safeString`insufficient funds for gas * price + value`,
|
|
73
|
+
type: EVM_ERROR_TYPES.BROADCAST,
|
|
74
|
+
},
|
|
75
|
+
alreadyKnown: {
|
|
76
|
+
reason: safeString`already known`,
|
|
77
|
+
type: EVM_ERROR_TYPES.BROADCAST,
|
|
78
|
+
},
|
|
79
|
+
nonceTooLow: {
|
|
80
|
+
reason: safeString`nonce too low`,
|
|
81
|
+
type: EVM_ERROR_TYPES.BROADCAST,
|
|
82
|
+
},
|
|
83
|
+
insufficientBalance: {
|
|
84
|
+
reason: safeString`execution reverted: insufficient balance / transfer exceeds balance`,
|
|
85
|
+
type: EVM_ERROR_TYPES.BROADCAST,
|
|
86
|
+
},
|
|
87
|
+
replacementTransactionUnderpriced: {
|
|
88
|
+
reason: safeString`replacement transaction underpriced`,
|
|
89
|
+
type: EVM_ERROR_TYPES.BROADCAST,
|
|
90
|
+
},
|
|
91
|
+
zeroTransfer: {
|
|
92
|
+
reason: safeString`execution reverted: transfer amount must be greater than zero`,
|
|
93
|
+
type: EVM_ERROR_TYPES.BROADCAST,
|
|
94
|
+
},
|
|
95
|
+
mathError: {
|
|
96
|
+
reason: safeString`execution reverted: arithmetic overflow/underflow`,
|
|
97
|
+
type: EVM_ERROR_TYPES.BROADCAST,
|
|
98
|
+
},
|
|
99
|
+
|
|
100
|
+
transactionUnderpriced: {
|
|
101
|
+
reason: safeString`transaction underpriced`,
|
|
102
|
+
type: EVM_ERROR_TYPES.BROADCAST,
|
|
103
|
+
},
|
|
104
|
+
nonceTooHigh: {
|
|
105
|
+
reason: safeString`nonce too high`,
|
|
106
|
+
type: EVM_ERROR_TYPES.BROADCAST,
|
|
107
|
+
},
|
|
108
|
+
gasPriceBelowMin: {
|
|
109
|
+
reason: safeString`transaction gas price below minimum`,
|
|
110
|
+
type: EVM_ERROR_TYPES.BROADCAST,
|
|
111
|
+
},
|
|
112
|
+
insufficientGas: {
|
|
113
|
+
reason: safeString`insufficient gas`,
|
|
114
|
+
type: EVM_ERROR_TYPES.BROADCAST,
|
|
115
|
+
},
|
|
116
|
+
futureTransactionReplace: {
|
|
117
|
+
reason: safeString`future transaction tries to replace pending`,
|
|
118
|
+
type: EVM_ERROR_TYPES.BROADCAST,
|
|
119
|
+
},
|
|
120
|
+
eip155Required: {
|
|
121
|
+
reason: safeString`eip-155 replay protection required`,
|
|
122
|
+
type: EVM_ERROR_TYPES.BROADCAST,
|
|
123
|
+
},
|
|
124
|
+
invalidRlpSerialization: {
|
|
125
|
+
reason: safeString`invalid rlp serialization`,
|
|
126
|
+
type: EVM_ERROR_TYPES.BROADCAST,
|
|
127
|
+
},
|
|
128
|
+
transactionTypeNotSupported: {
|
|
129
|
+
reason: safeString`transaction type not supported`,
|
|
130
|
+
type: EVM_ERROR_TYPES.BROADCAST,
|
|
131
|
+
},
|
|
132
|
+
delegatedAccountError: {
|
|
133
|
+
reason: safeString`delegated account error`,
|
|
134
|
+
type: EVM_ERROR_TYPES.BROADCAST,
|
|
135
|
+
},
|
|
136
|
+
invalidJumpDestination: {
|
|
137
|
+
reason: safeString`invalid jump destination`,
|
|
138
|
+
type: EVM_ERROR_TYPES.BROADCAST,
|
|
139
|
+
},
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
// Patterns to normalize (group similar errors together)
|
|
143
|
+
const EVM_NORMALIZATION_PATTERNS = [
|
|
144
|
+
// Insufficient funds for gas (native currency, not tokens)
|
|
145
|
+
{
|
|
146
|
+
pattern: /(insufficient funds for gas \* price \+ value|insufficient funds(?!.*balance))/i,
|
|
147
|
+
errorInfo: EVM_ERROR_REASONS.insufficientFundsForGas,
|
|
148
|
+
},
|
|
149
|
+
// Nonce already used. Two flavors:
|
|
150
|
+
// - geth: "nonce too low" / "nonce is too low"
|
|
151
|
+
// - Cosmos-EVM / Ethermint AnteHandler (e.g. Cronos):
|
|
152
|
+
// "invalid nonce; got X, expected Y: invalid sequence: invalid sequence"
|
|
153
|
+
// surfaces from load-balancer node drift (chain ahead of read node).
|
|
154
|
+
// The retry path in tx-send.js bumps to max(nodeNonce, triedNonce + 1)
|
|
155
|
+
// and re-broadcasts. See exodus-desktop#19535.
|
|
156
|
+
{
|
|
157
|
+
pattern: /(\bnonce\b.*\btoo low\b|invalid nonce.*invalid sequence)/i,
|
|
158
|
+
errorInfo: EVM_ERROR_REASONS.nonceTooLow,
|
|
159
|
+
},
|
|
160
|
+
// Comprehensive insufficient balance / transfer exceeds balance pattern (TOKEN balance issues)
|
|
161
|
+
// Catches: ERC20/BEP20, 0xe450d38c custom error, insufficient balance, balanceNotEnough,
|
|
162
|
+
// and token-specific "transfer amount exceeds balance" (BaseToken, SaveYourAssets, XVS, Ondo, FLOKI, etc.)
|
|
163
|
+
{
|
|
164
|
+
pattern:
|
|
165
|
+
/(0xe450d38c|insufficient.?balance|balance.?not.?enough|not enough fund|(erc20|bep20):.*transfer amount exceeds balance|\w+:.*transfer amount exceeds balance)/i,
|
|
166
|
+
errorInfo: EVM_ERROR_REASONS.insufficientBalance,
|
|
167
|
+
},
|
|
168
|
+
// Arithmetic overflow/underflow errors (SafeMath, Solidity 0.8+, etc.)
|
|
169
|
+
{
|
|
170
|
+
pattern: /(safemath:.*overflow|arithmetic underflow or overflow)/i,
|
|
171
|
+
errorInfo: EVM_ERROR_REASONS.mathError,
|
|
172
|
+
},
|
|
173
|
+
// Transfer amount must be greater than zero (various phrasings)
|
|
174
|
+
{
|
|
175
|
+
pattern:
|
|
176
|
+
/(transfer amount must be greater than zero|amount must be greater than 0|amt must be over than 0|zero_amount|transfer amount must be positive|transfer amount zero)/i,
|
|
177
|
+
errorInfo: EVM_ERROR_REASONS.zeroTransfer,
|
|
178
|
+
},
|
|
179
|
+
// Gas price below network minimum (not mempool competition, but protocol minimum)
|
|
180
|
+
// "gas price below minimum", "gas tip cap...minimum", "max fee per gas less than block base fee"
|
|
181
|
+
{
|
|
182
|
+
pattern:
|
|
183
|
+
/gas price below minimum|gas tip cap.*minimum|max fee per gas less than block base fee/i,
|
|
184
|
+
errorInfo: EVM_ERROR_REASONS.gasPriceBelowMin,
|
|
185
|
+
},
|
|
186
|
+
// Gas limit errors
|
|
187
|
+
// "intrinsic gas too low", "insufficient gas", "floor data gas cost" - gas limit below minimum required
|
|
188
|
+
{
|
|
189
|
+
pattern: /intrinsic gas too low|insufficient gas|floor data gas cost/i,
|
|
190
|
+
errorInfo: EVM_ERROR_REASONS.insufficientGas,
|
|
191
|
+
},
|
|
192
|
+
// "nonce too high" or "nonce is too high" - tx nonce too far ahead
|
|
193
|
+
{
|
|
194
|
+
pattern: /\bnonce\b.*\btoo high\b/i,
|
|
195
|
+
errorInfo: EVM_ERROR_REASONS.nonceTooHigh,
|
|
196
|
+
},
|
|
197
|
+
// Transaction type not supported
|
|
198
|
+
// "transaction type not supported", "tx type not supported" - chain doesn't support this tx type (e.g., EIP-1559)
|
|
199
|
+
{
|
|
200
|
+
pattern: /transaction type not supported|tx type not supported/i,
|
|
201
|
+
errorInfo: EVM_ERROR_REASONS.transactionTypeNotSupported,
|
|
202
|
+
},
|
|
203
|
+
// Invalid jump destination
|
|
204
|
+
{
|
|
205
|
+
pattern: /invalid jump destination/i,
|
|
206
|
+
errorInfo: EVM_ERROR_REASONS.invalidJumpDestination,
|
|
207
|
+
},
|
|
208
|
+
// Transaction already in mempool/chain
|
|
209
|
+
// "already known", "known transaction", "already imported", "already in mempool"
|
|
210
|
+
{
|
|
211
|
+
pattern: /already known|known transaction|already imported|already in mempool/i,
|
|
212
|
+
errorInfo: EVM_ERROR_REASONS.alreadyKnown,
|
|
213
|
+
},
|
|
214
|
+
// Replacement transaction underpriced (must come BEFORE "transaction underpriced")
|
|
215
|
+
{
|
|
216
|
+
pattern: /replacement transaction underpriced/i,
|
|
217
|
+
errorInfo: EVM_ERROR_REASONS.replacementTransactionUnderpriced,
|
|
218
|
+
},
|
|
219
|
+
// Transaction underpriced (checked after replacement to avoid double match)
|
|
220
|
+
{
|
|
221
|
+
pattern: /transaction underpriced/i,
|
|
222
|
+
errorInfo: EVM_ERROR_REASONS.transactionUnderpriced,
|
|
223
|
+
},
|
|
224
|
+
// Replay-protected transactions required (EIP-155 chain ID signing)
|
|
225
|
+
// "only replay-protected", "replay-protected transaction/tx required", "not replay protected", "invalid chain id"
|
|
226
|
+
{
|
|
227
|
+
pattern:
|
|
228
|
+
/only replay-protected|replay-protected (transaction|tx) required|not replay protected|invalid chain id/i,
|
|
229
|
+
errorInfo: EVM_ERROR_REASONS.eip155Required,
|
|
230
|
+
},
|
|
231
|
+
// Future transaction tries to replace pending (nonce conflict)
|
|
232
|
+
{
|
|
233
|
+
pattern: /future transaction.*replace.*pending/i,
|
|
234
|
+
errorInfo: EVM_ERROR_REASONS.futureTransactionReplace,
|
|
235
|
+
},
|
|
236
|
+
// Serialization/signature errors
|
|
237
|
+
// "rlp:", "invalid rlp", "decode rlp", "invalid sender", "invalid signature", "bad signature"
|
|
238
|
+
{
|
|
239
|
+
pattern: /rlp:|invalid rlp|decode rlp|invalid sender|invalid signature|bad signature/i,
|
|
240
|
+
errorInfo: EVM_ERROR_REASONS.invalidRlpSerialization,
|
|
241
|
+
},
|
|
242
|
+
// EIP-7702 delegation errors
|
|
243
|
+
// "delegated" (but not "delegatecall"), "eip-7702", "eip7702" - account delegation related errors
|
|
244
|
+
{
|
|
245
|
+
pattern: /\bdelegated\b(?!\s*call)|eip-?7702/i,
|
|
246
|
+
errorInfo: EVM_ERROR_REASONS.delegatedAccountError,
|
|
247
|
+
},
|
|
248
|
+
// GENERIC CATCH-ALL PATTERNS (must be last)
|
|
249
|
+
{
|
|
250
|
+
pattern: /^execution reverted$/i,
|
|
251
|
+
errorInfo: EVM_ERROR_REASONS.executionReverted,
|
|
252
|
+
},
|
|
253
|
+
{
|
|
254
|
+
pattern: /\binvalid opcode\b/i,
|
|
255
|
+
errorInfo: EVM_ERROR_REASONS.invalidOpcode,
|
|
256
|
+
},
|
|
257
|
+
{
|
|
258
|
+
pattern: /^execution reverted: 0x$/i,
|
|
259
|
+
errorInfo: EVM_ERROR_REASONS.executionReverted,
|
|
260
|
+
},
|
|
261
|
+
// Final catch-all for any remaining "execution reverted:" messages
|
|
262
|
+
{
|
|
263
|
+
pattern: /execution reverted:/i,
|
|
264
|
+
errorInfo: EVM_ERROR_REASONS.executionReverted,
|
|
265
|
+
},
|
|
266
|
+
]
|
|
267
|
+
|
|
268
|
+
// This should be renamed something better
|
|
269
|
+
export function getEvmErrorReason(errorMessage) {
|
|
270
|
+
for (const { pattern, errorInfo } of EVM_NORMALIZATION_PATTERNS) {
|
|
271
|
+
if (pattern.test(errorMessage)) {
|
|
272
|
+
return {
|
|
273
|
+
reason: errorInfo.reason,
|
|
274
|
+
type: errorInfo.type,
|
|
275
|
+
wasNormalized: true,
|
|
276
|
+
patternUsed: pattern.toString(),
|
|
277
|
+
}
|
|
278
|
+
}
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
return null
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
const MAX_HINT_LENGTH = 100
|
|
285
|
+
// TODO: move this to be a generic error for all assets
|
|
286
|
+
export class EthLikeError extends Error {
|
|
287
|
+
#hintStack
|
|
288
|
+
|
|
289
|
+
/**
|
|
290
|
+
* Creates an instance of EthLikeError.
|
|
291
|
+
*
|
|
292
|
+
* @param {string} message - Standard error message.
|
|
293
|
+
* @param {Object} errorReasonInfo - Object containing reason and type for the error.
|
|
294
|
+
* @param {string} errorReasonInfo.reason - A constant indicating the generic failure. Must not contain any sensitive information such as private keys, transaction IDs, or wallet addresses.
|
|
295
|
+
* @param {string} errorReasonInfo.type - A category for the error (e.g., 'NODE_STATE_READ', 'CONTRACT_CALL', 'BROADCAST').
|
|
296
|
+
* @param {string} hint - A hint to help the user understand the error. Must not contain any sensitive information such as private keys, transaction IDs, or wallet addresses.
|
|
297
|
+
* @param {TraceId} [traceId] - Optional TraceId object from HTTP response 'traceparent' header for debugging.
|
|
298
|
+
* @param {string} [baseAssetName] - Optional base asset name (e.g. 'ethereum', 'bnbsmartchain') for identifying the network.
|
|
299
|
+
*/
|
|
300
|
+
constructor({ message, errorReasonInfo, hint, traceId, baseAssetName }) {
|
|
301
|
+
super(message)
|
|
302
|
+
this.name = safeString`EthLikeError`
|
|
303
|
+
const filteredHint = this.#extractHint(hint)
|
|
304
|
+
// Only filtered hints are kept on the stack so that subsequent joins via
|
|
305
|
+
// `addHint` cannot leak sensitive info or untruncated content.
|
|
306
|
+
this.#hintStack = filteredHint ? [filteredHint] : []
|
|
307
|
+
this.reason = errorReasonInfo.reason
|
|
308
|
+
this.hint = filteredHint
|
|
309
|
+
this.type = errorReasonInfo.type
|
|
310
|
+
this.traceId = traceId
|
|
311
|
+
this.baseAssetName = baseAssetName
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
addHint = (hint) => {
|
|
315
|
+
const filteredHint = this.#extractHint(hint)
|
|
316
|
+
if (!filteredHint) {
|
|
317
|
+
return this
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
this.#hintStack.push(filteredHint)
|
|
321
|
+
this.hint = this.#hintStack.join(':')
|
|
322
|
+
return this
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
getContext = () => {
|
|
326
|
+
return {
|
|
327
|
+
reason: this.reason,
|
|
328
|
+
type: this.type,
|
|
329
|
+
traceId: this.traceId,
|
|
330
|
+
baseAssetName: this.baseAssetName,
|
|
331
|
+
}
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
// NOTE: a class method for now, if we move this to be a generic error for all assets, each asset will have its own implementation
|
|
335
|
+
#extractHint = (hint) => {
|
|
336
|
+
if (!hint) return ''
|
|
337
|
+
|
|
338
|
+
// Define regex patterns for sensitive information
|
|
339
|
+
const sensitivePatterns = [
|
|
340
|
+
/(?:0x)?[\dA-Fa-f]{20,}/g, // Pattern for hex values of 20 or more characters
|
|
341
|
+
/(?:[A-Za-z]{3,20}\s+){11}[A-Za-z]{3,20}/g, // Pattern for 12-word phrases
|
|
342
|
+
/[1-9A-HJ-NP-Za-km-z]{50,}/g, // Pattern for base58 strings of 50 or more characters
|
|
343
|
+
/(?:[\d+/A-Za-z]{4}){3,}(?:[\d+/A-Za-z]{2}==|[\d+/A-Za-z]{3}=|[\d+/A-Za-z]{4})/g, // Pattern for base64 strings of 12 or more characters
|
|
344
|
+
]
|
|
345
|
+
|
|
346
|
+
let filteredHint = hint
|
|
347
|
+
|
|
348
|
+
// Check for sensitive information
|
|
349
|
+
const containsSensitiveInfo = sensitivePatterns.some((pattern) => pattern.test(filteredHint))
|
|
350
|
+
|
|
351
|
+
if (containsSensitiveInfo) {
|
|
352
|
+
console.warn('Hint contains sensitive information and will be ignored.')
|
|
353
|
+
return ''
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
// Limit length to 100 characters
|
|
357
|
+
if (filteredHint.length > MAX_HINT_LENGTH) {
|
|
358
|
+
filteredHint = filteredHint.slice(0, MAX_HINT_LENGTH - 3) + '...'
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
return filteredHint
|
|
362
|
+
}
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
export const withErrorReason = async ({ promise, errorReasonInfo, hint, baseAssetName }) => {
|
|
366
|
+
try {
|
|
367
|
+
return await promise
|
|
368
|
+
} catch (err) {
|
|
369
|
+
if (err instanceof EthLikeError) {
|
|
370
|
+
// ignore errorReason and add hint to stack
|
|
371
|
+
err.addHint(hint)
|
|
372
|
+
throw err
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
throw new EthLikeError({
|
|
376
|
+
message: err.message,
|
|
377
|
+
errorReasonInfo,
|
|
378
|
+
hint,
|
|
379
|
+
traceId: err.traceId,
|
|
380
|
+
baseAssetName,
|
|
381
|
+
})
|
|
382
|
+
}
|
|
383
|
+
}
|