@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/dist/theme.d.ts
ADDED
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Theming.
|
|
3
|
+
*
|
|
4
|
+
* These components ship into other people's wallets, so two things are true at
|
|
5
|
+
* once: they must look like something out of the box, and they must never
|
|
6
|
+
* force a Decentrys palette onto an application that has its own. Every visual
|
|
7
|
+
* value is therefore a CSS custom property with a default, and there are three
|
|
8
|
+
* escalating ways to override it:
|
|
9
|
+
*
|
|
10
|
+
* 1. `theme` — a token map applied inline on the component's own root, which
|
|
11
|
+
* beats every stylesheet rule including the dark-mode defaults.
|
|
12
|
+
* 2. `className` — the integrator's own class on the same root, for anything
|
|
13
|
+
* tokens do not reach.
|
|
14
|
+
* 3. `data-dcy-*` attributes, present on every meaningful element
|
|
15
|
+
* (`data-dcy-level`, `data-dcy-kind`, `data-dcy-tone`), so an integrator
|
|
16
|
+
* can target our internals from their own stylesheet without us exporting
|
|
17
|
+
* a class-name contract we would then have to keep.
|
|
18
|
+
*
|
|
19
|
+
* No styling dependency is used or needed. Adding Tailwind or a CSS-in-JS
|
|
20
|
+
* runtime to a package that lands inside someone else's build is a cost they
|
|
21
|
+
* did not agree to.
|
|
22
|
+
*/
|
|
23
|
+
import type { CSSProperties } from 'react';
|
|
24
|
+
/**
|
|
25
|
+
* Tones.
|
|
26
|
+
*
|
|
27
|
+
* Deliberately *not* a severity scale with `safe` at one end. `fact`,
|
|
28
|
+
* `capability` and `unknown` are separate tone families from the risk tones,
|
|
29
|
+
* because a fact and a threat signal rendering in the same colour is the exact
|
|
30
|
+
* failure this product exists to prevent — and `inactive` exists so a stale or
|
|
31
|
+
* low-confidence signal, which did not affect the assessment, cannot be shown
|
|
32
|
+
* in the colour of one that did.
|
|
33
|
+
*/
|
|
34
|
+
export type Tone = 'neutral' | 'info' | 'caution' | 'elevated' | 'high' | 'critical' | 'fact' | 'capability' | 'unknown' | 'inactive';
|
|
35
|
+
export declare const THEME_TOKENS: readonly ["font", "font-mono", "radius", "gap", "surface", "surface-raised", "border", "text", "text-muted", "text-faint", "focus", "tone-neutral", "tone-neutral-bg", "tone-info", "tone-info-bg", "tone-caution", "tone-caution-bg", "tone-elevated", "tone-elevated-bg", "tone-high", "tone-high-bg", "tone-critical", "tone-critical-bg", "tone-fact", "tone-fact-bg", "tone-capability", "tone-capability-bg", "tone-unknown", "tone-unknown-bg", "tone-inactive", "tone-inactive-bg"];
|
|
36
|
+
export type ThemeToken = (typeof THEME_TOKENS)[number];
|
|
37
|
+
/** A partial map of token → CSS value. Anything omitted keeps its default. */
|
|
38
|
+
export type DecentrysTheme = Partial<Record<ThemeToken, string>>;
|
|
39
|
+
/**
|
|
40
|
+
* Turn a theme into inline custom properties.
|
|
41
|
+
*
|
|
42
|
+
* Inline custom properties win over the stylesheet's defaults *and* over the
|
|
43
|
+
* `prefers-color-scheme` block, which is the behaviour an integrator with a
|
|
44
|
+
* fixed brand palette needs — otherwise their light theme would flip to our
|
|
45
|
+
* dark one on a user's OS setting.
|
|
46
|
+
*/
|
|
47
|
+
export declare function themeStyle(theme?: DecentrysTheme, base?: CSSProperties): CSSProperties | undefined;
|
|
48
|
+
/** The CSS variable reference for a tone's foreground, e.g. for a border. */
|
|
49
|
+
export declare function toneVar(tone: Tone): string;
|
|
50
|
+
/** The CSS variable reference for a tone's background wash. */
|
|
51
|
+
export declare function toneBgVar(tone: Tone): string;
|
|
52
|
+
//# sourceMappingURL=theme.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"theme.d.ts","sourceRoot":"","sources":["../src/theme.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;GAqBG;AAEH,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,OAAO,CAAC;AAE3C;;;;;;;;;GASG;AACH,MAAM,MAAM,IAAI,GACZ,SAAS,GACT,MAAM,GACN,SAAS,GACT,UAAU,GACV,MAAM,GACN,UAAU,GACV,MAAM,GACN,YAAY,GACZ,SAAS,GACT,UAAU,CAAC;AAEf,eAAO,MAAM,YAAY,6dAgCf,CAAC;AAEX,MAAM,MAAM,UAAU,GAAG,CAAC,OAAO,YAAY,CAAC,CAAC,MAAM,CAAC,CAAC;AAEvD,8EAA8E;AAC9E,MAAM,MAAM,cAAc,GAAG,OAAO,CAAC,MAAM,CAAC,UAAU,EAAE,MAAM,CAAC,CAAC,CAAC;AAEjE;;;;;;;GAOG;AACH,wBAAgB,UAAU,CACxB,KAAK,CAAC,EAAE,cAAc,EACtB,IAAI,CAAC,EAAE,aAAa,GACnB,aAAa,GAAG,SAAS,CAS3B;AAED,6EAA6E;AAC7E,wBAAgB,OAAO,CAAC,IAAI,EAAE,IAAI,GAAG,MAAM,CAE1C;AAED,+DAA+D;AAC/D,wBAAgB,SAAS,CAAC,IAAI,EAAE,IAAI,GAAG,MAAM,CAE5C"}
|
package/dist/theme.js
ADDED
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Theming.
|
|
4
|
+
*
|
|
5
|
+
* These components ship into other people's wallets, so two things are true at
|
|
6
|
+
* once: they must look like something out of the box, and they must never
|
|
7
|
+
* force a Decentrys palette onto an application that has its own. Every visual
|
|
8
|
+
* value is therefore a CSS custom property with a default, and there are three
|
|
9
|
+
* escalating ways to override it:
|
|
10
|
+
*
|
|
11
|
+
* 1. `theme` — a token map applied inline on the component's own root, which
|
|
12
|
+
* beats every stylesheet rule including the dark-mode defaults.
|
|
13
|
+
* 2. `className` — the integrator's own class on the same root, for anything
|
|
14
|
+
* tokens do not reach.
|
|
15
|
+
* 3. `data-dcy-*` attributes, present on every meaningful element
|
|
16
|
+
* (`data-dcy-level`, `data-dcy-kind`, `data-dcy-tone`), so an integrator
|
|
17
|
+
* can target our internals from their own stylesheet without us exporting
|
|
18
|
+
* a class-name contract we would then have to keep.
|
|
19
|
+
*
|
|
20
|
+
* No styling dependency is used or needed. Adding Tailwind or a CSS-in-JS
|
|
21
|
+
* runtime to a package that lands inside someone else's build is a cost they
|
|
22
|
+
* did not agree to.
|
|
23
|
+
*/
|
|
24
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
25
|
+
exports.THEME_TOKENS = void 0;
|
|
26
|
+
exports.themeStyle = themeStyle;
|
|
27
|
+
exports.toneVar = toneVar;
|
|
28
|
+
exports.toneBgVar = toneBgVar;
|
|
29
|
+
exports.THEME_TOKENS = [
|
|
30
|
+
'font',
|
|
31
|
+
'font-mono',
|
|
32
|
+
'radius',
|
|
33
|
+
'gap',
|
|
34
|
+
'surface',
|
|
35
|
+
'surface-raised',
|
|
36
|
+
'border',
|
|
37
|
+
'text',
|
|
38
|
+
'text-muted',
|
|
39
|
+
'text-faint',
|
|
40
|
+
'focus',
|
|
41
|
+
'tone-neutral',
|
|
42
|
+
'tone-neutral-bg',
|
|
43
|
+
'tone-info',
|
|
44
|
+
'tone-info-bg',
|
|
45
|
+
'tone-caution',
|
|
46
|
+
'tone-caution-bg',
|
|
47
|
+
'tone-elevated',
|
|
48
|
+
'tone-elevated-bg',
|
|
49
|
+
'tone-high',
|
|
50
|
+
'tone-high-bg',
|
|
51
|
+
'tone-critical',
|
|
52
|
+
'tone-critical-bg',
|
|
53
|
+
'tone-fact',
|
|
54
|
+
'tone-fact-bg',
|
|
55
|
+
'tone-capability',
|
|
56
|
+
'tone-capability-bg',
|
|
57
|
+
'tone-unknown',
|
|
58
|
+
'tone-unknown-bg',
|
|
59
|
+
'tone-inactive',
|
|
60
|
+
'tone-inactive-bg',
|
|
61
|
+
];
|
|
62
|
+
/**
|
|
63
|
+
* Turn a theme into inline custom properties.
|
|
64
|
+
*
|
|
65
|
+
* Inline custom properties win over the stylesheet's defaults *and* over the
|
|
66
|
+
* `prefers-color-scheme` block, which is the behaviour an integrator with a
|
|
67
|
+
* fixed brand palette needs — otherwise their light theme would flip to our
|
|
68
|
+
* dark one on a user's OS setting.
|
|
69
|
+
*/
|
|
70
|
+
function themeStyle(theme, base) {
|
|
71
|
+
if (!theme)
|
|
72
|
+
return base;
|
|
73
|
+
const style = {};
|
|
74
|
+
for (const [token, value] of Object.entries(theme)) {
|
|
75
|
+
if (typeof value === 'string' && value.length > 0)
|
|
76
|
+
style[`--dcy-${token}`] = value;
|
|
77
|
+
}
|
|
78
|
+
// Custom properties are not part of the CSSProperties index signature; the
|
|
79
|
+
// cast is the standard, and only, way to express them in React's types.
|
|
80
|
+
return { ...(base ?? {}), ...style };
|
|
81
|
+
}
|
|
82
|
+
/** The CSS variable reference for a tone's foreground, e.g. for a border. */
|
|
83
|
+
function toneVar(tone) {
|
|
84
|
+
return `var(--dcy-tone-${tone})`;
|
|
85
|
+
}
|
|
86
|
+
/** The CSS variable reference for a tone's background wash. */
|
|
87
|
+
function toneBgVar(tone) {
|
|
88
|
+
return `var(--dcy-tone-${tone}-bg)`;
|
|
89
|
+
}
|
|
90
|
+
//# sourceMappingURL=theme.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"theme.js","sourceRoot":"","sources":["../src/theme.ts"],"names":[],"mappings":";AAAA;;;;;;;;;;;;;;;;;;;;;GAqBG;;;AAyEH,gCAYC;AAGD,0BAEC;AAGD,8BAEC;AArEY,QAAA,YAAY,GAAG;IAC1B,MAAM;IACN,WAAW;IACX,QAAQ;IACR,KAAK;IACL,SAAS;IACT,gBAAgB;IAChB,QAAQ;IACR,MAAM;IACN,YAAY;IACZ,YAAY;IACZ,OAAO;IACP,cAAc;IACd,iBAAiB;IACjB,WAAW;IACX,cAAc;IACd,cAAc;IACd,iBAAiB;IACjB,eAAe;IACf,kBAAkB;IAClB,WAAW;IACX,cAAc;IACd,eAAe;IACf,kBAAkB;IAClB,WAAW;IACX,cAAc;IACd,iBAAiB;IACjB,oBAAoB;IACpB,cAAc;IACd,iBAAiB;IACjB,eAAe;IACf,kBAAkB;CACV,CAAC;AAOX;;;;;;;GAOG;AACH,SAAgB,UAAU,CACxB,KAAsB,EACtB,IAAoB;IAEpB,IAAI,CAAC,KAAK;QAAE,OAAO,IAAI,CAAC;IACxB,MAAM,KAAK,GAA2B,EAAE,CAAC;IACzC,KAAK,MAAM,CAAC,KAAK,EAAE,KAAK,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,KAAK,CAAC,EAAE,CAAC;QACnD,IAAI,OAAO,KAAK,KAAK,QAAQ,IAAI,KAAK,CAAC,MAAM,GAAG,CAAC;YAAE,KAAK,CAAC,SAAS,KAAK,EAAE,CAAC,GAAG,KAAK,CAAC;IACrF,CAAC;IACD,2EAA2E;IAC3E,wEAAwE;IACxE,OAAO,EAAE,GAAG,CAAC,IAAI,IAAI,EAAE,CAAC,EAAE,GAAG,KAAK,EAAmB,CAAC;AACxD,CAAC;AAED,6EAA6E;AAC7E,SAAgB,OAAO,CAAC,IAAU;IAChC,OAAO,kBAAkB,IAAI,GAAG,CAAC;AACnC,CAAC;AAED,+DAA+D;AAC/D,SAAgB,SAAS,CAAC,IAAU;IAClC,OAAO,kBAAkB,IAAI,MAAM,CAAC;AACtC,CAAC"}
|
package/package.json
ADDED
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@decentrys/ui-sdk",
|
|
3
|
+
"version": "0.1.0",
|
|
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
|
+
"license": "MIT",
|
|
6
|
+
"author": "Decentrys Labs",
|
|
7
|
+
"homepage": "https://decentrys.com",
|
|
8
|
+
"repository": {
|
|
9
|
+
"type": "git",
|
|
10
|
+
"url": "git+https://github.com/teamdecentrys-byte/Decentrys.git",
|
|
11
|
+
"directory": "sdk/ui"
|
|
12
|
+
},
|
|
13
|
+
"bugs": {
|
|
14
|
+
"url": "https://github.com/teamdecentrys-byte/Decentrys/issues"
|
|
15
|
+
},
|
|
16
|
+
"keywords": [
|
|
17
|
+
"decentrys",
|
|
18
|
+
"blockchain",
|
|
19
|
+
"web3",
|
|
20
|
+
"security",
|
|
21
|
+
"crypto",
|
|
22
|
+
"react",
|
|
23
|
+
"components",
|
|
24
|
+
"ui",
|
|
25
|
+
"wallet-ui"
|
|
26
|
+
],
|
|
27
|
+
"sideEffects": false,
|
|
28
|
+
"engines": {
|
|
29
|
+
"node": ">=18"
|
|
30
|
+
},
|
|
31
|
+
"publishConfig": {
|
|
32
|
+
"access": "public"
|
|
33
|
+
},
|
|
34
|
+
"main": "dist/index.js",
|
|
35
|
+
"types": "dist/index.d.ts",
|
|
36
|
+
"exports": {
|
|
37
|
+
".": {
|
|
38
|
+
"types": "./dist/index.d.ts",
|
|
39
|
+
"import": "./dist/browser/decentrys-ui.mjs",
|
|
40
|
+
"default": "./dist/index.js"
|
|
41
|
+
},
|
|
42
|
+
"./browser": "./dist/browser/decentrys-ui.mjs"
|
|
43
|
+
},
|
|
44
|
+
"files": [
|
|
45
|
+
"dist",
|
|
46
|
+
"!dist/.tsbuildinfo*",
|
|
47
|
+
"src",
|
|
48
|
+
"LICENSE",
|
|
49
|
+
"README.md"
|
|
50
|
+
],
|
|
51
|
+
"dependencies": {
|
|
52
|
+
"@decentrys/protect": "0.1.0"
|
|
53
|
+
},
|
|
54
|
+
"peerDependencies": {
|
|
55
|
+
"react": ">=18",
|
|
56
|
+
"react-dom": ">=18"
|
|
57
|
+
},
|
|
58
|
+
"devDependencies": {
|
|
59
|
+
"@types/react": "^19.0.7",
|
|
60
|
+
"@types/react-dom": "^19.0.3",
|
|
61
|
+
"typescript": "^5.7.2",
|
|
62
|
+
"vitest": "^3.2.7"
|
|
63
|
+
},
|
|
64
|
+
"scripts": {
|
|
65
|
+
"build": "tsc -p tsconfig.json && npm run build:browser",
|
|
66
|
+
"build:browser": "../../node_modules/.bin/esbuild src/index.ts --bundle --format=esm --target=es2022 --jsx=automatic --external:react --external:react-dom --external:react/jsx-runtime --outfile=dist/browser/decentrys-ui.mjs && ../../node_modules/.bin/esbuild src/index.ts --bundle --format=iife --global-name=DecentrysUI --target=es2022 --jsx=automatic --alias:react=./src/browser/react-global.ts --alias:react/jsx-runtime=./src/browser/react-jsx-runtime.ts --alias:react-dom=./src/browser/react-dom-global.ts --outfile=dist/browser/decentrys-ui.js",
|
|
67
|
+
"typecheck": "tsc -p tsconfig.spec.json",
|
|
68
|
+
"test": "vitest run"
|
|
69
|
+
}
|
|
70
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `react-dom` for the script-tag build. Only `createPortal` is used, by the
|
|
3
|
+
* details modal, and only when its `portal` prop is left on.
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
type ReactDomModule = Record<string, any>;
|
|
7
|
+
|
|
8
|
+
function resolveReactDom(): ReactDomModule {
|
|
9
|
+
const candidate = (globalThis as { ReactDOM?: ReactDomModule }).ReactDOM;
|
|
10
|
+
if (!candidate || typeof candidate.createPortal !== 'function') {
|
|
11
|
+
throw new Error(
|
|
12
|
+
'DecentrysUI: RiskDetailsModal needs ReactDOM on the page (window.ReactDOM) to portal into '
|
|
13
|
+
+ 'document.body. Load ReactDOM, or pass portal={false} to render the dialog in place.',
|
|
14
|
+
);
|
|
15
|
+
}
|
|
16
|
+
return candidate;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export const createPortal = (...args: unknown[]): unknown =>
|
|
20
|
+
resolveReactDom().createPortal(...args);
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* How the script-tag build gets React.
|
|
3
|
+
*
|
|
4
|
+
* The ESM browser build leaves `react` as a bare import, which is right: an
|
|
5
|
+
* ESM consumer has a bundler or an import map. The IIFE build has neither, and
|
|
6
|
+
* esbuild's `--external` would leave a `require("react")` call in it that
|
|
7
|
+
* throws the moment the file loads. Bundling React instead is not an option —
|
|
8
|
+
* two copies of React in one application is a real and confusing breakage, and
|
|
9
|
+
* this file is aliased in precisely so that never happens.
|
|
10
|
+
*
|
|
11
|
+
* So the IIFE build resolves React from the page, the way every UMD component
|
|
12
|
+
* library has always done.
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
type ReactModule = Record<string, any>;
|
|
16
|
+
|
|
17
|
+
function resolveReact(): ReactModule {
|
|
18
|
+
const candidate = (globalThis as { React?: ReactModule }).React;
|
|
19
|
+
if (!candidate || typeof candidate.createElement !== 'function') {
|
|
20
|
+
// Loud and immediate, with the fix in the message. The alternative is
|
|
21
|
+
// "cannot read property createElement of undefined" from inside a bundle,
|
|
22
|
+
// several frames away from the missing script tag that caused it.
|
|
23
|
+
throw new Error(
|
|
24
|
+
'DecentrysUI: the script-tag build expects React on the page already '
|
|
25
|
+
+ '(window.React, and window.ReactDOM for the details modal). Load React first, '
|
|
26
|
+
+ 'or use the ESM build / the npm package if you have a bundler.',
|
|
27
|
+
);
|
|
28
|
+
}
|
|
29
|
+
return candidate;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
const React: ReactModule = resolveReact();
|
|
33
|
+
|
|
34
|
+
export default React;
|
|
35
|
+
export const createContext = React.createContext;
|
|
36
|
+
export const useContext = React.useContext;
|
|
37
|
+
export const useState = React.useState;
|
|
38
|
+
export const useEffect = React.useEffect;
|
|
39
|
+
export const useId = React.useId;
|
|
40
|
+
export const useRef = React.useRef;
|
|
41
|
+
export const createElement = React.createElement;
|
|
42
|
+
export const Fragment = React.Fragment;
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The automatic JSX runtime, expressed through `createElement`.
|
|
3
|
+
*
|
|
4
|
+
* React 17+ ships `react/jsx-runtime` as its own entry point, and a page that
|
|
5
|
+
* loaded React from a CDN script tag has the global but not that entry. The
|
|
6
|
+
* two are equivalent for everything this package emits: `jsx(type, props,
|
|
7
|
+
* key)` is `createElement` with `children` already inside `props`, which is
|
|
8
|
+
* exactly what `createElement` does with a config object when no variadic
|
|
9
|
+
* children are passed.
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
import React from './react-global';
|
|
13
|
+
|
|
14
|
+
export const Fragment = React.Fragment;
|
|
15
|
+
|
|
16
|
+
function create(type: unknown, props: Record<string, unknown> | null, key?: unknown): unknown {
|
|
17
|
+
if (key === undefined || key === null) return React.createElement(type, props);
|
|
18
|
+
return React.createElement(type, { ...(props ?? {}), key });
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export const jsx = create;
|
|
22
|
+
export const jsxs = create;
|
|
23
|
+
export const jsxDEV = create;
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The compact form — an inline badge beside an address.
|
|
3
|
+
*
|
|
4
|
+
* Small enough that it is tempting to reduce to a coloured dot, which would be
|
|
5
|
+
* the single worst thing this package could ship: a colour with no words is
|
|
6
|
+
* unreadable to a colour-blind user and unfalsifiable to everyone else. So the
|
|
7
|
+
* badge always carries the level's label as text, always carries an ordinal
|
|
8
|
+
* glyph that does not depend on colour, and always carries the model's own
|
|
9
|
+
* meaning in a screen-reader-only span, so the short label is never the only
|
|
10
|
+
* thing a non-sighted user gets.
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
import { type ReactNode } from 'react';
|
|
14
|
+
import type { Assessment, RiskLevel } from '@decentrys/protect';
|
|
15
|
+
import { useUiConfig } from '../context';
|
|
16
|
+
import { themeStyle } from '../theme';
|
|
17
|
+
import { LevelBadge, Mono, ScreenReaderText, StyleGate, type CommonProps, cx } from '../primitives';
|
|
18
|
+
import { levelPresentation, shortenAddress } from '../presentation';
|
|
19
|
+
|
|
20
|
+
interface AddressRiskBadgeBaseProps extends CommonProps {
|
|
21
|
+
/** Rendered in short form beside the badge when supplied. */
|
|
22
|
+
address?: string;
|
|
23
|
+
/**
|
|
24
|
+
* Turns the badge into a button — for opening details, never for blocking.
|
|
25
|
+
* Nothing in this package disables a host application's controls.
|
|
26
|
+
*/
|
|
27
|
+
onSelect?: () => void;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* One of `assessment` or `level` is required, enforced by the type rather than
|
|
32
|
+
* by a runtime throw: a component that throws inside a wallet's render tree
|
|
33
|
+
* takes that wallet's screen down, which is a worse failure than the wiring
|
|
34
|
+
* mistake it was reporting.
|
|
35
|
+
*/
|
|
36
|
+
export type AddressRiskBadgeProps = AddressRiskBadgeBaseProps &
|
|
37
|
+
({ assessment: Assessment; level?: never } | { level: RiskLevel; assessment?: never });
|
|
38
|
+
|
|
39
|
+
export function AddressRiskBadge(props: AddressRiskBadgeProps): ReactNode {
|
|
40
|
+
const { assessment, level, address, onSelect, className, style, theme, copy, levelLabels } = props;
|
|
41
|
+
const { theme: mergedTheme, emitStyles } = useUiConfig({ theme, copy });
|
|
42
|
+
const resolved = assessment?.riskLevel ?? level;
|
|
43
|
+
if (!resolved) return null;
|
|
44
|
+
|
|
45
|
+
const presentation = levelPresentation(resolved, { labels: levelLabels });
|
|
46
|
+
const rootClass = cx('dcy', 'dcy-inline', className);
|
|
47
|
+
const rootStyle = themeStyle(mergedTheme, style);
|
|
48
|
+
|
|
49
|
+
const content = (
|
|
50
|
+
<>
|
|
51
|
+
<LevelBadge level={presentation} />
|
|
52
|
+
{address ? <Mono>{shortenAddress(address)}</Mono> : null}
|
|
53
|
+
<ScreenReaderText>{presentation.meaning}</ScreenReaderText>
|
|
54
|
+
</>
|
|
55
|
+
);
|
|
56
|
+
|
|
57
|
+
// The stylesheet is emitted as a sibling rather than a child: a <style> tag
|
|
58
|
+
// inside a <button> is legal but pointless clutter in the accessibility tree.
|
|
59
|
+
if (onSelect) {
|
|
60
|
+
return (
|
|
61
|
+
<>
|
|
62
|
+
<StyleGate emit={emitStyles} />
|
|
63
|
+
<button
|
|
64
|
+
type="button"
|
|
65
|
+
className={rootClass}
|
|
66
|
+
style={rootStyle}
|
|
67
|
+
title={presentation.meaning}
|
|
68
|
+
onClick={onSelect}
|
|
69
|
+
data-dcy-component="address-risk-badge"
|
|
70
|
+
data-dcy-level={resolved}
|
|
71
|
+
data-dcy-tone={presentation.tone}
|
|
72
|
+
>
|
|
73
|
+
{content}
|
|
74
|
+
</button>
|
|
75
|
+
</>
|
|
76
|
+
);
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
return (
|
|
80
|
+
<>
|
|
81
|
+
<StyleGate emit={emitStyles} />
|
|
82
|
+
<span
|
|
83
|
+
className={rootClass}
|
|
84
|
+
style={rootStyle}
|
|
85
|
+
title={presentation.meaning}
|
|
86
|
+
data-dcy-component="address-risk-badge"
|
|
87
|
+
data-dcy-level={resolved}
|
|
88
|
+
data-dcy-tone={presentation.tone}
|
|
89
|
+
>
|
|
90
|
+
{content}
|
|
91
|
+
</span>
|
|
92
|
+
</>
|
|
93
|
+
);
|
|
94
|
+
}
|
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The approval screen's component.
|
|
3
|
+
*
|
|
4
|
+
* The bar this has to clear is the one Stage 5 of the plan set: it shows
|
|
5
|
+
* "unlimited approval requested", not "scam detected". An unlimited allowance
|
|
6
|
+
* is a *scope*, and describing that scope precisely — who, over what, for how
|
|
7
|
+
* long, and that it survives until revoked — is more useful than any verdict,
|
|
8
|
+
* because it is the part a person can actually act on.
|
|
9
|
+
*
|
|
10
|
+
* So the scope block renders in the capability tone, never the threat tone,
|
|
11
|
+
* at any allowance size. Threat signals about the spender appear too, in their
|
|
12
|
+
* own section, looking nothing like the scope block. If those two ever merged
|
|
13
|
+
* visually, an unlimited approval to a perfectly ordinary DEX router would
|
|
14
|
+
* look identical to one to a confirmed drainer.
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
import { type ReactNode } from 'react';
|
|
18
|
+
import type { ProtectResult } from '@decentrys/protect';
|
|
19
|
+
import { useUiConfig } from '../context';
|
|
20
|
+
import { themeStyle } from '../theme';
|
|
21
|
+
import {
|
|
22
|
+
AvailabilityNote, Chip, type CommonProps, EmptyNote, HistoryNote, LevelHeader, Mono, Section,
|
|
23
|
+
SignalList, StyleGate, UnknownRow, cx,
|
|
24
|
+
} from '../primitives';
|
|
25
|
+
import {
|
|
26
|
+
type ApprovalDetails, approvalPresentation, assessmentSections, decisionPresentation,
|
|
27
|
+
levelPresentation,
|
|
28
|
+
} from '../presentation';
|
|
29
|
+
|
|
30
|
+
export interface ApprovalWarningProps extends CommonProps {
|
|
31
|
+
result: ProtectResult;
|
|
32
|
+
/** The allowance itself: token, spender and amount. */
|
|
33
|
+
approval: ApprovalDetails;
|
|
34
|
+
/** The integrator's own controls. Rendered untouched and never disabled. */
|
|
35
|
+
actions?: ReactNode;
|
|
36
|
+
onViewDetails?: () => void;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
export function ApprovalWarning({
|
|
40
|
+
result, approval, actions, onViewDetails,
|
|
41
|
+
className, style, theme, copy: copyOverrides, levelLabels,
|
|
42
|
+
}: ApprovalWarningProps): ReactNode {
|
|
43
|
+
const { copy, theme: mergedTheme, emitStyles } = useUiConfig({ theme, copy: copyOverrides });
|
|
44
|
+
const { assessment, decision, demotedSignals } = result;
|
|
45
|
+
const level = levelPresentation(assessment.riskLevel, { labels: levelLabels });
|
|
46
|
+
const scope = approvalPresentation(approval);
|
|
47
|
+
const sections = assessmentSections(assessment, copy);
|
|
48
|
+
const policy = decisionPresentation(decision);
|
|
49
|
+
|
|
50
|
+
return (
|
|
51
|
+
<section
|
|
52
|
+
className={cx('dcy', 'dcy-card', className)}
|
|
53
|
+
style={themeStyle(mergedTheme, style)}
|
|
54
|
+
data-dcy-component="approval-warning"
|
|
55
|
+
data-dcy-level={assessment.riskLevel}
|
|
56
|
+
data-dcy-tone={level.tone}
|
|
57
|
+
data-dcy-unlimited={String(scope.unlimited)}
|
|
58
|
+
role={level.liveRegionRole}
|
|
59
|
+
aria-label={`${scope.scopeLabel}. ${level.ariaLabel}`}
|
|
60
|
+
>
|
|
61
|
+
<StyleGate emit={emitStyles} />
|
|
62
|
+
<LevelHeader assessment={assessment} copy={copy} levelLabels={levelLabels} />
|
|
63
|
+
|
|
64
|
+
<AvailabilityNote assessment={assessment} />
|
|
65
|
+
|
|
66
|
+
{/*
|
|
67
|
+
The scope block. Capability tone at every size — an unlimited allowance
|
|
68
|
+
is a description of what is being granted, not an accusation about who
|
|
69
|
+
is asking for it.
|
|
70
|
+
*/}
|
|
71
|
+
<Section title={copy.approvalHeading}>
|
|
72
|
+
<div
|
|
73
|
+
className="dcy-row"
|
|
74
|
+
data-dcy-kind="capability"
|
|
75
|
+
data-dcy-tone={scope.tone}
|
|
76
|
+
data-dcy-rule="dashed"
|
|
77
|
+
>
|
|
78
|
+
<span className="dcy-glyph" aria-hidden="true">◇</span>
|
|
79
|
+
<div className="dcy-row-body">
|
|
80
|
+
<p className={cx('dcy-row-statement', scope.emphasis === 'strong' && 'dcy-row-strong')}>
|
|
81
|
+
<Chip>{scope.scopeLabel}</Chip> {scope.statement}
|
|
82
|
+
</p>
|
|
83
|
+
<p className="dcy-note">{copy.approvalRevocation}</p>
|
|
84
|
+
</div>
|
|
85
|
+
</div>
|
|
86
|
+
<dl className="dcy-kv">
|
|
87
|
+
<dt>{copy.spenderLabel}</dt>
|
|
88
|
+
<dd>
|
|
89
|
+
<Mono>{approval.spender}</Mono>
|
|
90
|
+
{approval.spenderLabel ? ` — ${approval.spenderLabel}` : null}
|
|
91
|
+
</dd>
|
|
92
|
+
<dt>{copy.tokenLabel}</dt>
|
|
93
|
+
<dd>
|
|
94
|
+
<Mono>{approval.token}</Mono>
|
|
95
|
+
{approval.symbol ? ` — ${approval.symbol}` : null}
|
|
96
|
+
</dd>
|
|
97
|
+
<dt>Amount</dt>
|
|
98
|
+
<dd>{scope.amountLabel}</dd>
|
|
99
|
+
</dl>
|
|
100
|
+
</Section>
|
|
101
|
+
|
|
102
|
+
<Section title={copy.signalsHeading}>
|
|
103
|
+
<SignalList
|
|
104
|
+
signals={[...sections.activeSignals, ...sections.inertSignals]}
|
|
105
|
+
copy={copy}
|
|
106
|
+
subjectNoun="spender"
|
|
107
|
+
/>
|
|
108
|
+
</Section>
|
|
109
|
+
|
|
110
|
+
{sections.unknowns.length > 0 ? (
|
|
111
|
+
<Section title={copy.unknownsHeading} note={copy.unknownsAreNotRisk}>
|
|
112
|
+
<ul className="dcy-list">
|
|
113
|
+
{sections.unknowns.map((unknown) => (
|
|
114
|
+
<UnknownRow key={unknown.field} unknown={unknown} copy={copy} />
|
|
115
|
+
))}
|
|
116
|
+
</ul>
|
|
117
|
+
</Section>
|
|
118
|
+
) : null}
|
|
119
|
+
|
|
120
|
+
<HistoryNote status={assessment.historyStatus} copy={copy} />
|
|
121
|
+
|
|
122
|
+
{demotedSignals && demotedSignals.length > 0 ? (
|
|
123
|
+
<EmptyNote>
|
|
124
|
+
{copy.demotedHeading}: {demotedSignals.join(', ')}. {copy.demotedExplanation}
|
|
125
|
+
</EmptyNote>
|
|
126
|
+
) : null}
|
|
127
|
+
|
|
128
|
+
<p className="dcy-note" data-dcy-decision={policy.action}>
|
|
129
|
+
<span className="dcy-chip">{policy.label}</span> {policy.statement}
|
|
130
|
+
</p>
|
|
131
|
+
|
|
132
|
+
{onViewDetails || actions ? (
|
|
133
|
+
<div className="dcy-head">
|
|
134
|
+
{onViewDetails ? (
|
|
135
|
+
<button type="button" className="dcy-btn dcy-btn-quiet" onClick={onViewDetails}>
|
|
136
|
+
{copy.viewDetails}
|
|
137
|
+
</button>
|
|
138
|
+
) : null}
|
|
139
|
+
{actions}
|
|
140
|
+
</div>
|
|
141
|
+
) : null}
|
|
142
|
+
</section>
|
|
143
|
+
);
|
|
144
|
+
}
|
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* What a contract is and what it can do — and nothing about what it might be
|
|
3
|
+
* accused of.
|
|
4
|
+
*
|
|
5
|
+
* The separation is the point. This component renders facts, capabilities and
|
|
6
|
+
* unknowns; threat signals are rendered by `ThreatSignalList`, in a visually
|
|
7
|
+
* different shape, because "deployed 3 days ago" and "attributed to a drainer
|
|
8
|
+
* family" appearing as two grey rows in the same list is exactly the confusion
|
|
9
|
+
* this product exists to remove.
|
|
10
|
+
*
|
|
11
|
+
* It does not therefore *hide* signals. When the assessment carries any, this
|
|
12
|
+
* component says how many and that they are shown separately, so nobody
|
|
13
|
+
* embeds this panel alone and unwittingly presents a partial picture.
|
|
14
|
+
*/
|
|
15
|
+
|
|
16
|
+
import { type ReactNode } from 'react';
|
|
17
|
+
import type { Assessment } from '@decentrys/protect';
|
|
18
|
+
import { useUiConfig } from '../context';
|
|
19
|
+
import { themeStyle } from '../theme';
|
|
20
|
+
import {
|
|
21
|
+
AvailabilityNote, CapabilityRow, type CommonProps, DemotedNote, EmptyNote, FactRow, HistoryNote,
|
|
22
|
+
Section, StyleGate, UnknownRow, cx,
|
|
23
|
+
} from '../primitives';
|
|
24
|
+
import { assessmentSections, plural } from '../presentation';
|
|
25
|
+
|
|
26
|
+
export interface ContractFactsProps extends CommonProps {
|
|
27
|
+
assessment: Assessment;
|
|
28
|
+
/** Coverage signals the SDK refused to score, from `ProtectResult`. */
|
|
29
|
+
demotedSignals?: string[];
|
|
30
|
+
/** Called when a reader asks to see the threat signals shown elsewhere. */
|
|
31
|
+
onViewSignals?: () => void;
|
|
32
|
+
title?: string;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export function ContractFacts({
|
|
36
|
+
assessment, demotedSignals, onViewSignals, title,
|
|
37
|
+
className, style, theme, copy: copyOverrides,
|
|
38
|
+
}: ContractFactsProps): ReactNode {
|
|
39
|
+
const { copy, theme: mergedTheme, emitStyles } = useUiConfig({ theme, copy: copyOverrides });
|
|
40
|
+
const sections = assessmentSections(assessment, copy);
|
|
41
|
+
const signalCount = sections.activeSignals.length + sections.inertSignals.length;
|
|
42
|
+
|
|
43
|
+
return (
|
|
44
|
+
<section
|
|
45
|
+
className={cx('dcy', 'dcy-card', className)}
|
|
46
|
+
style={themeStyle(mergedTheme, style)}
|
|
47
|
+
data-dcy-component="contract-facts"
|
|
48
|
+
aria-label={title ?? 'Contract facts and capabilities'}
|
|
49
|
+
>
|
|
50
|
+
<StyleGate emit={emitStyles} />
|
|
51
|
+
{title ? <h3 className="dcy-title">{title}</h3> : null}
|
|
52
|
+
|
|
53
|
+
<AvailabilityNote assessment={assessment} />
|
|
54
|
+
|
|
55
|
+
<Section title={copy.capabilitiesHeading} note={copy.capabilitiesAreNotFindings}>
|
|
56
|
+
{sections.capabilities.length === 0 ? (
|
|
57
|
+
<EmptyNote>{copy.noCapabilities}</EmptyNote>
|
|
58
|
+
) : (
|
|
59
|
+
<ul className="dcy-list">
|
|
60
|
+
{sections.capabilities.map((capability, index) => (
|
|
61
|
+
<CapabilityRow
|
|
62
|
+
key={`${capability.type}:${index}`}
|
|
63
|
+
capability={capability}
|
|
64
|
+
copy={copy}
|
|
65
|
+
/>
|
|
66
|
+
))}
|
|
67
|
+
</ul>
|
|
68
|
+
)}
|
|
69
|
+
</Section>
|
|
70
|
+
|
|
71
|
+
<Section title={copy.factsHeading} note={copy.factsAreNotFindings}>
|
|
72
|
+
{sections.facts.length === 0 ? (
|
|
73
|
+
<EmptyNote>{copy.noFacts}</EmptyNote>
|
|
74
|
+
) : (
|
|
75
|
+
<ul className="dcy-list">
|
|
76
|
+
{sections.facts.map((fact, index) => (
|
|
77
|
+
<FactRow key={`${fact.type}:${index}`} fact={fact} copy={copy} />
|
|
78
|
+
))}
|
|
79
|
+
</ul>
|
|
80
|
+
)}
|
|
81
|
+
</Section>
|
|
82
|
+
|
|
83
|
+
<Section title={copy.unknownsHeading} note={copy.unknownsAreNotRisk}>
|
|
84
|
+
{sections.unknowns.length === 0 ? (
|
|
85
|
+
<EmptyNote>{copy.noUnknowns}</EmptyNote>
|
|
86
|
+
) : (
|
|
87
|
+
<ul className="dcy-list">
|
|
88
|
+
{sections.unknowns.map((unknown) => (
|
|
89
|
+
<UnknownRow key={unknown.field} unknown={unknown} copy={copy} />
|
|
90
|
+
))}
|
|
91
|
+
</ul>
|
|
92
|
+
)}
|
|
93
|
+
</Section>
|
|
94
|
+
|
|
95
|
+
<HistoryNote status={assessment.historyStatus} copy={copy} />
|
|
96
|
+
|
|
97
|
+
<DemotedNote demotedSignals={demotedSignals} copy={copy} />
|
|
98
|
+
|
|
99
|
+
{/* Signals live in their own component. Saying so keeps this panel from
|
|
100
|
+
quietly reading as the whole assessment. */}
|
|
101
|
+
{signalCount > 0 ? (
|
|
102
|
+
<p className="dcy-note" data-dcy-signals-elsewhere="">
|
|
103
|
+
{signalCount} {plural(signalCount, 'threat signal is', 'threat signals are')} reported for
|
|
104
|
+
this subject and {plural(signalCount, 'is', 'are')} shown separately.
|
|
105
|
+
{onViewSignals ? (
|
|
106
|
+
<>
|
|
107
|
+
{' '}
|
|
108
|
+
<button type="button" className="dcy-btn dcy-btn-quiet" onClick={onViewSignals}>
|
|
109
|
+
{copy.signalsHeading}
|
|
110
|
+
</button>
|
|
111
|
+
</>
|
|
112
|
+
) : null}
|
|
113
|
+
</p>
|
|
114
|
+
) : (
|
|
115
|
+
<p className="dcy-note">{copy.noSignals}</p>
|
|
116
|
+
)}
|
|
117
|
+
</section>
|
|
118
|
+
);
|
|
119
|
+
}
|