From e7da88d7886e08037514acb433e41271d2c5f5e6 Mon Sep 17 00:00:00 2001 From: creativityventures Date: Sat, 19 Sep 2026 13:51:17 +0200 Subject: [PATCH 1/7] fix(erc20): compare transfer guardrail addresses case-insensitively --- .../agentkit/src/action-providers/erc20/erc20ActionProvider.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/typescript/agentkit/src/action-providers/erc20/erc20ActionProvider.ts b/typescript/agentkit/src/action-providers/erc20/erc20ActionProvider.ts index db03f8db7..16b101319 100644 --- a/typescript/agentkit/src/action-providers/erc20/erc20ActionProvider.ts +++ b/typescript/agentkit/src/action-providers/erc20/erc20ActionProvider.ts @@ -97,7 +97,7 @@ Important notes: } // Guardrails to prevent loss of funds - if (args.tokenAddress === args.destinationAddress) { + if (args.tokenAddress.toLowerCase() === args.destinationAddress.toLowerCase()) { return "Error: Transfer destination is the token contract address. Refusing transfer to prevent loss of funds."; } if ( From c76df9a12e8383a5f26d01daf278892cb33f9b24 Mon Sep 17 00:00:00 2001 From: creativityventures Date: Sat, 19 Sep 2026 13:52:27 +0200 Subject: [PATCH 2/7] test(erc20): cover a destination that differs from the token only in case --- .../erc20/erc20ActionProvider.test.ts | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/typescript/agentkit/src/action-providers/erc20/erc20ActionProvider.test.ts b/typescript/agentkit/src/action-providers/erc20/erc20ActionProvider.test.ts index 2089d2036..691bc3c80 100644 --- a/typescript/agentkit/src/action-providers/erc20/erc20ActionProvider.test.ts +++ b/typescript/agentkit/src/action-providers/erc20/erc20ActionProvider.test.ts @@ -139,6 +139,25 @@ describe("Transfer Action", () => { expect(response).toContain(`Transaction hash for the transfer: ${TRANSACTION_HASH}`); }); + it("should refuse a transfer whose destination is the token contract in a different case", async () => { + mockMulticall.mockResolvedValueOnce([ + { result: "MockToken" }, // name + { result: MOCK_DECIMALS }, // decimals + { result: BigInt(100000 * 10 ** MOCK_DECIMALS) }, // balance + ]); + + const args = { + amount: MOCK_AMOUNT.toString(), + tokenAddress: "0xABCDEF1234567890123456789012345678901234", + destinationAddress: "0xabcdef1234567890123456789012345678901234", + }; + + const response = await actionProvider.transfer(mockWallet, args); + + expect(mockWallet.sendTransaction).not.toHaveBeenCalled(); + expect(response).toContain("Transfer destination is the token contract address"); + }); + it("should fail with an error", async () => { mockMulticall.mockRejectedValue(new Error("Failed to get token details")); From a8c6ea3a4ea45280190a62badcfeba72f39a2042 Mon Sep 17 00:00:00 2001 From: creativityventures Date: Sat, 19 Sep 2026 13:52:49 +0200 Subject: [PATCH 3/7] fix(sushi): match the native asset address case-insensitively --- .../src/action-providers/sushi/sushiRouterActionProvider.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/typescript/agentkit/src/action-providers/sushi/sushiRouterActionProvider.ts b/typescript/agentkit/src/action-providers/sushi/sushiRouterActionProvider.ts index 0eab9575b..c5a904849 100644 --- a/typescript/agentkit/src/action-providers/sushi/sushiRouterActionProvider.ts +++ b/typescript/agentkit/src/action-providers/sushi/sushiRouterActionProvider.ts @@ -270,7 +270,7 @@ async function fetchDecimals({ }; } - if (token === nativeAddress) { + if (token.toLowerCase() === nativeAddress) { return { success: true, decimals: EvmNative.fromChainId(chainId).decimals }; } From 6728529db62b4ed2b76ac91e780b5c86bd4d37a3 Mon Sep 17 00:00:00 2001 From: creativityventures Date: Sat, 19 Sep 2026 13:53:22 +0200 Subject: [PATCH 4/7] test(sushi): cover a mixed-case native asset address --- .../sushi/sushiRouterActionProvider.test.ts | 25 +++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/typescript/agentkit/src/action-providers/sushi/sushiRouterActionProvider.test.ts b/typescript/agentkit/src/action-providers/sushi/sushiRouterActionProvider.test.ts index 2f3dc1798..266e0e3a7 100644 --- a/typescript/agentkit/src/action-providers/sushi/sushiRouterActionProvider.test.ts +++ b/typescript/agentkit/src/action-providers/sushi/sushiRouterActionProvider.test.ts @@ -356,6 +356,31 @@ describe("Sushi Action Provider", () => { expect(result).toContain(`on ${getEvmChainById(chainId).shortName}`); }); + it("should treat a mixed-case native address as native", async () => { + const args: Parameters<(typeof actionProvider)["swap"]>[1] = { + amount: formatUnits(amountIn, tokenIn.decimals), + fromAssetAddress: nativeAddress.toUpperCase().replace("0X", "0x") as Address, + toAssetAddress: tokenOut.address, + maxSlippage: 0.005, + }; + + mockWallet.getBalance.mockResolvedValue(amountIn); + mockWallet.sendTransaction.mockResolvedValue(txHash); + mockWallet.waitForTransactionReceipt.mockResolvedValueOnce({ + status: "success", + logs: getRouteLog({ tokenIn: nativeToken, tokenOut, amountIn, amountOut }), + }); + mockedGetSwap.mockReturnValue( + getSuccessfullSwapResponse({ tokenIn: nativeToken, amountIn, tokenOut, amountOut }), + ); + + const result = await actionProvider.swap(mockWallet, args); + + // No decimals lookup: the native asset has no ERC20 contract to read it from + expect(mockWallet.readContract).toHaveBeenCalledTimes(0); + expect(result).toContain("Swapped"); + }); + it("should fail if there isn't enough balance (native)", async () => { const args: Parameters<(typeof actionProvider)["swap"]>[1] = { amount: formatUnits(amountIn, tokenIn.decimals), From afc4ad6b21bbfbc8c24f5763a0557c1c9344a3ef Mon Sep 17 00:00:00 2001 From: creativityventures Date: Sat, 19 Sep 2026 13:53:46 +0200 Subject: [PATCH 5/7] fix(yelay): match vault addresses case-insensitively --- .../action-providers/yelay/yelayActionProvider.ts | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/typescript/agentkit/src/action-providers/yelay/yelayActionProvider.ts b/typescript/agentkit/src/action-providers/yelay/yelayActionProvider.ts index d06c840ca..af46e987c 100644 --- a/typescript/agentkit/src/action-providers/yelay/yelayActionProvider.ts +++ b/typescript/agentkit/src/action-providers/yelay/yelayActionProvider.ts @@ -124,7 +124,9 @@ APY: ${vault.apy}% const chainId = wallet.getNetwork().chainId! as ChainId; const vaultsResponse = await fetch(`${YELAY_BACKEND_URL}/vaults?chainId=${chainId}`); const vaults = (await vaultsResponse.json()) as VaultsDetailsResponse[]; - const vault = vaults.find(vault => vault.address === args.vaultAddress); + const vault = vaults.find( + vault => vault.address.toLowerCase() === args.vaultAddress.toLowerCase(), + ); if (!vault) { return "Error: Vault not found"; @@ -181,7 +183,9 @@ Important notes: const chainId = wallet.getNetwork().chainId! as ChainId; const vaultsResponse = await fetch(`${YELAY_BACKEND_URL}/vaults?chainId=${chainId}`); const vaults = (await vaultsResponse.json()) as VaultsDetailsResponse[]; - const vault = vaults.find(vault => vault.address === args.vaultAddress); + const vault = vaults.find( + vault => vault.address.toLowerCase() === args.vaultAddress.toLowerCase(), + ); if (!vault) { return "Error: Vault not found"; @@ -287,7 +291,9 @@ It takes: const chainId = wallet.getNetwork().chainId! as ChainId; const vaultsResponse = await fetch(`${YELAY_BACKEND_URL}/vaults?chainId=${chainId}`); const vaults = (await vaultsResponse.json()) as VaultsDetailsResponse[]; - const vault = vaults.find(vault => vault.address === args.vaultAddress); + const vault = vaults.find( + vault => vault.address.toLowerCase() === args.vaultAddress.toLowerCase(), + ); if (!vault) { return "Error: Vault not found"; From 355f0b6f26abaf3cac98aaa95eda92efb5e424c5 Mon Sep 17 00:00:00 2001 From: creativityventures Date: Sat, 19 Sep 2026 13:54:15 +0200 Subject: [PATCH 6/7] test(yelay): cover a vault address whose case differs from the API --- .../yelay/yelayActionProvider.test.ts | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/typescript/agentkit/src/action-providers/yelay/yelayActionProvider.test.ts b/typescript/agentkit/src/action-providers/yelay/yelayActionProvider.test.ts index 3b20e906a..4ea78e22e 100644 --- a/typescript/agentkit/src/action-providers/yelay/yelayActionProvider.test.ts +++ b/typescript/agentkit/src/action-providers/yelay/yelayActionProvider.test.ts @@ -211,6 +211,23 @@ APY: 5.2% const response = await provider.deposit(mockWallet, args); expect(response).toContain("Deposit failed"); }); + + it("should find the vault when its address case differs from the API response", async () => { + const checksummedVault = "0xAbCdEf1234567890123456789012345678901234"; + mockedFetch.mockResolvedValueOnce( + mockFetchResult(200, [{ ...mockVaults[0], address: checksummedVault }]), + ); + + const args = { + assets: MOCK_WHOLE_ASSETS, + vaultAddress: checksummedVault.toLowerCase(), + }; + + const response = await provider.deposit(mockWallet, args); + + expect(response).not.toContain("Vault not found"); + expect(response).toContain(`Deposited ${MOCK_WHOLE_ASSETS}`); + }); }); describe("redeem action", () => { From 34c900ab30af4b1e4c7bea0e8ce943fe79c33549 Mon Sep 17 00:00:00 2001 From: creativityventures Date: Sat, 19 Sep 2026 13:54:42 +0200 Subject: [PATCH 7/7] chore: add changeset --- .../.changeset/case-insensitive-address-comparisons.md | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 typescript/.changeset/case-insensitive-address-comparisons.md diff --git a/typescript/.changeset/case-insensitive-address-comparisons.md b/typescript/.changeset/case-insensitive-address-comparisons.md new file mode 100644 index 000000000..001906ee0 --- /dev/null +++ b/typescript/.changeset/case-insensitive-address-comparisons.md @@ -0,0 +1,5 @@ +--- +"@coinbase/agentkit": patch +--- + +Fixed three EVM address comparisons that were case-sensitive: the erc20 transfer guardrail that refuses to send tokens to the token's own contract, the sushi router's native-asset check, and the yelay vault lookup. Each compared addresses as raw strings, so the same address written in a different case did not match. They now compare lowercased, as the rest of the codebase and the Python SDK already do.