├── .gitignore ├── LICENSE ├── README.md ├── SUMMARY.md ├── build_tonlib.sh ├── developer-interface.md ├── developer-interface ├── account.md ├── tonlib_client.md └── tonlibclient.md ├── examples ├── generate_wallet.py ├── init.py ├── nft.py ├── raw.py ├── transactions.py └── wallet.py ├── infrastructure ├── tonlib.Dockerfile └── tonlib.patch ├── makefile ├── poetry.lock ├── pyproject.toml ├── synchronous.md ├── ton ├── __init__.py ├── account │ ├── __init__.py │ ├── ft_methods.py │ ├── nft_methods.py │ ├── smc_methods.py │ ├── state_methods.py │ └── wallet_methods.py ├── client │ ├── __init__.py │ ├── converter_methods.py │ ├── function_methods.py │ ├── tonlib_methods.py │ └── wallet_methods.py ├── distlib │ ├── darwin │ │ ├── libtonlibjson.arm64.dylib │ │ └── libtonlibjson.x86_64.dylib │ ├── freebsd │ │ └── libtonlibjson.amd64.so │ ├── linux │ │ ├── libtonlibjson.aarch64.so │ │ └── libtonlibjson.x86_64.so │ └── windows │ │ └── tonlibjson.amd64.dll ├── errors.py ├── sync.py ├── tl │ ├── __init__.py │ ├── base.py │ ├── functions │ │ ├── __init__.py │ │ ├── accounts.py │ │ ├── keys.py │ │ ├── queries.py │ │ ├── settings.py │ │ └── smc.py │ └── types │ │ ├── __init__.py │ │ ├── accounts.py │ │ ├── actions.py │ │ ├── keys.py │ │ ├── messages.py │ │ ├── smc.py │ │ ├── tvm.py │ │ └── wallets.py ├── tonlibjson.py └── utils │ ├── __init__.py │ ├── cell.py │ ├── common.py │ └── wallet.py └── troubleshooting.md /.gitignore: -------------------------------------------------------------------------------- 1 | .idea/ 2 | .keystore/ 3 | index.html 4 | playground.py 5 | 6 | # Byte-compiled / optimized / DLL files 7 | __pycache__/ 8 | *.pyc 9 | *.pyo 10 | *.pyd 11 | *.local 12 | 13 | # C extensions 14 | *.so 15 | 16 | # Distribution / packaging 17 | .Python 18 | env/ 19 | build/ 20 | dist/ 21 | eggs/ 22 | .eggs/ 23 | lib/ 24 | lib64/ 25 | sdist/ 26 | var/ 27 | *.egg-info/ 28 | .installed.cfg 29 | *.egg 30 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2022 Maxim Gurov 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # 🌐 Introduction 2 | 3 | This library allows you to work with the [TON blockchain](https://ton.org) API from Python. 4 | 5 | **Features**: 6 | 7 | * Creating and importing wallet 8 | * Getting wallets balance 9 | * Getting transactions of any wallet 10 | * Transfer coins 11 | * Executing methods of smart-contracts 12 | * Transfer NFT 13 | 14 | [![PyPI version](https://badge.fury.io/py/ton.svg)](https://badge.fury.io/py/ton) ![visitors](https://visitor-badge.glitch.me/badge?page\_id=psylopunk.pytonlib.readme\&left\_color=gray\&right\_color=red) ![](https://pepy.tech/badge/ton) [![](https://img.shields.io/badge/%F0%9F%92%8E-TON-green)](https://ton.org) 15 | 16 | ### How to install: 17 | 18 | ```bash 19 | pip install ton 20 | ``` 21 | 22 | If you have an _illegal instruction_ error then you need to build libtonlibjson by yourself: 23 | 24 | ```bash 25 | git clone https://github.com/psylopunk/pytonlib && cd pytonlib 26 | && ./build_tonlib.sh # docker is needed 27 | ``` 28 | 29 | #### Getting started 30 | 31 | [Examples](https://github.com/psylopunk/pytonlib/tree/main/examples) will give a general look at the library. They describe almost all supported methods, but in addition, below you can see each method in detail. To make a custom request to `libtonlibjson`, check out list of available methods and execute it using `client.execute` 32 | 33 | #### [More documentation here](developer-interface.md) 34 | 35 | #### Troubleshooting 36 | 37 | Found a bug? Or just improvments? -- Read more about this in [Troubleshooting](troubleshooting.md) 38 | 39 | **Donate** 40 | 41 | * BTC – 192gK2df3izkpaNgcMbfEDrLgoofyjjfeC 42 | * TON – [EQCl1Ug9ZT9ZfGyFH9l4q-bqaUy6kyOzVPmrk7bivmVKJRRZ](ton://transfer/EQCl1Ug9ZT9ZfGyFH9l4q-bqaUy6kyOzVPmrk7bivmVKJRRZ) 43 | -------------------------------------------------------------------------------- /SUMMARY.md: -------------------------------------------------------------------------------- 1 | # Table of contents 2 | 3 | * [🌐 Introduction](README.md) 4 | * [Examples](https://github.com/psylopunk/pytonlib/tree/main/examples) 5 | * [Synchronous](synchronous.md) 6 | * [🛠 Developer Interface](developer-interface.md) 7 | * [TonlibClient](developer-interface/tonlibclient.md) 8 | * [Account](developer-interface/account.md) 9 | * [⚠ Troubleshooting](troubleshooting.md) 10 | -------------------------------------------------------------------------------- /build_tonlib.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | set -e 3 | 4 | echo "Building image" 5 | docker build -t tonlib-builder -f infrastructure/tonlib.Dockerfile . $@ 6 | 7 | echo "Running container" 8 | CONTAINER_ID=$(docker run -d tonlib-builder) 9 | 10 | echo "Copying libtonlibjson.so" 11 | docker cp ${CONTAINER_ID}:/ton/build/tonlib/libtonlibjson.so.0.5 ton/distlib/linux/libtonlibjson.so 12 | 13 | echo "Removing container" 14 | docker container rm ${CONTAINER_ID} 15 | -------------------------------------------------------------------------------- /developer-interface.md: -------------------------------------------------------------------------------- 1 | # 🛠 Developer Interface 2 | 3 | **The files in this directory are module's classes. This page describe all their functions** 4 | 5 | [ton.TonlibClient](developer-interface/tonlibclient.md) – data provider and the parent class of everything\ 6 | [ton.Account](developer-interface/account.md) – representation of a wallet or any account on the network 7 | -------------------------------------------------------------------------------- /developer-interface/account.md: -------------------------------------------------------------------------------- 1 | # Account 2 | 3 | General class for wallets and account browsing. To execute any methods, you must specify client. It is recommended to get an instance of this class only through methods: `TonlibClient.find_account`, `TonlibClient.create_wallet`, `TonlibClient.import_wallet`, `TonlibClient.find_wallet`. 4 | 5 | ```python 6 | class ton.Account(address, key=None, client=None) 7 | ``` 8 | 9 | #### Parameters: 10 | 11 | * **address** – (str, required) b64url address form 12 | * **key** – (str, optional) private key for transfer coins (wallet contract) 13 | * **client** – (int, required) initialized TonlibClient 14 | 15 | ### Methods: 16 | 17 | * `get_balance() -> int nanoTONs`\ 18 | `` 19 | * `get_transactions(from_transaction_lt=None, from_transaction_hash=None, to_transaction_lt=None, limit=10) -> TLObject`\ 20 | ``Get a list of account transactions\ 21 | **from\_transaction\_lt & from\_transaction\_hash** must only be used together to get transactions older than specified\ 22 | **to\_transaction\_lt**: ignoring transactions older than specified logic time\ 23 | 24 | * `detect_type() -> dict`\ 25 | ``With this, you can determine the version of the wallet, if it is initialized\ 26 | 27 | * `get_state(force=True) -> state TLObject`\ 28 | ``Full information about the contract, including its code and data\ 29 | ``**force**: (bool) if False data is taken from cache\ 30 | 31 | * `transfer(destination, amount=None, data=None, comment=None, send_mode=1)`\ 32 | ``Transferring coins.\ 33 | **destination**: (str, list) account address. Or list for multiple output messages ([read more](../examples/transactions.py))\ 34 | **amount**: (int) nanoTONs amount\ 35 | **data**: (bytes) serialized cells / BOC\ 36 | **comment**: (str) comment\ 37 | **send\_mode**: (int) default 1\ 38 | 0 - commission is taken from transfer amount\ 39 | 1 - commission is taken separately from the balance\ 40 | 128 - transfer of all funds\ 41 | 128+32 - deleting an account\ 42 | Read more in the documentation ton.org/docs\ 43 | 44 | * `transfer_nft(item_addr, new_owner_addr, response_address=None, query_id=0, forward_amount=0, forward_payload=None)`\ 45 | ``Changing the owner of the NFT by sending an internal transaction (0.05 TON)\ 46 | **item\_addr**: (str) b64url address NFT\ 47 | **new\_owner\_addr**: (str) b64url address of recipient NFT\ 48 | **response\_address**: (str) b64url address for response\ 49 | **query\_id**: (int) arbitrary request number\ 50 | **forward\_amount**: (int) forward nanoTONs amount\ 51 | **forward\_payload**: (bytes) forward body\ 52 | 53 | * `run_get_method(name|id, stack=[], force=False)`\ 54 | ``Execution GET method of smart contract\ 55 | **name|id**: (int, str) method ID or its string representation\ 56 | **stack:** (list of Tvm\_StackEntry..) arguments\ 57 | **force**: (bool) if False data is taken from cache\ 58 | **Example:**\ 59 | ****`from ton.tl.types import Tvm_StackEntryNumber, Tvm_NumberDecimal`\ 60 | `account.run_get_method('get_nft_address_by_index', [Tvm_StackEntryNumber(Tvm_NumberDecimal(1))])`\ 61 | `` 62 | * `send_message(body)`\ 63 | ``Sending an external message\ 64 | **body**: (bytes) serialized cells / BOC\ 65 | 66 | * `get_nft_data()` 67 | * `get_collection_data()` 68 | * `get_nft_address_by_index(index)` 69 | * `royalty_params()` 70 | * `create_transfer_nft_body(new_owner, response_address=None, query_id=0, forward_amount=0, forward_payload=0)` 71 | 72 | In addition, there is a property method that serializes the private key into a string to load wallet from Keystore. For more information, see the method `TonlibClient.find_wallet` 73 | 74 | -------------------------------------------------------------------------------- /developer-interface/tonlib_client.md: -------------------------------------------------------------------------------- 1 | # Client 2 | 3 | In TON we have a library (or a binary) called _tonlib_. It is used to interact with TON blokchain. 4 | \ 5 | This class creates an instanse of the tonlib to do the all TON stuff. 6 | 7 | ``` python 8 | class ton.TonlibClient(keystore=None, 9 | config='https://newton-blockchain.github.io/global.config.json', 10 | ls_index: int=0, workchain_id=0) 11 | ``` 12 | 13 | #### Parameters: 14 | 15 | * **keystore** -- (str, optional) Directory where keys (will be) stored, need 777 rights. Default is `.keystore`. 16 | * **config** -- (str, optional) File or URL with the TON config. 17 | * **ls\_index** -- (int, optional) Light server index. Like config modes. More [here](https://github.com/ton-blockchain/ton/blob/master/lite-client/lite-client.cpp#L329). 18 | * **work**_**chain\_id** -- (int, optional) Workchain ID. With this parameter, you can work, for example, in the masterchain by specifying -1 or in the workchain by specifying 0. No one really knows how this parameter will be used in the future, but it is there. 19 | 20 |
21 | 22 | ### Usage example: 23 | 24 | **Default:** 25 | ```python 26 | >>> # Classic initializing. 27 | >>> client = ton.TonlibClient() 28 | >>> await client.init_tonlib() 29 | ``` 30 | 31 | **Advanced:** 32 | ```python 33 | >>> # The following code will use libtonlibjson from the current 34 | >>> # directory, will use testnet config, will save keys in .mykeys 35 | >>> # directory and will start the lightserver in mode 2. 36 | >>> client = ton.TonlibClient(keystore='.mykeys', 37 | config='https://ton-blockchain.github.io/testnet-global.config.json', 38 | ls_index=2) 39 | >>> await client.init_tonlib(cdll_path='./libtonlibjson.so') 40 | ``` 41 | 42 |
43 | 44 | ### Methods: 45 | * `init_tonlib(self, cdll_path=None)` 46 | 47 | Initialising of a tonlib library. Can take it's path. 48 | \ 49 | Default path will be selected automaticaly depending on your OS and CPU acrhitecture. 50 | 51 | 52 | * `reconnect(self)` 53 | 54 | Reinitializing of a tonlib. In case if something gone wrong, for example. 55 | 56 | 57 | * `execute(self, query, timeout=30) -> result: TL Object` 58 | 59 | Direct request to liteserver. 60 | \ 61 | In mostly, used by the other functions of module like transfer, get_address, etc. 62 | 63 | **query**: dict or TLObject 64 | \ 65 | **timeout**: int -------------------------------------------------------------------------------- /developer-interface/tonlibclient.md: -------------------------------------------------------------------------------- 1 | # TonlibClient 2 | 3 | In TON we have a library (or a binary) called _tonlib_. It is used to interact with TON blokchain.\ 4 | This class creates an instanse of the tonlib to do the all TON stuff. 5 | 6 | ```python 7 | class ton.TonlibClient(keystore=None, 8 | config='https://newton-blockchain.github.io/global.config.json', 9 | ls_index: int=0, workchain_id=0) 10 | ``` 11 | 12 | #### Parameters: 13 | 14 | * **keystore** – (str, optional) Directory where keys (will be) stored, need 777 rights. Default is `.keystore`. 15 | * **config** – (str, optional) File or URL with the TON config. 16 | * **ls\_index** – (int, optional) Light server index. Like config modes. More [here](https://github.com/ton-blockchain/ton/blob/master/lite-client/lite-client.cpp#L329). 17 | * **workchain\_id** – (int, optional) Workchain ID. With this parameter, you can work, for example, in the masterchain by specifying -1 or in the workchain by specifying 0. No one really knows how this parameter will be used in the future, but it is there. 18 | 19 | ### Usage example: 20 | 21 | **Default:** 22 | 23 | ```python 24 | >>> # Classic initializing. 25 | >>> client = ton.TonlibClient() 26 | >>> await client.init_tonlib() 27 | ``` 28 | 29 | **Advanced:** 30 | 31 | ```python 32 | >>> # The following code will use libtonlibjson from the current 33 | >>> # directory, will use testnet config, will save keys in .mykeys 34 | >>> # directory and will start the lightserver in mode 2. 35 | >>> client = ton.TonlibClient(keystore='.mykeys', 36 | config='https://ton-blockchain.github.io/testnet-global.config.json', 37 | ls_index=2) 38 | >>> await client.init_tonlib(cdll_path='./libtonlibjson.so') 39 | ``` 40 | 41 | ### Methods: 42 | 43 | * `init_tonlib(self, cdll_path=None)` 44 | 45 | Initialising of a tonlib library. Can take it's path.\ 46 | Default path will be selected automaticaly depending on your OS and CPU acrhitecture.\ 47 | 48 | * `reconnect(self)` 49 | 50 | Reinitializing of a tonlib. In case if something gone wrong, for example.\ 51 | 52 | * `execute(self, query, timeout=30) -> result TLObject` 53 | 54 | Direct request to liteserver.\ 55 | In mostly, used by the other functions of module like transfer, get\_address, etc. 56 | 57 | **query**: dict or TLObject\ 58 | **timeout**: int\ 59 | 60 | * `find_account(address, preload_state=True) -> Account`\ 61 | _``_Load Account instance of any address\ 62 | **address**: str in b64url form\ 63 | **preload\_state**: bool. Load state in cache\ 64 | 65 | * `create_wallet(source='v3r2', workchain_id=0, wallet_id=0, local_password=None) -> Account`\ 66 | ``The easiest and most intuitive way to create a wallet.\ 67 | ``**source**: (str) Wallet version. It is better not to change, because others are not supported by libtonlibjson :(\ 68 | **workchain\_id:** (int, optional) Workchain ID. With this parameter, you can work, for example, in the masterchain by specifying -1 or in the workchain by specifying 0. No one really knows how this parameter will be used in the future, but it is there.\ 69 | **wallet\_id**: (int, optional) Wallet ID. Derivation parameter\ 70 | **local\_password**: (None, str) password for encrypt keys in keystore dir\ 71 | 72 | * `find_wallet(path, local_password=None)`\ 73 | Getting wallet from Keystore via Wallet.path\ 74 | **path**: (str) serialized key-path\ 75 | **local\_password**: (None, str) password for encrypt keys in keystore dir\ 76 | 77 | * `import_wallet(word_list, source='v3r2', workchain_id=0, wallet_id=0, local_password=None) -> Account`\ 78 | ``Import wallet using seed phrase. Do not use this all the time with one wallet, because a new file is created in the Keystore every time \ 79 | **word\_list**: (str) seed phrase separated by spaces\ 80 | **source**: (str) Wallet version. It is better not to change, because others are not supported by libtonlibjson :(\ 81 | **workchain\_id:** (int, optional) Workchain ID. With this parameter, you can work, for example, in the masterchain by specifying -1 or in the workchain by specifying 0. No one really knows how this parameter will be used in the future, but it is there.\ 82 | **wallet\_id**: (int, optional) Wallet ID. Derivation parameter\ 83 | **local\_password**: (None, str) password for encrypt keys in keystore dir\ 84 | 85 | * `from_nano(amount)`\ 86 | `to_nano(amount)`\ 87 | ``Conversion of amounts from TONs to nanoTONs and back\ 88 | 89 | * `init_wallet(key, source='v3r2', workchain_id=0, wallet_id=0, local_password=None) -> Account`\ 90 | ``**key**: (required) Key. May be created using `create_new_key`\ 91 | ``**source**: (str) Wallet version. It is better not to change, because others are not supported by libtonlibjson :(\ 92 | **workchain\_id:** (int, optional) Workchain ID. With this parameter, you can work, for example, in the masterchain by specifying -1 or in the workchain by specifying 0. No one really knows how this parameter will be used in the future, but it is there.\ 93 | **wallet\_id**: (int, optional) Wallet ID. Derivation parameter\ 94 | **local\_password**: (None, str) password for encrypt keys in keystore dir\ 95 | 96 | * `send_boc(message) -> result TLObject`\ 97 | ``Send raw boc messages to liteserver\ 98 | **message**: (bytes) serialized cells\ 99 | 100 | * `set_verbosity_level(level) -> result TLObject`\ 101 | ``Setting the log level from 0 to 5 for the libtonlibjson execution debug\ 102 | **level**: (int) 0-5\ 103 | 104 | * `create_new_key(mnemonic_password='', random_extra_seed=None, local_password=None)`\ 105 | ``The method creates a new private key in the Keystore to initialize the wallet account. In order for all applications to support the created wallet, do not add arguments.\ 106 | 107 | * `export_key(input_key)`\ 108 | ``Export NaCl raw private key for signing messages\ 109 | ``**input\_key**: (InputKeyRegular)\ 110 | **Usage:**\ 111 | ****`from ton.tl.types import InputKeyRegular`\ 112 | ****`client.export_key(InputKeyRegular(wallet.key, local_password=None))` 113 | 114 | -------------------------------------------------------------------------------- /examples/generate_wallet.py: -------------------------------------------------------------------------------- 1 | from ton.sync import TonlibClient 2 | 3 | client = TonlibClient() 4 | TonlibClient.enable_unaudited_binaries() 5 | client.init_tonlib() 6 | 7 | wallet = client.create_wallet() 8 | print('Wallet address:', wallet.address) 9 | print('Seed:', wallet.export()) 10 | -------------------------------------------------------------------------------- /examples/init.py: -------------------------------------------------------------------------------- 1 | from ton import TonlibClient 2 | # OR 3 | # from ton.sync import TonlibClient 4 | 5 | # Initiate module 6 | client = TonlibClient() 7 | TonlibClient.enable_unaudited_binaries() 8 | await client.init_tonlib() -------------------------------------------------------------------------------- /examples/nft.py: -------------------------------------------------------------------------------- 1 | from .init import client 2 | from .wallet import wallet 3 | 4 | await wallet.transfer_nft( 5 | 'EQCEXDQWeqjLP4BehKzzwbuRBsxQHVwEa9j4MGunBs1Vkg_w', # NFT address 6 | 'EQCl1Ug9ZT9ZfGyFH9l4q-bqaUy6kyOzVPmrk7bivmVKJRRZ' # Recipient address 7 | ) 8 | # OR 9 | account = await client.find_account('EQCEXDQWeqjLP4BehKzzwbuRBsxQHVwEa9j4MGunBs1Vkg_w') 10 | body = account.create_transfer_nft_body('EQCl1Ug9ZT9ZfGyFH9l4q-bqaUy6kyOzVPmrk7bivmVKJRRZ') 11 | wallet.transfer(account.address, client.to_nano(0.05), data=body.serialize_boc()) -------------------------------------------------------------------------------- /examples/raw.py: -------------------------------------------------------------------------------- 1 | from .init import client 2 | from tvm_valuetypes import Cell 3 | 4 | # Send raw BOC 5 | await client.send_boc(Cell().serialize_boc()) -------------------------------------------------------------------------------- /examples/transactions.py: -------------------------------------------------------------------------------- 1 | from .init import client 2 | from .wallet import wallet 3 | 4 | # Viewing transactions 5 | txs = await wallet.get_transactions() 6 | in_msg = txs[0].in_msg 7 | in_msg.source.account_address # Sender 8 | in_msg.destination.account_address # Recipient 9 | int(in_msg.amount) # Amount in nanoTONs 10 | client.from_nano(int(in_msg.value)) # Amount in TONs 11 | 12 | # Sending transaction with 1 TON 13 | await wallet.transfer('EQCl1Ug9ZT9ZfGyFH9l4q-bqaUy6kyOzVPmrk7bivmVKJRRZ', client.to_nano(1), comment='test') 14 | 15 | # Send transaction with multiple outputs 16 | await wallet.transfer( 17 | ('EQCl1Ug9ZT9ZfGyFH9l4q-bqaUy6kyOzVPmrk7bivmVKJRRZ', client.to_nano(1), 'test comment'), 18 | ('EQCl1Ug9ZT9ZfGyFH9l4q-bqaUy6kyOzVPmrk7bivmVKJRRZ', client.to_nano(0.5), 'test comment 2'), 19 | ('EQCl1Ug9ZT9ZfGyFH9l4q-bqaUy6kyOzVPmrk7bivmVKJRRZ', client.to_nano(1)) 20 | ) 21 | 22 | # Sending transaction with raw BOC data 23 | from tvm_valuetypes import Cell 24 | await wallet.transfer('EQCl1Ug9ZT9ZfGyFH9l4q-bqaUy6kyOzVPmrk7bivmVKJRRZ', client.to_nano(1), data=Cell().serialize_boc()) 25 | 26 | 27 | # View account 28 | account = await client.find_account('EQCl1Ug9ZT9ZfGyFH9l4q-bqaUy6kyOzVPmrk7bivmVKJRRZ') 29 | # View transactions of an account 30 | txs = await account.get_transactions() # Returns a list of TL Objects (transactions) 31 | # [ 32 | # { 33 | # '@type': 'raw.transaction', # An example of 'in' transaction 34 | # 'data': 'XXXXXX', 35 | # 'fee': '0', 36 | # 'in_msg': { 37 | # '@type': 'raw.message', 38 | # 'body_hash': 'XXXXXX', 39 | # 'created_lt': '28669675000002', 40 | # 'destination': {'@type': 'accountAddress', 41 | # 'account_address': 'XXXXXX'}, 42 | # 'fwd_fee': '666672', 43 | # 'ihr_fee': '0', 44 | # 'msg_data': {'@type': 'msg.dataRaw', 45 | # 'body': 'XXXXXX', 46 | # 'init_state': ''}, 47 | # 'source': {'@type': 'accountAddress', 48 | # 'account_address': 'XXXXXX'}, 49 | # 'value': '1000000000' 50 | # }, 51 | # 'other_fee': '0', 52 | # 'out_msgs': [], # When it is 'in' transaction then there will be an array of msgs like 'in_msg' 53 | # 'storage_fee': '0', 54 | # 'transaction_id': {'@type': 'internal.transactionId', 55 | # 'hash': 'XXXXXX', 56 | # 'lt': '28669675000003'}, 57 | # 'utime': 1654954281 # Timestamp 58 | # } 59 | # ] -------------------------------------------------------------------------------- /examples/wallet.py: -------------------------------------------------------------------------------- 1 | from .init import client 2 | 3 | # Wallet generation 4 | wallet = await client.create_wallet() 5 | 6 | # Get a word list (for tonkeeper, tonhub etc) 7 | seed = await wallet.export() 8 | 9 | # Importing wallet 10 | wallet = await client.import_wallet(seed) 11 | 12 | # Get saved wallet from Keystore 13 | path = wallet.path 14 | wallet = await client.find_wallet(path) 15 | 16 | # Getting an address 17 | wallet.address -------------------------------------------------------------------------------- /infrastructure/tonlib.Dockerfile: -------------------------------------------------------------------------------- 1 | FROM ubuntu:20.04 2 | 3 | RUN apt-get update 4 | RUN DEBIAN_FRONTEND=noninteractive TZ=Etc/UTC apt-get -y install tzdata 5 | 6 | RUN apt install -y build-essential cmake clang-6.0 openssl libssl-dev zlib1g-dev gperf wget git curl libreadline-dev ccache libmicrohttpd-dev 7 | 8 | # build tonlib 9 | WORKDIR / 10 | 11 | # remove /tree/ to build master branch 12 | RUN git clone --recurse-submodules https://github.com/newton-blockchain/ton.git 13 | 14 | # fix lib version and patch logging 15 | WORKDIR /ton 16 | # RUN git checkout 9875f02ef4ceba5b065d5e63c920f91aec73224e 17 | # COPY infrastructure/tonlib.patch /ton/ 18 | # RUN git apply /ton/tonlib.patch 19 | # RUN cat /ton/crypto/smc-envelope/SmartContract.h 20 | 21 | RUN mkdir /ton/build 22 | WORKDIR /ton/build 23 | ENV CC clang-6.0 24 | ENV CXX clang++-6.0 25 | RUN cmake -DCMAKE_BUILD_TYPE=Release .. 26 | RUN cmake --build . -j$(nproc) --target tonlibjson -------------------------------------------------------------------------------- /infrastructure/tonlib.patch: -------------------------------------------------------------------------------- 1 | From b46941e50462b6d74806a9f4a49d6c001f60612c Mon Sep 17 00:00:00 2001 2 | From: Starlight Duck 3 | Date: Thu, 20 Jan 2022 23:20:56 +0200 4 | Subject: [PATCH] tonlib use correct c7: config and address when executing 5 | 6 | --- 7 | crypto/smc-envelope/SmartContract.cpp | 64 ++++++++++++++++++--------- 8 | crypto/smc-envelope/SmartContract.h | 18 ++++++++ 9 | crypto/vm/vm.cpp | 1 + 10 | crypto/vm/vm.h | 4 ++ 11 | tonlib/tonlib/LastConfig.cpp | 4 +- 12 | tonlib/tonlib/TonlibClient.cpp | 45 +++++++++++++------ 13 | tonlib/tonlib/TonlibClient.h | 1 + 14 | 7 files changed, 99 insertions(+), 38 deletions(-) 15 | 16 | diff --git a/crypto/smc-envelope/SmartContract.cpp b/crypto/smc-envelope/SmartContract.cpp 17 | index f3485be9..30030aae 100644 18 | --- a/crypto/smc-envelope/SmartContract.cpp 19 | +++ b/crypto/smc-envelope/SmartContract.cpp 20 | @@ -52,30 +52,51 @@ td::Ref prepare_vm_stack(td::RefInt256 amount, td::Ref 21 | return stack_ref; 22 | } 23 | 24 | -td::Ref prepare_vm_c7(td::uint32 now, td::uint64 balance) { 25 | - // TODO: fix initialization of c7 26 | +td::Ref prepare_vm_c7(SmartContract::Args args) { 27 | td::BitArray<256> rand_seed; 28 | rand_seed.as_slice().fill(0); 29 | td::RefInt256 rand_seed_int{true}; 30 | rand_seed_int.unique_write().import_bits(rand_seed.cbits(), 256, false); 31 | + 32 | + td::uint32 now = 0; 33 | + if (args.now) { 34 | + now = args.now.unwrap(); 35 | + } 36 | + 37 | + vm::CellBuilder cb; 38 | + if (args.address) { 39 | + td::BigInt256 dest_addr; 40 | + dest_addr.import_bits((*args.address).addr.as_bitslice()); 41 | + cb.store_ones(1) 42 | + .store_zeroes(2) 43 | + .store_long((*args.address).workchain, 8) 44 | + .store_int256(dest_addr, 256); 45 | + } 46 | + auto address = cb.finalize(); 47 | + auto config = td::Ref(); 48 | + 49 | + if (args.config) { 50 | + config = (*args.config)->get_root_cell(); 51 | + } 52 | + 53 | auto tuple = vm::make_tuple_ref( 54 | td::make_refint(0x076ef1ea), // [ magic:0x076ef1ea 55 | td::make_refint(0), // actions:Integer 56 | td::make_refint(0), // msgs_sent:Integer 57 | td::make_refint(now), // unixtime:Integer 58 | - td::make_refint(0), // block_lt:Integer 59 | - td::make_refint(0), // trans_lt:Integer 60 | + td::make_refint(0), //TODO: // block_lt:Integer 61 | + td::make_refint(0), //TODO: // trans_lt:Integer 62 | std::move(rand_seed_int), // rand_seed:Integer 63 | - block::CurrencyCollection(balance).as_vm_tuple(), // balance_remaining:[Integer (Maybe Cell)] 64 | - vm::load_cell_slice_ref(vm::CellBuilder().finalize()) // myself:MsgAddressInt 65 | - //vm::StackEntry::maybe(td::Ref()) 66 | + block::CurrencyCollection(args.balance).as_vm_tuple(), // balance_remaining:[Integer (Maybe Cell)] 67 | + vm::load_cell_slice_ref(address), // myself:MsgAddressInt 68 | + vm::StackEntry::maybe(config) //vm::StackEntry::maybe(td::Ref()) 69 | ); // global_config:(Maybe Cell) ] = SmartContractInfo; 70 | //LOG(DEBUG) << "SmartContractInfo initialized with " << vm::StackEntry(tuple).to_string(); 71 | return vm::make_tuple_ref(std::move(tuple)); 72 | } 73 | 74 | SmartContract::Answer run_smartcont(SmartContract::State state, td::Ref stack, td::Ref c7, 75 | - vm::GasLimits gas, bool ignore_chksig) { 76 | + vm::GasLimits gas, bool ignore_chksig, td::Ref libraries) { 77 | auto gas_credit = gas.gas_credit; 78 | vm::init_op_cp0(); 79 | vm::DictionaryBase::get_empty_dictionary(); 80 | @@ -108,11 +129,14 @@ SmartContract::Answer run_smartcont(SmartContract::State state, td::Ref SmartContract::get_init_state() const { 115 | } 116 | 117 | SmartContract::Answer SmartContract::run_method(Args args) { 118 | - td::uint32 now = 0; 119 | - if (args.now) { 120 | - now = args.now.unwrap(); 121 | - } 122 | if (!args.c7) { 123 | - args.c7 = prepare_vm_c7(now, args.balance); 124 | + args.c7 = prepare_vm_c7(args); 125 | } 126 | if (!args.limits) { 127 | bool is_internal = args.get_method_id().ok() == 0; 128 | @@ -193,18 +217,15 @@ SmartContract::Answer SmartContract::run_method(Args args) { 129 | CHECK(args.method_id); 130 | args.stack.value().write().push_smallint(args.method_id.unwrap()); 131 | auto res = 132 | - run_smartcont(get_state(), args.stack.unwrap(), args.c7.unwrap(), args.limits.unwrap(), args.ignore_chksig); 133 | + run_smartcont(get_state(), args.stack.unwrap(), args.c7.unwrap(), args.limits.unwrap(), args.ignore_chksig, 134 | + args.libraries ? args.libraries.unwrap().get_root_cell() : td::Ref{}); 135 | state_ = res.new_state; 136 | return res; 137 | } 138 | 139 | SmartContract::Answer SmartContract::run_get_method(Args args) const { 140 | - td::uint32 now = 0; 141 | - if (args.now) { 142 | - now = args.now.unwrap(); 143 | - } 144 | if (!args.c7) { 145 | - args.c7 = prepare_vm_c7(now, args.balance); 146 | + args.c7 = prepare_vm_c7(args); 147 | } 148 | if (!args.limits) { 149 | args.limits = vm::GasLimits{1000000}; 150 | @@ -214,7 +235,8 @@ SmartContract::Answer SmartContract::run_get_method(Args args) const { 151 | } 152 | CHECK(args.method_id); 153 | args.stack.value().write().push_smallint(args.method_id.unwrap()); 154 | - return run_smartcont(get_state(), args.stack.unwrap(), args.c7.unwrap(), args.limits.unwrap(), args.ignore_chksig); 155 | + return run_smartcont(get_state(), args.stack.unwrap(), args.c7.unwrap(), args.limits.unwrap(), args.ignore_chksig, 156 | + args.libraries ? args.libraries.unwrap().get_root_cell() : td::Ref{}); 157 | } 158 | 159 | SmartContract::Answer SmartContract::run_get_method(td::Slice method, Args args) const { 160 | diff --git a/crypto/smc-envelope/SmartContract.h b/crypto/smc-envelope/SmartContract.h 161 | index b26f4885..95dd2e84 100644 162 | --- a/crypto/smc-envelope/SmartContract.h 163 | +++ b/crypto/smc-envelope/SmartContract.h 164 | @@ -26,6 +26,7 @@ 165 | #include "td/utils/crypto.h" 166 | 167 | #include "block/block.h" 168 | +#include "block/mc-config.h" 169 | 170 | namespace ton { 171 | class SmartContract : public td::CntObject { 172 | @@ -48,6 +49,7 @@ class SmartContract : public td::CntObject { 173 | td::Ref actions; 174 | td::int32 code; 175 | td::int64 gas_used; 176 | + td::ConstBitPtr missing_library{0}; 177 | static int output_actions_count(td::Ref list); 178 | }; 179 | 180 | @@ -61,6 +63,10 @@ class SmartContract : public td::CntObject { 181 | td::uint64 amount{0}; 182 | td::uint64 balance{0}; 183 | 184 | + td::optional address; 185 | + td::optional> config; 186 | + td::optional libraries; 187 | + 188 | Args() { 189 | } 190 | Args(std::initializer_list stack) 191 | @@ -106,6 +112,18 @@ class SmartContract : public td::CntObject { 192 | this->balance = balance; 193 | return std::move(*this); 194 | } 195 | + Args&& set_address(block::StdAddress address) { 196 | + this->address = address; 197 | + return std::move(*this); 198 | + } 199 | + Args&& set_config(std::shared_ptr& config) { 200 | + this->config = config; 201 | + return std::move(*this); 202 | + } 203 | + Args&& set_libraries(vm::Dictionary libraries) { 204 | + this->libraries = libraries; 205 | + return std::move(*this); 206 | + } 207 | 208 | td::Result get_method_id() const { 209 | if (!method_id) { 210 | diff --git a/crypto/vm/vm.cpp b/crypto/vm/vm.cpp 211 | index 038640a5..fb2f4d1e 100644 212 | --- a/crypto/vm/vm.cpp 213 | +++ b/crypto/vm/vm.cpp 214 | @@ -604,6 +604,7 @@ Ref VmState::load_library(td::ConstBitPtr hash) { 215 | return lib; 216 | } 217 | } 218 | + missing_library = hash; 219 | return {}; 220 | } 221 | 222 | diff --git a/crypto/vm/vm.h b/crypto/vm/vm.h 223 | index 0fef9642..44aa7f23 100644 224 | --- a/crypto/vm/vm.h 225 | +++ b/crypto/vm/vm.h 226 | @@ -96,6 +96,7 @@ class VmState final : public VmStateInterface { 227 | td::int64 loaded_cells_count{0}; 228 | int stack_trace{0}, debug_off{0}; 229 | bool chksig_always_succeed{false}; 230 | + td::ConstBitPtr missing_library{0}; 231 | 232 | public: 233 | enum { 234 | @@ -321,6 +322,9 @@ class VmState final : public VmStateInterface { 235 | Ref ref_to_cont(Ref cell) const { 236 | return td::make_ref(load_cell_slice_ref(std::move(cell)), get_cp()); 237 | } 238 | + td::ConstBitPtr get_missing_library() const { 239 | + return missing_library; 240 | + } 241 | 242 | private: 243 | void init_cregs(bool same_c3 = false, bool push_0 = true); 244 | diff --git a/tonlib/tonlib/LastConfig.cpp b/tonlib/tonlib/LastConfig.cpp 245 | index d07482ce..0111095b 100644 246 | --- a/tonlib/tonlib/LastConfig.cpp 247 | +++ b/tonlib/tonlib/LastConfig.cpp 248 | @@ -62,9 +62,7 @@ void LastConfig::with_last_block(td::Result r_last_block) { 249 | } 250 | 251 | auto last_block = r_last_block.move_as_ok(); 252 | - auto params = params_; 253 | - client_.send_query(ton::lite_api::liteServer_getConfigParams(0, create_tl_lite_block_id(last_block.last_block_id), 254 | - std::move(params)), 255 | + client_.send_query(ton::lite_api::liteServer_getConfigAll(0, create_tl_lite_block_id(last_block.last_block_id)), 256 | [this](auto r_config) { this->on_config(std::move(r_config)); }); 257 | } 258 | 259 | diff --git a/tonlib/tonlib/TonlibClient.cpp b/tonlib/tonlib/TonlibClient.cpp 260 | index 1ac3a0d7..422dae12 100644 261 | --- a/tonlib/tonlib/TonlibClient.cpp 262 | +++ b/tonlib/tonlib/TonlibClient.cpp 263 | @@ -861,13 +861,13 @@ class Query { 264 | } 265 | return res; 266 | } 267 | - td::Result>> estimate_fees(bool ignore_chksig, const block::Config& cfg) { 268 | + td::Result>> estimate_fees(bool ignore_chksig, std::shared_ptr& cfg, vm::Dictionary& libraries) { 269 | // gas fees 270 | bool is_masterchain = raw_.source->get_address().workchain == ton::masterchainId; 271 | - TRY_RESULT(gas_limits_prices, cfg.get_gas_limits_prices(is_masterchain)); 272 | - TRY_RESULT(storage_prices, cfg.get_storage_prices()); 273 | - TRY_RESULT(masterchain_msg_prices, cfg.get_msg_prices(true)); 274 | - TRY_RESULT(basechain_msg_prices, cfg.get_msg_prices(false)); 275 | + TRY_RESULT(gas_limits_prices, cfg->get_gas_limits_prices(is_masterchain)); 276 | + TRY_RESULT(storage_prices, cfg->get_storage_prices()); 277 | + TRY_RESULT(masterchain_msg_prices, cfg->get_msg_prices(true)); 278 | + TRY_RESULT(basechain_msg_prices, cfg->get_msg_prices(false)); 279 | block::MsgPrices* msg_prices[2] = {&basechain_msg_prices, &masterchain_msg_prices}; 280 | auto storage_fee_256 = block::StoragePrices::compute_storage_fees( 281 | raw_.source->get_sync_time(), storage_prices, raw_.source->raw().storage_stat, 282 | @@ -888,7 +888,9 @@ class Query { 283 | .set_limits(gas_limits) 284 | .set_balance(raw_.source->get_balance()) 285 | .set_now(raw_.source->get_sync_time()) 286 | - .set_ignore_chksig(ignore_chksig)); 287 | + .set_ignore_chksig(ignore_chksig) 288 | + .set_address(raw_.source->get_address()) 289 | + .set_config(cfg).set_libraries(libraries)); 290 | td::int64 fwd_fee = 0; 291 | if (res.success) { 292 | LOG(DEBUG) << "output actions:\n" 293 | @@ -910,7 +912,7 @@ class Query { 294 | 295 | for (auto& destination : raw_.destinations) { 296 | bool dest_is_masterchain = destination && destination->get_address().workchain == ton::masterchainId; 297 | - TRY_RESULT(dest_gas_limits_prices, cfg.get_gas_limits_prices(dest_is_masterchain)); 298 | + TRY_RESULT(dest_gas_limits_prices, cfg->get_gas_limits_prices(dest_is_masterchain)); 299 | auto dest_storage_fee_256 = 300 | destination ? block::StoragePrices::compute_storage_fees( 301 | destination->get_sync_time(), storage_prices, destination->raw().storage_stat, 302 | @@ -3266,7 +3268,7 @@ void TonlibClient::query_estimate_fees(td::int64 id, bool ignore_chksig, td::Res 303 | return; 304 | } 305 | TRY_RESULT_PROMISE(promise, state, std::move(r_state)); 306 | - TRY_RESULT_PROMISE_PREFIX(promise, fees, TRY_VM(it->second->estimate_fees(ignore_chksig, *state.config)), 307 | + TRY_RESULT_PROMISE_PREFIX(promise, fees, TRY_VM(it->second->estimate_fees(ignore_chksig, state.config, libraries)), 308 | TonlibError::Internal()); 309 | promise.set_value(tonlib_api::make_object( 310 | fees.first.to_tonlib_api(), td::transform(fees.second, [](auto& x) { return x.to_tonlib_api(); }))); 311 | @@ -3493,14 +3495,29 @@ td::Status TonlibClient::do_request(const tonlib_api::smc_runGetMethod& request, 312 | args.set_stack(std::move(stack)); 313 | args.set_balance(it->second->get_balance()); 314 | args.set_now(it->second->get_sync_time()); 315 | - auto res = smc->run_get_method(std::move(args)); 316 | + args.set_address(it->second->get_address()); 317 | 318 | - // smc.runResult gas_used:int53 stack:vector exit_code:int32 = smc.RunResult; 319 | - std::vector> res_stack; 320 | - for (auto& entry : res.stack->as_span()) { 321 | - res_stack.push_back(to_tonlib_api(entry)); 322 | + auto code = smc->get_state().code; 323 | + if (code.not_null()) { 324 | + std::vector libraryList; 325 | } 326 | - promise.set_value(tonlib_api::make_object(res.gas_used, std::move(res_stack), res.code)); 327 | + 328 | + args.set_libraries(libraries); 329 | + 330 | + client_.with_last_config([smc=std::move(smc), args=std::move(args), 331 | + promise = std::move(promise)](td::Result r_state) mutable { 332 | + TRY_RESULT_PROMISE(promise, state, std::move(r_state)); 333 | + args.set_config(state.config); 334 | + 335 | + auto res = smc->run_get_method(std::move(args)); 336 | + 337 | + // smc.runResult gas_used:int53 stack:vector exit_code:int32 = smc.RunResult; 338 | + std::vector> res_stack; 339 | + for (auto& entry : res.stack->as_span()) { 340 | + res_stack.push_back(to_tonlib_api(entry)); 341 | + } 342 | + promise.set_value(tonlib_api::make_object(res.gas_used, std::move(res_stack), res.code)); 343 | + }); 344 | return td::Status::OK(); 345 | } 346 | 347 | diff --git a/tonlib/tonlib/TonlibClient.h b/tonlib/tonlib/TonlibClient.h 348 | index bedc1d49..4dfbe8c6 100644 349 | --- a/tonlib/tonlib/TonlibClient.h 350 | +++ b/tonlib/tonlib/TonlibClient.h 351 | @@ -107,6 +107,7 @@ class TonlibClient : public td::actor::Actor { 352 | td::optional block_id; 353 | }; 354 | QueryContext query_context_; 355 | + vm::Dictionary libraries{256}; 356 | 357 | // network 358 | td::actor::ActorOwn raw_client_; 359 | -------------------------------------------------------------------------------- /makefile: -------------------------------------------------------------------------------- 1 | VENV ?= .venv 2 | PYTHON_VERSION ?= 3.10 3 | 4 | init: 5 | python$(PYTHON_VERSION) -m venv $(VENV) 6 | $(VENV)/bin/python -m pip install --upgrade pip 7 | $(VENV)/bin/python -m pip install poetry 8 | $(VENV)/bin/poetry install 9 | 10 | publish: 11 | $(VENV)/bin/poetry config pypi-token.pypi $(PYPI_TOKEN) 12 | $(VENV)/bin/poetry publish --build 13 | -------------------------------------------------------------------------------- /poetry.lock: -------------------------------------------------------------------------------- 1 | [[package]] 2 | name = "certifi" 3 | version = "2022.9.24" 4 | description = "Python package for providing Mozilla's CA Bundle." 5 | category = "main" 6 | optional = false 7 | python-versions = ">=3.6" 8 | 9 | [[package]] 10 | name = "cffi" 11 | version = "1.15.1" 12 | description = "Foreign Function Interface for Python calling C code." 13 | category = "main" 14 | optional = false 15 | python-versions = "*" 16 | 17 | [package.dependencies] 18 | pycparser = "*" 19 | 20 | [[package]] 21 | name = "charset-normalizer" 22 | version = "2.1.1" 23 | description = "The Real First Universal Charset Detector. Open, modern and actively maintained alternative to Chardet." 24 | category = "main" 25 | optional = false 26 | python-versions = ">=3.6.0" 27 | 28 | [package.extras] 29 | unicode-backport = ["unicodedata2"] 30 | 31 | [[package]] 32 | name = "crc16" 33 | version = "0.1.1" 34 | description = "Library for calculating CRC16" 35 | category = "main" 36 | optional = false 37 | python-versions = "*" 38 | 39 | [[package]] 40 | name = "idna" 41 | version = "3.4" 42 | description = "Internationalized Domain Names in Applications (IDNA)" 43 | category = "main" 44 | optional = false 45 | python-versions = ">=3.5" 46 | 47 | [[package]] 48 | name = "pycparser" 49 | version = "2.21" 50 | description = "C parser in Python" 51 | category = "main" 52 | optional = false 53 | python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" 54 | 55 | [[package]] 56 | name = "pynacl" 57 | version = "1.5.0" 58 | description = "Python binding to the Networking and Cryptography (NaCl) library" 59 | category = "main" 60 | optional = false 61 | python-versions = ">=3.6" 62 | 63 | [package.dependencies] 64 | cffi = ">=1.4.1" 65 | 66 | [package.extras] 67 | docs = ["sphinx (>=1.6.5)", "sphinx_rtd_theme"] 68 | tests = ["hypothesis (>=3.27.0)", "pytest (>=3.2.1,!=3.3.0)"] 69 | 70 | [[package]] 71 | name = "requests" 72 | version = "2.28.1" 73 | description = "Python HTTP for Humans." 74 | category = "main" 75 | optional = false 76 | python-versions = ">=3.7, <4" 77 | 78 | [package.dependencies] 79 | certifi = ">=2017.4.17" 80 | charset-normalizer = ">=2,<3" 81 | idna = ">=2.5,<4" 82 | urllib3 = ">=1.21.1,<1.27" 83 | 84 | [package.extras] 85 | socks = ["PySocks (>=1.5.6,!=1.5.7)"] 86 | use-chardet-on-py3 = ["chardet (>=3.0.2,<6)"] 87 | 88 | [[package]] 89 | name = "tonsdk" 90 | version = "1.0.9" 91 | description = "Python SDK for TON" 92 | category = "main" 93 | optional = false 94 | python-versions = ">=3.7" 95 | 96 | [package.dependencies] 97 | pynacl = ">=1.4.0" 98 | 99 | [[package]] 100 | name = "urllib3" 101 | version = "1.26.12" 102 | description = "HTTP library with thread-safe connection pooling, file post, and more." 103 | category = "main" 104 | optional = false 105 | python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, !=3.5.*, <4" 106 | 107 | [package.extras] 108 | brotli = ["brotli (>=1.0.9)", "brotlicffi (>=0.8.0)", "brotlipy (>=0.6.0)"] 109 | secure = ["certifi", "cryptography (>=1.3.4)", "idna (>=2.0.0)", "ipaddress", "pyOpenSSL (>=0.14)", "urllib3-secure-extra"] 110 | socks = ["PySocks (>=1.5.6,!=1.5.7,<2.0)"] 111 | 112 | [metadata] 113 | lock-version = "1.1" 114 | python-versions = ">=3.7,<4.0" 115 | content-hash = "568b3e7dd1e9c8dacc61fdc3f4d08f27d81c0677489c2ae7e898860c569561a4" 116 | 117 | [metadata.files] 118 | certifi = [ 119 | {file = "certifi-2022.9.24-py3-none-any.whl", hash = "sha256:90c1a32f1d68f940488354e36370f6cca89f0f106db09518524c88d6ed83f382"}, 120 | {file = "certifi-2022.9.24.tar.gz", hash = "sha256:0d9c601124e5a6ba9712dbc60d9c53c21e34f5f641fe83002317394311bdce14"}, 121 | ] 122 | cffi = [ 123 | {file = "cffi-1.15.1-cp27-cp27m-macosx_10_9_x86_64.whl", hash = "sha256:a66d3508133af6e8548451b25058d5812812ec3798c886bf38ed24a98216fab2"}, 124 | {file = "cffi-1.15.1-cp27-cp27m-manylinux1_i686.whl", hash = "sha256:470c103ae716238bbe698d67ad020e1db9d9dba34fa5a899b5e21577e6d52ed2"}, 125 | {file = "cffi-1.15.1-cp27-cp27m-manylinux1_x86_64.whl", hash = "sha256:9ad5db27f9cabae298d151c85cf2bad1d359a1b9c686a275df03385758e2f914"}, 126 | {file = "cffi-1.15.1-cp27-cp27m-win32.whl", hash = "sha256:b3bbeb01c2b273cca1e1e0c5df57f12dce9a4dd331b4fa1635b8bec26350bde3"}, 127 | {file = "cffi-1.15.1-cp27-cp27m-win_amd64.whl", hash = "sha256:e00b098126fd45523dd056d2efba6c5a63b71ffe9f2bbe1a4fe1716e1d0c331e"}, 128 | {file = "cffi-1.15.1-cp27-cp27mu-manylinux1_i686.whl", hash = "sha256:d61f4695e6c866a23a21acab0509af1cdfd2c013cf256bbf5b6b5e2695827162"}, 129 | {file = "cffi-1.15.1-cp27-cp27mu-manylinux1_x86_64.whl", hash = "sha256:ed9cb427ba5504c1dc15ede7d516b84757c3e3d7868ccc85121d9310d27eed0b"}, 130 | {file = "cffi-1.15.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:39d39875251ca8f612b6f33e6b1195af86d1b3e60086068be9cc053aa4376e21"}, 131 | {file = "cffi-1.15.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:285d29981935eb726a4399badae8f0ffdff4f5050eaa6d0cfc3f64b857b77185"}, 132 | {file = "cffi-1.15.1-cp310-cp310-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3eb6971dcff08619f8d91607cfc726518b6fa2a9eba42856be181c6d0d9515fd"}, 133 | {file = "cffi-1.15.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:21157295583fe8943475029ed5abdcf71eb3911894724e360acff1d61c1d54bc"}, 134 | {file = "cffi-1.15.1-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5635bd9cb9731e6d4a1132a498dd34f764034a8ce60cef4f5319c0541159392f"}, 135 | {file = "cffi-1.15.1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2012c72d854c2d03e45d06ae57f40d78e5770d252f195b93f581acf3ba44496e"}, 136 | {file = "cffi-1.15.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dd86c085fae2efd48ac91dd7ccffcfc0571387fe1193d33b6394db7ef31fe2a4"}, 137 | {file = "cffi-1.15.1-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:fa6693661a4c91757f4412306191b6dc88c1703f780c8234035eac011922bc01"}, 138 | {file = "cffi-1.15.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:59c0b02d0a6c384d453fece7566d1c7e6b7bae4fc5874ef2ef46d56776d61c9e"}, 139 | {file = "cffi-1.15.1-cp310-cp310-win32.whl", hash = "sha256:cba9d6b9a7d64d4bd46167096fc9d2f835e25d7e4c121fb2ddfc6528fb0413b2"}, 140 | {file = "cffi-1.15.1-cp310-cp310-win_amd64.whl", hash = "sha256:ce4bcc037df4fc5e3d184794f27bdaab018943698f4ca31630bc7f84a7b69c6d"}, 141 | {file = "cffi-1.15.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:3d08afd128ddaa624a48cf2b859afef385b720bb4b43df214f85616922e6a5ac"}, 142 | {file = "cffi-1.15.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:3799aecf2e17cf585d977b780ce79ff0dc9b78d799fc694221ce814c2c19db83"}, 143 | {file = "cffi-1.15.1-cp311-cp311-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a591fe9e525846e4d154205572a029f653ada1a78b93697f3b5a8f1f2bc055b9"}, 144 | {file = "cffi-1.15.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3548db281cd7d2561c9ad9984681c95f7b0e38881201e157833a2342c30d5e8c"}, 145 | {file = "cffi-1.15.1-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:91fc98adde3d7881af9b59ed0294046f3806221863722ba7d8d120c575314325"}, 146 | {file = "cffi-1.15.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:94411f22c3985acaec6f83c6df553f2dbe17b698cc7f8ae751ff2237d96b9e3c"}, 147 | {file = "cffi-1.15.1-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:03425bdae262c76aad70202debd780501fabeaca237cdfddc008987c0e0f59ef"}, 148 | {file = "cffi-1.15.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:cc4d65aeeaa04136a12677d3dd0b1c0c94dc43abac5860ab33cceb42b801c1e8"}, 149 | {file = "cffi-1.15.1-cp311-cp311-win32.whl", hash = "sha256:a0f100c8912c114ff53e1202d0078b425bee3649ae34d7b070e9697f93c5d52d"}, 150 | {file = "cffi-1.15.1-cp311-cp311-win_amd64.whl", hash = "sha256:04ed324bda3cda42b9b695d51bb7d54b680b9719cfab04227cdd1e04e5de3104"}, 151 | {file = "cffi-1.15.1-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:50a74364d85fd319352182ef59c5c790484a336f6db772c1a9231f1c3ed0cbd7"}, 152 | {file = "cffi-1.15.1-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e263d77ee3dd201c3a142934a086a4450861778baaeeb45db4591ef65550b0a6"}, 153 | {file = "cffi-1.15.1-cp36-cp36m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:cec7d9412a9102bdc577382c3929b337320c4c4c4849f2c5cdd14d7368c5562d"}, 154 | {file = "cffi-1.15.1-cp36-cp36m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:4289fc34b2f5316fbb762d75362931e351941fa95fa18789191b33fc4cf9504a"}, 155 | {file = "cffi-1.15.1-cp36-cp36m-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:173379135477dc8cac4bc58f45db08ab45d228b3363adb7af79436135d028405"}, 156 | {file = "cffi-1.15.1-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:6975a3fac6bc83c4a65c9f9fcab9e47019a11d3d2cf7f3c0d03431bf145a941e"}, 157 | {file = "cffi-1.15.1-cp36-cp36m-win32.whl", hash = "sha256:2470043b93ff09bf8fb1d46d1cb756ce6132c54826661a32d4e4d132e1977adf"}, 158 | {file = "cffi-1.15.1-cp36-cp36m-win_amd64.whl", hash = "sha256:30d78fbc8ebf9c92c9b7823ee18eb92f2e6ef79b45ac84db507f52fbe3ec4497"}, 159 | {file = "cffi-1.15.1-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:198caafb44239b60e252492445da556afafc7d1e3ab7a1fb3f0584ef6d742375"}, 160 | {file = "cffi-1.15.1-cp37-cp37m-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:5ef34d190326c3b1f822a5b7a45f6c4535e2f47ed06fec77d3d799c450b2651e"}, 161 | {file = "cffi-1.15.1-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8102eaf27e1e448db915d08afa8b41d6c7ca7a04b7d73af6514df10a3e74bd82"}, 162 | {file = "cffi-1.15.1-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5df2768244d19ab7f60546d0c7c63ce1581f7af8b5de3eb3004b9b6fc8a9f84b"}, 163 | {file = "cffi-1.15.1-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a8c4917bd7ad33e8eb21e9a5bbba979b49d9a97acb3a803092cbc1133e20343c"}, 164 | {file = "cffi-1.15.1-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0e2642fe3142e4cc4af0799748233ad6da94c62a8bec3a6648bf8ee68b1c7426"}, 165 | {file = "cffi-1.15.1-cp37-cp37m-win32.whl", hash = "sha256:e229a521186c75c8ad9490854fd8bbdd9a0c9aa3a524326b55be83b54d4e0ad9"}, 166 | {file = "cffi-1.15.1-cp37-cp37m-win_amd64.whl", hash = "sha256:a0b71b1b8fbf2b96e41c4d990244165e2c9be83d54962a9a1d118fd8657d2045"}, 167 | {file = "cffi-1.15.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:320dab6e7cb2eacdf0e658569d2575c4dad258c0fcc794f46215e1e39f90f2c3"}, 168 | {file = "cffi-1.15.1-cp38-cp38-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1e74c6b51a9ed6589199c787bf5f9875612ca4a8a0785fb2d4a84429badaf22a"}, 169 | {file = "cffi-1.15.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a5c84c68147988265e60416b57fc83425a78058853509c1b0629c180094904a5"}, 170 | {file = "cffi-1.15.1-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3b926aa83d1edb5aa5b427b4053dc420ec295a08e40911296b9eb1b6170f6cca"}, 171 | {file = "cffi-1.15.1-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:87c450779d0914f2861b8526e035c5e6da0a3199d8f1add1a665e1cbc6fc6d02"}, 172 | {file = "cffi-1.15.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4f2c9f67e9821cad2e5f480bc8d83b8742896f1242dba247911072d4fa94c192"}, 173 | {file = "cffi-1.15.1-cp38-cp38-win32.whl", hash = "sha256:8b7ee99e510d7b66cdb6c593f21c043c248537a32e0bedf02e01e9553a172314"}, 174 | {file = "cffi-1.15.1-cp38-cp38-win_amd64.whl", hash = "sha256:00a9ed42e88df81ffae7a8ab6d9356b371399b91dbdf0c3cb1e84c03a13aceb5"}, 175 | {file = "cffi-1.15.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:54a2db7b78338edd780e7ef7f9f6c442500fb0d41a5a4ea24fff1c929d5af585"}, 176 | {file = "cffi-1.15.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:fcd131dd944808b5bdb38e6f5b53013c5aa4f334c5cad0c72742f6eba4b73db0"}, 177 | {file = "cffi-1.15.1-cp39-cp39-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7473e861101c9e72452f9bf8acb984947aa1661a7704553a9f6e4baa5ba64415"}, 178 | {file = "cffi-1.15.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6c9a799e985904922a4d207a94eae35c78ebae90e128f0c4e521ce339396be9d"}, 179 | {file = "cffi-1.15.1-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3bcde07039e586f91b45c88f8583ea7cf7a0770df3a1649627bf598332cb6984"}, 180 | {file = "cffi-1.15.1-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:33ab79603146aace82c2427da5ca6e58f2b3f2fb5da893ceac0c42218a40be35"}, 181 | {file = "cffi-1.15.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5d598b938678ebf3c67377cdd45e09d431369c3b1a5b331058c338e201f12b27"}, 182 | {file = "cffi-1.15.1-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:db0fbb9c62743ce59a9ff687eb5f4afbe77e5e8403d6697f7446e5f609976f76"}, 183 | {file = "cffi-1.15.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:98d85c6a2bef81588d9227dde12db8a7f47f639f4a17c9ae08e773aa9c697bf3"}, 184 | {file = "cffi-1.15.1-cp39-cp39-win32.whl", hash = "sha256:40f4774f5a9d4f5e344f31a32b5096977b5d48560c5592e2f3d2c4374bd543ee"}, 185 | {file = "cffi-1.15.1-cp39-cp39-win_amd64.whl", hash = "sha256:70df4e3b545a17496c9b3f41f5115e69a4f2e77e94e1d2a8e1070bc0c38c8a3c"}, 186 | {file = "cffi-1.15.1.tar.gz", hash = "sha256:d400bfb9a37b1351253cb402671cea7e89bdecc294e8016a707f6d1d8ac934f9"}, 187 | ] 188 | charset-normalizer = [ 189 | {file = "charset-normalizer-2.1.1.tar.gz", hash = "sha256:5a3d016c7c547f69d6f81fb0db9449ce888b418b5b9952cc5e6e66843e9dd845"}, 190 | {file = "charset_normalizer-2.1.1-py3-none-any.whl", hash = "sha256:83e9a75d1911279afd89352c68b45348559d1fc0506b054b346651b5e7fee29f"}, 191 | ] 192 | crc16 = [ 193 | {file = "crc16-0.1.1.tar.gz", hash = "sha256:c1f86aa0390f4baf07d2631b16b979580eae1d9a973a826ce45353a22ee8d396"}, 194 | {file = "crc16-0.1.1.zip", hash = "sha256:1b9f697a93491ae42ed653c1e78ea25a33532afab87b513e6890975450271a01"}, 195 | ] 196 | idna = [ 197 | {file = "idna-3.4-py3-none-any.whl", hash = "sha256:90b77e79eaa3eba6de819a0c442c0b4ceefc341a7a2ab77d7562bf49f425c5c2"}, 198 | {file = "idna-3.4.tar.gz", hash = "sha256:814f528e8dead7d329833b91c5faa87d60bf71824cd12a7530b5526063d02cb4"}, 199 | ] 200 | pycparser = [ 201 | {file = "pycparser-2.21-py2.py3-none-any.whl", hash = "sha256:8ee45429555515e1f6b185e78100aea234072576aa43ab53aefcae078162fca9"}, 202 | {file = "pycparser-2.21.tar.gz", hash = "sha256:e644fdec12f7872f86c58ff790da456218b10f863970249516d60a5eaca77206"}, 203 | ] 204 | pynacl = [ 205 | {file = "PyNaCl-1.5.0-cp36-abi3-macosx_10_10_universal2.whl", hash = "sha256:401002a4aaa07c9414132aaed7f6836ff98f59277a234704ff66878c2ee4a0d1"}, 206 | {file = "PyNaCl-1.5.0-cp36-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_24_aarch64.whl", hash = "sha256:52cb72a79269189d4e0dc537556f4740f7f0a9ec41c1322598799b0bdad4ef92"}, 207 | {file = "PyNaCl-1.5.0-cp36-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a36d4a9dda1f19ce6e03c9a784a2921a4b726b02e1c736600ca9c22029474394"}, 208 | {file = "PyNaCl-1.5.0-cp36-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_24_x86_64.whl", hash = "sha256:0c84947a22519e013607c9be43706dd42513f9e6ae5d39d3613ca1e142fba44d"}, 209 | {file = "PyNaCl-1.5.0-cp36-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:06b8f6fa7f5de8d5d2f7573fe8c863c051225a27b61e6860fd047b1775807858"}, 210 | {file = "PyNaCl-1.5.0-cp36-abi3-musllinux_1_1_aarch64.whl", hash = "sha256:a422368fc821589c228f4c49438a368831cb5bbc0eab5ebe1d7fac9dded6567b"}, 211 | {file = "PyNaCl-1.5.0-cp36-abi3-musllinux_1_1_x86_64.whl", hash = "sha256:61f642bf2378713e2c2e1de73444a3778e5f0a38be6fee0fe532fe30060282ff"}, 212 | {file = "PyNaCl-1.5.0-cp36-abi3-win32.whl", hash = "sha256:e46dae94e34b085175f8abb3b0aaa7da40767865ac82c928eeb9e57e1ea8a543"}, 213 | {file = "PyNaCl-1.5.0-cp36-abi3-win_amd64.whl", hash = "sha256:20f42270d27e1b6a29f54032090b972d97f0a1b0948cc52392041ef7831fee93"}, 214 | {file = "PyNaCl-1.5.0.tar.gz", hash = "sha256:8ac7448f09ab85811607bdd21ec2464495ac8b7c66d146bf545b0f08fb9220ba"}, 215 | ] 216 | requests = [ 217 | {file = "requests-2.28.1-py3-none-any.whl", hash = "sha256:8fefa2a1a1365bf5520aac41836fbee479da67864514bdb821f31ce07ce65349"}, 218 | {file = "requests-2.28.1.tar.gz", hash = "sha256:7c5599b102feddaa661c826c56ab4fee28bfd17f5abca1ebbe3e7f19d7c97983"}, 219 | ] 220 | tonsdk = [ 221 | {file = "tonsdk-1.0.9-py3-none-any.whl", hash = "sha256:1ad1a5e3994f0ca38d840e6c8e40da1458e7c1b8cde2c3df3f46f9ef7f7d739e"}, 222 | {file = "tonsdk-1.0.9.tar.gz", hash = "sha256:0e0ba36456ca8915a5f7a80e4a618884c2485f5264aa15722736cfe7ea99e26c"}, 223 | ] 224 | urllib3 = [ 225 | {file = "urllib3-1.26.12-py2.py3-none-any.whl", hash = "sha256:b930dd878d5a8afb066a637fbb35144fe7901e3b209d1cd4f524bd0e9deee997"}, 226 | {file = "urllib3-1.26.12.tar.gz", hash = "sha256:3fa96cf423e6987997fc326ae8df396db2a8b7c667747d47ddd8ecba91f4a74e"}, 227 | ] 228 | -------------------------------------------------------------------------------- /pyproject.toml: -------------------------------------------------------------------------------- 1 | [tool.poetry] 2 | name = "ton" 3 | version = "0.26.0" 4 | description = "Python client for The Open Network" 5 | readme = "README.md" 6 | authors = [ 7 | "psylopunk ", 8 | ] 9 | maintainers = [] 10 | repository = "https://github.com/psylopunk/pytonlib" 11 | homepage = "https://pydocs.xton.me" 12 | classifiers = [ 13 | "Programming Language :: Python :: 3", 14 | "License :: OSI Approved :: MIT License", 15 | "Operating System :: OS Independent", 16 | "Development Status :: 4 - Beta", 17 | "Intended Audience :: Developers", 18 | ] 19 | keywords = ["ton", "blockchain", "pytonlib"] 20 | 21 | [tool.setuptools.package-data] 22 | ton = [ 23 | "distlib/linux/*", 24 | "distlib/darwin/*", 25 | "distlib/windows/*", 26 | "distlib/freebsd/*", 27 | ] 28 | 29 | [tool.poetry.dependencies] 30 | python = ">=3.7,<4.0" 31 | crc16 = "^0.1.1" 32 | requests = "^2.28.1" 33 | tonsdk = "^1.0.9" 34 | pynacl = "^1.5.0" 35 | 36 | [build-system] 37 | requires = ["poetry>=1.2.0"] 38 | build-backend = "poetry.core.masonry.api" 39 | -------------------------------------------------------------------------------- /synchronous.md: -------------------------------------------------------------------------------- 1 | # Synchronous 2 | 3 | To use the library without asyncio, we can use similar code: 4 | 5 | ```python 6 | from ton.sync import TonlibClient 7 | client = TonlibClient() 8 | client.init_tonlib() 9 | 10 | # then all methods can be used without await 11 | wallet = client.create_wallet() 12 | ``` 13 | -------------------------------------------------------------------------------- /ton/__init__.py: -------------------------------------------------------------------------------- 1 | from .client import TonlibClient -------------------------------------------------------------------------------- /ton/account/__init__.py: -------------------------------------------------------------------------------- 1 | from .ft_methods import FTMethods 2 | from .nft_methods import NFTMethods 3 | from .smc_methods import SmcMethods 4 | from .state_methods import StateMethods 5 | from .wallet_methods import WalletMethods 6 | from ..errors import InvalidUsage 7 | from ..tl.functions import GetAccountAddress 8 | from ..tl.types import AccountAddress, Raw_InitialAccountState 9 | 10 | 11 | class Account(StateMethods, WalletMethods, SmcMethods, NFTMethods, FTMethods): 12 | def __repr__(self): 13 | return f"Account<{self.account_address.account_address}>" 14 | 15 | def __init__(self, address, **kwargs): 16 | if isinstance(address, AccountAddress): 17 | self.account_address = self.account_address 18 | elif isinstance(address, str): 19 | self.account_address = AccountAddress(address) 20 | else: 21 | raise InvalidUsage('Specify the account address') 22 | 23 | self.__dict__.update(kwargs) 24 | 25 | self.state = None 26 | self.smc_id = None 27 | 28 | @property 29 | def address(self): 30 | return self.account_address.account_address 31 | 32 | @classmethod 33 | async def from_state(cls, client, state: Raw_InitialAccountState, workchain_id=0): 34 | account_address = await client.execute(GetAccountAddress(state, workchain_id=workchain_id)) 35 | return cls(account_address.account_address, state=state, workchain_id=workchain_id) 36 | -------------------------------------------------------------------------------- /ton/account/ft_methods.py: -------------------------------------------------------------------------------- 1 | from base64 import b64decode 2 | 3 | from tonsdk.boc import Cell 4 | from tonsdk.utils import Address 5 | 6 | from ..tl.types import Tvm_StackEntrySlice, Tvm_Slice 7 | from ..utils.cell import read_address 8 | 9 | 10 | class FTMethods: 11 | async def get_jetton_data(self, **kwargs): 12 | response = await self.run_get_method('get_jetton_data', **kwargs) 13 | if response.exit_code != 0: 14 | raise Exception('get_jetton_data exit_code: {}'.format(response.exit_code)) 15 | 16 | return { 17 | 'total_supply': int(response.stack[0].number.number), 18 | 'admin_address': read_address(Cell.one_from_boc(b64decode(response.stack[2].cell.bytes))), 19 | 'content': Cell.one_from_boc(b64decode(response.stack[3].cell.bytes)), 20 | 'jetton_wallet_code': Cell.one_from_boc(b64decode(response.stack[4].cell.bytes)) 21 | } 22 | 23 | async def get_wallet_address(self, owner_address, **kwargs): 24 | addr_slice = Cell() 25 | addr_slice.bits.write_address(Address(owner_address)) 26 | response = await self.run_get_method('get_wallet_address', [ 27 | Tvm_StackEntrySlice(Tvm_Slice(addr_slice.to_boc(False, False))) 28 | ], **kwargs) 29 | if response.exit_code != 0: 30 | raise Exception('get_wallet_address exit_code: {}'.format(response.exit_code)) 31 | 32 | return { 33 | 'address': read_address(Cell.one_from_boc(b64decode(response.stack[0].cell.bytes))) 34 | } 35 | 36 | async def get_wallet_data(self, **kwargs): 37 | response = await self.run_get_method('get_wallet_data', **kwargs) 38 | if response.exit_code != 0: 39 | raise Exception('get_wallet_data exit_code: {}'.format(response.exit_code)) 40 | 41 | return { 42 | 'balance': int(response.stack[0].number.number), 43 | 'owner_address': read_address(Cell.one_from_boc(b64decode(response.stack[1].cell.bytes))), 44 | 'jetton_master_address': read_address(Cell.one_from_boc(b64decode(response.stack[2].cell.bytes))), 45 | 'jetton_wallet_code': Cell.one_from_boc(b64decode(response.stack[3].cell.bytes)) 46 | } 47 | 48 | -------------------------------------------------------------------------------- /ton/account/nft_methods.py: -------------------------------------------------------------------------------- 1 | from base64 import b64decode 2 | 3 | from tonsdk.boc import Cell 4 | from tonsdk.utils import Address 5 | 6 | from ..tl.types import Tvm_StackEntryNumber, Tvm_NumberDecimal 7 | from ..utils.cell import read_address 8 | 9 | 10 | class NFTMethods: 11 | async def get_nft_data(self, **kwargs): 12 | response = await self.run_get_method('get_nft_data', **kwargs) 13 | if response.exit_code != 0: 14 | raise Exception('get_nft_data exit_code: {}'.format(response.exit_code)) 15 | 16 | 17 | return { 18 | 'is_initialized': int(response.stack[0].number.number), 19 | 'index': int(response.stack[1].number.number), 20 | 'collection_address': read_address(Cell.one_from_boc(b64decode(response.stack[2].cell.bytes))), 21 | 'owner_address': read_address(Cell.one_from_boc(b64decode(response.stack[3].cell.bytes))), 22 | 'content': Cell.one_from_boc(b64decode(response.stack[4].cell.bytes)) 23 | } 24 | 25 | 26 | async def get_collection_data(self, **kwargs): 27 | response = await self.run_get_method('get_collection_data', **kwargs) 28 | if response.exit_code != 0: 29 | raise Exception('get_collection_data exit_code: {}'.format(response.exit_code)) 30 | 31 | return { 32 | 'next_item_index': int(response.stack[0].number.number), 33 | 'content': Cell.one_from_boc(b64decode(response.stack[1].cell.bytes)), 34 | 'owner_address': read_address(Cell.one_from_boc(b64decode(response.stack[2].cell.bytes))) 35 | } 36 | 37 | 38 | async def get_nft_address_by_index(self, index, **kwargs): 39 | response = await self.run_get_method( 40 | 'get_nft_address_by_index', [Tvm_StackEntryNumber(Tvm_NumberDecimal(index))], **kwargs) 41 | if response.exit_code != 0: 42 | raise Exception('get_nft_address_by_index exit_code: {}'.format(response.exit_code)) 43 | 44 | return { 45 | 'address': read_address(Cell.one_from_boc(b64decode(response.stack[0].cell.bytes))) 46 | } 47 | 48 | 49 | async def royalty_params(self, **kwargs): 50 | response = await self.run_get_method('royalty_params', **kwargs) 51 | if response.exit_code != 0: 52 | raise Exception('royalty_params exit_code: {}'.format(response.exit_code)) 53 | 54 | return { 55 | 'royalty_factor': int(response.stack[0].number.number), 56 | 'royalty_base': int(response.stack[1].number.number), 57 | 'royalty': int(response.stack[0].number.number) / int(response.stack[1].number.number), 58 | 'royalty_address': read_address(Cell.one_from_boc(b64decode(response.stack[2].cell.bytes))) 59 | } 60 | 61 | 62 | def create_transfer_nft_body( 63 | self, new_owner_address, response_address=None, query_id: int = 0, 64 | forward_amount: int = 0, forward_payload: bytes = None 65 | ) -> Cell: 66 | cell = Cell() 67 | cell.bits.write_uint(0x5fcc3d14, 32) # transfer op-code 68 | cell.bits.write_uint(query_id, 64) 69 | cell.bits.write_address(Address(new_owner_address)) 70 | cell.bits.write_address(Address(response_address or new_owner_address)) 71 | cell.bits.write_uint(0, 1) # null custom_payload 72 | cell.bits.write_grams(forward_amount) 73 | cell.bits.write_uint(0, 1) # forward_payload in this slice, not separate cell 74 | if forward_payload: 75 | cell.bits.write_bytes(forward_payload) 76 | 77 | return cell -------------------------------------------------------------------------------- /ton/account/smc_methods.py: -------------------------------------------------------------------------------- 1 | from typing import Union 2 | 3 | from ..tl.functions import Smc_Load, Smc_RunGetMethod, Raw_CreateAndSendMessage 4 | 5 | 6 | class SmcMethods: 7 | async def load_smc(self, **kwargs): 8 | self.smc_id = (await self.client.tonlib_wrapper.execute( 9 | Smc_Load(self.account_address), **kwargs 10 | )).id 11 | 12 | async def run_get_method(self, method: Union[str, int], stack: list = [], force=False, **kwargs): 13 | if self.smc_id is None or force is True: 14 | await self.load_smc(**kwargs) 15 | 16 | return await self.client.tonlib_wrapper.execute( 17 | Smc_RunGetMethod(self.smc_id, method, stack), **kwargs 18 | ) 19 | 20 | async def send_message(self, body: bytes, **kwargs): 21 | return await self.client.tonlib_wrapper.execute( 22 | Raw_CreateAndSendMessage(self.account_address, body), **kwargs 23 | ) 24 | -------------------------------------------------------------------------------- /ton/account/state_methods.py: -------------------------------------------------------------------------------- 1 | from ..tl.functions import Raw_GetAccountState, Raw_GetTransactions 2 | from ..tl.types import Internal_TransactionId 3 | from ..utils import sha256, contracts 4 | 5 | 6 | class StateMethods: 7 | async def load_state(self, **kwargs): 8 | self.state = await self.client.tonlib_wrapper.execute( 9 | Raw_GetAccountState(self.account_address), **kwargs 10 | ) 11 | 12 | async def get_state(self, force=False, **kwargs): 13 | if self.state is None or force: 14 | await self.load_state(**kwargs) 15 | 16 | return self.state 17 | 18 | async def detect_type(self, **kwargs): 19 | state = await self.get_state(**kwargs) 20 | return contracts.get(sha256(state.code), None) 21 | 22 | async def get_balance(self, **kwargs): 23 | return int( 24 | (await self.get_state(force=True, **kwargs)).balance # nanoTONs 25 | ) 26 | 27 | async def get_transactions(self, from_transaction_lt=None, from_transaction_hash=None, to_transaction_lt=0, limit=10): 28 | if from_transaction_lt is None or from_transaction_hash is None: 29 | state = await self.get_state(force=True) 30 | from_transaction_lt, from_transaction_hash = state.last_transaction_id.lt, state.last_transaction_id.hash 31 | 32 | reach_lt = False 33 | all_transactions = [] 34 | current_tx = Internal_TransactionId( 35 | from_transaction_lt, from_transaction_hash 36 | ) 37 | while not reach_lt and len(all_transactions) < limit: 38 | raw_transactions = await self.client.tonlib_wrapper.execute( 39 | Raw_GetTransactions( 40 | self.account_address, 41 | current_tx 42 | ) 43 | ) 44 | transactions, current_tx = raw_transactions.transactions, raw_transactions.__dict__.get("previous_transaction_id", None) 45 | for tx in transactions: 46 | tlt = int(tx.transaction_id.lt) 47 | if tlt <= to_transaction_lt: 48 | reach_lt = True 49 | break 50 | all_transactions.append(tx) 51 | 52 | if current_tx is None: 53 | break 54 | 55 | if int(current_tx.lt) == 0: 56 | break 57 | 58 | return all_transactions -------------------------------------------------------------------------------- /ton/account/wallet_methods.py: -------------------------------------------------------------------------------- 1 | import json 2 | import logging 3 | from base64 import b64encode, b64decode 4 | from typing import Union 5 | 6 | from nacl.signing import SigningKey 7 | 8 | from ..errors import InvalidUsage 9 | from ..tl.functions import CreateQuery, Query_Send, ExportKey 10 | from ..tl.types import InputKeyRegular, Raw_InitialAccountState, ActionMsg, MsgMessage, MsgDataRaw, MsgDataText 11 | from ..utils.wallet import sources, contracts, sha256 12 | 13 | logger = logging.getLogger('ton') 14 | 15 | 16 | class WalletMethods: 17 | async def build_state(self): 18 | state = await self.get_state() 19 | if state.code: 20 | return {'code': b64decode(state.code), 'data': b64decode(state.data) if state.data else state.data} 21 | 22 | assert hasattr(self, 'source'), 'source must be specified' 23 | assert hasattr(self, 'wallet_id'), 'wallet_id must be specified' 24 | assert hasattr(self, 'key'), 'key must be specified' 25 | return { 26 | 'code': b64decode(sources[self.source]), 27 | 'data': contracts[sha256(sources[self.source])]['data_builder']( 28 | self.wallet_id, SigningKey( 29 | b64decode( 30 | await self.client.export_key( 31 | InputKeyRegular(self.key, local_password=self.__dict__.get('local_password')) 32 | ) 33 | ) 34 | ).verify_key._key 35 | ).to_boc(False) 36 | } 37 | 38 | 39 | async def send_messages(self, messages: list, allow_send_to_uninited=False, timeout: int = 300): 40 | state = await self.build_state() 41 | query = CreateQuery( 42 | InputKeyRegular(self.key, local_password=self.local_password), 43 | Raw_InitialAccountState(state['code'], state['data']), 44 | self.account_address, 45 | ActionMsg( 46 | messages, 47 | allow_send_to_uninited=allow_send_to_uninited 48 | ), 49 | timeout=timeout 50 | ) 51 | query_id = (await self.client.execute(query)).id 52 | result = await self.client.execute(Query_Send(query_id)) 53 | return result 54 | 55 | 56 | async def transfer(self, destination: Union[str, list], amount: int = None, data: bytes = None, comment=None, send_mode: int = 1, **kwargs): 57 | messages = [] 58 | if type(destination) == str: 59 | assert type(amount) == int, 'amount type must be int if destination is str' 60 | messages.append( 61 | MsgMessage( 62 | destination, 63 | amount, 64 | data=MsgDataRaw(data) if not (data is None) else MsgDataText(comment or ''), 65 | public_key=self.key.public_key, 66 | send_mode=send_mode 67 | ) 68 | ) 69 | elif type(destination) == list: 70 | for output in destination: 71 | assert type(output) in [tuple, list], 'output must be tuple or list (address, amount, comment: optional)' 72 | messages.append( 73 | MsgMessage( 74 | output[0], 75 | int(output[1]), 76 | data=(MsgDataRaw(data) if not (data is None) else MsgDataText( 77 | (output[2] if len(output) > 2 else '') if comment is None else comment 78 | )), 79 | public_key=self.key.public_key, 80 | send_mode=send_mode 81 | ) 82 | ) 83 | else: 84 | raise InvalidUsage('destination must be str or list') 85 | 86 | return await self.send_messages(messages, **kwargs) 87 | 88 | 89 | async def transfer_nft( 90 | self, nft_address, new_owner_address, response_address=None, 91 | query_id: int = 0, forward_amount: int = 0, forward_payload: bytes = None 92 | ): 93 | account = await self.client.find_account(nft_address) 94 | body = account.create_transfer_nft_body( 95 | new_owner_address, response_address, query_id, forward_amount, forward_payload 96 | ).to_boc(False) 97 | return await self.transfer(nft_address, self.client.to_nano(0.05), data=body) 98 | 99 | 100 | async def seqno(self, **kwargs): 101 | result = await self.run_get_method('seqno', force=True, **kwargs) 102 | if result.exit_code != 0: 103 | return 0 104 | 105 | return int(result.stack[0].number.number) 106 | 107 | 108 | async def get_public_key(self, **kwargs): 109 | if hasattr(self, 'key'): 110 | return b64decode( 111 | await self.client.export_key(InputKeyRegular(self.key, local_password=self.__dict__.get('local_password'))) 112 | ) 113 | else: 114 | try: 115 | result = await self.run_get_method('get_public_key', **kwargs) 116 | assert result.exit_code == 0, 'get_public_key failed' 117 | return int(result.stack[0].number.number).to_bytes(32, 'big') 118 | except Exception as e: 119 | logger.debug('get_public_key failed: {}'.format(e)) 120 | return None 121 | 122 | 123 | @property 124 | def path(self): 125 | """ 126 | :return: path (str) 127 | """ 128 | 129 | path_obj = dict() 130 | path_obj['pk'] = self.key.public_key 131 | path_obj['sk'] = self.key.secret if hasattr(self.key, 'secret') else None 132 | path_obj['wi'] = self.wallet_id if hasattr(self, 'wallet_id') else None 133 | path_obj['wc'] = self.workchain_id if hasattr(self, 'workchain_id') else None 134 | path_obj['sr'] = self.source if hasattr(self, 'source') else None 135 | return b64encode( 136 | json.dumps(path_obj).encode('utf-8') 137 | ).decode('utf-8') 138 | 139 | 140 | async def export(self): 141 | return ' '.join((await self.client.execute( 142 | ExportKey( 143 | InputKeyRegular(self.key, local_password=self.local_password) 144 | ) 145 | )).word_list) 146 | -------------------------------------------------------------------------------- /ton/client/__init__.py: -------------------------------------------------------------------------------- 1 | import asyncio 2 | import os 3 | from typing import Union 4 | 5 | from .converter_methods import ConverterMethods 6 | from .function_methods import FunctionMethods 7 | from .tonlib_methods import TonlibMethods 8 | from .tonlib_methods import TonlibMethods 9 | from .wallet_methods import WalletMethods 10 | from ..account import Account 11 | from ..tl.types import AccountAddress 12 | 13 | 14 | class TonlibClient(TonlibMethods, WalletMethods, FunctionMethods, ConverterMethods): 15 | # Enable unaudited binaries (off by default) 16 | _use_unaudited_binaries = False 17 | 18 | def __init__( 19 | self, 20 | ls_index=0, 21 | config='https://ton.org/global-config.json', 22 | keystore=None, 23 | workchain_id=0, 24 | verbosity_level=0, 25 | default_timeout=10 26 | ): 27 | if keystore is None: # while memory keystore libtonlibjson bug keep 28 | keystore = '.keystore' 29 | 30 | self.loop = None 31 | self.ls_index = ls_index 32 | self.config = config 33 | self.keystore = keystore 34 | self.workchain_id = workchain_id 35 | self.verbosity_level = verbosity_level 36 | self.default_timeout = default_timeout 37 | 38 | self.queue = [] # legacy 39 | self.lock = asyncio.Lock() 40 | self.locked = False 41 | 42 | async def find_account(self, account_address: Union[AccountAddress, str], preload_state: bool=True, **kwargs): 43 | """ 44 | Getting an Account object by account address 45 | 46 | :param account_address: 47 | :return: Account 48 | """ 49 | 50 | account = Account(account_address, client=self) 51 | if preload_state: 52 | await account.load_state(**kwargs) 53 | 54 | return account 55 | -------------------------------------------------------------------------------- /ton/client/converter_methods.py: -------------------------------------------------------------------------------- 1 | 2 | class ConverterMethods: 3 | def from_nano(self, value) -> float: 4 | return round(int(value) / 10 ** 9, 9) 5 | 6 | def to_nano(self, value) -> int: 7 | return int(float(value) * (10 ** 9)) -------------------------------------------------------------------------------- /ton/client/function_methods.py: -------------------------------------------------------------------------------- 1 | from ..tl.functions import CreateNewKey, SetLogVerbosityLevel, ExportUnencryptedKey, Raw_SendMessage 2 | from ..tl.types import Key, InputKeyRegular 3 | 4 | 5 | class FunctionMethods: 6 | async def set_verbosity_level(self, level): 7 | return await self.tonlib_wrapper.execute( 8 | SetLogVerbosityLevel(level) 9 | ) 10 | 11 | 12 | async def send_boc(self, message: bytes, **kwargs): 13 | """ 14 | Sending a message to the network 15 | 16 | :param message: bytes 17 | :return: 18 | """ 19 | 20 | query = Raw_SendMessage(message) 21 | return await self.tonlib_wrapper.execute(query, **kwargs) 22 | 23 | 24 | async def create_new_key(self, mnemonic_password='', random_extra_seed=None, local_password=None): 25 | """ 26 | Generating a private key from random seeds 27 | 28 | :param mnemonic_password: str 29 | :param random_extra_seed: 30 | :param local_password: local storage encrypt password 31 | :return: Key, mnemonic 32 | """ 33 | 34 | query = CreateNewKey( 35 | mnemonic_password, 36 | random_extra_seed=random_extra_seed, 37 | local_password=local_password 38 | ) 39 | r = await self.tonlib_wrapper.execute(query) 40 | return Key(r.public_key, secret=r.secret) 41 | 42 | async def export_key(self, input_key: InputKeyRegular): 43 | """ 44 | Exporting a signing private key 45 | 46 | :param input_key: 47 | :return: base64 str 48 | """ 49 | 50 | query = ExportUnencryptedKey(input_key) 51 | r = await self.tonlib_wrapper.execute(query) 52 | return r.data -------------------------------------------------------------------------------- /ton/client/tonlib_methods.py: -------------------------------------------------------------------------------- 1 | import asyncio 2 | import json 3 | from copy import deepcopy 4 | from logging import getLogger 5 | from pathlib import Path 6 | 7 | import requests 8 | 9 | from ..tonlibjson import TonLib, get_tonlib_path 10 | 11 | logger = getLogger('ton') 12 | 13 | 14 | class TonlibMethods: 15 | @classmethod 16 | def enable_unaudited_binaries(cls): 17 | """ 18 | Use this flag to enable unaudited libtonlibjson binaries. 19 | """ 20 | cls._use_unaudited_binaries = True 21 | 22 | @property 23 | def local_config(self): 24 | local = deepcopy(self.config) 25 | local['liteservers'] = [local['liteservers'][self.ls_index]] 26 | return local 27 | 28 | async def execute(self, query, timeout=None): 29 | """ 30 | Direct request to liteserver 31 | 32 | :param query: dict or TLObject 33 | :param timeout: 34 | :return: TLObject 35 | """ 36 | result = await self.tonlib_wrapper.execute(query, timeout=(timeout or self.default_timeout)) 37 | if result.type == 'error': 38 | raise Exception(result.message) 39 | 40 | return result 41 | 42 | async def init_tonlib(self, cdll_path=None): 43 | if self.loop is None: 44 | self.loop = asyncio.get_event_loop() 45 | 46 | # Fetching actual network config from spec. url without cache 47 | if type(self.config) == str: 48 | if self.config.find('http://') == 0 or self.config.find('https://') == 0: 49 | self.config = requests.get(self.config).json() 50 | 51 | self.max_parallel_requests = self.config['liteservers'][0].get("max_parallel_requests", 50) 52 | 53 | if cdll_path is None: 54 | if self._use_unaudited_binaries is False: 55 | raise AttributeError( 56 | "For really safe work with TON, you need to compile the TON executable files yourself (read more at https://github.com/psylopunk/ton/issues/7), " 57 | "specifically libtonlibjson, with which pytonlib communicates with the network. " 58 | "If the cdll_path parameter was not passed to the init_tanlib function, " 59 | "it is proposed to use our collection of binaries from the community. " 60 | "There is a small chance of dangerous code getting into them, so we recommend using them ONLY FOR LEARNING PURPOSES. " 61 | "To use third-party binaries, please enable them by running " 62 | "`TonlibClient.enable_unaudited_binaries()` and try again." 63 | ) 64 | 65 | cdll_path = get_tonlib_path() 66 | 67 | wrapper = TonLib(self.loop, self.ls_index, cdll_path, self.verbosity_level, default_timeout=self.default_timeout) 68 | keystore_obj = { 69 | '@type': 'keyStoreTypeDirectory', 70 | 'directory': self.keystore 71 | } 72 | # create keystore 73 | Path(self.keystore).mkdir(parents=True, exist_ok=True) 74 | 75 | request = { 76 | '@type': 'init', 77 | 'options': { 78 | '@type': 'options', 79 | 'config': { 80 | '@type': 'config', 81 | 'config': json.dumps(self.local_config), 82 | 'use_callbacks_for_network': False, 83 | 'blockchain_name': '', 84 | 'ignore_cache': False 85 | }, 86 | 'keystore_type': keystore_obj 87 | } 88 | } 89 | self.tonlib_wrapper = wrapper 90 | 91 | # set confog 92 | r = await self.tonlib_wrapper.execute(request) 93 | self.config_info = r.config_info 94 | 95 | # set semaphore 96 | self.semaphore = asyncio.Semaphore(self.max_parallel_requests) 97 | 98 | logger.info(F"TonLib #{self.ls_index:03d} inited successfully") 99 | await self.set_verbosity_level(self.verbosity_level) 100 | 101 | async def reconnect(self): 102 | """ 103 | Restart libtonlibjson.so in case of failure 104 | 105 | :return: None 106 | """ 107 | 108 | if not self.tonlib_wrapper.shutdown_state: 109 | logger = self.tonlib_wrapper.logger 110 | logger.info(f'Client #{self.ls_index:03d} reconnecting') 111 | self.tonlib_wrapper.shutdown_state = "started" 112 | await self.init_tonlib() 113 | logger.info(f'Client #{self.ls_index:03d} reconnected') 114 | -------------------------------------------------------------------------------- /ton/client/wallet_methods.py: -------------------------------------------------------------------------------- 1 | import json 2 | from base64 import b64decode 3 | 4 | from nacl.signing import SigningKey 5 | 6 | from ..account import Account 7 | from ..tl.functions import GetAccountAddress, ImportKey 8 | from ..tl.types import Key, ExportedKey, Raw_InitialAccountState, InputKeyRegular 9 | from ..utils.wallet import contracts, sources, sha256 10 | 11 | 12 | class WalletMethods: 13 | async def create_wallet(self, source='v3r2', workchain_id: int = 0, wallet_id: int = None, local_password=None): 14 | """ 15 | Generating a wallet using a key from self.create_new_key and TON default seed 16 | 17 | :param source: from sources list 18 | :param workchain_id: 19 | :param wallet_id: 20 | :param local_password: 21 | :return: Account 22 | """ 23 | assert source in sources or source is None, 'Select one version' 24 | 25 | key = await self.create_new_key(local_password=local_password) 26 | return await self.init_wallet(key, source=source, workchain_id=workchain_id, wallet_id=wallet_id, local_password=local_password) 27 | 28 | async def init_wallet(self, key: Key, source='v3r2', workchain_id: int = None, wallet_id: int = None, local_password=None): 29 | assert source in sources or source is None, 'Select one version' 30 | 31 | workchain_id = workchain_id or self.workchain_id 32 | wallet_id = wallet_id or self.config_info.default_wallet_id 33 | 34 | query = GetAccountAddress( 35 | Raw_InitialAccountState( 36 | b64decode(sources[source]), 37 | contracts[sha256(sources[source])]['data_builder']( 38 | wallet_id, SigningKey( 39 | b64decode(await self.export_key(InputKeyRegular(key, local_password=local_password))) 40 | ).verify_key._key 41 | ).to_boc(False) 42 | ), 43 | revision=0, 44 | workchain_id=workchain_id 45 | ) 46 | r = await self.tonlib_wrapper.execute(query) 47 | return Account( 48 | r.account_address, key=key, local_password=local_password, client=self, 49 | source=source, workchain_id=workchain_id, wallet_id=wallet_id 50 | ) 51 | 52 | async def find_wallet(self, path, local_password=None, workchain_id=0): 53 | """ 54 | Getting a wallet from Keystore via Wallet.path 55 | 56 | :param path: 57 | :param local_password: local storage encrypt password 58 | :param workchain_id: 59 | :return: Wallet 60 | """ 61 | 62 | path = json.loads(b64decode(path).decode('utf-8')) 63 | key = Key(path.get('pk'), secret=path.get('sk')) 64 | source = path.get('sr', 'v3r2') 65 | wallet_id = path.get('wi', int(self.config_info.default_wallet_id)) 66 | workchain_id = workchain_id or path.get('wc', 0) 67 | query = GetAccountAddress( 68 | Raw_InitialAccountState( 69 | b64decode(sources[source]), 70 | contracts[sha256(sources[source])]['data_builder']( 71 | wallet_id, SigningKey( 72 | b64decode(await self.export_key(InputKeyRegular(key, local_password=local_password))) 73 | ).verify_key._key 74 | ).to_boc(False) 75 | ), 76 | revision=0, 77 | workchain_id=workchain_id 78 | ) 79 | r = await self.tonlib_wrapper.execute(query) 80 | return Account( 81 | r.account_address, key=key, local_password=local_password, client=self, 82 | source=source, workchain_id=workchain_id, wallet_id=wallet_id 83 | ) 84 | 85 | async def import_wallet(self, word_list, source='v3r2', workchain_id: int = None, wallet_id: int = None, local_password=None): 86 | """ 87 | Restoring a wallet from a word list 88 | 89 | :param wallet_id: 90 | :param workchain_id: 91 | :param source: 92 | :param word_list: 93 | :param local_password: 94 | :return: Wallet 95 | """ 96 | 97 | assert source in sources or source is None, 'Select one version' 98 | 99 | workchain_id = workchain_id or self.workchain_id 100 | wallet_id = wallet_id or self.config_info.default_wallet_id 101 | 102 | query = ImportKey( 103 | ExportedKey(word_list.split(' ')), 104 | '', 105 | local_password=local_password 106 | ) 107 | key = await self.tonlib_wrapper.execute(query) 108 | key = Key(key.public_key, secret=key.secret) 109 | return await self.init_wallet( 110 | key, source=source, local_password=local_password, 111 | workchain_id=workchain_id, wallet_id=wallet_id 112 | ) 113 | -------------------------------------------------------------------------------- /ton/distlib/darwin/libtonlibjson.arm64.dylib: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/psylopunk/pytonlib/de34d75930c0923e613148ca4806a0e695872bd3/ton/distlib/darwin/libtonlibjson.arm64.dylib -------------------------------------------------------------------------------- /ton/distlib/darwin/libtonlibjson.x86_64.dylib: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/psylopunk/pytonlib/de34d75930c0923e613148ca4806a0e695872bd3/ton/distlib/darwin/libtonlibjson.x86_64.dylib -------------------------------------------------------------------------------- /ton/distlib/freebsd/libtonlibjson.amd64.so: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/psylopunk/pytonlib/de34d75930c0923e613148ca4806a0e695872bd3/ton/distlib/freebsd/libtonlibjson.amd64.so -------------------------------------------------------------------------------- /ton/distlib/linux/libtonlibjson.aarch64.so: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/psylopunk/pytonlib/de34d75930c0923e613148ca4806a0e695872bd3/ton/distlib/linux/libtonlibjson.aarch64.so -------------------------------------------------------------------------------- /ton/distlib/linux/libtonlibjson.x86_64.so: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/psylopunk/pytonlib/de34d75930c0923e613148ca4806a0e695872bd3/ton/distlib/linux/libtonlibjson.x86_64.so -------------------------------------------------------------------------------- /ton/distlib/windows/tonlibjson.amd64.dll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/psylopunk/pytonlib/de34d75930c0923e613148ca4806a0e695872bd3/ton/distlib/windows/tonlibjson.amd64.dll -------------------------------------------------------------------------------- /ton/errors.py: -------------------------------------------------------------------------------- 1 | 2 | 3 | class InvalidUsage(Exception): 4 | def __init__(self, hint=None): pass -------------------------------------------------------------------------------- /ton/sync.py: -------------------------------------------------------------------------------- 1 | import asyncio 2 | import functools 3 | import inspect 4 | 5 | from .account import Account 6 | from .client import TonlibClient 7 | 8 | 9 | def _syncify_wrap(t, method_name): 10 | method = getattr(t, method_name) 11 | 12 | @functools.wraps(method) 13 | def syncified(*args, **kwargs): 14 | coro = method(*args, **kwargs) 15 | loop = asyncio.get_event_loop() 16 | if loop.is_running(): 17 | return coro 18 | else: 19 | return loop.run_until_complete(coro) 20 | 21 | # Save an accessible reference to the original method 22 | setattr(syncified, '__tl.sync', method) 23 | setattr(t, method_name, syncified) 24 | 25 | 26 | def syncify(*types): 27 | """ 28 | Converts all the methods in the given types (class definitions) 29 | into synchronous, which return either the coroutine or the result 30 | based on whether ``asyncio's`` event loop is running. 31 | """ 32 | # Our asynchronous generators all are `RequestIter`, which already 33 | # provide a synchronous iterator variant, so we don't need to worry 34 | # about asyncgenfunction's here. 35 | for t in types: 36 | for name in dir(t): 37 | if not name.startswith('_') or name == '__call__': 38 | if inspect.iscoroutinefunction(getattr(t, name)): 39 | _syncify_wrap(t, name) 40 | 41 | 42 | syncify(TonlibClient, Account) 43 | 44 | __all__ = [ 45 | 'TonlibClient', 'Account' 46 | ] -------------------------------------------------------------------------------- /ton/tl/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/psylopunk/pytonlib/de34d75930c0923e613148ca4806a0e695872bd3/ton/tl/__init__.py -------------------------------------------------------------------------------- /ton/tl/base.py: -------------------------------------------------------------------------------- 1 | import json 2 | 3 | class TLObject: 4 | def __repr__(self): 5 | return json.dumps(self.to_json(), indent=4) 6 | 7 | def __init__(self, _type): 8 | if type(_type) != str: 9 | raise Exception(f'@type must be a string, {_type}') 10 | 11 | self.type = _type 12 | 13 | def to_json(self): 14 | _values = {} 15 | for key in self.__dict__: 16 | if key in ['type', 'extra']: 17 | continue 18 | elif type(self.__dict__[key]) == list: 19 | _values[key] = [] 20 | for i, e in enumerate(self.__dict__[key]): 21 | if isinstance(e, TLObject): 22 | _values[key].append(e.to_json()) 23 | else: 24 | _values[key].append(e) 25 | elif isinstance(self.__dict__[key], TLObject): 26 | _values[key] = self.__dict__[key].to_json() 27 | else: 28 | _values[key] = self.__dict__[key] 29 | 30 | return { 31 | '@type': self.type, 32 | **_values 33 | } 34 | 35 | @classmethod 36 | def from_json(cls, entries): 37 | if '@type' not in entries: 38 | raise Exception('Type is not specified') 39 | 40 | object = TLObject(entries['@type']) 41 | del entries['@type'] 42 | _data = {} 43 | for key in entries: 44 | if type(entries[key]) == dict: 45 | _data[key] = TLObject.from_json(entries[key]) 46 | elif type(entries[key]) == list: 47 | _data[key] = [] 48 | for i, e in enumerate(entries[key]): 49 | if type(e) == dict: 50 | _data[key].append(TLObject.from_json(e)) 51 | else: 52 | _data[key].append(e) 53 | else: 54 | _data[key] = entries[key] 55 | 56 | object.__dict__.update(_data) 57 | return object -------------------------------------------------------------------------------- /ton/tl/functions/__init__.py: -------------------------------------------------------------------------------- 1 | from .accounts import * 2 | from .keys import * 3 | from .queries import * 4 | from .settings import * 5 | from .smc import * 6 | -------------------------------------------------------------------------------- /ton/tl/functions/accounts.py: -------------------------------------------------------------------------------- 1 | from typing import Union 2 | 3 | from ..base import TLObject 4 | from ..types import AccountAddress, Internal_TransactionId 5 | 6 | 7 | class GetAccountAddress(TLObject): 8 | def __init__(self, initial_account_state, revision: int=0, workchain_id: int=0): 9 | self.type = 'getAccountAddress' 10 | self.initial_account_state = initial_account_state 11 | self.revision = revision 12 | self.workchain_id = workchain_id 13 | 14 | 15 | class GetAccountState(TLObject): 16 | def __init__(self, account_address: Union[AccountAddress, str]): 17 | if type(account_address) == str: 18 | account_address = AccountAddress(account_address) 19 | 20 | self.type = 'getAccountState' 21 | self.account_address = account_address 22 | 23 | 24 | class Raw_GetAccountState(TLObject): 25 | def __init__(self, account_address: Union[AccountAddress, str]): 26 | if type(account_address) == str: 27 | account_address = AccountAddress(account_address) 28 | 29 | self.type = 'raw.getAccountState' 30 | self.account_address = account_address 31 | 32 | 33 | class Raw_GetTransactions(TLObject): 34 | def __init__(self, account_address: Union[AccountAddress, str], from_transaction_id: Internal_TransactionId): 35 | self.type = 'raw.getTransactions' 36 | self.account_address = account_address 37 | self.from_transaction_id = from_transaction_id -------------------------------------------------------------------------------- /ton/tl/functions/keys.py: -------------------------------------------------------------------------------- 1 | from ..base import TLObject 2 | from ..types import InputKeyRegular 3 | from ...utils import str_b64encode 4 | 5 | 6 | class CreateNewKey(TLObject): 7 | def __init__(self, mnemonic_password: str=None, random_extra_seed: str=None, local_password: str=None): 8 | self.type = 'createNewKey' 9 | self.mnemonic_password = str_b64encode(mnemonic_password) 10 | self.random_extra_seed = str_b64encode(random_extra_seed) 11 | self.local_password = str_b64encode(local_password) 12 | 13 | 14 | class ExportKey(TLObject): 15 | def __init__(self, input_key: InputKeyRegular): 16 | self.type = 'exportKey' 17 | self.input_key = input_key 18 | 19 | 20 | class ExportUnencryptedKey(TLObject): 21 | def __init__(self, input_key: InputKeyRegular): 22 | self.type = 'exportUnencryptedKey' 23 | self.input_key = input_key 24 | 25 | 26 | class ImportKey(TLObject): 27 | def __init__(self, exported_key, mnemonic_password, local_password=None): 28 | self.type = 'importKey' 29 | self.exported_key = exported_key 30 | self.mnemonic_password = str_b64encode(mnemonic_password) 31 | self.local_password = str_b64encode(local_password) -------------------------------------------------------------------------------- /ton/tl/functions/queries.py: -------------------------------------------------------------------------------- 1 | from typing import Union 2 | 3 | from ..base import TLObject 4 | from ..types import InputKeyRegular, Raw_InitialAccountState, AccountAddress, ActionMsg 5 | from ...utils.common import bytes_b64encode 6 | 7 | 8 | class Raw_CreateQuery(TLObject): 9 | def __init__( 10 | self, 11 | destination: Union[AccountAddress, str], 12 | body: bytes, 13 | init_code: bytes = None, 14 | init_data: bytes = None 15 | ): 16 | if type(destination) == str: 17 | destination = AccountAddress(destination) 18 | 19 | self.type = 'raw.createQuery' 20 | self.destination = destination 21 | self.body = bytes_b64encode(body) 22 | if init_code: self.init_code = bytes_b64encode(init_code) 23 | if init_data: self.init_data = bytes_b64encode(init_data) 24 | 25 | 26 | class Raw_CreateAndSendMessage(TLObject): 27 | def __init__(self, destination: Union[AccountAddress, str], data: bytes, initial_account_state: bytes = None): 28 | if type(destination) == str: 29 | destination = AccountAddress(destination) 30 | 31 | self.type = 'raw.createAndSendMessage' 32 | self.destination = destination 33 | self.data = bytes_b64encode(data) 34 | if initial_account_state: 35 | self.initial_account_state = bytes_b64encode(initial_account_state) 36 | 37 | class Raw_SendMessage(TLObject): 38 | def __init__(self, body: bytes): 39 | self.type = 'raw.sendMessage' 40 | self.body = bytes_b64encode(body) 41 | 42 | 43 | class CreateQuery(TLObject): 44 | def __init__( 45 | self, 46 | private_key: InputKeyRegular, 47 | initial_account_state: Raw_InitialAccountState, 48 | address: AccountAddress, 49 | action: Union[ActionMsg], 50 | timeout: int = 300 51 | ): 52 | self.type = 'createQuery' 53 | self.private_key = private_key 54 | self.initial_account_state = initial_account_state 55 | self.address = address 56 | self.action = action 57 | self.timeout = timeout 58 | 59 | 60 | class Query_Send(TLObject): 61 | def __init__(self, id: int): 62 | self.type = 'query.send' 63 | self.id = id 64 | 65 | 66 | class Query_Forget(TLObject): 67 | def __init__(self, id: int): 68 | self.type = 'query.forget' 69 | self.id = id 70 | 71 | 72 | class Query_EstimateFees(TLObject): 73 | def __init__(self, id: int, ignore_chksig: bool = False): 74 | self.type = 'query.estimateFees' 75 | self.id = id 76 | self.ignore_chksig = ignore_chksig 77 | 78 | 79 | class Query_GetInfo(TLObject): 80 | def __init__(self, id: int): 81 | self.type = 'query.getInfo' 82 | self.id = id -------------------------------------------------------------------------------- /ton/tl/functions/settings.py: -------------------------------------------------------------------------------- 1 | from ..base import TLObject 2 | 3 | class SetLogVerbosityLevel(TLObject): 4 | def __init__(self, level: int): 5 | self.type = 'setLogVerbosityLevel' 6 | self.new_verbosity_level = level -------------------------------------------------------------------------------- /ton/tl/functions/smc.py: -------------------------------------------------------------------------------- 1 | from typing import Union 2 | 3 | from ..base import TLObject 4 | from ..types import AccountAddress, Smc_MethodIdName, Smc_MethodIdNumber 5 | 6 | 7 | class Smc_Load(TLObject): 8 | def __init__(self, account_address: Union[str, AccountAddress]): 9 | if isinstance(account_address, str): 10 | account_address = AccountAddress(account_address) 11 | 12 | self.type = 'smc.load' 13 | self.account_address = account_address 14 | 15 | 16 | class Smc_GetCode(TLObject): 17 | def __init__(self, id: int): 18 | self.type = 'smc.getCode' 19 | self.id = id 20 | 21 | 22 | class Smc_GetData(TLObject): 23 | def __init__(self, id: int): 24 | self.type = 'smc.getData' 25 | self.id = id 26 | 27 | 28 | class Smc_GetState(TLObject): 29 | def __init__(self, id: int): 30 | self.type = 'smc.getState' 31 | self.id = id 32 | 33 | 34 | class Smc_RunGetMethod(TLObject): 35 | def __init__(self, id: int, method: Union[Smc_MethodIdName, Smc_MethodIdNumber], stack: list = []): 36 | if type(method) == str: 37 | method = Smc_MethodIdName(method) 38 | elif type(method) == int: 39 | method = Smc_MethodIdNumber(method) 40 | 41 | self.type = 'smc.runGetMethod' 42 | self.id = id 43 | self.method = method 44 | self.stack = stack -------------------------------------------------------------------------------- /ton/tl/types/__init__.py: -------------------------------------------------------------------------------- 1 | from .accounts import * 2 | from .actions import * 3 | from .keys import * 4 | from .messages import * 5 | from .smc import * 6 | from .tvm import * 7 | from .wallets import * 8 | -------------------------------------------------------------------------------- /ton/tl/types/accounts.py: -------------------------------------------------------------------------------- 1 | from ..base import TLObject 2 | 3 | class AccountAddress(TLObject): 4 | def __init__(self, account_address): 5 | self.type = 'accountAddress' 6 | self.account_address = account_address -------------------------------------------------------------------------------- /ton/tl/types/actions.py: -------------------------------------------------------------------------------- 1 | from ..base import TLObject 2 | 3 | class ActionMsg(TLObject): 4 | def __init__( 5 | self, 6 | messages: dict, 7 | allow_send_to_uninited: bool=False 8 | ): 9 | self.type = 'actionMsg' 10 | self.messages = messages 11 | self.allow_send_to_uninited = allow_send_to_uninited -------------------------------------------------------------------------------- /ton/tl/types/keys.py: -------------------------------------------------------------------------------- 1 | from ..base import TLObject 2 | from ...utils.common import str_b64encode 3 | 4 | 5 | class Key(TLObject): 6 | def __init__(self, public_key, secret=None): 7 | """ 8 | 9 | :type public_key: str 10 | :param secret: str 11 | """ 12 | self.type = 'key' 13 | self.public_key = public_key 14 | self.secret = secret 15 | 16 | 17 | class InputKeyRegular(TLObject): 18 | def __init__(self, key: Key, local_password: str=None): 19 | self.type = 'inputKeyRegular' 20 | self.key = key 21 | self.local_password = str_b64encode(local_password) 22 | 23 | 24 | class ExportedKey(TLObject): 25 | def __init__(self, word_list: list): 26 | self.type = 'exportedKey' 27 | self.word_list = word_list -------------------------------------------------------------------------------- /ton/tl/types/messages.py: -------------------------------------------------------------------------------- 1 | from typing import Union 2 | 3 | from .accounts import * 4 | from ...utils.common import str_b64encode, bytes_b64encode 5 | 6 | 7 | class MsgDataText(TLObject): 8 | def __init__(self, text: str): 9 | self.type = 'msg.dataText' 10 | self.text = str_b64encode(text) 11 | 12 | class MsgDataRaw(TLObject): 13 | def __init__(self, body: bytes, init_state: bytes=None): 14 | self.type = 'msg.dataRaw' 15 | self.body = bytes_b64encode(body) 16 | self.init_state = bytes_b64encode(init_state) 17 | 18 | class MsgMessage(TLObject): 19 | def __init__( 20 | self, 21 | destination: Union[AccountAddress, str], 22 | amount: int, 23 | data: Union[MsgDataText]=None, 24 | public_key: str=None, 25 | send_mode: int=0 26 | ): 27 | if type(destination) == str: 28 | destination = AccountAddress(destination) 29 | 30 | self.type = 'msg.message' 31 | self.destination = destination 32 | self.amount = amount 33 | self.data = data 34 | self.public_key = public_key 35 | self.send_mode = send_mode -------------------------------------------------------------------------------- /ton/tl/types/smc.py: -------------------------------------------------------------------------------- 1 | from ..base import TLObject 2 | 3 | class Smc_MethodIdNumber(TLObject): 4 | def __init__(self, number: int): 5 | self.type = 'smc.methodIdNumber' 6 | self.number = number 7 | 8 | class Smc_MethodIdName(TLObject): 9 | def __init__(self, name: str): 10 | self.type = 'smc.methodIdName' 11 | self.name = name -------------------------------------------------------------------------------- /ton/tl/types/tvm.py: -------------------------------------------------------------------------------- 1 | from ..base import TLObject 2 | from ...utils import bytes_b64encode 3 | 4 | 5 | class Tvm_Slice(TLObject): 6 | def __init__(self, data: bytes): 7 | self.type = 'tvm.slice' 8 | self.bytes = bytes_b64encode(data) 9 | 10 | 11 | class Tvm_Cell(TLObject): 12 | def __init__(self, data: bytes): 13 | self.type = 'tvm.cell' 14 | self.data = bytes_b64encode(data) 15 | 16 | 17 | class Tvm_NumberDecimal(TLObject): 18 | def __init__(self, number: int): 19 | self.type = 'tvm.numberDecimal' 20 | self.number = str(int(number)) 21 | 22 | 23 | class Tvm_StackEntrySlice(TLObject): 24 | def __init__(self, slice: Tvm_Slice): 25 | self.type = 'tvm.stackEntrySlice' 26 | self.slice = slice 27 | 28 | 29 | class Tvm_StackEntryCell(TLObject): 30 | def __init__(self, cell: Tvm_Cell): 31 | self.type = 'tvm.stackEntryCell' 32 | self.cell = cell 33 | 34 | 35 | class Tvm_StackEntryNumber(TLObject): 36 | def __init__(self, number: Tvm_NumberDecimal): 37 | self.type = 'tvm.stackEntryNumber' 38 | self.number = number 39 | 40 | 41 | class Tvm_StackEntryTuple(TLObject): 42 | def __init__(self, tuple_data: list): 43 | self.type = 'tvm.stackEntryTuple' 44 | self.tuple = Tvm_Tuple(tuple_data) 45 | 46 | 47 | class Tvm_StackEntryList(TLObject): 48 | def __init__(self, list_data): 49 | self.type = 'tvm.stackEntryList' 50 | self.list = Tvm_List(list_data) 51 | 52 | 53 | class Tvm_Tuple(TLObject): 54 | def __init__(self, elements): 55 | self.type = 'tvm.tuple' 56 | self.elements = [] 57 | for element in elements: 58 | if type(element) == int: 59 | self.elements.append(Tvm_StackEntryNumber(element)) 60 | elif element.type == 'tvm.numberDecimal': 61 | self.elements.append(Tvm_StackEntryNumber(element)) 62 | elif element.type == 'tvm.slice': 63 | self.elements.append(Tvm_StackEntrySlice(element)) 64 | elif element.type == 'tvm.cell': 65 | self.elements.append(Tvm_StackEntryCell(element)) 66 | elif element.type == 'tvm.tuple': 67 | self.elements.append(Tvm_StackEntryTuple(element.elements)) 68 | elif element.type == 'tvm.list': 69 | self.elements.append(Tvm_StackEntryList(element.elements)) 70 | 71 | class Tvm_List(TLObject): 72 | def __init__(self, elements): 73 | self.type = 'tvm.list' 74 | self.elements = [] 75 | for element in elements: 76 | if type(element) == int: 77 | self.elements.append(Tvm_StackEntryNumber(element)) 78 | elif element.type == 'tvm.numberDecimal': 79 | self.elements.append(Tvm_StackEntryNumber(element)) 80 | elif element.type == 'tvm.slice': 81 | self.elements.append(Tvm_StackEntrySlice(element)) 82 | elif element.type == 'tvm.cell': 83 | self.elements.append(Tvm_StackEntryCell(element)) 84 | elif element.type == 'tvm.tuple': 85 | self.elements.append(Tvm_StackEntryTuple(element.elements)) 86 | elif element.type == 'tvm.list': 87 | self.elements.append(Tvm_StackEntryList(element.elements)) -------------------------------------------------------------------------------- /ton/tl/types/wallets.py: -------------------------------------------------------------------------------- 1 | from typing import Union 2 | 3 | from .keys import * 4 | from ...utils.common import bytes_b64encode 5 | 6 | 7 | class Raw_InitialAccountState(TLObject): 8 | def __init__(self, code: bytes, data: bytes=None): 9 | self.type = 'raw.initialAccountState' 10 | self.code = bytes_b64encode(code) 11 | self.data = bytes_b64encode(data) 12 | 13 | 14 | class WalletV3InitialAccountState(TLObject): 15 | def __init__(self, public_key: Union[Key, str], wallet_id: int): 16 | if isinstance(public_key, Key): 17 | public_key = public_key.public_key 18 | 19 | self.type = 'wallet.v3.initialAccountState' 20 | self.public_key = public_key 21 | self.wallet_id = int(wallet_id) 22 | 23 | 24 | class Internal_TransactionId(TLObject): 25 | def __init__(self, lt: Union[str, int], hash: Union[str]): 26 | self.type = 'internal.transactionId' 27 | self.lt = int(lt) 28 | self.hash = hash 29 | -------------------------------------------------------------------------------- /ton/tonlibjson.py: -------------------------------------------------------------------------------- 1 | import asyncio 2 | import functools 3 | import json 4 | import logging 5 | import platform 6 | import random 7 | import time 8 | import traceback 9 | from ctypes import * 10 | 11 | import pkg_resources 12 | 13 | from .tl.base import TLObject 14 | 15 | logger = logging.getLogger('ton') 16 | 17 | 18 | class TonlibException(Exception): 19 | pass 20 | 21 | 22 | class TonlibNoResponse(TonlibException): 23 | def __str__(self): 24 | return 'tonlibjson did not respond' 25 | 26 | 27 | class TonlibError(TonlibException): 28 | def __init__(self, result): 29 | self.result = result 30 | 31 | @property 32 | def code(self): 33 | return self.result.get('code') 34 | 35 | def __str__(self): 36 | return self.result.get('message') 37 | 38 | 39 | class LiteServerTimeout(TonlibError): 40 | pass 41 | 42 | 43 | class BlockNotFound(TonlibError): 44 | pass 45 | 46 | 47 | class BlockDeleted(TonlibError): 48 | pass 49 | 50 | 51 | class ExternalMessageNotAccepted(TonlibError): 52 | pass 53 | 54 | 55 | def parse_tonlib_error(result): 56 | if result.get('@type') == 'error': 57 | message = result.get('message') 58 | if 'not in db' in message: 59 | return BlockNotFound(result) 60 | if "state already gc'd" in message: 61 | return BlockDeleted(result) 62 | if 'cannot apply external message to current state' in message: 63 | return ExternalMessageNotAccepted(result) 64 | if 'adnl query timeout' in message: 65 | return LiteServerTimeout(result) 66 | return TonlibError(result) 67 | return None 68 | 69 | 70 | def get_tonlib_path(): 71 | arch_name = platform.system().lower() 72 | machine = platform.machine().lower() 73 | if arch_name == 'linux': 74 | lib_name = f'libtonlibjson.{machine}.so' 75 | elif arch_name == 'darwin': 76 | lib_name = f'libtonlibjson.{machine}.dylib' 77 | elif arch_name == 'freebsd': 78 | lib_name = f'libtonlibjson.{machine}.so' 79 | elif arch_name == 'windows': 80 | lib_name = f'tonlibjson.{machine}.dll' 81 | else: 82 | raise RuntimeError(f"Platform '{arch_name}({machine})' is not compatible yet. Read more at https://github.com/psylopunk/pytonlib/issues/7") 83 | return pkg_resources.resource_filename('ton', f'distlib/{arch_name}/{lib_name}') 84 | 85 | 86 | class TonLib: 87 | def __init__(self, loop, ls_index, cdll_path=None, verbosity_level=0, default_timeout=None): 88 | self.loop = loop 89 | self.default_timeout = default_timeout 90 | 91 | cdll_path = get_tonlib_path() if not cdll_path else cdll_path 92 | tonlib = CDLL(cdll_path) 93 | 94 | tonlib_json_client_create = tonlib.tonlib_client_json_create 95 | tonlib_json_client_create.restype = c_void_p 96 | tonlib_json_client_create.argtypes = [] 97 | try: 98 | self._client = tonlib_json_client_create() 99 | except Exception as ee: 100 | raise RuntimeError(f"Failed to create tonlibjson client: {ee}") 101 | 102 | tonlib_json_client_receive = tonlib.tonlib_client_json_receive 103 | tonlib_json_client_receive.restype = c_char_p 104 | tonlib_json_client_receive.argtypes = [c_void_p, c_double] 105 | self._tonlib_json_client_receive = tonlib_json_client_receive 106 | 107 | tonlib_json_client_send = tonlib.tonlib_client_json_send 108 | tonlib_json_client_send.restype = None 109 | tonlib_json_client_send.argtypes = [c_void_p, c_char_p] 110 | self._tonlib_json_client_send = tonlib_json_client_send 111 | 112 | tonlib_json_client_execute = tonlib.tonlib_client_json_execute 113 | tonlib_json_client_execute.restype = c_char_p 114 | tonlib_json_client_execute.argtypes = [c_void_p, c_char_p] 115 | self._tonlib_json_client_execute = tonlib_json_client_execute 116 | 117 | tonlib_json_client_destroy = tonlib.tonlib_client_json_destroy 118 | tonlib_json_client_destroy.restype = None 119 | tonlib_json_client_destroy.argtypes = [c_void_p] 120 | self._tonlib_json_client_destroy = tonlib_json_client_destroy 121 | 122 | self.futures = {} 123 | self.ls_index = ls_index 124 | self._state = None # None, "finished", "crashed", "stuck" 125 | 126 | self.is_dead = False 127 | # creating tasks 128 | self.read_results_task = self.loop.create_task(self.read_results()) 129 | self.del_expired_futures_task = self.loop.create_task(self.del_expired_futures_loop()) 130 | 131 | def __del__(self): 132 | try: 133 | self._tonlib_json_client_destroy(self._client) 134 | except Exception as ee: 135 | logger.error(f"Exception in tonlibjson.__del__: {traceback.format_exc()}") 136 | raise RuntimeError(f'Error in tonlibjson.__del__: {ee}') 137 | 138 | def send(self, query): 139 | if not self._is_working: 140 | raise RuntimeError(f"TonLib failed with state: {self._state}") 141 | 142 | query = json.dumps(query).encode('utf-8') 143 | try: 144 | self._tonlib_json_client_send(self._client, query) 145 | except Exception as ee: 146 | logger.error(f"Exception in tonlibjson.send: {traceback.format_exc()}") 147 | raise RuntimeError(f'Error in tonlibjson.send: {ee}') 148 | 149 | def receive(self, timeout=30): 150 | result = None 151 | try: 152 | result = self._tonlib_json_client_receive(self._client, timeout) # time.sleep # asyncio.sleep 153 | except Exception as ee: 154 | logger.error(f"Exception in tonlibjson.receive: {traceback.format_exc()}") 155 | raise RuntimeError(f'Error in tonlibjson.receive: {ee}') 156 | if result: 157 | result = json.loads(result.decode('utf-8')) 158 | return result 159 | 160 | def _execute(self, query, timeout=30): 161 | if not self._is_working: 162 | raise RuntimeError(f"TonLib failed with state: {self._state}") 163 | 164 | extra_id = "%s:%s:%s" % (time.time() + timeout, self.ls_index, random.random()) 165 | query["@extra"] = extra_id 166 | 167 | future_result = self.loop.create_future() 168 | self.futures[extra_id] = future_result 169 | self.loop.run_in_executor(None, lambda: self.send(query)) 170 | return future_result 171 | 172 | async def execute(self, query, timeout=30): 173 | logger.debug(f'SENT' + '\n' + f'{query}') 174 | if isinstance(query, TLObject): query = query.to_json() 175 | result = await self._execute(query, timeout=timeout) 176 | result = TLObject.from_json(result) 177 | logger.debug(f'RECEIVED' + '\n' + f'{result}') 178 | if result.type == 'updateSyncState': 179 | await asyncio.sleep(1) 180 | return await self.execute(query, timeout=timeout) 181 | 182 | return result 183 | 184 | @property 185 | def _is_working(self): 186 | return self._state not in ('crashed', 'stuck', 'finished') 187 | 188 | @property 189 | def _is_closing(self): 190 | return self._state == 'finishing' 191 | 192 | async def close(self): 193 | try: 194 | self._state = 'finishing' 195 | await self.read_results_task 196 | await self.del_expired_futures_task 197 | self._state = 'finished' 198 | except Exception as ee: 199 | logger.error(f"Exception in tonlibjson.close: {traceback.format_exc()}") 200 | raise RuntimeError(f'Error in tonlibjson.close: {ee}') 201 | 202 | def cancel_futures(self, cancel_all=False): 203 | now = time.time() 204 | to_del = [] 205 | for i in self.futures: 206 | if float(i.split(":")[0]) <= now or cancel_all: 207 | to_del.append(i) 208 | logger.debug(f'Pruning {len(to_del)} tasks') 209 | for i in to_del: 210 | self.futures[i].set_exception(TonlibNoResponse()) 211 | self.futures.pop(i) 212 | 213 | # tasks 214 | async def read_results(self): 215 | timeout = self.default_timeout or 1 216 | receive_func = functools.partial(self.receive, 0.1) 217 | try: 218 | while self._is_working and not self._is_closing: 219 | # return reading result 220 | result = None 221 | try: 222 | result = await asyncio.wait_for(self.loop.run_in_executor(None, receive_func), timeout=timeout) 223 | except asyncio.TimeoutError: 224 | logger.critical(f"Tonlib #{self.ls_index:03d} stuck (timeout error)") 225 | self._state = "stuck" 226 | except: 227 | logger.critical(f"Tonlib #{self.ls_index:03d} crashed: {traceback.format_exc()}") 228 | self._state = "crashed" 229 | 230 | if isinstance(result, dict) and ("@extra" in result) and (result["@extra"] in self.futures): 231 | try: 232 | if not self.futures[result["@extra"]].done(): 233 | tonlib_error = parse_tonlib_error(result) 234 | if tonlib_error is not None: 235 | self.futures[result["@extra"]].set_exception(tonlib_error) 236 | else: 237 | self.futures[result["@extra"]].set_result(result) 238 | self.futures.pop(result["@extra"]) 239 | except Exception as e: 240 | logger.error(f'Tonlib #{self.ls_index:03d} receiving result exception: {e}') 241 | except Exception as ee: 242 | logger.critical(f'Task read_results failed: {ee}') 243 | 244 | async def del_expired_futures_loop(self): 245 | try: 246 | while self._is_working and not self._is_closing: 247 | self.cancel_futures() 248 | await asyncio.sleep(1) 249 | 250 | self.cancel_futures(cancel_all=True) 251 | except Exception as ee: 252 | logger.critical(f'Task del_expired_futures_loop failed: {ee}') 253 | -------------------------------------------------------------------------------- /ton/utils/__init__.py: -------------------------------------------------------------------------------- 1 | from .cell import * 2 | from .common import * 3 | from .wallet import * 4 | -------------------------------------------------------------------------------- /ton/utils/cell.py: -------------------------------------------------------------------------------- 1 | from tonsdk.utils import Address 2 | 3 | 4 | def read_address(cell): 5 | data = ''.join([str(cell.bits.get(x)) for x in range(cell.bits.length)]) 6 | if len(data) < 267: return None 7 | wc = int(data[3:11], 2) 8 | hashpart = int(data[11:11+256], 2).to_bytes(32, 'big').hex() 9 | return Address(f"{wc if wc != 255 else -1}:{hashpart}") 10 | -------------------------------------------------------------------------------- /ton/utils/common.py: -------------------------------------------------------------------------------- 1 | import codecs 2 | from base64 import b64encode 3 | from hashlib import sha256 as hasher 4 | 5 | 6 | def sha256(x): 7 | if not isinstance(x, bytes): 8 | x = codecs.encode(x, 'utf-8') 9 | 10 | h = hasher() 11 | h.update(x) 12 | return h.digest() 13 | 14 | def str_b64encode(s): 15 | return b64encode(s.encode('utf-8')).decode('utf-8') if s and isinstance(s, str) else None 16 | 17 | def bytes_b64encode(s): 18 | return None if s is None else b64encode(s).decode('utf-8') -------------------------------------------------------------------------------- /ton/utils/wallet.py: -------------------------------------------------------------------------------- 1 | import codecs 2 | from hashlib import sha256 as hasher 3 | 4 | from tonsdk.boc import Cell 5 | 6 | 7 | def seqno_extractor(result, data): 8 | data_cell = Cell.one_from_boc(codecs.decode(codecs.encode(data["data"], 'utf-8'), 'base64')) 9 | seqno = int.from_bytes(data_cell.bits.array[0:4], 'big') 10 | result['seqno'] = seqno 11 | 12 | 13 | def v3_extractor(result, data): 14 | seqno_extractor(result, data) 15 | try: 16 | data_cell = Cell.one_from_boc(codecs.decode(codecs.encode(data["data"], 'utf-8'), 'base64')) 17 | wallet_id = int.from_bytes(data_cell.bits.array[4:8], 'big') 18 | result['wallet_id'] = wallet_id 19 | except Exception as e: 20 | print('Extracting wallet_id failed:', str(e)) 21 | 22 | 23 | def v3_builder(wallet_id: int, public_key: bytes): 24 | cell = Cell() 25 | cell.bits.write_uint(0, 32) # seqno 26 | cell.bits.write_uint(int(wallet_id), 32) 27 | cell.bits.write_bytes(public_key) 28 | return cell 29 | 30 | 31 | def v4_builder(wallet_id: int, public_key: bytes): 32 | cell = v3_builder(wallet_id, public_key) 33 | cell.bits.write_uint(0, 1) # empty plugins dict 34 | return cell 35 | 36 | 37 | def sha256(x): 38 | if not isinstance(x, bytes): 39 | x = codecs.encode(x, 'utf-8') 40 | h = hasher() 41 | h.update(x) 42 | return h.digest() 43 | 44 | 45 | sources = { 46 | # Wallets 47 | 'v1r1': "te6cckEBAQEARAAAhP8AIN2k8mCBAgDXGCDXCx/tRNDTH9P/0VESuvKhIvkBVBBE+RDyovgAAdMfMSDXSpbTB9QC+wDe0aTIyx/L/8ntVEH98Ik=", 48 | 'v1r2': "te6cckEBAQEAUwAAov8AIN0gggFMl7qXMO1E0NcLH+Ck8mCBAgDXGCDXCx/tRNDTH9P/0VESuvKhIvkBVBBE+RDyovgAAdMfMSDXSpbTB9QC+wDe0aTIyx/L/8ntVNDieG8=", 49 | 'v1r3': "te6cckEBAQEAXwAAuv8AIN0gggFMl7ohggEznLqxnHGw7UTQ0x/XC//jBOCk8mCBAgDXGCDXCx/tRNDTH9P/0VESuvKhIvkBVBBE+RDyovgAAdMfMSDXSpbTB9QC+wDe0aTIyx/L/8ntVLW4bkI=", 50 | 'v2r1': "te6cckEBAQEAVwAAqv8AIN0gggFMl7qXMO1E0NcLH+Ck8mCDCNcYINMf0x8B+CO78mPtRNDTH9P/0VExuvKhA/kBVBBC+RDyovgAApMg10qW0wfUAvsA6NGkyMsfy//J7VShNwu2", 51 | 'v2r2': "te6cckEBAQEAYwAAwv8AIN0gggFMl7ohggEznLqxnHGw7UTQ0x/XC//jBOCk8mCDCNcYINMf0x8B+CO78mPtRNDTH9P/0VExuvKhA/kBVBBC+RDyovgAApMg10qW0wfUAvsA6NGkyMsfy//J7VQETNeh", 52 | 'v3r1': "te6cckEBAQEAYgAAwP8AIN0gggFMl7qXMO1E0NcLH+Ck8mCDCNcYINMf0x/TH/gjE7vyY+1E0NMf0x/T/9FRMrryoVFEuvKiBPkBVBBV+RDyo/gAkyDXSpbTB9QC+wDo0QGkyMsfyx/L/8ntVD++buA=", 53 | 'v3r2': "te6cckEBAQEAcQAA3v8AIN0gggFMl7ohggEznLqxn3Gw7UTQ0x/THzHXC//jBOCk8mCDCNcYINMf0x/TH/gjE7vyY+1E0NMf0x/T/9FRMrryoVFEuvKiBPkBVBBV+RDyo/gAkyDXSpbTB9QC+wDo0QGkyMsfyx/L/8ntVBC9ba0=", 54 | 'v4r1': "te6cckECFQEAAvUAART/APSkE/S88sgLAQIBIAIDAgFIBAUE+PKDCNcYINMf0x/THwL4I7vyY+1E0NMf0x/T//QE0VFDuvKhUVG68qIF+QFUEGT5EPKj+AAkpMjLH1JAyx9SMMv/UhD0AMntVPgPAdMHIcAAn2xRkyDXSpbTB9QC+wDoMOAhwAHjACHAAuMAAcADkTDjDQOkyMsfEssfy/8REhMUA+7QAdDTAwFxsJFb4CHXScEgkVvgAdMfIYIQcGx1Z70ighBibG5jvbAighBkc3RyvbCSXwPgAvpAMCD6RAHIygfL/8nQ7UTQgQFA1yH0BDBcgQEI9ApvoTGzkl8F4ATTP8glghBwbHVnupEx4w0kghBibG5juuMABAYHCAIBIAkKAFAB+gD0BDCCEHBsdWeDHrFwgBhQBcsFJ88WUAP6AvQAEstpyx9SEMs/AFL4J28ighBibG5jgx6xcIAYUAXLBSfPFiT6AhTLahPLH1Iwyz8B+gL0AACSghBkc3Ryuo41BIEBCPRZMO1E0IEBQNcgyAHPFvQAye1UghBkc3Rygx6xcIAYUATLBVjPFiL6AhLLassfyz+UEDRfBOLJgED7AAIBIAsMAFm9JCtvaiaECAoGuQ+gIYRw1AgIR6STfSmRDOaQPp/5g3gSgBt4EBSJhxWfMYQCAVgNDgARuMl+1E0NcLH4AD2ynftRNCBAUDXIfQEMALIygfL/8nQAYEBCPQKb6ExgAgEgDxAAGa3OdqJoQCBrkOuF/8AAGa8d9qJoQBBrkOuFj8AAbtIH+gDU1CL5AAXIygcVy//J0Hd0gBjIywXLAiLPFlAF+gIUy2sSzMzJcfsAyEAUgQEI9FHypwIAbIEBCNcYyFQgJYEBCPRR8qeCEG5vdGVwdIAYyMsFywJQBM8WghAF9eEA+gITy2oSyx/JcfsAAgBygQEI1xgwUgKBAQj0WfKn+CWCEGRzdHJwdIAYyMsFywJQBc8WghAF9eEA+gIUy2oTyx8Syz/Jc/sAAAr0AMntVEap808=", 55 | 'v4r2': "te6cckECFAEAAtQAART/APSkE/S88sgLAQIBIAIDAgFIBAUE+PKDCNcYINMf0x/THwL4I7vyZO1E0NMf0x/T//QE0VFDuvKhUVG68qIF+QFUEGT5EPKj+AAkpMjLH1JAyx9SMMv/UhD0AMntVPgPAdMHIcAAn2xRkyDXSpbTB9QC+wDoMOAhwAHjACHAAuMAAcADkTDjDQOkyMsfEssfy/8QERITAubQAdDTAyFxsJJfBOAi10nBIJJfBOAC0x8hghBwbHVnvSKCEGRzdHK9sJJfBeAD+kAwIPpEAcjKB8v/ydDtRNCBAUDXIfQEMFyBAQj0Cm+hMbOSXwfgBdM/yCWCEHBsdWe6kjgw4w0DghBkc3RyupJfBuMNBgcCASAICQB4AfoA9AQw+CdvIjBQCqEhvvLgUIIQcGx1Z4MesXCAGFAEywUmzxZY+gIZ9ADLaRfLH1Jgyz8gyYBA+wAGAIpQBIEBCPRZMO1E0IEBQNcgyAHPFvQAye1UAXKwjiOCEGRzdHKDHrFwgBhQBcsFUAPPFiP6AhPLassfyz/JgED7AJJfA+ICASAKCwBZvSQrb2omhAgKBrkPoCGEcNQICEekk30pkQzmkD6f+YN4EoAbeBAUiYcVnzGEAgFYDA0AEbjJftRNDXCx+AA9sp37UTQgQFA1yH0BDACyMoHy//J0AGBAQj0Cm+hMYAIBIA4PABmtznaiaEAga5Drhf/AABmvHfaiaEAQa5DrhY/AAG7SB/oA1NQi+QAFyMoHFcv/ydB3dIAYyMsFywIizxZQBfoCFMtrEszMyXP7AMhAFIEBCPRR8qcCAHCBAQjXGPoA0z/IVCBHgQEI9FHyp4IQbm90ZXB0gBjIywXLAlAGzxZQBPoCFMtqEssfyz/Jc/sAAgBsgQEI1xj6ANM/MFIkgQEI9Fnyp4IQZHN0cnB0gBjIywXLAlAFzxZQA/oCE8tqyx8Syz/Jc/sAAAr0AMntVGliJeU=" 56 | } 57 | 58 | contracts = { 59 | sha256(sources[name]): { 60 | 'type': name, 61 | 'data_extractor': v3_extractor if name.find('v') == 0 else None, # because v3 is the only one with data extractor 62 | 'data_builder': { 63 | 'v1': None, 64 | 'v2': None, 65 | 'v3': v3_builder, 66 | 'v4': v4_builder 67 | }[name[:2]], 68 | } for name in sources 69 | } -------------------------------------------------------------------------------- /troubleshooting.md: -------------------------------------------------------------------------------- 1 | # ⚠ Troubleshooting 2 | 3 | Library functions make requests via `ton.tl.functions`. Each of the classes is a reference to the `libtonlibjson` method ([full list](https://github.com/newton-blockchain/ton/blob/master/tl/generate/scheme/tonlib\_api.tl)). 4 | 5 | To enable debugging of library actions, need to do this: 6 | 7 | ```python 8 | import logging 9 | logging.getLogger('ton').disabled = False 10 | logging.getLogger('ton').setLevel(logging.DEBUG) 11 | ``` 12 | 13 | But apart from visible errors, the action can simply last indefinitely in time. This means that the error occurred inside `libtonlibjson` OR the selected liteserver is not responding. In that case, we need to see what's going on inside: 14 | 15 | ```python 16 | await client.set_verbosity_level(5) 17 | ``` 18 | 19 | If it seems that the problem is in the lite server, you need to change it, reinitialize the library with a different ls\_index: 20 | 21 | ```python 22 | client = TonlibClient(ls_index=N) 23 | await client.init_tonlib() 24 | ``` 25 | --------------------------------------------------------------------------------