@usesigil/kit 0.2.2 → 0.3.0

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 (270) hide show
  1. package/README.md +2 -2
  2. package/dist/advanced-analytics.d.ts +3 -1
  3. package/dist/advanced-analytics.d.ts.map +1 -1
  4. package/dist/advanced-analytics.js +31 -15
  5. package/dist/advanced-analytics.js.map +1 -1
  6. package/dist/agent-analytics.d.ts +6 -6
  7. package/dist/agent-analytics.d.ts.map +1 -1
  8. package/dist/agent-analytics.js +16 -14
  9. package/dist/agent-analytics.js.map +1 -1
  10. package/dist/agent-errors.d.ts +4 -3
  11. package/dist/agent-errors.d.ts.map +1 -1
  12. package/dist/agent-errors.js +83 -7
  13. package/dist/agent-errors.js.map +1 -1
  14. package/dist/balance-tracker.d.ts +20 -0
  15. package/dist/balance-tracker.d.ts.map +1 -1
  16. package/dist/balance-tracker.js +18 -5
  17. package/dist/balance-tracker.js.map +1 -1
  18. package/dist/create-vault.js +1 -1
  19. package/dist/create-vault.js.map +1 -1
  20. package/dist/dashboard/constraint-reads.d.ts +50 -0
  21. package/dist/dashboard/constraint-reads.d.ts.map +1 -0
  22. package/dist/dashboard/constraint-reads.js +119 -0
  23. package/dist/dashboard/constraint-reads.js.map +1 -0
  24. package/dist/dashboard/errors.d.ts +14 -0
  25. package/dist/dashboard/errors.d.ts.map +1 -0
  26. package/dist/dashboard/errors.js +71 -0
  27. package/dist/dashboard/errors.js.map +1 -0
  28. package/dist/dashboard/index.d.ts +29 -2
  29. package/dist/dashboard/index.d.ts.map +1 -1
  30. package/dist/dashboard/index.js +45 -0
  31. package/dist/dashboard/index.js.map +1 -1
  32. package/dist/dashboard/mutations.d.ts +18 -0
  33. package/dist/dashboard/mutations.d.ts.map +1 -1
  34. package/dist/dashboard/mutations.js +36 -34
  35. package/dist/dashboard/mutations.js.map +1 -1
  36. package/dist/dashboard/reads.d.ts +86 -1
  37. package/dist/dashboard/reads.d.ts.map +1 -1
  38. package/dist/dashboard/reads.js +469 -166
  39. package/dist/dashboard/reads.js.map +1 -1
  40. package/dist/dashboard/types.d.ts +118 -6
  41. package/dist/dashboard/types.d.ts.map +1 -1
  42. package/dist/event-analytics.d.ts +5 -0
  43. package/dist/event-analytics.d.ts.map +1 -1
  44. package/dist/event-analytics.js +40 -3
  45. package/dist/event-analytics.js.map +1 -1
  46. package/dist/events.d.ts.map +1 -1
  47. package/dist/events.js +6 -1
  48. package/dist/events.js.map +1 -1
  49. package/dist/formatting.d.ts +40 -1
  50. package/dist/formatting.d.ts.map +1 -1
  51. package/dist/formatting.js +53 -5
  52. package/dist/formatting.js.map +1 -1
  53. package/dist/generated/accounts/index.d.ts +1 -0
  54. package/dist/generated/accounts/index.d.ts.map +1 -1
  55. package/dist/generated/accounts/index.js +1 -0
  56. package/dist/generated/accounts/index.js.map +1 -1
  57. package/dist/generated/accounts/instructionConstraints.d.ts +20 -11
  58. package/dist/generated/accounts/instructionConstraints.d.ts.map +1 -1
  59. package/dist/generated/accounts/instructionConstraints.js +17 -8
  60. package/dist/generated/accounts/instructionConstraints.js.map +1 -1
  61. package/dist/generated/accounts/pendingAgentPermissionsUpdate.d.ts +4 -2
  62. package/dist/generated/accounts/pendingAgentPermissionsUpdate.d.ts.map +1 -1
  63. package/dist/generated/accounts/pendingAgentPermissionsUpdate.js +4 -2
  64. package/dist/generated/accounts/pendingAgentPermissionsUpdate.js.map +1 -1
  65. package/dist/generated/accounts/pendingConstraintsUpdate.d.ts +30 -21
  66. package/dist/generated/accounts/pendingConstraintsUpdate.d.ts.map +1 -1
  67. package/dist/generated/accounts/pendingConstraintsUpdate.js +17 -10
  68. package/dist/generated/accounts/pendingConstraintsUpdate.js.map +1 -1
  69. package/dist/generated/accounts/policyConfig.d.ts +14 -8
  70. package/dist/generated/accounts/policyConfig.d.ts.map +1 -1
  71. package/dist/generated/accounts/policyConfig.js +2 -0
  72. package/dist/generated/accounts/policyConfig.js.map +1 -1
  73. package/dist/generated/accounts/postExecutionAssertions.d.ts +50 -0
  74. package/dist/generated/accounts/postExecutionAssertions.d.ts.map +1 -0
  75. package/dist/generated/accounts/postExecutionAssertions.js +72 -0
  76. package/dist/generated/accounts/postExecutionAssertions.js.map +1 -0
  77. package/dist/generated/accounts/sessionAuthority.d.ts +14 -5
  78. package/dist/generated/accounts/sessionAuthority.d.ts.map +1 -1
  79. package/dist/generated/accounts/sessionAuthority.js +5 -4
  80. package/dist/generated/accounts/sessionAuthority.js.map +1 -1
  81. package/dist/generated/errors/sigil.d.ts +16 -2
  82. package/dist/generated/errors/sigil.d.ts.map +1 -1
  83. package/dist/generated/errors/sigil.js +23 -2
  84. package/dist/generated/errors/sigil.js.map +1 -1
  85. package/dist/generated/event-discriminators.d.ts.map +1 -1
  86. package/dist/generated/event-discriminators.js +6 -1
  87. package/dist/generated/event-discriminators.js.map +1 -1
  88. package/dist/generated/instructions/allocateConstraintsPda.d.ts +62 -0
  89. package/dist/generated/instructions/allocateConstraintsPda.d.ts.map +1 -0
  90. package/dist/generated/instructions/allocateConstraintsPda.js +134 -0
  91. package/dist/generated/instructions/allocateConstraintsPda.js.map +1 -0
  92. package/dist/generated/instructions/allocatePendingConstraintsPda.d.ts +66 -0
  93. package/dist/generated/instructions/allocatePendingConstraintsPda.d.ts.map +1 -0
  94. package/dist/generated/instructions/allocatePendingConstraintsPda.js +157 -0
  95. package/dist/generated/instructions/allocatePendingConstraintsPda.js.map +1 -0
  96. package/dist/generated/instructions/applyConstraintsUpdate.d.ts +3 -12
  97. package/dist/generated/instructions/applyConstraintsUpdate.d.ts.map +1 -1
  98. package/dist/generated/instructions/applyConstraintsUpdate.js.map +1 -1
  99. package/dist/generated/instructions/closePostAssertions.d.ts +59 -0
  100. package/dist/generated/instructions/closePostAssertions.d.ts.map +1 -0
  101. package/dist/generated/instructions/closePostAssertions.js +137 -0
  102. package/dist/generated/instructions/closePostAssertions.js.map +1 -0
  103. package/dist/generated/instructions/createInstructionConstraints.d.ts +10 -11
  104. package/dist/generated/instructions/createInstructionConstraints.d.ts.map +1 -1
  105. package/dist/generated/instructions/createInstructionConstraints.js +2 -16
  106. package/dist/generated/instructions/createInstructionConstraints.js.map +1 -1
  107. package/dist/generated/instructions/createPostAssertions.d.ts +65 -0
  108. package/dist/generated/instructions/createPostAssertions.d.ts.map +1 -0
  109. package/dist/generated/instructions/createPostAssertions.js +146 -0
  110. package/dist/generated/instructions/createPostAssertions.js.map +1 -0
  111. package/dist/generated/instructions/extendPda.d.ts +52 -0
  112. package/dist/generated/instructions/extendPda.d.ts.map +1 -0
  113. package/dist/generated/instructions/extendPda.js +86 -0
  114. package/dist/generated/instructions/extendPda.js.map +1 -0
  115. package/dist/generated/instructions/index.d.ts +5 -0
  116. package/dist/generated/instructions/index.d.ts.map +1 -1
  117. package/dist/generated/instructions/index.js +5 -0
  118. package/dist/generated/instructions/index.js.map +1 -1
  119. package/dist/generated/instructions/queueAgentPermissionsUpdate.d.ts +4 -4
  120. package/dist/generated/instructions/queueAgentPermissionsUpdate.d.ts.map +1 -1
  121. package/dist/generated/instructions/queueAgentPermissionsUpdate.js +3 -3
  122. package/dist/generated/instructions/queueAgentPermissionsUpdate.js.map +1 -1
  123. package/dist/generated/instructions/queueConstraintsUpdate.d.ts +13 -11
  124. package/dist/generated/instructions/queueConstraintsUpdate.d.ts.map +1 -1
  125. package/dist/generated/instructions/queueConstraintsUpdate.js +2 -16
  126. package/dist/generated/instructions/queueConstraintsUpdate.js.map +1 -1
  127. package/dist/generated/instructions/reactivateVault.d.ts +3 -3
  128. package/dist/generated/instructions/reactivateVault.d.ts.map +1 -1
  129. package/dist/generated/instructions/reactivateVault.js +3 -3
  130. package/dist/generated/instructions/reactivateVault.js.map +1 -1
  131. package/dist/generated/instructions/registerAgent.d.ts +3 -3
  132. package/dist/generated/instructions/registerAgent.d.ts.map +1 -1
  133. package/dist/generated/instructions/registerAgent.js +3 -3
  134. package/dist/generated/instructions/registerAgent.js.map +1 -1
  135. package/dist/generated/instructions/validateAndAuthorize.d.ts +4 -13
  136. package/dist/generated/instructions/validateAndAuthorize.d.ts.map +1 -1
  137. package/dist/generated/instructions/validateAndAuthorize.js +1 -6
  138. package/dist/generated/instructions/validateAndAuthorize.js.map +1 -1
  139. package/dist/generated/programs/sigil.d.ts +56 -34
  140. package/dist/generated/programs/sigil.d.ts.map +1 -1
  141. package/dist/generated/programs/sigil.js +99 -34
  142. package/dist/generated/programs/sigil.js.map +1 -1
  143. package/dist/generated/types/accountConstraintZC.d.ts +18 -0
  144. package/dist/generated/types/accountConstraintZC.d.ts.map +1 -0
  145. package/dist/generated/types/accountConstraintZC.js +26 -0
  146. package/dist/generated/types/accountConstraintZC.js.map +1 -0
  147. package/dist/generated/types/actionAuthorized.d.ts +2 -13
  148. package/dist/generated/types/actionAuthorized.d.ts.map +1 -1
  149. package/dist/generated/types/actionAuthorized.js +2 -3
  150. package/dist/generated/types/actionAuthorized.js.map +1 -1
  151. package/dist/generated/types/agentEntry.d.ts +13 -3
  152. package/dist/generated/types/agentEntry.d.ts.map +1 -1
  153. package/dist/generated/types/agentEntry.js +5 -3
  154. package/dist/generated/types/agentEntry.js.map +1 -1
  155. package/dist/generated/types/agentRegistered.d.ts +2 -2
  156. package/dist/generated/types/agentRegistered.d.ts.map +1 -1
  157. package/dist/generated/types/agentRegistered.js +3 -3
  158. package/dist/generated/types/agentRegistered.js.map +1 -1
  159. package/dist/generated/types/constraintEntry.d.ts +11 -1
  160. package/dist/generated/types/constraintEntry.d.ts.map +1 -1
  161. package/dist/generated/types/constraintEntry.js +8 -2
  162. package/dist/generated/types/constraintEntry.js.map +1 -1
  163. package/dist/generated/types/constraintEntryZC.d.ts +51 -0
  164. package/dist/generated/types/constraintEntryZC.d.ts.map +1 -0
  165. package/dist/generated/types/constraintEntryZC.js +49 -0
  166. package/dist/generated/types/constraintEntryZC.js.map +1 -0
  167. package/dist/generated/types/constraintsChangeApplied.d.ts +6 -4
  168. package/dist/generated/types/constraintsChangeApplied.d.ts.map +1 -1
  169. package/dist/generated/types/constraintsChangeApplied.js +3 -1
  170. package/dist/generated/types/constraintsChangeApplied.js.map +1 -1
  171. package/dist/generated/types/constraintsChangeQueued.d.ts +6 -4
  172. package/dist/generated/types/constraintsChangeQueued.d.ts.map +1 -1
  173. package/dist/generated/types/constraintsChangeQueued.js +3 -1
  174. package/dist/generated/types/constraintsChangeQueued.js.map +1 -1
  175. package/dist/generated/types/dataConstraintZC.d.ts +20 -0
  176. package/dist/generated/types/dataConstraintZC.d.ts.map +1 -0
  177. package/dist/generated/types/dataConstraintZC.js +30 -0
  178. package/dist/generated/types/dataConstraintZC.js.map +1 -0
  179. package/dist/generated/types/discriminatorFormat.d.ts +17 -0
  180. package/dist/generated/types/discriminatorFormat.d.ts.map +1 -0
  181. package/dist/generated/types/discriminatorFormat.js +23 -0
  182. package/dist/generated/types/discriminatorFormat.js.map +1 -0
  183. package/dist/generated/types/index.d.ts +11 -1
  184. package/dist/generated/types/index.d.ts.map +1 -1
  185. package/dist/generated/types/index.js +11 -1
  186. package/dist/generated/types/index.js.map +1 -1
  187. package/dist/generated/types/instructionConstraintsCreated.d.ts +6 -4
  188. package/dist/generated/types/instructionConstraintsCreated.d.ts.map +1 -1
  189. package/dist/generated/types/instructionConstraintsCreated.js +3 -1
  190. package/dist/generated/types/instructionConstraintsCreated.js.map +1 -1
  191. package/dist/generated/types/pdaAllocated.d.ts +24 -0
  192. package/dist/generated/types/pdaAllocated.d.ts.map +1 -0
  193. package/dist/generated/types/pdaAllocated.js +28 -0
  194. package/dist/generated/types/pdaAllocated.js.map +1 -0
  195. package/dist/generated/types/pdaExtended.d.ts +24 -0
  196. package/dist/generated/types/pdaExtended.d.ts.map +1 -0
  197. package/dist/generated/types/pdaExtended.js +28 -0
  198. package/dist/generated/types/pdaExtended.js.map +1 -0
  199. package/dist/generated/types/postAssertionChecked.d.ts +24 -0
  200. package/dist/generated/types/postAssertionChecked.d.ts.map +1 -0
  201. package/dist/generated/types/postAssertionChecked.js +28 -0
  202. package/dist/generated/types/postAssertionChecked.js.map +1 -0
  203. package/dist/generated/types/postAssertionEntry.d.ts +22 -0
  204. package/dist/generated/types/postAssertionEntry.d.ts.map +1 -0
  205. package/dist/generated/types/postAssertionEntry.js +32 -0
  206. package/dist/generated/types/postAssertionEntry.js.map +1 -0
  207. package/dist/generated/types/postAssertionEntryZC.d.ts +54 -0
  208. package/dist/generated/types/postAssertionEntryZC.d.ts.map +1 -0
  209. package/dist/generated/types/postAssertionEntryZC.js +34 -0
  210. package/dist/generated/types/postAssertionEntryZC.js.map +1 -0
  211. package/dist/generated/types/postAssertionsClosed.d.ts +20 -0
  212. package/dist/generated/types/postAssertionsClosed.d.ts.map +1 -0
  213. package/dist/generated/types/postAssertionsClosed.js +24 -0
  214. package/dist/generated/types/postAssertionsClosed.js.map +1 -0
  215. package/dist/generated/types/postAssertionsCreated.d.ts +22 -0
  216. package/dist/generated/types/postAssertionsCreated.d.ts.map +1 -0
  217. package/dist/generated/types/postAssertionsCreated.js +26 -0
  218. package/dist/generated/types/postAssertionsCreated.js.map +1 -0
  219. package/dist/generated/types/sessionFinalized.d.ts +10 -12
  220. package/dist/generated/types/sessionFinalized.d.ts.map +1 -1
  221. package/dist/generated/types/sessionFinalized.js +4 -2
  222. package/dist/generated/types/sessionFinalized.js.map +1 -1
  223. package/dist/generated/types/vaultReactivated.d.ts +2 -2
  224. package/dist/generated/types/vaultReactivated.d.ts.map +1 -1
  225. package/dist/generated/types/vaultReactivated.js +3 -3
  226. package/dist/generated/types/vaultReactivated.js.map +1 -1
  227. package/dist/index.d.ts +3 -3
  228. package/dist/index.d.ts.map +1 -1
  229. package/dist/index.js +5 -3
  230. package/dist/index.js.map +1 -1
  231. package/dist/inscribe.d.ts +1 -1
  232. package/dist/inscribe.d.ts.map +1 -1
  233. package/dist/integrations/compose-errors.d.ts +64 -0
  234. package/dist/integrations/compose-errors.d.ts.map +1 -0
  235. package/dist/integrations/compose-errors.js +105 -0
  236. package/dist/integrations/compose-errors.js.map +1 -0
  237. package/dist/integrations/protocol-handler.d.ts +59 -0
  238. package/dist/integrations/protocol-handler.d.ts.map +1 -0
  239. package/dist/integrations/protocol-handler.js +9 -0
  240. package/dist/integrations/protocol-handler.js.map +1 -0
  241. package/dist/presets.d.ts +10 -4
  242. package/dist/presets.d.ts.map +1 -1
  243. package/dist/presets.js +8 -4
  244. package/dist/presets.js.map +1 -1
  245. package/dist/seal.d.ts +2 -17
  246. package/dist/seal.d.ts.map +1 -1
  247. package/dist/seal.js +18 -62
  248. package/dist/seal.js.map +1 -1
  249. package/dist/security-analytics.d.ts +1 -1
  250. package/dist/security-analytics.d.ts.map +1 -1
  251. package/dist/security-analytics.js +48 -20
  252. package/dist/security-analytics.js.map +1 -1
  253. package/dist/testing/devnet.js +3 -3
  254. package/dist/testing/devnet.js.map +1 -1
  255. package/dist/testing/mock-state.d.ts +2 -0
  256. package/dist/testing/mock-state.d.ts.map +1 -1
  257. package/dist/testing/mock-state.js +6 -2
  258. package/dist/testing/mock-state.js.map +1 -1
  259. package/dist/types.d.ts +16 -4
  260. package/dist/types.d.ts.map +1 -1
  261. package/dist/types.js +15 -4
  262. package/dist/types.js.map +1 -1
  263. package/dist/vault-analytics.d.ts.map +1 -1
  264. package/dist/vault-analytics.js +3 -3
  265. package/dist/vault-analytics.js.map +1 -1
  266. package/package.json +5 -5
  267. package/dist/generated/types/actionType.d.ts +0 -37
  268. package/dist/generated/types/actionType.d.ts.map +0 -1
  269. package/dist/generated/types/actionType.js +0 -43
  270. package/dist/generated/types/actionType.js.map +0 -1
@@ -3,9 +3,17 @@
3
3
  *
4
4
  * Each function is stateless (fetches fresh from RPC), composes existing
5
5
  * SDK functions, and returns raw values with toJSON() for MCP serialization.
6
+ *
7
+ * S14: the five view types are composed by pure `build*` helpers that take a
8
+ * shared {@link OverviewContext}. The existing reads each assemble their own
9
+ * context with minimal fetches; `getOverview` fetches once and shares the
10
+ * context across all helpers so derived values (security posture etc.) are
11
+ * computed exactly once.
6
12
  */
13
+ import { isSome } from "@solana/kit";
14
+ import { toDxError } from "./errors.js";
7
15
  import { resolveVaultStateForOwner, getSpendingHistory, getPendingPolicyForVault, } from "../state-resolver.js";
8
- import { getVaultPnL } from "../balance-tracker.js";
16
+ import { getVaultPnL, getVaultPnLFromState } from "../balance-tracker.js";
9
17
  import { getSecurityPosture } from "../security-analytics.js";
10
18
  import { evaluateAlertConditions } from "../security-analytics.js";
11
19
  import { getAgentProfile } from "../agent-analytics.js";
@@ -40,15 +48,61 @@ function serializeBigints(obj) {
40
48
  }
41
49
  return obj;
42
50
  }
43
- // ─── getVaultState ───────────────────────────────────────────────────────────
44
- export async function getVaultState(rpc, vault, network) {
45
- const [state, pnl] = await Promise.all([
46
- resolveVaultStateForOwner(rpc, vault, undefined, toNet(network)),
47
- getVaultPnL(rpc, vault, toNet(network)),
48
- ]);
49
- const posture = getSecurityPosture(asVaultState(state));
50
- const v = state.vault;
51
- const bal = state.stablecoinBalances;
51
+ /**
52
+ * Default size of the activity window included in `getOverview`. Consumers
53
+ * may override via `GetOverviewOptions.activityLimit`.
54
+ *
55
+ * The value matches `getAgents`' existing per-agent enrichment window so one
56
+ * fetch serves both the overview's activity feed and the agents' last-action
57
+ * fields without inflating RPC cost.
58
+ */
59
+ export const DEFAULT_OVERVIEW_ACTIVITY_LIMIT = 100;
60
+ /**
61
+ * Shared "is this an account-not-found error?" predicate.
62
+ *
63
+ * Both `getPolicy` and `getOverview` treat a missing `PendingPolicyUpdate`
64
+ * account as "no pending update" (not an error). The current Kit doesn't
65
+ * expose a typed `AccountNotFound` SolanaError at this call site, so both
66
+ * paths fall back to substring matching. Extracting it here means the
67
+ * fragility lives in one place and only one site needs to update if Kit
68
+ * ever surfaces a typed variant.
69
+ */
70
+ function isAccountNotFoundError(err) {
71
+ const message = err instanceof Error ? err.message : String(err);
72
+ return (message.includes("could not find") ||
73
+ message.includes("Account does not exist"));
74
+ }
75
+ // ─── Build helpers (pure composition — no RPC) ───────────────────────────────
76
+ // Each helper accepts an OverviewContext and returns one view type. `getOverview`
77
+ // pre-populates memoized derivations (posture/breakdown/alerts) so repeat calls
78
+ // share one computation; existing reads pass a minimal ctx and the helper
79
+ // derives what it needs from `ctx.state`.
80
+ /**
81
+ * Guard for state fields that `resolveVaultStateForOwner` normally guarantees.
82
+ *
83
+ * `state.vault` and `state.policy` are non-null on any success path from the
84
+ * resolver, but consumers that hand-construct an {@link OverviewContext} for
85
+ * testing or custom composition could pass a partial shape. Fail fast with a
86
+ * labeled error instead of a cryptic "cannot read properties of null".
87
+ */
88
+ function requireCtxField(value, field) {
89
+ if (value === null || value === undefined) {
90
+ throw new Error(`[dashboard/reads] OverviewContext.state.${field} is required but missing`);
91
+ }
92
+ return value;
93
+ }
94
+ /**
95
+ * Compose {@link VaultState} from a pre-fetched {@link OverviewContext}.
96
+ *
97
+ * Requires `ctx.state`. Uses `ctx.pnl` when present; otherwise defaults to
98
+ * zero P&L. Uses `ctx.posture` when memoized; otherwise computes from state.
99
+ */
100
+ export function buildVaultState(ctx) {
101
+ const v = requireCtxField(ctx.state.vault, "vault");
102
+ const posture = ctx.posture ?? getSecurityPosture(asVaultState(ctx.state));
103
+ const pnlPercent = ctx.pnl && Number.isFinite(ctx.pnl.pnlPercent) ? ctx.pnl.pnlPercent : 0;
104
+ const pnlAbsolute = ctx.pnl ? ctx.pnl.pnl : 0n;
105
+ const bal = ctx.state.stablecoinBalances;
52
106
  const total = bal.usdc + bal.usdt;
53
107
  const tokens = [
54
108
  ...(bal.usdc > 0n ? [{ mint: "USDC", amount: bal.usdc, decimals: 6 }] : []),
@@ -63,10 +117,12 @@ export async function getVaultState(rpc, vault, network) {
63
117
  : posture.failCount > 0
64
118
  ? "elevated"
65
119
  : "healthy";
120
+ const vaultAddr = ctx.vault;
121
+ const status = (v.status === 0 ? "active" : v.status === 1 ? "frozen" : "closed");
66
122
  return {
67
123
  vault: {
68
- address: vault,
69
- status: v.status === 0 ? "active" : v.status === 1 ? "frozen" : "closed",
124
+ address: vaultAddr,
125
+ status,
70
126
  owner: v.owner,
71
127
  agentCount: v.agents?.length ?? 0,
72
128
  openPositions: v.openPositions,
@@ -74,19 +130,12 @@ export async function getVaultState(rpc, vault, network) {
74
130
  totalFees: v.totalFeesCollected,
75
131
  },
76
132
  balance: { total, tokens },
77
- pnl: {
78
- percent: Number.isFinite(pnl.pnlPercent) ? pnl.pnlPercent : 0,
79
- absolute: pnl.pnl,
80
- },
133
+ pnl: { percent: pnlPercent, absolute: pnlAbsolute },
81
134
  health: { level, alertCount: posture.failCount, checks },
82
135
  toJSON: () => ({
83
136
  vault: {
84
- address: vault,
85
- status: (v.status === 0
86
- ? "active"
87
- : v.status === 1
88
- ? "frozen"
89
- : "closed"),
137
+ address: vaultAddr,
138
+ status,
90
139
  owner: v.owner,
91
140
  agentCount: v.agents?.length ?? 0,
92
141
  openPositions: v.openPositions,
@@ -97,20 +146,25 @@ export async function getVaultState(rpc, vault, network) {
97
146
  total: bs(total),
98
147
  tokens: tokens.map((t) => ({ ...t, amount: bs(t.amount) })),
99
148
  },
100
- pnl: {
101
- percent: Number.isFinite(pnl.pnlPercent) ? pnl.pnlPercent : 0,
102
- absolute: bs(pnl.pnl),
103
- },
149
+ pnl: { percent: pnlPercent, absolute: bs(pnlAbsolute) },
104
150
  health: { level, alertCount: posture.failCount, checks },
105
151
  }),
106
152
  };
107
153
  }
108
- // ─── getAgents ───────────────────────────────────────────────────────────────
109
- export async function getAgents(rpc, vault, network) {
110
- const state = await resolveVaultStateForOwner(rpc, vault, undefined, toNet(network));
111
- const vaultAgents = state.vault.agents;
154
+ /**
155
+ * Compose {@link AgentData}[] from a pre-fetched {@link OverviewContext}.
156
+ *
157
+ * Requires `ctx.state`. Uses `ctx.activity` to populate per-agent last-action
158
+ * and blocked-count fields; when absent, those fields default to empty/zero.
159
+ */
160
+ export function buildAgents(ctx) {
161
+ const state = ctx.state;
162
+ const v = requireCtxField(state.vault, "vault");
163
+ const vaultAgents = v.agents;
112
164
  if (!vaultAgents || vaultAgents.length === 0)
113
165
  return [];
166
+ const activity = ctx.activity ?? [];
167
+ const blockedCutoffMs = Date.now() - 24 * 3600 * 1000;
114
168
  return vaultAgents.map((entry) => {
115
169
  const addr = entry.pubkey;
116
170
  const profile = getAgentProfile(asVaultState(state), addr);
@@ -118,39 +172,52 @@ export async function getAgents(rpc, vault, network) {
118
172
  const spentAmt = budget?.spent24h ?? 0n;
119
173
  const capAmt = budget?.cap ?? 0n;
120
174
  const pct = capAmt > 0n ? Number((spentAmt * 10000n) / capAmt) / 100 : 0;
175
+ // Items are newest-first (getSignaturesForAddress ordering).
176
+ const agentActivity = activity.filter((item) => item.agent !== null && item.agent === addr);
177
+ const last = agentActivity[0];
178
+ const lastActionType = last
179
+ ? mapCategory(last.category ?? "unknown", last.eventType ?? "", last.actionType ?? undefined)
180
+ : "";
181
+ const lastActionProtocol = last?.protocolName ?? "";
182
+ const lastActionTimestamp = last ? last.timestamp * 1000 : 0;
183
+ const blockedCount24h = agentActivity.filter((item) => !item.success && item.timestamp * 1000 >= blockedCutoffMs).length;
121
184
  return {
122
185
  address: addr,
123
186
  status: (profile?.paused ? "paused" : "active"),
124
- permissions: profile?.permissionStrings ?? [],
125
- permissionBitmask: profile?.permissions ?? 0n,
187
+ capabilityLabel: profile?.capabilityLabel ?? "Disabled",
188
+ capability: profile?.capability ?? 0,
126
189
  spending: { amount: spentAmt, limit: capAmt, percent: pct },
127
- lastActionType: "",
128
- lastActionProtocol: "",
129
- lastActionTimestamp: 0,
130
- blockedCount24h: 0,
190
+ lastActionType,
191
+ lastActionProtocol,
192
+ lastActionTimestamp,
193
+ blockedCount24h,
131
194
  toJSON: () => ({
132
195
  address: addr,
133
196
  status: profile?.paused ? "paused" : "active",
134
- permissions: profile?.permissionStrings ?? [],
135
- permissionBitmask: bs(profile?.permissions ?? 0n),
197
+ capabilityLabel: profile?.capabilityLabel ?? "Disabled",
198
+ capability: profile?.capability ?? 0,
136
199
  spending: { amount: bs(spentAmt), limit: bs(capAmt), percent: pct },
137
- lastActionType: "",
138
- lastActionProtocol: "",
139
- lastActionTimestamp: 0,
140
- blockedCount24h: 0,
200
+ lastActionType,
201
+ lastActionProtocol,
202
+ lastActionTimestamp,
203
+ blockedCount24h,
141
204
  }),
142
205
  };
143
206
  });
144
207
  }
145
- // ─── getSpending ─────────────────────────────────────────────────────────────
146
- export async function getSpending(rpc, vault, network) {
147
- const state = await resolveVaultStateForOwner(rpc, vault, undefined, toNet(network));
148
- const breakdown = getSpendingBreakdown(asVaultState(state));
208
+ /**
209
+ * Compose {@link SpendingData} from a pre-fetched {@link OverviewContext}.
210
+ *
211
+ * Requires `ctx.state`. Uses `ctx.breakdown` when memoized.
212
+ */
213
+ export function buildSpending(ctx) {
214
+ const state = ctx.state;
215
+ const breakdown = ctx.breakdown ?? getSpendingBreakdown(asVaultState(state));
149
216
  const nowUnix = BigInt(Math.floor(Date.now() / 1000));
150
217
  const epochs = getSpendingHistory(state.tracker, nowUnix);
151
218
  const chart = epochs.map((e) => ({
152
219
  time: new Date(e.timestamp * 1000).toISOString(),
153
- amount: Number(e.usdAmount),
220
+ amount: Number(e.usdAmount) / 1_000_000,
154
221
  }));
155
222
  const { spent24h: spent, cap, remaining } = state.globalBudget;
156
223
  const percent = cap > 0n ? Number((spent * 10000n) / cap) / 100 : 0;
@@ -184,109 +251,15 @@ export async function getSpending(rpc, vault, network) {
184
251
  }),
185
252
  };
186
253
  }
187
- // ─── getActivity ─────────────────────────────────────────────────────────────
188
- export async function getActivity(rpc, vault, network, filters) {
189
- const limit = filters?.limit ?? 50;
190
- const items = await getVaultActivity(rpc, vault, limit, toNet(network));
191
- let rows = items.map((item, i) => {
192
- const cat = item.category ?? "unknown";
193
- const evt = item.eventType ?? "";
194
- const act = item.actionType ?? undefined;
195
- const type = mapCategory(cat, evt, act);
196
- const amt = item.amount ?? 0n;
197
- const sig = item.txSignature || `evt-${item.timestamp}-${item.eventType}`;
198
- return {
199
- id: sig,
200
- timestamp: item.timestamp * 1000,
201
- type,
202
- protocol: item.protocolName || "",
203
- protocolId: item.protocol || "",
204
- agent: item.agent || "",
205
- amount: amt,
206
- status: item.success ? "approved" : "blocked",
207
- reason: item.success ? undefined : item.description,
208
- txSignature: item.txSignature,
209
- toJSON: () => ({
210
- id: sig,
211
- timestamp: item.timestamp * 1000,
212
- type,
213
- protocol: item.protocolName || "",
214
- protocolId: item.protocol || "",
215
- agent: item.agent || "",
216
- amount: bs(amt),
217
- status: item.success ? "approved" : "blocked",
218
- reason: item.success ? undefined : item.description,
219
- txSignature: item.txSignature,
220
- }),
221
- };
222
- });
223
- if (filters?.agent)
224
- rows = rows.filter((r) => r.agent === filters.agent);
225
- if (filters?.protocol)
226
- rows = rows.filter((r) => r.protocolId === filters.protocol || r.protocol === filters.protocol);
227
- if (filters?.status)
228
- rows = rows.filter((r) => r.status === filters.status);
229
- if (filters?.timeRange) {
230
- const cutoff = Date.now() - rangeToMs(filters.timeRange);
231
- rows = rows.filter((r) => r.timestamp >= cutoff);
232
- }
233
- const approved = rows.filter((r) => r.status === "approved").length;
234
- const blocked = rows.length - approved;
235
- const volume = rows.reduce((s, r) => s + r.amount, 0n);
236
- return {
237
- rows,
238
- summary: { total: rows.length, approved, blocked, volume },
239
- toJSON: () => ({
240
- rows: rows.map((r) => r.toJSON()),
241
- summary: { total: rows.length, approved, blocked, volume: bs(volume) },
242
- }),
243
- };
244
- }
245
- function mapCategory(cat, evt, actionType) {
246
- if (cat === "trade") {
247
- // ActionAuthorized carries actionType to distinguish swap vs lend vs perps
248
- if (actionType) {
249
- const at = actionType.toLowerCase();
250
- if (at.includes("lend") ||
251
- at.includes("deposit") ||
252
- at.includes("withdraw"))
253
- return "lend";
254
- if (at.includes("open") ||
255
- at === "openposition" ||
256
- at === "swapandopenposition")
257
- return "open_position";
258
- if (at.includes("close") || at === "closeposition")
259
- return "close_position";
260
- }
261
- if (evt.includes("Open"))
262
- return "open_position";
263
- if (evt.includes("Close"))
264
- return "close_position";
265
- return "swap";
266
- }
267
- if (cat === "deposit")
268
- return "deposit";
269
- if (cat === "withdrawal")
270
- return "withdraw";
271
- if (evt === "AgentTransferExecuted")
272
- return "transfer";
273
- return "swap";
274
- }
275
- function rangeToMs(r) {
276
- const map = {
277
- "1h": 3600000,
278
- "6h": 21600000,
279
- "24h": 86400000,
280
- "7d": 604800000,
281
- "30d": 2592000000,
282
- };
283
- return map[r] ?? 86400000;
284
- }
285
- // ─── getHealth ───────────────────────────────────────────────────────────────
286
- export async function getHealth(rpc, vault, network) {
287
- const state = await resolveVaultStateForOwner(rpc, vault, undefined, toNet(network));
288
- const posture = getSecurityPosture(asVaultState(state));
289
- const alerts = evaluateAlertConditions(state, vault);
254
+ /**
255
+ * Compose {@link HealthData} from a pre-fetched {@link OverviewContext}.
256
+ *
257
+ * Requires `ctx.state` + `ctx.vault` (for alert evaluation). Uses `ctx.posture`
258
+ * and `ctx.alerts` when memoized.
259
+ */
260
+ export function buildHealth(ctx) {
261
+ const posture = ctx.posture ?? getSecurityPosture(asVaultState(ctx.state));
262
+ const alerts = ctx.alerts ?? evaluateAlertConditions(ctx.state, ctx.vault);
290
263
  const level = posture.criticalFailures.length > 0
291
264
  ? "critical"
292
265
  : posture.failCount > 0
@@ -320,21 +293,16 @@ export async function getHealth(rpc, vault, network) {
320
293
  }),
321
294
  };
322
295
  }
323
- // ─── getPolicy ───────────────────────────────────────────────────────────────
324
- export async function getPolicy(rpc, vault, network) {
325
- const [state, pendingPolicy] = await Promise.all([
326
- resolveVaultStateForOwner(rpc, vault, undefined, toNet(network)),
327
- getPendingPolicyForVault(rpc, vault).catch((err) => {
328
- // Account-not-found is expected (no pending update) — return null.
329
- // Re-throw RPC errors so they're not silently swallowed.
330
- if (err?.message?.includes("could not find") ||
331
- err?.message?.includes("Account does not exist")) {
332
- return null;
333
- }
334
- throw err;
335
- }),
336
- ]);
337
- const p = state.policy;
296
+ /**
297
+ * Compose {@link PolicyData} from a pre-fetched {@link OverviewContext}.
298
+ *
299
+ * Requires `ctx.state`. Uses `ctx.pendingPolicy` (which may be `null` to mean
300
+ * "confirmed no pending update"); when `undefined` treats as no pending update.
301
+ */
302
+ export function buildPolicy(ctx) {
303
+ const state = ctx.state;
304
+ const pendingPolicy = ctx.pendingPolicy ?? null;
305
+ const p = requireCtxField(state.policy, "policy");
338
306
  const protocols = (p.protocols || []);
339
307
  const approvedApps = protocols.map((addr) => ({
340
308
  name: resolveProtocolName(addr),
@@ -357,8 +325,40 @@ export async function getPolicy(rpc, vault, network) {
357
325
  const executesAtSec = Number(pp.executesAt ?? 0);
358
326
  const appliesAt = Number.isFinite(executesAtSec) ? executesAtSec * 1000 : 0;
359
327
  const nowSec = Math.floor(Date.now() / 1000);
328
+ // Decode each Option<T> field from PendingPolicyUpdate. Only Some fields
329
+ // land in `changes`. 14 fields total — every timelockable PolicyConfig field.
330
+ // Source: programs/sigil/src/state/pending_policy.rs
331
+ const changes = {};
332
+ if (isSome(pp.dailySpendingCapUsd))
333
+ changes.dailyCap = pp.dailySpendingCapUsd.value;
334
+ if (isSome(pp.maxTransactionAmountUsd))
335
+ changes.maxPerTrade = pp.maxTransactionAmountUsd.value;
336
+ if (isSome(pp.protocols))
337
+ changes.approvedApps = pp.protocols.value;
338
+ if (isSome(pp.protocolMode))
339
+ changes.protocolMode = modeMap[pp.protocolMode.value] || "unrestricted";
340
+ if (isSome(pp.hasProtocolCaps))
341
+ changes.hasProtocolCaps = pp.hasProtocolCaps.value;
342
+ if (isSome(pp.protocolCaps))
343
+ changes.protocolCaps = pp.protocolCaps.value;
344
+ if (isSome(pp.canOpenPositions))
345
+ changes.canOpenPositions = pp.canOpenPositions.value;
346
+ if (isSome(pp.maxConcurrentPositions))
347
+ changes.maxConcurrentPositions = pp.maxConcurrentPositions.value;
348
+ if (isSome(pp.maxSlippageBps))
349
+ changes.maxSlippageBps = pp.maxSlippageBps.value;
350
+ if (isSome(pp.maxLeverageBps))
351
+ changes.leverageLimit = pp.maxLeverageBps.value;
352
+ if (isSome(pp.allowedDestinations))
353
+ changes.allowedDestinations = pp.allowedDestinations.value;
354
+ if (isSome(pp.developerFeeRate))
355
+ changes.developerFeeRate = pp.developerFeeRate.value;
356
+ if (isSome(pp.sessionExpirySlots))
357
+ changes.sessionExpirySlots = pp.sessionExpirySlots.value;
358
+ if (isSome(pp.timelockDuration))
359
+ changes.timelock = Number(pp.timelockDuration.value);
360
360
  pendingUpdate = {
361
- changes: {},
361
+ changes,
362
362
  appliesAt,
363
363
  canApply: executesAtSec > 0 && executesAtSec <= nowSec,
364
364
  canCancel: true,
@@ -408,4 +408,307 @@ export async function getPolicy(rpc, vault, network) {
408
408
  }),
409
409
  };
410
410
  }
411
+ /**
412
+ * Map raw {@link VaultActivityItem}[] to {@link ActivityRow}[] with stable
413
+ * derived IDs and toJSON serializers. Pure — no filtering applied.
414
+ *
415
+ * Both `getActivity` (which then filters) and `getOverview` (which returns
416
+ * unfiltered) consume the output.
417
+ */
418
+ export function buildActivityRows(items) {
419
+ return items.map((item) => {
420
+ const cat = item.category ?? "unknown";
421
+ const evt = item.eventType ?? "";
422
+ const act = item.actionType ?? undefined;
423
+ const posEffect = item.positionEffect ?? undefined;
424
+ const type = mapCategory(cat, evt, act, posEffect);
425
+ const amt = item.amount ?? 0n;
426
+ const sig = item.txSignature || `evt-${item.timestamp}-${item.eventType}`;
427
+ return {
428
+ id: sig,
429
+ timestamp: item.timestamp * 1000,
430
+ type,
431
+ protocol: item.protocolName || "",
432
+ protocolId: item.protocol || "",
433
+ agent: item.agent || "",
434
+ amount: amt,
435
+ status: item.success ? "approved" : "blocked",
436
+ reason: item.success ? undefined : item.description,
437
+ txSignature: item.txSignature,
438
+ toJSON: () => ({
439
+ id: sig,
440
+ timestamp: item.timestamp * 1000,
441
+ type,
442
+ protocol: item.protocolName || "",
443
+ protocolId: item.protocol || "",
444
+ agent: item.agent || "",
445
+ amount: bs(amt),
446
+ status: item.success ? "approved" : "blocked",
447
+ reason: item.success ? undefined : item.description,
448
+ txSignature: item.txSignature,
449
+ }),
450
+ };
451
+ });
452
+ }
453
+ // ─── getVaultState ───────────────────────────────────────────────────────────
454
+ export async function getVaultState(rpc, vault, network) {
455
+ try {
456
+ const [state, pnl] = await Promise.all([
457
+ resolveVaultStateForOwner(rpc, vault, undefined, toNet(network)),
458
+ getVaultPnL(rpc, vault, toNet(network)),
459
+ ]);
460
+ return buildVaultState({ vault, state, pnl });
461
+ }
462
+ catch (err) {
463
+ throw toDxError(err, "OwnerClient.getVaultState");
464
+ }
465
+ }
466
+ // ─── getAgents ───────────────────────────────────────────────────────────────
467
+ export async function getAgents(rpc, vault, network) {
468
+ try {
469
+ // Single getVaultActivity call is shared across all agents (N+1 prevention).
470
+ // Activity is enrichment, so a fetch failure degrades gracefully to empty
471
+ // last-action fields. Window: 100 most recent signatures — large enough to
472
+ // surface last action for low-volume agents without inflating RPC cost.
473
+ //
474
+ // Fix for docs/SECURITY-FINDINGS-2026-04-07.md Finding 5: the previous
475
+ // `.catch(() => [])` swallowed activity-fetch failures silently. If Helius
476
+ // started rate-limiting getSignaturesForAddress, every dashboard call would
477
+ // show "last action: never" for every agent forever and nobody would
478
+ // notice. Graceful degradation is still the right behavior (activity is
479
+ // enrichment, not core), but it must be observable — console.warn is the
480
+ // minimum bar.
481
+ const [state, activity] = await Promise.all([
482
+ resolveVaultStateForOwner(rpc, vault, undefined, toNet(network)),
483
+ getVaultActivity(rpc, vault, 100, toNet(network)).catch((err) => {
484
+ // eslint-disable-next-line no-console
485
+ console.warn("[OwnerClient.getAgents] activity enrichment failed — falling back to empty last-action fields:", err instanceof Error ? err.message : String(err));
486
+ return [];
487
+ }),
488
+ ]);
489
+ return buildAgents({ vault, state, activity });
490
+ }
491
+ catch (err) {
492
+ throw toDxError(err, "OwnerClient.getAgents");
493
+ }
494
+ }
495
+ // ─── getSpending ─────────────────────────────────────────────────────────────
496
+ export async function getSpending(rpc, vault, network) {
497
+ try {
498
+ const state = await resolveVaultStateForOwner(rpc, vault, undefined, toNet(network));
499
+ return buildSpending({ vault, state });
500
+ }
501
+ catch (err) {
502
+ throw toDxError(err, "OwnerClient.getSpending");
503
+ }
504
+ }
505
+ // ─── getActivity ─────────────────────────────────────────────────────────────
506
+ export async function getActivity(rpc, vault, network, filters) {
507
+ try {
508
+ const limit = filters?.limit ?? 50;
509
+ const items = await getVaultActivity(rpc, vault, limit, toNet(network));
510
+ let rows = buildActivityRows(items);
511
+ if (filters?.agent)
512
+ rows = rows.filter((r) => r.agent === filters.agent);
513
+ if (filters?.protocol)
514
+ rows = rows.filter((r) => r.protocolId === filters.protocol || r.protocol === filters.protocol);
515
+ if (filters?.status)
516
+ rows = rows.filter((r) => r.status === filters.status);
517
+ if (filters?.type)
518
+ rows = rows.filter((r) => r.type === filters.type);
519
+ if (filters?.timeRange) {
520
+ const cutoff = Date.now() - rangeToMs(filters.timeRange);
521
+ rows = rows.filter((r) => r.timestamp >= cutoff);
522
+ }
523
+ const approved = rows.filter((r) => r.status === "approved").length;
524
+ const blocked = rows.length - approved;
525
+ const volume = rows.reduce((s, r) => s + r.amount, 0n);
526
+ return {
527
+ rows,
528
+ summary: { total: rows.length, approved, blocked, volume },
529
+ toJSON: () => ({
530
+ rows: rows.map((r) => r.toJSON()),
531
+ summary: { total: rows.length, approved, blocked, volume: bs(volume) },
532
+ }),
533
+ };
534
+ }
535
+ catch (err) {
536
+ throw toDxError(err, "OwnerClient.getActivity");
537
+ }
538
+ }
539
+ function mapCategory(cat, evt, actionType, positionEffect) {
540
+ if (cat === "trade") {
541
+ // v6 events carry positionEffect ("increment"/"decrement"/"none") instead
542
+ // of actionType. Prefer it when present — actionType is null for post-
543
+ // ActionType-elimination events.
544
+ if (positionEffect === "increment")
545
+ return "open_position";
546
+ if (positionEffect === "decrement")
547
+ return "close_position";
548
+ // Legacy v5 event path: actionType carries swap vs lend vs perps detail.
549
+ if (actionType) {
550
+ const at = actionType.toLowerCase();
551
+ if (at.includes("lend") ||
552
+ at.includes("deposit") ||
553
+ at.includes("withdraw"))
554
+ return "lend";
555
+ if (at.includes("open") ||
556
+ at === "openposition" ||
557
+ at === "swapandopenposition")
558
+ return "open_position";
559
+ if (at.includes("close") || at === "closeposition")
560
+ return "close_position";
561
+ }
562
+ if (evt.includes("Open"))
563
+ return "open_position";
564
+ if (evt.includes("Close"))
565
+ return "close_position";
566
+ return "swap";
567
+ }
568
+ if (cat === "deposit")
569
+ return "deposit";
570
+ if (cat === "withdrawal")
571
+ return "withdraw";
572
+ if (evt === "AgentTransferExecuted")
573
+ return "transfer";
574
+ return "swap";
575
+ }
576
+ function rangeToMs(r) {
577
+ const map = {
578
+ "1h": 3600000,
579
+ "6h": 21600000,
580
+ "24h": 86400000,
581
+ "7d": 604800000,
582
+ "30d": 2592000000,
583
+ };
584
+ return map[r] ?? 86400000;
585
+ }
586
+ // ─── getHealth ───────────────────────────────────────────────────────────────
587
+ export async function getHealth(rpc, vault, network) {
588
+ try {
589
+ const state = await resolveVaultStateForOwner(rpc, vault, undefined, toNet(network));
590
+ return buildHealth({ vault, state });
591
+ }
592
+ catch (err) {
593
+ throw toDxError(err, "OwnerClient.getHealth");
594
+ }
595
+ }
596
+ // ─── getPolicy ───────────────────────────────────────────────────────────────
597
+ export async function getPolicy(rpc, vault, network) {
598
+ try {
599
+ const [state, pendingPolicy] = await Promise.all([
600
+ resolveVaultStateForOwner(rpc, vault, undefined, toNet(network)),
601
+ getPendingPolicyForVault(rpc, vault).catch((err) => {
602
+ // Account-not-found is expected (no pending update) — return null.
603
+ // Re-throw RPC errors so they're not silently swallowed.
604
+ if (isAccountNotFoundError(err))
605
+ return null;
606
+ throw err;
607
+ }),
608
+ ]);
609
+ return buildPolicy({ vault, state, pendingPolicy });
610
+ }
611
+ catch (err) {
612
+ throw toDxError(err, "OwnerClient.getPolicy");
613
+ }
614
+ }
615
+ // ─── getOverview (S14) ───────────────────────────────────────────────────────
616
+ /**
617
+ * Single-call overview bundle — resolves vault state once, composes all five
618
+ * view types (vault, agents, spending, health, policy) plus a raw activity
619
+ * list, with PnL derived from the resolved state (no duplicate resolve).
620
+ *
621
+ * **Actual RPC shape.** Calling the five individual reads duplicates the
622
+ * vault-state resolution up to five times. `getOverview` resolves state
623
+ * exactly once and computes PnL from it via {@link getVaultPnLFromState}.
624
+ * The activity fetch is independent: `getVaultActivity(limit)` issues one
625
+ * `getSignaturesForAddress` followed by up to `limit` sequential
626
+ * `getTransaction` calls, so the wall-time cost of the activity feed
627
+ * dominates regardless of this method. Net savings vs. five separate reads:
628
+ * state resolution count drops from ~5 → 1.
629
+ *
630
+ * Activity is **unfiltered**. For filtered activity, call {@link getActivity}
631
+ * with `ActivityFilters`.
632
+ *
633
+ * Graceful degradation: activity fetch failure degrades to empty activity
634
+ * (same observable pattern as `getAgents`, documented in
635
+ * `docs/SECURITY-FINDINGS-2026-04-07.md` Finding 5); pending-policy
636
+ * account-not-found is treated as "no pending update" (same as `getPolicy`).
637
+ * **PnL and state-resolution errors are NOT degraded** and propagate via
638
+ * `toDxError`. A pending-policy error that is NOT account-not-found (e.g.
639
+ * network failure) also propagates — it is NOT treated as "no pending
640
+ * update", even on the `includeActivity: false` lightweight path.
641
+ */
642
+ export async function getOverview(rpc, vault, network, options) {
643
+ try {
644
+ const includeActivity = options?.includeActivity ?? true;
645
+ const activityLimit = options?.activityLimit ?? DEFAULT_OVERVIEW_ACTIVITY_LIMIT;
646
+ const net = toNet(network);
647
+ // Fan out every independent fetch in one Promise.all. State resolution,
648
+ // activity, and pending-policy have no cross-dependency, so wall time
649
+ // collapses to the slowest of the three. PnL is derived from state
650
+ // synchronously after — one state resolve, zero duplication.
651
+ const [state, activity, pendingPolicy] = await Promise.all([
652
+ resolveVaultStateForOwner(rpc, vault, undefined, net),
653
+ includeActivity
654
+ ? getVaultActivity(rpc, vault, activityLimit, net).catch((err) => {
655
+ // Same graceful-degradation pattern as getAgents
656
+ // (docs/SECURITY-FINDINGS-2026-04-07.md Finding 5): activity is
657
+ // enrichment, not core, but the failure must be observable.
658
+ // eslint-disable-next-line no-console
659
+ console.warn("[OwnerClient.getOverview] activity fetch failed — falling back to empty:", err instanceof Error ? err.message : String(err));
660
+ return [];
661
+ })
662
+ : Promise.resolve(undefined),
663
+ getPendingPolicyForVault(rpc, vault).catch((err) => {
664
+ if (isAccountNotFoundError(err))
665
+ return null;
666
+ throw err;
667
+ }),
668
+ ]);
669
+ // PnL is pure from resolved state — no extra RPC.
670
+ const pnl = getVaultPnLFromState(state);
671
+ // Compute the three state-derived values exactly once and memoize on ctx.
672
+ // Every build* helper reads these via the `ctx.field ?? derive()` fallback
673
+ // so the memoized value short-circuits re-derivation.
674
+ const posture = getSecurityPosture(asVaultState(state));
675
+ const breakdown = getSpendingBreakdown(asVaultState(state));
676
+ const alerts = evaluateAlertConditions(state, vault);
677
+ const ctx = {
678
+ vault,
679
+ state,
680
+ pnl,
681
+ activity,
682
+ pendingPolicy,
683
+ posture,
684
+ breakdown,
685
+ alerts,
686
+ };
687
+ const vaultView = buildVaultState(ctx);
688
+ const agentsView = buildAgents(ctx);
689
+ const spendingView = buildSpending(ctx);
690
+ const healthView = buildHealth(ctx);
691
+ const policyView = buildPolicy(ctx);
692
+ const activityRows = buildActivityRows(activity ?? []);
693
+ return {
694
+ vault: vaultView,
695
+ agents: agentsView,
696
+ spending: spendingView,
697
+ health: healthView,
698
+ policy: policyView,
699
+ activity: activityRows,
700
+ toJSON: () => ({
701
+ vault: vaultView.toJSON(),
702
+ agents: agentsView.map((a) => a.toJSON()),
703
+ spending: spendingView.toJSON(),
704
+ health: healthView.toJSON(),
705
+ policy: policyView.toJSON(),
706
+ activity: activityRows.map((r) => r.toJSON()),
707
+ }),
708
+ };
709
+ }
710
+ catch (err) {
711
+ throw toDxError(err, "OwnerClient.getOverview");
712
+ }
713
+ }
411
714
  //# sourceMappingURL=reads.js.map