@metamask-previews/wallet-cli 0.0.0-preview-2dc3d26a4 → 0.0.0-preview-aa202ec10

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -9,6 +9,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
9
9
 
10
10
  ### Added
11
11
 
12
+ - Add the `mm wallet send` command and a dedicated daemon `sendTransaction` RPC handler for sending a transaction through the daemon-hosted `TransactionController` ([#9636](https://github.com/MetaMask/core/pull/9636))
13
+ - The command converts the ether `--value` to wei, resolves the network client (`--network-client-id` or `--chain-id`) and sender (defaulting to the selected account), previews the resolved plan, and broadcasts after confirmation, printing the resulting transaction hash. `--yes` skips the prompt; `--dry-run` resolves and validates without broadcasting.
14
+ - The `sendTransaction` handler awaits the broadcast server-side and returns a serializable `{ transactionHash, transactionId, status }`, because `addTransaction`'s `result` promise cannot travel back over the generic `call` dispatch. Transactions are submitted as internal (auto-approved by the daemon).
12
15
  - Auto-accept pending approval requests in the daemon so a transaction or signature flow resolves instead of hanging on the headless no-op `showApprovalRequest`; the subscription is torn down on `dispose` ([#9612](https://github.com/MetaMask/core/pull/9612))
13
16
  - **Security note:** the daemon approves every request without a per-request prompt; the trust boundary is its `0600`, same-user Unix socket.
14
17
  - Wire the `transactionController` slot in the daemon wallet's instance options, so the daemon runs the `TransactionController` with an explicit CLI-appropriate configuration (swaps processing disabled, no client hooks) rather than relying on the controller's implicit defaults ([#9509](https://github.com/MetaMask/core/pull/9509))
package/README.md CHANGED
@@ -32,6 +32,16 @@ mm wallet unlock --password <pw> # or: MM_WALLET_PASSWORD=<pw> mm wallet unloc
32
32
  mm wallet unlock # prompts interactively (input masked)
33
33
  ```
34
34
 
35
+ Send a transaction. `--value` is in ether; select the network with `--network-client-id` or `--chain-id`; the sender defaults to the selected account. The command previews the resolved plan and asks for confirmation before broadcasting, then prints the transaction hash:
36
+
37
+ ```sh
38
+ mm wallet send --to 0xRecipient --value 0.01 --chain-id 0x1
39
+ mm wallet send --to 0xRecipient --value 0.01 --network-client-id mainnet --yes # skip the prompt
40
+ mm wallet send --to 0xContract --data 0xabcdef --value 0 --chain-id 0x1 --dry-run # resolve + validate only
41
+ ```
42
+
43
+ Because the daemon auto-approves (see the security note below), the confirmation prompt — or your explicit `--yes` — is the only boundary before funds move; use `--dry-run` first if unsure. Gas is estimated automatically unless overridden with `--gas` / `--max-fee-per-gas` / `--max-priority-fee-per-gas` / `--gas-price` (each a `0x`-prefixed hex quantity).
44
+
35
45
  Discover what the running wallet can do — `list` prints every messenger action currently dispatchable via `call`. This surface grows as more controllers are wired, so treat it as evolving rather than a stability contract:
36
46
 
37
47
  ```sh
@@ -57,7 +67,7 @@ mm daemon purge # stop, then delete all daemon state files (--force to
57
67
 
58
68
  State (socket, PID file, log, and the SQLite database) lives in the per-user oclif data directory; override it with `MM_DATA_DIR`.
59
69
 
60
- > **Security model — the daemon auto-approves everything.** Because it is headless, the daemon accepts every approval request (transactions and signatures included) with no per-request prompt; otherwise an awaited request would hang forever with no UI to resolve it. The trust boundary is therefore the daemon's `0600`, same-user Unix socket — anything that can reach the socket can move funds. A scoped/opt-in approval policy is planned for when a user-facing send command lands.
70
+ > **Security model — the daemon auto-approves everything.** Because it is headless, the daemon accepts every approval request (transactions and signatures included) with no per-request prompt; otherwise an awaited request would hang forever with no UI to resolve it. The trust boundary is therefore the daemon's `0600`, same-user Unix socket — anything that can reach the socket can move funds. For `mm wallet send` the confirmation prompt (or `--yes`) is the only per-transaction gate; the daemon itself still applies no per-request policy.
61
71
 
62
72
  ## Troubleshooting
63
73
 
@@ -0,0 +1,332 @@
1
+ "use strict";
2
+ var __classPrivateFieldGet = (this && this.__classPrivateFieldGet) || function (receiver, state, kind, f) {
3
+ if (kind === "a" && !f) throw new TypeError("Private accessor was defined without a getter");
4
+ if (typeof state === "function" ? receiver !== state || !f : !state.has(receiver)) throw new TypeError("Cannot read private member from an object whose class did not declare it");
5
+ return kind === "m" ? f : kind === "a" ? f.call(receiver) : f ? f.value : state.get(receiver);
6
+ };
7
+ var _WalletSend_instances, _WalletSend_dispatchSend;
8
+ Object.defineProperty(exports, "__esModule", { value: true });
9
+ exports.parseEtherToWeiHex = void 0;
10
+ const superstruct_1 = require("@metamask/superstruct");
11
+ const utils_1 = require("@metamask/utils");
12
+ const core_1 = require("@oclif/core");
13
+ const daemon_client_js_1 = require("../../daemon/daemon-client.cjs");
14
+ const paths_js_1 = require("../../daemon/paths.cjs");
15
+ const prompts_js_1 = require("../../daemon/prompts.cjs");
16
+ const send_transaction_js_1 = require("../../daemon/send-transaction.cjs");
17
+ const utils_js_1 = require("../../daemon/utils.cjs");
18
+ const ETHER_DECIMALS = 18;
19
+ /**
20
+ * Fallback response timeout (ms) for the broadcast call. Broadcasting waits for
21
+ * signing and `eth_sendRawTransaction` server-side, which can far outlast the
22
+ * 30s default used for cheap RPCs; too short a timeout would make a slow but
23
+ * successful send look like a failure and tempt a duplicate re-send.
24
+ * `--timeout` overrides it.
25
+ */
26
+ const BROADCAST_TIMEOUT_MS = 120000;
27
+ /**
28
+ * Convert a decimal ether amount to a `0x`-prefixed wei quantity.
29
+ *
30
+ * The fixed-point conversion is delegated to `@metamask/utils`' `toWei` rather
31
+ * than re-deriving the wei math here. The daemon's `sendTransaction` boundary
32
+ * expects canonical hex wei; this is where the human-friendly `--value` (ether)
33
+ * is turned into it.
34
+ *
35
+ * `toWei` silently accepts negatives and throws opaque messages on other
36
+ * malformed input, so the value is guarded up front for a clear error and a
37
+ * strictly non-negative amount.
38
+ *
39
+ * @param amount - A non-negative decimal ether amount (e.g. `"0.1"`, `"1"`).
40
+ * @returns The equivalent wei as a `0x`-prefixed hex string.
41
+ * @throws If `amount` is not a non-negative decimal or has more than 18
42
+ * fractional digits.
43
+ */
44
+ function parseEtherToWeiHex(amount) {
45
+ const trimmed = amount.trim();
46
+ if (!/^\d+(?:\.\d+)?$/u.test(trimmed)) {
47
+ throw new Error(`Invalid value "${amount}": expected a non-negative decimal amount of ether (e.g. 0.1).`);
48
+ }
49
+ if ((trimmed.split('.')[1] ?? '').length > ETHER_DECIMALS) {
50
+ throw new Error(`Invalid value "${amount}": ether has at most ${ETHER_DECIMALS} decimal places.`);
51
+ }
52
+ return (0, utils_1.bigIntToHex)((0, utils_1.toWei)(trimmed, 'ether'));
53
+ }
54
+ exports.parseEtherToWeiHex = parseEtherToWeiHex;
55
+ class WalletSend extends core_1.Command {
56
+ constructor() {
57
+ super(...arguments);
58
+ _WalletSend_instances.add(this);
59
+ }
60
+ async run() {
61
+ const { flags } = await this.parse(WalletSend);
62
+ const networkClientId = (0, utils_js_1.emptyToUndefined)(flags['network-client-id']);
63
+ const chainId = (0, utils_js_1.emptyToUndefined)(flags['chain-id']);
64
+ if ((networkClientId === undefined) === (chainId === undefined)) {
65
+ this.error('Provide exactly one of --network-client-id or --chain-id.');
66
+ }
67
+ let value;
68
+ try {
69
+ value = parseEtherToWeiHex(flags.value);
70
+ }
71
+ catch (error) {
72
+ // `parseEtherToWeiHex` only ever throws `Error`.
73
+ this.error(error.message);
74
+ }
75
+ // Everything except the network selector. The selector is added per call:
76
+ // a confirmed broadcast pins the `networkClientId` the preview resolved
77
+ // rather than re-sending `--chain-id` and re-resolving it.
78
+ const baseParams = {
79
+ to: flags.to,
80
+ value,
81
+ ...(flags.from ? { from: flags.from } : {}),
82
+ ...(flags.data ? { data: flags.data } : {}),
83
+ ...(flags.gas ? { gas: flags.gas } : {}),
84
+ ...(flags['max-fee-per-gas']
85
+ ? { maxFeePerGas: flags['max-fee-per-gas'] }
86
+ : {}),
87
+ ...(flags['max-priority-fee-per-gas']
88
+ ? { maxPriorityFeePerGas: flags['max-priority-fee-per-gas'] }
89
+ : {}),
90
+ ...(flags['gas-price'] ? { gasPrice: flags['gas-price'] } : {}),
91
+ };
92
+ // Exactly one selector is defined (guarded above); guard each spread by its
93
+ // own `undefined` check so the object never carries an `undefined` value.
94
+ const params = {
95
+ ...baseParams,
96
+ ...(networkClientId === undefined ? {} : { networkClientId }),
97
+ ...(chainId === undefined ? {} : { chainId }),
98
+ };
99
+ const planExtras = {
100
+ data: flags.data,
101
+ gas: flags.gas,
102
+ maxFeePerGas: flags['max-fee-per-gas'],
103
+ maxPriorityFeePerGas: flags['max-priority-fee-per-gas'],
104
+ gasPrice: flags['gas-price'],
105
+ };
106
+ const { socketPath } = (0, paths_js_1.getDaemonPaths)(this.config.dataDir);
107
+ const timeoutMs = flags.timeout;
108
+ // A `dryRun` resolves the sender and network client server-side without
109
+ // broadcasting. `--dry-run` stops after previewing; an interactive run
110
+ // previews first so the confirmation shows (and then pins) the resolved
111
+ // sender/network; `--yes` skips straight to the broadcast.
112
+ if (flags['dry-run']) {
113
+ const preview = await __classPrivateFieldGet(this, _WalletSend_instances, "m", _WalletSend_dispatchSend).call(this, {
114
+ socketPath,
115
+ params: { ...params, dryRun: true },
116
+ timeoutMs,
117
+ struct: send_transaction_js_1.SendTransactionDryRunResultStruct,
118
+ broadcast: false,
119
+ });
120
+ this.log(formatPlan(preview, flags.value, planExtras));
121
+ return;
122
+ }
123
+ let resolved;
124
+ if (!flags.yes) {
125
+ const preview = await __classPrivateFieldGet(this, _WalletSend_instances, "m", _WalletSend_dispatchSend).call(this, {
126
+ socketPath,
127
+ params: { ...params, dryRun: true },
128
+ timeoutMs,
129
+ struct: send_transaction_js_1.SendTransactionDryRunResultStruct,
130
+ broadcast: false,
131
+ });
132
+ let confirmed;
133
+ try {
134
+ confirmed = await (0, prompts_js_1.confirmSend)(formatPlan(preview, flags.value, planExtras));
135
+ }
136
+ catch (error) {
137
+ // Ctrl+C at the prompt (@inquirer/core's ExitPromptError) is a clean
138
+ // abort, not a failure; anything else should surface.
139
+ if (error instanceof Error && error.name === 'ExitPromptError') {
140
+ this.log('Aborted.');
141
+ return;
142
+ }
143
+ throw error;
144
+ }
145
+ if (!confirmed) {
146
+ this.log('Aborted.');
147
+ return;
148
+ }
149
+ resolved = preview;
150
+ }
151
+ // Broadcast the exact sender/network the user reviewed (when they confirmed
152
+ // a preview), so what is signed matches what was shown. `--yes` skips the
153
+ // preview, so there it re-resolves server-side from `params`.
154
+ const broadcastParams = resolved
155
+ ? {
156
+ ...baseParams,
157
+ from: resolved.from,
158
+ networkClientId: resolved.networkClientId,
159
+ }
160
+ : params;
161
+ const result = await __classPrivateFieldGet(this, _WalletSend_instances, "m", _WalletSend_dispatchSend).call(this, {
162
+ socketPath,
163
+ params: broadcastParams,
164
+ timeoutMs: timeoutMs ?? BROADCAST_TIMEOUT_MS,
165
+ struct: send_transaction_js_1.SendTransactionBroadcastResultStruct,
166
+ broadcast: true,
167
+ });
168
+ this.log('Transaction broadcast.');
169
+ this.log(`Hash: ${result.transactionHash}`);
170
+ this.log(`Id: ${result.transactionId}`);
171
+ this.log(`Status: ${result.status}`);
172
+ }
173
+ }
174
+ _WalletSend_instances = new WeakSet(), _WalletSend_dispatchSend =
175
+ /**
176
+ * Send a `sendTransaction` RPC to the daemon and return its validated result,
177
+ * translating connection errors, broadcast timeouts, JSON-RPC failures, and
178
+ * unexpected payloads into command errors.
179
+ *
180
+ * @param options - Dispatch options.
181
+ * @param options.socketPath - The daemon Unix socket path.
182
+ * @param options.params - The `sendTransaction` params (with or without
183
+ * `dryRun`).
184
+ * @param options.timeoutMs - Optional response timeout in milliseconds.
185
+ * @param options.struct - Struct the `result` payload must satisfy; the
186
+ * return type is inferred from it.
187
+ * @param options.broadcast - Whether this is the real (fund-moving) broadcast.
188
+ * When true, a read timeout is reported with guidance that the send may
189
+ * already be in flight, so the user does not blindly re-send.
190
+ * @returns The validated `result` payload.
191
+ */
192
+ async function _WalletSend_dispatchSend(options) {
193
+ const { socketPath, params, timeoutMs, struct, broadcast } = options;
194
+ let response;
195
+ try {
196
+ response = await (0, daemon_client_js_1.sendCommand)({
197
+ socketPath,
198
+ method: 'sendTransaction',
199
+ params,
200
+ ...(timeoutMs === undefined ? {} : { timeoutMs }),
201
+ });
202
+ }
203
+ catch (error) {
204
+ if (broadcast && isReadTimeout(error)) {
205
+ this.error(broadcastTimeoutMessage());
206
+ }
207
+ this.error((0, utils_js_1.makeDaemonConnectionError)(error));
208
+ }
209
+ if ((0, utils_1.isJsonRpcFailure)(response)) {
210
+ this.error((0, utils_js_1.formatJsonRpcError)(response.error));
211
+ }
212
+ const { result } = response;
213
+ if (!(0, superstruct_1.is)(result, struct)) {
214
+ this.error(`The daemon returned an unexpected ${broadcast ? 'send' : 'dry-run'} ` +
215
+ 'result. It may be running an incompatible version.');
216
+ }
217
+ return result;
218
+ };
219
+ WalletSend.description = 'Send a transaction through the daemon-hosted TransactionController. ' +
220
+ 'Estimates gas automatically unless overridden, signs, broadcasts, and ' +
221
+ 'prints the resulting transaction hash. The daemon auto-approves, so the ' +
222
+ 'confirmation boundary is this command.';
223
+ WalletSend.examples = [
224
+ '<%= config.bin %> wallet send --to 0xRecipient --value 0.01 --chain-id 0x1',
225
+ '<%= config.bin %> wallet send --to 0xRecipient --value 0.01 --network-client-id mainnet --yes',
226
+ '<%= config.bin %> wallet send --to 0xContract --data 0xabcdef --value 0 --chain-id 0x1 --dry-run',
227
+ ];
228
+ WalletSend.flags = {
229
+ to: core_1.Flags.string({
230
+ description: 'Recipient address (0x-prefixed)',
231
+ required: true,
232
+ }),
233
+ value: core_1.Flags.string({
234
+ description: 'Amount to send, in ether (e.g. 0.01). Defaults to 0.',
235
+ default: '0',
236
+ }),
237
+ from: core_1.Flags.string({
238
+ description: 'Sender address (0x-prefixed). Defaults to the selected account.',
239
+ }),
240
+ data: core_1.Flags.string({
241
+ description: 'Calldata as a 0x-prefixed hex string (for contract calls)',
242
+ }),
243
+ 'network-client-id': core_1.Flags.string({
244
+ description: 'Network client to send on. Provide this or --chain-id, not both.',
245
+ }),
246
+ 'chain-id': core_1.Flags.string({
247
+ description: 'Chain ID (0x-prefixed hex) to resolve to a network client. Provide this or --network-client-id, not both.',
248
+ }),
249
+ gas: core_1.Flags.string({
250
+ description: 'Gas limit override, as a 0x-prefixed hex quantity',
251
+ }),
252
+ 'max-fee-per-gas': core_1.Flags.string({
253
+ description: 'maxFeePerGas override, as a 0x-prefixed hex wei quantity',
254
+ }),
255
+ 'max-priority-fee-per-gas': core_1.Flags.string({
256
+ description: 'maxPriorityFeePerGas override, as a 0x-prefixed hex wei quantity',
257
+ }),
258
+ 'gas-price': core_1.Flags.string({
259
+ description: 'Legacy gasPrice override, as a 0x-prefixed hex wei quantity',
260
+ }),
261
+ 'dry-run': core_1.Flags.boolean({
262
+ description: 'Resolve the network client and sender and validate params, but do not broadcast.',
263
+ }),
264
+ yes: core_1.Flags.boolean({
265
+ char: 'y',
266
+ description: 'Skip the confirmation prompt and broadcast immediately.',
267
+ }),
268
+ timeout: core_1.Flags.integer({
269
+ char: 't',
270
+ description: 'Response timeout in milliseconds',
271
+ }),
272
+ };
273
+ exports.default = WalletSend;
274
+ /**
275
+ * Whether an error is the daemon-client socket read timeout. It carries no
276
+ * errno code, so its message is the only signal.
277
+ *
278
+ * @param error - The caught value.
279
+ * @returns True if it is the read timeout.
280
+ */
281
+ function isReadTimeout(error) {
282
+ return error instanceof Error && error.message === 'Socket read timed out';
283
+ }
284
+ /**
285
+ * The message shown when the broadcast call times out. A send is not
286
+ * idempotent, and the daemon may still be broadcasting after the client gives
287
+ * up waiting, so this warns against a blind re-run rather than reading as a
288
+ * plain connection failure.
289
+ *
290
+ * @returns The user-facing guidance.
291
+ */
292
+ function broadcastTimeoutMessage() {
293
+ return ('The daemon did not respond in time, but your transaction may still be ' +
294
+ 'broadcasting. Do NOT re-run this command — you could send it twice. ' +
295
+ 'Check `mm daemon status`, the daemon log, or your account on-chain to ' +
296
+ 'confirm, then use --timeout to wait longer if needed.');
297
+ }
298
+ /**
299
+ * Format a dry-run plan for display (and as the confirmation prompt body).
300
+ *
301
+ * @param plan - The dry-run result returned by the daemon.
302
+ * @param etherAmount - The original `--value` (ether), shown alongside the
303
+ * resolved wei so the user sees the human amount they typed.
304
+ * @param extras - The `--data` / gas overrides the daemon does not echo back,
305
+ * shown so the user confirms exactly what will be sent.
306
+ * @returns A multi-line summary of the transaction to be sent.
307
+ */
308
+ function formatPlan(plan, etherAmount, extras) {
309
+ const lines = [
310
+ 'About to send:',
311
+ ` To: ${plan.to}`,
312
+ ` From: ${plan.from}`,
313
+ ` Value: ${etherAmount} ETH (${plan.value} wei)`,
314
+ ` Network: ${plan.networkClientId}`,
315
+ ];
316
+ if (extras.data) {
317
+ lines.push(` Data: ${extras.data}`);
318
+ }
319
+ const gasParts = [
320
+ extras.gas ? `gas=${extras.gas}` : undefined,
321
+ extras.maxFeePerGas ? `maxFeePerGas=${extras.maxFeePerGas}` : undefined,
322
+ extras.maxPriorityFeePerGas
323
+ ? `maxPriorityFeePerGas=${extras.maxPriorityFeePerGas}`
324
+ : undefined,
325
+ extras.gasPrice ? `gasPrice=${extras.gasPrice}` : undefined,
326
+ ].filter((part) => part !== undefined);
327
+ if (gasParts.length > 0) {
328
+ lines.push(` Gas: ${gasParts.join(', ')}`);
329
+ }
330
+ return lines.join('\n');
331
+ }
332
+ //# sourceMappingURL=send.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"send.cjs","sourceRoot":"","sources":["../../../src/commands/wallet/send.ts"],"names":[],"mappings":"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","sourcesContent":["import { is } from '@metamask/superstruct';\nimport type { Struct } from '@metamask/superstruct';\nimport { bigIntToHex, isJsonRpcFailure, toWei } from '@metamask/utils';\nimport type { Hex, Json, JsonRpcParams } from '@metamask/utils';\nimport { Command, Flags } from '@oclif/core';\n\nimport { sendCommand } from '../../daemon/daemon-client.js';\nimport { getDaemonPaths } from '../../daemon/paths.js';\nimport { confirmSend } from '../../daemon/prompts.js';\nimport {\n SendTransactionBroadcastResultStruct,\n SendTransactionDryRunResultStruct,\n} from '../../daemon/send-transaction.js';\nimport type { SendTransactionDryRunResult } from '../../daemon/send-transaction.js';\nimport {\n emptyToUndefined,\n formatJsonRpcError,\n makeDaemonConnectionError,\n} from '../../daemon/utils.js';\n\nconst ETHER_DECIMALS = 18;\n\n/**\n * Fallback response timeout (ms) for the broadcast call. Broadcasting waits for\n * signing and `eth_sendRawTransaction` server-side, which can far outlast the\n * 30s default used for cheap RPCs; too short a timeout would make a slow but\n * successful send look like a failure and tempt a duplicate re-send.\n * `--timeout` overrides it.\n */\nconst BROADCAST_TIMEOUT_MS = 120_000;\n\n/**\n * The transaction fields shown in the confirmation preview that the daemon\n * does not echo back (they are the raw `--data` / gas overrides the user\n * passed, so the CLI supplies them for display).\n */\ntype PlanExtras = {\n data?: string | undefined;\n gas?: string | undefined;\n maxFeePerGas?: string | undefined;\n maxPriorityFeePerGas?: string | undefined;\n gasPrice?: string | undefined;\n};\n\n/**\n * Convert a decimal ether amount to a `0x`-prefixed wei quantity.\n *\n * The fixed-point conversion is delegated to `@metamask/utils`' `toWei` rather\n * than re-deriving the wei math here. The daemon's `sendTransaction` boundary\n * expects canonical hex wei; this is where the human-friendly `--value` (ether)\n * is turned into it.\n *\n * `toWei` silently accepts negatives and throws opaque messages on other\n * malformed input, so the value is guarded up front for a clear error and a\n * strictly non-negative amount.\n *\n * @param amount - A non-negative decimal ether amount (e.g. `\"0.1\"`, `\"1\"`).\n * @returns The equivalent wei as a `0x`-prefixed hex string.\n * @throws If `amount` is not a non-negative decimal or has more than 18\n * fractional digits.\n */\nexport function parseEtherToWeiHex(amount: string): Hex {\n const trimmed = amount.trim();\n if (!/^\\d+(?:\\.\\d+)?$/u.test(trimmed)) {\n throw new Error(\n `Invalid value \"${amount}\": expected a non-negative decimal amount of ether (e.g. 0.1).`,\n );\n }\n\n if ((trimmed.split('.')[1] ?? '').length > ETHER_DECIMALS) {\n throw new Error(\n `Invalid value \"${amount}\": ether has at most ${ETHER_DECIMALS} decimal places.`,\n );\n }\n\n return bigIntToHex(toWei(trimmed, 'ether'));\n}\n\nexport default class WalletSend extends Command {\n static override description =\n 'Send a transaction through the daemon-hosted TransactionController. ' +\n 'Estimates gas automatically unless overridden, signs, broadcasts, and ' +\n 'prints the resulting transaction hash. The daemon auto-approves, so the ' +\n 'confirmation boundary is this command.';\n\n static override examples = [\n '<%= config.bin %> wallet send --to 0xRecipient --value 0.01 --chain-id 0x1',\n '<%= config.bin %> wallet send --to 0xRecipient --value 0.01 --network-client-id mainnet --yes',\n '<%= config.bin %> wallet send --to 0xContract --data 0xabcdef --value 0 --chain-id 0x1 --dry-run',\n ];\n\n static override flags = {\n to: Flags.string({\n description: 'Recipient address (0x-prefixed)',\n required: true,\n }),\n value: Flags.string({\n description: 'Amount to send, in ether (e.g. 0.01). Defaults to 0.',\n default: '0',\n }),\n from: Flags.string({\n description:\n 'Sender address (0x-prefixed). Defaults to the selected account.',\n }),\n data: Flags.string({\n description: 'Calldata as a 0x-prefixed hex string (for contract calls)',\n }),\n 'network-client-id': Flags.string({\n description:\n 'Network client to send on. Provide this or --chain-id, not both.',\n }),\n 'chain-id': Flags.string({\n description:\n 'Chain ID (0x-prefixed hex) to resolve to a network client. Provide this or --network-client-id, not both.',\n }),\n gas: Flags.string({\n description: 'Gas limit override, as a 0x-prefixed hex quantity',\n }),\n 'max-fee-per-gas': Flags.string({\n description: 'maxFeePerGas override, as a 0x-prefixed hex wei quantity',\n }),\n 'max-priority-fee-per-gas': Flags.string({\n description:\n 'maxPriorityFeePerGas override, as a 0x-prefixed hex wei quantity',\n }),\n 'gas-price': Flags.string({\n description:\n 'Legacy gasPrice override, as a 0x-prefixed hex wei quantity',\n }),\n 'dry-run': Flags.boolean({\n description:\n 'Resolve the network client and sender and validate params, but do not broadcast.',\n }),\n yes: Flags.boolean({\n char: 'y',\n description: 'Skip the confirmation prompt and broadcast immediately.',\n }),\n timeout: Flags.integer({\n char: 't',\n description: 'Response timeout in milliseconds',\n }),\n };\n\n public async run(): Promise<void> {\n const { flags } = await this.parse(WalletSend);\n\n const networkClientId = emptyToUndefined(flags['network-client-id']);\n const chainId = emptyToUndefined(flags['chain-id']);\n if ((networkClientId === undefined) === (chainId === undefined)) {\n this.error('Provide exactly one of --network-client-id or --chain-id.');\n }\n\n let value: string;\n try {\n value = parseEtherToWeiHex(flags.value);\n } catch (error) {\n // `parseEtherToWeiHex` only ever throws `Error`.\n this.error((error as Error).message);\n }\n\n // Everything except the network selector. The selector is added per call:\n // a confirmed broadcast pins the `networkClientId` the preview resolved\n // rather than re-sending `--chain-id` and re-resolving it.\n const baseParams: Record<string, Json> = {\n to: flags.to,\n value,\n ...(flags.from ? { from: flags.from } : {}),\n ...(flags.data ? { data: flags.data } : {}),\n ...(flags.gas ? { gas: flags.gas } : {}),\n ...(flags['max-fee-per-gas']\n ? { maxFeePerGas: flags['max-fee-per-gas'] }\n : {}),\n ...(flags['max-priority-fee-per-gas']\n ? { maxPriorityFeePerGas: flags['max-priority-fee-per-gas'] }\n : {}),\n ...(flags['gas-price'] ? { gasPrice: flags['gas-price'] } : {}),\n };\n // Exactly one selector is defined (guarded above); guard each spread by its\n // own `undefined` check so the object never carries an `undefined` value.\n const params: Record<string, Json> = {\n ...baseParams,\n ...(networkClientId === undefined ? {} : { networkClientId }),\n ...(chainId === undefined ? {} : { chainId }),\n };\n const planExtras: PlanExtras = {\n data: flags.data,\n gas: flags.gas,\n maxFeePerGas: flags['max-fee-per-gas'],\n maxPriorityFeePerGas: flags['max-priority-fee-per-gas'],\n gasPrice: flags['gas-price'],\n };\n\n const { socketPath } = getDaemonPaths(this.config.dataDir);\n const timeoutMs = flags.timeout;\n\n // A `dryRun` resolves the sender and network client server-side without\n // broadcasting. `--dry-run` stops after previewing; an interactive run\n // previews first so the confirmation shows (and then pins) the resolved\n // sender/network; `--yes` skips straight to the broadcast.\n if (flags['dry-run']) {\n const preview = await this.#dispatchSend({\n socketPath,\n params: { ...params, dryRun: true },\n timeoutMs,\n struct: SendTransactionDryRunResultStruct,\n broadcast: false,\n });\n this.log(formatPlan(preview, flags.value, planExtras));\n return;\n }\n\n let resolved: SendTransactionDryRunResult | undefined;\n if (!flags.yes) {\n const preview = await this.#dispatchSend({\n socketPath,\n params: { ...params, dryRun: true },\n timeoutMs,\n struct: SendTransactionDryRunResultStruct,\n broadcast: false,\n });\n\n let confirmed: boolean;\n try {\n confirmed = await confirmSend(\n formatPlan(preview, flags.value, planExtras),\n );\n } catch (error) {\n // Ctrl+C at the prompt (@inquirer/core's ExitPromptError) is a clean\n // abort, not a failure; anything else should surface.\n if (error instanceof Error && error.name === 'ExitPromptError') {\n this.log('Aborted.');\n return;\n }\n throw error;\n }\n if (!confirmed) {\n this.log('Aborted.');\n return;\n }\n resolved = preview;\n }\n\n // Broadcast the exact sender/network the user reviewed (when they confirmed\n // a preview), so what is signed matches what was shown. `--yes` skips the\n // preview, so there it re-resolves server-side from `params`.\n const broadcastParams: Record<string, Json> = resolved\n ? {\n ...baseParams,\n from: resolved.from,\n networkClientId: resolved.networkClientId,\n }\n : params;\n\n const result = await this.#dispatchSend({\n socketPath,\n params: broadcastParams,\n timeoutMs: timeoutMs ?? BROADCAST_TIMEOUT_MS,\n struct: SendTransactionBroadcastResultStruct,\n broadcast: true,\n });\n this.log('Transaction broadcast.');\n this.log(`Hash: ${result.transactionHash}`);\n this.log(`Id: ${result.transactionId}`);\n this.log(`Status: ${result.status}`);\n }\n\n /**\n * Send a `sendTransaction` RPC to the daemon and return its validated result,\n * translating connection errors, broadcast timeouts, JSON-RPC failures, and\n * unexpected payloads into command errors.\n *\n * @param options - Dispatch options.\n * @param options.socketPath - The daemon Unix socket path.\n * @param options.params - The `sendTransaction` params (with or without\n * `dryRun`).\n * @param options.timeoutMs - Optional response timeout in milliseconds.\n * @param options.struct - Struct the `result` payload must satisfy; the\n * return type is inferred from it.\n * @param options.broadcast - Whether this is the real (fund-moving) broadcast.\n * When true, a read timeout is reported with guidance that the send may\n * already be in flight, so the user does not blindly re-send.\n * @returns The validated `result` payload.\n */\n async #dispatchSend<Value>(options: {\n socketPath: string;\n params: JsonRpcParams;\n timeoutMs: number | undefined;\n struct: Struct<Value>;\n broadcast: boolean;\n }): Promise<Value> {\n const { socketPath, params, timeoutMs, struct, broadcast } = options;\n let response;\n try {\n response = await sendCommand({\n socketPath,\n method: 'sendTransaction',\n params,\n ...(timeoutMs === undefined ? {} : { timeoutMs }),\n });\n } catch (error) {\n if (broadcast && isReadTimeout(error)) {\n this.error(broadcastTimeoutMessage());\n }\n this.error(makeDaemonConnectionError(error));\n }\n\n if (isJsonRpcFailure(response)) {\n this.error(formatJsonRpcError(response.error));\n }\n\n const { result } = response;\n if (!is(result, struct)) {\n this.error(\n `The daemon returned an unexpected ${broadcast ? 'send' : 'dry-run'} ` +\n 'result. It may be running an incompatible version.',\n );\n }\n return result;\n }\n}\n\n/**\n * Whether an error is the daemon-client socket read timeout. It carries no\n * errno code, so its message is the only signal.\n *\n * @param error - The caught value.\n * @returns True if it is the read timeout.\n */\nfunction isReadTimeout(error: unknown): boolean {\n return error instanceof Error && error.message === 'Socket read timed out';\n}\n\n/**\n * The message shown when the broadcast call times out. A send is not\n * idempotent, and the daemon may still be broadcasting after the client gives\n * up waiting, so this warns against a blind re-run rather than reading as a\n * plain connection failure.\n *\n * @returns The user-facing guidance.\n */\nfunction broadcastTimeoutMessage(): string {\n return (\n 'The daemon did not respond in time, but your transaction may still be ' +\n 'broadcasting. Do NOT re-run this command — you could send it twice. ' +\n 'Check `mm daemon status`, the daemon log, or your account on-chain to ' +\n 'confirm, then use --timeout to wait longer if needed.'\n );\n}\n\n/**\n * Format a dry-run plan for display (and as the confirmation prompt body).\n *\n * @param plan - The dry-run result returned by the daemon.\n * @param etherAmount - The original `--value` (ether), shown alongside the\n * resolved wei so the user sees the human amount they typed.\n * @param extras - The `--data` / gas overrides the daemon does not echo back,\n * shown so the user confirms exactly what will be sent.\n * @returns A multi-line summary of the transaction to be sent.\n */\nfunction formatPlan(\n plan: SendTransactionDryRunResult,\n etherAmount: string,\n extras: PlanExtras,\n): string {\n const lines = [\n 'About to send:',\n ` To: ${plan.to}`,\n ` From: ${plan.from}`,\n ` Value: ${etherAmount} ETH (${plan.value} wei)`,\n ` Network: ${plan.networkClientId}`,\n ];\n if (extras.data) {\n lines.push(` Data: ${extras.data}`);\n }\n const gasParts = [\n extras.gas ? `gas=${extras.gas}` : undefined,\n extras.maxFeePerGas ? `maxFeePerGas=${extras.maxFeePerGas}` : undefined,\n extras.maxPriorityFeePerGas\n ? `maxPriorityFeePerGas=${extras.maxPriorityFeePerGas}`\n : undefined,\n extras.gasPrice ? `gasPrice=${extras.gasPrice}` : undefined,\n ].filter((part): part is string => part !== undefined);\n if (gasParts.length > 0) {\n lines.push(` Gas: ${gasParts.join(', ')}`);\n }\n return lines.join('\\n');\n}\n"]}
@@ -0,0 +1,42 @@
1
+ import type { Hex } from "@metamask/utils";
2
+ import { Command } from "@oclif/core";
3
+ /**
4
+ * Convert a decimal ether amount to a `0x`-prefixed wei quantity.
5
+ *
6
+ * The fixed-point conversion is delegated to `@metamask/utils`' `toWei` rather
7
+ * than re-deriving the wei math here. The daemon's `sendTransaction` boundary
8
+ * expects canonical hex wei; this is where the human-friendly `--value` (ether)
9
+ * is turned into it.
10
+ *
11
+ * `toWei` silently accepts negatives and throws opaque messages on other
12
+ * malformed input, so the value is guarded up front for a clear error and a
13
+ * strictly non-negative amount.
14
+ *
15
+ * @param amount - A non-negative decimal ether amount (e.g. `"0.1"`, `"1"`).
16
+ * @returns The equivalent wei as a `0x`-prefixed hex string.
17
+ * @throws If `amount` is not a non-negative decimal or has more than 18
18
+ * fractional digits.
19
+ */
20
+ export declare function parseEtherToWeiHex(amount: string): Hex;
21
+ export default class WalletSend extends Command {
22
+ #private;
23
+ static description: string;
24
+ static examples: string[];
25
+ static flags: {
26
+ to: import("@oclif/core/interfaces").OptionFlag<string, import("@oclif/core/interfaces").CustomOptions>;
27
+ value: import("@oclif/core/interfaces").OptionFlag<string, import("@oclif/core/interfaces").CustomOptions>;
28
+ from: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
29
+ data: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
30
+ 'network-client-id': import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
31
+ 'chain-id': import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
32
+ gas: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
33
+ 'max-fee-per-gas': import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
34
+ 'max-priority-fee-per-gas': import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
35
+ 'gas-price': import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
36
+ 'dry-run': import("@oclif/core/interfaces").BooleanFlag<boolean>;
37
+ yes: import("@oclif/core/interfaces").BooleanFlag<boolean>;
38
+ timeout: import("@oclif/core/interfaces").OptionFlag<number | undefined, import("@oclif/core/interfaces").CustomOptions>;
39
+ };
40
+ run(): Promise<void>;
41
+ }
42
+ //# sourceMappingURL=send.d.cts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"send.d.cts","sourceRoot":"","sources":["../../../src/commands/wallet/send.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,GAAG,EAAuB,wBAAwB;AAChE,OAAO,EAAE,OAAO,EAAS,oBAAoB;AAwC7C;;;;;;;;;;;;;;;;GAgBG;AACH,wBAAgB,kBAAkB,CAAC,MAAM,EAAE,MAAM,GAAG,GAAG,CAetD;AAED,MAAM,CAAC,OAAO,OAAO,UAAW,SAAQ,OAAO;;IAC7C,OAAgB,WAAW,SAIgB;IAE3C,OAAgB,QAAQ,WAItB;IAEF,OAAgB,KAAK;;;;;;;;;;;;;;MAkDnB;IAEW,GAAG,IAAI,OAAO,CAAC,IAAI,CAAC;CAgLlC"}
@@ -0,0 +1,42 @@
1
+ import type { Hex } from "@metamask/utils";
2
+ import { Command } from "@oclif/core";
3
+ /**
4
+ * Convert a decimal ether amount to a `0x`-prefixed wei quantity.
5
+ *
6
+ * The fixed-point conversion is delegated to `@metamask/utils`' `toWei` rather
7
+ * than re-deriving the wei math here. The daemon's `sendTransaction` boundary
8
+ * expects canonical hex wei; this is where the human-friendly `--value` (ether)
9
+ * is turned into it.
10
+ *
11
+ * `toWei` silently accepts negatives and throws opaque messages on other
12
+ * malformed input, so the value is guarded up front for a clear error and a
13
+ * strictly non-negative amount.
14
+ *
15
+ * @param amount - A non-negative decimal ether amount (e.g. `"0.1"`, `"1"`).
16
+ * @returns The equivalent wei as a `0x`-prefixed hex string.
17
+ * @throws If `amount` is not a non-negative decimal or has more than 18
18
+ * fractional digits.
19
+ */
20
+ export declare function parseEtherToWeiHex(amount: string): Hex;
21
+ export default class WalletSend extends Command {
22
+ #private;
23
+ static description: string;
24
+ static examples: string[];
25
+ static flags: {
26
+ to: import("@oclif/core/interfaces").OptionFlag<string, import("@oclif/core/interfaces").CustomOptions>;
27
+ value: import("@oclif/core/interfaces").OptionFlag<string, import("@oclif/core/interfaces").CustomOptions>;
28
+ from: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
29
+ data: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
30
+ 'network-client-id': import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
31
+ 'chain-id': import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
32
+ gas: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
33
+ 'max-fee-per-gas': import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
34
+ 'max-priority-fee-per-gas': import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
35
+ 'gas-price': import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
36
+ 'dry-run': import("@oclif/core/interfaces").BooleanFlag<boolean>;
37
+ yes: import("@oclif/core/interfaces").BooleanFlag<boolean>;
38
+ timeout: import("@oclif/core/interfaces").OptionFlag<number | undefined, import("@oclif/core/interfaces").CustomOptions>;
39
+ };
40
+ run(): Promise<void>;
41
+ }
42
+ //# sourceMappingURL=send.d.mts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"send.d.mts","sourceRoot":"","sources":["../../../src/commands/wallet/send.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,GAAG,EAAuB,wBAAwB;AAChE,OAAO,EAAE,OAAO,EAAS,oBAAoB;AAwC7C;;;;;;;;;;;;;;;;GAgBG;AACH,wBAAgB,kBAAkB,CAAC,MAAM,EAAE,MAAM,GAAG,GAAG,CAetD;AAED,MAAM,CAAC,OAAO,OAAO,UAAW,SAAQ,OAAO;;IAC7C,OAAgB,WAAW,SAIgB;IAE3C,OAAgB,QAAQ,WAItB;IAEF,OAAgB,KAAK;;;;;;;;;;;;;;MAkDnB;IAEW,GAAG,IAAI,OAAO,CAAC,IAAI,CAAC;CAgLlC"}