コード例 #1
0
    def check_src_address_balance(self, **kwargs) -> bool:
        token_name = kwargs['token_name']
        from_addr = kwargs['from_addr']
        # to_addr = kwargs['to_addr']
        amount = kwargs['amount']  # 特别注意
        amount = round_down(amount)
        pro_id = kwargs['pro_id']

        project = self.session.query(Project).filter_by(pro_id=pro_id).first()
        assert isinstance(project,
                          Project), f'not found pro_id:{pro_id} in project'

        btc_withdraw_cfg = self.session.query(WithdrawConfig).filter_by(
            pro_id=pro_id, token_name='BTC').first()
        assert isinstance(
            btc_withdraw_cfg,
            WithdrawConfig), f'not found pro_id:{pro_id} in withdraw_cfg'

        sms_content = ''
        sms_template = '【shbao】 尊敬的管理员,余额预警。{0}出币地址{1}余额为{2},请立即充值{3}。{4},{5}' + f',{project.pro_name}'
        try:
            assert self.proxy.ping() == True, 'bitcoind rpc is gone'

            # 获取余额(包含未确认的收币, 减去未确认的出币)
            balance_in_satoshi = self.proxy.get_balance(address=from_addr,
                                                        mem_spent=True,
                                                        mem_recv=True)
            balance_in_btc = round_down(
                Decimal(balance_in_satoshi) / Decimal(10**8))

            decim_balance = balance_in_btc
            if decim_balance < amount + BTC_TX_FEE:
                self.logger.error(
                    f'BTC balance {decim_balance} < {amount + BTC_TX_FEE}')
                sms_content = sms_template.format('BTC', 'BTC', decim_balance,
                                                  'BTC', str(datetime.now()),
                                                  ENV_NAME.upper())
                raise BalanceNotEnoughException(sms_content)

            if decim_balance < btc_withdraw_cfg.balance_threshold_to_sms:
                sms_content = sms_template.format('BTC', 'BTC', decim_balance,
                                                  'BTC', str(datetime.now()),
                                                  ENV_NAME.upper())

        except BadConnectionError as e:
            self.logger.error(f'connection BTC API server error: {e}')
            raise HttpConnectionError(e)
        except BalanceNotEnoughException as e:
            tel_no = project.tel_no
            self.send_sms_queue(sms_content=sms_content, tel_no=tel_no)
            raise e

        # 发送余额预警短信
        if len(sms_content) != 0:
            tel_no = project.tel_no
            self.send_sms_queue(sms_content=sms_content, tel_no=tel_no)

        return True
コード例 #2
0
    def search_utxo(self, addrs: list, total_amount: Decimal) -> (bool, Dict):

        ret_utxos_map = dict()
        sum = Decimal(0)
        is_enough = False
        for addr in addrs:
            if is_enough: break
            utxos = self.get_utxo(address=addr, include_mem=True)

            utxos.sort(key=lambda item: item['value'],
                       reverse=False)  #按照金额升序排序
            utxos.sort(key=lambda item: item['status']['confirmed'],
                       reverse=True)  #按照确认状态排序, 已确认的靠前

            for utxo in utxos:
                value_in_satoshi = utxo['value']
                if value_in_satoshi < MIN_AVAILABLE_UTXO_VALUE_IN_SATOSHI:
                    continue  #金额太小, 小于 10000satoshi, 不要
                sum += round_down(Decimal(value_in_satoshi) / Decimal(10**8))

                if addr not in ret_utxos_map: ret_utxos_map[addr] = []
                ret_utxos_map[addr].append(utxo)
                if sum >= total_amount:
                    is_enough = True
                    break

        return is_enough, ret_utxos_map
コード例 #3
0
    def getHRC20TokenBalance(self, contract_addr: str, address: str) -> str:
        # 123.56.71.141:1317/hs/contract/htdf1nkkc48lfchy92ahg50akj2384v4yfqpm4hsq6y/70a0823100000000000000000000000067ee5cbe5cb9ae6794ca1437186f12066b0196af

        # assert  self.cointype.upper() == 'HTDF'

        rlp_data = '70a08231'
        # from cosmos.my_bech32 import Bech32AddrToHex
        hex_addr = Bech32AddrToHex(addr=address)
        hex_addr_fmt = '0' * (32 * 2 - len(hex_addr)) + hex_addr

        rlp_data += hex_addr_fmt

        assert len(rlp_data) == (4 * 2 + 32 * 2)

        urlpath = f'/hs/contract/{contract_addr}/{rlp_data}'
        rsp = self._get_url_call(urlpath)
        data = rsp.replace('"', '')

        assert len(data) == 32 * 2

        ndecimals = self.getHRC20_decimals(contract_addr=contract_addr)
        assert 1 < ndecimals <= 18

        # balance =   int(data, 16) * 1.0 / 10**(ndecimals)  #会四舍五入!!  有问题
        balance = Decimal(int(data, 16)) / Decimal(10**(ndecimals))
        if balance < 0.00010000: return '0.00000000'
        # balance.quantize(Decimal("0.00000000"), getattr(decimal, 'ROUND_DOWN'))
        strfmt_balance = str(round_down(Decimal(str(balance))))
        # strfmt_balance = '%.8f' % balance
        return strfmt_balance
コード例 #4
0
    def process_withdraw_order(self, **kwargs):
        """
        处理提币
        :param kwargs:
        :return:
        """
        logger.info(f'kwargs: { json.dumps(kwargs)}')

        orderid = str(kwargs['order_id']).strip()
        from_addr = str(kwargs['from_address']).strip()
        to_addr = str(kwargs['to_address']).strip()
        amount = round_down(Decimal(kwargs['amount']))
        token_name = str(kwargs['token_name']).strip()
        challback_url = kwargs['callback_url']
        pro_id = kwargs['pro_id']
        memo = '' if 'memo' not in kwargs else kwargs['memo']

        if not is_valid_addr(address=from_addr, token_name=token_name):
            raise Exception('`from` is invalid address')

        if not is_valid_addr(address=to_addr, token_name=token_name):
            raise Exception('`to` is invalid address')

        self.check_valid_params(pro_id=pro_id,
                                token_name=token_name,
                                from_addr=from_addr,
                                amount=amount)

        #处理, 如果有异常, 则抛出异常
        #根据订单号查询是否存在该订单
        if self.is_order_exists(orderid, pro_id):
            logging.info(f"order already exists, order_id : {orderid}")
            #订单重复咋处理?   直接抛异常
            raise Exception("order already exists")

        #生成唯一的流水号
        serial_id = self.generate_serial_id()

        # 保存订单
        self.insert_order(orderid, serial_id, token_name, from_addr, to_addr,
                          amount, challback_url, pro_id, memo)

        # 发送消息到mq
        msg = {"serial_id": serial_id}

        logger.info('start send MQ...')

        #TODO  如果发送失败, 应该从数据库里面删除刚刚插入订单那数据
        self.send_msg_to_mq(msg, token_name)

        logger.info('send MQ sucessed')

        #如果一切顺利, 返回
        ret_info = {'serial_id': serial_id, 'order_id': orderid}

        return ret_info
コード例 #5
0
def btc_collect():
    # 1) 查询 tb_active_address_balances 表获取符合归集条件的地址
    engine = create_engine(MYSQL_CONNECT_INFO,
                           max_overflow=0,
                           pool_size=5)
    Session = sessionmaker(bind=engine, autoflush=False, autocommit=True)  # 增删改操作 需要手动flush,
    session = Session()

    query_sets = session.query(CollectionConfig, ActiveAddressBalance, Address) \
        .filter(ActiveAddressBalance.token_name == 'BTC', CollectionConfig.token_name == 'BTC') \
        .filter(CollectionConfig.pro_id == ActiveAddressBalance.pro_id) \
        .filter(Address.address == ActiveAddressBalance.address) \
        .filter(ActiveAddressBalance.balance >= CollectionConfig.min_amount_to_collect) \
        .all()

    tx_hash_set = set()  # 保存本次广播的tx_hash 用于后面监控

    if not (query_sets is None or len(query_sets) == 0):
        logger.info(f'query_sets size is  {len(query_sets)} ')

        proxy = BTCProxy(host=BTC_API_HOST, port=BTC_API_PORT)
        btcutil = BTCTransferUitl(host=BTC_API_HOST, port=BTC_API_PORT,
                                       net_type='mainnet' if g_IS_MAINNET else 'testnet')

        # 2) 遍历地址列表进行转账操作, 并插入归集记录表

        class CLC_ADDR_INFO:
            pro_id = 0xffffff
            src_address = ''
            dst_address = ''
            clc_amount_in_satoshi = 0
            priv_key = ''
            txhash  = ''

        addr_info_map = dict()

        for clc_cfg, act_addr, addr in query_sets:
            assert isinstance(clc_cfg, CollectionConfig)
            assert isinstance(act_addr, ActiveAddressBalance)
            assert isinstance(addr, Address)

            if not is_valid_addr(clc_cfg.collection_dst_addr, token_name='BTC'):
                logger.error(f'invalid collection dst address : {clc_cfg.collection_dst_addr}')
                continue

            from_addr = addr.address
            balance_in_satoshi = proxy.get_balance(address=from_addr) #默认是保守方式的余额
            balance_in_btc = round_down(Decimal(balance_in_satoshi) / Decimal(10 ** 8))

            # 判断地址的(链上)余额是否满足归集条件
            if balance_in_btc < Decimal(clc_cfg.min_amount_to_collect):
                logger.info(f'{from_addr}, BTC balance:{balance_in_btc} is less than min_amount_to_collect')
                continue
            logger.info(f'active address: {act_addr}')

            nettype = 'mainnet' if g_IS_MAINNET else 'testnet'
            priv_key, sub_addr = gen_bip44_subprivkey_from_mnemonic(mnemonic=g_MNEMONIC,
                                                                    coin_type='BTC',
                                                                    account_index=addr.pro_id,
                                                                    address_index=addr.address_index,
                                                                    nettype=nettype)

            if not sub_addr == addr.address:  #大小写敏感, 不能转为小写
                # 致命错误, 不可恢复
                raise Exception(f'ADDRESS NOT MATCH! { sub_addr  } != { addr.address}')
                pass

            if clc_cfg.pro_id not in addr_info_map:
                addr_info_map[clc_cfg.pro_id] = []

            addr_info = CLC_ADDR_INFO()
            addr_info.pro_id = clc_cfg.pro_id
            addr_info.src_address = from_addr
            addr_info.priv_key = priv_key
            addr_info.txhash = ''
            addr_info.clc_amount_in_satoshi = balance_in_satoshi
            addr_info.dst_address = clc_cfg.collection_dst_addr

            addr_info_map[clc_cfg.pro_id].append(addr_info)


        for pro_id, addr_infos in addr_info_map.items():

            if len(addr_infos) == 0: continue

            sum_amount_in_satoshi = 0
            tmp_addrs = []
            dst_addr = addr_infos[0].dst_address

            src_addrs_key_map = OrderedDict()

            for addr_info in addr_infos:
                assert pro_id == addr_info.pro_id , 'pro_id not match!'
                assert dst_addr == addr_info.dst_address, 'dst_addr not match!'
                if addr_info.src_address not in tmp_addrs: #去重
                    tmp_addrs.append(addr_info.src_address)
                    sum_amount_in_satoshi += addr_info.clc_amount_in_satoshi
                    src_addrs_key_map[addr_info.src_address] = addr_info.priv_key


            logger.info(f'to collect sub-address is: {tmp_addrs} ')


            # tmp_amount_in_btc = round_down(Decimal(sum_amount_in_satoshi) / Decimal(10 ** 8))
            is_enough, founded_utxos, sum_utxo_satoshi = btcutil.search_utxo(addrs=tmp_addrs,
                                                                        total_amount=Decimal('1000.0'), #为了获取所有的UTXO(包括尚未确认的utxo)
                                                                        min_utxo_value=1000)



            if sum_utxo_satoshi > sum_amount_in_satoshi:
                logger.info('sum_utxo_satoshi > sum_amount_in_satoshi,  will collect unconfirmed utxo')
            elif sum_utxo_satoshi == sum_amount_in_satoshi:
                logger.info('sum_utxo_satoshi == sum_amount_in_satoshi, not exist unconfirmed utxo, everything is perfect! ')
            else:
                logger.info('sum_utxo_satoshi < sum_amount_in_satoshi, maybe exsit some dusty utxo those less than 1000 satoshi')

            # 计算输入的utxo的数量, 用于计算手续费
            fee_rate = 20
            utxo_count = 0
            for addr, utxos in founded_utxos.items():
                utxo_count += len(utxos)

            txfee =  round_down( Decimal(str((148 * utxo_count + 34 * 1 + 10))) * Decimal(fee_rate) / Decimal(10 ** 8) )# Decimal(str((148 * nIn + 34 * nOut + 10))) * Decimal(rate)
            txfee = txfee if txfee <= Decimal('0.0002') else Decimal('0.0002')  # 防止手续费给太多

            #本次归集的金额, 以搜索到的所有utxo的总金额为准
            total_clc_amount_satoshi = sum_utxo_satoshi - int(txfee * Decimal(10**8)) - 1    #减去手续费, 因为 transfer中真实到账金额,不包括手续费

            dst_addrs_amount_map = OrderedDict()
            dst_addrs_amount_map[dst_addr] = round_down(Decimal(total_clc_amount_satoshi) / Decimal(10 ** 8))



            try:
                assert proxy.ping() == True, 'bitcoind rpc is gone'  # 测试 bitcoind的 rpc服务是否还在
                txid = btcutil.transfer(src_addrs_key_map=src_addrs_key_map,
                                        dst_addrs_amount_map=dst_addrs_amount_map,
                                        txfee=txfee,
                                        auto_calc_pay_back=False,
                                        pay_back_index=0xfffffff,
                                        ensure_one_txout=True)

                logger.info(f'txid: {txid}')

                if not session.is_active:
                    session = Session()

                for addr, utxos in founded_utxos.items():

                    collect_amount_in_satoshi = 0  # 当前地址归集了的金额
                    for utxo in utxos:
                        collect_amount_in_satoshi += utxo['value']

                    clc_records = CollectionRecords()
                    clc_records.tx_hash = txid
                    clc_records.pro_id = pro_id
                    clc_records.token_name = 'BTC'
                    clc_records.amount = round_down(Decimal(collect_amount_in_satoshi) / Decimal(10 ** 8))
                    clc_records.transaction_status = WithdrawStatus.transaction_status.PENDING
                    clc_records.from_address = addr
                    clc_records.collection_type = CollectionType.AUTO
                    clc_records.to_address = dst_addr
                    clc_records.complete_time = datetime.now()

                    session.add(clc_records)
                    session.flush()

                # 稍后要查询交易状态
                tx_hash_set.add(txid)

            except Exception as e:
                logger.error(f'error: {e}')
                time.sleep(2)
                pass


    # 3) 监控发出的交易, 获取区块高度等信息, 并修改 交易状态
    logger.info('waiting  5s for tx confirmed')
    time.sleep(5)

    # 4) 获取数据库中所有
    all_pending_txs = session.query(CollectionRecords.tx_hash) \
        .filter(CollectionRecords.transaction_status == WithdrawStatus.transaction_status.PENDING,
                CollectionRecords.token_name == 'BTC') \
        .all()

    for item in all_pending_txs:
        tx_hash_set.add( item.tx_hash)  #自动去重


    #因为BTC是批量归集, 所以存在多笔归集记录的tx_hash相同, 更新数据库的交易状态时, 根据tx_hash进行查找即可
    for tx_hash in tx_hash_set:
        try:
            tx_status = btc_get_transaction_status(tx_hash=tx_hash)
            if tx_status.transaction_status == WithdrawStatus.transaction_status.SUCCESS:
                session.query(CollectionRecords) \
                    .filter_by(tx_hash=tx_hash) \
                    .update({
                    'block_height': tx_status.block_height,
                    'transaction_status': tx_status.transaction_status,
                    'block_time': tx_status.block_time,
                    'tx_confirmations': tx_status.confirmations,
                    'complete_time': datetime.now()
                })
            elif tx_status.transaction_status == WithdrawStatus.transaction_status.FAIL:
                session.query(CollectionRecords) \
                    .filter_by(tx_hash=tx_hash) \
                    .update({
                    'block_height': tx_status.block_height,
                    'transaction_status': tx_status.transaction_status,
                    'block_time': tx_status.block_time,
                    # 'confirmations': tx_status.confirmations,
                    'complete_time': datetime.now()
                })
            else:
                logger.info(f'{tx_hash} is still pending....')
                pass
        except Exception as e:
            logger.error(f'error: {e}')
            time.sleep(5)


    pass
コード例 #6
0
    def check_src_address_balance(self, **kwargs) -> bool:

        token_name = kwargs['token_name']
        from_addr = kwargs['from_addr']
        # to_addr = kwargs['to_addr']
        amount = kwargs['amount']  # 特别注意
        amount = round_down(amount)
        pro_id = kwargs['pro_id']

        project = self.session.query(Project).filter_by(pro_id=pro_id).first()
        assert isinstance(project,
                          Project), f'not found pro_id:{pro_id} in project'

        htdf_withdraw_cfg = self.session.query(WithdrawConfig).filter_by(
            pro_id=pro_id, token_name='HTDF').first()
        assert isinstance(
            htdf_withdraw_cfg,
            WithdrawConfig), f'not found pro_id:{pro_id} in withdraw_cfg'

        rpc = CosmosProxy(host=HTDF_NODE_RPC_HOST,
                          port=HTDF_NODE_RPC_PORT,
                          cointype=token_name)

        sms_content = ''
        sms_template = '【shbao】 尊敬的管理员,余额预警。{0}出币地址{1}余额为{2},请立即充值{3}。{4},{5}' + f',{project.pro_name}'
        try:
            if token_name == 'HTDF':
                strbalance = rpc.getBalance(from_addr)  # 获取余额

                decim_balance = Decimal(strbalance)
                if decim_balance < amount + Decimal('0.1'):
                    self.logger.error(
                        f'HTDF balance {decim_balance} < {amount + Decimal("0.1")}'
                    )
                    sms_content = sms_template.format('HTDF', 'HTDF',
                                                      decim_balance, 'HTDF',
                                                      str(datetime.now()),
                                                      ENV_NAME.upper())
                    raise BalanceNotEnoughException(sms_content)

                if decim_balance < htdf_withdraw_cfg.balance_threshold_to_sms:
                    sms_content = sms_template.format('HTDF', 'HTDF',
                                                      decim_balance, 'HTDF',
                                                      str(datetime.now()),
                                                      ENV_NAME.upper())
            else:  #HRC20
                hrc20_contract = ''
                hrc20_decimals = 18
                for con_addr, sym_info in HRC20_CONTRACT_MAP.items():
                    if sym_info['symbol'] == token_name:
                        hrc20_contract = con_addr
                        hrc20_decimals = sym_info['decimal']

                assert len(hrc20_contract) == 43, 'hrc20_contract is illegal'
                assert hrc20_decimals == 18, 'hrc20_deciaml not equal 18'

                hrc20_btu_withdraw_cfg = self.session.query(WithdrawConfig)\
                                        .filter_by(pro_id=pro_id, token_name=token_name).first()
                assert isinstance(
                    hrc20_btu_withdraw_cfg, WithdrawConfig
                ), f'not found pro_id:{pro_id} in withdraw_cfg'

                htdf_balance = rpc.getBalance(from_addr)  # 获取余额
                htdf_decim_balance = Decimal(htdf_balance)
                if htdf_decim_balance < Decimal('0.3'):
                    str_balance = (
                        '%.8f' % htdf_decim_balance
                    ) if htdf_decim_balance > Decimal('0.00000001') else '0'
                    self.logger.error(f'HTDF fee balance {str_balance} < 0.01')
                    sms_content = sms_template.format(token_name, 'HTDF手续费',
                                                      str_balance, 'HTDF',
                                                      str(datetime.now()),
                                                      ENV_NAME.upper())
                    raise BalanceNotEnoughException(sms_content)

                strbalance = rpc.getHRC20TokenBalance(
                    contract_addr=hrc20_contract, address=from_addr)
                token_balance = round_down(Decimal(strbalance))
                if token_balance < amount + Decimal('0.1'):
                    self.logger.error(
                        f'token balance {token_balance} < {amount + Decimal("0.1")}'
                    )
                    str_balance = (
                        '%.8f' % token_balance
                    ) if token_balance > Decimal('0.00000001') else '0'
                    sms_content = sms_template.format(token_name, token_name,
                                                      str_balance, token_name,
                                                      str(datetime.now()),
                                                      ENV_NAME.upper())
                    raise BalanceNotEnoughException(sms_content)

                # 如果当前余额小于设定的预警值 则发送短信通知项目方
                if token_balance < hrc20_btu_withdraw_cfg.balance_threshold_to_sms:
                    sms_content = sms_template.format(token_name, token_name,
                                                      token_balance,
                                                      token_name,
                                                      str(datetime.now()),
                                                      ENV_NAME.upper())

        except BadConnectionError as e:
            self.logger.error(f'connection HTDF node error: {e}')
            raise HttpConnectionError(e)
        except BalanceNotEnoughException as e:
            tel_no = project.tel_no
            self.send_sms_queue(sms_content=sms_content, tel_no=tel_no)
            raise e

        # 发送余额预警短信
        if len(sms_content) != 0:
            tel_no = project.tel_no
            self.send_sms_queue(sms_content=sms_content, tel_no=tel_no)

        return True
コード例 #7
0
    def check_src_address_balance(self, **kwargs) -> bool:

        token_name = kwargs['token_name']
        from_addr = kwargs['from_addr']
        # to_addr = kwargs['to_addr']
        amount = kwargs['amount']  # 特别注意: 以 ETH 为单位, 不要转为 wei !!!
        # priv_key = kwargs['priv_key']
        pro_id = kwargs['pro_id']

        project = self.session.query(Project).filter_by(pro_id=pro_id).first()
        assert isinstance(project,
                          Project), f'not found pro_id:{pro_id} in project'

        eth_withdraw_cfg = self.session.query(WithdrawConfig).filter_by(
            pro_id=pro_id, token_name='ETH').first()
        assert isinstance(
            eth_withdraw_cfg,
            WithdrawConfig), f'not found pro_id:{pro_id} in withdraw_cfg'

        amount = round_down(amount)

        block_identifier = HexStr('latest')  # 不能用pending
        myweb3 = Web3(provider=HTTPProvider(
            endpoint_uri=URI(ETH_FULL_NODE_RPC_URL)))
        nbalance = myweb3.eth.getBalance(
            account=to_checksum_address(from_addr),
            block_identifier=block_identifier)
        ether_balance = myweb3.fromWei(nbalance, 'ether')  # ETH 余额

        decim_eth_balance = round_down(ether_balance)

        sms_content = ''
        sms_template = '【shbao】 尊敬的管理员,余额预警。{0}出币地址{1}余额为{2},请立即充值{3}。{4},{5}' + f',{project.pro_name}'

        try:
            if token_name == 'ETH':
                if decim_eth_balance < amount + Decimal('0.01'):
                    str_balance = (
                        '%.8f' % decim_eth_balance
                    ) if decim_eth_balance > Decimal('0.00000001') else '0'
                    self.logger.error(
                        f'ETH balance {str_balance } < {amount + Decimal("0.01")}'
                    )
                    sms_content = sms_template.format('ETH', 'ETH',
                                                      str_balance, 'ETH',
                                                      str(datetime.now()),
                                                      ENV_NAME.upper())
                    raise BalanceNotEnoughException(sms_content)

                #如果当前余额小于设定的预警值 则发送短信通知项目方
                if decim_eth_balance < eth_withdraw_cfg.balance_threshold_to_sms:
                    sms_content = sms_template.format('ETH', 'ETH',
                                                      decim_eth_balance, 'ETH',
                                                      str(datetime.now()),
                                                      ENV_NAME.upper())
            else:  # USDT交易
                usdt_withdraw_cfg = self.session.query(
                    WithdrawConfig).filter_by(pro_id=pro_id,
                                              token_name='USDT').first()
                assert isinstance(
                    usdt_withdraw_cfg, WithdrawConfig
                ), f'not found pro_id:{pro_id} in withdraw_cfg'

                if decim_eth_balance < Decimal('0.01'):
                    str_balance = (
                        '%.8f' % decim_eth_balance
                    ) if decim_eth_balance > Decimal('0.00000001') else '0'
                    self.logger.error(f'ETH balance {str_balance} < 0.01')
                    sms_content = sms_template.format('USDT', 'ETH手续费',
                                                      str_balance, 'ETH',
                                                      str(datetime.now()),
                                                      ENV_NAME.upper())
                    raise BalanceNotEnoughException(sms_content)

                chksum_contract_addr = to_checksum_address(
                    ERC20_USDT_CONTRACT_ADDRESS)
                contract = myweb3.eth.contract(address=chksum_contract_addr,
                                               abi=EIP20_ABI)
                # symbol = contract.functions.symbol().call()
                # decimals = contract.functions.decimals().call()
                erc20_token_balance_int = contract.functions.balanceOf(
                    to_checksum_address(from_addr)).call()
                if erc20_token_balance_int == 0:
                    self.logger.error(f'token balance is 0')
                    sms_content = sms_template.format('USDT', 'USDT', 0,
                                                      'USDT',
                                                      str(datetime.now()),
                                                      ENV_NAME.upper())
                    raise BalanceNotEnoughException(sms_content)

                erc20_token_balance_decimal = myweb3.fromWei(
                    erc20_token_balance_int, unit='mwei')
                token_balance = round_down(erc20_token_balance_decimal)
                if token_balance < amount + Decimal('0.1'):
                    self.logger.error(
                        f'token balance {token_balance} < {amount + Decimal("0.1")}'
                    )
                    sms_content = sms_template.format('USDT', 'USDT',
                                                      token_balance, 'USDT',
                                                      str(datetime.now()),
                                                      ENV_NAME.upper())
                    raise BalanceNotEnoughException(sms_content)

                # 如果当前余额小于设定的预警值 则发送短信通知项目方
                if token_balance < usdt_withdraw_cfg.balance_threshold_to_sms:
                    sms_content = sms_template.format('USDT', 'USDT',
                                                      token_balance, 'USDT',
                                                      str(datetime.now()),
                                                      ENV_NAME.upper())

        except BalanceNotEnoughException as e:
            tel_no = project.tel_no
            self.send_sms_queue(sms_content=sms_content, tel_no=tel_no)
            self.logger.info('send_sms_queue finished')
            raise e

        #发送余额预警短信
        if len(sms_content) != 0:
            tel_no = project.tel_no
            self.send_sms_queue(sms_content=sms_content, tel_no=tel_no)
            self.logger.info('send_sms_queue finished')

        return True