Skip to content

Fix: replace 0x with aerodrome and uniswap adapters so we no longer have arbitrary calldata#10

Merged
krishan711 merged 29 commits into
mainfrom
fixzerox2
Feb 25, 2026
Merged

Fix: replace 0x with aerodrome and uniswap adapters so we no longer have arbitrary calldata#10
krishan711 merged 29 commits into
mainfrom
fixzerox2

Conversation

@krishan711
Copy link
Copy Markdown
Contributor

No description provided.

Copilot AI review requested due to automatic review settings February 16, 2026 20:32
@krishan711 krishan711 changed the base branch from main to fixzerox February 16, 2026 20:32
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

This pull request replaces the 0x swap integration with three new decentralized exchange adapters: Uniswap V3, Aerodrome V2, and Aerodrome CL (Concentrated Liquidity). The PR introduces a new base class for swap adapters (AWKSwapAdapter) and implements a separate sell policy contract for managing which tokens can be swapped.

Changes:

  • Added generic AWK swap adapters for three DEX protocols with structured route validation
  • Added YieldSeeker-specific wrapper adapters that integrate with fee tracking and sell policy
  • Introduced SwapSellPolicy contract for managing sellable token allowlists with role-based access control

Reviewed changes

Copilot reviewed 8 out of 8 changed files in this pull request and generated 17 comments.

Show a summary per file
File Description
src/agentwalletkit/adapters/AWKSwapAdapter.sol Base class for all swap adapters, provides balance tracking and validation hooks
src/agentwalletkit/adapters/AWKUniswapV3SwapAdapter.sol Generic Uniswap V3 swap adapter with fee tier validation and multi-hop routing
src/agentwalletkit/adapters/AWKAerodromeV2SwapAdapter.sol Generic Aerodrome V2 swap adapter with stable/volatile pool routing
src/agentwalletkit/adapters/AWKAerodromeCLSwapAdapter.sol Generic Aerodrome CL swap adapter with tick spacing validation
src/adapters/SwapSellPolicy.sol Access-controlled policy contract for managing sellable token allowlist
src/adapters/UniswapV3SwapAdapter.sol YieldSeeker wrapper for Uniswap V3 with fee tracking and base asset validation
src/adapters/AerodromeV2SwapAdapter.sol YieldSeeker wrapper for Aerodrome V2 with fee tracking and base asset validation
src/adapters/AerodromeCLSwapAdapter.sol YieldSeeker wrapper for Aerodrome CL with fee tracking and base asset validation
Comments suppressed due to low confidence (1)

src/agentwalletkit/adapters/AWKSwapAdapter.sol:61

  • The error always passes sellToken as the parameter, even when buyToken might be the zero address or when the issue is that sellToken == buyToken. This makes error diagnosis more difficult. Consider improving the error handling to indicate which specific validation failed, or pass both tokens to the error for better context.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread src/agentwalletkit/adapters/AWKUniswapV3SwapAdapter.sol
Comment on lines +1 to +136
// SPDX-License-Identifier: MIT
//
// _ _ __ __ _ _ _ _ ___ _
// / \ __ _ ___ _ __ | |\ \ / /_ _| | | ___| |_| |/ (_) |_
// / _ \ / _` |/ _ \ '_ \| __\ \ /\ / / _` | | |/ _ \ __| ' /| | __|
// / ___ \ (_| | __/ | | | |_ \ V V / (_| | | | __/ |_| . \| | |_
// /_/ \_\__, |\___|_| |_|\__| \_/\_/ \__,_|_|_|\___|\__|_|\_\_|\__|
// |___/
//
// Build verifiably secure onchain agents
// https://agentwalletkit.tokenpage.xyz
//
// For technical queries or guidance contact @krishan711
//
pragma solidity 0.8.28;

import {UnknownOperation} from "../AWKAdapter.sol";
import {AWKErrors} from "../AWKErrors.sol";
import {AWKSwapAdapter} from "./AWKSwapAdapter.sol";
import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import {SafeERC20} from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";

error InvalidUniswapV3RouterTarget(address target, address expected);
error InvalidSwapTokenAddress(address token);
error InvalidUniswapV3FeeTier(uint24 fee);
error InvalidSwapRoute();
error InvalidRouteLength(uint256 length);
error InvalidRouteEndpoints(address expectedSellToken, address expectedBuyToken);

interface IUniswapV3SwapRouter {
struct ExactInputSingleParams {
address tokenIn;
address tokenOut;
uint24 fee;
address recipient;
uint256 amountIn;
uint256 amountOutMinimum;
uint160 sqrtPriceLimitX96;
}

struct ExactInputParams {
bytes path;
address recipient;
uint256 amountIn;
uint256 amountOutMinimum;
}

function exactInputSingle(ExactInputSingleParams calldata params) external payable returns (uint256 amountOut);
function exactInput(ExactInputParams calldata params) external payable returns (uint256 amountOut);
}

/**
* @title AWKUniswapV3SwapAdapter
* @notice Generic adapter for Uniswap V3 swaps using structured route parameters
* @dev Swap execution runs via delegatecall from AgentWallet.
* Accepts multi-hop routes through path+fee arrays with strict validation.
*/
contract AWKUniswapV3SwapAdapter is AWKSwapAdapter {
using SafeERC20 for IERC20;

uint256 internal constant MAX_HOPS = 4;

struct SwapRoute {
address[] path;
uint24[] fees;
}

address public immutable UNISWAP_V3_ROUTER;

constructor(address uniswapV3Router) {
if (uniswapV3Router == address(0)) revert AWKErrors.ZeroAddress();
UNISWAP_V3_ROUTER = uniswapV3Router;
}

// ============ Swap Operations ============

/**
* @notice Swap tokens via Uniswap V3 (public interface, should not be called directly)
* @dev This is a placeholder function signature. Actual execution happens via execute() -> _swap()
*/
function swap(address sellToken, address buyToken, SwapRoute calldata route, uint256 sellAmount, uint256 minBuyAmount) external pure returns (uint256) {
revert AWKErrors.DirectCallForbidden();
}

// ============ Swap Execution (delegatecall only) ============

/**
* @notice Route delegatecall operations to the swap handler
* @param target The Uniswap V3 router target contract
* @param data ABI-encoded call data (must match swap() selector)
* @return ABI-encoded buy amount
*/
function execute(address target, bytes calldata data) external payable virtual override onlyDelegateCall returns (bytes memory) {
bytes4 selector = bytes4(data[:4]);
if (selector == this.swap.selector) {
(address sellToken, address buyToken, SwapRoute memory route, uint256 sellAmount, uint256 minBuyAmount) = abi.decode(data[4:], (address, address, SwapRoute, uint256, uint256));
(uint256 buyAmount,) = _swap(target, sellToken, buyToken, route, sellAmount, minBuyAmount);
return abi.encode(buyAmount);
}
revert UnknownOperation();
}

// ============ Internal Implementations ============

function _swap(address target, address sellToken, address buyToken, SwapRoute memory route, uint256 sellAmount, uint256 minBuyAmount) internal virtual returns (uint256 buyAmount, uint256 soldAmount) {
if (target != UNISWAP_V3_ROUTER) revert InvalidUniswapV3RouterTarget(target, UNISWAP_V3_ROUTER);
_validateRoute(sellToken, buyToken, route);
SwapBalanceSnapshot memory balanceSnapshot = _beforeSwapInternal(sellToken, buyToken, sellAmount, minBuyAmount);
IERC20(sellToken).forceApprove(UNISWAP_V3_ROUTER, sellAmount);
bytes memory encodedPath = abi.encodePacked(route.path[0]);
for (uint256 i = 0; i < route.fees.length; i++) {
encodedPath = bytes.concat(encodedPath, abi.encodePacked(route.fees[i]), abi.encodePacked(route.path[i + 1]));
}
IUniswapV3SwapRouter(UNISWAP_V3_ROUTER).exactInput(IUniswapV3SwapRouter.ExactInputParams({path: encodedPath, recipient: address(this), amountIn: sellAmount, amountOutMinimum: minBuyAmount}));
(buyAmount, soldAmount) = _afterSwapInternal(UNISWAP_V3_ROUTER, sellToken, buyToken, minBuyAmount, balanceSnapshot);
}

function _validateFeeTier(uint24 fee) internal pure {
if (fee != 100 && fee != 500 && fee != 3000 && fee != 10000) revert InvalidUniswapV3FeeTier(fee);
}

function _validateRoute(address sellToken, address buyToken, SwapRoute memory route) internal pure {
uint256 pathLength = route.path.length;
if (pathLength < 2 || pathLength > MAX_HOPS + 1) revert InvalidRouteLength(pathLength);
if (route.fees.length != pathLength - 1) revert InvalidSwapRoute();
if (route.path[0] != sellToken || route.path[pathLength - 1] != buyToken) {
revert InvalidRouteEndpoints(sellToken, buyToken);
}
for (uint256 i = 0; i < pathLength; i++) {
if (route.path[i] == address(0)) revert InvalidSwapTokenAddress(route.path[i]);
}
for (uint256 i = 0; i < route.fees.length; i++) {
_validateFeeTier(route.fees[i]);
}
}
}
Copy link

Copilot AI Feb 16, 2026

Choose a reason for hiding this comment

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

This new adapter lacks test coverage. The repository has comprehensive unit tests for other adapters (e.g., test/unit/adapters/ZeroXAdapter.t.sol). This adapter should have similar test coverage including: successful swaps, validation failures (route validation, token validation, fee tier validation), insufficient output handling, and integration with YieldSeeker's fee tracking and sell policy.

Copilot uses AI. Check for mistakes.
Comment thread src/adapters/SwapSellPolicy.sol
Comment thread src/adapters/UniswapV3SwapAdapter.sol
Comment on lines +1 to +43
// SPDX-License-Identifier: MIT
//
// /$$ /$$ /$$ /$$ /$$ /$$$$$$ /$$
// | $$ /$$/|__/ | $$ | $$ /$$__ $$ | $$
// \ $$ /$$/ /$$ /$$$$$$ | $$ /$$$$$$$| $$ \__/ /$$$$$$ /$$$$$$ | $$ /$$ /$$$$$$ /$$$$$$
// \ $$$$/ | $$ /$$__ $$| $$ /$$__ $$| $$$$$$ /$$__ $$ /$$__ $$| $$ /$$/ /$$__ $$ /$$__ $$
// \ $$/ | $$| $$$$$$$$| $$| $$ | $$ \____ $$| $$$$$$$$| $$$$$$$$| $$$$$$/ | $$$$$$$$| $$ \__/
// | $$ | $$| $$_____/| $$| $$ | $$ /$$ \ $$| $$_____/| $$_____/| $$_ $$ | $$_____/| $$
// | $$ | $$| $$$$$$$| $$| $$$$$$$| $$$$$$/| $$$$$$$| $$$$$$$| $$ \ $$| $$$$$$$| $$
// |__/ |__/ \_______/|__/ \_______/ \______/ \_______/ \_______/|__/ \__/ \_______/|__/
//
// Grow your wealth on auto-pilot with DeFi agents
// https://yieldseeker.xyz
//
// For technical queries or guidance contact @krishan711
//
pragma solidity 0.8.28;

import {AWKErrors} from "../agentwalletkit/AWKErrors.sol";
import {AWKAerodromeCLSwapAdapter} from "../agentwalletkit/adapters/AWKAerodromeCLSwapAdapter.sol";
import {YieldSeekerAdapter} from "./Adapter.sol";
import {IYieldSeekerSwapSellPolicy} from "./SwapSellPolicy.sol";

error SellTokenNotAllowed(address token);

contract YieldSeekerAerodromeCLSwapAdapter is AWKAerodromeCLSwapAdapter, YieldSeekerAdapter {
address public immutable SELL_POLICY;

constructor(address aerodromeClRouter, address sellPolicy) AWKAerodromeCLSwapAdapter(aerodromeClRouter) {
if (sellPolicy == address(0)) revert AWKErrors.ZeroAddress();
SELL_POLICY = sellPolicy;
}

function _beforeSwap(address sellToken, address buyToken) internal view override {
if (!IYieldSeekerSwapSellPolicy(SELL_POLICY).isSellableToken(sellToken)) revert SellTokenNotAllowed(sellToken);
_requireNotBaseAsset(sellToken);
_requireBaseAsset(buyToken);
}

function _afterSwap(address sellToken, uint256 soldAmount, uint256 buyAmount) internal override {
_feeTracker().recordAgentTokenSwap(sellToken, soldAmount, buyAmount);
}
}
Copy link

Copilot AI Feb 16, 2026

Choose a reason for hiding this comment

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

This new wrapper adapter lacks test coverage. The repository has comprehensive unit tests for other adapters. This adapter should have test coverage verifying that it correctly integrates with the sell policy, validates base asset requirements via _beforeSwap, and records fee tracking via _afterSwap.

Copilot uses AI. Check for mistakes.
Comment thread src/agentwalletkit/adapters/AWKAerodromeCLSwapAdapter.sol Outdated
Comment thread src/agentwalletkit/adapters/AWKAerodromeV2SwapAdapter.sol
Comment thread src/agentwalletkit/adapters/AWKAerodromeCLSwapAdapter.sol
import {SafeERC20} from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";

error InvalidAerodromeV2RouterTarget(address target, address expected);
error InvalidSwapTokenAddress(address token);
Copy link

Copilot AI Feb 16, 2026

Choose a reason for hiding this comment

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

The error InvalidSwapTokenAddress is defined both in this file (line 24) and in the parent AWKSwapAdapter (line 21). This creates duplicate error definitions which can lead to confusion and inconsistency. Consider removing this duplicate definition and using the one from the parent contract.

Suggested change
error InvalidSwapTokenAddress(address token);

Copilot uses AI. Check for mistakes.
import {SafeERC20} from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";

error InvalidAerodromeCLRouterTarget(address target, address expected);
error InvalidSwapTokenAddress(address token);
Copy link

Copilot AI Feb 16, 2026

Choose a reason for hiding this comment

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

The error InvalidSwapTokenAddress is defined both in this file (line 24) and in the parent AWKSwapAdapter (line 21). This creates duplicate error definitions which can lead to confusion and inconsistency. Consider removing this duplicate definition and using the one from the parent contract.

Suggested change
error InvalidSwapTokenAddress(address token);

Copilot uses AI. Check for mistakes.
@krishan711 krishan711 changed the title Feature: replace 0x with aerodrome and uniswap adapters Fix: replace 0x with aerodrome and uniswap adapters Feb 16, 2026
@krishan711 krishan711 changed the title Fix: replace 0x with aerodrome and uniswap adapters Fix: replace 0x with aerodrome and uniswap adapters so we no longer have arbitrary calldata Feb 16, 2026
@krishan711 krishan711 changed the base branch from fixzerox to fixaavecompoundadapter February 16, 2026 22:31
Base automatically changed from fixaavecompoundadapter to main February 25, 2026 16:03
Worktree directories should not be tracked as submodules.
Added .worktrees/ to .gitignore to prevent future issues.
@krishan711 krishan711 merged commit ec017e3 into main Feb 25, 2026
1 check passed
@krishan711 krishan711 deleted the fixzerox2 branch February 25, 2026 17:13
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants