@exodus/web3-ethereum-utils 4.7.5 → 5.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.
- package/lib/createSimulateMessage.d.ts +6 -6
- package/lib/createSimulateMessage.js +3 -37
- package/lib/createSimulateTransactions.d.ts +3 -1
- package/lib/createSimulateTransactions.js +3 -95
- package/lib/signatures.js +16 -2
- package/lib/types.d.ts +1 -13
- package/package.json +12 -11
- package/lib/simulation/common.d.ts +0 -10
- package/lib/simulation/common.js +0 -33
- package/lib/simulation/estimateFee.d.ts +0 -6
- package/lib/simulation/estimateFee.js +0 -21
- package/lib/simulation/estimateSimpleTransfer.d.ts +0 -9
- package/lib/simulation/estimateSimpleTransfer.js +0 -22
- package/lib/simulation/getMessageType.d.ts +0 -6
- package/lib/simulation/getMessageType.js +0 -21
- package/lib/simulation/simulateMessage.d.ts +0 -7
- package/lib/simulation/simulateMessage.js +0 -46
- package/lib/simulation/simulateTransactions.d.ts +0 -14
- package/lib/simulation/simulateTransactions.js +0 -225
- package/lib/simulation/tryEstimatingChangesLocally.d.ts +0 -7
- package/lib/simulation/tryEstimatingChangesLocally.js +0 -24
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import type { EthSimulateMessageParams } from './types.js';
|
|
2
2
|
import type { MessageSimulationResult } from '@exodus/web3-types';
|
|
3
|
-
|
|
4
|
-
apiEndpoint?: string
|
|
5
|
-
headers?:
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
3
|
+
type CreateSimulateMessage = (params?: {
|
|
4
|
+
apiEndpoint?: string;
|
|
5
|
+
headers?: Record<string, string>;
|
|
6
|
+
}) => (params: EthSimulateMessageParams) => Promise<MessageSimulationResult>;
|
|
7
|
+
export declare const createSimulateMessage: CreateSimulateMessage;
|
|
8
|
+
export {};
|
|
@@ -1,37 +1,3 @@
|
|
|
1
|
-
|
|
2
|
-
import
|
|
3
|
-
|
|
4
|
-
import { simulateMessage } from './simulation/simulateMessage.js';
|
|
5
|
-
export const createSimulateMessage = ({ apiEndpoint = 'https://simulation.a.exodus.io/simulateMessage', headers = {
|
|
6
|
-
'X-Api-Version': '2023-06-05',
|
|
7
|
-
}, } = {}) => async ({ message, url, asset, address, }) => {
|
|
8
|
-
assert(url instanceof URL, "'url' should be an instance of the URL object.");
|
|
9
|
-
const simulationResult = {
|
|
10
|
-
baseAssetName: asset.baseAssetName,
|
|
11
|
-
action: 'NONE',
|
|
12
|
-
};
|
|
13
|
-
const messageType = getMessageType(message);
|
|
14
|
-
if (messageType === 2 /* MessageTypeEnum.Unknown */) {
|
|
15
|
-
return simulationResult;
|
|
16
|
-
}
|
|
17
|
-
if (messageType === 0 /* MessageTypeEnum.RawMessage */) {
|
|
18
|
-
tryParsingSIWE({
|
|
19
|
-
address,
|
|
20
|
-
message,
|
|
21
|
-
url,
|
|
22
|
-
simulationResult,
|
|
23
|
-
});
|
|
24
|
-
}
|
|
25
|
-
await simulateMessage({
|
|
26
|
-
address,
|
|
27
|
-
message: {
|
|
28
|
-
message,
|
|
29
|
-
messageType,
|
|
30
|
-
},
|
|
31
|
-
url,
|
|
32
|
-
apiEndpoint,
|
|
33
|
-
headers,
|
|
34
|
-
simulationResult,
|
|
35
|
-
});
|
|
36
|
-
return simulationResult;
|
|
37
|
-
};
|
|
1
|
+
// @ts-expect-error @exodus/ethereum-api is a JavaScript package without deep declarations.
|
|
2
|
+
import { createSimulateMessage as createEthereumApiSimulateMessage } from '@exodus/ethereum-api/src/simulation/create-simulate-message.js';
|
|
3
|
+
export const createSimulateMessage = createEthereumApiSimulateMessage;
|
|
@@ -1,3 +1,5 @@
|
|
|
1
1
|
import type { EthAggregatedTransactionSimulationResult, EthSimulateTransactionParams } from './types.js';
|
|
2
2
|
import type { CreateSimulateTransactionsParams } from '@exodus/web3-types';
|
|
3
|
-
|
|
3
|
+
type CreateSimulateTransactions = (params: Required<CreateSimulateTransactionsParams>) => (params: EthSimulateTransactionParams) => Promise<EthAggregatedTransactionSimulationResult>;
|
|
4
|
+
export declare const createSimulateTransactions: CreateSimulateTransactions;
|
|
5
|
+
export {};
|
|
@@ -1,95 +1,3 @@
|
|
|
1
|
-
|
|
2
|
-
import {
|
|
3
|
-
|
|
4
|
-
import { tryEstimatingChangesLocally } from './simulation/tryEstimatingChangesLocally.js';
|
|
5
|
-
import { decodeRecipientAddresses, getDisplayDetails, getTxFeeDetails } from './transactions.js';
|
|
6
|
-
export const createSimulateTransactions = ({ apiEndpoint, headers: headersFromDeps }) => async ({ transactions, asset, origin, blockNumber, headers: headersFromParams, overrideApiEndpoint, }) => {
|
|
7
|
-
const headers = { ...headersFromDeps, ...headersFromParams };
|
|
8
|
-
const simulationResult = createEmptySimulationResult({
|
|
9
|
-
asset,
|
|
10
|
-
});
|
|
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;
|
|
36
|
-
return simulationResult;
|
|
37
|
-
}
|
|
38
|
-
// Fees are added only after a successful simulation (remote or local)
|
|
39
|
-
simulationResult.recipientAddresses = transactions.map((tx) => decodeRecipientAddresses(tx));
|
|
40
|
-
// Mutates the simulationResult, simulationTransactionInternalResult is only for checking the kind of response
|
|
41
|
-
const simulationTransactionInternalResult = await simulateTransactions({
|
|
42
|
-
asset,
|
|
43
|
-
transactions,
|
|
44
|
-
apiEndpoint: overrideApiEndpoint || apiEndpoint,
|
|
45
|
-
origin,
|
|
46
|
-
blockNumber,
|
|
47
|
-
simulationResult,
|
|
48
|
-
headers,
|
|
49
|
-
});
|
|
50
|
-
// Blowfish simulation failed, try local simulation
|
|
51
|
-
if (simulationTransactionInternalResult.kind === 'error') {
|
|
52
|
-
if (transactions.length > 1) {
|
|
53
|
-
// For batched transactions, don't attempt local simulation
|
|
54
|
-
const message = 'Cannot locally simulate batched transactions.';
|
|
55
|
-
simulationResult.warnings.push({ kind: 'INTERNAL_ERROR', severity: 'HIGH', message });
|
|
56
|
-
simulationResult.metadata.humanReadableError = message;
|
|
57
|
-
}
|
|
58
|
-
else {
|
|
59
|
-
const transaction = transactions[0];
|
|
60
|
-
// Local simulation also mutates the simulationResult, so we need to check if it was successful
|
|
61
|
-
const simulatedLocally = tryEstimatingChangesLocally({
|
|
62
|
-
asset,
|
|
63
|
-
simulationResult,
|
|
64
|
-
transaction,
|
|
65
|
-
});
|
|
66
|
-
// If local simulation succeeded, add fee for the (single) transaction now
|
|
67
|
-
if (simulatedLocally) {
|
|
68
|
-
simulationResult.metadata.simulatedLocally = true;
|
|
69
|
-
simulationResult.balanceChanges.willPayFee.push({
|
|
70
|
-
balance: estimateFee({ asset, transaction }),
|
|
71
|
-
feeDetails: getTxFeeDetails(transaction),
|
|
72
|
-
});
|
|
73
|
-
}
|
|
74
|
-
else {
|
|
75
|
-
// Local Simulation also failed.
|
|
76
|
-
const message = 'Balance changes cannot be estimated.';
|
|
77
|
-
simulationResult.warnings.push({ kind: 'INTERNAL_ERROR', severity: 'HIGH', message });
|
|
78
|
-
simulationResult.metadata.humanReadableError = message;
|
|
79
|
-
}
|
|
80
|
-
}
|
|
81
|
-
if (simulationTransactionInternalResult.errorMessage) {
|
|
82
|
-
simulationResult.metadata.humanReadableError = simulationTransactionInternalResult.errorMessage;
|
|
83
|
-
}
|
|
84
|
-
return simulationResult;
|
|
85
|
-
}
|
|
86
|
-
// Remote simulation succeeded; add fees for all transactions now
|
|
87
|
-
for (const transaction of transactions) {
|
|
88
|
-
simulationResult.balanceChanges.willPayFee.push({
|
|
89
|
-
balance: estimateFee({ asset, transaction }),
|
|
90
|
-
feeDetails: getTxFeeDetails(transaction),
|
|
91
|
-
});
|
|
92
|
-
}
|
|
93
|
-
simulationResult.displayDetails = getDisplayDetails(simulationResult.balanceChanges.willApprove);
|
|
94
|
-
return simulationResult;
|
|
95
|
-
};
|
|
1
|
+
// @ts-expect-error @exodus/ethereum-api is a JavaScript package without deep declarations.
|
|
2
|
+
import { createSimulateTransactions as createEthereumApiSimulateTransactions } from '@exodus/ethereum-api/src/simulation/create-simulate-transactions.js';
|
|
3
|
+
export const createSimulateTransactions = createEthereumApiSimulateTransactions;
|
package/lib/signatures.js
CHANGED
|
@@ -18,9 +18,23 @@ export function signTransaction(transactionParams, privateKey) {
|
|
|
18
18
|
const signedTransaction = transaction.sign(privateKey);
|
|
19
19
|
return signedTransaction.serialize();
|
|
20
20
|
}
|
|
21
|
+
const HEX_BODY = /^[0-9a-fA-F]*$/;
|
|
21
22
|
export function hex0xStringToBuffer(hex) {
|
|
23
|
+
if (typeof hex !== 'string') {
|
|
24
|
+
throw new TypeError('expected a hex string');
|
|
25
|
+
}
|
|
22
26
|
// Remove the 0x
|
|
23
27
|
const hexWithout0x = hex.startsWith('0x') ? hex.slice(2) : hex;
|
|
28
|
+
// `Buffer.from(str, 'hex')` decodes the leading valid pairs of `str` and SILENTLY DROPS
|
|
29
|
+
// the remainder, without error. Anything not fully hex therefore produced a buffer that
|
|
30
|
+
// no longer corresponds to its input: `<hex> Login only.` decoded to `<hex>`, an
|
|
31
|
+
// uppercase `0X` prefix decoded to nothing, and plain text decoded to nothing at all --
|
|
32
|
+
// so `personal_sign` signed bytes that were never on screen, and every plain-text
|
|
33
|
+
// message signed as the EMPTY message, whose hash is a constant. A partial decode is
|
|
34
|
+
// never what the caller meant, so refuse it instead of guessing.
|
|
35
|
+
if (!HEX_BODY.test(hexWithout0x)) {
|
|
36
|
+
throw new TypeError('expected a hex string');
|
|
37
|
+
}
|
|
24
38
|
// Pad the value until even
|
|
25
39
|
const hexWithLeadingZeros = hexWithout0x.length % 2 === 0 ? hexWithout0x : '0' + hexWithout0x;
|
|
26
40
|
// Finally return the buffer
|
|
@@ -28,11 +42,11 @@ export function hex0xStringToBuffer(hex) {
|
|
|
28
42
|
}
|
|
29
43
|
export function bufferToHex0xString(buf) {
|
|
30
44
|
if (!Buffer.isBuffer(buf)) {
|
|
31
|
-
throw new
|
|
45
|
+
throw new TypeError('expected a buffer');
|
|
32
46
|
}
|
|
33
47
|
const hex = buf.toString('hex');
|
|
34
48
|
if (typeof hex !== 'string') {
|
|
35
|
-
throw new
|
|
49
|
+
throw new TypeError('expected hex string');
|
|
36
50
|
}
|
|
37
51
|
return '0x' + hex;
|
|
38
52
|
}
|
package/lib/types.d.ts
CHANGED
|
@@ -1,5 +1,4 @@
|
|
|
1
|
-
import type {
|
|
2
|
-
import type { AggregatedTransactionSimulationResult, Asset, BN, MessageSimulationResult, SimulateTransactionParams } from '@exodus/web3-types';
|
|
1
|
+
import type { AggregatedTransactionSimulationResult, Asset, BN, SimulateTransactionParams } from '@exodus/web3-types';
|
|
3
2
|
export interface EthTransactionParams {
|
|
4
3
|
chainId: number;
|
|
5
4
|
from: string;
|
|
@@ -61,14 +60,3 @@ export interface EthSimulateMessageParams {
|
|
|
61
60
|
url: URL;
|
|
62
61
|
address: string;
|
|
63
62
|
}
|
|
64
|
-
export interface SimulateMessageParams {
|
|
65
|
-
address: EthSimulateMessageParams['address'];
|
|
66
|
-
message: {
|
|
67
|
-
message: string;
|
|
68
|
-
messageType: MessageTypeEnum.RawMessage | MessageTypeEnum.TypedData;
|
|
69
|
-
};
|
|
70
|
-
url: EthSimulateMessageParams['url'];
|
|
71
|
-
apiEndpoint: string;
|
|
72
|
-
headers: Record<string, string>;
|
|
73
|
-
simulationResult: MessageSimulationResult;
|
|
74
|
-
}
|
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
|
+
"version": "5.0.0",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"sideEffects": false,
|
|
7
7
|
"module": "lib/index.js",
|
|
@@ -27,24 +27,25 @@
|
|
|
27
27
|
},
|
|
28
28
|
"license": "MIT",
|
|
29
29
|
"scripts": {
|
|
30
|
-
"clean": "
|
|
31
|
-
"build": "
|
|
32
|
-
"
|
|
33
|
-
"
|
|
34
|
-
"prepublishOnly": "yarn run -T build --scope @exodus/web3-ethereum-utils",
|
|
35
|
-
"test": "run -T exodus-test --jest --esbuild"
|
|
30
|
+
"clean": "rimraf lib",
|
|
31
|
+
"build": "pnpm clean && tsc",
|
|
32
|
+
"prepublishOnly": "pnpm -w run build --scope @exodus/web3-ethereum-utils",
|
|
33
|
+
"test": "exodus-test --jest --esbuild"
|
|
36
34
|
},
|
|
37
35
|
"devDependencies": {
|
|
38
|
-
"@exodus/assets": "^11.
|
|
39
|
-
"@exodus/ethereum-meta": "^2.
|
|
40
|
-
"@
|
|
36
|
+
"@exodus/assets": "^11.10.0",
|
|
37
|
+
"@exodus/ethereum-meta": "^2.13.0",
|
|
38
|
+
"@types/bn.js": "^5.1.6",
|
|
39
|
+
"@types/minimalistic-assert": "^1.0.3",
|
|
41
40
|
"lodash": "^4.17.21"
|
|
42
41
|
},
|
|
43
42
|
"dependencies": {
|
|
44
43
|
"@exodus/basic-utils": "^3.0.1",
|
|
44
|
+
"@exodus/ethereum-api": "^8.76.4",
|
|
45
45
|
"@exodus/ethereum-lib": "^5.20.0",
|
|
46
46
|
"@exodus/ethereumjs": "^1.5.0",
|
|
47
47
|
"@exodus/solidity-contract": "^1.2.1",
|
|
48
|
+
"@exodus/web3-types": "^1.62.0",
|
|
48
49
|
"@exodus/web3-utils": "^1.50.1",
|
|
49
50
|
"bn.js": "^5.2.0",
|
|
50
51
|
"minimalistic-assert": "^1.0.1"
|
|
@@ -54,5 +55,5 @@
|
|
|
54
55
|
"url": "https://github.com/ExodusMovement/assets/issues?q=is%3Aissue+is%3Aopen+label%3Aweb3-ethereum-utils"
|
|
55
56
|
},
|
|
56
57
|
"homepage": "https://github.com/ExodusMovement/assets/tree/master/web3/web3-ethereum-utils",
|
|
57
|
-
"gitHead": "
|
|
58
|
+
"gitHead": "57bdb90a5fea29136612440c40f8203aae238caf"
|
|
58
59
|
}
|
package/lib/simulation/common.js
DELETED
|
@@ -1,33 +0,0 @@
|
|
|
1
|
-
import BN from 'bn.js';
|
|
2
|
-
export const BLOWFISH_EVM_CHAINS = {
|
|
3
|
-
avalanchec: {
|
|
4
|
-
network: 'avalanche',
|
|
5
|
-
chain: 'mainnet',
|
|
6
|
-
},
|
|
7
|
-
basemainnet: {
|
|
8
|
-
network: 'base',
|
|
9
|
-
chain: 'mainnet',
|
|
10
|
-
},
|
|
11
|
-
bsc: {
|
|
12
|
-
network: 'bnb',
|
|
13
|
-
chain: 'mainnet',
|
|
14
|
-
},
|
|
15
|
-
ethereum: {
|
|
16
|
-
network: 'ethereum',
|
|
17
|
-
chain: 'mainnet',
|
|
18
|
-
},
|
|
19
|
-
ethereumarbone: {
|
|
20
|
-
network: 'arbitrum',
|
|
21
|
-
chain: 'one',
|
|
22
|
-
},
|
|
23
|
-
matic: {
|
|
24
|
-
network: 'polygon',
|
|
25
|
-
chain: 'mainnet',
|
|
26
|
-
},
|
|
27
|
-
optimism: {
|
|
28
|
-
network: 'optimism',
|
|
29
|
-
chain: 'mainnet',
|
|
30
|
-
},
|
|
31
|
-
};
|
|
32
|
-
// The maximum signed integer 2\^255 - 1 represented in BN.
|
|
33
|
-
export const MAX_INT256_SOLIDITY = new BN(2).pow(new BN(255)).subn(1);
|
|
@@ -1,21 +0,0 @@
|
|
|
1
|
-
/*
|
|
2
|
-
This utility determines a preliminary fee a transaction is going to pay based on
|
|
3
|
-
its "gas", "gasPrice" and "maxFeePerGas" properties.
|
|
4
|
-
|
|
5
|
-
Note: if EIP-1559 is enabled, a transaction will have the "maxFeePerGas" field. In that case,
|
|
6
|
-
a final gas price will be determined on-chain (the utility uses a maximum possible gas price value).
|
|
7
|
-
If a transaction has only "gasPrice", it can be considered as a final gas price.
|
|
8
|
-
However, a final fee for non-simple transfers is always calculated on-chain once the EVM executes a transaction
|
|
9
|
-
and has the number of gas consumed.
|
|
10
|
-
*/
|
|
11
|
-
import { hexToBN } from '@exodus/web3-utils';
|
|
12
|
-
export const estimateFee = ({ asset, transaction, }) => {
|
|
13
|
-
const gas = hexToBN(transaction.gas);
|
|
14
|
-
if (transaction.maxFeePerGas) {
|
|
15
|
-
const maxFeePerGas = hexToBN(transaction.maxFeePerGas);
|
|
16
|
-
const maximumFee = gas.mul(maxFeePerGas);
|
|
17
|
-
return asset.currency.baseUnit(maximumFee.toString());
|
|
18
|
-
}
|
|
19
|
-
const gasPrice = hexToBN(transaction.gasPrice);
|
|
20
|
-
return asset.currency.baseUnit(gas.mul(gasPrice).toString());
|
|
21
|
-
};
|
|
@@ -1,9 +0,0 @@
|
|
|
1
|
-
import type { EthTransaction } from '../types.js';
|
|
2
|
-
import type { Asset, NumberUnit } from '@exodus/web3-types';
|
|
3
|
-
export declare class NotSimpleTransferError extends Error {
|
|
4
|
-
constructor(msg: string);
|
|
5
|
-
}
|
|
6
|
-
export declare const estimateSimpleTransfer: ({ asset, transaction, }: {
|
|
7
|
-
asset: Asset;
|
|
8
|
-
transaction: EthTransaction;
|
|
9
|
-
}) => NumberUnit | never;
|
|
@@ -1,22 +0,0 @@
|
|
|
1
|
-
/*
|
|
2
|
-
This utility determines whether the provided transaction is a simple native token transfer
|
|
3
|
-
and returns the value it's sending if that's true. Throws an error otherwise.
|
|
4
|
-
|
|
5
|
-
Note: only simple transfers (non-smart contract calls) can be reliably evaluated locally.
|
|
6
|
-
Even empty `data` field transactions ("0x") can cause unexpected state changes if sent to a smart contract
|
|
7
|
-
with a gas limit exceeding 21000 units.
|
|
8
|
-
*/
|
|
9
|
-
import { hexToBN } from '@exodus/web3-utils';
|
|
10
|
-
import { isSimpleTransfer } from '../transactions.js';
|
|
11
|
-
export class NotSimpleTransferError extends Error {
|
|
12
|
-
constructor(msg) {
|
|
13
|
-
super(msg);
|
|
14
|
-
}
|
|
15
|
-
}
|
|
16
|
-
export const estimateSimpleTransfer = ({ asset, transaction, }) => {
|
|
17
|
-
if (!isSimpleTransfer(transaction)) {
|
|
18
|
-
throw new NotSimpleTransferError('The transaction input should be empty ("0x").');
|
|
19
|
-
}
|
|
20
|
-
const txValue = hexToBN(transaction.value || '0x0').toString();
|
|
21
|
-
return asset.currency.baseUnit(txValue);
|
|
22
|
-
};
|
|
@@ -1,6 +0,0 @@
|
|
|
1
|
-
export declare const enum MessageTypeEnum {
|
|
2
|
-
'RawMessage' = 0,// References to the `eth_sign` RPC method.
|
|
3
|
-
'TypedData' = 1,// References to the `eth_signTypedData` RPC method.
|
|
4
|
-
'Unknown' = 2
|
|
5
|
-
}
|
|
6
|
-
export declare const getMessageType: (message: string) => MessageTypeEnum;
|
|
@@ -1,21 +0,0 @@
|
|
|
1
|
-
/*
|
|
2
|
-
This utility figures out if the supplied message is plaint text to be signed or a structured typed data.
|
|
3
|
-
See for differences:
|
|
4
|
-
- https://docs.metamask.io/wallet/reference/personal_sign/
|
|
5
|
-
- https://docs.metamask.io/wallet/reference/eth_signtypeddata_v4/
|
|
6
|
-
|
|
7
|
-
The utility does not make any security checks (e.g. schema validation), it's supposed to be used only for choosing
|
|
8
|
-
a proper payload type for the simulation API.
|
|
9
|
-
*/
|
|
10
|
-
export const getMessageType = (message) => {
|
|
11
|
-
if (typeof message !== 'string') {
|
|
12
|
-
return 2 /* MessageTypeEnum.Unknown */;
|
|
13
|
-
}
|
|
14
|
-
try {
|
|
15
|
-
JSON.parse(message);
|
|
16
|
-
return 1 /* MessageTypeEnum.TypedData */;
|
|
17
|
-
}
|
|
18
|
-
catch (err) {
|
|
19
|
-
return 0 /* MessageTypeEnum.RawMessage */;
|
|
20
|
-
}
|
|
21
|
-
};
|
|
@@ -1,7 +0,0 @@
|
|
|
1
|
-
import type { SimulateMessageParams } from '../types.js';
|
|
2
|
-
import type { MessageSimulationResult, ScanMessageEvm200Response } from '@exodus/web3-types';
|
|
3
|
-
export declare const handleActionResponse: ({ simulationResult, response, }: {
|
|
4
|
-
simulationResult: MessageSimulationResult;
|
|
5
|
-
response: ScanMessageEvm200Response;
|
|
6
|
-
}) => void;
|
|
7
|
-
export declare const simulateMessage: ({ address, apiEndpoint, message, url, headers, simulationResult, }: SimulateMessageParams) => Promise<void>;
|
|
@@ -1,46 +0,0 @@
|
|
|
1
|
-
import { makeSimulationAPICall } from '@exodus/web3-utils';
|
|
2
|
-
import { BLOWFISH_EVM_CHAINS } from './common.js';
|
|
3
|
-
const convertToAPIPayload = ({ address, message, url, }) => {
|
|
4
|
-
const restParameters = {
|
|
5
|
-
metadata: { origin: url.href },
|
|
6
|
-
userAccount: address,
|
|
7
|
-
};
|
|
8
|
-
if (message.messageType === 1 /* MessageTypeEnum.TypedData */) {
|
|
9
|
-
const kind = 'SIGN_TYPED_DATA';
|
|
10
|
-
return {
|
|
11
|
-
message: {
|
|
12
|
-
kind,
|
|
13
|
-
data: JSON.parse(message.message),
|
|
14
|
-
},
|
|
15
|
-
...restParameters,
|
|
16
|
-
};
|
|
17
|
-
}
|
|
18
|
-
const kind = 'SIGN_MESSAGE';
|
|
19
|
-
return {
|
|
20
|
-
message: {
|
|
21
|
-
kind,
|
|
22
|
-
rawMessage: message.message,
|
|
23
|
-
},
|
|
24
|
-
...restParameters,
|
|
25
|
-
};
|
|
26
|
-
};
|
|
27
|
-
export const handleActionResponse = ({ simulationResult, response, }) => {
|
|
28
|
-
if (['BLOCK', 'WARN'].includes(response.action)) {
|
|
29
|
-
simulationResult.action = response.action;
|
|
30
|
-
}
|
|
31
|
-
};
|
|
32
|
-
export const simulateMessage = async ({ address, apiEndpoint, message, url, headers, simulationResult, }) => {
|
|
33
|
-
const { network, chain } = BLOWFISH_EVM_CHAINS[simulationResult.baseAssetName];
|
|
34
|
-
const payload = convertToAPIPayload({ address, message, url });
|
|
35
|
-
const response = await makeSimulationAPICall({
|
|
36
|
-
url: apiEndpoint,
|
|
37
|
-
chain,
|
|
38
|
-
network,
|
|
39
|
-
payload,
|
|
40
|
-
headers,
|
|
41
|
-
});
|
|
42
|
-
if (!response) {
|
|
43
|
-
return;
|
|
44
|
-
}
|
|
45
|
-
handleActionResponse({ simulationResult, response });
|
|
46
|
-
};
|
|
@@ -1,14 +0,0 @@
|
|
|
1
|
-
import type { EthAggregatedTransactionSimulationResult, EthSimulateTransactionParams } from '../types.js';
|
|
2
|
-
import type { CreateSimulateTransactionsParams, SimulateTransactionsInternalResult } from '@exodus/web3-types';
|
|
3
|
-
type SimulateTransactionParams = Required<Pick<CreateSimulateTransactionsParams, 'apiEndpoint'>> & Required<Pick<EthSimulateTransactionParams, 'asset' | 'transactions' | 'origin'>> & {
|
|
4
|
-
blockNumber?: EthSimulateTransactionParams['blockNumber'];
|
|
5
|
-
simulationResult: EthAggregatedTransactionSimulationResult;
|
|
6
|
-
headers?: Record<string, string>;
|
|
7
|
-
};
|
|
8
|
-
export declare const enum SimulationErrorMessages {
|
|
9
|
-
'APICallFailed' = 0,
|
|
10
|
-
'SimulationFailed' = 1,
|
|
11
|
-
'SimulationNotSupported' = 2
|
|
12
|
-
}
|
|
13
|
-
export declare const simulateTransactions: ({ transactions, apiEndpoint, origin, blockNumber, simulationResult, headers, asset, }: SimulateTransactionParams) => SimulateTransactionsInternalResult<undefined, SimulationErrorMessages>;
|
|
14
|
-
export {};
|
|
@@ -1,225 +0,0 @@
|
|
|
1
|
-
/*
|
|
2
|
-
This utility makes an API call to a simulation service, parses the response and mutates the supplied "simulationResult" object
|
|
3
|
-
with the according balance changes.
|
|
4
|
-
*/
|
|
5
|
-
import { pick } from '@exodus/basic-utils';
|
|
6
|
-
import { createCurrency, makeSimulationAPICall } from '@exodus/web3-utils';
|
|
7
|
-
import { BLOWFISH_EVM_CHAINS, MAX_INT256_SOLIDITY } from './common.js';
|
|
8
|
-
const convertTransactionToApiPayload = (transactions, origin, blockNumber) => {
|
|
9
|
-
const payload = {
|
|
10
|
-
txObjects: transactions.map((transaction) => ({
|
|
11
|
-
from: transaction.from,
|
|
12
|
-
to: transaction.to,
|
|
13
|
-
data: transaction.data,
|
|
14
|
-
value: transaction.value,
|
|
15
|
-
gas: transaction.gas,
|
|
16
|
-
gas_price: transaction.gasPrice,
|
|
17
|
-
})),
|
|
18
|
-
metadata: {
|
|
19
|
-
origin,
|
|
20
|
-
},
|
|
21
|
-
userAccount: transactions[0].from,
|
|
22
|
-
};
|
|
23
|
-
if (blockNumber !== undefined) {
|
|
24
|
-
payload.blockNumber = blockNumber;
|
|
25
|
-
}
|
|
26
|
-
return payload;
|
|
27
|
-
};
|
|
28
|
-
const createBalanceAmount = (amount, { decimals, symbol }) => {
|
|
29
|
-
const beforeAmount = createCurrency({
|
|
30
|
-
amount: amount.before,
|
|
31
|
-
symbol: symbol,
|
|
32
|
-
denominator: decimals,
|
|
33
|
-
});
|
|
34
|
-
const afterAmount = createCurrency({
|
|
35
|
-
amount: amount.after,
|
|
36
|
-
symbol: symbol,
|
|
37
|
-
denominator: decimals,
|
|
38
|
-
});
|
|
39
|
-
return beforeAmount.sub(afterAmount);
|
|
40
|
-
};
|
|
41
|
-
const handleExpectedBalanceChange = (expectedBalanceChange, simulationResult, { network }) => {
|
|
42
|
-
const { willApprove, willSend, willReceive } = simulationResult.balanceChanges;
|
|
43
|
-
const { data, kind } = expectedBalanceChange.rawInfo;
|
|
44
|
-
if (kind === 'FARCASTER_CHANGE_RECOVERY_ADDRESS' || kind === 'ERC20_PERMIT') {
|
|
45
|
-
return;
|
|
46
|
-
}
|
|
47
|
-
const { asset } = data;
|
|
48
|
-
// ERC1155 is a multi-token standard (https://eips.ethereum.org/EIPS/eip-1155) and can be used for NFTs as well.
|
|
49
|
-
const isNftTransfer = kind === 'ERC721_TRANSFER' ||
|
|
50
|
-
(kind === 'ERC1155_TRANSFER' && !asset.decimals);
|
|
51
|
-
const isNftTransferApproval = kind === 'ERC721_APPROVAL' ||
|
|
52
|
-
(kind === 'ERC721_APPROVAL_FOR_ALL' && !asset.decimals) ||
|
|
53
|
-
(kind === 'ERC1155_APPROVAL_FOR_ALL' && !asset.decimals);
|
|
54
|
-
const decimals = isNftTransfer || isNftTransferApproval || !Number.isFinite(asset.decimals) ? 0 : asset.decimals;
|
|
55
|
-
const asset_ = pick(asset, [
|
|
56
|
-
'address',
|
|
57
|
-
'imageUrl',
|
|
58
|
-
'name',
|
|
59
|
-
'symbol',
|
|
60
|
-
'verified',
|
|
61
|
-
]);
|
|
62
|
-
// According to the Blowfish docs, `symbol` and `name` can be null as they are not required by the standard.
|
|
63
|
-
// Using `NFT` as a fallback ensures a user sees a meaningful UI in that case.
|
|
64
|
-
let symbol = asset.symbol || asset.name || 'NFT';
|
|
65
|
-
// TODO: remove this once Blowfish completes their migration from Matic to POL.
|
|
66
|
-
if (network === 'polygon' && asset.symbol === 'MATIC') {
|
|
67
|
-
symbol = 'POL';
|
|
68
|
-
}
|
|
69
|
-
if (isNftTransfer || isNftTransferApproval) {
|
|
70
|
-
asset_.imageUrl = data?.metadata?.rawImageUrl;
|
|
71
|
-
}
|
|
72
|
-
if (kind === 'ERC20_APPROVAL' ||
|
|
73
|
-
kind === 'ERC721_APPROVAL_FOR_ALL' ||
|
|
74
|
-
kind === 'ERC1155_APPROVAL_FOR_ALL' ||
|
|
75
|
-
kind === 'ERC721_APPROVAL') {
|
|
76
|
-
const unitsToUse = symbol || asset.name || 'Units.';
|
|
77
|
-
const approvingBalance = createCurrency({
|
|
78
|
-
amount: data.amount.after,
|
|
79
|
-
symbol: unitsToUse,
|
|
80
|
-
denominator: decimals,
|
|
81
|
-
});
|
|
82
|
-
willApprove.push({
|
|
83
|
-
asset: asset_,
|
|
84
|
-
spender: data.spender.address,
|
|
85
|
-
unitName: unitsToUse,
|
|
86
|
-
balance: approvingBalance,
|
|
87
|
-
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
|
88
|
-
// @ts-ignore _number exists on NumberUnit, but deprecated
|
|
89
|
-
isMaxApproval: approvingBalance._number.gte(MAX_INT256_SOLIDITY),
|
|
90
|
-
});
|
|
91
|
-
return;
|
|
92
|
-
}
|
|
93
|
-
const { amount } = data;
|
|
94
|
-
const balance = createBalanceAmount(amount, { decimals, symbol });
|
|
95
|
-
if (balance.isZero) {
|
|
96
|
-
return;
|
|
97
|
-
}
|
|
98
|
-
const balanceChange = {
|
|
99
|
-
asset: asset_,
|
|
100
|
-
balance,
|
|
101
|
-
};
|
|
102
|
-
if (isNftTransfer || isNftTransferApproval) {
|
|
103
|
-
const tokenId = data.tokenId;
|
|
104
|
-
const compositeId = `${asset.address}/${tokenId}`;
|
|
105
|
-
balanceChange.nft = {
|
|
106
|
-
id: `${network}:${compositeId}`,
|
|
107
|
-
compositeId,
|
|
108
|
-
title: asset.name,
|
|
109
|
-
};
|
|
110
|
-
}
|
|
111
|
-
if (balance.isNegative) {
|
|
112
|
-
// Convert to non-negative value.
|
|
113
|
-
balanceChange.balance = balanceChange.balance.negate();
|
|
114
|
-
willReceive.push(balanceChange);
|
|
115
|
-
}
|
|
116
|
-
else {
|
|
117
|
-
willSend.push(balanceChange);
|
|
118
|
-
}
|
|
119
|
-
};
|
|
120
|
-
const simulationResultHasAssets = (expectedStateChanges) => {
|
|
121
|
-
for (const expectedChange of expectedStateChanges) {
|
|
122
|
-
const { asset } = expectedChange.rawInfo
|
|
123
|
-
.data;
|
|
124
|
-
if (!asset) {
|
|
125
|
-
return false;
|
|
126
|
-
}
|
|
127
|
-
}
|
|
128
|
-
return true;
|
|
129
|
-
};
|
|
130
|
-
export const simulateTransactions = async ({ transactions, apiEndpoint, origin, blockNumber, simulationResult, headers, asset, }) => {
|
|
131
|
-
const successDefaultResult = {
|
|
132
|
-
kind: 'success',
|
|
133
|
-
value: undefined,
|
|
134
|
-
};
|
|
135
|
-
if (!BLOWFISH_EVM_CHAINS[asset.name]) {
|
|
136
|
-
return {
|
|
137
|
-
kind: 'error',
|
|
138
|
-
error: 2 /* SimulationErrorMessages.SimulationNotSupported */,
|
|
139
|
-
errorMessage: 'Simulation is not supported for this asset.',
|
|
140
|
-
};
|
|
141
|
-
}
|
|
142
|
-
//Only one user address is supported
|
|
143
|
-
//Sending multiple transactions with different from addresses would lead to invalid results
|
|
144
|
-
const userAddress = transactions[0].from;
|
|
145
|
-
const payload = convertTransactionToApiPayload(transactions, origin, blockNumber);
|
|
146
|
-
if (!Object.hasOwnProperty.call(BLOWFISH_EVM_CHAINS, asset.name)) {
|
|
147
|
-
// ToDo: accept a logger dep and log the error
|
|
148
|
-
return successDefaultResult;
|
|
149
|
-
}
|
|
150
|
-
const { network, chain } = BLOWFISH_EVM_CHAINS[asset.name];
|
|
151
|
-
const response = await makeSimulationAPICall({
|
|
152
|
-
url: apiEndpoint,
|
|
153
|
-
chain,
|
|
154
|
-
network,
|
|
155
|
-
payload,
|
|
156
|
-
headers,
|
|
157
|
-
});
|
|
158
|
-
if (!response) {
|
|
159
|
-
return {
|
|
160
|
-
kind: 'error',
|
|
161
|
-
error: 0 /* SimulationErrorMessages.APICallFailed */,
|
|
162
|
-
errorMessage: 'Simulation API call failed.',
|
|
163
|
-
};
|
|
164
|
-
}
|
|
165
|
-
simulationResult.metadata.simulatedLocally = false;
|
|
166
|
-
const { expectedStateChanges = {}, error } = response.simulationResults.aggregated;
|
|
167
|
-
if (error) {
|
|
168
|
-
// ToDo: accept a logger dep and log the error
|
|
169
|
-
// ToDo: consider pushing an error to the "warnings" list
|
|
170
|
-
return {
|
|
171
|
-
kind: 'error',
|
|
172
|
-
error: 1 /* SimulationErrorMessages.SimulationFailed */,
|
|
173
|
-
errorMessage: error?.humanReadableError,
|
|
174
|
-
};
|
|
175
|
-
}
|
|
176
|
-
const { action } = response;
|
|
177
|
-
if (action === 'BLOCK') {
|
|
178
|
-
const warning = {
|
|
179
|
-
kind: 'MALICIOUS_ACTION',
|
|
180
|
-
severity: 'CRITICAL',
|
|
181
|
-
// We use a generic default message unless we find a known warning type below
|
|
182
|
-
// (TODO: add known warning types)
|
|
183
|
-
message: 'This dApp could be malicious. Do not proceed unless you are certain this is safe.',
|
|
184
|
-
};
|
|
185
|
-
simulationResult.warnings.push(warning);
|
|
186
|
-
}
|
|
187
|
-
else if (action === 'WARN') {
|
|
188
|
-
const warning = {
|
|
189
|
-
kind: 'SUSPICIOUS_ACTION',
|
|
190
|
-
severity: 'MEDIUM',
|
|
191
|
-
message: 'This transaction may be suspicious. Proceed with caution.',
|
|
192
|
-
};
|
|
193
|
-
simulationResult.warnings.push(warning);
|
|
194
|
-
}
|
|
195
|
-
const noExpectedStateChangesDetected = Object.keys(expectedStateChanges).length === 0;
|
|
196
|
-
// This is a self-send transaction, adding a zero "transfer" so that the UI can display it.
|
|
197
|
-
if (noExpectedStateChangesDetected) {
|
|
198
|
-
simulationResult.balanceChanges.willSend.push({
|
|
199
|
-
balance: createCurrency({
|
|
200
|
-
amount: 0,
|
|
201
|
-
symbol: asset.displayTicker,
|
|
202
|
-
denominator: asset.currency.defaultUnit.power,
|
|
203
|
-
}),
|
|
204
|
-
});
|
|
205
|
-
return successDefaultResult;
|
|
206
|
-
}
|
|
207
|
-
const userExpectedChanges = Object.keys(expectedStateChanges).reduce((prev, address) => {
|
|
208
|
-
if (userAddress.toLowerCase() !== address.toLowerCase()) {
|
|
209
|
-
return prev;
|
|
210
|
-
}
|
|
211
|
-
const result = (Object.prototype.hasOwnProperty.call(expectedStateChanges, address) &&
|
|
212
|
-
expectedStateChanges[address]) ||
|
|
213
|
-
[];
|
|
214
|
-
return [...prev, ...result];
|
|
215
|
-
}, []);
|
|
216
|
-
// Return and log if asset is not present in the expectedStateChanges
|
|
217
|
-
if (!simulationResultHasAssets(userExpectedChanges)) {
|
|
218
|
-
// ToDo: accept a logger dep and log the error
|
|
219
|
-
return successDefaultResult;
|
|
220
|
-
}
|
|
221
|
-
userExpectedChanges.forEach((expectedChange) => {
|
|
222
|
-
handleExpectedBalanceChange(expectedChange, simulationResult, { network });
|
|
223
|
-
});
|
|
224
|
-
return successDefaultResult;
|
|
225
|
-
};
|
|
@@ -1,7 +0,0 @@
|
|
|
1
|
-
import type { EthAggregatedTransactionSimulationResult, EthTransaction } from '../types.js';
|
|
2
|
-
import type { Asset } from '@exodus/web3-types';
|
|
3
|
-
export declare const tryEstimatingChangesLocally: ({ asset, simulationResult, transaction, }: {
|
|
4
|
-
asset: Asset;
|
|
5
|
-
simulationResult: EthAggregatedTransactionSimulationResult;
|
|
6
|
-
transaction: EthTransaction;
|
|
7
|
-
}) => boolean;
|
|
@@ -1,24 +0,0 @@
|
|
|
1
|
-
import { estimateSimpleTransfer, NotSimpleTransferError, } from './estimateSimpleTransfer.js';
|
|
2
|
-
/*
|
|
3
|
-
Mutates the provided 'simulationResult' adding a 'willSend' entry if the transaction is a simple transfer.
|
|
4
|
-
The function also returns a boolean indicating whether the local simulation was possible (e.g., if any balance changes were made).
|
|
5
|
-
*/
|
|
6
|
-
export const tryEstimatingChangesLocally = ({ asset, simulationResult, transaction, }) => {
|
|
7
|
-
try {
|
|
8
|
-
const estimatedValue = estimateSimpleTransfer({ asset, transaction });
|
|
9
|
-
const actualTransferValue = transaction.from.toLowerCase() === transaction.to.toLowerCase()
|
|
10
|
-
? asset.currency.ZERO // A self-send does not transfer any funds.
|
|
11
|
-
: estimatedValue;
|
|
12
|
-
simulationResult.balanceChanges.willSend.push({
|
|
13
|
-
balance: actualTransferValue,
|
|
14
|
-
});
|
|
15
|
-
return true;
|
|
16
|
-
}
|
|
17
|
-
catch (err) {
|
|
18
|
-
const expectedError = err instanceof NotSimpleTransferError;
|
|
19
|
-
if (!expectedError) {
|
|
20
|
-
// ToDo: accept a logger dep and log an error here
|
|
21
|
-
}
|
|
22
|
-
return false;
|
|
23
|
-
}
|
|
24
|
-
};
|