Skip to content

wallet-cli: make the daemon transaction-capable — consume GasFeeController + headless auto-approval #9512

Description

@sirtimid

Summary

Make the @metamask/wallet-cli daemon capable of processing a transaction end-to-end, by completing the two consumer-side pieces the wired TransactionController needs at runtime:

  1. Consume GasFeeController — add a gasFeeController slot to the daemon's buildInstanceOptions so gas-fee estimates resolve.
  2. Headless auto-approval — replace the daemon's no-op showApprovalRequest with logic that auto-accepts pending approval requests, so a transaction (or signature) request doesn't hang forever waiting on a UI that doesn't exist.

Both are edits to the same daemon area (packages/wallet-cli/src/daemon/wallet-factory.ts + daemon wiring), so they land together. This makes the daemon transaction-capable; the user-facing mm send command is a separate follow-on (see Dependencies).

Background — why both are needed

TransactionController is wired into @metamask/wallet and its messenger delegates to both controllers. A send flow (TransactionController:addTransaction) does two things that currently dead-end in the daemon:

  • It calls GasFeeController:fetchGasFeeEstimates. That action is only lazily registered via delegation; until GasFeeController is actually wired and consumed, the call throws A handler for GasFeeController:fetchGasFeeEstimates has not been registered.
  • It calls ApprovalController:addRequest and awaits the result. The daemon's showApprovalRequest is () => undefined — that hook only signals "a request needs UI attention," it does not resolve the request. With nothing accepting it, the transaction hangs.

Piece 1 — consume GasFeeController

Add to buildInstanceOptions:

gasFeeController: {
  EIP1559APIEndpoint: 'https://gas.api.cx.metamask.io/networks/<chain_id>/suggestedGasFees',
  clientId: 'cli',
  // legacyAPIEndpoint / interval left at controller defaults
},
  • Global fetch is fine here (the daemon runs on Node 18+), so no fetch-injection work is needed on the CLI side.
  • Update the buildInstanceOptions JSDoc slot list and add a unit assertion (mirrors how the transactionController slot was wired).

Piece 2 — headless auto-approval

Replace the no-op with logic that accepts pending approvals. Recommended mechanism (the showApprovalRequest hook does not receive the request id, so accept via the messenger instead):

  • Subscribe to ApprovalController:stateChange (or the pending-approvals signal) and, for each pending request, call ApprovalController:acceptRequest(id).
  • Both ApprovalController:addRequest and ApprovalController:acceptRequest are available messenger actions on the wallet bus.
  • Register this in the daemon setup and tear it down in the existing dispose path.

Security consideration (must be a conscious decision)

Auto-approval means the daemon accepts every approval request without confirmation — including transaction and signature requests. For a headless daemon this is the intended model: it is driven only by its owner's local CLI over a 0600, same-user Unix socket, so the trust boundary is the socket, not a per-request prompt. This should be:

  • documented as the daemon's explicit trust model, and
  • ideally scoped/opt-in later (e.g. a config flag, or accepting only specific approval types) once the send command exists — out of scope here, but note it so auto-approve isn't silently treated as "safe by default."

Dependencies

Out of scope (follow-on)

Acceptance criteria

  • With Wire GasFeeController into @metamask/wallet default initialization #9510 released, a daemon-hosted Wallet resolves GasFeeController:fetchGasFeeEstimates (no "handler not registered").
  • A transaction/approval request raised in the daemon is auto-accepted rather than hanging.
  • The auto-approval trust model is documented in code.
  • Teardown (dispose) unsubscribes the auto-approval listener.
  • Tests cover both the gas slot and the auto-approval accept path; 100% coverage maintained; build, test, yarn lint:fix, yarn lint, changelog:validate pass.

References

  • packages/wallet-cli/src/daemon/wallet-factory.ts — buildInstanceOptions (approvalController.showApprovalRequest no-op; transactionController slot for the pattern to mirror).
  • packages/wallet/src/initialization/instances/approval-controller/approval-controller.ts — the injectable showApprovalRequest.

Owning teams: @MetaMask/core-platform @MetaMask/ocap-kernel.

Related (tracking)

Activity

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

Metadata

Metadata

Assignees

Labels

No labels
No labels

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions