Settings Overview
| Setting | Type | Default | Purpose |
|---|---|---|---|
strategy_name | text | "ETH DCA" | User-facing label for the strategy |
auto_compound | boolean | true | Whether to reinvest gains |
risk_level | single_select | "medium" | Options: low, medium, high |
max_orders_per_day | integer | 3 | Cap on daily buy orders |
buy_amount_usd | number | 50.0 | USD amount per DCA buy |
slippage_tolerance | percentage | 0.5 | Max slippage per swap |
treasury | address | "0x1234..." | Wallet for fee collection |
circuit.toml
circuit.toml
name = "DCA Strategy Agent"
tagline = "Dollar-cost average into tokens"
category = "quant"
imageUrl = "https://api.circuit.org/assets/agents/default"
walletVmFamily = "evm"
allowedExecutionModes = ["auto", "manual"]
[[triggers]]
type = "schedule"
every = 60
align = "rolling"
[startingAsset]
network = "ethereum:1"
address = "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48" # USDC
minimumAmount = "100000000" # 100 USDC (6 decimals)
[settings.strategy_name]
description = "User-facing label for the strategy"
type = "text"
default = "ETH DCA"
required = false
[settings.auto_compound]
description = "Whether to reinvest gains"
type = "boolean"
default = true
required = false
[settings.risk_level]
description = "Risk tolerance level"
type = "single_select"
default = "medium"
required = false
options = ["low", "medium", "high"]
[settings.max_orders_per_day]
description = "Cap on daily buy orders"
type = "integer"
default = 3
required = false
[settings.buy_amount_usd]
description = "USD amount per DCA buy"
type = "number"
default = 50.0
required = false
[settings.slippage_tolerance]
description = "Max slippage per swap"
type = "percentage"
default = 0.5
required = false
[settings.treasury]
description = "Wallet for fee collection"
type = "address"
default = "0x1234567890abcdef1234567890abcdef12345678"
required = false
Example
from circuit_sdk import AgentContext, decimal_to_base_units
def run(agent: AgentContext) -> None:
# Read all settings - resolved as defaults merged with session overrides
strategy_name = agent.settings.get("strategy_name") # str
auto_compound = agent.settings.get("auto_compound") # bool
risk_level = agent.settings.get("risk_level") # str
max_orders = agent.settings.get("max_orders_per_day") # int
buy_amount = agent.settings.get("buy_amount_usd") # float
slippage = agent.settings.get("slippage_tolerance") # float (0-100)
treasury = agent.settings.get("treasury") # str (address)
agent.log(f"[{strategy_name}] Starting DCA run")
agent.log(f" Risk: {risk_level}, Amount: ${buy_amount}, Slippage: {slippage}%")
agent.log(f" Max orders: {max_orders}, Auto-compound: {auto_compound}")
agent.log(f" Treasury: {treasury}")
# Check how many orders we've placed today
orders_today_mem = agent.memory.get("orders_today")
orders_today = (
int(orders_today_mem.data.value)
if orders_today_mem.data and orders_today_mem.data.value is not None
else 0
)
if orders_today >= max_orders:
agent.log(f"Daily order limit reached ({orders_today}/{max_orders}), skipping")
return
# Execute a swap of USDC → ETH using the configured amount and slippage.
# Size in exact raw units (never float math on an amount), capped at the
# session's allocated USDC so the order can never exceed what it may spend.
usdc = "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48" # USDC on Ethereum mainnet
allocated_usdc = next(
(b for b in agent.allocation.balances if b.token_address.lower() == usdc.lower()),
None,
)
if allocated_usdc is None or int(allocated_usdc.amount_raw) == 0:
agent.log("No allocated USDC to buy with", error=True)
return
buy_amount_raw = int(decimal_to_base_units(str(buy_amount), allocated_usdc.decimals))
amount_raw = min(buy_amount_raw, int(allocated_usdc.amount_raw))
limits = {
"from": {"network": "ethereum:1", "address": agent.session_wallet_address},
"to": {"network": "ethereum:1", "address": agent.session_wallet_address},
"amount": str(amount_raw),
"fromToken": usdc,
# toToken omitted → buy native ETH
"slippage": str(slippage), # percentage as a string, e.g. "0.5"
}
quote = agent.swap.quote(limits)
if quote.success and quote.data:
result = agent.swap.execute({**limits, "expiresAt": None})
if result.success:
agent.memory.set("orders_today", str(orders_today + 1))
agent.log(f"DCA buy executed (order {orders_today + 1}/{max_orders})")
else:
agent.log(result.error or "Swap failed", error=True)
else:
agent.log(quote.error or "No swap quote available", error=True)
def unwind(agent: AgentContext) -> None:
allocation = agent.allocation
agent.log(f"Unwinding {len(allocation.balances)} balances")
import { type AgentContext, decimalToBaseUnits } from "circuit:sdk";
export async function run(agent: AgentContext): Promise<void> {
// Read all settings - resolved as defaults merged with session overrides
const strategyName = agent.settings.strategy_name as string;
const autoCompound = agent.settings.auto_compound as boolean;
const riskLevel = agent.settings.risk_level as string;
const maxOrders = agent.settings.max_orders_per_day as number;
const buyAmount = agent.settings.buy_amount_usd as number;
const slippage = agent.settings.slippage_tolerance as number;
const treasury = agent.settings.treasury as string;
await agent.log(`[${strategyName}] Starting DCA run`);
await agent.log(` Risk: ${riskLevel}, Amount: $${buyAmount}, Slippage: ${slippage}%`);
await agent.log(` Max orders: ${maxOrders}, Auto-compound: ${autoCompound}`);
await agent.log(` Treasury: ${treasury}`);
// Check how many orders we've placed today
const ordersTodayMem = await agent.memory.get("orders_today");
const ordersToday =
ordersTodayMem.data?.value != null ? parseInt(ordersTodayMem.data.value, 10) : 0;
if (ordersToday >= maxOrders) {
await agent.log(`Daily order limit reached (${ordersToday}/${maxOrders}), skipping`);
return;
}
// Execute a swap of USDC → ETH using the configured amount and slippage.
// Size in exact raw units (never float math on an amount), capped at the
// session's allocated USDC so the order can never exceed what it may spend.
const USDC = "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48"; // USDC on Ethereum mainnet
const allocatedUsdc = agent.allocation.balances.find(
(b) => b.tokenAddress.toLowerCase() === USDC.toLowerCase(),
);
if (!allocatedUsdc || BigInt(allocatedUsdc.amountRaw) === 0n) {
await agent.log("No allocated USDC to buy with", { error: true });
return;
}
const buyAmountRaw = BigInt(decimalToBaseUnits(String(buyAmount), allocatedUsdc.decimals));
const amountRaw =
buyAmountRaw < BigInt(allocatedUsdc.amountRaw) ? buyAmountRaw : BigInt(allocatedUsdc.amountRaw);
const limits: Parameters<AgentContext["swap"]["quote"]>[0] = {
from: { network: "ethereum:1", address: agent.sessionWalletAddress },
to: { network: "ethereum:1", address: agent.sessionWalletAddress },
amount: amountRaw.toString(),
fromToken: USDC,
// toToken omitted → buy native ETH
slippage: String(slippage), // percentage as a string, e.g. "0.5"
};
const quote = await agent.swap.quote(limits);
if (quote.success && quote.data) {
const result = await agent.swap.execute({ ...limits, expiresAt: null });
if (result.success) {
await agent.memory.set("orders_today", String(ordersToday + 1));
await agent.log(`DCA buy executed (order ${ordersToday + 1}/${maxOrders})`);
} else {
await agent.log(result.error || "Swap failed", { error: true });
}
} else {
await agent.log(quote.error || "No swap quote available", { error: true });
}
}
export async function unwind(agent: AgentContext): Promise<void> {
const allocation = agent.allocation;
await agent.log(`Unwinding ${allocation.balances.length} balances`);
}
How It Works
- Read settings: All seven setting types are read from
agent.settingsas their native runtime types - strings, booleans, and numbers - Rate limiting: Uses
integersetting (max_orders_per_day) with agent memory to enforce a daily order cap - Swap execution: Uses
numbersetting (buy_amount_usd) andpercentagesetting (slippage_tolerance) to configure the swap parameters - Address tracking: The
addresssetting (treasury) is validated against the agent’swalletVmFamilyat upload time
Overriding Settings at Session Start
When users start a session, they can override any setting. For example, to run a more aggressive strategy:{
"settings": [
{ "key": "risk_level", "value": { "text": "high" } },
{ "key": "buy_amount_usd", "value": { "number": 100 } },
{ "key": "slippage_tolerance", "value": { "number": 1.0 } },
{ "key": "max_orders_per_day", "value": { "number": 5 } }
]
}
integer, number, percentage) all use { "number": N } on the wire. The setting’s type determines validation rules (whole numbers for integer, 0-100 range for percentage).
See Also
- Settings SDK Reference - Accessing settings at runtime
circuit.tomlReference - Defining settings