Git Product home page Git Product logo

pytoniq's Introduction

pytoniq

PyPI version PyPI - Python Version Downloads Downloads

Pytoniq is a Python SDK for the TON Blockchain. This library extends pytoniq-core with native LiteClient and ADNL.

If you have any questions join Python - TON developers chat.

Documentation

GitBook

Installation

pip install pytoniq 

Examples

You can find them in the examples folder.

LiteClient

General LiteClient usage examples

Client initializing

from pytoniq import LiteClient


async def main():
    client = LiteClient.from_mainnet_config(  # choose mainnet, testnet or custom config dict
        ls_i=0,  # index of liteserver from config
        trust_level=2,  # trust level to liteserver
        timeout=15  # timeout not includes key blocks synchronization as it works in pytonlib
    )

    await client.connect()
    
    await client.get_masterchain_info()

    await client.reconnect()  # can reconnect to an exising object if had any errors

    await client.close()
    
    """ or use it with context manager: """
    async with LiteClient.from_mainnet_config(ls_i=0, trust_level=2, timeout=15) as client:
        await client.get_masterchain_info()

Blocks transactions scanning

See BlockScanner code here.

from pytoniq_core import BlockIdExt
from pytoniq import LiteClient
from examples.blocks.block_scanner import BlockScanner  # this import is not available if downloaded from pypi

async def handle_block(block: BlockIdExt):
    if block.workchain == -1:  # skip masterchain blocks
        return
    print(block)
    transactions = await client.raw_get_block_transactions_ext(block)
    for transaction in transactions:
        print(transaction.in_msg)


client = LiteClient.from_mainnet_config(ls_i=14, trust_level=0, timeout=20)


async def main():

    await client.connect()
    await BlockScanner(client=client, block_handler=handle_block).run()

LiteBalancer

LiteBalancer is constantly pinging LiteServers to identify "alive" peers. When you make a request through LiteBalancer, it forwards the request to the "best" peer - the "alive" peer with the maximum last masterchain block seqno among all and minimum average response time.

LiteBalancer can also retry the request if a asyncio.TimeoutError occurs, but this must be explicitly set using the LiteBalancer.set_max_retries(retries_num) method.

client = LiteBalancer.from_mainnet_config(trust_level=1)

await client.start_up()

result = await client.run_get_method(address='EQBvW8Z5huBkMJYdnfAEM5JqTNkuWX3diqYENkWsIL0XggGG', method='seqno', stack=[])

await client.close_all()

""" or use it with context manager: """

async with LiteBalancer.from_mainnet_config(trust_level=1) as client:
    result = await client.run_get_method(address='EQBvW8Z5huBkMJYdnfAEM5JqTNkuWX3diqYENkWsIL0XggGG', method='seqno', stack=[])

Moreover, one of the most important features of LiteBalancer is that it detects archival LiteServers, so you can do requests only to archival LiteServers providing True for argument only_archive in any method:

# ask for very very old block
blk, _ = await client.lookup_block(-1, -2**63, 100, only_archive=True)  

# ask for old block and run get method for that block:
blk, _ = await client.lookup_block(-1, -2**63, 25000000, only_archive=True)
result = await client.run_get_method(address='EQBvW8Z5huBkMJYdnfAEM5JqTNkuWX3diqYENkWsIL0XggGG', method='seqno',
                                             stack=[], block=blk, only_archive=True)

Blockstore

The library can prove all data it receives from a Liteserver (Learn about trust levels here). If you want to use LiteClient or LiteBalancer with the zero trust level, at the first time run library will prove block link from the init_block to the last masterchain block. Last proved blocks will be stored in the .blockstore folder. The file data contains ttl and gen_utime of the last synced key block, its data serialized according to the BlockIdExt TL scheme (but in big–endian), last synced masterchain block data. Filename is first 88 bytes of data described above with init block hash.

ADNL

from pytoniq.adnl.adnl import AdnlTransport, Node

adnl = AdnlTransport(timeout=3)

# start adnl receiving server
await adnl.start()

# take peer from public config
peer = Node('172.104.59.125', 14432, "/YDNd+IwRUgL0mq21oC0L3RxrS8gTu0nciSPUrhqR78=", adnl)
await adnl.connect_to_peer(peer)
# or await peer.connect()

await peer.disconnect()

# send pings
await asyncio.sleep(10)

# stop adnl receiving server
await adnl.close()

DHT

import time

from pytoniq.adnl.adnl import AdnlTransport
from pytoniq.adnl.dht import DhtClient, DhtNode


adnl = AdnlTransport(timeout=5)
client = DhtClient.from_mainnet_config(adnl)

await adnl.start()

foundation_adnl_addr = '516618cf6cbe9004f6883e742c9a2e3ca53ed02e3e36f4cef62a98ee1e449174'
resp = await client.find_value(key=DhtClient.get_dht_key_id(bytes.fromhex(foundation_adnl_addr)))
print(resp)
#  {'@type': 'dht.valueFound', 'value': {'key': {'key': {'id': '516618cf6cbe9004f6883e742c9a2e3ca53ed02e3e36f4cef62a98ee1e449174', 'name': b'address', 'idx': 0, '@type': 'dht.key'}, 'id': {'key': '927d3e71e3ce651c3f172134d39163f70e4c792169e39f3d520bfad9388ad4ca', '@type': 'pub.ed25519'}, 'update_rule': {'@type': 'dht.updateRule.signature'}, 'signature': b"g\x08\xf8yo\xed1\xb83\x17\xb9\x10\xb4\x8f\x00\x17]D\xd2\xae\xfa\x87\x9f\xf7\xfa\x192\x971\xee'2\x83\x0fk\x03w\xbb0\xfcU\xc8\x89Zm\x8e\xba\xce \xfc\xde\xf2F\xdb\x0cI*\xe0\xaeN\xef\xc2\x9e\r", '@type': 'dht.keyDescription'}, 'value': {'@type': 'adnl.addressList', 'addrs': [{'@type': 'adnl.address.udp', 'ip': -1537433966, 'port': 3333}], 'version': 1694227845, 'reinit_date': 1694227845, 'priority': 0, 'expire_at': 0}, 'ttl': 1695832194, 'signature': b'z\x8aW\x80k\xceXQ\xff\xb9D{C\x98T\x02e\xef&\xfc\xb6\xde\x80y\xf7\xb4\x92\xae\xd2\xd0\xbakU}3\xfa\xec\x03\xb6v\x98\xb0\xcb\xe8\x05\xb9\xd0\x07o\xb6\xa0)I\x17\xcb\x1a\xc4(Dt\xe6y\x18\x0b', '@type': 'dht.value'}}

key = client.get_dht_key(id_=adnl.client.get_key_id())
ts = int(time.time())
value_data = {
    'addrs': [
        {
            "@type": "adnl.address.udp",
            "ip": 1111111,
            "port": 12000
        }
    ],
    'version': ts,
    'reinit_date': ts,
    'priority': 0,
    'expire_at': 0,
}

value = client.schemas.serialize(client.schemas.get_by_name('adnl.addressList'), value_data)

stored = await client.store_value(  # store our address list in dht as value
    key=key,
    value=value,
    private_key=adnl.client.ed25519_private.encode(),
    ttl=100,
    try_find_after=False
)

print(stored)  # True if value was stored, False otherwise

# disconnect from all peers
await client.close()

pytoniq's People

Contributors

crazyministr avatar ilyaqznetsow avatar nonam3e avatar xabbl4 avatar yungwine avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar

pytoniq's Issues

connect_to_peer always timeouts

Trying to connect to specific peers on start. Tried public ones as well as own full node but connect_to_peer always timeouts.

I double checked the IP, port and the key and they are correct.

Is there any code or action or config needed to be able to connect with other node via adnl.connect_to_peer?

Without this, simple adnl.connect works fine, gets some peers connected automatically on start however after some minutes all peers disconnects. That's my second question: why does it happen and how to make sure the adnl is always connected (for example to my own node)?

python minimum version

It is worth adding to the description that for the library to work, the minimum version of python is 3.10
On older versions of python there is an error:

ERROR: Ignored the following versions that require a different python version: 0.0.5 Requires-Python >=3.10; 0.0.6 Requires-Python >=3.10; 0.0.7 Requires-Python >=3.10
ERROR: Could not find a version that satisfies the requirement pytoniq (from versions: none)
ERROR: No matching distribution found for pytoniq

No module named 'Cryptodome'

Hello, I'm trying to use the library, and I get an error:

>>> from pytoniq import LiteClient, MessageAny
Traceback (most recent call last):
  File "C:\Users\user\AppData\Local\Programs\Python\Python311\Lib\site-packages\pytoniq\crypto\ciphers.py", line 9, in <module>
    from Cryptodome.Random import get_random_bytes
ModuleNotFoundError: No module named 'Cryptodome'

I solved the problem by installing the dependency:

pip install pycryptodomex

He is in requirements.txt, but I didn't see it installed when I installed the source library.
I am attaching a screenshot of the installation and error:
image

Only getting 651 error when using block scanner

I am using the block scanner with the LiteBalancer and getting 651 error when trying to call the raw_get_block_transactions_ext method for the block.

The code worked a few days ago (without any change) but now it's only returning 651 block is not applied
I think its because of a bad liteserver that is scoring high on the ranking of the LiteBalancer but have corrupted state.

What can I do about it?

How can I get the transaction msg_data field from the in_msg?

When getting transactions from toncenter API I get a msg_data field inside the in_msg where I can get the comment of the transaction, how can I get that in pytoniq?

"msg_data": {
  "@type": "msg.dataText",
  "text": "Y29udGFjdC10cmFuc2Zlcg=="
},
"message": "contact-transfer"

Get method "seqno" for account returned exit code -256

Hello. Every time I am trying to make transfer I am getting this error. My code:

from dedust import Asset, Factory, PoolType, SwapParams, VaultNative
from pytoniq import WalletV4R2, LiteBalancer
import asyncio
import time

mnemonics = ""

async def main():
    provider = LiteBalancer.from_mainnet_config(2)
    await provider.start_up()

    wallet = await WalletV4R2.from_mnemonic(provider=provider, mnemonics=mnemonics)

    SCALE_ADDRESS = "EQBlqsm144Dq6SjbPI4jjZvA1hqTIP3CvHovbIfW_t-SCALE"

    TON = Asset.native()
    SCALE = Asset.jetton(SCALE_ADDRESS)

    pool = await Factory.get_pool(pool_type=PoolType.VOLATILE,
                                  assets=[TON, SCALE],
                                  provider=provider)

    swap_params = SwapParams(deadline=int(time.time() + 60 * 5),
                             recipient_address=wallet.address)
    swap_amount = int(float(input("Enter swap amount: ")) * 1e9)

    swap = VaultNative.create_swap_payload(amount=swap_amount,
                                           pool_address=pool.address,
                                           swap_params=swap_params)

    swap_amount = int(swap_amount + (0.25 * 1e9))  # 0.25 = gas_value

    await wallet.transfer(destination="EQDa4VOnTYlLvDJ0gZjNYm5PXfSmmtL6Vs6A_CZEtXCNICq_",  # native vault
                          amount=swap_amount,
                          body=swap)



asyncio.run(main())

Traceback:

Traceback (most recent call last):
  File "/root/sniper-bot/test_requests.py", line 46, in <module>
    asyncio.run(main())
  File "/root/.pyenv/versions/3.10.14/lib/python3.10/asyncio/runners.py", line 44, in run
    return loop.run_until_complete(main)
  File "/root/.pyenv/versions/3.10.14/lib/python3.10/asyncio/base_events.py", line 649, in run_until_complete
    return future.result()
  File "/root/sniper-bot/test_requests.py", line 40, in main
    await wallet.transfer(destination="EQDa4VOnTYlLvDJ0gZjNYm5PXfSmmtL6Vs6A_CZEtXCNICq_",  # native vault
  File "/root/.pyenv/versions/sniper/lib/python3.10/site-packages/pytoniq/contract/wallets/wallet.py", line 143, in transfer
    return await self.raw_transfer(msgs=[wallet_message])
  File "/root/.pyenv/versions/sniper/lib/python3.10/site-packages/pytoniq/contract/wallets/wallet.py", line 131, in raw_transfer
    seqno = await self.get_seqno()
  File "/root/.pyenv/versions/sniper/lib/python3.10/site-packages/pytoniq/contract/wallets/wallet.py", line 157, in get_seqno
    return (await super().run_get_method('seqno'))[0]
  File "/root/.pyenv/versions/sniper/lib/python3.10/site-packages/pytoniq/contract/contract.py", line 111, in run_get_method
    return await self.provider.run_get_method(self.address, method, stack)
  File "/root/.pyenv/versions/sniper/lib/python3.10/site-packages/pytoniq/liteclient/balancer.py", line 359, in run_get_method
    return await self.execute_method('run_get_method', **self._get_args(locals())) 
  File "/root/.pyenv/versions/sniper/lib/python3.10/site-packages/pytoniq/liteclient/balancer.py", line 267, in execute_method
    resp = await peer_meth(*args, **kwargs)
  File "/root/.pyenv/versions/sniper/lib/python3.10/site-packages/pytoniq/liteclient/client.py", line 494, in run_get_method
    return await self.run_get_method_remote(address, method, stack, block)  # will be replaced with run_get_method_local in future
  File "/root/.pyenv/versions/sniper/lib/python3.10/site-packages/pytoniq/liteclient/client.py", line 526, in run_get_method_remote
    raise RunGetMethodError(address=address, method=method, exit_code=result['exit_code'])
pytoniq.liteclient.client.RunGetMethodError: Get method "seqno" for account Address<EQBqIGPRZfmOuk5o8CiqOOCVnpHIj7vx2_UF-REfLwc3znW-> returned exit code -256

AttributeError: 'NoneType' object has no attribute 'seqno' with trust_level 0

pytoniq 0.1.39
python 3.11

import asyncio

from pytoniq import LiteClient


async def main():
    client = LiteClient.from_mainnet_config(  # choose mainnet, testnet or custom config dict
        ls_i=0,  # index of liteserver from config
        trust_level=0,  # trust level to liteserver
        timeout=15  # timeout not includes key blocks synchronization as it works in pytonlib
    )

    await client.connect()


if __name__ == '__main__':
    asyncio.run(main())

With some probability I get:

Traceback (most recent call last):
  File "<rootPath>/pytoniq_test.py", line 44, in <module>
    asyncio.run(main())
  File "/usr/lib/python3.11/asyncio/runners.py", line 190, in run
    return runner.run(main)
           ^^^^^^^^^^^^^^^^
  File "/usr/lib/python3.11/asyncio/runners.py", line 118, in run
    return self._loop.run_until_complete(task)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/usr/lib/python3.11/asyncio/base_events.py", line 654, in run_until_complete
    return future.result()
           ^^^^^^^^^^^^^^^
  File "<rootPath>/pytoniq_test.py", line 13, in main
    await client.connect()
  File "<rootPath>/.venv/lib/python3.11/site-packages/pytoniq/liteclient/client.py", line 190, in connect
    await self.update_last_blocks()
  File "<rootPath>/.venv/lib/python3.11/site-packages/pytoniq/liteclient/client.py", line 310, in update_last_blocks
    self.last_mc_block = await self.get_trusted_last_mc_block()
                         ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "<rootPath>/.venv/lib/python3.11/site-packages/pytoniq/liteclient/client.py", line 306, in get_trusted_last_mc_block
    await self.get_mc_block_proof(known_block=self.last_key_block, target_block=last_block)
  File "<rootPath>/.venv/lib/python3.11/site-packages/pytoniq/liteclient/client.py", line 982, in get_mc_block_proof
    while last_proved != target_block:
          ^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "<rootPath>/.venv/lib/python3.11/site-packages/pytoniq_core/tl/block.py", line 66, in __eq__
    if self.seqno != other.seqno or self.workchain != other.workchain or self.shard != other.shard or self.root_hash != other.root_hash or self.file_hash != other.file_hash:
                     ^^^^^^^^^^^
AttributeError: 'NoneType' object has no attribute 'seqno'

Non parseable transactions in specific blocks

We've been running block scanner for a few months now and we first time encountered issues with specific blocks. None of the blocks on mainnet from 37719962 to 37719966 are parseable and all fail with following error. Starting with block 37719967 all works again. Any ideas how to fix? Thank you.

$ python blockscanner.py 37719962

Traceback (most recent call last):
  File "/Users/user/www/project/ton/blockscanner.py", line 246, in <module>
    asyncio.run(main())
  File "/Users/user/.pyenv/versions/3.9.9/lib/python3.9/asyncio/runners.py", line 44, in run
    return loop.run_until_complete(main)
  File "/Users/user/.pyenv/versions/3.9.9/lib/python3.9/asyncio/base_events.py", line 642, in run_until_complete
    return future.result()
  File "/Users/user/www/project/ton/blockscanner.py", line 240, in main
    await BlockScanner(
  File "/Users/user/www/project/ton/blockscanner.py", line 63, in run
    await self.block_handler(self.blks_queue.get_nowait(), self)
  File "/Users/user/www/project/ton/blockscanner.py", line 198, in handle_block
    transactions = await client.raw_get_block_transactions_ext(block)
  File "/Users/user/www/project/ton/.venv/lib/python3.9/site-packages/pytoniq/liteclient/client.py", line 756, in raw_get_block_transactions_ext
    transactions = parse_transactions(result)
  File "/Users/user/www/project/ton/.venv/lib/python3.9/site-packages/pytoniq/liteclient/client.py", line 739, in parse_transactions
    transaction = Transaction.deserialize(tr_root.begin_parse())
  File "/Users/user/www/project/ton/.venv/lib/python3.9/site-packages/pytoniq_core/tlb/transaction.py", line 119, in deserialize
    out_msgs = ref.load_dict(15, value_deserializer=lambda src: MessageAny.deserialize(src.load_ref().begin_parse()))
  File "/Users/user/www/project/ton/.venv/lib/python3.9/site-packages/pytoniq_core/boc/slice.py", line 222, in load_dict
    return HashMap.parse(self.load_ref().begin_parse(), key_length, key_deserializer, value_deserializer)
  File "/Users/user/www/project/ton/.venv/lib/python3.9/site-packages/pytoniq_core/boc/hashmap/hashmap.py", line 123, in parse
    result = {key_deserializer(i): value_deserializer(j) for i, j in dict_result.items()}
  File "/Users/user/www/project/ton/.venv/lib/python3.9/site-packages/pytoniq_core/boc/hashmap/hashmap.py", line 123, in <dictcomp>
    result = {key_deserializer(i): value_deserializer(j) for i, j in dict_result.items()}
  File "/Users/user/www/project/ton/.venv/lib/python3.9/site-packages/pytoniq_core/tlb/transaction.py", line 119, in <lambda>
    out_msgs = ref.load_dict(15, value_deserializer=lambda src: MessageAny.deserialize(src.load_ref().begin_parse()))
  File "/Users/user/www/project/ton/.venv/lib/python3.9/site-packages/pytoniq_core/tlb/transaction.py", line 188, in deserialize
    init = StateInit.deserialize(cell_slice)
  File "/Users/user/www/project/ton/.venv/lib/python3.9/site-packages/pytoniq_core/tlb/account.py", line 270, in deserialize
    code=cell_slice.load_ref() if cell_slice.load_bit() else None,
  File "/Users/user/www/project/ton/.venv/lib/python3.9/site-packages/pytoniq_core/boc/slice.py", line 172, in load_ref
    ref = self.refs[self.ref_offset]
IndexError: list index out of range

How to retrieve transaction hash same as sender sees?

I'm using block scanner example and so far I only have found hash of transaction using transaction.cell.hash.hex(). But it is different of the hash that sender sees on his wallet under Sent transactions.

For example I have this hash (B circle) a8065b4a8d941e52367965b7bb7dc72af2bbb89d4095bdcc40cc1fa58feaddea:
https://tonviewer.com/transaction/a8065b4a8d941e52367965b7bb7dc72af2bbb89d4095bdcc40cc1fa58feaddea

But when letter A in the circle is set then URL changes to this hash, which also matches hash that sender sees in his sent transactions archive: 23eff21be48727416de6b71d00bfd50451227ec4972431eb95e4071bc9dafd46

But I couldn't find a way to get that hash. Is it possible?

When I use RPC API /getTransactions method using the hash I have, then transaction is successfully found however even then field result.transaction_id.hash hash not same hash I searched with, but the one that sender sees.

PS. Thank you for this useful library.

nacl.exceptions.RuntimeError: Unexpected library error

I trying to connect to my node, and i get error :

ensure(rc == 0, "Unexpected library error", raising=exc.RuntimeError) File "/home/meqy/Projects/Python/stonfi/.venv/lib/python3.12/site-packages/nacl/exceptions.py", line 88, in ensure raise raising(*args) nacl.exceptions.RuntimeError: Unexpected library error

WalletV4R2.create() error

simple code to create new wallet not working:

from pytoniq import LiteClient
from pytoniq.contract.wallets import WalletV4R2

async def main():
  client = LiteClient.from_mainnet_config(trust_level=2)
  await client.connect()
  mnemo, wallet = await WalletV4R2.create(provider=client)

Error:

File "/opt/bot/lib/python3.10/site-packages/pytoniq/contract/wallets/wallet.py", line 364, in create
    return await super().create(provider=provider, wc=wc, wallet_id=wallet_id, version='v4r2')
  File "/opt/bot/lib/python3.10/site-packages/pytoniq/contract/wallets/wallet.py", line 98, in create
    return mnemo, await cls.from_mnemonic(provider, mnemo, wc, wallet_id, version)
TypeError: WalletV4R2.from_mnemonic() takes from 3 to 5 positional arguments but 6 were given

reproduced on 3 different projects/envs.
btw, WalletV4R2.from_mnemonic() works correctly.

Liteserver crashed with -400 code

Hi all, I have a question about testnet.

When I use get_transactions on testnet, it usually returns -400 code for me. But when I remove the specific node and use different trans_lt to go on, other node also may throws -400. So how to resolve this problem?

Update:
Now im trying set only_archieve to true and it seems no error returned, so can i think that archieve_peer contains all data? or I have a wrong thinking?

only_archive = kwargs.pop('only_archive', False)

Thanks for everyone's answer.

Provide more examples

Provide examples of using LiteClient for:

  • Parsing blocks (using lookup_block, get_block_header, get_block and so on),
  • Accounts: get methods, account state, transactions
  • Config params
  • Additional suggestions are welcome!

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.