@decentrys/ui-sdk 0.1.1 → 0.1.2

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 (2) hide show
  1. package/README.md +159 -52
  2. package/package.json +2 -2
package/README.md CHANGED
@@ -8,97 +8,204 @@
8
8
  npm install @decentrys/ui-sdk @decentrys/protect
9
9
  ```
10
10
 
11
- React 18 or 19, as a peer dependency. Never bundled — two copies of React in one
12
- app is a real breakage.
11
+ React 18 or 19, as a peer dependency alongside `react-dom`. Never bundled — two
12
+ copies of React in one app is a real breakage. No styling dependency, no font
13
+ fetch, no network calls of its own.
13
14
 
14
15
  ## Quick start
15
16
 
16
17
  ```tsx
17
- import { Decentrys } from '@decentrys/protect';
18
- import { TransactionRiskBanner, DECENTRYS_UI_CSS } from '@decentrys/ui-sdk';
18
+ import { useEffect, useState } from 'react';
19
+ import { Decentrys, type ProtectResult, type TransactionRequest } from '@decentrys/protect';
20
+ import { DecentrysUiProvider, TransactionRiskBanner, RiskDetailsModal } from '@decentrys/ui-sdk';
19
21
 
20
22
  const decentrys = new Decentrys({ apiKey: 'dk_pub_live_...' });
21
23
 
22
- function SigningScreen({ tx }) {
23
- const [result, setResult] = useState(null);
24
- useEffect(() => { decentrys.assessTransaction(tx).then(setResult); }, [tx]);
24
+ export function SigningScreen({ tx, onSign, onCancel }: {
25
+ tx: TransactionRequest;
26
+ onSign: () => void;
27
+ onCancel: () => void;
28
+ }) {
29
+ const [result, setResult] = useState<ProtectResult | null>(null);
30
+ const [detailsOpen, setDetailsOpen] = useState(false);
31
+
32
+ useEffect(() => {
33
+ let live = true;
34
+ decentrys.assessTransaction(tx).then((r) => { if (live) setResult(r); });
35
+ return () => { live = false; };
36
+ }, [tx]);
37
+
38
+ if (!result) return null;
25
39
 
26
40
  return (
27
- <>
28
- <style>{DECENTRYS_UI_CSS}</style>
29
- {result && (
30
- <TransactionRiskBanner
31
- result={result}
32
- actions={<>
33
- <button onClick={sign}>Confirm</button>
34
- <button onClick={cancel}>Cancel</button>
35
- </>}
36
- />
37
- )}
38
- </>
41
+ <DecentrysUiProvider theme={{ surface: '#0b0d10', text: '#e8ebef', radius: '12px' }}>
42
+ <TransactionRiskBanner
43
+ result={result}
44
+ onViewDetails={() => setDetailsOpen(true)}
45
+ actions={
46
+ <>
47
+ {/* Your buttons, your policy. This package never disables them. */}
48
+ <button onClick={onSign}>Confirm</button>
49
+ <button onClick={onCancel}>Cancel</button>
50
+ </>
51
+ }
52
+ />
53
+ <RiskDetailsModal result={result} open={detailsOpen} onClose={() => setDetailsOpen(false)} />
54
+ </DecentrysUiProvider>
39
55
  );
40
56
  }
41
57
  ```
42
58
 
43
- Inject `DECENTRYS_UI_CSS` once, anywhere a `<style>` tag, your `<head>`, or a
44
- shadow root. No external stylesheet, no font fetch, no network calls.
59
+ `DecentrysUiProvider` sets theme and copy once for everything beneath it and
60
+ emits the stylesheet a single time. Without a provider every component emits
61
+ its own — so a single `<AddressRiskBadge>` dropped into a page still renders
62
+ correctly.
63
+
64
+ Placing the CSS yourself instead — in a `<head>`, a bundled stylesheet or a
65
+ shadow root — is `<DecentrysUiProvider injectStyles={false}>`, or export the
66
+ string directly:
67
+
68
+ ```tsx
69
+ import { DECENTRYS_UI_CSS } from '@decentrys/ui-sdk';
70
+ <style>{DECENTRYS_UI_CSS}</style>
71
+ ```
45
72
 
46
73
  ## Components
47
74
 
48
- | Component | Use it when |
49
- |---|---|
50
- | `TransactionRiskBanner` | A user is about to sign. The main one. |
51
- | `ApprovalWarning` | A token approval leads with what is being granted, not a score. |
52
- | `ContractFacts` | Showing what a contract can do (upgrade, mint, pause). |
53
- | `TokenSecurityPanel` | A token detail screen. |
54
- | `ThreatSignalList` | You want signals only, in your own layout. |
55
- | `AddressRiskBadge` | Compact inline badge beside an address. |
56
- | `RiskDetailsModal` | "Why?" every fact, signal, evidence item and score component. |
75
+ Note which prop each one takes — some want the whole `ProtectResult`, some want
76
+ a piece of it.
77
+
78
+ | Component | Required props | Use it when |
79
+ |---|---|---|
80
+ | `TransactionRiskBanner` | `result` | A user is about to sign. The main one. |
81
+ | `ApprovalWarning` | `result`, `approval` | A token approval — leads with what is being granted, not a score. |
82
+ | `ContractFacts` | `assessment` | Showing what a contract can do (upgrade, mint, pause). |
83
+ | `TokenSecurityPanel` | `result` | A token detail screen. |
84
+ | `ThreatSignalList` | `signals` | You want signals only, in your own layout. |
85
+ | `AddressRiskBadge` | `assessment` **or** `level` | Compact inline badge beside an address. |
86
+ | `RiskDetailsModal` | `result`, `open`, `onClose` | "Why?" — every fact, signal, evidence item and score component. |
57
87
 
58
88
  ```tsx
59
- <ApprovalWarning result={approvalResult} />
60
- // "Grants 0x1111… permission to spend an unlimited amount of your USDC, until it is revoked."
89
+ // An approval. `approval` describes what is being granted; the SDK does not
90
+ // infer it from the assessment.
91
+ <ApprovalWarning
92
+ result={approvalResult} // from decentrys.screenApproval(...)
93
+ approval={{
94
+ token: '0xa0b8…',
95
+ symbol: 'USDC',
96
+ decimals: 6,
97
+ spender: '0x1111…',
98
+ spenderLabel: '1inch Router',
99
+ amount: 'unlimited', // base units, or the literal 'unlimited'
100
+ }}
101
+ />
102
+ // "This grants 1inch Router permission to spend an unlimited amount of your USDC,
103
+ // now and at any time in the future, until it is revoked."
104
+ //
105
+ // Omit `amount` and it says the allowance could not be decoded — never that it
106
+ // is small. Omit `decimals` and the figure is labelled as base units rather
107
+ // than silently shown 10^18 times too small.
108
+
109
+ <ContractFacts assessment={result.assessment} demotedSignals={result.demotedSignals} />
110
+
111
+ <TokenSecurityPanel result={result} tokenSymbol="USDC" showComponents />
61
112
 
62
- <AddressRiskBadge result={result} />
63
- <RiskDetailsModal result={result} open={open} onClose={() => setOpen(false)} />
113
+ <ThreatSignalList signals={result.assessment.threatSignals} />
114
+
115
+ <AddressRiskBadge assessment={result.assessment} address="0x1111…" />
116
+ <AddressRiskBadge level="CAUTION" /> {/* when you only have the level */}
64
117
  ```
65
118
 
119
+ Every component also accepts `className`, `style`, `theme`, `copy` and
120
+ `levelLabels`. `AddressRiskBadge` takes exactly one of `assessment` or `level`,
121
+ enforced by the type rather than a runtime throw — a component that throws
122
+ inside a wallet's render tree takes that wallet's screen down.
123
+
66
124
  ## What these will not do
67
125
 
68
- **Nothing blocks.** `blocksUi` is typed as the literal `false`, including for a
69
- `block` policy action. No control here is ever rendered `disabled` — the
70
- decision belongs to your application, and you wire your own buttons via `actions`.
126
+ **Nothing blocks.** `DecisionPresentation.blocksUi` is typed as the literal
127
+ `false`, including for a `block` policy action. No control here is ever
128
+ rendered `disabled` — the decision belongs to your application, and you wire
129
+ your own buttons through `actions`.
130
+
131
+ **`LIMITED` history is never a warning.** `HistoryPresentation` types `tone` as
132
+ the literal `'neutral'` and `isWarning` as the literal `false`, so rendering it
133
+ as an alert doesn't compile. It's the correct, expected state for anything
134
+ recently deployed.
71
135
 
72
- **`LIMITED` history is never a warning.** Its presentation is typed
73
- `tone: 'neutral'` and `isWarning: false` as *literal types*, so rendering it as
74
- an alert doesn't compile. It's the correct state for anything recently deployed.
136
+ **Facts never look like threat signals.** `fact`, `capability` and `unknown`
137
+ are separate tone families from the risk tones, each row carries its kind as a
138
+ word and its own non-colour glyph, and a stale or low-confidence signal that
139
+ did not raise the level is toned `inactive`. Colour is the third carrier of
140
+ meaning, not the first — a colour-blind user still has to be able to tell
141
+ "deployed three days ago" from "attributed to a drainer family".
75
142
 
76
- **Facts never look like threat signals.** Each row carries its kind as a word,
77
- its own rule style and its own glyph. Colour is the third carrier of meaning,
78
- not the first — a colour-blind user still has to tell "deployed three days ago"
79
- from "attributed to a drainer family".
143
+ **Anything above zero hops says so in a sentence**, not just a badge. A hop is
144
+ an inference about a counterparty, and a badge reading "2" tells nobody that.
80
145
 
81
146
  ## Theming
82
147
 
83
- 31 CSS custom properties with light defaults and a dark-mode block.
148
+ 31 CSS custom properties. Pass them by their **bare token name** — the
149
+ `--dcy-` prefix is added for you:
84
150
 
85
151
  ```tsx
86
152
  <TransactionRiskBanner
87
153
  result={result}
88
- theme={{ '--dcy-accent': '#ab9ff2', '--dcy-surface': '#1a1a2e', '--dcy-radius': '12px' }}
154
+ theme={{ surface: '#1a1a2e', radius: '12px', 'tone-caution': '#ab9ff2' }}
89
155
  className="my-wallet-card"
90
156
  />
91
157
  ```
92
158
 
93
- Inline `theme` beats both the defaults and the dark-mode block, so a fixed brand
94
- palette doesn't flip on a user's OS setting. Every meaningful element also
95
- carries a `data-dcy-*` attribute if you'd rather style from your own CSS.
159
+ The tokens (`THEME_TOKENS` is exported, and `DecentrysTheme` is
160
+ `Partial<Record<ThemeToken, string>>`):
161
+
162
+ - Layout and type — `font`, `font-mono`, `radius`, `gap`
163
+ - Surfaces and text — `surface`, `surface-raised`, `border`, `text`, `text-muted`, `text-faint`, `focus`
164
+ - A foreground and a `-bg` wash for each of ten tones — `tone-neutral`, `tone-info`, `tone-caution`, `tone-elevated`, `tone-high`, `tone-critical`, `tone-fact`, `tone-capability`, `tone-unknown`, `tone-inactive`
165
+
166
+ Inline `theme` is applied on the component's own root, so it beats both the
167
+ defaults and the `prefers-color-scheme` dark block — a fixed brand palette
168
+ doesn't flip on a user's OS setting. Props win over the provider, so one banner
169
+ can be re-themed inside an otherwise uniform tree.
170
+
171
+ Every meaningful element also carries a `data-dcy-*` attribute
172
+ (`data-dcy-level`, `data-dcy-kind`, `data-dcy-tone`) if you'd rather style from
173
+ your own CSS.
174
+
175
+ ## Copy and translation
176
+
177
+ Every string is overridable, on the provider or per component:
178
+
179
+ ```tsx
180
+ <DecentrysUiProvider copy={{ /* Partial<UiCopy> */ }}>
181
+ <TransactionRiskBanner result={result} levelLabels={{ CAUTION: 'Attention' }} />
182
+ ```
183
+
184
+ `DEFAULT_COPY` and `resolveCopy` are exported.
185
+
186
+ ## Building your own presentation
187
+
188
+ The presentation logic is pure, React-free and separately exported, so a
189
+ renderer you write yourself cannot drift from the one here:
190
+
191
+ ```ts
192
+ import {
193
+ levelPresentation, factPresentation, capabilityPresentation, signalPresentation,
194
+ historyPresentation, unknownPresentation, approvalPresentation, assessmentSections,
195
+ componentRows, decisionPresentation, evidencePresentation, availabilityPresentation,
196
+ hopPresentation, formatConfidence, formatTokenAmount, shortenAddress,
197
+ MIN_RAISING_CONFIDENCE, RISK_LEVEL_LABEL,
198
+ } from '@decentrys/ui-sdk';
199
+ ```
200
+
201
+ `MIN_RAISING_CONFIDENCE` is re-exported from `@decentrys/protect` rather than
202
+ copied, so a renderer cannot put a signal in the "did not raise the level" list
203
+ while the classifier was in fact raising it.
96
204
 
97
205
  ## React Native
98
206
 
99
- These render React DOM and won't work in RN. The *presentation logic*
100
- `presentation.ts` and `copy.ts` — is React-free and reusable; a native renderer
101
- is planned as `@decentrys/protect-react-native`.
207
+ These render React DOM and won't work in RN. The presentation logic above is
208
+ React-free and reusable as-is.
102
209
 
103
210
  ## The rest of the SDK
104
211
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@decentrys/ui-sdk",
3
- "version": "0.1.1",
3
+ "version": "0.1.2",
4
4
  "description": "Drop-in React components for rendering Decentrys risk assessments, built so a new project can never be shown as dangerous for being new.",
5
5
  "license": "MIT",
6
6
  "author": "Decentrys Labs",
@@ -49,7 +49,7 @@
49
49
  "README.md"
50
50
  ],
51
51
  "dependencies": {
52
- "@decentrys/protect": "0.1.1"
52
+ "@decentrys/protect": "0.1.2"
53
53
  },
54
54
  "peerDependencies": {
55
55
  "react": ">=18",