@chipi-stack/starknet-connector 0.3.0 → 0.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -72,12 +72,31 @@ chipi({
72
72
  name: "Connect with Chipi", // display name
73
73
  icon: "data:image/svg+xml;base64,...", // display icon
74
74
  register: true, // self-register on window (default)
75
+ transport: {
76
+ openTimeoutMs: 180_000, // ready handshake; on timeout the popup is closed
77
+ requestTimeoutMs: 120_000, // per request; 0 disables
78
+ },
75
79
  });
76
80
  ```
77
81
 
78
82
  `chipi(options)` is a thin wrapper: `new ChipiConnector(options)` still works
79
83
  identically if you prefer instantiating the class directly.
80
84
 
85
+ ## Events and errors
86
+
87
+ - The connector forwards the wallet's `accountsChanged` / `networkChanged` as
88
+ starknet-react `change` events (`{ account, chainId }`); an empty account list
89
+ emits `disconnect`.
90
+ - `connect({ chainIdHint })` rejects with `code: "UNSUPPORTED_CHAIN"` for any
91
+ chain other than mainnet (`SN_MAIN`), before opening a popup.
92
+ - A request that gets no answer within `requestTimeoutMs` rejects with
93
+ `code: "REQUEST_TIMEOUT"`.
94
+ - If the user closes the wallet window, later requests reject with
95
+ `code: "POPUP_CLOSED"` instead of reopening it (browsers block popups outside
96
+ a click). Call `connect()` again from a click.
97
+ - `disconnect()` closes the popup and removes `window.starknet_chipi`; the next
98
+ `connect()` registers it again.
99
+
81
100
  ## Compatibility
82
101
 
83
102
  - `@starknet-react/core` **>= 3** (peer) · tested against `5.x`
@@ -95,6 +114,28 @@ avoid a duplicate entry in a picker that already lists the connector
95
114
  explicitly). It's a no-op during SSR. You can also call
96
115
  `registerChipiWallet(swo)` directly.
97
116
 
117
+ ### Wallet Standard (get-starknet v5+ discovery)
118
+
119
+ For dApps that discover wallets through the Wallet Standard
120
+ (`@starknet-io/get-starknet-discovery`, `@starknet-start/react`), register Chipi
121
+ as a `WalletWithStarknetFeatures` (`starknet:walletApi`, `standard:connect`,
122
+ `standard:disconnect`, `standard:events`) backed by the same popup:
123
+
124
+ ```ts
125
+ import { registerChipiStandardWallet } from "@chipi-stack/starknet-connector";
126
+
127
+ const unregister = registerChipiStandardWallet({
128
+ walletUrl: "https://wallet.chipipay.com", // default
129
+ });
130
+ // later: unregister();
131
+ ```
132
+
133
+ It dispatches `wallet-standard:register-wallet` and answers
134
+ `wallet-standard:app-ready`, with no extra dependency. Accounts are on
135
+ `starknet:0x534e5f4d41494e` (mainnet). `connect({ silent: true })` never opens a
136
+ popup. Use `createChipiStandardWallet(options)` to build the wallet object
137
+ without registering it.
138
+
98
139
  ## Notes / limits (v1)
99
140
 
100
141
  - **Popup-based** (external dApps). The in-app-browser ("parent") mode is in the
package/dist/index.cjs CHANGED
@@ -15,6 +15,22 @@ function isChipiDappMessage(data) {
15
15
  }
16
16
 
17
17
  // src/transport.ts
18
+ function transportError(code, message) {
19
+ const err = Object.assign(new Error(message), { code });
20
+ err.name = code;
21
+ return err;
22
+ }
23
+ function stripUndefined(value) {
24
+ if (Array.isArray(value)) return value.map((v) => stripUndefined(v));
25
+ if (value === null || typeof value !== "object") return value;
26
+ const proto = Object.getPrototypeOf(value);
27
+ if (proto !== Object.prototype && proto !== null) return value;
28
+ const out = {};
29
+ for (const [k, v] of Object.entries(value)) {
30
+ if (v !== void 0) out[k] = stripUndefined(v);
31
+ }
32
+ return out;
33
+ }
18
34
  function deferred() {
19
35
  let resolve;
20
36
  let reject;
@@ -39,18 +55,19 @@ var ChipiPopupTransport = class {
39
55
  this.readyDeferred = null;
40
56
  this.openPromise = null;
41
57
  this.closeWatch = null;
58
+ /** True once this transport opened a popup. A later request with no live
59
+ * popup means the user closed it: reject instead of reopening off-gesture. */
60
+ this.hadPopup = false;
42
61
  this.onMessage = (e) => {
43
62
  if (e.origin !== this.walletOrigin || !isChipiWalletMessage(e.data)) return;
44
- if (this.target && e.source !== this.target) return;
63
+ if (!this.target || e.source !== this.target) return;
45
64
  const msg = e.data;
46
65
  if (msg.type === "ready") {
47
66
  this.ready = true;
48
67
  this.readyDeferred?.resolve();
49
68
  this.readyDeferred = null;
50
- if (this.target) {
51
- for (const p of this.pending.values()) {
52
- this.target.postMessage(p.message, this.walletOrigin);
53
- }
69
+ for (const p of this.pending.values()) {
70
+ this.target.postMessage(p.message, this.walletOrigin);
54
71
  }
55
72
  return;
56
73
  }
@@ -58,6 +75,7 @@ var ChipiPopupTransport = class {
58
75
  const p = this.pending.get(msg.id);
59
76
  if (!p) return;
60
77
  this.pending.delete(msg.id);
78
+ if (p.timer) clearTimeout(p.timer);
61
79
  if (msg.ok) p.resolve(msg.result);
62
80
  else p.reject(Object.assign(new Error(msg.error.message), { code: msg.error.code }));
63
81
  return;
@@ -116,9 +134,26 @@ var ChipiPopupTransport = class {
116
134
  );
117
135
  this.ownsTarget = true;
118
136
  if (!this.target) throw Object.assign(new Error("Popup blocked"), { code: "popup_blocked" });
137
+ this.hadPopup = true;
119
138
  }
139
+ const opened = this.target;
120
140
  this.readyDeferred = deferred();
121
- await this.awaitReady();
141
+ try {
142
+ await this.awaitReady();
143
+ } catch (err) {
144
+ if (this.target === opened) this.resetTarget();
145
+ throw err;
146
+ }
147
+ }
148
+ /** Close the popup (if we own one) and forget the target + ready state. */
149
+ resetTarget() {
150
+ try {
151
+ if (this.ownsTarget) this.target?.close();
152
+ } catch {
153
+ }
154
+ this.target = null;
155
+ this.ready = false;
156
+ this.readyDeferred = null;
122
157
  }
123
158
  async awaitReady() {
124
159
  const timeoutMs = this.opts.openTimeoutMs ?? 18e4;
@@ -134,7 +169,10 @@ var ChipiPopupTransport = class {
134
169
  }
135
170
  /** Reject every in-flight request (popup closed / disconnected). */
136
171
  rejectAllPending(message) {
137
- for (const [, p] of this.pending) p.reject(Object.assign(new Error(message), { code: 4001 }));
172
+ for (const [, p] of this.pending) {
173
+ if (p.timer) clearTimeout(p.timer);
174
+ p.reject(Object.assign(new Error(message), { code: 4001 }));
175
+ }
138
176
  this.pending.clear();
139
177
  }
140
178
  /** While requests are in flight, watch for the user closing the popup and
@@ -158,8 +196,22 @@ var ChipiPopupTransport = class {
158
196
  this.closeWatch = null;
159
197
  }
160
198
  }
161
- /** Forward a get-starknet RPC call to the wallet, awaiting its response. */
199
+ /**
200
+ * Forward a get-starknet RPC call to the wallet, awaiting its response.
201
+ *
202
+ * The first request may open the popup. Once a popup has been opened, a
203
+ * request made after it was closed rejects with `code: "POPUP_CLOSED"`
204
+ * instead of calling `window.open` again: requests usually run outside a
205
+ * click, where browsers block popups. Reopen via `connect()` / `open()`
206
+ * from a click.
207
+ */
162
208
  async request(call) {
209
+ if (this.hadPopup && this.resolveMode() === "popup" && (!this.target || this.target.closed)) {
210
+ throw transportError(
211
+ "POPUP_CLOSED",
212
+ "The Chipi wallet window was closed. Call connect() again from a click."
213
+ );
214
+ }
163
215
  await this.open();
164
216
  if (this.ownsTarget) this.target?.focus();
165
217
  const id = randomId();
@@ -168,10 +220,24 @@ var ChipiPopupTransport = class {
168
220
  type: "rpc",
169
221
  id,
170
222
  origin: window.location.origin,
171
- call
223
+ call: stripUndefined(call)
172
224
  };
225
+ const timeoutMs = this.opts.requestTimeoutMs ?? 12e4;
173
226
  return new Promise((resolve, reject) => {
174
- this.pending.set(id, { resolve, reject, message });
227
+ const entry = { resolve, reject, message };
228
+ if (timeoutMs > 0 && Number.isFinite(timeoutMs)) {
229
+ entry.timer = setTimeout(() => {
230
+ if (this.pending.get(id) !== entry) return;
231
+ this.pending.delete(id);
232
+ reject(
233
+ transportError(
234
+ "REQUEST_TIMEOUT",
235
+ `The Chipi wallet did not answer "${call.type}" within ${timeoutMs} ms.`
236
+ )
237
+ );
238
+ }, timeoutMs);
239
+ }
240
+ this.pending.set(id, entry);
175
241
  this.target.postMessage(message, this.walletOrigin);
176
242
  this.startCloseWatch();
177
243
  });
@@ -179,12 +245,8 @@ var ChipiPopupTransport = class {
179
245
  /** Close the popup (if we own one) + reject in-flight requests. */
180
246
  close() {
181
247
  this.stopCloseWatch();
182
- try {
183
- if (this.ownsTarget) this.target?.close();
184
- } catch {
185
- }
186
- this.target = null;
187
- this.ready = false;
248
+ this.readyDeferred?.reject(Object.assign(new Error("disconnected"), { code: 4001 }));
249
+ this.resetTarget();
188
250
  this.rejectAllPending("disconnected");
189
251
  if (this.listening && typeof window !== "undefined") {
190
252
  window.removeEventListener("message", this.onMessage);
@@ -215,13 +277,46 @@ function registerChipiWallet(wallet, target = typeof window !== "undefined" ? wi
215
277
  var CHIPI_ICON = "data:image/png;base64,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";
216
278
 
217
279
  // src/connector.ts
218
- var SN_MAIN = BigInt("0x534e5f4d41494e");
280
+ var SN_MAIN = BigInt(starknet.constants.StarknetChainId.SN_MAIN);
281
+ function toChainId(value) {
282
+ if (typeof value === "bigint") return value;
283
+ if (typeof value === "number" || typeof value === "string") {
284
+ try {
285
+ return BigInt(value);
286
+ } catch {
287
+ return void 0;
288
+ }
289
+ }
290
+ return void 0;
291
+ }
219
292
  var ChipiConnector = class extends core.Connector {
220
293
  constructor(options = {}) {
221
294
  super();
222
295
  this._account = null;
223
296
  this._swo = null;
224
297
  this._registration = null;
298
+ this.onAccountsChanged = (data) => {
299
+ const accounts = Array.isArray(data) ? data : [];
300
+ const account = accounts[0];
301
+ if (typeof account === "string" && account) {
302
+ this._account = account;
303
+ this.emitEvent("change", { account, chainId: SN_MAIN });
304
+ } else {
305
+ this._account = null;
306
+ this.emitEvent("disconnect");
307
+ }
308
+ };
309
+ this.onNetworkChanged = (data) => {
310
+ const payload = typeof data === "object" && data !== null && !Array.isArray(data) ? data : { chainId: data };
311
+ const chainId = toChainId(payload.chainId);
312
+ const accounts = Array.isArray(payload.accounts) ? payload.accounts : [];
313
+ const next = typeof accounts[0] === "string" && accounts[0] ? accounts[0] : this._account;
314
+ if (next) this._account = next;
315
+ const change = {};
316
+ if (chainId !== void 0) change.chainId = chainId;
317
+ if (next) change.account = next;
318
+ this.emitEvent("change", change);
319
+ };
225
320
  const walletUrl = options.walletUrl ?? options.transport?.walletUrl ?? "https://wallet.chipipay.com";
226
321
  this.transport = new ChipiPopupTransport({
227
322
  ...options.transport,
@@ -232,7 +327,8 @@ var ChipiConnector = class extends core.Connector {
232
327
  this._id = options.id ?? "chipi";
233
328
  this._name = options.name ?? "Connect with Chipi";
234
329
  this._icon = options.icon ?? CHIPI_ICON;
235
- if (options.register !== false) this.register();
330
+ this._autoRegister = options.register !== false;
331
+ if (this._autoRegister) this.register();
236
332
  }
237
333
  /**
238
334
  * Announce this wallet to scanning dApps (`window.starknet_chipi` +
@@ -286,7 +382,22 @@ var ChipiConnector = class extends core.Connector {
286
382
  };
287
383
  return this._swo;
288
384
  }
289
- async connect() {
385
+ /**
386
+ * Open the hosted wallet (call from a click: it opens a popup) and request
387
+ * the account. Chipi is mainnet-only: a `chainIdHint` other than SN_MAIN
388
+ * rejects before any popup opens.
389
+ */
390
+ async connect(args = {}) {
391
+ if (args.chainIdHint !== void 0 && BigInt(args.chainIdHint) !== SN_MAIN) {
392
+ throw Object.assign(
393
+ new Error(
394
+ `Chipi is mainnet-only: chainIdHint 0x${BigInt(args.chainIdHint).toString(16)} is not SN_MAIN.`
395
+ ),
396
+ { code: "UNSUPPORTED_CHAIN" }
397
+ );
398
+ }
399
+ if (this._autoRegister) this.register();
400
+ this.subscribe();
290
401
  await this.transport.open();
291
402
  const accounts = await this.transport.request({ type: "wallet_requestAccounts" });
292
403
  const account = accounts?.[0];
@@ -297,16 +408,27 @@ var ChipiConnector = class extends core.Connector {
297
408
  return data;
298
409
  }
299
410
  async disconnect() {
411
+ this.unsubscribe();
300
412
  this.transport.close();
301
413
  this._account = null;
414
+ this.unregister();
302
415
  this._swo = null;
303
416
  this.emitEvent("disconnect");
304
417
  }
305
- // `Connector` extends eventemitter3's EventEmitter, but the bundled .d.ts
306
- // doesn't surface `emit` cleanly through the abstract class: reach it via a
307
- // narrow cast rather than fight the upstream typing.
418
+ /** Forward the wallet's account/network events as starknet-react events. */
419
+ subscribe() {
420
+ this.unsubscribe();
421
+ this.transport.on("accountsChanged", this.onAccountsChanged);
422
+ this.transport.on("networkChanged", this.onNetworkChanged);
423
+ }
424
+ unsubscribe() {
425
+ this.transport.off("accountsChanged", this.onAccountsChanged);
426
+ this.transport.off("networkChanged", this.onNetworkChanged);
427
+ }
308
428
  emitEvent(event, data) {
309
- this.emit(event, data);
429
+ const emitter = this;
430
+ if (data === void 0) emitter.emit(event);
431
+ else emitter.emit(event, data);
310
432
  }
311
433
  async account(provider) {
312
434
  if (!this._account) throw new Error("chipi_not_connected");
@@ -325,13 +447,154 @@ function chipi(options) {
325
447
  return new ChipiConnector(options);
326
448
  }
327
449
 
450
+ // src/standard.ts
451
+ var CHIPI_STARKNET_MAINNET_CHAIN = "starknet:0x534e5f4d41494e";
452
+ function toStarknetChain(value) {
453
+ if (typeof value !== "string" && typeof value !== "bigint" && typeof value !== "number") {
454
+ return void 0;
455
+ }
456
+ try {
457
+ return `starknet:0x${BigInt(value).toString(16)}`;
458
+ } catch {
459
+ return void 0;
460
+ }
461
+ }
462
+ function createChipiStandardWallet(options = {}) {
463
+ const transport = new ChipiPopupTransport({
464
+ ...options.transport,
465
+ walletUrl: options.walletUrl ?? "https://wallet.chipipay.com",
466
+ mode: "popup"
467
+ });
468
+ const listeners = /* @__PURE__ */ new Set();
469
+ let current = null;
470
+ const accounts = () => current ? [
471
+ {
472
+ address: current.address,
473
+ publicKey: new Uint8Array(),
474
+ chains: [current.chain],
475
+ features: []
476
+ }
477
+ ] : [];
478
+ const emitChange = () => {
479
+ const properties = { accounts: accounts() };
480
+ for (const listener of [...listeners]) listener(properties);
481
+ };
482
+ const setDisconnected = () => {
483
+ if (!current) return;
484
+ current = null;
485
+ emitChange();
486
+ };
487
+ const onAccountsChanged = (data) => {
488
+ const list = Array.isArray(data) ? data : [];
489
+ const address = list[0];
490
+ if (typeof address !== "string" || !address) return setDisconnected();
491
+ if (!current) return;
492
+ current = { ...current, address };
493
+ emitChange();
494
+ };
495
+ const onNetworkChanged = (data) => {
496
+ const payload = typeof data === "object" && data !== null && !Array.isArray(data) ? data : { chainId: data };
497
+ const chain = toStarknetChain(payload.chainId);
498
+ if (!chain) return setDisconnected();
499
+ if (!current) return;
500
+ const list = Array.isArray(payload.accounts) ? payload.accounts : [];
501
+ const address = typeof list[0] === "string" && list[0] ? list[0] : current.address;
502
+ current = { address, chain };
503
+ emitChange();
504
+ };
505
+ transport.on("accountsChanged", onAccountsChanged);
506
+ transport.on("networkChanged", onNetworkChanged);
507
+ const request = ((call) => transport.request(call));
508
+ const features = {
509
+ "starknet:walletApi": {
510
+ version: "1.0.0",
511
+ request,
512
+ walletVersion: "1.0.0",
513
+ id: "chipi"
514
+ },
515
+ "standard:connect": {
516
+ version: "1.0.0",
517
+ connect: async (input) => {
518
+ if (current || input?.silent) return { accounts: accounts() };
519
+ await transport.open();
520
+ const result = await transport.request({ type: "wallet_requestAccounts" });
521
+ const list = Array.isArray(result) ? result : [];
522
+ const address = list[0];
523
+ if (typeof address !== "string" || !address) return { accounts: [] };
524
+ current = { address, chain: CHIPI_STARKNET_MAINNET_CHAIN };
525
+ emitChange();
526
+ return { accounts: accounts() };
527
+ }
528
+ },
529
+ "standard:disconnect": {
530
+ version: "1.0.0",
531
+ disconnect: async () => {
532
+ transport.close();
533
+ setDisconnected();
534
+ }
535
+ },
536
+ "standard:events": {
537
+ version: "1.0.0",
538
+ on: (_event, listener) => {
539
+ listeners.add(listener);
540
+ return () => {
541
+ listeners.delete(listener);
542
+ };
543
+ }
544
+ }
545
+ };
546
+ return {
547
+ version: "1.0.0",
548
+ name: options.name ?? "Chipi",
549
+ icon: options.icon ?? CHIPI_ICON,
550
+ chains: [CHIPI_STARKNET_MAINNET_CHAIN],
551
+ get accounts() {
552
+ return accounts();
553
+ },
554
+ features
555
+ };
556
+ }
557
+ function registerChipiStandardWallet(options = {}, target = typeof window !== "undefined" ? window : void 0) {
558
+ if (!target) return () => {
559
+ };
560
+ const wallet = createChipiStandardWallet(options);
561
+ const unregisters = [];
562
+ const callback = ({ register }) => {
563
+ unregisters.push(register(wallet));
564
+ };
565
+ const onAppReady = (event) => {
566
+ const api = event.detail;
567
+ if (api && typeof api.register === "function") callback(api);
568
+ };
569
+ try {
570
+ target.dispatchEvent(
571
+ new CustomEvent("wallet-standard:register-wallet", {
572
+ detail: callback,
573
+ bubbles: false,
574
+ cancelable: false,
575
+ composed: false
576
+ })
577
+ );
578
+ } catch (error) {
579
+ console.error("wallet-standard:register-wallet event could not be dispatched\n", error);
580
+ }
581
+ target.addEventListener("wallet-standard:app-ready", onAppReady);
582
+ return () => {
583
+ target.removeEventListener("wallet-standard:app-ready", onAppReady);
584
+ for (const unregister of unregisters.splice(0)) unregister();
585
+ };
586
+ }
587
+
588
+ exports.CHIPI_STARKNET_MAINNET_CHAIN = CHIPI_STARKNET_MAINNET_CHAIN;
328
589
  exports.CHIPI_WC = CHIPI_WC;
329
590
  exports.CHIPI_WINDOW_KEY = CHIPI_WINDOW_KEY;
330
591
  exports.ChipiConnector = ChipiConnector;
331
592
  exports.ChipiPopupTransport = ChipiPopupTransport;
332
593
  exports.chipi = chipi;
594
+ exports.createChipiStandardWallet = createChipiStandardWallet;
333
595
  exports.isChipiDappMessage = isChipiDappMessage;
334
596
  exports.isChipiWalletMessage = isChipiWalletMessage;
597
+ exports.registerChipiStandardWallet = registerChipiStandardWallet;
335
598
  exports.registerChipiWallet = registerChipiWallet;
336
599
  //# sourceMappingURL=index.cjs.map
337
600
  //# sourceMappingURL=index.cjs.map