@decentrys/ui-sdk 0.1.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 (93) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +51 -0
  3. package/dist/browser/decentrys-ui.js +1733 -0
  4. package/dist/browser/decentrys-ui.mjs +1681 -0
  5. package/dist/browser/react-dom-global.d.ts +6 -0
  6. package/dist/browser/react-dom-global.d.ts.map +1 -0
  7. package/dist/browser/react-dom-global.js +18 -0
  8. package/dist/browser/react-dom-global.js.map +1 -0
  9. package/dist/browser/react-global.d.ts +25 -0
  10. package/dist/browser/react-global.d.ts.map +1 -0
  11. package/dist/browser/react-global.js +39 -0
  12. package/dist/browser/react-global.js.map +1 -0
  13. package/dist/browser/react-jsx-runtime.d.ts +17 -0
  14. package/dist/browser/react-jsx-runtime.d.ts.map +1 -0
  15. package/dist/browser/react-jsx-runtime.js +27 -0
  16. package/dist/browser/react-jsx-runtime.js.map +1 -0
  17. package/dist/components/AddressRiskBadge.d.ts +39 -0
  18. package/dist/components/AddressRiskBadge.d.ts.map +1 -0
  19. package/dist/components/AddressRiskBadge.js +26 -0
  20. package/dist/components/AddressRiskBadge.js.map +1 -0
  21. package/dist/components/ApprovalWarning.d.ts +29 -0
  22. package/dist/components/ApprovalWarning.d.ts.map +1 -0
  23. package/dist/components/ApprovalWarning.js +18 -0
  24. package/dist/components/ApprovalWarning.js.map +1 -0
  25. package/dist/components/ContractFacts.d.ts +27 -0
  26. package/dist/components/ContractFacts.d.ts.map +1 -0
  27. package/dist/components/ContractFacts.js +15 -0
  28. package/dist/components/ContractFacts.js.map +1 -0
  29. package/dist/components/RiskDetailsModal.d.ts +35 -0
  30. package/dist/components/RiskDetailsModal.d.ts.map +1 -0
  31. package/dist/components/RiskDetailsModal.js +74 -0
  32. package/dist/components/RiskDetailsModal.js.map +1 -0
  33. package/dist/components/ThreatSignalList.d.ts +28 -0
  34. package/dist/components/ThreatSignalList.d.ts.map +1 -0
  35. package/dist/components/ThreatSignalList.js +28 -0
  36. package/dist/components/ThreatSignalList.js.map +1 -0
  37. package/dist/components/TokenSecurityPanel.d.ts +28 -0
  38. package/dist/components/TokenSecurityPanel.d.ts.map +1 -0
  39. package/dist/components/TokenSecurityPanel.js +26 -0
  40. package/dist/components/TokenSecurityPanel.js.map +1 -0
  41. package/dist/components/TransactionRiskBanner.d.ts +36 -0
  42. package/dist/components/TransactionRiskBanner.d.ts.map +1 -0
  43. package/dist/components/TransactionRiskBanner.js +20 -0
  44. package/dist/components/TransactionRiskBanner.js.map +1 -0
  45. package/dist/context.d.ts +51 -0
  46. package/dist/context.d.ts.map +1 -0
  47. package/dist/context.js +47 -0
  48. package/dist/context.js.map +1 -0
  49. package/dist/copy.d.ts +72 -0
  50. package/dist/copy.d.ts.map +1 -0
  51. package/dist/copy.js +77 -0
  52. package/dist/copy.js.map +1 -0
  53. package/dist/index.d.ts +57 -0
  54. package/dist/index.d.ts.map +1 -0
  55. package/dist/index.js +107 -0
  56. package/dist/index.js.map +1 -0
  57. package/dist/presentation.d.ts +316 -0
  58. package/dist/presentation.d.ts.map +1 -0
  59. package/dist/presentation.js +533 -0
  60. package/dist/presentation.js.map +1 -0
  61. package/dist/primitives.d.ts +139 -0
  62. package/dist/primitives.d.ts.map +1 -0
  63. package/dist/primitives.js +150 -0
  64. package/dist/primitives.js.map +1 -0
  65. package/dist/styles.d.ts +15 -0
  66. package/dist/styles.d.ts.map +1 -0
  67. package/dist/styles.js +285 -0
  68. package/dist/styles.js.map +1 -0
  69. package/dist/theme.d.ts +52 -0
  70. package/dist/theme.d.ts.map +1 -0
  71. package/dist/theme.js +90 -0
  72. package/dist/theme.js.map +1 -0
  73. package/package.json +70 -0
  74. package/src/browser/react-dom-global.ts +20 -0
  75. package/src/browser/react-global.ts +42 -0
  76. package/src/browser/react-jsx-runtime.ts +23 -0
  77. package/src/components/AddressRiskBadge.tsx +94 -0
  78. package/src/components/ApprovalWarning.tsx +144 -0
  79. package/src/components/ContractFacts.tsx +119 -0
  80. package/src/components/RiskDetailsModal.tsx +229 -0
  81. package/src/components/ThreatSignalList.tsx +76 -0
  82. package/src/components/TokenSecurityPanel.tsx +168 -0
  83. package/src/components/TransactionRiskBanner.tsx +123 -0
  84. package/src/context.tsx +89 -0
  85. package/src/copy.test.ts +214 -0
  86. package/src/copy.ts +151 -0
  87. package/src/index.ts +84 -0
  88. package/src/presentation.test.ts +521 -0
  89. package/src/presentation.ts +846 -0
  90. package/src/primitives.tsx +384 -0
  91. package/src/styles.ts +281 -0
  92. package/src/theme.test.ts +105 -0
  93. package/src/theme.ts +117 -0
@@ -0,0 +1,1681 @@
1
+ // src/context.tsx
2
+ import { createContext, useContext } from "react";
3
+
4
+ // src/styles.ts
5
+ var DECENTRYS_UI_CSS = `
6
+ .dcy {
7
+ --dcy-font: ui-sans-serif, system-ui, -apple-system, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
8
+ --dcy-font-mono: ui-monospace, SFMono-Regular, Menlo, Consolas, "Liberation Mono", monospace;
9
+ --dcy-radius: 8px;
10
+ --dcy-gap: 12px;
11
+
12
+ --dcy-surface: #ffffff;
13
+ --dcy-surface-raised: #f5f6f8;
14
+ --dcy-border: #dde1e7;
15
+ --dcy-text: #14171c;
16
+ --dcy-text-muted: #545d6b;
17
+ --dcy-text-faint: #737d8c;
18
+ --dcy-focus: #2f6fed;
19
+
20
+ --dcy-tone-neutral: #545d6b;
21
+ --dcy-tone-neutral-bg: #f2f4f6;
22
+ --dcy-tone-info: #2f5f96;
23
+ --dcy-tone-info-bg: #eef3f9;
24
+ --dcy-tone-caution: #7d6113;
25
+ --dcy-tone-caution-bg: #faf4e4;
26
+ --dcy-tone-elevated: #97500f;
27
+ --dcy-tone-elevated-bg: #fbefe3;
28
+ --dcy-tone-high: #a83c18;
29
+ --dcy-tone-high-bg: #fbece6;
30
+ --dcy-tone-critical: #98211d;
31
+ --dcy-tone-critical-bg: #fbe9e9;
32
+ --dcy-tone-fact: #6a7382;
33
+ --dcy-tone-fact-bg: transparent;
34
+ --dcy-tone-capability: #554d8c;
35
+ --dcy-tone-capability-bg: #f2f1fa;
36
+ --dcy-tone-unknown: #77808f;
37
+ --dcy-tone-unknown-bg: transparent;
38
+ --dcy-tone-inactive: #8d97a4;
39
+ --dcy-tone-inactive-bg: transparent;
40
+
41
+ font-family: var(--dcy-font);
42
+ font-size: 14px;
43
+ line-height: 1.5;
44
+ color: var(--dcy-text);
45
+ box-sizing: border-box;
46
+ }
47
+ .dcy *, .dcy *::before, .dcy *::after { box-sizing: inherit; }
48
+
49
+ @media (prefers-color-scheme: dark) {
50
+ .dcy {
51
+ --dcy-surface: #14171c;
52
+ --dcy-surface-raised: #1c2027;
53
+ --dcy-border: #2c323b;
54
+ --dcy-text: #e8ebef;
55
+ --dcy-text-muted: #a4adba;
56
+ --dcy-text-faint: #808a98;
57
+ --dcy-focus: #7aa5ff;
58
+
59
+ --dcy-tone-neutral: #a4adba;
60
+ --dcy-tone-neutral-bg: #1e232a;
61
+ --dcy-tone-info: #86b0e6;
62
+ --dcy-tone-info-bg: #17202b;
63
+ --dcy-tone-caution: #d8b463;
64
+ --dcy-tone-caution-bg: #241f14;
65
+ --dcy-tone-elevated: #e0a065;
66
+ --dcy-tone-elevated-bg: #261c13;
67
+ --dcy-tone-high: #eb8c68;
68
+ --dcy-tone-high-bg: #271812;
69
+ --dcy-tone-critical: #ef7d78;
70
+ --dcy-tone-critical-bg: #291414;
71
+ --dcy-tone-fact: #97a1ae;
72
+ --dcy-tone-capability: #a79fe0;
73
+ --dcy-tone-capability-bg: #1d1b2a;
74
+ --dcy-tone-unknown: #8b95a3;
75
+ --dcy-tone-inactive: #79828f;
76
+ }
77
+ }
78
+
79
+
80
+ /*
81
+ * Tone resolution happens here, in the stylesheet, rather than in inline
82
+ * styles on the elements. An inline colour would beat any rule an integrator
83
+ * wrote against these components without \`!important\`, which is not a
84
+ * position to put somebody in who only wanted to match their own palette.
85
+ */
86
+ [data-dcy-tone="neutral"] { --dcy-tone: var(--dcy-tone-neutral); --dcy-tone-bg: var(--dcy-tone-neutral-bg); }
87
+ [data-dcy-tone="info"] { --dcy-tone: var(--dcy-tone-info); --dcy-tone-bg: var(--dcy-tone-info-bg); }
88
+ [data-dcy-tone="caution"] { --dcy-tone: var(--dcy-tone-caution); --dcy-tone-bg: var(--dcy-tone-caution-bg); }
89
+ [data-dcy-tone="elevated"] { --dcy-tone: var(--dcy-tone-elevated); --dcy-tone-bg: var(--dcy-tone-elevated-bg); }
90
+ [data-dcy-tone="high"] { --dcy-tone: var(--dcy-tone-high); --dcy-tone-bg: var(--dcy-tone-high-bg); }
91
+ [data-dcy-tone="critical"] { --dcy-tone: var(--dcy-tone-critical); --dcy-tone-bg: var(--dcy-tone-critical-bg); }
92
+ [data-dcy-tone="fact"] { --dcy-tone: var(--dcy-tone-fact); --dcy-tone-bg: var(--dcy-tone-fact-bg); }
93
+ [data-dcy-tone="capability"] { --dcy-tone: var(--dcy-tone-capability); --dcy-tone-bg: var(--dcy-tone-capability-bg); }
94
+ [data-dcy-tone="unknown"] { --dcy-tone: var(--dcy-tone-unknown); --dcy-tone-bg: var(--dcy-tone-unknown-bg); }
95
+ [data-dcy-tone="inactive"] { --dcy-tone: var(--dcy-tone-inactive); --dcy-tone-bg: var(--dcy-tone-inactive-bg); }
96
+
97
+ [data-dcy-rule="solid"] { border-left-style: solid; }
98
+ [data-dcy-rule="dashed"] { border-left-style: dashed; }
99
+ [data-dcy-rule="dotted"] { border-left-style: dotted; }
100
+
101
+ .dcy-row[data-dcy-kind="signal"] { border-left-width: 4px; }
102
+
103
+ .dcy :focus-visible {
104
+ outline: 2px solid var(--dcy-focus);
105
+ outline-offset: 2px;
106
+ border-radius: 4px;
107
+ }
108
+
109
+ .dcy-card {
110
+ border: 1px solid var(--dcy-border);
111
+ border-radius: var(--dcy-radius);
112
+ background: var(--dcy-surface);
113
+ padding: var(--dcy-gap);
114
+ display: flex;
115
+ flex-direction: column;
116
+ gap: var(--dcy-gap);
117
+ }
118
+
119
+ .dcy-head { display: flex; align-items: flex-start; gap: 10px; flex-wrap: wrap; }
120
+ .dcy-head-main { flex: 1 1 14rem; min-width: 0; }
121
+ .dcy-title { margin: 0; font-size: 14px; font-weight: 600; }
122
+ .dcy-meaning { margin: 4px 0 0; color: var(--dcy-text-muted); font-size: 13px; }
123
+
124
+ .dcy-badge {
125
+ display: inline-flex;
126
+ align-items: center;
127
+ gap: 6px;
128
+ padding: 2px 8px;
129
+ border-radius: 999px;
130
+ border: 1px solid currentColor;
131
+ font-size: 12px;
132
+ font-weight: 600;
133
+ line-height: 1.6;
134
+ white-space: nowrap;
135
+ color: var(--dcy-tone, var(--dcy-text-muted));
136
+ background: var(--dcy-tone-bg, transparent);
137
+ }
138
+ .dcy-glyph {
139
+ font-family: var(--dcy-font-mono);
140
+ font-weight: 700;
141
+ letter-spacing: -0.05em;
142
+ }
143
+
144
+ .dcy-chip {
145
+ display: inline-block;
146
+ padding: 1px 6px;
147
+ border-radius: 4px;
148
+ border: 1px solid var(--dcy-border);
149
+ background: var(--dcy-surface-raised);
150
+ color: var(--dcy-text-muted);
151
+ font-size: 11px;
152
+ font-weight: 600;
153
+ text-transform: uppercase;
154
+ letter-spacing: 0.04em;
155
+ white-space: nowrap;
156
+ }
157
+
158
+ .dcy-section { display: flex; flex-direction: column; gap: 8px; }
159
+ .dcy-section-title {
160
+ margin: 0;
161
+ font-size: 11px;
162
+ font-weight: 700;
163
+ text-transform: uppercase;
164
+ letter-spacing: 0.06em;
165
+ color: var(--dcy-text-faint);
166
+ }
167
+ .dcy-note { margin: 0; font-size: 12px; color: var(--dcy-text-muted); }
168
+ .dcy-list { list-style: none; margin: 0; padding: 0; display: flex; flex-direction: column; gap: 8px; }
169
+
170
+ /*
171
+ * Kind rows.
172
+ *
173
+ * The left rule's *style* and the row's text label are what separate a fact
174
+ * from a threat signal; the colour is the third carrier, not the only one.
175
+ */
176
+ .dcy-row {
177
+ display: flex;
178
+ gap: 8px;
179
+ padding: 6px 0 6px 10px;
180
+ border-left-style: solid;
181
+ border-left-width: 2px;
182
+ border-left-color: var(--dcy-tone, var(--dcy-tone-fact));
183
+ }
184
+ .dcy-row-body { min-width: 0; flex: 1 1 auto; }
185
+ .dcy-row-statement { margin: 0; }
186
+ .dcy-row-strong { font-weight: 600; }
187
+ .dcy-row-meta {
188
+ margin: 3px 0 0;
189
+ font-size: 12px;
190
+ color: var(--dcy-text-muted);
191
+ display: flex;
192
+ flex-wrap: wrap;
193
+ gap: 4px 10px;
194
+ }
195
+ .dcy-mono { font-family: var(--dcy-font-mono); font-size: 12px; word-break: break-all; }
196
+
197
+ .dcy-signal {
198
+ border: 1px solid var(--dcy-border);
199
+ border-left-width: 4px;
200
+ border-left-color: var(--dcy-tone, var(--dcy-border));
201
+ border-radius: var(--dcy-radius);
202
+ padding: 10px;
203
+ display: flex;
204
+ flex-direction: column;
205
+ gap: 6px;
206
+ }
207
+ .dcy-signal-head { display: flex; align-items: center; gap: 6px; flex-wrap: wrap; }
208
+
209
+ .dcy-kv { display: grid; grid-template-columns: minmax(6rem, auto) 1fr; gap: 4px 12px; font-size: 13px; }
210
+ .dcy-kv dt { color: var(--dcy-text-muted); }
211
+ .dcy-kv dd { margin: 0; }
212
+
213
+ .dcy-btn {
214
+ font: inherit;
215
+ font-size: 13px;
216
+ font-weight: 600;
217
+ color: var(--dcy-text);
218
+ background: var(--dcy-surface-raised);
219
+ border: 1px solid var(--dcy-border);
220
+ border-radius: 6px;
221
+ padding: 5px 12px;
222
+ cursor: pointer;
223
+ }
224
+ .dcy-btn:hover { background: var(--dcy-border); }
225
+ .dcy-btn-quiet { background: transparent; border-color: transparent; text-decoration: underline; padding-left: 0; }
226
+
227
+ .dcy-backdrop {
228
+ position: fixed;
229
+ inset: 0;
230
+ background: rgba(8, 10, 14, 0.55);
231
+ display: flex;
232
+ align-items: flex-start;
233
+ justify-content: center;
234
+ padding: 24px 16px;
235
+ overflow: auto;
236
+ z-index: 2147483000;
237
+ }
238
+ .dcy-modal {
239
+ width: min(40rem, 100%);
240
+ max-height: calc(100vh - 48px);
241
+ overflow: auto;
242
+ background: var(--dcy-surface);
243
+ border: 1px solid var(--dcy-border);
244
+ border-radius: var(--dcy-radius);
245
+ padding: 16px;
246
+ display: flex;
247
+ flex-direction: column;
248
+ gap: var(--dcy-gap);
249
+ }
250
+ .dcy-modal-head { display: flex; align-items: center; justify-content: space-between; gap: 12px; }
251
+
252
+ .dcy-inline {
253
+ display: inline-flex;
254
+ align-items: center;
255
+ gap: 6px;
256
+ font-family: var(--dcy-font);
257
+ font-size: 12px;
258
+ }
259
+ .dcy-sr {
260
+ position: absolute;
261
+ width: 1px;
262
+ height: 1px;
263
+ padding: 0;
264
+ margin: -1px;
265
+ overflow: hidden;
266
+ clip: rect(0 0 0 0);
267
+ clip-path: inset(50%);
268
+ white-space: nowrap;
269
+ border: 0;
270
+ }
271
+ .dcy-hr { border: none; border-top: 1px solid var(--dcy-border); margin: 0; }
272
+ `;
273
+
274
+ // src/copy.ts
275
+ var DEFAULT_COPY = {
276
+ factsHeading: "Observed facts",
277
+ capabilitiesHeading: "What this contract can do",
278
+ signalsHeading: "Threat signals",
279
+ unknownsHeading: "Not known",
280
+ evidenceHeading: "Evidence",
281
+ componentsHeading: "Score components",
282
+ historyHeading: "History",
283
+ demotedHeading: "Reported as facts, not as risk",
284
+ factLabel: "Fact",
285
+ capabilityLabel: "Capability",
286
+ signalLabel: "Threat signal",
287
+ unknownLabel: "Unknown",
288
+ notAnAssurance: "An absence of findings is an absence of findings. It is not an assurance about this subject.",
289
+ factsAreNotFindings: "These are directly observed and carry no accusation. None of them raises a risk level on its own.",
290
+ capabilitiesAreNotFindings: "What the code is able to do. A capability is not evidence that it has been or will be used.",
291
+ unknownsAreNotRisk: "Reported as unknown rather than guessed at. An unknown never contributes to a risk level.",
292
+ coverageIsNotRisk: "Coverage measures how much Decentrys knows about this subject. It is reported separately and is never added to a risk total.",
293
+ policyIsYours: "Decentrys reports evidence. What happens next is this application\u2019s decision, not ours.",
294
+ demotedExplanation: "These arrived labelled as risk signals and describe how much is known rather than what was found, so they are shown as facts and cannot raise a risk level.",
295
+ noFacts: "No facts were returned for this subject.",
296
+ noCapabilities: "No security-sensitive capabilities were identified.",
297
+ noSignals: "No threat signals were found. That is an absence of findings, not a finding that there is nothing to find.",
298
+ noUnknowns: "Every attribute checked returned an answer.",
299
+ nothingReturned: "Decentrys returned no facts, capabilities, threat signals or unknowns for this subject.",
300
+ viewDetails: "View details",
301
+ close: "Close",
302
+ detailsTitle: "Assessment details",
303
+ showEvidence: "Show evidence",
304
+ hideEvidence: "Hide evidence",
305
+ confidenceLabel: "Confidence",
306
+ confidenceCaveat: "How well the evidence supports this classification. It is not a measure of how much harm the subject could do.",
307
+ sourceLabel: "Source",
308
+ observedLabel: "Observed",
309
+ grantedByLabel: "Granted by",
310
+ hopsLabel: "Distance",
311
+ statusLabel: "Status",
312
+ analystConfirmed: "Confirmed by a Decentrys analyst against the evidence above.",
313
+ notAnalystConfirmed: "Automated finding. No analyst has confirmed it.",
314
+ cachedNotice: "Served from a local cache rather than a fresh lookup.",
315
+ modelLabel: "Model",
316
+ assessedLabel: "Assessed",
317
+ approvalHeading: "What you are about to grant",
318
+ approvalRevocation: "An allowance stays in place until it is revoked, whether or not the application is used again.",
319
+ spenderLabel: "Spender",
320
+ tokenLabel: "Token"
321
+ };
322
+ function resolveCopy(overrides) {
323
+ return overrides ? { ...DEFAULT_COPY, ...overrides } : DEFAULT_COPY;
324
+ }
325
+
326
+ // src/context.tsx
327
+ import { jsx, jsxs } from "react/jsx-runtime";
328
+ var UiContext = createContext(null);
329
+ function DecentrysUiStyles() {
330
+ return /* @__PURE__ */ jsx("style", { "data-decentrys-ui": "", children: DECENTRYS_UI_CSS });
331
+ }
332
+ function DecentrysUiProvider({
333
+ theme,
334
+ copy,
335
+ injectStyles = true,
336
+ children
337
+ }) {
338
+ return /* @__PURE__ */ jsxs(UiContext.Provider, { value: { theme, copy, stylesProvided: true }, children: [
339
+ injectStyles ? /* @__PURE__ */ jsx(DecentrysUiStyles, {}) : null,
340
+ children
341
+ ] });
342
+ }
343
+ function useUiConfig(local = {}) {
344
+ const ctx = useContext(UiContext);
345
+ const theme = ctx?.theme || local.theme ? { ...ctx?.theme ?? {}, ...local.theme ?? {} } : void 0;
346
+ return {
347
+ theme,
348
+ copy: resolveCopy({ ...ctx?.copy ?? {}, ...local.copy ?? {} }),
349
+ emitStyles: !ctx?.stylesProvided
350
+ };
351
+ }
352
+
353
+ // src/theme.ts
354
+ var THEME_TOKENS = [
355
+ "font",
356
+ "font-mono",
357
+ "radius",
358
+ "gap",
359
+ "surface",
360
+ "surface-raised",
361
+ "border",
362
+ "text",
363
+ "text-muted",
364
+ "text-faint",
365
+ "focus",
366
+ "tone-neutral",
367
+ "tone-neutral-bg",
368
+ "tone-info",
369
+ "tone-info-bg",
370
+ "tone-caution",
371
+ "tone-caution-bg",
372
+ "tone-elevated",
373
+ "tone-elevated-bg",
374
+ "tone-high",
375
+ "tone-high-bg",
376
+ "tone-critical",
377
+ "tone-critical-bg",
378
+ "tone-fact",
379
+ "tone-fact-bg",
380
+ "tone-capability",
381
+ "tone-capability-bg",
382
+ "tone-unknown",
383
+ "tone-unknown-bg",
384
+ "tone-inactive",
385
+ "tone-inactive-bg"
386
+ ];
387
+ function themeStyle(theme, base) {
388
+ if (!theme) return base;
389
+ const style = {};
390
+ for (const [token, value] of Object.entries(theme)) {
391
+ if (typeof value === "string" && value.length > 0) style[`--dcy-${token}`] = value;
392
+ }
393
+ return { ...base ?? {}, ...style };
394
+ }
395
+ function toneVar(tone) {
396
+ return `var(--dcy-tone-${tone})`;
397
+ }
398
+ function toneBgVar(tone) {
399
+ return `var(--dcy-tone-${tone}-bg)`;
400
+ }
401
+
402
+ // ../protect/dist/browser/decentrys-protect.mjs
403
+ var RISK_LEVEL_MEANING = {
404
+ NO_CRITICAL_RISK_DETECTED: "No known critical threat evidence was identified. This is not an assurance of safety.",
405
+ INFORMATIONAL: "Facts worth knowing before proceeding. Nothing here indicates danger.",
406
+ CAUTION: "A security-sensitive capability or behaviour exists that deserves attention.",
407
+ ELEVATED_RISK: "Several meaningful risk signals are present together.",
408
+ HIGH_RISK: "Strong technical or behavioural evidence of significant danger.",
409
+ CRITICAL_THREAT: "Severe threat supported by concrete evidence.",
410
+ KNOWN_MALICIOUS: "Confirmed malicious infrastructure or behaviour, verified against evidence."
411
+ };
412
+ var HISTORY_STATUS_MEANING = {
413
+ ESTABLISHED: "Substantial on-chain history is available.",
414
+ MODERATE: "Some history is available.",
415
+ LIMITED: "Little history is available yet. This is normal for anything recently deployed and is not a risk finding.",
416
+ NONE: "No history is available. This is not a risk finding."
417
+ };
418
+ var MIN_RAISING_CONFIDENCE = 0.5;
419
+
420
+ // src/presentation.ts
421
+ var RISK_LEVEL_LABEL = {
422
+ NO_CRITICAL_RISK_DETECTED: "No critical risk detected",
423
+ INFORMATIONAL: "Informational",
424
+ CAUTION: "Caution",
425
+ ELEVATED_RISK: "Elevated risk",
426
+ HIGH_RISK: "High risk",
427
+ CRITICAL_THREAT: "Critical threat",
428
+ KNOWN_MALICIOUS: "Known malicious"
429
+ };
430
+ var LEVEL_GLYPH = {
431
+ NO_CRITICAL_RISK_DETECTED: "\xB7",
432
+ INFORMATIONAL: "i",
433
+ CAUTION: "!",
434
+ ELEVATED_RISK: "!!",
435
+ HIGH_RISK: "!!!",
436
+ CRITICAL_THREAT: "!!!!",
437
+ KNOWN_MALICIOUS: "\u2715"
438
+ };
439
+ var LEVEL_TONE = {
440
+ // Neither of these two may look like a warning. Nothing in them indicates
441
+ // danger, and a new project landing on INFORMATIONAL is the common case.
442
+ NO_CRITICAL_RISK_DETECTED: "neutral",
443
+ INFORMATIONAL: "info",
444
+ CAUTION: "caution",
445
+ ELEVATED_RISK: "elevated",
446
+ HIGH_RISK: "high",
447
+ CRITICAL_THREAT: "critical",
448
+ KNOWN_MALICIOUS: "critical"
449
+ };
450
+ var LEVEL_ORDER = [
451
+ "NO_CRITICAL_RISK_DETECTED",
452
+ "INFORMATIONAL",
453
+ "CAUTION",
454
+ "ELEVATED_RISK",
455
+ "HIGH_RISK",
456
+ "CRITICAL_THREAT",
457
+ "KNOWN_MALICIOUS"
458
+ ];
459
+ function levelRank(level) {
460
+ return LEVEL_ORDER.indexOf(level);
461
+ }
462
+ function levelPresentation(level, options = {}) {
463
+ const label = options.labels?.[level] ?? RISK_LEVEL_LABEL[level];
464
+ const meaning = RISK_LEVEL_MEANING[level];
465
+ const evidenceBacked = levelRank(level) >= levelRank("ELEVATED_RISK");
466
+ return {
467
+ level,
468
+ label,
469
+ meaning,
470
+ tone: LEVEL_TONE[level],
471
+ glyph: LEVEL_GLYPH[level],
472
+ evidenceBacked,
473
+ accusatory: level === "KNOWN_MALICIOUS",
474
+ liveRegionRole: evidenceBacked ? "alert" : "status",
475
+ ariaLabel: `Decentrys assessment: ${label}. ${meaning}`
476
+ };
477
+ }
478
+ function kindPresentation(kind, copy) {
479
+ switch (kind) {
480
+ case "fact":
481
+ return { kind, label: copy.factLabel, glyph: "\xB7", tone: "fact", rule: "solid", ruleWidth: "2px" };
482
+ case "capability":
483
+ return { kind, label: copy.capabilityLabel, glyph: "\u25C7", tone: "capability", rule: "dashed", ruleWidth: "2px" };
484
+ case "signal":
485
+ return { kind, label: copy.signalLabel, glyph: "\u25B2", tone: "critical", rule: "solid", ruleWidth: "4px" };
486
+ case "unknown":
487
+ return { kind, label: copy.unknownLabel, glyph: "?", tone: "unknown", rule: "dotted", ruleWidth: "2px" };
488
+ }
489
+ }
490
+ function factPresentation(fact) {
491
+ return {
492
+ kind: "fact",
493
+ statement: fact.statement,
494
+ source: fact.source,
495
+ observedAt: formatTimestamp(fact.observedAt),
496
+ tone: "fact"
497
+ };
498
+ }
499
+ var CAPABILITY_SEVERITY_LABEL = {
500
+ INFO: "Standard",
501
+ NOTABLE: "Notable",
502
+ SIGNIFICANT: "Significant"
503
+ };
504
+ function capabilityPresentation(capability) {
505
+ return {
506
+ kind: "capability",
507
+ statement: capability.statement,
508
+ severity: capability.severity,
509
+ severityLabel: CAPABILITY_SEVERITY_LABEL[capability.severity],
510
+ grantedBy: capability.grantedBy,
511
+ tone: "capability",
512
+ emphasis: capability.severity === "SIGNIFICANT" ? "strong" : "normal"
513
+ };
514
+ }
515
+ var SEVERITY_LABEL = {
516
+ LOW: "Low",
517
+ MEDIUM: "Medium",
518
+ HIGH: "High",
519
+ CRITICAL: "Critical"
520
+ };
521
+ var SEVERITY_TONE = {
522
+ LOW: "caution",
523
+ MEDIUM: "elevated",
524
+ HIGH: "high",
525
+ CRITICAL: "critical"
526
+ };
527
+ var SIGNAL_STATUS_LABEL = {
528
+ ACTIVE: "Active",
529
+ STALE: "Stale",
530
+ RESOLVED: "Resolved",
531
+ DISPUTED_FACT: "Disputed",
532
+ REMOVED: "Withdrawn"
533
+ };
534
+ function signalPresentation(signal, copy, options = {}) {
535
+ const subject = options.subjectNoun ?? "address";
536
+ const active = signal.status === "ACTIVE";
537
+ const raising = active && signal.confidence >= MIN_RAISING_CONFIDENCE;
538
+ const statusLabel = SIGNAL_STATUS_LABEL[signal.status];
539
+ const analystConfirmed = signal.evidence.some((e) => e.analystVerified);
540
+ let inertStatement;
541
+ if (!active) {
542
+ inertStatement = `This signal is ${statusLabel.toLowerCase()} and did not affect this assessment.`;
543
+ } else if (!raising) {
544
+ inertStatement = `Reported at ${formatConfidence(signal.confidence)} confidence, which is too low to raise the risk level on its own. It is shown because it was observed, not because it counted.`;
545
+ }
546
+ const hop = hopPresentation(signal.hops, subject);
547
+ return {
548
+ kind: "signal",
549
+ explanation: signal.explanation,
550
+ severity: signal.severity,
551
+ severityLabel: SEVERITY_LABEL[signal.severity],
552
+ tone: raising ? SEVERITY_TONE[signal.severity] : "inactive",
553
+ status: signal.status,
554
+ statusLabel,
555
+ active,
556
+ raising,
557
+ inertStatement,
558
+ hops: signal.hops,
559
+ hopLabel: hop.label,
560
+ hopStatement: hop.statement,
561
+ indirect: hop.indirect,
562
+ confidence: signal.confidence,
563
+ confidenceLabel: formatConfidence(signal.confidence),
564
+ analystConfirmed,
565
+ attribution: analystConfirmed ? copy.analystConfirmed : copy.notAnalystConfirmed,
566
+ evidenceCount: signal.evidence.length,
567
+ ariaLabel: `${copy.signalLabel}, ${SEVERITY_LABEL[signal.severity].toLowerCase()} severity, ${formatConfidence(signal.confidence)} confidence, ${hop.label.toLowerCase()}. ${signal.explanation}`
568
+ };
569
+ }
570
+ function hopPresentation(hops, subjectNoun = "address") {
571
+ const n = Math.max(0, Math.trunc(hops));
572
+ if (n === 0) {
573
+ return {
574
+ hops: 0,
575
+ label: "Direct",
576
+ statement: `Observed directly on this ${subjectNoun}.`,
577
+ indirect: false
578
+ };
579
+ }
580
+ if (n === 1) {
581
+ return {
582
+ hops: 1,
583
+ label: "1 hop",
584
+ statement: `Observed one hop away. This is an inference about a counterparty of this ${subjectNoun}, not an observation about the ${subjectNoun} itself.`,
585
+ indirect: true
586
+ };
587
+ }
588
+ return {
589
+ hops: n,
590
+ label: `${n} hops`,
591
+ statement: `Observed ${n} hops away. This is an inference about a counterparty ${n} steps removed from this ${subjectNoun}, not an observation about the ${subjectNoun} itself.`,
592
+ indirect: true
593
+ };
594
+ }
595
+ function evidencePresentation(evidence, copy) {
596
+ return {
597
+ id: evidence.id,
598
+ type: evidence.type,
599
+ source: evidence.source,
600
+ observedAt: formatTimestamp(evidence.observedAt),
601
+ confidenceLabel: formatConfidence(evidence.confidence),
602
+ analystConfirmed: evidence.analystVerified,
603
+ attribution: evidence.analystVerified ? copy.analystConfirmed : copy.notAnalystConfirmed,
604
+ reference: evidence.txHash ?? evidence.contract ?? evidence.address
605
+ };
606
+ }
607
+ var UNKNOWN_REASON_LABEL = {
608
+ UNKNOWN: "Not known",
609
+ INSUFFICIENT_DATA: "Not enough data",
610
+ PROVIDER_UNAVAILABLE: "Could not be checked",
611
+ NOT_APPLICABLE: "Not applicable"
612
+ };
613
+ function unknownPresentation(unknown) {
614
+ return {
615
+ kind: "unknown",
616
+ field: unknown.field,
617
+ statement: unknown.statement,
618
+ reasonLabel: UNKNOWN_REASON_LABEL[unknown.reason],
619
+ tone: "unknown"
620
+ };
621
+ }
622
+ var HISTORY_LABEL = {
623
+ ESTABLISHED: "Established history",
624
+ MODERATE: "Some history",
625
+ LIMITED: "Limited history",
626
+ NONE: "No history"
627
+ };
628
+ function historyPresentation(status) {
629
+ return {
630
+ status,
631
+ label: HISTORY_LABEL[status],
632
+ statement: HISTORY_STATUS_MEANING[status],
633
+ tone: "neutral",
634
+ isWarning: false
635
+ };
636
+ }
637
+ function componentRows(components, copy) {
638
+ return [
639
+ { key: "technicalRisk", label: "Technical", value: components.technicalRisk, isCoverage: false },
640
+ { key: "behavioralRisk", label: "Behavioural", value: components.behavioralRisk, isCoverage: false },
641
+ {
642
+ key: "threatIntelligenceRisk",
643
+ label: "Threat intelligence",
644
+ value: components.threatIntelligenceRisk,
645
+ isCoverage: false
646
+ },
647
+ {
648
+ key: "historyConfidence",
649
+ label: "Coverage",
650
+ value: components.historyConfidence,
651
+ isCoverage: true,
652
+ caveat: copy.coverageIsNotRisk
653
+ }
654
+ ];
655
+ }
656
+ function assessmentSections(assessment, copy) {
657
+ const facts = assessment.facts ?? [];
658
+ const capabilities = assessment.capabilities ?? [];
659
+ const signals = assessment.threatSignals ?? [];
660
+ const unknowns = assessment.unknowns ?? [];
661
+ const activeSignals = signals.filter(
662
+ (s) => s.status === "ACTIVE" && s.confidence >= MIN_RAISING_CONFIDENCE
663
+ );
664
+ const inertSignals = signals.filter((s) => !activeSignals.includes(s));
665
+ const isEmpty = facts.length === 0 && capabilities.length === 0 && signals.length === 0 && unknowns.length === 0;
666
+ const parts = [];
667
+ if (facts.length) parts.push(`${facts.length} ${plural(facts.length, "fact")}`);
668
+ if (capabilities.length) {
669
+ parts.push(`${capabilities.length} ${plural(capabilities.length, "capability", "capabilities")}`);
670
+ }
671
+ if (signals.length) parts.push(`${signals.length} ${plural(signals.length, "threat signal")}`);
672
+ if (unknowns.length) parts.push(`${unknowns.length} ${plural(unknowns.length, "unknown")}`);
673
+ return {
674
+ facts,
675
+ capabilities,
676
+ activeSignals,
677
+ inertSignals,
678
+ unknowns,
679
+ isEmpty,
680
+ emptyStatement: isEmpty ? `${copy.nothingReturned} ${copy.notAnAssurance}` : void 0,
681
+ summary: parts.length ? parts.join(" \xB7 ") : copy.nothingReturned
682
+ };
683
+ }
684
+ function availabilityPresentation(assessment) {
685
+ const outage = (assessment.unknowns ?? []).filter((u) => u.reason === "PROVIDER_UNAVAILABLE");
686
+ if (outage.length === 0) return { unavailable: false, tone: "neutral" };
687
+ return {
688
+ unavailable: true,
689
+ statement: outage.map((u) => u.statement).join(" "),
690
+ tone: "neutral"
691
+ };
692
+ }
693
+ var DECISION_LABEL = {
694
+ allow: "No action requested",
695
+ inform: "For your information",
696
+ warn: "Review before continuing",
697
+ warn_strong: "Review carefully before continuing",
698
+ require_confirmation: "Explicit confirmation requested",
699
+ block: "This application will not continue"
700
+ };
701
+ var DECISION_STATEMENT = {
702
+ allow: "This application\u2019s policy asks for nothing further.",
703
+ inform: "Shown because this application\u2019s policy asks that you see it.",
704
+ warn: "This application\u2019s policy asks you to read the detail before continuing.",
705
+ warn_strong: "This application\u2019s policy asks you to read the detail carefully before continuing.",
706
+ require_confirmation: "This application\u2019s policy asks you to confirm explicitly before continuing.",
707
+ block: "This application\u2019s policy stops here. That decision belongs to this application; Decentrys reports evidence and does not stop anything."
708
+ };
709
+ function decisionPresentation(decision) {
710
+ return {
711
+ action: decision.action,
712
+ label: DECISION_LABEL[decision.action],
713
+ statement: DECISION_STATEMENT[decision.action],
714
+ reason: decision.reason,
715
+ blocksUi: false
716
+ };
717
+ }
718
+ function approvalPresentation(details) {
719
+ const spenderName = details.spenderLabel?.trim() || shortenAddress(details.spender);
720
+ const assetName = details.symbol?.trim() || `tokens at ${shortenAddress(details.token)}`;
721
+ if (details.amount === void 0 || details.amount === null || details.amount === "") {
722
+ return {
723
+ unlimited: false,
724
+ amountKnown: false,
725
+ amountLabel: "Not decoded",
726
+ scopeLabel: "Allowance amount not decoded",
727
+ statement: `The allowance amount could not be decoded, so how much this grants ${spenderName} over your ${assetName} is not known.`,
728
+ spenderName,
729
+ assetName,
730
+ tone: "capability",
731
+ emphasis: "strong"
732
+ };
733
+ }
734
+ if (isUnlimitedAllowance(details.amount)) {
735
+ return {
736
+ unlimited: true,
737
+ amountKnown: true,
738
+ amountLabel: "Unlimited",
739
+ scopeLabel: "Unlimited allowance",
740
+ statement: `This grants ${spenderName} permission to spend an unlimited amount of your ${assetName}, now and at any time in the future, until it is revoked.`,
741
+ spenderName,
742
+ assetName,
743
+ tone: "capability",
744
+ emphasis: "strong"
745
+ };
746
+ }
747
+ const baseUnits = details.decimals === void 0;
748
+ const figure = formatTokenAmount(details.amount, details.decimals);
749
+ const amountLabel = baseUnits ? `${figure} base units` : `${figure} ${assetName}`;
750
+ return {
751
+ unlimited: false,
752
+ amountKnown: true,
753
+ amountLabel,
754
+ scopeLabel: "Limited allowance",
755
+ statement: `This grants ${spenderName} permission to spend up to ${figure} ${baseUnits ? `base units of ${assetName}` : assetName}, until it is revoked.`,
756
+ spenderName,
757
+ assetName,
758
+ tone: "capability",
759
+ emphasis: "normal"
760
+ };
761
+ }
762
+ function isUnlimitedAllowance(amount) {
763
+ if (amount.trim().toLowerCase() === "unlimited") return true;
764
+ try {
765
+ return BigInt(amount) >= 1n << 255n;
766
+ } catch {
767
+ return false;
768
+ }
769
+ }
770
+ function formatConfidence(confidence) {
771
+ const clamped = Math.min(1, Math.max(0, confidence));
772
+ return `${Math.round(clamped * 100)}%`;
773
+ }
774
+ function formatTimestamp(iso) {
775
+ const ms = Date.parse(iso);
776
+ if (Number.isNaN(ms)) return iso;
777
+ return `${new Date(ms).toISOString().slice(0, 16).replace("T", " ")} UTC`;
778
+ }
779
+ function shortenAddress(address, lead = 6, tail = 4) {
780
+ if (address.length <= lead + tail + 1) return address;
781
+ return `${address.slice(0, lead)}\u2026${address.slice(-tail)}`;
782
+ }
783
+ function formatTokenAmount(amount, decimals) {
784
+ let value;
785
+ try {
786
+ value = BigInt(amount);
787
+ } catch {
788
+ return amount;
789
+ }
790
+ if (decimals === void 0 || decimals < 0) return group(value.toString());
791
+ const negative = value < 0n;
792
+ const abs = negative ? -value : value;
793
+ const scale = 10n ** BigInt(decimals);
794
+ const whole = abs / scale;
795
+ const fraction = (abs % scale).toString().padStart(decimals, "0").replace(/0+$/, "");
796
+ const text = fraction ? `${group(whole.toString())}.${fraction}` : group(whole.toString());
797
+ return negative ? `-${text}` : text;
798
+ }
799
+ function group(digits) {
800
+ return digits.replace(/\B(?=(\d{3})+(?!\d))/g, ",");
801
+ }
802
+ function plural(count, singular, pluralForm) {
803
+ if (count === 1) return singular;
804
+ return pluralForm ?? `${singular}s`;
805
+ }
806
+
807
+ // src/primitives.tsx
808
+ import { Fragment, jsx as jsx2, jsxs as jsxs2 } from "react/jsx-runtime";
809
+ function cx(...parts) {
810
+ return parts.filter(Boolean).join(" ");
811
+ }
812
+ function StyleGate({ emit }) {
813
+ return emit ? /* @__PURE__ */ jsx2(DecentrysUiStyles, {}) : null;
814
+ }
815
+ function LevelBadge({ level }) {
816
+ return /* @__PURE__ */ jsxs2("span", { className: "dcy-badge", "data-dcy-tone": level.tone, "data-dcy-level": level.level, children: [
817
+ /* @__PURE__ */ jsx2("span", { className: "dcy-glyph", "aria-hidden": "true", children: level.glyph }),
818
+ /* @__PURE__ */ jsx2("span", { children: level.label })
819
+ ] });
820
+ }
821
+ function Chip({ children, title }) {
822
+ return /* @__PURE__ */ jsx2("span", { className: "dcy-chip", title, children });
823
+ }
824
+ function Mono({ children }) {
825
+ return /* @__PURE__ */ jsx2("span", { className: "dcy-mono", children });
826
+ }
827
+ function ScreenReaderText({ children }) {
828
+ return /* @__PURE__ */ jsx2("span", { className: "dcy-sr", children });
829
+ }
830
+ function Section({
831
+ title,
832
+ note,
833
+ children
834
+ }) {
835
+ return /* @__PURE__ */ jsxs2("section", { className: "dcy-section", children: [
836
+ /* @__PURE__ */ jsx2("h4", { className: "dcy-section-title", children: title }),
837
+ note ? /* @__PURE__ */ jsx2("p", { className: "dcy-note", children: note }) : null,
838
+ children
839
+ ] });
840
+ }
841
+ function EmptyNote({ children }) {
842
+ return /* @__PURE__ */ jsx2("p", { className: "dcy-note", "data-dcy-empty": "", children });
843
+ }
844
+ function FactRow({ fact, copy }) {
845
+ const kind = kindPresentation("fact", copy);
846
+ const p = factPresentation(fact);
847
+ return /* @__PURE__ */ jsxs2(
848
+ "li",
849
+ {
850
+ className: "dcy-row",
851
+ "data-dcy-kind": "fact",
852
+ "data-dcy-tone": p.tone,
853
+ "data-dcy-rule": kind.rule,
854
+ children: [
855
+ /* @__PURE__ */ jsx2("span", { className: "dcy-glyph", "aria-hidden": "true", children: kind.glyph }),
856
+ /* @__PURE__ */ jsxs2("div", { className: "dcy-row-body", children: [
857
+ /* @__PURE__ */ jsxs2("p", { className: "dcy-row-statement", children: [
858
+ /* @__PURE__ */ jsx2(Chip, { children: kind.label }),
859
+ " ",
860
+ p.statement
861
+ ] }),
862
+ /* @__PURE__ */ jsxs2("p", { className: "dcy-row-meta", children: [
863
+ /* @__PURE__ */ jsxs2("span", { children: [
864
+ copy.sourceLabel,
865
+ ": ",
866
+ p.source
867
+ ] }),
868
+ /* @__PURE__ */ jsxs2("span", { children: [
869
+ copy.observedLabel,
870
+ ": ",
871
+ p.observedAt
872
+ ] })
873
+ ] })
874
+ ] })
875
+ ]
876
+ }
877
+ );
878
+ }
879
+ function CapabilityRow({
880
+ capability,
881
+ copy
882
+ }) {
883
+ const kind = kindPresentation("capability", copy);
884
+ const p = capabilityPresentation(capability);
885
+ return /* @__PURE__ */ jsxs2(
886
+ "li",
887
+ {
888
+ className: "dcy-row",
889
+ "data-dcy-kind": "capability",
890
+ "data-dcy-tone": p.tone,
891
+ "data-dcy-rule": kind.rule,
892
+ "data-dcy-severity": p.severity,
893
+ children: [
894
+ /* @__PURE__ */ jsx2("span", { className: "dcy-glyph", "aria-hidden": "true", children: kind.glyph }),
895
+ /* @__PURE__ */ jsxs2("div", { className: "dcy-row-body", children: [
896
+ /* @__PURE__ */ jsxs2("p", { className: cx("dcy-row-statement", p.emphasis === "strong" && "dcy-row-strong"), children: [
897
+ /* @__PURE__ */ jsx2(Chip, { children: kind.label }),
898
+ " ",
899
+ /* @__PURE__ */ jsx2(Chip, { children: p.severityLabel }),
900
+ " ",
901
+ p.statement
902
+ ] }),
903
+ p.grantedBy ? /* @__PURE__ */ jsx2("p", { className: "dcy-row-meta", children: /* @__PURE__ */ jsxs2("span", { children: [
904
+ copy.grantedByLabel,
905
+ ": ",
906
+ /* @__PURE__ */ jsx2(Mono, { children: p.grantedBy })
907
+ ] }) }) : null
908
+ ] })
909
+ ]
910
+ }
911
+ );
912
+ }
913
+ function UnknownRow({
914
+ unknown,
915
+ copy
916
+ }) {
917
+ const kind = kindPresentation("unknown", copy);
918
+ const p = unknownPresentation(unknown);
919
+ return /* @__PURE__ */ jsxs2(
920
+ "li",
921
+ {
922
+ className: "dcy-row",
923
+ "data-dcy-kind": "unknown",
924
+ "data-dcy-tone": p.tone,
925
+ "data-dcy-rule": kind.rule,
926
+ children: [
927
+ /* @__PURE__ */ jsx2("span", { className: "dcy-glyph", "aria-hidden": "true", children: kind.glyph }),
928
+ /* @__PURE__ */ jsxs2("div", { className: "dcy-row-body", children: [
929
+ /* @__PURE__ */ jsxs2("p", { className: "dcy-row-statement", children: [
930
+ /* @__PURE__ */ jsx2(Chip, { children: kind.label }),
931
+ " ",
932
+ p.statement
933
+ ] }),
934
+ /* @__PURE__ */ jsxs2("p", { className: "dcy-row-meta", children: [
935
+ /* @__PURE__ */ jsx2("span", { children: p.field }),
936
+ /* @__PURE__ */ jsx2("span", { children: p.reasonLabel })
937
+ ] })
938
+ ] })
939
+ ]
940
+ }
941
+ );
942
+ }
943
+ function EvidenceList({
944
+ evidence,
945
+ copy
946
+ }) {
947
+ if (evidence.length === 0) return null;
948
+ return /* @__PURE__ */ jsxs2("div", { className: "dcy-section", children: [
949
+ /* @__PURE__ */ jsx2("h5", { className: "dcy-section-title", children: copy.evidenceHeading }),
950
+ evidence.map((item) => {
951
+ const e = evidencePresentation(item, copy);
952
+ return /* @__PURE__ */ jsxs2("dl", { className: "dcy-kv", children: [
953
+ /* @__PURE__ */ jsx2("dt", { children: copy.sourceLabel }),
954
+ /* @__PURE__ */ jsx2("dd", { children: e.source }),
955
+ /* @__PURE__ */ jsx2("dt", { children: "Type" }),
956
+ /* @__PURE__ */ jsx2("dd", { children: e.type }),
957
+ /* @__PURE__ */ jsx2("dt", { children: copy.observedLabel }),
958
+ /* @__PURE__ */ jsx2("dd", { children: e.observedAt }),
959
+ /* @__PURE__ */ jsx2("dt", { children: copy.confidenceLabel }),
960
+ /* @__PURE__ */ jsx2("dd", { children: e.confidenceLabel }),
961
+ e.reference ? /* @__PURE__ */ jsxs2(Fragment, { children: [
962
+ /* @__PURE__ */ jsx2("dt", { children: "Reference" }),
963
+ /* @__PURE__ */ jsx2("dd", { children: /* @__PURE__ */ jsx2(Mono, { children: e.reference }) })
964
+ ] }) : null,
965
+ /* @__PURE__ */ jsx2("dt", { children: "Attribution" }),
966
+ /* @__PURE__ */ jsx2("dd", { children: e.attribution }),
967
+ /* @__PURE__ */ jsx2("dt", { children: "Evidence id" }),
968
+ /* @__PURE__ */ jsx2("dd", { children: /* @__PURE__ */ jsx2(Mono, { children: e.id }) })
969
+ ] }, e.id);
970
+ })
971
+ ] });
972
+ }
973
+ function SignalCard({
974
+ signal,
975
+ copy,
976
+ subjectNoun,
977
+ showEvidence = false
978
+ }) {
979
+ const kind = kindPresentation("signal", copy);
980
+ const p = signalPresentation(signal, copy, { subjectNoun });
981
+ return /* @__PURE__ */ jsxs2(
982
+ "li",
983
+ {
984
+ className: "dcy-signal",
985
+ "data-dcy-kind": "signal",
986
+ "data-dcy-tone": p.tone,
987
+ "data-dcy-severity": p.severity,
988
+ "data-dcy-raising": String(p.raising),
989
+ "data-dcy-hops": String(p.hops),
990
+ children: [
991
+ /* @__PURE__ */ jsxs2("div", { className: "dcy-signal-head", children: [
992
+ /* @__PURE__ */ jsx2("span", { className: "dcy-glyph", "aria-hidden": "true", children: kind.glyph }),
993
+ /* @__PURE__ */ jsx2(Chip, { children: kind.label }),
994
+ /* @__PURE__ */ jsxs2(Chip, { children: [
995
+ p.severityLabel,
996
+ " severity"
997
+ ] }),
998
+ /* @__PURE__ */ jsxs2(Chip, { title: copy.confidenceCaveat, children: [
999
+ p.confidenceLabel,
1000
+ " ",
1001
+ copy.confidenceLabel.toLowerCase()
1002
+ ] }),
1003
+ /* @__PURE__ */ jsx2(Chip, { children: p.hopLabel }),
1004
+ p.active ? null : /* @__PURE__ */ jsx2(Chip, { children: p.statusLabel })
1005
+ ] }),
1006
+ /* @__PURE__ */ jsx2("p", { className: "dcy-row-statement dcy-row-strong", children: p.explanation }),
1007
+ /* @__PURE__ */ jsx2("p", { className: "dcy-note", children: p.hopStatement }),
1008
+ p.inertStatement ? /* @__PURE__ */ jsx2("p", { className: "dcy-note", children: p.inertStatement }) : null,
1009
+ /* @__PURE__ */ jsx2("p", { className: "dcy-note", children: p.attribution }),
1010
+ showEvidence ? /* @__PURE__ */ jsx2(EvidenceList, { evidence: signal.evidence, copy }) : null
1011
+ ]
1012
+ }
1013
+ );
1014
+ }
1015
+ function SignalList({
1016
+ signals,
1017
+ copy,
1018
+ subjectNoun,
1019
+ showEvidence
1020
+ }) {
1021
+ if (signals.length === 0) return /* @__PURE__ */ jsx2(EmptyNote, { children: copy.noSignals });
1022
+ return /* @__PURE__ */ jsx2("ul", { className: "dcy-list", children: signals.map((signal, index) => /* @__PURE__ */ jsx2(
1023
+ SignalCard,
1024
+ {
1025
+ signal,
1026
+ copy,
1027
+ subjectNoun,
1028
+ showEvidence
1029
+ },
1030
+ `${signal.type}:${signal.createdAt}:${index}`
1031
+ )) });
1032
+ }
1033
+ function HistoryNote({
1034
+ status,
1035
+ copy
1036
+ }) {
1037
+ const h = historyPresentation(status);
1038
+ return /* @__PURE__ */ jsxs2("p", { className: "dcy-note", "data-dcy-tone": h.tone, "data-dcy-history": h.status, children: [
1039
+ /* @__PURE__ */ jsx2(Chip, { children: h.label }),
1040
+ " ",
1041
+ h.statement
1042
+ ] });
1043
+ }
1044
+ function AvailabilityNote({ assessment }) {
1045
+ const a = availabilityPresentation(assessment);
1046
+ if (!a.unavailable) return null;
1047
+ return /* @__PURE__ */ jsx2("p", { className: "dcy-note", "data-dcy-tone": a.tone, "data-dcy-unavailable": "", children: a.statement });
1048
+ }
1049
+ function DemotedNote({
1050
+ demotedSignals,
1051
+ copy
1052
+ }) {
1053
+ if (!demotedSignals || demotedSignals.length === 0) return null;
1054
+ return /* @__PURE__ */ jsx2(Section, { title: copy.demotedHeading, note: copy.demotedExplanation, children: /* @__PURE__ */ jsx2("p", { className: "dcy-row-meta", children: demotedSignals.map((type) => /* @__PURE__ */ jsx2(Chip, { children: type }, type)) }) });
1055
+ }
1056
+ function ExplanationList({ explanation }) {
1057
+ if (explanation.length === 0) return null;
1058
+ return /* @__PURE__ */ jsx2("ul", { className: "dcy-list", "data-dcy-explanation": "", children: explanation.map((line, index) => /* @__PURE__ */ jsx2("li", { className: "dcy-note", children: line }, `${index}:${line.slice(0, 24)}`)) });
1059
+ }
1060
+ function LevelHeader({
1061
+ assessment,
1062
+ copy,
1063
+ levelLabels,
1064
+ children
1065
+ }) {
1066
+ const level = levelPresentation(assessment.riskLevel, { labels: levelLabels });
1067
+ return /* @__PURE__ */ jsxs2("div", { className: "dcy-head", children: [
1068
+ /* @__PURE__ */ jsxs2("div", { className: "dcy-head-main", children: [
1069
+ /* @__PURE__ */ jsx2("h3", { className: "dcy-title", children: level.label }),
1070
+ /* @__PURE__ */ jsx2("p", { className: "dcy-meaning", children: level.meaning })
1071
+ ] }),
1072
+ /* @__PURE__ */ jsx2(LevelBadge, { level }),
1073
+ children
1074
+ ] });
1075
+ }
1076
+
1077
+ // src/components/TransactionRiskBanner.tsx
1078
+ import { jsx as jsx3, jsxs as jsxs3 } from "react/jsx-runtime";
1079
+ function TransactionRiskBanner({
1080
+ result,
1081
+ subjectNoun = "transaction",
1082
+ actions,
1083
+ onViewDetails,
1084
+ maxExplanationLines = 4,
1085
+ className,
1086
+ style,
1087
+ theme,
1088
+ copy: copyOverrides,
1089
+ levelLabels
1090
+ }) {
1091
+ const { copy, theme: mergedTheme, emitStyles } = useUiConfig({ theme, copy: copyOverrides });
1092
+ const { assessment, decision, cached, demotedSignals } = result;
1093
+ const level = levelPresentation(assessment.riskLevel, { labels: levelLabels });
1094
+ const sections = assessmentSections(assessment, copy);
1095
+ const policy = decisionPresentation(decision);
1096
+ return /* @__PURE__ */ jsxs3(
1097
+ "section",
1098
+ {
1099
+ className: cx("dcy", "dcy-card", className),
1100
+ style: themeStyle(mergedTheme, style),
1101
+ "data-dcy-component": "transaction-risk-banner",
1102
+ "data-dcy-level": assessment.riskLevel,
1103
+ "data-dcy-tone": level.tone,
1104
+ role: level.liveRegionRole,
1105
+ "aria-label": level.ariaLabel,
1106
+ children: [
1107
+ /* @__PURE__ */ jsx3(StyleGate, { emit: emitStyles }),
1108
+ /* @__PURE__ */ jsx3(LevelHeader, { assessment, copy, levelLabels }),
1109
+ /* @__PURE__ */ jsx3(AvailabilityNote, { assessment }),
1110
+ /* @__PURE__ */ jsx3(ExplanationList, { explanation: assessment.explanation.slice(0, maxExplanationLines) }),
1111
+ sections.isEmpty ? /* @__PURE__ */ jsx3(EmptyNote, { children: sections.emptyStatement }) : /* @__PURE__ */ jsx3("p", { className: "dcy-note", "data-dcy-summary": "", children: sections.summary }),
1112
+ sections.activeSignals.length > 0 ? /* @__PURE__ */ jsx3(Section, { title: copy.signalsHeading, children: /* @__PURE__ */ jsx3(SignalList, { signals: sections.activeSignals, copy, subjectNoun }) }) : null,
1113
+ sections.inertSignals.length > 0 ? /* @__PURE__ */ jsxs3(EmptyNote, { children: [
1114
+ sections.inertSignals.length,
1115
+ " ",
1116
+ plural(sections.inertSignals.length, "further signal was", "further signals were"),
1117
+ " ",
1118
+ "observed and did not affect this assessment. They are listed in full in the details."
1119
+ ] }) : null,
1120
+ sections.unknowns.length > 0 ? /* @__PURE__ */ jsx3(Section, { title: copy.unknownsHeading, note: copy.unknownsAreNotRisk, children: /* @__PURE__ */ jsx3("ul", { className: "dcy-list", children: sections.unknowns.map((unknown) => /* @__PURE__ */ jsx3(UnknownRow, { unknown, copy }, unknown.field)) }) }) : null,
1121
+ /* @__PURE__ */ jsx3(HistoryNote, { status: assessment.historyStatus, copy }),
1122
+ /* @__PURE__ */ jsx3(DemotedNote, { demotedSignals, copy }),
1123
+ /* @__PURE__ */ jsxs3("p", { className: "dcy-note", "data-dcy-decision": policy.action, children: [
1124
+ /* @__PURE__ */ jsx3("span", { className: "dcy-chip", children: policy.label }),
1125
+ " ",
1126
+ policy.statement
1127
+ ] }),
1128
+ cached ? /* @__PURE__ */ jsx3("p", { className: "dcy-note", children: copy.cachedNotice }) : null,
1129
+ onViewDetails || actions ? /* @__PURE__ */ jsxs3("div", { className: "dcy-head", children: [
1130
+ onViewDetails ? /* @__PURE__ */ jsx3("button", { type: "button", className: "dcy-btn dcy-btn-quiet", onClick: onViewDetails, children: copy.viewDetails }) : null,
1131
+ actions
1132
+ ] }) : null
1133
+ ]
1134
+ }
1135
+ );
1136
+ }
1137
+
1138
+ // src/components/ApprovalWarning.tsx
1139
+ import { jsx as jsx4, jsxs as jsxs4 } from "react/jsx-runtime";
1140
+ function ApprovalWarning({
1141
+ result,
1142
+ approval,
1143
+ actions,
1144
+ onViewDetails,
1145
+ className,
1146
+ style,
1147
+ theme,
1148
+ copy: copyOverrides,
1149
+ levelLabels
1150
+ }) {
1151
+ const { copy, theme: mergedTheme, emitStyles } = useUiConfig({ theme, copy: copyOverrides });
1152
+ const { assessment, decision, demotedSignals } = result;
1153
+ const level = levelPresentation(assessment.riskLevel, { labels: levelLabels });
1154
+ const scope = approvalPresentation(approval);
1155
+ const sections = assessmentSections(assessment, copy);
1156
+ const policy = decisionPresentation(decision);
1157
+ return /* @__PURE__ */ jsxs4(
1158
+ "section",
1159
+ {
1160
+ className: cx("dcy", "dcy-card", className),
1161
+ style: themeStyle(mergedTheme, style),
1162
+ "data-dcy-component": "approval-warning",
1163
+ "data-dcy-level": assessment.riskLevel,
1164
+ "data-dcy-tone": level.tone,
1165
+ "data-dcy-unlimited": String(scope.unlimited),
1166
+ role: level.liveRegionRole,
1167
+ "aria-label": `${scope.scopeLabel}. ${level.ariaLabel}`,
1168
+ children: [
1169
+ /* @__PURE__ */ jsx4(StyleGate, { emit: emitStyles }),
1170
+ /* @__PURE__ */ jsx4(LevelHeader, { assessment, copy, levelLabels }),
1171
+ /* @__PURE__ */ jsx4(AvailabilityNote, { assessment }),
1172
+ /* @__PURE__ */ jsxs4(Section, { title: copy.approvalHeading, children: [
1173
+ /* @__PURE__ */ jsxs4(
1174
+ "div",
1175
+ {
1176
+ className: "dcy-row",
1177
+ "data-dcy-kind": "capability",
1178
+ "data-dcy-tone": scope.tone,
1179
+ "data-dcy-rule": "dashed",
1180
+ children: [
1181
+ /* @__PURE__ */ jsx4("span", { className: "dcy-glyph", "aria-hidden": "true", children: "\u25C7" }),
1182
+ /* @__PURE__ */ jsxs4("div", { className: "dcy-row-body", children: [
1183
+ /* @__PURE__ */ jsxs4("p", { className: cx("dcy-row-statement", scope.emphasis === "strong" && "dcy-row-strong"), children: [
1184
+ /* @__PURE__ */ jsx4(Chip, { children: scope.scopeLabel }),
1185
+ " ",
1186
+ scope.statement
1187
+ ] }),
1188
+ /* @__PURE__ */ jsx4("p", { className: "dcy-note", children: copy.approvalRevocation })
1189
+ ] })
1190
+ ]
1191
+ }
1192
+ ),
1193
+ /* @__PURE__ */ jsxs4("dl", { className: "dcy-kv", children: [
1194
+ /* @__PURE__ */ jsx4("dt", { children: copy.spenderLabel }),
1195
+ /* @__PURE__ */ jsxs4("dd", { children: [
1196
+ /* @__PURE__ */ jsx4(Mono, { children: approval.spender }),
1197
+ approval.spenderLabel ? ` \u2014 ${approval.spenderLabel}` : null
1198
+ ] }),
1199
+ /* @__PURE__ */ jsx4("dt", { children: copy.tokenLabel }),
1200
+ /* @__PURE__ */ jsxs4("dd", { children: [
1201
+ /* @__PURE__ */ jsx4(Mono, { children: approval.token }),
1202
+ approval.symbol ? ` \u2014 ${approval.symbol}` : null
1203
+ ] }),
1204
+ /* @__PURE__ */ jsx4("dt", { children: "Amount" }),
1205
+ /* @__PURE__ */ jsx4("dd", { children: scope.amountLabel })
1206
+ ] })
1207
+ ] }),
1208
+ /* @__PURE__ */ jsx4(Section, { title: copy.signalsHeading, children: /* @__PURE__ */ jsx4(
1209
+ SignalList,
1210
+ {
1211
+ signals: [...sections.activeSignals, ...sections.inertSignals],
1212
+ copy,
1213
+ subjectNoun: "spender"
1214
+ }
1215
+ ) }),
1216
+ sections.unknowns.length > 0 ? /* @__PURE__ */ jsx4(Section, { title: copy.unknownsHeading, note: copy.unknownsAreNotRisk, children: /* @__PURE__ */ jsx4("ul", { className: "dcy-list", children: sections.unknowns.map((unknown) => /* @__PURE__ */ jsx4(UnknownRow, { unknown, copy }, unknown.field)) }) }) : null,
1217
+ /* @__PURE__ */ jsx4(HistoryNote, { status: assessment.historyStatus, copy }),
1218
+ demotedSignals && demotedSignals.length > 0 ? /* @__PURE__ */ jsxs4(EmptyNote, { children: [
1219
+ copy.demotedHeading,
1220
+ ": ",
1221
+ demotedSignals.join(", "),
1222
+ ". ",
1223
+ copy.demotedExplanation
1224
+ ] }) : null,
1225
+ /* @__PURE__ */ jsxs4("p", { className: "dcy-note", "data-dcy-decision": policy.action, children: [
1226
+ /* @__PURE__ */ jsx4("span", { className: "dcy-chip", children: policy.label }),
1227
+ " ",
1228
+ policy.statement
1229
+ ] }),
1230
+ onViewDetails || actions ? /* @__PURE__ */ jsxs4("div", { className: "dcy-head", children: [
1231
+ onViewDetails ? /* @__PURE__ */ jsx4("button", { type: "button", className: "dcy-btn dcy-btn-quiet", onClick: onViewDetails, children: copy.viewDetails }) : null,
1232
+ actions
1233
+ ] }) : null
1234
+ ]
1235
+ }
1236
+ );
1237
+ }
1238
+
1239
+ // src/components/ContractFacts.tsx
1240
+ import { Fragment as Fragment2, jsx as jsx5, jsxs as jsxs5 } from "react/jsx-runtime";
1241
+ function ContractFacts({
1242
+ assessment,
1243
+ demotedSignals,
1244
+ onViewSignals,
1245
+ title,
1246
+ className,
1247
+ style,
1248
+ theme,
1249
+ copy: copyOverrides
1250
+ }) {
1251
+ const { copy, theme: mergedTheme, emitStyles } = useUiConfig({ theme, copy: copyOverrides });
1252
+ const sections = assessmentSections(assessment, copy);
1253
+ const signalCount = sections.activeSignals.length + sections.inertSignals.length;
1254
+ return /* @__PURE__ */ jsxs5(
1255
+ "section",
1256
+ {
1257
+ className: cx("dcy", "dcy-card", className),
1258
+ style: themeStyle(mergedTheme, style),
1259
+ "data-dcy-component": "contract-facts",
1260
+ "aria-label": title ?? "Contract facts and capabilities",
1261
+ children: [
1262
+ /* @__PURE__ */ jsx5(StyleGate, { emit: emitStyles }),
1263
+ title ? /* @__PURE__ */ jsx5("h3", { className: "dcy-title", children: title }) : null,
1264
+ /* @__PURE__ */ jsx5(AvailabilityNote, { assessment }),
1265
+ /* @__PURE__ */ jsx5(Section, { title: copy.capabilitiesHeading, note: copy.capabilitiesAreNotFindings, children: sections.capabilities.length === 0 ? /* @__PURE__ */ jsx5(EmptyNote, { children: copy.noCapabilities }) : /* @__PURE__ */ jsx5("ul", { className: "dcy-list", children: sections.capabilities.map((capability, index) => /* @__PURE__ */ jsx5(
1266
+ CapabilityRow,
1267
+ {
1268
+ capability,
1269
+ copy
1270
+ },
1271
+ `${capability.type}:${index}`
1272
+ )) }) }),
1273
+ /* @__PURE__ */ jsx5(Section, { title: copy.factsHeading, note: copy.factsAreNotFindings, children: sections.facts.length === 0 ? /* @__PURE__ */ jsx5(EmptyNote, { children: copy.noFacts }) : /* @__PURE__ */ jsx5("ul", { className: "dcy-list", children: sections.facts.map((fact, index) => /* @__PURE__ */ jsx5(FactRow, { fact, copy }, `${fact.type}:${index}`)) }) }),
1274
+ /* @__PURE__ */ jsx5(Section, { title: copy.unknownsHeading, note: copy.unknownsAreNotRisk, children: sections.unknowns.length === 0 ? /* @__PURE__ */ jsx5(EmptyNote, { children: copy.noUnknowns }) : /* @__PURE__ */ jsx5("ul", { className: "dcy-list", children: sections.unknowns.map((unknown) => /* @__PURE__ */ jsx5(UnknownRow, { unknown, copy }, unknown.field)) }) }),
1275
+ /* @__PURE__ */ jsx5(HistoryNote, { status: assessment.historyStatus, copy }),
1276
+ /* @__PURE__ */ jsx5(DemotedNote, { demotedSignals, copy }),
1277
+ signalCount > 0 ? /* @__PURE__ */ jsxs5("p", { className: "dcy-note", "data-dcy-signals-elsewhere": "", children: [
1278
+ signalCount,
1279
+ " ",
1280
+ plural(signalCount, "threat signal is", "threat signals are"),
1281
+ " reported for this subject and ",
1282
+ plural(signalCount, "is", "are"),
1283
+ " shown separately.",
1284
+ onViewSignals ? /* @__PURE__ */ jsxs5(Fragment2, { children: [
1285
+ " ",
1286
+ /* @__PURE__ */ jsx5("button", { type: "button", className: "dcy-btn dcy-btn-quiet", onClick: onViewSignals, children: copy.signalsHeading })
1287
+ ] }) : null
1288
+ ] }) : /* @__PURE__ */ jsx5("p", { className: "dcy-note", children: copy.noSignals })
1289
+ ]
1290
+ }
1291
+ );
1292
+ }
1293
+
1294
+ // src/components/ThreatSignalList.tsx
1295
+ import { useState } from "react";
1296
+ import { jsx as jsx6, jsxs as jsxs6 } from "react/jsx-runtime";
1297
+ function ThreatSignalList({
1298
+ signals,
1299
+ subjectNoun = "address",
1300
+ defaultShowEvidence = false,
1301
+ showEvidenceToggle = true,
1302
+ title,
1303
+ className,
1304
+ style,
1305
+ theme,
1306
+ copy: copyOverrides
1307
+ }) {
1308
+ const { copy, theme: mergedTheme, emitStyles } = useUiConfig({ theme, copy: copyOverrides });
1309
+ const [showEvidence, setShowEvidence] = useState(defaultShowEvidence);
1310
+ const evidenceVisible = showEvidenceToggle ? showEvidence : defaultShowEvidence;
1311
+ return /* @__PURE__ */ jsxs6(
1312
+ "section",
1313
+ {
1314
+ className: cx("dcy", "dcy-card", className),
1315
+ style: themeStyle(mergedTheme, style),
1316
+ "data-dcy-component": "threat-signal-list",
1317
+ "data-dcy-signal-count": String(signals.length),
1318
+ "aria-label": title ?? copy.signalsHeading,
1319
+ children: [
1320
+ /* @__PURE__ */ jsx6(StyleGate, { emit: emitStyles }),
1321
+ /* @__PURE__ */ jsx6(Section, { title: title ?? copy.signalsHeading, children: signals.length === 0 ? /* @__PURE__ */ jsx6(EmptyNote, { children: copy.noSignals }) : /* @__PURE__ */ jsx6(
1322
+ SignalList,
1323
+ {
1324
+ signals,
1325
+ copy,
1326
+ subjectNoun,
1327
+ showEvidence: evidenceVisible
1328
+ }
1329
+ ) }),
1330
+ signals.length > 0 && showEvidenceToggle ? /* @__PURE__ */ jsx6("div", { children: /* @__PURE__ */ jsx6(
1331
+ "button",
1332
+ {
1333
+ type: "button",
1334
+ className: "dcy-btn dcy-btn-quiet",
1335
+ "aria-expanded": evidenceVisible,
1336
+ onClick: () => setShowEvidence((value) => !value),
1337
+ children: evidenceVisible ? copy.hideEvidence : copy.showEvidence
1338
+ }
1339
+ ) }) : null
1340
+ ]
1341
+ }
1342
+ );
1343
+ }
1344
+
1345
+ // src/components/RiskDetailsModal.tsx
1346
+ import { useEffect, useId, useRef } from "react";
1347
+ import { createPortal } from "react-dom";
1348
+ import { Fragment as Fragment3, jsx as jsx7, jsxs as jsxs7 } from "react/jsx-runtime";
1349
+ function RiskDetailsModal({
1350
+ result,
1351
+ open,
1352
+ onClose,
1353
+ subjectNoun = "address",
1354
+ portal = true,
1355
+ className,
1356
+ style,
1357
+ theme,
1358
+ copy: copyOverrides,
1359
+ levelLabels
1360
+ }) {
1361
+ const { copy, theme: mergedTheme, emitStyles } = useUiConfig({ theme, copy: copyOverrides });
1362
+ const titleId = useId();
1363
+ const dialogRef = useRef(null);
1364
+ const restoreFocusTo = useRef(null);
1365
+ useEffect(() => {
1366
+ if (!open) return void 0;
1367
+ restoreFocusTo.current = typeof document === "undefined" ? null : document.activeElement;
1368
+ dialogRef.current?.focus();
1369
+ const onKeyDown = (event) => {
1370
+ if (event.key === "Escape") {
1371
+ event.stopPropagation();
1372
+ onClose();
1373
+ }
1374
+ };
1375
+ document.addEventListener("keydown", onKeyDown);
1376
+ return () => {
1377
+ document.removeEventListener("keydown", onKeyDown);
1378
+ const previous = restoreFocusTo.current;
1379
+ if (previous instanceof HTMLElement) previous.focus();
1380
+ };
1381
+ }, [open, onClose]);
1382
+ if (!open) return null;
1383
+ const { assessment, subject, decision, cached, demotedSignals } = result;
1384
+ const level = levelPresentation(assessment.riskLevel, { labels: levelLabels });
1385
+ const sections = assessmentSections(assessment, copy);
1386
+ const policy = decisionPresentation(decision);
1387
+ const dialog = /* @__PURE__ */ jsxs7(
1388
+ "div",
1389
+ {
1390
+ className: cx("dcy", "dcy-backdrop"),
1391
+ style: themeStyle(mergedTheme),
1392
+ "data-dcy-component": "risk-details-modal",
1393
+ onClick: (event) => {
1394
+ if (event.target === event.currentTarget) onClose();
1395
+ },
1396
+ children: [
1397
+ /* @__PURE__ */ jsx7(StyleGate, { emit: emitStyles }),
1398
+ /* @__PURE__ */ jsxs7(
1399
+ "div",
1400
+ {
1401
+ ref: dialogRef,
1402
+ role: "dialog",
1403
+ "aria-modal": "true",
1404
+ "aria-labelledby": titleId,
1405
+ tabIndex: -1,
1406
+ className: cx("dcy-modal", className),
1407
+ style,
1408
+ "data-dcy-level": assessment.riskLevel,
1409
+ "data-dcy-tone": level.tone,
1410
+ children: [
1411
+ /* @__PURE__ */ jsxs7("div", { className: "dcy-modal-head", children: [
1412
+ /* @__PURE__ */ jsx7("h2", { className: "dcy-title", id: titleId, children: copy.detailsTitle }),
1413
+ /* @__PURE__ */ jsx7("button", { type: "button", className: "dcy-btn", onClick: onClose, children: copy.close })
1414
+ ] }),
1415
+ /* @__PURE__ */ jsxs7("div", { className: "dcy-head", children: [
1416
+ /* @__PURE__ */ jsxs7("div", { className: "dcy-head-main", children: [
1417
+ /* @__PURE__ */ jsx7("h3", { className: "dcy-title", children: level.label }),
1418
+ /* @__PURE__ */ jsx7("p", { className: "dcy-meaning", children: level.meaning })
1419
+ ] }),
1420
+ /* @__PURE__ */ jsx7(LevelBadge, { level })
1421
+ ] }),
1422
+ /* @__PURE__ */ jsxs7("dl", { className: "dcy-kv", children: [
1423
+ /* @__PURE__ */ jsx7("dt", { children: "Subject" }),
1424
+ /* @__PURE__ */ jsx7("dd", { children: /* @__PURE__ */ jsx7(Mono, { children: subject.identifier }) }),
1425
+ /* @__PURE__ */ jsx7("dt", { children: "Chain" }),
1426
+ /* @__PURE__ */ jsx7("dd", { children: subject.chain }),
1427
+ /* @__PURE__ */ jsx7("dt", { children: "Kind" }),
1428
+ /* @__PURE__ */ jsx7("dd", { children: subject.kind }),
1429
+ /* @__PURE__ */ jsx7("dt", { children: copy.confidenceLabel }),
1430
+ /* @__PURE__ */ jsxs7("dd", { children: [
1431
+ formatConfidence(assessment.confidence),
1432
+ " \u2014 ",
1433
+ copy.confidenceCaveat
1434
+ ] }),
1435
+ /* @__PURE__ */ jsx7("dt", { children: copy.modelLabel }),
1436
+ /* @__PURE__ */ jsx7("dd", { children: /* @__PURE__ */ jsx7(Mono, { children: assessment.modelVersion }) }),
1437
+ /* @__PURE__ */ jsx7("dt", { children: copy.assessedLabel }),
1438
+ /* @__PURE__ */ jsx7("dd", { children: formatTimestamp(assessment.assessedAt) })
1439
+ ] }),
1440
+ /* @__PURE__ */ jsx7(AvailabilityNote, { assessment }),
1441
+ /* @__PURE__ */ jsx7(Section, { title: "Why this level", children: /* @__PURE__ */ jsx7(ExplanationList, { explanation: assessment.explanation }) }),
1442
+ sections.isEmpty ? /* @__PURE__ */ jsx7(EmptyNote, { children: sections.emptyStatement }) : null,
1443
+ /* @__PURE__ */ jsx7(Section, { title: copy.signalsHeading, children: /* @__PURE__ */ jsx7(
1444
+ SignalList,
1445
+ {
1446
+ signals: [...sections.activeSignals, ...sections.inertSignals],
1447
+ copy,
1448
+ subjectNoun,
1449
+ showEvidence: true
1450
+ }
1451
+ ) }),
1452
+ /* @__PURE__ */ jsx7(Section, { title: copy.capabilitiesHeading, note: copy.capabilitiesAreNotFindings, children: sections.capabilities.length === 0 ? /* @__PURE__ */ jsx7(EmptyNote, { children: copy.noCapabilities }) : /* @__PURE__ */ jsx7("ul", { className: "dcy-list", children: sections.capabilities.map((capability, index) => /* @__PURE__ */ jsx7(CapabilityRow, { capability, copy }, `${capability.type}:${index}`)) }) }),
1453
+ /* @__PURE__ */ jsx7(Section, { title: copy.factsHeading, note: copy.factsAreNotFindings, children: sections.facts.length === 0 ? /* @__PURE__ */ jsx7(EmptyNote, { children: copy.noFacts }) : /* @__PURE__ */ jsx7("ul", { className: "dcy-list", children: sections.facts.map((fact, index) => /* @__PURE__ */ jsx7(FactRow, { fact, copy }, `${fact.type}:${index}`)) }) }),
1454
+ /* @__PURE__ */ jsx7(Section, { title: copy.unknownsHeading, note: copy.unknownsAreNotRisk, children: sections.unknowns.length === 0 ? /* @__PURE__ */ jsx7(EmptyNote, { children: copy.noUnknowns }) : /* @__PURE__ */ jsx7("ul", { className: "dcy-list", children: sections.unknowns.map((unknown) => /* @__PURE__ */ jsx7(UnknownRow, { unknown, copy }, unknown.field)) }) }),
1455
+ /* @__PURE__ */ jsx7(Section, { title: copy.historyHeading, children: /* @__PURE__ */ jsx7(HistoryNote, { status: assessment.historyStatus, copy }) }),
1456
+ /* @__PURE__ */ jsx7(Section, { title: copy.componentsHeading, children: /* @__PURE__ */ jsx7("dl", { className: "dcy-kv", children: componentRows(assessment.components, copy).map((row) => /* @__PURE__ */ jsx7(ComponentEntry, { label: row.label, value: row.value, caveat: row.caveat }, row.key)) }) }),
1457
+ /* @__PURE__ */ jsx7(DemotedNote, { demotedSignals, copy }),
1458
+ /* @__PURE__ */ jsx7("hr", { className: "dcy-hr" }),
1459
+ /* @__PURE__ */ jsxs7("p", { className: "dcy-note", "data-dcy-decision": policy.action, children: [
1460
+ /* @__PURE__ */ jsx7("span", { className: "dcy-chip", children: policy.label }),
1461
+ " ",
1462
+ policy.statement,
1463
+ " ",
1464
+ copy.policyIsYours
1465
+ ] }),
1466
+ cached ? /* @__PURE__ */ jsx7("p", { className: "dcy-note", children: copy.cachedNotice }) : null
1467
+ ]
1468
+ }
1469
+ )
1470
+ ]
1471
+ }
1472
+ );
1473
+ if (portal && typeof document !== "undefined") {
1474
+ return createPortal(dialog, document.body);
1475
+ }
1476
+ return dialog;
1477
+ }
1478
+ function ComponentEntry({
1479
+ label,
1480
+ value,
1481
+ caveat
1482
+ }) {
1483
+ return /* @__PURE__ */ jsxs7(Fragment3, { children: [
1484
+ /* @__PURE__ */ jsx7("dt", { children: label }),
1485
+ /* @__PURE__ */ jsxs7("dd", { children: [
1486
+ Math.round(value),
1487
+ " / 100",
1488
+ caveat ? /* @__PURE__ */ jsxs7("span", { className: "dcy-note", children: [
1489
+ " ",
1490
+ caveat
1491
+ ] }) : null
1492
+ ] })
1493
+ ] });
1494
+ }
1495
+
1496
+ // src/components/TokenSecurityPanel.tsx
1497
+ import { Fragment as Fragment4, jsx as jsx8, jsxs as jsxs8 } from "react/jsx-runtime";
1498
+ function TokenSecurityPanel({
1499
+ result,
1500
+ tokenName,
1501
+ tokenSymbol,
1502
+ onViewDetails,
1503
+ showComponents = true,
1504
+ className,
1505
+ style,
1506
+ theme,
1507
+ copy: copyOverrides,
1508
+ levelLabels
1509
+ }) {
1510
+ const { copy, theme: mergedTheme, emitStyles } = useUiConfig({ theme, copy: copyOverrides });
1511
+ const { assessment, subject, decision, cached, demotedSignals } = result;
1512
+ const level = levelPresentation(assessment.riskLevel, { labels: levelLabels });
1513
+ const sections = assessmentSections(assessment, copy);
1514
+ const policy = decisionPresentation(decision);
1515
+ return /* @__PURE__ */ jsxs8(
1516
+ "section",
1517
+ {
1518
+ className: cx("dcy", "dcy-card", className),
1519
+ style: themeStyle(mergedTheme, style),
1520
+ "data-dcy-component": "token-security-panel",
1521
+ "data-dcy-level": assessment.riskLevel,
1522
+ "data-dcy-tone": level.tone,
1523
+ "aria-label": level.ariaLabel,
1524
+ children: [
1525
+ /* @__PURE__ */ jsx8(StyleGate, { emit: emitStyles }),
1526
+ /* @__PURE__ */ jsx8(LevelHeader, { assessment, copy, levelLabels }),
1527
+ /* @__PURE__ */ jsxs8("p", { className: "dcy-note", children: [
1528
+ tokenName || tokenSymbol ? /* @__PURE__ */ jsxs8(Fragment4, { children: [
1529
+ /* @__PURE__ */ jsx8(Chip, { children: tokenSymbol ?? tokenName }),
1530
+ " ",
1531
+ tokenName && tokenSymbol ? `${tokenName} \u2014 ` : null
1532
+ ] }) : null,
1533
+ /* @__PURE__ */ jsx8(Mono, { children: subject.identifier }),
1534
+ " on ",
1535
+ subject.chain
1536
+ ] }),
1537
+ /* @__PURE__ */ jsx8(AvailabilityNote, { assessment }),
1538
+ /* @__PURE__ */ jsx8(ExplanationList, { explanation: assessment.explanation }),
1539
+ sections.isEmpty ? /* @__PURE__ */ jsx8(EmptyNote, { children: sections.emptyStatement }) : null,
1540
+ /* @__PURE__ */ jsx8(Section, { title: copy.capabilitiesHeading, note: copy.capabilitiesAreNotFindings, children: sections.capabilities.length === 0 ? /* @__PURE__ */ jsx8(EmptyNote, { children: copy.noCapabilities }) : /* @__PURE__ */ jsx8("ul", { className: "dcy-list", children: sections.capabilities.map((capability, index) => /* @__PURE__ */ jsx8(CapabilityRow, { capability, copy }, `${capability.type}:${index}`)) }) }),
1541
+ /* @__PURE__ */ jsx8(Section, { title: copy.factsHeading, note: copy.factsAreNotFindings, children: sections.facts.length === 0 ? /* @__PURE__ */ jsx8(EmptyNote, { children: copy.noFacts }) : /* @__PURE__ */ jsx8("ul", { className: "dcy-list", children: sections.facts.map((fact, index) => /* @__PURE__ */ jsx8(FactRow, { fact, copy }, `${fact.type}:${index}`)) }) }),
1542
+ /* @__PURE__ */ jsx8(Section, { title: copy.signalsHeading, children: /* @__PURE__ */ jsx8(
1543
+ SignalList,
1544
+ {
1545
+ signals: [...sections.activeSignals, ...sections.inertSignals],
1546
+ copy,
1547
+ subjectNoun: "token"
1548
+ }
1549
+ ) }),
1550
+ /* @__PURE__ */ jsx8(Section, { title: copy.unknownsHeading, note: copy.unknownsAreNotRisk, children: sections.unknowns.length === 0 ? /* @__PURE__ */ jsx8(EmptyNote, { children: copy.noUnknowns }) : /* @__PURE__ */ jsx8("ul", { className: "dcy-list", children: sections.unknowns.map((unknown) => /* @__PURE__ */ jsx8(UnknownRow, { unknown, copy }, unknown.field)) }) }),
1551
+ /* @__PURE__ */ jsx8(HistoryNote, { status: assessment.historyStatus, copy }),
1552
+ showComponents ? /* @__PURE__ */ jsx8(Section, { title: copy.componentsHeading, children: /* @__PURE__ */ jsx8("dl", { className: "dcy-kv", children: componentRows(assessment.components, copy).map((row) => /* @__PURE__ */ jsx8(ComponentEntry2, { label: row.label, value: row.value, caveat: row.caveat }, row.key)) }) }) : null,
1553
+ /* @__PURE__ */ jsx8(DemotedNote, { demotedSignals, copy }),
1554
+ /* @__PURE__ */ jsxs8("p", { className: "dcy-note", "data-dcy-decision": policy.action, children: [
1555
+ /* @__PURE__ */ jsx8("span", { className: "dcy-chip", children: policy.label }),
1556
+ " ",
1557
+ policy.statement
1558
+ ] }),
1559
+ cached ? /* @__PURE__ */ jsx8("p", { className: "dcy-note", children: copy.cachedNotice }) : null,
1560
+ onViewDetails ? /* @__PURE__ */ jsx8("div", { children: /* @__PURE__ */ jsx8("button", { type: "button", className: "dcy-btn dcy-btn-quiet", onClick: onViewDetails, children: copy.viewDetails }) }) : null
1561
+ ]
1562
+ }
1563
+ );
1564
+ }
1565
+ function ComponentEntry2({
1566
+ label,
1567
+ value,
1568
+ caveat
1569
+ }) {
1570
+ return /* @__PURE__ */ jsxs8(Fragment4, { children: [
1571
+ /* @__PURE__ */ jsx8("dt", { children: label }),
1572
+ /* @__PURE__ */ jsxs8("dd", { children: [
1573
+ Math.round(value),
1574
+ " / 100",
1575
+ caveat ? /* @__PURE__ */ jsxs8("span", { className: "dcy-note", children: [
1576
+ " ",
1577
+ caveat
1578
+ ] }) : null
1579
+ ] })
1580
+ ] });
1581
+ }
1582
+
1583
+ // src/components/AddressRiskBadge.tsx
1584
+ import { Fragment as Fragment5, jsx as jsx9, jsxs as jsxs9 } from "react/jsx-runtime";
1585
+ function AddressRiskBadge(props) {
1586
+ const { assessment, level, address, onSelect, className, style, theme, copy, levelLabels } = props;
1587
+ const { theme: mergedTheme, emitStyles } = useUiConfig({ theme, copy });
1588
+ const resolved = assessment?.riskLevel ?? level;
1589
+ if (!resolved) return null;
1590
+ const presentation = levelPresentation(resolved, { labels: levelLabels });
1591
+ const rootClass = cx("dcy", "dcy-inline", className);
1592
+ const rootStyle = themeStyle(mergedTheme, style);
1593
+ const content = /* @__PURE__ */ jsxs9(Fragment5, { children: [
1594
+ /* @__PURE__ */ jsx9(LevelBadge, { level: presentation }),
1595
+ address ? /* @__PURE__ */ jsx9(Mono, { children: shortenAddress(address) }) : null,
1596
+ /* @__PURE__ */ jsx9(ScreenReaderText, { children: presentation.meaning })
1597
+ ] });
1598
+ if (onSelect) {
1599
+ return /* @__PURE__ */ jsxs9(Fragment5, { children: [
1600
+ /* @__PURE__ */ jsx9(StyleGate, { emit: emitStyles }),
1601
+ /* @__PURE__ */ jsx9(
1602
+ "button",
1603
+ {
1604
+ type: "button",
1605
+ className: rootClass,
1606
+ style: rootStyle,
1607
+ title: presentation.meaning,
1608
+ onClick: onSelect,
1609
+ "data-dcy-component": "address-risk-badge",
1610
+ "data-dcy-level": resolved,
1611
+ "data-dcy-tone": presentation.tone,
1612
+ children: content
1613
+ }
1614
+ )
1615
+ ] });
1616
+ }
1617
+ return /* @__PURE__ */ jsxs9(Fragment5, { children: [
1618
+ /* @__PURE__ */ jsx9(StyleGate, { emit: emitStyles }),
1619
+ /* @__PURE__ */ jsx9(
1620
+ "span",
1621
+ {
1622
+ className: rootClass,
1623
+ style: rootStyle,
1624
+ title: presentation.meaning,
1625
+ "data-dcy-component": "address-risk-badge",
1626
+ "data-dcy-level": resolved,
1627
+ "data-dcy-tone": presentation.tone,
1628
+ children: content
1629
+ }
1630
+ )
1631
+ ] });
1632
+ }
1633
+ export {
1634
+ AddressRiskBadge,
1635
+ ApprovalWarning,
1636
+ Chip,
1637
+ ContractFacts,
1638
+ DECENTRYS_UI_CSS,
1639
+ DEFAULT_COPY,
1640
+ DecentrysUiProvider,
1641
+ DecentrysUiStyles,
1642
+ EmptyNote,
1643
+ LevelBadge,
1644
+ MIN_RAISING_CONFIDENCE,
1645
+ Mono,
1646
+ RISK_LEVEL_LABEL,
1647
+ RiskDetailsModal,
1648
+ ScreenReaderText,
1649
+ Section,
1650
+ THEME_TOKENS,
1651
+ ThreatSignalList,
1652
+ TokenSecurityPanel,
1653
+ TransactionRiskBanner,
1654
+ approvalPresentation,
1655
+ assessmentSections,
1656
+ availabilityPresentation,
1657
+ capabilityPresentation,
1658
+ componentRows,
1659
+ cx,
1660
+ decisionPresentation,
1661
+ evidencePresentation,
1662
+ factPresentation,
1663
+ formatConfidence,
1664
+ formatTimestamp,
1665
+ formatTokenAmount,
1666
+ historyPresentation,
1667
+ hopPresentation,
1668
+ isUnlimitedAllowance,
1669
+ kindPresentation,
1670
+ levelPresentation,
1671
+ levelRank,
1672
+ plural,
1673
+ resolveCopy,
1674
+ shortenAddress,
1675
+ signalPresentation,
1676
+ themeStyle,
1677
+ toneBgVar,
1678
+ toneVar,
1679
+ unknownPresentation,
1680
+ useUiConfig
1681
+ };