@reclack/walletkit 0.1.0 → 0.1.1

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 +271 -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
17
+ ## Quick start
51
18
 
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
- ```
62
-
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,230 @@ 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
+ | `theme-mode` | `light`, `dark`, `system` | `system` | Which palette to wear |
129
+ | `brand` | `#rrggbb` | Reclack's | Your colour, worn by the primary action |
130
+ | `radius` | CSS length | `10px` | Corner radius |
131
+ | `font` | CSS font stack | inherited | Type |
132
+ | `asset-base` | URL path | `/images/` | Where the wallet logos are served from |
133
+ | `api-url` | URL | Reclack's | A different deployment, such as a sandbox |
134
+
135
+ ### Properties
213
136
 
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:
137
+ For anything an attribute cannot carry, set a property instead.
138
+
139
+ ```js
140
+ element.kit = sharedKit // share one kit, see below
141
+ element.theme = { mode: 'dark', brand: '#f60' }
142
+ element.request = { // an intent carrying assets
143
+ intent: ['address', 'balance'],
144
+ assets: [{ family: 'evm', chainId: 8453, token: USDC, decimals: 6 }],
145
+ }
146
+ ```
147
+
148
+ ### Events
149
+
150
+ | Event | Detail |
151
+ | --- | --- |
152
+ | `connect` | `{ address, chainId, walletId, name, family, balances }` |
153
+ | `cancel` | none, the person closed the picker |
154
+ | `connect-error` | `{ message }` |
155
+
156
+ All three are composed, so a listener on an ancestor hears them through the
157
+ shadow boundary. It is `connect-error` rather than `error` because `error`
158
+ already means a resource failed to load.
159
+
160
+ The markup lives in a shadow root, so the picker cannot restyle your page and
161
+ your stylesheet cannot reach into it. Theme it with the attributes above, or
162
+ by setting any `--rkw-` token on the element.
163
+
164
+ `variant="modal"` is a native `<dialog>`, so focus trapping, Esc to close, an
165
+ inert background and a backdrop all come from the browser.
166
+
167
+ ## The kit
168
+
169
+ The element makes its own kit, and you never have to touch one. Reach for the
170
+ kit directly when you want the wallet state without the picker, or when
171
+ several pickers should share one.
216
172
 
217
173
  ```ts
218
- import { connectWallet } from '@reclack/walletkit/element'
174
+ import { createWalletKit } from '@reclack/walletkit'
219
175
 
220
- const { address } = await connectWallet({ intent: ['address'], family: 'evm' })
176
+ const kit = createWalletKit()
177
+ const stop = kit.subscribe(() => render(kit.getState()))
221
178
  ```
222
179
 
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.
180
+ ```ts
181
+ const { evm, svm, catalog, scheme, variables, handheld } = kit.getState()
182
+ evm.available // the wallets this browser has
183
+ evm.connected // { address, chainId, walletId, name } once one is
184
+ ```
226
185
 
227
- ### Sharing a kit
186
+ | Method | What it does |
187
+ | --- | --- |
188
+ | `getState()` | The whole state, as one object |
189
+ | `subscribe(fn)` | Calls `fn` on every change, returns an unsubscribe |
190
+ | `connectEvm(wallet)` / `connectSvm(wallet)` | Connect one from `available` |
191
+ | `connectRemote(wallet, chainId, onUri)` | Pair by QR code |
192
+ | `resolveIntent(family, request)` | Connect and return what you asked for |
193
+ | `readBalances(family, assets)` | Balances for a connected address |
194
+ | `ensureChain(chainId)` | Switch the wallet's chain, true if it is there |
195
+ | `setTheme(theme)` | Replace the theme, such as when branding loads |
196
+ | `disconnect(family?)` | Drop the session |
197
+ | `destroy()` | Release listeners |
198
+
199
+ `getState` and `subscribe` are all any framework needs:
228
200
 
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.
201
+ ```ts
202
+ // React
203
+ const state = useSyncExternalStore(kit.subscribe, kit.getState, kit.getState)
204
+
205
+ // Vue
206
+ const state = shallowRef(kit.getState())
207
+ onMounted(() => kit.subscribe(() => { state.value = kit.getState() }))
208
+
209
+ // Svelte
210
+ const state = {
211
+ subscribe: (run) => (run(kit.getState()), kit.subscribe(() => run(kit.getState()))),
212
+ }
213
+
214
+ // Angular
215
+ readonly state = toSignal(
216
+ new Observable((s) => kit.subscribe(() => s.next(kit.getState()))),
217
+ )
218
+ ```
219
+
220
+ ### Sharing one kit
221
+
222
+ Discovering wallets means a window listener and a catalogue fetch. A page with
223
+ several pickers should share a kit rather than run both twice.
232
224
 
233
225
  ```ts
234
- element.useKit(myKit)
226
+ element.kit = myKit
235
227
  ```
236
228
 
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.
229
+ Themes stay per element, so one kit can serve a light picker in a panel and a
230
+ dark one in a drawer.
239
231
 
240
- ## Theme
232
+ ### Options
241
233
 
242
- Twelve tokens, light or dark, and nothing else to override.
234
+ Every one of these is an override. The defaults are what most integrations
235
+ want.
243
236
 
244
237
  ```ts
245
- const kit = createWalletKit({
238
+ createWalletKit({
239
+ theme: { mode: 'system', brand: '#ff6600' },
240
+ assetBase: '/wallets/',
241
+ rpcUrls: { 8453: 'https://your-node.example' }, // worth setting, see Balances
242
+ svmRpcUrl: 'https://your-solana-node.example',
243
+ walletConnect: { projectId: 'your-project-id' },
244
+ apiUrl: 'https://api.reclack.com',
245
+ catalog: [], // supply the wallet list yourself instead of fetching it
246
+ })
247
+ ```
248
+
249
+ ## Intent
250
+
251
+ What you want back from connecting, as a list rather than a mode, because
252
+ these are not alternatives.
253
+
254
+ ```ts
255
+ await kit.connectEvm(wallet)
256
+
257
+ const result = await kit.resolveIntent('evm', {
258
+ intent: ['address', 'balance'],
259
+ assets: [{ family: 'evm', chainId: 8453, token: USDC, decimals: 6 }],
260
+ keepAlive: false,
261
+ })
262
+ // { address, chainId, walletId, name, family, balances }
263
+ ```
264
+
265
+ - `address` always comes back. Listing it says you want it handed to you.
266
+ - `balance` needs an asset to be a balance of. Asking without one throws.
267
+ - `keepAlive` holds the connection open and keeps following account and chain
268
+ changes. True for a checkout that signs later, false for a form that wanted
269
+ a value and is done.
270
+
271
+ A chain is enforced only when you pass `chainId`. Leave it out when collecting
272
+ a settlement address: the same address receives on every EVM chain, so asking
273
+ someone to switch networks to read it back serves no purpose.
274
+
275
+ ## Theming
276
+
277
+ Twelve tokens, light or dark.
278
+
279
+ ```ts
280
+ createWalletKit({
246
281
  theme: {
247
- mode: 'system', // 'light' | 'dark' | 'system' (the default)
248
- brand: '#ff6600', // your colour, worn by the primary action
282
+ mode: 'system', // 'light' | 'dark' | 'system'
283
+ brand: '#ff6600', // worn by the primary action
249
284
  radius: '14px',
250
285
  fontFamily: 'Inter, sans-serif',
251
- dark: { surface: '#000000' }, // applied on top, in dark only
286
+ dark: { surface: '#000000' }, // applied on top, in dark only
252
287
  },
253
288
  })
254
289
  ```
255
290
 
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:
291
+ The resolved mode and the tokens are part of the state you are already
292
+ subscribed to, so the picker follows the reader's system without a second
293
+ listener:
259
294
 
260
295
  ```ts
261
296
  const { scheme, variables } = kit.getState()
@@ -263,80 +298,96 @@ const { scheme, variables } = kit.getState()
263
298
  // variables: { '--rkw-surface': '#16161a', '--rkw-brand': '#ff6600', ... }
264
299
  ```
265
300
 
266
- `kit.setTheme(...)` replaces it later, which is what an embedded checkout does
267
- when a merchant's branding arrives.
301
+ Three things worth knowing:
268
302
 
269
- Three things the kit decides rather than asking:
303
+ - **Text on your brand colour is computed** from its relative luminance, so a
304
+ pale brand gets dark text rather than unreadable white.
305
+ - **Dark is its own palette, not an inversion.** A brand colour you set is
306
+ kept in both modes.
307
+ - **Colours must be `#rrggbb`.** Anything else is ignored and the default
308
+ stands, so a value from an API response can never end up inside a style.
270
309
 
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.
281
-
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.
285
-
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.
310
+ Tokens are prefixed `--rkw-` so they cannot collide with your own.
289
311
 
290
312
  ## Balances
291
313
 
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.
314
+ Amounts are `bigint`, in the asset's smallest unit. `formatUnits` places the
315
+ decimal point with string arithmetic, because money does not survive a float.
294
316
 
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.
317
+ ```ts
318
+ import { formatUnits } from '@reclack/walletkit'
319
+
320
+ const [usdc] = await kit.readBalances('evm', [
321
+ { family: 'evm', chainId: 8453, token: USDC, decimals: 6 },
322
+ ])
323
+ formatUnits(usdc.amount, 6) // "125.40"
324
+ ```
301
325
 
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.
326
+ Balances are read through an endpoint by default, which can cover several
327
+ chains at once and does not move the wallet onto another network. Reading
328
+ through the connected wallet instead requires it to already be on that chain,
329
+ which suits a check immediately before signing.
304
330
 
305
- Solana is endpoint-only. Solana wallets sign; they do not answer questions
306
- about the chain.
331
+ Public endpoints are used unless you pass `rpcUrls`. Set your own for anything
332
+ heavier than a balance or two. Solana balances are endpoint-only.
307
333
 
308
- ## Artwork
334
+ ## Wallet artwork
309
335
 
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:
336
+ Logos ship in `assets/` as `.webp`. Copy them into whatever your app serves
337
+ static files from, then say where that is:
312
338
 
313
339
  ```ts
314
- createWalletKit({ assetBase: '/wallets/' }) // the kit
340
+ createWalletKit({ assetBase: '/wallets/' })
315
341
  ```
316
342
 
317
343
  ```html
318
344
  <reclack-connect asset-base="/wallets/"></reclack-connect>
319
345
  ```
320
346
 
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.
347
+ The default is `/images/`. Serving them yourself means your checkout makes no
348
+ third-party image request. A wallet that supplies its own icon through
349
+ EIP-6963 uses that instead, so getting the path wrong costs you the logos of
350
+ wallets that are not installed, not the ability to connect.
326
351
 
327
- ## WalletConnect
352
+ ## Pairing by QR code
328
353
 
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.
354
+ Some wallets connect over a relay rather than through the browser. That works
355
+ out of the box, with nothing to install or configure, and the relay provider
356
+ is fetched only if someone picks one of those wallets.
333
357
 
334
- Pass `walletConnect: { projectId, metadata }` to pair under your own project.
358
+ To pair under your own WalletConnect project instead:
335
359
 
336
- ## Build
360
+ ```ts
361
+ createWalletKit({
362
+ walletConnect: {
363
+ projectId: 'your-project-id',
364
+ metadata: { name: 'Your app', url: 'https://your-app.example' },
365
+ },
366
+ })
367
+ ```
337
368
 
338
- ```sh
339
- pnpm install
340
- pnpm test
341
- pnpm build
369
+ ## Server rendering
370
+
371
+ Nothing touches `window` until something subscribes, so importing the kit in a
372
+ server render is safe and needs no guards. Registering the element is skipped
373
+ where there is no `customElements`.
374
+
375
+ ## TypeScript
376
+
377
+ Types ship with the package. The ones you are most likely to name:
378
+
379
+ ```ts
380
+ import type {
381
+ WalletKit,
382
+ WalletKitState,
383
+ WalletKitTheme,
384
+ ConnectRequest,
385
+ ConnectResult,
386
+ Asset,
387
+ Balance,
388
+ } from '@reclack/walletkit'
342
389
  ```
390
+
391
+ ## Licence
392
+
393
+ 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.1",
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",