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.
| Fact | Value |
|---|---|
| Address (Monad mainnet) | 0x92D3d4Af4B3b0F29Ec020e407803e135af61987bView 0x92D3d4Af4B3b0F29Ec020e407803e135af61987b on the explorer (opens in a new tab) |
| Deployed | Oct 19, 2026, block 114,783,301 |
| Source | Verified on Sourcify (exact match, Oct 19, 2026); Monadscan shows the verified source |
| License | GPL-2.0-or-later (it links vendored Uniswap v3 libraries) |
| Who sends trades | The operator key, from the production deployment only (ANIMR_ENV or VERCEL_ENV = production) |
Roles
| Role | Can | Cannot |
|---|---|---|
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 limits | Withdraw, 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 steps | Move funds, trade, change anyone's limits, or become the operator |
| NFT holder | Grant or revoke the guard, configure limits, disable, turn buy-only on or off | Act 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:
Create the wallet
If needed, create and initialize the Chog's account (see Your Chog's wallet).
wallet.setPermissions([guard], [true])Sent by the holder directly, not through a forwarder.
guard.configure(wallet, limits)Only accepts canonical Tokenbound accounts of Chog Genesis tokens: registry v0.3.1, AccountProxy, salt 0, this chain.
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.
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
| Limit | What it bounds | Ceiling | App default |
|---|---|---|---|
| maxTradeBps | amountIn per trade, as a share of the balance being sold | 50% | 10% |
| maxDailyBps | Value traded per 24 hours, buys and sells together, as a share of the vault's value at the 30-minute TWAP | 100% | 30% |
| minInterval | Seconds between two trades | None | 10 minutes |
| maxSlippageBps | Output shortfall vs the TWAP quote, and how far spot may sit on the wrong side of it | 2% | 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
| Check | Rule |
|---|---|
| Caller | Only the operator |
| Pause | The admin's global kill switch |
| Enabled | The current holder configured this wallet. State is keyed by (wallet, holder), so this fails after a sale. |
| Buy-only | If the holder turned it on, sells revert with "buy-only vault" |
| Size and budget | Within maxTradeBps and the daily bucket |
| Pace | minInterval since the last trade |
| TWAP | observe([1800, 60, 0]) on the pool. If it can't serve 30 minutes, the trade fails closed ("TWAP unavailable"). |
| Spot check | Spot may not sit more than maxSlippageBps ticks on the vault's unfavourable side of the reference price |
| Floor | The 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. |
| Deadline | block.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 withwallet.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.
cd contracts
forge test --fork-url https://rpc.monad.xyz -vv