Ejemplo n.º 1
0
        static void Main(string[] args)
        {
            try
            {
                string root         = Directory.GetCurrentDirectory();
                string pluginFolder = TxBuilder.GetPluginsFolder(root);
                if (pluginFolder == null)
                {
                    Console.WriteLine("plugins subfolder not found");
                    return;
                }

                string progressId        = "";
                bool   ignoreOldProgress = false;
                if (args.Length > 0 && args[0].StartsWith(progressParamPrefix))
                {
                    progressId = args[0].Substring(progressParamPrefix.Length);
                    if (progressId[0] == '*')
                    {
                        ignoreOldProgress = true;
                        progressId        = progressId.Substring(1);
                    }
                    args = args.Skip(1).ToArray();
                }

                string progress = Path.Combine(pluginFolder, $"progress{progressId}.txt");
                if (ignoreOldProgress)
                {
                    File.Delete(progress);
                }

                if (File.Exists(progress))
                {
                    Console.WriteLine("Found unfinished action, retrying...");
                    args = File.ReadAllText(progress).Split();
                }
                else if (args.Length > 0)
                {
                    File.WriteAllText(progress, string.Join(' ', args));
                }

                if (args.Length < 1)
                {
                    Console.WriteLine("Usage: ccclient [-progress:[*]progressId] [-config:configFileName] [-txid] command [parameters]");
                    Console.WriteLine("commands:");
                    Console.WriteLine("sighash");
                    Console.WriteLine("tip [numBlocksBelow]");
                    Console.WriteLine("list Settings");
                    Console.WriteLine("list Wallets");
                    Console.WriteLine("list Addresses");
                    Console.WriteLine("list Transfers");
                    Console.WriteLine("list AskOrders");
                    Console.WriteLine("list BidOrders");
                    Console.WriteLine("list Offers");
                    Console.WriteLine("list DealOrders");
                    Console.WriteLine("list RepaymentOrders");
                    Console.WriteLine("show Balance sighash|0");
                    Console.WriteLine("show Address sighash|0 blockchain address network");
                    Console.WriteLine("show MatchingOrders sighash|0");
                    Console.WriteLine("show CurrentOffers sighash|0");
                    Console.WriteLine("show CreditHistory sighash|0");
                    Console.WriteLine("show NewDeals sighash|0");
                    Console.WriteLine("show Transfer sighash|0 orderId");
                    Console.WriteLine("show CurrentLoans sighash|0");
                    Console.WriteLine("show NewRepaymentOrders sighash|0");
                    Console.WriteLine("show CurrentRepaymentOrders sighash|0");
                    Console.WriteLine("creditcoin SendFunds amount sighash ");
                    Console.WriteLine("creditcoin RegisterAddress blockchain address network");
                    Console.WriteLine("creditcoin RegisterTransfer gain orderId txId");
                    Console.WriteLine("creditcoin AddAskOrder addressId amount interest maturity fee expiration");
                    Console.WriteLine("creditcoin AddBidOrder addressId amount interest maturity fee expiration");
                    Console.WriteLine("creditcoin AddOffer askOrderId bidOrderId expiration");
                    Console.WriteLine("creditcoin AddDealOrder offerId expiration");
                    Console.WriteLine("creditcoin CompleteDealOrder dealOrderId transferId");
                    Console.WriteLine("creditcoin LockDealOrder dealOrderId");
                    Console.WriteLine("creditcoin CloseDealOrder dealOrderId transferId");
                    Console.WriteLine("creditcoin Exempt dealOrderId transferId");
                    Console.WriteLine("creditcoin AddRepaymentOrder dealOrderId addressId amount expiration");
                    Console.WriteLine("creditcoin CompleteRepaymentOrder repaymentOrderId");
                    Console.WriteLine("creditcoin CloseRepaymentOrder repaymentOrderId transferId");
                    Console.WriteLine("creditcoin CollectCoins addressId amount txId");
                    Console.WriteLine("bitcoin RegisterTransfer gain orderId sourceTxId");
                    Console.WriteLine("ethereum RegisterTransfer gain orderId");
                    Console.WriteLine("ethereum CollectCoins amount");
                    return;
                }

                string configFile = null;
                if (args.Length > 0 && args[0].StartsWith(configParamPrefix))
                {
                    configFile = args[0].Substring(configParamPrefix.Length);
                    args       = args.Skip(1).ToArray();
                    if (!File.Exists(configFile))
                    {
                        configFile = Path.Combine(pluginFolder, configFile);
                        if (!File.Exists(configFile))
                        {
                            Console.WriteLine("Cannot find the specified config file");
                            return;
                        }
                    }
                }
                bool txid = false;
                if (args.Length > 0 && args[0].Equals(txidParam))
                {
                    args = args.Skip(1).ToArray();
                    txid = true;
                }

                string   action;
                string[] command;

                var builder = new ConfigurationBuilder()
                              .AddJsonFile("appsettings.json", true, false)
#if DEBUG
                              .AddJsonFile("appsettings.dev.json", true, false)
#endif
                ;

                if (configFile != null)
                {
                    builder.AddJsonFile(configFile, true, false);
                }

                IConfiguration config = builder.Build();

                string creditcoinRestApiURL = config["creditcoinRestApiURL"];
                if (!string.IsNullOrWhiteSpace(creditcoinRestApiURL))
                {
                    creditcoinUrl = creditcoinRestApiURL;
                }

                Signer signer = getSigner(config);

                if (args.Length < 1)
                {
                    Console.WriteLine("Command is not provided");
                    return;
                }

                action  = args[0].ToLower();
                command = args.Skip(1).ToArray();

                bool inProgress = false;

                if (action.Equals("sighash"))
                {
                    Console.WriteLine(TxBuilder.getSighash(signer));
                }
                else if (action.Equals("tip"))
                {
                    BigInteger headIdx = GetHeadIdx();
                    if (command.Length == 1)
                    {
                        BigInteger num;
                        if (!BigInteger.TryParse(command[0], out num))
                        {
                            throw new Exception("Invalid numerics");
                        }
                        headIdx -= num;
                    }
                    Console.WriteLine(headIdx);
                }
                else if (action.Equals("list"))
                {
                    if (command.Length > 2)
                    {
                        throw new Exception("1 or 2 parametersd expected");
                    }
                    string id = null;
                    if (command.Length == 2)
                    {
                        id = command[1];
                    }
                    if (command[0].Equals("settings", StringComparison.OrdinalIgnoreCase))
                    {
                        filter(RpcHelper.settingNamespace, (string objid, byte[] protobuf) =>
                        {
                            if (id == null || id != null && id.Equals(objid))
                            {
                                Setting setting = Setting.Parser.ParseFrom(protobuf);
                                foreach (var entry in setting.Entries)
                                {
                                    Console.WriteLine($"{entry.Key}: {entry.Value}");
                                }
                            }
                        });
                    }
                    else if (command[0].Equals("wallets", StringComparison.OrdinalIgnoreCase))
                    {
                        filter(RpcHelper.creditCoinNamespace + RpcHelper.walletPrefix, (string objid, byte[] protobuf) =>
                        {
                            if (id == null || id != null && id.Equals(objid))
                            {
                                Wallet wallet = Wallet.Parser.ParseFrom(protobuf);
                                Console.WriteLine($"wallet({objid}) amount:{wallet.Amount}");
                            }
                        });
                    }
                    else if (command[0].Equals("addresses", StringComparison.OrdinalIgnoreCase))
                    {
                        filter(RpcHelper.creditCoinNamespace + RpcHelper.addressPrefix, (string objid, byte[] protobuf) =>
                        {
                            if (id == null || id != null && id.Equals(objid))
                            {
                                Address address = Address.Parser.ParseFrom(protobuf);
                                Console.WriteLine($"address({objid}) blockchain:{address.Blockchain} value:{address.Value} network:{address.Network} sighash:{address.Sighash}");
                            }
                        });
                    }
                    else if (command[0].Equals("transfers", StringComparison.OrdinalIgnoreCase))
                    {
                        filter(RpcHelper.creditCoinNamespace + RpcHelper.transferPrefix, (string objid, byte[] protobuf) =>
                        {
                            if (id == null || id != null && id.Equals(objid))
                            {
                                Transfer transfer = Transfer.Parser.ParseFrom(protobuf);
                                Console.WriteLine($"transfer({objid}) blockchain:{transfer.Blockchain} srcAddress:{transfer.SrcAddress} dstAddress:{transfer.DstAddress} order:{transfer.Order} amount:{transfer.Amount} tx:{transfer.Tx} block:{transfer.Block} processed:{transfer.Processed} sighash:{transfer.Sighash}");
                            }
                        });
                    }
                    else if (command[0].Equals("askOrders", StringComparison.OrdinalIgnoreCase))
                    {
                        filter(RpcHelper.creditCoinNamespace + RpcHelper.askOrderPrefix, (string objid, byte[] protobuf) =>
                        {
                            if (id == null || id != null && id.Equals(objid))
                            {
                                AskOrder askOrder = AskOrder.Parser.ParseFrom(protobuf);
                                Console.WriteLine($"askOrder({objid}) blockchain:{askOrder.Blockchain} address:{askOrder.Address} amount:{askOrder.Amount} interest:{askOrder.Interest} maturity:{askOrder.Maturity} fee:{askOrder.Fee} expiration:{askOrder.Expiration} block:{askOrder.Block} sighash:{askOrder.Sighash}");
                            }
                        });
                    }
                    else if (command[0].Equals("bidOrders", StringComparison.OrdinalIgnoreCase))
                    {
                        filter(RpcHelper.creditCoinNamespace + RpcHelper.bidOrderPrefix, (string objid, byte[] protobuf) =>
                        {
                            if (id == null || id != null && id.Equals(objid))
                            {
                                BidOrder bidOrder = BidOrder.Parser.ParseFrom(protobuf);
                                Console.WriteLine($"bidOrder({objid}) blockchain:{bidOrder.Blockchain} address:{bidOrder.Address} amount:{bidOrder.Amount} interest:{bidOrder.Interest} maturity:{bidOrder.Maturity} fee:{bidOrder.Fee} expiration:{bidOrder.Expiration} block:{bidOrder.Block} sighash:{bidOrder.Sighash}");
                            }
                        });
                    }
                    else if (command[0].Equals("offers", StringComparison.OrdinalIgnoreCase))
                    {
                        filter(RpcHelper.creditCoinNamespace + RpcHelper.offerPrefix, (string objid, byte[] protobuf) =>
                        {
                            if (id == null || id != null && id.Equals(objid))
                            {
                                Offer offer = Offer.Parser.ParseFrom(protobuf);
                                Console.WriteLine($"offer({objid}) blockchain:{offer.Blockchain} askOrder:{offer.AskOrder} bidOrder:{offer.BidOrder} expiration:{offer.Expiration} block:{offer.Block}");
                            }
                        });
                    }
                    else if (command[0].Equals("dealOrders", StringComparison.OrdinalIgnoreCase))
                    {
                        filter(RpcHelper.creditCoinNamespace + RpcHelper.dealOrderPrefix, (string objid, byte[] protobuf) =>
                        {
                            if (id == null || id != null && id.Equals(objid))
                            {
                                DealOrder dealOrder = DealOrder.Parser.ParseFrom(protobuf);
                                Console.WriteLine($"dealOrder({objid}) blockchain:{dealOrder.Blockchain} srcAddress:{dealOrder.SrcAddress} dstAddress:{dealOrder.DstAddress} amount:{dealOrder.Amount} interest:{dealOrder.Interest} maturity:{dealOrder.Maturity} fee:{dealOrder.Fee} expiration:{dealOrder.Expiration} block:{dealOrder.Block} loanTransfer:{(dealOrder.LoanTransfer.Equals(string.Empty) ? "*" : dealOrder.LoanTransfer)} repaymentTransfer:{(dealOrder.RepaymentTransfer.Equals(string.Empty) ? "*" : dealOrder.RepaymentTransfer)} lock:{(dealOrder.Lock.Equals(string.Empty) ? "*" : dealOrder.Lock)} sighash:{dealOrder.Sighash}");
                            }
                        });
                    }
                    else if (command[0].Equals("repaymentOrders", StringComparison.OrdinalIgnoreCase))
                    {
                        filter(RpcHelper.creditCoinNamespace + RpcHelper.repaymentOrderPrefix, (string objid, byte[] protobuf) =>
                        {
                            if (id == null || id != null && id.Equals(objid))
                            {
                                RepaymentOrder repaymentOrder = RepaymentOrder.Parser.ParseFrom(protobuf);
                                Console.WriteLine($"repaymentOrder({objid}) blockchain:{repaymentOrder.Blockchain} srcAddress:{repaymentOrder.SrcAddress} dstAddress:{repaymentOrder.DstAddress} amount:{repaymentOrder.Amount} expiration:{repaymentOrder.Expiration} block:{repaymentOrder.Block} deal:{repaymentOrder.Deal} previousOwner:{(repaymentOrder.PreviousOwner.Equals(string.Empty)? "*": repaymentOrder.PreviousOwner)} transfer:{(repaymentOrder.Transfer.Equals(string.Empty) ? "*" : repaymentOrder.Transfer)} sighash:{repaymentOrder.Sighash}");
                            }
                        });
                    }
                }
                else if (action.Equals("show"))
                {
                    bool success = true;

                    BigInteger headIdx = GetHeadIdx();

                    if (command.Length <= 1)
                    {
                        throw new Exception("1 or more parametersd expected");
                    }
                    string sighash;
                    if (command[1].Equals("0"))
                    {
                        sighash = TxBuilder.getSighash(signer);
                    }
                    else
                    {
                        sighash = command[1];
                    }

                    if (command[0].Equals("balance", StringComparison.OrdinalIgnoreCase))
                    {
                        if (command.Length != 2)
                        {
                            throw new Exception("2 parametersd expected");
                        }
                        string prefix = RpcHelper.creditCoinNamespace + RpcHelper.walletPrefix;
                        string id     = prefix + sighash;
                        string amount = "0";
                        filter(prefix, (string objid, byte[] protobuf) =>
                        {
                            Wallet wallet = Wallet.Parser.ParseFrom(protobuf);
                            if (objid.Equals(id))
                            {
                                amount = wallet.Amount;
                            }
                        });
                        Console.WriteLine($"{amount}");
                    }
                    else if (command[0].Equals("address", StringComparison.OrdinalIgnoreCase))
                    {
                        if (command.Length != 5)
                        {
                            throw new Exception("5 parametersd expected");
                        }
                        var blockchain = command[2].ToLower();
                        var addr       = command[3];
                        var network    = command[4].ToLower();
                        filter(RpcHelper.creditCoinNamespace + RpcHelper.addressPrefix, (string objid, byte[] protobuf) =>
                        {
                            Address address = Address.Parser.ParseFrom(protobuf);
                            if (address.Sighash == sighash && address.Blockchain == blockchain && address.Value == addr && address.Network == network)
                            {
                                Console.WriteLine(objid);
                            }
                        });
                    }
                    else if (command[0].Equals("matchingOrders", StringComparison.OrdinalIgnoreCase))
                    {
                        if (command.Length != 2)
                        {
                            throw new Exception("2 parametersd expected");
                        }

                        var askOrders = new Dictionary <string, AskOrder>();
                        filter(RpcHelper.creditCoinNamespace + RpcHelper.askOrderPrefix, (string objid, byte[] protobuf) =>
                        {
                            AskOrder askOrder = AskOrder.Parser.ParseFrom(protobuf);
                            BigInteger block;
                            if (!BigInteger.TryParse(askOrder.Block, out block))
                            {
                                throw new Exception("Invalid numerics");
                            }
                            if (block + askOrder.Expiration > headIdx)
                            {
                                askOrders.Add(objid, askOrder);
                            }
                        });
                        var bidOrders = new Dictionary <string, BidOrder>();
                        filter(RpcHelper.creditCoinNamespace + RpcHelper.bidOrderPrefix, (string objid, byte[] protobuf) =>
                        {
                            BidOrder bidOrder = BidOrder.Parser.ParseFrom(protobuf);
                            BigInteger block;
                            if (!BigInteger.TryParse(bidOrder.Block, out block))
                            {
                                throw new Exception("Invalid numerics");
                            }
                            if (block + bidOrder.Expiration > headIdx)
                            {
                                bidOrders.Add(objid, bidOrder);
                            }
                        });

                        match(sighash, askOrders, bidOrders);
                    }
                    else if (command[0].Equals("currentOffers", StringComparison.OrdinalIgnoreCase))
                    {
                        if (command.Length != 2)
                        {
                            throw new Exception("2 parametersd expected");
                        }

                        var bidOrders = new Dictionary <string, BidOrder>();
                        filter(RpcHelper.creditCoinNamespace + RpcHelper.bidOrderPrefix, (string objid, byte[] protobuf) =>
                        {
                            BidOrder bidOrder = BidOrder.Parser.ParseFrom(protobuf);
                            bidOrders.Add(objid, bidOrder);
                        });

                        filter(RpcHelper.creditCoinNamespace + RpcHelper.offerPrefix, (string objid, byte[] protobuf) =>
                        {
                            Offer offer       = Offer.Parser.ParseFrom(protobuf);
                            BidOrder bidOrder = bidOrders[offer.BidOrder];
                            if (bidOrder.Sighash == sighash)
                            {
                                BigInteger block;
                                if (!BigInteger.TryParse(offer.Block, out block))
                                {
                                    throw new Exception("Invalid numerics");
                                }
                                if (block + offer.Expiration > headIdx)
                                {
                                    Console.WriteLine(objid);
                                }
                            }
                        });
                    }
                    else if (command[0].Equals("creditHistory", StringComparison.OrdinalIgnoreCase))
                    {
                        if (command.Length != 2)
                        {
                            throw new Exception("2 parametersd expected");
                        }

                        filterDeals(null, sighash, (string dealAddress, DealOrder dealOrder) =>
                        {
                            var status = dealOrder.LoanTransfer.Equals(string.Empty) ? "NEW" : "COMPLETE";
                            if (!dealOrder.RepaymentTransfer.Equals(string.Empty))
                            {
                                status = "CLOSED";
                            }
                            Console.WriteLine($"status:{status}, amount:{dealOrder.Amount}, blockchain:{dealOrder.Blockchain}");
                        });
                    }
                    else if (command[0].Equals("newDeals", StringComparison.OrdinalIgnoreCase))
                    {
                        if (command.Length != 2)
                        {
                            throw new Exception("2 parametersd expected");
                        }

                        filterDeals(sighash, null, (string dealAddress, DealOrder dealOrder) =>
                        {
                            if (dealOrder.LoanTransfer.Equals(string.Empty))
                            {
                                BigInteger block;
                                if (!BigInteger.TryParse(dealOrder.Block, out block))
                                {
                                    throw new Exception("Invalid numerics");
                                }
                                if (block + dealOrder.Expiration > headIdx)
                                {
                                    Console.WriteLine(dealAddress);
                                }
                            }
                        });
                    }
                    else if (command[0].Equals("transfer", StringComparison.OrdinalIgnoreCase))
                    {
                        if (command.Length != 3)
                        {
                            throw new Exception("3 parametersd expected");
                        }
                        var orderId = command[2];

                        filter(RpcHelper.creditCoinNamespace + RpcHelper.transferPrefix, (string objid, byte[] protobuf) =>
                        {
                            Transfer transfer = Transfer.Parser.ParseFrom(protobuf);
                            if (transfer.Sighash == sighash && transfer.Order == orderId && !transfer.Processed)
                            {
                                Console.WriteLine(objid);
                            }
                        });
                    }
                    else if (command[0].Equals("currentLoans", StringComparison.OrdinalIgnoreCase))
                    {
                        if (command.Length != 2)
                        {
                            throw new Exception("2 parametersd expected");
                        }

                        filterDeals(null, sighash, (string dealAddress, DealOrder dealOrder) =>
                        {
                            if (!dealOrder.LoanTransfer.Equals(string.Empty) && dealOrder.RepaymentTransfer.Equals(string.Empty))
                            {
                                Console.WriteLine(dealAddress);
                            }
                        });
                    }
                    else if (command[0].Equals("newRepaymentOrders", StringComparison.OrdinalIgnoreCase))
                    {
                        if (command.Length != 2)
                        {
                            throw new Exception("2 parametersd expected");
                        }

                        var addresses = new Dictionary <string, Address>();
                        filter(RpcHelper.creditCoinNamespace + RpcHelper.addressPrefix, (string objid, byte[] protobuf) =>
                        {
                            Address address = Address.Parser.ParseFrom(protobuf);
                            addresses.Add(objid, address);
                        });

                        var dealOrders = new Dictionary <string, DealOrder>();
                        filter(RpcHelper.creditCoinNamespace + RpcHelper.dealOrderPrefix, (string objid, byte[] protobuf) =>
                        {
                            DealOrder dealOrder = DealOrder.Parser.ParseFrom(protobuf);
                            dealOrders.Add(objid, dealOrder);
                        });

                        filter(RpcHelper.creditCoinNamespace + RpcHelper.repaymentOrderPrefix, (string objid, byte[] protobuf) =>
                        {
                            RepaymentOrder repaymentOrder = RepaymentOrder.Parser.ParseFrom(protobuf);
                            DealOrder deal  = dealOrders[repaymentOrder.Deal];
                            Address address = addresses[deal.SrcAddress];
                            if (repaymentOrder.Transfer.Equals(string.Empty) && repaymentOrder.PreviousOwner.Equals(string.Empty) && address.Sighash.Equals(sighash))
                            {
                                BigInteger block;
                                if (!BigInteger.TryParse(repaymentOrder.Block, out block))
                                {
                                    throw new Exception("Invalid numerics");
                                }
                                if (block + repaymentOrder.Expiration > headIdx)
                                {
                                    Console.WriteLine(objid);
                                }
                            }
                        });
                    }
                    else if (command[0].Equals("currentRepaymentOrders", StringComparison.OrdinalIgnoreCase))
                    {
                        filter(RpcHelper.creditCoinNamespace + RpcHelper.repaymentOrderPrefix, (string objid, byte[] protobuf) =>
                        {
                            RepaymentOrder repaymentOrder = RepaymentOrder.Parser.ParseFrom(protobuf);
                            if (repaymentOrder.Transfer.Equals(string.Empty) && !repaymentOrder.PreviousOwner.Equals(string.Empty) && repaymentOrder.Sighash.Equals(sighash))
                            {
                                Console.WriteLine(objid);
                            }
                        });
                    }
                    else
                    {
                        Console.WriteLine("Invalid command " + command[0]);
                        success = false;
                    }

                    if (success)
                    {
                        Console.WriteLine("Success");
                    }
                }
                else
                {
                    var txBuilder = new TxBuilder(signer);
                    if (action.Equals("creditcoin"))
                    {
                        string msg;
                        var    tx = txBuilder.BuildTx(command, out msg);
                        if (tx == null)
                        {
                            Debug.Assert(msg != null);
                            Console.WriteLine(msg);
                        }
                        else
                        {
                            Debug.Assert(msg == null);
                            var content = new ByteArrayContent(tx);
                            content.Headers.Add("Content-Type", "application/octet-stream");
                            Console.WriteLine(RpcHelper.CompleteBatch(httpClient, creditcoinUrl, "batches", content, txid));
                        }
                    }
                    else
                    {
                        var loader = new Loader <ICCClientPlugin>();
                        var msgs   = new List <string>();
                        loader.Load(pluginFolder, msgs);
                        foreach (var msg in msgs)
                        {
                            Console.WriteLine(msg);
                        }

                        ICCClientPlugin plugin       = loader.Get(action);
                        var             pluginConfig = config.GetSection(action);
                        if (plugin == null)
                        {
                            Console.WriteLine("Error: Unknown action " + action);
                        }
                        else
                        {
                            string msg;
                            var    settings = getSettings();
                            bool   done     = plugin.Run(txid, pluginConfig, httpClient, txBuilder, settings, progressId, pluginFolder, creditcoinUrl, command, out inProgress, out msg);
                            if (done)
                            {
                                if (msg == null)
                                {
                                    msg = "Success";
                                }
                                Console.WriteLine(msg);
                            }
                            else
                            {
                                Console.WriteLine("Error: " + msg);
                            }
                        }
                    }
                }
                if (!inProgress)
                {
                    File.Delete(progress);
                }
            }
            catch (Exception x)
            {
                Console.WriteLine($"Error: unexpected failure - {x.Message}");
            }
        }
Ejemplo n.º 2
0
        public bool Run(IConfiguration cfg, HttpClient httpClient, ITxBuilder txBuilder, Dictionary <string, string> settings, string pluginsFolder, string url, string[] command, out bool inProgress, out string msg)
        {
            Debug.Assert(command != null);
            Debug.Assert(command.Length > 1);
            if (command[0].Equals("registerTransfer", StringComparison.OrdinalIgnoreCase))
            {
                // ethereum RegisterTransfer registeredSourceId amount

                string progress = Path.Combine(pluginsFolder, $"{name}_progress.txt");

                inProgress = false;

                Debug.Assert(command.Length == 3 || command.Length == 4);
                string registeredSourceId = command[1];
                string amountString       = command[2];
                bool   erc20 = command.Length == 4;

                string secret = cfg["secret"];
                if (string.IsNullOrWhiteSpace(secret))
                {
                    msg = "ethereum.secret is not set";

                    return(false);
                }
                var ethereumPrivateKey = secret;

                // TODO disable confirmation count config for release build
                string confirmationsCount = cfg["confirmationsCount"];
                if (string.IsNullOrWhiteSpace(confirmationsCount))
                {
                    msg = "ethereum.confirmationsCount is not set";

                    return(false);
                }
                if (!int.TryParse(confirmationsCount, out int confirmationsExpected))
                {
                    msg = "ethereum.confirmationsCount is not an int";

                    return(false);
                }

                string rpcUrl = cfg["rpc"];
                if (string.IsNullOrWhiteSpace(rpcUrl))
                {
                    msg = "ethereum.rpc is not set";

                    return(false);
                }

                var web3 = new Nethereum.Web3.Web3(rpcUrl);

                string     payTxId;
                BigInteger fee;
                if (File.Exists(progress))
                {
                    Console.WriteLine("Found unfinished action, retrying...");
                    var data = File.ReadAllText(progress).Split(':');
                    if (data.Length != 2)
                    {
                        msg = "Invalid progress data";
                        return(false);
                    }
                    payTxId = data[0];
                    if (!BigInteger.TryParse(data[1], out fee))
                    {
                        msg = "Invalid progress data";
                        return(false);
                    }
                }
                else
                {
                    var protobuf = RpcHelper.ReadProtobuf(httpClient, $"{url}/state/{registeredSourceId}", out msg);
                    if (protobuf == null)
                    {
                        return(false);
                    }
                    var address = Address.Parser.ParseFrom(protobuf);

                    string destinationAddress;
                    if (!settings.TryGetValue("sawtooth.escrow." + name, out destinationAddress))
                    {
                        msg = "Escrow not found for " + name;
                        return(false);
                    }

                    BigInteger transferAmount;
                    if (!BigInteger.TryParse(amountString, out transferAmount) || transferAmount <= 0)
                    {
                        msg = "Invalid amount";
                        return(false);
                    }

                    if (!address.Blockchain.Equals(name))
                    {
                        msg = $"ethereum RegisterTransfer can only transfer ether.\nThis source is registered for {address.Blockchain}";
                        return(false);
                    }

                    string sourceAddress = EthECKey.GetPublicAddress(ethereumPrivateKey);

                    if (!sourceAddress.Equals(address.Address_, StringComparison.OrdinalIgnoreCase))
                    {
                        msg = "The deal is for a different client";
                        return(false);
                    }

                    var txCount = web3.Eth.Transactions.GetTransactionCount.SendRequestAsync(sourceAddress).Result;
                    TransactionSigner signer = new TransactionSigner();

                    // TODO maybe add a y/n choice for user to decline or configurable gas price override
                    var gasPrice = web3.Eth.GasPrice.SendRequestAsync().Result;
                    Console.WriteLine("gasPrice: " + gasPrice.Value.ToString());

                    string        to;
                    string        data;
                    BigInteger    amount;
                    HexBigInteger gasLimit;
                    if (erc20)
                    {
                        string creditcoinContract    = cfg["creditcoinContract"];
                        string creditcoinContractAbi = cfg["creditcoinContractAbi"];

                        to = creditcoinContract;

                        var contract      = web3.Eth.GetContract(creditcoinContractAbi, creditcoinContract);
                        var burn          = contract.GetFunction("exchange");
                        var functionInput = new object[] { transferAmount, address.Sighash };
                        data     = burn.GetData(functionInput);
                        gasLimit = burn.EstimateGasAsync(functionInput).Result;
                        fee      = gasLimit.Value * gasPrice.Value;
                        amount   = 0;
                    }
                    else
                    {
                        gasLimit = web3.Eth.Transactions.EstimateGas.SendRequestAsync(new Nethereum.RPC.Eth.DTOs.CallInput(registeredSourceId, destinationAddress, new Nethereum.Hex.HexTypes.HexBigInteger(transferAmount))).Result;
                        Console.WriteLine("gasLimit: " + gasLimit.Value.ToString());

                        fee    = gasLimit.Value * gasPrice.Value;
                        to     = destinationAddress;
                        data   = address.Sighash;
                        amount = transferAmount + fee;
                    }

                    string txRaw = signer.SignTransaction(ethereumPrivateKey, to, amount, txCount, gasPrice, gasLimit, data);
                    payTxId = web3.Eth.Transactions.SendRawTransaction.SendRequestAsync("0x" + txRaw).Result;
                    Console.WriteLine("Ethereum Transaction ID: " + payTxId);

                    File.WriteAllText(progress, $"{payTxId}:{fee.ToString()}");
                }

                inProgress = true;
                while (true)
                {
                    var receipt = web3.Eth.TransactionManager.TransactionReceiptService.PollForReceiptAsync(payTxId).Result;
                    if (receipt.BlockNumber != null)
                    {
                        var blockNumber = web3.Eth.Blocks.GetBlockNumber.SendRequestAsync().Result;
                        if (blockNumber.Value - receipt.BlockNumber.Value >= confirmationsExpected)
                        {
                            break;
                        }
                    }
                    Thread.Sleep(1000);
                }
                File.Delete(progress);
                inProgress = false;

                command = new string[] { command[0], registeredSourceId, amountString, erc20 ? "0" : fee.ToString(), payTxId, erc20 ? "creditcoin" : "1" };
                var tx = txBuilder.BuildTx(command, out msg);
                if (tx == null)
                {
                    return(false);
                }

                Debug.Assert(msg == null);

                var content = new ByteArrayContent(tx);
                content.Headers.Add("Content-Type", "application/octet-stream");

                msg = RpcHelper.CompleteBatch(httpClient, $"{url}/batches", content);

                return(true);
            }
            else
            {
                inProgress = false;
                msg        = "Unknown command: " + command[0];
                return(false);
            }
        }
Ejemplo n.º 3
0
        public bool Run(bool txid, IConfiguration cfg, HttpClient httpClient, ITxBuilder txBuilder, Dictionary <string, string> settings, string progressId, string pluginsFolder, string url, string[] command, out bool inProgress, out string msg)
        {
            Debug.Assert(command != null);
            if (command.Length < 2)
            {
                inProgress = false;
                msg        = "invalid parameter count";
                return(false);
            }

            bool erc20 = command[0].Equals("collectCoins", StringComparison.OrdinalIgnoreCase);

            if (erc20 || command[0].Equals("registerTransfer", StringComparison.OrdinalIgnoreCase))
            {
                // ethereum RegisterTransfer gain orderId
                // ethereum CollectCoins amount

                string progress = Path.Combine(pluginsFolder, $"{name}_progress{progressId}.txt");

                inProgress = false;

                if (erc20 && command.Length != 2 || !erc20 && command.Length != 3)
                {
                    msg = "invalid parameter count";
                    return(false);
                }

                string gainString   = "0";
                string orderId      = null;
                string amountString = null;
                if (erc20)
                {
                    amountString = command[1];
                }
                else
                {
                    gainString = command[1];
                    orderId    = command[2];
                }

                string secret = cfg["secret"];
                if (string.IsNullOrWhiteSpace(secret))
                {
                    msg = "ethereum.secret is not set";
                    return(false);
                }
                var ethereumPrivateKey = secret;

#if DEBUG
                string confirmationsCount = cfg["confirmationsCount"];
                if (int.TryParse(confirmationsCount, out int parsedCount))
                {
                    mConfirmationsExpected = parsedCount;
                }
#endif

                string rpcUrl = cfg["rpc"];
                if (string.IsNullOrWhiteSpace(rpcUrl))
                {
                    msg = "ethereum.rpc is not set";
                    return(false);
                }

                string ethSrcAddress = EthECKey.GetPublicAddress(ethereumPrivateKey);
                var    web3          = new Nethereum.Web3.Web3(rpcUrl);

                string srcAddressId = null;
                string dstAddressId = null;
                if (!erc20)
                {
                    var protobuf = RpcHelper.ReadProtobuf(httpClient, $"{url}/state/{orderId}", out msg);
                    if (protobuf == null)
                    {
                        msg = "failed to extract address data through RPC";
                        return(false);
                    }
                    if (orderId.StartsWith(RpcHelper.creditCoinNamespace + RpcHelper.dealOrderPrefix))
                    {
                        var dealOrder = DealOrder.Parser.ParseFrom(protobuf);
                        if (gainString.Equals("0"))
                        {
                            srcAddressId = dealOrder.SrcAddress;
                            dstAddressId = dealOrder.DstAddress;
                        }
                        else
                        {
                            dstAddressId = dealOrder.SrcAddress;
                            srcAddressId = dealOrder.DstAddress;
                        }
                        amountString = dealOrder.Amount;
                    }
                    else if (orderId.StartsWith(RpcHelper.creditCoinNamespace + RpcHelper.repaymentOrderPrefix))
                    {
                        var repaymentOrder = RepaymentOrder.Parser.ParseFrom(protobuf);
                        if (gainString.Equals("0"))
                        {
                            srcAddressId = repaymentOrder.SrcAddress;
                            dstAddressId = repaymentOrder.DstAddress;
                        }
                        else
                        {
                            dstAddressId = repaymentOrder.SrcAddress;
                            srcAddressId = repaymentOrder.DstAddress;
                        }
                        amountString = repaymentOrder.Amount;
                    }
                    else
                    {
                        msg = "unexpected referred order";
                        return(false);
                    }
                }

                string payTxId;
                if (File.Exists(progress))
                {
                    Console.WriteLine("Found unfinished action, retrying...");
                    payTxId = File.ReadAllText(progress);
                }
                else
                {
                    string ethDstAddress = null;
                    if (!erc20)
                    {
                        var protobuf = RpcHelper.ReadProtobuf(httpClient, $"{url}/state/{srcAddressId}", out msg);
                        if (protobuf == null)
                        {
                            msg = "failed to extract address data through RPC";
                            return(false);
                        }
                        var srcAddress = Address.Parser.ParseFrom(protobuf);

                        protobuf = RpcHelper.ReadProtobuf(httpClient, $"{url}/state/{dstAddressId}", out msg);
                        if (protobuf == null)
                        {
                            msg = "failed to extract address data through RPC";
                            return(false);
                        }
                        Address dstAddress = Address.Parser.ParseFrom(protobuf);

                        if (!srcAddress.Blockchain.Equals(name) || !dstAddress.Blockchain.Equals(name))
                        {
                            msg = $"ethereum RegisterTransfer can only transfer ether.\nThis source is registered for {srcAddress.Blockchain} and destination for {dstAddress.Blockchain}";
                            return(false);
                        }
                        ethDstAddress = dstAddress.Value;

                        if (!ethSrcAddress.Equals(srcAddress.Value, StringComparison.OrdinalIgnoreCase))
                        {
                            msg = "The deal is for a different client";
                            return(false);
                        }
                    }

                    BigInteger transferAmount;
                    if (!BigInteger.TryParse(amountString, out transferAmount) || transferAmount <= 0)
                    {
                        msg = "Invalid amount";
                        return(false);
                    }
                    BigInteger gain;
                    if (!BigInteger.TryParse(gainString, out gain))
                    {
                        msg = "Invalid amount";
                        return(false);
                    }
                    transferAmount = transferAmount + gain;
                    if (transferAmount < 0)
                    {
                        msg = "Invalid amount";
                        return(false);
                    }

                    var txCount = web3.Eth.Transactions.GetTransactionCount.SendRequestAsync(ethSrcAddress).Result;
                    TransactionSigner signer = new TransactionSigner();

                    HexBigInteger gasPrice;

                    string gasPriceInGweiString = cfg["gasPriceInGwei"];
                    if (int.TryParse(gasPriceInGweiString, out int gasPriceOverride))
                    {
                        gasPrice = new HexBigInteger(Nethereum.Util.UnitConversion.Convert.ToWei(gasPriceOverride, Nethereum.Util.UnitConversion.EthUnit.Gwei));
                    }
                    else
                    {
                        gasPrice = web3.Eth.GasPrice.SendRequestAsync().Result;
                    }
                    Console.WriteLine("gasPrice: " + gasPrice.Value.ToString());

                    string        to;
                    string        data;
                    BigInteger    amount;
                    HexBigInteger gasLimit;
                    if (erc20)
                    {
                        string creditcoinContract    = cfg["creditcoinContract"];
                        string creditcoinContractAbi = cfg["creditcoinContractAbi"];

                        to = creditcoinContract;

                        var contract      = web3.Eth.GetContract(creditcoinContractAbi, creditcoinContract);
                        var burn          = contract.GetFunction("exchange");
                        var functionInput = new object[] { transferAmount, txBuilder.getSighash() };
                        data     = burn.GetData(functionInput);
                        gasLimit = burn.EstimateGasAsync(functionInput).Result;
                        amount   = 0;
                    }
                    else
                    {
                        gasLimit = web3.Eth.Transactions.EstimateGas.SendRequestAsync(new Nethereum.RPC.Eth.DTOs.CallInput(orderId, ethDstAddress, new Nethereum.Hex.HexTypes.HexBigInteger(transferAmount))).Result;
                        Console.WriteLine("gasLimit: " + gasLimit.Value.ToString());

                        to     = ethDstAddress;
                        data   = orderId;
                        amount = transferAmount;
                    }

                    string txRaw = signer.SignTransaction(ethereumPrivateKey, to, amount, txCount, gasPrice, gasLimit, data);
                    payTxId = web3.Eth.Transactions.SendRawTransaction.SendRequestAsync("0x" + txRaw).Result;
                    Console.WriteLine("Ethereum Transaction ID: " + payTxId);

                    File.WriteAllText(progress, $"{payTxId}");
                }

                inProgress = true;
                while (true)
                {
                    var receipt = web3.Eth.TransactionManager.TransactionReceiptService.PollForReceiptAsync(payTxId).Result;
                    if (receipt.BlockNumber != null)
                    {
                        var blockNumber = web3.Eth.Blocks.GetBlockNumber.SendRequestAsync().Result;
                        if (blockNumber.Value - receipt.BlockNumber.Value >= mConfirmationsExpected)
                        {
                            break;
                        }
                    }
                    Thread.Sleep(1000);
                }
                File.Delete(progress);
                inProgress = false;

                if (erc20)
                {
                    command = new string[] { command[0], ethSrcAddress, amountString, payTxId };
                }
                else
                {
                    command = new string[] { command[0], gainString, orderId, payTxId };
                }
                var tx = txBuilder.BuildTx(command, out msg);
                if (tx == null)
                {
                    return(false);
                }

                Debug.Assert(msg == null);

                var content = new ByteArrayContent(tx);
                content.Headers.Add("Content-Type", "application/octet-stream");

                msg = RpcHelper.CompleteBatch(httpClient, url, "batches", content, txid);

                return(true);
            }
            else
            {
                inProgress = false;
                msg        = "Unknown command: " + command[0];
                return(false);
            }
        }
Ejemplo n.º 4
0
        public bool Run(IConfiguration cfg, string signerHexStr, string[] command, out string msg)
        {
            Debug.Assert(command != null);
            Debug.Assert(command.Length > 0);
            if (command[0].Equals("verify"))
            {
                Debug.Assert(command.Length == 7);
                string txId = command[1];
                string destinationAdressString = command[2];
                string destinationAmount       = command[3];
                string sighash             = command[4];
                string sourceAddressString = command[5];
                string networkId           = command[6];

                string rpcAddress = cfg["rpc"];
                if (string.IsNullOrWhiteSpace(rpcAddress))
                {
                    msg = "bitcoin.rpc is not set";
                    return(false);
                }

                string credential = cfg["credential"];
                if (string.IsNullOrWhiteSpace(credential))
                {
                    msg = "bitcoin.credential is not set";
                    return(false);
                }

                // TODO disable confirmation customization for release build
                string confirmationsCount = cfg["confirmationsCount"];
                if (string.IsNullOrWhiteSpace(confirmationsCount))
                {
                    msg = "bitcoin.confirmationsCount is not set";
                    return(false);
                }
                var confirmationsExpected = 1;
                if (!int.TryParse(confirmationsCount, out confirmationsExpected))
                {
                    msg = "bitcoin.confirmationsCount is not an int";
                    return(false);
                }

                Network network   = ((networkId == "1") ? Network.Main : Network.TestNet);
                var     rpcClient = new RPCClient(credential, new Uri(rpcAddress), network);
                if (!uint256.TryParse(txId, out var transactionId))
                {
                    msg = "Invalid transaction: transaction ID invalid";
                    return(false);
                }

                var transactionInfoResponse = rpcClient.GetRawTransactionInfo(transactionId);
                if (transactionInfoResponse.Confirmations < confirmationsExpected)
                {
                    msg = "Invalid transaction: not enough confirmations";
                    return(false);
                }

                if (transactionInfoResponse.Transaction.Outputs.Count < 2 || transactionInfoResponse.Transaction.Outputs.Count > 3)
                {
                    msg = "Invalid transaction: unexpected amount of output";
                    return(false);
                }

                var destinationAddress = BitcoinAddress.Create(destinationAdressString, network);
                var outputCoins        = transactionInfoResponse.Transaction.Outputs.AsCoins();
                var paymentCoin        = outputCoins.SingleOrDefault(oc => oc.ScriptPubKey == destinationAddress.ScriptPubKey);
                if (paymentCoin == null)
                {
                    msg = "Invalid transaction: wrong destinationAdressString";
                    return(false);
                }

                if (paymentCoin.TxOut.Value.Satoshi.ToString() != destinationAmount.ToString())
                {
                    msg = "Invalid transaction: wrong amount";
                    return(false);
                }

                var bytes        = Encoding.UTF8.GetBytes(sighash);
                var nullDataCoin = outputCoins.SingleOrDefault(oc => oc.ScriptPubKey == TxNullDataTemplate.Instance.GenerateScriptPubKey(bytes));
                if (nullDataCoin == null)
                {
                    msg = "Invalid transaction: wrong sighash";
                    return(false);
                }

                if (nullDataCoin.TxOut.Value.CompareTo(Money.Zero) != 0)
                {
                    msg = "Invalid transaction: expecting a message";
                    return(false);
                }

                var sourceAddress = BitcoinAddress.Create(sourceAddressString, network);
                var input         = transactionInfoResponse.Transaction.Inputs[0];
                if (!Script.VerifyScript(input.ScriptSig, sourceAddress.ScriptPubKey, transactionInfoResponse.Transaction, 0))
                {
                    msg = "Invalid transaction: wrong sourceAddressString";
                    return(false);
                }

                msg = null;
                return(true);
            }
            else if (command[0].Equals("unlock"))
            {
                var ccSigner  = new Signer(RpcHelper.HexToBytes(signerHexStr));
                var txBuilder = new TxBuilder(ccSigner);

                Debug.Assert(command.Length == 5);
                string kind = command[1];
                string addressFromString = command[2];

                HttpClient httpClient = new HttpClient();

                string txFrom;
                string amountString;
                string feeString;
                string addressToString;
                string networkId;

                string ccCommand;

                if (kind.Equals("funds"))
                {
                    string dealOrderId            = command[3];
                    string addressToUnlockFundsTo = command[4];

                    var protobuf = RpcHelper.ReadProtobuf(httpClient, $"{creditcoinUrl}/state/{dealOrderId}", out msg);
                    if (protobuf == null)
                    {
                        return(false);
                    }
                    var dealOrder = DealOrder.Parser.ParseFrom(protobuf);
                    protobuf = RpcHelper.ReadProtobuf(httpClient, $"{creditcoinUrl}/state/{dealOrder.AskOrderId}", out msg);
                    if (protobuf == null)
                    {
                        return(false);
                    }
                    var askOrder = AskOrder.Parser.ParseFrom(protobuf);
                    protobuf = RpcHelper.ReadProtobuf(httpClient, $"{creditcoinUrl}/state/{askOrder.TransferId}", out msg);
                    if (protobuf == null)
                    {
                        return(false);
                    }
                    var transfer = Transfer.Parser.ParseFrom(protobuf);
                    protobuf = RpcHelper.ReadProtobuf(httpClient, $"{creditcoinUrl}/state/{addressToUnlockFundsTo}", out msg);
                    if (protobuf == null)
                    {
                        return(false);
                    }
                    var address = Address.Parser.ParseFrom(protobuf);
                    if (!askOrder.Sighash.Equals(address.Sighash))
                    {
                        msg = "The adress doesn't match the ask order";
                        return(false);
                    }
                    txFrom          = transfer.Txid;
                    amountString    = transfer.Amount;
                    feeString       = transfer.Fee;
                    addressToString = address.Address_;
                    networkId       = transfer.Network;
                    ccCommand       = "UnlockFunds";
                }
                else if (kind.Equals("collateral"))
                {
                    string repaymentOrderId             = command[3];
                    string addressToUnlockCollateralsTo = command[4];

                    var protobuf = RpcHelper.ReadProtobuf(httpClient, $"{creditcoinUrl}/state/{repaymentOrderId}", out msg);
                    if (protobuf == null)
                    {
                        return(false);
                    }
                    var repaymentOrder = RepaymentOrder.Parser.ParseFrom(protobuf);
                    protobuf = RpcHelper.ReadProtobuf(httpClient, $"{creditcoinUrl}/state/{repaymentOrder.DealId}", out msg);
                    if (protobuf == null)
                    {
                        return(false);
                    }
                    var dealOrder = DealOrder.Parser.ParseFrom(protobuf);
                    protobuf = RpcHelper.ReadProtobuf(httpClient, $"{creditcoinUrl}/state/{dealOrder.AskOrderId}", out msg);
                    if (protobuf == null)
                    {
                        return(false);
                    }
                    var askOrder = AskOrder.Parser.ParseFrom(protobuf);
                    protobuf = RpcHelper.ReadProtobuf(httpClient, $"{creditcoinUrl}/state/{askOrder.TransferId}", out msg);
                    if (protobuf == null)
                    {
                        return(false);
                    }
                    var transfer = Transfer.Parser.ParseFrom(protobuf);
                    protobuf = RpcHelper.ReadProtobuf(httpClient, $"{creditcoinUrl}/state/{addressToUnlockCollateralsTo}", out msg);
                    if (protobuf == null)
                    {
                        return(false);
                    }
                    var address = Address.Parser.ParseFrom(protobuf);
                    if (!askOrder.Sighash.Equals(address.Sighash))
                    {
                        msg = "The adress doesn't match the ask order";
                        return(false);
                    }
                    txFrom          = transfer.Txid;
                    amountString    = transfer.Amount;
                    feeString       = transfer.Fee;
                    addressToString = address.Address_;
                    networkId       = transfer.Network;
                    ccCommand       = "UnlockCollateral";
                }
                else
                {
                    msg = "unknown unlock kind";
                    return(false);
                }

                string rpcAddress = cfg["rpc"];
                if (string.IsNullOrWhiteSpace(rpcAddress))
                {
                    msg = "bitcoin.rpc is not set";
                    return(false);
                }

                string credential = cfg["credential"];
                if (string.IsNullOrWhiteSpace(credential))
                {
                    msg = "bitcoin.credential is not set";
                    return(false);
                }

                string secret = cfg["secret"];
                if (string.IsNullOrWhiteSpace(secret))
                {
                    msg = "bitcoin.secret is not set";
                    return(false);
                }

                // TODO disable confirmation config for release build
                string confirmationsCount = cfg["confirmationsCount"];
                if (string.IsNullOrWhiteSpace(confirmationsCount))
                {
                    msg = "bitcoin.confirmationsCount is not set";
                    return(false);
                }
                var confirmationsExpected = 1;
                if (!int.TryParse(confirmationsCount, out confirmationsExpected))
                {
                    msg = "bitcoin.confirmationsCount is not an int";
                    return(false);
                }

                Network network       = ((networkId == "1") ? Network.Main : Network.TestNet);
                var     rpcClient     = new RPCClient(credential, new Uri(rpcAddress), network);
                var     transactionId = uint256.Parse(txFrom);

                var bitcoinPrivateKey = new BitcoinSecret(secret);
                if (bitcoinPrivateKey.Network != network)
                {
                    msg = "Mismatching networks";
                    return(false);
                }

                var transactionResponse = rpcClient.GetRawTransaction(transactionId);

                Money transferAmount;
                if (!Money.TryParse(amountString, out transferAmount) || transferAmount <= 0)
                {
                    msg = "Invalid amount";
                    return(false);
                }

                if (!int.TryParse(feeString, out int fee))
                {
                    msg = "bitcoin.fee is not an int";
                    return(false);
                }

                var      receivedCoins   = transactionResponse.Outputs.AsCoins();
                OutPoint outPointToSpend = null;
                TxOut    outTxToSpend    = null;
                Money    amount          = null;
                foreach (var coin in receivedCoins)
                {
                    if (coin.TxOut.ScriptPubKey == bitcoinPrivateKey.ScriptPubKey)
                    {
                        outPointToSpend = coin.Outpoint;
                        outTxToSpend    = coin.TxOut;
                        amount          = (Money)coin.Amount;
                        if (amount.CompareTo(transferAmount + fee * 2) < 0)
                        {
                            msg = $"Invalid transaction - needed: {transferAmount}, has: {amount.ToString()}";
                            return(false);
                        }
                        break;
                    }
                }
                if (outPointToSpend == null)
                {
                    msg = "Invalid transaction - no outputs that the client can spend";
                    return(false);
                }

                var addressFrom = BitcoinAddress.Create(addressFromString, network);
                var addressTo   = BitcoinAddress.Create(addressToString, network);

                var bitcoinTransactionBuilder = new TransactionBuilder();

                var transaction = bitcoinTransactionBuilder
                                  .AddCoins(new Coin(outPointToSpend, outTxToSpend))
                                  .AddKeys(bitcoinPrivateKey)
                                  .Send(addressTo.ScriptPubKey, transferAmount)
                                  .SendFees(new Money(fee, MoneyUnit.Satoshi))
                                  .SetChange(addressFrom.ScriptPubKey)
                                  .BuildTransaction(true);

                if (!bitcoinTransactionBuilder.Verify(transaction))
                {
                    msg = "failed verify transaction";
                    return(false);
                }

                uint256 payTxId;
                try
                {
                    payTxId = rpcClient.SendRawTransaction(transaction);
                }
                catch (RPCException e)
                {
                    msg = $"failed to broadcast - error: {e.RPCCode}, reason: {e.RPCCodeMessage}";
                    return(false);
                }

                if (payTxId == null)
                {
                    msg = "failed to broadcast - unknown error";
                    return(false);
                }

                while (true) //TODO: this may lock for a very long time or forever, fix ---------------------------------------------------------------------------------------------------------------------------------------
                {
                    var transactionInfo = rpcClient.GetRawTransactionInfo(payTxId);
                    if (transactionInfo != null && transactionInfo.BlockHash != null && transactionInfo.Confirmations >= confirmationsExpected)
                    {
                        break;
                    }

                    Thread.Sleep(1000);
                }

                command = new string[] { ccCommand, command[3], command[4] };
                var tx = txBuilder.BuildTx(command, out msg);
                Debug.Assert(tx != null);
                Debug.Assert(msg == null);
                var content = new ByteArrayContent(tx);
                content.Headers.Add("Content-Type", "application/octet-stream");
                msg = RpcHelper.CompleteBatch(httpClient, $"{creditcoinUrl}/batches", content);
            }

            msg = "Unknown command: " + command[0];
            return(false);
        }
Ejemplo n.º 5
0
        private const int SKIP_TO_GET_60 = 512 / 8 * 2 - 60; // 512 - hash size, 8 - bits in byte, 2 - hex digits for byte, 60 - merkle address length (70) without namespace length (6) and prexix length (4)

        static void Main(string[] args)
        {
            try
            {
                IConfiguration config = new ConfigurationBuilder()
                                        .AddJsonFile("appsettings.json", true, false)
#if DEBUG
                                        .AddJsonFile("appsettings.dev.json", true, false)
#endif
                                        .Build();
                string signerHexStr = config["signer"];
                if (string.IsNullOrWhiteSpace(signerHexStr))
                {
                    Console.WriteLine("Signer is not configured");
                    return;
                }
                var signer = new Signer(RpcHelper.HexToBytes(signerHexStr));

                string creditcoinRestApiURL = config["creditcoinRestApiURL"];
                if (!string.IsNullOrWhiteSpace(creditcoinRestApiURL))
                {
                    creditcoinUrl = creditcoinRestApiURL;
                }

                string root   = Directory.GetCurrentDirectory();
                string folder = TxBuilder.GetPluginsFolder(root);
                if (folder == null)
                {
                    Console.WriteLine("plugins subfolder not found");
                    return;
                }

                string progress = Path.Combine(folder, "progress.txt");

                string   action;
                string[] command;
                if (File.Exists(progress))
                {
                    Console.WriteLine("Found unfinished action, retrying...");
                    args = File.ReadAllText(progress).Split();
                }
                else
                {
                    File.WriteAllText(progress, string.Join(' ', args));
                }
                action  = args[0].ToLower();
                command = args.Skip(1).ToArray();
                var txBuilder = new TxBuilder(signer);

                var settings = new Dictionary <string, string>();
                filter(settingNamespace, (string address, byte[] protobuf) =>
                {
                    Setting setting = Setting.Parser.ParseFrom(protobuf);
                    foreach (var entry in setting.Entries)
                    {
                        settings.Add(entry.Key, entry.Value);
                    }
                });

                bool inProgress = false;

                // API:
                // list Settings|Wallets|Addresses|Transfers|AskOrders|BidOrders|DealOrders|RepaymentOrders|
                // list Balance
                // list Sighash
                // list Address blockchain address
                // list UnusedTransfers addressId amount
                // list MatchingOrders
                // list CreditHistory sighash
                // list NewDeals
                // list RunningDeals
                // list NewRepaymentOrders
                // creditcoin AddFunds amount sighash (creditcoin AddFunds 1000000 16de574ac8ac3067977df056ecff51345672d25d528303b3555ab2aa4cd5)
                //      AddFunds only works for a registered signer
                // creditcoin SendFunds amount sighash (creditcoin SendFunds 200000 8704a4f77befea5c8082d414f98dc16e4ba82a0898422d031f41693260a0)
                // creditcoin RegisterAddress blockchain address (creditcoin RegisterAddress bitcoin <BITCOIN-ADDRESS>)
                // creditcoin AddAskOrder blockchain amount interest blockchain collateral fee expiration capitalLockTransferId (creditcoin AddAskOrder bitcoin 1000000 10 bitcoin 50 100 1565386152 <TRANSFER-ID>)
                // creditcoin AddBidOrder blockchain amount interest blockchain collateral fee expiration (creditcoin AddAskOrder bitcoin 1000000 10 bitcoin 50 100 1565386152)
                // creditcoin AddDealOrder askOrderId bidOrderId
                // creditcoin CompleteDealOrder dealOrderId collateralLockTransferId
                // creditcoin AddRepaymentOrder dealOrderId
                // creditcoin CompleteRepaymentOrder repaymentOrderId transferId
                // creditcoin CollectCoins transferId
                // <blockchain> RegisterTransfer ...
                //      bitcoin RegisterTransfer registeredAddressId amount sourceTxId
                //      ethereum RegisterTransfer registeredAddressId amount [erc20]
                // unlock Funds dealOrderId addressToUnlockFundsTo
                // unlock Collateral repaymentOrderId addressToUnlockCollateralTo

                if (action.Equals("unlock"))
                {
                    string externalGatewayAddress;
                    if (!settings.TryGetValue("sawtooth.validator.gateway", out externalGatewayAddress))
                    {
                        Console.WriteLine("Error: external gateway is not configured");
                    }
                    else
                    {
                        if (!externalGatewayAddress.StartsWith("tcp://"))
                        {
                            externalGatewayAddress = "tcp://" + externalGatewayAddress;
                        }
                        bool success = true;
                        switch (command[0].ToLower())
                        {
                        case "funds":
                            Debug.Assert(command.Length == 3);
                            {
                                var    dealOrderId            = command[1].ToLower();
                                var    addressToUnlockFundsTo = command[2].ToLower();
                                string msg;
                                string blockchain = null;
                                var    protobuf   = RpcHelper.ReadProtobuf(httpClient, $"{creditcoinUrl}/state/{dealOrderId}", out msg);
                                if (protobuf != null)
                                {
                                    var dealOrder = DealOrder.Parser.ParseFrom(protobuf);
                                    protobuf = RpcHelper.ReadProtobuf(httpClient, $"{creditcoinUrl}/state/{dealOrder.AskOrderId}", out msg);
                                    if (protobuf != null)
                                    {
                                        var askOrder = AskOrder.Parser.ParseFrom(protobuf);
                                        blockchain = askOrder.Blockchain;
                                    }
                                }

                                if (blockchain == null)
                                {
                                    success = false;
                                    Console.WriteLine("Error: " + msg);
                                }
                                else
                                {
                                    string escrow;
                                    if (!settings.TryGetValue("sawtooth.escrow." + blockchain, out escrow))
                                    {
                                        success = false;
                                        Console.WriteLine("Error: escrow is not configured for " + blockchain);
                                    }
                                    else
                                    {
                                        using (var socket = new RequestSocket())
                                        {
                                            socket.Connect(externalGatewayAddress);
                                            var request = $"{blockchain} unlock funds {escrow} {dealOrderId} {addressToUnlockFundsTo}";
                                            socket.SendFrame(request);
                                            string response = socket.ReceiveFrameString();
                                            if (response != "good")
                                            {
                                                success = false;
                                                Console.WriteLine("Error: failed to execute the global gateway command");
                                            }
                                        }
                                    }
                                }
                            }
                            break;

                        case "collateral":
                            Debug.Assert(command.Length == 3);
                            {
                                var    repaymentOrderId             = command[1].ToLower();
                                var    addressToUnlockCollateralsTo = command[2].ToLower();
                                string msg;
                                string blockchain = null;
                                var    protobuf   = RpcHelper.ReadProtobuf(httpClient, $"{creditcoinUrl}/state/{repaymentOrderId}", out msg);
                                if (protobuf != null)
                                {
                                    var repaymentOrder = RepaymentOrder.Parser.ParseFrom(protobuf);
                                    protobuf = RpcHelper.ReadProtobuf(httpClient, $"{creditcoinUrl}/state/{repaymentOrder.DealId}", out msg);
                                    if (protobuf != null)
                                    {
                                        var dealOrder = DealOrder.Parser.ParseFrom(protobuf);
                                        protobuf = RpcHelper.ReadProtobuf(httpClient, $"{creditcoinUrl}/state/{dealOrder.AskOrderId}", out msg);
                                        if (protobuf != null)
                                        {
                                            var askOrder = AskOrder.Parser.ParseFrom(protobuf);
                                            blockchain = askOrder.Blockchain;
                                        }
                                    }
                                }

                                if (blockchain == null)
                                {
                                    success = false;
                                    Console.WriteLine("Error: " + msg);
                                }
                                else
                                {
                                    string escrow;
                                    if (!settings.TryGetValue("sawtooth.escrow." + blockchain, out escrow))
                                    {
                                        success = false;
                                        Console.WriteLine("Error: escrow is not configured for " + blockchain);
                                    }
                                    else
                                    {
                                        using (var socket = new RequestSocket())
                                        {
                                            socket.Connect(externalGatewayAddress);
                                            var request = $"{blockchain} unlock collateral {escrow} {repaymentOrderId} {addressToUnlockCollateralsTo}";
                                            socket.SendFrame(request);
                                            string response = socket.ReceiveFrameString();
                                            if (response != "good")
                                            {
                                                success = false;
                                                Console.WriteLine("Error: failed to execute the global gateway command");
                                            }
                                        }
                                    }
                                }
                            }
                            break;
                        }
                        if (success)
                        {
                            Console.WriteLine("Success");
                        }
                    }
                }
                else if (action.Equals("list"))
                {
                    bool success = true;

                    if (command[0].Equals("settings", StringComparison.OrdinalIgnoreCase))
                    {
                        Debug.Assert(command.Length == 1);
                        filter(settingNamespace, (string objid, byte[] protobuf) =>
                        {
                            Setting setting = Setting.Parser.ParseFrom(protobuf);
                            foreach (var entry in setting.Entries)
                            {
                                Console.WriteLine($"{entry.Key}: {entry.Value}");
                            }
                        });
                    }
                    else if (command[0].Equals("wallets", StringComparison.OrdinalIgnoreCase))
                    {
                        Debug.Assert(command.Length == 1);
                        filter(creditCoinNamespace + walletPrefix, (string objid, byte[] protobuf) =>
                        {
                            Wallet wallet = Wallet.Parser.ParseFrom(protobuf);
                            Console.WriteLine($"wallet({objid}) amount:{wallet.Amount}");
                        });
                    }
                    else if (command[0].Equals("balance", StringComparison.OrdinalIgnoreCase))
                    {
                        Debug.Assert(command.Length == 1);
                        {
                            string sighash = sha256(signer.GetPublicKey().ToHexString());
                            string prefix  = creditCoinNamespace + walletPrefix;
                            string id      = prefix + sighash;

                            filter(prefix, (string objid, byte[] protobuf) =>
                            {
                                Wallet wallet = Wallet.Parser.ParseFrom(protobuf);
                                if (objid.Equals(id))
                                {
                                    Console.WriteLine($"balance for {sighash} is {wallet.Amount}");
                                }
                            });
                        }
                    }
                    else if (command[0].Equals("addresses", StringComparison.OrdinalIgnoreCase))
                    {
                        Debug.Assert(command.Length == 1);
                        filter(creditCoinNamespace + addressPrefix, (string objid, byte[] protobuf) =>
                        {
                            Address address = Address.Parser.ParseFrom(protobuf);
                            Console.WriteLine($"address({objid}) sighash:{address.Sighash}, blockchain: {address.Blockchain}, address:{address.Address_}");
                        });
                    }
                    else if (command[0].Equals("transfers", StringComparison.OrdinalIgnoreCase))
                    {
                        Debug.Assert(command.Length == 1);
                        filter(creditCoinNamespace + transferPrefix, (string objid, byte[] protobuf) =>
                        {
                            Transfer transfer = Transfer.Parser.ParseFrom(protobuf);
                            Console.WriteLine($"transfer({objid}) sighash:{transfer.Sighash}, blockchain: {transfer.Blockchain}, amount:{transfer.Amount}, fee:{transfer.Fee}, txid:{transfer.Txid}");
                        });
                    }
                    else if (command[0].Equals("askOrders", StringComparison.OrdinalIgnoreCase))
                    {
                        Debug.Assert(command.Length == 1);
                        filter(creditCoinNamespace + askOrderPrefix, (string objid, byte[] protobuf) =>
                        {
                            AskOrder askOrder = AskOrder.Parser.ParseFrom(protobuf);
                            Console.WriteLine($"askOrder({objid}) sighash:{askOrder.Sighash}, blockchain: {askOrder.Blockchain}, amount:{askOrder.Amount}, interest:{askOrder.Interest}, collateralBlockchain:{askOrder.CollateralBlockchain}, collateral:{askOrder.Collateral}, fee:{askOrder.Fee}, expiration:{askOrder.Expiration}, transferId:{askOrder.TransferId}");
                        });
                    }
                    else if (command[0].Equals("bidOrders", StringComparison.OrdinalIgnoreCase))
                    {
                        Debug.Assert(command.Length == 1);
                        filter(creditCoinNamespace + bidOrderPrefix, (string objid, byte[] protobuf) =>
                        {
                            BidOrder bidOrder = BidOrder.Parser.ParseFrom(protobuf);
                            Console.WriteLine($"bidOrder({objid}) sighash:{bidOrder.Sighash}, blockchain: {bidOrder.Blockchain}, amount:{bidOrder.Amount}, interest:{bidOrder.Interest}, collateralBlockchain:{bidOrder.CollateralBlockchain}, collateral:{bidOrder.Collateral}, fee:{bidOrder.Fee}, expiration:{bidOrder.Expiration}");
                        });
                    }
                    else if (command[0].Equals("dealOrders", StringComparison.OrdinalIgnoreCase))
                    {
                        Debug.Assert(command.Length == 1);
                        filter(creditCoinNamespace + dealOrderPrefix, (string objid, byte[] protobuf) =>
                        {
                            DealOrder dealOrder = DealOrder.Parser.ParseFrom(protobuf);
                            Console.WriteLine($"dealOrder({objid}) askOrderId:{dealOrder.AskOrderId}, bidOrderId: {dealOrder.BidOrderId}, collateralTransferId:{dealOrder.CollateralTransferId}");
                        });
                    }
                    else if (command[0].Equals("repaymentOrders", StringComparison.OrdinalIgnoreCase))
                    {
                        Debug.Assert(command.Length == 1);
                        filter(creditCoinNamespace + repaymentOrderPrefix, (string objid, byte[] protobuf) =>
                        {
                            RepaymentOrder repaymentOrder = RepaymentOrder.Parser.ParseFrom(protobuf);
                            Console.WriteLine($"repaymentOrder({objid}) dealId:{repaymentOrder.DealId}, transferId:{repaymentOrder.TransferId}");
                        });
                    }
                    else if (command[0].Equals("sighash", StringComparison.OrdinalIgnoreCase))
                    {
                        Console.WriteLine(sha256(signer.GetPublicKey().ToHexString()));
                    }
                    else if (command[0].Equals("address", StringComparison.OrdinalIgnoreCase))
                    {
                        Debug.Assert(command.Length == 3);
                        var blockchain = command[1].ToLower();
                        var addr       = command[2];
                        filter(creditCoinNamespace + addressPrefix, (string objid, byte[] protobuf) =>
                        {
                            Address address = Address.Parser.ParseFrom(protobuf);
                            if (address.Blockchain == blockchain && address.Address_ == addr)
                            {
                                Console.WriteLine(objid);
                            }
                        });
                    }
                    else if (command[0].Equals("unusedTransfers", StringComparison.OrdinalIgnoreCase))
                    {
                        Debug.Assert(command.Length == 3);
                        var addressId = command[1];
                        var amount    = command[2];
                        var sighash   = sha256(signer.GetPublicKey().ToHexString());

                        Address address = null;
                        filter(creditCoinNamespace + addressPrefix, (string objid, byte[] protobuf) =>
                        {
                            if (objid == addressId)
                            {
                                address = Address.Parser.ParseFrom(protobuf);
                            }
                        });
                        if (address == null)
                        {
                            Console.WriteLine("Invalid command " + command[0]);
                            success = false;
                        }
                        else
                        {
                            filter(creditCoinNamespace + transferPrefix, (string objid, byte[] protobuf) =>
                            {
                                Transfer transfer = Transfer.Parser.ParseFrom(protobuf);
                                if (transfer.Sighash == sighash && transfer.Orderid.Equals(string.Empty) && transfer.Blockchain == address.Blockchain && transfer.Amount == amount)
                                {
                                    Console.WriteLine(objid);
                                }
                            });
                        }
                    }
                    else if (command[0].Equals("matchingOrders", StringComparison.OrdinalIgnoreCase))
                    {
                        Debug.Assert(command.Length == 1);

                        var askOrders = new Dictionary <string, AskOrder>();
                        filter(creditCoinNamespace + askOrderPrefix, (string objid, byte[] protobuf) =>
                        {
                            AskOrder askOrder = AskOrder.Parser.ParseFrom(protobuf);
                            askOrders.Add(objid, askOrder);
                        });
                        var bidOrders = new Dictionary <string, BidOrder>();
                        filter(creditCoinNamespace + bidOrderPrefix, (string objid, byte[] protobuf) =>
                        {
                            BidOrder bidOrder = BidOrder.Parser.ParseFrom(protobuf);
                            bidOrders.Add(objid, bidOrder);
                        });

                        match(signer, askOrders, bidOrders);
                    }
                    else if (command[0].Equals("creditHistory", StringComparison.OrdinalIgnoreCase))
                    {
                        Debug.Assert(command.Length == 2);

                        var fundraiser = command[1].ToLower();

                        filterDeals(null, fundraiser, (string dealAddress, DealOrder dealOrder, AskOrder askOrder, BidOrder bidOrder) =>
                        {
                            Debug.Assert(askOrder == null);
                            var status = dealOrder.CollateralTransferId.Equals(string.Empty) ? "INCOMPLETE" : "COMPLETE";
                            if (!dealOrder.UnlockCollateralDestinationAddressId.Equals(string.Empty))
                            {
                                status = "CLOSED";
                            }
                            else if (!dealOrder.UnlockFundsDestinationAddressId.Equals(string.Empty))
                            {
                                status = "ACTIVE";
                            }
                            Console.WriteLine($"status: {status}, amount:{bidOrder.Amount}, blockchain: {bidOrder.Blockchain}, collateral:{bidOrder.Collateral}");
                        });
                    }
                    else if (command[0].Equals("newDeals", StringComparison.OrdinalIgnoreCase))
                    {
                        Debug.Assert(command.Length == 1);

                        var fundraiser = sha256(signer.GetPublicKey().ToHexString());

                        filterDeals(null, fundraiser, (string dealAddress, DealOrder dealOrder, AskOrder askOrder, BidOrder bidOrder) =>
                        {
                            Debug.Assert(askOrder == null);
                            if (dealOrder.CollateralTransferId.Equals(string.Empty))
                            {
                                Console.WriteLine(dealAddress);
                            }
                        });
                    }
                    else if (command[0].Equals("runningDeals", StringComparison.OrdinalIgnoreCase))
                    {
                        Debug.Assert(command.Length == 1);

                        var investor = sha256(signer.GetPublicKey().ToHexString());

                        var deals = new List <string>();
                        filterDeals(investor, null, (string dealAddress, DealOrder dealOrder, AskOrder askOrder, BidOrder bidOrder) =>
                        {
                            Debug.Assert(bidOrder == null);
                            if (!dealOrder.CollateralTransferId.Equals(string.Empty) && dealOrder.UnlockCollateralDestinationAddressId.Equals(string.Empty))
                            {
                                deals.Add(dealAddress);
                            }
                        });

                        filter(creditCoinNamespace + repaymentOrderPrefix, (string objid, byte[] protobuf) =>
                        {
                            RepaymentOrder repaymentOrder = RepaymentOrder.Parser.ParseFrom(protobuf);
                            if (deals.SingleOrDefault(x => x.Equals(repaymentOrder.DealId)) != null)
                            {
                                deals.Remove(repaymentOrder.DealId);
                            }
                        });

                        foreach (var deal in deals)
                        {
                            Console.WriteLine(deal);
                        }
                    }
                    else if (command[0].Equals("newRepaymentOrders", StringComparison.OrdinalIgnoreCase))
                    {
                        Debug.Assert(command.Length == 1);

                        var fundraiser = sha256(signer.GetPublicKey().ToHexString());

                        var deals = new List <string>();
                        filterDeals(null, fundraiser, (string dealAddress, DealOrder dealOrder, AskOrder askOrder, BidOrder bidOrder) =>
                        {
                            Debug.Assert(askOrder == null);
                            if (!dealOrder.CollateralTransferId.Equals(string.Empty) && dealOrder.UnlockCollateralDestinationAddressId.Equals(string.Empty))
                            {
                                deals.Add(dealAddress);
                            }
                        });

                        filter(creditCoinNamespace + repaymentOrderPrefix, (string objid, byte[] protobuf) =>
                        {
                            RepaymentOrder repaymentOrder = RepaymentOrder.Parser.ParseFrom(protobuf);
                            if (repaymentOrder.TransferId.Equals(string.Empty) && deals.SingleOrDefault(x => x.Equals(repaymentOrder.DealId)) != null)
                            {
                                Console.WriteLine(objid);
                            }
                        });
                    }
                    else
                    {
                        Console.WriteLine("Invalid command " + command[0]);
                        success = false;
                    }

                    if (success)
                    {
                        Console.WriteLine("Success");
                    }
                }
                else if (action.Equals("creditcoin"))
                {
                    string msg;
                    var    tx = txBuilder.BuildTx(command, out msg);
                    if (tx == null)
                    {
                        Debug.Assert(msg != null);
                        Console.WriteLine(msg);
                    }
                    else
                    {
                        Debug.Assert(msg == null);
                        var content = new ByteArrayContent(tx);
                        content.Headers.Add("Content-Type", "application/octet-stream");
                        Console.WriteLine(RpcHelper.CompleteBatch(httpClient, $"{creditcoinUrl}/batches", content));
                    }
                }
                else
                {
                    var loader = new Loader <ICCClientPlugin>();
                    var msgs   = new List <string>();
                    loader.Load(folder, msgs);
                    foreach (var msg in msgs)
                    {
                        Console.WriteLine(msg);
                    }

                    ICCClientPlugin plugin       = loader.Get(action);
                    var             pluginConfig = config.GetSection(action);
                    if (plugin == null)
                    {
                        Console.WriteLine("Error: Unknown action " + action);
                    }
                    else
                    {
                        string msg;
                        bool   done = plugin.Run(pluginConfig, httpClient, txBuilder, settings, folder, creditcoinUrl, command, out inProgress, out msg);
                        if (done)
                        {
                            if (msg == null)
                            {
                                msg = "Success";
                            }
                            Console.WriteLine(msg);
                        }
                        else
                        {
                            Console.WriteLine("Error: " + msg);
                        }
                    }
                }
                if (!inProgress)
                {
                    File.Delete(progress);
                }
            }
            catch (Exception x)
            {
                Console.WriteLine($"Error: unexpected failure - {x.Message}");
            }
        }
Ejemplo n.º 6
0
        public bool Run(IConfiguration cfg, HttpClient httpClient, ITxBuilder txBuilder, Dictionary <string, string> settings, string pluginsFolder, string url, string[] command, out bool inProgress, out string msg)
        {
            Debug.Assert(command != null);
            Debug.Assert(command.Length > 1);
            if (command[0].Equals("registerTransfer", StringComparison.OrdinalIgnoreCase))
            {
                // bitcoin RegisterTransfer registeredSourceId amount sourceTxId

                string progress = Path.Combine(pluginsFolder, $"{name}_progress.txt");

                inProgress = false;

                Debug.Assert(command.Length == 4);
                string registeredSourceId = command[1];
                string amountString       = command[2];
                string sourceTxIdString   = command[3];

                string secret = cfg["secret"];
                if (string.IsNullOrWhiteSpace(secret))
                {
                    msg = "bitcoin.secret is not set";
                    return(false);
                }
                string rpcAddress = cfg["rpc"];
                if (string.IsNullOrWhiteSpace(rpcAddress))
                {
                    msg = "bitcoin.rpc is not set";
                    return(false);
                }

                string credential = cfg["credential"];
                if (string.IsNullOrWhiteSpace(credential))
                {
                    msg = "bitcoin.credential is not set";
                    return(false);
                }

                //TODO disable confirmation count config for release build
                string confirmationsCount = cfg["confirmationsCount"];
                if (string.IsNullOrWhiteSpace(confirmationsCount))
                {
                    msg = "bitcoin.confirmationsCount is not set";
                    return(false);
                }
                int confirmationsExpected = 1;
                if (!int.TryParse(confirmationsCount, out confirmationsExpected))
                {
                    msg = "bitcoin.confirmationsCount is not an int";
                    return(false);
                }

                string feeString = cfg["fee"];
                if (string.IsNullOrWhiteSpace(feeString))
                {
                    msg = "bitcoin.fee is not set";
                    return(false);
                }
                if (!int.TryParse(feeString, out int fee))
                {
                    msg = "bitcoin.fee is not an int";
                    return(false);
                }

                var bitcoinPrivateKey = new BitcoinSecret(secret);
                var network           = bitcoinPrivateKey.Network;
                var rpcClient         = new RPCClient(credential, new Uri(rpcAddress), network);

                string  payTxIdHash;
                uint256 payTxId;
                if (File.Exists(progress))
                {
                    Console.WriteLine("Found unfinished action, retrying...");
                    payTxIdHash = File.ReadAllText(progress);
                    if (!uint256.TryParse(payTxIdHash, out payTxId))
                    {
                        msg = "corrupted progress file";
                        return(false);
                    }
                }
                else
                {
                    var protobuf = RpcHelper.ReadProtobuf(httpClient, $"{url}/state/{registeredSourceId}", out msg);
                    if (protobuf == null)
                    {
                        return(false);
                    }

                    var address = Address.Parser.ParseFrom(protobuf);

                    string destinationAddress;
                    if (!settings.TryGetValue("sawtooth.escrow." + name, out destinationAddress))
                    {
                        msg = "Escrow not found for " + name;
                        return(false);
                    }

                    Money transferAmount;
                    if (!Money.TryParse(amountString, out transferAmount) || transferAmount <= 0)
                    {
                        msg = "Invalid amount";
                        return(false);
                    }

                    if (!address.Blockchain.Equals(name))
                    {
                        msg = $"bitcoin RegisterTransfer can only transfer bitcoins.\nThis source is registered for {address.Blockchain}";
                        return(false);
                    }

                    var sourceAddress = bitcoinPrivateKey.GetAddress();
                    if (!sourceAddress.ToString().Equals(address.Address_, StringComparison.OrdinalIgnoreCase))
                    {
                        msg = "The deal is for a different client";
                        return(false);
                    }

#if RELEASE
                    if (network != NBitcoin.Network.Main)
                    {
                        msg = "bitcoin.secret is not defined for main network";
                        return(false);
                    }
#endif

                    var sourceTxId          = uint256.Parse(sourceTxIdString);
                    var transactionResponse = rpcClient.GetRawTransaction(sourceTxId);

                    //TODO: fix (assuming that the transaction has one output intended for the private key with the exact amount)
                    var      receivedCoins   = transactionResponse.Outputs.AsCoins();
                    OutPoint outPointToSpend = null;
                    TxOut    outTxToSpend    = null;
                    Money    amount          = null;
                    foreach (var coin in receivedCoins)
                    {
                        if (coin.TxOut.ScriptPubKey == bitcoinPrivateKey.ScriptPubKey)
                        {
                            outPointToSpend = coin.Outpoint;
                            outTxToSpend    = coin.TxOut;
                            amount          = (Money)coin.Amount;
                            if (amount.CompareTo(transferAmount + fee * 2) < 0)
                            {
                                msg = $"Invalid transaction - needed: {transferAmount}, has: {amount.ToString()}";
                                return(false);
                            }
                            break;
                        }
                    }
                    if (outPointToSpend == null)
                    {
                        msg = "Invalid transaction - no outputs that the client can spend";
                        return(false);
                    }

                    var addressFrom = BitcoinAddress.Create(address.Address_, network);
                    var addressTo   = BitcoinAddress.Create(destinationAddress, network);
                    var message     = address.Sighash;
                    var bytes       = Encoding.UTF8.GetBytes(message);

                    var bitcoinTransactionBuilder = new TransactionBuilder();

                    var transaction = bitcoinTransactionBuilder
                                      .AddCoins(new Coin(outPointToSpend, outTxToSpend))
                                      .AddKeys(bitcoinPrivateKey)
                                      .Send(addressTo.ScriptPubKey, transferAmount + fee)
                                      .Send(TxNullDataTemplate.Instance.GenerateScriptPubKey(bytes), Money.Zero)
                                      .SendFees(new Money(fee, MoneyUnit.Satoshi))
                                      .SetChange(addressFrom.ScriptPubKey)
                                      .BuildTransaction(true);

                    if (!bitcoinTransactionBuilder.Verify(transaction))
                    {
                        msg = "failed verify transaction";
                        return(false);
                    }

                    try
                    {
                        payTxId = rpcClient.SendRawTransaction(transaction);
                    }
                    catch (RPCException e)
                    {
                        msg = $"failed to broadcast - error: {e.RPCCode}, reason: {e.RPCCodeMessage}";
                        return(false);
                    }

                    if (payTxId == null)
                    {
                        msg = "failed to broadcast - unknown error";
                        return(false);
                    }

                    payTxIdHash = payTxId.ToString();
                    File.WriteAllText(progress, payTxIdHash);
                }

                inProgress = true;
                while (true)
                {
                    var transactionResponse = rpcClient.GetRawTransactionInfo(payTxId);
                    if (transactionResponse != null && transactionResponse.BlockHash != null && transactionResponse.Confirmations >= confirmationsExpected)
                    {
                        break;
                    }

                    Thread.Sleep(1000);
                }

                command = new string[] { command[0], registeredSourceId, amountString, feeString, payTxIdHash, ((network == Network.Main) ? "1" : "0") };
                var tx = txBuilder.BuildTx(command, out msg);
                Debug.Assert(tx != null);
                Debug.Assert(msg == null);

                var content = new ByteArrayContent(tx);
                content.Headers.Add("Content-Type", "application/octet-stream");

                msg = RpcHelper.CompleteBatch(httpClient, $"{url}/batches", content);

                File.Delete(progress);
                inProgress = false;

                return(true);
            }
            else
            {
                inProgress = false;
                msg        = "Unknown command: " + command[0];
                return(false);
            }
        }
Ejemplo n.º 7
0
        public bool Run(IConfiguration cfg, string signerHexStr, string[] command, out string msg)
        {
            Debug.Assert(command != null);
            Debug.Assert(command.Length > 0);
            if (command[0].Equals("verify"))
            {
                Debug.Assert(command.Length == 7);
                string txId = command[1];
                string destinationAddressString = command[2];
                string destinationAmount        = command[3];
                string sighash             = command[4];
                string sourceAddressString = command[5];
                string networkId           = command[6];

                // TODO disable confirmation count config for release build
                string confirmationsCount = cfg["confirmationsCount"];
                if (string.IsNullOrWhiteSpace(confirmationsCount))
                {
                    msg = "ethereum.confirmationsCount is not set";

                    return(false);
                }
                if (!int.TryParse(confirmationsCount, out int confirmationsExpected))
                {
                    msg = "ethereum.confirmationsCount is not an int";

                    return(false);
                }

                string rpcUrl = cfg["rpc"];
                if (string.IsNullOrWhiteSpace(rpcUrl))
                {
                    msg = "ethereum.rpc is not set";

                    return(false);
                }

                var web3 = new Nethereum.Web3.Web3(rpcUrl);

                var tx            = web3.Eth.Transactions.GetTransactionByHash.SendRequestAsync(txId).Result;
                int confirmations = 0;
                if (tx.BlockNumber != null)
                {
                    var blockNumber = web3.Eth.Blocks.GetBlockNumber.SendRequestAsync().Result;
                    confirmations = (int)(blockNumber.Value - tx.BlockNumber.Value);
                }

                if (confirmations < confirmationsExpected)
                {
                    msg = "Invalid transaction: not enough confirmations";
                    return(false);
                }

                if (!sourceAddressString.Equals(tx.From, System.StringComparison.OrdinalIgnoreCase))
                {
                    msg = "Invalid transaction: wrong sourceAddressString";
                    return(false);
                }

                if (networkId.Equals("creditcoin"))
                {
                    string creditcoinContract = cfg["creditcoinContract"];

                    if (string.IsNullOrWhiteSpace(creditcoinContract))
                    {
                        msg = "ethereum.creditcoinContract is not set";

                        return(false);
                    }

                    string creditcoinContractAbi = cfg["creditcoinContractAbi"];
                    if (string.IsNullOrWhiteSpace(creditcoinContractAbi))
                    {
                        msg = "ethereum.creditcoinContractAbi is not set";

                        return(false);
                    }

                    var contract = web3.Eth.GetContract(creditcoinContractAbi, creditcoinContract);
                    var burn     = contract.GetFunction("exchange");
                    var inputs   = burn.DecodeInput(tx.Input);
                    Debug.Assert(inputs.Count == 2);
                    var value = inputs[0].Result.ToString();
                    if (destinationAmount != value)
                    {
                        msg = "Invalid transaction: wrong amount";
                        return(false);
                    }

                    var tag = inputs[1].Result.ToString();
                    if (!tag.Equals(sighash))
                    {
                        msg = "Invalid transaction: wrong sighash";
                        return(false);
                    }
                }
                else
                {
                    if (destinationAmount != tx.Value.Value.ToString())
                    {
                        msg = "Invalid transaction: wrong amount";
                        return(false);
                    }

                    if (tx.Input == null)
                    {
                        msg = "Invalid transaction: expecting data";
                        return(false);
                    }

                    if (!sighash.StartsWith("0x"))
                    {
                        sighash = "0x" + sighash;
                    }
                    if (!tx.Input.Equals(sighash, System.StringComparison.OrdinalIgnoreCase))
                    {
                        msg = "Invalid transaction: wrong sighash";
                        return(false);
                    }

                    if (!tx.To.Equals(destinationAddressString, System.StringComparison.OrdinalIgnoreCase))
                    {
                        msg = "Invalid transaction: wrong destinationAddressString";
                        return(false);
                    }
                }

                msg = null;
                return(true);
            }
            else if (command[0].Equals("unlock"))
            {
                var ccSigner  = new Signer(RpcHelper.HexToBytes(signerHexStr));
                var txBuilder = new TxBuilder(ccSigner);

                Debug.Assert(command.Length == 5);
                string kind = command[1];
                string addressFromString = command[2];

                HttpClient httpClient = new HttpClient();

                string txFrom;
                string amountString;
                string feeString;
                string addressToString;
                string networkId;

                string ccCommand;

                if (kind.Equals("funds"))
                {
                    string dealOrderId            = command[3];
                    string addressToUnlockFundsTo = command[4];

                    var protobuf = RpcHelper.ReadProtobuf(httpClient, $"{creditcoinUrl}/state/{dealOrderId}", out msg);
                    if (protobuf == null)
                    {
                        return(false);
                    }
                    var dealOrder = DealOrder.Parser.ParseFrom(protobuf);
                    protobuf = RpcHelper.ReadProtobuf(httpClient, $"{creditcoinUrl}/state/{dealOrder.AskOrderId}", out msg);
                    if (protobuf == null)
                    {
                        return(false);
                    }
                    var askOrder = AskOrder.Parser.ParseFrom(protobuf);
                    protobuf = RpcHelper.ReadProtobuf(httpClient, $"{creditcoinUrl}/state/{askOrder.TransferId}", out msg);
                    if (protobuf == null)
                    {
                        return(false);
                    }
                    var transfer = Transfer.Parser.ParseFrom(protobuf);
                    protobuf = RpcHelper.ReadProtobuf(httpClient, $"{creditcoinUrl}/state/{addressToUnlockFundsTo}", out msg);
                    if (protobuf == null)
                    {
                        return(false);
                    }
                    var address = Address.Parser.ParseFrom(protobuf);
                    if (!askOrder.Sighash.Equals(address.Sighash))
                    {
                        msg = "The address doesn't match the ask order";
                        return(false);
                    }
                    txFrom          = transfer.Txid;
                    amountString    = transfer.Amount;
                    feeString       = transfer.Fee;
                    addressToString = address.Address_;
                    networkId       = transfer.Network;
                    ccCommand       = "UnlockFunds";
                }
                else if (kind.Equals("collateral"))
                {
                    string repaymentOrderId             = command[3];
                    string addressToUnlockCollateralsTo = command[4];

                    var protobuf = RpcHelper.ReadProtobuf(httpClient, $"{creditcoinUrl}/state/{repaymentOrderId}", out msg);
                    if (protobuf == null)
                    {
                        return(false);
                    }
                    var repaymentOrder = RepaymentOrder.Parser.ParseFrom(protobuf);
                    protobuf = RpcHelper.ReadProtobuf(httpClient, $"{creditcoinUrl}/state/{repaymentOrder.DealId}", out msg);
                    if (protobuf == null)
                    {
                        return(false);
                    }
                    var dealOrder = DealOrder.Parser.ParseFrom(protobuf);
                    protobuf = RpcHelper.ReadProtobuf(httpClient, $"{creditcoinUrl}/state/{dealOrder.AskOrderId}", out msg);
                    if (protobuf == null)
                    {
                        return(false);
                    }
                    var askOrder = AskOrder.Parser.ParseFrom(protobuf);
                    protobuf = RpcHelper.ReadProtobuf(httpClient, $"{creditcoinUrl}/state/{askOrder.TransferId}", out msg);
                    if (protobuf == null)
                    {
                        return(false);
                    }
                    var transfer = Transfer.Parser.ParseFrom(protobuf);
                    protobuf = RpcHelper.ReadProtobuf(httpClient, $"{creditcoinUrl}/state/{addressToUnlockCollateralsTo}", out msg);
                    if (protobuf == null)
                    {
                        return(false);
                    }
                    var address = Address.Parser.ParseFrom(protobuf);
                    if (!askOrder.Sighash.Equals(address.Sighash))
                    {
                        msg = "The address doesn't match the ask order";
                        return(false);
                    }
                    txFrom          = transfer.Txid;
                    amountString    = transfer.Amount;
                    feeString       = transfer.Fee;
                    addressToString = address.Address_;
                    networkId       = transfer.Network;
                    ccCommand       = "UnlockCollateral";
                }
                else
                {
                    msg = "unknown unlock kind";
                    return(false);
                }

                string secret = cfg["secret"];
                if (string.IsNullOrWhiteSpace(secret))
                {
                    msg = "ethereum.secret is not set";
                    return(false);
                }
                var ethereumPrivateKey = secret;

                // TODO disable confirmation count config for release build
                string confirmationsCount = cfg["confirmationsCount"];
                if (string.IsNullOrWhiteSpace(confirmationsCount))
                {
                    msg = "ethereum.confirmationsCount is not set";
                    return(false);
                }
                if (!int.TryParse(confirmationsCount, out int confirmationsExpected))
                {
                    msg = "ethereum.confirmationsCount is not an int";
                    return(false);
                }

                string rpcUrl = cfg["rpc"];
                if (string.IsNullOrWhiteSpace(rpcUrl))
                {
                    msg = "ethereum.rpc is not set";
                    return(false);
                }

                BigInteger fee;
                if (!BigInteger.TryParse(feeString, out fee))
                {
                    msg = "Invalid progress data";
                    return(false);
                }

                var web3 = new Nethereum.Web3.Web3(rpcUrl);

                BigInteger transferAmount;
                if (!BigInteger.TryParse(amountString, out transferAmount) || transferAmount <= 0)
                {
                    msg = "Invalid amount";
                    return(false);
                }

                string sourceAddress = EthECKey.GetPublicAddress(ethereumPrivateKey);

                if (!sourceAddress.Equals(addressFromString, StringComparison.OrdinalIgnoreCase))
                {
                    msg = "The deal is for a different client";
                    return(false);
                }

                var txCount = web3.Eth.Transactions.GetTransactionCount.SendRequestAsync(sourceAddress).Result;
                TransactionSigner signer = new TransactionSigner();
                var    gasLimit          = web3.Eth.Transactions.EstimateGas.SendRequestAsync(new Nethereum.RPC.Eth.DTOs.CallInput(string.Empty, addressToString, new Nethereum.Hex.HexTypes.HexBigInteger(transferAmount))).Result;
                var    gasPrice          = web3.Eth.GasPrice.SendRequestAsync().Result;
                string txRaw             = signer.SignTransaction(ethereumPrivateKey, addressToString, transferAmount + fee, txCount, gasPrice, gasLimit, string.Empty);
                string payTxId           = web3.Eth.Transactions.SendRawTransaction.SendRequestAsync("0x" + txRaw).Result;

                while (true)
                {
                    var receipt = web3.Eth.TransactionManager.TransactionReceiptService.PollForReceiptAsync(payTxId).Result;
                    if (receipt.BlockNumber != null)
                    {
                        var blockNumber = web3.Eth.Blocks.GetBlockNumber.SendRequestAsync().Result;
                        if (blockNumber.Value - receipt.BlockNumber.Value >= confirmationsExpected)
                        {
                            break;
                        }
                    }
                    Thread.Sleep(1000);
                }

                command = new string[] { ccCommand, command[3], command[4] };
                var tx = txBuilder.BuildTx(command, out msg);
                Debug.Assert(tx != null);
                Debug.Assert(msg == null);
                var content = new ByteArrayContent(tx);
                content.Headers.Add("Content-Type", "application/octet-stream");
                msg = RpcHelper.CompleteBatch(httpClient, $"{creditcoinUrl}/batches", content);
            }

            msg = "Unknown command: " + command[0];
            return(false);
        }
Ejemplo n.º 8
0
        public bool Run(bool txid, IConfiguration cfg, HttpClient httpClient, ITxBuilder txBuilder, Dictionary <string, string> settings, string progressId, string pluginsFolder, string url, string[] command, out bool inProgress, out string msg)
        {
            Debug.Assert(command != null);
            if (command.Length < 2)
            {
                inProgress = false;
                msg        = "invalid parameter count";
                return(false);
            }

            if (command[0].Equals("registerTransfer", StringComparison.OrdinalIgnoreCase))
            {
                // bitcoin RegisterTransfer srcAddressId dstAddressId orderId amount sourceTxId

                string progress = Path.Combine(pluginsFolder, $"{name}_progress{progressId}.txt");

                inProgress = false;

                if (command.Length != 4)
                {
                    msg = "invalid parameter count";
                    return(false);
                }

                string gainString       = command[1];
                string orderId          = command[2];
                string sourceTxIdString = command[3];

                string secret = cfg["secret"];
                if (string.IsNullOrWhiteSpace(secret))
                {
                    msg = "bitcoin.secret is not set";
                    return(false);
                }
                string rpcAddress = cfg["rpc"];
                if (string.IsNullOrWhiteSpace(rpcAddress))
                {
                    msg = "bitcoin.rpc is not set";
                    return(false);
                }

                string credential = cfg["credential"];
                if (string.IsNullOrWhiteSpace(credential))
                {
                    msg = "bitcoin.credential is not set";
                    return(false);
                }

#if DEBUG
                string confirmationsCount = cfg["confirmationsCount"];
                if (int.TryParse(confirmationsCount, out int parsedCount))
                {
                    mConfirmationsExpected = parsedCount;
                }
#endif

                string feeString = cfg["fee"];
                if (string.IsNullOrWhiteSpace(feeString))
                {
                    msg = "bitcoin.fee is not set";
                    return(false);
                }
                if (!int.TryParse(feeString, out int fee))
                {
                    msg = "bitcoin.fee is not an int";
                    return(false);
                }

                var bitcoinPrivateKey = new BitcoinSecret(secret);
                var network           = bitcoinPrivateKey.Network;
                var rpcClient         = new RPCClient(credential, new Uri(rpcAddress), network);

                string srcAddressId;
                string dstAddressId;
                string amountString;

                var protobuf = RpcHelper.ReadProtobuf(httpClient, $"{url}/state/{orderId}", out msg);
                if (protobuf == null)
                {
                    msg = "failed to extract address data through RPC";
                    return(false);
                }
                if (orderId.StartsWith(RpcHelper.creditCoinNamespace + RpcHelper.dealOrderPrefix))
                {
                    var dealOrder = DealOrder.Parser.ParseFrom(protobuf);
                    if (gainString.Equals("0"))
                    {
                        srcAddressId = dealOrder.SrcAddress;
                        dstAddressId = dealOrder.DstAddress;
                    }
                    else
                    {
                        dstAddressId = dealOrder.SrcAddress;
                        srcAddressId = dealOrder.DstAddress;
                    }
                    amountString = dealOrder.Amount;
                }
                else if (orderId.StartsWith(RpcHelper.creditCoinNamespace + RpcHelper.repaymentOrderPrefix))
                {
                    var repaymentOrder = RepaymentOrder.Parser.ParseFrom(protobuf);
                    if (gainString.Equals("0"))
                    {
                        srcAddressId = repaymentOrder.SrcAddress;
                        dstAddressId = repaymentOrder.DstAddress;
                    }
                    else
                    {
                        dstAddressId = repaymentOrder.SrcAddress;
                        srcAddressId = repaymentOrder.DstAddress;
                    }
                    amountString = repaymentOrder.Amount;
                }
                else
                {
                    msg = "unexpected referred order";
                    return(false);
                }

                string  payTxIdHash;
                uint256 payTxId;
                if (File.Exists(progress))
                {
                    Console.WriteLine("Found unfinished action, retrying...");
                    payTxIdHash = File.ReadAllText(progress);
                    if (!uint256.TryParse(payTxIdHash, out payTxId))
                    {
                        msg = "corrupted progress file";
                        return(false);
                    }
                }
                else
                {
                    protobuf = RpcHelper.ReadProtobuf(httpClient, $"{url}/state/{srcAddressId}", out msg);
                    if (protobuf == null)
                    {
                        msg = "failed to extract address data through RPC";
                        return(false);
                    }

                    var srcAddress = Address.Parser.ParseFrom(protobuf);

                    protobuf = RpcHelper.ReadProtobuf(httpClient, $"{url}/state/{dstAddressId}", out msg);
                    if (protobuf == null)
                    {
                        msg = "failed to extract address data through RPC";
                        return(false);
                    }

                    var dstAddress = Address.Parser.ParseFrom(protobuf);

                    long transferAmount;
                    if (!long.TryParse(amountString, out transferAmount) || transferAmount <= 0)
                    {
                        msg = "Invalid amount";
                        return(false);
                    }
                    long gain;
                    if (!long.TryParse(gainString, out gain))
                    {
                        msg = "Invalid amount";
                        return(false);
                    }
                    if (transferAmount + gain < transferAmount)
                    {
                        msg = "Overflow";
                        return(false);
                    }

                    transferAmount = transferAmount + gain;

                    if (transferAmount < 0)
                    {
                        msg = "Invalid amount";
                        return(false);
                    }

                    if (transferAmount + fee < transferAmount)
                    {
                        msg = "Overflow";
                        return(false);
                    }

                    if (!srcAddress.Blockchain.Equals(name) || !dstAddress.Blockchain.Equals(name))
                    {
                        msg = $"bitcoin RegisterTransfer can only transfer bitcoins.\nThis source is registered for {srcAddress.Blockchain} and destination for {dstAddress.Blockchain}";
                        return(false);
                    }

                    var sourceAddress = bitcoinPrivateKey.GetAddress();
                    if (!sourceAddress.ToString().Equals(srcAddress.Value, StringComparison.OrdinalIgnoreCase))
                    {
                        msg = "The deal is for a different client";
                        return(false);
                    }

#if RELEASE
                    if (network != NBitcoin.Network.Main)
                    {
                        msg = "bitcoin.secret is not defined for main network";
                        return(false);
                    }
#endif

                    var sourceTxId          = uint256.Parse(sourceTxIdString);
                    var transactionResponse = rpcClient.GetRawTransaction(sourceTxId);

                    //TODO: fix (assuming that the transaction has one output intended for the private key with the exact amount)
                    var      receivedCoins   = transactionResponse.Outputs.AsCoins();
                    OutPoint outPointToSpend = null;
                    TxOut    outTxToSpend    = null;
                    foreach (var coin in receivedCoins)
                    {
                        if (coin.TxOut.ScriptPubKey == bitcoinPrivateKey.ScriptPubKey)
                        {
                            outPointToSpend = coin.Outpoint;
                            outTxToSpend    = coin.TxOut;
                            long amount = coin.Amount.Satoshi;
                            if (amount < transferAmount + fee)
                            {
                                msg = $"Invalid transaction - needed: {transferAmount} + {fee}, has: {amount.ToString()}";
                                return(false);
                            }
                            break;
                        }
                    }
                    if (outPointToSpend == null)
                    {
                        msg = "Invalid transaction - no outputs that the client can spend";
                        return(false);
                    }

                    var addressFrom = BitcoinAddress.Create(srcAddress.Value, network);
                    var addressTo   = BitcoinAddress.Create(dstAddress.Value, network);
                    var message     = orderId;
                    var bytes       = Encoding.UTF8.GetBytes(message);

                    var bitcoinTransactionBuilder = new TransactionBuilder();

                    var transaction = bitcoinTransactionBuilder
                                      .AddCoins(new Coin(outPointToSpend, outTxToSpend))
                                      .AddKeys(bitcoinPrivateKey)
                                      .Send(addressTo.ScriptPubKey, transferAmount)
                                      .Send(TxNullDataTemplate.Instance.GenerateScriptPubKey(bytes), Money.Zero)
                                      .SendFees(new Money(fee, MoneyUnit.Satoshi))
                                      .SetChange(addressFrom.ScriptPubKey)
                                      .BuildTransaction(true);

                    if (!bitcoinTransactionBuilder.Verify(transaction))
                    {
                        msg = "failed verify transaction";
                        return(false);
                    }

                    try
                    {
                        payTxId = rpcClient.SendRawTransaction(transaction);
                    }
                    catch (RPCException e)
                    {
                        msg = $"failed to broadcast - error: {e.RPCCode}, reason: {e.RPCCodeMessage}";
                        return(false);
                    }

                    if (payTxId == null)
                    {
                        msg = "failed to broadcast - unknown error";
                        return(false);
                    }

                    payTxIdHash = payTxId.ToString();
                    File.WriteAllText(progress, payTxIdHash);
                }

                inProgress = true;
                while (true)
                {
                    var transactionResponse = rpcClient.GetRawTransactionInfo(payTxId);
                    if (transactionResponse != null && transactionResponse.BlockHash != null && transactionResponse.Confirmations >= mConfirmationsExpected)
                    {
                        break;
                    }

                    Thread.Sleep(1000);
                }

                command = new string[] { command[0], gainString, orderId, payTxIdHash };
                var tx = txBuilder.BuildTx(command, out msg);
                Debug.Assert(tx != null);
                Debug.Assert(msg == null);

                var content = new ByteArrayContent(tx);
                content.Headers.Add("Content-Type", "application/octet-stream");

                msg = RpcHelper.CompleteBatch(httpClient, url, "batches", content, txid);

                File.Delete(progress);
                inProgress = false;

                return(true);
            }
            else
            {
                inProgress = false;
                msg        = "Unknown command: " + command[0];
                return(false);
            }
        }