feat(cli): Agent Vault standard + agent-vaults command [experimental] - #254
feat(cli): Agent Vault standard + agent-vaults command [experimental]#254trevormil wants to merge 6 commits into
Conversation
Introduces "Agent Vault" — a Smart Token whose withdrawal approval is gated for an autonomous agent: a per-period spend cap, an optional time window, and an optional one-time multisig "unlock" vote. The human is the collection manager; the agent holds the vault tokens and withdraws within the guardrails. Ported from a reference implementation built on the Meridian devnet. Distinct from the existing simpler `Vault` standard (daily cap + 2FA + emergency-recovery) — carries `standards: ['Smart Token', 'Agent Vault']`. - core/builders/agent-vault.ts — buildAgentVault(): gating → approvalAmounts (per-period cap), transferTimes (time window), votingChallenges (multisig). - core/agent-vaults.ts — validator/detector + deposit/withdraw/pay/vote msg builders. - api-indexer/verify-standards.ts — verifyAgentVault + registry/alias entry. - cli/commands/agent-vaults.ts — list/show/status/deposit/withdraw/pay/vote. - cli/commands/build.ts — `bb build agent-vault` preset. - cli/index.ts — register command + build alias. Tests: 23 new specs (builder gating, detector, msg shapes, command surface); full suite green (3087 tests); tsc clean. REVIEW NOTE: the N-of-M → quorumThreshold(percent) mapping uses floor(threshold/totalWeight*100); confirm this is the intended convention. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
… transfers Three review fixes on the experimental Agent Vault standard: - buildAgentVaultVoteMsg emitted MsgCastVote in snake_case, but the `new MsgCastVote(v)` encoder behind `bb deploy` reads camelCase only — every field but `creator` landed undefined and `toProto()` crashed on `collectionId.toString()`. Switched to camelCase (matching the deposit/ withdraw transfer builders and the wrapper class). Verified end-to-end: the value now encodes cleanly through encodeMsgFromJson; the old shape throws. Spec updated to assert camelCase + guard the regression. - Multisig quorum: confirmed against the chain handler that quorumThreshold is a percent checked with floor()+GTE, so the floor() mapping is exact for total weight <= 100 (kept). Added a guard throwing when --threshold is outside [1, totalWeight] instead of silently clamping a typo'd N-of-M to unanimous/1%. Resolves the PR's open quorum question. - Locked noForcefulPostMintTransfers on the vault invariants (matches buildSmartToken). An Agent Vault is a wallet-like token holding an agent's funds; with frozenPermissions this guarantees no approval can forcibly move the vault tokens, and clears the `bb check` CRITICAL flag. Full unit suite green (3089); tsc clean. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Review fixes pushed (0dc48cf)Reviewed against 🔴
|
…k MsgSend - Add cli/integration/agent-vaults.spec.ts — full lifecycle against a live local chain + indexer: build/deploy/show/status/list, deposit→withdraw→pay on a capped vault, and the multisig path (pre-quorum withdraw rejected → alice+charlie cast votes → post-quorum withdraw succeeds). 12/12 green. This exercises the camelCase MsgCastVote fix on-chain. - deploy --with-keyring: `bb agent-vaults pay` emits a 2-msg envelope whose send leg is /cosmos.bank.v1beta1.MsgSend — outside `tx tokenization`. The keyring command builder only mapped tokenization verbs, so pay failed to deploy. Added a FULL_TX_BUILDERS category (owns its module segment) wiring MsgSend → `tx bank send`, in both the single- and multi-msg builders, with unit coverage. - Clarified buildAgentVaultPayMsgs docstring: atomicity holds only on the single-tx browser/burner paths; --with-keyring chains two sequential txs. Full unit suite green (3090); tsc clean; agent-vaults e2e 12/12. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
✅ E2E verified against a live local chain + indexer (5dafc06)Added Capped vault: build → deploy → Multisig vault (2-of-2): deploy + deposit → pre-quorum One more consistency fix found via e2e
Acceptance status
Full unit suite green (3090), |
…ue proposalId status: - Replace the hardcoded `status:'active'` with live on-chain state: backing TVL (best-effort /swap/balances), the per-period cap, time-window state (always-open|before-unlock|open|expired), the multisig tally (quorum %, weighted yes, quorumMet) read from /vote, and a derived `withdrawable` boolean + status string (withdrawable|locked-until-unlock|expired| locked-pending-multisig). Each live fetch degrades gracefully. proposalId uniqueness (bug fix): - The multisig withdraw proposalId was a constant, but the indexer keys VoteDocs by the bare proposalId (handleMsgCastVote `_docId = proposalId`), so two multisig vaults would collide on one vote doc. Now hashed per vault params via stableHashId (matches bounty.ts), sharing one withdrawSeed with the withdraw approvalId. Renamed the constant → AGENT_VAULT_WITHDRAW_PROPOSAL_PREFIX. NOTE: a deeper fix belongs in the indexer (key votes by collectionId+proposalId) — affects all votingChallenge standards; documented for follow-up. e2e: agent-vaults.spec.ts asserts the real status (capped vault withdrawable; multisig vault locked-pending-multisig → withdrawable after quorum). 12/12 green. Unit suite green (3091); tsc clean. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…n recovery
Adds an optional admin kill-switch, off by default. `bb build agent-vault
--recovery <addr>` bakes two approvals that let the human/recovery address
FULLY exit the vault at any time, bypassing the cap / time-window / multisig:
1. agent-vault-emergency-freeze — forceful clawback of vault tokens from any
holder (the agent) to recovery (overridesFrom/To; from excludes the
backing alias per the reserved-protocol-address rule).
2. agent-vault-emergency-exit — an ungated, recovery-only withdraw lane that
unbacks to the backing coin.
- noForcefulPostMintTransfers is now conditional: true (fully locked) when no
recovery is configured, false only when the kill-switch needs the forceful
freeze. (Reverts the earlier unconditional lock, which both blocked any
kill-switch and was redundant — frozenPermissions already downgrades the
bb check finding to info.)
- New `bb agent-vaults recover <id> --creator <recovery> --from <holder>
--amount <n>` emits {messages:[freeze, exit]} → `bb deploy`.
- extractAgentVaultDetails surfaces the recovery approvals; buildAgentVaultRecoverMsgs
builds the pair (throws if the vault has no kill-switch).
Tests: builder (approvals + invariant flip + standards-compliance), core
(extractor + recover msgs), CLI shape. e2e: a time-locked vault blocks the
agent's withdraw, then recovery claws back + drains to the coin, bypassing the
lock (charlie +5 USDC, alice tokens → 0). 15/15 e2e green; unit 3098; tsc clean.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Real
|
…transparency
Adversarial review follow-ups:
- vote: stop falling back to a constant proposalId. The on-chain proposalId is
a per-vault hash, so the bare prefix is never real — a fallback vote would
silently miss the proposal and never advance quorum. Now throws if no
multisig proposal can be resolved.
- verifyAgentVault: brought to parity with the consumer validator — now also
requires both a deposit AND a withdraw approval (rejects deposit-only fund
traps), validTokenIds == [{1,1}], and kill-switch consistency (any forceful
approval must be admin-scoped, never initiatedBy "All", and requires
noForcefulPostMintTransfers=false).
- recover: --creator not matching the vault's recovery address is now a hard
error (exit 2), not a warning — it would always be rejected on-chain.
- builder guards: throw on duplicate --signers (inflated quorum weight) and on
--unlock-at >= --expires-at (permanently-locked window).
- transparency: `show` and `status` now surface the kill-switch `recovery`
address so depositors can see whether an admin can claw back + drain.
- docs: corrected `pay` description (atomicity is single-tx only).
Tests: +5 unit (guards, verifier negatives, vote throw) → 3103 green; e2e adds
show/status recovery assertions → 15/15. tsc clean.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Hardening pass (c930d8c) — adversarial review follow-upsRan a fresh adversarial review of the full feature. Fixes:
Reviewed-and-OK (not changed): quorum floor() mapping + range guard, proposalId hashing, status field reads, deposit/withdraw detection vs the emergency ids, the Tests: unit 3103 green (+5: guards, verifier negatives, vote-throw); e2e 15/15 (adds show/status recovery assertions). Net state of PR #254: review + 5 rounds of fixes, full e2e against a live chain+indexer. The one thing I'd still call out as a separate-repo follow-up: the indexer keys VoteDocs by bare proposalId — it should be |
|
Follow-up: the indexer-side vote-keying bug this surfaced is now fixed in bitbadges-indexer PR #185 — VoteDocs are keyed by a composite id (collectionId+approverAddress+approvalLevel+approvalId+proposalId) instead of the bare proposalId, with readers switched to field queries (no migration). That removes the cross-collection collision for all votingChallenge standards. The SDK-side per-vault proposalId hashing here stays as defense-in-depth. Re-ran this PR's agent-vaults e2e against the patched indexer: 15/15 green. |
Summary (experimental / draft)
Adds an Agent Vault standard + a first-class
agent-vaultsCLI command. AnAgent Vault is a Smart Token (
standards: ['Smart Token', 'Agent Vault'])whose withdrawal approval is gated for an autonomous agent:
withdrawLimit+daily|weekly|monthly) →approvalCriteria.approvalAmounts(per-initiator tally + reset interval)unlockAt/expiresAt) → the withdraw approval'stransferTimessigners+threshold) →approvalCriteria.votingChallenges(resetAfterExecution: false)The human is the collection manager (
--manager); the agent holds the vaulttokens and withdraws within the guardrails. Ported from a reference
implementation built on the Meridian devnet.
What's added
core/builders/agent-vault.ts—buildAgentVault()(mirrorsbuildVault/buildSmartToken).core/agent-vaults.ts— validator/detector/extractor +deposit/withdraw/pay/votemsg builders.api-indexer/verify-standards.ts—verifyAgentVault+ registry/alias.cli/commands/agent-vaults.ts—list / show / status / deposit / withdraw / pay / vote(emit →bb deploy).cli/commands/build.ts—bb build agent-vaultpreset;cli/index.tsregisters command + build alias.Test plan
tsc --noEmitclean.bb build agent-vault … | bb explainemits the expected standards + gated approvals.bb … | bb simulateagainst a devnet — flagged for review.quorumThresholdis a percent of total voter weight; mapped viafloor(threshold/totalWeight*100). Confirm the intended convention.emergency-recovery) intentionally deferred.🤖 Generated with Claude Code