from circuit_sdk import AgentContext, SessionPortfolio
NETWORK = "ethereum:8453" # Base
USDC = "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913" # 6 decimals, ~$1
# The basket this index tracks — equal weight across every entry. Swap these
# for the tokens you want exposure to (e.g. the top AI tokens); each must be a
# valid, liquid ERC-20 on NETWORK.
INDEX_TOKENS = [
{"symbol": "WETH", "address": "0x4200000000000000000000000000000000000006"},
{"symbol": "cbBTC", "address": "0xcbB7C0000aB88B473b1f5aFd9ef808440eed33Bf"},
]
# Skip drift smaller than this so we don't churn fees on tiny moves.
REBALANCE_BAND_USD = 1
# One same-chain swap, quoted then executed. Used both ways: USDC -> token to
# build the basket, token -> USDC to unwind it.
def execute_swap(agent: AgentContext, from_token: str, to_token: str, amount_raw: int, label: str) -> None:
quote = agent.swap.quote({
"from": {"network": NETWORK, "address": agent.sessionWalletAddress},
"to": {"network": NETWORK, "address": agent.sessionWalletAddress},
"amount": str(amount_raw),
"fromToken": from_token,
"toToken": to_token,
})
if not quote.success or not quote.data:
agent.log(f"{label} quote failed: {quote.error}", error=True)
return
result = agent.swap.execute(quote.data)
if result.success:
agent.log(f"{label}: ~{quote.data.asset_receive.amount_formatted} expected")
else:
agent.log(f"{label} failed: {result.error}", error=True)
def run(agent: AgentContext) -> None:
# Rebalance the basket back to equal weight: mark each leg's value, split the
# portfolio's total evenly, then trim the overweight legs and top up the
# underweight ones. The first run (all USDC, no tokens yet) is just the
# all-underweight case, so this one path both builds and rebalances the basket.
def balance(addr: str):
return next(
(b for b in agent.portfolio.balances if b.token_address.lower() == addr.lower()),
None,
)
usdc = balance(USDC)
cash_usd = int(usdc.amount_raw) / 1e6 if usdc else 0 # USDC is ~$1 — value it from raw, no oracle
legs = []
for token in INDEX_TOKENS:
held = balance(token["address"])
legs.append({
"token": token,
"raw": int(held.amount_raw) if held else 0,
# Marked value from the portfolio; bail rather than guess if a held leg is unpriced.
"value_usd": float(held.value_usd) if held and held.value_usd is not None else None,
})
if any(leg["raw"] > 0 and leg["value_usd"] is None for leg in legs):
agent.log("A basket token is unpriced — skipping rebalance", error=True)
return
total = cash_usd + sum((leg["value_usd"] or 0) for leg in legs)
if total == 0:
agent.log("No funds to allocate")
return
target = total / len(INDEX_TOKENS)
agent.log(f"Rebalancing ${total:.2f} to ${target:.2f} per token")
# Trim overweight legs first so the USDC proceeds fund the top-ups below.
for leg in legs:
value = leg["value_usd"] or 0
excess = value - target
if excess <= REBALANCE_BAND_USD or leg["raw"] == 0:
continue
# Sell the fraction of the holding whose value equals the excess.
sell_raw = leg["raw"] * round(excess * 1e6) // round(value * 1e6)
execute_swap(agent, leg["token"]["address"], USDC, sell_raw, f"Trim {leg['token']['symbol']}")
# Top up underweight legs from USDC (~$1, so a dollar gap is that many 6-decimal units).
for leg in legs:
shortfall = target - (leg["value_usd"] or 0)
if shortfall <= REBALANCE_BAND_USD:
continue
execute_swap(agent, USDC, leg["token"]["address"], round(shortfall * 1e6), f"Add {leg['token']['symbol']}")
def unwind(agent: AgentContext, portfolio: SessionPortfolio) -> None:
# Sell every basket token held back to USDC.
for token in INDEX_TOKENS:
held = next(
(b for b in portfolio.balances if b.token_address.lower() == token["address"].lower()),
None,
)
held_raw = int(held.amount_raw) if held else 0
if held_raw == 0:
continue
execute_swap(agent, token["address"], USDC, held_raw, f"Sell {token['symbol']}")