@exodus/web3-ethereum-utils 4.5.1 → 4.7.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.
@@ -1,3 +1,3 @@
1
1
  import type { EthAggregatedTransactionSimulationResult, EthSimulateTransactionParams } from './types.js';
2
2
  import type { CreateSimulateTransactionsParams } from '@exodus/web3-types';
3
- export declare const createSimulateTransactions: ({ apiEndpoint, headers }: Required<CreateSimulateTransactionsParams>) => ({ transactions, asset, origin, overrideApiEndpoint, }: EthSimulateTransactionParams) => Promise<EthAggregatedTransactionSimulationResult>;
3
+ export declare const createSimulateTransactions: ({ apiEndpoint, headers: headersFromDeps }: Required<CreateSimulateTransactionsParams>) => ({ transactions, asset, origin, headers: headersFromParams, overrideApiEndpoint, }: EthSimulateTransactionParams) => Promise<EthAggregatedTransactionSimulationResult>;
@@ -3,20 +3,41 @@ import { estimateFee } from './simulation/estimateFee.js';
3
3
  import { simulateTransactions } from './simulation/simulateTransactions.js';
4
4
  import { tryEstimatingChangesLocally } from './simulation/tryEstimatingChangesLocally.js';
5
5
  import { decodeRecipientAddresses, getDisplayDetails, getTxFeeDetails } from './transactions.js';
6
- export const createSimulateTransactions = ({ apiEndpoint, headers }) => async ({ transactions, asset, origin, overrideApiEndpoint, }) => {
7
- // Ethereum transactions from web3 providers are always one item in an array.
8
- const transaction = transactions[0];
6
+ export const createSimulateTransactions = ({ apiEndpoint, headers: headersFromDeps }) => async ({ transactions, asset, origin, headers: headersFromParams, overrideApiEndpoint, }) => {
7
+ const headers = { ...headersFromDeps, ...headersFromParams };
9
8
  const simulationResult = createEmptySimulationResult({
10
9
  asset,
11
10
  });
12
- simulationResult.balanceChanges.willPayFee.push({
13
- balance: estimateFee({ asset, transaction }),
14
- feeDetails: getTxFeeDetails(transaction),
15
- });
16
- if (!transaction.to) {
11
+ if (transactions.length === 0) {
12
+ const message = 'No transactions provided. Cannot simulate transaction batch.';
13
+ simulationResult.warnings.push({ kind: 'INTERNAL_ERROR', severity: 'HIGH', message });
14
+ simulationResult.metadata.humanReadableError = message;
15
+ return simulationResult;
16
+ }
17
+ // Ensure all transactions share the same "from" address
18
+ // Added this because simulateTransactions makes this assumption and would give us invalid results if it's not the case
19
+ // If we need this (which is unlikely) we can remove this check and update simulateTransactions to handle multiple from addresses
20
+ const firstFrom = transactions[0].from.toLowerCase();
21
+ const hasMismatchedFrom = transactions.some((tx) => tx.from.toLowerCase() !== firstFrom);
22
+ if (hasMismatchedFrom) {
23
+ const message = 'All transactions in a batch must have the same "from" address. Cannot simulate transaction batch.';
24
+ simulationResult.warnings.push({ kind: 'INTERNAL_ERROR', severity: 'HIGH', message });
25
+ // add human readable error
26
+ simulationResult.metadata.humanReadableError = message;
27
+ return simulationResult;
28
+ }
29
+ // Check if ALL transactions have a 'to' address - if any are missing, return early
30
+ // Transactions are linked, so if one is invalid, the entire batch is invalid
31
+ const hasInvalidTransaction = transactions.some(tx => !tx.to);
32
+ if (hasInvalidTransaction) {
33
+ const message = 'One or more transactions are missing required "to" address. Cannot simulate transaction batch.';
34
+ simulationResult.warnings.push({ kind: 'INTERNAL_ERROR', severity: 'HIGH', message });
35
+ simulationResult.metadata.humanReadableError = message;
17
36
  return simulationResult;
18
37
  }
38
+ // Fees are added only after a successful simulation (remote or local)
19
39
  simulationResult.recipientAddresses = transactions.map((tx) => decodeRecipientAddresses(tx));
40
+ // Mutates the simulationResult, simulationTransactionInternalResult is only for checking the kind of response
20
41
  const simulationTransactionInternalResult = await simulateTransactions({
21
42
  asset,
22
43
  transactions,
@@ -25,24 +46,48 @@ export const createSimulateTransactions = ({ apiEndpoint, headers }) => async ({
25
46
  simulationResult,
26
47
  headers,
27
48
  });
49
+ // Blowfish simulation failed, try local simulation
28
50
  if (simulationTransactionInternalResult.kind === 'error') {
51
+ if (transactions.length > 1) {
52
+ // For batched transactions, don't attempt local simulation
53
+ const message = 'Cannot locally simulate batched transactions.';
54
+ simulationResult.warnings.push({ kind: 'INTERNAL_ERROR', severity: 'HIGH', message });
55
+ simulationResult.metadata.humanReadableError = message;
56
+ return simulationResult;
57
+ }
58
+ // Local simulation also mutates the simulationResult, so we need to check if it was successful
29
59
  const simulatedLocally = tryEstimatingChangesLocally({
30
60
  asset,
31
61
  simulationResult,
32
- transaction,
62
+ transaction: transactions[0],
33
63
  });
34
- if (!simulatedLocally) {
35
- simulationResult.warnings.push({
36
- kind: 'INTERNAL_ERROR',
37
- severity: 'HIGH',
38
- message: 'Balance changes cannot be estimated.',
64
+ // If local simulation succeeded, add fee for the (single) transaction now
65
+ if (simulatedLocally) {
66
+ simulationResult.metadata.simulatedLocally = true;
67
+ const transaction = transactions[0];
68
+ simulationResult.balanceChanges.willPayFee.push({
69
+ balance: estimateFee({ asset, transaction }),
70
+ feeDetails: getTxFeeDetails(transaction),
39
71
  });
40
72
  }
73
+ else {
74
+ // Local Simulation also failed.
75
+ const message = 'Balance changes cannot be estimated.';
76
+ simulationResult.warnings.push({ kind: 'INTERNAL_ERROR', severity: 'HIGH', message });
77
+ simulationResult.metadata.humanReadableError = message;
78
+ }
41
79
  if (simulationTransactionInternalResult.errorMessage) {
42
80
  simulationResult.metadata.humanReadableError = simulationTransactionInternalResult.errorMessage;
43
81
  }
44
82
  return simulationResult;
45
83
  }
84
+ // Remote simulation succeeded; add fees for all transactions now
85
+ for (const transaction of transactions) {
86
+ simulationResult.balanceChanges.willPayFee.push({
87
+ balance: estimateFee({ asset, transaction }),
88
+ feeDetails: getTxFeeDetails(transaction),
89
+ });
90
+ }
46
91
  simulationResult.displayDetails = getDisplayDetails(simulationResult.balanceChanges.willApprove);
47
92
  return simulationResult;
48
93
  };
@@ -45,7 +45,7 @@ const handleExpectedBalanceChange = (expectedBalanceChange, simulationResult, {
45
45
  const isNftTransferApproval = kind === 'ERC721_APPROVAL' ||
46
46
  (kind === 'ERC721_APPROVAL_FOR_ALL' && !asset.decimals) ||
47
47
  (kind === 'ERC1155_APPROVAL_FOR_ALL' && !asset.decimals);
48
- const decimals = isNftTransfer || isNftTransferApproval ? 0 : asset.decimals;
48
+ const decimals = isNftTransfer || isNftTransferApproval || !Number.isFinite(asset.decimals) ? 0 : asset.decimals;
49
49
  const asset_ = pick(asset, [
50
50
  'address',
51
51
  'imageUrl',
@@ -130,8 +130,11 @@ export const simulateTransactions = async ({ transactions, apiEndpoint, origin,
130
130
  return {
131
131
  kind: 'error',
132
132
  error: 2 /* SimulationErrorMessages.SimulationNotSupported */,
133
+ errorMessage: 'Simulation is not supported for this asset.',
133
134
  };
134
135
  }
136
+ //Only one user address is supported
137
+ //Sending multiple transactions with different from addresses would lead to invalid results
135
138
  const userAddress = transactions[0].from;
136
139
  const payload = convertTransactionToApiPayload(transactions, origin);
137
140
  if (!Object.hasOwnProperty.call(BLOWFISH_EVM_CHAINS, asset.name)) {
@@ -150,6 +153,7 @@ export const simulateTransactions = async ({ transactions, apiEndpoint, origin,
150
153
  return {
151
154
  kind: 'error',
152
155
  error: 0 /* SimulationErrorMessages.APICallFailed */,
156
+ errorMessage: 'Simulation API call failed.',
153
157
  };
154
158
  }
155
159
  simulationResult.metadata.simulatedLocally = false;
package/lib/types.d.ts CHANGED
@@ -46,6 +46,7 @@ export interface EthWalletWatchAsset {
46
46
  }
47
47
  export interface EthSimulateTransactionParams extends SimulateTransactionParams {
48
48
  transactions: EthTransaction[];
49
+ headers?: Record<string, string>;
49
50
  }
50
51
  export interface EthFeeDetails {
51
52
  gasLimit: BN;
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@exodus/web3-ethereum-utils",
3
3
  "description": "Web3 Ethereum utils.",
4
- "version": "4.5.1",
4
+ "version": "4.7.0",
5
5
  "type": "module",
6
6
  "sideEffects": false,
7
7
  "module": "lib/index.js",
@@ -18,7 +18,8 @@
18
18
  "lib/**/*.js"
19
19
  ],
20
20
  "publishConfig": {
21
- "access": "public"
21
+ "access": "public",
22
+ "provenance": false
22
23
  },
23
24
  "repository": {
24
25
  "type": "git",
@@ -38,7 +39,7 @@
38
39
  },
39
40
  "dependencies": {
40
41
  "@exodus/basic-utils": "^3.0.1",
41
- "@exodus/ethereum-lib": "^5.18.1",
42
+ "@exodus/ethereum-lib": "^5.20.0",
42
43
  "@exodus/ethereumjs": "^1.5.0",
43
44
  "@exodus/solidity-contract": "^1.2.1",
44
45
  "@exodus/web3-utils": "^1.50.1",
@@ -49,5 +50,5 @@
49
50
  "url": "https://github.com/ExodusMovement/assets/issues?q=is%3Aissue+is%3Aopen+label%3Aweb3-ethereum-utils"
50
51
  },
51
52
  "homepage": "https://github.com/ExodusMovement/assets/tree/master/web3/web3-ethereum-utils",
52
- "gitHead": "f6ea2714c95f2e7ec71ffecdc589726b7974511e"
53
+ "gitHead": "600b3f3f510e30333080011e85958f71835e020c"
53
54
  }