@bootnodedev/canton-connect 0.3.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 +155 -0
- package/dist/CantonConnectProvider-CJ1yWGXz.js +777 -0
- package/dist/index.d.ts +342 -0
- package/dist/index.js +444 -0
- package/dist/testing/index.d.ts +127 -0
- package/dist/testing/index.js +291 -0
- package/dist/types-Deu_03jh.d.ts +385 -0
- package/package.json +86 -0
package/dist/index.js
ADDED
|
@@ -0,0 +1,444 @@
|
|
|
1
|
+
import { a as toConnectionStatus, c as toError, n as CantonConnectProvider, o as ConnectCancelledError, r as useCantonConnectContext, s as toConnectError } from "./CantonConnectProvider-CJ1yWGXz.js";
|
|
2
|
+
import { useCallback, useEffect, useMemo, useState } from "react";
|
|
3
|
+
import { useSelector } from "@xstate/react";
|
|
4
|
+
//#region src/hooks/useConnect.ts
|
|
5
|
+
/**
|
|
6
|
+
* Connects the wallet and reports that transition. `connect` takes no argument: the picker chooses
|
|
7
|
+
* the wallet, so there is no mode to pass. Gate a pending face on `isPending` and
|
|
8
|
+
* session-dependent content on `useParty().party`, not on `isConnected`.
|
|
9
|
+
*
|
|
10
|
+
* @throws with no {@link CantonConnectProvider} above it, as every hook here does.
|
|
11
|
+
*
|
|
12
|
+
* @example
|
|
13
|
+
* const { connect, isPending } = useConnect()
|
|
14
|
+
* <button onClick={() => void connect().catch(() => undefined)} disabled={isPending}>
|
|
15
|
+
* Connect
|
|
16
|
+
* </button>
|
|
17
|
+
*
|
|
18
|
+
* @category Hooks
|
|
19
|
+
*/
|
|
20
|
+
const useConnect = () => {
|
|
21
|
+
const { cancelConnect, connect, connection, resetConnectError } = useCantonConnectContext();
|
|
22
|
+
const status = useSelector(connection, toConnectionStatus);
|
|
23
|
+
const isPending = useSelector(connection, (snapshot) => snapshot.hasTag("connecting"));
|
|
24
|
+
const lastConnectError = useSelector(connection, (snapshot) => snapshot.context.lastConnectError);
|
|
25
|
+
const error = useMemo(() => lastConnectError === void 0 ? void 0 : toConnectError(lastConnectError), [lastConnectError]);
|
|
26
|
+
return {
|
|
27
|
+
cancelConnect,
|
|
28
|
+
connect,
|
|
29
|
+
isPending,
|
|
30
|
+
isConnected: status === "connected",
|
|
31
|
+
error,
|
|
32
|
+
reset: resetConnectError
|
|
33
|
+
};
|
|
34
|
+
};
|
|
35
|
+
//#endregion
|
|
36
|
+
//#region src/hooks/useDisconnect.ts
|
|
37
|
+
/**
|
|
38
|
+
* Disconnects the wallet and reports that transition. No `error`: a disconnect always settles, by
|
|
39
|
+
* the timeout if the wallet never answers.
|
|
40
|
+
*
|
|
41
|
+
* @throws with no {@link CantonConnectProvider} above it, as every hook here does.
|
|
42
|
+
*
|
|
43
|
+
* @example
|
|
44
|
+
* const { disconnect, isPending } = useDisconnect()
|
|
45
|
+
* <button onClick={() => void disconnect()} disabled={isPending}>
|
|
46
|
+
* Disconnect
|
|
47
|
+
* </button>
|
|
48
|
+
*
|
|
49
|
+
* @category Hooks
|
|
50
|
+
*/
|
|
51
|
+
const useDisconnect = () => {
|
|
52
|
+
const { connection, disconnect } = useCantonConnectContext();
|
|
53
|
+
return {
|
|
54
|
+
disconnect,
|
|
55
|
+
isPending: useSelector(connection, toConnectionStatus) === "disconnecting"
|
|
56
|
+
};
|
|
57
|
+
};
|
|
58
|
+
//#endregion
|
|
59
|
+
//#region src/hooks/useTxFeed.ts
|
|
60
|
+
/** Tracks the SDK's `txChanged` pushes while a session is active, clearing on session end. */
|
|
61
|
+
const useTxFeed = (sdk, connection) => {
|
|
62
|
+
const [lastTx, setLastTx] = useState(void 0);
|
|
63
|
+
const sessionActive = useSelector(connection, (snapshot) => snapshot.matches("session"));
|
|
64
|
+
useEffect(() => {
|
|
65
|
+
if (!sessionActive) {
|
|
66
|
+
setLastTx(void 0);
|
|
67
|
+
return;
|
|
68
|
+
}
|
|
69
|
+
const onTx = (event) => {
|
|
70
|
+
setLastTx({
|
|
71
|
+
status: event.status,
|
|
72
|
+
commandId: event.commandId,
|
|
73
|
+
payload: "payload" in event ? event.payload : void 0
|
|
74
|
+
});
|
|
75
|
+
};
|
|
76
|
+
sdk.onTxChanged(onTx).catch(() => void 0);
|
|
77
|
+
return () => {
|
|
78
|
+
sdk.removeOnTxChanged(onTx).catch(() => void 0);
|
|
79
|
+
};
|
|
80
|
+
}, [sessionActive, sdk]);
|
|
81
|
+
return lastTx;
|
|
82
|
+
};
|
|
83
|
+
//#endregion
|
|
84
|
+
//#region src/hooks/useWalletCall.ts
|
|
85
|
+
/** The resting state, hoisted so a hook that never called keeps one identity across renders. */
|
|
86
|
+
const IDLE = {
|
|
87
|
+
isPending: false,
|
|
88
|
+
error: void 0
|
|
89
|
+
};
|
|
90
|
+
/** Throws when the wallet is disconnected or locked, the guard every SDK-calling hook shares. */
|
|
91
|
+
const assertUsable = (status, isLocked) => {
|
|
92
|
+
if (status !== "connected") throw new Error("wallet is not connected - call useConnect().connect() first");
|
|
93
|
+
if (isLocked) throw new Error("wallet is locked - unlock it in the wallet");
|
|
94
|
+
};
|
|
95
|
+
/** Throws when the session reports no party, which a connected one can. */
|
|
96
|
+
function assertPartyId(partyId) {
|
|
97
|
+
if (partyId === void 0) throw new Error("wallet reports no usable party - allocate one in the wallet");
|
|
98
|
+
}
|
|
99
|
+
/**
|
|
100
|
+
* Selects the session and wraps one SDK call with the connect, lock and party guards plus the
|
|
101
|
+
* pending/error bookkeeping `useExecute` and `useSignMessage` share.
|
|
102
|
+
*/
|
|
103
|
+
const useWalletCall = () => {
|
|
104
|
+
const { connection } = useCantonConnectContext();
|
|
105
|
+
const sdk = useSelector(connection, (snapshot) => snapshot.context.sdk);
|
|
106
|
+
const partyId = useSelector(connection, (snapshot) => snapshot.context.party?.partyId);
|
|
107
|
+
const status = useSelector(connection, toConnectionStatus);
|
|
108
|
+
const isLocked = useSelector(connection, (snapshot) => snapshot.hasTag("unauthenticated"));
|
|
109
|
+
const [state, setState] = useState(IDLE);
|
|
110
|
+
const call = useCallback(async (run) => {
|
|
111
|
+
assertUsable(status, isLocked);
|
|
112
|
+
assertPartyId(partyId);
|
|
113
|
+
setState({
|
|
114
|
+
isPending: true,
|
|
115
|
+
error: void 0
|
|
116
|
+
});
|
|
117
|
+
try {
|
|
118
|
+
const result = await run(sdk, partyId);
|
|
119
|
+
setState(IDLE);
|
|
120
|
+
return result;
|
|
121
|
+
} catch (err) {
|
|
122
|
+
const error = toError(err);
|
|
123
|
+
setState({
|
|
124
|
+
isPending: false,
|
|
125
|
+
error
|
|
126
|
+
});
|
|
127
|
+
throw error;
|
|
128
|
+
}
|
|
129
|
+
}, [
|
|
130
|
+
isLocked,
|
|
131
|
+
partyId,
|
|
132
|
+
sdk,
|
|
133
|
+
status
|
|
134
|
+
]);
|
|
135
|
+
const reset = useCallback(() => setState(IDLE), []);
|
|
136
|
+
return {
|
|
137
|
+
call,
|
|
138
|
+
isPending: state.isPending,
|
|
139
|
+
error: state.error,
|
|
140
|
+
reset,
|
|
141
|
+
connection,
|
|
142
|
+
sdk,
|
|
143
|
+
status,
|
|
144
|
+
isLocked
|
|
145
|
+
};
|
|
146
|
+
};
|
|
147
|
+
//#endregion
|
|
148
|
+
//#region src/hooks/useExecute.ts
|
|
149
|
+
/** Defaults `actAs` to the connected party, leaving a caller's own `actAs` untouched. */
|
|
150
|
+
const withActAs = (params, partyId) => params.actAs === void 0 ? {
|
|
151
|
+
...params,
|
|
152
|
+
actAs: [partyId]
|
|
153
|
+
} : params;
|
|
154
|
+
/**
|
|
155
|
+
* Submits ledger commands and tracks the transaction in `lastTx`, fed by the SDK's `txChanged`
|
|
156
|
+
* event. `actAs` defaults to the party `useParty` reports, so a submit acts as the party the UI
|
|
157
|
+
* shows rather than the wallet's own primary.
|
|
158
|
+
*
|
|
159
|
+
* @throws with no {@link CantonConnectProvider} above it, and from `execute` where nothing is
|
|
160
|
+
* connected or no party is reported. A command that fails throws too, and lands in `error`.
|
|
161
|
+
*
|
|
162
|
+
* @example
|
|
163
|
+
* const { execute, lastTx } = useExecute()
|
|
164
|
+
* await execute({ commandId: 'claim-1', commands })
|
|
165
|
+
* lastTx?.status // 'pending' | 'signed' | 'executed' | 'failed'
|
|
166
|
+
*
|
|
167
|
+
* @category Hooks
|
|
168
|
+
*/
|
|
169
|
+
const useExecute = () => {
|
|
170
|
+
const { call, isPending, error, reset, connection, sdk } = useWalletCall();
|
|
171
|
+
const lastTx = useTxFeed(sdk, connection);
|
|
172
|
+
return {
|
|
173
|
+
execute: useCallback((params) => call((walletSdk, actingPartyId) => walletSdk.prepareExecuteAndWait(withActAs(params, actingPartyId))), [call]),
|
|
174
|
+
lastTx,
|
|
175
|
+
isPending,
|
|
176
|
+
error,
|
|
177
|
+
reset
|
|
178
|
+
};
|
|
179
|
+
};
|
|
180
|
+
//#endregion
|
|
181
|
+
//#region src/hooks/useLedger.ts
|
|
182
|
+
/**
|
|
183
|
+
* Escape hatch for ledger reads `useExecute` and `useSignMessage` do not cover: the participant's
|
|
184
|
+
* JSON API, passed through untyped.
|
|
185
|
+
*
|
|
186
|
+
* @throws with no {@link CantonConnectProvider} above it, and from `ledgerApi` itself where nothing
|
|
187
|
+
* is connected, which `isReady` is there to check first.
|
|
188
|
+
*
|
|
189
|
+
* @example
|
|
190
|
+
* const { ledgerApi } = useLedger()
|
|
191
|
+
* await ledgerApi({ requestMethod: 'get', resource: '/v2/state/ledger-end' })
|
|
192
|
+
*
|
|
193
|
+
* @category Hooks
|
|
194
|
+
*/
|
|
195
|
+
const useLedger = () => {
|
|
196
|
+
const { sdk, status, isLocked } = useWalletCall();
|
|
197
|
+
return {
|
|
198
|
+
ledgerApi: useCallback(async (params) => {
|
|
199
|
+
assertUsable(status, isLocked);
|
|
200
|
+
return await sdk.ledgerApi(params);
|
|
201
|
+
}, [
|
|
202
|
+
isLocked,
|
|
203
|
+
sdk,
|
|
204
|
+
status
|
|
205
|
+
]),
|
|
206
|
+
isReady: status === "connected" && !isLocked
|
|
207
|
+
};
|
|
208
|
+
};
|
|
209
|
+
//#endregion
|
|
210
|
+
//#region src/hooks/useParty.ts
|
|
211
|
+
/**
|
|
212
|
+
* The connected account and status. `party` is `undefined` until a connect succeeds, and again
|
|
213
|
+
* whenever a restored session is locked.
|
|
214
|
+
*
|
|
215
|
+
* @throws with no {@link CantonConnectProvider} above it.
|
|
216
|
+
*
|
|
217
|
+
* @example
|
|
218
|
+
* const { party, isConnected } = useParty()
|
|
219
|
+
* isConnected && <span>{party?.partyId}</span>
|
|
220
|
+
*
|
|
221
|
+
* @category Hooks
|
|
222
|
+
*/
|
|
223
|
+
const useParty = () => {
|
|
224
|
+
const { connection } = useCantonConnectContext();
|
|
225
|
+
const party = useSelector(connection, (snapshot) => snapshot.context.party);
|
|
226
|
+
const status = useSelector(connection, toConnectionStatus);
|
|
227
|
+
return {
|
|
228
|
+
party,
|
|
229
|
+
status,
|
|
230
|
+
isConnected: status === "connected"
|
|
231
|
+
};
|
|
232
|
+
};
|
|
233
|
+
//#endregion
|
|
234
|
+
//#region src/hooks/usePartyType.ts
|
|
235
|
+
const namespaceOf = (id) => /::(.+)$/.exec(id)?.[1];
|
|
236
|
+
const readParticipantNamespace = async (sdk) => {
|
|
237
|
+
const answer = await sdk.ledgerApi({
|
|
238
|
+
requestMethod: "get",
|
|
239
|
+
resource: "/v2/parties/participant-id"
|
|
240
|
+
});
|
|
241
|
+
const namespace = typeof answer.participantId === "string" ? namespaceOf(answer.participantId) : void 0;
|
|
242
|
+
if (namespace === void 0) throw new Error(`participant id not found in ${JSON.stringify(answer)}`);
|
|
243
|
+
return namespace;
|
|
244
|
+
};
|
|
245
|
+
/**
|
|
246
|
+
* Tells a local party from an external one when asked. Each `readPartyType` call is one
|
|
247
|
+
* `ledgerApi` read of the participant id, its namespace compared with the party's; nothing is
|
|
248
|
+
* cached, so hold the answer where several components need it. Reach for it before an action a
|
|
249
|
+
* local party cannot take, such as `signMessage`, which the reference gateway refuses.
|
|
250
|
+
*
|
|
251
|
+
* @throws with no {@link CantonConnectProvider} above it, and from `readPartyType` where nothing
|
|
252
|
+
* is connected or no party is reported, which `isReady` is there to check first.
|
|
253
|
+
*
|
|
254
|
+
* @example
|
|
255
|
+
* const { readPartyType } = usePartyType()
|
|
256
|
+
* if ((await readPartyType()) === 'local') {
|
|
257
|
+
* toast.error('This wallet cannot sign messages for a local party')
|
|
258
|
+
* }
|
|
259
|
+
*
|
|
260
|
+
* @category Hooks
|
|
261
|
+
*/
|
|
262
|
+
const usePartyType = () => {
|
|
263
|
+
const { connection, sdk, status, isLocked } = useWalletCall();
|
|
264
|
+
const party = useSelector(connection, (snapshot) => snapshot.context.party);
|
|
265
|
+
return {
|
|
266
|
+
readPartyType: useCallback(async () => {
|
|
267
|
+
assertUsable(status, isLocked);
|
|
268
|
+
if (party === void 0) throw new Error("wallet reports no usable party - allocate one in the wallet");
|
|
269
|
+
const participantNamespace = await readParticipantNamespace(sdk);
|
|
270
|
+
return party.namespace === participantNamespace ? "local" : "external";
|
|
271
|
+
}, [
|
|
272
|
+
isLocked,
|
|
273
|
+
party,
|
|
274
|
+
sdk,
|
|
275
|
+
status
|
|
276
|
+
]),
|
|
277
|
+
isReady: status === "connected" && !isLocked && party !== void 0
|
|
278
|
+
};
|
|
279
|
+
};
|
|
280
|
+
//#endregion
|
|
281
|
+
//#region src/hooks/useSignMessage.ts
|
|
282
|
+
/**
|
|
283
|
+
* Signs an arbitrary message with the connected wallet; the SDK owns the encoding.
|
|
284
|
+
*
|
|
285
|
+
* @throws with no {@link CantonConnectProvider} above it, and from `signMessage` where nothing is
|
|
286
|
+
* connected or no party is reported. A wallet refusal throws too, and lands in `error`.
|
|
287
|
+
*
|
|
288
|
+
* @example
|
|
289
|
+
* const { signMessage } = useSignMessage()
|
|
290
|
+
* const signed = await signMessage('Approve vesting claim')
|
|
291
|
+
*
|
|
292
|
+
* @category Hooks
|
|
293
|
+
*/
|
|
294
|
+
const useSignMessage = () => {
|
|
295
|
+
const { call, isPending, error, reset: resetCall } = useWalletCall();
|
|
296
|
+
const [signature, setSignature] = useState(void 0);
|
|
297
|
+
return {
|
|
298
|
+
signMessage: useCallback(async (message) => {
|
|
299
|
+
setSignature(void 0);
|
|
300
|
+
const result = await call((walletSdk) => walletSdk.signMessage({ message }));
|
|
301
|
+
setSignature(result.signature);
|
|
302
|
+
return result.signature;
|
|
303
|
+
}, [call]),
|
|
304
|
+
signature,
|
|
305
|
+
isPending,
|
|
306
|
+
error,
|
|
307
|
+
reset: useCallback(() => {
|
|
308
|
+
setSignature(void 0);
|
|
309
|
+
resetCall();
|
|
310
|
+
}, [resetCall])
|
|
311
|
+
};
|
|
312
|
+
};
|
|
313
|
+
//#endregion
|
|
314
|
+
//#region src/hooks/useWalletStatus.ts
|
|
315
|
+
/**
|
|
316
|
+
* Reports the session and lock state from the wallet's own pushes. A wallet that disconnected on
|
|
317
|
+
* its own pushed the same thing as a lock, so `isLocked` cannot tell them apart.
|
|
318
|
+
*
|
|
319
|
+
* @throws with no {@link CantonConnectProvider} above it.
|
|
320
|
+
*
|
|
321
|
+
* @example
|
|
322
|
+
* const { isConnected, isLocked } = useWalletStatus()
|
|
323
|
+
* if (!isConnected) return <p>No session.</p>
|
|
324
|
+
* return isLocked ? <p>Unlock your wallet to continue.</p> : <p>Ready.</p>
|
|
325
|
+
*
|
|
326
|
+
* @category Hooks
|
|
327
|
+
*/
|
|
328
|
+
const useWalletStatus = () => {
|
|
329
|
+
const { connection } = useCantonConnectContext();
|
|
330
|
+
return {
|
|
331
|
+
isLocked: useSelector(connection, (snapshot) => snapshot.hasTag("unauthenticated")),
|
|
332
|
+
isConnected: useSelector(connection, toConnectionStatus) === "connected"
|
|
333
|
+
};
|
|
334
|
+
};
|
|
335
|
+
//#endregion
|
|
336
|
+
//#region src/mock/mockAdapter.ts
|
|
337
|
+
const DEFAULT_PROVIDER_ID = "mock";
|
|
338
|
+
const DEFAULT_NAME = "Mock Wallet";
|
|
339
|
+
const DEFAULT_DESCRIPTION = "Mock wallet for dev and tests — no real signing, never a live wallet";
|
|
340
|
+
const MOCK_WALLET_STATUS = "allocated";
|
|
341
|
+
const MOCK_SIGNING_PROVIDER_ID = "mock";
|
|
342
|
+
const MOCK_PUBLIC_KEY = "mock-public-key";
|
|
343
|
+
/** The single mock account `createMockAdapter` reports when the caller supplies none. */
|
|
344
|
+
const defaultAccounts = (providerId) => [{ partyId: `${providerId}::1220abcd` }];
|
|
345
|
+
/** Shapes one `MockAccount` into the `Wallet` the mock adapter's `listAccounts` returns. */
|
|
346
|
+
const toWallet = (account, primary, networkId) => ({
|
|
347
|
+
primary,
|
|
348
|
+
partyId: account.partyId,
|
|
349
|
+
status: MOCK_WALLET_STATUS,
|
|
350
|
+
hint: account.name ?? account.partyId,
|
|
351
|
+
publicKey: account.publicKey ?? MOCK_PUBLIC_KEY,
|
|
352
|
+
namespace: account.partyId.split("::")[1] ?? account.partyId,
|
|
353
|
+
signingProviderId: MOCK_SIGNING_PROVIDER_ID,
|
|
354
|
+
...networkId === void 0 ? {} : { networkId }
|
|
355
|
+
});
|
|
356
|
+
/**
|
|
357
|
+
* The adapter `createMockAdapter` returns: it announces itself like an installed wallet and
|
|
358
|
+
* answers the connect flow from canned accounts, with no wallet present.
|
|
359
|
+
*/
|
|
360
|
+
var MockProviderAdapter = class {
|
|
361
|
+
providerId;
|
|
362
|
+
name = DEFAULT_NAME;
|
|
363
|
+
type = "browser";
|
|
364
|
+
wallets;
|
|
365
|
+
connected = false;
|
|
366
|
+
listenerMap = {};
|
|
367
|
+
constructor(options) {
|
|
368
|
+
this.providerId = options.id ?? DEFAULT_PROVIDER_ID;
|
|
369
|
+
const accounts = options.accounts ?? defaultAccounts(this.providerId);
|
|
370
|
+
this.wallets = accounts.map((account, index) => toWallet(account, index === 0, options.networkId));
|
|
371
|
+
}
|
|
372
|
+
getInfo() {
|
|
373
|
+
return {
|
|
374
|
+
providerId: this.providerId,
|
|
375
|
+
name: this.name,
|
|
376
|
+
type: this.type,
|
|
377
|
+
description: DEFAULT_DESCRIPTION
|
|
378
|
+
};
|
|
379
|
+
}
|
|
380
|
+
async detect() {
|
|
381
|
+
return true;
|
|
382
|
+
}
|
|
383
|
+
provider() {
|
|
384
|
+
return this;
|
|
385
|
+
}
|
|
386
|
+
teardown() {}
|
|
387
|
+
handlers = {
|
|
388
|
+
connect: () => {
|
|
389
|
+
this.connected = true;
|
|
390
|
+
return {
|
|
391
|
+
isConnected: true,
|
|
392
|
+
isNetworkConnected: true
|
|
393
|
+
};
|
|
394
|
+
},
|
|
395
|
+
disconnect: () => {
|
|
396
|
+
this.connected = false;
|
|
397
|
+
return null;
|
|
398
|
+
},
|
|
399
|
+
status: () => ({
|
|
400
|
+
provider: {
|
|
401
|
+
id: this.providerId,
|
|
402
|
+
providerType: this.type
|
|
403
|
+
},
|
|
404
|
+
connection: {
|
|
405
|
+
isConnected: this.connected,
|
|
406
|
+
isNetworkConnected: true
|
|
407
|
+
}
|
|
408
|
+
}),
|
|
409
|
+
listAccounts: () => this.wallets
|
|
410
|
+
};
|
|
411
|
+
async request(args) {
|
|
412
|
+
const handler = this.handlers[args.method];
|
|
413
|
+
if (handler === void 0) throw new Error(`mock adapter does not implement '${args.method}'`);
|
|
414
|
+
return handler();
|
|
415
|
+
}
|
|
416
|
+
on = (event, listener) => {
|
|
417
|
+
const listeners = this.listenerMap[event] ?? [];
|
|
418
|
+
listeners.push(listener);
|
|
419
|
+
this.listenerMap[event] = listeners;
|
|
420
|
+
return this;
|
|
421
|
+
};
|
|
422
|
+
emit = (event, ...args) => {
|
|
423
|
+
for (const listener of this.listenerMap[event] ?? []) listener(...args);
|
|
424
|
+
return true;
|
|
425
|
+
};
|
|
426
|
+
removeListener = (event, listenerToRemove) => {
|
|
427
|
+
this.listenerMap[event] = (this.listenerMap[event] ?? []).filter((listener) => listener !== listenerToRemove);
|
|
428
|
+
return this;
|
|
429
|
+
};
|
|
430
|
+
};
|
|
431
|
+
/**
|
|
432
|
+
* Answers the connect flow with canned data, so `CantonConnectProvider` runs with no wallet
|
|
433
|
+
* installed; pass it via `CantonConnectConfig.additionalAdapters`. Anything outside that flow
|
|
434
|
+
* throws naming the method; a canned result would be indistinguishable from a real one. Reach for
|
|
435
|
+
* `createFakeWallet` instead to exercise the SDK's real extension transport.
|
|
436
|
+
*
|
|
437
|
+
* @example
|
|
438
|
+
* const config = { appName: 'Vesting', additionalAdapters: [createMockAdapter()] }
|
|
439
|
+
*
|
|
440
|
+
* @category Utilities
|
|
441
|
+
*/
|
|
442
|
+
const createMockAdapter = (options = {}) => new MockProviderAdapter(options);
|
|
443
|
+
//#endregion
|
|
444
|
+
export { CantonConnectProvider, ConnectCancelledError, createMockAdapter, useCantonConnectContext, useConnect, useDisconnect, useExecute, useLedger, useParty, usePartyType, useSignMessage, useWalletStatus };
|
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
import { a as Party, c as WalletSdk, r as ConnectionStatus } from "../types-Deu_03jh.js";
|
|
2
|
+
import { WalletPickerFn } from "@canton-network/dapp-sdk";
|
|
3
|
+
import { JSX, ReactNode } from "react";
|
|
4
|
+
//#region src/testing/autoPicker.d.ts
|
|
5
|
+
/**
|
|
6
|
+
* A `walletPicker` that selects with no UI, for tests and headless dev flows: the entry whose
|
|
7
|
+
* `providerId` matches `pick`, or the first discovered one.
|
|
8
|
+
*
|
|
9
|
+
* @throws when no discovered entry matches `pick`, so a test naming a wallet that never registered
|
|
10
|
+
* fails at the picker rather than at the connect.
|
|
11
|
+
*
|
|
12
|
+
* @example
|
|
13
|
+
* const config = { appName: 'Vesting', walletPicker: createAutoPicker('mock') }
|
|
14
|
+
*
|
|
15
|
+
* @category Utilities
|
|
16
|
+
*/
|
|
17
|
+
declare const createAutoPicker: (pick?: string) => WalletPickerFn;
|
|
18
|
+
//#endregion
|
|
19
|
+
//#region src/testing/fakeSession.d.ts
|
|
20
|
+
/**
|
|
21
|
+
* Props for {@link FakeSessionProvider}. `status` starts the session mid-flight and `party` is
|
|
22
|
+
* what a connect resolves to; `readingAccounts` reaches the pending face over a live session, and
|
|
23
|
+
* `sdk` drives a hook's own pending, error and `reset()`, never what a wallet returns.
|
|
24
|
+
*
|
|
25
|
+
* @category Components
|
|
26
|
+
*/
|
|
27
|
+
interface FakeSessionProviderProps {
|
|
28
|
+
children: ReactNode;
|
|
29
|
+
connectError?: Error;
|
|
30
|
+
isLocked?: boolean;
|
|
31
|
+
party?: Party;
|
|
32
|
+
readingAccounts?: boolean;
|
|
33
|
+
sdk?: Partial<WalletSdk>;
|
|
34
|
+
status?: ConnectionStatus;
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* Stands in for `CantonConnectProvider` with the session already in a given shape, so a component
|
|
38
|
+
* test asserts on markup without paying the SDK's discovery sleeps or its connect flow. The shape
|
|
39
|
+
* is a real `connectionMachine` actor rehydrated at the state the props ask for, so the hooks
|
|
40
|
+
* select from it exactly as they do in the app. `connect` and `disconnect` move the session, but
|
|
41
|
+
* reach for the real provider plus `createMockAdapter` to test connecting itself — the intermediate
|
|
42
|
+
* states here are not the SDK's.
|
|
43
|
+
*
|
|
44
|
+
* @example
|
|
45
|
+
* render(
|
|
46
|
+
* <FakeSessionProvider status="connected" party={party}>
|
|
47
|
+
* <ConnectButton />
|
|
48
|
+
* </FakeSessionProvider>,
|
|
49
|
+
* )
|
|
50
|
+
*
|
|
51
|
+
* @category Components
|
|
52
|
+
*/
|
|
53
|
+
declare const FakeSessionProvider: ({ children, connectError, isLocked, party, readingAccounts, sdk, status: initialStatus }: FakeSessionProviderProps) => JSX.Element;
|
|
54
|
+
//#endregion
|
|
55
|
+
//#region src/testing/fakeWallet.d.ts
|
|
56
|
+
/**
|
|
57
|
+
* One account the fake wallet reports from `listAccounts`. Mark exactly one `primary`: that is the
|
|
58
|
+
* entry `selectPrimaryAccount` resolves to `Party`.
|
|
59
|
+
*
|
|
60
|
+
* @category Utilities
|
|
61
|
+
*/
|
|
62
|
+
interface FakeWalletAccount {
|
|
63
|
+
partyId: string;
|
|
64
|
+
primary?: boolean;
|
|
65
|
+
name?: string;
|
|
66
|
+
publicKey?: string;
|
|
67
|
+
networkId?: string;
|
|
68
|
+
}
|
|
69
|
+
/**
|
|
70
|
+
* Wiring for {@link createFakeWallet}. `id` is announced to `window` and doubles as the postMessage
|
|
71
|
+
* target and the display name unless `target` or `name` override it, and `accounts` defaults to one
|
|
72
|
+
* account with `id` as its party prefix. `statusResponses` is `isConnected` per successive `status`
|
|
73
|
+
* call, last entry repeating, which is how a test restores a session and then reports it locked.
|
|
74
|
+
*
|
|
75
|
+
* @example
|
|
76
|
+
* const options: FakeWalletOptions = { id: 'mock', statusResponses: [true, false] }
|
|
77
|
+
*
|
|
78
|
+
* @category Utilities
|
|
79
|
+
*/
|
|
80
|
+
interface FakeWalletOptions {
|
|
81
|
+
id: string;
|
|
82
|
+
name?: string;
|
|
83
|
+
target?: string;
|
|
84
|
+
accounts?: FakeWalletAccount[];
|
|
85
|
+
statusResponses?: boolean[];
|
|
86
|
+
}
|
|
87
|
+
/**
|
|
88
|
+
* Handles on a running fake wallet: `announce` re-announces it as if the extension had just loaded,
|
|
89
|
+
* `push` sends an unsolicited notification the way a real one does (`'statusChanged'`, say), and
|
|
90
|
+
* `dispose` removes the `window` listeners it installed, which every test must do in teardown.
|
|
91
|
+
*
|
|
92
|
+
* @category Utilities
|
|
93
|
+
*/
|
|
94
|
+
interface FakeWallet {
|
|
95
|
+
announce: () => void;
|
|
96
|
+
push: (method: string, params: unknown) => void;
|
|
97
|
+
dispose: () => void;
|
|
98
|
+
}
|
|
99
|
+
/**
|
|
100
|
+
* A fake CIP-0103 extension wallet for tests. It speaks the real postMessage protocol, so it
|
|
101
|
+
* exercises the SDK's genuine `ExtensionAdapter` rather than a stub. Answers `connect`, `status`,
|
|
102
|
+
* `listAccounts` and `disconnect`; anything else rejects naming the method. Reach for
|
|
103
|
+
* `createMockAdapter` instead where the transport is not what is under test.
|
|
104
|
+
*
|
|
105
|
+
* @example
|
|
106
|
+
* const wallet = createFakeWallet({ id: 'mock' })
|
|
107
|
+
* wallet.push('statusChanged', { connection: { isConnected: false } })
|
|
108
|
+
* wallet.dispose()
|
|
109
|
+
*
|
|
110
|
+
* @category Utilities
|
|
111
|
+
*/
|
|
112
|
+
declare const createFakeWallet: (options: FakeWalletOptions) => FakeWallet;
|
|
113
|
+
//#endregion
|
|
114
|
+
//#region src/testing/pause.d.ts
|
|
115
|
+
/**
|
|
116
|
+
* Real-timer sleep, which is how a suite awaits a promise-settling send; `pause(0)` flushes the
|
|
117
|
+
* pending macrotasks. Reach for it over `waitFor`, whose retry window turns a genuine red into a
|
|
118
|
+
* timeout rather than a failed assertion.
|
|
119
|
+
*
|
|
120
|
+
* @example
|
|
121
|
+
* await pause(0) // one macrotask on, so the send above has settled
|
|
122
|
+
*
|
|
123
|
+
* @category Utilities
|
|
124
|
+
*/
|
|
125
|
+
declare const pause: (ms: number) => Promise<unknown>;
|
|
126
|
+
//#endregion
|
|
127
|
+
export { FakeSessionProvider, type FakeSessionProviderProps, type FakeWallet, type FakeWalletAccount, type FakeWalletOptions, createAutoPicker, createFakeWallet, pause };
|