@partylayer/adapter-send 1.0.2 → 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/LICENSE +21 -0
- package/dist/index.d.mts +68 -39
- package/dist/index.d.ts +68 -39
- package/dist/index.js +97 -78
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +98 -79
- package/dist/index.mjs.map +1 -1
- package/package.json +11 -10
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 PartyLayer
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
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
|
|
199
|
+
* Typed wrapper around the Send Canton wallet, reached via the
|
|
200
|
+
* `canton:announceProvider` + extension postMessage `target` channel.
|
|
199
201
|
*
|
|
200
|
-
*
|
|
201
|
-
*
|
|
202
|
-
* (
|
|
203
|
-
*
|
|
204
|
-
*
|
|
205
|
-
*
|
|
206
|
-
*
|
|
207
|
-
*
|
|
208
|
-
*
|
|
209
|
-
*
|
|
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
|
|
217
|
-
*
|
|
218
|
-
*
|
|
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
|
-
*
|
|
223
|
-
*
|
|
224
|
-
*
|
|
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
|
|
229
|
-
*
|
|
230
|
-
*
|
|
231
|
-
*
|
|
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
|
|
236
|
-
*
|
|
237
|
-
*
|
|
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
|
|
199
|
+
* Typed wrapper around the Send Canton wallet, reached via the
|
|
200
|
+
* `canton:announceProvider` + extension postMessage `target` channel.
|
|
199
201
|
*
|
|
200
|
-
*
|
|
201
|
-
*
|
|
202
|
-
* (
|
|
203
|
-
*
|
|
204
|
-
*
|
|
205
|
-
*
|
|
206
|
-
*
|
|
207
|
-
*
|
|
208
|
-
*
|
|
209
|
-
*
|
|
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
|
|
217
|
-
*
|
|
218
|
-
*
|
|
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
|
-
*
|
|
223
|
-
*
|
|
224
|
-
*
|
|
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
|
|
229
|
-
*
|
|
230
|
-
*
|
|
231
|
-
*
|
|
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
|
|
236
|
-
*
|
|
237
|
-
*
|
|
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
|
|
177
|
-
*
|
|
178
|
-
*
|
|
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
|
|
185
|
-
*
|
|
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
|
-
|
|
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
|
|
199
|
-
*
|
|
200
|
-
*
|
|
201
|
-
*
|
|
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"
|
|
251
|
+
return typeof window !== "undefined";
|
|
205
252
|
}
|
|
206
253
|
/**
|
|
207
|
-
* Read
|
|
208
|
-
*
|
|
209
|
-
*
|
|
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
|
-
"
|
|
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
|
-
|
|
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
|
-
|
|
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.
|
|
291
|
+
return this.channelRequest("status");
|
|
269
292
|
}
|
|
270
293
|
connect() {
|
|
271
|
-
return this.
|
|
294
|
+
return this.channelRequest("connect");
|
|
272
295
|
}
|
|
273
296
|
disconnect() {
|
|
274
|
-
return this.
|
|
297
|
+
return this.channelRequest("disconnect");
|
|
275
298
|
}
|
|
276
299
|
isConnected() {
|
|
277
|
-
return this.
|
|
300
|
+
return this.channelRequest("isConnected");
|
|
278
301
|
}
|
|
279
302
|
getActiveNetwork() {
|
|
280
|
-
return this.
|
|
303
|
+
return this.channelRequest("getActiveNetwork");
|
|
281
304
|
}
|
|
282
305
|
listAccounts() {
|
|
283
|
-
return this.
|
|
306
|
+
return this.channelRequest("listAccounts");
|
|
284
307
|
}
|
|
285
308
|
getPrimaryAccount() {
|
|
286
|
-
return this.
|
|
309
|
+
return this.channelRequest("getPrimaryAccount");
|
|
287
310
|
}
|
|
288
311
|
signMessage(message) {
|
|
289
|
-
return this.
|
|
312
|
+
return this.channelRequest("signMessage", { message });
|
|
290
313
|
}
|
|
291
314
|
prepareExecute(params) {
|
|
292
|
-
return this.
|
|
315
|
+
return this.channelRequest("prepareExecute", params);
|
|
293
316
|
}
|
|
294
317
|
prepareExecuteAndWait(params) {
|
|
295
|
-
return this.
|
|
318
|
+
return this.channelRequest("prepareExecuteAndWait", params);
|
|
296
319
|
}
|
|
297
320
|
ledgerApi(req) {
|
|
298
|
-
return this.
|
|
321
|
+
return this.channelRequest("ledgerApi", req);
|
|
299
322
|
}
|
|
300
323
|
// ── Events ─────────────────────────────────────────────────────────────
|
|
301
|
-
//
|
|
302
|
-
//
|
|
303
|
-
//
|
|
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
|
-
|
|
306
|
-
|
|
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
|
-
|
|
312
|
-
if (
|
|
313
|
-
|
|
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:
|
|
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) {
|