@blazium/ton-connect-mobile 1.2.4 → 1.2.6
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/README.md +7 -20
- package/dist/adapters/react-native.js +8 -1
- package/dist/core/bridge.d.ts +61 -0
- package/dist/core/bridge.js +237 -0
- package/dist/core/crypto.d.ts +8 -19
- package/dist/core/crypto.js +15 -141
- package/dist/core/index.d.ts +5 -3
- package/dist/core/index.js +20 -17
- package/dist/core/protocol.d.ts +35 -32
- package/dist/core/protocol.js +109 -285
- package/dist/core/session.d.ts +65 -0
- package/dist/core/session.js +235 -0
- package/dist/core/wallets.d.ts +6 -6
- package/dist/core/wallets.js +17 -18
- package/dist/index.d.ts +33 -72
- package/dist/index.js +322 -769
- package/dist/react/TonConnectUIProvider.d.ts +4 -52
- package/dist/react/TonConnectUIProvider.js +18 -122
- package/dist/react/index.d.ts +1 -2
- package/dist/react/index.js +0 -1
- package/dist/types/index.d.ts +84 -139
- package/dist/types/index.js +1 -1
- package/package.json +2 -3
- package/src/adapters/react-native.ts +7 -1
- package/src/core/bridge.ts +307 -0
- package/src/core/crypto.ts +62 -238
- package/src/core/index.ts +17 -7
- package/src/core/protocol.ts +217 -441
- package/src/core/session.ts +247 -0
- package/src/core/wallets.ts +90 -93
- package/src/index.ts +811 -1338
- package/src/react/TonConnectUIProvider.tsx +272 -441
- package/src/react/index.ts +23 -27
- package/src/types/index.ts +217 -272
package/dist/core/index.js
CHANGED
|
@@ -1,21 +1,24 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
/**
|
|
3
|
-
* Core
|
|
3
|
+
* Core module exports
|
|
4
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
5
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
20
|
-
|
|
21
|
-
|
|
6
|
+
exports.getWalletsForPlatform = exports.getDefaultWallet = exports.getWalletByName = exports.SUPPORTED_WALLETS = exports.validateTransactionRequest = exports.extractWalletInfoFromEvent = exports.parseRpcResponse = exports.parseConnectResponse = exports.buildDisconnectRpcRequest = exports.buildSendTransactionRpcRequest = exports.buildReturnUniversalLink = exports.buildConnectUniversalLink = exports.BridgeGateway = exports.SessionCrypto = void 0;
|
|
7
|
+
var session_1 = require("./session");
|
|
8
|
+
Object.defineProperty(exports, "SessionCrypto", { enumerable: true, get: function () { return session_1.SessionCrypto; } });
|
|
9
|
+
var bridge_1 = require("./bridge");
|
|
10
|
+
Object.defineProperty(exports, "BridgeGateway", { enumerable: true, get: function () { return bridge_1.BridgeGateway; } });
|
|
11
|
+
var protocol_1 = require("./protocol");
|
|
12
|
+
Object.defineProperty(exports, "buildConnectUniversalLink", { enumerable: true, get: function () { return protocol_1.buildConnectUniversalLink; } });
|
|
13
|
+
Object.defineProperty(exports, "buildReturnUniversalLink", { enumerable: true, get: function () { return protocol_1.buildReturnUniversalLink; } });
|
|
14
|
+
Object.defineProperty(exports, "buildSendTransactionRpcRequest", { enumerable: true, get: function () { return protocol_1.buildSendTransactionRpcRequest; } });
|
|
15
|
+
Object.defineProperty(exports, "buildDisconnectRpcRequest", { enumerable: true, get: function () { return protocol_1.buildDisconnectRpcRequest; } });
|
|
16
|
+
Object.defineProperty(exports, "parseConnectResponse", { enumerable: true, get: function () { return protocol_1.parseConnectResponse; } });
|
|
17
|
+
Object.defineProperty(exports, "parseRpcResponse", { enumerable: true, get: function () { return protocol_1.parseRpcResponse; } });
|
|
18
|
+
Object.defineProperty(exports, "extractWalletInfoFromEvent", { enumerable: true, get: function () { return protocol_1.extractWalletInfoFromEvent; } });
|
|
19
|
+
Object.defineProperty(exports, "validateTransactionRequest", { enumerable: true, get: function () { return protocol_1.validateTransactionRequest; } });
|
|
20
|
+
var wallets_1 = require("./wallets");
|
|
21
|
+
Object.defineProperty(exports, "SUPPORTED_WALLETS", { enumerable: true, get: function () { return wallets_1.SUPPORTED_WALLETS; } });
|
|
22
|
+
Object.defineProperty(exports, "getWalletByName", { enumerable: true, get: function () { return wallets_1.getWalletByName; } });
|
|
23
|
+
Object.defineProperty(exports, "getDefaultWallet", { enumerable: true, get: function () { return wallets_1.getDefaultWallet; } });
|
|
24
|
+
Object.defineProperty(exports, "getWalletsForPlatform", { enumerable: true, get: function () { return wallets_1.getWalletsForPlatform; } });
|
package/dist/core/protocol.d.ts
CHANGED
|
@@ -1,51 +1,54 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
3
|
-
*
|
|
2
|
+
* TON Connect v2 Protocol Implementation
|
|
3
|
+
* Builds correct universal links and parses bridge responses
|
|
4
4
|
*/
|
|
5
|
-
import {
|
|
5
|
+
import type { SendTransactionRequest, WalletInfo, ConnectEvent, ConnectErrorEvent, RpcResponse, RpcErrorResponse } from '../types';
|
|
6
6
|
/**
|
|
7
|
-
*
|
|
7
|
+
* Build a TON Connect v2 universal link for wallet connection
|
|
8
|
+
* Format: {universalLink}?v=2&id={sessionId}&r={connectRequest}&ret={returnStrategy}
|
|
8
9
|
*/
|
|
9
|
-
export declare function
|
|
10
|
+
export declare function buildConnectUniversalLink(universalLink: string, sessionId: string, manifestUrl: string, returnStrategy?: string): string;
|
|
10
11
|
/**
|
|
11
|
-
*
|
|
12
|
+
* Build a universal link to bring wallet to foreground (for pending transactions)
|
|
13
|
+
* Format: {universalLink}?ret={returnStrategy}
|
|
12
14
|
*/
|
|
13
|
-
export declare function
|
|
15
|
+
export declare function buildReturnUniversalLink(universalLink: string, returnStrategy?: string): string;
|
|
14
16
|
/**
|
|
15
|
-
* Build
|
|
16
|
-
* Format: tonconnect://connect?<base64_encoded_payload>
|
|
17
|
-
* Or universal link: https://app.tonkeeper.com/ton-connect?<base64_encoded_payload>
|
|
18
|
-
* Or custom wallet universal link
|
|
17
|
+
* Build a JSON-RPC request for sendTransaction
|
|
19
18
|
*/
|
|
20
|
-
export declare function
|
|
19
|
+
export declare function buildSendTransactionRpcRequest(request: SendTransactionRequest, id: number): string;
|
|
21
20
|
/**
|
|
22
|
-
* Build
|
|
23
|
-
* Format: tonconnect://send-transaction?<base64_encoded_payload>
|
|
24
|
-
* Or universal link: https://app.tonkeeper.com/ton-connect/send-transaction?<base64_encoded_payload>
|
|
25
|
-
* Or custom wallet universal link
|
|
21
|
+
* Build a JSON-RPC request for disconnect
|
|
26
22
|
*/
|
|
27
|
-
export declare function
|
|
23
|
+
export declare function buildDisconnectRpcRequest(id: number): string;
|
|
28
24
|
/**
|
|
29
|
-
* Parse
|
|
30
|
-
* Format: <scheme>://tonconnect?<base64_encoded_response>
|
|
25
|
+
* Parse a connect response from the wallet (received via bridge, after decryption)
|
|
31
26
|
*/
|
|
32
|
-
export declare function
|
|
33
|
-
type: 'connect'
|
|
34
|
-
data:
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
*/
|
|
40
|
-
export declare function extractWalletInfo(response: ConnectionResponsePayload): WalletInfo;
|
|
27
|
+
export declare function parseConnectResponse(decrypted: string): {
|
|
28
|
+
type: 'connect';
|
|
29
|
+
data: ConnectEvent;
|
|
30
|
+
} | {
|
|
31
|
+
type: 'error';
|
|
32
|
+
data: ConnectErrorEvent;
|
|
33
|
+
} | null;
|
|
41
34
|
/**
|
|
42
|
-
*
|
|
35
|
+
* Parse an RPC response from the wallet (for sendTransaction, disconnect, etc.)
|
|
43
36
|
*/
|
|
44
|
-
export declare function
|
|
37
|
+
export declare function parseRpcResponse(decrypted: string): {
|
|
38
|
+
type: 'result';
|
|
39
|
+
data: RpcResponse;
|
|
40
|
+
} | {
|
|
41
|
+
type: 'error';
|
|
42
|
+
data: RpcErrorResponse;
|
|
43
|
+
} | {
|
|
44
|
+
type: 'event';
|
|
45
|
+
event: string;
|
|
46
|
+
data: any;
|
|
47
|
+
} | null;
|
|
45
48
|
/**
|
|
46
|
-
*
|
|
49
|
+
* Extract wallet info from a connect event
|
|
47
50
|
*/
|
|
48
|
-
export declare function
|
|
51
|
+
export declare function extractWalletInfoFromEvent(event: ConnectEvent): WalletInfo;
|
|
49
52
|
/**
|
|
50
53
|
* Validate transaction request
|
|
51
54
|
*/
|
package/dist/core/protocol.js
CHANGED
|
@@ -1,289 +1,146 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
/**
|
|
3
|
-
*
|
|
4
|
-
*
|
|
3
|
+
* TON Connect v2 Protocol Implementation
|
|
4
|
+
* Builds correct universal links and parses bridge responses
|
|
5
5
|
*/
|
|
6
6
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
7
|
-
exports.
|
|
8
|
-
exports.
|
|
9
|
-
exports.
|
|
10
|
-
exports.
|
|
11
|
-
exports.
|
|
12
|
-
exports.
|
|
13
|
-
exports.
|
|
14
|
-
exports.validateTransactionResponse = validateTransactionResponse;
|
|
7
|
+
exports.buildConnectUniversalLink = buildConnectUniversalLink;
|
|
8
|
+
exports.buildReturnUniversalLink = buildReturnUniversalLink;
|
|
9
|
+
exports.buildSendTransactionRpcRequest = buildSendTransactionRpcRequest;
|
|
10
|
+
exports.buildDisconnectRpcRequest = buildDisconnectRpcRequest;
|
|
11
|
+
exports.parseConnectResponse = parseConnectResponse;
|
|
12
|
+
exports.parseRpcResponse = parseRpcResponse;
|
|
13
|
+
exports.extractWalletInfoFromEvent = extractWalletInfoFromEvent;
|
|
15
14
|
exports.validateTransactionRequest = validateTransactionRequest;
|
|
16
15
|
/**
|
|
17
|
-
*
|
|
16
|
+
* Protocol version
|
|
18
17
|
*/
|
|
19
18
|
const PROTOCOL_VERSION = '2';
|
|
20
|
-
const CONNECT_PREFIX = 'tonconnect://connect';
|
|
21
|
-
const CONNECT_UNIVERSAL_PREFIX = 'https://app.tonkeeper.com/ton-connect';
|
|
22
|
-
const SEND_TRANSACTION_PREFIX = 'tonconnect://send-transaction';
|
|
23
|
-
const SEND_TRANSACTION_UNIVERSAL_PREFIX = 'https://app.tonkeeper.com/ton-connect/send-transaction';
|
|
24
|
-
const CALLBACK_PREFIX = 'tonconnect';
|
|
25
19
|
/**
|
|
26
|
-
*
|
|
20
|
+
* Build a TON Connect v2 universal link for wallet connection
|
|
21
|
+
* Format: {universalLink}?v=2&id={sessionId}&r={connectRequest}&ret={returnStrategy}
|
|
27
22
|
*/
|
|
28
|
-
function
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
while (i < bytes.length) {
|
|
46
|
-
const a = bytes[i++];
|
|
47
|
-
const b = i < bytes.length ? bytes[i++] : 0;
|
|
48
|
-
const c = i < bytes.length ? bytes[i++] : 0;
|
|
49
|
-
const bitmap = (a << 16) | (b << 8) | c;
|
|
50
|
-
result += chars.charAt((bitmap >> 18) & 63);
|
|
51
|
-
result += chars.charAt((bitmap >> 12) & 63);
|
|
52
|
-
result += i - 2 < bytes.length ? chars.charAt((bitmap >> 6) & 63) : '=';
|
|
53
|
-
result += i - 1 < bytes.length ? chars.charAt(bitmap & 63) : '=';
|
|
54
|
-
}
|
|
55
|
-
return result;
|
|
23
|
+
function buildConnectUniversalLink(universalLink, sessionId, manifestUrl, returnStrategy = 'back') {
|
|
24
|
+
// Build connect request (TON Connect v2 format)
|
|
25
|
+
const connectRequest = {
|
|
26
|
+
manifestUrl,
|
|
27
|
+
items: [{ name: 'ton_addr' }],
|
|
28
|
+
};
|
|
29
|
+
// Build URL with proper query parameters
|
|
30
|
+
const r = JSON.stringify(connectRequest);
|
|
31
|
+
const params = [
|
|
32
|
+
`v=${PROTOCOL_VERSION}`,
|
|
33
|
+
`id=${sessionId}`,
|
|
34
|
+
`r=${encodeURIComponent(r)}`,
|
|
35
|
+
`ret=${encodeURIComponent(returnStrategy)}`,
|
|
36
|
+
];
|
|
37
|
+
// Handle wallet universal links that may already have query params
|
|
38
|
+
const separator = universalLink.includes('?') ? '&' : '?';
|
|
39
|
+
return `${universalLink}${separator}${params.join('&')}`;
|
|
56
40
|
}
|
|
57
41
|
/**
|
|
58
|
-
*
|
|
42
|
+
* Build a universal link to bring wallet to foreground (for pending transactions)
|
|
43
|
+
* Format: {universalLink}?ret={returnStrategy}
|
|
59
44
|
*/
|
|
60
|
-
function
|
|
61
|
-
const
|
|
62
|
-
|
|
63
|
-
let bitsCollected = 0;
|
|
64
|
-
let result = '';
|
|
65
|
-
for (let i = 0; i < base64.length; i++) {
|
|
66
|
-
const ch = base64[i];
|
|
67
|
-
if (ch === '=')
|
|
68
|
-
break;
|
|
69
|
-
const index = chars.indexOf(ch);
|
|
70
|
-
if (index === -1)
|
|
71
|
-
continue;
|
|
72
|
-
buffer = (buffer << 6) | index;
|
|
73
|
-
bitsCollected += 6;
|
|
74
|
-
if (bitsCollected >= 8) {
|
|
75
|
-
bitsCollected -= 8;
|
|
76
|
-
result += String.fromCharCode((buffer >> bitsCollected) & 0xff);
|
|
77
|
-
buffer &= (1 << bitsCollected) - 1;
|
|
78
|
-
}
|
|
79
|
-
}
|
|
80
|
-
return result;
|
|
45
|
+
function buildReturnUniversalLink(universalLink, returnStrategy = 'back') {
|
|
46
|
+
const separator = universalLink.includes('?') ? '&' : '?';
|
|
47
|
+
return `${universalLink}${separator}ret=${encodeURIComponent(returnStrategy)}`;
|
|
81
48
|
}
|
|
82
49
|
/**
|
|
83
|
-
*
|
|
50
|
+
* Build a JSON-RPC request for sendTransaction
|
|
84
51
|
*/
|
|
85
|
-
function
|
|
86
|
-
|
|
87
|
-
const
|
|
88
|
-
|
|
89
|
-
|
|
52
|
+
function buildSendTransactionRpcRequest(request, id) {
|
|
53
|
+
// TON Connect v2 sendTransaction format
|
|
54
|
+
const params = JSON.stringify({
|
|
55
|
+
valid_until: Math.floor(request.validUntil / 1000), // Convert ms to seconds
|
|
56
|
+
network: request.network === 'testnet' ? '-3' : '-239',
|
|
57
|
+
from: request.from,
|
|
58
|
+
messages: request.messages.map((msg) => ({
|
|
59
|
+
address: msg.address,
|
|
60
|
+
amount: msg.amount,
|
|
61
|
+
payload: msg.payload,
|
|
62
|
+
stateInit: msg.stateInit,
|
|
63
|
+
})),
|
|
64
|
+
});
|
|
65
|
+
return JSON.stringify({
|
|
66
|
+
method: 'sendTransaction',
|
|
67
|
+
params: [params],
|
|
68
|
+
id,
|
|
69
|
+
});
|
|
90
70
|
}
|
|
91
71
|
/**
|
|
92
|
-
*
|
|
72
|
+
* Build a JSON-RPC request for disconnect
|
|
93
73
|
*/
|
|
94
|
-
function
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
return JSON.parse(json);
|
|
74
|
+
function buildDisconnectRpcRequest(id) {
|
|
75
|
+
return JSON.stringify({
|
|
76
|
+
method: 'disconnect',
|
|
77
|
+
params: [],
|
|
78
|
+
id,
|
|
79
|
+
});
|
|
101
80
|
}
|
|
102
81
|
/**
|
|
103
|
-
*
|
|
104
|
-
* Format: tonconnect://connect?<base64_encoded_payload>
|
|
105
|
-
* Or universal link: https://app.tonkeeper.com/ton-connect?<base64_encoded_payload>
|
|
106
|
-
* Or custom wallet universal link
|
|
82
|
+
* Parse a connect response from the wallet (received via bridge, after decryption)
|
|
107
83
|
*/
|
|
108
|
-
function
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
if (requiresReturnScheme !== false) {
|
|
119
|
-
// Default to true if not specified - safer to include it
|
|
120
|
-
payload.returnScheme = returnScheme;
|
|
121
|
-
}
|
|
122
|
-
const encoded = encodeBase64URL(payload);
|
|
123
|
-
// Use custom wallet universal link if provided
|
|
124
|
-
if (walletUniversalLink) {
|
|
125
|
-
return `${walletUniversalLink}?${encoded}`;
|
|
126
|
-
}
|
|
127
|
-
// Default to Tonkeeper universal link for Android compatibility
|
|
128
|
-
return `${CONNECT_UNIVERSAL_PREFIX}?${encoded}`;
|
|
129
|
-
}
|
|
130
|
-
/**
|
|
131
|
-
* Build transaction request URL
|
|
132
|
-
* Format: tonconnect://send-transaction?<base64_encoded_payload>
|
|
133
|
-
* Or universal link: https://app.tonkeeper.com/ton-connect/send-transaction?<base64_encoded_payload>
|
|
134
|
-
* Or custom wallet universal link
|
|
135
|
-
*/
|
|
136
|
-
function buildTransactionRequest(manifestUrl, request, returnScheme, walletUniversalLink, returnStrategy, requiresReturnScheme) {
|
|
137
|
-
const payload = {
|
|
138
|
-
manifestUrl,
|
|
139
|
-
request: {
|
|
140
|
-
validUntil: request.validUntil,
|
|
141
|
-
messages: request.messages.map((msg) => ({
|
|
142
|
-
address: msg.address,
|
|
143
|
-
amount: msg.amount,
|
|
144
|
-
payload: msg.payload,
|
|
145
|
-
stateInit: msg.stateInit,
|
|
146
|
-
})),
|
|
147
|
-
network: request.network,
|
|
148
|
-
from: request.from,
|
|
149
|
-
},
|
|
150
|
-
returnStrategy: returnStrategy || 'back',
|
|
151
|
-
};
|
|
152
|
-
// CRITICAL FIX: Include returnScheme for mobile wallets that require it
|
|
153
|
-
if (requiresReturnScheme !== false) {
|
|
154
|
-
payload.returnScheme = returnScheme;
|
|
84
|
+
function parseConnectResponse(decrypted) {
|
|
85
|
+
try {
|
|
86
|
+
const parsed = JSON.parse(decrypted);
|
|
87
|
+
if (parsed.event === 'connect' && parsed.payload) {
|
|
88
|
+
return { type: 'connect', data: parsed };
|
|
89
|
+
}
|
|
90
|
+
if (parsed.event === 'connect_error' && parsed.payload) {
|
|
91
|
+
return { type: 'error', data: parsed };
|
|
92
|
+
}
|
|
93
|
+
return null;
|
|
155
94
|
}
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
if (walletUniversalLink) {
|
|
159
|
-
// For transaction, append /send-transaction to the base universal link
|
|
160
|
-
const baseUrl = walletUniversalLink.endsWith('/ton-connect')
|
|
161
|
-
? walletUniversalLink
|
|
162
|
-
: `${walletUniversalLink}/ton-connect`;
|
|
163
|
-
return `${baseUrl}/send-transaction?${encoded}`;
|
|
95
|
+
catch {
|
|
96
|
+
return null;
|
|
164
97
|
}
|
|
165
|
-
// Default to Tonkeeper universal link for Android compatibility
|
|
166
|
-
return `${SEND_TRANSACTION_UNIVERSAL_PREFIX}?${encoded}`;
|
|
167
98
|
}
|
|
168
99
|
/**
|
|
169
|
-
* Parse
|
|
170
|
-
* Format: <scheme>://tonconnect?<base64_encoded_response>
|
|
100
|
+
* Parse an RPC response from the wallet (for sendTransaction, disconnect, etc.)
|
|
171
101
|
*/
|
|
172
|
-
function
|
|
102
|
+
function parseRpcResponse(decrypted) {
|
|
173
103
|
try {
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
// CRITICAL FIX: Validate URL length (prevent DoS)
|
|
179
|
-
if (url.length > 10000) {
|
|
180
|
-
return { type: 'unknown', data: null };
|
|
181
|
-
}
|
|
182
|
-
// CRITICAL FIX: Validate scheme format
|
|
183
|
-
if (!scheme || typeof scheme !== 'string' || scheme.length === 0 || scheme.length > 50) {
|
|
184
|
-
return { type: 'unknown', data: null };
|
|
104
|
+
const parsed = JSON.parse(decrypted);
|
|
105
|
+
// Check for events (disconnect, etc.)
|
|
106
|
+
if (parsed.event) {
|
|
107
|
+
return { type: 'event', event: parsed.event, data: parsed.payload || null };
|
|
185
108
|
}
|
|
186
|
-
//
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
return { type: 'unknown', data: null };
|
|
109
|
+
// Check for RPC result
|
|
110
|
+
if ('result' in parsed && parsed.id !== undefined) {
|
|
111
|
+
return { type: 'result', data: parsed };
|
|
190
112
|
}
|
|
191
|
-
//
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
if (!urlAfterScheme.startsWith(`${CALLBACK_PREFIX}?`)) {
|
|
195
|
-
return { type: 'unknown', data: null };
|
|
113
|
+
// Check for RPC error
|
|
114
|
+
if ('error' in parsed && parsed.id !== undefined) {
|
|
115
|
+
return { type: 'error', data: parsed };
|
|
196
116
|
}
|
|
197
|
-
|
|
198
|
-
let encoded = url.substring(expectedPrefix.length);
|
|
199
|
-
// CRITICAL FIX: Decode URL encoding first (wallet may URL-encode the payload)
|
|
200
|
-
try {
|
|
201
|
-
encoded = decodeURIComponent(encoded);
|
|
202
|
-
}
|
|
203
|
-
catch (error) {
|
|
204
|
-
// If decodeURIComponent fails, try using the original encoded string
|
|
205
|
-
// Some wallets may not URL-encode the payload
|
|
206
|
-
console.log('[TON Connect] Payload not URL-encoded, using as-is');
|
|
207
|
-
}
|
|
208
|
-
// CRITICAL FIX: Validate base64 payload size (prevent DoS)
|
|
209
|
-
if (encoded.length === 0 || encoded.length > 5000) {
|
|
210
|
-
return { type: 'unknown', data: null };
|
|
211
|
-
}
|
|
212
|
-
// CRITICAL FIX: Validate base64 characters only (after URL decoding)
|
|
213
|
-
if (!/^[A-Za-z0-9_-]+$/.test(encoded)) {
|
|
214
|
-
return { type: 'unknown', data: null };
|
|
215
|
-
}
|
|
216
|
-
const decoded = decodeBase64URL(encoded);
|
|
217
|
-
// Validate decoded data is an object
|
|
218
|
-
if (!decoded || typeof decoded !== 'object' || Array.isArray(decoded)) {
|
|
219
|
-
return { type: 'unknown', data: null };
|
|
220
|
-
}
|
|
221
|
-
// Check if it's an error response
|
|
222
|
-
if ('error' in decoded && typeof decoded.error === 'object') {
|
|
223
|
-
const errorData = decoded;
|
|
224
|
-
if (errorData.error && typeof errorData.error.code === 'number' && typeof errorData.error.message === 'string') {
|
|
225
|
-
return { type: 'error', data: errorData };
|
|
226
|
-
}
|
|
227
|
-
}
|
|
228
|
-
// Check if it's a connection response (has session, address, publicKey)
|
|
229
|
-
if ('session' in decoded &&
|
|
230
|
-
'address' in decoded &&
|
|
231
|
-
'publicKey' in decoded &&
|
|
232
|
-
typeof decoded.session === 'string' &&
|
|
233
|
-
typeof decoded.address === 'string' &&
|
|
234
|
-
typeof decoded.publicKey === 'string') {
|
|
235
|
-
return { type: 'connect', data: decoded };
|
|
236
|
-
}
|
|
237
|
-
// Check if it's a transaction response (has boc, signature)
|
|
238
|
-
if ('boc' in decoded &&
|
|
239
|
-
'signature' in decoded &&
|
|
240
|
-
typeof decoded.boc === 'string' &&
|
|
241
|
-
typeof decoded.signature === 'string') {
|
|
242
|
-
return { type: 'transaction', data: decoded };
|
|
243
|
-
}
|
|
244
|
-
return { type: 'unknown', data: null };
|
|
117
|
+
return null;
|
|
245
118
|
}
|
|
246
|
-
catch
|
|
247
|
-
|
|
248
|
-
return { type: 'unknown', data: null };
|
|
119
|
+
catch {
|
|
120
|
+
return null;
|
|
249
121
|
}
|
|
250
122
|
}
|
|
251
123
|
/**
|
|
252
|
-
* Extract wallet info from
|
|
253
|
-
* CRITICAL: This function assumes response has been validated by validateConnectionResponse
|
|
124
|
+
* Extract wallet info from a connect event
|
|
254
125
|
*/
|
|
255
|
-
function
|
|
256
|
-
|
|
257
|
-
if (!
|
|
258
|
-
throw new Error('
|
|
126
|
+
function extractWalletInfoFromEvent(event) {
|
|
127
|
+
const tonAddr = event.payload.items.find((item) => item.name === 'ton_addr');
|
|
128
|
+
if (!tonAddr) {
|
|
129
|
+
throw new Error('Connect response missing ton_addr item');
|
|
259
130
|
}
|
|
131
|
+
const device = event.payload.device || {};
|
|
260
132
|
return {
|
|
261
|
-
name:
|
|
262
|
-
appName:
|
|
263
|
-
version:
|
|
264
|
-
platform:
|
|
265
|
-
address:
|
|
266
|
-
publicKey:
|
|
267
|
-
|
|
133
|
+
name: device.appName || 'Unknown Wallet',
|
|
134
|
+
appName: device.appName || 'unknown',
|
|
135
|
+
version: device.appVersion || 'unknown',
|
|
136
|
+
platform: device.platform || 'unknown',
|
|
137
|
+
address: tonAddr.address,
|
|
138
|
+
publicKey: tonAddr.publicKey,
|
|
139
|
+
network: tonAddr.network,
|
|
140
|
+
walletStateInit: tonAddr.walletStateInit,
|
|
141
|
+
icon: undefined,
|
|
268
142
|
};
|
|
269
143
|
}
|
|
270
|
-
/**
|
|
271
|
-
* Validate connection response
|
|
272
|
-
*/
|
|
273
|
-
function validateConnectionResponse(response) {
|
|
274
|
-
return !!(response.session &&
|
|
275
|
-
response.address &&
|
|
276
|
-
response.publicKey &&
|
|
277
|
-
response.name &&
|
|
278
|
-
response.appName &&
|
|
279
|
-
response.version);
|
|
280
|
-
}
|
|
281
|
-
/**
|
|
282
|
-
* Validate transaction response
|
|
283
|
-
*/
|
|
284
|
-
function validateTransactionResponse(response) {
|
|
285
|
-
return !!(response.boc && response.signature);
|
|
286
|
-
}
|
|
287
144
|
/**
|
|
288
145
|
* Validate transaction request
|
|
289
146
|
*/
|
|
@@ -294,59 +151,26 @@ function validateTransactionRequest(request) {
|
|
|
294
151
|
if (!request.messages || request.messages.length === 0) {
|
|
295
152
|
return { valid: false, error: 'Transaction must have at least one message' };
|
|
296
153
|
}
|
|
297
|
-
// CRITICAL: Validate each message
|
|
298
154
|
for (let i = 0; i < request.messages.length; i++) {
|
|
299
155
|
const msg = request.messages[i];
|
|
300
|
-
// Validate address
|
|
301
156
|
if (!msg.address || typeof msg.address !== 'string') {
|
|
302
|
-
return { valid: false, error: `Message ${i + 1}: Address is required
|
|
303
|
-
}
|
|
304
|
-
// CRITICAL: Validate TON address format (EQ... or 0Q...)
|
|
305
|
-
if (!/^(EQ|0Q)[A-Za-z0-9_-]{46}$/.test(msg.address)) {
|
|
306
|
-
return { valid: false, error: `Message ${i + 1}: Invalid TON address format. Address must start with EQ or 0Q and be 48 characters long.` };
|
|
157
|
+
return { valid: false, error: `Message ${i + 1}: Address is required` };
|
|
307
158
|
}
|
|
308
|
-
// Validate amount
|
|
309
159
|
if (!msg.amount || typeof msg.amount !== 'string') {
|
|
310
|
-
return { valid: false, error: `Message ${i + 1}: Amount is required
|
|
160
|
+
return { valid: false, error: `Message ${i + 1}: Amount is required (nanotons string)` };
|
|
311
161
|
}
|
|
312
|
-
// CRITICAL: Validate amount is a valid positive number (nanotons)
|
|
313
162
|
try {
|
|
314
163
|
const amount = BigInt(msg.amount);
|
|
315
164
|
if (amount <= 0n) {
|
|
316
|
-
return { valid: false, error: `Message ${i + 1}: Amount must be
|
|
317
|
-
}
|
|
318
|
-
// Check for reasonable maximum (prevent overflow)
|
|
319
|
-
if (amount > BigInt('1000000000000000000')) { // 1 billion TON
|
|
320
|
-
return { valid: false, error: `Message ${i + 1}: Amount exceeds maximum allowed (1 billion TON)` };
|
|
165
|
+
return { valid: false, error: `Message ${i + 1}: Amount must be > 0` };
|
|
321
166
|
}
|
|
322
167
|
}
|
|
323
|
-
catch
|
|
324
|
-
return { valid: false, error: `Message ${i + 1}: Amount must be a valid number string
|
|
325
|
-
}
|
|
326
|
-
// Validate payload if provided (must be base64)
|
|
327
|
-
if (msg.payload !== undefined && msg.payload !== null) {
|
|
328
|
-
if (typeof msg.payload !== 'string') {
|
|
329
|
-
return { valid: false, error: `Message ${i + 1}: Payload must be a base64 string` };
|
|
330
|
-
}
|
|
331
|
-
// Basic base64 validation
|
|
332
|
-
if (msg.payload.length > 0 && !/^[A-Za-z0-9+/=]+$/.test(msg.payload)) {
|
|
333
|
-
return { valid: false, error: `Message ${i + 1}: Payload must be valid base64 encoded` };
|
|
334
|
-
}
|
|
335
|
-
}
|
|
336
|
-
// Validate stateInit if provided (must be base64)
|
|
337
|
-
if (msg.stateInit !== undefined && msg.stateInit !== null) {
|
|
338
|
-
if (typeof msg.stateInit !== 'string') {
|
|
339
|
-
return { valid: false, error: `Message ${i + 1}: StateInit must be a base64 string` };
|
|
340
|
-
}
|
|
341
|
-
// Basic base64 validation
|
|
342
|
-
if (msg.stateInit.length > 0 && !/^[A-Za-z0-9+/=]+$/.test(msg.stateInit)) {
|
|
343
|
-
return { valid: false, error: `Message ${i + 1}: StateInit must be valid base64 encoded` };
|
|
344
|
-
}
|
|
168
|
+
catch {
|
|
169
|
+
return { valid: false, error: `Message ${i + 1}: Amount must be a valid number string` };
|
|
345
170
|
}
|
|
346
171
|
}
|
|
347
|
-
// CRITICAL: Limit maximum number of messages (prevent DoS)
|
|
348
172
|
if (request.messages.length > 255) {
|
|
349
|
-
return { valid: false, error: '
|
|
173
|
+
return { valid: false, error: 'Maximum 255 messages per transaction' };
|
|
350
174
|
}
|
|
351
175
|
return { valid: true };
|
|
352
176
|
}
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Session crypto for TON Connect v2 protocol
|
|
3
|
+
* Uses X25519 (NaCl box) for key exchange and message encryption
|
|
4
|
+
*/
|
|
5
|
+
/**
|
|
6
|
+
* Convert Uint8Array to hex string
|
|
7
|
+
*/
|
|
8
|
+
export declare function bytesToHex(bytes: Uint8Array): string;
|
|
9
|
+
/**
|
|
10
|
+
* Convert hex string to Uint8Array
|
|
11
|
+
*/
|
|
12
|
+
export declare function hexToBytes(hex: string): Uint8Array;
|
|
13
|
+
/**
|
|
14
|
+
* Convert Uint8Array to base64
|
|
15
|
+
*/
|
|
16
|
+
export declare function bytesToBase64(bytes: Uint8Array): string;
|
|
17
|
+
/**
|
|
18
|
+
* Convert base64 to Uint8Array
|
|
19
|
+
*/
|
|
20
|
+
export declare function base64ToBytes(base64: string): Uint8Array;
|
|
21
|
+
/**
|
|
22
|
+
* Serialized session state for persistence
|
|
23
|
+
*/
|
|
24
|
+
export interface SessionState {
|
|
25
|
+
secretKey: string;
|
|
26
|
+
walletPublicKey?: string;
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* Session crypto for TON Connect v2
|
|
30
|
+
* Handles X25519 key exchange and NaCl box encryption/decryption
|
|
31
|
+
*/
|
|
32
|
+
export declare class SessionCrypto {
|
|
33
|
+
private keypair;
|
|
34
|
+
constructor(existingSecretKey?: Uint8Array);
|
|
35
|
+
/**
|
|
36
|
+
* Session ID = hex-encoded public key (used as client_id in bridge)
|
|
37
|
+
*/
|
|
38
|
+
get sessionId(): string;
|
|
39
|
+
/**
|
|
40
|
+
* Public key bytes
|
|
41
|
+
*/
|
|
42
|
+
get publicKey(): Uint8Array;
|
|
43
|
+
/**
|
|
44
|
+
* Secret key bytes (for persistence)
|
|
45
|
+
*/
|
|
46
|
+
get secretKey(): Uint8Array;
|
|
47
|
+
/**
|
|
48
|
+
* Encrypt a message for a recipient
|
|
49
|
+
* Format: nonce (24 bytes) + ciphertext
|
|
50
|
+
*/
|
|
51
|
+
encrypt(message: string, receiverPublicKey: Uint8Array): Uint8Array;
|
|
52
|
+
/**
|
|
53
|
+
* Decrypt a message from a sender
|
|
54
|
+
* Input format: nonce (24 bytes) + ciphertext
|
|
55
|
+
*/
|
|
56
|
+
decrypt(encryptedMessage: Uint8Array, senderPublicKey: Uint8Array): string;
|
|
57
|
+
/**
|
|
58
|
+
* Serialize session for persistence
|
|
59
|
+
*/
|
|
60
|
+
serialize(): SessionState;
|
|
61
|
+
/**
|
|
62
|
+
* Restore session from persisted state
|
|
63
|
+
*/
|
|
64
|
+
static fromState(state: SessionState): SessionCrypto;
|
|
65
|
+
}
|