public static void ClaimPreloadGas(NeoSystem system, Neo.Wallets.Wallet wallet, Random random)
        {
            void SubmitTransaction(Func <Snapshot, WalletAccount, Transaction?> factory)
            {
                using var snapshot = Blockchain.Singleton.GetSnapshot();
                var validators = snapshot.GetValidators();

                if (validators.Length != 1)
                {
                    throw new InvalidOperationException("Preload only supported for single-node blockchains");
                }

                var account = wallet.GetAccounts().Single(a => a.Contract.Script.IsMultiSigContract());

                var tx = factory(snapshot, account);

                if (tx == null)
                {
                    throw new Exception("Attempted to submit null preload transaction");
                }
                var context = new ContractParametersContext(tx);

                wallet.Sign(context);
                if (!context.Completed)
                {
                    throw new InvalidOperationException("could not complete signing of preload transaction");
                }

                var block  = CreatePreloadBlock(wallet, random, tx);
                var result = system.Blockchain.Ask <RelayResultReason>(block).Result;

                if (result != RelayResultReason.Succeed)
                {
                    throw new Exception($"Preload {tx.Type} transaction failed {result}");
                }
            }

            SubmitTransaction((snapshot, account) => NodeUtility.MakeTransferTransaction(snapshot,
                                                                                         ImmutableHashSet.Create(account.ScriptHash), account.ScriptHash, Blockchain.GoverningToken.Hash, null));

            // There needs to be least one block after the transfer transaction before submitting a GAS claim
            var block  = CreatePreloadBlock(wallet, random);
            var result = system.Blockchain.Ask <RelayResultReason>(block).Result;

            if (result != RelayResultReason.Succeed)
            {
                throw new Exception($"Preload transfer suffix block failed {result}");
            }

            SubmitTransaction((snapshot, account) => NodeUtility.MakeClaimTransaction(snapshot, account.ScriptHash,
                                                                                      Blockchain.GoverningToken.Hash));
        }
        public JObject OnGetUnspents(JArray @params)
        {
            JObject GetBalance(IEnumerable <Coin> coins, UInt256 assetId, string symbol)
            {
                var unspents = new JArray();
                var total    = Fixed8.Zero;

                foreach (var coin in coins.Where(c => c.Output.AssetId == assetId))
                {
                    var unspent = new JObject();
                    unspent["txid"]  = coin.Reference.PrevHash.ToString().Substring(2);
                    unspent["n"]     = coin.Reference.PrevIndex;
                    unspent["value"] = (double)(decimal)coin.Output.Value;

                    total += coin.Output.Value;
                    unspents.Add(unspent);
                }

                var balance = new JObject();

                balance["asset_hash"]   = assetId.ToString().Substring(2);
                balance["asset_symbol"] = balance["asset"] = symbol;
                balance["amount"]       = (double)(decimal)total;
                balance["unspent"]      = unspents;

                return(balance);
            }

            var address = @params[0].AsString().ToScriptHash();

            string[]  nativeAssetNames = { "GAS", "NEO" };
            UInt256[] nativeAssetIds   = { Blockchain.UtilityToken.Hash, Blockchain.GoverningToken.Hash };

            using (var snapshot = Blockchain.Singleton.GetSnapshot())
            {
                var coins = NodeUtility.GetCoins(snapshot, ImmutableHashSet.Create(address)).Unspent();

                var neoCoins = coins.Where(c => c.Output.AssetId == Blockchain.GoverningToken.Hash);
                var gasCoins = coins.Where(c => c.Output.AssetId == Blockchain.UtilityToken.Hash);

                JObject json = new JObject();
                json["address"] = address.ToAddress();
                json["balance"] = new JArray(
                    GetBalance(coins, Blockchain.GoverningToken.Hash, "NEO"),
                    GetBalance(coins, Blockchain.UtilityToken.Hash, "GAS"));
                return(json);
            }
        }
        private JObject OnShowCoins(JArray @params)
        {
            var address = @params[0].AsString().ToScriptHash();

            using (var snapshot = Blockchain.Singleton.GetSnapshot())
            {
                var coins = NodeUtility.GetCoins(snapshot, ImmutableHashSet.Create(address));

                return(new JArray(coins.Select(c =>
                {
                    var j = new JObject();
                    j["state"] = (byte)c.State;
                    j["state-label"] = c.State.ToString();
                    j["reference"] = c.Reference.ToJson();
                    j["output"] = c.Output.ToJson(0);
                    return j;
                })));
            }
        }
        private JObject GetClaimable(JArray @params)
        {
            var address = @params[0].AsString().ToScriptHash();

            using (var snapshot = Blockchain.Singleton.GetSnapshot())
            {
                var coins          = NodeUtility.GetCoins(snapshot, ImmutableHashSet.Create(address));
                var unclaimedCoins = coins.Unclaimed(Blockchain.GoverningToken.Hash);

                var totalUnclaimed = Fixed8.Zero;
                var claimable      = new JArray();
                foreach (var coin in unclaimedCoins)
                {
                    var spentCoinState = snapshot.SpentCoins.TryGet(coin.Reference.PrevHash);
                    var startHeight    = spentCoinState.TransactionHeight;
                    var endHeight      = spentCoinState.Items[coin.Reference.PrevIndex];
                    var(generated, sysFee) = NodeUtility.CalculateClaimable(snapshot, coin.Output.Value, startHeight, endHeight);
                    var unclaimed = generated + sysFee;
                    totalUnclaimed += unclaimed;

                    var utxo = new JObject();
                    utxo["txid"]         = coin.Reference.PrevHash.ToString().Substring(2);
                    utxo["n"]            = coin.Reference.PrevIndex;
                    utxo["start_height"] = startHeight;
                    utxo["end_height"]   = endHeight;
                    utxo["generated"]    = (double)(decimal)generated;
                    utxo["sys_fee"]      = (double)(decimal)sysFee;
                    utxo["unclaimed"]    = (double)(decimal)(unclaimed);

                    claimable.Add(utxo);
                }

                JObject json = new JObject();
                json["claimable"] = claimable;
                json["address"]   = address.ToAddress();
                json["unclaimed"] = (double)(decimal)totalUnclaimed;
                return(json);
            }
        }
        private JObject GetUnclaimed(JArray @params)
        {
            var address = @params[0].AsString().ToScriptHash();

            using (var snapshot = Blockchain.Singleton.GetSnapshot())
            {
                var coins = NodeUtility.GetCoins(snapshot, ImmutableHashSet.Create(address));

                var unclaimedCoins = coins.Unclaimed(Blockchain.GoverningToken.Hash);
                var unspentCoins   = coins.Unspent(Blockchain.GoverningToken.Hash);

                var unavailable = snapshot.CalculateBonus(
                    unspentCoins.Select(c => c.Reference),
                    snapshot.Height + 1);
                var available = snapshot.CalculateBonus(unclaimedCoins.Select(c => c.Reference));

                JObject json = new JObject();
                json["unavailable"] = (double)(decimal)unavailable;
                json["available"]   = (double)(decimal)available;
                json["unclaimed"]   = (double)(decimal)(available + unavailable);
                return(json);
            }
        }