Loading...
Loading...
© 2026 Hydrex. All rights reserved.
Automate recurring token swaps on a fixed schedule. The HydrexDCA contract is custodial — it holds the full order amount upfront and executes each swap as the interval elapses.
| Endpoint | Parameters | Description |
|---|---|---|
GET /dca/orders | None | Returns all DCA orders from the contract. |
GET /dca/orders/user/{address} | address path | Returns all DCA orders for a specific wallet address. |
Each order reflects the on-chain Order struct. Amounts are in the token's base units (wei).
[
{
"orderId": 0,
"user": "0x742d35Cc6634C0532925a3b844Bc454e4438f44e",
"tokenIn": "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913",
"tokenOut": "0x4200000000000000000000000000000000000006",
"totalAmount": "100000000",
"remainingAmount": "70000000",
"amountPerSwap": "10000000",
"numberOfSwaps": 10,
"swapsExecuted": 3,
"interval": 86400,
"lastExecutionTime": 1712000000,
"createdAt": 1711740000,
"status": "Active",
"routerConfig": {
"minUsdPrice": "0",
"maxUsdPrice": "0",
"slippageTolerance": 50
}
}
]| Value | Meaning |
|---|---|
Active | Order is live and will be executed each interval |
Completed | All swaps have been executed |
Cancelled | User cancelled; remaining funds were refunded |
/// Parameters passed to createOrder()
struct OrderCreateParams {
address tokenIn; // input token (use 0xEeee...EeEE for native ETH)
address tokenOut; // output token
uint256 totalAmount; // total amount to DCA (deposited upfront)
uint256 numberOfSwaps; // number of executions — minimum 2, maximum 100
uint256 interval; // seconds between each swap — minimum 60s, maximum 365 days
uint256 minUsdPrice; // optional: skip swap if price < this (0 = no floor)
uint256 maxUsdPrice; // optional: skip swap if price > this (0 = no ceiling)
uint256 slippageTolerance; // in basis points, max 10000
}
/// On-chain order state
struct Order {
address user;
address tokenIn;
address tokenOut;
uint256 totalAmount;
uint256 remainingAmount;
uint256 amountPerSwap; // totalAmount / numberOfSwaps
uint96 numberOfSwaps;
uint96 swapsExecuted;
uint32 interval;
uint32 lastExecutionTime;
uint32 createdAt;
OrderStatus status; // Active | Completed | Cancelled
RouterConfig routerConfig;
}| Function | Description |
|---|---|
createOrder(OrderCreateParams) | Creates a new DCA order. Transfers totalAmount of tokenIn from the caller upfront. Returns the orderId. |
cancelOrder(uint256) | Cancels an active order and immediately refunds remainingAmount to the caller. Only the order owner can cancel. |
getOrder(uint256) | Returns the full Order struct for a given order ID. |
getUserOrders(address) | Returns all order IDs for a given wallet. |
getUserOrdersPaginated(address, offset, limit) | Returns full Order structs for a user with pagination. Max limit is 100. |
event OrderCreated(
uint256 indexed orderId,
address indexed user,
address tokenIn,
address tokenOut,
uint256 totalAmount,
uint256 numberOfSwaps,
uint256 amountPerSwap,
uint256 interval
);
event DCASwapExecuted(
uint256 indexed orderId,
address indexed user,
address tokenIn,
address tokenOut,
uint256 amountIn,
uint256 amountOut,
uint256 feeAmount // protocol fee deducted (default 0.5%)
);
event DCASwapFailed(uint256 indexed orderId, address indexed user, string reason);
event OrderCancelled(uint256 indexed orderId, address indexed user, uint256 refundAmount);
event OrderCompleted(uint256 indexed orderId, address indexed user);For ERC-20 tokens, approve the DCA contract to spend totalAmount before calling createOrder. For native ETH, pass the amount as value and set tokenIn to 0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE.
The full totalAmount is transferred from the caller at order creation time and held by the contract until each swap executes. Ensure you approve the contract for the full amount before calling createOrder. Fee-on-transfer tokens are not supported.
import { createPublicClient, createWalletClient, http, parseUnits } from "viem";
import { privateKeyToAccount } from "viem/accounts";
import { base } from "viem/chains";
const DCA_CONTRACT = "0x612697A124aB01b3AcF44c6e0135539416BF3fAB";
const INPUT_TOKEN = "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913"; // USDC
const OUTPUT_TOKEN = "0x4200000000000000000000000000000000000006"; // WETH
const account = privateKeyToAccount(process.env.PRIVATE_KEY);
const publicClient = createPublicClient({ chain: base, transport: http() });
const walletClient = createWalletClient({ account, chain: base, transport: http() });
const ERC20_ABI = [
{ name: "approve", type: "function", stateMutability: "nonpayable",
inputs: [{ name: "spender", type: "address" }, { name: "amount", type: "uint256" }],
outputs: [{ type: "bool" }] },
];
const DCA_ABI = [
{
name: "createOrder",
type: "function",
stateMutability: "payable",
inputs: [{
name: "params",
type: "tuple",
components: [
{ name: "tokenIn", type: "address" },
{ name: "tokenOut", type: "address" },
{ name: "totalAmount", type: "uint256" },
{ name: "numberOfSwaps", type: "uint256" },
{ name: "interval", type: "uint256" },
{ name: "minUsdPrice", type: "uint256" },
{ name: "maxUsdPrice", type: "uint256" },
{ name: "slippageTolerance", type: "uint256" },
],
}],
outputs: [{ name: "orderId", type: "uint256" }],
},
];
// Buy WETH with 100 USDC total, split into 10 daily swaps (10 USDC each)
const totalAmount = parseUnits("100", 6); // 100 USDC
// 1. Approve the DCA contract to pull totalAmount
const { request: approveReq } = await publicClient.simulateContract({
address: INPUT_TOKEN,
abi: ERC20_ABI,
functionName: "approve",
args: [DCA_CONTRACT, totalAmount],
account,
});
await publicClient.waitForTransactionReceipt({
hash: await walletClient.writeContract(approveReq),
});
// 2. Create the DCA order
const { request: orderReq } = await publicClient.simulateContract({
address: DCA_CONTRACT,
abi: DCA_ABI,
functionName: "createOrder",
args: [{
tokenIn: INPUT_TOKEN,
tokenOut: OUTPUT_TOKEN,
totalAmount: totalAmount,
numberOfSwaps: 10n, // 10 swaps
interval: 86400n, // 1 day in seconds
minUsdPrice: 0n, // no price floor
maxUsdPrice: 0n, // no price ceiling
slippageTolerance: 50n, // 0.5% slippage
}],
account,
});
const hash = await walletClient.writeContract(orderReq);
const receipt = await publicClient.waitForTransactionReceipt({ hash });
console.log("DCA order created:", receipt.transactionHash);const CANCEL_ABI = [
{ name: "cancelOrder", type: "function", stateMutability: "nonpayable",
inputs: [{ name: "orderId", type: "uint256" }], outputs: [] },
];
const { request } = await publicClient.simulateContract({
address: DCA_CONTRACT,
abi: CANCEL_ABI,
functionName: "cancelOrder",
args: [0n], // orderId — remaining funds are refunded immediately
account,
});
const hash = await walletClient.writeContract(request);
await publicClient.waitForTransactionReceipt({ hash });const READ_ABI = [
{ name: "getUserOrders", type: "function", stateMutability: "view",
inputs: [{ name: "user", type: "address" }],
outputs: [{ name: "orderIds", type: "uint256[]" }] },
{ name: "getOrder", type: "function", stateMutability: "view",
inputs: [{ name: "orderId", type: "uint256" }],
outputs: [{
name: "",
type: "tuple",
components: [
{ name: "user", type: "address" },
{ name: "tokenIn", type: "address" },
{ name: "tokenOut", type: "address" },
{ name: "totalAmount", type: "uint256" },
{ name: "remainingAmount", type: "uint256" },
{ name: "amountPerSwap", type: "uint256" },
{ name: "numberOfSwaps", type: "uint96" },
{ name: "swapsExecuted", type: "uint96" },
{ name: "interval", type: "uint32" },
{ name: "lastExecutionTime", type: "uint32" },
{ name: "createdAt", type: "uint32" },
{ name: "status", type: "uint8" }, // 0=Active, 1=Completed, 2=Cancelled
{ name: "routerConfig", type: "tuple",
components: [
{ name: "minUsdPrice", type: "uint256" },
{ name: "maxUsdPrice", type: "uint256" },
{ name: "slippageTolerance", type: "uint256" },
]},
],
}] },
];
const orderIds = await publicClient.readContract({
address: DCA_CONTRACT,
abi: READ_ABI,
functionName: "getUserOrders",
args: ["0xYourWalletAddress"],
});
const order = await publicClient.readContract({
address: DCA_CONTRACT,
abi: READ_ABI,
functionName: "getOrder",
args: [orderIds[0]],
});
const STATUS = ["Active", "Completed", "Cancelled"];
console.log("Status:", STATUS[order.status]);
console.log("Remaining:", order.remainingAmount.toString());
console.log("Swaps done:", order.swapsExecuted, "/", order.numberOfSwaps);Swap execution is automated by Hydrex operators — you do not need to trigger individual swaps. A 0.5% protocol fee is deducted from each output amount at execution time.