@partylayer/provider 0.5.2 → 0.6.1

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
@@ -18,7 +18,7 @@
18
18
 
19
19
  ### Features
20
20
 
21
- - **CIP-0103 Compliant**: All 10 mandatory methods implemented (`connect`, `disconnect`, `isConnected`, `status`, `getActiveNetwork`, `listAccounts`, `getPrimaryAccount`, `signMessage`, `prepareExecute`, `ledgerApi`)
21
+ - **Provider request surface**: Handles the ten method names according to backing wallet capabilities (`connect`, `disconnect`, `isConnected`, `status`, `getActiveNetwork`, `listAccounts`, `getPrimaryAccount`, `signMessage`, `prepareExecute`, `ledgerApi`)
22
22
  - **Wallet Discovery**: Automatic scanning for injected CIP-0103 providers at `window.canton.*`
23
23
  - **Async Wallet Support**: Handles both synchronous (browser extension) and asynchronous (mobile/QR) wallet flows
24
24
  - **Standard Error Model**: `ProviderRpcError` with EIP-1193 / EIP-1474 numeric codes
@@ -58,7 +58,15 @@ const result = await provider.request({ method: 'connect' });
58
58
  const account = await provider.request({ method: 'getPrimaryAccount' });
59
59
  ```
60
60
 
61
- ### Legacy Bridge (with PartyLayerClient)
61
+ ### Client compatibility bridge
62
+
63
+ The bridge projects the active SDK party, retains the legacy `{ response: string }`
64
+ Ledger API result, and emits transaction states only with genuine evidence. It does
65
+ not establish full account/key enumeration or complete native protocol ownership.
66
+ See [alignment and limits](../../docs/CIP-0103-ALIGNMENT.md) before relying on it
67
+ as an exact standard wire implementation.
68
+
69
+ Existing low-level composition:
62
70
 
63
71
  ```typescript
64
72
  import { createProviderBridge } from '@partylayer/provider';
@@ -67,8 +75,10 @@ import { createPartyLayer } from '@partylayer/sdk';
67
75
  const client = createPartyLayer({ network: 'devnet' });
68
76
  const provider = createProviderBridge(client);
69
77
 
70
- // Use standard CIP-0103 interface
71
- await provider.request({ method: 'connect', params: { walletId: 'console' } });
78
+ // Choose a wallet through the SDK's documented application interface.
79
+ await client.connect({ walletId: 'console' });
80
+ // Then use the supported provider requests.
81
+ await provider.request({ method: 'status' });
72
82
  ```
73
83
 
74
84
  ---
package/dist/index.d.mts CHANGED
@@ -255,17 +255,21 @@ interface BridgeableClient {
255
255
  signTransaction(params: {
256
256
  tx: unknown;
257
257
  }): Promise<{
258
- transactionHash: unknown;
258
+ transactionHash?: unknown;
259
259
  signedTx?: unknown;
260
260
  partyId?: unknown;
261
+ /** Actual signature metadata, never a transaction hash. */
262
+ signature?: unknown;
263
+ signedBy?: unknown;
261
264
  }>;
262
265
  submitTransaction(params: {
263
266
  signedTx: unknown;
264
267
  }): Promise<{
265
- transactionHash: unknown;
268
+ transactionHash?: unknown;
266
269
  submittedAt?: number;
267
270
  commandId?: string;
268
271
  updateId?: string;
272
+ completionOffset?: number;
269
273
  }>;
270
274
  ledgerApi?(params: {
271
275
  requestMethod: string;
@@ -396,11 +400,15 @@ interface ExtensionChannelOptions {
396
400
  /** Per-request timeout in ms. Default 30000. */
397
401
  timeoutMs?: number;
398
402
  }
403
+ interface ExtensionChannelProvider extends CIP0103Provider {
404
+ /** Remove the channel listener, reject pending RPCs and release subscriptions. */
405
+ destroy(): void;
406
+ }
399
407
  /**
400
408
  * Create a CIP-0103 provider that talks the splice-wallet postMessage protocol
401
409
  * over the page `window`, routed by `target`.
402
410
  */
403
- declare function createExtensionChannelProvider(options?: ExtensionChannelOptions): CIP0103Provider;
411
+ declare function createExtensionChannelProvider(options?: ExtensionChannelOptions): ExtensionChannelProvider;
404
412
 
405
413
  /**
406
414
  * Async Wallet Handling
@@ -496,4 +504,4 @@ declare class MethodRouter {
496
504
  private registerDefaults;
497
505
  }
498
506
 
499
- export { type AnnounceDiscoveryOptions, type AnnounceSubscribeOptions, type AnnouncedWallet, type AsyncConnectOptions, type AsyncPrepareExecuteOptions, type BridgeableClient, CIP0103EventBus, type DiscoveredProvider, type ExtensionChannelOptions, JSON_RPC_ERRORS, type MethodHandler, MethodRouter, PartyLayerProvider, type PartyLayerProviderOptions, ProviderRpcError, RPC_ERRORS, type WaitForAnnouncedOptions, chainDisconnected, createExtensionChannelProvider, createProviderBridge, disconnected, discoverAnnouncedProviders, discoverInjectedProviders, discoverProviders, handleAsyncConnect, handleAsyncPrepareExecute, internalError, invalidParams, isCIP0103Provider, methodNotFound, resourceNotFound, resourceUnavailable, subscribeAnnouncedProviders, toPartyLayerError, toProviderRpcError, transactionRejected, unauthorized, unsupportedMethod, userRejected, waitForAnnouncedProvider, waitForProvider };
507
+ export { type AnnounceDiscoveryOptions, type AnnounceSubscribeOptions, type AnnouncedWallet, type AsyncConnectOptions, type AsyncPrepareExecuteOptions, type BridgeableClient, CIP0103EventBus, type DiscoveredProvider, type ExtensionChannelOptions, type ExtensionChannelProvider, JSON_RPC_ERRORS, type MethodHandler, MethodRouter, PartyLayerProvider, type PartyLayerProviderOptions, ProviderRpcError, RPC_ERRORS, type WaitForAnnouncedOptions, chainDisconnected, createExtensionChannelProvider, createProviderBridge, disconnected, discoverAnnouncedProviders, discoverInjectedProviders, discoverProviders, handleAsyncConnect, handleAsyncPrepareExecute, internalError, invalidParams, isCIP0103Provider, methodNotFound, resourceNotFound, resourceUnavailable, subscribeAnnouncedProviders, toPartyLayerError, toProviderRpcError, transactionRejected, unauthorized, unsupportedMethod, userRejected, waitForAnnouncedProvider, waitForProvider };
package/dist/index.d.ts CHANGED
@@ -255,17 +255,21 @@ interface BridgeableClient {
255
255
  signTransaction(params: {
256
256
  tx: unknown;
257
257
  }): Promise<{
258
- transactionHash: unknown;
258
+ transactionHash?: unknown;
259
259
  signedTx?: unknown;
260
260
  partyId?: unknown;
261
+ /** Actual signature metadata, never a transaction hash. */
262
+ signature?: unknown;
263
+ signedBy?: unknown;
261
264
  }>;
262
265
  submitTransaction(params: {
263
266
  signedTx: unknown;
264
267
  }): Promise<{
265
- transactionHash: unknown;
268
+ transactionHash?: unknown;
266
269
  submittedAt?: number;
267
270
  commandId?: string;
268
271
  updateId?: string;
272
+ completionOffset?: number;
269
273
  }>;
270
274
  ledgerApi?(params: {
271
275
  requestMethod: string;
@@ -396,11 +400,15 @@ interface ExtensionChannelOptions {
396
400
  /** Per-request timeout in ms. Default 30000. */
397
401
  timeoutMs?: number;
398
402
  }
403
+ interface ExtensionChannelProvider extends CIP0103Provider {
404
+ /** Remove the channel listener, reject pending RPCs and release subscriptions. */
405
+ destroy(): void;
406
+ }
399
407
  /**
400
408
  * Create a CIP-0103 provider that talks the splice-wallet postMessage protocol
401
409
  * over the page `window`, routed by `target`.
402
410
  */
403
- declare function createExtensionChannelProvider(options?: ExtensionChannelOptions): CIP0103Provider;
411
+ declare function createExtensionChannelProvider(options?: ExtensionChannelOptions): ExtensionChannelProvider;
404
412
 
405
413
  /**
406
414
  * Async Wallet Handling
@@ -496,4 +504,4 @@ declare class MethodRouter {
496
504
  private registerDefaults;
497
505
  }
498
506
 
499
- export { type AnnounceDiscoveryOptions, type AnnounceSubscribeOptions, type AnnouncedWallet, type AsyncConnectOptions, type AsyncPrepareExecuteOptions, type BridgeableClient, CIP0103EventBus, type DiscoveredProvider, type ExtensionChannelOptions, JSON_RPC_ERRORS, type MethodHandler, MethodRouter, PartyLayerProvider, type PartyLayerProviderOptions, ProviderRpcError, RPC_ERRORS, type WaitForAnnouncedOptions, chainDisconnected, createExtensionChannelProvider, createProviderBridge, disconnected, discoverAnnouncedProviders, discoverInjectedProviders, discoverProviders, handleAsyncConnect, handleAsyncPrepareExecute, internalError, invalidParams, isCIP0103Provider, methodNotFound, resourceNotFound, resourceUnavailable, subscribeAnnouncedProviders, toPartyLayerError, toProviderRpcError, transactionRejected, unauthorized, unsupportedMethod, userRejected, waitForAnnouncedProvider, waitForProvider };
507
+ export { type AnnounceDiscoveryOptions, type AnnounceSubscribeOptions, type AnnouncedWallet, type AsyncConnectOptions, type AsyncPrepareExecuteOptions, type BridgeableClient, CIP0103EventBus, type DiscoveredProvider, type ExtensionChannelOptions, type ExtensionChannelProvider, JSON_RPC_ERRORS, type MethodHandler, MethodRouter, PartyLayerProvider, type PartyLayerProviderOptions, ProviderRpcError, RPC_ERRORS, type WaitForAnnouncedOptions, chainDisconnected, createExtensionChannelProvider, createProviderBridge, disconnected, discoverAnnouncedProviders, discoverInjectedProviders, discoverProviders, handleAsyncConnect, handleAsyncPrepareExecute, internalError, invalidParams, isCIP0103Provider, methodNotFound, resourceNotFound, resourceUnavailable, subscribeAnnouncedProviders, toPartyLayerError, toProviderRpcError, transactionRejected, unauthorized, unsupportedMethod, userRejected, waitForAnnouncedProvider, waitForProvider };
package/dist/index.js CHANGED
@@ -514,7 +514,7 @@ var PartyLayerProvider = class {
514
514
  };
515
515
 
516
516
  // src/bridge.ts
517
- var PROVIDER_VERSION = "0.5.2" ;
517
+ var PROVIDER_VERSION = "0.6.1" ;
518
518
  function createProviderBridge(client) {
519
519
  const eventBus = new CIP0103EventBus();
520
520
  const pendingEmitted = /* @__PURE__ */ new Set();
@@ -579,9 +579,8 @@ async function handleRequest(client, args, eventBus, pendingEmitted) {
579
579
  }
580
580
  case "getActiveNetwork": {
581
581
  const session = await client.getActiveSession();
582
- return core.toCAIP2Network(
583
- session?.network ?? "devnet"
584
- );
582
+ if (!session) throw disconnected("No active session");
583
+ return core.toCAIP2Network(session.network);
585
584
  }
586
585
  case "listAccounts": {
587
586
  const session = await client.getActiveSession();
@@ -606,55 +605,42 @@ async function handleRequest(client, args, eventBus, pendingEmitted) {
606
605
  }
607
606
  case "prepareExecute": {
608
607
  const p = normalizeParams(params);
609
- const cmdId = `cmd_${Date.now()}_${Math.random().toString(36).slice(2, 10)}`;
608
+ const session = await client.getActiveSession();
609
+ if (!session) throw disconnected("No active session");
610
+ const cmdId = nonempty(p.commandId) ? p.commandId : `cmd_${Date.now()}_${Math.random().toString(36).slice(2, 10)}`;
611
+ if (pendingEmitted.has(cmdId)) throw resourceUnavailable("This commandId is already pending");
612
+ const payload = { ...p, commandId: cmdId };
610
613
  pendingEmitted.add(cmdId);
611
- eventBus.emit(core.CIP0103_EVENTS.TX_CHANGED, {
612
- status: "pending",
613
- commandId: cmdId
614
- });
614
+ eventBus.emit(core.CIP0103_EVENTS.TX_CHANGED, { status: "pending", commandId: cmdId });
615
+ let submitting = false;
615
616
  try {
616
- const signResult = await client.signTransaction({ tx: p });
617
- const session = await client.getActiveSession();
618
- const partyId = String(signResult.partyId ?? session?.partyId ?? "unknown");
619
- eventBus.emit(core.CIP0103_EVENTS.TX_CHANGED, {
620
- status: "signed",
621
- commandId: cmdId,
622
- payload: {
623
- signature: String(signResult.transactionHash),
624
- signedBy: partyId,
625
- party: partyId
617
+ let signedTx = payload;
618
+ if (!session.capabilitiesSnapshot || session.capabilitiesSnapshot.includes("signTransaction")) {
619
+ const signed = await client.signTransaction({ tx: payload });
620
+ if (signed.partyId !== void 0 && signed.partyId !== session.partyId) {
621
+ throw disconnected("Wallet account changed during signing");
626
622
  }
627
- });
628
- try {
629
- const receipt = await client.submitTransaction({
630
- signedTx: signResult.signedTx ?? signResult.transactionHash
631
- });
632
- pendingEmitted.delete(cmdId);
633
- eventBus.emit(core.CIP0103_EVENTS.TX_CHANGED, {
634
- status: "executed",
635
- commandId: cmdId,
636
- payload: {
637
- updateId: receipt.updateId ?? receipt.commandId ?? String(receipt.transactionHash),
638
- completionOffset: 0
639
- }
640
- });
641
- } catch (submitErr) {
642
- pendingEmitted.delete(cmdId);
643
- eventBus.emit(core.CIP0103_EVENTS.TX_CHANGED, {
644
- status: "failed",
645
- commandId: cmdId
646
- });
647
- throw submitErr;
623
+ signedTx = signed.signedTx;
624
+ if (signedTx === void 0) throw new Error("Wallet returned no signed transaction");
625
+ const event2 = signedEvent({ signature: signed.signature, signedBy: signed.signedBy, party: signed.partyId }, cmdId);
626
+ if (event2) eventBus.emit(core.CIP0103_EVENTS.TX_CHANGED, event2);
627
+ }
628
+ const current = await client.getActiveSession();
629
+ if (!current || current.sessionId !== session.sessionId || current.partyId !== session.partyId || current.network !== session.network) {
630
+ throw disconnected("Wallet session changed before submission");
648
631
  }
649
- } catch (signErr) {
650
- if (pendingEmitted.has(cmdId)) {
651
- pendingEmitted.delete(cmdId);
652
- eventBus.emit(core.CIP0103_EVENTS.TX_CHANGED, {
653
- status: "failed",
654
- commandId: cmdId
655
- });
632
+ submitting = true;
633
+ const receipt = await client.submitTransaction({ signedTx });
634
+ const event = executedEvent(receipt, cmdId);
635
+ if (event) eventBus.emit(core.CIP0103_EVENTS.TX_CHANGED, event);
636
+ } catch (reason) {
637
+ const code = reason?.code;
638
+ if (!submitting || code === 4001 || code === "USER_REJECTED" || code === -32003) {
639
+ eventBus.emit(core.CIP0103_EVENTS.TX_CHANGED, { status: "failed", commandId: cmdId });
656
640
  }
657
- throw signErr;
641
+ throw reason;
642
+ } finally {
643
+ pendingEmitted.delete(cmdId);
658
644
  }
659
645
  return void 0;
660
646
  }
@@ -729,59 +715,51 @@ function wireEvents(client, eventBus, pendingEmitted) {
729
715
  });
730
716
  });
731
717
  client.on("tx:status", (event) => {
732
- const e = event;
733
- if (!e || typeof e !== "object" || !("status" in e)) return;
734
- const statusMap = {
735
- pending: "pending",
736
- submitted: "pending",
737
- signed: "signed",
738
- committed: "executed",
739
- rejected: "failed",
740
- failed: "failed"
741
- };
742
- const mappedStatus = statusMap[e.status] ?? "pending";
743
- const commandId = String(e.txId);
744
- if (mappedStatus === "pending" && pendingEmitted.has(commandId)) {
745
- return;
746
- }
747
- if (mappedStatus === "executed" || mappedStatus === "failed") {
748
- pendingEmitted.delete(commandId);
749
- }
718
+ const e = record(event);
719
+ if (!e) return;
720
+ const raw = record(e.raw);
721
+ const tx = record(raw?.tx) ?? raw;
722
+ const commandId = nonempty(tx?.commandId) ? tx.commandId : e.txId;
723
+ if (!nonempty(commandId)) return;
750
724
  let txEvent;
751
- switch (mappedStatus) {
725
+ switch (e.status) {
726
+ case "pending":
727
+ case "submitted":
728
+ if (pendingEmitted.has(commandId)) return;
729
+ txEvent = { status: "pending", commandId };
730
+ break;
752
731
  case "signed":
753
- txEvent = {
754
- status: "signed",
755
- commandId,
756
- payload: {
757
- signature: "",
758
- signedBy: "",
759
- party: ""
760
- }
761
- };
732
+ txEvent = signedEvent(tx, commandId);
762
733
  break;
763
- case "executed": {
764
- const raw = e.raw;
765
- txEvent = {
766
- status: "executed",
767
- commandId,
768
- payload: {
769
- updateId: raw?.updateId ?? raw?.commandId ?? commandId,
770
- completionOffset: 0
771
- }
772
- };
734
+ case "committed":
735
+ txEvent = executedEvent(tx, commandId);
773
736
  break;
774
- }
737
+ case "rejected":
775
738
  case "failed":
776
739
  txEvent = { status: "failed", commandId };
777
740
  break;
778
741
  default:
779
- txEvent = { status: "pending", commandId };
780
- break;
742
+ return;
781
743
  }
782
- eventBus.emit(core.CIP0103_EVENTS.TX_CHANGED, txEvent);
744
+ if (txEvent) eventBus.emit(core.CIP0103_EVENTS.TX_CHANGED, txEvent);
783
745
  });
784
746
  }
747
+ function record(value) {
748
+ return value && typeof value === "object" && !Array.isArray(value) ? value : null;
749
+ }
750
+ function nonempty(value) {
751
+ return typeof value === "string" && value.trim().length > 0;
752
+ }
753
+ function signedEvent(value, commandId) {
754
+ const tx = record(value);
755
+ const p = record(tx?.payload) ?? tx;
756
+ return nonempty(p?.signature) && nonempty(p?.signedBy) && nonempty(p?.party) ? { status: "signed", commandId, payload: { signature: p.signature, signedBy: p.signedBy, party: p.party } } : null;
757
+ }
758
+ function executedEvent(value, commandId) {
759
+ const tx = record(value);
760
+ const p = record(tx?.payload) ?? tx;
761
+ return nonempty(p?.updateId) && typeof p.completionOffset === "number" && Number.isSafeInteger(p.completionOffset) && p.completionOffset >= 0 ? { status: "executed", commandId, payload: { updateId: p.updateId, completionOffset: p.completionOffset } } : null;
762
+ }
785
763
  function normalizeParams(params) {
786
764
  if (!params) return {};
787
765
  if (Array.isArray(params)) return params[0] ?? {};
@@ -813,11 +791,33 @@ function createExtensionChannelProvider(options = {}) {
813
791
  const eventBus = new CIP0103EventBus();
814
792
  const channelId = `pl-${++channelSeq}-${target ?? "ext"}`;
815
793
  let requestSeq = 0;
794
+ let listening = false;
795
+ let destroyed = false;
796
+ const subscribedEvents = /* @__PURE__ */ new Set();
797
+ function listen() {
798
+ if (!listening && typeof window !== "undefined") {
799
+ window.addEventListener("message", onMessage);
800
+ listening = true;
801
+ }
802
+ }
803
+ function detachIfIdle() {
804
+ if (pending.size > 0 || [...subscribedEvents].some((name) => eventBus.listenerCount(name) > 0)) return;
805
+ if (listening && typeof window !== "undefined") window.removeEventListener("message", onMessage);
806
+ listening = false;
807
+ }
816
808
  const pending = /* @__PURE__ */ new Map();
817
809
  function onMessage(event) {
818
810
  if (event.source !== window) return;
819
811
  const selfOrigin = typeof window !== "undefined" ? window.location?.origin : void 0;
820
812
  if (selfOrigin && selfOrigin !== "null" && event.origin !== selfOrigin) return;
813
+ const notification = event.data;
814
+ if (notification?.type === SPLICE_WALLET_REQUEST && notification.request?.jsonrpc === "2.0" && notification.request.id == null && (!target || notification.target === target)) {
815
+ const method = notification.request.method;
816
+ if (method && ["statusChanged", "accountsChanged", "txChanged", "connected"].includes(method)) {
817
+ eventBus.emit(method, notification.request.params);
818
+ }
819
+ return;
820
+ }
821
821
  const data = event.data;
822
822
  if (!data || data.type !== SPLICE_WALLET_RESPONSE || !data.response) return;
823
823
  const { id, result, error } = data.response;
@@ -826,6 +826,7 @@ function createExtensionChannelProvider(options = {}) {
826
826
  if (!entry) return;
827
827
  pending.delete(String(id));
828
828
  clearTimeout(entry.timer);
829
+ detachIfIdle();
829
830
  if (error) {
830
831
  entry.reject(
831
832
  new ProviderRpcError(
@@ -838,24 +839,24 @@ function createExtensionChannelProvider(options = {}) {
838
839
  entry.resolve(result);
839
840
  }
840
841
  }
841
- if (typeof window !== "undefined") {
842
- window.addEventListener("message", onMessage);
843
- }
844
842
  function postTargetOrigin() {
845
843
  const origin = typeof window !== "undefined" ? window.location?.origin : void 0;
846
844
  return origin && origin !== "null" ? origin : "*";
847
845
  }
848
846
  const provider = {
849
847
  request(args) {
848
+ if (destroyed) return Promise.reject(new ProviderRpcError("Extension channel was destroyed", RPC_ERRORS.DISCONNECTED));
850
849
  if (typeof window === "undefined") {
851
850
  return Promise.reject(
852
851
  new ProviderRpcError("No window: extension channel unavailable", JSON_RPC_ERRORS.INTERNAL_ERROR)
853
852
  );
854
853
  }
854
+ listen();
855
855
  const id = `${channelId}-${++requestSeq}`;
856
856
  return new Promise((resolve, reject) => {
857
857
  const timer = setTimeout(() => {
858
858
  pending.delete(id);
859
+ detachIfIdle();
859
860
  reject(
860
861
  new ProviderRpcError(
861
862
  `Wallet request "${args.method}" timed out after ${timeoutMs}ms`,
@@ -874,11 +875,21 @@ function createExtensionChannelProvider(options = {}) {
874
875
  },
875
876
  ...target ? { target } : {}
876
877
  };
877
- window.postMessage(message, postTargetOrigin());
878
+ try {
879
+ window.postMessage(message, postTargetOrigin());
880
+ } catch (error) {
881
+ pending.delete(id);
882
+ clearTimeout(timer);
883
+ detachIfIdle();
884
+ reject(error);
885
+ }
878
886
  });
879
887
  },
880
888
  on(event, listener) {
889
+ if (destroyed) return provider;
881
890
  eventBus.on(event, listener);
891
+ subscribedEvents.add(event);
892
+ listen();
882
893
  return provider;
883
894
  },
884
895
  emit(event, ...args) {
@@ -886,7 +897,20 @@ function createExtensionChannelProvider(options = {}) {
886
897
  },
887
898
  removeListener(event, listener) {
888
899
  eventBus.removeListener(event, listener);
900
+ detachIfIdle();
889
901
  return provider;
902
+ },
903
+ destroy() {
904
+ if (destroyed) return;
905
+ destroyed = true;
906
+ eventBus.removeAllListeners();
907
+ subscribedEvents.clear();
908
+ for (const entry of pending.values()) {
909
+ clearTimeout(entry.timer);
910
+ entry.reject(new ProviderRpcError("Extension channel was destroyed", RPC_ERRORS.DISCONNECTED));
911
+ }
912
+ pending.clear();
913
+ detachIfIdle();
890
914
  }
891
915
  };
892
916
  eventBus.setOwner(provider);