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'] } }
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
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, } }
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)
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 }
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
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)
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
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
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
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'])
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)}
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
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
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 }
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)
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}
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
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
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
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
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