@cardano-slips/flow 0.0.2 → 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/README.md +27 -0
- package/dist/address.d.ts +13 -0
- package/dist/address.d.ts.map +1 -0
- package/dist/address.js +54 -0
- package/dist/address.js.map +1 -0
- package/dist/cip30.d.ts +66 -0
- package/dist/cip30.d.ts.map +1 -0
- package/dist/cip30.js +53 -0
- package/dist/cip30.js.map +1 -0
- package/dist/connect.d.ts +45 -0
- package/dist/connect.d.ts.map +1 -0
- package/dist/connect.js +73 -0
- package/dist/connect.js.map +1 -0
- package/dist/discovery.d.ts +37 -0
- package/dist/discovery.d.ts.map +1 -0
- package/dist/discovery.js +71 -0
- package/dist/discovery.js.map +1 -0
- package/dist/index.d.ts +12 -7
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +6 -1
- package/dist/index.js.map +1 -1
- package/dist/registry.d.ts +18 -0
- package/dist/registry.d.ts.map +1 -0
- package/dist/registry.js +15 -0
- package/dist/registry.js.map +1 -0
- package/dist/tokens.css +184 -0
- package/dist/wallet-error.d.ts +48 -0
- package/dist/wallet-error.d.ts.map +1 -0
- package/dist/wallet-error.js +31 -0
- package/dist/wallet-error.js.map +1 -0
- package/package.json +6 -4
- package/src/address.ts +69 -0
- package/src/cip30.ts +94 -0
- package/src/connect.ts +136 -0
- package/src/discovery.ts +92 -0
- package/src/index.ts +12 -7
- package/src/registry.ts +32 -0
- package/src/tokens.css +184 -0
- package/src/wallet-error.ts +68 -0
package/src/discovery.ts
ADDED
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* What is injected at `window.cardano`, read on demand. Nothing here touches a
|
|
3
|
+
* browser global at module scope: this SDK is imported by server-rendered pages
|
|
4
|
+
* we do not control, and a module that reads `window` while loading breaks them
|
|
5
|
+
* before any of it runs (ADR-0004).
|
|
6
|
+
*/
|
|
7
|
+
import { type Cip30Provider, isCip30Provider } from "./cip30.js"
|
|
8
|
+
import { knownWallet } from "./registry.js"
|
|
9
|
+
|
|
10
|
+
export type DiscoveredWallet = {
|
|
11
|
+
/** The `window.cardano` key. What `connectWallet` is asked for. */
|
|
12
|
+
readonly key: string
|
|
13
|
+
readonly name: string
|
|
14
|
+
readonly icon: string | undefined
|
|
15
|
+
readonly apiVersion: string
|
|
16
|
+
/** CIP numbers the provider says it supports, before `enable()` negotiates any of them. */
|
|
17
|
+
readonly extensions: ReadonlyArray<number>
|
|
18
|
+
/** Present only for a wallet in the registry. */
|
|
19
|
+
readonly install: string | undefined
|
|
20
|
+
readonly provider: Cip30Provider
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/** Anything with a `cardano` namespace: a `Window`, or a stand-in in a test. */
|
|
24
|
+
export type WalletHost = { readonly cardano?: unknown }
|
|
25
|
+
|
|
26
|
+
const namespaceOf = (host: unknown): Record<string, unknown> | undefined => {
|
|
27
|
+
if (typeof host !== "object" || host === null) return undefined
|
|
28
|
+
const cardano = (host as Record<string, unknown>).cardano
|
|
29
|
+
if (typeof cardano !== "object" || cardano === null) return undefined
|
|
30
|
+
return cardano as Record<string, unknown>
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
const extensionsOf = (provider: Cip30Provider): ReadonlyArray<number> =>
|
|
34
|
+
(provider.supportedExtensions ?? [])
|
|
35
|
+
.filter((extension) => typeof extension?.cip === "number")
|
|
36
|
+
.map((extension) => extension.cip)
|
|
37
|
+
|
|
38
|
+
const describe = (key: string, provider: Cip30Provider): DiscoveredWallet => {
|
|
39
|
+
const known = knownWallet(key)
|
|
40
|
+
return {
|
|
41
|
+
key,
|
|
42
|
+
// The registry wins over the injected name so one extension reads the same
|
|
43
|
+
// way in every list; an unknown wallet still gets to name itself.
|
|
44
|
+
name: known?.name ?? (provider.name === "" ? key : provider.name),
|
|
45
|
+
icon: typeof provider.icon === "string" && provider.icon !== "" ? provider.icon : undefined,
|
|
46
|
+
apiVersion: provider.apiVersion,
|
|
47
|
+
extensions: extensionsOf(provider),
|
|
48
|
+
install: known?.install,
|
|
49
|
+
provider
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* Every CIP-30 provider in the namespace, by display name. Returns an empty
|
|
55
|
+
* list where there is no namespace at all — a page with no wallet and a page
|
|
56
|
+
* rendered on a server are the same case to a caller, and neither is an error.
|
|
57
|
+
*/
|
|
58
|
+
export const discoverWallets = (host: unknown = globalThis): ReadonlyArray<DiscoveredWallet> => {
|
|
59
|
+
const namespace = namespaceOf(host)
|
|
60
|
+
if (namespace === undefined) return []
|
|
61
|
+
|
|
62
|
+
const found = new Map<string, DiscoveredWallet>()
|
|
63
|
+
for (const key of Object.keys(namespace)) {
|
|
64
|
+
const provider = namespace[key]
|
|
65
|
+
if (isCip30Provider(provider)) found.set(key, describe(key, provider))
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
// An extension injecting two keys is one wallet: listing it twice asks the
|
|
69
|
+
// person to choose between a wallet and itself.
|
|
70
|
+
for (const key of [...found.keys()]) {
|
|
71
|
+
const alias = knownWallet(key)?.aliasOf
|
|
72
|
+
if (alias !== undefined && found.has(alias)) found.delete(key)
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
return [...found.values()].sort(
|
|
76
|
+
(left, right) => left.name.localeCompare(right.name) || left.key.localeCompare(right.key)
|
|
77
|
+
)
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* Whatever sits under the key, connector or not. Lets a caller separate a
|
|
82
|
+
* wallet that is not installed from one that is and does not speak CIP-30.
|
|
83
|
+
*/
|
|
84
|
+
export const injectedUnder = (key: string, host: unknown = globalThis): unknown => namespaceOf(host)?.[key]
|
|
85
|
+
|
|
86
|
+
/** The provider under one key, or `undefined` where nothing usable is there. */
|
|
87
|
+
export const findWallet = (key: string, host: unknown = globalThis): DiscoveredWallet | undefined => {
|
|
88
|
+
const namespace = namespaceOf(host)
|
|
89
|
+
if (namespace === undefined) return undefined
|
|
90
|
+
const provider = namespace[key]
|
|
91
|
+
return isCip30Provider(provider) ? describe(key, provider) : undefined
|
|
92
|
+
}
|
package/src/index.ts
CHANGED
|
@@ -1,11 +1,16 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* The public entry point of `@cardano-slips/flow`. The `exports` map exposes
|
|
3
3
|
* this module and nothing else, so moving a file is never a breaking change.
|
|
4
|
-
*
|
|
5
|
-
* Two properties hold as the rest of the package arrives. It refuses but does
|
|
6
|
-
* not decide — the comparison is `verifier`'s, and a mismatch renders as a
|
|
7
|
-
* block with no override path to write. And it does not own the page it is
|
|
8
|
-
* dropped into: no fixed positioning, no assumptions about the document, and
|
|
9
|
-
* colours only from the custom properties in `tokens.css`.
|
|
10
4
|
*/
|
|
11
|
-
export {}
|
|
5
|
+
export type { NetworkId, WalletAddress } from "./address.js"
|
|
6
|
+
export { readWalletAddress } from "./address.js"
|
|
7
|
+
export type { CborHex, Cip30Api, Cip30ApiError, Cip30Provider, Paginate, SignedData } from "./cip30.js"
|
|
8
|
+
export { apiErrorNames, describeApiError, isCip30Api, isCip30Provider, readApiError, REFUSED } from "./cip30.js"
|
|
9
|
+
export type { ConnectedWallet, ConnectOptions } from "./connect.js"
|
|
10
|
+
export { connectWallet, networkIdFor } from "./connect.js"
|
|
11
|
+
export type { DiscoveredWallet, WalletHost } from "./discovery.js"
|
|
12
|
+
export { discoverWallets, findWallet, injectedUnder } from "./discovery.js"
|
|
13
|
+
export type { KnownWallet } from "./registry.js"
|
|
14
|
+
export { knownWallet, knownWallets } from "./registry.js"
|
|
15
|
+
export type { ConnectRefusal } from "./wallet-error.js"
|
|
16
|
+
export { connectRefusals, slipErrorCodeFor, WalletConnectError } from "./wallet-error.js"
|
package/src/registry.ts
ADDED
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Display metadata for the wallets we know by name. Ours to maintain — the one
|
|
3
|
+
* cost ADR-0004 accepted — and deliberately thin: a key an entry does not cover
|
|
4
|
+
* still connects, using the name and icon the extension injects.
|
|
5
|
+
*
|
|
6
|
+
* `install` is the wallet's own home page rather than a store listing, because
|
|
7
|
+
* a store listing is per browser and goes stale on its own schedule.
|
|
8
|
+
*/
|
|
9
|
+
export type KnownWallet = {
|
|
10
|
+
readonly key: string
|
|
11
|
+
readonly name: string
|
|
12
|
+
readonly install: string
|
|
13
|
+
/** A second key the same extension injects. Hidden while the primary is present. */
|
|
14
|
+
readonly aliasOf?: string
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export const knownWallets: ReadonlyArray<KnownWallet> = [
|
|
18
|
+
{ key: "lace", name: "Lace", install: "https://www.lace.io" },
|
|
19
|
+
{ key: "eternl", name: "Eternl", install: "https://eternl.io" },
|
|
20
|
+
{ key: "ccvault", name: "Eternl", install: "https://eternl.io", aliasOf: "eternl" },
|
|
21
|
+
{ key: "vespr", name: "VESPR", install: "https://vespr.xyz" },
|
|
22
|
+
{ key: "typhoncip30", name: "Typhon", install: "https://typhonwallet.io" },
|
|
23
|
+
{ key: "nami", name: "Nami", install: "https://www.namiwallet.io" },
|
|
24
|
+
{ key: "begin", name: "Begin", install: "https://begin.is" },
|
|
25
|
+
{ key: "nufi", name: "NuFi", install: "https://nu.fi" },
|
|
26
|
+
{ key: "gerowallet", name: "GeroWallet", install: "https://gerowallet.io" },
|
|
27
|
+
{ key: "yoroi", name: "Yoroi", install: "https://yoroi-wallet.com" }
|
|
28
|
+
]
|
|
29
|
+
|
|
30
|
+
const byKey = new Map(knownWallets.map((wallet) => [wallet.key, wallet]))
|
|
31
|
+
|
|
32
|
+
export const knownWallet = (key: string): KnownWallet | undefined => byKey.get(key)
|
package/src/tokens.css
ADDED
|
@@ -0,0 +1,184 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Every colour, type role, step of spacing and radius the client uses, taken
|
|
3
|
+
* from `1 · Tokens` on the design sheet. This is the only file in the package
|
|
4
|
+
* that writes a colour — `test/no-hard-coded-colours.test.ts` fails if another
|
|
5
|
+
* one does, and `test/tokens.test.ts` re-runs the sheet's contrast audit
|
|
6
|
+
* against these values.
|
|
7
|
+
*
|
|
8
|
+
* Scoped to a class, never `:root`: this ships into pages we do not own, and a
|
|
9
|
+
* package that writes tokens onto the document redefines whatever the host
|
|
10
|
+
* already called `--ink`. Put `slip-root` on the element that wraps the Slip.
|
|
11
|
+
*
|
|
12
|
+
* There is no reset here and no rule that selects an element. Nothing sets a
|
|
13
|
+
* font-family on anything — a component reads `var(--font-text)` — so a host
|
|
14
|
+
* page's own stack stays where it is until something of ours asks for a token.
|
|
15
|
+
*
|
|
16
|
+
* Three families of surface, and the difference is the point:
|
|
17
|
+
* page / card the ordinary ground, and what lifts off it
|
|
18
|
+
* vault-* the transaction preview, which separates itself by surface
|
|
19
|
+
* and border rather than by being dark
|
|
20
|
+
* dark-* code blocks and the Open Graph card, dark in both themes
|
|
21
|
+
*/
|
|
22
|
+
|
|
23
|
+
.slip-root {
|
|
24
|
+
color-scheme: light;
|
|
25
|
+
|
|
26
|
+
/* Surfaces. Depth is a border, never a shadow: there is no shadow token. */
|
|
27
|
+
--page: #f6f8fe; /* never pure white */
|
|
28
|
+
--card: #ffffff;
|
|
29
|
+
--subtle: rgba(114, 142, 243, 0.08); /* hover, chips */
|
|
30
|
+
--skeleton: rgba(114, 142, 243, 0.14);
|
|
31
|
+
--border: rgba(18, 23, 33, 0.12);
|
|
32
|
+
--divider: rgba(18, 23, 33, 0.1);
|
|
33
|
+
|
|
34
|
+
/* Ink */
|
|
35
|
+
--ink: #121721;
|
|
36
|
+
--muted: #5c6a9c;
|
|
37
|
+
|
|
38
|
+
/*
|
|
39
|
+
* The split rule: --accent-fill reaches 3.05:1 on the card and is a fill —
|
|
40
|
+
* it never carries text below 24px. An accent-coloured word uses
|
|
41
|
+
* --accent-text. It stays a fill in the dark theme too, where it would
|
|
42
|
+
* otherwise clear AA, because the rule is about what the colour is for.
|
|
43
|
+
*/
|
|
44
|
+
--accent-fill: #728ef3;
|
|
45
|
+
--accent-text: #2f4bc4; /* links */
|
|
46
|
+
--accent-action: #4361e8; /* button background */
|
|
47
|
+
--accent-hover: #2f4bc4;
|
|
48
|
+
--accent-deep: #0c288d; /* headings */
|
|
49
|
+
--on-accent: #ffffff; /* anything drawn on --accent-action */
|
|
50
|
+
--focus: #4361e8;
|
|
51
|
+
|
|
52
|
+
/* Semantic, and rare. A page that colours everything says nothing. */
|
|
53
|
+
--pos: #1f7a5c; /* positive, refundable */
|
|
54
|
+
--warn: #8c5a12;
|
|
55
|
+
--bad: #b3261e;
|
|
56
|
+
--bad-tint: rgba(179, 38, 30, 0.06);
|
|
57
|
+
--bad-line: rgba(179, 38, 30, 0.24);
|
|
58
|
+
--warn-tint: rgba(233, 180, 76, 0.16);
|
|
59
|
+
--warn-line: rgba(233, 180, 76, 0.5);
|
|
60
|
+
|
|
61
|
+
/* The alternating rows of the effects list, on and off. */
|
|
62
|
+
--stripe-a: rgba(114, 142, 243, 0.16);
|
|
63
|
+
--stripe-b: rgba(114, 142, 243, 0.05);
|
|
64
|
+
--stripe-off-a: rgba(92, 106, 156, 0.12);
|
|
65
|
+
--stripe-off-b: rgba(92, 106, 156, 0.04);
|
|
66
|
+
|
|
67
|
+
/*
|
|
68
|
+
* The transaction preview. Its own family because it is the surface that
|
|
69
|
+
* judges the publisher, and `10 · Chrome rules` fixes it whoever hosts the
|
|
70
|
+
* page. It follows the theme like everything else.
|
|
71
|
+
*/
|
|
72
|
+
--vault: #ffffff;
|
|
73
|
+
--vault-ink: #121721;
|
|
74
|
+
--vault-muted: #5c6a9c;
|
|
75
|
+
--vault-surface: rgba(114, 142, 243, 0.08);
|
|
76
|
+
--vault-border: rgba(18, 23, 33, 0.12);
|
|
77
|
+
--vault-chip: rgba(114, 142, 243, 0.16);
|
|
78
|
+
--vault-rule: rgba(114, 142, 243, 0.8);
|
|
79
|
+
--vault-pos: #1f7a5c;
|
|
80
|
+
--vault-warn: #8c5a12;
|
|
81
|
+
--vault-bad: #b3261e;
|
|
82
|
+
--vault-bad-tint: rgba(179, 38, 30, 0.06);
|
|
83
|
+
--vault-bad-line: rgba(179, 38, 30, 0.32);
|
|
84
|
+
--vault-bad-chip: rgba(179, 38, 30, 0.1);
|
|
85
|
+
--vault-warn-tint: rgba(233, 180, 76, 0.14);
|
|
86
|
+
--vault-warn-line: rgba(233, 180, 76, 0.55);
|
|
87
|
+
|
|
88
|
+
/*
|
|
89
|
+
* Dark in both themes, and nowhere else: code blocks in the docs and the
|
|
90
|
+
* Open Graph card. Deliberately absent from the dark theme below — a value
|
|
91
|
+
* that is fixed cannot also follow.
|
|
92
|
+
*/
|
|
93
|
+
--dark-bg: #121721;
|
|
94
|
+
--dark-surface: rgba(255, 255, 255, 0.08);
|
|
95
|
+
--dark-text: #ffffff;
|
|
96
|
+
--dark-muted: #ccd6ff;
|
|
97
|
+
|
|
98
|
+
/* Type. Each stack ends in a generic family, so a missing webfont degrades. */
|
|
99
|
+
--font-display: Poppins, system-ui, sans-serif;
|
|
100
|
+
--font-text: Figtree, system-ui, sans-serif;
|
|
101
|
+
--font-mono: "IBM Plex Mono", ui-monospace, monospace;
|
|
102
|
+
|
|
103
|
+
/* Whole roles, so half a role cannot be applied without the other half. */
|
|
104
|
+
--type-h1: 700 44px/48px var(--font-display);
|
|
105
|
+
--type-h1-tracking: -0.01em;
|
|
106
|
+
--type-card-title: 700 18px/24px var(--font-display);
|
|
107
|
+
--type-numeral: 700 26px/32px var(--font-display); /* a figure is always Poppins 700 */
|
|
108
|
+
--type-body: 500 16px/24px var(--font-text);
|
|
109
|
+
--type-button: 500 14px/20px var(--font-text);
|
|
110
|
+
--type-button-tracking: 0.1px;
|
|
111
|
+
--type-label: 500 13px var(--font-text);
|
|
112
|
+
--type-mono: 400 12px/20px var(--font-mono); /* addresses, hashes, the tx block */
|
|
113
|
+
|
|
114
|
+
/* Spacing, on a 4px base. */
|
|
115
|
+
--space-4: 4px;
|
|
116
|
+
--space-8: 8px;
|
|
117
|
+
--space-12: 12px;
|
|
118
|
+
--space-16: 16px;
|
|
119
|
+
--space-20: 20px;
|
|
120
|
+
--space-24: 24px;
|
|
121
|
+
--space-32: 32px;
|
|
122
|
+
--space-48: 48px;
|
|
123
|
+
|
|
124
|
+
--radius-chip: 6px;
|
|
125
|
+
--radius-control: 8px;
|
|
126
|
+
--radius-card: 12px;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
/*
|
|
130
|
+
* The dark theme rebinds the roles rather than adding a second vocabulary. A
|
|
131
|
+
* component writes var(--ink) once and is correct on both grounds; a component
|
|
132
|
+
* choosing between a light token and a dark one is a component deciding the
|
|
133
|
+
* rule, and two of them will decide it differently.
|
|
134
|
+
*
|
|
135
|
+
* Whether dark follows the operating system is not settled by the sheet, so it
|
|
136
|
+
* is not decided here: the attribute is the only way in.
|
|
137
|
+
*/
|
|
138
|
+
.slip-root[data-theme="dark"] {
|
|
139
|
+
color-scheme: dark;
|
|
140
|
+
|
|
141
|
+
--page: #121721; /* the brand dark */
|
|
142
|
+
--card: #1b2231; /* lifted off the page */
|
|
143
|
+
--subtle: rgba(114, 142, 243, 0.16);
|
|
144
|
+
--skeleton: rgba(114, 142, 243, 0.18);
|
|
145
|
+
--border: rgba(255, 255, 255, 0.14);
|
|
146
|
+
--divider: rgba(255, 255, 255, 0.1);
|
|
147
|
+
|
|
148
|
+
--ink: #ffffff;
|
|
149
|
+
--muted: #ccd6ff;
|
|
150
|
+
|
|
151
|
+
--accent-text: #a8bcff;
|
|
152
|
+
--accent-hover: #5c79ee;
|
|
153
|
+
--accent-deep: #a8bcff;
|
|
154
|
+
--focus: #a8bcff;
|
|
155
|
+
|
|
156
|
+
--pos: #5fd1a6;
|
|
157
|
+
--warn: #e9b44c;
|
|
158
|
+
--bad: #ffa39b;
|
|
159
|
+
--bad-tint: rgba(255, 163, 155, 0.1);
|
|
160
|
+
--bad-line: rgba(255, 163, 155, 0.4);
|
|
161
|
+
--warn-tint: rgba(233, 180, 76, 0.14);
|
|
162
|
+
--warn-line: rgba(233, 180, 76, 0.45);
|
|
163
|
+
|
|
164
|
+
--stripe-a: rgba(114, 142, 243, 0.26);
|
|
165
|
+
--stripe-b: rgba(114, 142, 243, 0.1);
|
|
166
|
+
--stripe-off-a: rgba(204, 214, 255, 0.16);
|
|
167
|
+
--stripe-off-b: rgba(204, 214, 255, 0.05);
|
|
168
|
+
|
|
169
|
+
--vault: #080b12; /* still the gravest surface */
|
|
170
|
+
--vault-ink: #ffffff;
|
|
171
|
+
--vault-muted: #ccd6ff;
|
|
172
|
+
--vault-surface: rgba(255, 255, 255, 0.08);
|
|
173
|
+
--vault-border: rgba(255, 255, 255, 0.14);
|
|
174
|
+
--vault-chip: rgba(114, 142, 243, 0.24);
|
|
175
|
+
--vault-rule: rgba(114, 142, 243, 0.5);
|
|
176
|
+
--vault-pos: #5fd1a6;
|
|
177
|
+
--vault-warn: #e9b44c;
|
|
178
|
+
--vault-bad: #ffa39b;
|
|
179
|
+
--vault-bad-tint: rgba(255, 163, 155, 0.08);
|
|
180
|
+
--vault-bad-line: rgba(255, 163, 155, 0.45);
|
|
181
|
+
--vault-bad-chip: rgba(255, 163, 155, 0.2);
|
|
182
|
+
--vault-warn-tint: rgba(233, 180, 76, 0.08);
|
|
183
|
+
--vault-warn-line: rgba(233, 180, 76, 0.45);
|
|
184
|
+
}
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Why a wallet did not reach a usable connection. One error, a closed set of
|
|
3
|
+
* refusals, because the client renders a state per refusal and a state nothing
|
|
4
|
+
* can produce is a screen nobody sees.
|
|
5
|
+
*/
|
|
6
|
+
import { type ClientErrorCode } from "@cardano-slips/core"
|
|
7
|
+
import { Data } from "effect"
|
|
8
|
+
|
|
9
|
+
import { type Cip30ApiError } from "./cip30.js"
|
|
10
|
+
|
|
11
|
+
export type ConnectRefusal =
|
|
12
|
+
/** Nothing is injected under this key. The wallet was uninstalled, or never there. */
|
|
13
|
+
| "NotInjected"
|
|
14
|
+
/** Something is under the key that does not implement the connector. */
|
|
15
|
+
| "NotCip30"
|
|
16
|
+
/** The person declined the connection — CIP-30 `APIError` `-3`. Not a fault. */
|
|
17
|
+
| "Refused"
|
|
18
|
+
/** `enable()` rejected for another reason, or returned something that is not the API. */
|
|
19
|
+
| "EnableFailed"
|
|
20
|
+
/** A question the wallet did not answer, or answered with something that is not what it claims. */
|
|
21
|
+
| "Unreadable"
|
|
22
|
+
/** The wallet is on a different network from the one the Slip declares. */
|
|
23
|
+
| "WrongNetwork"
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Exhaustive by type: adding a refusal above without listing it here fails to
|
|
27
|
+
* compile, and `test/connect.test.ts` fails if any of them stops being reachable.
|
|
28
|
+
*/
|
|
29
|
+
export const connectRefusals: Readonly<Record<ConnectRefusal, true>> = {
|
|
30
|
+
NotInjected: true,
|
|
31
|
+
NotCip30: true,
|
|
32
|
+
Refused: true,
|
|
33
|
+
EnableFailed: true,
|
|
34
|
+
Unreadable: true,
|
|
35
|
+
WrongNetwork: true
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Only the network disagreement has a spec code, because only it is a failure
|
|
40
|
+
* of the exchange the spec describes. The rest are states of a browser
|
|
41
|
+
* extension, and dressing them in a protocol code would say the endpoint
|
|
42
|
+
* answered when nothing was ever sent.
|
|
43
|
+
*/
|
|
44
|
+
export const slipErrorCodeFor = (refusal: ConnectRefusal): ClientErrorCode | undefined =>
|
|
45
|
+
refusal === "WrongNetwork" ? "WRONG_NETWORK" : undefined
|
|
46
|
+
|
|
47
|
+
/** `cip30` is the wallet's own `{ code, info }`, kept so a `-3` stays distinguishable from a crash. */
|
|
48
|
+
export class WalletConnectError extends Data.TaggedError("WalletConnectError")<{
|
|
49
|
+
readonly refusal: ConnectRefusal
|
|
50
|
+
readonly key: string
|
|
51
|
+
readonly detail: string
|
|
52
|
+
readonly cip30?: Cip30ApiError
|
|
53
|
+
}> {
|
|
54
|
+
override get message(): string {
|
|
55
|
+
return `${this.key}: ${this.detail}`
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
get code(): ClientErrorCode | undefined {
|
|
59
|
+
return slipErrorCodeFor(this.refusal)
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
export const refuse = (
|
|
64
|
+
refusal: ConnectRefusal,
|
|
65
|
+
key: string,
|
|
66
|
+
detail: string,
|
|
67
|
+
cip30?: Cip30ApiError
|
|
68
|
+
): WalletConnectError => new WalletConnectError({ refusal, key, detail, ...(cip30 === undefined ? {} : { cip30 }) })
|