Esempio 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}");
            }
        }
Esempio n. 2
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}");
            }
        }