前言

首先要了解下tronlink的交易机制,进行trc20如usdt和usdc转账的时候,如果没有冻结能量将会损耗一定的trx,该系统的目的就是减少这部分损耗的trx

相关笔记 tronlink-扫块系统,有需要的可先前往了解

目标

搭建冻结能量系统,例如你有10000trx后,在自己的交易系统里可以根据需求为自己的子钱包进行冻结能量,减少trx的支出

前置条件

python

正式内容

下面会细分说明下每个def的方法用途,以及总的冻结流程如何处理

获取当次可冻结的钱包数

每完成一次流程,需要执行(解冻->质押)*n->投票,计算大概需要的能量判断该次可操作多少个,基础操作目前发现大约300带宽一次

def get_max_unfreeze_count(tronapi):
    once = 300
    getaccountnet = tronapi.getaccountresource()
    freeNetUsed = getaccountnet['freeNetUsed'] if 'freeNetUsed' in getaccountnet else 0
    freeNetLimit = getaccountnet['freeNetLimit'] if 'freeNetLimit' in getaccountnet else 0
    NetUsed = getaccountnet['NetUsed'] if 'NetUsed' in getaccountnet else 0
    NetLimit = getaccountnet['NetLimit'] if 'NetLimit' in getaccountnet else 0
    bandwidth = NetLimit + freeNetLimit - NetUsed - freeNetUsed
    # 质押->投票 固定操作两次
    counts=math.floor((bandwidth-once) / (2 * once))
    print("[%s]:当前带宽:%s 可操作:%s个地址" % (datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S"),bandwidth,counts))
    return counts

从接口或者数据库中查询需要冻结的钱包

本项目的子钱包的表由于需要保存到密钥,因此相对放在另一处服务器保存比较完全

def get_need_freeze_array():
    try:
        global default_min_energy
        url = "api url"
        response = requests.get(url)
        data = response.json()
        if 'status_code' in data:
            print("[%s]:请求需冻结钱包失败,返回:[%s]%s" % (
                datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S"), data['status_code'], data['message']))
            return []
        if 'Energy' in data:
            default_min_energy=data['Energy']
        if 'Data' not in data:
            return []
        return data['Data']
    except Exception as r:
        return []

如使用仿照例子则接口返回的格式如下

{"Data":[{"address":"TBbcpKgoEW56RDXDyFvCkNmtNGMnruZDF9"}],"Energy":20000}

计算子钱包归集转账一次需要冻结多少trx

归集的能量如需要20000,但需要换算出具体需要多少trx,而这个值是需要根据全网的情况换算得出,不能固定写死的

def get_min_trx(tronapi):
    account = tronapi.getaccount()
    account_balance = math.floor(account['balance'] / TRX) if 'balance' in account else 0
    time.sleep(1)
    getaccountnet = tronapi.getaccountresource()
    # 换算计算出当前最低需冻结的trx
    min_trx = 10 + math.ceil(
        default_min_energy / (getaccountnet['TotalEnergyLimit'] / getaccountnet['TotalEnergyWeight']))
    tronPowerLimit = getaccountnet['tronPowerLimit'] if 'tronPowerLimit' in getaccountnet else 0
    print( "[%s]:总TRX %s,可用TRX %s,已冻结 %s,单次最低 %s" % (
        datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S"), tronPowerLimit + account_balance,
        account_balance, tronPowerLimit, min_trx))
    return {"min_trx": min_trx}

解冻历史的已冻结过的钱包

need_unfreeze_count 需要解冻的数量, 如目前已冻结了10个钱包,我可能只需要解冻2~3个就满足该次的需求,那么就没必要把全部的钱包都解冻了,这样可以保证在tronlink中的投票收益最大化,而且解冻过程中需要使用主钱包的带宽,用过多的带宽也是会损耗trx的,

def unfreeze_address(tronapi, need_unfreeze_count=0):
    excute_unfreeze_address = False
    try:
        now_unfreeze_count = 0
        newJson = []
        self_unfreeze = tronapi.unfreeze_balance()
        time.sleep(1)
        if not self_unfreeze.get('response').get("Error"):
            excute_unfreeze_address = True
        else:
            newJson.append({'address': energy_config.owner_address})
        GetDelegatedResourceAccountIndex = tronapi.GetDelegatedResourceAccountIndex()
        array = GetDelegatedResourceAccountIndex['toAccounts']
        print( "[%s]:已冻结用户:%s 个" % (
            datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S"), len(array)))
        for address in array:
            GetDelegatedResource = tronapi.GetDelegatedResource(keys.to_base58check_address(address))
            time.sleep(1)
            expire_time = None
            resource = "ENERGY"
            try:
                expire_time = GetDelegatedResource['delegatedResource'][0]['expire_time_for_energy']
                resource="ENERGY"
            except Exception as r:
                expire_time = GetDelegatedResource['delegatedResource'][0]['expire_time_for_bandwidth']
                resource="BANDWIDTH"
            if expire_time is not None and (int(time.time() * 1000) > expire_time):
                if  now_unfreeze_count < need_unfreeze_count:
                    print("[%s]:地址:%s 已解冻" % (datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S"),
                                                     keys.to_base58check_address(address),))
                    tronapi.unfreeze_balance(receiver=keys.to_base58check_address(address),resource=resource)
                    time.sleep(1)
                    excute_unfreeze_address = True
                    now_unfreeze_count = now_unfreeze_count + 1
                else:
                    newJson.append({'address': keys.to_base58check_address(address)})
            else:
                print("[%s]:地址:%s 解冻 %s" % (datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S"),
                                                   keys.to_base58check_address(address),
                                                   time.strftime("%Y-%m-%d %H:%M:%S", time.localtime(
                                                       GetDelegatedResource['delegatedResource'][0][
                                                           'expire_time_for_energy'] / 1000))
                                                   ))
                newJson.append({'address': keys.to_base58check_address(address)})
        return {'newJson': newJson, 'excute_unfreeze_address': excute_unfreeze_address}
    except Exception as r:
        print("[%s]:解冻流程报错:%s" % (datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S"),r))
        return {'newJson': [], 'excute_unfreeze_address': False}

主循环程序

流程说明:

  1. 获取需要新冻结的钱包列表,换算该次执行的单次能量的最小trx,主钱包可冻结的钱包数
  2. 循环执行冻结
  3. 投票获取收益
  4. 睡眠等到下次的执行时间

def now_time_format():
    return datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S")
tronapi = Tronapi(
    network=energy_config.environment,
    priv_key=energy_config.owner_key,
    owner=energy_config.owner_address
)

while True:
    try:
        default_min_energy = energy_config.default_min_energy
        need_freeze_array = get_need_freeze_array()
        resp = get_min_trx(tronapi)
        min_trx = resp['min_trx']
        time.sleep(1)
        print( "[%s]:需冻结%s 个" % (now_time_format(), len(need_freeze_array)))
        if len(need_freeze_array) > 0:
            #是否需要投票
            isNeedvote = False
            max_unfreeze_count = get_max_unfreeze_count(tronapi)
            if max_unfreeze_count <= 0:
                print( "[%s]:当前带宽不足 " % (now_time_format()))
                raise BaseException("60")
            need_unfreeze_count=len(need_freeze_array) if len(need_freeze_array)<max_unfreeze_count else max_unfreeze_count
            newJsonResp = unfreeze_address(
                tronapi,
                need_unfreeze_count=need_unfreeze_count)
            newJson = newJsonResp['newJson']
            excute_unfreeze_address = newJsonResp['excute_unfreeze_address']
            time.sleep(1)
            getaccountnet = tronapi.getaccountresource()
            tronPowerLimit = getaccountnet['tronPowerLimit'] if 'tronPowerLimit' in getaccountnet else 0
            time.sleep(1)
            account = tronapi.getaccount()
            account_balance = math.floor(account['balance'] / TRX) if 'balance' in account else 0

            print( "[%s]:总TRX%s,可用TRX%s,已冻结%s" % (now_time_format(), tronPowerLimit + account_balance, account_balance,
                tronPowerLimit))
            address_array = [(item.get('address')) for n, item in enumerate(newJson)]
            for i in range(max_unfreeze_count):
                target = need_freeze_array.pop()
                if target['address'] not in address_array:
                    freeze_resp = tronapi.freeze_balance(amount=min_trx, receiver=target['address'])
                    if freeze_resp.get('response').get("Error"):
                        print( "[%s]:地址 %s,冻结失败%s" % (now_time_format(), target['address'], freeze_resp.get('response').get("Error")))
                    else:
                        isNeedvote = True
                        print("[%s]:地址 %s,冻结能量%s" % (now_time_format(), target['address'], min_trx))
                        newJson.append({"address": target['address']})
                time.sleep(5)
                if len(need_freeze_array) == 0:
                    break
            getaccountnet = tronapi.getaccountresource()
            tronPowerLimit = getaccountnet['tronPowerLimit'] if 'tronPowerLimit' in getaccountnet else 0
            if (tronPowerLimit > 0 and isNeedvote is True) or excute_unfreeze_address is True:
                vote_witness_account_resp = tronapi.vote_witness_account(
                    vote_count=tronPowerLimit,
                    vote_address=energy_config.vote_address)
                print( "[%s]:已冻结 %s,投票 %s" % (now_time_format(), tronPowerLimit, energy_config.vote_address))
            print( "[%s]:执行结束" % (now_time_format()))
            time.sleep(60 * 30)
        else:
            time.sleep(60 * 30)
    except BaseException as e:
        time.sleep(60 * int(str(e)))