Skip to content

Commit 31c73eb

Browse files
adds forward multi call support
1 parent 26a378b commit 31c73eb

File tree

2 files changed

+119
-39
lines changed

2 files changed

+119
-39
lines changed

src/constants.ts

Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,71 @@ import {
1919
} from "viem/chains";
2020
import type { SupportedChainId } from "./types";
2121

22+
23+
export const FORWARDING_MULTICALL_ABI = [
24+
{
25+
"type": "receive",
26+
"stateMutability": "payable"
27+
},
28+
{
29+
"type": "function",
30+
"name": "multicall",
31+
"inputs": [
32+
{
33+
"name": "calls",
34+
"type": "tuple[]",
35+
"internalType": "struct IMultiCall.Call[]",
36+
"components": [
37+
{
38+
"name": "target",
39+
"type": "address",
40+
"internalType": "address"
41+
},
42+
{
43+
"name": "revertPolicy",
44+
"type": "uint8",
45+
"internalType": "enum IMultiCall.RevertPolicy"
46+
},
47+
{
48+
"name": "value",
49+
"type": "uint256",
50+
"internalType": "uint256"
51+
},
52+
{
53+
"name": "data",
54+
"type": "bytes",
55+
"internalType": "bytes"
56+
}
57+
]
58+
},
59+
{
60+
"name": "contextdepth",
61+
"type": "uint256",
62+
"internalType": "uint256"
63+
}
64+
],
65+
"outputs": [
66+
{
67+
"name": "",
68+
"type": "tuple[]",
69+
"internalType": "struct IMultiCall.Result[]",
70+
"components": [
71+
{
72+
"name": "success",
73+
"type": "bool",
74+
"internalType": "bool"
75+
},
76+
{
77+
"name": "data",
78+
"type": "bytes",
79+
"internalType": "bytes"
80+
}
81+
]
82+
}
83+
],
84+
"stateMutability": "payable"
85+
}
86+
]
2287
export const SETTLER_META_TXN_ABI = [
2388
{
2489
inputs: [
@@ -92,6 +157,8 @@ export const NATIVE_SYMBOL_BY_CHAIN_ID: { [key in SupportedChainId]: string } =
92157

93158
export const NATIVE_TOKEN_ADDRESS = `0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE`;
94159

160+
export const FORWARDING_MULTICALL_ADDRESS = `0x00000000000000cf9e3c5a26621af382fa17f24f`;
95161
export const MULTICALL3_ADDRESS = `0xcA11bde05977b3631167028862bE2a173976CA11`;
96162

163+
97164
export const ERC_4337_ENTRY_POINT = `0x5FF137D4b0FDCD49DcA30c7CF57E578a026d2789`;

src/index.ts

Lines changed: 52 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -7,12 +7,14 @@ import {
77
} from "viem";
88
import {
99
SUPPORTED_CHAINS,
10-
MULTICALL3_ADDRESS,
10+
FORWARDING_MULTICALL_ABI,
1111
FUNCTION_SELECTORS,
1212
ERC_4337_ENTRY_POINT,
1313
NATIVE_TOKEN_ADDRESS,
1414
SETTLER_META_TXN_ABI,
1515
NATIVE_SYMBOL_BY_CHAIN_ID,
16+
FORWARDING_MULTICALL_ADDRESS,
17+
MULTICALL3_ADDRESS,
1618
} from "./constants";
1719
import {
1820
transferLogs,
@@ -37,40 +39,28 @@ export async function parseSwap({
3739
if (!isChainIdSupported(chainId)) {
3840
throw new Error(`chainId ${chainId} is unsupported…`);
3941
}
40-
// This code extends the viem publicClient with a custom method called traceCall.
42+
4143
const client = publicClient.extend((client) => ({
4244
async traceCall(args: { hash: Hash }) {
4345
return client.request<TraceTransactionSchema>({
44-
method: "debug_traceTransaction", //replays the txn & returns execution data
45-
/**
46-
* The callTracer returns a tree of all internal calls made during execution, including:
47-
Contract-to-contract calls
48-
Internal ETH transfers
49-
Call depth, gas used, return values
50-
*/
46+
method: "debug_traceTransaction",
5147
params: [args.hash, { tracer: "callTracer" }],
52-
//
5348
});
5449
},
5550
}));
5651

57-
// trace - ETH transfers, contract calls
58-
// transaction- from, to, value, input calldata
59-
// transactionReceipt - status, gas used, logs emitted
6052
const [trace, transaction, transactionReceipt] = await Promise.all([
6153
client.traceCall({ hash }),
6254
publicClient.getTransaction({ hash }),
6355
publicClient.getTransactionReceipt({ hash }),
6456
]);
6557

6658
const { from: taker, value, to } = transaction;
67-
// The Issue: FOR SETTLERINTENT TXNS only - The taker address passed into calculateNativeTransfer is not the actual takers address, causing nativeAmountToTaker to be 0 when it shouldn't be
68-
// Potential Solution: we need to determine if the txn is a settlerIntent txn, if so we
69-
// can determine the taker from the to address in the last trace call.
70-
const actualTaker = trace.calls[trace.calls.length-1].to;
71-
//Scans the execution trace for any internal ETH transfers to the taker's address
59+
60+
const isToERC4337 = to === ERC_4337_ENTRY_POINT.toLowerCase();
61+
7262
const nativeAmountToTaker = calculateNativeTransfer(trace, {
73-
recipient: taker, // for Settler Intent txns we should use actualTaker
63+
recipient: taker,
7464
});
7565

7666
if (transactionReceipt.status === "reverted") {
@@ -86,9 +76,7 @@ export async function parseSwap({
8676
publicClient,
8777
transactionReceipt,
8878
});
89-
// if a Smart wallet, then the way in which we determine the taker is different than traditional EOA's.
90-
// The actual taker is the smart contract embedded in the wallet
91-
const isToERC4337 = to === ERC_4337_ENTRY_POINT.toLowerCase();
79+
9280
if (isToERC4337) {
9381
if (!smartContractWallet) {
9482
throw new Error(
@@ -126,7 +114,40 @@ export async function parseSwap({
126114
amount: nativeAmountToTaker,
127115
address: NATIVE_TOKEN_ADDRESS,
128116
};
117+
if (to?.toLowerCase() === FORWARDING_MULTICALL_ADDRESS.toLowerCase()) {
118+
const { args: multicallArgs } = decodeFunctionData({
119+
abi: FORWARDING_MULTICALL_ABI,
120+
data: transaction.input,
121+
});
122+
123+
if (multicallArgs && Array.isArray(multicallArgs) && multicallArgs[0] && Array.isArray(multicallArgs[0])) {
124+
const { args: settlerArgs } = decodeFunctionData({
125+
abi: SETTLER_META_TXN_ABI,
126+
data: multicallArgs[0][1]?.data,
127+
});
128+
129+
const recipient =
130+
settlerArgs[0].recipient.toLowerCase() as Address;
131+
132+
const msgSender = settlerArgs[3];
133+
134+
const nativeAmountToTaker = calculateNativeTransfer(trace, {
135+
recipient,
136+
});
129137

138+
if (nativeAmountToTaker === "0") {
139+
[output] = logs.filter(
140+
(log) => log.to.toLowerCase() === msgSender.toLowerCase()
141+
);
142+
} else {
143+
output = {
144+
symbol: NATIVE_SYMBOL_BY_CHAIN_ID[chainId],
145+
amount: nativeAmountToTaker,
146+
address: NATIVE_TOKEN_ADDRESS,
147+
};
148+
}
149+
}
150+
}
130151
if (to?.toLowerCase() === MULTICALL3_ADDRESS.toLowerCase()) {
131152
const { args: multicallArgs } = decodeFunctionData({
132153
abi: multicall3Abi,
@@ -136,7 +157,7 @@ export async function parseSwap({
136157
if (multicallArgs[0]) {
137158
const { args: settlerArgs } = decodeFunctionData({
138159
abi: SETTLER_META_TXN_ABI,
139-
data: multicallArgs[0][1].callData,
160+
data: multicallArgs[0][1]?.callData,
140161
});
141162

142163
const takerForGaslessApprovalSwap =
@@ -238,22 +259,14 @@ export async function parseSwap({
238259
};
239260
}
240261

241-
if (!output) {
242-
if (!logs.length) /* v8 ignore next */ return null;
243-
const lastTransfer = logs[logs.length - 1];
244-
return {
245-
tokenIn: {
246-
symbol: input.symbol,
247-
amount: input.amount,
248-
address: input.address,
249-
},
250-
tokenOut: {
251-
symbol: lastTransfer.symbol,
252-
address: lastTransfer.address,
253-
amount: lastTransfer.amount
254-
}
255-
};
256-
}
262+
/* v8 ignore start */
263+
if (!output) {
264+
console.error(
265+
"File a bug report here, including the expected results (URL to a block explorer) and the unexpected results: https://github.com/0xProject/0x-parser/issues/new/choose."
266+
);
267+
return null;
268+
}
269+
/* v8 ignore stop */
257270

258271
return {
259272
tokenIn: {

0 commit comments

Comments
 (0)