@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.
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
@@ -0,0 +1,60 @@
1
+ import { safeString } from '@exodus/safe-string'
2
+ import SolidityContract from '@exodus/solidity-contract'
3
+ import assert from 'minimalistic-assert'
4
+
5
+ import { EthLikeError, EVM_ERROR_REASONS } from './error-wrapper.js'
6
+ import { fetchGasLimit } from './gas-estimation.js'
7
+
8
+ export const getNftArguments = async ({ asset, nft, fromAddress, toAddress }) => {
9
+ assert(asset, 'asset is required')
10
+ assert(nft, 'nft is required')
11
+ assert(fromAddress, 'fromAddress is required')
12
+ assert(toAddress, 'toAddress is required')
13
+ const { contractAddress, tokenId, contractType } = nft
14
+
15
+ const txInputs = []
16
+
17
+ if (!contractType || contractType === 'erc1155') {
18
+ const txInput = `0x${SolidityContract.simpleErc1155(contractAddress)
19
+ .safeTransferFrom.build(fromAddress, toAddress, tokenId, 1, [])
20
+ .toString('hex')}`
21
+ txInputs.push({ txInput, contractType: 'erc1155' })
22
+ }
23
+
24
+ if (!contractType || contractType === 'erc721') {
25
+ const txInput = `0x${SolidityContract.simpleErc721(contractAddress)
26
+ .safeTransferFrom.build(fromAddress, toAddress, tokenId)
27
+ .toString('hex')}`
28
+ txInputs.push({ txInput, contractType: 'erc721' })
29
+ }
30
+
31
+ const errors = [`Cannot fetch gas for ${asset.name}`]
32
+ const { txInput, gasLimit } = await Promise.any(
33
+ txInputs.map(async ({ txInput, contractType }) => {
34
+ try {
35
+ const gasLimit = await fetchGasLimit({
36
+ asset,
37
+ fromAddress,
38
+ contractAddress,
39
+ txInput,
40
+ })
41
+ return { txInput, gasLimit }
42
+ } catch (e) {
43
+ errors.push(`ContractType ${contractType} nft input ${txInput}. ${e.message}`)
44
+ throw e
45
+ }
46
+ })
47
+ ).catch((e) => {
48
+ throw new EthLikeError({
49
+ message: errors.join('\n'),
50
+ errorReasonInfo: EVM_ERROR_REASONS.fetchGasLimitFailed,
51
+ hint: safeString`getNftArguments`,
52
+ traceId: e.traceId,
53
+ })
54
+ })
55
+ return {
56
+ contractAddress,
57
+ gasLimit,
58
+ txInput,
59
+ }
60
+ }
@@ -1,8 +1,40 @@
1
+ import BN from 'bn.js'
2
+
1
3
  export function fromHexToString(value) {
2
4
  return fromHexToBigInt(value).toString()
3
5
  }
4
6
 
5
7
  export function fromHexToBigInt(value) {
6
- const hex = value.startsWith('0x') ? value : '0x' + value
7
- return BigInt(hex)
8
+ return BigInt(addHexPrefix(value))
9
+ }
10
+
11
+ export const addHexPrefix = (str) => {
12
+ if (typeof str !== 'string') {
13
+ return str
14
+ }
15
+
16
+ return str.startsWith('0x') ? str : '0x' + str
17
+ }
18
+
19
+ export const removeHexPrefix = (str) => {
20
+ if (typeof str !== 'string' || str === '') {
21
+ return str
22
+ }
23
+
24
+ return str.startsWith('0x') ? str.slice(2) : str
25
+ }
26
+
27
+ export const splitIn32BytesArray = (output) => {
28
+ const trimmed = removeHexPrefix(output)
29
+ const result = trimmed.match(/^([\da-f]{64})+$/gi)
30
+ if (result) {
31
+ return trimmed.match(/.{64}/g)
32
+ }
33
+
34
+ throw new Error(`${output} is not a 64 bytes hex that can be split`)
8
35
  }
36
+
37
+ export const removeLeadingZeroes = (str) => str.replace(/^0+/, '')
38
+
39
+ // Deprecated, use BigInt
40
+ export const fromHexToBN = (str) => new BN(removeLeadingZeroes(removeHexPrefix(str)), 16)
@@ -1,8 +1,11 @@
1
- import * as ethUtil from '@exodus/ethereumjs-util'
2
- import { createEthereumJsTx, createContract } from '@exodus/ethereum-lib'
3
- import { fromHexToBigInt } from '../number-utils'
1
+ import { createContract, createEthereumJsTx } from '@exodus/ethereum-lib'
2
+ import { bufferToHex } from '@exodus/ethereumjs/util'
3
+ import { safeString } from '@exodus/safe-string'
4
4
  import assert from 'minimalistic-assert'
5
5
 
6
+ import { EVM_ERROR_REASONS, withErrorReason } from '../error-wrapper.js'
7
+ import { fromHexToBigInt } from '../number-utils.js'
8
+
6
9
  export const estimateL1DataFeeFactory = ({ l1GasOracleAddress, server }) => {
7
10
  assert(l1GasOracleAddress, 'l1GasOracleAddress is required')
8
11
  assert(server, 'server is required')
@@ -12,8 +15,13 @@ export const estimateL1DataFeeFactory = ({ l1GasOracleAddress, server }) => {
12
15
  const serialized = ethjsTx.serialize()
13
16
  const callData = gasContract.getL1Fee.build(serialized)
14
17
  const buffer = Buffer.from(callData)
15
- const data = ethUtil.bufferToHex(buffer)
16
- const hex = await server.ethCall({ to: l1GasOracleAddress, data }, 'latest')
18
+ const data = bufferToHex(buffer)
19
+ const hex = await withErrorReason({
20
+ promise: server.ethCall({ to: l1GasOracleAddress, data }, 'latest'),
21
+ errorReasonInfo: EVM_ERROR_REASONS.ethCallFailed,
22
+ hint: safeString`estimateL1DataFee`,
23
+ baseAssetName: server.baseAssetName,
24
+ })
17
25
  const l1DataFee = fromHexToBigInt(hex)
18
26
  const padFee = l1DataFee / BigInt(4)
19
27
  const maxL1DataFee = l1DataFee + padFee
@@ -0,0 +1,58 @@
1
+ import sendValidationModel from '@exodus/send-validation-model'
2
+ import assert from 'minimalistic-assert'
3
+
4
+ const { FIELDS, PRIORITY_LEVELS, VALIDATION_TYPES } = sendValidationModel
5
+
6
+ export const SCAM_RECIPIENT = 'SCAM_RECIPIENT'
7
+ export const SCAM_RECIPIENT_MESSAGE =
8
+ 'This recipient was flagged as risky. Please verify before sending.'
9
+
10
+ const toWalletAccountId = (fromWalletAccount) =>
11
+ typeof fromWalletAccount === 'string' ? fromWalletAccount : fromWalletAccount?.toString?.()
12
+
13
+ export const createSendValidations = ({ assetClientInterface, checkTx }) => {
14
+ assert(assetClientInterface, 'assetClientInterface is required')
15
+ assert(typeof checkTx === 'function', 'checkTx must be a function')
16
+
17
+ const scamRecipientValidator = {
18
+ id: SCAM_RECIPIENT,
19
+ type: VALIDATION_TYPES.WARN,
20
+ field: FIELDS.ADDRESS,
21
+ priority: PRIORITY_LEVELS.MIDDLE,
22
+ validateAndGetMessage: async ({ asset, destinationAddress, sendAmount, fromWalletAccount }) => {
23
+ // M1: native sends only. M2 will cover tokens/approvals/contract calls
24
+ // via a post-createTx call site on the review screen.
25
+ if (!asset || asset.name !== asset.baseAsset.name) return
26
+ if (!destinationAddress || !sendAmount) return
27
+ if (sendAmount.isZero) return
28
+
29
+ const isValidAddress = await asset.address.validate(destinationAddress)
30
+ if (!isValidAddress) return
31
+
32
+ const walletAccountId = toWalletAccountId(fromWalletAccount)
33
+ if (!walletAccountId) return
34
+
35
+ const fromAddress = await assetClientInterface.getReceiveAddress({
36
+ assetName: asset.name,
37
+ walletAccount: walletAccountId,
38
+ useCache: true,
39
+ })
40
+ if (!fromAddress) return
41
+
42
+ // Native send: empty calldata, no signed tx hash to pass yet.
43
+ const result = await checkTx({
44
+ chain: asset.chainId,
45
+ fromAddress,
46
+ toAddress: destinationAddress,
47
+ value: sendAmount.toBaseString({ unit: false }),
48
+ input: '0x',
49
+ })
50
+
51
+ if (result?.action !== 'WARN') return
52
+
53
+ return result.reason || SCAM_RECIPIENT_MESSAGE
54
+ },
55
+ }
56
+
57
+ return [scamRecipientValidator]
58
+ }
@@ -1,7 +1,7 @@
1
1
  import { FeeMonitor } from '@exodus/asset-lib'
2
2
  import assert from 'minimalistic-assert'
3
- import { DEFAULT_FEE_MONITOR_INTERVAL } from '@exodus/ethereum-lib'
4
- import { fromHexToString } from './number-utils'
3
+
4
+ import { calculateEthLikeFeeMonitorUpdate } from './fee-utils.js'
5
5
 
6
6
  /**
7
7
  * Generic eth server based fee monitor.
@@ -12,39 +12,32 @@ import { fromHexToString } from './number-utils'
12
12
  *
13
13
  * const api = {
14
14
  * ...
15
- * createFeeMonitor: serverBasedFeeMonitorFactoryFactory({ interval: '50s', server, assetName: base.name }),
15
+ * createFeeMonitor: serverBasedFeeMonitorFactoryFactory({ interval: '50s', server, asset, aci }),
16
16
  * ...
17
17
  * }
18
18
  */
19
19
 
20
- export const serverBasedFeeMonitorFactoryFactory = ({
21
- assetName,
22
- interval = DEFAULT_FEE_MONITOR_INTERVAL,
23
- eip1559Enabled,
24
- server,
25
- }) => {
26
- assert(assetName, 'assetName is required')
20
+ export const serverBasedFeeMonitorFactoryFactory = ({ asset, interval, server, aci }) => {
21
+ assert(asset, 'asset is required')
27
22
  assert(server, 'server is required')
23
+ assert(aci, 'aci is required')
28
24
 
29
25
  const FeeMonitorClass = class ServerBaseEthereumFeeMonitor extends FeeMonitor {
30
26
  constructor({ updateFee }) {
31
27
  assert(updateFee, 'updateFee is required')
32
28
  super({
33
29
  updateFee,
34
- assetName,
30
+ assetName: asset.name,
35
31
  interval,
36
32
  })
37
33
  }
38
34
 
39
35
  async fetchFee() {
40
- const gasPrice = fromHexToString(await server.getGasPrice())
41
-
42
- const baseFeePerGas = eip1559Enabled ? `${await server.getBaseFeePerGas()} wei` : undefined
43
-
44
- return {
45
- gasPrice: `${gasPrice} wei`,
46
- baseFeePerGas,
47
- }
36
+ return calculateEthLikeFeeMonitorUpdate({
37
+ assetClientInterface: aci,
38
+ feeAsset: asset,
39
+ fetchedGasPrices: await server.getGasPriceEstimation(),
40
+ })
48
41
  }
49
42
  }
50
43
  return (...args) => new FeeMonitorClass(...args)
@@ -1,5 +1,3 @@
1
- import { fetch } from '@exodus/fetch'
2
-
3
1
  const ETHEREUM_TX_PREVIEW_API = 'https://simulation.a.exodus.io/simulate'
4
2
 
5
3
  export async function fetchTxPreview(transaction) {
@@ -1,2 +1,2 @@
1
- export * from './fetch-tx-preview'
2
- export * from './simulate-eth-tx'
1
+ export * from './fetch-tx-preview.js'
2
+ export * from './simulate-eth-tx.js'
@@ -1,18 +1,29 @@
1
- import assert from 'minimalistic-assert'
2
- import { asset as ethereum } from '@exodus/ethereum-meta'
3
1
  import {
2
+ APPROVE_METHOD_ID,
4
3
  checkIsERC721InputData,
5
4
  checkIsNFTInputData,
6
- APPROVE_METHOD_ID,
7
5
  } from '@exodus/ethereum-lib'
6
+ import { asset as ethereum } from '@exodus/ethereum-meta'
8
7
  import SolidityContract from '@exodus/solidity-contract'
8
+ import assert from 'minimalistic-assert'
9
9
 
10
- import { fetchTxPreview } from './fetch-tx-preview'
11
- import { getERC20Params } from '../eth-like-util'
10
+ import { getERC20Params } from '../eth-like-util.js'
11
+ import { fromHexToBigInt } from '../number-utils.js'
12
+ import { fetchTxPreview } from './fetch-tx-preview.js'
12
13
 
13
14
  const ethDecimals = ethereum.units.ETH
14
15
 
15
- const ethHexToInt = (hexValue) => parseInt(hexValue, '16')
16
+ // Convert a hex transaction field (e.g. "0x10a741a462780000" for 1.2 ETH in wei)
17
+ // to a precision-preserving decimal string. `parseInt` would silently truncate
18
+ // values above Number.MAX_SAFE_INTEGER (~9e15), and Ethereum wei amounts routinely
19
+ // exceed that (1 ETH = 1e18 wei).
20
+ const ethHexToDecimalString = (hexValue) => {
21
+ if (hexValue === undefined || hexValue === null || hexValue === '' || hexValue === '0x') {
22
+ return '0'
23
+ }
24
+
25
+ return fromHexToBigInt(hexValue).toString()
26
+ }
16
27
 
17
28
  async function getAssetSymbolFromContract(contractAddress, asset) {
18
29
  const { symbol: assetSymbol } = await getERC20Params({
@@ -169,14 +180,16 @@ export async function retrieveSideEffects({ transaction, asset, shouldSimulate =
169
180
 
170
181
  if (!transaction.to) throw new Error(`'to' field is missing in the TX object`)
171
182
 
183
+ const txValueDecimal = ethHexToDecimalString(transaction.value)
184
+
172
185
  const blocknativeTxObject = {
173
186
  to: transaction.to,
174
187
  input: transaction.data,
175
188
  from: transaction.from,
176
- value: ethHexToInt(transaction.value),
177
- gas: ethHexToInt(transaction.gas),
178
- maxFeePerGas: ethHexToInt(transaction.maxFeePerGas),
179
- maxPriorityFeePerGas: ethHexToInt(transaction.maxPriorityFeePerGas),
189
+ value: txValueDecimal,
190
+ gas: ethHexToDecimalString(transaction.gas),
191
+ maxFeePerGas: ethHexToDecimalString(transaction.maxFeePerGas),
192
+ maxPriorityFeePerGas: ethHexToDecimalString(transaction.maxPriorityFeePerGas),
180
193
  }
181
194
 
182
195
  try {
@@ -224,7 +237,7 @@ export async function retrieveSideEffects({ transaction, asset, shouldSimulate =
224
237
  if (willSend.length === 0) {
225
238
  willSend.push({
226
239
  symbol: 'ETH',
227
- balance: `-${ethHexToInt(transaction.value)}`,
240
+ balance: `-${txValueDecimal}`,
228
241
  assetType: 'ether',
229
242
  decimal: ethDecimals,
230
243
  })
@@ -0,0 +1,34 @@
1
+ import BN from 'bn.js'
2
+
3
+ export const BLOWFISH_EVM_CHAINS = {
4
+ avalanchec: {
5
+ network: 'avalanche',
6
+ chain: 'mainnet',
7
+ },
8
+ basemainnet: {
9
+ network: 'base',
10
+ chain: 'mainnet',
11
+ },
12
+ bsc: {
13
+ network: 'bnb',
14
+ chain: 'mainnet',
15
+ },
16
+ ethereum: {
17
+ network: 'ethereum',
18
+ chain: 'mainnet',
19
+ },
20
+ ethereumarbone: {
21
+ network: 'arbitrum',
22
+ chain: 'one',
23
+ },
24
+ matic: {
25
+ network: 'polygon',
26
+ chain: 'mainnet',
27
+ },
28
+ optimism: {
29
+ network: 'optimism',
30
+ chain: 'mainnet',
31
+ },
32
+ }
33
+
34
+ export const MAX_INT256_SOLIDITY = new BN(2).pow(new BN(255)).subn(1)
@@ -0,0 +1,49 @@
1
+ import { tryParsingSIWE } from '@exodus/web3-utils'
2
+ import assert from 'minimalistic-assert'
3
+
4
+ import { getMessageType, MessageTypeEnum } from './get-message-type.js'
5
+ import { simulateMessageApi } from './simulate-message-api.js'
6
+
7
+ export const createSimulateMessage =
8
+ ({
9
+ apiEndpoint = 'https://simulation.a.exodus.io/simulateMessage',
10
+ headers = {
11
+ 'X-Api-Version': '2023-06-05',
12
+ },
13
+ } = {}) =>
14
+ async ({ message, url, asset, address }) => {
15
+ assert(url instanceof URL, "'url' should be an instance of the URL object.")
16
+
17
+ const simulationResult = {
18
+ baseAssetName: asset.baseAssetName,
19
+ action: 'NONE',
20
+ }
21
+
22
+ const messageType = getMessageType(message)
23
+ if (messageType === MessageTypeEnum.Unknown) {
24
+ return simulationResult
25
+ }
26
+
27
+ if (messageType === MessageTypeEnum.RawMessage) {
28
+ tryParsingSIWE({
29
+ address,
30
+ message,
31
+ url,
32
+ simulationResult,
33
+ })
34
+ }
35
+
36
+ await simulateMessageApi({
37
+ address,
38
+ message: {
39
+ message,
40
+ messageType,
41
+ },
42
+ url,
43
+ apiEndpoint,
44
+ headers,
45
+ simulationResult,
46
+ })
47
+
48
+ return simulationResult
49
+ }
@@ -0,0 +1,108 @@
1
+ import { createEmptySimulationResult } from '@exodus/web3-utils'
2
+
3
+ import { estimateFee } from './estimate-fee.js'
4
+ import { simulateTransactionsApi } from './simulate-transactions-api.js'
5
+ import { decodeRecipientAddresses, getDisplayDetails, getTxFeeDetails } from './transactions.js'
6
+ import { tryEstimatingChangesLocally } from './try-estimating-changes-locally.js'
7
+
8
+ export const createSimulateTransactions =
9
+ ({ apiEndpoint, headers: headersFromDeps }) =>
10
+ async ({
11
+ transactions,
12
+ asset,
13
+ origin,
14
+ blockNumber,
15
+ recipientAddress,
16
+ headers: headersFromParams,
17
+ overrideApiEndpoint,
18
+ }) => {
19
+ const headers = { ...headersFromDeps, ...headersFromParams }
20
+
21
+ const simulationResult = createEmptySimulationResult({
22
+ asset,
23
+ })
24
+
25
+ if (transactions.length === 0) {
26
+ const message = 'No transactions provided. Cannot simulate transaction batch.'
27
+ simulationResult.warnings.push({ kind: 'INTERNAL_ERROR', severity: 'HIGH', message })
28
+ simulationResult.metadata.humanReadableError = message
29
+ return simulationResult
30
+ }
31
+
32
+ const firstFrom = transactions[0].from.toLowerCase()
33
+ const hasMismatchedFrom = transactions.some((tx) => tx.from.toLowerCase() !== firstFrom)
34
+ if (hasMismatchedFrom) {
35
+ const message =
36
+ 'All transactions in a batch must have the same "from" address. Cannot simulate transaction batch.'
37
+ simulationResult.warnings.push({ kind: 'INTERNAL_ERROR', severity: 'HIGH', message })
38
+ simulationResult.metadata.humanReadableError = message
39
+ return simulationResult
40
+ }
41
+
42
+ const hasInvalidTransaction = transactions.some((tx) => !tx.to)
43
+ if (hasInvalidTransaction) {
44
+ const message =
45
+ 'One or more transactions are missing required "to" address. Cannot simulate transaction batch.'
46
+ simulationResult.warnings.push({ kind: 'INTERNAL_ERROR', severity: 'HIGH', message })
47
+ simulationResult.metadata.humanReadableError = message
48
+ return simulationResult
49
+ }
50
+
51
+ simulationResult.recipientAddresses = transactions.map((tx) => decodeRecipientAddresses(tx))
52
+ const feeCurrency = asset.feeAsset.currency
53
+
54
+ const simulationTransactionInternalResult = await simulateTransactionsApi({
55
+ asset,
56
+ transactions,
57
+ apiEndpoint: overrideApiEndpoint || apiEndpoint,
58
+ origin,
59
+ blockNumber,
60
+ recipientAddress,
61
+ simulationResult,
62
+ headers,
63
+ })
64
+
65
+ if (simulationTransactionInternalResult.kind === 'error') {
66
+ if (transactions.length > 1) {
67
+ const message = 'Cannot locally simulate batched transactions.'
68
+ simulationResult.warnings.push({ kind: 'INTERNAL_ERROR', severity: 'HIGH', message })
69
+ simulationResult.metadata.humanReadableError = message
70
+ } else {
71
+ const transaction = transactions[0]
72
+ const simulatedLocally = tryEstimatingChangesLocally({
73
+ asset,
74
+ simulationResult,
75
+ transaction,
76
+ })
77
+ if (simulatedLocally) {
78
+ simulationResult.metadata.simulatedLocally = true
79
+ simulationResult.balanceChanges.willPayFee.push({
80
+ balance: estimateFee({ feeCurrency, transaction }),
81
+ feeDetails: getTxFeeDetails(transaction),
82
+ })
83
+ } else {
84
+ const message = 'Balance changes cannot be estimated.'
85
+ simulationResult.warnings.push({ kind: 'INTERNAL_ERROR', severity: 'HIGH', message })
86
+ simulationResult.metadata.humanReadableError = message
87
+ }
88
+ }
89
+
90
+ if (simulationTransactionInternalResult.errorMessage) {
91
+ simulationResult.metadata.humanReadableError =
92
+ simulationTransactionInternalResult.errorMessage
93
+ }
94
+
95
+ return simulationResult
96
+ }
97
+
98
+ for (const transaction of transactions) {
99
+ simulationResult.balanceChanges.willPayFee.push({
100
+ balance: estimateFee({ feeCurrency, transaction }),
101
+ feeDetails: getTxFeeDetails(transaction),
102
+ })
103
+ }
104
+
105
+ simulationResult.displayDetails = getDisplayDetails(simulationResult.balanceChanges.willApprove)
106
+
107
+ return simulationResult
108
+ }
@@ -0,0 +1,14 @@
1
+ import { hexToBN } from '@exodus/web3-utils'
2
+
3
+ export const estimateFee = ({ feeCurrency, transaction }) => {
4
+ const gas = hexToBN(transaction.gas)
5
+
6
+ if (transaction.maxFeePerGas) {
7
+ const maxFeePerGas = hexToBN(transaction.maxFeePerGas)
8
+ const maximumFee = gas.mul(maxFeePerGas)
9
+ return feeCurrency.baseUnit(maximumFee.toString())
10
+ }
11
+
12
+ const gasPrice = hexToBN(transaction.gasPrice)
13
+ return feeCurrency.baseUnit(gas.mul(gasPrice).toString())
14
+ }
@@ -0,0 +1,15 @@
1
+ import { hexToBN } from '@exodus/web3-utils'
2
+
3
+ import { isSimpleTransfer } from './transactions.js'
4
+
5
+ export class NotSimpleTransferError extends Error {}
6
+
7
+ export const estimateSimpleTransfer = ({ baseAsset, transaction }) => {
8
+ if (!isSimpleTransfer(transaction)) {
9
+ throw new NotSimpleTransferError('The transaction input should be empty ("0x").')
10
+ }
11
+
12
+ const txValue = hexToBN(transaction.value || '0x0').toString()
13
+
14
+ return baseAsset.currency.baseUnit(txValue)
15
+ }
@@ -0,0 +1,18 @@
1
+ export const MessageTypeEnum = {
2
+ RawMessage: 0,
3
+ TypedData: 1,
4
+ Unknown: 2,
5
+ }
6
+
7
+ export const getMessageType = (message) => {
8
+ if (typeof message !== 'string') {
9
+ return MessageTypeEnum.Unknown
10
+ }
11
+
12
+ try {
13
+ JSON.parse(message)
14
+ return MessageTypeEnum.TypedData
15
+ } catch {
16
+ return MessageTypeEnum.RawMessage
17
+ }
18
+ }
@@ -0,0 +1,68 @@
1
+ import { makeSimulationAPICall } from '@exodus/web3-utils'
2
+
3
+ import { BLOWFISH_EVM_CHAINS } from './common.js'
4
+ import { MessageTypeEnum } from './get-message-type.js'
5
+
6
+ const convertToAPIPayload = ({ address, message, url }) => {
7
+ const restParameters = {
8
+ metadata: { origin: url.href },
9
+ userAccount: address,
10
+ }
11
+
12
+ if (message.messageType === MessageTypeEnum.TypedData) {
13
+ return {
14
+ message: {
15
+ kind: 'SIGN_TYPED_DATA',
16
+ data: JSON.parse(message.message),
17
+ },
18
+ ...restParameters,
19
+ }
20
+ }
21
+
22
+ return {
23
+ message: {
24
+ kind: 'SIGN_MESSAGE',
25
+ rawMessage: message.message,
26
+ },
27
+ ...restParameters,
28
+ }
29
+ }
30
+
31
+ export const handleActionResponse = ({ simulationResult, response }) => {
32
+ if (['BLOCK', 'WARN'].includes(response.action)) {
33
+ // eslint-disable-next-line @exodus/hydra/no-param-reassign-prop-only
34
+ simulationResult.action = response.action
35
+ }
36
+ }
37
+
38
+ export const simulateMessageApi = async ({
39
+ address,
40
+ apiEndpoint,
41
+ message,
42
+ url,
43
+ headers,
44
+ simulationResult,
45
+ }) => {
46
+ const chainInfo = BLOWFISH_EVM_CHAINS[simulationResult.baseAssetName]
47
+ if (!chainInfo) {
48
+ return
49
+ }
50
+
51
+ const { network, chain } = chainInfo
52
+
53
+ const payload = convertToAPIPayload({ address, message, url })
54
+
55
+ const response = await makeSimulationAPICall({
56
+ url: apiEndpoint,
57
+ chain,
58
+ network,
59
+ payload,
60
+ headers,
61
+ })
62
+
63
+ if (!response) {
64
+ return
65
+ }
66
+
67
+ handleActionResponse({ simulationResult, response })
68
+ }