@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.
- package/LICENSE +22 -0
- package/README.md +271 -0
- package/dist/adapters/expo.d.ts +25 -0
- package/dist/adapters/expo.js +145 -0
- package/dist/adapters/index.d.ts +6 -0
- package/dist/adapters/index.js +12 -0
- package/dist/adapters/react-native.d.ts +25 -0
- package/dist/adapters/react-native.js +133 -0
- package/dist/adapters/web.d.ts +27 -0
- package/dist/adapters/web.js +147 -0
- package/dist/core/crypto.d.ts +28 -0
- package/dist/core/crypto.js +183 -0
- package/dist/core/index.d.ts +5 -0
- package/dist/core/index.js +21 -0
- package/dist/core/protocol.d.ts +50 -0
- package/dist/core/protocol.js +260 -0
- package/dist/index.d.ts +112 -0
- package/dist/index.js +502 -0
- package/dist/types/index.d.ts +192 -0
- package/dist/types/index.js +6 -0
- package/package.json +57 -0
- package/src/adapters/expo.ts +160 -0
- package/src/adapters/index.ts +8 -0
- package/src/adapters/react-native.ts +148 -0
- package/src/adapters/web.ts +176 -0
- package/src/core/crypto.ts +238 -0
- package/src/core/index.ts +7 -0
- package/src/core/protocol.ts +330 -0
- package/src/index.d.ts +19 -0
- package/src/index.ts +578 -0
- package/src/types/index.ts +206 -0
package/dist/index.js
ADDED
|
@@ -0,0 +1,502 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* TON Connect Mobile SDK
|
|
4
|
+
* Production-ready implementation for React Native and Expo
|
|
5
|
+
*/
|
|
6
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
7
|
+
if (k2 === undefined) k2 = k;
|
|
8
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
9
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
10
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
11
|
+
}
|
|
12
|
+
Object.defineProperty(o, k2, desc);
|
|
13
|
+
}) : (function(o, m, k, k2) {
|
|
14
|
+
if (k2 === undefined) k2 = k;
|
|
15
|
+
o[k2] = m[k];
|
|
16
|
+
}));
|
|
17
|
+
var __exportStar = (this && this.__exportStar) || function(m, exports) {
|
|
18
|
+
for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
|
|
19
|
+
};
|
|
20
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
21
|
+
exports.TonConnectMobile = exports.TransactionInProgressError = exports.ConnectionInProgressError = exports.UserRejectedError = exports.TransactionTimeoutError = exports.ConnectionTimeoutError = exports.TonConnectError = void 0;
|
|
22
|
+
const protocol_1 = require("./core/protocol");
|
|
23
|
+
const crypto_1 = require("./core/crypto");
|
|
24
|
+
const expo_1 = require("./adapters/expo");
|
|
25
|
+
const react_native_1 = require("./adapters/react-native");
|
|
26
|
+
const web_1 = require("./adapters/web");
|
|
27
|
+
/**
|
|
28
|
+
* Custom error classes
|
|
29
|
+
*/
|
|
30
|
+
class TonConnectError extends Error {
|
|
31
|
+
constructor(message, code) {
|
|
32
|
+
super(message);
|
|
33
|
+
this.code = code;
|
|
34
|
+
this.name = 'TonConnectError';
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
exports.TonConnectError = TonConnectError;
|
|
38
|
+
class ConnectionTimeoutError extends TonConnectError {
|
|
39
|
+
constructor() {
|
|
40
|
+
super('Connection request timed out', 'CONNECTION_TIMEOUT');
|
|
41
|
+
this.name = 'ConnectionTimeoutError';
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
exports.ConnectionTimeoutError = ConnectionTimeoutError;
|
|
45
|
+
class TransactionTimeoutError extends TonConnectError {
|
|
46
|
+
constructor() {
|
|
47
|
+
super('Transaction request timed out', 'TRANSACTION_TIMEOUT');
|
|
48
|
+
this.name = 'TransactionTimeoutError';
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
exports.TransactionTimeoutError = TransactionTimeoutError;
|
|
52
|
+
class UserRejectedError extends TonConnectError {
|
|
53
|
+
constructor() {
|
|
54
|
+
super('User rejected the request', 'USER_REJECTED');
|
|
55
|
+
this.name = 'UserRejectedError';
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
exports.UserRejectedError = UserRejectedError;
|
|
59
|
+
class ConnectionInProgressError extends TonConnectError {
|
|
60
|
+
constructor() {
|
|
61
|
+
super('Connection request already in progress', 'CONNECTION_IN_PROGRESS');
|
|
62
|
+
this.name = 'ConnectionInProgressError';
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
exports.ConnectionInProgressError = ConnectionInProgressError;
|
|
66
|
+
class TransactionInProgressError extends TonConnectError {
|
|
67
|
+
constructor() {
|
|
68
|
+
super('Transaction request already in progress', 'TRANSACTION_IN_PROGRESS');
|
|
69
|
+
this.name = 'TransactionInProgressError';
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
exports.TransactionInProgressError = TransactionInProgressError;
|
|
73
|
+
/**
|
|
74
|
+
* Main TON Connect Mobile SDK class
|
|
75
|
+
*/
|
|
76
|
+
class TonConnectMobile {
|
|
77
|
+
constructor(config) {
|
|
78
|
+
this.statusChangeCallbacks = new Set();
|
|
79
|
+
this.currentStatus = { connected: false, wallet: null };
|
|
80
|
+
this.urlUnsubscribe = null;
|
|
81
|
+
this.connectionPromise = null;
|
|
82
|
+
this.transactionPromise = null;
|
|
83
|
+
// Validate config
|
|
84
|
+
if (!config.manifestUrl) {
|
|
85
|
+
throw new TonConnectError('manifestUrl is required');
|
|
86
|
+
}
|
|
87
|
+
if (!config.scheme) {
|
|
88
|
+
throw new TonConnectError('scheme is required');
|
|
89
|
+
}
|
|
90
|
+
this.config = {
|
|
91
|
+
storageKeyPrefix: 'tonconnect_',
|
|
92
|
+
connectionTimeout: 300000, // 5 minutes
|
|
93
|
+
transactionTimeout: 300000, // 5 minutes
|
|
94
|
+
...config,
|
|
95
|
+
};
|
|
96
|
+
// Initialize platform adapter
|
|
97
|
+
this.adapter = this.createAdapter();
|
|
98
|
+
// Set up URL listener
|
|
99
|
+
this.setupURLListener();
|
|
100
|
+
// Load persisted session
|
|
101
|
+
this.loadSession();
|
|
102
|
+
}
|
|
103
|
+
/**
|
|
104
|
+
* Create platform adapter based on available modules
|
|
105
|
+
*/
|
|
106
|
+
createAdapter() {
|
|
107
|
+
// Check if we're in a web environment
|
|
108
|
+
// eslint-disable-next-line no-undef
|
|
109
|
+
if (typeof globalThis !== 'undefined' && globalThis.window && globalThis.document) {
|
|
110
|
+
// Web platform
|
|
111
|
+
return new web_1.WebAdapter();
|
|
112
|
+
}
|
|
113
|
+
// Try to detect Expo environment
|
|
114
|
+
try {
|
|
115
|
+
// Check if expo-linking is available
|
|
116
|
+
if (typeof require !== 'undefined') {
|
|
117
|
+
const expoLinking = require('expo-linking');
|
|
118
|
+
if (expoLinking) {
|
|
119
|
+
return new expo_1.ExpoAdapter();
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
catch {
|
|
124
|
+
// expo-linking not available, continue to React Native adapter
|
|
125
|
+
}
|
|
126
|
+
// Fall back to React Native adapter
|
|
127
|
+
// This will work for both React Native CLI and Expo (since Expo also has react-native)
|
|
128
|
+
return new react_native_1.ReactNativeAdapter();
|
|
129
|
+
}
|
|
130
|
+
/**
|
|
131
|
+
* Set up URL listener for wallet callbacks
|
|
132
|
+
*/
|
|
133
|
+
setupURLListener() {
|
|
134
|
+
this.urlUnsubscribe = this.adapter.addURLListener((url) => {
|
|
135
|
+
this.handleCallback(url);
|
|
136
|
+
});
|
|
137
|
+
// Also check initial URL (when app was opened via deep link)
|
|
138
|
+
this.adapter.getInitialURL().then((url) => {
|
|
139
|
+
if (url) {
|
|
140
|
+
this.handleCallback(url);
|
|
141
|
+
}
|
|
142
|
+
});
|
|
143
|
+
}
|
|
144
|
+
/**
|
|
145
|
+
* Handle callback from wallet
|
|
146
|
+
*/
|
|
147
|
+
handleCallback(url) {
|
|
148
|
+
const parsed = (0, protocol_1.parseCallbackURL)(url, this.config.scheme);
|
|
149
|
+
if (parsed.type === 'connect' && parsed.data) {
|
|
150
|
+
this.handleConnectionResponse(parsed.data);
|
|
151
|
+
}
|
|
152
|
+
else if (parsed.type === 'transaction' && parsed.data) {
|
|
153
|
+
this.handleTransactionResponse(parsed.data);
|
|
154
|
+
}
|
|
155
|
+
else if (parsed.type === 'error' && parsed.data) {
|
|
156
|
+
const errorData = parsed.data;
|
|
157
|
+
if (errorData?.error) {
|
|
158
|
+
if (errorData.error.code === 300 || errorData.error.message?.toLowerCase().includes('reject')) {
|
|
159
|
+
this.rejectWithError(new UserRejectedError());
|
|
160
|
+
}
|
|
161
|
+
else {
|
|
162
|
+
this.rejectWithError(new TonConnectError(errorData.error.message || 'Unknown error', String(errorData.error.code)));
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
/**
|
|
168
|
+
* Handle connection response from wallet
|
|
169
|
+
*/
|
|
170
|
+
handleConnectionResponse(response) {
|
|
171
|
+
if (!(0, protocol_1.validateConnectionResponse)(response)) {
|
|
172
|
+
this.rejectWithError(new TonConnectError('Invalid connection response'));
|
|
173
|
+
return;
|
|
174
|
+
}
|
|
175
|
+
// Verify proof if present
|
|
176
|
+
if (response.proof) {
|
|
177
|
+
const isValid = (0, crypto_1.verifyConnectionProof)(response, this.config.manifestUrl);
|
|
178
|
+
if (!isValid) {
|
|
179
|
+
this.rejectWithError(new TonConnectError('Connection proof verification failed'));
|
|
180
|
+
return;
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
else {
|
|
184
|
+
// Log warning if proof is missing (security consideration)
|
|
185
|
+
console.warn('TON Connect: Connection proof missing - wallet may not support proof verification');
|
|
186
|
+
}
|
|
187
|
+
const wallet = (0, protocol_1.extractWalletInfo)(response);
|
|
188
|
+
// Validate session ID before saving
|
|
189
|
+
if (!this.validateSessionId(response.session)) {
|
|
190
|
+
this.rejectWithError(new TonConnectError('Invalid session ID format'));
|
|
191
|
+
return;
|
|
192
|
+
}
|
|
193
|
+
// Save session
|
|
194
|
+
this.saveSession(response.session, wallet).catch((error) => {
|
|
195
|
+
console.error('TON Connect: Failed to save session:', error);
|
|
196
|
+
// Continue anyway - connection is still valid
|
|
197
|
+
});
|
|
198
|
+
// Update status
|
|
199
|
+
this.currentStatus = { connected: true, wallet };
|
|
200
|
+
this.notifyStatusChange();
|
|
201
|
+
// Resolve connection promise
|
|
202
|
+
if (this.connectionPromise) {
|
|
203
|
+
if (this.connectionPromise.timeout !== null) {
|
|
204
|
+
clearTimeout(this.connectionPromise.timeout);
|
|
205
|
+
}
|
|
206
|
+
this.connectionPromise.resolve(wallet);
|
|
207
|
+
this.connectionPromise = null;
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
/**
|
|
211
|
+
* Handle transaction response from wallet
|
|
212
|
+
*/
|
|
213
|
+
handleTransactionResponse(response) {
|
|
214
|
+
if (!(0, protocol_1.validateTransactionResponse)(response)) {
|
|
215
|
+
this.rejectWithError(new TonConnectError('Invalid transaction response'));
|
|
216
|
+
return;
|
|
217
|
+
}
|
|
218
|
+
// Resolve transaction promise
|
|
219
|
+
if (this.transactionPromise) {
|
|
220
|
+
if (this.transactionPromise.timeout !== null) {
|
|
221
|
+
clearTimeout(this.transactionPromise.timeout);
|
|
222
|
+
}
|
|
223
|
+
this.transactionPromise.resolve({
|
|
224
|
+
boc: response.boc,
|
|
225
|
+
signature: response.signature,
|
|
226
|
+
});
|
|
227
|
+
this.transactionPromise = null;
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
/**
|
|
231
|
+
* Reject current promise with error
|
|
232
|
+
*/
|
|
233
|
+
rejectWithError(error) {
|
|
234
|
+
if (this.connectionPromise) {
|
|
235
|
+
if (this.connectionPromise.timeout !== null) {
|
|
236
|
+
clearTimeout(this.connectionPromise.timeout);
|
|
237
|
+
}
|
|
238
|
+
this.connectionPromise.reject(error);
|
|
239
|
+
this.connectionPromise = null;
|
|
240
|
+
}
|
|
241
|
+
if (this.transactionPromise) {
|
|
242
|
+
if (this.transactionPromise.timeout !== null) {
|
|
243
|
+
clearTimeout(this.transactionPromise.timeout);
|
|
244
|
+
}
|
|
245
|
+
this.transactionPromise.reject(error);
|
|
246
|
+
this.transactionPromise = null;
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
/**
|
|
250
|
+
* Connect to wallet
|
|
251
|
+
*/
|
|
252
|
+
async connect() {
|
|
253
|
+
// If already connected, return current wallet
|
|
254
|
+
if (this.currentStatus.connected && this.currentStatus.wallet) {
|
|
255
|
+
return this.currentStatus.wallet;
|
|
256
|
+
}
|
|
257
|
+
// CRITICAL FIX: Check if connection is already in progress
|
|
258
|
+
if (this.connectionPromise) {
|
|
259
|
+
throw new ConnectionInProgressError();
|
|
260
|
+
}
|
|
261
|
+
// Build connection request URL
|
|
262
|
+
const url = (0, protocol_1.buildConnectionRequest)(this.config.manifestUrl, this.config.scheme);
|
|
263
|
+
// Create promise for connection
|
|
264
|
+
return new Promise((resolve, reject) => {
|
|
265
|
+
let timeout = null;
|
|
266
|
+
this.connectionPromise = {
|
|
267
|
+
resolve: (wallet) => {
|
|
268
|
+
if (timeout !== null) {
|
|
269
|
+
clearTimeout(timeout);
|
|
270
|
+
}
|
|
271
|
+
this.connectionPromise = null;
|
|
272
|
+
resolve(wallet);
|
|
273
|
+
},
|
|
274
|
+
reject: (error) => {
|
|
275
|
+
if (timeout !== null) {
|
|
276
|
+
clearTimeout(timeout);
|
|
277
|
+
}
|
|
278
|
+
this.connectionPromise = null;
|
|
279
|
+
reject(error);
|
|
280
|
+
},
|
|
281
|
+
timeout: null,
|
|
282
|
+
};
|
|
283
|
+
// Set timeout
|
|
284
|
+
timeout = setTimeout(() => {
|
|
285
|
+
if (this.connectionPromise) {
|
|
286
|
+
this.connectionPromise.reject(new ConnectionTimeoutError());
|
|
287
|
+
}
|
|
288
|
+
}, this.config.connectionTimeout);
|
|
289
|
+
this.connectionPromise.timeout = timeout;
|
|
290
|
+
// Open wallet app
|
|
291
|
+
this.adapter.openURL(url).catch((error) => {
|
|
292
|
+
if (this.connectionPromise) {
|
|
293
|
+
this.connectionPromise.reject(new TonConnectError(`Failed to open wallet: ${error?.message || String(error)}`));
|
|
294
|
+
}
|
|
295
|
+
});
|
|
296
|
+
});
|
|
297
|
+
}
|
|
298
|
+
/**
|
|
299
|
+
* Send transaction
|
|
300
|
+
*/
|
|
301
|
+
async sendTransaction(request) {
|
|
302
|
+
// Validate request
|
|
303
|
+
const validation = (0, protocol_1.validateTransactionRequest)(request);
|
|
304
|
+
if (!validation.valid) {
|
|
305
|
+
throw new TonConnectError(validation.error || 'Invalid transaction request');
|
|
306
|
+
}
|
|
307
|
+
// Check if connected
|
|
308
|
+
if (!this.currentStatus.connected || !this.currentStatus.wallet) {
|
|
309
|
+
throw new TonConnectError('Not connected to wallet. Call connect() first.');
|
|
310
|
+
}
|
|
311
|
+
// CRITICAL FIX: Check if transaction is already in progress
|
|
312
|
+
if (this.transactionPromise) {
|
|
313
|
+
throw new TransactionInProgressError();
|
|
314
|
+
}
|
|
315
|
+
// Build transaction request URL
|
|
316
|
+
const url = (0, protocol_1.buildTransactionRequest)(this.config.manifestUrl, request, this.config.scheme);
|
|
317
|
+
// Create promise for transaction
|
|
318
|
+
return new Promise((resolve, reject) => {
|
|
319
|
+
let timeout = null;
|
|
320
|
+
this.transactionPromise = {
|
|
321
|
+
resolve: (response) => {
|
|
322
|
+
if (timeout !== null) {
|
|
323
|
+
clearTimeout(timeout);
|
|
324
|
+
}
|
|
325
|
+
this.transactionPromise = null;
|
|
326
|
+
resolve(response);
|
|
327
|
+
},
|
|
328
|
+
reject: (error) => {
|
|
329
|
+
if (timeout !== null) {
|
|
330
|
+
clearTimeout(timeout);
|
|
331
|
+
}
|
|
332
|
+
this.transactionPromise = null;
|
|
333
|
+
reject(error);
|
|
334
|
+
},
|
|
335
|
+
timeout: null,
|
|
336
|
+
};
|
|
337
|
+
// Set timeout
|
|
338
|
+
timeout = setTimeout(() => {
|
|
339
|
+
if (this.transactionPromise) {
|
|
340
|
+
this.transactionPromise.reject(new TransactionTimeoutError());
|
|
341
|
+
}
|
|
342
|
+
}, this.config.transactionTimeout);
|
|
343
|
+
this.transactionPromise.timeout = timeout;
|
|
344
|
+
// Open wallet app
|
|
345
|
+
this.adapter.openURL(url).catch((error) => {
|
|
346
|
+
if (this.transactionPromise) {
|
|
347
|
+
this.transactionPromise.reject(new TonConnectError(`Failed to open wallet: ${error?.message || String(error)}`));
|
|
348
|
+
}
|
|
349
|
+
});
|
|
350
|
+
});
|
|
351
|
+
}
|
|
352
|
+
/**
|
|
353
|
+
* Disconnect from wallet
|
|
354
|
+
*/
|
|
355
|
+
async disconnect() {
|
|
356
|
+
// Clear session
|
|
357
|
+
await this.clearSession();
|
|
358
|
+
// Update status
|
|
359
|
+
this.currentStatus = { connected: false, wallet: null };
|
|
360
|
+
this.notifyStatusChange();
|
|
361
|
+
}
|
|
362
|
+
/**
|
|
363
|
+
* Get current connection status
|
|
364
|
+
*/
|
|
365
|
+
getStatus() {
|
|
366
|
+
return { ...this.currentStatus };
|
|
367
|
+
}
|
|
368
|
+
/**
|
|
369
|
+
* Subscribe to status changes
|
|
370
|
+
*/
|
|
371
|
+
onStatusChange(callback) {
|
|
372
|
+
this.statusChangeCallbacks.add(callback);
|
|
373
|
+
// Immediately call with current status
|
|
374
|
+
callback(this.getStatus());
|
|
375
|
+
// Return unsubscribe function
|
|
376
|
+
return () => {
|
|
377
|
+
this.statusChangeCallbacks.delete(callback);
|
|
378
|
+
};
|
|
379
|
+
}
|
|
380
|
+
/**
|
|
381
|
+
* Notify all status change callbacks
|
|
382
|
+
*/
|
|
383
|
+
notifyStatusChange() {
|
|
384
|
+
const status = this.getStatus();
|
|
385
|
+
this.statusChangeCallbacks.forEach((callback) => {
|
|
386
|
+
try {
|
|
387
|
+
callback(status);
|
|
388
|
+
}
|
|
389
|
+
catch (error) {
|
|
390
|
+
// Ignore errors in callbacks
|
|
391
|
+
}
|
|
392
|
+
});
|
|
393
|
+
}
|
|
394
|
+
/**
|
|
395
|
+
* Validate session ID format
|
|
396
|
+
*/
|
|
397
|
+
validateSessionId(sessionId) {
|
|
398
|
+
if (!sessionId || typeof sessionId !== 'string') {
|
|
399
|
+
return false;
|
|
400
|
+
}
|
|
401
|
+
// Session ID should be reasonable length (1-200 characters)
|
|
402
|
+
if (sessionId.length === 0 || sessionId.length > 200) {
|
|
403
|
+
return false;
|
|
404
|
+
}
|
|
405
|
+
// Basic validation: should not contain control characters
|
|
406
|
+
if (/[\x00-\x1F\x7F]/.test(sessionId)) {
|
|
407
|
+
return false;
|
|
408
|
+
}
|
|
409
|
+
return true;
|
|
410
|
+
}
|
|
411
|
+
/**
|
|
412
|
+
* Save session to storage
|
|
413
|
+
*/
|
|
414
|
+
async saveSession(sessionId, wallet) {
|
|
415
|
+
// Validate inputs
|
|
416
|
+
if (!this.validateSessionId(sessionId)) {
|
|
417
|
+
throw new TonConnectError('Invalid session ID format');
|
|
418
|
+
}
|
|
419
|
+
if (!wallet || !wallet.address || !wallet.publicKey) {
|
|
420
|
+
throw new TonConnectError('Invalid wallet data');
|
|
421
|
+
}
|
|
422
|
+
try {
|
|
423
|
+
const sessionKey = `${this.config.storageKeyPrefix}session`;
|
|
424
|
+
const walletKey = `${this.config.storageKeyPrefix}wallet`;
|
|
425
|
+
await this.adapter.setItem(sessionKey, sessionId);
|
|
426
|
+
await this.adapter.setItem(walletKey, JSON.stringify(wallet));
|
|
427
|
+
}
|
|
428
|
+
catch (error) {
|
|
429
|
+
// Log error but don't throw - connection is still valid
|
|
430
|
+
console.error('TON Connect: Failed to save session to storage:', error);
|
|
431
|
+
}
|
|
432
|
+
}
|
|
433
|
+
/**
|
|
434
|
+
* Load session from storage
|
|
435
|
+
*/
|
|
436
|
+
async loadSession() {
|
|
437
|
+
try {
|
|
438
|
+
const sessionKey = `${this.config.storageKeyPrefix}session`;
|
|
439
|
+
const walletKey = `${this.config.storageKeyPrefix}wallet`;
|
|
440
|
+
const sessionId = await this.adapter.getItem(sessionKey);
|
|
441
|
+
const walletJson = await this.adapter.getItem(walletKey);
|
|
442
|
+
if (sessionId && walletJson) {
|
|
443
|
+
try {
|
|
444
|
+
// Validate session ID
|
|
445
|
+
if (!this.validateSessionId(sessionId)) {
|
|
446
|
+
await this.clearSession();
|
|
447
|
+
return;
|
|
448
|
+
}
|
|
449
|
+
const wallet = JSON.parse(walletJson);
|
|
450
|
+
// Validate wallet data
|
|
451
|
+
if (!wallet || !wallet.address || !wallet.publicKey) {
|
|
452
|
+
await this.clearSession();
|
|
453
|
+
return;
|
|
454
|
+
}
|
|
455
|
+
this.currentStatus = { connected: true, wallet };
|
|
456
|
+
this.notifyStatusChange();
|
|
457
|
+
}
|
|
458
|
+
catch (error) {
|
|
459
|
+
// Invalid wallet data, clear it
|
|
460
|
+
console.error('TON Connect: Invalid session data, clearing:', error);
|
|
461
|
+
await this.clearSession();
|
|
462
|
+
}
|
|
463
|
+
}
|
|
464
|
+
}
|
|
465
|
+
catch (error) {
|
|
466
|
+
// Log storage errors for debugging
|
|
467
|
+
console.error('TON Connect: Failed to load session from storage:', error);
|
|
468
|
+
}
|
|
469
|
+
}
|
|
470
|
+
/**
|
|
471
|
+
* Clear session from storage
|
|
472
|
+
*/
|
|
473
|
+
async clearSession() {
|
|
474
|
+
try {
|
|
475
|
+
const sessionKey = `${this.config.storageKeyPrefix}session`;
|
|
476
|
+
const walletKey = `${this.config.storageKeyPrefix}wallet`;
|
|
477
|
+
await this.adapter.removeItem(sessionKey);
|
|
478
|
+
await this.adapter.removeItem(walletKey);
|
|
479
|
+
}
|
|
480
|
+
catch (error) {
|
|
481
|
+
// Ignore storage errors
|
|
482
|
+
}
|
|
483
|
+
}
|
|
484
|
+
/**
|
|
485
|
+
* Cleanup resources
|
|
486
|
+
*/
|
|
487
|
+
destroy() {
|
|
488
|
+
if (this.urlUnsubscribe) {
|
|
489
|
+
this.urlUnsubscribe();
|
|
490
|
+
this.urlUnsubscribe = null;
|
|
491
|
+
}
|
|
492
|
+
if ('destroy' in this.adapter && typeof this.adapter.destroy === 'function') {
|
|
493
|
+
this.adapter.destroy();
|
|
494
|
+
}
|
|
495
|
+
this.statusChangeCallbacks.clear();
|
|
496
|
+
this.connectionPromise = null;
|
|
497
|
+
this.transactionPromise = null;
|
|
498
|
+
}
|
|
499
|
+
}
|
|
500
|
+
exports.TonConnectMobile = TonConnectMobile;
|
|
501
|
+
// Export types
|
|
502
|
+
__exportStar(require("./types"), exports);
|
|
@@ -0,0 +1,192 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Core types for TON Connect Mobile SDK
|
|
3
|
+
* These types define the protocol structure for TonConnect
|
|
4
|
+
*/
|
|
5
|
+
/**
|
|
6
|
+
* Wallet information returned after successful connection
|
|
7
|
+
*/
|
|
8
|
+
export interface WalletInfo {
|
|
9
|
+
/** Wallet name (e.g., "Tonkeeper", "MyTonWallet") */
|
|
10
|
+
name: string;
|
|
11
|
+
/** Wallet app name */
|
|
12
|
+
appName: string;
|
|
13
|
+
/** Wallet app version */
|
|
14
|
+
version: string;
|
|
15
|
+
/** Platform (ios/android) */
|
|
16
|
+
platform: 'ios' | 'android' | 'unknown';
|
|
17
|
+
/** TON address of the connected wallet */
|
|
18
|
+
address: string;
|
|
19
|
+
/** Public key in hex format */
|
|
20
|
+
publicKey: string;
|
|
21
|
+
/** Wallet icon URL */
|
|
22
|
+
icon?: string;
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* Connection status
|
|
26
|
+
*/
|
|
27
|
+
export interface ConnectionStatus {
|
|
28
|
+
/** Whether a wallet is currently connected */
|
|
29
|
+
connected: boolean;
|
|
30
|
+
/** Wallet information if connected, null otherwise */
|
|
31
|
+
wallet: WalletInfo | null;
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* Transaction message to send
|
|
35
|
+
*/
|
|
36
|
+
export interface TransactionMessage {
|
|
37
|
+
/** Recipient address in TON format (EQ...) */
|
|
38
|
+
address: string;
|
|
39
|
+
/** Amount in nanotons (1 TON = 1,000,000,000 nanotons) */
|
|
40
|
+
amount: string;
|
|
41
|
+
/** Optional message payload (base64 encoded) */
|
|
42
|
+
payload?: string;
|
|
43
|
+
/** Optional state init (base64 encoded) */
|
|
44
|
+
stateInit?: string;
|
|
45
|
+
}
|
|
46
|
+
/**
|
|
47
|
+
* Transaction request parameters
|
|
48
|
+
*/
|
|
49
|
+
export interface SendTransactionRequest {
|
|
50
|
+
/** Unix timestamp (ms) when the request expires */
|
|
51
|
+
validUntil: number;
|
|
52
|
+
/** Array of messages to send */
|
|
53
|
+
messages: TransactionMessage[];
|
|
54
|
+
/** Optional network (mainnet/testnet) */
|
|
55
|
+
network?: 'mainnet' | 'testnet';
|
|
56
|
+
/** Optional from address */
|
|
57
|
+
from?: string;
|
|
58
|
+
}
|
|
59
|
+
/**
|
|
60
|
+
* Transaction response from wallet
|
|
61
|
+
*/
|
|
62
|
+
export interface TransactionResponse {
|
|
63
|
+
/** Base64 encoded BOC of the transaction */
|
|
64
|
+
boc: string;
|
|
65
|
+
/** Transaction signature */
|
|
66
|
+
signature: string;
|
|
67
|
+
}
|
|
68
|
+
/**
|
|
69
|
+
* Connection request payload (sent to wallet)
|
|
70
|
+
*/
|
|
71
|
+
export interface ConnectionRequestPayload {
|
|
72
|
+
/** Manifest URL for the app */
|
|
73
|
+
manifestUrl: string;
|
|
74
|
+
/** Items requested from wallet */
|
|
75
|
+
items: Array<{
|
|
76
|
+
name: 'ton_addr';
|
|
77
|
+
}>;
|
|
78
|
+
/** Return URL scheme */
|
|
79
|
+
returnStrategy?: 'back' | 'none';
|
|
80
|
+
}
|
|
81
|
+
/**
|
|
82
|
+
* Connection response payload (received from wallet)
|
|
83
|
+
*/
|
|
84
|
+
export interface ConnectionResponsePayload {
|
|
85
|
+
/** Session ID */
|
|
86
|
+
session: string;
|
|
87
|
+
/** Wallet information */
|
|
88
|
+
name: string;
|
|
89
|
+
/** Wallet app name */
|
|
90
|
+
appName: string;
|
|
91
|
+
/** Wallet version */
|
|
92
|
+
version: string;
|
|
93
|
+
/** Platform */
|
|
94
|
+
platform: 'ios' | 'android' | 'unknown';
|
|
95
|
+
/** TON address */
|
|
96
|
+
address: string;
|
|
97
|
+
/** Public key in hex */
|
|
98
|
+
publicKey: string;
|
|
99
|
+
/** Wallet icon URL */
|
|
100
|
+
icon?: string;
|
|
101
|
+
/** Proof (signature) for verification */
|
|
102
|
+
proof?: {
|
|
103
|
+
timestamp: number;
|
|
104
|
+
domain: {
|
|
105
|
+
lengthBytes: number;
|
|
106
|
+
value: string;
|
|
107
|
+
};
|
|
108
|
+
signature: string;
|
|
109
|
+
};
|
|
110
|
+
}
|
|
111
|
+
/**
|
|
112
|
+
* Transaction request payload (sent to wallet)
|
|
113
|
+
*/
|
|
114
|
+
export interface TransactionRequestPayload {
|
|
115
|
+
/** Manifest URL */
|
|
116
|
+
manifestUrl: string;
|
|
117
|
+
/** Transaction request */
|
|
118
|
+
request: {
|
|
119
|
+
/** Unix timestamp (ms) when request expires */
|
|
120
|
+
validUntil: number;
|
|
121
|
+
/** Array of messages */
|
|
122
|
+
messages: Array<{
|
|
123
|
+
address: string;
|
|
124
|
+
amount: string;
|
|
125
|
+
payload?: string;
|
|
126
|
+
stateInit?: string;
|
|
127
|
+
}>;
|
|
128
|
+
/** Optional network */
|
|
129
|
+
network?: 'mainnet' | 'testnet';
|
|
130
|
+
/** Optional from address */
|
|
131
|
+
from?: string;
|
|
132
|
+
};
|
|
133
|
+
/** Return URL scheme */
|
|
134
|
+
returnStrategy?: 'back' | 'none';
|
|
135
|
+
}
|
|
136
|
+
/**
|
|
137
|
+
* Transaction response payload (received from wallet)
|
|
138
|
+
*/
|
|
139
|
+
export interface TransactionResponsePayload {
|
|
140
|
+
/** Base64 encoded BOC */
|
|
141
|
+
boc: string;
|
|
142
|
+
/** Transaction signature */
|
|
143
|
+
signature: string;
|
|
144
|
+
}
|
|
145
|
+
/**
|
|
146
|
+
* Error response from wallet
|
|
147
|
+
*/
|
|
148
|
+
export interface ErrorResponse {
|
|
149
|
+
/** Error code */
|
|
150
|
+
error: {
|
|
151
|
+
code: number;
|
|
152
|
+
message: string;
|
|
153
|
+
};
|
|
154
|
+
}
|
|
155
|
+
/**
|
|
156
|
+
* Platform adapter interface for deep linking and storage
|
|
157
|
+
*/
|
|
158
|
+
export interface PlatformAdapter {
|
|
159
|
+
/** Open a deep link URL */
|
|
160
|
+
openURL(url: string): Promise<boolean>;
|
|
161
|
+
/** Get initial URL when app was opened via deep link */
|
|
162
|
+
getInitialURL(): Promise<string | null>;
|
|
163
|
+
/** Add listener for URL changes */
|
|
164
|
+
addURLListener(callback: (url: string) => void): () => void;
|
|
165
|
+
/** Store data */
|
|
166
|
+
setItem(key: string, value: string): Promise<void>;
|
|
167
|
+
/** Retrieve data */
|
|
168
|
+
getItem(key: string): Promise<string | null>;
|
|
169
|
+
/** Remove data */
|
|
170
|
+
removeItem(key: string): Promise<void>;
|
|
171
|
+
/** Generate random bytes */
|
|
172
|
+
randomBytes(length: number): Promise<Uint8Array>;
|
|
173
|
+
}
|
|
174
|
+
/**
|
|
175
|
+
* SDK configuration
|
|
176
|
+
*/
|
|
177
|
+
export interface TonConnectMobileConfig {
|
|
178
|
+
/** Manifest URL (required) */
|
|
179
|
+
manifestUrl: string;
|
|
180
|
+
/** Deep link scheme for callbacks (required) */
|
|
181
|
+
scheme: string;
|
|
182
|
+
/** Optional storage key prefix */
|
|
183
|
+
storageKeyPrefix?: string;
|
|
184
|
+
/** Optional connection timeout in ms (default: 300000 = 5 minutes) */
|
|
185
|
+
connectionTimeout?: number;
|
|
186
|
+
/** Optional transaction timeout in ms (default: 300000 = 5 minutes) */
|
|
187
|
+
transactionTimeout?: number;
|
|
188
|
+
}
|
|
189
|
+
/**
|
|
190
|
+
* Event listener callback type
|
|
191
|
+
*/
|
|
192
|
+
export type StatusChangeCallback = (status: ConnectionStatus) => void;
|