@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/dist/cli.cjs
CHANGED
|
@@ -12981,6 +12981,9 @@ var require_commander = __commonJS({
|
|
|
12981
12981
|
}
|
|
12982
12982
|
});
|
|
12983
12983
|
|
|
12984
|
+
// src/cli.ts
|
|
12985
|
+
var import_promises2 = require("timers/promises");
|
|
12986
|
+
|
|
12984
12987
|
// packages/config/src/index.js
|
|
12985
12988
|
var src_exports = {};
|
|
12986
12989
|
__export(src_exports, {
|
|
@@ -13069,23 +13072,6 @@ var BUILTIN_TOKENS = {
|
|
|
13069
13072
|
arbitrum: { chainId: 42161, isNative: true, decimals: 18 }
|
|
13070
13073
|
}
|
|
13071
13074
|
},
|
|
13072
|
-
usdc: {
|
|
13073
|
-
symbol: "USDC",
|
|
13074
|
-
chains: {
|
|
13075
|
-
ethereum: {
|
|
13076
|
-
chainId: 1,
|
|
13077
|
-
isNative: false,
|
|
13078
|
-
address: "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48",
|
|
13079
|
-
decimals: 6
|
|
13080
|
-
},
|
|
13081
|
-
base: {
|
|
13082
|
-
chainId: 8453,
|
|
13083
|
-
isNative: false,
|
|
13084
|
-
address: "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913",
|
|
13085
|
-
decimals: 6
|
|
13086
|
-
}
|
|
13087
|
-
}
|
|
13088
|
-
},
|
|
13089
13075
|
usd1: {
|
|
13090
13076
|
name: "USD1",
|
|
13091
13077
|
symbol: "USD1",
|
|
@@ -14019,7 +14005,7 @@ function resolveRustBinaryPath(binaryName, config = readConfig()) {
|
|
|
14019
14005
|
return import_node_path.default.join(config.rustBinDir ?? defaultRustBinDir(), binaryName + (process.platform === "win32" ? ".exe" : ""));
|
|
14020
14006
|
}
|
|
14021
14007
|
|
|
14022
|
-
// packages/rpc/src/index.
|
|
14008
|
+
// packages/rpc/src/index.js
|
|
14023
14009
|
var src_exports2 = {};
|
|
14024
14010
|
__export(src_exports2, {
|
|
14025
14011
|
TRANSFER_EVENT: () => TRANSFER_EVENT,
|
|
@@ -23709,6 +23695,78 @@ function resolveLaunchDaemonHelperScriptPath(scriptName, config) {
|
|
|
23709
23695
|
return candidates[0];
|
|
23710
23696
|
}
|
|
23711
23697
|
|
|
23698
|
+
// src/lib/hidden-tty-prompt.ts
|
|
23699
|
+
async function promptHiddenTty(query, nonInteractiveError, deps = {}) {
|
|
23700
|
+
const input = deps.input ?? process.stdin;
|
|
23701
|
+
const output = deps.output ?? process.stderr;
|
|
23702
|
+
if (!input.isTTY || !output.isTTY || typeof input.setRawMode !== "function") {
|
|
23703
|
+
throw new Error(nonInteractiveError);
|
|
23704
|
+
}
|
|
23705
|
+
output.write("\r\x1B[2K");
|
|
23706
|
+
output.write(query);
|
|
23707
|
+
const wasRaw = Boolean(input.isRaw);
|
|
23708
|
+
if (!wasRaw) {
|
|
23709
|
+
input.setRawMode(true);
|
|
23710
|
+
}
|
|
23711
|
+
input.resume?.();
|
|
23712
|
+
return await new Promise((resolve, reject) => {
|
|
23713
|
+
let answer = "";
|
|
23714
|
+
let settled = false;
|
|
23715
|
+
const cleanup = () => {
|
|
23716
|
+
input.removeListener("data", onData);
|
|
23717
|
+
input.removeListener("error", onError);
|
|
23718
|
+
if (!wasRaw) {
|
|
23719
|
+
input.setRawMode?.(false);
|
|
23720
|
+
}
|
|
23721
|
+
input.pause?.();
|
|
23722
|
+
output.write("\n");
|
|
23723
|
+
};
|
|
23724
|
+
const finish = (callback) => {
|
|
23725
|
+
if (settled) {
|
|
23726
|
+
return;
|
|
23727
|
+
}
|
|
23728
|
+
settled = true;
|
|
23729
|
+
cleanup();
|
|
23730
|
+
callback();
|
|
23731
|
+
};
|
|
23732
|
+
const onError = (error) => {
|
|
23733
|
+
finish(() => reject(error));
|
|
23734
|
+
};
|
|
23735
|
+
const onData = (chunk) => {
|
|
23736
|
+
const text = typeof chunk === "string" ? chunk : chunk.toString("utf8");
|
|
23737
|
+
if (text.includes("\x1B")) {
|
|
23738
|
+
return;
|
|
23739
|
+
}
|
|
23740
|
+
for (const char of text) {
|
|
23741
|
+
if (char === "\r" || char === "\n") {
|
|
23742
|
+
finish(() => resolve(answer));
|
|
23743
|
+
return;
|
|
23744
|
+
}
|
|
23745
|
+
if (char === "") {
|
|
23746
|
+
finish(() => reject(new Error("prompt canceled")));
|
|
23747
|
+
return;
|
|
23748
|
+
}
|
|
23749
|
+
if (char === "\x7F" || char === "\b") {
|
|
23750
|
+
const chars = Array.from(answer);
|
|
23751
|
+
chars.pop();
|
|
23752
|
+
answer = chars.join("");
|
|
23753
|
+
continue;
|
|
23754
|
+
}
|
|
23755
|
+
if (char === "") {
|
|
23756
|
+
answer = "";
|
|
23757
|
+
continue;
|
|
23758
|
+
}
|
|
23759
|
+
if (/[\u0000-\u001f\u007f]/u.test(char)) {
|
|
23760
|
+
continue;
|
|
23761
|
+
}
|
|
23762
|
+
answer += char;
|
|
23763
|
+
}
|
|
23764
|
+
};
|
|
23765
|
+
input.on("data", onData);
|
|
23766
|
+
input.on("error", onError);
|
|
23767
|
+
});
|
|
23768
|
+
}
|
|
23769
|
+
|
|
23712
23770
|
// src/lib/sudo.ts
|
|
23713
23771
|
var import_node_child_process2 = require("child_process");
|
|
23714
23772
|
function currentProcessIsRoot() {
|
|
@@ -23717,6 +23775,14 @@ function currentProcessIsRoot() {
|
|
|
23717
23775
|
function isSudoAuthenticationFailure(output) {
|
|
23718
23776
|
return /sudo: .*password is required/iu.test(output) || /sudo: .*incorrect password/iu.test(output) || /sudo: no password was provided/iu.test(output) || /sorry, try again\./iu.test(output);
|
|
23719
23777
|
}
|
|
23778
|
+
function formatSudoFailureMessage(result) {
|
|
23779
|
+
const combinedOutput = `${result.stderr}
|
|
23780
|
+
${result.stdout}`;
|
|
23781
|
+
if (isSudoAuthenticationFailure(combinedOutput)) {
|
|
23782
|
+
return "sudo authentication failed. Enter your system admin password used for sudo, not the WLFI vault password.";
|
|
23783
|
+
}
|
|
23784
|
+
return result.stderr.trim() || result.stdout.trim() || `sudo credential check failed (exit code ${result.code})`;
|
|
23785
|
+
}
|
|
23720
23786
|
async function runCommand(command, args, options, deps) {
|
|
23721
23787
|
return await new Promise((resolve, reject) => {
|
|
23722
23788
|
const spawnOptions = {
|
|
@@ -23773,9 +23839,7 @@ function createSudoSession(deps) {
|
|
|
23773
23839
|
}
|
|
23774
23840
|
);
|
|
23775
23841
|
if (result.code !== 0) {
|
|
23776
|
-
throw new Error(
|
|
23777
|
-
result.stderr.trim() || result.stdout.trim() || `sudo credential check failed (exit code ${result.code})`
|
|
23778
|
-
);
|
|
23842
|
+
throw new Error(formatSudoFailureMessage(result));
|
|
23779
23843
|
}
|
|
23780
23844
|
primed = true;
|
|
23781
23845
|
}
|
|
@@ -23853,29 +23917,7 @@ function validateSecret(value, label) {
|
|
|
23853
23917
|
return value;
|
|
23854
23918
|
}
|
|
23855
23919
|
async function promptHidden(query, label) {
|
|
23856
|
-
|
|
23857
|
-
throw new Error(`${label} is required; rerun on a local TTY`);
|
|
23858
|
-
}
|
|
23859
|
-
const rl = import_node_readline.default.createInterface({
|
|
23860
|
-
input: process.stdin,
|
|
23861
|
-
output: process.stdout,
|
|
23862
|
-
terminal: true
|
|
23863
|
-
});
|
|
23864
|
-
rl.stdoutMuted = true;
|
|
23865
|
-
rl._writeToOutput = (value) => {
|
|
23866
|
-
if (value.includes(query)) {
|
|
23867
|
-
rl.output.write(value);
|
|
23868
|
-
return;
|
|
23869
|
-
}
|
|
23870
|
-
if (!rl.stdoutMuted) {
|
|
23871
|
-
rl.output.write(value);
|
|
23872
|
-
}
|
|
23873
|
-
};
|
|
23874
|
-
const answer = await new Promise((resolve) => {
|
|
23875
|
-
rl.question(query, resolve);
|
|
23876
|
-
});
|
|
23877
|
-
rl.close();
|
|
23878
|
-
process.stdout.write("\n");
|
|
23920
|
+
const answer = await promptHiddenTty(query, `${label} is required; rerun on a local TTY`);
|
|
23879
23921
|
return validateSecret(answer, label);
|
|
23880
23922
|
}
|
|
23881
23923
|
async function promptVisible(query) {
|
|
@@ -23895,8 +23937,8 @@ async function promptVisible(query) {
|
|
|
23895
23937
|
}
|
|
23896
23938
|
var sudoSession = createSudoSession({
|
|
23897
23939
|
promptPassword: async () => await promptHidden(
|
|
23898
|
-
"
|
|
23899
|
-
"
|
|
23940
|
+
"macOS admin password for sudo (input hidden; required to uninstall the root daemon and delete its state): ",
|
|
23941
|
+
"macOS admin password for sudo"
|
|
23900
23942
|
)
|
|
23901
23943
|
});
|
|
23902
23944
|
function createProgress(message, enabled = true) {
|
|
@@ -24155,7 +24197,7 @@ async function runAdminReset(options) {
|
|
|
24155
24197
|
const keychainAccount = import_node_os2.default.userInfo().username;
|
|
24156
24198
|
if (!options.json && typeof process.geteuid === "function" && process.geteuid() !== 0) {
|
|
24157
24199
|
process.stderr.write(
|
|
24158
|
-
"
|
|
24200
|
+
"macOS admin password required: reset uses sudo to uninstall the root LaunchDaemon and delete the root-managed daemon state.\n"
|
|
24159
24201
|
);
|
|
24160
24202
|
}
|
|
24161
24203
|
await sudoSession.prime();
|
|
@@ -24261,7 +24303,9 @@ function printUninstallSummary(result) {
|
|
|
24261
24303
|
`managed state directory removed: ${result.daemon.stateDir}`,
|
|
24262
24304
|
`managed log directory removed: ${result.daemon.logDir}`,
|
|
24263
24305
|
result.local.agentKeyId ? `old agent key cleared: ${result.local.agentKeyId}` : "old agent key cleared: no configured agent key was found",
|
|
24306
|
+
/* c8 ignore start -- public uninstall summary only reaches here when a WLFI home existed or config provided the helper paths */
|
|
24264
24307
|
result.local.wlfiHome.existed ? `local WLFI home removed: ${result.local.wlfiHome.path}` : `local WLFI home not found: ${result.local.wlfiHome.path}`,
|
|
24308
|
+
/* c8 ignore stop */
|
|
24265
24309
|
result.local.config.existed ? `config removed: ${result.local.config.path}` : `config not found: ${result.local.config.path}`,
|
|
24266
24310
|
"next: reinstall with `wlfi-agent admin setup` only if you want a fresh managed wallet again"
|
|
24267
24311
|
];
|
|
@@ -24273,7 +24317,7 @@ async function runAdminUninstall(options) {
|
|
|
24273
24317
|
const keychainAccount = import_node_os2.default.userInfo().username;
|
|
24274
24318
|
if (!options.json && typeof process.geteuid === "function" && process.geteuid() !== 0) {
|
|
24275
24319
|
process.stderr.write(
|
|
24276
|
-
"
|
|
24320
|
+
"macOS admin password required: uninstall uses sudo to remove the root LaunchDaemon and all managed root-owned files.\n"
|
|
24277
24321
|
);
|
|
24278
24322
|
}
|
|
24279
24323
|
await sudoSession.prime();
|
|
@@ -24935,6 +24979,7 @@ async function prepareSpawnOptions(binaryName, args, options) {
|
|
|
24935
24979
|
|
|
24936
24980
|
// src/lib/rust.ts
|
|
24937
24981
|
var { readConfig: readConfig2, resolveRustBinaryPath: resolveRustBinaryPath2 } = src_exports;
|
|
24982
|
+
var MAX_STDOUT_SNIPPET_CHARS = 200;
|
|
24938
24983
|
var RustBinaryExitError = class extends Error {
|
|
24939
24984
|
binaryName;
|
|
24940
24985
|
code;
|
|
@@ -24949,6 +24994,20 @@ var RustBinaryExitError = class extends Error {
|
|
|
24949
24994
|
this.stderr = stderr;
|
|
24950
24995
|
}
|
|
24951
24996
|
};
|
|
24997
|
+
var RustBinaryJsonParseError = class extends Error {
|
|
24998
|
+
binaryName;
|
|
24999
|
+
stdout;
|
|
25000
|
+
cause;
|
|
25001
|
+
constructor(binaryName, stdout, cause) {
|
|
25002
|
+
const snippet = stdout.length > MAX_STDOUT_SNIPPET_CHARS ? `${stdout.slice(0, MAX_STDOUT_SNIPPET_CHARS)}...` : stdout;
|
|
25003
|
+
const message = cause instanceof Error ? `${binaryName} produced invalid JSON: ${cause.message}. stdout: ${snippet}` : `${binaryName} produced invalid JSON. stdout: ${snippet}`;
|
|
25004
|
+
super(message);
|
|
25005
|
+
this.name = "RustBinaryJsonParseError";
|
|
25006
|
+
this.binaryName = binaryName;
|
|
25007
|
+
this.stdout = stdout;
|
|
25008
|
+
this.cause = cause;
|
|
25009
|
+
}
|
|
25010
|
+
};
|
|
24952
25011
|
function ensureBinary(binaryName, config) {
|
|
24953
25012
|
const resolved = resolveRustBinaryPath2(binaryName, config ?? readConfig2());
|
|
24954
25013
|
if (!import_node_fs7.default.existsSync(resolved)) {
|
|
@@ -24964,6 +25023,37 @@ function forwardedArgsIncludeDaemonSocket(args) {
|
|
|
24964
25023
|
(arg, _index) => arg === "--daemon-socket" || arg.startsWith("--daemon-socket=")
|
|
24965
25024
|
);
|
|
24966
25025
|
}
|
|
25026
|
+
async function writeChildStdin(child, stdin) {
|
|
25027
|
+
const stream = child.stdin;
|
|
25028
|
+
if (!stream) {
|
|
25029
|
+
return;
|
|
25030
|
+
}
|
|
25031
|
+
await new Promise((resolve, reject) => {
|
|
25032
|
+
let settled = false;
|
|
25033
|
+
const finish = () => {
|
|
25034
|
+
if (settled) {
|
|
25035
|
+
return;
|
|
25036
|
+
}
|
|
25037
|
+
settled = true;
|
|
25038
|
+
stream.off("error", handleError);
|
|
25039
|
+
resolve();
|
|
25040
|
+
};
|
|
25041
|
+
const handleError = (error) => {
|
|
25042
|
+
if (settled) {
|
|
25043
|
+
return;
|
|
25044
|
+
}
|
|
25045
|
+
if (error?.code === "EPIPE" || error?.code === "ERR_STREAM_DESTROYED") {
|
|
25046
|
+
finish();
|
|
25047
|
+
return;
|
|
25048
|
+
}
|
|
25049
|
+
settled = true;
|
|
25050
|
+
stream.off("error", handleError);
|
|
25051
|
+
reject(error);
|
|
25052
|
+
};
|
|
25053
|
+
stream.on("error", handleError);
|
|
25054
|
+
stream.end(stdin, finish);
|
|
25055
|
+
});
|
|
25056
|
+
}
|
|
24967
25057
|
async function passthroughRustBinary(binaryName, args, config) {
|
|
24968
25058
|
const resolvedConfig = config ?? readConfig2();
|
|
24969
25059
|
const resolvedDaemonSocket = resolveValidatedPassthroughDaemonSocket(
|
|
@@ -24974,20 +25064,25 @@ async function passthroughRustBinary(binaryName, args, config) {
|
|
|
24974
25064
|
const executable = ensureBinary(binaryName, resolvedConfig);
|
|
24975
25065
|
const prepared = await prepareSpawnOptions(binaryName, args, {});
|
|
24976
25066
|
const env = { ...prepared.env };
|
|
24977
|
-
if (resolvedDaemonSocket && !forwardedArgsIncludeDaemonSocket(args) &&
|
|
25067
|
+
if (resolvedDaemonSocket && !forwardedArgsIncludeDaemonSocket(args) && /* c8 ignore next -- explicit env override is exercised, but c8 misattributes this optional-chain/nullish check */
|
|
25068
|
+
!env.WLFI_DAEMON_SOCKET?.trim()) {
|
|
24978
25069
|
env.WLFI_DAEMON_SOCKET = resolvedDaemonSocket;
|
|
24979
25070
|
}
|
|
24980
25071
|
const child = (0, import_node_child_process3.spawn)(executable, prepared.args, {
|
|
24981
|
-
stdio: [
|
|
25072
|
+
stdio: [
|
|
25073
|
+
prepared.stdin !== void 0 ? "pipe" : "inherit",
|
|
25074
|
+
"inherit",
|
|
25075
|
+
"inherit"
|
|
25076
|
+
],
|
|
24982
25077
|
env
|
|
24983
25078
|
});
|
|
24984
|
-
|
|
24985
|
-
child.stdin?.end(prepared.stdin);
|
|
24986
|
-
}
|
|
24987
|
-
return await new Promise((resolve, reject) => {
|
|
25079
|
+
const codePromise = new Promise((resolve, reject) => {
|
|
24988
25080
|
child.on("error", reject);
|
|
24989
|
-
child.on("close", (
|
|
25081
|
+
child.on("close", (code2) => resolve(code2 ?? 1));
|
|
24990
25082
|
});
|
|
25083
|
+
const stdinPromise = prepared.stdin !== void 0 ? writeChildStdin(child, prepared.stdin) : Promise.resolve();
|
|
25084
|
+
const [code] = await Promise.all([codePromise, stdinPromise]);
|
|
25085
|
+
return code;
|
|
24991
25086
|
}
|
|
24992
25087
|
async function runRustBinary(binaryName, args, config, options = {}) {
|
|
24993
25088
|
const resolvedConfig = config ?? readConfig2();
|
|
@@ -25014,13 +25109,20 @@ async function runRustBinary(binaryName, args, config, options = {}) {
|
|
|
25014
25109
|
child.stderr?.on("data", (chunk) => {
|
|
25015
25110
|
stderr += chunk.toString();
|
|
25016
25111
|
});
|
|
25017
|
-
|
|
25018
|
-
child.stdin?.end(prepared.stdin);
|
|
25019
|
-
}
|
|
25020
|
-
const code = await new Promise((resolve, reject) => {
|
|
25112
|
+
const codePromise = new Promise((resolve, reject) => {
|
|
25021
25113
|
child.on("error", reject);
|
|
25022
|
-
child.on("close", (
|
|
25114
|
+
child.on("close", (code2, signal) => {
|
|
25115
|
+
if (code2 !== null && code2 !== void 0) {
|
|
25116
|
+
resolve(code2);
|
|
25117
|
+
} else {
|
|
25118
|
+
resolve(
|
|
25119
|
+
signal ? 128 + (require("os").constants.errno?.SIGTERM ?? 143) : 1
|
|
25120
|
+
);
|
|
25121
|
+
}
|
|
25122
|
+
});
|
|
25023
25123
|
});
|
|
25124
|
+
const stdinPromise = prepared.stdin !== void 0 ? writeChildStdin(child, prepared.stdin) : Promise.resolve();
|
|
25125
|
+
const [code] = await Promise.all([codePromise, stdinPromise]);
|
|
25024
25126
|
if (code !== 0) {
|
|
25025
25127
|
throw new RustBinaryExitError(binaryName, code, stdout, stderr);
|
|
25026
25128
|
}
|
|
@@ -25028,7 +25130,11 @@ async function runRustBinary(binaryName, args, config, options = {}) {
|
|
|
25028
25130
|
}
|
|
25029
25131
|
async function runRustBinaryJson(binaryName, args, config, options = {}) {
|
|
25030
25132
|
const { stdout } = await runRustBinary(binaryName, args, config, options);
|
|
25031
|
-
|
|
25133
|
+
try {
|
|
25134
|
+
return JSON.parse(stdout);
|
|
25135
|
+
} catch (error) {
|
|
25136
|
+
throw new RustBinaryJsonParseError(binaryName, stdout, error);
|
|
25137
|
+
}
|
|
25032
25138
|
}
|
|
25033
25139
|
|
|
25034
25140
|
// src/lib/wallet-profile.ts
|
|
@@ -25126,6 +25232,7 @@ function collectWalletBalanceTargets(config) {
|
|
|
25126
25232
|
symbol: tokenProfile.symbol,
|
|
25127
25233
|
name: tokenProfile.name,
|
|
25128
25234
|
chainKey,
|
|
25235
|
+
/* c8 ignore next -- resolveChainProfile only yields usable balance targets when a chain name is available */
|
|
25129
25236
|
chainName: resolvedChain?.name ?? chainKey,
|
|
25130
25237
|
chainId: chainProfile.chainId,
|
|
25131
25238
|
rpcUrl,
|
|
@@ -25623,6 +25730,22 @@ function resolveValidatedWalletSetupPolicyIds(values) {
|
|
|
25623
25730
|
normalizedList(values).map((value) => assertWalletSetupUuid(value, "attachPolicyId"))
|
|
25624
25731
|
);
|
|
25625
25732
|
}
|
|
25733
|
+
function resolveValidatedExistingVaultReuse(input) {
|
|
25734
|
+
const keyId = presentString4(input.existingVaultKeyId);
|
|
25735
|
+
const publicKey = presentString4(input.existingVaultPublicKey);
|
|
25736
|
+
if (!keyId && !publicKey) {
|
|
25737
|
+
return {};
|
|
25738
|
+
}
|
|
25739
|
+
if (!keyId || !publicKey) {
|
|
25740
|
+
throw new Error(
|
|
25741
|
+
"existingVaultKeyId and existingVaultPublicKey must be provided together to reuse an existing wallet"
|
|
25742
|
+
);
|
|
25743
|
+
}
|
|
25744
|
+
return {
|
|
25745
|
+
keyId: assertWalletSetupUuid(keyId, "existingVaultKeyId"),
|
|
25746
|
+
publicKey
|
|
25747
|
+
};
|
|
25748
|
+
}
|
|
25626
25749
|
function previewBootstrapOutputPath(inputPath) {
|
|
25627
25750
|
const explicitPath = presentString4(inputPath);
|
|
25628
25751
|
if (explicitPath) {
|
|
@@ -26040,7 +26163,9 @@ function formatBooleanPlanValue(value) {
|
|
|
26040
26163
|
}
|
|
26041
26164
|
function formatWalletSetupScope(plan) {
|
|
26042
26165
|
return [
|
|
26166
|
+
/* c8 ignore next -- both default-network and explicit-network renderings are exercised, but c8 misattributes this ternary under --experimental-strip-types */
|
|
26043
26167
|
`- Network: ${plan.policyScope.network === null ? "daemon default" : String(plan.policyScope.network)}`,
|
|
26168
|
+
/* c8 ignore next -- both default and explicit chain-name renderings are exercised, but c8 misattributes this nullish expression */
|
|
26044
26169
|
`- Chain Name: ${plan.policyScope.chainName ?? "daemon default"}`,
|
|
26045
26170
|
`- Recipient: ${plan.policyScope.recipient ?? "all recipients"}`,
|
|
26046
26171
|
`- Asset Mode: ${plan.policyScope.assets.mode}`,
|
|
@@ -26066,6 +26191,7 @@ function formatWalletSetupPreflight(plan) {
|
|
|
26066
26191
|
return [
|
|
26067
26192
|
`- Daemon Socket Trusted: ${formatBooleanPlanValue(plan.preflight.daemonSocketTrusted)}`,
|
|
26068
26193
|
plan.preflight.daemonSocketError ? ` ${plan.preflight.daemonSocketError}` : null,
|
|
26194
|
+
/* c8 ignore next -- both null and boolean RPC preflight states are exercised, but c8 misattributes this ternary */
|
|
26069
26195
|
`- RPC URL Trusted: ${plan.preflight.rpcUrlTrusted === null ? "not applicable" : formatBooleanPlanValue(plan.preflight.rpcUrlTrusted)}`,
|
|
26070
26196
|
plan.preflight.rpcUrlError ? ` ${plan.preflight.rpcUrlError}` : null,
|
|
26071
26197
|
`- Bootstrap Output Ready: ${formatBooleanPlanValue(plan.preflight.bootstrapOutputReady)}`,
|
|
@@ -26076,6 +26202,7 @@ function formatWalletSetupPlanText(plan) {
|
|
|
26076
26202
|
const bootstrapOutputLabel = `${plan.bootstrapOutput.path} (${plan.bootstrapOutput.autoGenerated ? "auto-generated" : "explicit"}, ${plan.bootstrapOutput.cleanupAction} after import)`;
|
|
26077
26203
|
const lines = [
|
|
26078
26204
|
"Wallet Setup Preview",
|
|
26205
|
+
/* c8 ignore next -- allowed and blocked admin access renderings are both exercised, but c8 misattributes this ternary */
|
|
26079
26206
|
`Admin Access: ${plan.adminAccess.permitted ? "allowed" : "blocked"} (${plan.adminAccess.mode})`,
|
|
26080
26207
|
`Admin Access Reason: ${plan.adminAccess.reason}`,
|
|
26081
26208
|
`Daemon Socket: ${plan.daemonSocket}`,
|
|
@@ -26098,8 +26225,11 @@ function formatWalletSetupPlanText(plan) {
|
|
|
26098
26225
|
"Config After Setup",
|
|
26099
26226
|
`- Agent Key ID: ${plan.configAfterSetup.agentKeyId}`,
|
|
26100
26227
|
`- Daemon Socket: ${plan.configAfterSetup.daemonSocket}`,
|
|
26228
|
+
/* c8 ignore next -- unchanged and explicit chain-id renderings are both exercised, but c8 misattributes this ternary */
|
|
26101
26229
|
`- Chain ID: ${plan.configAfterSetup.chainId === null ? "unchanged" : String(plan.configAfterSetup.chainId)}`,
|
|
26230
|
+
/* c8 ignore next -- unchanged and explicit chain-name renderings are both exercised, but c8 misattributes this nullish expression */
|
|
26102
26231
|
`- Chain Name: ${plan.configAfterSetup.chainName ?? "unchanged"}`,
|
|
26232
|
+
/* c8 ignore next -- unchanged and explicit RPC URL renderings are both exercised, but c8 misattributes this nullish expression */
|
|
26103
26233
|
`- RPC URL: ${plan.configAfterSetup.rpcUrl ?? "unchanged"}`,
|
|
26104
26234
|
"",
|
|
26105
26235
|
"Preflight",
|
|
@@ -26146,6 +26276,7 @@ function buildWalletSetupAdminArgs(input) {
|
|
|
26146
26276
|
const tokens = resolveValidatedWalletSetupTokenList(input.token);
|
|
26147
26277
|
const policyIds = resolveValidatedWalletSetupPolicyIds(input.attachPolicyId);
|
|
26148
26278
|
const fromSharedConfig = shouldBootstrapFromSharedConfig(input);
|
|
26279
|
+
const existingVaultReuse = resolveValidatedExistingVaultReuse(input);
|
|
26149
26280
|
if (input.vaultPassword) {
|
|
26150
26281
|
throw new Error(
|
|
26151
26282
|
"insecure vaultPassword is disabled; use vaultPasswordStdin or an interactive prompt"
|
|
@@ -26164,6 +26295,14 @@ function buildWalletSetupAdminArgs(input) {
|
|
|
26164
26295
|
if (fromSharedConfig) {
|
|
26165
26296
|
args.push("--from-shared-config");
|
|
26166
26297
|
}
|
|
26298
|
+
if (existingVaultReuse.keyId && existingVaultReuse.publicKey) {
|
|
26299
|
+
args.push(
|
|
26300
|
+
"--existing-vault-key-id",
|
|
26301
|
+
existingVaultReuse.keyId,
|
|
26302
|
+
"--existing-vault-public-key",
|
|
26303
|
+
existingVaultReuse.publicKey
|
|
26304
|
+
);
|
|
26305
|
+
}
|
|
26167
26306
|
const appendValue = (flag, value) => {
|
|
26168
26307
|
if (value) {
|
|
26169
26308
|
args.push(flag, value);
|
|
@@ -26244,7 +26383,8 @@ function completeWalletSetup(options, deps = {}) {
|
|
|
26244
26383
|
}
|
|
26245
26384
|
if (options.network !== void 0) {
|
|
26246
26385
|
nextConfig.chainId = assertPositiveChainId(options.network);
|
|
26247
|
-
nextConfig.chainName = normalizedChainName ??
|
|
26386
|
+
nextConfig.chainName = normalizedChainName ?? /* c8 ignore next -- configured-chain and synthetic fallback label paths are exercised, but c8 misattributes this nullish expression */
|
|
26387
|
+
configuredChainName(nextConfig.chainId, currentConfig) ?? `chain-${nextConfig.chainId}`;
|
|
26248
26388
|
nextConfig.rpcUrl = normalizedRpcUrl ?? configuredRpcUrl(nextConfig.chainId, currentConfig);
|
|
26249
26389
|
}
|
|
26250
26390
|
storeAgentAuthToken(credentials.agentKeyId, credentials.agentAuthToken);
|
|
@@ -26302,31 +26442,9 @@ async function readTrimmedStdin(label) {
|
|
|
26302
26442
|
}
|
|
26303
26443
|
return validateSecret4(raw.replace(/[\r\n]+$/u, ""), label);
|
|
26304
26444
|
}
|
|
26305
|
-
async function promptHidden2(query) {
|
|
26306
|
-
|
|
26307
|
-
|
|
26308
|
-
}
|
|
26309
|
-
const rl = import_node_readline2.default.createInterface({
|
|
26310
|
-
input: process.stdin,
|
|
26311
|
-
output: process.stdout,
|
|
26312
|
-
terminal: true
|
|
26313
|
-
});
|
|
26314
|
-
rl.stdoutMuted = true;
|
|
26315
|
-
rl._writeToOutput = (value) => {
|
|
26316
|
-
if (value.includes(query)) {
|
|
26317
|
-
rl.output.write(value);
|
|
26318
|
-
return;
|
|
26319
|
-
}
|
|
26320
|
-
if (!rl.stdoutMuted) {
|
|
26321
|
-
rl.output.write(value);
|
|
26322
|
-
}
|
|
26323
|
-
};
|
|
26324
|
-
const answer = await new Promise((resolve) => {
|
|
26325
|
-
rl.question(query, resolve);
|
|
26326
|
-
});
|
|
26327
|
-
rl.close();
|
|
26328
|
-
process.stdout.write("\n");
|
|
26329
|
-
return validateSecret4(answer, "vault password");
|
|
26445
|
+
async function promptHidden2(query, label = "vault password", nonInteractiveError = "vault password is required; use --vault-password-stdin or a local TTY prompt") {
|
|
26446
|
+
const answer = await promptHiddenTty(query, nonInteractiveError);
|
|
26447
|
+
return validateSecret4(answer, label);
|
|
26330
26448
|
}
|
|
26331
26449
|
async function promptVisible2(query, nonInteractiveError) {
|
|
26332
26450
|
if (!process.stdin.isTTY || !process.stdout.isTTY) {
|
|
@@ -26369,6 +26487,28 @@ function resolveExistingWalletSetupTarget(config) {
|
|
|
26369
26487
|
hasLegacyAgentAuthToken
|
|
26370
26488
|
};
|
|
26371
26489
|
}
|
|
26490
|
+
function resolveReusableWalletSetupTarget(config) {
|
|
26491
|
+
let walletProfile;
|
|
26492
|
+
try {
|
|
26493
|
+
walletProfile = resolveWalletProfile(config);
|
|
26494
|
+
} catch (error) {
|
|
26495
|
+
throw new Error(
|
|
26496
|
+
`--reuse-existing-wallet requires a local wallet to reuse; ${renderError5(error)}`
|
|
26497
|
+
);
|
|
26498
|
+
}
|
|
26499
|
+
const existingVaultKeyId = walletProfile.vaultKeyId?.trim();
|
|
26500
|
+
const existingVaultPublicKey = walletProfile.vaultPublicKey?.trim();
|
|
26501
|
+
if (!existingVaultKeyId || !existingVaultPublicKey) {
|
|
26502
|
+
throw new Error(
|
|
26503
|
+
"--reuse-existing-wallet requires wallet.vaultKeyId and wallet.vaultPublicKey in the local wallet profile"
|
|
26504
|
+
);
|
|
26505
|
+
}
|
|
26506
|
+
return {
|
|
26507
|
+
address: walletProfile.address?.trim() || deriveWalletAddress2(existingVaultPublicKey),
|
|
26508
|
+
existingVaultKeyId,
|
|
26509
|
+
existingVaultPublicKey
|
|
26510
|
+
};
|
|
26511
|
+
}
|
|
26372
26512
|
async function confirmAdminSetupOverwrite(options, config, deps = {}) {
|
|
26373
26513
|
const existing = resolveExistingWalletSetupTarget(config);
|
|
26374
26514
|
if (!existing || options.yes) {
|
|
@@ -26376,12 +26516,12 @@ async function confirmAdminSetupOverwrite(options, config, deps = {}) {
|
|
|
26376
26516
|
}
|
|
26377
26517
|
if (options.nonInteractive) {
|
|
26378
26518
|
throw new Error(
|
|
26379
|
-
"`wlfi-agent admin setup` would overwrite the existing wallet; rerun with --yes in non-interactive mode"
|
|
26519
|
+
options.reuseExistingWallet ? "`wlfi-agent admin setup --reuse-existing-wallet` would refresh the existing local wallet metadata and agent credentials; rerun with --yes in non-interactive mode" : "`wlfi-agent admin setup` would overwrite the existing wallet; rerun with --yes in non-interactive mode"
|
|
26380
26520
|
);
|
|
26381
26521
|
}
|
|
26382
26522
|
const stderr = deps.stderr ?? process.stderr;
|
|
26383
26523
|
stderr.write(
|
|
26384
|
-
"warning: admin setup will overwrite the current local wallet metadata and agent credentials.\n"
|
|
26524
|
+
options.reuseExistingWallet ? "warning: admin setup will reuse the current vault and refresh the local wallet metadata and agent credentials.\n" : "warning: admin setup will overwrite the current local wallet metadata and agent credentials.\n"
|
|
26385
26525
|
);
|
|
26386
26526
|
if (existing.address) {
|
|
26387
26527
|
stderr.write(`current address: ${existing.address}
|
|
@@ -26394,11 +26534,13 @@ async function confirmAdminSetupOverwrite(options, config, deps = {}) {
|
|
|
26394
26534
|
if (existing.hasLegacyAgentAuthToken) {
|
|
26395
26535
|
stderr.write("legacy agent auth token is still present in config.json\n");
|
|
26396
26536
|
}
|
|
26537
|
+
const confirmationToken = options.reuseExistingWallet ? "REUSE" : "OVERWRITE";
|
|
26538
|
+
const confirmationPrompt = options.reuseExistingWallet ? "Type REUSE to reattach the current local vault: " : "Type OVERWRITE to replace the current local wallet: ";
|
|
26397
26539
|
const confirmation = await (deps.prompt ?? ((query) => promptVisible2(
|
|
26398
26540
|
query,
|
|
26399
|
-
"`wlfi-agent admin setup` requires --yes in non-interactive environments when overwriting an existing wallet"
|
|
26400
|
-
)))(
|
|
26401
|
-
if (confirmation !==
|
|
26541
|
+
options.reuseExistingWallet ? "`wlfi-agent admin setup --reuse-existing-wallet` requires --yes in non-interactive environments when reusing an existing wallet" : "`wlfi-agent admin setup` requires --yes in non-interactive environments when overwriting an existing wallet"
|
|
26542
|
+
)))(confirmationPrompt);
|
|
26543
|
+
if (confirmation !== confirmationToken) {
|
|
26402
26544
|
throw new Error("admin setup aborted");
|
|
26403
26545
|
}
|
|
26404
26546
|
}
|
|
@@ -26429,7 +26571,9 @@ async function resolveAdminSetupVaultPassword(options, deps = {}) {
|
|
|
26429
26571
|
"vault password is required in non-interactive mode; use --vault-password-stdin"
|
|
26430
26572
|
);
|
|
26431
26573
|
}
|
|
26432
|
-
return promptForVaultPassword(
|
|
26574
|
+
return promptForVaultPassword(
|
|
26575
|
+
"Vault password (input hidden; this unlocks the wallet, not sudo): "
|
|
26576
|
+
);
|
|
26433
26577
|
}
|
|
26434
26578
|
function resolveDaemonSocket(optionValue) {
|
|
26435
26579
|
return import_node_path8.default.resolve(optionValue?.trim() || DEFAULT_MANAGED_DAEMON_SOCKET2);
|
|
@@ -26460,6 +26604,7 @@ function createAdminSetupPlan(options, deps = {}) {
|
|
|
26460
26604
|
installError = renderError5(error);
|
|
26461
26605
|
}
|
|
26462
26606
|
const existingWallet = resolveExistingWalletSetupTarget(config);
|
|
26607
|
+
const reusableWallet = options.reuseExistingWallet ? resolveReusableWalletSetupTarget(config) : null;
|
|
26463
26608
|
return {
|
|
26464
26609
|
command: "setup",
|
|
26465
26610
|
mode: "plan",
|
|
@@ -26502,6 +26647,8 @@ function createAdminSetupPlan(options, deps = {}) {
|
|
|
26502
26647
|
recipient: options.recipient,
|
|
26503
26648
|
attachPolicyId: options.attachPolicyId,
|
|
26504
26649
|
attachBootstrapPolicies: options.attachBootstrapPolicies,
|
|
26650
|
+
existingVaultKeyId: reusableWallet?.existingVaultKeyId,
|
|
26651
|
+
existingVaultPublicKey: reusableWallet?.existingVaultPublicKey,
|
|
26505
26652
|
bootstrapOutputPath: options.bootstrapOutput,
|
|
26506
26653
|
deleteBootstrapOutput: options.deleteBootstrapOutput
|
|
26507
26654
|
},
|
|
@@ -26826,7 +26973,9 @@ ${error.stdout}`;
|
|
|
26826
26973
|
}
|
|
26827
26974
|
var sudoSession2 = createSudoSession({
|
|
26828
26975
|
promptPassword: async () => await promptHidden2(
|
|
26829
|
-
"
|
|
26976
|
+
"macOS admin password for sudo (input hidden; required to install or recover the root daemon): ",
|
|
26977
|
+
"macOS admin password for sudo",
|
|
26978
|
+
"macOS admin password for sudo is required; rerun on a local TTY"
|
|
26830
26979
|
)
|
|
26831
26980
|
});
|
|
26832
26981
|
async function managedStateFileExists(stateFile) {
|
|
@@ -26841,6 +26990,134 @@ async function managedStateFileExists(stateFile) {
|
|
|
26841
26990
|
result.stderr.trim() || result.stdout.trim() || `failed to inspect managed daemon state file '${stateFile}' (exit code ${result.code})`
|
|
26842
26991
|
);
|
|
26843
26992
|
}
|
|
26993
|
+
async function inspectManagedState(stateFile, showProgress, message = "Inspecting managed daemon state") {
|
|
26994
|
+
await sudoSession2.prime();
|
|
26995
|
+
const stateProbeProgress = createProgress2(message, showProgress);
|
|
26996
|
+
let stateExists;
|
|
26997
|
+
try {
|
|
26998
|
+
stateExists = await managedStateFileExists(stateFile);
|
|
26999
|
+
stateProbeProgress.succeed(
|
|
27000
|
+
stateExists ? "Managed daemon state already exists" : "No managed daemon state found"
|
|
27001
|
+
);
|
|
27002
|
+
} catch (error) {
|
|
27003
|
+
stateProbeProgress.fail();
|
|
27004
|
+
throw error;
|
|
27005
|
+
}
|
|
27006
|
+
return stateExists;
|
|
27007
|
+
}
|
|
27008
|
+
function createManagedStatePasswordMismatchError(stateFile) {
|
|
27009
|
+
return new Error(
|
|
27010
|
+
`managed daemon state already exists at ${stateFile} and is encrypted with a different vault password. Re-run setup with the original vault password, or remove/reset the managed daemon state before initializing a fresh wallet.`
|
|
27011
|
+
);
|
|
27012
|
+
}
|
|
27013
|
+
function isManagedStatePasswordMismatch(output) {
|
|
27014
|
+
return /failed to decrypt state|wrong password or tampered file|authentication failed/iu.test(
|
|
27015
|
+
output
|
|
27016
|
+
);
|
|
27017
|
+
}
|
|
27018
|
+
async function managedStateAcceptsRequestedVaultPassword(config, daemonSocket, stateFile, vaultPassword) {
|
|
27019
|
+
const installPreconditions = assertManagedDaemonInstallPreconditions(
|
|
27020
|
+
config,
|
|
27021
|
+
daemonSocket,
|
|
27022
|
+
stateFile
|
|
27023
|
+
);
|
|
27024
|
+
const tempRoot = import_node_fs9.default.mkdtempSync(import_node_path8.default.join(import_node_os3.default.tmpdir(), "wlfi-managed-state-probe-"));
|
|
27025
|
+
const tempSocket = import_node_path8.default.join(tempRoot, "daemon.sock");
|
|
27026
|
+
const allowedUid = String(process.getuid?.() ?? process.geteuid?.() ?? 0);
|
|
27027
|
+
const probeScript = [
|
|
27028
|
+
"set -euo pipefail",
|
|
27029
|
+
'vault_password="$(cat)"',
|
|
27030
|
+
'daemon_bin="$1"',
|
|
27031
|
+
'state_file="$2"',
|
|
27032
|
+
'daemon_socket="$3"',
|
|
27033
|
+
'admin_uid="$4"',
|
|
27034
|
+
'agent_uid="$5"',
|
|
27035
|
+
'signer_backend="$6"',
|
|
27036
|
+
'"$daemon_bin" \\',
|
|
27037
|
+
" --non-interactive \\",
|
|
27038
|
+
" --vault-password-stdin \\",
|
|
27039
|
+
' --state-file "$state_file" \\',
|
|
27040
|
+
' --daemon-socket "$daemon_socket" \\',
|
|
27041
|
+
' --signer-backend "$signer_backend" \\',
|
|
27042
|
+
' --allow-admin-euid "$admin_uid" \\',
|
|
27043
|
+
' --allow-agent-euid "$agent_uid" <<<"$vault_password" &',
|
|
27044
|
+
'child="$!"',
|
|
27045
|
+
"for _ in $(seq 1 20); do",
|
|
27046
|
+
' if ! kill -0 "$child" 2>/dev/null; then',
|
|
27047
|
+
' wait "$child"',
|
|
27048
|
+
" exit $?",
|
|
27049
|
+
" fi",
|
|
27050
|
+
" sleep 0.25",
|
|
27051
|
+
"done",
|
|
27052
|
+
'kill "$child" >/dev/null 2>&1 || true',
|
|
27053
|
+
'wait "$child" >/dev/null 2>&1 || true',
|
|
27054
|
+
"exit 0"
|
|
27055
|
+
].join("\n");
|
|
27056
|
+
try {
|
|
27057
|
+
const result = await sudoSession2.run(
|
|
27058
|
+
[
|
|
27059
|
+
"/bin/bash",
|
|
27060
|
+
"-lc",
|
|
27061
|
+
probeScript,
|
|
27062
|
+
"--",
|
|
27063
|
+
installPreconditions.daemonBin,
|
|
27064
|
+
stateFile,
|
|
27065
|
+
tempSocket,
|
|
27066
|
+
allowedUid,
|
|
27067
|
+
allowedUid,
|
|
27068
|
+
DEFAULT_SIGNER_BACKEND
|
|
27069
|
+
],
|
|
27070
|
+
{
|
|
27071
|
+
stdin: `${vaultPassword}
|
|
27072
|
+
`
|
|
27073
|
+
}
|
|
27074
|
+
);
|
|
27075
|
+
if (result.code === 0) {
|
|
27076
|
+
return true;
|
|
27077
|
+
}
|
|
27078
|
+
const combinedOutput = `${result.stderr}
|
|
27079
|
+
${result.stdout}`.trim();
|
|
27080
|
+
if (isManagedStatePasswordMismatch(combinedOutput)) {
|
|
27081
|
+
return false;
|
|
27082
|
+
}
|
|
27083
|
+
throw new Error(
|
|
27084
|
+
combinedOutput || `failed to validate the managed daemon state with the requested vault password (exit code ${result.code})`
|
|
27085
|
+
);
|
|
27086
|
+
} finally {
|
|
27087
|
+
import_node_fs9.default.rmSync(tempRoot, { recursive: true, force: true });
|
|
27088
|
+
}
|
|
27089
|
+
}
|
|
27090
|
+
async function assertManagedStateMatchesRequestedVaultPasswordBeforeInstall(config, daemonSocket, stateFile, vaultPassword, showProgress) {
|
|
27091
|
+
const stateExists = await inspectManagedState(
|
|
27092
|
+
stateFile,
|
|
27093
|
+
showProgress,
|
|
27094
|
+
"Inspecting managed daemon state before install"
|
|
27095
|
+
);
|
|
27096
|
+
if (!stateExists) {
|
|
27097
|
+
return;
|
|
27098
|
+
}
|
|
27099
|
+
const verifyProgress = createProgress2(
|
|
27100
|
+
"Verifying the requested vault password against the managed daemon state",
|
|
27101
|
+
showProgress
|
|
27102
|
+
);
|
|
27103
|
+
let accepted;
|
|
27104
|
+
try {
|
|
27105
|
+
accepted = await managedStateAcceptsRequestedVaultPassword(
|
|
27106
|
+
config,
|
|
27107
|
+
daemonSocket,
|
|
27108
|
+
stateFile,
|
|
27109
|
+
vaultPassword
|
|
27110
|
+
);
|
|
27111
|
+
} catch (error) {
|
|
27112
|
+
verifyProgress.fail();
|
|
27113
|
+
throw error;
|
|
27114
|
+
}
|
|
27115
|
+
if (!accepted) {
|
|
27116
|
+
verifyProgress.fail("Existing daemon password does not unlock the stored daemon state");
|
|
27117
|
+
throw createManagedStatePasswordMismatchError(stateFile);
|
|
27118
|
+
}
|
|
27119
|
+
verifyProgress.succeed("Requested vault password unlocks the existing managed daemon state");
|
|
27120
|
+
}
|
|
26844
27121
|
async function waitForTrustedDaemonSocket(targetPath, timeoutMs = 15e3) {
|
|
26845
27122
|
async function canConnect(socketPath) {
|
|
26846
27123
|
return new Promise((resolve) => {
|
|
@@ -27019,6 +27296,7 @@ async function runAdminSetup(options) {
|
|
|
27019
27296
|
const config = readConfig();
|
|
27020
27297
|
const daemonSocket = resolveDaemonSocket(options.daemonSocket);
|
|
27021
27298
|
const stateFile = resolveStateFile();
|
|
27299
|
+
const reusableWallet = options.reuseExistingWallet ? resolveReusableWalletSetupTarget(config) : null;
|
|
27022
27300
|
assertManagedDaemonInstallPreconditions(config, daemonSocket, stateFile);
|
|
27023
27301
|
await confirmAdminSetupOverwrite(options, config);
|
|
27024
27302
|
const vaultPassword = await resolveAdminSetupVaultPassword(options);
|
|
@@ -27076,10 +27354,17 @@ async function runAdminSetup(options) {
|
|
|
27076
27354
|
}
|
|
27077
27355
|
if (!options.json && typeof process.geteuid === "function" && process.geteuid() !== 0) {
|
|
27078
27356
|
process.stderr.write(
|
|
27079
|
-
"
|
|
27357
|
+
"macOS admin password required: setup uses sudo to install or recover the root LaunchDaemon and store the daemon password in System Keychain.\n"
|
|
27080
27358
|
);
|
|
27081
27359
|
}
|
|
27082
27360
|
await sudoSession2.prime();
|
|
27361
|
+
await assertManagedStateMatchesRequestedVaultPasswordBeforeInstall(
|
|
27362
|
+
config,
|
|
27363
|
+
daemonSocket,
|
|
27364
|
+
stateFile,
|
|
27365
|
+
vaultPassword,
|
|
27366
|
+
showProgress
|
|
27367
|
+
);
|
|
27083
27368
|
const installProgress = createProgress2("Installing and restarting daemon", showProgress);
|
|
27084
27369
|
try {
|
|
27085
27370
|
daemon = await installLaunchDaemon(config, daemonSocket, stateFile, vaultPassword);
|
|
@@ -27109,34 +27394,20 @@ async function runAdminSetup(options) {
|
|
|
27109
27394
|
if (!daemonAcceptedPassword) {
|
|
27110
27395
|
if (existingDaemonRejectedPassword) {
|
|
27111
27396
|
authProgress.fail("Existing daemon password does not unlock the stored daemon state");
|
|
27112
|
-
throw
|
|
27113
|
-
`managed daemon state already exists at ${stateFile} and is encrypted with a different vault password. Re-run setup with the original vault password, or remove/reset the managed daemon state before initializing a fresh wallet.`
|
|
27114
|
-
);
|
|
27397
|
+
throw createManagedStatePasswordMismatchError(stateFile);
|
|
27115
27398
|
}
|
|
27116
27399
|
authProgress.info("Daemon rejected the requested vault password; inspecting managed state");
|
|
27117
|
-
const
|
|
27118
|
-
let stateExists;
|
|
27119
|
-
try {
|
|
27120
|
-
stateExists = await managedStateFileExists(stateFile);
|
|
27121
|
-
stateProbeProgress.succeed(
|
|
27122
|
-
stateExists ? "Managed daemon state already exists" : "No managed daemon state found"
|
|
27123
|
-
);
|
|
27124
|
-
} catch (error) {
|
|
27125
|
-
stateProbeProgress.fail();
|
|
27126
|
-
throw error;
|
|
27127
|
-
}
|
|
27400
|
+
const stateExists = await inspectManagedState(stateFile, showProgress);
|
|
27128
27401
|
if (stateExists) {
|
|
27129
27402
|
authProgress.fail("Existing daemon password does not unlock the stored daemon state");
|
|
27130
|
-
throw
|
|
27131
|
-
`managed daemon state already exists at ${stateFile} and is encrypted with a different vault password. Re-run setup with the original vault password, or remove/reset the managed daemon state before initializing a fresh wallet.`
|
|
27132
|
-
);
|
|
27403
|
+
throw createManagedStatePasswordMismatchError(stateFile);
|
|
27133
27404
|
}
|
|
27134
27405
|
authProgress.fail(
|
|
27135
27406
|
"Existing daemon password differs; reinstalling with the requested vault password"
|
|
27136
27407
|
);
|
|
27137
27408
|
if (!options.json && typeof process.geteuid === "function" && process.geteuid() !== 0) {
|
|
27138
27409
|
process.stderr.write(
|
|
27139
|
-
"
|
|
27410
|
+
"macOS admin password required: setup uses sudo to reinstall the root LaunchDaemon and rotate the managed daemon password.\n"
|
|
27140
27411
|
);
|
|
27141
27412
|
}
|
|
27142
27413
|
await sudoSession2.prime();
|
|
@@ -27216,6 +27487,8 @@ async function runAdminSetup(options) {
|
|
|
27216
27487
|
recipient: options.recipient,
|
|
27217
27488
|
attachPolicyId: options.attachPolicyId,
|
|
27218
27489
|
attachBootstrapPolicies: options.attachBootstrapPolicies,
|
|
27490
|
+
existingVaultKeyId: reusableWallet?.existingVaultKeyId,
|
|
27491
|
+
existingVaultPublicKey: reusableWallet?.existingVaultPublicKey,
|
|
27219
27492
|
bootstrapOutputPath: bootstrapOutput.path
|
|
27220
27493
|
});
|
|
27221
27494
|
const bootstrapProgress = createProgress2("Setting up wallet access", showProgress);
|
|
@@ -27338,6 +27611,8 @@ function buildAdminSetupBootstrapInvocation(input) {
|
|
|
27338
27611
|
recipient: input.recipient,
|
|
27339
27612
|
attachPolicyId: input.attachPolicyId,
|
|
27340
27613
|
attachBootstrapPolicies: input.attachBootstrapPolicies,
|
|
27614
|
+
existingVaultKeyId: input.existingVaultKeyId,
|
|
27615
|
+
existingVaultPublicKey: input.existingVaultPublicKey,
|
|
27341
27616
|
bootstrapOutputPath: input.bootstrapOutputPath
|
|
27342
27617
|
}),
|
|
27343
27618
|
stdin: `${validateSecret4(input.vaultPassword, "vault password")}
|
|
@@ -27396,7 +27671,11 @@ async function runAdminSetupCli(argv) {
|
|
|
27396
27671
|
const program2 = new Command();
|
|
27397
27672
|
program2.name("wlfi-agent admin setup").description(
|
|
27398
27673
|
"Store the vault password, install the root daemon autostart, set up wallet access, and print the wallet address"
|
|
27399
|
-
).option("--plan", "Print a sanitized setup preview without prompting or mutating state", false).option("--vault-password-stdin", "Read vault password from stdin", false).option("--non-interactive", "Disable password prompts", false).option("-y, --yes", "Skip the overwrite confirmation prompt", false).option(
|
|
27674
|
+
).option("--plan", "Print a sanitized setup preview without prompting or mutating state", false).option("--vault-password-stdin", "Read vault password from stdin", false).option("--non-interactive", "Disable password prompts", false).option("-y, --yes", "Skip the overwrite confirmation prompt", false).option(
|
|
27675
|
+
"--reuse-existing-wallet",
|
|
27676
|
+
"Reuse the current local vault instead of generating a fresh wallet",
|
|
27677
|
+
false
|
|
27678
|
+
).option("--daemon-socket <path>", "Daemon unix socket path").option("--per-tx-max-wei <wei>", "Per-transaction max spend in wei").option("--daily-max-wei <wei>", "Daily max spend in wei").option("--weekly-max-wei <wei>", "Weekly max spend in wei").option("--max-gas-per-chain-wei <wei>", "Per-chain gas-spend ceiling in wei").option("--daily-max-tx-count <count>", "Optional daily tx-count cap").option("--per-tx-max-fee-per-gas-wei <wei>", "Optional max fee-per-gas cap").option("--per-tx-max-priority-fee-per-gas-wei <wei>", "Optional max priority fee-per-gas cap").option("--per-tx-max-calldata-bytes <bytes>", "Optional calldata size cap").option(
|
|
27400
27679
|
"--token <address>",
|
|
27401
27680
|
"Allowed ERC-20 token address",
|
|
27402
27681
|
(value, acc) => {
|
|
@@ -27531,8 +27810,12 @@ function resolveConfiguredAgentKeyId(config, explicitAgentKeyId) {
|
|
|
27531
27810
|
if (explicitAgentKeyId) {
|
|
27532
27811
|
return void 0;
|
|
27533
27812
|
}
|
|
27813
|
+
const renderedError = error instanceof Error ? error.message : (
|
|
27814
|
+
/* c8 ignore next -- assertValidAgentKeyId throws Error objects */
|
|
27815
|
+
String(error)
|
|
27816
|
+
);
|
|
27534
27817
|
throw new Error(
|
|
27535
|
-
|
|
27818
|
+
renderedError + "; pass --agent-key-id to migrate the legacy config secret explicitly"
|
|
27536
27819
|
);
|
|
27537
27820
|
}
|
|
27538
27821
|
}
|
|
@@ -27818,11 +28101,26 @@ function rewriteAmountPolicyErrorMessage(message, asset) {
|
|
|
27818
28101
|
(_match, usedAmountWei, requestedAmountWei, maxAmountWei) => `window usage ${formatAmount(usedAmountWei)} + requested ${formatAmount(requestedAmountWei)} > max ${formatAmount(maxAmountWei)}`
|
|
27819
28102
|
).replace(
|
|
27820
28103
|
/requires manual approval for requested amount (\d+) within range (None|Some\((\d+)\))\.\.=(\d+)/gu,
|
|
27821
|
-
(_match, requestedAmountWei, minAmountLiteral, minAmountWei, maxAmountWei) =>
|
|
28104
|
+
(_match, requestedAmountWei, minAmountLiteral, minAmountWei, maxAmountWei) => {
|
|
28105
|
+
const minAmountDisplay = (
|
|
28106
|
+
/* c8 ignore next -- both None and Some(...) paths are exercised, but c8 misattributes this ternary under --experimental-strip-types */
|
|
28107
|
+
minAmountLiteral === "None" ? "None" : `Some(${formatAmount(minAmountWei ?? "0")})`
|
|
28108
|
+
);
|
|
28109
|
+
return `requires manual approval for requested amount ${formatAmount(requestedAmountWei)} within range ${minAmountDisplay}..=${formatAmount(maxAmountWei)}`;
|
|
28110
|
+
}
|
|
27822
28111
|
);
|
|
27823
28112
|
}
|
|
27824
28113
|
|
|
27825
28114
|
// src/lib/asset-broadcast.ts
|
|
28115
|
+
function resolveEstimatedPriorityFeePerGasWei(fees) {
|
|
28116
|
+
const resolved = fees.maxPriorityFeePerGas ?? fees.gasPrice;
|
|
28117
|
+
if (resolved === null || resolved <= 0n) {
|
|
28118
|
+
throw new Error(
|
|
28119
|
+
"Could not determine maxPriorityFeePerGas; pass --max-priority-fee-per-gas-wei"
|
|
28120
|
+
);
|
|
28121
|
+
}
|
|
28122
|
+
return resolved;
|
|
28123
|
+
}
|
|
27826
28124
|
function encodeErc20TransferData(recipient, amountWei) {
|
|
27827
28125
|
return encodeFunctionData({
|
|
27828
28126
|
abi: erc20Abi,
|
|
@@ -27851,10 +28149,10 @@ async function resolveAssetBroadcastPlan(input, deps) {
|
|
|
27851
28149
|
const fees = await deps.estimateFees(input.rpcUrl);
|
|
27852
28150
|
const resolvedFees = fees;
|
|
27853
28151
|
const maxFeePerGasWei = input.maxFeePerGasWei ?? (resolvedFees.maxFeePerGas ?? resolvedFees.gasPrice);
|
|
27854
|
-
const maxPriorityFeePerGasWei = input.maxPriorityFeePerGasWei ?? (resolvedFees.maxPriorityFeePerGas ?? resolvedFees.gasPrice ?? 0n);
|
|
27855
28152
|
if (maxFeePerGasWei === null || maxFeePerGasWei <= 0n) {
|
|
27856
28153
|
throw new Error("Could not determine maxFeePerGas; pass --max-fee-per-gas-wei");
|
|
27857
28154
|
}
|
|
28155
|
+
const maxPriorityFeePerGasWei = input.maxPriorityFeePerGasWei ?? resolveEstimatedPriorityFeePerGasWei(resolvedFees);
|
|
27858
28156
|
return {
|
|
27859
28157
|
rpcUrl: input.rpcUrl,
|
|
27860
28158
|
chainId: input.chainId,
|
|
@@ -27879,7 +28177,7 @@ async function completeAssetBroadcast(plan, signed, deps) {
|
|
|
27879
28177
|
to: plan.to,
|
|
27880
28178
|
chainId: plan.chainId,
|
|
27881
28179
|
nonce: plan.nonce,
|
|
27882
|
-
allowHigherNonce:
|
|
28180
|
+
allowHigherNonce: false,
|
|
27883
28181
|
value: plan.valueWei,
|
|
27884
28182
|
data: plan.dataHex,
|
|
27885
28183
|
gasLimit: plan.gasLimit,
|
|
@@ -27986,7 +28284,6 @@ function resolveConfigMutationCommandLabel(command, key) {
|
|
|
27986
28284
|
}
|
|
27987
28285
|
|
|
27988
28286
|
// src/lib/local-admin-access.ts
|
|
27989
|
-
var import_node_readline3 = __toESM(require("readline"), 1);
|
|
27990
28287
|
var MAX_SECRET_STDIN_BYTES4 = 16 * 1024;
|
|
27991
28288
|
function renderError6(error) {
|
|
27992
28289
|
return error instanceof Error ? error.message : String(error);
|
|
@@ -28004,35 +28301,13 @@ function currentProcessIsRoot2() {
|
|
|
28004
28301
|
return typeof process.geteuid === "function" && process.geteuid() === 0;
|
|
28005
28302
|
}
|
|
28006
28303
|
async function promptHidden3(query, label) {
|
|
28007
|
-
|
|
28008
|
-
throw new Error(`${label} is required; rerun on a local TTY`);
|
|
28009
|
-
}
|
|
28010
|
-
const rl = import_node_readline3.default.createInterface({
|
|
28011
|
-
input: process.stdin,
|
|
28012
|
-
output: process.stdout,
|
|
28013
|
-
terminal: true
|
|
28014
|
-
});
|
|
28015
|
-
rl.stdoutMuted = true;
|
|
28016
|
-
rl._writeToOutput = (value) => {
|
|
28017
|
-
if (value.includes(query)) {
|
|
28018
|
-
rl.output.write(value);
|
|
28019
|
-
return;
|
|
28020
|
-
}
|
|
28021
|
-
if (!rl.stdoutMuted) {
|
|
28022
|
-
rl.output.write(value);
|
|
28023
|
-
}
|
|
28024
|
-
};
|
|
28025
|
-
const answer = await new Promise((resolve) => {
|
|
28026
|
-
rl.question(query, resolve);
|
|
28027
|
-
});
|
|
28028
|
-
rl.close();
|
|
28029
|
-
process.stdout.write("\n");
|
|
28304
|
+
const answer = await promptHiddenTty(query, `${label} is required; rerun on a local TTY`);
|
|
28030
28305
|
return validateSecret5(answer, label);
|
|
28031
28306
|
}
|
|
28032
28307
|
var sudoSession3 = createSudoSession({
|
|
28033
28308
|
promptPassword: async () => await promptHidden3(
|
|
28034
|
-
"
|
|
28035
|
-
"
|
|
28309
|
+
"macOS admin password for sudo (input hidden; required to change local admin chain and token configuration): ",
|
|
28310
|
+
"macOS admin password for sudo"
|
|
28036
28311
|
)
|
|
28037
28312
|
});
|
|
28038
28313
|
async function requireLocalAdminMutationAccess(commandLabel, deps = {}) {
|
|
@@ -28915,10 +29190,9 @@ function isManualApprovalRequiredOutput(value) {
|
|
|
28915
29190
|
const candidate = value;
|
|
28916
29191
|
return typeof candidate.command === "string" && typeof candidate.approval_request_id === "string" && typeof candidate.cli_approval_command === "string";
|
|
28917
29192
|
}
|
|
28918
|
-
function
|
|
29193
|
+
function renderManualApprovalRequired(output, asJson) {
|
|
28919
29194
|
if (asJson) {
|
|
28920
|
-
|
|
28921
|
-
return;
|
|
29195
|
+
return formatJson(output);
|
|
28922
29196
|
}
|
|
28923
29197
|
const lines = [
|
|
28924
29198
|
`Command: ${output.command}`,
|
|
@@ -28931,7 +29205,27 @@ function printManualApprovalRequired(output, asJson) {
|
|
|
28931
29205
|
lines.push(`Relay URL: ${output.relay_url}`);
|
|
28932
29206
|
}
|
|
28933
29207
|
lines.push(`CLI Approval Command: ${output.cli_approval_command}`);
|
|
28934
|
-
|
|
29208
|
+
return lines.join("\n");
|
|
29209
|
+
}
|
|
29210
|
+
function printManualApprovalRequired(output, asJson, useStderr = false) {
|
|
29211
|
+
const rendered = renderManualApprovalRequired(output, asJson);
|
|
29212
|
+
if (useStderr) {
|
|
29213
|
+
console.error(rendered);
|
|
29214
|
+
return;
|
|
29215
|
+
}
|
|
29216
|
+
console.log(rendered);
|
|
29217
|
+
}
|
|
29218
|
+
function printManualApprovalWaiting(output, asJson) {
|
|
29219
|
+
if (asJson) {
|
|
29220
|
+
console.error(
|
|
29221
|
+
formatJson({
|
|
29222
|
+
event: "manualApprovalPending",
|
|
29223
|
+
approvalRequestId: output.approval_request_id
|
|
29224
|
+
})
|
|
29225
|
+
);
|
|
29226
|
+
return;
|
|
29227
|
+
}
|
|
29228
|
+
console.error(`Waiting for manual approval decision: ${output.approval_request_id}`);
|
|
28935
29229
|
}
|
|
28936
29230
|
var MAX_SECRET_STDIN_BYTES5 = 16 * 1024;
|
|
28937
29231
|
var AGENT_KEY_ID_PATTERN2 = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/iu;
|
|
@@ -29052,6 +29346,15 @@ function print2(payload, asJson) {
|
|
|
29052
29346
|
}
|
|
29053
29347
|
var ONCHAIN_RECEIPT_TIMEOUT_MS = 3e4;
|
|
29054
29348
|
var ONCHAIN_RECEIPT_POLL_INTERVAL_MS = 2e3;
|
|
29349
|
+
var MANUAL_APPROVAL_POLL_INTERVAL_MS = 2e3;
|
|
29350
|
+
var MANUAL_APPROVAL_WAIT_TIMEOUT_MS = (() => {
|
|
29351
|
+
const raw = process.env.WLFI_TEST_MANUAL_APPROVAL_TIMEOUT_MS;
|
|
29352
|
+
if (!raw) {
|
|
29353
|
+
return 5 * 6e4;
|
|
29354
|
+
}
|
|
29355
|
+
const parsed = Number(raw);
|
|
29356
|
+
return Number.isFinite(parsed) && parsed > 0 ? parsed : 5 * 6e4;
|
|
29357
|
+
})();
|
|
29055
29358
|
async function reportOnchainReceiptStatus(input) {
|
|
29056
29359
|
if (input.asJson) {
|
|
29057
29360
|
console.error(
|
|
@@ -29193,39 +29496,40 @@ async function resolveAgentCommandContext(options, config) {
|
|
|
29193
29496
|
warnForAgentAuthTokenSource(agentAuthTokenSource, agentKeyId);
|
|
29194
29497
|
return { agentKeyId, agentAuthToken, daemonSocket };
|
|
29195
29498
|
}
|
|
29196
|
-
async function
|
|
29197
|
-
const { agentKeyId, agentAuthToken, daemonSocket } = await resolveAgentCommandContext(
|
|
29198
|
-
input.auth,
|
|
29199
|
-
input.config
|
|
29200
|
-
);
|
|
29499
|
+
async function runAgentCommandJsonOnce(input) {
|
|
29201
29500
|
try {
|
|
29202
|
-
return
|
|
29203
|
-
"
|
|
29204
|
-
|
|
29205
|
-
"
|
|
29206
|
-
|
|
29207
|
-
|
|
29208
|
-
|
|
29209
|
-
|
|
29210
|
-
|
|
29211
|
-
|
|
29212
|
-
|
|
29213
|
-
|
|
29214
|
-
|
|
29215
|
-
|
|
29501
|
+
return {
|
|
29502
|
+
type: "success",
|
|
29503
|
+
value: await runRustBinaryJson(
|
|
29504
|
+
"wlfi-agent-agent",
|
|
29505
|
+
[
|
|
29506
|
+
"--json",
|
|
29507
|
+
"--agent-key-id",
|
|
29508
|
+
input.agentKeyId,
|
|
29509
|
+
"--agent-auth-token-stdin",
|
|
29510
|
+
"--daemon-socket",
|
|
29511
|
+
input.daemonSocket,
|
|
29512
|
+
...input.commandArgs
|
|
29513
|
+
],
|
|
29514
|
+
input.config,
|
|
29515
|
+
{
|
|
29516
|
+
stdin: `${input.agentAuthToken}
|
|
29216
29517
|
`,
|
|
29217
|
-
|
|
29218
|
-
|
|
29219
|
-
|
|
29220
|
-
|
|
29518
|
+
preSuppliedSecretStdin: "agentAuthToken",
|
|
29519
|
+
scrubSensitiveEnv: true
|
|
29520
|
+
}
|
|
29521
|
+
)
|
|
29522
|
+
};
|
|
29221
29523
|
} catch (error) {
|
|
29222
29524
|
if (error instanceof RustBinaryExitError && error.stdout.trim()) {
|
|
29223
29525
|
try {
|
|
29224
29526
|
const parsed = JSON.parse(error.stdout);
|
|
29225
29527
|
if (isManualApprovalRequiredOutput(parsed)) {
|
|
29226
|
-
|
|
29227
|
-
|
|
29228
|
-
|
|
29528
|
+
return {
|
|
29529
|
+
type: "manualApproval",
|
|
29530
|
+
output: parsed,
|
|
29531
|
+
exitCode: error.code
|
|
29532
|
+
};
|
|
29229
29533
|
}
|
|
29230
29534
|
} catch {
|
|
29231
29535
|
}
|
|
@@ -29233,6 +29537,49 @@ async function runAgentCommandJson(input) {
|
|
|
29233
29537
|
throw error;
|
|
29234
29538
|
}
|
|
29235
29539
|
}
|
|
29540
|
+
async function runAgentCommandJson(input) {
|
|
29541
|
+
const { agentKeyId, agentAuthToken, daemonSocket } = await resolveAgentCommandContext(
|
|
29542
|
+
input.auth,
|
|
29543
|
+
input.config
|
|
29544
|
+
);
|
|
29545
|
+
const runOnce = () => runAgentCommandJsonOnce({
|
|
29546
|
+
commandArgs: input.commandArgs,
|
|
29547
|
+
config: input.config,
|
|
29548
|
+
agentKeyId,
|
|
29549
|
+
agentAuthToken,
|
|
29550
|
+
daemonSocket
|
|
29551
|
+
});
|
|
29552
|
+
const firstAttempt = await runOnce();
|
|
29553
|
+
if (firstAttempt.type === "success") {
|
|
29554
|
+
return firstAttempt.value;
|
|
29555
|
+
}
|
|
29556
|
+
if (!input.waitForManualApproval) {
|
|
29557
|
+
printManualApprovalRequired(firstAttempt.output, input.asJson);
|
|
29558
|
+
process.exitCode = firstAttempt.exitCode;
|
|
29559
|
+
return null;
|
|
29560
|
+
}
|
|
29561
|
+
printManualApprovalRequired(firstAttempt.output, input.asJson, true);
|
|
29562
|
+
printManualApprovalWaiting(firstAttempt.output, input.asJson);
|
|
29563
|
+
const pendingApprovalRequestId = firstAttempt.output.approval_request_id;
|
|
29564
|
+
const startedWaitingAt = Date.now();
|
|
29565
|
+
for (; ; ) {
|
|
29566
|
+
await (0, import_promises2.setTimeout)(MANUAL_APPROVAL_POLL_INTERVAL_MS);
|
|
29567
|
+
const nextAttempt = await runOnce();
|
|
29568
|
+
if (nextAttempt.type === "success") {
|
|
29569
|
+
return nextAttempt.value;
|
|
29570
|
+
}
|
|
29571
|
+
if (nextAttempt.output.approval_request_id !== pendingApprovalRequestId) {
|
|
29572
|
+
throw new Error(
|
|
29573
|
+
`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.`
|
|
29574
|
+
);
|
|
29575
|
+
}
|
|
29576
|
+
if (Date.now() - startedWaitingAt >= MANUAL_APPROVAL_WAIT_TIMEOUT_MS) {
|
|
29577
|
+
throw new Error(
|
|
29578
|
+
`Timed out after ${Math.ceil(MANUAL_APPROVAL_WAIT_TIMEOUT_MS / 1e3)}s waiting for manual approval decision: ${pendingApprovalRequestId}`
|
|
29579
|
+
);
|
|
29580
|
+
}
|
|
29581
|
+
}
|
|
29582
|
+
}
|
|
29236
29583
|
function legacyAmountToDecimalString(value) {
|
|
29237
29584
|
if (value === void 0) {
|
|
29238
29585
|
return void 0;
|
|
@@ -29870,7 +30217,8 @@ async function main() {
|
|
|
29870
30217
|
],
|
|
29871
30218
|
auth: options,
|
|
29872
30219
|
config,
|
|
29873
|
-
asJson: options.json
|
|
30220
|
+
asJson: options.json,
|
|
30221
|
+
waitForManualApproval: true
|
|
29874
30222
|
});
|
|
29875
30223
|
if (!signed) {
|
|
29876
30224
|
return;
|
|
@@ -29926,20 +30274,108 @@ async function main() {
|
|
|
29926
30274
|
}
|
|
29927
30275
|
});
|
|
29928
30276
|
addAgentCommandAuthOptions(
|
|
29929
|
-
program2.command("transfer-native").description("Submit a native ETH transfer request through policy checks").requiredOption("--network <name>", "Network name").requiredOption("--to <address>", "Recipient address").requiredOption("--amount <amount>", "Transfer amount in configured native token units").
|
|
30277
|
+
program2.command("transfer-native").description("Submit a native ETH transfer request through policy checks").requiredOption("--network <name>", "Network name").requiredOption("--to <address>", "Recipient address").requiredOption("--amount <amount>", "Transfer amount in configured native token units").option("--broadcast", "Broadcast the signed transaction through RPC", false).option("--rpc-url <url>", "Ethereum RPC URL override used only for broadcast").option(
|
|
30278
|
+
"--from <address>",
|
|
30279
|
+
"Sender address override for broadcast; defaults to configured wallet address"
|
|
30280
|
+
).option("--nonce <nonce>", "Explicit nonce override for broadcast").option("--gas-limit <gas>", "Gas limit override for broadcast").option("--max-fee-per-gas-wei <wei>", "Max fee per gas override for broadcast").option("--max-priority-fee-per-gas-wei <wei>", "Priority fee per gas override for broadcast").option("--tx-type <type>", "Typed tx value for broadcast", "0x02").option("--no-wait", "Do not wait up to 30s for an on-chain receipt after broadcast").option(
|
|
30281
|
+
"--reveal-raw-tx",
|
|
30282
|
+
"Include the signed raw transaction bytes in broadcast output",
|
|
30283
|
+
false
|
|
30284
|
+
).option("--reveal-signature", "Include signer r/s/v fields in broadcast output", false).addOption(new Option("--amount-wei <wei>").hideHelp())
|
|
29930
30285
|
).action(async (options) => {
|
|
29931
30286
|
const config = readConfig3();
|
|
29932
30287
|
const network = resolveCliNetworkProfile(options.network, config).chainId;
|
|
29933
30288
|
const asset = resolveConfiguredNativeAsset(config, network);
|
|
30289
|
+
const recipient = assertAddress(options.to, "to");
|
|
29934
30290
|
const amountWei = options.amount ? parseConfiguredAmount(options.amount, asset.decimals, "amount") : parseBigIntString(options.amountWei, "amountWei");
|
|
29935
30291
|
try {
|
|
30292
|
+
if (options.broadcast) {
|
|
30293
|
+
const plan = await resolveAssetBroadcastPlan(
|
|
30294
|
+
{
|
|
30295
|
+
rpcUrl: resolveCliRpcUrl(options.rpcUrl, options.network, config),
|
|
30296
|
+
chainId: network,
|
|
30297
|
+
from: options.from ? assertAddress(options.from, "from") : resolveWalletAddress(config),
|
|
30298
|
+
to: recipient,
|
|
30299
|
+
valueWei: amountWei,
|
|
30300
|
+
dataHex: "0x",
|
|
30301
|
+
nonce: options.nonce ? parseIntegerString(options.nonce, "nonce") : void 0,
|
|
30302
|
+
gasLimit: options.gasLimit ? parsePositiveBigIntString(options.gasLimit, "gasLimit") : void 0,
|
|
30303
|
+
maxFeePerGasWei: options.maxFeePerGasWei ? parsePositiveBigIntString(options.maxFeePerGasWei, "maxFeePerGasWei") : void 0,
|
|
30304
|
+
maxPriorityFeePerGasWei: options.maxPriorityFeePerGasWei ? parseBigIntString(options.maxPriorityFeePerGasWei, "maxPriorityFeePerGasWei") : void 0,
|
|
30305
|
+
txType: options.txType
|
|
30306
|
+
},
|
|
30307
|
+
{
|
|
30308
|
+
getChainInfo: getChainInfo2,
|
|
30309
|
+
assertRpcChainIdMatches,
|
|
30310
|
+
getNonce: getNonce2,
|
|
30311
|
+
estimateGas: estimateGas3,
|
|
30312
|
+
estimateFees: estimateFees2
|
|
30313
|
+
}
|
|
30314
|
+
);
|
|
30315
|
+
const signed = await runAgentCommandJson({
|
|
30316
|
+
commandArgs: [
|
|
30317
|
+
"broadcast",
|
|
30318
|
+
"--network",
|
|
30319
|
+
String(plan.chainId),
|
|
30320
|
+
"--nonce",
|
|
30321
|
+
String(plan.nonce),
|
|
30322
|
+
"--to",
|
|
30323
|
+
recipient,
|
|
30324
|
+
"--value-wei",
|
|
30325
|
+
amountWei.toString(),
|
|
30326
|
+
"--data-hex",
|
|
30327
|
+
"0x",
|
|
30328
|
+
"--gas-limit",
|
|
30329
|
+
plan.gasLimit.toString(),
|
|
30330
|
+
"--max-fee-per-gas-wei",
|
|
30331
|
+
plan.maxFeePerGasWei.toString(),
|
|
30332
|
+
"--max-priority-fee-per-gas-wei",
|
|
30333
|
+
plan.maxPriorityFeePerGasWei.toString(),
|
|
30334
|
+
"--tx-type",
|
|
30335
|
+
plan.txType
|
|
30336
|
+
],
|
|
30337
|
+
auth: options,
|
|
30338
|
+
config,
|
|
30339
|
+
asJson: options.json,
|
|
30340
|
+
waitForManualApproval: true
|
|
30341
|
+
});
|
|
30342
|
+
if (!signed) {
|
|
30343
|
+
return;
|
|
30344
|
+
}
|
|
30345
|
+
const completed = await completeAssetBroadcast(plan, signed, {
|
|
30346
|
+
assertSignedBroadcastTransactionMatchesRequest,
|
|
30347
|
+
broadcastRawTransaction: broadcastRawTransaction2
|
|
30348
|
+
});
|
|
30349
|
+
print2(
|
|
30350
|
+
formatBroadcastedAssetOutput({
|
|
30351
|
+
command: "transfer-native",
|
|
30352
|
+
counterparty: recipient,
|
|
30353
|
+
asset,
|
|
30354
|
+
signed,
|
|
30355
|
+
plan,
|
|
30356
|
+
signedNonce: completed.signedNonce,
|
|
30357
|
+
networkTxHash: completed.networkTxHash,
|
|
30358
|
+
revealRawTx: options.revealRawTx,
|
|
30359
|
+
revealSignature: options.revealSignature
|
|
30360
|
+
}),
|
|
30361
|
+
options.json
|
|
30362
|
+
);
|
|
30363
|
+
if (options.wait) {
|
|
30364
|
+
await reportOnchainReceiptStatus({
|
|
30365
|
+
rpcUrl: plan.rpcUrl,
|
|
30366
|
+
txHash: completed.networkTxHash,
|
|
30367
|
+
asJson: options.json
|
|
30368
|
+
});
|
|
30369
|
+
}
|
|
30370
|
+
return;
|
|
30371
|
+
}
|
|
29936
30372
|
const result = await runAgentCommandJson({
|
|
29937
30373
|
commandArgs: [
|
|
29938
30374
|
"transfer-native",
|
|
29939
30375
|
"--network",
|
|
29940
30376
|
String(network),
|
|
29941
30377
|
"--to",
|
|
29942
|
-
|
|
30378
|
+
recipient,
|
|
29943
30379
|
"--amount-wei",
|
|
29944
30380
|
amountWei.toString()
|
|
29945
30381
|
],
|
|
@@ -30018,7 +30454,8 @@ async function main() {
|
|
|
30018
30454
|
],
|
|
30019
30455
|
auth: options,
|
|
30020
30456
|
config,
|
|
30021
|
-
asJson: options.json
|
|
30457
|
+
asJson: options.json,
|
|
30458
|
+
waitForManualApproval: true
|
|
30022
30459
|
});
|
|
30023
30460
|
if (!signed) {
|
|
30024
30461
|
return;
|
|
@@ -30074,29 +30511,46 @@ async function main() {
|
|
|
30074
30511
|
}
|
|
30075
30512
|
});
|
|
30076
30513
|
addAgentCommandAuthOptions(
|
|
30077
|
-
program2.command("broadcast").description("Submit a raw transaction broadcast request through policy checks").requiredOption("--network <name>", "Network name").requiredOption("--to <address>", "Recipient or target contract").requiredOption("--gas-limit <gas>", "Gas limit").requiredOption("--max-fee-per-gas-wei <wei>", "Max fee per gas in wei").option("--nonce <nonce>", "Explicit nonce override"
|
|
30514
|
+
program2.command("broadcast").description("Submit a raw transaction broadcast request through policy checks").requiredOption("--network <name>", "Network name").requiredOption("--to <address>", "Recipient or target contract").requiredOption("--gas-limit <gas>", "Gas limit").requiredOption("--max-fee-per-gas-wei <wei>", "Max fee per gas in wei").option("--nonce <nonce>", "Explicit nonce override").option("--value-wei <wei>", "Value in wei", "0").option("--data-hex <hex>", "Calldata hex", "0x").option("--max-priority-fee-per-gas-wei <wei>", "Priority fee per gas in wei").option("--tx-type <type>", "Typed tx value", "0x02").option("--delegation-enabled", "Forward delegation flag to Rust signing request", false)
|
|
30078
30515
|
).action(async (options) => {
|
|
30079
30516
|
const config = readConfig3();
|
|
30080
30517
|
const network = resolveCliNetworkProfile(options.network, config);
|
|
30081
|
-
const
|
|
30518
|
+
const to = assertAddress(options.to, "to");
|
|
30519
|
+
const valueWei = parseBigIntString(options.valueWei, "valueWei");
|
|
30520
|
+
const dataHex = assertHex(options.dataHex, "dataHex");
|
|
30521
|
+
const gasLimit = parsePositiveBigIntString(options.gasLimit, "gasLimit");
|
|
30522
|
+
const maxFeePerGasWei = parsePositiveBigIntString(options.maxFeePerGasWei, "maxFeePerGasWei");
|
|
30523
|
+
const explicitNonce = options.nonce ? parseIntegerString(options.nonce, "nonce") : void 0;
|
|
30524
|
+
const rpcUrl = resolveCliRpcUrl(void 0, options.network, config);
|
|
30525
|
+
const from14 = resolveWalletAddress(config);
|
|
30526
|
+
const chainInfo = await getChainInfo2(rpcUrl);
|
|
30527
|
+
assertRpcChainIdMatches(network.chainId, chainInfo.chainId);
|
|
30528
|
+
const nonce = explicitNonce ?? await getNonce2(rpcUrl, from14);
|
|
30529
|
+
const fees = options.maxPriorityFeePerGasWei ? null : await estimateFees2(rpcUrl);
|
|
30530
|
+
const maxPriorityFeePerGasWei = options.maxPriorityFeePerGasWei ? parseBigIntString(options.maxPriorityFeePerGasWei, "maxPriorityFeePerGasWei") : resolveEstimatedPriorityFeePerGasWei({
|
|
30531
|
+
gasPrice: fees?.gasPrice ?? null,
|
|
30532
|
+
maxFeePerGas: fees?.maxFeePerGas ?? null,
|
|
30533
|
+
maxPriorityFeePerGas: fees?.maxPriorityFeePerGas ?? null
|
|
30534
|
+
});
|
|
30535
|
+
const signed = await runAgentCommandJson({
|
|
30082
30536
|
commandArgs: [
|
|
30083
30537
|
"broadcast",
|
|
30084
30538
|
"--network",
|
|
30085
30539
|
String(network.chainId),
|
|
30086
30540
|
"--nonce",
|
|
30087
|
-
String(
|
|
30541
|
+
String(nonce),
|
|
30088
30542
|
"--to",
|
|
30089
|
-
|
|
30543
|
+
to,
|
|
30090
30544
|
"--value-wei",
|
|
30091
|
-
|
|
30545
|
+
valueWei.toString(),
|
|
30092
30546
|
"--data-hex",
|
|
30093
|
-
|
|
30547
|
+
dataHex,
|
|
30094
30548
|
"--gas-limit",
|
|
30095
|
-
|
|
30549
|
+
gasLimit.toString(),
|
|
30096
30550
|
"--max-fee-per-gas-wei",
|
|
30097
|
-
|
|
30551
|
+
maxFeePerGasWei.toString(),
|
|
30098
30552
|
"--max-priority-fee-per-gas-wei",
|
|
30099
|
-
|
|
30553
|
+
maxPriorityFeePerGasWei.toString(),
|
|
30100
30554
|
"--tx-type",
|
|
30101
30555
|
options.txType,
|
|
30102
30556
|
...options.delegationEnabled ? ["--delegation-enabled"] : []
|
|
@@ -30105,9 +30559,28 @@ async function main() {
|
|
|
30105
30559
|
config,
|
|
30106
30560
|
asJson: options.json
|
|
30107
30561
|
});
|
|
30108
|
-
if (
|
|
30109
|
-
|
|
30562
|
+
if (!signed) {
|
|
30563
|
+
return;
|
|
30564
|
+
}
|
|
30565
|
+
if (!signed.raw_tx_hex) {
|
|
30566
|
+
throw new Error("Rust agent did not return raw_tx_hex for broadcast signing");
|
|
30110
30567
|
}
|
|
30568
|
+
await assertSignedBroadcastTransactionMatchesRequest({
|
|
30569
|
+
rawTxHex: signed.raw_tx_hex,
|
|
30570
|
+
from: from14,
|
|
30571
|
+
to,
|
|
30572
|
+
chainId: network.chainId,
|
|
30573
|
+
nonce,
|
|
30574
|
+
allowHigherNonce: false,
|
|
30575
|
+
value: valueWei,
|
|
30576
|
+
data: dataHex,
|
|
30577
|
+
gasLimit,
|
|
30578
|
+
maxFeePerGas: maxFeePerGasWei,
|
|
30579
|
+
maxPriorityFeePerGas: maxPriorityFeePerGasWei,
|
|
30580
|
+
txType: options.txType
|
|
30581
|
+
});
|
|
30582
|
+
await broadcastRawTransaction2(rpcUrl, signed.raw_tx_hex);
|
|
30583
|
+
print2(signed, options.json);
|
|
30111
30584
|
});
|
|
30112
30585
|
const rpc = program2.command("rpc").description("RPC methods implemented in TypeScript");
|
|
30113
30586
|
rpc.command("chain").option("--rpc-url <url>", "Ethereum RPC URL").option("--json", "Print JSON output", false).action(async (options) => {
|
|
@@ -30319,7 +30792,7 @@ async function main() {
|
|
|
30319
30792
|
to,
|
|
30320
30793
|
chainId,
|
|
30321
30794
|
nonce,
|
|
30322
|
-
allowHigherNonce:
|
|
30795
|
+
allowHigherNonce: false,
|
|
30323
30796
|
value: valueWei,
|
|
30324
30797
|
data: dataHex,
|
|
30325
30798
|
gasLimit,
|