@morpho-org/evm-simulation 4.0.1 → 4.1.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/cjs/simulate/backends/eth-simulate-v1.d.ts +7 -8
- package/lib/cjs/simulate/backends/eth-simulate-v1.js +22 -25
- package/lib/cjs/simulate/backends/tenderly-rpc.js +5 -1
- package/lib/cjs/simulate/parsing/transfers.d.ts +9 -0
- package/lib/cjs/simulate/parsing/transfers.js +25 -1
- package/lib/cjs/types.d.ts +4 -4
- package/lib/esm/simulate/backends/eth-simulate-v1.d.ts +7 -8
- package/lib/esm/simulate/backends/eth-simulate-v1.js +23 -26
- package/lib/esm/simulate/backends/tenderly-rpc.js +5 -1
- package/lib/esm/simulate/parsing/transfers.d.ts +9 -0
- package/lib/esm/simulate/parsing/transfers.js +26 -2
- package/lib/esm/types.d.ts +4 -4
- package/package.json +5 -5
|
@@ -6,14 +6,13 @@ import type { RawSimulationResult, SimulationTransaction } from "../../types.js"
|
|
|
6
6
|
* Uses stateOverride to set the user's ETH balance high enough to avoid
|
|
7
7
|
* false "insufficient balance for gas" reverts.
|
|
8
8
|
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
11
|
-
* `
|
|
12
|
-
*
|
|
13
|
-
*
|
|
14
|
-
*
|
|
15
|
-
*
|
|
16
|
-
* is not captured. For full native-ETH accounting use Tenderly.
|
|
9
|
+
* Runs with `traceTransfers` enabled, so the node synthesizes native-ETH moves
|
|
10
|
+
* (the top-level `value` *and* ETH moved through internal calls — e.g. a
|
|
11
|
+
* `WETH.withdraw` refund or a swap that pays out ETH) as `Transfer` events from
|
|
12
|
+
* the native sentinel. Derives `assetChanges` (net per-token deltas grouped by
|
|
13
|
+
* account) entirely from the emitted transfer logs, with native ETH normalized
|
|
14
|
+
* to viem's `ethAddress`. Top-level `value` is intentionally *not* added on top:
|
|
15
|
+
* `traceTransfers` already logs it, so adding it would double-count.
|
|
17
16
|
*/
|
|
18
17
|
export declare function simulateV1(params: {
|
|
19
18
|
rpcUrl: string;
|
|
@@ -11,14 +11,13 @@ const index_js_1 = require("../parsing/index.js");
|
|
|
11
11
|
* Uses stateOverride to set the user's ETH balance high enough to avoid
|
|
12
12
|
* false "insufficient balance for gas" reverts.
|
|
13
13
|
*
|
|
14
|
-
*
|
|
15
|
-
*
|
|
16
|
-
* `
|
|
17
|
-
*
|
|
18
|
-
*
|
|
19
|
-
*
|
|
20
|
-
*
|
|
21
|
-
* is not captured. For full native-ETH accounting use Tenderly.
|
|
14
|
+
* Runs with `traceTransfers` enabled, so the node synthesizes native-ETH moves
|
|
15
|
+
* (the top-level `value` *and* ETH moved through internal calls — e.g. a
|
|
16
|
+
* `WETH.withdraw` refund or a swap that pays out ETH) as `Transfer` events from
|
|
17
|
+
* the native sentinel. Derives `assetChanges` (net per-token deltas grouped by
|
|
18
|
+
* account) entirely from the emitted transfer logs, with native ETH normalized
|
|
19
|
+
* to viem's `ethAddress`. Top-level `value` is intentionally *not* added on top:
|
|
20
|
+
* `traceTransfers` already logs it, so adding it would double-count.
|
|
22
21
|
*/
|
|
23
22
|
async function simulateV1(params) {
|
|
24
23
|
const { rpcUrl, transactions, blockNumber, signal } = params;
|
|
@@ -53,9 +52,17 @@ async function simulateV1(params) {
|
|
|
53
52
|
account: sender,
|
|
54
53
|
calls,
|
|
55
54
|
...blockParam,
|
|
55
|
+
// Synthesize native-ETH moves (top-level value + internal calls) as
|
|
56
|
+
// Transfer logs from the native sentinel, so `parseTransfers` captures
|
|
57
|
+
// ETH that emits no real log (e.g. a WETH.withdraw refund).
|
|
58
|
+
traceTransfers: true,
|
|
56
59
|
// Inflate sender ETH balance to prevent false "insufficient gas" reverts.
|
|
57
60
|
// Without this, valid ERC20 flows fail when the sender has low ETH.
|
|
58
|
-
|
|
61
|
+
// Use half of uint256 (not the ceiling) so the override leaves headroom
|
|
62
|
+
// for inbound native ETH — e.g. a WETH.withdraw refund or a swap payout
|
|
63
|
+
// would overflow the recipient balance and revert the value transfer if
|
|
64
|
+
// the sender were pinned at maxUint256.
|
|
65
|
+
stateOverrides: [{ address: sender, balance: viem_1.maxUint256 / 2n }],
|
|
59
66
|
});
|
|
60
67
|
const results = simulationResult.results;
|
|
61
68
|
if (!Array.isArray(results)) {
|
|
@@ -77,10 +84,7 @@ async function simulateV1(params) {
|
|
|
77
84
|
}));
|
|
78
85
|
return {
|
|
79
86
|
calls: rawCalls,
|
|
80
|
-
assetChanges: toAssetChanges(
|
|
81
|
-
transfers: (0, index_js_1.parseTransfers)(rawCalls),
|
|
82
|
-
transactions,
|
|
83
|
-
}),
|
|
87
|
+
assetChanges: toAssetChanges((0, index_js_1.parseTransfers)(rawCalls)),
|
|
84
88
|
};
|
|
85
89
|
}
|
|
86
90
|
catch (error) {
|
|
@@ -92,20 +96,13 @@ async function simulateV1(params) {
|
|
|
92
96
|
}
|
|
93
97
|
}
|
|
94
98
|
/**
|
|
95
|
-
* Reduce parsed transfer logs
|
|
96
|
-
*
|
|
97
|
-
*
|
|
98
|
-
*
|
|
99
|
+
* Reduce parsed transfer logs to net per-token balance changes grouped by
|
|
100
|
+
* account. With `traceTransfers` enabled, native ETH (top-level and internal)
|
|
101
|
+
* arrives as transfer logs under viem's `ethAddress`, so no separate top-level
|
|
102
|
+
* `value` accounting is needed — doing so would double-count.
|
|
99
103
|
*/
|
|
100
|
-
function toAssetChanges(
|
|
101
|
-
const { transfers, transactions } = params;
|
|
104
|
+
function toAssetChanges(transfers) {
|
|
102
105
|
const entries = [];
|
|
103
|
-
for (const tx of transactions) {
|
|
104
|
-
if (!tx.value)
|
|
105
|
-
continue;
|
|
106
|
-
entries.push({ account: tx.from, token: viem_1.ethAddress, diff: -tx.value });
|
|
107
|
-
entries.push({ account: tx.to, token: viem_1.ethAddress, diff: tx.value });
|
|
108
|
-
}
|
|
109
106
|
for (const { token, from, to, amount } of transfers) {
|
|
110
107
|
entries.push({ account: to, token, diff: amount });
|
|
111
108
|
entries.push({ account: from, token, diff: -amount });
|
|
@@ -156,7 +156,11 @@ function buildCall(tx) {
|
|
|
156
156
|
};
|
|
157
157
|
}
|
|
158
158
|
function buildStateOverrides(sender) {
|
|
159
|
-
|
|
159
|
+
// Half of uint256 (not the ceiling) so the override leaves headroom for
|
|
160
|
+
// inbound native ETH — a WETH.withdraw refund or swap payout would overflow
|
|
161
|
+
// the recipient balance and revert the value transfer if the sender were
|
|
162
|
+
// pinned at maxUint256.
|
|
163
|
+
return { [sender]: { balance: (0, viem_1.numberToHex)(viem_1.maxUint256 / 2n) } };
|
|
160
164
|
}
|
|
161
165
|
function encodeBlock(blockNumber) {
|
|
162
166
|
if (blockNumber === undefined)
|
|
@@ -14,6 +14,15 @@ export declare const DEPOSIT_TOPIC = "0xe1fffcc4923d04b559f4d29a8bfc6cda04eb5b0d
|
|
|
14
14
|
* ERC1155 transfer events are **not** parsed — consumers with NFT flows will
|
|
15
15
|
* see an incomplete transfer list.
|
|
16
16
|
*
|
|
17
|
+
* **Native ETH (`eth_simulateV1` + `traceTransfers`).** When the backend runs
|
|
18
|
+
* `eth_simulateV1` with `traceTransfers` enabled, native-ETH moves — including
|
|
19
|
+
* those made through internal calls (e.g. a `WETH.withdraw` refund) — are
|
|
20
|
+
* synthesized as `Transfer` events emitted from the native sentinel
|
|
21
|
+
* `0xeee…eee`. These are parsed like any ERC20 transfer, with `token`
|
|
22
|
+
* collapsed to viem's `ethAddress` so native deltas key consistently across
|
|
23
|
+
* backends. Tenderly does not emit these synthetic logs (it derives native ETH
|
|
24
|
+
* separately), so this path is inert there.
|
|
25
|
+
*
|
|
17
26
|
* **WETH9 dedup assumption — canonical atomic emission.** Dedup is scoped to
|
|
18
27
|
* the same tx: a zero-address `Transfer` is suppressed only if its paired
|
|
19
28
|
* `Deposit`/`Withdrawal` appears in the same `calls[txIdx].logs` slice. This
|
|
@@ -23,6 +23,15 @@ const UINT256_HEX_LENGTH = 66; // "0x" + 32 bytes
|
|
|
23
23
|
* ERC1155 transfer events are **not** parsed — consumers with NFT flows will
|
|
24
24
|
* see an incomplete transfer list.
|
|
25
25
|
*
|
|
26
|
+
* **Native ETH (`eth_simulateV1` + `traceTransfers`).** When the backend runs
|
|
27
|
+
* `eth_simulateV1` with `traceTransfers` enabled, native-ETH moves — including
|
|
28
|
+
* those made through internal calls (e.g. a `WETH.withdraw` refund) — are
|
|
29
|
+
* synthesized as `Transfer` events emitted from the native sentinel
|
|
30
|
+
* `0xeee…eee`. These are parsed like any ERC20 transfer, with `token`
|
|
31
|
+
* collapsed to viem's `ethAddress` so native deltas key consistently across
|
|
32
|
+
* backends. Tenderly does not emit these synthetic logs (it derives native ETH
|
|
33
|
+
* separately), so this path is inert there.
|
|
34
|
+
*
|
|
26
35
|
* **WETH9 dedup assumption — canonical atomic emission.** Dedup is scoped to
|
|
27
36
|
* the same tx: a zero-address `Transfer` is suppressed only if its paired
|
|
28
37
|
* `Deposit`/`Withdrawal` appears in the same `calls[txIdx].logs` slice. This
|
|
@@ -132,7 +141,7 @@ function parseTransfers(calls, logger) {
|
|
|
132
141
|
}
|
|
133
142
|
}
|
|
134
143
|
transfers.push({
|
|
135
|
-
token: (
|
|
144
|
+
token: normalizeTransferToken(log.address),
|
|
136
145
|
from: (0, viem_1.getAddress)(`0x${fromTopic.slice(26)}`),
|
|
137
146
|
to: (0, viem_1.getAddress)(`0x${toTopic.slice(26)}`),
|
|
138
147
|
amount: BigInt(log.data),
|
|
@@ -153,6 +162,21 @@ function parseTransfers(calls, logger) {
|
|
|
153
162
|
}
|
|
154
163
|
return sortTransfers(transfers);
|
|
155
164
|
}
|
|
165
|
+
/**
|
|
166
|
+
* Normalize a `Transfer` log's emitting address into a transfer `token`.
|
|
167
|
+
*
|
|
168
|
+
* `eth_simulateV1` with `traceTransfers` enabled synthesizes native-ETH moves
|
|
169
|
+
* (including internal calls) as `Transfer` events emitted from the native
|
|
170
|
+
* sentinel `0xeee…eee`. The rest of the SDK keys native ETH by viem's
|
|
171
|
+
* `ethAddress` constant, so we collapse the sentinel to that exact value rather
|
|
172
|
+
* than the checksummed form `getAddress` would produce — keeping native deltas
|
|
173
|
+
* on a single map key across backends. Real ERC20 tokens are checksummed.
|
|
174
|
+
*/
|
|
175
|
+
function normalizeTransferToken(address) {
|
|
176
|
+
return address.toLowerCase() === viem_1.ethAddress
|
|
177
|
+
? viem_1.ethAddress
|
|
178
|
+
: (0, viem_1.getAddress)(address);
|
|
179
|
+
}
|
|
156
180
|
function isTopicHex(value) {
|
|
157
181
|
return typeof value === "string" && value.length === TOPIC_HEX_LENGTH;
|
|
158
182
|
}
|
package/lib/cjs/types.d.ts
CHANGED
|
@@ -82,10 +82,10 @@ export interface AssetChange {
|
|
|
82
82
|
* alike (the zero address is kept for mints/burns). Accounts and their `changes`
|
|
83
83
|
* are sorted by address for deterministic, cross-backend output.
|
|
84
84
|
*
|
|
85
|
-
*
|
|
86
|
-
*
|
|
87
|
-
*
|
|
88
|
-
*
|
|
85
|
+
* Both backends report the full net native-ETH delta, including ETH moved via
|
|
86
|
+
* internal calls (e.g. a `WETH.withdraw` refund): Tenderly derives it from its
|
|
87
|
+
* trace, and the `eth_simulateV1` fallback runs with `traceTransfers` so the
|
|
88
|
+
* node synthesizes native moves as transfer logs under viem's `ethAddress`.
|
|
89
89
|
*/
|
|
90
90
|
export interface AccountAssetChanges {
|
|
91
91
|
readonly account: Address;
|
|
@@ -6,14 +6,13 @@ import type { RawSimulationResult, SimulationTransaction } from "../../types.js"
|
|
|
6
6
|
* Uses stateOverride to set the user's ETH balance high enough to avoid
|
|
7
7
|
* false "insufficient balance for gas" reverts.
|
|
8
8
|
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
11
|
-
* `
|
|
12
|
-
*
|
|
13
|
-
*
|
|
14
|
-
*
|
|
15
|
-
*
|
|
16
|
-
* is not captured. For full native-ETH accounting use Tenderly.
|
|
9
|
+
* Runs with `traceTransfers` enabled, so the node synthesizes native-ETH moves
|
|
10
|
+
* (the top-level `value` *and* ETH moved through internal calls — e.g. a
|
|
11
|
+
* `WETH.withdraw` refund or a swap that pays out ETH) as `Transfer` events from
|
|
12
|
+
* the native sentinel. Derives `assetChanges` (net per-token deltas grouped by
|
|
13
|
+
* account) entirely from the emitted transfer logs, with native ETH normalized
|
|
14
|
+
* to viem's `ethAddress`. Top-level `value` is intentionally *not* added on top:
|
|
15
|
+
* `traceTransfers` already logs it, so adding it would double-count.
|
|
17
16
|
*/
|
|
18
17
|
export declare function simulateV1(params: {
|
|
19
18
|
rpcUrl: string;
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { createPublicClient,
|
|
1
|
+
import { createPublicClient, getAddress, http, maxUint256, } from "viem";
|
|
2
2
|
import { ExternalServiceError, SimulationRevertedError, SimulationValidationError, } from "../../errors.js";
|
|
3
3
|
import { groupAssetChanges } from "../asset-changes.js";
|
|
4
4
|
import { parseTransfers } from "../parsing/index.js";
|
|
@@ -8,14 +8,13 @@ import { parseTransfers } from "../parsing/index.js";
|
|
|
8
8
|
* Uses stateOverride to set the user's ETH balance high enough to avoid
|
|
9
9
|
* false "insufficient balance for gas" reverts.
|
|
10
10
|
*
|
|
11
|
-
*
|
|
12
|
-
*
|
|
13
|
-
* `
|
|
14
|
-
*
|
|
15
|
-
*
|
|
16
|
-
*
|
|
17
|
-
*
|
|
18
|
-
* is not captured. For full native-ETH accounting use Tenderly.
|
|
11
|
+
* Runs with `traceTransfers` enabled, so the node synthesizes native-ETH moves
|
|
12
|
+
* (the top-level `value` *and* ETH moved through internal calls — e.g. a
|
|
13
|
+
* `WETH.withdraw` refund or a swap that pays out ETH) as `Transfer` events from
|
|
14
|
+
* the native sentinel. Derives `assetChanges` (net per-token deltas grouped by
|
|
15
|
+
* account) entirely from the emitted transfer logs, with native ETH normalized
|
|
16
|
+
* to viem's `ethAddress`. Top-level `value` is intentionally *not* added on top:
|
|
17
|
+
* `traceTransfers` already logs it, so adding it would double-count.
|
|
19
18
|
*/
|
|
20
19
|
export async function simulateV1(params) {
|
|
21
20
|
const { rpcUrl, transactions, blockNumber, signal } = params;
|
|
@@ -50,9 +49,17 @@ export async function simulateV1(params) {
|
|
|
50
49
|
account: sender,
|
|
51
50
|
calls,
|
|
52
51
|
...blockParam,
|
|
52
|
+
// Synthesize native-ETH moves (top-level value + internal calls) as
|
|
53
|
+
// Transfer logs from the native sentinel, so `parseTransfers` captures
|
|
54
|
+
// ETH that emits no real log (e.g. a WETH.withdraw refund).
|
|
55
|
+
traceTransfers: true,
|
|
53
56
|
// Inflate sender ETH balance to prevent false "insufficient gas" reverts.
|
|
54
57
|
// Without this, valid ERC20 flows fail when the sender has low ETH.
|
|
55
|
-
|
|
58
|
+
// Use half of uint256 (not the ceiling) so the override leaves headroom
|
|
59
|
+
// for inbound native ETH — e.g. a WETH.withdraw refund or a swap payout
|
|
60
|
+
// would overflow the recipient balance and revert the value transfer if
|
|
61
|
+
// the sender were pinned at maxUint256.
|
|
62
|
+
stateOverrides: [{ address: sender, balance: maxUint256 / 2n }],
|
|
56
63
|
});
|
|
57
64
|
const results = simulationResult.results;
|
|
58
65
|
if (!Array.isArray(results)) {
|
|
@@ -74,10 +81,7 @@ export async function simulateV1(params) {
|
|
|
74
81
|
}));
|
|
75
82
|
return {
|
|
76
83
|
calls: rawCalls,
|
|
77
|
-
assetChanges: toAssetChanges(
|
|
78
|
-
transfers: parseTransfers(rawCalls),
|
|
79
|
-
transactions,
|
|
80
|
-
}),
|
|
84
|
+
assetChanges: toAssetChanges(parseTransfers(rawCalls)),
|
|
81
85
|
};
|
|
82
86
|
}
|
|
83
87
|
catch (error) {
|
|
@@ -89,20 +93,13 @@ export async function simulateV1(params) {
|
|
|
89
93
|
}
|
|
90
94
|
}
|
|
91
95
|
/**
|
|
92
|
-
* Reduce parsed transfer logs
|
|
93
|
-
*
|
|
94
|
-
*
|
|
95
|
-
*
|
|
96
|
+
* Reduce parsed transfer logs to net per-token balance changes grouped by
|
|
97
|
+
* account. With `traceTransfers` enabled, native ETH (top-level and internal)
|
|
98
|
+
* arrives as transfer logs under viem's `ethAddress`, so no separate top-level
|
|
99
|
+
* `value` accounting is needed — doing so would double-count.
|
|
96
100
|
*/
|
|
97
|
-
function toAssetChanges(
|
|
98
|
-
const { transfers, transactions } = params;
|
|
101
|
+
function toAssetChanges(transfers) {
|
|
99
102
|
const entries = [];
|
|
100
|
-
for (const tx of transactions) {
|
|
101
|
-
if (!tx.value)
|
|
102
|
-
continue;
|
|
103
|
-
entries.push({ account: tx.from, token: ethAddress, diff: -tx.value });
|
|
104
|
-
entries.push({ account: tx.to, token: ethAddress, diff: tx.value });
|
|
105
|
-
}
|
|
106
103
|
for (const { token, from, to, amount } of transfers) {
|
|
107
104
|
entries.push({ account: to, token, diff: amount });
|
|
108
105
|
entries.push({ account: from, token, diff: -amount });
|
|
@@ -153,7 +153,11 @@ function buildCall(tx) {
|
|
|
153
153
|
};
|
|
154
154
|
}
|
|
155
155
|
function buildStateOverrides(sender) {
|
|
156
|
-
|
|
156
|
+
// Half of uint256 (not the ceiling) so the override leaves headroom for
|
|
157
|
+
// inbound native ETH — a WETH.withdraw refund or swap payout would overflow
|
|
158
|
+
// the recipient balance and revert the value transfer if the sender were
|
|
159
|
+
// pinned at maxUint256.
|
|
160
|
+
return { [sender]: { balance: numberToHex(maxUint256 / 2n) } };
|
|
157
161
|
}
|
|
158
162
|
function encodeBlock(blockNumber) {
|
|
159
163
|
if (blockNumber === undefined)
|
|
@@ -14,6 +14,15 @@ export declare const DEPOSIT_TOPIC = "0xe1fffcc4923d04b559f4d29a8bfc6cda04eb5b0d
|
|
|
14
14
|
* ERC1155 transfer events are **not** parsed — consumers with NFT flows will
|
|
15
15
|
* see an incomplete transfer list.
|
|
16
16
|
*
|
|
17
|
+
* **Native ETH (`eth_simulateV1` + `traceTransfers`).** When the backend runs
|
|
18
|
+
* `eth_simulateV1` with `traceTransfers` enabled, native-ETH moves — including
|
|
19
|
+
* those made through internal calls (e.g. a `WETH.withdraw` refund) — are
|
|
20
|
+
* synthesized as `Transfer` events emitted from the native sentinel
|
|
21
|
+
* `0xeee…eee`. These are parsed like any ERC20 transfer, with `token`
|
|
22
|
+
* collapsed to viem's `ethAddress` so native deltas key consistently across
|
|
23
|
+
* backends. Tenderly does not emit these synthetic logs (it derives native ETH
|
|
24
|
+
* separately), so this path is inert there.
|
|
25
|
+
*
|
|
17
26
|
* **WETH9 dedup assumption — canonical atomic emission.** Dedup is scoped to
|
|
18
27
|
* the same tx: a zero-address `Transfer` is suppressed only if its paired
|
|
19
28
|
* `Deposit`/`Withdrawal` appears in the same `calls[txIdx].logs` slice. This
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { getAddress, zeroAddress, zeroHash } from "viem";
|
|
1
|
+
import { ethAddress, getAddress, zeroAddress, zeroHash, } from "viem";
|
|
2
2
|
// keccak256("Transfer(address,address,uint256)") — ERC-20 transfer event
|
|
3
3
|
export const TRANSFER_TOPIC = "0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef";
|
|
4
4
|
// keccak256("Withdrawal(address,uint256)") — WETH9 unwrap event
|
|
@@ -19,6 +19,15 @@ const UINT256_HEX_LENGTH = 66; // "0x" + 32 bytes
|
|
|
19
19
|
* ERC1155 transfer events are **not** parsed — consumers with NFT flows will
|
|
20
20
|
* see an incomplete transfer list.
|
|
21
21
|
*
|
|
22
|
+
* **Native ETH (`eth_simulateV1` + `traceTransfers`).** When the backend runs
|
|
23
|
+
* `eth_simulateV1` with `traceTransfers` enabled, native-ETH moves — including
|
|
24
|
+
* those made through internal calls (e.g. a `WETH.withdraw` refund) — are
|
|
25
|
+
* synthesized as `Transfer` events emitted from the native sentinel
|
|
26
|
+
* `0xeee…eee`. These are parsed like any ERC20 transfer, with `token`
|
|
27
|
+
* collapsed to viem's `ethAddress` so native deltas key consistently across
|
|
28
|
+
* backends. Tenderly does not emit these synthetic logs (it derives native ETH
|
|
29
|
+
* separately), so this path is inert there.
|
|
30
|
+
*
|
|
22
31
|
* **WETH9 dedup assumption — canonical atomic emission.** Dedup is scoped to
|
|
23
32
|
* the same tx: a zero-address `Transfer` is suppressed only if its paired
|
|
24
33
|
* `Deposit`/`Withdrawal` appears in the same `calls[txIdx].logs` slice. This
|
|
@@ -128,7 +137,7 @@ export function parseTransfers(calls, logger) {
|
|
|
128
137
|
}
|
|
129
138
|
}
|
|
130
139
|
transfers.push({
|
|
131
|
-
token:
|
|
140
|
+
token: normalizeTransferToken(log.address),
|
|
132
141
|
from: getAddress(`0x${fromTopic.slice(26)}`),
|
|
133
142
|
to: getAddress(`0x${toTopic.slice(26)}`),
|
|
134
143
|
amount: BigInt(log.data),
|
|
@@ -149,6 +158,21 @@ export function parseTransfers(calls, logger) {
|
|
|
149
158
|
}
|
|
150
159
|
return sortTransfers(transfers);
|
|
151
160
|
}
|
|
161
|
+
/**
|
|
162
|
+
* Normalize a `Transfer` log's emitting address into a transfer `token`.
|
|
163
|
+
*
|
|
164
|
+
* `eth_simulateV1` with `traceTransfers` enabled synthesizes native-ETH moves
|
|
165
|
+
* (including internal calls) as `Transfer` events emitted from the native
|
|
166
|
+
* sentinel `0xeee…eee`. The rest of the SDK keys native ETH by viem's
|
|
167
|
+
* `ethAddress` constant, so we collapse the sentinel to that exact value rather
|
|
168
|
+
* than the checksummed form `getAddress` would produce — keeping native deltas
|
|
169
|
+
* on a single map key across backends. Real ERC20 tokens are checksummed.
|
|
170
|
+
*/
|
|
171
|
+
function normalizeTransferToken(address) {
|
|
172
|
+
return address.toLowerCase() === ethAddress
|
|
173
|
+
? ethAddress
|
|
174
|
+
: getAddress(address);
|
|
175
|
+
}
|
|
152
176
|
function isTopicHex(value) {
|
|
153
177
|
return typeof value === "string" && value.length === TOPIC_HEX_LENGTH;
|
|
154
178
|
}
|
package/lib/esm/types.d.ts
CHANGED
|
@@ -82,10 +82,10 @@ export interface AssetChange {
|
|
|
82
82
|
* alike (the zero address is kept for mints/burns). Accounts and their `changes`
|
|
83
83
|
* are sorted by address for deterministic, cross-backend output.
|
|
84
84
|
*
|
|
85
|
-
*
|
|
86
|
-
*
|
|
87
|
-
*
|
|
88
|
-
*
|
|
85
|
+
* Both backends report the full net native-ETH delta, including ETH moved via
|
|
86
|
+
* internal calls (e.g. a `WETH.withdraw` refund): Tenderly derives it from its
|
|
87
|
+
* trace, and the `eth_simulateV1` fallback runs with `traceTransfers` so the
|
|
88
|
+
* node synthesizes native moves as transfer logs under viem's `ethAddress`.
|
|
89
89
|
*/
|
|
90
90
|
export interface AccountAssetChanges {
|
|
91
91
|
readonly account: Address;
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@morpho-org/evm-simulation",
|
|
3
3
|
"description": "EVM simulation engine for Morpho transactions.",
|
|
4
|
-
"version": "4.0
|
|
4
|
+
"version": "4.1.0",
|
|
5
5
|
"author": "Morpho Association <contact@morpho.org>",
|
|
6
6
|
"contributors": [
|
|
7
7
|
"Rubilmax <rmilon@gmail.com>"
|
|
@@ -25,16 +25,16 @@
|
|
|
25
25
|
],
|
|
26
26
|
"dependencies": {
|
|
27
27
|
"zod": "^4.4.3",
|
|
28
|
-
"@morpho-org/
|
|
29
|
-
"@morpho-org/
|
|
28
|
+
"@morpho-org/blue-sdk": "^6.3.0",
|
|
29
|
+
"@morpho-org/morpho-ts": "^2.7.0"
|
|
30
30
|
},
|
|
31
31
|
"peerDependencies": {
|
|
32
32
|
"viem": "^2.0.0"
|
|
33
33
|
},
|
|
34
34
|
"devDependencies": {
|
|
35
35
|
"typescript": "^6.0.3",
|
|
36
|
-
"viem": "2.
|
|
37
|
-
"vitest": "^4.1.
|
|
36
|
+
"viem": "^2.54.2",
|
|
37
|
+
"vitest": "^4.1.9",
|
|
38
38
|
"@morpho-org/test": "^2.8.1"
|
|
39
39
|
},
|
|
40
40
|
"scripts": {
|