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.

⚠️ Warning: Risk Of Fund Loss

fillOrder with callback is designed for maximum implementation flexibility. The fillOrderCallback comes with risks that need to be well understood based on your use case.

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

The following snippet demonstrates a transient lock pattern to prevent unauthorized callback execution which could drain contract funds upto the permit2 allowance.

This is unaudited code not meant for production.

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

contract SwapRouter is IFillOrderCallback {
    /*///////////////////////////////////////////////////////////////
                            State Variables
    ///////////////////////////////////////////////////////////////*/

    // The slot holding the unlocked state, transiently. keccak256("locked")
    bytes32 internal constant LOCKED_SLOT = 0xab99c6d7581cbb37d2e578d3097bfdd3323e05447f1fd7670b6c3a3fb9d9ff79;

    ...

    /*///////////////////////////////////////////////////////////////
                            Errors
    ///////////////////////////////////////////////////////////////*/

    ...

    /*///////////////////////////////////////////////////////////////
                            Events
    ///////////////////////////////////////////////////////////////*/

    ...

    /*///////////////////////////////////////////////////////////////
                            Modifiers
    ///////////////////////////////////////////////////////////////*/

    /// @notice CRITICAL: This modifier prevents unauthorized callback execution
    modifier onlyWhenUnlocked() {
        if (!isUnlocked()) revert NotUnlocked();
        _;
    }

    /*///////////////////////////////////////////////////////////////
                            Constructor
    ///////////////////////////////////////////////////////////////*/

    ...

    /*///////////////////////////////////////////////////////////////
                            Transient Lock Functions
    ///////////////////////////////////////////////////////////////*/

    /// @notice Sets the transient lock to unlocked state
    function unlock() internal {
        assembly ("memory-safe") {
            tstore(LOCKED_SLOT, true)
        }
    }

    /// @notice Sets the transient lock to locked state
    function lock() internal {
        assembly ("memory-safe") {
            tstore(LOCKED_SLOT, false)
        }
    }

    /// @notice Checks if the contract is in unlocked state
    function isUnlocked() internal view returns (bool unlocked) {
        assembly ("memory-safe") {
            unlocked := tload(LOCKED_SLOT)
        }
    }

    /*///////////////////////////////////////////////////////////////
                              Token Management
    ///////////////////////////////////////////////////////////////*/

    ...

    /*///////////////////////////////////////////////////////////////
                            Execute Swap
    ///////////////////////////////////////////////////////////////*/

    /// @notice Executes a swap against a DcaDotFun order
    /// @dev CRITICAL: unlock() MUST be called before fillOrder, lock() after
    /// @param encodedData_ The encoded order data used to fill the order
    /// @param data_ The data passed back to address(this) fillOrderCallback
    function executeSwap(bytes calldata encodedData_, bytes calldata data_) external {
        // 1. UNLOCK before calling fillOrder
        unlock();

        // 2. Call fillOrder - this will trigger fillOrderCallback
        IDcaDotFun(dcaDotFun).fillOrder(
            encodedData_,
            address(this), // callback address
            address(this), // recipient for tokenIn
            data_
        );

        // 3. LOCK after fillOrder completes
        lock();
    }

    /// @notice PROTECTED by onlyWhenUnlocked modifier
    function fillOrderCallback(bytes calldata data_) external onlyWhenUnlocked returns (bool) {
        ...
    }

    /*///////////////////////////////////////////////////////////////
                           Withdraw
    ///////////////////////////////////////////////////////////////*/

    ...

    receive() external payable {}
}

Key Security Components

ComponentPurpose
LOCKED_SLOTTransient storage slot for the lock state
unlock()Sets transient lock before calling fillOrder
lock()Clears transient lock after fillOrder completes
onlyWhenUnlockedModifier that reverts if callback is called without lock

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);
  }
}

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

Security Considerations

For Both Methods:

  1. Always validate quote data before execution
  2. Monitor gas prices
  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
  7. Understand your use case risks

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