@ton/blueprint 0.15.0 → 0.16.0

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/CHANGELOG.md CHANGED
@@ -5,6 +5,18 @@ All notable changes to this project will be documented in this file.
5
5
  The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
6
6
  and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
7
 
8
+ ## [0.16.0] - 2024-02-15
9
+
10
+ ### Added
11
+
12
+ - Added the `network` entry to the global config, which allows one to specify a custom network to be used instead of having to add `--custom` flags on each run
13
+ - Added the `convert` command which attempts to convert a legacy bash build script into a blueprint `.compile.ts` file
14
+ - Added the ability to pass any user data into the compile hooks
15
+
16
+ ### Changed
17
+
18
+ - Improved the `verify` command
19
+
8
20
  ## [0.15.0] - 2023-12-15
9
21
 
10
22
  ### Added
package/README.md CHANGED
@@ -103,26 +103,56 @@ Run in terminal:   `npx blueprint help`   or   `yarn blueprint he
103
103
  2. Implement a deployment script in `scripts/deploy<CONTRACT>.ts`
104
104
  3. Rely on the wrapper TypeScript class from `wrappers/<CONTRACT>.ts` to initialize the contract
105
105
 
106
- ## Plugins
106
+ ## Config
107
+
108
+ A config may be created in order to control some of blueprint's features. If a config is needed, create a `blueprint.config.ts` file in the root of your project with something like this:
109
+ ```typescript
110
+ import { Config } from '@ton/blueprint';
111
+
112
+ export const config: Config = {
113
+ // config contents
114
+ };
115
+ ```
116
+ It is important that the config is exported, is named `config`, and is not `default` exported.
117
+
118
+ Config's features are explained below.
119
+
120
+ ### Plugins
107
121
 
108
122
  Blueprint has a plugin system to allow the community to develop their own additions for the ecosystem without the need to change blueprint's code.
109
123
 
110
- In order to use plugins, create a `blueprint.config.ts` file in the root of your project with something like this:
124
+ In order to use plugins, add a `plugins` array to your config:
111
125
  ```typescript
112
126
  import { Config } from '@ton/blueprint';
113
127
  import { ScaffoldPlugin } from 'blueprint-scaffold';
114
128
 
115
129
  export const config: Config = {
116
- plugins: [new ScaffoldPlugin()],
130
+ plugins: [new ScaffoldPlugin()],
117
131
  };
118
132
  ```
119
133
  (This example shows how to add the [scaffold](https://github.com/1IxI1/blueprint-scaffold) plugin)
120
134
 
121
- It is important that the config is exported, is named `config`, and is not `default` exported.
122
-
123
135
  Here are some of the plugins developed by the community:
124
136
  - [scaffold](https://github.com/1IxI1/blueprint-scaffold) - allows developers to quickly create a simple dapp automatically using the wrappers' code
125
137
 
138
+ ### Custom network
139
+
140
+ A custom network may be specified by using the `--custom` flags, which you can read about by running `blueprint help run`, but it can be tiresome to use these at all times. Instead, to specify a custom network to always be used (unless `--custom` flags are present), add a `network` object to your config:
141
+ ```typescript
142
+ import { Config } from '@ton/blueprint';
143
+
144
+ export const config: Config = {
145
+ network: {
146
+ endpoint: 'https://toncenter.com/api/v2/',
147
+ type: 'mainnet',
148
+ version: 'v2',
149
+ key: 'YOUR_API_KEY',
150
+ },
151
+ };
152
+ ```
153
+
154
+ Properties of the `network` object have the same semantics as the `--custom` flags with respective names (see `blueprint help run`).
155
+
126
156
  ## Contributors
127
157
 
128
158
  Special thanks to [@qdevstudio](https://t.me/qdevstudio) for their logo for blueprint.
@@ -1,5 +1,9 @@
1
1
  import arg from 'arg';
2
2
  import { UIProvider } from '../ui/UIProvider';
3
+ import { Config } from '../config/Config';
3
4
  export declare const argSpec: {};
4
5
  export type Args = arg.Result<typeof argSpec>;
5
- export type Runner = (args: Args, ui: UIProvider) => Promise<void>;
6
+ export type RunnerContext = {
7
+ config?: Config;
8
+ };
9
+ export type Runner = (args: Args, ui: UIProvider, context: RunnerContext) => Promise<void>;
package/dist/cli/cli.js CHANGED
@@ -36,6 +36,7 @@ const run_1 = require("./run");
36
36
  const build_1 = require("./build");
37
37
  const test_1 = require("./test");
38
38
  const verify_1 = require("./verify");
39
+ const convert_1 = require("./convert");
39
40
  const help_1 = require("./help");
40
41
  const InquirerUIProvider_1 = require("../ui/InquirerUIProvider");
41
42
  const Runner_1 = require("./Runner");
@@ -47,6 +48,7 @@ const runners = {
47
48
  test: test_1.test,
48
49
  help: help_1.help,
49
50
  verify: verify_1.verify,
51
+ convert: convert_1.convert,
50
52
  };
51
53
  async function main() {
52
54
  var _a;
@@ -59,12 +61,14 @@ async function main() {
59
61
  process.exit(0);
60
62
  }
61
63
  let effectiveRunners = {};
64
+ const runnerContext = {};
62
65
  try {
63
66
  const configModule = await (_a = path_1.default.join(process.cwd(), 'blueprint.config.ts'), Promise.resolve().then(() => __importStar(require(_a))));
64
67
  try {
65
68
  if ('config' in configModule && typeof configModule.config === 'object') {
66
69
  const config = configModule.config;
67
- for (const plugin of config.plugins) {
70
+ runnerContext.config = config;
71
+ for (const plugin of config.plugins ?? []) {
68
72
  for (const runner of plugin.runners()) {
69
73
  effectiveRunners[runner.name] = runner.runner;
70
74
  help_1.additionalHelpMessages[runner.name] = runner.help;
@@ -91,7 +95,7 @@ async function main() {
91
95
  process.exit(1);
92
96
  }
93
97
  const ui = new InquirerUIProvider_1.InquirerUIProvider();
94
- await runner(args, ui);
98
+ await runner(args, ui, runnerContext);
95
99
  ui.close();
96
100
  }
97
101
  process.on('SIGINT', () => {
@@ -0,0 +1,2 @@
1
+ import { Runner } from './Runner';
2
+ export declare const convert: Runner;
@@ -0,0 +1,133 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.convert = void 0;
7
+ const fs_1 = require("fs");
8
+ const path_1 = __importDefault(require("path"));
9
+ const arg_1 = __importDefault(require("arg"));
10
+ const createNetworkProvider_1 = require("../network/createNetworkProvider");
11
+ const template_1 = require("../template");
12
+ const paths_1 = require("../paths");
13
+ function createWrapperName(old) {
14
+ return old
15
+ .split(/[-_]/)
16
+ .map((x) => x.replace(x[0], x[0].toUpperCase()))
17
+ .join('');
18
+ }
19
+ function quoteString(str) {
20
+ const quote = "'";
21
+ const quoted = str.startsWith(quote) && str.endsWith(quote);
22
+ return quoted ? str : quote + str + quote;
23
+ }
24
+ function findFile(dir, filename) {
25
+ const contents = (0, fs_1.readdirSync)(dir);
26
+ let hasFile = contents.includes(filename);
27
+ let foundPath = '';
28
+ if (hasFile) {
29
+ foundPath = path_1.default.join(dir, filename);
30
+ if ((0, fs_1.statSync)(foundPath).isFile()) {
31
+ return foundPath;
32
+ }
33
+ }
34
+ for (let entry of contents) {
35
+ const entryPath = path_1.default.join(dir, entry);
36
+ const stat = (0, fs_1.statSync)(entryPath);
37
+ if (stat.isDirectory()) {
38
+ foundPath = findFile(entryPath, filename);
39
+ if (foundPath !== '') {
40
+ break;
41
+ }
42
+ }
43
+ }
44
+ return foundPath;
45
+ }
46
+ function parseCompileString(str, src_dir, ui) {
47
+ // Naive but does the job
48
+ const tokens = str.split(/\\?\s+/).filter((t) => t != '\\');
49
+ const outputIdx = tokens.indexOf('-o');
50
+ if (outputIdx < 0) {
51
+ throw new Error('No output flag (-o) found in command:' + str);
52
+ }
53
+ const outFile = tokens[outputIdx + 1];
54
+ const outputName = outFile.match(/([A-Za-z0-9\-_\\\/]*)/);
55
+ if (outputName === null) {
56
+ throw new Error(`Something went wrong when parsing output from ${outFile}`);
57
+ }
58
+ const wrapperName = createWrapperName(path_1.default.basename(outputName[1]));
59
+ const sourceFiles = tokens.filter((x) => x.match(/\.func|\.fc['"]?$/) !== null).map((t) => t.replace(/['"`]/g, ''));
60
+ if (sourceFiles.length === 0) {
61
+ throw new Error(`No source files found in command:${str}`);
62
+ }
63
+ for (let i = 0; i < sourceFiles.length; i++) {
64
+ const testPath = path_1.default.join(src_dir, sourceFiles[i]);
65
+ if ((0, fs_1.existsSync)(testPath)) {
66
+ sourceFiles[i] = quoteString(testPath);
67
+ }
68
+ else {
69
+ const foundPath = findFile(src_dir, sourceFiles[i]);
70
+ if (foundPath === '') {
71
+ throw new Error(`${sourceFiles[i]} is not found anywhere`);
72
+ }
73
+ src_dir = path_1.default.dirname(foundPath);
74
+ sourceFiles[i] = quoteString(foundPath);
75
+ }
76
+ }
77
+ return {
78
+ name: wrapperName,
79
+ targets: sourceFiles.join(','),
80
+ };
81
+ }
82
+ const convert = async (args, ui) => {
83
+ const localArgs = (0, arg_1.default)(createNetworkProvider_1.argSpec);
84
+ let filePath;
85
+ if (localArgs._.length < 2) {
86
+ filePath = await ui.input('Please specify path to convert from:');
87
+ }
88
+ else {
89
+ filePath = localArgs._[1];
90
+ }
91
+ const content = (0, fs_1.readFileSync)(filePath, { encoding: 'utf-8' });
92
+ const srcDir = path_1.default.dirname(filePath);
93
+ const compileStrings = content.replace(/\\[\r?\n]+/g, '').matchAll(/\s?func\s+(.*)\n/g);
94
+ if (compileStrings === null) {
95
+ throw new Error(`No func related commands found in ${filePath}`);
96
+ }
97
+ const templatePath = path_1.default.join(template_1.TEMPLATES_DIR, 'func', 'legacy', 'wrappers', 'compile.ts.template');
98
+ const templateContent = (0, fs_1.readFileSync)(templatePath, { encoding: 'utf-8' });
99
+ if (!(0, fs_1.existsSync)(paths_1.WRAPPERS_DIR)) {
100
+ ui.write('Creating wrappers directory...');
101
+ (0, fs_1.mkdirSync)(paths_1.WRAPPERS_DIR);
102
+ }
103
+ for (let compileStr of compileStrings) {
104
+ let parsed;
105
+ try {
106
+ parsed = parseCompileString(compileStr[1], srcDir, ui);
107
+ }
108
+ catch (e) {
109
+ ui.write(e.toString());
110
+ continue;
111
+ }
112
+ const resTemplate = (0, template_1.executeTemplate)(templateContent, parsed);
113
+ let lineIdx = resTemplate.indexOf('\n');
114
+ const contentIdx = lineIdx + 1;
115
+ if (resTemplate[lineIdx - 1] === '\r') {
116
+ lineIdx--;
117
+ }
118
+ const wrapperName = resTemplate.substring(0, lineIdx);
119
+ const fileName = path_1.default.join(paths_1.WRAPPERS_DIR, wrapperName);
120
+ const content = resTemplate.substring(contentIdx);
121
+ if ((0, fs_1.existsSync)(fileName)) {
122
+ ui.write(`File ${wrapperName} already exists!`);
123
+ const overwrite = await ui.prompt('Do you want to overwrite it?');
124
+ if (!overwrite) {
125
+ ui.write(`Skipping ${wrapperName}`);
126
+ continue;
127
+ }
128
+ }
129
+ (0, fs_1.writeFileSync)(fileName, content, { encoding: 'utf-8' });
130
+ ui.write(`${wrapperName} wrapper created!`);
131
+ }
132
+ };
133
+ exports.convert = convert;
package/dist/cli/help.js CHANGED
@@ -15,7 +15,8 @@ List of available commands:
15
15
  - build
16
16
  - help
17
17
  - test
18
- - verify`,
18
+ - verify
19
+ - convert`,
19
20
  create: `Usage: blueprint create [contract name] [flags]
20
21
 
21
22
  Creates a new contract together with supporting files according to a template.
@@ -61,6 +62,8 @@ Flags:
61
62
  --custom-version - specifies the API version to use with the custom API. Options: v2 (defualt), v4.
62
63
  --custom-key - specifies the API key to use with the custom API, can only be used with API v2.
63
64
  --custom-type - specifies the network type to be indicated to scripts. Options: mainnet, testnet.`,
65
+ convert: `Usage: blueprint convert [path to build script]
66
+ Atempts to convert legacy bash build script to a blueprint compile wrapper.`,
64
67
  };
65
68
  exports.additionalHelpMessages = {};
66
69
  const help = async (args, ui) => {
package/dist/cli/run.js CHANGED
@@ -7,7 +7,7 @@ exports.run = void 0;
7
7
  const createNetworkProvider_1 = require("../network/createNetworkProvider");
8
8
  const utils_1 = require("../utils");
9
9
  const arg_1 = __importDefault(require("arg"));
10
- const run = async (args, ui) => {
10
+ const run = async (args, ui, context) => {
11
11
  const localArgs = (0, arg_1.default)(createNetworkProvider_1.argSpec);
12
12
  const { module: mod } = await (0, utils_1.selectFile)(await (0, utils_1.findScripts)(), {
13
13
  ui,
@@ -16,7 +16,7 @@ const run = async (args, ui) => {
16
16
  if (typeof mod.run !== 'function') {
17
17
  throw new Error('Function `run` is missing!');
18
18
  }
19
- const networkProvider = await (0, createNetworkProvider_1.createNetworkProvider)(ui, localArgs);
19
+ const networkProvider = await (0, createNetworkProvider_1.createNetworkProvider)(ui, localArgs, context.config);
20
20
  await mod.run(networkProvider, localArgs._.slice(2));
21
21
  };
22
22
  exports.run = run;
@@ -9,6 +9,7 @@ const compile_1 = require("../compile/compile");
9
9
  const path_1 = __importDefault(require("path"));
10
10
  const createNetworkProvider_1 = require("../network/createNetworkProvider");
11
11
  const build_1 = require("./build");
12
+ const utils_1 = require("../utils");
12
13
  const arg_1 = __importDefault(require("arg"));
13
14
  const backends = {
14
15
  mainnet: {
@@ -57,10 +58,62 @@ class VerifierRegistry {
57
58
  });
58
59
  }
59
60
  }
60
- const verify = async (args, ui) => {
61
+ async function lookupCodeHash(hash, ui, retryCount = 5) {
62
+ let queryResponse;
63
+ let foundAddr;
64
+ let done = false;
65
+ const graphqlUrl = 'https://dton.io/graphql/';
66
+ const query = `{
67
+ account_states(page:0, page_size:1, account_state_state_init_code_hash: "${hash.toString('hex').toUpperCase()}")
68
+ {
69
+ address
70
+ workchain
71
+ }
72
+ }`;
73
+ do {
74
+ try {
75
+ ui.write('Checking if such a contract is already deployed...');
76
+ const resp = await fetch(graphqlUrl, {
77
+ method: 'POST',
78
+ body: JSON.stringify({ query }),
79
+ headers: { 'Content-Type': 'application/json' },
80
+ });
81
+ if (resp.ok) {
82
+ queryResponse = await resp.json();
83
+ const states = queryResponse.data.account_states;
84
+ if (states.length > 0) {
85
+ const state = states[0];
86
+ foundAddr = core_1.Address.parseRaw(`${state.workchain}:${state.address}`).toString();
87
+ }
88
+ else {
89
+ ui.write('No such contract was found!');
90
+ }
91
+ done = true;
92
+ }
93
+ else {
94
+ retryCount--;
95
+ }
96
+ // Meh
97
+ }
98
+ catch (e) {
99
+ retryCount--;
100
+ if (e.cause) {
101
+ if (e.cause.code == 'ETIMEDOUT') {
102
+ ui.write('API timed out, waiting...');
103
+ await (0, utils_1.sleep)(5000);
104
+ }
105
+ }
106
+ else {
107
+ ui.write(e);
108
+ }
109
+ }
110
+ } while (!done && retryCount > 0);
111
+ return foundAddr;
112
+ }
113
+ const verify = async (args, ui, context) => {
61
114
  const localArgs = (0, arg_1.default)(createNetworkProvider_1.argSpec);
62
115
  const sel = await (0, build_1.selectCompile)(ui, localArgs);
63
- const networkProvider = await (0, createNetworkProvider_1.createNetworkProvider)(ui, localArgs, false);
116
+ const networkProvider = await (0, createNetworkProvider_1.createNetworkProvider)(ui, localArgs, context.config, false);
64
117
  const sender = networkProvider.sender();
65
118
  const senderAddress = sender.address;
66
119
  if (senderAddress === undefined) {
@@ -70,8 +123,27 @@ const verify = async (args, ui) => {
70
123
  if (network === 'custom') {
71
124
  throw new Error('Cannot use custom network');
72
125
  }
73
- const addr = await ui.input('Deployed contract address');
74
126
  const result = await (0, compile_1.doCompile)(sel.name);
127
+ const resHash = result.code.hash();
128
+ ui.write(`Compiled code hash hex:${resHash.toString('hex')}`);
129
+ ui.write('We can look up the address with such code hash in the blockchain automatically');
130
+ const passManually = await ui.prompt('Do you want to specify the address manually?');
131
+ let addr;
132
+ if (passManually) {
133
+ addr = (await ui.inputAddress('Deployed contract address')).toString();
134
+ }
135
+ else {
136
+ const alreadyDeployed = await lookupCodeHash(resHash, ui);
137
+ if (alreadyDeployed) {
138
+ ui.write(`Contract is already deployed at: ${alreadyDeployed}\nUsing that address.`);
139
+ ui.write(`https://tonscan.org/address/${alreadyDeployed}`);
140
+ addr = alreadyDeployed;
141
+ }
142
+ else {
143
+ ui.write("Please enter the contract's address manually");
144
+ addr = (await ui.inputAddress('Deployed contract address')).toString();
145
+ }
146
+ }
75
147
  let src;
76
148
  const fd = new FormData();
77
149
  if (result.lang === 'func') {
@@ -149,23 +221,26 @@ const verify = async (args, ui) => {
149
221
  body: fd,
150
222
  });
151
223
  if (sourceResponse.status !== 200) {
152
- throw new Error('Could not compile on backend:\n' + (await sourceResponse.text()));
224
+ throw new Error('Could not compile on backend:\n' + (await sourceResponse.json()));
153
225
  }
154
226
  const sourceResult = await sourceResponse.json();
155
227
  if (sourceResult.compileResult.result !== 'similar') {
156
- throw new Error('The code is not similar');
228
+ throw new Error(sourceResult.compileResult.error);
157
229
  }
158
230
  let msgCell = sourceResult.msgCell;
159
231
  let acquiredSigs = 1;
160
232
  while (acquiredSigs < verifier.quorum) {
161
- const signResponse = await fetch(removeRandom(remainingBackends) + '/sign', {
233
+ const curBackend = removeRandom(remainingBackends);
234
+ ui.write(`Using backend: ${curBackend}`);
235
+ const signResponse = await fetch(curBackend + '/sign', {
162
236
  method: 'POST',
163
237
  body: JSON.stringify({
164
238
  messageCell: msgCell,
165
239
  }),
240
+ headers: { 'Content-Type': 'application/json' },
166
241
  });
167
242
  if (signResponse.status !== 200) {
168
- throw new Error('Could not compile on backend:\n' + (await signResponse.text()));
243
+ throw new Error('Could not sign on backend:\n' + (await signResponse.text()));
169
244
  }
170
245
  const signResult = await signResponse.json();
171
246
  msgCell = signResult.msgCell;
@@ -177,5 +252,6 @@ const verify = async (args, ui) => {
177
252
  value: (0, core_1.toNano)('0.5'),
178
253
  body: c,
179
254
  });
255
+ ui.write(`Contract successfully verified at https://verifier.ton.org/${addr}`);
180
256
  };
181
257
  exports.verify = verify;
@@ -1,9 +1,12 @@
1
1
  import { SourceResolver, SourcesMap, SourcesArray } from '@ton-community/func-js';
2
2
  import { Cell } from '@ton/core';
3
3
  import { ConfigProject } from '@tact-lang/compiler';
4
+ export type HookParams = {
5
+ userData?: any;
6
+ };
4
7
  export type CommonCompilerConfig = {
5
- preCompileHook?: () => Promise<void>;
6
- postCompileHook?: (code: Cell) => Promise<void>;
8
+ preCompileHook?: (params: HookParams) => Promise<void>;
9
+ postCompileHook?: (code: Cell, params: HookParams) => Promise<void>;
7
10
  };
8
11
  export type TactCompilerConfig = {
9
12
  lang: 'tact';
@@ -14,5 +14,8 @@ export type TactCompileResult = {
14
14
  code: Cell;
15
15
  };
16
16
  export type CompileResult = TactCompileResult | FuncCompileResult;
17
- export declare function doCompile(name: string): Promise<CompileResult>;
18
- export declare function compile(name: string): Promise<Cell>;
17
+ export declare function doCompile(name: string, opts?: CompileOpts): Promise<CompileResult>;
18
+ export type CompileOpts = {
19
+ hookUserData?: any;
20
+ };
21
+ export declare function compile(name: string, opts?: CompileOpts): Promise<Cell>;
@@ -106,20 +106,24 @@ async function doCompileInner(name, config) {
106
106
  optLevel: config.optLevel,
107
107
  });
108
108
  }
109
- async function doCompile(name) {
109
+ async function doCompile(name, opts) {
110
110
  const config = await getCompilerConfigForContract(name);
111
111
  if (config.preCompileHook !== undefined) {
112
- await config.preCompileHook();
112
+ await config.preCompileHook({
113
+ userData: opts?.hookUserData,
114
+ });
113
115
  }
114
116
  const res = await doCompileInner(name, config);
115
117
  if (config.postCompileHook !== undefined) {
116
- await config.postCompileHook(res.code);
118
+ await config.postCompileHook(res.code, {
119
+ userData: opts?.hookUserData,
120
+ });
117
121
  }
118
122
  return res;
119
123
  }
120
124
  exports.doCompile = doCompile;
121
- async function compile(name) {
122
- const result = await doCompile(name);
125
+ async function compile(name, opts) {
126
+ const result = await doCompile(name, opts);
123
127
  return result.code;
124
128
  }
125
129
  exports.compile = compile;
@@ -1,4 +1,6 @@
1
+ import { CustomNetwork } from './CustomNetwork';
1
2
  import { Plugin } from './Plugin';
2
3
  export interface Config {
3
- plugins: Plugin[];
4
+ plugins?: Plugin[];
5
+ network?: 'mainnet' | 'testnet' | CustomNetwork;
4
6
  }
@@ -0,0 +1,6 @@
1
+ export type CustomNetwork = {
2
+ endpoint: string;
3
+ version?: 'v2' | 'v4';
4
+ key?: string;
5
+ type?: 'mainnet' | 'testnet' | 'custom';
6
+ };
@@ -0,0 +1,2 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
package/dist/index.d.ts CHANGED
@@ -1,10 +1,11 @@
1
1
  export { tonDeepLink, sleep } from './utils';
2
2
  export { NetworkProvider } from './network/NetworkProvider';
3
3
  export { createNetworkProvider } from './network/createNetworkProvider';
4
- export { compile } from './compile/compile';
5
- export { CompilerConfig } from './compile/CompilerConfig';
4
+ export { compile, CompileOpts } from './compile/compile';
5
+ export { CompilerConfig, HookParams } from './compile/CompilerConfig';
6
6
  export { UIProvider } from './ui/UIProvider';
7
7
  export { Config } from './config/Config';
8
- export { Args, Runner } from './cli/Runner';
8
+ export { Args, Runner, RunnerContext } from './cli/Runner';
9
9
  export { PluginRunner, Plugin } from './config/Plugin';
10
+ export { CustomNetwork } from './config/CustomNetwork';
10
11
  export { buildOne, buildAll } from './build';
@@ -1,6 +1,7 @@
1
1
  import arg from 'arg';
2
2
  import { UIProvider } from '../ui/UIProvider';
3
3
  import { NetworkProvider } from './NetworkProvider';
4
+ import { Config } from '../config/Config';
4
5
  export declare const argSpec: {
5
6
  '--mainnet': BooleanConstructor;
6
7
  '--testnet': BooleanConstructor;
@@ -18,4 +19,4 @@ export declare const argSpec: {
18
19
  '--dton': BooleanConstructor;
19
20
  };
20
21
  export type Args = arg.Result<typeof argSpec>;
21
- export declare function createNetworkProvider(ui: UIProvider, args: Args, allowCustom?: boolean): Promise<NetworkProvider>;
22
+ export declare function createNetworkProvider(ui: UIProvider, args: Args, config?: Config, allowCustom?: boolean): Promise<NetworkProvider>;
@@ -198,9 +198,10 @@ async function createMnemonicProvider(client, ui) {
198
198
  });
199
199
  }
200
200
  class NetworkProviderBuilder {
201
- constructor(args, ui, allowCustom = true) {
201
+ constructor(args, ui, config, allowCustom = true) {
202
202
  this.args = args;
203
203
  this.ui = ui;
204
+ this.config = config;
204
205
  this.allowCustom = allowCustom;
205
206
  }
206
207
  async chooseNetwork() {
@@ -209,14 +210,18 @@ class NetworkProviderBuilder {
209
210
  testnet: this.args['--testnet'],
210
211
  custom: this.args['--custom'] !== undefined,
211
212
  });
212
- if (!network) {
213
- network = await this.ui.choose('Which network do you want to use?', ['mainnet', 'testnet', 'custom'], (c) => c);
214
- if (network === 'custom') {
215
- const defaultCustomEndpoint = 'http://localhost:8081/';
216
- this.args['--custom'] = (await this.ui.input(`Provide a custom API v2 endpoint (default is ${defaultCustomEndpoint})`)).trim();
217
- if (this.args['--custom'] === '')
218
- this.args['--custom'] = defaultCustomEndpoint;
219
- }
213
+ if (network !== undefined) {
214
+ return network;
215
+ }
216
+ if (this.config?.network !== undefined) {
217
+ return typeof this.config.network === 'string' ? this.config.network : 'custom';
218
+ }
219
+ network = await this.ui.choose('Which network do you want to use?', ['mainnet', 'testnet', 'custom'], (c) => c);
220
+ if (network === 'custom') {
221
+ const defaultCustomEndpoint = 'http://localhost:8081/';
222
+ this.args['--custom'] = (await this.ui.input(`Provide a custom API v2 endpoint (default is ${defaultCustomEndpoint})`)).trim();
223
+ if (this.args['--custom'] === '')
224
+ this.args['--custom'] = defaultCustomEndpoint;
220
225
  }
221
226
  return network;
222
227
  }
@@ -290,26 +295,50 @@ class NetworkProviderBuilder {
290
295
  }
291
296
  let tc;
292
297
  if (network === 'custom') {
293
- const endpoint = this.args['--custom'];
294
- if (this.args['--custom-version'] === undefined || this.args['--custom-version'].toLowerCase() === 'v2') {
298
+ let configNetwork = undefined;
299
+ if (this.config?.network !== undefined && typeof this.config.network !== 'string') {
300
+ configNetwork = this.config.network;
301
+ }
302
+ if (this.args['--custom'] !== undefined) {
303
+ const inputVer = this.args['--custom-version'];
304
+ let version = undefined;
305
+ if (inputVer !== undefined) {
306
+ version = inputVer.toLowerCase(); // checks come later
307
+ }
308
+ const inputType = this.args['--custom-type'];
309
+ let type = undefined;
310
+ if (inputType !== undefined) {
311
+ type = inputType; // checks come later
312
+ }
313
+ configNetwork = {
314
+ endpoint: this.args['--custom'],
315
+ version,
316
+ key: this.args['--custom-key'],
317
+ type,
318
+ };
319
+ }
320
+ if (configNetwork === undefined) {
321
+ throw new Error('Custom network is (somehow) undefined');
322
+ }
323
+ if (configNetwork.version === undefined || configNetwork.version === 'v2') {
295
324
  tc = new ton_1.TonClient({
296
- endpoint: endpoint + 'jsonRPC',
297
- apiKey: this.args['--custom-key'],
325
+ endpoint: configNetwork.endpoint + 'jsonRPC',
326
+ apiKey: configNetwork.key,
298
327
  });
299
328
  }
300
- else if (this.args['--custom-version'].toLowerCase() === 'v4') {
301
- if (this.args['--custom-key'] !== undefined) {
329
+ else if (configNetwork.version === 'v4') {
330
+ if (configNetwork.key !== undefined) {
302
331
  throw new Error('Cannot use a custom API key with a v4 API');
303
332
  }
304
333
  tc = new ton_1.TonClient4({
305
- endpoint,
334
+ endpoint: configNetwork.endpoint,
306
335
  });
307
336
  }
308
337
  else {
309
- throw new Error('Unknown API version: ' + this.args['--custom-version']);
338
+ throw new Error('Unknown API version: ' + configNetwork.version);
310
339
  }
311
- if (this.args['--custom-type'] !== undefined) {
312
- const ct = this.args['--custom-type'].toLowerCase();
340
+ if (configNetwork.type !== undefined) {
341
+ const ct = configNetwork.type.toLowerCase();
313
342
  if (!['mainnet', 'testnet', 'custom'].includes(ct)) {
314
343
  throw new Error('Unknown network type: ' + ct);
315
344
  }
@@ -339,7 +368,7 @@ class NetworkProviderBuilder {
339
368
  return new NetworkProviderImpl(tc, sender, network, explorer, this.ui);
340
369
  }
341
370
  }
342
- async function createNetworkProvider(ui, args, allowCustom = true) {
343
- return await new NetworkProviderBuilder(args, ui, allowCustom).build();
371
+ async function createNetworkProvider(ui, args, config, allowCustom = true) {
372
+ return await new NetworkProviderBuilder(args, ui, config, allowCustom).build();
344
373
  }
345
374
  exports.createNetworkProvider = createNetworkProvider;
@@ -0,0 +1,7 @@
1
+ {{name}}.compile.ts
2
+ import { CompilerConfig } from '@ton/blueprint';
3
+
4
+ export const compile: CompilerConfig = {
5
+ lang: 'func',
6
+ targets: [{{targets}}],
7
+ };
@@ -1,9 +1,11 @@
1
1
  import { UIProvider } from '../ui/UIProvider';
2
+ import { Address } from '@ton/core';
2
3
  export declare class InquirerUIProvider implements UIProvider {
3
4
  #private;
4
5
  constructor();
5
6
  write(message: string): void;
6
7
  prompt(message: string): Promise<boolean>;
8
+ inputAddress(message: string, fallback?: Address): Promise<Address>;
7
9
  input(message: string): Promise<string>;
8
10
  choose<T>(message: string, choices: T[], display: (v: T) => string): Promise<T>;
9
11
  setActionPrompt(message: string): void;
@@ -17,6 +17,7 @@ var _InquirerUIProvider_bottomBar;
17
17
  Object.defineProperty(exports, "__esModule", { value: true });
18
18
  exports.InquirerUIProvider = void 0;
19
19
  const inquirer_1 = __importDefault(require("inquirer"));
20
+ const core_1 = require("@ton/core");
20
21
  class DestroyableBottomBar extends inquirer_1.default.ui.BottomBar {
21
22
  destroy() {
22
23
  this.close();
@@ -38,6 +39,18 @@ class InquirerUIProvider {
38
39
  });
39
40
  return prompt;
40
41
  }
42
+ async inputAddress(message, fallback) {
43
+ const prompt = message + (fallback === undefined ? '' : ` (default: ${fallback})`);
44
+ while (true) {
45
+ const addr = (await this.input(prompt)).trim();
46
+ try {
47
+ return addr === '' && fallback !== undefined ? fallback : core_1.Address.parse(addr);
48
+ }
49
+ catch (e) {
50
+ this.write(addr + ' is not valid!\n');
51
+ }
52
+ }
53
+ }
41
54
  async input(message) {
42
55
  const { val } = await inquirer_1.default.prompt({
43
56
  name: 'val',
@@ -1,6 +1,8 @@
1
+ import { Address } from '@ton/core';
1
2
  export interface UIProvider {
2
3
  write(message: string): void;
3
4
  prompt(message: string): Promise<boolean>;
5
+ inputAddress(message: string, fallback?: Address): Promise<Address>;
4
6
  input(message: string): Promise<string>;
5
7
  choose<T>(message: string, choices: T[], display: (v: T) => string): Promise<T>;
6
8
  setActionPrompt(message: string): void;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ton/blueprint",
3
- "version": "0.15.0",
3
+ "version": "0.16.0",
4
4
  "description": "Framework for development of TON smart contracts",
5
5
  "main": "dist/index.js",
6
6
  "bin": "./dist/cli/cli.js",