@wlfi-agent/cli 1.4.17 → 1.4.19

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.
Files changed (93) hide show
  1. package/Cargo.lock +5 -0
  2. package/README.md +61 -28
  3. package/crates/vault-cli-admin/src/io_utils.rs +149 -1
  4. package/crates/vault-cli-admin/src/main.rs +639 -16
  5. package/crates/vault-cli-admin/src/shared_config.rs +18 -18
  6. package/crates/vault-cli-admin/src/tui/token_rpc.rs +190 -3
  7. package/crates/vault-cli-admin/src/tui/utils.rs +59 -0
  8. package/crates/vault-cli-admin/src/tui.rs +1205 -120
  9. package/crates/vault-cli-agent/Cargo.toml +1 -0
  10. package/crates/vault-cli-agent/src/io_utils.rs +163 -2
  11. package/crates/vault-cli-agent/src/main.rs +648 -32
  12. package/crates/vault-cli-daemon/Cargo.toml +4 -0
  13. package/crates/vault-cli-daemon/src/main.rs +617 -67
  14. package/crates/vault-cli-daemon/src/relay_sync.rs +776 -4
  15. package/crates/vault-cli-daemon/tests/system_keychain_helper_acl.rs +5 -0
  16. package/crates/vault-daemon/src/daemon_parts/api_impl_and_utils.rs +32 -1
  17. package/crates/vault-daemon/src/persistence.rs +637 -100
  18. package/crates/vault-daemon/src/tests.rs +1013 -3
  19. package/crates/vault-daemon/src/tests_parts/part2.rs +99 -0
  20. package/crates/vault-daemon/src/tests_parts/part4.rs +11 -7
  21. package/crates/vault-domain/src/nonce.rs +4 -0
  22. package/crates/vault-domain/src/tests.rs +616 -0
  23. package/crates/vault-policy/src/engine.rs +55 -32
  24. package/crates/vault-policy/src/tests.rs +195 -0
  25. package/crates/vault-sdk-agent/src/lib.rs +415 -22
  26. package/crates/vault-signer/Cargo.toml +3 -0
  27. package/crates/vault-signer/src/lib.rs +266 -40
  28. package/crates/vault-transport-unix/src/lib.rs +653 -5
  29. package/crates/vault-transport-xpc/src/tests.rs +531 -3
  30. package/crates/vault-transport-xpc/tests/e2e_flow.rs +3 -0
  31. package/dist/cli.cjs +756 -194
  32. package/dist/cli.cjs.map +1 -1
  33. package/package.json +5 -2
  34. package/packages/cache/.turbo/turbo-build.log +20 -20
  35. package/packages/cache/coverage/clover.xml +529 -394
  36. package/packages/cache/coverage/coverage-final.json +2 -2
  37. package/packages/cache/coverage/index.html +21 -21
  38. package/packages/cache/coverage/src/client/index.html +1 -1
  39. package/packages/cache/coverage/src/client/index.ts.html +1 -1
  40. package/packages/cache/coverage/src/errors/index.html +1 -1
  41. package/packages/cache/coverage/src/errors/index.ts.html +12 -12
  42. package/packages/cache/coverage/src/index.html +1 -1
  43. package/packages/cache/coverage/src/index.ts.html +1 -1
  44. package/packages/cache/coverage/src/service/index.html +21 -21
  45. package/packages/cache/coverage/src/service/index.ts.html +769 -313
  46. package/packages/cache/dist/{chunk-QNK6GOTI.js → chunk-KC53LH5Z.js} +35 -2
  47. package/packages/cache/dist/chunk-KC53LH5Z.js.map +1 -0
  48. package/packages/cache/dist/{chunk-QF4XKEIA.cjs → chunk-UVU7VFE3.cjs} +35 -2
  49. package/packages/cache/dist/chunk-UVU7VFE3.cjs.map +1 -0
  50. package/packages/cache/dist/index.cjs +2 -2
  51. package/packages/cache/dist/index.js +1 -1
  52. package/packages/cache/dist/service/index.cjs +2 -2
  53. package/packages/cache/dist/service/index.js +1 -1
  54. package/packages/cache/node_modules/.bin/tsc +2 -2
  55. package/packages/cache/node_modules/.bin/tsserver +2 -2
  56. package/packages/cache/node_modules/.bin/tsup +2 -2
  57. package/packages/cache/node_modules/.bin/tsup-node +2 -2
  58. package/packages/cache/node_modules/.bin/vitest +4 -4
  59. package/packages/cache/node_modules/.vite/vitest/da39a3ee5e6b4b0d3255bfef95601890afd80709/results.json +1 -1
  60. package/packages/cache/src/service/index.test.ts +165 -19
  61. package/packages/cache/src/service/index.ts +38 -1
  62. package/packages/config/.turbo/turbo-build.log +4 -4
  63. package/packages/config/dist/index.cjs +0 -17
  64. package/packages/config/dist/index.cjs.map +1 -1
  65. package/packages/config/src/index.ts +0 -17
  66. package/packages/rpc/.turbo/turbo-build.log +11 -11
  67. package/packages/rpc/dist/index.cjs +0 -17
  68. package/packages/rpc/dist/index.cjs.map +1 -1
  69. package/packages/rpc/src/index.js +1 -0
  70. package/packages/ui/node_modules/.bin/tsc +2 -2
  71. package/packages/ui/node_modules/.bin/tsserver +2 -2
  72. package/packages/ui/node_modules/.bin/tsup +2 -2
  73. package/packages/ui/node_modules/.bin/tsup-node +2 -2
  74. package/scripts/install-cli-launcher.mjs +37 -0
  75. package/scripts/install-rust-binaries.mjs +47 -0
  76. package/scripts/run-tests-isolated.mjs +210 -0
  77. package/src/cli.ts +310 -50
  78. package/src/lib/admin-reset.ts +101 -33
  79. package/src/lib/admin-setup.ts +285 -55
  80. package/src/lib/agent-auth-migrate.ts +5 -1
  81. package/src/lib/asset-broadcast.ts +15 -4
  82. package/src/lib/config-amounts.ts +6 -4
  83. package/src/lib/hidden-tty-prompt.js +1 -0
  84. package/src/lib/hidden-tty-prompt.ts +105 -0
  85. package/src/lib/keychain.ts +1 -0
  86. package/src/lib/local-admin-access.ts +4 -29
  87. package/src/lib/rust.ts +129 -33
  88. package/src/lib/signed-tx.ts +1 -0
  89. package/src/lib/sudo.ts +15 -5
  90. package/src/lib/wallet-profile.ts +3 -0
  91. package/src/lib/wallet-setup.ts +52 -0
  92. package/packages/cache/dist/chunk-QF4XKEIA.cjs.map +0 -1
  93. package/packages/cache/dist/chunk-QNK6GOTI.js.map +0 -1
package/src/cli.ts CHANGED
@@ -1,5 +1,6 @@
1
+ import { setTimeout as sleep } from 'node:timers/promises';
1
2
  import * as configPackage from '../packages/config/src/index.js';
2
- import * as rpcPackage from '../packages/rpc/src/index.ts';
3
+ import * as rpcPackage from '../packages/rpc/src/index.js';
3
4
  import type { ChainProfile, TokenChainProfile, WlfiConfig } from '../packages/config/src/index.js';
4
5
  import { Command, Option } from 'commander';
5
6
  import { type Address, type Hex, isAddress, isHex } from 'viem';
@@ -29,6 +30,7 @@ import {
29
30
  encodeErc20ApproveData,
30
31
  encodeErc20TransferData,
31
32
  formatBroadcastedAssetOutput,
33
+ resolveEstimatedPriorityFeePerGasWei,
32
34
  resolveAssetBroadcastPlan,
33
35
  waitForOnchainReceipt,
34
36
  } from './lib/asset-broadcast.js';
@@ -151,6 +153,14 @@ interface RustManualApprovalRequiredOutput {
151
153
  cli_approval_command: string;
152
154
  }
153
155
 
156
+ type AgentCommandAttemptResult<T> =
157
+ | { type: 'success'; value: T }
158
+ | {
159
+ type: 'manualApproval';
160
+ output: RustManualApprovalRequiredOutput;
161
+ exitCode: number;
162
+ };
163
+
154
164
  function rewriteAgentAmountError(
155
165
  error: unknown,
156
166
  symbolAwareAsset: { decimals: number; symbol: string; assetId: string },
@@ -179,10 +189,12 @@ function isManualApprovalRequiredOutput(value: unknown): value is RustManualAppr
179
189
  );
180
190
  }
181
191
 
182
- function printManualApprovalRequired(output: RustManualApprovalRequiredOutput, asJson: boolean) {
192
+ function renderManualApprovalRequired(
193
+ output: RustManualApprovalRequiredOutput,
194
+ asJson: boolean,
195
+ ): string {
183
196
  if (asJson) {
184
- print(output, true);
185
- return;
197
+ return formatJson(output);
186
198
  }
187
199
 
188
200
  const lines = [
@@ -196,7 +208,37 @@ function printManualApprovalRequired(output: RustManualApprovalRequiredOutput, a
196
208
  lines.push(`Relay URL: ${output.relay_url}`);
197
209
  }
198
210
  lines.push(`CLI Approval Command: ${output.cli_approval_command}`);
199
- print(lines.join('\n'), false);
211
+ return lines.join('\n');
212
+ }
213
+
214
+ function printManualApprovalRequired(
215
+ output: RustManualApprovalRequiredOutput,
216
+ asJson: boolean,
217
+ useStderr = false,
218
+ ) {
219
+ const rendered = renderManualApprovalRequired(output, asJson);
220
+ if (useStderr) {
221
+ console.error(rendered);
222
+ return;
223
+ }
224
+ console.log(rendered);
225
+ }
226
+
227
+ function printManualApprovalWaiting(
228
+ output: RustManualApprovalRequiredOutput,
229
+ asJson: boolean,
230
+ ) {
231
+ if (asJson) {
232
+ console.error(
233
+ formatJson({
234
+ event: 'manualApprovalPending',
235
+ approvalRequestId: output.approval_request_id,
236
+ }),
237
+ );
238
+ return;
239
+ }
240
+
241
+ console.error(`Waiting for manual approval decision: ${output.approval_request_id}`);
200
242
  }
201
243
 
202
244
  const MAX_SECRET_STDIN_BYTES = 16 * 1024;
@@ -335,6 +377,16 @@ function print(payload: unknown, asJson: boolean) {
335
377
 
336
378
  const ONCHAIN_RECEIPT_TIMEOUT_MS = 30_000;
337
379
  const ONCHAIN_RECEIPT_POLL_INTERVAL_MS = 2_000;
380
+ const MANUAL_APPROVAL_POLL_INTERVAL_MS = 2_000;
381
+ const MANUAL_APPROVAL_WAIT_TIMEOUT_MS =
382
+ (() => {
383
+ const raw = process.env.WLFI_TEST_MANUAL_APPROVAL_TIMEOUT_MS;
384
+ if (!raw) {
385
+ return 5 * 60_000;
386
+ }
387
+ const parsed = Number(raw);
388
+ return Number.isFinite(parsed) && parsed > 0 ? parsed : 5 * 60_000;
389
+ })();
338
390
 
339
391
  async function reportOnchainReceiptStatus(input: {
340
392
  rpcUrl: string;
@@ -510,44 +562,45 @@ async function resolveAgentCommandContext(
510
562
  return { agentKeyId, agentAuthToken, daemonSocket };
511
563
  }
512
564
 
513
- async function runAgentCommandJson<T>(input: {
565
+ async function runAgentCommandJsonOnce<T>(input: {
514
566
  commandArgs: string[];
515
- auth: AgentCommandAuthOptions;
516
567
  config: WlfiConfig;
517
- asJson: boolean;
518
- }): Promise<T | null> {
519
- const { agentKeyId, agentAuthToken, daemonSocket } = await resolveAgentCommandContext(
520
- input.auth,
521
- input.config,
522
- );
523
-
568
+ agentKeyId: string;
569
+ agentAuthToken: string;
570
+ daemonSocket: string;
571
+ }): Promise<AgentCommandAttemptResult<T>> {
524
572
  try {
525
- return await runRustBinaryJson<T>(
526
- 'wlfi-agent-agent',
527
- [
528
- '--json',
529
- '--agent-key-id',
530
- agentKeyId,
531
- '--agent-auth-token-stdin',
532
- '--daemon-socket',
533
- daemonSocket,
534
- ...input.commandArgs,
535
- ],
536
- input.config,
537
- {
538
- stdin: `${agentAuthToken}\n`,
539
- preSuppliedSecretStdin: 'agentAuthToken',
540
- scrubSensitiveEnv: true,
541
- },
542
- );
573
+ return {
574
+ type: 'success',
575
+ value: await runRustBinaryJson<T>(
576
+ 'wlfi-agent-agent',
577
+ [
578
+ '--json',
579
+ '--agent-key-id',
580
+ input.agentKeyId,
581
+ '--agent-auth-token-stdin',
582
+ '--daemon-socket',
583
+ input.daemonSocket,
584
+ ...input.commandArgs,
585
+ ],
586
+ input.config,
587
+ {
588
+ stdin: `${input.agentAuthToken}\n`,
589
+ preSuppliedSecretStdin: 'agentAuthToken',
590
+ scrubSensitiveEnv: true,
591
+ },
592
+ ),
593
+ };
543
594
  } catch (error) {
544
595
  if (error instanceof RustBinaryExitError && error.stdout.trim()) {
545
596
  try {
546
597
  const parsed = JSON.parse(error.stdout) as unknown;
547
598
  if (isManualApprovalRequiredOutput(parsed)) {
548
- printManualApprovalRequired(parsed, input.asJson);
549
- process.exitCode = error.code;
550
- return null;
599
+ return {
600
+ type: 'manualApproval',
601
+ output: parsed,
602
+ exitCode: error.code,
603
+ };
551
604
  }
552
605
  } catch {
553
606
  // fall through to the original error
@@ -557,6 +610,64 @@ async function runAgentCommandJson<T>(input: {
557
610
  }
558
611
  }
559
612
 
613
+ async function runAgentCommandJson<T>(input: {
614
+ commandArgs: string[];
615
+ auth: AgentCommandAuthOptions;
616
+ config: WlfiConfig;
617
+ asJson: boolean;
618
+ waitForManualApproval?: boolean;
619
+ }): Promise<T | null> {
620
+ const { agentKeyId, agentAuthToken, daemonSocket } = await resolveAgentCommandContext(
621
+ input.auth,
622
+ input.config,
623
+ );
624
+
625
+ const runOnce = () =>
626
+ runAgentCommandJsonOnce<T>({
627
+ commandArgs: input.commandArgs,
628
+ config: input.config,
629
+ agentKeyId,
630
+ agentAuthToken,
631
+ daemonSocket,
632
+ });
633
+
634
+ const firstAttempt = await runOnce();
635
+ if (firstAttempt.type === 'success') {
636
+ return firstAttempt.value;
637
+ }
638
+
639
+ if (!input.waitForManualApproval) {
640
+ printManualApprovalRequired(firstAttempt.output, input.asJson);
641
+ process.exitCode = firstAttempt.exitCode;
642
+ return null;
643
+ }
644
+
645
+ printManualApprovalRequired(firstAttempt.output, input.asJson, true);
646
+ printManualApprovalWaiting(firstAttempt.output, input.asJson);
647
+ const pendingApprovalRequestId = firstAttempt.output.approval_request_id;
648
+ const startedWaitingAt = Date.now();
649
+
650
+ for (;;) {
651
+ await sleep(MANUAL_APPROVAL_POLL_INTERVAL_MS);
652
+ const nextAttempt = await runOnce();
653
+ if (nextAttempt.type === 'success') {
654
+ return nextAttempt.value;
655
+ }
656
+
657
+ if (nextAttempt.output.approval_request_id !== pendingApprovalRequestId) {
658
+ throw new Error(
659
+ `manual approval request changed while waiting for a decision (${pendingApprovalRequestId} -> ${nextAttempt.output.approval_request_id}); stop and rerun the command after checking the approval status.`,
660
+ );
661
+ }
662
+
663
+ if (Date.now() - startedWaitingAt >= MANUAL_APPROVAL_WAIT_TIMEOUT_MS) {
664
+ throw new Error(
665
+ `Timed out after ${Math.ceil(MANUAL_APPROVAL_WAIT_TIMEOUT_MS / 1000)}s waiting for manual approval decision: ${pendingApprovalRequestId}`,
666
+ );
667
+ }
668
+ }
669
+ }
670
+
560
671
  function legacyAmountToDecimalString(value: number | undefined): string | undefined {
561
672
  if (value === undefined) {
562
673
  return undefined;
@@ -1487,6 +1598,7 @@ async function main() {
1487
1598
  auth: options,
1488
1599
  config,
1489
1600
  asJson: options.json,
1601
+ waitForManualApproval: true,
1490
1602
  });
1491
1603
  if (!signed) {
1492
1604
  return;
@@ -1550,22 +1662,128 @@ async function main() {
1550
1662
  .requiredOption('--network <name>', 'Network name')
1551
1663
  .requiredOption('--to <address>', 'Recipient address')
1552
1664
  .requiredOption('--amount <amount>', 'Transfer amount in configured native token units')
1665
+ .option('--broadcast', 'Broadcast the signed transaction through RPC', false)
1666
+ .option('--rpc-url <url>', 'Ethereum RPC URL override used only for broadcast')
1667
+ .option(
1668
+ '--from <address>',
1669
+ 'Sender address override for broadcast; defaults to configured wallet address',
1670
+ )
1671
+ .option('--nonce <nonce>', 'Explicit nonce override for broadcast')
1672
+ .option('--gas-limit <gas>', 'Gas limit override for broadcast')
1673
+ .option('--max-fee-per-gas-wei <wei>', 'Max fee per gas override for broadcast')
1674
+ .option('--max-priority-fee-per-gas-wei <wei>', 'Priority fee per gas override for broadcast')
1675
+ .option('--tx-type <type>', 'Typed tx value for broadcast', '0x02')
1676
+ .option('--no-wait', 'Do not wait up to 30s for an on-chain receipt after broadcast')
1677
+ .option(
1678
+ '--reveal-raw-tx',
1679
+ 'Include the signed raw transaction bytes in broadcast output',
1680
+ false,
1681
+ )
1682
+ .option('--reveal-signature', 'Include signer r/s/v fields in broadcast output', false)
1553
1683
  .addOption(new Option('--amount-wei <wei>').hideHelp()),
1554
1684
  ).action(async (options) => {
1555
1685
  const config = readConfig();
1556
1686
  const network = resolveCliNetworkProfile(options.network, config).chainId;
1557
1687
  const asset = resolveConfiguredNativeAsset(config, network);
1688
+ const recipient = assertAddress(options.to, 'to');
1558
1689
  const amountWei = options.amount
1559
1690
  ? parseConfiguredAmount(options.amount, asset.decimals, 'amount')
1560
1691
  : parseBigIntString(options.amountWei, 'amountWei');
1561
1692
  try {
1693
+ if (options.broadcast) {
1694
+ const plan = await resolveAssetBroadcastPlan(
1695
+ {
1696
+ rpcUrl: resolveCliRpcUrl(options.rpcUrl, options.network, config),
1697
+ chainId: network,
1698
+ from: options.from ? assertAddress(options.from, 'from') : resolveWalletAddress(config),
1699
+ to: recipient,
1700
+ valueWei: amountWei,
1701
+ dataHex: '0x',
1702
+ nonce: options.nonce ? parseIntegerString(options.nonce, 'nonce') : undefined,
1703
+ gasLimit: options.gasLimit
1704
+ ? parsePositiveBigIntString(options.gasLimit, 'gasLimit')
1705
+ : undefined,
1706
+ maxFeePerGasWei: options.maxFeePerGasWei
1707
+ ? parsePositiveBigIntString(options.maxFeePerGasWei, 'maxFeePerGasWei')
1708
+ : undefined,
1709
+ maxPriorityFeePerGasWei: options.maxPriorityFeePerGasWei
1710
+ ? parseBigIntString(options.maxPriorityFeePerGasWei, 'maxPriorityFeePerGasWei')
1711
+ : undefined,
1712
+ txType: options.txType,
1713
+ },
1714
+ {
1715
+ getChainInfo,
1716
+ assertRpcChainIdMatches,
1717
+ getNonce,
1718
+ estimateGas,
1719
+ estimateFees,
1720
+ },
1721
+ );
1722
+ const signed = await runAgentCommandJson<RustBroadcastOutput>({
1723
+ commandArgs: [
1724
+ 'broadcast',
1725
+ '--network',
1726
+ String(plan.chainId),
1727
+ '--nonce',
1728
+ String(plan.nonce),
1729
+ '--to',
1730
+ recipient,
1731
+ '--value-wei',
1732
+ amountWei.toString(),
1733
+ '--data-hex',
1734
+ '0x',
1735
+ '--gas-limit',
1736
+ plan.gasLimit.toString(),
1737
+ '--max-fee-per-gas-wei',
1738
+ plan.maxFeePerGasWei.toString(),
1739
+ '--max-priority-fee-per-gas-wei',
1740
+ plan.maxPriorityFeePerGasWei.toString(),
1741
+ '--tx-type',
1742
+ plan.txType,
1743
+ ],
1744
+ auth: options,
1745
+ config,
1746
+ asJson: options.json,
1747
+ waitForManualApproval: true,
1748
+ });
1749
+ if (!signed) {
1750
+ return;
1751
+ }
1752
+ const completed = await completeAssetBroadcast(plan, signed, {
1753
+ assertSignedBroadcastTransactionMatchesRequest,
1754
+ broadcastRawTransaction,
1755
+ });
1756
+ print(
1757
+ formatBroadcastedAssetOutput({
1758
+ command: 'transfer-native',
1759
+ counterparty: recipient,
1760
+ asset,
1761
+ signed,
1762
+ plan,
1763
+ signedNonce: completed.signedNonce,
1764
+ networkTxHash: completed.networkTxHash,
1765
+ revealRawTx: options.revealRawTx,
1766
+ revealSignature: options.revealSignature,
1767
+ }),
1768
+ options.json,
1769
+ );
1770
+ if (options.wait) {
1771
+ await reportOnchainReceiptStatus({
1772
+ rpcUrl: plan.rpcUrl,
1773
+ txHash: completed.networkTxHash,
1774
+ asJson: options.json,
1775
+ });
1776
+ }
1777
+ return;
1778
+ }
1779
+
1562
1780
  const result = await runAgentCommandJson<RustBroadcastOutput>({
1563
1781
  commandArgs: [
1564
1782
  'transfer-native',
1565
1783
  '--network',
1566
1784
  String(network),
1567
1785
  '--to',
1568
- assertAddress(options.to, 'to'),
1786
+ recipient,
1569
1787
  '--amount-wei',
1570
1788
  amountWei.toString(),
1571
1789
  ],
@@ -1672,6 +1890,7 @@ async function main() {
1672
1890
  auth: options,
1673
1891
  config,
1674
1892
  asJson: options.json,
1893
+ waitForManualApproval: true,
1675
1894
  });
1676
1895
  if (!signed) {
1677
1896
  return;
@@ -1736,34 +1955,53 @@ async function main() {
1736
1955
  .requiredOption('--to <address>', 'Recipient or target contract')
1737
1956
  .requiredOption('--gas-limit <gas>', 'Gas limit')
1738
1957
  .requiredOption('--max-fee-per-gas-wei <wei>', 'Max fee per gas in wei')
1739
- .option('--nonce <nonce>', 'Explicit nonce override', '0')
1958
+ .option('--nonce <nonce>', 'Explicit nonce override')
1740
1959
  .option('--value-wei <wei>', 'Value in wei', '0')
1741
1960
  .option('--data-hex <hex>', 'Calldata hex', '0x')
1742
- .option('--max-priority-fee-per-gas-wei <wei>', 'Priority fee per gas in wei', '0')
1743
- .option('--tx-type <type>', 'Typed tx value', '2')
1961
+ .option('--max-priority-fee-per-gas-wei <wei>', 'Priority fee per gas in wei')
1962
+ .option('--tx-type <type>', 'Typed tx value', '0x02')
1744
1963
  .option('--delegation-enabled', 'Forward delegation flag to Rust signing request', false),
1745
1964
  ).action(async (options) => {
1746
1965
  const config = readConfig();
1747
1966
  const network = resolveCliNetworkProfile(options.network, config);
1748
- const result = await runAgentCommandJson<RustBroadcastOutput>({
1967
+ const to = assertAddress(options.to, 'to');
1968
+ const valueWei = parseBigIntString(options.valueWei, 'valueWei');
1969
+ const dataHex = assertHex(options.dataHex, 'dataHex');
1970
+ const gasLimit = parsePositiveBigIntString(options.gasLimit, 'gasLimit');
1971
+ const maxFeePerGasWei = parsePositiveBigIntString(options.maxFeePerGasWei, 'maxFeePerGasWei');
1972
+ const explicitNonce = options.nonce ? parseIntegerString(options.nonce, 'nonce') : undefined;
1973
+ const rpcUrl = resolveCliRpcUrl(undefined, options.network, config);
1974
+ const from = resolveWalletAddress(config);
1975
+ const chainInfo = await getChainInfo(rpcUrl);
1976
+ assertRpcChainIdMatches(network.chainId, chainInfo.chainId);
1977
+ const nonce = explicitNonce ?? (await getNonce(rpcUrl, from));
1978
+ const fees = options.maxPriorityFeePerGasWei ? null : await estimateFees(rpcUrl);
1979
+ const maxPriorityFeePerGasWei = options.maxPriorityFeePerGasWei
1980
+ ? parseBigIntString(options.maxPriorityFeePerGasWei, 'maxPriorityFeePerGasWei')
1981
+ : resolveEstimatedPriorityFeePerGasWei({
1982
+ gasPrice: fees?.gasPrice ?? null,
1983
+ maxFeePerGas: fees?.maxFeePerGas ?? null,
1984
+ maxPriorityFeePerGas: fees?.maxPriorityFeePerGas ?? null,
1985
+ });
1986
+ const signed = await runAgentCommandJson<RustBroadcastOutput>({
1749
1987
  commandArgs: [
1750
1988
  'broadcast',
1751
1989
  '--network',
1752
1990
  String(network.chainId),
1753
1991
  '--nonce',
1754
- String(parseIntegerString(options.nonce, 'nonce')),
1992
+ String(nonce),
1755
1993
  '--to',
1756
- assertAddress(options.to, 'to'),
1994
+ to,
1757
1995
  '--value-wei',
1758
- parseBigIntString(options.valueWei, 'valueWei').toString(),
1996
+ valueWei.toString(),
1759
1997
  '--data-hex',
1760
- assertHex(options.dataHex, 'dataHex'),
1998
+ dataHex,
1761
1999
  '--gas-limit',
1762
- parsePositiveBigIntString(options.gasLimit, 'gasLimit').toString(),
2000
+ gasLimit.toString(),
1763
2001
  '--max-fee-per-gas-wei',
1764
- parsePositiveBigIntString(options.maxFeePerGasWei, 'maxFeePerGasWei').toString(),
2002
+ maxFeePerGasWei.toString(),
1765
2003
  '--max-priority-fee-per-gas-wei',
1766
- parseBigIntString(options.maxPriorityFeePerGasWei, 'maxPriorityFeePerGasWei').toString(),
2004
+ maxPriorityFeePerGasWei.toString(),
1767
2005
  '--tx-type',
1768
2006
  options.txType,
1769
2007
  ...(options.delegationEnabled ? ['--delegation-enabled'] : []),
@@ -1772,9 +2010,31 @@ async function main() {
1772
2010
  config,
1773
2011
  asJson: options.json,
1774
2012
  });
1775
- if (result) {
1776
- print(result, options.json);
2013
+ if (!signed) {
2014
+ return;
1777
2015
  }
2016
+
2017
+ if (!signed.raw_tx_hex) {
2018
+ throw new Error('Rust agent did not return raw_tx_hex for broadcast signing');
2019
+ }
2020
+
2021
+ await assertSignedBroadcastTransactionMatchesRequest({
2022
+ rawTxHex: signed.raw_tx_hex as Hex,
2023
+ from,
2024
+ to,
2025
+ chainId: network.chainId,
2026
+ nonce,
2027
+ allowHigherNonce: false,
2028
+ value: valueWei,
2029
+ data: dataHex,
2030
+ gasLimit,
2031
+ maxFeePerGas: maxFeePerGasWei,
2032
+ maxPriorityFeePerGas: maxPriorityFeePerGasWei,
2033
+ txType: options.txType,
2034
+ });
2035
+
2036
+ await broadcastRawTransaction(rpcUrl, signed.raw_tx_hex as Hex);
2037
+ print(signed, options.json);
1778
2038
  });
1779
2039
 
1780
2040
  const rpc = program.command('rpc').description('RPC methods implemented in TypeScript');
@@ -2083,7 +2343,7 @@ async function main() {
2083
2343
  to,
2084
2344
  chainId,
2085
2345
  nonce,
2086
- allowHigherNonce: true,
2346
+ allowHigherNonce: false,
2087
2347
  value: valueWei,
2088
2348
  data: dataHex,
2089
2349
  gasLimit,