@partylayer/provider 0.6.0 → 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
@@ -258,6 +258,9 @@ interface BridgeableClient {
258
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;
@@ -266,6 +269,7 @@ interface BridgeableClient {
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
@@ -258,6 +258,9 @@ interface BridgeableClient {
258
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;
@@ -266,6 +269,7 @@ interface BridgeableClient {
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.6.0" ;
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,61 +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
- if (signResult.transactionHash !== void 0) {
620
- eventBus.emit(core.CIP0103_EVENTS.TX_CHANGED, {
621
- status: "signed",
622
- commandId: cmdId,
623
- payload: {
624
- signature: String(signResult.transactionHash),
625
- signedBy: partyId,
626
- party: partyId
627
- }
628
- });
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");
622
+ }
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);
629
627
  }
630
- try {
631
- const receipt = await client.submitTransaction({
632
- signedTx: signResult.signedTx ?? signResult.transactionHash
633
- });
634
- pendingEmitted.delete(cmdId);
635
- eventBus.emit(core.CIP0103_EVENTS.TX_CHANGED, {
636
- status: "executed",
637
- commandId: cmdId,
638
- payload: {
639
- // All three are real identifiers — the receipt's update id, the
640
- // wallet's command id, or the bridge's own command id for this
641
- // operation. `String(receipt.transactionHash)` was the old tail
642
- // and would now stringify `undefined`.
643
- updateId: receipt.updateId ?? receipt.commandId ?? cmdId,
644
- completionOffset: 0
645
- }
646
- });
647
- } catch (submitErr) {
648
- pendingEmitted.delete(cmdId);
649
- eventBus.emit(core.CIP0103_EVENTS.TX_CHANGED, {
650
- status: "failed",
651
- commandId: cmdId
652
- });
653
- throw submitErr;
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");
654
631
  }
655
- } catch (signErr) {
656
- if (pendingEmitted.has(cmdId)) {
657
- pendingEmitted.delete(cmdId);
658
- eventBus.emit(core.CIP0103_EVENTS.TX_CHANGED, {
659
- status: "failed",
660
- commandId: cmdId
661
- });
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 });
662
640
  }
663
- throw signErr;
641
+ throw reason;
642
+ } finally {
643
+ pendingEmitted.delete(cmdId);
664
644
  }
665
645
  return void 0;
666
646
  }
@@ -735,59 +715,51 @@ function wireEvents(client, eventBus, pendingEmitted) {
735
715
  });
736
716
  });
737
717
  client.on("tx:status", (event) => {
738
- const e = event;
739
- if (!e || typeof e !== "object" || !("status" in e)) return;
740
- const statusMap = {
741
- pending: "pending",
742
- submitted: "pending",
743
- signed: "signed",
744
- committed: "executed",
745
- rejected: "failed",
746
- failed: "failed"
747
- };
748
- const mappedStatus = statusMap[e.status] ?? "pending";
749
- const commandId = String(e.txId);
750
- if (mappedStatus === "pending" && pendingEmitted.has(commandId)) {
751
- return;
752
- }
753
- if (mappedStatus === "executed" || mappedStatus === "failed") {
754
- pendingEmitted.delete(commandId);
755
- }
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;
756
724
  let txEvent;
757
- 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;
758
731
  case "signed":
759
- txEvent = {
760
- status: "signed",
761
- commandId,
762
- payload: {
763
- signature: "",
764
- signedBy: "",
765
- party: ""
766
- }
767
- };
732
+ txEvent = signedEvent(tx, commandId);
768
733
  break;
769
- case "executed": {
770
- const raw = e.raw;
771
- txEvent = {
772
- status: "executed",
773
- commandId,
774
- payload: {
775
- updateId: raw?.updateId ?? raw?.commandId ?? commandId,
776
- completionOffset: 0
777
- }
778
- };
734
+ case "committed":
735
+ txEvent = executedEvent(tx, commandId);
779
736
  break;
780
- }
737
+ case "rejected":
781
738
  case "failed":
782
739
  txEvent = { status: "failed", commandId };
783
740
  break;
784
741
  default:
785
- txEvent = { status: "pending", commandId };
786
- break;
742
+ return;
787
743
  }
788
- eventBus.emit(core.CIP0103_EVENTS.TX_CHANGED, txEvent);
744
+ if (txEvent) eventBus.emit(core.CIP0103_EVENTS.TX_CHANGED, txEvent);
789
745
  });
790
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
+ }
791
763
  function normalizeParams(params) {
792
764
  if (!params) return {};
793
765
  if (Array.isArray(params)) return params[0] ?? {};
@@ -819,11 +791,33 @@ function createExtensionChannelProvider(options = {}) {
819
791
  const eventBus = new CIP0103EventBus();
820
792
  const channelId = `pl-${++channelSeq}-${target ?? "ext"}`;
821
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
+ }
822
808
  const pending = /* @__PURE__ */ new Map();
823
809
  function onMessage(event) {
824
810
  if (event.source !== window) return;
825
811
  const selfOrigin = typeof window !== "undefined" ? window.location?.origin : void 0;
826
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
+ }
827
821
  const data = event.data;
828
822
  if (!data || data.type !== SPLICE_WALLET_RESPONSE || !data.response) return;
829
823
  const { id, result, error } = data.response;
@@ -832,6 +826,7 @@ function createExtensionChannelProvider(options = {}) {
832
826
  if (!entry) return;
833
827
  pending.delete(String(id));
834
828
  clearTimeout(entry.timer);
829
+ detachIfIdle();
835
830
  if (error) {
836
831
  entry.reject(
837
832
  new ProviderRpcError(
@@ -844,24 +839,24 @@ function createExtensionChannelProvider(options = {}) {
844
839
  entry.resolve(result);
845
840
  }
846
841
  }
847
- if (typeof window !== "undefined") {
848
- window.addEventListener("message", onMessage);
849
- }
850
842
  function postTargetOrigin() {
851
843
  const origin = typeof window !== "undefined" ? window.location?.origin : void 0;
852
844
  return origin && origin !== "null" ? origin : "*";
853
845
  }
854
846
  const provider = {
855
847
  request(args) {
848
+ if (destroyed) return Promise.reject(new ProviderRpcError("Extension channel was destroyed", RPC_ERRORS.DISCONNECTED));
856
849
  if (typeof window === "undefined") {
857
850
  return Promise.reject(
858
851
  new ProviderRpcError("No window: extension channel unavailable", JSON_RPC_ERRORS.INTERNAL_ERROR)
859
852
  );
860
853
  }
854
+ listen();
861
855
  const id = `${channelId}-${++requestSeq}`;
862
856
  return new Promise((resolve, reject) => {
863
857
  const timer = setTimeout(() => {
864
858
  pending.delete(id);
859
+ detachIfIdle();
865
860
  reject(
866
861
  new ProviderRpcError(
867
862
  `Wallet request "${args.method}" timed out after ${timeoutMs}ms`,
@@ -880,11 +875,21 @@ function createExtensionChannelProvider(options = {}) {
880
875
  },
881
876
  ...target ? { target } : {}
882
877
  };
883
- 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
+ }
884
886
  });
885
887
  },
886
888
  on(event, listener) {
889
+ if (destroyed) return provider;
887
890
  eventBus.on(event, listener);
891
+ subscribedEvents.add(event);
892
+ listen();
888
893
  return provider;
889
894
  },
890
895
  emit(event, ...args) {
@@ -892,7 +897,20 @@ function createExtensionChannelProvider(options = {}) {
892
897
  },
893
898
  removeListener(event, listener) {
894
899
  eventBus.removeListener(event, listener);
900
+ detachIfIdle();
895
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();
896
914
  }
897
915
  };
898
916
  eventBus.setOwner(provider);