@pizzaton/ton 0.1.0 → 0.1.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/README.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # @pizzaton/ton
2
2
 
3
- Typed TON protocol infrastructure for PizzaTon applications. The package provides reusable primitives for TON addresses, amounts, messages, providers, standard Jetton and NFT contracts, wallet versions, TON Connect, emulation, simulation, Merkle airdrops, and vesting calculations.
3
+ Typed TON protocol infrastructure for PizzaTon applications. The package provides reusable primitives for TON addresses, amounts, messages, providers, standard Jetton and NFT contracts, wallet versions, TON Connect proof verification, emulation, simulation, Merkle airdrops, and vesting calculations.
4
4
 
5
5
  It is intentionally protocol-focused. Application workflows, database persistence, authentication, launchpad rules, product-specific contracts, and UI components belong in the application or domain packages that consume this package.
6
6
 
@@ -33,8 +33,8 @@ npm install @ton/sandbox @ton-api/client react @types/react
33
33
  ```
34
34
 
35
35
  Install `@ton/sandbox` for `/emulator` and `/simulation`, `@ton-api/client` for
36
- `/indexer`, `/jetton/details`, and TonAPI simulation fallback, and `react` plus
37
- `@types/react` for `/react`.
36
+ `/indexer`, `/jetton/details`, and TonAPI simulation fallback, `@tonconnect/sdk`
37
+ for `/connect`, and `react` plus `@types/react` for `/react`.
38
38
 
39
39
  Use the narrowest subpath that contains the feature you need. This keeps browser bundles free of server-only and emulator dependencies.
40
40
 
@@ -45,6 +45,7 @@ Use the narrowest subpath that contains the feature you need. This keeps browser
45
45
  | `@pizzaton/ton/provider` | Provider interfaces for application-defined adapters |
46
46
  | `@pizzaton/ton/server` | Server-side `@ton/ton` HTTP provider |
47
47
  | `@pizzaton/ton/connect` | TON Connect adapter and network validation |
48
+ | `@pizzaton/ton/proof` | Server-side TON Connect proof, sign-data, and wallet state-init verification |
48
49
  | `@pizzaton/ton/react` | React hooks over the adapter |
49
50
  | `@pizzaton/ton/jetton` | Jetton payloads, data readers, wallet resolution, and implementation profiles |
50
51
  | `@pizzaton/ton/jetton/details` | Server-side token detail lookup through TonAPI and TonCenter |
@@ -67,6 +68,8 @@ package; there is no separate contracts workspace package.
67
68
 
68
69
  `@pizzaton/ton/server`, `@pizzaton/ton/jetton/details`, `@pizzaton/ton/emulator`, `@pizzaton/ton/simulation`, `@pizzaton/ton/indexer`, and `@pizzaton/ton/wallet` are feature subpaths outside the root entry point. Do not import them from browser components unless the bundler is explicitly configured for those dependencies. React components should use `@pizzaton/ton/connect`, `@pizzaton/ton/transactions`, and `@pizzaton/ton/react`.
69
70
 
71
+ `@pizzaton/ton/proof` is server-only. It verifies TON Connect proof and text sign-data signatures, timestamp and domain bindings, the wallet address derived from `state_init`, and public keys for standard WalletContract V1–V5R1 wallets. WalletTg uses an external public key, so pass `resolveDelegatedWalletPublicKey` to resolve that key through the application's trusted RPC adapter. Challenge storage, nonce consumption, session binding, and trusted-origin policy remain application responsibilities.
72
+
70
73
  ## Official TON SDK first
71
74
 
72
75
  The package uses the official TON libraries as its source of truth. `Address`, `Cell`, `Builder`, `ContractProvider`, `Sender`, `StateInit`, message codecs, dictionaries, and tuple types come from `@ton/core`. RPC clients, Jetton master/wallet readers, wallet-version contracts, and fee helpers come from `@ton/ton`. Cryptographic hashing comes from `@ton/crypto`; local execution comes from `@ton/sandbox`; wallet transport comes from `@tonconnect/sdk`.
@@ -294,6 +297,27 @@ const broadcastedBoc = await ton.sendTransaction(intent);
294
297
 
295
298
  The adapter checks that a wallet is connected and that its chain id matches the selected network. React applications can use `useTonWallet(adapter)` and `useTonTransaction(adapter)` from `@pizzaton/ton/react`; the transaction hook exposes `send`, `status`, `boc`, `error`, and `reset`.
296
299
 
300
+ ### TON Connect proof verification
301
+
302
+ Run proof verification on the server after validating the challenge and its origin. The verifier checks the wallet `state_init` and signature; the application remains responsible for one-time challenge and session rules.
303
+
304
+ ```ts
305
+ import { verifyTonConnectProof } from "@pizzaton/ton/proof";
306
+
307
+ const verified = await verifyTonConnectProof(
308
+ { address, proof, expectedDomain: "example.com", expectedPublicKey: publicKeyHex },
309
+ {
310
+ network: "mainnet",
311
+ resolveDelegatedWalletPublicKey: (walletAddress, network) =>
312
+ readWalletTgPublicKey(walletAddress, network),
313
+ },
314
+ );
315
+ ```
316
+
317
+ The delegated-wallet resolver is called only for the built-in WalletTg trampoline code hash or a code hash explicitly added by the application. Resolver errors propagate so applications can report RPC outages separately from invalid proofs.
318
+
319
+ For text messages returned by TON Connect `signData`, use `verifyTonConnectSignData` from the same subpath. It verifies the canonical text digest and wallet signature; bind its `expectedDomain` and payload to an application-owned, single-use challenge before calling it.
320
+
297
321
  ## Native transaction simulation
298
322
 
299
323
  The preferred simulator is native `@ton/sandbox` execution. It can run against a local blockchain or a pinned live-state snapshot hydrated through `TonClient4`; it does not call TonAPI for the primary path.
@@ -4,7 +4,13 @@ import { Address } from "@ton/core";
4
4
  export type TonAddress = string & {
5
5
  readonly __brand: "TonAddress";
6
6
  };
7
+ export interface ParsedFriendlyTonAddress {
8
+ readonly address: Address;
9
+ readonly isBounceable: boolean;
10
+ readonly isTestOnly: boolean;
11
+ }
7
12
  export declare function parseTonAddress(value: string | Address): Address;
13
+ export declare function parseFriendlyTonAddress(value: string): ParsedFriendlyTonAddress;
8
14
  export declare function toTonAddress(value: string | Address): TonAddress;
9
15
  export declare function addressesEqual(left: string | Address, right: string | Address): boolean;
10
16
  export declare function toFriendlyAddress(value: string | Address, options?: {
@@ -10,6 +10,14 @@ function parseTonAddress(value) {
10
10
  throw new TonPackageError("INVALID_ADDRESS", "Invalid TON address", { cause: String(error) });
11
11
  }
12
12
  }
13
+ function parseFriendlyTonAddress(value) {
14
+ if (typeof value !== "string") throw new TonPackageError("INVALID_ADDRESS", "Invalid friendly TON address");
15
+ try {
16
+ return Address.parseFriendly(value.trim());
17
+ } catch (error) {
18
+ throw new TonPackageError("INVALID_ADDRESS", "Invalid friendly TON address", { cause: String(error) });
19
+ }
20
+ }
13
21
  function toTonAddress(value) {
14
22
  return parseTonAddress(value).toRawString();
15
23
  }
@@ -42,4 +50,4 @@ function assertWorkchain(value, workchain) {
42
50
  invariant(address.workChain === workchain, "INVALID_ADDRESS", `Expected workchain ${workchain}, received ${address.workChain}`);
43
51
  }
44
52
  //#endregion
45
- export { addressesEqual, assertWorkchain, parseTonAddress, shortenTonAddress, toFriendlyAddress, toTonAddress };
53
+ export { addressesEqual, assertWorkchain, parseFriendlyTonAddress, parseTonAddress, shortenTonAddress, toFriendlyAddress, toTonAddress };
@@ -1,8 +1,8 @@
1
1
  import { TON_NETWORKS, TonNetwork, TonNetworkConfig, createNetworkConfig, explorerAddressUrl } from "./network.mjs";
2
- import { TonAddress, addressesEqual, assertWorkchain, parseTonAddress, shortenTonAddress, toFriendlyAddress, toTonAddress } from "./address.mjs";
2
+ import { ParsedFriendlyTonAddress, TonAddress, addressesEqual, assertWorkchain, parseFriendlyTonAddress, parseTonAddress, shortenTonAddress, toFriendlyAddress, toTonAddress } from "./address.mjs";
3
3
  import { TokenMetadata, parseTokenMetadata, parseTokenMetadataJson } from "./metadata.mjs";
4
4
  import { MAX_TON_COINS, assertTonAmount, formatTon, formatUnits, parseTon, parseUnits } from "./amount.mjs";
5
5
  import { bytesToBase64, bytesToHex, hexToBytes } from "./bytes.mjs";
6
6
  import { PIZZATON_MAINNET_ADDRESSES, PTJ_TOKEN_INFO } from "./constants.mjs";
7
7
  import { TonErrorCode, TonPackageError, invariant } from "./errors.mjs";
8
- export { MAX_TON_COINS, PIZZATON_MAINNET_ADDRESSES, PTJ_TOKEN_INFO, TON_NETWORKS, TokenMetadata, TonAddress, TonErrorCode, TonNetwork, TonNetworkConfig, TonPackageError, addressesEqual, assertTonAmount, assertWorkchain, bytesToBase64, bytesToHex, createNetworkConfig, explorerAddressUrl, formatTon, formatUnits, hexToBytes, invariant, parseTokenMetadata, parseTokenMetadataJson, parseTon, parseTonAddress, parseUnits, shortenTonAddress, toFriendlyAddress, toTonAddress };
8
+ export { MAX_TON_COINS, PIZZATON_MAINNET_ADDRESSES, PTJ_TOKEN_INFO, ParsedFriendlyTonAddress, TON_NETWORKS, TokenMetadata, TonAddress, TonErrorCode, TonNetwork, TonNetworkConfig, TonPackageError, addressesEqual, assertTonAmount, assertWorkchain, bytesToBase64, bytesToHex, createNetworkConfig, explorerAddressUrl, formatTon, formatUnits, hexToBytes, invariant, parseFriendlyTonAddress, parseTokenMetadata, parseTokenMetadataJson, parseTon, parseTonAddress, parseUnits, shortenTonAddress, toFriendlyAddress, toTonAddress };
@@ -1,8 +1,8 @@
1
1
  import { TonPackageError, invariant } from "./errors.mjs";
2
- import { addressesEqual, assertWorkchain, parseTonAddress, shortenTonAddress, toFriendlyAddress, toTonAddress } from "./address.mjs";
2
+ import { addressesEqual, assertWorkchain, parseFriendlyTonAddress, parseTonAddress, shortenTonAddress, toFriendlyAddress, toTonAddress } from "./address.mjs";
3
3
  import { MAX_TON_COINS, assertTonAmount, formatTon, formatUnits, parseTon, parseUnits } from "./amount.mjs";
4
4
  import { bytesToBase64, bytesToHex, hexToBytes } from "./bytes.mjs";
5
5
  import { PIZZATON_MAINNET_ADDRESSES, PTJ_TOKEN_INFO } from "./constants.mjs";
6
6
  import { parseTokenMetadata, parseTokenMetadataJson } from "./metadata.mjs";
7
7
  import { TON_NETWORKS, createNetworkConfig, explorerAddressUrl } from "./network.mjs";
8
- export { MAX_TON_COINS, PIZZATON_MAINNET_ADDRESSES, PTJ_TOKEN_INFO, TON_NETWORKS, TonPackageError, addressesEqual, assertTonAmount, assertWorkchain, bytesToBase64, bytesToHex, createNetworkConfig, explorerAddressUrl, formatTon, formatUnits, hexToBytes, invariant, parseTokenMetadata, parseTokenMetadataJson, parseTon, parseTonAddress, parseUnits, shortenTonAddress, toFriendlyAddress, toTonAddress };
8
+ export { MAX_TON_COINS, PIZZATON_MAINNET_ADDRESSES, PTJ_TOKEN_INFO, TON_NETWORKS, TonPackageError, addressesEqual, assertTonAmount, assertWorkchain, bytesToBase64, bytesToHex, createNetworkConfig, explorerAddressUrl, formatTon, formatUnits, hexToBytes, invariant, parseFriendlyTonAddress, parseTokenMetadata, parseTokenMetadataJson, parseTon, parseTonAddress, parseUnits, shortenTonAddress, toFriendlyAddress, toTonAddress };
package/dist/index.d.mts CHANGED
@@ -1,5 +1,5 @@
1
1
  import { TON_NETWORKS, TonNetwork, TonNetworkConfig, createNetworkConfig, explorerAddressUrl } from "./core/network.mjs";
2
- import { TonAddress, addressesEqual, assertWorkchain, parseTonAddress, shortenTonAddress, toFriendlyAddress, toTonAddress } from "./core/address.mjs";
2
+ import { ParsedFriendlyTonAddress, TonAddress, addressesEqual, assertWorkchain, parseFriendlyTonAddress, parseTonAddress, shortenTonAddress, toFriendlyAddress, toTonAddress } from "./core/address.mjs";
3
3
  import { TonConnectMessage, TonConnectTransaction, TonTransactionMessage, TransactionIntent, createTransactionIntent, toTonConnectTransaction, validateTransactionIntent } from "./transactions/index.mjs";
4
4
  import { readableTonConnectError } from "./connect/errors.mjs";
5
5
  import { TON_CONNECT_CHAIN_IDS, TonConnectAdapter, TonConnectClient, TonConnectWallet, createTonConnectAdapter, toWalletTransaction } from "./connect/index.mjs";
@@ -18,4 +18,4 @@ import { PIZZATON_MAINNET_ADDRESSES, PTJ_TOKEN_INFO } from "./core/constants.mjs
18
18
  import { TonErrorCode, TonPackageError, invariant } from "./core/errors.mjs";
19
19
  import "./core/index.mjs";
20
20
  import { VestingSchedule, claimableAmount, unlockedAmount, validateVestingSchedule } from "./vesting/index.mjs";
21
- export { CONTRACT_CATALOG, ContractAuditReference, ContractCatalogId, ContractDescriptor, ContractFamily, ContractModule, ContractModuleClient, ContractModuleClientOptions, ContractModuleRegistry, ContractProductionApproval, ContractReadiness, ContractSendOptions, ContractSourceReference, MAX_TON_COINS, PIZZATON_MAINNET_ADDRESSES, PTJ_TOKEN_INFO, TON_CONNECT_CHAIN_IDS, TON_NETWORKS, TokenMetadata, TonAccountState, TonAddress, TonConnectAdapter, TonConnectClient, TonConnectMessage, TonConnectTransaction, TonConnectWallet, TonContractClient, TonContractModule, TonErrorCode, TonGetMethodResult, TonNetwork, TonNetworkConfig, TonPackageError, TonProvider, TonTransactionMessage, TonTransactionSummary, TransactionIntent, UpgradePolicy, VestingSchedule, addressFlagDictionary, addressesEqual, assertProductionApproved, assertTonAmount, assertWorkchain, bytesToBase64, bytesToHex, claimableAmount, crc32, createContractDescriptor, createContractModule, createContractModuleRegistry, createNetworkConfig, createTonConnectAdapter, createTransactionIntent, explorerAddressUrl, formatTon, formatUnits, getContractDescriptor, hexToBytes, invariant, parseTokenMetadata, parseTokenMetadataJson, parseTon, parseTonAddress, parseUnits, readableTonConnectError, shortenTonAddress, toFriendlyAddress, toTonAddress, toTonConnectTransaction, toWalletTransaction, uint256CoinsDictionary, uint32FlagDictionary, unlockedAmount, validateTransactionIntent, validateVestingSchedule };
21
+ export { CONTRACT_CATALOG, ContractAuditReference, ContractCatalogId, ContractDescriptor, ContractFamily, ContractModule, ContractModuleClient, ContractModuleClientOptions, ContractModuleRegistry, ContractProductionApproval, ContractReadiness, ContractSendOptions, ContractSourceReference, MAX_TON_COINS, PIZZATON_MAINNET_ADDRESSES, PTJ_TOKEN_INFO, ParsedFriendlyTonAddress, TON_CONNECT_CHAIN_IDS, TON_NETWORKS, TokenMetadata, TonAccountState, TonAddress, TonConnectAdapter, TonConnectClient, TonConnectMessage, TonConnectTransaction, TonConnectWallet, TonContractClient, TonContractModule, TonErrorCode, TonGetMethodResult, TonNetwork, TonNetworkConfig, TonPackageError, TonProvider, TonTransactionMessage, TonTransactionSummary, TransactionIntent, UpgradePolicy, VestingSchedule, addressFlagDictionary, addressesEqual, assertProductionApproved, assertTonAmount, assertWorkchain, bytesToBase64, bytesToHex, claimableAmount, crc32, createContractDescriptor, createContractModule, createContractModuleRegistry, createNetworkConfig, createTonConnectAdapter, createTransactionIntent, explorerAddressUrl, formatTon, formatUnits, getContractDescriptor, hexToBytes, invariant, parseFriendlyTonAddress, parseTokenMetadata, parseTokenMetadataJson, parseTon, parseTonAddress, parseUnits, readableTonConnectError, shortenTonAddress, toFriendlyAddress, toTonAddress, toTonConnectTransaction, toWalletTransaction, uint256CoinsDictionary, uint32FlagDictionary, unlockedAmount, validateTransactionIntent, validateVestingSchedule };
package/dist/index.mjs CHANGED
@@ -1,5 +1,5 @@
1
1
  import { TonPackageError, invariant } from "./core/errors.mjs";
2
- import { addressesEqual, assertWorkchain, parseTonAddress, shortenTonAddress, toFriendlyAddress, toTonAddress } from "./core/address.mjs";
2
+ import { addressesEqual, assertWorkchain, parseFriendlyTonAddress, parseTonAddress, shortenTonAddress, toFriendlyAddress, toTonAddress } from "./core/address.mjs";
3
3
  import { MAX_TON_COINS, assertTonAmount, formatTon, formatUnits, parseTon, parseUnits } from "./core/amount.mjs";
4
4
  import { bytesToBase64, bytesToHex, hexToBytes } from "./core/bytes.mjs";
5
5
  import { createTransactionIntent, toTonConnectTransaction, validateTransactionIntent } from "./transactions/index.mjs";
@@ -16,4 +16,4 @@ import { PIZZATON_MAINNET_ADDRESSES, PTJ_TOKEN_INFO } from "./core/constants.mjs
16
16
  import { parseTokenMetadata, parseTokenMetadataJson } from "./core/metadata.mjs";
17
17
  import { TON_NETWORKS, createNetworkConfig, explorerAddressUrl } from "./core/network.mjs";
18
18
  import { claimableAmount, unlockedAmount, validateVestingSchedule } from "./vesting/index.mjs";
19
- export { CONTRACT_CATALOG, ContractModuleClient, MAX_TON_COINS, PIZZATON_MAINNET_ADDRESSES, PTJ_TOKEN_INFO, TON_CONNECT_CHAIN_IDS, TON_NETWORKS, TonContractClient, TonPackageError, addressFlagDictionary, addressesEqual, assertProductionApproved, assertTonAmount, assertWorkchain, bytesToBase64, bytesToHex, claimableAmount, crc32, createContractDescriptor, createContractModule, createContractModuleRegistry, createNetworkConfig, createTonConnectAdapter, createTransactionIntent, explorerAddressUrl, formatTon, formatUnits, getContractDescriptor, hexToBytes, invariant, parseTokenMetadata, parseTokenMetadataJson, parseTon, parseTonAddress, parseUnits, readableTonConnectError, shortenTonAddress, toFriendlyAddress, toTonAddress, toTonConnectTransaction, toWalletTransaction, uint256CoinsDictionary, uint32FlagDictionary, unlockedAmount, validateTransactionIntent, validateVestingSchedule };
19
+ export { CONTRACT_CATALOG, ContractModuleClient, MAX_TON_COINS, PIZZATON_MAINNET_ADDRESSES, PTJ_TOKEN_INFO, TON_CONNECT_CHAIN_IDS, TON_NETWORKS, TonContractClient, TonPackageError, addressFlagDictionary, addressesEqual, assertProductionApproved, assertTonAmount, assertWorkchain, bytesToBase64, bytesToHex, claimableAmount, crc32, createContractDescriptor, createContractModule, createContractModuleRegistry, createNetworkConfig, createTonConnectAdapter, createTransactionIntent, explorerAddressUrl, formatTon, formatUnits, getContractDescriptor, hexToBytes, invariant, parseFriendlyTonAddress, parseTokenMetadata, parseTokenMetadataJson, parseTon, parseTonAddress, parseUnits, readableTonConnectError, shortenTonAddress, toFriendlyAddress, toTonAddress, toTonConnectTransaction, toWalletTransaction, uint256CoinsDictionary, uint32FlagDictionary, unlockedAmount, validateTransactionIntent, validateVestingSchedule };
@@ -0,0 +1,62 @@
1
+ import { TonNetwork } from "../core/network.mjs";
2
+ import { Address } from "@ton/core";
3
+ //#region src/proof/index.d.ts
4
+ /** The trampoline wallet code used by WalletTg. */
5
+ export declare const TON_CONNECT_WALLET_TG_CODE_HASH = "9149ae51c1e4689710cebf7830297b16acfbadb363a920a537893e7ffeeca768";
6
+ export interface TonConnectProof {
7
+ readonly timestamp: number;
8
+ readonly domain: {
9
+ readonly lengthBytes: number;
10
+ readonly value: string;
11
+ };
12
+ readonly payload: string;
13
+ readonly signature: string;
14
+ readonly state_init: string;
15
+ }
16
+ export interface VerifyTonConnectProofInput {
17
+ readonly address: string;
18
+ readonly proof: TonConnectProof;
19
+ readonly expectedDomain: string;
20
+ readonly expectedPublicKey?: string | Uint8Array;
21
+ }
22
+ export interface VerifyTonConnectProofOptions {
23
+ readonly network: TonNetwork;
24
+ readonly now?: number;
25
+ readonly maxAgeSeconds?: number;
26
+ readonly maxDomainBytes?: number;
27
+ readonly maxPayloadBytes?: number;
28
+ /** Additional wallet code hashes whose public key is resolved externally. */
29
+ readonly delegatedWalletCodeHashes?: readonly string[];
30
+ /** Resolve the signing key for wallets that keep it outside state_init. */
31
+ readonly resolveDelegatedWalletPublicKey?: (address: Address, network: TonNetwork) => Promise<Uint8Array | null>;
32
+ }
33
+ export interface VerifyTonConnectSignDataInput {
34
+ readonly address: string;
35
+ readonly stateInit: string;
36
+ readonly timestamp: number;
37
+ readonly domain: {
38
+ readonly lengthBytes: number;
39
+ readonly value: string;
40
+ };
41
+ readonly payload: {
42
+ readonly text: string;
43
+ };
44
+ readonly signature: string;
45
+ readonly expectedDomain: string;
46
+ readonly expectedPublicKey?: string | Uint8Array;
47
+ }
48
+ /**
49
+ * Verifies the TON Connect `ton_proof` signature and its state-init wallet.
50
+ * WalletTg and additional delegated wallets can resolve their public key with
51
+ * `resolveDelegatedWalletPublicKey`; resolver errors are allowed to propagate
52
+ * so callers can distinguish an RPC outage from an invalid proof.
53
+ */
54
+ export declare function verifyTonConnectProof(input: VerifyTonConnectProofInput, options: VerifyTonConnectProofOptions): Promise<boolean>;
55
+ /**
56
+ * Verifies a text payload returned by TON Connect `signData`. This validates
57
+ * the canonical TON Connect digest, the state-init/address binding, and the
58
+ * expected signing domain. It does not replace the application's challenge,
59
+ * message-hash, or single-use checks.
60
+ */
61
+ export declare function verifyTonConnectSignData(input: VerifyTonConnectSignDataInput, options: VerifyTonConnectProofOptions): Promise<boolean>;
62
+ //#endregion
@@ -0,0 +1,227 @@
1
+ import { bytesToHex, hexToBytes } from "../core/bytes.mjs";
2
+ import { Address, Cell } from "@ton/core";
3
+ import { sha256, signVerify } from "@ton/crypto";
4
+ import { WalletContractV1R1, WalletContractV1R2, WalletContractV1R3, WalletContractV2R1, WalletContractV2R2, WalletContractV3R1, WalletContractV3R2, WalletContractV4, WalletContractV5R1, contractAddress as contractAddress$1, loadStateInit } from "@ton/ton";
5
+ //#region src/proof/index.ts
6
+ const MAX_STATE_INIT_BYTES = 12288;
7
+ const DEFAULT_MAX_PROOF_AGE_SECONDS = 600;
8
+ function isTonNetwork(value) {
9
+ return value === "mainnet" || value === "testnet";
10
+ }
11
+ /** The trampoline wallet code used by WalletTg. */
12
+ const TON_CONNECT_WALLET_TG_CODE_HASH = "9149ae51c1e4689710cebf7830297b16acfbadb363a920a537893e7ffeeca768";
13
+ const readV1V2 = (data) => {
14
+ const slice = data.beginParse();
15
+ slice.loadUint(32);
16
+ return slice.loadBuffer(32);
17
+ };
18
+ const readV3V4 = (data) => {
19
+ const slice = data.beginParse();
20
+ slice.loadUint(32);
21
+ slice.loadUint(32);
22
+ return slice.loadBuffer(32);
23
+ };
24
+ const readV5 = (data) => {
25
+ const slice = data.beginParse();
26
+ slice.loadBoolean();
27
+ slice.loadUint(32);
28
+ slice.loadUint(32);
29
+ return slice.loadBuffer(32);
30
+ };
31
+ const standardWallets = [
32
+ ...[
33
+ WalletContractV1R1,
34
+ WalletContractV1R2,
35
+ WalletContractV1R3,
36
+ WalletContractV2R1,
37
+ WalletContractV2R2
38
+ ].map((wallet) => ({
39
+ code: wallet.create({
40
+ workchain: 0,
41
+ publicKey: Buffer.alloc(32)
42
+ }).init.code,
43
+ readPublicKey: readV1V2
44
+ })),
45
+ ...[
46
+ WalletContractV3R1,
47
+ WalletContractV3R2,
48
+ WalletContractV4
49
+ ].map((wallet) => ({
50
+ code: wallet.create({
51
+ workchain: 0,
52
+ publicKey: Buffer.alloc(32)
53
+ }).init.code,
54
+ readPublicKey: readV3V4
55
+ })),
56
+ {
57
+ code: WalletContractV5R1.create({
58
+ workchain: 0,
59
+ publicKey: Buffer.alloc(32)
60
+ }).init.code,
61
+ readPublicKey: readV5
62
+ }
63
+ ];
64
+ function parseStateInitWallet(addressValue, stateInitBase64) {
65
+ if (stateInitBase64.length === 0 || stateInitBase64.length > Math.ceil(MAX_STATE_INIT_BYTES * 4 / 3)) return null;
66
+ let address;
67
+ let stateInit;
68
+ try {
69
+ const bytes = Buffer.from(stateInitBase64, "base64");
70
+ if (bytes.length === 0 || bytes.length > MAX_STATE_INIT_BYTES || bytes.toString("base64") !== stateInitBase64) return null;
71
+ const cells = Cell.fromBoc(bytes);
72
+ if (cells.length !== 1) return null;
73
+ const cell = cells[0];
74
+ if (!cell) return null;
75
+ stateInit = loadStateInit(cell.beginParse());
76
+ address = Address.parse(addressValue);
77
+ } catch {
78
+ return null;
79
+ }
80
+ if (!stateInit.code || !stateInit.data) return null;
81
+ if (!contractAddress$1(address.workChain, stateInit).equals(address)) return null;
82
+ const codeHash = bytesToHex(stateInit.code.hash());
83
+ for (const wallet of standardWallets) {
84
+ if (!wallet.code.equals(stateInit.code)) continue;
85
+ try {
86
+ const publicKey = wallet.readPublicKey(stateInit.data);
87
+ return publicKey.length === 32 ? {
88
+ address,
89
+ codeHash,
90
+ publicKey,
91
+ delegated: false
92
+ } : null;
93
+ } catch {
94
+ return null;
95
+ }
96
+ }
97
+ return {
98
+ address,
99
+ codeHash,
100
+ publicKey: null,
101
+ delegated: true
102
+ };
103
+ }
104
+ function decodeExpectedPublicKey(value) {
105
+ if (value === void 0) return null;
106
+ if (typeof value !== "string") return value.length === 32 ? value : null;
107
+ try {
108
+ const bytes = hexToBytes(value);
109
+ return bytes.length === 32 ? bytes : null;
110
+ } catch {
111
+ return null;
112
+ }
113
+ }
114
+ function decodeSignature(value) {
115
+ try {
116
+ const signature = Buffer.from(value, "base64");
117
+ return signature.length === 64 && signature.toString("base64") === value ? signature : null;
118
+ } catch {
119
+ return null;
120
+ }
121
+ }
122
+ async function resolveSigningPublicKey(parsed, expectedPublicKey, options) {
123
+ let publicKey = parsed.publicKey;
124
+ if (!publicKey) {
125
+ if (!options.resolveDelegatedWalletPublicKey) return null;
126
+ publicKey = await options.resolveDelegatedWalletPublicKey(parsed.address, options.network);
127
+ if (publicKey?.length !== 32) return null;
128
+ }
129
+ const expected = decodeExpectedPublicKey(expectedPublicKey);
130
+ if (expectedPublicKey !== void 0 && !expected) return null;
131
+ if (expected && !Buffer.from(publicKey).equals(Buffer.from(expected))) return null;
132
+ return publicKey;
133
+ }
134
+ /**
135
+ * Verifies the TON Connect `ton_proof` signature and its state-init wallet.
136
+ * WalletTg and additional delegated wallets can resolve their public key with
137
+ * `resolveDelegatedWalletPublicKey`; resolver errors are allowed to propagate
138
+ * so callers can distinguish an RPC outage from an invalid proof.
139
+ */
140
+ async function verifyTonConnectProof(input, options) {
141
+ if (!input || !options || !isTonNetwork(options.network) || typeof input.address !== "string" || typeof input.expectedDomain !== "string" || input.expectedDomain.length === 0 || !input.proof || typeof input.proof.state_init !== "string" || typeof input.proof.domain?.value !== "string" || !Number.isSafeInteger(input.proof.domain.lengthBytes) || !Number.isSafeInteger(input.proof.timestamp) || typeof input.proof.payload !== "string" || typeof input.proof.signature !== "string") return false;
142
+ const { proof } = input;
143
+ const domain = Buffer.from(proof.domain.value, "utf8");
144
+ const payload = Buffer.from(proof.payload, "utf8");
145
+ const maxDomainBytes = options.maxDomainBytes ?? 128;
146
+ const maxPayloadBytes = options.maxPayloadBytes ?? 128;
147
+ const now = options.now ?? Math.floor(Date.now() / 1e3);
148
+ const maxAgeSeconds = options.maxAgeSeconds ?? DEFAULT_MAX_PROOF_AGE_SECONDS;
149
+ if (proof.domain.value !== input.expectedDomain || proof.domain.lengthBytes !== domain.length || domain.length > maxDomainBytes || payload.length > maxPayloadBytes || proof.timestamp < 0 || !Number.isSafeInteger(now) || !Number.isSafeInteger(maxAgeSeconds) || maxAgeSeconds < 0 || Math.abs(now - proof.timestamp) > maxAgeSeconds) return false;
150
+ const signature = decodeSignature(proof.signature);
151
+ if (!signature) return false;
152
+ const parsed = parseStateInitWallet(input.address, input.proof.state_init);
153
+ if (!parsed) return false;
154
+ const delegatedHashes = /* @__PURE__ */ new Set([TON_CONNECT_WALLET_TG_CODE_HASH, ...(options.delegatedWalletCodeHashes ?? []).map((hash) => hash.toLowerCase())]);
155
+ if (parsed.delegated && !delegatedHashes.has(parsed.codeHash)) return false;
156
+ const publicKey = await resolveSigningPublicKey(parsed, input.expectedPublicKey, options);
157
+ if (!publicKey) return false;
158
+ const workchain = Buffer.alloc(4);
159
+ workchain.writeInt32BE(parsed.address.workChain, 0);
160
+ const domainLength = Buffer.alloc(4);
161
+ domainLength.writeUInt32LE(domain.length, 0);
162
+ const timestamp = Buffer.alloc(8);
163
+ timestamp.writeBigUInt64LE(BigInt(proof.timestamp), 0);
164
+ const message = Buffer.concat([
165
+ Buffer.from("ton-proof-item-v2/", "utf8"),
166
+ workchain,
167
+ parsed.address.hash,
168
+ domainLength,
169
+ domain,
170
+ timestamp,
171
+ payload
172
+ ]);
173
+ const messageHash = await sha256(message);
174
+ const digest = await sha256(Buffer.concat([
175
+ Buffer.from([255, 255]),
176
+ Buffer.from("ton-connect", "utf8"),
177
+ messageHash
178
+ ]));
179
+ return signVerify(digest, signature, Buffer.from(publicKey));
180
+ }
181
+ /**
182
+ * Verifies a text payload returned by TON Connect `signData`. This validates
183
+ * the canonical TON Connect digest, the state-init/address binding, and the
184
+ * expected signing domain. It does not replace the application's challenge,
185
+ * message-hash, or single-use checks.
186
+ */
187
+ async function verifyTonConnectSignData(input, options) {
188
+ if (!input || !options || !isTonNetwork(options.network) || typeof input.address !== "string" || typeof input.stateInit !== "string" || typeof input.expectedDomain !== "string" || input.expectedDomain.length === 0 || typeof input.domain?.value !== "string" || !Number.isSafeInteger(input.domain.lengthBytes) || !Number.isSafeInteger(input.timestamp) || typeof input.payload?.text !== "string" || typeof input.signature !== "string") return false;
189
+ const domain = Buffer.from(input.domain.value, "utf8");
190
+ const payload = Buffer.from(input.payload.text, "utf8");
191
+ const now = options.now ?? Math.floor(Date.now() / 1e3);
192
+ const maxAgeSeconds = options.maxAgeSeconds ?? DEFAULT_MAX_PROOF_AGE_SECONDS;
193
+ const maxPayloadBytes = options.maxPayloadBytes ?? 2e3;
194
+ if (input.timestamp < 0 || !Number.isSafeInteger(now) || !Number.isSafeInteger(maxAgeSeconds) || maxAgeSeconds < 0 || input.domain.value !== input.expectedDomain || input.domain.lengthBytes !== domain.length || domain.length > (options.maxDomainBytes ?? 128) || payload.length === 0 || payload.length > maxPayloadBytes || input.timestamp > now + 60 || now - input.timestamp > maxAgeSeconds) return false;
195
+ const signature = decodeSignature(input.signature);
196
+ if (!signature) return false;
197
+ const parsed = parseStateInitWallet(input.address, input.stateInit);
198
+ if (!parsed) return false;
199
+ const delegatedHashes = /* @__PURE__ */ new Set([TON_CONNECT_WALLET_TG_CODE_HASH, ...(options.delegatedWalletCodeHashes ?? []).map((hash) => hash.toLowerCase())]);
200
+ if (parsed.delegated && !delegatedHashes.has(parsed.codeHash)) return false;
201
+ const publicKey = await resolveSigningPublicKey(parsed, input.expectedPublicKey, options);
202
+ if (!publicKey) return false;
203
+ const workchain = Buffer.alloc(4);
204
+ workchain.writeInt32BE(parsed.address.workChain, 0);
205
+ const domainLength = Buffer.alloc(4);
206
+ domainLength.writeUInt32BE(domain.length, 0);
207
+ const timestamp = Buffer.alloc(8);
208
+ timestamp.writeBigUInt64BE(BigInt(input.timestamp), 0);
209
+ const payloadLength = Buffer.alloc(4);
210
+ payloadLength.writeUInt32BE(payload.length, 0);
211
+ const message = Buffer.concat([
212
+ Buffer.from([255, 255]),
213
+ Buffer.from("ton-connect/sign-data/", "utf8"),
214
+ workchain,
215
+ parsed.address.hash,
216
+ domainLength,
217
+ domain,
218
+ timestamp,
219
+ Buffer.from("txt", "utf8"),
220
+ payloadLength,
221
+ payload
222
+ ]);
223
+ const digest = await sha256(message);
224
+ return signVerify(digest, signature, Buffer.from(publicKey));
225
+ }
226
+ //#endregion
227
+ export { TON_CONNECT_WALLET_TG_CODE_HASH, verifyTonConnectProof, verifyTonConnectSignData };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pizzaton/ton",
3
- "version": "0.1.0",
3
+ "version": "0.1.1",
4
4
  "description": "Typed TON blockchain primitives, contract wrappers, and protocol adapters",
5
5
  "type": "module",
6
6
  "main": "./dist/index.mjs",
@@ -43,6 +43,10 @@
43
43
  "types": "./dist/provider/index.d.mts",
44
44
  "import": "./dist/provider/index.mjs"
45
45
  },
46
+ "./proof": {
47
+ "types": "./dist/proof/index.d.mts",
48
+ "import": "./dist/proof/index.mjs"
49
+ },
46
50
  "./indexer": {
47
51
  "types": "./dist/indexer/index.d.mts",
48
52
  "import": "./dist/indexer/index.mjs"
@@ -130,10 +134,10 @@
130
134
  "dependencies": {
131
135
  "@ton/crypto": "^3.3.0",
132
136
  "@ton/ton": "^16.3.0",
133
- "@tonconnect/sdk": "^4.0.2",
134
137
  "zod": "^4.5.4"
135
138
  },
136
139
  "peerDependencies": {
140
+ "@tonconnect/sdk": ">=4.0.2 <6.0.0",
137
141
  "@ton/core": "^0.63.1",
138
142
  "@types/node": ">=20",
139
143
  "@ton/sandbox": "^0.44.0",
@@ -142,6 +146,9 @@
142
146
  "@types/react": "^18.0.0 || ^19.0.0"
143
147
  },
144
148
  "peerDependenciesMeta": {
149
+ "@tonconnect/sdk": {
150
+ "optional": true
151
+ },
145
152
  "@ton/sandbox": {
146
153
  "optional": true
147
154
  },