@exodus/web3-ethereum-utils 4.5.0 → 4.6.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.
@@ -4,19 +4,39 @@ import { simulateTransactions } from './simulation/simulateTransactions.js';
4
4
  import { tryEstimatingChangesLocally } from './simulation/tryEstimatingChangesLocally.js';
5
5
  import { decodeRecipientAddresses, getDisplayDetails, getTxFeeDetails } from './transactions.js';
6
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];
9
7
  const simulationResult = createEmptySimulationResult({
10
8
  asset,
11
9
  });
12
- simulationResult.balanceChanges.willPayFee.push({
13
- balance: estimateFee({ asset, transaction }),
14
- feeDetails: getTxFeeDetails(transaction),
15
- });
16
- if (!transaction.to) {
10
+ if (transactions.length === 0) {
11
+ const message = 'No transactions provided. Cannot simulate transaction batch.';
12
+ simulationResult.warnings.push({ kind: 'INTERNAL_ERROR', severity: 'HIGH', message });
13
+ simulationResult.metadata.humanReadableError = message;
14
+ return simulationResult;
15
+ }
16
+ // Ensure all transactions share the same "from" address
17
+ // Added this because simulateTransactions makes this assumption and would give us invalid results if it's not the case
18
+ // If we need this (which is unlikely) we can remove this check and update simulateTransactions to handle multiple from addresses
19
+ const firstFrom = transactions[0].from.toLowerCase();
20
+ const hasMismatchedFrom = transactions.some((tx) => tx.from.toLowerCase() !== firstFrom);
21
+ if (hasMismatchedFrom) {
22
+ const message = 'All transactions in a batch must have the same "from" address. Cannot simulate transaction batch.';
23
+ simulationResult.warnings.push({ kind: 'INTERNAL_ERROR', severity: 'HIGH', message });
24
+ // add human readable error
25
+ simulationResult.metadata.humanReadableError = message;
26
+ return simulationResult;
27
+ }
28
+ // Check if ALL transactions have a 'to' address - if any are missing, return early
29
+ // Transactions are linked, so if one is invalid, the entire batch is invalid
30
+ const hasInvalidTransaction = transactions.some(tx => !tx.to);
31
+ if (hasInvalidTransaction) {
32
+ const message = 'One or more transactions are missing required "to" address. Cannot simulate transaction batch.';
33
+ simulationResult.warnings.push({ kind: 'INTERNAL_ERROR', severity: 'HIGH', message });
34
+ simulationResult.metadata.humanReadableError = message;
17
35
  return simulationResult;
18
36
  }
37
+ // Fees are added only after a successful simulation (remote or local)
19
38
  simulationResult.recipientAddresses = transactions.map((tx) => decodeRecipientAddresses(tx));
39
+ // Mutates the simulationResult, simulationTransactionInternalResult is only for checking the kind of response
20
40
  const simulationTransactionInternalResult = await simulateTransactions({
21
41
  asset,
22
42
  transactions,
@@ -25,24 +45,48 @@ export const createSimulateTransactions = ({ apiEndpoint, headers }) => async ({
25
45
  simulationResult,
26
46
  headers,
27
47
  });
48
+ // Blowfish simulation failed, try local simulation
28
49
  if (simulationTransactionInternalResult.kind === 'error') {
50
+ if (transactions.length > 1) {
51
+ // For batched transactions, don't attempt local simulation
52
+ const message = 'Cannot locally simulate batched transactions.';
53
+ simulationResult.warnings.push({ kind: 'INTERNAL_ERROR', severity: 'HIGH', message });
54
+ simulationResult.metadata.humanReadableError = message;
55
+ return simulationResult;
56
+ }
57
+ // Local simulation also mutates the simulationResult, so we need to check if it was successful
29
58
  const simulatedLocally = tryEstimatingChangesLocally({
30
59
  asset,
31
60
  simulationResult,
32
- transaction,
61
+ transaction: transactions[0],
33
62
  });
34
- if (!simulatedLocally) {
35
- simulationResult.warnings.push({
36
- kind: 'INTERNAL_ERROR',
37
- severity: 'HIGH',
38
- message: 'Balance changes cannot be estimated.',
63
+ // If local simulation succeeded, add fee for the (single) transaction now
64
+ if (simulatedLocally) {
65
+ simulationResult.metadata.simulatedLocally = true;
66
+ const transaction = transactions[0];
67
+ simulationResult.balanceChanges.willPayFee.push({
68
+ balance: estimateFee({ asset, transaction }),
69
+ feeDetails: getTxFeeDetails(transaction),
39
70
  });
40
71
  }
72
+ else {
73
+ // Local Simulation also failed.
74
+ const message = 'Balance changes cannot be estimated.';
75
+ simulationResult.warnings.push({ kind: 'INTERNAL_ERROR', severity: 'HIGH', message });
76
+ simulationResult.metadata.humanReadableError = message;
77
+ }
41
78
  if (simulationTransactionInternalResult.errorMessage) {
42
79
  simulationResult.metadata.humanReadableError = simulationTransactionInternalResult.errorMessage;
43
80
  }
44
81
  return simulationResult;
45
82
  }
83
+ // Remote simulation succeeded; add fees for all transactions now
84
+ for (const transaction of transactions) {
85
+ simulationResult.balanceChanges.willPayFee.push({
86
+ balance: estimateFee({ asset, transaction }),
87
+ feeDetails: getTxFeeDetails(transaction),
88
+ });
89
+ }
46
90
  simulationResult.displayDetails = getDisplayDetails(simulationResult.balanceChanges.willApprove);
47
91
  return simulationResult;
48
92
  };
package/lib/signatures.js CHANGED
@@ -1,7 +1,6 @@
1
- import { Common, Hardfork } from '@exodus/ethereumjs/common';
1
+ import { getEthereumJsTxByTransactionProps } from '@exodus/ethereum-lib';
2
2
  import { SignTypedDataVersion, personalSign, signTypedData as ethSigUtilSignTypedData, } from '@exodus/ethereumjs/eth-sig-util';
3
- import { FeeMarketEIP1559Transaction, Transaction as LegacyTransaction, } from '@exodus/ethereumjs/tx';
4
- const custom = Common.default?.custom || Common.custom;
3
+ import assert from 'minimalistic-assert';
5
4
  export function signMessage(message, privateKey) {
6
5
  return personalSign({ privateKey, data: message });
7
6
  }
@@ -9,15 +8,13 @@ export function signTypedData(typedData, privateKey, version = SignTypedDataVers
9
8
  return ethSigUtilSignTypedData({ privateKey, data: typedData, version });
10
9
  }
11
10
  export function signTransaction(transactionParams, privateKey) {
12
- const { chainId, ...transactionData } = transactionParams;
13
- const isEIP1559Enabled = !!transactionParams.maxFeePerGas;
14
- const transaction = isEIP1559Enabled
15
- ? FeeMarketEIP1559Transaction.fromTxData(transactionData, {
16
- common: custom({ chainId }, { hardfork: Hardfork.London }),
17
- })
18
- : LegacyTransaction.fromTxData(transactionData, {
19
- common: custom({ chainId }),
20
- });
11
+ const { chainId, ...transactionProps } = transactionParams;
12
+ assert(typeof chainId === 'number', 'signTransaction must be explicitly passed a chainId');
13
+ const transaction = getEthereumJsTxByTransactionProps({
14
+ chainId,
15
+ eip1559Enabled: Boolean(transactionProps.maxFeePerGas),
16
+ transactionProps,
17
+ });
21
18
  const signedTransaction = transaction.sign(privateKey);
22
19
  return signedTransaction.serialize();
23
20
  }
@@ -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;
@@ -1,6 +1,10 @@
1
1
  import type { EthTransaction } from './types.js';
2
2
  import type { ApprovalBalanceChange } from '@exodus/web3-types';
3
- export declare function getTransactionId(serializedTransaction: Buffer, isEIP1559Transaction?: boolean): string;
3
+ export declare function getTransactionId({ chainId, serializedTransaction, }: {
4
+ chainId: number;
5
+ serializedTransaction: Buffer;
6
+ eip1559Enabled?: boolean;
7
+ }): string;
4
8
  export declare function getDisplayDetails(willApprove: ApprovalBalanceChange[]): {
5
9
  title: string;
6
10
  };
@@ -1,10 +1,10 @@
1
- import { FeeMarketEIP1559Transaction, Transaction as LegacyTransaction, } from '@exodus/ethereumjs/tx';
1
+ import { getEthereumJsTxByTransactionBuffer } from '@exodus/ethereum-lib';
2
2
  import SolidityContract from '@exodus/solidity-contract';
3
- export function getTransactionId(serializedTransaction, isEIP1559Transaction = false) {
4
- const Transaction = isEIP1559Transaction
5
- ? FeeMarketEIP1559Transaction
6
- : LegacyTransaction;
7
- const transaction = Transaction.fromSerializedTx(serializedTransaction);
3
+ export function getTransactionId({ chainId, serializedTransaction, }) {
4
+ const transaction = getEthereumJsTxByTransactionBuffer({
5
+ chainId,
6
+ transactionBuffer: serializedTransaction,
7
+ });
8
8
  return `0x${transaction.hash().toString('hex')}`;
9
9
  }
10
10
  export function getDisplayDetails(willApprove) {
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.0",
4
+ "version": "4.6.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",
@@ -34,10 +35,11 @@
34
35
  "test": "run -T exodus-test --jest --esbuild"
35
36
  },
36
37
  "devDependencies": {
37
- "@exodus/web3-types": "1.57.2"
38
+ "@exodus/web3-types": "1.59.0"
38
39
  },
39
40
  "dependencies": {
40
41
  "@exodus/basic-utils": "^3.0.1",
42
+ "@exodus/ethereum-lib": "^5.20.0",
41
43
  "@exodus/ethereumjs": "^1.5.0",
42
44
  "@exodus/solidity-contract": "^1.2.1",
43
45
  "@exodus/web3-utils": "^1.50.1",
@@ -48,5 +50,5 @@
48
50
  "url": "https://github.com/ExodusMovement/assets/issues?q=is%3Aissue+is%3Aopen+label%3Aweb3-ethereum-utils"
49
51
  },
50
52
  "homepage": "https://github.com/ExodusMovement/assets/tree/master/web3/web3-ethereum-utils",
51
- "gitHead": "a703c06af7e51ff5810dc4feb397ef61639059fa"
53
+ "gitHead": "a15d8239871e44fd99d48a2b1a66606e2d428735"
52
54
  }