private async Task ShieldCoinbaseEmulatedAsync() { logger.Info(() => $"[{LogCategory}] Shielding ZCash Coinbase funds (emulated)"); // get t-addr unspent balance for just the coinbase address (pool wallet) var unspentResult = await daemon.ExecuteCmdSingleAsync <Utxo[]>(logger, BitcoinCommands.ListUnspent); if (unspentResult.Error != null) { logger.Error(() => $"[{LogCategory}] {BitcoinCommands.ListUnspent} returned error: {unspentResult.Error.Message} code {unspentResult.Error.Code}"); return; } var balance = unspentResult.Response .Where(x => x.Spendable && x.Address == poolConfig.Address) .Sum(x => x.Amount); // make sure there's enough balance to shield after reserves if (balance - TransferFee <= TransferFee) { logger.Info(() => $"[{LogCategory}] Balance {FormatAmount(balance)} too small for emulated shielding"); return; } logger.Info(() => $"[{LogCategory}] Transferring {FormatAmount(balance - TransferFee)} to pool's z-addr"); // transfer to z-addr var recipient = new ZSendManyRecipient { Address = poolExtraConfig.ZAddress, Amount = balance - TransferFee }; var args = new object[] { poolConfig.Address, // default account new object[] // addresses and associated amounts { recipient }, 1, TransferFee }; // send command var sendResult = await daemon.ExecuteCmdSingleAsync <string>(logger, EquihashCommands.ZSendMany, args); if (sendResult.Error != null) { logger.Error(() => $"[{LogCategory}] {EquihashCommands.ZSendMany} returned error: {unspentResult.Error.Message} code {unspentResult.Error.Code}"); return; } var operationId = sendResult.Response; logger.Info(() => $"[{LogCategory}] {EquihashCommands.ZSendMany} operation id: {operationId}"); var continueWaiting = true; while (continueWaiting) { var operationResultResponse = await daemon.ExecuteCmdSingleAsync <ZCashAsyncOperationStatus[]>(logger, EquihashCommands.ZGetOperationResult, new object[] { new object[] { operationId } }); if (operationResultResponse.Error == null && operationResultResponse.Response?.Any(x => x.OperationId == operationId) == true) { var operationResult = operationResultResponse.Response.First(x => x.OperationId == operationId); if (!Enum.TryParse(operationResult.Status, true, out ZOperationStatus status)) { logger.Error(() => $"Unrecognized operation status: {operationResult.Status}"); break; } switch (status) { case ZOperationStatus.Success: var txId = operationResult.Result?.Value <string>("txid") ?? string.Empty; logger.Info(() => $"[{LogCategory}] Transfer completed with transaction id: {txId}"); continueWaiting = false; continue; case ZOperationStatus.Cancelled: case ZOperationStatus.Failed: logger.Error(() => $"{EquihashCommands.ZSendMany} failed: {operationResult.Error.Message} code {operationResult.Error.Code}"); continueWaiting = false; continue; } } logger.Info(() => $"[{LogCategory}] Waiting for shielding transfer completion: {operationId}"); await Task.Delay(TimeSpan.FromSeconds(10)); } }
/// <summary> /// ZCash coins are mined into a t-addr (transparent address), but can only be /// spent to a z-addr (shielded address), and must be swept out of the t-addr /// in one transaction with no change. /// </summary> private async Task ShieldCoinbaseAsync() { logger.Info(() => $"[{LogCategory}] Shielding ZCash Coinbase funds"); // Emulate z_shieldcoinbase until its stable #if true // get t-addr balance var balanceResult = await daemon.ExecuteCmdSingleAsync <object>(BitcoinCommands.GetBalance); if (balanceResult.Error != null) { logger.Error(() => $"[{LogCategory}] {BitcoinCommands.GetBalance} returned error: {balanceResult.Error.Message} code {balanceResult.Error.Code}"); return; } var balance = (decimal)(double)balanceResult.Response; if (balance > 0) { logger.Info(() => $"[{LogCategory}] Transferring {FormatAmount(balance)} to pool's z-addr"); // transfer to z-addr var recipient = new ZSendManyRecipient { Address = poolExtraConfig.ZAddress, Amount = balance - TransferFee }; var args = new object[] { poolConfig.Address, // default account new object[] // addresses and associated amounts { recipient }, 10, // only spend funds covered by this many confirmations TransferFee }; // send command var sendResult = await daemon.ExecuteCmdSingleAsync <string>(ZCashCommands.ZSendMany, args); if (sendResult.Error != null) { logger.Error(() => $"[{LogCategory}] {ZCashCommands.ZSendMany} returned error: {balanceResult.Error.Message} code {balanceResult.Error.Code}"); return; } var operationId = sendResult.Response; logger.Info(() => $"[{LogCategory}] {ZCashCommands.ZSendMany} operation id: {operationId}"); var continueWaiting = true; while (continueWaiting) { var operationResultResponse = await daemon.ExecuteCmdSingleAsync <ZCashAsyncOperationStatus[]>( ZCashCommands.ZGetOperationResult, new object[] { new object[] { operationId } }); if (operationResultResponse.Error == null && operationResultResponse.Response?.Any(x => x.OperationId == operationId) == true) { var operationResult = operationResultResponse.Response.First(x => x.OperationId == operationId); if (!Enum.TryParse(operationResult.Status, true, out ZOperationStatus status)) { logger.Error(() => $"Unrecognized operation status: {operationResult.Status}"); break; } switch (status) { case ZOperationStatus.Success: var txId = operationResult.Result?.Value <string>("txid") ?? string.Empty; logger.Info(() => $"[{LogCategory}] Transfer completed with transaction id: {txId}"); continueWaiting = false; continue; case ZOperationStatus.Cancelled: case ZOperationStatus.Failed: logger.Error(() => $"{ZCashCommands.ZSendMany} failed: {operationResult.Error.Message} code {operationResult.Error.Code}"); continueWaiting = false; continue; } } logger.Info(() => $"[{LogCategory}] Waiting for transfer completion: {operationId}"); await Task.Delay(TimeSpan.FromSeconds(10)); } } #else var args = new object[] { poolConfig.Address, // source: pool's t-addr receiving coinbase rewards poolExtraConfig.ZAddress, // dest: pool's z-addr }; var result = await daemon.ExecuteCmdSingleAsync <string>(ZCashCommands.ZShieldCoinbase, args); if (result.Error != null) { logger.Error(() => $"[{LogCategory}] {ZCashCommands.ZShieldCoinbase} returned error: {result.Error.Message} code {result.Error.Code}"); return; } var operationId = result.Response; logger.Info(() => $"[{LogCategory}] {ZCashCommands.ZShieldCoinbase} operation id: {operationId}"); var continueWaiting = true; while (continueWaiting) { var operationResultResponse = await daemon.ExecuteCmdSingleAsync <ZCashAsyncOperationStatus[]>( ZCashCommands.ZGetOperationResult, new object[] { new object[] { operationId } }); if (operationResultResponse.Error == null && operationResultResponse.Response?.Any(x => x.OperationId == operationId) == true) { var operationResult = operationResultResponse.Response.First(x => x.OperationId == operationId); if (!Enum.TryParse(operationResult.Status, true, out ZOperationStatus status)) { logger.Error(() => $"Unrecognized operation status: {operationResult.Status}"); break; } switch (status) { case ZOperationStatus.Success: logger.Info(() => $"[{LogCategory}] {ZCashCommands.ZShieldCoinbase} successful"); continueWaiting = false; continue; case ZOperationStatus.Cancelled: case ZOperationStatus.Failed: logger.Error(() => $"{ZCashCommands.ZShieldCoinbase} failed: {operationResult.Error.Message} code {operationResult.Error.Code}"); continueWaiting = false; continue; } } logger.Info(() => $"[{LogCategory}] Waiting for operation completion: {operationId}"); await Task.Delay(TimeSpan.FromSeconds(10)); } #endif }
/// <summary> /// ZCash coins are mined into a t-addr (transparent address), but can only be /// spent to a z -addr (shielded address), and must be swept out of the t-addr /// in one transaction with no change. /// </summary> private async Task TransferTransparentPoolBalance() { // get t-addr balance var balanceResult = await daemon.ExecuteCmdSingleAsync <object>(BitcoinCommands.GetBalance); if (balanceResult.Error != null) { logger.Error(() => $"[{LogCategory}] Daemon command '{BitcoinCommands.GetBalance}' returned error: {balanceResult.Error.Message} code {balanceResult.Error.Code}"); return; } var balance = (decimal)(double)balanceResult.Response; if (balance > 0) { logger.Info(() => $"[{LogCategory}] Transferring {FormatAmount(balance)} to pool's z-addr"); // transfer to z-addr var recipient = new ZSendManyRecipient { Address = poolExtraConfig.ZAddress, Amount = balance - TransferFee }; var args = new object[] { poolConfig.Address, // default account new object[] // addresses and associated amounts { recipient }, 10, // only spend funds covered by this many confirmations TransferFee }; // send command var sendResult = await daemon.ExecuteCmdSingleAsync <string>(ZCashCommands.ZSendMany, args); if (sendResult.Error != null) { logger.Error(() => $"[{LogCategory}] Daemon command '{ZCashCommands.ZSendMany}' returned error: {balanceResult.Error.Message} code {balanceResult.Error.Code}"); return; } var operationId = sendResult.Response; logger.Info(() => $"[{LogCategory}] ZCash Balance Transfer operation id: {operationId}"); var continueWaiting = true; while (continueWaiting) { var operationResultResponse = await daemon.ExecuteCmdSingleAsync <ZCashAsyncOperationStatus[]>( ZCashCommands.ZGetOperationResult, new object[] { new object[] { operationId } }); if (operationResultResponse.Error == null && operationResultResponse.Response?.Any(x => x.OperationId == operationId) == true) { var operationResult = operationResultResponse.Response.First(x => x.OperationId == operationId); switch (operationResult.Status.ToLower()) { case "success": // extract transaction id var txId = operationResult.Result?.Value <string>("txid") ?? string.Empty; logger.Info(() => $"[{LogCategory}] ZCash Balance Transfer transaction id: {txId}"); continueWaiting = false; continue; case "cancelled": case "failed": logger.Error(() => $"ZCash transparent balance transfer failed: {operationResult.Error.Message} code {operationResult.Error.Code}"); continueWaiting = false; continue; } } logger.Info(() => $"[{LogCategory}] Waiting for ZCash Balance transfer to complete: {operationId}"); await Task.Delay(TimeSpan.FromSeconds(10)); } } }