@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.
- package/README.md +271 -220
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -1,154 +1,54 @@
|
|
|
1
1
|
# @reclack/walletkit
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
|
|
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
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
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
|
-
##
|
|
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
|
-
```
|
|
53
|
-
|
|
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
|
-
|
|
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
|
-
|
|
92
|
-
|
|
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="
|
|
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
|
|
37
|
+
console.log(event.detail.address)
|
|
128
38
|
})
|
|
129
39
|
```
|
|
130
40
|
|
|
131
|
-
|
|
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
|
-
|
|
142
|
-
|
|
143
|
-
where a host can still override any single one of them.
|
|
43
|
+
```ts
|
|
44
|
+
import { connectWallet } from '@reclack/walletkit/element'
|
|
144
45
|
|
|
145
|
-
|
|
46
|
+
const { address } = await connectWallet({ intent: ['address'], family: 'evm' })
|
|
47
|
+
```
|
|
146
48
|
|
|
147
|
-
|
|
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
|
-
|
|
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
|
|
171
|
-
|
|
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
|
-
|
|
176
|
-
components work unchanged, which is how this checkout uses it.
|
|
73
|
+
### Preact
|
|
177
74
|
|
|
178
|
-
|
|
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
|
-
<
|
|
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
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
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
|
|
195
|
-
<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
|
|
105
|
+
<reclack-connect family="evm" on:connect={(e) => take(e.detail)} />
|
|
201
106
|
```
|
|
202
107
|
|
|
203
108
|
```jsx
|
|
204
109
|
/* SolidJS */
|
|
205
|
-
<reclack-connect
|
|
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
|
|
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
|
-
|
|
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
|
|
215
|
-
|
|
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 {
|
|
174
|
+
import { createWalletKit } from '@reclack/walletkit'
|
|
219
175
|
|
|
220
|
-
const
|
|
176
|
+
const kit = createWalletKit()
|
|
177
|
+
const stop = kit.subscribe(() => render(kit.getState()))
|
|
221
178
|
```
|
|
222
179
|
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
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
|
-
|
|
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
|
-
|
|
230
|
-
|
|
231
|
-
|
|
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.
|
|
226
|
+
element.kit = myKit
|
|
235
227
|
```
|
|
236
228
|
|
|
237
|
-
Themes stay per element
|
|
238
|
-
|
|
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
|
-
|
|
232
|
+
### Options
|
|
241
233
|
|
|
242
|
-
|
|
234
|
+
Every one of these is an override. The defaults are what most integrations
|
|
235
|
+
want.
|
|
243
236
|
|
|
244
237
|
```ts
|
|
245
|
-
|
|
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',
|
|
248
|
-
brand: '#ff6600',
|
|
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' },
|
|
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
|
|
257
|
-
subscribed to, so
|
|
258
|
-
|
|
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
|
-
|
|
267
|
-
when a merchant's branding arrives.
|
|
301
|
+
Three things worth knowing:
|
|
268
302
|
|
|
269
|
-
|
|
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
|
-
|
|
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
|
|
293
|
-
|
|
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
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
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
|
-
|
|
303
|
-
|
|
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
|
-
|
|
306
|
-
|
|
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
|
-
##
|
|
334
|
+
## Wallet artwork
|
|
309
335
|
|
|
310
|
-
|
|
311
|
-
|
|
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/' })
|
|
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/`.
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
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
|
-
##
|
|
352
|
+
## Pairing by QR code
|
|
328
353
|
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
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
|
-
|
|
358
|
+
To pair under your own WalletConnect project instead:
|
|
335
359
|
|
|
336
|
-
|
|
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
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
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