182 lines
6.3 KiB
Python
182 lines
6.3 KiB
Python
import os
|
|
import pickle
|
|
from decimal import Decimal
|
|
from time import sleep
|
|
|
|
from Crypto.Cipher import AES
|
|
from Crypto.Protocol.KDF import PBKDF2
|
|
from tonsdk.boc import begin_cell
|
|
from tonsdk.contract import Contract
|
|
from tonsdk.contract.token.ft import JettonWallet
|
|
from tonsdk.contract.token.nft import NFTItem
|
|
from tonsdk.utils import Address
|
|
|
|
from toncenter import TonCenter
|
|
from wallet_contract import WalletV3CR3
|
|
|
|
ACTION_TYPES_DESC = {
|
|
1: "Send TON",
|
|
2: "Send Jetton",
|
|
3: "Send NFT",
|
|
4: "Update wallet contract",
|
|
7: "New decentralized note",
|
|
}
|
|
|
|
|
|
def unpack_wallet(wallet_settings):
|
|
kwargs = {}
|
|
if wallet_settings['custom_address']:
|
|
kwargs['address'] = Address(wallet_settings['custom_address'])
|
|
|
|
wallet = WalletV3CR3(
|
|
public_key=bytes.fromhex(wallet_settings['public_key']),
|
|
private_key=bytes.fromhex(wallet_settings['secret_key']),
|
|
subwallet_id=wallet_settings['subwallet_id'],
|
|
trusted_hashpart=0,
|
|
**kwargs
|
|
)
|
|
return wallet
|
|
|
|
|
|
def generate_key(password, salt, iterations=100000):
|
|
key = PBKDF2(password, salt, dkLen=32, count=iterations)
|
|
return key
|
|
|
|
|
|
def encrypt_data(data: bytes, password: bytes) -> bytes:
|
|
salt = os.urandom(16)
|
|
key = generate_key(password, salt)
|
|
cipher = AES.new(key, AES.MODE_GCM)
|
|
ciphertext, tag = cipher.encrypt_and_digest(data)
|
|
return pickle.dumps([salt, ciphertext, tag, cipher.nonce])
|
|
|
|
|
|
def decrypt_data(data: bytes, password: bytes) -> bytes:
|
|
salt, ciphertext, tag, nonce = pickle.loads(data)
|
|
key = generate_key(password, salt)
|
|
cipher = AES.new(key, AES.MODE_GCM, nonce=nonce)
|
|
plaintext = cipher.decrypt_and_verify(ciphertext, tag)
|
|
return plaintext
|
|
|
|
|
|
async def perform_action(wallet_settings, _commands):
|
|
wallet = unpack_wallet(wallet_settings)
|
|
toncenter = TonCenter(testnet=wallet_settings.get("is_testnet", True))
|
|
|
|
async def get_wallet_seqno():
|
|
result = await toncenter.run_get_method(wallet.address.to_string(1, 1, 1), 'seqno')
|
|
if result.get('exit_code', -1) != 0:
|
|
seqno = 0
|
|
else:
|
|
seqno = int(result['stack'][0][1], 16)
|
|
|
|
sleep(1)
|
|
return seqno
|
|
|
|
seqno = -100
|
|
_new_deploy = False
|
|
while seqno < 0:
|
|
seqno = await get_wallet_seqno()
|
|
if seqno < 1:
|
|
_new_deploy = True
|
|
result = await toncenter.send_boc(
|
|
wallet.create_external_message(
|
|
begin_cell()
|
|
.store_cell(wallet.create_signing_message(0, 60))
|
|
.store_ref(begin_cell().end_cell())
|
|
.end_cell(), 0
|
|
)['message'].to_boc(False)
|
|
)
|
|
print("Init transaction broadcast result:", result)
|
|
sleep(7)
|
|
|
|
if _new_deploy:
|
|
seqno += 1
|
|
|
|
print(f"=== Current seqno: {seqno} ===")
|
|
assert input("Broadcast transaction? (yes/NO): ").lower() == 'yes'
|
|
|
|
signing_message = begin_cell().store_cell(wallet.create_signing_message(seqno, 60))
|
|
signing_message = signing_message.store_ref(_commands)
|
|
|
|
query = wallet.create_external_message(
|
|
signing_message.end_cell(), seqno, False
|
|
)
|
|
print("Raw signed transaction:", query['message'].to_boc(False).hex())
|
|
|
|
result = await toncenter.send_boc(query['message'].to_boc(False))
|
|
print("Transaction broadcast result:", result)
|
|
|
|
|
|
def serialize_command(action, response_address: Address = None):
|
|
if action['type'] == 1:
|
|
return (
|
|
begin_cell()
|
|
.store_uint(0xAAC1, 32)
|
|
.store_uint(action['args'].get('send_mode', 1), 8)
|
|
.store_ref(
|
|
Contract.create_common_msg_info(
|
|
Contract.create_internal_message_header(
|
|
Address(action['args']['destination']), Decimal(action['args']['amount'])
|
|
), action['args'].get('state_init'), action['args'].get('payload_cell')
|
|
)
|
|
)
|
|
.end_cell()
|
|
)
|
|
elif action['type'] == 2:
|
|
return (
|
|
begin_cell()
|
|
.store_uint(0xAAC1, 32)
|
|
.store_uint(action['args'].get('send_mode', 1), 8)
|
|
.store_ref(
|
|
Contract.create_common_msg_info(
|
|
Contract.create_internal_message_header(
|
|
Address(action['args']['jetton_wallet']), Decimal(1e8)
|
|
), None, JettonWallet().create_transfer_body(
|
|
Address(action['args']['recipient']), action['args']['amount'],
|
|
forward_amount=1e7, forward_payload=(
|
|
(bytes(4) + action['args']['comment']) if action['args'].get('comment') else None
|
|
),
|
|
response_address=response_address
|
|
)
|
|
)
|
|
)
|
|
.end_cell()
|
|
)
|
|
elif action['type'] == 3:
|
|
return (
|
|
begin_cell()
|
|
.store_uint(0xAAC1, 32)
|
|
.store_uint(action['args'].get('send_mode', 1), 8)
|
|
.store_ref(
|
|
Contract.create_common_msg_info(
|
|
Contract.create_internal_message_header(
|
|
Address(action['args']['nft_address']), Decimal(1e8)
|
|
), None, NFTItem().create_transfer_body(
|
|
Address(action['args']['recipient']), response_address=response_address,
|
|
forward_amount = 1e7
|
|
)
|
|
)
|
|
)
|
|
.end_cell()
|
|
)
|
|
elif action['type'] == 4:
|
|
return (
|
|
begin_cell()
|
|
.store_uint(0xAAA0, 32)
|
|
.store_ref(action['args']['new_code'])
|
|
.store_ref(action['args']['new_data'])
|
|
.end_cell()
|
|
)
|
|
|
|
raise Exception('Unsupported action type')
|
|
|
|
|
|
def print_actions(actions, prefix='\n' + "=== {actions_count} actions selected"):
|
|
if not actions: return
|
|
print(prefix.format(actions_count=len(actions)))
|
|
for action in actions:
|
|
print(f"Action {ACTION_TYPES_DESC[action['type']]}: {action['args']}")
|
|
|
|
print("===")
|