From 579d1c4252cb362b74ef2dc779bff11226f61d91 Mon Sep 17 00:00:00 2001 From: SashaMIT Date: Wed, 5 Aug 2026 22:04:02 +0700 Subject: [PATCH] fix(sushi): report executed swap when Route event is missing MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit When the swap transaction succeeds on-chain but the receipt contains no RouteProcessor9 Route event (e.g. fills routed via a different executor, or a wallet provider returning receipts without logs), the action threw while decoding routeLog.args and returned a generic "Error swapping tokens" message — even though the swap had executed. Agent frameworks treat that as a failure and retry, executing a second, unintended swap (double-spend). Guard the decode: if no Route event is present, report the swap as executed with the tx hash, the quoted AmountOut, and an explicit instruction not to retry automatically. Co-authored-by: Cursor --- .../sushi/sushiRouterActionProvider.test.ts | 44 +++++++++++++++++++ .../sushi/sushiRouterActionProvider.ts | 14 +++++- 2 files changed, 57 insertions(+), 1 deletion(-) diff --git a/typescript/agentkit/src/action-providers/sushi/sushiRouterActionProvider.test.ts b/typescript/agentkit/src/action-providers/sushi/sushiRouterActionProvider.test.ts index 2f3dc1798..bfd693312 100644 --- a/typescript/agentkit/src/action-providers/sushi/sushiRouterActionProvider.test.ts +++ b/typescript/agentkit/src/action-providers/sushi/sushiRouterActionProvider.test.ts @@ -463,6 +463,50 @@ describe("Sushi Action Provider", () => { expect(result).toContain(`Swapped`); }); + it("should report an executed swap when the Route event is missing from the receipt", async () => { + const args: Parameters<(typeof actionProvider)["swap"]>[1] = { + amount: formatUnits(amountIn, tokenIn.decimals), + fromAssetAddress: tokenIn.address, + toAssetAddress: tokenOut.address, + maxSlippage: 0.005, + }; + + /* + * 1. Mock the readContract which checks the decimals of the fromAssetAddress token (18, default) + * 2. Mock the readContract which checks for the balance of the fromAssetAddress token (1000000, enough balance) + * 3. Mock the readContract which checks for the approval (1000000, approved) + */ + mockWallet.readContract + .mockResolvedValueOnce(tokenIn.decimals) + .mockResolvedValueOnce(amountIn) + .mockResolvedValueOnce(amountIn); + + mockWallet.sendTransaction.mockResolvedValue(txHash); + + // Swap tx succeeds on-chain but the receipt contains no Route event log + mockWallet.waitForTransactionReceipt.mockResolvedValueOnce({ + status: "success", + logs: [], + }); + + mockedGetSwap.mockReturnValue( + getSuccessfullSwapResponse({ + tokenIn, + amountIn, + tokenOut, + amountOut, + }), + ); + + const result = await actionProvider.swap(mockWallet, args); + + expect(mockWallet.sendTransaction).toHaveBeenCalledTimes(1); // Swap only + expect(result).toContain("Swap executed"); + expect(result).not.toContain("Error"); + expect(result).toContain(`Transaction hash: ${txHash}`); + expect(result).toContain("Do not retry this swap automatically"); + }); + it("should fail if there's no route", async () => { const args: Parameters<(typeof actionProvider)["swap"]>[1] = { amount: formatUnits(amountIn, tokenIn.decimals), diff --git a/typescript/agentkit/src/action-providers/sushi/sushiRouterActionProvider.ts b/typescript/agentkit/src/action-providers/sushi/sushiRouterActionProvider.ts index 0eab9575b..bdc844272 100644 --- a/typescript/agentkit/src/action-providers/sushi/sushiRouterActionProvider.ts +++ b/typescript/agentkit/src/action-providers/sushi/sushiRouterActionProvider.ts @@ -149,7 +149,7 @@ Important notes: } // Find the Route event log, which includes the actual amountOut - const [routeLog] = swapReceipt.logs + const [routeLog] = (swapReceipt.logs ?? []) .filter( log => encodeEventTopics({ @@ -165,6 +165,18 @@ Important notes: }), ); + if (!routeLog) { + // The swap succeeded on-chain, but the receipt has no Route event to + // decode amounts from (e.g. fills not routed through RouteProcessor9). + // Report this as an executed swap — returning a generic error here + // leads agents to retry and execute a second, unintended swap. + return `Swap executed on ${chain.shortName}, but the Route event was not found in the transaction receipt, so the exact output amount could not be decoded. + - Quoted AmountOut: ${formatUnits(BigInt(secondSwap.swap.assumedAmountOut), secondSwap.swap.tokenTo.decimals)} ${secondSwap.swap.tokenTo.symbol} (${args.toAssetAddress}) + - Transaction hash: ${swapHash} + - Transaction link: ${chain.getTransactionUrl(swapHash)} +Do not retry this swap automatically; check the transaction first.`; + } + return `Swapped ${formatUnits(routeLog.args.amountIn, secondSwap.swap.tokenFrom.decimals)} of ${secondSwap.swap.tokenFrom.symbol} (${args.fromAssetAddress}) for ${formatUnits(routeLog.args.amountOut, secondSwap.swap.tokenTo.decimals)} of ${secondSwap.swap.tokenTo.symbol} (${args.toAssetAddress}) on ${chain.shortName} - Transaction hash: ${swapHash} - Transaction link: ${chain.getTransactionUrl(swapHash)}`;