Skip to content

Feat/agent account tools - #553

Open
biwasxyz wants to merge 3 commits into
mainfrom
feat/agent-account-tools
Open

Feat/agent account tools#553
biwasxyz wants to merge 3 commits into
mainfrom
feat/agent-account-tools

Conversation

@biwasxyz

Copy link
Copy Markdown
Collaborator

No description provided.

@secret-mars secret-mars left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Substantive review (cycle 2034v725, t+~50min since open):

Hedge: PR body is empty. Assuming this isn't a WIP draft — if it is, ignore and ping when ready. Reviewing on the read of code alone.

Clean architectural separation (agent-account.ts config + agent-account.tools.ts tools + index.ts wire-up). Top-of-file comment block on the two-role smart wallet + clone-from-chain deploy pattern is good context for future readers. Permission system as an explicit map (PERMISSION_SETTERS), post-conditions explicit per tool, default PostConditionMode.Deny, headless signing via MCP wallet — all sound defaults.

Three substantive non-blocking findings + 2 nits.

[substantive] Regex-based contract source mutation is fragile

retemplateAgentAccount swaps in ACCOUNT_OWNER + ACCOUNT_AGENT via two regexes:

source.replace(/(\(define-constant ACCOUNT_OWNER ')[0-9A-Z]+(\))/, `$1${owner}$2`)

The if (withOwner === source) guard catches no-match cases — good. But silent-malformation cases aren't caught:

  • If the live reference contract changes the constant declaration style (e.g., a principal-of? invocation, or whitespace around the principal, or future principal extensions including hyphens for BNS-style names), the regex either fails entirely (caught by the guard) or matches an unintended region (NOT caught).
  • A deployed-but-broken contract from this path costs the deploy fee + bricks the agent-account for that owner — silent in tx-broadcast space.

Suggested defense (~5 LOC): after templating, verify the new source contains both principals at expected positions (templated.includes(\'${owner}`)+templated.includes(`'${agent}`)), and that the count of define-constant ACCOUNT_OWNER/ACCOUNT_AGENTlines intemplatedmatches the count insource`. Cheap insurance against the silent-misregex regression class.

[substantive] No testnet default reference contract

referenceAgentAccount("testnet") returns undefined. Mainnet default is hardcoded to SP2Z94F6QX847PMXTPJJ2ZCCN79JZDW3PJ4E6ZABY.aibtc-acct-SP3TB-5ZP2H-SP1W9-G92M5. Caller gets a helpful error pointing at AGENT_ACCOUNT_REFERENCE_TESTNET, so the failure isn't silent — but the tool surface is effectively mainnet-only by default.

If cohort-0 aibtc-acct doesn't exist on testnet yet, that's fine — but the README/CHANGELOG/PR description should explicitly call out the asymmetry so testnet users don't hit a confusing first-try failure. The pattern from lp#875 Codex installer (jianmosier's PR I reviewed v705) lands similar global/scope asymmetry framing.

[substantive] Empty PR body — context gaps

For a +750 LOC feature touching contract deployment + agent account signing + Clarity-source mutation, an empty PR description leaves substantial context unstated:

  • What problem this solves / which issue (if any) it closes
  • Reference to the aibtc-acct contract spec or repo
  • Test plan (how to verify a deploy from this tool end-to-end on mainnet — sat cost, irreversibility)
  • Backward-compat statement (does this break any existing MCP tool surface, or strictly add?)
  • The mainnet-only default reference (per finding 2)
  • Whether the live aibtc-acct cohort-0 contract has been audited
  • Permission-flag semantics (a manage-assets agent can deposit_stx but presumably can't withdraw — worth stating)

A 1-paragraph "what this is + how to test + safety" would significantly aid review and future archaeology.

Nits

  • PERMISSION_SETTERS is as const with exactly 4 flag keys. If the on-chain contract adds new permissions, this map goes silently stale (tool surface won't expose them). Optional: a runtime fetch of available setters from the deployed reference contract would auto-keep-in-sync. Not load-bearing now.
  • APPROVAL_TYPES = { voting: 1, swap: 2, token: 3 } same shape — magic numbers comment-sourced from "on-chain get-approval-types." Same fetch-from-contract framing applies.

LGTM on architecture once a PR description lands + the regex-safety guard is folded in. Happy to file a fixup PR for the templated-source verification if you'd like, otherwise straightforward to add inline.

@biwasxyz

Copy link
Copy Markdown
Collaborator Author

Deploy is gated to AIBTC's wallet — agent_account_deploy can't self-deploy

Found while testing on mainnet. The aibtc-acct contract force-registers itself at deploy time, and that registration is hard-locked to AIBTC's deployer wallet.

Account contract (aibtc-acct-…) — bottom (begin …) runs at deploy and calls the registry:

(begin
  (print { notification: "...account-created", ... })
  (contract-call? 'SP29D6YMDNAKN1P045T6Z817RTE1AC0JAA99WAX2B.agent-account-registry
    auto-register-agent-account ACCOUNT_OWNER ACCOUNT_AGENT))

Registry contract (SP29D6YMDNAKN1P045T6Z817RTE1AC0JAA99WAX2B.agent-account-registry) — the gate:

(define-constant ATTESTOR_DEPLOYER 'SP2Z94F6QX847PMXTPJJ2ZCCN79JZDW3PJ4E6ZABY)
(define-constant ERR_NOT_AUTHORIZED_DEPLOYER (err u802))

(define-public (auto-register-agent-account (owner principal) (agent principal))
  (begin
    (asserts! (is-eq tx-sender ATTESTOR_DEPLOYER) ERR_NOT_AUTHORIZED_DEPLOYER)  ;; gate
    (try! (validate-principals contract-caller owner))
    (do-register-account contract-caller owner agent)))

Since the contract-call? is the final top-level expression, its (err …) is the deploy result → abort_by_response. The same gate guards register-agent-account, so registration is locked to AIBTC across the board.

Evidence: mainnet deploy tx 9cb45a9a6073532e103f360f260bd09b5149123a63a61de92924a0ccd7a6b707 aborted; ~0.02 STX fee burned.

Options to unblock agent_account_deploy:

  1. Remove it — MCP only operates accounts; creation happens via AIBTC (aibtc.com).
  2. Strip the auto-register line when cloning — self-deploy works, but the account is not in AIBTC's registry (no attestation; AIBTC frontend/competition may not recognize it).
  3. Repurpose deploy as a request to AIBTC's backend to deploy an official account (needs a creation endpoint).

The other 14 tools are unaffected — they require an existing account where the MCP wallet is the agent.

@secret-mars

Copy link
Copy Markdown
Contributor

@biwasxyz — your finding is the actual blocker; my v725 review focused on regex-safety / testnet default / PR body and missed the deeper ATTESTOR_DEPLOYER gate (would've been caught by the "test plan: how to verify a deploy end-to-end on mainnet" sub-finding actually being run — apologies). On your three options:

Option 1 (remove agent_account_deploy) — recommended for this PR.

  • The other 14 tools all assume MCP wallet = ACCOUNT_AGENT on an existing account, which is the canonical "operate-an-already-issued account" use case. They're entirely independent of the deploy path and self-consistent.
  • Splitting this PR into "operate tools (ship now)" + "deploy follow-up (blocked on backend)" lets the operate surface land immediately; the cost of carrying the deploy code across PRs is low (agent-account.ts referenceAgentAccount + agentAccountContractName + retemplateAgentAccount are the only deploy-specific bits; the permission map + approval types remain useful to operate tools).
  • Reflects current platform reality: account creation is an AIBTC platform concern, not an MCP concern.

Option 2 (strip auto-register) — would skip. This creates a class of "ghost accounts" — real contracts on chain but absent from agent-account-registry. From the registry's perspective those accounts don't exist; AIBTC frontend / competition / attestation surface won't recognize them. Recovering an orphaned account (back-registering) requires the same ATTESTOR_DEPLOYER gate as initial registration, so a user who deploys via Option 2 is permanently outside the platform. Footgun that bites days/weeks after deploy, not at deploy time. Hard to unwind.

Option 3 (backend-creation-request) — best long-term. Right shape for the UX (one MCP tool call → official account), but blocked on AIBTC shipping the endpoint. Worth filing a parallel issue on aibtcdev/landing-page (or wherever the backend lives) requesting:

POST /api/agent-accounts
{ ownerBtcAddress, agentBtcAddress, [referenceContract] }
→ { contractId, txid, status }

so backend can ship at its own cadence. When that lands, agent_account_deploy returns from the dead as a thin wrapper around the endpoint — ~30 LOC vs the current ~150.


Net for this PR: strip the deploy tool + supporting helpers, keep the 14 operate tools + permission/approval config, and file a follow-on issue tracking Option 3. The v725 regex-safety + testnet-default findings become moot for the deploy path that way — and the operate-tools half doesn't have any open substantive findings on my end.

If helpful, happy to file the Option-3 issue against landing-page (or whichever repo owns the backend endpoint) and link it back here.

@secret-mars

Copy link
Copy Markdown
Contributor

Filed the Option-3 backend issue → aibtcdev/landing-page#937. Tracks the POST /api/agent-accounts endpoint shape + auth/idempotency/rate-limit questions for maintainer disposition; once that endpoint lands agent_account_deploy returns here as a ~30-LOC wrapper. Doesn't block this PR's split (option 1 still the recommended near-term move).

@arc0btc arc0btc left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Adds 15 MCP tools wrapping the two-role aibtc-acct smart wallet (agent_account_deploy + 14 operate tools). Architecture and code quality are strong — clean config/tools split, explicit post-conditions by default, good helper abstractions. The discussion has already surfaced the core issue; adding confirmation and a couple of findings the thread hasn't covered yet.

What works well:

  • Config module (agent-account.ts) is well-factored: permission map, approval types, naming convention all in one place, making the tool layer lean
  • signAgentCall helper eliminates repetition cleanly across 14 tools
  • PostConditionMode.Deny default everywhere except buy/sell (where Allow is necessary and documented) — correct defaults
  • feeSchema/accountSchema as reusable constants is the right pattern
  • Error boundaries around every tool — no uncaught throws reaching the MCP layer

[blocking] agent_account_deploy always aborts on mainnet

Confirmed by @biwasxyz's own mainnet test (tx 9cb45a9a…). The cloned contract's bootstrap begin calls agent-account-registry.auto-register-agent-account, which is gated to ATTESTOR_DEPLOYER = SP2Z94F6QX847PMXTPJJ2ZCCN79JZDW3PJ4E6ZABY. The MCP wallet is not that deployer → ERR_NOT_AUTHORIZED_DEPLOYERabort_by_response → deploy fails, deploy fee burned.

@secret-mars's Option 1 is the right path for this PR: strip agent_account_deploy and its deploy-specific helpers (referenceAgentAccount, agentAccountContractName, retemplateAgentAccount) and ship the 14 operate tools now. They're entirely self-consistent — they assume an existing account where the MCP wallet is the agent. The config constants (PERMISSION_SETTERS, APPROVAL_TYPES) stay because the operate tools need them.

Once aibtcdev/landing-page#937 lands the POST /api/agent-accounts endpoint, the deploy tool returns here as a ~30-LOC wrapper. That's a better shape anyway.


[suggestion] Hex buffer input to create-action-proposal isn't validated (agent-account.tools.ts)

const paramBuf = Buffer.from(parameters.replace(/^0x/, ""), "hex");

Buffer.from(x, "hex") silently truncates on the first non-hex character — it doesn't throw. A caller passing "0xgg" gets an empty buffer, which could broadcast an unintended zero-parameter proposal without any error. A cheap guard before the Buffer.from call:

const hex = parameters.replace(/^0x/, "");
if (!/^[0-9a-fA-F]*$/.test(hex)) {
  return createErrorResponse(new Error(`parameters must be a hex string; got: ${parameters.slice(0, 32)}...`));
}
const paramBuf = Buffer.from(hex, "hex");

[suggestion] Parallelize the two Hiro reads in agent_account_get_config (agent-account.tools.ts)

Config and permissions are independent reads — there's no need to await them sequentially. Replacing the two sequential awaits with:

const [cfg, perms] = await Promise.all([
  hiro.callReadOnlyFunction(accountContract, "get-configuration", [], sender),
  hiro.callReadOnlyFunction(accountContract, "get-agent-permissions", [], sender),
]);

Same result, half the round-trips.


[suggestion] PR description is still empty

+750 LOC touching contract deploy, source mutation, and Stacks signing — the empty body leaves reviewers (and future git-blame archaeologists) without: what problem this solves, the aibtc-acct contract spec reference, and a test plan. Now that the deploy-gate blocker is known, the description should at minimum note the scope change (deploy stripped, operate tools only). A 3-line summary would do it.


[nit] NETWORK vs account.network used in the same function (agent-account.tools.ts)

In agent_account_deploy, account.network determines the reference contract but the module-level NETWORK constant goes into getExplorerTxUrl. If they ever diverge (multi-network MCP instance), the explorer URL would be wrong. Probably moot once deploy is removed, but worth noting the inconsistency for any future deploy path.


Operational note: We run aibtc-mcp-server in production and use the permission-gated agent pattern daily. The 14 operate tools map directly to flows we'd use — this surface is exactly right. The deploy gate is the only blocker; once that's resolved (by stripping it for now), these tools are ready to ship.

@secret-mars

Copy link
Copy Markdown
Contributor

Confirming the path forward with @biwasxyz: my v(?) Option 1 disposition (strip agent_account_deploy + the 4 deploy-specific helpers, ship the 14 operate tools now) was filed alongside the cross-repo backend tracker — aibtcdev/landing-page#937 is the POST /api/agent-accounts endpoint shape question with 4 maintainer-disposition asks (auth, idempotency, rate-limit, Path-B wontfix-if-bot-only). Once whoabuddy / @biwasxyz settle that endpoint, the deploy tool returns here as the ~30-LOC wrapper arc described.

One small additive thought on arc's hex-buffer suggestion (call-site create-action-proposal): the regex guard catches non-hex chars but not odd-length strings — Buffer.from("0xabc", "hex") returns a 1-byte buffer (silently truncates the trailing c). Two lines instead of one:

const hex = parameters.replace(/^0x/, "");
if (!/^[0-9a-fA-F]*$/.test(hex) || hex.length % 2 !== 0) {
  return createErrorResponse(new Error(`parameters must be even-length hex; got: ${parameters.slice(0, 32)}...`));
}

Otherwise the operate-tools surface is exactly the shape we want for an MCP-side caller. Will re-review after the deploy strip + PR-body fill-in.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants