Skip to content

wallet-cli: add the mm send-transaction command #9513

Description

@sirtimid

Summary

Add the user-facing mm command that sends a transaction through the daemon-hosted TransactionController. This is the CLI surface on top of the daemon transaction capability: it collects transaction parameters, dispatches them to the daemon, waits for the broadcast, and reports the resulting transaction hash.

Background

TransactionController is wired into the daemon's Wallet (#9509), and once the daemon can process a transaction (#9512: gas estimates + headless auto-approval), the only missing piece is a command that drives it. TransactionController.addTransaction(txParams, options) is exposed as the TransactionController:addTransaction messenger action, so the daemon already has the capability internally — this issue exposes it over the CLI.

Why a dedicated RPC handler (not raw mm daemon call)

addTransaction returns a Result shaped like { transactionMeta, result }, where result is a Promise<hash> that resolves once the transaction is signed and broadcast. That promise is not JSON-serializable, so it cannot travel back over the daemon's JSON-RPC socket via the generic call dispatch.

So the daemon needs a dedicated RPC handler (e.g. sendTransaction) that, server-side:

  1. calls TransactionController:addTransaction(txParams, { networkClientId, origin: 'metamask' /* internal */ }),
  2. awaits the inner result promise (the broadcast) to completion,
  3. returns a serializable payload — at minimum the transaction hash and id/status.

The mm command is then a thin client over that handler, following the existing command pattern (src/commands/wallet/unlock.ts, src/commands/daemon/call.ts).

Proposed CLI shape

mm wallet send (or mm tx send) with flags:

  • --to <address> (required)
  • --value <wei|eth> (native amount)
  • --data <hex> (optional, for contract calls)
  • --network-client-id <id> or --chain-id <hex> to select the network client (resolve chainId → networkClientId via NetworkController:findNetworkClientIdByChainId)
  • optional gas overrides (--gas, --max-fee-per-gas, --max-priority-fee-per-gas); otherwise rely on GasFeeController estimates
  • output: the transaction hash (and id / status), with a clear error if the keyring is locked, no network is selected, or the account is unfunded

Validate params with superstruct at dispatch time (consistent with the daemon's existing RPC param validation).

Dependencies (must land first)

Transitively also depends on #9510 (GasFeeController wired upstream), via #9512.

Security / UX considerations

Acceptance criteria

  • mm wallet send (name TBD) creates, signs, broadcasts a transaction via the daemon and prints the resulting hash.
  • The daemon exposes a dedicated sendTransaction RPC handler that awaits the broadcast and returns a serializable result (never tries to serialize the result promise).
  • Params validated with superstruct; clear errors for locked keyring / missing network / invalid params.
  • Tests cover the handler (happy path + broadcast failure) and the command (arg parsing, output, error surfaces); 100% coverage maintained; build, test, yarn lint:fix, yarn lint, changelog:validate pass.

References

  • packages/transaction-controller/src/TransactionController.tsaddTransaction (~line 1055) and its exposed TransactionController:addTransaction method action.
  • packages/wallet-cli/src/commands/wallet/unlock.ts, src/commands/daemon/call.ts — command + RPC-dispatch patterns to mirror.
  • packages/wallet-cli/src/daemon/rpc-socket-server.ts — where a dedicated sendTransaction handler is registered.

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

Related (tracking)

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Fields

    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions