@blazium/ton-connect-mobile 1.1.1 → 1.1.3

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/src/index.ts CHANGED
@@ -22,11 +22,13 @@ import {
22
22
  validateConnectionResponse,
23
23
  validateTransactionRequest,
24
24
  validateTransactionResponse,
25
+ decodeBase64URL,
25
26
  } from './core/protocol';
26
27
  import type {
27
28
  ConnectionResponsePayload,
28
29
  TransactionResponsePayload,
29
30
  ErrorResponse,
31
+ ConnectionRequestPayload,
30
32
  } from './types';
31
33
  import { verifyConnectionProof, generateSessionId } from './core/crypto';
32
34
  import { ExpoAdapter } from './adapters/expo';
@@ -99,6 +101,11 @@ export class TonConnectMobile {
99
101
  reject: (error: Error) => void;
100
102
  timeout: number | null;
101
103
  } | null = null;
104
+ private signDataPromise: {
105
+ resolve: (response: { signature: string; timestamp: number }) => void;
106
+ reject: (error: Error) => void;
107
+ timeout: number | null;
108
+ } | null = null;
102
109
 
103
110
  constructor(config: TonConnectMobileConfig) {
104
111
  // Validate config
@@ -219,6 +226,41 @@ export class TonConnectMobile {
219
226
  const parsed = parseCallbackURL(url, this.config.scheme);
220
227
  console.log('[TON Connect] Parsed callback:', parsed.type, parsed.data ? 'has data' : 'no data');
221
228
 
229
+ // CRITICAL FIX: Check for sign data response first (before other handlers)
230
+ if (this.signDataPromise && !this.signDataPromise.timeout) {
231
+ // Sign data request is pending
232
+ if (parsed.type === 'error' && parsed.data) {
233
+ const errorData = parsed.data as ErrorResponse;
234
+ if (errorData?.error) {
235
+ const promise = this.signDataPromise;
236
+ this.signDataPromise = null;
237
+ if (errorData.error.code === 300) {
238
+ promise.reject(new UserRejectedError());
239
+ } else {
240
+ promise.reject(new TonConnectError(errorData.error.message || 'Sign data failed'));
241
+ }
242
+ return;
243
+ }
244
+ }
245
+
246
+ // Check for sign data response format
247
+ // Note: TON Connect protocol may return sign data in different format
248
+ // We check for signature field in the response
249
+ if (parsed.data && typeof parsed.data === 'object') {
250
+ const data = parsed.data as any;
251
+ if (data.signature && typeof data.signature === 'string') {
252
+ const promise = this.signDataPromise;
253
+ this.signDataPromise = null;
254
+ promise.resolve({
255
+ signature: data.signature,
256
+ timestamp: data.timestamp || Date.now(),
257
+ });
258
+ return;
259
+ }
260
+ }
261
+ }
262
+
263
+ // Handle connection responses
222
264
  if (parsed.type === 'connect' && parsed.data) {
223
265
  this.handleConnectionResponse(parsed.data as ConnectionResponsePayload);
224
266
  } else if (parsed.type === 'transaction' && parsed.data) {
@@ -349,11 +391,35 @@ export class TonConnectMobile {
349
391
  // Build connection request URL (use wallet's universal link)
350
392
  console.log('[TON Connect] Building connection request URL for wallet:', this.currentWallet.name);
351
393
  console.log('[TON Connect] Using universal link:', this.currentWallet.universalLink);
352
- const url = buildConnectionRequest(this.config.manifestUrl, this.config.scheme, this.currentWallet.universalLink);
353
- console.log('[TON Connect] Built URL:', url.substring(0, 100) + '...');
394
+ console.log('[TON Connect] Wallet return strategy:', this.currentWallet.preferredReturnStrategy || 'back');
395
+ console.log('[TON Connect] Wallet requires returnScheme:', this.currentWallet.requiresReturnScheme !== false);
396
+
397
+ const url = buildConnectionRequest(
398
+ this.config.manifestUrl,
399
+ this.config.scheme,
400
+ this.currentWallet.universalLink,
401
+ this.currentWallet.preferredReturnStrategy,
402
+ this.currentWallet.requiresReturnScheme
403
+ );
354
404
 
355
- // DEBUG: Log the URL being opened
356
- console.log('[TON Connect] Opening URL:', url);
405
+ // DEBUG: Decode and log the payload for debugging
406
+ try {
407
+ const urlParts = url.split('?');
408
+ if (urlParts.length > 1) {
409
+ const payload = urlParts[1];
410
+ // CRITICAL FIX: Handle URL encoding - payload might have additional encoding
411
+ const cleanPayload = decodeURIComponent(payload);
412
+ const decoded = decodeBase64URL<ConnectionRequestPayload>(cleanPayload);
413
+ console.log('[TON Connect] Connection request payload:', JSON.stringify(decoded, null, 2));
414
+ }
415
+ } catch (e: any) {
416
+ // Log decode errors for debugging but don't fail
417
+ console.log('[TON Connect] Could not decode payload for logging:', e?.message || e);
418
+ // This is just for logging, the actual URL is correct
419
+ }
420
+
421
+ console.log('[TON Connect] Built URL:', url.substring(0, 100) + '...');
422
+ console.log('[TON Connect] Full URL:', url);
357
423
  console.log('[TON Connect] Manifest URL:', this.config.manifestUrl);
358
424
  console.log('[TON Connect] Return scheme:', this.config.scheme);
359
425
  console.log('[TON Connect] Adapter type:', this.adapter.constructor.name);
@@ -437,7 +503,14 @@ export class TonConnectMobile {
437
503
  }
438
504
 
439
505
  // Build transaction request URL (use universal link for Android compatibility)
440
- const url = buildTransactionRequest(this.config.manifestUrl, request, this.config.scheme, this.currentWallet.universalLink);
506
+ const url = buildTransactionRequest(
507
+ this.config.manifestUrl,
508
+ request,
509
+ this.config.scheme,
510
+ this.currentWallet.universalLink,
511
+ this.currentWallet.preferredReturnStrategy,
512
+ this.currentWallet.requiresReturnScheme
513
+ );
441
514
 
442
515
  // Create promise for transaction
443
516
  return new Promise<{ boc: string; signature: string }>((resolve, reject) => {
@@ -490,6 +563,156 @@ export class TonConnectMobile {
490
563
  });
491
564
  }
492
565
 
566
+ /**
567
+ * Sign data (for authentication, etc.)
568
+ * Note: Not all wallets support signData. This is a TON Connect extension.
569
+ */
570
+ async signData(data: string | Uint8Array, version: string = '1.0'): Promise<{ signature: string; timestamp: number }> {
571
+ // Check if connected
572
+ if (!this.currentStatus.connected || !this.currentStatus.wallet) {
573
+ throw new TonConnectError('Not connected to wallet. Call connect() first.');
574
+ }
575
+
576
+ // Helper function to encode bytes to base64
577
+ const base64EncodeBytes = (bytes: Uint8Array): string => {
578
+ const chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/';
579
+ let result = '';
580
+ let i = 0;
581
+
582
+ while (i < bytes.length) {
583
+ const a = bytes[i++];
584
+ const b = i < bytes.length ? bytes[i++] : 0;
585
+ const c = i < bytes.length ? bytes[i++] : 0;
586
+
587
+ const bitmap = (a << 16) | (b << 8) | c;
588
+
589
+ result += chars.charAt((bitmap >> 18) & 63);
590
+ result += chars.charAt((bitmap >> 12) & 63);
591
+ result += i - 2 < bytes.length ? chars.charAt((bitmap >> 6) & 63) : '=';
592
+ result += i - 1 < bytes.length ? chars.charAt(bitmap & 63) : '=';
593
+ }
594
+
595
+ return result;
596
+ };
597
+
598
+ // Helper function to get TextEncoder
599
+ const getTextEncoder = (): { encode(input: string): Uint8Array } => {
600
+ // eslint-disable-next-line no-undef
601
+ if (typeof globalThis !== 'undefined' && (globalThis as any).TextEncoder) {
602
+ // eslint-disable-next-line no-undef
603
+ return new (globalThis as any).TextEncoder();
604
+ }
605
+ // Fallback: manual encoding
606
+ return {
607
+ encode(input: string): Uint8Array {
608
+ const bytes = new Uint8Array(input.length);
609
+ for (let i = 0; i < input.length; i++) {
610
+ bytes[i] = input.charCodeAt(i);
611
+ }
612
+ return bytes;
613
+ },
614
+ };
615
+ };
616
+
617
+ // Convert data to base64
618
+ let dataBase64: string;
619
+ if (typeof data === 'string') {
620
+ // Check if it's already base64
621
+ const base64Regex = /^[A-Za-z0-9+/]*={0,2}$/;
622
+ if (base64Regex.test(data) && data.length % 4 === 0) {
623
+ // Likely base64, use as-is
624
+ dataBase64 = data;
625
+ } else {
626
+ // Not base64, encode it
627
+ const encoder = getTextEncoder();
628
+ const bytes = encoder.encode(data);
629
+ dataBase64 = base64EncodeBytes(bytes);
630
+ }
631
+ } else {
632
+ // Uint8Array - convert to base64
633
+ dataBase64 = base64EncodeBytes(data);
634
+ }
635
+
636
+ // Build sign data request
637
+ const payload = {
638
+ manifestUrl: this.config.manifestUrl,
639
+ data: dataBase64,
640
+ version,
641
+ returnStrategy: this.currentWallet.preferredReturnStrategy || 'back',
642
+ returnScheme: this.currentWallet.requiresReturnScheme !== false ? this.config.scheme : undefined,
643
+ };
644
+
645
+ // Encode payload
646
+ const { encodeBase64URL } = require('./core/protocol');
647
+ const encoded = encodeBase64URL(payload);
648
+
649
+ // Build URL
650
+ const baseUrl = this.currentWallet.universalLink.endsWith('/ton-connect')
651
+ ? this.currentWallet.universalLink
652
+ : `${this.currentWallet.universalLink}/ton-connect`;
653
+ const url = `${baseUrl}/sign-data?${encoded}`;
654
+
655
+ // Open wallet app and wait for response
656
+ return new Promise<{ signature: string; timestamp: number }>((resolve, reject) => {
657
+ let timeout: number | null = null;
658
+ let resolved = false;
659
+
660
+ // CRITICAL FIX: Check if sign data is already in progress
661
+ if (this.signDataPromise) {
662
+ throw new TonConnectError('Sign data request already in progress');
663
+ }
664
+
665
+ // Create promise for sign data
666
+ const signDataPromise = {
667
+ resolve: (response: { signature: string; timestamp: number }) => {
668
+ if (timeout !== null) {
669
+ clearTimeout(timeout);
670
+ }
671
+ resolved = true;
672
+ if (this.signDataPromise === signDataPromise) {
673
+ this.signDataPromise = null;
674
+ }
675
+ resolve(response);
676
+ },
677
+ reject: (error: Error) => {
678
+ if (timeout !== null) {
679
+ clearTimeout(timeout);
680
+ }
681
+ resolved = true;
682
+ if (this.signDataPromise === signDataPromise) {
683
+ this.signDataPromise = null;
684
+ }
685
+ reject(error);
686
+ },
687
+ timeout: null as number | null,
688
+ };
689
+
690
+ // Set timeout
691
+ timeout = setTimeout(() => {
692
+ if (!resolved && this.signDataPromise === signDataPromise) {
693
+ this.signDataPromise = null;
694
+ signDataPromise.reject(new TonConnectError('Sign data request timed out'));
695
+ }
696
+ }, this.config.transactionTimeout) as unknown as number;
697
+
698
+ signDataPromise.timeout = timeout;
699
+
700
+ // Store promise for callback handling
701
+ // CRITICAL FIX: Don't mutate handleCallback method - use a separate tracking mechanism
702
+ this.signDataPromise = signDataPromise;
703
+
704
+ // Open URL
705
+ this.adapter.openURL(url, this.config.skipCanOpenURLCheck).then(() => {
706
+ // URL opened, wait for callback
707
+ // Callback will be handled by handleCallback method checking signDataPromise
708
+ }).catch((error: Error) => {
709
+ // Clear promise on error
710
+ this.signDataPromise = null;
711
+ signDataPromise.reject(new TonConnectError(`Failed to open wallet: ${error?.message || String(error)}`));
712
+ });
713
+ });
714
+ }
715
+
493
716
  /**
494
717
  * Disconnect from wallet
495
718
  */
@@ -688,6 +911,7 @@ export class TonConnectMobile {
688
911
  this.statusChangeCallbacks.clear();
689
912
  this.connectionPromise = null;
690
913
  this.transactionPromise = null;
914
+ this.signDataPromise = null;
691
915
  }
692
916
  }
693
917
 
@@ -0,0 +1,107 @@
1
+ /**
2
+ * TonConnectButton component
3
+ * Compatible with @tonconnect/ui-react TonConnectButton
4
+ */
5
+
6
+ import React, { useState } from 'react';
7
+ import { TouchableOpacity, Text, StyleSheet, ActivityIndicator, ViewStyle, TextStyle } from 'react-native';
8
+ import { useTonConnectUI, useTonWallet } from './index';
9
+
10
+ export interface TonConnectButtonProps {
11
+ /** Button text when disconnected */
12
+ text?: string;
13
+ /** Button text when connected */
14
+ connectedText?: string;
15
+ /** Custom styles */
16
+ style?: ViewStyle;
17
+ /** Custom text styles */
18
+ textStyle?: TextStyle;
19
+ /** Callback when button is pressed */
20
+ onPress?: () => void;
21
+ }
22
+
23
+ /**
24
+ * TonConnectButton - Button component for connecting/disconnecting wallet
25
+ * Compatible with @tonconnect/ui-react TonConnectButton
26
+ */
27
+ export function TonConnectButton({
28
+ text = 'Connect Wallet',
29
+ connectedText = 'Disconnect',
30
+ style,
31
+ textStyle,
32
+ onPress,
33
+ }: TonConnectButtonProps): JSX.Element {
34
+ const tonConnectUI = useTonConnectUI();
35
+ const wallet = useTonWallet();
36
+ const isConnected = wallet?.connected || false;
37
+ const [isLoading, setIsLoading] = useState(false);
38
+
39
+ const handlePress = async () => {
40
+ // CRITICAL FIX: Prevent multiple simultaneous presses
41
+ if (isLoading) {
42
+ return;
43
+ }
44
+
45
+ if (onPress) {
46
+ onPress();
47
+ return;
48
+ }
49
+
50
+ setIsLoading(true);
51
+ try {
52
+ if (isConnected) {
53
+ await tonConnectUI.disconnect();
54
+ } else {
55
+ // CRITICAL FIX: Only open modal, don't auto-connect
56
+ // The modal should handle wallet selection and connection
57
+ // This allows users to choose which wallet to connect
58
+ await tonConnectUI.openModal();
59
+ // Note: connectWallet() should be called by the modal/wallet selection UI
60
+ // Not automatically here, to allow wallet selection
61
+ }
62
+ } catch (error) {
63
+ // CRITICAL FIX: Handle errors gracefully
64
+ console.error('TonConnectButton error:', error);
65
+ // Error is already handled by the SDK/UI, just reset loading state
66
+ } finally {
67
+ setIsLoading(false);
68
+ }
69
+ };
70
+
71
+ return (
72
+ <TouchableOpacity
73
+ style={[styles.button, style, isLoading && styles.buttonDisabled]}
74
+ onPress={handlePress}
75
+ disabled={isLoading}
76
+ >
77
+ {isLoading ? (
78
+ <ActivityIndicator color="#ffffff" />
79
+ ) : (
80
+ <Text style={[styles.buttonText, textStyle]}>
81
+ {isConnected ? connectedText : text}
82
+ </Text>
83
+ )}
84
+ </TouchableOpacity>
85
+ );
86
+ }
87
+
88
+ const styles = StyleSheet.create({
89
+ button: {
90
+ backgroundColor: '#0088cc',
91
+ paddingHorizontal: 24,
92
+ paddingVertical: 12,
93
+ borderRadius: 8,
94
+ alignItems: 'center',
95
+ justifyContent: 'center',
96
+ minHeight: 44,
97
+ },
98
+ buttonDisabled: {
99
+ opacity: 0.6,
100
+ },
101
+ buttonText: {
102
+ color: '#ffffff',
103
+ fontSize: 16,
104
+ fontWeight: '600',
105
+ },
106
+ });
107
+
@@ -0,0 +1,290 @@
1
+ /**
2
+ * React integration layer for @tonconnect/ui-react compatibility
3
+ * Provides TonConnectUIProvider, hooks, and components compatible with @tonconnect/ui-react API
4
+ */
5
+
6
+ import React, { createContext, useContext, useState, useEffect, useCallback, ReactNode } from 'react';
7
+ import { TonConnectMobile, ConnectionStatus, WalletInfo, SendTransactionRequest } from '../index';
8
+ import type { TonConnectMobileConfig } from '../types';
9
+
10
+ /**
11
+ * Account information (compatible with @tonconnect/ui-react)
12
+ */
13
+ export interface Account {
14
+ address: string;
15
+ chain: number;
16
+ publicKey?: string;
17
+ }
18
+
19
+ /**
20
+ * Wallet state (compatible with @tonconnect/ui-react)
21
+ */
22
+ export interface WalletState {
23
+ account: Account | null;
24
+ wallet: WalletInfo | null;
25
+ connected: boolean;
26
+ }
27
+
28
+ /**
29
+ * Transaction response (compatible with @tonconnect/ui-react)
30
+ */
31
+ export interface TransactionResponse {
32
+ boc: string;
33
+ signature: string;
34
+ }
35
+
36
+ /**
37
+ * Sign data request
38
+ */
39
+ export interface SignDataRequest {
40
+ /** Data to sign (will be base64 encoded) */
41
+ data: string | Uint8Array;
42
+ /** Optional version */
43
+ version?: string;
44
+ }
45
+
46
+ /**
47
+ * Sign data response
48
+ */
49
+ export interface SignDataResponse {
50
+ signature: string;
51
+ timestamp: number;
52
+ }
53
+
54
+ /**
55
+ * TonConnect UI instance interface (compatible with @tonconnect/ui-react)
56
+ */
57
+ export interface TonConnectUI {
58
+ /** Open connection modal */
59
+ openModal: () => Promise<void>;
60
+ /** Close connection modal */
61
+ closeModal: () => void;
62
+ /** Connect to wallet */
63
+ connectWallet: () => Promise<void>;
64
+ /** Disconnect from wallet */
65
+ disconnect: () => Promise<void>;
66
+ /** Send transaction */
67
+ sendTransaction: (transaction: SendTransactionRequest) => Promise<TransactionResponse>;
68
+ /** Sign data */
69
+ signData: (request: SignDataRequest) => Promise<SignDataResponse>;
70
+ /** Current wallet state */
71
+ wallet: WalletState | null;
72
+ /** Modal open state */
73
+ modalState: {
74
+ open: boolean;
75
+ };
76
+ /** UI kit version */
77
+ uiVersion: string;
78
+ }
79
+
80
+ /**
81
+ * Context value
82
+ */
83
+ interface TonConnectUIContextValue {
84
+ tonConnectUI: TonConnectUI;
85
+ sdk: TonConnectMobile;
86
+ }
87
+
88
+ const TonConnectUIContext = createContext<TonConnectUIContextValue | null>(null);
89
+
90
+ /**
91
+ * TonConnectUIProvider props
92
+ */
93
+ export interface TonConnectUIProviderProps {
94
+ /** SDK configuration */
95
+ config: TonConnectMobileConfig;
96
+ /** Children */
97
+ children: ReactNode;
98
+ /** Optional SDK instance (for testing or custom instances) */
99
+ sdkInstance?: TonConnectMobile;
100
+ }
101
+
102
+ /**
103
+ * TonConnectUIProvider - React context provider for TON Connect
104
+ * Compatible with @tonconnect/ui-react API
105
+ */
106
+ export function TonConnectUIProvider({
107
+ config,
108
+ children,
109
+ sdkInstance,
110
+ }: TonConnectUIProviderProps): JSX.Element {
111
+ const [sdk] = useState<TonConnectMobile>(() => sdkInstance || new TonConnectMobile(config));
112
+ const [walletState, setWalletState] = useState<WalletState | null>(null);
113
+ const [modalOpen, setModalOpen] = useState(false);
114
+ const [isConnecting, setIsConnecting] = useState(false);
115
+
116
+ // Update wallet state from SDK status
117
+ const updateWalletState = useCallback((status: ConnectionStatus) => {
118
+ if (status.connected && status.wallet) {
119
+ setWalletState({
120
+ account: {
121
+ address: status.wallet.address,
122
+ chain: -239, // TON mainnet chain ID
123
+ publicKey: status.wallet.publicKey,
124
+ },
125
+ wallet: status.wallet,
126
+ connected: true,
127
+ });
128
+ } else {
129
+ setWalletState({
130
+ account: null,
131
+ wallet: null,
132
+ connected: false,
133
+ });
134
+ }
135
+ }, []);
136
+
137
+ // Subscribe to SDK status changes
138
+ useEffect(() => {
139
+ // Set initial state
140
+ const initialStatus = sdk.getStatus();
141
+ updateWalletState(initialStatus);
142
+
143
+ // Subscribe to changes
144
+ const unsubscribe = sdk.onStatusChange((status) => {
145
+ updateWalletState(status);
146
+ // Close modal when connected
147
+ if (status.connected) {
148
+ setModalOpen(false);
149
+ setIsConnecting(false);
150
+ }
151
+ });
152
+
153
+ return () => {
154
+ unsubscribe();
155
+ // CRITICAL FIX: Cleanup SDK on unmount to prevent memory leaks
156
+ // Note: SDK has its own cleanup via destroy(), but we don't call it here
157
+ // to allow SDK to persist across component remounts (e.g., navigation)
158
+ };
159
+ }, [sdk, updateWalletState]);
160
+
161
+ // Open modal
162
+ const openModal = useCallback(async () => {
163
+ setModalOpen(true);
164
+ }, []);
165
+
166
+ // Close modal
167
+ const closeModal = useCallback(() => {
168
+ setModalOpen(false);
169
+ setIsConnecting(false);
170
+ }, []);
171
+
172
+ // Connect wallet
173
+ const connectWallet = useCallback(async () => {
174
+ // CRITICAL FIX: Use functional update to avoid race condition
175
+ setIsConnecting((prev) => {
176
+ if (prev) {
177
+ // Already connecting, return early
178
+ return prev;
179
+ }
180
+ return true;
181
+ });
182
+
183
+ // Wait for connection
184
+ try {
185
+ await sdk.connect();
186
+ // Status update will be handled by the subscription
187
+ } catch (error) {
188
+ setIsConnecting(false);
189
+ throw error;
190
+ }
191
+ }, [sdk]);
192
+
193
+ // Disconnect
194
+ const disconnect = useCallback(async () => {
195
+ await sdk.disconnect();
196
+ setModalOpen(false);
197
+ }, [sdk]);
198
+
199
+ // Send transaction
200
+ const sendTransaction = useCallback(
201
+ async (transaction: SendTransactionRequest): Promise<TransactionResponse> => {
202
+ const response = await sdk.sendTransaction(transaction);
203
+ return {
204
+ boc: response.boc,
205
+ signature: response.signature,
206
+ };
207
+ },
208
+ [sdk]
209
+ );
210
+
211
+ // Sign data
212
+ const signData = useCallback(
213
+ async (request: SignDataRequest): Promise<SignDataResponse> => {
214
+ const response = await sdk.signData(request.data, request.version);
215
+ return {
216
+ signature: response.signature,
217
+ timestamp: response.timestamp,
218
+ };
219
+ },
220
+ [sdk]
221
+ );
222
+
223
+ // Create TonConnectUI instance
224
+ const tonConnectUI: TonConnectUI = {
225
+ openModal,
226
+ closeModal,
227
+ connectWallet,
228
+ disconnect,
229
+ sendTransaction,
230
+ signData,
231
+ wallet: walletState,
232
+ modalState: {
233
+ open: modalOpen,
234
+ },
235
+ uiVersion: '1.0.0',
236
+ };
237
+
238
+ const contextValue: TonConnectUIContextValue = {
239
+ tonConnectUI,
240
+ sdk,
241
+ };
242
+
243
+ return <TonConnectUIContext.Provider value={contextValue}>{children}</TonConnectUIContext.Provider>;
244
+ }
245
+
246
+ /**
247
+ * Hook to access TonConnectUI instance
248
+ * Compatible with @tonconnect/ui-react useTonConnectUI hook
249
+ */
250
+ export function useTonConnectUI(): TonConnectUI {
251
+ const context = useContext(TonConnectUIContext);
252
+ if (!context) {
253
+ throw new Error('useTonConnectUI must be used within TonConnectUIProvider');
254
+ }
255
+ return context.tonConnectUI;
256
+ }
257
+
258
+ /**
259
+ * Hook to access wallet state
260
+ * Compatible with @tonconnect/ui-react useTonWallet hook
261
+ */
262
+ export function useTonWallet(): WalletState | null {
263
+ const tonConnectUI = useTonConnectUI();
264
+ return tonConnectUI.wallet;
265
+ }
266
+
267
+ /**
268
+ * Hook to access modal state
269
+ * Compatible with @tonconnect/ui-react useTonConnectModal hook
270
+ */
271
+ export function useTonConnectModal(): { open: boolean; close: () => void; openModal: () => Promise<void> } {
272
+ const tonConnectUI = useTonConnectUI();
273
+ return {
274
+ open: tonConnectUI.modalState.open,
275
+ close: tonConnectUI.closeModal,
276
+ openModal: tonConnectUI.openModal,
277
+ };
278
+ }
279
+
280
+ /**
281
+ * Hook to access SDK instance (for advanced usage)
282
+ */
283
+ export function useTonConnectSDK(): TonConnectMobile {
284
+ const context = useContext(TonConnectUIContext);
285
+ if (!context) {
286
+ throw new Error('useTonConnectSDK must be used within TonConnectUIProvider');
287
+ }
288
+ return context.sdk;
289
+ }
290
+
@@ -0,0 +1,24 @@
1
+ /**
2
+ * React integration exports
3
+ * Re-export all React components and hooks
4
+ */
5
+
6
+ export {
7
+ TonConnectUIProvider,
8
+ useTonConnectUI,
9
+ useTonWallet,
10
+ useTonConnectModal,
11
+ useTonConnectSDK,
12
+ } from './TonConnectUIProvider';
13
+ export type {
14
+ TonConnectUIProviderProps,
15
+ TonConnectUI,
16
+ WalletState,
17
+ Account,
18
+ TransactionResponse,
19
+ SignDataRequest,
20
+ SignDataResponse,
21
+ } from './TonConnectUIProvider';
22
+ export { TonConnectButton } from './TonConnectButton';
23
+ export type { TonConnectButtonProps } from './TonConnectButton';
24
+