/// <inheritdoc /> public Transaction BuildWithdrawalTransaction(uint256 depositId, uint blockTime, Recipient recipient) { try { this.logger.LogDebug("BuildDeterministicTransaction depositId(opReturnData)={0} recipient.ScriptPubKey={1} recipient.Amount={2}", depositId, recipient.ScriptPubKey, recipient.Amount); // Build the multisig transaction template. uint256 opReturnData = depositId; string walletPassword = this.federationWalletManager.Secret.WalletPassword; bool sign = (walletPassword ?? "") != ""; var multiSigContext = new TransactionBuildContext(new List <Recipient>(), opReturnData: opReturnData.ToBytes()) { MinConfirmations = MinConfirmations, Shuffle = false, IgnoreVerify = true, WalletPassword = walletPassword, Sign = sign, Time = this.network.Consensus.IsProofOfStake ? blockTime : (uint?)null }; multiSigContext.Recipients = new List <Recipient> { recipient.WithPaymentReducedByFee(FederatedPegSettings.CrossChainTransferFee) }; // The fee known to the user is taken. // TODO: Amend this so we're not picking coins twice. (List <Coin> coins, List <Wallet.UnspentOutputReference> unspentOutputs) = FederationWalletTransactionHandler.DetermineCoins(this.federationWalletManager, this.network, multiSigContext, this.federatedPegSettings); if (coins.Count > FederatedPegSettings.MaxInputs) { this.logger.LogDebug("Too many inputs. Triggering the consolidation process."); this.signals.Publish(new WalletNeedsConsolidation(recipient.Amount)); this.logger.LogTrace("(-)[CONSOLIDATING_INPUTS]"); return(null); } multiSigContext.TransactionFee = this.federatedPegSettings.GetWithdrawalTransactionFee(coins.Count); // The "actual fee". Everything else goes to the fed. multiSigContext.SelectedInputs = unspentOutputs.Select(u => u.ToOutPoint()).ToList(); multiSigContext.AllowOtherInputs = false; // Build the transaction. Transaction transaction = this.federationWalletTransactionHandler.BuildTransaction(multiSigContext); this.logger.LogDebug("transaction = {0}", transaction.ToString(this.network, RawFormat.BlockExplorer)); return(transaction); } catch (Exception error) { if (error is WalletException walletException && (walletException.Message == FederationWalletTransactionHandler.NoSpendableTransactionsMessage || walletException.Message == FederationWalletTransactionHandler.NotEnoughFundsMessage)) { this.logger.LogWarning("Not enough spendable transactions in the wallet. Should be resolved when a pending transaction is included in a block."); }
/// <inheritdoc /> public Transaction BuildWithdrawalTransaction(uint256 depositId, uint blockTime, Recipient recipient) { try { this.logger.LogInformation("BuildDeterministicTransaction depositId(opReturnData)={0} recipient.ScriptPubKey={1} recipient.Amount={2}", depositId, recipient.ScriptPubKey, recipient.Amount); // Build the multisig transaction template. uint256 opReturnData = depositId; string walletPassword = this.federationWalletManager.Secret.WalletPassword; bool sign = (walletPassword ?? "") != ""; var multiSigContext = new TransactionBuildContext(new[] { recipient.WithPaymentReducedByFee(this.federationGatewaySettings.TransactionFee) }.ToList(), opReturnData: opReturnData.ToBytes()) { TransactionFee = this.federationGatewaySettings.TransactionFee, MinConfirmations = MinConfirmations, Shuffle = false, IgnoreVerify = true, WalletPassword = walletPassword, Sign = sign, Time = this.network.Consensus.IsProofOfStake ? blockTime : (uint?)null }; // Build the transaction. Transaction transaction = this.federationWalletTransactionHandler.BuildTransaction(multiSigContext); this.logger.LogInformation("transaction = {0}", transaction.ToString(this.network, RawFormat.BlockExplorer)); return(transaction); } catch (Exception error) { if (error is WalletException walletException && (walletException.Message == FederationWalletTransactionHandler.NoSpendableTransactionsMessage || walletException.Message == FederationWalletTransactionHandler.NotEnoughFundsMessage)) { this.logger.LogWarning("Not enough spendable transactions in the wallet. Should be resolved when a pending transaction is included in a block."); }
/// <inheritdoc /> public Transaction BuildWithdrawalTransaction(int blockHeight, uint256 depositId, uint blockTime, Recipient recipient) { try { this.logger.LogDebug("BuildDeterministicTransaction depositId(opReturnData)={0}; recipient.ScriptPubKey={1}; recipient.Amount={2}; height={3}", depositId, recipient.ScriptPubKey, recipient.Amount, blockHeight); // Build the multisig transaction template. uint256 opReturnData = depositId; string walletPassword = this.federationWalletManager.Secret.WalletPassword; bool sign = (walletPassword ?? "") != ""; var multiSigContext = new TransactionBuildContext(new List <Recipient>(), opReturnData: opReturnData.ToBytes()) { MinConfirmations = MinConfirmations, Shuffle = false, IgnoreVerify = true, WalletPassword = walletPassword, Sign = sign, Time = this.network.Consensus.IsProofOfStake ? blockTime : (uint?)null }; // Withdrawals from the sidechain won't have the OP_RETURN transaction tag, so we need to check against the ScriptPubKey of the Cirrus Dummy address. if (!this.federatedPegSettings.IsMainChain && recipient.ScriptPubKey.Length > 0 && recipient.ScriptPubKey == BitcoinAddress.Create(this.network.CirrusRewardDummyAddress).ScriptPubKey) { // Use the distribution manager to determine the actual list of recipients. // TODO: This would probably be neater if it was moved to the CCTS with the current method accepting a list of recipients instead multiSigContext.Recipients = this.distributionManager.Distribute(blockHeight, recipient.WithPaymentReducedByFee(FederatedPegSettings.CrossChainTransferFee).Amount); // Reduce the overall amount by the fee first before splitting it up. // This should never happen as we should always have at least one federation member with a configured wallet. if (multiSigContext.Recipients.Count == 0) { this.logger.LogWarning("Could not identify recipients for the distribution transaction, adding dummy recipient to avoid the CCTS suspending irrecoverably."); multiSigContext.Recipients = new List <Recipient> { recipient.WithPaymentReducedByFee(FederatedPegSettings.CrossChainTransferFee) }; } } else { multiSigContext.Recipients = new List <Recipient> { recipient.WithPaymentReducedByFee(FederatedPegSettings.CrossChainTransferFee) }; // The fee known to the user is taken. } // TODO: Amend this so we're not picking coins twice. (List <Coin> coins, List <Wallet.UnspentOutputReference> unspentOutputs) = FederationWalletTransactionHandler.DetermineCoins(this.federationWalletManager, this.network, multiSigContext, this.federatedPegSettings); if (coins.Count > FederatedPegSettings.MaxInputs) { this.logger.LogDebug("Too many inputs. Triggering the consolidation process."); this.signals.Publish(new WalletNeedsConsolidation(recipient.Amount)); this.logger.LogTrace("(-)[CONSOLIDATING_INPUTS]"); return(null); } multiSigContext.TransactionFee = this.federatedPegSettings.GetWithdrawalTransactionFee(coins.Count); // The "actual fee". Everything else goes to the fed. multiSigContext.SelectedInputs = unspentOutputs.Select(u => u.ToOutPoint()).ToList(); multiSigContext.AllowOtherInputs = false; // Build the transaction. Transaction transaction = this.federationWalletTransactionHandler.BuildTransaction(multiSigContext); this.logger.LogDebug("transaction = {0}", transaction.ToString(this.network, RawFormat.BlockExplorer)); return(transaction); } catch (Exception error) { if (error is WalletException walletException && (walletException.Message == FederationWalletTransactionHandler.NoSpendableTransactionsMessage || walletException.Message == FederationWalletTransactionHandler.NotEnoughFundsMessage)) { this.logger.LogWarning("Not enough spendable transactions in the wallet. Should be resolved when a pending transaction is included in a block."); }