@reclack/walletkit 0.1.0 → 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 +297 -220
  2. package/package.json +1 -1
package/README.md CHANGED
@@ -1,154 +1,54 @@
1
1
  # @reclack/walletkit
2
2
 
3
- Connect a wallet, read an address, read a balance. No renderer, no framework,
4
- no wallet SDK.
3
+ A wallet picker and connection layer for the web. Connect a wallet, read an
4
+ address, read a balance, on EVM chains and on Solana.
5
5
 
6
- Wallets speak EIP-1193, EIP-6963 and the Wallet Standard, all of which are
7
- small enough to talk to by hand. A page that handles other people's money
8
- should ship as little code it did not write as it can, so this talks to them
9
- directly.
10
-
11
- One dependency, and only five wallets need it: Safe, Kraken, BitPay,
12
- Bitcoin.com and Blockchain.com inject nothing into a page, so they are reached
13
- over a relay. Its provider sits behind a dynamic import and is fetched only
14
- when one of those is chosen. Everyone else loads none of it.
6
+ - **Works with any framework.** The picker is a custom element, and the
7
+ connection layer is a plain object with `getState` and `subscribe`.
8
+ - **One dependency**, loaded only when someone picks a wallet that needs it.
9
+ - **Nothing to configure.** Endpoints, the wallet catalogue and relay pairing
10
+ all ship with sensible defaults.
11
+ - **Typed**, and safe to import in a server render.
15
12
 
16
13
  ```sh
17
14
  npm install @reclack/walletkit
18
15
  ```
19
16
 
20
- ## The kit
21
-
22
- Everything hangs off one object with `getState` and `subscribe`. That pair is
23
- the only shape every framework agrees on, so this package picks none of them.
24
-
25
- ```ts
26
- import { createWalletKit } from '@reclack/walletkit'
27
-
28
- const kit = createWalletKit()
29
- const stop = kit.subscribe(() => render(kit.getState()))
30
- ```
31
-
32
- No configuration. Which API to read the wallet catalogue from, which relay to
33
- pair over, which endpoints to read balances from: none of that is a decision a
34
- page embedding a wallet picker should have to make, so the kit ships with it.
35
- Options exist for a sandbox, a private endpoint or a partner running the kit
36
- under its own name, and all of them are overrides rather than requirements.
37
-
38
- Nothing touches `window` until the first subscriber arrives, so importing this
39
- on a server is safe and a host that renders on one needs no guards.
40
-
41
- ### Vanilla
42
-
43
- ```js
44
- kit.subscribe(() => {
45
- const { evm } = kit.getState()
46
- list.replaceChildren(...evm.available.map(toRow))
47
- })
48
- ```
49
-
50
- ### React
51
-
52
- ```tsx
53
- const state = useSyncExternalStore(kit.subscribe, kit.getState, kit.getState)
54
- ```
55
-
56
- ### Vue
57
-
58
- ```ts
59
- const state = shallowRef(kit.getState())
60
- onMounted(() => kit.subscribe(() => { state.value = kit.getState() }))
61
- ```
17
+ ## Quick start
62
18
 
63
- ### Svelte
64
-
65
- ```ts
66
- const state = { subscribe: (run) => (run(kit.getState()), kit.subscribe(() => run(kit.getState()))) }
67
- ```
68
-
69
- ### Angular
70
-
71
- ```ts
72
- readonly state = toSignal(new Observable((s) => kit.subscribe(() => s.next(kit.getState()))))
73
- ```
74
-
75
- ## Intent
76
-
77
- What a caller wants back from connecting, as a list rather than a mode,
78
- because these are not alternatives.
79
-
80
- ```ts
81
- await kit.connectEvm(wallet)
19
+ ```html
20
+ <script type="module">
21
+ import '@reclack/walletkit/element'
22
+ </script>
82
23
 
83
- const result = await kit.resolveIntent('evm', {
84
- intent: ['address', 'balance'],
85
- assets: [{ family: 'evm', chainId: 8453, token: USDC, decimals: 6 }],
86
- keepAlive: false,
87
- })
88
- // { address, chainId, walletId, name, family, balances }
24
+ <reclack-connect variant="modal" family="evm"></reclack-connect>
89
25
  ```
90
26
 
91
- - `address` is the by-product of connecting, so it is always obtained. Asking
92
- for it says only that the caller wants it handed back.
93
- - `balance` needs an asset to be a balance of. Asking without one throws: it
94
- is a mistake in the caller, not a state anybody can connect their way out of.
95
- - `keepAlive` holds the provider open and keeps following account and chain
96
- changes. True for a checkout, which signs later. False for a form, which
97
- wanted a value and is done.
98
-
99
- A chain is enforced only when `chainId` is given. A page collecting a
100
- settlement address should leave it out: the same address receives on every EVM
101
- chain, and asking somebody to switch networks in their wallet to read an
102
- address back is theatre.
103
-
104
- ## The element
105
-
106
- The picker itself, as a custom element. An element is the only component model
107
- React, Vue, Angular, Svelte and a page with no framework already share, so
108
- that is what it is.
27
+ Importing `@reclack/walletkit/element` defines the tag. With no bundler, point
28
+ a module script at a CDN instead:
109
29
 
110
30
  ```html
111
- <script type="module" src="/path/to/@reclack/walletkit/element"></script>
112
-
113
- <reclack-connect
114
- variant="modal"
115
- family="evm"
116
- chain-id="8453"
117
- intent="address,balance"
118
- theme-mode="system"
119
- brand="#ff6600"
120
- asset-base="/wallets/"
121
- ></reclack-connect>
31
+ <script type="module" src="https://esm.sh/@reclack/walletkit/element"></script>
122
32
  ```
123
33
 
124
34
  ```js
125
35
  document.querySelector('reclack-connect')
126
36
  .addEventListener('connect', (event) => {
127
- event.detail // { address, chainId, walletId, name, family, balances }
37
+ console.log(event.detail.address)
128
38
  })
129
39
  ```
130
40
 
131
- Events are `connect`, `cancel` and `connect-error`, all composed, so a
132
- listener on an ancestor hears them through the shadow boundary. `connect-error`
133
- rather than `error`, which already means a resource failed to load.
134
-
135
- `variant` is `inline`, the short list a checkout puts on the page with the
136
- full catalogue folded behind one row, or `modal`, the whole picker over the
137
- page. The modal is a native `<dialog>`: focus trapping, Esc, an inert
138
- background and a backdrop come from the platform rather than from a focus-trap
139
- library shipped on a page that takes payments.
41
+ Or, if you only need a value and not a component:
140
42
 
141
- Markup is in a shadow root, so nothing here can reach the host's page and
142
- nothing in the page can reach in. The tokens are set on the element itself,
143
- where a host can still override any single one of them.
43
+ ```ts
44
+ import { connectWallet } from '@reclack/walletkit/element'
144
45
 
145
- ### Your framework
46
+ const { address } = await connectWallet({ intent: ['address'], family: 'evm' })
47
+ ```
146
48
 
147
- The tag is `reclack-connect` because a custom element's name must be
148
- lowercase and contain a hyphen. That is the HTML spec, not a style choice, and
149
- nobody should have to write it.
49
+ ## Your framework
150
50
 
151
- **React, and anything built on it (Next.js, Remix, React Router):**
51
+ ### React, Next.js, Remix, React Router
152
52
 
153
53
  ```sh
154
54
  npm install @reclack/walletkit @reclack/walletkit-react
@@ -167,95 +67,256 @@ import { ReclackConnect } from '@reclack/walletkit-react'
167
67
  />
168
68
  ```
169
69
 
170
- In Next.js the file needs `'use client'`, because the component uses hooks.
171
- The kit itself is safe to import on a server: it touches no browser API until
172
- something subscribes, and registering the element is skipped where there is no
173
- `customElements`.
70
+ In Next.js, add `'use client'` to the file that renders it. Importing
71
+ `@reclack/walletkit` itself on the server is fine.
174
72
 
175
- **Preact:** the same package. Preact's `preact/compat` alias makes React
176
- components work unchanged, which is how this checkout uses it.
73
+ ### Preact
177
74
 
178
- **Vue:**
75
+ The React package, unchanged. `preact/compat` handles the rest.
76
+
77
+ ### Vue
179
78
 
180
79
  ```sh
181
80
  npm install @reclack/walletkit @reclack/walletkit-vue
182
81
  ```
183
82
 
184
83
  ```vue
185
- <ReclackConnect variant="modal" family="evm" :chain-id="8453" @connect="onConnect" />
84
+ <script setup>
85
+ import { ReclackConnect } from '@reclack/walletkit-vue'
86
+ </script>
87
+
88
+ <template>
89
+ <ReclackConnect variant="modal" family="evm" :chain-id="8453" @connect="onConnect" />
90
+ </template>
186
91
  ```
187
92
 
188
- **Angular, Svelte, SolidJS, and plain HTML need no wrapper**, and adding one
189
- would be ceremony rather than help: all three bind properties and listen to
190
- custom events on an element natively, which is the whole thing a wrapper does
191
- for React.
93
+ ### Angular, Svelte, SolidJS, plain HTML
94
+
95
+ No wrapper needed. Each binds properties and listens for events on a custom
96
+ element natively.
192
97
 
193
98
  ```html
194
- <!-- Angular, with CUSTOM_ELEMENTS_SCHEMA in the component or module -->
195
- <reclack-connect [kit]="kit" family="evm" (connect)="onConnect($event)"></reclack-connect>
99
+ <!-- Angular, with CUSTOM_ELEMENTS_SCHEMA on the component or module -->
100
+ <reclack-connect family="evm" (connect)="onConnect($event)"></reclack-connect>
196
101
  ```
197
102
 
198
103
  ```svelte
199
104
  <!-- Svelte -->
200
- <reclack-connect {kit} family="evm" on:connect={(e) => take(e.detail)} />
105
+ <reclack-connect family="evm" on:connect={(e) => take(e.detail)} />
201
106
  ```
202
107
 
203
108
  ```jsx
204
109
  /* SolidJS */
205
- <reclack-connect prop:kit={kit} family="evm" on:connect={(e) => take(e.detail)} />
110
+ <reclack-connect family="evm" on:connect={(e) => take(e.detail)} />
206
111
  ```
207
112
 
208
- Editors and Vue's language server read `custom-elements.json`, shipped in this
209
- package, so the tag and its attributes autocomplete without a wrapper.
113
+ Editors and Vue's language server read the `custom-elements.json` shipped in
114
+ this package, so the tag and its attributes autocomplete without a wrapper.
210
115
  `vscode.html-custom-data.json` does the same for plain HTML.
211
116
 
212
- ### One call
117
+ ## The element
118
+
119
+ ### Attributes
120
+
121
+ | Attribute | Values | Default | What it does |
122
+ | --- | --- | --- | --- |
123
+ | `variant` | `inline`, `modal` | `inline` | A list on the page, or the picker over it |
124
+ | `family` | `evm`, `svm` | `evm` | Which wallets to offer |
125
+ | `chain-id` | number | none | Require this chain, switching the wallet if needed |
126
+ | `intent` | `address`, `balance`, comma separated | `address` | What to return |
127
+ | `keep-alive` | present or absent | absent | Stay connected after resolving |
128
+ | `asset-base` | URL path | `/images/` | Where the wallet logos are served from |
129
+ | `api-url` | URL | Reclack's | A different deployment, such as a sandbox |
130
+ | `theme` | `WalletKitTheme`, as a property | none | Colours, radius and type. See [Theming](#theming) |
131
+
132
+ ### Properties
133
+
134
+ For anything an attribute cannot carry, set a property instead.
135
+
136
+ ```js
137
+ element.kit = sharedKit // share one kit, see below
138
+ element.theme = { mode: 'dark', brand: '#f60' }
139
+ element.request = { // an intent carrying assets
140
+ intent: ['address', 'balance'],
141
+ assets: [{ family: 'evm', chainId: 8453, token: USDC, decimals: 6 }],
142
+ }
143
+ ```
144
+
145
+ ### Events
146
+
147
+ | Event | Detail |
148
+ | --- | --- |
149
+ | `connect` | `{ address, chainId, walletId, name, family, balances }` |
150
+ | `cancel` | none, the person closed the picker |
151
+ | `connect-error` | `{ message }` |
152
+
153
+ All three are composed, so a listener on an ancestor hears them through the
154
+ shadow boundary. It is `connect-error` rather than `error` because `error`
155
+ already means a resource failed to load.
156
+
157
+ The markup lives in a shadow root, so the picker cannot restyle your page and
158
+ your stylesheet cannot reach into it. Theme it with the attributes above, or
159
+ by setting any `--rkw-` token on the element.
160
+
161
+ `variant="modal"` is a native `<dialog>`, so focus trapping, Esc to close, an
162
+ inert background and a backdrop all come from the browser.
213
163
 
214
- For a host that only wants a value, filling a settlement address should not
215
- take an element, two listeners and a piece of state:
164
+ ## The kit
165
+
166
+ The element makes its own kit, and you never have to touch one. Reach for the
167
+ kit directly when you want the wallet state without the picker, or when
168
+ several pickers should share one.
216
169
 
217
170
  ```ts
218
- import { connectWallet } from '@reclack/walletkit/element'
171
+ import { createWalletKit } from '@reclack/walletkit'
219
172
 
220
- const { address } = await connectWallet({ intent: ['address'], family: 'evm' })
173
+ const kit = createWalletKit()
174
+ const stop = kit.subscribe(() => render(kit.getState()))
175
+ ```
176
+
177
+ ```ts
178
+ const { evm, svm, catalog, scheme, variables, handheld } = kit.getState()
179
+ evm.available // the wallets this browser has
180
+ evm.connected // { address, chainId, walletId, name } once one is
221
181
  ```
222
182
 
223
- It opens the modal, resolves with the result and rejects when the person
224
- closes it. That is a refusal, not a failure, and a caller that wants to ignore
225
- it can.
183
+ | Method | What it does |
184
+ | --- | --- |
185
+ | `getState()` | The whole state, as one object |
186
+ | `subscribe(fn)` | Calls `fn` on every change, returns an unsubscribe |
187
+ | `connectEvm(wallet)` / `connectSvm(wallet)` | Connect one from `available` |
188
+ | `connectRemote(wallet, chainId, onUri)` | Pair by QR code |
189
+ | `resolveIntent(family, request)` | Connect and return what you asked for |
190
+ | `readBalances(family, assets)` | Balances for a connected address |
191
+ | `ensureChain(chainId)` | Switch the wallet's chain, true if it is there |
192
+ | `setTheme(theme)` | Replace the theme, such as when branding loads |
193
+ | `disconnect(family?)` | Drop the session |
194
+ | `destroy()` | Release listeners |
195
+
196
+ `getState` and `subscribe` are all any framework needs:
197
+
198
+ ```ts
199
+ // React
200
+ const state = useSyncExternalStore(kit.subscribe, kit.getState, kit.getState)
201
+
202
+ // Vue
203
+ const state = shallowRef(kit.getState())
204
+ onMounted(() => kit.subscribe(() => { state.value = kit.getState() }))
205
+
206
+ // Svelte
207
+ const state = {
208
+ subscribe: (run) => (run(kit.getState()), kit.subscribe(() => run(kit.getState()))),
209
+ }
210
+
211
+ // Angular
212
+ readonly state = toSignal(
213
+ new Observable((s) => kit.subscribe(() => s.next(kit.getState()))),
214
+ )
215
+ ```
226
216
 
227
- ### Sharing a kit
217
+ ### Sharing one kit
228
218
 
229
- Each element makes its own kit unless given one. A page with several pickers
230
- should share, because discovery is a window listener and a catalogue fetch,
231
- and a second kit runs both again to answer the same question.
219
+ Discovering wallets means a window listener and a catalogue fetch. A page with
220
+ several pickers should share a kit rather than run both twice.
232
221
 
233
222
  ```ts
234
- element.useKit(myKit)
223
+ element.kit = myKit
235
224
  ```
236
225
 
237
- Themes stay per element even then, so a dashboard can show a light picker in a
238
- panel and a dark one in a drawer off one kit.
226
+ Themes stay per element, so one kit can serve a light picker in a panel and a
227
+ dark one in a drawer.
239
228
 
240
- ## Theme
229
+ ### Options
241
230
 
242
- Twelve tokens, light or dark, and nothing else to override.
231
+ Every one of these is an override. The defaults are what most integrations
232
+ want.
243
233
 
244
234
  ```ts
245
- const kit = createWalletKit({
235
+ createWalletKit({
236
+ theme: { mode: 'system', brand: '#ff6600' },
237
+ assetBase: '/wallets/',
238
+ rpcUrls: { 8453: 'https://your-node.example' }, // worth setting, see Balances
239
+ svmRpcUrl: 'https://your-solana-node.example',
240
+ walletConnect: { projectId: 'your-project-id' },
241
+ apiUrl: 'https://api.reclack.com',
242
+ catalog: [], // supply the wallet list yourself instead of fetching it
243
+ })
244
+ ```
245
+
246
+ ## Intent
247
+
248
+ What you want back from connecting, as a list rather than a mode, because
249
+ these are not alternatives.
250
+
251
+ ```ts
252
+ await kit.connectEvm(wallet)
253
+
254
+ const result = await kit.resolveIntent('evm', {
255
+ intent: ['address', 'balance'],
256
+ assets: [{ family: 'evm', chainId: 8453, token: USDC, decimals: 6 }],
257
+ keepAlive: false,
258
+ })
259
+ // { address, chainId, walletId, name, family, balances }
260
+ ```
261
+
262
+ - `address` always comes back. Listing it says you want it handed to you.
263
+ - `balance` needs an asset to be a balance of. Asking without one throws.
264
+ - `keepAlive` holds the connection open and keeps following account and chain
265
+ changes. True for a checkout that signs later, false for a form that wanted
266
+ a value and is done.
267
+
268
+ A chain is enforced only when you pass `chainId`. Leave it out when collecting
269
+ a settlement address: the same address receives on every EVM chain, so asking
270
+ someone to switch networks to read it back serves no purpose.
271
+
272
+ ## Theming
273
+
274
+ Twelve tokens, light or dark.
275
+
276
+ ```ts
277
+ createWalletKit({
246
278
  theme: {
247
- mode: 'system', // 'light' | 'dark' | 'system' (the default)
248
- brand: '#ff6600', // your colour, worn by the primary action
279
+ mode: 'system', // 'light' | 'dark' | 'system'
280
+ brand: '#ff6600', // worn by the primary action
249
281
  radius: '14px',
250
282
  fontFamily: 'Inter, sans-serif',
251
- dark: { surface: '#000000' }, // applied on top, in dark only
283
+ dark: { surface: '#000000' }, // applied on top, in dark only
252
284
  },
253
285
  })
254
286
  ```
255
287
 
256
- The resolved mode and the tokens live in the state everything is already
257
- subscribed to, so when the reader's system flips to dark the picker follows
258
- without a second listener:
288
+ On the element, set it as a property:
289
+
290
+ ```ts
291
+ element.theme = { mode: 'dark', brand: '#ff6600', radius: '14px' }
292
+ ```
293
+
294
+ ### Theme attributes
295
+
296
+ A nested object will not fit in an attribute, so the four values a plain HTML
297
+ page is most likely to want have one each. They build the same theme:
298
+
299
+ | Attribute | Theme field | Values | Default |
300
+ | --- | --- | --- | --- |
301
+ | `theme-mode` | `mode` | `light`, `dark`, `system` | `system` |
302
+ | `brand` | `brand` | `#rrggbb` | Reclack's |
303
+ | `radius` | `radius` | CSS length | `10px` |
304
+ | `font` | `fontFamily` | CSS font stack | inherited |
305
+
306
+ ```html
307
+ <reclack-connect theme-mode="dark" brand="#ff6600" radius="14px"></reclack-connect>
308
+ ```
309
+
310
+ **A theme wins over all four.** Setting the `theme` property, or sharing a kit
311
+ that already carries a theme, makes these attributes ignored and uses the
312
+ theme whole. That is deliberate: a checkout that themed its kit should not
313
+ have to theme every picker inside it as well.
314
+
315
+ ### Following the reader's system
316
+
317
+ The resolved mode and the tokens are part of the state you are already
318
+ subscribed to, so the picker follows the reader's system without a second
319
+ listener:
259
320
 
260
321
  ```ts
261
322
  const { scheme, variables } = kit.getState()
@@ -263,80 +324,96 @@ const { scheme, variables } = kit.getState()
263
324
  // variables: { '--rkw-surface': '#16161a', '--rkw-brand': '#ff6600', ... }
264
325
  ```
265
326
 
266
- `kit.setTheme(...)` replaces it later, which is what an embedded checkout does
267
- when a merchant's branding arrives.
268
-
269
- Three things the kit decides rather than asking:
270
-
271
- - **Text on the brand colour is computed**, by relative luminance, so a pale
272
- brand gets dark text instead of the white that would leave the button
273
- unreadable.
274
- - **Dark is a chosen palette, not an inverted one.** Inverting gives muddy
275
- greys and a brand that has stopped contrasting with anything. A brand colour
276
- a host sets is kept in both modes, though: it is their colour.
277
- - **A value it cannot read is dropped** and the default stands. Colours must be
278
- `#rrggbb`. In the embedded checkout the branding comes from an API response
279
- and ends up inside a style, and a page that takes payment details must not
280
- interpolate whatever a response happens to carry.
327
+ Three things worth knowing:
281
328
 
282
- And one it decides from whether you set a brand at all: **a row is washed grey
283
- on hover until you pick a colour**, then in yours. The default brand is
284
- Reclack's own, and a page nobody has branded should not be hovering in it.
329
+ - **Text on your brand colour is computed** from its relative luminance, so a
330
+ pale brand gets dark text rather than unreadable white.
331
+ - **Dark is its own palette, not an inversion.** A brand colour you set is
332
+ kept in both modes.
333
+ - **Colours must be `#rrggbb`.** Anything else is ignored and the default
334
+ stands, so a value from an API response can never end up inside a style.
285
335
 
286
- Tokens are prefixed `--rkw-` because they are set on an element the host also
287
- styles. Without a prefix, `--surface` would silently take over or be taken
288
- over.
336
+ Tokens are prefixed `--rkw-` so they cannot collide with your own.
289
337
 
290
338
  ## Balances
291
339
 
292
- Amounts are `bigint`, in the asset's smallest unit, and `formatUnits` places
293
- the decimal point with string arithmetic. Money does not survive a float.
340
+ Amounts are `bigint`, in the asset's smallest unit. `formatUnits` places the
341
+ decimal point with string arithmetic, because money does not survive a float.
294
342
 
295
- Where a balance is read from changes what it costs. Through an endpoint,
296
- several chains can be read at once and no wallet is dragged onto another
297
- network. Through the connected wallet's own provider, the wallet must already
298
- be on that chain. So an endpoint is the default, and the provider is for the
299
- one case that needs it: the pre-flight immediately before signing, where the
300
- question is what this exact wallet can spend right now.
343
+ ```ts
344
+ import { formatUnits } from '@reclack/walletkit'
301
345
 
302
- The pinned public endpoints are used unless a host passes its own in
303
- `rpcUrls`, which is worth doing for anything heavier than a balance or two.
346
+ const [usdc] = await kit.readBalances('evm', [
347
+ { family: 'evm', chainId: 8453, token: USDC, decimals: 6 },
348
+ ])
349
+ formatUnits(usdc.amount, 6) // "125.40"
350
+ ```
304
351
 
305
- Solana is endpoint-only. Solana wallets sign; they do not answer questions
306
- about the chain.
352
+ Balances are read through an endpoint by default, which can cover several
353
+ chains at once and does not move the wallet onto another network. Reading
354
+ through the connected wallet instead requires it to already be on that chain,
355
+ which suits a check immediately before signing.
307
356
 
308
- ## Artwork
357
+ Public endpoints are used unless you pass `rpcUrls`. Set your own for anything
358
+ heavier than a balance or two. Solana balances are endpoint-only.
309
359
 
310
- Wallet logos ship in `assets/` as `.webp` files. Copy them into whatever your
311
- app serves static files from, then say where that is:
360
+ ## Wallet artwork
361
+
362
+ Logos ship in `assets/` as `.webp`. Copy them into whatever your app serves
363
+ static files from, then say where that is:
312
364
 
313
365
  ```ts
314
- createWalletKit({ assetBase: '/wallets/' }) // the kit
366
+ createWalletKit({ assetBase: '/wallets/' })
315
367
  ```
316
368
 
317
369
  ```html
318
370
  <reclack-connect asset-base="/wallets/"></reclack-connect>
319
371
  ```
320
372
 
321
- The default is `/images/`. Served by you rather than fetched from us, so a
322
- checkout still makes no third-party image request, and a wallet's own EIP-6963
323
- icon wins over these anyway: that is the artwork of the provider actually
324
- present. Getting the path wrong costs the logos of wallets that are not
325
- installed, not the ability to connect.
373
+ The default is `/images/`. Serving them yourself means your checkout makes no
374
+ third-party image request. A wallet that supplies its own icon through
375
+ EIP-6963 uses that instead, so getting the path wrong costs you the logos of
376
+ wallets that are not installed, not the ability to connect.
377
+
378
+ ## Pairing by QR code
379
+
380
+ Some wallets connect over a relay rather than through the browser. That works
381
+ out of the box, with nothing to install or configure, and the relay provider
382
+ is fetched only if someone picks one of those wallets.
383
+
384
+ To pair under your own WalletConnect project instead:
326
385
 
327
- ## WalletConnect
386
+ ```ts
387
+ createWalletKit({
388
+ walletConnect: {
389
+ projectId: 'your-project-id',
390
+ metadata: { name: 'Your app', url: 'https://your-app.example' },
391
+ },
392
+ })
393
+ ```
328
394
 
329
- Safe, Kraken, BitPay, Bitcoin.com and Blockchain.com inject nothing into the
330
- page, so they are reached over a relay. Nothing to install and nothing to
331
- configure: the relay provider ships with the kit, behind a dynamic import, so
332
- only somebody who picks one of those wallets ever downloads it.
395
+ ## Server rendering
333
396
 
334
- Pass `walletConnect: { projectId, metadata }` to pair under your own project.
397
+ Nothing touches `window` until something subscribes, so importing the kit in a
398
+ server render is safe and needs no guards. Registering the element is skipped
399
+ where there is no `customElements`.
335
400
 
336
- ## Build
401
+ ## TypeScript
337
402
 
338
- ```sh
339
- pnpm install
340
- pnpm test
341
- pnpm build
403
+ Types ship with the package. The ones you are most likely to name:
404
+
405
+ ```ts
406
+ import type {
407
+ WalletKit,
408
+ WalletKitState,
409
+ WalletKitTheme,
410
+ ConnectRequest,
411
+ ConnectResult,
412
+ Asset,
413
+ Balance,
414
+ } from '@reclack/walletkit'
342
415
  ```
416
+
417
+ ## Licence
418
+
419
+ MIT.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@reclack/walletkit",
3
- "version": "0.1.0",
3
+ "version": "0.1.2",
4
4
  "description": "Connect a wallet, read an address, read a balance. Framework-free, no wallet SDK, no dependencies.",
5
5
  "license": "MIT",
6
6
  "author": "Reclack",