The Problem Nobody Talks About in DeFi
Dollar-Cost Averaging is the most recommended investment strategy in traditional finance. Yet in crypto, most "DCA bots" are just centralized services holding your funds and running cron jobs. You hand over custody. You trust a server. You hope nobody rugs.
We wanted to build something different: a fully non-custodial DCA protocol where the smart contract itself executes swaps on schedule, with zero custody risk.
This is how we built TON DCA — and the architectural decisions that made it possible.
Why TON?
Before the "why not Solana/Ethereum" crowd arrives — here's why TON was the right chain for this:
- Asynchronous message-passing model — TON contracts communicate via internal messages, not synchronous calls. This is perfect for scheduled operations.
- Cheap storage — DCA requires persistent state per user. On Ethereum, that's prohibitively expensive. On TON, it's pennies.
- Native Telegram integration — Our target users live in Telegram. TonConnect + Telegram Mini Apps = seamless UX.
The Architecture
┌─────────────────┐ ┌──────────────┐ ┌─────────────┐
│ Telegram Mini │────▶│ NestJS │────▶│ TON Smart │
│ App (Frontend) │ │ Indexer │ │ Contracts │
└─────────────────┘ └──────────────┘ └─────────────┘
│
┌─────▼─────┐
│ STON.fi │
│ v2.1 DEX │
└───────────┘
Three layers, each with a distinct responsibility:
Layer 1: Smart Contracts (Tact)
We chose Tact over FunC for the contracts. Tact gives you type safety and a more readable syntax while compiling down to the same TVM bytecode.
The core challenge was tracking proportional distributions across all active DCA positions without iterating through every user on each swap. The naive approach — loop through all positions, calculate each user's share — would hit gas limits fast.
The MasterChef Index Model
We borrowed a pattern from SushiSwap's MasterChef contract and adapted it for TON's message-passing model:
struct GlobalState {
accRewardPerShare: Int; // Accumulated tokens per unit deposited
totalDeposited: Int; // Sum of all active DCA deposits
lastSwapTime: Int; // Timestamp of last executed swap
}
struct UserPosition {
deposited: Int; // User's TON committed to DCA
rewardDebt: Int; // Snapshot of accRewardPerShare at entry
interval: Int; // Swap frequency in seconds
}
When a swap executes:
fun executeSwap(swapResult: Int) {
let tokensReceived = swapResult;
self.accRewardPerShare += tokensReceived * PRECISION / self.totalDeposited;
}
fun pendingReward(user: UserPosition): Int {
return user.deposited * self.accRewardPerShare / PRECISION - user.rewardDebt;
}
This gives us O(1) complexity for both deposits and claims. Whether we have 10 or 10,000 active positions, gas cost stays constant.
Layer 2: The Proxy-TON Mechanism
Here's a wrinkle specific to TON: you can't directly swap native TON on most DEXes — they work with jettons (TON's token standard). We built a proxy-TON wrapper that:
- Accepts native TON from the DCA contract
- Wraps it into a jetton
- Routes to STON.fi v2.1 for the swap
- Unwraps the result back to the user's position
// Simplified indexer logic for swap routing
async function routeSwap(position: DCAPosition) {
const wrapMsg = buildWrapMessage(position.amount);
const swapMsg = buildStonfiSwapMessage({
tokenIn: PROXY_TON_ADDRESS,
tokenOut: position.targetToken,
amount: position.amount,
minOut: calculateMinOut(position.amount, slippage),
});
await tonClient.sendMessages([wrapMsg, swapMsg]);
}
Layer 3: NestJS Indexer
The indexer watches on-chain events and triggers swaps when intervals expire. It's the "cron job" — but it can only trigger, never custody:
@Injectable()
export class DCAScheduler {
@Cron('*/30 * * * * *') // Check every 30 seconds
async checkPendingSwaps() {
const positions = await this.contractService.getActivePositions();
const now = Math.floor(Date.now() / 1000);
for (const pos of positions) {
if (now - pos.lastSwapTime >= pos.interval) {
await this.contractService.triggerSwap(pos.id);
// The contract validates timing on-chain
// Even if indexer is compromised, invalid swaps revert
}
}
}
}
Key security property: if the indexer goes down, no funds are at risk. Users can withdraw anytime. If the indexer sends a premature trigger, the contract rejects it on-chain.
The Telegram Mini App UX
We built the frontend as a Telegram Mini App using TonConnect for wallet integration:
// TMA initialization with TonConnect
import { TonConnectUI } from '@tonconnect/ui';
const tonConnect = new TonConnectUI({
manifestUrl: 'https://dca.ton/tonconnect-manifest.json',
});
async function createDCAPosition(params: {
amount: bigint;
interval: number;
targetToken: string;
}) {
const tx = {
validUntil: Math.floor(Date.now() / 1000) + 600,
messages: [{
address: DCA_CONTRACT_ADDRESS,
amount: params.amount.toString(),
payload: buildCreatePositionPayload(params),
}],
};
await tonConnect.sendTransaction(tx);
}
Users never leave Telegram. They connect a wallet, set parameters, and the contract handles everything.
Lessons Learned
1. TON's async model requires different thinking
On EVM chains, you call a function and get a result in the same transaction. On TON, you send a message and handle the response in a separate transaction. Error handling becomes "send a bounce message back" rather than "revert."
2. The index model saves everything
Without the MasterChef-style accumulator, we'd need O(n) gas per swap. On a chain optimized for high throughput, this still matters — especially when you want to support thousands of concurrent positions.
3. Telegram Mini Apps are underrated
The conversion funnel from "sees the bot" to "first DCA position created" is remarkably short. No app store, no browser extension, no seed phrase management (TonConnect handles it). We saw 3x higher activation rates compared to a traditional web dApp.
What's Next
We're working on:
- Multi-token DCA — split one deposit across multiple target tokens
- Limit-DCA hybrid — only execute swaps when price is below a moving average
- Analytics dashboard — historical performance tracking within the Mini App
Wrapping Up
Building non-custodial DeFi on TON forced us to rethink patterns we'd internalized from EVM development. The async message model, the storage cost structure, and the Telegram-native distribution channel all create a different design space — one that's surprisingly well-suited for automated strategies like DCA.
If you're building on TON or exploring DeFi protocol architecture, I'd love to hear about your approach. Drop a comment or reach out.
We're Gerus Lab — a dev studio specializing in blockchain protocols, AI integrations, and Telegram-native products. Check out our other work including on-chain escrow systems, NFT staking protocols, and AI-powered SaaS products.
Top comments (0)