├── .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 | [](https://badge.fury.io/py/ton)   [](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 |
--------------------------------------------------------------------------------
| |