From 4a51efed01254c0df29b5cddf0b9350aac54c22a Mon Sep 17 00:00:00 2001 From: user Date: Fri, 1 Mar 2024 23:11:42 +0300 Subject: [PATCH] dev@locazia: add service wallet --- .gitignore | 1 + app/__main__.py | 2 +- app/api/routes/_blockchain.py | 2 +- app/api/routes/_system.py | 9 +++ app/bot/routers/content.py | 2 +- app/bot/routers/home.py | 2 +- app/bot/routers/tonconnect.py | 2 +- app/core/_blockchain/ton/wallet_v3cr3.py | 76 ++++++++++++++++++++++++ app/core/_config.py | 4 ++ app/core/_secrets.py | 30 ++++++++++ app/core/background/uploader_service.py | 7 +++ app/core/models/_config.py | 32 ++++++++++ docker-compose.yml | 3 + 13 files changed, 167 insertions(+), 5 deletions(-) create mode 100644 app/core/_blockchain/ton/wallet_v3cr3.py create mode 100644 app/core/_secrets.py create mode 100644 app/core/models/_config.py diff --git a/.gitignore b/.gitignore index 7fe0101..b3878f2 100644 --- a/.gitignore +++ b/.gitignore @@ -6,3 +6,4 @@ sqlStorage playground alembic.ini .DS_Store +messages.pot diff --git a/app/__main__.py b/app/__main__.py index 254dac2..d609a81 100644 --- a/app/__main__.py +++ b/app/__main__.py @@ -78,7 +78,6 @@ if __name__ == '__main__': app.run(host='0.0.0.0', port=SANIC_PORT) else: - loop = asyncio.get_event_loop() startup_fn = None if startup_target == 'indexator': from app.core.background.indexator_service import main as target_fn @@ -88,6 +87,7 @@ if __name__ == '__main__': startup_fn = startup_fn or target_fn assert startup_fn + loop = asyncio.get_event_loop() try: loop.run_until_complete(startup_fn()) except BaseException as e: diff --git a/app/api/routes/_blockchain.py b/app/api/routes/_blockchain.py index 1ebfa92..47c6f99 100644 --- a/app/api/routes/_blockchain.py +++ b/app/api/routes/_blockchain.py @@ -1,4 +1,4 @@ -from app.core.models._blockchain.ton.connect import TonConnect, unpack_wallet_info +from app.core._blockchain.ton.connect import TonConnect, unpack_wallet_info from sanic import response from datetime import datetime, timedelta diff --git a/app/api/routes/_system.py b/app/api/routes/_system.py index 8373b67..dc1c9bb 100644 --- a/app/api/routes/_system.py +++ b/app/api/routes/_system.py @@ -1,4 +1,6 @@ from sanic import response +from base58 import b58encode +from app.core._secrets import hot_pubkey, service_wallet import subprocess @@ -8,6 +10,13 @@ def get_git_info(): return branch_name, commit_hash +async def s_api_system(request): + return response.json({ + 'id': b58encode(hot_pubkey).decode(), + 'ton_address': service_wallet.address.to_string(1, 1, 1) + }) + + async def s_api_system_version(request): branch_name, commit_hash = get_git_info() return response.json({ diff --git a/app/bot/routers/content.py b/app/bot/routers/content.py index 336c882..d83b7ce 100644 --- a/app/bot/routers/content.py +++ b/app/bot/routers/content.py @@ -5,7 +5,7 @@ from app.core._utils.tg_process_template import tg_process_template from app.core._keyboards import get_inline_keyboard from app.core.logger import logger from app.core.models.wallet_connection import WalletConnection -from app.core.models._blockchain.ton.connect import TonConnect, unpack_wallet_info +from app.core._blockchain.ton.connect import TonConnect, unpack_wallet_info from app.core._config import WEB_APP_URLS diff --git a/app/bot/routers/home.py b/app/bot/routers/home.py index 29e2125..5ef58df 100644 --- a/app/bot/routers/home.py +++ b/app/bot/routers/home.py @@ -2,7 +2,7 @@ from app.core._utils.tg_process_template import tg_process_template from app.core._keyboards import get_inline_keyboard from app.core.logger import logger from app.core.models.wallet_connection import WalletConnection -from app.core.models._blockchain.ton.connect import TonConnect, unpack_wallet_info +from app.core._blockchain.ton.connect import TonConnect, unpack_wallet_info from app.core._config import WEB_APP_URLS from aiogram import types, Router, F from aiogram.filters import Command diff --git a/app/bot/routers/tonconnect.py b/app/bot/routers/tonconnect.py index d7bf70d..d2af717 100644 --- a/app/bot/routers/tonconnect.py +++ b/app/bot/routers/tonconnect.py @@ -6,7 +6,7 @@ from aiogram.filters import Command from app.core._keyboards import get_inline_keyboard from app.core._utils.tg_process_template import tg_process_template from app.core.logger import make_log -from app.core.models._blockchain.ton.connect import TonConnect, unpack_wallet_info +from app.core._blockchain.ton.connect import TonConnect, unpack_wallet_info from app.core.models.wallet_connection import WalletConnection from app.bot.routers.home import send_connect_wallets_list, send_home_menu from datetime import datetime, timedelta diff --git a/app/core/_blockchain/ton/wallet_v3cr3.py b/app/core/_blockchain/ton/wallet_v3cr3.py new file mode 100644 index 0000000..129078c --- /dev/null +++ b/app/core/_blockchain/ton/wallet_v3cr3.py @@ -0,0 +1,76 @@ +from decimal import Decimal +from time import time + +from tonsdk.boc import Cell, begin_cell +from tonsdk.contract import Contract +from tonsdk.contract.wallet import WalletContract +from tonsdk.utils import Address + +WALLET_V3_CR3_CODE_HEX = 'b5ee9c7241021001000162000114ff00f4a413f4bcf2c80b01020120020f02014803080202ce0407020120050600510ccc741d35c87e900c3e910c7b513420405035c874ffcc19aea6f0003cb41a750c341ffc00a456f8a000730074c7c860802ab06ea65b0874c1f50c007ec0380860802aa82ea384cc407cb81a75350c087ec100743b47bb54fb55380c0c7000372103fcbc20002349521d74ac2009801d401d022f00101e85b8020120090c0201200a0b0019bb39ced44d08020d721d3ff3080011b8c97ed44d0d31f3080201580d0e001bb71e3da89a1020481ae43a61e610001bb49f3da89a1020281ae43a7fe61000f6f28308d71820d31fd31fd31f3001f823bbf2d06ced44d0d31fd3ffd31fd3ff305151baf2e0695132baf2e06924f901541066f910f2e069f8007054715226ed44ed45ed479131ed67ed65ed64747fed118e1104d430d023c000917f9170e2f002045023ed41edf101f2ff04a4c8cb1f13cbffcb1fcbffcb0fc9ed542675fc7e' + + +class WalletV3CR3(WalletContract): + def __init__(self, **kwargs): + kwargs['code'] = Cell.one_from_boc(WALLET_V3_CR3_CODE_HEX) + kwargs['subwallet_id'] = kwargs.get('subwallet_id', 0) + super().__init__(**kwargs) + + def create_data_cell(self): + return ( + begin_cell() + .store_uint(0, 32) + .store_bytes(self.options['public_key']) + .store_uint(self.options['subwallet_id'], 32) + .store_uint(0, 256) + .end_cell() + ) + + def create_signing_message(self, seqno=None, timeout=60) -> Cell: + seqno = seqno or 0 + return begin_cell().store_uint(self.options['subwallet_id'], 32).store_uint(int(time() + timeout), + 32).store_uint(seqno, 32).end_cell() + + def create_transfer_message(self, recipients_list: list, seqno: int, timeout=60, dummy_signature=False) -> dict: + signing_message = begin_cell().store_cell(self.create_signing_message(seqno=seqno, timeout=timeout)) + _commands = begin_cell() + for i, recipient in enumerate(recipients_list): + if not recipient: continue + payload_cell = Cell() + if recipient.get('payload'): + if type(recipient['payload']) == str: + if len(recipient['payload']) > 0: + payload_cell.bits.write_uint(0, 32) + payload_cell.bits.write_string(recipient['payload']) + elif hasattr(recipient['payload'], 'refs'): + payload_cell = recipient['payload'] + else: + payload_cell.bits.write_bytes(recipient['payload']) + + order_header = Contract.create_internal_message_header( + Address(recipient['address']), Decimal(recipient['amount']) + ) + order = Contract.create_common_msg_info( + order_header, recipient.get('state_init'), payload_cell + ) + _commands = _commands.store_ref( + begin_cell() + .store_uint(0xAAC1, 32) + .store_uint8(recipient.get('send_mode', 0)) + .store_ref(order).end_cell() + ) + + signing_message = signing_message.store_ref(_commands.end_cell()) + return self.create_external_message(signing_message.end_cell(), seqno, dummy_signature) + + def create_upgrade_message(self, new_code: Cell, new_data: Cell) -> dict: + signing_message = begin_cell().store_cell(self.create_signing_message()) + _commands = begin_cell() + _commands = _commands.store_ref( + begin_cell() + .store_uint(0xAAA0, 32) + .store_ref(new_code) + .store_ref(new_data) + .end_cell() + ) + signing_message = signing_message.store_ref(_commands.end_cell()) + return self.create_external_message(signing_message.end_cell(), 0, True) diff --git a/app/core/_config.py b/app/core/_config.py index 7196831..df03a28 100644 --- a/app/core/_config.py +++ b/app/core/_config.py @@ -11,6 +11,10 @@ UPLOADS_DIR = os.getenv('UPLOADS_DIR', '/app/data') if not os.path.exists(UPLOADS_DIR): os.makedirs(UPLOADS_DIR) +CONFIG_FILE = os.getenv('CONFIG_FILE') or f"{UPLOADS_DIR}/../config" +from app.core.models._config import ConfigFile +config_manager = ConfigFile(CONFIG_FILE) + TELEGRAM_API_KEY = os.environ.get('TELEGRAM_API_KEY') assert TELEGRAM_API_KEY, "Telegram API_KEY required" diff --git a/app/core/_secrets.py b/app/core/_secrets.py new file mode 100644 index 0000000..a68cc1c --- /dev/null +++ b/app/core/_secrets.py @@ -0,0 +1,30 @@ +from nacl.bindings import crypto_sign_seed_keypair +from app.core._config import config_manager +from app.core.logger import make_log +from app.core._blockchain.ton.wallet_v3cr3 import WalletV3CR3 +from os import urandom, getenv +from tonsdk.utils import Address + + +def load_hot_pair(): + hot_seed = config_manager.get('private_key') + if hot_seed is None: + make_log("HotWallet", "No seed found, generating new one", level='info') + hot_seed = urandom(32).hex() + config_manager.set('private_key', hot_seed) + return load_hot_pair() + + public_key, private_key = crypto_sign_seed_keypair(bytes.fromhex(hot_seed)) + return public_key, private_key + + +_extra_ton_wallet_options = {} +if getenv('TON_CUSTOM_WALLET_ADDRESS'): + _extra_ton_wallet_options['wallet_address'] = Address(getenv('TON_CUSTOM_WALLET_ADDRESS')) + +hot_pubkey, hot_privkey = load_hot_pair() +service_wallet = WalletV3CR3( + private_key=hot_privkey, + public_key=hot_pubkey, + **_extra_ton_wallet_options +) diff --git a/app/core/background/uploader_service.py b/app/core/background/uploader_service.py index c718eb9..30ea16e 100644 --- a/app/core/background/uploader_service.py +++ b/app/core/background/uploader_service.py @@ -1,11 +1,18 @@ from app.core.logger import make_log +from app.core.storage import db_session import asyncio async def main(): make_log("Uploader", "Service started", level="info") + while True: + # make_log("Uploader", "Service running", level="debug") + # with db_session() as session: + # for stored_content in session.query(StoredContent).filter(StoredContent.uploaded == False).all(): + # pass + await asyncio.sleep(5) if __name__ == '__main__': loop = asyncio.get_event_loop() diff --git a/app/core/models/_config.py b/app/core/models/_config.py new file mode 100644 index 0000000..3e3c009 --- /dev/null +++ b/app/core/models/_config.py @@ -0,0 +1,32 @@ +from app.core.logger import make_log +from json import loads as json_loads +from json import dumps as json_dumps + + +class ConfigFile: + def __init__(self, filepath: str): + self.filepath = filepath + with open(self.filepath, 'r') as file: + self.values = json_loads(file.read()) + + assert isinstance(self.values, dict) + + def get(self, key, default=None): + return self.values.get(key, default) + + def save(self): + with open(self.filepath, 'w') as file: + file.write( + json_dumps( + self.values, + indent=4, + sort_keys=True + ) + ) + + def set(self, key, value): + self.values[key] = value + self.save() + make_log("ConfigFile", f"Edited {key}", level="debug") + + diff --git a/docker-compose.yml b/docker-compose.yml index 8910a75..928e7ec 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -28,6 +28,7 @@ services: volumes: - ./logs:/app/logs - ./storedContent:/app/data + - ./activeConfig:/app/config depends_on: maria_db: condition: service_healthy @@ -44,6 +45,7 @@ services: volumes: - ./logs:/app/logs - ./storedContent:/app/data + - ./activeConfig:/app/config depends_on: maria_db: condition: service_healthy @@ -60,6 +62,7 @@ services: volumes: - ./logs:/app/logs - ./storedContent:/app/data + - ./activeConfig:/app/config depends_on: maria_db: condition: service_healthy