@wlfi-agent/cli 1.4.12 → 1.4.14
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 +3968 -0
- package/Cargo.toml +50 -0
- package/README.md +426 -6
- package/crates/vault-cli-admin/Cargo.toml +26 -0
- package/crates/vault-cli-admin/src/io_utils.rs +500 -0
- package/crates/vault-cli-admin/src/main.rs +3990 -0
- package/crates/vault-cli-admin/src/shared_config.rs +624 -0
- package/crates/vault-cli-admin/src/tui/amounts.rs +180 -0
- package/crates/vault-cli-admin/src/tui/token_rpc.rs +250 -0
- package/crates/vault-cli-admin/src/tui/utils.rs +82 -0
- package/crates/vault-cli-admin/src/tui.rs +3410 -0
- package/crates/vault-cli-agent/Cargo.toml +24 -0
- package/crates/vault-cli-agent/src/io_utils.rs +576 -0
- package/crates/vault-cli-agent/src/main.rs +833 -0
- package/crates/vault-cli-daemon/Cargo.toml +28 -0
- package/crates/vault-cli-daemon/src/bin/wlfi-agent-system-keychain.rs +216 -0
- package/crates/vault-cli-daemon/src/main.rs +644 -0
- package/crates/vault-cli-daemon/src/relay_sync.rs +894 -0
- package/crates/vault-cli-daemon/tests/system_keychain_helper_acl.rs +167 -0
- package/crates/vault-daemon/Cargo.toml +32 -0
- package/crates/vault-daemon/src/daemon_parts/api_impl_and_utils.rs +1041 -0
- package/crates/vault-daemon/src/daemon_parts/core_helpers.rs +1256 -0
- package/crates/vault-daemon/src/daemon_parts/types_api_rpc.rs +622 -0
- package/crates/vault-daemon/src/lib.rs +54 -0
- package/crates/vault-daemon/src/persistence.rs +441 -0
- package/crates/vault-daemon/src/tests.rs +237 -0
- package/crates/vault-daemon/src/tests_parts/part1.rs +1224 -0
- package/crates/vault-daemon/src/tests_parts/part2.rs +1021 -0
- package/crates/vault-daemon/src/tests_parts/part3.rs +835 -0
- package/crates/vault-daemon/src/tests_parts/part4.rs +604 -0
- package/crates/vault-domain/Cargo.toml +20 -0
- package/crates/vault-domain/src/action.rs +849 -0
- package/crates/vault-domain/src/address.rs +51 -0
- package/crates/vault-domain/src/approval.rs +90 -0
- package/crates/vault-domain/src/constants.rs +4 -0
- package/crates/vault-domain/src/error.rs +54 -0
- package/crates/vault-domain/src/keys.rs +71 -0
- package/crates/vault-domain/src/lib.rs +42 -0
- package/crates/vault-domain/src/nonce.rs +102 -0
- package/crates/vault-domain/src/policy.rs +172 -0
- package/crates/vault-domain/src/request.rs +53 -0
- package/crates/vault-domain/src/scope.rs +24 -0
- package/crates/vault-domain/src/session.rs +50 -0
- package/crates/vault-domain/src/signature.rs +34 -0
- package/crates/vault-domain/src/tests.rs +651 -0
- package/crates/vault-domain/src/u128_as_decimal_string.rs +44 -0
- package/crates/vault-policy/Cargo.toml +17 -0
- package/crates/vault-policy/src/engine.rs +301 -0
- package/crates/vault-policy/src/error.rs +81 -0
- package/crates/vault-policy/src/lib.rs +17 -0
- package/crates/vault-policy/src/report.rs +34 -0
- package/crates/vault-policy/src/tests.rs +891 -0
- package/crates/vault-policy/src/tests_explain.rs +78 -0
- package/crates/vault-sdk-agent/Cargo.toml +21 -0
- package/crates/vault-sdk-agent/src/lib.rs +711 -0
- package/crates/vault-signer/Cargo.toml +25 -0
- package/crates/vault-signer/src/lib.rs +731 -0
- package/crates/vault-signer/tests/secure_enclave_acl.rs +54 -0
- package/crates/vault-transport-unix/Cargo.toml +24 -0
- package/crates/vault-transport-unix/src/lib.rs +1640 -0
- package/crates/vault-transport-xpc/Cargo.toml +25 -0
- package/crates/vault-transport-xpc/src/client_codec_api.rs +635 -0
- package/crates/vault-transport-xpc/src/lib.rs +680 -0
- package/crates/vault-transport-xpc/src/tests.rs +818 -0
- package/crates/vault-transport-xpc/tests/e2e_flow.rs +773 -0
- package/dist/cli.cjs +35088 -0
- package/dist/cli.cjs.map +1 -0
- package/package.json +49 -43
- package/packages/cache/.turbo/turbo-build.log +52 -0
- package/packages/cache/dist/chunk-2QFWMUXT.cjs +43 -0
- package/packages/cache/dist/chunk-2QFWMUXT.cjs.map +1 -0
- package/packages/cache/dist/chunk-4U63TZTQ.js +43 -0
- package/packages/cache/dist/chunk-4U63TZTQ.js.map +1 -0
- package/packages/cache/dist/chunk-ALQ6H7KG.cjs +404 -0
- package/packages/cache/dist/chunk-ALQ6H7KG.cjs.map +1 -0
- package/packages/cache/dist/chunk-FGJEEF5N.js +404 -0
- package/packages/cache/dist/chunk-FGJEEF5N.js.map +1 -0
- package/packages/cache/dist/chunk-UYNEHZHB.cjs +45 -0
- package/packages/cache/dist/chunk-UYNEHZHB.cjs.map +1 -0
- package/packages/cache/dist/chunk-VXVMPG3W.js +45 -0
- package/packages/cache/dist/chunk-VXVMPG3W.js.map +1 -0
- package/packages/cache/dist/client/index.cjs +11 -0
- package/packages/cache/dist/client/index.cjs.map +1 -0
- package/packages/cache/dist/client/index.d.cts +15 -0
- package/packages/cache/dist/client/index.d.ts +15 -0
- package/packages/cache/dist/client/index.js +11 -0
- package/packages/cache/dist/client/index.js.map +1 -0
- package/packages/cache/dist/errors/index.cjs +11 -0
- package/packages/cache/dist/errors/index.cjs.map +1 -0
- package/packages/cache/dist/errors/index.d.cts +26 -0
- package/packages/cache/dist/errors/index.d.ts +26 -0
- package/packages/cache/dist/errors/index.js +11 -0
- package/packages/cache/dist/errors/index.js.map +1 -0
- package/packages/cache/dist/index.cjs +29 -0
- package/packages/cache/dist/index.cjs.map +1 -0
- package/packages/cache/dist/index.d.cts +4 -0
- package/packages/cache/dist/index.d.ts +4 -0
- package/packages/cache/dist/index.js +29 -0
- package/packages/cache/dist/index.js.map +1 -0
- package/packages/cache/dist/service/index.cjs +15 -0
- package/packages/cache/dist/service/index.cjs.map +1 -0
- package/packages/cache/dist/service/index.d.cts +184 -0
- package/packages/cache/dist/service/index.d.ts +184 -0
- package/packages/cache/dist/service/index.js +15 -0
- package/packages/cache/dist/service/index.js.map +1 -0
- package/packages/cache/node_modules/.bin/jiti +17 -0
- package/packages/cache/node_modules/.bin/tsc +17 -0
- package/packages/cache/node_modules/.bin/tsserver +17 -0
- package/packages/cache/node_modules/.bin/tsup +17 -0
- package/packages/cache/node_modules/.bin/tsup-node +17 -0
- package/packages/cache/node_modules/.bin/tsx +17 -0
- package/packages/cache/node_modules/.bin/vitest +17 -0
- package/packages/cache/package.json +48 -0
- package/packages/cache/src/client/index.ts +56 -0
- package/packages/cache/src/errors/index.ts +53 -0
- package/packages/cache/src/index.ts +3 -0
- package/packages/cache/src/service/index.test.ts +263 -0
- package/packages/cache/src/service/index.ts +678 -0
- package/packages/cache/tsconfig.json +13 -0
- package/packages/cache/tsup.config.ts +13 -0
- package/packages/cache/vitest.config.ts +16 -0
- package/packages/config/.turbo/turbo-build.log +18 -0
- package/packages/config/dist/index.cjs +1037 -0
- package/packages/config/dist/index.cjs.map +1 -0
- package/packages/config/dist/index.d.ts +131 -0
- package/packages/config/node_modules/.bin/jiti +17 -0
- package/packages/config/node_modules/.bin/tsc +17 -0
- package/packages/config/node_modules/.bin/tsserver +17 -0
- package/packages/config/node_modules/.bin/tsup +17 -0
- package/packages/config/node_modules/.bin/tsup-node +17 -0
- package/packages/config/node_modules/.bin/tsx +17 -0
- package/packages/config/package.json +21 -0
- package/packages/config/src/index.js +1 -0
- package/packages/config/src/index.ts +1282 -0
- package/packages/config/tsconfig.json +4 -0
- package/packages/rpc/.turbo/turbo-build.log +32 -0
- package/packages/rpc/dist/_esm-BCLXDO2R.cjs +3660 -0
- package/packages/rpc/dist/_esm-BCLXDO2R.cjs.map +1 -0
- package/packages/rpc/dist/ccip-OWJLAW55.cjs +16 -0
- package/packages/rpc/dist/ccip-OWJLAW55.cjs.map +1 -0
- package/packages/rpc/dist/chunk-APQIFZ3B.cjs +6247 -0
- package/packages/rpc/dist/chunk-APQIFZ3B.cjs.map +1 -0
- package/packages/rpc/dist/chunk-CDO2GWRD.cjs +410 -0
- package/packages/rpc/dist/chunk-CDO2GWRD.cjs.map +1 -0
- package/packages/rpc/dist/chunk-QGTNTFJ7.cjs +2249 -0
- package/packages/rpc/dist/chunk-QGTNTFJ7.cjs.map +1 -0
- package/packages/rpc/dist/chunk-TZDTAHWR.cjs +44 -0
- package/packages/rpc/dist/chunk-TZDTAHWR.cjs.map +1 -0
- package/packages/rpc/dist/index.cjs +7342 -0
- package/packages/rpc/dist/index.cjs.map +1 -0
- package/packages/rpc/dist/index.d.ts +3857 -0
- package/packages/rpc/dist/secp256k1-WCNM675D.cjs +18 -0
- package/packages/rpc/dist/secp256k1-WCNM675D.cjs.map +1 -0
- package/packages/rpc/node_modules/.bin/jiti +17 -0
- package/packages/rpc/node_modules/.bin/tsc +17 -0
- package/packages/rpc/node_modules/.bin/tsserver +17 -0
- package/packages/rpc/node_modules/.bin/tsup +17 -0
- package/packages/rpc/node_modules/.bin/tsup-node +17 -0
- package/packages/rpc/node_modules/.bin/tsx +17 -0
- package/packages/rpc/package.json +25 -0
- package/packages/rpc/src/index.ts +206 -0
- package/packages/rpc/tsconfig.json +4 -0
- package/packages/typescript/base.json +36 -0
- package/packages/typescript/nextjs.json +17 -0
- package/packages/typescript/package.json +10 -0
- package/packages/ui/.turbo/turbo-build.log +44 -0
- package/packages/ui/dist/chunk-MOAFBKSA.js +11 -0
- package/packages/ui/dist/chunk-MOAFBKSA.js.map +1 -0
- package/packages/ui/dist/components/badge.d.ts +12 -0
- package/packages/ui/dist/components/badge.js +31 -0
- package/packages/ui/dist/components/badge.js.map +1 -0
- package/packages/ui/dist/components/button.d.ts +13 -0
- package/packages/ui/dist/components/button.js +40 -0
- package/packages/ui/dist/components/button.js.map +1 -0
- package/packages/ui/dist/components/card.d.ts +10 -0
- package/packages/ui/dist/components/card.js +39 -0
- package/packages/ui/dist/components/card.js.map +1 -0
- package/packages/ui/dist/components/input.d.ts +5 -0
- package/packages/ui/dist/components/input.js +28 -0
- package/packages/ui/dist/components/input.js.map +1 -0
- package/packages/ui/dist/components/label.d.ts +5 -0
- package/packages/ui/dist/components/label.js +13 -0
- package/packages/ui/dist/components/label.js.map +1 -0
- package/packages/ui/dist/components/separator.d.ts +5 -0
- package/packages/ui/dist/components/separator.js +13 -0
- package/packages/ui/dist/components/separator.js.map +1 -0
- package/packages/ui/dist/components/textarea.d.ts +5 -0
- package/packages/ui/dist/components/textarea.js +27 -0
- package/packages/ui/dist/components/textarea.js.map +1 -0
- package/packages/ui/dist/tailwind.d.ts +56 -0
- package/packages/ui/dist/tailwind.js +60 -0
- package/packages/ui/dist/tailwind.js.map +1 -0
- package/packages/ui/dist/utils/cn.d.ts +5 -0
- package/packages/ui/dist/utils/cn.js +7 -0
- package/packages/ui/dist/utils/cn.js.map +1 -0
- package/packages/ui/node_modules/.bin/jiti +17 -0
- package/packages/ui/node_modules/.bin/tsc +17 -0
- package/packages/ui/node_modules/.bin/tsserver +17 -0
- package/packages/ui/node_modules/.bin/tsup +17 -0
- package/packages/ui/node_modules/.bin/tsup-node +17 -0
- package/packages/ui/node_modules/.bin/tsx +17 -0
- package/packages/ui/package.json +69 -0
- package/packages/ui/src/components/badge.tsx +27 -0
- package/packages/ui/src/components/button.tsx +40 -0
- package/packages/ui/src/components/card.tsx +31 -0
- package/packages/ui/src/components/input.tsx +21 -0
- package/packages/ui/src/components/label.tsx +6 -0
- package/packages/ui/src/components/separator.tsx +6 -0
- package/packages/ui/src/components/textarea.tsx +20 -0
- package/packages/ui/src/globals.css +70 -0
- package/packages/ui/src/tailwind.ts +56 -0
- package/packages/ui/src/utils/cn.ts +6 -0
- package/packages/ui/tsconfig.json +20 -0
- package/packages/ui/tsup.config.ts +20 -0
- package/pnpm-workspace.yaml +4 -0
- package/scripts/install-rust-binaries.mjs +84 -0
- package/scripts/launchd/install-user-daemon.sh +358 -0
- package/scripts/launchd/run-vault-daemon.sh +5 -0
- package/scripts/launchd/run-wlfi-agent-daemon.sh +73 -0
- package/scripts/launchd/uninstall-user-daemon.sh +103 -0
- package/src/cli.ts +2121 -0
- package/src/lib/admin-guard.js +1 -0
- package/src/lib/admin-guard.ts +185 -0
- package/src/lib/admin-passthrough.ts +33 -0
- package/src/lib/admin-reset.ts +751 -0
- package/src/lib/admin-setup.ts +1612 -0
- package/src/lib/agent-auth-clear.js +1 -0
- package/src/lib/agent-auth-clear.ts +58 -0
- package/src/lib/agent-auth-forwarding.js +1 -0
- package/src/lib/agent-auth-forwarding.ts +149 -0
- package/src/lib/agent-auth-migrate.js +1 -0
- package/src/lib/agent-auth-migrate.ts +150 -0
- package/src/lib/agent-auth-revoke.ts +103 -0
- package/src/lib/agent-auth-rotate.ts +107 -0
- package/src/lib/agent-auth-token.js +1 -0
- package/src/lib/agent-auth-token.ts +25 -0
- package/src/lib/agent-auth.ts +89 -0
- package/src/lib/asset-broadcast.js +1 -0
- package/src/lib/asset-broadcast.ts +285 -0
- package/src/lib/bootstrap-artifacts.js +1 -0
- package/src/lib/bootstrap-artifacts.ts +205 -0
- package/src/lib/bootstrap-credentials.js +1 -0
- package/src/lib/bootstrap-credentials.ts +832 -0
- package/src/lib/config-amounts.js +1 -0
- package/src/lib/config-amounts.ts +189 -0
- package/src/lib/config-mutation.ts +27 -0
- package/src/lib/fs-trust.js +1 -0
- package/src/lib/fs-trust.ts +537 -0
- package/src/lib/keychain.js +1 -0
- package/src/lib/keychain.ts +225 -0
- package/src/lib/local-admin-access.ts +106 -0
- package/src/lib/network-selection.js +1 -0
- package/src/lib/network-selection.ts +71 -0
- package/src/lib/passthrough-security.js +1 -0
- package/src/lib/passthrough-security.ts +114 -0
- package/src/lib/rpc-guard.js +1 -0
- package/src/lib/rpc-guard.ts +7 -0
- package/src/lib/rust-spawn-options.js +1 -0
- package/src/lib/rust-spawn-options.ts +98 -0
- package/src/lib/rust.js +1 -0
- package/src/lib/rust.ts +143 -0
- package/src/lib/signed-tx.js +1 -0
- package/src/lib/signed-tx.ts +116 -0
- package/src/lib/status-repair-cli.ts +116 -0
- package/src/lib/sudo.js +1 -0
- package/src/lib/sudo.ts +172 -0
- package/src/lib/vault-password-forwarding.js +1 -0
- package/src/lib/vault-password-forwarding.ts +155 -0
- package/src/lib/wallet-profile.js +1 -0
- package/src/lib/wallet-profile.ts +332 -0
- package/src/lib/wallet-repair.js +1 -0
- package/src/lib/wallet-repair.ts +304 -0
- package/src/lib/wallet-setup.js +1 -0
- package/src/lib/wallet-setup.ts +1466 -0
- package/src/lib/wallet-status.js +1 -0
- package/src/lib/wallet-status.ts +640 -0
- package/tsconfig.base.json +17 -0
- package/tsconfig.json +10 -0
- package/tsup.config.ts +25 -0
- package/turbo.json +41 -0
- package/LICENSE.md +0 -1
- package/dist/wlfa/index.cjs +0 -250
- package/dist/wlfa/index.d.cts +0 -1
- package/dist/wlfa/index.d.ts +0 -1
- package/dist/wlfa/index.js +0 -250
- package/dist/wlfc/index.cjs +0 -1894
- package/dist/wlfc/index.d.cts +0 -1
- package/dist/wlfc/index.d.ts +0 -1
- package/dist/wlfc/index.js +0 -1894
package/src/cli.ts
ADDED
|
@@ -0,0 +1,2121 @@
|
|
|
1
|
+
import {
|
|
2
|
+
type ChainProfile,
|
|
3
|
+
defaultDaemonSocketPath,
|
|
4
|
+
deleteConfigKey,
|
|
5
|
+
ensureWlfiHome,
|
|
6
|
+
listBuiltinChains,
|
|
7
|
+
listBuiltinTokens,
|
|
8
|
+
listConfiguredChains,
|
|
9
|
+
listConfiguredTokens,
|
|
10
|
+
readConfig,
|
|
11
|
+
redactConfig,
|
|
12
|
+
removeChainProfile,
|
|
13
|
+
removeTokenChainProfile,
|
|
14
|
+
removeTokenProfile,
|
|
15
|
+
resolveChainProfile,
|
|
16
|
+
resolveConfigPath,
|
|
17
|
+
resolveTokenProfile,
|
|
18
|
+
saveChainProfile,
|
|
19
|
+
saveTokenChainProfile,
|
|
20
|
+
switchActiveChain,
|
|
21
|
+
type TokenChainProfile,
|
|
22
|
+
type WlfiConfig,
|
|
23
|
+
writeConfig,
|
|
24
|
+
} from '@wlfi-agent/config';
|
|
25
|
+
import {
|
|
26
|
+
broadcastRawTransaction,
|
|
27
|
+
estimateFees,
|
|
28
|
+
estimateGas,
|
|
29
|
+
getAccountSnapshot,
|
|
30
|
+
getChainInfo,
|
|
31
|
+
getCodeAtAddress,
|
|
32
|
+
getLatestBlockNumber,
|
|
33
|
+
getNativeBalance,
|
|
34
|
+
getNonce,
|
|
35
|
+
getTokenBalance,
|
|
36
|
+
getTransactionByHash,
|
|
37
|
+
getTransactionReceiptByHash,
|
|
38
|
+
} from '@wlfi-agent/rpc';
|
|
39
|
+
import { Command, Option } from 'commander';
|
|
40
|
+
import { type Address, type Hex, isAddress, isHex } from 'viem';
|
|
41
|
+
import { assertSafeRpcUrl } from '../packages/config/src/index.js';
|
|
42
|
+
import {
|
|
43
|
+
blockedRawAdminPassthroughMessage,
|
|
44
|
+
rewriteAdminHelpText,
|
|
45
|
+
} from './lib/admin-passthrough.js';
|
|
46
|
+
import { runAdminResetCli, runAdminUninstallCli } from './lib/admin-reset.js';
|
|
47
|
+
import { runAdminSetupCli, runAdminTuiCli } from './lib/admin-setup.js';
|
|
48
|
+
import { resolveAgentAuthToken } from './lib/agent-auth.js';
|
|
49
|
+
import { clearAgentAuthToken } from './lib/agent-auth-clear.js';
|
|
50
|
+
import { migrateLegacyAgentAuthToken } from './lib/agent-auth-migrate.js';
|
|
51
|
+
import {
|
|
52
|
+
buildRevokeAgentKeyAdminArgs,
|
|
53
|
+
completeAgentKeyRevocation,
|
|
54
|
+
type RevokeAgentKeyAdminOutput,
|
|
55
|
+
} from './lib/agent-auth-revoke.js';
|
|
56
|
+
import {
|
|
57
|
+
buildRotateAgentAuthTokenAdminArgs,
|
|
58
|
+
completeAgentAuthRotation,
|
|
59
|
+
type RotateAgentAuthTokenAdminOutput,
|
|
60
|
+
} from './lib/agent-auth-rotate.js';
|
|
61
|
+
import { assertValidAgentAuthToken } from './lib/agent-auth-token.js';
|
|
62
|
+
import {
|
|
63
|
+
completeAssetBroadcast,
|
|
64
|
+
encodeErc20ApproveData,
|
|
65
|
+
encodeErc20TransferData,
|
|
66
|
+
formatBroadcastedAssetOutput,
|
|
67
|
+
resolveAssetBroadcastPlan,
|
|
68
|
+
waitForOnchainReceipt,
|
|
69
|
+
} from './lib/asset-broadcast.js';
|
|
70
|
+
import {
|
|
71
|
+
assertBootstrapSetupSummaryLeaseIsActive,
|
|
72
|
+
deleteBootstrapAgentCredentialsFile,
|
|
73
|
+
readBootstrapAgentCredentialsFile,
|
|
74
|
+
readBootstrapSetupSummaryFile,
|
|
75
|
+
redactBootstrapAgentCredentialsFile,
|
|
76
|
+
} from './lib/bootstrap-credentials.js';
|
|
77
|
+
import {
|
|
78
|
+
normalizeAgentAmountOutput,
|
|
79
|
+
normalizePositiveDecimalInput,
|
|
80
|
+
parseConfiguredAmount,
|
|
81
|
+
resolveConfiguredErc20Asset,
|
|
82
|
+
resolveConfiguredNativeAsset,
|
|
83
|
+
rewriteAmountPolicyErrorMessage,
|
|
84
|
+
} from './lib/config-amounts.js';
|
|
85
|
+
import {
|
|
86
|
+
assertWritableConfigKey,
|
|
87
|
+
resolveConfigMutationCommandLabel,
|
|
88
|
+
type WritableConfigKey,
|
|
89
|
+
} from './lib/config-mutation.js';
|
|
90
|
+
import {
|
|
91
|
+
assertTrustedAdminDaemonSocketPath,
|
|
92
|
+
assertTrustedDaemonSocketPath,
|
|
93
|
+
} from './lib/fs-trust.js';
|
|
94
|
+
import {
|
|
95
|
+
AGENT_AUTH_TOKEN_KEYCHAIN_SERVICE,
|
|
96
|
+
hasAgentAuthTokenInKeychain,
|
|
97
|
+
readAgentAuthTokenFromKeychain,
|
|
98
|
+
storeAgentAuthTokenInKeychain,
|
|
99
|
+
} from './lib/keychain.js';
|
|
100
|
+
import {
|
|
101
|
+
withDynamicLocalAdminMutationAccess,
|
|
102
|
+
withLocalAdminMutationAccess,
|
|
103
|
+
} from './lib/local-admin-access.js';
|
|
104
|
+
import { resolveCliNetworkProfile, resolveCliRpcUrl } from './lib/network-selection.js';
|
|
105
|
+
import { assertRpcChainIdMatches } from './lib/rpc-guard.js';
|
|
106
|
+
import {
|
|
107
|
+
passthroughRustBinary,
|
|
108
|
+
RustBinaryExitError,
|
|
109
|
+
runRustBinary,
|
|
110
|
+
runRustBinaryJson,
|
|
111
|
+
} from './lib/rust.js';
|
|
112
|
+
import { assertSignedBroadcastTransactionMatchesRequest } from './lib/signed-tx.js';
|
|
113
|
+
import { registerRepairCommand, registerStatusCommand } from './lib/status-repair-cli.js';
|
|
114
|
+
import {
|
|
115
|
+
formatWalletProfileText,
|
|
116
|
+
resolveWalletAddress,
|
|
117
|
+
resolveWalletProfileWithBalances,
|
|
118
|
+
walletProfileFromBootstrapSummary,
|
|
119
|
+
} from './lib/wallet-profile.js';
|
|
120
|
+
|
|
121
|
+
interface RustBroadcastOutput {
|
|
122
|
+
command: string;
|
|
123
|
+
network: string;
|
|
124
|
+
asset: string;
|
|
125
|
+
counterparty: string;
|
|
126
|
+
amount_wei: string;
|
|
127
|
+
estimated_max_gas_spend_wei?: string;
|
|
128
|
+
tx_type?: string;
|
|
129
|
+
delegation_enabled?: boolean;
|
|
130
|
+
signature_hex: string;
|
|
131
|
+
r_hex?: string;
|
|
132
|
+
s_hex?: string;
|
|
133
|
+
v?: number;
|
|
134
|
+
raw_tx_hex?: string;
|
|
135
|
+
tx_hash_hex?: string;
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
interface RustManualApprovalRequiredOutput {
|
|
139
|
+
command: string;
|
|
140
|
+
approval_request_id: string;
|
|
141
|
+
relay_url?: string;
|
|
142
|
+
frontend_url?: string;
|
|
143
|
+
cli_approval_command: string;
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
function rewriteAgentAmountError(
|
|
147
|
+
error: unknown,
|
|
148
|
+
symbolAwareAsset: { decimals: number; symbol: string; assetId: string },
|
|
149
|
+
): Error {
|
|
150
|
+
if (!(error instanceof Error)) {
|
|
151
|
+
return new Error(String(error));
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
const rewritten = rewriteAmountPolicyErrorMessage(error.message, symbolAwareAsset);
|
|
155
|
+
if (rewritten === error.message) {
|
|
156
|
+
return error;
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
return new Error(rewritten);
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
function isManualApprovalRequiredOutput(value: unknown): value is RustManualApprovalRequiredOutput {
|
|
163
|
+
if (!value || typeof value !== 'object') {
|
|
164
|
+
return false;
|
|
165
|
+
}
|
|
166
|
+
const candidate = value as Partial<RustManualApprovalRequiredOutput>;
|
|
167
|
+
return (
|
|
168
|
+
typeof candidate.command === 'string' &&
|
|
169
|
+
typeof candidate.approval_request_id === 'string' &&
|
|
170
|
+
typeof candidate.cli_approval_command === 'string'
|
|
171
|
+
);
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
function printManualApprovalRequired(output: RustManualApprovalRequiredOutput, asJson: boolean) {
|
|
175
|
+
if (asJson) {
|
|
176
|
+
print(output, true);
|
|
177
|
+
return;
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
const lines = [
|
|
181
|
+
`Command: ${output.command}`,
|
|
182
|
+
`Approval Request ID: ${output.approval_request_id}`,
|
|
183
|
+
];
|
|
184
|
+
if (output.frontend_url) {
|
|
185
|
+
lines.push(`Frontend Approval URL: ${output.frontend_url}`);
|
|
186
|
+
}
|
|
187
|
+
if (output.relay_url) {
|
|
188
|
+
lines.push(`Relay URL: ${output.relay_url}`);
|
|
189
|
+
}
|
|
190
|
+
lines.push(`CLI Approval Command: ${output.cli_approval_command}`);
|
|
191
|
+
print(lines.join('\n'), false);
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
const MAX_SECRET_STDIN_BYTES = 16 * 1024;
|
|
195
|
+
const AGENT_KEY_ID_PATTERN = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/iu;
|
|
196
|
+
|
|
197
|
+
function requiredString(value: string | undefined, label: string): string {
|
|
198
|
+
if (!value?.trim()) {
|
|
199
|
+
throw new Error(`${label} is required`);
|
|
200
|
+
}
|
|
201
|
+
return value;
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
function assertAgentKeyId(value: string | undefined, label = 'agentKeyId'): string {
|
|
205
|
+
const normalized = requiredString(value, label).trim();
|
|
206
|
+
if (!AGENT_KEY_ID_PATTERN.test(normalized)) {
|
|
207
|
+
throw new Error(`${label} must be a valid UUID`);
|
|
208
|
+
}
|
|
209
|
+
return normalized;
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
function hasStoredAgentAuthToken(agentKeyId: string | undefined): boolean {
|
|
213
|
+
if (!agentKeyId) {
|
|
214
|
+
return false;
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
try {
|
|
218
|
+
return hasAgentAuthTokenInKeychain(agentKeyId);
|
|
219
|
+
} catch {
|
|
220
|
+
return false;
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
function resolveRpcUrl(value: string | undefined, config: WlfiConfig): string {
|
|
225
|
+
return assertSafeRpcUrl(requiredString(value ?? config.rpcUrl, 'rpcUrl'), 'rpcUrl');
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
function resolveDaemonSocket(value: string | undefined, config: WlfiConfig): string {
|
|
229
|
+
if (value !== undefined) {
|
|
230
|
+
return requiredString(value, 'daemonSocket').trim();
|
|
231
|
+
}
|
|
232
|
+
if (config.daemonSocket !== undefined) {
|
|
233
|
+
return requiredString(config.daemonSocket, 'configured daemonSocket').trim();
|
|
234
|
+
}
|
|
235
|
+
return defaultDaemonSocketPath();
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
function assertAddress(value: string, label: string): Address {
|
|
239
|
+
if (!isAddress(value)) {
|
|
240
|
+
throw new Error(`${label} must be a valid EVM address`);
|
|
241
|
+
}
|
|
242
|
+
return value as Address;
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
function assertHex(value: string, label: string): Hex {
|
|
246
|
+
if (!isHex(value)) {
|
|
247
|
+
throw new Error(`${label} must be valid hex`);
|
|
248
|
+
}
|
|
249
|
+
return value as Hex;
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
function parseBigIntString(value: string, label: string): bigint {
|
|
253
|
+
const normalized = value.trim();
|
|
254
|
+
if (!/^(0|[1-9][0-9]*)$/u.test(normalized)) {
|
|
255
|
+
throw new Error(`${label} must be a non-negative integer string`);
|
|
256
|
+
}
|
|
257
|
+
try {
|
|
258
|
+
return BigInt(normalized);
|
|
259
|
+
} catch {
|
|
260
|
+
throw new Error(`${label} must be a non-negative integer string`);
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
function parseIntegerString(value: string, label: string): number {
|
|
265
|
+
const normalized = value.trim();
|
|
266
|
+
if (!/^(0|[1-9][0-9]*)$/u.test(normalized)) {
|
|
267
|
+
throw new Error(`${label} must be a non-negative integer`);
|
|
268
|
+
}
|
|
269
|
+
const parsed = Number(normalized);
|
|
270
|
+
if (!Number.isSafeInteger(parsed)) {
|
|
271
|
+
throw new Error(`${label} must be a safe integer`);
|
|
272
|
+
}
|
|
273
|
+
return parsed;
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
function parsePositiveBigIntString(value: string, label: string): bigint {
|
|
277
|
+
const parsed = parseBigIntString(value, label);
|
|
278
|
+
if (parsed <= 0n) {
|
|
279
|
+
throw new Error(`${label} must be greater than zero`);
|
|
280
|
+
}
|
|
281
|
+
return parsed;
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
function parsePositiveIntegerString(value: string, label: string): number {
|
|
285
|
+
const parsed = parseIntegerString(value, label);
|
|
286
|
+
if (parsed <= 0) {
|
|
287
|
+
throw new Error(`${label} must be greater than zero`);
|
|
288
|
+
}
|
|
289
|
+
return parsed;
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
async function readTrimmedStdin(label: string): Promise<string> {
|
|
293
|
+
process.stdin.setEncoding('utf8');
|
|
294
|
+
let raw = '';
|
|
295
|
+
for await (const chunk of process.stdin) {
|
|
296
|
+
raw += chunk;
|
|
297
|
+
if (Buffer.byteLength(raw, 'utf8') > MAX_SECRET_STDIN_BYTES) {
|
|
298
|
+
throw new Error(`${label} must not exceed ${MAX_SECRET_STDIN_BYTES} bytes`);
|
|
299
|
+
}
|
|
300
|
+
}
|
|
301
|
+
return requiredString(raw.replace(/[\r\n]+$/u, ''), label);
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
function formatJson(payload: unknown) {
|
|
305
|
+
return JSON.stringify(payload, null, 2);
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
function stringifyOptionalValue(value: { toString(): string } | null | undefined): string | null {
|
|
309
|
+
return value === null || value === undefined ? null : value.toString();
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
function print(payload: unknown, asJson: boolean) {
|
|
313
|
+
if (asJson) {
|
|
314
|
+
console.log(formatJson(payload));
|
|
315
|
+
return;
|
|
316
|
+
}
|
|
317
|
+
if (typeof payload === 'string') {
|
|
318
|
+
console.log(payload);
|
|
319
|
+
return;
|
|
320
|
+
}
|
|
321
|
+
console.log(formatJson(payload));
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
const ONCHAIN_RECEIPT_TIMEOUT_MS = 30_000;
|
|
325
|
+
const ONCHAIN_RECEIPT_POLL_INTERVAL_MS = 2_000;
|
|
326
|
+
|
|
327
|
+
async function reportOnchainReceiptStatus(input: {
|
|
328
|
+
rpcUrl: string;
|
|
329
|
+
txHash: Hex;
|
|
330
|
+
asJson: boolean;
|
|
331
|
+
}): Promise<void> {
|
|
332
|
+
if (input.asJson) {
|
|
333
|
+
console.error(
|
|
334
|
+
formatJson({
|
|
335
|
+
event: 'onchainReceiptPending',
|
|
336
|
+
txHash: input.txHash,
|
|
337
|
+
timeoutMs: ONCHAIN_RECEIPT_TIMEOUT_MS,
|
|
338
|
+
}),
|
|
339
|
+
);
|
|
340
|
+
} else {
|
|
341
|
+
console.error(
|
|
342
|
+
`Waiting up to ${ONCHAIN_RECEIPT_TIMEOUT_MS / 1000}s for on-chain receipt: ${input.txHash}`,
|
|
343
|
+
);
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
try {
|
|
347
|
+
const result = await waitForOnchainReceipt(
|
|
348
|
+
{
|
|
349
|
+
rpcUrl: input.rpcUrl,
|
|
350
|
+
txHash: input.txHash,
|
|
351
|
+
timeoutMs: ONCHAIN_RECEIPT_TIMEOUT_MS,
|
|
352
|
+
intervalMs: ONCHAIN_RECEIPT_POLL_INTERVAL_MS,
|
|
353
|
+
},
|
|
354
|
+
{
|
|
355
|
+
getTransactionReceiptByHash,
|
|
356
|
+
},
|
|
357
|
+
);
|
|
358
|
+
|
|
359
|
+
if (result.receipt) {
|
|
360
|
+
const summary = {
|
|
361
|
+
event: 'onchainReceipt',
|
|
362
|
+
txHash: input.txHash,
|
|
363
|
+
blockNumber: stringifyOptionalValue(result.receipt.blockNumber),
|
|
364
|
+
transactionIndex: result.receipt.transactionIndex,
|
|
365
|
+
status: result.receipt.status,
|
|
366
|
+
};
|
|
367
|
+
if (input.asJson) {
|
|
368
|
+
console.error(formatJson(summary));
|
|
369
|
+
} else {
|
|
370
|
+
console.error(
|
|
371
|
+
`On-chain receipt: ${result.receipt.status} block ${result.receipt.blockNumber} txIndex ${result.receipt.transactionIndex}`,
|
|
372
|
+
);
|
|
373
|
+
}
|
|
374
|
+
return;
|
|
375
|
+
}
|
|
376
|
+
|
|
377
|
+
if (input.asJson) {
|
|
378
|
+
console.error(
|
|
379
|
+
formatJson({
|
|
380
|
+
event: 'onchainReceiptTimeout',
|
|
381
|
+
txHash: input.txHash,
|
|
382
|
+
timeoutMs: ONCHAIN_RECEIPT_TIMEOUT_MS,
|
|
383
|
+
}),
|
|
384
|
+
);
|
|
385
|
+
} else {
|
|
386
|
+
console.error(
|
|
387
|
+
`Timed out after ${ONCHAIN_RECEIPT_TIMEOUT_MS / 1000}s waiting for on-chain receipt`,
|
|
388
|
+
);
|
|
389
|
+
}
|
|
390
|
+
} catch (error) {
|
|
391
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
392
|
+
if (input.asJson) {
|
|
393
|
+
console.error(
|
|
394
|
+
formatJson({
|
|
395
|
+
event: 'onchainReceiptPollingError',
|
|
396
|
+
txHash: input.txHash,
|
|
397
|
+
error: message,
|
|
398
|
+
}),
|
|
399
|
+
);
|
|
400
|
+
} else {
|
|
401
|
+
console.error(`On-chain receipt polling failed: ${message}`);
|
|
402
|
+
}
|
|
403
|
+
}
|
|
404
|
+
}
|
|
405
|
+
|
|
406
|
+
function parseConfigValue(key: WritableConfigKey, value: string): WlfiConfig {
|
|
407
|
+
if (key === 'chainId') {
|
|
408
|
+
return { [key]: parsePositiveIntegerString(value, key) };
|
|
409
|
+
}
|
|
410
|
+
if (key === 'agentKeyId') {
|
|
411
|
+
return { [key]: assertAgentKeyId(value, key) };
|
|
412
|
+
}
|
|
413
|
+
if (key === 'rpcUrl') {
|
|
414
|
+
return { [key]: assertSafeRpcUrl(value, key) };
|
|
415
|
+
}
|
|
416
|
+
return { [key]: value } as WlfiConfig;
|
|
417
|
+
}
|
|
418
|
+
|
|
419
|
+
function activeChainSummary(config: WlfiConfig) {
|
|
420
|
+
return {
|
|
421
|
+
chainId: config.chainId ?? null,
|
|
422
|
+
chainName: config.chainName ?? null,
|
|
423
|
+
rpcUrl: config.rpcUrl ?? null,
|
|
424
|
+
};
|
|
425
|
+
}
|
|
426
|
+
|
|
427
|
+
function normalizeChainProfile(
|
|
428
|
+
key: string,
|
|
429
|
+
options: { chainId: string; name?: string; rpcUrl?: string },
|
|
430
|
+
): ChainProfile {
|
|
431
|
+
const rpcUrl = options.rpcUrl?.trim();
|
|
432
|
+
return {
|
|
433
|
+
chainId: parsePositiveIntegerString(options.chainId, 'chainId'),
|
|
434
|
+
name: options.name?.trim() || key.trim().toLowerCase(),
|
|
435
|
+
rpcUrl: rpcUrl ? assertSafeRpcUrl(rpcUrl, 'rpcUrl') : undefined,
|
|
436
|
+
};
|
|
437
|
+
}
|
|
438
|
+
|
|
439
|
+
interface AgentCommandAuthOptions {
|
|
440
|
+
agentKeyId?: string;
|
|
441
|
+
agentAuthToken?: string;
|
|
442
|
+
agentAuthTokenStdin?: boolean;
|
|
443
|
+
allowLegacyAgentAuthSource?: boolean;
|
|
444
|
+
daemonSocket?: string;
|
|
445
|
+
}
|
|
446
|
+
|
|
447
|
+
function warnForAgentAuthTokenSource(source: string, agentKeyId: string) {
|
|
448
|
+
if (source === 'argv') {
|
|
449
|
+
console.error(
|
|
450
|
+
'warning: --agent-auth-token exposes secrets in shell history and process listings; prefer --agent-auth-token-stdin',
|
|
451
|
+
);
|
|
452
|
+
return;
|
|
453
|
+
}
|
|
454
|
+
|
|
455
|
+
if (source === 'config') {
|
|
456
|
+
console.error(
|
|
457
|
+
'warning: agentAuthToken is being loaded from config.json; migrate it with `wlfi-agent config agent-auth set --agent-key-id ' +
|
|
458
|
+
agentKeyId +
|
|
459
|
+
' --agent-auth-token-stdin`',
|
|
460
|
+
);
|
|
461
|
+
return;
|
|
462
|
+
}
|
|
463
|
+
|
|
464
|
+
if (source === 'env') {
|
|
465
|
+
console.error(
|
|
466
|
+
'warning: WLFI_AGENT_AUTH_TOKEN exposes secrets to child processes and shell sessions; prefer macOS Keychain or --agent-auth-token-stdin',
|
|
467
|
+
);
|
|
468
|
+
}
|
|
469
|
+
}
|
|
470
|
+
|
|
471
|
+
async function resolveAgentCommandContext(
|
|
472
|
+
options: AgentCommandAuthOptions,
|
|
473
|
+
config: WlfiConfig,
|
|
474
|
+
): Promise<{ agentKeyId: string; agentAuthToken: string; daemonSocket: string }> {
|
|
475
|
+
const agentKeyId = assertAgentKeyId(
|
|
476
|
+
options.agentKeyId ?? config.agentKeyId ?? process.env.WLFI_AGENT_KEY_ID,
|
|
477
|
+
'agentKeyId',
|
|
478
|
+
);
|
|
479
|
+
const keychainAgentAuthToken = readAgentAuthTokenFromKeychain(agentKeyId);
|
|
480
|
+
const { token: agentAuthToken, source: agentAuthTokenSource } = await resolveAgentAuthToken({
|
|
481
|
+
agentKeyId,
|
|
482
|
+
cliToken: options.agentAuthToken,
|
|
483
|
+
cliTokenStdin: options.agentAuthTokenStdin,
|
|
484
|
+
keychainToken: keychainAgentAuthToken,
|
|
485
|
+
configToken: config.agentAuthToken,
|
|
486
|
+
envToken: process.env.WLFI_AGENT_AUTH_TOKEN,
|
|
487
|
+
allowLegacySource: options.allowLegacyAgentAuthSource,
|
|
488
|
+
readFromStdin: readTrimmedStdin,
|
|
489
|
+
});
|
|
490
|
+
const daemonSocket = resolveDaemonSocket(
|
|
491
|
+
options.daemonSocket ?? process.env.WLFI_DAEMON_SOCKET,
|
|
492
|
+
config,
|
|
493
|
+
);
|
|
494
|
+
assertTrustedDaemonSocketPath(daemonSocket);
|
|
495
|
+
|
|
496
|
+
warnForAgentAuthTokenSource(agentAuthTokenSource, agentKeyId);
|
|
497
|
+
|
|
498
|
+
return { agentKeyId, agentAuthToken, daemonSocket };
|
|
499
|
+
}
|
|
500
|
+
|
|
501
|
+
async function runAgentCommandJson<T>(input: {
|
|
502
|
+
commandArgs: string[];
|
|
503
|
+
auth: AgentCommandAuthOptions;
|
|
504
|
+
config: WlfiConfig;
|
|
505
|
+
asJson: boolean;
|
|
506
|
+
}): Promise<T | null> {
|
|
507
|
+
const { agentKeyId, agentAuthToken, daemonSocket } = await resolveAgentCommandContext(
|
|
508
|
+
input.auth,
|
|
509
|
+
input.config,
|
|
510
|
+
);
|
|
511
|
+
|
|
512
|
+
try {
|
|
513
|
+
return await runRustBinaryJson<T>(
|
|
514
|
+
'wlfi-agent-agent',
|
|
515
|
+
[
|
|
516
|
+
'--json',
|
|
517
|
+
'--agent-key-id',
|
|
518
|
+
agentKeyId,
|
|
519
|
+
'--agent-auth-token-stdin',
|
|
520
|
+
'--daemon-socket',
|
|
521
|
+
daemonSocket,
|
|
522
|
+
...input.commandArgs,
|
|
523
|
+
],
|
|
524
|
+
input.config,
|
|
525
|
+
{
|
|
526
|
+
stdin: `${agentAuthToken}\n`,
|
|
527
|
+
preSuppliedSecretStdin: 'agentAuthToken',
|
|
528
|
+
scrubSensitiveEnv: true,
|
|
529
|
+
},
|
|
530
|
+
);
|
|
531
|
+
} catch (error) {
|
|
532
|
+
if (error instanceof RustBinaryExitError && error.stdout.trim()) {
|
|
533
|
+
try {
|
|
534
|
+
const parsed = JSON.parse(error.stdout) as unknown;
|
|
535
|
+
if (isManualApprovalRequiredOutput(parsed)) {
|
|
536
|
+
printManualApprovalRequired(parsed, input.asJson);
|
|
537
|
+
process.exitCode = error.code;
|
|
538
|
+
return null;
|
|
539
|
+
}
|
|
540
|
+
} catch {
|
|
541
|
+
// fall through to the original error
|
|
542
|
+
}
|
|
543
|
+
}
|
|
544
|
+
throw error;
|
|
545
|
+
}
|
|
546
|
+
}
|
|
547
|
+
|
|
548
|
+
function legacyAmountToDecimalString(value: number | undefined): string | undefined {
|
|
549
|
+
if (value === undefined) {
|
|
550
|
+
return undefined;
|
|
551
|
+
}
|
|
552
|
+
return value.toString();
|
|
553
|
+
}
|
|
554
|
+
|
|
555
|
+
function describeResolvedToken(token: NonNullable<ReturnType<typeof resolveTokenProfile>>) {
|
|
556
|
+
return {
|
|
557
|
+
key: token.key,
|
|
558
|
+
source: token.source,
|
|
559
|
+
symbol: token.symbol,
|
|
560
|
+
chains: Object.entries(token.chains ?? {})
|
|
561
|
+
.map(([key, value]) => ({ key, ...value }))
|
|
562
|
+
.sort((left, right) => left.chainId - right.chainId || left.key.localeCompare(right.key)),
|
|
563
|
+
};
|
|
564
|
+
}
|
|
565
|
+
|
|
566
|
+
function normalizeTokenChainConfig(
|
|
567
|
+
tokenKey: string,
|
|
568
|
+
chainKey: string,
|
|
569
|
+
options: {
|
|
570
|
+
symbol?: string;
|
|
571
|
+
chainId?: string;
|
|
572
|
+
native?: boolean;
|
|
573
|
+
address?: string;
|
|
574
|
+
decimals?: string;
|
|
575
|
+
perTx?: string;
|
|
576
|
+
daily?: string;
|
|
577
|
+
weekly?: string;
|
|
578
|
+
},
|
|
579
|
+
config: WlfiConfig,
|
|
580
|
+
): { symbol: string; profile: TokenChainProfile } {
|
|
581
|
+
const normalizedTokenKey = tokenKey.trim().toLowerCase();
|
|
582
|
+
const normalizedChainKey = chainKey.trim().toLowerCase();
|
|
583
|
+
if (!normalizedTokenKey) {
|
|
584
|
+
throw new Error('token key is required');
|
|
585
|
+
}
|
|
586
|
+
if (!normalizedChainKey) {
|
|
587
|
+
throw new Error('chain key is required');
|
|
588
|
+
}
|
|
589
|
+
if (options.native && options.address) {
|
|
590
|
+
throw new Error('--native conflicts with --address');
|
|
591
|
+
}
|
|
592
|
+
|
|
593
|
+
const existingToken = resolveTokenProfile(normalizedTokenKey, config);
|
|
594
|
+
const existingChain = existingToken?.chains?.[normalizedChainKey];
|
|
595
|
+
const resolvedChain = resolveChainProfile(normalizedChainKey, config);
|
|
596
|
+
const symbol =
|
|
597
|
+
options.symbol?.trim() || existingToken?.symbol || normalizedTokenKey.toUpperCase();
|
|
598
|
+
const chainId = options.chainId
|
|
599
|
+
? parsePositiveIntegerString(options.chainId, 'chainId')
|
|
600
|
+
: (existingChain?.chainId ?? resolvedChain?.chainId);
|
|
601
|
+
if (chainId === undefined) {
|
|
602
|
+
throw new Error(
|
|
603
|
+
`chainId is required; pass --chain-id or configure chain '${normalizedChainKey}' first`,
|
|
604
|
+
);
|
|
605
|
+
}
|
|
606
|
+
|
|
607
|
+
const isNative = options.native ? true : options.address ? false : existingChain?.isNative;
|
|
608
|
+
if (isNative === undefined) {
|
|
609
|
+
throw new Error('pass --native or --address for a new token chain');
|
|
610
|
+
}
|
|
611
|
+
|
|
612
|
+
const decimals =
|
|
613
|
+
options.decimals !== undefined
|
|
614
|
+
? parseIntegerString(options.decimals, 'decimals')
|
|
615
|
+
: existingChain?.decimals;
|
|
616
|
+
if (decimals === undefined) {
|
|
617
|
+
throw new Error('decimals is required; pass --decimals or edit an existing token chain');
|
|
618
|
+
}
|
|
619
|
+
|
|
620
|
+
const address = isNative
|
|
621
|
+
? undefined
|
|
622
|
+
: options.address
|
|
623
|
+
? assertAddress(options.address, 'address')
|
|
624
|
+
: existingChain?.address;
|
|
625
|
+
if (!isNative && !address) {
|
|
626
|
+
throw new Error('address is required for a non-native token chain');
|
|
627
|
+
}
|
|
628
|
+
|
|
629
|
+
const hasPolicyOverride =
|
|
630
|
+
options.perTx !== undefined || options.daily !== undefined || options.weekly !== undefined;
|
|
631
|
+
const defaultPolicy =
|
|
632
|
+
hasPolicyOverride || existingChain?.defaultPolicy
|
|
633
|
+
? {
|
|
634
|
+
perTxAmountDecimal:
|
|
635
|
+
options.perTx !== undefined
|
|
636
|
+
? normalizePositiveDecimalInput(options.perTx, 'perTx')
|
|
637
|
+
: (existingChain?.defaultPolicy?.perTxAmountDecimal ??
|
|
638
|
+
legacyAmountToDecimalString(existingChain?.defaultPolicy?.perTxAmount)),
|
|
639
|
+
dailyAmountDecimal:
|
|
640
|
+
options.daily !== undefined
|
|
641
|
+
? normalizePositiveDecimalInput(options.daily, 'daily')
|
|
642
|
+
: (existingChain?.defaultPolicy?.dailyAmountDecimal ??
|
|
643
|
+
legacyAmountToDecimalString(existingChain?.defaultPolicy?.dailyAmount)),
|
|
644
|
+
weeklyAmountDecimal:
|
|
645
|
+
options.weekly !== undefined
|
|
646
|
+
? normalizePositiveDecimalInput(options.weekly, 'weekly')
|
|
647
|
+
: (existingChain?.defaultPolicy?.weeklyAmountDecimal ??
|
|
648
|
+
legacyAmountToDecimalString(existingChain?.defaultPolicy?.weeklyAmount)),
|
|
649
|
+
}
|
|
650
|
+
: undefined;
|
|
651
|
+
|
|
652
|
+
return {
|
|
653
|
+
symbol,
|
|
654
|
+
profile: {
|
|
655
|
+
chainId,
|
|
656
|
+
isNative,
|
|
657
|
+
decimals,
|
|
658
|
+
...(address ? { address } : {}),
|
|
659
|
+
...(defaultPolicy && Object.values(defaultPolicy).some((value) => value !== undefined)
|
|
660
|
+
? { defaultPolicy }
|
|
661
|
+
: {}),
|
|
662
|
+
},
|
|
663
|
+
};
|
|
664
|
+
}
|
|
665
|
+
|
|
666
|
+
function buildAdminChainCommand(): Command {
|
|
667
|
+
const command = new Command('chain')
|
|
668
|
+
.description(
|
|
669
|
+
'Manage active chain selection and chain profiles (mutations require verified root access)',
|
|
670
|
+
)
|
|
671
|
+
.showHelpAfterError();
|
|
672
|
+
|
|
673
|
+
command
|
|
674
|
+
.command('list')
|
|
675
|
+
.option('--json', 'Print JSON output', false)
|
|
676
|
+
.action((options) => {
|
|
677
|
+
const config = readConfig();
|
|
678
|
+
print(
|
|
679
|
+
{
|
|
680
|
+
active: activeChainSummary(config),
|
|
681
|
+
configured: listConfiguredChains(config),
|
|
682
|
+
builtin: listBuiltinChains(),
|
|
683
|
+
},
|
|
684
|
+
options.json,
|
|
685
|
+
);
|
|
686
|
+
});
|
|
687
|
+
|
|
688
|
+
command
|
|
689
|
+
.command('current')
|
|
690
|
+
.option('--json', 'Print JSON output', false)
|
|
691
|
+
.action(async (options) => {
|
|
692
|
+
const config = readConfig();
|
|
693
|
+
let rpc = null;
|
|
694
|
+
if (config.rpcUrl) {
|
|
695
|
+
try {
|
|
696
|
+
rpc = await getChainInfo(config.rpcUrl);
|
|
697
|
+
} catch {
|
|
698
|
+
rpc = null;
|
|
699
|
+
}
|
|
700
|
+
}
|
|
701
|
+
print(
|
|
702
|
+
{
|
|
703
|
+
active: activeChainSummary(config),
|
|
704
|
+
rpc,
|
|
705
|
+
},
|
|
706
|
+
options.json,
|
|
707
|
+
);
|
|
708
|
+
});
|
|
709
|
+
|
|
710
|
+
command
|
|
711
|
+
.command('add')
|
|
712
|
+
.argument('<key>')
|
|
713
|
+
.requiredOption('--chain-id <id>', 'Chain ID')
|
|
714
|
+
.option('--name <name>', 'Display name')
|
|
715
|
+
.option('--rpc-url <url>', 'Default RPC URL for this chain profile')
|
|
716
|
+
.option('--activate', 'Activate this profile immediately', false)
|
|
717
|
+
.option('--json', 'Print JSON output', false)
|
|
718
|
+
.action(
|
|
719
|
+
withLocalAdminMutationAccess('wlfi-agent admin chain add', (key: string, options) => {
|
|
720
|
+
const profile = normalizeChainProfile(key, options);
|
|
721
|
+
let updated = saveChainProfile(key, profile);
|
|
722
|
+
if (options.activate) {
|
|
723
|
+
updated = switchActiveChain(key, { rpcUrl: profile.rpcUrl });
|
|
724
|
+
}
|
|
725
|
+
print(
|
|
726
|
+
{
|
|
727
|
+
saved: { key: key.trim().toLowerCase(), ...profile },
|
|
728
|
+
active: activeChainSummary(updated),
|
|
729
|
+
},
|
|
730
|
+
options.json,
|
|
731
|
+
);
|
|
732
|
+
}),
|
|
733
|
+
);
|
|
734
|
+
|
|
735
|
+
command
|
|
736
|
+
.command('remove')
|
|
737
|
+
.argument('<key>')
|
|
738
|
+
.option('--json', 'Print JSON output', false)
|
|
739
|
+
.action(
|
|
740
|
+
withLocalAdminMutationAccess('wlfi-agent admin chain remove', (key: string, options) => {
|
|
741
|
+
const updated = removeChainProfile(key);
|
|
742
|
+
print(
|
|
743
|
+
{
|
|
744
|
+
removed: key.trim().toLowerCase(),
|
|
745
|
+
configured: listConfiguredChains(updated),
|
|
746
|
+
},
|
|
747
|
+
options.json,
|
|
748
|
+
);
|
|
749
|
+
}),
|
|
750
|
+
);
|
|
751
|
+
|
|
752
|
+
command
|
|
753
|
+
.command('switch')
|
|
754
|
+
.argument('<selector>')
|
|
755
|
+
.option('--rpc-url <url>', 'RPC URL to save as the active endpoint')
|
|
756
|
+
.option('--save', 'Persist the active selection as a named profile', false)
|
|
757
|
+
.option('--json', 'Print JSON output', false)
|
|
758
|
+
.action(
|
|
759
|
+
withLocalAdminMutationAccess('wlfi-agent admin chain switch', (selector: string, options) => {
|
|
760
|
+
const updated = switchActiveChain(selector, {
|
|
761
|
+
rpcUrl: options.rpcUrl,
|
|
762
|
+
persistProfile: options.save,
|
|
763
|
+
});
|
|
764
|
+
const profile = resolveChainProfile(selector, updated);
|
|
765
|
+
print(
|
|
766
|
+
{
|
|
767
|
+
selected: selector,
|
|
768
|
+
resolved: profile,
|
|
769
|
+
active: activeChainSummary(updated),
|
|
770
|
+
},
|
|
771
|
+
options.json,
|
|
772
|
+
);
|
|
773
|
+
}),
|
|
774
|
+
);
|
|
775
|
+
|
|
776
|
+
return command;
|
|
777
|
+
}
|
|
778
|
+
|
|
779
|
+
function buildAdminTokenCommand(): Command {
|
|
780
|
+
const command = new Command('token')
|
|
781
|
+
.description(
|
|
782
|
+
'Manage shared token definitions and default policies (mutations require verified root access)',
|
|
783
|
+
)
|
|
784
|
+
.showHelpAfterError();
|
|
785
|
+
|
|
786
|
+
command
|
|
787
|
+
.command('list')
|
|
788
|
+
.option('--json', 'Print JSON output', false)
|
|
789
|
+
.action((options) => {
|
|
790
|
+
const config = readConfig();
|
|
791
|
+
print(
|
|
792
|
+
{
|
|
793
|
+
builtin: listBuiltinTokens(),
|
|
794
|
+
configured: listConfiguredTokens(config),
|
|
795
|
+
},
|
|
796
|
+
options.json,
|
|
797
|
+
);
|
|
798
|
+
});
|
|
799
|
+
|
|
800
|
+
command
|
|
801
|
+
.command('show')
|
|
802
|
+
.argument('<selector>')
|
|
803
|
+
.option('--json', 'Print JSON output', false)
|
|
804
|
+
.action((selector: string, options) => {
|
|
805
|
+
const config = readConfig();
|
|
806
|
+
const resolved = resolveTokenProfile(selector, config);
|
|
807
|
+
if (!resolved) {
|
|
808
|
+
throw new Error(`unknown token selector: ${selector}`);
|
|
809
|
+
}
|
|
810
|
+
print(describeResolvedToken(resolved), options.json);
|
|
811
|
+
});
|
|
812
|
+
|
|
813
|
+
command
|
|
814
|
+
.command('set-chain')
|
|
815
|
+
.argument('<tokenKey>')
|
|
816
|
+
.argument('<chainKey>')
|
|
817
|
+
.option('--symbol <symbol>', 'Token symbol')
|
|
818
|
+
.option('--chain-id <id>', 'Chain ID (defaults from the existing token or configured chain)')
|
|
819
|
+
.option('--native', 'Mark the token as the native asset on this chain', false)
|
|
820
|
+
.option('--address <address>', 'ERC-20 token contract address')
|
|
821
|
+
.option('--decimals <count>', 'Token decimals')
|
|
822
|
+
.option('--per-tx <amount>', 'Default per-transaction policy amount in token units')
|
|
823
|
+
.option('--daily <amount>', 'Default daily policy amount in token units')
|
|
824
|
+
.option('--weekly <amount>', 'Default weekly policy amount in token units')
|
|
825
|
+
.option('--json', 'Print JSON output', false)
|
|
826
|
+
.action(
|
|
827
|
+
withLocalAdminMutationAccess(
|
|
828
|
+
'wlfi-agent admin token set-chain',
|
|
829
|
+
(tokenKey: string, chainKey: string, options) => {
|
|
830
|
+
const config = readConfig();
|
|
831
|
+
const { symbol, profile } = normalizeTokenChainConfig(
|
|
832
|
+
tokenKey,
|
|
833
|
+
chainKey,
|
|
834
|
+
options,
|
|
835
|
+
config,
|
|
836
|
+
);
|
|
837
|
+
const updated = saveTokenChainProfile(tokenKey, chainKey, profile, { symbol });
|
|
838
|
+
const resolved = resolveTokenProfile(tokenKey, updated);
|
|
839
|
+
if (!resolved) {
|
|
840
|
+
throw new Error(`failed to save token '${tokenKey}'`);
|
|
841
|
+
}
|
|
842
|
+
print(
|
|
843
|
+
{
|
|
844
|
+
saved: describeResolvedToken(resolved),
|
|
845
|
+
},
|
|
846
|
+
options.json,
|
|
847
|
+
);
|
|
848
|
+
},
|
|
849
|
+
),
|
|
850
|
+
);
|
|
851
|
+
|
|
852
|
+
command
|
|
853
|
+
.command('remove')
|
|
854
|
+
.argument('<tokenKey>')
|
|
855
|
+
.option('--json', 'Print JSON output', false)
|
|
856
|
+
.action(
|
|
857
|
+
withLocalAdminMutationAccess('wlfi-agent admin token remove', (tokenKey: string, options) => {
|
|
858
|
+
const updated = removeTokenProfile(tokenKey);
|
|
859
|
+
print(
|
|
860
|
+
{
|
|
861
|
+
removed: tokenKey.trim().toLowerCase(),
|
|
862
|
+
configured: listConfiguredTokens(updated),
|
|
863
|
+
},
|
|
864
|
+
options.json,
|
|
865
|
+
);
|
|
866
|
+
}),
|
|
867
|
+
);
|
|
868
|
+
|
|
869
|
+
command
|
|
870
|
+
.command('remove-chain')
|
|
871
|
+
.argument('<tokenKey>')
|
|
872
|
+
.argument('<chainKey>')
|
|
873
|
+
.option('--json', 'Print JSON output', false)
|
|
874
|
+
.action(
|
|
875
|
+
withLocalAdminMutationAccess(
|
|
876
|
+
'wlfi-agent admin token remove-chain',
|
|
877
|
+
(tokenKey: string, chainKey: string, options) => {
|
|
878
|
+
const updated = removeTokenChainProfile(tokenKey, chainKey);
|
|
879
|
+
const resolved = resolveTokenProfile(tokenKey, updated);
|
|
880
|
+
print(
|
|
881
|
+
{
|
|
882
|
+
removed: {
|
|
883
|
+
token: tokenKey.trim().toLowerCase(),
|
|
884
|
+
chain: chainKey.trim().toLowerCase(),
|
|
885
|
+
},
|
|
886
|
+
remaining: resolved ? describeResolvedToken(resolved) : null,
|
|
887
|
+
},
|
|
888
|
+
options.json,
|
|
889
|
+
);
|
|
890
|
+
},
|
|
891
|
+
),
|
|
892
|
+
);
|
|
893
|
+
|
|
894
|
+
return command;
|
|
895
|
+
}
|
|
896
|
+
|
|
897
|
+
async function runLocalAdminCommand(forwarded: string[]): Promise<boolean> {
|
|
898
|
+
const target = forwarded[0] === 'help' ? forwarded[1] : forwarded[0];
|
|
899
|
+
if (target !== 'chain' && target !== 'token' && target !== 'daemon') {
|
|
900
|
+
return false;
|
|
901
|
+
}
|
|
902
|
+
|
|
903
|
+
const command =
|
|
904
|
+
target === 'chain'
|
|
905
|
+
? buildAdminChainCommand()
|
|
906
|
+
: target === 'token'
|
|
907
|
+
? buildAdminTokenCommand()
|
|
908
|
+
: buildAdminDaemonCommand();
|
|
909
|
+
const args = forwarded[0] === 'help' ? ['--help'] : forwarded.slice(1);
|
|
910
|
+
command.exitOverride();
|
|
911
|
+
|
|
912
|
+
try {
|
|
913
|
+
await command.parseAsync(args, { from: 'user' });
|
|
914
|
+
return true;
|
|
915
|
+
} catch (error) {
|
|
916
|
+
if (error && typeof error === 'object' && 'code' in error) {
|
|
917
|
+
const code = String((error as { code?: unknown }).code ?? '');
|
|
918
|
+
if (code === 'commander.helpDisplayed') {
|
|
919
|
+
return true;
|
|
920
|
+
}
|
|
921
|
+
}
|
|
922
|
+
throw error;
|
|
923
|
+
}
|
|
924
|
+
}
|
|
925
|
+
|
|
926
|
+
function buildAdminDaemonCommand(): Command {
|
|
927
|
+
return new Command()
|
|
928
|
+
.name('daemon')
|
|
929
|
+
.description('Daemon launch is managed by wlfi-agent admin setup')
|
|
930
|
+
.action(() => {
|
|
931
|
+
throw new Error(
|
|
932
|
+
'Direct daemon execution is disabled. Use `wlfi-agent admin setup` to install and manage the daemon.',
|
|
933
|
+
);
|
|
934
|
+
});
|
|
935
|
+
}
|
|
936
|
+
|
|
937
|
+
async function main() {
|
|
938
|
+
ensureWlfiHome();
|
|
939
|
+
const program = new Command();
|
|
940
|
+
program
|
|
941
|
+
.name('wlfi-agent')
|
|
942
|
+
.description('Single entrypoint for WLFI agent admin and signing operations')
|
|
943
|
+
.showHelpAfterError();
|
|
944
|
+
|
|
945
|
+
const configCommand = program.command('config').description('Manage ~/.wlfi_agent configuration');
|
|
946
|
+
configCommand
|
|
947
|
+
.command('show')
|
|
948
|
+
.option('--json', 'Print JSON output', false)
|
|
949
|
+
.action((options) => {
|
|
950
|
+
const config = readConfig();
|
|
951
|
+
print(
|
|
952
|
+
{
|
|
953
|
+
...redactConfig(config),
|
|
954
|
+
keychain: {
|
|
955
|
+
agentAuthTokenStored: hasStoredAgentAuthToken(config.agentKeyId),
|
|
956
|
+
service: process.platform === 'darwin' ? AGENT_AUTH_TOKEN_KEYCHAIN_SERVICE : null,
|
|
957
|
+
},
|
|
958
|
+
},
|
|
959
|
+
options.json,
|
|
960
|
+
);
|
|
961
|
+
});
|
|
962
|
+
configCommand.command('path').action(() => {
|
|
963
|
+
console.log(resolveConfigPath());
|
|
964
|
+
});
|
|
965
|
+
configCommand
|
|
966
|
+
.command('set')
|
|
967
|
+
.argument('<key>')
|
|
968
|
+
.argument('<value>')
|
|
969
|
+
.action(
|
|
970
|
+
withDynamicLocalAdminMutationAccess(
|
|
971
|
+
(key: string, _value: string) => resolveConfigMutationCommandLabel('set', key),
|
|
972
|
+
(key: string, value: string) => {
|
|
973
|
+
const writableKey = assertWritableConfigKey(key);
|
|
974
|
+
const updated = writeConfig(parseConfigValue(writableKey, value));
|
|
975
|
+
print(redactConfig(updated), true);
|
|
976
|
+
},
|
|
977
|
+
),
|
|
978
|
+
);
|
|
979
|
+
configCommand
|
|
980
|
+
.command('unset')
|
|
981
|
+
.argument('<key>')
|
|
982
|
+
.action(
|
|
983
|
+
withDynamicLocalAdminMutationAccess(
|
|
984
|
+
(key: string) => resolveConfigMutationCommandLabel('unset', key),
|
|
985
|
+
(key: string) => {
|
|
986
|
+
const writableKey = assertWritableConfigKey(key);
|
|
987
|
+
const updated = deleteConfigKey(writableKey);
|
|
988
|
+
print(redactConfig(updated), true);
|
|
989
|
+
},
|
|
990
|
+
),
|
|
991
|
+
);
|
|
992
|
+
|
|
993
|
+
const agentAuthCommand = configCommand
|
|
994
|
+
.command('agent-auth')
|
|
995
|
+
.description('Manage the agent auth token in macOS Keychain');
|
|
996
|
+
|
|
997
|
+
agentAuthCommand
|
|
998
|
+
.command('set')
|
|
999
|
+
.requiredOption('--agent-key-id <uuid>', 'Agent key id')
|
|
1000
|
+
.option('--agent-auth-token <token>', 'Agent auth token')
|
|
1001
|
+
.option('--agent-auth-token-stdin', 'Read agent auth token from stdin', false)
|
|
1002
|
+
.option('--json', 'Print JSON output', false)
|
|
1003
|
+
.action(
|
|
1004
|
+
withLocalAdminMutationAccess('wlfi-agent config agent-auth set', async (options) => {
|
|
1005
|
+
if (options.agentAuthToken && options.agentAuthTokenStdin) {
|
|
1006
|
+
throw new Error('--agent-auth-token conflicts with --agent-auth-token-stdin');
|
|
1007
|
+
}
|
|
1008
|
+
|
|
1009
|
+
const agentKeyId = assertAgentKeyId(options.agentKeyId);
|
|
1010
|
+
const agentAuthToken = options.agentAuthTokenStdin
|
|
1011
|
+
? assertValidAgentAuthToken(await readTrimmedStdin('agentAuthToken'), 'agentAuthToken')
|
|
1012
|
+
: assertValidAgentAuthToken(
|
|
1013
|
+
requiredString(options.agentAuthToken, 'agentAuthToken'),
|
|
1014
|
+
'agentAuthToken',
|
|
1015
|
+
);
|
|
1016
|
+
|
|
1017
|
+
if (options.agentAuthToken) {
|
|
1018
|
+
console.error(
|
|
1019
|
+
'warning: --agent-auth-token exposes secrets in shell history and process listings; prefer --agent-auth-token-stdin',
|
|
1020
|
+
);
|
|
1021
|
+
}
|
|
1022
|
+
|
|
1023
|
+
storeAgentAuthTokenInKeychain(agentKeyId, agentAuthToken);
|
|
1024
|
+
|
|
1025
|
+
let updated = writeConfig({ agentKeyId });
|
|
1026
|
+
if (updated.agentAuthToken !== undefined) {
|
|
1027
|
+
updated = deleteConfigKey('agentAuthToken');
|
|
1028
|
+
}
|
|
1029
|
+
|
|
1030
|
+
print(
|
|
1031
|
+
{
|
|
1032
|
+
agentKeyId: updated.agentKeyId ?? agentKeyId,
|
|
1033
|
+
keychain: {
|
|
1034
|
+
stored: true,
|
|
1035
|
+
service: AGENT_AUTH_TOKEN_KEYCHAIN_SERVICE,
|
|
1036
|
+
},
|
|
1037
|
+
config: redactConfig(updated),
|
|
1038
|
+
},
|
|
1039
|
+
options.json,
|
|
1040
|
+
);
|
|
1041
|
+
}),
|
|
1042
|
+
);
|
|
1043
|
+
|
|
1044
|
+
agentAuthCommand
|
|
1045
|
+
.command('import')
|
|
1046
|
+
.description('Import agent credentials from a private admin bootstrap JSON file')
|
|
1047
|
+
.argument('<path>', 'Path to bootstrap JSON output with an unredacted agent auth token')
|
|
1048
|
+
.option('--keep-source', 'Keep the imported bootstrap file unchanged after import', false)
|
|
1049
|
+
.option('--delete-source', 'Delete the imported bootstrap file after import', false)
|
|
1050
|
+
.option('--json', 'Print JSON output', false)
|
|
1051
|
+
.action(
|
|
1052
|
+
withLocalAdminMutationAccess(
|
|
1053
|
+
'wlfi-agent config agent-auth import',
|
|
1054
|
+
(inputPath: string, options) => {
|
|
1055
|
+
if (options.keepSource && options.deleteSource) {
|
|
1056
|
+
throw new Error('--keep-source conflicts with --delete-source');
|
|
1057
|
+
}
|
|
1058
|
+
|
|
1059
|
+
const summary = readBootstrapSetupSummaryFile(inputPath);
|
|
1060
|
+
assertBootstrapSetupSummaryLeaseIsActive(summary);
|
|
1061
|
+
const imported = readBootstrapAgentCredentialsFile(inputPath);
|
|
1062
|
+
const agentKeyId = assertAgentKeyId(imported.agentKeyId);
|
|
1063
|
+
if (summary.agentKeyId !== agentKeyId) {
|
|
1064
|
+
throw new Error(
|
|
1065
|
+
'bootstrap credentials file agent_key_id does not match setup summary agent_key_id',
|
|
1066
|
+
);
|
|
1067
|
+
}
|
|
1068
|
+
|
|
1069
|
+
storeAgentAuthTokenInKeychain(agentKeyId, imported.agentAuthToken);
|
|
1070
|
+
|
|
1071
|
+
let sourceCleanup: 'redacted' | 'deleted' | 'kept' = 'kept';
|
|
1072
|
+
if (options.deleteSource) {
|
|
1073
|
+
deleteBootstrapAgentCredentialsFile(imported.sourcePath);
|
|
1074
|
+
sourceCleanup = 'deleted';
|
|
1075
|
+
} else if (!options.keepSource) {
|
|
1076
|
+
redactBootstrapAgentCredentialsFile(imported.sourcePath);
|
|
1077
|
+
sourceCleanup = 'redacted';
|
|
1078
|
+
} else {
|
|
1079
|
+
console.error(
|
|
1080
|
+
'warning: imported bootstrap file still contains a plaintext agent auth token; prefer the default redaction or --delete-source',
|
|
1081
|
+
);
|
|
1082
|
+
}
|
|
1083
|
+
|
|
1084
|
+
let updated = writeConfig({
|
|
1085
|
+
agentKeyId,
|
|
1086
|
+
wallet: walletProfileFromBootstrapSummary(summary),
|
|
1087
|
+
});
|
|
1088
|
+
if (updated.agentAuthToken !== undefined) {
|
|
1089
|
+
updated = deleteConfigKey('agentAuthToken');
|
|
1090
|
+
}
|
|
1091
|
+
|
|
1092
|
+
print(
|
|
1093
|
+
{
|
|
1094
|
+
sourcePath: imported.sourcePath,
|
|
1095
|
+
sourceCleanup,
|
|
1096
|
+
agentKeyId,
|
|
1097
|
+
keychain: {
|
|
1098
|
+
stored: true,
|
|
1099
|
+
service: AGENT_AUTH_TOKEN_KEYCHAIN_SERVICE,
|
|
1100
|
+
},
|
|
1101
|
+
config: redactConfig(updated),
|
|
1102
|
+
},
|
|
1103
|
+
options.json,
|
|
1104
|
+
);
|
|
1105
|
+
},
|
|
1106
|
+
),
|
|
1107
|
+
);
|
|
1108
|
+
|
|
1109
|
+
agentAuthCommand
|
|
1110
|
+
.command('migrate')
|
|
1111
|
+
.description(
|
|
1112
|
+
'Move a legacy config.json agentAuthToken into macOS Keychain and scrub plaintext storage',
|
|
1113
|
+
)
|
|
1114
|
+
.option('--agent-key-id <uuid>', 'Agent key id (defaults to configured agentKeyId)')
|
|
1115
|
+
.option(
|
|
1116
|
+
'--overwrite-keychain',
|
|
1117
|
+
'Replace a different existing Keychain token for this agent',
|
|
1118
|
+
false,
|
|
1119
|
+
)
|
|
1120
|
+
.option('--json', 'Print JSON output', false)
|
|
1121
|
+
.action(
|
|
1122
|
+
withLocalAdminMutationAccess('wlfi-agent config agent-auth migrate', (options) => {
|
|
1123
|
+
print(
|
|
1124
|
+
migrateLegacyAgentAuthToken({
|
|
1125
|
+
agentKeyId: options.agentKeyId,
|
|
1126
|
+
overwriteKeychain: options.overwriteKeychain,
|
|
1127
|
+
}),
|
|
1128
|
+
options.json,
|
|
1129
|
+
);
|
|
1130
|
+
}),
|
|
1131
|
+
);
|
|
1132
|
+
|
|
1133
|
+
agentAuthCommand
|
|
1134
|
+
.command('rotate')
|
|
1135
|
+
.description('Rotate the agent auth token via Rust admin flow, then store it in macOS Keychain')
|
|
1136
|
+
.option('--agent-key-id <uuid>', 'Agent key id (defaults to configured agentKeyId)')
|
|
1137
|
+
.option('--vault-password-stdin', 'Read vault password from stdin', false)
|
|
1138
|
+
.option('--non-interactive', 'Disable password prompts', false)
|
|
1139
|
+
.option('--daemon-socket <path>', 'Daemon unix socket path')
|
|
1140
|
+
.option('--json', 'Print JSON output', false)
|
|
1141
|
+
.action(async (options) => {
|
|
1142
|
+
const config = readConfig();
|
|
1143
|
+
const agentKeyId = options.agentKeyId
|
|
1144
|
+
? assertAgentKeyId(options.agentKeyId)
|
|
1145
|
+
: config.agentKeyId
|
|
1146
|
+
? assertAgentKeyId(config.agentKeyId, 'configured agentKeyId')
|
|
1147
|
+
: undefined;
|
|
1148
|
+
if (!agentKeyId) {
|
|
1149
|
+
throw new Error(
|
|
1150
|
+
'agentKeyId is required; pass --agent-key-id or configure agentKeyId first',
|
|
1151
|
+
);
|
|
1152
|
+
}
|
|
1153
|
+
|
|
1154
|
+
const daemonSocket = resolveDaemonSocket(
|
|
1155
|
+
options.daemonSocket ?? process.env.WLFI_DAEMON_SOCKET,
|
|
1156
|
+
config,
|
|
1157
|
+
);
|
|
1158
|
+
assertTrustedAdminDaemonSocketPath(daemonSocket);
|
|
1159
|
+
|
|
1160
|
+
const rotated = await runRustBinaryJson<RotateAgentAuthTokenAdminOutput>(
|
|
1161
|
+
'wlfi-agent-admin',
|
|
1162
|
+
buildRotateAgentAuthTokenAdminArgs({
|
|
1163
|
+
agentKeyId,
|
|
1164
|
+
vaultPasswordStdin: options.vaultPasswordStdin,
|
|
1165
|
+
nonInteractive: options.nonInteractive,
|
|
1166
|
+
daemonSocket,
|
|
1167
|
+
}),
|
|
1168
|
+
config,
|
|
1169
|
+
);
|
|
1170
|
+
|
|
1171
|
+
print(completeAgentAuthRotation(rotated), options.json);
|
|
1172
|
+
});
|
|
1173
|
+
|
|
1174
|
+
agentAuthCommand
|
|
1175
|
+
.command('revoke')
|
|
1176
|
+
.description('Revoke the agent key via Rust admin flow, then remove local credentials')
|
|
1177
|
+
.option('--agent-key-id <uuid>', 'Agent key id (defaults to configured agentKeyId)')
|
|
1178
|
+
.option('--vault-password-stdin', 'Read vault password from stdin', false)
|
|
1179
|
+
.option('--non-interactive', 'Disable password prompts', false)
|
|
1180
|
+
.option('--daemon-socket <path>', 'Daemon unix socket path')
|
|
1181
|
+
.option('--json', 'Print JSON output', false)
|
|
1182
|
+
.action(async (options) => {
|
|
1183
|
+
const config = readConfig();
|
|
1184
|
+
const agentKeyId = options.agentKeyId
|
|
1185
|
+
? assertAgentKeyId(options.agentKeyId)
|
|
1186
|
+
: config.agentKeyId
|
|
1187
|
+
? assertAgentKeyId(config.agentKeyId, 'configured agentKeyId')
|
|
1188
|
+
: undefined;
|
|
1189
|
+
if (!agentKeyId) {
|
|
1190
|
+
throw new Error(
|
|
1191
|
+
'agentKeyId is required; pass --agent-key-id or configure agentKeyId first',
|
|
1192
|
+
);
|
|
1193
|
+
}
|
|
1194
|
+
|
|
1195
|
+
const daemonSocket = resolveDaemonSocket(
|
|
1196
|
+
options.daemonSocket ?? process.env.WLFI_DAEMON_SOCKET,
|
|
1197
|
+
config,
|
|
1198
|
+
);
|
|
1199
|
+
assertTrustedAdminDaemonSocketPath(daemonSocket);
|
|
1200
|
+
|
|
1201
|
+
const revoked = await runRustBinaryJson<RevokeAgentKeyAdminOutput>(
|
|
1202
|
+
'wlfi-agent-admin',
|
|
1203
|
+
buildRevokeAgentKeyAdminArgs({
|
|
1204
|
+
agentKeyId,
|
|
1205
|
+
vaultPasswordStdin: options.vaultPasswordStdin,
|
|
1206
|
+
nonInteractive: options.nonInteractive,
|
|
1207
|
+
daemonSocket,
|
|
1208
|
+
}),
|
|
1209
|
+
config,
|
|
1210
|
+
);
|
|
1211
|
+
|
|
1212
|
+
print(completeAgentKeyRevocation(revoked), options.json);
|
|
1213
|
+
});
|
|
1214
|
+
|
|
1215
|
+
agentAuthCommand
|
|
1216
|
+
.command('status')
|
|
1217
|
+
.option('--agent-key-id <uuid>', 'Agent key id (defaults to configured agentKeyId)')
|
|
1218
|
+
.option('--json', 'Print JSON output', false)
|
|
1219
|
+
.action((options) => {
|
|
1220
|
+
const config = readConfig();
|
|
1221
|
+
const agentKeyId = options.agentKeyId
|
|
1222
|
+
? assertAgentKeyId(options.agentKeyId)
|
|
1223
|
+
: config.agentKeyId && AGENT_KEY_ID_PATTERN.test(config.agentKeyId.trim())
|
|
1224
|
+
? config.agentKeyId.trim()
|
|
1225
|
+
: null;
|
|
1226
|
+
print(
|
|
1227
|
+
{
|
|
1228
|
+
agentKeyId,
|
|
1229
|
+
keychain: {
|
|
1230
|
+
supported: process.platform === 'darwin',
|
|
1231
|
+
service: process.platform === 'darwin' ? AGENT_AUTH_TOKEN_KEYCHAIN_SERVICE : null,
|
|
1232
|
+
stored: hasStoredAgentAuthToken(agentKeyId ?? undefined),
|
|
1233
|
+
},
|
|
1234
|
+
},
|
|
1235
|
+
options.json,
|
|
1236
|
+
);
|
|
1237
|
+
});
|
|
1238
|
+
|
|
1239
|
+
agentAuthCommand
|
|
1240
|
+
.command('clear')
|
|
1241
|
+
.option('--agent-key-id <uuid>', 'Agent key id (defaults to configured agentKeyId)')
|
|
1242
|
+
.option('--json', 'Print JSON output', false)
|
|
1243
|
+
.action(
|
|
1244
|
+
withLocalAdminMutationAccess('wlfi-agent config agent-auth clear', (options) => {
|
|
1245
|
+
const config = readConfig();
|
|
1246
|
+
const agentKeyId = options.agentKeyId
|
|
1247
|
+
? assertAgentKeyId(options.agentKeyId)
|
|
1248
|
+
: config.agentKeyId
|
|
1249
|
+
? assertAgentKeyId(config.agentKeyId, 'configured agentKeyId')
|
|
1250
|
+
: undefined;
|
|
1251
|
+
if (!agentKeyId) {
|
|
1252
|
+
throw new Error(
|
|
1253
|
+
'agentKeyId is required; pass --agent-key-id or configure agentKeyId first',
|
|
1254
|
+
);
|
|
1255
|
+
}
|
|
1256
|
+
|
|
1257
|
+
print(clearAgentAuthToken(agentKeyId), options.json);
|
|
1258
|
+
}),
|
|
1259
|
+
);
|
|
1260
|
+
|
|
1261
|
+
program
|
|
1262
|
+
.command('wallet')
|
|
1263
|
+
.description('Show the configured wallet public key and associated policy summary')
|
|
1264
|
+
.option('--json', 'Print JSON output', false)
|
|
1265
|
+
.action(async (options) => {
|
|
1266
|
+
const profile = await resolveWalletProfileWithBalances(readConfig(), {
|
|
1267
|
+
getNativeBalance,
|
|
1268
|
+
getTokenBalance,
|
|
1269
|
+
});
|
|
1270
|
+
print(options.json ? profile : formatWalletProfileText(profile), options.json);
|
|
1271
|
+
});
|
|
1272
|
+
|
|
1273
|
+
registerStatusCommand(program, {
|
|
1274
|
+
print: (payload, options) => {
|
|
1275
|
+
print(payload, options.asJson);
|
|
1276
|
+
},
|
|
1277
|
+
setExitCode: (code) => {
|
|
1278
|
+
process.exitCode = code;
|
|
1279
|
+
},
|
|
1280
|
+
});
|
|
1281
|
+
|
|
1282
|
+
registerRepairCommand(program, {
|
|
1283
|
+
print: (payload, options) => {
|
|
1284
|
+
print(payload, options.asJson);
|
|
1285
|
+
},
|
|
1286
|
+
setExitCode: (code) => {
|
|
1287
|
+
process.exitCode = code;
|
|
1288
|
+
},
|
|
1289
|
+
});
|
|
1290
|
+
|
|
1291
|
+
program
|
|
1292
|
+
.command('admin')
|
|
1293
|
+
.helpOption(false)
|
|
1294
|
+
.allowUnknownOption(true)
|
|
1295
|
+
.allowExcessArguments(true)
|
|
1296
|
+
.argument('[args...]')
|
|
1297
|
+
.description('Admin commands and setup passthrough')
|
|
1298
|
+
.action(async () => {
|
|
1299
|
+
const index = process.argv.indexOf('admin');
|
|
1300
|
+
const forwarded = index >= 0 ? process.argv.slice(index + 1) : [];
|
|
1301
|
+
const passthroughTarget = forwarded[0] === 'help' ? forwarded[1] : forwarded[0];
|
|
1302
|
+
const blockedPassthroughMessage = blockedRawAdminPassthroughMessage(passthroughTarget);
|
|
1303
|
+
if (forwarded[0] === 'setup') {
|
|
1304
|
+
await runAdminSetupCli(forwarded.slice(1));
|
|
1305
|
+
return;
|
|
1306
|
+
}
|
|
1307
|
+
if (forwarded[0] === 'tui') {
|
|
1308
|
+
await runAdminTuiCli(forwarded.slice(1));
|
|
1309
|
+
return;
|
|
1310
|
+
}
|
|
1311
|
+
if (forwarded[0] === 'reset') {
|
|
1312
|
+
await runAdminResetCli(forwarded.slice(1));
|
|
1313
|
+
return;
|
|
1314
|
+
}
|
|
1315
|
+
if (forwarded[0] === 'uninstall') {
|
|
1316
|
+
await runAdminUninstallCli(forwarded.slice(1));
|
|
1317
|
+
return;
|
|
1318
|
+
}
|
|
1319
|
+
if (forwarded[0] === 'help' && forwarded[1] === 'tui') {
|
|
1320
|
+
await runAdminTuiCli(['--help']);
|
|
1321
|
+
return;
|
|
1322
|
+
}
|
|
1323
|
+
if (forwarded[0] === 'help' && forwarded[1] === 'setup') {
|
|
1324
|
+
await runAdminSetupCli(['--help']);
|
|
1325
|
+
return;
|
|
1326
|
+
}
|
|
1327
|
+
if (forwarded[0] === 'help' && forwarded[1] === 'reset') {
|
|
1328
|
+
await runAdminResetCli(['--help']);
|
|
1329
|
+
return;
|
|
1330
|
+
}
|
|
1331
|
+
if (forwarded[0] === 'help' && forwarded[1] === 'uninstall') {
|
|
1332
|
+
await runAdminUninstallCli(['--help']);
|
|
1333
|
+
return;
|
|
1334
|
+
}
|
|
1335
|
+
if (blockedPassthroughMessage) {
|
|
1336
|
+
if (forwarded[0] === 'help') {
|
|
1337
|
+
print(blockedPassthroughMessage, false);
|
|
1338
|
+
return;
|
|
1339
|
+
}
|
|
1340
|
+
throw new Error(blockedPassthroughMessage);
|
|
1341
|
+
}
|
|
1342
|
+
if (forwarded[0] === 'bootstrap') {
|
|
1343
|
+
throw new Error(
|
|
1344
|
+
'`wlfi-agent admin bootstrap` has been removed; use `wlfi-agent admin setup`',
|
|
1345
|
+
);
|
|
1346
|
+
}
|
|
1347
|
+
if (await runLocalAdminCommand(forwarded)) {
|
|
1348
|
+
return;
|
|
1349
|
+
}
|
|
1350
|
+
const config = readConfig();
|
|
1351
|
+
const wantsHelp =
|
|
1352
|
+
forwarded.length === 0 ||
|
|
1353
|
+
forwarded[0] === 'help' ||
|
|
1354
|
+
forwarded.includes('--help') ||
|
|
1355
|
+
forwarded.includes('-h');
|
|
1356
|
+
if (wantsHelp) {
|
|
1357
|
+
const rendered = await runRustBinary(
|
|
1358
|
+
'wlfi-agent-admin',
|
|
1359
|
+
forwarded.length === 0 ? ['--help'] : forwarded,
|
|
1360
|
+
config,
|
|
1361
|
+
);
|
|
1362
|
+
if (rendered.stdout) {
|
|
1363
|
+
process.stdout.write(rewriteAdminHelpText(rendered.stdout));
|
|
1364
|
+
}
|
|
1365
|
+
if (rendered.stderr) {
|
|
1366
|
+
process.stderr.write(rewriteAdminHelpText(rendered.stderr));
|
|
1367
|
+
}
|
|
1368
|
+
return;
|
|
1369
|
+
}
|
|
1370
|
+
const code = await passthroughRustBinary('wlfi-agent-admin', forwarded, config);
|
|
1371
|
+
process.exitCode = code;
|
|
1372
|
+
});
|
|
1373
|
+
|
|
1374
|
+
const addAgentCommandAuthOptions = (command: Command) =>
|
|
1375
|
+
command
|
|
1376
|
+
.option('--daemon-socket <path>', 'Daemon socket path')
|
|
1377
|
+
.option('--agent-key-id <uuid>', 'Agent key id')
|
|
1378
|
+
.option('--agent-auth-token <token>', 'Agent auth token')
|
|
1379
|
+
.option('--agent-auth-token-stdin', 'Read agent auth token from stdin', false)
|
|
1380
|
+
.option(
|
|
1381
|
+
'--allow-legacy-agent-auth-source',
|
|
1382
|
+
'Allow deprecated argv/config/env fallback for agent auth token',
|
|
1383
|
+
false,
|
|
1384
|
+
)
|
|
1385
|
+
.option('--json', 'Print JSON output', false);
|
|
1386
|
+
|
|
1387
|
+
addAgentCommandAuthOptions(
|
|
1388
|
+
program
|
|
1389
|
+
.command('transfer')
|
|
1390
|
+
.description('Submit an ERC-20 transfer request through policy checks')
|
|
1391
|
+
.requiredOption('--network <name>', 'Network name')
|
|
1392
|
+
.requiredOption('--token <address>', 'ERC-20 token contract')
|
|
1393
|
+
.requiredOption('--to <address>', 'Recipient address')
|
|
1394
|
+
.requiredOption('--amount <amount>', 'Transfer amount in token units')
|
|
1395
|
+
.option('--broadcast', 'Broadcast the signed transaction through RPC', false)
|
|
1396
|
+
.option('--rpc-url <url>', 'Ethereum RPC URL override used only for broadcast')
|
|
1397
|
+
.option(
|
|
1398
|
+
'--from <address>',
|
|
1399
|
+
'Sender address override for broadcast; defaults to configured wallet address',
|
|
1400
|
+
)
|
|
1401
|
+
.option('--nonce <nonce>', 'Explicit nonce override for broadcast')
|
|
1402
|
+
.option('--gas-limit <gas>', 'Gas limit override for broadcast')
|
|
1403
|
+
.option('--max-fee-per-gas-wei <wei>', 'Max fee per gas override for broadcast')
|
|
1404
|
+
.option('--max-priority-fee-per-gas-wei <wei>', 'Priority fee per gas override for broadcast')
|
|
1405
|
+
.option('--tx-type <type>', 'Typed tx value for broadcast', '0x02')
|
|
1406
|
+
.option('--no-wait', 'Do not wait up to 30s for an on-chain receipt after broadcast')
|
|
1407
|
+
.option(
|
|
1408
|
+
'--reveal-raw-tx',
|
|
1409
|
+
'Include the signed raw transaction bytes in broadcast output',
|
|
1410
|
+
false,
|
|
1411
|
+
)
|
|
1412
|
+
.option('--reveal-signature', 'Include signer r/s/v fields in broadcast output', false)
|
|
1413
|
+
.addOption(new Option('--amount-wei <wei>').hideHelp()),
|
|
1414
|
+
).action(async (options) => {
|
|
1415
|
+
const config = readConfig();
|
|
1416
|
+
const network = resolveCliNetworkProfile(options.network, config).chainId;
|
|
1417
|
+
const token = assertAddress(options.token, 'token');
|
|
1418
|
+
const recipient = assertAddress(options.to, 'to');
|
|
1419
|
+
const asset = resolveConfiguredErc20Asset(config, network, token);
|
|
1420
|
+
const amountWei = options.amount
|
|
1421
|
+
? parseConfiguredAmount(options.amount, asset.decimals, 'amount')
|
|
1422
|
+
: parseBigIntString(options.amountWei, 'amountWei');
|
|
1423
|
+
try {
|
|
1424
|
+
if (options.broadcast) {
|
|
1425
|
+
const plan = await resolveAssetBroadcastPlan(
|
|
1426
|
+
{
|
|
1427
|
+
rpcUrl: resolveCliRpcUrl(options.rpcUrl, options.network, config),
|
|
1428
|
+
chainId: network,
|
|
1429
|
+
from: options.from ? assertAddress(options.from, 'from') : resolveWalletAddress(config),
|
|
1430
|
+
to: token,
|
|
1431
|
+
valueWei: 0n,
|
|
1432
|
+
dataHex: encodeErc20TransferData(recipient, amountWei),
|
|
1433
|
+
nonce: options.nonce ? parseIntegerString(options.nonce, 'nonce') : undefined,
|
|
1434
|
+
gasLimit: options.gasLimit
|
|
1435
|
+
? parsePositiveBigIntString(options.gasLimit, 'gasLimit')
|
|
1436
|
+
: undefined,
|
|
1437
|
+
maxFeePerGasWei: options.maxFeePerGasWei
|
|
1438
|
+
? parsePositiveBigIntString(options.maxFeePerGasWei, 'maxFeePerGasWei')
|
|
1439
|
+
: undefined,
|
|
1440
|
+
maxPriorityFeePerGasWei: options.maxPriorityFeePerGasWei
|
|
1441
|
+
? parseBigIntString(options.maxPriorityFeePerGasWei, 'maxPriorityFeePerGasWei')
|
|
1442
|
+
: undefined,
|
|
1443
|
+
txType: options.txType,
|
|
1444
|
+
},
|
|
1445
|
+
{
|
|
1446
|
+
getChainInfo,
|
|
1447
|
+
assertRpcChainIdMatches,
|
|
1448
|
+
getNonce,
|
|
1449
|
+
estimateGas,
|
|
1450
|
+
estimateFees,
|
|
1451
|
+
},
|
|
1452
|
+
);
|
|
1453
|
+
const signed = await runAgentCommandJson<RustBroadcastOutput>({
|
|
1454
|
+
commandArgs: [
|
|
1455
|
+
'broadcast',
|
|
1456
|
+
'--network',
|
|
1457
|
+
String(plan.chainId),
|
|
1458
|
+
'--nonce',
|
|
1459
|
+
String(plan.nonce),
|
|
1460
|
+
'--to',
|
|
1461
|
+
token,
|
|
1462
|
+
'--value-wei',
|
|
1463
|
+
'0',
|
|
1464
|
+
'--data-hex',
|
|
1465
|
+
plan.dataHex,
|
|
1466
|
+
'--gas-limit',
|
|
1467
|
+
plan.gasLimit.toString(),
|
|
1468
|
+
'--max-fee-per-gas-wei',
|
|
1469
|
+
plan.maxFeePerGasWei.toString(),
|
|
1470
|
+
'--max-priority-fee-per-gas-wei',
|
|
1471
|
+
plan.maxPriorityFeePerGasWei.toString(),
|
|
1472
|
+
'--tx-type',
|
|
1473
|
+
plan.txType,
|
|
1474
|
+
],
|
|
1475
|
+
auth: options,
|
|
1476
|
+
config,
|
|
1477
|
+
asJson: options.json,
|
|
1478
|
+
});
|
|
1479
|
+
if (!signed) {
|
|
1480
|
+
return;
|
|
1481
|
+
}
|
|
1482
|
+
const completed = await completeAssetBroadcast(plan, signed, {
|
|
1483
|
+
assertSignedBroadcastTransactionMatchesRequest,
|
|
1484
|
+
broadcastRawTransaction,
|
|
1485
|
+
});
|
|
1486
|
+
print(
|
|
1487
|
+
formatBroadcastedAssetOutput({
|
|
1488
|
+
command: 'transfer',
|
|
1489
|
+
counterparty: recipient,
|
|
1490
|
+
asset,
|
|
1491
|
+
signed,
|
|
1492
|
+
plan,
|
|
1493
|
+
signedNonce: completed.signedNonce,
|
|
1494
|
+
networkTxHash: completed.networkTxHash,
|
|
1495
|
+
revealRawTx: options.revealRawTx,
|
|
1496
|
+
revealSignature: options.revealSignature,
|
|
1497
|
+
}),
|
|
1498
|
+
options.json,
|
|
1499
|
+
);
|
|
1500
|
+
if (options.wait) {
|
|
1501
|
+
await reportOnchainReceiptStatus({
|
|
1502
|
+
rpcUrl: plan.rpcUrl,
|
|
1503
|
+
txHash: completed.networkTxHash,
|
|
1504
|
+
asJson: options.json,
|
|
1505
|
+
});
|
|
1506
|
+
}
|
|
1507
|
+
return;
|
|
1508
|
+
}
|
|
1509
|
+
|
|
1510
|
+
const result = await runAgentCommandJson<RustBroadcastOutput>({
|
|
1511
|
+
commandArgs: [
|
|
1512
|
+
'transfer',
|
|
1513
|
+
'--network',
|
|
1514
|
+
String(network),
|
|
1515
|
+
'--token',
|
|
1516
|
+
token,
|
|
1517
|
+
'--to',
|
|
1518
|
+
recipient,
|
|
1519
|
+
'--amount-wei',
|
|
1520
|
+
amountWei.toString(),
|
|
1521
|
+
],
|
|
1522
|
+
auth: options,
|
|
1523
|
+
config,
|
|
1524
|
+
asJson: options.json,
|
|
1525
|
+
});
|
|
1526
|
+
if (result) {
|
|
1527
|
+
print(normalizeAgentAmountOutput(result, asset), options.json);
|
|
1528
|
+
}
|
|
1529
|
+
} catch (error) {
|
|
1530
|
+
throw rewriteAgentAmountError(error, asset);
|
|
1531
|
+
}
|
|
1532
|
+
});
|
|
1533
|
+
|
|
1534
|
+
addAgentCommandAuthOptions(
|
|
1535
|
+
program
|
|
1536
|
+
.command('transfer-native')
|
|
1537
|
+
.description('Submit a native ETH transfer request through policy checks')
|
|
1538
|
+
.requiredOption('--network <name>', 'Network name')
|
|
1539
|
+
.requiredOption('--to <address>', 'Recipient address')
|
|
1540
|
+
.requiredOption('--amount <amount>', 'Transfer amount in configured native token units')
|
|
1541
|
+
.addOption(new Option('--amount-wei <wei>').hideHelp()),
|
|
1542
|
+
).action(async (options) => {
|
|
1543
|
+
const config = readConfig();
|
|
1544
|
+
const network = resolveCliNetworkProfile(options.network, config).chainId;
|
|
1545
|
+
const asset = resolveConfiguredNativeAsset(config, network);
|
|
1546
|
+
const amountWei = options.amount
|
|
1547
|
+
? parseConfiguredAmount(options.amount, asset.decimals, 'amount')
|
|
1548
|
+
: parseBigIntString(options.amountWei, 'amountWei');
|
|
1549
|
+
try {
|
|
1550
|
+
const result = await runAgentCommandJson<RustBroadcastOutput>({
|
|
1551
|
+
commandArgs: [
|
|
1552
|
+
'transfer-native',
|
|
1553
|
+
'--network',
|
|
1554
|
+
String(network),
|
|
1555
|
+
'--to',
|
|
1556
|
+
assertAddress(options.to, 'to'),
|
|
1557
|
+
'--amount-wei',
|
|
1558
|
+
amountWei.toString(),
|
|
1559
|
+
],
|
|
1560
|
+
auth: options,
|
|
1561
|
+
config,
|
|
1562
|
+
asJson: options.json,
|
|
1563
|
+
});
|
|
1564
|
+
if (result) {
|
|
1565
|
+
print(normalizeAgentAmountOutput(result, asset), options.json);
|
|
1566
|
+
}
|
|
1567
|
+
} catch (error) {
|
|
1568
|
+
throw rewriteAgentAmountError(error, asset);
|
|
1569
|
+
}
|
|
1570
|
+
});
|
|
1571
|
+
|
|
1572
|
+
addAgentCommandAuthOptions(
|
|
1573
|
+
program
|
|
1574
|
+
.command('approve')
|
|
1575
|
+
.description('Submit an ERC-20 approve request through policy checks')
|
|
1576
|
+
.requiredOption('--network <name>', 'Network name')
|
|
1577
|
+
.requiredOption('--token <address>', 'ERC-20 token contract')
|
|
1578
|
+
.requiredOption('--spender <address>', 'Spender address')
|
|
1579
|
+
.requiredOption('--amount <amount>', 'Approval amount in token units')
|
|
1580
|
+
.option('--broadcast', 'Broadcast the signed transaction through RPC', false)
|
|
1581
|
+
.option('--rpc-url <url>', 'Ethereum RPC URL override used only for broadcast')
|
|
1582
|
+
.option(
|
|
1583
|
+
'--from <address>',
|
|
1584
|
+
'Sender address override for broadcast; defaults to configured wallet address',
|
|
1585
|
+
)
|
|
1586
|
+
.option('--nonce <nonce>', 'Explicit nonce override for broadcast')
|
|
1587
|
+
.option('--gas-limit <gas>', 'Gas limit override for broadcast')
|
|
1588
|
+
.option('--max-fee-per-gas-wei <wei>', 'Max fee per gas override for broadcast')
|
|
1589
|
+
.option('--max-priority-fee-per-gas-wei <wei>', 'Priority fee per gas override for broadcast')
|
|
1590
|
+
.option('--tx-type <type>', 'Typed tx value for broadcast', '0x02')
|
|
1591
|
+
.option('--no-wait', 'Do not wait up to 30s for an on-chain receipt after broadcast')
|
|
1592
|
+
.option(
|
|
1593
|
+
'--reveal-raw-tx',
|
|
1594
|
+
'Include the signed raw transaction bytes in broadcast output',
|
|
1595
|
+
false,
|
|
1596
|
+
)
|
|
1597
|
+
.option('--reveal-signature', 'Include signer r/s/v fields in broadcast output', false)
|
|
1598
|
+
.addOption(new Option('--amount-wei <wei>').hideHelp()),
|
|
1599
|
+
).action(async (options) => {
|
|
1600
|
+
const config = readConfig();
|
|
1601
|
+
const network = resolveCliNetworkProfile(options.network, config).chainId;
|
|
1602
|
+
const token = assertAddress(options.token, 'token');
|
|
1603
|
+
const spender = assertAddress(options.spender, 'spender');
|
|
1604
|
+
const asset = resolveConfiguredErc20Asset(config, network, token);
|
|
1605
|
+
const amountWei = options.amount
|
|
1606
|
+
? parseConfiguredAmount(options.amount, asset.decimals, 'amount')
|
|
1607
|
+
: parseBigIntString(options.amountWei, 'amountWei');
|
|
1608
|
+
try {
|
|
1609
|
+
if (options.broadcast) {
|
|
1610
|
+
const plan = await resolveAssetBroadcastPlan(
|
|
1611
|
+
{
|
|
1612
|
+
rpcUrl: resolveCliRpcUrl(options.rpcUrl, options.network, config),
|
|
1613
|
+
chainId: network,
|
|
1614
|
+
from: options.from ? assertAddress(options.from, 'from') : resolveWalletAddress(config),
|
|
1615
|
+
to: token,
|
|
1616
|
+
valueWei: 0n,
|
|
1617
|
+
dataHex: encodeErc20ApproveData(spender, amountWei),
|
|
1618
|
+
nonce: options.nonce ? parseIntegerString(options.nonce, 'nonce') : undefined,
|
|
1619
|
+
gasLimit: options.gasLimit
|
|
1620
|
+
? parsePositiveBigIntString(options.gasLimit, 'gasLimit')
|
|
1621
|
+
: undefined,
|
|
1622
|
+
maxFeePerGasWei: options.maxFeePerGasWei
|
|
1623
|
+
? parsePositiveBigIntString(options.maxFeePerGasWei, 'maxFeePerGasWei')
|
|
1624
|
+
: undefined,
|
|
1625
|
+
maxPriorityFeePerGasWei: options.maxPriorityFeePerGasWei
|
|
1626
|
+
? parseBigIntString(options.maxPriorityFeePerGasWei, 'maxPriorityFeePerGasWei')
|
|
1627
|
+
: undefined,
|
|
1628
|
+
txType: options.txType,
|
|
1629
|
+
},
|
|
1630
|
+
{
|
|
1631
|
+
getChainInfo,
|
|
1632
|
+
assertRpcChainIdMatches,
|
|
1633
|
+
getNonce,
|
|
1634
|
+
estimateGas,
|
|
1635
|
+
estimateFees,
|
|
1636
|
+
},
|
|
1637
|
+
);
|
|
1638
|
+
const signed = await runAgentCommandJson<RustBroadcastOutput>({
|
|
1639
|
+
commandArgs: [
|
|
1640
|
+
'broadcast',
|
|
1641
|
+
'--network',
|
|
1642
|
+
String(plan.chainId),
|
|
1643
|
+
'--nonce',
|
|
1644
|
+
String(plan.nonce),
|
|
1645
|
+
'--to',
|
|
1646
|
+
token,
|
|
1647
|
+
'--value-wei',
|
|
1648
|
+
'0',
|
|
1649
|
+
'--data-hex',
|
|
1650
|
+
plan.dataHex,
|
|
1651
|
+
'--gas-limit',
|
|
1652
|
+
plan.gasLimit.toString(),
|
|
1653
|
+
'--max-fee-per-gas-wei',
|
|
1654
|
+
plan.maxFeePerGasWei.toString(),
|
|
1655
|
+
'--max-priority-fee-per-gas-wei',
|
|
1656
|
+
plan.maxPriorityFeePerGasWei.toString(),
|
|
1657
|
+
'--tx-type',
|
|
1658
|
+
plan.txType,
|
|
1659
|
+
],
|
|
1660
|
+
auth: options,
|
|
1661
|
+
config,
|
|
1662
|
+
asJson: options.json,
|
|
1663
|
+
});
|
|
1664
|
+
if (!signed) {
|
|
1665
|
+
return;
|
|
1666
|
+
}
|
|
1667
|
+
const completed = await completeAssetBroadcast(plan, signed, {
|
|
1668
|
+
assertSignedBroadcastTransactionMatchesRequest,
|
|
1669
|
+
broadcastRawTransaction,
|
|
1670
|
+
});
|
|
1671
|
+
print(
|
|
1672
|
+
formatBroadcastedAssetOutput({
|
|
1673
|
+
command: 'approve',
|
|
1674
|
+
counterparty: spender,
|
|
1675
|
+
asset,
|
|
1676
|
+
signed,
|
|
1677
|
+
plan,
|
|
1678
|
+
signedNonce: completed.signedNonce,
|
|
1679
|
+
networkTxHash: completed.networkTxHash,
|
|
1680
|
+
revealRawTx: options.revealRawTx,
|
|
1681
|
+
revealSignature: options.revealSignature,
|
|
1682
|
+
}),
|
|
1683
|
+
options.json,
|
|
1684
|
+
);
|
|
1685
|
+
if (options.wait) {
|
|
1686
|
+
await reportOnchainReceiptStatus({
|
|
1687
|
+
rpcUrl: plan.rpcUrl,
|
|
1688
|
+
txHash: completed.networkTxHash,
|
|
1689
|
+
asJson: options.json,
|
|
1690
|
+
});
|
|
1691
|
+
}
|
|
1692
|
+
return;
|
|
1693
|
+
}
|
|
1694
|
+
|
|
1695
|
+
const result = await runAgentCommandJson<RustBroadcastOutput>({
|
|
1696
|
+
commandArgs: [
|
|
1697
|
+
'approve',
|
|
1698
|
+
'--network',
|
|
1699
|
+
String(network),
|
|
1700
|
+
'--token',
|
|
1701
|
+
token,
|
|
1702
|
+
'--spender',
|
|
1703
|
+
spender,
|
|
1704
|
+
'--amount-wei',
|
|
1705
|
+
amountWei.toString(),
|
|
1706
|
+
],
|
|
1707
|
+
auth: options,
|
|
1708
|
+
config,
|
|
1709
|
+
asJson: options.json,
|
|
1710
|
+
});
|
|
1711
|
+
if (result) {
|
|
1712
|
+
print(normalizeAgentAmountOutput(result, asset), options.json);
|
|
1713
|
+
}
|
|
1714
|
+
} catch (error) {
|
|
1715
|
+
throw rewriteAgentAmountError(error, asset);
|
|
1716
|
+
}
|
|
1717
|
+
});
|
|
1718
|
+
|
|
1719
|
+
addAgentCommandAuthOptions(
|
|
1720
|
+
program
|
|
1721
|
+
.command('broadcast')
|
|
1722
|
+
.description('Submit a raw transaction broadcast request through policy checks')
|
|
1723
|
+
.requiredOption('--network <name>', 'Network name')
|
|
1724
|
+
.requiredOption('--to <address>', 'Recipient or target contract')
|
|
1725
|
+
.requiredOption('--gas-limit <gas>', 'Gas limit')
|
|
1726
|
+
.requiredOption('--max-fee-per-gas-wei <wei>', 'Max fee per gas in wei')
|
|
1727
|
+
.option('--nonce <nonce>', 'Explicit nonce override', '0')
|
|
1728
|
+
.option('--value-wei <wei>', 'Value in wei', '0')
|
|
1729
|
+
.option('--data-hex <hex>', 'Calldata hex', '0x')
|
|
1730
|
+
.option('--max-priority-fee-per-gas-wei <wei>', 'Priority fee per gas in wei', '0')
|
|
1731
|
+
.option('--tx-type <type>', 'Typed tx value', '2')
|
|
1732
|
+
.option('--delegation-enabled', 'Forward delegation flag to Rust signing request', false),
|
|
1733
|
+
).action(async (options) => {
|
|
1734
|
+
const config = readConfig();
|
|
1735
|
+
const network = resolveCliNetworkProfile(options.network, config);
|
|
1736
|
+
const result = await runAgentCommandJson<RustBroadcastOutput>({
|
|
1737
|
+
commandArgs: [
|
|
1738
|
+
'broadcast',
|
|
1739
|
+
'--network',
|
|
1740
|
+
String(network.chainId),
|
|
1741
|
+
'--nonce',
|
|
1742
|
+
String(parseIntegerString(options.nonce, 'nonce')),
|
|
1743
|
+
'--to',
|
|
1744
|
+
assertAddress(options.to, 'to'),
|
|
1745
|
+
'--value-wei',
|
|
1746
|
+
parseBigIntString(options.valueWei, 'valueWei').toString(),
|
|
1747
|
+
'--data-hex',
|
|
1748
|
+
assertHex(options.dataHex, 'dataHex'),
|
|
1749
|
+
'--gas-limit',
|
|
1750
|
+
parsePositiveBigIntString(options.gasLimit, 'gasLimit').toString(),
|
|
1751
|
+
'--max-fee-per-gas-wei',
|
|
1752
|
+
parsePositiveBigIntString(options.maxFeePerGasWei, 'maxFeePerGasWei').toString(),
|
|
1753
|
+
'--max-priority-fee-per-gas-wei',
|
|
1754
|
+
parseBigIntString(options.maxPriorityFeePerGasWei, 'maxPriorityFeePerGasWei').toString(),
|
|
1755
|
+
'--tx-type',
|
|
1756
|
+
options.txType,
|
|
1757
|
+
...(options.delegationEnabled ? ['--delegation-enabled'] : []),
|
|
1758
|
+
],
|
|
1759
|
+
auth: options,
|
|
1760
|
+
config,
|
|
1761
|
+
asJson: options.json,
|
|
1762
|
+
});
|
|
1763
|
+
if (result) {
|
|
1764
|
+
print(result, options.json);
|
|
1765
|
+
}
|
|
1766
|
+
});
|
|
1767
|
+
|
|
1768
|
+
const rpc = program.command('rpc').description('RPC methods implemented in TypeScript');
|
|
1769
|
+
rpc
|
|
1770
|
+
.command('chain')
|
|
1771
|
+
.option('--rpc-url <url>', 'Ethereum RPC URL')
|
|
1772
|
+
.option('--json', 'Print JSON output', false)
|
|
1773
|
+
.action(async (options) => {
|
|
1774
|
+
const config = readConfig();
|
|
1775
|
+
const rpcUrl = resolveRpcUrl(options.rpcUrl, config);
|
|
1776
|
+
const result = await getChainInfo(rpcUrl);
|
|
1777
|
+
print(
|
|
1778
|
+
{
|
|
1779
|
+
rpcUrl,
|
|
1780
|
+
...result,
|
|
1781
|
+
configured: activeChainSummary(config),
|
|
1782
|
+
},
|
|
1783
|
+
options.json,
|
|
1784
|
+
);
|
|
1785
|
+
});
|
|
1786
|
+
rpc
|
|
1787
|
+
.command('block-number')
|
|
1788
|
+
.option('--rpc-url <url>', 'Ethereum RPC URL')
|
|
1789
|
+
.option('--json', 'Print JSON output', false)
|
|
1790
|
+
.action(async (options) => {
|
|
1791
|
+
const config = readConfig();
|
|
1792
|
+
const rpcUrl = resolveRpcUrl(options.rpcUrl, config);
|
|
1793
|
+
const blockNumber = await getLatestBlockNumber(rpcUrl);
|
|
1794
|
+
print({ rpcUrl, blockNumber: blockNumber.toString() }, options.json);
|
|
1795
|
+
});
|
|
1796
|
+
rpc
|
|
1797
|
+
.command('account')
|
|
1798
|
+
.requiredOption('--address <address>', 'Account address')
|
|
1799
|
+
.option('--rpc-url <url>', 'Ethereum RPC URL')
|
|
1800
|
+
.option('--json', 'Print JSON output', false)
|
|
1801
|
+
.action(async (options) => {
|
|
1802
|
+
const config = readConfig();
|
|
1803
|
+
const rpcUrl = resolveRpcUrl(options.rpcUrl, config);
|
|
1804
|
+
const address = assertAddress(options.address, 'address');
|
|
1805
|
+
const snapshot = await getAccountSnapshot(rpcUrl, address);
|
|
1806
|
+
print(
|
|
1807
|
+
{
|
|
1808
|
+
...snapshot,
|
|
1809
|
+
latestBlockNumber: snapshot.latestBlockNumber.toString(),
|
|
1810
|
+
balance: {
|
|
1811
|
+
raw: snapshot.balance.raw.toString(),
|
|
1812
|
+
formatted: snapshot.balance.formatted,
|
|
1813
|
+
},
|
|
1814
|
+
},
|
|
1815
|
+
options.json,
|
|
1816
|
+
);
|
|
1817
|
+
});
|
|
1818
|
+
rpc
|
|
1819
|
+
.command('balance')
|
|
1820
|
+
.requiredOption('--address <address>', 'Account address')
|
|
1821
|
+
.option('--token <address>', 'Optional ERC-20 token address')
|
|
1822
|
+
.option('--rpc-url <url>', 'Ethereum RPC URL')
|
|
1823
|
+
.option('--decimals <decimals>', 'ERC-20 decimals override')
|
|
1824
|
+
.option('--json', 'Print JSON output', false)
|
|
1825
|
+
.action(async (options) => {
|
|
1826
|
+
const config = readConfig();
|
|
1827
|
+
const rpcUrl = resolveRpcUrl(options.rpcUrl, config);
|
|
1828
|
+
const owner = assertAddress(options.address, 'address');
|
|
1829
|
+
if (options.token) {
|
|
1830
|
+
const token = assertAddress(options.token, 'token');
|
|
1831
|
+
const result = await getTokenBalance(
|
|
1832
|
+
rpcUrl,
|
|
1833
|
+
token,
|
|
1834
|
+
owner,
|
|
1835
|
+
options.decimals ? parseIntegerString(options.decimals, 'decimals') : undefined,
|
|
1836
|
+
);
|
|
1837
|
+
print(
|
|
1838
|
+
{
|
|
1839
|
+
kind: 'erc20',
|
|
1840
|
+
token,
|
|
1841
|
+
owner,
|
|
1842
|
+
balanceWei: result.raw.toString(),
|
|
1843
|
+
decimals: result.decimals,
|
|
1844
|
+
name: result.name,
|
|
1845
|
+
symbol: result.symbol,
|
|
1846
|
+
formatted: result.formatted,
|
|
1847
|
+
},
|
|
1848
|
+
options.json,
|
|
1849
|
+
);
|
|
1850
|
+
return;
|
|
1851
|
+
}
|
|
1852
|
+
|
|
1853
|
+
const result = await getNativeBalance(rpcUrl, owner);
|
|
1854
|
+
print(
|
|
1855
|
+
{
|
|
1856
|
+
kind: 'native',
|
|
1857
|
+
owner,
|
|
1858
|
+
balanceWei: result.raw.toString(),
|
|
1859
|
+
formattedEth: result.formatted,
|
|
1860
|
+
},
|
|
1861
|
+
options.json,
|
|
1862
|
+
);
|
|
1863
|
+
});
|
|
1864
|
+
rpc
|
|
1865
|
+
.command('nonce')
|
|
1866
|
+
.requiredOption('--address <address>', 'Account address')
|
|
1867
|
+
.option('--rpc-url <url>', 'Ethereum RPC URL')
|
|
1868
|
+
.option('--json', 'Print JSON output', false)
|
|
1869
|
+
.action(async (options) => {
|
|
1870
|
+
const config = readConfig();
|
|
1871
|
+
const rpcUrl = resolveRpcUrl(options.rpcUrl, config);
|
|
1872
|
+
const address = assertAddress(options.address, 'address');
|
|
1873
|
+
const nonce = await getNonce(rpcUrl, address);
|
|
1874
|
+
print({ address, nonce }, options.json);
|
|
1875
|
+
});
|
|
1876
|
+
rpc
|
|
1877
|
+
.command('fees')
|
|
1878
|
+
.option('--rpc-url <url>', 'Ethereum RPC URL')
|
|
1879
|
+
.option('--json', 'Print JSON output', false)
|
|
1880
|
+
.action(async (options) => {
|
|
1881
|
+
const config = readConfig();
|
|
1882
|
+
const rpcUrl = resolveRpcUrl(options.rpcUrl, config);
|
|
1883
|
+
const fees = await estimateFees(rpcUrl);
|
|
1884
|
+
print(
|
|
1885
|
+
{
|
|
1886
|
+
rpcUrl,
|
|
1887
|
+
gasPrice: stringifyOptionalValue(fees.gasPrice),
|
|
1888
|
+
maxFeePerGas: stringifyOptionalValue(fees.maxFeePerGas),
|
|
1889
|
+
maxPriorityFeePerGas: stringifyOptionalValue(fees.maxPriorityFeePerGas),
|
|
1890
|
+
},
|
|
1891
|
+
options.json,
|
|
1892
|
+
);
|
|
1893
|
+
});
|
|
1894
|
+
rpc
|
|
1895
|
+
.command('gas-estimate')
|
|
1896
|
+
.requiredOption('--from <address>', 'Sender address')
|
|
1897
|
+
.requiredOption('--to <address>', 'Recipient or target contract')
|
|
1898
|
+
.option('--value-wei <wei>', 'Value in wei', '0')
|
|
1899
|
+
.option('--data-hex <hex>', 'Calldata hex', '0x')
|
|
1900
|
+
.option('--rpc-url <url>', 'Ethereum RPC URL')
|
|
1901
|
+
.option('--json', 'Print JSON output', false)
|
|
1902
|
+
.action(async (options) => {
|
|
1903
|
+
const config = readConfig();
|
|
1904
|
+
const rpcUrl = resolveRpcUrl(options.rpcUrl, config);
|
|
1905
|
+
const gas = await estimateGas({
|
|
1906
|
+
rpcUrl,
|
|
1907
|
+
from: assertAddress(options.from, 'from'),
|
|
1908
|
+
to: assertAddress(options.to, 'to'),
|
|
1909
|
+
value: parseBigIntString(options.valueWei, 'valueWei'),
|
|
1910
|
+
data: assertHex(options.dataHex, 'dataHex'),
|
|
1911
|
+
});
|
|
1912
|
+
print({ rpcUrl, gas: gas.toString() }, options.json);
|
|
1913
|
+
});
|
|
1914
|
+
rpc
|
|
1915
|
+
.command('tx')
|
|
1916
|
+
.requiredOption('--hash <hash>', 'Transaction hash')
|
|
1917
|
+
.option('--rpc-url <url>', 'Ethereum RPC URL')
|
|
1918
|
+
.option('--json', 'Print JSON output', false)
|
|
1919
|
+
.action(async (options) => {
|
|
1920
|
+
const config = readConfig();
|
|
1921
|
+
const rpcUrl = resolveRpcUrl(options.rpcUrl, config);
|
|
1922
|
+
const tx = await getTransactionByHash(rpcUrl, assertHex(options.hash, 'hash'));
|
|
1923
|
+
print(tx, options.json);
|
|
1924
|
+
});
|
|
1925
|
+
rpc
|
|
1926
|
+
.command('receipt')
|
|
1927
|
+
.requiredOption('--hash <hash>', 'Transaction hash')
|
|
1928
|
+
.option('--rpc-url <url>', 'Ethereum RPC URL')
|
|
1929
|
+
.option('--json', 'Print JSON output', false)
|
|
1930
|
+
.action(async (options) => {
|
|
1931
|
+
const config = readConfig();
|
|
1932
|
+
const rpcUrl = resolveRpcUrl(options.rpcUrl, config);
|
|
1933
|
+
const receipt = await getTransactionReceiptByHash(rpcUrl, assertHex(options.hash, 'hash'));
|
|
1934
|
+
print(receipt, options.json);
|
|
1935
|
+
});
|
|
1936
|
+
rpc
|
|
1937
|
+
.command('code')
|
|
1938
|
+
.requiredOption('--address <address>', 'Contract address')
|
|
1939
|
+
.option('--rpc-url <url>', 'Ethereum RPC URL')
|
|
1940
|
+
.option('--json', 'Print JSON output', false)
|
|
1941
|
+
.action(async (options) => {
|
|
1942
|
+
const config = readConfig();
|
|
1943
|
+
const rpcUrl = resolveRpcUrl(options.rpcUrl, config);
|
|
1944
|
+
const bytecode = await getCodeAtAddress(rpcUrl, assertAddress(options.address, 'address'));
|
|
1945
|
+
print(
|
|
1946
|
+
{
|
|
1947
|
+
address: options.address,
|
|
1948
|
+
rpcUrl,
|
|
1949
|
+
bytecode: bytecode ?? '0x',
|
|
1950
|
+
hasCode: bytecode !== undefined && bytecode !== '0x',
|
|
1951
|
+
},
|
|
1952
|
+
options.json,
|
|
1953
|
+
);
|
|
1954
|
+
});
|
|
1955
|
+
rpc
|
|
1956
|
+
.command('broadcast-raw')
|
|
1957
|
+
.requiredOption('--raw-tx-hex <hex>', 'Signed raw transaction hex')
|
|
1958
|
+
.option('--rpc-url <url>', 'Ethereum RPC URL')
|
|
1959
|
+
.option('--no-wait', 'Do not wait up to 30s for an on-chain receipt after broadcast')
|
|
1960
|
+
.option('--json', 'Print JSON output', false)
|
|
1961
|
+
.action(async (options) => {
|
|
1962
|
+
const config = readConfig();
|
|
1963
|
+
const rpcUrl = resolveRpcUrl(options.rpcUrl, config);
|
|
1964
|
+
const txHash = await broadcastRawTransaction(rpcUrl, assertHex(options.rawTxHex, 'rawTxHex'));
|
|
1965
|
+
print({ txHash }, options.json);
|
|
1966
|
+
if (options.wait) {
|
|
1967
|
+
await reportOnchainReceiptStatus({
|
|
1968
|
+
rpcUrl,
|
|
1969
|
+
txHash,
|
|
1970
|
+
asJson: options.json,
|
|
1971
|
+
});
|
|
1972
|
+
}
|
|
1973
|
+
});
|
|
1974
|
+
|
|
1975
|
+
const tx = program
|
|
1976
|
+
.command('tx')
|
|
1977
|
+
.description('Sign with Rust, then perform network RPC in TypeScript');
|
|
1978
|
+
tx.command('broadcast')
|
|
1979
|
+
.requiredOption('--from <address>', 'Sender address for nonce/gas estimation')
|
|
1980
|
+
.requiredOption('--to <address>', 'Recipient or target contract')
|
|
1981
|
+
.option('--network <name>', 'Network name')
|
|
1982
|
+
.option('--rpc-url <url>', 'Ethereum RPC URL')
|
|
1983
|
+
.option('--daemon-socket <path>', 'Daemon socket path')
|
|
1984
|
+
.option('--agent-key-id <uuid>', 'Agent key id')
|
|
1985
|
+
.option('--agent-auth-token <token>', 'Agent auth token')
|
|
1986
|
+
.option('--agent-auth-token-stdin', 'Read agent auth token from stdin', false)
|
|
1987
|
+
.option(
|
|
1988
|
+
'--allow-legacy-agent-auth-source',
|
|
1989
|
+
'Allow deprecated argv/config/env fallback for agent auth token',
|
|
1990
|
+
false,
|
|
1991
|
+
)
|
|
1992
|
+
.option('--nonce <nonce>', 'Explicit nonce override')
|
|
1993
|
+
.option('--value-wei <wei>', 'Value in wei', '0')
|
|
1994
|
+
.option('--data-hex <hex>', 'Calldata hex', '0x')
|
|
1995
|
+
.option('--gas-limit <gas>', 'Gas limit override')
|
|
1996
|
+
.option('--max-fee-per-gas-wei <wei>', 'Max fee per gas override')
|
|
1997
|
+
.option('--max-priority-fee-per-gas-wei <wei>', 'Priority fee per gas override')
|
|
1998
|
+
.option('--tx-type <type>', 'Typed tx value', '0x02')
|
|
1999
|
+
.option('--delegation-enabled', 'Forward delegation flag to Rust signing request', false)
|
|
2000
|
+
.option('--no-wait', 'Do not wait up to 30s for an on-chain receipt after broadcast')
|
|
2001
|
+
.option('--reveal-raw-tx', 'Include the signed raw transaction bytes in output', false)
|
|
2002
|
+
.option('--reveal-signature', 'Include signer r/s/v fields in output', false)
|
|
2003
|
+
.option('--json', 'Print JSON output', false)
|
|
2004
|
+
.action(async (options) => {
|
|
2005
|
+
const config = readConfig();
|
|
2006
|
+
const network = resolveCliNetworkProfile(options.network, config);
|
|
2007
|
+
const rpcUrl = resolveCliRpcUrl(options.rpcUrl, options.network, config);
|
|
2008
|
+
const chainId = network.chainId;
|
|
2009
|
+
const chainInfo = await getChainInfo(rpcUrl);
|
|
2010
|
+
assertRpcChainIdMatches(chainId, chainInfo.chainId);
|
|
2011
|
+
const from = assertAddress(options.from, 'from');
|
|
2012
|
+
const to = assertAddress(options.to, 'to');
|
|
2013
|
+
const valueWei = parseBigIntString(options.valueWei, 'valueWei');
|
|
2014
|
+
const dataHex = assertHex(options.dataHex, 'dataHex');
|
|
2015
|
+
const nonce = options.nonce
|
|
2016
|
+
? parseIntegerString(options.nonce, 'nonce')
|
|
2017
|
+
: await getNonce(rpcUrl, from);
|
|
2018
|
+
const gasLimit = options.gasLimit
|
|
2019
|
+
? parsePositiveBigIntString(options.gasLimit, 'gasLimit')
|
|
2020
|
+
: await estimateGas({ rpcUrl, from, to, value: valueWei, data: dataHex });
|
|
2021
|
+
const fees = await estimateFees(rpcUrl);
|
|
2022
|
+
const maxFeePerGasWei = options.maxFeePerGasWei
|
|
2023
|
+
? parsePositiveBigIntString(options.maxFeePerGasWei, 'maxFeePerGasWei')
|
|
2024
|
+
: (fees.maxFeePerGas ?? fees.gasPrice);
|
|
2025
|
+
const maxPriorityFeePerGasWei = options.maxPriorityFeePerGasWei
|
|
2026
|
+
? parseBigIntString(options.maxPriorityFeePerGasWei, 'maxPriorityFeePerGasWei')
|
|
2027
|
+
: (fees.maxPriorityFeePerGas ?? fees.gasPrice ?? 0n);
|
|
2028
|
+
|
|
2029
|
+
if (maxFeePerGasWei === null || maxFeePerGasWei <= 0n) {
|
|
2030
|
+
throw new Error('Could not determine maxFeePerGas; pass --max-fee-per-gas-wei');
|
|
2031
|
+
}
|
|
2032
|
+
|
|
2033
|
+
const signed = await runAgentCommandJson<RustBroadcastOutput>({
|
|
2034
|
+
commandArgs: [
|
|
2035
|
+
'broadcast',
|
|
2036
|
+
'--network',
|
|
2037
|
+
String(chainId),
|
|
2038
|
+
'--nonce',
|
|
2039
|
+
String(nonce),
|
|
2040
|
+
'--to',
|
|
2041
|
+
to,
|
|
2042
|
+
'--value-wei',
|
|
2043
|
+
valueWei.toString(),
|
|
2044
|
+
'--data-hex',
|
|
2045
|
+
dataHex,
|
|
2046
|
+
'--gas-limit',
|
|
2047
|
+
gasLimit.toString(),
|
|
2048
|
+
'--max-fee-per-gas-wei',
|
|
2049
|
+
maxFeePerGasWei.toString(),
|
|
2050
|
+
'--max-priority-fee-per-gas-wei',
|
|
2051
|
+
maxPriorityFeePerGasWei.toString(),
|
|
2052
|
+
'--tx-type',
|
|
2053
|
+
options.txType,
|
|
2054
|
+
...(options.delegationEnabled ? ['--delegation-enabled'] : []),
|
|
2055
|
+
],
|
|
2056
|
+
auth: options,
|
|
2057
|
+
config,
|
|
2058
|
+
asJson: options.json,
|
|
2059
|
+
});
|
|
2060
|
+
if (!signed) {
|
|
2061
|
+
return;
|
|
2062
|
+
}
|
|
2063
|
+
|
|
2064
|
+
if (!signed.raw_tx_hex) {
|
|
2065
|
+
throw new Error('Rust agent did not return raw_tx_hex for broadcast signing');
|
|
2066
|
+
}
|
|
2067
|
+
|
|
2068
|
+
const inspected = await assertSignedBroadcastTransactionMatchesRequest({
|
|
2069
|
+
rawTxHex: signed.raw_tx_hex as Hex,
|
|
2070
|
+
from,
|
|
2071
|
+
to,
|
|
2072
|
+
chainId,
|
|
2073
|
+
nonce,
|
|
2074
|
+
allowHigherNonce: true,
|
|
2075
|
+
value: valueWei,
|
|
2076
|
+
data: dataHex,
|
|
2077
|
+
gasLimit,
|
|
2078
|
+
maxFeePerGas: maxFeePerGasWei,
|
|
2079
|
+
maxPriorityFeePerGas: maxPriorityFeePerGasWei,
|
|
2080
|
+
txType: options.txType,
|
|
2081
|
+
});
|
|
2082
|
+
|
|
2083
|
+
const networkTxHash = await broadcastRawTransaction(rpcUrl, signed.raw_tx_hex as Hex);
|
|
2084
|
+
print(
|
|
2085
|
+
{
|
|
2086
|
+
command: 'broadcast',
|
|
2087
|
+
rpcUrl,
|
|
2088
|
+
chainId,
|
|
2089
|
+
nonce: inspected.nonce,
|
|
2090
|
+
gasLimit: gasLimit.toString(),
|
|
2091
|
+
maxFeePerGasWei: maxFeePerGasWei.toString(),
|
|
2092
|
+
maxPriorityFeePerGasWei: maxPriorityFeePerGasWei.toString(),
|
|
2093
|
+
signedTxHash: signed.tx_hash_hex,
|
|
2094
|
+
networkTxHash,
|
|
2095
|
+
rawTxHex: options.revealRawTx ? signed.raw_tx_hex : '<redacted>',
|
|
2096
|
+
signer: options.revealSignature
|
|
2097
|
+
? {
|
|
2098
|
+
r: signed.r_hex,
|
|
2099
|
+
s: signed.s_hex,
|
|
2100
|
+
v: signed.v,
|
|
2101
|
+
}
|
|
2102
|
+
: '<redacted>',
|
|
2103
|
+
},
|
|
2104
|
+
options.json,
|
|
2105
|
+
);
|
|
2106
|
+
if (options.wait) {
|
|
2107
|
+
await reportOnchainReceiptStatus({
|
|
2108
|
+
rpcUrl,
|
|
2109
|
+
txHash: networkTxHash,
|
|
2110
|
+
asJson: options.json,
|
|
2111
|
+
});
|
|
2112
|
+
}
|
|
2113
|
+
});
|
|
2114
|
+
|
|
2115
|
+
await program.parseAsync(process.argv);
|
|
2116
|
+
}
|
|
2117
|
+
|
|
2118
|
+
main().catch((error) => {
|
|
2119
|
+
console.error(error instanceof Error ? error.message : String(error));
|
|
2120
|
+
process.exitCode = 1;
|
|
2121
|
+
});
|