@blazium/ton-connect-mobile 1.0.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.
@@ -0,0 +1,147 @@
1
+ "use strict";
2
+ /**
3
+ * Web platform adapter
4
+ * Handles deep linking and storage for web environments
5
+ */
6
+ Object.defineProperty(exports, "__esModule", { value: true });
7
+ exports.WebAdapter = void 0;
8
+ /**
9
+ * Web platform adapter implementation
10
+ * Uses browser APIs for deep linking and localStorage for storage
11
+ */
12
+ class WebAdapter {
13
+ constructor() {
14
+ this.urlListeners = [];
15
+ this.isListening = false;
16
+ // Set up URL listener
17
+ this.setupURLListener();
18
+ }
19
+ setupURLListener() {
20
+ // Listen for hash changes (web deep links)
21
+ if (typeof window !== 'undefined') {
22
+ window.addEventListener('hashchange', () => {
23
+ this.handleURLChange();
24
+ });
25
+ // Also check on popstate (browser back/forward)
26
+ window.addEventListener('popstate', () => {
27
+ this.handleURLChange();
28
+ });
29
+ this.isListening = true;
30
+ }
31
+ }
32
+ handleURLChange() {
33
+ if (typeof window === 'undefined')
34
+ return;
35
+ const url = window.location.href;
36
+ this.urlListeners.forEach((listener) => {
37
+ try {
38
+ listener(url);
39
+ }
40
+ catch (error) {
41
+ // Ignore errors in listeners
42
+ }
43
+ });
44
+ }
45
+ async openURL(url) {
46
+ try {
47
+ if (typeof window !== 'undefined') {
48
+ // For web, we can use window.open or window.location
49
+ // For deep links to mobile wallets, we'll use window.location
50
+ if (url.startsWith('tonconnect://')) {
51
+ // Try to open in new window/tab, fallback to current window
52
+ const opened = window.open(url, '_blank');
53
+ if (!opened) {
54
+ // Popup blocked, try current window
55
+ window.location.href = url;
56
+ }
57
+ return true;
58
+ }
59
+ else {
60
+ window.location.href = url;
61
+ return true;
62
+ }
63
+ }
64
+ return false;
65
+ }
66
+ catch (error) {
67
+ return false;
68
+ }
69
+ }
70
+ async getInitialURL() {
71
+ if (typeof window !== 'undefined') {
72
+ return window.location.href;
73
+ }
74
+ return null;
75
+ }
76
+ addURLListener(callback) {
77
+ this.urlListeners.push(callback);
78
+ // Immediately check current URL
79
+ if (typeof window !== 'undefined') {
80
+ try {
81
+ callback(window.location.href);
82
+ }
83
+ catch (error) {
84
+ // Ignore errors
85
+ }
86
+ }
87
+ // Return unsubscribe function
88
+ return () => {
89
+ const index = this.urlListeners.indexOf(callback);
90
+ if (index > -1) {
91
+ this.urlListeners.splice(index, 1);
92
+ }
93
+ };
94
+ }
95
+ async setItem(key, value) {
96
+ if (typeof window === 'undefined' || !window.localStorage) {
97
+ throw new Error('localStorage is not available');
98
+ }
99
+ try {
100
+ window.localStorage.setItem(key, value);
101
+ }
102
+ catch (error) {
103
+ throw new Error(`Failed to set storage item: ${error?.message || error}`);
104
+ }
105
+ }
106
+ async getItem(key) {
107
+ if (typeof window === 'undefined' || !window.localStorage) {
108
+ return null;
109
+ }
110
+ try {
111
+ return window.localStorage.getItem(key);
112
+ }
113
+ catch (error) {
114
+ return null;
115
+ }
116
+ }
117
+ async removeItem(key) {
118
+ if (typeof window === 'undefined' || !window.localStorage) {
119
+ return;
120
+ }
121
+ try {
122
+ window.localStorage.removeItem(key);
123
+ }
124
+ catch (error) {
125
+ // Ignore errors on remove
126
+ }
127
+ }
128
+ async randomBytes(length) {
129
+ // Use crypto.getRandomValues (available in modern browsers)
130
+ if (typeof window !== 'undefined' && window.crypto && window.crypto.getRandomValues) {
131
+ const bytes = new Uint8Array(length);
132
+ window.crypto.getRandomValues(bytes);
133
+ return bytes;
134
+ }
135
+ // Fallback: should not happen in modern browsers
136
+ throw new Error('Cryptographically secure random number generation not available. ' +
137
+ 'Please use a modern browser with crypto.getRandomValues support.');
138
+ }
139
+ /**
140
+ * Cleanup resources
141
+ */
142
+ destroy() {
143
+ this.urlListeners = [];
144
+ this.isListening = false;
145
+ }
146
+ }
147
+ exports.WebAdapter = WebAdapter;
@@ -0,0 +1,28 @@
1
+ /**
2
+ * Cryptographic utilities for TonConnect
3
+ * Uses tweetnacl for signature verification
4
+ */
5
+ import { ConnectionResponsePayload } from '../types';
6
+ /**
7
+ * Verify connection proof signature
8
+ * The proof is signed by the wallet to verify authenticity
9
+ */
10
+ export declare function verifyConnectionProof(response: ConnectionResponsePayload, manifestUrl: string): boolean;
11
+ /**
12
+ * Verify transaction signature
13
+ *
14
+ * WARNING: This function only performs basic format validation.
15
+ * Full signature verification requires parsing the BOC (Bag of Cells) and
16
+ * verifying the signature against the transaction hash, which requires
17
+ * TON library integration (@ton/core or @ton/crypto).
18
+ *
19
+ * For production use, transaction signatures should be verified server-side
20
+ * using proper TON libraries.
21
+ *
22
+ * @returns false - Always returns false to be safe until proper implementation
23
+ */
24
+ export declare function verifyTransactionSignature(boc: string, signature: string, publicKey: string): boolean;
25
+ /**
26
+ * Generate random session ID
27
+ */
28
+ export declare function generateSessionId(): string;
@@ -0,0 +1,183 @@
1
+ "use strict";
2
+ /**
3
+ * Cryptographic utilities for TonConnect
4
+ * Uses tweetnacl for signature verification
5
+ */
6
+ Object.defineProperty(exports, "__esModule", { value: true });
7
+ exports.verifyConnectionProof = verifyConnectionProof;
8
+ exports.verifyTransactionSignature = verifyTransactionSignature;
9
+ exports.generateSessionId = generateSessionId;
10
+ // eslint-disable-next-line @typescript-eslint/no-var-requires
11
+ const nacl = require('tweetnacl');
12
+ /**
13
+ * Decode base64 string to Uint8Array
14
+ */
15
+ function decodeBase64(base64) {
16
+ // Remove padding and convert URL-safe to standard base64
17
+ const cleanBase64 = base64.replace(/-/g, '+').replace(/_/g, '/');
18
+ const padded = cleanBase64 + '='.repeat((4 - (cleanBase64.length % 4)) % 4);
19
+ const chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/';
20
+ const bytes = [];
21
+ let buffer = 0;
22
+ let bitsCollected = 0;
23
+ for (let i = 0; i < padded.length; i++) {
24
+ const ch = padded[i];
25
+ if (ch === '=')
26
+ break;
27
+ const index = chars.indexOf(ch);
28
+ if (index === -1)
29
+ continue;
30
+ buffer = (buffer << 6) | index;
31
+ bitsCollected += 6;
32
+ if (bitsCollected >= 8) {
33
+ bitsCollected -= 8;
34
+ bytes.push((buffer >> bitsCollected) & 0xff);
35
+ buffer &= (1 << bitsCollected) - 1;
36
+ }
37
+ }
38
+ return new Uint8Array(bytes);
39
+ }
40
+ /**
41
+ * Get TextEncoder with fallback
42
+ */
43
+ function getTextEncoder() {
44
+ if (typeof TextEncoder !== 'undefined') {
45
+ return new TextEncoder();
46
+ }
47
+ // Fallback implementation for older React Native
48
+ throw new Error('TextEncoder is not available. Please use React Native 0.59+ or add a polyfill.');
49
+ }
50
+ /**
51
+ * Verify connection proof signature
52
+ * The proof is signed by the wallet to verify authenticity
53
+ */
54
+ function verifyConnectionProof(response, manifestUrl) {
55
+ // HIGH FIX: Log warning if proof is missing but allow for compatibility
56
+ if (!response.proof) {
57
+ console.warn('TON Connect: Connection proof missing - wallet may not support proof verification');
58
+ // Allow connection for compatibility, but log warning
59
+ return true;
60
+ }
61
+ try {
62
+ const { timestamp, domain, signature } = response.proof;
63
+ // Validate proof structure
64
+ if (typeof timestamp !== 'number' || !domain || typeof domain.lengthBytes !== 'number' || typeof domain.value !== 'string' || typeof signature !== 'string') {
65
+ return false;
66
+ }
67
+ // Build the message that was signed
68
+ // Format: <timestamp>.<domain_length>.<domain_value>.<address>.<publicKey>
69
+ const domainLength = domain.lengthBytes;
70
+ const message = `${timestamp}.${domainLength}.${domain.value}.${response.address}.${response.publicKey}`;
71
+ // Convert public key from hex to Uint8Array
72
+ const publicKeyBytes = hexToBytes(response.publicKey);
73
+ if (publicKeyBytes.length !== 32) {
74
+ return false;
75
+ }
76
+ // Convert signature from base64 to Uint8Array
77
+ const signatureBytes = decodeBase64(signature);
78
+ if (signatureBytes.length !== 64) {
79
+ return false;
80
+ }
81
+ // Verify signature using nacl
82
+ const encoder = getTextEncoder();
83
+ const messageBytes = encoder.encode(message);
84
+ return nacl.sign.detached.verify(messageBytes, signatureBytes, publicKeyBytes);
85
+ }
86
+ catch (error) {
87
+ // If verification fails, return false
88
+ console.error('TON Connect: Proof verification error:', error);
89
+ return false;
90
+ }
91
+ }
92
+ /**
93
+ * Verify transaction signature
94
+ *
95
+ * WARNING: This function only performs basic format validation.
96
+ * Full signature verification requires parsing the BOC (Bag of Cells) and
97
+ * verifying the signature against the transaction hash, which requires
98
+ * TON library integration (@ton/core or @ton/crypto).
99
+ *
100
+ * For production use, transaction signatures should be verified server-side
101
+ * using proper TON libraries.
102
+ *
103
+ * @returns false - Always returns false to be safe until proper implementation
104
+ */
105
+ function verifyTransactionSignature(boc, signature, publicKey) {
106
+ // CRITICAL FIX: This function does not actually verify signatures
107
+ // It only checks format. For security, we return false until proper implementation.
108
+ try {
109
+ // Basic format validation
110
+ if (!boc || typeof boc !== 'string' || boc.length === 0) {
111
+ return false;
112
+ }
113
+ if (!signature || typeof signature !== 'string' || signature.length === 0) {
114
+ return false;
115
+ }
116
+ if (!publicKey || typeof publicKey !== 'string' || publicKey.length === 0) {
117
+ return false;
118
+ }
119
+ // Convert public key from hex to Uint8Array
120
+ const publicKeyBytes = hexToBytes(publicKey);
121
+ if (publicKeyBytes.length !== 32) {
122
+ return false;
123
+ }
124
+ // Convert signature from base64 to Uint8Array
125
+ const signatureBytes = decodeBase64(signature);
126
+ if (signatureBytes.length !== 64) {
127
+ return false;
128
+ }
129
+ // Convert BOC from base64 to Uint8Array
130
+ const bocBytes = decodeBase64(boc);
131
+ if (bocBytes.length === 0) {
132
+ return false;
133
+ }
134
+ // CRITICAL: Return false - actual signature verification requires TON library
135
+ // TODO: Integrate @ton/core or @ton/crypto for proper BOC parsing and signature verification
136
+ console.warn('TON Connect: Transaction signature verification not fully implemented. Signature format is valid but not cryptographically verified. Verify server-side using @ton/core.');
137
+ return false; // Fail-safe: reject until properly implemented
138
+ }
139
+ catch (error) {
140
+ console.error('TON Connect: Transaction signature verification error:', error);
141
+ return false;
142
+ }
143
+ }
144
+ /**
145
+ * Convert hex string to Uint8Array
146
+ */
147
+ function hexToBytes(hex) {
148
+ // Remove 0x prefix if present
149
+ const cleanHex = hex.startsWith('0x') ? hex.slice(2) : hex;
150
+ // Handle odd-length hex strings
151
+ const paddedHex = cleanHex.length % 2 === 0 ? cleanHex : '0' + cleanHex;
152
+ const bytes = new Uint8Array(paddedHex.length / 2);
153
+ for (let i = 0; i < paddedHex.length; i += 2) {
154
+ bytes[i / 2] = parseInt(paddedHex.substr(i, 2), 16);
155
+ }
156
+ return bytes;
157
+ }
158
+ /**
159
+ * Generate cryptographically secure random bytes
160
+ */
161
+ function getSecureRandomBytes(length) {
162
+ const bytes = new Uint8Array(length);
163
+ // Try to use crypto.getRandomValues (available in React Native with polyfill)
164
+ // eslint-disable-next-line no-undef
165
+ if (typeof globalThis !== 'undefined' && globalThis.crypto && globalThis.crypto.getRandomValues) {
166
+ globalThis.crypto.getRandomValues(bytes);
167
+ return bytes;
168
+ }
169
+ // HIGH FIX: Throw error instead of using insecure Math.random()
170
+ throw new Error('Cryptographically secure random number generation not available. ' +
171
+ 'Please install react-native-get-random-values or use React Native 0.59+');
172
+ }
173
+ /**
174
+ * Generate random session ID
175
+ */
176
+ function generateSessionId() {
177
+ // HIGH FIX: Use secure random bytes
178
+ const bytes = getSecureRandomBytes(32);
179
+ // Convert to hex string
180
+ return Array.from(bytes)
181
+ .map((b) => b.toString(16).padStart(2, '0'))
182
+ .join('');
183
+ }
@@ -0,0 +1,5 @@
1
+ /**
2
+ * Core protocol exports
3
+ */
4
+ export * from './protocol';
5
+ export * from './crypto';
@@ -0,0 +1,21 @@
1
+ "use strict";
2
+ /**
3
+ * Core protocol exports
4
+ */
5
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
6
+ if (k2 === undefined) k2 = k;
7
+ var desc = Object.getOwnPropertyDescriptor(m, k);
8
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
9
+ desc = { enumerable: true, get: function() { return m[k]; } };
10
+ }
11
+ Object.defineProperty(o, k2, desc);
12
+ }) : (function(o, m, k, k2) {
13
+ if (k2 === undefined) k2 = k;
14
+ o[k2] = m[k];
15
+ }));
16
+ var __exportStar = (this && this.__exportStar) || function(m, exports) {
17
+ for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
18
+ };
19
+ Object.defineProperty(exports, "__esModule", { value: true });
20
+ __exportStar(require("./protocol"), exports);
21
+ __exportStar(require("./crypto"), exports);
@@ -0,0 +1,50 @@
1
+ /**
2
+ * Core TonConnect protocol implementation
3
+ * Pure TypeScript, no platform dependencies
4
+ */
5
+ import { ConnectionResponsePayload, TransactionResponsePayload, ErrorResponse, WalletInfo, SendTransactionRequest } from '../types';
6
+ /**
7
+ * Encode JSON to base64 URL-safe string
8
+ */
9
+ export declare function encodeBase64URL(data: unknown): string;
10
+ /**
11
+ * Decode base64 URL-safe string to JSON
12
+ */
13
+ export declare function decodeBase64URL<T>(encoded: string): T;
14
+ /**
15
+ * Build connection request URL
16
+ * Format: tonconnect://connect?<base64_encoded_payload>
17
+ */
18
+ export declare function buildConnectionRequest(manifestUrl: string, returnScheme: string): string;
19
+ /**
20
+ * Build transaction request URL
21
+ * Format: tonconnect://send-transaction?<base64_encoded_payload>
22
+ */
23
+ export declare function buildTransactionRequest(manifestUrl: string, request: SendTransactionRequest, returnScheme: string): string;
24
+ /**
25
+ * Parse callback URL
26
+ * Format: <scheme>://tonconnect?<base64_encoded_response>
27
+ */
28
+ export declare function parseCallbackURL(url: string, scheme: string): {
29
+ type: 'connect' | 'transaction' | 'error' | 'unknown';
30
+ data: ConnectionResponsePayload | TransactionResponsePayload | ErrorResponse | null;
31
+ };
32
+ /**
33
+ * Extract wallet info from connection response
34
+ */
35
+ export declare function extractWalletInfo(response: ConnectionResponsePayload): WalletInfo;
36
+ /**
37
+ * Validate connection response
38
+ */
39
+ export declare function validateConnectionResponse(response: ConnectionResponsePayload): boolean;
40
+ /**
41
+ * Validate transaction response
42
+ */
43
+ export declare function validateTransactionResponse(response: TransactionResponsePayload): boolean;
44
+ /**
45
+ * Validate transaction request
46
+ */
47
+ export declare function validateTransactionRequest(request: SendTransactionRequest): {
48
+ valid: boolean;
49
+ error?: string;
50
+ };