@ton/blueprint 0.15.0 → 0.17.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,32 @@ 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.17.0] - 2024-03-01
9
+
10
+ This release contains a breaking change.
11
+
12
+ ### Changed
13
+
14
+ - Blueprint no longer automatically adds `jsonRPC` to custom v2 endpoints
15
+
16
+ ### Added
17
+
18
+ - Added `set` command which can currently set func version (run `blueprint set func`)
19
+ - Added `open` and `getTransactions` to `WrappedContractProvider`
20
+ - Added cell hash to build artifacts
21
+
22
+ ## [0.16.0] - 2024-02-15
23
+
24
+ ### Added
25
+
26
+ - 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
27
+ - Added the `convert` command which attempts to convert a legacy bash build script into a blueprint `.compile.ts` file
28
+ - Added the ability to pass any user data into the compile hooks
29
+
30
+ ### Changed
31
+
32
+ - Improved the `verify` command
33
+
8
34
  ## [0.15.0] - 2023-12-15
9
35
 
10
36
  ### Added
package/README.md CHANGED
@@ -103,26 +103,61 @@ 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
+ The above config parameters are equivalent to the arguments in the following command:
155
+ ```bash
156
+ npx blueprint run --custom https://toncenter.com/api/v2/ --custom-version v2 --custom-type mainnet --custom-key YOUR_API_KEY
157
+ ```
158
+
159
+ Properties of the `network` object have the same semantics as the `--custom` flags with respective names (see `blueprint help run`).
160
+
126
161
  ## Contributors
127
162
 
128
163
  Special thanks to [@qdevstudio](https://t.me/qdevstudio) for their logo for blueprint.
package/dist/build.js CHANGED
@@ -28,13 +28,17 @@ async function buildOne(contract, ui) {
28
28
  }
29
29
  }
30
30
  const cell = result.code;
31
+ const rHash = cell.hash();
32
+ const res = {
33
+ hash: rHash.toString('hex'),
34
+ hashBase64: rHash.toString('base64'),
35
+ hex: cell.toBoc().toString('hex')
36
+ };
31
37
  ui?.clearActionPrompt();
32
- ui?.write('\n✅ Compiled successfully! Cell BOC hex result:\n\n');
33
- ui?.write(cell.toBoc().toString('hex'));
38
+ ui?.write('\n✅ Compiled successfully! Cell BOC result:\n\n');
39
+ ui?.write(JSON.stringify(res, null, 2));
34
40
  await promises_1.default.mkdir(paths_1.BUILD_DIR, { recursive: true });
35
- await promises_1.default.writeFile(buildArtifactPath, JSON.stringify({
36
- hex: cell.toBoc().toString('hex'),
37
- }));
41
+ await promises_1.default.writeFile(buildArtifactPath, JSON.stringify(res));
38
42
  ui?.write(`\n✅ Wrote compilation artifact to ${path_1.default.relative(process.cwd(), buildArtifactPath)}`);
39
43
  }
40
44
  catch (e) {
@@ -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
@@ -34,8 +34,10 @@ const chalk_1 = __importDefault(require("chalk"));
34
34
  const create_1 = require("./create");
35
35
  const run_1 = require("./run");
36
36
  const build_1 = require("./build");
37
+ const set_1 = require("./set");
37
38
  const test_1 = require("./test");
38
39
  const verify_1 = require("./verify");
40
+ const convert_1 = require("./convert");
39
41
  const help_1 = require("./help");
40
42
  const InquirerUIProvider_1 = require("../ui/InquirerUIProvider");
41
43
  const Runner_1 = require("./Runner");
@@ -44,9 +46,11 @@ const runners = {
44
46
  create: create_1.create,
45
47
  run: run_1.run,
46
48
  build: build_1.build,
49
+ set: set_1.set,
47
50
  test: test_1.test,
48
51
  help: help_1.help,
49
52
  verify: verify_1.verify,
53
+ convert: convert_1.convert,
50
54
  };
51
55
  async function main() {
52
56
  var _a;
@@ -59,12 +63,14 @@ async function main() {
59
63
  process.exit(0);
60
64
  }
61
65
  let effectiveRunners = {};
66
+ const runnerContext = {};
62
67
  try {
63
68
  const configModule = await (_a = path_1.default.join(process.cwd(), 'blueprint.config.ts'), Promise.resolve().then(() => __importStar(require(_a))));
64
69
  try {
65
70
  if ('config' in configModule && typeof configModule.config === 'object') {
66
71
  const config = configModule.config;
67
- for (const plugin of config.plugins) {
72
+ runnerContext.config = config;
73
+ for (const plugin of config.plugins ?? []) {
68
74
  for (const runner of plugin.runners()) {
69
75
  effectiveRunners[runner.name] = runner.runner;
70
76
  help_1.additionalHelpMessages[runner.name] = runner.help;
@@ -91,7 +97,7 @@ async function main() {
91
97
  process.exit(1);
92
98
  }
93
99
  const ui = new InquirerUIProvider_1.InquirerUIProvider();
94
- await runner(args, ui);
100
+ await runner(args, ui, runnerContext);
95
101
  ui.close();
96
102
  }
97
103
  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
@@ -13,9 +13,11 @@ List of available commands:
13
13
  - create
14
14
  - run
15
15
  - build
16
+ - custom
16
17
  - help
17
18
  - test
18
- - verify`,
19
+ - verify
20
+ - convert`,
19
21
  create: `Usage: blueprint create [contract name] [flags]
20
22
 
21
23
  Creates a new contract together with supporting files according to a template.
@@ -48,6 +50,9 @@ If contract name is not specified on the command line, the buildable contracts (
48
50
 
49
51
  Flags:
50
52
  --all - builds all buildable contracts instead of just one.`,
53
+ set: `Usage: blueprint set <key> [value]
54
+ Available keys:
55
+ - func - overrides @ton-community/func-js-bin version, effectively setting the func version. The required version may be passed as the value, otherwise available versions will be displayed.`,
51
56
  test: `Usage: blueprint test
52
57
 
53
58
  Just runs \`npm test\`, which by default runs \`jest\`.`,
@@ -61,6 +66,8 @@ Flags:
61
66
  --custom-version - specifies the API version to use with the custom API. Options: v2 (defualt), v4.
62
67
  --custom-key - specifies the API key to use with the custom API, can only be used with API v2.
63
68
  --custom-type - specifies the network type to be indicated to scripts. Options: mainnet, testnet.`,
69
+ convert: `Usage: blueprint convert [path to build script]
70
+ Atempts to convert legacy bash build script to a blueprint compile wrapper.`,
64
71
  };
65
72
  exports.additionalHelpMessages = {};
66
73
  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;
@@ -0,0 +1,2 @@
1
+ import { Runner } from './Runner';
2
+ export declare const set: Runner;
@@ -0,0 +1,104 @@
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.set = void 0;
7
+ const promises_1 = require("fs/promises");
8
+ const node_child_process_1 = require("node:child_process");
9
+ const path_1 = __importDefault(require("path"));
10
+ const getVersions = (pkg, ui) => {
11
+ return new Promise((resolve, reject) => {
12
+ (0, node_child_process_1.exec)(`npm view ${pkg} versions --json`, (error, stdout, stderr) => {
13
+ if (stderr) {
14
+ ui.write(stderr);
15
+ }
16
+ if (stdout) {
17
+ if (error === null) {
18
+ try {
19
+ const resJson = JSON.parse(stdout);
20
+ if (Array.isArray(resJson)) {
21
+ resolve(resJson);
22
+ }
23
+ else {
24
+ reject(new TypeError("Expect json array on stdout, but got:\n" + stdout));
25
+ }
26
+ }
27
+ catch (e) {
28
+ reject(e);
29
+ }
30
+ return;
31
+ }
32
+ else {
33
+ ui.write(stdout);
34
+ }
35
+ }
36
+ if (error) {
37
+ ui.write("Failed to get func-js-bin package versions!");
38
+ reject(error);
39
+ }
40
+ });
41
+ });
42
+ };
43
+ const install = (cmd, ui) => {
44
+ return new Promise((resolve, reject) => {
45
+ (0, node_child_process_1.exec)(cmd, (error, stdout, stderr) => {
46
+ if (stderr) {
47
+ ui.write(stderr);
48
+ }
49
+ if (stdout) {
50
+ ui.write(stdout);
51
+ }
52
+ if (error) {
53
+ reject(error);
54
+ return;
55
+ }
56
+ resolve();
57
+ });
58
+ });
59
+ };
60
+ const set = async (args, ui) => {
61
+ if (args._.length < 2) {
62
+ throw new Error('Please pass a key');
63
+ }
64
+ switch (args._[1]) {
65
+ case 'func': {
66
+ const pkg = '@ton-community/func-js-bin';
67
+ const funcVersions = await getVersions(pkg, ui);
68
+ let version = args._.length > 2 ? args._[2] : '';
69
+ if (!funcVersions.includes(version)) {
70
+ version = await ui.choose('Choose FunC version', funcVersions, (s) => s);
71
+ }
72
+ const packagePath = path_1.default.join(process.cwd(), 'package.json');
73
+ const packageContents = (await (0, promises_1.readFile)(packagePath)).toString('utf-8');
74
+ const parsedPackage = JSON.parse(packageContents);
75
+ const packageManager = await ui.choose('Choose your package manager', ['npm', 'yarn', 'pnpm', 'other'], (s) => s);
76
+ if (packageManager === 'other') {
77
+ ui.write(`Please find out how to override @ton-community/func-js-bin version to ${version} using your package manager, do that, and then install the packages`);
78
+ return;
79
+ }
80
+ const overrideKey = packageManager === 'yarn' ? 'resolutions' : 'overrides';
81
+ parsedPackage[overrideKey] = {
82
+ ...parsedPackage[overrideKey],
83
+ [pkg]: version,
84
+ };
85
+ ui.write('Updating package.json...');
86
+ await (0, promises_1.writeFile)(packagePath, JSON.stringify(parsedPackage, null, 4));
87
+ const installCmd = packageManager === 'yarn' ? 'yarn' : `${packageManager} i`;
88
+ try {
89
+ ui.write('Installing dependencies...');
90
+ await install(installCmd, ui);
91
+ }
92
+ catch (e) {
93
+ ui.write('Failed to install dependencies, rolling back package.json');
94
+ await (0, promises_1.writeFile)(packagePath, packageContents);
95
+ throw e;
96
+ }
97
+ break;
98
+ }
99
+ default: {
100
+ throw new Error('Unknown key: ' + args._[1]);
101
+ }
102
+ }
103
+ };
104
+ exports.set = set;
@@ -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>;
@@ -13,7 +13,7 @@ var __classPrivateFieldGet = (this && this.__classPrivateFieldGet) || function (
13
13
  var __importDefault = (this && this.__importDefault) || function (mod) {
14
14
  return (mod && mod.__esModule) ? mod : { "default": mod };
15
15
  };
16
- var _SendProviderSender_provider, _WrappedContractProvider_address, _WrappedContractProvider_provider, _WrappedContractProvider_init, _NetworkProviderImpl_tc, _NetworkProviderImpl_sender, _NetworkProviderImpl_network, _NetworkProviderImpl_explorer, _NetworkProviderImpl_ui;
16
+ var _SendProviderSender_provider, _WrappedContractProvider_address, _WrappedContractProvider_provider, _WrappedContractProvider_init, _WrappedContractProvider_factory, _NetworkProviderImpl_tc, _NetworkProviderImpl_sender, _NetworkProviderImpl_network, _NetworkProviderImpl_explorer, _NetworkProviderImpl_ui;
17
17
  Object.defineProperty(exports, "__esModule", { value: true });
18
18
  exports.createNetworkProvider = exports.argSpec = void 0;
19
19
  const utils_1 = require("../utils");
@@ -63,13 +63,15 @@ class SendProviderSender {
63
63
  }
64
64
  _SendProviderSender_provider = new WeakMap();
65
65
  class WrappedContractProvider {
66
- constructor(address, provider, init) {
66
+ constructor(address, factory, init) {
67
67
  _WrappedContractProvider_address.set(this, void 0);
68
68
  _WrappedContractProvider_provider.set(this, void 0);
69
69
  _WrappedContractProvider_init.set(this, void 0);
70
+ _WrappedContractProvider_factory.set(this, void 0);
70
71
  __classPrivateFieldSet(this, _WrappedContractProvider_address, address, "f");
71
- __classPrivateFieldSet(this, _WrappedContractProvider_provider, provider, "f");
72
+ __classPrivateFieldSet(this, _WrappedContractProvider_provider, factory({ address, init }), "f");
72
73
  __classPrivateFieldSet(this, _WrappedContractProvider_init, init, "f");
74
+ __classPrivateFieldSet(this, _WrappedContractProvider_factory, factory, "f");
73
75
  }
74
76
  async getState() {
75
77
  return await __classPrivateFieldGet(this, _WrappedContractProvider_provider, "f").getState();
@@ -91,8 +93,14 @@ class WrappedContractProvider {
91
93
  body: typeof args.body === 'string' ? (0, core_1.comment)(args.body) : args.body,
92
94
  });
93
95
  }
96
+ open(contract) {
97
+ return (0, core_1.openContract)(contract, (params) => new WrappedContractProvider(params.address, __classPrivateFieldGet(this, _WrappedContractProvider_factory, "f"), params.init));
98
+ }
99
+ getTransactions(address, lt, hash, limit) {
100
+ return __classPrivateFieldGet(this, _WrappedContractProvider_provider, "f").getTransactions(address, lt, hash, limit);
101
+ }
94
102
  }
95
- _WrappedContractProvider_address = new WeakMap(), _WrappedContractProvider_provider = new WeakMap(), _WrappedContractProvider_init = new WeakMap();
103
+ _WrappedContractProvider_address = new WeakMap(), _WrappedContractProvider_provider = new WeakMap(), _WrappedContractProvider_init = new WeakMap(), _WrappedContractProvider_factory = new WeakMap();
96
104
  class NetworkProviderImpl {
97
105
  constructor(tc, sender, network, explorer, ui) {
98
106
  _NetworkProviderImpl_tc.set(this, void 0);
@@ -119,12 +127,8 @@ class NetworkProviderImpl {
119
127
  return __classPrivateFieldGet(this, _NetworkProviderImpl_tc, "f");
120
128
  }
121
129
  provider(address, init) {
122
- if (__classPrivateFieldGet(this, _NetworkProviderImpl_tc, "f") instanceof ton_1.TonClient4) {
123
- return new WrappedContractProvider(address, __classPrivateFieldGet(this, _NetworkProviderImpl_tc, "f").provider(address, init ? { code: init.code ?? new core_1.Cell(), data: init.data ?? new core_1.Cell() } : undefined), init);
124
- }
125
- else {
126
- return new WrappedContractProvider(address, __classPrivateFieldGet(this, _NetworkProviderImpl_tc, "f").provider(address, { code: init?.code ?? new core_1.Cell(), data: init?.data ?? new core_1.Cell() }), init);
127
- }
130
+ const factory = (params) => __classPrivateFieldGet(this, _NetworkProviderImpl_tc, "f").provider(params.address, params.init);
131
+ return new WrappedContractProvider(address, factory, init);
128
132
  }
129
133
  async isContractDeployed(address) {
130
134
  if (__classPrivateFieldGet(this, _NetworkProviderImpl_tc, "f") instanceof ton_1.TonClient4) {
@@ -176,7 +180,7 @@ class NetworkProviderImpl {
176
180
  await this.waitForDeploy(contract.address, waitAttempts);
177
181
  }
178
182
  open(contract) {
179
- return (0, core_1.openContract)(contract, (params) => this.provider(params.address, params.init ?? undefined));
183
+ return (0, core_1.openContract)(contract, (params) => this.provider(params.address, params.init ?? null));
180
184
  }
181
185
  ui() {
182
186
  return __classPrivateFieldGet(this, _NetworkProviderImpl_ui, "f");
@@ -198,9 +202,10 @@ async function createMnemonicProvider(client, ui) {
198
202
  });
199
203
  }
200
204
  class NetworkProviderBuilder {
201
- constructor(args, ui, allowCustom = true) {
205
+ constructor(args, ui, config, allowCustom = true) {
202
206
  this.args = args;
203
207
  this.ui = ui;
208
+ this.config = config;
204
209
  this.allowCustom = allowCustom;
205
210
  }
206
211
  async chooseNetwork() {
@@ -209,14 +214,18 @@ class NetworkProviderBuilder {
209
214
  testnet: this.args['--testnet'],
210
215
  custom: this.args['--custom'] !== undefined,
211
216
  });
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
- }
217
+ if (network !== undefined) {
218
+ return network;
219
+ }
220
+ if (this.config?.network !== undefined) {
221
+ return typeof this.config.network === 'string' ? this.config.network : 'custom';
222
+ }
223
+ network = await this.ui.choose('Which network do you want to use?', ['mainnet', 'testnet', 'custom'], (c) => c);
224
+ if (network === 'custom') {
225
+ const defaultCustomEndpoint = 'http://localhost:8081/';
226
+ this.args['--custom'] = (await this.ui.input(`Provide a custom API v2 endpoint (default is ${defaultCustomEndpoint})`)).trim();
227
+ if (this.args['--custom'] === '')
228
+ this.args['--custom'] = defaultCustomEndpoint;
220
229
  }
221
230
  return network;
222
231
  }
@@ -290,26 +299,50 @@ class NetworkProviderBuilder {
290
299
  }
291
300
  let tc;
292
301
  if (network === 'custom') {
293
- const endpoint = this.args['--custom'];
294
- if (this.args['--custom-version'] === undefined || this.args['--custom-version'].toLowerCase() === 'v2') {
302
+ let configNetwork = undefined;
303
+ if (this.config?.network !== undefined && typeof this.config.network !== 'string') {
304
+ configNetwork = this.config.network;
305
+ }
306
+ if (this.args['--custom'] !== undefined) {
307
+ const inputVer = this.args['--custom-version'];
308
+ let version = undefined;
309
+ if (inputVer !== undefined) {
310
+ version = inputVer.toLowerCase(); // checks come later
311
+ }
312
+ const inputType = this.args['--custom-type'];
313
+ let type = undefined;
314
+ if (inputType !== undefined) {
315
+ type = inputType; // checks come later
316
+ }
317
+ configNetwork = {
318
+ endpoint: this.args['--custom'],
319
+ version,
320
+ key: this.args['--custom-key'],
321
+ type,
322
+ };
323
+ }
324
+ if (configNetwork === undefined) {
325
+ throw new Error('Custom network is (somehow) undefined');
326
+ }
327
+ if (configNetwork.version === undefined || configNetwork.version === 'v2') {
295
328
  tc = new ton_1.TonClient({
296
- endpoint: endpoint + 'jsonRPC',
297
- apiKey: this.args['--custom-key'],
329
+ endpoint: configNetwork.endpoint,
330
+ apiKey: configNetwork.key,
298
331
  });
299
332
  }
300
- else if (this.args['--custom-version'].toLowerCase() === 'v4') {
301
- if (this.args['--custom-key'] !== undefined) {
333
+ else if (configNetwork.version === 'v4') {
334
+ if (configNetwork.key !== undefined) {
302
335
  throw new Error('Cannot use a custom API key with a v4 API');
303
336
  }
304
337
  tc = new ton_1.TonClient4({
305
- endpoint,
338
+ endpoint: configNetwork.endpoint,
306
339
  });
307
340
  }
308
341
  else {
309
- throw new Error('Unknown API version: ' + this.args['--custom-version']);
342
+ throw new Error('Unknown API version: ' + configNetwork.version);
310
343
  }
311
- if (this.args['--custom-type'] !== undefined) {
312
- const ct = this.args['--custom-type'].toLowerCase();
344
+ if (configNetwork.type !== undefined) {
345
+ const ct = configNetwork.type.toLowerCase();
313
346
  if (!['mainnet', 'testnet', 'custom'].includes(ct)) {
314
347
  throw new Error('Unknown network type: ' + ct);
315
348
  }
@@ -339,7 +372,7 @@ class NetworkProviderBuilder {
339
372
  return new NetworkProviderImpl(tc, sender, network, explorer, this.ui);
340
373
  }
341
374
  }
342
- async function createNetworkProvider(ui, args, allowCustom = true) {
343
- return await new NetworkProviderBuilder(args, ui, allowCustom).build();
375
+ async function createNetworkProvider(ui, args, config, allowCustom = true) {
376
+ return await new NetworkProviderBuilder(args, ui, config, allowCustom).build();
344
377
  }
345
378
  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.17.0",
4
4
  "description": "Framework for development of TON smart contracts",
5
5
  "main": "dist/index.js",
6
6
  "bin": "./dist/cli/cli.js",
@@ -19,9 +19,9 @@
19
19
  "format": "prettier --write src"
20
20
  },
21
21
  "devDependencies": {
22
- "@ton/core": "^0.49.2",
22
+ "@ton/core": "^0.56.0",
23
23
  "@ton/crypto": "^3.2.0",
24
- "@ton/ton": "^13.5.1",
24
+ "@ton/ton": "^13.11.0",
25
25
  "@types/inquirer": "^8.2.6",
26
26
  "@types/node": "^20.2.5",
27
27
  "@types/qrcode-terminal": "^0.12.0",
@@ -29,9 +29,9 @@
29
29
  "typescript": "^4.9.5"
30
30
  },
31
31
  "peerDependencies": {
32
- "@ton/core": ">=0.49.2",
32
+ "@ton/core": ">=0.56.0",
33
33
  "@ton/crypto": ">=3.2.0",
34
- "@ton/ton": ">=13.4.1"
34
+ "@ton/ton": ">=13.11.0"
35
35
  },
36
36
  "dependencies": {
37
37
  "@orbs-network/ton-access": "^2.3.3",