Skip to content
DocsAnimrTradeGuard

Own a Chog

AnimrTradeGuard

The contract that lets a Chog's AI trade its wallet within your limits, and never withdraw from it.

The AI can trade your Chog's wallet. It can never withdraw from it. AnimrTradeGuard is the only door between a Chog's AI and its wallet. The holder grants Tokenbound permission to the guard, never to the AI's key, and the guard only ever builds a swap whose output lands back in the same wallet. Its live status is at /guard.

Deployment
FactValue
Address (Monad mainnet)0x92D3d4Af4B3b0F29Ec020e407803e135af61987bView 0x92D3d4Af4B3b0F29Ec020e407803e135af61987b on the explorer (opens in a new tab)
DeployedOct 19, 2026, block 114,783,301
SourceVerified on Sourcify (exact match, Oct 19, 2026); Monadscan shows the verified source
LicenseGPL-2.0-or-later (it links vendored Uniswap v3 libraries)
Who sends tradesThe operator key, from the production deployment only (ANIMR_ENV or VERCEL_ENV = production)

Roles

Guard roles
RoleCanCannot
Operator (the AI's key)
0x2523bBdD941783C4E565180D2a1b18DE86319cC2View 0x2523bBdD941783C4E565180D2a1b18DE86319cC2 on the explorer (opens in a new tab)
Call trade() for wallets whose current holder opted in, within their limitsWithdraw, transfer, approve, pick a recipient, token, router or calldata, lower the price floor, or sell from a buy-only wallet
Admin (the team's main wallet)
0xc742AdA2872a042dD36D2E706907b4036968960CView 0xc742AdA2872a042dD36D2E706907b4036968960C on the explorer (opens in a new tab)
Pause all trading, rotate the operator, hand over admin in two stepsMove funds, trade, change anyone's limits, or become the operator
NFT holderGrant or revoke the guard, configure limits, disable, turn buy-only on or offAct on another holder's Chog; settings stop applying the moment it is sold

Holder setup

The Chog page runs this as a six-step stepper, with Wake the AI between funding and the switch; the Holder guide follows it button by button. In contract terms:

  1. Create the wallet

    If needed, create and initialize the Chog's account (see Your Chog's wallet).

  2. wallet.setPermissions([guard], [true])

    Sent by the holder directly, not through a forwarder.

  3. guard.configure(wallet, limits)

    Only accepts canonical Tokenbound accounts of Chog Genesis tokens: registry v0.3.1, AccountProxy, salt 0, this chain.

  4. Fund the wallet

    With $CHOG and/or WMON (the guard trades the two ERC-20s, not native MON). The page can also wrap MON into WMON on the way in.

  5. Switch the AI to on-chain

    Sign the mode switch on the Chog page, after waking the AI.

Optionally, at any time, guard.setBuyOnly(wallet, true): the AI can then still buy $CHOG with the wallet's WMON but never sell $CHOG. It is off by default, and off for the House Chog.

Limits

Guard limits
LimitWhat it boundsCeilingApp default
maxTradeBpsamountIn per trade, as a share of the balance being sold50%10%
maxDailyBpsValue traded per 24 hours, buys and sells together, as a share of the vault's value at the 30-minute TWAP100%30%
minIntervalSeconds between two tradesNone10 minutes
maxSlippageBpsOutput shortfall vs the TWAP quote, and how far spot may sit on the wrong side of it2%2%

The daily budget is one token bucket that frees up continuously, so there is no burst at the edge of a 24-hour window. Ceilings are constants in the contract; configure rejects anything above them.

What trade() checks

trade() checks
CheckRule
CallerOnly the operator
PauseThe admin's global kill switch
EnabledThe current holder configured this wallet. State is keyed by (wallet, holder), so this fails after a sale.
Buy-onlyIf the holder turned it on, sells revert with "buy-only vault"
Size and budgetWithin maxTradeBps and the daily bucket
PaceminInterval since the last trade
TWAPobserve([1800, 60, 0]) on the pool. If it can't serve 30 minutes, the trade fails closed ("TWAP unavailable").
Spot checkSpot may not sit more than maxSlippageBps ticks on the vault's unfavourable side of the reference price
FloorThe quote at the better of the 30- and 1-minute TWAPs, minus the pool fee and maxSlippageBps. The swap uses max(minAmountOut, floor): the operator can raise it, never lower it.
Deadlineblock.timestamp ≤ deadline, also passed to the router

Execution and settlement

The guard makes the wallet run exactly three calls. tokenIn.approve(router, amountIn), then router.exactInputSingle(…, recipient = the same wallet), then tokenIn.approve(router, 0). The venue is immutable: $CHOG, WMON, the router, the pool and its fee. At construction the guard checks that the router's factory maps to that pool.

Settlement is judged from the wallet's own balances. Exactly amountIn spent, at least the minimum received and the allowance back to zero; anything else reverts with SettlementMismatch. State is written before the external calls, behind a reentrancy lock.

Kill switches

  • Holder: guard.disable(wallet), or revoke with wallet.setPermissions([guard], [false]), at any time.
  • Admin: setPaused(true) stops every trade. The admin can never be the operator, and the deploy script refuses an operator that is also the deployer.
  • Sale: limits, the grant and buy-only mode go dormant automatically; the buyer opts in again.
  • Nested holders: a holder that is itself an ERC-6551 account is refused, because selling the NFT above it would not reset anything.

Oracle hardening and tests

GrowPoolOracle.s.sol grew the pool's observation ring to 1,801 slots on Oct 19, 2026, so bursts of swaps can't push the 30-minute TWAP out of reach.

37 Foundry tests (2 of them fuzz tests) fork live Monad mainnet state and use the real Chog Genesis, $CHOG, WMON, registry, Tokenbound and pool. They cover the happy path, every limit, sandwich and spot manipulation, the TWAP failing closed, pause, disable, NFT sale dormancy, nested ownership, buy-only mode (including a fuzz test that every sell from a buy-only wallet is refused) and a fuzz test that a trade only moves tokens between the wallet and the pool. Result: 37 fork tests, 36 pass, 1 skipped by design (0 failures), Oct 21 2026. The skipped one, the TWAP failing closed, needs an ungrown oracle and skips itself now the live pool's ring is past 256 slots.

Run them
cd contracts
forge test --fork-url https://rpc.monad.xyz -vv