int IComparable.CompareTo(object obj) //sortable by value { UTXO x = (UTXO)obj; if (this.Value == x.Value) { return(0); } return(this.Value > x.Value ? 1 : -1); }
//parse json UTXO response public static UTXO[] ParseUTXO(string msg) { List <UTXO> lst = new List <UTXO>(); /* * {"jsonrpc": "2.0", "id": "1", "result": * [{"tx_hash": "60c21462d1ddbac55ed34f205b9776303953c2b74de1f4283630e69db78d65ec", "tx_pos": 1, "height": 1260557, "value": 129967000}, * {"tx_hash": "04fa60e351ffedff984e7cad9697a532dd6dc97966ed64b26919cc1741639861", "tx_pos": 0, "height": 1261826, "value": 16230000}, * {"tx_hash": "70c3f2ff2721846bac77bf8aad863efdbc20ed0cc2f59dda6d7dfd02682c2805", "tx_pos": 0, "height": 1261827, "value": 32480000}] * } */ msg = msg.Replace("\"", "").Replace(" ", ""); /* * {jsonrpc:2.0,id:1,result: * [{tx_hash:60c21462d1ddbac55ed34f205b9776303953c2b74de1f4283630e69db78d65ec,tx_pos:1,height:1260557,value:129967000}, * {tx_hash:04fa60e351ffedff984e7cad9697a532dd6dc97966ed64b26919cc1741639861,tx_pos:0,height:1261826,value:16230000}, * {tx_hash:70c3f2ff2721846bac77bf8aad863efdbc20ed0cc2f59dda6d7dfd02682c2805,tx_pos:0,height:1261827,value:32480000}] * } */ int pos = 0; while (true) { pos = msg.IndexOf("tx_hash", pos); if (pos < 0) { break; } int pos2 = msg.IndexOf("}", pos) - 1; string oneutxo = msg.Substring(pos, pos2 - pos + 1); //tx_hash:60c21462d1ddbac55ed34f205b9776303953c2b74de1f4283630e69db78d65ec,tx_pos:1,height:1260557,value:129967000 string[] vals = oneutxo.Split(','); UTXO x = new UTXO(); x.Tx_hash = vals[0].Split(':')[1]; //tx_hash:60c21462d1ddbac55ed34f205b9776303953c2b74de1f4283630e69db78d65ec x.Tx_pos = int.Parse(vals[1].Split(':')[1]); //tx_pos:1 x.Height = int.Parse(vals[2].Split(':')[1]); //height:1260557 x.Value = int.Parse(vals[3].Split(':')[1]); //value:129967000 (satoshi) lst.Add(x); pos = pos2; } UTXO[] arr = lst.ToArray(); Array.Sort(arr); //smallest value first return(arr); }
//take in tx parameters, return tx object public static TxSerial CreateTx(string extPubKey, string pubToAddr, string chgAddr, int walletId, long satToSend, long fee, bool testnet) { CheckNullOrEmpty(new object[] { extPubKey, pubToAddr, ElectrumXhost }, new string[] { "extPubKey", "pubToAddr", "ElectrumXhost" }); string err = ""; if (satToSend == 0) { err += "satoshiToSend = 0, "; } if (fee == 0) { err += "satoshiFee = 0, "; } if (err != "") { throw new Exception("[CreateTx] " + err); } //get first 100+100 child address from ext pub key List <Tuple <string, string> > recAddrList = GetDerivedKeys(extPubKey, 0, 20, false, testnet); //receive addresses List <Tuple <string, string> > chgAddrList = GetDerivedKeys(extPubKey, 0, 20, true, testnet); //change addresses //TODO - create process for getting next change address, so address never used twice if (chgAddr == null || chgAddr == "") //get first chg addr for extPubKey { chgAddr = chgAddrList.First().Item1; } //server status check string info = ElectrumX.GetServerInfo(ElectrumXhost, ElectrumXport); if (info == null) { throw new Exception("[CreateTx] ElectrumX Server Check Failed"); } string[] recAddrListAddr = new string[recAddrList.Count]; //short address string[] recAddrListExt = new string[recAddrList.Count]; //long address int ctr = 0; foreach (var t in recAddrList) { recAddrListAddr[ctr++] = t.Item1; //short } ctr = 0; foreach (var t in recAddrList) { recAddrListExt[ctr++] = t.Item2; //long - hash } string[] chgAddrListAddr = new string[recAddrList.Count]; string[] chgAddrListExt = new string[recAddrList.Count]; ctr = 0; foreach (var t in chgAddrList) { chgAddrListAddr[ctr++] = t.Item1; } ctr = 0; foreach (var t in chgAddrList) { chgAddrListExt[ctr++] = t.Item2; } //get all UTXOs (unspent inputs) from receive addresses UTXO[] recUTXOs = ElectrumX.GetUTXOs(recAddrList, ElectrumXhost, ElectrumXport); UTXO.SetAddressType(recUTXOs, 0); //receiver //get all UTXOs (unspent inputs) from change addresses UTXO[] chgUTXOs = ElectrumX.GetUTXOs(chgAddrList, ElectrumXhost, ElectrumXport); UTXO.SetAddressType(chgUTXOs, 1); //change //start new tx TransactionBuilder bldr = new TransactionBuilder(); bldr.Send(new BitcoinPubKeyAddress(pubToAddr), Money.Satoshis(satToSend)); //amount to send to recipient bldr.SetChange(new BitcoinPubKeyAddress(chgAddr)); //send change to this address bldr.SendFees(Money.Satoshis(fee)); //miner (tx) fee //collect all UTXOs List <UTXO> allUTXOs = new List <UTXO>(); allUTXOs.AddRange(recUTXOs); allUTXOs.AddRange(chgUTXOs); List <ICoin> lstTxCoins = new List <ICoin>(); //Coin is a UTXO //add new coin for each UTXO foreach (UTXO x in allUTXOs) //tx builder will select coins from this list { BitcoinPubKeyAddress fromAddr = new BitcoinPubKeyAddress(x.Address); NBitcoin.Coin cn = null; //create new coin from UTXO bldr.AddCoins(cn = new NBitcoin.Coin( new OutPoint(new uint256(x.Tx_hash), x.Tx_pos), //tx that funded wallet, spend this coin new TxOut(Money.Satoshis(x.Value), fromAddr.ScriptPubKey))); //specify full coin amount, else SetChange ignored lstTxCoins.Add(cn); //add coin to transaction, may not be used x.tmp = cn; //link UTXO with coin } List <UTXO> usedUTXOs = new List <UTXO>(); //coins actually used in tx NBitcoin.Transaction tx = bldr.BuildTransaction(false); //sort\filter coins, some coins will not be needed\used //coin objects not stored in tx, so we need to determine which coins were used //scan tx inputs for matching coins, ignore other coins foreach (UTXO u in allUTXOs) { foreach (TxIn i in tx.Inputs) { if (i.PrevOut == ((NBitcoin.Coin)u.tmp).Outpoint) //this coin in tx { usedUTXOs.Add(u); //this UTXO will be used\spent in tx } } } //populate return object TxSerial txs = new TxSerial() { SendAmt = satToSend, Fee = fee, ExtPublicKey = extPubKey, ToAddress = pubToAddr, ChgAddress = chgAddr, WalletId = walletId }; txs.ExtPublicKey = extPubKey; foreach (UTXO u in usedUTXOs) { u.tmp = null; //don't serialize coin object, will rebuild coins in signing process } txs.InputUTXOs = new List <UTXO>(); txs.InputUTXOs.AddRange(usedUTXOs); //string jsn = Newtonsoft.Json.JsonConvert.SerializeObject(txs, Newtonsoft.Json.Formatting.Indented); return(txs); }