@wlfi-agent/cli 1.4.17 → 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 (93) hide show
  1. package/Cargo.lock +5 -0
  2. package/README.md +61 -28
  3. package/crates/vault-cli-admin/src/io_utils.rs +149 -1
  4. package/crates/vault-cli-admin/src/main.rs +639 -16
  5. package/crates/vault-cli-admin/src/shared_config.rs +18 -18
  6. package/crates/vault-cli-admin/src/tui/token_rpc.rs +190 -3
  7. package/crates/vault-cli-admin/src/tui/utils.rs +59 -0
  8. package/crates/vault-cli-admin/src/tui.rs +1205 -120
  9. package/crates/vault-cli-agent/Cargo.toml +1 -0
  10. package/crates/vault-cli-agent/src/io_utils.rs +163 -2
  11. package/crates/vault-cli-agent/src/main.rs +648 -32
  12. package/crates/vault-cli-daemon/Cargo.toml +4 -0
  13. package/crates/vault-cli-daemon/src/main.rs +617 -67
  14. package/crates/vault-cli-daemon/src/relay_sync.rs +776 -4
  15. package/crates/vault-cli-daemon/tests/system_keychain_helper_acl.rs +5 -0
  16. package/crates/vault-daemon/src/daemon_parts/api_impl_and_utils.rs +32 -1
  17. package/crates/vault-daemon/src/persistence.rs +637 -100
  18. package/crates/vault-daemon/src/tests.rs +1013 -3
  19. package/crates/vault-daemon/src/tests_parts/part2.rs +99 -0
  20. package/crates/vault-daemon/src/tests_parts/part4.rs +11 -7
  21. package/crates/vault-domain/src/nonce.rs +4 -0
  22. package/crates/vault-domain/src/tests.rs +616 -0
  23. package/crates/vault-policy/src/engine.rs +55 -32
  24. package/crates/vault-policy/src/tests.rs +195 -0
  25. package/crates/vault-sdk-agent/src/lib.rs +415 -22
  26. package/crates/vault-signer/Cargo.toml +3 -0
  27. package/crates/vault-signer/src/lib.rs +266 -40
  28. package/crates/vault-transport-unix/src/lib.rs +653 -5
  29. package/crates/vault-transport-xpc/src/tests.rs +531 -3
  30. package/crates/vault-transport-xpc/tests/e2e_flow.rs +3 -0
  31. package/dist/cli.cjs +663 -190
  32. package/dist/cli.cjs.map +1 -1
  33. package/package.json +5 -2
  34. package/packages/cache/.turbo/turbo-build.log +20 -20
  35. package/packages/cache/coverage/clover.xml +529 -394
  36. package/packages/cache/coverage/coverage-final.json +2 -2
  37. package/packages/cache/coverage/index.html +21 -21
  38. package/packages/cache/coverage/src/client/index.html +1 -1
  39. package/packages/cache/coverage/src/client/index.ts.html +1 -1
  40. package/packages/cache/coverage/src/errors/index.html +1 -1
  41. package/packages/cache/coverage/src/errors/index.ts.html +12 -12
  42. package/packages/cache/coverage/src/index.html +1 -1
  43. package/packages/cache/coverage/src/index.ts.html +1 -1
  44. package/packages/cache/coverage/src/service/index.html +21 -21
  45. package/packages/cache/coverage/src/service/index.ts.html +769 -313
  46. package/packages/cache/dist/{chunk-QNK6GOTI.js → chunk-KC53LH5Z.js} +35 -2
  47. package/packages/cache/dist/chunk-KC53LH5Z.js.map +1 -0
  48. package/packages/cache/dist/{chunk-QF4XKEIA.cjs → chunk-UVU7VFE3.cjs} +35 -2
  49. package/packages/cache/dist/chunk-UVU7VFE3.cjs.map +1 -0
  50. package/packages/cache/dist/index.cjs +2 -2
  51. package/packages/cache/dist/index.js +1 -1
  52. package/packages/cache/dist/service/index.cjs +2 -2
  53. package/packages/cache/dist/service/index.js +1 -1
  54. package/packages/cache/node_modules/.bin/tsc +2 -2
  55. package/packages/cache/node_modules/.bin/tsserver +2 -2
  56. package/packages/cache/node_modules/.bin/tsup +2 -2
  57. package/packages/cache/node_modules/.bin/tsup-node +2 -2
  58. package/packages/cache/node_modules/.bin/vitest +4 -4
  59. package/packages/cache/node_modules/.vite/vitest/da39a3ee5e6b4b0d3255bfef95601890afd80709/results.json +1 -1
  60. package/packages/cache/src/service/index.test.ts +165 -19
  61. package/packages/cache/src/service/index.ts +38 -1
  62. package/packages/config/.turbo/turbo-build.log +4 -4
  63. package/packages/config/dist/index.cjs +0 -17
  64. package/packages/config/dist/index.cjs.map +1 -1
  65. package/packages/config/src/index.ts +0 -17
  66. package/packages/rpc/.turbo/turbo-build.log +11 -11
  67. package/packages/rpc/dist/index.cjs +0 -17
  68. package/packages/rpc/dist/index.cjs.map +1 -1
  69. package/packages/rpc/src/index.js +1 -0
  70. package/packages/ui/node_modules/.bin/tsc +2 -2
  71. package/packages/ui/node_modules/.bin/tsserver +2 -2
  72. package/packages/ui/node_modules/.bin/tsup +2 -2
  73. package/packages/ui/node_modules/.bin/tsup-node +2 -2
  74. package/scripts/install-cli-launcher.mjs +37 -0
  75. package/scripts/install-rust-binaries.mjs +47 -0
  76. package/scripts/run-tests-isolated.mjs +210 -0
  77. package/src/cli.ts +310 -50
  78. package/src/lib/admin-reset.ts +15 -30
  79. package/src/lib/admin-setup.ts +246 -55
  80. package/src/lib/agent-auth-migrate.ts +5 -1
  81. package/src/lib/asset-broadcast.ts +15 -4
  82. package/src/lib/config-amounts.ts +6 -4
  83. package/src/lib/hidden-tty-prompt.js +1 -0
  84. package/src/lib/hidden-tty-prompt.ts +105 -0
  85. package/src/lib/keychain.ts +1 -0
  86. package/src/lib/local-admin-access.ts +4 -29
  87. package/src/lib/rust.ts +129 -33
  88. package/src/lib/signed-tx.ts +1 -0
  89. package/src/lib/sudo.ts +15 -5
  90. package/src/lib/wallet-profile.ts +3 -0
  91. package/src/lib/wallet-setup.ts +52 -0
  92. package/packages/cache/dist/chunk-QF4XKEIA.cjs.map +0 -1
  93. package/packages/cache/dist/chunk-QNK6GOTI.js.map +0 -1
@@ -4,6 +4,54 @@ use vault_domain::{AgentAction, PolicyAttachment, PolicyType, SpendEvent, Spendi
4
4
 
5
5
  use crate::{PolicyDecision, PolicyError, PolicyEvaluation, PolicyExplanation};
6
6
 
7
+ pub(crate) fn increment_counter_or_mark_overflow(counter: &mut u128) -> bool {
8
+ match counter.checked_add(1) {
9
+ Some(next) => {
10
+ *counter = next;
11
+ false
12
+ }
13
+ None => {
14
+ *counter = u128::MAX;
15
+ true
16
+ }
17
+ }
18
+ }
19
+
20
+ pub(crate) fn enforce_priority_fee_limit(
21
+ policy: &SpendingPolicy,
22
+ action: &AgentAction,
23
+ ) -> Result<(), PolicyError> {
24
+ if let Some(requested_max_priority_fee_per_gas_wei) = action.max_priority_fee_per_gas_wei() {
25
+ if requested_max_priority_fee_per_gas_wei > policy.max_amount_wei {
26
+ return Err(PolicyError::PriorityFeePerGasLimitExceeded {
27
+ policy_id: policy.id,
28
+ max_priority_fee_per_gas_wei: policy.max_amount_wei,
29
+ requested_max_priority_fee_per_gas_wei,
30
+ });
31
+ }
32
+ }
33
+
34
+ Ok(())
35
+ }
36
+
37
+ pub(crate) fn enforce_calldata_bytes_limit(
38
+ policy: &SpendingPolicy,
39
+ action: &AgentAction,
40
+ ) -> Result<(), PolicyError> {
41
+ if let Some(requested_calldata_bytes) = action.calldata_len_bytes() {
42
+ let requested_calldata_bytes = requested_calldata_bytes as u128;
43
+ if requested_calldata_bytes > policy.max_amount_wei {
44
+ return Err(PolicyError::CalldataBytesLimitExceeded {
45
+ policy_id: policy.id,
46
+ max_calldata_bytes: policy.max_amount_wei,
47
+ requested_calldata_bytes,
48
+ });
49
+ }
50
+ }
51
+
52
+ Ok(())
53
+ }
54
+
7
55
  /// Stateless policy evaluator.
8
56
  #[derive(Debug, Default)]
9
57
  pub struct PolicyEngine;
@@ -140,29 +188,10 @@ impl PolicyEngine {
140
188
  }
141
189
  }
142
190
  PolicyType::PerTxMaxPriorityFeePerGas => {
143
- if let Some(requested_max_priority_fee_per_gas_wei) =
144
- action.max_priority_fee_per_gas_wei()
145
- {
146
- if requested_max_priority_fee_per_gas_wei > policy.max_amount_wei {
147
- return Err(PolicyError::PriorityFeePerGasLimitExceeded {
148
- policy_id: policy.id,
149
- max_priority_fee_per_gas_wei: policy.max_amount_wei,
150
- requested_max_priority_fee_per_gas_wei,
151
- });
152
- }
153
- }
191
+ enforce_priority_fee_limit(policy, action)?;
154
192
  }
155
193
  PolicyType::PerTxMaxCalldataBytes => {
156
- if let Some(requested_calldata_bytes) = action.calldata_len_bytes() {
157
- let requested_calldata_bytes = requested_calldata_bytes as u128;
158
- if requested_calldata_bytes > policy.max_amount_wei {
159
- return Err(PolicyError::CalldataBytesLimitExceeded {
160
- policy_id: policy.id,
161
- max_calldata_bytes: policy.max_amount_wei,
162
- requested_calldata_bytes,
163
- });
164
- }
165
- }
194
+ enforce_calldata_bytes_limit(policy, action)?;
166
195
  }
167
196
  PolicyType::DailyMaxSpending => self.enforce_window_limit(
168
197
  policy,
@@ -223,23 +252,17 @@ impl PolicyEngine {
223
252
  window_start: OffsetDateTime,
224
253
  ) -> Result<(), PolicyError> {
225
254
  let mut used_tx_count = 0u128;
226
- for _event in spend_history.iter().filter(|event| {
255
+ let overflowed = spend_history.iter().filter(|event| {
227
256
  event.agent_key_id == agent_key_id
228
257
  && event.at >= window_start
229
258
  && policy.assets.allows(&event.asset)
230
259
  && policy.recipients.allows(&event.recipient)
231
260
  && policy.networks.allows(&event.chain_id)
232
- }) {
233
- match used_tx_count.checked_add(1) {
234
- Some(next) => used_tx_count = next,
235
- None => {
236
- used_tx_count = u128::MAX;
237
- break;
238
- }
239
- }
240
- }
261
+ })
262
+ .any(|_| increment_counter_or_mark_overflow(&mut used_tx_count));
241
263
 
242
- let exceeds_window = used_tx_count
264
+ let exceeds_window = overflowed
265
+ || used_tx_count
243
266
  .checked_add(1)
244
267
  .is_none_or(|total| total > policy.max_amount_wei);
245
268
  if exceeds_window {
@@ -8,6 +8,10 @@ use vault_domain::{
8
8
  SpendEvent, SpendingPolicy,
9
9
  };
10
10
 
11
+ use crate::engine::{
12
+ enforce_calldata_bytes_limit, enforce_priority_fee_limit, increment_counter_or_mark_overflow,
13
+ };
14
+
11
15
  use super::{PolicyEngine, PolicyError};
12
16
 
13
17
  fn addr(x: &str) -> EvmAddress {
@@ -768,6 +772,197 @@ fn fee_policy_is_not_applicable_when_action_lacks_metadata() {
768
772
  assert!(result.is_ok());
769
773
  }
770
774
 
775
+ #[test]
776
+ fn explain_reports_no_attached_policies_when_attachment_filters_everything() {
777
+ let engine = PolicyEngine;
778
+ let policy = policy_all_per_tx(100);
779
+ let attachment =
780
+ PolicyAttachment::policy_set(BTreeSet::from([Uuid::new_v4()])).expect("attachment");
781
+ let action = AgentAction::TransferNative {
782
+ chain_id: 1,
783
+ to: addr("0x2000000000000000000000000000000000000000"),
784
+ amount_wei: 1,
785
+ };
786
+
787
+ let explanation = engine.explain(
788
+ &[policy],
789
+ &attachment,
790
+ &action,
791
+ &[],
792
+ Uuid::new_v4(),
793
+ OffsetDateTime::now_utc(),
794
+ );
795
+
796
+ assert!(matches!(explanation.decision, super::PolicyDecision::Deny(PolicyError::NoAttachedPolicies)));
797
+ assert!(explanation.applicable_policy_ids.is_empty());
798
+ assert!(explanation.evaluated_policy_ids.is_empty());
799
+ }
800
+
801
+ #[test]
802
+ fn fee_and_calldata_limits_allow_broadcasts_within_cap() {
803
+ let engine = PolicyEngine;
804
+ let fee_policy = SpendingPolicy::new(
805
+ 0,
806
+ PolicyType::PerTxMaxPriorityFeePerGas,
807
+ 1_000_000_000,
808
+ EntityScope::All,
809
+ EntityScope::All,
810
+ EntityScope::All,
811
+ )
812
+ .expect("policy");
813
+ let calldata_policy = SpendingPolicy::new(
814
+ 1,
815
+ PolicyType::PerTxMaxCalldataBytes,
816
+ 4,
817
+ EntityScope::All,
818
+ EntityScope::All,
819
+ EntityScope::All,
820
+ )
821
+ .expect("policy");
822
+ let action = broadcast_action_with_fees(2_000_000_000, 1_000_000_000, "0xdeadbeef");
823
+
824
+ let result = engine.evaluate(
825
+ &[fee_policy, calldata_policy],
826
+ &PolicyAttachment::AllPolicies,
827
+ &action,
828
+ &[],
829
+ Uuid::new_v4(),
830
+ OffsetDateTime::now_utc(),
831
+ );
832
+
833
+ assert!(result.is_ok());
834
+ }
835
+
836
+ #[test]
837
+ fn weekly_limit_and_manual_approval_paths_are_enforced() {
838
+ let engine = PolicyEngine;
839
+ let now = OffsetDateTime::now_utc();
840
+ let agent_key_id = Uuid::new_v4();
841
+ let token = addr("0x3000000000000000000000000000000000000000");
842
+ let recipient = addr("0x4000000000000000000000000000000000000000");
843
+
844
+ let weekly_policy = SpendingPolicy::new(
845
+ 0,
846
+ PolicyType::WeeklyMaxSpending,
847
+ 100,
848
+ EntityScope::All,
849
+ EntityScope::All,
850
+ EntityScope::All,
851
+ )
852
+ .expect("policy");
853
+ let weekly_history = vec![SpendEvent {
854
+ agent_key_id,
855
+ chain_id: 1,
856
+ asset: AssetId::Erc20(token.clone()),
857
+ recipient: recipient.clone(),
858
+ amount_wei: 80,
859
+ at: now - Duration::days(2),
860
+ }];
861
+ let weekly_action = AgentAction::Transfer {
862
+ chain_id: 1,
863
+ token: token.clone(),
864
+ to: recipient.clone(),
865
+ amount_wei: 30,
866
+ };
867
+ assert!(matches!(
868
+ engine.evaluate(
869
+ &[weekly_policy],
870
+ &PolicyAttachment::AllPolicies,
871
+ &weekly_action,
872
+ &weekly_history,
873
+ agent_key_id,
874
+ now
875
+ ),
876
+ Err(PolicyError::WindowLimitExceeded { .. })
877
+ ));
878
+
879
+ let manual_policy = SpendingPolicy::new_manual_approval(
880
+ 0,
881
+ 10,
882
+ 100,
883
+ EntityScope::All,
884
+ EntityScope::All,
885
+ EntityScope::All,
886
+ )
887
+ .expect("policy");
888
+ let in_range = AgentAction::Transfer {
889
+ chain_id: 1,
890
+ token: token.clone(),
891
+ to: recipient.clone(),
892
+ amount_wei: 50,
893
+ };
894
+ assert!(matches!(
895
+ engine.evaluate(
896
+ &[manual_policy.clone()],
897
+ &PolicyAttachment::AllPolicies,
898
+ &in_range,
899
+ &[],
900
+ agent_key_id,
901
+ now
902
+ ),
903
+ Err(PolicyError::ManualApprovalRequired { .. })
904
+ ));
905
+
906
+ let below_range = AgentAction::Transfer {
907
+ chain_id: 1,
908
+ token,
909
+ to: recipient,
910
+ amount_wei: 5,
911
+ };
912
+ assert!(engine
913
+ .evaluate(
914
+ &[manual_policy],
915
+ &PolicyAttachment::AllPolicies,
916
+ &below_range,
917
+ &[],
918
+ agent_key_id,
919
+ now
920
+ )
921
+ .is_ok());
922
+ }
923
+
924
+ #[test]
925
+ fn tx_count_overflow_helper_saturates_at_max() {
926
+ let mut counter = u128::MAX;
927
+
928
+ assert!(increment_counter_or_mark_overflow(&mut counter));
929
+ assert_eq!(counter, u128::MAX);
930
+ }
931
+
932
+ #[test]
933
+ fn optional_limit_helpers_allow_missing_or_in_range_metadata() {
934
+ let policy = SpendingPolicy::new(
935
+ 0,
936
+ PolicyType::PerTxMaxPriorityFeePerGas,
937
+ 1_000_000_000,
938
+ EntityScope::All,
939
+ EntityScope::All,
940
+ EntityScope::All,
941
+ )
942
+ .expect("policy");
943
+ let plain_transfer = AgentAction::TransferNative {
944
+ chain_id: 1,
945
+ to: addr("0x2000000000000000000000000000000000000000"),
946
+ amount_wei: 1,
947
+ };
948
+ assert!(enforce_priority_fee_limit(&policy, &plain_transfer).is_ok());
949
+
950
+ let broadcast = broadcast_action_with_fees(2_000_000_000, 1_000_000_000, "0xdeadbeef");
951
+ assert!(enforce_priority_fee_limit(&policy, &broadcast).is_ok());
952
+
953
+ let calldata_policy = SpendingPolicy::new(
954
+ 0,
955
+ PolicyType::PerTxMaxCalldataBytes,
956
+ 4,
957
+ EntityScope::All,
958
+ EntityScope::All,
959
+ EntityScope::All,
960
+ )
961
+ .expect("policy");
962
+ assert!(enforce_calldata_bytes_limit(&calldata_policy, &plain_transfer).is_ok());
963
+ assert!(enforce_calldata_bytes_limit(&calldata_policy, &broadcast).is_ok());
964
+ }
965
+
771
966
  #[test]
772
967
  fn gas_policy_is_not_applicable_when_action_lacks_metadata() {
773
968
  let engine = PolicyEngine;