private GetBalancesAndFormatResponse GetBalanceAndFormat( string[] addresses, string[] tips, long threshold, int start, int security, Stopwatch stopwatch = null) { if (stopwatch == null) { stopwatch = Stopwatch.StartNew(); } if (!InputValidator.IsValidSecurityLevel(security)) { throw new ArgumentException(Constants.INVALID_SECURITY_LEVEL_INPUT_ERROR); } var getBalancesResponse = IotaClient.GetBalances(100, addresses.ToList(), tips.ToList()); var balances = getBalancesResponse.Balances; // If threshold defined, keep track of whether reached or not // else set default to true var inputList = new List <Input>(); long totalBalance = 0; var thresholdReached = threshold == 0; for (var i = 0; i < addresses.Length; i++) { if (balances[i] > 0) { inputList.Add(new Input { Address = addresses[i], Balance = balances[i], KeyIndex = start + i, Security = security }); totalBalance += balances[i]; if (totalBalance >= threshold) { thresholdReached = true; break; } } } stopwatch.Stop(); if (thresholdReached) { return(new GetBalancesAndFormatResponse(inputList, totalBalance, stopwatch.ElapsedMilliseconds)); } throw new IllegalStateException(Constants.NOT_ENOUGH_BALANCE_ERROR); }
/// <summary> /// /// </summary> /// <param name="seed"></param> /// <param name="security"></param> /// <param name="transfers"></param> /// <param name="remainder"></param> /// <param name="inputs"></param> /// <param name="tips"></param> /// <param name="validateInputs"></param> /// <returns></returns> public List <string> PrepareTransfers( string seed, int security, Transfer[] transfers, string remainder, Input[] inputs, Transaction[] tips, bool validateInputs) { // validate seed if (!InputValidator.IsValidSeed(seed)) { throw new IllegalStateException(Constants.INVALID_SEED_INPUT_ERROR); } if (!InputValidator.IsValidSecurityLevel(security)) { throw new ArgumentException(Constants.INVALID_SECURITY_LEVEL_INPUT_ERROR); } if (remainder != null && !InputValidator.IsAddress(remainder)) { throw new ArgumentException(Constants.INVALID_ADDRESSES_INPUT_ERROR); } // Input validation of transfers object if (!InputValidator.IsValidTransfersCollection(transfers.ToList())) { throw new ArgumentException(Constants.INVALID_TRANSFERS_INPUT_ERROR); } if (inputs != null && !InputValidator.IsValidInputsCollection(inputs)) { throw new ArgumentException(Constants.INVALID_ADDRESSES_INPUT_ERROR); } // 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.MESSAGE_LENGTH) { // Get total length, message / maxLength (2187 trytes) signatureMessageLength += (int)Math.Floor((double)transfer.Message.Length / Constants.MESSAGE_LENGTH); 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(fragment.Length, msgCopy.Length - fragment.Length); // Pad remainder of fragment if (fragment.Length < 2187) { fragment = fragment.PadRight(2187, '9'); } signatureFragments.Add(fragment); } } else { // Else, get single fragment with 2187 of 9's trytes var fragment = transfer.Message.PadRight(Constants.MESSAGE_LENGTH, '9'); signatureFragments.Add(fragment); } // get current timestamp in seconds var timestamp = (long)Math.Floor((double)TimeStamp.Now() / 1000); // If no tag defined, get 27 tryte tag. tag = string.IsNullOrEmpty(transfer.Tag) ? "999999999999999999999999999" : transfer.Tag; // Pad for required 27 tryte length if (tag.Length < Constants.TAG_LENGTH) { tag = tag.PadRight(Constants.TAG_LENGTH, '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) { // validate seed if (!InputValidator.IsValidSeed(seed)) { throw new IllegalStateException(Constants.INVALID_SEED_INPUT_ERROR); } // Case 1: user provided inputs // Validate the inputs by calling getBalances if (inputs != null && inputs.Length > 0) { if (!validateInputs) { return(AddRemainder(seed, security, inputs.ToList(), bundle, tag, totalValue, remainder, signatureFragments)); } // Get list if addresses of the provided inputs var inputAddresses = new List <string>(); foreach (var input in inputs) { inputAddresses.Add(input.Address); } List <string> tipHashes = null; if (tips != null) { tipHashes = new List <string>(); foreach (var tx in tips) { tipHashes.Add(tx.CurlHash()); } } var balances = IotaClient.GetBalances(100, inputAddresses, tipHashes); 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); // if we've already reached the intended input value, break out of loop if (totalBalance >= totalValue) { Log.Info("Total balance already reached "); break; } } } // Return not enough balance error if (totalValue > totalBalance) { throw new IllegalStateException(Constants.NOT_ENOUGH_BALANCE_ERROR); } return(AddRemainder(seed, security, confirmedInputs, bundle, tag, totalValue, remainder, signatureFragments)); } // Case 2: Get inputs deterministically // // If no inputs provided, derive the addresses from the seed and // confirm that the inputs exceed the threshold var inputList = GetInputs(seed, security, 0, 0, (int)totalValue).Inputs; return(AddRemainder(seed, security, inputList, bundle, tag, totalValue, remainder, signatureFragments)); } // If no input required, don't sign and simply finalize the bundle bundle.FinalizeBundle(SpongeFactory.Create(SpongeFactory.Mode.KERL)); bundle.AddTrytes(signatureFragments); var bundleTrytes = new List <string>(); bundle.Transactions.ForEach(tx => bundleTrytes.Add(tx.ToTrytes())); bundleTrytes.Reverse(); return(bundleTrytes); }
/// <summary> /// /// </summary> /// <param name="securitySum"></param> /// <param name="inputAddress"></param> /// <param name="remainderAddress"></param> /// <param name="transfers"></param> /// <param name="tips"></param> /// <param name="testMode"></param> /// <returns></returns> public List <Transaction> InitiateTransfer( int securitySum, string inputAddress, string remainderAddress, List <Transfer> transfers, List <Transaction> tips, bool testMode) { // validate input address if (!InputValidator.IsAddress(inputAddress)) { throw new ArgumentException("Invalid addresses provided."); } // validate remainder address if (remainderAddress != null && !InputValidator.IsAddress(remainderAddress)) { throw new ArgumentException("Invalid addresses provided."); } // Input validation of transfers object if (!InputValidator.IsValidTransfersCollection(transfers)) { throw new ArgumentException("Invalid transfers provided."); } // Create a new bundle Bundle bundle = new Bundle(); long totalValue = 0; List <String> signatureFragments = new List <string>(); String tag = ""; // // Iterate over all transfers, get totalValue // and prepare the signatureFragments, message and tag foreach (Transfer transfer in transfers) { // remove the checksum of the address if provided if (transfer.Address.IsValidChecksum()) { transfer.Address = transfer.Address.RemoveChecksum(); } int signatureMessageLength = 1; // If message longer than 2187 trytes, increase signatureMessageLength (add next transaction) if (transfer.Message.Length > Constants.MESSAGE_LENGTH) { // Get total length, message / maxLength (2187 trytes) signatureMessageLength += (int)Math.Floor((double)transfer.Message.Length / Constants.MESSAGE_LENGTH); String msgCopy = transfer.Message; // While there is still a message, copy it while (!string.IsNullOrEmpty(msgCopy)) { string fragment = msgCopy.Substring(0, Constants.MESSAGE_LENGTH); msgCopy = msgCopy.Substring(Constants.MESSAGE_LENGTH, msgCopy.Length - Constants.MESSAGE_LENGTH); // Pad remainder of fragment fragment = fragment.PadRight(Constants.MESSAGE_LENGTH, '9'); signatureFragments.Add(fragment); } } else { // Else, get single fragment with 2187 of 9's trytes String fragment = transfer.Message; if (transfer.Message.Length < Constants.MESSAGE_LENGTH) { fragment = fragment.PadRight(Constants.MESSAGE_LENGTH, '9'); } signatureFragments.Add(fragment); } tag = transfer.Tag; // pad for required 27 tryte length if (transfer.Tag.Length < Constants.TAG_LENGTH) { tag = tag.PadRight(Constants.TAG_LENGTH, '9'); } // get current timestamp in seconds long timestamp = (long)Math.Floor(GetCurrentTimestampInSeconds()); // Add first entry to the bundle 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) { List <string> tipHashes = null; if (tips != null) { tipHashes = new List <string>(); foreach (var tx in tips) { tipHashes.Add(tx.CurlHash()); } } GetBalancesResponse balancesResponse = IotaClient.GetBalances(100, new List <string> { inputAddress }, tipHashes); var balances = balancesResponse.Balances; long totalBalance = 0; foreach (var balance in balances) { totalBalance += balance; } // get current timestamp in seconds long timestamp = (long)Math.Floor(GetCurrentTimestampInSeconds()); // bypass the balance checks during unit testing //TODO remove this ugliness if (testMode) { totalBalance += 1000; } if (totalBalance > 0) { long toSubtract = 0 - totalBalance; // Add input as bundle entry // Only a single entry, signatures will be added later bundle.AddEntry(securitySum, inputAddress, toSubtract, tag, timestamp); } // Return not enough balance error if (totalValue > totalBalance) { throw new IllegalStateException("Not enough balance."); } // If there is a remainder value // Add extra output to send remaining funds to if (totalBalance > totalValue) { long remainder = totalBalance - totalValue; // Remainder bundle entry if necessary if (remainderAddress == null) { throw new IllegalStateException("No remainder address defined."); } bundle.AddEntry(1, remainderAddress, remainder, tag, timestamp); } bundle.FinalizeBundle(SpongeFactory.Create(SpongeFactory.Mode.CURLP81)); bundle.AddTrytes(signatureFragments); return(bundle.Transactions); } else { throw new System.Exception("Invalid value transfer: the transfer does not require a signature."); } }
/// <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)); } }