bitcoin-wallet-connector 0.1.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.
Files changed (158) hide show
  1. package/README.md +208 -0
  2. package/lib/BitcoinConnectionProvider.d.ts +23 -0
  3. package/lib/BitcoinWalletAdapterConnector-Bq835yj0.mjs +123 -0
  4. package/lib/BitcoinWalletAdapterConnector-Bq835yj0.mjs.map +1 -0
  5. package/lib/BitcoinWalletAdapterConnector-DMef0iHV.js +2 -0
  6. package/lib/BitcoinWalletAdapterConnector-DMef0iHV.js.map +1 -0
  7. package/lib/BitcoinWalletAdapterConnector.d.ts +30 -0
  8. package/lib/BitgetWalletAdapter.impl-C_HLO7Oi.mjs +10 -0
  9. package/lib/BitgetWalletAdapter.impl-C_HLO7Oi.mjs.map +1 -0
  10. package/lib/BitgetWalletAdapter.impl-CxnKMf7U.js +2 -0
  11. package/lib/BitgetWalletAdapter.impl-CxnKMf7U.js.map +1 -0
  12. package/lib/LeatherWalletAdapter.impl-B2QgX_tO.js +2 -0
  13. package/lib/LeatherWalletAdapter.impl-B2QgX_tO.js.map +1 -0
  14. package/lib/LeatherWalletAdapter.impl-RUYx555r.mjs +184 -0
  15. package/lib/LeatherWalletAdapter.impl-RUYx555r.mjs.map +1 -0
  16. package/lib/MagicEdenWalletAdapter.impl-CrA6SGvG.mjs +235 -0
  17. package/lib/MagicEdenWalletAdapter.impl-CrA6SGvG.mjs.map +1 -0
  18. package/lib/MagicEdenWalletAdapter.impl-Di3Nu2S5.js +2 -0
  19. package/lib/MagicEdenWalletAdapter.impl-Di3Nu2S5.js.map +1 -0
  20. package/lib/OkxWalletAdapter.impl-BepoUL1B.mjs +67 -0
  21. package/lib/OkxWalletAdapter.impl-BepoUL1B.mjs.map +1 -0
  22. package/lib/OkxWalletAdapter.impl-C8kesjGu.js +2 -0
  23. package/lib/OkxWalletAdapter.impl-C8kesjGu.js.map +1 -0
  24. package/lib/UnisatCompatibleWalletAdapterImpl-Cq2Oqk1b.js +2 -0
  25. package/lib/UnisatCompatibleWalletAdapterImpl-Cq2Oqk1b.js.map +1 -0
  26. package/lib/UnisatCompatibleWalletAdapterImpl-M38FqkZI.mjs +137 -0
  27. package/lib/UnisatCompatibleWalletAdapterImpl-M38FqkZI.mjs.map +1 -0
  28. package/lib/UnisatWalletAdapter.impl-CJB22se8.mjs +14 -0
  29. package/lib/UnisatWalletAdapter.impl-CJB22se8.mjs.map +1 -0
  30. package/lib/UnisatWalletAdapter.impl-EISvxdpc.js +2 -0
  31. package/lib/UnisatWalletAdapter.impl-EISvxdpc.js.map +1 -0
  32. package/lib/WalletAdapters.types-CnvOqHFH.mjs +32 -0
  33. package/lib/WalletAdapters.types-CnvOqHFH.mjs.map +1 -0
  34. package/lib/WalletAdapters.types-De_x1lzr.js +2 -0
  35. package/lib/WalletAdapters.types-De_x1lzr.js.map +1 -0
  36. package/lib/WalletAdapters.types.d.ts +110 -0
  37. package/lib/XverseCompatibleWalletAdapterImpl-Bf-BK5VK.js +2 -0
  38. package/lib/XverseCompatibleWalletAdapterImpl-Bf-BK5VK.js.map +1 -0
  39. package/lib/XverseCompatibleWalletAdapterImpl-DXKnO_-V.mjs +151 -0
  40. package/lib/XverseCompatibleWalletAdapterImpl-DXKnO_-V.mjs.map +1 -0
  41. package/lib/XverseWalletAdapter.impl-CZO0RQva.mjs +105 -0
  42. package/lib/XverseWalletAdapter.impl-CZO0RQva.mjs.map +1 -0
  43. package/lib/XverseWalletAdapter.impl-lJwMi-Iv.js +2 -0
  44. package/lib/XverseWalletAdapter.impl-lJwMi-Iv.js.map +1 -0
  45. package/lib/adapters/BitgetWalletAdapter.d.ts +2 -0
  46. package/lib/adapters/BitgetWalletAdapter.impl.d.ts +8 -0
  47. package/lib/adapters/LeatherWalletAdapter.d.ts +2 -0
  48. package/lib/adapters/LeatherWalletAdapter.impl.d.ts +41 -0
  49. package/lib/adapters/MagicEdenWalletAdapter.d.ts +11 -0
  50. package/lib/adapters/MagicEdenWalletAdapter.impl.d.ts +22 -0
  51. package/lib/adapters/MockAddressWalletAdapter.d.ts +33 -0
  52. package/lib/adapters/OkxWalletAdapter.d.ts +2 -0
  53. package/lib/adapters/OkxWalletAdapter.impl.d.ts +51 -0
  54. package/lib/adapters/UnisatWalletAdapter.d.ts +2 -0
  55. package/lib/adapters/UnisatWalletAdapter.impl.d.ts +14 -0
  56. package/lib/adapters/XverseWalletAdapter.d.ts +3 -0
  57. package/lib/adapters/XverseWalletAdapter.impl.d.ts +14 -0
  58. package/lib/adapters/index.d.ts +7 -0
  59. package/lib/adapters.js +2 -0
  60. package/lib/adapters.js.map +1 -0
  61. package/lib/adapters.mjs +11 -0
  62. package/lib/adapters.mjs.map +1 -0
  63. package/lib/bitget-C7oB4Ffq.mjs +5 -0
  64. package/lib/bitget-C7oB4Ffq.mjs.map +1 -0
  65. package/lib/bitget-DXnsxx_y.js +2 -0
  66. package/lib/bitget-DXnsxx_y.js.map +1 -0
  67. package/lib/index-CaV3F1Nm.js +424 -0
  68. package/lib/index-CaV3F1Nm.js.map +1 -0
  69. package/lib/index-CcQUdePc.mjs +12224 -0
  70. package/lib/index-CcQUdePc.mjs.map +1 -0
  71. package/lib/index-D7YwhNAG.mjs +3946 -0
  72. package/lib/index-D7YwhNAG.mjs.map +1 -0
  73. package/lib/index-Zx0KcpYx.js +2 -0
  74. package/lib/index-Zx0KcpYx.js.map +1 -0
  75. package/lib/index.d.ts +3 -0
  76. package/lib/index.js +2 -0
  77. package/lib/index.js.map +1 -0
  78. package/lib/index.mjs +20 -0
  79. package/lib/index.mjs.map +1 -0
  80. package/lib/leather-BoQG_CPn.mjs +5 -0
  81. package/lib/leather-BoQG_CPn.mjs.map +1 -0
  82. package/lib/leather-DJ8nWmM8.js +2 -0
  83. package/lib/leather-DJ8nWmM8.js.map +1 -0
  84. package/lib/magiceden-B36CEQa6.js +2 -0
  85. package/lib/magiceden-B36CEQa6.js.map +1 -0
  86. package/lib/magiceden-Cg7d3agI.mjs +5 -0
  87. package/lib/magiceden-Cg7d3agI.mjs.map +1 -0
  88. package/lib/misc-B5EWO_dn.mjs +10 -0
  89. package/lib/misc-B5EWO_dn.mjs.map +1 -0
  90. package/lib/misc-CigR0RqC.js +2 -0
  91. package/lib/misc-CigR0RqC.js.map +1 -0
  92. package/lib/okx-ChwzM0dK.js +2 -0
  93. package/lib/okx-ChwzM0dK.js.map +1 -0
  94. package/lib/okx-DWbHwazu.mjs +5 -0
  95. package/lib/okx-DWbHwazu.mjs.map +1 -0
  96. package/lib/react.d.ts +2 -0
  97. package/lib/react.js +2 -0
  98. package/lib/react.js.map +1 -0
  99. package/lib/react.mjs +128 -0
  100. package/lib/react.mjs.map +1 -0
  101. package/lib/transaction-CiLOYSE_.mjs +1063 -0
  102. package/lib/transaction-CiLOYSE_.mjs.map +1 -0
  103. package/lib/transaction-CzdnbXSo.js +2 -0
  104. package/lib/transaction-CzdnbXSo.js.map +1 -0
  105. package/lib/unisat-BvZW5h0U.js +2 -0
  106. package/lib/unisat-BvZW5h0U.js.map +1 -0
  107. package/lib/unisat-pLgab4nG.mjs +5 -0
  108. package/lib/unisat-pLgab4nG.mjs.map +1 -0
  109. package/lib/utils/StateChannel.d.ts +14 -0
  110. package/lib/utils/UnisatCompatibleWalletAdapterImpl.d.ts +99 -0
  111. package/lib/utils/XverseCompatibleWalletAdapterImpl.d.ts +80 -0
  112. package/lib/utils/XverseCompatibleWalletAdapterImpl_legacy.d.ts +44 -0
  113. package/lib/utils/bitcoinAddressHelpers.d.ts +14 -0
  114. package/lib/utils/bitcoinNetworkHelpers.d.ts +4 -0
  115. package/lib/utils/createAdapterAvailability.d.ts +15 -0
  116. package/lib/utils/error.d.ts +6 -0
  117. package/lib/utils/misc.d.ts +3 -0
  118. package/lib/xverse-IKOHyGi-.js +2 -0
  119. package/lib/xverse-IKOHyGi-.js.map +1 -0
  120. package/lib/xverse-iHLNanCB.mjs +5 -0
  121. package/lib/xverse-iHLNanCB.mjs.map +1 -0
  122. package/package.json +86 -0
  123. package/src/BitcoinConnectionProvider.stories.tsx +329 -0
  124. package/src/BitcoinConnectionProvider.tsx +234 -0
  125. package/src/BitcoinWalletAdapterConnector.ts +166 -0
  126. package/src/WalletAdapters.types.ts +154 -0
  127. package/src/_/bitget.png +0 -0
  128. package/src/_/leather.svg +4 -0
  129. package/src/_/magiceden.png +0 -0
  130. package/src/_/okx.png +0 -0
  131. package/src/_/unisat.svg +31 -0
  132. package/src/_/xverse.png +0 -0
  133. package/src/adapters/BitgetWalletAdapter.impl.ts +22 -0
  134. package/src/adapters/BitgetWalletAdapter.ts +44 -0
  135. package/src/adapters/LeatherWalletAdapter.impl.ts +324 -0
  136. package/src/adapters/LeatherWalletAdapter.ts +35 -0
  137. package/src/adapters/MagicEdenWalletAdapter.impl.ts +139 -0
  138. package/src/adapters/MagicEdenWalletAdapter.ts +51 -0
  139. package/src/adapters/MockAddressWalletAdapter.ts +199 -0
  140. package/src/adapters/OkxWalletAdapter.impl.ts +168 -0
  141. package/src/adapters/OkxWalletAdapter.ts +37 -0
  142. package/src/adapters/UnisatWalletAdapter.impl.ts +32 -0
  143. package/src/adapters/UnisatWalletAdapter.ts +50 -0
  144. package/src/adapters/XverseWalletAdapter.impl.ts +150 -0
  145. package/src/adapters/XverseWalletAdapter.ts +37 -0
  146. package/src/adapters/index.ts +7 -0
  147. package/src/env.d.ts +9 -0
  148. package/src/index.ts +3 -0
  149. package/src/react.ts +9 -0
  150. package/src/utils/StateChannel.ts +39 -0
  151. package/src/utils/UnisatCompatibleWalletAdapterImpl.ts +342 -0
  152. package/src/utils/XverseCompatibleWalletAdapterImpl.ts +288 -0
  153. package/src/utils/XverseCompatibleWalletAdapterImpl_legacy.ts +278 -0
  154. package/src/utils/bitcoinAddressHelpers.ts +132 -0
  155. package/src/utils/bitcoinNetworkHelpers.ts +17 -0
  156. package/src/utils/createAdapterAvailability.ts +92 -0
  157. package/src/utils/error.ts +13 -0
  158. package/src/utils/misc.ts +10 -0
@@ -0,0 +1,329 @@
1
+ import { Meta, StoryFn } from "@storybook/react-vite"
2
+ import React, { useState } from "react"
3
+ import {
4
+ BitcoinConnectionProvider,
5
+ useBitcoinConnectionContext,
6
+ WalletSession,
7
+ } from "./BitcoinConnectionProvider"
8
+ import {
9
+ WalletAdapterAddress,
10
+ WalletAdapterBitcoinNetwork,
11
+ } from "./WalletAdapters.types"
12
+ import {
13
+ UnisatWalletAdapter,
14
+ XverseWalletAdapter,
15
+ OkxWalletAdapter,
16
+ LeatherWalletAdapter,
17
+ BitgetWalletAdapter,
18
+ MagicEdenWalletAdapterFactory,
19
+ } from "./adapters"
20
+
21
+ const adapterFactories = [
22
+ UnisatWalletAdapter,
23
+ XverseWalletAdapter,
24
+ OkxWalletAdapter,
25
+ LeatherWalletAdapter,
26
+ BitgetWalletAdapter,
27
+ MagicEdenWalletAdapterFactory(WalletAdapterBitcoinNetwork.MAINNET),
28
+ ]
29
+
30
+ export default {
31
+ title: "Components/BitcoinConnectionProvider",
32
+ component: BitcoinConnectionProvider,
33
+ } as Meta<typeof BitcoinConnectionProvider>
34
+
35
+ const WalletConnectionContent = (): React.ReactElement => {
36
+ const [debugInfo, setDebugInfo] = useState<string[]>([])
37
+ const [signMessageText, setSignMessageText] = useState("Hello Bitcoin!")
38
+ const [selectedAddress, setSelectedAddress] = useState<string>("")
39
+ const [signResult, setSignResult] = useState<string>("")
40
+ const [isSigning, setIsSigning] = useState(false)
41
+ const ctx = useBitcoinConnectionContext()
42
+
43
+ const addDebugInfo = (info: string): void => {
44
+ setDebugInfo(prev => [
45
+ ...prev,
46
+ `${new Date().toLocaleTimeString()}: ${info}`,
47
+ ])
48
+ }
49
+
50
+ const handleSignMessage = async (): Promise<void> => {
51
+ if (!ctx.walletSession || !selectedAddress) return
52
+
53
+ setIsSigning(true)
54
+ addDebugInfo(`Signing message with ${selectedAddress.slice(0, 10)}...`)
55
+
56
+ try {
57
+ const result = await ctx.walletSession.adapter.signMessage(
58
+ selectedAddress,
59
+ signMessageText,
60
+ )
61
+ setSignResult(JSON.stringify(result, null, 2))
62
+ addDebugInfo(`Sign success: ${result.algorithm}`)
63
+ } catch (err) {
64
+ const message = err instanceof Error ? err.message : String(err)
65
+ addDebugInfo(`Sign failed: ${message}`)
66
+ setSignResult(`Error: ${message}`)
67
+ } finally {
68
+ setIsSigning(false)
69
+ }
70
+ }
71
+
72
+ return (
73
+ <div style={{ padding: "16px", fontFamily: "system-ui, sans-serif" }}>
74
+ <h2 style={{ marginBottom: "16px" }}>Bitcoin Connection Provider Demo</h2>
75
+
76
+ <div style={{ marginBottom: "16px" }}>
77
+ <h3>Connection Status</h3>
78
+ <p>
79
+ <strong>Connected:</strong> {ctx.walletSession ? "Yes" : "No"}
80
+ </p>
81
+ <p>
82
+ <strong>Initializing:</strong>{" "}
83
+ {ctx.isConnectionInitializing ? "Yes" : "No"}
84
+ </p>
85
+ {ctx.walletSession && (
86
+ <>
87
+ <p>
88
+ <strong>Adapter ID:</strong> {ctx.walletSession.adapterId}
89
+ </p>
90
+ <p>
91
+ <strong>Addresses:</strong>
92
+ </p>
93
+ <ul>
94
+ {ctx.walletSession.addresses.map((addr, i) => (
95
+ <li key={i}>
96
+ {addr.purposes.join(", ")}: {addr.address.slice(0, 20)}...
97
+ </li>
98
+ ))}
99
+ </ul>
100
+ </>
101
+ )}
102
+ </div>
103
+
104
+ <div style={{ marginBottom: "16px" }}>
105
+ <h3>Available Wallets ({ctx.availableAdapters.length})</h3>
106
+ <div style={{ display: "flex", flexWrap: "wrap", gap: "8px" }}>
107
+ {ctx.availableAdapters.map(([adapterId, adapter]) => (
108
+ <button
109
+ key={adapterId}
110
+ onClick={() => {
111
+ addDebugInfo(`Connecting to ${adapterId}...`)
112
+ ctx
113
+ .connect(adapterId, adapter)
114
+ .then(() => {
115
+ addDebugInfo(`Connected to ${adapterId}`)
116
+ })
117
+ .catch(err => {
118
+ addDebugInfo(`Failed to connect: ${err.message}`)
119
+ })
120
+ }}
121
+ disabled={ctx.isConnectionInitializing}
122
+ style={{
123
+ padding: "8px 16px",
124
+ cursor: ctx.isConnectionInitializing
125
+ ? "not-allowed"
126
+ : "pointer",
127
+ border: "1px solid #ccc",
128
+ borderRadius: "4px",
129
+ background: "#fff",
130
+ }}
131
+ >
132
+ {adapterId}
133
+ </button>
134
+ ))}
135
+ </div>
136
+ </div>
137
+
138
+ {ctx.walletSession && (
139
+ <button
140
+ onClick={() => {
141
+ addDebugInfo("Disconnecting...")
142
+ void ctx.disconnect().then(() => {
143
+ addDebugInfo("Disconnected")
144
+ })
145
+ }}
146
+ style={{
147
+ padding: "8px 16px",
148
+ cursor: "pointer",
149
+ border: "1px solid #dc3545",
150
+ borderRadius: "4px",
151
+ background: "#dc3545",
152
+ color: "#fff",
153
+ marginBottom: "16px",
154
+ }}
155
+ >
156
+ Disconnect
157
+ </button>
158
+ )}
159
+
160
+ {ctx.walletSession && (
161
+ <div
162
+ style={{
163
+ marginBottom: "16px",
164
+ padding: "12px",
165
+ border: "1px solid #ddd",
166
+ borderRadius: "4px",
167
+ }}
168
+ >
169
+ <h3 style={{ marginTop: 0 }}>Sign Message</h3>
170
+ <div style={{ marginBottom: "8px" }}>
171
+ <label style={{ display: "block", marginBottom: "4px" }}>
172
+ Select Address:
173
+ </label>
174
+ <select
175
+ value={selectedAddress}
176
+ onChange={e => setSelectedAddress(e.target.value)}
177
+ style={{
178
+ width: "100%",
179
+ padding: "8px",
180
+ borderRadius: "4px",
181
+ border: "1px solid #ccc",
182
+ }}
183
+ >
184
+ <option value="">-- Select an address --</option>
185
+ {ctx.walletSession.addresses.map((addr, i) => (
186
+ <option key={i} value={addr.address}>
187
+ [{addr.purposes.join(", ")}] {addr.address.slice(0, 20)}...
188
+ </option>
189
+ ))}
190
+ </select>
191
+ </div>
192
+ <div style={{ marginBottom: "8px" }}>
193
+ <label style={{ display: "block", marginBottom: "4px" }}>
194
+ Message to Sign:
195
+ </label>
196
+ <input
197
+ type="text"
198
+ value={signMessageText}
199
+ onChange={e => setSignMessageText(e.target.value)}
200
+ style={{
201
+ width: "100%",
202
+ padding: "8px",
203
+ borderRadius: "4px",
204
+ border: "1px solid #ccc",
205
+ boxSizing: "border-box",
206
+ }}
207
+ />
208
+ </div>
209
+ <button
210
+ onClick={() => void handleSignMessage()}
211
+ disabled={!selectedAddress || isSigning}
212
+ style={{
213
+ padding: "8px 16px",
214
+ cursor: !selectedAddress || isSigning ? "not-allowed" : "pointer",
215
+ border: "1px solid #007bff",
216
+ borderRadius: "4px",
217
+ background: "#007bff",
218
+ color: "#fff",
219
+ opacity: !selectedAddress || isSigning ? 0.6 : 1,
220
+ }}
221
+ >
222
+ {isSigning ? "Signing..." : "Sign Message"}
223
+ </button>
224
+ {signResult && (
225
+ <div style={{ marginTop: "8px" }}>
226
+ <label style={{ display: "block", marginBottom: "4px" }}>
227
+ Result:
228
+ </label>
229
+ <pre
230
+ style={{
231
+ background: "#f8f9fa",
232
+ padding: "8px",
233
+ borderRadius: "4px",
234
+ fontSize: "12px",
235
+ overflow: "auto",
236
+ maxHeight: "200px",
237
+ margin: 0,
238
+ }}
239
+ >
240
+ {signResult}
241
+ </pre>
242
+ </div>
243
+ )}
244
+ </div>
245
+ )}
246
+
247
+ <div style={{ marginBottom: "16px" }}>
248
+ <h3>Registered Adapter Factories ({ctx.adapterFactories.length})</h3>
249
+ <ul>
250
+ {ctx.adapterFactories.map((factory, i) => (
251
+ <li key={i}>{factory.adapterId}</li>
252
+ ))}
253
+ </ul>
254
+ </div>
255
+
256
+ <button
257
+ onClick={() => setDebugInfo([])}
258
+ style={{
259
+ padding: "4px 8px",
260
+ cursor: "pointer",
261
+ border: "1px solid #ccc",
262
+ borderRadius: "4px",
263
+ background: "#f8f9fa",
264
+ marginBottom: "8px",
265
+ }}
266
+ >
267
+ Clear Debug Info
268
+ </button>
269
+
270
+ {debugInfo.length > 0 && (
271
+ <div
272
+ style={{
273
+ padding: "8px",
274
+ background: "#f8f9fa",
275
+ borderRadius: "4px",
276
+ fontSize: "12px",
277
+ }}
278
+ >
279
+ <h4 style={{ margin: "0 0 8px 0" }}>Debug Info:</h4>
280
+ {debugInfo.map((info, index) => (
281
+ <div key={index}>{info}</div>
282
+ ))}
283
+ </div>
284
+ )}
285
+ </div>
286
+ )
287
+ }
288
+
289
+ export const Default: StoryFn<typeof BitcoinConnectionProvider> = () => {
290
+ const [events, setEvents] = useState<string[]>([])
291
+
292
+ const addEvent = (event: string): void => {
293
+ setEvents(prev => [...prev, `${new Date().toLocaleTimeString()}: ${event}`])
294
+ }
295
+
296
+ return (
297
+ <BitcoinConnectionProvider
298
+ adapterFactories={adapterFactories}
299
+ onWalletConnected={(session: WalletSession) => {
300
+ addEvent(`Wallet connected: ${session.adapterId}`)
301
+ }}
302
+ onWalletAddressesChanged={(addresses: WalletAdapterAddress[]) => {
303
+ addEvent(`Addresses changed: ${addresses.length} addresses`)
304
+ }}
305
+ onWalletDisconnected={() => {
306
+ debugger
307
+ addEvent("Wallet disconnected")
308
+ }}
309
+ >
310
+ <WalletConnectionContent />
311
+ {events.length > 0 && (
312
+ <div
313
+ style={{
314
+ margin: "16px",
315
+ padding: "8px",
316
+ background: "#e8f4f8",
317
+ borderRadius: "4px",
318
+ fontSize: "12px",
319
+ }}
320
+ >
321
+ <h4 style={{ margin: "0 0 8px 0" }}>Provider Events:</h4>
322
+ {events.map((event, index) => (
323
+ <div key={index}>{event}</div>
324
+ ))}
325
+ </div>
326
+ )}
327
+ </BitcoinConnectionProvider>
328
+ )
329
+ }
@@ -0,0 +1,234 @@
1
+ import {
2
+ createContext,
3
+ FC,
4
+ ReactNode,
5
+ useCallback,
6
+ useContext,
7
+ useEffect,
8
+ useMemo,
9
+ useRef,
10
+ useState,
11
+ } from "react"
12
+ import { BitcoinWalletAdapterConnector } from "./BitcoinWalletAdapterConnector"
13
+ import {
14
+ WalletAdapter,
15
+ WalletAdapterAddress,
16
+ WalletAdapterStatic,
17
+ } from "./WalletAdapters.types"
18
+
19
+ export interface WalletSession {
20
+ adapterId: string
21
+ adapter: WalletAdapter
22
+ addresses: WalletAdapterAddress[]
23
+ }
24
+
25
+ export interface BitcoinConnectionContextValue {
26
+ // Core connection state
27
+ walletSession: null | WalletSession
28
+ isConnectionInitializing: boolean
29
+ connect: (adapterId: string, adapter: WalletAdapter) => Promise<void>
30
+ disconnect: () => Promise<void>
31
+
32
+ // Wallet management (for UI)
33
+ adapterFactories: WalletAdapterStatic<WalletAdapter>[]
34
+ availableAdapters: (readonly [adapterId: string, adapter: WalletAdapter])[]
35
+ }
36
+
37
+ const BitcoinConnectionContext =
38
+ createContext<null | BitcoinConnectionContextValue>(null)
39
+
40
+ function useConnectorState<T>(
41
+ subscribe: (listener: (value: T) => void) => { unsubscribe: () => void },
42
+ getSnapshot: () => T,
43
+ deps: ReadonlyArray<unknown>,
44
+ ): T {
45
+ const [value, setValue] = useState(getSnapshot)
46
+
47
+ useEffect(() => {
48
+ const subscription = subscribe(setValue)
49
+ return () => subscription.unsubscribe()
50
+ // eslint-disable-next-line react-hooks/exhaustive-deps
51
+ }, deps)
52
+
53
+ return value
54
+ }
55
+
56
+ export const BitcoinConnectionProvider: FC<{
57
+ children: ReactNode
58
+ adapterFactories: WalletAdapterStatic<WalletAdapter>[]
59
+ onWalletConnected?: (session: WalletSession) => void
60
+ onWalletAddressesChanged?: (addresses: WalletAdapterAddress[]) => void
61
+ onWalletDisconnected?: () => void
62
+ }> = props => {
63
+ const [isConnectionInitializing, setIsConnectionInitializing] =
64
+ useState(false)
65
+ const [walletSession, setWalletSession] = useState<null | WalletSession>(null)
66
+
67
+ const onWalletConnected = usePersistFn(props.onWalletConnected ?? noop)
68
+ const onWalletAddressesChanged = usePersistFn(
69
+ props.onWalletAddressesChanged ?? noop,
70
+ )
71
+ const onWalletDisconnected = usePersistFn(props.onWalletDisconnected ?? noop)
72
+
73
+ const connector = useMemo(
74
+ () => new BitcoinWalletAdapterConnector(props.adapterFactories),
75
+ [props.adapterFactories],
76
+ )
77
+
78
+ useEffect(
79
+ () => () => {
80
+ connector.dispose()
81
+ },
82
+ [connector],
83
+ )
84
+
85
+ const availableAdapters = useConnectorState(
86
+ listener => connector.subscribeAvailableAdapters(listener),
87
+ () => connector.getAvailableAdapters(),
88
+ [connector],
89
+ )
90
+
91
+ const connectInfo = useConnectorState(
92
+ listener => connector.subscribeConnectedInfo(listener),
93
+ () => connector.getConnectedInfo(),
94
+ [connector],
95
+ )
96
+
97
+ const ctxValue = useMemo(
98
+ (): BitcoinConnectionContextValue => ({
99
+ walletSession,
100
+ isConnectionInitializing,
101
+ connect: connector.connect.bind(connector),
102
+ disconnect: connector.disconnect.bind(connector),
103
+
104
+ adapterFactories: props.adapterFactories,
105
+ availableAdapters,
106
+ }),
107
+ [
108
+ walletSession,
109
+ isConnectionInitializing,
110
+ connector,
111
+ props.adapterFactories,
112
+ availableAdapters,
113
+ ],
114
+ )
115
+
116
+ useEffect(() => {
117
+ const abortController = new AbortController()
118
+
119
+ const sub = connector.subscribeConnectedInfo(info => {
120
+ if (info == null) {
121
+ try {
122
+ onWalletDisconnected()
123
+ } catch (e) {
124
+ console.error(
125
+ "[BitcoinConnectionProvider] onWalletDisconnected error",
126
+ e,
127
+ )
128
+ }
129
+
130
+ setWalletSession(null)
131
+ setIsConnectionInitializing(false)
132
+ return
133
+ }
134
+
135
+ setIsConnectionInitializing(true)
136
+ info.adapter
137
+ .getAddresses()
138
+ .then(addresses => {
139
+ if (abortController.signal.aborted) return
140
+
141
+ const walletSession: WalletSession = {
142
+ adapterId: info.adapterId,
143
+ adapter: info.adapter,
144
+ addresses,
145
+ }
146
+
147
+ try {
148
+ onWalletConnected(walletSession)
149
+ } catch (e) {
150
+ console.error(
151
+ "[BitcoinConnectionProvider] onWalletConnected error",
152
+ e,
153
+ )
154
+ }
155
+
156
+ setWalletSession(walletSession)
157
+ setIsConnectionInitializing(false)
158
+ })
159
+ .catch(err => {
160
+ if (abortController.signal.aborted) return
161
+ setIsConnectionInitializing(false)
162
+ console.error("[BitcoinConnectionProvider] connect error", err)
163
+ })
164
+ })
165
+
166
+ return () => {
167
+ abortController.abort()
168
+ sub.unsubscribe()
169
+ }
170
+ }, [connector, onWalletDisconnected, onWalletConnected])
171
+
172
+ // Listen to address changes from wallet
173
+ useEffect(() => {
174
+ if (connectInfo == null) return
175
+
176
+ const abortController = new AbortController()
177
+
178
+ const sub = connectInfo.adapter.onAddressesChanged(({ addresses }) => {
179
+ if (abortController.signal.aborted) return
180
+
181
+ setWalletSession(prev => {
182
+ if (prev == null) {
183
+ console.warn(
184
+ "[BitcoinConnectionProvider] onWalletAddressesChanged event received while disconnected, skipping...",
185
+ )
186
+ return prev
187
+ }
188
+
189
+ try {
190
+ onWalletAddressesChanged(addresses)
191
+ } catch (e) {
192
+ console.error(
193
+ "[BitcoinConnectionProvider] onWalletAddressesChanged error",
194
+ e,
195
+ )
196
+ }
197
+
198
+ return { ...prev, addresses }
199
+ })
200
+ })
201
+ return () => {
202
+ abortController.abort()
203
+ sub.unsubscribe()
204
+ }
205
+ }, [connectInfo, onWalletAddressesChanged])
206
+
207
+ return (
208
+ <BitcoinConnectionContext.Provider value={ctxValue}>
209
+ {props.children}
210
+ </BitcoinConnectionContext.Provider>
211
+ )
212
+ }
213
+
214
+ export function useBitcoinConnectionContext(): BitcoinConnectionContextValue {
215
+ const ctx = useContext(BitcoinConnectionContext)
216
+ if (!ctx) {
217
+ throw new Error(
218
+ "useBitcoinConnectionContext must be used within BitcoinConnectionProvider",
219
+ )
220
+ }
221
+
222
+ return ctx
223
+ }
224
+
225
+ const noop = (): void => {}
226
+
227
+ const usePersistFn = <T extends (...args: any[]) => any>(
228
+ fn: T,
229
+ ): ((...args: Parameters<T>) => ReturnType<T>) => {
230
+ const fnRef = useRef(fn)
231
+ fnRef.current = fn
232
+
233
+ return useCallback((...args) => fnRef.current(...args), [])
234
+ }