@usesigil/kit 0.2.3 → 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.
- package/dist/balance-tracker.d.ts +20 -0
- package/dist/balance-tracker.d.ts.map +1 -1
- package/dist/balance-tracker.js +18 -5
- package/dist/balance-tracker.js.map +1 -1
- package/dist/dashboard/index.d.ts +18 -2
- package/dist/dashboard/index.d.ts.map +1 -1
- package/dist/dashboard/index.js +26 -0
- package/dist/dashboard/index.js.map +1 -1
- package/dist/dashboard/reads.d.ts +86 -1
- package/dist/dashboard/reads.d.ts.map +1 -1
- package/dist/dashboard/reads.js +520 -328
- package/dist/dashboard/reads.js.map +1 -1
- package/dist/dashboard/types.d.ts +110 -0
- package/dist/dashboard/types.d.ts.map +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -1
- package/dist/index.js.map +1 -1
- package/package.json +2 -2
package/dist/dashboard/reads.js
CHANGED
|
@@ -3,11 +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
|
*/
|
|
7
13
|
import { isSome } from "@solana/kit";
|
|
8
14
|
import { toDxError } from "./errors.js";
|
|
9
15
|
import { resolveVaultStateForOwner, getSpendingHistory, getPendingPolicyForVault, } from "../state-resolver.js";
|
|
10
|
-
import { getVaultPnL } from "../balance-tracker.js";
|
|
16
|
+
import { getVaultPnL, getVaultPnLFromState } from "../balance-tracker.js";
|
|
11
17
|
import { getSecurityPosture } from "../security-analytics.js";
|
|
12
18
|
import { evaluateAlertConditions } from "../security-analytics.js";
|
|
13
19
|
import { getAgentProfile } from "../agent-analytics.js";
|
|
@@ -42,75 +48,416 @@ function serializeBigints(obj) {
|
|
|
42
48
|
}
|
|
43
49
|
return obj;
|
|
44
50
|
}
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
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;
|
|
106
|
+
const total = bal.usdc + bal.usdt;
|
|
107
|
+
const tokens = [
|
|
108
|
+
...(bal.usdc > 0n ? [{ mint: "USDC", amount: bal.usdc, decimals: 6 }] : []),
|
|
109
|
+
...(bal.usdt > 0n ? [{ mint: "USDT", amount: bal.usdt, decimals: 6 }] : []),
|
|
110
|
+
];
|
|
111
|
+
const checks = posture.checks.map((c) => ({
|
|
112
|
+
name: c.id,
|
|
113
|
+
passed: c.passed,
|
|
114
|
+
}));
|
|
115
|
+
const level = posture.criticalFailures.length > 0
|
|
116
|
+
? "critical"
|
|
117
|
+
: posture.failCount > 0
|
|
118
|
+
? "elevated"
|
|
119
|
+
: "healthy";
|
|
120
|
+
const vaultAddr = ctx.vault;
|
|
121
|
+
const status = (v.status === 0 ? "active" : v.status === 1 ? "frozen" : "closed");
|
|
122
|
+
return {
|
|
123
|
+
vault: {
|
|
124
|
+
address: vaultAddr,
|
|
125
|
+
status,
|
|
126
|
+
owner: v.owner,
|
|
127
|
+
agentCount: v.agents?.length ?? 0,
|
|
128
|
+
openPositions: v.openPositions,
|
|
129
|
+
totalVolume: v.totalVolume,
|
|
130
|
+
totalFees: v.totalFeesCollected,
|
|
131
|
+
},
|
|
132
|
+
balance: { total, tokens },
|
|
133
|
+
pnl: { percent: pnlPercent, absolute: pnlAbsolute },
|
|
134
|
+
health: { level, alertCount: posture.failCount, checks },
|
|
135
|
+
toJSON: () => ({
|
|
74
136
|
vault: {
|
|
75
|
-
address:
|
|
76
|
-
status
|
|
137
|
+
address: vaultAddr,
|
|
138
|
+
status,
|
|
77
139
|
owner: v.owner,
|
|
78
140
|
agentCount: v.agents?.length ?? 0,
|
|
79
141
|
openPositions: v.openPositions,
|
|
80
|
-
totalVolume: v.totalVolume,
|
|
81
|
-
totalFees: v.totalFeesCollected,
|
|
142
|
+
totalVolume: bs(v.totalVolume),
|
|
143
|
+
totalFees: bs(v.totalFeesCollected),
|
|
82
144
|
},
|
|
83
|
-
balance: {
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
absolute: pnl.pnl,
|
|
145
|
+
balance: {
|
|
146
|
+
total: bs(total),
|
|
147
|
+
tokens: tokens.map((t) => ({ ...t, amount: bs(t.amount) })),
|
|
87
148
|
},
|
|
149
|
+
pnl: { percent: pnlPercent, absolute: bs(pnlAbsolute) },
|
|
88
150
|
health: { level, alertCount: posture.failCount, checks },
|
|
151
|
+
}),
|
|
152
|
+
};
|
|
153
|
+
}
|
|
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;
|
|
164
|
+
if (!vaultAgents || vaultAgents.length === 0)
|
|
165
|
+
return [];
|
|
166
|
+
const activity = ctx.activity ?? [];
|
|
167
|
+
const blockedCutoffMs = Date.now() - 24 * 3600 * 1000;
|
|
168
|
+
return vaultAgents.map((entry) => {
|
|
169
|
+
const addr = entry.pubkey;
|
|
170
|
+
const profile = getAgentProfile(asVaultState(state), addr);
|
|
171
|
+
const budget = state.allAgentBudgets.get(addr);
|
|
172
|
+
const spentAmt = budget?.spent24h ?? 0n;
|
|
173
|
+
const capAmt = budget?.cap ?? 0n;
|
|
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;
|
|
184
|
+
return {
|
|
185
|
+
address: addr,
|
|
186
|
+
status: (profile?.paused ? "paused" : "active"),
|
|
187
|
+
capabilityLabel: profile?.capabilityLabel ?? "Disabled",
|
|
188
|
+
capability: profile?.capability ?? 0,
|
|
189
|
+
spending: { amount: spentAmt, limit: capAmt, percent: pct },
|
|
190
|
+
lastActionType,
|
|
191
|
+
lastActionProtocol,
|
|
192
|
+
lastActionTimestamp,
|
|
193
|
+
blockedCount24h,
|
|
89
194
|
toJSON: () => ({
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
openPositions: v.openPositions,
|
|
100
|
-
totalVolume: bs(v.totalVolume),
|
|
101
|
-
totalFees: bs(v.totalFeesCollected),
|
|
102
|
-
},
|
|
103
|
-
balance: {
|
|
104
|
-
total: bs(total),
|
|
105
|
-
tokens: tokens.map((t) => ({ ...t, amount: bs(t.amount) })),
|
|
106
|
-
},
|
|
107
|
-
pnl: {
|
|
108
|
-
percent: Number.isFinite(pnl.pnlPercent) ? pnl.pnlPercent : 0,
|
|
109
|
-
absolute: bs(pnl.pnl),
|
|
110
|
-
},
|
|
111
|
-
health: { level, alertCount: posture.failCount, checks },
|
|
195
|
+
address: addr,
|
|
196
|
+
status: profile?.paused ? "paused" : "active",
|
|
197
|
+
capabilityLabel: profile?.capabilityLabel ?? "Disabled",
|
|
198
|
+
capability: profile?.capability ?? 0,
|
|
199
|
+
spending: { amount: bs(spentAmt), limit: bs(capAmt), percent: pct },
|
|
200
|
+
lastActionType,
|
|
201
|
+
lastActionProtocol,
|
|
202
|
+
lastActionTimestamp,
|
|
203
|
+
blockedCount24h,
|
|
112
204
|
}),
|
|
113
205
|
};
|
|
206
|
+
});
|
|
207
|
+
}
|
|
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));
|
|
216
|
+
const nowUnix = BigInt(Math.floor(Date.now() / 1000));
|
|
217
|
+
const epochs = getSpendingHistory(state.tracker, nowUnix);
|
|
218
|
+
const chart = epochs.map((e) => ({
|
|
219
|
+
time: new Date(e.timestamp * 1000).toISOString(),
|
|
220
|
+
amount: Number(e.usdAmount) / 1_000_000,
|
|
221
|
+
}));
|
|
222
|
+
const { spent24h: spent, cap, remaining } = state.globalBudget;
|
|
223
|
+
const percent = cap > 0n ? Number((spent * 10000n) / cap) / 100 : 0;
|
|
224
|
+
const velocityPerMs = spent > 0n ? Number(spent) / (24 * 3600 * 1000) : 0;
|
|
225
|
+
const rundown = velocityPerMs > 0 && remaining > 0n
|
|
226
|
+
? Math.floor(Number(remaining) / velocityPerMs)
|
|
227
|
+
: 0;
|
|
228
|
+
const protoBreak = breakdown.byProtocol.map((p) => ({
|
|
229
|
+
name: resolveProtocolName(p.protocol),
|
|
230
|
+
programId: p.protocol,
|
|
231
|
+
amount: p.spent24h,
|
|
232
|
+
percent: p.utilization,
|
|
233
|
+
}));
|
|
234
|
+
return {
|
|
235
|
+
global: { today: spent, cap, remaining, percent, rundownMs: rundown },
|
|
236
|
+
chart,
|
|
237
|
+
protocolBreakdown: protoBreak,
|
|
238
|
+
toJSON: () => ({
|
|
239
|
+
global: {
|
|
240
|
+
today: bs(spent),
|
|
241
|
+
cap: bs(cap),
|
|
242
|
+
remaining: bs(remaining),
|
|
243
|
+
percent,
|
|
244
|
+
rundownMs: rundown,
|
|
245
|
+
},
|
|
246
|
+
chart,
|
|
247
|
+
protocolBreakdown: protoBreak.map((p) => ({
|
|
248
|
+
...p,
|
|
249
|
+
amount: bs(p.amount),
|
|
250
|
+
})),
|
|
251
|
+
}),
|
|
252
|
+
};
|
|
253
|
+
}
|
|
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);
|
|
263
|
+
const level = posture.criticalFailures.length > 0
|
|
264
|
+
? "critical"
|
|
265
|
+
: posture.failCount > 0
|
|
266
|
+
? "elevated"
|
|
267
|
+
: "healthy";
|
|
268
|
+
const critAlerts = alerts.filter((a) => a.severity === "critical");
|
|
269
|
+
const lastBlock = critAlerts.length > 0
|
|
270
|
+
? {
|
|
271
|
+
agent: critAlerts[0].agentAddress || "",
|
|
272
|
+
reason: critAlerts[0].title,
|
|
273
|
+
amount: 0n,
|
|
274
|
+
timestamp: Date.now(),
|
|
275
|
+
}
|
|
276
|
+
: undefined;
|
|
277
|
+
const checks = posture.checks.map((c) => ({
|
|
278
|
+
name: c.id,
|
|
279
|
+
passed: c.passed,
|
|
280
|
+
}));
|
|
281
|
+
return {
|
|
282
|
+
level,
|
|
283
|
+
blockedCount24h: critAlerts.length,
|
|
284
|
+
checks,
|
|
285
|
+
lastBlock,
|
|
286
|
+
toJSON: () => ({
|
|
287
|
+
level,
|
|
288
|
+
blockedCount24h: critAlerts.length,
|
|
289
|
+
checks,
|
|
290
|
+
lastBlock: lastBlock
|
|
291
|
+
? { ...lastBlock, amount: bs(lastBlock.amount) }
|
|
292
|
+
: undefined,
|
|
293
|
+
}),
|
|
294
|
+
};
|
|
295
|
+
}
|
|
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");
|
|
306
|
+
const protocols = (p.protocols || []);
|
|
307
|
+
const approvedApps = protocols.map((addr) => ({
|
|
308
|
+
name: resolveProtocolName(addr),
|
|
309
|
+
programId: addr,
|
|
310
|
+
}));
|
|
311
|
+
const modeMap = {
|
|
312
|
+
0: "unrestricted",
|
|
313
|
+
1: "whitelist",
|
|
314
|
+
2: "blacklist",
|
|
315
|
+
};
|
|
316
|
+
const dailyCap = p.dailySpendingCapUsd;
|
|
317
|
+
const maxPerTrade = p.maxTransactionSizeUsd ?? 0n;
|
|
318
|
+
const protocolCaps = (p.protocolCaps || []);
|
|
319
|
+
const sessionExpiry = p.sessionExpirySlots;
|
|
320
|
+
const policyVer = (p.policyVersion ?? 0n);
|
|
321
|
+
const timelockSec = Number(p.timelockDuration);
|
|
322
|
+
let pendingUpdate;
|
|
323
|
+
if (pendingPolicy) {
|
|
324
|
+
const pp = pendingPolicy;
|
|
325
|
+
const executesAtSec = Number(pp.executesAt ?? 0);
|
|
326
|
+
const appliesAt = Number.isFinite(executesAtSec) ? executesAtSec * 1000 : 0;
|
|
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
|
+
pendingUpdate = {
|
|
361
|
+
changes,
|
|
362
|
+
appliesAt,
|
|
363
|
+
canApply: executesAtSec > 0 && executesAtSec <= nowSec,
|
|
364
|
+
canCancel: true,
|
|
365
|
+
};
|
|
366
|
+
}
|
|
367
|
+
return {
|
|
368
|
+
dailyCap,
|
|
369
|
+
maxPerTrade,
|
|
370
|
+
approvedApps,
|
|
371
|
+
protocolMode: modeMap[p.protocolMode] || "unrestricted",
|
|
372
|
+
hasProtocolCaps: p.hasProtocolCaps,
|
|
373
|
+
protocolCaps,
|
|
374
|
+
canOpenPositions: p.canOpenPositions,
|
|
375
|
+
maxConcurrentPositions: p.maxConcurrentPositions,
|
|
376
|
+
maxSlippageBps: p.maxSlippageBps,
|
|
377
|
+
leverageLimitBps: p.maxLeverageBps,
|
|
378
|
+
allowedDestinations: (p.allowedDestinations || []),
|
|
379
|
+
developerFeeRate: p.developerFeeRate,
|
|
380
|
+
sessionExpirySlots: sessionExpiry,
|
|
381
|
+
timelockSeconds: timelockSec,
|
|
382
|
+
policyVersion: policyVer,
|
|
383
|
+
pendingUpdate,
|
|
384
|
+
toJSON: () => ({
|
|
385
|
+
dailyCap: bs(dailyCap),
|
|
386
|
+
maxPerTrade: bs(maxPerTrade),
|
|
387
|
+
approvedApps,
|
|
388
|
+
protocolMode: modeMap[p.protocolMode] || "unrestricted",
|
|
389
|
+
hasProtocolCaps: p.hasProtocolCaps,
|
|
390
|
+
protocolCaps: protocolCaps.map(bs),
|
|
391
|
+
canOpenPositions: p.canOpenPositions,
|
|
392
|
+
maxConcurrentPositions: p.maxConcurrentPositions,
|
|
393
|
+
maxSlippageBps: p.maxSlippageBps,
|
|
394
|
+
leverageLimitBps: p.maxLeverageBps,
|
|
395
|
+
allowedDestinations: (p.allowedDestinations || []),
|
|
396
|
+
developerFeeRate: p.developerFeeRate,
|
|
397
|
+
sessionExpirySlots: bs(sessionExpiry),
|
|
398
|
+
timelockSeconds: timelockSec,
|
|
399
|
+
policyVersion: bs(policyVer),
|
|
400
|
+
pendingUpdate: pendingUpdate
|
|
401
|
+
? {
|
|
402
|
+
changes: serializeBigints(pendingUpdate.changes),
|
|
403
|
+
appliesAt: pendingUpdate.appliesAt,
|
|
404
|
+
canApply: pendingUpdate.canApply,
|
|
405
|
+
canCancel: pendingUpdate.canCancel,
|
|
406
|
+
}
|
|
407
|
+
: undefined,
|
|
408
|
+
}),
|
|
409
|
+
};
|
|
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 });
|
|
114
461
|
}
|
|
115
462
|
catch (err) {
|
|
116
463
|
throw toDxError(err, "OwnerClient.getVaultState");
|
|
@@ -119,70 +466,27 @@ export async function getVaultState(rpc, vault, network) {
|
|
|
119
466
|
// ─── getAgents ───────────────────────────────────────────────────────────────
|
|
120
467
|
export async function getAgents(rpc, vault, network) {
|
|
121
468
|
try {
|
|
122
|
-
//
|
|
123
|
-
//
|
|
124
|
-
//
|
|
125
|
-
//
|
|
126
|
-
//
|
|
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.
|
|
127
481
|
const [state, activity] = await Promise.all([
|
|
128
482
|
resolveVaultStateForOwner(rpc, vault, undefined, toNet(network)),
|
|
129
|
-
// Fix for docs/SECURITY-FINDINGS-2026-04-07.md Finding 5: the
|
|
130
|
-
// previous `.catch(() => [])` swallowed activity-fetch failures
|
|
131
|
-
// silently. If Helius started rate-limiting getSignaturesForAddress,
|
|
132
|
-
// every dashboard call would show "last action: never" for every
|
|
133
|
-
// agent forever and nobody would notice. Graceful degradation is
|
|
134
|
-
// still the right behavior (activity is enrichment, not core), but
|
|
135
|
-
// it must be observable — console.warn is the minimum bar.
|
|
136
483
|
getVaultActivity(rpc, vault, 100, toNet(network)).catch((err) => {
|
|
137
484
|
// eslint-disable-next-line no-console
|
|
138
485
|
console.warn("[OwnerClient.getAgents] activity enrichment failed — falling back to empty last-action fields:", err instanceof Error ? err.message : String(err));
|
|
139
486
|
return [];
|
|
140
487
|
}),
|
|
141
488
|
]);
|
|
142
|
-
|
|
143
|
-
if (!vaultAgents || vaultAgents.length === 0)
|
|
144
|
-
return [];
|
|
145
|
-
const blockedCutoffMs = Date.now() - 24 * 3600 * 1000;
|
|
146
|
-
return vaultAgents.map((entry) => {
|
|
147
|
-
const addr = entry.pubkey;
|
|
148
|
-
const profile = getAgentProfile(asVaultState(state), addr);
|
|
149
|
-
const budget = state.allAgentBudgets.get(addr);
|
|
150
|
-
const spentAmt = budget?.spent24h ?? 0n;
|
|
151
|
-
const capAmt = budget?.cap ?? 0n;
|
|
152
|
-
const pct = capAmt > 0n ? Number((spentAmt * 10000n) / capAmt) / 100 : 0;
|
|
153
|
-
// Derive last-action and blocked-count fields from the shared activity
|
|
154
|
-
// feed. Items are newest-first (getSignaturesForAddress ordering).
|
|
155
|
-
const agentActivity = activity.filter((item) => item.agent !== null && item.agent === addr);
|
|
156
|
-
const last = agentActivity[0];
|
|
157
|
-
const lastActionType = last
|
|
158
|
-
? mapCategory(last.category ?? "unknown", last.eventType ?? "", last.actionType ?? undefined)
|
|
159
|
-
: "";
|
|
160
|
-
const lastActionProtocol = last?.protocolName ?? "";
|
|
161
|
-
const lastActionTimestamp = last ? last.timestamp * 1000 : 0;
|
|
162
|
-
const blockedCount24h = agentActivity.filter((item) => !item.success && item.timestamp * 1000 >= blockedCutoffMs).length;
|
|
163
|
-
return {
|
|
164
|
-
address: addr,
|
|
165
|
-
status: (profile?.paused ? "paused" : "active"),
|
|
166
|
-
capabilityLabel: profile?.capabilityLabel ?? "Disabled",
|
|
167
|
-
capability: profile?.capability ?? 0,
|
|
168
|
-
spending: { amount: spentAmt, limit: capAmt, percent: pct },
|
|
169
|
-
lastActionType,
|
|
170
|
-
lastActionProtocol,
|
|
171
|
-
lastActionTimestamp,
|
|
172
|
-
blockedCount24h,
|
|
173
|
-
toJSON: () => ({
|
|
174
|
-
address: addr,
|
|
175
|
-
status: profile?.paused ? "paused" : "active",
|
|
176
|
-
capabilityLabel: profile?.capabilityLabel ?? "Disabled",
|
|
177
|
-
capability: profile?.capability ?? 0,
|
|
178
|
-
spending: { amount: bs(spentAmt), limit: bs(capAmt), percent: pct },
|
|
179
|
-
lastActionType,
|
|
180
|
-
lastActionProtocol,
|
|
181
|
-
lastActionTimestamp,
|
|
182
|
-
blockedCount24h,
|
|
183
|
-
}),
|
|
184
|
-
};
|
|
185
|
-
});
|
|
489
|
+
return buildAgents({ vault, state, activity });
|
|
186
490
|
}
|
|
187
491
|
catch (err) {
|
|
188
492
|
throw toDxError(err, "OwnerClient.getAgents");
|
|
@@ -192,44 +496,7 @@ export async function getAgents(rpc, vault, network) {
|
|
|
192
496
|
export async function getSpending(rpc, vault, network) {
|
|
193
497
|
try {
|
|
194
498
|
const state = await resolveVaultStateForOwner(rpc, vault, undefined, toNet(network));
|
|
195
|
-
|
|
196
|
-
const nowUnix = BigInt(Math.floor(Date.now() / 1000));
|
|
197
|
-
const epochs = getSpendingHistory(state.tracker, nowUnix);
|
|
198
|
-
const chart = epochs.map((e) => ({
|
|
199
|
-
time: new Date(e.timestamp * 1000).toISOString(),
|
|
200
|
-
amount: Number(e.usdAmount) / 1_000_000,
|
|
201
|
-
}));
|
|
202
|
-
const { spent24h: spent, cap, remaining } = state.globalBudget;
|
|
203
|
-
const percent = cap > 0n ? Number((spent * 10000n) / cap) / 100 : 0;
|
|
204
|
-
const velocityPerMs = spent > 0n ? Number(spent) / (24 * 3600 * 1000) : 0;
|
|
205
|
-
const rundown = velocityPerMs > 0 && remaining > 0n
|
|
206
|
-
? Math.floor(Number(remaining) / velocityPerMs)
|
|
207
|
-
: 0;
|
|
208
|
-
const protoBreak = breakdown.byProtocol.map((p) => ({
|
|
209
|
-
name: resolveProtocolName(p.protocol),
|
|
210
|
-
programId: p.protocol,
|
|
211
|
-
amount: p.spent24h,
|
|
212
|
-
percent: p.utilization,
|
|
213
|
-
}));
|
|
214
|
-
return {
|
|
215
|
-
global: { today: spent, cap, remaining, percent, rundownMs: rundown },
|
|
216
|
-
chart,
|
|
217
|
-
protocolBreakdown: protoBreak,
|
|
218
|
-
toJSON: () => ({
|
|
219
|
-
global: {
|
|
220
|
-
today: bs(spent),
|
|
221
|
-
cap: bs(cap),
|
|
222
|
-
remaining: bs(remaining),
|
|
223
|
-
percent,
|
|
224
|
-
rundownMs: rundown,
|
|
225
|
-
},
|
|
226
|
-
chart,
|
|
227
|
-
protocolBreakdown: protoBreak.map((p) => ({
|
|
228
|
-
...p,
|
|
229
|
-
amount: bs(p.amount),
|
|
230
|
-
})),
|
|
231
|
-
}),
|
|
232
|
-
};
|
|
499
|
+
return buildSpending({ vault, state });
|
|
233
500
|
}
|
|
234
501
|
catch (err) {
|
|
235
502
|
throw toDxError(err, "OwnerClient.getSpending");
|
|
@@ -240,39 +507,7 @@ export async function getActivity(rpc, vault, network, filters) {
|
|
|
240
507
|
try {
|
|
241
508
|
const limit = filters?.limit ?? 50;
|
|
242
509
|
const items = await getVaultActivity(rpc, vault, limit, toNet(network));
|
|
243
|
-
let rows = items
|
|
244
|
-
const cat = item.category ?? "unknown";
|
|
245
|
-
const evt = item.eventType ?? "";
|
|
246
|
-
const act = item.actionType ?? undefined;
|
|
247
|
-
const posEffect = item.positionEffect ?? undefined;
|
|
248
|
-
const type = mapCategory(cat, evt, act, posEffect);
|
|
249
|
-
const amt = item.amount ?? 0n;
|
|
250
|
-
const sig = item.txSignature || `evt-${item.timestamp}-${item.eventType}`;
|
|
251
|
-
return {
|
|
252
|
-
id: sig,
|
|
253
|
-
timestamp: item.timestamp * 1000,
|
|
254
|
-
type,
|
|
255
|
-
protocol: item.protocolName || "",
|
|
256
|
-
protocolId: item.protocol || "",
|
|
257
|
-
agent: item.agent || "",
|
|
258
|
-
amount: amt,
|
|
259
|
-
status: item.success ? "approved" : "blocked",
|
|
260
|
-
reason: item.success ? undefined : item.description,
|
|
261
|
-
txSignature: item.txSignature,
|
|
262
|
-
toJSON: () => ({
|
|
263
|
-
id: sig,
|
|
264
|
-
timestamp: item.timestamp * 1000,
|
|
265
|
-
type,
|
|
266
|
-
protocol: item.protocolName || "",
|
|
267
|
-
protocolId: item.protocol || "",
|
|
268
|
-
agent: item.agent || "",
|
|
269
|
-
amount: bs(amt),
|
|
270
|
-
status: item.success ? "approved" : "blocked",
|
|
271
|
-
reason: item.success ? undefined : item.description,
|
|
272
|
-
txSignature: item.txSignature,
|
|
273
|
-
}),
|
|
274
|
-
};
|
|
275
|
-
});
|
|
510
|
+
let rows = buildActivityRows(items);
|
|
276
511
|
if (filters?.agent)
|
|
277
512
|
rows = rows.filter((r) => r.agent === filters.agent);
|
|
278
513
|
if (filters?.protocol)
|
|
@@ -352,40 +587,7 @@ function rangeToMs(r) {
|
|
|
352
587
|
export async function getHealth(rpc, vault, network) {
|
|
353
588
|
try {
|
|
354
589
|
const state = await resolveVaultStateForOwner(rpc, vault, undefined, toNet(network));
|
|
355
|
-
|
|
356
|
-
const alerts = evaluateAlertConditions(state, vault);
|
|
357
|
-
const level = posture.criticalFailures.length > 0
|
|
358
|
-
? "critical"
|
|
359
|
-
: posture.failCount > 0
|
|
360
|
-
? "elevated"
|
|
361
|
-
: "healthy";
|
|
362
|
-
const critAlerts = alerts.filter((a) => a.severity === "critical");
|
|
363
|
-
const lastBlock = critAlerts.length > 0
|
|
364
|
-
? {
|
|
365
|
-
agent: critAlerts[0].agentAddress || "",
|
|
366
|
-
reason: critAlerts[0].title,
|
|
367
|
-
amount: 0n,
|
|
368
|
-
timestamp: Date.now(),
|
|
369
|
-
}
|
|
370
|
-
: undefined;
|
|
371
|
-
const checks = posture.checks.map((c) => ({
|
|
372
|
-
name: c.id,
|
|
373
|
-
passed: c.passed,
|
|
374
|
-
}));
|
|
375
|
-
return {
|
|
376
|
-
level,
|
|
377
|
-
blockedCount24h: critAlerts.length,
|
|
378
|
-
checks,
|
|
379
|
-
lastBlock,
|
|
380
|
-
toJSON: () => ({
|
|
381
|
-
level,
|
|
382
|
-
blockedCount24h: critAlerts.length,
|
|
383
|
-
checks,
|
|
384
|
-
lastBlock: lastBlock
|
|
385
|
-
? { ...lastBlock, amount: bs(lastBlock.amount) }
|
|
386
|
-
: undefined,
|
|
387
|
-
}),
|
|
388
|
-
};
|
|
590
|
+
return buildHealth({ vault, state });
|
|
389
591
|
}
|
|
390
592
|
catch (err) {
|
|
391
593
|
throw toDxError(err, "OwnerClient.getHealth");
|
|
@@ -399,124 +601,114 @@ export async function getPolicy(rpc, vault, network) {
|
|
|
399
601
|
getPendingPolicyForVault(rpc, vault).catch((err) => {
|
|
400
602
|
// Account-not-found is expected (no pending update) — return null.
|
|
401
603
|
// Re-throw RPC errors so they're not silently swallowed.
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
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))
|
|
405
665
|
return null;
|
|
406
|
-
}
|
|
407
666
|
throw err;
|
|
408
667
|
}),
|
|
409
668
|
]);
|
|
410
|
-
|
|
411
|
-
const
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
const
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
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,
|
|
420
686
|
};
|
|
421
|
-
const
|
|
422
|
-
const
|
|
423
|
-
const
|
|
424
|
-
const
|
|
425
|
-
const
|
|
426
|
-
const
|
|
427
|
-
let pendingUpdate;
|
|
428
|
-
if (pendingPolicy) {
|
|
429
|
-
const pp = pendingPolicy;
|
|
430
|
-
const executesAtSec = Number(pp.executesAt ?? 0);
|
|
431
|
-
const appliesAt = Number.isFinite(executesAtSec)
|
|
432
|
-
? executesAtSec * 1000
|
|
433
|
-
: 0;
|
|
434
|
-
const nowSec = Math.floor(Date.now() / 1000);
|
|
435
|
-
// Decode each Option<T> field from PendingPolicyUpdate. Only Some fields
|
|
436
|
-
// land in `changes`. 14 fields total — every timelockable PolicyConfig field.
|
|
437
|
-
// Source: programs/sigil/src/state/pending_policy.rs
|
|
438
|
-
const changes = {};
|
|
439
|
-
if (isSome(pp.dailySpendingCapUsd))
|
|
440
|
-
changes.dailyCap = pp.dailySpendingCapUsd.value;
|
|
441
|
-
if (isSome(pp.maxTransactionAmountUsd))
|
|
442
|
-
changes.maxPerTrade = pp.maxTransactionAmountUsd.value;
|
|
443
|
-
if (isSome(pp.protocols))
|
|
444
|
-
changes.approvedApps = pp.protocols.value;
|
|
445
|
-
if (isSome(pp.protocolMode))
|
|
446
|
-
changes.protocolMode = modeMap[pp.protocolMode.value] || "unrestricted";
|
|
447
|
-
if (isSome(pp.hasProtocolCaps))
|
|
448
|
-
changes.hasProtocolCaps = pp.hasProtocolCaps.value;
|
|
449
|
-
if (isSome(pp.protocolCaps))
|
|
450
|
-
changes.protocolCaps = pp.protocolCaps.value;
|
|
451
|
-
if (isSome(pp.canOpenPositions))
|
|
452
|
-
changes.canOpenPositions = pp.canOpenPositions.value;
|
|
453
|
-
if (isSome(pp.maxConcurrentPositions))
|
|
454
|
-
changes.maxConcurrentPositions = pp.maxConcurrentPositions.value;
|
|
455
|
-
if (isSome(pp.maxSlippageBps))
|
|
456
|
-
changes.maxSlippageBps = pp.maxSlippageBps.value;
|
|
457
|
-
if (isSome(pp.maxLeverageBps))
|
|
458
|
-
changes.leverageLimit = pp.maxLeverageBps.value;
|
|
459
|
-
if (isSome(pp.allowedDestinations))
|
|
460
|
-
changes.allowedDestinations = pp.allowedDestinations.value;
|
|
461
|
-
if (isSome(pp.developerFeeRate))
|
|
462
|
-
changes.developerFeeRate = pp.developerFeeRate.value;
|
|
463
|
-
if (isSome(pp.sessionExpirySlots))
|
|
464
|
-
changes.sessionExpirySlots = pp.sessionExpirySlots.value;
|
|
465
|
-
if (isSome(pp.timelockDuration))
|
|
466
|
-
changes.timelock = Number(pp.timelockDuration.value);
|
|
467
|
-
pendingUpdate = {
|
|
468
|
-
changes,
|
|
469
|
-
appliesAt,
|
|
470
|
-
canApply: executesAtSec > 0 && executesAtSec <= nowSec,
|
|
471
|
-
canCancel: true,
|
|
472
|
-
};
|
|
473
|
-
}
|
|
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 ?? []);
|
|
474
693
|
return {
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
canOpenPositions: p.canOpenPositions,
|
|
482
|
-
maxConcurrentPositions: p.maxConcurrentPositions,
|
|
483
|
-
maxSlippageBps: p.maxSlippageBps,
|
|
484
|
-
leverageLimitBps: p.maxLeverageBps,
|
|
485
|
-
allowedDestinations: (p.allowedDestinations || []),
|
|
486
|
-
developerFeeRate: p.developerFeeRate,
|
|
487
|
-
sessionExpirySlots: sessionExpiry,
|
|
488
|
-
timelockSeconds: timelockSec,
|
|
489
|
-
policyVersion: policyVer,
|
|
490
|
-
pendingUpdate,
|
|
694
|
+
vault: vaultView,
|
|
695
|
+
agents: agentsView,
|
|
696
|
+
spending: spendingView,
|
|
697
|
+
health: healthView,
|
|
698
|
+
policy: policyView,
|
|
699
|
+
activity: activityRows,
|
|
491
700
|
toJSON: () => ({
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
canOpenPositions: p.canOpenPositions,
|
|
499
|
-
maxConcurrentPositions: p.maxConcurrentPositions,
|
|
500
|
-
maxSlippageBps: p.maxSlippageBps,
|
|
501
|
-
leverageLimitBps: p.maxLeverageBps,
|
|
502
|
-
allowedDestinations: (p.allowedDestinations || []),
|
|
503
|
-
developerFeeRate: p.developerFeeRate,
|
|
504
|
-
sessionExpirySlots: bs(sessionExpiry),
|
|
505
|
-
timelockSeconds: timelockSec,
|
|
506
|
-
policyVersion: bs(policyVer),
|
|
507
|
-
pendingUpdate: pendingUpdate
|
|
508
|
-
? {
|
|
509
|
-
changes: serializeBigints(pendingUpdate.changes),
|
|
510
|
-
appliesAt: pendingUpdate.appliesAt,
|
|
511
|
-
canApply: pendingUpdate.canApply,
|
|
512
|
-
canCancel: pendingUpdate.canCancel,
|
|
513
|
-
}
|
|
514
|
-
: undefined,
|
|
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()),
|
|
515
707
|
}),
|
|
516
708
|
};
|
|
517
709
|
}
|
|
518
710
|
catch (err) {
|
|
519
|
-
throw toDxError(err, "OwnerClient.
|
|
711
|
+
throw toDxError(err, "OwnerClient.getOverview");
|
|
520
712
|
}
|
|
521
713
|
}
|
|
522
714
|
//# sourceMappingURL=reads.js.map
|