Exemplo n.º 1
0
        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));
                }
            }
        }
Exemplo n.º 2
0
        /// <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);
            }
        }
Exemplo n.º 3
0
        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));
        }
Exemplo n.º 4
0
        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));
        }
Exemplo n.º 5
0
        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));
        }
Exemplo n.º 6
0
        /// <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));
        }
Exemplo n.º 7
0
        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);
        }
Exemplo n.º 8
0
        private static string SerializeTransactionOutput(Transaction.Output output)
        {
            var value = num2fixed8(output.value);

            return(reverseHex(output.assetID) + value + reverseHex(output.scriptHash));
        }
Exemplo n.º 9
0
        /// <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));
            }
        }
Exemplo n.º 10
0
        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.");
        }
Exemplo n.º 11
0
        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);
            }
        }
Exemplo n.º 12
0
 private static WalletTransaction.Output MarshalControlledOutput(TransactionDetails.Types.Output o, Transaction.Output txOutput) =>
 new WalletTransaction.Output.ControlledOutput(txOutput.Amount, new Account(o.Account), o.Internal);