windkit 0.2.2 → 0.3.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/CHANGELOG.md +69 -22
- package/README.md +170 -47
- package/index.js +9 -1
- package/package.json +8 -4
- package/src/InjectedWindProvider.js +518 -0
package/CHANGELOG.md
CHANGED
|
@@ -1,54 +1,101 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
## 0.3.0
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
- Added Wind Wallet in-app browser injected provider support.
|
|
6
|
+
- Added `InjectedWalletSession`.
|
|
7
|
+
- Added `WindClient` hybrid connector.
|
|
8
|
+
- Added `connectWindWallet()` helper.
|
|
9
|
+
- Keeps existing QR/VSR + PeerJS flow as fallback.
|
|
10
|
+
- Exposes provider detection helpers: `getInjectedWindProvider()` and `isWindWalletInjected()`.
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
All notable changes to this project are documented in this file.
|
|
14
|
+
|
|
15
|
+
This project follows [Semantic Versioning](https://semver.org/).
|
|
6
16
|
|
|
7
17
|
---
|
|
8
18
|
|
|
9
|
-
## [0.2.
|
|
19
|
+
## [0.2.3] - 2026-03-02
|
|
20
|
+
|
|
21
|
+
Published: 2026-03-02T14:07:04Z
|
|
10
22
|
|
|
11
23
|
### Changed
|
|
12
24
|
|
|
13
25
|
- Session storage key standardized to `"vex-session"`.
|
|
14
|
-
- Login identity
|
|
15
|
-
- `pi` (peer id)
|
|
16
|
-
-
|
|
17
|
-
- `
|
|
26
|
+
- Login identity payload compacted with standardized keys:
|
|
27
|
+
- `pi` (peer id)
|
|
28
|
+
- `na` (app name)
|
|
29
|
+
- `ic` (icon)
|
|
30
|
+
- `do` (origin)
|
|
31
|
+
- optional `auth` (cached identity proof)
|
|
32
|
+
- RPC method names standardized:
|
|
33
|
+
- `signRequest`
|
|
34
|
+
- `signMessage`
|
|
35
|
+
- `sharedSecret`
|
|
18
36
|
|
|
19
37
|
### Added
|
|
20
38
|
|
|
21
|
-
- `clearSession()` helper.
|
|
22
|
-
-
|
|
23
|
-
- Reuses
|
|
39
|
+
- `clearSession()` helper for manual session reset.
|
|
40
|
+
- Smart session reuse:
|
|
41
|
+
- Reuses stored `peerID`
|
|
42
|
+
- Reuses permission hints (`account@permission`) when available.
|
|
24
43
|
|
|
25
44
|
### Fixed
|
|
26
45
|
|
|
27
|
-
-
|
|
28
|
-
-
|
|
46
|
+
- Hardened session loader:
|
|
47
|
+
- Automatically clears expired or malformed session payloads.
|
|
48
|
+
- Improved disconnect cleanup:
|
|
49
|
+
- Safe destroy/disconnect (best-effort, non-throwing).
|
|
29
50
|
|
|
30
51
|
### Improved
|
|
31
52
|
|
|
32
|
-
-
|
|
33
|
-
- Lightweight heartbeat
|
|
34
|
-
-
|
|
53
|
+
- Optimized for low-memory environments:
|
|
54
|
+
- Lightweight heartbeat with jitter
|
|
55
|
+
- Reduced message routing allocations
|
|
56
|
+
- Lower background CPU usage on mobile devices
|
|
57
|
+
|
|
58
|
+
---
|
|
59
|
+
|
|
60
|
+
## [0.2.1] - 2025-10-13
|
|
61
|
+
|
|
62
|
+
Published: 2025-10-13T16:28:38Z
|
|
63
|
+
|
|
64
|
+
### Added
|
|
65
|
+
|
|
66
|
+
- PeerJS signaling support.
|
|
67
|
+
- Session persistence via `sessionStorage`.
|
|
68
|
+
- Transaction signing via Vexanium Signing Request (VSR).
|
|
69
|
+
- Message signing support.
|
|
70
|
+
- Shared secret derivation (ECDH).
|
|
35
71
|
|
|
36
72
|
---
|
|
37
73
|
|
|
38
|
-
## [0.2.
|
|
74
|
+
## [0.2.0] - 2025-10-06
|
|
75
|
+
|
|
76
|
+
Published: 2025-10-06T14:44:23Z
|
|
39
77
|
|
|
40
78
|
### Added
|
|
41
79
|
|
|
42
|
-
-
|
|
43
|
-
-
|
|
44
|
-
-
|
|
45
|
-
-
|
|
46
|
-
|
|
80
|
+
- Structured WebRTC connection lifecycle.
|
|
81
|
+
- WalletSession abstraction layer.
|
|
82
|
+
- Basic login flow via embedded PeerID inside VSR.
|
|
83
|
+
- Request/response routing via internal request IDs.
|
|
84
|
+
|
|
85
|
+
### Improved
|
|
86
|
+
|
|
87
|
+
- More predictable session initialization flow.
|
|
88
|
+
- Internal protocol normalization groundwork.
|
|
47
89
|
|
|
48
90
|
---
|
|
49
91
|
|
|
50
|
-
## [0.1.1]
|
|
92
|
+
## [0.1.1] - 2025-08-12
|
|
93
|
+
|
|
94
|
+
Published: 2025-08-12T01:33:25Z
|
|
51
95
|
|
|
52
96
|
### Added
|
|
53
97
|
|
|
54
98
|
- Initial public release of WindKit.
|
|
99
|
+
- Basic VSR identity login.
|
|
100
|
+
- WebRTC DataConnection integration.
|
|
101
|
+
- Minimal transaction signing flow.
|
package/README.md
CHANGED
|
@@ -7,39 +7,125 @@ WindKit is a lightweight WebRTC protocol for connecting Vexanium DApps to Wind W
|
|
|
7
7
|
|
|
8
8
|
It enables secure cross-device login and transaction signing without requiring browser extensions.
|
|
9
9
|
|
|
10
|
+
Designed for production environments, including low-RAM mobile devices.
|
|
11
|
+
|
|
10
12
|
---
|
|
11
13
|
|
|
12
|
-
## Features
|
|
14
|
+
## ✨ Features
|
|
13
15
|
|
|
14
16
|
- Cross-device login via VSR (Vexanium Signing Request)
|
|
15
17
|
- Transaction signing (single action, multiple actions, or full transaction)
|
|
16
|
-
- Optional broadcast (sign-only or broadcast)
|
|
18
|
+
- Optional broadcast control (sign-only or broadcast)
|
|
17
19
|
- Message signing
|
|
18
20
|
- Shared secret derivation (ECDH)
|
|
19
|
-
- Session persistence via sessionStorage
|
|
20
|
-
-
|
|
21
|
-
- Pure ESM module
|
|
21
|
+
- Session persistence via `sessionStorage`
|
|
22
|
+
- Low-memory heartbeat strategy
|
|
23
|
+
- Pure ESM module (JavaScript-only)
|
|
24
|
+
|
|
25
|
+
---
|
|
26
|
+
|
|
27
|
+
## 🧩 In-App Browser Injected Provider
|
|
28
|
+
|
|
29
|
+
WindKit now supports Wind Wallet's in-app DApp Browser provider, similar to MetaMask / Phantom / Rabby.
|
|
30
|
+
|
|
31
|
+
When a DApp is opened inside Wind Wallet Browser, Wind Wallet injects:
|
|
32
|
+
|
|
33
|
+
```js
|
|
34
|
+
window.wind
|
|
35
|
+
window.windwallet
|
|
36
|
+
window.windWallet
|
|
37
|
+
window.vexanium
|
|
38
|
+
window.vex
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
Use the hybrid client to support both injected provider and QR/VSR fallback:
|
|
42
|
+
|
|
43
|
+
```js
|
|
44
|
+
import { WindClient } from "windkit";
|
|
45
|
+
|
|
46
|
+
const wind = new WindClient();
|
|
47
|
+
|
|
48
|
+
const session = await wind.connect({
|
|
49
|
+
name: "My Vexanium DApp",
|
|
50
|
+
icon: "https://example.com/icon.png",
|
|
51
|
+
|
|
52
|
+
// Called only when the DApp is opened outside Wind Wallet Browser.
|
|
53
|
+
// Render this VSR as QR, or show your existing login modal.
|
|
54
|
+
onLoginRequest(vsr) {
|
|
55
|
+
console.log("Show QR:", vsr);
|
|
56
|
+
}
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
console.log("Connected:", session.permissionLevel?.toString());
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
### Direct injected request
|
|
63
|
+
|
|
64
|
+
```js
|
|
65
|
+
import { InjectedWalletSession } from "windkit";
|
|
66
|
+
|
|
67
|
+
if (InjectedWalletSession.isAvailable()) {
|
|
68
|
+
const session = await InjectedWalletSession.connect();
|
|
69
|
+
|
|
70
|
+
await session.signMessage("Hello Wind!");
|
|
71
|
+
}
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
### Provider API
|
|
75
|
+
|
|
76
|
+
Inside Wind Wallet Browser, DApps can also call the provider directly:
|
|
77
|
+
|
|
78
|
+
```js
|
|
79
|
+
const accounts = await window.wind.request({
|
|
80
|
+
method: "vex_requestAccounts"
|
|
81
|
+
});
|
|
82
|
+
|
|
83
|
+
const signature = await window.wind.request({
|
|
84
|
+
method: "vex_signMessage",
|
|
85
|
+
params: ["Hello Wind!"]
|
|
86
|
+
});
|
|
87
|
+
|
|
88
|
+
const result = await window.wind.request({
|
|
89
|
+
method: "signRequest",
|
|
90
|
+
params: ["vsr:..."]
|
|
91
|
+
});
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
### Recommended DApp logic
|
|
95
|
+
|
|
96
|
+
```txt
|
|
97
|
+
1. Try injected provider first: window.wind / window.vexanium.
|
|
98
|
+
2. If not available, use QR/VSR + PeerJS fallback.
|
|
99
|
+
3. Keep signing approval inside Wind Wallet.
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
This keeps old QR pairing working while enabling MetaMask-style connect inside the Wind Wallet app.
|
|
22
103
|
|
|
23
104
|
---
|
|
24
105
|
|
|
25
|
-
## Installation
|
|
106
|
+
## 📦 Installation
|
|
26
107
|
|
|
108
|
+
```bash
|
|
27
109
|
npm install windkit
|
|
110
|
+
```
|
|
28
111
|
|
|
29
|
-
WindKit is ESM-only
|
|
112
|
+
WindKit is **ESM-only**.
|
|
30
113
|
|
|
31
|
-
Your project must
|
|
114
|
+
Your project must include:
|
|
32
115
|
|
|
116
|
+
```json
|
|
33
117
|
{
|
|
34
118
|
"type": "module"
|
|
35
119
|
}
|
|
120
|
+
```
|
|
36
121
|
|
|
37
122
|
---
|
|
38
123
|
|
|
39
|
-
## Quick Start
|
|
124
|
+
## 🚀 Quick Start
|
|
40
125
|
|
|
41
126
|
### Create Connector
|
|
42
127
|
|
|
128
|
+
```js
|
|
43
129
|
import { WindConnector } from "windkit";
|
|
44
130
|
|
|
45
131
|
const connector = new WindConnector();
|
|
@@ -49,29 +135,37 @@ connector.on("session", (session, proof) => {
|
|
|
49
135
|
});
|
|
50
136
|
|
|
51
137
|
await connector.connect();
|
|
138
|
+
```
|
|
52
139
|
|
|
53
|
-
By default, WindKit
|
|
140
|
+
By default, WindKit uses PeerJS default signaling.
|
|
54
141
|
|
|
55
142
|
---
|
|
56
143
|
|
|
57
|
-
|
|
144
|
+
## 🌐 Custom PeerJS Server (Optional)
|
|
58
145
|
|
|
146
|
+
```js
|
|
59
147
|
connector.setServer("peer.yourdomain.com", 443, "/", true);
|
|
148
|
+
```
|
|
60
149
|
|
|
61
150
|
Signature:
|
|
62
151
|
|
|
63
|
-
|
|
152
|
+
```js
|
|
153
|
+
setServer(host, port, path, secure);
|
|
154
|
+
```
|
|
64
155
|
|
|
65
|
-
Add custom
|
|
156
|
+
Add custom ICE server:
|
|
66
157
|
|
|
158
|
+
```js
|
|
67
159
|
connector.addIceServer({
|
|
68
160
|
urls: "stun:stun.cloudflare.com:3478"
|
|
69
161
|
});
|
|
162
|
+
```
|
|
70
163
|
|
|
71
164
|
---
|
|
72
165
|
|
|
73
|
-
## Login Flow (VSR)
|
|
166
|
+
## 🔐 Login Flow (VSR)
|
|
74
167
|
|
|
168
|
+
```js
|
|
75
169
|
const vsr = connector.createLoginRequest(
|
|
76
170
|
"My Vexanium DApp",
|
|
77
171
|
"https://example.com/icon.png"
|
|
@@ -83,20 +177,21 @@ window.open(
|
|
|
83
177
|
`https://wallet.windcrypto.com/login?vsr=${encodeURIComponent(payload)}`,
|
|
84
178
|
"Wind Wallet"
|
|
85
179
|
);
|
|
180
|
+
```
|
|
86
181
|
|
|
87
182
|
Wallet flow:
|
|
88
183
|
|
|
89
184
|
1. Decode VSR
|
|
90
185
|
2. Connect to embedded PeerID
|
|
91
|
-
3. Send LOGIN_OK
|
|
186
|
+
3. Send `LOGIN_OK`
|
|
92
187
|
4. Emit session
|
|
93
188
|
|
|
94
189
|
---
|
|
95
190
|
|
|
96
|
-
## Session Handling
|
|
191
|
+
## 🔄 Session Handling
|
|
97
192
|
|
|
193
|
+
```js
|
|
98
194
|
connector.on("session", (session, proof) => {
|
|
99
|
-
|
|
100
195
|
session.onClose(() => {
|
|
101
196
|
console.log("Wallet disconnected");
|
|
102
197
|
});
|
|
@@ -107,13 +202,15 @@ connector.on("session", (session, proof) => {
|
|
|
107
202
|
|
|
108
203
|
window.appSession = session;
|
|
109
204
|
});
|
|
205
|
+
```
|
|
110
206
|
|
|
111
207
|
---
|
|
112
208
|
|
|
113
|
-
## Send Transaction
|
|
209
|
+
## ✍️ Send Transaction
|
|
114
210
|
|
|
115
211
|
### With ABI Cache (Recommended)
|
|
116
212
|
|
|
213
|
+
```js
|
|
117
214
|
import { Action } from "@wharfkit/antelope";
|
|
118
215
|
import { ABICache } from "@wharfkit/abicache";
|
|
119
216
|
|
|
@@ -130,7 +227,7 @@ const action = Action.from(
|
|
|
130
227
|
from: "alice",
|
|
131
228
|
to: "bob",
|
|
132
229
|
quantity: "1.0000 VEX",
|
|
133
|
-
memo: "test"
|
|
230
|
+
memo: "WindKit test"
|
|
134
231
|
},
|
|
135
232
|
authorization: [appSession.permissionLevel]
|
|
136
233
|
},
|
|
@@ -140,119 +237,145 @@ const action = Action.from(
|
|
|
140
237
|
const result = await appSession.transact({ action });
|
|
141
238
|
|
|
142
239
|
console.log(result.transaction_id ?? result.id);
|
|
240
|
+
```
|
|
143
241
|
|
|
144
242
|
---
|
|
145
243
|
|
|
146
244
|
### Sign Only (No Broadcast)
|
|
147
245
|
|
|
246
|
+
```js
|
|
148
247
|
await appSession.transact(
|
|
149
248
|
{ action },
|
|
150
249
|
{ broadcast: false }
|
|
151
250
|
);
|
|
251
|
+
```
|
|
152
252
|
|
|
153
253
|
---
|
|
154
254
|
|
|
155
|
-
## Sign Message
|
|
255
|
+
## 📝 Sign Message
|
|
156
256
|
|
|
257
|
+
```js
|
|
157
258
|
const signature = await appSession.signMessage("Hello Wind!");
|
|
158
259
|
console.log(signature.toString());
|
|
260
|
+
```
|
|
159
261
|
|
|
160
262
|
---
|
|
161
263
|
|
|
162
|
-
## Shared Secret (ECDH)
|
|
264
|
+
## 🔑 Shared Secret (ECDH)
|
|
163
265
|
|
|
266
|
+
```js
|
|
164
267
|
import { PublicKey } from "@wharfkit/antelope";
|
|
165
268
|
|
|
166
269
|
const pub = PublicKey.from("PUB_K1_...");
|
|
167
270
|
const secret = await appSession.sharedSecret(pub);
|
|
168
271
|
|
|
169
272
|
console.log(secret.toString());
|
|
273
|
+
```
|
|
170
274
|
|
|
171
275
|
---
|
|
172
276
|
|
|
173
|
-
## Session Storage
|
|
277
|
+
## 💾 Session Storage
|
|
174
278
|
|
|
175
279
|
WindKit stores session data in:
|
|
176
280
|
|
|
281
|
+
```js
|
|
177
282
|
sessionStorage["vex-session"]
|
|
283
|
+
```
|
|
178
284
|
|
|
179
285
|
Example structure:
|
|
180
286
|
|
|
287
|
+
```json
|
|
181
288
|
{
|
|
182
289
|
"peerID": "VEX-xxxx",
|
|
183
290
|
"permission": "account@active",
|
|
184
291
|
"expiration": "2026-03-01T12:00:00",
|
|
185
292
|
"auth": "base64u_identity_proof"
|
|
186
293
|
}
|
|
294
|
+
```
|
|
187
295
|
|
|
188
|
-
|
|
296
|
+
Clear session manually:
|
|
189
297
|
|
|
298
|
+
```js
|
|
190
299
|
import { clearSession } from "windkit";
|
|
191
300
|
|
|
192
301
|
clearSession();
|
|
302
|
+
```
|
|
193
303
|
|
|
194
304
|
---
|
|
195
305
|
|
|
196
|
-
## Architecture
|
|
306
|
+
## 🏗 Architecture
|
|
307
|
+
|
|
308
|
+
### WindConnector
|
|
197
309
|
|
|
198
|
-
WindConnector
|
|
199
310
|
- Creates VSR identity login
|
|
200
311
|
- Hosts PeerJS PeerID (DApp-side)
|
|
201
312
|
- Waits for wallet connection
|
|
202
313
|
- Emits session
|
|
203
314
|
|
|
204
|
-
WalletSession
|
|
315
|
+
### WalletSession
|
|
316
|
+
|
|
205
317
|
- Sends:
|
|
206
|
-
- signRequest
|
|
207
|
-
- signMessage
|
|
208
|
-
- sharedSecret
|
|
318
|
+
- `signRequest`
|
|
319
|
+
- `signMessage`
|
|
320
|
+
- `sharedSecret`
|
|
209
321
|
- Routes replies via request IDs
|
|
210
|
-
- Lightweight
|
|
322
|
+
- Lightweight heartbeat ping
|
|
211
323
|
- Handles account change events
|
|
212
324
|
|
|
213
325
|
---
|
|
214
326
|
|
|
215
|
-
## Protocol Notes
|
|
327
|
+
## 🔎 Protocol Notes
|
|
328
|
+
|
|
329
|
+
Transaction signing method:
|
|
216
330
|
|
|
217
|
-
|
|
218
|
-
|
|
331
|
+
```
|
|
332
|
+
signRequest
|
|
333
|
+
```
|
|
219
334
|
|
|
220
335
|
Wallet push events handled:
|
|
336
|
+
|
|
337
|
+
```
|
|
221
338
|
LOGIN_OK
|
|
222
339
|
ACTIVE_ACCOUNT_CHANGED
|
|
340
|
+
```
|
|
223
341
|
|
|
224
|
-
All communication occurs
|
|
342
|
+
All communication occurs over PeerJS `DataConnection`.
|
|
225
343
|
|
|
226
344
|
---
|
|
227
345
|
|
|
228
|
-
## Technical Details
|
|
346
|
+
## ⚙ Technical Details
|
|
347
|
+
|
|
348
|
+
Chain ID is internally fixed via:
|
|
229
349
|
|
|
230
|
-
|
|
350
|
+
```js
|
|
231
351
|
WalletSession.ChainID
|
|
352
|
+
```
|
|
353
|
+
|
|
354
|
+
Dependencies:
|
|
355
|
+
|
|
356
|
+
- `@wharfkit/signing-request`
|
|
357
|
+
- `@wharfkit/antelope`
|
|
358
|
+
- `peerjs`
|
|
359
|
+
- `pako`
|
|
232
360
|
|
|
233
|
-
|
|
234
|
-
- @wharfkit/signing-request
|
|
235
|
-
- @wharfkit/antelope
|
|
236
|
-
- peerjs
|
|
237
|
-
- pako (zlib compression)
|
|
361
|
+
Optimized for:
|
|
238
362
|
|
|
239
|
-
Designed for:
|
|
240
363
|
- Low-RAM mobile devices
|
|
241
|
-
- Background tabs
|
|
242
|
-
- Unstable WebRTC
|
|
364
|
+
- Background browser tabs
|
|
365
|
+
- Unstable WebRTC networks
|
|
243
366
|
|
|
244
367
|
---
|
|
245
368
|
|
|
246
|
-
## Security Model
|
|
369
|
+
## 🔐 Security Model
|
|
247
370
|
|
|
248
371
|
- IdentityProof can be verified by the DApp (recommended).
|
|
249
372
|
- Private keys never leave the wallet.
|
|
250
373
|
- VSR ensures transaction integrity.
|
|
251
|
-
- PeerID is embedded inside VSR to prevent misrouting.
|
|
374
|
+
- PeerID is embedded inside the VSR payload to prevent misrouting.
|
|
252
375
|
|
|
253
376
|
---
|
|
254
377
|
|
|
255
|
-
## License
|
|
378
|
+
## 📄 License
|
|
256
379
|
|
|
257
380
|
MIT License
|
|
258
381
|
© Wind Stack
|
package/index.js
CHANGED
|
@@ -1,3 +1,11 @@
|
|
|
1
1
|
export { WindConnector } from "./src/WindConnector.js";
|
|
2
2
|
export { WalletSession } from "./src/WalletSession.js";
|
|
3
|
-
export {
|
|
3
|
+
export {
|
|
4
|
+
InjectedWalletSession,
|
|
5
|
+
WindClient,
|
|
6
|
+
connectWindWallet,
|
|
7
|
+
getInjectedWindProvider,
|
|
8
|
+
isWindWalletInjected,
|
|
9
|
+
requestWithTimeout,
|
|
10
|
+
} from "./src/InjectedWindProvider.js";
|
|
11
|
+
export { saveSession, loadSession, clearSession } from "./src/StoreSession.js";
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "windkit",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"description": "
|
|
3
|
+
"version": "0.3.0",
|
|
4
|
+
"description": "Hybrid protocol to connect Vexanium DApps to Wind Wallet using injected provider, PeerJS, and VSR.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"author": "windstack",
|
|
7
7
|
"type": "module",
|
|
@@ -35,7 +35,11 @@
|
|
|
35
35
|
"wharfkit",
|
|
36
36
|
"signing-request",
|
|
37
37
|
"vsr",
|
|
38
|
-
"blockchain"
|
|
38
|
+
"blockchain",
|
|
39
|
+
"injected-provider",
|
|
40
|
+
"in-app-browser",
|
|
41
|
+
"metamask-compatible",
|
|
42
|
+
"phantom-compatible"
|
|
39
43
|
],
|
|
40
44
|
"sideEffects": false,
|
|
41
45
|
"publishConfig": {
|
|
@@ -56,4 +60,4 @@
|
|
|
56
60
|
"engines": {
|
|
57
61
|
"node": ">=18"
|
|
58
62
|
}
|
|
59
|
-
}
|
|
63
|
+
}
|
|
@@ -0,0 +1,518 @@
|
|
|
1
|
+
// windkit/InjectedWindProvider.js
|
|
2
|
+
// WindKit injected-provider bridge
|
|
3
|
+
// ✅ In-app browser first (window.wind / window.windwallet / window.vexanium)
|
|
4
|
+
// ✅ Keeps QR/VSR + PeerJS fallback untouched
|
|
5
|
+
// ✅ MetaMask-style request({ method, params }) API for Wind Wallet browser
|
|
6
|
+
// ✅ No wallet keys, no signing logic here — DApp-side transport only
|
|
7
|
+
|
|
8
|
+
import { SigningRequest } from "@wharfkit/signing-request";
|
|
9
|
+
import {
|
|
10
|
+
Checksum512,
|
|
11
|
+
Name,
|
|
12
|
+
PermissionLevel,
|
|
13
|
+
PublicKey,
|
|
14
|
+
Signature,
|
|
15
|
+
SignedTransaction,
|
|
16
|
+
} from "@wharfkit/antelope";
|
|
17
|
+
import zlib from "pako";
|
|
18
|
+
|
|
19
|
+
import { WindConnector } from "./WindConnector.js";
|
|
20
|
+
|
|
21
|
+
const DEFAULT_TIMEOUT_MS = 90_000;
|
|
22
|
+
|
|
23
|
+
function normalizeTimeoutMs(v, fallback = DEFAULT_TIMEOUT_MS) {
|
|
24
|
+
const n = Number(v);
|
|
25
|
+
if (!Number.isFinite(n)) return fallback;
|
|
26
|
+
return Math.max(1000, Math.trunc(n));
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
function setTimer(fn, ms) {
|
|
30
|
+
try {
|
|
31
|
+
return globalThis.setTimeout(fn, ms);
|
|
32
|
+
} catch {
|
|
33
|
+
return setTimeout(fn, ms);
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
function clearTimer(id) {
|
|
38
|
+
try {
|
|
39
|
+
globalThis.clearTimeout(id);
|
|
40
|
+
} catch {
|
|
41
|
+
clearTimeout(id);
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
function getWindowLike() {
|
|
46
|
+
try {
|
|
47
|
+
if (typeof globalThis !== "undefined") return globalThis;
|
|
48
|
+
} catch {}
|
|
49
|
+
return undefined;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
function normalizeParams(params) {
|
|
53
|
+
if (params === undefined) return [];
|
|
54
|
+
return Array.isArray(params) ? params : [params];
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
function replyError(reply, fallback) {
|
|
58
|
+
const err = reply?.error;
|
|
59
|
+
if (typeof err === "string") return new Error(err);
|
|
60
|
+
if (err?.message) return new Error(err.message);
|
|
61
|
+
if (reply?.message) return new Error(reply.message);
|
|
62
|
+
return new Error(fallback || "Wind Wallet request failed.");
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
function parseAccountLike(value) {
|
|
66
|
+
if (!value) return "";
|
|
67
|
+
if (typeof value === "string") return value;
|
|
68
|
+
if (Array.isArray(value)) return parseAccountLike(value[0]);
|
|
69
|
+
if (typeof value === "object") {
|
|
70
|
+
return (
|
|
71
|
+
value.permission ||
|
|
72
|
+
value.permissionLevel ||
|
|
73
|
+
value.account ||
|
|
74
|
+
value.address ||
|
|
75
|
+
value.actor ||
|
|
76
|
+
""
|
|
77
|
+
);
|
|
78
|
+
}
|
|
79
|
+
return "";
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
function toPermissionLevel(value) {
|
|
83
|
+
const raw = parseAccountLike(value);
|
|
84
|
+
if (!raw) return undefined;
|
|
85
|
+
|
|
86
|
+
// Vexanium permission is canonical actor@permission.
|
|
87
|
+
if (String(raw).includes("@")) {
|
|
88
|
+
return PermissionLevel.from(String(raw));
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
// Some injected providers may return { actor, permission } separately.
|
|
92
|
+
if (value && typeof value === "object" && value.actor && value.permission) {
|
|
93
|
+
return PermissionLevel.from(`${value.actor}@${value.permission}`);
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
return undefined;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
function isRequestProvider(value) {
|
|
100
|
+
return Boolean(value && typeof value.request === "function");
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
/**
|
|
104
|
+
* Return the first Wind-compatible injected provider available in the current browser.
|
|
105
|
+
*
|
|
106
|
+
* Wind Wallet app injects several aliases for compatibility:
|
|
107
|
+
* - window.wind
|
|
108
|
+
* - window.windwallet
|
|
109
|
+
* - window.windWallet
|
|
110
|
+
* - window.vexanium
|
|
111
|
+
* - window.vex
|
|
112
|
+
*/
|
|
113
|
+
export function getInjectedWindProvider(scope) {
|
|
114
|
+
const w = scope || getWindowLike();
|
|
115
|
+
if (!w) return null;
|
|
116
|
+
|
|
117
|
+
const candidates = [
|
|
118
|
+
w.wind,
|
|
119
|
+
w.windwallet,
|
|
120
|
+
w.windWallet,
|
|
121
|
+
w.vexanium,
|
|
122
|
+
w.vex,
|
|
123
|
+
];
|
|
124
|
+
|
|
125
|
+
for (const provider of candidates) {
|
|
126
|
+
if (isRequestProvider(provider)) return provider;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
return null;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
export function isWindWalletInjected(scope) {
|
|
133
|
+
const provider = getInjectedWindProvider(scope);
|
|
134
|
+
return Boolean(provider?.isWindWallet || provider?.isWind || provider?.request);
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
/**
|
|
138
|
+
* InjectedWalletSession
|
|
139
|
+
* DApp-side session object for Wind Wallet in-app browser.
|
|
140
|
+
*
|
|
141
|
+
* It intentionally mirrors WalletSession's public API:
|
|
142
|
+
* - transact()
|
|
143
|
+
* - signRequest()
|
|
144
|
+
* - signMessage()
|
|
145
|
+
* - sharedSecret()
|
|
146
|
+
* - permissionLevel
|
|
147
|
+
*/
|
|
148
|
+
export class InjectedWalletSession {
|
|
149
|
+
static ChainID = "f9f432b1851b5c179d2091a96f593aaed50ec7466b74f89301f957a83e56ce1f";
|
|
150
|
+
|
|
151
|
+
/** @type {any} */
|
|
152
|
+
#provider;
|
|
153
|
+
|
|
154
|
+
/** @type {{zlib:any, abiProvider?:any} | undefined} */
|
|
155
|
+
#encodingOptions;
|
|
156
|
+
|
|
157
|
+
/** @type {PermissionLevel | undefined} */
|
|
158
|
+
#permissionLevel;
|
|
159
|
+
|
|
160
|
+
/** @type {(permission: PermissionLevel) => void | undefined} */
|
|
161
|
+
#accountChangeListener;
|
|
162
|
+
|
|
163
|
+
/** @type {() => void | undefined} */
|
|
164
|
+
#closeListener;
|
|
165
|
+
|
|
166
|
+
/** @type {(error: Error) => void | undefined} */
|
|
167
|
+
#errorListener;
|
|
168
|
+
|
|
169
|
+
constructor(provider, permissionLevel) {
|
|
170
|
+
if (!isRequestProvider(provider)) {
|
|
171
|
+
throw new Error("Wind Wallet injected provider was not found.");
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
this.#provider = provider;
|
|
175
|
+
|
|
176
|
+
const perm = toPermissionLevel(permissionLevel);
|
|
177
|
+
if (perm) this.#permissionLevel = perm;
|
|
178
|
+
|
|
179
|
+
this.#bindProviderEvents();
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
static isAvailable(scope) {
|
|
183
|
+
return isWindWalletInjected(scope);
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
static getProvider(scope) {
|
|
187
|
+
return getInjectedWindProvider(scope);
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
/**
|
|
191
|
+
* Request accounts from the injected Wind Wallet provider.
|
|
192
|
+
* @param {{provider?: any, timeoutMs?: number}=} options
|
|
193
|
+
*/
|
|
194
|
+
static async connect(options = {}) {
|
|
195
|
+
const provider = options.provider || getInjectedWindProvider();
|
|
196
|
+
if (!provider) throw new Error("Wind Wallet injected provider is not available.");
|
|
197
|
+
|
|
198
|
+
const result = await requestWithTimeout(
|
|
199
|
+
provider,
|
|
200
|
+
"vex_requestAccounts",
|
|
201
|
+
[],
|
|
202
|
+
options.timeoutMs
|
|
203
|
+
);
|
|
204
|
+
|
|
205
|
+
const permission = toPermissionLevel(result) || toPermissionLevel(result?.accounts);
|
|
206
|
+
return new InjectedWalletSession(provider, permission || result);
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
#bindProviderEvents() {
|
|
210
|
+
const provider = this.#provider;
|
|
211
|
+
|
|
212
|
+
// MetaMask-style provider events if Wind Wallet exposes them later.
|
|
213
|
+
if (typeof provider.on === "function") {
|
|
214
|
+
try {
|
|
215
|
+
provider.on("accountsChanged", (accounts) => {
|
|
216
|
+
const next = toPermissionLevel(accounts);
|
|
217
|
+
if (next) {
|
|
218
|
+
this.#permissionLevel = next;
|
|
219
|
+
this.#accountChangeListener?.(next);
|
|
220
|
+
}
|
|
221
|
+
});
|
|
222
|
+
} catch {}
|
|
223
|
+
|
|
224
|
+
try {
|
|
225
|
+
provider.on("disconnect", () => {
|
|
226
|
+
this.#closeListener?.();
|
|
227
|
+
});
|
|
228
|
+
} catch {}
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
setABICache(cache) {
|
|
233
|
+
this.#encodingOptions = { zlib, abiProvider: cache };
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
onAccountChange(listener) {
|
|
237
|
+
this.#accountChangeListener = listener;
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
onClose(listener) {
|
|
241
|
+
this.#closeListener = listener;
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
onError(listener) {
|
|
245
|
+
this.#errorListener = listener;
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
isOpen() {
|
|
249
|
+
return true;
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
close() {
|
|
253
|
+
// Injected browser sessions are page-scoped. No persistent socket to close.
|
|
254
|
+
this.#closeListener?.();
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
metadata() {
|
|
258
|
+
return { transport: "injected", wallet: "Wind Wallet" };
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
get permissionLevel() {
|
|
262
|
+
return this.#permissionLevel;
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
set permissionLevel(value) {
|
|
266
|
+
const perm = toPermissionLevel(value);
|
|
267
|
+
this.#permissionLevel = perm || value;
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
get actor() {
|
|
271
|
+
return this.#permissionLevel?.actor ?? Name.from("");
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
get permission() {
|
|
275
|
+
return this.#permissionLevel?.permission ?? Name.from("");
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
async request(method, params, timeoutMs) {
|
|
279
|
+
return await requestWithTimeout(this.#provider, method, params, timeoutMs);
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
/**
|
|
283
|
+
* Create a VSR signing request for a transaction and send to wallet.
|
|
284
|
+
* Matches WalletSession.transact().
|
|
285
|
+
*/
|
|
286
|
+
async transact(args, options) {
|
|
287
|
+
const willBroadcast = typeof options?.broadcast === "boolean" ? options.broadcast : true;
|
|
288
|
+
const requestArgs = { ...args, chainId: InjectedWalletSession.ChainID };
|
|
289
|
+
|
|
290
|
+
const req = await SigningRequest.create(requestArgs, this.#encodingOptions);
|
|
291
|
+
req.setBroadcast(willBroadcast);
|
|
292
|
+
|
|
293
|
+
const vsr = req.encode(true, false, "vsr:");
|
|
294
|
+
return this.signRequest(vsr, options?.timeoutMs);
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
/**
|
|
298
|
+
* Send a VSR string to Wind Wallet approval UI.
|
|
299
|
+
*/
|
|
300
|
+
async signRequest(vsr, timeoutMs) {
|
|
301
|
+
const payload = typeof vsr === "string" ? vsr : String(vsr || "");
|
|
302
|
+
let reply;
|
|
303
|
+
|
|
304
|
+
if (typeof this.#provider.signRequest === "function") {
|
|
305
|
+
reply = await withTimeout(
|
|
306
|
+
Promise.resolve(this.#provider.signRequest(payload)),
|
|
307
|
+
timeoutMs,
|
|
308
|
+
"signRequest"
|
|
309
|
+
);
|
|
310
|
+
} else {
|
|
311
|
+
reply = await this.request("signRequest", [payload], timeoutMs);
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
if (reply?.code === "SENT") return reply.result;
|
|
315
|
+
if (reply?.code === "SIGNED") return SignedTransaction.from(reply.result);
|
|
316
|
+
if (reply?.signatures || reply?.transaction) return SignedTransaction.from(reply);
|
|
317
|
+
if (reply?.transaction_id || reply?.processed) return reply;
|
|
318
|
+
|
|
319
|
+
throw replyError(reply, "Signing request rejected.");
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
async signMessage(message, timeoutMs) {
|
|
323
|
+
const reply = await this.request("vex_signMessage", [message], timeoutMs);
|
|
324
|
+
|
|
325
|
+
if (typeof reply === "string") return Signature.from(reply);
|
|
326
|
+
if (reply?.signature) return Signature.from(reply.signature);
|
|
327
|
+
if (reply?.code === "SIGNED" && reply?.result?.signature) {
|
|
328
|
+
return Signature.from(reply.result.signature);
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
throw replyError(reply, "Message signing rejected.");
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
async sharedSecret(publicKey, timeoutMs) {
|
|
335
|
+
const key = publicKey?.toString ? publicKey.toString() : String(publicKey || "");
|
|
336
|
+
const reply = await this.request("vex_sharedSecret", [key], timeoutMs);
|
|
337
|
+
|
|
338
|
+
if (typeof reply === "string") return Checksum512.from(reply);
|
|
339
|
+
if (reply?.secret) return Checksum512.from(reply.secret);
|
|
340
|
+
if (reply?.code === "CREATED" && reply?.result?.secret) {
|
|
341
|
+
return Checksum512.from(reply.result.secret);
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
throw replyError(reply, "Shared secret creation failed.");
|
|
345
|
+
}
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
/**
|
|
349
|
+
* Low-level helper for request({ method, params }) providers.
|
|
350
|
+
*/
|
|
351
|
+
export async function requestWithTimeout(provider, method, params, timeoutMs) {
|
|
352
|
+
if (!isRequestProvider(provider)) {
|
|
353
|
+
throw new Error("Wind Wallet injected provider is not available.");
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
const req = provider.request({
|
|
357
|
+
method: String(method || ""),
|
|
358
|
+
params: normalizeParams(params),
|
|
359
|
+
});
|
|
360
|
+
|
|
361
|
+
return await withTimeout(Promise.resolve(req), timeoutMs, method);
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
async function withTimeout(promise, timeoutMs, label = "request") {
|
|
365
|
+
const ms = normalizeTimeoutMs(timeoutMs, DEFAULT_TIMEOUT_MS);
|
|
366
|
+
|
|
367
|
+
return await new Promise((resolve, reject) => {
|
|
368
|
+
const timer = setTimer(() => {
|
|
369
|
+
reject(new Error(`${label} timed out.`));
|
|
370
|
+
}, ms);
|
|
371
|
+
|
|
372
|
+
promise.then(
|
|
373
|
+
(value) => {
|
|
374
|
+
clearTimer(timer);
|
|
375
|
+
resolve(value);
|
|
376
|
+
},
|
|
377
|
+
(error) => {
|
|
378
|
+
clearTimer(timer);
|
|
379
|
+
reject(error);
|
|
380
|
+
}
|
|
381
|
+
);
|
|
382
|
+
});
|
|
383
|
+
}
|
|
384
|
+
|
|
385
|
+
/**
|
|
386
|
+
* WindClient
|
|
387
|
+
* Hybrid connector for DApps.
|
|
388
|
+
*
|
|
389
|
+
* connect() chooses:
|
|
390
|
+
* 1. Injected provider when opened inside Wind Wallet browser.
|
|
391
|
+
* 2. QR/VSR + PeerJS fallback when opened in a normal browser.
|
|
392
|
+
*/
|
|
393
|
+
export class WindClient {
|
|
394
|
+
#connector;
|
|
395
|
+
#listeners = new Map();
|
|
396
|
+
#options;
|
|
397
|
+
|
|
398
|
+
constructor(options = {}) {
|
|
399
|
+
this.#options = { preferInjected: true, ...options };
|
|
400
|
+
}
|
|
401
|
+
|
|
402
|
+
on(event, listener) {
|
|
403
|
+
this.#listeners.set(String(event || ""), listener);
|
|
404
|
+
if (this.#connector && typeof this.#connector.on === "function") {
|
|
405
|
+
this.#connector.on(event, listener);
|
|
406
|
+
}
|
|
407
|
+
}
|
|
408
|
+
|
|
409
|
+
off(event) {
|
|
410
|
+
this.#listeners.delete(String(event || ""));
|
|
411
|
+
if (this.#connector && typeof this.#connector.off === "function") {
|
|
412
|
+
this.#connector.off(event);
|
|
413
|
+
}
|
|
414
|
+
}
|
|
415
|
+
|
|
416
|
+
get connector() {
|
|
417
|
+
return this.#connector;
|
|
418
|
+
}
|
|
419
|
+
|
|
420
|
+
isInjectedAvailable() {
|
|
421
|
+
return isWindWalletInjected();
|
|
422
|
+
}
|
|
423
|
+
|
|
424
|
+
/**
|
|
425
|
+
* Connect to Wind Wallet.
|
|
426
|
+
*
|
|
427
|
+
* @param {{
|
|
428
|
+
* name?: string,
|
|
429
|
+
* icon?: string,
|
|
430
|
+
* preferInjected?: boolean,
|
|
431
|
+
* timeoutMs?: number,
|
|
432
|
+
* walletUrl?: string,
|
|
433
|
+
* openWallet?: (vsr:string, connector:WindConnector)=>void,
|
|
434
|
+
* onLoginRequest?: (vsr:string, connector:WindConnector)=>void,
|
|
435
|
+
* peerOptions?: any
|
|
436
|
+
* }=} options
|
|
437
|
+
*
|
|
438
|
+
* For non-injected fallback, pass onLoginRequest(vsr) to show QR/modal.
|
|
439
|
+
*/
|
|
440
|
+
async connect(options = {}) {
|
|
441
|
+
const opts = { ...this.#options, ...options };
|
|
442
|
+
|
|
443
|
+
if (opts.preferInjected !== false && isWindWalletInjected()) {
|
|
444
|
+
const session = await InjectedWalletSession.connect({ timeoutMs: opts.timeoutMs });
|
|
445
|
+
this.#listeners.get("session")?.(session, { transport: "injected" });
|
|
446
|
+
return session;
|
|
447
|
+
}
|
|
448
|
+
|
|
449
|
+
const connector = new WindConnector(opts.peerOptions);
|
|
450
|
+
this.#connector = connector;
|
|
451
|
+
|
|
452
|
+
for (const [event, listener] of this.#listeners.entries()) {
|
|
453
|
+
connector.on(event, listener);
|
|
454
|
+
}
|
|
455
|
+
|
|
456
|
+
await connector.connect();
|
|
457
|
+
const vsr = connector.createLoginRequest(opts.name || "Wind DApp", opts.icon || "");
|
|
458
|
+
|
|
459
|
+
return await new Promise((resolve, reject) => {
|
|
460
|
+
const cleanup = () => {
|
|
461
|
+
clearTimer(timeout);
|
|
462
|
+
};
|
|
463
|
+
|
|
464
|
+
const timeout = setTimer(() => {
|
|
465
|
+
cleanup();
|
|
466
|
+
const err = new Error("Wind Wallet peer login timed out.");
|
|
467
|
+
err.vsr = vsr;
|
|
468
|
+
reject(err);
|
|
469
|
+
}, normalizeTimeoutMs(opts.timeoutMs, DEFAULT_TIMEOUT_MS));
|
|
470
|
+
|
|
471
|
+
connector.on("session", (session, proof) => {
|
|
472
|
+
cleanup();
|
|
473
|
+
this.#listeners.get("session")?.(session, proof);
|
|
474
|
+
resolve(session);
|
|
475
|
+
});
|
|
476
|
+
|
|
477
|
+
connector.on("error", (error) => {
|
|
478
|
+
this.#listeners.get("error")?.(error);
|
|
479
|
+
});
|
|
480
|
+
|
|
481
|
+
if (typeof opts.onLoginRequest === "function") {
|
|
482
|
+
opts.onLoginRequest(vsr, connector);
|
|
483
|
+
return;
|
|
484
|
+
}
|
|
485
|
+
|
|
486
|
+
if (typeof opts.openWallet === "function") {
|
|
487
|
+
opts.openWallet(vsr, connector);
|
|
488
|
+
return;
|
|
489
|
+
}
|
|
490
|
+
|
|
491
|
+
if (opts.walletUrl && typeof globalThis?.open === "function") {
|
|
492
|
+
const payload = vsr.startsWith("vsr:") ? vsr.slice(4) : vsr;
|
|
493
|
+
const url = `${String(opts.walletUrl).replace(/\/$/, "")}/login?vsr=${encodeURIComponent(payload)}`;
|
|
494
|
+
try {
|
|
495
|
+
globalThis.open(url, "Wind Wallet");
|
|
496
|
+
} catch {}
|
|
497
|
+
return;
|
|
498
|
+
}
|
|
499
|
+
|
|
500
|
+
// No UI handler was provided. Reject with the generated VSR so the DApp can render it.
|
|
501
|
+
const err = new Error(
|
|
502
|
+
"Wind Wallet injected provider not found. Show this VSR as QR or pass onLoginRequest(vsr)."
|
|
503
|
+
);
|
|
504
|
+
err.vsr = vsr;
|
|
505
|
+
err.connector = connector;
|
|
506
|
+
cleanup();
|
|
507
|
+
reject(err);
|
|
508
|
+
});
|
|
509
|
+
}
|
|
510
|
+
}
|
|
511
|
+
|
|
512
|
+
/**
|
|
513
|
+
* Convenience helper.
|
|
514
|
+
*/
|
|
515
|
+
export async function connectWindWallet(options = {}) {
|
|
516
|
+
const client = new WindClient(options);
|
|
517
|
+
return await client.connect(options);
|
|
518
|
+
}
|