@chipi-stack/starknet-connector 0.3.0 → 0.5.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/dist/index.js CHANGED
@@ -1,5 +1,5 @@
1
1
  import { Connector } from '@starknet-react/core';
2
- import { WalletAccount } from 'starknet';
2
+ import { constants, WalletAccount } from 'starknet';
3
3
 
4
4
  // src/connector.ts
5
5
 
@@ -13,6 +13,22 @@ function isChipiDappMessage(data) {
13
13
  }
14
14
 
15
15
  // src/transport.ts
16
+ function transportError(code, message) {
17
+ const err = Object.assign(new Error(message), { code });
18
+ err.name = code;
19
+ return err;
20
+ }
21
+ function stripUndefined(value) {
22
+ if (Array.isArray(value)) return value.map((v) => stripUndefined(v));
23
+ if (value === null || typeof value !== "object") return value;
24
+ const proto = Object.getPrototypeOf(value);
25
+ if (proto !== Object.prototype && proto !== null) return value;
26
+ const out = {};
27
+ for (const [k, v] of Object.entries(value)) {
28
+ if (v !== void 0) out[k] = stripUndefined(v);
29
+ }
30
+ return out;
31
+ }
16
32
  function deferred() {
17
33
  let resolve;
18
34
  let reject;
@@ -22,6 +38,21 @@ function deferred() {
22
38
  });
23
39
  return { promise, resolve, reject };
24
40
  }
41
+ var SN_MAIN_HEX = "0x534e5f4d41494e";
42
+ function passiveAnswer(call) {
43
+ switch (call.type) {
44
+ case "wallet_getPermissions":
45
+ return { value: [] };
46
+ case "wallet_requestAccounts": {
47
+ const params = call.params;
48
+ return params?.silent_mode === true ? { value: [] } : void 0;
49
+ }
50
+ case "wallet_requestChainId":
51
+ return { value: SN_MAIN_HEX };
52
+ default:
53
+ return void 0;
54
+ }
55
+ }
25
56
  function randomId() {
26
57
  return `${Date.now().toString(36)}-${Math.random().toString(36).slice(2, 10)}`;
27
58
  }
@@ -37,18 +68,19 @@ var ChipiPopupTransport = class {
37
68
  this.readyDeferred = null;
38
69
  this.openPromise = null;
39
70
  this.closeWatch = null;
71
+ /** True once this transport opened a popup. A later request with no live
72
+ * popup means the user closed it: reject instead of reopening off-gesture. */
73
+ this.hadPopup = false;
40
74
  this.onMessage = (e) => {
41
75
  if (e.origin !== this.walletOrigin || !isChipiWalletMessage(e.data)) return;
42
- if (this.target && e.source !== this.target) return;
76
+ if (!this.target || e.source !== this.target) return;
43
77
  const msg = e.data;
44
78
  if (msg.type === "ready") {
45
79
  this.ready = true;
46
80
  this.readyDeferred?.resolve();
47
81
  this.readyDeferred = null;
48
- if (this.target) {
49
- for (const p of this.pending.values()) {
50
- this.target.postMessage(p.message, this.walletOrigin);
51
- }
82
+ for (const p of this.pending.values()) {
83
+ this.target.postMessage(p.message, this.walletOrigin);
52
84
  }
53
85
  return;
54
86
  }
@@ -56,6 +88,7 @@ var ChipiPopupTransport = class {
56
88
  const p = this.pending.get(msg.id);
57
89
  if (!p) return;
58
90
  this.pending.delete(msg.id);
91
+ if (p.timer) clearTimeout(p.timer);
59
92
  if (msg.ok) p.resolve(msg.result);
60
93
  else p.reject(Object.assign(new Error(msg.error.message), { code: msg.error.code }));
61
94
  return;
@@ -114,9 +147,26 @@ var ChipiPopupTransport = class {
114
147
  );
115
148
  this.ownsTarget = true;
116
149
  if (!this.target) throw Object.assign(new Error("Popup blocked"), { code: "popup_blocked" });
150
+ this.hadPopup = true;
117
151
  }
152
+ const opened = this.target;
118
153
  this.readyDeferred = deferred();
119
- await this.awaitReady();
154
+ try {
155
+ await this.awaitReady();
156
+ } catch (err) {
157
+ if (this.target === opened) this.resetTarget();
158
+ throw err;
159
+ }
160
+ }
161
+ /** Close the popup (if we own one) and forget the target + ready state. */
162
+ resetTarget() {
163
+ try {
164
+ if (this.ownsTarget) this.target?.close();
165
+ } catch {
166
+ }
167
+ this.target = null;
168
+ this.ready = false;
169
+ this.readyDeferred = null;
120
170
  }
121
171
  async awaitReady() {
122
172
  const timeoutMs = this.opts.openTimeoutMs ?? 18e4;
@@ -132,7 +182,10 @@ var ChipiPopupTransport = class {
132
182
  }
133
183
  /** Reject every in-flight request (popup closed / disconnected). */
134
184
  rejectAllPending(message) {
135
- for (const [, p] of this.pending) p.reject(Object.assign(new Error(message), { code: 4001 }));
185
+ for (const [, p] of this.pending) {
186
+ if (p.timer) clearTimeout(p.timer);
187
+ p.reject(Object.assign(new Error(message), { code: 4001 }));
188
+ }
136
189
  this.pending.clear();
137
190
  }
138
191
  /** While requests are in flight, watch for the user closing the popup and
@@ -156,8 +209,30 @@ var ChipiPopupTransport = class {
156
209
  this.closeWatch = null;
157
210
  }
158
211
  }
159
- /** Forward a get-starknet RPC call to the wallet, awaiting its response. */
212
+ /**
213
+ * Forward a get-starknet RPC call to the wallet, awaiting its response.
214
+ *
215
+ * With no wallet window open, passive calls are answered locally and never
216
+ * open it: `wallet_getPermissions` → `[]`, `wallet_requestAccounts` with
217
+ * `silent_mode: true` → `[]`, `wallet_requestChainId` → SN_MAIN.
218
+ *
219
+ * The first request may open the popup. Once a popup has been opened, a
220
+ * request made after it was closed rejects with `code: "POPUP_CLOSED"`
221
+ * instead of calling `window.open` again: requests usually run outside a
222
+ * click, where browsers block popups. Reopen via `connect()` / `open()`
223
+ * from a click.
224
+ */
160
225
  async request(call) {
226
+ if (this.resolveMode() === "popup" && (!this.target || this.target.closed)) {
227
+ const passive = passiveAnswer(call);
228
+ if (passive) return passive.value;
229
+ }
230
+ if (this.hadPopup && this.resolveMode() === "popup" && (!this.target || this.target.closed)) {
231
+ throw transportError(
232
+ "POPUP_CLOSED",
233
+ "The Chipi wallet window was closed. Call connect() again from a click."
234
+ );
235
+ }
161
236
  await this.open();
162
237
  if (this.ownsTarget) this.target?.focus();
163
238
  const id = randomId();
@@ -166,10 +241,24 @@ var ChipiPopupTransport = class {
166
241
  type: "rpc",
167
242
  id,
168
243
  origin: window.location.origin,
169
- call
244
+ call: stripUndefined(call)
170
245
  };
246
+ const timeoutMs = this.opts.requestTimeoutMs ?? 12e4;
171
247
  return new Promise((resolve, reject) => {
172
- this.pending.set(id, { resolve, reject, message });
248
+ const entry = { resolve, reject, message };
249
+ if (timeoutMs > 0 && Number.isFinite(timeoutMs)) {
250
+ entry.timer = setTimeout(() => {
251
+ if (this.pending.get(id) !== entry) return;
252
+ this.pending.delete(id);
253
+ reject(
254
+ transportError(
255
+ "REQUEST_TIMEOUT",
256
+ `The Chipi wallet did not answer "${call.type}" within ${timeoutMs} ms.`
257
+ )
258
+ );
259
+ }, timeoutMs);
260
+ }
261
+ this.pending.set(id, entry);
173
262
  this.target.postMessage(message, this.walletOrigin);
174
263
  this.startCloseWatch();
175
264
  });
@@ -177,12 +266,8 @@ var ChipiPopupTransport = class {
177
266
  /** Close the popup (if we own one) + reject in-flight requests. */
178
267
  close() {
179
268
  this.stopCloseWatch();
180
- try {
181
- if (this.ownsTarget) this.target?.close();
182
- } catch {
183
- }
184
- this.target = null;
185
- this.ready = false;
269
+ this.readyDeferred?.reject(Object.assign(new Error("disconnected"), { code: 4001 }));
270
+ this.resetTarget();
186
271
  this.rejectAllPending("disconnected");
187
272
  if (this.listening && typeof window !== "undefined") {
188
273
  window.removeEventListener("message", this.onMessage);
@@ -213,13 +298,46 @@ function registerChipiWallet(wallet, target = typeof window !== "undefined" ? wi
213
298
  var CHIPI_ICON = "data:image/png;base64,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";
214
299
 
215
300
  // src/connector.ts
216
- var SN_MAIN = BigInt("0x534e5f4d41494e");
301
+ var SN_MAIN = BigInt(constants.StarknetChainId.SN_MAIN);
302
+ function toChainId(value) {
303
+ if (typeof value === "bigint") return value;
304
+ if (typeof value === "number" || typeof value === "string") {
305
+ try {
306
+ return BigInt(value);
307
+ } catch {
308
+ return void 0;
309
+ }
310
+ }
311
+ return void 0;
312
+ }
217
313
  var ChipiConnector = class extends Connector {
218
314
  constructor(options = {}) {
219
315
  super();
220
316
  this._account = null;
221
317
  this._swo = null;
222
318
  this._registration = null;
319
+ this.onAccountsChanged = (data) => {
320
+ const accounts = Array.isArray(data) ? data : [];
321
+ const account = accounts[0];
322
+ if (typeof account === "string" && account) {
323
+ this._account = account;
324
+ this.emitEvent("change", { account, chainId: SN_MAIN });
325
+ } else {
326
+ this._account = null;
327
+ this.emitEvent("disconnect");
328
+ }
329
+ };
330
+ this.onNetworkChanged = (data) => {
331
+ const payload = typeof data === "object" && data !== null && !Array.isArray(data) ? data : { chainId: data };
332
+ const chainId = toChainId(payload.chainId);
333
+ const accounts = Array.isArray(payload.accounts) ? payload.accounts : [];
334
+ const next = typeof accounts[0] === "string" && accounts[0] ? accounts[0] : this._account;
335
+ if (next) this._account = next;
336
+ const change = {};
337
+ if (chainId !== void 0) change.chainId = chainId;
338
+ if (next) change.account = next;
339
+ this.emitEvent("change", change);
340
+ };
223
341
  const walletUrl = options.walletUrl ?? options.transport?.walletUrl ?? "https://wallet.chipipay.com";
224
342
  this.transport = new ChipiPopupTransport({
225
343
  ...options.transport,
@@ -230,7 +348,8 @@ var ChipiConnector = class extends Connector {
230
348
  this._id = options.id ?? "chipi";
231
349
  this._name = options.name ?? "Connect with Chipi";
232
350
  this._icon = options.icon ?? CHIPI_ICON;
233
- if (options.register !== false) this.register();
351
+ this._autoRegister = options.register !== false;
352
+ if (this._autoRegister) this.register();
234
353
  }
235
354
  /**
236
355
  * Announce this wallet to scanning dApps (`window.starknet_chipi` +
@@ -284,7 +403,22 @@ var ChipiConnector = class extends Connector {
284
403
  };
285
404
  return this._swo;
286
405
  }
287
- async connect() {
406
+ /**
407
+ * Open the hosted wallet (call from a click: it opens a popup) and request
408
+ * the account. Chipi is mainnet-only: a `chainIdHint` other than SN_MAIN
409
+ * rejects before any popup opens.
410
+ */
411
+ async connect(args = {}) {
412
+ if (args.chainIdHint !== void 0 && BigInt(args.chainIdHint) !== SN_MAIN) {
413
+ throw Object.assign(
414
+ new Error(
415
+ `Chipi is mainnet-only: chainIdHint 0x${BigInt(args.chainIdHint).toString(16)} is not SN_MAIN.`
416
+ ),
417
+ { code: "UNSUPPORTED_CHAIN" }
418
+ );
419
+ }
420
+ if (this._autoRegister) this.register();
421
+ this.subscribe();
288
422
  await this.transport.open();
289
423
  const accounts = await this.transport.request({ type: "wallet_requestAccounts" });
290
424
  const account = accounts?.[0];
@@ -295,16 +429,27 @@ var ChipiConnector = class extends Connector {
295
429
  return data;
296
430
  }
297
431
  async disconnect() {
432
+ this.unsubscribe();
298
433
  this.transport.close();
299
434
  this._account = null;
435
+ this.unregister();
300
436
  this._swo = null;
301
437
  this.emitEvent("disconnect");
302
438
  }
303
- // `Connector` extends eventemitter3's EventEmitter, but the bundled .d.ts
304
- // doesn't surface `emit` cleanly through the abstract class: reach it via a
305
- // narrow cast rather than fight the upstream typing.
439
+ /** Forward the wallet's account/network events as starknet-react events. */
440
+ subscribe() {
441
+ this.unsubscribe();
442
+ this.transport.on("accountsChanged", this.onAccountsChanged);
443
+ this.transport.on("networkChanged", this.onNetworkChanged);
444
+ }
445
+ unsubscribe() {
446
+ this.transport.off("accountsChanged", this.onAccountsChanged);
447
+ this.transport.off("networkChanged", this.onNetworkChanged);
448
+ }
306
449
  emitEvent(event, data) {
307
- this.emit(event, data);
450
+ const emitter = this;
451
+ if (data === void 0) emitter.emit(event);
452
+ else emitter.emit(event, data);
308
453
  }
309
454
  async account(provider) {
310
455
  if (!this._account) throw new Error("chipi_not_connected");
@@ -323,6 +468,144 @@ function chipi(options) {
323
468
  return new ChipiConnector(options);
324
469
  }
325
470
 
326
- export { CHIPI_WC, CHIPI_WINDOW_KEY, ChipiConnector, ChipiPopupTransport, chipi, isChipiDappMessage, isChipiWalletMessage, registerChipiWallet };
471
+ // src/standard.ts
472
+ var CHIPI_STARKNET_MAINNET_CHAIN = "starknet:0x534e5f4d41494e";
473
+ function toStarknetChain(value) {
474
+ if (typeof value !== "string" && typeof value !== "bigint" && typeof value !== "number") {
475
+ return void 0;
476
+ }
477
+ try {
478
+ return `starknet:0x${BigInt(value).toString(16)}`;
479
+ } catch {
480
+ return void 0;
481
+ }
482
+ }
483
+ function createChipiStandardWallet(options = {}) {
484
+ const transport = new ChipiPopupTransport({
485
+ ...options.transport,
486
+ walletUrl: options.walletUrl ?? "https://wallet.chipipay.com",
487
+ mode: "popup"
488
+ });
489
+ const listeners = /* @__PURE__ */ new Set();
490
+ let current = null;
491
+ const accounts = () => current ? [
492
+ {
493
+ address: current.address,
494
+ publicKey: new Uint8Array(),
495
+ chains: [current.chain],
496
+ features: []
497
+ }
498
+ ] : [];
499
+ const emitChange = () => {
500
+ const properties = { accounts: accounts() };
501
+ for (const listener of [...listeners]) listener(properties);
502
+ };
503
+ const setDisconnected = () => {
504
+ if (!current) return;
505
+ current = null;
506
+ emitChange();
507
+ };
508
+ const onAccountsChanged = (data) => {
509
+ const list = Array.isArray(data) ? data : [];
510
+ const address = list[0];
511
+ if (typeof address !== "string" || !address) return setDisconnected();
512
+ if (!current) return;
513
+ current = { ...current, address };
514
+ emitChange();
515
+ };
516
+ const onNetworkChanged = (data) => {
517
+ const payload = typeof data === "object" && data !== null && !Array.isArray(data) ? data : { chainId: data };
518
+ const chain = toStarknetChain(payload.chainId);
519
+ if (!chain) return setDisconnected();
520
+ if (!current) return;
521
+ const list = Array.isArray(payload.accounts) ? payload.accounts : [];
522
+ const address = typeof list[0] === "string" && list[0] ? list[0] : current.address;
523
+ current = { address, chain };
524
+ emitChange();
525
+ };
526
+ transport.on("accountsChanged", onAccountsChanged);
527
+ transport.on("networkChanged", onNetworkChanged);
528
+ const request = ((call) => transport.request(call));
529
+ const features = {
530
+ "starknet:walletApi": {
531
+ version: "1.0.0",
532
+ request,
533
+ walletVersion: "1.0.0",
534
+ id: "chipi"
535
+ },
536
+ "standard:connect": {
537
+ version: "1.0.0",
538
+ connect: async (input) => {
539
+ if (current || input?.silent) return { accounts: accounts() };
540
+ await transport.open();
541
+ const result = await transport.request({ type: "wallet_requestAccounts" });
542
+ const list = Array.isArray(result) ? result : [];
543
+ const address = list[0];
544
+ if (typeof address !== "string" || !address) return { accounts: [] };
545
+ current = { address, chain: CHIPI_STARKNET_MAINNET_CHAIN };
546
+ emitChange();
547
+ return { accounts: accounts() };
548
+ }
549
+ },
550
+ "standard:disconnect": {
551
+ version: "1.0.0",
552
+ disconnect: async () => {
553
+ transport.close();
554
+ setDisconnected();
555
+ }
556
+ },
557
+ "standard:events": {
558
+ version: "1.0.0",
559
+ on: (_event, listener) => {
560
+ listeners.add(listener);
561
+ return () => {
562
+ listeners.delete(listener);
563
+ };
564
+ }
565
+ }
566
+ };
567
+ return {
568
+ version: "1.0.0",
569
+ name: options.name ?? "Chipi",
570
+ icon: options.icon ?? CHIPI_ICON,
571
+ chains: [CHIPI_STARKNET_MAINNET_CHAIN],
572
+ get accounts() {
573
+ return accounts();
574
+ },
575
+ features
576
+ };
577
+ }
578
+ function registerChipiStandardWallet(options = {}, target = typeof window !== "undefined" ? window : void 0) {
579
+ if (!target) return () => {
580
+ };
581
+ const wallet = createChipiStandardWallet(options);
582
+ const unregisters = [];
583
+ const callback = ({ register }) => {
584
+ unregisters.push(register(wallet));
585
+ };
586
+ const onAppReady = (event) => {
587
+ const api = event.detail;
588
+ if (api && typeof api.register === "function") callback(api);
589
+ };
590
+ try {
591
+ target.dispatchEvent(
592
+ new CustomEvent("wallet-standard:register-wallet", {
593
+ detail: callback,
594
+ bubbles: false,
595
+ cancelable: false,
596
+ composed: false
597
+ })
598
+ );
599
+ } catch (error) {
600
+ console.error("wallet-standard:register-wallet event could not be dispatched\n", error);
601
+ }
602
+ target.addEventListener("wallet-standard:app-ready", onAppReady);
603
+ return () => {
604
+ target.removeEventListener("wallet-standard:app-ready", onAppReady);
605
+ for (const unregister of unregisters.splice(0)) unregister();
606
+ };
607
+ }
608
+
609
+ export { CHIPI_STARKNET_MAINNET_CHAIN, CHIPI_WC, CHIPI_WINDOW_KEY, ChipiConnector, ChipiPopupTransport, chipi, createChipiStandardWallet, isChipiDappMessage, isChipiWalletMessage, registerChipiStandardWallet, registerChipiWallet };
327
610
  //# sourceMappingURL=index.js.map
328
611
  //# sourceMappingURL=index.js.map