public void Can_getNewAddress() { string apiKey = "bb7c-fc4a-42a2-cf6e"; BlockIO blockio = new BlockIO(apiKey); JSONAPI json = blockio.GetNewAddress(); Assert.AreEqual("success", json.Status); }
public void Can_getTransactionsReceived() { string apiKey = "bb7c-fc4a-42a2-cf6e"; BlockIO blockio = new BlockIO(apiKey); JSONAPI json = blockio.GetTransactions("received"); Assert.AreEqual("success", json.Status); }
// ask block.io for the Addresses (really we care about the Labels) for a given apikey, and update the UI if successful (this is also how we detect the cointype) private bool tryGetAddresses(string apikey) { try { BlockIO client = new BlockIO(apikey); APIResponse r = client.getMyAddresses(); string net = (string)r.Data["network"]; currentNet = net; // reload the Fractions dropdown to follow the potential change in coin network reloadFractions(); // update the text on the "add tx fee" checkbox, again to follow potential network change updateFeeAddCheckbox(); lblCoinType.Text = string.Format("Using coin type: {0}", net); // gather list of addresses-labels-values and put in to a nice dropdown list (replacing current silly "label" textbox) ArrayList addresses = (ArrayList)r.Data["addresses"]; // devnote: store the data properly structured internally (make a class, store as array-of-class), then build display strings for the DDL // and in a SelectedIndexChanged handler for that new DDL, update a new Label to show available balance labels = new List <LabelAddress>(); foreach (Dictionary <string, object> a in addresses) { LabelAddress la = new LabelAddress(); la.net = net; la.label = (string)a["label"]; la.balance = decimal.Parse((string)a["available_balance"]); labels.Add(la); } ddlTakeFromLabel.DataSource = labels; ddlTakeFromLabel.SelectedIndex = 0; //reloadLabels(); } catch (Exception ex) { // that didn't work... string errmsg = string.Format("Error when asking Block.Io for Address/Label List.\nInternal Error message is:\n{0}", ex.Message); MessageBox.Show(errmsg, "Error getting Label List", MessageBoxButtons.OK, MessageBoxIcon.Error); return(false); } return(true); }
private void btnGo_Click(object sender, EventArgs e) { BlockIO client = new BlockIO(txtApiKey.Text.Trim()); decimal TargetBalance; bool includePending = chkIncludePendingBalance.Checked; // basic input validations if (!decimal.TryParse(txtTargetBalance.Text, out TargetBalance)) { MessageBox.Show("Target Balance must be a valid number", "Error: Target Balance", MessageBoxButtons.OK, MessageBoxIcon.Error); return; } if (TargetBalance <= 0) { MessageBox.Show("Target Balance must be greater than zero", "Error: Target Balance", MessageBoxButtons.OK, MessageBoxIcon.Error); return; } List <string> addresses = txtInputAddresses.Lines.Where(L => L.Trim() != "").ToList(); lblSummaryInfo.Text = "... Working ..."; block_io_sharp.APIResponse response; // ask block.io for the balances of all these addresses, in a slightly non-crashy manner try { response = client.getAddressBalance("addresses", addresses); } catch (Exception ex) { lblSummaryInfo.Text = "Request failed."; return; } string network = (string)response.Data["network"]; decimal totalBalanceAvail = decimal.Parse((string)response.Data["available_balance"]); decimal totalPending = decimal.Parse((string)response.Data["pending_received_balance"]); List <string> addressesExact = new List <string>(); // addresses with exactly the desired balance List <string> addressesZero = new List <string>(); // addresses with an exactly-zero balance List <string> addressesLow = new List <string>(); // addresses with a non-zero but under-spec balance List <string> addressesOver = new List <string>(); // addresses with a balance over the target amount // loop round each balance response received and stick it into the appropriate list foreach (Dictionary <string, object> b in (ArrayList)response.Data["balances"]) { Decimal balance = decimal.Parse((string)b["available_balance"]); Decimal pending = decimal.Parse((string)b["pending_received_balance"]);; if (includePending) { balance += pending; } string address = b["address"].ToString(); if (balance == TargetBalance) { addressesExact.Add(address); } else if (balance == 0) { addressesZero.Add(address); } else if (balance < TargetBalance) { addressesLow.Add(address + " (" + balance.ToString() + ")"); } else if (balance > TargetBalance) { addressesOver.Add(address + " (" + balance.ToString() + ")"); } } // output the single-line summary lblSummaryInfo.Text = string.Format( "Coin: {0}, Total Value Available {1}, Total Value Pending {2}. Of {3} Addresses, found {4} with Zero, {5} with Under, {6} with Exact and {7} with Over", network, totalBalanceAvail, totalPending, addresses.Count, addressesZero.Count, addressesLow.Count, addressesExact.Count, addressesOver.Count); // and output the full lists txtInputAddresses.Lines = addresses.ToArray(); txtResultsZero.Lines = addressesZero.ToArray(); txtResultsLow.Lines = addressesLow.ToArray(); txtResultsOK.Lines = addressesExact.ToArray(); txtResultsOver.Lines = addressesOver.ToArray(); }
// actually do the work private void btnSend_Click(object sender, EventArgs e) { // validate API Key string apikey = txtApiKey.Text.Trim(); if (!isValidApiKey(apikey)) { lblApiKeyInvalidFormat.Visible = true; MessageBox.Show("API Key is required and must be in expected format (like '0123-4567-890a-bcde')", "Invalid API Key format", MessageBoxButtons.OK, MessageBoxIcon.Error); return; } // load in address-list, check we don't exceed 100 List <string> addresses = txtAddresses.Lines.Where(L => L.Trim() != "").ToList(); if (addresses.Count > 100) { MessageBox.Show(String.Format("Can't send to {0} addresses in one shot, Block.io's maximum is 100", addresses.Count), "Too many Addresses", MessageBoxButtons.OK, MessageBoxIcon.Error); return; } decimal perAddressValue; // check the send amount is sensible (a valid number, and greater than zero) if (decimal.TryParse(txtSendAmount.Text, out perAddressValue)) { if (perAddressValue <= 0) { MessageBox.Show("Send Amount must be greater than zero!", "Invalid Send Amount", MessageBoxButtons.OK, MessageBoxIcon.Error); return; } } else { MessageBox.Show("Send Amount field is not a valid number", "Invalid Send Amount", MessageBoxButtons.OK, MessageBoxIcon.Error); return; } // a block.io "Label" is required, to be the source of the coins string sourceLabel = ((LabelAddress)ddlTakeFromLabel.SelectedValue).label; //txtFromLabels.Text.Trim(); if (sourceLabel.Length < 1) { MessageBox.Show("Take From Label must be provided", "Missing 'Take From Label'", MessageBoxButtons.OK, MessageBoxIcon.Error); return; } // the block.io Secret Pin is necessary too string secretPin = txtSecretPin.Text.Trim(); if (secretPin == "") { MessageBox.Show("Secret PIN must be provided", "Missing 'Secret PIN'", MessageBoxButtons.OK, MessageBoxIcon.Error); return; } perAddressValue = currentFraction.toWholeCoin(perAddressValue); // calculate the absolute minimal cost of this operation (Minimal here means 'just the value of the coins, not including any Fees') decimal projectedCostMinimal = perAddressValue * addresses.Count; // create an instance of the BlockIO API Client, using their library code BlockIO client = new BlockIO(txtApiKey.Text.Trim()); decimal AvailableBalance; string net; // look up the available Balance of the specified Label, which will also tell us which Coin (BTC/LTC/DOGE) is being used APIResponse response; try { response = client.getAddressByLabel(sourceLabel); } catch (Exception ex) { // that didn't work... string errmsg = string.Format("Error when asking Block.Io for Label Balance.\nInternal Error message is:\n{0}", ex.Message); MessageBox.Show(errmsg, "Error when getting Label Balance", MessageBoxButtons.OK, MessageBoxIcon.Error); // bail out of this Function entirely (give up the process) return; } AvailableBalance = decimal.Parse((string)response.Data["available_balance"]); net = (string)response.Data["network"]; string networkName; if (net != null) { networkName = net; } else { networkName = "[unknown]"; } // now we know the network in use, we can optionally bump the per-wallet amount and recalculate the projected cost (if user has chosen the "Include Network Fee" option) if (chkIncludeWithdrawFee.Checked) { decimal feeAdd = 0; // select fee-included amount based on the coin // these are currently hard-coded (erring on the side of being a little over-generous to try to ensure the resultant wallets can be redeemed reasonably quickly and net the recipient the intended amount after fee-adding) // a future version may see these turned in to user-facing settings. for now they are hard-coded // user can choose to not use this option and adjust the main send amount value instead, if they wish to get finer control feeAdd = pickTxFeeBonus(net); perAddressValue += feeAdd; projectedCostMinimal = perAddressValue * addresses.Count; } // compare Label Balance against projected cost if (AvailableBalance < projectedCostMinimal) { MessageBox.Show(string.Format("Not enough funds in selected Account Label - you have {0}, but I think you need at least {1}", currentFraction.formatFraction(AvailableBalance), currentFraction.formatFraction(projectedCostMinimal)), "Insufficient Funds", MessageBoxButtons.OK, MessageBoxIcon.Error); return; } // build the list of send-values // right now, we only send the same amount to each address List <string> sendValues = Enumerable.Repeat(perAddressValue.ToString(), addresses.Count).ToList(); // estimate the network fee on the intended transaction response = client.getNetworkFeeEstimate(sendValues, addresses); decimal estFee = decimal.Parse((string)response.Data["estimated_network_fee"]); // ensure that the per-wallet amount is a sensible amount - I don't want this app to be used to create Dust Transactions // The amount sent to each recipient address must be at least a certain multiple of the Total Fee estimated to send the entire initial transaction. // The ratio below is totally artificial and I just pulled it out of thin air. If you REALLY want to send smaller values than I'm allowing, you can just change the value in the next line int feeToValueMinRatio = 5; if (perAddressValue < (estFee * feeToValueMinRatio)) { MessageBox.Show("This app declines to send your transaction, because your Per-Wallet value is too small compared to the Network Fee about to be incurred. Please increase your Amount To Send, or decrease your number of target wallets. (Note: this is NOT a Block.Io limitation)", "Wallet Value too small", MessageBoxButtons.OK, MessageBoxIcon.Error); return; } // final balance check, now I know what the Estimated Send Fee is... decimal finalEstimate = projectedCostMinimal + estFee; if (AvailableBalance < finalEstimate) { MessageBox.Show(string.Format("Not enough funds in nominated Block.Io Label - you have {0}, but after calculating TX Fee you need at least {1}", currentFraction.formatFraction(AvailableBalance), currentFraction.formatFraction(finalEstimate)), "Insufficient Funds", MessageBoxButtons.OK, MessageBoxIcon.Error); return; } // show a final YES/NO confirmation dialog now we've done the pre-flight tests if (MessageBox.Show(String.Format("Sure you want to send {0} to each of {1} addresses, with a TX Fee of {2}, total spend {3}?", currentFraction.formatFraction(perAddressValue), addresses.Count, currentFraction.formatFraction(estFee), currentFraction.formatFraction(finalEstimate)), "Final Confirmation", MessageBoxButtons.YesNo, MessageBoxIcon.Question) == System.Windows.Forms.DialogResult.No) { // they chose not to proceed return; } List <string> labelsList = new List <string>(); labelsList.Add(sourceLabel); // Ask Block.io to perform the intended transaction. Note: This is NOT using client-side tx signing. try { response = client.withdrawFromLabels(labelsList, sendValues, secretPin, addresses); MessageBox.Show(string.Format("Amount Sent:{0}, Amount Withdrawn:{1}, Block.IO Fee:{2}, Network Fee:{3}, TX ID:{4}", response.Data["amount_sent"], response.Data["amount_withdrawn"], response.Data["blockio_fee"], response.Data["network_fee"], response.Data["txid"]), "Transaction Completed OK", MessageBoxButtons.OK, MessageBoxIcon.Information); } catch (Exception ex) { // that didn't work... string errmsg = string.Format("Error when asking Block.Io to Send the Transaction - please check your block.io account before retrying, just in case the transaction WAS actually sent.\nInternal Error Message is:\n{0}", ex.Message); MessageBox.Show(errmsg, "Error when Sending Transaction", MessageBoxButtons.OK, MessageBoxIcon.Error); // bail out of this Function entirely (give up the process) return; } }