@thru/wallet 0.2.22

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 (69) hide show
  1. package/README.md +67 -0
  2. package/android/build.gradle +37 -0
  3. package/android/src/main/AndroidManifest.xml +1 -0
  4. package/android/src/main/java/org/thru/walletnative/ThruWebViewBridgeModule.kt +77 -0
  5. package/app.plugin.cjs +101 -0
  6. package/dist/BrowserSDK-CpRFiJsW.d.ts +409 -0
  7. package/dist/index.d.ts +23 -0
  8. package/dist/index.js +941 -0
  9. package/dist/index.js.map +1 -0
  10. package/dist/native/react.d.ts +109 -0
  11. package/dist/native/react.js +2381 -0
  12. package/dist/native/react.js.map +1 -0
  13. package/dist/native.d.ts +329 -0
  14. package/dist/native.js +1126 -0
  15. package/dist/native.js.map +1 -0
  16. package/dist/react-ui.d.ts +5 -0
  17. package/dist/react-ui.js +266 -0
  18. package/dist/react-ui.js.map +1 -0
  19. package/dist/react.d.ts +66 -0
  20. package/dist/react.js +1151 -0
  21. package/dist/react.js.map +1 -0
  22. package/expo-module.config.json +6 -0
  23. package/package.json +114 -0
  24. package/src/BrowserSDK.ts +315 -0
  25. package/src/index.ts +27 -0
  26. package/src/interfaces/IThruChain.ts +37 -0
  27. package/src/interfaces/accounts.ts +61 -0
  28. package/src/interfaces/index.ts +9 -0
  29. package/src/interfaces/types.ts +95 -0
  30. package/src/native/NativeSDK.test.ts +819 -0
  31. package/src/native/NativeSDK.ts +773 -0
  32. package/src/native/index.ts +39 -0
  33. package/src/native/provider/NativeProvider.ts +363 -0
  34. package/src/native/provider/WebViewBridge.test.ts +339 -0
  35. package/src/native/provider/WebViewBridge.ts +339 -0
  36. package/src/native/provider/chains/ThruChain.ts +85 -0
  37. package/src/native/provider/shell.html +88 -0
  38. package/src/native/provider/shell.test.ts +56 -0
  39. package/src/native/provider/shell.ts +111 -0
  40. package/src/native/provider/shims-html.d.ts +4 -0
  41. package/src/native/react/ThruContext.ts +37 -0
  42. package/src/native/react/ThruProvider.tsx +168 -0
  43. package/src/native/react/ThruWalletSheet.tsx +1162 -0
  44. package/src/native/react/android-webauthn.ts +37 -0
  45. package/src/native/react/hooks/useAccounts.ts +35 -0
  46. package/src/native/react/hooks/useThru.ts +11 -0
  47. package/src/native/react/hooks/useWallet.ts +71 -0
  48. package/src/native/react/hooks/useWalletAvailability.ts +31 -0
  49. package/src/native/react/hooks/waitForWallet.ts +21 -0
  50. package/src/native/react/index.ts +29 -0
  51. package/src/protocol/index.ts +2 -0
  52. package/src/protocol/postMessage.ts +283 -0
  53. package/src/protocol/walletState.ts +12 -0
  54. package/src/provider/EmbeddedProvider.ts +330 -0
  55. package/src/provider/IframeManager.ts +438 -0
  56. package/src/provider/chains/ThruChain.ts +86 -0
  57. package/src/provider/index.ts +17 -0
  58. package/src/provider/types/messages.ts +37 -0
  59. package/src/react/ThruContext.ts +31 -0
  60. package/src/react/ThruProvider.tsx +169 -0
  61. package/src/react/hooks/useAccounts.ts +38 -0
  62. package/src/react/hooks/useThru.ts +11 -0
  63. package/src/react/hooks/useWallet.ts +81 -0
  64. package/src/react/index.ts +30 -0
  65. package/src/react-ui/ThruAccountSwitcher.tsx +187 -0
  66. package/src/react-ui/custom.d.ts +8 -0
  67. package/src/react-ui/index.ts +1 -0
  68. package/src/static/logo.png +0 -0
  69. package/src/static/logomark_red.svg +11 -0
@@ -0,0 +1,438 @@
1
+ import type {
2
+ InferSuccessfulPostMessageResponse,
3
+ PostMessageEvent,
4
+ PostMessageRequest,
5
+ PostMessageResponse,
6
+ } from './types/messages';
7
+ import {
8
+ IFRAME_READY_EVENT,
9
+ POST_MESSAGE_EVENT_TYPE,
10
+ POST_MESSAGE_REQUEST_TYPES,
11
+ createRequestId,
12
+ } from './types/messages';
13
+
14
+ /**
15
+ * Allowed production origins for wallet iframe URLs.
16
+ * Development builds additionally allow localhost, LAN, and Tailscale
17
+ * origins so local HTTPS RP-ID testing can use the hosted wallet path.
18
+ */
19
+ const PRODUCTION_IFRAME_ORIGINS = ['https://wallet.thru.org'];
20
+
21
+ const SLOW_REQUEST_TIMEOUT_MS = 5 * 60 * 1000;
22
+ const FAST_REQUEST_TIMEOUT_MS = 30 * 1000;
23
+
24
+ const SLOW_REQUEST_TYPES: ReadonlySet<string> = new Set([
25
+ POST_MESSAGE_REQUEST_TYPES.CONNECT,
26
+ POST_MESSAGE_REQUEST_TYPES.SIGN_MESSAGE,
27
+ POST_MESSAGE_REQUEST_TYPES.SIGN_TRANSACTION,
28
+ POST_MESSAGE_REQUEST_TYPES.MANAGE_ACCOUNTS,
29
+ ]);
30
+
31
+ function isPrivateIpv4Host(hostname: string): boolean {
32
+ const parts = hostname.split('.').map((part) => Number(part));
33
+ if (
34
+ parts.length !== 4 ||
35
+ parts.some((part) => !Number.isInteger(part) || part < 0 || part > 255)
36
+ ) {
37
+ return false;
38
+ }
39
+
40
+ const [a, b] = parts;
41
+ return (
42
+ a === 10 ||
43
+ a === 127 ||
44
+ (a === 172 && b >= 16 && b <= 31) ||
45
+ (a === 192 && b === 168) ||
46
+ (a === 100 && b >= 64 && b <= 127)
47
+ );
48
+ }
49
+
50
+ function isDevelopmentHostname(hostname: string): boolean {
51
+ return (
52
+ hostname === 'localhost' ||
53
+ hostname === '::1' ||
54
+ !hostname.includes('.') ||
55
+ hostname.endsWith('.local') ||
56
+ hostname.endsWith('.ts.net') ||
57
+ isPrivateIpv4Host(hostname)
58
+ );
59
+ }
60
+
61
+ function isAllowedDevelopmentOrigin(url: URL): boolean {
62
+ if (url.protocol !== 'http:' && url.protocol !== 'https:') return false;
63
+ if (typeof window === 'undefined') return false;
64
+
65
+ const appHostname = window.location.hostname.toLowerCase();
66
+ if (!isDevelopmentHostname(appHostname)) return false;
67
+
68
+ return isDevelopmentHostname(url.hostname.toLowerCase());
69
+ }
70
+
71
+ /**
72
+ * Validates that the iframe URL is from a trusted origin
73
+ * @throws Error if the origin is not allowed
74
+ */
75
+ function validateIframeOrigin(iframeUrl: string): void {
76
+ let url: URL;
77
+ try {
78
+ url = new URL(iframeUrl);
79
+ } catch (error) {
80
+ throw new Error(
81
+ `Invalid iframe URL: ${iframeUrl}. URL must be a valid absolute URL.`
82
+ );
83
+ }
84
+
85
+ const origin = url.origin;
86
+ const isAllowed =
87
+ PRODUCTION_IFRAME_ORIGINS.includes(origin) || isAllowedDevelopmentOrigin(url);
88
+
89
+ if (!isAllowed) {
90
+ throw new Error(
91
+ `Untrusted iframe origin: ${origin}. ` +
92
+ `Only trusted wallet origins are allowed: ${PRODUCTION_IFRAME_ORIGINS.join(', ')}. ` +
93
+ `Development builds also allow localhost, LAN, and Tailscale wallet origins. ` +
94
+ `This security check prevents malicious websites from loading unauthorized wallet iframes.`
95
+ );
96
+ }
97
+ }
98
+
99
+ /**
100
+ * Manages iframe lifecycle and postMessage communication
101
+ * Handles creating, showing/hiding iframe, and message passing
102
+ */
103
+ export class IframeManager {
104
+ private iframe: HTMLIFrameElement | null = null;
105
+ private iframeUrl: string;
106
+ private iframeOrigin: string;
107
+ private frameId: string;
108
+ private messageHandlers = new Map<string, (response: PostMessageResponse) => void>();
109
+ private messageListener: ((event: MessageEvent) => void) | null = null;
110
+ private readyPromise: Promise<void> | null = null;
111
+ private displayMode: 'modal' | 'inline' = 'modal';
112
+ private inlineContainer: HTMLElement | null = null;
113
+ private visible = false;
114
+
115
+ /**
116
+ * Callback for event broadcasts from iframe (no request id)
117
+ */
118
+ public onEvent?: (eventType: string, payload: any) => void;
119
+
120
+ constructor(iframeUrl: string) {
121
+ // Validate origin before accepting the URL
122
+ validateIframeOrigin(iframeUrl);
123
+
124
+ this.iframeUrl = iframeUrl;
125
+ this.iframeOrigin = new URL(iframeUrl).origin;
126
+ /* Used to correlate postMessage traffic with the correct iframe instance.
127
+ Important in dev (React Strict Mode) where iframes can be created twice. */
128
+ this.frameId = createRequestId('frame');
129
+ }
130
+
131
+ private getIframeSrc(): string {
132
+ const url = new URL(this.iframeUrl);
133
+ url.searchParams.set('tn_frame_id', this.frameId);
134
+ return url.toString();
135
+ }
136
+
137
+ /**
138
+ * Create and inject iframe into DOM
139
+ * Returns a promise that resolves when iframe is ready
140
+ */
141
+ async createIframe(): Promise<void> {
142
+ if (this.readyPromise) {
143
+ return this.readyPromise;
144
+ }
145
+
146
+ this.readyPromise = (async () => {
147
+ if (!this.iframe) {
148
+ this.iframe = document.createElement('iframe');
149
+ this.iframe.src = this.getIframeSrc();
150
+ /* Allow WebAuthn in cross-origin iframe for passkey auth. */
151
+ this.iframe.allow = 'publickey-credentials-get; publickey-credentials-create';
152
+ this.applyIframeStyles();
153
+ /* Keep hidden (but still load) until the wallet asks to show UI. */
154
+ this.setVisibility(false);
155
+
156
+ if (this.displayMode === 'inline' && this.inlineContainer) {
157
+ this.inlineContainer.appendChild(this.iframe);
158
+ } else {
159
+ document.body.appendChild(this.iframe);
160
+ }
161
+
162
+ // Set up message listener
163
+ this.messageListener = this.handleMessage.bind(this);
164
+ window.addEventListener('message', this.messageListener);
165
+ }
166
+
167
+ await this.waitForReady();
168
+ })().catch((error) => {
169
+ this.readyPromise = null;
170
+ throw error;
171
+ });
172
+
173
+ return this.readyPromise;
174
+ }
175
+
176
+ /**
177
+ * Wait for iframe to send 'ready' signal
178
+ */
179
+ private waitForReady(): Promise<void> {
180
+ return new Promise((resolve, reject) => {
181
+ let resolved = false;
182
+ let readyHandler: (event: MessageEvent) => void;
183
+ const cleanup = () => {
184
+ if (resolved) {
185
+ return;
186
+ }
187
+ resolved = true;
188
+ window.removeEventListener('message', readyHandler);
189
+ clearTimeout(timeout);
190
+ };
191
+
192
+ const timeout = setTimeout(() => {
193
+ cleanup();
194
+ reject(new Error('Iframe ready timeout - wallet failed to load'));
195
+ }, 10000);
196
+
197
+ readyHandler = (event: MessageEvent) => {
198
+ if (!this.isMessageFromIframe(event)) {
199
+ return;
200
+ }
201
+
202
+ if (event.data?.type === IFRAME_READY_EVENT) {
203
+ cleanup();
204
+ resolve();
205
+ }
206
+ };
207
+
208
+ window.addEventListener('message', readyHandler);
209
+ });
210
+ }
211
+
212
+ /**
213
+ * Mount iframe inline inside the provided container.
214
+ */
215
+ async mountInline(container: HTMLElement): Promise<void> {
216
+ this.inlineContainer = container;
217
+ this.displayMode = 'inline';
218
+ await this.createIframe();
219
+ this.showInline();
220
+ }
221
+
222
+ /**
223
+ * Show iframe inline (embedded in container).
224
+ */
225
+ showInline(): void {
226
+ if (!this.iframe) {
227
+ return;
228
+ }
229
+ this.displayMode = 'inline';
230
+ if (this.inlineContainer && this.iframe.parentElement !== this.inlineContainer) {
231
+ this.inlineContainer.appendChild(this.iframe);
232
+ }
233
+ this.applyIframeStyles();
234
+ this.setVisibility(true);
235
+ }
236
+
237
+ /**
238
+ * Show iframe as a full-screen modal.
239
+ */
240
+ showModal(): void {
241
+ if (!this.iframe) {
242
+ return;
243
+ }
244
+ this.displayMode = 'modal';
245
+ if (this.iframe.parentElement !== document.body) {
246
+ document.body.appendChild(this.iframe);
247
+ }
248
+ this.applyIframeStyles();
249
+ this.setVisibility(true);
250
+ }
251
+
252
+ /**
253
+ * Show iframe modal
254
+ */
255
+ show(): void {
256
+ this.showModal();
257
+ }
258
+
259
+ /**
260
+ * Hide iframe modal
261
+ */
262
+ hide(): void {
263
+ this.setVisibility(false);
264
+ }
265
+
266
+ isInline(): boolean {
267
+ return this.displayMode === 'inline';
268
+ }
269
+
270
+ private applyIframeStyles(): void {
271
+ if (!this.iframe) {
272
+ return;
273
+ }
274
+
275
+ if (this.displayMode === 'inline') {
276
+ this.iframe.style.cssText = `
277
+ position: relative;
278
+ width: 100%;
279
+ height: 100%;
280
+ border: none;
281
+ z-index: 1;
282
+ display: block;
283
+ background: transparent;
284
+ `;
285
+ return;
286
+ }
287
+
288
+ this.iframe.style.cssText = `
289
+ position: fixed;
290
+ top: 0;
291
+ left: 0;
292
+ width: 100%;
293
+ height: 100%;
294
+ border: none;
295
+ z-index: 999999;
296
+ display: block;
297
+ background: rgba(0, 0, 0, 0.5);
298
+ `;
299
+ }
300
+
301
+ private setVisibility(visible: boolean): void {
302
+ if (!this.iframe) {
303
+ return;
304
+ }
305
+ this.visible = visible;
306
+ this.iframe.style.opacity = visible ? '1' : '0';
307
+ this.iframe.style.pointerEvents = visible ? 'auto' : 'none';
308
+ this.iframe.style.visibility = visible ? 'visible' : 'hidden';
309
+ }
310
+
311
+ /**
312
+ * Send message to iframe and wait for response
313
+ */
314
+ async sendMessage<TRequest extends PostMessageRequest>(
315
+ request: TRequest
316
+ ): Promise<InferSuccessfulPostMessageResponse<TRequest>> {
317
+ /* Ensure the iframe has navigated to the wallet origin before we try to
318
+ postMessage to a strict targetOrigin. Otherwise the iframe can still be
319
+ about:blank (same-origin with the dapp) and postMessage will throw. */
320
+ if (this.readyPromise) {
321
+ await this.readyPromise;
322
+ } else {
323
+ await this.createIframe();
324
+ }
325
+
326
+ if (!this.iframe?.contentWindow) {
327
+ throw new Error('Iframe not initialized - call createIframe() first');
328
+ }
329
+
330
+ return new Promise<InferSuccessfulPostMessageResponse<TRequest>>((resolve, reject) => {
331
+ /* CONNECT, signing, and account-management requests require a human click and can take minutes.
332
+ Keep a longer timeout to avoid breaking "inline connect button" flows. */
333
+ const timeoutMs = SLOW_REQUEST_TYPES.has(request.type)
334
+ ? SLOW_REQUEST_TIMEOUT_MS
335
+ : FAST_REQUEST_TIMEOUT_MS;
336
+
337
+ const timeout = setTimeout(() => {
338
+ this.messageHandlers.delete(request.id);
339
+ reject(new Error('Request timeout - wallet did not respond'));
340
+ }, timeoutMs);
341
+
342
+ // Store handler for this request
343
+ this.messageHandlers.set(request.id, (response: PostMessageResponse) => {
344
+ clearTimeout(timeout);
345
+ this.messageHandlers.delete(request.id);
346
+
347
+ if (response.success) {
348
+ resolve(response as InferSuccessfulPostMessageResponse<TRequest>);
349
+ } else {
350
+ const error = new Error(response.error?.message || 'Unknown error');
351
+ (error as any).code = response.error?.code;
352
+ (error as any).data = response.error?.data;
353
+ reject(error);
354
+ }
355
+ });
356
+
357
+ // Send message to iframe
358
+ this.iframe!.contentWindow!.postMessage(request, this.iframeOrigin);
359
+ });
360
+ }
361
+
362
+ /**
363
+ * Handle incoming messages from iframe
364
+ */
365
+ private handleMessage(event: MessageEvent): void {
366
+ if (!this.isMessageFromIframe(event)) {
367
+ return; // Ignore messages from other origins
368
+ }
369
+
370
+ const data = event.data;
371
+
372
+ // Handle response to a specific request (has id)
373
+ if (data.id && this.messageHandlers.has(data.id)) {
374
+ const handler = this.messageHandlers.get(data.id);
375
+ if (handler) {
376
+ handler(data as PostMessageResponse);
377
+ }
378
+ return;
379
+ }
380
+
381
+ // Handle event broadcasts (type === 'event')
382
+ if (data.type === POST_MESSAGE_EVENT_TYPE) {
383
+ this.handleEvent(data as PostMessageEvent);
384
+ }
385
+ }
386
+
387
+ /**
388
+ * Handle event broadcasts from iframe
389
+ */
390
+ private handleEvent(data: PostMessageEvent): void {
391
+ // Forward to EmbeddedProvider via callback
392
+ if (this.onEvent) {
393
+ this.onEvent(data.event, data.data);
394
+ }
395
+ }
396
+
397
+ private isMessageFromIframe(event: MessageEvent): boolean {
398
+ if (event.origin !== this.iframeOrigin) {
399
+ return false;
400
+ }
401
+
402
+ const data = event.data as any;
403
+ if (!data || data.frameId !== this.frameId) {
404
+ return false;
405
+ }
406
+
407
+ /* Some browsers (notably Safari) can provide a null `event.source` for
408
+ cross-origin postMessage events. Frame id + origin is sufficient. */
409
+ if (!event.source) {
410
+ return true;
411
+ }
412
+
413
+ if (this.iframe?.contentWindow && event.source !== this.iframe.contentWindow) {
414
+ return false;
415
+ }
416
+
417
+ return true;
418
+ }
419
+
420
+ /**
421
+ * Destroy iframe and cleanup
422
+ */
423
+ destroy(): void {
424
+ if (this.iframe) {
425
+ this.iframe.remove();
426
+ this.iframe = null;
427
+ }
428
+
429
+ this.readyPromise = null;
430
+
431
+ if (this.messageListener) {
432
+ window.removeEventListener('message', this.messageListener);
433
+ this.messageListener = null;
434
+ }
435
+
436
+ this.messageHandlers.clear();
437
+ }
438
+ }
@@ -0,0 +1,86 @@
1
+ import {
2
+ AddressType,
3
+ type IThruChain,
4
+ type ThruSigningContext,
5
+ type ThruTransactionIntent,
6
+ } from "../../interfaces";
7
+ import { POST_MESSAGE_REQUEST_TYPES, createRequestId } from "../../protocol";
8
+ import type { EmbeddedProvider } from "../EmbeddedProvider";
9
+ import type { IframeManager } from "../IframeManager";
10
+
11
+ /**
12
+ * EmbeddedThruChain - postMessage-backed Thru chain adapter.
13
+ */
14
+ export class EmbeddedThruChain implements IThruChain {
15
+ private readonly iframeManager: IframeManager;
16
+ private readonly provider: EmbeddedProvider;
17
+
18
+ constructor(iframeManager: IframeManager, provider: EmbeddedProvider) {
19
+ this.iframeManager = iframeManager;
20
+ this.provider = provider;
21
+ }
22
+
23
+ get connected(): boolean {
24
+ return this.provider.isConnected();
25
+ }
26
+
27
+ async connect(): Promise<{ publicKey: string }> {
28
+ const result = await this.provider.connect();
29
+ const selectedAccount = result.selectedAccount;
30
+ const thruAccount =
31
+ selectedAccount?.accountType === AddressType.THRU
32
+ ? selectedAccount
33
+ : result.accounts.find((addr) => addr.accountType === AddressType.THRU);
34
+
35
+ if (!thruAccount) {
36
+ throw new Error("Thru address not found in connection result");
37
+ }
38
+
39
+ return { publicKey: thruAccount.address };
40
+ }
41
+
42
+ async disconnect(): Promise<void> {
43
+ await this.provider.disconnect();
44
+ }
45
+
46
+ async getSigningContext(): Promise<ThruSigningContext> {
47
+ if (!this.provider.isConnected()) {
48
+ throw new Error("Wallet not connected");
49
+ }
50
+
51
+ const response = await this.iframeManager.sendMessage({
52
+ id: createRequestId(),
53
+ type: POST_MESSAGE_REQUEST_TYPES.GET_SIGNING_CONTEXT,
54
+ origin: window.location.origin,
55
+ });
56
+
57
+ return response.result.signingContext;
58
+ }
59
+
60
+ async signTransaction(transaction: ThruTransactionIntent): Promise<string> {
61
+ if (!this.provider.isConnected()) {
62
+ throw new Error("Wallet not connected");
63
+ }
64
+
65
+ this.iframeManager.show();
66
+
67
+ try {
68
+ const response = await this.iframeManager.sendMessage({
69
+ id: createRequestId(),
70
+ type: POST_MESSAGE_REQUEST_TYPES.SIGN_TRANSACTION,
71
+ payload: {
72
+ walletAddress: transaction.walletAddress,
73
+ programAddress: transaction.programAddress,
74
+ instructionData: transaction.instructionData,
75
+ readWriteAddresses: transaction.readWriteAddresses,
76
+ readOnlyAddresses: transaction.readOnlyAddresses,
77
+ review: transaction.review,
78
+ },
79
+ origin: window.location.origin,
80
+ });
81
+ return response.result.signedTransaction;
82
+ } finally {
83
+ this.iframeManager.hide();
84
+ }
85
+ }
86
+ }
@@ -0,0 +1,17 @@
1
+ // Main exports
2
+ export { EmbeddedThruChain } from './chains/ThruChain';
3
+ export { EmbeddedProvider, type ConnectOptions, type EmbeddedProviderConfig } from './EmbeddedProvider';
4
+
5
+ // Type exports
6
+ export type {
7
+ ConnectResult, EmbeddedProviderEvent, PostMessageEvent, PostMessageRequest,
8
+ PostMessageResponse, RequestType, SignMessagePayload,
9
+ SignMessageResult,
10
+ SignTransactionPayload,
11
+ SignTransactionResult
12
+ } from './types/messages';
13
+
14
+ export { ErrorCode } from './types/messages';
15
+
16
+ // Re-export types from chain-interfaces for convenience
17
+ export type { IThruChain, WalletAccount } from '../interfaces';
@@ -0,0 +1,37 @@
1
+ export {
2
+ POST_MESSAGE_REQUEST_TYPES,
3
+ EMBEDDED_PROVIDER_EVENTS,
4
+ POST_MESSAGE_EVENT_TYPE,
5
+ IFRAME_READY_EVENT,
6
+ DEFAULT_IFRAME_URL,
7
+ createRequestId,
8
+ ErrorCode,
9
+ type RequestType,
10
+ type EmbeddedProviderEvent,
11
+ type PostMessageRequest,
12
+ type ConnectRequestMessage,
13
+ type DisconnectRequestMessage,
14
+ type SignMessageRequestMessage,
15
+ type SignTransactionRequestMessage,
16
+ type GetAccountsRequestMessage,
17
+ type GetSigningContextRequestMessage,
18
+ type ManageAccountsRequestMessage,
19
+ type SelectAccountRequestMessage,
20
+ type DisconnectResult,
21
+ type GetAccountsResult,
22
+ type GetSigningContextResult,
23
+ type ManageAccountsResult,
24
+ type SelectAccountPayload,
25
+ type SelectAccountResult,
26
+ type PostMessageResponse,
27
+ type SuccessfulPostMessageResponse,
28
+ type InferPostMessageResponse,
29
+ type InferSuccessfulPostMessageResponse,
30
+ type PostMessageEvent,
31
+ type ConnectRequestPayload,
32
+ type ConnectResult,
33
+ type SignMessagePayload,
34
+ type SignMessageResult,
35
+ type SignTransactionPayload,
36
+ type SignTransactionResult,
37
+ } from "../../protocol";
@@ -0,0 +1,31 @@
1
+ import { BrowserSDK } from "../BrowserSDK";
2
+ import type { WalletAccount } from "../interfaces";
3
+ import type { ManageAccountsResult } from "../protocol";
4
+ import { Thru } from "@thru/sdk/client";
5
+ import { createContext } from "react";
6
+
7
+ export interface ThruContextValue {
8
+ wallet: BrowserSDK | null;
9
+ isConnected: boolean;
10
+ accounts: WalletAccount[];
11
+ isConnecting: boolean;
12
+ error: Error | null;
13
+ thru: Thru | null;
14
+ selectedAccount: WalletAccount | null;
15
+ selectAccount: (account: WalletAccount) => Promise<void>;
16
+ manageAccounts: () => Promise<ManageAccountsResult>;
17
+ }
18
+
19
+ const defaultContextValue: ThruContextValue = {
20
+ wallet: null,
21
+ isConnected: false,
22
+ accounts: [],
23
+ isConnecting: false,
24
+ error: null,
25
+ thru: null,
26
+ selectedAccount: null,
27
+ selectAccount: async () => undefined,
28
+ manageAccounts: async () => ({ accounts: [], selectedAccount: null }),
29
+ };
30
+
31
+ export const ThruContext = createContext<ThruContextValue>(defaultContextValue);