Beispiel #1
0
        private async Task <bool> GetAccount(Message m) // verify that user is a part of the master chat
        {
            var text    = m.Text?.Trim();
            var user    = m.From;
            var args    = text.Split(" ");
            var cliArgs = CLIHelper.GetNamedArguments(args);

            var token    = args.TryGetValueOrDefault(2)?.TrimStartSingle("$"); // $ATOM
            var baseName = token?.ToUpper();

            if (token.IsNullOrEmpty())
            {
                token = cliArgs.GetValueOrDefault("token");
            }

            token = token?.ToLower();
            var        propsVar = Environment.GetEnvironmentVariable($"{token?.ToLower()}_PROPS");
            TokenProps props;

            if (!propsVar.IsNullOrEmpty())
            {
                props = propsVar.JsonDeserialize <TokenProps>();
            }
            else
            {
                props = new TokenProps();
            }

            props.name = token;

            var account = args.TryGetValueOrDefault(3)?.Trim()?.ToLower();

            props.memo = cliArgs.GetValueOrDefault("memo") ?? "";
            if (!props.memo.IsNullOrEmpty() && props.memo.Length > 256)
            {
                await _TBC.SendTextMessageAsync(text : $"*memo* can't have more then 256 characters, but was {props.memo.Length} characters.\nCheck description to see allowed parameters.",
                                                chatId : new ChatId(m.Chat.Id), replyToMessageId : m.MessageId, parseMode : Telegram.Bot.Types.Enums.ParseMode.Markdown);

                return(true);
            }

            if (props.index < 0 || props.index > 99999999)
            {
                props.index = (cliArgs.GetValueOrDefault("index")).ToIntOrDefault(BitcoinEx.GetCoinIndex(baseName));
            }

            if (props.index < 0 || props.index > 99999999) // vlaidate coin index
            {
                await _TBC.SendTextMessageAsync(text : $"*index* flag `{props.index}` is invalid.\nCheck description to see allowed parameters.", chatId : new ChatId(m.Chat.Id), replyToMessageId : m.MessageId, parseMode : Telegram.Bot.Types.Enums.ParseMode.Markdown);

                return(true);
            }

            if (props.prefix.IsNullOrWhitespace())
            {
                props.prefix = cliArgs.GetValueOrDefault("prefix");
            }

            if (props.prefix.IsNullOrWhitespace()) // vlaidate address prefix
            {
                await _TBC.SendTextMessageAsync(text : $"*prefix* flag `{props.address ?? "undefined"}` is invalid.\nCheck description to see allowed parameters.", chatId : new ChatId(m.Chat.Id), replyToMessageId : m.MessageId, parseMode : Telegram.Bot.Types.Enums.ParseMode.Markdown);

                return(true);
            }

            var ua = await GetUserAccount(user.Id);

            var acc = new AsmodatStandard.Cryptography.Cosmos.Account(props.prefix, (uint)props.index);

            acc.InitializeWithMnemonic(ua.GetSecret());
            var cosmosAdress = acc.CosmosAddress;

            if (account.EquailsAny(StringComparison.OrdinalIgnoreCase, "address", "account", "acc", "addr", "a", "deposit", "d", "adr", "adres", "adress", "addres"))
            {
                await _TBC.SendTextMessageAsync(chatId : m.Chat,
                                                $"{user.GetMarkDownUsername()} Public Address Is:\n`{cosmosAdress}`",
                                                replyToMessageId : m.MessageId,
                                                parseMode : Telegram.Bot.Types.Enums.ParseMode.Markdown);

                return(true);
            }
            else if (account.EquailsAny(StringComparison.OrdinalIgnoreCase, "balance"))
            {
                var lcd = cliArgs.GetValueOrDefault("lcd");
                if (!lcd.IsNullOrWhitespace())
                {
                    props.lcd = lcd;
                }

                var       client = new CosmosHub(lcd: props.lcd, timeoutSeconds: _cosmosHubClientTimeout);
                node_info nodeInfo;
                try
                {
                    nodeInfo = await client.GetNodeInfo();
                }
                catch
                {
                    await _TBC.SendTextMessageAsync(text : $"*lcd* flag `{props.lcd ?? "undefined"}` is invalid or node can NOT be reached.\nCheck description to see allowed parameters.", chatId : new ChatId(m.Chat.Id), replyToMessageId : m.MessageId, parseMode : Telegram.Bot.Types.Enums.ParseMode.Markdown);

                    return(true);
                }

                var network = cliArgs.GetValueOrDefault("network");
                if (network.IsNullOrWhitespace())
                {
                    network = nodeInfo?.network;
                }
                if (network.IsNullOrWhitespace())
                {
                    network = props.network;
                }
                props.network = network;

                if (props.network.IsNullOrWhitespace() || props.network.Length <= 1)
                {
                    await _TBC.SendTextMessageAsync(text : $"*network* flag `{props.network ?? "undefined"}` is invalid.\nCheck description to see allowed parameters.", chatId : new ChatId(m.Chat.Id), replyToMessageId : m.MessageId, parseMode : Telegram.Bot.Types.Enums.ParseMode.Markdown);

                    return(true);
                }

                props.denom = cliArgs.GetValueOrDefault("denom") ?? props.denom ?? props.name.ToLower();
                var fromAccountInfo = await client.GetAccount(account : cosmosAdress);

                var fromAccountBalance = fromAccountInfo?.coins?.FirstOrDefault(x => x?.denom?.ToLower() == props.denom.ToLower());

                await _TBC.SendTextMessageAsync(chatId : m.Chat,
                                                $"Address: `{cosmosAdress}`\n" +
                                                $"Amount: `{fromAccountBalance?.amount ?? "0"} {fromAccountBalance?.denom ?? props.denom}`\n" +
                                                $"Network: `{props.network}`",
                                                replyToMessageId : m.MessageId,
                                                parseMode : Telegram.Bot.Types.Enums.ParseMode.Markdown);

                return(true);
            }
            else
            {
                await _TBC.SendTextMessageAsync(text : $"Command `{account ?? "undefined"}` is invalid.\nCheck description to see allowed parameters.", chatId : new ChatId(m.Chat.Id), replyToMessageId : m.MessageId, parseMode : Telegram.Bot.Types.Enums.ParseMode.Markdown);

                return(false);
            }
        }
        private async Task <TokenProps> GetTokenFaucetProps(Message m) // verify that user is a part of the master chat
        {
            var text    = m.Text?.Trim();
            var args    = text.Split(" ");
            var cliArgs = CLIHelper.GetNamedArguments(args);
            var user    = m.From;

            var props    = GetTokenPropsFromTextCommand(text);
            var baseName = props?.name?.ToUpper();

            if (props.name.IsNullOrWhitespace() || props.name.Length < 2 || props.name.Length > 10) // validate token name
            {
                await _TBC.SendTextMessageAsync(text : $"Token name `${props.name ?? "undefined"}` is invalid.\nCheck description to see allowed parameters.", chatId : new ChatId(m.Chat.Id), replyToMessageId : m.MessageId, parseMode : Telegram.Bot.Types.Enums.ParseMode.Markdown);

                return(null);
            }

            if (props.index < 0 || props.index > 99999999)
            {
                props.index = (cliArgs.GetValueOrDefault("index")).ToIntOrDefault(BitcoinEx.GetCoinIndex(baseName));
            }

            if (props.index < 0 || props.index > 99999999) // vlaidate coin index
            {
                await _TBC.SendTextMessageAsync(text : $"*index* flag `{props.index}` is invalid.\nCheck description to see allowed parameters.", chatId : new ChatId(m.Chat.Id), replyToMessageId : m.MessageId, parseMode : Telegram.Bot.Types.Enums.ParseMode.Markdown);

                return(null);
            }

            props.address = cliArgs.GetValueOrDefault("address") ?? args.FirstOrDefault(x => Bech32Ex.CanDecode(x));

            if (Bech32Ex.TryDecode(props.address, out var hrp, out var addrBytes))
            {
                props.prefix = hrp;
            }

            props.prefix = props.prefix ?? cliArgs.GetValueOrDefault("prefix");

            if (props.prefix.IsNullOrWhitespace())
            {
                await _TBC.SendTextMessageAsync(text : $"*prefix* ({props.prefix ?? "undefined"}) or *address* ({props.address ?? "undefined"}) flag was not defined.\nCheck description to see allowed parameters.",
                                                chatId : new ChatId(m.Chat.Id), replyToMessageId : m.MessageId, parseMode : Telegram.Bot.Types.Enums.ParseMode.Markdown);

                return(null);
            }

            if (!Bech32Ex.CanDecode(props.address))
            {
                var ua = await GetUserAccount(user, createNewAcount : false);

                var acc = new AsmodatStandard.Cryptography.Cosmos.Account(props.prefix, (uint)props.index);
                acc.InitializeWithMnemonic(ua.GetSecret());
                props.address = acc.CosmosAddress;
            }

            var       client = new CosmosHub(lcd: props.lcd, timeoutSeconds: _cosmosHubClientTimeout);
            node_info nodeInfo;

            try
            {
                nodeInfo = await client.GetNodeInfo();
            }
            catch
            {
                await _TBC.SendTextMessageAsync(text : $"*lcd* flag `{props.lcd ?? "undefined"}` is invalid or node can NOT be reached.\nCheck description to see allowed parameters.", chatId : new ChatId(m.Chat.Id), replyToMessageId : m.MessageId, parseMode : Telegram.Bot.Types.Enums.ParseMode.Markdown);

                return(null);
            }

            var network = cliArgs.GetValueOrDefault("network");

            if (network.IsNullOrWhitespace())
            {
                network = nodeInfo?.network;
            }
            if (network.IsNullOrWhitespace())
            {
                network = props.network;
            }
            props.network = network;

            if (props.network.IsNullOrWhitespace() || props.network.Length <= 1)
            {
                await _TBC.SendTextMessageAsync(text : $"*network* flag `{props.network ?? "undefined"}` is invalid.\nCheck description to see allowed parameters.", chatId : new ChatId(m.Chat.Id), replyToMessageId : m.MessageId, parseMode : Telegram.Bot.Types.Enums.ParseMode.Markdown);

                return(null);
            }

            props.denom = cliArgs.GetValueOrDefault("denom") ?? props.denom ?? props.name.ToLower();
            return(props);
        }
        private async Task TransferProcessAcceptCallback(CallbackQuery c, long from, string to)
        {
            var text     = c?.Message?.ReplyToMessage?.Text;
            var fromUser = c?.Message?.ReplyToMessage?.From;
            var toUser   = c?.Message?.ReplyToMessage?.ReplyToMessage?.From;
            var chat     = c.Message.Chat;
            var replyId  = c?.Message?.ReplyToMessage?.MessageId ?? 0;

            if (c?.Message?.ReplyToMessage?.EditDate != null)
            {
                await _TBC.SendTextMessageAsync(chatId : chat, $"Transaction will *NOT* be processed, message was edited by the author.",
                                                replyToMessageId : replyId,
                                                parseMode : ParseMode.Markdown);

                return;
            }

            var args    = text.Split(" ");
            var cliArgs = CLIHelper.GetNamedArguments(args);

            var props = await GetTokenTransferProps(chat, replyId, text, from, to);

            if (props == null) //failed to read properties
            {
                return;
            }


            var fromUA = await GetUserAccount(from, createNewAcount : false);

            var acc = new AsmodatStandard.Cryptography.Cosmos.Account(props.prefix, (uint)props.index);

            acc.InitializeWithMnemonic(fromUA.GetSecret());

            Account    fromAccountInfo    = null;
            Token      fromAccountBalance = null;
            var        notEnoughFunds     = false;
            var        client             = new CosmosHub(lcd: props.lcd, timeoutSeconds: _cosmosHubClientTimeout);
            var        sequenceKey        = $"{props.origin}-{props.network}";
            BigInteger fromBalance        = 0;

            try
            {
                await _txLocker.Lock(async() =>
                {
                    fromAccountInfo    = await client.GetAccount(account: props.origin);
                    fromAccountBalance = fromAccountInfo?.coins?.FirstOrDefault(x => x?.denom?.ToLower() == props.denom.ToLower());
                    fromBalance        = (fromAccountBalance?.amount).ToBigIntOrDefault(0);

                    if (fromBalance < (props.amount + props.fees))
                    {
                        notEnoughFunds = true;
                        return;
                    }

                    var sequence             = fromAccountInfo.sequence.ToLongOrDefault();
                    var oldSeque             = sequences.GetValueOrDefault(sequenceKey, -1);
                    sequences[sequenceKey]   = Math.Max(sequence, oldSeque + 1);
                    fromAccountInfo.sequence = sequences[sequenceKey].ToString();
                });
            }
            catch (Exception ex)
            {
                _logger.Log($"[ERROR] => Filed to fetch '{props.denom ?? "undefined"}' balance of '{props.origin ?? "undefined"}' from '{props.lcd ?? "undefined"}': '{ex.JsonSerializeAsPrettyException(Newtonsoft.Json.Formatting.Indented)}'");
                await _TBC.SendTextMessageAsync(text : $"Your account balance is `0 {props.denom}` or lcd '{props.lcd ?? "undefined"}' property is invalid.",
                                                chatId : chat, replyToMessageId : replyId, parseMode : ParseMode.Markdown);

                return;
            }

            if (notEnoughFunds)
            {
                await _TBC.SendTextMessageAsync(text : $"Transaction including fees requires `{props.amount + props.fees} {props.denom}` but your account balance is `{fromBalance} {props.denom}`",
                                                chatId : chat, replyToMessageId : replyId, parseMode : ParseMode.Markdown);

                return;
            }

            var doc = await client.CreateMsgSend(
                account : fromAccountInfo,
                to : props.address,
                amount : props.amount,
                denom : fromAccountBalance?.denom ?? props.denom,
                fees : props.fees,
                gas : props.gas,
                memo : props.memo ?? "Kira Interchain Wallet - Join us at https://t.me/kirainterex");

            var tx         = doc.CreateTx(acc);
            var txResponse = await client.PostTx(tx);

            var toUserId = to.ToIntOrDefault(0);

            if (toUser == null && toUserId != 0)
            {
                toUser = await _TBC.TryGetChatMember(chat, toUserId);
            }

            var fromUserName = $"{fromUser.GetMarkDownUsername()}";
            var toUserName   = $"{toUser?.GetMarkDownUsername() ?? $"`{props.address}`"}";
            var sentAmount   = $"{props.amount} {fromAccountBalance?.denom ?? props.denom}";

            var statusMsg   = "";
            var debugLog    = $"\nDebug Log: {txResponse?.raw_log ?? txResponse.error}";
            var fromMsg     = $"\nFrom: {fromUserName}";
            var toMsg       = $"\nTo: {toUserName}";
            var amountMsg   = $"\nAmount: `{sentAmount}`\n";
            var networkMsg  = $"\nNetwork Id: `{props.network ?? "undefined"}`";
            var sequenceMsg = $"\nSequence: `{fromAccountInfo.sequence}`";
            var hashMsg     = $"\nTx Hash: `{txResponse?.txhash}`";

            if (txResponse == null || txResponse.height.ToLongOrDefault(0) <= 0 || !txResponse.error.IsNullOrWhitespace())
            {
                statusMsg = $"*Failed* 😢 Action ❌: `tx send`\n" + fromMsg + toMsg + amountMsg;
                debugLog  = $"\nDebug Log: {txResponse?.raw_log}";
                sequences[sequenceKey] = sequences.GetValueOrDefault(sequenceKey, -1) - 1;
            }
            else
            {
                statusMsg = $"*SUCCESS* 😄 {fromUserName} sent `{sentAmount}` 💸 to {toUserName} \n";

                if (!text.Contains("--debug"))
                {
                    debugLog    = "";
                    sequenceMsg = "";
                }
            }

            await _TBC.SendTextMessageAsync(chatId : chat,
                                            statusMsg +
                                            debugLog +
                                            networkMsg +
                                            sequenceMsg +
                                            hashMsg,
                                            replyToMessageId : replyId,
                                            parseMode : ParseMode.Markdown);
        }
Beispiel #4
0
        public async Task TxHashDiscovery(Message msg)
        {
            var chat = msg?.Chat;
            var text = $"{msg?.Text} {msg?.ReplyToMessage?.Text} {msg?.ReplyToMessage?.ReplyToMessage?.Text}";

            if (chat == null || text.IsNullOrWhitespace())
            {
                return;
            }

            var    arr  = text.Split();
            string hash = null;

            foreach (var s in arr)
            {
                hash = s.Trim("*", "`", "\"", "'", " ", "[", "]", "(", ")", "\n", "\r");
                if (hash.Length > 32 && hash.IsHex())
                {
                    break;
                }
            }

            if (!hash.IsHex())
            {
                return;
            }

            var props = GetTokenPropsFromTextCommand(text);

            var lcds = FHelper.GetAllLCDs();

            if (!props.lcd.IsNullOrEmpty())
            {
                lcds = lcds.Prepend(props.lcd).ToArray();
            }

            string      error = null, gas = null, height = null, timestamp = null, network = null, output = null, log = null;
            TxsResponse txs = null;

            if (lcds.IsNullOrEmpty())
            {
                await _TBC.SendTextMessageAsync(chatId : chat, "*lcd* property was not found", replyToMessageId : msg.MessageId, parseMode : ParseMode.Markdown);

                return;
            }

            for (int i = 0; i < lcds.Length; i++)
            {
                error = null;
                var client        = new CosmosHub(lcd: lcds[i], timeoutSeconds: _cosmosHubClientTimeout);
                var keystrokeTask = _TBC.SendChatActionAsync(chat, ChatAction.Typing); //simulate keystrokes

                try
                {
                    var t1 = client.GetNodeInfo();
                    var t2 = client.GetTxs(hash);
                    props.network = (await t1).network ?? props.network;
                    txs           = await t2;
                    if ((txs.height).ToLongOrDefault(-1) > 0)
                    {
                        break;
                    }
                }
                catch (Exception ex)
                {
                    await keystrokeTask;
                    error = $"\nError: `Tx hash was not found.`";
                    _logger.Log($"[DISCOVERY PROCESS ERROR] => Filed ('{msg?.Chat?.Id ?? 0}') to query tx hash: '{ex.JsonSerializeAsPrettyException(Newtonsoft.Json.Formatting.Indented)}'");
                }
            }

            if (error.IsNullOrWhitespace() && !(txs?.error).IsNullOrWhitespace())
            {
                error = $"\nError: `{txs.error}`";
            }

            if (error.IsNullOrWhitespace())
            {
                gas       = $"\nGas Used: `{txs.gas_used}`\nGas Wanted: `{txs.gas_wanted}`";
                height    = $"\nHeight: `{txs.height}`";
                timestamp = $"\nTimestamp: `{txs.timestamp}`";
                network   = $"\nNetwork: `{props.network}`";
                log       = $"\nLog: `{txs.raw_log}`";

                var outputJson = (txs?.tx?.value).JsonSerialize(Newtonsoft.Json.Formatting.Indented);
                outputJson = outputJson.TrimOnTrigger('[', '\n', '\r', ' ');
                outputJson = outputJson.TrimOnTrigger(']', '\n', '\r', ' ');
                outputJson = outputJson.TrimOnTrigger('}', '\n', '\r', ' ');

                if (outputJson.Length > 8 && outputJson.Length < 3072)
                {
                    output = $"\n\nOutput: `{outputJson}`";
                }
                else if (outputJson.Length >= 3072)
                {
                    output = $"\n\nOutput:\n`Too long to display 😢`";
                }
            }

            await _TBC.SendTextMessageAsync(chatId : chat,
                                            $"Hash: `{hash}`\n" + height + gas + network + timestamp + log + error + output,
                                            replyToMessageId : msg.MessageId,
                                            parseMode : ParseMode.Markdown);
        }
Beispiel #5
0
        private async Task TransactionProcessMessage(Message m)
        {
            if (!await this.CheckMasterChatMembership(m))
            {
                return;
            }

            var chat = m.Chat;
            var from = m.From;
            var to   = m.ReplyToMessage?.From;
            var text = (m.Text?.Trim() ?? "").Trim('\'', '"', '`', '*', '[', ']');

            var args    = text.Split(" ");
            var cliArgs = CLIHelper.GetNamedArguments(args);

            var toUsername = args.TryGetValueOrDefault(1, "").Trim(' ', '\'', '"', '*', '`', '[', ']');
            var toAddress  = (cliArgs.GetValueOrDefault("address") ?? args.TryGetValueOrDefault(3, "")).Trim(' ', '\'', '"', '*', '`', '[', ']');

            if (!Bech32Ex.TryDecode(toAddress, out var hrp, out var addrBytes))
            {
                toAddress = null;
            }

            if (!toAddress.IsNullOrWhitespace())
            {
                to = null;
            }

            if (toAddress.IsNullOrWhitespace() && to == null && toUsername.StartsWith("@"))
            {
                to = await TryGetUserByUsername(toUsername.TrimStart("@"));
            }

            if (to == null && toAddress.IsNullOrWhitespace())
            {
                await _TBC.SendTextMessageAsync(chatId : chat,
                                                $"Transaction can't be send.\n" +
                                                $"User @{toUsername ?? "null"} is not an active member of *{chat.Title}* group, `address` property is invalid or you responded to the old message that bot can't see.\n" +
                                                $"Try a 'reply to option', for example: Reply to -> `tip <amount> $<token_name>` rather then `tx @<username> <amount> $<token>` or specify `address` argument, e.g: `tx <amount> $<token> --address=<publicKey>`.",
                                                replyToMessageId : m.MessageId,
                                                parseMode : Telegram.Bot.Types.Enums.ParseMode.Markdown);

                return;
            }

            var        token    = (args.FirstOrDefault(x => x.StartsWith("$")).Trim() ?? cliArgs.GetValueOrDefault("token") ?? "").TrimStart("$");
            var        propsVar = Environment.GetEnvironmentVariable($"{token?.ToLower()}_PROPS");
            TokenProps props;

            if (!propsVar.IsNullOrEmpty())
            {
                props = propsVar.JsonDeserialize <TokenProps>();
            }
            else
            {
                props = new TokenProps();
            }

            props.denom  = cliArgs.GetValueOrDefault("denom") ?? props.denom ?? token.ToLower();
            props.amount = (args.FirstOrDefault(x => x.Trim().IsDigits()).Trim() ?? cliArgs.GetValueOrDefault("amount")).ToLongOrDefault(0);
            props.fees   = cliArgs.GetValueOrDefault("fees", props.fees.ToString()).ToLongOrDefault(0);

            if (props.amount < 0 || props.fees < 0)
            {
                await _TBC.SendTextMessageAsync(chatId : chat,
                                                $"`amount` or `fees` or `token` were not specified.",
                                                replyToMessageId : m.MessageId,
                                                parseMode : ParseMode.Markdown);

                return;
            }

            string wallet = null;

            try
            {
                var account = await GetUserAccount(from);

                var acc = new AsmodatStandard.Cryptography.Cosmos.Account(props.prefix, (uint)props.index);
                acc.InitializeWithMnemonic(account.GetSecret());
                var client          = new CosmosHub(lcd: props.lcd, timeoutSeconds: _cosmosHubClientTimeout);
                var fromAccountInfo = await client.GetAccount(account : acc.CosmosAddress);

                var fromAccountBalance = fromAccountInfo?.coins?.FirstOrDefault(x => x?.denom?.ToLower() == props.denom.ToLower());
                var fromBalance        = (fromAccountBalance?.amount).ToBigIntOrDefault(0);
                wallet = $"Wallet: `{fromBalance} {props.denom}`";
            }
            catch
            {
            }

            var optionsKeyboard = new InlineKeyboardMarkup(new[]
            {
                new []         // first row
                {
                    InlineKeyboardButton.WithCallbackData("YES, SEND", $"{OptionKeys.txConfirm.ToString()} {from.Id} {to?.Id.ToString() ?? toAddress}"),
                    InlineKeyboardButton.WithCallbackData("NO!, CANCEL", OptionKeys.txCancel.ToString())
                },
            });

            string toConfirm = (to == null ? $"To: `{toAddress}`\n" : $"To: {to.GetMarkDownUsername()} (`{to.Id}`)\n");
            await _TBC.SendTextMessageAsync(chatId : chat,
                                            $"*[ CONFIRM REQUEST {m.MessageId} ]*\n" +
                                            $"Action: `transfer`\n" +
                                            $"From: {from.GetMarkDownUsername()} (`{from.Id}`)\n" +
                                            toConfirm +
                                            $"Amount: `{props.amount} {props.denom}`\n" +
                                            $"Fees: `{props.fees} {props.denom}`\n" + wallet,
                                            replyToMessageId : m.MessageId,
                                            replyMarkup : optionsKeyboard,
                                            parseMode : ParseMode.Markdown);
        }
        private async Task FaucetProcessMessage(Message m)
        {
            var chat    = m.Chat;
            var userId  = m.From.Id;
            var text    = (m.Text?.Trim() ?? "").Trim('\'', '"', '`', '*', '[', ']');
            var args    = text.Split(" ");
            var cliArgs = CLIHelper.GetNamedArguments(args);

            await _TBC.SendChatActionAsync(chat, ChatAction.Typing);        //simulate keystrokes

            var token = args.TryGetValueOrDefault(2)?.TrimStartSingle("$"); // $ATOM

            if (token?.ToLower() == "kex" && Environment.GetEnvironmentVariable($"{token?.ToLower()}_PROPS").IsNullOrWhitespace())
            {
                await _TBC.SendTextMessageAsync(chatId : chat, $"That one's comming 🔜 😉",
                                                replyToMessageId : m.MessageId, parseMode : ParseMode.Default);

                return;
            }

            if (!await this.CheckMasterChatMembership(m))
            {
                return;
            }
            if (await GetDeposit(m))
            {
                return;
            }

            var props = await GetTokenFaucetProps(m);

            if (props == null) //failed to read properties
            {
                return;
            }

            var acc = new AsmodatStandard.Cryptography.Cosmos.Account(props.prefix, (uint)props.index);

            acc.InitializeWithMnemonic(_mnemonic.Release());
            var cosmosAdress = acc.CosmosAddress;

            TxResponse txResponse         = null;
            Account    accountInfo        = null;
            Token      accountBalance     = null;
            long       faucetTokenBalance = 0;
            var        notEnoughFunds     = false;
            var        client             = new CosmosHub(lcd: props.lcd, timeoutSeconds: _cosmosHubClientTimeout);

            if (props.address != cosmosAdress)
            {
                try
                {
                    var userAccountInfo = await client.GetAccount(account : props.address);

                    var userAccountBalance = userAccountInfo?.coins?.FirstOrDefault(x => x?.denom?.ToLower() == props.denom);
                    var userBalance        = (userAccountBalance?.amount ?? "0").ToBigIntOrDefault(0);

                    if (userBalance >= props.amount)
                    {
                        await _TBC.SendTextMessageAsync(text : $"Your account balance exceeds `{props.amount} {props.denom}`", chatId : new ChatId(m.Chat.Id), replyToMessageId : m.MessageId, parseMode : Telegram.Bot.Types.Enums.ParseMode.Markdown);

                        return;
                    }

                    props.amount = props.amount - userBalance; //only send difference up to max amount
                }
                catch (Exception ex)
                {
                    _logger.Log($"[ERROR] => Filed to fetch '{props.denom ?? "undefined"}' balance of '{props.address ?? "undefined"}' from '{props.lcd ?? "undefined"}': '{ex.JsonSerializeAsPrettyException(Newtonsoft.Json.Formatting.Indented)}'");
                }
            }

            var sequenceKey = $"{cosmosAdress}-{props.network}";
            await _ssLocker.Lock(async() =>
            {
                accountInfo        = await client.GetAccount(account: cosmosAdress);
                accountBalance     = accountInfo?.coins?.FirstOrDefault(x => x?.denom?.ToLower() == props.denom.ToLower());
                faucetTokenBalance = (accountBalance?.amount).ToLongOrDefault(0);

                if (faucetTokenBalance < (props.amount + props.fees))
                {
                    notEnoughFunds = true;
                    return;
                }

                var sequence           = accountInfo.sequence.ToLongOrDefault();
                var oldSeque           = sequences.GetValueOrDefault(sequenceKey, -1);
                sequences[sequenceKey] = Math.Max(sequence, oldSeque + 1);
                accountInfo.sequence   = sequences[sequenceKey].ToString();
            });

            if (notEnoughFunds)
            {
                await _TBC.SendTextMessageAsync(chatId : chat,
                                                $"Faucet does not have enough `{props.denom ?? "undefined"}` tokens ({faucetTokenBalance}) or coin index ({props.index}) is invalid.\n\n" +
                                                $"Network Id: `{props.network ?? "undefined"}`\n" +
                                                $"Faucet Public Address: `{cosmosAdress}`",
                                                replyToMessageId : m.MessageId,
                                                parseMode : Telegram.Bot.Types.Enums.ParseMode.Markdown);

                return;
            }

            var doc = await client.CreateMsgSend(
                account : accountInfo,
                to : props.address,
                amount : props.amount,
                denom : accountBalance?.denom ?? props.denom,
                fees : props.fees,
                gas : props.gas,
                memo : props.memo ?? "Kira Interchain Faucet - Join us at https://t.me/kirainterex");

            var tx = doc.CreateTx(acc);

            txResponse = await client.PostTx(tx);

            var inviteLink = await GetMasterChatInviteLink();

            string debugLog = null;

            if (text.Contains("--debug"))
            {
                debugLog = $"\nDebug Log: {txResponse?.raw_log}";
            }

            if (txResponse == null || txResponse.height.ToLongOrDefault(0) <= 0 || !txResponse.error.IsNullOrWhitespace())
            {
                await _TBC.SendTextMessageAsync(chatId : chat,
                                                $"*Failed* 😢 sending `{props.amount} {props.denom}` to {m.From.GetMarkDownUsername()} ❌\n" +
                                                $"Debug Log: `{txResponse?.raw_log ?? txResponse?.error}`\n" +
                                                $"Network Id: `{props?.network ?? "undefined"}`\n" +
                                                $"Sequence: `{accountInfo?.sequence}`\n" +
                                                $"Tx Hash: `{txResponse?.txhash}`",
                                                replyToMessageId : m.MessageId,
                                                parseMode : ParseMode.Markdown);

                sequences[sequenceKey] = sequences.GetValueOrDefault(sequenceKey, -1) - 1;
            }
            else
            {
                await _TBC.SendTextMessageAsync(chatId : chat,
                                                $"*SUCCESS* 😄 {inviteLink} sent you `{props.amount} {props.denom}` 💸\n" +
                                                $"{debugLog}\n" +
                                                $"Network Id: `{props.network ?? "undefined"}`\n" +
                                                $"Tx Hash: `{txResponse.txhash}`",
                                                replyToMessageId : m.MessageId,
                                                parseMode : ParseMode.Markdown);
            }
        }