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
54 changes: 38 additions & 16 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -37,28 +37,40 @@ export async function parseSwap({
if (!isChainIdSupported(chainId)) {
throw new Error(`chainId ${chainId} is unsupported…`);
}

// This code extends the viem publicClient with a custom method called traceCall.
const client = publicClient.extend((client) => ({
async traceCall(args: { hash: Hash }) {
return client.request<TraceTransactionSchema>({
method: "debug_traceTransaction",
method: "debug_traceTransaction", //replays the txn & returns execution data
/**
* The callTracer returns a tree of all internal calls made during execution, including:
Contract-to-contract calls
Internal ETH transfers
Call depth, gas used, return values
*/
params: [args.hash, { tracer: "callTracer" }],
//
});
},
}));

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

const { from: taker, value, to } = transaction;

const isToERC4337 = to === ERC_4337_ENTRY_POINT.toLowerCase();

// 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
// Potential Solution: we need to determine if the txn is a settlerIntent txn, if so we
// can determine the taker from the to address in the last trace call.
const actualTaker = trace.calls[trace.calls.length-1].to;
//Scans the execution trace for any internal ETH transfers to the taker's address
const nativeAmountToTaker = calculateNativeTransfer(trace, {
recipient: taker,
recipient: taker, // for Settler Intent txns we should use actualTaker
Comment on lines +67 to +73
Copy link
Member Author

@barskhianfannie barskhianfannie Dec 3, 2025

Choose a reason for hiding this comment

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

@duncancmt In order to solve the failing parse for this Optimism txn, I am thinking of using the solution outlined in the comments of this codeblock. Any feedback before I begin implementation on if this is a valid approach or not?

});

if (transactionReceipt.status === "reverted") {
Expand All @@ -74,7 +86,9 @@ export async function parseSwap({
publicClient,
transactionReceipt,
});

// if a Smart wallet, then the way in which we determine the taker is different than traditional EOA's.
// The actual taker is the smart contract embedded in the wallet
const isToERC4337 = to === ERC_4337_ENTRY_POINT.toLowerCase();
if (isToERC4337) {
if (!smartContractWallet) {
throw new Error(
Expand Down Expand Up @@ -224,14 +238,22 @@ export async function parseSwap({
};
}

/* v8 ignore start */
if (!output) {
console.error(
"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."
);
return null;
}
/* v8 ignore stop */
if (!output) {
if (!logs.length) /* v8 ignore next */ return null;
const lastTransfer = logs[logs.length - 1];
return {
tokenIn: {
symbol: input.symbol,
amount: input.amount,
address: input.address,
},
tokenOut: {
symbol: lastTransfer.symbol,
address: lastTransfer.address,
amount: lastTransfer.amount
}
};
}

return {
tokenIn: {
Expand All @@ -245,4 +267,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",
},
});
});