@partylayer/sdk 0.2.5 → 0.2.6

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.mjs CHANGED
@@ -1,5 +1,5 @@
1
- import { mapUnknownErrorToPartyLayerError, WalletNotFoundError, capabilityGuard, installGuard, toSessionId, CapabilityNotSupportedError } from '@partylayer/core';
2
- export { PartyLayerError as CantonConnectError, CapabilityNotSupportedError, InternalError, OriginNotAllowedError, PartyLayerError, RegistryFetchFailedError, RegistrySchemaInvalidError, RegistryVerificationFailedError, SessionExpiredError, TimeoutError, TransportError, UserRejectedError, WalletNotFoundError, WalletNotInstalledError } from '@partylayer/core';
1
+ import { hashForPrivacy, createMetricsPayload, mapUnknownErrorToPartyLayerError, METRICS, WalletNotFoundError, capabilityGuard, installGuard, toSessionId, CapabilityNotSupportedError, errorMetricName } from '@partylayer/core';
2
+ export { PartyLayerError as CantonConnectError, CapabilityNotSupportedError, ENABLEMENT_METRICS, ERROR_METRICS, InternalError, METRICS, OriginNotAllowedError, PartyLayerError, REGISTRY_METRICS, RegistryFetchFailedError, RegistrySchemaInvalidError, RegistryVerificationFailedError, SessionExpiredError, TimeoutError, TransportError, UserRejectedError, WalletNotFoundError, WalletNotInstalledError, errorMetricName } from '@partylayer/core';
3
3
  import { RegistryClient } from '@partylayer/registry-client';
4
4
  import { ConsoleAdapter } from '@partylayer/adapter-console';
5
5
  export { ConsoleAdapter } from '@partylayer/adapter-console';
@@ -7,9 +7,17 @@ import { LoopAdapter } from '@partylayer/adapter-loop';
7
7
  export { LoopAdapter } from '@partylayer/adapter-loop';
8
8
  import { Cantor8Adapter } from '@partylayer/adapter-cantor8';
9
9
  export { Cantor8Adapter } from '@partylayer/adapter-cantor8';
10
+ import { NightlyAdapter } from '@partylayer/adapter-nightly';
11
+ export { NightlyAdapter } from '@partylayer/adapter-nightly';
10
12
  export { BronAdapter } from '@partylayer/adapter-bron';
13
+ export { CANTON_NETWORKS, CIP0103EventBus, CIP0103_EVENTS, CIP0103_MANDATORY_METHODS, CIP0103_METHODS, JSON_RPC_ERRORS, PartyLayerProvider, ProviderRpcError, RPC_ERRORS, createProviderBridge, discoverInjectedProviders, fromCAIP2Network, isCIP0103Provider, isValidCAIP2, toCAIP2Network, waitForProvider } from '@partylayer/provider';
11
14
 
12
- // src/client.ts
15
+ var __require = /* @__PURE__ */ ((x) => typeof require !== "undefined" ? require : typeof Proxy !== "undefined" ? new Proxy(x, {
16
+ get: (a, b) => (typeof require !== "undefined" ? require : a)[b]
17
+ }) : x)(function(x) {
18
+ if (typeof require !== "undefined") return require.apply(this, arguments);
19
+ throw Error('Dynamic require of "' + x + '" is not supported');
20
+ });
13
21
 
14
22
  // src/config.ts
15
23
  var DEFAULT_REGISTRY_URL = "https://registry.partylayer.xyz";
@@ -168,12 +176,196 @@ function getBuiltinAdapters() {
168
176
  // Console Wallet - browser extension
169
177
  new LoopAdapter(),
170
178
  // 5N Loop - QR code / popup
171
- new Cantor8Adapter()
179
+ new Cantor8Adapter(),
172
180
  // Cantor8 - deep link transport
181
+ new NightlyAdapter()
182
+ // Nightly - multichain wallet (injected)
173
183
  ];
174
184
  }
175
-
176
- // src/client.ts
185
+ var SDK_VERSION = "0.3.0";
186
+ var MetricsTelemetryAdapter = class {
187
+ config;
188
+ metrics = /* @__PURE__ */ new Map();
189
+ buffer = [];
190
+ flushTimer = null;
191
+ appIdHash = null;
192
+ originHash = null;
193
+ initialized = false;
194
+ constructor(config) {
195
+ this.config = {
196
+ enabled: config.enabled ?? false,
197
+ endpoint: config.endpoint,
198
+ sampleRate: config.sampleRate ?? 1,
199
+ appId: config.appId,
200
+ includeOrigin: config.includeOrigin ?? false,
201
+ batchSize: config.batchSize ?? 10,
202
+ flushIntervalMs: config.flushIntervalMs ?? 3e4,
203
+ network: config.network
204
+ };
205
+ this.initialize();
206
+ }
207
+ /**
208
+ * Initialize async components (hashing)
209
+ */
210
+ async initialize() {
211
+ if (this.initialized) return;
212
+ if (this.config.appId) {
213
+ this.appIdHash = await hashForPrivacy(this.config.appId);
214
+ }
215
+ if (this.config.includeOrigin && typeof window !== "undefined") {
216
+ this.originHash = await hashForPrivacy(window.location.origin);
217
+ }
218
+ if (this.config.enabled && this.config.flushIntervalMs) {
219
+ this.flushTimer = setInterval(() => {
220
+ this.flush().catch(console.error);
221
+ }, this.config.flushIntervalMs);
222
+ }
223
+ this.initialized = true;
224
+ }
225
+ /**
226
+ * Check if telemetry is enabled
227
+ */
228
+ isEnabled() {
229
+ return this.config.enabled;
230
+ }
231
+ /**
232
+ * Track a named event (TelemetryAdapter interface)
233
+ */
234
+ track(event, properties) {
235
+ if (!this.config.enabled) return;
236
+ const sampleRate = this.config.sampleRate ?? 1;
237
+ if (sampleRate < 1 && Math.random() >= sampleRate) {
238
+ return;
239
+ }
240
+ const current = this.metrics.get(`event_${event}`) ?? 0;
241
+ this.metrics.set(`event_${event}`, current + 1);
242
+ this.bufferEvent({ type: "increment", metric: `event_${event}`, value: 1, timestamp: Date.now() });
243
+ if (properties) {
244
+ for (const [key, value] of Object.entries(properties)) {
245
+ if (typeof value === "number") {
246
+ this.gauge(`${event}_${key}`, value);
247
+ }
248
+ }
249
+ }
250
+ }
251
+ /**
252
+ * Track an error (TelemetryAdapter interface)
253
+ */
254
+ error(error, properties) {
255
+ if (!this.config.enabled) return;
256
+ const code = error.code ?? "UNKNOWN";
257
+ this.increment(`error_${code}`);
258
+ if (properties) {
259
+ this.track("error", properties);
260
+ }
261
+ }
262
+ /**
263
+ * Increment a metric counter
264
+ */
265
+ increment(metric, value = 1) {
266
+ if (!this.config.enabled) return;
267
+ const sampleRate = this.config.sampleRate ?? 1;
268
+ if (sampleRate < 1 && Math.random() >= sampleRate) {
269
+ return;
270
+ }
271
+ const current = this.metrics.get(metric) ?? 0;
272
+ this.metrics.set(metric, current + value);
273
+ this.bufferEvent({
274
+ type: "increment",
275
+ metric,
276
+ value,
277
+ timestamp: Date.now()
278
+ });
279
+ }
280
+ /**
281
+ * Set a gauge metric value
282
+ */
283
+ gauge(metric, value) {
284
+ if (!this.config.enabled) return;
285
+ this.metrics.set(metric, value);
286
+ this.bufferEvent({
287
+ type: "gauge",
288
+ metric,
289
+ value,
290
+ timestamp: Date.now()
291
+ });
292
+ }
293
+ /**
294
+ * Buffer an event for batched sending
295
+ */
296
+ bufferEvent(event) {
297
+ this.buffer.push(event);
298
+ if (this.config.batchSize && this.buffer.length >= this.config.batchSize) {
299
+ this.flush().catch(console.error);
300
+ }
301
+ }
302
+ /**
303
+ * Flush buffered metrics to backend
304
+ */
305
+ async flush() {
306
+ if (!this.config.enabled) return;
307
+ if (this.metrics.size === 0) return;
308
+ if (!this.config.endpoint) {
309
+ this.buffer = [];
310
+ return;
311
+ }
312
+ if (!this.initialized) {
313
+ await this.initialize();
314
+ }
315
+ try {
316
+ const payload = createMetricsPayload({
317
+ sdkVersion: SDK_VERSION,
318
+ network: this.config.network ?? "unknown",
319
+ metrics: Object.fromEntries(this.metrics),
320
+ appIdHash: this.appIdHash ?? void 0,
321
+ originHash: this.originHash ?? void 0
322
+ });
323
+ await fetch(this.config.endpoint, {
324
+ method: "POST",
325
+ headers: {
326
+ "Content-Type": "application/json"
327
+ },
328
+ body: JSON.stringify(payload)
329
+ });
330
+ this.buffer = [];
331
+ } catch (error) {
332
+ console.debug("[PartyLayer Telemetry] Flush failed:", error);
333
+ }
334
+ }
335
+ /**
336
+ * Get current metrics snapshot (for testing/debugging)
337
+ */
338
+ getMetrics() {
339
+ return Object.fromEntries(this.metrics);
340
+ }
341
+ /**
342
+ * Reset all metrics (for testing)
343
+ */
344
+ reset() {
345
+ this.metrics.clear();
346
+ this.buffer = [];
347
+ }
348
+ /**
349
+ * Destroy the adapter (cleanup timers)
350
+ */
351
+ destroy() {
352
+ if (this.flushTimer) {
353
+ clearInterval(this.flushTimer);
354
+ this.flushTimer = null;
355
+ }
356
+ this.flush().catch(console.error);
357
+ }
358
+ };
359
+ function isTelemetryConfig(value) {
360
+ return typeof value === "object" && value !== null && "enabled" in value && typeof value.enabled === "boolean";
361
+ }
362
+ function createTelemetryAdapter(config) {
363
+ if (!config) return void 0;
364
+ if (isTelemetryConfig(config)) {
365
+ return new MetricsTelemetryAdapter(config);
366
+ }
367
+ return config;
368
+ }
177
369
  var PartyLayerClient = class {
178
370
  config;
179
371
  adapters = /* @__PURE__ */ new Map();
@@ -198,7 +390,8 @@ var PartyLayerClient = class {
198
390
  this.logger = config.logger || new DefaultLogger();
199
391
  this.crypto = config.crypto || new DefaultCrypto();
200
392
  this.storage = config.storage || new DefaultStorage();
201
- this.telemetry = config.telemetry || new DefaultTelemetry();
393
+ const telemetryAdapter = createTelemetryAdapter(config.telemetry);
394
+ this.telemetry = telemetryAdapter || new DefaultTelemetry();
202
395
  const adaptersToRegister = config.adapters ?? getBuiltinAdapters();
203
396
  for (const adapterOrClass of adaptersToRegister) {
204
397
  let adapter;
@@ -244,33 +437,49 @@ var PartyLayerClient = class {
244
437
  * List available wallets
245
438
  */
246
439
  async listWallets(filter) {
440
+ let registryWallets;
247
441
  try {
248
- const allWalletInfos = await this.registryClient.getWallets();
442
+ registryWallets = await this.registryClient.getWallets();
249
443
  this.updateRegistryStatus();
250
- if (filter?.requiredCapabilities) {
251
- return allWalletInfos.filter(
252
- (walletInfo) => filter.requiredCapabilities.every(
253
- (cap) => walletInfo.capabilities.includes(cap)
254
- )
255
- );
256
- }
257
- if (!filter?.includeExperimental) {
258
- return allWalletInfos.filter((walletInfo) => walletInfo.channel === "stable");
259
- }
260
- return allWalletInfos;
261
444
  } catch (err) {
262
445
  this.updateRegistryStatus();
263
- const error = mapUnknownErrorToPartyLayerError(err, {
264
- phase: "connect"
446
+ this.logger.warn("Registry fetch failed, using registered adapters only", {
447
+ error: err instanceof Error ? err.message : String(err)
448
+ });
449
+ registryWallets = [];
450
+ }
451
+ const registryIds = new Set(registryWallets.map((w) => String(w.walletId)));
452
+ for (const [, adapter] of this.adapters) {
453
+ if (registryIds.has(String(adapter.walletId))) continue;
454
+ registryWallets.push({
455
+ walletId: adapter.walletId,
456
+ name: adapter.name,
457
+ website: "",
458
+ icons: {},
459
+ capabilities: adapter.getCapabilities(),
460
+ adapter: { packageName: "builtin", versionRange: "*" },
461
+ docs: [],
462
+ networks: [this.config.network || "devnet"],
463
+ channel: "stable"
265
464
  });
266
- this.emit("error", { type: "error", error });
267
- throw error;
268
465
  }
466
+ if (filter?.requiredCapabilities) {
467
+ return registryWallets.filter(
468
+ (walletInfo) => filter.requiredCapabilities.every(
469
+ (cap) => walletInfo.capabilities.includes(cap)
470
+ )
471
+ );
472
+ }
473
+ if (!filter?.includeExperimental) {
474
+ return registryWallets.filter((walletInfo) => walletInfo.channel === "stable");
475
+ }
476
+ return registryWallets;
269
477
  }
270
478
  /**
271
479
  * Connect to a wallet
272
480
  */
273
481
  async connect(options) {
482
+ this.telemetry?.increment?.(METRICS.WALLET_CONNECT_ATTEMPTS);
274
483
  try {
275
484
  const wallets = await this.listWallets({
276
485
  requiredCapabilities: options?.requiredCapabilities,
@@ -282,31 +491,33 @@ var PartyLayerClient = class {
282
491
  (w) => options.allowWallets.includes(w.walletId)
283
492
  );
284
493
  }
285
- if (options?.preferInstalled) {
286
- const installedWallets = [];
287
- const notInstalledWallets = [];
288
- for (const wallet of availableWallets) {
289
- const adapter2 = this.adapters.get(wallet.walletId);
290
- if (adapter2) {
291
- const detect = await adapter2.detectInstalled();
292
- if (detect.installed) {
293
- installedWallets.push(wallet);
294
- } else {
295
- notInstalledWallets.push(wallet);
296
- }
297
- } else {
298
- notInstalledWallets.push(wallet);
299
- }
300
- }
301
- availableWallets = [...installedWallets, ...notInstalledWallets];
302
- }
303
494
  let selectedWallet;
495
+ let isNativeWallet = false;
304
496
  if (options?.walletId) {
305
- selectedWallet = availableWallets.find(
497
+ const found = availableWallets.find(
306
498
  (w) => w.walletId === options.walletId
307
499
  );
308
- if (!selectedWallet) {
309
- throw new WalletNotFoundError(String(options.walletId));
500
+ if (found) {
501
+ selectedWallet = found;
502
+ } else {
503
+ const nativeAdapter = this.adapters.get(options.walletId);
504
+ if (nativeAdapter) {
505
+ isNativeWallet = true;
506
+ selectedWallet = {
507
+ walletId: options.walletId,
508
+ name: nativeAdapter.name,
509
+ website: "",
510
+ icons: {},
511
+ capabilities: nativeAdapter.getCapabilities(),
512
+ adapter: { packageName: "native-cip0103", versionRange: "*" },
513
+ docs: [],
514
+ networks: [this.config.network],
515
+ channel: "stable",
516
+ metadata: { source: "native-cip0103" }
517
+ };
518
+ } else {
519
+ throw new WalletNotFoundError(String(options.walletId));
520
+ }
310
521
  }
311
522
  } else if (availableWallets.length === 0) {
312
523
  throw new WalletNotFoundError("No wallets available");
@@ -317,14 +528,22 @@ var PartyLayerClient = class {
317
528
  if (!adapter) {
318
529
  throw new WalletNotFoundError(String(selectedWallet.walletId));
319
530
  }
320
- const walletEntry = await this.registryClient.getWalletEntry(String(selectedWallet.walletId));
321
- if (walletEntry.originAllowlist && walletEntry.originAllowlist.length > 0) {
322
- if (!walletEntry.originAllowlist.includes(this.origin)) {
323
- const { OriginNotAllowedError: OriginNotAllowedError2 } = await import('@partylayer/core');
324
- throw new OriginNotAllowedError2(
325
- this.origin,
326
- walletEntry.originAllowlist
327
- );
531
+ if (!isNativeWallet) {
532
+ try {
533
+ const walletEntry = await this.registryClient.getWalletEntry(String(selectedWallet.walletId));
534
+ if (walletEntry.originAllowlist && walletEntry.originAllowlist.length > 0) {
535
+ if (!walletEntry.originAllowlist.includes(this.origin)) {
536
+ const { OriginNotAllowedError: OriginNotAllowedError2 } = await import('@partylayer/core');
537
+ throw new OriginNotAllowedError2(
538
+ this.origin,
539
+ walletEntry.originAllowlist
540
+ );
541
+ }
542
+ }
543
+ } catch (e) {
544
+ if (!(e instanceof WalletNotFoundError)) {
545
+ throw e;
546
+ }
328
547
  }
329
548
  }
330
549
  if (options?.requiredCapabilities) {
@@ -358,6 +577,8 @@ var PartyLayerClient = class {
358
577
  await this.persistSession(session);
359
578
  this.activeSession = session;
360
579
  this.updateRegistryStatus();
580
+ this.telemetry?.increment?.(METRICS.WALLET_CONNECT_SUCCESS);
581
+ this.telemetry?.increment?.(METRICS.SESSIONS_CREATED);
361
582
  this.emit("session:connected", {
362
583
  type: "session:connected",
363
584
  session
@@ -517,6 +738,33 @@ var PartyLayerClient = class {
517
738
  throw error;
518
739
  }
519
740
  }
741
+ /**
742
+ * Proxy a JSON Ledger API request through the active wallet adapter
743
+ */
744
+ async ledgerApi(params) {
745
+ const session = await this.getActiveSession();
746
+ if (!session) {
747
+ throw new Error("No active session");
748
+ }
749
+ const adapter = this.adapters.get(session.walletId);
750
+ if (!adapter || !adapter.ledgerApi) {
751
+ throw new CapabilityNotSupportedError(
752
+ session.walletId,
753
+ "ledgerApi"
754
+ );
755
+ }
756
+ try {
757
+ const ctx = this.createAdapterContext();
758
+ return await adapter.ledgerApi(ctx, session, params);
759
+ } catch (err) {
760
+ const error = mapUnknownErrorToPartyLayerError(err, {
761
+ phase: "ledgerApi",
762
+ walletId: String(session.walletId)
763
+ });
764
+ this.emit("error", { type: "error", error });
765
+ throw error;
766
+ }
767
+ }
520
768
  /**
521
769
  * Subscribe to events
522
770
  */
@@ -538,10 +786,36 @@ var PartyLayerClient = class {
538
786
  handlers.delete(handler);
539
787
  }
540
788
  }
789
+ /**
790
+ * Get a CIP-0103 Provider backed by this client.
791
+ *
792
+ * This bridge routes all request() calls through the existing
793
+ * PartyLayerClient methods and maps events to CIP-0103 format.
794
+ *
795
+ * The bridge implements the full CIP-0103 specification:
796
+ * - All 10 mandatory methods including `ledgerApi` (when adapter supports it)
797
+ * - Full transaction lifecycle: pending -> signed -> executed/failed
798
+ * - All CIP-0103 events: statusChanged, accountsChanged, txChanged, connected
799
+ *
800
+ * **Note:** Async wallets (userUrl pattern) are not supported through the
801
+ * bridge. For async wallet support, use `PartyLayerProvider` directly.
802
+ *
803
+ * @returns CIP-0103 compliant Provider
804
+ */
805
+ asProvider() {
806
+ const { createProviderBridge: createProviderBridge2 } = __require("@partylayer/provider");
807
+ return createProviderBridge2(this);
808
+ }
541
809
  /**
542
810
  * Destroy client and cleanup
543
811
  */
544
812
  destroy() {
813
+ if (this.telemetry && "destroy" in this.telemetry && typeof this.telemetry.destroy === "function") {
814
+ this.telemetry.destroy();
815
+ } else if (this.telemetry?.flush) {
816
+ this.telemetry.flush().catch(() => {
817
+ });
818
+ }
545
819
  this.eventHandlers.clear();
546
820
  this.activeSession = null;
547
821
  }
@@ -595,6 +869,7 @@ var PartyLayerClient = class {
595
869
  * Restore session from storage
596
870
  */
597
871
  async restoreSession() {
872
+ this.telemetry?.increment?.(METRICS.RESTORE_ATTEMPTS);
598
873
  try {
599
874
  const encrypted = await this.storage.get("active_session");
600
875
  if (!encrypted) {
@@ -619,6 +894,8 @@ var PartyLayerClient = class {
619
894
  if (restored) {
620
895
  this.activeSession = restored;
621
896
  await this.persistSession(restored);
897
+ this.telemetry?.increment?.(METRICS.SESSIONS_RESTORED);
898
+ this.telemetry?.increment?.(METRICS.WALLET_CONNECT_SUCCESS);
622
899
  this.emit("session:connected", {
623
900
  type: "session:connected",
624
901
  session: restored
@@ -646,6 +923,14 @@ var PartyLayerClient = class {
646
923
  updateRegistryStatus() {
647
924
  const status = this.registryClient.getStatus();
648
925
  if (status) {
926
+ if (status.source === "network") {
927
+ this.telemetry?.increment?.(METRICS.REGISTRY_FETCH);
928
+ } else if (status.source === "cache") {
929
+ this.telemetry?.increment?.(METRICS.REGISTRY_CACHE_HIT);
930
+ }
931
+ if (status.stale) {
932
+ this.telemetry?.increment?.(METRICS.REGISTRY_STALE);
933
+ }
649
934
  this.emit("registry:status", {
650
935
  type: "registry:status",
651
936
  status: {
@@ -671,6 +956,12 @@ var PartyLayerClient = class {
671
956
  * Emit event to handlers
672
957
  */
673
958
  emit(event, payload) {
959
+ if (event === "error" && "error" in payload) {
960
+ const error = payload.error;
961
+ if (error.code) {
962
+ this.telemetry?.increment?.(errorMetricName(error.code));
963
+ }
964
+ }
674
965
  const handlers = this.eventHandlers.get(event);
675
966
  if (handlers) {
676
967
  for (const handler of handlers) {
@@ -687,6 +978,6 @@ function createPartyLayer(config) {
687
978
  return new PartyLayerClient(config);
688
979
  }
689
980
 
690
- export { PartyLayerClient as CantonConnectClient, DEFAULT_REGISTRY_URL, PartyLayerClient, createPartyLayer as createCantonConnect, createPartyLayer, getBuiltinAdapters };
981
+ export { PartyLayerClient as CantonConnectClient, DEFAULT_REGISTRY_URL, MetricsTelemetryAdapter, PartyLayerClient, createPartyLayer as createCantonConnect, createPartyLayer, createTelemetryAdapter, getBuiltinAdapters, isTelemetryConfig };
691
982
  //# sourceMappingURL=index.mjs.map
692
983
  //# sourceMappingURL=index.mjs.map