@exodus/ethereum-api 9.0.0-alpha.3 → 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.
Files changed (143) hide show
  1. package/CHANGELOG.md +2121 -0
  2. package/LICENSE +7 -0
  3. package/README.md +124 -0
  4. package/package.json +34 -33
  5. package/src/address-has-history.js +6 -2
  6. package/src/allowance/constants.js +4 -5
  7. package/src/allowance/index.js +159 -40
  8. package/src/check-tx/create-assess-transaction.js +23 -0
  9. package/src/check-tx/create-check-tx.js +104 -0
  10. package/src/check-tx/index.js +1 -0
  11. package/src/create-asset-plugin-factory.js +115 -0
  12. package/src/create-asset-utils.js +520 -0
  13. package/src/create-asset.js +272 -97
  14. package/src/create-token-factory.js +47 -17
  15. package/src/custom-fees.js +57 -0
  16. package/src/eip7702-whitelist.js +62 -0
  17. package/src/ens/index.js +12 -17
  18. package/src/error-wrapper.js +383 -0
  19. package/src/eth-like-util.js +172 -33
  20. package/src/exodus-eth-server/api-coin-nodes.js +27 -196
  21. package/src/exodus-eth-server/api.js +23 -14
  22. package/src/exodus-eth-server/clarity-v2.js +255 -0
  23. package/src/exodus-eth-server/clarity.js +67 -265
  24. package/src/exodus-eth-server/errors.js +40 -0
  25. package/src/exodus-eth-server/eth-like-server.js +385 -0
  26. package/src/exodus-eth-server/fetch-json.js +48 -0
  27. package/src/exodus-eth-server/index.js +77 -0
  28. package/src/exodus-eth-server/utils.js +31 -0
  29. package/src/exodus-eth-server/ws-gateway.js +306 -0
  30. package/src/exodus-eth-server/ws.js +1 -2
  31. package/src/fee-data/EthLikeFeeData.js +81 -0
  32. package/src/fee-data/index.js +54 -0
  33. package/src/fee-utils.js +229 -0
  34. package/src/gas-estimation.js +183 -66
  35. package/src/get-balance-for-address.js +1 -1
  36. package/src/get-balances.js +285 -27
  37. package/src/get-fee.js +92 -56
  38. package/src/get-historical-balance.js +58 -0
  39. package/src/hooks/index.js +1 -1
  40. package/src/hooks/monitor.js +182 -43
  41. package/src/hooks/staking-freshness.js +58 -0
  42. package/src/index.js +118 -12
  43. package/src/move-funds.js +153 -0
  44. package/src/multicall3/index.js +212 -0
  45. package/src/multisig-address.js +146 -0
  46. package/src/nft-utils.js +60 -0
  47. package/src/number-utils.js +34 -2
  48. package/src/optimism-gas/index.js +13 -5
  49. package/src/send-validations.js +58 -0
  50. package/src/server-based-fee-monitor.js +12 -19
  51. package/src/simulate-tx/fetch-tx-preview.js +0 -2
  52. package/src/simulate-tx/index.js +2 -2
  53. package/src/simulate-tx/simulate-eth-tx.js +24 -11
  54. package/src/simulation/common.js +34 -0
  55. package/src/simulation/create-simulate-message.js +49 -0
  56. package/src/simulation/create-simulate-transactions.js +108 -0
  57. package/src/simulation/estimate-fee.js +14 -0
  58. package/src/simulation/estimate-simple-transfer.js +15 -0
  59. package/src/simulation/get-message-type.js +18 -0
  60. package/src/simulation/simulate-message-api.js +68 -0
  61. package/src/simulation/simulate-transactions-api.js +266 -0
  62. package/src/simulation/simulate-transactions.js +16 -0
  63. package/src/simulation/transactions.js +52 -0
  64. package/src/simulation/try-estimating-changes-locally.js +26 -0
  65. package/src/staking/api/index.js +43 -0
  66. package/src/staking/cached-read.js +33 -0
  67. package/src/staking/ethereum/api.js +166 -34
  68. package/src/staking/ethereum/deps.js +37 -0
  69. package/src/staking/ethereum/everstake.js +113 -0
  70. package/src/staking/ethereum/index.js +4 -3
  71. package/src/staking/ethereum/service.js +682 -181
  72. package/src/staking/ethereum/staking-utils.js +275 -4
  73. package/src/staking/fantom-staking.js +9 -9
  74. package/src/staking/index.js +4 -4
  75. package/src/staking/matic/addresses.js +2 -0
  76. package/src/staking/matic/api.js +69 -49
  77. package/src/staking/matic/index.js +3 -3
  78. package/src/staking/matic/matic-staking-utils.js +243 -5
  79. package/src/staking/matic/matic-staking.md +53 -0
  80. package/src/staking/staking-provider-client.js +1 -1
  81. package/src/staking/utils/index.js +39 -0
  82. package/src/tx-create.js +666 -0
  83. package/src/tx-log/clarity-monitor.js +273 -103
  84. package/src/tx-log/clarity-truncated-history-monitor.js +109 -0
  85. package/src/tx-log/clarity-truncated-history-monitor.md +219 -0
  86. package/src/tx-log/clarity-utils/absolute.js +96 -0
  87. package/src/tx-log/clarity-utils/get-log-items-from-server-tx.js +94 -31
  88. package/src/tx-log/clarity-utils/get-names-of-tokens-transferred-by-server-tx.js +3 -2
  89. package/src/tx-log/clarity-utils/index.js +9 -3
  90. package/src/tx-log/clarity-utils/is-spam-pending-tx.js +13 -0
  91. package/src/tx-log/clarity-utils/normalize-transactions-response.js +65 -0
  92. package/src/tx-log/ethereum-monitor.js +93 -47
  93. package/src/tx-log/ethereum-no-history-monitor.js +52 -41
  94. package/src/tx-log/get-optimistic-txlog-effects.js +379 -0
  95. package/src/tx-log/index.js +5 -3
  96. package/src/tx-log/monitor-utils/check-pending-transactions.js +14 -5
  97. package/src/tx-log/monitor-utils/exclude-unchanged-token-balances.js +1 -1
  98. package/src/tx-log/monitor-utils/extract-balance-from-clarity-account-info.js +108 -0
  99. package/src/tx-log/monitor-utils/get-balance-updates.js +44 -0
  100. package/src/tx-log/monitor-utils/get-batched-rpc-balances.js +47 -0
  101. package/src/tx-log/monitor-utils/get-current-blacklist-status.js +38 -0
  102. package/src/tx-log/monitor-utils/get-current-eip7702-delegation.js +79 -0
  103. package/src/tx-log/monitor-utils/get-derive-transactions-to-check.js +10 -5
  104. package/src/tx-log/monitor-utils/get-fee-amount.js +1 -1
  105. package/src/tx-log/monitor-utils/get-history-from-server.js +1 -1
  106. package/src/tx-log/monitor-utils/get-log-items-from-server-tx.js +75 -33
  107. package/src/tx-log/monitor-utils/get-names-of-tokens-transferred-by-server-tx.js +3 -2
  108. package/src/tx-log/monitor-utils/get-raw-transfer-amount.js +29 -0
  109. package/src/tx-log/monitor-utils/get-transfers-by-token-name.js +3 -2
  110. package/src/tx-log/monitor-utils/get-value-of-transfers.js +10 -12
  111. package/src/tx-log/monitor-utils/index.js +12 -7
  112. package/src/tx-log/monitor-utils/is-unverifiable-pending-tx-expired.js +19 -0
  113. package/src/tx-log/monitor-utils/verify-pending-tx-status-rpc.js +90 -0
  114. package/src/tx-log-staking-processor/asset-staking-tx-data.js +82 -24
  115. package/src/tx-log-staking-processor/index.js +28 -14
  116. package/src/tx-log-staking-processor/utils.js +21 -20
  117. package/src/tx-send/broadcast-error-handler.js +63 -0
  118. package/src/tx-send/index.js +1 -2
  119. package/src/tx-send/nonce-utils.js +97 -0
  120. package/src/tx-send/tx-send.js +94 -288
  121. package/src/tx-sign/index.js +17 -0
  122. package/src/tx-type/index.js +280 -0
  123. package/src/watch-tx.js +36 -0
  124. package/src/web3/createSimulateMessage.js +3 -2
  125. package/src/web3/createSimulateTransactions.js +11 -0
  126. package/src/web3/index.js +3 -1
  127. package/src/etherscan/account.js +0 -49
  128. package/src/etherscan/index.js +0 -28
  129. package/src/etherscan/logs.js +0 -16
  130. package/src/etherscan/proxy.js +0 -47
  131. package/src/etherscan/request.js +0 -26
  132. package/src/etherscan/ws.js +0 -88
  133. package/src/exodus-eth-server/server-factory.js +0 -28
  134. package/src/exodus-eth-server/server-map.js +0 -21
  135. package/src/get-fee-async.js +0 -97
  136. package/src/staking/matic/service.js +0 -437
  137. package/src/staking-api.js +0 -5
  138. package/src/tx-log/clarity-utils/get-derive-data-needed-for-tick.js +0 -12
  139. package/src/tx-log-staking-processor/get-asset-tx-amount.js +0 -26
  140. package/src/tx-send/get-fee-info.js +0 -41
  141. package/src/websocket/index.android.js +0 -2
  142. package/src/websocket/index.ios.js +0 -2
  143. package/src/websocket/index.js +0 -21
@@ -1,27 +1,41 @@
1
- import { assetStakingTxData } from './asset-staking-tx-data'
2
- import { getStakeTxAmount } from './get-asset-tx-amount'
1
+ import { getPolygonUndelegateTxInEthereumTxLog } from '../staking/matic/matic-staking-utils.js'
2
+ import { assetStakingTxProps } from './asset-staking-tx-data.js'
3
3
 
4
- const getTxStakeData = ({ assetName, currency, tx, server }) => {
5
- return assetStakingTxData[assetName]({ tx, currency, server })
4
+ const getAssetExpandedTxLog = async ({ assetName, aci, txs, walletAccount }) => {
5
+ const additionalTxs = []
6
+ if (assetName === 'polygon') {
7
+ const ethereumTxLog = await aci.getTxLog({ walletAccount, assetName: 'ethereum' })
8
+ const polygonUndelegateTxs = getPolygonUndelegateTxInEthereumTxLog(ethereumTxLog)
9
+
10
+ for (const tx of polygonUndelegateTxs) {
11
+ if (txs.has(tx.txId)) continue
12
+
13
+ additionalTxs.push(tx)
14
+ }
15
+ }
16
+
17
+ return additionalTxs
6
18
  }
7
19
 
8
- const processTxLog = async ({ asset, assetClientInterface: aci, walletAccount, batch, server }) => {
20
+ const processTxLog = async ({ asset, assetClientInterface: aci, walletAccount, batch }) => {
9
21
  const { name: assetName, currency } = asset
10
22
  const txs = await aci.getTxLog({ assetName, walletAccount })
11
23
 
12
24
  const newTxs = []
13
25
  for (const tx of txs) {
14
- const txStakeData = getTxStakeData({ assetName, currency, tx, server })
15
- if (txStakeData) {
16
- const txAmount = getStakeTxAmount[assetName]({ tx, currency, type: txStakeData, server })
17
- newTxs.push({
18
- ...tx,
19
- coinAmount: txAmount,
20
- data: { ...tx.data, ...txStakeData },
21
- })
22
- }
26
+ const stakingProps = assetStakingTxProps[assetName]?.({ tx, currency })
27
+ if (!stakingProps) continue
28
+
29
+ newTxs.push({
30
+ ...tx,
31
+ ...stakingProps,
32
+ data: { ...tx.data, ...stakingProps.data },
33
+ })
23
34
  }
24
35
 
36
+ const expandedTxs = await getAssetExpandedTxLog({ assetName, aci, txs, walletAccount })
37
+ newTxs.push(...expandedTxs)
38
+
25
39
  if (newTxs.length > 0) {
26
40
  aci.updateTxLogAndNotifyBatch({ assetName, walletAccount, txs: newTxs, batch })
27
41
  }
@@ -1,9 +1,18 @@
1
- import { EthereumStaking, isEthereumStakingTx, MaticStakingApi } from '../staking'
2
1
  import { asset as ethereum } from '@exodus/ethereum-meta'
3
- import { asset as ethereumholesky } from '@exodus/ethereumholesky-meta'
4
2
 
5
- export const decodePolygonStakingTxInputAmount = ({ tx, server }) => {
6
- const polygonStakingApi = new MaticStakingApi({ server })
3
+ import { EthereumStaking } from '../staking/ethereum/api.js'
4
+ import { isEthereumStakingTx } from '../staking/ethereum/staking-utils.js'
5
+ import { MaticStakingApi } from '../staking/matic/api.js'
6
+
7
+ const polygonStakingApi = new MaticStakingApi()
8
+ const ethereumStakingApi = new EthereumStaking(
9
+ ethereum.name,
10
+ ethereum.currency,
11
+ undefined,
12
+ undefined
13
+ )
14
+
15
+ export const decodePolygonStakingTxInputAmount = (tx) => {
7
16
  const {
8
17
  data: { data: txInput },
9
18
  } = tx
@@ -15,43 +24,35 @@ export const decodePolygonStakingTxInputAmount = ({ tx, server }) => {
15
24
  return amount
16
25
  }
17
26
 
18
- export const calculateTxAmountAndRewardFromStakeTx = ({ tx, currency, server }) => {
19
- const stakedAmount = currency.baseUnit(decodePolygonStakingTxInputAmount({ tx, server }))
20
- const { reward } = tx.data
27
+ export const calculateRewardsFromStakeTx = ({ tx, currency }) => {
28
+ const stakedAmount = currency.baseUnit(decodePolygonStakingTxInputAmount(tx))
29
+ const { rewards } = tx.data
21
30
  // stake tx might have rewards in it,
22
31
  // i.e: https://etherscan.io/tx/0x0a81d266109034a3a70c6f1b9601c105d8caebbd0de652a0619344f9559ae4fa
23
32
  // thus reward = txInputAmount(amount to stake) - tx.coinAmount
24
33
  // tx.coinAmount is already computed from monitor; incoming - outgoing ERC20 token txs
25
34
  const stakeTxContainsReward = !stakedAmount.equals(tx.coinAmount.abs())
26
35
 
27
- if (reward) {
36
+ if (rewards) {
28
37
  // cache rewards
29
- return currency.baseUnit(reward)
38
+ return rewards
30
39
  }
31
40
 
32
41
  if (stakeTxContainsReward) {
33
42
  const txAmount = stakedAmount.sub(tx.coinAmount.abs()).abs()
34
- // eslint-disable-next-line @exodus/mutable/no-param-reassign-prop-only -- TODO: Fix this the next time the file is edited.
35
- tx.data.reward = txAmount.toBaseString()
36
- return txAmount
43
+ // eslint-disable-next-line @exodus/hydra/no-param-reassign-prop-only -- TODO: Fix this the next time the file is edited.
44
+ return txAmount.toDefaultString()
37
45
  }
38
-
39
- // no rewards, set stake tx amount to ZERO
40
- return currency.ZERO
41
46
  }
42
47
 
43
- export const decodeEthLikeStakingTxInputAmount = ({ tx, server }) => {
48
+ export const decodeEthLikeStakingTxInputAmount = (tx) => {
44
49
  const {
45
50
  data: { data: txInput },
46
51
  coinName: assetName,
47
52
  } = tx
48
53
 
49
- const ethereumStakingApi = new EthereumStaking({ asset: ethereum, server })
50
- const ethereumHoleskyStakingApi = new EthereumStaking({ asset: ethereumholesky, server })
51
-
52
54
  const ethereumLikeContracts = {
53
55
  ethereum: ethereumStakingApi,
54
- ethereumholesky: ethereumHoleskyStakingApi,
55
56
  }
56
57
 
57
58
  const ethLikeContract = ethereumLikeContracts[assetName]
@@ -0,0 +1,63 @@
1
+ import { EthLikeError, EVM_ERROR_REASONS, getEvmErrorReason } from '../error-wrapper.js'
2
+ import { transactionExists } from '../eth-like-util.js'
3
+
4
+ /**
5
+ * Handles broadcast errors by parsing the error message and either throwing
6
+ * an appropriate EthLikeError or returning info for retry logic.
7
+ *
8
+ * @param {Error} err - The error from broadcastTx.
9
+ * @param {Object} options - Context for error handling.
10
+ * @param {Object} options.asset - The asset.
11
+ * @param {string} options.txId - The transaction ID.
12
+ * @param {boolean} options.isHardware - Whether this is a hardware wallet.
13
+ * @param {string} options.hint - Hint for the error.
14
+ * @param {boolean} options.isBumpTx - Whether the occur occured whilst bumping a Tx.
15
+ * @param {boolean} options.isRetryNonceTx - If the error occurred during a nonce bump.
16
+ * @returns {Promise<{ shouldRetry: boolean }>} - Returns if nonce too low and can retry.
17
+ * @throws {EthLikeError} - Throws for all other error cases.
18
+ */
19
+ export const handleBroadcastError = async (
20
+ err,
21
+ { asset, txId, isHardware, hint, isBumpTx, isRetryNonceTx }
22
+ ) => {
23
+ const message = err.message
24
+
25
+ const errorInfo = getEvmErrorReason(message) || EVM_ERROR_REASONS.broadcastTxFailed
26
+
27
+ const isNonceTooLow = errorInfo.reason === EVM_ERROR_REASONS.nonceTooLow.reason
28
+ const isAmbiguousError =
29
+ isNonceTooLow || errorInfo.reason === EVM_ERROR_REASONS.transactionUnderpriced.reason
30
+
31
+ if (errorInfo.reason === EVM_ERROR_REASONS.alreadyKnown.reason) {
32
+ console.debug('tx already broadcast')
33
+ return { shouldRetry: false }
34
+ }
35
+
36
+ let txAlreadyExists = false
37
+ if (isAmbiguousError) {
38
+ try {
39
+ txAlreadyExists = await transactionExists({ asset, txId })
40
+ } catch (verifyErr) {
41
+ // Can't verify - fall through to original error handling
42
+ console.warn('Could not verify tx existence:', verifyErr.message)
43
+ }
44
+ }
45
+
46
+ if (txAlreadyExists) {
47
+ console.debug('tx already broadcast')
48
+ return { shouldRetry: false }
49
+ }
50
+
51
+ // NOTE: Don't auto-retry nonce repair for bump/replacement txs.
52
+ // A replacement must keep the *same nonce* as the tx it's replacing.
53
+ // If we "fix" a bump tx by advancing the nonce, we create a brand-new tx instead of replacing the pending one.
54
+ if (isNonceTooLow && !isHardware && !isBumpTx && !isRetryNonceTx) return { shouldRetry: true }
55
+
56
+ throw new EthLikeError({
57
+ message: err.message,
58
+ errorReasonInfo: errorInfo,
59
+ hint,
60
+ traceId: err.traceId,
61
+ baseAssetName: asset.baseAsset.name,
62
+ })
63
+ }
@@ -1,2 +1 @@
1
- export { default as txSendFactory } from './tx-send'
2
- export { default as getFeeInfo } from './get-fee-info'
1
+ export { default as txSendFactory } from './tx-send.js'
@@ -0,0 +1,97 @@
1
+ import assert from 'minimalistic-assert'
2
+
3
+ import { getNonce } from '../eth-like-util.js'
4
+ import { getLatestCanonicalAbsoluteNonceTx } from '../tx-log/clarity-utils/absolute.js'
5
+
6
+ export const BLOCK_TAG_LATEST = 'latest'
7
+ export const BLOCK_TAG_PENDING = 'pending'
8
+
9
+ const VALID_BLOCK_TAGS = new Set([BLOCK_TAG_LATEST, BLOCK_TAG_PENDING])
10
+
11
+ // NOTE: We do not yet support tags at arbitrary block heights.
12
+ const assertValidBlockTag = (tag) => assert(VALID_BLOCK_TAGS.has(tag), `invalid tag "${tag}"`)
13
+
14
+ export const resolveNonce = async ({
15
+ asset,
16
+ forceFromNode,
17
+ fromAddress,
18
+ txLog, // TxSet expected. Array only for testing purposes.
19
+ accountState,
20
+ tag = BLOCK_TAG_LATEST,
21
+ useAbsoluteNonce,
22
+ }) => {
23
+ assertValidBlockTag(tag)
24
+
25
+ if (asset.baseAsset?.api?.features?.noHistory || forceFromNode) {
26
+ return getNonce({ asset: asset.baseAsset, address: fromAddress, tag })
27
+ }
28
+
29
+ const nonceFromTxLog = getNonceFromTxLog({ txLog, useAbsoluteNonce, tag })
30
+
31
+ // `accountState.nonce` is the single source of truth — clarity-truncated-history
32
+ // collapses both `account_balance` and `truncated` source nonces into this field.
33
+ const nonceFromAccountState = accountState?.nonce || 0
34
+
35
+ return Math.max(nonceFromTxLog, nonceFromAccountState)
36
+ }
37
+
38
+ const getLatestTxWithNonceChange = ({ reversedTxLog }) => {
39
+ assert(reversedTxLog, 'expected reversedTxLog')
40
+
41
+ const maybeLatestAbsoluteNonceTx = getLatestCanonicalAbsoluteNonceTx({
42
+ reversedTxLog,
43
+ })
44
+
45
+ if (maybeLatestAbsoluteNonceTx) return maybeLatestAbsoluteNonceTx
46
+
47
+ for (const tx of reversedTxLog) {
48
+ if (tx.data.nonceChange) {
49
+ return tx
50
+ }
51
+ }
52
+ }
53
+
54
+ /**
55
+ * Computes the next nonce from the given `txLog`.
56
+ *
57
+ * @param {Object} options
58
+ * @param {import('@exodus/models').TxSet} options.txLog - A `TxSet` from
59
+ * `@exodus/models`, NOT a plain `Array`.
60
+ * @param {boolean} [options.useAbsoluteNonce]
61
+ * @param {string} [options.tag]
62
+ * @returns {number}
63
+ */
64
+ const getNonceFromTxLog = ({ txLog, useAbsoluteNonce, tag }) => {
65
+ if (!txLog || txLog.size === 0) return 0
66
+
67
+ let absoluteNonce = 0
68
+
69
+ if (useAbsoluteNonce) {
70
+ // NOTE: `TxSet#reverse()` returns a one-shot iterable yielding txs
71
+ // in reverse order — it is NOT an in-place reversal like
72
+ // `Array.prototype.reverse()`, so the caller's `txLog` is
73
+ // left untouched.
74
+ const reversedTxLog = txLog.reverse()
75
+ const maybeLatestTxWithNonceChange = getLatestTxWithNonceChange({ reversedTxLog })
76
+
77
+ if (maybeLatestTxWithNonceChange) {
78
+ // NOTE: This is the latest nonce currently confirmed by
79
+ // Clarity, which lags behind `CLARITY_MIN_CONFIRMS`
80
+ // depth:
81
+ //
82
+ // https://github.com/ExodusMovement/clarity/blob/4a6ea30fce33246d1ef1440e61b0a84b876900d6/deployment/eth-clarity-indexer/values.yaml#L34
83
+ absoluteNonce = parseInt(maybeLatestTxWithNonceChange.data.nonceChange.to, 10)
84
+ }
85
+ }
86
+
87
+ // TODO: can't we iterate in reverse up to the first (few) nonce(s)?
88
+ const nonceFromLog = [...txLog]
89
+ .filter((tx) => tx.sent && !tx.dropped && tx.data.nonce != null)
90
+ // NOTE: If we're only considering the `'latest'` block `tag`,
91
+ // then we should not take into account unconfirmed
92
+ // transactions when computing the `nonce`.
93
+ .filter((tx) => tag !== BLOCK_TAG_LATEST || !tx.pending)
94
+ .reduce((nonce, tx) => Math.max(tx.data.nonce + 1, nonce), 0)
95
+
96
+ return Math.max(absoluteNonce, nonceFromLog)
97
+ }