Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions .env.example
Original file line number Diff line number Diff line change
Expand Up @@ -3,3 +3,11 @@
# Create an Alchemy API key: https://docs.alchemy.com/docs/alchemy-quickstart-guide
# Or even better, run a local node: https://ethereum.org/en/run-a-node and use it's RPC URL.
ETH_MAINNET_RPC="https://eth-mainnet.g.alchemy.com/v2/<your-api-key>"
ALCHEMY_API_KEY="your-alchemy-api-key-here"


# Required: Quicknode API keys for specific chain tests
# Sign up at https://quicknode.com for free trial with debug method support
QUICKNODE_BNB_CHAIN_API_KEY="your-quicknode-bnb-key"
QUICKNODE_MANTLE_API_KEY="your-quicknode-mantle-key"
QUICKNODE_MODE_API_KEY="your-quicknode-mode-key"
14 changes: 7 additions & 7 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@
<br>Mantle
</div>
</td>
<td style="width:100px; text-align:center;">
<td style="width:100px; text-align:center;">
<div align="center">
<img alt="mantle" src="https://raw.githubusercontent.com/rainbow-me/assets/master/blockchains/monad/info/logo.png" width="22"/>
<br>Monad
Expand All @@ -84,14 +84,14 @@
<br>Scroll
</div>
</td>
</tr>
<tr>
<td style="width:100px; text-align:center;">
<div align="center">
<img alt="coming soon" src="https://raw.githubusercontent.com/rainbow-me/assets/master/blockchains/mode/info/logo.png" width="22"/>
<br>Mode
</div>
</td>
</tr>
<tr>
<td style="width:100px; text-align:center;">
<div align="center">
<img alt="Worldchain" src="https://cdn.prod.website-files.com/6503306c491d20f69e484470/6718ce22ee5879d832765fd6_66ced64f18a10922ffcff77d_65d8bce782514cfb6c149b7a_1VQdZPHJ_400x400.webp" width="22"/>
Expand Down Expand Up @@ -121,15 +121,15 @@
<img alt="coming soon" src="https://i.imgur.com/CexTjqF.png" width="22"/>
<br>🔜
</div>
</td>
</td>
</tr>
<tr>
<td style="width:100px; text-align:center;">
<div align="center">
<img alt="coming soon" src="https://cdn.prod.website-files.com/63692bf32544bee8b1836ea6/637b01428c7bd8e16af26756_favicon-32.png" width="22"/>
<br>🔜
</div>
</td>
</tr>
<tr>
</td>
<td style="width:100px; text-align:center;">
<div align="center">
<img alt="coming soon" src="https://i.imgur.com/OPA8A9u.png" width="22"/>
Expand Down
67 changes: 67 additions & 0 deletions src/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,71 @@ import {
} from "viem/chains";
import type { SupportedChainId } from "./types";


export const FORWARDING_MULTICALL_ABI = [
{
"type": "receive",
"stateMutability": "payable"
},
{
"type": "function",
"name": "multicall",
"inputs": [
{
"name": "calls",
"type": "tuple[]",
"internalType": "struct IMultiCall.Call[]",
"components": [
{
"name": "target",
"type": "address",
"internalType": "address"
},
{
"name": "revertPolicy",
"type": "uint8",
"internalType": "enum IMultiCall.RevertPolicy"
},
{
"name": "value",
"type": "uint256",
"internalType": "uint256"
},
{
"name": "data",
"type": "bytes",
"internalType": "bytes"
}
]
},
{
"name": "contextdepth",
"type": "uint256",
"internalType": "uint256"
}
],
"outputs": [
{
"name": "",
"type": "tuple[]",
"internalType": "struct IMultiCall.Result[]",
"components": [
{
"name": "success",
"type": "bool",
"internalType": "bool"
},
{
"name": "data",
"type": "bytes",
"internalType": "bytes"
}
]
}
],
"stateMutability": "payable"
}
]
export const SETTLER_META_TXN_ABI = [
{
inputs: [
Expand Down Expand Up @@ -92,6 +157,8 @@ export const NATIVE_SYMBOL_BY_CHAIN_ID: { [key in SupportedChainId]: string } =

export const NATIVE_TOKEN_ADDRESS = `0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE`;

export const FORWARDING_MULTICALL_ADDRESS = `0x00000000000000cf9e3c5a26621af382fa17f24f`;
export const MULTICALL3_ADDRESS = `0xcA11bde05977b3631167028862bE2a173976CA11`;


export const ERC_4337_ENTRY_POINT = `0x5FF137D4b0FDCD49DcA30c7CF57E578a026d2789`;
41 changes: 38 additions & 3 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,12 +7,14 @@ import {
} from "viem";
import {
SUPPORTED_CHAINS,
MULTICALL3_ADDRESS,
FORWARDING_MULTICALL_ABI,
FUNCTION_SELECTORS,
ERC_4337_ENTRY_POINT,
NATIVE_TOKEN_ADDRESS,
SETTLER_META_TXN_ABI,
NATIVE_SYMBOL_BY_CHAIN_ID,
FORWARDING_MULTICALL_ADDRESS,
MULTICALL3_ADDRESS,
} from "./constants";
import {
transferLogs,
Expand Down Expand Up @@ -112,7 +114,40 @@ export async function parseSwap({
amount: nativeAmountToTaker,
address: NATIVE_TOKEN_ADDRESS,
};
if (to?.toLowerCase() === FORWARDING_MULTICALL_ADDRESS.toLowerCase()) {
const { args: multicallArgs } = decodeFunctionData({
abi: FORWARDING_MULTICALL_ABI,
data: transaction.input,
});

if (multicallArgs && Array.isArray(multicallArgs) && multicallArgs[0] && Array.isArray(multicallArgs[0])) {
const { args: settlerArgs } = decodeFunctionData({
abi: SETTLER_META_TXN_ABI,
data: multicallArgs[0][1]?.data,
});

const recipient =
settlerArgs[0].recipient.toLowerCase() as Address;

const msgSender = settlerArgs[3];

const nativeAmountToTaker = calculateNativeTransfer(trace, {
recipient,
});

if (nativeAmountToTaker === "0") {
[output] = logs.filter(
(log) => log.to.toLowerCase() === msgSender.toLowerCase()
);
} else {
output = {
symbol: NATIVE_SYMBOL_BY_CHAIN_ID[chainId],
amount: nativeAmountToTaker,
address: NATIVE_TOKEN_ADDRESS,
};
}
}
}
Comment on lines +117 to +150
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

MultiCall3 will no longer be used and we can instead rely on the forwarding multi call address and abi @ this step of txn parsing. I decided to leave the previous logic related to Multicall3 to maintain backwards compatibility.
cc: @duncancmt

if (to?.toLowerCase() === MULTICALL3_ADDRESS.toLowerCase()) {
const { args: multicallArgs } = decodeFunctionData({
abi: multicall3Abi,
Expand All @@ -122,7 +157,7 @@ export async function parseSwap({
if (multicallArgs[0]) {
const { args: settlerArgs } = decodeFunctionData({
abi: SETTLER_META_TXN_ABI,
data: multicallArgs[0][1].callData,
data: multicallArgs[0][1]?.callData,
});

const takerForGaslessApprovalSwap =
Expand Down Expand Up @@ -245,4 +280,4 @@ export async function parseSwap({
address: output.address,
},
};
}
}
78 changes: 78 additions & 0 deletions src/tests/index.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1455,3 +1455,81 @@ test("logs a warning for reverted transactions)", async () => {

warnSpy.mockRestore();
});

//https://etherscan.io/tx/0x7eea91c5c715ef4bb1e39ddf4c7832113693e87c18392740353d5ae669406a46
test("parse a swap on Ethereum (USDC for WMON) with SettlerIntent", async () => {
const transactionHash = "0x7eea91c5c715ef4bb1e39ddf4c7832113693e87c18392740353d5ae669406a46";

const result = await parseSwap({
publicClient: publicClient,
transactionHash,
});

expect(result).toEqual({
tokenIn: {
symbol: "USDC",
amount: "1000.080833",
address: "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48",
},
tokenOut: {
symbol: "WMON",
amount: "22204.573291811240325155",
address: "0x6917037F8944201b2648198a89906Edf863B9517",
},
});
});

// https://optimistic.etherscan.io/tx/0xdee6f4fea0250f297ed9663c4ca4479e8a253c62e16faa60759e25832cd1f34f
test("parse a swap on Optimism (wstETH for ETH) via Balancer pool", async () => {
const publicClient = createPublicClient({
chain: optimism,
transport: http(
`https://opt-mainnet.g.alchemy.com/v2/${process.env.ALCHEMY_API_KEY}`
),
}) as PublicClient<Transport, Chain>;

const transactionHash =
"0xdee6f4fea0250f297ed9663c4ca4479e8a253c62e16faa60759e25832cd1f34f";

const result = await parseSwap({
publicClient,
transactionHash,
});

expect(result).toEqual({
tokenIn: {
symbol: "wstETH",
amount: "0.008868",
address: "0x1F32b1c2345538c0c6f582fCB022739c4A194Ebb",
},
tokenOut: {
symbol: "ETH",
amount: "0.010671015314389981",
address: NATIVE_TOKEN_ADDRESS,
},
});
});

// https://etherscan.io/tx/0x967cb342227a2541a845058862e70833d638bf5bb7ce229c6506466dbb43a004
test("parse a swap on Mainnet (TRG for SHITCOIN)", async () => {
const transactionHash =
"0x967cb342227a2541a845058862e70833d638bf5bb7ce229c6506466dbb43a004" as `0x${string}`;

const result = await parseSwap({
publicClient,
transactionHash,
});

expect(result).toEqual({
tokenIn: {
symbol: "TRG",
amount: "5053634.405791388940070628",
address: "0x93eEB426782BD88fCD4B48D7b0368CF061044928",
},
tokenOut: {
symbol: "SHITCOIN",
amount: "881342.949331124",
address: "0x4fD1b29d1aAFeA37A2d19E7d912b6eda44dBd82C",
},
});
});