Showing preview only (414K chars total). The displayed content is truncated. Use the JSON API for full output.
Repository: ton-org/blueprint
Branch: develop
Commit: def4bb51d4d9
Files: 133
Total size: 380.6 KB
Directory structure:
gitextract_fv1vau06/
├── .github/
│ ├── ISSUE_TEMPLATE/
│ │ ├── bug_report.yml
│ │ ├── docs_update.yml
│ │ ├── feature_request.yml
│ │ └── generic.yml
│ ├── pull_request_template.md
│ └── workflows/
│ ├── publish.yml
│ ├── qa.yaml
│ ├── reward-merged-prs.yml
│ └── reward.yml
├── .gitignore
├── .yarnrc.yml
├── CHANGELOG.md
├── LICENSE
├── README.md
├── contributing.md
├── eslint.config.js
├── example/
│ ├── .gitignore
│ ├── .prettierignore
│ ├── .prettierrc
│ ├── .snapshot/
│ │ └── 1748254056557.json
│ ├── README.md
│ ├── blueprint.config.ts
│ ├── compilables/
│ │ └── Counter.compile.ts
│ ├── contract.abi.json
│ ├── contracts/
│ │ ├── counter.fc
│ │ └── imports/
│ │ └── stdlib.fc
│ ├── gas-report.json
│ ├── jest.config.ts
│ ├── package.json
│ ├── scripts/
│ │ ├── deployCounter.ts
│ │ └── incrementCounter.ts
│ ├── tests/
│ │ └── Counter.spec.ts
│ ├── tsconfig.json
│ └── wrappers/
│ └── Counter.ts
├── jest.config.js
├── package.json
├── src/
│ ├── build.ts
│ ├── cli/
│ │ ├── Runner.ts
│ │ ├── build.ts
│ │ ├── cli.ts
│ │ ├── constants.ts
│ │ ├── convert.ts
│ │ ├── create.ts
│ │ ├── help.ts
│ │ ├── pack.ts
│ │ ├── rename.spec.ts
│ │ ├── rename.ts
│ │ ├── run.ts
│ │ ├── set.ts
│ │ ├── snapshot.ts
│ │ ├── test.ts
│ │ └── verify.ts
│ ├── compile/
│ │ ├── CompilerConfig.ts
│ │ ├── SourceSnapshot.ts
│ │ ├── SupportedLang.ts
│ │ ├── compile.ts
│ │ ├── func/
│ │ │ ├── compile.func.ts
│ │ │ └── config.ts
│ │ ├── tact/
│ │ │ ├── OverwritableVirtualFileSystem.ts
│ │ │ ├── compile.tact.ts
│ │ │ └── config.ts
│ │ └── tolk/
│ │ ├── compile.tolk.ts
│ │ └── config.ts
│ ├── config/
│ │ ├── Config.ts
│ │ ├── CustomNetwork.ts
│ │ ├── Plugin.ts
│ │ ├── tact.config.ts
│ │ └── utils.ts
│ ├── index.ts
│ ├── jest/
│ │ ├── CoverageReporter.ts
│ │ └── coverageSetup.ts
│ ├── network/
│ │ ├── Explorer.ts
│ │ ├── Network.ts
│ │ ├── NetworkProvider.ts
│ │ ├── NetworkVersion.ts
│ │ ├── constants.ts
│ │ ├── createNetworkProvider.ts
│ │ ├── send/
│ │ │ ├── DeeplinkProvider.ts
│ │ │ ├── MnemonicProvider.ts
│ │ │ ├── SendProvider.ts
│ │ │ ├── TonConnectProvider.ts
│ │ │ └── wallets.ts
│ │ ├── storage/
│ │ │ ├── FSStorage.ts
│ │ │ └── Storage.ts
│ │ └── utils.ts
│ ├── paths.ts
│ ├── template.spec.ts
│ ├── template.ts
│ ├── templates/
│ │ ├── func/
│ │ │ ├── common/
│ │ │ │ ├── compilables/
│ │ │ │ │ └── compile.ts.template
│ │ │ │ └── contracts/
│ │ │ │ └── imports/
│ │ │ │ └── stdlib.fc.template
│ │ │ ├── counter/
│ │ │ │ ├── contracts/
│ │ │ │ │ └── contract.fc.template
│ │ │ │ ├── scripts/
│ │ │ │ │ ├── deploy.ts.template
│ │ │ │ │ └── increment.ts.template
│ │ │ │ ├── tests/
│ │ │ │ │ └── spec.ts.template
│ │ │ │ └── wrappers/
│ │ │ │ └── wrapper.ts.template
│ │ │ ├── empty/
│ │ │ │ ├── contracts/
│ │ │ │ │ └── contract.fc.template
│ │ │ │ ├── scripts/
│ │ │ │ │ └── deploy.ts.template
│ │ │ │ ├── tests/
│ │ │ │ │ └── spec.ts.template
│ │ │ │ └── wrappers/
│ │ │ │ └── wrapper.ts.template
│ │ │ ├── legacy/
│ │ │ │ └── wrappers/
│ │ │ │ └── compile.ts.template
│ │ │ └── not-separated-common/
│ │ │ ├── contracts/
│ │ │ │ └── imports/
│ │ │ │ └── stdlib.fc.template
│ │ │ └── wrappers/
│ │ │ └── compile.ts.template
│ │ ├── tact/
│ │ │ ├── counter/
│ │ │ │ ├── contracts/
│ │ │ │ │ └── contract.tact.template
│ │ │ │ ├── scripts/
│ │ │ │ │ ├── deploy.ts.template
│ │ │ │ │ └── increment.ts.template
│ │ │ │ └── tests/
│ │ │ │ └── spec.ts.template
│ │ │ └── empty/
│ │ │ ├── contracts/
│ │ │ │ └── contract.tact.template
│ │ │ ├── scripts/
│ │ │ │ └── deploy.ts.template
│ │ │ └── tests/
│ │ │ └── spec.ts.template
│ │ └── tolk/
│ │ ├── common/
│ │ │ └── compilables/
│ │ │ └── compile.ts.template
│ │ ├── counter/
│ │ │ ├── contracts/
│ │ │ │ └── contract.tolk.template
│ │ │ ├── scripts/
│ │ │ │ ├── deploy.ts.template
│ │ │ │ ├── increment.ts.template
│ │ │ │ └── reset.ts.template
│ │ │ ├── tests/
│ │ │ │ └── spec.ts.template
│ │ │ └── wrappers/
│ │ │ └── wrapper.ts.template
│ │ ├── empty/
│ │ │ ├── contracts/
│ │ │ │ └── contract.tolk.template
│ │ │ ├── scripts/
│ │ │ │ └── deploy.ts.template
│ │ │ ├── tests/
│ │ │ │ └── spec.ts.template
│ │ │ └── wrappers/
│ │ │ └── wrapper.ts.template
│ │ └── not-separated-common/
│ │ └── wrappers/
│ │ └── compile.ts.template
│ ├── types/
│ │ └── file.ts
│ ├── ui/
│ │ ├── InquirerUIProvider.ts
│ │ └── UIProvider.ts
│ └── utils/
│ ├── file.utils.ts
│ ├── index.ts
│ ├── object.utils.ts
│ ├── selection.utils.ts
│ ├── string.utils.ts
│ ├── timer.utils.ts
│ └── ton.utils.ts
├── tonconnect/
│ └── manifest.json
└── tsconfig.json
================================================
FILE CONTENTS
================================================
================================================
FILE: .github/ISSUE_TEMPLATE/bug_report.yml
================================================
name: Bug Report
description: Report a reproducible bug or unexpected behavior
labels: ["bug"]
body:
- type: textarea
id: description
attributes:
label: Description
description: Describe the bug, including expected and actual behavior
validations:
required: true
- type: textarea
id: steps
attributes:
label: Steps to Reproduce
description: Provide a list of steps to reproduce the bug
placeholder: |
1. Run `blueprint ...`
2. Enter `...`
3. See error
validations:
required: true
- type: input
id: environment
attributes:
label: Environment
description: Provide relevant environment details (OS, runtime, etc).
placeholder: "`node -v && uname -a` e.g., Ubuntu 25.04, Node 22.16.0"
validations:
required: false
- type: textarea
id: logs
attributes:
label: Logs or Screenshots
description: Include relevant logs, stack traces, or screenshots
validations:
required: false
================================================
FILE: .github/ISSUE_TEMPLATE/docs_update.yml
================================================
name: Docs Update
description: Suggest an update or improvement to the documentation
labels: ["documentation"]
body:
- type: textarea
id: suggestion
attributes:
label: Suggested Update
description: Describe the proposed documentation update or fix
validations:
required: true
- type: textarea
id: context
attributes:
label: Additional Context
description: Add any other relevant information or context
validations:
required: false
================================================
FILE: .github/ISSUE_TEMPLATE/feature_request.yml
================================================
name: Feature Request
description: Suggest an idea or enhancement
labels: ["enhancement"]
body:
- type: textarea
id: summary
attributes:
label: Feature Summary
description: Clearly describe the feature you’d like to see added
validations:
required: true
- type: textarea
id: usecase
attributes:
label: Use Case
description: Explain why this feature is useful or what problem it solves
validations:
required: true
- type: textarea
id: alternatives
attributes:
label: Alternatives Considered
description: Have you considered other solutions or workarounds?
validations:
required: false
- type: textarea
id: additional
attributes:
label: Additional Context
description: Add any other context or screenshots here
validations:
required: false
================================================
FILE: .github/ISSUE_TEMPLATE/generic.yml
================================================
name: Generic Issue
description: Open a general issue or question
body:
- type: textarea
id: description
attributes:
label: Description
description: Provide a detailed description of the issue or question
validations:
required: true
================================================
FILE: .github/pull_request_template.md
================================================
## Issue
Closes #<issue-number>
## Checklist
Please ensure the following items are completed before requesting review:
* [ ] Updated `CHANGELOG.md` with relevant changes
* [ ] Documented the contribution in `README.md`
* [ ] Added tests to demonstrate correct behavior (both positive and negative cases)
* [ ] All tests pass successfully (`yarn test`)
* [ ] Code passes linting checks (`yarn lint`)
================================================
FILE: .github/workflows/publish.yml
================================================
on:
push:
branches:
- main
- develop
name: Publish to NPM
jobs:
publish:
name: Publish
runs-on: ubuntu-latest
environment: ${{ github.ref_name == 'main' && 'prod' || 'dev' }}
permissions:
contents: read
id-token: write
steps:
- uses: actions/checkout@v6
- name: Enable Corepack
run: corepack enable
- name: Use Node.js 18
uses: actions/setup-node@v6
with:
node-version: 24
registry-url: 'https://registry.npmjs.org'
- name: Install dependencies
run: yarn install
- name: Bump version (dev)
if: github.ref_name == 'develop'
id: version
run: |
CURRENT_VERSION=$(jq -r '.version' package.json)
BASE_VERSION=$(echo $CURRENT_VERSION | cut -d '-' -f 1)
NEW_BASE=$(echo $BASE_VERSION | awk -v OFS='.' -F. '{
$2 = $2 + 1;
$3 = 0;
print $0
}')
TIMESTAMP=$(date -u +"%Y%m%d%H%M%S")
COMMIT_SHA=$(git rev-parse --short HEAD)
NEW_VERSION="${NEW_BASE}-dev.${TIMESTAMP}.${COMMIT_SHA}"
jq ".version = \"$NEW_VERSION\"" package.json > package.tmp
mv package.tmp package.json
- name: Build
run: yarn run build
- name: Publish
run: |
npm config delete //registry.npmjs.org/:_authToken || true
if [ "${{ github.ref_name }}" = "develop" ]; then
npm publish --provenance --access public --tag dev
else
npm publish --provenance --access public
fi
================================================
FILE: .github/workflows/qa.yaml
================================================
name: QA
on:
push:
paths-ignore:
- '.github/workflows/publish.yml'
- '.github/workflows/publish-dev.yml'
- '.github/workflows/reward.yml'
- '.github/workflows/reward-merged-prs.yml'
- '.github/ISSUE_TEMPLATE/*'
- '*.md'
- '*.svg'
branches:
- main
- develop
pull_request:
paths-ignore:
- '.github/workflows/publish.yml'
- '.github/workflows/publish-dev.yml'
- '.github/workflows/reward.yml'
- '.github/workflows/reward-merged-prs.yml'
- '.github/ISSUE_TEMPLATE/*'
- '*.md'
- '*.svg'
branches:
- main
- develop
jobs:
lint:
name: Lint
runs-on: ubuntu-latest
steps:
- uses: the-ton-tech/toolchain/lint@v1.6.0
build:
needs: lint
runs-on: ${{ matrix.os }}
strategy:
matrix:
os:
- ubuntu-24.04
- windows-2025
- macos-15
name: Test & Build on ${{ matrix.os }}
steps:
- uses: the-ton-tech/toolchain/build@v1.6.0
with:
os: ${{ matrix.os }}
================================================
FILE: .github/workflows/reward-merged-prs.yml
================================================
name: Reward merged PRs
on:
workflow_dispatch:
inputs:
per_page:
description: 'Number of merged PRs to process'
required: false
default: '15000' # Default value shown in UI
type: string
jobs:
reward:
runs-on: ubuntu-latest
permissions:
contents: read
pull-requests: write
steps:
- uses: ton-society/gh-ton-contribution-reward/.github/actions/rewards-for-merged-prs@v1
with:
per_page: ${{ inputs.per_page }}
activity_id: '9747'
github_token: ${{ secrets.GITHUB_TOKEN }}
x_api_key: ${{ secrets.X_API_KEY }}
x_partner_id: ${{ secrets.X_PARTNER_ID }}
================================================
FILE: .github/workflows/reward.yml
================================================
name: Reward
on:
push:
branches:
- main
- develop
jobs:
reward:
name: Reward
runs-on: ubuntu-latest
permissions:
contents: read
pull-requests: write
steps:
- uses: ton-society/gh-ton-contribution-reward@v1
with:
activity_id: '9747'
github_token: ${{ secrets.GITHUB_TOKEN }}
x_api_key: ${{ secrets.X_API_KEY }}
x_partner_id: ${{ secrets.X_PARTNER_ID }}
================================================
FILE: .gitignore
================================================
node_modules
dist
.idea
coverage/
# yarn
yarn-error.log
.yarn/
package.tgz
================================================
FILE: .yarnrc.yml
================================================
nodeLinker: node-modules
enableScripts: false
================================================
FILE: CHANGELOG.md
================================================
# Changelog
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.2] - 2026-04-16
- Pin axios to version 1.15.0 to prevent installation of critically vulnerable version 1.14.0 (CVE-2026-40175, CVE-2025-62718). See https://github.com/advisories/GHSA-fvcv-3m26-pcqx and https://github.com/advisories/GHSA-3p68-rc4w-qgx5
## [0.44.1] - 2026-03-31
- Pin axios to version 1.14.0 to prevent installation of compromised version 1.14.1 containing remote access trojan (RAT). See https://github.com/axios/axios/issues/10604
## [0.44.0] - 2026-03-02
- Support tetra via `tetra` network option
## [0.43.0] - 2026-01-19
### Added
- Added `--verifier` flag to `blueprint verify` command to specify verifier ID
- Added `--list-verifiers` flag to `blueprint verify` command to list all available verifiers
### Changed
- `blueprint verify` uses a new config structure that supports multiple verifiers
### Fixed
- Verification success message now includes testnet flag in URL when verifying on testnet
## [0.42.0] - 2025-12-04
### Added
- Added `blueprint test --ui` command
### Changed
- Changed `@ton/sandbox` peer dependency to `>=0.40.0`
## [0.41.0] - 2025-09-23
### Added
- Added `blueprint test --coverage` command
### Changed
- When creating a contract, if the input contract name is invalid, blueprint does not exit, but asks for the name again
## [0.40.0] - 2025-08-18
### Added
- Added `libraryHash` and `libraryBoc` to the build result when building libraries
## [0.39.1] - 2025-08-05
### Added
- Added `--compiler-version` flag to support precise version selection when verifying
## [0.39.0] - 2025-08-04
### Added
- Added `buildLibrary` wrapper compile config and `compile` function option to convert the built code into a library
## [0.38.0] - 2025-07-07
### Fixed
- Fixed contracts not compiling in subfolders
### Changed
- Changed contracts templates positions in `create` command
- Changed counter template to match Tolk v1.0
## [0.37.0] - 2025-06-26
### Added
- Added debugger support
## [0.36.1] - 2025-06-17
### Fixed
- Fixed contract creation on Windows
## [0.36.0] - 2025-06-16
### Added
- Added `getConfig` and `getContractState` methods to network provider
- Added `getNormalizedExtMessageHash` function
- Added `NetworkProvider.waitForLastTransaction` method
### Fixed
- Use `Dirent.parentPath` instead of `.path` which is deprecated
## [0.35.1] - 2025-06-13
### Fixed
- Fixed Tolk counter template
## [0.35.0] - 2025-06-02
### Added
- Added ton lite client network provider
- Added tolk verifier
- Wallet v4 extended support: added v4r1, v4 is treated as v4r2
- Added possibility to specify custom `manifestUrl` in blueprint configuration
- Added documentation about the [Tact plugin by TON Studio](https://plugins.jetbrains.com/plugin/27290-tact)
- Added tolk v0.13 support
### Changed
- Updated documentation about Tact wrappers
## [0.34.0] - 2025-05-20
### Added
- Added config option to look for wrappers recursively
- Exported `getCompilerConfigForContract` function for plugin support
- Added request timout configuration
- Added docs for script args
- Added the `rename` command which renames contracts
- Added the `pack` command which builds and prepares a publish-ready package of contracts' wrappers
- Added support for wallet IDs in mnemonic provider. Environment variables `WALLET_ID` or `SUBWALLET_NUMBER` should be set, or a .env file with them must be present in order for it to be usable
- Added command `blueprint snapshot` to run tests with metric collection and write new benchmark report
- Added option `blueprint test --gas-report` to run tests and compare with the last snapshot metrics
### Fixed
- Fix address format in testnet
### Changed
- Updated FunC stdlib
## [0.33.1] - 2025-05-16
### Fixed
- Fixed blueprint build command failure in Tact projects
## [0.33.0] - 2025-05-16
### Added
- Added `tact.config.json` support
- Added tolk v0.12 support
### Fixed
- Fixed tact counter deploy script error
## [0.32.1] - 2025-05-06
### Fixed
- Fix unexpected code duplication on parralel compile
## [0.32.0] - 2025-05-02
### Added
- Compiler version is now shown during contract build
- Added 'All Contracts' option to build wizard
- Added function to build all tact contracts, required for rebuilding before tests
### Changed
- Made error of non-PascalCase contract names nicer
### Fixed
- `blueprint build --all` now exits with a non-zero exit code on failure
## [0.31.1] - 2025-04-24
### Fixed
- Fixed build directory creation
## [0.31.0] - 2025-04-24
### Added
- Added Fift output for FunC and Tolk build command
- Added API reference in code
### Changed
- Improved CLI appearance
## [0.30.0] - 2025-04-08
### Fixed
- Fixed Tact compilation
### Changed
- Updated Tact templates and `@tact-lang/compiler` dependency to v1.6.5
## [0.29.0] - 2025-03-02
### Changed
- Tolk's stderr is now printed on successful builds
## [0.28.0] - 2025-01-17
### Changed
- Moved compilers to peer dependencies, allowing end users to use their preferred versions of compilers
## [0.27.0] - 2024-12-18
### Changed
- Updated `@ton-community/func-js` dependency to v0.9.0
## [0.26.0] - 2024-11-26
### Added
- Added support for tonapi as an API provider
## [0.25.0] - 2024-11-02
### Added
- Support for Tolk, "next-generation FunC", a new language for writing smart contracts in TON. [Tolk overview](https://docs.ton.org/develop/tolk/overview)
## [0.24.0] - 2024-09-16
### Added
- Added support for wallet v5 in the Mnemonic provider
### Changed
- Changed the default API provider to Toncenter v2 (instead of Orbs TON Access). Rate limited requests are automatically retried
- Updated dependencies
### Removed
- Removed the specialized TonHub connector. Use TON Connect instead
## [0.23.0] - 2024-09-11
### Changed
- Toncenter v2 is now used by default instead of orbs access. Rate limited API requests are automatically retried
### Removed
- Removed `@orbs-network/ton-access` dependency
## [0.22.0] - 2024-07-08
### Added
- Added support for scripts in subdirectories, for example `scripts/counter/deploy.ts`
- Added the ability to specify test files in `blueprint test` command, for example `blueprint test Counter`
### Changed
- Separated compilables and wrappers
### Fixed
- Fixed `code overflow` error when generating QR code for ton:// link
## [0.21.0] - 2024-05-27
### Changed
- Changed `contract.tact.template` counter template to return remaining value from the message
- Updated TON Connect manifest
## [0.20.0] - 2024-05-07
### Added
- Added auto-sourcing of root `tact.config.json` files for merging compilation options with `wrappers/*.compile.ts`
- Added a warning for disabling `debug` in contract wrappers of Tact before doing production deployments
### Changed
- Changed `@tact-lang/compiler` dependency to be `^1.3.0` instead of `^1.2.0`
- Changed `compile.ts.template` template for Tact to have `debug` set to `true` by default
- Changed `contract.tact.template` empty template for Tact to mention implicit empty `init()` function
## [0.19.1] - 2024-04-12
### Fixed
- Fixed `verify` command
### Changed
- Updated readme to reflect the fact that blueprint no longer automatically adds `jsonRPC` to custom v2 endpoints
## [0.19.0] - 2024-03-27
### Changed
- Updated dependencies: func-js to 0.7.0, tonconnect sdk to 2.2.0
## [0.18.0] - 2024-03-13
### Changed
- Changed `@tact-lang/compiler` dependency to be `^1.2.0` instead of `^1.1.5`
- Updated the Tact counter template to use the new `+=` operator from Tact v1.2.0
## [0.17.0] - 2024-03-01
This release contains a breaking change.
### Changed
- Blueprint no longer automatically adds `jsonRPC` to custom v2 endpoints
### Added
- Added `set` command which can currently set func version (run `blueprint set func`)
- Added `open` and `getTransactions` to `WrappedContractProvider`
- Added cell hash to build artifacts
## [0.16.0] - 2024-02-15
### Added
- 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
- Added the `convert` command which attempts to convert a legacy bash build script into a blueprint `.compile.ts` file
- Added the ability to pass any user data into the compile hooks
### Changed
- Improved the `verify` command
## [0.15.0] - 2023-12-15
### Added
- Added flags `--custom-version`, `--custom-key`, `--custom-type` to `run` and `verify` commands to allow better control over custom API behavior
### Changed
- `--custom` now always adds `jsonRPC` to API URL for v2 APIs
### Fixed
- Fixed argument handling
## [0.14.2] - 2023-12-01
### Changed
- Changed `@tact-lang/compiler` dependency to be `^1.1.5` instead of `^1.1.3`
## [0.14.1] - 2023-12-01
### Fixed
- Fixed test templates (added missing imports)
## [0.14.0] - 2023-11-23
### Added
- Added `verify` command to quickly verify contracts on [verifier.ton.org](https://verifier.ton.org)
## [0.13.0] - 2023-11-05
### Added
- Added plugin support
- Added custom API v2 endpoints
### Changed
- Improved docs
- Changed deployed contract explorer link to use tonviewer
- Moved `deployer` to the global `describe` context in default tests
## [0.12.1] - 2023-07-31
### Changed
- Updated all dependencies to @ton organization packages
## [0.12.0] - 2023-07-14
### Fixed
- Fixed Tact imports
- Fixed missing newlines when printing error messages while building contracts
## [0.11.0] - 2023-07-03
### Added
- Added an `options` field to the `tact` variant of `CompilerConfig`, which is of the same type as the `options` of the Tact compiler, and includes fields such as `debug`, `experimental`, etc
### Changed
- Updated Tact to 1.1.3
## [0.10.0] - 2023-06-06
### Added
- Added two optional fields to the `CompilerConfig`: `preCompileHook?: () => Promise<void>` and `postCompileHook?: (code: Cell) => Promise<void>`. The former one gets called before any compilation of the respective contract happens, the latter one - after any compilation with the compiled code cell (they are called both during the `build` command and when calling the `compile` function for the respective contracts)
### Changed
- Changed the `run` command to only show `.ts` scripts
## [0.9.0] - 2023-04-21
### Changed
- Updated dependencies, of note: func-js to use func 0.4.3, Tact to 1.1.1
## [0.8.0] - 2023-04-07
### Changed
- Changed the `help` command to contain significantly more detailed help for every command
- Added the `success: true` requirement to every template test
- Updated dependencies
## [0.7.0] - 2023-04-03
This release contains a breaking change.
### Changed
- Changed the return type of `networkProvider.api()` and the type used internally in `NetworkProvider` from `TonClient` to `TonClient4`. This is a breaking change
- Updated dependencies
## [0.6.1] - 2023-03-27
### Changed
- Changed `UIProvider.prompt` return type from `Promise<void>` to `Promise<boolean>`
## [0.6.0] - 2023-03-21
### Added
- Added support for [Tact](https://github.com/tact-lang/tact), including Tact smart contract templates
- Added `--all` option for `build` command
### Changed
- Updated dependencies
## [0.5.0] - 2023-03-13
### Changed
- `SendProvider` now returns `Promise<any>` instead of `Promise<void>` from `sendTransaction`. This allows providers using TON Connect and TonHub to return the results from their backends to the end user
## [0.4.1] - 2023-03-02
### Changed
- Changed ton peer dependency version to `>=13.4.1`
## [0.4.0] - 2023-03-01
This release contains a breaking change.
### Changed
- Changed ton-core peer dependency version to `>=0.48.0`. This is a breaking change
### Added
- Added a new mnemonic deployer. Environment variables `WALLET_MNEMONIC` and `WALLET_VERSION` must be set, or a .env file with them must be present in order for it to be usable. Tonkeeper's v4R2 wallet corresponds to v4 version in blueprint
- Added the ability to choose the explorer for the deployed contracts. Pass one of the CLI flags `--tonscan`, `--tonapi`, `--toncx`, `--dton` to choose. Tonscan is the default
- Added ton-crypto peer dependency of version `>=3.2.0`
### Fixed
- Fixed TonHub deployer's `Sent transaction` message
- Fixed `SendMode.PAY_GAS_SEPARATLY` (missing E) typo in accordance with ton-core update
- Fixed a crash when using `blueprint run` with flags but no file name
## [0.3.0] - 2023-02-27
### Added
- Added an increment counter script for the counter template
- Added `isContractDeployed(address)` method to `NetworkProvider`
- Added `waitForDeploy(address, attempts?, sleepDuration?)` method to `NetworkProvider`
### Fixed
- Fixed exit code 1 on Windows even in case of successful execution
- Fixed paths to `.fc` files in `.compile.ts` files on Windows
- Fixed `TonConnectProvider` output
### Changed
- Converted ```Deployer sender does not support `bounce` ``` error into a warning
- Added an optional `init?: { code?: Cell; data?: Cell }` argument to `provider` method on `NetworkProvider`
- `createNetworkProvider` now requires a `UIProvider`
- Removed excessive comments from counter template contract
- Changed deploy script templates to use `sendDeploy` and `waitForDeploy` instead of `deploy`
- Refactored test templates to create `Blockchain` and deploy contract in `beforeEach`
- Disabled file choice menu when there is only 1 file
### Deprecated
- Deprecated `deploy` method on `NetworkProvider`. Users are advised to use self-implemented `sendDeploy` (or similar) methods on their `Contract` instances together with `isContractDeployed` or `waitForDeploy` on `NetworkProvider`
## [0.2.0] - 2023-02-09
### Added
- Added `blueprint test` command
- Added a pretty help message
- Added a hint to indicate that contract names must be PascalCase, and a restriction that contract names must start with a capital letter
- Added a better error message on an unknown command
### Fixed
- Fixed counter templates
- Fixed an issue where using `networkProvider.provider` and `networkProvider.open` would lead to `Deployer sender does not support "bounce"` message when trying to send internal messages
### Changed
- `networkProvider.provider` and `networkProvider.open` now wrap `TonClient`'s `ContractProvider` instead of directly using it for better control over passed arguments
- Removed unnecessary `await` keywords from all templates
## [0.1.0] - 2023-02-03
### Added
- Added fully interactive and fully non-interactive modes for the `create` command
- Added `input(message)` method to `UIProvider` and `InquirerUIProvider`
### Fixed
- File selection (compilation files in `build` and scripts in `run`) now accepts CLI argument hints in any case
================================================
FILE: LICENSE
================================================
MIT License
Copyright (c) 2025 Ton Tech
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
================================================
FILE: README.md
================================================
<img src="https://raw.githubusercontent.com/ton-org/blueprint/main/logo.svg" width=400 >
# Blueprint
A development environment for TON blockchain for writing, testing, and deploying smart contracts.
## Table of Contents
* [Quick start](#quick-start-)
* [Overview](#overview)
* [Core features](#core-features)
* [Tech stack](#tech-stack)
* [Requirements](#requirements)
* [Features overview](#features-overview)
* [Project creation](#project-creation)
* [Directory structure](#directory-structure)
* [Building contracts](#building-contracts)
* [Running the test suites](#running-the-test-suites)
* [Running scripts](#running-scripts)
* [Deploying contracts](#deploying-contracts)
* [Using Mnemonic Provider](#using-mnemonic-provider)
* [Contract development](#contract-development)
* [Creating contracts](#creating-contracts)
* [Writing contract code](#writing-contract-code)
* [Testing contracts](#testing-contracts)
* [Benchmark contracts](#benchmark-contracts)
* [Configuration](#configuration)
* [Plugins](#plugins)
* [Custom network](#custom-network)
* [Contributors](#contributors)
* [License](#license)
* [Donations](#donations)
## Quick start 🚀
Run the command in terminal to create a new project and follow the on-screen instructions:
```console
npm create ton@latest
```
## Overview
Blueprint is an all-in-one development environment designed to enhance the process of creating, testing, and deploying smart contracts on TON blockchain using [Tolk](https://docs.ton.org/develop/tolk/overview), [FunC](https://docs.ton.org/develop/func/overview), and [Tact](https://docs.tact-lang.org/) languages.
### Core features
* Create a development environment from template - `npm create ton@latest`
* Streamlined workflow for building, testing and deploying smart contracts
* Dead simple deployment to mainnet/testnet using your favorite wallet (eg. Tonkeeper)
* Blazing fast testing of multiple smart contracts in an isolated blockchain running in-process
### Tech stack
1. Compiling Tolk with https://github.com/ton-blockchain/tolk-js
2. Compiling FunC with https://github.com/ton-community/func-js
3. Compiling Tact with https://github.com/tact-lang/tact
* Uses [`tact.config.json`](https://docs.tact-lang.org/book/config/) as the build configuration file
4. Testing smart contracts with https://github.com/ton-org/sandbox
5. Deploying smart contracts with [TON Connect 2](https://github.com/ton-connect) or a `ton://` deeplink
### Requirements
* [Node.js](https://nodejs.org) with a recent version like v18. Version can be verified with `node -v`
* IDE with TON support:
* [Visual Studio Code](https://code.visualstudio.com/) with the [TON plugin](https://marketplace.visualstudio.com/items?itemName=ton-core.vscode-ton) or [Tact plugin](https://marketplace.visualstudio.com/items?itemName=tonstudio.vscode-tact)
* [IntelliJ IDEA](https://www.jetbrains.com/idea/)
* [TON Development plugin](https://plugins.jetbrains.com/plugin/23382-ton) for Tolk, FunC and Fift
* [Tact plugin by TON Studio](https://plugins.jetbrains.com/plugin/27290-tact) for Tact
## Features overview
### Project creation
1. Run and follow the on-screen instructions: `npm create ton@latest` or `npx create-ton@latest`
2. From the project directory run `npm/yarn install` to install dependencies
### Directory structure
* `contracts/` - Source code for all smart contracts and their imports
* `wrappers/` - TypeScript interface classes for all contracts **except Tact**.
* Tact-generated wrappers are located according to the build path defined in [`tact.config.json`](https://docs.tact-lang.org/book/config/)
* Each wrapper implements `Contract` interface from [@ton/core](https://www.npmjs.com/package/@ton/core)
* Includes message [de]serialization primitives, getter wrappers and compilation functions
* Used by the test suite and client code to interact with the contracts from TypeScript
* `compilables/` - Compilations scripts for contracts
* `tests/` - TypeScript test suite for all contracts (relying on [Sandbox](https://github.com/ton-org/sandbox) for in-process tests)
* `scripts/` - Deployment scripts to mainnet/testnet and other scripts interacting with live contracts
* `build/` - Compilation artifacts created here after running a build command
### Building contracts
1. You need a compilation script in `compilables/<CONTRACT>.compile.ts` - [example](/example/compilables/Counter.compile.ts)
2. Run interactive: `npx blueprint build` or `yarn blueprint build`
3. Non-interactive: `npx/yarn blueprint build <CONTRACT>` OR build all contracts `yarn blueprint build --all`
* Example: `yarn blueprint build counter`
4. Build results are generated in `build/<CONTRACT>.compiled.json`
5. Tact generated files are located in `build/<CONTRACT>` directory
6. Fift output is located in `build/<CONTRACT>/<CONTRACT>.fif`
### Running the test suites
1. Run in terminal: `npx blueprint test` or `yarn blueprint test`
2. Alternative method: `npm test` or `yarn test`
3. You can specify test file to run: `npm/yarn test <CONTRACT>`
* Example: `yarn test counter`
> Learn more about writing tests from the Sandbox's documentation - [here](https://github.com/ton-org/sandbox#writing-tests).
### Running scripts
1. Custom scripts should be located in `scripts` folder
2. Script file must have exported function `run`
```ts
export async function run(provider: NetworkProvider, args: string[]) {
//
}
```
3. Script can be run using `npx/yarn blueprint run <SCRIPT> [arg1, arg2, ...]` command
#### Deploying contracts
1. You need a deployment script in `scripts/deploy<CONTRACT>.ts` - [example](/example/scripts/deployCounter.ts)
2. Run interactive: `npx blueprint run` or `yarn blueprint run`
3. Non-interactive: `npx/yarn blueprint run deploy<CONTRACT> --<NETWORK> --<DEPLOY_METHOD>`
* Example: `yarn blueprint run deployCounter --mainnet --tonconnect`
#### Using Mnemonic Provider
To run scripts using a wallet by mnemonic authentication, you need to configure your environment and use the `Mnemonic` option when running scripts.
Start by adding the following environment variables to your `.env` file:
* **`WALLET_MNEMONIC`**: Your wallet's mnemonic phrase (space-separated words).
* **`WALLET_VERSION`**: The wallet contract version to use. Supported versions: `v1r1`, `v1r2`, `v1r3`, `v2r1`, `v2r2`, `v3r1`, `v3r2`, `v4r1`, `v4r2` (or `v4`), `v5r1`.
**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.
### Updating Tolk version
Tolk version can be updated to the latest using `npm update/yarn upgrade @ton/tolk-js` command
### Updating FunC version
FunC version can be updated to a specific version using `npx/yarn blueprint set func` command, or to the latest using `npm update/yarn upgrade @ton-community/func-js` command
### Updating Tact version
Tact version can be updated to the latest using `npm update/yarn upgrade @tact-lang/compiler` command
### Help and additional commands
Run in terminal: `npx blueprint help` or `yarn blueprint help`
## Contract development
Before developing, make sure that your current working directory is located in the root of the project created using `npm create ton@latest`
### Creating contracts
1. Run interactive: `npx blueprint create` or `yarn blueprint create`
2. Non-interactive: `npx/yarn blueprint create <CONTRACT> --type <TYPE>` (type can be `tolk-empty`, `func-empty`, `tact-empty`, `tolk-counter`, `func-counter`, `tact-counter`)
* Example: `yarn blueprint create MyNewContract --type tolk-empty`
### Renaming contracts
1. Run interactive: `npx blueprint rename` or `yarn blueprint rename`
2. Non-interactive: `npx/yarn blueprint rename <OLD_NAME> <NEW_NAME>`
* Example: `yarn blueprint rename OldContract NewContract `
### Writing contract code
#### Tolk
1. Implement the contract in `contracts/<CONTRACT>.tolk`; if you wish, split into multiple files
2. Implement wrapper TypeScript class in `wrappers/<CONTRACT>.ts` to encode messages and decode getters
#### FunC
1. Implement the standalone FunC root contract in `contracts/<CONTRACT>.fc`
2. Implement shared FunC imports (if breaking code to multiple files) in `contracts/imports/*.fc`
3. Implement wrapper TypeScript class in `wrappers/<CONTRACT>.ts` to encode messages and decode getters
#### Tact
1. Implement the contract in `contracts/<CONTRACT>.tact`
2. Wrappers will be automatically generated in `build/<CONTRACT>/tact_<CONTRACT>.ts`
### Testing contracts
1. Implement TypeScript tests in `tests/<CONTRACT>.spec.ts`
2. Rely on the wrapper TypeScript class from `wrappers/<CONTRACT>.ts` to interact with the contract
#### Collecting coverage
To collect coverage run `blueprint test --coverage`. Coverage will appear in coverage directory.
> Learn more about writing tests from the Sandbox's documentation - [here](https://github.com/ton-org/sandbox#writing-tests).
### Publishing Wrapper Code
1. **Authenticate with npm**
Run `npm adduser` to log in to your npm account.
> 📝 **Note:** You can learn more about advanced authentication in the official npm docs:
> [npmrc – Auth-Related Configuration](https://docs.npmjs.com/cli/v9/configuring-npm/npmrc#auth-related-configuration)
2. **Update the package version**
Edit the `version` field in your `package.json` to reflect the new release (e.g., `1.0.1` → `1.0.2`).
3. **Build the package**
Run the following command to generate and bundle your contract wrappers:
```bash
blueprint pack
```
4. **Publish to npm**
Push the package to the public npm registry:
```bash
npm publish --access public
```
### Benchmark contracts
1. Run `npx blueprint snapshot [--label=<comment>|-l <comment>]` to collect metrics of contracts and save snapshot
2. Run `npx blueprint test --gas-report|-g` to compare current metrics and saved snapshot
> Learn more about collect metric from the Sandbox's documentation - [here](https://github.com/ton-org/sandbox#benchmark-contracts).
## Configuration
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:
```typescript
import { Config } from '@ton/blueprint';
export const config: Config = {
// config contents
};
```
It is important that the config is exported, is named `config`, and is not `default` exported.
Config's features are explained below.
### Plugins
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.
In order to use plugins, add a `plugins` array to your config:
```typescript
import { Config } from '@ton/blueprint';
import { ScaffoldPlugin } from 'blueprint-scaffold';
export const config: Config = {
plugins: [new ScaffoldPlugin()],
};
```
(This example shows how to add the [scaffold](https://github.com/1IxI1/blueprint-scaffold) plugin)
Here are some of the plugins developed by the community:
- [scaffold](https://github.com/1IxI1/blueprint-scaffold) - allows developers to quickly create a simple dapp automatically using the wrappers' code
- [misti](https://github.com/nowarp/blueprint-misti) - simplifies workflow with the [Misti](https://nowarp.github.io/tools/misti/) static analyzer
### Custom network
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:
```typescript
import { Config } from '@ton/blueprint';
export const config: Config = {
network: {
endpoint: 'https://toncenter.com/api/v2/jsonRPC',
type: 'mainnet',
version: 'v2',
key: 'YOUR_API_KEY',
},
};
```
The above config parameters are equivalent to the arguments in the following command:
```bash
npx blueprint run --custom https://toncenter.com/api/v2/jsonRPC --custom-version v2 --custom-type mainnet --custom-key YOUR_API_KEY
```
Properties of the `network` object have the same semantics as the `--custom` flags with respective names (see `blueprint help run`).
### Liteclient Support
Lite client is supported through the following configuration:
```ts
import { Config } from '@ton/blueprint';
export const config: Config = {
network: {
endpoint: 'https://ton.org/testnet-global.config.json', // Use https://ton.org/global.config.json for mainnet or any custom configuration
version: 'liteclient',
type: 'testnet',
}
};
```
You can also provide these parameters via CLI:
```bash
npx blueprint run \
--custom https://ton.org/testnet-global.config.json \
--custom-version liteclient \
--custom-type testnet
```
#### Contract Verification Using Custom Network
You can also use custom network to verify contracts, like so:
```bash
npx blueprint verify --custom https://toncenter.com/api/v2/jsonRPC --custom-version v2 --custom-type mainnet --custom-key YOUR_API_KEY --compiler-version 0.4.4-newops.1
```
(or similarly using the config), however custom type MUST be specified as either `mainnet` or `testnet` when verifying.
### Request timeout
You can optionally configure how long HTTP requests should wait before timing out using the `requestTimout` field. This can be especially useful when working with unstable or slow networks.
```typescript
import { Config } from '@ton/blueprint';
export const config: Config = {
requestTimeout: 10000, // 10 seconds
};
```
### Recursive wrappers
You can configure whether the `wrappers` or `compilables` directories should be searched recursively for contracts configs by setting the `recursiveWrappers` field.
```typescript
import { Config } from '@ton/blueprint';
export const config: Config = {
recursiveWrappers: true,
};
```
By default, this is set to `false`.
### TonConnect manifest URL
If you're using a TonConnect provider, you can override the default manifest URL by specifying the `manifestUrl` field.
```typescript
import { Config } from '@ton/blueprint';
export const config: Config = {
manifestUrl: 'https://yourdomain.com/custom-manifest.json',
};
```
By default, the manifest URL is set to:
```
https://raw.githubusercontent.com/ton-org/blueprint/main/tonconnect/manifest.json
```
## Contributors
Special thanks to [@qdevstudio](https://t.me/qdevstudio) for their logo for blueprint.
## License
MIT
## Donations
TON - `EQAQR1d1Q4NaE5EefwUMdrr1QvXg-8mDB0XI2-fwDBD0nYxC`
================================================
FILE: contributing.md
================================================
# Contributing
We highly appreciate your contributions to the project ❤️
## Main flow
### Step 1 — get code
```shell
git clone git@github.com:ton-org/blueprint.git
cd blueprint
git checkout -b name-of-your-feature origin/develop
```
### Step 2 — write code
Write the code for your change, test it locally, then commit
> Git history: work log vs recipe https://www.bitsnbites.eu/git-history-work-log-vs-recipe/
Use [Conventional Commits](https://www.conventionalcommits.org/)
```shell
git commit --message "feat: paypal payment for different users"
```
or
```shell
git commit --message "fix: hide password display when searching for a user"
```
### Step 3 — make a fork
Go [here](https://github.com/ton-org/blueprint/fork) to make a fork, then setup your remote:
```bash
git remote add self url_of_your_fork
```
### Step 4 — make a pull request
Push:
```shell
git push --set-upstream self name-of-your-feature
```
Then create a pull request from the [pull requests page](https://github.com/ton-org/blueprint/pulls) or directly:
```shell
https://github.com/ton-org/blueprint/pull/new/name-of-your-feature
```
(note the name of your branch in the URL)
### Step 5 — update your branch from main
This step may be necessary in case the `main`/`develop` branch has changed since you created your branch
> [!NOTE]
> A tidy, linear Git history https://www.bitsnbites.eu/a-tidy-linear-git-history/
Get the latest upstream changes and update the working branch:
```shell
git fetch --prune origin
git rebase --autostash --ignore-date origin/main
```
> [!WARNING]
> Please note that you get the current state of the `main` branch from the **origin** remote for pushing to your own branch
During the rebase, there may be conflicts, they need to be resolved; once the conflicts are resolved, you can continue the rebase:
```shell
git rebase --continue
```
Upload the updated working branch to the repository; given that we changed the history, this should be done with the force option:
```shell
git push --force --set-upstream self name-of-your-feature
```
More details can be found in the tutorial: [git rebase](https://www.atlassian.com/git/tutorials/rewriting-history/git-rebase)
## All set 🎉
Thanks for your time and code!
================================================
FILE: eslint.config.js
================================================
const base = require('@ton/toolchain');
const tsEslint = require('@ton/toolchain').tsEslint;
const globals = require('globals');
module.exports = [
...base,
{ ignores: ['example/**'] },
{
plugins: {
'@typescript-eslint': tsEslint,
},
rules: {
'no-console': 'off',
'no-redeclare': 'off',
'@typescript-eslint/no-redeclare': ['error'],
'@typescript-eslint/no-explicit-any': 'off',
},
},
{
files: ['src/jest/**/*.ts', 'src/jest/**/*.tsx'],
languageOptions: {
globals: {
...globals.jest,
...globals.node,
},
},
},
];
================================================
FILE: example/.gitignore
================================================
node_modules
.yarn
temp
build
================================================
FILE: example/.prettierignore
================================================
build
================================================
FILE: example/.prettierrc
================================================
{
"printWidth": 120,
"tabWidth": 4,
"singleQuote": true,
"bracketSpacing": true,
"semi": true
}
================================================
FILE: example/.snapshot/1748254056557.json
================================================
{
"label": "v1",
"createdAt": "2025-05-26T10:07:36.557Z",
"items": [
{
"testName": "Counter should deploy",
"address": "EQBGhqLAZseEqRXz4ByFPTGV7SVMlI4hrbs-Sps_Xzx01x8G",
"codeHash": "0x61689f0431f0bead3490202a24110f59f58ac3c23f5e4ad4168923eee237287a",
"contractName": "Counter",
"methodName": "sendDeploy",
"receiver": "external-in",
"opCode": "0x0",
"execute": {
"compute": {
"type": "vm",
"success": true,
"gasUsed": 1937,
"exitCode": 0,
"vmSteps": 50
},
"action": {
"success": true,
"totalActions": 1,
"skippedActions": 0,
"resultCode": 0,
"totalActionFees": 1,
"totalFwdFees": 1196800,
"totalMessageSize": {
"cells": 12,
"bits": 1603
}
}
},
"message": {
"in": {
"cells": 14,
"bits": 1864
},
"out": {
"cells": 13,
"bits": 1610
}
},
"state": {
"code": {
"cells": 10,
"bits": 828
},
"data": {
"cells": 1,
"bits": 64
}
}
},
{
"testName": "Counter should deploy",
"address": "EQBgsSaKwODQBj4W0ITaf6SqrILmhKsr9MtAd_b_DdzQkfJQ",
"codeHash": "0x61689f0431f0bead3490202a24110f59f58ac3c23f5e4ad4168923eee237287a",
"contractName": "Counter",
"methodName": "send",
"receiver": "internal",
"opCode": "0x0",
"execute": {
"compute": {
"type": "vm",
"success": true,
"gasUsed": 615,
"exitCode": 0,
"vmSteps": 10
},
"action": {
"success": true,
"totalActions": 0,
"skippedActions": 0,
"resultCode": 0,
"totalActionFees": 0,
"totalMessageSize": {
"cells": 0,
"bits": 0
}
}
},
"message": {
"in": {
"cells": 12,
"bits": 1603
},
"out": {
"cells": 0,
"bits": 0
}
},
"state": {
"code": {
"cells": 10,
"bits": 828
},
"data": {
"cells": 1,
"bits": 64
}
}
},
{
"testName": "Counter should increase counter",
"address": "EQBGhqLAZseEqRXz4ByFPTGV7SVMlI4hrbs-Sps_Xzx01x8G",
"codeHash": "0x61689f0431f0bead3490202a24110f59f58ac3c23f5e4ad4168923eee237287a",
"contractName": "Counter",
"methodName": "sendDeploy",
"receiver": "external-in",
"opCode": "0x0",
"execute": {
"compute": {
"type": "vm",
"success": true,
"gasUsed": 1937,
"exitCode": 0,
"vmSteps": 50
},
"action": {
"success": true,
"totalActions": 1,
"skippedActions": 0,
"resultCode": 0,
"totalActionFees": 1,
"totalFwdFees": 1196800,
"totalMessageSize": {
"cells": 12,
"bits": 1603
}
}
},
"message": {
"in": {
"cells": 14,
"bits": 1864
},
"out": {
"cells": 13,
"bits": 1610
}
},
"state": {
"code": {
"cells": 10,
"bits": 828
},
"data": {
"cells": 1,
"bits": 64
}
}
},
{
"testName": "Counter should increase counter",
"address": "EQBgsSaKwODQBj4W0ITaf6SqrILmhKsr9MtAd_b_DdzQkfJQ",
"codeHash": "0x61689f0431f0bead3490202a24110f59f58ac3c23f5e4ad4168923eee237287a",
"contractName": "Counter",
"methodName": "send",
"receiver": "internal",
"opCode": "0x0",
"execute": {
"compute": {
"type": "vm",
"success": true,
"gasUsed": 615,
"exitCode": 0,
"vmSteps": 10
},
"action": {
"success": true,
"totalActions": 0,
"skippedActions": 0,
"resultCode": 0,
"totalActionFees": 0,
"totalMessageSize": {
"cells": 0,
"bits": 0
}
}
},
"message": {
"in": {
"cells": 12,
"bits": 1603
},
"out": {
"cells": 0,
"bits": 0
}
},
"state": {
"code": {
"cells": 10,
"bits": 828
},
"data": {
"cells": 1,
"bits": 64
}
}
},
{
"testName": "Counter should increase counter",
"address": "EQBRa72FONfWv_od_gbseysN3HHqZ5FEZ3oqY0-aqzZ2vkhF",
"codeHash": "0x61689f0431f0bead3490202a24110f59f58ac3c23f5e4ad4168923eee237287a",
"contractName": "Counter",
"methodName": "sendIncrease",
"receiver": "external-in",
"opCode": "0x0",
"execute": {
"compute": {
"type": "vm",
"success": true,
"gasUsed": 1937,
"exitCode": 0,
"vmSteps": 50
},
"action": {
"success": true,
"totalActions": 1,
"skippedActions": 0,
"resultCode": 0,
"totalActionFees": 1,
"totalFwdFees": 400000,
"totalMessageSize": {
"cells": 1,
"bits": 833
}
}
},
"message": {
"in": {
"cells": 3,
"bits": 1094
},
"out": {
"cells": 2,
"bits": 840
}
},
"state": {
"code": {
"cells": 10,
"bits": 828
},
"data": {
"cells": 1,
"bits": 64
}
}
},
{
"testName": "Counter should increase counter",
"address": "EQBgsSaKwODQBj4W0ITaf6SqrILmhKsr9MtAd_b_DdzQkfJQ",
"codeHash": "0x61689f0431f0bead3490202a24110f59f58ac3c23f5e4ad4168923eee237287a",
"contractName": "Counter",
"receiver": "internal",
"opCode": "0x7e8764ef",
"execute": {
"compute": {
"type": "vm",
"success": true,
"gasUsed": 2681,
"exitCode": 0,
"vmSteps": 57
},
"action": {
"success": true,
"totalActions": 0,
"skippedActions": 0,
"resultCode": 0,
"totalActionFees": 0,
"totalMessageSize": {
"cells": 0,
"bits": 0
}
}
},
"message": {
"in": {
"cells": 1,
"bits": 833
},
"out": {
"cells": 0,
"bits": 0
}
},
"state": {
"code": {
"cells": 10,
"bits": 828
},
"data": {
"cells": 1,
"bits": 64
}
},
"methodName": "increase"
},
{
"testName": "Counter should increase counter",
"address": "EQBtSVGtsm08pdZdbsXJ226QHsfMsO1HsZbkmhE7qrZ8q3Ki",
"codeHash": "0x61689f0431f0bead3490202a24110f59f58ac3c23f5e4ad4168923eee237287a",
"contractName": "Counter",
"methodName": "sendIncrease",
"receiver": "external-in",
"opCode": "0x0",
"execute": {
"compute": {
"type": "vm",
"success": true,
"gasUsed": 1937,
"exitCode": 0,
"vmSteps": 50
},
"action": {
"success": true,
"totalActions": 1,
"skippedActions": 0,
"resultCode": 0,
"totalActionFees": 1,
"totalFwdFees": 400000,
"totalMessageSize": {
"cells": 1,
"bits": 833
}
}
},
"message": {
"in": {
"cells": 3,
"bits": 1094
},
"out": {
"cells": 2,
"bits": 840
}
},
"state": {
"code": {
"cells": 10,
"bits": 828
},
"data": {
"cells": 1,
"bits": 64
}
}
},
{
"testName": "Counter should increase counter",
"address": "EQAZYCEwTbF4Cn5fy7A_XlfYgXgaNIArebxjyzWvQXbFIc5G",
"codeHash": "0x61689f0431f0bead3490202a24110f59f58ac3c23f5e4ad4168923eee237287a",
"contractName": "Counter",
"methodName": "sendIncrease",
"receiver": "external-in",
"opCode": "0x0",
"execute": {
"compute": {
"type": "vm",
"success": true,
"gasUsed": 1937,
"exitCode": 0,
"vmSteps": 50
},
"action": {
"success": true,
"totalActions": 1,
"skippedActions": 0,
"resultCode": 0,
"totalActionFees": 1,
"totalFwdFees": 400000,
"totalMessageSize": {
"cells": 1,
"bits": 833
}
}
},
"message": {
"in": {
"cells": 3,
"bits": 1094
},
"out": {
"cells": 2,
"bits": 840
}
},
"state": {
"code": {
"cells": 10,
"bits": 828
},
"data": {
"cells": 1,
"bits": 64
}
}
}
]
}
================================================
FILE: example/README.md
================================================
# TON project
Starter template for a new TON project - FunC contracts, unit tests, compilation and deployment scripts.
## Layout
- `contracts` - contains the source code of all the smart contracts of the project and their dependencies.
- `wrappers` - contains the wrapper classes (implementing `Contract` from @ton/core) for the contracts, including any [de]serialization primitives and compilation functions.
- `tests` - tests for the contracts. Would typically use the wrappers.
- `scripts` - contains scripts used by the project, mainly the deployment scripts.
## Repo contents / tech stack
1. Compiling FunC - [https://github.com/ton-community/func-js](https://github.com/ton-community/func-js)
2. Testing TON smart contracts - [https://github.com/ton-org/sandbox](https://github.com/ton-org/sandbox)
3. Deployment of contracts is supported with [TON Connect 2](https://github.com/ton-connect/), [Tonhub wallet](https://tonhub.com/), using mnemonics, or via a direct `ton://` deeplink
## How to use
* Run `npm create ton@latest`
### Building a contract
1. Interactively
1. Run `yarn blueprint build`
2. Choose the contract you'd like to build
1. Non-interactively
1. Run `yarn blueprint build <CONTRACT>`
2. example: `yarn blueprint build pingpong`
### Deploying a contract
1. Interactively
1. Run `yarn blueprint run`
2. Choose the contract you'd like to deploy
3. Choose whether you're deploying on mainnet or testnet
4. Choose how to deploy:
1. With a TON Connect compatible wallet
2. A `ton://` deep link / QR code
3. Tonhub wallet
4. Mnemonic
5. Deploy the contract
2. Non-interactively
1. Run `yarn blueprint run <CONTRACT> --<NETWORK> --<DEPLOY_METHOD>`
2. example: `yarn blueprint run pingpong --mainnet --tonconnect`
### Testing
1. Run `yarn test`
## Adding your own contract
1. Run `yarn blueprint create <CONTRACT>`
2. example: `yarn blueprint create MyContract`
* Write code
* FunC contracts are located in `contracts/*.fc`
* Standalone root contracts are located in `contracts/*.fc`
* Shared imports (when breaking code to multiple files) are in `contracts/imports/*.fc`
* Tests in TypeScript are located in `test/*.spec.ts`
* Wrapper classes for interacting with the contract are located in `wrappers/*.ts`
* Any scripts (including deployers) are located in `scripts/*.ts`
* Build
* Builder configs are located in `wrappers/*.compile.ts`
* In the root repo dir, run in terminal `yarn blueprint build`
* Compilation errors will appear on screen, if applicable
* Resulting build artifacts include:
* `build/*.compiled.json` - the binary code cell of the compiled contract (for deployment). Saved in a hex format within a json file to support webapp imports
* Test
* In the root repo dir, run in terminal `yarn test`
* Don't forget to build (or rebuild) before running tests
* Tests are running inside Node.js by running TVM in web-assembly using [sandbox](https://github.com/ton-org/sandbox)
* To get gas metric report, run `yarn gas-report`; see [Benchmark contracts](https://github.com/ton-org/blueprint/blob/main/README.md#benchmark-contracts)
* To save new gas metric snapshot, run `yarn gas-snapshot '<label name>'`
* Deploy
* Run `yarn blueprint run <deployscript>`
* Contracts will be rebuilt on each execution
* Follow the on-screen instructions of the deploy script
# License
MIT
================================================
FILE: example/blueprint.config.ts
================================================
export const config = { separateCompilables: true };
================================================
FILE: example/compilables/Counter.compile.ts
================================================
import { CompilerConfig } from '@ton/blueprint';
export const compile: CompilerConfig = {
lang: 'func',
targets: ['contracts/counter.fc'],
};
================================================
FILE: example/contract.abi.json
================================================
{
"0x61689f0431f0bead3490202a24110f59f58ac3c23f5e4ad4168923eee237287a": "Counter",
"Counter": {
"receivers": [
{
"receiver": "internal",
"message": {
"kind": "typed",
"type": "increase"
}
}
],
"types": [
{
"name": "increase",
"header": 2122802415
}
],
"name": "Counter"
}
}
================================================
FILE: example/contracts/counter.fc
================================================
#include "imports/stdlib.fc";
const op::increase = "op::increase"c; ;; create an opcode from string using the "c" prefix, this results in 0x7e8764ef opcode in this case
;; storage variables
;; id is required to be able to create different instances of counters
;; since addresses in TON depend on the initial state of the contract
global int ctx_id;
global int ctx_counter;
;; load_data populates storage variables using stored data
() load_data() impure {
var ds = get_data().begin_parse();
ctx_id = ds~load_uint(32);
ctx_counter = ds~load_uint(32);
ds.end_parse();
}
;; save_data stores storage variables as a cell into persistent storage
() save_data() impure {
set_data(
begin_cell()
.store_uint(ctx_id, 32)
.store_uint(ctx_counter, 32)
.end_cell()
);
}
;; recv_internal is the main function of the contract and is called when it receives a message from other contracts
() recv_internal(int my_balance, int msg_value, cell in_msg_full, slice in_msg_body) impure {
if (in_msg_body.slice_empty?()) { ;; ignore all empty messages
return ();
}
slice cs = in_msg_full.begin_parse();
int flags = cs~load_uint(4);
if (flags & 1) { ;; ignore all bounced messages
return ();
}
load_data(); ;; here we populate the storage variables
int op = in_msg_body~load_uint(32); ;; by convention, the first 32 bits of incoming message is the op
int query_id = in_msg_body~load_uint(64); ;; also by convention, the next 64 bits contain the "query id", although this is not always the case
if (op == op::increase) {
int increase_by = in_msg_body~load_uint(32);
ctx_counter += increase_by;
save_data();
return ();
}
throw(0xffff); ;; if the message contains an op that is not known to this contract, we throw
}
;; get methods are a means to conveniently read contract data using, for example, HTTP APIs
;; they are marked with method_id
;; note that unlike in many other smart contract VMs, get methods cannot be called by other contracts
int get_counter() method_id {
load_data();
return ctx_counter;
}
int get_id() method_id {
load_data();
return ctx_id;
}
================================================
FILE: example/contracts/imports/stdlib.fc
================================================
;; Standard library for funC
;;
{-
# Tuple manipulation primitives
The names and the types are mostly self-explaining.
See [polymorhism with forall](https://ton.org/docs/#/func/functions?id=polymorphism-with-forall)
for more info on the polymorphic functions.
Note that currently values of atomic type `tuple` can't be cast to composite tuple type (e.g. `[int, cell]`)
and vise versa.
-}
{-
# Lisp-style lists
Lists can be represented as nested 2-elements tuples.
Empty list is conventionally represented as TVM `null` value (it can be obtained by calling [null()]).
For example, tuple `(1, (2, (3, null)))` represents list `[1, 2, 3]`. Elements of a list can be of different types.
-}
;;; Adds an element to the beginning of lisp-style list.
forall X -> tuple cons(X head, tuple tail) asm "CONS";
;;; Extracts the head and the tail of lisp-style list.
forall X -> (X, tuple) uncons(tuple list) asm "UNCONS";
;;; Extracts the tail and the head of lisp-style list.
forall X -> (tuple, X) list_next(tuple list) asm( -> 1 0) "UNCONS";
;;; Returns the head of lisp-style list.
forall X -> X car(tuple list) asm "CAR";
;;; Returns the tail of lisp-style list.
tuple cdr(tuple list) asm "CDR";
;;; Creates tuple with zero elements.
tuple empty_tuple() asm "NIL";
;;; Appends a value `x` to a `Tuple t = (x1, ..., xn)`, but only if the resulting `Tuple t' = (x1, ..., xn, x)`
;;; is of length at most 255. Otherwise throws a type check exception.
forall X -> tuple tpush(tuple t, X value) asm "TPUSH";
forall X -> (tuple, ()) ~tpush(tuple t, X value) asm "TPUSH";
;;; Creates a tuple of length one with given argument as element.
forall X -> [X] single(X x) asm "SINGLE";
;;; Unpacks a tuple of length one
forall X -> X unsingle([X] t) asm "UNSINGLE";
;;; Creates a tuple of length two with given arguments as elements.
forall X, Y -> [X, Y] pair(X x, Y y) asm "PAIR";
;;; Unpacks a tuple of length two
forall X, Y -> (X, Y) unpair([X, Y] t) asm "UNPAIR";
;;; Creates a tuple of length three with given arguments as elements.
forall X, Y, Z -> [X, Y, Z] triple(X x, Y y, Z z) asm "TRIPLE";
;;; Unpacks a tuple of length three
forall X, Y, Z -> (X, Y, Z) untriple([X, Y, Z] t) asm "UNTRIPLE";
;;; Creates a tuple of length four with given arguments as elements.
forall X, Y, Z, W -> [X, Y, Z, W] tuple4(X x, Y y, Z z, W w) asm "4 TUPLE";
;;; Unpacks a tuple of length four
forall X, Y, Z, W -> (X, Y, Z, W) untuple4([X, Y, Z, W] t) asm "4 UNTUPLE";
;;; Returns the first element of a tuple (with unknown element types).
forall X -> X first(tuple t) asm "FIRST";
;;; Returns the second element of a tuple (with unknown element types).
forall X -> X second(tuple t) asm "SECOND";
;;; Returns the third element of a tuple (with unknown element types).
forall X -> X third(tuple t) asm "THIRD";
;;; Returns the fourth element of a tuple (with unknown element types).
forall X -> X fourth(tuple t) asm "3 INDEX";
;;; Returns the first element of a pair tuple.
forall X, Y -> X pair_first([X, Y] p) asm "FIRST";
;;; Returns the second element of a pair tuple.
forall X, Y -> Y pair_second([X, Y] p) asm "SECOND";
;;; Returns the first element of a triple tuple.
forall X, Y, Z -> X triple_first([X, Y, Z] p) asm "FIRST";
;;; Returns the second element of a triple tuple.
forall X, Y, Z -> Y triple_second([X, Y, Z] p) asm "SECOND";
;;; Returns the third element of a triple tuple.
forall X, Y, Z -> Z triple_third([X, Y, Z] p) asm "THIRD";
;;; Push null element (casted to given type)
;;; By the TVM type `Null` FunC represents absence of a value of some atomic type.
;;; So `null` can actually have any atomic type.
forall X -> X null() asm "PUSHNULL";
;;; Moves a variable [x] to the top of the stack
forall X -> (X, ()) ~impure_touch(X x) impure asm "NOP";
;;; Returns the current Unix time as an Integer
int now() asm "NOW";
;;; Returns the internal address of the current smart contract as a Slice with a `MsgAddressInt`.
;;; If necessary, it can be parsed further using primitives such as [parse_std_addr].
slice my_address() asm "MYADDR";
;;; Returns the balance of the smart contract as a tuple consisting of an int
;;; (balance in nanotoncoins) and a `cell`
;;; (a dictionary with 32-bit keys representing the balance of "extra currencies")
;;; at the start of Computation Phase.
;;; Note that RAW primitives such as [send_raw_message] do not update this field.
[int, cell] get_balance() asm "BALANCE";
;;; Returns the logical time of the current transaction.
int cur_lt() asm "LTIME";
;;; Returns the starting logical time of the current block.
int block_lt() asm "BLOCKLT";
;;; Computes the representation hash of a `cell` [c] and returns it as a 256-bit unsigned integer `x`.
;;; Useful for signing and checking signatures of arbitrary entities represented by a tree of cells.
int cell_hash(cell c) asm "HASHCU";
;;; Computes the hash of a `slice s` and returns it as a 256-bit unsigned integer `x`.
;;; The result is the same as if an ordinary cell containing only data and references from `s` had been created
;;; and its hash computed by [cell_hash].
int slice_hash(slice s) asm "HASHSU";
;;; Computes sha256 of the data bits of `slice` [s]. If the bit length of `s` is not divisible by eight,
;;; throws a cell underflow exception. The hash value is returned as a 256-bit unsigned integer `x`.
int string_hash(slice s) asm "SHA256U";
{-
# Signature checks
-}
;;; Checks the Ed25519-`signature` of a `hash` (a 256-bit unsigned integer, usually computed as the hash of some data)
;;; using [public_key] (also represented by a 256-bit unsigned integer).
;;; The signature must contain at least 512 data bits; only the first 512 bits are used.
;;; The result is `−1` if the signature is valid, `0` otherwise.
;;; Note that `CHKSIGNU` creates a 256-bit slice with the hash and calls `CHKSIGNS`.
;;; That is, if [hash] is computed as the hash of some data, these data are hashed twice,
;;; the second hashing occurring inside `CHKSIGNS`.
int check_signature(int hash, slice signature, int public_key) asm "CHKSIGNU";
;;; Checks whether [signature] is a valid Ed25519-signature of the data portion of `slice data` using `public_key`,
;;; similarly to [check_signature].
;;; If the bit length of [data] is not divisible by eight, throws a cell underflow exception.
;;; The verification of Ed25519 signatures is the standard one,
;;; with sha256 used to reduce [data] to the 256-bit number that is actually signed.
int check_data_signature(slice data, slice signature, int public_key) asm "CHKSIGNS";
{---
# Computation of boc size
The primitives below may be useful for computing storage fees of user-provided data.
-}
;;; Returns `(x, y, z, -1)` or `(null, null, null, 0)`.
;;; Recursively computes the count of distinct cells `x`, data bits `y`, and cell references `z`
;;; in the DAG rooted at `cell` [c], effectively returning the total storage used by this DAG taking into account
;;; the identification of equal cells.
;;; The values of `x`, `y`, and `z` are computed by a depth-first traversal of this DAG,
;;; with a hash table of visited cell hashes used to prevent visits of already-visited cells.
;;; The total count of visited cells `x` cannot exceed non-negative [max_cells];
;;; otherwise the computation is aborted before visiting the `(max_cells + 1)`-st cell and
;;; a zero flag is returned to indicate failure. If [c] is `null`, returns `x = y = z = 0`.
(int, int, int) compute_data_size(cell c, int max_cells) impure asm "CDATASIZE";
;;; Similar to [compute_data_size?], but accepting a `slice` [s] instead of a `cell`.
;;; The returned value of `x` does not take into account the cell that contains the `slice` [s] itself;
;;; however, the data bits and the cell references of [s] are accounted for in `y` and `z`.
(int, int, int) slice_compute_data_size(slice s, int max_cells) impure asm "SDATASIZE";
;;; A non-quiet version of [compute_data_size?] that throws a cell overflow exception (`8`) on failure.
(int, int, int, int) compute_data_size?(cell c, int max_cells) asm "CDATASIZEQ NULLSWAPIFNOT2 NULLSWAPIFNOT";
;;; A non-quiet version of [slice_compute_data_size?] that throws a cell overflow exception (8) on failure.
(int, int, int, int) slice_compute_data_size?(cell c, int max_cells) asm "SDATASIZEQ NULLSWAPIFNOT2 NULLSWAPIFNOT";
;;; Throws an exception with exit_code excno if cond is not 0 (commented since implemented in compilator)
;; () throw_if(int excno, int cond) impure asm "THROWARGIF";
{--
# Debug primitives
Only works for local TVM execution with debug level verbosity
-}
;;; Dumps the stack (at most the top 255 values) and shows the total stack depth.
() dump_stack() impure asm "DUMPSTK";
{-
# Persistent storage save and load
-}
;;; Returns the persistent contract storage cell. It can be parsed or modified with slice and builder primitives later.
cell get_data() asm "c4 PUSH";
;;; Sets `cell` [c] as persistent contract data. You can update persistent contract storage with this primitive.
() set_data(cell c) impure asm "c4 POP";
{-
# Continuation primitives
-}
;;; Usually `c3` has a continuation initialized by the whole code of the contract. It is used for function calls.
;;; The primitive returns the current value of `c3`.
cont get_c3() impure asm "c3 PUSH";
;;; Updates the current value of `c3`. Usually, it is used for updating smart contract code in run-time.
;;; Note that after execution of this primitive the current code
;;; (and the stack of recursive function calls) won't change,
;;; but any other function call will use a function from the new code.
() set_c3(cont c) impure asm "c3 POP";
;;; Transforms a `slice` [s] into a simple ordinary continuation `c`, with `c.code = s` and an empty stack and savelist.
cont bless(slice s) impure asm "BLESS";
{---
# Gas related primitives
-}
;;; Sets current gas limit `gl` to its maximal allowed value `gm`, and resets the gas credit `gc` to zero,
;;; decreasing the value of `gr` by `gc` in the process.
;;; In other words, the current smart contract agrees to buy some gas to finish the current transaction.
;;; This action is required to process external messages, which bring no value (hence no gas) with themselves.
;;;
;;; For more details check [accept_message effects](https://ton.org/docs/#/smart-contracts/accept).
() accept_message() impure asm "ACCEPT";
;;; Sets current gas limit `gl` to the minimum of limit and `gm`, and resets the gas credit `gc` to zero.
;;; If the gas consumed so far (including the present instruction) exceeds the resulting value of `gl`,
;;; an (unhandled) out of gas exception is thrown before setting new gas limits.
;;; Notice that [set_gas_limit] with an argument `limit ≥ 2^63 − 1` is equivalent to [accept_message].
() set_gas_limit(int limit) impure asm "SETGASLIMIT";
;;; Commits the current state of registers `c4` (“persistent data”) and `c5` (“actions”)
;;; so that the current execution is considered “successful” with the saved values even if an exception
;;; in Computation Phase is thrown later.
() commit() impure asm "COMMIT";
;;; Not implemented
;;() buy_gas(int gram) impure asm "BUYGAS";
;;; Computes the amount of gas that can be bought for `amount` nanoTONs,
;;; and sets `gl` accordingly in the same way as [set_gas_limit].
() buy_gas(int amount) impure asm "BUYGAS";
;;; Computes the minimum of two integers [x] and [y].
int min(int x, int y) asm "MIN";
;;; Computes the maximum of two integers [x] and [y].
int max(int x, int y) asm "MAX";
;;; Sorts two integers.
(int, int) minmax(int x, int y) asm "MINMAX";
;;; Computes the absolute value of an integer [x].
int abs(int x) asm "ABS";
{-
# Slice primitives
It is said that a primitive _loads_ some data,
if it returns the data and the remainder of the slice
(so it can also be used as [modifying method](https://ton.org/docs/#/func/statements?id=modifying-methods)).
It is said that a primitive _preloads_ some data, if it returns only the data
(it can be used as [non-modifying method](https://ton.org/docs/#/func/statements?id=non-modifying-methods)).
Unless otherwise stated, loading and preloading primitives read the data from a prefix of the slice.
-}
;;; Converts a `cell` [c] into a `slice`. Notice that [c] must be either an ordinary cell,
;;; or an exotic cell (see [TVM.pdf](https://ton-blockchain.github.io/docs/tvm.pdf), 3.1.2)
;;; which is automatically loaded to yield an ordinary cell `c'`, converted into a `slice` afterwards.
slice begin_parse(cell c) asm "CTOS";
;;; Checks if [s] is empty. If not, throws an exception.
() end_parse(slice s) impure asm "ENDS";
;;; Loads the first reference from the slice.
(slice, cell) load_ref(slice s) asm( -> 1 0) "LDREF";
;;; Preloads the first reference from the slice.
cell preload_ref(slice s) asm "PLDREF";
{- Functions below are commented because are implemented on compilator level for optimisation -}
;;; Loads a signed [len]-bit integer from a slice [s].
;; (slice, int) ~load_int(slice s, int len) asm(s len -> 1 0) "LDIX";
;;; Loads an unsigned [len]-bit integer from a slice [s].
;; (slice, int) ~load_uint(slice s, int len) asm( -> 1 0) "LDUX";
;;; Preloads a signed [len]-bit integer from a slice [s].
;; int preload_int(slice s, int len) asm "PLDIX";
;;; Preloads an unsigned [len]-bit integer from a slice [s].
;; int preload_uint(slice s, int len) asm "PLDUX";
;;; Loads the first `0 ≤ len ≤ 1023` bits from slice [s] into a separate `slice s''`.
;; (slice, slice) load_bits(slice s, int len) asm(s len -> 1 0) "LDSLICEX";
;;; Preloads the first `0 ≤ len ≤ 1023` bits from slice [s] into a separate `slice s''`.
;; slice preload_bits(slice s, int len) asm "PLDSLICEX";
;;; Loads serialized amount of TonCoins (any unsigned integer up to `2^128 - 1`).
(slice, int) load_grams(slice s) asm( -> 1 0) "LDGRAMS";
(slice, int) load_coins(slice s) asm( -> 1 0) "LDGRAMS";
;;; Returns all but the first `0 ≤ len ≤ 1023` bits of `slice` [s].
slice skip_bits(slice s, int len) asm "SDSKIPFIRST";
(slice, ()) ~skip_bits(slice s, int len) asm "SDSKIPFIRST";
;;; Returns the first `0 ≤ len ≤ 1023` bits of `slice` [s].
slice first_bits(slice s, int len) asm "SDCUTFIRST";
;;; Returns all but the last `0 ≤ len ≤ 1023` bits of `slice` [s].
slice skip_last_bits(slice s, int len) asm "SDSKIPLAST";
(slice, ()) ~skip_last_bits(slice s, int len) asm "SDSKIPLAST";
;;; Returns the last `0 ≤ len ≤ 1023` bits of `slice` [s].
slice slice_last(slice s, int len) asm "SDCUTLAST";
;;; Loads a dictionary `D` (HashMapE) from `slice` [s].
;;; (returns `null` if `nothing` constructor is used).
(slice, cell) load_dict(slice s) asm( -> 1 0) "LDDICT";
;;; Preloads a dictionary `D` from `slice` [s].
cell preload_dict(slice s) asm "PLDDICT";
;;; Loads a dictionary as [load_dict], but returns only the remainder of the slice.
slice skip_dict(slice s) asm "SKIPDICT";
;;; Loads (Maybe ^Cell) from `slice` [s].
;;; In other words loads 1 bit and if it is true
;;; loads first ref and return it with slice remainder
;;; otherwise returns `null` and slice remainder
(slice, cell) load_maybe_ref(slice s) asm( -> 1 0) "LDOPTREF";
;;; Preloads (Maybe ^Cell) from `slice` [s].
cell preload_maybe_ref(slice s) asm "PLDOPTREF";
;;; Returns the depth of `cell` [c].
;;; If [c] has no references, then return `0`;
;;; otherwise the returned value is one plus the maximum of depths of cells referred to from [c].
;;; If [c] is a `null` instead of a cell, returns zero.
int cell_depth(cell c) asm "CDEPTH";
{-
# Slice size primitives
-}
;;; Returns the number of references in `slice` [s].
int slice_refs(slice s) asm "SREFS";
;;; Returns the number of data bits in `slice` [s].
int slice_bits(slice s) asm "SBITS";
;;; Returns both the number of data bits and the number of references in `slice` [s].
(int, int) slice_bits_refs(slice s) asm "SBITREFS";
;;; Checks whether a `slice` [s] is empty (i.e., contains no bits of data and no cell references).
int slice_empty?(slice s) asm "SEMPTY";
;;; Checks whether `slice` [s] has no bits of data.
int slice_data_empty?(slice s) asm "SDEMPTY";
;;; Checks whether `slice` [s] has no references.
int slice_refs_empty?(slice s) asm "SREMPTY";
;;; Returns the depth of `slice` [s].
;;; If [s] has no references, then returns `0`;
;;; otherwise the returned value is one plus the maximum of depths of cells referred to from [s].
int slice_depth(slice s) asm "SDEPTH";
{-
# Builder size primitives
-}
;;; Returns the number of cell references already stored in `builder` [b]
int builder_refs(builder b) asm "BREFS";
;;; Returns the number of data bits already stored in `builder` [b].
int builder_bits(builder b) asm "BBITS";
;;; Returns the depth of `builder` [b].
;;; If no cell references are stored in [b], then returns 0;
;;; otherwise the returned value is one plus the maximum of depths of cells referred to from [b].
int builder_depth(builder b) asm "BDEPTH";
{-
# Builder primitives
It is said that a primitive _stores_ a value `x` into a builder `b`
if it returns a modified version of the builder `b'` with the value `x` stored at the end of it.
It can be used as [non-modifying method](https://ton.org/docs/#/func/statements?id=non-modifying-methods).
All the primitives below first check whether there is enough space in the `builder`,
and only then check the range of the value being serialized.
-}
;;; Creates a new empty `builder`.
builder begin_cell() asm "NEWC";
;;; Converts a `builder` into an ordinary `cell`.
cell end_cell(builder b) asm "ENDC";
;;; Stores a reference to `cell` [c] into `builder` [b].
builder store_ref(builder b, cell c) asm(c b) "STREF";
;;; Stores an unsigned [len]-bit integer `x` into `b` for `0 ≤ len ≤ 256`.
;; builder store_uint(builder b, int x, int len) asm(x b len) "STUX";
;;; Stores a signed [len]-bit integer `x` into `b` for` 0 ≤ len ≤ 257`.
;; builder store_int(builder b, int x, int len) asm(x b len) "STIX";
;;; Stores `slice` [s] into `builder` [b]
builder store_slice(builder b, slice s) asm "STSLICER";
;;; Stores (serializes) an integer [x] in the range `0..2^128 − 1` into `builder` [b].
;;; The serialization of [x] consists of a 4-bit unsigned big-endian integer `l`,
;;; which is the smallest integer `l ≥ 0`, such that `x < 2^8l`,
;;; followed by an `8l`-bit unsigned big-endian representation of [x].
;;; If [x] does not belong to the supported range, a range check exception is thrown.
;;;
;;; Store amounts of TonCoins to the builder as VarUInteger 16
builder store_grams(builder b, int x) asm "STGRAMS";
builder store_coins(builder b, int x) asm "STGRAMS";
;;; Stores dictionary `D` represented by `cell` [c] or `null` into `builder` [b].
;;; In other words, stores a `1`-bit and a reference to [c] if [c] is not `null` and `0`-bit otherwise.
builder store_dict(builder b, cell c) asm(c b) "STDICT";
;;; Stores (Maybe ^Cell) to builder:
;;; if cell is null store 1 zero bit
;;; otherwise store 1 true bit and ref to cell
builder store_maybe_ref(builder b, cell c) asm(c b) "STOPTREF";
{-
# Address manipulation primitives
The address manipulation primitives listed below serialize and deserialize values according to the following TL-B scheme:
```TL-B
addr_none$00 = MsgAddressExt;
addr_extern$01 len:(## 8) external_address:(bits len)
= MsgAddressExt;
anycast_info$_ depth:(#<= 30) { depth >= 1 }
rewrite_pfx:(bits depth) = Anycast;
addr_std$10 anycast:(Maybe Anycast)
workchain_id:int8 address:bits256 = MsgAddressInt;
addr_var$11 anycast:(Maybe Anycast) addr_len:(## 9)
workchain_id:int32 address:(bits addr_len) = MsgAddressInt;
_ _:MsgAddressInt = MsgAddress;
_ _:MsgAddressExt = MsgAddress;
int_msg_info$0 ihr_disabled:Bool bounce:Bool bounced:Bool
src:MsgAddress dest:MsgAddressInt
value:CurrencyCollection ihr_fee:Grams fwd_fee:Grams
created_lt:uint64 created_at:uint32 = CommonMsgInfoRelaxed;
ext_out_msg_info$11 src:MsgAddress dest:MsgAddressExt
created_lt:uint64 created_at:uint32 = CommonMsgInfoRelaxed;
```
A deserialized `MsgAddress` is represented by a tuple `t` as follows:
- `addr_none` is represented by `t = (0)`,
i.e., a tuple containing exactly one integer equal to zero.
- `addr_extern` is represented by `t = (1, s)`,
where slice `s` contains the field `external_address`. In other words, `
t` is a pair (a tuple consisting of two entries), containing an integer equal to one and slice `s`.
- `addr_std` is represented by `t = (2, u, x, s)`,
where `u` is either a `null` (if `anycast` is absent) or a slice `s'` containing `rewrite_pfx` (if anycast is present).
Next, integer `x` is the `workchain_id`, and slice `s` contains the address.
- `addr_var` is represented by `t = (3, u, x, s)`,
where `u`, `x`, and `s` have the same meaning as for `addr_std`.
-}
;;; Loads from slice [s] the only prefix that is a valid `MsgAddress`,
;;; and returns both this prefix `s'` and the remainder `s''` of [s] as slices.
(slice, slice) load_msg_addr(slice s) asm( -> 1 0) "LDMSGADDR";
;;; Decomposes slice [s] containing a valid `MsgAddress` into a `tuple t` with separate fields of this `MsgAddress`.
;;; If [s] is not a valid `MsgAddress`, a cell deserialization exception is thrown.
tuple parse_addr(slice s) asm "PARSEMSGADDR";
;;; Parses slice [s] containing a valid `MsgAddressInt` (usually a `msg_addr_std`),
;;; applies rewriting from the anycast (if present) to the same-length prefix of the address,
;;; and returns both the workchain and the 256-bit address as integers.
;;; If the address is not 256-bit, or if [s] is not a valid serialization of `MsgAddressInt`,
;;; throws a cell deserialization exception.
(int, int) parse_std_addr(slice s) asm "REWRITESTDADDR";
;;; A variant of [parse_std_addr] that returns the (rewritten) address as a slice [s],
;;; even if it is not exactly 256 bit long (represented by a `msg_addr_var`).
(int, slice) parse_var_addr(slice s) asm "REWRITEVARADDR";
{-
# Dictionary primitives
-}
;;; Sets the value associated with [key_len]-bit key signed index in dictionary [dict] to [value] (cell),
;;; and returns the resulting dictionary.
cell idict_set_ref(cell dict, int key_len, int index, cell value) asm(value index dict key_len) "DICTISETREF";
(cell, ()) ~idict_set_ref(cell dict, int key_len, int index, cell value) asm(value index dict key_len) "DICTISETREF";
;;; Sets the value associated with [key_len]-bit key unsigned index in dictionary [dict] to [value] (cell),
;;; and returns the resulting dictionary.
cell udict_set_ref(cell dict, int key_len, int index, cell value) asm(value index dict key_len) "DICTUSETREF";
(cell, ()) ~udict_set_ref(cell dict, int key_len, int index, cell value) asm(value index dict key_len) "DICTUSETREF";
cell idict_get_ref(cell dict, int key_len, int index) asm(index dict key_len) "DICTIGETOPTREF";
(cell, int) idict_get_ref?(cell dict, int key_len, int index) asm(index dict key_len) "DICTIGETREF" "NULLSWAPIFNOT";
(cell, int) udict_get_ref?(cell dict, int key_len, int index) asm(index dict key_len) "DICTUGETREF" "NULLSWAPIFNOT";
(cell, cell) idict_set_get_ref(cell dict, int key_len, int index, cell value) asm(value index dict key_len) "DICTISETGETOPTREF";
(cell, cell) udict_set_get_ref(cell dict, int key_len, int index, cell value) asm(value index dict key_len) "DICTUSETGETOPTREF";
(cell, int) idict_delete?(cell dict, int key_len, int index) asm(index dict key_len) "DICTIDEL";
(cell, int) udict_delete?(cell dict, int key_len, int index) asm(index dict key_len) "DICTUDEL";
(slice, int) idict_get?(cell dict, int key_len, int index) asm(index dict key_len) "DICTIGET" "NULLSWAPIFNOT";
(slice, int) udict_get?(cell dict, int key_len, int index) asm(index dict key_len) "DICTUGET" "NULLSWAPIFNOT";
(cell, slice, int) idict_delete_get?(cell dict, int key_len, int index) asm(index dict key_len) "DICTIDELGET" "NULLSWAPIFNOT";
(cell, slice, int) udict_delete_get?(cell dict, int key_len, int index) asm(index dict key_len) "DICTUDELGET" "NULLSWAPIFNOT";
(cell, (slice, int)) ~idict_delete_get?(cell dict, int key_len, int index) asm(index dict key_len) "DICTIDELGET" "NULLSWAPIFNOT";
(cell, (slice, int)) ~udict_delete_get?(cell dict, int key_len, int index) asm(index dict key_len) "DICTUDELGET" "NULLSWAPIFNOT";
cell udict_set(cell dict, int key_len, int index, slice value) asm(value index dict key_len) "DICTUSET";
(cell, ()) ~udict_set(cell dict, int key_len, int index, slice value) asm(value index dict key_len) "DICTUSET";
cell idict_set(cell dict, int key_len, int index, slice value) asm(value index dict key_len) "DICTISET";
(cell, ()) ~idict_set(cell dict, int key_len, int index, slice value) asm(value index dict key_len) "DICTISET";
cell dict_set(cell dict, int key_len, slice index, slice value) asm(value index dict key_len) "DICTSET";
(cell, ()) ~dict_set(cell dict, int key_len, slice index, slice value) asm(value index dict key_len) "DICTSET";
(cell, int) udict_add?(cell dict, int key_len, int index, slice value) asm(value index dict key_len) "DICTUADD";
(cell, int) udict_replace?(cell dict, int key_len, int index, slice value) asm(value index dict key_len) "DICTUREPLACE";
(cell, int) idict_add?(cell dict, int key_len, int index, slice value) asm(value index dict key_len) "DICTIADD";
(cell, int) idict_replace?(cell dict, int key_len, int index, slice value) asm(value index dict key_len) "DICTIREPLACE";
cell udict_set_builder(cell dict, int key_len, int index, builder value) asm(value index dict key_len) "DICTUSETB";
(cell, ()) ~udict_set_builder(cell dict, int key_len, int index, builder value) asm(value index dict key_len) "DICTUSETB";
cell idict_set_builder(cell dict, int key_len, int index, builder value) asm(value index dict key_len) "DICTISETB";
(cell, ()) ~idict_set_builder(cell dict, int key_len, int index, builder value) asm(value index dict key_len) "DICTISETB";
cell dict_set_builder(cell dict, int key_len, slice index, builder value) asm(value index dict key_len) "DICTSETB";
(cell, ()) ~dict_set_builder(cell dict, int key_len, slice index, builder value) asm(value index dict key_len) "DICTSETB";
(cell, int) udict_add_builder?(cell dict, int key_len, int index, builder value) asm(value index dict key_len) "DICTUADDB";
(cell, int) udict_replace_builder?(cell dict, int key_len, int index, builder value) asm(value index dict key_len) "DICTUREPLACEB";
(cell, int) idict_add_builder?(cell dict, int key_len, int index, builder value) asm(value index dict key_len) "DICTIADDB";
(cell, int) idict_replace_builder?(cell dict, int key_len, int index, builder value) asm(value index dict key_len) "DICTIREPLACEB";
(cell, int, slice, int) udict_delete_get_min(cell dict, int key_len) asm(-> 0 2 1 3) "DICTUREMMIN" "NULLSWAPIFNOT2";
(cell, (int, slice, int)) ~udict::delete_get_min(cell dict, int key_len) asm(-> 0 2 1 3) "DICTUREMMIN" "NULLSWAPIFNOT2";
(cell, int, slice, int) idict_delete_get_min(cell dict, int key_len) asm(-> 0 2 1 3) "DICTIREMMIN" "NULLSWAPIFNOT2";
(cell, (int, slice, int)) ~idict::delete_get_min(cell dict, int key_len) asm(-> 0 2 1 3) "DICTIREMMIN" "NULLSWAPIFNOT2";
(cell, slice, slice, int) dict_delete_get_min(cell dict, int key_len) asm(-> 0 2 1 3) "DICTREMMIN" "NULLSWAPIFNOT2";
(cell, (slice, slice, int)) ~dict::delete_get_min(cell dict, int key_len) asm(-> 0 2 1 3) "DICTREMMIN" "NULLSWAPIFNOT2";
(cell, int, slice, int) udict_delete_get_max(cell dict, int key_len) asm(-> 0 2 1 3) "DICTUREMMAX" "NULLSWAPIFNOT2";
(cell, (int, slice, int)) ~udict::delete_get_max(cell dict, int key_len) asm(-> 0 2 1 3) "DICTUREMMAX" "NULLSWAPIFNOT2";
(cell, int, slice, int) idict_delete_get_max(cell dict, int key_len) asm(-> 0 2 1 3) "DICTIREMMAX" "NULLSWAPIFNOT2";
(cell, (int, slice, int)) ~idict::delete_get_max(cell dict, int key_len) asm(-> 0 2 1 3) "DICTIREMMAX" "NULLSWAPIFNOT2";
(cell, slice, slice, int) dict_delete_get_max(cell dict, int key_len) asm(-> 0 2 1 3) "DICTREMMAX" "NULLSWAPIFNOT2";
(cell, (slice, slice, int)) ~dict::delete_get_max(cell dict, int key_len) asm(-> 0 2 1 3) "DICTREMMAX" "NULLSWAPIFNOT2";
(int, slice, int) udict_get_min?(cell dict, int key_len) asm (-> 1 0 2) "DICTUMIN" "NULLSWAPIFNOT2";
(int, slice, int) udict_get_max?(cell dict, int key_len) asm (-> 1 0 2) "DICTUMAX" "NULLSWAPIFNOT2";
(int, cell, int) udict_get_min_ref?(cell dict, int key_len) asm (-> 1 0 2) "DICTUMINREF" "NULLSWAPIFNOT2";
(int, cell, int) udict_get_max_ref?(cell dict, int key_len) asm (-> 1 0 2) "DICTUMAXREF" "NULLSWAPIFNOT2";
(int, slice, int) idict_get_min?(cell dict, int key_len) asm (-> 1 0 2) "DICTIMIN" "NULLSWAPIFNOT2";
(int, slice, int) idict_get_max?(cell dict, int key_len) asm (-> 1 0 2) "DICTIMAX" "NULLSWAPIFNOT2";
(int, cell, int) idict_get_min_ref?(cell dict, int key_len) asm (-> 1 0 2) "DICTIMINREF" "NULLSWAPIFNOT2";
(int, cell, int) idict_get_max_ref?(cell dict, int key_len) asm (-> 1 0 2) "DICTIMAXREF" "NULLSWAPIFNOT2";
(int, slice, int) udict_get_next?(cell dict, int key_len, int pivot) asm(pivot dict key_len -> 1 0 2) "DICTUGETNEXT" "NULLSWAPIFNOT2";
(int, slice, int) udict_get_nexteq?(cell dict, int key_len, int pivot) asm(pivot dict key_len -> 1 0 2) "DICTUGETNEXTEQ" "NULLSWAPIFNOT2";
(int, slice, int) udict_get_prev?(cell dict, int key_len, int pivot) asm(pivot dict key_len -> 1 0 2) "DICTUGETPREV" "NULLSWAPIFNOT2";
(int, slice, int) udict_get_preveq?(cell dict, int key_len, int pivot) asm(pivot dict key_len -> 1 0 2) "DICTUGETPREVEQ" "NULLSWAPIFNOT2";
(int, slice, int) idict_get_next?(cell dict, int key_len, int pivot) asm(pivot dict key_len -> 1 0 2) "DICTIGETNEXT" "NULLSWAPIFNOT2";
(int, slice, int) idict_get_nexteq?(cell dict, int key_len, int pivot) asm(pivot dict key_len -> 1 0 2) "DICTIGETNEXTEQ" "NULLSWAPIFNOT2";
(int, slice, int) idict_get_prev?(cell dict, int key_len, int pivot) asm(pivot dict key_len -> 1 0 2) "DICTIGETPREV" "NULLSWAPIFNOT2";
(int, slice, int) idict_get_preveq?(cell dict, int key_len, int pivot) asm(pivot dict key_len -> 1 0 2) "DICTIGETPREVEQ" "NULLSWAPIFNOT2";
;;; Creates an empty dictionary, which is actually a null value. Equivalent to PUSHNULL
cell new_dict() asm "NEWDICT";
;;; Checks whether a dictionary is empty. Equivalent to cell_null?.
int dict_empty?(cell c) asm "DICTEMPTY";
{- Prefix dictionary primitives -}
(slice, slice, slice, int) pfxdict_get?(cell dict, int key_len, slice key) asm(key dict key_len) "PFXDICTGETQ" "NULLSWAPIFNOT2";
(cell, int) pfxdict_set?(cell dict, int key_len, slice key, slice value) asm(value key dict key_len) "PFXDICTSET";
(cell, int) pfxdict_delete?(cell dict, int key_len, slice key) asm(key dict key_len) "PFXDICTDEL";
;;; Returns the value of the global configuration parameter with integer index `i` as a `cell` or `null` value.
cell config_param(int x) asm "CONFIGOPTPARAM";
;;; Checks whether c is a null. Note, that FunC also has polymorphic null? built-in.
int cell_null?(cell c) asm "ISNULL";
;;; Creates an output action which would reserve exactly amount nanotoncoins (if mode = 0), at most amount nanotoncoins (if mode = 2), or all but amount nanotoncoins (if mode = 1 or mode = 3), from the remaining balance of the account. It is roughly equivalent to creating an outbound message carrying amount nanotoncoins (or b − amount nanotoncoins, where b is the remaining balance) to oneself, so that the subsequent output actions would not be able to spend more money than the remainder. Bit +2 in mode means that the external action does not fail if the specified amount cannot be reserved; instead, all remaining balance is reserved. Bit +8 in mode means `amount <- -amount` before performing any further actions. Bit +4 in mode means that amount is increased by the original balance of the current account (before the compute phase), including all extra currencies, before performing any other checks and actions. Currently, amount must be a non-negative integer, and mode must be in the range 0..15.
() raw_reserve(int amount, int mode) impure asm "RAWRESERVE";
;;; Similar to raw_reserve, but also accepts a dictionary extra_amount (represented by a cell or null) with extra currencies. In this way currencies other than TonCoin can be reserved.
() raw_reserve_extra(int amount, cell extra_amount, int mode) impure asm "RAWRESERVEX";
;;; Sends a raw message contained in msg, which should contain a correctly serialized object Message X, with the only exception that the source address is allowed to have dummy value addr_none (to be automatically replaced with the current smart contract address), and ihr_fee, fwd_fee, created_lt and created_at fields can have arbitrary values (to be rewritten with correct values during the action phase of the current transaction). Integer parameter mode contains the flags. Currently mode = 0 is used for ordinary messages; mode = 128 is used for messages that are to carry all the remaining balance of the current smart contract (instead of the value originally indicated in the message); mode = 64 is used for messages that carry all the remaining value of the inbound message in addition to the value initially indicated in the new message (if bit 0 is not set, the gas fees are deducted from this amount); mode' = mode + 1 means that the sender wants to pay transfer fees separately; mode' = mode + 2 means that any errors arising while processing this message during the action phase should be ignored. Finally, mode' = mode + 32 means that the current account must be destroyed if its resulting balance is zero. This flag is usually employed together with +128.
() send_raw_message(cell msg, int mode) impure asm "SENDRAWMSG";
;;; Creates an output action that would change this smart contract code to that given by cell new_code. Notice that this change will take effect only after the successful termination of the current run of the smart contract
() set_code(cell new_code) impure asm "SETCODE";
;;; Generates a new pseudo-random unsigned 256-bit integer x. The algorithm is as follows: if r is the old value of the random seed, considered as a 32-byte array (by constructing the big-endian representation of an unsigned 256-bit integer), then its sha512(r) is computed; the first 32 bytes of this hash are stored as the new value r' of the random seed, and the remaining 32 bytes are returned as the next random value x.
int random() impure asm "RANDU256";
;;; Generates a new pseudo-random integer z in the range 0..range−1 (or range..−1, if range < 0). More precisely, an unsigned random value x is generated as in random; then z := x * range / 2^256 is computed.
int rand(int range) impure asm "RAND";
;;; Returns the current random seed as an unsigned 256-bit Integer.
int get_seed() impure asm "RANDSEED";
;;; Sets the random seed to unsigned 256-bit seed.
() set_seed(int x) impure asm "SETRAND";
;;; Mixes unsigned 256-bit integer x into the random seed r by setting the random seed to sha256 of the concatenation of two 32-byte strings: the first with the big-endian representation of the old seed r, and the second with the big-endian representation of x.
() randomize(int x) impure asm "ADDRAND";
;;; Equivalent to randomize(cur_lt());.
() randomize_lt() impure asm "LTIME" "ADDRAND";
;;; Checks whether the data parts of two slices coinside
int equal_slice_bits(slice a, slice b) asm "SDEQ";
int equal_slices(slice a, slice b) asm "SDEQ";
;;; Concatenates two builders
builder store_builder(builder to, builder from) asm "STBR";
================================================
FILE: example/gas-report.json
================================================
[
{
"label": "v1",
"createdAt": "2025-05-26T10:07:36.557Z",
"result": {
"Counter": {
"sendDeploy": {
"gasUsed": {
"kind": "init",
"value": "1937"
},
"cells": {
"kind": "init",
"value": "11"
},
"bits": {
"kind": "init",
"value": "892"
}
},
"send": {
"gasUsed": {
"kind": "init",
"value": "615"
},
"cells": {
"kind": "init",
"value": "11"
},
"bits": {
"kind": "init",
"value": "892"
}
},
"sendIncrease": {
"gasUsed": {
"kind": "init",
"value": "1937"
},
"cells": {
"kind": "init",
"value": "11"
},
"bits": {
"kind": "init",
"value": "892"
}
},
"increase": {
"gasUsed": {
"kind": "init",
"value": "2681"
},
"cells": {
"kind": "init",
"value": "11"
},
"bits": {
"kind": "init",
"value": "892"
}
}
}
}
},
{
"label": "current",
"createdAt": "2025-05-26T10:19:13.247Z",
"result": {
"Counter": {
"sendDeploy": {
"gasUsed": {
"kind": "same",
"value": "1937 same"
},
"cells": {
"kind": "same",
"value": "11 same"
},
"bits": {
"kind": "same",
"value": "892 same"
}
},
"send": {
"gasUsed": {
"kind": "same",
"value": "615 same"
},
"cells": {
"kind": "same",
"value": "11 same"
},
"bits": {
"kind": "same",
"value": "892 same"
}
},
"sendIncrease": {
"gasUsed": {
"kind": "same",
"value": "1937 same"
},
"cells": {
"kind": "same",
"value": "11 same"
},
"bits": {
"kind": "same",
"value": "892 same"
}
},
"increase": {
"gasUsed": {
"kind": "same",
"value": "2681 same"
},
"cells": {
"kind": "same",
"value": "11 same"
},
"bits": {
"kind": "same",
"value": "892 same"
}
}
}
}
}
]
================================================
FILE: example/jest.config.ts
================================================
import type { Config } from 'jest';
const config: Config = {
preset: 'ts-jest',
testEnvironment: '@ton/sandbox/jest-environment',
testPathIgnorePatterns: ['/node_modules/', '/dist/'],
reporters: [
'default',
['@ton/sandbox/jest-reporter', {
}],
]
};
export default config;
================================================
FILE: example/package.json
================================================
{
"name": "example",
"version": "0.0.1",
"license": "MIT",
"scripts": {
"gas-report": "blueprint test --gas-report",
"gas-snapshot": "npx blueprint snapshot --label",
"test": "jest"
},
"private": true,
"devDependencies": {
"@tact-lang/compiler": ">=1.6.12 <2.0.0",
"@ton-community/func-js": "^0.9.1",
"@ton/blueprint": ">=0.34.0",
"@ton/crypto": "^3.3.0",
"@ton/sandbox": ">=0.31.0",
"@ton/test-utils": ">=0.6.0",
"@ton/tolk-js": "^0.13.0",
"@ton/ton": ">=15.2.1 <16.0.0",
"@types/jest": "^29.5.14",
"@types/node": "^22.14.1",
"jest": "^29.7.0",
"prettier": "^3.5.3",
"ts-jest": "^29.3.2",
"ts-node": "^10.9.2",
"typescript": "^5.8.3"
}
}
================================================
FILE: example/scripts/deployCounter.ts
================================================
import { toNano } from '@ton/core';
import { Counter } from '../wrappers/Counter';
import { compile, NetworkProvider } from '@ton/blueprint';
export async function run(provider: NetworkProvider) {
const counter = provider.open(
Counter.createFromConfig(
{
id: Math.floor(Math.random() * 10000),
counter: 0,
},
await compile('Counter')
)
);
await counter.sendDeploy(provider.sender(), toNano('0.05'));
await provider.waitForDeploy(counter.address);
console.log('ID', await counter.getID());
}
================================================
FILE: example/scripts/incrementCounter.ts
================================================
import { Address, toNano } from '@ton/core';
import { Counter } from '../wrappers/Counter';
import { NetworkProvider, sleep } from '@ton/blueprint';
export async function run(provider: NetworkProvider, args: string[]) {
const ui = provider.ui();
const address = Address.parse(args.length > 0 ? args[0] : await ui.input('Counter address'));
if (!(await provider.isContractDeployed(address))) {
ui.write(`Error: Contract at address ${address} is not deployed!`);
return;
}
const counter = provider.open(Counter.createFromAddress(address));
const counterBefore = await counter.getCounter();
await counter.sendIncrease(provider.sender(), {
increaseBy: 1,
value: toNano('0.05'),
});
ui.write('Waiting for counter to increase...');
let counterAfter = await counter.getCounter();
let attempt = 1;
while (counterAfter === counterBefore) {
ui.setActionPrompt(`Attempt ${attempt}`);
await sleep(2000);
counterAfter = await counter.getCounter();
attempt++;
}
ui.clearActionPrompt();
ui.write('Counter increased successfully!');
}
================================================
FILE: example/tests/Counter.spec.ts
================================================
import { Blockchain, SandboxContract } from '@ton/sandbox';
import { Cell, toNano } from '@ton/core';
import { Counter } from '../wrappers/Counter';
import '@ton/test-utils';
import { compile } from '@ton/blueprint';
describe('Counter', () => {
let code: Cell;
beforeAll(async () => {
code = await compile('Counter');
});
let blockchain: Blockchain;
let counter: SandboxContract<Counter>;
beforeEach(async () => {
blockchain = await Blockchain.create();
counter = blockchain.openContract(
Counter.createFromConfig(
{
id: 0,
counter: 0,
},
code
)
);
const deployer = await blockchain.treasury('deployer');
const deployResult = await counter.sendDeploy(deployer.getSender(), toNano('0.05'));
expect(deployResult.transactions).toHaveTransaction({
from: deployer.address,
to: counter.address,
deploy: true,
});
});
it('should deploy', async () => {
// the check is done inside beforeEach
// blockchain and counter are ready to use
});
it('should increase counter', async () => {
const increaseTimes = 3;
for (let i = 0; i < increaseTimes; i++) {
console.log(`increase ${i + 1}/${increaseTimes}`);
const increaser = await blockchain.treasury('increaser' + i);
const counterBefore = await counter.getCounter();
console.log('counter before increasing', counterBefore);
const increaseBy = Math.floor(Math.random() * 100);
console.log('increasing by', increaseBy);
const increaseResult = await counter.sendIncrease(increaser.getSender(), {
increaseBy,
value: toNano('0.05'),
});
expect(increaseResult.transactions).toHaveTransaction({
from: increaser.address,
to: counter.address,
success: true,
});
const counterAfter = await counter.getCounter();
console.log('counter after increasing', counterAfter);
expect(counterAfter).toBe(counterBefore + increaseBy);
}
});
});
================================================
FILE: example/tsconfig.json
================================================
{
"compilerOptions": {
"target": "ES2020",
"outDir": "dist",
"module": "commonjs",
"declaration": true,
"esModuleInterop": true,
"forceConsistentCasingInFileNames": true,
"strict": true,
"skipLibCheck": true
}
}
================================================
FILE: example/wrappers/Counter.ts
================================================
import { Address, beginCell, Cell, Contract, contractAddress, ContractProvider, Sender, SendMode } from '@ton/core';
export type CounterConfig = {
id: number;
counter: number;
};
export function counterConfigToCell(config: CounterConfig): Cell {
return beginCell().storeUint(config.id, 32).storeUint(config.counter, 32).endCell();
}
export const Opcodes = {
increase: 0x7e8764ef,
};
export class Counter implements Contract {
constructor(readonly address: Address, readonly init?: { code: Cell; data: Cell }) {}
static createFromAddress(address: Address) {
return new Counter(address);
}
static createFromConfig(config: CounterConfig, code: Cell, workchain = 0) {
const data = counterConfigToCell(config);
const init = { code, data };
return new Counter(contractAddress(workchain, init), init);
}
async sendDeploy(provider: ContractProvider, via: Sender, value: bigint) {
await provider.internal(via, {
value,
sendMode: SendMode.PAY_GAS_SEPARATELY,
body: beginCell().endCell(),
});
}
async sendIncrease(
provider: ContractProvider,
via: Sender,
opts: {
increaseBy: number;
value: bigint;
queryID?: number;
}
) {
await provider.internal(via, {
value: opts.value,
sendMode: SendMode.PAY_GAS_SEPARATELY,
body: beginCell()
.storeUint(Opcodes.increase, 32)
.storeUint(opts.queryID ?? 0, 64)
.storeUint(opts.increaseBy, 32)
.endCell(),
});
}
async getCounter(provider: ContractProvider) {
const result = await provider.get('get_counter', []);
return result.stack.readNumber();
}
async getID(provider: ContractProvider) {
const result = await provider.get('get_id', []);
return result.stack.readNumber();
}
}
================================================
FILE: jest.config.js
================================================
module.exports = {
preset: 'ts-jest',
testEnvironment: 'node',
testPathIgnorePatterns: ['src/cli/test.ts'],
};
================================================
FILE: package.json
================================================
{
"name": "@ton/blueprint",
"version": "0.44.2",
"description": "Framework for development of TON smart contracts",
"main": "dist/index.js",
"bin": "./dist/cli/cli.js",
"author": "TonTech",
"license": "MIT",
"repository": {
"type": "git",
"url": "git+https://github.com/ton-org/blueprint.git"
},
"files": [
"dist/**/*"
],
"scripts": {
"build": "rm -rf dist && tsc && cp -r src/templates dist/",
"test": "jest src",
"release": "yarn build && yarn publish --access public",
"lint": "eslint . --max-warnings 0",
"lint:fix": "eslint . --max-warnings 0 --fix"
},
"prettier": "@ton/toolchain/prettier",
"devDependencies": {
"@tact-lang/compiler": "^1.6.13",
"@ton-community/func-js": "^0.10.0",
"@ton/core": "^0.63.1",
"@ton/crypto": "^3.3.0",
"@ton/sandbox": "^0.40.0",
"@ton/tolk-js": "^1.0.0",
"@ton/ton": "^16.2.4",
"@ton/toolchain": "the-ton-tech/toolchain#v1.6.0",
"@types/inquirer": "^8.2.6",
"@types/jest": "^30.0.0",
"@types/node": "^20.2.5",
"@types/qrcode-terminal": "^0.12.0",
"eslint": "^9.28.0",
"globals": "^16.4.0",
"jest": "^30.0.5",
"ts-jest": "^29.4.1",
"typescript": "^5.8.3"
},
"peerDependencies": {
"@tact-lang/compiler": ">=1.6.5",
"@ton-community/func-js": ">=0.10.0",
"@ton/core": ">=0.63.1",
"@ton/crypto": ">=3.3.0",
"@ton/sandbox": ">=0.40.0",
"@ton/tolk-js": ">=0.13.0",
"@ton/ton": ">=16.2.4"
},
"peerDependenciesMeta": {
"@ton/sandbox": {
"optional": true
}
},
"dependencies": {
"@ton-api/client": "^0.2.0",
"@ton-api/ton-adapter": "^0.2.0",
"@tonconnect/sdk": "^2.2.0",
"arg": "^5.0.2",
"axios": "1.15.0",
"chalk": "^4.1.0",
"dotenv": "^16.1.4",
"inquirer": "^8.2.5",
"qrcode-terminal": "^0.12.0",
"ton-lite-client": "^3.1.1",
"ts-node": "^10.9.1"
},
"packageManager": "yarn@4.9.2"
}
================================================
FILE: src/build.ts
================================================
import path from 'path';
import fs from 'fs/promises';
import {
doCompile,
extractCompilableConfig,
getCompilerConfigForContract,
getCompilerOptions,
libraryCellFromCode,
} from './compile/compile';
import { BUILD_DIR } from './paths';
import { UIProvider } from './ui/UIProvider';
import { findCompiles, findContracts } from './utils';
import { getRootTactConfig } from './config/tact.config';
export async function buildOne(contract: string, ui?: UIProvider) {
ui?.write(`Build script running, compiling ${contract}`);
const buildArtifactPath = path.join(BUILD_DIR, `${contract}.compiled.json`);
try {
await fs.unlink(buildArtifactPath);
// eslint-disable-next-line no-empty
} catch (_) {}
ui?.setActionPrompt('⏳ Compiling...');
try {
const config = await getCompilerConfigForContract(contract);
const compilerOptions = await getCompilerOptions(config);
ui?.write(`🔧 Using ${compilerOptions.lang} version ${compilerOptions.version}...`);
// Build raw code cell by default
const result = await doCompile(contract, { buildLibrary: false });
if (result.lang === 'tact') {
for (const [k, v] of result.fs) {
await fs.mkdir(path.dirname(k), {
recursive: true,
});
await fs.writeFile(k, v);
}
if (result.options !== undefined && result.options?.debug === true) {
ui?.clearActionPrompt();
ui?.write(
'\n⚠️ Make sure to disable debug mode in contract wrappers before doing production deployments!',
);
}
}
let libAttributes:
| {
libraryHash: string;
libraryBoc: string;
}
| undefined;
const cell = result.code;
// If build was configured as library, add attributes
if ('buildLibrary' in config && config.buildLibrary === true) {
const libCell = libraryCellFromCode(cell);
libAttributes = {
libraryHash: libCell.hash().toString('hex'),
libraryBoc: libCell.toBoc().toString('hex'),
};
}
const rHash = cell.hash();
const res = {
hash: rHash.toString('hex'),
hashBase64: rHash.toString('base64'),
hex: cell.toBoc().toString('hex'),
...libAttributes,
};
ui?.clearActionPrompt();
if (result.lang === 'tolk') {
ui?.write(`\n${result.stderr}`);
}
ui?.write('\n✅ Compiled successfully! Cell BOC result:\n\n');
ui?.write(JSON.stringify(res, null, 2));
await fs.mkdir(BUILD_DIR, { recursive: true });
await fs.writeFile(buildArtifactPath, JSON.stringify(res));
if (result.lang === 'func' || result.lang === 'tolk') {
const fiftFilepath = path.join(BUILD_DIR, contract, `${contract}.fif`);
await fs.mkdir(path.join(BUILD_DIR, contract), { recursive: true });
await fs.writeFile(fiftFilepath, result.fiftCode);
}
ui?.write(`\n✅ Wrote compilation artifact to ${path.relative(process.cwd(), buildArtifactPath)}`);
} catch (e) {
if (ui) {
ui?.clearActionPrompt();
ui?.write((e as Error).toString());
process.exit(1);
} else {
throw e;
}
}
}
async function buildContracts(contracts: string[], ui?: UIProvider) {
for (const contract of contracts) {
await buildOne(contract, ui);
}
}
export async function buildAll(ui?: UIProvider) {
await buildContracts(await findContracts(), ui);
}
export async function buildAllTact(ui?: UIProvider) {
const legacyTactContract = (await findCompiles())
.filter((file) => extractCompilableConfig(file.path).lang === 'tact')
.map((file) => file.name);
const tactConfig = getRootTactConfig();
const tactContracts = [...legacyTactContract, ...tactConfig.projects.map((project) => project.name)];
await buildContracts(tactContracts, ui);
}
================================================
FILE: src/cli/Runner.ts
================================================
import arg from 'arg';
import { UIProvider } from '../ui/UIProvider';
import { Config } from '../config/Config';
export const argSpec = {};
export type Args = arg.Result<typeof argSpec>;
export function extractPosArg(args: Args, position: number) {
return args._.length > position && args._[position].trim().length > 0 ? args._[position].trim() : undefined;
}
export function extractSecondArg(args: Args) {
return extractPosArg(args, 2);
}
export function extractFirstArg(args: Args) {
return extractPosArg(args, 1);
}
export type RunnerContext = {
config?: Config;
};
export type Runner = (args: Args, ui: UIProvider, context: RunnerContext) => Promise<void>;
================================================
FILE: src/cli/build.ts
================================================
import arg from 'arg';
import { findContracts, selectOption } from '../utils';
import { UIProvider } from '../ui/UIProvider';
import { buildAll, buildOne } from '../build';
import { helpArgs, helpMessages } from './constants';
import { Args, extractFirstArg, Runner } from './Runner';
export async function selectContract(ui: UIProvider, hint?: string): Promise<string>;
export async function selectContract(
ui: UIProvider,
hint?: string,
withAllOption?: boolean,
): Promise<string | string[]>;
export async function selectContract(
ui: UIProvider,
hint?: string,
withAllOption: boolean = false,
): Promise<string | string[]> {
const contracts = await findContracts();
const options = contracts.map<{ name: string; value: string }>((contract) => ({ name: contract, value: contract }));
const allContractsValue = 'all_contracts';
if (withAllOption) {
const allContractsOption = {
name: 'All Contracts',
value: allContractsValue,
};
options.push(allContractsOption);
}
const selectedOption = await selectOption(options, {
msg: 'Select contract to use',
ui,
hint,
});
if (selectedOption.value === allContractsValue) {
return contracts;
}
return selectedOption.value;
}
export const build: Runner = async (args: Args, ui: UIProvider) => {
const localArgs = arg({
'--all': Boolean,
...helpArgs,
});
if (localArgs['--help']) {
ui.write(helpMessages['build']);
return;
}
if (localArgs['--all']) {
await buildAll(ui);
} else {
const selected = await selectContract(ui, extractFirstArg(args), true);
if (typeof selected === 'string') {
await buildOne(selected, ui);
} else {
await buildAll(ui);
}
}
};
================================================
FILE: src/cli/cli.ts
================================================
#!/usr/bin/env node
import * as dotenv from 'dotenv';
dotenv.config();
import arg from 'arg';
import chalk from 'chalk';
import { snapshot } from './snapshot';
import { create } from './create';
import { run } from './run';
import { build } from './build';
import { set } from './set';
import { test } from './test';
import { verify } from './verify';
import { convert } from './convert';
import { additionalHelpMessages, buildHelpMessage, help } from './help';
import { pack } from './pack';
import { InquirerUIProvider } from '../ui/InquirerUIProvider';
import { argSpec, Runner, RunnerContext } from './Runner';
import { getConfig } from '../config/utils';
import { rename } from './rename';
import { availableCommands, KnownCommandName } from './constants';
const runners: Record<KnownCommandName, Runner> = {
create,
run,
build,
set,
test,
help,
verify,
convert,
rename,
pack,
snapshot,
};
async function main() {
// eslint-disable-next-line @typescript-eslint/no-require-imports
require('ts-node/register');
const args = arg(argSpec, {
permissive: true,
});
if (args._.length === 0) {
showHelp();
process.exit(0);
}
let effectiveRunners: Record<string, Runner> = {};
const runnerContext: RunnerContext = {};
const config = await getConfig();
try {
runnerContext.config = config;
for (const plugin of config?.plugins ?? []) {
for (const runner of plugin.runners()) {
effectiveRunners[runner.name] = runner.runner;
additionalHelpMessages[runner.name] = runner.help;
}
}
} catch (e) {
// if plugin.runners() throws
console.error('Could not load one or more plugins');
console.error(e);
}
effectiveRunners = {
...effectiveRunners,
...runners,
};
const command = args._[0];
const runner = effectiveRunners[command];
if (!runner) {
console.log(
chalk.redBright(`Error: command ${command} not found.`) +
`\nRunning ${chalk.cyanBright('blueprint help')}...`,
);
const helpMessage = buildHelpMessage();
console.log(helpMessage);
process.exit(1);
return;
}
const ui = new InquirerUIProvider();
await runner(args, ui, runnerContext);
ui.close();
}
process.on('SIGINT', () => {
process.exit(130);
});
main()
.catch(console.error)
.then(() => process.exit(0));
function showHelp() {
console.log(
chalk.blueBright(`
____ _ _ _ _____ ____ ____ ___ _ _ _____
| __ )| | | | | | ____| _ \\| _ \\|_ _| \\ | |_ _|
| _ \\| | | | | | _| | |_) | |_) || || \\| | | |
| |_) | |__| |_| | |___| __/| _ < | || |\\ | | |
|____/|_____\\___/|_____|_| |_| \\_\\___|_| \\_| |_| `),
);
console.log(chalk.blue(` TON development for professionals`));
console.log(``);
console.log(` Usage: blueprint [OPTIONS] COMMAND [ARGS]`);
console.log(``);
const mainPageCommands: Set<KnownCommandName> = new Set(['create', 'build', 'test', 'run', 'help']);
for (const cmd of availableCommands) {
if (!mainPageCommands.has(cmd.name)) {
continue;
}
const commandName = ` blueprint ${cmd.name}`;
const description = cmd.description;
const cmdPadding = ' '.repeat(Math.max(0, 24 - commandName.length));
const examplePadding = ' '.repeat(cmdPadding.length + commandName.length);
console.log(chalk.cyanBright(commandName) + cmdPadding + chalk.whiteBright(description));
console.log(examplePadding + chalk.gray(cmd.example));
}
console.log(``);
}
================================================
FILE: src/cli/constants.ts
================================================
import chalk from 'chalk';
export const templateTypes: { name: string; value: string }[] = [
{
name: 'An empty contract (Tolk)',
value: 'tolk-empty',
},
{
name: 'An empty contract (FunC)',
value: 'func-empty',
},
{
name: 'An empty contract (Tact)',
value: 'tact-empty',
},
{
name: 'A simple counter contract (Tolk)',
value: 'tolk-counter',
},
{
name: 'A simple counter contract (FunC)',
value: 'func-counter',
},
{
name: 'A simple counter contract (Tact)',
value: 'tact-counter',
},
];
export const helpArgs = { '--help': Boolean, '-h': '--help' };
export type KnownCommandName =
| 'create'
| 'run'
| 'build'
| 'set'
| 'help'
| 'test'
| 'verify'
| 'convert'
| 'rename'
| 'pack'
| 'snapshot';
export interface CommandInfo {
readonly name: KnownCommandName;
readonly description: string;
readonly example: string;
}
export const availableCommands: CommandInfo[] = [
{
name: 'create',
description: 'create a new contract with .fc source, .ts wrapper, .spec.ts test',
example: 'blueprint create ContractName',
},
{
name: 'build',
description: 'builds a contract that has a .compile.ts file',
example: 'blueprint build ContractName',
},
{ name: 'test', description: 'run the full project test suite with all .spec.ts files', example: 'blueprint test' },
{
name: 'run',
description: "runs a script from 'scripts' directory (eg. a deploy script)",
example: 'blueprint run deployContractName',
},
{
name: 'help',
description: 'shows more detailed help, also see https://github.com/ton-org/blueprint',
example: 'blueprint help',
},
{ name: 'set', description: 'sets configuration values', example: 'blueprint set' },
{ name: 'verify', description: 'verifies a deployed contract on verifier.ton.org', example: 'blueprint verify' },
{
name: 'convert',
description: 'converts legacy bash build scripts to Blueprint wrappers',
example: 'blueprint convert',
},
{
name: 'rename',
description: 'renames contract by matching in wrappers, scripts and tests',
example: 'blueprint rename',
},
{ name: 'pack', description: 'builds and prepares a publish-ready package of wrappers', example: 'blueprint pack' },
{
name: 'snapshot',
description: 'creates snapshots with gas usage and cells sizes',
example: 'blueprint snapshot',
},
];
export const helpMessages = {
help: `${chalk.bold('Usage:')} blueprint ${chalk.cyan('help')} [${chalk.yellow('command')}]
Displays this message if no command is specified, or displays detailed help for the specified command.
Blueprint is generally invoked as follows:
${chalk.cyan('blueprint')} ${chalk.yellow('[command]')} ${chalk.gray('[command-args]')} ${chalk.gray('[flags]')}
${chalk.bold('List of available commands:')}
${availableCommands.map((c) => ` ${chalk.cyanBright(c.name)}${' '.repeat(Math.max(0, 20 - c.name.length))}${c.description}`).join('\n')}
To get more information about a command, run ${chalk.cyan('blueprint help <command>')}`,
create: `${chalk.bold('Usage:')} blueprint ${chalk.cyan('create')} ${chalk.yellow('[contract name]')} ${chalk.gray('[flags]')}
Creates a new contract together with supporting files according to a template.
Contract name must be specified in PascalCase and may only include characters a-z, A-Z, 0-9. If not specified on the command line, it will be asked interactively.
${chalk.bold('Flags:')}
${chalk.cyan('--type')} <type> - specifies the template type to use when creating the contract. If not specified on the command line, it will be asked interactively.
${chalk.bold('List of available types:')}
${templateTypes.map((t) => `${chalk.cyan(t.value)} - ${t.name}`).join('\n')}`,
run: `${chalk.bold('Usage:')} blueprint ${chalk.cyan('run')} ${chalk.yellow('[script name]')} ${chalk.gray('[flags]')} ${chalk.gray('[...args]')}
Runs a script from the scripts directory.
Script name is matched (ignoring case) to a file in the scripts directory. If not specified on the command line, the available scripts will be presented interactively.
${chalk.bold('Flags:')}
${chalk.cyan('--mainnet')}, ${chalk.cyan('--testnet')} - selects network
${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-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.
${chalk.bold('Examples:')}
blueprint run ${chalk.yellow('deployCounter')} ${chalk.cyan('--testnet')} ${chalk.cyan('--tonconnect')}
blueprint run ${chalk.yellow('incrementCounter')} ${chalk.cyan('--testnet')} ${chalk.cyan('--tonconnect')} ${chalk.gray('0.05 1')}`,
build: `${chalk.bold('Usage:')} blueprint ${chalk.cyan('build')} ${chalk.yellow('[contract name]')} ${chalk.gray('[flags]')}
Builds the specified contract according to the respective .compile.ts file. For Tact contracts, all generated files will be placed in the ${chalk.cyan('build/<contract name>')} folder.
${chalk.bold('Flags:')}
${chalk.cyan('--all')} - builds all available contracts.`,
set: `${chalk.bold('Usage:')} blueprint ${chalk.cyan('set')} <${chalk.yellow('key')}> [${chalk.yellow('value')}]
${chalk.bold('Available keys:')}
- ${chalk.cyan('func')} - overrides @ton-community/func-js-bin version.`,
test: `${chalk.bold('Usage:')} blueprint ${chalk.cyan('test')} ${chalk.yellow('[--gas-report|-g ...args]')}
Runs ${chalk.green('npm test [...args]')}, which by default executes ${chalk.green('jest')}
${chalk.bold('Options:')}
${chalk.cyan('--gas-report')}, ${chalk.cyan('-g')} - Run tests and compare with the last snapshot's metrics
${chalk.cyan('--ui')} - Connects to sandbox UI server
${chalk.cyan('--coverage')} - Collects tests coverage
${chalk.bold('SEE ALSO')}
${chalk.cyan('blueprint snapshot')}
`,
verify: `${chalk.bold('Usage:')} blueprint ${chalk.cyan('verify')} ${chalk.yellow('[contract name]')} ${chalk.gray('[flags]')}
Verifies a deployed contract on ${chalk.underline('https://verifier.ton.org')}.
${chalk.bold('Flags:')}
${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.
${chalk.cyan('--custom')} [api-endpoint] - use custom API (requires --custom-type)
${chalk.cyan('--custom-version')} - API version (v2 default)
${chalk.cyan('--custom-key')} - API key (v2 only)
${chalk.cyan('--custom-type')} - network type (mainnet, testnet, tetra)`,
convert: `${chalk.bold('Usage:')} blueprint ${chalk.cyan('convert')} ${chalk.yellow('[path to build script]')}
Attempts to convert a legacy bash build script to a Blueprint compile wrapper.`,
rename: `${chalk.bold('Usage:')} blueprint ${chalk.cyan('rename')} ${chalk.yellow('[old contract name (PascalCase)]')} ${chalk.yellow('[new contract name (PascalCase)]')}
Renames contract by exact matching in wrappers, scripts, tests and contracts folders.`,
pack: `${chalk.bold('Usage:')} blueprint ${chalk.cyan('pack')}
Builds and prepares a publish-ready package of contract wrappers.
${chalk.bold('Flags:')}
${chalk.cyan('--no-warn')}, ${chalk.cyan('-n')} - ignore warnings about modifying tsconfig.json, package.json, and removing the dist directory.`,
snapshot: `${chalk.bold('Usage:')} blueprint ${chalk.cyan('snapshot')} ${chalk.yellow(
'[--label=<comment>|-l=<comment>]',
)}
Run with gas usage and cells' sizes collected and write a new snapshot
${chalk.bold('SEE ALSO')}
${chalk.cyan('blueprint test --gas-report')}`,
};
================================================
FILE: src/cli/convert.ts
================================================
import { readFileSync, existsSync, writeFileSync, mkdirSync, readdirSync, statSync } from 'fs';
import path from 'path';
import arg from 'arg';
import { UIProvider } from '../ui/UIProvider';
import { argSpec } from '../network/createNetworkProvider';
import { executeTemplate, TEMPLATES_DIR } from '../template';
import { WRAPPERS_DIR } from '../paths';
import { Args, Runner } from './Runner';
import { helpArgs, helpMessages } from './constants';
function createWrapperName(old: string) {
return old
.split(/[-_]/)
.map((x) => x.replace(x[0], x[0].toUpperCase()))
.join('');
}
function quoteString(str: string) {
const quote = "'";
const quoted = str.startsWith(quote) && str.endsWith(quote);
return quoted ? str : quote + str + quote;
}
function findFile(dir: string, filename: string): string {
const contents = readdirSync(dir);
let hasFile = contents.includes(filename);
let foundPath = '';
if (hasFile) {
foundPath = path.join(dir, filename);
if (statSync(foundPath).isFile()) {
return foundPath;
}
}
for (let entry of contents) {
const entryPath = path.join(dir, entry);
const stat = statSync(entryPath);
if (stat.isDirectory()) {
foundPath = findFile(entryPath, filename);
if (foundPath !== '') {
break;
}
}
}
return foundPath;
}
function parseCompileString(str: string, src_dir: string, _ui: UIProvider) {
// Naive but does the job
const tokens = str.split(/\\?\s+/).filter((t) => t != '\\');
const outputIdx = tokens.indexOf('-o');
if (outputIdx < 0) {
throw new Error('No output flag (-o) found in command:' + str);
}
const outFile = tokens[outputIdx + 1];
const outputName = outFile.match(/([A-Za-z0-9\-_\\/]*)/);
if (outputName === null) {
throw new Error(`Something went wrong when parsing output from ${outFile}`);
}
const wrapperName = createWrapperName(path.basename(outputName[1]));
const sourceFiles = tokens.filter((x) => x.match(/\.func|\.fc['"]?$/) !== null).map((t) => t.replace(/['"`]/g, ''));
if (sourceFiles.length === 0) {
throw new Error(`No source files found in command:${str}`);
}
for (let i = 0; i < sourceFiles.length; i++) {
const testPath = path.join(src_dir, sourceFiles[i]);
if (existsSync(testPath)) {
sourceFiles[i] = quoteString(testPath);
} else {
const foundPath = findFile(src_dir, sourceFiles[i]);
if (foundPath === '') {
throw new Error(`${sourceFiles[i]} is not found anywhere`);
}
src_dir = path.dirname(foundPath);
sourceFiles[i] = quoteString(foundPath);
}
}
return {
name: wrapperName,
targets: sourceFiles.join(','),
};
}
export const convert: Runner = async (_args: Args, ui: UIProvider) => {
const localArgs = arg({ ...argSpec, ...helpArgs });
if (localArgs['--help']) {
ui.write(helpMessages['convert']);
return;
}
let filePath: string;
if (localArgs._.length < 2) {
filePath = await ui.input('Please specify path to convert from:');
} else {
filePath = localArgs._[1];
}
const content = readFileSync(filePath, { encoding: 'utf-8' });
const srcDir = path.dirname(filePath);
const compileStrings = content.replace(/\\[\r?\n]+/g, '').matchAll(/\s?func\s+(.*)\n/g);
if (compileStrings === null) {
throw new Error(`No func related commands found in ${filePath}`);
}
const templatePath = path.join(TEMPLATES_DIR, 'func', 'legacy', 'wrappers', 'compile.ts.template');
const templateContent = readFileSync(templatePath, { encoding: 'utf-8' });
if (!existsSync(WRAPPERS_DIR)) {
ui.write('Creating wrappers directory...');
mkdirSync(WRAPPERS_DIR);
}
for (let compileStr of compileStrings) {
let parsed: { name: string; targets: string };
try {
parsed = parseCompileString(compileStr[1], srcDir, ui);
} catch (e: any) {
ui.write(e.toString());
continue;
}
const resTemplate = executeTemplate(templateContent, parsed);
let lineIdx = resTemplate.indexOf('\n');
const contentIdx = lineIdx + 1;
if (resTemplate[lineIdx - 1] === '\r') {
lineIdx--;
}
const wrapperName = resTemplate.substring(0, lineIdx);
const fileName = path.join(WRAPPERS_DIR, wrapperName);
const content = resTemplate.substring(contentIdx);
if (existsSync(fileName)) {
ui.write(`File ${wrapperName} already exists!`);
const overwrite = await ui.prompt('Do you want to overwrite it?');
if (!overwrite) {
ui.write(`Skipping ${wrapperName}`);
continue;
}
}
writeFileSync(fileName, content, { encoding: 'utf-8' });
ui.write(`${wrapperName} wrapper created!`);
}
};
================================================
FILE: src/cli/create.ts
================================================
import path from 'path';
import unixPath from 'path/posix';
import { lstat, mkdir, open, readdir, readFile } from 'fs/promises';
import arg from 'arg';
import { Project } from '@tact-lang/compiler';
import chalk from 'chalk';
import { getConfig } from '../config/utils';
import { getRootTactConfig, TactConfig, updateRootTactConfig } from '../config/tact.config';
import { Args, extractFirstArg, Runner } from './Runner';
import { executeTemplate, TEMPLATES_DIR } from '../template';
import { validateContractName, selectOption, toSnakeCase } from '../utils';
import { UIProvider } from '../ui/UIProvider';
import { buildOne } from '../build';
import { helpArgs, helpMessages, templateTypes } from './constants';
async function createFile(templatePath: string, realPath: string, replaces: { [k: string]: string }) {
const template = (await readFile(templatePath)).toString('utf-8');
const lines = template.split('\n');
const fileName = executeTemplate(lines.shift()!, replaces);
const contents = executeTemplate(lines.join('\n'), replaces);
const p = path.join(realPath, fileName);
const file = await open(p, 'a+');
if ((await file.stat()).size > 0) {
console.warn(`${p} already exists, not changing.`);
await file.close();
return;
}
await file.writeFile(contents);
await file.close();
}
async function createFiles(templatePath: string, realPath: string, replaces: { [k: string]: string }) {
const contents = await readdir(templatePath);
for (const file of contents) {
const tp = path.join(templatePath, file);
const rp = path.join(realPath, file);
if ((await lstat(tp)).isDirectory()) {
await createFiles(tp, rp, replaces);
} else {
await mkdir(path.dirname(rp), {
recursive: true,
});
await createFile(tp, realPath, replaces);
}
}
}
function getFileExtension(lang: string): string {
if (lang === 'func') return 'fc';
if (lang === 'tolk') return 'tolk';
return 'tact';
}
function addToTactConfig(contractName: string, contractPath: string) {
const tactConfig = getRootTactConfig();
const projectConfig = {
name: contractName,
path: contractPath,
output: path.join('build', contractName),
options: {
debug: false,
external: false,
},
mode: 'full',
} satisfies Project;
const newConfig: TactConfig = {
...tactConfig,
projects: [...tactConfig.projects, projectConfig],
};
updateRootTactConfig(newConfig);
}
export const create: Runner = async (_args: Args, ui: UIProvider) => {
const localArgs = arg({
'--type': String,
...helpArgs,
});
if (localArgs['--help']) {
ui.write(helpMessages['create']);
return;
}
const argName = extractFirstArg(localArgs);
if (argName !== undefined) {
const error = validateContractName(argName);
if (error) {
throw new Error(error);
}
}
const name = argName ?? (await requestContractName('Contract name (PascalCase)', ui));
const which = (
await selectOption(templateTypes, {
ui,
msg: 'What type of contract do you want to create?',
hint: localArgs['--type'],
})
).value;
const [lang, template] = which.split('-');
const snakeName = toSnakeCase(name);
const contractPath = unixPath.join('contracts', snakeName + '.' + getFileExtension(lang));
const replaces = {
name,
loweredName: name.substring(0, 1).toLowerCase() + name.substring(1),
snakeName,
contractPath,
};
const config = await getConfig();
if (lang === 'tact') {
await createFiles(path.join(TEMPLATES_DIR, lang, template), process.cwd(), replaces);
addToTactConfig(name, contractPath);
await buildOne(name, ui);
} else {
const commonPath = config?.separateCompilables ? 'common' : 'not-separated-common';
await createFiles(path.join(TEMPLATES_DIR, lang, commonPath), process.cwd(), replaces);
await createFiles(path.join(TEMPLATES_DIR, lang, template), process.cwd(), replaces);
}
};
export async function requestContractName(message: string, ui: UIProvider): Promise<string> {
while (true) {
const name = await ui.input(message);
const error = validateContractName(name);
if (error !== undefined) {
ui.write(chalk.redBright(`Error: `) + error);
// ask user again
continue;
}
return name;
}
}
================================================
FILE: src/cli/help.ts
================================================
import { UIProvider } from '../ui/UIProvider';
import { Args, extractFirstArg, Runner } from './Runner';
import { helpMessages } from './constants';
export let additionalHelpMessages: Record<string, string> = {};
export function buildHelpMessage(cmd: string = '') {
const effectiveHelpMessages: Record<string, string> = {
...additionalHelpMessages,
...helpMessages,
};
for (const k in additionalHelpMessages) {
effectiveHelpMessages.help += '\n- ' + k;
}
return cmd in effectiveHelpMessages ? effectiveHelpMessages[cmd] : effectiveHelpMessages['help'];
}
export const help: Runner = async (args: Args, ui: UIProvider) => {
const cmd = extractFirstArg(args)?.toLowerCase();
const helpMessage = buildHelpMessage(cmd);
ui.write(helpMessage);
};
================================================
FILE: src/cli/pack.ts
================================================
import { execSync } from 'child_process';
import path from 'path';
import { promises as fs, existsSync } from 'fs';
import arg from 'arg';
import { Args, Runner, RunnerContext } from './Runner';
import { UIProvider } from '../ui/UIProvider';
import { helpArgs, helpMessages } from './constants';
import { buildAll } from '../build';
import { BUILD_DIR, PACKAGE_ENTRY_POINT, PACKAGE_JSON, TYPESCRIPT_CONFIG } from '../paths';
import { distinct, findContracts } from '../utils';
import { getCompilerConfigForContract } from '../compile/compile';
import { isCompilableConfig } from '../compile/CompilerConfig';
import { extractContractConfig } from '../compile/tact/compile.tact';
async function correctTsConfig() {
if (!existsSync(TYPESCRIPT_CONFIG)) {
throw new Error('TypeScript config does not exist. Ensure the command runs in the correct environment.');
}
const tsConfig = JSON.parse(await fs.readFile(TYPESCRIPT_CONFIG, 'utf-8'));
const newConfig = {
...tsConfig,
compilerOptions: {
...tsConfig.compilerOptions,
outDir: 'dist',
declaration: true,
esModuleInterop: true,
},
include: distinct([...(tsConfig.include ?? []), 'package.ts']),
};
await fs.writeFile(TYPESCRIPT_CONFIG, JSON.stringify(newConfig, null, 2), 'utf8');
}
async function getContractWrapperPath(contract: string) {
const config = await getCompilerConfigForContract(contract);
if (isCompilableConfig(config)) {
return `./wrappers/${contract}`;
} else {
const contractConfig = extractContractConfig(config, contract);
return `./${contractConfig.output}/${contract}_${contract}`;
}
}
async function generatePackageEntryPoint() {
const contracts = await findContracts();
let entryPoint = 'import { Cell } from "@ton/core"\n';
for (const contract of contracts) {
const wrapperPath = await getContractWrapperPath(contract);
entryPoint += `import * as ${contract} from '${wrapperPath}';\n`;
entryPoint += `export { ${contract} };\n`;
const buildArtifactPath = path.join(BUILD_DIR, `${contract}.compiled.json`);
const buildArtifact = JSON.parse(await fs.readFile(buildArtifactPath, 'utf8'));
entryPoint += `export const ${contract}Code = Cell.fromHex("${buildArtifact.hex}");\n`;
}
await fs.writeFile(PACKAGE_ENTRY_POINT, entryPoint, 'utf8');
}
async function correctPackageJson() {
const packageJson = JSON.parse(await fs.readFile(PACKAGE_JSON, 'utf8'));
const newPackageJson = {
...packageJson,
main: 'dist/package.js',
files: ['dist/**/*'],
};
await fs.writeFile(PACKAGE_JSON, JSON.stringify(newPackageJson, null, 2));
}
export const pack: Runner = async (_args: Args, ui: UIProvider, _context: RunnerContext) => {
const localArgs = arg({
'--no-warn': Boolean,
'-n': '--no-warn',
...helpArgs,
});
if (localArgs['--help']) {
ui.write(helpMessages['pack']);
return;
}
if (!localArgs['--no-warn']) {
ui.write(
'🚨 WARNING: This command may modify tsconfig.json, package.json, and remove the dist directory. Make sure these files are committed to your repository. If you wish to ignore this warning, use the -n or --no-warn flag.',
);
const answer = await ui.input('Are you sure you want to continue? (y/N)');
if (answer.toLowerCase() !== 'y') {
ui.write('Aborting...');
return;
}
}
ui.write('🔨 Building contracts... Please wait...');
await buildAll(ui);
ui.write('📦 Generating package entry point...');
await generatePackageEntryPoint();
ui.write('🛠️ Updating tsconfig.json...');
await correctTsConfig();
ui.write('🏗️ Building package...');
await fs.rm(path.join(process.cwd(), 'dist'), {
recursive: true,
force: true,
});
execSync(`tsc`, { stdio: 'inherit' });
ui.write('📝 Updating package.json...');
await correctPackageJson();
ui.write('🎉 Package is ready and packed successfully! You can now publish it 🚀');
};
================================================
FILE: src/cli/rename.spec.ts
================================================
import { renameExactIfRequired } from './rename';
describe('Rename', function () {
it('It should rename exact match', function () {
const replaces = {
a: 'b',
};
const result = renameExactIfRequired('a a', replaces);
expect(result.isRenamed).toStrictEqual(true);
expect(result.newValue).toStrictEqual('b b');
});
it('It should not rename not exact match', function () {
const replaces = {
a: 'b',
};
const result = renameExactIfRequired('and', replaces);
expect(result.isRenamed).toStrictEqual(false);
expect(result.newValue).toStrictEqual('and');
});
it('It should not rename another case', function () {
const replaces = {
a: 'b',
};
const result = renameExactIfRequired('A', replaces);
expect(result.isRenamed).toStrictEqual(false);
expect(result.newValue).toStrictEqual('A');
});
it('It should rename contract name in brackets', function () {
const replaces = {
['MyContract']: 'NotMyContract',
};
const result = renameExactIfRequired(`compile('MyContract')`, replaces);
expect(result.isRenamed).toStrictEqual(true);
expect(result.newValue).toStrictEqual(`compile('NotMyContract')`);
});
it('It should not rename contract name if not exact match', function () {
const replaces = {
['MyContract']: 'NotMyContract',
};
const result = renameExactIfRequired(`compile('MyContract2')`, replaces);
expect(result.isRenamed).toStrictEqual(false);
expect(result.newValue).toStrictEqual(`compile('MyContract2')`);
});
it('It should do multiple replaces', function () {
const replaces = {
['myContract']: 'notMyContract',
['MyContract']: 'NotMyContract',
};
const result = renameExactIfRequired(`let myContract: MyContract;`, replaces);
expect(result.isRenamed).toStrictEqual(true);
expect(result.newValue).toStrictEqual(`let notMyContract: NotMyContract;`);
});
});
================================================
FILE: src/cli/rename.ts
================================================
import { promises as fs, existsSync } from 'fs';
import path from 'path';
import arg from 'arg';
import { COMPILABLES_DIR, CONTRACTS_DIR, SCRIPTS_DIR, TACT_ROOT_CONFIG, TESTS_DIR, WRAPPERS_DIR } from '../paths';
import { Args, extractFirstArg, extractSecondArg, Runner, RunnerContext } from './Runner';
import { validateContractName, findContracts, toLowerCase, toSnakeCase, extractFile } from '../utils';
import { UIProvider } from '../ui/UIProvider';
import { helpArgs, helpMessages } from './constants';
import { selectContract } from './build';
import { requestContractName } from './create';
export function renameExactIfRequired(
str: string,
replaces: Record<string, string>,
): {
isRenamed: boolean;
newValue: string;
} {
let renamedString = str;
let isRenamed = false;
for (const [oldValue, newValue] of Object.entries(replaces)) {
const regex = new RegExp(`\\b${oldValue}\\b`, 'g');
if (regex.test(renamedString)) {
isRenamed = true;
renamedString = renamedString.replace(regex, newValue);
}
}
return { isRenamed, newValue: renamedString };
}
// Introduced this class to prevent fails in the middle of renaming, when the part is renamed and part is not
class RenameContext {
constructor(readonly replaces: Record<string, string>) {}
private effects: (() => Promise<void>)[] = [];
async prepareRenameExactOccurrencesInDirectory(directory: string) {
if (!existsSync(directory)) {
return;
}
const dir = await fs.readdir(directory, {
recursive: true,
withFileTypes: true,
});
await Promise.all(
dir
.filter((dir) => dir.isFile())
.map(extractFile)
.map(async (dir) => {
const filePath = path.join(dir.path, dir.name);
await this.prepareRenameContentInFile(filePath);
const pathRenameResult = renameExactIfRequired(dir.name, this.replaces);
if (pathRenameResult.isRenamed) {
this.effects.push(() =>
fs.rename(path.join(dir.path, dir.name), path.join(dir.path, pathRenameResult.newValue)),
);
}
}),
);
}
async prepareRenameContentInFile(filePath: string) {
const content = await fs.readFile(filePath, 'utf8');
const { isRenamed, newValue } = renameExactIfRequired(content, this.replaces);
if (isRenamed) {
this.effects.push(() => fs.writeFile(filePath, newValue, 'utf8'));
}
}
async applyEffects() {
for (const effect of this.effects) {
await effect();
}
}
}
export const rename: Runner = async (_args: Args, ui: UIProvider, _context: RunnerContext) => {
const localArgs = arg(helpArgs);
if (localArgs['--help']) {
ui.write(helpMessages['rename']);
return;
}
const oldName = await selectContract(ui, extractFirstArg(localArgs));
const argName = extractSecondArg(localArgs);
if (argName !== undefined) {
const error = validateContractName(argName);
if (error) {
throw new Error(error);
}
}
const newName = argName ?? (await requestContractName('New contract name (PascalCase)', ui));
const contracts = await findContracts();
if (contracts.includes(newName)) {
ui.write(`Contract with name ${newName} already exists.`);
process.exit(1);
}
const modifiers: ((name: string) => string)[] = [
(name) => name,
toSnakeCase,
toLowerCase,
(name) => `deploy${name}`,
(name) => `${name}_${name}`,
(name) => `increment${name}`,
(name) => `${toLowerCase(name)}ConfigToCell`,
(name) => `${name}Config`,
];
const replaces = Object.fromEntries(modifiers.map((modifier) => [modifier(oldName), modifier(newName)]));
ui.setActionPrompt('Renaming in progress...');
const renameContext = new RenameContext(replaces);
for (const directory of [SCRIPTS_DIR, WRAPPERS_DIR, CONTRACTS_DIR, TESTS_DIR, COMPILABLES_DIR]) {
await renameContext.prepareRenameExactOccurrencesInDirectory(directory);
}
if (existsSync(TACT_ROOT_CONFIG)) {
await renameContext.prepareRenameContentInFile(TACT_ROOT_CONFIG);
}
await renameContext.applyEffects();
ui.clearActionPrompt();
ui.write('Contract successfully renamed!');
};
================================================
FILE: src/cli/run.ts
================================================
import arg from 'arg';
import { Args, extractFirstArg, Runner, RunnerContext } from './Runner';
import { createNetworkProvider, argSpec } from '../network/createNetworkProvider';
import { findScripts, selectFile } from '../utils';
import { UIProvider } from '../ui/UIProvider';
import { helpArgs, helpMessages } from './constants';
export const run: Runner = async (_args: Args, ui: UIProvider, context: RunnerContext) => {
const localArgs = arg({ ...argSpec, ...helpArgs });
if (localArgs['--help']) {
ui.write(helpMessages['run']);
return;
}
const { module: mod } = await selectFile(await findScripts(), {
ui,
hint: extractFirstArg(localArgs),
});
if (typeof mod.run !== 'function') {
throw new Error('Function `run` is missing!');
}
const networkProvider = await createNetworkProvider(ui, localArgs, context.config);
await mod.run(networkProvider, localArgs._.slice(2));
};
================================================
FILE: src/cli/set.ts
================================================
import { readFile, writeFile } from 'fs/promises';
import { exec } from 'node:child_process';
import path from 'path';
import arg from 'arg';
import { UIProvider } from '../ui/UIProvider';
import { Args, Runner } from './Runner';
import { helpArgs, helpMessages } from './constants';
const getVersions = (pkg: string, ui: UIProvider): Promise<string[]> => {
return new Promise((resolve, reject) => {
exec(`npm view ${pkg} versions --json`, (error, stdout, stderr) => {
if (stderr) {
ui.write(stderr);
}
if (stdout) {
if (error === null) {
try {
const resJson = JSON.parse(stdout);
if (Array.isArray(resJson)) {
resolve(resJson);
} else {
reject(new TypeError('Expect json array on stdout, but got:\n' + stdout));
}
} catch (e) {
reject(e);
}
return;
} else {
ui.write(stdout);
}
}
if (error) {
ui.write('Failed to get func-js-bin package versions!');
reject(error);
}
});
});
};
const install = (cmd: string, ui: UIProvider): Promise<void> => {
return new Promise((resolve, reject) => {
exec(cmd, (error, stdout, stderr) => {
if (stderr) {
ui.write(stderr);
}
if (stdout) {
ui.write(stdout);
}
if (error) {
reject(error);
return;
}
resolve();
});
});
};
export const set: Runner = async (args: Args, ui: UIProvider) => {
const localArgs = arg(helpArgs);
if (localArgs['--help']) {
ui.write(helpMessages['set']);
return;
}
if (args._.length < 2) {
throw new Error('Please pass a key');
}
switch (args._[1]) {
case 'func': {
const pkg = '@ton-community/func-js-bin';
const funcVersions = await getVersions(pkg, ui);
let version = args._.length > 2 ? args._[2] : '';
if (!funcVersions.includes(version)) {
version = await ui.choose('Choose FunC version', funcVersions, (s) => s);
}
const packagePath = path.join(process.cwd(), 'package.json');
const packageContents = (await readFile(packagePath)).toString('utf-8');
const parsedPackage = JSON.parse(packageContents);
const packageManager: 'npm' | 'yarn' | 'pnpm' | 'other' = await ui.choose(
'Choose your package manager',
['npm', 'yarn', 'pnpm', 'other'],
(s) => s,
);
if (packageManager === 'other') {
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`,
);
return;
}
const overrideKey = packageManager === 'yarn' ? 'resolutions' : 'overrides';
parsedPackage[overrideKey] = {
...parsedPackage[overrideKey],
[pkg]: version,
};
ui.write('Updating package.json...');
await writeFile(packagePath, JSON.stringify(parsedPackage, null, 4));
const installCmd = packageManager === 'yarn' ? 'yarn' : `${packageManager} i`;
try {
ui.write('Installing dependencies...');
await install(installCmd, ui);
} catch (e) {
ui.write('Failed to install dependencies, rolling back package.json');
await writeFile(packagePath, packageContents);
throw e;
}
break;
}
default: {
throw new Error('Unknown key: ' + args._[1]);
}
}
};
================================================
FILE: src/cli/snapshot.ts
================================================
import { execSync } from 'node:child_process';
import arg from 'arg';
import { Runner } from './Runner';
import { helpArgs, helpMessages } from './constants';
export const argSpec = {
'--label': String,
'-l': '--label',
};
export const snapshot: Runner = async (args, ui) => {
const localArgs = arg({ ...argSpec, ...helpArgs });
if (localArgs['--help']) {
ui.write(helpMessages['snapshot']);
return;
}
let comment = localArgs['--label'];
let testArgs = args._.slice(1); // first argument is `snapshot`, need to get rid of it
if (typeof comment === 'undefined') {
comment = await ui.input('Enter comment:');
} else {
testArgs = testArgs.slice(2);
}
execSync(`npm test -- ${testArgs.join(' ')}`, {
stdio: 'inherit',
env: {
...process.env,
BENCH_NEW: comment,
},
});
};
================================================
FILE: src/cli/test.ts
================================================
import { execSync } from 'child_process';
import arg from 'arg';
import { Runner } from './Runner';
import { helpArgs, helpMessages } from './constants';
export const argSpec = {
'--gas-report': Boolean,
'-g': '--gas-report',
'--coverage': Boolean,
'--ui': Boolean,
};
async function coverage(): Promise<void> {
execSync(
`npm test -- --reporters @ton/blueprint/dist/jest/CoverageReporter --setupFilesAfterEnv @ton/blueprint/dist/jest/coverageSetup`,
{
stdio: 'inherit',
},
);
}
export const test: Runner = async (args, ui) => {
const localArgs = arg({
...helpArgs,
...argSpec,
});
if (localArgs['--help']) {
ui.write(helpMessages['test']);
return;
}
if (localArgs['--coverage']) {
await coverage();
return;
}
let testArgs = args._.slice(1); // first argument is `test`, need to get rid of it
if (localArgs['--gas-report']) {
testArgs = testArgs.slice(1);
}
if (localArgs['--ui']) {
testArgs = [...testArgs.slice(1), '--setupFilesAfterEnv', '@ton/sandbox/dist/jest/uiSetup'];
}
execSync(`npm test -- ${testArgs.join(' ')}`, {
stdio: 'inherit',
env: {
...process.env,
BENCH_DIFF: localArgs['--gas-report'] ? 'true' : 'false',
},
});
};
================================================
FILE: src/cli/verify.ts
================================================
import path from 'path';
import { Address, Cell, Contract, ContractProvider, Dictionary, toNano } from '@ton/core';
import arg from 'arg';
import { doCompile } from '../compile/compile';
import { UIProvider } from '../ui/UIProvider';
import { Args, extractFirstArg, Runner, RunnerContext } from './Runner';
import { argSpec, createNetworkProvider } from '../network/createNetworkProvider';
import { selectContract } from './build';
import { sleep } from '../utils';
import { helpArgs, helpMessages } from './constants';
type FuncCompilerSettings = {
compiler: 'func';
compilerSettings: {
funcVersion: string;
commandLine: string;
};
};
type TactCompilerSettings = {
compiler: 'tact';
compilerSettings: {
tactVersion: string;
};
};
type TolkCompilerSettings = {
compiler: 'tolk';
compilerSettings: {
tolkVersion: string;
};
};
type CompilerSettings = FuncCompilerSettings | TolkCompilerSettings | TactCompilerSettings;
type SourceObject = {
includeInCommand: boolean;
isEntrypoint: boolean;
isStdLib: boolean;
hasIncludeDirectives: boolean;
folder: string;
};
type SourcesObject = {
knownContractHash: string; // base64
knownContractAddress: string;
senderAddress: string;
sources: SourceObject[];
} & CompilerSettings;
type VerifierConfig = {
verifiers: Array<{
id: string;
network: string;
backends: string[];
}>;
// other fields are also present, but intentionally omitted
};
const DEFAULT_VERIFIER_ID = 'verifier.ton.org';
const VERIFIER_CONFIG_URL = 'https://raw.githubusercontent.com/ton-community/contract-verifier-config/main/config.json';
const MAINNET_VERIFIER_REGISTRY = Address.parse('EQD-BJSVUJviud_Qv7Ymfd3qzXdrmV525e3YDzWQoHIAiInL');
const TESTNET_VERIFIER_REGISTRY = Address.parse('EQCsdKYwUaXkgJkz2l0ol6qT_WxeRbE_wBCwnEybmR0u5TO8');
async function getVerifierConfig(): Promise<VerifierConfig> {
try {
const response = await fetch(VERIFIER_CONFIG_URL);
if (!response.ok) {
throw new Error(`Failed to fetch verifier config: ${response.status} ${response.statusText}`);
}
return await response.json();
} catch (error) {
const errorMessage = error instanceof Error ? error.message : String(error);
throw new Error(`Unable to fetch contract verifier config: ${errorMessage}`);
}
}
async function listVerifiers(ui: UIProvider): Promise<void> {
try {
const config = await getVerifierConfig();
ui.write('\nAvailable verifiers:');
const mainnetVerifiers = config.verifiers.filter((v) => v.network === 'mainnet');
const testnetVerifiers = config.verifiers.filter((v) => v.network === 'testnet');
if (mainnetVerifiers.length > 0) {
ui.write('\n Mainnet:');
mainnetVerifiers.forEach((v) => {
ui.write(` - ${v.id}`);
});
}
if (testnetVerifiers.length > 0) {
ui.write('\n Testnet:');
testnetVerifiers.forEach((v) => {
ui.write(` - ${v.id}`);
});
}
if (mainnetVerifiers.length === 0 && testnetVerifiers.length === 0) {
ui.write(' (none)');
}
ui.write('');
} catch (error) {
const errorMessage = error instanceof Error ? error.message : String(error);
throw new Error(`Unable to list verifiers: ${errorMessage}`);
}
}
async function getBackends(verifierId: string, network: 'mainnet' | 'testnet' | 'tetra'): Promise<string[]> {
try {
const config = await getVerifierConfig();
const verifierConfig = config.verifiers.find((v) => v.id === verifierId && v.network === network);
if (!verifierConfig) {
const availableVerifiers = config.verifiers
.filter((v) => v.network === network)
.map((v) => v.id)
.join(', ');
throw new Error(
`Verifier '${verifierId}' not found for ${network} network. Available verifiers: ${availableVerifiers || 'none'}`,
);
}
return verifierConfig.backends;
} catch (error) {
const errorMessage = error instanceof Error ? error.message : String(error);
throw new Error(`Unable to fetch contract verifier backends: ${errorMessage}`);
}
}
function removeRandom<T>(els: T[]): T {
return els.splice(Math.floor(Math.random() * els.length), 1)[0];
}
class VerifierRegistry implements Contract {
constructor(readonly address: Address) {}
async getVerifiers(provider: ContractProvider) {
const res = await provider.get('get_verifiers', []);
const item = res.stack.readCell();
const c = item.beginParse();
const d = c.loadDict(Dictionary.Keys.BigUint(256), {
serialize: () => {
throw undefined;
},
parse: (s) => s,
});
return Array.from(d.values()).map((v) => {
const admin = v.loadAddress();
const quorom = v.loadUint(8);
const pubKeyEndpoints = v.loadDict(Dictionary.Keys.BigUint(256), Dictionary.Values.Uint(32));
return {
admin: admin,
quorum: quorom,
pubKeyEndpoints: new Map<bigint, number>(Array.from(pubKeyEndpoints).map(([k, v]) => [k, v])),
name: v.loadRef().beginParse().loadStringTail(),
url: v.loadRef().beginParse().loadStringTail(),
};
});
}
}
class SourceRegistry implements Contract {
constructor(readonly address: Address) {}
async getVerifierRegistry(provider: ContractProvider) {
const { stack } = await provider.get('get_verifier_registry_address', []);
return stack.readAddress();
}
}
async function lookupCodeHash(hash: Buffer, ui: UIProvider, retryCount: number = 5): Promise<string | undefined> {
type QueryResponse = {
data: {
account_states: Array<{
address: string;
workchain: number;
}>;
};
};
let queryResponse: QueryResponse;
let foundAddr: string | undefined;
let done = false;
const graphqlUrl = 'https://dton.io/graphql/';
const query = `{
account_states(page:0, page_size:1, account_state_state_init_code_hash: "${hash.toString('hex').toUpperCase()}")
{
address
workchain
}
}`;
do {
try {
ui.write('Checking if such a contract is already deployed...');
const resp = await fetch(graphqlUrl, {
method: 'POST',
body: JSON.stringify({ query }),
headers: { 'Content-Type': 'application/json' },
});
if (resp.ok) {
queryResponse = await resp.json();
const states = queryResponse.data.account_states;
if (states.length > 0) {
const state = states[0];
foundAddr = Address.parseRaw(`${state.workchain}:${state.address}`).toString();
} else {
ui.write('No such contract was found!');
}
done = true;
} else {
retryCount--;
}
// Meh
} catch (e: any) {
retryCount--;
if (e.cause) {
if (e.cause.code == 'ETIMEDOUT') {
ui.write('API timed out, waiting...');
await sleep(5000);
}
} else {
ui.write(e);
}
}
} while (!done && retryCount > 0);
return foundAddr;
}
export const verify: Runner = async (_args: Args, ui: UIProvider, context: RunnerContext) => {
const localArgs = arg({ ...argSpec, ...helpArgs });
if (localArgs['--help']) {
ui.write(helpMessages['verify']);
return;
}
if (localArgs['--list-verifiers']) {
await listVerifiers(ui);
return;
}
const preciseVersion = localArgs['--compiler-version'];
const selectedContract = await selectContract(ui, extractFirstArg(localArgs));
const networkProvider = await createNetworkProvider(ui, localArgs, context.config, false);
const sender = networkProvider.sender();
const senderAddress = sender.address;
if (senderAddress === undefined) {
throw new Error('Sender address needs to be known');
}
const network = networkProvider.network();
if (network === 'custom') {
throw new Error('Cannot use custom network');
}
const result = await doCompile(selectedContract, { buildLibrary: false });
const resHash = result.code.hash();
ui.write(`Compiled code hash hex: ${resHash.toString('hex')}`);
ui.write('We can look up the address with such code hash in the blockchain automatically');
const passManually = await ui.prompt('Do you want to specify the address manually?');
let addr: string;
if (passManually) {
addr = (await ui.inputAddress('Deployed contract address')).toString();
} else {
const alreadyDeployed = await lookupCodeHash(resHash, ui);
if (alreadyDeployed) {
ui.write(`Contract is already deployed at: ${alreadyDeployed}\nUsing that address.`);
ui.write(`https://tonscan.org/address/${alreadyDeployed}`);
addr = alreadyDeployed;
} else {
ui.write("Please enter the contract's address manually");
addr = (await ui.inputAddress('Deployed contract address')).toString();
}
}
let src: SourcesObject;
const fd = new FormData();
if (result.lang === 'func') {
for (const f of result.snapshot) {
fd.append(f.filename, new Blob([f.content]), path.basename(f.filename));
}
src = {
compiler: 'func',
compilerSettings: {
funcVersion: preciseVersion ?? result.version,
commandLine: '-SPA ' + result.targets.join(' '),
},
knownContractAddress: addr,
knownContractHash: result.code.hash().toString('base64'),
sources: result.snapshot.map((s) => ({
includeInCommand: result.targets.includes(s.filename),
isEntrypoint: result.targets.includes(s.filename),
isStdLib: false,
hasIncludeDirectives: true,
folder: path.dirname(s.filename),
})),
senderAddress: senderAddress.toString(),
};
} else if (result.lang === 'tact') {
let pkg: { name: string; content: Buffer } | undefined = undefined;
for (const [k, v] of result.fs) {
if (k.endsWith('.pkg')) {
pkg = {
name: k,
content: v,
};
break;
}
}
if (pkg === undefined) {
throw new Error('Could not find .pkg in compilation results');
}
fd.append(path.basename(pkg.name), new Blob([pkg.content]), path.basename(pkg.name));
src = {
compiler: 'tact',
compilerSettings: {
tactVersion: '',
},
knownContractAddress: addr,
knownContractHash: result.code.hash().toString('base64'),
sources: [
{
includeInCommand: true,
isEntrypoint: false,
isStdLib: false,
hasIncludeDirectives: false,
folder: '',
},
],
senderAddress: senderAddress.toString(),
};
} else if (result.lang === 'tolk') {
for (const f of result.snapshot) {
fd.append(f.filename, new Blob([f.content]), path.basename(f.filename));
}
src = {
compiler: 'tolk',
compilerSettings: {
tolkVersion: preciseVersion ?? result.version,
},
knownContractAddress: addr,
knownContractHash: result.code.hash().toString('base64'),
sources: result.snapshot.map((s) => ({
includeInCommand: true,
isStdLib: false,
hasIncludeDirectives: true,
isEntrypoint: s === result.snapshot[0],
folder: path.dirname(s.filename),
})),
senderAddress: senderAddress.toString(),
};
} else {
// future proofing
throw new Error('Unsupported language ' + (result as any).lang);
}
fd.append(
'json',
new Blob([JSON.stringify(src)], {
type: 'application/json',
}),
'blob',
);
const verifierId = localArgs['--verifier'] ?? DEFAULT_VERIFIER_ID;
const sourceRegistryAddress = network === 'mainnet' ? MAINNET_VERIFIER_REGISTRY : TESTNET_VERIFIER_REGISTRY;
const sourceRegistry = networkProvider.open(new SourceRegistry(sourceRegistryAddress));
const verifierRegistry = networkProvider.open(new VerifierRegistry(await sourceRegistry.getVerifierRegistry()));
const verifiers = await verifierRegistry.getVerifiers();
const verifier = verifiers.find((v) => v.name === verifierId);
if (!verifier) {
const availableVerifiers = verifiers.map((v) => v.name).join(', ');
throw new Error(
`Verifier '${verifierId}' is not registered in the verifier registry. Available verifiers: ${availableVerifiers || 'none'}`,
);
}
const backends = await getBackends(verifierId, network);
const backendUrl = removeRandom(backends);
const sourceResponse = await fetch(`${backendUrl}/source`, {
method: 'POST',
body: fd,
});
if (sourceResponse.status !== 200) {
throw new Error('Could not compile on backend:\n' + (await sourceResponse.json()));
}
const sourceResult = await sourceResponse.json();
if (sourceResult.compileResult.result !== 'similar') {
throw new Error(sourceResult.compileResult.error);
}
let msgCell = sourceResult.msgCell;
let acquiredSigs = 1;
while (acquiredSigs < verifier.quorum) {
const backendUrl = removeRandom(backends);
ui.write(`Using backend: ${backendUrl}`);
const signResponse = await fetch(`${backendUrl}/sign`, {
method: 'POST',
body: JSON.stringify({
messageCell: msgCell,
}),
headers: { 'Content-Type': 'application/json' },
});
if (signResponse.status !== 200) {
throw new Error('Could not sign on backend:\n' + (await signResponse.text()));
}
const signResult = await signResponse.json();
msgCell = signResult.msgCell;
acquiredSigs++;
}
const c = Cell.fromBoc(Buffer.from(msgCell.data))[0];
await networkProvider.sender().send({
to: verifierRegistry.address,
value: toNano('0.5'),
body: c,
});
const testnetFlag = network === 'testnet' ? '?testnet=true' : '';
ui.write(`Contract successfully verified at https://verifier.ton.org/${addr}${testnetFlag}`);
};
================================================
FILE: src/compile/CompilerConfig.ts
================================================
import { Cell } from '@ton/core';
import { TolkCompilerConfig } from './tolk/config';
import { FuncCompilerConfig } from './func/config';
import { TactCompilerConfig, TactLegacyCompilerConfig } from './tact/config';
export type HookParams = {
userData?: any;
};
export type CommonCompilerConfig = {
/**
* A hook that runs **before** the contract is compiled.
*
* @param {HookParams} params - Hook parameters including user-defined data.
*
* @example
* const config: CompilerConfig = {
* preCompileHook: async (params) => {
* console.log("Preparing to compile, user params:", params);
* }
* };
*/
preCompileHook?: (params: HookParams) => Promise<void>;
/**
* A hook that runs **after** the contract is compiled.
*
* @param {Cell} code - The compiled contract code as a TON `Cell`.
* @param {HookParams} params - Hook parameters including user-defined data.
*
* @example
* const config: CompilerConfig = {
* postCompileHook: async (code) => {
* console.log("Compiled BOC size:", code.toBoc().length);
* }
* };
*/
postCompileHook?: (code: Cell, params: HookParams) => Promise<void>;
/**
* Allows to build artifact into library cell instead of regular
* code cell
* https://docs.ton.org/v3/documentation/data-formats/tlb/library-cells#introduction
*/
buildLibrary?: boolean;
};
export type CompilableConfig = (TactLegacyCompilerConfig | FuncCompilerConfig | TolkCompilerConfig) &
CommonCompilerConfig;
export type CompilerConfig = TactCompilerConfig | CompilableConfig;
export function isCompilableConfig(config: CompilerConfig): config is CompilableConfig {
return 'lang' in config;
}
================================================
FILE: src/compile/SourceSnapshot.ts
================================================
export type SourceSnapshot = {
filename: string;
content: string;
};
================================================
FILE: src/compile/SupportedLang.ts
================================================
export type SupportedLang = 'tact' | 'tolk' | 'func';
================================================
FILE: src/compile/compile.ts
================================================
import { readFileSync } from 'fs';
import path from 'path';
import { beginCell, Cell } from '@ton/core';
import { COMPILABLES_DIR, WRAPPERS_DIR } from '../paths';
import { CompilableConfig, CompilerConfig, isCompilableConfig } from './CompilerConfig';
import { getConfig } from '../config/utils';
import { doCompileFunc, FuncCompileResult, getFuncVersion, DoCompileFuncConfig } from './func/compile.func';
import { doCompileTact, TactCompileResult, getTactVersion, getTactConfigForContract } from './tact/compile.tact';
import { doCompileTolk, TolkCompileResult, getTolkVersion } from './tolk/compile.tolk';
import { findCompiles } from '../utils';
import { SupportedLang } from './SupportedLang';
export async function getCompilablesDirectory(): Promise<string> {
const config = await getConfig();
if (config?.separateCompilables) {
return COMPILABLES_DIR;
}
return WRAPPERS_DIR;
}
export function extractCompilableConfig(path: string): CompilableConfig {
// eslint-disable-next-line @typescript-eslint/no-require-imports
const mod = require(path);
if (typeof mod.compile !== 'object') {
throw new Error(`Object 'compile' is missing`);
}
mod.compile.lang ??= 'func';
return mod.compile;
}
export const COMPILE_END = '.compile.ts';
/**
* Retrieves the compiler configuration for a specific contract.
*
* This function checks if a Tact configuration exists for the given contract
* `tact.config.json`. If found, it returns that configuration. Otherwise, it falls back
* to loading and extracting the `.compile.ts` configuration file from the appropriate
* compilables directory (`compilables/` or `wrappers/`).
*
* @param {string} name - The name of the contract
*
* @throws Error Throws if configuration is invalid or not found.
*
* @example
* const config = await getCompilerConfigForContract('MyContract');
* console.log('Compiler config:', config);
*/
export async function getCompilerConfigForContract(name: string): Promise<CompilerConfig> {
const tactConfig = getTactConfigForContract(name);
if (tactConfig) {
return tactConfig;
}
const compilablesDirectory = await getCompilablesDirectory();
const compilables = await findCompiles(compilablesDirectory);
const compilable = compilables.find((c) => c.name === name);
// Ensure compatibility with legacy usage like compile('subdirectory/ContractName')
const pathToExtract = compilable?.path ?? path.join(compilablesDirectory, name + COMPILE_END);
return extractCompilableConfig(pathToExtract);
}
export type CompileResult = TactCompileResult | FuncCompileResult | TolkCompileResult;
async function doCompileInner(name: string, config: CompilerConfig): Promise<CompileResult> {
if (isCompilableConfig(config)) {
if (config.lang === 'tact') {
return await doCompileTact(config, name);
}
if (config.lang === 'tolk') {
return await doCompileTolk({
entrypointFileName: config.entrypoint,
fsReadCallback: (path) => readFileSync(path).toString(),
optimizationLevel: config.optimizationLevel,
withStackComments: config.withStackComments,
withSrcLineComments: config.withSrcLineComments,
experimentalOptions: config.experimentalOptions,
});
}
return await doCompileFunc({
targets: config.targets,
sources: config.sources ?? ((path: string) => readFileSync(path).toString()),
optLevel: config.optLevel,
debugInfo: config.debugInfo,
} as DoCompileFuncConfig);
}
return await doCompileTact(config, name);
}
function getCompilerName(config: CompilerConfig): SupportedLang {
if (isCompilableConfig(config)) {
return config.lang ?? 'func';
}
return 'tact';
}
async function getCompilerVersion(config: CompilerConfig): Promise<string> {
if (isCompilableConfig(config)) {
if (config.lang === 'tact') {
return getTactVersion();
}
if (config.lang === 'tolk') {
return getTolkVersion();
}
return getFuncVersion();
}
return getTactVersion();
}
export async function getCompilerOptions(config: CompilerConfig): Promise<{
lang: SupportedLang;
version: string;
}> {
return {
lang: getCompilerName(config),
version: await getCompilerVersion(config),
};
}
export function libraryCellFromCode(code: Cell) {
// Pack resulting code hash into library cell
const libPrep = beginCell().storeUint(2, 8).storeBuffer(code.hash()).endCell();
return new Cell({ exotic: true, bits: libPrep.bits, refs: libPrep.refs });
}
export async function doCompile(name: string, opts?: CompileOpts): Promise<CompileResult> {
const config = await getCompilerConfigForContract(name);
if (opts?.debugInfo && isCompilableConfig(config) && (config.lang === undefined || config.lang === 'func')) {
config.debugInfo = true;
}
if ('preCompileHook' in config && config.preCompileHook !== undefined) {
await config.preCompileHook({
userData: opts?.hookUserData,
});
}
const res = await doCompileInner(name, config);
if ('postCompileHook' in config && config.postCompileHook !== undefined) {
await config.postCompileHook(res.code, {
userData: opts?.hookUserData,
});
}
const buildLibrary = opts?.buildLibrary ?? ('buildLibrary' in config && config.buildLibrary === true);
if (buildLibrary) {
res.code = libraryCellFromCode(res.code);
}
return res;
}
/**
* Optional compilation settings, including user data passed to hooks
*/
export type CompileOpts = {
/**
* Any user-defined data that will be passed to both `preCompileHook` and `postCompileHook`.
*/
hookUserData?: any;
debugInfo?: boolean;
buildLibrary?: boolean;
};
/**
* Compiles a contract using the specified configuration for `tact`, `func`, or `tolk` languages.
*
* This function resolves the appropriate compiler configuration for a given contract name,
* runs any defined pre-compile and post-compile hooks, and returns the resulting compiled code
* as a [Cell]{@link Cell}.
*
* @param {string} name - The name of the contract to compile. This should correspond to a
* file named `<name>.compile.ts` in the `compilables` or `wrappers` directory.
* @param {CompileOpts} [opts] - Optional compilation options, including user data passed to hooks.
*
* @returns {Promise<Cell>} A promise that resolves to the compiled contract code as a `Cell`.
*
* @example
* import { compile } from '@ton/blueprint';
*
* async function main() {
* const codeCell = await compile('Contract');
* console.log('Compiled code BOC:', codeCell.toBoc().toString('base64'));
* }
*
* main();
*/
export async function compile(name: string, opts?: CompileOpts): Promise<Cell> {
const result = await doCompile(name, opts);
return result.code;
}
export type { TactCompileResult, TolkCompileResult, FuncCompileResult };
================================================
FILE: src/compile/func/compile.func.ts
================================================
import { Cell } from '@ton/core';
import { compileFunc, CompilerConfig, compilerVersion, DebugInfo } from '@ton-community/func-js';
import { SourceSnapshot } from '../SourceSnapshot';
export type FuncCompileResult = {
lang: 'func';
fiftCode: string;
code: Cell;
targets: string[];
snapshot: SourceSnapshot[];
version: string;
debugInfo?: DebugInfo;
marks?: Cell;
};
export async function getFuncVersion(): Promise<string> {
return (await compilerVersion()).funcVersion;
}
export async function doCompileFunc(config: CompilerConfig): Promise<FuncCompileResult> {
const cr = await compileFunc(config);
if (cr.status === 'error') {
throw new Error(cr.message);
}
let targets: string[] = [];
if (config.targets) {
targets = config.targets;
} else if (Array.isArray(config.sources)) {
targets = config.sources.map((s) => s.filename);
}
const result: FuncCompileResult = {
lang: 'func',
fiftCode: cr.fiftCode,
code: Cell.fromBase64(cr.codeBoc),
targets,
snapshot: cr.snapshot,
version: await getFuncVersion(),
debugInfo: cr.debugInfo,
marks: cr.debugMarksBoc === undefined ? undefined : Cell.fromBase64(cr.debugMarksBoc),
};
if (config.debugInfo) {
const crRegular = await compileFunc({
...config,
debugInfo: false,
});
if (crRegular.status === 'error') {
throw new Error(
'Debug info compilation succeeded, but regular compilation failed, please report this bug: ' +
crRegular.message,
);
}
if (!Cell.fromBase64(crRegular.codeBoc).hash().equals(result.code.hash())) {
console.error(
'Debug info compilation yielded different code than regular compilation, please report this bug.',
);
}
let sandbox: any;
try {
// eslint-disable-next-line @typescript-eslint/no-require-imports
sandbox = require('@ton/sandbox');
} catch (_) {
console.error('Could not register debug info in sandbox because it is not installed.');
}
if (sandbox?.registerCompiledContract) {
sandbox.registerCompiledContract(result.code, result.debugInfo, result.marks);
}
}
return result;
}
export { CompilerConfig as DoCompileFuncConfig } from '@ton-community/func-js';
================================================
FILE: src/compile/func/config.ts
================================================
import { SourceResolver, SourcesArray, SourcesMap } from '@ton-community/func-js';
export type FuncCompilerConfig = {
lang?: 'func';
optLevel?: number;
debugInfo?: boolean;
} & (
| {
targets: string[];
sources?: SourceResolver | SourcesMap;
}
| {
targets?: string[];
sources: SourcesArray;
}
);
================================================
FILE: src/compile/tact/OverwritableVirtualFileSystem.ts
================================================
import { resolve, normalize, sep } from 'path';
import { existsSync, readFileSync } from 'fs';
import { VirtualFileSystem } from '@tact-lang/compiler';
export class OverwritableVirtualFileSystem implements VirtualFileSystem {
root: string;
overwrites: Map<string, Buffer> = new Map();
constructor(root: string) {
this.root = normalize(root);
if (!this.root.endsWith(sep)) {
this.root += sep;
}
}
resolve(...path: string[]): string {
return normalize(resolve(this.root, ...path));
}
exists(path: string): boolean {
return existsSync(path);
}
readFile(path: string): Buffer {
return this.overwrites.get(path) ?? readFileSync(path);
}
writeFile(path: string, content: string | Buffer): void {
this.overwrites.set(path, typeof content === 'string' ? Buffer.from(content, 'utf-8') : content);
}
}
================================================
FILE: src/compile/tact/compile.tact.ts
================================================
import path from 'path';
import { build, createVirtualFileSystem, Options, Project, stdLibFiles } from '@tact-lang/compiler';
import { Cell } from '@ton/core';
import { BUILD_DIR } from '../../paths';
import { OverwritableVirtualFileSystem } from './OverwritableVirtualFileSystem';
import { TactCompilerConfig, TactLegacyCompilerConfig } from './config';
import { getRootTactConfig } from '../../config/tact.config';
export type TactCompileResult = {
lang: 'tact';
fs: Map<string, Buffer>;
code: Cell;
options?: Options;
version: string;
};
function findTactBoc(fs: Map<string, Buffer>): Cell {
let buf: Buffer | undefined = undefined;
for (const [k, v] of fs) {
if (k.endsWith('.code.boc')) {
buf = v;
break;
}
}
if (buf === undefined) {
throw new Error('Could not find boc in tact compilation result');
}
return Cell.fromBoc(buf)[0];
}
export function getTactConfigForContract(name: string): TactCompilerConfig | undefined {
const config = getRootTactConfig();
const projects = config.projects.filter((project) => project.name === name);
if (!projects.length) {
return;
}
return {
...config,
projects,
};
}
function getRootTactConfigOptionsForContract(name: string): Options | undefined {
const filteredTactConfig = getTactConfigForContract(name);
if (!filteredTactConfig) {
return;
}
const [project] = filteredTactConfig.projects;
return project?.options;
}
export async function getTactVersion() {
const packageJsonPath = require.resolve('@tact-lang/compiler/package.json');
const { version } = await import(packageJsonPath);
return version;
}
function isLegacyTactConfig(config: TactLegacyCompilerConfig | TactCompilerConfig): config is TactLegacyCompilerConfig {
return 'lang' in config;
}
export function extractContractConfig(config: TactCompilerConfig, name: string): Project {
const project = config.projects.find((p) => p.name === name);
if (!project) {
throw new Error(`Config for project ${name} not found`);
}
return project;
}
function getTactBuildProject(config: TactLegacyCompilerConfig | TactCompilerConfig, name: string): Project {
if (isLegacyTactConfig(config)) {
const rootConfigOptions = getRootTactConfigOptionsForContract(name);
return {
name: 'tact',
path: config.target,
output: path.join(BUILD_DIR, name),
options: { ...rootConfigOptions, ...config.options },
};
}
return extractContractConfig(config, name);
}
export async function doCompileTact(
config: TactLegacyCompilerConfig | TactCompilerConfig,
name: string,
): Promise<TactCompileResult> {
const fs = new OverwritableVirtualFileSystem(process.cwd());
const buildConfig = {
config: getTactBuildProject(config, name),
stdlib: createVirtualFileSystem('@stdlib', stdLibFiles),
project: fs,
};
const res = await build(buildConfig);
if (!res.ok) {
throw new Error('Could not compile tact');
}
const code = findTactBoc(fs.overwrites);
return {
lang: 'tact',
fs: fs.overwrites,
code,
options: buildConfig.config.options,
version: await getTactVersion(),
};
}
================================================
FILE: src/compile/tact/config.ts
================================================
import { Config, Options } from '@tact-lang/compiler';
export type TactLegacyCompilerConfig = {
lang: 'tact';
target: string;
options?: Options;
};
export type TactCompilerConfig = Config;
================================================
FILE: src/compile/tolk/compile.tolk.ts
================================================
import { Cell } from '@ton/core';
import { getTolkCompilerVersion, runTolkCompiler, TolkCompilerConfig } from '@ton/tolk-js';
import { SourceSnapshot } from '../SourceSnapshot';
export type TolkCompileResult = {
lang: 'tolk';
stderr: string;
fiftCode: string;
code: Cell;
snapshot: SourceSnapshot[];
version: string;
};
export async function doCompileTolk(config: TolkCompilerConfig): Promise<TolkCompileResult> {
const res = await runTolkCompiler(config);
if (res.status === 'error') {
throw new Error(res.message);
}
return {
lang: 'tolk',
stderr: res.stderr,
fiftCode: res.fiftCode,
code: Cell.fromBase64(res.codeBoc64),
snapshot: res.sourcesSnapshot.map((e) => ({
filename: e.filename,
content: e.contents,
})),
version: await getTolkCompilerVersion(),
};
}
export { getTolkCompilerVersion as getTolkVersion } from '@ton/tolk-js';
================================================
FILE: src/compile/tolk/config.ts
================================================
export type TolkCompilerConfig = {
lang: 'tolk';
entrypoint: string;
optimizationLevel?: number;
withStackComments?: boolean;
withSrcLineComments?: boolean;
experimentalOptions?: string;
};
================================================
FILE: src/config/Confi
gitextract_fv1vau06/ ├── .github/ │ ├── ISSUE_TEMPLATE/ │ │ ├── bug_report.yml │ │ ├── docs_update.yml │ │ ├── feature_request.yml │ │ └── generic.yml │ ├── pull_request_template.md │ └── workflows/ │ ├── publish.yml │ ├── qa.yaml │ ├── reward-merged-prs.yml │ └── reward.yml ├── .gitignore ├── .yarnrc.yml ├── CHANGELOG.md ├── LICENSE ├── README.md ├── contributing.md ├── eslint.config.js ├── example/ │ ├── .gitignore │ ├── .prettierignore │ ├── .prettierrc │ ├── .snapshot/ │ │ └── 1748254056557.json │ ├── README.md │ ├── blueprint.config.ts │ ├── compilables/ │ │ └── Counter.compile.ts │ ├── contract.abi.json │ ├── contracts/ │ │ ├── counter.fc │ │ └── imports/ │ │ └── stdlib.fc │ ├── gas-report.json │ ├── jest.config.ts │ ├── package.json │ ├── scripts/ │ │ ├── deployCounter.ts │ │ └── incrementCounter.ts │ ├── tests/ │ │ └── Counter.spec.ts │ ├── tsconfig.json │ └── wrappers/ │ └── Counter.ts ├── jest.config.js ├── package.json ├── src/ │ ├── build.ts │ ├── cli/ │ │ ├── Runner.ts │ │ ├── build.ts │ │ ├── cli.ts │ │ ├── constants.ts │ │ ├── convert.ts │ │ ├── create.ts │ │ ├── help.ts │ │ ├── pack.ts │ │ ├── rename.spec.ts │ │ ├── rename.ts │ │ ├── run.ts │ │ ├── set.ts │ │ ├── snapshot.ts │ │ ├── test.ts │ │ └── verify.ts │ ├── compile/ │ │ ├── CompilerConfig.ts │ │ ├── SourceSnapshot.ts │ │ ├── SupportedLang.ts │ │ ├── compile.ts │ │ ├── func/ │ │ │ ├── compile.func.ts │ │ │ └── config.ts │ │ ├── tact/ │ │ │ ├── OverwritableVirtualFileSystem.ts │ │ │ ├── compile.tact.ts │ │ │ └── config.ts │ │ └── tolk/ │ │ ├── compile.tolk.ts │ │ └── config.ts │ ├── config/ │ │ ├── Config.ts │ │ ├── CustomNetwork.ts │ │ ├── Plugin.ts │ │ ├── tact.config.ts │ │ └── utils.ts │ ├── index.ts │ ├── jest/ │ │ ├── CoverageReporter.ts │ │ └── coverageSetup.ts │ ├── network/ │ │ ├── Explorer.ts │ │ ├── Network.ts │ │ ├── NetworkProvider.ts │ │ ├── NetworkVersion.ts │ │ ├── constants.ts │ │ ├── createNetworkProvider.ts │ │ ├── send/ │ │ │ ├── DeeplinkProvider.ts │ │ │ ├── MnemonicProvider.ts │ │ │ ├── SendProvider.ts │ │ │ ├── TonConnectProvider.ts │ │ │ └── wallets.ts │ │ ├── storage/ │ │ │ ├── FSStorage.ts │ │ │ └── Storage.ts │ │ └── utils.ts │ ├── paths.ts │ ├── template.spec.ts │ ├── template.ts │ ├── templates/ │ │ ├── func/ │ │ │ ├── common/ │ │ │ │ ├── compilables/ │ │ │ │ │ └── compile.ts.template │ │ │ │ └── contracts/ │ │ │ │ └── imports/ │ │ │ │ └── stdlib.fc.template │ │ │ ├── counter/ │ │ │ │ ├── contracts/ │ │ │ │ │ └── contract.fc.template │ │ │ │ ├── scripts/ │ │ │ │ │ ├── deploy.ts.template │ │ │ │ │ └── increment.ts.template │ │ │ │ ├── tests/ │ │ │ │ │ └── spec.ts.template │ │ │ │ └── wrappers/ │ │ │ │ └── wrapper.ts.template │ │ │ ├── empty/ │ │ │ │ ├── contracts/ │ │ │ │ │ └── contract.fc.template │ │ │ │ ├── scripts/ │ │ │ │ │ └── deploy.ts.template │ │ │ │ ├── tests/ │ │ │ │ │ └── spec.ts.template │ │ │ │ └── wrappers/ │ │ │ │ └── wrapper.ts.template │ │ │ ├── legacy/ │ │ │ │ └── wrappers/ │ │ │ │ └── compile.ts.template │ │ │ └── not-separated-common/ │ │ │ ├── contracts/ │ │ │ │ └── imports/ │ │ │ │ └── stdlib.fc.template │ │ │ └── wrappers/ │ │ │ └── compile.ts.template │ │ ├── tact/ │ │ │ ├── counter/ │ │ │ │ ├── contracts/ │ │ │ │ │ └── contract.tact.template │ │ │ │ ├── scripts/ │ │ │ │ │ ├── deploy.ts.template │ │ │ │ │ └── increment.ts.template │ │ │ │ └── tests/ │ │ │ │ └── spec.ts.template │ │ │ └── empty/ │ │ │ ├── contracts/ │ │ │ │ └── contract.tact.template │ │ │ ├── scripts/ │ │ │ │ └── deploy.ts.template │ │ │ └── tests/ │ │ │ └── spec.ts.template │ │ └── tolk/ │ │ ├── common/ │ │ │ └── compilables/ │ │ │ └── compile.ts.template │ │ ├── counter/ │ │ │ ├── contracts/ │ │ │ │ └── contract.tolk.template │ │ │ ├── scripts/ │ │ │ │ ├── deploy.ts.template │ │ │ │ ├── increment.ts.template │ │ │ │ └── reset.ts.template │ │ │ ├── tests/ │ │ │ │ └── spec.ts.template │ │ │ └── wrappers/ │ │ │ └── wrapper.ts.template │ │ ├── empty/ │ │ │ ├── contracts/ │ │ │ │ └── contract.tolk.template │ │ │ ├── scripts/ │ │ │ │ └── deploy.ts.template │ │ │ ├── tests/ │ │ │ │ └── spec.ts.template │ │ │ └── wrappers/ │ │ │ └── wrapper.ts.template │ │ └── not-separated-common/ │ │ └── wrappers/ │ │ └── compile.ts.template │ ├── types/ │ │ └── file.ts │ ├── ui/ │ │ ├── InquirerUIProvider.ts │ │ └── UIProvider.ts │ └── utils/ │ ├── file.utils.ts │ ├── index.ts │ ├── object.utils.ts │ ├── selection.utils.ts │ ├── string.utils.ts │ ├── timer.utils.ts │ └── ton.utils.ts ├── tonconnect/ │ └── manifest.json └── tsconfig.json
Condensed preview — 133 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (412K chars).
[
{
"path": ".github/ISSUE_TEMPLATE/bug_report.yml",
"chars": 1036,
"preview": "name: Bug Report\ndescription: Report a reproducible bug or unexpected behavior\nlabels: [\"bug\"]\nbody:\n - type: textarea..."
},
{
"path": ".github/ISSUE_TEMPLATE/docs_update.yml",
"chars": 496,
"preview": "name: Docs Update\ndescription: Suggest an update or improvement to the documentation\nlabels: [\"documentation\"]\nbody:\n -..."
},
{
"path": ".github/ISSUE_TEMPLATE/feature_request.yml",
"chars": 869,
"preview": "name: Feature Request\ndescription: Suggest an idea or enhancement\nlabels: [\"enhancement\"]\nbody:\n - type: textarea\n i..."
},
{
"path": ".github/ISSUE_TEMPLATE/generic.yml",
"chars": 265,
"preview": "name: Generic Issue\ndescription: Open a general issue or question\nbody:\n - type: textarea\n id: description\n attri..."
},
{
"path": ".github/pull_request_template.md",
"chars": 403,
"preview": "## Issue\n\nCloses #<issue-number>\n\n## Checklist\n\nPlease ensure the following items are completed before requesting review..."
},
{
"path": ".github/workflows/publish.yml",
"chars": 1642,
"preview": "on:\n push:\n branches:\n - main\n - develop\n\nname: Publish to NPM\njobs:\n publish:\n name: Publish\n runs..."
},
{
"path": ".github/workflows/qa.yaml",
"chars": 1070,
"preview": "name: QA\n\non:\n push:\n paths-ignore:\n - '.github/workflows/publish.yml'\n - '.github/workflows/publish-dev.y..."
},
{
"path": ".github/workflows/reward-merged-prs.yml",
"chars": 684,
"preview": "name: Reward merged PRs\n\non:\n workflow_dispatch:\n inputs:\n per_page:\n description: 'Number of merged PRs..."
},
{
"path": ".github/workflows/reward.yml",
"chars": 456,
"preview": "name: Reward\n\non:\n push:\n branches:\n - main\n - develop\n\njobs:\n reward:\n name: Reward\n runs-on: ubun..."
},
{
"path": ".gitignore",
"chars": 76,
"preview": "node_modules\ndist\n.idea\ncoverage/\n\n# yarn\nyarn-error.log\n.yarn/\npackage.tgz\n"
},
{
"path": ".yarnrc.yml",
"chars": 46,
"preview": "nodeLinker: node-modules\nenableScripts: false\n"
},
{
"path": "CHANGELOG.md",
"chars": 14945,
"preview": "# Changelog\n\nAll notable changes to this project will be documented in this file.\n\nThe format is based on [Keep a Change..."
},
{
"path": "LICENSE",
"chars": 1065,
"preview": "MIT License\n\nCopyright (c) 2025 Ton Tech\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\no..."
},
{
"path": "README.md",
"chars": 15235,
"preview": "<img src=\"https://raw.githubusercontent.com/ton-org/blueprint/main/logo.svg\" width=400 >\n\n# Blueprint\n\nA development env..."
},
{
"path": "contributing.md",
"chars": 2244,
"preview": "# Contributing\n\nWe highly appreciate your contributions to the project ❤️\n\n## Main flow\n\n### Step 1 — get code\n\n```shell..."
},
{
"path": "eslint.config.js",
"chars": 709,
"preview": "const base = require('@ton/toolchain');\nconst tsEslint = require('@ton/toolchain').tsEslint;\nconst globals = require('gl..."
},
{
"path": "example/.gitignore",
"chars": 30,
"preview": "node_modules\n.yarn\ntemp\nbuild\n"
},
{
"path": "example/.prettierignore",
"chars": 6,
"preview": "build\n"
},
{
"path": "example/.prettierrc",
"chars": 116,
"preview": "{\n \"printWidth\": 120,\n \"tabWidth\": 4,\n \"singleQuote\": true,\n \"bracketSpacing\": true,\n \"semi\": true\n}\n"
},
{
"path": "example/.snapshot/1748254056557.json",
"chars": 9355,
"preview": "{\n \"label\": \"v1\",\n \"createdAt\": \"2025-05-26T10:07:36.557Z\",\n \"items\": [\n {\n \"testName\": \"Counter should deplo..."
},
{
"path": "example/README.md",
"chars": 3433,
"preview": "# TON project\n\nStarter template for a new TON project - FunC contracts, unit tests, compilation and deployment scripts...."
},
{
"path": "example/blueprint.config.ts",
"chars": 53,
"preview": "export const config = { separateCompilables: true };\n"
},
{
"path": "example/compilables/Counter.compile.ts",
"chars": 151,
"preview": "import { CompilerConfig } from '@ton/blueprint';\n\nexport const compile: CompilerConfig = {\n lang: 'func',\n targets..."
},
{
"path": "example/contract.abi.json",
"chars": 384,
"preview": "{\n \"0x61689f0431f0bead3490202a24110f59f58ac3c23f5e4ad4168923eee237287a\": \"Counter\",\n \"Counter\": {\n \"receivers\": [..."
},
{
"path": "example/contracts/counter.fc",
"chars": 2236,
"preview": "#include \"imports/stdlib.fc\";\n\nconst op::increase = \"op::increase\"c; ;; create an opcode from string using the \"c\" prefi..."
},
{
"path": "example/contracts/imports/stdlib.fc",
"chars": 35388,
"preview": ";; Standard library for funC\n;;\n\n{-\n # Tuple manipulation primitives\n The names and the types are mostly self-explaini..."
},
{
"path": "example/gas-report.json",
"chars": 2712,
"preview": "[\n {\n \"label\": \"v1\",\n \"createdAt\": \"2025-05-26T10:07:36.557Z\",\n \"result\": {\n \"Counter\": {\n \"sendDe..."
},
{
"path": "example/jest.config.ts",
"chars": 319,
"preview": "import type { Config } from 'jest';\n\nconst config: Config = {\n preset: 'ts-jest',\n testEnvironment: '@ton/sandbox/..."
},
{
"path": "example/package.json",
"chars": 815,
"preview": "{\n \"name\": \"example\",\n \"version\": \"0.0.1\",\n \"license\": \"MIT\",\n \"scripts\": {\n \"gas-report\": \"blueprint..."
},
{
"path": "example/scripts/deployCounter.ts",
"chars": 600,
"preview": "import { toNano } from '@ton/core';\nimport { Counter } from '../wrappers/Counter';\nimport { compile, NetworkProvider } f..."
},
{
"path": "example/scripts/incrementCounter.ts",
"chars": 1152,
"preview": "import { Address, toNano } from '@ton/core';\nimport { Counter } from '../wrappers/Counter';\nimport { NetworkProvider, sl..."
},
{
"path": "example/tests/Counter.spec.ts",
"chars": 2312,
"preview": "import { Blockchain, SandboxContract } from '@ton/sandbox';\nimport { Cell, toNano } from '@ton/core';\nimport { Counter }..."
},
{
"path": "example/tsconfig.json",
"chars": 284,
"preview": "{\n \"compilerOptions\": {\n \"target\": \"ES2020\",\n \"outDir\": \"dist\",\n \"module\": \"commonjs\",\n \"..."
},
{
"path": "example/wrappers/Counter.ts",
"chars": 1984,
"preview": "import { Address, beginCell, Cell, Contract, contractAddress, ContractProvider, Sender, SendMode } from '@ton/core';\n\nex..."
},
{
"path": "jest.config.js",
"chars": 123,
"preview": "module.exports = {\n preset: 'ts-jest',\n testEnvironment: 'node',\n testPathIgnorePatterns: ['src/cli/test.ts'],..."
},
{
"path": "package.json",
"chars": 2174,
"preview": "{\n \"name\": \"@ton/blueprint\",\n \"version\": \"0.44.2\",\n \"description\": \"Framework for development of TON smart cont..."
},
{
"path": "src/build.ts",
"chars": 4179,
"preview": "import path from 'path';\nimport fs from 'fs/promises';\n\nimport {\n doCompile,\n extractCompilableConfig,\n getComp..."
},
{
"path": "src/cli/Runner.ts",
"chars": 685,
"preview": "import arg from 'arg';\n\nimport { UIProvider } from '../ui/UIProvider';\nimport { Config } from '../config/Config';\n\nexpor..."
},
{
"path": "src/cli/build.ts",
"chars": 1871,
"preview": "import arg from 'arg';\n\nimport { findContracts, selectOption } from '../utils';\nimport { UIProvider } from '../ui/UIProv..."
},
{
"path": "src/cli/cli.ts",
"chars": 3773,
"preview": "#!/usr/bin/env node\nimport * as dotenv from 'dotenv';\ndotenv.config();\nimport arg from 'arg';\nimport chalk from 'chalk';..."
},
{
"path": "src/cli/constants.ts",
"chars": 8658,
"preview": "import chalk from 'chalk';\n\nexport const templateTypes: { name: string; value: string }[] = [\n {\n name: 'An em..."
},
{
"path": "src/cli/convert.ts",
"chars": 5104,
"preview": "import { readFileSync, existsSync, writeFileSync, mkdirSync, readdirSync, statSync } from 'fs';\nimport path from 'path';..."
},
{
"path": "src/cli/create.ts",
"chars": 4664,
"preview": "import path from 'path';\nimport unixPath from 'path/posix';\nimport { lstat, mkdir, open, readdir, readFile } from 'fs/pr..."
},
{
"path": "src/cli/help.ts",
"chars": 804,
"preview": "import { UIProvider } from '../ui/UIProvider';\nimport { Args, extractFirstArg, Runner } from './Runner';\nimport { helpMe..."
},
{
"path": "src/cli/pack.ts",
"chars": 4170,
"preview": "import { execSync } from 'child_process';\nimport path from 'path';\nimport { promises as fs, existsSync } from 'fs';\n\nimp..."
},
{
"path": "src/cli/rename.spec.ts",
"chars": 2137,
"preview": "import { renameExactIfRequired } from './rename';\n\ndescribe('Rename', function () {\n it('It should rename exact match..."
},
{
"path": "src/cli/rename.ts",
"chars": 4566,
"preview": "import { promises as fs, existsSync } from 'fs';\nimport path from 'path';\n\nimport arg from 'arg';\n\nimport { COMPILABLES_..."
},
{
"path": "src/cli/run.ts",
"chars": 959,
"preview": "import arg from 'arg';\n\nimport { Args, extractFirstArg, Runner, RunnerContext } from './Runner';\nimport { createNetworkP..."
},
{
"path": "src/cli/set.ts",
"chars": 4127,
"preview": "import { readFile, writeFile } from 'fs/promises';\nimport { exec } from 'node:child_process';\nimport path from 'path';..."
},
{
"path": "src/cli/snapshot.ts",
"chars": 899,
"preview": "import { execSync } from 'node:child_process';\n\nimport arg from 'arg';\n\nimport { Runner } from './Runner';\nimport { help..."
},
{
"path": "src/cli/test.ts",
"chars": 1366,
"preview": "import { execSync } from 'child_process';\n\nimport arg from 'arg';\n\nimport { Runner } from './Runner';\nimport { helpArgs,..."
},
{
"path": "src/cli/verify.ts",
"chars": 15310,
"preview": "import path from 'path';\n\nimport { Address, Cell, Contract, ContractProvider, Dictionary, toNano } from '@ton/core';\nimp..."
},
{
"path": "src/compile/CompilerConfig.ts",
"chars": 1793,
"preview": "import { Cell } from '@ton/core';\n\nimport { TolkCompilerConfig } from './tolk/config';\nimport { FuncCompilerConfig } fro..."
},
{
"path": "src/compile/SourceSnapshot.ts",
"chars": 77,
"preview": "export type SourceSnapshot = {\n filename: string;\n content: string;\n};\n"
},
{
"path": "src/compile/SupportedLang.ts",
"chars": 54,
"preview": "export type SupportedLang = 'tact' | 'tolk' | 'func';\n"
},
{
"path": "src/compile/compile.ts",
"chars": 7181,
"preview": "import { readFileSync } from 'fs';\nimport path from 'path';\n\nimport { beginCell, Cell } from '@ton/core';\n\nimport { COMP..."
},
{
"path": "src/compile/func/compile.func.ts",
"chars": 2485,
"preview": "import { Cell } from '@ton/core';\nimport { compileFunc, CompilerConfig, compilerVersion, DebugInfo } from '@ton-communit..."
},
{
"path": "src/compile/func/config.ts",
"chars": 368,
"preview": "import { SourceResolver, SourcesArray, SourcesMap } from '@ton-community/func-js';\n\nexport type FuncCompilerConfig = {..."
},
{
"path": "src/compile/tact/OverwritableVirtualFileSystem.ts",
"chars": 915,
"preview": "import { resolve, normalize, sep } from 'path';\nimport { existsSync, readFileSync } from 'fs';\n\nimport { VirtualFileSyst..."
},
{
"path": "src/compile/tact/compile.tact.ts",
"chars": 3367,
"preview": "import path from 'path';\n\nimport { build, createVirtualFileSystem, Options, Project, stdLibFiles } from '@tact-lang/comp..."
},
{
"path": "src/compile/tact/config.ts",
"chars": 203,
"preview": "import { Config, Options } from '@tact-lang/compiler';\n\nexport type TactLegacyCompilerConfig = {\n lang: 'tact';\n t..."
},
{
"path": "src/compile/tolk/compile.tolk.ts",
"chars": 973,
"preview": "import { Cell } from '@ton/core';\nimport { getTolkCompilerVersion, runTolkCompiler, TolkCompilerConfig } from '@ton/tolk..."
},
{
"path": "src/compile/tolk/config.ts",
"chars": 214,
"preview": "export type TolkCompilerConfig = {\n lang: 'tolk';\n entrypoint: string;\n optimizationLevel?: number;\n withSta..."
},
{
"path": "src/config/Config.ts",
"chars": 1828,
"preview": "import { CustomNetwork } from './CustomNetwork';\nimport { Plugin } from './Plugin';\n\nexport interface Config {\n /**..."
},
{
"path": "src/config/CustomNetwork.ts",
"chars": 253,
"preview": "import { Network } from '../network/Network';\nimport { NetworkVersion } from '../network/NetworkVersion';\n\nexport type C..."
},
{
"path": "src/config/Plugin.ts",
"chars": 191,
"preview": "import { Runner } from '../cli/Runner';\n\nexport interface PluginRunner {\n name: string;\n runner: Runner;\n help:..."
},
{
"path": "src/config/tact.config.ts",
"chars": 709,
"preview": "import { existsSync, readFileSync, writeFileSync } from 'fs';\n\nimport { parseConfig, Config as TactConfig } from '@tact-..."
},
{
"path": "src/config/utils.ts",
"chars": 534,
"preview": "import { Config } from './Config';\nimport { BLUEPRINT_CONFIG } from '../paths';\n\nlet config: Config | undefined;\n\nexport..."
},
{
"path": "src/index.ts",
"chars": 952,
"preview": "export { tonDeepLink, sleep, getNormalizedExtMessageHash } from './utils';\n\nexport { NetworkProvider, SenderWithSendResu..."
},
{
"path": "src/jest/CoverageReporter.ts",
"chars": 3749,
"preview": "import path from 'node:path';\nimport fs from 'node:fs/promises';\nimport { existsSync } from 'fs';\n\nimport type { Reporte..."
},
{
"path": "src/jest/coverageSetup.ts",
"chars": 2090,
"preview": "import fs from 'node:fs';\nimport path from 'node:path';\n\nimport { Blockchain, BlockchainTransaction, GetMethodResult } f..."
},
{
"path": "src/network/Explorer.ts",
"chars": 67,
"preview": "export type Explorer = 'tonscan' | 'tonviewer' | 'toncx' | 'dton';\n"
},
{
"path": "src/network/Network.ts",
"chars": 110,
"preview": "import { AVAILABLE_NETWORKS } from './constants';\n\nexport type Network = (typeof AVAILABLE_NETWORKS)[number];\n"
},
{
"path": "src/network/NetworkProvider.ts",
"chars": 6539,
"preview": "import { parseFullConfig, TonClient, TonClient4 } from '@ton/ton';\nimport { Address, Cell, Contract, ContractProvider, C..."
},
{
"path": "src/network/NetworkVersion.ts",
"chars": 68,
"preview": "export type NetworkVersion = 'v2' | 'v4' | 'tonapi' | 'liteclient';\n"
},
{
"path": "src/network/constants.ts",
"chars": 336,
"preview": "export const AVAILABLE_NETWORKS = ['mainnet', 'testnet', 'tetra', 'custom'] as const satisfies string[];\n\nexport const M..."
},
{
"path": "src/network/createNetworkProvider.ts",
"chars": 25165,
"preview": "import path from 'path';\n\nimport arg from 'arg';\nimport {\n Address,\n Cell,\n comment,\n Contract,\n Contract..."
},
{
"path": "src/network/send/DeeplinkProvider.ts",
"chars": 1776,
"preview": "import { Address, beginCell, Cell, StateInit, storeStateInit } from '@ton/core';\nimport qrcode from 'qrcode-terminal';..."
},
{
"path": "src/network/send/MnemonicProvider.ts",
"chars": 5057,
"preview": "import { Buffer } from 'buffer';\n\nimport {\n Address,\n Cell,\n Contract,\n ContractProvider,\n MessageRelaxed..."
},
{
"path": "src/network/send/SendProvider.ts",
"chars": 263,
"preview": "import { Address, Cell, StateInit } from '@ton/core';\n\nexport interface SendProvider {\n connect(): Promise<void>;..."
},
{
"path": "src/network/send/TonConnectProvider.ts",
"chars": 4037,
"preview": "import qrcode from 'qrcode-terminal';\nimport TonConnect, { IStorage, WalletInfo, WalletInfoRemote } from '@tonconnect/sd..."
},
{
"path": "src/network/send/wallets.ts",
"chars": 3018,
"preview": "import { Buffer } from 'buffer';\n\nimport { WalletContractV4 as WalletContractV4R2 } from '@ton/ton/dist/wallets/WalletCo..."
},
{
"path": "src/network/storage/FSStorage.ts",
"chars": 1145,
"preview": "import path from 'path';\nimport fs from 'fs/promises';\n\nimport { Storage } from './Storage';\n\ntype StorageObject = {..."
},
{
"path": "src/network/storage/Storage.ts",
"chars": 862,
"preview": "export interface Storage {\n /**\n * Saves the `value` to the storage. Value can be accessed later by the `key`. Im..."
},
{
"path": "src/network/utils.ts",
"chars": 464,
"preview": "import { Network } from './Network';\nimport { MAINNET_NETWORK_GLOBAL_ID, TESTNET_NETWORK_GLOBAL_ID } from './constants';..."
},
{
"path": "src/paths.ts",
"chars": 1080,
"preview": "import path from 'path';\n\nexport const CONTRACTS = 'contracts';\nexport const TESTS = 'tests';\nexport const COMPILABLES =..."
},
{
"path": "src/template.spec.ts",
"chars": 242,
"preview": "import { executeTemplate } from './template';\n\ndescribe('template', () => {\n it('simple template', async () => {..."
},
{
"path": "src/template.ts",
"chars": 305,
"preview": "import path from 'path';\n\nexport const TEMPLATES_DIR = path.join(__dirname, 'templates');\n\nexport function executeTempla..."
},
{
"path": "src/templates/func/common/compilables/compile.ts.template",
"chars": 167,
"preview": "{{name}}.compile.ts\nimport { CompilerConfig } from '@ton/blueprint';\n\nexport const compile: CompilerConfig = {\n lang:..."
},
{
"path": "src/templates/func/common/contracts/imports/stdlib.fc.template",
"chars": 46315,
"preview": "stdlib.fc\n;; Standard library for funC\n;;\n\n{-\n This file is part of TON FunC Standard Library.\n\n FunC Standard Lib..."
},
{
"path": "src/templates/func/counter/contracts/contract.fc.template",
"chars": 2253,
"preview": "{{snakeName}}.fc\n#include \"imports/stdlib.fc\";\n\nconst op::increase = \"op::increase\"c; ;; create an opcode from string us..."
},
{
"path": "src/templates/func/counter/scripts/deploy.ts.template",
"chars": 654,
"preview": "deploy{{name}}.ts\nimport { toNano } from '@ton/core';\nimport { {{name}} } from '../wrappers/{{name}}';\nimport { compile,..."
},
{
"path": "src/templates/func/counter/scripts/increment.ts.template",
"chars": 1217,
"preview": "increment{{name}}.ts\nimport { Address, toNano } from '@ton/core';\nimport { {{name}} } from '../wrappers/{{name}}';\nimpor..."
},
{
"path": "src/templates/func/counter/tests/spec.ts.template",
"chars": 2499,
"preview": "{{name}}.spec.ts\nimport { Blockchain, SandboxContract, TreasuryContract } from '@ton/sandbox';\nimport { Cell, toNano } f..."
},
{
"path": "src/templates/func/counter/wrappers/wrapper.ts.template",
"chars": 2112,
"preview": "{{name}}.ts\nimport {\n Address,\n beginCell,\n Cell,\n Contract,\n ContractABI,\n contractAddress,\n Contr..."
},
{
"path": "src/templates/func/empty/contracts/contract.fc.template",
"chars": 145,
"preview": "{{snakeName}}.fc\n#include \"imports/stdlib.fc\";\n\n() recv_internal(int my_balance, int msg_value, cell in_msg_full, slice..."
},
{
"path": "src/templates/func/empty/scripts/deploy.ts.template",
"chars": 496,
"preview": "deploy{{name}}.ts\nimport { toNano } from '@ton/core';\nimport { {{name}} } from '../wrappers/{{name}}';\nimport { compile,..."
},
{
"path": "src/templates/func/empty/tests/spec.ts.template",
"chars": 1219,
"preview": "{{name}}.spec.ts\nimport { Blockchain, SandboxContract, TreasuryContract } from '@ton/sandbox';\nimport { Cell, toNano } f..."
},
{
"path": "src/templates/func/empty/wrappers/wrapper.ts.template",
"chars": 1101,
"preview": "{{name}}.ts\nimport {\n Address,\n beginCell,\n Cell,\n Contract,\n ContractABI,\n contractAddress,\n Contr..."
},
{
"path": "src/templates/func/legacy/wrappers/compile.ts.template",
"chars": 160,
"preview": "{{name}}.compile.ts\nimport { CompilerConfig } from '@ton/blueprint';\n\nexport const compile: CompilerConfig = {\n lang:..."
},
{
"path": "src/templates/func/not-separated-common/contracts/imports/stdlib.fc.template",
"chars": 46315,
"preview": "stdlib.fc\n;; Standard library for funC\n;;\n\n{-\n This file is part of TON FunC Standard Library.\n\n FunC Standard Lib..."
},
{
"path": "src/templates/func/not-separated-common/wrappers/compile.ts.template",
"chars": 167,
"preview": "{{name}}.compile.ts\nimport { CompilerConfig } from '@ton/blueprint';\n\nexport const compile: CompilerConfig = {\n lang:..."
},
{
"path": "src/templates/tact/counter/contracts/contract.tact.template",
"chars": 868,
"preview": "{{snakeName}}.tact\nmessage Add {\n amount: Int as uint32;\n}\n\ncontract {{name}}(\n // Persistent state variables decl..."
},
{
"path": "src/templates/tact/counter/scripts/deploy.ts.template",
"chars": 593,
"preview": "deploy{{name}}.ts\nimport { toNano } from '@ton/core';\nimport { {{name}} } from '../build/{{name}}/{{name}}_{{name}}';\nim..."
},
{
"path": "src/templates/tact/counter/scripts/increment.ts.template",
"chars": 1297,
"preview": "increment{{name}}.ts\nimport { Address, toNano } from '@ton/core';\nimport { {{name}} } from '../build/{{name}}/{{name}}_{..."
},
{
"path": "src/templates/tact/counter/tests/spec.ts.template",
"chars": 2443,
"preview": "{{name}}.spec.ts\nimport { Blockchain, SandboxContract, TreasuryContract } from '@ton/sandbox';\nimport { toNano } from '@..."
},
{
"path": "src/templates/tact/empty/contracts/contract.tact.template",
"chars": 809,
"preview": "{{snakeName}}.tact\n// Since v1.6.0, Tact has a contract parameters syntax that can supersede\n// lazy initialization by i..."
},
{
"path": "src/templates/tact/empty/scripts/deploy.ts.template",
"chars": 534,
"preview": "deploy{{name}}.ts\nimport { toNano } from '@ton/core';\nimport { {{name}} } from '../build/{{name}}/{{name}}_{{name}}';\nim..."
},
{
"path": "src/templates/tact/empty/tests/spec.ts.template",
"chars": 1163,
"preview": "{{name}}.spec.ts\nimport { Blockchain, SandboxContract, TreasuryContract } from '@ton/sandbox';\nimport { toNano } from '@..."
},
{
"path": "src/templates/tolk/common/compilables/compile.ts.template",
"chars": 437,
"preview": "{{name}}.compile.ts\nimport { CompilerConfig } from '@ton/blueprint';\n\nexport const compile: CompilerConfig = {\n lang:..."
},
{
"path": "src/templates/tolk/counter/contracts/contract.tolk.template",
"chars": 2252,
"preview": "{{snakeName}}.tolk\ntolk 1.0\n\n// this struct defines storage layout of the contract\nstruct Storage {\n id: uint32 // r..."
},
{
"path": "src/templates/tolk/counter/scripts/deploy.ts.template",
"chars": 654,
"preview": "deploy{{name}}.ts\nimport { toNano } from '@ton/core';\nimport { {{name}} } from '../wrappers/{{name}}';\nimport { compile,..."
},
{
"path": "src/templates/tolk/counter/scripts/increment.ts.template",
"chars": 1217,
"preview": "increment{{name}}.ts\nimport { Address, toNano } from '@ton/core';\nimport { {{name}} } from '../wrappers/{{name}}';\nimpor..."
},
{
"path": "src/templates/tolk/counter/scripts/reset.ts.template",
"chars": 1105,
"preview": "reset{{name}}.ts\nimport { Address, toNano } from '@ton/core';\nimport { {{name}} } from '../wrappers/{{name}}';\nimport {..."
},
{
"path": "src/templates/tolk/counter/tests/spec.ts.template",
"chars": 3094,
"preview": "{{name}}.spec.ts\nimport { Blockchain, SandboxContract, TreasuryContract } from '@ton/sandbox';\nimport { Cell, toNano } f..."
},
{
"path": "src/templates/tolk/counter/wrappers/wrapper.ts.template",
"chars": 2616,
"preview": "{{name}}.ts\nimport {\n Address,\n beginCell,\n Cell,\n Contract,\n ContractABI,\n contractAddress,\n Contr..."
},
{
"path": "src/templates/tolk/empty/contracts/contract.tolk.template",
"chars": 61,
"preview": "{{snakeName}}.tolk\nfun onInternalMessage(in: InMessage) {\n\n}\n"
},
{
"path": "src/templates/tolk/empty/scripts/deploy.ts.template",
"chars": 496,
"preview": "deploy{{name}}.ts\nimport { toNano } from '@ton/core';\nimport { {{name}} } from '../wrappers/{{name}}';\nimport { compile,..."
},
{
"path": "src/templates/tolk/empty/tests/spec.ts.template",
"chars": 1219,
"preview": "{{name}}.spec.ts\nimport { Blockchain, SandboxContract, TreasuryContract } from '@ton/sandbox';\nimport { Cell, toNano } f..."
},
{
"path": "src/templates/tolk/empty/wrappers/wrapper.ts.template",
"chars": 1101,
"preview": "{{name}}.ts\nimport {\n Address,\n beginCell,\n Cell,\n Contract,\n ContractABI,\n contractAddress,\n Contr..."
},
{
"path": "src/templates/tolk/not-separated-common/wrappers/compile.ts.template",
"chars": 437,
"preview": "{{name}}.compile.ts\nimport { CompilerConfig } from '@ton/blueprint';\n\nexport const compile: CompilerConfig = {\n lang:..."
},
{
"path": "src/types/file.ts",
"chars": 60,
"preview": "export type File = {\n name: string;\n path: string;\n};\n"
},
{
"path": "src/ui/InquirerUIProvider.ts",
"chars": 2011,
"preview": "import inquirer from 'inquirer';\nimport { Address } from '@ton/core';\n\nimport { UIProvider } from '../ui/UIProvider';\n\nc..."
},
{
"path": "src/ui/UIProvider.ts",
"chars": 2907,
"preview": "import { Address } from '@ton/core';\n\n/**\n * Interface for handling user interactions, such as displaying messages,\n * p..."
},
{
"path": "src/utils/file.utils.ts",
"chars": 539,
"preview": "import { Dirent } from 'fs';\nimport path from 'path';\n\nimport { File } from '../types/file';\n\nexport function extractFil..."
},
{
"path": "src/utils/index.ts",
"chars": 189,
"preview": "export * from './object.utils';\nexport * from './timer.utils';\nexport * from './ton.utils';\nexport * from './selection.u..."
},
{
"path": "src/utils/object.utils.ts",
"chars": 537,
"preview": "export function oneOrZeroOf<T extends { [k: string]: boolean | undefined }>(options: T): keyof T | undefined {\n let o..."
},
{
"path": "src/utils/selection.utils.ts",
"chars": 3344,
"preview": "import path from 'path';\nimport fs from 'fs/promises';\nimport { existsSync } from 'fs';\n\nimport { UIProvider } from '../..."
},
{
"path": "src/utils/string.utils.ts",
"chars": 1327,
"preview": "export function isPascalCase(str: string): boolean {\n return /^[A-Z][a-zA-Z0-9]*$/.test(str);\n}\n\nexport function toSn..."
},
{
"path": "src/utils/timer.utils.ts",
"chars": 117,
"preview": "export function sleep(ms: number) {\n return new Promise((resolve) => {\n setTimeout(resolve, ms);\n });\n}\n"
},
{
"path": "src/utils/ton.utils.ts",
"chars": 4324,
"preview": "import { Address, beginCell, Cell, Message, storeMessage } from '@ton/core';\n\nimport { Explorer } from '../network/Explo..."
},
{
"path": "tonconnect/manifest.json",
"chars": 185,
"preview": "{\n \"url\": \"https://github.com/ton-org/blueprint\",\n \"name\": \"TON Blueprint Project\",\n \"iconUrl\": \"https://raw.gi..."
},
{
"path": "tsconfig.json",
"chars": 387,
"preview": "{\n \"compilerOptions\": {\n \"target\": \"ES2021\",\n \"outDir\": \"dist\",\n \"module\": \"commonjs\",\n \"..."
}
]
About this extraction
This page contains the full source code of the ton-org/blueprint GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 133 files (380.6 KB), approximately 102.4k tokens. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.
Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.