@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.
- package/LICENSE +21 -0
- package/README.md +51 -0
- package/dist/browser/decentrys-ui.js +1733 -0
- package/dist/browser/decentrys-ui.mjs +1681 -0
- package/dist/browser/react-dom-global.d.ts +6 -0
- package/dist/browser/react-dom-global.d.ts.map +1 -0
- package/dist/browser/react-dom-global.js +18 -0
- package/dist/browser/react-dom-global.js.map +1 -0
- package/dist/browser/react-global.d.ts +25 -0
- package/dist/browser/react-global.d.ts.map +1 -0
- package/dist/browser/react-global.js +39 -0
- package/dist/browser/react-global.js.map +1 -0
- package/dist/browser/react-jsx-runtime.d.ts +17 -0
- package/dist/browser/react-jsx-runtime.d.ts.map +1 -0
- package/dist/browser/react-jsx-runtime.js +27 -0
- package/dist/browser/react-jsx-runtime.js.map +1 -0
- package/dist/components/AddressRiskBadge.d.ts +39 -0
- package/dist/components/AddressRiskBadge.d.ts.map +1 -0
- package/dist/components/AddressRiskBadge.js +26 -0
- package/dist/components/AddressRiskBadge.js.map +1 -0
- package/dist/components/ApprovalWarning.d.ts +29 -0
- package/dist/components/ApprovalWarning.d.ts.map +1 -0
- package/dist/components/ApprovalWarning.js +18 -0
- package/dist/components/ApprovalWarning.js.map +1 -0
- package/dist/components/ContractFacts.d.ts +27 -0
- package/dist/components/ContractFacts.d.ts.map +1 -0
- package/dist/components/ContractFacts.js +15 -0
- package/dist/components/ContractFacts.js.map +1 -0
- package/dist/components/RiskDetailsModal.d.ts +35 -0
- package/dist/components/RiskDetailsModal.d.ts.map +1 -0
- package/dist/components/RiskDetailsModal.js +74 -0
- package/dist/components/RiskDetailsModal.js.map +1 -0
- package/dist/components/ThreatSignalList.d.ts +28 -0
- package/dist/components/ThreatSignalList.d.ts.map +1 -0
- package/dist/components/ThreatSignalList.js +28 -0
- package/dist/components/ThreatSignalList.js.map +1 -0
- package/dist/components/TokenSecurityPanel.d.ts +28 -0
- package/dist/components/TokenSecurityPanel.d.ts.map +1 -0
- package/dist/components/TokenSecurityPanel.js +26 -0
- package/dist/components/TokenSecurityPanel.js.map +1 -0
- package/dist/components/TransactionRiskBanner.d.ts +36 -0
- package/dist/components/TransactionRiskBanner.d.ts.map +1 -0
- package/dist/components/TransactionRiskBanner.js +20 -0
- package/dist/components/TransactionRiskBanner.js.map +1 -0
- package/dist/context.d.ts +51 -0
- package/dist/context.d.ts.map +1 -0
- package/dist/context.js +47 -0
- package/dist/context.js.map +1 -0
- package/dist/copy.d.ts +72 -0
- package/dist/copy.d.ts.map +1 -0
- package/dist/copy.js +77 -0
- package/dist/copy.js.map +1 -0
- package/dist/index.d.ts +57 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +107 -0
- package/dist/index.js.map +1 -0
- package/dist/presentation.d.ts +316 -0
- package/dist/presentation.d.ts.map +1 -0
- package/dist/presentation.js +533 -0
- package/dist/presentation.js.map +1 -0
- package/dist/primitives.d.ts +139 -0
- package/dist/primitives.d.ts.map +1 -0
- package/dist/primitives.js +150 -0
- package/dist/primitives.js.map +1 -0
- package/dist/styles.d.ts +15 -0
- package/dist/styles.d.ts.map +1 -0
- package/dist/styles.js +285 -0
- package/dist/styles.js.map +1 -0
- package/dist/theme.d.ts +52 -0
- package/dist/theme.d.ts.map +1 -0
- package/dist/theme.js +90 -0
- package/dist/theme.js.map +1 -0
- package/package.json +70 -0
- package/src/browser/react-dom-global.ts +20 -0
- package/src/browser/react-global.ts +42 -0
- package/src/browser/react-jsx-runtime.ts +23 -0
- package/src/components/AddressRiskBadge.tsx +94 -0
- package/src/components/ApprovalWarning.tsx +144 -0
- package/src/components/ContractFacts.tsx +119 -0
- package/src/components/RiskDetailsModal.tsx +229 -0
- package/src/components/ThreatSignalList.tsx +76 -0
- package/src/components/TokenSecurityPanel.tsx +168 -0
- package/src/components/TransactionRiskBanner.tsx +123 -0
- package/src/context.tsx +89 -0
- package/src/copy.test.ts +214 -0
- package/src/copy.ts +151 -0
- package/src/index.ts +84 -0
- package/src/presentation.test.ts +521 -0
- package/src/presentation.ts +846 -0
- package/src/primitives.tsx +384 -0
- package/src/styles.ts +281 -0
- package/src/theme.test.ts +105 -0
- package/src/theme.ts +117 -0
package/src/copy.test.ts
ADDED
|
@@ -0,0 +1,214 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The language guard.
|
|
3
|
+
*
|
|
4
|
+
* These components are where a risk model becomes sentences, and a sentence is
|
|
5
|
+
* far easier to get wrong than a type. One careless "verified" or "safe" in a
|
|
6
|
+
* default string undoes the argument the whole product is built on — and it
|
|
7
|
+
* would ship, because nothing else in the stack would notice.
|
|
8
|
+
*
|
|
9
|
+
* So every string this package authors is walked here. The exception, stated
|
|
10
|
+
* explicitly below, is the strings it *quotes* from `@decentrys/protect`: the
|
|
11
|
+
* risk-level and history meanings are the model's own words, they legitimately
|
|
12
|
+
* contain "assurance of safety" and "verified against evidence", and rendering
|
|
13
|
+
* them verbatim is the correct behaviour.
|
|
14
|
+
*/
|
|
15
|
+
|
|
16
|
+
import { describe, expect, it } from 'vitest';
|
|
17
|
+
import {
|
|
18
|
+
HISTORY_STATUS_MEANING, RISK_LEVELS, RISK_LEVEL_MEANING,
|
|
19
|
+
type Evidence, type ThreatSignal, type UnknownReason,
|
|
20
|
+
} from '@decentrys/protect';
|
|
21
|
+
import { DEFAULT_COPY, resolveCopy, type UiCopy } from './copy';
|
|
22
|
+
import {
|
|
23
|
+
RISK_LEVEL_LABEL, approvalPresentation, capabilityPresentation, decisionPresentation,
|
|
24
|
+
historyPresentation, hopPresentation, kindPresentation, levelPresentation, signalPresentation,
|
|
25
|
+
unknownPresentation,
|
|
26
|
+
} from './presentation';
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Words that must never appear in a string this package writes.
|
|
30
|
+
*
|
|
31
|
+
* - `safe` / `safety` — a claim no analysis can support.
|
|
32
|
+
* - `scam` / `fraud` — accusations that need evidence, and that the model
|
|
33
|
+
* deliberately refuses to make even at its top level.
|
|
34
|
+
* - `trusted` / `verified` / `certified` / `approved` — they imply a registry
|
|
35
|
+
* a project can join, which is the pay-to-verify model this product exists
|
|
36
|
+
* to refuse.
|
|
37
|
+
* - `legitimate`, `dangerous` — verdicts rather than observations.
|
|
38
|
+
*/
|
|
39
|
+
const BANNED = [
|
|
40
|
+
'safe', 'safely', 'safety', 'unsafe',
|
|
41
|
+
'scam', 'scammer', 'fraud', 'fraudulent',
|
|
42
|
+
'trusted', 'untrusted', 'trustworthy',
|
|
43
|
+
'verified', 'verify', 'verification', 'certified',
|
|
44
|
+
'whitelisted', 'blacklisted', 'legitimate', 'legit',
|
|
45
|
+
'dangerous', 'guaranteed',
|
|
46
|
+
];
|
|
47
|
+
|
|
48
|
+
const BANNED_RE = new RegExp(`\\b(${BANNED.join('|')})\\b`, 'i');
|
|
49
|
+
|
|
50
|
+
const EXAMPLE_EVIDENCE = (analystVerified: boolean): Evidence => ({
|
|
51
|
+
id: 'ev_example_1',
|
|
52
|
+
type: 'ADDRESS_ATTRIBUTION',
|
|
53
|
+
source: 'Decentrys',
|
|
54
|
+
observedAt: '2026-09-05T18:22:04.000Z',
|
|
55
|
+
confidence: 0.9,
|
|
56
|
+
analystVerified,
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
const EXAMPLE_SIGNAL = (overrides: Partial<ThreatSignal> = {}): ThreatSignal => ({
|
|
60
|
+
type: 'ATTRIBUTED_COUNTERPARTY',
|
|
61
|
+
severity: 'HIGH',
|
|
62
|
+
confidence: 0.8,
|
|
63
|
+
explanation: 'Example explanation supplied by the API, not authored here.',
|
|
64
|
+
hops: 1,
|
|
65
|
+
evidence: [EXAMPLE_EVIDENCE(false)],
|
|
66
|
+
status: 'ACTIVE',
|
|
67
|
+
createdAt: '2026-09-01T00:00:00.000Z',
|
|
68
|
+
lastSeen: '2026-09-05T00:00:00.000Z',
|
|
69
|
+
...overrides,
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
/** Every string this package authors, as opposed to quotes from the model. */
|
|
73
|
+
function authoredStrings(): string[] {
|
|
74
|
+
const out: string[] = [...Object.values(DEFAULT_COPY)];
|
|
75
|
+
|
|
76
|
+
out.push(...Object.values(RISK_LEVEL_LABEL));
|
|
77
|
+
for (const level of RISK_LEVELS) out.push(levelPresentation(level).label);
|
|
78
|
+
|
|
79
|
+
for (const status of ['ESTABLISHED', 'MODERATE', 'LIMITED', 'NONE'] as const) {
|
|
80
|
+
out.push(historyPresentation(status).label);
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
for (const hops of [0, 1, 2, 5]) {
|
|
84
|
+
const hop = hopPresentation(hops, 'token');
|
|
85
|
+
out.push(hop.label, hop.statement);
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
for (const action of ['allow', 'inform', 'warn', 'warn_strong', 'require_confirmation', 'block'] as const) {
|
|
89
|
+
const d = decisionPresentation({ action, reason: 'example reason from the API' });
|
|
90
|
+
out.push(d.label, d.statement);
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
for (const kind of ['fact', 'capability', 'signal', 'unknown'] as const) {
|
|
94
|
+
out.push(kindPresentation(kind, DEFAULT_COPY).label);
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
for (const severity of ['INFO', 'NOTABLE', 'SIGNIFICANT'] as const) {
|
|
98
|
+
out.push(capabilityPresentation({
|
|
99
|
+
type: 'MINT_AUTHORITY_ACTIVE', severity, statement: 'Example statement from the API.',
|
|
100
|
+
}).severityLabel);
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
for (const reason of ['UNKNOWN', 'INSUFFICIENT_DATA', 'PROVIDER_UNAVAILABLE', 'NOT_APPLICABLE'] as UnknownReason[]) {
|
|
104
|
+
out.push(unknownPresentation({
|
|
105
|
+
field: 'example', reason, statement: 'Example statement from the API.',
|
|
106
|
+
}).reasonLabel);
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
for (const signal of [
|
|
110
|
+
EXAMPLE_SIGNAL(),
|
|
111
|
+
EXAMPLE_SIGNAL({ status: 'STALE' }),
|
|
112
|
+
EXAMPLE_SIGNAL({ status: 'DISPUTED_FACT' }),
|
|
113
|
+
EXAMPLE_SIGNAL({ confidence: 0.2 }),
|
|
114
|
+
EXAMPLE_SIGNAL({ evidence: [EXAMPLE_EVIDENCE(true)] }),
|
|
115
|
+
]) {
|
|
116
|
+
const p = signalPresentation(signal, DEFAULT_COPY, { subjectNoun: 'address' });
|
|
117
|
+
out.push(p.severityLabel, p.statusLabel, p.hopLabel, p.hopStatement, p.attribution);
|
|
118
|
+
if (p.inertStatement) out.push(p.inertStatement);
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
for (const approval of [
|
|
122
|
+
{ token: '0xtoken', symbol: 'USDC', decimals: 6, spender: '0xspender', amount: 'unlimited' },
|
|
123
|
+
{ token: '0xtoken', symbol: 'USDC', decimals: 6, spender: '0xspender', amount: '1000000' },
|
|
124
|
+
{ token: '0xtoken', spender: '0xspender', amount: '1000000' },
|
|
125
|
+
{ token: '0xtoken', spender: '0xspender' },
|
|
126
|
+
]) {
|
|
127
|
+
const p = approvalPresentation(approval);
|
|
128
|
+
out.push(p.scopeLabel, p.statement, p.amountLabel);
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
return out;
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
describe('default copy', () => {
|
|
135
|
+
it('has a non-empty string for every key', () => {
|
|
136
|
+
for (const [key, value] of Object.entries(DEFAULT_COPY)) {
|
|
137
|
+
expect(typeof value, key).toBe('string');
|
|
138
|
+
expect(value.trim().length, key).toBeGreaterThan(0);
|
|
139
|
+
}
|
|
140
|
+
});
|
|
141
|
+
|
|
142
|
+
it('names the four kinds with four distinct words', () => {
|
|
143
|
+
const labels = [
|
|
144
|
+
DEFAULT_COPY.factLabel, DEFAULT_COPY.capabilityLabel,
|
|
145
|
+
DEFAULT_COPY.signalLabel, DEFAULT_COPY.unknownLabel,
|
|
146
|
+
];
|
|
147
|
+
expect(new Set(labels).size).toBe(4);
|
|
148
|
+
});
|
|
149
|
+
|
|
150
|
+
it('merges an integrator’s overrides without losing the rest', () => {
|
|
151
|
+
const merged: UiCopy = resolveCopy({ factLabel: 'Beobachtung' });
|
|
152
|
+
expect(merged.factLabel).toBe('Beobachtung');
|
|
153
|
+
expect(merged.signalLabel).toBe(DEFAULT_COPY.signalLabel);
|
|
154
|
+
});
|
|
155
|
+
|
|
156
|
+
it('returns the defaults untouched when nothing is overridden', () => {
|
|
157
|
+
expect(resolveCopy()).toBe(DEFAULT_COPY);
|
|
158
|
+
});
|
|
159
|
+
|
|
160
|
+
it('carries an override through into a signal’s attribution', () => {
|
|
161
|
+
const copy = resolveCopy({ notAnalystConfirmed: 'Automatisch erkannt.' });
|
|
162
|
+
const p = signalPresentation(EXAMPLE_SIGNAL(), copy);
|
|
163
|
+
expect(p.attribution).toBe('Automatisch erkannt.');
|
|
164
|
+
});
|
|
165
|
+
});
|
|
166
|
+
|
|
167
|
+
describe('language rules', () => {
|
|
168
|
+
it('uses none of the banned words in any string it authors', () => {
|
|
169
|
+
const offenders = authoredStrings().filter((text) => BANNED_RE.test(text));
|
|
170
|
+
expect(offenders).toEqual([]);
|
|
171
|
+
});
|
|
172
|
+
|
|
173
|
+
it('quotes the model’s own meanings rather than paraphrasing them', () => {
|
|
174
|
+
// These *do* contain "assurance of safety" and "verified against evidence",
|
|
175
|
+
// and that is correct: they are the model's definitions of its own levels,
|
|
176
|
+
// and rewording them here would let the SDK and the UI drift apart.
|
|
177
|
+
for (const level of RISK_LEVELS) {
|
|
178
|
+
expect(levelPresentation(level).meaning).toBe(RISK_LEVEL_MEANING[level]);
|
|
179
|
+
}
|
|
180
|
+
for (const status of ['ESTABLISHED', 'MODERATE', 'LIMITED', 'NONE'] as const) {
|
|
181
|
+
expect(historyPresentation(status).statement).toBe(HISTORY_STATUS_MEANING[status]);
|
|
182
|
+
}
|
|
183
|
+
});
|
|
184
|
+
|
|
185
|
+
it('describes an absence of findings as an absence', () => {
|
|
186
|
+
expect(DEFAULT_COPY.noSignals).toContain('absence of findings');
|
|
187
|
+
expect(DEFAULT_COPY.notAnAssurance).toContain('not an assurance');
|
|
188
|
+
});
|
|
189
|
+
|
|
190
|
+
it('says an unknown never contributes to risk', () => {
|
|
191
|
+
expect(DEFAULT_COPY.unknownsAreNotRisk).toContain('never contributes to a risk level');
|
|
192
|
+
});
|
|
193
|
+
|
|
194
|
+
it('says coverage is never added to a risk total', () => {
|
|
195
|
+
expect(DEFAULT_COPY.coverageIsNotRisk).toContain('never added to a risk total');
|
|
196
|
+
});
|
|
197
|
+
|
|
198
|
+
it('says a capability is not evidence of use', () => {
|
|
199
|
+
expect(DEFAULT_COPY.capabilitiesAreNotFindings).toContain('not evidence that it has been');
|
|
200
|
+
});
|
|
201
|
+
|
|
202
|
+
it('leaves the decision with the integrator', () => {
|
|
203
|
+
expect(DEFAULT_COPY.policyIsYours).toContain('not ours');
|
|
204
|
+
});
|
|
205
|
+
|
|
206
|
+
it('explains a demotion instead of silently dropping the signal', () => {
|
|
207
|
+
expect(DEFAULT_COPY.demotedExplanation).toContain('cannot raise a risk level');
|
|
208
|
+
});
|
|
209
|
+
|
|
210
|
+
it('attributes an analyst confirmation to a person, not to a badge', () => {
|
|
211
|
+
expect(DEFAULT_COPY.analystConfirmed).toContain('analyst');
|
|
212
|
+
expect(DEFAULT_COPY.notAnalystConfirmed).toContain('No analyst has confirmed it');
|
|
213
|
+
});
|
|
214
|
+
});
|
package/src/copy.ts
ADDED
|
@@ -0,0 +1,151 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Every fixed string these components can put in front of a user.
|
|
3
|
+
*
|
|
4
|
+
* It lives in one object for two reasons. The first is customisation: an
|
|
5
|
+
* integrator passes `copy` to any component and overrides whatever it needs,
|
|
6
|
+
* including into another language, without forking a component. The second is
|
|
7
|
+
* enforcement — a single object is something a test can walk end to end, and
|
|
8
|
+
* `copy.test.ts` does exactly that, because the language rules are far easier
|
|
9
|
+
* to break in a sentence than in a type.
|
|
10
|
+
*
|
|
11
|
+
* The rules these strings are held to:
|
|
12
|
+
*
|
|
13
|
+
* - Never "safe", "scam", "trusted" or "verified" as a claim about a subject.
|
|
14
|
+
* "Safe" is a claim no analysis supports, "scam" is an accusation needing
|
|
15
|
+
* evidence, and "verified"/"trusted" imply a registry Decentrys does not
|
|
16
|
+
* run and will never sell a place in.
|
|
17
|
+
* - Limited or absent history is stated, never warned about.
|
|
18
|
+
* - An absence of findings is described as an absence, never as a clean bill.
|
|
19
|
+
* - Anything above zero hops is described as an inference about a
|
|
20
|
+
* counterparty, because that is what it is.
|
|
21
|
+
*/
|
|
22
|
+
|
|
23
|
+
export interface UiCopy {
|
|
24
|
+
// Section headings
|
|
25
|
+
factsHeading: string;
|
|
26
|
+
capabilitiesHeading: string;
|
|
27
|
+
signalsHeading: string;
|
|
28
|
+
unknownsHeading: string;
|
|
29
|
+
evidenceHeading: string;
|
|
30
|
+
componentsHeading: string;
|
|
31
|
+
historyHeading: string;
|
|
32
|
+
demotedHeading: string;
|
|
33
|
+
|
|
34
|
+
// Kind labels — the non-colour carrier that keeps a fact from reading as a
|
|
35
|
+
// threat. Every row is prefixed with one of these, in text.
|
|
36
|
+
factLabel: string;
|
|
37
|
+
capabilityLabel: string;
|
|
38
|
+
signalLabel: string;
|
|
39
|
+
unknownLabel: string;
|
|
40
|
+
|
|
41
|
+
// Standing statements
|
|
42
|
+
notAnAssurance: string;
|
|
43
|
+
factsAreNotFindings: string;
|
|
44
|
+
capabilitiesAreNotFindings: string;
|
|
45
|
+
unknownsAreNotRisk: string;
|
|
46
|
+
coverageIsNotRisk: string;
|
|
47
|
+
policyIsYours: string;
|
|
48
|
+
demotedExplanation: string;
|
|
49
|
+
|
|
50
|
+
// Empty states — a blank panel is indistinguishable from a clean result, so
|
|
51
|
+
// there is no blank panel.
|
|
52
|
+
noFacts: string;
|
|
53
|
+
noCapabilities: string;
|
|
54
|
+
noSignals: string;
|
|
55
|
+
noUnknowns: string;
|
|
56
|
+
nothingReturned: string;
|
|
57
|
+
|
|
58
|
+
// Controls
|
|
59
|
+
viewDetails: string;
|
|
60
|
+
close: string;
|
|
61
|
+
detailsTitle: string;
|
|
62
|
+
showEvidence: string;
|
|
63
|
+
hideEvidence: string;
|
|
64
|
+
|
|
65
|
+
// Assorted
|
|
66
|
+
confidenceLabel: string;
|
|
67
|
+
confidenceCaveat: string;
|
|
68
|
+
sourceLabel: string;
|
|
69
|
+
observedLabel: string;
|
|
70
|
+
grantedByLabel: string;
|
|
71
|
+
hopsLabel: string;
|
|
72
|
+
statusLabel: string;
|
|
73
|
+
analystConfirmed: string;
|
|
74
|
+
notAnalystConfirmed: string;
|
|
75
|
+
cachedNotice: string;
|
|
76
|
+
modelLabel: string;
|
|
77
|
+
assessedLabel: string;
|
|
78
|
+
approvalHeading: string;
|
|
79
|
+
approvalRevocation: string;
|
|
80
|
+
spenderLabel: string;
|
|
81
|
+
tokenLabel: string;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
export const DEFAULT_COPY: UiCopy = {
|
|
85
|
+
factsHeading: 'Observed facts',
|
|
86
|
+
capabilitiesHeading: 'What this contract can do',
|
|
87
|
+
signalsHeading: 'Threat signals',
|
|
88
|
+
unknownsHeading: 'Not known',
|
|
89
|
+
evidenceHeading: 'Evidence',
|
|
90
|
+
componentsHeading: 'Score components',
|
|
91
|
+
historyHeading: 'History',
|
|
92
|
+
demotedHeading: 'Reported as facts, not as risk',
|
|
93
|
+
|
|
94
|
+
factLabel: 'Fact',
|
|
95
|
+
capabilityLabel: 'Capability',
|
|
96
|
+
signalLabel: 'Threat signal',
|
|
97
|
+
unknownLabel: 'Unknown',
|
|
98
|
+
|
|
99
|
+
notAnAssurance:
|
|
100
|
+
'An absence of findings is an absence of findings. It is not an assurance about this subject.',
|
|
101
|
+
factsAreNotFindings:
|
|
102
|
+
'These are directly observed and carry no accusation. None of them raises a risk level on its own.',
|
|
103
|
+
capabilitiesAreNotFindings:
|
|
104
|
+
'What the code is able to do. A capability is not evidence that it has been or will be used.',
|
|
105
|
+
unknownsAreNotRisk:
|
|
106
|
+
'Reported as unknown rather than guessed at. An unknown never contributes to a risk level.',
|
|
107
|
+
coverageIsNotRisk:
|
|
108
|
+
'Coverage measures how much Decentrys knows about this subject. It is reported separately and is never added to a risk total.',
|
|
109
|
+
policyIsYours:
|
|
110
|
+
'Decentrys reports evidence. What happens next is this application’s decision, not ours.',
|
|
111
|
+
demotedExplanation:
|
|
112
|
+
'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.',
|
|
113
|
+
|
|
114
|
+
noFacts: 'No facts were returned for this subject.',
|
|
115
|
+
noCapabilities: 'No security-sensitive capabilities were identified.',
|
|
116
|
+
noSignals:
|
|
117
|
+
'No threat signals were found. That is an absence of findings, not a finding that there is nothing to find.',
|
|
118
|
+
noUnknowns: 'Every attribute checked returned an answer.',
|
|
119
|
+
nothingReturned:
|
|
120
|
+
'Decentrys returned no facts, capabilities, threat signals or unknowns for this subject.',
|
|
121
|
+
|
|
122
|
+
viewDetails: 'View details',
|
|
123
|
+
close: 'Close',
|
|
124
|
+
detailsTitle: 'Assessment details',
|
|
125
|
+
showEvidence: 'Show evidence',
|
|
126
|
+
hideEvidence: 'Hide evidence',
|
|
127
|
+
|
|
128
|
+
confidenceLabel: 'Confidence',
|
|
129
|
+
confidenceCaveat:
|
|
130
|
+
'How well the evidence supports this classification. It is not a measure of how much harm the subject could do.',
|
|
131
|
+
sourceLabel: 'Source',
|
|
132
|
+
observedLabel: 'Observed',
|
|
133
|
+
grantedByLabel: 'Granted by',
|
|
134
|
+
hopsLabel: 'Distance',
|
|
135
|
+
statusLabel: 'Status',
|
|
136
|
+
analystConfirmed: 'Confirmed by a Decentrys analyst against the evidence above.',
|
|
137
|
+
notAnalystConfirmed: 'Automated finding. No analyst has confirmed it.',
|
|
138
|
+
cachedNotice: 'Served from a local cache rather than a fresh lookup.',
|
|
139
|
+
modelLabel: 'Model',
|
|
140
|
+
assessedLabel: 'Assessed',
|
|
141
|
+
approvalHeading: 'What you are about to grant',
|
|
142
|
+
approvalRevocation:
|
|
143
|
+
'An allowance stays in place until it is revoked, whether or not the application is used again.',
|
|
144
|
+
spenderLabel: 'Spender',
|
|
145
|
+
tokenLabel: 'Token',
|
|
146
|
+
};
|
|
147
|
+
|
|
148
|
+
/** Merge an integrator's overrides over the defaults. */
|
|
149
|
+
export function resolveCopy(overrides?: Partial<UiCopy>): UiCopy {
|
|
150
|
+
return overrides ? { ...DEFAULT_COPY, ...overrides } : DEFAULT_COPY;
|
|
151
|
+
}
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `@decentrys/ui-sdk` — the drop-in React components integrators embed.
|
|
3
|
+
*
|
|
4
|
+
* ## Naming
|
|
5
|
+
*
|
|
6
|
+
* The plan calls this package `@decentrys/ui`. That name is already taken by
|
|
7
|
+
* `packages/ui`, the platform's internal component library, and two packages
|
|
8
|
+
* sharing a name in one pnpm workspace makes `workspace:*` ambiguous and
|
|
9
|
+
* breaks install for the whole repository. The `-sdk` suffix follows the
|
|
10
|
+
* convention already set by `@decentrys/risk-sdk` and `@decentrys/sentinel-sdk`,
|
|
11
|
+
* which collided with `packages/risk` and `packages/sentinel` the same way.
|
|
12
|
+
*
|
|
13
|
+
* ## The rule these components exist to keep
|
|
14
|
+
*
|
|
15
|
+
* Unknown is neutral. Risk requires evidence. Malicious requires stronger
|
|
16
|
+
* evidence. Users should be shown why.
|
|
17
|
+
*
|
|
18
|
+
* A classifier that honours that rule and a UI that renders "deployed 3 days
|
|
19
|
+
* ago" in the same amber as "attributed to a drainer family" has still told
|
|
20
|
+
* the user the wrong thing. So the presentation decisions live in
|
|
21
|
+
* `presentation.ts` as pure functions, and they are what the tests target.
|
|
22
|
+
*
|
|
23
|
+
* ## Usage
|
|
24
|
+
*
|
|
25
|
+
* ```tsx
|
|
26
|
+
* import { Decentrys } from '@decentrys/protect';
|
|
27
|
+
* import { DecentrysUiProvider, TransactionRiskBanner } from '@decentrys/ui-sdk';
|
|
28
|
+
*
|
|
29
|
+
* const decentrys = new Decentrys({ apiKey, failMode: 'warn' });
|
|
30
|
+
* const result = await decentrys.assessTransaction(tx);
|
|
31
|
+
*
|
|
32
|
+
* <DecentrysUiProvider theme={{ surface: '#0b0d10', text: '#e8ebef' }}>
|
|
33
|
+
* <TransactionRiskBanner
|
|
34
|
+
* result={result}
|
|
35
|
+
* // Your button, your policy. This package never disables it.
|
|
36
|
+
* actions={<button onClick={sign}>Sign</button>}
|
|
37
|
+
* />
|
|
38
|
+
* </DecentrysUiProvider>
|
|
39
|
+
* ```
|
|
40
|
+
*
|
|
41
|
+
* React and react-dom are peer dependencies and are never bundled. Two copies
|
|
42
|
+
* of React in one application is a real, common and confusing breakage.
|
|
43
|
+
*/
|
|
44
|
+
|
|
45
|
+
export { TransactionRiskBanner, type TransactionRiskBannerProps } from './components/TransactionRiskBanner';
|
|
46
|
+
export { ApprovalWarning, type ApprovalWarningProps } from './components/ApprovalWarning';
|
|
47
|
+
export { ContractFacts, type ContractFactsProps } from './components/ContractFacts';
|
|
48
|
+
export { ThreatSignalList, type ThreatSignalListProps } from './components/ThreatSignalList';
|
|
49
|
+
export { RiskDetailsModal, type RiskDetailsModalProps } from './components/RiskDetailsModal';
|
|
50
|
+
export { TokenSecurityPanel, type TokenSecurityPanelProps } from './components/TokenSecurityPanel';
|
|
51
|
+
export { AddressRiskBadge, type AddressRiskBadgeProps } from './components/AddressRiskBadge';
|
|
52
|
+
|
|
53
|
+
export {
|
|
54
|
+
DecentrysUiProvider, DecentrysUiStyles, useUiConfig,
|
|
55
|
+
type DecentrysUiProviderProps, type ResolvedUiConfig, type UiConfigInput,
|
|
56
|
+
} from './context';
|
|
57
|
+
|
|
58
|
+
export { DECENTRYS_UI_CSS } from './styles';
|
|
59
|
+
|
|
60
|
+
export {
|
|
61
|
+
THEME_TOKENS, themeStyle, toneBgVar, toneVar,
|
|
62
|
+
type DecentrysTheme, type ThemeToken, type Tone,
|
|
63
|
+
} from './theme';
|
|
64
|
+
|
|
65
|
+
export { DEFAULT_COPY, resolveCopy, type UiCopy } from './copy';
|
|
66
|
+
|
|
67
|
+
export {
|
|
68
|
+
MIN_RAISING_CONFIDENCE, RISK_LEVEL_LABEL,
|
|
69
|
+
approvalPresentation, assessmentSections, availabilityPresentation, capabilityPresentation,
|
|
70
|
+
componentRows, decisionPresentation, evidencePresentation, factPresentation, formatConfidence,
|
|
71
|
+
formatTimestamp, formatTokenAmount, historyPresentation, hopPresentation, isUnlimitedAllowance,
|
|
72
|
+
kindPresentation, levelPresentation, levelRank, plural, shortenAddress, signalPresentation,
|
|
73
|
+
unknownPresentation,
|
|
74
|
+
type ApprovalDetails, type ApprovalPresentation, type AssessmentSections,
|
|
75
|
+
type AvailabilityPresentation, type CapabilityPresentation, type ComponentRow,
|
|
76
|
+
type DecisionPresentation, type EvidenceKind, type EvidencePresentation, type FactPresentation,
|
|
77
|
+
type HistoryPresentation, type HopPresentation, type KindPresentation, type LevelPresentation,
|
|
78
|
+
type SignalPresentation, type UnknownPresentation,
|
|
79
|
+
} from './presentation';
|
|
80
|
+
|
|
81
|
+
export {
|
|
82
|
+
Chip, EmptyNote, LevelBadge, Mono, ScreenReaderText, Section, cx,
|
|
83
|
+
type CommonProps,
|
|
84
|
+
} from './primitives';
|