@wlfi-agent/cli 1.4.16 → 1.4.18
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/Cargo.lock +26 -20
- package/Cargo.toml +1 -1
- package/README.md +61 -28
- package/crates/vault-cli-admin/src/io_utils.rs +149 -1
- package/crates/vault-cli-admin/src/main.rs +639 -16
- package/crates/vault-cli-admin/src/shared_config.rs +18 -18
- package/crates/vault-cli-admin/src/tui/token_rpc.rs +190 -3
- package/crates/vault-cli-admin/src/tui/utils.rs +59 -0
- package/crates/vault-cli-admin/src/tui.rs +1205 -120
- package/crates/vault-cli-agent/Cargo.toml +1 -0
- package/crates/vault-cli-agent/src/io_utils.rs +163 -2
- package/crates/vault-cli-agent/src/main.rs +648 -32
- package/crates/vault-cli-daemon/Cargo.toml +4 -0
- package/crates/vault-cli-daemon/src/main.rs +617 -67
- package/crates/vault-cli-daemon/src/relay_sync.rs +776 -4
- package/crates/vault-cli-daemon/tests/system_keychain_helper_acl.rs +5 -0
- package/crates/vault-daemon/src/daemon_parts/api_impl_and_utils.rs +32 -1
- package/crates/vault-daemon/src/persistence.rs +637 -100
- package/crates/vault-daemon/src/tests.rs +1013 -3
- package/crates/vault-daemon/src/tests_parts/part2.rs +99 -0
- package/crates/vault-daemon/src/tests_parts/part4.rs +11 -7
- package/crates/vault-domain/src/nonce.rs +4 -0
- package/crates/vault-domain/src/tests.rs +616 -0
- package/crates/vault-policy/src/engine.rs +55 -32
- package/crates/vault-policy/src/tests.rs +195 -0
- package/crates/vault-sdk-agent/src/lib.rs +415 -22
- package/crates/vault-signer/Cargo.toml +3 -0
- package/crates/vault-signer/src/lib.rs +266 -40
- package/crates/vault-transport-unix/src/lib.rs +653 -5
- package/crates/vault-transport-xpc/src/tests.rs +531 -3
- package/crates/vault-transport-xpc/tests/e2e_flow.rs +3 -0
- package/dist/cli.cjs +663 -190
- package/dist/cli.cjs.map +1 -1
- package/package.json +5 -2
- package/packages/cache/.turbo/turbo-build.log +53 -52
- package/packages/cache/coverage/clover.xml +529 -394
- package/packages/cache/coverage/coverage-final.json +2 -2
- package/packages/cache/coverage/index.html +21 -21
- package/packages/cache/coverage/src/client/index.html +1 -1
- package/packages/cache/coverage/src/client/index.ts.html +1 -1
- package/packages/cache/coverage/src/errors/index.html +1 -1
- package/packages/cache/coverage/src/errors/index.ts.html +12 -12
- package/packages/cache/coverage/src/index.html +1 -1
- package/packages/cache/coverage/src/index.ts.html +1 -1
- package/packages/cache/coverage/src/service/index.html +21 -21
- package/packages/cache/coverage/src/service/index.ts.html +769 -313
- package/packages/cache/dist/{chunk-QNK6GOTI.js → chunk-KC53LH5Z.js} +35 -2
- package/packages/cache/dist/chunk-KC53LH5Z.js.map +1 -0
- package/packages/cache/dist/{chunk-QF4XKEIA.cjs → chunk-UVU7VFE3.cjs} +35 -2
- package/packages/cache/dist/chunk-UVU7VFE3.cjs.map +1 -0
- package/packages/cache/dist/index.cjs +2 -2
- package/packages/cache/dist/index.js +1 -1
- package/packages/cache/dist/service/index.cjs +2 -2
- package/packages/cache/dist/service/index.js +1 -1
- package/packages/cache/node_modules/.bin/tsc +2 -2
- package/packages/cache/node_modules/.bin/tsserver +2 -2
- package/packages/cache/node_modules/.bin/tsup +2 -2
- package/packages/cache/node_modules/.bin/tsup-node +2 -2
- package/packages/cache/node_modules/.bin/vitest +4 -4
- package/packages/cache/node_modules/.vite/vitest/da39a3ee5e6b4b0d3255bfef95601890afd80709/results.json +1 -1
- package/packages/cache/src/service/index.test.ts +165 -19
- package/packages/cache/src/service/index.ts +38 -1
- package/packages/config/.turbo/turbo-build.log +18 -17
- package/packages/config/dist/index.cjs +0 -17
- package/packages/config/dist/index.cjs.map +1 -1
- package/packages/config/src/index.ts +0 -17
- package/packages/rpc/.turbo/turbo-build.log +32 -31
- package/packages/rpc/dist/index.cjs +0 -17
- package/packages/rpc/dist/index.cjs.map +1 -1
- package/packages/rpc/src/index.js +1 -0
- package/packages/ui/.turbo/turbo-build.log +44 -43
- package/packages/ui/dist/components/badge.d.ts +1 -1
- package/packages/ui/dist/components/button.d.ts +1 -1
- package/packages/ui/node_modules/.bin/tsc +2 -2
- package/packages/ui/node_modules/.bin/tsserver +2 -2
- package/packages/ui/node_modules/.bin/tsup +2 -2
- package/packages/ui/node_modules/.bin/tsup-node +2 -2
- package/scripts/install-cli-launcher.mjs +37 -0
- package/scripts/install-rust-binaries.mjs +112 -0
- package/scripts/run-tests-isolated.mjs +210 -0
- package/src/cli.ts +310 -50
- package/src/lib/admin-reset.ts +15 -30
- package/src/lib/admin-setup.ts +246 -55
- package/src/lib/agent-auth-migrate.ts +5 -1
- package/src/lib/asset-broadcast.ts +15 -4
- package/src/lib/config-amounts.ts +6 -4
- package/src/lib/hidden-tty-prompt.js +1 -0
- package/src/lib/hidden-tty-prompt.ts +105 -0
- package/src/lib/keychain.ts +1 -0
- package/src/lib/local-admin-access.ts +4 -29
- package/src/lib/rust.ts +129 -33
- package/src/lib/signed-tx.ts +1 -0
- package/src/lib/sudo.ts +15 -5
- package/src/lib/wallet-profile.ts +3 -0
- package/src/lib/wallet-setup.ts +52 -0
- package/packages/cache/dist/chunk-QF4XKEIA.cjs.map +0 -1
- 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.
|
|
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
|
|
192
|
+
function renderManualApprovalRequired(
|
|
193
|
+
output: RustManualApprovalRequiredOutput,
|
|
194
|
+
asJson: boolean,
|
|
195
|
+
): string {
|
|
183
196
|
if (asJson) {
|
|
184
|
-
|
|
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
|
-
|
|
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
|
|
565
|
+
async function runAgentCommandJsonOnce<T>(input: {
|
|
514
566
|
commandArgs: string[];
|
|
515
|
-
auth: AgentCommandAuthOptions;
|
|
516
567
|
config: WlfiConfig;
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
input.config,
|
|
522
|
-
);
|
|
523
|
-
|
|
568
|
+
agentKeyId: string;
|
|
569
|
+
agentAuthToken: string;
|
|
570
|
+
daemonSocket: string;
|
|
571
|
+
}): Promise<AgentCommandAttemptResult<T>> {
|
|
524
572
|
try {
|
|
525
|
-
return
|
|
526
|
-
'
|
|
527
|
-
|
|
528
|
-
'
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
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
|
-
|
|
549
|
-
|
|
550
|
-
|
|
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
|
-
|
|
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'
|
|
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'
|
|
1743
|
-
.option('--tx-type <type>', 'Typed tx value', '
|
|
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
|
|
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(
|
|
1992
|
+
String(nonce),
|
|
1755
1993
|
'--to',
|
|
1756
|
-
|
|
1994
|
+
to,
|
|
1757
1995
|
'--value-wei',
|
|
1758
|
-
|
|
1996
|
+
valueWei.toString(),
|
|
1759
1997
|
'--data-hex',
|
|
1760
|
-
|
|
1998
|
+
dataHex,
|
|
1761
1999
|
'--gas-limit',
|
|
1762
|
-
|
|
2000
|
+
gasLimit.toString(),
|
|
1763
2001
|
'--max-fee-per-gas-wei',
|
|
1764
|
-
|
|
2002
|
+
maxFeePerGasWei.toString(),
|
|
1765
2003
|
'--max-priority-fee-per-gas-wei',
|
|
1766
|
-
|
|
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 (
|
|
1776
|
-
|
|
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:
|
|
2346
|
+
allowHigherNonce: false,
|
|
2087
2347
|
value: valueWei,
|
|
2088
2348
|
data: dataHex,
|
|
2089
2349
|
gasLimit,
|
package/src/lib/admin-reset.ts
CHANGED
|
@@ -24,6 +24,7 @@ import {
|
|
|
24
24
|
LAUNCHD_UNINSTALL_SCRIPT_NAME,
|
|
25
25
|
resolveLaunchDaemonHelperScriptPath,
|
|
26
26
|
} from './launchd-assets.js';
|
|
27
|
+
import { promptHiddenTty } from './hidden-tty-prompt.js';
|
|
27
28
|
import { createSudoSession } from './sudo.js';
|
|
28
29
|
|
|
29
30
|
const DEFAULT_LAUNCH_DAEMON_LABEL = 'com.wlfi.agent.daemon';
|
|
@@ -134,32 +135,7 @@ function validateSecret(value: string, label: string): string {
|
|
|
134
135
|
}
|
|
135
136
|
|
|
136
137
|
async function promptHidden(query: string, label: string): Promise<string> {
|
|
137
|
-
|
|
138
|
-
throw new Error(`${label} is required; rerun on a local TTY`);
|
|
139
|
-
}
|
|
140
|
-
|
|
141
|
-
const rl = readline.createInterface({
|
|
142
|
-
input: process.stdin,
|
|
143
|
-
output: process.stdout,
|
|
144
|
-
terminal: true,
|
|
145
|
-
}) as readline.Interface & { stdoutMuted?: boolean; _writeToOutput?: (value: string) => void };
|
|
146
|
-
|
|
147
|
-
rl.stdoutMuted = true;
|
|
148
|
-
rl._writeToOutput = (value: string) => {
|
|
149
|
-
if (value.includes(query)) {
|
|
150
|
-
(rl as unknown as { output: NodeJS.WritableStream }).output.write(value);
|
|
151
|
-
return;
|
|
152
|
-
}
|
|
153
|
-
if (!rl.stdoutMuted) {
|
|
154
|
-
(rl as unknown as { output: NodeJS.WritableStream }).output.write(value);
|
|
155
|
-
}
|
|
156
|
-
};
|
|
157
|
-
|
|
158
|
-
const answer = await new Promise<string>((resolve) => {
|
|
159
|
-
rl.question(query, resolve);
|
|
160
|
-
});
|
|
161
|
-
rl.close();
|
|
162
|
-
process.stdout.write('\n');
|
|
138
|
+
const answer = await promptHiddenTty(query, `${label} is required; rerun on a local TTY`);
|
|
163
139
|
return validateSecret(answer, label);
|
|
164
140
|
}
|
|
165
141
|
|
|
@@ -184,8 +160,8 @@ async function promptVisible(query: string): Promise<string> {
|
|
|
184
160
|
const sudoSession = createSudoSession({
|
|
185
161
|
promptPassword: async () =>
|
|
186
162
|
await promptHidden(
|
|
187
|
-
'
|
|
188
|
-
'
|
|
163
|
+
'macOS admin password for sudo (input hidden; required to uninstall the root daemon and delete its state): ',
|
|
164
|
+
'macOS admin password for sudo',
|
|
189
165
|
),
|
|
190
166
|
});
|
|
191
167
|
|
|
@@ -241,11 +217,13 @@ function print(payload: unknown, asJson: boolean | undefined): void {
|
|
|
241
217
|
console.log(JSON.stringify(payload, null, 2));
|
|
242
218
|
return;
|
|
243
219
|
}
|
|
220
|
+
/* c8 ignore start -- this module only calls print() for JSON output; non-JSON summaries bypass this helper */
|
|
244
221
|
if (typeof payload === 'string') {
|
|
245
222
|
console.log(payload);
|
|
246
223
|
return;
|
|
247
224
|
}
|
|
248
225
|
console.dir(payload, { depth: null, colors: process.stdout.isTTY });
|
|
226
|
+
/* c8 ignore stop */
|
|
249
227
|
}
|
|
250
228
|
|
|
251
229
|
function printResetSummary(result: {
|
|
@@ -332,6 +310,7 @@ export function cleanupLocalAdminResetState(
|
|
|
332
310
|
const keychainRemoved = agentKeyId ? deleteAgentAuthTokenImpl(agentKeyId) : false;
|
|
333
311
|
|
|
334
312
|
let configDeleted = false;
|
|
313
|
+
/* c8 ignore next -- configExists implies readConfigImpl() returned an object in this module; nullish fallback is defensive */
|
|
335
314
|
let configValue: Record<string, unknown> | null = configExists ? redactConfig(currentConfig ?? {}) : null;
|
|
336
315
|
if (options.deleteConfig) {
|
|
337
316
|
if (configExists) {
|
|
@@ -483,7 +462,7 @@ async function runAdminReset(options: AdminResetOptions): Promise<void> {
|
|
|
483
462
|
|
|
484
463
|
if (!options.json && typeof process.geteuid === 'function' && process.geteuid() !== 0) {
|
|
485
464
|
process.stderr.write(
|
|
486
|
-
'
|
|
465
|
+
'macOS admin password required: reset uses sudo to uninstall the root LaunchDaemon and delete the root-managed daemon state.\n',
|
|
487
466
|
);
|
|
488
467
|
}
|
|
489
468
|
await sudoSession.prime();
|
|
@@ -502,6 +481,7 @@ async function runAdminReset(options: AdminResetOptions): Promise<void> {
|
|
|
502
481
|
keychainAccount,
|
|
503
482
|
'--delete-keychain-password',
|
|
504
483
|
]);
|
|
484
|
+
/* c8 ignore next 4 -- sudoSession.run reports command failures via exit codes; synchronous throws are defensive */
|
|
505
485
|
} catch (error) {
|
|
506
486
|
uninstallProgress.fail();
|
|
507
487
|
throw error;
|
|
@@ -524,6 +504,7 @@ async function runAdminReset(options: AdminResetOptions): Promise<void> {
|
|
|
524
504
|
'-f',
|
|
525
505
|
...managedDaemonResetArtifactPaths(),
|
|
526
506
|
]);
|
|
507
|
+
/* c8 ignore next 4 -- sudoSession.run reports command failures via exit codes; synchronous throws are defensive */
|
|
527
508
|
} catch (error) {
|
|
528
509
|
stateProgress.fail();
|
|
529
510
|
throw error;
|
|
@@ -612,9 +593,11 @@ function printUninstallSummary(result: {
|
|
|
612
593
|
result.local.agentKeyId
|
|
613
594
|
? `old agent key cleared: ${result.local.agentKeyId}`
|
|
614
595
|
: 'old agent key cleared: no configured agent key was found',
|
|
596
|
+
/* c8 ignore start -- public uninstall summary only reaches here when a WLFI home existed or config provided the helper paths */
|
|
615
597
|
result.local.wlfiHome.existed
|
|
616
598
|
? `local WLFI home removed: ${result.local.wlfiHome.path}`
|
|
617
599
|
: `local WLFI home not found: ${result.local.wlfiHome.path}`,
|
|
600
|
+
/* c8 ignore stop */
|
|
618
601
|
result.local.config.existed
|
|
619
602
|
? `config removed: ${result.local.config.path}`
|
|
620
603
|
: `config not found: ${result.local.config.path}`,
|
|
@@ -630,7 +613,7 @@ async function runAdminUninstall(options: AdminUninstallOptions): Promise<void>
|
|
|
630
613
|
|
|
631
614
|
if (!options.json && typeof process.geteuid === 'function' && process.geteuid() !== 0) {
|
|
632
615
|
process.stderr.write(
|
|
633
|
-
'
|
|
616
|
+
'macOS admin password required: uninstall uses sudo to remove the root LaunchDaemon and all managed root-owned files.\n',
|
|
634
617
|
);
|
|
635
618
|
}
|
|
636
619
|
await sudoSession.prime();
|
|
@@ -649,6 +632,7 @@ async function runAdminUninstall(options: AdminUninstallOptions): Promise<void>
|
|
|
649
632
|
keychainAccount,
|
|
650
633
|
'--delete-keychain-password',
|
|
651
634
|
]);
|
|
635
|
+
/* c8 ignore next 4 -- sudoSession.run reports command failures via exit codes; synchronous throws are defensive */
|
|
652
636
|
} catch (error) {
|
|
653
637
|
uninstallProgress.fail();
|
|
654
638
|
throw error;
|
|
@@ -673,6 +657,7 @@ async function runAdminUninstall(options: AdminUninstallOptions): Promise<void>
|
|
|
673
657
|
DEFAULT_MANAGED_STATE_DIR,
|
|
674
658
|
DEFAULT_MANAGED_LOG_DIR,
|
|
675
659
|
]);
|
|
660
|
+
/* c8 ignore next 4 -- sudoSession.run reports command failures via exit codes; synchronous throws are defensive */
|
|
676
661
|
} catch (error) {
|
|
677
662
|
rootProgress.fail();
|
|
678
663
|
throw error;
|