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