Narcissistic Pickle Meister


    0.2.4 • Public • Published

    CustomSwaps (Governance Safe Smart Swaps)

    name is not final, all suggestions are welcome


    These contracts were developed to allow devs to provide a safe, flexible and governable way to handle swaps on their smart contracts. This has synergy with since keep3rs can submit custom dex+data to reduce slippage (and possibly get a bonus for it)



    • [x] Uniswap (swapExactTokensForTokens)
    • [x] Sushiswap (swapExactTokensForTokens)
    • [ ] 0x
    • [ ] bancor
    • [ ] paraswap
    • [ ] crv
    • [ ] 1inch


    • [ ] yearn strategy
    • [ ] erc20-dca (TBD)

    Run tests

    • npm i
    • cp example.config.json .config.json
      • add Infura and Alchemy APIs
      • add "accounts.mainnet.publicKey" (should have ETH balance > 3 for tests to run ok)
    • npm test
      • use npm run test:gas to get gas report




    Abstract contract that should be used to extend from when creating DexHandlers. (see UniswapV2DexHandler.sol)

    address public dex;
    constructor(address _dex) public {
        require(_dex.isContract(), 'dex-handler::constructor:dex-is-not-a-contract');
        dex = _dex;
    function isDexHandler() external override view returns (bool) { return true; }
    function swap(bytes memory _data, uint256 _amount) public virtual override returns (uint256 _amountOut) {}
    function getAmountOut(bytes memory _data, uint256 _amount) public virtual override view returns (uint256 _amountOut) {}
    function swapData() external virtual override pure returns (bytes memory) {}
    function decodeData(bytes memory _data) public virtual pure;


    uniswap verified on etherscan

    sushiswap verified on etherscan

    UniswapV2 custom DexHandler.

    Overrides following DexHandlerAbstract functions:

    function swap(bytes memory _data, uint256 _amount) public override returns (uint256 _amountOut)
    function getAmountOut(bytes memory _data, uint256 _amount) public override view returns (uint256 _amountOut)
    function decodeData(bytes memory _data) public override pure
    function swapData() external override pure returns (bytes memory) {

    For decodeData it always returns a message pointing to correct custom implementation:

    require(false, 'use customDecodeData(bytes memory _data) returns (uint256 _amount, uint256 _min, address[] memory _path, address _to, uint256 _expire)');

    For swapData it always returns a message pointing to correct custom implementation:

    require(false, 'use customSwapData(uint256 _amount, uint256 _min, address[] memory _path, address _to, uint256 _expire) returns (bytes memory)');

    These 2 functions above will return a different message for other DexHandlers (more examples in the future)

    customSwap handles decoding and overwriting the data + the UniswapV2 logic for swapExactTokensForTokens



    verified on etherscan

    Governance managed mappings for dex and pairs.

    // Dex handlers mapping
    mapping(address => address) internal dexHandlers;
    // in => out defaults
    mapping(address => mapping(address => address)) internal defaultPairDex;
    mapping(address => mapping(address => bytes)) internal defaultPairData;

    This contract and it's governance need to be REALLY secure and trustworthy, since they have the ability to steal funds and/or block custom swaps by introducing an evil handler and/or data into a pair's default.

    I need to find a way to incentivize good behaviour and minimize oversight required.


    Abstract contract that has all the internal functions to safely handle and verify swaps done via GovernanceSwap + DexHandlers.

    Contracts that extend SafeSmartSwap should call SafeSmartSwap(_governanceSwap) on construction. (see MockStrategy.sol)

    There are only 2 functions that respectively handle default and custom swaps:

    // Governance swap
    function _swap(uint256 _amount, address _in, address _out) internal returns (uint _amountOut)
    // Custom swap
    function _swap(uint256 _amount, address _in, address _out, address _dex, bytes memory _data) internal returns (uint _amountOut)

    Governance (default) swaps spend less gas since they don't require extra checks (see gas spendage with npm run test:gas)

    Custom swaps reverts if the output is less than the estimated output by the current Governance (default) swap for the pair.

    require(_amountOut >= _governanceAmountOut, 'custom-swap-is-suboptimal');

    Also checks final token balances in case the dex+data used was maliciously returning a higher _amountOut:

    require(inBalancePostSwap >= inBalancePreSwap.sub(_amount), 'in-balance-mismatch');
    require(outBalancePostSwap >= outBalancePreSwap.add(_governanceAmountOut), 'out-balance-mismatch');



    Mocked Strategy based on yearn's StrategyCurveYVoterProxy.sol

    Extends SafeSmartSwapAbstract.sol, constructor needs address _governanceSwap.

    3 key/test functions are:

    // Swaps CRV to DAI by using a custom dex (available on GovernanceSwap) + custom data (can be created by using dex default handler)
    function customHarvest(address _dex, bytes memory _data) public returns (uint256 _amountOut)
    // New harvest function that uses GovernanceSwap default dex and data
    function harvest() public override
    // Legacy harvest function that uses uniswap and hardcoded path
    function oldHarvest() public


    npm i @lbertenasco/custom-swaps

    DownloadsWeekly Downloads






    Unpacked Size

    34.3 kB

    Total Files


    Last publish


    • lucianob