@partylayer/adapter-send 1.0.3 → 1.0.4

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.d.mts CHANGED
@@ -1,5 +1,6 @@
1
1
  import * as _partylayer_core from '@partylayer/core';
2
- import { ProviderDetection, WalletAdapter, CapabilityKey, AdapterDetectResult, AdapterContext, PartyId, AdapterConnectResult, Session, PersistedSession, SignMessageParams, SignedMessage, SignTransactionParams, SignedTransaction, SubmitTransactionParams, TxReceipt, LedgerApiParams, LedgerApiResult, AdapterEventName, WalletNotInstalledError, ErrorMappingContext, PartyLayerError } from '@partylayer/core';
2
+ import { ProviderDetection, CIP0103Provider, WalletAdapter, CapabilityKey, AdapterDetectResult, AdapterContext, PartyId, AdapterConnectResult, Session, PersistedSession, SignMessageParams, SignedMessage, SignTransactionParams, SignedTransaction, SubmitTransactionParams, TxReceipt, LedgerApiParams, LedgerApiResult, AdapterEventName, WalletNotInstalledError, ErrorMappingContext, PartyLayerError } from '@partylayer/core';
3
+ import { AnnounceDiscoveryOptions, DiscoveredProvider } from '@partylayer/provider';
3
4
 
4
5
  /**
5
6
  * Local Sigilry-shape types.
@@ -195,65 +196,93 @@ declare global {
195
196
  }
196
197
 
197
198
  /**
198
- * Typed wrapper around the `window.canton` provider exposed by Send.
199
+ * Typed wrapper around the Send Canton wallet, reached via the
200
+ * `canton:announceProvider` + extension postMessage `target` channel.
199
201
  *
200
- * Detection is **registry-driven** via `matchesProviderDetection`. The
201
- * adapter accepts an optional `ProviderDetection` rule set at construction
202
- * (sourced from the registry's `providerDetection` field for the Send
203
- * entry); when omitted, it falls back to `SEND_BUILTIN_DETECTION` which
204
- * mirrors the canonical registry rule. Every public RPC method goes
205
- * through `guardedRequest`, which checks the live `status` response
206
- * against those rules before forwarding the call. The result: if a
207
- * non-Send wallet is sitting at `window.canton`, every Send call resolves
208
- * to a `SendKernelMismatchError` (treated by the SDK as "Send is not
209
- * installed"), and Send only ever acts on its own provider.
202
+ * WHY NOT `window.canton`: Send is announce-only. When another wallet (e.g.
203
+ * Console) owns the single shared `window.canton` slot, the old transport
204
+ * (bind `window.canton`, guard by `kernel.id`) returned a kernel mismatch and
205
+ * Send was unconnectable. Send instead fires `canton:announceProvider` with
206
+ * `{ id, name, icon, target }` (id == target == its extension id) and does NOT
207
+ * inject `window.canton`. So detection + every RPC now go through the announce
208
+ * handshake and the splice postMessage `target` channel, regardless of who
209
+ * owns `window.canton`.
210
+ *
211
+ * Transport is reused from `@partylayer/provider`: `discoverAnnouncedProviders`
212
+ * finds Send's announce entry (a ready `createExtensionChannelProvider` over
213
+ * its `target`), and every call is forwarded through that channel provider's
214
+ * request/response. Detection is registry-driven: the announce `id` is matched
215
+ * against Send's accepted extension ids (the `provider.id` matchers of the
216
+ * supplied `ProviderDetection`, plus `SEND_KNOWN_EXTENSION_IDS`).
217
+ *
218
+ * INBOUND EVENTS: the official splice extension (sync) provider does not push
219
+ * events over `postMessage` — the wire protocol has no inbound-event message
220
+ * type, and event push exists only on the remote/SSE path. Send's tx result
221
+ * comes from `prepareExecuteAndWait`'s response, not from `txChanged`. So
222
+ * `on`/`off` simply delegate to the channel provider's local event bus (kept so
223
+ * the `events` capability and API are preserved); they never throw.
210
224
  */
211
225
 
226
+ interface SendProviderOptions {
227
+ /**
228
+ * Pre-resolved channel provider (used by tests). When set, the announce
229
+ * handshake is skipped and every call routes through this provider.
230
+ */
231
+ provider?: CIP0103Provider;
232
+ /** Override the announce-collection window (ms). Default 500. */
233
+ announceTimeoutMs?: number;
234
+ /** Override announce discovery (used by tests). Defaults to the real handshake. */
235
+ discover?: (options?: AnnounceDiscoveryOptions) => Promise<DiscoveredProvider[]>;
236
+ }
212
237
  declare class SendProvider {
213
238
  private readonly detection;
239
+ private readonly announceTimeoutMs;
240
+ private readonly discover;
241
+ private readonly injectedProvider?;
242
+ private cachedChannel;
243
+ private channelPromise;
214
244
  private cachedStatus;
215
245
  /**
216
- * @param detection Optional. Used to match the running `window.canton`
217
- * provider against Send's identity. When omitted, falls back to
218
- * `SEND_BUILTIN_DETECTION` (canonical registry rule mirror).
246
+ * @param detection Optional registry `ProviderDetection`. Its `provider.id`
247
+ * exact-match values define which announced extension ids are treated as
248
+ * Send. Defaults to `SEND_BUILTIN_DETECTION`.
249
+ * @param options Optional test/advanced hooks (see {@link SendProviderOptions}).
219
250
  */
220
- constructor(detection?: ProviderDetection);
251
+ constructor(detection?: ProviderDetection, options?: SendProviderOptions);
252
+ /** Extension ids accepted as Send: registry `provider.id` matchers ∪ known ids. */
253
+ private acceptedIds;
221
254
  /**
222
- * True when `window.canton` is present AND its self-reported status
223
- * matches Send's detection rules. Performs an actual `status` round-trip
224
- * on first call and caches the response for subsequent ones.
255
+ * Resolve (and cache) Send's announce channel. Returns null if Send did not
256
+ * announce. Concurrent callers share a single in-flight announce (dedup), so
257
+ * a burst of requests triggers exactly one handshake.
258
+ */
259
+ private resolveChannel;
260
+ private doResolveChannel;
261
+ private channelRequest;
262
+ /**
263
+ * True iff Send announces via `canton:announceProvider` — independent of who
264
+ * owns `window.canton`. Caches the resolved channel.
225
265
  */
226
266
  isInstalled(): Promise<boolean>;
227
267
  /**
228
- * Synchronous best-effort presence check. Used for fast picker rendering
229
- * before any async status introspection. May report `true` for a
230
- * non-Send provider callers must follow up with `isInstalled()` (or
231
- * any guarded request) before assuming Send is wired in.
268
+ * Synchronous best-effort presence check: only that we are in a browser where
269
+ * announce discovery can run. The authoritative check is `isInstalled()` /
270
+ * any request (which performs the announce handshake). No longer depends on
271
+ * the shared `window.canton` slot.
232
272
  */
233
273
  isPotentiallyAvailable(): boolean;
234
274
  /**
235
- * Read the cached `kernel.id` from the running provider, fetching status
236
- * on demand. Diagnostic helper kept public for back-compat detection
237
- * itself no longer hinges on this single field.
275
+ * Read `status().kernel.id`. Diagnostic helper kept for back-compat. Live
276
+ * Send no longer reports a kernel; this throws `SendNotInstalledError` when
277
+ * absent (callers that need the stable id should use the announce target).
238
278
  */
239
279
  getKernelId(): Promise<string>;
240
- /**
241
- * Read the latest cached status object. Resolves the underlying RPC on
242
- * demand if no cached value is present.
243
- */
280
+ /** Latest status (cached after first fetch). */
244
281
  getStatus(): Promise<SendStatusResponse>;
245
- /**
246
- * Reset the cached status (e.g. after the user uninstalls and reinstalls
247
- * the extension, or you suspect kernel identity changed mid-session).
248
- * Kept under both names to avoid breaking existing test imports.
249
- */
282
+ /** Reset cached status AND the resolved announce channel (forces re-announce). */
250
283
  resetKernelCache(): void;
251
284
  resetStatusCache(): void;
252
285
  private fetchStatus;
253
- /** Internal — bypasses the detection guard. */
254
- private rawRequest;
255
- /** Public dispatch — guards every call with a registry-driven detection check. */
256
- private guardedRequest;
257
286
  status(): Promise<SendStatusResponse>;
258
287
  connect(): Promise<SendStatusResponse>;
259
288
  disconnect(): Promise<null>;
package/dist/index.d.ts CHANGED
@@ -1,5 +1,6 @@
1
1
  import * as _partylayer_core from '@partylayer/core';
2
- import { ProviderDetection, WalletAdapter, CapabilityKey, AdapterDetectResult, AdapterContext, PartyId, AdapterConnectResult, Session, PersistedSession, SignMessageParams, SignedMessage, SignTransactionParams, SignedTransaction, SubmitTransactionParams, TxReceipt, LedgerApiParams, LedgerApiResult, AdapterEventName, WalletNotInstalledError, ErrorMappingContext, PartyLayerError } from '@partylayer/core';
2
+ import { ProviderDetection, CIP0103Provider, WalletAdapter, CapabilityKey, AdapterDetectResult, AdapterContext, PartyId, AdapterConnectResult, Session, PersistedSession, SignMessageParams, SignedMessage, SignTransactionParams, SignedTransaction, SubmitTransactionParams, TxReceipt, LedgerApiParams, LedgerApiResult, AdapterEventName, WalletNotInstalledError, ErrorMappingContext, PartyLayerError } from '@partylayer/core';
3
+ import { AnnounceDiscoveryOptions, DiscoveredProvider } from '@partylayer/provider';
3
4
 
4
5
  /**
5
6
  * Local Sigilry-shape types.
@@ -195,65 +196,93 @@ declare global {
195
196
  }
196
197
 
197
198
  /**
198
- * Typed wrapper around the `window.canton` provider exposed by Send.
199
+ * Typed wrapper around the Send Canton wallet, reached via the
200
+ * `canton:announceProvider` + extension postMessage `target` channel.
199
201
  *
200
- * Detection is **registry-driven** via `matchesProviderDetection`. The
201
- * adapter accepts an optional `ProviderDetection` rule set at construction
202
- * (sourced from the registry's `providerDetection` field for the Send
203
- * entry); when omitted, it falls back to `SEND_BUILTIN_DETECTION` which
204
- * mirrors the canonical registry rule. Every public RPC method goes
205
- * through `guardedRequest`, which checks the live `status` response
206
- * against those rules before forwarding the call. The result: if a
207
- * non-Send wallet is sitting at `window.canton`, every Send call resolves
208
- * to a `SendKernelMismatchError` (treated by the SDK as "Send is not
209
- * installed"), and Send only ever acts on its own provider.
202
+ * WHY NOT `window.canton`: Send is announce-only. When another wallet (e.g.
203
+ * Console) owns the single shared `window.canton` slot, the old transport
204
+ * (bind `window.canton`, guard by `kernel.id`) returned a kernel mismatch and
205
+ * Send was unconnectable. Send instead fires `canton:announceProvider` with
206
+ * `{ id, name, icon, target }` (id == target == its extension id) and does NOT
207
+ * inject `window.canton`. So detection + every RPC now go through the announce
208
+ * handshake and the splice postMessage `target` channel, regardless of who
209
+ * owns `window.canton`.
210
+ *
211
+ * Transport is reused from `@partylayer/provider`: `discoverAnnouncedProviders`
212
+ * finds Send's announce entry (a ready `createExtensionChannelProvider` over
213
+ * its `target`), and every call is forwarded through that channel provider's
214
+ * request/response. Detection is registry-driven: the announce `id` is matched
215
+ * against Send's accepted extension ids (the `provider.id` matchers of the
216
+ * supplied `ProviderDetection`, plus `SEND_KNOWN_EXTENSION_IDS`).
217
+ *
218
+ * INBOUND EVENTS: the official splice extension (sync) provider does not push
219
+ * events over `postMessage` — the wire protocol has no inbound-event message
220
+ * type, and event push exists only on the remote/SSE path. Send's tx result
221
+ * comes from `prepareExecuteAndWait`'s response, not from `txChanged`. So
222
+ * `on`/`off` simply delegate to the channel provider's local event bus (kept so
223
+ * the `events` capability and API are preserved); they never throw.
210
224
  */
211
225
 
226
+ interface SendProviderOptions {
227
+ /**
228
+ * Pre-resolved channel provider (used by tests). When set, the announce
229
+ * handshake is skipped and every call routes through this provider.
230
+ */
231
+ provider?: CIP0103Provider;
232
+ /** Override the announce-collection window (ms). Default 500. */
233
+ announceTimeoutMs?: number;
234
+ /** Override announce discovery (used by tests). Defaults to the real handshake. */
235
+ discover?: (options?: AnnounceDiscoveryOptions) => Promise<DiscoveredProvider[]>;
236
+ }
212
237
  declare class SendProvider {
213
238
  private readonly detection;
239
+ private readonly announceTimeoutMs;
240
+ private readonly discover;
241
+ private readonly injectedProvider?;
242
+ private cachedChannel;
243
+ private channelPromise;
214
244
  private cachedStatus;
215
245
  /**
216
- * @param detection Optional. Used to match the running `window.canton`
217
- * provider against Send's identity. When omitted, falls back to
218
- * `SEND_BUILTIN_DETECTION` (canonical registry rule mirror).
246
+ * @param detection Optional registry `ProviderDetection`. Its `provider.id`
247
+ * exact-match values define which announced extension ids are treated as
248
+ * Send. Defaults to `SEND_BUILTIN_DETECTION`.
249
+ * @param options Optional test/advanced hooks (see {@link SendProviderOptions}).
219
250
  */
220
- constructor(detection?: ProviderDetection);
251
+ constructor(detection?: ProviderDetection, options?: SendProviderOptions);
252
+ /** Extension ids accepted as Send: registry `provider.id` matchers ∪ known ids. */
253
+ private acceptedIds;
221
254
  /**
222
- * True when `window.canton` is present AND its self-reported status
223
- * matches Send's detection rules. Performs an actual `status` round-trip
224
- * on first call and caches the response for subsequent ones.
255
+ * Resolve (and cache) Send's announce channel. Returns null if Send did not
256
+ * announce. Concurrent callers share a single in-flight announce (dedup), so
257
+ * a burst of requests triggers exactly one handshake.
258
+ */
259
+ private resolveChannel;
260
+ private doResolveChannel;
261
+ private channelRequest;
262
+ /**
263
+ * True iff Send announces via `canton:announceProvider` — independent of who
264
+ * owns `window.canton`. Caches the resolved channel.
225
265
  */
226
266
  isInstalled(): Promise<boolean>;
227
267
  /**
228
- * Synchronous best-effort presence check. Used for fast picker rendering
229
- * before any async status introspection. May report `true` for a
230
- * non-Send provider callers must follow up with `isInstalled()` (or
231
- * any guarded request) before assuming Send is wired in.
268
+ * Synchronous best-effort presence check: only that we are in a browser where
269
+ * announce discovery can run. The authoritative check is `isInstalled()` /
270
+ * any request (which performs the announce handshake). No longer depends on
271
+ * the shared `window.canton` slot.
232
272
  */
233
273
  isPotentiallyAvailable(): boolean;
234
274
  /**
235
- * Read the cached `kernel.id` from the running provider, fetching status
236
- * on demand. Diagnostic helper kept public for back-compat detection
237
- * itself no longer hinges on this single field.
275
+ * Read `status().kernel.id`. Diagnostic helper kept for back-compat. Live
276
+ * Send no longer reports a kernel; this throws `SendNotInstalledError` when
277
+ * absent (callers that need the stable id should use the announce target).
238
278
  */
239
279
  getKernelId(): Promise<string>;
240
- /**
241
- * Read the latest cached status object. Resolves the underlying RPC on
242
- * demand if no cached value is present.
243
- */
280
+ /** Latest status (cached after first fetch). */
244
281
  getStatus(): Promise<SendStatusResponse>;
245
- /**
246
- * Reset the cached status (e.g. after the user uninstalls and reinstalls
247
- * the extension, or you suspect kernel identity changed mid-session).
248
- * Kept under both names to avoid breaking existing test imports.
249
- */
282
+ /** Reset cached status AND the resolved announce channel (forces re-announce). */
250
283
  resetKernelCache(): void;
251
284
  resetStatusCache(): void;
252
285
  private fetchStatus;
253
- /** Internal — bypasses the detection guard. */
254
- private rawRequest;
255
- /** Public dispatch — guards every call with a registry-driven detection check. */
256
- private guardedRequest;
257
286
  status(): Promise<SendStatusResponse>;
258
287
  connect(): Promise<SendStatusResponse>;
259
288
  disconnect(): Promise<null>;
package/dist/index.js CHANGED
@@ -1,6 +1,7 @@
1
1
  'use strict';
2
2
 
3
3
  var core = require('@partylayer/core');
4
+ var provider = require('@partylayer/provider');
4
5
 
5
6
  // src/send-adapter.ts
6
7
 
@@ -169,153 +170,171 @@ function safePreview(value, maxLen = 200) {
169
170
  return String(value);
170
171
  }
171
172
  }
173
+ var DEFAULT_ANNOUNCE_TIMEOUT_MS = 500;
172
174
  var SendProvider = class {
173
175
  detection;
176
+ announceTimeoutMs;
177
+ discover;
178
+ injectedProvider;
179
+ cachedChannel = null;
180
+ channelPromise = null;
174
181
  cachedStatus = null;
175
182
  /**
176
- * @param detection Optional. Used to match the running `window.canton`
177
- * provider against Send's identity. When omitted, falls back to
178
- * `SEND_BUILTIN_DETECTION` (canonical registry rule mirror).
183
+ * @param detection Optional registry `ProviderDetection`. Its `provider.id`
184
+ * exact-match values define which announced extension ids are treated as
185
+ * Send. Defaults to `SEND_BUILTIN_DETECTION`.
186
+ * @param options Optional test/advanced hooks (see {@link SendProviderOptions}).
179
187
  */
180
- constructor(detection) {
188
+ constructor(detection, options) {
181
189
  this.detection = detection ?? SEND_BUILTIN_DETECTION;
190
+ this.announceTimeoutMs = options?.announceTimeoutMs ?? DEFAULT_ANNOUNCE_TIMEOUT_MS;
191
+ this.discover = options?.discover ?? ((o) => provider.discoverAnnouncedProviders(o));
192
+ this.injectedProvider = options?.provider;
182
193
  }
194
+ /** Extension ids accepted as Send: registry `provider.id` matchers ∪ known ids. */
195
+ acceptedIds() {
196
+ const fromDetection = this.detection.matchers.filter((m) => m.field === "provider.id" && m.match === "exact").flatMap((m) => m.values);
197
+ return Array.from(/* @__PURE__ */ new Set([...fromDetection, ...SEND_KNOWN_EXTENSION_IDS]));
198
+ }
199
+ /**
200
+ * Resolve (and cache) Send's announce channel. Returns null if Send did not
201
+ * announce. Concurrent callers share a single in-flight announce (dedup), so
202
+ * a burst of requests triggers exactly one handshake.
203
+ */
204
+ resolveChannel() {
205
+ if (this.cachedChannel) return Promise.resolve(this.cachedChannel);
206
+ if (this.channelPromise) return this.channelPromise;
207
+ this.channelPromise = this.doResolveChannel().then((channel) => {
208
+ if (channel) this.cachedChannel = channel;
209
+ return channel;
210
+ }).finally(() => {
211
+ this.channelPromise = null;
212
+ });
213
+ return this.channelPromise;
214
+ }
215
+ async doResolveChannel() {
216
+ if (this.injectedProvider) {
217
+ return { target: "injected", provider: this.injectedProvider };
218
+ }
219
+ if (typeof window === "undefined") return null;
220
+ const accepted = this.acceptedIds();
221
+ const entries = await this.discover({ timeoutMs: this.announceTimeoutMs });
222
+ const match = entries.find((e) => accepted.includes(e.id));
223
+ if (!match) return null;
224
+ return { target: match.id, provider: match.provider };
225
+ }
226
+ async channelRequest(method, params) {
227
+ const channel = await this.resolveChannel();
228
+ if (!channel) throw new SendNotInstalledError();
229
+ const payload = params === void 0 ? { method } : { method, params };
230
+ return channel.provider.request(payload);
231
+ }
232
+ // ── Detection ────────────────────────────────────────────────────────────
183
233
  /**
184
- * True when `window.canton` is present AND its self-reported status
185
- * matches Send's detection rules. Performs an actual `status` round-trip
186
- * on first call and caches the response for subsequent ones.
234
+ * True iff Send announces via `canton:announceProvider` independent of who
235
+ * owns `window.canton`. Caches the resolved channel.
187
236
  */
188
237
  async isInstalled() {
189
- if (typeof window === "undefined" || !window.canton) return false;
190
238
  try {
191
- const status = await this.fetchStatus();
192
- return core.matchesProviderDetection(status, this.detection);
239
+ return await this.resolveChannel() !== null;
193
240
  } catch {
194
241
  return false;
195
242
  }
196
243
  }
197
244
  /**
198
- * Synchronous best-effort presence check. Used for fast picker rendering
199
- * before any async status introspection. May report `true` for a
200
- * non-Send provider callers must follow up with `isInstalled()` (or
201
- * any guarded request) before assuming Send is wired in.
245
+ * Synchronous best-effort presence check: only that we are in a browser where
246
+ * announce discovery can run. The authoritative check is `isInstalled()` /
247
+ * any request (which performs the announce handshake). No longer depends on
248
+ * the shared `window.canton` slot.
202
249
  */
203
250
  isPotentiallyAvailable() {
204
- return typeof window !== "undefined" && !!window.canton;
251
+ return typeof window !== "undefined";
205
252
  }
206
253
  /**
207
- * Read the cached `kernel.id` from the running provider, fetching status
208
- * on demand. Diagnostic helper kept public for back-compat detection
209
- * itself no longer hinges on this single field.
254
+ * Read `status().kernel.id`. Diagnostic helper kept for back-compat. Live
255
+ * Send no longer reports a kernel; this throws `SendNotInstalledError` when
256
+ * absent (callers that need the stable id should use the announce target).
210
257
  */
211
258
  async getKernelId() {
212
259
  const status = await this.fetchStatus();
213
260
  const id = status?.kernel?.id;
214
261
  if (typeof id !== "string" || id.length === 0) {
215
262
  throw new SendNotInstalledError(
216
- "window.canton.status() did not return a kernel.id \u2014 provider is malformed."
263
+ "Send status() did not return a kernel.id."
217
264
  );
218
265
  }
219
266
  return id;
220
267
  }
221
- /**
222
- * Read the latest cached status object. Resolves the underlying RPC on
223
- * demand if no cached value is present.
224
- */
268
+ /** Latest status (cached after first fetch). */
225
269
  async getStatus() {
226
270
  return this.fetchStatus();
227
271
  }
228
- /**
229
- * Reset the cached status (e.g. after the user uninstalls and reinstalls
230
- * the extension, or you suspect kernel identity changed mid-session).
231
- * Kept under both names to avoid breaking existing test imports.
232
- */
272
+ /** Reset cached status AND the resolved announce channel (forces re-announce). */
233
273
  resetKernelCache() {
234
274
  this.cachedStatus = null;
275
+ this.cachedChannel = null;
276
+ this.channelPromise = null;
235
277
  }
236
278
  resetStatusCache() {
237
279
  this.cachedStatus = null;
280
+ this.cachedChannel = null;
281
+ this.channelPromise = null;
238
282
  }
239
283
  async fetchStatus() {
240
284
  if (this.cachedStatus) return this.cachedStatus;
241
- if (typeof window === "undefined" || !window.canton) {
242
- throw new SendNotInstalledError();
243
- }
244
- const provider = window.canton;
245
- const status = await provider.request({ method: "status" });
285
+ const status = await this.channelRequest("status");
246
286
  this.cachedStatus = status;
247
287
  return status;
248
288
  }
249
- /** Internal bypasses the detection guard. */
250
- async rawRequest(args) {
251
- if (typeof window === "undefined" || !window.canton) {
252
- throw new SendNotInstalledError();
253
- }
254
- const provider = window.canton;
255
- return provider.request(args);
256
- }
257
- /** Public dispatch — guards every call with a registry-driven detection check. */
258
- async guardedRequest(args) {
259
- const status = await this.fetchStatus();
260
- if (!core.matchesProviderDetection(status, this.detection)) {
261
- const observedId = status?.kernel?.id ?? "<unknown>";
262
- throw new SendKernelMismatchError(observedId);
263
- }
264
- return this.rawRequest(args);
265
- }
266
- // ── Sigilry RPC methods (every one is guarded) ─────────────────────────
289
+ // ── Sigilry RPC methods (all over the announce target channel) ────────────
267
290
  status() {
268
- return this.guardedRequest({ method: "status" });
291
+ return this.channelRequest("status");
269
292
  }
270
293
  connect() {
271
- return this.guardedRequest({ method: "connect" });
294
+ return this.channelRequest("connect");
272
295
  }
273
296
  disconnect() {
274
- return this.guardedRequest({ method: "disconnect" });
297
+ return this.channelRequest("disconnect");
275
298
  }
276
299
  isConnected() {
277
- return this.guardedRequest({ method: "isConnected" });
300
+ return this.channelRequest("isConnected");
278
301
  }
279
302
  getActiveNetwork() {
280
- return this.guardedRequest({ method: "getActiveNetwork" });
303
+ return this.channelRequest("getActiveNetwork");
281
304
  }
282
305
  listAccounts() {
283
- return this.guardedRequest({ method: "listAccounts" });
306
+ return this.channelRequest("listAccounts");
284
307
  }
285
308
  getPrimaryAccount() {
286
- return this.guardedRequest({ method: "getPrimaryAccount" });
309
+ return this.channelRequest("getPrimaryAccount");
287
310
  }
288
311
  signMessage(message) {
289
- return this.guardedRequest({ method: "signMessage", params: { message } });
312
+ return this.channelRequest("signMessage", { message });
290
313
  }
291
314
  prepareExecute(params) {
292
- return this.guardedRequest({ method: "prepareExecute", params });
315
+ return this.channelRequest("prepareExecute", params);
293
316
  }
294
317
  prepareExecuteAndWait(params) {
295
- return this.guardedRequest({ method: "prepareExecuteAndWait", params });
318
+ return this.channelRequest("prepareExecuteAndWait", params);
296
319
  }
297
320
  ledgerApi(req) {
298
- return this.guardedRequest({ method: "ledgerApi", params: req });
321
+ return this.channelRequest("ledgerApi", req);
299
322
  }
300
323
  // ── Events ─────────────────────────────────────────────────────────────
301
- // No kernel guard here on purpose by the time the dApp wires up an
302
- // event listener it has already gone through `connect()` (which IS
303
- // guarded), so we trust the binding.
324
+ // Delegated to the channel provider's local event bus. By the time a dApp
325
+ // wires up a listener it has already gone through connect(), so the channel
326
+ // is cached. The official extension (sync) provider has no postMessage event
327
+ // push either, so this preserves the API/`events` capability without
328
+ // inventing a non-existent wire shape; it never throws.
304
329
  on(event, listener) {
305
- if (typeof window === "undefined" || !window.canton) {
306
- throw new SendNotInstalledError();
307
- }
308
- window.canton.on(event, listener);
330
+ const channel = this.cachedChannel;
331
+ if (!channel) return;
332
+ channel.provider.on(event, listener);
309
333
  }
310
334
  off(event, listener) {
311
- if (typeof window === "undefined" || !window.canton) return;
312
- if (typeof window.canton.off === "function") {
313
- window.canton.off(event, listener);
314
- return;
315
- }
316
- if (typeof window.canton.removeListener === "function") {
317
- window.canton.removeListener(event, listener);
318
- }
335
+ const channel = this.cachedChannel;
336
+ if (!channel) return;
337
+ channel.provider.removeListener(event, listener);
319
338
  }
320
339
  };
321
340
 
@@ -367,7 +386,7 @@ var SendAdapter = class {
367
386
  }
368
387
  return {
369
388
  installed: false,
370
- reason: "window.canton is present but its kernel.id does not match Send. Another Canton wallet is active."
389
+ reason: `Send Canton Wallet did not announce (canton:announceProvider). Visit ${SEND_INSTALL_URL} for installation instructions`
371
390
  };
372
391
  }
373
392
  async connect(ctx, _opts) {