FFFFFFFF OOOOO L L OOOOO W W
F O O L L O O W W
FFFF O O L L O O W W W
F O O L L O O W W W W
F OOOOO LLLLLLL LLLLLLL OOOOO WW WW
DDDDDD RRRRRR AAA GGGGGG OOOOO N N SSSSSS
D D R R A A G O O NN N S
D D RRRRRR AAAAAAA G GGGG O O N N N SSSSS
D D R R A A G G O O N N N S
DDDDDD R RR A A GGGGGG OOOOO N NNN SSSSSS
This project is a Python CLI application for TON SBT collection deployment and minting.
It provides an end-to-end command-line workflow for:
- creating an owner wallet
- generating recipient wallets
- validating configuration and balances before sending transactions
- deploying an SBT collection
- batch minting SBTs
- verifying minted items on-chain
This tool is intended to support two distinct workflows:
- testnet validation, where you can create throwaway wallets and verify the full deploy/mint flow safely
- mainnet execution, where you should use a real owner wallet and a real recipient list supplied by the end user or your production process
This project is open-source software developed by FollowDragons and released under the MIT License.
If you want to contribute changes, see CONTRIBUTING.md. If you are using a coding agent in this repository, see AGENTS.md.
.
|-- AGENTS.md
|-- CONTRIBUTING.md
|-- .env
|-- .env.example
|-- .gitignore
|-- LICENSE
|-- README.md
|-- pyproject.toml
|-- contracts/
| `-- README.md
|-- generated_wallets/
|-- scripts/
| |-- new-wallet.ps1
| |-- run.ps1
| |-- setup.ps1
| `-- test.ps1
|-- src/
| `-- ton_sbt_tool/
| |-- __init__.py
| |-- __main__.py
| |-- cli.py
| |-- config.py
| |-- doctor.py
| |-- metadata.py
| |-- minting.py
| |-- output.py
| `-- wallets.py
|-- tests/
| |-- test_cli_config.py
| |-- test_cli_files.py
| |-- test_cli_metadata.py
| `-- test_integration_manual.py
`-- wallets.txt.example
There are no legacy root-level Python launchers in the final layout. The supported entrypoints are the PowerShell scripts, the installed console script, and the Python module entrypoint.
Main PowerShell entrypoint:
.\scripts\run.ps1 <command> [options]Installed console script entrypoint:
.\.venv\Scripts\ton-sbt-cli <command> [options]Direct Python module entrypoint:
.\.venv\Scripts\python -m ton_sbt_tool <command> [options]Install the project in editable mode:
.\scripts\setup.ps1What this does:
- creates
.venv - upgrades
pip - installs the package with
pip install -e .[dev]
Run the automated test suite:
.\scripts\test.ps1Run only the opt-in manual integration tests:
$env:TON_SBT_RUN_INTEGRATION=1
.\scripts\test.ps1 tests\test_integration_manual.py -qYou do not have to create a new owner wallet with this CLI.
You can use either of these approaches:
- use an existing wallet by pasting its mnemonic into
.env - create a new wallet with
wallet-new
Use an existing wallet when:
- you already have a funded wallet
- you want to mint from a wallet you control today
- you want to keep one stable owner or authority address for the collection
Create a new wallet when:
- you are testing the system from scratch
- you want a clean dedicated owner wallet
- you do not already have a funded TON wallet for this workflow
If you already have a wallet, set these values directly in .env:
MNEMONIC=your existing 24-word mnemonic
WALLET_VERSION=v5r1If you want to create a new wallet instead, use one of these commands:
If your funded wallet is W5:
.\scripts\run.ps1 wallet-new --wallet-version v5r1If you need W4:
.\scripts\run.ps1 wallet-new --wallet-version v4r2Shortcut:
.\scripts\new-wallet.ps1 --wallet-version v5r1Copy these values from the output into .env:
MNEMONIC=...WALLET_VERSION=...- the printed testnet address for funding
Minimal first-run configuration:
MNEMONIC=your 24 words here
WALLET_VERSION=v5r1
COLLECTION_ADDRESS=
IS_TESTNET=true
COLLECTION_METADATA_URL=https://example.com/collection.json
ITEMS_BASE_URL=https://example.com/metadata/
ITEM_METADATA_SUFFIX=
TONCENTER_API_KEY=
TONCENTER_ENDPOINT=
TONCENTER_RPS_LIMIT=1
TONCENTER_RPS_PERIOD=1.0
SBT_COLLECTION_CODE_BOC=contracts/nft-collection.boc
SBT_ITEM_CODE_BOC=contracts/nft-item-sbt.bocRules:
- keep
COLLECTION_ADDRESS=empty for the first deploy - use
WALLET_VERSION=v5r1for funded W5 wallets - use
WALLET_VERSION=v4r2for funded W4 wallets ITEMS_BASE_URLsupports both prefix mode and fixed-file modeITEM_METADATA_SUFFIXis optional and is used only in prefix mode- the BOC paths are optional; built-in
tonutilscode is used if files are missing TONCENTER_API_KEYis optional but recommended for better reliability and fewer rate-limit issues
ITEMS_BASE_URL behavior:
- prefix mode: if
ITEMS_BASE_URL=https://example.com/nft/, the CLI stores item metadata asITEMS_BASE_URL + suffix - fixed-file mode: if
ITEMS_BASE_URL=https://example.com/nft/176.json, every minted SBT points to that exact file
Precedence rules:
- if
ITEMS_BASE_URLis a concrete file URL, it overrides per-index generation andITEM_METADATA_SUFFIX - if
ITEMS_BASE_URLis a prefix andITEM_METADATA_SUFFIXis set, every minted SBT uses that same suffix under the prefix - if
ITEMS_BASE_URLis a prefix andITEM_METADATA_SUFFIXis empty, the CLI generates0.json,1.json,2.json, and so on
Use testnet when:
- you are validating the full workflow for the first time
- you are testing metadata and recipient formatting
- you are checking wallet version, balances, deploy logic, or verification logic
Testnet configuration:
IS_TESTNET=true
COLLECTION_ADDRESS=Testnet rules:
- fund the owner wallet with test TON only
- it is safe to use generated wallets for recipients
- it is acceptable to reuse one fixed test metadata suffix such as
176.json - always run
doctorbeforerun
Recommended testnet flow:
.\scripts\setup.ps1
.\scripts\run.ps1 doctor --wallets wallets.txt
.\scripts\run.ps1 run --wallets wallets.txtUse mainnet only after the testnet flow is already working.
Mainnet configuration:
IS_TESTNET=falseMainnet rules:
- use a real funded owner wallet
- prefer an existing wallet mnemonic instead of generating a new wallet unless you explicitly want a new dedicated owner
- use a real recipient list supplied by the user, CRM, export, or production process
- verify metadata URLs and suffix rules before sending transactions
- verify the collection settings before the first irreversible deploy
Recommended mainnet flow:
.\scripts\run.ps1 doctor --wallets wallets.txt
.\scripts\run.ps1 deploy
.\scripts\run.ps1 mint --wallets wallets.txtMainnet safety guidance:
- test with a small recipient list first
- confirm
WALLET_VERSIONmatches the real owner wallet - confirm
COLLECTION_ADDRESSis empty before first deploy and preserved after deploy - keep a backup of the mnemonic outside the project directory
Before deploy or run, the owner wallet must have enough balance on the selected network.
Balance guidance:
- testnet: fund with test TON
- mainnet: fund with real TON and keep extra balance for deploy, mint value, and network fees
Approximate minimum values used by this project:
- collection deploy: about
0.05 TON - mint message per one SBT: about
0.02 TON - forwarded amount into the new SBT item: about
0.005 TON
Practical funding guidance:
- first deploy with one SBT: roughly
0.07 TONplus fees - first deploy with ten SBTs: roughly
0.25 TONplus fees - keep extra balance above the minimum, especially on mainnet
Network rule:
- testnet TON and mainnet TON are separate
- you must have funds on the owner wallet in the same network where you are running the command
- testnet balance does not help on mainnet, and mainnet balance does not help on testnet
Testnet funding source:
- for this guide, get test TON from
t.me/testgiver_ton_bot
wallets-generate is a test helper, not a production recipient sourcing method.
Use it only when:
- you want to test deploy and mint end to end
- you need disposable recipient addresses
- you are validating metadata or verification logic
Do not use generated wallets as your real production recipient source.
For real minting, prepare wallets.txt from the actual user wallet list.
Recipient file location rules:
- default location: keep
wallets.txtin the project root - alternative: keep the file anywhere and pass the exact path with
--wallets - the CLI does not require the file to be in a special folder if you pass
--wallets
Generate ten test wallets:
.\scripts\run.ps1 wallets-generate --count 10Write recipient addresses directly into wallets.txt:
.\scripts\run.ps1 wallets-generate --count 10 --addresses-out wallets.txt.\scripts\run.ps1 doctor --wallets wallets.txt.\scripts\run.ps1 run --wallets wallets.txtCreate one wallet:
.\scripts\run.ps1 wallet-new --wallet-version v5r1Examples:
.\scripts\run.ps1 wallet-new --wallet-version v5r1 --label owner
.\scripts\run.ps1 wallet-new --wallet-version v4r2 --words 24Generate recipient wallets in batch:
.\scripts\run.ps1 wallets-generate --count 10 --wallet-version v5r1Examples:
.\scripts\run.ps1 wallets-generate --count 10 --addresses-out wallets.txt
.\scripts\run.ps1 wallets-generate --count 50 --label sbt-testImportant:
- this command is for testing and system validation
- the generated mnemonics are stored in
generated_wallets/ - production minting should use a user-provided recipient file instead of generated test wallets
Dry-run diagnostics:
.\scripts\run.ps1 doctor --wallets wallets.txtChecks:
.env- wallet version and derived address
- owner wallet balance and state
- collection and item code availability
- recipient file parsing
- projected collection address
- existing
COLLECTION_ADDRESS, if already set
Deploy the collection only:
.\scripts\run.ps1 deployMint into an existing collection:
.\scripts\run.ps1 mint --wallets wallets.txtExamples:
.\scripts\run.ps1 mint --wallets wallets.txt --batch-size 100
.\scripts\run.ps1 mint --wallets wallets.txt --no-verifyRequires COLLECTION_ADDRESS in .env.
Deploy if needed, then mint:
.\scripts\run.ps1 run --wallets wallets.txtExamples:
.\scripts\run.ps1 run --wallets wallets.txt --batch-size 100
.\scripts\run.ps1 run --wallets generated_wallets\test-wallets-<timestamp>.txtRun the automated test suite:
.\scripts\test.ps1Examples:
.\scripts\test.ps1 -q
.\scripts\test.ps1 tests\test_cli_metadata.py
.\scripts\test.ps1 tests\test_integration_manual.py -qThe CLI only stores metadata URLs on-chain. It does not upload or host JSON metadata files. You must host collection metadata and item metadata yourself so the URLs are reachable over the web.
For smoke testing:
COLLECTION_METADATA_URL=https://save.followdragons.monster/collections/followdragons/collection.json
ITEMS_BASE_URL=https://save.followdragons.monster/collections/followdragons/metadata/
ITEM_METADATA_SUFFIX=176.jsonThis means:
- collection metadata comes from
collection.json - every minted SBT points to the metadata suffix
176.json
This is useful for testnet smoke tests only. For production, prepare your real metadata structure and use the intended suffix scheme.
Alternative fixed-file smoke test:
COLLECTION_METADATA_URL=https://save.followdragons.monster/collections/followdragons/collection.json
ITEMS_BASE_URL=https://save.followdragons.monster/collections/followdragons/metadata/176.json
ITEM_METADATA_SUFFIX=This means:
- the collection common prefix is stored as empty
- every minted SBT points directly to
176.json ITEM_METADATA_SUFFIXis ignored becauseITEMS_BASE_URLalready points to a concrete file
Use these standards consistently so the CLI behaves predictably and the mint data remains auditable.
- file format: plain text UTF-8
- one
KEY=valuepair per line - no quotes unless the value itself requires them in your own tooling
- keep URLs absolute, including the protocol, for example
https://... - keep TON amounts in decimal TON units, not nanotons
- keep booleans as
trueorfalse - leave
COLLECTION_ADDRESSempty for the first deploy
Required operational fields:
MNEMONICWALLET_VERSIONIS_TESTNETCOLLECTION_METADATA_URLITEMS_BASE_URL
Optional operational fields:
COLLECTION_ADDRESSITEM_METADATA_SUFFIXTONCENTER_API_KEYTONCENTER_ENDPOINTSBT_COLLECTION_CODE_BOCSBT_ITEM_CODE_BOC
The recipient file used by doctor, mint, and run must follow these rules:
- default filename and location:
wallets.txtin the project root - alternative location: any path passed with
--wallets - plain text UTF-8
- one TON address per line
- no commas
- no JSON
- no tabular formatting
- no duplicate addresses unless duplicate minting is intentional
- keep the file generated from the real user source of truth for production
Valid example:
EQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAM9c
kQBBYzw3-N7wle1V7KJJv9yQopTB_iNuEX93e8UgUFsIGQGf
Recommended production practice:
- prepare the final recipient file separately
- review it before minting
- archive the exact file used for each mint run
COLLECTION_METADATA_URLshould point to one collection JSON documentITEMS_BASE_URLmay be either a common prefix or one concrete metadata file- metadata URLs should be publicly reachable over HTTP or HTTPS at mint time
- this project does not host metadata files or provide storage for them
- item suffixes should be deterministic and documented when prefix mode is used
- for smoke tests, either
ITEM_METADATA_SUFFIXmay point every token to one known file orITEMS_BASE_URLmay point directly to one file - for production, prefer a suffix strategy that maps clearly to item indices or business identifiers
Examples:
- base URL:
https://example.com/metadata/ - per-item suffixes:
0.json,1.json,2.json - fixed-file URL:
https://example.com/metadata/176.json - smoke-test suffix:
176.json
Resolution rules:
- prefix mode with empty
ITEM_METADATA_SUFFIX:- item
0->https://example.com/metadata/0.json - item
1->https://example.com/metadata/1.json
- item
- prefix mode with
ITEM_METADATA_SUFFIX=176.json:- every item ->
https://example.com/metadata/176.json
- every item ->
- fixed-file mode with
ITEMS_BASE_URL=https://example.com/metadata/176.json:- every item ->
https://example.com/metadata/176.json
- every item ->
- keep custom BOCs under
contracts/ - use stable filenames when possible
- treat custom BOCs as versioned deployment artifacts
- if you do not supply BOCs, the CLI falls back to the built-in
tonutilscode
Use this operating model for clean, repeatable runs:
- Prepare or verify the owner wallet mnemonic and
WALLET_VERSION. - Prepare metadata URLs.
- Prepare the recipient file.
- Run
doctor. - On first deploy, keep
COLLECTION_ADDRESSempty. - Run
deployorrun. - Archive the final recipient file and resulting collection address.
Production recommendation:
- use
wallets-generateonly for testnet system checks - use real user recipient data for real minting
- separate test artifacts from production recipient files
The project includes an automated pytest suite for the non-network-critical logic:
.envparsing and validation- TON endpoint normalization
- metadata mode resolution for
ITEMS_BASE_URL - recipient file parsing and duplicate filtering
.envcollection address persistence
The project also includes a very small optional integration layer:
- file:
tests/test_integration_manual.py - marked as
integrationandmanual - skipped by default
- runs only when
TON_SBT_RUN_INTEGRATION=1 - intended for live testnet credentials already present in
.env - does not send deploy or mint transactions
- verifies live connectivity, wallet derivation, and collection projection/queryability
What these tests do not cover:
- live deploys against testnet or mainnet
- Toncenter availability
- external wallet balances
- explorer-side verification
Recommended practice:
- run
.\scripts\test.ps1 - optionally run
TON_SBT_RUN_INTEGRATION=1withtests\test_integration_manual.py - run
.\scripts\run.ps1 doctor --wallets wallets.txt - run live testnet transactions
- only then move to mainnet
The same mnemonic resolves to different addresses for v4r2 and v5r1.
If you use the wrong version:
- the CLI derives a different address
- the balance may appear as
0 TON - the wallet may appear as
nonexist
- first run: keep it empty
- after
deployorrun: the CLI writes it automatically - later
mintruns: reuse the saved address
The public testnet endpoint can return 429 Ratelimit exceed.
Built into this project:
DEFAULT_HTTP_RETRY_POLICYfromtonutils- conservative default limit:
TONCENTER_RPS_LIMIT=1
TONCENTER_RPS_PERIOD=1.0Safer but slower mode:
TONCENTER_RPS_LIMIT=1
TONCENTER_RPS_PERIOD=2.0Recommendation:
- if you have a Toncenter API key, set
TONCENTER_API_KEYin.env - this improves reliability on both testnet and mainnet
- it reduces the chance of public rate-limit failures during
doctor,deploy,mint, andrun
How to get a key:
- official TON Docs guide: https://docs.ton.org/ecosystem/api/toncenter/get-api-key
- Toncenter bot: https://t.me/toncenter
contracts/ may contain:
nft-collection.bocnft-item-sbt.boc
These files are optional.
If they are missing, the CLI uses built-in compiled contract code from tonutils.
After successful completion, the CLI prints a Next block explaining the most likely next action:
- after
wallet-new - after
wallets-generate - after
doctor - after
deploy - after
mint - after
run
generated_wallets/contains mnemonic phrases and private key material- the directory is ignored by git
- do not publish these files into a public repository
- do not treat generated test wallets as production user wallets
- do not commit
.env,wallets.txt,generated_wallets/, recipient lists, mnemonic phrases, or API keys
This project is licensed under the MIT License. See LICENSE.
- TON NFT reference: https://docs.ton.org/standard/tokens/nft/reference
- TON NFT deploy guide: https://docs.ton.org/standard/tokens/nft/deploy
- TON SBT guide: https://docs.ton.org/standard/tokens/nft/sbt
- TON Docs, Toncenter API key: https://docs.ton.org/ecosystem/api/toncenter/get-api-key
- Token contracts repo: https://github.com/ton-blockchain/token-contract
- tonutils docs: https://tonutils.com