@wlfi-agent/cli 1.4.13 → 1.4.15

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (289) hide show
  1. package/Cargo.lock +3968 -0
  2. package/Cargo.toml +50 -0
  3. package/README.md +426 -6
  4. package/crates/vault-cli-admin/Cargo.toml +26 -0
  5. package/crates/vault-cli-admin/src/io_utils.rs +500 -0
  6. package/crates/vault-cli-admin/src/main.rs +3990 -0
  7. package/crates/vault-cli-admin/src/shared_config.rs +624 -0
  8. package/crates/vault-cli-admin/src/tui/amounts.rs +180 -0
  9. package/crates/vault-cli-admin/src/tui/token_rpc.rs +250 -0
  10. package/crates/vault-cli-admin/src/tui/utils.rs +82 -0
  11. package/crates/vault-cli-admin/src/tui.rs +3410 -0
  12. package/crates/vault-cli-agent/Cargo.toml +24 -0
  13. package/crates/vault-cli-agent/src/io_utils.rs +576 -0
  14. package/crates/vault-cli-agent/src/main.rs +833 -0
  15. package/crates/vault-cli-daemon/Cargo.toml +28 -0
  16. package/crates/vault-cli-daemon/src/bin/wlfi-agent-system-keychain.rs +216 -0
  17. package/crates/vault-cli-daemon/src/main.rs +644 -0
  18. package/crates/vault-cli-daemon/src/relay_sync.rs +894 -0
  19. package/crates/vault-cli-daemon/tests/system_keychain_helper_acl.rs +167 -0
  20. package/crates/vault-daemon/Cargo.toml +32 -0
  21. package/crates/vault-daemon/src/daemon_parts/api_impl_and_utils.rs +1041 -0
  22. package/crates/vault-daemon/src/daemon_parts/core_helpers.rs +1256 -0
  23. package/crates/vault-daemon/src/daemon_parts/types_api_rpc.rs +622 -0
  24. package/crates/vault-daemon/src/lib.rs +54 -0
  25. package/crates/vault-daemon/src/persistence.rs +441 -0
  26. package/crates/vault-daemon/src/tests.rs +237 -0
  27. package/crates/vault-daemon/src/tests_parts/part1.rs +1224 -0
  28. package/crates/vault-daemon/src/tests_parts/part2.rs +1021 -0
  29. package/crates/vault-daemon/src/tests_parts/part3.rs +835 -0
  30. package/crates/vault-daemon/src/tests_parts/part4.rs +604 -0
  31. package/crates/vault-domain/Cargo.toml +20 -0
  32. package/crates/vault-domain/src/action.rs +849 -0
  33. package/crates/vault-domain/src/address.rs +51 -0
  34. package/crates/vault-domain/src/approval.rs +90 -0
  35. package/crates/vault-domain/src/constants.rs +4 -0
  36. package/crates/vault-domain/src/error.rs +54 -0
  37. package/crates/vault-domain/src/keys.rs +71 -0
  38. package/crates/vault-domain/src/lib.rs +42 -0
  39. package/crates/vault-domain/src/nonce.rs +102 -0
  40. package/crates/vault-domain/src/policy.rs +172 -0
  41. package/crates/vault-domain/src/request.rs +53 -0
  42. package/crates/vault-domain/src/scope.rs +24 -0
  43. package/crates/vault-domain/src/session.rs +50 -0
  44. package/crates/vault-domain/src/signature.rs +34 -0
  45. package/crates/vault-domain/src/tests.rs +651 -0
  46. package/crates/vault-domain/src/u128_as_decimal_string.rs +44 -0
  47. package/crates/vault-policy/Cargo.toml +17 -0
  48. package/crates/vault-policy/src/engine.rs +301 -0
  49. package/crates/vault-policy/src/error.rs +81 -0
  50. package/crates/vault-policy/src/lib.rs +17 -0
  51. package/crates/vault-policy/src/report.rs +34 -0
  52. package/crates/vault-policy/src/tests.rs +891 -0
  53. package/crates/vault-policy/src/tests_explain.rs +78 -0
  54. package/crates/vault-sdk-agent/Cargo.toml +21 -0
  55. package/crates/vault-sdk-agent/src/lib.rs +711 -0
  56. package/crates/vault-signer/Cargo.toml +25 -0
  57. package/crates/vault-signer/src/lib.rs +731 -0
  58. package/crates/vault-signer/tests/secure_enclave_acl.rs +54 -0
  59. package/crates/vault-transport-unix/Cargo.toml +24 -0
  60. package/crates/vault-transport-unix/src/lib.rs +1640 -0
  61. package/crates/vault-transport-xpc/Cargo.toml +25 -0
  62. package/crates/vault-transport-xpc/src/client_codec_api.rs +635 -0
  63. package/crates/vault-transport-xpc/src/lib.rs +680 -0
  64. package/crates/vault-transport-xpc/src/tests.rs +818 -0
  65. package/crates/vault-transport-xpc/tests/e2e_flow.rs +773 -0
  66. package/dist/cli.cjs +35088 -0
  67. package/dist/cli.cjs.map +1 -0
  68. package/package.json +45 -41
  69. package/packages/cache/.turbo/turbo-build.log +52 -0
  70. package/packages/cache/dist/chunk-2QFWMUXT.cjs +43 -0
  71. package/packages/cache/dist/chunk-2QFWMUXT.cjs.map +1 -0
  72. package/packages/cache/dist/chunk-4U63TZTQ.js +43 -0
  73. package/packages/cache/dist/chunk-4U63TZTQ.js.map +1 -0
  74. package/packages/cache/dist/chunk-ALQ6H7KG.cjs +404 -0
  75. package/packages/cache/dist/chunk-ALQ6H7KG.cjs.map +1 -0
  76. package/packages/cache/dist/chunk-FGJEEF5N.js +404 -0
  77. package/packages/cache/dist/chunk-FGJEEF5N.js.map +1 -0
  78. package/packages/cache/dist/chunk-UYNEHZHB.cjs +45 -0
  79. package/packages/cache/dist/chunk-UYNEHZHB.cjs.map +1 -0
  80. package/packages/cache/dist/chunk-VXVMPG3W.js +45 -0
  81. package/packages/cache/dist/chunk-VXVMPG3W.js.map +1 -0
  82. package/packages/cache/dist/client/index.cjs +11 -0
  83. package/packages/cache/dist/client/index.cjs.map +1 -0
  84. package/packages/cache/dist/client/index.d.cts +15 -0
  85. package/packages/cache/dist/client/index.d.ts +15 -0
  86. package/packages/cache/dist/client/index.js +11 -0
  87. package/packages/cache/dist/client/index.js.map +1 -0
  88. package/packages/cache/dist/errors/index.cjs +11 -0
  89. package/packages/cache/dist/errors/index.cjs.map +1 -0
  90. package/packages/cache/dist/errors/index.d.cts +26 -0
  91. package/packages/cache/dist/errors/index.d.ts +26 -0
  92. package/packages/cache/dist/errors/index.js +11 -0
  93. package/packages/cache/dist/errors/index.js.map +1 -0
  94. package/packages/cache/dist/index.cjs +29 -0
  95. package/packages/cache/dist/index.cjs.map +1 -0
  96. package/packages/cache/dist/index.d.cts +4 -0
  97. package/packages/cache/dist/index.d.ts +4 -0
  98. package/packages/cache/dist/index.js +29 -0
  99. package/packages/cache/dist/index.js.map +1 -0
  100. package/packages/cache/dist/service/index.cjs +15 -0
  101. package/packages/cache/dist/service/index.cjs.map +1 -0
  102. package/packages/cache/dist/service/index.d.cts +184 -0
  103. package/packages/cache/dist/service/index.d.ts +184 -0
  104. package/packages/cache/dist/service/index.js +15 -0
  105. package/packages/cache/dist/service/index.js.map +1 -0
  106. package/packages/cache/node_modules/.bin/jiti +17 -0
  107. package/packages/cache/node_modules/.bin/tsc +17 -0
  108. package/packages/cache/node_modules/.bin/tsserver +17 -0
  109. package/packages/cache/node_modules/.bin/tsup +17 -0
  110. package/packages/cache/node_modules/.bin/tsup-node +17 -0
  111. package/packages/cache/node_modules/.bin/tsx +17 -0
  112. package/packages/cache/node_modules/.bin/vitest +17 -0
  113. package/packages/cache/package.json +48 -0
  114. package/packages/cache/src/client/index.ts +56 -0
  115. package/packages/cache/src/errors/index.ts +53 -0
  116. package/packages/cache/src/index.ts +3 -0
  117. package/packages/cache/src/service/index.test.ts +263 -0
  118. package/packages/cache/src/service/index.ts +678 -0
  119. package/packages/cache/tsconfig.json +13 -0
  120. package/packages/cache/tsup.config.ts +13 -0
  121. package/packages/cache/vitest.config.ts +16 -0
  122. package/packages/config/.turbo/turbo-build.log +18 -0
  123. package/packages/config/dist/index.cjs +1037 -0
  124. package/packages/config/dist/index.cjs.map +1 -0
  125. package/packages/config/dist/index.d.ts +131 -0
  126. package/packages/config/node_modules/.bin/jiti +17 -0
  127. package/packages/config/node_modules/.bin/tsc +17 -0
  128. package/packages/config/node_modules/.bin/tsserver +17 -0
  129. package/packages/config/node_modules/.bin/tsup +17 -0
  130. package/packages/config/node_modules/.bin/tsup-node +17 -0
  131. package/packages/config/node_modules/.bin/tsx +17 -0
  132. package/packages/config/package.json +21 -0
  133. package/packages/config/src/index.js +1 -0
  134. package/packages/config/src/index.ts +1282 -0
  135. package/packages/config/tsconfig.json +4 -0
  136. package/packages/rpc/.turbo/turbo-build.log +32 -0
  137. package/packages/rpc/dist/_esm-BCLXDO2R.cjs +3660 -0
  138. package/packages/rpc/dist/_esm-BCLXDO2R.cjs.map +1 -0
  139. package/packages/rpc/dist/ccip-OWJLAW55.cjs +16 -0
  140. package/packages/rpc/dist/ccip-OWJLAW55.cjs.map +1 -0
  141. package/packages/rpc/dist/chunk-APQIFZ3B.cjs +6247 -0
  142. package/packages/rpc/dist/chunk-APQIFZ3B.cjs.map +1 -0
  143. package/packages/rpc/dist/chunk-CDO2GWRD.cjs +410 -0
  144. package/packages/rpc/dist/chunk-CDO2GWRD.cjs.map +1 -0
  145. package/packages/rpc/dist/chunk-QGTNTFJ7.cjs +2249 -0
  146. package/packages/rpc/dist/chunk-QGTNTFJ7.cjs.map +1 -0
  147. package/packages/rpc/dist/chunk-TZDTAHWR.cjs +44 -0
  148. package/packages/rpc/dist/chunk-TZDTAHWR.cjs.map +1 -0
  149. package/packages/rpc/dist/index.cjs +7342 -0
  150. package/packages/rpc/dist/index.cjs.map +1 -0
  151. package/packages/rpc/dist/index.d.ts +3857 -0
  152. package/packages/rpc/dist/secp256k1-WCNM675D.cjs +18 -0
  153. package/packages/rpc/dist/secp256k1-WCNM675D.cjs.map +1 -0
  154. package/packages/rpc/node_modules/.bin/jiti +17 -0
  155. package/packages/rpc/node_modules/.bin/tsc +17 -0
  156. package/packages/rpc/node_modules/.bin/tsserver +17 -0
  157. package/packages/rpc/node_modules/.bin/tsup +17 -0
  158. package/packages/rpc/node_modules/.bin/tsup-node +17 -0
  159. package/packages/rpc/node_modules/.bin/tsx +17 -0
  160. package/packages/rpc/package.json +25 -0
  161. package/packages/rpc/src/index.ts +206 -0
  162. package/packages/rpc/tsconfig.json +4 -0
  163. package/packages/typescript/base.json +36 -0
  164. package/packages/typescript/nextjs.json +17 -0
  165. package/packages/typescript/package.json +10 -0
  166. package/packages/ui/.turbo/turbo-build.log +44 -0
  167. package/packages/ui/dist/chunk-MOAFBKSA.js +11 -0
  168. package/packages/ui/dist/chunk-MOAFBKSA.js.map +1 -0
  169. package/packages/ui/dist/components/badge.d.ts +12 -0
  170. package/packages/ui/dist/components/badge.js +31 -0
  171. package/packages/ui/dist/components/badge.js.map +1 -0
  172. package/packages/ui/dist/components/button.d.ts +13 -0
  173. package/packages/ui/dist/components/button.js +40 -0
  174. package/packages/ui/dist/components/button.js.map +1 -0
  175. package/packages/ui/dist/components/card.d.ts +10 -0
  176. package/packages/ui/dist/components/card.js +39 -0
  177. package/packages/ui/dist/components/card.js.map +1 -0
  178. package/packages/ui/dist/components/input.d.ts +5 -0
  179. package/packages/ui/dist/components/input.js +28 -0
  180. package/packages/ui/dist/components/input.js.map +1 -0
  181. package/packages/ui/dist/components/label.d.ts +5 -0
  182. package/packages/ui/dist/components/label.js +13 -0
  183. package/packages/ui/dist/components/label.js.map +1 -0
  184. package/packages/ui/dist/components/separator.d.ts +5 -0
  185. package/packages/ui/dist/components/separator.js +13 -0
  186. package/packages/ui/dist/components/separator.js.map +1 -0
  187. package/packages/ui/dist/components/textarea.d.ts +5 -0
  188. package/packages/ui/dist/components/textarea.js +27 -0
  189. package/packages/ui/dist/components/textarea.js.map +1 -0
  190. package/packages/ui/dist/tailwind.d.ts +56 -0
  191. package/packages/ui/dist/tailwind.js +60 -0
  192. package/packages/ui/dist/tailwind.js.map +1 -0
  193. package/packages/ui/dist/utils/cn.d.ts +5 -0
  194. package/packages/ui/dist/utils/cn.js +7 -0
  195. package/packages/ui/dist/utils/cn.js.map +1 -0
  196. package/packages/ui/node_modules/.bin/jiti +17 -0
  197. package/packages/ui/node_modules/.bin/tsc +17 -0
  198. package/packages/ui/node_modules/.bin/tsserver +17 -0
  199. package/packages/ui/node_modules/.bin/tsup +17 -0
  200. package/packages/ui/node_modules/.bin/tsup-node +17 -0
  201. package/packages/ui/node_modules/.bin/tsx +17 -0
  202. package/packages/ui/package.json +69 -0
  203. package/packages/ui/src/components/badge.tsx +27 -0
  204. package/packages/ui/src/components/button.tsx +40 -0
  205. package/packages/ui/src/components/card.tsx +31 -0
  206. package/packages/ui/src/components/input.tsx +21 -0
  207. package/packages/ui/src/components/label.tsx +6 -0
  208. package/packages/ui/src/components/separator.tsx +6 -0
  209. package/packages/ui/src/components/textarea.tsx +20 -0
  210. package/packages/ui/src/globals.css +70 -0
  211. package/packages/ui/src/tailwind.ts +56 -0
  212. package/packages/ui/src/utils/cn.ts +6 -0
  213. package/packages/ui/tsconfig.json +20 -0
  214. package/packages/ui/tsup.config.ts +20 -0
  215. package/pnpm-workspace.yaml +4 -0
  216. package/scripts/install-rust-binaries.mjs +84 -0
  217. package/scripts/launchd/install-user-daemon.sh +358 -0
  218. package/scripts/launchd/run-vault-daemon.sh +5 -0
  219. package/scripts/launchd/run-wlfi-agent-daemon.sh +73 -0
  220. package/scripts/launchd/uninstall-user-daemon.sh +103 -0
  221. package/src/cli.ts +2121 -0
  222. package/src/lib/admin-guard.js +1 -0
  223. package/src/lib/admin-guard.ts +185 -0
  224. package/src/lib/admin-passthrough.ts +33 -0
  225. package/src/lib/admin-reset.ts +751 -0
  226. package/src/lib/admin-setup.ts +1612 -0
  227. package/src/lib/agent-auth-clear.js +1 -0
  228. package/src/lib/agent-auth-clear.ts +58 -0
  229. package/src/lib/agent-auth-forwarding.js +1 -0
  230. package/src/lib/agent-auth-forwarding.ts +149 -0
  231. package/src/lib/agent-auth-migrate.js +1 -0
  232. package/src/lib/agent-auth-migrate.ts +150 -0
  233. package/src/lib/agent-auth-revoke.ts +103 -0
  234. package/src/lib/agent-auth-rotate.ts +107 -0
  235. package/src/lib/agent-auth-token.js +1 -0
  236. package/src/lib/agent-auth-token.ts +25 -0
  237. package/src/lib/agent-auth.ts +89 -0
  238. package/src/lib/asset-broadcast.js +1 -0
  239. package/src/lib/asset-broadcast.ts +285 -0
  240. package/src/lib/bootstrap-artifacts.js +1 -0
  241. package/src/lib/bootstrap-artifacts.ts +205 -0
  242. package/src/lib/bootstrap-credentials.js +1 -0
  243. package/src/lib/bootstrap-credentials.ts +832 -0
  244. package/src/lib/config-amounts.js +1 -0
  245. package/src/lib/config-amounts.ts +189 -0
  246. package/src/lib/config-mutation.ts +27 -0
  247. package/src/lib/fs-trust.js +1 -0
  248. package/src/lib/fs-trust.ts +537 -0
  249. package/src/lib/keychain.js +1 -0
  250. package/src/lib/keychain.ts +225 -0
  251. package/src/lib/local-admin-access.ts +106 -0
  252. package/src/lib/network-selection.js +1 -0
  253. package/src/lib/network-selection.ts +71 -0
  254. package/src/lib/passthrough-security.js +1 -0
  255. package/src/lib/passthrough-security.ts +114 -0
  256. package/src/lib/rpc-guard.js +1 -0
  257. package/src/lib/rpc-guard.ts +7 -0
  258. package/src/lib/rust-spawn-options.js +1 -0
  259. package/src/lib/rust-spawn-options.ts +98 -0
  260. package/src/lib/rust.js +1 -0
  261. package/src/lib/rust.ts +143 -0
  262. package/src/lib/signed-tx.js +1 -0
  263. package/src/lib/signed-tx.ts +116 -0
  264. package/src/lib/status-repair-cli.ts +116 -0
  265. package/src/lib/sudo.js +1 -0
  266. package/src/lib/sudo.ts +172 -0
  267. package/src/lib/vault-password-forwarding.js +1 -0
  268. package/src/lib/vault-password-forwarding.ts +155 -0
  269. package/src/lib/wallet-profile.js +1 -0
  270. package/src/lib/wallet-profile.ts +332 -0
  271. package/src/lib/wallet-repair.js +1 -0
  272. package/src/lib/wallet-repair.ts +304 -0
  273. package/src/lib/wallet-setup.js +1 -0
  274. package/src/lib/wallet-setup.ts +1466 -0
  275. package/src/lib/wallet-status.js +1 -0
  276. package/src/lib/wallet-status.ts +640 -0
  277. package/tsconfig.base.json +17 -0
  278. package/tsconfig.json +10 -0
  279. package/tsup.config.ts +25 -0
  280. package/turbo.json +41 -0
  281. package/LICENSE.md +0 -1
  282. package/dist/wlfa/index.cjs +0 -250
  283. package/dist/wlfa/index.d.cts +0 -1
  284. package/dist/wlfa/index.d.ts +0 -1
  285. package/dist/wlfa/index.js +0 -250
  286. package/dist/wlfc/index.cjs +0 -1839
  287. package/dist/wlfc/index.d.cts +0 -1
  288. package/dist/wlfc/index.d.ts +0 -1
  289. package/dist/wlfc/index.js +0 -1839
@@ -0,0 +1,832 @@
1
+ import fs from 'node:fs';
2
+ import path from 'node:path';
3
+ import { assertValidAgentAuthToken } from './agent-auth-token.js';
4
+ import { assertPrivateFileStats, assertTrustedDirectoryPath, readUtf8FileSecure } from './fs-trust.js';
5
+ import { assertValidAgentKeyId } from './keychain.js';
6
+
7
+ const PRIVATE_FILE_MODE = 0o600;
8
+ const REDACTED_SECRET_PLACEHOLDER = '<redacted>';
9
+ const MAX_BOOTSTRAP_FILE_BYTES = 256 * 1024;
10
+
11
+ export interface BootstrapAgentCredentials {
12
+ agentKeyId: string;
13
+ agentAuthToken: string;
14
+ sourcePath: string;
15
+ }
16
+
17
+ export interface BootstrapCredentialsCleanupResult {
18
+ sourcePath: string;
19
+ }
20
+
21
+ export interface BootstrapCredentialsBestEffortCleanupResult extends BootstrapCredentialsCleanupResult {
22
+ action: 'deleted' | 'redacted' | 'missing';
23
+ }
24
+
25
+ export interface BootstrapSetupFileContents {
26
+ summary: BootstrapSetupSummary;
27
+ credentials: BootstrapAgentCredentials;
28
+ }
29
+
30
+ export interface BootstrapDestinationOverrideSummary {
31
+ recipient: string;
32
+ perTxPolicyId: string;
33
+ dailyPolicyId: string;
34
+ weeklyPolicyId: string;
35
+ gasPolicyId: string | null;
36
+ perTxMaxWei: string;
37
+ dailyMaxWei: string;
38
+ weeklyMaxWei: string;
39
+ maxGasPerChainWei: string | null;
40
+ dailyMaxTxCount: string | null;
41
+ dailyTxCountPolicyId: string | null;
42
+ perTxMaxFeePerGasWei: string | null;
43
+ perTxMaxFeePerGasPolicyId: string | null;
44
+ perTxMaxPriorityFeePerGasWei: string | null;
45
+ perTxMaxPriorityFeePerGasPolicyId: string | null;
46
+ perTxMaxCalldataBytes: string | null;
47
+ perTxMaxCalldataBytesPolicyId: string | null;
48
+ }
49
+
50
+ export interface BootstrapTokenPolicySummary {
51
+ tokenKey: string;
52
+ symbol: string;
53
+ chainKey: string;
54
+ chainId: number;
55
+ assetScope: string;
56
+ recipientScope: string;
57
+ perTxPolicyId: string;
58
+ dailyPolicyId: string;
59
+ weeklyPolicyId: string;
60
+ gasPolicyId: string | null;
61
+ perTxMaxWei: string;
62
+ dailyMaxWei: string;
63
+ weeklyMaxWei: string;
64
+ maxGasPerChainWei: string | null;
65
+ dailyMaxTxCount: string | null;
66
+ dailyTxCountPolicyId: string | null;
67
+ perTxMaxFeePerGasWei: string | null;
68
+ perTxMaxFeePerGasPolicyId: string | null;
69
+ perTxMaxPriorityFeePerGasWei: string | null;
70
+ perTxMaxPriorityFeePerGasPolicyId: string | null;
71
+ perTxMaxCalldataBytes: string | null;
72
+ perTxMaxCalldataBytesPolicyId: string | null;
73
+ }
74
+
75
+ export interface BootstrapTokenDestinationOverrideSummary {
76
+ tokenKey: string;
77
+ symbol: string;
78
+ chainKey: string;
79
+ chainId: number;
80
+ recipient: string;
81
+ assetScope: string;
82
+ perTxPolicyId: string;
83
+ dailyPolicyId: string;
84
+ weeklyPolicyId: string;
85
+ gasPolicyId: string | null;
86
+ perTxMaxWei: string;
87
+ dailyMaxWei: string;
88
+ weeklyMaxWei: string;
89
+ maxGasPerChainWei: string | null;
90
+ dailyMaxTxCount: string | null;
91
+ dailyTxCountPolicyId: string | null;
92
+ perTxMaxFeePerGasWei: string | null;
93
+ perTxMaxFeePerGasPolicyId: string | null;
94
+ perTxMaxPriorityFeePerGasWei: string | null;
95
+ perTxMaxPriorityFeePerGasPolicyId: string | null;
96
+ perTxMaxCalldataBytes: string | null;
97
+ perTxMaxCalldataBytesPolicyId: string | null;
98
+ }
99
+
100
+ export interface BootstrapTokenManualApprovalPolicySummary {
101
+ tokenKey: string;
102
+ symbol: string;
103
+ chainKey: string;
104
+ chainId: number;
105
+ priority: number;
106
+ minAmountWei: string;
107
+ maxAmountWei: string;
108
+ assetScope: string;
109
+ recipientScope: string;
110
+ policyId: string;
111
+ }
112
+
113
+ export interface BootstrapSetupSummary {
114
+ sourcePath: string;
115
+ leaseId: string;
116
+ leaseExpiresAt: string;
117
+ perTxPolicyId: string | null;
118
+ dailyPolicyId: string | null;
119
+ weeklyPolicyId: string | null;
120
+ gasPolicyId: string | null;
121
+ perTxMaxWei: string | null;
122
+ dailyMaxWei: string | null;
123
+ weeklyMaxWei: string | null;
124
+ maxGasPerChainWei: string | null;
125
+ dailyMaxTxCount: string | null;
126
+ dailyTxCountPolicyId: string | null;
127
+ perTxMaxFeePerGasWei: string | null;
128
+ perTxMaxFeePerGasPolicyId: string | null;
129
+ perTxMaxPriorityFeePerGasWei: string | null;
130
+ perTxMaxPriorityFeePerGasPolicyId: string | null;
131
+ perTxMaxCalldataBytes: string | null;
132
+ perTxMaxCalldataBytesPolicyId: string | null;
133
+ vaultKeyId: string;
134
+ vaultPublicKey: string;
135
+ vaultPrivateKey: string | null;
136
+ agentKeyId: string;
137
+ networkScope: string | null;
138
+ assetScope: string | null;
139
+ recipientScope: string | null;
140
+ destinationOverrideCount: number;
141
+ destinationOverrides: BootstrapDestinationOverrideSummary[];
142
+ tokenPolicies: BootstrapTokenPolicySummary[];
143
+ tokenDestinationOverrides: BootstrapTokenDestinationOverrideSummary[];
144
+ tokenManualApprovalPolicies: BootstrapTokenManualApprovalPolicySummary[];
145
+ policyAttachment: string;
146
+ attachedPolicyIds: string[];
147
+ policyNote: string;
148
+ }
149
+
150
+ export interface BootstrapLeaseValidationDeps {
151
+ now?: () => number;
152
+ }
153
+
154
+ function resolveInputPath(inputPath: string): string {
155
+ const normalized = inputPath.trim();
156
+ if (!normalized) {
157
+ throw new Error('bootstrap credentials file path is required');
158
+ }
159
+ return path.resolve(normalized);
160
+ }
161
+
162
+ function assertJsonRecord(value: unknown, label: string): Record<string, unknown> {
163
+ if (value === null || typeof value !== 'object' || Array.isArray(value)) {
164
+ throw new Error(`${label} must contain a JSON object`);
165
+ }
166
+ return value as Record<string, unknown>;
167
+ }
168
+
169
+ function readRequiredString(
170
+ payload: Record<string, unknown>,
171
+ fieldNames: string[],
172
+ label: string,
173
+ options: { trim?: boolean } = {}
174
+ ): string {
175
+ for (const fieldName of fieldNames) {
176
+ const value = payload[fieldName];
177
+ if (typeof value !== 'string') {
178
+ continue;
179
+ }
180
+
181
+ const normalized = options.trim === false ? value : value.trim();
182
+ if (normalized.length > 0) {
183
+ return normalized;
184
+ }
185
+ break;
186
+ }
187
+
188
+ throw new Error(`${label} is required in bootstrap credentials file`);
189
+ }
190
+
191
+ function readOptionalBoolean(payload: Record<string, unknown>, fieldNames: string[]): boolean | null {
192
+ for (const fieldName of fieldNames) {
193
+ const value = payload[fieldName];
194
+ if (value === undefined) {
195
+ continue;
196
+ }
197
+ if (typeof value !== 'boolean') {
198
+ throw new Error(`${fieldName} must be a boolean in bootstrap credentials file`);
199
+ }
200
+ return value;
201
+ }
202
+ return null;
203
+ }
204
+
205
+ function readOptionalString(payload: Record<string, unknown>, fieldNames: string[]): string | null {
206
+ for (const fieldName of fieldNames) {
207
+ const value = payload[fieldName];
208
+ if (value === undefined || value === null) {
209
+ continue;
210
+ }
211
+ if (typeof value !== 'string') {
212
+ throw new Error(`${fieldName} must be a string in bootstrap credentials file`);
213
+ }
214
+
215
+ const normalized = value.trim();
216
+ return normalized.length > 0 ? normalized : null;
217
+ }
218
+
219
+ return null;
220
+ }
221
+
222
+ function readOptionalStringArray(payload: Record<string, unknown>, fieldNames: string[]): string[] {
223
+ for (const fieldName of fieldNames) {
224
+ const value = payload[fieldName];
225
+ if (value === undefined || value === null) {
226
+ continue;
227
+ }
228
+ if (!Array.isArray(value) || value.some((entry) => typeof entry !== 'string')) {
229
+ throw new Error(`${fieldName} must be an array of strings in bootstrap credentials file`);
230
+ }
231
+
232
+ return value
233
+ .map((entry) => entry.trim())
234
+ .filter((entry) => entry.length > 0);
235
+ }
236
+
237
+ return [];
238
+ }
239
+
240
+
241
+ function readOptionalNumber(payload: Record<string, unknown>, fieldNames: string[]): number | null {
242
+ for (const fieldName of fieldNames) {
243
+ const value = payload[fieldName];
244
+ if (value === undefined || value === null) {
245
+ continue;
246
+ }
247
+ if (typeof value !== 'number' || !Number.isFinite(value)) {
248
+ throw new Error(`${fieldName} must be a number in bootstrap credentials file`);
249
+ }
250
+ return value;
251
+ }
252
+ return null;
253
+ }
254
+
255
+ function readRequiredNumber(
256
+ payload: Record<string, unknown>,
257
+ fieldNames: string[],
258
+ label: string
259
+ ): number {
260
+ const value = readOptionalNumber(payload, fieldNames);
261
+ if (value === null) {
262
+ throw new Error(`${label} is required in bootstrap credentials file`);
263
+ }
264
+ return value;
265
+ }
266
+
267
+ function readOptionalRecordArray(
268
+ payload: Record<string, unknown>,
269
+ fieldNames: string[]
270
+ ): Record<string, unknown>[] {
271
+ for (const fieldName of fieldNames) {
272
+ const value = payload[fieldName];
273
+ if (value === undefined || value === null) {
274
+ continue;
275
+ }
276
+ if (!Array.isArray(value)) {
277
+ throw new Error(`${fieldName} must be an array in bootstrap credentials file`);
278
+ }
279
+ return value.map((entry, index) => assertJsonRecord(entry, `${fieldName}[${index}]`));
280
+ }
281
+ return [];
282
+ }
283
+
284
+ function readDestinationOverrideSummary(
285
+ payload: Record<string, unknown>,
286
+ label: string
287
+ ): BootstrapDestinationOverrideSummary {
288
+ return {
289
+ recipient: readRequiredString(payload, ['recipient'], `${label}.recipient`),
290
+ perTxPolicyId: readRequiredString(payload, ['per_tx_policy_id', 'perTxPolicyId'], `${label}.per_tx_policy_id`),
291
+ dailyPolicyId: readRequiredString(payload, ['daily_policy_id', 'dailyPolicyId'], `${label}.daily_policy_id`),
292
+ weeklyPolicyId: readRequiredString(payload, ['weekly_policy_id', 'weeklyPolicyId'], `${label}.weekly_policy_id`),
293
+ gasPolicyId: readOptionalString(payload, ['gas_policy_id', 'gasPolicyId']),
294
+ perTxMaxWei: readRequiredString(payload, ['per_tx_max_wei', 'perTxMaxWei'], `${label}.per_tx_max_wei`),
295
+ dailyMaxWei: readRequiredString(payload, ['daily_max_wei', 'dailyMaxWei'], `${label}.daily_max_wei`),
296
+ weeklyMaxWei: readRequiredString(payload, ['weekly_max_wei', 'weeklyMaxWei'], `${label}.weekly_max_wei`),
297
+ maxGasPerChainWei: readOptionalString(payload, ['max_gas_per_chain_wei', 'maxGasPerChainWei']),
298
+ dailyMaxTxCount: readOptionalString(payload, ['daily_max_tx_count', 'dailyMaxTxCount']),
299
+ dailyTxCountPolicyId: readOptionalString(
300
+ payload,
301
+ ['daily_tx_count_policy_id', 'dailyTxCountPolicyId']
302
+ ),
303
+ perTxMaxFeePerGasWei: readOptionalString(
304
+ payload,
305
+ ['per_tx_max_fee_per_gas_wei', 'perTxMaxFeePerGasWei']
306
+ ),
307
+ perTxMaxFeePerGasPolicyId: readOptionalString(
308
+ payload,
309
+ ['per_tx_max_fee_per_gas_policy_id', 'perTxMaxFeePerGasPolicyId']
310
+ ),
311
+ perTxMaxPriorityFeePerGasWei: readOptionalString(
312
+ payload,
313
+ ['per_tx_max_priority_fee_per_gas_wei', 'perTxMaxPriorityFeePerGasWei']
314
+ ),
315
+ perTxMaxPriorityFeePerGasPolicyId: readOptionalString(
316
+ payload,
317
+ [
318
+ 'per_tx_max_priority_fee_per_gas_policy_id',
319
+ 'perTxMaxPriorityFeePerGasPolicyId'
320
+ ]
321
+ ),
322
+ perTxMaxCalldataBytes: readOptionalString(
323
+ payload,
324
+ ['per_tx_max_calldata_bytes', 'perTxMaxCalldataBytes']
325
+ ),
326
+ perTxMaxCalldataBytesPolicyId: readOptionalString(
327
+ payload,
328
+ ['per_tx_max_calldata_bytes_policy_id', 'perTxMaxCalldataBytesPolicyId']
329
+ )
330
+ };
331
+ }
332
+
333
+ function readTokenPolicySummary(
334
+ payload: Record<string, unknown>,
335
+ label: string
336
+ ): BootstrapTokenPolicySummary {
337
+ return {
338
+ tokenKey: readRequiredString(payload, ['token_key', 'tokenKey'], `${label}.token_key`),
339
+ symbol: readRequiredString(payload, ['symbol'], `${label}.symbol`),
340
+ chainKey: readRequiredString(payload, ['chain_key', 'chainKey'], `${label}.chain_key`),
341
+ chainId: readRequiredNumber(payload, ['chain_id', 'chainId'], `${label}.chain_id`),
342
+ assetScope: readRequiredString(payload, ['asset_scope', 'assetScope'], `${label}.asset_scope`),
343
+ recipientScope: readRequiredString(
344
+ payload,
345
+ ['recipient_scope', 'recipientScope'],
346
+ `${label}.recipient_scope`
347
+ ),
348
+ perTxPolicyId: readRequiredString(payload, ['per_tx_policy_id', 'perTxPolicyId'], `${label}.per_tx_policy_id`),
349
+ dailyPolicyId: readRequiredString(payload, ['daily_policy_id', 'dailyPolicyId'], `${label}.daily_policy_id`),
350
+ weeklyPolicyId: readRequiredString(payload, ['weekly_policy_id', 'weeklyPolicyId'], `${label}.weekly_policy_id`),
351
+ gasPolicyId: readOptionalString(payload, ['gas_policy_id', 'gasPolicyId']),
352
+ perTxMaxWei: readRequiredString(payload, ['per_tx_max_wei', 'perTxMaxWei'], `${label}.per_tx_max_wei`),
353
+ dailyMaxWei: readRequiredString(payload, ['daily_max_wei', 'dailyMaxWei'], `${label}.daily_max_wei`),
354
+ weeklyMaxWei: readRequiredString(payload, ['weekly_max_wei', 'weeklyMaxWei'], `${label}.weekly_max_wei`),
355
+ maxGasPerChainWei: readOptionalString(payload, ['max_gas_per_chain_wei', 'maxGasPerChainWei']),
356
+ dailyMaxTxCount: readOptionalString(payload, ['daily_max_tx_count', 'dailyMaxTxCount']),
357
+ dailyTxCountPolicyId: readOptionalString(
358
+ payload,
359
+ ['daily_tx_count_policy_id', 'dailyTxCountPolicyId']
360
+ ),
361
+ perTxMaxFeePerGasWei: readOptionalString(
362
+ payload,
363
+ ['per_tx_max_fee_per_gas_wei', 'perTxMaxFeePerGasWei']
364
+ ),
365
+ perTxMaxFeePerGasPolicyId: readOptionalString(
366
+ payload,
367
+ ['per_tx_max_fee_per_gas_policy_id', 'perTxMaxFeePerGasPolicyId']
368
+ ),
369
+ perTxMaxPriorityFeePerGasWei: readOptionalString(
370
+ payload,
371
+ ['per_tx_max_priority_fee_per_gas_wei', 'perTxMaxPriorityFeePerGasWei']
372
+ ),
373
+ perTxMaxPriorityFeePerGasPolicyId: readOptionalString(
374
+ payload,
375
+ [
376
+ 'per_tx_max_priority_fee_per_gas_policy_id',
377
+ 'perTxMaxPriorityFeePerGasPolicyId'
378
+ ]
379
+ ),
380
+ perTxMaxCalldataBytes: readOptionalString(
381
+ payload,
382
+ ['per_tx_max_calldata_bytes', 'perTxMaxCalldataBytes']
383
+ ),
384
+ perTxMaxCalldataBytesPolicyId: readOptionalString(
385
+ payload,
386
+ ['per_tx_max_calldata_bytes_policy_id', 'perTxMaxCalldataBytesPolicyId']
387
+ )
388
+ };
389
+ }
390
+
391
+ function readTokenDestinationOverrideSummary(
392
+ payload: Record<string, unknown>,
393
+ label: string
394
+ ): BootstrapTokenDestinationOverrideSummary {
395
+ return {
396
+ tokenKey: readRequiredString(payload, ['token_key', 'tokenKey'], `${label}.token_key`),
397
+ symbol: readRequiredString(payload, ['symbol'], `${label}.symbol`),
398
+ chainKey: readRequiredString(payload, ['chain_key', 'chainKey'], `${label}.chain_key`),
399
+ chainId: readRequiredNumber(payload, ['chain_id', 'chainId'], `${label}.chain_id`),
400
+ recipient: readRequiredString(payload, ['recipient'], `${label}.recipient`),
401
+ assetScope: readRequiredString(payload, ['asset_scope', 'assetScope'], `${label}.asset_scope`),
402
+ perTxPolicyId: readRequiredString(payload, ['per_tx_policy_id', 'perTxPolicyId'], `${label}.per_tx_policy_id`),
403
+ dailyPolicyId: readRequiredString(payload, ['daily_policy_id', 'dailyPolicyId'], `${label}.daily_policy_id`),
404
+ weeklyPolicyId: readRequiredString(payload, ['weekly_policy_id', 'weeklyPolicyId'], `${label}.weekly_policy_id`),
405
+ gasPolicyId: readOptionalString(payload, ['gas_policy_id', 'gasPolicyId']),
406
+ perTxMaxWei: readRequiredString(payload, ['per_tx_max_wei', 'perTxMaxWei'], `${label}.per_tx_max_wei`),
407
+ dailyMaxWei: readRequiredString(payload, ['daily_max_wei', 'dailyMaxWei'], `${label}.daily_max_wei`),
408
+ weeklyMaxWei: readRequiredString(payload, ['weekly_max_wei', 'weeklyMaxWei'], `${label}.weekly_max_wei`),
409
+ maxGasPerChainWei: readOptionalString(payload, ['max_gas_per_chain_wei', 'maxGasPerChainWei']),
410
+ dailyMaxTxCount: readOptionalString(payload, ['daily_max_tx_count', 'dailyMaxTxCount']),
411
+ dailyTxCountPolicyId: readOptionalString(
412
+ payload,
413
+ ['daily_tx_count_policy_id', 'dailyTxCountPolicyId']
414
+ ),
415
+ perTxMaxFeePerGasWei: readOptionalString(
416
+ payload,
417
+ ['per_tx_max_fee_per_gas_wei', 'perTxMaxFeePerGasWei']
418
+ ),
419
+ perTxMaxFeePerGasPolicyId: readOptionalString(
420
+ payload,
421
+ ['per_tx_max_fee_per_gas_policy_id', 'perTxMaxFeePerGasPolicyId']
422
+ ),
423
+ perTxMaxPriorityFeePerGasWei: readOptionalString(
424
+ payload,
425
+ ['per_tx_max_priority_fee_per_gas_wei', 'perTxMaxPriorityFeePerGasWei']
426
+ ),
427
+ perTxMaxPriorityFeePerGasPolicyId: readOptionalString(
428
+ payload,
429
+ [
430
+ 'per_tx_max_priority_fee_per_gas_policy_id',
431
+ 'perTxMaxPriorityFeePerGasPolicyId'
432
+ ]
433
+ ),
434
+ perTxMaxCalldataBytes: readOptionalString(
435
+ payload,
436
+ ['per_tx_max_calldata_bytes', 'perTxMaxCalldataBytes']
437
+ ),
438
+ perTxMaxCalldataBytesPolicyId: readOptionalString(
439
+ payload,
440
+ ['per_tx_max_calldata_bytes_policy_id', 'perTxMaxCalldataBytesPolicyId']
441
+ )
442
+ };
443
+ }
444
+
445
+ function readTokenManualApprovalPolicySummary(
446
+ payload: Record<string, unknown>,
447
+ label: string
448
+ ): BootstrapTokenManualApprovalPolicySummary {
449
+ return {
450
+ tokenKey: readRequiredString(payload, ['token_key', 'tokenKey'], `${label}.token_key`),
451
+ symbol: readRequiredString(payload, ['symbol'], `${label}.symbol`),
452
+ chainKey: readRequiredString(payload, ['chain_key', 'chainKey'], `${label}.chain_key`),
453
+ chainId: readRequiredNumber(payload, ['chain_id', 'chainId'], `${label}.chain_id`),
454
+ priority: readRequiredNumber(payload, ['priority'], `${label}.priority`),
455
+ minAmountWei: readRequiredString(payload, ['min_amount_wei', 'minAmountWei'], `${label}.min_amount_wei`),
456
+ maxAmountWei: readRequiredString(payload, ['max_amount_wei', 'maxAmountWei'], `${label}.max_amount_wei`),
457
+ assetScope: readRequiredString(payload, ['asset_scope', 'assetScope'], `${label}.asset_scope`),
458
+ recipientScope: readRequiredString(
459
+ payload,
460
+ ['recipient_scope', 'recipientScope'],
461
+ `${label}.recipient_scope`
462
+ ),
463
+ policyId: readRequiredString(payload, ['policy_id', 'policyId'], `${label}.policy_id`)
464
+ };
465
+ }
466
+
467
+ function assertPrivateFile(stats: fs.Stats, resolvedPath: string): void {
468
+ assertPrivateFileStats(stats, resolvedPath, 'bootstrap credentials file');
469
+ }
470
+
471
+ function readBootstrapPayload(resolvedPath: string): Record<string, unknown> {
472
+ return assertJsonRecord(
473
+ JSON.parse(
474
+ readUtf8FileSecure(
475
+ resolvedPath,
476
+ `bootstrap credentials file '${resolvedPath}'`,
477
+ MAX_BOOTSTRAP_FILE_BYTES
478
+ )
479
+ ),
480
+ `bootstrap credentials file '${resolvedPath}'`
481
+ );
482
+ }
483
+
484
+ function assertWritableBootstrapFile(resolvedPath: string): void {
485
+ const stats = fs.lstatSync(resolvedPath);
486
+ if (stats.isSymbolicLink()) {
487
+ throw new Error(`bootstrap credentials file '${resolvedPath}' must not be a symlink`);
488
+ }
489
+ if (!stats.isFile()) {
490
+ throw new Error(`bootstrap credentials file '${resolvedPath}' must be a regular file`);
491
+ }
492
+ assertPrivateFile(stats, resolvedPath);
493
+ assertTrustedDirectoryPath(path.dirname(resolvedPath), 'bootstrap credentials directory');
494
+ }
495
+
496
+ function writePrivateBootstrapFile(resolvedPath: string, payload: Record<string, unknown>): void {
497
+ const directoryPath = path.dirname(resolvedPath);
498
+ const tempPath = path.join(
499
+ directoryPath,
500
+ `.${path.basename(resolvedPath)}.tmp-${process.pid}-${Date.now()}`
501
+ );
502
+
503
+ try {
504
+ fs.writeFileSync(tempPath, JSON.stringify(payload, null, 2) + '\n', {
505
+ encoding: 'utf8',
506
+ mode: PRIVATE_FILE_MODE,
507
+ flag: 'wx'
508
+ });
509
+ fs.chmodSync(tempPath, PRIVATE_FILE_MODE);
510
+ fs.renameSync(tempPath, resolvedPath);
511
+ fs.chmodSync(resolvedPath, PRIVATE_FILE_MODE);
512
+ } finally {
513
+ try {
514
+ if (fs.existsSync(tempPath)) {
515
+ fs.rmSync(tempPath);
516
+ }
517
+ } catch {}
518
+ }
519
+ }
520
+
521
+ function applyRedactedSecretFields(payload: Record<string, unknown>): Record<string, unknown> {
522
+ const nextPayload = { ...payload };
523
+ let wroteTokenField = false;
524
+ for (const fieldName of ['agent_auth_token', 'agentAuthToken']) {
525
+ if (fieldName in nextPayload) {
526
+ nextPayload[fieldName] = REDACTED_SECRET_PLACEHOLDER;
527
+ wroteTokenField = true;
528
+ }
529
+ }
530
+
531
+ if (!wroteTokenField) {
532
+ nextPayload.agent_auth_token = REDACTED_SECRET_PLACEHOLDER;
533
+ }
534
+
535
+ let wroteRedactedField = false;
536
+ for (const fieldName of ['agent_auth_token_redacted', 'agentAuthTokenRedacted']) {
537
+ if (fieldName in nextPayload) {
538
+ nextPayload[fieldName] = true;
539
+ wroteRedactedField = true;
540
+ }
541
+ }
542
+
543
+ if (!wroteRedactedField) {
544
+ nextPayload.agent_auth_token_redacted = true;
545
+ }
546
+
547
+ let wrotePrivateKeyField = false;
548
+ for (const fieldName of ['vault_private_key', 'vaultPrivateKey']) {
549
+ if (fieldName in nextPayload) {
550
+ nextPayload[fieldName] = REDACTED_SECRET_PLACEHOLDER;
551
+ wrotePrivateKeyField = true;
552
+ }
553
+ }
554
+
555
+ if (wrotePrivateKeyField) {
556
+ let wrotePrivateKeyRedactedField = false;
557
+ for (const fieldName of ['vault_private_key_redacted', 'vaultPrivateKeyRedacted']) {
558
+ if (fieldName in nextPayload) {
559
+ nextPayload[fieldName] = true;
560
+ wrotePrivateKeyRedactedField = true;
561
+ }
562
+ }
563
+
564
+ if (!wrotePrivateKeyRedactedField) {
565
+ nextPayload.vault_private_key_redacted = true;
566
+ }
567
+ }
568
+
569
+ return nextPayload;
570
+ }
571
+
572
+ function parseBootstrapAgentCredentialsPayload(
573
+ payload: Record<string, unknown>,
574
+ sourcePath: string
575
+ ): BootstrapAgentCredentials {
576
+ const redacted = readOptionalBoolean(payload, [
577
+ 'agent_auth_token_redacted',
578
+ 'agentAuthTokenRedacted'
579
+ ]);
580
+ if (redacted === true) {
581
+ throw new Error(
582
+ 'bootstrap credentials file contains a redacted agent auth token; rerun `wlfi-agent admin setup --print-agent-auth-token`'
583
+ );
584
+ }
585
+
586
+ const agentKeyId = assertValidAgentKeyId(
587
+ readRequiredString(payload, ['agent_key_id', 'agentKeyId'], 'agent_key_id')
588
+ );
589
+ const rawAgentAuthToken = readRequiredString(
590
+ payload,
591
+ ['agent_auth_token', 'agentAuthToken'],
592
+ 'agent_auth_token',
593
+ { trim: false }
594
+ );
595
+
596
+ if (rawAgentAuthToken === '<redacted>') {
597
+ throw new Error(
598
+ 'bootstrap credentials file contains a redacted agent auth token; rerun `wlfi-agent admin setup --print-agent-auth-token`'
599
+ );
600
+ }
601
+ const agentAuthToken = assertValidAgentAuthToken(rawAgentAuthToken, 'agent_auth_token');
602
+
603
+ return {
604
+ agentKeyId,
605
+ agentAuthToken,
606
+ sourcePath,
607
+ };
608
+ }
609
+
610
+ function parseBootstrapSetupSummaryPayload(
611
+ payload: Record<string, unknown>,
612
+ sourcePath: string
613
+ ): BootstrapSetupSummary {
614
+ const vaultPrivateKey = readOptionalString(
615
+ payload,
616
+ ['vault_private_key', 'vaultPrivateKey']
617
+ );
618
+ const tokenPolicies = readOptionalRecordArray(payload, ['token_policies', 'tokenPolicies'])
619
+ .map((entry, index) => readTokenPolicySummary(entry, `token_policies[${index}]`));
620
+ const tokenDestinationOverrides = readOptionalRecordArray(
621
+ payload,
622
+ ['token_destination_overrides', 'tokenDestinationOverrides']
623
+ ).map((entry, index) =>
624
+ readTokenDestinationOverrideSummary(entry, `token_destination_overrides[${index}]`)
625
+ );
626
+ const tokenManualApprovalPolicies = readOptionalRecordArray(
627
+ payload,
628
+ ['token_manual_approval_policies', 'tokenManualApprovalPolicies']
629
+ ).map((entry, index) =>
630
+ readTokenManualApprovalPolicySummary(entry, `token_manual_approval_policies[${index}]`)
631
+ );
632
+ const perTxPolicyId = readOptionalString(payload, ['per_tx_policy_id', 'perTxPolicyId']);
633
+ const dailyPolicyId = readOptionalString(payload, ['daily_policy_id', 'dailyPolicyId']);
634
+ const weeklyPolicyId = readOptionalString(payload, ['weekly_policy_id', 'weeklyPolicyId']);
635
+ const networkScope = readOptionalString(payload, ['network_scope', 'networkScope']);
636
+ const assetScope = readOptionalString(payload, ['asset_scope', 'assetScope']);
637
+ const recipientScope = readOptionalString(payload, ['recipient_scope', 'recipientScope']);
638
+
639
+ if (tokenPolicies.length === 0) {
640
+ if (!perTxPolicyId || !dailyPolicyId || !weeklyPolicyId) {
641
+ throw new Error('per-token bootstrap summary is missing token_policies and legacy policy ids');
642
+ }
643
+ if (!networkScope || !assetScope || !recipientScope) {
644
+ throw new Error('legacy bootstrap summary is missing policy scope fields');
645
+ }
646
+ }
647
+
648
+ return {
649
+ sourcePath,
650
+ leaseId: readRequiredString(payload, ['lease_id', 'leaseId'], 'lease_id'),
651
+ leaseExpiresAt: readRequiredString(
652
+ payload,
653
+ ['lease_expires_at', 'leaseExpiresAt'],
654
+ 'lease_expires_at'
655
+ ),
656
+ perTxPolicyId,
657
+ dailyPolicyId,
658
+ weeklyPolicyId,
659
+ gasPolicyId: readOptionalString(payload, ['gas_policy_id', 'gasPolicyId']),
660
+ perTxMaxWei: readOptionalString(payload, ['per_tx_max_wei', 'perTxMaxWei']),
661
+ dailyMaxWei: readOptionalString(payload, ['daily_max_wei', 'dailyMaxWei']),
662
+ weeklyMaxWei: readOptionalString(payload, ['weekly_max_wei', 'weeklyMaxWei']),
663
+ maxGasPerChainWei: readOptionalString(
664
+ payload,
665
+ ['max_gas_per_chain_wei', 'maxGasPerChainWei']
666
+ ),
667
+ dailyMaxTxCount: readOptionalString(payload, ['daily_max_tx_count', 'dailyMaxTxCount']),
668
+ dailyTxCountPolicyId: readOptionalString(
669
+ payload,
670
+ ['daily_tx_count_policy_id', 'dailyTxCountPolicyId']
671
+ ),
672
+ perTxMaxFeePerGasWei: readOptionalString(
673
+ payload,
674
+ ['per_tx_max_fee_per_gas_wei', 'perTxMaxFeePerGasWei']
675
+ ),
676
+ perTxMaxFeePerGasPolicyId: readOptionalString(
677
+ payload,
678
+ ['per_tx_max_fee_per_gas_policy_id', 'perTxMaxFeePerGasPolicyId']
679
+ ),
680
+ perTxMaxPriorityFeePerGasWei: readOptionalString(
681
+ payload,
682
+ [
683
+ 'per_tx_max_priority_fee_per_gas_wei',
684
+ 'perTxMaxPriorityFeePerGasWei'
685
+ ]
686
+ ),
687
+ perTxMaxPriorityFeePerGasPolicyId: readOptionalString(
688
+ payload,
689
+ [
690
+ 'per_tx_max_priority_fee_per_gas_policy_id',
691
+ 'perTxMaxPriorityFeePerGasPolicyId'
692
+ ]
693
+ ),
694
+ perTxMaxCalldataBytes: readOptionalString(
695
+ payload,
696
+ ['per_tx_max_calldata_bytes', 'perTxMaxCalldataBytes']
697
+ ),
698
+ perTxMaxCalldataBytesPolicyId: readOptionalString(
699
+ payload,
700
+ ['per_tx_max_calldata_bytes_policy_id', 'perTxMaxCalldataBytesPolicyId']
701
+ ),
702
+ vaultKeyId: readRequiredString(payload, ['vault_key_id', 'vaultKeyId'], 'vault_key_id'),
703
+ vaultPublicKey: readRequiredString(
704
+ payload,
705
+ ['vault_public_key', 'vaultPublicKey'],
706
+ 'vault_public_key'
707
+ ),
708
+ vaultPrivateKey:
709
+ vaultPrivateKey === REDACTED_SECRET_PLACEHOLDER ? null : vaultPrivateKey,
710
+ agentKeyId: readRequiredString(payload, ['agent_key_id', 'agentKeyId'], 'agent_key_id'),
711
+ networkScope,
712
+ assetScope,
713
+ recipientScope,
714
+ destinationOverrideCount:
715
+ readOptionalNumber(payload, ['destination_override_count', 'destinationOverrideCount']) ?? 0,
716
+ destinationOverrides: readOptionalRecordArray(payload, [
717
+ 'destination_overrides',
718
+ 'destinationOverrides'
719
+ ]).map((entry, index) => readDestinationOverrideSummary(entry, `destination_overrides[${index}]`)),
720
+ tokenPolicies,
721
+ tokenDestinationOverrides,
722
+ tokenManualApprovalPolicies,
723
+ policyAttachment: readRequiredString(
724
+ payload,
725
+ ['policy_attachment', 'policyAttachment'],
726
+ 'policy_attachment'
727
+ ),
728
+ attachedPolicyIds: readOptionalStringArray(payload, ['attached_policy_ids', 'attachedPolicyIds']),
729
+ policyNote: readRequiredString(payload, ['policy_note', 'policyNote'], 'policy_note')
730
+ };
731
+ }
732
+
733
+ export function readBootstrapSetupFile(inputPath: string): BootstrapSetupFileContents {
734
+ const resolvedPath = resolveInputPath(inputPath);
735
+ assertWritableBootstrapFile(resolvedPath);
736
+ const payload = readBootstrapPayload(resolvedPath);
737
+
738
+ return {
739
+ summary: parseBootstrapSetupSummaryPayload(payload, resolvedPath),
740
+ credentials: parseBootstrapAgentCredentialsPayload(payload, resolvedPath),
741
+ };
742
+ }
743
+
744
+ export function readBootstrapAgentCredentialsFile(inputPath: string): BootstrapAgentCredentials {
745
+ const resolvedPath = resolveInputPath(inputPath);
746
+ assertWritableBootstrapFile(resolvedPath);
747
+ return parseBootstrapAgentCredentialsPayload(readBootstrapPayload(resolvedPath), resolvedPath);
748
+ }
749
+
750
+ export function readBootstrapSetupSummaryFile(inputPath: string): BootstrapSetupSummary {
751
+ const resolvedPath = resolveInputPath(inputPath);
752
+ assertWritableBootstrapFile(resolvedPath);
753
+ return parseBootstrapSetupSummaryPayload(readBootstrapPayload(resolvedPath), resolvedPath);
754
+ }
755
+
756
+ export function assertBootstrapSetupSummaryLeaseIsActive(
757
+ summary: Pick<BootstrapSetupSummary, 'leaseExpiresAt'>,
758
+ deps: BootstrapLeaseValidationDeps = {}
759
+ ): void {
760
+ const now = deps.now ?? (() => Date.now());
761
+ const leaseExpiry = Date.parse(summary.leaseExpiresAt);
762
+
763
+ if (!Number.isFinite(leaseExpiry)) {
764
+ throw new Error('bootstrap summary lease_expires_at is not a valid ISO-8601 timestamp');
765
+ }
766
+
767
+ if (leaseExpiry <= now()) {
768
+ throw new Error(
769
+ 'bootstrap summary lease has expired; rerun `wlfi-agent admin setup --print-agent-auth-token`'
770
+ );
771
+ }
772
+ }
773
+
774
+ export function redactBootstrapAgentCredentialsFile(
775
+ inputPath: string
776
+ ): BootstrapCredentialsCleanupResult {
777
+ const resolvedPath = resolveInputPath(inputPath);
778
+ assertWritableBootstrapFile(resolvedPath);
779
+ const payload = readBootstrapPayload(resolvedPath);
780
+ writePrivateBootstrapFile(resolvedPath, applyRedactedSecretFields(payload));
781
+ return { sourcePath: resolvedPath };
782
+ }
783
+
784
+ export function deleteBootstrapAgentCredentialsFile(
785
+ inputPath: string
786
+ ): BootstrapCredentialsCleanupResult {
787
+ const resolvedPath = resolveInputPath(inputPath);
788
+ assertWritableBootstrapFile(resolvedPath);
789
+ fs.rmSync(resolvedPath);
790
+ return { sourcePath: resolvedPath };
791
+ }
792
+
793
+ export function cleanupBootstrapAgentCredentialsFile(
794
+ inputPath: string,
795
+ action: 'deleted' | 'redacted'
796
+ ): BootstrapCredentialsBestEffortCleanupResult {
797
+ const resolvedPath = resolveInputPath(inputPath);
798
+
799
+ try {
800
+ fs.lstatSync(resolvedPath);
801
+ } catch (error) {
802
+ if ((error as NodeJS.ErrnoException).code === 'ENOENT') {
803
+ return {
804
+ sourcePath: resolvedPath,
805
+ action: 'missing'
806
+ };
807
+ }
808
+ throw error;
809
+ }
810
+
811
+ if (action === 'deleted') {
812
+ deleteBootstrapAgentCredentialsFile(resolvedPath);
813
+ return {
814
+ sourcePath: resolvedPath,
815
+ action: 'deleted'
816
+ };
817
+ }
818
+
819
+ try {
820
+ redactBootstrapAgentCredentialsFile(resolvedPath);
821
+ return {
822
+ sourcePath: resolvedPath,
823
+ action: 'redacted'
824
+ };
825
+ } catch {
826
+ deleteBootstrapAgentCredentialsFile(resolvedPath);
827
+ return {
828
+ sourcePath: resolvedPath,
829
+ action: 'deleted'
830
+ };
831
+ }
832
+ }