static CoinPicker() { Transaction baseTx = Transaction.Create(new List <Transaction.Output>(), new Dictionary <Address, Money>(), new Dictionary <Address, ECKey>(), allowDust: true); MinBytesForTx = (long)(baseTx.ToByteArray().LongCount() * (1 + PercentError)); var input = new Transaction.Input("23e90c875e2ed7a1ec01f5a80643879625b8aeb48b67db64c0f9edb8259240b6", 0, 0) { ScriptSig = Script.FromString("3045022100f65c5e8c5d3b2386547a876db4ddb7bba1e57f9dbeaec9f3010516e453577fda02206f9df1a9262997263ac01be4342d0ade7057f5cceab4375fa1020ac7bfc5054b01 04ef96e3bccc8fff6b21d28e81f61c4a93cfe0f133214c9547c0d683a9fc12f529229c8d1ab20004c0f7f13961566b65492c6267fa452784c0724b4f542e4001f1") }; using (var stream = new MemoryStream()) { using (var writer = new BinaryWriter(stream)) { input.Write(writer); ApproxBytesPerInput = (long)(stream.ToArray().LongCount() * (1 + PercentError)); } } var output = new Transaction.Output(Script.FromString("OP_DUP OP_HASH160 4da1b9632e160406693b961ff321402b22ce5452 OP_EQUALVERIFY OP_CHECKSIG"), Money.Create(0.002m, "BTC"), baseTx, 0); using (var stream = new MemoryStream()) { using (var writer = new BinaryWriter(stream)) { output.Write(writer); ApproxBytesPerOutput = (long)(stream.ToArray().LongCount() * (1 + PercentError)); } } }
/// <summary> /// Constructs an unsigned transaction by referencing previous unspent outputs. /// A change output is added when necessary to return extra value back to the wallet. /// </summary> /// <param name="outputs">Transaction output array without change.</param> /// <param name="changeScript">Output script to pay change to.</param> /// <param name="fetchInputsAsync">Input selection source.</param> /// <returns>Unsigned transaction and total input amount.</returns> /// <exception cref="InsufficientFundsException">Input source was unable to provide enough input value.</exception> public static async Task<Tuple<Transaction, Amount>> BuildUnsignedTransaction(Transaction.Output[] outputs, Amount feePerKb, InputSource fetchInputsAsync, ChangeSource fetchChangeAsync) { if (outputs == null) throw new ArgumentNullException(nameof(outputs)); if (fetchInputsAsync == null) throw new ArgumentNullException(nameof(fetchInputsAsync)); if (fetchChangeAsync == null) throw new ArgumentNullException(nameof(fetchChangeAsync)); var targetAmount = outputs.Sum(o => o.Amount); var estimatedSize = Transaction.EstimateSerializeSize(1, outputs, true); var targetFee = TransactionFees.FeeForSerializeSize(feePerKb, estimatedSize); while (true) { var funding = await fetchInputsAsync(targetAmount + targetFee); var inputAmount = funding.Item1; var inputs = funding.Item2; if (inputAmount < targetAmount + targetFee) { throw new InsufficientFundsException(); } var maxSignedSize = Transaction.EstimateSerializeSize(inputs.Length, outputs, true); var maxRequiredFee = TransactionFees.FeeForSerializeSize(feePerKb, maxSignedSize); var remainingAmount = inputAmount - targetAmount; if (remainingAmount < maxRequiredFee) { targetFee = maxRequiredFee; continue; } var unsignedTransaction = new Transaction(Transaction.SupportedVersion, inputs, outputs, 0, 0); var changeAmount = inputAmount - targetAmount - maxRequiredFee; if (changeAmount != 0 && !TransactionRules.IsDustAmount(changeAmount, Transaction.PayToPubKeyHashPkScriptSize, feePerKb)) { var changeScript = await fetchChangeAsync(); if (changeScript.Script.Length > Transaction.PayToPubKeyHashPkScriptSize) { throw new Exception("Fee estimation requires change scripts no larger than P2PKH output scripts"); } var changeOutput = new Transaction.Output(changeAmount, Transaction.Output.LatestPkScriptVersion, changeScript.Script); var outputList = unsignedTransaction.Outputs.ToList(); outputList.Add(changeOutput); var outputsWithChange = outputList.ToArray(); unsignedTransaction = new Transaction(unsignedTransaction.Version, unsignedTransaction.Inputs, outputsWithChange, unsignedTransaction.LockTime, unsignedTransaction.Expiry); } return Tuple.Create(unsignedTransaction, inputAmount); } }
public Transaction WithdrawAsset(KeyPair toKey, string fromAddress, string symbol, decimal amount, byte[] verificationScript) { var fromScriptHash = new UInt160(fromAddress.GetScriptHashFromAddress()); var target = new Transaction.Output() { scriptHash = new UInt160(toKey.address.GetScriptHashFromAddress()), value = amount }; var targets = new List <Transaction.Output>() { target }; return(WithdrawAsset(toKey, fromScriptHash, symbol, targets, verificationScript)); }
public IEnumerator SendAsset(Action <Transaction, string> callback, UnspentEntries unspent, NeoKeys fromKey, string toAddress, string symbol, decimal amount, string interop, decimal fee = 0, bool allowSameSourceAndDest = false) { if (!allowSameSourceAndDest && String.Equals(fromKey.Address, toAddress, StringComparison.OrdinalIgnoreCase)) { throw new NeoException("Source and dest addresses are the same"); } var toScriptHash = toAddress.GetScriptHashFromAddress(); var target = new Transaction.Output() { scriptHash = new UInt160(toScriptHash), value = amount }; var targets = new List <Transaction.Output>() { target }; return(SendAsset(callback, unspent, fromKey, symbol, targets, interop, fee, allowSameSourceAndDest)); }
public Transaction SendAsset(KeyPair fromKey, string toAddress, string symbol, decimal amount) { if (String.Equals(fromKey.address, toAddress, StringComparison.OrdinalIgnoreCase)) { throw new NeoException("Source and dest addresses are the same"); } var toScriptHash = toAddress.GetScriptHashFromAddress(); var target = new Transaction.Output() { scriptHash = new UInt160(toScriptHash), value = amount }; var targets = new List <Transaction.Output>() { target }; return(SendAsset(fromKey, symbol, targets)); }
/// <summary> /// Potentially adds a change output to a transaction to set an appropiate fee. /// </summary> public static Transaction AddChange(Transaction tx, Amount totalInput, OutputScript changeScript, Amount feePerKb) { if (tx == null) { throw new ArgumentNullException(nameof(tx)); } if (totalInput < 0) { throw Errors.RequireNonNegative(nameof(totalInput)); } if (changeScript == null) { throw new ArgumentNullException(nameof(changeScript)); } if (feePerKb < 0) { throw Errors.RequireNonNegative(nameof(feePerKb)); } var txSerializeSizeEstimate = Transaction.EstimateSerializeSize(tx.Inputs.Length, tx.Outputs, true); var feeEstimate = FeeForSerializeSize(feePerKb, txSerializeSizeEstimate); var totalNonChangeOutput = tx.Outputs.Sum(o => o.Amount); var changeAmount = totalInput - totalNonChangeOutput - feeEstimate; var changeOutput = new Transaction.Output(changeAmount, Transaction.SupportedVersion, changeScript.Script); // Change should not be created if the change output itself would be considered dust. if (TransactionRules.IsDust(changeOutput, feePerKb)) { return(tx); } var outputList = tx.Outputs.ToList(); outputList.Add(changeOutput); // TODO: Randomize change output position. var outputs = outputList.ToArray(); return(new Transaction(tx.Version, tx.Inputs, outputs, tx.LockTime, tx.Expiry)); }
public static Transaction SendAsset(NeoAPI neoAPI, KeyPair fromKey, string toAddress, string symbol, decimal amount) { if (String.Equals(fromKey.address, toAddress, StringComparison.OrdinalIgnoreCase)) { throw new NeoException("Source and dest addresses are the same"); } var toScriptHash = toAddress.GetScriptHashFromAddress(); var target = new Transaction.Output() { scriptHash = new UInt160(toScriptHash), value = amount }; var targets = new List <Transaction.Output>() { target }; List <Transaction.Input> inputs; List <Transaction.Output> outputs; neoAPI.GenerateInputsOutputs(fromKey, symbol, targets, out inputs, out outputs); Transaction tx = new Transaction() { type = TransactionType.ContractTransaction, version = 0, script = null, gas = -1, inputs = inputs.ToArray(), outputs = outputs.ToArray() }; tx.Sign(fromKey); return(tx); }
private static string SerializeTransactionOutput(Transaction.Output output) { var value = num2fixed8(output.value); return(reverseHex(output.assetID) + value + reverseHex(output.scriptHash)); }
/// <summary> /// Constructs an unsigned transaction by referencing previous unspent outputs. /// A change output is added when necessary to return extra value back to the wallet. /// </summary> /// <param name="outputs">Transaction output array without change.</param> /// <param name="changeScript">Output script to pay change to.</param> /// <param name="fetchInputsAsync">Input selection source.</param> /// <returns>Unsigned transaction and total input amount.</returns> /// <exception cref="InsufficientFundsException">Input source was unable to provide enough input value.</exception> public static async Task <Tuple <Transaction, Amount> > BuildUnsignedTransaction(Transaction.Output[] outputs, Amount feePerKb, InputSource fetchInputsAsync, ChangeSource fetchChangeAsync) { if (outputs == null) { throw new ArgumentNullException(nameof(outputs)); } if (fetchInputsAsync == null) { throw new ArgumentNullException(nameof(fetchInputsAsync)); } if (fetchChangeAsync == null) { throw new ArgumentNullException(nameof(fetchChangeAsync)); } var targetAmount = outputs.Sum(o => o.Amount); var estimatedSize = Transaction.EstimateSerializeSize(1, outputs, true); var targetFee = TransactionFees.FeeForSerializeSize(feePerKb, estimatedSize); while (true) { var funding = await fetchInputsAsync(targetAmount + targetFee); var inputAmount = funding.Item1; var inputs = funding.Item2; if (inputAmount < targetAmount + targetFee) { throw new InsufficientFundsException(); } var maxSignedSize = Transaction.EstimateSerializeSize(inputs.Length, outputs, true); var maxRequiredFee = TransactionFees.FeeForSerializeSize(feePerKb, maxSignedSize); var remainingAmount = inputAmount - targetAmount; if (remainingAmount < maxRequiredFee) { targetFee = maxRequiredFee; continue; } var unsignedTransaction = new Transaction(Transaction.SupportedVersion, inputs, outputs, 0, 0); var changeAmount = inputAmount - targetAmount - maxRequiredFee; if (changeAmount != 0 && !TransactionRules.IsDustAmount(changeAmount, Transaction.PayToPubKeyHashPkScriptSize, feePerKb)) { var changeScript = await fetchChangeAsync(); if (changeScript.Script.Length > Transaction.PayToPubKeyHashPkScriptSize) { throw new Exception("Fee estimation requires change scripts no larger than P2PKH output scripts"); } var changeOutput = new Transaction.Output(changeAmount, Transaction.Output.LatestPkScriptVersion, changeScript.Script); var outputList = unsignedTransaction.Outputs.ToList(); outputList.Add(changeOutput); var outputsWithChange = outputList.ToArray(); unsignedTransaction = new Transaction(unsignedTransaction.Version, unsignedTransaction.Inputs, outputsWithChange, unsignedTransaction.LockTime, unsignedTransaction.Expiry); } return(Tuple.Create(unsignedTransaction, inputAmount)); } }
private async Task SignTransactionWithPassphrase(string passphrase, Transaction.Output[] outputs, bool publishImmediately) { var walletClient = App.Current.Synchronizer.WalletRpcClient; var requiredConfirmations = 1; // TODO: Don't hardcode confs. var targetAmount = outputs.Sum(o => o.Amount); var targetFee = (Amount)1e6; // TODO: Don't hardcode fee/kB. var funding = await walletClient.FundTransactionAsync(SelectedAccount.Account, targetAmount + targetFee, requiredConfirmations); var fundingAmount = funding.Item2; if (fundingAmount < targetAmount + targetFee) { MessageBox.Show($"Transaction requires {(Amount)(targetAmount + targetFee)} input value but only {fundingAmount} is spendable.", "Insufficient funds to create transaction."); return; } var selectedOutputs = funding.Item1; var inputs = selectedOutputs .Select(o => { var prevOutPoint = new Transaction.OutPoint(o.TransactionHash, o.OutputIndex, 0); return(Transaction.Input.CreateFromPrefix(prevOutPoint, TransactionRules.MaxInputSequence)); }) .ToArray(); // TODO: Port the fee estimation logic from btcwallet. Using a hardcoded fee is unacceptable. var estimatedFee = targetFee; var changePkScript = funding.Item3; if (changePkScript != null) { // Change output amount is calculated by solving for changeAmount with the equation: // estimatedFee = fundingAmount - (targetAmount + changeAmount) var changeOutput = new Transaction.Output(fundingAmount - targetAmount - estimatedFee, Transaction.Output.LatestPkScriptVersion, changePkScript.Script); var outputsList = outputs.ToList(); // TODO: Randomize change output position. outputsList.Add(changeOutput); outputs = outputsList.ToArray(); } // TODO: User may want to set the locktime. var unsignedTransaction = new Transaction(Transaction.SupportedVersion, inputs, outputs, 0, 0); var signingResponse = await walletClient.SignTransactionAsync(passphrase, unsignedTransaction); var complete = signingResponse.Item2; if (!complete) { MessageBox.Show("Failed to create transaction input signatures."); return; } var signedTransaction = signingResponse.Item1; MessageBox.Show($"Created tx with {estimatedFee} fee."); if (!publishImmediately) { MessageBox.Show("Reviewing signed transaction before publishing is not implemented yet."); return; } // TODO: The client just deserialized the transaction, so serializing it is a // little silly. This could be optimized. await walletClient.PublishTransactionAsync(signedTransaction.Serialize()); MessageBox.Show("Published transaction."); }
public void GenerateInputsOutputs(UInt160 from_script_hash, string symbol, IEnumerable <Transaction.Output> targets, out List <Transaction.Input> inputs, out List <Transaction.Output> outputs, decimal system_fee = 0) { var unspent = GetUnspent(from_script_hash); // filter any asset lists with zero unspent inputs unspent = unspent.Where(pair => pair.Value.Count > 0).ToDictionary(pair => pair.Key, pair => pair.Value); inputs = new List <Transaction.Input>(); outputs = new List <Transaction.Output>(); string assetID; var info = GetAssetsInfo(); if (info.ContainsKey(symbol)) { assetID = info[symbol]; } else { throw new NeoException($"{symbol} is not a valid blockchain asset."); } var from_address = from_script_hash.ToAddress(); if (!unspent.ContainsKey(symbol)) { throw new NeoException($"Not enough {symbol} in address {from_address}"); } decimal cost = 0; if (targets != null) { foreach (var target in targets) { if (target.scriptHash.Equals(from_script_hash)) { throw new NeoException("Target can't be same as input"); } cost += target.value; } } var targetAssetID = LuxUtils.ReverseHex(assetID).HexToBytes(); var sources = unspent[symbol]; decimal selected = 0; if (lastTransactions.ContainsKey(from_address)) { var lastTx = lastTransactions[from_address]; uint index = 0; foreach (var output in lastTx.outputs) { if (output.assetID.SequenceEqual(targetAssetID) && output.scriptHash.Equals(from_script_hash)) { selected += output.value; var input = new Transaction.Input() { prevHash = lastTx.Hash, prevIndex = index, }; inputs.Add(input); break; } index++; } } foreach (var src in sources) { if (selected >= cost && inputs.Count > 0) { break; } selected += src.value; var input = new Transaction.Input() { prevHash = src.hash, prevIndex = src.index, }; inputs.Add(input); } if (selected < cost) { throw new NeoException($"Not enough {symbol}"); } if (cost > 0 && targets != null) { foreach (var target in targets) { var output = new Transaction.Output() { assetID = targetAssetID, scriptHash = target.scriptHash, value = target.value }; outputs.Add(output); } } if (selected > cost || cost == 0) { var left = selected - cost; var change = new Transaction.Output() { assetID = targetAssetID, scriptHash = from_script_hash, value = left }; outputs.Add(change); } }
private static WalletTransaction.Output MarshalControlledOutput(TransactionDetails.Types.Output o, Transaction.Output txOutput) => new WalletTransaction.Output.ControlledOutput(txOutput.Amount, new Account(o.Account), o.Internal);