DCA.fun Pig LogoDCA.fun
Filler guide

Filling Methods

Different methods for executing DCA orders

DCA.fun supports two methods for executing orders, each optimized for different use cases and capital requirements. Both methods use Permit2 for secure token transfers.

Prerequisites: Permit2 Approval

Before filling any orders, you must approve the Permit2 contract to transfer tokens on your behalf:

const PERMIT2_ADDRESS = "0x000000000022D473030F116dDEE9F6B43aC78BA3";

// One-time approval for each output token
async function approvePermit2(tokenAddress) {
  const token = new Contract(tokenAddress, ERC20_ABI, signer);
  const allowance = await token.allowance(signer.address, PERMIT2_ADDRESS);

  if (allowance.eq(0)) {
    const tx = await token.approve(PERMIT2_ADDRESS, ethers.constants.MaxUint256);
    await tx.wait();
    console.log(`Approved Permit2 for ${tokenAddress}`);
  }
}

Method 1: Direct Fill

The simplest method for filling orders, requiring upfront capital.

Function Signature

function fillOrder(
    bytes calldata encodedData,
    address recipient
) external nonReentrant whenOrderFillNotPaused

Parameters

  • encodedData: ABI-encoded order data from the quote API
  • recipient: Address to receive the input tokens (usually the filler's address)

Implementation Flow

  1. Quote order using the API to get encodedData
  2. Ensure token balance for the output amount
  3. Approve Permit2 (one-time per token)
  4. Call fillOrder with the quote data
  5. Receive input tokens directly to recipient address

Example Implementation

async function directFill(orderId, chainId) {
  try {
    // 1. Get quote from API
    const response = await fetch(
      `https://api.dca.fun/quote?orderId=${orderId}&chainId=${chainId}`
    );
    const quote = await response.json();

    // 2. Calculate profitability
    const profit = calculateProfit(quote);
    if (profit.profitUSD < minProfitThreshold) {
      console.log("Order not profitable enough");
      return;
    }

    // 3. Check output token balance
    const outputToken = new Contract(outputTokenAddress, ERC20_ABI, signer);
    const balance = await outputToken.balanceOf(signer.address);
    const requiredAmount = quote.orderValidation.amountOfTokenOut;

    if (balance.lt(requiredAmount)) {
      throw new Error("Insufficient output token balance");
    }

    // 4. Ensure Permit2 approval
    await approvePermit2(outputTokenAddress);

    // 5. Execute fill order
    const tx = await dcaDotFun.fillOrder(
      quote.encodedData,
      signer.address // recipient of input tokens
    );

    const receipt = await tx.wait();
    console.log(`Order ${orderId} filled! TX: ${receipt.transactionHash}`);

    // Input tokens are now in your wallet
    // The protocol transfers output tokens via Permit2

  } catch (error) {
    console.error(`Failed to fill order ${orderId}:`, error);
  }
}

Transaction Flow

  1. Protocol transfers output tokens from filler to order vault (via Permit2)
  2. Protocol transfers input tokens from vault to recipient
  3. Protocol takes fee from output tokens to fee collector
  4. Order state is updated (repeats decreased, lastRun updated)

Pros and Cons

Pros:

  • Simple implementation
  • Lower gas costs (no callback overhead)
  • Direct control over execution
  • Predictable behavior

Cons:

  • Requires upfront capital
  • Capital locked until arbitrage execution
  • Limited to available balance

Method 2: Callback Fill

Advanced execution method allowing for atomic arbitrage and flash loans.

Function Signature

function fillOrder(
    bytes calldata encodedData,
    address callback,
    address recipient,
    bytes calldata data
) external nonReentrant whenOrderFillNotPaused

Parameters

  • encodedData: ABI-encoded order data from the quote API
  • callback: Address of your callback contract
  • recipient: Address to receive the input tokens
  • data: Custom data passed to your callback

Callback Interface

Your callback contract must implement the IFillOrderCallback interface:

interface IFillOrderCallback {
    /// @notice Callback function executed during order fill
    /// @param data The custom data passed from fillOrder call
    /// @return success Must return true for fill to complete
    function fillOrderCallback(
        bytes calldata data
    ) external returns (bool);
}

Implementation Flow

  1. Quote order using the API
  2. Call fillOrder with callback parameters
  3. Protocol sends input tokens to recipient
  4. Protocol calls your callback contract
  5. Callback executes arbitrage logic
  6. Callback approves output tokens to Permit2
  7. Protocol pulls output tokens via Permit2
  8. Transaction completes atomically

Example Callback Contract

// SPDX-License-Identifier: MIT
pragma solidity 0.8.25;

import {IFillOrderCallback} from "./interfaces/IFillOrderCallback.sol";
import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import {ISwapRouter} from "./interfaces/ISwapRouter.sol";

contract FillerCallback is IFillOrderCallback {
    address constant PERMIT2 = 0x000000000022D473030F116dDEE9F6B43aC78BA3;
    address constant UNISWAP_ROUTER = 0x...; // Your DEX router
    address public immutable owner;

    modifier onlyOwner() {
        require(msg.sender == owner, "Not owner");
        _;
    }

    constructor() {
        owner = msg.sender;
    }

    function fillOrderCallback(bytes calldata data) external returns (bool) {
        // Decode the data passed from your script
        (
            address tokenIn,
            address tokenOut,
            uint256 amountIn,
            uint256 amountOutRequired,
            bytes memory swapData
        ) = abi.decode(data, (address, address, uint256, uint256, bytes));

        // At this point, we have received tokenIn tokens

        // 1. Approve router to spend our input tokens
        IERC20(tokenIn).approve(UNISWAP_ROUTER, amountIn);

        // 2. Execute swap on DEX
        uint256 amountReceived = ISwapRouter(UNISWAP_ROUTER).exactInputSingle(
            ISwapRouter.ExactInputSingleParams({
                tokenIn: tokenIn,
                tokenOut: tokenOut,
                fee: 3000, // 0.3% fee tier
                recipient: address(this),
                deadline: block.timestamp,
                amountIn: amountIn,
                amountOutMinimum: amountOutRequired,
                sqrtPriceLimitX96: 0
            })
        );

        // 3. Approve Permit2 to take the output tokens
        IERC20(tokenOut).approve(PERMIT2, amountOutRequired);

        // 4. Keep any profit
        uint256 profit = amountReceived - amountOutRequired;
        if (profit > 0) {
            IERC20(tokenOut).transfer(owner, profit);
        }

        return true;
    }

    // Emergency withdrawal function
    function withdraw(address token) external onlyOwner {
        uint256 balance = IERC20(token).balanceOf(address(this));
        if (balance > 0) {
            IERC20(token).transfer(owner, balance);
        }
    }
}

JavaScript Integration

async function callbackFill(orderId, chainId, callbackAddress) {
  try {
    // 1. Get quote
    const response = await fetch(
      `https://api.dca.fun/quote?orderId=${orderId}&chainId=${chainId}`
    );
    const quote = await response.json();

    // 2. Prepare callback data
    const callbackData = ethers.utils.defaultAbiCoder.encode(
      ["address", "address", "uint256", "uint256", "bytes"],
      [
        tokenInAddress,
        tokenOutAddress,
        quote.orderValidation.fillableAmount,
        quote.orderValidation.amountOfTokenOut,
        swapCalldata // Your DEX swap data
      ]
    );

    // 3. Execute fill with callback
    const tx = await dcaDotFun.fillOrder(
      quote.encodedData,
      callbackAddress,     // Your deployed callback contract
      callbackAddress,     // Recipient of input tokens
      callbackData        // Data for your callback
    );

    const receipt = await tx.wait();
    console.log(`Order ${orderId} filled with callback! TX: ${receipt.transactionHash}`);

  } catch (error) {
    console.error(`Callback fill failed:`, error);
  }
}

Flash Loan Integration

Callbacks enable flash loan arbitrage:

contract FlashLoanCallback is IFillOrderCallback, IFlashLoanReceiver {
    function fillOrderCallback(bytes calldata data) external returns (bool) {
        // Decode parameters
        (address tokenOut, uint256 amountRequired) =
            abi.decode(data, (address, uint256));

        // 1. Initiate flash loan for output tokens
        ILendingPool(AAVE_POOL).flashLoan(
            address(this),
            tokenOut,
            amountRequired,
            data
        );

        return true;
    }

    function executeOperation(
        address asset,
        uint256 amount,
        uint256 premium,
        address initiator,
        bytes calldata params
    ) external returns (bool) {
        // 2. Approve Permit2 to take flash loaned tokens
        IERC20(asset).approve(PERMIT2, amount);

        // 3. After fill completes, we have input tokens
        // 4. Swap input tokens back to repay flash loan
        // 5. Keep profit

        return true;
    }
}

Pros and Cons

Pros:

  • No upfront capital required
  • Atomic arbitrage execution
  • Flash loan compatible
  • Complex routing strategies possible
  • Maximum capital efficiency

Cons:

  • Requires deploying callback contract
  • Higher gas costs
  • More complex implementation
  • Additional smart contract risk

Choosing the Right Method

Use Direct Fill When:

  • You have sufficient capital
  • Simplicity is preferred
  • Gas efficiency is critical
  • Testing or starting out
  • Small to medium order sizes

Use Callback Fill When:

  • Capital efficiency is paramount
  • Using flash loans
  • Implementing complex arbitrage
  • Large order sizes
  • MEV protection needed

Security Considerations

For Both Methods:

  1. Always validate quote data before execution
  2. Monitor gas prices to maintain profitability
  3. Use MEV protection (flashbots, etc.)
  4. Implement proper error handling
  5. Track transaction status

For Callbacks Specifically:

  1. Audit your callback contract thoroughly
  2. Implement reentrancy guards
  3. Validate all external calls
  4. Handle reverts gracefully
  5. Include emergency withdrawal functions
  6. Restrict access to owner-only functions

Gas Optimization Tips

  1. Batch Operations: Fill multiple orders in one transaction when possible
  2. Optimize Callbacks: Minimize external calls and storage operations
  3. Pre-calculate: Do calculations off-chain when possible
  4. Use Events: Emit events for tracking instead of storing data
  5. Efficient Routing: Find optimal swap paths before execution