Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,10 @@ All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).

## [0.44.0] - 2026-03-02

- Support tetra via `tetra` network option

## [0.43.0] - 2026-01-19

### Added
Expand Down
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -136,6 +136,7 @@ Start by adding the following environment variables to your `.env` file:
**Optional variables:**
* **`WALLET_ID`**: The wallet ID (can be used with versions below `v5r1`).
* **`SUBWALLET_NUMBER`**: The subwallet number used to build the wallet ID (can be used with `v5r1` wallets).
* **`WALLET_NETWORK_ID`**: Network ID used to build the wallet ID (v5 wallets). Defaults: `-3` testnet, `-239` mainnet and other networks (tetra, custom).

Once your environment is set up, you can use the mnemonic wallet for deployment with the appropriate configuration.

Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@ton/blueprint",
"version": "0.43.0",
"version": "0.44.0",
"description": "Framework for development of TON smart contracts",
"main": "dist/index.js",
"bin": "./dist/cli/cli.js",
Expand Down
5 changes: 2 additions & 3 deletions src/cli/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -127,8 +127,7 @@ ${chalk.cyan('--custom')} [api-endpoint] - use a custom API
${chalk.cyan('--custom-version')} - API version (v2, v4)
${chalk.cyan('--custom-key')} - API key (v2 only)
${chalk.cyan('--custom-type')} - network type (custom, mainnet, testnet)
${chalk.cyan('--custom-domain')} - network domain (for custom l2 domain)
${chalk.cyan('--custom-network-id')} - network global ID (for custom network)
${chalk.cyan('--custom-global-id')} - network global ID (for custom l2 domain)
${chalk.cyan('--tonconnect')}, ${chalk.cyan('--deeplink')}, ${chalk.cyan('--mnemonic')} - deployer options
${chalk.cyan('--tonscan')}, ${chalk.cyan('--tonviewer')}, ${chalk.cyan('--toncx')}, ${chalk.cyan('--dton')} - explorer (default: tonviewer)
${chalk.gray('[...args]')} (array of strings, optional) - Arguments passed directly to the script.
Expand Down Expand Up @@ -166,7 +165,7 @@ ${chalk.bold('SEE ALSO')}
Verifies a deployed contract on ${chalk.underline('https://verifier.ton.org')}.

${chalk.bold('Flags:')}
${chalk.cyan('--mainnet')}, ${chalk.cyan('--testnet')} - selects network
${chalk.cyan('--mainnet')}, ${chalk.cyan('--testnet')}, ${chalk.cyan('--tetra')} - selects network
${chalk.cyan('--verifier')} - specifies the verifier ID to use (default: ${chalk.cyan('verifier.ton.org')})
${chalk.cyan('--list-verifiers')} - lists all available verifiers for the selected network (or both networks if none selected)
${chalk.cyan('--compiler-version')} - specifies the exact compiler version to use (e.g. ${chalk.cyan('0.4.4-newops.1')}). Note: this does not change the underlying compiler itself.
Expand Down
3 changes: 0 additions & 3 deletions src/config/Config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,9 +33,6 @@ export interface Config {
*/
network?: 'mainnet' | 'testnet' | 'tetra' | CustomNetwork;

domain?: number;
networkId?: number;

/**
* If true, keeps compilable files (`*.compile.ts`) in a separate directory `compilables`.
* When false or unset, compilables are stored in `wrappers` directory.
Expand Down
1 change: 1 addition & 0 deletions src/config/CustomNetwork.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,4 +6,5 @@ export type CustomNetwork = {
version?: NetworkVersion;
key?: string;
type?: Network;
globalId?: number;
};
4 changes: 3 additions & 1 deletion src/network/Network.ts
Original file line number Diff line number Diff line change
@@ -1 +1,3 @@
export type Network = 'mainnet' | 'testnet' | 'tetra' | 'custom';
import { AVAILABLE_NETWORKS } from './constants';

export type Network = (typeof AVAILABLE_NETWORKS)[number];
10 changes: 10 additions & 0 deletions src/network/constants.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
export const AVAILABLE_NETWORKS = ['mainnet', 'testnet', 'tetra', 'custom'] as const satisfies string[];

export const MAINNET_NETWORK_GLOBAL_ID = -239;
export const TESTNET_NETWORK_GLOBAL_ID = -3;
const TETRA_NETWORK_GLOBAL_ID = 662387;

export const TETRA_DOMAIN = {
type: 'l2',
globalId: TETRA_NETWORK_GLOBAL_ID,
} as const;
55 changes: 19 additions & 36 deletions src/network/createNetworkProvider.ts
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ import { CustomNetwork } from '../config/CustomNetwork';
import { Network } from './Network';
import { WalletVersion } from './send/wallets';
import { Explorer } from './Explorer';
import { AVAILABLE_NETWORKS } from './constants';

const INITIAL_DELAY = 400;
const MAX_ATTEMPTS = 4;
Expand All @@ -55,8 +56,7 @@ export const argSpec = {
'--custom-type': String,
'--custom-version': String,
'--custom-key': String,
'--custom-domain': Number,
'--custom-network-id': Number,
'--custom-global-id': Number,

'--compiler-version': String,

Expand Down Expand Up @@ -403,13 +403,7 @@ function getOptionalNumberEnv(envName: string) {
return value;
}

async function createMnemonicProvider(
client: BlueprintTonClient,
network: Network,
ui: UIProvider,
domain?: number,
networkId?: number,
) {
async function createMnemonicProvider(client: BlueprintTonClient, network: Network, ui: UIProvider, globalId?: number) {
const mnemonic = process.env.WALLET_MNEMONIC ?? '';
const walletVersion = process.env.WALLET_VERSION ?? '';
if (mnemonic.length === 0 || walletVersion.length === 0) {
Expand All @@ -419,6 +413,7 @@ async function createMnemonicProvider(
}
const walletId = getOptionalNumberEnv('WALLET_ID');
const subwalletNumber = getOptionalNumberEnv('SUBWALLET_NUMBER');
const walletNetworkId = getOptionalNumberEnv('WALLET_NETWORK_ID');

const keyPair = await mnemonicToPrivateKey(mnemonic.split(' '));
return new MnemonicProvider({
Expand All @@ -429,8 +424,8 @@ async function createMnemonicProvider(
walletId,
subwalletNumber,
network,
domain,
networkId,
globalId,
networkId: walletNetworkId,
});
}

Expand Down Expand Up @@ -493,11 +488,7 @@ class NetworkProviderBuilder {
return typeof this.config.network === 'string' ? this.config.network : 'custom';
}

network = await this.ui.choose(
'Which network do you want to use?',
['mainnet', 'testnet', 'tetra', 'custom'],
(c) => c,
);
network = await this.ui.choose('Which network do you want to use?', AVAILABLE_NETWORKS, (c) => c);
if (network === 'custom') {
const defaultCustomEndpoint = 'http://localhost:8081/';
this.args['--custom'] = (
Expand Down Expand Up @@ -566,15 +557,14 @@ class NetworkProviderBuilder {
this.config?.manifestUrl,
);
break;
case 'mnemonic':
provider = await createMnemonicProvider(
client,
network,
this.ui,
this.config?.domain,
this.config?.networkId,
);
case 'mnemonic': {
let globalId: number | undefined = undefined;
if (typeof this.config?.network === 'object') {
globalId = this.config.network.globalId;
}
provider = await createMnemonicProvider(client, network, this.ui, globalId);
break;
}
default:
throw new Error('Unknown deploy option');
}
Expand All @@ -584,7 +574,6 @@ class NetworkProviderBuilder {

async build(): Promise<NetworkProvider> {
let network = await this.chooseNetwork();
const explorer = this.chooseExplorer();

if (
network !== 'custom' &&
Expand Down Expand Up @@ -612,21 +601,14 @@ class NetworkProviderBuilder {
if (inputType !== undefined) {
type = inputType as any; // checks come later
}
const globalId = this.args['--custom-global-id'];
configNetwork = {
endpoint: this.args['--custom'],
version,
key: this.args['--custom-key'],
globalId,
type,
};

if (this.config && this.args['--custom-domain']) {
const customDomain = this.args['--custom-domain'];
this.config.domain = customDomain;
}

if (this.config && this.args['--custom-network-id']) {
this.config.networkId = this.args['--custom-network-id'];
}
}
if (configNetwork === undefined) {
throw new Error('Custom network is (somehow) undefined');
Expand Down Expand Up @@ -657,8 +639,8 @@ class NetworkProviderBuilder {
}

if (configNetwork.type !== undefined) {
const ct = configNetwork.type.toLowerCase();
if (!['mainnet', 'testnet', 'custom', 'tetra'].includes(ct)) {
const ct = configNetwork.type.toLowerCase() as Network;
if (!AVAILABLE_NETWORKS.includes(ct)) {
throw new Error('Unknown network type: ' + ct);
}
network = ct as Network;
Expand Down Expand Up @@ -719,6 +701,7 @@ class NetworkProviderBuilder {

const sender = new SendProviderSender(sendProvider);

const explorer = network === 'tetra' ? 'tonviewer' : this.chooseExplorer();
return new NetworkProviderImpl(tc, sender, network, explorer, this.ui);
}
}
Expand Down
21 changes: 16 additions & 5 deletions src/network/send/MnemonicProvider.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import {
openContract,
OpenedContract,
SendMode,
SignatureDomain,
StateInit,
} from '@ton/core';
import { KeyPair, keyPairFromSecretKey } from '@ton/crypto';
Expand All @@ -18,7 +19,8 @@ import { UIProvider } from '../../ui/UIProvider';
import { BlueprintTonClient } from '../NetworkProvider';
import { Network } from '../Network';
import { wallets, WalletVersion } from './wallets';
import { getW5NetworkGlobalId, TETRA_DOMAIN } from '../../utils/network.utils';
import { getW5NetworkGlobalId } from '../utils';
import { TETRA_DOMAIN } from '../constants';

interface WalletInstance extends Contract {
getSeqno(provider: ContractProvider): Promise<number>;
Expand All @@ -44,7 +46,7 @@ type MnemonicProviderParams = {
client: BlueprintTonClient;
ui: UIProvider;
network: Network;
domain?: number;
globalId?: number;
networkId?: number;
};

Expand Down Expand Up @@ -79,12 +81,21 @@ export class MnemonicProvider implements SendProvider {
this.ui = params.ui;
}

private getDomain(params: MnemonicProviderParams): SignatureDomain | undefined {
if (params.globalId !== undefined) {
return { type: 'l2' as const, globalId: params.globalId };
}
if (params.network === 'tetra') {
return TETRA_DOMAIN;
}
return undefined;
}

private createWallet(params: MnemonicProviderParams, kp: KeyPair): WalletInstance {
const networkDomain = params.network === 'tetra' ? TETRA_DOMAIN : undefined;
const domain = params.domain ? { type: 'l2' as const, globalId: params.domain } : networkDomain;
const networkGlobalId = params.networkId ?? getW5NetworkGlobalId(params.network);
const domain = this.getDomain(params);

if (params.version === 'v5r1') {
const networkGlobalId = params.networkId ?? getW5NetworkGlobalId(params.network);
return wallets[params.version].create({
publicKey: kp.publicKey,
walletId: {
Expand Down
14 changes: 3 additions & 11 deletions src/utils/network.utils.ts → src/network/utils.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,5 @@
import { Network } from '../network/Network';

const MAINNET_NETWORK_GLOBAL_ID = -239;
const TESTNET_NETWORK_GLOBAL_ID = -3;
const TETRA_NETWORK_GLOBAL_ID = 662387;

export const TETRA_DOMAIN = {
type: 'l2',
globalId: TETRA_NETWORK_GLOBAL_ID,
} as const;
import { Network } from './Network';
import { MAINNET_NETWORK_GLOBAL_ID, TESTNET_NETWORK_GLOBAL_ID } from './constants';

export function getW5NetworkGlobalId(network: Network): number {
switch (network) {
Expand All @@ -18,5 +10,5 @@ export function getW5NetworkGlobalId(network: Network): number {
case 'tetra':
return MAINNET_NETWORK_GLOBAL_ID;
}
return TESTNET_NETWORK_GLOBAL_ID;
return MAINNET_NETWORK_GLOBAL_ID;
}
12 changes: 10 additions & 2 deletions src/utils/ton.utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,23 +31,31 @@ export const tonDeepLink = (
stateInit ? '&init=' + stateInit.toBoc().toString('base64url') : ''
}`;

function getNetworkPrefix(network: string) {
if (network === 'testnet' || network === 'tetra') {
return `${network}.`;
}
return '';
}

/**
* Generates a link to view a TON address in a selected blockchain explorer.
*
* Supports several TON explorers like TONSCan, Tonviewer, dton.io, etc., and
* dynamically adds the testnet prefix when needed.
*
* @param {string} address - The TON address to view in explorer.
* @param {string} network - The target network, either 'mainnet' or 'testnet'.
* @param {string} network - The target network.
* @param {string} explorer - The desired explorer. Supported values: 'tonscan', 'tonviewer', 'toncx', 'dton'.
* @returns {string} A full URL pointing to the address in the selected explorer.
*
* @example
* const link = getExplorerLink("EQC...", "testnet", "tonscan");
* // "https://testnet.tonscan.org/address/EQC..."
*/

export function getExplorerLink(address: string, network: string, explorer: Explorer) {
const networkPrefix = network === 'testnet' ? 'testnet.' : '';
const networkPrefix = getNetworkPrefix(network);

switch (explorer) {
case 'tonscan':
Expand Down