@wlfi-agent/cli 1.4.13 → 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.
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 +49 -43
  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,711 @@
1
+ //! Agent-side SDK for daemon-mediated token operations.
2
+
3
+ #![forbid(unsafe_code)]
4
+
5
+ use std::sync::Arc;
6
+
7
+ use async_trait::async_trait;
8
+ use thiserror::Error;
9
+ use time::{Duration, OffsetDateTime};
10
+ use uuid::Uuid;
11
+ use vault_daemon::{DaemonError, KeyManagerDaemonApi};
12
+ use vault_domain::{
13
+ action_from_erc20_calldata, AgentAction, AgentCredentials, BroadcastTx, DomainError,
14
+ Eip3009Transfer, EvmAddress, NonceReleaseRequest, NonceReservation, NonceReservationRequest,
15
+ Permit2Permit, SignRequest, Signature,
16
+ };
17
+ use zeroize::Zeroizing;
18
+
19
+ /// Errors returned by the agent SDK.
20
+ #[derive(Debug, Error)]
21
+ pub enum AgentSdkError {
22
+ /// Upstream daemon call failed.
23
+ #[error("daemon call failed: {0}")]
24
+ Daemon(#[from] DaemonError),
25
+ /// Invalid action payload.
26
+ #[error("invalid action: {0}")]
27
+ Domain(#[from] DomainError),
28
+ /// Failed to serialize action payload for daemon verification.
29
+ #[error("failed to serialize action payload: {0}")]
30
+ Serialization(String),
31
+ }
32
+
33
+ /// High-level agent operations.
34
+ #[async_trait]
35
+ pub trait AgentOperations: Send + Sync {
36
+ /// Requests an ERC-20 `approve` signature.
37
+ async fn approve(
38
+ &self,
39
+ chain_id: u64,
40
+ token: EvmAddress,
41
+ spender: EvmAddress,
42
+ amount_wei: u128,
43
+ ) -> Result<Signature, AgentSdkError>;
44
+
45
+ /// Requests an ERC-20 `transfer` signature.
46
+ async fn transfer(
47
+ &self,
48
+ chain_id: u64,
49
+ token: EvmAddress,
50
+ to: EvmAddress,
51
+ amount_wei: u128,
52
+ ) -> Result<Signature, AgentSdkError>;
53
+
54
+ /// Requests a native ETH transfer signature.
55
+ async fn transfer_native(
56
+ &self,
57
+ chain_id: u64,
58
+ to: EvmAddress,
59
+ amount_wei: u128,
60
+ ) -> Result<Signature, AgentSdkError>;
61
+
62
+ /// Requests a Permit2 `PermitSingle` signature.
63
+ async fn permit2_permit(&self, permit: Permit2Permit) -> Result<Signature, AgentSdkError>;
64
+
65
+ /// Requests an EIP-3009 `transferWithAuthorization` signature.
66
+ async fn eip3009_transfer_with_authorization(
67
+ &self,
68
+ authorization: Eip3009Transfer,
69
+ ) -> Result<Signature, AgentSdkError>;
70
+
71
+ /// Requests an EIP-3009 `receiveWithAuthorization` signature.
72
+ async fn eip3009_receive_with_authorization(
73
+ &self,
74
+ authorization: Eip3009Transfer,
75
+ ) -> Result<Signature, AgentSdkError>;
76
+
77
+ /// Parses ERC-20 calldata and signs derived action (`approve` / `transfer`).
78
+ async fn sign_erc20_calldata(
79
+ &self,
80
+ chain_id: u64,
81
+ token: EvmAddress,
82
+ calldata: Vec<u8>,
83
+ ) -> Result<Signature, AgentSdkError>;
84
+
85
+ /// Requests signature authorization for a raw broadcast transaction.
86
+ async fn broadcast_tx(&self, tx: BroadcastTx) -> Result<Signature, AgentSdkError>;
87
+
88
+ /// Reserves a nonce lease for a future broadcast transaction.
89
+ async fn reserve_broadcast_nonce(
90
+ &self,
91
+ chain_id: u64,
92
+ min_nonce: u64,
93
+ ) -> Result<NonceReservation, AgentSdkError>;
94
+
95
+ /// Explicitly releases an unused nonce reservation.
96
+ async fn release_broadcast_nonce(
97
+ &self,
98
+ reservation_id: uuid::Uuid,
99
+ ) -> Result<(), AgentSdkError>;
100
+ }
101
+
102
+ /// Default SDK implementation backed by a daemon API client.
103
+ pub struct AgentSdk<D>
104
+ where
105
+ D: KeyManagerDaemonApi + ?Sized,
106
+ {
107
+ daemon: Arc<D>,
108
+ agent_key_id: Uuid,
109
+ agent_auth_token: Zeroizing<String>,
110
+ }
111
+
112
+ impl<D> AgentSdk<D>
113
+ where
114
+ D: KeyManagerDaemonApi + ?Sized,
115
+ {
116
+ const REQUEST_TTL: Duration = Duration::minutes(2);
117
+
118
+ /// Creates SDK bound to agent credentials.
119
+ #[must_use]
120
+ pub fn new(daemon: Arc<D>, agent_credentials: AgentCredentials) -> Self {
121
+ Self {
122
+ daemon,
123
+ agent_key_id: agent_credentials.agent_key.id,
124
+ agent_auth_token: Zeroizing::new(agent_credentials.auth_token),
125
+ }
126
+ }
127
+
128
+ /// Creates SDK from raw agent key id and auth token.
129
+ ///
130
+ /// This constructor is useful for CLIs and integrations where only the
131
+ /// stable key id + bearer token are persisted.
132
+ #[must_use]
133
+ pub fn new_with_key_id_and_token(
134
+ daemon: Arc<D>,
135
+ agent_key_id: Uuid,
136
+ agent_auth_token: String,
137
+ ) -> Self {
138
+ Self {
139
+ daemon,
140
+ agent_key_id,
141
+ agent_auth_token: Zeroizing::new(agent_auth_token),
142
+ }
143
+ }
144
+
145
+ async fn sign_action(&self, action: AgentAction) -> Result<Signature, AgentSdkError> {
146
+ action.validate()?;
147
+ let now = OffsetDateTime::now_utc();
148
+ let payload = serde_json::to_vec(&action)
149
+ .map_err(|err| AgentSdkError::Serialization(err.to_string()))?;
150
+ let request = SignRequest {
151
+ request_id: uuid::Uuid::new_v4(),
152
+ agent_key_id: self.agent_key_id,
153
+ agent_auth_token: self.agent_auth_token.as_str().to_owned(),
154
+ payload,
155
+ action,
156
+ requested_at: now,
157
+ expires_at: now + Self::REQUEST_TTL,
158
+ };
159
+
160
+ Ok(self.daemon.sign_for_agent(request).await?)
161
+ }
162
+ }
163
+
164
+ #[async_trait]
165
+ impl<D> AgentOperations for AgentSdk<D>
166
+ where
167
+ D: KeyManagerDaemonApi + ?Sized,
168
+ {
169
+ async fn approve(
170
+ &self,
171
+ chain_id: u64,
172
+ token: EvmAddress,
173
+ spender: EvmAddress,
174
+ amount_wei: u128,
175
+ ) -> Result<Signature, AgentSdkError> {
176
+ self.sign_action(AgentAction::Approve {
177
+ chain_id,
178
+ token,
179
+ spender,
180
+ amount_wei,
181
+ })
182
+ .await
183
+ }
184
+
185
+ async fn transfer(
186
+ &self,
187
+ chain_id: u64,
188
+ token: EvmAddress,
189
+ to: EvmAddress,
190
+ amount_wei: u128,
191
+ ) -> Result<Signature, AgentSdkError> {
192
+ self.sign_action(AgentAction::Transfer {
193
+ chain_id,
194
+ token,
195
+ to,
196
+ amount_wei,
197
+ })
198
+ .await
199
+ }
200
+
201
+ async fn transfer_native(
202
+ &self,
203
+ chain_id: u64,
204
+ to: EvmAddress,
205
+ amount_wei: u128,
206
+ ) -> Result<Signature, AgentSdkError> {
207
+ self.sign_action(AgentAction::TransferNative {
208
+ chain_id,
209
+ to,
210
+ amount_wei,
211
+ })
212
+ .await
213
+ }
214
+
215
+ async fn permit2_permit(&self, permit: Permit2Permit) -> Result<Signature, AgentSdkError> {
216
+ self.sign_action(AgentAction::Permit2Permit { permit })
217
+ .await
218
+ }
219
+
220
+ async fn eip3009_transfer_with_authorization(
221
+ &self,
222
+ authorization: Eip3009Transfer,
223
+ ) -> Result<Signature, AgentSdkError> {
224
+ self.sign_action(AgentAction::Eip3009TransferWithAuthorization { authorization })
225
+ .await
226
+ }
227
+
228
+ async fn eip3009_receive_with_authorization(
229
+ &self,
230
+ authorization: Eip3009Transfer,
231
+ ) -> Result<Signature, AgentSdkError> {
232
+ self.sign_action(AgentAction::Eip3009ReceiveWithAuthorization { authorization })
233
+ .await
234
+ }
235
+
236
+ async fn sign_erc20_calldata(
237
+ &self,
238
+ chain_id: u64,
239
+ token: EvmAddress,
240
+ calldata: Vec<u8>,
241
+ ) -> Result<Signature, AgentSdkError> {
242
+ let action = action_from_erc20_calldata(chain_id, token, &calldata)?;
243
+ self.sign_action(action).await
244
+ }
245
+
246
+ async fn broadcast_tx(&self, mut tx: BroadcastTx) -> Result<Signature, AgentSdkError> {
247
+ let reservation = self.reserve_broadcast_nonce(tx.chain_id, tx.nonce).await?;
248
+ tx.nonce = reservation.nonce;
249
+
250
+ match self.sign_action(AgentAction::BroadcastTx { tx }).await {
251
+ Ok(signature) => Ok(signature),
252
+ Err(err) => {
253
+ let _ = self
254
+ .release_broadcast_nonce(reservation.reservation_id)
255
+ .await;
256
+ Err(err)
257
+ }
258
+ }
259
+ }
260
+
261
+ async fn reserve_broadcast_nonce(
262
+ &self,
263
+ chain_id: u64,
264
+ min_nonce: u64,
265
+ ) -> Result<NonceReservation, AgentSdkError> {
266
+ let now = OffsetDateTime::now_utc();
267
+ let request = NonceReservationRequest {
268
+ request_id: uuid::Uuid::new_v4(),
269
+ agent_key_id: self.agent_key_id,
270
+ agent_auth_token: self.agent_auth_token.to_string(),
271
+ chain_id,
272
+ min_nonce,
273
+ requested_at: now,
274
+ expires_at: now + Self::REQUEST_TTL,
275
+ };
276
+ Ok(self.daemon.reserve_nonce(request).await?)
277
+ }
278
+
279
+ async fn release_broadcast_nonce(
280
+ &self,
281
+ reservation_id: uuid::Uuid,
282
+ ) -> Result<(), AgentSdkError> {
283
+ let now = OffsetDateTime::now_utc();
284
+ let request = NonceReleaseRequest {
285
+ request_id: uuid::Uuid::new_v4(),
286
+ agent_key_id: self.agent_key_id,
287
+ agent_auth_token: self.agent_auth_token.to_string(),
288
+ reservation_id,
289
+ requested_at: now,
290
+ expires_at: now + Self::REQUEST_TTL,
291
+ };
292
+ Ok(self.daemon.release_nonce(request).await?)
293
+ }
294
+ }
295
+
296
+ #[cfg(test)]
297
+ mod tests {
298
+ use std::sync::{Arc, Mutex};
299
+
300
+ use async_trait::async_trait;
301
+ use time::OffsetDateTime;
302
+ use uuid::Uuid;
303
+ use vault_daemon::{DaemonError, KeyManagerDaemonApi};
304
+ use vault_domain::{
305
+ AdminSession, AgentAction, AgentCredentials, AgentKey, BroadcastTx, EvmAddress, Lease,
306
+ ManualApprovalDecision, ManualApprovalRequest, NonceReleaseRequest, NonceReservation,
307
+ NonceReservationRequest, PolicyAttachment, RelayConfig, SignRequest, Signature,
308
+ SpendingPolicy, VaultKey,
309
+ };
310
+ use vault_policy::PolicyEvaluation;
311
+ use vault_signer::KeyCreateRequest;
312
+
313
+ use super::{AgentOperations, AgentSdk, AgentSdkError};
314
+
315
+ #[derive(Default)]
316
+ pub(super) struct RecordingDaemon {
317
+ pub(super) request: Mutex<Option<SignRequest>>,
318
+ fail_sign: bool,
319
+ reserved_nonce_override: Option<u64>,
320
+ }
321
+
322
+ #[async_trait]
323
+ impl KeyManagerDaemonApi for RecordingDaemon {
324
+ async fn issue_lease(&self, _vault_password: &str) -> Result<Lease, DaemonError> {
325
+ Err(DaemonError::Transport("not used".to_string()))
326
+ }
327
+
328
+ async fn add_policy(
329
+ &self,
330
+ _session: &AdminSession,
331
+ _policy: SpendingPolicy,
332
+ ) -> Result<(), DaemonError> {
333
+ Err(DaemonError::Transport("not used".to_string()))
334
+ }
335
+
336
+ async fn list_policies(
337
+ &self,
338
+ _session: &AdminSession,
339
+ ) -> Result<Vec<SpendingPolicy>, DaemonError> {
340
+ Err(DaemonError::Transport("not used".to_string()))
341
+ }
342
+
343
+ async fn disable_policy(
344
+ &self,
345
+ _session: &AdminSession,
346
+ _policy_id: Uuid,
347
+ ) -> Result<(), DaemonError> {
348
+ Err(DaemonError::Transport("not used".to_string()))
349
+ }
350
+
351
+ async fn create_vault_key(
352
+ &self,
353
+ _session: &AdminSession,
354
+ _request: KeyCreateRequest,
355
+ ) -> Result<VaultKey, DaemonError> {
356
+ Err(DaemonError::Transport("not used".to_string()))
357
+ }
358
+
359
+ async fn export_vault_private_key(
360
+ &self,
361
+ _session: &AdminSession,
362
+ _vault_key_id: Uuid,
363
+ ) -> Result<Option<String>, DaemonError> {
364
+ Err(DaemonError::Transport("not used".to_string()))
365
+ }
366
+
367
+ async fn create_agent_key(
368
+ &self,
369
+ _session: &AdminSession,
370
+ _vault_key_id: Uuid,
371
+ _attachment: PolicyAttachment,
372
+ ) -> Result<AgentCredentials, DaemonError> {
373
+ Err(DaemonError::Transport("not used".to_string()))
374
+ }
375
+
376
+ async fn rotate_agent_auth_token(
377
+ &self,
378
+ _session: &AdminSession,
379
+ _agent_key_id: Uuid,
380
+ ) -> Result<String, DaemonError> {
381
+ Err(DaemonError::Transport("not used".to_string()))
382
+ }
383
+
384
+ async fn revoke_agent_key(
385
+ &self,
386
+ _session: &AdminSession,
387
+ _agent_key_id: Uuid,
388
+ ) -> Result<(), DaemonError> {
389
+ Err(DaemonError::Transport("not used".to_string()))
390
+ }
391
+
392
+ async fn list_manual_approval_requests(
393
+ &self,
394
+ _session: &AdminSession,
395
+ ) -> Result<Vec<ManualApprovalRequest>, DaemonError> {
396
+ Err(DaemonError::Transport("not used".to_string()))
397
+ }
398
+
399
+ async fn decide_manual_approval_request(
400
+ &self,
401
+ _session: &AdminSession,
402
+ _approval_request_id: Uuid,
403
+ _decision: ManualApprovalDecision,
404
+ _rejection_reason: Option<String>,
405
+ ) -> Result<ManualApprovalRequest, DaemonError> {
406
+ Err(DaemonError::Transport("not used".to_string()))
407
+ }
408
+
409
+ async fn set_relay_config(
410
+ &self,
411
+ _session: &AdminSession,
412
+ _relay_url: Option<String>,
413
+ _frontend_url: Option<String>,
414
+ ) -> Result<RelayConfig, DaemonError> {
415
+ Err(DaemonError::Transport("not used".to_string()))
416
+ }
417
+
418
+ async fn get_relay_config(
419
+ &self,
420
+ _session: &AdminSession,
421
+ ) -> Result<RelayConfig, DaemonError> {
422
+ Err(DaemonError::Transport("not used".to_string()))
423
+ }
424
+
425
+ async fn evaluate_for_agent(
426
+ &self,
427
+ _request: SignRequest,
428
+ ) -> Result<PolicyEvaluation, DaemonError> {
429
+ Err(DaemonError::Transport("not used".to_string()))
430
+ }
431
+
432
+ async fn explain_for_agent(
433
+ &self,
434
+ _request: SignRequest,
435
+ ) -> Result<vault_policy::PolicyExplanation, DaemonError> {
436
+ Err(DaemonError::Transport("not used".to_string()))
437
+ }
438
+
439
+ async fn reserve_nonce(
440
+ &self,
441
+ request: NonceReservationRequest,
442
+ ) -> Result<NonceReservation, DaemonError> {
443
+ Ok(NonceReservation {
444
+ reservation_id: Uuid::new_v4(),
445
+ agent_key_id: request.agent_key_id,
446
+ vault_key_id: Uuid::new_v4(),
447
+ chain_id: request.chain_id,
448
+ nonce: self.reserved_nonce_override.unwrap_or(request.min_nonce),
449
+ issued_at: OffsetDateTime::now_utc(),
450
+ expires_at: OffsetDateTime::now_utc() + time::Duration::minutes(2),
451
+ })
452
+ }
453
+
454
+ async fn release_nonce(&self, _request: NonceReleaseRequest) -> Result<(), DaemonError> {
455
+ Ok(())
456
+ }
457
+
458
+ async fn sign_for_agent(&self, request: SignRequest) -> Result<Signature, DaemonError> {
459
+ *self.request.lock().expect("lock") = Some(request);
460
+ if self.fail_sign {
461
+ return Err(DaemonError::AgentAuthenticationFailed);
462
+ }
463
+ Ok(Signature::from_der(vec![0x11, 0x22]))
464
+ }
465
+ }
466
+
467
+ pub(super) fn test_credentials() -> AgentCredentials {
468
+ AgentCredentials {
469
+ agent_key: AgentKey {
470
+ id: Uuid::new_v4(),
471
+ vault_key_id: Uuid::new_v4(),
472
+ policies: PolicyAttachment::AllPolicies,
473
+ created_at: OffsetDateTime::now_utc(),
474
+ },
475
+ auth_token: "agent-secret-token".to_string(),
476
+ }
477
+ }
478
+
479
+ #[tokio::test]
480
+ async fn approve_sends_canonical_action_payload_and_auth_token() {
481
+ let daemon = Arc::new(RecordingDaemon::default());
482
+ let credentials = test_credentials();
483
+ let key_id = credentials.agent_key.id;
484
+ let auth_token = credentials.auth_token.clone();
485
+ let sdk = AgentSdk::new(daemon.clone(), credentials);
486
+
487
+ let token: EvmAddress = "0x1000000000000000000000000000000000000000"
488
+ .parse()
489
+ .expect("token");
490
+ let spender: EvmAddress = "0x2000000000000000000000000000000000000000"
491
+ .parse()
492
+ .expect("spender");
493
+
494
+ let signature = sdk
495
+ .approve(1, token.clone(), spender.clone(), 42)
496
+ .await
497
+ .expect("approve");
498
+ assert_eq!(signature.bytes, vec![0x11, 0x22]);
499
+
500
+ let captured = daemon
501
+ .request
502
+ .lock()
503
+ .expect("lock")
504
+ .clone()
505
+ .expect("captured request");
506
+ assert_eq!(captured.agent_key_id, key_id);
507
+ assert_eq!(captured.agent_auth_token, auth_token);
508
+ assert_eq!(
509
+ captured.action,
510
+ AgentAction::Approve {
511
+ chain_id: 1,
512
+ token,
513
+ spender,
514
+ amount_wei: 42
515
+ }
516
+ );
517
+ let decoded: AgentAction = serde_json::from_slice(&captured.payload).expect("decode");
518
+ assert_eq!(decoded, captured.action);
519
+ }
520
+
521
+ #[tokio::test]
522
+ async fn transfer_propagates_daemon_errors() {
523
+ let daemon = Arc::new(RecordingDaemon {
524
+ request: Mutex::new(None),
525
+ fail_sign: true,
526
+ reserved_nonce_override: None,
527
+ });
528
+ let sdk = AgentSdk::new(daemon, test_credentials());
529
+
530
+ let token: EvmAddress = "0x3000000000000000000000000000000000000000"
531
+ .parse()
532
+ .expect("token");
533
+ let recipient: EvmAddress = "0x4000000000000000000000000000000000000000"
534
+ .parse()
535
+ .expect("recipient");
536
+
537
+ let err = sdk
538
+ .transfer(1, token, recipient, 7)
539
+ .await
540
+ .expect_err("must fail");
541
+ assert!(matches!(
542
+ err,
543
+ AgentSdkError::Daemon(DaemonError::AgentAuthenticationFailed)
544
+ ));
545
+ }
546
+
547
+ #[tokio::test]
548
+ async fn broadcast_tx_sends_canonical_action_payload() {
549
+ let daemon = Arc::new(RecordingDaemon::default());
550
+ let credentials = test_credentials();
551
+ let sdk = AgentSdk::new(daemon.clone(), credentials);
552
+
553
+ let tx = BroadcastTx {
554
+ chain_id: 1,
555
+ nonce: 0,
556
+ to: "0x5000000000000000000000000000000000000000"
557
+ .parse()
558
+ .expect("to"),
559
+ value_wei: 0,
560
+ data_hex: "0xdeadbeef".to_string(),
561
+ gas_limit: 50_000,
562
+ max_fee_per_gas_wei: 1_000_000_000,
563
+ max_priority_fee_per_gas_wei: 1_000_000_000,
564
+ tx_type: 0x02,
565
+ delegation_enabled: false,
566
+ };
567
+
568
+ let signature = sdk.broadcast_tx(tx.clone()).await.expect("broadcast");
569
+ assert_eq!(signature.bytes, vec![0x11, 0x22]);
570
+
571
+ let captured = daemon
572
+ .request
573
+ .lock()
574
+ .expect("lock")
575
+ .clone()
576
+ .expect("captured request");
577
+ assert_eq!(captured.action, AgentAction::BroadcastTx { tx: tx.clone() });
578
+ let decoded: AgentAction = serde_json::from_slice(&captured.payload).expect("decode");
579
+ assert_eq!(decoded, AgentAction::BroadcastTx { tx });
580
+ }
581
+
582
+ #[tokio::test]
583
+ async fn broadcast_tx_uses_reserved_nonce_from_daemon() {
584
+ let daemon = Arc::new(RecordingDaemon {
585
+ request: Mutex::new(None),
586
+ fail_sign: false,
587
+ reserved_nonce_override: Some(1),
588
+ });
589
+ let sdk = AgentSdk::new(daemon.clone(), test_credentials());
590
+
591
+ let tx = BroadcastTx {
592
+ chain_id: 56,
593
+ nonce: 0,
594
+ to: "0x5000000000000000000000000000000000000000"
595
+ .parse()
596
+ .expect("to"),
597
+ value_wei: 0,
598
+ data_hex: "0xdeadbeef".to_string(),
599
+ gas_limit: 50_000,
600
+ max_fee_per_gas_wei: 1_000_000_000,
601
+ max_priority_fee_per_gas_wei: 1_000_000_000,
602
+ tx_type: 0x02,
603
+ delegation_enabled: false,
604
+ };
605
+
606
+ sdk.broadcast_tx(tx.clone()).await.expect("broadcast");
607
+
608
+ let captured = daemon
609
+ .request
610
+ .lock()
611
+ .expect("lock")
612
+ .clone()
613
+ .expect("captured request");
614
+ match captured.action {
615
+ AgentAction::BroadcastTx { tx: signed_tx } => {
616
+ assert_eq!(signed_tx.nonce, 1);
617
+ assert_eq!(signed_tx.chain_id, tx.chain_id);
618
+ assert_eq!(signed_tx.to, tx.to);
619
+ }
620
+ other => panic!("unexpected action: {other:?}"),
621
+ }
622
+ }
623
+ }
624
+
625
+ #[cfg(test)]
626
+ mod typed_data_tests {
627
+ use std::sync::Arc;
628
+
629
+ use super::tests::{test_credentials, RecordingDaemon};
630
+ use super::{AgentOperations, AgentSdk};
631
+ use vault_domain::{AgentAction, Eip3009Transfer, Permit2Permit};
632
+
633
+ #[tokio::test]
634
+ async fn permit2_sends_canonical_action_payload() {
635
+ let daemon = Arc::new(RecordingDaemon::default());
636
+ let sdk = AgentSdk::new(daemon.clone(), test_credentials());
637
+ let permit = Permit2Permit {
638
+ chain_id: 1,
639
+ permit2_contract: "0x000000000022d473030f116ddee9f6b43ac78ba3"
640
+ .parse()
641
+ .expect("permit2"),
642
+ token: "0x1000000000000000000000000000000000000000"
643
+ .parse()
644
+ .expect("token"),
645
+ spender: "0x2000000000000000000000000000000000000000"
646
+ .parse()
647
+ .expect("spender"),
648
+ amount_wei: 42,
649
+ expiration: 100,
650
+ nonce: 1,
651
+ sig_deadline: 200,
652
+ };
653
+
654
+ let signature = sdk.permit2_permit(permit.clone()).await.expect("permit2");
655
+ assert_eq!(signature.bytes, vec![0x11, 0x22]);
656
+
657
+ let captured = daemon
658
+ .request
659
+ .lock()
660
+ .expect("lock")
661
+ .clone()
662
+ .expect("captured request");
663
+ assert_eq!(captured.action, AgentAction::Permit2Permit { permit });
664
+ let decoded: AgentAction = serde_json::from_slice(&captured.payload).expect("decode");
665
+ assert_eq!(decoded, captured.action);
666
+ }
667
+
668
+ #[tokio::test]
669
+ async fn eip3009_receive_sends_canonical_action_payload() {
670
+ let daemon = Arc::new(RecordingDaemon::default());
671
+ let sdk = AgentSdk::new(daemon.clone(), test_credentials());
672
+ let authorization = Eip3009Transfer {
673
+ chain_id: 1,
674
+ token: "0x3000000000000000000000000000000000000000"
675
+ .parse()
676
+ .expect("token"),
677
+ token_name: "USD Coin".to_string(),
678
+ token_version: Some("2".to_string()),
679
+ from: "0x4000000000000000000000000000000000000000"
680
+ .parse()
681
+ .expect("from"),
682
+ to: "0x5000000000000000000000000000000000000000"
683
+ .parse()
684
+ .expect("to"),
685
+ amount_wei: 9,
686
+ valid_after: 1,
687
+ valid_before: 2,
688
+ nonce_hex: "0x5555555555555555555555555555555555555555555555555555555555555555"
689
+ .to_string(),
690
+ };
691
+
692
+ let signature = sdk
693
+ .eip3009_receive_with_authorization(authorization.clone())
694
+ .await
695
+ .expect("eip3009 receive");
696
+ assert_eq!(signature.bytes, vec![0x11, 0x22]);
697
+
698
+ let captured = daemon
699
+ .request
700
+ .lock()
701
+ .expect("lock")
702
+ .clone()
703
+ .expect("captured request");
704
+ assert_eq!(
705
+ captured.action,
706
+ AgentAction::Eip3009ReceiveWithAuthorization { authorization }
707
+ );
708
+ let decoded: AgentAction = serde_json::from_slice(&captured.payload).expect("decode");
709
+ assert_eq!(decoded, captured.action);
710
+ }
711
+ }