@wlfi-agent/cli 1.4.16 → 1.4.18

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (97) hide show
  1. package/Cargo.lock +26 -20
  2. package/Cargo.toml +1 -1
  3. package/README.md +61 -28
  4. package/crates/vault-cli-admin/src/io_utils.rs +149 -1
  5. package/crates/vault-cli-admin/src/main.rs +639 -16
  6. package/crates/vault-cli-admin/src/shared_config.rs +18 -18
  7. package/crates/vault-cli-admin/src/tui/token_rpc.rs +190 -3
  8. package/crates/vault-cli-admin/src/tui/utils.rs +59 -0
  9. package/crates/vault-cli-admin/src/tui.rs +1205 -120
  10. package/crates/vault-cli-agent/Cargo.toml +1 -0
  11. package/crates/vault-cli-agent/src/io_utils.rs +163 -2
  12. package/crates/vault-cli-agent/src/main.rs +648 -32
  13. package/crates/vault-cli-daemon/Cargo.toml +4 -0
  14. package/crates/vault-cli-daemon/src/main.rs +617 -67
  15. package/crates/vault-cli-daemon/src/relay_sync.rs +776 -4
  16. package/crates/vault-cli-daemon/tests/system_keychain_helper_acl.rs +5 -0
  17. package/crates/vault-daemon/src/daemon_parts/api_impl_and_utils.rs +32 -1
  18. package/crates/vault-daemon/src/persistence.rs +637 -100
  19. package/crates/vault-daemon/src/tests.rs +1013 -3
  20. package/crates/vault-daemon/src/tests_parts/part2.rs +99 -0
  21. package/crates/vault-daemon/src/tests_parts/part4.rs +11 -7
  22. package/crates/vault-domain/src/nonce.rs +4 -0
  23. package/crates/vault-domain/src/tests.rs +616 -0
  24. package/crates/vault-policy/src/engine.rs +55 -32
  25. package/crates/vault-policy/src/tests.rs +195 -0
  26. package/crates/vault-sdk-agent/src/lib.rs +415 -22
  27. package/crates/vault-signer/Cargo.toml +3 -0
  28. package/crates/vault-signer/src/lib.rs +266 -40
  29. package/crates/vault-transport-unix/src/lib.rs +653 -5
  30. package/crates/vault-transport-xpc/src/tests.rs +531 -3
  31. package/crates/vault-transport-xpc/tests/e2e_flow.rs +3 -0
  32. package/dist/cli.cjs +663 -190
  33. package/dist/cli.cjs.map +1 -1
  34. package/package.json +5 -2
  35. package/packages/cache/.turbo/turbo-build.log +53 -52
  36. package/packages/cache/coverage/clover.xml +529 -394
  37. package/packages/cache/coverage/coverage-final.json +2 -2
  38. package/packages/cache/coverage/index.html +21 -21
  39. package/packages/cache/coverage/src/client/index.html +1 -1
  40. package/packages/cache/coverage/src/client/index.ts.html +1 -1
  41. package/packages/cache/coverage/src/errors/index.html +1 -1
  42. package/packages/cache/coverage/src/errors/index.ts.html +12 -12
  43. package/packages/cache/coverage/src/index.html +1 -1
  44. package/packages/cache/coverage/src/index.ts.html +1 -1
  45. package/packages/cache/coverage/src/service/index.html +21 -21
  46. package/packages/cache/coverage/src/service/index.ts.html +769 -313
  47. package/packages/cache/dist/{chunk-QNK6GOTI.js → chunk-KC53LH5Z.js} +35 -2
  48. package/packages/cache/dist/chunk-KC53LH5Z.js.map +1 -0
  49. package/packages/cache/dist/{chunk-QF4XKEIA.cjs → chunk-UVU7VFE3.cjs} +35 -2
  50. package/packages/cache/dist/chunk-UVU7VFE3.cjs.map +1 -0
  51. package/packages/cache/dist/index.cjs +2 -2
  52. package/packages/cache/dist/index.js +1 -1
  53. package/packages/cache/dist/service/index.cjs +2 -2
  54. package/packages/cache/dist/service/index.js +1 -1
  55. package/packages/cache/node_modules/.bin/tsc +2 -2
  56. package/packages/cache/node_modules/.bin/tsserver +2 -2
  57. package/packages/cache/node_modules/.bin/tsup +2 -2
  58. package/packages/cache/node_modules/.bin/tsup-node +2 -2
  59. package/packages/cache/node_modules/.bin/vitest +4 -4
  60. package/packages/cache/node_modules/.vite/vitest/da39a3ee5e6b4b0d3255bfef95601890afd80709/results.json +1 -1
  61. package/packages/cache/src/service/index.test.ts +165 -19
  62. package/packages/cache/src/service/index.ts +38 -1
  63. package/packages/config/.turbo/turbo-build.log +18 -17
  64. package/packages/config/dist/index.cjs +0 -17
  65. package/packages/config/dist/index.cjs.map +1 -1
  66. package/packages/config/src/index.ts +0 -17
  67. package/packages/rpc/.turbo/turbo-build.log +32 -31
  68. package/packages/rpc/dist/index.cjs +0 -17
  69. package/packages/rpc/dist/index.cjs.map +1 -1
  70. package/packages/rpc/src/index.js +1 -0
  71. package/packages/ui/.turbo/turbo-build.log +44 -43
  72. package/packages/ui/dist/components/badge.d.ts +1 -1
  73. package/packages/ui/dist/components/button.d.ts +1 -1
  74. package/packages/ui/node_modules/.bin/tsc +2 -2
  75. package/packages/ui/node_modules/.bin/tsserver +2 -2
  76. package/packages/ui/node_modules/.bin/tsup +2 -2
  77. package/packages/ui/node_modules/.bin/tsup-node +2 -2
  78. package/scripts/install-cli-launcher.mjs +37 -0
  79. package/scripts/install-rust-binaries.mjs +112 -0
  80. package/scripts/run-tests-isolated.mjs +210 -0
  81. package/src/cli.ts +310 -50
  82. package/src/lib/admin-reset.ts +15 -30
  83. package/src/lib/admin-setup.ts +246 -55
  84. package/src/lib/agent-auth-migrate.ts +5 -1
  85. package/src/lib/asset-broadcast.ts +15 -4
  86. package/src/lib/config-amounts.ts +6 -4
  87. package/src/lib/hidden-tty-prompt.js +1 -0
  88. package/src/lib/hidden-tty-prompt.ts +105 -0
  89. package/src/lib/keychain.ts +1 -0
  90. package/src/lib/local-admin-access.ts +4 -29
  91. package/src/lib/rust.ts +129 -33
  92. package/src/lib/signed-tx.ts +1 -0
  93. package/src/lib/sudo.ts +15 -5
  94. package/src/lib/wallet-profile.ts +3 -0
  95. package/src/lib/wallet-setup.ts +52 -0
  96. package/packages/cache/dist/chunk-QF4XKEIA.cjs.map +0 -1
  97. package/packages/cache/dist/chunk-QNK6GOTI.js.map +0 -1
@@ -392,9 +392,9 @@ mod tests {
392
392
  r#"{
393
393
  "chains": {},
394
394
  "tokens": {},
395
- "chainId": 11155111,
396
- "chainName": "sepolia",
397
- "rpcUrl": "https://rpc.sepolia.example"
395
+ "chainId": 56,
396
+ "chainName": "bsc",
397
+ "rpcUrl": "https://rpc.bsc.example"
398
398
  }
399
399
  "#,
400
400
  )
@@ -405,11 +405,11 @@ mod tests {
405
405
  assert!(config.chains.contains_key("bsc"));
406
406
  assert!(config.tokens.contains_key("usd1"));
407
407
  assert!(config.tokens.contains_key("bnb"));
408
- assert_eq!(config.chain_id, Some(11155111));
409
- assert_eq!(config.chain_name.as_deref(), Some("sepolia"));
408
+ assert_eq!(config.chain_id, Some(56));
409
+ assert_eq!(config.chain_name.as_deref(), Some("bsc"));
410
410
  assert_eq!(
411
411
  config.rpc_url.as_deref(),
412
- Some("https://rpc.sepolia.example")
412
+ Some("https://rpc.bsc.example")
413
413
  );
414
414
 
415
415
  fs::remove_dir_all(&root).expect("cleanup temp root");
@@ -489,25 +489,25 @@ mod tests {
489
489
  let path = root.join("config.json");
490
490
 
491
491
  let mut config = WlfiConfig {
492
- chain_id: Some(11155111),
493
- chain_name: Some("sepolia".to_string()),
494
- rpc_url: Some("https://rpc.sepolia.example".to_string()),
492
+ chain_id: Some(1),
493
+ chain_name: Some("eth".to_string()),
494
+ rpc_url: Some("https://rpc.ethereum.example".to_string()),
495
495
  ..WlfiConfig::default()
496
496
  };
497
497
  config.chains.insert(
498
- "sepolia".to_string(),
498
+ "eth".to_string(),
499
499
  super::ChainProfile {
500
- chain_id: 11155111,
501
- name: "sepolia".to_string(),
502
- rpc_url: Some("https://rpc.sepolia.example".to_string()),
500
+ chain_id: 1,
501
+ name: "eth".to_string(),
502
+ rpc_url: Some("https://rpc.ethereum.example".to_string()),
503
503
  extra: BTreeMap::new(),
504
504
  },
505
505
  );
506
506
  config.tokens.insert(
507
- "usdc".to_string(),
507
+ "usd1".to_string(),
508
508
  TokenProfile {
509
- name: Some("USD Coin".to_string()),
510
- symbol: "USDC".to_string(),
509
+ name: Some("USD1".to_string()),
510
+ symbol: "USD1".to_string(),
511
511
  default_policy: Some(TokenPolicyProfile {
512
512
  per_tx_amount: Some(25.0),
513
513
  daily_amount: Some(100.0),
@@ -559,9 +559,9 @@ mod tests {
559
559
  extra: BTreeMap::new(),
560
560
  }],
561
561
  chains: BTreeMap::from([(
562
- "sepolia".to_string(),
562
+ "eth".to_string(),
563
563
  TokenChainProfile {
564
- chain_id: 11155111,
564
+ chain_id: 1,
565
565
  is_native: false,
566
566
  address: Some("0x1000000000000000000000000000000000000000".to_string()),
567
567
  decimals: 6,
@@ -229,7 +229,54 @@ fn decode_abi_usize(bytes: &[u8]) -> Result<usize> {
229
229
 
230
230
  #[cfg(test)]
231
231
  mod tests {
232
- use super::{decode_abi_string, parse_hex_u64};
232
+ use std::io::{Read, Write};
233
+ use std::net::TcpListener;
234
+ use std::thread;
235
+ use std::time::Duration;
236
+
237
+ use super::{
238
+ call_rpc, decode_abi_string, decode_hex_bytes, decode_abi_usize, eth_call,
239
+ fetch_token_metadata, native_metadata_for_chain, parse_hex_u64, read_erc20_decimals,
240
+ };
241
+ use reqwest::Client;
242
+ use serde_json::json;
243
+ use vault_domain::EvmAddress;
244
+
245
+ fn start_mock_rpc_server(responses: Vec<String>) -> (String, thread::JoinHandle<()>) {
246
+ let listener = TcpListener::bind("127.0.0.1:0").expect("bind");
247
+ let addr = listener.local_addr().expect("addr");
248
+ let handle = thread::spawn(move || {
249
+ for body in responses {
250
+ let (mut stream, _) = listener.accept().expect("accept");
251
+ stream
252
+ .set_read_timeout(Some(Duration::from_millis(250)))
253
+ .expect("timeout");
254
+ let mut buffer = [0u8; 4096];
255
+ loop {
256
+ match stream.read(&mut buffer) {
257
+ Ok(0) => break,
258
+ Ok(_) => continue,
259
+ Err(err)
260
+ if err.kind() == std::io::ErrorKind::WouldBlock
261
+ || err.kind() == std::io::ErrorKind::TimedOut =>
262
+ {
263
+ break;
264
+ }
265
+ Err(err) => panic!("failed to read request: {err}"),
266
+ }
267
+ }
268
+ let response = format!(
269
+ "HTTP/1.1 200 OK\r\ncontent-type: application/json\r\ncontent-length: {}\r\nconnection: close\r\n\r\n{}",
270
+ body.len(),
271
+ body
272
+ );
273
+ stream
274
+ .write_all(response.as_bytes())
275
+ .expect("write response");
276
+ }
277
+ });
278
+ (format!("http://{addr}"), handle)
279
+ }
233
280
 
234
281
  #[test]
235
282
  fn parse_hex_u64_reads_eth_chain_id_payloads() {
@@ -243,8 +290,148 @@ mod tests {
243
290
  "0x",
244
291
  "0000000000000000000000000000000000000000000000000000000000000020",
245
292
  "0000000000000000000000000000000000000000000000000000000000000004",
246
- "5553444300000000000000000000000000000000000000000000000000000000"
293
+ "5553443100000000000000000000000000000000000000000000000000000000"
294
+ );
295
+ assert_eq!(decode_abi_string(encoded).expect("string"), "USD1");
296
+ }
297
+
298
+ #[test]
299
+ fn helper_parsers_reject_invalid_inputs() {
300
+ assert!(parse_hex_u64("1", "chain").is_err());
301
+ assert!(parse_hex_u64("0xzz", "chain").is_err());
302
+ assert!(decode_hex_bytes("deadbeef").is_err());
303
+ assert!(decode_hex_bytes("0xzz").is_err());
304
+ assert!(decode_abi_usize(&[0u8; 31]).is_err());
305
+
306
+ let too_short = "0x1234";
307
+ assert!(decode_abi_string(too_short).is_err());
308
+ let bad_offset = concat!(
309
+ "0x",
310
+ "0000000000000000000000000000000000000000000000000000000000000060",
311
+ "0000000000000000000000000000000000000000000000000000000000000004",
312
+ "5553443100000000000000000000000000000000000000000000000000000000"
247
313
  );
248
- assert_eq!(decode_abi_string(encoded).expect("string"), "USDC");
314
+ assert!(decode_abi_string(bad_offset).is_err());
315
+ let bad_utf8 = concat!(
316
+ "0x",
317
+ "0000000000000000000000000000000000000000000000000000000000000020",
318
+ "0000000000000000000000000000000000000000000000000000000000000002",
319
+ "ffff000000000000000000000000000000000000000000000000000000000000"
320
+ );
321
+ assert!(decode_abi_string(bad_utf8).is_err());
322
+ }
323
+
324
+ #[test]
325
+ fn native_metadata_for_chain_covers_known_and_fallback_symbols() {
326
+ assert_eq!(native_metadata_for_chain(1, "eth"), ("Ether".to_string(), "ETH".to_string()));
327
+ assert_eq!(native_metadata_for_chain(56, "bsc"), ("BNB".to_string(), "BNB".to_string()));
328
+ assert_eq!(native_metadata_for_chain(137, "polygon"), ("MATIC".to_string(), "MATIC".to_string()));
329
+ assert_eq!(
330
+ native_metadata_for_chain(999, "custom"),
331
+ ("Native Asset (custom)".to_string(), "NATIVE".to_string())
332
+ );
333
+ }
334
+
335
+ #[tokio::test]
336
+ async fn rpc_helpers_cover_success_and_error_responses() {
337
+ let (rpc_url, handle) = start_mock_rpc_server(vec![
338
+ json!({"jsonrpc":"2.0","id":1,"result":"0x1"}).to_string(),
339
+ json!({"jsonrpc":"2.0","id":1,"error":{"code":-32000,"message":"boom"}}).to_string(),
340
+ json!({"jsonrpc":"2.0","id":1,"result":42}).to_string(),
341
+ ]);
342
+
343
+ let client = Client::new();
344
+ let value = call_rpc(&client, &rpc_url, "eth_chainId", json!([]))
345
+ .await
346
+ .expect("chain id");
347
+ assert_eq!(value, json!("0x1"));
348
+
349
+ let err = call_rpc(&client, &rpc_url, "eth_call", json!([]))
350
+ .await
351
+ .expect_err("rpc error");
352
+ assert!(err.to_string().contains("boom"));
353
+
354
+ let address: EvmAddress = "0x1111111111111111111111111111111111111111"
355
+ .parse()
356
+ .expect("address");
357
+ let err = eth_call(&client, &rpc_url, &address, "0x313ce567")
358
+ .await
359
+ .expect_err("non-string eth_call");
360
+ assert!(err.to_string().contains("non-string"));
361
+
362
+ handle.join().expect("server join");
363
+ }
364
+
365
+ #[tokio::test]
366
+ async fn fetch_token_metadata_covers_native_erc20_and_validation_failures() {
367
+ let symbol_payload = concat!(
368
+ "0x",
369
+ "0000000000000000000000000000000000000000000000000000000000000020",
370
+ "0000000000000000000000000000000000000000000000000000000000000004",
371
+ "5553443100000000000000000000000000000000000000000000000000000000"
372
+ );
373
+ let name_payload = concat!(
374
+ "0x",
375
+ "0000000000000000000000000000000000000000000000000000000000000020",
376
+ "0000000000000000000000000000000000000000000000000000000000000004",
377
+ "5553443100000000000000000000000000000000000000000000000000000000"
378
+ );
379
+ let decimals_payload = format!("0x{}", "00".repeat(31) + "06");
380
+ let address: EvmAddress = "0x1111111111111111111111111111111111111111"
381
+ .parse()
382
+ .expect("address");
383
+
384
+ let (rpc_url, handle) = start_mock_rpc_server(vec![
385
+ json!({"jsonrpc":"2.0","id":1,"result":"0x1"}).to_string(),
386
+ json!({"jsonrpc":"2.0","id":1,"result":"0x1"}).to_string(),
387
+ json!({"jsonrpc":"2.0","id":1,"result":decimals_payload}).to_string(),
388
+ json!({"jsonrpc":"2.0","id":1,"result":symbol_payload}).to_string(),
389
+ json!({"jsonrpc":"2.0","id":1,"result":name_payload}).to_string(),
390
+ json!({"jsonrpc":"2.0","id":1,"result":"0x2"}).to_string(),
391
+ json!({"jsonrpc":"2.0","id":1,"result":"0x1"}).to_string(),
392
+ ]);
393
+
394
+ let native = fetch_token_metadata("eth", &rpc_url, 1, true, None)
395
+ .await
396
+ .expect("native metadata");
397
+ assert_eq!(native.symbol, "ETH");
398
+ assert_eq!(native.decimals, 18);
399
+
400
+ let erc20 = fetch_token_metadata("eth", &rpc_url, 1, false, Some(&address))
401
+ .await
402
+ .expect("erc20 metadata");
403
+ assert_eq!(erc20.symbol, "USD1");
404
+ assert_eq!(erc20.name, "USD1");
405
+ assert_eq!(erc20.decimals, 6);
406
+
407
+ let err = fetch_token_metadata("eth", &rpc_url, 1, true, None)
408
+ .await
409
+ .expect_err("chain mismatch");
410
+ assert!(err.to_string().contains("expects 1"));
411
+
412
+ let err = fetch_token_metadata("eth", &rpc_url, 1, false, None)
413
+ .await
414
+ .expect_err("missing address");
415
+ assert!(err.to_string().contains("token address is required"));
416
+
417
+ handle.join().expect("server join");
418
+ }
419
+
420
+ #[tokio::test]
421
+ async fn read_erc20_decimals_rejects_wrong_length_payloads() {
422
+ let (rpc_url, handle) = start_mock_rpc_server(vec![
423
+ json!({"jsonrpc":"2.0","id":1,"result":"0x0102"}).to_string(),
424
+ ]);
425
+ let client = Client::new();
426
+ let address: EvmAddress = "0x1111111111111111111111111111111111111111"
427
+ .parse()
428
+ .expect("address");
429
+
430
+ let err = read_erc20_decimals(&client, &rpc_url, &address)
431
+ .await
432
+ .expect_err("bad decimals payload");
433
+ assert!(err.to_string().contains("expected 32"));
434
+
435
+ handle.join().expect("server join");
249
436
  }
250
437
  }
@@ -80,3 +80,62 @@ pub(super) fn is_allowed_input_char(field: Field, ch: char) -> bool {
80
80
  _ => true,
81
81
  }
82
82
  }
83
+
84
+ #[cfg(test)]
85
+ mod tests {
86
+ use super::{
87
+ bool_label, is_allowed_input_char, parse_address, parse_non_negative_u128,
88
+ parse_positive_u128, parse_positive_u64,
89
+ };
90
+ use crate::tui::Field;
91
+
92
+ #[test]
93
+ fn parsers_accept_trimmed_values_and_reject_invalid_or_zero_inputs() {
94
+ assert_eq!(parse_positive_u128("amount", " 42 ").expect("u128"), 42);
95
+ assert_eq!(
96
+ parse_non_negative_u128("count", " 0 ").expect("non-negative"),
97
+ 0
98
+ );
99
+ assert_eq!(parse_positive_u64("chain", " 7 ").expect("u64"), 7);
100
+
101
+ assert!(parse_positive_u128("amount", "0").is_err());
102
+ assert!(parse_positive_u128("amount", "nope").is_err());
103
+ assert!(parse_non_negative_u128("count", "nope").is_err());
104
+ assert!(parse_positive_u64("chain", "0").is_err());
105
+ assert!(parse_positive_u64("chain", "bad").is_err());
106
+ }
107
+
108
+ #[test]
109
+ fn parse_address_and_bool_label_cover_happy_and_error_paths() {
110
+ assert_eq!(bool_label(true), "yes");
111
+ assert_eq!(bool_label(false), "no");
112
+
113
+ let address = parse_address(
114
+ "recipient",
115
+ " 0x1111111111111111111111111111111111111111 ",
116
+ )
117
+ .expect("address");
118
+ assert_eq!(address.as_str(), "0x1111111111111111111111111111111111111111");
119
+ assert!(parse_address("recipient", "not-an-address").is_err());
120
+ }
121
+
122
+ #[test]
123
+ fn allowed_input_characters_match_field_types() {
124
+ assert!(is_allowed_input_char(Field::TokenKey, 'a'));
125
+ assert!(is_allowed_input_char(Field::TokenKey, '-'));
126
+ assert!(!is_allowed_input_char(Field::TokenKey, '!'));
127
+
128
+ assert!(is_allowed_input_char(Field::PerTxLimit, '9'));
129
+ assert!(is_allowed_input_char(Field::PerTxLimit, '.'));
130
+ assert!(!is_allowed_input_char(Field::PerTxLimit, 'x'));
131
+
132
+ assert!(is_allowed_input_char(Field::DailyMaxTxCount, '4'));
133
+ assert!(!is_allowed_input_char(Field::DailyMaxTxCount, '.'));
134
+
135
+ assert!(is_allowed_input_char(Field::NetworkAddress, 'a'));
136
+ assert!(is_allowed_input_char(Field::NetworkAddress, 'X'));
137
+ assert!(!is_allowed_input_char(Field::NetworkAddress, 'g'));
138
+
139
+ assert!(is_allowed_input_char(Field::TokenName, '!'));
140
+ }
141
+ }