public static void issue_burn_tx( WalletConfig config, Keychain keychain, ulong amount, ulong minimumConfirmations, uint maxOutputs ) { keychain = Keychain.Burn_enabled(keychain, Identifier.Zero()); var chainTip = Checker.get_tip_from_node(config); var currentHeight = chainTip.Height; var _ = Checker.refresh_outputs(config, keychain); var keyId = keychain.Root_key_id(); // select some spendable coins from the walvar var coins = WalletData.Read_wallet( config.DataFileDir, walletData => walletData.Select( keyId.Clone(), amount, currentHeight, minimumConfirmations, maxOutputs, false)); Log.Debug("selected some coins - {}", coins.Length); var(partsArray, _) = inputs_and_change(coins, config, keychain, amount); var parts = partsArray.ToList(); // add burn output and fees var fee = Types.tx_fee((uint)coins.Length, 2, null); parts.Add(c => c.Output(amount - fee, Identifier.Zero())); // finalize the burn transaction and send var(txBurn, _) = Build.Transaction(parts.ToArray(), keychain); txBurn.Validate(keychain.Secp); var txHex = HexUtil.to_hex(Ser.Ser_vec(txBurn)); var url = $"{config.CheckNodeApiHttpAddr}/v1/pool/push"; var json = JsonConvert.SerializeObject(new TxWrapper { TxHex = txHex }); var res = ApiClient.PostContentAsync(url, new StringContent(json, Encoding.UTF8, "application/json")).Result; Log.Debug("{StatusCode}", res.StatusCode); }
// Read wallet data without acquiring the write lock. public static (Identifier, uint) retrieve_existing_key( WalletConfig config, Identifier keyId ) { return(WalletData.Read_wallet(config.DataFileDir, walletData => { var existing = walletData.Get_output(keyId); if (existing != null) { var keyId2 = existing.KeyId; var derivation = existing.NChild; return (Identifier.From_hex(keyId2), derivation); }
/// Builds a transaction to send to someone from the HD seed associated with the /// walvar and the amount to send. Handles reading through the walvar data file, /// selecting outputs to spend and building the change. public static (Transaction tx, BlindingFactor blind, OutputData[] outputs, Identifier keyid) build_send_tx( WalletConfig config, Keychain keychain, ulong amount, ulong currentHeight, ulong minimumConfirmations, ulong lockHeight, uint maxOutputs, bool defaultStrategy ) { var keyId = keychain.Root_key_id().Clone(); // select some spendable coins from the walvar var coins = WalletData.Read_wallet(config.DataFileDir, walletData => walletData.Select( keyId.Clone(), amount, currentHeight, minimumConfirmations, maxOutputs, defaultStrategy)); // build transaction skevaron with inputs and change var(partsArray, changeKey) = inputs_and_change(coins, config, keychain, amount); var parts = partsArray.ToList(); // This is more proof of concept than anything but here we set lock_height // on tx being sent (based on current chain height via api). parts.Add(c => c.with_lock_height(lockHeight)); var(tx, blind) = Build.Transaction(parts.ToArray(), keychain); return(tx, blind, coins, changeKey); }
/// Builds a single api query to retrieve the latest output data from the node. /// So we can refresh the local wallet outputs. public static bool refresh_outputs(WalletConfig config, Keychain keychain) { Log.Debug("Refreshing wallet outputs"); var walletOutputs = new Dictionary <string, Identifier>(); var commits = new List <Commitment>(); // build a local map of wallet outputs by commits // and a list of outputs we want to query the node for WalletData.Read_wallet(config.DataFileDir, walletData => { foreach (var op in walletData .Outputs .Values .Where(w => w.RootKeyId == keychain.Root_key_id().HexValue&& w.Status != OutputStatus.Spent)) { var commit = keychain.commit_with_key_index(op.Value, op.NChild); commits.Add(commit); walletOutputs.Add(commit.Hex, Identifier.From_hex(op.KeyId)); } return(walletData); }); // build the necessary query params - // ?id=xxx&id=yyy&id=zzz var queryParams = commits.Select(s => $"id={s.Hex}"); var queryString = string.Join("&", queryParams); var url = $"{config.CheckNodeApiHttpAddr}/v1/chain/utxos/byids?{queryString}"; // build a map of api outputs by commit so we can look them up efficiently var apiOutputs = new Dictionary <string, ApiOutput>(); //HttpClient here // todo:asyncification HttpResponseMessage response; try { response = ApiClient.GetAsync(url).Result; } catch (Exception ex) { Log.Warning(ex, "Failed to refresh from {Url}", url); return(false); } // if we got anything other than 200 back from server, don't attempt to refresh the wallet // data after if (!response.IsSuccessStatusCode) { Log.Warning("Failed to refresh from {Url} : {StatusCode} - {ReasonPhrase}", url, response.StatusCode, response.ReasonPhrase); return(false); } var content = response.Content.ReadAsStringAsync().Result; var outputs = JsonConvert.DeserializeObject <ApiOutput[]>(content); foreach (var op in outputs) { apiOutputs.Add(HexUtil.to_hex(op.Commit), op); } // now for each commit, find the output in the wallet and // the corresponding api output (if it exists) // and refresh it in-place in the wallet. // Note: minimizing the time we spend holding the wallet lock. WalletData.With_wallet(config.DataFileDir, walletData => { foreach (var commit in commits) { var id = walletOutputs[commit.Hex]; if (walletData.Outputs.TryGetValue(id.HexValue, out var op)) { if (apiOutputs.TryGetValue(commit.Hex, out var apiOutput)) { refresh_output(op, apiOutput); } else { mark_spent_output(op); } } } return(walletData); }); return(true); }
public static string build_info(WalletConfig config, Keychain keychain) { var sb = new StringBuilder(); var result = Checker.refresh_outputs(config, keychain); WalletData.Read_wallet(config.DataFileDir, walletData => { ulong currentHeight; try { var tip = Checker.get_tip_from_node(config); currentHeight = tip.Height; } catch { currentHeight = walletData.Outputs.Any() ? walletData.Outputs.Values.Max(m => m.Height) : 0; } ulong unspentTotal = 0; ulong unspentButLockedTotal = 0; ulong unconfirmedTotal = 0; ulong lockedTotal = 0; foreach (var op in walletData.Outputs.Values.Where(w => w.RootKeyId == keychain.Root_key_id().HexValue)) { if (op.Status == OutputStatus.Unspent) { unspentTotal += op.Value; if (op.LockHeight > currentHeight) { unspentButLockedTotal += op.Value; } } if (op.Status == OutputStatus.Unconfirmed && !op.IsCoinbase) { unconfirmedTotal += op.Value; } if (op.Status == OutputStatus.Locked) { lockedTotal += op.Value; } } var title = $"Wallet Summary Info - Block Height: {currentHeight}"; sb.AppendLine($"{title}"); sb.AppendLine("----------------------------------------------"); sb.AppendLine(""); sb.AppendLine($"Total: {unspentTotal + unconfirmedTotal}"); sb.AppendLine($"Awaiting Confirmation: {unconfirmedTotal}"); sb.AppendLine($"Confirmed but Still Locked: {unspentButLockedTotal}"); sb.AppendLine($"Currently Spendable {unspentTotal - unspentButLockedTotal}"); sb.AppendLine(""); sb.AppendLine("-----------------------------------------------"); sb.AppendLine(""); sb.AppendLine($"Locked by previous transaction: {lockedTotal}"); return(walletData); }); if (!result) { sb.AppendLine(""); sb.AppendLine( "WARNING - Showing local data only - Wallet was unable to contact a node to update and verify the info shown here."); } return(sb.ToString()); }