@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/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 BootNode
|
|
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/README.md
ADDED
|
@@ -0,0 +1,155 @@
|
|
|
1
|
+
# canton-connect
|
|
2
|
+
|
|
3
|
+
wagmi-style React hooks for connecting Canton dApps to CIP-0103 wallets, over
|
|
4
|
+
`@canton-network/dapp-sdk`'s `DappSDK` facade.
|
|
5
|
+
|
|
6
|
+
## Why
|
|
7
|
+
|
|
8
|
+
`dapp-sdk` handles wallet discovery, the connect picker, the session and every transport (browser
|
|
9
|
+
extension, WalletConnect, remote gateway), but ships no React hooks. This package adds that layer.
|
|
10
|
+
A consumer never calls the SDK: it installs it as a peer, and the hooks take the SDK's own parameter
|
|
11
|
+
types.
|
|
12
|
+
|
|
13
|
+
[`@partylayer/react`](https://partylayer.xyz) is the alternative, built over its own wallet
|
|
14
|
+
adapters. This one wraps Digital Asset's official SDK, the dependency these dApps already carry, and
|
|
15
|
+
stays thin enough to delete. How the hook results relate to wagmi's is in
|
|
16
|
+
[coming-from-wagmi.md](https://github.com/BootNodeDev/canton-dappbooster/blob/main/canton-connect/coming-from-wagmi.md).
|
|
17
|
+
|
|
18
|
+
## Why a state machine
|
|
19
|
+
|
|
20
|
+
The connection lifecycle looks like four states (idle, connecting, connected,
|
|
21
|
+
disconnected) and isn't. The hard part isn't holding state, it's canceling work
|
|
22
|
+
when the state that started it is gone: a picker the user abandoned, a lock
|
|
23
|
+
that races the account read, a connect asked for mid-disconnect, a wallet that
|
|
24
|
+
answers late or never. Handled one at a time these were five separate races
|
|
25
|
+
(#76). A state machine folds them into one model: a state's invoked work is
|
|
26
|
+
canceled when the state is left, and the combinations that used to be bugs
|
|
27
|
+
(connected with no party, an error beside a live session) are states that name
|
|
28
|
+
their case: `session.unauthenticated`, `session.authenticated.unavailable`.
|
|
29
|
+
|
|
30
|
+
Most of that weight works around `@canton-network/dapp-sdk` gaps, not domain
|
|
31
|
+
complexity (below). When those close upstream, this layer collapses to its floor
|
|
32
|
+
(idle-vs-disconnected, the account-read states, the CIP-0103 lock/disconnect
|
|
33
|
+
ambiguity), small enough that a lighter store wins on bundle size. A spike
|
|
34
|
+
reimplementing it on zustand confirmed this: full behavioral parity, but the win
|
|
35
|
+
is bundle size and one fewer dependency, not less logic, and it points to
|
|
36
|
+
switching only once those gaps close. Until then, the machine earns its cost.
|
|
37
|
+
|
|
38
|
+
| dapp-sdk gap | what it costs us | gone when |
|
|
39
|
+
|---|---|---|
|
|
40
|
+
| `connect()` can't be aborted; a closed popup hangs it (#49) | `guardedConnect`, `settleAbandonedConnect`, `PickerClosedError`, the `retiring` state, `retireSdk`'s picker swap | `connect(signal)` truly aborts |
|
|
41
|
+
| `init()` caches a rejected promise forever | `retireSdk`, the `retiring` state, `InitFailedError` | `init()` retries after a failure |
|
|
42
|
+
| `disconnect()` has no timeout (#105) | `DISCONNECT_TIMEOUT_MS`, and `retireSdk` when it fires | `disconnect()` times out itself |
|
|
43
|
+
| lock and wallet-side disconnect are one push | `session.unauthenticated`, party-dropped-on-lock | CIP-0103 separates them (spec, not SDK) |
|
|
44
|
+
|
|
45
|
+
## Status
|
|
46
|
+
|
|
47
|
+
Consumed by `dapp/frontend` in this repo as a workspace package. Not published (`private: true`).
|
|
48
|
+
|
|
49
|
+
## Install
|
|
50
|
+
|
|
51
|
+
Peers a consumer installs beside it: `@canton-network/dapp-sdk`, `@canton-network/core-types`,
|
|
52
|
+
`react` 19 and `@walletconnect/sign-client`. The last is declared optional, but `dapp-sdk` imports
|
|
53
|
+
it statically at the top of its bundle (checked on 1.5.1), so it has to be present whether or not
|
|
54
|
+
you set `walletConnectProjectId`. Only the session is lazy: `SignClient.init()` runs when a pairing
|
|
55
|
+
starts, not at import.
|
|
56
|
+
|
|
57
|
+
## Usage
|
|
58
|
+
|
|
59
|
+
```tsx
|
|
60
|
+
import {
|
|
61
|
+
CantonConnectProvider,
|
|
62
|
+
useConnect,
|
|
63
|
+
useParty,
|
|
64
|
+
useWalletStatus,
|
|
65
|
+
useSignMessage,
|
|
66
|
+
useExecute,
|
|
67
|
+
useLedger,
|
|
68
|
+
} from '@bootnodedev/canton-connect'
|
|
69
|
+
|
|
70
|
+
function App() {
|
|
71
|
+
return (
|
|
72
|
+
<CantonConnectProvider config={{ appName: 'My dApp', networkId: 'canton:local' }}>
|
|
73
|
+
<Dapp />
|
|
74
|
+
</CantonConnectProvider>
|
|
75
|
+
)
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
function Dapp() {
|
|
79
|
+
const { connect, isPending, isConnected, error } = useConnect()
|
|
80
|
+
const { party } = useParty()
|
|
81
|
+
const { isLocked } = useWalletStatus()
|
|
82
|
+
const { signMessage } = useSignMessage()
|
|
83
|
+
const { execute } = useExecute()
|
|
84
|
+
const { ledgerApi } = useLedger()
|
|
85
|
+
|
|
86
|
+
if (!isConnected) {
|
|
87
|
+
return (
|
|
88
|
+
<div>
|
|
89
|
+
<button onClick={() => connect().catch(() => undefined)} disabled={isPending}>
|
|
90
|
+
Connect
|
|
91
|
+
</button>
|
|
92
|
+
{error !== undefined && <p>{error.message}</p>}
|
|
93
|
+
</div>
|
|
94
|
+
)
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
if (isLocked) {
|
|
98
|
+
return <p>Wallet locked. Unlock it to continue.</p>
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
// ... your dApp: party.partyId, signMessage(text), execute(params), ledgerApi(params)
|
|
102
|
+
}
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
`connect()` opens the SDK's wallet picker, a popup by default. There is no mode argument: the picker
|
|
106
|
+
is what chooses the wallet. Dismissing it rejects with `ConnectCancelledError`, which you filter by
|
|
107
|
+
`instanceof`, never by message. Whether `error` records it as well depends on which side saw
|
|
108
|
+
the close, so do not gate on that.
|
|
109
|
+
|
|
110
|
+
`signMessage`, `execute` and `ledgerApi` refuse with no session, and refuse again while the wallet
|
|
111
|
+
reports it is not authenticated; that is `isLocked`, and it happens after a successful connect.
|
|
112
|
+
`signMessage` and `execute` also refuse with no party, which `ledgerApi` does not need. The
|
|
113
|
+
reference gateway also refuses `signMessage` for a local party; `usePartyType().readPartyType()`
|
|
114
|
+
tells local from external when you ask. The SDK's status carries one `isConnected` flag, so a lock
|
|
115
|
+
and a wallet-side disconnect look the same
|
|
116
|
+
here. `useLedger().isReady` covers both, and `useParty().party` is `undefined` for the duration:
|
|
117
|
+
gate session content on the party, and use `isLocked` only to explain why it went away.
|
|
118
|
+
|
|
119
|
+
## Reference
|
|
120
|
+
|
|
121
|
+
Every hook and every config field is documented in JSDoc, which your editor surfaces at the call
|
|
122
|
+
site and which is published at
|
|
123
|
+
[docs.dappbooster.cc](https://docs.dappbooster.cc/). Start at
|
|
124
|
+
`CantonConnectProvider` and `CantonConnectConfig`.
|
|
125
|
+
|
|
126
|
+
## Testing helpers
|
|
127
|
+
|
|
128
|
+
- `createMockAdapter()`, from the package root: a `ProviderAdapter` answering `connect`,
|
|
129
|
+
`disconnect`, `status` and `listAccounts` with no wallet installed, so a dApp or a test can
|
|
130
|
+
connect and show a party. Anything else throws, naming the method.
|
|
131
|
+
- From `@bootnodedev/canton-connect/testing`: `createFakeWallet()`, a CIP-0103 extension driven over
|
|
132
|
+
`postMessage` that exercises the SDK's real announce and detect path; `createAutoPicker()`, a
|
|
133
|
+
headless picker so `connect()` runs without a popup; `FakeSessionProvider`, the context rehydrated
|
|
134
|
+
at an asked-for session with no SDK behind it; and `pause(ms)`, a real-timer sleep.
|
|
135
|
+
|
|
136
|
+
```tsx
|
|
137
|
+
import { CantonConnectProvider, createMockAdapter } from '@bootnodedev/canton-connect'
|
|
138
|
+
import { createAutoPicker } from '@bootnodedev/canton-connect/testing'
|
|
139
|
+
|
|
140
|
+
const config = {
|
|
141
|
+
appName: 'My dApp',
|
|
142
|
+
additionalAdapters: [createMockAdapter()],
|
|
143
|
+
walletPicker: createAutoPicker(),
|
|
144
|
+
}
|
|
145
|
+
```
|
|
146
|
+
|
|
147
|
+
## Architecture
|
|
148
|
+
|
|
149
|
+
[`architecture.md`](https://github.com/BootNodeDev/canton-dappbooster/blob/main/canton-connect/architecture.md)
|
|
150
|
+
maps the seams; its `architecture/` chapters carry the connection machine and the popup close guard.
|
|
151
|
+
|
|
152
|
+
## Testing
|
|
153
|
+
|
|
154
|
+
`pnpm test`: vitest + jsdom + Testing Library. `pnpm coverage` runs the same suite under v8, with
|
|
155
|
+
`testing/`, `mock/` and the barrel excluded.
|