Example #1
0
def get_address_balance(address, virtual=None):
    if virtual:
        balances = {'BIP': virtual}
        balances_bip = {'BIP': Decimal(to_bip(virtual))}
    else:
        balances = NodeAPI.get_balance(address)['balance']
        balances_bip = effective_balance(balances)

    main_coin, main_balance_bip = max(balances_bip.items(), key=lambda i: i[1])
    bip_value_total = truncate(float(main_balance_bip), 4)

    usd_value_total = truncate(bip_to_usdt(bip_value_total), 4)
    usd_rates = fiat_to_usd_rates()
    local_fiat = 'RUB'
    local_fiat_value = truncate(usd_value_total * usd_rates[local_fiat], 4)
    coin_value = to_bip(balances[main_coin])
    coin_value = truncate(float(effective_value(coin_value, main_coin)), 4)
    return {
        'balance': {
            'coin': main_coin,
            'value': coin_value,
            'bip_value': bip_value_total,
            'usd_value': usd_value_total,
            'local_fiat': local_fiat,
            'local_fiat_value': local_fiat_value
        },
        'fiat_rates': {
            symbol: rate
            for symbol, rate in usd_rates.items()
            if symbol in ['UAH', 'USD', 'RUB']
        }
    }
Example #2
0
def send_coins(wallet: PushWallet, to=None, amount=None, payload='', wait=True, gas_coin=None):
    private_key = MinterWallet.create(mnemonic=wallet.mnemonic)['private_key']
    response = NodeAPI.get_balance(wallet.address)
    nonce = int(response['transaction_count']) + 1
    balances = response['balance']
    balances_bip = effective_balance(balances)
    main_coin, main_balance_bip = max(balances_bip.items(), key=lambda i: i[1])
    main_balance = float(to_bip(balances[main_coin]))

    gas_coin, tx_fee = find_gas_coin(balances, get_fee=True, payload=payload)
    gas_coin_balance = float(to_bip(balances.get(gas_coin, 0)))

    if not gas_coin or not tx_fee or gas_coin_balance < tx_fee:
        return 'Not enough balance to pay commission'
    tx_fee = float(tx_fee)

    # если в обычной пересылке пришлют сумму без учета комиссии - не будем мучать ошибками
    amount = main_balance - tx_fee if amount == truncate(main_balance, 4) \
        and gas_coin == main_coin and not payload else amount
    tx_fee = tx_fee if gas_coin == main_coin else 0
    if amount > main_balance - tx_fee:
        return 'Not enough balance'
    tx = send_coin_tx(private_key, main_coin, amount, to, nonce, payload=payload, gas_coin=gas_coin)
    NodeAPI.send_tx(tx, wait=wait)
    return True
Example #3
0
 def get(self, campaign_id):
     campaign = RewardCampaign.get_or_none(link_id=campaign_id,
                                           status='open')
     if not campaign:
         return {}
     balances = NodeAPI.get_balance(campaign.address)['balance']
     campaign_balance = to_bip(balances.get(campaign.coin, '0'))
     # if not campaign_balance:
     #     return {}
     times_completed = campaign.times_completed
     reward = float(to_bip(campaign.action_reward))
     value_spent = times_completed * reward
     return {
         'id': campaign.link_id,
         'name': campaign.name,
         'address': campaign.address,
         'count': campaign.count,
         'coin': campaign.coin,
         'balance': float(campaign_balance),
         'times_completed': times_completed,
         'value_spent': value_spent,
         'icon_url': campaign.icon.url if campaign.icon else None,
         'action': {
             'type': campaign.action_type,
             'link': campaign.action_params['link'],
             'reward': reward,
         }
     }
Example #4
0
def push_balance(link_id):
    """
    swagger: swagger/core/push-balance.yml
    """
    payload = request.get_json() or {}
    password = payload.get('password')

    wallet = PushWallet.get_or_none(link_id=link_id)
    if not wallet:
        return jsonify({'error': 'Link does not exist'}), HTTPStatus.NOT_FOUND

    if not wallet.auth(password):
        return jsonify({'error':
                        'Incorrect password'}), HTTPStatus.UNAUTHORIZED

    # зарефакторить
    virtual_balance = None if wallet.virtual_balance == '0' else wallet.virtual_balance
    if virtual_balance is not None and not wallet.seen:
        if wallet.sent_from:
            from_w = PushWallet.get(link_id=wallet.sent_from)
            result = send_coins(from_w,
                                wallet.address,
                                amount=to_bip(wallet.virtual_balance),
                                wait=False)
            if result is not True:
                return jsonify({'error':
                                result}), HTTPStatus.INTERNAL_SERVER_ERROR
            wallet.virtual_balance = '0'
            wallet.save()
        else:
            cmp = PushCampaign.get_or_none(id=wallet.campaign_id)
            cmp_wallet = PushWallet.get(link_id=cmp.wallet_link_id)
            result = send_coins(cmp_wallet,
                                wallet.address,
                                amount=to_bip(wallet.virtual_balance),
                                wait=False)
            if result is not True:
                return jsonify({'error':
                                result}), HTTPStatus.INTERNAL_SERVER_ERROR
            wallet.virtual_balance = '0'
            recipient = Recipient.get(wallet_link_id=wallet.link_id)
            recipient.linked_at = datetime.utcnow()
            recipient.save()
            wallet.save()

    if not wallet.seen:
        wallet.seen = True
        wallet.save()
    balance = get_address_balance(wallet.address, virtual=virtual_balance)
    response = {'address': wallet.address, **balance}
    return jsonify(response)
Example #5
0
    def api_repr(self):
        price_patch = None
        if not self.price_list_fiat and self.price_fiat:
            price_patch = {
                'price_fiat': self.price_fiat,
                'price_bip': float(to_bip(self.price_pip)),
            }
        elif not self.price_list_fiat or (self.price_list_fiat and self.price_list_fiat[0] == 0):
            price_patch = {
                'price_fiat_min': self.price_fiat_min,
                'price_fiat_max': self.price_fiat_min,
                'price_fiat_step': self.price_fiat_step
            }
        elif self.price_list_fiat:
            price_patch = {
                'price_list_fiat': self.price_list_fiat
            }

        return {
            'slug': self.slug,
            'currency': self.currency,
            'coin': self.coin,
            'name': self.title,
            **price_patch
        }
Example #6
0
def find_gas_coin(balances):
    for coin, balance_pip in balances.items():
        tx_fee = estimate_custom_fee(coin)
        if not tx_fee:
            continue
        if to_bip(balance_pip) - tx_fee >= 0:
            return coin
Example #7
0
def find_gas_coin(balances, get_fee=False, payload=''):
    for coin, balance_pip in balances.items():
        tx_fee = estimate_custom_fee(coin, payload=payload)
        if not tx_fee:
            continue
        if to_bip(balance_pip) - tx_fee >= 0:
            return coin if not get_fee else (coin, tx_fee)
    return None if not get_fee else (None, None)
Example #8
0
    def get_delegations(self, by_node='', min_delegated=0, stop_list=None):
        """
        Получаем всех делегаторов монеты self.token
        :param stop_list: list ['Mx...1', 'Mx...2', ...]
        :param by_node: str 'Mp....'
        :param min_delegated: float/int Минимальное количество делегированных токенов
        :return: dict {address: delegated_tokens, ...}
        """

        stop_list = stop_list or [] or self.stop_list
        min_delegated = min_delegated or self.min_delegated

        # Получаем стейки
        stakes = []

        # По отдельной ноде
        if by_node:
            stakes = self.API.get_candidate(by_node)['result']['stakes']
        # По всем нодам
        else:
            validators = self.API.get_validators(limit=256)['result']
            pub_keys = [v['pub_key'] for v in validators]
            print(f"Получаем стейки валидаторов")
            for i, pub_key in enumerate(pub_keys, 1):
                print(f"{i} / {len(validators)}")
                stakes += self.API.get_candidate(pub_key)['result']['stakes']

        # Получаем словарь со всеми делегаторами и суммарное количество заделегированных токенов
        delegators = {}
        for stake in stakes:
            if (stake['coin'] == self.token
                    or self.token is None) and stake['owner'] not in stop_list:
                if stake['owner'] not in delegators.keys():
                    delegators[stake['owner']] = to_bip(stake['value'])
                else:
                    delegators[stake['owner']] += to_bip(stake['value'])

        # Фильтруем делегаторов по минимальной сумме
        if min_delegated > 0:
            delegators = {
                k: v
                for k, v in delegators.items() if v >= min_delegated
            }

        return delegators
Example #9
0
def send_coin_tx(pk, coin, value, to, nonce, gas_coin=BASE_COIN, payload=''):
    to = to.strip()
    value = to_bip(value) if isinstance(value, str) else value
    tx = MinterSendCoinTx(coin,
                          to,
                          value,
                          nonce=nonce,
                          gas_coin=gas_coin,
                          payload=payload)
    tx.sign(pk)
    return tx
Example #10
0
def generate_push(campaign):
    response = NodeAPI.get_balance(campaign.address)
    balances = response['balance']
    campaign_balance = to_bip(balances.get(campaign.coin, '0'))
    if not campaign_balance:
        logging.info(
            f'Campaign {campaign.link_id} {campaign.name}: balance too low {campaign_balance}'
        )
        return

    tx_fee = estimate_custom_fee(campaign.coin)
    reward = to_bip(campaign.action_reward)

    if campaign_balance < reward + tx_fee:
        logging.info(
            f'Campaign {campaign.link_id} {campaign.name}: balance too low {campaign_balance}'
        )
        return

    push = generate_and_save_wallet()
    private_key = MinterWallet.create(
        mnemonic=campaign.mnemonic)['private_key']
    nonce = int(response['transaction_count']) + 1

    tx = send_coin_tx(private_key,
                      campaign.coin,
                      reward + tx_fee,
                      push.address,
                      nonce,
                      gas_coin=campaign.coin)
    NodeAPI.send_tx(tx, wait=True)
    logging.info(
        f'Campaign {campaign.link_id} {campaign.name} rewarded {reward} {campaign.coin}, fee {tx_fee}'
    )

    campaign.times_completed += 1
    if campaign.times_completed == campaign.count:
        campaign.status = 'close'
        logging.info(f'Campaign {campaign.link_id} {campaign.name} finished!')
    campaign.save()
    return YYY_PUSH_URL + push.link_id
Example #11
0
def estimate_custom_fee(coin):
    if coin == BASE_COIN:
        return Decimal('0.01')
    coin_info = NodeAPI.get_coin_info(coin)
    if coin_info['reserve_balance'] < to_pip(MIN_RESERVE_BIP + 0.01):
        return
    w = MinterWallet.create()
    tx = send_coin_tx(w['private_key'],
                      coin,
                      0,
                      w['address'],
                      1,
                      gas_coin=coin)
    return to_bip(NodeAPI.estimate_tx_commission(tx.signed_tx)['commission'])
Example #12
0
def effective_balance(balances):
    balances_bip = {}
    for coin, balance in balances.items():
        if coin == BASE_COIN:
            balances_bip[coin] = max(Decimal(0),
                                     to_bip(balance) - Decimal('0.01'))
            continue

        # ROUBLE WORKAROUND
        coin_info = NodeAPI.get_coin_info(coin)
        if coin_info['reserve_balance'] < to_pip(
                Decimal(MIN_RESERVE_BIP) + Decimal('0.01')):
            return {coin: Decimal(0)}

        est_sell_response = NodeAPI.estimate_coin_sell(coin, balance,
                                                       BASE_COIN)
        will_get_pip, comm_pip = est_sell_response[
            'will_get'], est_sell_response['commission']
        if int(balance) < int(comm_pip):
            continue
        will_get_pip = int(will_get_pip) - to_pip(0.01)
        if will_get_pip > 0:
            balances_bip[coin] = to_bip(will_get_pip)
    return balances_bip or {'BIP': Decimal(0)}
Example #13
0
    def send(self, to, value, coin="BIP", payload='', include_commission=True):
        value = Decimal(str(value))

        nonce = self.API.get_nonce(self.address)
        tx = MinterSendCoinTx(coin=coin,
                              to=to,
                              value=value,
                              nonce=nonce,
                              gas_coin=coin,
                              payload=payload)

        if include_commission:
            if coin == 'BIP':
                commission = to_bip(tx.get_fee())
            else:
                tx.sign(self.private_key)
                commission = self.API.estimate_tx_commission(
                    tx.signed_tx, pip2bip=True)['result']['commission']

            tx.value = value - commission

        # Проверяем на ошибки
        if tx.value <= 0:
            print(
                f'Ошибка: Комиссия ({to_bip(tx.get_fee())}) превышает сумму выплаты ({value})'
            )
            return
        elif tx.value > self.get_balance(in_bip=True)[coin]:
            print(f'Ошибка: На кошельке недостаточно {coin}')
            return

        tx.sign(private_key=self.private_key)

        r = self.API.send_transaction(tx.signed_tx)

        try:
            if r['result']['code'] == 0:
                print(f'{value} {coin} успешно отпрвлены на адрес {to}')
                self._wait_for_nonce(
                    nonce
                )  # Ждем nonce, чтобы предотвратить отправку нескольких транзакций в блоке
        except Exception:
            print(f'Не удалось отправить {coin}\nServer response: {r}')

        return r
Example #14
0
def mobile_top_up(wallet: PushWallet, phone=None, amount=None, confirm=True):
    if not confirm:
        return get_info()

    phone_reqs = get_tx_requirements(phone)
    if not phone_reqs:
        return f'Phone number {phone} not supported or invalid'

    response = NodeAPI.get_balance(wallet.address)
    balance = response['balance']
    balances_bip = effective_balance(balance)
    main_coin, main_balance_bip = max(balances_bip.items(), key=lambda i: i[1])
    balance_coin = to_bip(balance[main_coin])
    nonce = int(response['transaction_count']) + 1
    to_send = amount or balance_coin

    private_key = MinterWallet.create(mnemonic=wallet.mnemonic)['private_key']

    gas_coin = find_gas_coin(balance)
    if not gas_coin:
        return 'Coin not spendable. Send any coin to pay fee'
    # fee = estimate_custom_fee(gas_coin)
    min_topup = phone_reqs['min_bip_value']
    effective_topup = rub_to_bip(
        to_send) if main_coin == 'ROUBLE' else main_balance_bip

    if balance_coin < to_send:
        return 'Not enough balance'
    if effective_topup < min_topup:
        return f"Minimal top-up: {min_topup} BIP"

    tx = send_coin_tx(private_key,
                      main_coin,
                      to_send,
                      BIP2PHONE_PAYMENT_ADDRESS,
                      nonce,
                      payload=phone_reqs['payload'],
                      gas_coin=gas_coin)
    try:
        NodeAPI.send_tx(tx, wait=True)
    except MinterAPIException as exc:
        return exc.message
    return True
Example #15
0
def get_campaign_stats(campaign, extended=False):
    if extended:
        sent_list = campaign.recipients \
            .select().where(Recipient.sent_at.is_null(False)) \
            .order_by(Recipient.sent_at.asc())
        return {
            'customization_id':
            campaign.customization_setting_id,
            'status':
            campaign.status,
            'recipients': [{
                'email': r.email,
                'name': r.name,
                'amount_bip': float(to_bip(r.amount_pip)),
                'sent_at': r.sent_at,
                'opened_at': r.opened_at,
                'clicked_at': r.linked_at,
                'push_id': r.wallet_link_id,
                'target': r.target_shop
            } for r in sent_list]
        }
    result = campaign.recipients.select(
        fn.COUNT(Recipient.created_at).alias('emails'),
        fn.COUNT(Recipient.sent_at).alias('sent'),
        fn.COUNT(Recipient.opened_at).alias('open'),
        fn.COUNT(Recipient.linked_at).alias('clicked'))
    summary = result[0] if result else None
    result = {
        'customization_id': campaign.customization_setting_id,
        'status': campaign.status,
        'sent': 0,
        'open': 0,
        'clicked': 0
    }
    if not summary:
        return result
    return {
        'customization_id': campaign.customization_setting_id,
        'sent': summary.sent,
        'open': summary.open,
        'clicked': summary.clicked,
        'status': campaign.status
    }
Example #16
0
def push_create():
    """
    swagger: swagger/core/push-create.yml
    """
    payload = request.get_json() or {}
    sender, recipient = payload.get('sender'), payload.get('recipient')
    password = payload.get('password')
    amount = payload.get('amount')
    coin = payload.get('coin', 'BIP')

    customization_setting_id = payload.get('customization_setting_id')
    setting = CustomizationSetting.get_or_none(id=customization_setting_id)
    if not setting:
        jsonify({'error': 'Customization setting does not exist'
                 }), HTTPStatus.BAD_REQUEST

    wallet = generate_and_save_wallet(
        sender=sender,
        recipient=recipient,
        password=password,
        customization_setting_id=customization_setting_id)
    response = {'address': wallet.address, 'link_id': wallet.link_id}
    if amount:
        w = MinterWallet.create()
        tx = send_coin_tx(w['private_key'],
                          coin,
                          float(amount),
                          w['address'],
                          1,
                          gas_coin=coin)
        tx_fee = float(
            to_bip(NodeAPI.estimate_tx_comission(tx.signed_tx)['commission']))
        response['deeplink'] = TxDeeplink.create('send',
                                                 to=wallet.address,
                                                 value=float(amount) + tx_fee,
                                                 coin=coin).mobile
    return jsonify(response)
Example #17
0
    def delete(self, campaign_id):
        campaign = RewardCampaign.get_or_none(link_id=campaign_id,
                                              status='open')
        if not campaign:
            return {}, HTTPStatus.NOT_FOUND

        response = NodeAPI.get_balance(campaign.address)
        balances = response['balance']
        campaign_balance = to_bip(balances.get(campaign.coin, '0'))
        if campaign_balance:
            tx_fee = estimate_custom_fee(campaign.coin)
            gas_coin = find_gas_coin(
                balances) if tx_fee is None else campaign.coin
            if not gas_coin:
                return {
                    'error':
                    f'Campaign coin not spendable.'
                    f'Send any coin to campaign address {campaign.address} to pay fee'
                }, HTTPStatus.BAD_REQUEST
            private_key = MinterWallet.create(
                mnemonic=campaign.mnemonic)['private_key']
            refund_address = get_first_transaction(campaign.address)
            nonce = int(response['transaction_count']) + 1

            tx_fee = 0 if tx_fee is None else tx_fee
            tx = send_coin_tx(private_key,
                              campaign.coin,
                              campaign_balance - tx_fee,
                              refund_address,
                              nonce,
                              gas_coin=campaign.coin)
            NodeAPI.send_tx(tx, wait=True)

        campaign.status = 'closed'
        campaign.save()
        return {'success': True}
Example #18
0
def build_custom_email(person, campaign):
    name, company, amount, recipient_id = \
        person.name, campaign.company, str(to_bip(person.amount_pip)), str(person.id)
    customization = CustomizationSetting.get_or_none(id=campaign.customization_setting_id)

    msg_variables_tmpl = SHARING_TMPL_DEFAULT_VARS.copy()
    if customization:
        img = UserImage.get_or_none(id=customization.email_image_id)
        with scheduler.app.app_context():
            custom_img_url = images.url(img.filename) if img else None
        changes = {
            'email_image_url': custom_img_url,
            'email_head_text': customization.email_head_text,
            'email_body_text': customization.email_body_text,
            'email_button_text': customization.email_button_text,
            'email_subject_text': customization.email_subject_text,
        }
        msg_variables_tmpl.update(**{k: v for k, v in changes.items() if v is not None})
    msg_variables = {
        k: v.format(name=name, company=company, amount=amount)
        for k, v in msg_variables_tmpl.items()
    }

    msg = MIMEMultipart()
    msg['From'] = EMAIL_SENDER
    msg['To'] = person.email
    msg['Subject'] = msg_variables['email_subject_text']
    html_body = SHARING_MSG_TMPL \
        .replace('{{email_image_url}}', msg_variables['email_image_url']) \
        .replace('{{email_head_text}}', msg_variables['email_head_text']) \
        .replace('{{email_body_text}}', msg_variables['email_body_text']) \
        .replace('{{email_button_text}}', msg_variables['email_button_text']) \
        .replace('{{token}}', person.wallet_link_id + person.target_route) \
        .replace('{{recipient_id}}', recipient_id)
    msg.attach(MIMEText(html_body, 'html'))
    return msg
Example #19
0
def estimate_payload_fee(payload, bip=False):
    fee_pip = MinterHelper.pybcmul(
        len(bytes(payload, encoding='utf-8')) * MinterTx.PAYLOAD_COMMISSION,
        MinterTx.FEE_DEFAULT_MULTIPLIER)
    return to_bip(fee_pip) if bip else fee_pip
Example #20
0
    def convert(self, value, from_symbol, to_symbol, gas_coin=None):
        """
        Конвертирует одну монету в другую
        :param gas_coin: str 'SYMBOL' - монета для оплаты комиссии
        :param value: int/float
        :param from_symbol: str (Тикер монеты)
        :param to_symbol: str (Тикер монеты)
        :return:
        """

        from_symbol = from_symbol.upper()
        to_symbol = to_symbol.upper()
        value = Decimal(str(value))
        if gas_coin is None:
            gas_coin = from_symbol

        balances = self.get_balance(in_bip=True)

        if balances[from_symbol] < value:
            print(
                f"На кошельке недостаточно {from_symbol}. Нужно {value}, а есть {balances[from_symbol]}"
            )
            return

        # Генерируем транзакцию
        nonce = self.API.get_nonce(self.address)
        tx = MinterSellCoinTx(coin_to_sell=from_symbol,
                              value_to_sell=value,
                              coin_to_buy=to_symbol,
                              min_value_to_buy=0,
                              nonce=nonce,
                              gas_coin=gas_coin)

        # Проверяем достаточно ли баланса на оплату комиссии
        commission = to_bip(tx.get_fee())
        if gas_coin == from_symbol and balances[from_symbol] < (value +
                                                                commission):
            print(
                f"На кошельке недостаточно {from_symbol} для оплаты комиссии {commission}\n"
                f"Баланс: {round(balances[from_symbol], 2)}\n"
                f"Нужно:  {value + commission} (+{value + commission - round(balances[from_symbol], 2)})"
            )
            return
        elif balances[gas_coin] < commission:
            print(
                f"На кошельке недостаточно {gas_coin} для оплаты комиссии {commission}\n"
                f"Баланс: {round(balances[gas_coin], 2)}\n"
                f"Нужно:  {commission}")
            return

        # Отправляем транзакицю
        tx.sign(private_key=self.private_key)
        r = self.API.send_transaction(tx.signed_tx)

        try:
            if r['result']['code'] == 0:
                print(f'{from_symbol} сконвертирован в {to_symbol}')
                self._wait_for_nonce(
                    nonce
                )  # Ждем nonce, чтобы предотвратить отправку нескольких транзакций в блоке
        except Exception:
            print(
                f'Не удалось сконвертировать {from_symbol} в {to_symbol}\nServer response: {r}'
            )

        return r
Example #21
0
def get_balance(address, coin='BIP', bip=True):
    balance = NodeAPI.get_balance(address)['balance']
    balance_pip = balance[coin]
    return float(to_bip(balance_pip)) if bip else balance_pip
Example #22
0
    def multisend(self,
                  to_dict,
                  coin="BIP",
                  payload='',
                  include_commission=True):
        """
        Multisend на любое количество адресов

        :param to_dict: dict {address: value, ...}
        :param coin: str 'SYMBOL'
        :param payload: str 'Комментарий к транзакции'
        :param include_commission: bool Платит с учетом комиссии
        :return:
        """

        # Генерация общего списка транзакций и расчет общей суммы выплаты
        all_txs = []
        total_value = 0
        for d_address, d_value in to_dict.items():
            d_value = Decimal(str(d_value))
            all_txs.append({'coin': coin, 'to': d_address, 'value': d_value})
            total_value += d_value

        # Проверяем хватит ли баланса для совершения транзакции
        balance = self.get_balance(in_bip=True)[coin]
        if total_value > balance:
            print(
                f'Ошибка: На кошельке недостаточно {coin}. Нужно {total_value}, а у нас {balance}'
            )
            return

        # Разбивка на списки по 100 транзакций
        all_txs = self._split_txs(all_txs)

        # Генерируем шаблоны транзакций
        tx_templates = [
            MinterMultiSendCoinTx(txs, nonce=1, gas_coin=coin, payload=payload)
            for txs in all_txs
        ]

        # Считаем общую комиссию за все транзакции
        if coin == 'BIP':
            total_commission = to_bip(sum(tx.get_fee() for tx in tx_templates))
        else:
            [tx.sign(self.private_key) for tx in tx_templates]
            total_commission = sum(
                self.API.estimate_tx_commission(tx.signed_tx, pip2bip=True)
                ['result']['commission'] for tx in tx_templates)

        # Если перевод с учетом комиссии, то пересчитываем выплаты
        if include_commission:
            new_total_value = total_value - total_commission
            if new_total_value <= 0:
                print(
                    f'Ошибка: Комиссия ({total_commission}) превышает сумму выплаты ({total_value})'
                )
                return

            for tx in tx_templates:
                for tx_dict in tx.txs:
                    tx_dict['value'] = new_total_value * Decimal(
                        str(tx_dict['value'])) / Decimal(str(total_value))
        else:
            total_value -= total_commission
            if total_value <= 0:
                print(
                    f'Ошибка: Комиссия ({total_commission}) превышает сумму выплаты ({total_value})'
                )
                return

        r_out = []
        # Делаем multisend
        for tx in tx_templates:
            tx.nonce = self.API.get_nonce(self.address)
            tx.sign(self.private_key)
            r = self.API.send_transaction(tx.signed_tx)

            try:
                if r['result']['code'] == 0:
                    print(
                        f'Multisend для {len(tx.txs)} получателей успешно отправлен'
                    )
                    self._wait_for_nonce(
                        tx.nonce
                    )  # Ждем nonce, чтобы предотвратить отправку нескольких транзакций в блоке

            except Exception:
                print(f'Не удалось отправить multisend\nServer response: {r}')

            r_out.append(r)

        return r_out