@partylayer/adapter-send 1.0.0
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/README.md +134 -0
- package/dist/index.d.mts +447 -0
- package/dist/index.d.ts +447 -0
- package/dist/index.js +636 -0
- package/dist/index.js.map +1 -0
- package/dist/index.mjs +619 -0
- package/dist/index.mjs.map +1 -0
- package/package.json +55 -0
package/dist/index.mjs
ADDED
|
@@ -0,0 +1,619 @@
|
|
|
1
|
+
import { WalletNotInstalledError, PartyLayerError, UserRejectedError, CapabilityNotSupportedError, TransportError, mapUnknownErrorToPartyLayerError, matchesProviderDetection, toWalletId, toPartyId, toSignature, toTransactionHash } from '@partylayer/core';
|
|
2
|
+
|
|
3
|
+
// src/send-adapter.ts
|
|
4
|
+
|
|
5
|
+
// src/constants.ts
|
|
6
|
+
var SEND_KERNEL_ID = "ldmohiccoioolenadmogclhoklmanpgi";
|
|
7
|
+
var SEND_BUILTIN_DETECTION = {
|
|
8
|
+
transport: "window.canton",
|
|
9
|
+
matchers: [
|
|
10
|
+
{ field: "kernel.url", match: "domain", value: "cantonwallet.com" },
|
|
11
|
+
{ field: "kernel.userUrl", match: "domain", value: "cantonwallet.com" },
|
|
12
|
+
{ field: "kernel.id", match: "exact", values: [SEND_KERNEL_ID] }
|
|
13
|
+
]
|
|
14
|
+
};
|
|
15
|
+
var SEND_SUPPORTED_NETWORKS = ["canton:mainnet"];
|
|
16
|
+
var SEND_INSTALL_URL = "https://sigilry.org";
|
|
17
|
+
var SEND_HOMEPAGE = "https://cantonwallet.com";
|
|
18
|
+
var SEND_DOCS_URL = "https://sigilry.org";
|
|
19
|
+
var SEND_SIGNING_METHOD = "webauthn-prf";
|
|
20
|
+
var SendRpcErrorCode = {
|
|
21
|
+
PARSE_ERROR: -32700,
|
|
22
|
+
INVALID_REQUEST: -32600,
|
|
23
|
+
METHOD_NOT_FOUND: -32601,
|
|
24
|
+
INVALID_PARAMS: -32602,
|
|
25
|
+
INTERNAL_ERROR: -32603,
|
|
26
|
+
USER_REJECTED: 4001,
|
|
27
|
+
UNAUTHORIZED: 4100,
|
|
28
|
+
UNSUPPORTED_METHOD: 4200,
|
|
29
|
+
DISCONNECTED: 4900,
|
|
30
|
+
CHAIN_DISCONNECTED: 4901,
|
|
31
|
+
INVALID_INPUT: -32e3,
|
|
32
|
+
RESOURCE_NOT_FOUND: -32001,
|
|
33
|
+
RESOURCE_UNAVAILABLE: -32002,
|
|
34
|
+
TRANSACTION_REJECTED: -32003,
|
|
35
|
+
METHOD_NOT_SUPPORTED: -32004,
|
|
36
|
+
LIMIT_EXCEEDED: -32005
|
|
37
|
+
};
|
|
38
|
+
var WALLET_ID = "send";
|
|
39
|
+
var SendNotInstalledError = class extends WalletNotInstalledError {
|
|
40
|
+
constructor(reason) {
|
|
41
|
+
super(
|
|
42
|
+
WALLET_ID,
|
|
43
|
+
reason ?? `Send Canton Wallet is not detected. Visit ${SEND_INSTALL_URL} for installation instructions`
|
|
44
|
+
);
|
|
45
|
+
this.name = "SendNotInstalledError";
|
|
46
|
+
this.details = {
|
|
47
|
+
...this.details ?? {},
|
|
48
|
+
installUrl: SEND_INSTALL_URL
|
|
49
|
+
};
|
|
50
|
+
}
|
|
51
|
+
};
|
|
52
|
+
var SendKernelMismatchError = class extends WalletNotInstalledError {
|
|
53
|
+
constructor(actualKernelId) {
|
|
54
|
+
super(
|
|
55
|
+
WALLET_ID,
|
|
56
|
+
`Another Canton wallet is active at window.canton (kernel.id="${actualKernelId}", expected "${SEND_KERNEL_ID}"). The Send adapter will yield to the matching adapter.`
|
|
57
|
+
);
|
|
58
|
+
this.name = "SendKernelMismatchError";
|
|
59
|
+
this.details = {
|
|
60
|
+
...this.details ?? {},
|
|
61
|
+
actualKernelId,
|
|
62
|
+
expectedKernelId: SEND_KERNEL_ID
|
|
63
|
+
};
|
|
64
|
+
}
|
|
65
|
+
};
|
|
66
|
+
var SendAuthTimeoutError = class extends WalletNotInstalledError {
|
|
67
|
+
constructor(originalMessage) {
|
|
68
|
+
super(
|
|
69
|
+
WALLET_ID,
|
|
70
|
+
originalMessage ?? "Send authentication timed out. Please try again. If the problem persists, see https://cantonwallet.com"
|
|
71
|
+
);
|
|
72
|
+
this.name = "SendAuthTimeoutError";
|
|
73
|
+
this.details = {
|
|
74
|
+
...this.details ?? {},
|
|
75
|
+
cause: "send-auth-timeout",
|
|
76
|
+
retry: true,
|
|
77
|
+
helpUrl: "https://cantonwallet.com",
|
|
78
|
+
...originalMessage ? { originalMessage } : {}
|
|
79
|
+
};
|
|
80
|
+
}
|
|
81
|
+
};
|
|
82
|
+
function detectSendAuthTimeout(err) {
|
|
83
|
+
if (!err || typeof err !== "object") return false;
|
|
84
|
+
const raw = err.message;
|
|
85
|
+
if (typeof raw !== "string" || raw.length === 0) return false;
|
|
86
|
+
const msg = raw.toLowerCase();
|
|
87
|
+
return msg.includes("authentication timed out") || msg.includes("cannot reach authentication server") || msg.includes("auth.cantonwallet.com");
|
|
88
|
+
}
|
|
89
|
+
function isSendRpcError(err) {
|
|
90
|
+
if (!err || typeof err !== "object") return false;
|
|
91
|
+
const candidate = err;
|
|
92
|
+
return typeof candidate.code === "number" && typeof candidate.message === "string";
|
|
93
|
+
}
|
|
94
|
+
function mapSigilryError(err, context) {
|
|
95
|
+
if (err instanceof PartyLayerError) return err;
|
|
96
|
+
if (detectSendAuthTimeout(err)) {
|
|
97
|
+
const original = err instanceof Error ? err.message : void 0;
|
|
98
|
+
return new SendAuthTimeoutError(original);
|
|
99
|
+
}
|
|
100
|
+
if (isSendRpcError(err)) {
|
|
101
|
+
const code = err.code;
|
|
102
|
+
const message = err.message;
|
|
103
|
+
if (code === SendRpcErrorCode.USER_REJECTED) {
|
|
104
|
+
return new UserRejectedError(context.phase, {
|
|
105
|
+
walletId: context.walletId,
|
|
106
|
+
transport: context.transport,
|
|
107
|
+
rpcCode: code,
|
|
108
|
+
originalMessage: message
|
|
109
|
+
});
|
|
110
|
+
}
|
|
111
|
+
if (code === SendRpcErrorCode.UNSUPPORTED_METHOD || code === SendRpcErrorCode.METHOD_NOT_FOUND || code === SendRpcErrorCode.METHOD_NOT_SUPPORTED) {
|
|
112
|
+
return new CapabilityNotSupportedError(WALLET_ID, context.phase);
|
|
113
|
+
}
|
|
114
|
+
if (code === SendRpcErrorCode.DISCONNECTED || code === SendRpcErrorCode.CHAIN_DISCONNECTED || code === SendRpcErrorCode.UNAUTHORIZED) {
|
|
115
|
+
return new TransportError(message, err, {
|
|
116
|
+
walletId: context.walletId,
|
|
117
|
+
phase: context.phase,
|
|
118
|
+
transport: context.transport,
|
|
119
|
+
rpcCode: code
|
|
120
|
+
});
|
|
121
|
+
}
|
|
122
|
+
return new TransportError(message, err, {
|
|
123
|
+
walletId: context.walletId,
|
|
124
|
+
phase: context.phase,
|
|
125
|
+
transport: context.transport,
|
|
126
|
+
rpcCode: code
|
|
127
|
+
});
|
|
128
|
+
}
|
|
129
|
+
return mapUnknownErrorToPartyLayerError(err, context);
|
|
130
|
+
}
|
|
131
|
+
function templateIdHint(payload) {
|
|
132
|
+
if (!payload || typeof payload !== "object") return "";
|
|
133
|
+
const commands = payload.commands;
|
|
134
|
+
if (!Array.isArray(commands)) return "";
|
|
135
|
+
try {
|
|
136
|
+
for (const cmd of commands) {
|
|
137
|
+
const exercise = cmd?.ExerciseCommand ?? cmd?.exerciseCommand ?? cmd?.exercise;
|
|
138
|
+
const create = cmd?.CreateCommand ?? cmd?.createCommand ?? cmd?.create;
|
|
139
|
+
const raw = exercise?.templateId ?? create?.templateId;
|
|
140
|
+
const choice = exercise?.choice;
|
|
141
|
+
if (choice === "Amulet_Transfer" && typeof raw === "string" && raw.includes("Splice.Amulet:Amulet")) {
|
|
142
|
+
return " The command exercises 'Amulet_Transfer' directly on the Amulet template \u2014 that's the legacy (pre-CIP-56) path Canton no longer accepts. Use the Token Standard flow: exercise 'TransferFactory_Transfer' by interface on a TransferFactory contract (interfaceId '#splice-api-token-transfer-instruction-v1:Splice.Api.Token.TransferInstructionV1:TransferFactory'). See https://partylayer.xyz/docs/token-transfers for the canonical flow.";
|
|
143
|
+
}
|
|
144
|
+
if (typeof raw === "string" && raw.length > 0 && !raw.startsWith("#")) {
|
|
145
|
+
return ` The command uses templateId="${raw}" which is the short Canton form; Send requires the fully-qualified Daml form (e.g. '#splice-amulet:Splice.Amulet:Amulet').`;
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
} catch {
|
|
149
|
+
}
|
|
150
|
+
return "";
|
|
151
|
+
}
|
|
152
|
+
function safePreview(value, maxLen = 200) {
|
|
153
|
+
if (value === void 0) return "undefined";
|
|
154
|
+
if (value === null) return "null";
|
|
155
|
+
try {
|
|
156
|
+
const s = JSON.stringify(value);
|
|
157
|
+
if (typeof s !== "string") return String(value);
|
|
158
|
+
return s.length > maxLen ? s.slice(0, maxLen) + "..." : s;
|
|
159
|
+
} catch {
|
|
160
|
+
return String(value);
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
var SendProvider = class {
|
|
164
|
+
detection;
|
|
165
|
+
cachedStatus = null;
|
|
166
|
+
/**
|
|
167
|
+
* @param detection Optional. Used to match the running `window.canton`
|
|
168
|
+
* provider against Send's identity. When omitted, falls back to
|
|
169
|
+
* `SEND_BUILTIN_DETECTION` (canonical registry rule mirror).
|
|
170
|
+
*/
|
|
171
|
+
constructor(detection) {
|
|
172
|
+
this.detection = detection ?? SEND_BUILTIN_DETECTION;
|
|
173
|
+
}
|
|
174
|
+
/**
|
|
175
|
+
* True when `window.canton` is present AND its self-reported status
|
|
176
|
+
* matches Send's detection rules. Performs an actual `status` round-trip
|
|
177
|
+
* on first call and caches the response for subsequent ones.
|
|
178
|
+
*/
|
|
179
|
+
async isInstalled() {
|
|
180
|
+
if (typeof window === "undefined" || !window.canton) return false;
|
|
181
|
+
try {
|
|
182
|
+
const status = await this.fetchStatus();
|
|
183
|
+
return matchesProviderDetection(status, this.detection);
|
|
184
|
+
} catch {
|
|
185
|
+
return false;
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
/**
|
|
189
|
+
* Synchronous best-effort presence check. Used for fast picker rendering
|
|
190
|
+
* before any async status introspection. May report `true` for a
|
|
191
|
+
* non-Send provider — callers must follow up with `isInstalled()` (or
|
|
192
|
+
* any guarded request) before assuming Send is wired in.
|
|
193
|
+
*/
|
|
194
|
+
isPotentiallyAvailable() {
|
|
195
|
+
return typeof window !== "undefined" && !!window.canton;
|
|
196
|
+
}
|
|
197
|
+
/**
|
|
198
|
+
* Read the cached `kernel.id` from the running provider, fetching status
|
|
199
|
+
* on demand. Diagnostic helper kept public for back-compat — detection
|
|
200
|
+
* itself no longer hinges on this single field.
|
|
201
|
+
*/
|
|
202
|
+
async getKernelId() {
|
|
203
|
+
const status = await this.fetchStatus();
|
|
204
|
+
const id = status?.kernel?.id;
|
|
205
|
+
if (typeof id !== "string" || id.length === 0) {
|
|
206
|
+
throw new SendNotInstalledError(
|
|
207
|
+
"window.canton.status() did not return a kernel.id \u2014 provider is malformed."
|
|
208
|
+
);
|
|
209
|
+
}
|
|
210
|
+
return id;
|
|
211
|
+
}
|
|
212
|
+
/**
|
|
213
|
+
* Read the latest cached status object. Resolves the underlying RPC on
|
|
214
|
+
* demand if no cached value is present.
|
|
215
|
+
*/
|
|
216
|
+
async getStatus() {
|
|
217
|
+
return this.fetchStatus();
|
|
218
|
+
}
|
|
219
|
+
/**
|
|
220
|
+
* Reset the cached status (e.g. after the user uninstalls and reinstalls
|
|
221
|
+
* the extension, or you suspect kernel identity changed mid-session).
|
|
222
|
+
* Kept under both names to avoid breaking existing test imports.
|
|
223
|
+
*/
|
|
224
|
+
resetKernelCache() {
|
|
225
|
+
this.cachedStatus = null;
|
|
226
|
+
}
|
|
227
|
+
resetStatusCache() {
|
|
228
|
+
this.cachedStatus = null;
|
|
229
|
+
}
|
|
230
|
+
async fetchStatus() {
|
|
231
|
+
if (this.cachedStatus) return this.cachedStatus;
|
|
232
|
+
if (typeof window === "undefined" || !window.canton) {
|
|
233
|
+
throw new SendNotInstalledError();
|
|
234
|
+
}
|
|
235
|
+
const provider = window.canton;
|
|
236
|
+
const status = await provider.request({ method: "status" });
|
|
237
|
+
this.cachedStatus = status;
|
|
238
|
+
return status;
|
|
239
|
+
}
|
|
240
|
+
/** Internal — bypasses the detection guard. */
|
|
241
|
+
async rawRequest(args) {
|
|
242
|
+
if (typeof window === "undefined" || !window.canton) {
|
|
243
|
+
throw new SendNotInstalledError();
|
|
244
|
+
}
|
|
245
|
+
const provider = window.canton;
|
|
246
|
+
return provider.request(args);
|
|
247
|
+
}
|
|
248
|
+
/** Public dispatch — guards every call with a registry-driven detection check. */
|
|
249
|
+
async guardedRequest(args) {
|
|
250
|
+
const status = await this.fetchStatus();
|
|
251
|
+
if (!matchesProviderDetection(status, this.detection)) {
|
|
252
|
+
const observedId = status?.kernel?.id ?? "<unknown>";
|
|
253
|
+
throw new SendKernelMismatchError(observedId);
|
|
254
|
+
}
|
|
255
|
+
return this.rawRequest(args);
|
|
256
|
+
}
|
|
257
|
+
// ── Sigilry RPC methods (every one is guarded) ─────────────────────────
|
|
258
|
+
status() {
|
|
259
|
+
return this.guardedRequest({ method: "status" });
|
|
260
|
+
}
|
|
261
|
+
connect() {
|
|
262
|
+
return this.guardedRequest({ method: "connect" });
|
|
263
|
+
}
|
|
264
|
+
disconnect() {
|
|
265
|
+
return this.guardedRequest({ method: "disconnect" });
|
|
266
|
+
}
|
|
267
|
+
isConnected() {
|
|
268
|
+
return this.guardedRequest({ method: "isConnected" });
|
|
269
|
+
}
|
|
270
|
+
getActiveNetwork() {
|
|
271
|
+
return this.guardedRequest({ method: "getActiveNetwork" });
|
|
272
|
+
}
|
|
273
|
+
listAccounts() {
|
|
274
|
+
return this.guardedRequest({ method: "listAccounts" });
|
|
275
|
+
}
|
|
276
|
+
getPrimaryAccount() {
|
|
277
|
+
return this.guardedRequest({ method: "getPrimaryAccount" });
|
|
278
|
+
}
|
|
279
|
+
signMessage(message) {
|
|
280
|
+
return this.guardedRequest({ method: "signMessage", params: { message } });
|
|
281
|
+
}
|
|
282
|
+
prepareExecute(params) {
|
|
283
|
+
return this.guardedRequest({ method: "prepareExecute", params });
|
|
284
|
+
}
|
|
285
|
+
prepareExecuteAndWait(params) {
|
|
286
|
+
return this.guardedRequest({ method: "prepareExecuteAndWait", params });
|
|
287
|
+
}
|
|
288
|
+
ledgerApi(req) {
|
|
289
|
+
return this.guardedRequest({ method: "ledgerApi", params: req });
|
|
290
|
+
}
|
|
291
|
+
// ── Events ─────────────────────────────────────────────────────────────
|
|
292
|
+
// No kernel guard here on purpose — by the time the dApp wires up an
|
|
293
|
+
// event listener it has already gone through `connect()` (which IS
|
|
294
|
+
// guarded), so we trust the binding.
|
|
295
|
+
on(event, listener) {
|
|
296
|
+
if (typeof window === "undefined" || !window.canton) {
|
|
297
|
+
throw new SendNotInstalledError();
|
|
298
|
+
}
|
|
299
|
+
window.canton.on(event, listener);
|
|
300
|
+
}
|
|
301
|
+
off(event, listener) {
|
|
302
|
+
if (typeof window === "undefined" || !window.canton) return;
|
|
303
|
+
if (typeof window.canton.off === "function") {
|
|
304
|
+
window.canton.off(event, listener);
|
|
305
|
+
return;
|
|
306
|
+
}
|
|
307
|
+
if (typeof window.canton.removeListener === "function") {
|
|
308
|
+
window.canton.removeListener(event, listener);
|
|
309
|
+
}
|
|
310
|
+
}
|
|
311
|
+
};
|
|
312
|
+
|
|
313
|
+
// src/send-adapter.ts
|
|
314
|
+
var WALLET_ID2 = "send";
|
|
315
|
+
var SEND_CAPABILITIES = [
|
|
316
|
+
"connect",
|
|
317
|
+
"disconnect",
|
|
318
|
+
"restore",
|
|
319
|
+
"signMessage",
|
|
320
|
+
"submitTransaction",
|
|
321
|
+
"ledgerApi",
|
|
322
|
+
"events",
|
|
323
|
+
"injected"
|
|
324
|
+
];
|
|
325
|
+
var SendAdapter = class {
|
|
326
|
+
walletId = toWalletId(WALLET_ID2);
|
|
327
|
+
name = "Send";
|
|
328
|
+
provider;
|
|
329
|
+
/**
|
|
330
|
+
* @param options.detection Optional. When supplied, the adapter uses
|
|
331
|
+
* these matcher rules to decide whether the running `window.canton`
|
|
332
|
+
* belongs to Send. Inject this from the registry entry's
|
|
333
|
+
* `providerDetection` field for canonical behaviour. Omitting it
|
|
334
|
+
* falls back to the built-in pattern that mirrors the canonical
|
|
335
|
+
* registry rule (parity is verified by tests).
|
|
336
|
+
* @param options.provider Optional. Pre-built provider instance, used
|
|
337
|
+
* primarily by tests; takes precedence over `options.detection`.
|
|
338
|
+
*/
|
|
339
|
+
constructor(options) {
|
|
340
|
+
this.provider = options?.provider ?? new SendProvider(options?.detection);
|
|
341
|
+
}
|
|
342
|
+
getCapabilities() {
|
|
343
|
+
return SEND_CAPABILITIES;
|
|
344
|
+
}
|
|
345
|
+
async detectInstalled() {
|
|
346
|
+
if (typeof window === "undefined") {
|
|
347
|
+
return { installed: false, reason: "Browser environment required" };
|
|
348
|
+
}
|
|
349
|
+
if (!this.provider.isPotentiallyAvailable()) {
|
|
350
|
+
return {
|
|
351
|
+
installed: false,
|
|
352
|
+
reason: `Send Canton Wallet not detected. Visit ${SEND_INSTALL_URL} for installation instructions`
|
|
353
|
+
};
|
|
354
|
+
}
|
|
355
|
+
const installed = await this.provider.isInstalled();
|
|
356
|
+
if (installed) {
|
|
357
|
+
return { installed: true, reason: "Send Canton Wallet detected" };
|
|
358
|
+
}
|
|
359
|
+
return {
|
|
360
|
+
installed: false,
|
|
361
|
+
reason: "window.canton is present but its kernel.id does not match Send. Another Canton wallet is active."
|
|
362
|
+
};
|
|
363
|
+
}
|
|
364
|
+
async connect(ctx, _opts) {
|
|
365
|
+
try {
|
|
366
|
+
if (!this.provider.isPotentiallyAvailable()) {
|
|
367
|
+
throw new SendNotInstalledError();
|
|
368
|
+
}
|
|
369
|
+
ctx.logger.debug("Connecting to Send Canton Wallet", {
|
|
370
|
+
appName: ctx.appName,
|
|
371
|
+
network: ctx.network
|
|
372
|
+
});
|
|
373
|
+
const status = await this.provider.connect();
|
|
374
|
+
const account = await this.provider.getPrimaryAccount();
|
|
375
|
+
const partyId = toPartyId(account.partyId);
|
|
376
|
+
ctx.logger.info("Connected to Send Canton Wallet", {
|
|
377
|
+
partyId: account.partyId,
|
|
378
|
+
signingProviderId: account.signingProviderId,
|
|
379
|
+
kernelId: status.kernel?.id
|
|
380
|
+
});
|
|
381
|
+
return {
|
|
382
|
+
partyId,
|
|
383
|
+
session: {
|
|
384
|
+
walletId: this.walletId,
|
|
385
|
+
network: ctx.network,
|
|
386
|
+
createdAt: Date.now(),
|
|
387
|
+
metadata: buildSessionMetadata(status, account)
|
|
388
|
+
},
|
|
389
|
+
capabilities: this.getCapabilities()
|
|
390
|
+
};
|
|
391
|
+
} catch (err) {
|
|
392
|
+
throw mapSigilryError(err, {
|
|
393
|
+
walletId: this.walletId,
|
|
394
|
+
phase: "connect",
|
|
395
|
+
transport: "injected",
|
|
396
|
+
details: { origin: ctx.origin, network: ctx.network }
|
|
397
|
+
});
|
|
398
|
+
}
|
|
399
|
+
}
|
|
400
|
+
async disconnect(ctx, _session) {
|
|
401
|
+
try {
|
|
402
|
+
await this.provider.disconnect();
|
|
403
|
+
} catch (err) {
|
|
404
|
+
ctx.logger.warn("Error during Send wallet disconnect", err);
|
|
405
|
+
}
|
|
406
|
+
}
|
|
407
|
+
async restore(ctx, persisted) {
|
|
408
|
+
try {
|
|
409
|
+
if (typeof window === "undefined") return null;
|
|
410
|
+
if (!this.provider.isPotentiallyAvailable()) return null;
|
|
411
|
+
if (persisted.expiresAt && Date.now() >= persisted.expiresAt) return null;
|
|
412
|
+
const status = await this.provider.status();
|
|
413
|
+
if (!status.isConnected) return null;
|
|
414
|
+
const account = await this.provider.getPrimaryAccount();
|
|
415
|
+
if (account.partyId !== persisted.partyId) {
|
|
416
|
+
ctx.logger.debug(
|
|
417
|
+
"Send primary account changed since session was persisted; treating as expired",
|
|
418
|
+
{
|
|
419
|
+
persistedPartyId: persisted.partyId,
|
|
420
|
+
currentPartyId: account.partyId
|
|
421
|
+
}
|
|
422
|
+
);
|
|
423
|
+
return null;
|
|
424
|
+
}
|
|
425
|
+
ctx.logger.debug("Restored Send Canton Wallet session", {
|
|
426
|
+
partyId: account.partyId,
|
|
427
|
+
kernelId: status.kernel?.id
|
|
428
|
+
});
|
|
429
|
+
return {
|
|
430
|
+
...persisted,
|
|
431
|
+
walletId: this.walletId,
|
|
432
|
+
metadata: {
|
|
433
|
+
...persisted.metadata ?? {},
|
|
434
|
+
...buildSessionMetadata(status, account)
|
|
435
|
+
}
|
|
436
|
+
};
|
|
437
|
+
} catch (err) {
|
|
438
|
+
ctx.logger.warn("Failed to restore Send wallet session", err);
|
|
439
|
+
return null;
|
|
440
|
+
}
|
|
441
|
+
}
|
|
442
|
+
async signMessage(ctx, session, params) {
|
|
443
|
+
try {
|
|
444
|
+
if (typeof params.message !== "string" || params.message.length === 0) {
|
|
445
|
+
throw new Error("signMessage requires a non-empty string `message`");
|
|
446
|
+
}
|
|
447
|
+
ctx.logger.debug("Signing message with Send Canton Wallet", {
|
|
448
|
+
sessionId: session.sessionId,
|
|
449
|
+
messageLength: params.message.length
|
|
450
|
+
});
|
|
451
|
+
const { signature } = await this.provider.signMessage(params.message);
|
|
452
|
+
return {
|
|
453
|
+
signature: toSignature(signature),
|
|
454
|
+
partyId: session.partyId,
|
|
455
|
+
message: params.message,
|
|
456
|
+
nonce: params.nonce,
|
|
457
|
+
domain: params.domain
|
|
458
|
+
};
|
|
459
|
+
} catch (err) {
|
|
460
|
+
throw mapSigilryError(err, {
|
|
461
|
+
walletId: this.walletId,
|
|
462
|
+
phase: "signMessage",
|
|
463
|
+
transport: "injected",
|
|
464
|
+
details: { sessionId: session.sessionId }
|
|
465
|
+
});
|
|
466
|
+
}
|
|
467
|
+
}
|
|
468
|
+
async signTransaction(_ctx, _session, _params) {
|
|
469
|
+
throw new CapabilityNotSupportedError(
|
|
470
|
+
this.walletId,
|
|
471
|
+
"signTransaction \u2014 Send fuses sign-and-submit through prepareExecuteAndWait. Use submitTransaction instead."
|
|
472
|
+
);
|
|
473
|
+
}
|
|
474
|
+
async submitTransaction(ctx, session, params) {
|
|
475
|
+
const payload = params.signedTx;
|
|
476
|
+
try {
|
|
477
|
+
if (!payload || typeof payload !== "object") {
|
|
478
|
+
throw new Error(
|
|
479
|
+
`submitTransaction requires a SendPrepareSubmissionRequest as \`signedTx\` (received ${safePreview(payload)})`
|
|
480
|
+
);
|
|
481
|
+
}
|
|
482
|
+
if (!payload.commands || typeof payload.commands !== "object") {
|
|
483
|
+
throw new Error(
|
|
484
|
+
`submitTransaction signedTx is missing the required 'commands' field (received ${safePreview(payload)})`
|
|
485
|
+
);
|
|
486
|
+
}
|
|
487
|
+
ctx.logger.debug("Submitting transaction via Send Canton Wallet", {
|
|
488
|
+
sessionId: session.sessionId,
|
|
489
|
+
commandId: payload.commandId
|
|
490
|
+
});
|
|
491
|
+
const { tx } = await this.provider.prepareExecuteAndWait(payload);
|
|
492
|
+
if (!tx || typeof tx !== "object" || !tx.payload?.updateId) {
|
|
493
|
+
throw new Error(
|
|
494
|
+
`Send returned an unexpected shape from prepareExecuteAndWait. Expected { tx: { commandId, status:'executed', payload: { updateId, completionOffset } } } but received ${safePreview(tx)}.`
|
|
495
|
+
);
|
|
496
|
+
}
|
|
497
|
+
return {
|
|
498
|
+
transactionHash: toTransactionHash(tx.payload.updateId),
|
|
499
|
+
submittedAt: Date.now(),
|
|
500
|
+
commandId: tx.commandId,
|
|
501
|
+
updateId: tx.payload.updateId
|
|
502
|
+
};
|
|
503
|
+
} catch (err) {
|
|
504
|
+
const baseHint = templateIdHint(payload);
|
|
505
|
+
if (baseHint && !isSendRpcError(err)) {
|
|
506
|
+
const baseMessage = err instanceof Error ? err.message : String(err);
|
|
507
|
+
throw new TransportError(
|
|
508
|
+
baseMessage + baseHint,
|
|
509
|
+
err instanceof Error ? err : void 0,
|
|
510
|
+
{
|
|
511
|
+
walletId: this.walletId,
|
|
512
|
+
phase: "submitTransaction",
|
|
513
|
+
transport: "injected",
|
|
514
|
+
sessionId: session.sessionId,
|
|
515
|
+
commandId: payload?.commandId
|
|
516
|
+
}
|
|
517
|
+
);
|
|
518
|
+
}
|
|
519
|
+
throw mapSigilryError(err, {
|
|
520
|
+
walletId: this.walletId,
|
|
521
|
+
phase: "submitTransaction",
|
|
522
|
+
transport: "injected",
|
|
523
|
+
details: { sessionId: session.sessionId, commandId: payload?.commandId }
|
|
524
|
+
});
|
|
525
|
+
}
|
|
526
|
+
}
|
|
527
|
+
async ledgerApi(ctx, session, params) {
|
|
528
|
+
try {
|
|
529
|
+
ctx.logger.debug("Proxying ledger API request via Send Canton Wallet", {
|
|
530
|
+
sessionId: session.sessionId,
|
|
531
|
+
requestMethod: params.requestMethod,
|
|
532
|
+
resource: params.resource
|
|
533
|
+
});
|
|
534
|
+
const result = await this.provider.ledgerApi({
|
|
535
|
+
requestMethod: params.requestMethod,
|
|
536
|
+
resource: params.resource,
|
|
537
|
+
body: params.body
|
|
538
|
+
});
|
|
539
|
+
if (result && typeof result.response === "string") {
|
|
540
|
+
return { response: result.response };
|
|
541
|
+
}
|
|
542
|
+
return { response: JSON.stringify(result ?? null) };
|
|
543
|
+
} catch (err) {
|
|
544
|
+
throw mapSigilryError(err, {
|
|
545
|
+
walletId: this.walletId,
|
|
546
|
+
phase: "ledgerApi",
|
|
547
|
+
transport: "injected",
|
|
548
|
+
details: {
|
|
549
|
+
sessionId: session.sessionId,
|
|
550
|
+
requestMethod: params.requestMethod,
|
|
551
|
+
resource: params.resource
|
|
552
|
+
}
|
|
553
|
+
});
|
|
554
|
+
}
|
|
555
|
+
}
|
|
556
|
+
/**
|
|
557
|
+
* Subscribe to PartyLayer adapter events. Currently bridges only
|
|
558
|
+
* `txStatus` from Send's native `txChanged` event. Other PartyLayer
|
|
559
|
+
* adapter events (`connect` / `disconnect` / `sessionExpired` / `error`)
|
|
560
|
+
* are emitted by the SDK itself, not the wallet, so we no-op them.
|
|
561
|
+
*/
|
|
562
|
+
on(event, handler) {
|
|
563
|
+
if (event !== "txStatus") {
|
|
564
|
+
return () => {
|
|
565
|
+
};
|
|
566
|
+
}
|
|
567
|
+
const listener = (...args) => {
|
|
568
|
+
const tx = args[0];
|
|
569
|
+
if (!tx) return;
|
|
570
|
+
handler({
|
|
571
|
+
status: mapTxStatus(tx.status),
|
|
572
|
+
commandId: tx.commandId,
|
|
573
|
+
raw: tx
|
|
574
|
+
});
|
|
575
|
+
};
|
|
576
|
+
try {
|
|
577
|
+
this.provider.on("txChanged", listener);
|
|
578
|
+
} catch {
|
|
579
|
+
return () => {
|
|
580
|
+
};
|
|
581
|
+
}
|
|
582
|
+
return () => this.provider.off("txChanged", listener);
|
|
583
|
+
}
|
|
584
|
+
};
|
|
585
|
+
function mapTxStatus(status) {
|
|
586
|
+
switch (status) {
|
|
587
|
+
case "pending":
|
|
588
|
+
return "pending";
|
|
589
|
+
case "signed":
|
|
590
|
+
return "submitted";
|
|
591
|
+
case "executed":
|
|
592
|
+
return "committed";
|
|
593
|
+
case "failed":
|
|
594
|
+
default:
|
|
595
|
+
return "failed";
|
|
596
|
+
}
|
|
597
|
+
}
|
|
598
|
+
function buildSessionMetadata(status, account) {
|
|
599
|
+
const meta = {
|
|
600
|
+
kernelId: status.kernel?.id ?? "",
|
|
601
|
+
signingProviderId: account.signingProviderId,
|
|
602
|
+
signingMethod: SEND_SIGNING_METHOD,
|
|
603
|
+
publicKey: account.publicKey,
|
|
604
|
+
namespace: account.namespace,
|
|
605
|
+
networkId: account.networkId,
|
|
606
|
+
hint: account.hint
|
|
607
|
+
};
|
|
608
|
+
if (status.network?.ledgerApi?.baseUrl) {
|
|
609
|
+
meta.ledgerApiBaseUrl = status.network.ledgerApi.baseUrl;
|
|
610
|
+
}
|
|
611
|
+
if (status.session?.userId) {
|
|
612
|
+
meta.userId = status.session.userId;
|
|
613
|
+
}
|
|
614
|
+
return meta;
|
|
615
|
+
}
|
|
616
|
+
|
|
617
|
+
export { SEND_DOCS_URL, SEND_HOMEPAGE, SEND_INSTALL_URL, SEND_KERNEL_ID, SEND_SIGNING_METHOD, SEND_SUPPORTED_NETWORKS, SendAdapter, SendAuthTimeoutError, SendKernelMismatchError, SendNotInstalledError, SendProvider, SendRpcErrorCode, detectSendAuthTimeout, mapSigilryError, safePreview, templateIdHint };
|
|
618
|
+
//# sourceMappingURL=index.mjs.map
|
|
619
|
+
//# sourceMappingURL=index.mjs.map
|