Skip to content

High-level OOP Python library to interact with TON Blockchain

License

Notifications You must be signed in to change notification settings

yungwine/TonTools

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

60 Commits
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

TonTools

TonTools is a high-level OOP library for Python, which can be used to interact with TON Blockchain.

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

PyPI version Downloads

How to install:

pip install tontools

Possibilities

With TonTools you can:

  • Scan custom Contracts and run get methods
  • Create, deploy and scan wallets
  • Scan NFT Collections, Items, Sale contracts
  • Scan Jettons, Jetton Wallets
  • Transfer Tons, Jettons, NFTs
  • Scan Transactions in raw or User-Friendly forms
  • And so much more...

Examples

You can find them in examples/ directory.

Donations

TON - EQBvW8Z5huBkMJYdnfAEM5JqTNkuWX3diqYENkWsIL0XggGG

Providers

TonTools gets data from blockchain using Providers: TonCenterClient, LsClient, DtonClient and TonApiClient

Most provider methods are the same, but there are some differences.

TonCenterClient

TonCenter is an Api which uses lite servers

To initialize TonCenterClient:

client = TonCenterClient(base_url='http://127.0.0.1:80/')
or
client = TonCenterClient(api_key)

Notice that TonCenter has Limit 10 RPS with Api Key, so It's highly recommend to use Local TonCenter and specify your host in base_url parameter or use the Orbs Ton Access:

client = TonCenterClient(orbs_access=True,
                         testnet=True)

contract = Contract('EQBvW8Z5huBkMJYdnfAEM5JqTNkuWX3diqYENkWsIL0XggGG', client)
print((await contract.get_transactions(limit=10))[-1].out_msgs[0].destination)  # kQCdaMggjCXoW867yRXilPw2bu8Av9dSBlGGCdDPIGNLKM8N

LsClient

LsClient gets data from blockhain using lite servers (based on pytonlib)

To initialize LsClient:

client = LsClient(ls_index=2, default_timeout=30, addresses_form='user_friendly')
await client.init_tonlib()

LsClient is some more advanced, for e.g. you may need to compile binaries to use it.

DtonClient

Dton is a high level indexing GraphQL Api.

To initialize DtonClient:

client = DtonClient(
    key: str = None,  # dton api key
    addresses_form='user_friendly',  # addresses_form could be 'raw' or 'user_friendly'
    testnet=False,  # if testnet, all addresses will be in testnet form and base url will start with https://testnet.dton.io/
    private_graphql=False  # you can use private_graphql if you have an api key
)

Note: Dton currently doesn't support sending messages to blockchain, so you can't, for example, transfer toncoins using this provider

TonApiClient - currently v1

Note: in future TonApiClient will be overwritten to use v2 methods and current TonApiClient will be renamed into TonApiClientV1, because tonapi v1 endpoints soon will become unsupported

TonApi is a high level indexing Api.

To initialize TonApiClient:

client = TonApiClient(api_key, addresses_form)

TonApiClient hasn't run_get_method method, but it fast (cause of indexator), so you should use it if you want to scan a lot of transactions and contracts

Contracts

All Contracts are inherited from the base class Contract, which has .get_transactions(), .run_get_method(), .get_balance(), .get_state() methods. So you can use them with any type of Contract:

client = TonCenterClient(base_url='http://127.0.0.1:80/')

item = NftItem('EQDzyRLwjasHwP-y5c9rtoVi2iqriu-sbL3080FlCc-XyUG4', provider=client)
await item.update()

owner = Wallet(provider=client, address=item.sale.owner)
transactions = await owner.get_transactions(limit=2)

print(transactions[0], transactions[1])
# Transaction({"type": "out", "utime": 1677531709, "hash": "h+lVX0qK4T76QtRqC0FWWGhLptgPLM4MjSEbgKODcFc=", "value": 2500.0, "from": "EQBZVBXBpirFPOQ5Wmgi5Es2hDCRAfiT3i5JRy_gVsJOlpZv", "to": "EQBfAN7LfaUYgXZNw5Wc7GBgkEX2yhuJ5ka95J1JJwXXf4a8", "comment": "6017835"}) Transaction({"type": "in", "utime": 1677413260, "hash": "erk0nLWW9W3m9boFM+/9v0YSeRz1jJvpyiRQYEgN5AE=", "value": 1e-09, "from": "EQCPGzW1dJURRybL41Q3KYfzX4fZdQUeY8-7-TKyeR7f-7cU", "to": "EQBZVBXBpirFPOQ5Wmgi5Es2hDCRAfiT3i5JRy_gVsJOlpZv", "comment": ""})

print(await item.sale.get_balance()) # 75730000

You can init object of some Contract just specifying address and provider, but to get full data of this object you should call await object.update()

NFT Contracts

There are NftItem, NftCollection and NftItemSale classes.

item = NftItem('EQDzyRLwjasHwP-y5c9rtoVi2iqriu-sbL3080FlCc-XyUG4', provider=client)
await item.update()

collection = item.collection
await collection.update()

print(collection.metadata) #  {"name": "Whales Club", "description": "Collection limited to 10000 utility-enabled NFTs, where the token is your membership to the Whales Club. Join the club and participate in weekly Ambra token giveaways, have access to the most profitable Ton Whales decentralized staking pools and many other useful club privileges.", "external_link": "https://tonwhales.com/club", "external_url": "https://tonwhales.com/club", "image": "ipfs://QmZc5PwuyVKSV4urDTArqfDbkGVjkKs6q4dBk8kpPt1bqD/logo.gif", "social_links": ["https://t.me/tonwhalesnft", "https://t.me/tonwhalesnften", "https://twitter.com/whalescorp"], "cover_image": "ipfs://QmZc5PwuyVKSV4urDTArqfDbkGVjkKs6q4dBk8kpPt1bqD/cover.gif"}
items = await collection.get_collection_items()
print(len(items), items[0])  # 1621 NftItem({"address": "EQD6ufFjSIUJSkbVuV7w00ORT8UvoMLQ9RDZ1lJ8sYh3cOIx"})

sale = item.sale
print(sale.price_value, sale.owner) #  200000000000 EQBZVBXBpirFPOQ5Wmgi5Es2hDCRAfiT3i5JRy_gVsJOlpZv

Jetton Contracts

There are Jetton and JettonWallet classes.

client = LsClient(ls_index=2, default_timeout=30)
await client.init_tonlib()

jetton = Jetton('EQBl3gg6AAdjgjO2ZoNU5Q5EzUIl8XMNZrix8Z5dJmkHUfxI', provider=client)
print(jetton)  # Jetton({"address": "EQBl3gg6AAdjgjO2ZoNU5Q5EzUIl8XMNZrix8Z5dJmkHUfxI"})

await jetton.update()
print(jetton)  # Jetton({"supply": 4600000000000000000, "address": "EQBl3gg6AAdjgjO2ZoNU5Q5EzUIl8XMNZrix8Z5dJmkHUfxI", "decimals": 9, "symbol": "LAVE", "name": "Lavandos", "description": "This is a universal token for use in all areas of the decentralized Internet in the TON blockchain, web3, Telegram bots, TON sites. Issue of 4.6 billion coins. Telegram channels: Englishversion: @lave_eng \u0420\u0443\u0441\u0441\u043a\u043e\u044f\u0437\u044b\u0447\u043d\u0430\u044f \u0432\u0435\u0440\u0441\u0438\u044f: @lavet", "image": "https://i.ibb.co/Bj5KqK4/IMG-20221213-115545-207.png", "token_supply": 4600000000.0})

jetton_wallet = await jetton.get_jetton_wallet('EQBvW8Z5huBkMJYdnfAEM5JqTNkuWX3diqYENkWsIL0XggGG')  # for TonCenterClient and LsClient
print(jetton_wallet)  # JettonWallet({"address": "EQDgCBnCncRp4jOi3CMeLn-b71gymAX3W28YZT3Dn0a2dKj-"})

await jetton_wallet.update()
print(jetton_wallet)  # JettonWallet({"address": "EQDgCBnCncRp4jOi3CMeLn-b71gymAX3W28YZT3Dn0a2dKj-", "balance": 10000000000000, "owner": "EQBvW8Z5huBkMJYdnfAEM5JqTNkuWX3diqYENkWsIL0XggGG", "jetton_master_address": "EQBl3gg6AAdjgjO2ZoNU5Q5EzUIl8XMNZrix8Z5dJmkHUfxI"})

my_wallet_mnemonics = []
my_wallet = Wallet(provider=client, mnemonics=my_wallet_mnemonics, version='v4r2')
await my_wallet.transfer_jetton(destination_address='address', jetton_master_address=jetton.address, jettons_amount=1000, fee=0.15)  # for TonCenterClient and LsClient
await my_wallet.transfer_jetton_by_jetton_wallet(destination_address='address', jetton_wallet='your jetton wallet address', jettons_amount=1000, fee=0.1)  # for all clients

Wallet contracts

Currently there is only Wallet class (will add HighLoadWallet and MultiSigWallet in future versions).

You can create new wallet just calling Wallet(provider, wallet_version), check existing wallet Wallet(provider, address) or enter wallet Wallet(provider, mnemonics, wallet_version)

client = LsClient(ls_index=2, default_timeout=20)
await client.init_tonlib()

my_wallet_mnemonics = []
my_wallet = Wallet(provider=client, mnemonics=my_wallet_mnemonics, version='v4r2')
my_wallet_nano_balance = await my_wallet.get_balance()

new_wallet = Wallet(provider=client)
print(new_wallet.address, new_wallet.mnemonics, my_wallet_nano_balance)  # EQBcMK8CBrZKfSYdvT8FDVo1TxZV_d3Lz-xPyGp8c7mUacko ['federal', 'memory', 'scare', 'exact', 'extend', 'rain', 'private', 'ribbon', 'inspire', 'capital', 'arrow', 'glimpse', 'toy', 'double', 'man', 'speak', 'imitate', 'hint', 'dinner', 'oblige', 'rather', 'answer', 'unfold', 'small'] 496348289

non_bounceable_new_wallet_address = Address(new_wallet.address).to_string(True, True, False)
await my_wallet.transfer_ton(destination_address=non_bounceable_new_wallet_address, amount=0.02, message='just random comment')
await new_wallet.deploy()

print(await new_wallet.get_state())  # active

Transactions

Class Transaction has .to_dict() and .to_dict_user_friendly() methods. The first one returns full data of transaction, and the second one only user-friendly data of transaction

status - True if computation and action phases have returned zero code.

client = TonApiClient()
wallet = Wallet(provider=client, address='EQBvW8Z5huBkMJYdnfAEM5JqTNkuWX3diqYENkWsIL0XggGG')
trs = await wallet.get_transactions(limit=1) 
print(trs[0].to_dict())  # {'utime': 1677658702, 'fee': 7384081, 'data': 'a lot of bytes :)', 'hash': 'skqFysIHksJDkH8Sy4UAKmQSuW95WGS6V/XD/QaJCdE=', 'in_msg': {'created_lt': 35690250000001, 'source': '', 'destination': 'EQBvW8Z5huBkMJYdnfAEM5JqTNkuWX3diqYENkWsIL0XggGG', 'value': 0, 'msg_data': 'a lot of bytes :'}, 'out_msgs': [{'created_lt': 35690250000002, 'source': 'EQBvW8Z5huBkMJYdnfAEM5JqTNkuWX3diqYENkWsIL0XggGG', 'destination': 'EQDgCBnCncRp4jOi3CMeLn-b71gymAX3W28YZT3Dn0a2dKj-', 'value': 100000000, 'msg_data': 'te6ccgEBAQEAVwAAqg+KfqUAAAAAAAAAAF6NSlEACADvv6jNfMa6nPxbbgyeiO7riR4Cq0JAynas1pLFqNpq9wAd9/UZr5jXU5+LbcGT0R3dcSPAVWhIGU7VmtJYtRtNXsA='}]}
print(trs[0].to_dict_user_friendly())  # {'type': 'out', 'utime': 1677658702, 'status': True, 'hash': 'skqFysIHksJDkH8Sy4UAKmQSuW95WGS6V/XD/QaJCdE=', 'value': 0.1, 'from': 'EQBvW8Z5huBkMJYdnfAEM5JqTNkuWX3diqYENkWsIL0XggGG', 'to': 'EQDgCBnCncRp4jOi3CMeLn-b71gymAX3W28YZT3Dn0a2dKj-', 'comment': ''}

Note: .to_dict_user_friendly() works good with many recipients in one transaction

Messages

You can check the type of message using .try_detect_type() method.

client = TonCenterClient()

contract = Contract('EQB5DER03H1uhKGX6BJh_IWa_zV9MzvH2lcy6t30tZ9k4RSL', client)
print((await contract.get_transactions())[-1].in_msg.try_detect_type())  # JettonTransferNotificationMessage

contract = Contract('EQB5QP6tAVlWBXKhMN9TynyusIR8_oTuN10NozaOfpFzAXDj', client)
print((await contract.get_transactions())[-1].in_msg.try_detect_type())  # JettonInternalTransferMessage