@tonconnect/sdk 3.3.0-beta.0 → 3.3.0-beta.1

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/lib/cjs/index.cjs CHANGED
@@ -6,7 +6,7 @@ var protocol = require('@tonconnect/protocol');
6
6
  require('@tonconnect/isomorphic-eventsource');
7
7
  require('@tonconnect/isomorphic-fetch');
8
8
 
9
- /*! *****************************************************************************
9
+ /******************************************************************************
10
10
  Copyright (c) Microsoft Corporation.
11
11
 
12
12
  Permission to use, copy, modify, and/or distribute this software for any
@@ -20,6 +20,8 @@ LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
20
20
  OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
21
21
  PERFORMANCE OF THIS SOFTWARE.
22
22
  ***************************************************************************** */
23
+ /* global Reflect, Promise, SuppressedError, Symbol, Iterator */
24
+
23
25
 
24
26
  function __rest(s, e) {
25
27
  var t = {};
@@ -41,20 +43,25 @@ function __awaiter(thisArg, _arguments, P, generator) {
41
43
  function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
42
44
  step((generator = generator.apply(thisArg, _arguments || [])).next());
43
45
  });
44
- }
46
+ }
47
+
48
+ typeof SuppressedError === "function" ? SuppressedError : function (error, suppressed, message) {
49
+ var e = new Error(message);
50
+ return e.name = "SuppressedError", e.error = error, e.suppressed = suppressed, e;
51
+ };
45
52
 
46
53
  /**
47
54
  * Base class for TonConnect errors. You can check if the error was triggered by the @tonconnect/sdk using `err instanceof TonConnectError`.
48
55
  */
49
56
  class TonConnectError extends Error {
57
+ get info() {
58
+ return '';
59
+ }
50
60
  constructor(message, options) {
51
61
  super(message, options);
52
62
  this.message = `${TonConnectError.prefix} ${this.constructor.name}${this.info ? ': ' + this.info : ''}${message ? '\n' + message : ''}`;
53
63
  Object.setPrototypeOf(this, TonConnectError.prototype);
54
64
  }
55
- get info() {
56
- return '';
57
- }
58
65
  }
59
66
  TonConnectError.prefix = '[TON_CONNECT_SDK_ERROR]';
60
67
 
@@ -445,8 +452,8 @@ function createAbortController(signal) {
445
452
  * @param {CallForSuccessOptions} [options] - optional configuration options
446
453
  */
447
454
  function callForSuccess(fn, options) {
448
- var _a, _b;
449
455
  return __awaiter(this, void 0, void 0, function* () {
456
+ var _a, _b;
450
457
  const attempts = (_a = options === null || options === void 0 ? void 0 : options.attempts) !== null && _a !== void 0 ? _a : 10;
451
458
  const delayMs = (_b = options === null || options === void 0 ? void 0 : options.delayMs) !== null && _b !== void 0 ? _b : 200;
452
459
  const abortController = createAbortController(options === null || options === void 0 ? void 0 : options.signal);
@@ -617,6 +624,18 @@ function timeout(fn, options) {
617
624
  }
618
625
 
619
626
  class BridgeGateway {
627
+ get isReady() {
628
+ const eventSource = this.eventSource.current();
629
+ return (eventSource === null || eventSource === void 0 ? void 0 : eventSource.readyState) === EventSource.OPEN;
630
+ }
631
+ get isClosed() {
632
+ const eventSource = this.eventSource.current();
633
+ return (eventSource === null || eventSource === void 0 ? void 0 : eventSource.readyState) !== EventSource.OPEN;
634
+ }
635
+ get isConnecting() {
636
+ const eventSource = this.eventSource.current();
637
+ return (eventSource === null || eventSource === void 0 ? void 0 : eventSource.readyState) === EventSource.CONNECTING;
638
+ }
620
639
  constructor(storage, bridgeUrl, sessionId, listener, errorsListener) {
621
640
  this.bridgeUrl = bridgeUrl;
622
641
  this.sessionId = sessionId;
@@ -645,26 +664,14 @@ class BridgeGateway {
645
664
  }));
646
665
  this.bridgeGatewayStorage = new HttpBridgeGatewayStorage(storage, bridgeUrl);
647
666
  }
648
- get isReady() {
649
- const eventSource = this.eventSource.current();
650
- return (eventSource === null || eventSource === void 0 ? void 0 : eventSource.readyState) === EventSource.OPEN;
651
- }
652
- get isClosed() {
653
- const eventSource = this.eventSource.current();
654
- return (eventSource === null || eventSource === void 0 ? void 0 : eventSource.readyState) !== EventSource.OPEN;
655
- }
656
- get isConnecting() {
657
- const eventSource = this.eventSource.current();
658
- return (eventSource === null || eventSource === void 0 ? void 0 : eventSource.readyState) === EventSource.CONNECTING;
659
- }
660
667
  registerSession(options) {
661
668
  return __awaiter(this, void 0, void 0, function* () {
662
669
  yield this.eventSource.create(options === null || options === void 0 ? void 0 : options.signal, options === null || options === void 0 ? void 0 : options.openingDeadlineMS);
663
670
  });
664
671
  }
665
672
  send(message, receiver, topic, ttlOrOptions) {
666
- var _a;
667
673
  return __awaiter(this, void 0, void 0, function* () {
674
+ var _a;
668
675
  // TODO: remove deprecated method
669
676
  const options = {};
670
677
  if (typeof ttlOrOptions === 'number') {
@@ -1013,6 +1020,16 @@ class BridgeConnectionStorage {
1013
1020
  const PROTOCOL_VERSION = 2;
1014
1021
 
1015
1022
  class BridgeProvider {
1023
+ static fromStorage(storage) {
1024
+ return __awaiter(this, void 0, void 0, function* () {
1025
+ const bridgeConnectionStorage = new BridgeConnectionStorage(storage);
1026
+ const connection = yield bridgeConnectionStorage.getHttpConnection();
1027
+ if (isPendingConnectionHttp(connection)) {
1028
+ return new BridgeProvider(storage, connection.connectionSource);
1029
+ }
1030
+ return new BridgeProvider(storage, { bridgeUrl: connection.session.bridgeUrl });
1031
+ });
1032
+ }
1016
1033
  constructor(storage, walletConnectionSource) {
1017
1034
  this.storage = storage;
1018
1035
  this.walletConnectionSource = walletConnectionSource;
@@ -1027,16 +1044,6 @@ class BridgeProvider {
1027
1044
  this.defaultRetryTimeoutMS = 2000;
1028
1045
  this.connectionStorage = new BridgeConnectionStorage(storage);
1029
1046
  }
1030
- static fromStorage(storage) {
1031
- return __awaiter(this, void 0, void 0, function* () {
1032
- const bridgeConnectionStorage = new BridgeConnectionStorage(storage);
1033
- const connection = yield bridgeConnectionStorage.getHttpConnection();
1034
- if (isPendingConnectionHttp(connection)) {
1035
- return new BridgeProvider(storage, connection.connectionSource);
1036
- }
1037
- return new BridgeProvider(storage, { bridgeUrl: connection.session.bridgeUrl });
1038
- });
1039
- }
1040
1047
  connect(message, options) {
1041
1048
  var _a;
1042
1049
  const abortController = createAbortController(options === null || options === void 0 ? void 0 : options.signal);
@@ -1079,8 +1086,8 @@ class BridgeProvider {
1079
1086
  return this.generateUniversalLink(universalLink, message);
1080
1087
  }
1081
1088
  restoreConnection(options) {
1082
- var _a, _b;
1083
1089
  return __awaiter(this, void 0, void 0, function* () {
1090
+ var _a, _b;
1084
1091
  const abortController = createAbortController(options === null || options === void 0 ? void 0 : options.signal);
1085
1092
  (_a = this.abortController) === null || _a === void 0 ? void 0 : _a.abort();
1086
1093
  this.abortController = abortController;
@@ -1426,15 +1433,15 @@ function isJSBridgeWithMetadata(value) {
1426
1433
  * Uses as a fallback for localStorage in Safari's private mode.
1427
1434
  */
1428
1435
  class InMemoryStorage {
1429
- constructor() {
1430
- this.storage = {};
1431
- }
1432
1436
  static getInstance() {
1433
1437
  if (!InMemoryStorage.instance) {
1434
1438
  InMemoryStorage.instance = new InMemoryStorage();
1435
1439
  }
1436
1440
  return InMemoryStorage.instance;
1437
1441
  }
1442
+ constructor() {
1443
+ this.storage = {};
1444
+ }
1438
1445
  get length() {
1439
1446
  return Object.keys(this.storage).length;
1440
1447
  }
@@ -1529,19 +1536,6 @@ function isNodeJs() {
1529
1536
  }
1530
1537
 
1531
1538
  class InjectedProvider {
1532
- constructor(storage, injectedWalletKey) {
1533
- this.injectedWalletKey = injectedWalletKey;
1534
- this.type = 'injected';
1535
- this.unsubscribeCallback = null;
1536
- this.listenSubscriptions = false;
1537
- this.listeners = [];
1538
- const window = InjectedProvider.window;
1539
- if (!InjectedProvider.isWindowContainsWallet(window, injectedWalletKey)) {
1540
- throw new WalletNotInjectedError();
1541
- }
1542
- this.connectionStorage = new BridgeConnectionStorage(storage);
1543
- this.injectedWallet = window[injectedWalletKey].tonconnect;
1544
- }
1545
1539
  static fromStorage(storage) {
1546
1540
  return __awaiter(this, void 0, void 0, function* () {
1547
1541
  const bridgeConnectionStorage = new BridgeConnectionStorage(storage);
@@ -1583,6 +1577,19 @@ class InjectedProvider {
1583
1577
  typeof window[injectedWalletKey] === 'object' &&
1584
1578
  'tonconnect' in window[injectedWalletKey]);
1585
1579
  }
1580
+ constructor(storage, injectedWalletKey) {
1581
+ this.injectedWalletKey = injectedWalletKey;
1582
+ this.type = 'injected';
1583
+ this.unsubscribeCallback = null;
1584
+ this.listenSubscriptions = false;
1585
+ this.listeners = [];
1586
+ const window = InjectedProvider.window;
1587
+ if (!InjectedProvider.isWindowContainsWallet(window, injectedWalletKey)) {
1588
+ throw new WalletNotInjectedError();
1589
+ }
1590
+ this.connectionStorage = new BridgeConnectionStorage(storage);
1591
+ this.injectedWallet = window[injectedWalletKey].tonconnect;
1592
+ }
1586
1593
  connect(message) {
1587
1594
  this._connect(PROTOCOL_VERSION, message);
1588
1595
  }
@@ -1644,8 +1651,8 @@ class InjectedProvider {
1644
1651
  return () => (this.listeners = this.listeners.filter(listener => listener !== eventsCallback));
1645
1652
  }
1646
1653
  sendRequest(request, optionsOrOnRequestSent) {
1647
- var _a;
1648
1654
  return __awaiter(this, void 0, void 0, function* () {
1655
+ var _a;
1649
1656
  // TODO: remove deprecated method
1650
1657
  const options = {};
1651
1658
  if (typeof optionsOrOnRequestSent === 'function') {
@@ -2084,13 +2091,132 @@ const FALLBACK_WALLETS_LIST = [
2084
2091
  }
2085
2092
  ];
2086
2093
 
2094
+ let qaModeEnabled = false;
2095
+ let bannerObserver = null;
2096
+ function enableQaMode() {
2097
+ qaModeEnabled = true;
2098
+ console.warn('🚨 QA Mode enabled - validation is disabled. This is unsafe for production!');
2099
+ showQaModeBanner();
2100
+ startBannerObserver();
2101
+ addQaModeStyles();
2102
+ }
2103
+ function isQaModeEnabled() {
2104
+ return qaModeEnabled;
2105
+ }
2106
+ function logValidationError(message) {
2107
+ if (isQaModeEnabled()) {
2108
+ console.error(`[QA Mode] Validation failed: ${message}`);
2109
+ }
2110
+ }
2111
+ function showQaModeBanner() {
2112
+ if (typeof window === 'undefined')
2113
+ return;
2114
+ const existingBanner = document.getElementById('ton-connect-qa-banner');
2115
+ if (existingBanner)
2116
+ return;
2117
+ const banner = document.createElement('div');
2118
+ banner.id = 'ton-connect-qa-banner';
2119
+ banner.style.cssText = `
2120
+ position: fixed;
2121
+ top: 0;
2122
+ left: 0;
2123
+ right: 0;
2124
+ background: linear-gradient(90deg, #ff6b6b, #ff8e8e);
2125
+ color: white;
2126
+ padding: 12px 20px;
2127
+ text-align: center;
2128
+ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
2129
+ font-weight: 600;
2130
+ font-size: 14px;
2131
+ z-index: 999999;
2132
+ box-shadow: 0 2px 8px rgba(0,0,0,0.2);
2133
+ animation: slideDown 0.3s ease-out;
2134
+ user-select: none;
2135
+ pointer-events: none;
2136
+ `;
2137
+ banner.innerHTML = `
2138
+ 🚨 QA Mode Active - Validation Disabled (Unsafe for Production)
2139
+ `;
2140
+ // Add CSS animation
2141
+ const style = document.createElement('style');
2142
+ style.textContent = `
2143
+ @keyframes slideDown {
2144
+ from { transform: translateY(-100%); }
2145
+ to { transform: translateY(0); }
2146
+ }
2147
+ `;
2148
+ document.head.appendChild(style);
2149
+ document.body.appendChild(banner);
2150
+ addQaModeStyles();
2151
+ }
2152
+ function addQaModeStyles() {
2153
+ if (typeof window === 'undefined')
2154
+ return;
2155
+ const existingStyle = document.getElementById('ton-connect-qa-mode-styles');
2156
+ if (existingStyle)
2157
+ return;
2158
+ const style = document.createElement('style');
2159
+ style.id = 'ton-connect-qa-mode-styles';
2160
+ style.textContent = `
2161
+ body.qa-mode-active {
2162
+ padding-top: 48px !important;
2163
+ }
2164
+
2165
+ body.qa-mode-active header {
2166
+ margin-top: 48px !important;
2167
+ }
2168
+
2169
+ body.qa-mode-active .qa-mode-control {
2170
+ top: 128px !important;
2171
+ }
2172
+ `;
2173
+ document.head.appendChild(style);
2174
+ document.body.classList.add('qa-mode-active');
2175
+ }
2176
+ function startBannerObserver() {
2177
+ if (typeof window === 'undefined' || bannerObserver)
2178
+ return;
2179
+ bannerObserver = new MutationObserver((mutations) => {
2180
+ mutations.forEach((mutation) => {
2181
+ if (mutation.type === 'childList') {
2182
+ mutation.removedNodes.forEach((node) => {
2183
+ if (node.nodeType === Node.ELEMENT_NODE) {
2184
+ const element = node;
2185
+ if (element.id === 'ton-connect-qa-banner' && qaModeEnabled) {
2186
+ console.warn('QA Mode banner was removed, restoring...');
2187
+ setTimeout(() => showQaModeBanner(), 100);
2188
+ }
2189
+ else if (element.id === 'ton-connect-qa-mode-styles' && qaModeEnabled) {
2190
+ console.warn('QA Mode styles were removed, restoring...');
2191
+ setTimeout(() => addQaModeStyles(), 100);
2192
+ }
2193
+ }
2194
+ });
2195
+ }
2196
+ });
2197
+ });
2198
+ bannerObserver.observe(document.body, {
2199
+ childList: true,
2200
+ subtree: false
2201
+ });
2202
+ bannerObserver.observe(document.head, {
2203
+ childList: true,
2204
+ subtree: false
2205
+ });
2206
+ }
2207
+
2087
2208
  class WalletsListManager {
2088
2209
  constructor(options) {
2089
2210
  var _a;
2090
2211
  this.walletsListCache = null;
2091
2212
  this.walletsListCacheCreationTimestamp = null;
2092
- this.walletsListSource =
2093
- (_a = options === null || options === void 0 ? void 0 : options.walletsListSource) !== null && _a !== void 0 ? _a : 'https://raw.githubusercontent.com/ton-blockchain/wallets-list/main/wallets-v2.json';
2213
+ if (isQaModeEnabled()) {
2214
+ this.walletsListSource = 'https://raw.githubusercontent.com/ton-connect/wallets-list-staging/refs/heads/main/wallets-v2.json';
2215
+ }
2216
+ else {
2217
+ this.walletsListSource =
2218
+ (_a = options === null || options === void 0 ? void 0 : options.walletsListSource) !== null && _a !== void 0 ? _a : 'https://raw.githubusercontent.com/ton-blockchain/wallets-list/main/wallets-v2.json';
2219
+ }
2094
2220
  this.cacheTTLMs = options === null || options === void 0 ? void 0 : options.cacheTTLMs;
2095
2221
  }
2096
2222
  getWallets() {
@@ -2517,8 +2643,8 @@ class BrowserEventDispatcher {
2517
2643
  * @returns A promise that resolves when the event has been dispatched.
2518
2644
  */
2519
2645
  dispatchEvent(eventName, eventDetails) {
2520
- var _a;
2521
2646
  return __awaiter(this, void 0, void 0, function* () {
2647
+ var _a;
2522
2648
  const event = new CustomEvent(eventName, { detail: eventDetails });
2523
2649
  (_a = this.window) === null || _a === void 0 ? void 0 : _a.dispatchEvent(event);
2524
2650
  });
@@ -2531,8 +2657,8 @@ class BrowserEventDispatcher {
2531
2657
  * @returns A function that removes the listener.
2532
2658
  */
2533
2659
  addEventListener(eventName, listener, options) {
2534
- var _a;
2535
2660
  return __awaiter(this, void 0, void 0, function* () {
2661
+ var _a;
2536
2662
  (_a = this.window) === null || _a === void 0 ? void 0 : _a.addEventListener(eventName, listener, options);
2537
2663
  return () => {
2538
2664
  var _a;
@@ -2570,6 +2696,15 @@ class BrowserEventDispatcher {
2570
2696
  * @internal
2571
2697
  */
2572
2698
  class TonConnectTracker {
2699
+ /**
2700
+ * Version of the library.
2701
+ */
2702
+ get version() {
2703
+ return createVersionInfo({
2704
+ ton_connect_sdk_lib: this.tonConnectSdkVersion,
2705
+ ton_connect_ui_lib: this.tonConnectUiVersion
2706
+ });
2707
+ }
2573
2708
  constructor(options) {
2574
2709
  var _a;
2575
2710
  /**
@@ -2585,15 +2720,6 @@ class TonConnectTracker {
2585
2720
  this.tonConnectSdkVersion = options.tonConnectSdkVersion;
2586
2721
  this.init().catch();
2587
2722
  }
2588
- /**
2589
- * Version of the library.
2590
- */
2591
- get version() {
2592
- return createVersionInfo({
2593
- ton_connect_sdk_lib: this.tonConnectSdkVersion,
2594
- ton_connect_ui_lib: this.tonConnectUiVersion
2595
- });
2596
- }
2597
2723
  /**
2598
2724
  * Called once when the tracker is created and request version other libraries.
2599
2725
  */
@@ -2794,36 +2920,377 @@ class TonConnectTracker {
2794
2920
  }
2795
2921
  }
2796
2922
 
2797
- const tonConnectSdkVersion = "3.3.0-beta.0";
2923
+ const tonConnectSdkVersion = "3.3.0-beta.1";
2798
2924
 
2799
- class TonConnect {
2800
- constructor(options) {
2801
- this.walletsList = new WalletsListManager();
2802
- this._wallet = null;
2803
- this.provider = null;
2804
- this.statusChangeSubscriptions = [];
2805
- this.statusChangeErrorSubscriptions = [];
2806
- this.dappSettings = {
2807
- manifestUrl: (options === null || options === void 0 ? void 0 : options.manifestUrl) || getWebPageManifest(),
2808
- storage: (options === null || options === void 0 ? void 0 : options.storage) || new DefaultStorage()
2809
- };
2810
- this.walletsRequiredFeatures = options === null || options === void 0 ? void 0 : options.walletsRequiredFeatures;
2811
- this.walletsList = new WalletsListManager({
2812
- walletsListSource: options === null || options === void 0 ? void 0 : options.walletsListSource,
2813
- cacheTTLMs: options === null || options === void 0 ? void 0 : options.walletsListCacheTTLMs
2814
- });
2815
- this.tracker = new TonConnectTracker({
2816
- eventDispatcher: options === null || options === void 0 ? void 0 : options.eventDispatcher,
2817
- tonConnectSdkVersion: tonConnectSdkVersion
2818
- });
2819
- if (!this.dappSettings.manifestUrl) {
2820
- throw new DappMetadataError('Dapp tonconnect-manifest.json must be specified if window.location.origin is undefined. See more https://github.com/ton-connect/docs/blob/main/requests-responses.md#app-manifest');
2925
+ const noBounceableTag = 0x51;
2926
+ const testOnlyTag = 0x80;
2927
+ /**
2928
+ * Converts raw TON address to no-bounceable user-friendly format. [See details]{@link https://ton.org/docs/learn/overviews/addresses#user-friendly-address}
2929
+ * @param hexAddress raw TON address formatted as "0:<hex string without 0x>".
2930
+ * @param [testOnly=false] convert address to test-only form. [See details]{@link https://ton.org/docs/learn/overviews/addresses#user-friendly-address}
2931
+ */
2932
+ function toUserFriendlyAddress(hexAddress, testOnly = false) {
2933
+ const { wc, hex } = parseHexAddress(hexAddress);
2934
+ let tag = noBounceableTag;
2935
+ if (testOnly) {
2936
+ tag |= testOnlyTag;
2937
+ }
2938
+ const addr = new Int8Array(34);
2939
+ addr[0] = tag;
2940
+ addr[1] = wc;
2941
+ addr.set(hex, 2);
2942
+ const addressWithChecksum = new Uint8Array(36);
2943
+ addressWithChecksum.set(addr);
2944
+ addressWithChecksum.set(crc16(addr), 34);
2945
+ let addressBase64 = protocol.Base64.encode(addressWithChecksum);
2946
+ return addressBase64.replace(/\+/g, '-').replace(/\//g, '_');
2947
+ }
2948
+ /**
2949
+ * Validates if the address is in user-friendly format by attempting to parse it.
2950
+ * @param address address to validate
2951
+ * @returns true if the address is valid user-friendly format, false otherwise
2952
+ */
2953
+ function isValidUserFriendlyAddress(address) {
2954
+ try {
2955
+ parseUserFriendlyAddress(address);
2956
+ return true;
2957
+ }
2958
+ catch (_a) {
2959
+ return false;
2960
+ }
2961
+ }
2962
+ /**
2963
+ * Validates if the address is in raw hex format (e.g., "0:1234..." or "-1:1234...").
2964
+ * @param address address to validate
2965
+ * @returns true if the address is valid raw format, false otherwise
2966
+ */
2967
+ function isValidRawAddress(address) {
2968
+ try {
2969
+ parseHexAddress(address);
2970
+ return true;
2971
+ }
2972
+ catch (_a) {
2973
+ return false;
2974
+ }
2975
+ }
2976
+ /**
2977
+ * Parses user-friendly address and returns its components.
2978
+ * @param address user-friendly address
2979
+ * @returns parsed address components
2980
+ */
2981
+ function parseUserFriendlyAddress(address) {
2982
+ const base64 = address.replace(/-/g, '+').replace(/_/g, '/');
2983
+ let decoded;
2984
+ try {
2985
+ decoded = protocol.Base64.decode(base64).toUint8Array();
2986
+ }
2987
+ catch (_a) {
2988
+ throw new WrongAddressError(`Invalid base64 encoding in address: ${address}`);
2989
+ }
2990
+ if (decoded.length !== 36) {
2991
+ throw new WrongAddressError(`Invalid address length: ${address}`);
2992
+ }
2993
+ const addr = decoded.slice(0, 34);
2994
+ const checksum = decoded.slice(34);
2995
+ const calculatedChecksum = crc16(addr);
2996
+ if (!checksum.every((byte, i) => byte === calculatedChecksum[i])) {
2997
+ throw new WrongAddressError(`Invalid checksum in address: ${address}`);
2998
+ }
2999
+ const tag = addr[0];
3000
+ const wc = addr[1];
3001
+ const hex = addr.slice(2);
3002
+ if (wc !== 0 && wc !== -1) {
3003
+ throw new WrongAddressError(`Invalid workchain: ${wc}`);
3004
+ }
3005
+ const testOnly = (tag & testOnlyTag) !== 0;
3006
+ const isBounceable = (tag & 0x40) !== 0;
3007
+ return {
3008
+ wc,
3009
+ hex: Array.from(hex).map(b => b.toString(16).padStart(2, '0')).join(''),
3010
+ testOnly,
3011
+ isBounceable
3012
+ };
3013
+ }
3014
+ function parseHexAddress(hexAddress) {
3015
+ if (!hexAddress.includes(':')) {
3016
+ throw new WrongAddressError(`Wrong address ${hexAddress}. Address must include ":".`);
3017
+ }
3018
+ const parts = hexAddress.split(':');
3019
+ if (parts.length !== 2) {
3020
+ throw new WrongAddressError(`Wrong address ${hexAddress}. Address must include ":" only once.`);
3021
+ }
3022
+ const wc = parseInt(parts[0]);
3023
+ if (wc !== 0 && wc !== -1) {
3024
+ throw new WrongAddressError(`Wrong address ${hexAddress}. WC must be eq 0 or -1, but ${wc} received.`);
3025
+ }
3026
+ const hex = parts[1];
3027
+ if ((hex === null || hex === void 0 ? void 0 : hex.length) !== 64) {
3028
+ throw new WrongAddressError(`Wrong address ${hexAddress}. Hex part must be 64bytes length, but ${hex === null || hex === void 0 ? void 0 : hex.length} received.`);
3029
+ }
3030
+ return {
3031
+ wc,
3032
+ hex: hexToBytes(hex)
3033
+ };
3034
+ }
3035
+ function crc16(data) {
3036
+ const poly = 0x1021;
3037
+ let reg = 0;
3038
+ const message = new Uint8Array(data.length + 2);
3039
+ message.set(data);
3040
+ for (let byte of message) {
3041
+ let mask = 0x80;
3042
+ while (mask > 0) {
3043
+ reg <<= 1;
3044
+ if (byte & mask) {
3045
+ reg += 1;
3046
+ }
3047
+ mask >>= 1;
3048
+ if (reg > 0xffff) {
3049
+ reg &= 0xffff;
3050
+ reg ^= poly;
3051
+ }
2821
3052
  }
2822
- this.bridgeConnectionStorage = new BridgeConnectionStorage(this.dappSettings.storage);
2823
- if (!(options === null || options === void 0 ? void 0 : options.disableAutoPauseConnection)) {
2824
- this.addWindowFocusAndBlurSubscriptions();
3053
+ }
3054
+ return new Uint8Array([Math.floor(reg / 256), reg % 256]);
3055
+ }
3056
+ const toByteMap = {};
3057
+ for (let ord = 0; ord <= 0xff; ord++) {
3058
+ let s = ord.toString(16);
3059
+ if (s.length < 2) {
3060
+ s = '0' + s;
3061
+ }
3062
+ toByteMap[s] = ord;
3063
+ }
3064
+ function hexToBytes(hex) {
3065
+ hex = hex.toLowerCase();
3066
+ const length2 = hex.length;
3067
+ if (length2 % 2 !== 0) {
3068
+ throw new ParseHexError('Hex string must have length a multiple of 2: ' + hex);
3069
+ }
3070
+ const length = length2 / 2;
3071
+ const result = new Uint8Array(length);
3072
+ for (let i = 0; i < length; i++) {
3073
+ const doubled = i * 2;
3074
+ const hexSubstring = hex.substring(doubled, doubled + 2);
3075
+ if (!toByteMap.hasOwnProperty(hexSubstring)) {
3076
+ throw new ParseHexError('Invalid hex character: ' + hexSubstring);
3077
+ }
3078
+ result[i] = toByteMap[hexSubstring];
3079
+ }
3080
+ return result;
3081
+ }
3082
+
3083
+ const BASE64_REGEX = /^(?:[A-Za-z0-9+/]{4})*(?:[A-Za-z0-9+/]{2}==|[A-Za-z0-9+/]{3}=)?$/;
3084
+ const BOC_PREFIX = 'te6cc';
3085
+ const INTEGER_REGEX = /^-?\d+$/;
3086
+ const POSITIVE_INTEGER_REGEX = /^\d+$/;
3087
+ function isValidNumber(value) {
3088
+ return typeof value === 'number' && !isNaN(value);
3089
+ }
3090
+ function isValidString(value) {
3091
+ return typeof value === 'string' && value.length > 0;
3092
+ }
3093
+ function isValidBoc(value) {
3094
+ return typeof value === 'string' && BASE64_REGEX.test(value) && value.startsWith(BOC_PREFIX);
3095
+ }
3096
+ function isValidObject(value) {
3097
+ return typeof value === 'object' && value !== null && !Array.isArray(value);
3098
+ }
3099
+ function isValidArray(value) {
3100
+ return Array.isArray(value);
3101
+ }
3102
+ function hasExtraProperties(obj, allowedKeys) {
3103
+ return Object.keys(obj).some(key => !allowedKeys.includes(key));
3104
+ }
3105
+ function validateSendTransactionRequest(data) {
3106
+ console.log('[Validation Debug] validateSendTransactionRequest called');
3107
+ console.log('[Validation Debug] isQaModeEnabled():', isQaModeEnabled());
3108
+ if (!isValidObject(data)) {
3109
+ const error = "Request must be an object";
3110
+ logValidationError(error);
3111
+ const shouldReturnNull = isQaModeEnabled();
3112
+ console.log('[Validation Debug] Should return null:', shouldReturnNull);
3113
+ return shouldReturnNull ? null : error;
3114
+ }
3115
+ const allowedKeys = ['validUntil', 'network', 'from', 'messages'];
3116
+ if (hasExtraProperties(data, allowedKeys)) {
3117
+ const error = "Request contains extra properties";
3118
+ logValidationError(error);
3119
+ return isQaModeEnabled() ? null : error;
3120
+ }
3121
+ if (!isValidNumber(data.validUntil)) {
3122
+ const error = "Incorrect 'validUntil'";
3123
+ logValidationError(error);
3124
+ return isQaModeEnabled() ? null : error;
3125
+ }
3126
+ const now = Math.floor(Date.now() / 1000);
3127
+ const fiveMinutesFromNow = now + 300;
3128
+ if (data.validUntil > fiveMinutesFromNow) {
3129
+ console.warn(`validUntil (${data.validUntil}) is more than 5 minutes from now (${now})`);
3130
+ }
3131
+ if (data.network !== undefined) {
3132
+ if (!isValidString(data.network) || !/^[\d-]+$/.test(data.network)) {
3133
+ const error = "Invalid 'network' format";
3134
+ logValidationError(error);
3135
+ return isQaModeEnabled() ? null : error;
3136
+ }
3137
+ }
3138
+ if (data.from !== undefined) {
3139
+ if (!isValidString(data.from) || !isValidRawAddress(data.from)) {
3140
+ const error = "Invalid 'from' address format";
3141
+ logValidationError(error);
3142
+ return isQaModeEnabled() ? null : error;
2825
3143
  }
2826
3144
  }
3145
+ if (!isValidArray(data.messages) || data.messages.length === 0) {
3146
+ const error = "'messages' is required";
3147
+ logValidationError(error);
3148
+ return isQaModeEnabled() ? null : error;
3149
+ }
3150
+ for (let i = 0; i < data.messages.length; i++) {
3151
+ const message = data.messages[i];
3152
+ const messageError = validateTransactionMessage(message, i);
3153
+ if (messageError) {
3154
+ return messageError;
3155
+ }
3156
+ }
3157
+ return null;
3158
+ }
3159
+ function validateTransactionMessage(message, index) {
3160
+ if (!isValidObject(message)) {
3161
+ return `Message at index ${index} must be an object`;
3162
+ }
3163
+ const allowedKeys = ['address', 'amount', 'stateInit', 'payload', 'extraCurrency'];
3164
+ if (hasExtraProperties(message, allowedKeys)) {
3165
+ return `Message at index ${index} contains extra properties`;
3166
+ }
3167
+ if (!isValidString(message.address)) {
3168
+ return `'address' is required in message at index ${index}`;
3169
+ }
3170
+ if (!isValidUserFriendlyAddress(message.address)) {
3171
+ return `Wrong 'address' format in message at index ${index}`;
3172
+ }
3173
+ if (!isValidString(message.amount)) {
3174
+ return `'amount' is required in message at index ${index}`;
3175
+ }
3176
+ if (!/^[0-9]+$/.test(message.amount)) {
3177
+ return `Incorrect 'amount' in message at index ${index}`;
3178
+ }
3179
+ if (message.stateInit !== undefined) {
3180
+ if (!isValidString(message.stateInit) || !isValidBoc(message.stateInit)) {
3181
+ return `Invalid 'stateInit' in message at index ${index}`;
3182
+ }
3183
+ }
3184
+ if (message.payload !== undefined) {
3185
+ if (!isValidString(message.payload) || !isValidBoc(message.payload)) {
3186
+ return `Invalid 'payload' in message at index ${index}`;
3187
+ }
3188
+ }
3189
+ if (message.extraCurrency !== undefined) {
3190
+ if (!isValidObject(message.extraCurrency)) {
3191
+ return `Invalid 'extraCurrency' in message at index ${index}`;
3192
+ }
3193
+ for (const [key, value] of Object.entries(message.extraCurrency)) {
3194
+ if (!INTEGER_REGEX.test(key) || typeof value !== 'string' || !POSITIVE_INTEGER_REGEX.test(value)) {
3195
+ return `Invalid 'extraCurrency' format in message at index ${index}`;
3196
+ }
3197
+ }
3198
+ }
3199
+ return null;
3200
+ }
3201
+ function validateConnectAdditionalRequest(data) {
3202
+ if (!isValidObject(data)) {
3203
+ return "Request must be an object";
3204
+ }
3205
+ const allowedKeys = ['tonProof'];
3206
+ if (hasExtraProperties(data, allowedKeys)) {
3207
+ return "Request contains extra properties";
3208
+ }
3209
+ if (data.tonProof !== undefined && !isValidString(data.tonProof)) {
3210
+ return "Invalid 'tonProof'";
3211
+ }
3212
+ return null;
3213
+ }
3214
+ function validateSignDataPayload(data) {
3215
+ if (!isValidObject(data)) {
3216
+ return "Payload must be an object";
3217
+ }
3218
+ if (!isValidString(data.type)) {
3219
+ return "'type' is required";
3220
+ }
3221
+ switch (data.type) {
3222
+ case 'text':
3223
+ return validateSignDataPayloadText(data);
3224
+ case 'binary':
3225
+ return validateSignDataPayloadBinary(data);
3226
+ case 'cell':
3227
+ return validateSignDataPayloadCell(data);
3228
+ default:
3229
+ return "Invalid 'type' value";
3230
+ }
3231
+ }
3232
+ function validateSignDataPayloadText(data) {
3233
+ const allowedKeys = ['type', 'text', 'network', 'from'];
3234
+ if (hasExtraProperties(data, allowedKeys)) {
3235
+ return "Text payload contains extra properties";
3236
+ }
3237
+ if (!isValidString(data.text)) {
3238
+ return "'text' is required";
3239
+ }
3240
+ if (data.network !== undefined) {
3241
+ if (!isValidString(data.network) || !/^\d+$/.test(data.network)) {
3242
+ return "Invalid 'network' format";
3243
+ }
3244
+ }
3245
+ if (data.from !== undefined && !isValidString(data.from)) {
3246
+ return "Invalid 'from'";
3247
+ }
3248
+ return null;
3249
+ }
3250
+ function validateSignDataPayloadBinary(data) {
3251
+ const allowedKeys = ['type', 'bytes', 'network', 'from'];
3252
+ if (hasExtraProperties(data, allowedKeys)) {
3253
+ return "Binary payload contains extra properties";
3254
+ }
3255
+ if (!isValidString(data.bytes)) {
3256
+ return "'bytes' is required";
3257
+ }
3258
+ if (data.network !== undefined) {
3259
+ if (!isValidString(data.network) || !/^\d+$/.test(data.network)) {
3260
+ return "Invalid 'network' format";
3261
+ }
3262
+ }
3263
+ if (data.from !== undefined && !isValidString(data.from)) {
3264
+ return "Invalid 'from'";
3265
+ }
3266
+ return null;
3267
+ }
3268
+ function validateSignDataPayloadCell(data) {
3269
+ const allowedKeys = ['type', 'schema', 'cell', 'network', 'from'];
3270
+ if (hasExtraProperties(data, allowedKeys)) {
3271
+ return "Cell payload contains extra properties";
3272
+ }
3273
+ if (!isValidString(data.schema)) {
3274
+ return "'schema' is required";
3275
+ }
3276
+ if (!isValidString(data.cell)) {
3277
+ return "'cell' is required";
3278
+ }
3279
+ if (!isValidBoc(data.cell)) {
3280
+ return "Invalid 'cell' format (must be valid base64)";
3281
+ }
3282
+ if (data.network !== undefined) {
3283
+ if (!isValidString(data.network) || !/^\d+$/.test(data.network)) {
3284
+ return "Invalid 'network' format";
3285
+ }
3286
+ }
3287
+ if (data.from !== undefined && !isValidString(data.from)) {
3288
+ return "Invalid 'from'";
3289
+ }
3290
+ return null;
3291
+ }
3292
+
3293
+ class TonConnect {
2827
3294
  /**
2828
3295
  * Returns available wallets list.
2829
3296
  */
@@ -2853,6 +3320,33 @@ class TonConnect {
2853
3320
  this._wallet = value;
2854
3321
  this.statusChangeSubscriptions.forEach(callback => callback(this._wallet));
2855
3322
  }
3323
+ constructor(options) {
3324
+ this.walletsList = new WalletsListManager();
3325
+ this._wallet = null;
3326
+ this.provider = null;
3327
+ this.statusChangeSubscriptions = [];
3328
+ this.statusChangeErrorSubscriptions = [];
3329
+ this.dappSettings = {
3330
+ manifestUrl: (options === null || options === void 0 ? void 0 : options.manifestUrl) || getWebPageManifest(),
3331
+ storage: (options === null || options === void 0 ? void 0 : options.storage) || new DefaultStorage()
3332
+ };
3333
+ this.walletsRequiredFeatures = options === null || options === void 0 ? void 0 : options.walletsRequiredFeatures;
3334
+ this.walletsList = new WalletsListManager({
3335
+ walletsListSource: options === null || options === void 0 ? void 0 : options.walletsListSource,
3336
+ cacheTTLMs: options === null || options === void 0 ? void 0 : options.walletsListCacheTTLMs
3337
+ });
3338
+ this.tracker = new TonConnectTracker({
3339
+ eventDispatcher: options === null || options === void 0 ? void 0 : options.eventDispatcher,
3340
+ tonConnectSdkVersion: tonConnectSdkVersion
3341
+ });
3342
+ if (!this.dappSettings.manifestUrl) {
3343
+ throw new DappMetadataError('Dapp tonconnect-manifest.json must be specified if window.location.origin is undefined. See more https://github.com/ton-connect/docs/blob/main/requests-responses.md#app-manifest');
3344
+ }
3345
+ this.bridgeConnectionStorage = new BridgeConnectionStorage(this.dappSettings.storage);
3346
+ if (!(options === null || options === void 0 ? void 0 : options.disableAutoPauseConnection)) {
3347
+ this.addWindowFocusAndBlurSubscriptions();
3348
+ }
3349
+ }
2856
3350
  /**
2857
3351
  * Returns available wallets list.
2858
3352
  */
@@ -2892,6 +3386,17 @@ class TonConnect {
2892
3386
  options.openingDeadlineMS = requestOrOptions === null || requestOrOptions === void 0 ? void 0 : requestOrOptions.openingDeadlineMS;
2893
3387
  options.signal = requestOrOptions === null || requestOrOptions === void 0 ? void 0 : requestOrOptions.signal;
2894
3388
  }
3389
+ if (options.request) {
3390
+ const validationError = validateConnectAdditionalRequest(options.request);
3391
+ if (validationError) {
3392
+ if (isQaModeEnabled()) {
3393
+ console.error('ConnectAdditionalRequest validation failed: ' + validationError);
3394
+ }
3395
+ else {
3396
+ throw new TonConnectError('ConnectAdditionalRequest validation failed: ' + validationError);
3397
+ }
3398
+ }
3399
+ }
2895
3400
  if (this.connected) {
2896
3401
  throw new WalletAlreadyConnectedError();
2897
3402
  }
@@ -2918,8 +3423,8 @@ class TonConnect {
2918
3423
  * Try to restore existing session and reconnect to the corresponding wallet. Call it immediately when your app is loaded.
2919
3424
  */
2920
3425
  restoreConnection(options) {
2921
- var _a, _b;
2922
3426
  return __awaiter(this, void 0, void 0, function* () {
3427
+ var _a, _b;
2923
3428
  this.tracker.trackConnectionRestoringStarted();
2924
3429
  const abortController = createAbortController(options === null || options === void 0 ? void 0 : options.signal);
2925
3430
  (_a = this.abortController) === null || _a === void 0 ? void 0 : _a.abort();
@@ -3014,6 +3519,16 @@ class TonConnect {
3014
3519
  options.onRequestSent = optionsOrOnRequestSent === null || optionsOrOnRequestSent === void 0 ? void 0 : optionsOrOnRequestSent.onRequestSent;
3015
3520
  options.signal = optionsOrOnRequestSent === null || optionsOrOnRequestSent === void 0 ? void 0 : optionsOrOnRequestSent.signal;
3016
3521
  }
3522
+ // Validate transaction
3523
+ const validationError = validateSendTransactionRequest(transaction);
3524
+ if (validationError) {
3525
+ if (isQaModeEnabled()) {
3526
+ console.error('SendTransactionRequest validation failed: ' + validationError);
3527
+ }
3528
+ else {
3529
+ throw new TonConnectError('SendTransactionRequest validation failed: ' + validationError);
3530
+ }
3531
+ }
3017
3532
  const abortController = createAbortController(options === null || options === void 0 ? void 0 : options.signal);
3018
3533
  if (abortController.signal.aborted) {
3019
3534
  throw new TonConnectError('Transaction sending was aborted');
@@ -3049,6 +3564,16 @@ class TonConnect {
3049
3564
  if (abortController.signal.aborted) {
3050
3565
  throw new TonConnectError('data sending was aborted');
3051
3566
  }
3567
+ // Validate sign data
3568
+ const validationError = validateSignDataPayload(data);
3569
+ if (validationError) {
3570
+ if (isQaModeEnabled()) {
3571
+ console.error('SignDataPayload validation failed: ' + validationError);
3572
+ }
3573
+ else {
3574
+ throw new TonConnectError('SignDataPayload validation failed: ' + validationError);
3575
+ }
3576
+ }
3052
3577
  this.checkConnection();
3053
3578
  checkSignDataSupport(this.wallet.device.features, { requiredTypes: [data.type] });
3054
3579
  this.tracker.trackDataSentForSignature(this.wallet, data);
@@ -3069,8 +3594,8 @@ class TonConnect {
3069
3594
  * Disconnect form thw connected wallet and drop current session.
3070
3595
  */
3071
3596
  disconnect(options) {
3072
- var _a;
3073
3597
  return __awaiter(this, void 0, void 0, function* () {
3598
+ var _a;
3074
3599
  if (!this.connected) {
3075
3600
  throw new WalletNotConnectedError();
3076
3601
  }
@@ -3266,98 +3791,6 @@ TonConnect.isWalletInjected = (walletJSKey) => InjectedProvider.isWalletInjected
3266
3791
  */
3267
3792
  TonConnect.isInsideWalletBrowser = (walletJSKey) => InjectedProvider.isInsideWalletBrowser(walletJSKey);
3268
3793
 
3269
- const noBounceableTag = 0x51;
3270
- const testOnlyTag = 0x80;
3271
- /**
3272
- * Converts raw TON address to no-bounceable user-friendly format. [See details]{@link https://ton.org/docs/learn/overviews/addresses#user-friendly-address}
3273
- * @param hexAddress raw TON address formatted as "0:<hex string without 0x>".
3274
- * @param [testOnly=false] convert address to test-only form. [See details]{@link https://ton.org/docs/learn/overviews/addresses#user-friendly-address}
3275
- */
3276
- function toUserFriendlyAddress(hexAddress, testOnly = false) {
3277
- const { wc, hex } = parseHexAddress(hexAddress);
3278
- let tag = noBounceableTag;
3279
- if (testOnly) {
3280
- tag |= testOnlyTag;
3281
- }
3282
- const addr = new Int8Array(34);
3283
- addr[0] = tag;
3284
- addr[1] = wc;
3285
- addr.set(hex, 2);
3286
- const addressWithChecksum = new Uint8Array(36);
3287
- addressWithChecksum.set(addr);
3288
- addressWithChecksum.set(crc16(addr), 34);
3289
- let addressBase64 = protocol.Base64.encode(addressWithChecksum);
3290
- return addressBase64.replace(/\+/g, '-').replace(/\//g, '_');
3291
- }
3292
- function parseHexAddress(hexAddress) {
3293
- if (!hexAddress.includes(':')) {
3294
- throw new WrongAddressError(`Wrong address ${hexAddress}. Address must include ":".`);
3295
- }
3296
- const parts = hexAddress.split(':');
3297
- if (parts.length !== 2) {
3298
- throw new WrongAddressError(`Wrong address ${hexAddress}. Address must include ":" only once.`);
3299
- }
3300
- const wc = parseInt(parts[0]);
3301
- if (wc !== 0 && wc !== -1) {
3302
- throw new WrongAddressError(`Wrong address ${hexAddress}. WC must be eq 0 or -1, but ${wc} received.`);
3303
- }
3304
- const hex = parts[1];
3305
- if ((hex === null || hex === void 0 ? void 0 : hex.length) !== 64) {
3306
- throw new WrongAddressError(`Wrong address ${hexAddress}. Hex part must be 64bytes length, but ${hex === null || hex === void 0 ? void 0 : hex.length} received.`);
3307
- }
3308
- return {
3309
- wc,
3310
- hex: hexToBytes(hex)
3311
- };
3312
- }
3313
- function crc16(data) {
3314
- const poly = 0x1021;
3315
- let reg = 0;
3316
- const message = new Uint8Array(data.length + 2);
3317
- message.set(data);
3318
- for (let byte of message) {
3319
- let mask = 0x80;
3320
- while (mask > 0) {
3321
- reg <<= 1;
3322
- if (byte & mask) {
3323
- reg += 1;
3324
- }
3325
- mask >>= 1;
3326
- if (reg > 0xffff) {
3327
- reg &= 0xffff;
3328
- reg ^= poly;
3329
- }
3330
- }
3331
- }
3332
- return new Uint8Array([Math.floor(reg / 256), reg % 256]);
3333
- }
3334
- const toByteMap = {};
3335
- for (let ord = 0; ord <= 0xff; ord++) {
3336
- let s = ord.toString(16);
3337
- if (s.length < 2) {
3338
- s = '0' + s;
3339
- }
3340
- toByteMap[s] = ord;
3341
- }
3342
- function hexToBytes(hex) {
3343
- hex = hex.toLowerCase();
3344
- const length2 = hex.length;
3345
- if (length2 % 2 !== 0) {
3346
- throw new ParseHexError('Hex string must have length a multiple of 2: ' + hex);
3347
- }
3348
- const length = length2 / 2;
3349
- const result = new Uint8Array(length);
3350
- for (let i = 0; i < length; i++) {
3351
- const doubled = i * 2;
3352
- const hexSubstring = hex.substring(doubled, doubled + 2);
3353
- if (!toByteMap.hasOwnProperty(hexSubstring)) {
3354
- throw new ParseHexError('Invalid hex character: ' + hexSubstring);
3355
- }
3356
- result[i] = toByteMap[hexSubstring];
3357
- }
3358
- return result;
3359
- }
3360
-
3361
3794
  Object.defineProperty(exports, 'CHAIN', {
3362
3795
  enumerable: true,
3363
3796
  get: function () { return protocol.CHAIN; }
@@ -3413,7 +3846,9 @@ exports.createTransactionSignedEvent = createTransactionSignedEvent;
3413
3846
  exports.createTransactionSigningFailedEvent = createTransactionSigningFailedEvent;
3414
3847
  exports.createVersionInfo = createVersionInfo;
3415
3848
  exports.default = TonConnect;
3849
+ exports.enableQaMode = enableQaMode;
3416
3850
  exports.encodeTelegramUrlParameters = encodeTelegramUrlParameters;
3851
+ exports.isQaModeEnabled = isQaModeEnabled;
3417
3852
  exports.isTelegramUrl = isTelegramUrl;
3418
3853
  exports.isWalletInfoCurrentlyEmbedded = isWalletInfoCurrentlyEmbedded;
3419
3854
  exports.isWalletInfoCurrentlyInjected = isWalletInfoCurrentlyInjected;