@pizzaton/ton 0.0.0-stage → 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/CONTRACTS.md +239 -0
- package/README.md +505 -2
- package/dist/airdrop/index.d.mts +18 -0
- package/dist/airdrop/index.mjs +86 -0
- package/dist/connect/errors.d.mts +4 -0
- package/dist/connect/errors.mjs +21 -0
- package/dist/connect/index.d.mts +23 -0
- package/dist/connect/index.mjs +34 -0
- package/dist/contracts/catalog/index.d.mts +361 -0
- package/dist/contracts/catalog/index.mjs +403 -0
- package/dist/contracts/client.d.mts +18 -0
- package/dist/contracts/client.mjs +31 -0
- package/dist/contracts/codecs.d.mts +14 -0
- package/dist/contracts/codecs.mjs +61 -0
- package/dist/contracts/hipo.d.mts +138 -0
- package/dist/contracts/hipo.mjs +198 -0
- package/dist/contracts/index.d.mts +23 -0
- package/dist/contracts/index.mjs +20 -0
- package/dist/contracts/metadata.d.mts +38 -0
- package/dist/contracts/metadata.mjs +50 -0
- package/dist/contracts/module-client.d.mts +20 -0
- package/dist/contracts/module-client.mjs +34 -0
- package/dist/contracts/module.d.mts +8 -0
- package/dist/contracts/module.mjs +11 -0
- package/dist/contracts/wrappers/base.d.mts +18 -0
- package/dist/contracts/wrappers/base.mjs +31 -0
- package/dist/contracts/wrappers/index.d.mts +12 -0
- package/dist/contracts/wrappers/index.mjs +12 -0
- package/dist/contracts/wrappers/jvault-protocols.d.mts +206 -0
- package/dist/contracts/wrappers/jvault-protocols.mjs +432 -0
- package/dist/contracts/wrappers/jvault-support.d.mts +135 -0
- package/dist/contracts/wrappers/jvault-support.mjs +269 -0
- package/dist/contracts/wrappers/jvault.d.mts +123 -0
- package/dist/contracts/wrappers/jvault.mjs +258 -0
- package/dist/contracts/wrappers/memeland.d.mts +253 -0
- package/dist/contracts/wrappers/memeland.mjs +396 -0
- package/dist/contracts/wrappers/multix.d.mts +92 -0
- package/dist/contracts/wrappers/multix.mjs +156 -0
- package/dist/contracts/wrappers/pizzabox.d.mts +66 -0
- package/dist/contracts/wrappers/pizzabox.mjs +105 -0
- package/dist/contracts/wrappers/ton-raffles.d.mts +215 -0
- package/dist/contracts/wrappers/ton-raffles.mjs +398 -0
- package/dist/contracts/wrappers/tonresistor.d.mts +189 -0
- package/dist/contracts/wrappers/tonresistor.mjs +276 -0
- package/dist/core/address.d.mts +31 -0
- package/dist/core/address.mjs +53 -0
- package/dist/core/amount.d.mts +8 -0
- package/dist/core/amount.mjs +34 -0
- package/dist/core/bytes.d.mts +5 -0
- package/dist/core/bytes.mjs +18 -0
- package/dist/core/constants.d.mts +14 -0
- package/dist/core/constants.mjs +15 -0
- package/dist/core/errors.d.mts +9 -0
- package/dist/core/errors.mjs +16 -0
- package/dist/core/http.mjs +39 -0
- package/dist/core/index.d.mts +8 -0
- package/dist/core/index.mjs +8 -0
- package/dist/core/metadata.d.mts +12 -0
- package/dist/core/metadata.mjs +35 -0
- package/dist/core/network.d.mts +24 -0
- package/dist/core/network.mjs +41 -0
- package/dist/emulator/index.d.mts +20 -0
- package/dist/emulator/index.mjs +24 -0
- package/dist/index.d.mts +21 -0
- package/dist/index.mjs +19 -0
- package/dist/indexer/index.d.mts +90 -0
- package/dist/indexer/index.mjs +279 -0
- package/dist/jetton/details.d.mts +27 -0
- package/dist/jetton/details.mjs +116 -0
- package/dist/jetton/index.d.mts +54 -0
- package/dist/jetton/index.mjs +92 -0
- package/dist/jetton/versions.d.mts +12 -0
- package/dist/jetton/versions.mjs +50 -0
- package/dist/nft/collection.d.mts +57 -0
- package/dist/nft/collection.mjs +148 -0
- package/dist/nft/content.d.mts +34 -0
- package/dist/nft/content.mjs +142 -0
- package/dist/nft/index.d.mts +35 -0
- package/dist/nft/index.mjs +51 -0
- package/dist/nft/versions.d.mts +11 -0
- package/dist/nft/versions.mjs +29 -0
- package/dist/proof/index.d.mts +62 -0
- package/dist/proof/index.mjs +227 -0
- package/dist/provider/index.d.mts +32 -0
- package/dist/provider/index.mjs +1 -0
- package/dist/react/index.d.mts +13 -0
- package/dist/react/index.mjs +48 -0
- package/dist/server/index.d.mts +11 -0
- package/dist/server/index.mjs +55 -0
- package/dist/simulation/index.d.mts +112 -0
- package/dist/simulation/index.mjs +310 -0
- package/dist/transactions/index.d.mts +33 -0
- package/dist/transactions/index.mjs +51 -0
- package/dist/vesting/index.d.mts +12 -0
- package/dist/vesting/index.mjs +25 -0
- package/dist/wallet/index.d.mts +23 -0
- package/dist/wallet/index.mjs +71 -0
- package/package.json +176 -5
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import { invariant } from "../core/errors.mjs";
|
|
2
|
+
import { MAX_TON_COINS, assertTonAmount } from "../core/amount.mjs";
|
|
3
|
+
import { NFT_ONCHAIN_CONTENT_KEYS, assertCell, buildOnchainMetadata, nftContentFromCell, nftContentToCell, parseNftContentSlice, royaltyParamsToCell, storeNftContent } from "./content.mjs";
|
|
4
|
+
import { NFT_COLLECTION_OPCODES, NftCollectionContract, buildNftBatchDeployBody, buildNftChangeOwnerBody, buildNftDeployItemBody, buildNftRoyaltyParamsBody, collectionConfigToCell } from "./collection.mjs";
|
|
5
|
+
import { NFT_IMPLEMENTATION_PROFILES } from "./versions.mjs";
|
|
6
|
+
import { SendMode, beginCell } from "@ton/core";
|
|
7
|
+
//#region src/nft/index.ts
|
|
8
|
+
const NFT_OPCODES = {
|
|
9
|
+
transfer: 1607220500,
|
|
10
|
+
ownershipAssigned: 85167505,
|
|
11
|
+
getStaticData: 801842850
|
|
12
|
+
};
|
|
13
|
+
var NftItemContract = class NftItemContract {
|
|
14
|
+
address;
|
|
15
|
+
constructor(address) {
|
|
16
|
+
this.address = address;
|
|
17
|
+
}
|
|
18
|
+
static create(address) {
|
|
19
|
+
return new NftItemContract(address);
|
|
20
|
+
}
|
|
21
|
+
async getData(provider) {
|
|
22
|
+
const { stack } = await provider.get("get_nft_data", []);
|
|
23
|
+
return {
|
|
24
|
+
initialized: stack.readBigNumber() !== 0n,
|
|
25
|
+
index: stack.readBigNumber(),
|
|
26
|
+
collection: stack.readAddress(),
|
|
27
|
+
owner: stack.readAddressOpt(),
|
|
28
|
+
content: stack.readCellOpt()
|
|
29
|
+
};
|
|
30
|
+
}
|
|
31
|
+
async sendTransfer(provider, via, input, value) {
|
|
32
|
+
assertTonAmount(value, "NFT transfer value");
|
|
33
|
+
await provider.internal(via, {
|
|
34
|
+
value,
|
|
35
|
+
body: buildNftTransferBody(input),
|
|
36
|
+
sendMode: SendMode.PAY_GAS_SEPARATELY
|
|
37
|
+
});
|
|
38
|
+
}
|
|
39
|
+
};
|
|
40
|
+
function buildNftTransferBody(input) {
|
|
41
|
+
const queryId = input.queryId ?? 0n;
|
|
42
|
+
const forwardAmount = input.forwardAmount ?? 0n;
|
|
43
|
+
invariant(queryId >= 0n && queryId < 2n ** 64n, "INVALID_TRANSACTION", "Query id is out of range");
|
|
44
|
+
invariant(forwardAmount >= 0n && forwardAmount <= MAX_TON_COINS, "INVALID_AMOUNT", "Forward amount is outside the TON coin range");
|
|
45
|
+
const builder = beginCell().storeUint(NFT_OPCODES.transfer, 32).storeUint(queryId, 64).storeAddress(input.newOwner).storeAddress(input.responseDestination).storeMaybeRef(input.customPayload).storeCoins(forwardAmount);
|
|
46
|
+
if (input.forwardPayload) builder.storeBit(1).storeRef(input.forwardPayload);
|
|
47
|
+
else builder.storeBit(0);
|
|
48
|
+
return builder.endCell();
|
|
49
|
+
}
|
|
50
|
+
//#endregion
|
|
51
|
+
export { NFT_COLLECTION_OPCODES, NFT_IMPLEMENTATION_PROFILES, NFT_ONCHAIN_CONTENT_KEYS, NFT_OPCODES, NftCollectionContract, NftItemContract, assertCell, buildNftBatchDeployBody, buildNftChangeOwnerBody, buildNftDeployItemBody, buildNftRoyaltyParamsBody, buildNftTransferBody, buildOnchainMetadata, collectionConfigToCell, nftContentFromCell, nftContentToCell, parseNftContentSlice, royaltyParamsToCell, storeNftContent };
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
//#region src/nft/versions.d.ts
|
|
2
|
+
export type NftCollectionImplementationVersion = "default" | "compressed";
|
|
3
|
+
export type NftItemImplementationVersion = "default" | "sbt" | "single";
|
|
4
|
+
export interface NftImplementationProfile {
|
|
5
|
+
readonly version: NftCollectionImplementationVersion | NftItemImplementationVersion;
|
|
6
|
+
readonly transferable: boolean;
|
|
7
|
+
readonly supportsCollectionIndexing: boolean;
|
|
8
|
+
readonly supportsMerkleSelfDeployment: boolean;
|
|
9
|
+
}
|
|
10
|
+
export declare const NFT_IMPLEMENTATION_PROFILES: Readonly<Record<NftImplementationProfile["version"], NftImplementationProfile>>;
|
|
11
|
+
//#endregion
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
//#region src/nft/versions.ts
|
|
2
|
+
const NFT_IMPLEMENTATION_PROFILES = Object.freeze({
|
|
3
|
+
default: {
|
|
4
|
+
version: "default",
|
|
5
|
+
transferable: true,
|
|
6
|
+
supportsCollectionIndexing: true,
|
|
7
|
+
supportsMerkleSelfDeployment: false
|
|
8
|
+
},
|
|
9
|
+
compressed: {
|
|
10
|
+
version: "compressed",
|
|
11
|
+
transferable: true,
|
|
12
|
+
supportsCollectionIndexing: true,
|
|
13
|
+
supportsMerkleSelfDeployment: true
|
|
14
|
+
},
|
|
15
|
+
sbt: {
|
|
16
|
+
version: "sbt",
|
|
17
|
+
transferable: false,
|
|
18
|
+
supportsCollectionIndexing: true,
|
|
19
|
+
supportsMerkleSelfDeployment: false
|
|
20
|
+
},
|
|
21
|
+
single: {
|
|
22
|
+
version: "single",
|
|
23
|
+
transferable: true,
|
|
24
|
+
supportsCollectionIndexing: false,
|
|
25
|
+
supportsMerkleSelfDeployment: false
|
|
26
|
+
}
|
|
27
|
+
});
|
|
28
|
+
//#endregion
|
|
29
|
+
export { NFT_IMPLEMENTATION_PROFILES };
|
|
@@ -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 };
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import { TonNetwork } from "../core/network.mjs";
|
|
2
|
+
import { Address, Contract, ContractProvider, OpenedContract, StateInit, TupleItem, TupleReader } from "@ton/core";
|
|
3
|
+
//#region src/provider/index.d.ts
|
|
4
|
+
export interface TonAccountState {
|
|
5
|
+
readonly status: "active" | "uninitialized" | "frozen" | "non-existing";
|
|
6
|
+
readonly balance: bigint;
|
|
7
|
+
}
|
|
8
|
+
export interface TonTransactionSummary {
|
|
9
|
+
readonly lt: bigint;
|
|
10
|
+
readonly hash: string;
|
|
11
|
+
readonly timestamp: number;
|
|
12
|
+
}
|
|
13
|
+
export interface TonGetMethodResult {
|
|
14
|
+
readonly gasUsed: number;
|
|
15
|
+
readonly stack: TupleReader;
|
|
16
|
+
readonly exitCode?: number;
|
|
17
|
+
}
|
|
18
|
+
export interface TonProvider {
|
|
19
|
+
readonly network: TonNetwork;
|
|
20
|
+
openContract<T extends Contract>(contract: T): OpenedContract<T>;
|
|
21
|
+
contractProvider(address: Address, init?: StateInit | null): ContractProvider;
|
|
22
|
+
getBalance(address: Address): Promise<bigint>;
|
|
23
|
+
getAccountState(address: Address): Promise<TonAccountState>;
|
|
24
|
+
runGetMethod(address: Address, method: string, args?: readonly TupleItem[]): Promise<TonGetMethodResult>;
|
|
25
|
+
getTransactions(address: Address, options?: {
|
|
26
|
+
readonly limit?: number;
|
|
27
|
+
readonly lt?: bigint;
|
|
28
|
+
readonly hash?: string;
|
|
29
|
+
}): Promise<readonly TonTransactionSummary[]>;
|
|
30
|
+
isContractDeployed(address: Address): Promise<boolean>;
|
|
31
|
+
}
|
|
32
|
+
//#endregion
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { TransactionIntent } from "../transactions/index.mjs";
|
|
2
|
+
import { TonConnectAdapter, TonConnectWallet } from "../connect/index.mjs";
|
|
3
|
+
//#region src/react/index.d.ts
|
|
4
|
+
export declare function useTonWallet(adapter: TonConnectAdapter | null): TonConnectWallet | null;
|
|
5
|
+
export type TonTransactionStatus = "idle" | "sending" | "sent" | "error";
|
|
6
|
+
export declare function useTonTransaction(adapter: TonConnectAdapter | null): {
|
|
7
|
+
readonly send: (intent: TransactionIntent) => Promise<string | null>;
|
|
8
|
+
readonly status: TonTransactionStatus;
|
|
9
|
+
readonly boc: string | null;
|
|
10
|
+
readonly error: Error | null;
|
|
11
|
+
readonly reset: () => void;
|
|
12
|
+
};
|
|
13
|
+
//#endregion
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import { useCallback, useEffect, useRef, useState, useSyncExternalStore } from "react";
|
|
2
|
+
//#region src/react/index.ts
|
|
3
|
+
function useTonWallet(adapter) {
|
|
4
|
+
return useSyncExternalStore((listener) => adapter?.subscribe(listener) ?? (() => void 0), () => adapter?.getWallet() ?? null, () => null);
|
|
5
|
+
}
|
|
6
|
+
function useTonTransaction(adapter) {
|
|
7
|
+
const [status, setStatus] = useState("idle");
|
|
8
|
+
const [boc, setBoc] = useState(null);
|
|
9
|
+
const [error, setError] = useState(null);
|
|
10
|
+
const sending = useRef(false);
|
|
11
|
+
const reset = useCallback(() => {
|
|
12
|
+
setStatus("idle");
|
|
13
|
+
setBoc(null);
|
|
14
|
+
setError(null);
|
|
15
|
+
}, []);
|
|
16
|
+
const send = useCallback(async (intent) => {
|
|
17
|
+
if (!adapter) throw new Error("A TON Connect adapter is required");
|
|
18
|
+
if (sending.current) throw new Error("A TON transaction is already being sent");
|
|
19
|
+
sending.current = true;
|
|
20
|
+
setStatus("sending");
|
|
21
|
+
setError(null);
|
|
22
|
+
try {
|
|
23
|
+
const result = await adapter.sendTransaction(intent);
|
|
24
|
+
setBoc(result);
|
|
25
|
+
setStatus("sent");
|
|
26
|
+
return result;
|
|
27
|
+
} catch (cause) {
|
|
28
|
+
const nextError = cause instanceof Error ? cause : new Error(String(cause));
|
|
29
|
+
setError(nextError);
|
|
30
|
+
setStatus("error");
|
|
31
|
+
return null;
|
|
32
|
+
} finally {
|
|
33
|
+
sending.current = false;
|
|
34
|
+
}
|
|
35
|
+
}, [adapter]);
|
|
36
|
+
useEffect(() => {
|
|
37
|
+
if (!adapter) reset();
|
|
38
|
+
}, [adapter, reset]);
|
|
39
|
+
return {
|
|
40
|
+
send,
|
|
41
|
+
status,
|
|
42
|
+
boc,
|
|
43
|
+
error,
|
|
44
|
+
reset
|
|
45
|
+
};
|
|
46
|
+
}
|
|
47
|
+
//#endregion
|
|
48
|
+
export { useTonTransaction, useTonWallet };
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { TonNetwork } from "../core/network.mjs";
|
|
2
|
+
import { TonProvider } from "../provider/index.mjs";
|
|
3
|
+
//#region src/server/index.d.ts
|
|
4
|
+
export interface TonHttpProviderOptions {
|
|
5
|
+
readonly network: TonNetwork;
|
|
6
|
+
readonly endpoint: string;
|
|
7
|
+
readonly apiKey?: string;
|
|
8
|
+
readonly timeoutMs?: number;
|
|
9
|
+
}
|
|
10
|
+
export declare function createTonHttpProvider(options: TonHttpProviderOptions): TonProvider;
|
|
11
|
+
//#endregion
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import { invariant } from "../core/errors.mjs";
|
|
2
|
+
import { createNetworkConfig } from "../core/network.mjs";
|
|
3
|
+
import { TonClient } from "@ton/ton";
|
|
4
|
+
//#region src/server/index.ts
|
|
5
|
+
function createTonHttpProvider(options) {
|
|
6
|
+
const networkConfig = createNetworkConfig({
|
|
7
|
+
network: options.network,
|
|
8
|
+
endpoint: options.endpoint
|
|
9
|
+
});
|
|
10
|
+
const timeoutMs = options.timeoutMs ?? 1e4;
|
|
11
|
+
invariant(Number.isSafeInteger(timeoutMs) && timeoutMs > 0, "INVALID_NETWORK", "TON provider timeout must be a positive safe integer");
|
|
12
|
+
const client = new TonClient({
|
|
13
|
+
endpoint: networkConfig.endpoint,
|
|
14
|
+
...options.apiKey === void 0 ? {} : { apiKey: options.apiKey },
|
|
15
|
+
timeout: timeoutMs
|
|
16
|
+
});
|
|
17
|
+
return {
|
|
18
|
+
network: options.network,
|
|
19
|
+
openContract: (contract) => client.open(contract),
|
|
20
|
+
contractProvider: (address, init) => client.provider(address, init),
|
|
21
|
+
getBalance: (address) => client.getBalance(address),
|
|
22
|
+
async getAccountState(address) {
|
|
23
|
+
const state = await client.getContractState(address);
|
|
24
|
+
return {
|
|
25
|
+
status: state.state === "active" ? "active" : state.state === "frozen" ? "frozen" : "uninitialized",
|
|
26
|
+
balance: state.balance
|
|
27
|
+
};
|
|
28
|
+
},
|
|
29
|
+
async runGetMethod(address, method, args) {
|
|
30
|
+
const result = await client.runMethodWithError(address, method, args ? [...args] : []);
|
|
31
|
+
return {
|
|
32
|
+
gasUsed: result.gas_used,
|
|
33
|
+
stack: result.stack,
|
|
34
|
+
exitCode: result.exit_code
|
|
35
|
+
};
|
|
36
|
+
},
|
|
37
|
+
async getTransactions(address, transactionOptions) {
|
|
38
|
+
const limit = transactionOptions?.limit ?? 20;
|
|
39
|
+
invariant(Number.isSafeInteger(limit) && limit >= 1 && limit <= 1e3, "INVALID_TRANSACTION", "Transaction limit must be an integer from 1 to 1000");
|
|
40
|
+
if (transactionOptions?.lt !== void 0) invariant(transactionOptions.lt >= 0n, "INVALID_TRANSACTION", "Transaction logical time cannot be negative");
|
|
41
|
+
return (await client.getTransactions(address, {
|
|
42
|
+
limit,
|
|
43
|
+
...transactionOptions?.lt !== void 0 ? { lt: transactionOptions.lt.toString() } : {},
|
|
44
|
+
...transactionOptions?.hash ? { hash: transactionOptions.hash } : {}
|
|
45
|
+
})).map((transaction) => ({
|
|
46
|
+
lt: transaction.lt,
|
|
47
|
+
hash: transaction.hash().toString("hex"),
|
|
48
|
+
timestamp: transaction.now
|
|
49
|
+
}));
|
|
50
|
+
},
|
|
51
|
+
isContractDeployed: (address) => client.isContractDeployed(address)
|
|
52
|
+
};
|
|
53
|
+
}
|
|
54
|
+
//#endregion
|
|
55
|
+
export { createTonHttpProvider };
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
import { Address, Cell, Message, StateInit } from "@ton/core";
|
|
2
|
+
import { AccountEvent } from "@ton-api/client";
|
|
3
|
+
import { Blockchain, BlockchainConfig, Event } from "@ton/sandbox";
|
|
4
|
+
//#region src/simulation/index.d.ts
|
|
5
|
+
export type TonSimulationSource = "sandbox" | "tonapi";
|
|
6
|
+
export interface TonSimulationInput {
|
|
7
|
+
/** A serialized external or internal message, or a message object for local sandbox execution. */
|
|
8
|
+
readonly message: Cell | Message;
|
|
9
|
+
/** Required for unsigned external-wallet previews. Internal messages do not need this flag. */
|
|
10
|
+
readonly ignoreSignatureCheck?: boolean;
|
|
11
|
+
/** Keep mutations in a local sandbox. Preview callers should leave this false. */
|
|
12
|
+
readonly persist?: boolean;
|
|
13
|
+
}
|
|
14
|
+
export interface TonSimulationMessage {
|
|
15
|
+
readonly type: "internal" | "external-in" | "external-out";
|
|
16
|
+
readonly source?: Address;
|
|
17
|
+
readonly destination?: Address;
|
|
18
|
+
readonly value?: bigint;
|
|
19
|
+
readonly body?: Cell;
|
|
20
|
+
readonly init?: StateInit;
|
|
21
|
+
readonly createdAt?: number;
|
|
22
|
+
readonly createdLt?: bigint;
|
|
23
|
+
}
|
|
24
|
+
export interface TonSimulationTransaction {
|
|
25
|
+
readonly hash: string;
|
|
26
|
+
readonly logicalTime: bigint;
|
|
27
|
+
/** Raw 256-bit account identifier; TON transactions do not carry the workchain in this field. */
|
|
28
|
+
readonly accountId: string;
|
|
29
|
+
readonly timestamp: number;
|
|
30
|
+
readonly success: boolean;
|
|
31
|
+
readonly oldStatus: string;
|
|
32
|
+
readonly endStatus: string;
|
|
33
|
+
readonly totalFees: bigint;
|
|
34
|
+
readonly storageFees: bigint;
|
|
35
|
+
readonly gasFees: bigint;
|
|
36
|
+
readonly forwardFees: bigint;
|
|
37
|
+
readonly actionFees: bigint;
|
|
38
|
+
readonly gasUsed: bigint;
|
|
39
|
+
readonly vmSteps: number;
|
|
40
|
+
readonly exitCode?: number;
|
|
41
|
+
readonly actionSuccess?: boolean;
|
|
42
|
+
readonly input?: TonSimulationMessage;
|
|
43
|
+
readonly outputs: readonly TonSimulationMessage[];
|
|
44
|
+
readonly parentHash?: string;
|
|
45
|
+
readonly childHashes: readonly string[];
|
|
46
|
+
readonly blockchainLogs: string;
|
|
47
|
+
readonly vmLogs: string;
|
|
48
|
+
readonly debugLogs: string;
|
|
49
|
+
readonly oldStorage?: Cell;
|
|
50
|
+
readonly newStorage?: Cell;
|
|
51
|
+
}
|
|
52
|
+
export interface TonSimulationCost {
|
|
53
|
+
readonly totalFees: bigint;
|
|
54
|
+
readonly storageFees: bigint;
|
|
55
|
+
readonly gasFees: bigint;
|
|
56
|
+
readonly forwardFees: bigint;
|
|
57
|
+
readonly actionFees: bigint;
|
|
58
|
+
readonly gasUsed: bigint;
|
|
59
|
+
readonly vmSteps: number;
|
|
60
|
+
}
|
|
61
|
+
export interface TonSimulationError {
|
|
62
|
+
readonly message: string;
|
|
63
|
+
readonly exitCode?: number;
|
|
64
|
+
}
|
|
65
|
+
export interface TonSimulationResult {
|
|
66
|
+
readonly source: TonSimulationSource;
|
|
67
|
+
readonly success: boolean;
|
|
68
|
+
readonly transactions: readonly TonSimulationTransaction[];
|
|
69
|
+
readonly cost: TonSimulationCost;
|
|
70
|
+
readonly rootTransactionHash?: string;
|
|
71
|
+
readonly events?: readonly Event[] | readonly AccountEvent[];
|
|
72
|
+
readonly error?: TonSimulationError;
|
|
73
|
+
/** Always true for remote previews because the remote chain may change before signing. */
|
|
74
|
+
readonly speculative: boolean;
|
|
75
|
+
}
|
|
76
|
+
export interface TonSimulationAdapter {
|
|
77
|
+
readonly source: TonSimulationSource;
|
|
78
|
+
simulate(input: TonSimulationInput): Promise<TonSimulationResult>;
|
|
79
|
+
}
|
|
80
|
+
export declare function createTonSimulationWithFallback(primary: TonSimulationAdapter, fallback: TonSimulationAdapter): TonSimulationAdapter;
|
|
81
|
+
export interface TonSandboxSimulation extends TonSimulationAdapter {
|
|
82
|
+
readonly source: "sandbox";
|
|
83
|
+
readonly blockchain: Blockchain;
|
|
84
|
+
}
|
|
85
|
+
export interface TonSandboxSimulationOptions {
|
|
86
|
+
/** Recording storage snapshots makes state diffs available but is slower. */
|
|
87
|
+
readonly recordStorage?: boolean;
|
|
88
|
+
}
|
|
89
|
+
export declare function messageToCell(message: Message): Cell;
|
|
90
|
+
export declare function createTonSandboxSimulation(blockchain: Blockchain, options?: TonSandboxSimulationOptions): TonSandboxSimulation;
|
|
91
|
+
export interface TonClient4SandboxSimulationOptions {
|
|
92
|
+
readonly endpoint: string;
|
|
93
|
+
readonly timeoutMs?: number;
|
|
94
|
+
readonly blockSeqno?: number;
|
|
95
|
+
readonly config?: BlockchainConfig;
|
|
96
|
+
readonly now?: number;
|
|
97
|
+
readonly recordStorage?: boolean;
|
|
98
|
+
}
|
|
99
|
+
/**
|
|
100
|
+
* Creates a native sandbox backed by a read-only snapshot of the live chain.
|
|
101
|
+
* Contract code and account state are fetched lazily from TonClient4.
|
|
102
|
+
*/
|
|
103
|
+
export declare function createTonClient4SandboxSimulation(options: TonClient4SandboxSimulationOptions): Promise<TonSandboxSimulation>;
|
|
104
|
+
export interface TonApiSimulationFallbackOptions {
|
|
105
|
+
readonly baseUrl?: string;
|
|
106
|
+
readonly apiKey?: string;
|
|
107
|
+
readonly timeoutMs?: number;
|
|
108
|
+
readonly fetch?: typeof globalThis.fetch;
|
|
109
|
+
}
|
|
110
|
+
/** Optional fallback for clients that cannot hydrate a native sandbox snapshot. */
|
|
111
|
+
export declare function createTonApiSimulationFallback(options?: TonApiSimulationFallbackOptions): TonSimulationAdapter;
|
|
112
|
+
//#endregion
|