public Transaction GetTransaction() { var cOutputs = txOutListBox1.Items.Where(p => p.AssetId is UInt160).GroupBy(p => new { AssetId = (UInt160)p.AssetId, Account = p.ScriptHash }, (k, g) => new { k.AssetId, Value = g.Aggregate(BigInteger.Zero, (x, y) => x + y.Value.Value), k.Account }).ToArray(); Transaction tx; List <TransactionAttribute> attributes = new List <TransactionAttribute>(); if (comboBoxFrom.SelectedItem == null) { FromAddress = null; } else { FromAddress = ((string)comboBoxFrom.SelectedItem).ToScriptHash(); } if (cOutputs.Length == 0) { tx = new P2PTransaction(); } else { UInt160[] addresses; if (FromAddress != null) { addresses = Program.CurrentWallet.GetAccounts().Where(e => e.ScriptHash.Equals(FromAddress)).Select(p => p.ScriptHash).ToArray(); } else { addresses = Program.CurrentWallet.GetAccounts().Where(e => !e.WatchOnly).Select(p => p.ScriptHash).ToArray(); } HashSet <UInt160> sAttributes = new HashSet <UInt160>(); using (ScriptBuilder sb = new ScriptBuilder()) { foreach (var output in cOutputs) { byte[] script; using (ScriptBuilder sb2 = new ScriptBuilder()) { foreach (UInt160 address in addresses) { sb2.EmitAppCall(output.AssetId, "balanceOf", address); } sb2.Emit(OpCode.DEPTH, OpCode.PACK); script = sb2.ToArray(); } using (ApplicationEngine engine = ApplicationEngine.Run(script)) { if (engine.State.HasFlag(VMState.FAULT)) { return(null); } var balances = ((VMArray)engine.ResultStack.Pop()).AsEnumerable().Reverse().Zip(addresses, (i, a) => new { Account = a, Value = i.GetBigInteger() }).Where(p => p.Value != 0).ToArray(); BigInteger sum = balances.Aggregate(BigInteger.Zero, (x, y) => x + y.Value); if (sum < output.Value) { return(null); } if (sum != output.Value) { balances = balances.OrderByDescending(p => p.Value).ToArray(); BigInteger amount = output.Value; int i = 0; while (balances[i].Value <= amount) { amount -= balances[i++].Value; } if (amount == BigInteger.Zero) { balances = balances.Take(i).ToArray(); } else { balances = balances.Take(i).Concat(new[] { balances.Last(p => p.Value >= amount) }).ToArray(); } sum = balances.Aggregate(BigInteger.Zero, (x, y) => x + y.Value); } sAttributes.UnionWith(balances.Select(p => p.Account)); for (int i = 0; i < balances.Length; i++) { BigInteger value = balances[i].Value; if (i == 0) { BigInteger change = sum - output.Value; if (change > 0) { value -= change; } } sb.EmitAppCall(output.AssetId, "transfer", balances[i].Account, output.Account, value); sb.Emit(OpCode.THROWIFNOT); } } } tx = new InvocationTransaction { Version = 1, Script = sb.ToArray() }; } attributes.AddRange(sAttributes.Select(p => new TransactionAttribute { Usage = TransactionAttributeUsage.Script, Data = p.ToArray() })); } if (!string.IsNullOrEmpty(remark)) { attributes.Add(new TransactionAttribute { Usage = TransactionAttributeUsage.Remark, Data = Encoding.UTF8.GetBytes(remark) }); } tx.Attributes = attributes.ToArray(); tx.Outputs = txOutListBox1.Items.Where(p => p.AssetId is UInt256).Select(p => p.ToTxOutput()).ToArray(); var tempOuts = tx.Outputs; if (tx is P2PTransaction copyTx) { copyTx.Witnesses = new Witness[0]; copyTx = Program.CurrentWallet.MakeTransaction(copyTx, FromAddress, change_address: ChangeAddress, fee: Fee); if (copyTx == null) { return(null); } ContractParametersContext transContext = new ContractParametersContext(copyTx); Program.CurrentWallet.Sign(transContext); if (transContext.Completed) { copyTx.Witnesses = transContext.GetWitnesses(); } if (copyTx.Size > 1024) { Fixed8 PriorityFee = Fixed8.FromDecimal(0.001m) + Fixed8.FromDecimal(copyTx.Size * 0.00001m); if (Fee > PriorityFee) { PriorityFee = Fee; } if (!Helper.CostRemind(Fixed8.Zero, PriorityFee)) { return(null); } tx = Program.CurrentWallet.MakeTransaction(new P2PTransaction { Outputs = tempOuts, Attributes = tx.Attributes }, FromAddress, change_address: ChangeAddress, fee: PriorityFee); } } return(tx); }
private bool OnSendCommand(string[] args) { if (args.Length < 4 || args.Length > 5) { Console.WriteLine("error"); return(true); } if (NoWallet()) { return(true); } string password = ReadUserInput("password", true); if (password.Length == 0) { Console.WriteLine("cancelled"); return(true); } if (!Program.Wallet.VerifyPassword(password)) { Console.WriteLine("Incorrect password"); return(true); } UIntBase assetId; switch (args[1].ToLower()) { case "krona": case "krn": assetId = Blockchain.GoverningToken.Hash; break; default: assetId = UIntBase.Parse(args[1]); break; } UInt160 scriptHash = args[2].ToScriptHash(); bool isSendAll = string.Equals(args[3], "all", StringComparison.OrdinalIgnoreCase); Transaction tx; if (isSendAll) { Coin[] coins = Program.Wallet.FindUnspentCoins().Where(p => p.Output.AssetId.Equals(assetId)).ToArray(); tx = new P2PTransaction { Attributes = new TransactionAttribute[0], Inputs = coins.Select(p => p.Reference).ToArray(), Outputs = new[] { new TransactionOutput { AssetId = (UInt256)assetId, Value = coins.Sum(p => p.Output.Value), ScriptHash = scriptHash } } }; ContractParametersContext context = new ContractParametersContext(tx); Program.Wallet.Sign(context); if (context.Completed) { tx.Witnesses = context.GetWitnesses(); Program.Wallet.ApplyTransaction(tx); system.LocalNode.Tell(new LocalNode.Relay { Inventory = tx }); Console.WriteLine($"TXID: {tx.Hash}"); } else { Console.WriteLine("SignatureContext:"); Console.WriteLine(context.ToString()); } } else { AssetDescriptor descriptor = new AssetDescriptor(assetId); if (!BigDecimal.TryParse(args[3], descriptor.Decimals, out BigDecimal amount) || amount.Sign <= 0) { Console.WriteLine("Incorrect Amount Format"); return(true); } Fixed8 fee = Fixed8.Zero; if (args.Length >= 5) { if (!Fixed8.TryParse(args[4], out fee) || fee < Fixed8.Zero) { Console.WriteLine("Incorrect Fee Format"); return(true); } } tx = Program.Wallet.MakeTransaction(null, new[] { new TransferOutput { AssetId = assetId, Value = amount, ScriptHash = scriptHash } }, fee: fee); if (tx == null) { Console.WriteLine("Insufficient funds"); return(true); } ContractParametersContext context = new ContractParametersContext(tx); Program.Wallet.Sign(context); if (context.Completed) { tx.Witnesses = context.GetWitnesses(); if (tx.Size > 1024) { Fixed8 calFee = Fixed8.FromDecimal(tx.Size * 0.00001m + 0.001m); if (fee < calFee) { fee = calFee; tx = Program.Wallet.MakeTransaction(null, new[] { new TransferOutput { AssetId = assetId, Value = amount, ScriptHash = scriptHash } }, fee: fee); if (tx == null) { Console.WriteLine("Insufficient funds"); return(true); } context = new ContractParametersContext(tx); Program.Wallet.Sign(context); tx.Witnesses = context.GetWitnesses(); } } Program.Wallet.ApplyTransaction(tx); system.LocalNode.Tell(new LocalNode.Relay { Inventory = tx }); Console.WriteLine($"TXID: {tx.Hash}"); } else { Console.WriteLine("SignatureContext:"); Console.WriteLine(context.ToString()); } } return(true); }
public Transaction MakeTransaction(List <TransactionAttribute> attributes, IEnumerable <TransferOutput> outputs, UInt160 from = null, UInt160 change_address = null, Fixed8 fee = default(Fixed8)) { Random rand = new Random(); var cOutputs = outputs.Where(p => !p.IsGlobalAsset).GroupBy(p => new { AssetId = (UInt160)p.AssetId, Account = p.ScriptHash }, (k, g) => new { k.AssetId, Value = g.Aggregate(BigInteger.Zero, (x, y) => x + y.Value.Value), k.Account }).ToArray(); Transaction tx; if (attributes == null) { attributes = new List <TransactionAttribute>(); } // Generate nonce var nonce = new byte[8]; rand.NextBytes(nonce); attributes.Add(new TransactionAttribute() { Usage = TransactionAttributeUsage.Remark, Data = nonce }); if (cOutputs.Length == 0) { tx = new P2PTransaction(); } else { UInt160[] accounts = from == null?GetAccounts().Where(p => !p.Lock && !p.WatchOnly).Select(p => p.ScriptHash).ToArray() : new[] { from }; HashSet <UInt160> sAttributes = new HashSet <UInt160>(); using (ScriptBuilder sb = new ScriptBuilder()) { foreach (var output in cOutputs) { var balances = new List <(UInt160 Account, BigInteger Value)>(); foreach (UInt160 account in accounts) { byte[] script; using (ScriptBuilder sb2 = new ScriptBuilder()) { sb2.EmitAppCall(output.AssetId, "balanceOf", account); script = sb2.ToArray(); } using (ApplicationEngine engine = ApplicationEngine.Run(script)) { if (engine.State.HasFlag(VMState.FAULT)) { return(null); } var result = engine.ResultStack.Pop().GetBigInteger(); if (result == 0) { continue; } balances.Add((account, result)); } } BigInteger sum = balances.Aggregate(BigInteger.Zero, (x, y) => x + y.Value); if (sum < output.Value) { return(null); } if (sum != output.Value) { balances = balances.OrderByDescending(p => p.Value).ToList(); BigInteger amount = output.Value; int i = 0; while (balances[i].Value <= amount) { amount -= balances[i++].Value; } if (amount == BigInteger.Zero) { balances = balances.Take(i).ToList(); } else { balances = balances.Take(i).Concat(new[] { balances.Last(p => p.Value >= amount) }).ToList(); } sum = balances.Aggregate(BigInteger.Zero, (x, y) => x + y.Value); } sAttributes.UnionWith(balances.Select(p => p.Account)); for (int i = 0; i < balances.Count; i++) { BigInteger value = balances[i].Value; if (i == 0) { BigInteger change = sum - output.Value; if (change > 0) { value -= change; } } sb.EmitAppCall(output.AssetId, "transfer", balances[i].Account, output.Account, value); sb.Emit(OpCode.THROWIFNOT); } } sb.Emit(OpCode.RET); tx = new InvocationTransaction { Version = 1, Script = sb.ToArray() }; } attributes.AddRange(sAttributes.Select(p => new TransactionAttribute { Usage = TransactionAttributeUsage.Script, Data = p.ToArray() })); } tx.Attributes = attributes.ToArray(); tx.Inputs = new CoinReference[0]; tx.Outputs = outputs.Where(p => p.IsGlobalAsset).Select(p => p.ToTxOutput()).ToArray(); tx.Witnesses = new Witness[0]; if (tx is InvocationTransaction itx) { using (ApplicationEngine engine = ApplicationEngine.Run(itx.Script, itx)) { if (engine.State.HasFlag(VMState.FAULT)) { return(null); } Fixed8 freeGas; if (WalletHeight < Blockchain.FreeGasChangeHeight) { freeGas = Fixed8.FromDecimal(10); } else { freeGas = Fixed8.FromDecimal(50); } tx = new InvocationTransaction { Version = itx.Version, Script = itx.Script, Gas = InvocationTransaction.GetGas(engine.GasConsumed, freeGas), Attributes = itx.Attributes, Inputs = itx.Inputs, Outputs = itx.Outputs }; } } tx = MakeTransaction(tx, from, change_address, fee); return(tx); }