package org.tron.walletserver;

import android.content.Context;
import android.content.SharedPreferences;
import android.os.Bundle;

import com.eletac.tronwallet.R;
import com.eletac.tronwallet.TronWalletApplication;
import com.google.firebase.analytics.FirebaseAnalytics;
import com.google.protobuf.ByteString;

import org.apache.commons.lang3.StringUtils;
import org.spongycastle.util.encoders.Hex;
import org.tron.api.GrpcAPI;
import org.tron.api.GrpcAPI.AccountNetMessage;
import org.tron.api.GrpcAPI.AssetIssueList;
import org.tron.api.GrpcAPI.NodeList;
import org.tron.api.GrpcAPI.WitnessList;
import org.tron.common.crypto.Hash;
import org.tron.common.crypto.SymmEncoder;
import org.tron.common.utils.Base58;
import org.tron.common.utils.ByteArray;
import org.tron.common.utils.TransactionUtils;
import org.tron.core.config.Parameter.CommonConstant;
import org.tron.protos.Contract;
import org.tron.protos.Contract.AssetIssueContract;
import org.tron.protos.Contract.FreezeBalanceContract;
import org.tron.protos.Contract.UnfreezeBalanceContract;
import org.tron.protos.Protocol;
import org.tron.protos.Protocol.Account;
import org.tron.protos.Protocol.Block;
import org.tron.protos.Protocol.Transaction;
import org.tron.protos.Protocol.Witness;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Locale;
import java.util.Set;

class AccountComparator implements Comparator {

    public int compare(Object o1, Object o2) {
        return Long.compare(((Account) o2).getBalance(), ((Account) o1).getBalance());
    }
}

class WitnessComparator implements Comparator {

    public int compare(Object o1, Object o2) {
        return Long.compare(((Witness) o2).getVoteCount(), ((Witness) o1).getVoteCount());
    }
}

public class WalletManager {

    private static GrpcClient rpcCli;

    public static void initGRPC() {
        if (rpcCli != null) {
            rpcCli.shutdown();
        }

        Context context = TronWalletApplication.getAppContext();
        SharedPreferences sharedPreferences = context.getSharedPreferences(context.getString(R.string.preference_file_key), Context.MODE_PRIVATE);

        String ip = "", ip_sol = "";
        int port = 0, port_sol = 0;

        try {
            ip = sharedPreferences.getString(context.getString(R.string.ip_key), context.getString(R.string.fullnode_ip));
            port = sharedPreferences.getInt(context.getString(R.string.port_key), Integer.parseInt(context.getString(R.string.fullnode_port)));

            ip_sol = sharedPreferences.getString(context.getString(R.string.ip_sol_key), context.getString(R.string.soliditynode_ip));
            port_sol = sharedPreferences.getInt(context.getString(R.string.port_sol_key), Integer.parseInt(context.getString(R.string.soliditynode_port)));

        } catch (NumberFormatException e) {
            e.printStackTrace();
        }
        rpcCli = new GrpcClient(
                !ip.equals("") ? String.format(Locale.US, "%s:%d", ip, port) : "",
                !ip_sol.equals("") ? String.format(Locale.US, "%s:%d", ip_sol, port_sol) : "");
    }

    public static void store(Wallet wallet, String password) throws DuplicateNameException, InvalidPasswordException, InvalidNameException {
        if (!wallet.isWatchOnly() && (wallet.getECKey() == null || wallet.getECKey().getPrivKey() == null)) {
            throw new NullPointerException("Private Key is null");
        }

        if(!wallet.isWatchOnly() && !isPasswordValid(password)) {
            throw new InvalidPasswordException("");
        }
        if(!isNameValid(wallet.getWalletName())) {
            throw new InvalidNameException("");
        }

        Context context = TronWalletApplication.getAppContext();
        if (context == null) {
            throw new NullPointerException("Context is null");
        }

        SharedPreferences sharedPreferences = context.getSharedPreferences(context.getString(R.string.preference_file_key), Context.MODE_PRIVATE);
        SharedPreferences.Editor sharedEditor = sharedPreferences.edit();

        Set<String> walletNames = new HashSet<>(sharedPreferences.getStringSet(context.getString(R.string.wallets_key), new HashSet<>()));

        if (walletNames.contains(wallet.getWalletName())) {
            throw new DuplicateNameException("Wallet name already exist");
        }
        walletNames.add(wallet.getWalletName());

        sharedEditor.putStringSet(context.getString(R.string.wallets_key), walletNames);
        sharedEditor.commit();


        SharedPreferences walletPreferences = context.getSharedPreferences(wallet.getWalletName(), Context.MODE_PRIVATE);
        SharedPreferences.Editor walletEditor = walletPreferences.edit();

        // SAVE IS WATCH ONLY
        walletEditor.putBoolean(context.getString(R.string.is_watch_only_setup_key), wallet.isWatchOnly());
        // SAVE IS COLD WALLET
        walletEditor.putBoolean(context.getString(R.string.is_cold_wallet_key), wallet.isColdWallet() && !wallet.isWatchOnly());
        // SAVE NAME
        walletEditor.putString(context.getString(R.string.wallet_name_key), wallet.getWalletName());
        // SAVE PUBKEY
        walletEditor.putString(context.getString(R.string.wallet_address_key), wallet.getAddress());

        if(!wallet.isWatchOnly()) {

            byte[] pwd = getPasswordHash(password);
            String pwdAsc = ByteArray.toHexString(pwd);

            //encrypted by password
            byte[] aseKey = getEncKey(password);

            byte[] privKeyPlain = wallet.getECKey().getPrivKeyBytes();
            byte[] privKeyEnced = SymmEncoder.AES128EcbEnc(privKeyPlain, aseKey);
            String privKeyStr = ByteArray.toHexString(privKeyEnced);

            byte[] pubKeyBytes = wallet.getECKey().getPubKey();
            String pubKeyStr = ByteArray.toHexString(pubKeyBytes);

            // SAVE PASSWORD
            walletEditor.putString(context.getString(R.string.pwd_key), pwdAsc);
            // SAVE PUBKEY
            walletEditor.putString(context.getString(R.string.pub_key), pubKeyStr);
            // SAVE PRIKEY
            walletEditor.putString(context.getString(R.string.priv_key), privKeyStr);
        }

        Bundle bundle = new Bundle();
        bundle.putString("address", wallet.getAddress());
        bundle.putBoolean("is_watch", wallet.isWatchOnly());
        FirebaseAnalytics.getInstance(context).logEvent("new_wallet", bundle);

        walletEditor.commit();
    }

    public static void storeWatchOnly(Wallet wallet) throws InvalidNameException, DuplicateNameException {
        wallet.setWatchOnly(true);
        try {
            store(wallet, null);
        } catch (InvalidPasswordException e) {
            e.printStackTrace();
        }
    }

    public static boolean existWallet(String walletName) {
        Context context = TronWalletApplication.getAppContext();
        SharedPreferences sharedPreferences = context.getSharedPreferences(context.getString(R.string.preference_file_key), Context.MODE_PRIVATE);
        Set<String> walletNames = sharedPreferences.getStringSet(context.getString(R.string.wallets_key), null);

        return walletNames != null && walletNames.contains(walletName);
    }

    public static boolean existAnyWallet() {
        return !getWalletNames().isEmpty();
    }

    public static Set<String> getWalletNames() {
        Context context = TronWalletApplication.getAppContext();
        SharedPreferences sharedPreferences = context.getSharedPreferences(context.getString(R.string.preference_file_key), Context.MODE_PRIVATE);
        return new HashSet<>(sharedPreferences.getStringSet(context.getString(R.string.wallets_key), new HashSet<>()));
    }

    public static Wallet getSelectedWallet() {
        Context context = TronWalletApplication.getAppContext();
        SharedPreferences pref = context.getSharedPreferences(context.getString(R.string.preference_file_key), Context.MODE_PRIVATE);
        return getWallet(pref.getString(context.getString(R.string.selected_wallet_key), ""));
    }

    public static Wallet getWallet(String walletName, String password) {
        if (existWallet(walletName)) {
            Context context = TronWalletApplication.getAppContext();
            SharedPreferences walletPref = context.getSharedPreferences(walletName, Context.MODE_PRIVATE);

            String privateKey = null;
            byte[] priKeyHexEnced = null;


            String privateKeyEncrypted = walletPref.getString(context.getString(R.string.priv_key), "");
            if (!privateKeyEncrypted.isEmpty()) {
                byte[] priKeyAscEnced = privateKeyEncrypted.getBytes();
                priKeyHexEnced = Hex.decode(priKeyAscEnced);

                if(password != null && checkPassword(walletName, password)) {
                    byte[] aesKey = getEncKey(password);
                    byte[] priKeyHexPlain = SymmEncoder.AES128EcbDec(priKeyHexEnced, aesKey);
                    if (priKeyHexPlain != null) {
                        privateKey = Hex.toHexString(priKeyHexPlain);
                    }
                }
            }

            Wallet wallet = new Wallet(privateKey);

            wallet.setWatchOnly(walletPref.getBoolean(context.getString(R.string.is_watch_only_setup_key), false));
            wallet.setColdWallet(walletPref.getBoolean(context.getString(R.string.is_cold_wallet_key), false));
            wallet.setWalletName(walletPref.getString(context.getString(R.string.wallet_name_key), ""));
            wallet.setEncryptedPassword(walletPref.getString(context.getString(R.string.pwd_key), ""));
            wallet.setAddress(walletPref.getString(context.getString(R.string.wallet_address_key), ""));
            wallet.setEncryptedPrivateKey(priKeyHexEnced);

            String publicKeyStr = walletPref.getString(context.getString(R.string.pub_key), "");
            if(!publicKeyStr.isEmpty()) {
                byte[] publicKey = Hex.decode(publicKeyStr.getBytes());
                wallet.setPublicKey(publicKey);
            }

            return wallet;
        }

        return null;
    }

    public static Wallet getWallet(String walletName) {
        return getWallet(walletName, null);
    }

    public static void selectWallet(String walletName) {
        Context context = TronWalletApplication.getAppContext();
        SharedPreferences sharedPreferences = context.getSharedPreferences(context.getString(R.string.preference_file_key), Context.MODE_PRIVATE);
        SharedPreferences.Editor editor = sharedPreferences.edit();

        if (existWallet(walletName)) {
            editor.putString(context.getString(R.string.selected_wallet_key), walletName);
            editor.commit();
        }
    }

    public static String decryptPrivateKey(byte[] privateKey, String password) {
        byte[] aesKey = getEncKey(password);
        byte[] priKeyHexPlain = SymmEncoder.AES128EcbDec(privateKey, aesKey);

        if (priKeyHexPlain != null) {
            return Hex.toHexString(priKeyHexPlain);
        }
        else
        {
            return "";
        }
    }

    public static Account queryAccount(byte[] address, boolean useSolidity) {
        return rpcCli.queryAccount(address, useSolidity);
    }

    public static Transaction createTransferAssetTransaction(byte[] to, byte[] assertName,
                                                             byte[] owner, long amount) {
        Contract.TransferAssetContract contract = createTransferAssetContract(to, assertName, owner,
                amount);
        return rpcCli.createTransferAssetTransaction(contract).getTransaction();
    }

    public static Transaction createParticipateAssetIssueTransaction(byte[] to, byte[] assertName,
                                                                     byte[] owner, long amount) {
        Contract.ParticipateAssetIssueContract contract = createParticipateAssetIssueContract(to, assertName,
                owner, amount);
        return rpcCli.createParticipateAssetIssueTransaction(contract).getTransaction();
    }

    public static Transaction createUpdateAccountTransaction(byte[] addressBytes, String accountName) {
        Contract.AccountUpdateContract contract = createAccountUpdateContract(ByteArray.fromString(accountName),
                addressBytes);
        return rpcCli.createTransaction(contract).getTransaction();
    }

    public static boolean broadcastTransaction(Transaction transaction) {
        return TransactionUtils.validTransaction(transaction)
                && rpcCli.broadcastTransaction(transaction);
    }

    public static Transaction createWitnessTransaction(byte[] owner, byte[] url) {
        Contract.WitnessCreateContract contract = createWitnessCreateContract(owner, url);
        return rpcCli.createWitness(contract).getTransaction();
    }

    public static Transaction createUpdateWitnessTransaction(byte[] owner, byte[] url) {
        Contract.WitnessUpdateContract contract = createWitnessUpdateContract(owner, url);
        return rpcCli.updateWitness(contract).getTransaction();
    }

    public static Transaction createVoteWitnessTransaction(byte[] owner,
                                                           HashMap<String, String> witness) {
        Contract.VoteWitnessContract contract = createVoteWitnessContract(owner, witness);
        return rpcCli.voteWitnessAccount(contract).getTransaction();
    }

    public static Contract.AssetIssueContract createAssetIssueContract(byte[] owner, String name, String abbr, long totalSupply, int trxNum, int icoNum,
                                                                       long startTime, long endTime, int voteScore, String description, String url,
                                                                       long freeNetLimit, long publicFreeNetLimit, List<AssetIssueContract.FrozenSupply> frozenSupply) {
        Contract.AssetIssueContract.Builder builder = Contract.AssetIssueContract.newBuilder();
        builder.setOwnerAddress(ByteString.copyFrom(owner));

        builder.setName(ByteString.copyFrom(name.getBytes()));
        builder.setAbbr(ByteString.copyFrom(abbr.getBytes()));

        if (totalSupply <= 0) {
            return null;
        }
        builder.setTotalSupply(totalSupply);
        if (trxNum <= 0) {
            return null;
        }
        builder.setTrxNum(trxNum);
        if (icoNum <= 0) {
            return null;
        }
        builder.setNum(icoNum);
        long now = System.currentTimeMillis();
        if (startTime <= now) {
            return null;
        }
        if (endTime <= startTime) {
            return null;
        }
        if (freeNetLimit < 0) {
            return null;
        }
        if (publicFreeNetLimit < 0) {
            return null;
        }

        builder.setStartTime(startTime);
        builder.setEndTime(endTime);
        builder.setVoteScore(voteScore);
        builder.setDescription(ByteString.copyFrom(description.getBytes()));
        builder.setUrl(ByteString.copyFrom(url.getBytes()));
        builder.setFreeAssetNetLimit(freeNetLimit);
        builder.setPublicFreeAssetNetLimit(publicFreeNetLimit);

        builder.addAllFrozenSupply(frozenSupply);

        return builder.build();
    }

    public static Transaction createAssetIssueTransaction(Contract.AssetIssueContract contract) {
        return rpcCli.createAssetIssue(contract).getTransaction();
    }

    public static GrpcAPI.BlockExtention getBlock(long blockNum, boolean useSolidity) {
        return rpcCli.getBlock(blockNum, useSolidity);
    }

    public static Contract.TransferContract createTransferContract(byte[] to, byte[] owner,
                                                                   long amount) {
        Contract.TransferContract.Builder builder = Contract.TransferContract.newBuilder();
        ByteString bsTo = ByteString.copyFrom(to);
        ByteString bsOwner = ByteString.copyFrom(owner);
        builder.setToAddress(bsTo);
        builder.setOwnerAddress(bsOwner);
        builder.setAmount(amount);

        return builder.build();
    }

    public static Contract.TransferAssetContract createTransferAssetContract(byte[] to,
                                                                             byte[] assertName, byte[] owner,
                                                                             long amount) {
        Contract.TransferAssetContract.Builder builder = Contract.TransferAssetContract.newBuilder();
        ByteString bsTo = ByteString.copyFrom(to);
        ByteString bsName = ByteString.copyFrom(assertName);
        ByteString bsOwner = ByteString.copyFrom(owner);
        builder.setToAddress(bsTo);
        builder.setAssetName(bsName);
        builder.setOwnerAddress(bsOwner);
        builder.setAmount(amount);

        return builder.build();
    }

    public static Contract.ParticipateAssetIssueContract createParticipateAssetIssueContract(byte[] to,
                                                                                             byte[] assertName, byte[] owner,
                                                                                             long amount) {
        Contract.ParticipateAssetIssueContract.Builder builder = Contract.ParticipateAssetIssueContract
                .newBuilder();
        ByteString bsTo = ByteString.copyFrom(to);
        ByteString bsName = ByteString.copyFrom(assertName);
        ByteString bsOwner = ByteString.copyFrom(owner);
        builder.setToAddress(bsTo);
        builder.setAssetName(bsName);
        builder.setOwnerAddress(bsOwner);
        builder.setAmount(amount);

        return builder.build();
    }

    public static Transaction createTransaction4Transfer(Contract.TransferContract contract) {
        Transaction transaction = rpcCli.createTransaction(contract).getTransaction();
        return transaction;
    }

    public static Contract.AccountUpdateContract createAccountUpdateContract(byte[] accountName,
                                                                             byte[] address) {
        Contract.AccountUpdateContract.Builder builder = Contract.AccountUpdateContract.newBuilder();
        ByteString basAddreess = ByteString.copyFrom(address);
        ByteString bsAccountName = ByteString.copyFrom(accountName);
        builder.setAccountName(bsAccountName);
        builder.setOwnerAddress(basAddreess);

        return builder.build();
    }

    public static Contract.UpdateAssetContract createUpdateAssetContract(
            byte[] address,
            byte[] description,
            byte[] url,
            long newLimit,
            long newPublicLimit
    ) {
        Contract.UpdateAssetContract.Builder builder =
                Contract.UpdateAssetContract.newBuilder();
        ByteString basAddreess = ByteString.copyFrom(address);
        builder.setDescription(ByteString.copyFrom(description));
        builder.setUrl(ByteString.copyFrom(url));
        builder.setNewLimit(newLimit);
        builder.setNewPublicLimit(newPublicLimit);
        builder.setOwnerAddress(basAddreess);

        return builder.build();
    }

    public static Contract.WitnessCreateContract createWitnessCreateContract(byte[] owner,
                                                                             byte[] url) {
        Contract.WitnessCreateContract.Builder builder = Contract.WitnessCreateContract.newBuilder();
        builder.setOwnerAddress(ByteString.copyFrom(owner));
        builder.setUrl(ByteString.copyFrom(url));

        return builder.build();
    }

    public static Contract.WitnessUpdateContract createWitnessUpdateContract(byte[] owner,
                                                                             byte[] url) {
        Contract.WitnessUpdateContract.Builder builder = Contract.WitnessUpdateContract.newBuilder();
        builder.setOwnerAddress(ByteString.copyFrom(owner));
        builder.setUpdateUrl(ByteString.copyFrom(url));

        return builder.build();
    }

    public static Contract.VoteWitnessContract createVoteWitnessContract(byte[] owner,
                                                                         HashMap<String, String> witness) {
        Contract.VoteWitnessContract.Builder builder = Contract.VoteWitnessContract.newBuilder();
        builder.setOwnerAddress(ByteString.copyFrom(owner));
        for (String addressBase58 : witness.keySet()) {
            String value = witness.get(addressBase58);
            long count = Long.parseLong(value);
            Contract.VoteWitnessContract.Vote.Builder voteBuilder = Contract.VoteWitnessContract.Vote
                    .newBuilder();
            byte[] address = WalletManager.decodeFromBase58Check(addressBase58);
            if (address == null) {
                continue;
            }
            voteBuilder.setVoteAddress(ByteString.copyFrom(address));
            voteBuilder.setVoteCount(count);
            builder.addVotes(voteBuilder.build());
        }

        return builder.build();
    }

    private static String loadPassword(String walletName) {
        Context context = TronWalletApplication.getAppContext();
        if (context == null || !existWallet(walletName))
            return null;
        SharedPreferences sharedPreferences = context.getSharedPreferences(walletName, Context.MODE_PRIVATE);
        return sharedPreferences.getString(context.getString(R.string.pwd_key), null);
    }

    public static String loadPubKey(String walletName) {
        Context context = TronWalletApplication.getAppContext();
        if (context == null || !existWallet(walletName))
            return null;
        SharedPreferences sharedPreferences = context.getSharedPreferences(walletName, Context.MODE_PRIVATE);
        return sharedPreferences.getString(context.getString(R.string.pub_key), null);
    }

    private static String loadPriKey(String walletName) {
        Context context = TronWalletApplication.getAppContext();
        if (context == null || !existWallet(walletName))
            return null;
        SharedPreferences sharedPreferences = context.getSharedPreferences(walletName, Context.MODE_PRIVATE);
        return sharedPreferences.getString(context.getString(R.string.priv_key), null);
    }

    public static byte[] getPasswordHash(String password) {
        if (!isPasswordValid(password)) {
            return null;
        }
        byte[] pwd;
        pwd = Hash.sha256(password.getBytes());
        pwd = Hash.sha256(pwd);
        pwd = Arrays.copyOfRange(pwd, 0, 16);
        return pwd;
    }

    public static byte[] getEncKey(String password) {
        if (!isPasswordValid(password)) {
            return null;
        }
        byte[] encKey;
        encKey = Hash.sha256(password.getBytes());
        encKey = Arrays.copyOfRange(encKey, 0, 16);
        return encKey;
    }

    public static boolean checkPassword(String walletName, String password) {
        byte[] pwd = getPasswordHash(password);
        if (pwd == null) {
            return false;
        }
        String pwdAsc = ByteArray.toHexString(pwd);
        String pwdInstore = loadPassword(walletName);
        return pwdAsc.equals(pwdInstore);
    }

    public static boolean checkPassword(Wallet wallet, String password) {
        byte[] pwd = getPasswordHash(password);
        if (pwd == null) {
            return false;
        }
        String pwdHexString = ByteArray.toHexString(pwd);
        return pwdHexString.equals(wallet.getEncryptedPassword());
    }

    public static boolean isNameValid(String name) {
        return !name.isEmpty();
    }

    public static boolean isPasswordValid(String password) {
        if (StringUtils.isEmpty(password)) {
            return false;
        }
        if (password.length() < 6) {
            return false;
        }
        if(password.contains("\\s")) {
            return false;
        }
        //Other rule;
        return true;
    }

    public static boolean isAddressValid(byte[] address) {
        if (address == null || address.length == 0) {
            return false;
        }
        if (address.length != CommonConstant.ADDRESS_SIZE) {
            return false;
        }
        byte preFixbyte = address[0];
        if (preFixbyte != CommonConstant.ADD_PRE_FIX_BYTE) {
            return false;
        }

        return true;
    }

    public static String encode58Check(byte[] input) {
        byte[] hash0 = Hash.sha256(input);
        byte[] hash1 = Hash.sha256(hash0);
        byte[] inputCheck = new byte[input.length + 4];
        System.arraycopy(input, 0, inputCheck, 0, input.length);
        System.arraycopy(hash1, 0, inputCheck, input.length, 4);
        return Base58.encode(inputCheck);
    }

    private static byte[] decode58Check(String input) {
        byte[] decodeCheck = Base58.decode(input);
        if (decodeCheck.length <= 4) {
            return null;
        }
        byte[] decodeData = new byte[decodeCheck.length - 4];
        System.arraycopy(decodeCheck, 0, decodeData, 0, decodeData.length);
        byte[] hash0 = Hash.sha256(decodeData);
        byte[] hash1 = Hash.sha256(hash0);
        if (hash1[0] == decodeCheck[decodeData.length] &&
                hash1[1] == decodeCheck[decodeData.length + 1] &&
                hash1[2] == decodeCheck[decodeData.length + 2] &&
                hash1[3] == decodeCheck[decodeData.length + 3]) {
            return decodeData;
        }
        return null;
    }

    public static byte[] decodeFromBase58Check(String addressBase58) {
        if (StringUtils.isEmpty(addressBase58)) {
            return null;
        }
        byte[] address = decode58Check(addressBase58);
        if (!isAddressValid(address)) {
            return null;
        }
        return address;
    }

    public static boolean isPrivateKeyValid(String priKey) {
        if (StringUtils.isEmpty(priKey)) {
            return false;
        }
        if (priKey.length() != 64) {
            return false;
        }

        return true;
    }

    public static WitnessList listWitnesses(boolean useSolidity) {
        WitnessList witnessList = rpcCli.listWitnesses(useSolidity);
        if (witnessList != null) {
            List<Witness> list = witnessList.getWitnessesList();
            List<Witness> newList = new ArrayList();
            newList.addAll(list);
            Collections.sort(newList, new WitnessComparator());
            WitnessList.Builder builder = WitnessList.newBuilder();
            for (Witness witness : newList) {
                builder.addWitnesses(witness);
            }
            witnessList = builder.build();
        }
        return witnessList;
    }

    public static AssetIssueList getAssetIssueList(boolean useSolidity) {
        return rpcCli.getAssetIssueList(useSolidity);
    }

    public static NodeList listNodes() {
        return rpcCli.listNodes();
    }

    public static AssetIssueList getAssetIssueByAccount(byte[] address) {
        return rpcCli.getAssetIssueByAccount(address);
    }

    public static AccountNetMessage getAccountNet(byte[] address) {
        return rpcCli.getAccountNet(address);
    }

    public static GrpcAPI.AccountResourceMessage getAccountRes(byte[] address) {
        return rpcCli.getAccountRes(address);
    }

    public static AssetIssueContract getAssetIssueByName(String assetName) {
        return rpcCli.getAssetIssueByName(assetName);
    }

    public static GrpcAPI.NumberMessage getTotalTransaction() {
        return rpcCli.getTotalTransaction();
    }

    public static GrpcAPI.NumberMessage getNextMaintenanceTime() {
        return rpcCli.getNextMaintenanceTime();
    }

    public static GrpcAPI.TransactionListExtention getTransactionsFromThis(byte[] address, int offset, int limit) {
        return rpcCli.getTransactionsFromThis(address, offset, limit);
    }

    public static GrpcAPI.TransactionListExtention getTransactionsToThis(byte[] address, int offset, int limit) {
        return rpcCli.getTransactionsToThis(address, offset, limit);
    }

    public static Transaction getTransactionById(String txID, boolean useSolidity) {
        return rpcCli.getTransactionById(txID, useSolidity);
    }

    public static Protocol.TransactionInfo getTransactionInfoById(String txID) {
        return rpcCli.getTransactionInfo(txID);
    }

    public static Protocol.TransactionInfo getTransactionInfo(Transaction transaction) {
        String txID = Hex.toHexString(Hash.sha256(transaction.getRawData().toByteArray()));
        return rpcCli.getTransactionInfo(txID);
    }

    public static boolean isTransactionConfirmed(Transaction transaction) {
        Protocol.Transaction confirmedTransaction = null;
        String txID = Hex.toHexString(Hash.sha256(transaction.getRawData().toByteArray()));

        int maxTries = 5;
        int tries = 0;
        while ((confirmedTransaction == null || !confirmedTransaction.hasRawData()) && tries <= maxTries) {
            try {
                confirmedTransaction = WalletManager.getTransactionById(txID, true);
                tries++;
            } catch (NullPointerException e) {
                e.printStackTrace();
            }
        }

        return confirmedTransaction.hasRawData() && txID.equals(Hex.toHexString(Hash.sha256(confirmedTransaction.getRawData().toByteArray())));
    }

    public static FreezeBalanceContract createFreezeBalanceContract(byte[] owner, long frozen_balance,
                                                                    long frozen_duration, Contract.ResourceCode resource) {
        Contract.FreezeBalanceContract.Builder builder = Contract.FreezeBalanceContract.newBuilder();
        ByteString byteAddreess = ByteString.copyFrom(owner);

        builder.setOwnerAddress(byteAddreess).setFrozenBalance(frozen_balance)
                .setFrozenDuration(frozen_duration)
        .setResource(resource);

        return builder.build();
    }

    public static Transaction createFreezeBalanceTransaction(byte[] owner, long frozen_balance, long frozen_duration, Contract.ResourceCode resource) {
        FreezeBalanceContract contract = createFreezeBalanceContract(owner, frozen_balance, frozen_duration, resource);
        return rpcCli.createTransaction(contract).getTransaction();
    }

    public static Transaction createUnfreezeBalanceTransaction(byte[] owner, Contract.ResourceCode resourceCode) {
        UnfreezeBalanceContract contract = createUnfreezeBalanceContract(owner, resourceCode);
        return rpcCli.createTransaction(contract).getTransaction();
    }

    public static UnfreezeBalanceContract createUnfreezeBalanceContract(byte[] owner, Contract.ResourceCode resourceCode) {

        Contract.UnfreezeBalanceContract.Builder builder = Contract.UnfreezeBalanceContract
                .newBuilder();
        ByteString byteAddreess = ByteString.copyFrom(owner);

        builder.setOwnerAddress(byteAddreess);
        builder.setResource(resourceCode);

        return builder.build();
    }

    public static Block getBlockById(String blockID) {
        return rpcCli.getBlockById(blockID);
    }

    public static GrpcAPI.BlockListExtention getBlockByLimitNext(long start, long end) {
        return rpcCli.getBlockByLimitNext(start, end);
    }

    public static GrpcAPI.BlockListExtention getBlockByLatestNum(long num) {
        return rpcCli.getBlockByLatestNum(num);
    }

}