@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
|
@@ -0,0 +1,291 @@
|
|
|
1
|
+
import { i as connectionMachine, t as CantonConnectContext } from "../CantonConnectProvider-CJ1yWGXz.js";
|
|
2
|
+
import { useCallback, useEffect, useMemo, useState } from "react";
|
|
3
|
+
import { createActor } from "xstate";
|
|
4
|
+
import { CANTON_ANNOUNCE_PROVIDER_EVENT, CANTON_REQUEST_PROVIDER_EVENT, WalletEvent } from "@canton-network/core-types";
|
|
5
|
+
import { jsx } from "react/jsx-runtime";
|
|
6
|
+
//#region src/testing/autoPicker.ts
|
|
7
|
+
/**
|
|
8
|
+
* A `walletPicker` that selects with no UI, for tests and headless dev flows: the entry whose
|
|
9
|
+
* `providerId` matches `pick`, or the first discovered one.
|
|
10
|
+
*
|
|
11
|
+
* @throws when no discovered entry matches `pick`, so a test naming a wallet that never registered
|
|
12
|
+
* fails at the picker rather than at the connect.
|
|
13
|
+
*
|
|
14
|
+
* @example
|
|
15
|
+
* const config = { appName: 'Vesting', walletPicker: createAutoPicker('mock') }
|
|
16
|
+
*
|
|
17
|
+
* @category Utilities
|
|
18
|
+
*/
|
|
19
|
+
const createAutoPicker = (pick) => async (entries) => {
|
|
20
|
+
const chosen = pick === void 0 ? entries[0] : entries.find((entry) => entry.providerId === pick);
|
|
21
|
+
if (chosen === void 0) throw new Error(`auto-picker: no wallet matching ${pick ?? "(first)"}`);
|
|
22
|
+
return chosen;
|
|
23
|
+
};
|
|
24
|
+
//#endregion
|
|
25
|
+
//#region src/testing/connectionInput.ts
|
|
26
|
+
/** A `WalletSdk` method stand-in that returns a promise which never settles. */
|
|
27
|
+
const pending = () => new Promise(() => {});
|
|
28
|
+
/** Every `WalletSdk` method left hanging, so a test only stubs the ones its path reaches. */
|
|
29
|
+
const unstubbed = {
|
|
30
|
+
connect: pending,
|
|
31
|
+
disconnect: pending,
|
|
32
|
+
init: pending,
|
|
33
|
+
ledgerApi: pending,
|
|
34
|
+
listAccounts: pending,
|
|
35
|
+
onAccountsChanged: pending,
|
|
36
|
+
onStatusChanged: pending,
|
|
37
|
+
onTxChanged: pending,
|
|
38
|
+
prepareExecuteAndWait: pending,
|
|
39
|
+
removeOnAccountsChanged: pending,
|
|
40
|
+
removeOnStatusChanged: pending,
|
|
41
|
+
removeOnTxChanged: pending,
|
|
42
|
+
signMessage: pending,
|
|
43
|
+
status: pending
|
|
44
|
+
};
|
|
45
|
+
/**
|
|
46
|
+
* Machine input for a test actor, over a double satisfying the `WalletSdk` the machine types.
|
|
47
|
+
* Whatever the double leaves out never settles, and every `createSdk()` hands back a fresh object,
|
|
48
|
+
* as `new DappSDK` does — so a retirement changes `context.sdk` here too.
|
|
49
|
+
*
|
|
50
|
+
* @example
|
|
51
|
+
* const actor = createActor(machine, { input: connectionInput(sdkDouble) })
|
|
52
|
+
*/
|
|
53
|
+
const connectionInput = (sdk = {}, overrides = {}) => ({
|
|
54
|
+
createSdk: () => ({
|
|
55
|
+
...unstubbed,
|
|
56
|
+
...sdk
|
|
57
|
+
}),
|
|
58
|
+
initOptions: {},
|
|
59
|
+
guardPicker: false,
|
|
60
|
+
networkId: "canton:local",
|
|
61
|
+
...overrides
|
|
62
|
+
});
|
|
63
|
+
//#endregion
|
|
64
|
+
//#region src/testing/fakeSession.tsx
|
|
65
|
+
const CONFIG = { appName: "fake-session" };
|
|
66
|
+
const NO_SDK = {};
|
|
67
|
+
/** A `sdk` wrapper that throws naming the method for anything the test never stubbed. */
|
|
68
|
+
const refusingSdk = (supplied) => new Proxy({}, { get: (_, key) => {
|
|
69
|
+
const method = supplied[key];
|
|
70
|
+
if (method === void 0) throw new Error(`fake session has no sdk.${String(key)} — drive the real provider for that`);
|
|
71
|
+
return method;
|
|
72
|
+
} });
|
|
73
|
+
/** Turns a `SessionShape` into the state value the real machine would hold for it. */
|
|
74
|
+
const toStateValue = ({ isLocked, readingAccounts, status }) => {
|
|
75
|
+
if (status !== "connected") return status;
|
|
76
|
+
if (isLocked) return { session: "unauthenticated" };
|
|
77
|
+
return { session: { authenticated: readingAccounts ? "reading" : "ready" } };
|
|
78
|
+
};
|
|
79
|
+
/** Starts a real `connectionMachine` actor rehydrated at the given `SessionShape`. */
|
|
80
|
+
const startSession = (shape, party, connectError, sdk) => {
|
|
81
|
+
const input = connectionInput({}, { createSdk: () => sdk });
|
|
82
|
+
const snapshot = connectionMachine.resolveState({
|
|
83
|
+
value: toStateValue(shape),
|
|
84
|
+
context: {
|
|
85
|
+
...input,
|
|
86
|
+
sdk,
|
|
87
|
+
lastConnectError: connectError,
|
|
88
|
+
party: shape.status === "connected" ? party : void 0
|
|
89
|
+
}
|
|
90
|
+
});
|
|
91
|
+
return createActor(connectionMachine, {
|
|
92
|
+
input,
|
|
93
|
+
snapshot
|
|
94
|
+
}).start();
|
|
95
|
+
};
|
|
96
|
+
/**
|
|
97
|
+
* Stands in for `CantonConnectProvider` with the session already in a given shape, so a component
|
|
98
|
+
* test asserts on markup without paying the SDK's discovery sleeps or its connect flow. The shape
|
|
99
|
+
* is a real `connectionMachine` actor rehydrated at the state the props ask for, so the hooks
|
|
100
|
+
* select from it exactly as they do in the app. `connect` and `disconnect` move the session, but
|
|
101
|
+
* reach for the real provider plus `createMockAdapter` to test connecting itself — the intermediate
|
|
102
|
+
* states here are not the SDK's.
|
|
103
|
+
*
|
|
104
|
+
* @example
|
|
105
|
+
* render(
|
|
106
|
+
* <FakeSessionProvider status="connected" party={party}>
|
|
107
|
+
* <ConnectButton />
|
|
108
|
+
* </FakeSessionProvider>,
|
|
109
|
+
* )
|
|
110
|
+
*
|
|
111
|
+
* @category Components
|
|
112
|
+
*/
|
|
113
|
+
const FakeSessionProvider = ({ children, connectError, isLocked = false, party, readingAccounts = false, sdk = NO_SDK, status: initialStatus = "disconnected" }) => {
|
|
114
|
+
const [status, setStatus] = useState(initialStatus);
|
|
115
|
+
const connection = useMemo(() => startSession({
|
|
116
|
+
isLocked,
|
|
117
|
+
readingAccounts,
|
|
118
|
+
status
|
|
119
|
+
}, party, connectError, refusingSdk(sdk)), [
|
|
120
|
+
connectError,
|
|
121
|
+
isLocked,
|
|
122
|
+
party,
|
|
123
|
+
readingAccounts,
|
|
124
|
+
sdk,
|
|
125
|
+
status
|
|
126
|
+
]);
|
|
127
|
+
useEffect(() => () => connection.stop(), [connection]);
|
|
128
|
+
const connect = useCallback(async () => {
|
|
129
|
+
setStatus("connected");
|
|
130
|
+
}, []);
|
|
131
|
+
const disconnect = useCallback(async () => {
|
|
132
|
+
setStatus("disconnected");
|
|
133
|
+
}, []);
|
|
134
|
+
const value = useMemo(() => ({
|
|
135
|
+
config: CONFIG,
|
|
136
|
+
connection,
|
|
137
|
+
connect,
|
|
138
|
+
cancelConnect: () => connection.send({ type: "connect.cancel" }),
|
|
139
|
+
disconnect,
|
|
140
|
+
resetConnectError: () => connection.send({ type: "connectError.reset" })
|
|
141
|
+
}), [
|
|
142
|
+
connection,
|
|
143
|
+
connect,
|
|
144
|
+
disconnect
|
|
145
|
+
]);
|
|
146
|
+
return /* @__PURE__ */ jsx(CantonConnectContext.Provider, {
|
|
147
|
+
value,
|
|
148
|
+
children
|
|
149
|
+
});
|
|
150
|
+
};
|
|
151
|
+
//#endregion
|
|
152
|
+
//#region src/testing/fakeWallet.ts
|
|
153
|
+
const JSON_RPC_METHOD_NOT_FOUND = -32601;
|
|
154
|
+
const FAKE_PUBLIC_KEY = "fake-public-key";
|
|
155
|
+
const FAKE_SIGNING_PROVIDER_ID = "fake";
|
|
156
|
+
const FAKE_NETWORK_ID = "canton:local";
|
|
157
|
+
const FAKE_WALLET_STATUS = "allocated";
|
|
158
|
+
/** Shapes one `FakeWalletAccount` into the `Wallet` object `listAccounts` reports. */
|
|
159
|
+
const toWallet = (account) => ({
|
|
160
|
+
primary: account.primary === true,
|
|
161
|
+
partyId: account.partyId,
|
|
162
|
+
status: FAKE_WALLET_STATUS,
|
|
163
|
+
hint: account.name ?? account.partyId,
|
|
164
|
+
publicKey: account.publicKey ?? FAKE_PUBLIC_KEY,
|
|
165
|
+
namespace: account.partyId.split("::")[1] ?? account.partyId,
|
|
166
|
+
networkId: account.networkId ?? FAKE_NETWORK_ID,
|
|
167
|
+
signingProviderId: FAKE_SIGNING_PROVIDER_ID
|
|
168
|
+
});
|
|
169
|
+
/**
|
|
170
|
+
* A fake CIP-0103 extension wallet for tests. It speaks the real postMessage protocol, so it
|
|
171
|
+
* exercises the SDK's genuine `ExtensionAdapter` rather than a stub. Answers `connect`, `status`,
|
|
172
|
+
* `listAccounts` and `disconnect`; anything else rejects naming the method. Reach for
|
|
173
|
+
* `createMockAdapter` instead where the transport is not what is under test.
|
|
174
|
+
*
|
|
175
|
+
* @example
|
|
176
|
+
* const wallet = createFakeWallet({ id: 'mock' })
|
|
177
|
+
* wallet.push('statusChanged', { connection: { isConnected: false } })
|
|
178
|
+
* wallet.dispose()
|
|
179
|
+
*
|
|
180
|
+
* @category Utilities
|
|
181
|
+
*/
|
|
182
|
+
const createFakeWallet = (options) => {
|
|
183
|
+
const target = options.target ?? options.id;
|
|
184
|
+
const accounts = options.accounts ?? [{
|
|
185
|
+
partyId: `${options.id}::1220abcd`,
|
|
186
|
+
primary: true
|
|
187
|
+
}];
|
|
188
|
+
let statusCallCount = 0;
|
|
189
|
+
const announce = () => {
|
|
190
|
+
window.dispatchEvent(new CustomEvent(CANTON_ANNOUNCE_PROVIDER_EVENT, { detail: {
|
|
191
|
+
id: options.id,
|
|
192
|
+
name: options.name ?? options.id,
|
|
193
|
+
target
|
|
194
|
+
} }));
|
|
195
|
+
};
|
|
196
|
+
const nextStatusIsConnected = () => {
|
|
197
|
+
const responses = options.statusResponses;
|
|
198
|
+
if (responses === void 0 || responses.length === 0) return true;
|
|
199
|
+
const index = Math.min(statusCallCount, responses.length - 1);
|
|
200
|
+
statusCallCount += 1;
|
|
201
|
+
return responses[index];
|
|
202
|
+
};
|
|
203
|
+
const buildStatus = () => ({
|
|
204
|
+
provider: {
|
|
205
|
+
id: options.id,
|
|
206
|
+
providerType: "browser"
|
|
207
|
+
},
|
|
208
|
+
connection: {
|
|
209
|
+
isConnected: nextStatusIsConnected(),
|
|
210
|
+
isNetworkConnected: true
|
|
211
|
+
}
|
|
212
|
+
});
|
|
213
|
+
const buildConnect = () => ({
|
|
214
|
+
isConnected: true,
|
|
215
|
+
isNetworkConnected: true
|
|
216
|
+
});
|
|
217
|
+
const responses = {
|
|
218
|
+
status: buildStatus,
|
|
219
|
+
connect: buildConnect,
|
|
220
|
+
listAccounts: () => accounts.map(toWallet),
|
|
221
|
+
disconnect: () => ({})
|
|
222
|
+
};
|
|
223
|
+
const answer = (method) => {
|
|
224
|
+
const handler = responses[method];
|
|
225
|
+
if (handler === void 0) return { error: {
|
|
226
|
+
code: JSON_RPC_METHOD_NOT_FOUND,
|
|
227
|
+
message: `createFakeWallet does not implement "${method}"`
|
|
228
|
+
} };
|
|
229
|
+
return { result: handler() };
|
|
230
|
+
};
|
|
231
|
+
const onMessage = (event) => {
|
|
232
|
+
const data = event.data;
|
|
233
|
+
if (data?.type === void 0) return;
|
|
234
|
+
if (data.target !== void 0 && data.target !== target) return;
|
|
235
|
+
if (data.type === WalletEvent.SPLICE_WALLET_EXT_READY) {
|
|
236
|
+
window.postMessage({
|
|
237
|
+
type: WalletEvent.SPLICE_WALLET_EXT_ACK,
|
|
238
|
+
target
|
|
239
|
+
}, "*");
|
|
240
|
+
return;
|
|
241
|
+
}
|
|
242
|
+
const requestId = data.request?.id;
|
|
243
|
+
if (!(data.type === WalletEvent.SPLICE_WALLET_REQUEST && requestId !== void 0 && requestId !== null)) return;
|
|
244
|
+
const method = data.request?.method ?? "";
|
|
245
|
+
window.postMessage({
|
|
246
|
+
type: WalletEvent.SPLICE_WALLET_RESPONSE,
|
|
247
|
+
response: {
|
|
248
|
+
jsonrpc: "2.0",
|
|
249
|
+
id: requestId,
|
|
250
|
+
...answer(method)
|
|
251
|
+
}
|
|
252
|
+
}, "*");
|
|
253
|
+
};
|
|
254
|
+
const push = (method, params) => {
|
|
255
|
+
window.postMessage({
|
|
256
|
+
type: WalletEvent.SPLICE_WALLET_REQUEST,
|
|
257
|
+
request: {
|
|
258
|
+
jsonrpc: "2.0",
|
|
259
|
+
method,
|
|
260
|
+
params
|
|
261
|
+
},
|
|
262
|
+
target
|
|
263
|
+
}, "*");
|
|
264
|
+
};
|
|
265
|
+
window.addEventListener("message", onMessage);
|
|
266
|
+
window.addEventListener(CANTON_REQUEST_PROVIDER_EVENT, announce);
|
|
267
|
+
queueMicrotask(announce);
|
|
268
|
+
return {
|
|
269
|
+
announce,
|
|
270
|
+
push,
|
|
271
|
+
dispose: () => {
|
|
272
|
+
window.removeEventListener("message", onMessage);
|
|
273
|
+
window.removeEventListener(CANTON_REQUEST_PROVIDER_EVENT, announce);
|
|
274
|
+
}
|
|
275
|
+
};
|
|
276
|
+
};
|
|
277
|
+
//#endregion
|
|
278
|
+
//#region src/testing/pause.ts
|
|
279
|
+
/**
|
|
280
|
+
* Real-timer sleep, which is how a suite awaits a promise-settling send; `pause(0)` flushes the
|
|
281
|
+
* pending macrotasks. Reach for it over `waitFor`, whose retry window turns a genuine red into a
|
|
282
|
+
* timeout rather than a failed assertion.
|
|
283
|
+
*
|
|
284
|
+
* @example
|
|
285
|
+
* await pause(0) // one macrotask on, so the send above has settled
|
|
286
|
+
*
|
|
287
|
+
* @category Utilities
|
|
288
|
+
*/
|
|
289
|
+
const pause = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
|
|
290
|
+
//#endregion
|
|
291
|
+
export { FakeSessionProvider, createAutoPicker, createFakeWallet, pause };
|
|
@@ -0,0 +1,385 @@
|
|
|
1
|
+
import { DappSDK, ProviderAdapter, StatusEvent, TxChangedEvent, WalletPickerFn } from "@canton-network/dapp-sdk";
|
|
2
|
+
import { ActorRefFrom } from "xstate";
|
|
3
|
+
//#region src/machine/connectionActors.d.ts
|
|
4
|
+
/**
|
|
5
|
+
* What `DappSDK.init` accepts, read off the SDK rather than restated. The init actor defaults
|
|
6
|
+
* `defaultAdapters` to empty, dropping the SDK's bundled dev gateway, and whatever is set here
|
|
7
|
+
* wins over that default.
|
|
8
|
+
*
|
|
9
|
+
* @example
|
|
10
|
+
* const initOptions: InitOptions = { additionalAdapters: [createMockAdapter()] }
|
|
11
|
+
*
|
|
12
|
+
* @category Types
|
|
13
|
+
*/
|
|
14
|
+
type InitOptions = NonNullable<Parameters<DappSDK['init']>[0]>;
|
|
15
|
+
/** Input for the `init` actor: the sdk slice and options `ensureInit` needs to boot it. */
|
|
16
|
+
type InitInput = {
|
|
17
|
+
sdk: Pick<DappSDK, 'init'>;
|
|
18
|
+
initOptions: InitOptions;
|
|
19
|
+
};
|
|
20
|
+
/** Input for the `connect` actor: enough sdk to init, connect, and recover a session. */
|
|
21
|
+
type ConnectInput = {
|
|
22
|
+
sdk: Pick<DappSDK, 'connect' | 'init' | 'status'>;
|
|
23
|
+
initOptions: InitOptions;
|
|
24
|
+
guardPicker: boolean;
|
|
25
|
+
};
|
|
26
|
+
/** Input for the `restore` actor: just enough sdk to read the wallet's status. */
|
|
27
|
+
type RestoreInput = {
|
|
28
|
+
sdk: Pick<DappSDK, 'status'>;
|
|
29
|
+
};
|
|
30
|
+
/** Input for the `disconnect` actor: the sdk slice it calls to end the session. */
|
|
31
|
+
type DisconnectInput = {
|
|
32
|
+
sdk: Pick<DappSDK, 'disconnect'>;
|
|
33
|
+
};
|
|
34
|
+
/** Input for the `walletEvents` actor: the sdk slice it subscribes to and unsubscribes from. */
|
|
35
|
+
type WalletEventsInput = {
|
|
36
|
+
sdk: Pick<DappSDK, 'onStatusChanged' | 'removeOnStatusChanged'>;
|
|
37
|
+
};
|
|
38
|
+
//#endregion
|
|
39
|
+
//#region src/machine/accountsMachine.d.ts
|
|
40
|
+
/** The result of an account read, initial or pushed: the party found, or none. */
|
|
41
|
+
type WalletAccounts = {
|
|
42
|
+
party: Party | undefined;
|
|
43
|
+
};
|
|
44
|
+
//#endregion
|
|
45
|
+
//#region src/machine/accountsActors.d.ts
|
|
46
|
+
/** Input shared by `readAccounts` and `accountsEvents`: the sdk slice plus the network id. */
|
|
47
|
+
type AccountsInput = {
|
|
48
|
+
sdk: Pick<DappSDK, 'listAccounts' | 'onAccountsChanged' | 'removeOnAccountsChanged'>;
|
|
49
|
+
networkId: string;
|
|
50
|
+
};
|
|
51
|
+
//#endregion
|
|
52
|
+
//#region src/machine/connectionMachine.d.ts
|
|
53
|
+
/** The wallet's connection status, narrowed from the SDK's `StatusEvent`. */
|
|
54
|
+
type WalletStatusUpdate = Pick<StatusEvent, 'connection'>;
|
|
55
|
+
/**
|
|
56
|
+
* What the machine needs to build and drive its own `DappSDK`. Read once, at actor creation: as in
|
|
57
|
+
* wagmi, a config swapped afterwards reaches the hooks but not the connection lifecycle.
|
|
58
|
+
*
|
|
59
|
+
* @example
|
|
60
|
+
* import { DappSDK } from '@canton-network/dapp-sdk'
|
|
61
|
+
* import { createActor } from 'xstate'
|
|
62
|
+
* import { connectionMachine } from '#src/machine/connectionMachine'
|
|
63
|
+
*
|
|
64
|
+
* const createSdk = () => new DappSDK({})
|
|
65
|
+
* const input = { createSdk, initOptions: {}, guardPicker: true, networkId: 'canton:local' }
|
|
66
|
+
* createActor(connectionMachine, { input })
|
|
67
|
+
*
|
|
68
|
+
* @category Types
|
|
69
|
+
*/
|
|
70
|
+
type ConnectionInput = {
|
|
71
|
+
createSdk: () => WalletSdk;
|
|
72
|
+
initOptions: InitOptions;
|
|
73
|
+
guardPicker: boolean;
|
|
74
|
+
networkId: string;
|
|
75
|
+
};
|
|
76
|
+
/** What the machine carries beyond its input: the sdk it drives, the last failure, the party. */
|
|
77
|
+
type ConnectionContext = ConnectionInput & {
|
|
78
|
+
sdk: WalletSdk;
|
|
79
|
+
lastConnectError: unknown;
|
|
80
|
+
party: Party | undefined;
|
|
81
|
+
};
|
|
82
|
+
/**
|
|
83
|
+
* The lifecycle itself: what a connect, a restore, a lock and a disconnect mean, and the tags the
|
|
84
|
+
* bridges and hooks read off them. `CantonConnectProvider` runs it; reach for it directly only to
|
|
85
|
+
* drive a session in a test.
|
|
86
|
+
*
|
|
87
|
+
* @category Types
|
|
88
|
+
*/
|
|
89
|
+
declare const connectionMachine: import("xstate").StateMachine<ConnectionContext, {
|
|
90
|
+
type: "connect";
|
|
91
|
+
} | {
|
|
92
|
+
type: "connect.cancel";
|
|
93
|
+
} | {
|
|
94
|
+
type: "connectError.reset";
|
|
95
|
+
} | {
|
|
96
|
+
type: "disconnect";
|
|
97
|
+
} | {
|
|
98
|
+
type: "restore";
|
|
99
|
+
} | {
|
|
100
|
+
type: "wallet.statusChanged";
|
|
101
|
+
status: WalletStatusUpdate;
|
|
102
|
+
}, {
|
|
103
|
+
[x: string]: import("xstate").ActorRefFromLogic<import("xstate").PromiseActorLogic<WalletStatusUpdate, ConnectInput, import("xstate").EventObject> | import("xstate").PromiseActorLogic<null, DisconnectInput, import("xstate").EventObject> | import("xstate").PromiseActorLogic<void, InitInput, import("xstate").EventObject> | import("xstate").PromiseActorLogic<WalletStatusUpdate, RestoreInput, import("xstate").EventObject> | import("xstate").CallbackActorLogic<import("xstate").EventObject, WalletEventsInput, import("xstate").EventObject>> | undefined;
|
|
104
|
+
accounts?: import("xstate").ActorRefFromLogic<import("xstate").StateMachine<AccountsInput & WalletAccounts & {
|
|
105
|
+
error: unknown;
|
|
106
|
+
}, {
|
|
107
|
+
type: "accounts.changed";
|
|
108
|
+
accounts: WalletAccounts;
|
|
109
|
+
}, {
|
|
110
|
+
[x: string]: import("xstate").ActorRefFromLogic<import("xstate").PromiseActorLogic<WalletAccounts, AccountsInput, import("xstate").EventObject> | import("xstate").CallbackActorLogic<import("xstate").EventObject, AccountsInput, import("xstate").EventObject>> | undefined;
|
|
111
|
+
}, {
|
|
112
|
+
src: "readAccounts";
|
|
113
|
+
logic: import("xstate").PromiseActorLogic<WalletAccounts, AccountsInput, import("xstate").EventObject>;
|
|
114
|
+
id: string | undefined;
|
|
115
|
+
} | {
|
|
116
|
+
src: "accountsEvents";
|
|
117
|
+
logic: import("xstate").CallbackActorLogic<import("xstate").EventObject, AccountsInput, import("xstate").EventObject>;
|
|
118
|
+
id: string | undefined;
|
|
119
|
+
}, {
|
|
120
|
+
type: "applyAccounts";
|
|
121
|
+
params: {
|
|
122
|
+
accounts: WalletAccounts;
|
|
123
|
+
};
|
|
124
|
+
} | {
|
|
125
|
+
type: "assignError";
|
|
126
|
+
params: {
|
|
127
|
+
error: unknown;
|
|
128
|
+
};
|
|
129
|
+
}, never, never, "reading" | "ready" | "unavailable", string, AccountsInput, import("xstate").NonReducibleUnknown, import("xstate").EventObject, import("xstate").MetaObject, {
|
|
130
|
+
id: "accounts";
|
|
131
|
+
states: {
|
|
132
|
+
readonly reading: {};
|
|
133
|
+
readonly ready: {};
|
|
134
|
+
readonly unavailable: {};
|
|
135
|
+
};
|
|
136
|
+
}>> | undefined;
|
|
137
|
+
}, {
|
|
138
|
+
src: "init";
|
|
139
|
+
logic: import("xstate").PromiseActorLogic<void, InitInput, import("xstate").EventObject>;
|
|
140
|
+
id: string | undefined;
|
|
141
|
+
} | {
|
|
142
|
+
src: "connect";
|
|
143
|
+
logic: import("xstate").PromiseActorLogic<WalletStatusUpdate, ConnectInput, import("xstate").EventObject>;
|
|
144
|
+
id: string | undefined;
|
|
145
|
+
} | {
|
|
146
|
+
src: "disconnect";
|
|
147
|
+
logic: import("xstate").PromiseActorLogic<null, DisconnectInput, import("xstate").EventObject>;
|
|
148
|
+
id: string | undefined;
|
|
149
|
+
} | {
|
|
150
|
+
src: "restore";
|
|
151
|
+
logic: import("xstate").PromiseActorLogic<WalletStatusUpdate, RestoreInput, import("xstate").EventObject>;
|
|
152
|
+
id: string | undefined;
|
|
153
|
+
} | {
|
|
154
|
+
src: "walletEvents";
|
|
155
|
+
logic: import("xstate").CallbackActorLogic<import("xstate").EventObject, WalletEventsInput, import("xstate").EventObject>;
|
|
156
|
+
id: string | undefined;
|
|
157
|
+
} | {
|
|
158
|
+
src: "accounts";
|
|
159
|
+
logic: import("xstate").StateMachine<AccountsInput & WalletAccounts & {
|
|
160
|
+
error: unknown;
|
|
161
|
+
}, {
|
|
162
|
+
type: "accounts.changed";
|
|
163
|
+
accounts: WalletAccounts;
|
|
164
|
+
}, {
|
|
165
|
+
[x: string]: import("xstate").ActorRefFromLogic<import("xstate").PromiseActorLogic<WalletAccounts, AccountsInput, import("xstate").EventObject> | import("xstate").CallbackActorLogic<import("xstate").EventObject, AccountsInput, import("xstate").EventObject>> | undefined;
|
|
166
|
+
}, {
|
|
167
|
+
src: "readAccounts";
|
|
168
|
+
logic: import("xstate").PromiseActorLogic<WalletAccounts, AccountsInput, import("xstate").EventObject>;
|
|
169
|
+
id: string | undefined;
|
|
170
|
+
} | {
|
|
171
|
+
src: "accountsEvents";
|
|
172
|
+
logic: import("xstate").CallbackActorLogic<import("xstate").EventObject, AccountsInput, import("xstate").EventObject>;
|
|
173
|
+
id: string | undefined;
|
|
174
|
+
}, {
|
|
175
|
+
type: "applyAccounts";
|
|
176
|
+
params: {
|
|
177
|
+
accounts: WalletAccounts;
|
|
178
|
+
};
|
|
179
|
+
} | {
|
|
180
|
+
type: "assignError";
|
|
181
|
+
params: {
|
|
182
|
+
error: unknown;
|
|
183
|
+
};
|
|
184
|
+
}, never, never, "reading" | "ready" | "unavailable", string, AccountsInput, import("xstate").NonReducibleUnknown, import("xstate").EventObject, import("xstate").MetaObject, {
|
|
185
|
+
id: "accounts";
|
|
186
|
+
states: {
|
|
187
|
+
readonly reading: {};
|
|
188
|
+
readonly ready: {};
|
|
189
|
+
readonly unavailable: {};
|
|
190
|
+
};
|
|
191
|
+
}>;
|
|
192
|
+
id: "accounts";
|
|
193
|
+
}, {
|
|
194
|
+
type: "assignError";
|
|
195
|
+
params: {
|
|
196
|
+
error: unknown;
|
|
197
|
+
};
|
|
198
|
+
} | {
|
|
199
|
+
type: "assignDeclined";
|
|
200
|
+
params: {
|
|
201
|
+
connection: WalletStatusUpdate["connection"];
|
|
202
|
+
};
|
|
203
|
+
} | {
|
|
204
|
+
type: "forgetError";
|
|
205
|
+
params: import("xstate").NonReducibleUnknown;
|
|
206
|
+
} | {
|
|
207
|
+
type: "retireSdk";
|
|
208
|
+
params: import("xstate").NonReducibleUnknown;
|
|
209
|
+
}, {
|
|
210
|
+
type: "isAuthenticated";
|
|
211
|
+
params: {
|
|
212
|
+
connection: WalletStatusUpdate["connection"] | undefined;
|
|
213
|
+
};
|
|
214
|
+
} | {
|
|
215
|
+
type: "isPickerClosed";
|
|
216
|
+
params: {
|
|
217
|
+
error: unknown;
|
|
218
|
+
};
|
|
219
|
+
} | {
|
|
220
|
+
type: "isInitFailed";
|
|
221
|
+
params: {
|
|
222
|
+
error: unknown;
|
|
223
|
+
};
|
|
224
|
+
}, "disconnectTimeout", "idle" | "disconnecting" | "disconnected" | "initializing" | "failure" | {
|
|
225
|
+
connecting: "new" | "changing";
|
|
226
|
+
} | {
|
|
227
|
+
session: "unauthenticated" | {
|
|
228
|
+
authenticated: "reading" | "ready" | "unavailable";
|
|
229
|
+
};
|
|
230
|
+
} | {
|
|
231
|
+
retiring: "new" | "changing";
|
|
232
|
+
} | {
|
|
233
|
+
restoring: "new" | "changing";
|
|
234
|
+
}, "connecting" | "connect.settled" | "connect.failed" | "connect.cancelled" | "unauthenticated" | "disconnect.settled", ConnectionInput, import("xstate").NonReducibleUnknown, import("xstate").EventObject, import("xstate").MetaObject, {
|
|
235
|
+
id: "connection";
|
|
236
|
+
states: {
|
|
237
|
+
readonly idle: {};
|
|
238
|
+
readonly disconnected: {};
|
|
239
|
+
readonly connecting: {
|
|
240
|
+
states: {
|
|
241
|
+
readonly new: {};
|
|
242
|
+
readonly changing: {};
|
|
243
|
+
};
|
|
244
|
+
};
|
|
245
|
+
readonly session: {
|
|
246
|
+
states: {
|
|
247
|
+
readonly authenticated: {
|
|
248
|
+
states: {
|
|
249
|
+
readonly reading: {};
|
|
250
|
+
readonly ready: {};
|
|
251
|
+
readonly unavailable: {};
|
|
252
|
+
};
|
|
253
|
+
};
|
|
254
|
+
readonly unauthenticated: {};
|
|
255
|
+
};
|
|
256
|
+
};
|
|
257
|
+
readonly failure: {};
|
|
258
|
+
readonly retiring: {
|
|
259
|
+
states: {
|
|
260
|
+
readonly new: {};
|
|
261
|
+
readonly changing: {};
|
|
262
|
+
};
|
|
263
|
+
};
|
|
264
|
+
readonly restoring: {
|
|
265
|
+
states: {
|
|
266
|
+
readonly new: {};
|
|
267
|
+
readonly changing: {};
|
|
268
|
+
};
|
|
269
|
+
};
|
|
270
|
+
readonly initializing: {};
|
|
271
|
+
readonly disconnecting: {};
|
|
272
|
+
};
|
|
273
|
+
}>;
|
|
274
|
+
/**
|
|
275
|
+
* The running connection machine. Consumers reach it narrowed to {@link ConnectionSubscription},
|
|
276
|
+
* so `send` stays inside this package.
|
|
277
|
+
*
|
|
278
|
+
* @category Types
|
|
279
|
+
*/
|
|
280
|
+
type ConnectionActorRef = ActorRefFrom<typeof connectionMachine>;
|
|
281
|
+
//#endregion
|
|
282
|
+
//#region src/types.d.ts
|
|
283
|
+
/**
|
|
284
|
+
* The slice of `DappSDK` this package calls. Deliberately narrower than the class: it states
|
|
285
|
+
* which methods the wrapper supports, and a real `DappSDK` satisfies it structurally.
|
|
286
|
+
*
|
|
287
|
+
* @category Types
|
|
288
|
+
*/
|
|
289
|
+
type WalletSdk = Pick<DappSDK, 'init' | 'connect' | 'disconnect' | 'status' | 'listAccounts' | 'onStatusChanged' | 'removeOnStatusChanged' | 'onAccountsChanged' | 'removeOnAccountsChanged' | 'onTxChanged' | 'removeOnTxChanged' | 'ledgerApi' | 'signMessage' | 'prepareExecuteAndWait'>;
|
|
290
|
+
/**
|
|
291
|
+
* `'idle'` is "not determined yet", not "disconnected": gate a connect button on `'disconnected'`,
|
|
292
|
+
* or a returning user is turned away before the boot restore runs. `'disconnecting'` is the session
|
|
293
|
+
* tearing down; keep connect disabled until it settles, so a new connect never overlaps it.
|
|
294
|
+
*
|
|
295
|
+
* @example
|
|
296
|
+
* const { status } = useParty()
|
|
297
|
+
* if (status === 'idle') return null
|
|
298
|
+
* return status === 'disconnected' ? <ConnectButton /> : <App />
|
|
299
|
+
*
|
|
300
|
+
* @category Types
|
|
301
|
+
*/
|
|
302
|
+
type ConnectionStatus = 'idle' | 'connecting' | 'connected' | 'disconnecting' | 'disconnected';
|
|
303
|
+
/**
|
|
304
|
+
* Canton's terms: a local party lives under the hosting participant's namespace and the participant
|
|
305
|
+
* signs for it; an external party lives under its own key's and signs for itself.
|
|
306
|
+
*
|
|
307
|
+
* @category Types
|
|
308
|
+
*/
|
|
309
|
+
type PartyType = 'local' | 'external';
|
|
310
|
+
/**
|
|
311
|
+
* The connected account, normalized from the wallet's CIP-0103 account entry. `networkId` falls
|
|
312
|
+
* back to `CantonConnectConfig.networkId` where the wallet reports none. `namespace` and
|
|
313
|
+
* `signingProviderId` come through as reported; CIP-0103 names no `signingProviderId` values.
|
|
314
|
+
*
|
|
315
|
+
* @category Types
|
|
316
|
+
*/
|
|
317
|
+
interface Party {
|
|
318
|
+
partyId: string;
|
|
319
|
+
networkId: string;
|
|
320
|
+
namespace: string;
|
|
321
|
+
signingProviderId: string;
|
|
322
|
+
name?: string;
|
|
323
|
+
publicKey?: string;
|
|
324
|
+
}
|
|
325
|
+
/**
|
|
326
|
+
* Wiring for `CantonConnectProvider`. From `appName` alone a local dev app works: `canton:local` as
|
|
327
|
+
* the network id and the WalletConnect chain id, `appName` as the description, the page origin as
|
|
328
|
+
* the url, and the SDK's popup as the picker, guarded so closing it rejects — pass a `walletPicker`
|
|
329
|
+
* and that surface is yours. The app fields stay inert until `walletConnectProjectId` (Reown) is set.
|
|
330
|
+
*
|
|
331
|
+
* @example
|
|
332
|
+
* const config: CantonConnectConfig = { appName: 'Vesting', networkId: 'canton:devnet' }
|
|
333
|
+
*
|
|
334
|
+
* @category Configuration
|
|
335
|
+
*/
|
|
336
|
+
interface CantonConnectConfig {
|
|
337
|
+
appName: string;
|
|
338
|
+
appDescription?: string;
|
|
339
|
+
appUrl?: string;
|
|
340
|
+
networkId?: string;
|
|
341
|
+
walletConnectProjectId?: string;
|
|
342
|
+
walletPicker?: WalletPickerFn;
|
|
343
|
+
additionalAdapters?: ProviderAdapter[];
|
|
344
|
+
}
|
|
345
|
+
/**
|
|
346
|
+
* Mirrored from the SDK's `txChanged` event as a command moves through
|
|
347
|
+
* pending, signed, executed or failed.
|
|
348
|
+
*
|
|
349
|
+
* @category Types
|
|
350
|
+
*/
|
|
351
|
+
interface TxStatusSnapshot {
|
|
352
|
+
status: TxChangedEvent['status'];
|
|
353
|
+
commandId: TxChangedEvent['commandId'];
|
|
354
|
+
payload?: unknown;
|
|
355
|
+
}
|
|
356
|
+
/**
|
|
357
|
+
* The connection machine as `useSelector` sees it: subscribe and read, never send. Narrowed from
|
|
358
|
+
* the actor ref so `connect` and `disconnect` stay the only senders — a transition asked for
|
|
359
|
+
* anywhere else is a lifecycle rule living outside the machine.
|
|
360
|
+
*
|
|
361
|
+
* @example
|
|
362
|
+
* import type { ConnectionSubscription } from '#src/types'
|
|
363
|
+
*
|
|
364
|
+
* const partyOf = (connection: ConnectionSubscription) => connection.getSnapshot().context.party
|
|
365
|
+
*
|
|
366
|
+
* @category Types
|
|
367
|
+
*/
|
|
368
|
+
type ConnectionSubscription = Pick<ConnectionActorRef, 'getSnapshot' | 'subscribe'>;
|
|
369
|
+
/**
|
|
370
|
+
* One connection and the actions on it, published once. Every hook selects its slice off
|
|
371
|
+
* `connection`: prefer the narrower hooks and reach for this only when none exposes the slice.
|
|
372
|
+
* The four actions are `useConnect`'s own, documented there.
|
|
373
|
+
*
|
|
374
|
+
* @category Types
|
|
375
|
+
*/
|
|
376
|
+
interface CantonConnectContextValue {
|
|
377
|
+
config: CantonConnectConfig;
|
|
378
|
+
connection: ConnectionSubscription;
|
|
379
|
+
connect: () => Promise<void>;
|
|
380
|
+
cancelConnect: () => void;
|
|
381
|
+
disconnect: () => Promise<void>;
|
|
382
|
+
resetConnectError: () => void;
|
|
383
|
+
}
|
|
384
|
+
//#endregion
|
|
385
|
+
export { Party as a, WalletSdk as c, ConnectionSubscription as i, ConnectionInput as l, CantonConnectContextValue as n, PartyType as o, ConnectionStatus as r, TxStatusSnapshot as s, CantonConnectConfig as t, InitOptions as u };
|