public void GeneratedTryteTest() { var trytes = IotaApiUtils.GenerateRandomTrytes(); Assert.AreEqual(81, trytes.Length); Assert.IsTrue(ContainsChars(trytes, Constants.TryteAlphabet)); //Assert.IsTrue(ContainsChars(Constants.TryteAlphabet, trytes)); // sometimes can throw }
private List <string> AddRemainder(string seed, List <Input> inputs, Bundle bundle, string tag, long totalValue, string remainderAddress, List <string> signatureFragments) { long totalTransferValue = totalValue; foreach (Input input in inputs) { var thisBalance = input.Balance; var toSubtract = 0 - thisBalance; var timestamp = IotaApiUtils.CreateTimeStampNow(); // Add input as bundle entry bundle.AddEntry(2, input.Address, toSubtract, tag, timestamp); // If there is a remainder value // Add extra output to send remaining funds to if (thisBalance >= totalTransferValue) { var remainder = thisBalance - totalTransferValue; // If user has provided remainder address // Use it to send remaining funds to if (remainder > 0 && remainderAddress != null) { // Remainder bundle entry bundle.AddEntry(1, remainderAddress, remainder, tag, timestamp); // function for signing inputs IotaApiUtils.SignInputsAndReturn(seed, inputs, bundle, signatureFragments, curl); } else if (remainder > 0) { // Generate a new Address by calling getNewAddress string address = GetNewAddress(seed)[0]; // Remainder bundle entry bundle.AddEntry(1, address, remainder, tag, timestamp); // function for signing inputs return(IotaApiUtils.SignInputsAndReturn(seed, inputs, bundle, signatureFragments, curl)); } else { // If there is no remainder, do not add transaction to bundle // simply sign and return return(IotaApiUtils.SignInputsAndReturn(seed, inputs, bundle, signatureFragments, curl)); } } // If multiple inputs provided, subtract the totalTransferValue by // the inputs balance else { totalTransferValue -= thisBalance; } } throw new NotEnoughBalanceException(totalValue); }
/// <summary> /// Attaches the specified transactions (trytes) to the Tangle by doing Proof of Work. /// You need to supply branchTransaction as well as trunkTransaction /// (basically the tips which you're going to validate and reference with this transaction) /// - both of which you'll get through the getTransactionsToApprove API call. /// </summary> /// <param name="trunkTransaction">Trunk transaction to approve.</param> /// <param name="branchTransaction">Branch transaction to approve.</param> /// <param name="trytes">List of trytes (raw transaction data) to attach to the tangle.</param> /// <param name="minWeightMagnitude">Proof of Work intensity. Minimum value is 18</param> /// <returns>The returned value contains a different set of tryte values which you can input into broadcastTransactions and storeTransactions. /// The returned tryte value, the last 243 trytes basically consist of the: trunkTransaction + branchTransaction + nonce. /// These are valid trytes which are then accepted by the network.</returns> public AttachToTangleResponse AttachToTangle(string trunkTransaction, string branchTransaction, string[] trytes, int minWeightMagnitude = 18) { if (!InputValidator.IsHash(trunkTransaction)) { throw new ArgumentException("Invalid hashes provided."); } if (!InputValidator.IsHash(branchTransaction)) { throw new ArgumentException("Invalid hashes provided."); } if (!InputValidator.IsArrayOfTrytes(trytes, 2673)) { throw new ArgumentException("Invalid trytes provided."); } if (LocalPow != null) { var response = new AttachToTangleResponse { Trytes = new List <string>() }; string previousTransaction = null; foreach (var t in trytes) { var txn = new Transaction(t) { TrunkTransaction = previousTransaction ?? trunkTransaction, BranchTransaction = previousTransaction == null ? branchTransaction : trunkTransaction }; if (string.IsNullOrEmpty(txn.Tag) || txn.Tag.Matches("9*")) { txn.Tag = txn.ObsoleteTag; } txn.AttachmentTimestamp = IotaApiUtils.CreateTimeStampNow(); txn.AttachmentTimestampLowerBound = 0; txn.AttachmentTimestampUpperBound = 3_812_798_742_493L; var resultTrytes = LocalPow.PerformPoW(txn.ToTransactionTrytes(), minWeightMagnitude); previousTransaction = new Transaction(resultTrytes).Hash; response.Trytes.Add(resultTrytes); } return(response); } AttachToTangleRequest attachToTangleRequest = new AttachToTangleRequest(trunkTransaction, branchTransaction, trytes, minWeightMagnitude); return(_genericIotaCoreApi.Request <AttachToTangleRequest, AttachToTangleResponse>(attachToTangleRequest)); }
public async Task <string> ApproveTransaction(string transactionHash, ApproveTransactionType approveType) { var api = CreateIotaClient(); //var address = await api.GetAddress(TestSeed2, 8); var emptyAddress = IotaApiUtils.GenerateRandomTrytes(81); // "".Pad(81); var transfer = new TransferItem() { Address = emptyAddress, Value = 0, Message = "", Tag = "" }; while (true) { try { CancellationTokenSource cts = new CancellationTokenSource(); var transactions = transfer.CreateTransactions(); var trytesToPow = transactions.GetTrytes().Single(); var toApprove = await api.IriApi.GetTransactionsToApprove(9); var diver = new PowDiver(); cts.CancelAfter(20000); var trunk = toApprove.TrunkTransaction; var branch = toApprove.BranchTransaction; if (approveType == ApproveTransactionType.Trunk) { trunk = transactionHash; } else { branch = transactionHash; } var trytesToSend = await diver.DoPow(trytesToPow.SetApproveTransactions(trunk, branch), 15, cts.Token); await api.IriApi.BroadcastTransactions(trytesToSend); await api.IriApi.StoreTransactions(trytesToSend); var transaction = new TransactionItem(trytesToSend); return(transaction.Hash); } catch (OperationCanceledException) { continue; } } }
public void TestAddressGeneration() { Assert.AreEqual(FIRST_ADDR, IotaApiUtils.NewAddress(TEST_SEED, 2, 0, true, null)); Assert.AreEqual(SIXTH_ADDR, IotaApiUtils.NewAddress(TEST_SEED, 2, 5, true, null)); Assert.AreEqual(ADDR_I0_S1, IotaApiUtils.NewAddress(ADDR_SEED, 1, 0, false, null)); Assert.AreEqual(ADDR_I0_S2, IotaApiUtils.NewAddress(ADDR_SEED, 2, 0, false, null)); Assert.AreEqual(ADDR_I0_S3, IotaApiUtils.NewAddress(ADDR_SEED, 3, 0, false, null)); Assert.AreEqual(ADDR_LS_I0_S1, IotaApiUtils.NewAddress(ADDR_SEED + ADDR_SEED, 1, 0, false, null)); Assert.AreEqual(ADDR_LS_I0_S2, IotaApiUtils.NewAddress(ADDR_SEED + ADDR_SEED, 2, 0, false, null)); Assert.AreEqual(ADDR_LS_I0_S3, IotaApiUtils.NewAddress(ADDR_SEED + ADDR_SEED, 3, 0, false, null)); }
/// <summary> /// Gets all possible inputs of a seed and returns them with the total balance. /// This is either done deterministically (by genearating all addresses until findTransactions is empty and doing /// getBalances), /// or by providing a key range to use for searching through. /// </summary> /// <param name="seed">Tryte-encoded seed. It should be noted that this seed is not transferred</param> /// <param name="security">The Security level of private key / seed.</param> /// <param name="start">Starting key index</param> /// <param name="end">Ending key index</param> /// <param name="threshold">The minimum threshold of accumulated balances from the inputs that is required</param> /// <returns>The inputs (see <see cref="Input" />) </returns> public Inputs GetInputs(string seed, int security, int start, int end, long threshold) { InputValidator.CheckIfValidSeed(seed); seed = InputValidator.PadSeedIfNecessary(seed); if (security < 1) { throw new ArgumentException("invalid security level provided"); } // If start value bigger than end, return error if (start > end) { throw new ArgumentException("start must be smaller than end", nameof(start)); } // or if difference between end and start is bigger than 500 keys if (end - start > 500) { throw new ArgumentException("total number of keys exceeded 500"); } // Case 1: start and end // // If start and end is defined by the user, simply iterate through the keys // and call getBalances if (end != 0) { var allAddresses = new string[end - start]; for (var i = start; i < end; i++) { var address = IotaApiUtils.NewAddress(seed, security, i, false, _curl); allAddresses[i] = address; } return(GetBalanceAndFormat(allAddresses, threshold, start, security)); } // Case 2: iterate till threshold || end // // Either start from index: 0 or start (if defined) until threshold is reached. // Calls getNewAddress and deterministically generates and returns all addresses // We then do getBalance, format the output and return it var addresses = GetNewAddress(seed, security, start, false, 0, true); return(GetBalanceAndFormat(addresses, threshold, start, security)); }
/// <summary> /// Generates a new address from a seed and returns the remainderAddress. This is either done deterministically, or by providing the index of the new remainderAddress /// </summary> /// <param name="seed">Tryte-encoded seed. It should be noted that this seed is not transferred</param> /// <param name="index">Optional (default null). Key index to start search from. If the index is provided, the generation of the address is not deterministic.</param> /// <param name="checksum">Optional (default false). Adds 9-tryte address checksum</param> /// <param name="total">Optional (default 1)Total number of addresses to generate.</param> /// <param name="returnAll">If true, it returns all addresses which were deterministically generated (until findTransactions returns null)</param> /// <returns>an array of strings with the specifed number of addresses</returns> public string[] GetNewAddress(string seed, int index = 0, bool checksum = false, int total = 0, bool returnAll = false) { List <string> allAdresses = new List <string>(); // TODO make two different functions out of this // Case 1: total // // If total number of addresses to generate is supplied, simply generate // and return the list of all addresses if (total > 0) { // Increase index with each iteration for (int i = index; i < index + total; i++) { allAdresses.Add(IotaApiUtils.NewAddress(seed, i, checksum, curl)); } return(allAdresses.ToArray()); } // Case 2: no total provided // // Continue calling findTransactions to see if address was already created // if null, return list of addresses // else { List <string> addresses = new List <string>(); for (int i = index;; i++) { string newAddress = IotaApiUtils.NewAddress(seed, i, checksum, curl); FindTransactionsResponse response = FindTransactionsByAddresses(newAddress); if (returnAll) { addresses.Add(newAddress); } if (response.Hashes.Count == 0) { break; } } return(addresses.ToArray()); } }
private string GetFirstUnusedAddress(string seed, int securityLevel, int index, bool checksum) { while (true) { string newAddress = IotaApiUtils.NewAddress( seed, securityLevel, index, checksum, SpongeFactory.Create(SpongeFactory.Mode.KERL)); if (!IsAddressSpent(newAddress, checksum)) { return(newAddress); } index++; } }
public async Task SendVeryEmptyTransactionTest() { var api = CreateIotaClient(); var emptyAddress = IotaApiUtils.GenerateRandomTrytes(81); // "".Pad(81); var transfer = new TransferItem() { Address = emptyAddress, Value = 0, Message = null, Tag = null }; while (true) { try { CancellationTokenSource cts = new CancellationTokenSource(); var transactions = transfer.CreateTransactions(); var trytesToPow = transactions.GetTrytes().Single(); var toApprove = await api.IriApi.GetTransactionsToApprove(9); var diver = new PowDiver(); //cts.CancelAfter(15000); var trunk = toApprove.TrunkTransaction; var branch = toApprove.BranchTransaction; var trytesToSend = await diver.DoPow(trytesToPow.SetApproveTransactions(trunk, branch), 15, cts.Token); await api.IriApi.BroadcastTransactions(trytesToSend); await api.IriApi.StoreTransactions(trytesToSend); var transaction = new TransactionItem(trytesToSend); } catch (OperationCanceledException) { continue; } } }
private async Task <AddressItem> NewAddressAsync(string seed, int index, CancellationToken cancellationToken) { await TaskIota.Yield().ConfigureAwait(false); // need yield because new address generation is very costly int[] key = new Signing(new Curl()).Key(Converter.ToTrits(seed), index, 2); string address = IotaApiUtils.NewAddress(key, false, new Curl(), cancellationToken); var addressItem = new AddressItem() { Address = address, PrivateKey = key, Index = index, Balance = 0 }; await RenewTransactions(addressItem); return(addressItem); }
private static IEnumerable <TransactionItem> CreateDepositTransaction(TransferItem transferItem) { var timestamp = IotaApiUtils.CreateTimeStampNow().ToString(); var tag = transferItem.Tag.ValidateTrytes(nameof(transferItem.Tag)).Pad(27); var messages = ChunksUpto(transferItem.Message, 2187).ToArray(); for (int i = 0; i < messages.Length; i++) { var message = messages[i].ValidateTrytes(nameof(transferItem.Message)).Pad(2187); var transactionItem = new TransactionItem() { Address = transferItem.Address, SignatureFragment = message, Value = (i == 0 ? transferItem.Value : 0).ToString(), Timestamp = timestamp, Tag = tag, }; yield return(transactionItem); } }
private List <string> GetAddresses(string seed, int securityLevel, int index, bool checksum, int amount, bool addSpendAddresses) { List <string> addresses = new List <string>(); for (int i = index, numUnspentFound = 0; numUnspentFound < amount; i++) { string newAddress = IotaApiUtils.NewAddress(seed, securityLevel, i, checksum, SpongeFactory.Create(SpongeFactory.Mode.KERL)); if (!IsAddressSpent(newAddress, checksum)) { addresses.Add(newAddress); numUnspentFound++; } else if (addSpendAddresses) { addresses.Add(newAddress); } } return(addresses); }
/// <summary> /// This does not mean that these addresses are safe to use (unspent) /// </summary> /// <param name="addressRequest"></param> /// <returns></returns> public GetNewAddressResponse GetAddressesUnchecked(AddressRequest addressRequest) { var stopwatch = Stopwatch.StartNew(); List <string> addresses = new List <string>(); for (int i = 0; i < addressRequest.Amount; i++) { addresses.Add( IotaApiUtils.NewAddress( addressRequest.Seed, addressRequest.SecurityLevel, i, addressRequest.Checksum, SpongeFactory.Create(SpongeFactory.Mode.KERL))); } stopwatch.Stop(); return(new GetNewAddressResponse { Addresses = addresses, Duration = stopwatch.ElapsedMilliseconds }); }
/// <summary> /// Main purpose of this function is to get an array of transfer objects as input, and then prepare the transfer by /// generating the correct bundle, /// as well as choosing and signing the inputs if necessary (if it's a value transfer). The output of this function is /// an array of the raw transaction data (trytes) /// </summary> /// <param name="seed">81-tryte encoded address of recipient</param> /// <param name="security"></param> /// <param name="transfers">the transfers to prepare</param> /// <param name="inputs">Optional (default null). The inputs</param> /// <param name="remainderAddress"> /// Optional (default null). if defined, this address will be used for sending the remainder /// value (of the inputs) to. /// </param> /// <param name="validateInputs"></param> /// <returns>a list containing the trytes of the new bundle</returns> public List <string> PrepareTransfers( string seed, int security, Transfer[] transfers, string remainderAddress, List <Input> inputs, bool validateInputs) { // validate seed if (!InputValidator.IsValidSeed(seed)) { throw new IllegalStateException("Invalid seed provided."); } if (security < 1) { throw new ArgumentException("Invalid security level provided."); } // Input validation of transfers object InputValidator.CheckTransferArray(transfers); // Create a new bundle var bundle = new Bundle(); var signatureFragments = new List <string>(); long totalValue = 0; var tag = ""; // // Iterate over all transfers, get totalValue // and prepare the signatureFragments, message and tag // foreach (var transfer in transfers) { // remove the checksum of the address if provided transfer.Address = transfer.Address.RemoveChecksum(); var signatureMessageLength = 1; // If message longer than 2187 trytes, increase signatureMessageLength (add 2nd transaction) if (transfer.Message.Length > Constants.MessageLength) { // Get total length, message / maxLength (2187 trytes) signatureMessageLength += (int)Math.Floor((double)transfer.Message.Length / Constants.MessageLength); var msgCopy = transfer.Message; // While there is still a message, copy it while (!string.IsNullOrEmpty(msgCopy)) { var fragment = msgCopy.Substring(0, 2187 > msgCopy.Length ? msgCopy.Length : 2187); msgCopy = msgCopy.Substring(2187, msgCopy.Length - 2187); // Pad remainder of fragment while (fragment.Length < 2187) { fragment += '9'; } signatureFragments.Add(fragment); } } else { // Else, get single fragment with 2187 of 9's trytes var fragment = string.Empty; if (!string.IsNullOrEmpty(transfer.Message)) { fragment = transfer.Message.Substring(0, transfer.Message.Length < 2187 ? transfer.Message.Length : 2187); } while (fragment.Length < 2187) { fragment += '9'; } signatureFragments.Add(fragment); } // get current timestamp in seconds var timestamp = (long)Math.Floor((double)IotaApiUtils.CreateTimeStampNow() / 1000); // If no tag defined, get 27 tryte tag. tag = string.IsNullOrEmpty(transfer.Tag) ? "999999999999999999999999999" : transfer.Tag; // Pad for required 27 tryte length while (tag.Length < 27) { tag += '9'; } // Add first entries to the bundle // Slice the address in case the user provided a checksummed one bundle.AddEntry(signatureMessageLength, transfer.Address, transfer.Value, tag, timestamp); // Sum up total value totalValue += transfer.Value; } // Get inputs if we are sending tokens if (totalValue != 0) { if (inputs != null && inputs.Count > 0) { // Get list if addresses of the provided inputs var inputAddresses = new List <string>(); foreach (var input in inputs) { inputAddresses.Add(input.Address); } var balances = GetBalances(inputAddresses, 100); var confirmedInputs = new List <Input>(); long totalBalance = 0; for (var i = 0; i < balances.Balances.Count; i++) { var thisBalance = balances.Balances[i]; totalBalance += thisBalance; // If input has balance, add it to confirmedInputs if (thisBalance > 0) { var inputEl = inputs[i]; inputEl.Balance = thisBalance; confirmedInputs.Add(inputEl); } } // Return not enough balance error if (totalValue > totalBalance) { throw new NotEnoughBalanceException(totalValue); } return(AddRemainder(seed, security, confirmedInputs, bundle, tag, totalValue, remainderAddress, signatureFragments)); } // Case 2: Get inputs deterministically // // If no inputs provided, derive the addresses from the seed and // confirm that the inputs exceed the threshold else { var inputList = GetInputs(seed, security, 0, 0, (int)totalValue).InputsList; return(AddRemainder(seed, security, inputList, bundle, tag, totalValue, remainderAddress, signatureFragments)); } } // If no input required, don't sign and simply finalize the bundle bundle.FinalizeBundle(_curl.Clone()); bundle.AddTrytes(signatureFragments); var bundleTrytes = new List <string>(); bundle.Transactions.ForEach(tx => bundleTrytes.Add(tx.ToTransactionTrytes())); bundleTrytes.Reverse(); return(bundleTrytes); }
private static IEnumerable <TransactionItem> CreateWithdrawalTransactions(string tag, long withdrawAmount, string reminderAddress, params AddressItem[] addressItems) { if (string.IsNullOrWhiteSpace(reminderAddress)) { throw new ArgumentNullException(nameof(reminderAddress)); } var curl = new Curl(); tag = tag.ValidateTrytes(nameof(tag)).Pad(27); foreach (var addressItem in addressItems) { if (addressItem.Balance <= 0) { continue; } var timestamp = IotaApiUtils.CreateTimeStampNow().ToString(); var amount = addressItem.Balance; withdrawAmount -= amount; var transactionItem = new TransactionItem() { Address = addressItem.Address, Value = (-amount).ToString(), // withdraw all amount Timestamp = timestamp, Tag = tag }; yield return(transactionItem); transactionItem = new TransactionItem() { Address = addressItem.Address, Value = "0", Timestamp = timestamp, Tag = tag }; yield return(transactionItem); if (withdrawAmount < 0) // deposit remind amount to reminder address { var message = "".Pad(2187); transactionItem = new TransactionItem() { Address = reminderAddress, Value = Math.Abs(withdrawAmount).ToString(), Timestamp = timestamp, SignatureFragment = message, Tag = tag }; yield return(transactionItem); } if (withdrawAmount <= 0) { break; } } }
/// <summary> /// /// </summary> /// <param name="seed"></param> /// <param name="security"></param> /// <param name="start"></param> /// <param name="end"></param> /// <param name="threshold"></param> /// <param name="tips"></param> /// <returns></returns> public GetBalancesAndFormatResponse GetInputs(string seed, int security, int start, int end, long threshold, params string[] tips) { // validate the seed if (!InputValidator.IsValidSeed(seed)) { throw new IllegalStateException(Constants.INVALID_SEED_INPUT_ERROR); } seed = InputValidator.PadSeedIfNecessary(seed); if (!InputValidator.IsValidSecurityLevel(security)) { throw new ArgumentException(Constants.INVALID_SECURITY_LEVEL_INPUT_ERROR); } // If start value bigger than end, return error if (start > end) { throw new ArgumentException("start must be smaller than end", nameof(start)); } // or if difference between end and start is bigger than 500 keys if (end - start > 500) { throw new ArgumentException("total number of keys exceeded 500"); } Stopwatch stopwatch = Stopwatch.StartNew(); // Case 1: start and end // // If start and end is defined by the user, simply iterate through the keys // and call getBalances if (end != 0) { var allAddresses = new string[end - start]; for (var i = start; i < end; i++) { var address = IotaApiUtils.NewAddress(seed, security, i, true, SpongeFactory.Create(SpongeFactory.Mode.KERL)); allAddresses[i] = address; } return(GetBalanceAndFormat(allAddresses, tips, threshold, start, security, stopwatch)); } { // Case 2: iterate till threshold // // Either start from index: 0 or start (if defined) until threshold is reached. List <Input> allInputs = new List <Input>(); bool thresholdReached = true; long currentTotal = 0; for (int i = start; thresholdReached; i++) { string address = IotaApiUtils.NewAddress(seed, security, i, true, SpongeFactory.Create(SpongeFactory.Mode.KERL)); // Received input, this epoch or previous GetBalancesResponse response = IotaClient.GetBalances(100, new List <string>() { address }, tips.ToList()); var balance = response.Balances[0]; if (balance > 0) { // Is it already spent from? WereAddressesSpentFromResponse wasSpent = IotaClient.WereAddressesSpentFrom(address); if (wasSpent.States.Length > 0 && !wasSpent.States[0]) { // We can use this! allInputs.Add(new Input { Address = address, Balance = balance, KeyIndex = i, Security = security }); currentTotal += balance; if (threshold != 0 && threshold <= currentTotal) { // Stop because we found threshold thresholdReached = false; } } } else { // Check if there was any activity at all FindTransactionsResponse tx = IotaClient.FindTransactionsByAddresses(address); if (tx.Hashes.Count == 0 || i - start > 500) { // Stop because we reached our limit or no activity thresholdReached = false; } } } stopwatch.Stop(); return(new GetBalancesAndFormatResponse(allInputs, currentTotal, stopwatch.ElapsedMilliseconds)); } }
/// <summary> /// Main purpose of this function is to get an array of transfer objects as input, and then prepare the transfer by generating the correct bundle, /// as well as choosing and signing the inputs if necessary (if it's a value transfer). The output of this function is an array of the raw transaction data (trytes) /// </summary> /// <param name="seed">81-tryte encoded address of recipient</param> /// <param name="transfers">the transfers to prepare</param> /// <param name="inputs">Optional (default null). The inputs</param> /// <param name="remainderAddress">Optional (default null). if defined, this address will be used for sending the remainder value (of the inputs) to.</param> /// <returns>a list containing the trytes of the new bundle</returns> public List <string> PrepareTransfers(string seed, Transfer[] transfers, List <Input> inputs = null, string remainderAddress = null) { InputValidator.CheckTransferArray(transfers); // Create a new bundle Bundle bundle = new Bundle(); List <string> signatureFragments = new List <string>(); long totalValue = 0; string tag = ""; // // Iterate over all transfers, get totalValue // and prepare the signatureFragments, message and tag // for (int i = 0; i < transfers.Length; i++) { int signatureMessageLength = 1; // If message longer than 2187 trytes, increase signatureMessageLength (add 2nd transaction) if (transfers[i].Message.Length > 2187) { // Get total length, message / maxLength (2187 trytes) signatureMessageLength += (int)Math.Floor(((double)transfers[i].Message.Length / 2187)); var msgCopy = transfers[i].Message; // While there is still a message, copy it while (msgCopy != null) { var fragment = msgCopy.Substring(0, 2187 > msgCopy.Length ? msgCopy.Length : 2187); msgCopy = msgCopy.Substring(2187, msgCopy.Length - 2187); // Pad remainder of fragment for (var j = 0; fragment.Length < 2187; j++) { fragment += '9'; } signatureFragments.Add(fragment); } } else { // Else, get single fragment with 2187 of 9's trytes string fragment = ""; if (transfers[i].Message != null) { fragment = transfers[i].Message.Substring(0, transfers[i].Message.Length < 2187 ? transfers[i].Message.Length : 2187); } for (var j = 0; fragment.Length < 2187; j++) { fragment += '9'; } signatureFragments.Add(fragment); } // get current timestamp in seconds long timestamp = IotaApiUtils.CreateTimeStampNow(); // If no tag defined, get 27 tryte tag. tag = transfers[i].Tag != null ? transfers[i].Tag : "999999999999999999999999999"; // Pad for required 27 tryte length for (var j = 0; tag.Length < 27; j++) { tag += '9'; } // Add first entries to the bundle // Slice the address in case the user provided a checksummed one bundle.AddEntry(signatureMessageLength, transfers[i].Address, transfers[i].Value, tag, timestamp); // Sum up total value totalValue += transfers[i].Value; } // Get inputs if we are sending tokens if (totalValue != 0) { // Case 1: user provided inputs // // Validate the inputs by calling getBalances if (inputs != null && inputs.Count > 0) { // Get list if addresses of the provided inputs List <string> inputAddresses = new List <string>(); foreach (var input in inputs) { inputAddresses.Add(input.Address); } GetBalancesResponse balances = GetBalances(inputAddresses, 100); List <Input> confirmedInputs = new List <Input>(); long totalBalance = 0; for (var i = 0; i < balances.Balances.Count; i++) { var thisBalance = balances.Balances[i]; totalBalance += thisBalance; // If input has balance, add it to confirmedInputs if (thisBalance > 0) { var inputEl = inputs[i]; inputEl.Balance = thisBalance; confirmedInputs.Add(inputEl); } } // Return not enough balance error if (totalValue > totalBalance) { throw new NotEnoughBalanceException(totalValue); } return(AddRemainder(seed, confirmedInputs, bundle, tag, totalValue, remainderAddress, signatureFragments)); } // Case 2: Get inputs deterministically // // If no inputs provided, derive the addresses from the seed and // confirm that the inputs exceed the threshold else { List <Input> inputList = GetInputs(seed, 0, 0, (int)totalValue).InputsList; return(AddRemainder(seed, inputList, bundle, tag, totalValue, remainderAddress, signatureFragments)); } } else { // If no input required, don't sign and simply finalize the bundle bundle.FinalizeBundle(curl); bundle.AddTrytes(signatureFragments); List <String> bundleTrytes = new List <string>(); bundle.Transactions.ForEach(tx => bundleTrytes.Add(tx.ToTransactionTrytes())); bundleTrytes.Reverse(); return(bundleTrytes); } }
private List <string> AddRemainder( string seed, int security, List <Input> inputs, Bundle bundle, string tag, long totalValue, string remainderAddress, List <string> signatureFragments) { var totalTransferValue = totalValue; foreach (var input in inputs) { var thisBalance = input.Balance; var toSubtract = 0 - thisBalance; var timestamp = TimeStamp.Now(); // Add input as bundle entry // use input.Security bundle.AddEntry(input.Security, input.Address, toSubtract, tag, timestamp); // If there is a remainder value // Add extra output to send remaining funds to if (thisBalance >= totalTransferValue) { var remainder = thisBalance - totalTransferValue; // If user has provided remainder address // Use it to send remaining funds to if (remainder > 0 && remainderAddress != null) { // Remainder bundle entry bundle.AddEntry(1, remainderAddress, remainder, tag, timestamp); // function for signing inputs return(IotaApiUtils.SignInputsAndReturn( seed, inputs, bundle, signatureFragments, SpongeFactory.Create(SpongeFactory.Mode.KERL))); } if (remainder > 0) { AddressRequest addressRequest = new AddressRequest(seed, security); var res = GenerateNewAddresses(addressRequest); // Remainder bundle entry bundle.AddEntry(1, res.Addresses[0], remainder, tag, timestamp); // function for signing inputs return(IotaApiUtils.SignInputsAndReturn( seed, inputs, bundle, signatureFragments, SpongeFactory.Create(SpongeFactory.Mode.KERL))); } // If there is no remainder, do not add transaction to bundle // simply sign and return return(IotaApiUtils.SignInputsAndReturn( seed, inputs, bundle, signatureFragments, SpongeFactory.Create(SpongeFactory.Mode.KERL))); } // If multiple inputs provided, subtract the totalTransferValue by // the inputs balance totalTransferValue -= thisBalance; } throw new IllegalStateException(Constants.NOT_ENOUGH_BALANCE_ERROR); }