+
-# @LightningTipBot 🏅
+# [@BitcoinDeepaBot](https://t.me/BitcoinDeepaBot) 🏅
A Telegram Lightning ⚡️ Bitcoin wallet and tip bot for group chats.
-This repository contains everything you need to set up and run your own tip bot. If you simply want to use this bot in your group chat without having to install anything just start a conversation with [@LightningTipBot](https://t.me/LightningTipBot) and invite it into your group chat.
+This repository contains everything you need to set up and run your own tip bot. If you simply want to use this bot in your group chat without having to install anything just start a conversation with [@](https://t.me/BitcoinDeepaBot) and invite it into your group chat.
## Setting up the bot
@@ -15,16 +13,16 @@ This repository contains everything you need to set up and run your own tip bot.
To build the bot from source, clone the repository and compile the source code.
```
-git clone https://github.com/LightningTipBot/LightningTipBot.git
-cd LightningTipBot
+git clone https://github.com/BitcoinDeepaBot/BitcoinDeepaBot.git
+cd BitcoinDeepaBot
go build .
-cp config.yaml-example config.yaml
+cp config.yaml.example config.yaml
```
After the configuration (see below), start it using the command
```
-./LightningTipBot
+./BitcoinDeepaBot
```
### Configuration
@@ -50,16 +48,49 @@ You can either use your own LNbits instance (recommended) or create an account a
-#### More configuration
+## API Send Endpoint 🚀
+
+The bot now includes a new HTTP API endpoint `/api/send` for programmatic Bitcoin Lightning payments. This feature allows authorized applications to send payments on behalf of whitelisted accounts.
+
+### Features
+
+- **Secure**: Only whitelisted sender accounts can use the API
+- **Network Restricted**: Limited to internal network access (10.0.0.0/24)
+- **Flexible Recipients**: Send to any Telegram username or wallet ID
+- **Transaction Logging**: All API payments are logged for audit purposes
+
+### Quick Start
+
+```bash
+curl -X POST http://10.0.0.5:8080/api/send \
+ -H "Content-Type: application/json" \
+ -d '{
+ "from": "BiccoindeepaDSA",
+ "to": "recipient_user",
+ "amount": 1000,
+ "memo": "API payment"
+ }'
+```
+
+### Configuration
-- `db_path`: User database file path.
-- `transactions_path`: Transaction database file path.
-- `buntdb_path`: Object storage database file path.
-- `lnbits_webhook_server`: URL that lnbits can reach the bot with. This is used for creating webhooks from LNbits to receive notifications about payments (optional).
-- `message_dispose_duration`: Duration in seconds after which `/tip` are deleted from a channel (only if the bot is channel admin).
-- `http_proxy` uses a proxy for all LNURL-related outbound requests (optional).
-- `lnurl_public_host_name` is the public URL of your lnbits/LndHub (for BlueWallet/Zap support, optional).
-- `lnurl_server` is the public URL for inbound LNURL payments and your lightning address host (optional).
+By default, only these accounts are whitelisted to send payments:
+- `@BiccoindeepaDSA`
+- `@CeycubeBank`
+
+To modify the whitelist, edit `WhitelistedFromAccounts` in `internal/api/send_config.go`.
+
+### Documentation
+
+For complete API documentation and examples, see [API_SEND_DOCUMENTATION.md](API_SEND_DOCUMENTATION.md).
+
+### Testing
+
+Use the included test script to verify the API functionality:
+
+```bash
+python test_api_send.py --test-suite
+```
## Features
@@ -75,6 +106,8 @@ You can either use your own LNbits instance (recommended) or create an account a
/advanced 🤖 Read the advanced help.
/basics 📚 More info.
/donate ❤️ Donate to the project: /donate
+/lkrsats 💱 Convert LKR to sats: /lkrsats
+/convert 💱 Convert sats to fiat: /convert
```
#### Advanced commands
@@ -86,14 +119,14 @@ You can either use your own LNbits instance (recommended) or create an account a
### Inline commands
```
-send 💸 Send sats to chat: @LightningTipBot send []
+send 💸 Send sats to chat: @BitcoinDeepaBot send []
```
📖 You can use inline commands in every chat, even in private conversations. Wait a second after entering an inline command and click the result, don't press enter.
### Inline send
-You can use inline commands to send payments to anyone who can read your messages, even inside private chats and group chat in which the bot isn't part of. For that, you need to trigger an [inline command](https://core.telegram.org/bots/inline). Here is how it works: Enter the name of the bot (`@LightningTipBot`) and the command you want to trigger (`send 13 Hi friend!`). When the result pops up above the text field, click it to send it to the chat. Do not press enter.
+You can use inline commands to send payments to anyone who can read your messages, even inside private chats and group chat in which the bot isn't part of. For that, you need to trigger an [inline command](https://core.telegram.org/bots/inline). Here is how it works: Enter the name of the bot (`@BitcoinDeepaBot`) and the command you want to trigger (`send 13 Hi friend!`). When the result pops up above the text field, click it to send it to the chat. Do not press enter.
@@ -116,7 +149,7 @@ Users can send and receive via . For this to work, you need to set the `lnurl_pu
Every user has a [Lightning Address](https://lightningaddress.com/) a la `username@host.com` with which they can send to via `/send ` and receive from other wallets.
-### Link to BlueWallet or Zap
+### Link to BlueWallet or Zeus
Every user can link their wallet to an external app like [Bluewallet](https://bluewallet.io/) or [Zeus](https://zeusln.app/) by using the command `/link`. If you host the bot, you will have to enable the LndHub extension in LNbits. You also need to edit the `lnbits_public_url` entry in `config.yaml` accordingly to an address that can be reached by the user's wallet (Tor should be fine as well).
@@ -136,6 +169,10 @@ To pay a Lightning invoice, you can snap a photo of a QR code and send it direct
To minimize the clutter all the heavy tipping can cause in a group chat, the bot will remove all failed commands (for example due to a syntax error) from the chat immediately. All successful commands will stay visible for `message_dispose_duration` seconds (default 10s) and then be removed. The tips will sill be visible for everyone in the Live tooltip. This feature only works, if the bot is made admin of the group.
+## Full Guide to Install and run on a VPS
+
+A complete guide to install and run BitcoinDeepaBot + LNBITS (on docker with PostgreSQL) on the same VPS with an external LND funding source has been prepared by Massimo Musumeci (@massmux) and it is available: [BitcoinDeepaBot full install](https://www.massmux.com/howto-complete-lightningtipbot-lnbits-setup-vps/)
+
## Made with
- [LNbits](https://github.com/lnbits/lnbits) – Free and open-source lightning-network wallet/accounts system.
diff --git a/amounts.go b/amounts.go
deleted file mode 100644
index 53074944..00000000
--- a/amounts.go
+++ /dev/null
@@ -1,38 +0,0 @@
-package main
-
-import (
- "errors"
- "strconv"
- "strings"
-)
-
-func getArgumentFromCommand(input string, which int) (output string, err error) {
- if len(strings.Split(input, " ")) < which+1 {
- return "", errors.New("message doesn't contain enough arguments")
- }
- output = strings.Split(input, " ")[which]
- return output, nil
-}
-
-func decodeAmountFromCommand(input string) (amount int, err error) {
- if len(strings.Split(input, " ")) < 2 {
- errmsg := "message doesn't contain any amount"
- // log.Errorln(errmsg)
- return 0, errors.New(errmsg)
- }
- amount, err = getAmount(input)
- return amount, err
-}
-
-func getAmount(input string) (amount int, err error) {
- amount, err = strconv.Atoi(strings.Split(input, " ")[1])
- if err != nil {
- return 0, err
- }
- if amount < 1 {
- errmsg := "error: Amount must be greater than 0"
- // log.Errorln(errmsg)
- return 0, errors.New(errmsg)
- }
- return amount, err
-}
diff --git a/balance.go b/balance.go
deleted file mode 100644
index 17c7f7a4..00000000
--- a/balance.go
+++ /dev/null
@@ -1,46 +0,0 @@
-package main
-
-import (
- "fmt"
-
- log "github.com/sirupsen/logrus"
-
- tb "gopkg.in/tucnak/telebot.v2"
-)
-
-const (
- balanceMessage = "👑 *Your balance:* %d sat"
- balanceErrorMessage = "🚫 Error fetching your balance. Please try again later."
-)
-
-func (bot TipBot) balanceHandler(m *tb.Message) {
- // check and print all commands
- bot.anyTextHandler(m)
- // reply only in private message
- if m.Chat.Type != tb.ChatPrivate {
- // delete message
- NewMessage(m, WithDuration(0, bot.telegram))
- }
- // first check whether the user is initialized
- fromUser, err := GetUser(m.Sender, bot)
- if err != nil {
- log.Errorf("[/balance] Error: %s", err)
- return
- }
- if !fromUser.Initialized {
- bot.startHandler(m)
- return
- }
-
- usrStr := GetUserStr(m.Sender)
- balance, err := bot.GetUserBalance(m.Sender)
- if err != nil {
- log.Errorf("[/balance] Error fetching %s's balance: %s", usrStr, err)
- bot.trySendMessage(m.Sender, balanceErrorMessage)
- return
- }
-
- log.Infof("[/balance] %s's balance: %d sat\n", usrStr, balance)
- bot.trySendMessage(m.Sender, fmt.Sprintf(balanceMessage, balance))
- return
-}
diff --git a/bot.go b/bot.go
deleted file mode 100644
index 7c4abb19..00000000
--- a/bot.go
+++ /dev/null
@@ -1,155 +0,0 @@
-package main
-
-import (
- "fmt"
- "strings"
- "sync"
- "time"
-
- "github.com/LightningTipBot/LightningTipBot/internal/storage"
-
- "github.com/LightningTipBot/LightningTipBot/internal/lnurl"
-
- log "github.com/sirupsen/logrus"
-
- "github.com/LightningTipBot/LightningTipBot/internal/lnbits"
- "gopkg.in/tucnak/telebot.v2"
- tb "gopkg.in/tucnak/telebot.v2"
-
- "gorm.io/gorm"
-)
-
-type TipBot struct {
- database *gorm.DB
- bunt *storage.DB
- logger *gorm.DB
- telegram *telebot.Bot
- client *lnbits.Client
-}
-
-var (
- paymentConfirmationMenu = &tb.ReplyMarkup{ResizeReplyKeyboard: true}
- btnCancelPay = paymentConfirmationMenu.Data("🚫 Cancel", "cancel_pay")
- btnPay = paymentConfirmationMenu.Data("✅ Pay", "confirm_pay")
- sendConfirmationMenu = &tb.ReplyMarkup{ResizeReplyKeyboard: true}
- btnCancelSend = sendConfirmationMenu.Data("🚫 Cancel", "cancel_send")
- btnSend = sendConfirmationMenu.Data("✅ Send", "confirm_send")
-
- botWalletInitialisation = sync.Once{}
- telegramHandlerRegistration = sync.Once{}
-)
-
-// NewBot migrates data and creates a new bot
-func NewBot() TipBot {
- db, txLogger := migration()
- return TipBot{
- database: db,
- logger: txLogger,
- bunt: storage.NewBunt(Configuration.Database.BuntDbPath),
- }
-}
-
-// newTelegramBot will create a new telegram bot.
-func newTelegramBot() *tb.Bot {
- tgb, err := tb.NewBot(tb.Settings{
- Token: Configuration.Telegram.ApiKey,
- Poller: &tb.LongPoller{Timeout: 60 * time.Second},
- ParseMode: tb.ModeMarkdown,
- })
- if err != nil {
- panic(err)
- }
- return tgb
-}
-
-// initBotWallet will create / initialize the bot wallet
-// todo -- may want to derive user wallets from this specific bot wallet (master wallet), since lnbits usermanager extension is able to do that.
-func (bot TipBot) initBotWallet() error {
- botWalletInitialisation.Do(func() {
- err := bot.initWallet(bot.telegram.Me)
- if err != nil {
- log.Errorln(fmt.Sprintf("[initBotWallet] Could not initialize bot wallet: %s", err.Error()))
- return
- }
- })
- return nil
-}
-
-// registerTelegramHandlers will register all telegram handlers.
-func (bot TipBot) registerTelegramHandlers() {
- telegramHandlerRegistration.Do(func() {
- // Set up handlers
- var endpointHandler = map[string]interface{}{
- "/tip": bot.tipHandler,
- "/pay": bot.confirmPaymentHandler,
- "/invoice": bot.invoiceHandler,
- "/balance": bot.balanceHandler,
- "/start": bot.startHandler,
- "/send": bot.confirmSendHandler,
- "/help": bot.helpHandler,
- "/basics": bot.basicsHandler,
- "/donate": bot.donationHandler,
- "/advanced": bot.advancedHelpHandler,
- "/link": bot.lndhubHandler,
- "/lnurl": bot.lnurlHandler,
- "/faucet": bot.faucetHandler,
- "/zapfhahn": bot.faucetHandler,
- "/kraan": bot.faucetHandler,
- tb.OnPhoto: bot.privatePhotoHandler,
- tb.OnText: bot.anyTextHandler,
- tb.OnQuery: bot.anyQueryHandler,
- tb.OnChosenInlineResult: bot.anyChosenInlineHandler,
- }
- // assign handler to endpoint
- for endpoint, handler := range endpointHandler {
- log.Debugf("Registering: %s", endpoint)
- bot.telegram.Handle(endpoint, handler)
-
- // if the endpoint is a string command (not photo etc)
- if strings.HasPrefix(endpoint, "/") {
- // register upper case versions as well
- bot.telegram.Handle(strings.ToUpper(endpoint), handler)
- }
- }
-
- // button handlers
- // for /pay
- bot.telegram.Handle(&btnPay, bot.payHandler)
- bot.telegram.Handle(&btnCancelPay, bot.cancelPaymentHandler)
- // for /send
- bot.telegram.Handle(&btnSend, bot.sendHandler)
- bot.telegram.Handle(&btnCancelSend, bot.cancelSendHandler)
-
- // register inline button handlers
- // button for inline send
- bot.telegram.Handle(&btnAcceptInlineSend, bot.acceptInlineSendHandler)
- bot.telegram.Handle(&btnCancelInlineSend, bot.cancelInlineSendHandler)
-
- // button for inline receive
- bot.telegram.Handle(&btnAcceptInlineReceive, bot.acceptInlineReceiveHandler)
- bot.telegram.Handle(&btnCancelInlineReceive, bot.cancelInlineReceiveHandler)
-
- // // button for inline faucet
- bot.telegram.Handle(&btnAcceptInlineFaucet, bot.accpetInlineFaucetHandler)
- bot.telegram.Handle(&btnCancelInlineFaucet, bot.cancelInlineFaucetHandler)
-
- })
-}
-
-// Start will initialize the telegram bot and lnbits.
-func (bot TipBot) Start() {
- // set up lnbits api
- bot.client = lnbits.NewClient(Configuration.Lnbits.AdminKey, Configuration.Lnbits.Url)
- // set up telebot
- bot.telegram = newTelegramBot()
- log.Infof("[Telegram] Authorized on account @%s", bot.telegram.Me.Username)
- // initialize the bot wallet
- err := bot.initBotWallet()
- if err != nil {
- log.Errorf("Could not initialize bot wallet: %s", err.Error())
- }
- bot.registerTelegramHandlers()
- lnbits.NewWebhookServer(Configuration.Lnbits.WebhookServerUrl, bot.telegram, bot.client, bot.database)
- lnurl.NewServer(Configuration.Bot.LNURLServerUrl, Configuration.Bot.LNURLHostUrl, Configuration.Lnbits.WebhookServer, bot.telegram, bot.client, bot.database)
- bot.telegram.Start()
-}
diff --git a/botfather-setcommands.txt b/botfather-setcommands.txt
index b0b4a6b8..8afb8420 100644
--- a/botfather-setcommands.txt
+++ b/botfather-setcommands.txt
@@ -7,10 +7,13 @@
help - Read the help.
balance - Check balance.
+transactions - List transactions
tip - Reply to a message to tip: /tip 50
send - Send funds to a user: /send 100 @LightningTipBot
invoice - Receive with Lightning: /invoice 1000
pay - Pay with Lightning: /pay lnbc10n1ps...
donate - Donate: /donate 1000
-faucet - Create a faucet: /faucet 2100 21
-advanced - Advanced help
\ No newline at end of file
+faucet - Create a faucet: /faucet 2100 21
+advanced - Advanced help
+lkrsats - Convert LKR to satoshis
+convert - Convert sats to fiat values
diff --git a/config.go b/config.go
deleted file mode 100644
index 5f2ca0a3..00000000
--- a/config.go
+++ /dev/null
@@ -1,81 +0,0 @@
-package main
-
-import (
- "fmt"
- "net/url"
- "strings"
-
- "github.com/jinzhu/configor"
- log "github.com/sirupsen/logrus"
-)
-
-var Configuration = struct {
- Bot BotConfiguration `yaml:"bot"`
- Telegram TelegramConfiguration `yaml:"telegram"`
- Database DatabaseConfiguration `yaml:"database"`
- Lnbits LnbitsConfiguration `yaml:"lnbits"`
-}{}
-
-type BotConfiguration struct {
- HttpProxy string `yaml:"http_proxy"`
- LNURLServer string `yaml:"lnurl_server"`
- LNURLServerUrl *url.URL `yaml:"-"`
- LNURLHostName string `yaml:"lnurl_public_host_name"`
- LNURLHostUrl *url.URL `yaml:"-"`
-}
-
-type TelegramConfiguration struct {
- MessageDisposeDuration int64 `yaml:"message_dispose_duration"`
- ApiKey string `yaml:"api_key"`
-}
-type DatabaseConfiguration struct {
- DbPath string `yaml:"db_path"`
- BuntDbPath string `yaml:"buntdb_path"`
- TransactionsPath string `yaml:"transactions_path"`
-}
-
-type LnbitsConfiguration struct {
- AdminId string `yaml:"admin_id"`
- AdminKey string `yaml:"admin_key"`
- Url string `yaml:"url"`
- LnbitsPublicUrl string `yaml:"lnbits_public_url"`
- WebhookServer string `yaml:"webhook_server"`
- WebhookServerUrl *url.URL `yaml:"-"`
-}
-
-func init() {
- err := configor.Load(&Configuration, "config.yaml")
- if err != nil {
- panic(err)
- }
- webhookUrl, err := url.Parse(Configuration.Lnbits.WebhookServer)
- if err != nil {
- panic(err)
- }
- Configuration.Lnbits.WebhookServerUrl = webhookUrl
-
- lnUrl, err := url.Parse(Configuration.Bot.LNURLServer)
- if err != nil {
- panic(err)
- }
- Configuration.Bot.LNURLServerUrl = lnUrl
- hostname, err := url.Parse(Configuration.Bot.LNURLHostName)
- if err != nil {
- panic(err)
- }
- Configuration.Bot.LNURLHostUrl = hostname
- checkLnbitsConfiguration()
-}
-
-func checkLnbitsConfiguration() {
- if Configuration.Lnbits.Url == "" {
- panic(fmt.Errorf("please configure a lnbits url"))
- }
- if Configuration.Lnbits.LnbitsPublicUrl == "" {
- log.Warnf("Please specify a lnbits public url otherwise users won't be able to")
- } else {
- if !strings.HasSuffix(Configuration.Lnbits.LnbitsPublicUrl, "/") {
- Configuration.Lnbits.LnbitsPublicUrl = Configuration.Lnbits.LnbitsPublicUrl + "/"
- }
- }
-}
diff --git a/config.yaml.example b/config.yaml.example
index f7c6c507..dcbb2d50 100644
--- a/config.yaml.example
+++ b/config.yaml.example
@@ -1,17 +1,71 @@
bot:
+ socks_proxy:
+ host: 127.0.0.1:9996
+ username: test
+ password: username
+ tor_proxy:
+ host: 127.0.0.1:9050
http_proxy: ""
- lnurl_public_host_name: "mylnurl.com"
- lnurl_server: "https://mylnurl.com"
+ lnurl_public_host_name: "https://mylnurl.com"
+ lnurl_server: "http://127.0.0.1:5454" # or http://0.0.0.0:5454 depending on your configuration
+ lnurl_image: true
+ admin_api_host: localhost:6060
telegram:
message_dispose_duration: 10
api_key: "1234"
+ log_group_id: -1001234567890 # Telegram group chat ID where errors will be logged
+ error_thread_id: 0 # Optional: specific thread ID for error messages (0 for main chat)
lnbits:
url: "http://127.0.0.1:5000"
admin_key: "1234"
admin_id: "1234"
- webhook_server: "http://0.0.0.0:5588"
+ webhook_server: "http://0.0.0.0:5588" # Local webhook server address
+ webhook_public_url: "https://yourdomain.com/webhook" # Public URL for webhooks (for reverse proxy)
lnbits_public_url: "link.mylnurl.com"
database:
db_path: "data/bot.db"
buntdb_path: "data/bunt.db"
- transactions_path: "data/transactions.db"
\ No newline at end of file
+ transactions_path: "data/transactions.db"
+ shop_buntdb_path: "data/shop.db"
+ groupsdb_path: "data/groups.db"
+generate:
+ open_ai_bearer_token: "token_here"
+ dalle_key: "asd"
+ dalle_price: 1000
+ worker: 2
+nostr:
+ private_key: "hex private key here"
+
+# API Configuration
+api:
+ # Analytics API - HMAC authenticated endpoints for data export
+ # Generate secrets with: openssl rand -hex 32
+ analytics:
+ enabled: true
+ timestamp_tolerance: 300 # seconds (5 minutes)
+ api_keys:
+ data-team:
+ name: "Data Team"
+ hmac_secret: "your-analytics-hmac-secret-here"
+ # Add more keys for different consumers:
+ # dashboard:
+ # name: "Internal Dashboard"
+ # hmac_secret: "another-secure-secret-here"
+
+ # Send API - wallet-based HMAC authenticated endpoints
+ send:
+ enabled: true
+ internal_network: "10.0.0.0/24"
+ max_amount: 1000000
+ min_amount: 1
+ admin_approval_threshold: 100000
+ max_memo_length: 280
+ rate_limit: 60
+ whitelisted_wallets:
+ BiccoindeepaDSA:
+ username: "BiccoindeepaDSA"
+ hmac_secret: "your-unique-bitcoindeepa-secret-here"
+ CeycubeBank:
+ username: "CeycubeBank"
+ hmac_secret: "your-unique-ceycube-secret-here"
+ timestamp_tolerance: 300
\ No newline at end of file
diff --git a/data/dalle/.placeholder b/data/dalle/.placeholder
new file mode 100644
index 00000000..284cc653
--- /dev/null
+++ b/data/dalle/.placeholder
@@ -0,0 +1 @@
+this is where dalle images are stored
diff --git a/database.go b/database.go
deleted file mode 100644
index 13f6392d..00000000
--- a/database.go
+++ /dev/null
@@ -1,75 +0,0 @@
-package main
-
-import (
- "fmt"
- "reflect"
- "strconv"
-
- log "github.com/sirupsen/logrus"
-
- "github.com/LightningTipBot/LightningTipBot/internal/lnbits"
- tb "gopkg.in/tucnak/telebot.v2"
- "gorm.io/driver/sqlite"
- "gorm.io/gorm"
-)
-
-func migration() (db *gorm.DB, txLogger *gorm.DB) {
- txLogger, err := gorm.Open(sqlite.Open(Configuration.Database.TransactionsPath), &gorm.Config{DisableForeignKeyConstraintWhenMigrating: true, FullSaveAssociations: true})
- if err != nil {
- panic("Initialize orm failed.")
- }
-
- orm, err := gorm.Open(sqlite.Open(Configuration.Database.DbPath), &gorm.Config{DisableForeignKeyConstraintWhenMigrating: true, FullSaveAssociations: true})
- if err != nil {
- panic("Initialize orm failed.")
- }
-
- err = orm.AutoMigrate(&lnbits.User{})
- if err != nil {
- panic(err)
- }
- err = txLogger.AutoMigrate(&Transaction{})
- if err != nil {
- panic(err)
- }
- return orm, txLogger
-}
-
-// GetUser from telegram user. Update the user if user information changed.
-func GetUser(u *tb.User, bot TipBot) (*lnbits.User, error) {
- user := &lnbits.User{Name: strconv.Itoa(u.ID)}
- tx := bot.database.First(user)
- if tx.Error != nil {
- errmsg := fmt.Sprintf("[GetUser] Couldn't fetch %s's info from database.", GetUserStr(u))
- log.Warnln(errmsg)
- return user, tx.Error
- }
- defer func() {
- user.Wallet.Client = bot.client
- }()
- var err error
- go func() {
- userCopy := bot.copyLowercaseUser(u)
- if !reflect.DeepEqual(userCopy, user.Telegram) {
- // update possibly changed user details in database
- user.Telegram = userCopy
- err = UpdateUserRecord(user, bot)
- if err != nil {
- log.Warnln(fmt.Sprintf("[UpdateUserRecord] %s", err.Error()))
- }
- }
- }()
- return user, err
-}
-
-func UpdateUserRecord(user *lnbits.User, bot TipBot) error {
- user.Telegram = bot.copyLowercaseUser(user.Telegram)
- tx := bot.database.Save(user)
- if tx.Error != nil {
- errmsg := fmt.Sprintf("[UpdateUserRecord] Error: Couldn't update %s's info in database.", GetUserStr(user.Telegram))
- log.Errorln(errmsg)
- return tx.Error
- }
- log.Debugf("[UpdateUserRecord] Records of user %s updated.", GetUserStr(user.Telegram))
- return nil
-}
diff --git a/donate.go b/donate.go
deleted file mode 100644
index b0e5987f..00000000
--- a/donate.go
+++ /dev/null
@@ -1,162 +0,0 @@
-package main
-
-import (
- "fmt"
- "io"
- "io/ioutil"
- "net/http"
- "strings"
-
- "github.com/LightningTipBot/LightningTipBot/internal/lnbits"
- log "github.com/sirupsen/logrus"
- tb "gopkg.in/tucnak/telebot.v2"
-)
-
-// PLEASE DO NOT CHANGE THE CODE IN THIS FILE
-// YOU MIGHT BREAK DONATIONS TO THE ORIGINAL PROJECT
-// THE DEVELOPMENT OF LIGHTNINGTIPBOT RELIES ON DONATIONS
-// IF YOU USE THIS PROJECT, LEAVE THIS CODE ALONE
-
-var (
- donationSuccess = "🙏 Thank you for your donation."
- donationErrorMessage = "🚫 Oh no. Donation failed."
- donationProgressMessage = "🧮 Preparing your donation..."
- donationFailedMessage = "🚫 Donation failed: %s"
- donateEnterAmountMessage = "Did you enter an amount?"
- donateValidAmountMessage = "Did you enter a valid amount?"
- donateHelpText = "📖 Oops, that didn't work. %s\n\n" +
- "*Usage:* `/donate `\n" +
- "*Example:* `/donate 1000`"
- endpoint string
-)
-
-func helpDonateUsage(errormsg string) string {
- if len(errormsg) > 0 {
- return fmt.Sprintf(donateHelpText, fmt.Sprintf("%s", errormsg))
- } else {
- return fmt.Sprintf(donateHelpText, "")
- }
-}
-
-func (bot TipBot) donationHandler(m *tb.Message) {
- // check and print all commands
- bot.anyTextHandler(m)
-
- if len(strings.Split(m.Text, " ")) < 2 {
- bot.trySendMessage(m.Sender, helpDonateUsage(donateEnterAmountMessage))
- return
- }
- amount, err := decodeAmountFromCommand(m.Text)
- if err != nil {
- return
- }
- if amount < 1 {
- bot.trySendMessage(m.Sender, helpDonateUsage(donateValidAmountMessage))
- return
- }
-
- // command is valid
- msg := bot.trySendMessage(m.Sender, donationProgressMessage)
- // get invoice
- resp, err := http.Get(fmt.Sprintf(endpoint, amount, GetUserStr(m.Sender), GetUserStr(bot.telegram.Me)))
- if err != nil {
- log.Errorln(err)
- bot.tryEditMessage(msg, donationErrorMessage)
- return
- }
- body, err := ioutil.ReadAll(resp.Body)
- if err != nil {
- log.Errorln(err)
- bot.tryEditMessage(msg, donationErrorMessage)
- return
- }
-
- // send donation invoice
- user, err := GetUser(m.Sender, bot)
- if err != nil {
- return
- }
-
- // bot.trySendMessage(user.Telegram, string(body))
- _, err = user.Wallet.Pay(lnbits.PaymentParams{Out: true, Bolt11: string(body)}, *user.Wallet)
- if err != nil {
- userStr := GetUserStr(m.Sender)
- errmsg := fmt.Sprintf("[/donate] Donation failed for user %s: %s", userStr, err)
- log.Errorln(errmsg)
- bot.tryEditMessage(msg, fmt.Sprintf(donationFailedMessage, err))
- return
- }
- bot.tryEditMessage(msg, donationSuccess)
-
-}
-
-func init() {
- var sb strings.Builder
- _, err := io.Copy(&sb, rot13Reader{strings.NewReader("uggcf://ya.gvcf/qbangr/%q?sebz=%f&obg=%f")})
- if err != nil {
- panic(err)
- }
- endpoint = sb.String()
-}
-
-type rot13Reader struct {
- r io.Reader
-}
-
-func (rot13 rot13Reader) Read(b []byte) (int, error) {
- n, err := rot13.r.Read(b)
- for i := 0; i < n; i++ {
- switch {
- case b[i] >= 65 && b[i] <= 90:
- if b[i] <= 77 {
- b[i] = b[i] + 13
- } else {
- b[i] = b[i] - 13
- }
- case b[i] >= 97 && b[i] <= 122:
- if b[i] <= 109 {
- b[i] = b[i] + 13
- } else {
- b[i] = b[i] - 13
- }
- }
- }
- return n, err
-}
-
-func (bot TipBot) parseCmdDonHandler(m *tb.Message) error {
- arg := ""
- if strings.HasPrefix(strings.ToLower(m.Text), "/send") {
- arg, _ = getArgumentFromCommand(m.Text, 2)
- if arg != "@"+bot.telegram.Me.Username {
- return fmt.Errorf("err")
- }
- }
- if strings.HasPrefix(strings.ToLower(m.Text), "/tip") {
- arg = GetUserStr(m.ReplyTo.Sender)
- if arg != "@"+bot.telegram.Me.Username {
- return fmt.Errorf("err")
- }
- }
- if arg == "@LightningTipBot" || len(arg) < 1 {
- return fmt.Errorf("err")
- }
-
- amount, err := decodeAmountFromCommand(m.Text)
- if err != nil {
- return err
- }
-
- var sb strings.Builder
- _, err = io.Copy(&sb, rot13Reader{strings.NewReader("Gunax lbh! V'z ebhgvat guvf qbangvba gb YvtugavatGvcObg@ya.gvcf.")})
- if err != nil {
- panic(err)
- }
- donationInterceptMessage := sb.String()
-
- bot.trySendMessage(m.Sender, MarkdownEscape(donationInterceptMessage))
- m.Text = fmt.Sprintf("/donate %d", amount)
- bot.donationHandler(m)
- // returning nil here will abort the parent handler (/pay or /tip)
- return nil
-}
diff --git a/go.mod b/go.mod
index e21cd11a..200021b4 100644
--- a/go.mod
+++ b/go.mod
@@ -1,21 +1,108 @@
module github.com/LightningTipBot/LightningTipBot
-go 1.15
+go 1.18
require (
- github.com/fiatjaf/go-lnurl v1.4.0
+ github.com/BurntSushi/toml v0.3.1
+ github.com/PuerkitoBio/goquery v1.8.0
+ github.com/btcsuite/btcd/btcec/v2 v2.2.0
+ github.com/eko/gocache v1.2.0
+ github.com/fiatjaf/go-lnurl v1.11.3-0.20220819192234-5c5819dd0aa7
github.com/fiatjaf/ln-decodepay v1.1.0
github.com/gorilla/mux v1.8.0
github.com/imroc/req v0.3.0
github.com/jinzhu/configor v1.2.1
github.com/makiuchi-d/gozxing v0.0.2
- github.com/sirupsen/logrus v1.2.0
+ github.com/nbd-wtf/go-nostr v0.13.0
+ github.com/nfnt/resize v0.0.0-20180221191011-83c6a9932646
+ github.com/nicksnyder/go-i18n/v2 v2.1.2
+ github.com/orcaman/concurrent-map v1.0.0
+ github.com/patrickmn/go-cache v2.1.0+incompatible
+ github.com/prometheus/common v0.26.0
+ github.com/r3labs/sse v0.0.0-20210224172625-26fe804710bc
+ github.com/satori/go.uuid v1.2.0
+ github.com/sirupsen/logrus v1.7.0
github.com/skip2/go-qrcode v0.0.0-20200617195104-da1b6568686e
- github.com/tidwall/btree v0.6.1 // indirect
- github.com/tidwall/buntdb v1.2.6
- github.com/tidwall/gjson v1.8.1
- github.com/tidwall/pretty v1.2.0 // indirect
- gopkg.in/tucnak/telebot.v2 v2.3.5
+ github.com/tidwall/buntdb v1.2.7
+ github.com/tidwall/gjson v1.12.1
+ github.com/tidwall/sjson v1.2.4
+ golang.org/x/net v0.0.0-20211015210444-4f30a5c0130f
+ golang.org/x/text v0.3.7
+ golang.org/x/time v0.0.0-20210220033141-f8bda1e9f3ba
+ gopkg.in/lightningtipbot/telebot.v3 v3.0.0-20220828121412-0dea11ecc6dd
gorm.io/driver/sqlite v1.1.4
gorm.io/gorm v1.21.12
)
+
+require (
+ github.com/SaveTheRbtz/generic-sync-map-go v0.0.0-20220414055132-a37292614db8 // indirect
+ github.com/XiaoMi/pegasus-go-client v0.0.0-20210427083443-f3b6b08bc4c2 // indirect
+ github.com/aead/siphash v1.0.1 // indirect
+ github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751 // indirect
+ github.com/alecthomas/units v0.0.0-20190924025748-f65c72e2690d // indirect
+ github.com/andybalholm/cascadia v1.3.1 // indirect
+ github.com/bradfitz/gomemcache v0.0.0-20190913173617-a41fca850d0b // indirect
+ github.com/btcsuite/btcd v0.23.1 // indirect
+ github.com/btcsuite/btcd/btcutil v1.1.1 // indirect
+ github.com/btcsuite/btcd/btcutil/psbt v1.1.4 // indirect
+ github.com/btcsuite/btcd/chaincfg/chainhash v1.0.1 // indirect
+ github.com/btcsuite/btclog v0.0.0-20170628155309-84c8d2346e9f // indirect
+ github.com/btcsuite/btcwallet v0.15.1 // indirect
+ github.com/btcsuite/btcwallet/wallet/txauthor v1.2.3 // indirect
+ github.com/btcsuite/btcwallet/wallet/txrules v1.2.0 // indirect
+ github.com/btcsuite/btcwallet/wallet/txsizes v1.1.0 // indirect
+ github.com/btcsuite/btcwallet/walletdb v1.4.0 // indirect
+ github.com/btcsuite/btcwallet/wtxmgr v1.5.0 // indirect
+ github.com/btcsuite/go-socks v0.0.0-20170105172521-4720035b7bfd // indirect
+ github.com/btcsuite/websocket v0.0.0-20150119174127-31079b680792 // indirect
+ github.com/cenkalti/backoff/v4 v4.1.0 // indirect
+ github.com/cespare/xxhash/v2 v2.1.1 // indirect
+ github.com/davecgh/go-spew v1.1.1 // indirect
+ github.com/decred/dcrd/crypto/blake256 v1.0.0 // indirect
+ github.com/decred/dcrd/dcrec/secp256k1/v4 v4.0.1 // indirect
+ github.com/decred/dcrd/lru v1.0.0 // indirect
+ github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect
+ github.com/go-errors/errors v1.0.1 // indirect
+ github.com/go-redis/redis/v8 v8.8.2 // indirect
+ github.com/gorilla/websocket v1.4.2 // indirect
+ github.com/jinzhu/inflection v1.0.0 // indirect
+ github.com/jinzhu/now v1.1.2 // indirect
+ github.com/kkdai/bstream v1.0.0 // indirect
+ github.com/lightninglabs/gozmq v0.0.0-20191113021534-d20a764486bf // indirect
+ github.com/lightninglabs/neutrino v0.14.2 // indirect
+ github.com/lightningnetwork/lnd v0.15.0-beta // indirect
+ github.com/lightningnetwork/lnd/clock v1.1.0 // indirect
+ github.com/lightningnetwork/lnd/queue v1.1.0 // indirect
+ github.com/lightningnetwork/lnd/ticker v1.1.0 // indirect
+ github.com/lightningnetwork/lnd/tlv v1.0.3 // indirect
+ github.com/lightningnetwork/lnd/tor v1.0.1 // indirect
+ github.com/mattn/go-sqlite3 v1.14.5 // indirect
+ github.com/miekg/dns v1.1.43 // indirect
+ github.com/nbd-wtf/ln-decodepay v1.5.1 // indirect
+ github.com/pegasus-kv/thrift v0.13.0 // indirect
+ github.com/spf13/cast v1.5.0 // indirect
+ github.com/tidwall/btree v0.6.1 // indirect
+ github.com/tidwall/grect v0.1.3 // indirect
+ github.com/tidwall/match v1.1.1 // indirect
+ github.com/tidwall/pretty v1.2.0 // indirect
+ github.com/tidwall/rtred v0.1.2 // indirect
+ github.com/tidwall/tinyqueue v0.1.1 // indirect
+ github.com/valyala/fastjson v1.6.3 // indirect
+ go.opentelemetry.io/otel v0.20.0 // indirect
+ go.opentelemetry.io/otel/metric v0.20.0 // indirect
+ go.opentelemetry.io/otel/trace v0.20.0 // indirect
+ golang.org/x/crypto v0.0.0-20210921155107-089bfa567519 // indirect
+ golang.org/x/exp v0.0.0-20221106115401-f9659909a136 // indirect
+ golang.org/x/sync v0.0.0-20220819030929-7fc1605a5dde // indirect
+ golang.org/x/sys v0.1.0 // indirect
+ golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1 // indirect
+ golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 // indirect
+ gopkg.in/alecthomas/kingpin.v2 v2.2.6 // indirect
+ gopkg.in/cenkalti/backoff.v1 v1.1.0 // indirect
+ gopkg.in/natefinch/lumberjack.v2 v2.0.0 // indirect
+ gopkg.in/tomb.v2 v2.0.0-20161208151619-d5d1b5820637 // indirect
+ gopkg.in/yaml.v2 v2.4.0 // indirect
+ k8s.io/apimachinery v0.0.0-20191123233150-4c4803ed55e3 // indirect
+)
+
+// replace gopkg.in/lightningtipbot/telebot.v3 => ../telebot
diff --git a/go.sum b/go.sum
index e3fad934..6544db96 100644
--- a/go.sum
+++ b/go.sum
@@ -1,107 +1,493 @@
cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
+cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
+cloud.google.com/go v0.38.0/go.mod h1:990N+gfupTy94rShfmMCWGDn0LpTmnzTp2qbd1dvSRU=
+cloud.google.com/go v0.44.1/go.mod h1:iSa0KzasP4Uvy3f1mN/7PiObzGgflwredwwASm/v6AU=
+cloud.google.com/go v0.44.2/go.mod h1:60680Gw3Yr4ikxnPRS/oxxkBccT6SA1yMk63TGekxKY=
+cloud.google.com/go v0.45.1/go.mod h1:RpBamKRgapWJb87xiFSdk4g1CME7QZg3uwTez+TSTjc=
+cloud.google.com/go v0.46.3/go.mod h1:a6bKKbmY7er1mI7TEI4lsAkts/mkhTSZK8w33B4RAg0=
+cloud.google.com/go v0.50.0/go.mod h1:r9sluTvynVuxRIOHXQEHMFffphuXHOMZMycpNR5e6To=
+cloud.google.com/go v0.52.0/go.mod h1:pXajvRH/6o3+F9jDHZWQ5PbGhn+o8w9qiu/CffaVdO4=
+cloud.google.com/go v0.53.0/go.mod h1:fp/UouUEsRkN6ryDKNW/Upv/JBKnv6WDthjR6+vze6M=
+cloud.google.com/go v0.54.0/go.mod h1:1rq2OEkV3YMf6n/9ZvGWI3GWw0VoqH/1x2nd8Is/bPc=
+cloud.google.com/go v0.56.0/go.mod h1:jr7tqZxxKOVYizybht9+26Z/gUq7tiRzu+ACVAMbKVk=
+cloud.google.com/go v0.57.0/go.mod h1:oXiQ6Rzq3RAkkY7N6t3TcE6jE+CIBBbA36lwQ1JyzZs=
+cloud.google.com/go v0.62.0/go.mod h1:jmCYTdRCQuc1PHIIJ/maLInMho30T/Y0M4hTdTShOYc=
+cloud.google.com/go v0.65.0/go.mod h1:O5N8zS7uWy9vkA9vayVHs65eM1ubvY4h553ofrNHObY=
+cloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o=
+cloud.google.com/go/bigquery v1.3.0/go.mod h1:PjpwJnslEMmckchkHFfq+HTD2DmtT67aNFKH1/VBDHE=
+cloud.google.com/go/bigquery v1.4.0/go.mod h1:S8dzgnTigyfTmLBfrtrhyYhwRxG72rYxvftPBK2Dvzc=
+cloud.google.com/go/bigquery v1.5.0/go.mod h1:snEHRnqQbz117VIFhE8bmtwIDY80NLUZUMb4Nv6dBIg=
+cloud.google.com/go/bigquery v1.7.0/go.mod h1://okPTzCYNXSlb24MZs83e2Do+h+VXtc4gLoIoXIAPc=
+cloud.google.com/go/bigquery v1.8.0/go.mod h1:J5hqkt3O0uAFnINi6JXValWIb1v0goeZM77hZzJN/fQ=
+cloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE=
+cloud.google.com/go/datastore v1.1.0/go.mod h1:umbIZjpQpHh4hmRpGhH4tLFup+FVzqBi1b3c64qFpCk=
+cloud.google.com/go/firestore v1.1.0/go.mod h1:ulACoGHTpvq5r8rxGJ4ddJZBZqakUQqClKRT5SZwBmk=
+cloud.google.com/go/pubsub v1.0.1/go.mod h1:R0Gpsv3s54REJCy4fxDixWD93lHJMoZTyQ2kNxGRt3I=
+cloud.google.com/go/pubsub v1.1.0/go.mod h1:EwwdRX2sKPjnvnqCa270oGRyludottCI76h+R3AArQw=
+cloud.google.com/go/pubsub v1.2.0/go.mod h1:jhfEVHT8odbXTkndysNHCcx0awwzvfOlguIAii9o8iA=
+cloud.google.com/go/pubsub v1.3.1/go.mod h1:i+ucay31+CNRpDW4Lu78I4xXG+O1r/MAHgjpRVR+TSU=
+cloud.google.com/go/storage v1.0.0/go.mod h1:IhtSnM/ZTZV8YYJWCY8RULGVqBDmpoyjwiyrjsg+URw=
+cloud.google.com/go/storage v1.5.0/go.mod h1:tpKbwo567HUNpVclU5sGELwQWBDZ8gh0ZeosJ0Rtdos=
+cloud.google.com/go/storage v1.6.0/go.mod h1:N7U0C8pVQ/+NIKOBQyamJIeKQKkZ+mxpohlUTyfDhBk=
+cloud.google.com/go/storage v1.8.0/go.mod h1:Wv1Oy7z6Yz3DshWRJFhqM/UCfaWIRTdp0RXyy7KQOVs=
+cloud.google.com/go/storage v1.10.0/go.mod h1:FLPqc6j+Ki4BU591ie1oL6qBQGu2Bl/tZ9ullr3+Kg0=
+dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU=
git.schwanenlied.me/yawning/bsaes.git v0.0.0-20180720073208-c0276d75487e/go.mod h1:BWqTsj8PgcPriQJGl7el20J/7TuT1d/hSyFDXMEpoEo=
github.com/BurntSushi/toml v0.3.1 h1:WXkYYl6Yr3qBf1K79EBnL4mak0OimBfB0XUf9Vl28OQ=
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
+github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo=
+github.com/Knetic/govaluate v3.0.1-0.20171022003610-9aa49832a739+incompatible/go.mod h1:r7JcOSlj0wfOMncg0iLm8Leh48TZaKVeNIfJntJ2wa0=
+github.com/Masterminds/semver/v3 v3.1.1/go.mod h1:VPu/7SZ7ePZ3QOrcuXROw5FAcLl4a0cBrbBpGY/8hQs=
+github.com/NYTimes/gziphandler v0.0.0-20170623195520-56545f4a5d46/go.mod h1:3wb06e3pkSAbeQ52E9H9iFoQsEEwGN64994WTCIhntQ=
github.com/NebulousLabs/fastrand v0.0.0-20181203155948-6fb6489aac4e/go.mod h1:Bdzq+51GR4/0DIhaICZEOm+OHvXGwwB2trKZ8B4Y6eQ=
github.com/NebulousLabs/go-upnp v0.0.0-20180202185039-29b680b06c82/go.mod h1:GbuBk21JqF+driLX3XtJYNZjGa45YDoa9IqCTzNSfEc=
github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU=
+github.com/PuerkitoBio/goquery v1.8.0 h1:PJTF7AmFCFKk1N6V6jmKfrNH9tV5pNE6lZMkG0gta/U=
+github.com/PuerkitoBio/goquery v1.8.0/go.mod h1:ypIiRMtY7COPGk+I/YbZLbxsxn9g5ejnI2HSMtkjZvI=
+github.com/PuerkitoBio/purell v1.0.0/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbtSwDGJws/X0=
+github.com/PuerkitoBio/urlesc v0.0.0-20160726150825-5bd2802263f2/go.mod h1:uGdkoq3SwY9Y+13GIhn11/XLaGBb4BfwItxLd5jeuXE=
+github.com/SaveTheRbtz/generic-sync-map-go v0.0.0-20220414055132-a37292614db8 h1:Xa6tp8DPDhdV+k23uiTC/GrAYOe4IdyJVKtob4KW3GA=
+github.com/SaveTheRbtz/generic-sync-map-go v0.0.0-20220414055132-a37292614db8/go.mod h1:ihkm1viTbO/LOsgdGoFPBSvzqvx7ibvkMzYp3CgtHik=
+github.com/Shopify/sarama v1.19.0/go.mod h1:FVkBWblsNy7DGZRfXLU0O9RCGt5g3g3yEuWXgklEdEo=
+github.com/Shopify/toxiproxy v2.1.4+incompatible/go.mod h1:OXgGpZ6Cli1/URJOF1DMxUHB2q5Ap20/P/eIdh4G0pI=
+github.com/VividCortex/gohistogram v1.0.0/go.mod h1:Pf5mBqqDxYaXu3hDrrU+w6nw50o/4+TcAqDqk/vUH7g=
+github.com/XiaoMi/pegasus-go-client v0.0.0-20210427083443-f3b6b08bc4c2 h1:pami0oPhVosjOu/qRHepRmdjD6hGILF7DBr+qQZeP10=
+github.com/XiaoMi/pegasus-go-client v0.0.0-20210427083443-f3b6b08bc4c2/go.mod h1:jNIx5ykW1MroBuaTja9+VpglmaJOUzezumfhLlER3oY=
github.com/Yawning/aez v0.0.0-20180114000226-4dad034d9db2/go.mod h1:9pIqrY6SXNL8vjRQE5Hd/OL5GyK/9MrGUWs87z/eFfk=
+github.com/Yawning/aez v0.0.0-20211027044916-e49e68abd344/go.mod h1:9pIqrY6SXNL8vjRQE5Hd/OL5GyK/9MrGUWs87z/eFfk=
+github.com/aead/chacha20 v0.0.0-20180709150244-8b13a72661da h1:KjTM2ks9d14ZYCvmHS9iAKVt9AyzRSqNU1qabPih5BY=
github.com/aead/chacha20 v0.0.0-20180709150244-8b13a72661da/go.mod h1:eHEWzANqSiWQsof+nXEI9bUVUyV6F53Fp89EuCh2EAA=
github.com/aead/siphash v1.0.1 h1:FwHfE/T45KPKYuuSAKyyvE+oPWcaQ+CUmFW0bPlM+kg=
github.com/aead/siphash v1.0.1/go.mod h1:Nywa3cDsYNNK3gaciGTWPwHt0wlpNV15vwmswBAUSII=
+github.com/afex/hystrix-go v0.0.0-20180502004556-fa1af6a1f4f5/go.mod h1:SkGFH1ia65gfNATL8TAiHDNxPzPdmEL5uirI2Uyuz6c=
github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
+github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751 h1:JYp7IbQjafoB+tBA3gMyHYHrpOtNuDiK/uB5uXxq5wM=
+github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
+github.com/alecthomas/units v0.0.0-20190717042225-c3de453c63f4/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
+github.com/alecthomas/units v0.0.0-20190924025748-f65c72e2690d h1:UQZhZ2O0vMHr2cI+DC1Mbh0TJxzA3RcLoMsFw+aXw7E=
+github.com/alecthomas/units v0.0.0-20190924025748-f65c72e2690d/go.mod h1:rBZYJk541a8SKzHPHnH3zbiI+7dagKZ0cgpgrD7Fyho=
+github.com/allegro/bigcache/v2 v2.2.5 h1:mRc8r6GQjuJsmSKQNPsR5jQVXc8IJ1xsW5YXUYMLfqI=
+github.com/allegro/bigcache/v2 v2.2.5/go.mod h1:FppZsIO+IZk7gCuj5FiIDHGygD9xvWQcqg1uIPMb6tY=
+github.com/andybalholm/brotli v1.0.0/go.mod h1:loMXtMfwqflxFJPmdbJO0a3KNoPuLBgiu3qAvBg8x/Y=
+github.com/andybalholm/brotli v1.0.3 h1:fpcw+r1N1h0Poc1F/pHbW40cUm/lMEQslZtCkBQ0UnM=
+github.com/andybalholm/brotli v1.0.3/go.mod h1:fO7iG3H7G2nSZ7m0zPUDn85XEX2GTukHGRSepvi9Eig=
+github.com/andybalholm/cascadia v1.3.1 h1:nhxRkql1kdYCc8Snf7D5/D3spOX+dBgjA6u8x004T2c=
+github.com/andybalholm/cascadia v1.3.1/go.mod h1:R4bJ1UQfqADjvDa4P6HZHLh/3OxWWEqc0Sk8XGwHqvA=
+github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY=
+github.com/apache/thrift v0.12.0/go.mod h1:cp2SuWMxlEZw2r+iP2GNCdIi4C1qmUzdZFSVb+bacwQ=
+github.com/apache/thrift v0.13.0/go.mod h1:cp2SuWMxlEZw2r+iP2GNCdIi4C1qmUzdZFSVb+bacwQ=
+github.com/armon/circbuf v0.0.0-20150827004946-bbbad097214e/go.mod h1:3U/XgcO3hCbHZ8TKRvWD2dDTCfh9M9ya+I9JpbB7O8o=
+github.com/armon/go-metrics v0.0.0-20180917152333-f0300d1749da/go.mod h1:Q73ZrmVTwzkszR9V5SSuryQ31EELlFMUz1kKyl939pY=
+github.com/armon/go-radix v0.0.0-20180808171621-7fddfc383310/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8=
+github.com/aryann/difflib v0.0.0-20170710044230-e206f873d14a/go.mod h1:DAHtR1m6lCRdSC2Tm3DSWRPvIPr6xNKyeHdqDQSQT+A=
github.com/asaskevich/govalidator v0.0.0-20190424111038-f61b66f89f4a/go.mod h1:lB+ZfQJz7igIIfQNfa7Ml4HSf2uFQQRzpGGRXenZAgY=
+github.com/aws/aws-lambda-go v1.13.3/go.mod h1:4UKl9IzQMoD+QF79YdCuzCwp8VbmG4VAQwij/eHl5CU=
+github.com/aws/aws-sdk-go v1.27.0/go.mod h1:KmX6BPdI08NWTb3/sm4ZGu5ShLoqVDhKgpiN924inxo=
+github.com/aws/aws-sdk-go-v2 v0.18.0/go.mod h1:JWVYvqSMppoMJC0x5wdwiImzgXTI9FuZwxzkQq9wy+g=
+github.com/benbjohnson/clock v1.0.3/go.mod h1:bGMdMPoPVvcYyt1gHDf4J2KE153Yf9BuiUKYMaxlTDM=
github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q=
github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8=
+github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM=
+github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw=
+github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs=
+github.com/bketelsen/crypt v0.0.3-0.20200106085610-5cbc8cc4026c/go.mod h1:MKsuJmJgSg28kpZDP6UIiPt0e0Oz0kqKNGyRaWEPv84=
+github.com/bradfitz/gomemcache v0.0.0-20190913173617-a41fca850d0b h1:L/QXpzIa3pOvUGt1D1lA5KjYhPBAN/3iWdP7xeFS9F0=
+github.com/bradfitz/gomemcache v0.0.0-20190913173617-a41fca850d0b/go.mod h1:H0wQNHz2YrLsuXOZozoeDmnHXkNCRmMW0gwFWDfEZDA=
github.com/btcsuite/btcd v0.0.0-20190629003639-c26ffa870fd8/go.mod h1:3J08xEfcugPacsc34/LKRU2yO7YmuT8yt28J8k2+rrI=
github.com/btcsuite/btcd v0.0.0-20190824003749-130ea5bddde3/go.mod h1:3J08xEfcugPacsc34/LKRU2yO7YmuT8yt28J8k2+rrI=
github.com/btcsuite/btcd v0.20.1-beta/go.mod h1:wVuoA8VJLEcwgqHBwHmzLRazpKxTv13Px/pDuV7OomQ=
github.com/btcsuite/btcd v0.20.1-beta.0.20200513120220-b470eee47728/go.mod h1:wVuoA8VJLEcwgqHBwHmzLRazpKxTv13Px/pDuV7OomQ=
-github.com/btcsuite/btcd v0.20.1-beta.0.20200515232429-9f0179fd2c46 h1:QyTpiR5nQe94vza2qkvf7Ns8XX2Rjh/vdIhO3RzGj4o=
github.com/btcsuite/btcd v0.20.1-beta.0.20200515232429-9f0179fd2c46/go.mod h1:Yktc19YNjh/Iz2//CX0vfRTS4IJKM/RKO5YZ9Fn+Pgo=
+github.com/btcsuite/btcd v0.22.0-beta.0.20220111032746-97732e52810c/go.mod h1:tjmYdS6MLJ5/s0Fj4DbLgSbDHbEqLJrtnHecBFkdz5M=
+github.com/btcsuite/btcd v0.22.0-beta.0.20220204213055-eaf0459ff879/go.mod h1:osu7EoKiL36UThEgzYPqdRaxeo0NU8VoXqgcnwpey0g=
+github.com/btcsuite/btcd v0.22.0-beta.0.20220207191057-4dc4ff7963b4/go.mod h1:7alexyj/lHlOtr2PJK7L/+HDJZpcGDn/pAU98r7DY08=
+github.com/btcsuite/btcd v0.22.0-beta.0.20220316175102-8d5c75c28923/go.mod h1:taIcYprAW2g6Z9S0gGUxyR+zDwimyDMK5ePOX+iJ2ds=
+github.com/btcsuite/btcd v0.23.0/go.mod h1:0QJIIN1wwIXF/3G/m87gIwGniDMDQqjVn4SZgnFpsYY=
+github.com/btcsuite/btcd v0.23.1 h1:IB8cVQcC2X5mHbnfirLG5IZnkWYNTPlLZVrxUYSotbE=
+github.com/btcsuite/btcd v0.23.1/go.mod h1:0QJIIN1wwIXF/3G/m87gIwGniDMDQqjVn4SZgnFpsYY=
+github.com/btcsuite/btcd/btcec/v2 v2.1.0/go.mod h1:2VzYrv4Gm4apmbVVsSq5bqf1Ec8v56E48Vt0Y/umPgA=
+github.com/btcsuite/btcd/btcec/v2 v2.1.1/go.mod h1:ctjw4H1kknNJmRN4iP1R7bTQ+v3GJkZBd6mui8ZsAZE=
+github.com/btcsuite/btcd/btcec/v2 v2.1.3/go.mod h1:ctjw4H1kknNJmRN4iP1R7bTQ+v3GJkZBd6mui8ZsAZE=
+github.com/btcsuite/btcd/btcec/v2 v2.2.0 h1:fzn1qaOt32TuLjFlkzYSsBC35Q3KUjT1SwPxiMSCF5k=
+github.com/btcsuite/btcd/btcec/v2 v2.2.0/go.mod h1:U7MHm051Al6XmscBQ0BoNydpOTsFAn707034b5nY8zU=
+github.com/btcsuite/btcd/btcutil v1.0.0/go.mod h1:Uoxwv0pqYWhD//tfTiipkxNfdhG9UrLwaeswfjfdF0A=
+github.com/btcsuite/btcd/btcutil v1.1.0/go.mod h1:5OapHB7A2hBBWLm48mmw4MOHNJCcUBTwmWH/0Jn8VHE=
+github.com/btcsuite/btcd/btcutil v1.1.1 h1:hDcDaXiP0uEzR8Biqo2weECKqEw0uHDZ9ixIWevVQqY=
+github.com/btcsuite/btcd/btcutil v1.1.1/go.mod h1:nbKlBMNm9FGsdvKvu0essceubPiAcI57pYBNnsLAa34=
+github.com/btcsuite/btcd/btcutil/psbt v1.1.4 h1:Edx4AfBn+YPam2KP5AobDitulGp4r1Oibm8oruzkMdI=
+github.com/btcsuite/btcd/btcutil/psbt v1.1.4/go.mod h1:9AyU6EQVJ9Iw9zPyNT1lcdHd6cnEZdno5wLu5FY74os=
+github.com/btcsuite/btcd/chaincfg/chainhash v1.0.0/go.mod h1:7SFka0XMvUgj3hfZtydOrQY2mwhPclbT2snogU7SQQc=
+github.com/btcsuite/btcd/chaincfg/chainhash v1.0.1 h1:q0rUy8C/TYNBQS1+CGKw68tLOFYSNEs0TFnxxnS9+4U=
+github.com/btcsuite/btcd/chaincfg/chainhash v1.0.1/go.mod h1:7SFka0XMvUgj3hfZtydOrQY2mwhPclbT2snogU7SQQc=
github.com/btcsuite/btclog v0.0.0-20170628155309-84c8d2346e9f h1:bAs4lUbRJpnnkd9VhRV3jjAVU7DJVjMaK+IsvSeZvFo=
github.com/btcsuite/btclog v0.0.0-20170628155309-84c8d2346e9f/go.mod h1:TdznJufoqS23FtqVCzL0ZqgP5MqXbb4fg/WgDys70nA=
github.com/btcsuite/btcutil v0.0.0-20190425235716-9e5f4b9a998d/go.mod h1:+5NJ2+qvTyV9exUAL/rxXi3DcLg2Ts+ymUAY5y4NvMg=
-github.com/btcsuite/btcutil v1.0.2 h1:9iZ1Terx9fMIOtq1VrwdqfsATL9MC2l8ZrUY6YZ2uts=
github.com/btcsuite/btcutil v1.0.2/go.mod h1:j9HUFwoQRsZL3V4n+qG+CUnEGHOarIxfC3Le2Yhbcts=
github.com/btcsuite/btcutil/psbt v1.0.2/go.mod h1:LVveMu4VaNSkIRTZu2+ut0HDBRuYjqGocxDMNS1KuGQ=
-github.com/btcsuite/btcwallet v0.11.1-0.20200515224913-e0e62245ecbe h1:0m9uXDcnUc3Fv72635O/MfLbhbW+0hfSVgRiWezpkHU=
github.com/btcsuite/btcwallet v0.11.1-0.20200515224913-e0e62245ecbe/go.mod h1:9+AH3V5mcTtNXTKe+fe63fDLKGOwQbZqmvOVUef+JFE=
-github.com/btcsuite/btcwallet/wallet/txauthor v1.0.0 h1:KGHMW5sd7yDdDMkCZ/JpP0KltolFsQcB973brBnfj4c=
+github.com/btcsuite/btcwallet v0.15.1 h1:SKfh/l2Bgz9sJwHZvfiVbZ8Pl3N/8fFcWWXzsAPz9GU=
+github.com/btcsuite/btcwallet v0.15.1/go.mod h1:7OFsQ8ypiRwmr67hE0z98uXgJgXGAihE79jCib9x6ag=
github.com/btcsuite/btcwallet/wallet/txauthor v1.0.0/go.mod h1:VufDts7bd/zs3GV13f/lXc/0lXrPnvxD/NvmpG/FEKU=
-github.com/btcsuite/btcwallet/wallet/txrules v1.0.0 h1:2VsfS0sBedcM5KmDzRMT3+b6xobqWveZGvjb+jFez5w=
+github.com/btcsuite/btcwallet/wallet/txauthor v1.2.3 h1:M2yr5UlULvpqtxUqpMxTME/pA92Z9cpqeyvAFk9lAg0=
+github.com/btcsuite/btcwallet/wallet/txauthor v1.2.3/go.mod h1:T2xSiKGpUkSLCh68aF+FMXmKK9mFqNdHl9VaqOr+JjU=
github.com/btcsuite/btcwallet/wallet/txrules v1.0.0/go.mod h1:UwQE78yCerZ313EXZwEiu3jNAtfXj2n2+c8RWiE/WNA=
-github.com/btcsuite/btcwallet/wallet/txsizes v1.0.0 h1:6DxkcoMnCPY4E9cUDPB5tbuuf40SmmMkSQkoE8vCT+s=
+github.com/btcsuite/btcwallet/wallet/txrules v1.2.0 h1:BtEN5Empw62/RVnZ0VcJaVtVlBijnLlJY+dwjAye2Bg=
+github.com/btcsuite/btcwallet/wallet/txrules v1.2.0/go.mod h1:AtkqiL7ccKWxuLYtZm8Bu8G6q82w4yIZdgq6riy60z0=
github.com/btcsuite/btcwallet/wallet/txsizes v1.0.0/go.mod h1:pauEU8UuMFiThe5PB3EO+gO5kx87Me5NvdQDsTuq6cs=
+github.com/btcsuite/btcwallet/wallet/txsizes v1.1.0 h1:wZnOolEAeNOHzHTnznw/wQv+j35ftCIokNrnOTOU5o8=
+github.com/btcsuite/btcwallet/wallet/txsizes v1.1.0/go.mod h1:pauEU8UuMFiThe5PB3EO+gO5kx87Me5NvdQDsTuq6cs=
github.com/btcsuite/btcwallet/walletdb v1.0.0/go.mod h1:bZTy9RyYZh9fLnSua+/CD48TJtYJSHjjYcSaszuxCCk=
github.com/btcsuite/btcwallet/walletdb v1.2.0/go.mod h1:9cwc1Yyg4uvd4ZdfdoMnALji+V9gfWSMfxEdLdR5Vwc=
-github.com/btcsuite/btcwallet/walletdb v1.3.1 h1:lW1Ac3F1jJY4K11P+YQtRNcP5jFk27ASfrV7C6mvRU0=
github.com/btcsuite/btcwallet/walletdb v1.3.1/go.mod h1:9cwc1Yyg4uvd4ZdfdoMnALji+V9gfWSMfxEdLdR5Vwc=
+github.com/btcsuite/btcwallet/walletdb v1.3.5/go.mod h1:oJDxAEUHVtnmIIBaa22wSBPTVcs6hUp5NKWmI8xDwwU=
+github.com/btcsuite/btcwallet/walletdb v1.3.6-0.20210803004036-eebed51155ec/go.mod h1:oJDxAEUHVtnmIIBaa22wSBPTVcs6hUp5NKWmI8xDwwU=
+github.com/btcsuite/btcwallet/walletdb v1.4.0 h1:/C5JRF+dTuE2CNMCO/or5N8epsrhmSM4710uBQoYPTQ=
+github.com/btcsuite/btcwallet/walletdb v1.4.0/go.mod h1:oJDxAEUHVtnmIIBaa22wSBPTVcs6hUp5NKWmI8xDwwU=
github.com/btcsuite/btcwallet/wtxmgr v1.0.0/go.mod h1:vc4gBprll6BP0UJ+AIGDaySoc7MdAmZf8kelfNb8CFY=
-github.com/btcsuite/btcwallet/wtxmgr v1.1.1-0.20200515224913-e0e62245ecbe h1:yQbJVYfsKbdqDQNLxd4hhiLSiMkIygefW5mSHMsdKpc=
github.com/btcsuite/btcwallet/wtxmgr v1.1.1-0.20200515224913-e0e62245ecbe/go.mod h1:OwC0W0HhUszbWdvJvH6xvgabKSJ0lXl11YbmmqF9YXQ=
+github.com/btcsuite/btcwallet/wtxmgr v1.5.0 h1:WO0KyN4l6H3JWnlFxfGR7r3gDnlGT7W2cL8vl6av4SU=
+github.com/btcsuite/btcwallet/wtxmgr v1.5.0/go.mod h1:TQVDhFxseiGtZwEPvLgtfyxuNUDsIdaJdshvWzR0HJ4=
github.com/btcsuite/fastsha256 v0.0.0-20160815193821-637e65642941/go.mod h1:QcFA8DZHtuIAdYKCq/BzELOaznRsCvwf4zTPmaYwaig=
github.com/btcsuite/go-socks v0.0.0-20170105172521-4720035b7bfd h1:R/opQEbFEy9JGkIguV40SvRY1uliPX8ifOvi6ICsFCw=
github.com/btcsuite/go-socks v0.0.0-20170105172521-4720035b7bfd/go.mod h1:HHNXQzUsZCxOoE+CPiyCTO6x34Zs86zZUiwtpXoGdtg=
github.com/btcsuite/golangcrypto v0.0.0-20150304025918-53f62d9b43e8/go.mod h1:tYvUd8KLhm/oXvUeSEs2VlLghFjQt9+ZaF9ghH0JNjc=
github.com/btcsuite/goleveldb v0.0.0-20160330041536-7834afc9e8cd/go.mod h1:F+uVaaLLH7j4eDXPRvw78tMflu7Ie2bzYOH4Y8rRKBY=
-github.com/btcsuite/goleveldb v1.0.0 h1:Tvd0BfvqX9o823q1j2UZ/epQo09eJh6dTcRp79ilIN4=
github.com/btcsuite/goleveldb v1.0.0/go.mod h1:QiK9vBlgftBg6rWQIj6wFzbPfRjiykIEhBH4obrXJ/I=
github.com/btcsuite/snappy-go v0.0.0-20151229074030-0bdef8d06723/go.mod h1:8woku9dyThutzjeg+3xrA5iCpBRH8XEEg3lh6TiUghc=
-github.com/btcsuite/snappy-go v1.0.0 h1:ZxaA6lo2EpxGddsA8JwWOcxlzRybb444sgmeJQMJGQE=
github.com/btcsuite/snappy-go v1.0.0/go.mod h1:8woku9dyThutzjeg+3xrA5iCpBRH8XEEg3lh6TiUghc=
github.com/btcsuite/websocket v0.0.0-20150119174127-31079b680792 h1:R8vQdOQdZ9Y3SkEwmHoWBmX1DNXhXZqlTpq6s4tyJGc=
github.com/btcsuite/websocket v0.0.0-20150119174127-31079b680792/go.mod h1:ghJtEyQwv5/p4Mg4C0fgbePVuGr935/5ddU9Z3TmDRY=
+github.com/btcsuite/winsvc v1.0.0 h1:J9B4L7e3oqhXOcm+2IuNApwzQec85lE+QaikUcCs+dk=
github.com/btcsuite/winsvc v1.0.0/go.mod h1:jsenWakMcC0zFBFurPLEAyrnc/teJEM1O46fmI40EZs=
+github.com/casbin/casbin/v2 v2.1.2/go.mod h1:YcPU1XXisHhLzuxH9coDNf2FbKpjGlbCg3n9yuLkIJQ=
+github.com/cenkalti/backoff v2.2.1+incompatible/go.mod h1:90ReRw6GdpyfrHakVjL/QHaoyV4aDUVVkXQJJJ3NXXM=
+github.com/cenkalti/backoff/v4 v4.1.0 h1:c8LkOFQTzuO0WBM/ae5HdGQuZPfPxp7lqBRwQRm4fSc=
+github.com/cenkalti/backoff/v4 v4.1.0/go.mod h1:scbssz8iZGpm3xbr14ovlUdkxfGXNInqkPWOWmG2CLw=
+github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
+github.com/certifi/gocertifi v0.0.0-20191021191039-0944d244cd40/go.mod h1:sGbDF6GwGcLpkNXPUTkMRoywsNa/ol15pxFe6ERfguA=
+github.com/certifi/gocertifi v0.0.0-20200922220541-2c3bb06c6054/go.mod h1:sGbDF6GwGcLpkNXPUTkMRoywsNa/ol15pxFe6ERfguA=
+github.com/cespare/xxhash v1.1.0 h1:a6HrQnmkObjyL+Gs60czilIUGqrzKutQD6XZog3p+ko=
github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc=
+github.com/cespare/xxhash/v2 v2.1.1 h1:6MnRN8NT7+YBpUIWxHtefFZOKTAPgGjpQSxqLNn0+qY=
+github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
+github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI=
+github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI=
+github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU=
+github.com/clbanning/x2j v0.0.0-20191024224557-825249438eec/go.mod h1:jMjuTZXRI4dUb/I5gc9Hdhagfvm9+RyrPryS/auMzxE=
github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
-github.com/coreos/bbolt v1.3.3 h1:n6AiVyVRKQFNb6mJlwESEvvLoDyiTzXX7ORAUlkeBdY=
+github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc=
+github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk=
+github.com/cockroachdb/apd v1.1.0/go.mod h1:8Sl8LxpKi29FqWXR16WEFZRNSz3SoPzUzeMeY4+DwBQ=
+github.com/cockroachdb/datadriven v0.0.0-20190809214429-80d97fb3cbaa/go.mod h1:zn76sxSg3SzpJ0PPJaLDCu+Bu0Lg3sKTORVIj19EIF8=
+github.com/cockroachdb/datadriven v0.0.0-20200714090401-bf6692d28da5/go.mod h1:h6jFvWxBdQXxjopDMZyH2UVceIRfR84bdzbkoKrsWNo=
+github.com/cockroachdb/errors v1.2.4/go.mod h1:rQD95gz6FARkaKkQXUksEje/d9a6wBJoCr5oaCLELYA=
+github.com/cockroachdb/logtags v0.0.0-20190617123548-eb05cc24525f/go.mod h1:i/u985jwjWRlyHXQbwatDASoW0RMlZ/3i9yJHE2xLkI=
+github.com/codahale/hdrhistogram v0.0.0-20161010025455-3a0bb77429bd/go.mod h1:sE/e/2PUdi/liOCUjSTXgM1o87ZssimdTWN964YiIeI=
+github.com/coocood/freecache v1.1.1 h1:uukNF7QKCZEdZ9gAV7WQzvh0SbjwdMF6m3x3rxEkaPc=
+github.com/coocood/freecache v1.1.1/go.mod h1:OKrEjkGVoxZhyWAJoeFi5BMLUJm2Tit0kpGkIr7NGYY=
+github.com/coreos/bbolt v1.3.2/go.mod h1:iRUV2dpdMOn7Bo10OQBFzIJO9kkE559Wcmn+qkEiiKk=
github.com/coreos/bbolt v1.3.3/go.mod h1:iRUV2dpdMOn7Bo10OQBFzIJO9kkE559Wcmn+qkEiiKk=
+github.com/coreos/etcd v3.3.13+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE=
+github.com/coreos/go-semver v0.2.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk=
+github.com/coreos/go-semver v0.3.0 h1:wkHLiw0WNATZnSG7epLsujiMCgPAc9xhjJ4tgnAxmfM=
+github.com/coreos/go-semver v0.3.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk=
+github.com/coreos/go-systemd v0.0.0-20180511133405-39ca1b05acc7/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4=
+github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4=
+github.com/coreos/go-systemd v0.0.0-20190719114852-fd7a80b32e1f h1:JOrtw2xFKzlg+cbHpyrpLDmnN1HqhBfnX7WDiW7eG2c=
+github.com/coreos/go-systemd v0.0.0-20190719114852-fd7a80b32e1f/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4=
+github.com/coreos/go-systemd/v22 v22.3.2 h1:D9/bQk5vlXQFZ6Kwuu6zaiXJ9oTPe68++AzAJc1DzSI=
+github.com/coreos/go-systemd/v22 v22.3.2/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc=
+github.com/coreos/pkg v0.0.0-20160727233714-3ac0863d7acf/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA=
+github.com/coreos/pkg v0.0.0-20180928190104-399ea9e2e55f/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA=
+github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU=
+github.com/cpuguy83/go-md2man/v2 v2.0.0/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU=
+github.com/creack/pty v1.1.7/go.mod h1:lj5s0c3V2DBrqTV7llrYr5NG6My20zk30Fl46Y7DoTY=
+github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
+github.com/creack/pty v1.1.11/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
+github.com/davecgh/go-spew v0.0.0-20151105211317-5215b55f46b2/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v0.0.0-20171005155431-ecdeabc65495/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
+github.com/decred/dcrd/crypto/blake256 v1.0.0 h1:/8DMNYp9SGi5f0w7uCm6d6M4OU2rGFK09Y2A4Xv7EE0=
+github.com/decred/dcrd/crypto/blake256 v1.0.0/go.mod h1:sQl2p6Y26YV+ZOcSTP6thNdn47hh8kt6rqSlvmrXFAc=
+github.com/decred/dcrd/dcrec/secp256k1/v4 v4.0.1 h1:YLtO71vCjJRCBcrPMtQ9nqBsqpA1m5sE92cU+pd5Mcc=
+github.com/decred/dcrd/dcrec/secp256k1/v4 v4.0.1/go.mod h1:hyedUtir6IdtD/7lIxGeCxkaw7y45JueMRL4DIyJDKs=
+github.com/decred/dcrd/lru v1.0.0 h1:Kbsb1SFDsIlaupWPwsPp+dkxiBY1frcS07PCPgotKz8=
+github.com/decred/dcrd/lru v1.0.0/go.mod h1:mxKOwFd7lFjN2GZYsiz/ecgqR6kkYAl+0pz0tEMk218=
+github.com/dgraph-io/ristretto v0.0.3 h1:jh22xisGBjrEVnRZ1DVTpBVQm0Xndu8sMl0CWDzSIBI=
+github.com/dgraph-io/ristretto v0.0.3/go.mod h1:KPxhHT9ZxKefz+PCeOGsrHpl1qZ7i70dGTu2u+Ahh6E=
+github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ=
+github.com/dgryski/go-farm v0.0.0-20190423205320-6a90982ecee2/go.mod h1:SqUrOPUnsFjfmXRMNPybcSiG0BgUW2AuFH8PAnS2iTw=
+github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f h1:lO4WD4F/rVNCu3HqELle0jiPLLBs70cWOduZpkS1E78=
+github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f/go.mod h1:cuUVRXasLTGF7a8hSLbxyZXjz+1KgoB3wDUb6vlszIc=
github.com/dgryski/go-sip13 v0.0.0-20181026042036-e10d5fee7954/go.mod h1:vAd38F8PWV+bWy6jNmig1y/TA+kYO4g3RSRF0IAv0no=
-github.com/fiatjaf/go-lnurl v1.4.0 h1:hVFEEJD2A9D6ojEcqLyD54CM2ZJ9Tzs2jNKw/GNq52A=
-github.com/fiatjaf/go-lnurl v1.4.0/go.mod h1:BqA8WXAOzntF7Z3EkVO7DfP4y5rhWUmJ/Bu9KBke+rs=
+github.com/docker/spdystream v0.0.0-20160310174837-449fdfce4d96/go.mod h1:Qh8CwZgvJUkLughtfhJv5dyTYa91l1fOUCrgjqmcifM=
+github.com/dsnet/compress v0.0.1 h1:PlZu0n3Tuv04TzpfPbrnI0HW/YwodEXDS+oPKahKF0Q=
+github.com/dsnet/compress v0.0.1/go.mod h1:Aw8dCMJ7RioblQeTqt88akK31OvO8Dhf5JflhBbQEHo=
+github.com/dsnet/golib v0.0.0-20171103203638-1ea166775780/go.mod h1:Lj+Z9rebOhdfkVLjJ8T6VcRQv3SXugXy999NBtR9aFY=
+github.com/dustin/go-humanize v0.0.0-20171111073723-bb3d318650d4/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk=
+github.com/dustin/go-humanize v1.0.0 h1:VSnTsYCnlFHaM2/igO1h6X3HA71jcobQuxemgkq4zYo=
+github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk=
+github.com/dvyukov/go-fuzz v0.0.0-20210602112143-b1f3d6f4ef4e h1:qTP1telKJHlToHlwPQNmVg4yfMDMHe4Z3SYmzkrvA2M=
+github.com/dvyukov/go-fuzz v0.0.0-20210602112143-b1f3d6f4ef4e/go.mod h1:11Gm+ccJnvAhCNLlf5+cS9KjtbaD5I5zaZpFMsTHWTw=
+github.com/eapache/go-resiliency v1.1.0/go.mod h1:kFI+JgMyC7bLPUVY133qvEBtVayf5mFgVsvEsIPBvNs=
+github.com/eapache/go-xerial-snappy v0.0.0-20180814174437-776d5712da21/go.mod h1:+020luEh2TKB4/GOp8oxxtq0Daoen/Cii55CzbTV6DU=
+github.com/eapache/queue v1.1.0/go.mod h1:6eCeP0CKFpHLu8blIFXhExK/dRa7WDZfr6jVFPTqq+I=
+github.com/edsrzf/mmap-go v1.0.0/go.mod h1:YO35OhQPt3KJa3ryjFM5Bs14WD66h8eGKpfaBNrHW5M=
+github.com/eko/gocache v1.2.0 h1:SCtTs65qMXjhdtu62yHPCQuzdMkQjP+fQmkNrVutkRw=
+github.com/eko/gocache v1.2.0/go.mod h1:6u8/2bnr+nOf87mRXWS710rqNNZUECF4CGsPNnsoJ78=
+github.com/elazarl/goproxy v0.0.0-20170405201442-c4fc26588b6e/go.mod h1:/Zj4wYkgs4iZTTu3o/KG3Itv/qCCa8VVMlb3i9OVuzc=
+github.com/emicklei/go-restful v0.0.0-20170410110728-ff4f55a20633/go.mod h1:otzb+WCGbkyDHkqmQmT5YD2WR4BBwUdeQoFo8l/7tVs=
+github.com/envoyproxy/go-control-plane v0.6.9/go.mod h1:SBwIajubJHhxtWwsL9s8ss4safvEdbitLhGGK48rN6g=
+github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
+github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
+github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98=
+github.com/envoyproxy/go-control-plane v0.9.9-0.20201210154907-fd9021fe5dad/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk=
+github.com/envoyproxy/go-control-plane v0.9.9-0.20210217033140-668b12f5399d/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk=
+github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
+github.com/evanphx/json-patch v4.2.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk=
+github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4=
+github.com/fatih/color v1.10.0/go.mod h1:ELkj/draVOlAH/xkhN6mQ50Qd0MPOk5AAr3maGEBuJM=
+github.com/fatih/color v1.13.0/go.mod h1:kLAiJbzzSOZDVNGyDpeOxJ47H46qBXwg5ILebYFFOfk=
+github.com/fergusstrange/embedded-postgres v1.10.0 h1:YnwF6xAQYmKLAXXrrRx4rHDLih47YJwVPvg8jeKfdNg=
+github.com/fergusstrange/embedded-postgres v1.10.0/go.mod h1:a008U8/Rws5FtIOTGYDYa7beVWsT3qVKyqExqYYjL+c=
+github.com/fiatjaf/go-lnurl v1.11.3-0.20220819192234-5c5819dd0aa7 h1:0m0ph1FcZY7p89OpaXOUs8N44GhP7gE/98l+eWCNSkQ=
+github.com/fiatjaf/go-lnurl v1.11.3-0.20220819192234-5c5819dd0aa7/go.mod h1:KJfs14iAY3gCgt/3T6fxfBvPhU67OfIp7PSrBg/v/R8=
github.com/fiatjaf/ln-decodepay v1.1.0 h1:HigjqNH+ApiO6gm7RV23jXNFuvwq+zgsWl4BJAfPWwE=
github.com/fiatjaf/ln-decodepay v1.1.0/go.mod h1:2qdTT95b8Z4dfuxiZxXuJ1M7bQ9CrLieEA1DKC50q6s=
+github.com/form3tech-oss/jwt-go v3.2.3+incompatible h1:7ZaBxOI7TMoYBfyA3cQHErNNyAWIKUMIwqxEtgHOs5c=
+github.com/form3tech-oss/jwt-go v3.2.3+incompatible/go.mod h1:pbq4aXjuKjdthFRnoDwaVPLA+WlJuPGy+QneDUgJi2k=
+github.com/fortytw2/leaktest v1.3.0 h1:u8491cBMTQ8ft8aeV+adlcytMZylmA5nnwwkRZjI8vw=
+github.com/fortytw2/leaktest v1.3.0/go.mod h1:jDsjWgpAGjm2CA7WthBh/CdZYEPF31XHquHwclZch5g=
+github.com/franela/goblin v0.0.0-20200105215937-c9ffbefa60db/go.mod h1:7dvUGVsVBjqR7JHJk0brhHOZYGmfBYOrK0ZhYMEtBr4=
+github.com/franela/goreq v0.0.0-20171204163338-bcd34c9993f8/go.mod h1:ZhphrRTfi2rbfLwlschooIH4+wKKDR4Pdxhh+TRoA20=
github.com/frankban/quicktest v1.2.2/go.mod h1:Qh/WofXFeiAFII1aEBu529AtJo6Zg2VHscnEsbBnJ20=
-github.com/fsnotify/fsnotify v1.4.7 h1:IXs+QLmnXW2CcXuY+8Mzv/fWEsPGWxqefPtCP5CnV9I=
+github.com/frankban/quicktest v1.14.3 h1:FJKSZTDHjyhriyC81FLQ0LY93eSai0ZyR/ZIkd3ZUKE=
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
+github.com/fsnotify/fsnotify v1.4.9 h1:hsms1Qyu0jgnwNXIxa+/V/PDsU6CfLf6CNO8H7IWoS4=
+github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ=
+github.com/getsentry/raven-go v0.2.0/go.mod h1:KungGk8q33+aIAZUIVWZDr2OfAEBsO49PX4NzFV5kcQ=
+github.com/ghodss/yaml v0.0.0-20150909031657-73d445a93680/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
github.com/go-errors/errors v1.0.1 h1:LUHzmkK3GUKUrL/1gfBUxAHzcev3apQlezX/+O7ma6w=
github.com/go-errors/errors v1.0.1/go.mod h1:f4zRHt4oKfwPJE5k8C9vpYG+aDHdBFUsgrm6/TyX73Q=
+github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU=
+github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
+github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
+github.com/go-kit/kit v0.9.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
+github.com/go-kit/kit v0.10.0/go.mod h1:xUsJbQ/Fp4kEt7AFgCuvyX4a71u8h9jB8tj/ORgOZ7o=
+github.com/go-kit/log v0.1.0/go.mod h1:zbhenjAZHb184qTLMA9ZjW7ThYL0H2mk7Q6pNt4vbaY=
github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE=
github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk=
+github.com/go-logfmt/logfmt v0.5.0/go.mod h1:wCYkCAKZfumFQihp8CzCvQ3paCTfi41vtzG1KdI/P7A=
+github.com/go-logr/logr v0.1.0/go.mod h1:ixOQHD9gLJUVQQ2ZOR7zLEifBX6tGkNJF4QyIY7sIas=
github.com/go-openapi/errors v0.19.2/go.mod h1:qX0BLWsyaKfvhluLejVpVNwNRdXZhEbTA4kxxpKBC94=
+github.com/go-openapi/jsonpointer v0.0.0-20160704185906-46af16f9f7b1/go.mod h1:+35s3my2LFTysnkMfxsJBAMHj/DoqoB9knIWoYG/Vk0=
+github.com/go-openapi/jsonreference v0.0.0-20160704190145-13c6e3589ad9/go.mod h1:W3Z9FmVs9qj+KR4zFKmDPGiLdk1D9Rlm7cyMvf57TTg=
+github.com/go-openapi/spec v0.0.0-20160808142527-6aced65f8501/go.mod h1:J8+jY1nAiCcj+friV/PDoE1/3eeccG9LYBs0tYvLOWc=
github.com/go-openapi/strfmt v0.19.5/go.mod h1:eftuHTlB/dI8Uq8JJOyRlieZf+WkkxUuk0dgdHXr2Qk=
+github.com/go-openapi/swag v0.0.0-20160704191624-1d0bd113de87/go.mod h1:DXUve3Dpr1UfpPtxFw+EFuQ41HhCWZfha5jSVRG7C7I=
+github.com/go-playground/assert/v2 v2.0.1/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4=
+github.com/go-playground/locales v0.13.0/go.mod h1:taPMhCMXrRLJO55olJkUXHZBHCxTMfnGwq/HNwmWNS8=
+github.com/go-playground/universal-translator v0.17.0/go.mod h1:UkSxE5sNxxRwHyU+Scu5vgOQjsIJAF8j9muTVoKLVtA=
+github.com/go-playground/validator/v10 v10.4.1/go.mod h1:nlOn6nFhuKACm19sB/8EGNn9GlaMV7XkbRSipzJ0Ii4=
+github.com/go-redis/redis/v8 v8.8.2 h1:O/NcHqobw7SEptA0yA6up6spZVFtwE06SXM8rgLtsP8=
+github.com/go-redis/redis/v8 v8.8.2/go.mod h1:F7resOH5Kdug49Otu24RjHWwgK7u9AmtqWMnCV1iP5Y=
+github.com/go-sql-driver/mysql v1.4.0/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w=
github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=
+github.com/goccy/go-yaml v1.9.5/go.mod h1:U/jl18uSupI5rdI2jmuCswEA2htH9eXfferR3KfscvA=
+github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
+github.com/gofrs/uuid v4.0.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM=
+github.com/gogo/googleapis v1.1.0/go.mod h1:gf4bu3Q80BeJ6H1S1vYPm8/ELATdvryBaNFGgqEef3s=
github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
+github.com/gogo/protobuf v1.2.0/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
+github.com/gogo/protobuf v1.2.1/go.mod h1:hp+jE20tsWTFYpLwKvXlhS1hjn+gTNwPg2I6zVXpSg4=
+github.com/gogo/protobuf v1.2.2-0.20190723190241-65acae22fc9d/go.mod h1:SlYgWuQ5SjCEi6WLHjHCa1yvBfUnHcTbrrZtXPKa29o=
+github.com/gogo/protobuf v1.3.1/go.mod h1:SlYgWuQ5SjCEi6WLHjHCa1yvBfUnHcTbrrZtXPKa29o=
+github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q=
+github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q=
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
+github.com/golang/glog v0.0.0-20210429001901-424d2337a529/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
+github.com/golang/groupcache v0.0.0-20160516000752-02826c3e7903/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
+github.com/golang/groupcache v0.0.0-20190129154638-5b532d6fd5ef/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
+github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
+github.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
+github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
+github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
github.com/golang/lint v0.0.0-20180702182130-06c8688daad7/go.mod h1:tluoj9z5200jBnyusfRPU2LqT6J+DAorxEvtC7LHB+E=
github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
+github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
+github.com/golang/mock v1.3.1/go.mod h1:sBzyDLLjw3U8JLTeZvSv8jJB+tU5PVekmnlKIyFUx0Y=
+github.com/golang/mock v1.4.0/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw=
+github.com/golang/mock v1.4.1/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw=
+github.com/golang/mock v1.4.3/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw=
+github.com/golang/mock v1.4.4/go.mod h1:l3mdAwkq5BuhzHwde/uurv3sEJeZMXNpwsxVWU71h+4=
+github.com/golang/mock v1.5.0 h1:jlYHihg//f7RRwuPfptm04yp4s7O6Kw8EZiVYIGcH0g=
+github.com/golang/mock v1.5.0/go.mod h1:CWnOUgYIOo4TcNZ0wHX3YZCqsaM1I1Jvs6v3mP3KVu8=
+github.com/golang/protobuf v0.0.0-20161109072736-4bd1920723d7/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
-github.com/golang/protobuf v1.3.1 h1:YF8+flBXS5eO826T4nzqPrxfhQThhXl0YzfuUPu4SBg=
github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
+github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
+github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw=
+github.com/golang/protobuf v1.3.4/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw=
+github.com/golang/protobuf v1.3.5/go.mod h1:6O5/vntMXwX2lRkT1hjjk0nAC1IDOTvTlVgjlRvqsdk=
+github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8=
+github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA=
+github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs=
+github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w=
+github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0=
+github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8=
+github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
+github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
+github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
+github.com/golang/protobuf v1.5.1/go.mod h1:DopwsBzvsk0Fs44TXzsVbJyPhcCPeIwnvohx4u74HPM=
+github.com/golang/protobuf v1.5.2 h1:ROPKBNFfQgOUMifHyP+KYbvpjbdoFNs+aK7DXlji0Tw=
+github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
+github.com/golang/snappy v0.0.0-20180518054509-2e65f85255db/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
+github.com/golang/snappy v0.0.1/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
+github.com/golang/snappy v0.0.4 h1:yAGX7huGHXlcLOEtBnF4w7FQwA26wojNCwOYAEhLjQM=
+github.com/golang/snappy v0.0.4/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
+github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
+github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
+github.com/google/btree v1.0.1 h1:gK4Kx5IaGY9CD5sPJ36FHiBJ6ZXl0kilRiiCj+jdYp4=
+github.com/google/btree v1.0.1/go.mod h1:xXMiIv4Fb/0kKde4SpL7qlzvu5cMJDRkFDxJfI9uaxA=
+github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
github.com/google/go-cmp v0.2.1-0.20190312032427-6f77996f0c42/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
+github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
+github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
+github.com/google/go-cmp v0.4.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
+github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
+github.com/google/go-cmp v0.5.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
+github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
+github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
+github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
+github.com/google/go-cmp v0.5.8 h1:e6P7q2lk1O+qJJb4BtCQXlK8vWEO8V1ZeuEdJNOqZyg=
+github.com/google/gofuzz v0.0.0-20161122191042-44d81051d367/go.mod h1:HP5RmnzzSNb993RKQDq4+1A4ia9nllfqcQFTQJedwGI=
+github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
+github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs=
+github.com/google/martian/v3 v3.0.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0=
+github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=
+github.com/google/pprof v0.0.0-20190515194954-54271f7e092f/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=
+github.com/google/pprof v0.0.0-20191218002539-d4f498aebedc/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
+github.com/google/pprof v0.0.0-20200212024743-f11f1df84d12/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
+github.com/google/pprof v0.0.0-20200229191704-1ebb73c60ed3/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
+github.com/google/pprof v0.0.0-20200430221834-fc25d7d30c6d/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
+github.com/google/pprof v0.0.0-20200708004538-1a94d8640e99/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
+github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI=
+github.com/google/uuid v1.0.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
+github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
+github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg=
+github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk=
+github.com/googleapis/gnostic v0.0.0-20170729233727-0c5108395e2d/go.mod h1:sJBsCZ4ayReDTBIg8b9dl28c5xFWyhBTVRp3pOg5EKY=
+github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1 h1:EGx4pi6eqNxGaHF6qqu48+N2wcFQ5qg5FXgOdqsJ5d8=
+github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=
+github.com/gorilla/context v1.1.1/go.mod h1:kBGZzfjB9CEq2AlWe17Uuf7NDRt0dE0s8S51q0aT7Yg=
+github.com/gorilla/mux v1.6.2/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs=
+github.com/gorilla/mux v1.7.3/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs=
github.com/gorilla/mux v1.8.0 h1:i40aqfkR1h2SlN9hojwV5ZA91wcXFOvkdNIeFDP5koI=
github.com/gorilla/mux v1.8.0/go.mod h1:DVbg23sWSpFRCP0SfiEN6jmj59UnW/n46BH5rLB71So=
+github.com/gorilla/websocket v0.0.0-20170926233335-4201258b820c/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ=
+github.com/gorilla/websocket v1.4.2 h1:+/TMaTYc4QFitKJxsQ7Yye35DkWvkdLcvGKqM+x0Ufc=
+github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
github.com/grpc-ecosystem/go-grpc-middleware v1.0.0/go.mod h1:FiyG127CGDf3tlThmgyCl78X/SZQqEOJBCDaAfeWzPs=
+github.com/grpc-ecosystem/go-grpc-middleware v1.0.1-0.20190118093823-f849b5445de4/go.mod h1:FiyG127CGDf3tlThmgyCl78X/SZQqEOJBCDaAfeWzPs=
+github.com/grpc-ecosystem/go-grpc-middleware v1.3.0 h1:+9834+KizmvFV7pXQGSXQTsaWhq2GjuNUt0aUU0YBYw=
+github.com/grpc-ecosystem/go-grpc-middleware v1.3.0/go.mod h1:z0ButlSOZa5vEBq9m2m2hlwIgKw+rp3sdCBRoJY+30Y=
+github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0 h1:Ovs26xHkKqVztRpIrF/92BcuyuQ/YW4NSIpoGtfXNho=
github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0/go.mod h1:8NvIoxWQoOIhqOTXgfV/d3M/q6VIi02HzZEHgUlZvzk=
github.com/grpc-ecosystem/grpc-gateway v1.8.6/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY=
-github.com/hpcloud/tail v1.0.0 h1:nfCOvKYfkgYP8hkirhJocXT2+zOD8yUNjXaWfTlyFKI=
+github.com/grpc-ecosystem/grpc-gateway v1.9.0/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY=
+github.com/grpc-ecosystem/grpc-gateway v1.9.5/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY=
+github.com/grpc-ecosystem/grpc-gateway v1.16.0 h1:gmcG1KaJ57LophUzW0Hy8NmPhnMZb4M0+kPpLofRdBo=
+github.com/grpc-ecosystem/grpc-gateway v1.16.0/go.mod h1:BDjrQk3hbvj6Nolgz8mAMFbcEtjT1g+wF4CSlocrBnw=
+github.com/grpc-ecosystem/grpc-gateway/v2 v2.5.0/go.mod h1:r1hZAcvfFXuYmcKyCJI9wlyOPIZUJl6FCB8Cpca/NLE=
+github.com/hashicorp/consul/api v1.1.0/go.mod h1:VmuI/Lkw1nC05EYQWNKwWGbkg+FbDBtguAZLlVdkD9Q=
+github.com/hashicorp/consul/api v1.3.0/go.mod h1:MmDNSzIMUjNpY/mQ398R4bk2FnqQLoPndWW5VkKPlCE=
+github.com/hashicorp/consul/sdk v0.1.1/go.mod h1:VKf9jXwCTEY1QZP2MOLRhb5i/I/ssyNV1vwHyQBF0x8=
+github.com/hashicorp/consul/sdk v0.3.0/go.mod h1:VKf9jXwCTEY1QZP2MOLRhb5i/I/ssyNV1vwHyQBF0x8=
+github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4=
+github.com/hashicorp/go-cleanhttp v0.5.1/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80=
+github.com/hashicorp/go-immutable-radix v1.0.0/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60=
+github.com/hashicorp/go-msgpack v0.5.3/go.mod h1:ahLV/dePpqEmjfWmKiqvPkv/twdG7iPBM1vqhUKIvfM=
+github.com/hashicorp/go-multierror v1.0.0/go.mod h1:dHtQlpGsu+cZNNAkkCN/P3hoUDHhCYQXV3UM06sGGrk=
+github.com/hashicorp/go-rootcerts v1.0.0/go.mod h1:K6zTfqpRlCUIjkwsN4Z+hiSfzSTQa6eBIzfwKfwNnHU=
+github.com/hashicorp/go-sockaddr v1.0.0/go.mod h1:7Xibr9yA9JjQq1JpNB2Vw7kxv8xerXegt+ozgdvDeDU=
+github.com/hashicorp/go-syslog v1.0.0/go.mod h1:qPfqrKkXGihmCqbJM2mZgkZGvKG1dFdvsLplgctolz4=
+github.com/hashicorp/go-uuid v1.0.0/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro=
+github.com/hashicorp/go-uuid v1.0.1/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro=
+github.com/hashicorp/go-version v1.2.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA=
+github.com/hashicorp/go.net v0.0.1/go.mod h1:hjKkEWcCURg++eb33jQU7oqQcI9XDCnUzHA0oac0k90=
+github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
+github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
+github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ=
+github.com/hashicorp/logutils v1.0.0/go.mod h1:QIAnNjmIWmVIIkWDTG1z5v++HQmx9WQRO+LraFDTW64=
+github.com/hashicorp/mdns v1.0.0/go.mod h1:tL+uN++7HEJ6SQLQ2/p+z2pH24WQKWjBPkE0mNTz8vQ=
+github.com/hashicorp/memberlist v0.1.3/go.mod h1:ajVTdAv/9Im8oMAAj5G31PhhMCZJV2pPBoIllUwCN7I=
+github.com/hashicorp/serf v0.8.2/go.mod h1:6hOLApaqBFA1NXqRQAsxw9QxuDEvNxSQRwA/JwenrHc=
github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=
+github.com/hudl/fargo v1.3.0/go.mod h1:y3CKSmjA+wD2gak7sUSXTAoopbhU08POFhmITJgmKTg=
+github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=
github.com/imroc/req v0.3.0 h1:3EioagmlSG+z+KySToa+Ylo3pTFZs+jh3Brl7ngU12U=
github.com/imroc/req v0.3.0/go.mod h1:F+NZ+2EFSo6EFXdeIbpfE9hcC233id70kf0byW97Caw=
+github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8=
+github.com/influxdata/influxdb1-client v0.0.0-20191209144304-8bf82d3c094d/go.mod h1:qj24IKcXYK6Iy9ceXlo3Tc+vtHo9lIhSX5JddghvEPo=
+github.com/jackc/chunkreader v1.0.0 h1:4s39bBR8ByfqH+DKm8rQA3E1LHZWB9XWcrz8fqaZbe0=
+github.com/jackc/chunkreader v1.0.0/go.mod h1:RT6O25fNZIuasFJRyZ4R/Y2BbhasbmZXF9QQ7T3kePo=
+github.com/jackc/chunkreader/v2 v2.0.0/go.mod h1:odVSm741yZoC3dpHEUXIqA9tQRhFrgOHwnPIn9lDKlk=
+github.com/jackc/chunkreader/v2 v2.0.1 h1:i+RDz65UE+mmpjTfyz0MoVTnzeYxroil2G82ki7MGG8=
+github.com/jackc/chunkreader/v2 v2.0.1/go.mod h1:odVSm741yZoC3dpHEUXIqA9tQRhFrgOHwnPIn9lDKlk=
+github.com/jackc/pgconn v0.0.0-20190420214824-7e0022ef6ba3/go.mod h1:jkELnwuX+w9qN5YIfX0fl88Ehu4XC3keFuOJJk9pcnA=
+github.com/jackc/pgconn v0.0.0-20190824142844-760dd75542eb/go.mod h1:lLjNuW/+OfW9/pnVKPazfWOgNfH2aPem8YQ7ilXGvJE=
+github.com/jackc/pgconn v0.0.0-20190831204454-2fabfa3c18b7/go.mod h1:ZJKsE/KZfsUgOEh9hBm+xYTstcNHg7UPMVJqRfQxq4s=
+github.com/jackc/pgconn v1.8.0/go.mod h1:1C2Pb36bGIP9QHGBYCjnyhqu7Rv3sGshaQUvmfGIB/o=
+github.com/jackc/pgconn v1.9.0/go.mod h1:YctiPyvzfU11JFxoXokUOOKQXQmDMoJL9vJzHH8/2JY=
+github.com/jackc/pgconn v1.9.1-0.20210724152538-d89c8390a530/go.mod h1:4z2w8XhRbP1hYxkpTuBjTS3ne3J48K83+u0zoyvg2pI=
+github.com/jackc/pgconn v1.10.0 h1:4EYhlDVEMsJ30nNj0mmgwIUXoq7e9sMJrVC2ED6QlCU=
+github.com/jackc/pgconn v1.10.0/go.mod h1:4z2w8XhRbP1hYxkpTuBjTS3ne3J48K83+u0zoyvg2pI=
+github.com/jackc/pgio v1.0.0 h1:g12B9UwVnzGhueNavwioyEEpAmqMe1E/BN9ES+8ovkE=
+github.com/jackc/pgio v1.0.0/go.mod h1:oP+2QK2wFfUWgr+gxjoBH9KGBb31Eio69xUb0w5bYf8=
+github.com/jackc/pgmock v0.0.0-20190831213851-13a1b77aafa2/go.mod h1:fGZlG77KXmcq05nJLRkk0+p82V8B8Dw8KN2/V9c/OAE=
+github.com/jackc/pgmock v0.0.0-20201204152224-4fe30f7445fd/go.mod h1:hrBW0Enj2AZTNpt/7Y5rr2xe/9Mn757Wtb2xeBzPv2c=
+github.com/jackc/pgmock v0.0.0-20210724152146-4ad1a8207f65/go.mod h1:5R2h2EEX+qri8jOWMbJCtaPWkrrNc7OHwsp2TCqp7ak=
+github.com/jackc/pgpassfile v1.0.0 h1:/6Hmqy13Ss2zCq62VdNG8tM1wchn8zjSGOBJ6icpsIM=
+github.com/jackc/pgpassfile v1.0.0/go.mod h1:CEx0iS5ambNFdcRtxPj5JhEz+xB6uRky5eyVu/W2HEg=
+github.com/jackc/pgproto3 v1.1.0 h1:FYYE4yRw+AgI8wXIinMlNjBbp/UitDJwfj5LqqewP1A=
+github.com/jackc/pgproto3 v1.1.0/go.mod h1:eR5FA3leWg7p9aeAqi37XOTgTIbkABlvcPB3E5rlc78=
+github.com/jackc/pgproto3/v2 v2.0.0-alpha1.0.20190420180111-c116219b62db/go.mod h1:bhq50y+xrl9n5mRYyCBFKkpRVTLYJVWeCc+mEAI3yXA=
+github.com/jackc/pgproto3/v2 v2.0.0-alpha1.0.20190609003834-432c2951c711/go.mod h1:uH0AWtUmuShn0bcesswc4aBTWGvw0cAxIJp+6OB//Wg=
+github.com/jackc/pgproto3/v2 v2.0.0-rc3/go.mod h1:ryONWYqW6dqSg1Lw6vXNMXoBJhpzvWKnT95C46ckYeM=
+github.com/jackc/pgproto3/v2 v2.0.0-rc3.0.20190831210041-4c03ce451f29/go.mod h1:ryONWYqW6dqSg1Lw6vXNMXoBJhpzvWKnT95C46ckYeM=
+github.com/jackc/pgproto3/v2 v2.0.6/go.mod h1:WfJCnwN3HIg9Ish/j3sgWXnAfK8A9Y0bwXYU5xKaEdA=
+github.com/jackc/pgproto3/v2 v2.1.1 h1:7PQ/4gLoqnl87ZxL7xjO0DR5gYuviDCZxQJsUlFW1eI=
+github.com/jackc/pgproto3/v2 v2.1.1/go.mod h1:WfJCnwN3HIg9Ish/j3sgWXnAfK8A9Y0bwXYU5xKaEdA=
+github.com/jackc/pgservicefile v0.0.0-20200714003250-2b9c44734f2b h1:C8S2+VttkHFdOOCXJe+YGfa4vHYwlt4Zx+IVXQ97jYg=
+github.com/jackc/pgservicefile v0.0.0-20200714003250-2b9c44734f2b/go.mod h1:vsD4gTJCa9TptPL8sPkXrLZ+hDuNrZCnj29CQpr4X1E=
+github.com/jackc/pgtype v0.0.0-20190421001408-4ed0de4755e0/go.mod h1:hdSHsc1V01CGwFsrv11mJRHWJ6aifDLfdV3aVjFF0zg=
+github.com/jackc/pgtype v0.0.0-20190824184912-ab885b375b90/go.mod h1:KcahbBH1nCMSo2DXpzsoWOAfFkdEtEJpPbVLq8eE+mc=
+github.com/jackc/pgtype v0.0.0-20190828014616-a8802b16cc59/go.mod h1:MWlu30kVJrUS8lot6TQqcg7mtthZ9T0EoIBFiJcmcyw=
+github.com/jackc/pgtype v1.8.1-0.20210724151600-32e20a603178/go.mod h1:C516IlIV9NKqfsMCXTdChteoXmwgUceqaLfjg2e3NlM=
+github.com/jackc/pgtype v1.8.1 h1:9k0IXtdJXHJbyAWQgbWr1lU+MEhPXZz6RIXxfR5oxXs=
+github.com/jackc/pgtype v1.8.1/go.mod h1:LUMuVrfsFfdKGLw+AFFVv6KtHOFMwRgDDzBt76IqCA4=
+github.com/jackc/pgx/v4 v4.0.0-20190420224344-cc3461e65d96/go.mod h1:mdxmSJJuR08CZQyj1PVQBHy9XOp5p8/SHH6a0psbY9Y=
+github.com/jackc/pgx/v4 v4.0.0-20190421002000-1b8f0016e912/go.mod h1:no/Y67Jkk/9WuGR0JG/JseM9irFbnEPbuWV2EELPNuM=
+github.com/jackc/pgx/v4 v4.0.0-pre1.0.20190824185557-6972a5742186/go.mod h1:X+GQnOEnf1dqHGpw7JmHqHc1NxDoalibchSk9/RWuDc=
+github.com/jackc/pgx/v4 v4.12.1-0.20210724153913-640aa07df17c/go.mod h1:1QD0+tgSXP7iUjYm9C1NxKhny7lq6ee99u/z+IHFcgs=
+github.com/jackc/pgx/v4 v4.13.0 h1:JCjhT5vmhMAf/YwBHLvrBn4OGdIQBiFG6ym8Zmdx570=
+github.com/jackc/pgx/v4 v4.13.0/go.mod h1:9P4X524sErlaxj0XSGZk7s+LD0eOyu1ZDUrrpznYDF0=
+github.com/jackc/puddle v0.0.0-20190413234325-e4ced69a3a2b/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk=
+github.com/jackc/puddle v0.0.0-20190608224051-11cab39313c9/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk=
+github.com/jackc/puddle v1.1.3/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk=
github.com/jackpal/gateway v1.0.5/go.mod h1:lTpwd4ACLXmpyiCTRtfiNyVnUmqT9RivzCDQetPfnjA=
github.com/jackpal/go-nat-pmp v0.0.0-20170405195558-28a68d0c24ad/go.mod h1:QPH045xvCAeXUZOxsnwmrtiCoxIr9eob+4orBN1SBKc=
github.com/jedib0t/go-pretty v4.3.0+incompatible/go.mod h1:XemHduiw8R651AF9Pt4FwCTKeG3oo7hrHJAoznj9nag=
+github.com/jedib0t/go-pretty/v6 v6.2.7/go.mod h1:FMkOpgGD3EZ91cW8g/96RfxoV7bdeJyzXPYgz1L1ln0=
github.com/jessevdk/go-flags v0.0.0-20141203071132-1679536dcc89/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI=
+github.com/jessevdk/go-flags v1.4.0 h1:4IU2WS7AumrZ/40jfhf4QVDMsQwqA7VEHozFRrGARJA=
github.com/jessevdk/go-flags v1.4.0/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI=
github.com/jinzhu/configor v1.2.1 h1:OKk9dsR8i6HPOCZR8BcMtcEImAFjIhbJFZNyn5GCZko=
github.com/jinzhu/configor v1.2.1/go.mod h1:nX89/MOmDba7ZX7GCyU/VIaQ2Ar2aizBl2d3JLF/rDc=
@@ -110,200 +496,940 @@ github.com/jinzhu/inflection v1.0.0/go.mod h1:h+uFLlag+Qp1Va5pdKtLDYj+kHp5pxUVkr
github.com/jinzhu/now v1.1.1/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8=
github.com/jinzhu/now v1.1.2 h1:eVKgfIdy9b6zbWBMgFpfDPoAMifwSZagU9HmEU6zgiI=
github.com/jinzhu/now v1.1.2/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8=
+github.com/jmespath/go-jmespath v0.0.0-20180206201540-c2b33e8439af/go.mod h1:Nht3zPeWKUH0NzdCt2Blrr5ys8VGpn0CEB0cQHVjt7k=
+github.com/jonboulle/clockwork v0.1.0/go.mod h1:Ii8DK3G1RaLaWxj9trq07+26W01tbo22gdxWY5EU2bo=
+github.com/jonboulle/clockwork v0.2.2 h1:UOGuzwb1PwsrDAObMuhUnj0p5ULPj8V/xJ7Kx9qUBdQ=
+github.com/jonboulle/clockwork v0.2.2/go.mod h1:Pkfl5aHPm1nk2H9h0bjmnJD/BcgbGXUBGnn1kMkgxc8=
+github.com/jpillora/backoff v1.0.0/go.mod h1:J/6gKK9jxlEcS3zixgDgUAsiuZ7yrSoa/FX5e0EB2j4=
github.com/jrick/logrotate v1.0.0 h1:lQ1bL/n9mBNeIXoTUoYRlK4dHuNJVofX9oWqBtPnSzI=
github.com/jrick/logrotate v1.0.0/go.mod h1:LNinyqDIJnpAur+b8yyulnQw/wDuN1+BYKlTRt3OuAQ=
+github.com/json-iterator/go v0.0.0-20180612202835-f2b4162afba3/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU=
+github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU=
+github.com/json-iterator/go v1.1.7/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
+github.com/json-iterator/go v1.1.8/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
+github.com/json-iterator/go v1.1.10/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
+github.com/json-iterator/go v1.1.11 h1:uVUAXhF2To8cbw/3xN3pxj6kk7TYKs98NIrTqPlMWAQ=
+github.com/json-iterator/go v1.1.11/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
+github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU=
+github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk=
+github.com/jtolds/gls v4.20.0+incompatible h1:xdiiI2gbIgH/gLH7ADydsJ1uDOEzR8yvV7C0MuV77Wo=
+github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU=
+github.com/juju/ansiterm v0.0.0-20160907234532-b99631de12cf/go.mod h1:UJSiEoRfvx3hP73CvoARgeLjaIOjybY9vj8PUPPFGeU=
+github.com/juju/ansiterm v0.0.0-20180109212912-720a0952cc2a/go.mod h1:UJSiEoRfvx3hP73CvoARgeLjaIOjybY9vj8PUPPFGeU=
github.com/juju/clock v0.0.0-20190205081909-9c5c9712527c/go.mod h1:nD0vlnrUjcjJhqN5WuCWZyzfd5AHZAC9/ajvbSx69xA=
+github.com/juju/cmd v0.0.0-20171107070456-e74f39857ca0/go.mod h1:yWJQHl73rdSX4DHVKGqkAip+huBslxRwS8m9CrOLq18=
+github.com/juju/collections v0.0.0-20200605021417-0d0ec82b7271/go.mod h1:5XgO71dV1JClcOJE+4dzdn4HrI5LiyKd7PlVG6eZYhY=
+github.com/juju/errors v0.0.0-20150916125642-1b5e39b83d18/go.mod h1:W54LbzXuIE0boCoNJfwqpmkKJ1O4TCTZMetAt6jGk7Q=
github.com/juju/errors v0.0.0-20190806202954-0232dcc7464d/go.mod h1:W54LbzXuIE0boCoNJfwqpmkKJ1O4TCTZMetAt6jGk7Q=
+github.com/juju/errors v0.0.0-20200330140219-3fe23663418f/go.mod h1:W54LbzXuIE0boCoNJfwqpmkKJ1O4TCTZMetAt6jGk7Q=
+github.com/juju/errors v0.0.0-20220203013757-bd733f3c86b9/go.mod h1:TRm7EVGA3mQOqSVcBySRY7a9Y1/gyVhh/WTCnc5sD4U=
+github.com/juju/gnuflag v0.0.0-20171113085948-2ce1bb71843d/go.mod h1:2PavIy+JPciBPrBUjwbNvtwB6RQlve+hkpll6QSNmOE=
+github.com/juju/httpprof v0.0.0-20141217160036-14bf14c30767/go.mod h1:+MaLYz4PumRkkyHYeXJ2G5g5cIW0sli2bOfpmbaMV/g=
+github.com/juju/loggo v0.0.0-20170605014607-8232ab8918d9/go.mod h1:vgyd7OREkbtVEN/8IXZe5Ooef3LQePvuBm9UWj6ZL8U=
github.com/juju/loggo v0.0.0-20190526231331-6e530bcce5d8/go.mod h1:vgyd7OREkbtVEN/8IXZe5Ooef3LQePvuBm9UWj6ZL8U=
+github.com/juju/loggo v0.0.0-20200526014432-9ce3a2e09b5e/go.mod h1:vgyd7OREkbtVEN/8IXZe5Ooef3LQePvuBm9UWj6ZL8U=
+github.com/juju/loggo v0.0.0-20210728185423-eebad3a902c4/go.mod h1:NIXFioti1SmKAlKNuUwbMenNdef59IF52+ZzuOmHYkg=
+github.com/juju/mgo/v2 v2.0.0-20210302023703-70d5d206e208/go.mod h1:0OChplkvPTZ174D2FYZXg4IB9hbEwyHkD+zT+/eK+Fg=
+github.com/juju/mutex v0.0.0-20171110020013-1fe2a4bf0a3a/go.mod h1:Y3oOzHH8CQ0Ppt0oCKJ2JFO81/EsWenH5AEqigLH+yY=
+github.com/juju/retry v0.0.0-20151029024821-62c620325291/go.mod h1:OohPQGsr4pnxwD5YljhQ+TZnuVRYpa5irjugL1Yuif4=
github.com/juju/retry v0.0.0-20180821225755-9058e192b216/go.mod h1:OohPQGsr4pnxwD5YljhQ+TZnuVRYpa5irjugL1Yuif4=
+github.com/juju/testing v0.0.0-20180402130637-44801989f0f7/go.mod h1:63prj8cnj0tU0S9OHjGJn+b1h0ZghCndfnbQolrYTwA=
github.com/juju/testing v0.0.0-20190723135506-ce30eb24acd2/go.mod h1:63prj8cnj0tU0S9OHjGJn+b1h0ZghCndfnbQolrYTwA=
+github.com/juju/testing v0.0.0-20210302031854-2c7ee8570c07/go.mod h1:7lxZW0B50+xdGFkvhAb8bwAGt6IU87JB1H9w4t8MNVM=
+github.com/juju/testing v0.0.0-20220202055744-1ad0816210a6/go.mod h1:QgWc2UdIPJ8t3rnvv95tFNOsQDfpXYEZDbP281o3b2c=
+github.com/juju/testing v0.0.0-20220203020004-a0ff61f03494/go.mod h1:rUquetT0ALL48LHZhyRGvjjBH8xZaZ8dFClulKK5wK4=
+github.com/juju/utils v0.0.0-20180424094159-2000ea4ff043/go.mod h1:6/KLg8Wz/y2KVGWEpkK9vMNGkOnu4k/cqs8Z1fKjTOk=
github.com/juju/utils v0.0.0-20180820210520-bf9cc5bdd62d/go.mod h1:6/KLg8Wz/y2KVGWEpkK9vMNGkOnu4k/cqs8Z1fKjTOk=
+github.com/juju/utils v0.0.0-20200116185830-d40c2fe10647/go.mod h1:6/KLg8Wz/y2KVGWEpkK9vMNGkOnu4k/cqs8Z1fKjTOk=
+github.com/juju/utils/v2 v2.0.0-20200923005554-4646bfea2ef1/go.mod h1:fdlDtQlzundleLLz/ggoYinEt/LmnrpNKcNTABQATNI=
+github.com/juju/utils/v3 v3.0.0-20220130232349-cd7ecef0e94a/go.mod h1:LzwbbEN7buYjySp4nqnti6c6olSqRXUk6RkbSUUP1n8=
+github.com/juju/version v0.0.0-20161031051906-1f41e27e54f2/go.mod h1:kE8gK5X0CImdr7qpSKl3xB2PmpySSmfj7zVbkZFs81U=
github.com/juju/version v0.0.0-20180108022336-b64dbd566305/go.mod h1:kE8gK5X0CImdr7qpSKl3xB2PmpySSmfj7zVbkZFs81U=
+github.com/juju/version v0.0.0-20191219164919-81c1be00b9a6/go.mod h1:kE8gK5X0CImdr7qpSKl3xB2PmpySSmfj7zVbkZFs81U=
+github.com/juju/version/v2 v2.0.0-20211007103408-2e8da085dc23/go.mod h1:Ljlbryh9sYaUSGXucslAEDf0A2XUSGvDbHJgW8ps6nc=
+github.com/julienschmidt/httprouter v1.1.1-0.20151013225520-77a895ad01eb/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w=
github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w=
+github.com/julienschmidt/httprouter v1.3.0/go.mod h1:JR6WtHb+2LUe8TCKY3cZOxFyyO8IZAc4RVcycCCAKdM=
+github.com/kisielk/errcheck v1.1.0/go.mod h1:EZBBE59ingxPouuu3KfxchcWSUPOHkagtvWXihfKN4Q=
+github.com/kisielk/errcheck v1.2.0/go.mod h1:/BMXB+zMLi60iA8Vv6Ksmxu/1UDYcXs4uQLJ+jE2L00=
+github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8=
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
github.com/kkdai/bstream v0.0.0-20161212061736-f391b8402d23/go.mod h1:J+Gs4SYgM6CZQHDETBtE9HaSEkGmuNXF86RwHhHUvq4=
-github.com/kkdai/bstream v0.0.0-20181106074824-b3251f7901ec h1:n1NeQ3SgUHyISrjFFoO5dR748Is8dBL9qpaTNfphQrs=
github.com/kkdai/bstream v0.0.0-20181106074824-b3251f7901ec/go.mod h1:J+Gs4SYgM6CZQHDETBtE9HaSEkGmuNXF86RwHhHUvq4=
-github.com/konsorten/go-windows-terminal-sequences v1.0.1 h1:mweAR1A6xJ3oS2pRaGiHgQ4OO8tzTaLawm8vnODuwDk=
+github.com/kkdai/bstream v1.0.0 h1:Se5gHwgp2VT2uHfDrkbbgbgEvV9cimLELwrPJctSjg8=
+github.com/kkdai/bstream v1.0.0/go.mod h1:FDnDOHt5Yx4p3FaHcioFT0QjDOtgUpvjeZqAs+NVZZA=
+github.com/klauspost/compress v1.4.1/go.mod h1:RyIbtBH6LamlWaDj8nUwkbUhJ87Yi3uG0guNDohfE1A=
+github.com/klauspost/compress v1.10.10/go.mod h1:aoV0uJVorq1K+umq18yTdKaF57EivdYsUV+/s2qKfXs=
+github.com/klauspost/compress v1.13.6 h1:P76CopJELS0TiO2mebmnzgWaajssP/EszplttgQxcgc=
+github.com/klauspost/compress v1.13.6/go.mod h1:/3/Vjq9QcHkK5uEr5lBEmyoZ1iFhe47etQ6QUkpK6sk=
+github.com/klauspost/cpuid v1.2.0/go.mod h1:Pj4uuM528wm8OyEC2QMXAi2YiTZ96dNQPGgoMS4s3ek=
+github.com/klauspost/pgzip v1.2.4/go.mod h1:Ch1tH69qFZu15pkjo5kYi6mth2Zzwzt50oCQKQE9RUs=
+github.com/klauspost/pgzip v1.2.5 h1:qnWYvvKqedOF2ulHpMG72XQol4ILEJ8k2wwRl/Km8oE=
+github.com/klauspost/pgzip v1.2.5/go.mod h1:Ch1tH69qFZu15pkjo5kYi6mth2Zzwzt50oCQKQE9RUs=
github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
+github.com/konsorten/go-windows-terminal-sequences v1.0.2/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
+github.com/konsorten/go-windows-terminal-sequences v1.0.3/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc=
-github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI=
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
+github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
+github.com/kr/pretty v0.3.0 h1:WgNl7dwNpEZ6jJ9k1snq4pZsg7DOEN8hP9Xw0Tsjwk0=
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
-github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE=
+github.com/kr/pty v1.1.8/go.mod h1:O1sed60cT9XZ5uDucP5qwvh+TE3NnUj51EiZO/lmSfw=
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
+github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
+github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
+github.com/leodido/go-urn v1.2.0/go.mod h1:+8+nEpDfqqsY+g338gtMEUOtuK+4dEMhiQEgxpxOKII=
+github.com/lib/pq v1.0.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
+github.com/lib/pq v1.1.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
+github.com/lib/pq v1.2.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
+github.com/lib/pq v1.8.0/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o=
+github.com/lib/pq v1.10.2/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o=
+github.com/lib/pq v1.10.3 h1:v9QZf2Sn6AmjXtQeFpdoq/eaNtYP6IN+7lcrygsIAtg=
+github.com/lib/pq v1.10.3/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o=
github.com/lightninglabs/gozmq v0.0.0-20191113021534-d20a764486bf h1:HZKvJUHlcXI/f/O0Avg7t8sqkPo78HFzjmeYFl6DPnc=
github.com/lightninglabs/gozmq v0.0.0-20191113021534-d20a764486bf/go.mod h1:vxmQPeIQxPf6Jf9rM8R+B4rKBqLA2AjttNxkFBL2Plk=
github.com/lightninglabs/neutrino v0.11.0/go.mod h1:CuhF0iuzg9Sp2HO6ZgXgayviFTn1QHdSTJlMncK80wg=
-github.com/lightninglabs/neutrino v0.11.1-0.20200316235139-bffc52e8f200 h1:j4iZ1XlUAPQmW6oSzMcJGILYsRHNs+4O3Gk+2Ms5Dww=
github.com/lightninglabs/neutrino v0.11.1-0.20200316235139-bffc52e8f200/go.mod h1:MlZmoKa7CJP3eR1s5yB7Rm5aSyadpKkxqAwLQmog7N0=
+github.com/lightninglabs/neutrino v0.14.2 h1:yrnZUCYMZ5ECtXhgDrzqPq2oX8awoAN2D/cgCewJcCo=
+github.com/lightninglabs/neutrino v0.14.2/go.mod h1:OICUeTCn+4Tu27YRJIpWvvqySxx4oH4vgdP33Sw9RDc=
github.com/lightninglabs/protobuf-hex-display v1.3.3-0.20191212020323-b444784ce75d/go.mod h1:KDb67YMzoh4eudnzClmvs2FbiLG9vxISmLApUkCa4uI=
+github.com/lightninglabs/protobuf-hex-display v1.4.3-hex-display/go.mod h1:2oKOBU042GKFHrdbgGiKax4xVrFiZu51lhacUZQ9MnE=
github.com/lightningnetwork/lightning-onion v1.0.1/go.mod h1:rigfi6Af/KqsF7Za0hOgcyq2PNH4AN70AaMRxcJkff4=
-github.com/lightningnetwork/lnd v0.10.1-beta h1:zA/rQoxC5FNHtayVuA2wRtSOEDnJbuzAzHKAf2PWj1Q=
+github.com/lightningnetwork/lightning-onion v1.0.2-0.20220211021909-bb84a1ccb0c5 h1:TkKwqFcQTGYoI+VEqyxA8rxpCin8qDaYX0AfVRinT3k=
+github.com/lightningnetwork/lightning-onion v1.0.2-0.20220211021909-bb84a1ccb0c5/go.mod h1:7dDx73ApjEZA0kcknI799m2O5kkpfg4/gr7N092ojNo=
github.com/lightningnetwork/lnd v0.10.1-beta/go.mod h1:F9er1DrpOHdQVQBqYqyBqIFyl6q16xgBM8yTioHj2Cg=
+github.com/lightningnetwork/lnd v0.15.0-beta h1:smzYjJqL4nGuj4qrAWdikrPzPJ8fcPRFHQ86S2tHR1M=
+github.com/lightningnetwork/lnd v0.15.0-beta/go.mod h1:Tm7LZrYeR2JQH1gEOKmd0NTCgjJ1Bnujkx4lcz9b5+A=
github.com/lightningnetwork/lnd/cert v1.0.2/go.mod h1:fmtemlSMf5t4hsQmcprSoOykypAPp+9c+0d0iqTScMo=
+github.com/lightningnetwork/lnd/cert v1.1.1/go.mod h1:1P46svkkd73oSoeI4zjkVKgZNwGq8bkGuPR8z+5vQUs=
+github.com/lightningnetwork/lnd/clock v1.0.1/go.mod h1:KnQudQ6w0IAMZi1SgvecLZQZ43ra2vpDNj7H/aasemg=
+github.com/lightningnetwork/lnd/clock v1.1.0 h1:/yfVAwtPmdx45aQBoXQImeY7sOIEr7IXlImRMBOZ7GQ=
+github.com/lightningnetwork/lnd/clock v1.1.0/go.mod h1:KnQudQ6w0IAMZi1SgvecLZQZ43ra2vpDNj7H/aasemg=
+github.com/lightningnetwork/lnd/healthcheck v1.0.0/go.mod h1:u92p1JGFJNMSkMvztKEwmt1P3TRnLeJBXZ3M85xkU1E=
+github.com/lightningnetwork/lnd/healthcheck v1.2.2 h1:im+qcpgSuteqRCGeorT9yqVXuLrS6A7/acYzGgarMS4=
+github.com/lightningnetwork/lnd/healthcheck v1.2.2/go.mod h1:IWY0GChlarRbXFkFDdE4WY5POYJabe/7/H1iCZt4ZKs=
+github.com/lightningnetwork/lnd/kvdb v1.3.1 h1:gEz3zudNNRrCLEvqRaktYoKwsUblyHX+MKjR0aI3QnM=
+github.com/lightningnetwork/lnd/kvdb v1.3.1/go.mod h1:x+IpsuDynubjokUofavLXroeGfS/WrqUXXTK6vN/gp4=
github.com/lightningnetwork/lnd/queue v1.0.1/go.mod h1:vaQwexir73flPW43Mrm7JOgJHmcEFBWWSl9HlyASoms=
-github.com/lightningnetwork/lnd/queue v1.0.3 h1:5ufYVE7lh9GJnL1wOoeO3bZ3aAHWNnkNFHP7W1+NiJ8=
github.com/lightningnetwork/lnd/queue v1.0.3/go.mod h1:YTkTVZCxz8tAYreH27EO3s8572ODumWrNdYW2E/YKxg=
-github.com/lightningnetwork/lnd/ticker v1.0.0 h1:S1b60TEGoTtCe2A0yeB+ecoj/kkS4qpwh6l+AkQEZwU=
+github.com/lightningnetwork/lnd/queue v1.1.0 h1:YpCJjlIvVxN/R7ww2aNiY8ex7U2fucZDLJ67tI3HFx8=
+github.com/lightningnetwork/lnd/queue v1.1.0/go.mod h1:YTkTVZCxz8tAYreH27EO3s8572ODumWrNdYW2E/YKxg=
github.com/lightningnetwork/lnd/ticker v1.0.0/go.mod h1:iaLXJiVgI1sPANIF2qYYUJXjoksPNvGNYowB8aRbpX0=
+github.com/lightningnetwork/lnd/ticker v1.1.0 h1:ShoBiRP3pIxZHaETndfQ5kEe+S4NdAY1hiX7YbZ4QE4=
+github.com/lightningnetwork/lnd/ticker v1.1.0/go.mod h1:ubqbSVCn6RlE0LazXuBr7/Zi6QT0uQo++OgIRBxQUrk=
+github.com/lightningnetwork/lnd/tlv v1.0.2/go.mod h1:fICAfsqk1IOsC1J7G9IdsWX1EqWRMqEDCNxZJSKr9C4=
+github.com/lightningnetwork/lnd/tlv v1.0.3 h1:0xBZcPuXagP6f7TY/RnLNR4igE21ov6qUdTr5NyvhhI=
+github.com/lightningnetwork/lnd/tlv v1.0.3/go.mod h1:dzR/aZetBri+ZY/fHbwV06fNn/3UID6htQzbHfREFdo=
+github.com/lightningnetwork/lnd/tor v1.0.0/go.mod h1:RDtaAdwfAm+ONuPYwUhNIH1RAvKPv+75lHPOegUcz64=
+github.com/lightningnetwork/lnd/tor v1.0.1 h1:A11FrpU0Y//g+fA827W4VnjOeoIvExONdchlLX8wYkA=
+github.com/lightningnetwork/lnd/tor v1.0.1/go.mod h1:RDtaAdwfAm+ONuPYwUhNIH1RAvKPv+75lHPOegUcz64=
+github.com/lightstep/lightstep-tracer-common/golang/gogo v0.0.0-20190605223551-bc2310a04743/go.mod h1:qklhhLq1aX+mtWk9cPHPzaBjWImj5ULL6C7HFJtXQMM=
+github.com/lightstep/lightstep-tracer-go v0.18.1/go.mod h1:jlF1pusYV4pidLvZ+XD0UBX0ZE6WURAspgAczcDHrL4=
github.com/ltcsuite/ltcd v0.0.0-20190101042124-f37f8bf35796 h1:sjOGyegMIhvgfq5oaue6Td+hxZuf3tDC8lAPrFldqFw=
github.com/ltcsuite/ltcd v0.0.0-20190101042124-f37f8bf35796/go.mod h1:3p7ZTf9V1sNPI5H8P3NkTFF4LuwMdPl2DodF60qAKqY=
github.com/ltcsuite/ltcutil v0.0.0-20181217130922-17f3b04680b6/go.mod h1:8Vg/LTOO0KYa/vlHWJ6XZAevPQThGH5sufO0Hrou/lA=
+github.com/lunixbochs/vtclean v0.0.0-20160125035106-4fbf7632a2c6/go.mod h1:pHhQNgMf3btfWnGBVipUOjRYhoOsdGqdm/+2c2E2WMI=
+github.com/lyft/protoc-gen-validate v0.0.13/go.mod h1:XbGvPuh87YZc5TdIa2/I4pLk0QoUACkjt2znoq26NVQ=
+github.com/magiconair/properties v1.8.1/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ=
+github.com/mailru/easyjson v0.0.0-20160728113105-d5b7844b561a/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc=
github.com/makiuchi-d/gozxing v0.0.2 h1:TGSCQRXd9QL1ze1G1JE9sZBMEr6/HLx7m5ADlLUgq7E=
github.com/makiuchi-d/gozxing v0.0.2/go.mod h1:Tt5nF+kNliU+5MDxqPpsFrtsWNdABQho/xdCZZVKCQc=
+github.com/masterzen/azure-sdk-for-go v3.2.0-beta.0.20161014135628-ee4f0065d00c+incompatible/go.mod h1:mf8fjOu33zCqxUjuiU3I8S1lJMyEAlH+0F2+M5xl3hE=
+github.com/masterzen/simplexml v0.0.0-20160608183007-4572e39b1ab9/go.mod h1:kCEbxUJlNDEBNbdQMkPSp6yaKcRXVI6f4ddk8Riv4bc=
+github.com/masterzen/winrm v0.0.0-20161014151040-7a535cd943fc/go.mod h1:CfZSN7zwz5gJiFhZJz49Uzk7mEBHIceWmbFmYx7Hf7E=
+github.com/masterzen/xmlpath v0.0.0-20140218185901-13f4951698ad/go.mod h1:A0zPC53iKKKcXYxr4ROjpQRQ5FgJXtelNdSmHHuq/tY=
+github.com/mattn/go-colorable v0.0.6/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU=
+github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU=
+github.com/mattn/go-colorable v0.1.1/go.mod h1:FuOcm+DKB9mbwrcAfNl7/TZVBZ6rcnceauSikq3lYCQ=
+github.com/mattn/go-colorable v0.1.6/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc=
+github.com/mattn/go-colorable v0.1.8/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc=
+github.com/mattn/go-colorable v0.1.9/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc=
+github.com/mattn/go-colorable v0.1.12/go.mod h1:u5H1YNBxpqRaxsYJYSkiCWKzEfiAb1Gb520KVy5xxl4=
+github.com/mattn/go-isatty v0.0.0-20160806122752-66b8e73f3f5c/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4=
+github.com/mattn/go-isatty v0.0.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4=
+github.com/mattn/go-isatty v0.0.4/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4=
+github.com/mattn/go-isatty v0.0.5/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s=
+github.com/mattn/go-isatty v0.0.7/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s=
+github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU=
+github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94=
+github.com/mattn/go-runewidth v0.0.2/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU=
github.com/mattn/go-runewidth v0.0.9/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI=
+github.com/mattn/go-runewidth v0.0.13/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w=
github.com/mattn/go-sqlite3 v1.14.5 h1:1IdxlwTNazvbKJQSxoJ5/9ECbEeaTTyeU7sEAZ5KKTQ=
github.com/mattn/go-sqlite3 v1.14.5/go.mod h1:WVKg1VTActs4Qso6iwGbiFih2UIHo0ENGwNd0Lj+XmI=
+github.com/matttproud/golang_protobuf_extensions v1.0.1 h1:4hp9jkHxhMHkqkrB3Ix0jegS5sx/RkqARlsWZ6pIwiU=
github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0=
-github.com/miekg/dns v0.0.0-20171125082028-79bfde677fa8 h1:PRMAcldsl4mXKJeRNB/KVNz6TlbS6hk2Rs42PqgU3Ws=
+github.com/mholt/archiver/v3 v3.5.0 h1:nE8gZIrw66cu4osS/U7UW7YDuGMHssxKutU8IfWxwWE=
+github.com/mholt/archiver/v3 v3.5.0/go.mod h1:qqTTPUK/HZPFgFQ/TJ3BzvTpF/dPtFVJXdQbCmeMxwc=
github.com/miekg/dns v0.0.0-20171125082028-79bfde677fa8/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg=
+github.com/miekg/dns v1.0.14/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg=
+github.com/miekg/dns v1.1.43 h1:JKfpVSCB84vrAmHzyrsxB5NAr5kLoMXZArPSw7Qlgyg=
+github.com/miekg/dns v1.1.43/go.mod h1:+evo5L0630/F6ca/Z9+GAqzhjGyn8/c+TBaOyfEl0V4=
+github.com/mitchellh/cli v1.0.0/go.mod h1:hNIlj7HEI86fIcpObd7a0FcrxTWetlwJDGcceTlRvqc=
+github.com/mitchellh/go-homedir v1.0.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=
+github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=
+github.com/mitchellh/go-testing-interface v1.0.0/go.mod h1:kRemZodwjscx+RGhAo8eIhFbs2+BFgRtFPeD/KE+zxI=
+github.com/mitchellh/gox v0.4.0/go.mod h1:Sd9lOJ0+aimLBi73mGofS1ycjY8lL3uZM3JPS42BGNg=
+github.com/mitchellh/iochan v1.0.0/go.mod h1:JwYml1nuB7xOzsp52dPpHFffvOCDupsG0QubkSMEySY=
+github.com/mitchellh/mapstructure v0.0.0-20160808181253-ca63d7c062ee/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=
github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=
+github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
+github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg=
+github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
+github.com/modern-go/reflect2 v0.0.0-20180320133207-05fbef0ca5da/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
+github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
+github.com/modern-go/reflect2 v1.0.1 h1:9f412s+6RmYXLWZSEzVVgPGK7C2PphHj5RJrvfx9AWI=
+github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
+github.com/munnerz/goautoneg v0.0.0-20120707110453-a547fc61f48d/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ=
github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U=
+github.com/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U=
+github.com/mxk/go-flowrate v0.0.0-20140419014527-cca7078d478f/go.mod h1:ZdcZmHo+o7JKHSa8/e818NopupXU1YMK5fe1lsApnBw=
+github.com/nats-io/jwt v0.3.0/go.mod h1:fRYCDE99xlTsqUzISS1Bi75UBJ6ljOJQOAAu5VglpSg=
+github.com/nats-io/jwt v0.3.2/go.mod h1:/euKqTS1ZD+zzjYrY7pseZrTtWQSjujC7xjPc8wL6eU=
+github.com/nats-io/nats-server/v2 v2.1.2/go.mod h1:Afk+wRZqkMQs/p45uXdrVLuab3gwv3Z8C4HTBu8GD/k=
+github.com/nats-io/nats.go v1.9.1/go.mod h1:ZjDU1L/7fJ09jvUSRVBR2e7+RnLiiIQyqyzEE/Zbp4w=
+github.com/nats-io/nkeys v0.1.0/go.mod h1:xpnFELMwJABBLVhffcfd1MZx6VsNRFpEugbxziKVo7w=
+github.com/nats-io/nkeys v0.1.3/go.mod h1:xpnFELMwJABBLVhffcfd1MZx6VsNRFpEugbxziKVo7w=
+github.com/nats-io/nuid v1.0.1/go.mod h1:19wcPz3Ph3q0Jbyiqsd0kePYG7A95tJPxeL+1OSON2c=
+github.com/nbd-wtf/go-nostr v0.13.0 h1:3OcUknuSLm8prTk2u/kUvbMQYtaSjLKemADRQ8TBIXk=
+github.com/nbd-wtf/go-nostr v0.13.0/go.mod h1:qFFTIxh15H5GGN0WsBI/P73DteqsevnhSEW/yk8nEf4=
+github.com/nbd-wtf/ln-decodepay v1.5.1 h1:i/SMR94AXIL21KxE/CyWLg/1kKUOVfxHD4QJswaNRDc=
+github.com/nbd-wtf/ln-decodepay v1.5.1/go.mod h1:xzBXPaCj/7oRRaui+iYSIxy5LYUjoPfAyAGq2WCyNKk=
+github.com/nfnt/resize v0.0.0-20180221191011-83c6a9932646 h1:zYyBkD/k9seD2A7fsi6Oo2LfFZAehjjQMERAvZLEDnQ=
+github.com/nfnt/resize v0.0.0-20180221191011-83c6a9932646/go.mod h1:jpp1/29i3P1S/RLdc7JQKbRpFeM1dOBd8T9ki5s+AY8=
+github.com/nicksnyder/go-i18n/v2 v2.1.2 h1:QHYxcUJnGHBaq7XbvgunmZ2Pn0focXFqTD61CkH146c=
+github.com/nicksnyder/go-i18n/v2 v2.1.2/go.mod h1:d++QJC9ZVf7pa48qrsRWhMJ5pSHIPmS3OLqK1niyLxs=
+github.com/nu7hatch/gouuid v0.0.0-20131221200532-179d4d0c4d8d/go.mod h1:YUTz3bUH2ZwIWBy3CJBeOBEugqcmXREj14T+iG/4k4U=
+github.com/nwaples/rardecode v1.1.0/go.mod h1:5DzqNKiOdpKKBH87u8VlvAnPZMXcGRhxWkRpHbbfGS0=
+github.com/nwaples/rardecode v1.1.2 h1:Cj0yZY6T1Zx1R7AhTbyGSALm44/Mmq+BAPc4B/p/d3M=
+github.com/nwaples/rardecode v1.1.2/go.mod h1:5DzqNKiOdpKKBH87u8VlvAnPZMXcGRhxWkRpHbbfGS0=
+github.com/nxadm/tail v1.4.4 h1:DQuhQpB1tVlglWS2hLQ5OV6B5r8aGxSrPc5Qo6uTN78=
+github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A=
+github.com/oklog/oklog v0.3.2/go.mod h1:FCV+B7mhrz4o+ueLpx+KqkyXRGMWOYEvfiXtdGtbWGs=
+github.com/oklog/run v1.0.0/go.mod h1:dlhp/R75TPv97u0XWUtDeV/lRKWPKSdTuV0TZvrmrQA=
github.com/oklog/ulid v1.3.1/go.mod h1:CirwcVhetQ6Lv90oh/F+FBtV6XMibvdAFo93nm5qn4U=
+github.com/olekukonko/tablewriter v0.0.0-20170122224234-a0225b3f23b5/go.mod h1:vsDQFd/mU46D+Z4whnwzcISnGGzXWMclvtLoiIKAKIo=
+github.com/onsi/ginkgo v0.0.0-20170829012221-11459a886d9c/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
-github.com/onsi/ginkgo v1.7.0 h1:WSHQ+IS43OoUrWtD1/bbclrwK8TTH5hzp+umCiuxHgs=
github.com/onsi/ginkgo v1.7.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
+github.com/onsi/ginkgo v1.10.1/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
+github.com/onsi/ginkgo v1.12.1/go.mod h1:zj2OWP4+oCPe1qIXoGWkgMRwljMUYCdkwsT2108oapk=
+github.com/onsi/ginkgo v1.14.0/go.mod h1:iSB4RoI2tjJc9BBv4NKIKWKya62Rps+oPG/Lv9klQyY=
+github.com/onsi/ginkgo v1.15.0 h1:1V1NfVQR87RtWAgp1lv9JZJ5Jap+XFGKPi00andXGi4=
+github.com/onsi/ginkgo v1.15.0/go.mod h1:hF8qUzuuC8DJGygJH3726JnCZX4MYbRB8yFfISqnKUg=
+github.com/onsi/gomega v0.0.0-20170829124025-dcabb60a477c/go.mod h1:C1qb7wdrVGGVU+Z6iS04AVkA3Q65CEZX59MT0QO5uiA=
github.com/onsi/gomega v1.4.1/go.mod h1:C1qb7wdrVGGVU+Z6iS04AVkA3Q65CEZX59MT0QO5uiA=
-github.com/onsi/gomega v1.4.3 h1:RE1xgDvH7imwFD45h+u2SgIfERHlS2yNG4DObb5BSKU=
github.com/onsi/gomega v1.4.3/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY=
+github.com/onsi/gomega v1.7.0/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY=
+github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY=
+github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo=
+github.com/onsi/gomega v1.10.5 h1:7n6FEkpFmfCoo2t+YYqXH0evK+a9ICQz0xcAy9dYcaQ=
+github.com/onsi/gomega v1.10.5/go.mod h1:gza4q3jKQJijlu05nKWRCW/GavJumGt8aNRxWg7mt48=
+github.com/op/go-logging v0.0.0-20160315200505-970db520ece7/go.mod h1:HzydrMdWErDVzsI23lYNej1Htcns9BCg93Dk0bBINWk=
+github.com/opentracing-contrib/go-observer v0.0.0-20170622124052-a52f23424492/go.mod h1:Ngi6UdF0k5OKD5t5wlmGhe/EDKPoUM3BXZSSfIuJbis=
+github.com/opentracing/basictracer-go v1.0.0/go.mod h1:QfBfYuafItcjQuMwinw9GhYKwFXS9KnPs5lxoYwgW74=
+github.com/opentracing/opentracing-go v1.0.2/go.mod h1:UkNAQd3GIcIGf0SeVgPpRdFStlNbqXla1AfSYxPUl2o=
+github.com/opentracing/opentracing-go v1.1.0/go.mod h1:UkNAQd3GIcIGf0SeVgPpRdFStlNbqXla1AfSYxPUl2o=
+github.com/openzipkin-contrib/zipkin-go-opentracing v0.4.5/go.mod h1:/wsWhb9smxSfWAKL3wpBW7V8scJMt8N8gnaMCS9E/cA=
+github.com/openzipkin/zipkin-go v0.1.6/go.mod h1:QgAqvLzwWbR/WpD4A3cGpPtJrZXNIiJc5AZX7/PBEpw=
+github.com/openzipkin/zipkin-go v0.2.1/go.mod h1:NaW6tEwdmWMaCDZzg8sh+IBNOxHMPnhQw8ySjnjRyN4=
+github.com/openzipkin/zipkin-go v0.2.2/go.mod h1:NaW6tEwdmWMaCDZzg8sh+IBNOxHMPnhQw8ySjnjRyN4=
+github.com/orcaman/concurrent-map v1.0.0 h1:I/2A2XPCb4IuQWcQhBhSwGfiuybl/J0ev9HDbW65HOY=
+github.com/orcaman/concurrent-map v1.0.0/go.mod h1:Lu3tH6HLW3feq74c2GC+jIMS/K2CFcDWnWD9XkenwhI=
+github.com/pact-foundation/pact-go v1.0.4/go.mod h1:uExwJY4kCzNPcHRj+hCR/HBbOOIwwtUjcrb0b5/5kLM=
+github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc=
+github.com/patrickmn/go-cache v2.1.0+incompatible h1:HRMgzkcYKYpi3C8ajMPV8OFXaaRUnok+kx1WdO15EQc=
+github.com/patrickmn/go-cache v2.1.0+incompatible/go.mod h1:3Qf8kWWT7OJRJbdiICTKqZju1ZixQ/KpMGzzAfe6+WQ=
+github.com/pborman/uuid v1.2.0/go.mod h1:X/NO0urCmaxf9VXbdlT7C2Yzkj2IKimNn4k+gtPdI/k=
+github.com/pegasus-kv/thrift v0.13.0 h1:4ESwaNoHImfbHa9RUGJiJZ4hrxorihZHk5aarYwY8d4=
+github.com/pegasus-kv/thrift v0.13.0/go.mod h1:Gl9NT/WHG6ABm6NsrbfE8LiJN0sAyneCrvB4qN4NPqQ=
+github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic=
+github.com/performancecopilot/speed v3.0.0+incompatible/go.mod h1:/CLtqpZ5gBg1M9iaPbIdPPGyKcA8hKdoy6hAWba7Yac=
+github.com/pierrec/lz4 v1.0.2-0.20190131084431-473cd7ce01a1/go.mod h1:3/3N9NVKO0jef7pBehbT1qWhCMrIgbYNnFAZCqQ5LRc=
+github.com/pierrec/lz4 v2.0.5+incompatible h1:2xWsjqPFWcplujydGg4WmhC/6fZqK42wMM8aXeqhl0I=
+github.com/pierrec/lz4 v2.0.5+incompatible/go.mod h1:pdkljMzZIN41W+lC3N2tnIh5sFi+IEE17M5jbnwPHcY=
+github.com/pierrec/lz4/v4 v4.0.3/go.mod h1:gZWDp/Ze/IJXGXf23ltt2EXimqmTUXEy0GFuRQyBid4=
+github.com/pierrec/lz4/v4 v4.1.8 h1:ieHkV+i2BRzngO4Wd/3HGowuZStgq6QkPsD1eolNAO4=
+github.com/pierrec/lz4/v4 v4.1.8/go.mod h1:gZWDp/Ze/IJXGXf23ltt2EXimqmTUXEy0GFuRQyBid4=
github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
-github.com/pkg/errors v0.8.1 h1:iURUrRGxPUNPdy5/HRSm+Yj6okJ6UtLINN0Q9M4+h3I=
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
+github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
+github.com/pkg/profile v1.2.1/go.mod h1:hJw3o1OdXxsrSjjVksARp5W95eeEaEfptyVZyv6JUPA=
+github.com/pkg/profile v1.6.0/go.mod h1:qBsxPvzyUincmltOk6iyRVxHYg4adc0OFOv72ZdLa18=
+github.com/pmezard/go-difflib v0.0.0-20151028094244-d8ed2627bdf0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
+github.com/posener/complete v1.1.1/go.mod h1:em0nMJCgc9GFtwrmVmEMR/ZL6WyhyjMBndrE9hABlRI=
github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw=
+github.com/prometheus/client_golang v0.9.3-0.20190127221311-3c4408c8b829/go.mod h1:p2iRAGwDERtqlqzRXnrOVns+ignqQo//hLXqYxZYVNs=
github.com/prometheus/client_golang v0.9.3/go.mod h1:/TN21ttK/J9q6uSwhBd54HahCDft0ttaMvbicHlPoso=
+github.com/prometheus/client_golang v1.0.0/go.mod h1:db9x61etRT2tGnBNRi70OPL5FsnadC4Ky3P0J6CfImo=
+github.com/prometheus/client_golang v1.3.0/go.mod h1:hJaj2vgQTGQmVCsAACORcieXFeDPbaTKGT+JTgUa3og=
+github.com/prometheus/client_golang v1.7.1/go.mod h1:PY5Wy2awLA44sXw4AOSfFBetzPP4j5+D6mVACh+pe2M=
+github.com/prometheus/client_golang v1.10.0/go.mod h1:WJM3cc3yu7XKBKa/I8WeZm+V3eltZnBwfENSU7mdogU=
+github.com/prometheus/client_golang v1.11.0 h1:HNkLOAEQMIDv/K+04rukrLx6ch7msSRwf3/SASFAGtQ=
+github.com/prometheus/client_golang v1.11.0/go.mod h1:Z6t4BnS23TR94PD6BsDNk8yVqroYurpAkEiz0P2BEV0=
github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo=
+github.com/prometheus/client_model v0.0.0-20190115171406-56726106282f/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo=
github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
+github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
+github.com/prometheus/client_model v0.1.0/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
+github.com/prometheus/client_model v0.2.0 h1:uq5h0d+GuxiXLJLNABMgp2qUWDPiLvgCzz2dUR+/W/M=
+github.com/prometheus/client_model v0.2.0/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
github.com/prometheus/common v0.0.0-20181113130724-41aa239b4cce/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro=
+github.com/prometheus/common v0.2.0/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4=
github.com/prometheus/common v0.4.0/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4=
+github.com/prometheus/common v0.4.1/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4=
+github.com/prometheus/common v0.7.0/go.mod h1:DjGbpBbp5NYNiECxcL/VnbXCCaQpKd3tt26CguLLsqA=
+github.com/prometheus/common v0.10.0/go.mod h1:Tlit/dnDKsSWFlCLTWaA1cyBgKHSMdTB80sz/V91rCo=
+github.com/prometheus/common v0.18.0/go.mod h1:U+gB1OBLb1lF3O42bTCL+FK18tX9Oar16Clt/msog/s=
+github.com/prometheus/common v0.26.0 h1:iMAkS2TDoNWnKM+Kopnx/8tnEStIfpYA0ur0xQzzhMQ=
+github.com/prometheus/common v0.26.0/go.mod h1:M7rCNAaPfAosfx8veZJCuw84e35h3Cfd9VFqTh1DIvc=
github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk=
+github.com/prometheus/procfs v0.0.0-20190117184657-bf6a532e95b1/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk=
github.com/prometheus/procfs v0.0.0-20190507164030-5867b95ac084/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA=
+github.com/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA=
+github.com/prometheus/procfs v0.0.8/go.mod h1:7Qr8sr6344vo1JqZ6HhLceV9o3AJ1Ff+GxbHq6oeK9A=
+github.com/prometheus/procfs v0.1.3/go.mod h1:lV6e/gmhEcM9IjHGsFOCxxuZ+z1YqCvr4OA4YeYWdaU=
+github.com/prometheus/procfs v0.6.0 h1:mxy4L2jP6qMonqmq+aTtOx1ifVWUgG/TAmntgbh3xv4=
+github.com/prometheus/procfs v0.6.0/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1xBZuNvfVA=
github.com/prometheus/tsdb v0.7.1/go.mod h1:qhTCs0VvXwvX/y3TZrWD7rabWM+ijKTux40TwIPHuXU=
+github.com/r3labs/sse v0.0.0-20210224172625-26fe804710bc h1:zAsgcP8MhzAbhMnB1QQ2O7ZhWYVGYSR2iVcjzQuPV+o=
+github.com/r3labs/sse v0.0.0-20210224172625-26fe804710bc/go.mod h1:S8xSOnV3CgpNrWd0GQ/OoQfMtlg2uPRSuTzcSGrzwK8=
+github.com/rcrowley/go-metrics v0.0.0-20181016184325-3113b8401b8a/go.mod h1:bCqnVzQkZxMG4s8nGwiZ5l3QUCyqpo9Y+/ZMZ9VjZe4=
+github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6SoW27p1b0cqNHllgS5HIMJraePCO15w5zCzIWYg=
github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ=
-github.com/sirupsen/logrus v1.2.0 h1:juTguoYk5qI21pwyTXY3B3Y5cOTH3ZUyZCg1v/mihuo=
+github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
+github.com/rogpeppe/go-internal v1.6.1 h1:/FiVV8dS/e+YqF2JvO3yXRFbBLTIuSDkuC7aBOAvL+k=
+github.com/rs/xid v1.2.1/go.mod h1:+uKXf+4Djp6Md1KODXJxgGQPKngRmWyn10oCKFzNHOQ=
+github.com/rs/zerolog v1.13.0/go.mod h1:YbFCdg8HfsridGWAh22vktObvhZbQsZXe4/zB0OKkWU=
+github.com/rs/zerolog v1.15.0/go.mod h1:xYTKnLHcpfU2225ny5qZjxnj9NvkumZYjJHlAThCjNc=
+github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
+github.com/ryanuber/columnize v0.0.0-20160712163229-9b3edd62028f/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts=
+github.com/samuel/go-zookeeper v0.0.0-20190923202752-2cc03de413da/go.mod h1:gi+0XIa01GRL2eRQVjQkKGqKF3SF9vZR/HnPullcV2E=
+github.com/satori/go.uuid v1.2.0 h1:0uYX9dsZ2yD7q2RtLRtPSdGDWzjeM3TbMJP9utgA0ww=
+github.com/satori/go.uuid v1.2.0/go.mod h1:dA0hQrYB0VpLJoorglMZABFdXlWrHn1NEOzdhQKdks0=
+github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg65j358z/aeFdxmN0P9QXhEzd20vsDc=
+github.com/shopspring/decimal v0.0.0-20180709203117-cd690d0c9e24/go.mod h1:M+9NzErvs504Cn4c5DxATwIqPbtswREoFCre64PpcG4=
+github.com/shopspring/decimal v1.2.0/go.mod h1:DKyhrW/HYNuLGql+MJL6WCR6knT2jwCFRcu2hWCYk4o=
+github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc=
github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo=
+github.com/sirupsen/logrus v1.4.1/go.mod h1:ni0Sbl8bgC9z8RoU9G6nDWqqs/fq4eDPysMBDgk/93Q=
+github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE=
+github.com/sirupsen/logrus v1.6.0/go.mod h1:7uNnSEd1DgxDLC74fIahvMZmmYsHGZGEOFrfsX/uA88=
+github.com/sirupsen/logrus v1.7.0 h1:ShrD1U9pZB12TX0cVy0DtePoCH97K8EtX+mg7ZARUtM=
+github.com/sirupsen/logrus v1.7.0/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0=
github.com/skip2/go-qrcode v0.0.0-20200617195104-da1b6568686e h1:MRM5ITcdelLK2j1vwZ3Je0FKVCfqOLp5zO6trqMLYs0=
github.com/skip2/go-qrcode v0.0.0-20200617195104-da1b6568686e/go.mod h1:XV66xRDqSt+GTGFMVlhk3ULuV0y9ZmzeVGR4mloJI3M=
+github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d h1:zE9ykElWQ6/NYmHa3jpm/yHnI4xSofP+UP6SpjHcSeM=
+github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc=
+github.com/smartystreets/goconvey v1.6.4 h1:fv0U8FUIMPNf1L9lnHLvLhgicrIVChEkdzIKYqbNC9s=
+github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA=
+github.com/soheilhy/cmux v0.1.4/go.mod h1:IM3LyeVVIOuxMH7sFAkER9+bJ4dT7Ms6E4xg4kGIyLM=
+github.com/soheilhy/cmux v0.1.5 h1:jjzc5WVemNEDTLwv9tlmemhC73tI08BNOIGwBOo10Js=
+github.com/soheilhy/cmux v0.1.5/go.mod h1:T7TcVDs9LWfQgPlPsdngu6I6QIoyIFZDDC6sNE1GqG0=
+github.com/sony/gobreaker v0.4.1/go.mod h1:ZKptC7FHNvhBz7dN2LGjPVBz2sZJmc0/PkyDJOjmxWY=
github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA=
+github.com/spf13/afero v1.1.2/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B0CQ=
+github.com/spf13/cast v1.3.0/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE=
+github.com/spf13/cast v1.3.1/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE=
+github.com/spf13/cast v1.5.0 h1:rj3WzYc11XZaIZMPKmwP96zkFEnnAmV8s6XbB2aY32w=
+github.com/spf13/cast v1.5.0/go.mod h1:SpXXQ5YoyJw6s3/6cMTQuxvgRl3PCJiyaX9p6b155UU=
+github.com/spf13/cobra v0.0.3/go.mod h1:1l0Ry5zgKvJasoi3XT1TypsSe7PqH0Sj9dhYf7v3XqQ=
+github.com/spf13/cobra v1.1.3/go.mod h1:pGADOWyqRD/YMrPZigI/zbliZ2wVD/23d+is3pSWzOo=
+github.com/spf13/jwalterweatherman v1.0.0/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb68N+wFjFa4jdeBTo=
+github.com/spf13/pflag v0.0.0-20170130214245-9ff6c6923cff/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4=
+github.com/spf13/pflag v1.0.1/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4=
+github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4=
+github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA=
+github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
+github.com/spf13/viper v1.7.0/go.mod h1:8WkrPz2fc9jxqZNCJI/76HCieCp4Q8HaLFoCha5qpdg=
+github.com/streadway/amqp v0.0.0-20190404075320-75d898a42a94/go.mod h1:AZpEONHx3DKn8O/DFsRAY58/XVQiIPMTMB1SddzLXVw=
+github.com/streadway/amqp v0.0.0-20190827072141-edfb9018d271/go.mod h1:AZpEONHx3DKn8O/DFsRAY58/XVQiIPMTMB1SddzLXVw=
+github.com/streadway/handy v0.0.0-20190108123426-d5acb3125c2a/go.mod h1:qNTQ5P5JnDBl6z3cMAg/SywNDC5ABu5ApDIw6lUbRmI=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
+github.com/stretchr/objx v0.2.0 h1:Hbg2NidpLE8veEBkEZTL3CvlkUIVzuU9jDplZO54c48=
github.com/stretchr/objx v0.2.0/go.mod h1:qt09Ya8vawLte6SNmTgCsAVtYtaKzEcn8ATUoHMkEqE=
+github.com/stretchr/testify v0.0.0-20151208002404-e3a8ff8ce365/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
-github.com/stretchr/testify v1.5.1 h1:nOGnQDM7FYENwehXlg/kFVnos3rEvtKTjRvOWSzb6H4=
+github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
-github.com/tidwall/btree v0.6.0 h1:JLYAFGV+1gjyFi3iQbO/fupBin+Ooh7dxqVV0twJ1Bo=
-github.com/tidwall/btree v0.6.0/go.mod h1:TzIRzen6yHbibdSfK6t8QimqbUnoxUSrZfeW7Uob0q4=
+github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
+github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
+github.com/stretchr/testify v1.7.1 h1:5TQK59W5E3v0r2duFAb7P95B6hEeOyEnHRa8MjYSMTY=
+github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
+github.com/subosito/gotenv v1.2.0/go.mod h1:N0PQaV/YGNqwC0u51sEeR/aUtSLEXKX9iv69rRypqCw=
+github.com/syndtr/goleveldb v1.0.1-0.20210819022825-2ae1ddf74ef7 h1:epCh84lMvA70Z7CTTCmYQn2CKbY8j86K7/FAIr141uY=
+github.com/syndtr/goleveldb v1.0.1-0.20210819022825-2ae1ddf74ef7/go.mod h1:q4W45IWZaF22tdD+VEXcAWRA037jwmWEB5VWYORlTpc=
+github.com/tidwall/assert v0.1.0 h1:aWcKyRBUAdLoVebxo95N7+YZVTFF/ASTr7BN4sLP6XI=
+github.com/tidwall/assert v0.1.0/go.mod h1:QLYtGyeqse53vuELQheYl9dngGCJQ+mTtlxcktb+Kj8=
github.com/tidwall/btree v0.6.1 h1:75VVgBeviiDO+3g4U+7+BaNBNhNINxB0ULPT3fs9pMY=
github.com/tidwall/btree v0.6.1/go.mod h1:TzIRzen6yHbibdSfK6t8QimqbUnoxUSrZfeW7Uob0q4=
-github.com/tidwall/buntdb v1.2.6 h1:eS0QSmzHfCKjxxYGh8eH6wnK5VLsJ7UjyyIr29JmnEg=
-github.com/tidwall/buntdb v1.2.6/go.mod h1:zpXqlA5D2772I4cTqV3ifr2AZihDgi8FV7xAQu6edfc=
-github.com/tidwall/gjson v1.6.0/go.mod h1:P256ACg0Mn+j1RXIDXoss50DeIABTYK1PULOJHhxOls=
-github.com/tidwall/gjson v1.6.1 h1:LRbvNuNuvAiISWg6gxLEFuCe72UKy5hDqhxW/8183ws=
+github.com/tidwall/buntdb v1.2.7 h1:SIyObKAymzLyGhDeIhVk2Yc1/EwfCC75Uyu77CHlVoA=
+github.com/tidwall/buntdb v1.2.7/go.mod h1:b6KvZM27x/8JLI5hgRhRu60pa3q0Tz9c50TyD46OHUM=
github.com/tidwall/gjson v1.6.1/go.mod h1:BaHyNc5bjzYkPqgLq7mdVzeiRtULKULXLgZFKsxEHI0=
-github.com/tidwall/gjson v1.8.0 h1:Qt+orfosKn0rbNTZqHYDqBrmm3UDA4KRkv70fDzG+PQ=
-github.com/tidwall/gjson v1.8.0/go.mod h1:5/xDoumyyDNerp2U36lyolv46b3uF/9Bu6OfyQ9GImk=
-github.com/tidwall/gjson v1.8.1 h1:8j5EE9Hrh3l9Od1OIEDAb7IpezNA20UdRngNAj5N0WU=
-github.com/tidwall/gjson v1.8.1/go.mod h1:5/xDoumyyDNerp2U36lyolv46b3uF/9Bu6OfyQ9GImk=
-github.com/tidwall/grect v0.1.2 h1:wKVeQVZhjaFCKTTlpkDe3Ex4ko3cMGW3MRKawRe8uQ4=
-github.com/tidwall/grect v0.1.2/go.mod h1:v+n4ewstPGduVJebcp5Eh2WXBJBumNzyhK8GZt4gHNw=
+github.com/tidwall/gjson v1.10.2/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk=
+github.com/tidwall/gjson v1.12.1 h1:ikuZsLdhr8Ws0IdROXUS1Gi4v9Z4pGqpX/CvJkxvfpo=
+github.com/tidwall/gjson v1.12.1/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk=
+github.com/tidwall/grect v0.1.3 h1:z9YwQAMUxVSBde3b7Sl8Da37rffgNfZ6Fq6h9t6KdXE=
+github.com/tidwall/grect v0.1.3/go.mod h1:8GMjwh3gPZVpLBI/jDz9uslCe0dpxRpWDdtN0lWAS/E=
github.com/tidwall/lotsa v1.0.2 h1:dNVBH5MErdaQ/xd9s769R31/n2dXavsQ0Yf4TMEHHw8=
github.com/tidwall/lotsa v1.0.2/go.mod h1:X6NiU+4yHA3fE3Puvpnn1XMDrFZrE9JO2/w+UMuqgR8=
-github.com/tidwall/match v1.0.1 h1:PnKP62LPNxHKTwvHHZZzdOAOCtsJTjo6dZLCwpKm5xc=
github.com/tidwall/match v1.0.1/go.mod h1:LujAq0jyVjBy028G1WhWfIzbpQfMO8bBZ6Tyb0+pL9E=
-github.com/tidwall/match v1.0.3 h1:FQUVvBImDutD8wJLN6c5eMzWtjgONK9MwIBCOrUJKeE=
-github.com/tidwall/match v1.0.3/go.mod h1:eRSPERbgtNPcGhD8UCthc6PmLEQXEWd3PRB5JTxsfmM=
+github.com/tidwall/match v1.1.1 h1:+Ho715JplO36QYgwN9PGYNhgZvoUSc9X2c80KVTi+GA=
+github.com/tidwall/match v1.1.1/go.mod h1:eRSPERbgtNPcGhD8UCthc6PmLEQXEWd3PRB5JTxsfmM=
github.com/tidwall/pretty v1.0.0/go.mod h1:XNkn88O1ChpSDQmQeStsy+sBenx6DDtFZJxhVysOjyk=
-github.com/tidwall/pretty v1.0.2 h1:Z7S3cePv9Jwm1KwS0513MRaoUe3S01WPbLNV40pwWZU=
github.com/tidwall/pretty v1.0.2/go.mod h1:XNkn88O1ChpSDQmQeStsy+sBenx6DDtFZJxhVysOjyk=
-github.com/tidwall/pretty v1.1.0 h1:K3hMW5epkdAVwibsQEfR/7Zj0Qgt4DxtNumTq/VloO8=
-github.com/tidwall/pretty v1.1.0/go.mod h1:XNkn88O1ChpSDQmQeStsy+sBenx6DDtFZJxhVysOjyk=
github.com/tidwall/pretty v1.2.0 h1:RWIZEg2iJ8/g6fDDYzMpobmaoGh5OLl4AXtGUGPcqCs=
github.com/tidwall/pretty v1.2.0/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU=
github.com/tidwall/rtred v0.1.2 h1:exmoQtOLvDoO8ud++6LwVsAMTu0KPzLTUrMln8u1yu8=
github.com/tidwall/rtred v0.1.2/go.mod h1:hd69WNXQ5RP9vHd7dqekAz+RIdtfBogmglkZSRxCHFQ=
+github.com/tidwall/sjson v1.2.4 h1:cuiLzLnaMeBhRmEv00Lpk3tkYrcxpmbU81tAY4Dw0tc=
+github.com/tidwall/sjson v1.2.4/go.mod h1:098SZ494YoMWPmMO6ct4dcFnqxwj9r/gF0Etp19pSNM=
github.com/tidwall/tinyqueue v0.1.1 h1:SpNEvEggbpyN5DIReaJ2/1ndroY8iyEGxPYxoSaymYE=
github.com/tidwall/tinyqueue v0.1.1/go.mod h1:O/QNHwrnjqr6IHItYrzoHAKYhBkLI67Q096fQP5zMYw=
+github.com/tmc/grpc-websocket-proxy v0.0.0-20170815181823-89b8d40f7ca8/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U=
+github.com/tmc/grpc-websocket-proxy v0.0.0-20190109142713-0ad062ec5ee5/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U=
+github.com/tmc/grpc-websocket-proxy v0.0.0-20201229170055-e5319fda7802 h1:uruHq4dN7GR16kFc5fp3d1RIYzJW5onx8Ybykw2YQFA=
+github.com/tmc/grpc-websocket-proxy v0.0.0-20201229170055-e5319fda7802/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U=
github.com/tv42/zbase32 v0.0.0-20160707012821-501572607d02/go.mod h1:tHlrkM198S068ZqfrO6S8HsoJq2bF3ETfTL+kt4tInY=
+github.com/ulikunitz/xz v0.5.6/go.mod h1:2bypXElzHzzJZwzH67Y6wb67pO62Rzfn7BSiF4ABRW8=
+github.com/ulikunitz/xz v0.5.7/go.mod h1:nbz6k7qbPmH4IRqmfOplQw/tblSgqTqBwxkY0oWt/14=
+github.com/ulikunitz/xz v0.5.10 h1:t92gobL9l3HE202wg3rlk19F6X+JOxl9BBrCCMYEYd8=
+github.com/ulikunitz/xz v0.5.10/go.mod h1:nbz6k7qbPmH4IRqmfOplQw/tblSgqTqBwxkY0oWt/14=
github.com/urfave/cli v1.18.0/go.mod h1:70zkFmudgCuE/ngEzBv17Jvp/497gISqfk5gWijbERA=
-go.etcd.io/bbolt v1.3.3 h1:MUGmc65QhB3pIlaQ5bB4LwqSj6GIonVJXpZiaKNyaKk=
+github.com/urfave/cli v1.20.0/go.mod h1:70zkFmudgCuE/ngEzBv17Jvp/497gISqfk5gWijbERA=
+github.com/urfave/cli v1.22.1/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0=
+github.com/urfave/cli v1.22.4/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0=
+github.com/valyala/fastjson v1.6.3 h1:tAKFnnwmeMGPbwJ7IwxcTPCNr3uIzoIj3/Fh90ra4xc=
+github.com/valyala/fastjson v1.6.3/go.mod h1:CLCAqky6SMuOcxStkYQvblddUtoRxhYMGLrsQns1aXY=
+github.com/vmihailenco/msgpack v4.0.4+incompatible/go.mod h1:fy3FlTQTDXWkZ7Bh6AcGMlsjHatGryHQYUTf1ShIgkk=
+github.com/xi2/xz v0.0.0-20171230120015-48954b6210f8 h1:nIPpBwaJSVYIxUFsDv3M8ofmx9yWTog9BfvIu0q41lo=
+github.com/xi2/xz v0.0.0-20171230120015-48954b6210f8/go.mod h1:HUYIGzjTL3rfEspMxjDjgmT5uz5wzYJKVo23qUhYTos=
+github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2 h1:eY9dn8+vbi4tKz5Qo6v2eYzo7kUS51QINcR5jNpbZS8=
+github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU=
+github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
+github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
+github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
+github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
+github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k=
+github.com/yuin/goldmark v1.4.1/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k=
+github.com/zenazn/goji v0.9.0/go.mod h1:7S9M489iMyHBNxwZnk9/EHS098H4/F6TATF2mIxtB1Q=
+gitlab.com/yawning/bsaes.git v0.0.0-20190805113838-0a714cd429ec/go.mod h1:BZ1RAoRPbCxum9Grlv5aeksu2H8BiKehBYooU2LFiOQ=
+go.etcd.io/bbolt v1.3.2/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU=
go.etcd.io/bbolt v1.3.3/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU=
+go.etcd.io/bbolt v1.3.5-0.20200615073812-232d8fc87f50/go.mod h1:G5EMThwa9y8QZGBClrRx5EY+Yw9kAhnjy3bSjsnlVTQ=
+go.etcd.io/bbolt v1.3.6 h1:/ecaJf0sk1l4l6V4awd65v2C3ILy7MSj+s/x1ADCIMU=
+go.etcd.io/bbolt v1.3.6/go.mod h1:qXsaaIqmgQH0T+OPdb99Bf+PKfBBQVAdyD6TY9G8XM4=
+go.etcd.io/etcd v0.0.0-20191023171146-3cf2f69b5738 h1:VcrIfasaLFkyjk6KNlXQSzO+B0fZcnECiDrKJsfxka0=
+go.etcd.io/etcd v0.0.0-20191023171146-3cf2f69b5738/go.mod h1:dnLIgRNXwCJa5e+c6mIZCrds/GIG4ncV9HhK5PX7jPg=
+go.etcd.io/etcd/api/v3 v3.5.0 h1:GsV3S+OfZEOCNXdtNkBSR7kgLobAa/SO6tCxRa0GAYw=
+go.etcd.io/etcd/api/v3 v3.5.0/go.mod h1:cbVKeC6lCfl7j/8jBhAK6aIYO9XOjdptoxU/nLQcPvs=
+go.etcd.io/etcd/client/pkg/v3 v3.5.0 h1:2aQv6F436YnN7I4VbI8PPYrBhu+SmrTaADcf8Mi/6PU=
+go.etcd.io/etcd/client/pkg/v3 v3.5.0/go.mod h1:IJHfcCEKxYu1Os13ZdwCwIUTUVGYTSAM3YSwc9/Ac1g=
+go.etcd.io/etcd/client/v2 v2.305.0 h1:ftQ0nOOHMcbMS3KIaDQ0g5Qcd6bhaBrQT6b89DfwLTs=
+go.etcd.io/etcd/client/v2 v2.305.0/go.mod h1:h9puh54ZTgAKtEbut2oe9P4L/oqKCVB6xsXlzd7alYQ=
+go.etcd.io/etcd/client/v3 v3.5.0 h1:62Eh0XOro+rDwkrypAGDfgmNh5Joq+z+W9HZdlXMzek=
+go.etcd.io/etcd/client/v3 v3.5.0/go.mod h1:AIKXXVX/DQXtfTEqBryiLTUXwON+GuvO6Z7lLS/oTh0=
+go.etcd.io/etcd/pkg/v3 v3.5.0 h1:ntrg6vvKRW26JRmHTE0iNlDgYK6JX3hg/4cD62X0ixk=
+go.etcd.io/etcd/pkg/v3 v3.5.0/go.mod h1:UzJGatBQ1lXChBkQF0AuAtkRQMYnHubxAEYIrC3MSsE=
+go.etcd.io/etcd/raft/v3 v3.5.0 h1:kw2TmO3yFTgE+F0mdKkG7xMxkit2duBDa2Hu6D/HMlw=
+go.etcd.io/etcd/raft/v3 v3.5.0/go.mod h1:UFOHSIvO/nKwd4lhkwabrTD3cqW5yVyYYf/KlD00Szc=
+go.etcd.io/etcd/server/v3 v3.5.0 h1:jk8D/lwGEDlQU9kZXUFMSANkE22Sg5+mW27ip8xcF9E=
+go.etcd.io/etcd/server/v3 v3.5.0/go.mod h1:3Ah5ruV+M+7RZr0+Y/5mNLwC+eQlni+mQmOVdCRJoS4=
go.mongodb.org/mongo-driver v1.0.3/go.mod h1:u7ryQJ+DOzQmeO7zB6MHyr8jkEQvC8vH7qLUO4lqsUM=
+go.opencensus.io v0.20.1/go.mod h1:6WKK9ahsWS3RSO+PY9ZHZUfv2irvY6gN279GOPZjmmk=
+go.opencensus.io v0.20.2/go.mod h1:6WKK9ahsWS3RSO+PY9ZHZUfv2irvY6gN279GOPZjmmk=
+go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU=
+go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8=
+go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
+go.opencensus.io v0.22.3/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
+go.opencensus.io v0.22.4/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
+go.opentelemetry.io/contrib v0.20.0 h1:ubFQUn0VCZ0gPwIoJfBJVpeBlyRMxu8Mm/huKWYd9p0=
+go.opentelemetry.io/contrib v0.20.0/go.mod h1:G/EtFaa6qaN7+LxqfIAT3GiZa7Wv5DTBUzl5H4LY0Kc=
+go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.20.0 h1:sO4WKdPAudZGKPcpZT4MJn6JaDmpyLrMPDGGyA1SttE=
+go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.20.0/go.mod h1:oVGt1LRbBOBq1A5BQLlUg9UaU/54aiHw8cgjV3aWZ/E=
+go.opentelemetry.io/otel v0.19.0/go.mod h1:j9bF567N9EfomkSidSfmMwIwIBuP37AMAIzVW85OxSg=
+go.opentelemetry.io/otel v0.20.0 h1:eaP0Fqu7SXHwvjiqDq83zImeehOHX8doTvU9AwXON8g=
+go.opentelemetry.io/otel v0.20.0/go.mod h1:Y3ugLH2oa81t5QO+Lty+zXf8zC9L26ax4Nzoxm/dooo=
+go.opentelemetry.io/otel/exporters/otlp v0.20.0 h1:PTNgq9MRmQqqJY0REVbZFvwkYOA85vbdQU/nVfxDyqg=
+go.opentelemetry.io/otel/exporters/otlp v0.20.0/go.mod h1:YIieizyaN77rtLJra0buKiNBOm9XQfkPEKBeuhoMwAM=
+go.opentelemetry.io/otel/metric v0.19.0/go.mod h1:8f9fglJPRnXuskQmKpnad31lcLJ2VmNNqIsx/uIwBSc=
+go.opentelemetry.io/otel/metric v0.20.0 h1:4kzhXFP+btKm4jwxpjIqjs41A7MakRFUS86bqLHTIw8=
+go.opentelemetry.io/otel/metric v0.20.0/go.mod h1:598I5tYlH1vzBjn+BTuhzTCSb/9debfNp6R3s7Pr1eU=
+go.opentelemetry.io/otel/oteltest v0.19.0/go.mod h1:tI4yxwh8U21v7JD6R3BcA/2+RBoTKFexE/PJ/nSO7IA=
+go.opentelemetry.io/otel/oteltest v0.20.0 h1:HiITxCawalo5vQzdHfKeZurV8x7ljcqAgiWzF6Vaeaw=
+go.opentelemetry.io/otel/oteltest v0.20.0/go.mod h1:L7bgKf9ZB7qCwT9Up7i9/pn0PWIa9FqQ2IQ8LoxiGnw=
+go.opentelemetry.io/otel/sdk v0.20.0 h1:JsxtGXd06J8jrnya7fdI/U/MR6yXA5DtbZy+qoHQlr8=
+go.opentelemetry.io/otel/sdk v0.20.0/go.mod h1:g/IcepuwNsoiX5Byy2nNV0ySUF1em498m7hBWC279Yc=
+go.opentelemetry.io/otel/sdk/export/metric v0.20.0 h1:c5VRjxCXdQlx1HjzwGdQHzZaVI82b5EbBgOu2ljD92g=
+go.opentelemetry.io/otel/sdk/export/metric v0.20.0/go.mod h1:h7RBNMsDJ5pmI1zExLi+bJK+Dr8NQCh0qGhm1KDnNlE=
+go.opentelemetry.io/otel/sdk/metric v0.20.0 h1:7ao1wpzHRVKf0OQ7GIxiQJA6X7DLX9o14gmVon7mMK8=
+go.opentelemetry.io/otel/sdk/metric v0.20.0/go.mod h1:knxiS8Xd4E/N+ZqKmUPf3gTTZ4/0TjTXukfxjzSTpHE=
+go.opentelemetry.io/otel/trace v0.19.0/go.mod h1:4IXiNextNOpPnRlI4ryK69mn5iC84bjBWZQA5DXz/qg=
+go.opentelemetry.io/otel/trace v0.20.0 h1:1DL6EXUdcg95gukhuRRvLDO/4X5THh/5dIV52lqtnbw=
+go.opentelemetry.io/otel/trace v0.20.0/go.mod h1:6GjCW8zgDjwGHGa6GkyeB8+/5vjT16gUEi0Nf1iBdgw=
+go.opentelemetry.io/proto/otlp v0.7.0 h1:rwOQPCuKAKmwGKq2aVNnYIibI6wnV7EvzgfTCzcdGg8=
+go.opentelemetry.io/proto/otlp v0.7.0/go.mod h1:PqfVotwruBrMGOCsRd/89rSnXhoiJIqeYNgFYFoEGnI=
+go.uber.org/atomic v1.3.2/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE=
+go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE=
+go.uber.org/atomic v1.5.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ=
+go.uber.org/atomic v1.6.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ=
+go.uber.org/atomic v1.7.0 h1:ADUqmZGgLDDfbSL9ZmPxKTybcoEYHgpYfELNoN+7hsw=
+go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc=
+go.uber.org/goleak v1.1.10/go.mod h1:8a7PlsEVH3e/a/GLqe5IIrQx6GzcnRmZEufDUTk4A7A=
+go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0=
+go.uber.org/multierr v1.3.0/go.mod h1:VgVr7evmIr6uPjLBxg28wmKNXyqE9akIJ5XnfpiKl+4=
+go.uber.org/multierr v1.5.0/go.mod h1:FeouvMocqHpRaaGuG9EjoKcStLC43Zu/fmqdUMPcKYU=
+go.uber.org/multierr v1.6.0 h1:y6IPFStTAIT5Ytl7/XYmHvzXQ7S3g/IeZW9hyZ5thw4=
+go.uber.org/multierr v1.6.0/go.mod h1:cdWPpRnG4AhwMwsgIHip0KRBQjJy5kYEpYjJxpXp9iU=
+go.uber.org/tools v0.0.0-20190618225709-2cfd321de3ee/go.mod h1:vJERXedbb3MVM5f9Ejo0C68/HhF8uaILCdgjnY+goOA=
+go.uber.org/zap v1.9.1/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q=
+go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q=
+go.uber.org/zap v1.13.0/go.mod h1:zwrFLgMcdUuIBviXEYEH1YKNaOBnKXsx2IPda5bBwHM=
+go.uber.org/zap v1.17.0 h1:MTjgFu6ZLKvY6Pvaqk97GlxNBuMpV4Hy/3P6tRGlI2U=
+go.uber.org/zap v1.17.0/go.mod h1:MXVU+bhUf/A7Xi2HNOnopQOrmycQ5Ih87HtOu4q5SSo=
golang.org/x/crypto v0.0.0-20170930174604-9419663f5a44/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
+golang.org/x/crypto v0.0.0-20180214000028-650f4a345ab4/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
+golang.org/x/crypto v0.0.0-20181029021203-45a5f77698d3/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
golang.org/x/crypto v0.0.0-20190211182817-74369b46fc67/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
+golang.org/x/crypto v0.0.0-20190411191339-88737f569e3a/go.mod h1:WFFai1msRO1wXaEeE5yQxYXgSfI8pQAWXbQop6sCtWE=
+golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
+golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
+golang.org/x/crypto v0.0.0-20190701094942-4def268fd1a4/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
+golang.org/x/crypto v0.0.0-20190820162420-60c769a6c586/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
+golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20200115085410-6d4e4cb37c7d/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
-golang.org/x/crypto v0.0.0-20200510223506-06a226fb4e37 h1:cg5LA/zNPRzIXIWSCxQW10Rvpy94aQh3LT/ShoCpkHw=
golang.org/x/crypto v0.0.0-20200510223506-06a226fb4e37/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
+golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
+golang.org/x/crypto v0.0.0-20200820211705-5c72a883971a/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
+golang.org/x/crypto v0.0.0-20201002170205-7f63de1d35b0/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
+golang.org/x/crypto v0.0.0-20201203163018-be400aefbc4c/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I=
+golang.org/x/crypto v0.0.0-20210616213533-5ff15b29337e/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
+golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
+golang.org/x/crypto v0.0.0-20210921155107-089bfa567519 h1:7I4JAnoQBe7ZtJcBaYHi5UtiO8tQHbUSXxL+pnGRANg=
+golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
+golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
+golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8=
+golang.org/x/exp v0.0.0-20190829153037-c13cbed26979/go.mod h1:86+5VVa7VpoJ4kLfm080zCjGlMRFzhUhsZKEZO7MGek=
+golang.org/x/exp v0.0.0-20191030013958-a1ab85dbe136/go.mod h1:JXzH8nQsPlswgeRAPE3MuO9GYsAcnJvJ4vnMwN/5qkY=
+golang.org/x/exp v0.0.0-20191129062945-2f5052295587/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4=
+golang.org/x/exp v0.0.0-20191227195350-da58074b4299/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4=
+golang.org/x/exp v0.0.0-20200119233911-0405dc783f0a/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4=
+golang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EHIKF9dgMWnmCNThgcyBT1FY9mM=
+golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6/go.mod h1:3jZMyOhIsHpP37uCMkUooju7aAi5cS1Q23tOzKc+0MU=
+golang.org/x/exp v0.0.0-20220426173459-3bcf042a4bf5/go.mod h1:lgLbSvA5ygNOMpwM/9anMpWVlVJ7Z+cHWq/eFuinpGE=
+golang.org/x/exp v0.0.0-20221106115401-f9659909a136 h1:Fq7F/w7MAa1KJ5bt2aJ62ihqp9HDcRuyILskkpIAurw=
+golang.org/x/exp v0.0.0-20221106115401-f9659909a136/go.mod h1:CxIveKay+FTh1D0yPZemJVgC/95VzuuOLq5Qi4xnoYc=
+golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js=
+golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
golang.org/x/lint v0.0.0-20180702182130-06c8688daad7/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
+golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=
+golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
+golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
+golang.org/x/lint v0.0.0-20190409202823-959b441ac422/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
+golang.org/x/lint v0.0.0-20190909230951-414d861bb4ac/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
+golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
+golang.org/x/lint v0.0.0-20191125180803-fdd1cda4f05f/go.mod h1:5qLYkcX4OjUUV8bRuDixDT3tpyyb+LUpUlRWLxfhWrs=
+golang.org/x/lint v0.0.0-20200130185559-910be7a94367/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=
+golang.org/x/lint v0.0.0-20200302205851-738671d3881b/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=
+golang.org/x/lint v0.0.0-20210508222113-6edffad5e616/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=
+golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE=
+golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o=
+golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc=
+golang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY=
+golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=
+golang.org/x/mod v0.1.1-0.20191107180719-034126e5016b/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=
+golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
+golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
+golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
+golang.org/x/mod v0.5.1/go.mod h1:5OXOZSfqPIIbmVBIIKWRFfZjPR0E5r58TLhUjH0a2Ro=
+golang.org/x/mod v0.6.0-dev.0.20211013180041-c96bc1413d57/go.mod h1:3p9vT2HGsQu2K1YbXdKPJLVgG5VJdoTa1poYQBtP1AY=
+golang.org/x/net v0.0.0-20170114055629-f2499483f923/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
+golang.org/x/net v0.0.0-20180406214816-61147c48b25b/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20180719180050-a680a1efc54d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
+golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
+golang.org/x/net v0.0.0-20181023162649-9b4f9f5ad519/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20181106065722-10aee1819953/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
+golang.org/x/net v0.0.0-20181201002055-351d144fa1fc/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20181220203305-927f97764cc3/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
+golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
+golang.org/x/net v0.0.0-20190125091013-d26f9f9a57f3/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190206173232-65e2d4e15006/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
-golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3 h1:0GoQqolDA55aaLxZyTzK/Y2ePZzZTUrRacwib7cNsYQ=
+golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
+golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
+golang.org/x/net v0.0.0-20190501004415-9ce7a6920f09/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
+golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
+golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks=
+golang.org/x/net v0.0.0-20190613194153-d28f0bde5980/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
+golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
+golang.org/x/net v0.0.0-20190628185345-da137c7871d7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
+golang.org/x/net v0.0.0-20190724013045-ca1201d0de80/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
+golang.org/x/net v0.0.0-20190813141303-74dc4d7220e7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
+golang.org/x/net v0.0.0-20191004110552-13f9640d40b9/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
+golang.org/x/net v0.0.0-20191105084925-a882066a44e0/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
+golang.org/x/net v0.0.0-20191116160921-f9c825593386/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
+golang.org/x/net v0.0.0-20191209160850-c0dbc17a3553/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
+golang.org/x/net v0.0.0-20200114155413-6afb5195e5aa/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
+golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
+golang.org/x/net v0.0.0-20200222125558-5a598a2470a0/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
+golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
+golang.org/x/net v0.0.0-20200301022130-244492dfa37a/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
+golang.org/x/net v0.0.0-20200324143707-d3edc9973b7e/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
+golang.org/x/net v0.0.0-20200501053045-e0ff5e5a1de5/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
+golang.org/x/net v0.0.0-20200506145744-7e3656a0809f/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
+golang.org/x/net v0.0.0-20200513185701-a91f0712d120/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
+golang.org/x/net v0.0.0-20200520004742-59133d7f0dd7/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
+golang.org/x/net v0.0.0-20200520182314-0ba52f642ac2/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
+golang.org/x/net v0.0.0-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
+golang.org/x/net v0.0.0-20200707034311-ab3426394381/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
+golang.org/x/net v0.0.0-20200813134508-3edf25e44fcc/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
+golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
+golang.org/x/net v0.0.0-20200904194848-62affa334b73/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
+golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
+golang.org/x/net v0.0.0-20201202161906-c7110b5ffcbb/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
+golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
+golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM=
+golang.org/x/net v0.0.0-20210916014120-12bc252f5db8/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
+golang.org/x/net v0.0.0-20211015210444-4f30a5c0130f h1:OfiFi4JbukWwe3lzw+xunroH1mnC1e2Gy5cxNJApiSY=
+golang.org/x/net v0.0.0-20211015210444-4f30a5c0130f/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
+golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
+golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
+golang.org/x/oauth2 v0.0.0-20191202225959-858c2ad4c8b6/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
+golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
+golang.org/x/oauth2 v0.0.0-20210615190721-d04028783cf1/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
+golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
+golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
+golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
+golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
+golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
+golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
+golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
+golang.org/x/sync v0.0.0-20220819030929-7fc1605a5dde h1:ejfdSekXMDxDLbRrJMwUk6KnSLZ2McaUCVcIKM+N6jc=
+golang.org/x/sync v0.0.0-20220819030929-7fc1605a5dde/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
+golang.org/x/sys v0.0.0-20170830134202-bb24a47a89ea/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
+golang.org/x/sys v0.0.0-20180816055513-1c9583448a9c/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
+golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
+golang.org/x/sys v0.0.0-20181026203630-95b1ffbd15a5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20181107165924-66b7b1311ac8/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
+golang.org/x/sys v0.0.0-20181122145206-62eef0e2fa9b/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190209173611-3b5209105503/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
+golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
+golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20190403152447-81d4e9dc473e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20190804053845-51ab0e2deafa/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20190813064441-fde4db37ae7a/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20190826190057-c7b8b68b1456/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190904154756-749cb33beabd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
-golang.org/x/sys v0.0.0-20200116001909-b77594299b42 h1:vEOn+mP2zCOVzKckCZy6YsCtDblrpj/w7B9nxGNELpg=
+golang.org/x/sys v0.0.0-20191001151750-bb3f8db39f24/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20191010194322-b09406accb47/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20191220142924-d4481acd189f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20191228213918-04cbcbbfeed8/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20200106162015-b016eb3dc98e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20200113162924-86b910548bc1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20200122134326-e047566fdf82/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20200212091648-12a6c2dcc1e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20200302150141-5c8b2ff67527/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20200331124033-c3d80250170d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20200501052902-10377860bb8e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20200511232937-7e40ca221e25/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20200515095857-1151b9dac4a9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20200519105757-fe76b779f299/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20200523222454-059865788121/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20200615200032-f1bc736245b1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20200625212154-ddb9806d33ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20200803210538-64077c9b5642/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20200814200057-3d37ad5750ed/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20200923182605-d9f96fdee20d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20210112080510-489259a85091/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20210303074136-134d130e1a04/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20210309074719-68d13333faf2/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20210403161142-5e06dd20ab57/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20210426080607-c94f62235c83/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.0.0-20210603081109-ebe580a85c40/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.0.0-20211019181941-9d821ace8654/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.0.0-20220114195835-da31bd327af9/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.1.0 h1:kunALQeHf1/185U1i0GOB/fy1IPRDDpuoOOqRReG57U=
+golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw=
+golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1 h1:v+OssWQX+hTHEmOBgwxdZxK4zHq3yOs8F9J7mk0PY8E=
+golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
+golang.org/x/text v0.0.0-20160726164857-2910a502d2bf/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
+golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
-golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2 h1:z99zHgr7hKfrUcX/KsoJk5FJfjTceCKIp96+biqP4To=
golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
-golang.org/x/text v0.3.5 h1:i6eZZ+zk0SOf0xgBpEpPD18qWcJda6q1sxt3S0kzyUQ=
+golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
+golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
+golang.org/x/text v0.3.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
+golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
+golang.org/x/text v0.3.7 h1:olpwvP2KacW1ZWvsR7uQhoyTYvKAupfQrRGBFM352Gk=
+golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
golang.org/x/time v0.0.0-20180412165947-fbb02b2291d2/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
+golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
+golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
+golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
+golang.org/x/time v0.0.0-20210220033141-f8bda1e9f3ba h1:O8mE0/t419eoIwhTFpKVkHiTs/Igowgfkj25AcZrtiE=
+golang.org/x/time v0.0.0-20210220033141-f8bda1e9f3ba/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
+golang.org/x/tools v0.0.0-20180221164845-07fd8470d635/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20180828015842-6cd1fcedba52/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
+golang.org/x/tools v0.0.0-20181011042414-1f849cf54d09/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
+golang.org/x/tools v0.0.0-20181030221726-6c7e314b6563/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
+golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY=
+golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
+golang.org/x/tools v0.0.0-20190312151545-0bb0c0a6e846/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
+golang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
+golang.org/x/tools v0.0.0-20190328211700-ab21143f2384/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
+golang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
+golang.org/x/tools v0.0.0-20190425163242-31fd60d6bfdc/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
+golang.org/x/tools v0.0.0-20190506145303-2d16b83fe98c/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
+golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
+golang.org/x/tools v0.0.0-20190606124116-d0a3d012864b/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
+golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
+golang.org/x/tools v0.0.0-20190628153133-6cdbf07be9d0/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
+golang.org/x/tools v0.0.0-20190816200558-6889da9d5479/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
+golang.org/x/tools v0.0.0-20190823170909-c4a336ef6a2f/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
+golang.org/x/tools v0.0.0-20190911174233-4f2ddba30aff/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
+golang.org/x/tools v0.0.0-20191012152004-8de300cfc20a/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
+golang.org/x/tools v0.0.0-20191029041327-9cc4af7d6b2c/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
+golang.org/x/tools v0.0.0-20191029190741-b9c20aec41a5/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
+golang.org/x/tools v0.0.0-20191108193012-7d206e10da11/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
+golang.org/x/tools v0.0.0-20191112195655-aa38f8e97acc/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
+golang.org/x/tools v0.0.0-20191113191852-77e3bb0ad9e7/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
+golang.org/x/tools v0.0.0-20191115202509-3a792d9c32b2/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
+golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
+golang.org/x/tools v0.0.0-20191125144606-a911d9008d1f/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
+golang.org/x/tools v0.0.0-20191130070609-6e064ea0cf2d/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
+golang.org/x/tools v0.0.0-20191216173652-a0e659d51361/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
+golang.org/x/tools v0.0.0-20191227053925-7b8e75db28f4/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
+golang.org/x/tools v0.0.0-20200103221440-774c71fcf114/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
+golang.org/x/tools v0.0.0-20200117161641-43d50277825c/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
+golang.org/x/tools v0.0.0-20200122220014-bf1340f18c4a/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
+golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
+golang.org/x/tools v0.0.0-20200204074204-1cc6d1ef6c74/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
+golang.org/x/tools v0.0.0-20200207183749-b753a1ba74fa/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
+golang.org/x/tools v0.0.0-20200212150539-ea181f53ac56/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
+golang.org/x/tools v0.0.0-20200224181240-023911ca70b2/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
+golang.org/x/tools v0.0.0-20200227222343-706bc42d1f0d/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
+golang.org/x/tools v0.0.0-20200304193943-95d2e580d8eb/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw=
+golang.org/x/tools v0.0.0-20200312045724-11d5b4c81c7d/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw=
+golang.org/x/tools v0.0.0-20200331025713-a30bf2db82d4/go.mod h1:Sl4aGygMT6LrqrWclx+PTx3U+LnKx/seiNR+3G19Ar8=
+golang.org/x/tools v0.0.0-20200501065659-ab2804fb9c9d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
+golang.org/x/tools v0.0.0-20200512131952-2bc93b1c0c88/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
+golang.org/x/tools v0.0.0-20200515010526-7d3b6ebf133d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
+golang.org/x/tools v0.0.0-20200618134242-20370b0cb4b2/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
+golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
+golang.org/x/tools v0.0.0-20200729194436-6467de6f59a7/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA=
+golang.org/x/tools v0.0.0-20200804011535-6c149bb5ef0d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA=
+golang.org/x/tools v0.0.0-20200825202427-b303f430e36d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA=
+golang.org/x/tools v0.0.0-20201224043029-2b0845dc783e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
+golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
+golang.org/x/tools v0.1.2/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
+golang.org/x/tools v0.1.3/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
+golang.org/x/tools v0.1.8-0.20211029000441-d6a9af8af023/go.mod h1:nABZi5QlRsZVlzPpHl034qft6wpY4eDcsTt5AaioBiU=
+golang.org/x/tools v0.1.8/go.mod h1:nABZi5QlRsZVlzPpHl034qft6wpY4eDcsTt5AaioBiU=
+golang.org/x/xerrors v0.0.0-20190410155217-1f06c39b4373/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
+golang.org/x/xerrors v0.0.0-20190513163551-3ee3066db522/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
+golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
+golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
+golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE=
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
+google.golang.org/api v0.3.1/go.mod h1:6wY9I6uQWHQ8EM57III9mq/AjF+i8G65rmVagqKMtkk=
+google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE=
+google.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E3M=
+google.golang.org/api v0.8.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg=
+google.golang.org/api v0.9.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg=
+google.golang.org/api v0.13.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI=
+google.golang.org/api v0.14.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI=
+google.golang.org/api v0.15.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI=
+google.golang.org/api v0.17.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=
+google.golang.org/api v0.18.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=
+google.golang.org/api v0.19.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=
+google.golang.org/api v0.20.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=
+google.golang.org/api v0.22.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=
+google.golang.org/api v0.24.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE=
+google.golang.org/api v0.28.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE=
+google.golang.org/api v0.29.0/go.mod h1:Lcubydp8VUV7KeIHD9z2Bys/sm/vGKnG1UHuDBSrHWM=
+google.golang.org/api v0.30.0/go.mod h1:QGmEvQ87FHZNiUVJkT14jQNYJ4ZJjdRF23ZXz5138Fc=
google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
+google.golang.org/appengine v1.2.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
+google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
+google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
+google.golang.org/appengine v1.6.1/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww/cMBSeb0=
+google.golang.org/appengine v1.6.5/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=
+google.golang.org/appengine v1.6.6/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=
+google.golang.org/appengine v1.6.7/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=
google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
google.golang.org/genproto v0.0.0-20190201180003-4b09977fb922/go.mod h1:L3J43x8/uS+qIUoksaLKe6OS3nUKxOKuIFz1sl2/jx4=
+google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
+google.golang.org/genproto v0.0.0-20190418145605-e7d98fc518a7/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
+google.golang.org/genproto v0.0.0-20190425155659-357c62f0e4bb/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
+google.golang.org/genproto v0.0.0-20190502173448-54afdca5d873/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
+google.golang.org/genproto v0.0.0-20190530194941-fb225487d101/go.mod h1:z3L6/3dTEVtUr6QSP8miRzeRqwQOioJ9I66odjN4I7s=
+google.golang.org/genproto v0.0.0-20190801165951-fa694d86fc64/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=
+google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=
+google.golang.org/genproto v0.0.0-20190911173649-1774047e7e51/go.mod h1:IbNlFCBrqXvoKpeg0TB2l7cyZUmoaFKYIwrEpbDKLA8=
+google.golang.org/genproto v0.0.0-20191108220845-16a3f7862a1a/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
+google.golang.org/genproto v0.0.0-20191115194625-c23dd37a84c9/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
+google.golang.org/genproto v0.0.0-20191216164720-4f79533eabd1/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
+google.golang.org/genproto v0.0.0-20191230161307-f3c370f40bfb/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
+google.golang.org/genproto v0.0.0-20200115191322-ca5a22157cba/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
+google.golang.org/genproto v0.0.0-20200122232147-0452cf42e150/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
+google.golang.org/genproto v0.0.0-20200204135345-fa8e72b47b90/go.mod h1:GmwEX6Z4W5gMy59cAlVYjN9JhxgbQH6Gn+gFDQe2lzA=
+google.golang.org/genproto v0.0.0-20200212174721-66ed5ce911ce/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
+google.golang.org/genproto v0.0.0-20200224152610-e50cd9704f63/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
+google.golang.org/genproto v0.0.0-20200228133532-8c2c7df3a383/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
+google.golang.org/genproto v0.0.0-20200305110556-506484158171/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
+google.golang.org/genproto v0.0.0-20200312145019-da6875a35672/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
+google.golang.org/genproto v0.0.0-20200331122359-1ee6d9798940/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
+google.golang.org/genproto v0.0.0-20200423170343-7949de9c1215/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
+google.golang.org/genproto v0.0.0-20200430143042-b979b6f78d84/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
+google.golang.org/genproto v0.0.0-20200511104702-f5ebc3bea380/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
+google.golang.org/genproto v0.0.0-20200513103714-09dca8ec2884/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
+google.golang.org/genproto v0.0.0-20200515170657-fc4c6c6a6587/go.mod h1:YsZOwe1myG/8QRHRsmBRE1LrgQY60beZKjly0O1fX9U=
+google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo=
+google.golang.org/genproto v0.0.0-20200618031413-b414f8b61790/go.mod h1:jDfRM7FcilCzHH/e9qn6dsT145K34l5v+OpcnNgKAAA=
+google.golang.org/genproto v0.0.0-20200729003335-053ba62fc06f/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
+google.golang.org/genproto v0.0.0-20200804131852-c06518451d9c/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
+google.golang.org/genproto v0.0.0-20200825200019-8632dd797987/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
+google.golang.org/genproto v0.0.0-20210602131652-f16073e35f0c/go.mod h1:UODoCrxHCcBojKKwX1terBiRUaqAsFqJiF615XL43r0=
+google.golang.org/genproto v0.0.0-20210617175327-b9e0b3197ced h1:c5geK1iMU3cDKtFrCVQIcjR3W+JOZMuhIyICMCTbtus=
+google.golang.org/genproto v0.0.0-20210617175327-b9e0b3197ced/go.mod h1:SzzZ/N+nwJDaO1kznhnlzqS8ocJICar6hYhVyhi++24=
google.golang.org/grpc v1.16.0/go.mod h1:0JHn/cJsOMiMfNA9+DeHDlAU7KAAB5GDlYFpa9MZMio=
+google.golang.org/grpc v1.17.0/go.mod h1:6QZJwpn2B+Zp71q/5VxRsJ6NXXVCE5NRUHRo+f3cWCs=
google.golang.org/grpc v1.18.0/go.mod h1:6QZJwpn2B+Zp71q/5VxRsJ6NXXVCE5NRUHRo+f3cWCs=
google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
+google.golang.org/grpc v1.20.0/go.mod h1:chYK+tFQF0nDUGJgXMSgLCQk3phJEuONr2DCgLDdAQM=
+google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38=
+google.golang.org/grpc v1.21.0/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM=
+google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM=
+google.golang.org/grpc v1.22.1/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg=
+google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg=
+google.golang.org/grpc v1.23.1/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg=
+google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY=
+google.golang.org/grpc v1.26.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
+google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
+google.golang.org/grpc v1.27.1/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
+google.golang.org/grpc v1.28.0/go.mod h1:rpkK4SK4GF4Ach/+MFLZUBavHOvF2JJB5uozKKal+60=
+google.golang.org/grpc v1.29.1/go.mod h1:itym6AZVZYACWQqET3MqgPpjcuV5QH3BxFS3IjizoKk=
+google.golang.org/grpc v1.30.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak=
+google.golang.org/grpc v1.31.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak=
+google.golang.org/grpc v1.33.1/go.mod h1:fr5YgcSWrqhRRxogOsw7RzIpsmvOZ6IcH4kBYTpR3n0=
+google.golang.org/grpc v1.36.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU=
+google.golang.org/grpc v1.37.0/go.mod h1:NREThFqKR1f3iQ6oBuvc5LadQuXVGo9rkm5ZGrQdJfM=
+google.golang.org/grpc v1.38.0 h1:/9BgsAsa5nWe26HqOlvlgJnqBuktYOLCgjCPqsa56W0=
+google.golang.org/grpc v1.38.0/go.mod h1:NREThFqKR1f3iQ6oBuvc5LadQuXVGo9rkm5ZGrQdJfM=
+google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=
+google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0=
+google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM=
+google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE=
+google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo=
+google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
+google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
+google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
+google.golang.org/protobuf v1.24.0/go.mod h1:r/3tXBNzIEhYS9I1OUVjXDlt8tc493IdKGjtUeSXeh4=
+google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c=
+google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
+google.golang.org/protobuf v1.26.0 h1:bxAC2xTBsZGibn2RTntX0oH50xLsqy1OxA9tTL3p/lk=
+google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
+gopkg.in/alecthomas/kingpin.v2 v2.2.6 h1:jMFz6MfLP0/4fUyZle81rXUoxOBFi19VUFKVDOQfozc=
gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw=
-gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
+gopkg.in/cenkalti/backoff.v1 v1.1.0 h1:Arh75ttbsvlpVA7WtVpH4u9h6Zl46xuptxqLxPiSo4Y=
+gopkg.in/cenkalti/backoff.v1 v1.1.0/go.mod h1:J6Vskwqd+OMVJl8C33mmtxTBs2gyzfv7UDAkHu8BrjI=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
-gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY=
+gopkg.in/check.v1 v1.0.0-20160105164936-4f90aeace3a2/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
+gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
+gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
+gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
+gopkg.in/cheggaaa/pb.v1 v1.0.25/go.mod h1:V/YB90LKu/1FcN3WVnfiiE5oMCibMjukxqG/qStrOgw=
+gopkg.in/errgo.v1 v1.0.0-20161222125816-442357a80af5/go.mod h1:u0ALmqvLRxLI95fkdCEWrE6mhWYZW1aMOJHp5YXLHTg=
gopkg.in/errgo.v1 v1.0.1/go.mod h1:3NjfXwocQRYAPTq4/fzX+CwUhPRcR/azYRhj8G+LqMo=
-gopkg.in/fsnotify.v1 v1.4.7 h1:xOHLXZwVvI9hhs+cLKq5+I5onOuwQLhQwiu63xxlHs4=
+gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys=
+gopkg.in/gcfg.v1 v1.2.3/go.mod h1:yesOnuUOFQAhST5vPY4nbZsb/huCgGGXlipJsBn0b3o=
+gopkg.in/httprequest.v1 v1.1.1/go.mod h1:/CkavNL+g3qLOrpFHVrEx4NKepeqR4XTZWNj4sGGjz0=
+gopkg.in/inconshreveable/log15.v2 v2.0.0-20180818164646-67afb5ed74ec/go.mod h1:aPpfJ7XW+gOuirDoZ8gHhLh3kZ1B08FtV2bbmy7Jv3s=
+gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw=
+gopkg.in/ini.v1 v1.51.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
+gopkg.in/lightningtipbot/telebot.v3 v3.0.0-20220828121412-0dea11ecc6dd h1:LI1Q9RRmTKusLTpLGAHFUVT+wgzc0GHFKd3+34fARE8=
+gopkg.in/lightningtipbot/telebot.v3 v3.0.0-20220828121412-0dea11ecc6dd/go.mod h1:BefN0q4hcqXE1jXo4mTXZ9+29zl7IGsoSFXEQF455Oc=
gopkg.in/macaroon-bakery.v2 v2.0.1/go.mod h1:B4/T17l+ZWGwxFSZQmlBwp25x+og7OkhETfr3S9MbIA=
gopkg.in/macaroon.v2 v2.0.0/go.mod h1:+I6LnTMkm/uV5ew/0nsulNjL16SK4+C8yDmRUzHR17I=
+gopkg.in/mgo.v2 v2.0.0-20160818015218-f2b6f6c918c4/go.mod h1:yeKp02qBN3iKW1OzL3MGk2IdtZzaj7SFntXj72NppTA=
gopkg.in/mgo.v2 v2.0.0-20190816093944-a6b53ec6cb22/go.mod h1:yeKp02qBN3iKW1OzL3MGk2IdtZzaj7SFntXj72NppTA=
+gopkg.in/natefinch/lumberjack.v2 v2.0.0 h1:1Lc07Kr7qY4U2YPouBjpCLxpiyxIVoxqXgkXLknAOE8=
+gopkg.in/natefinch/lumberjack.v2 v2.0.0/go.mod h1:l0ndWWf7gzL7RNwBG7wST/UCcT4T24xpD6X8LsfU/+k=
gopkg.in/resty.v1 v1.12.0/go.mod h1:mDo4pnntr5jdWRML875a/NmxYqAlA73dVijT2AXvQQo=
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ=
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw=
-gopkg.in/tucnak/telebot.v2 v2.3.5 h1:TdMJTlG8kvepsvZdy/gPeYEBdwKdwFFjH1AQTua9BOU=
-gopkg.in/tucnak/telebot.v2 v2.3.5/go.mod h1:BgaIIx50PSRS9pG59JH+geT82cfvoJU/IaI5TJdN3v8=
+gopkg.in/tomb.v2 v2.0.0-20161208151619-d5d1b5820637 h1:yiW+nvdHb9LVqSHQBXfZCieqV4fzYhNBql77zY0ykqs=
+gopkg.in/tomb.v2 v2.0.0-20161208151619-d5d1b5820637/go.mod h1:BHsqpu/nsuzkT5BpiH1EMZPLyqSMM8JbIavyFACoFNk=
+gopkg.in/warnings.v0 v0.1.2/go.mod h1:jksf8JmL6Qr/oQM2OXTHunEvvTAsrWBLb6OOjuVWRNI=
+gopkg.in/yaml.v2 v2.0.0-20170712054546-1be3d31502d6/go.mod h1:JAlM8MvJe8wmxCU4Bli9HhUf9+ttbYbLASfIpnQbh74=
gopkg.in/yaml.v2 v2.0.0-20170812160011-eb3733d160e7/go.mod h1:JAlM8MvJe8wmxCU4Bli9HhUf9+ttbYbLASfIpnQbh74=
gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
-gopkg.in/yaml.v2 v2.2.2 h1:ZCJp+EgiOT7lHqUV2J862kp8Qj64Jo6az82+3Td9dZw=
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
+gopkg.in/yaml.v2 v2.2.3/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
+gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
+gopkg.in/yaml.v2 v2.2.5/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
+gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
+gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
+gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
+gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
+gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
+gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b h1:h8qDotaEPuJATrMmW04NCwg7v22aHH28wwpauUhK9Oo=
+gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gorm.io/driver/sqlite v1.1.4 h1:PDzwYE+sI6De2+mxAneV9Xs11+ZyKV6oxD3wDGkaNvM=
gorm.io/driver/sqlite v1.1.4/go.mod h1:mJCeTFr7+crvS+TRnWc5Z3UvwxUN1BGBLMrf5LA9DYw=
gorm.io/gorm v1.20.7/go.mod h1:0HFTzE/SqkGTzK6TlDPPQbAYCluiVvhzoA1+aVyzenw=
@@ -311,3 +1437,25 @@ gorm.io/gorm v1.21.12 h1:3fQM0Eiz7jcJEhPggHEpoYnsGZqynMzverL77DV40RM=
gorm.io/gorm v1.21.12/go.mod h1:F+OptMscr0P2F2qU97WT1WimdH9GaQPoDW7AYd5i2Y0=
honnef.co/go/tools v0.0.0-20180728063816-88497007e858/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
+honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
+honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
+honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
+honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg=
+honnef.co/go/tools v0.0.1-2020.1.3/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k=
+honnef.co/go/tools v0.0.1-2020.1.4/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k=
+k8s.io/apimachinery v0.0.0-20191123233150-4c4803ed55e3 h1:FErmbNIJruD5GT2oVEjtPn5Ar5+rcWJsC8/PPUkR0s4=
+k8s.io/apimachinery v0.0.0-20191123233150-4c4803ed55e3/go.mod h1:b9qmWdKlLuU9EBh+06BtLcSf/Mu89rWL33naRxs1uZg=
+k8s.io/gengo v0.0.0-20190128074634-0689ccc1d7d6/go.mod h1:ezvh/TsK7cY6rbqRK0oQQ8IAqLxYwwyPxAX1Pzy0ii0=
+k8s.io/klog v0.0.0-20181102134211-b9b56d5dfc92/go.mod h1:Gq+BEi5rUBO/HRz0bTSXDUcqjScdoY3a9IHpCEIOOfk=
+k8s.io/klog v1.0.0/go.mod h1:4Bi6QPql/J/LkTDqv7R/cd3hPo4k2DG6Ptcz060Ez5I=
+k8s.io/kube-openapi v0.0.0-20191107075043-30be4d16710a/go.mod h1:1TqjTSzOxsLGIKfj0lK8EeCP7K1iUG65v09OM0/WG5E=
+launchpad.net/gocheck v0.0.0-20140225173054-000000000087/go.mod h1:hj7XX3B/0A+80Vse0e+BUHsHMTEhd0O4cpUHr/e/BUM=
+launchpad.net/xmlpath v0.0.0-20130614043138-000000000004/go.mod h1:vqyExLOM3qBx7mvYRkoxjSCF945s0mbe7YynlKYXtsA=
+rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8=
+rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0=
+rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA=
+sigs.k8s.io/structured-merge-diff v0.0.0-20190525122527-15d366b2352e/go.mod h1:wWxsB5ozmmv/SG7nM11ayaAW51xMvak/t1r0CSlcokI=
+sigs.k8s.io/yaml v1.1.0/go.mod h1:UJmg0vDUVViEyp3mgSv9WPwZCDxu4rQW1olrI1uml+o=
+sigs.k8s.io/yaml v1.2.0 h1:kr/MCeFWJWTwyaHoR9c8EjH9OumOmoF9YGiZd7lFm/Q=
+sigs.k8s.io/yaml v1.2.0/go.mod h1:yfXDCHCao9+ENCvLSE62v9VSji2MKu5jeNfTrofGhJc=
+sourcegraph.com/sourcegraph/appdash v0.0.0-20190731080439-ebfcffb1b5c0/go.mod h1:hI742Nqp5OhwiqlzhgfbWU4mW4yO10fP+LoT9WOswdU=
diff --git a/help.go b/help.go
deleted file mode 100644
index ceca3833..00000000
--- a/help.go
+++ /dev/null
@@ -1,132 +0,0 @@
-package main
-
-import (
- "fmt"
-
- tb "gopkg.in/tucnak/telebot.v2"
-)
-
-const (
- helpMessage = "⚡️ *Wallet*\n_This bot is a Bitcoin Lightning wallet that can sends tips on Telegram. To tip, add the bot to a group chat. The basic unit of tips are Satoshis (sat). 100,000,000 sat = 1 Bitcoin. Type 📚 /basics for more._\n\n" +
- "❤️ *Donate*\n" +
- "_This bot charges no fees but costs satoshis to operate. If you like the bot, please consider supporting this project with a donation. To donate, use_ `/donate 1000`\n\n" +
- "%s" +
- "⚙️ *Commands*\n" +
- "*/tip* 🏅 Reply to a message to tip: `/tip []`\n" +
- "*/balance* 👑 Check your balance: `/balance`\n" +
- "*/send* 💸 Send funds to a user: `/send @user or user@ln.tips []`\n" +
- "*/invoice* ⚡️ Receive with Lightning: `/invoice []`\n" +
- "*/pay* ⚡️ Pay with Lightning: `/pay `\n" +
- "*/donate* ❤️ Donate to the project: `/donate 1000`\n" +
- "*/advanced* 🤖 Advanced features.\n" +
- "*/help* 📖 Read this help."
-
- infoMessage = "🧡 *Bitcoin*\n" +
- "_Bitcoin is the currency of the internet. It is permissionless and decentralized and has no masters and no controling authority. Bitcoin is sound money that is faster, more secure, and more inclusive than the legacy financial system._\n\n" +
- "🧮 *Economnics*\n" +
- "_The smallest unit of Bitcoin are Satoshis (sat) and 100,000,000 sat = 1 Bitcoin. There will only ever be 21 Million Bitcoin. The fiat currency value of Bitcoin can change daily. However, if you live on a Bitcoin standard 1 sat will always equal 1 sat._\n\n" +
- "⚡️ *The Lightning Network*\n" +
- "_The Lightning Network is a payment protocol that enables fast and cheap Bitcoin payments that require almost no energy. It is what scales Bitcoin to the billions of people around the world._\n\n" +
- "📲 *Lightning Wallets*\n" +
- "_Your funds on this bot can be sent to any other Lightning wallet and vice versa. Recommended Lightning wallets for your phone are_ [Phoenix](https://phoenix.acinq.co/)_,_ [Breez](https://breez.technology/)_,_ [Muun](https://muun.com/)_ (non-custodial), or_ [Wallet of Satoshi](https://www.walletofsatoshi.com/) _(easy)_.\n\n" +
- "📄 *Open Source*\n" +
- "_This bot is free and_ [open source](https://github.com/LightningTipBot/LightningTipBot) _software. You can run it on your own computer and use it in your own community._\n\n" +
- "✈️ *Telegram*\n" +
- "_Add this bot to your Telegram group chat to /tip posts. If you make the bot admin of the group it will also clean up commands to keep the chat tidy._\n\n" +
- "🏛 *Terms*\n" +
- "_We are not custodian of your funds. We will act in your best interest but we're also aware that the situation without KYC is tricky until we figure something out. Any amount you load onto your wallet will be considered a donation. Do not give us all your money. Be aware that this bot is in beta development. Use at your own risk._\n\n" +
- "❤️ *Donate*\n" +
- "_This bot charges no fees but costs satoshis to operate. If you like the bot, please consider supporting this project with a donation. To donate, use_ `/donate 1000`"
-
- helpNoUsernameMessage = "ℹ️ Please set a Telegram username."
-
- advancedMessage = "%s\n\n" +
- "👉 *Inline commands*\n" +
- "*send* 💸 Send sats to chat: `%s send []`\n" +
- "*receive* 🏅 Request a payment: `%s receive []`\n" +
- "*faucet* 🚰 Create a faucet: `%s faucet `\n\n" +
- "📖 You can use inline commands in every chat, even in private conversations. Wait a second after entering an inline command and *click* the result, don't press enter.\n\n" +
- "⚙️ *Advanced commands*\n" +
- "*/link* 🔗 Link your wallet to [BlueWallet](https://bluewallet.io/) or [Zeus](https://zeusln.app/)\n" +
- "*/lnurl* ⚡️ Lnurl receive or pay: `/lnurl` or `/lnurl `\n" +
- "*/faucet* 🚰 Create a faucet `/faucet `"
-)
-
-func (bot TipBot) makeHelpMessage(m *tb.Message) string {
- dynamicHelpMessage := ""
- // user has no username set
- if len(m.Sender.Username) == 0 {
- // return fmt.Sprintf(helpMessage, fmt.Sprintf("%s\n\n", helpNoUsernameMessage))
- dynamicHelpMessage = dynamicHelpMessage + fmt.Sprintf("%s\n", helpNoUsernameMessage)
- } else {
- dynamicHelpMessage = "ℹ️ *Info*\n"
- lnaddr, err := bot.UserGetLightningAddress(m.Sender)
- if err != nil {
- dynamicHelpMessage = ""
- } else {
- dynamicHelpMessage = dynamicHelpMessage + fmt.Sprintf("Your Lightning Address is `%s`\n", lnaddr)
- }
- }
- dynamicHelpMessage = dynamicHelpMessage + "\n"
- return fmt.Sprintf(helpMessage, dynamicHelpMessage)
-}
-
-func (bot TipBot) helpHandler(m *tb.Message) {
- // check and print all commands
- bot.anyTextHandler(m)
- if !m.Private() {
- // delete message
- NewMessage(m, WithDuration(0, bot.telegram))
- }
- bot.trySendMessage(m.Sender, bot.makeHelpMessage(m), tb.NoPreview)
- return
-}
-
-func (bot TipBot) basicsHandler(m *tb.Message) {
- // check and print all commands
- bot.anyTextHandler(m)
- if !m.Private() {
- // delete message
- NewMessage(m, WithDuration(0, bot.telegram))
- }
- bot.trySendMessage(m.Sender, infoMessage, tb.NoPreview)
- return
-}
-
-func (bot TipBot) makeadvancedHelpMessage(m *tb.Message) string {
- dynamicHelpMessage := ""
- // user has no username set
- if len(m.Sender.Username) == 0 {
- // return fmt.Sprintf(helpMessage, fmt.Sprintf("%s\n\n", helpNoUsernameMessage))
- dynamicHelpMessage = dynamicHelpMessage + fmt.Sprintf("%s", helpNoUsernameMessage)
- } else {
- dynamicHelpMessage = "ℹ️ *Info*\n"
- lnaddr, err := bot.UserGetLightningAddress(m.Sender)
- if err != nil {
- dynamicHelpMessage = ""
- } else {
- dynamicHelpMessage = dynamicHelpMessage + fmt.Sprintf("Your Lightning Address:\n`%s`\n", lnaddr)
- }
-
- lnurl, err := bot.UserGetLNURL(m.Sender)
- if err != nil {
- dynamicHelpMessage = ""
- } else {
- dynamicHelpMessage = dynamicHelpMessage + fmt.Sprintf("Your LNURL:\n`%s`", lnurl)
- }
-
- }
- // this is so stupid:
- return fmt.Sprintf(advancedMessage, dynamicHelpMessage, GetUserStrMd(bot.telegram.Me), GetUserStrMd(bot.telegram.Me), GetUserStrMd(bot.telegram.Me))
-}
-
-func (bot TipBot) advancedHelpHandler(m *tb.Message) {
- // check and print all commands
- bot.anyTextHandler(m)
- if !m.Private() {
- // delete message
- NewMessage(m, WithDuration(0, bot.telegram))
- }
- bot.trySendMessage(m.Sender, bot.makeadvancedHelpMessage(m), tb.NoPreview)
- return
-}
diff --git a/inline_faucet.go b/inline_faucet.go
deleted file mode 100644
index 99e997e2..00000000
--- a/inline_faucet.go
+++ /dev/null
@@ -1,425 +0,0 @@
-package main
-
-import (
- "fmt"
- "strconv"
- "time"
-
- "github.com/LightningTipBot/LightningTipBot/internal/runtime"
- log "github.com/sirupsen/logrus"
- tb "gopkg.in/tucnak/telebot.v2"
-)
-
-const (
- inlineFaucetMessage = "Press ✅ to collect %d sat from this faucet.\n\n🚰 Remaining: %d/%d sat (given to %d/%d users)\n%s"
- inlineFaucetEndedMessage = "🏅 Faucet empty 🏅\n\n🚰 %d sat given to %d users."
- inlineFaucetAppendMemo = "\n✉️ %s"
- inlineFaucetCreateWalletMessage = "Chat with %s 👈 to manage your wallet."
- inlineFaucetCancelledMessage = "🚫 Faucet cancelled."
- inlineFaucetInvalidPeruserAmountMessage = "🚫 Peruser amount not divisor of capacity."
- inlineFaucetInvalidAmountMessage = "🚫 Invalid amount."
- inlineFaucetSentMessage = "🚰 %d sat sent to %s."
- inlineFaucetReceivedMessage = "🚰 %s sent you %d sat."
- inlineFaucetHelpFaucetInGroup = "Create a faucet in a group with the bot inside or use 👉 inline commands (/advanced for more)."
- inlineFaucetHelpText = "📖 Oops, that didn't work. %s\n\n" +
- "*Usage:* `/faucet `\n" +
- "*Example:* `/faucet 210 21`"
-)
-
-const (
- inlineQueryFaucetTitle = "🚰 Create a faucet."
- inlineQueryFaucetDescription = "Usage: @%s faucet "
- inlineResultFaucetTitle = "💸 Create a %d sat faucet."
- inlineResultFaucetDescription = "👉 Click here to create a faucet worth %d sat in this chat."
-)
-
-var (
- inlineFaucetMenu = &tb.ReplyMarkup{ResizeReplyKeyboard: true}
- btnCancelInlineFaucet = inlineFaucetMenu.Data("🚫 Cancel", "cancel_faucet_inline")
- btnAcceptInlineFaucet = inlineFaucetMenu.Data("✅ Collect", "confirm_faucet_inline")
-)
-
-type InlineFaucet struct {
- Message string `json:"inline_faucet_message"`
- Amount int `json:"inline_faucet_amount"`
- RemainingAmount int `json:"inline_faucet_remainingamount"`
- PerUserAmount int `json:"inline_faucet_peruseramount"`
- From *tb.User `json:"inline_faucet_from"`
- To []*tb.User `json:"inline_faucet_to"`
- Memo string `json:"inline_faucet_memo"`
- ID string `json:"inline_faucet_id"`
- Active bool `json:"inline_faucet_active"`
- NTotal int `json:"inline_faucet_ntotal"`
- NTaken int `json:"inline_faucet_ntaken"`
- UserNeedsWallet bool `json:"inline_faucet_userneedswallet"`
- InTransaction bool `json:"inline_faucet_intransaction"`
-}
-
-func NewInlineFaucet() *InlineFaucet {
- inlineFaucet := &InlineFaucet{
- Message: "",
- NTaken: 0,
- UserNeedsWallet: false,
- InTransaction: false,
- Active: true,
- }
- return inlineFaucet
-
-}
-
-func (msg InlineFaucet) Key() string {
- return msg.ID
-}
-
-func (bot *TipBot) LockFaucet(tx *InlineFaucet) error {
- // immediatelly set intransaction to block duplicate calls
- tx.InTransaction = true
- err := bot.bunt.Set(tx)
- if err != nil {
- return err
- }
- return nil
-}
-
-func (bot *TipBot) ReleaseFaucet(tx *InlineFaucet) error {
- // immediatelly set intransaction to block duplicate calls
- tx.InTransaction = false
- err := bot.bunt.Set(tx)
- if err != nil {
- return err
- }
- return nil
-}
-
-func (bot *TipBot) inactivateFaucet(tx *InlineFaucet) error {
- tx.Active = false
- err := bot.bunt.Set(tx)
- if err != nil {
- return err
- }
- return nil
-}
-
-// tipTooltipExists checks if this tip is already known
-func (bot *TipBot) getInlineFaucet(c *tb.Callback) (*InlineFaucet, error) {
- inlineFaucet := NewInlineFaucet()
- inlineFaucet.ID = c.Data
- err := bot.bunt.Get(inlineFaucet)
-
- // to avoid race conditions, we block the call if there is
- // already an active transaction by loop until InTransaction is false
- ticker := time.NewTicker(time.Second * 10)
-
- for inlineFaucet.InTransaction {
- select {
- case <-ticker.C:
- return nil, fmt.Errorf("[faucet] faucet %s timeout", inlineFaucet.ID)
- default:
- log.Infof("[faucet] faucet %s already in transaction", inlineFaucet.ID)
- time.Sleep(time.Duration(500) * time.Millisecond)
- err = bot.bunt.Get(inlineFaucet)
- }
- }
- if err != nil {
- return nil, fmt.Errorf("could not get inline faucet: %s", err)
- }
- return inlineFaucet, nil
-
-}
-
-func (bot TipBot) faucetHandler(m *tb.Message) {
- if m.Private() {
- bot.trySendMessage(m.Sender, fmt.Sprintf(inlineFaucetHelpText, inlineFaucetHelpFaucetInGroup))
- return
- }
- inlineFaucet := NewInlineFaucet()
- var err error
- inlineFaucet.Amount, err = decodeAmountFromCommand(m.Text)
- if err != nil {
- bot.trySendMessage(m.Sender, fmt.Sprintf(inlineFaucetHelpText, inlineFaucetInvalidAmountMessage))
- bot.tryDeleteMessage(m)
- return
- }
- peruserStr, err := getArgumentFromCommand(m.Text, 2)
- if err != nil {
- bot.trySendMessage(m.Sender, fmt.Sprintf(inlineFaucetHelpText, ""))
- bot.tryDeleteMessage(m)
- return
- }
- inlineFaucet.PerUserAmount, err = strconv.Atoi(peruserStr)
- if err != nil {
- bot.trySendMessage(m.Sender, fmt.Sprintf(inlineFaucetHelpText, inlineFaucetInvalidAmountMessage))
- bot.tryDeleteMessage(m)
- return
- }
- // peruser amount must be >1 and a divisor of amount
- if inlineFaucet.PerUserAmount < 1 || inlineFaucet.Amount%inlineFaucet.PerUserAmount != 0 {
- bot.trySendMessage(m.Sender, fmt.Sprintf(inlineFaucetHelpText, inlineFaucetInvalidPeruserAmountMessage))
- bot.tryDeleteMessage(m)
- return
- }
- inlineFaucet.NTotal = inlineFaucet.Amount / inlineFaucet.PerUserAmount
-
- fromUserStr := GetUserStr(m.Sender)
- balance, err := bot.GetUserBalance(m.Sender)
- if err != nil {
- errmsg := fmt.Sprintf("could not get balance of user %s", fromUserStr)
- log.Errorln(errmsg)
- bot.tryDeleteMessage(m)
- return
- }
- // check if fromUser has balance
- if balance < inlineFaucet.Amount {
- log.Errorln("Balance of user %s too low", fromUserStr)
- bot.trySendMessage(m.Sender, fmt.Sprintf(inlineSendBalanceLowMessage, balance))
- bot.tryDeleteMessage(m)
- return
- }
-
- // // check for memo in command
- memo := GetMemoFromCommand(m.Text, 3)
-
- inlineMessage := fmt.Sprintf(inlineFaucetMessage, inlineFaucet.PerUserAmount, inlineFaucet.Amount, inlineFaucet.Amount, 0, inlineFaucet.NTotal, MakeProgressbar(inlineFaucet.Amount, inlineFaucet.Amount))
- if len(memo) > 0 {
- inlineMessage = inlineMessage + fmt.Sprintf(inlineFaucetAppendMemo, memo)
- }
-
- inlineFaucet.ID = fmt.Sprintf("inl-faucet-%d-%d-%s", m.Sender.ID, inlineFaucet.Amount, RandStringRunes(5))
-
- btnAcceptInlineFaucet.Data = inlineFaucet.ID
- btnCancelInlineFaucet.Data = inlineFaucet.ID
- inlineFaucetMenu.Inline(inlineFaucetMenu.Row(btnAcceptInlineFaucet, btnCancelInlineFaucet))
- bot.trySendMessage(m.Chat, inlineMessage, inlineFaucetMenu)
- log.Infof("[faucet] %s created faucet %s: %d sat (%d per user)", fromUserStr, inlineFaucet.ID, inlineFaucet.Amount, inlineFaucet.PerUserAmount)
- inlineFaucet.Message = inlineMessage
- inlineFaucet.From = m.Sender
- inlineFaucet.Memo = memo
- inlineFaucet.RemainingAmount = inlineFaucet.Amount
- runtime.IgnoreError(bot.bunt.Set(inlineFaucet))
-
-}
-
-func (bot TipBot) handleInlineFaucetQuery(q *tb.Query) {
- inlineFaucet := NewInlineFaucet()
- var err error
- inlineFaucet.Amount, err = decodeAmountFromCommand(q.Text)
- if err != nil {
- bot.inlineQueryReplyWithError(q, inlineQueryFaucetTitle, fmt.Sprintf(inlineQueryFaucetDescription, bot.telegram.Me.Username))
- return
- }
- if inlineFaucet.Amount < 1 {
- bot.inlineQueryReplyWithError(q, inlineSendInvalidAmountMessage, fmt.Sprintf(inlineQueryFaucetDescription, bot.telegram.Me.Username))
- return
- }
-
- peruserStr, err := getArgumentFromCommand(q.Text, 2)
- if err != nil {
- bot.inlineQueryReplyWithError(q, inlineQueryFaucetTitle, fmt.Sprintf(inlineQueryFaucetDescription, bot.telegram.Me.Username))
- return
- }
- inlineFaucet.PerUserAmount, err = strconv.Atoi(peruserStr)
- if err != nil {
- bot.inlineQueryReplyWithError(q, inlineQueryFaucetTitle, fmt.Sprintf(inlineQueryFaucetDescription, bot.telegram.Me.Username))
- return
- }
- // peruser amount must be >1 and a divisor of amount
- if inlineFaucet.PerUserAmount < 1 || inlineFaucet.Amount%inlineFaucet.PerUserAmount != 0 {
- bot.inlineQueryReplyWithError(q, inlineFaucetInvalidPeruserAmountMessage, fmt.Sprintf(inlineQueryFaucetDescription, bot.telegram.Me.Username))
- return
- }
- inlineFaucet.NTotal = inlineFaucet.Amount / inlineFaucet.PerUserAmount
-
- fromUserStr := GetUserStr(&q.From)
- balance, err := bot.GetUserBalance(&q.From)
- if err != nil {
- errmsg := fmt.Sprintf("could not get balance of user %s", fromUserStr)
- log.Errorln(errmsg)
- return
- }
- // check if fromUser has balance
- if balance < inlineFaucet.Amount {
- log.Errorln("Balance of user %s too low", fromUserStr)
- bot.inlineQueryReplyWithError(q, fmt.Sprintf(inlineSendBalanceLowMessage, balance), fmt.Sprintf(inlineQueryFaucetDescription, bot.telegram.Me.Username))
- return
- }
-
- // check for memo in command
- memo := GetMemoFromCommand(q.Text, 3)
-
- urls := []string{
- queryImage,
- }
- results := make(tb.Results, len(urls)) // []tb.Result
- for i, url := range urls {
- inlineMessage := fmt.Sprintf(inlineFaucetMessage, inlineFaucet.PerUserAmount, inlineFaucet.Amount, inlineFaucet.Amount, 0, inlineFaucet.NTotal, MakeProgressbar(inlineFaucet.Amount, inlineFaucet.Amount))
- if len(memo) > 0 {
- inlineMessage = inlineMessage + fmt.Sprintf(inlineFaucetAppendMemo, memo)
- }
- result := &tb.ArticleResult{
- // URL: url,
- Text: inlineMessage,
- Title: fmt.Sprintf(inlineResultFaucetTitle, inlineFaucet.Amount),
- Description: fmt.Sprintf(inlineResultFaucetDescription, inlineFaucet.Amount),
- // required for photos
- ThumbURL: url,
- }
- id := fmt.Sprintf("inl-faucet-%d-%d-%s", q.From.ID, inlineFaucet.Amount, RandStringRunes(5))
- btnAcceptInlineFaucet.Data = id
- btnCancelInlineFaucet.Data = id
- inlineFaucetMenu.Inline(inlineFaucetMenu.Row(btnAcceptInlineFaucet, btnCancelInlineFaucet))
- result.ReplyMarkup = &tb.InlineKeyboardMarkup{InlineKeyboard: inlineFaucetMenu.InlineKeyboard}
- results[i] = result
-
- // needed to set a unique string ID for each result
- results[i].SetResultID(id)
-
- // create persistend inline send struct
- inlineFaucet.Message = inlineMessage
- inlineFaucet.ID = id
- inlineFaucet.From = &q.From
- inlineFaucet.RemainingAmount = inlineFaucet.Amount
- inlineFaucet.Memo = memo
- runtime.IgnoreError(bot.bunt.Set(inlineFaucet))
- }
-
- err = bot.telegram.Answer(q, &tb.QueryResponse{
- Results: results,
- CacheTime: 1,
- })
- log.Infof("[faucet] %s created inline faucet %s: %d sat (%d per user)", fromUserStr, inlineFaucet.ID, inlineFaucet.Amount, inlineFaucet.PerUserAmount)
- if err != nil {
- log.Errorln(err)
- }
-}
-
-func (bot *TipBot) accpetInlineFaucetHandler(c *tb.Callback) {
- inlineFaucet, err := bot.getInlineFaucet(c)
- if err != nil {
- log.Errorf("[faucet] %s", err)
- return
- }
- err = bot.LockFaucet(inlineFaucet)
- if err != nil {
- log.Errorf("[faucet] %s", err)
- return
- }
- if !inlineFaucet.Active {
- log.Errorf("[faucet] inline send not active anymore")
- return
- }
- // release faucet no matter what
- defer bot.ReleaseFaucet(inlineFaucet)
-
- to := c.Sender
- from := inlineFaucet.From
-
- if from.ID == to.ID {
- bot.trySendMessage(from, sendYourselfMessage)
- return
- }
- // check if to user has already taken from the faucet
- for _, a := range inlineFaucet.To {
- if a.ID == to.ID {
- // to user is already in To slice, has taken from facuet
- log.Infof("[faucet] %s already took from faucet %s", GetUserStr(to), inlineFaucet.ID)
- return
- }
- }
-
- if inlineFaucet.RemainingAmount >= inlineFaucet.PerUserAmount {
- toUserStrMd := GetUserStrMd(to)
- fromUserStrMd := GetUserStrMd(from)
- toUserStr := GetUserStr(to)
- fromUserStr := GetUserStr(from)
- // check if user exists and create a wallet if not
- _, exists := bot.UserExists(to)
- if !exists {
- log.Infof("[faucet] User %s has no wallet.", toUserStr)
- err = bot.CreateWalletForTelegramUser(to)
- if err != nil {
- errmsg := fmt.Errorf("[faucet] Error: Could not create wallet for %s", toUserStr)
- log.Errorln(errmsg)
- return
- }
- }
-
- if !bot.UserInitializedWallet(to) {
- inlineFaucet.UserNeedsWallet = true
- }
-
- // todo: user new get username function to get userStrings
- transactionMemo := fmt.Sprintf("Faucet from %s to %s (%d sat).", fromUserStr, toUserStr, inlineFaucet.PerUserAmount)
- t := NewTransaction(bot, from, to, inlineFaucet.PerUserAmount, TransactionType("faucet"))
- t.Memo = transactionMemo
-
- success, err := t.Send()
- if !success {
- if err != nil {
- bot.trySendMessage(from, fmt.Sprintf(tipErrorMessage, err))
- } else {
- bot.trySendMessage(from, fmt.Sprintf(tipErrorMessage, tipUndefinedErrorMsg))
- }
- errMsg := fmt.Sprintf("[faucet] Transaction failed: %s", err)
- log.Errorln(errMsg)
- return
- }
-
- log.Infof("[faucet] faucet %s: %d sat from %s to %s ", inlineFaucet.ID, inlineFaucet.PerUserAmount, fromUserStr, toUserStr)
- inlineFaucet.NTaken += 1
- inlineFaucet.To = append(inlineFaucet.To, to)
- inlineFaucet.RemainingAmount = inlineFaucet.RemainingAmount - inlineFaucet.PerUserAmount
-
- _, err = bot.telegram.Send(to, fmt.Sprintf(inlineFaucetReceivedMessage, fromUserStrMd, inlineFaucet.PerUserAmount))
- _, err = bot.telegram.Send(from, fmt.Sprintf(inlineFaucetSentMessage, inlineFaucet.PerUserAmount, toUserStrMd))
- if err != nil {
- errmsg := fmt.Errorf("[faucet] Error: Send message to %s: %s", toUserStr, err)
- log.Errorln(errmsg)
- return
- }
-
- // build faucet message
- inlineFaucet.Message = fmt.Sprintf(inlineFaucetMessage, inlineFaucet.PerUserAmount, inlineFaucet.RemainingAmount, inlineFaucet.Amount, inlineFaucet.NTaken, inlineFaucet.NTotal, MakeProgressbar(inlineFaucet.RemainingAmount, inlineFaucet.Amount))
- memo := inlineFaucet.Memo
- if len(memo) > 0 {
- inlineFaucet.Message = inlineFaucet.Message + fmt.Sprintf(inlineFaucetAppendMemo, memo)
- }
- if inlineFaucet.UserNeedsWallet {
- inlineFaucet.Message += "\n\n" + fmt.Sprintf(inlineFaucetCreateWalletMessage, GetUserStrMd(bot.telegram.Me))
- }
-
- // register new inline buttons
- inlineFaucetMenu = &tb.ReplyMarkup{ResizeReplyKeyboard: true}
- btnCancelInlineFaucet.Data = inlineFaucet.ID
- btnAcceptInlineFaucet.Data = inlineFaucet.ID
- inlineFaucetMenu.Inline(inlineFaucetMenu.Row(btnAcceptInlineFaucet, btnCancelInlineFaucet))
- // update message
- log.Infoln(inlineFaucet.Message)
- bot.tryEditMessage(c.Message, inlineFaucet.Message, inlineFaucetMenu)
- }
- if inlineFaucet.RemainingAmount < inlineFaucet.PerUserAmount {
- // faucet is depleted
- inlineFaucet.Message = fmt.Sprintf(inlineFaucetEndedMessage, inlineFaucet.Amount, inlineFaucet.NTaken)
- if inlineFaucet.UserNeedsWallet {
- inlineFaucet.Message += "\n\n" + fmt.Sprintf(inlineFaucetCreateWalletMessage, GetUserStrMd(bot.telegram.Me))
- }
- bot.tryEditMessage(c.Message, inlineFaucet.Message)
- inlineFaucet.Active = false
- }
-
-}
-
-func (bot *TipBot) cancelInlineFaucetHandler(c *tb.Callback) {
- inlineFaucet, err := bot.getInlineFaucet(c)
- if err != nil {
- log.Errorf("[cancelInlineSendHandler] %s", err)
- return
- }
- if c.Sender.ID == inlineFaucet.From.ID {
- bot.tryEditMessage(c.Message, inlineFaucetCancelledMessage, &tb.ReplyMarkup{})
- // set the inlineFaucet inactive
- inlineFaucet.Active = false
- inlineFaucet.InTransaction = false
- runtime.IgnoreError(bot.bunt.Set(inlineFaucet))
- }
- return
-}
diff --git a/inline_query.go b/inline_query.go
deleted file mode 100644
index 515fb926..00000000
--- a/inline_query.go
+++ /dev/null
@@ -1,111 +0,0 @@
-package main
-
-import (
- "fmt"
- "strconv"
- "strings"
-
- log "github.com/sirupsen/logrus"
- tb "gopkg.in/tucnak/telebot.v2"
-)
-
-const queryImage = "https://avatars.githubusercontent.com/u/88730856?v=4"
-
-func (bot TipBot) inlineQueryInstructions(q *tb.Query) {
- instructions := []struct {
- url string
- title string
- description string
- }{
- {
- url: queryImage,
- title: inlineQuerySendTitle,
- description: fmt.Sprintf(inlineQuerySendDescription, bot.telegram.Me.Username),
- },
- {
- url: queryImage,
- title: inlineQueryReceiveTitle,
- description: fmt.Sprintf(inlineQueryReceiveDescription, bot.telegram.Me.Username),
- },
- {
- url: queryImage,
- title: inlineQueryFaucetTitle,
- description: fmt.Sprintf(inlineQueryFaucetDescription, bot.telegram.Me.Username),
- },
- }
- results := make(tb.Results, len(instructions)) // []tb.Result
- for i, instruction := range instructions {
- result := &tb.ArticleResult{
- //URL: instruction.url,
- Text: instruction.description,
- Title: instruction.title,
- Description: instruction.description,
- // required for photos
- ThumbURL: instruction.url,
- }
- results[i] = result
- // needed to set a unique string ID for each result
- results[i].SetResultID(strconv.Itoa(i))
- }
-
- err := bot.telegram.Answer(q, &tb.QueryResponse{
- Results: results,
- CacheTime: 5, // a minute
- IsPersonal: true,
- QueryID: q.ID,
- })
-
- if err != nil {
- log.Errorln(err)
- }
-}
-
-func (bot TipBot) inlineQueryReplyWithError(q *tb.Query, message string, help string) {
- results := make(tb.Results, 1) // []tb.Result
- result := &tb.ArticleResult{
- // URL: url,
- Text: help,
- Title: message,
- Description: help,
- // required for photos
- ThumbURL: queryImage,
- }
- id := fmt.Sprintf("inl-error-%d-%s", q.From.ID, RandStringRunes(5))
- result.SetResultID(id)
- results[0] = result
- err := bot.telegram.Answer(q, &tb.QueryResponse{
- Results: results,
- CacheTime: 1, // 60 == 1 minute, todo: make higher than 1 s in production
-
- })
- if err != nil {
- log.Errorln(err)
- }
-}
-
-func (bot TipBot) anyChosenInlineHandler(q *tb.ChosenInlineResult) {
- fmt.Printf(q.Query)
-}
-
-func (bot TipBot) anyQueryHandler(q *tb.Query) {
- if q.Text == "" {
- bot.inlineQueryInstructions(q)
- return
- }
-
- // create the inline send result
- if strings.HasPrefix(q.Text, "/") {
- q.Text = strings.TrimPrefix(q.Text, "/")
- }
- if strings.HasPrefix(q.Text, "send") || strings.HasPrefix(q.Text, "pay") {
- bot.handleInlineSendQuery(q)
- }
-
- if strings.HasPrefix(q.Text, "faucet") || strings.HasPrefix(q.Text, "giveaway") || strings.HasPrefix(q.Text, "zapfhahn") || strings.HasPrefix(q.Text, "kraan") {
- bot.handleInlineFaucetQuery(q)
- }
-
- if strings.HasPrefix(q.Text, "receive") || strings.HasPrefix(q.Text, "get") || strings.HasPrefix(q.Text, "payme") || strings.HasPrefix(q.Text, "request") {
- bot.handleInlineReceiveQuery(q)
- }
-}
diff --git a/inline_receive.go b/inline_receive.go
deleted file mode 100644
index 56f202c3..00000000
--- a/inline_receive.go
+++ /dev/null
@@ -1,285 +0,0 @@
-package main
-
-import (
- "fmt"
- "time"
-
- "github.com/LightningTipBot/LightningTipBot/internal/runtime"
- log "github.com/sirupsen/logrus"
- tb "gopkg.in/tucnak/telebot.v2"
-)
-
-const (
- inlineReceiveMessage = "Press 💸 to pay to %s.\n\n💸 Amount: %d sat"
- inlineReceiveAppendMemo = "\n✉️ %s"
- inlineReceiveUpdateMessageAccept = "💸 %d sat sent from %s to %s."
- inlineReceiveCreateWalletMessage = "Chat with %s 👈 to manage your wallet."
- inlineReceiveYourselfMessage = "📖 You can't pay to yourself."
- inlineReceiveFailedMessage = "🚫 Receive failed."
-)
-
-var (
- inlineQueryReceiveTitle = "🏅 Request a payment in a chat."
- inlineQueryReceiveDescription = "Usage: @%s receive []"
- inlineResultReceiveTitle = "🏅 Receive %d sat."
- inlineResultReceiveDescription = "👉 Click to request a payment of %d sat."
- inlineReceiveMenu = &tb.ReplyMarkup{ResizeReplyKeyboard: true}
- btnCancelInlineReceive = inlineReceiveMenu.Data("🚫 Cancel", "cancel_receive_inline")
- btnAcceptInlineReceive = inlineReceiveMenu.Data("💸 Pay", "confirm_receive_inline")
-)
-
-type InlineReceive struct {
- Message string `json:"inline_receive_message"`
- Amount int `json:"inline_receive_amount"`
- From *tb.User `json:"inline_receive_from"`
- To *tb.User `json:"inline_receive_to"`
- Memo string
- ID string `json:"inline_receive_id"`
- Active bool `json:"inline_receive_active"`
- InTransaction bool `json:"inline_receive_intransaction"`
-}
-
-func NewInlineReceive() *InlineReceive {
- inlineReceive := &InlineReceive{
- Message: "",
- Active: true,
- InTransaction: false,
- }
- return inlineReceive
-
-}
-
-func (msg InlineReceive) Key() string {
- return msg.ID
-}
-
-func (bot *TipBot) LockReceive(tx *InlineReceive) error {
- // immediatelly set intransaction to block duplicate calls
- tx.InTransaction = true
- err := bot.bunt.Set(tx)
- if err != nil {
- return err
- }
- return nil
-}
-
-func (bot *TipBot) ReleaseReceive(tx *InlineReceive) error {
- // immediatelly set intransaction to block duplicate calls
- tx.InTransaction = false
- err := bot.bunt.Set(tx)
- if err != nil {
- return err
- }
- return nil
-}
-
-func (bot *TipBot) inactivateReceive(tx *InlineReceive) error {
- tx.Active = false
- err := bot.bunt.Set(tx)
- if err != nil {
- return err
- }
- return nil
-}
-
-// tipTooltipExists checks if this tip is already known
-func (bot *TipBot) getInlineReceive(c *tb.Callback) (*InlineReceive, error) {
- inlineReceive := NewInlineReceive()
- inlineReceive.ID = c.Data
- err := bot.bunt.Get(inlineReceive)
- // to avoid race conditions, we block the call if there is
- // already an active transaction by loop until InTransaction is false
- ticker := time.NewTicker(time.Second * 10)
-
- for inlineReceive.InTransaction {
- select {
- case <-ticker.C:
- return nil, fmt.Errorf("inline send timeout")
- default:
- log.Infoln("in transaction")
- time.Sleep(time.Duration(500) * time.Millisecond)
- err = bot.bunt.Get(inlineReceive)
- }
- }
- if err != nil {
- return nil, fmt.Errorf("could not get inline receive message")
- }
- return inlineReceive, nil
-
-}
-
-func (bot TipBot) handleInlineReceiveQuery(q *tb.Query) {
- inlineReceive := NewInlineReceive()
- var err error
- inlineReceive.Amount, err = decodeAmountFromCommand(q.Text)
- if err != nil {
- bot.inlineQueryReplyWithError(q, inlineQueryReceiveTitle, fmt.Sprintf(inlineQueryReceiveDescription, bot.telegram.Me.Username))
- return
- }
- if inlineReceive.Amount < 1 {
- bot.inlineQueryReplyWithError(q, inlineSendInvalidAmountMessage, fmt.Sprintf(inlineQueryReceiveDescription, bot.telegram.Me.Username))
- return
- }
-
- fromUserStr := GetUserStr(&q.From)
-
- // check for memo in command
- inlineReceive.Memo = GetMemoFromCommand(q.Text, 2)
-
- urls := []string{
- queryImage,
- }
- results := make(tb.Results, len(urls)) // []tb.Result
- for i, url := range urls {
-
- inlineMessage := fmt.Sprintf(inlineReceiveMessage, fromUserStr, inlineReceive.Amount)
-
- if len(inlineReceive.Memo) > 0 {
- inlineMessage = inlineMessage + fmt.Sprintf(inlineReceiveAppendMemo, inlineReceive.Memo)
- }
-
- result := &tb.ArticleResult{
- // URL: url,
- Text: inlineMessage,
- Title: fmt.Sprintf(inlineResultReceiveTitle, inlineReceive.Amount),
- Description: fmt.Sprintf(inlineResultReceiveDescription, inlineReceive.Amount),
- // required for photos
- ThumbURL: url,
- }
- id := fmt.Sprintf("inl-receive-%d-%d-%s", q.From.ID, inlineReceive.Amount, RandStringRunes(5))
- btnAcceptInlineReceive.Data = id
- btnCancelInlineReceive.Data = id
- inlineReceiveMenu.Inline(inlineReceiveMenu.Row(btnAcceptInlineReceive, btnCancelInlineReceive))
- result.ReplyMarkup = &tb.InlineKeyboardMarkup{InlineKeyboard: inlineReceiveMenu.InlineKeyboard}
-
- results[i] = result
-
- // needed to set a unique string ID for each result
- results[i].SetResultID(id)
-
- // create persistend inline send struct
- // add data to persistent object
- inlineReceive.ID = id
- inlineReceive.To = &q.From // The user who wants to receive
- // add result to persistent struct
- inlineReceive.Message = inlineMessage
- runtime.IgnoreError(bot.bunt.Set(inlineReceive))
- }
-
- err = bot.telegram.Answer(q, &tb.QueryResponse{
- Results: results,
- CacheTime: 1, // 60 == 1 minute, todo: make higher than 1 s in production
-
- })
-
- if err != nil {
- log.Errorln(err)
- }
-}
-
-func (bot *TipBot) acceptInlineReceiveHandler(c *tb.Callback) {
- inlineReceive, err := bot.getInlineReceive(c)
- // immediatelly set intransaction to block duplicate calls
- if err != nil {
- log.Errorf("[getInlineReceive] %s", err)
- return
- }
- err = bot.LockReceive(inlineReceive)
- if err != nil {
- log.Errorf("[acceptInlineReceiveHandler] %s", err)
- return
- }
-
- if !inlineReceive.Active {
- log.Errorf("[acceptInlineReceiveHandler] inline receive not active anymore")
- return
- }
-
- defer bot.ReleaseReceive(inlineReceive)
-
- // user `from` is the one who is SENDING
- // user `to` is the one who is RECEIVING
- from := c.Sender
- to := inlineReceive.To
- toUserStrMd := GetUserStrMd(to)
- fromUserStrMd := GetUserStrMd(from)
- toUserStr := GetUserStr(to)
- fromUserStr := GetUserStr(from)
-
- if from.ID == to.ID {
- bot.trySendMessage(from, sendYourselfMessage)
- return
- }
-
- // balance check of the user
- balance, err := bot.GetUserBalance(from)
- if err != nil {
- errmsg := fmt.Sprintf("could not get balance of user %s", fromUserStr)
- log.Errorln(errmsg)
- return
- }
- // check if fromUser has balance
- if balance < inlineReceive.Amount {
- log.Errorln("[acceptInlineReceiveHandler] balance of user %s too low", fromUserStr)
- bot.trySendMessage(from, fmt.Sprintf(inlineSendBalanceLowMessage, balance))
- return
- }
-
- // set inactive to avoid double-sends
- bot.inactivateReceive(inlineReceive)
-
- // todo: user new get username function to get userStrings
- transactionMemo := fmt.Sprintf("Send from %s to %s (%d sat).", fromUserStr, toUserStr, inlineReceive.Amount)
- t := NewTransaction(bot, from, to, inlineReceive.Amount, TransactionType("inline send"))
- t.Memo = transactionMemo
- success, err := t.Send()
- if !success {
- if err != nil {
- bot.trySendMessage(from, fmt.Sprintf(tipErrorMessage, err))
- } else {
- bot.trySendMessage(from, fmt.Sprintf(tipErrorMessage, tipUndefinedErrorMsg))
- }
- errMsg := fmt.Sprintf("[acceptInlineReceiveHandler] Transaction failed: %s", err)
- log.Errorln(errMsg)
- bot.tryEditMessage(c.Message, inlineReceiveFailedMessage, &tb.ReplyMarkup{})
- return
- }
-
- log.Infof("[acceptInlineReceiveHandler] %d sat from %s to %s", inlineReceive.Amount, fromUserStr, toUserStr)
-
- inlineReceive.Message = fmt.Sprintf("%s", fmt.Sprintf(inlineSendUpdateMessageAccept, inlineReceive.Amount, fromUserStrMd, toUserStrMd))
- memo := inlineReceive.Memo
- if len(memo) > 0 {
- inlineReceive.Message = inlineReceive.Message + fmt.Sprintf(inlineReceiveAppendMemo, memo)
- }
-
- if !bot.UserInitializedWallet(to) {
- inlineReceive.Message += "\n\n" + fmt.Sprintf(inlineSendCreateWalletMessage, GetUserStrMd(bot.telegram.Me))
- }
-
- bot.tryEditMessage(c.Message, inlineReceive.Message, &tb.ReplyMarkup{})
- // notify users
- _, err = bot.telegram.Send(to, fmt.Sprintf(sendReceivedMessage, fromUserStrMd, inlineReceive.Amount))
- _, err = bot.telegram.Send(from, fmt.Sprintf(tipSentMessage, inlineReceive.Amount, toUserStrMd))
- if err != nil {
- errmsg := fmt.Errorf("[acceptInlineReceiveHandler] Error: Receive message to %s: %s", toUserStr, err)
- log.Errorln(errmsg)
- return
- }
-}
-
-func (bot *TipBot) cancelInlineReceiveHandler(c *tb.Callback) {
- inlineReceive, err := bot.getInlineReceive(c)
- if err != nil {
- log.Errorf("[cancelInlineReceiveHandler] %s", err)
- return
- }
- if c.Sender.ID == inlineReceive.To.ID {
- bot.tryEditMessage(c.Message, sendCancelledMessage, &tb.ReplyMarkup{})
- // set the inlineReceive inactive
- inlineReceive.Active = false
- inlineReceive.InTransaction = false
- runtime.IgnoreError(bot.bunt.Set(inlineReceive))
- }
- return
-}
diff --git a/inline_send.go b/inline_send.go
deleted file mode 100644
index 80d54192..00000000
--- a/inline_send.go
+++ /dev/null
@@ -1,296 +0,0 @@
-package main
-
-import (
- "fmt"
- "time"
-
- "github.com/LightningTipBot/LightningTipBot/internal/runtime"
- log "github.com/sirupsen/logrus"
- tb "gopkg.in/tucnak/telebot.v2"
-)
-
-const (
- inlineSendMessage = "Press ✅ to receive payment from %s.\n\n💸 Amount: %d sat"
- inlineSendAppendMemo = "\n✉️ %s"
- inlineSendUpdateMessageAccept = "💸 %d sat sent from %s to %s."
- inlineSendCreateWalletMessage = "Chat with %s 👈 to manage your wallet."
- sendYourselfMessage = "📖 You can't pay to yourself."
- inlineSendFailedMessage = "🚫 Send failed."
- inlineSendInvalidAmountMessage = "🚫 Amount must be larger than 0."
- inlineSendBalanceLowMessage = "🚫 Your balance is too low (👑 %d sat)."
-)
-
-var (
- inlineQuerySendTitle = "💸 Send payment to a chat."
- inlineQuerySendDescription = "Usage: @%s send []"
- inlineResultSendTitle = "💸 Send %d sat."
- inlineResultSendDescription = "👉 Click to send %d sat to this chat."
- inlineSendMenu = &tb.ReplyMarkup{ResizeReplyKeyboard: true}
- btnCancelInlineSend = inlineSendMenu.Data("🚫 Cancel", "cancel_send_inline")
- btnAcceptInlineSend = inlineSendMenu.Data("✅ Receive", "confirm_send_inline")
-)
-
-type InlineSend struct {
- Message string `json:"inline_send_message"`
- Amount int `json:"inline_send_amount"`
- From *tb.User `json:"inline_send_from"`
- To *tb.User `json:"inline_send_to"`
- Memo string `json:"inline_send_memo"`
- ID string `json:"inline_send_id"`
- Active bool `json:"inline_send_active"`
- InTransaction bool `json:"inline_send_intransaction"`
-}
-
-func NewInlineSend() *InlineSend {
- inlineSend := &InlineSend{
- Message: "",
- Active: true,
- InTransaction: false,
- }
- return inlineSend
-
-}
-
-func (msg InlineSend) Key() string {
- return msg.ID
-}
-
-func (bot *TipBot) LockSend(tx *InlineSend) error {
- // immediatelly set intransaction to block duplicate calls
- tx.InTransaction = true
- err := bot.bunt.Set(tx)
- if err != nil {
- return err
- }
- return nil
-}
-
-func (bot *TipBot) ReleaseSend(tx *InlineSend) error {
- // immediatelly set intransaction to block duplicate calls
- tx.InTransaction = false
- err := bot.bunt.Set(tx)
- if err != nil {
- return err
- }
- return nil
-}
-
-func (bot *TipBot) inactivateSend(tx *InlineSend) error {
- tx.Active = false
- err := bot.bunt.Set(tx)
- if err != nil {
- return err
- }
- return nil
-}
-
-func (bot *TipBot) getInlineSend(c *tb.Callback) (*InlineSend, error) {
- inlineSend := NewInlineSend()
- inlineSend.ID = c.Data
-
- err := bot.bunt.Get(inlineSend)
-
- // to avoid race conditions, we block the call if there is
- // already an active transaction by loop until InTransaction is false
- ticker := time.NewTicker(time.Second * 10)
-
- for inlineSend.InTransaction {
- select {
- case <-ticker.C:
- return nil, fmt.Errorf("inline send timeout")
- default:
- log.Infoln("in transaction")
- time.Sleep(time.Duration(500) * time.Millisecond)
- err = bot.bunt.Get(inlineSend)
- }
- }
- if err != nil {
- return nil, fmt.Errorf("could not get inline send message")
- }
-
- return inlineSend, nil
-
-}
-
-func (bot TipBot) handleInlineSendQuery(q *tb.Query) {
- inlineSend := NewInlineSend()
- var err error
- inlineSend.Amount, err = decodeAmountFromCommand(q.Text)
- if err != nil {
- bot.inlineQueryReplyWithError(q, inlineQuerySendTitle, fmt.Sprintf(inlineQuerySendDescription, bot.telegram.Me.Username))
- return
- }
- if inlineSend.Amount < 1 {
- bot.inlineQueryReplyWithError(q, inlineSendInvalidAmountMessage, fmt.Sprintf(inlineQuerySendDescription, bot.telegram.Me.Username))
- return
- }
- fromUserStr := GetUserStr(&q.From)
- balance, err := bot.GetUserBalance(&q.From)
- if err != nil {
- errmsg := fmt.Sprintf("could not get balance of user %s", fromUserStr)
- log.Errorln(errmsg)
- return
- }
- // check if fromUser has balance
- if balance < inlineSend.Amount {
- log.Errorln("Balance of user %s too low", fromUserStr)
- bot.inlineQueryReplyWithError(q, fmt.Sprintf(inlineSendBalanceLowMessage, balance), fmt.Sprintf(inlineQuerySendDescription, bot.telegram.Me.Username))
- return
- }
-
- // check for memo in command
- inlineSend.Memo = GetMemoFromCommand(q.Text, 2)
-
- urls := []string{
- queryImage,
- }
- results := make(tb.Results, len(urls)) // []tb.Result
- for i, url := range urls {
-
- inlineMessage := fmt.Sprintf(inlineSendMessage, fromUserStr, inlineSend.Amount)
-
- if len(inlineSend.Memo) > 0 {
- inlineMessage = inlineMessage + fmt.Sprintf(inlineSendAppendMemo, inlineSend.Memo)
- }
-
- result := &tb.ArticleResult{
- // URL: url,
- Text: inlineMessage,
- Title: fmt.Sprintf(inlineResultSendTitle, inlineSend.Amount),
- Description: fmt.Sprintf(inlineResultSendDescription, inlineSend.Amount),
- // required for photos
- ThumbURL: url,
- }
- id := fmt.Sprintf("inl-send-%d-%d-%s", q.From.ID, inlineSend.Amount, RandStringRunes(5))
- btnAcceptInlineSend.Data = id
- btnCancelInlineSend.Data = id
- inlineSendMenu.Inline(inlineSendMenu.Row(btnAcceptInlineSend, btnCancelInlineSend))
- result.ReplyMarkup = &tb.InlineKeyboardMarkup{InlineKeyboard: inlineSendMenu.InlineKeyboard}
-
- results[i] = result
-
- // needed to set a unique string ID for each result
- results[i].SetResultID(id)
-
- // add data to persistent object
- inlineSend.Message = inlineMessage
- inlineSend.ID = id
- inlineSend.From = &q.From
- // add result to persistent struct
- runtime.IgnoreError(bot.bunt.Set(inlineSend))
- }
-
- err = bot.telegram.Answer(q, &tb.QueryResponse{
- Results: results,
- CacheTime: 1, // 60 == 1 minute, todo: make higher than 1 s in production
-
- })
- if err != nil {
- log.Errorln(err)
- }
-}
-
-func (bot *TipBot) acceptInlineSendHandler(c *tb.Callback) {
- inlineSend, err := bot.getInlineSend(c)
- if err != nil {
- log.Errorf("[acceptInlineSendHandler] %s", err)
- return
- }
- // immediatelly set intransaction to block duplicate calls
- err = bot.LockSend(inlineSend)
- if err != nil {
- log.Errorf("[getInlineSend] %s", err)
- return
- }
- if !inlineSend.Active {
- log.Errorf("[acceptInlineSendHandler] inline send not active anymore")
- return
- }
-
- defer bot.ReleaseSend(inlineSend)
-
- amount := inlineSend.Amount
- to := c.Sender
- from := inlineSend.From
-
- inlineSend.To = to
-
- if from.ID == to.ID {
- bot.trySendMessage(from, sendYourselfMessage)
- return
- }
-
- toUserStrMd := GetUserStrMd(to)
- fromUserStrMd := GetUserStrMd(from)
- toUserStr := GetUserStr(to)
- fromUserStr := GetUserStr(from)
-
- // check if user exists and create a wallet if not
- _, exists := bot.UserExists(to)
- if !exists {
- log.Infof("[sendInline] User %s has no wallet.", toUserStr)
- err = bot.CreateWalletForTelegramUser(to)
- if err != nil {
- errmsg := fmt.Errorf("[sendInline] Error: Could not create wallet for %s", toUserStr)
- log.Errorln(errmsg)
- return
- }
- }
- // set inactive to avoid double-sends
- bot.inactivateSend(inlineSend)
-
- // todo: user new get username function to get userStrings
- transactionMemo := fmt.Sprintf("Send from %s to %s (%d sat).", fromUserStr, toUserStr, amount)
- t := NewTransaction(bot, from, to, amount, TransactionType("inline send"))
- t.Memo = transactionMemo
- success, err := t.Send()
- if !success {
- if err != nil {
- bot.trySendMessage(from, fmt.Sprintf(tipErrorMessage, err))
- } else {
- bot.trySendMessage(from, fmt.Sprintf(tipErrorMessage, tipUndefinedErrorMsg))
- }
- errMsg := fmt.Sprintf("[sendInline] Transaction failed: %s", err)
- log.Errorln(errMsg)
- bot.tryEditMessage(c.Message, inlineSendFailedMessage, &tb.ReplyMarkup{})
- return
- }
-
- log.Infof("[sendInline] %d sat from %s to %s", amount, fromUserStr, toUserStr)
-
- inlineSend.Message = fmt.Sprintf("%s", fmt.Sprintf(inlineSendUpdateMessageAccept, amount, fromUserStrMd, toUserStrMd))
- memo := inlineSend.Memo
- if len(memo) > 0 {
- inlineSend.Message = inlineSend.Message + fmt.Sprintf(inlineSendAppendMemo, memo)
- }
-
- if !bot.UserInitializedWallet(to) {
- inlineSend.Message += "\n\n" + fmt.Sprintf(inlineSendCreateWalletMessage, GetUserStrMd(bot.telegram.Me))
- }
-
- bot.tryEditMessage(c.Message, inlineSend.Message, &tb.ReplyMarkup{})
- // notify users
- _, err = bot.telegram.Send(to, fmt.Sprintf(sendReceivedMessage, fromUserStrMd, amount))
- _, err = bot.telegram.Send(from, fmt.Sprintf(tipSentMessage, amount, toUserStrMd))
- if err != nil {
- errmsg := fmt.Errorf("[sendInline] Error: Send message to %s: %s", toUserStr, err)
- log.Errorln(errmsg)
- return
- }
-}
-
-func (bot *TipBot) cancelInlineSendHandler(c *tb.Callback) {
- inlineSend, err := bot.getInlineSend(c)
- if err != nil {
- log.Errorf("[cancelInlineSendHandler] %s", err)
- return
- }
- if c.Sender.ID == inlineSend.From.ID {
- bot.tryEditMessage(c.Message, sendCancelledMessage, &tb.ReplyMarkup{})
- // set the inlineSend inactive
- inlineSend.Active = false
- inlineSend.InTransaction = false
- runtime.IgnoreError(bot.bunt.Set(inlineSend))
- }
- return
-}
diff --git a/internal/api/admin/admin.go b/internal/api/admin/admin.go
new file mode 100644
index 00000000..b34ca610
--- /dev/null
+++ b/internal/api/admin/admin.go
@@ -0,0 +1,15 @@
+package admin
+
+import (
+ "github.com/LightningTipBot/LightningTipBot/internal/telegram"
+)
+
+type Service struct {
+ bot *telegram.TipBot
+}
+
+func New(b *telegram.TipBot) Service {
+ return Service{
+ bot: b,
+ }
+}
diff --git a/internal/api/admin/ban.go b/internal/api/admin/ban.go
new file mode 100644
index 00000000..80d41f38
--- /dev/null
+++ b/internal/api/admin/ban.go
@@ -0,0 +1,76 @@
+package admin
+
+import (
+ "fmt"
+ "net/http"
+ "strings"
+
+ "github.com/LightningTipBot/LightningTipBot/internal/lnbits"
+ "github.com/LightningTipBot/LightningTipBot/internal/telegram"
+ "github.com/gorilla/mux"
+ log "github.com/sirupsen/logrus"
+)
+
+func (s Service) UnbanUser(w http.ResponseWriter, r *http.Request) {
+ user, err := s.getUserByTelegramId(r)
+ if err != nil {
+ log.Errorf("[ADMIN] could not ban user: %v", err)
+ w.WriteHeader(http.StatusBadRequest)
+ return
+ }
+ if !user.Banned && !strings.HasPrefix(user.Wallet.Adminkey, "banned_") {
+ log.Infof("[ADMIN] user is not banned. Aborting.")
+ w.WriteHeader(http.StatusBadRequest)
+ return
+ }
+ user.Banned = false
+ adminSlice := strings.Split(user.Wallet.Adminkey, "_")
+ user.Wallet.Adminkey = adminSlice[len(adminSlice)-1]
+ err = telegram.UpdateUserRecord(user, *s.bot)
+ if err != nil {
+ log.Errorf("[ADMIN] could not update user: %v", err)
+ return
+ }
+ log.Infof("[ADMIN] Unbanned user (%s)", user.ID)
+ w.WriteHeader(http.StatusOK)
+}
+
+func (s Service) BanUser(w http.ResponseWriter, r *http.Request) {
+ user, err := s.getUserByTelegramId(r)
+ if err != nil {
+ log.Errorf("[ADMIN] could not ban user: %v", err)
+ w.WriteHeader(http.StatusBadRequest)
+ return
+ }
+ if user.Banned {
+ w.WriteHeader(http.StatusBadRequest)
+ log.Infof("[ADMIN] user is already banned. Aborting.")
+ return
+ }
+ user.Banned = true
+ if reason := r.URL.Query().Get("reason"); reason != "" {
+ user.Wallet.Adminkey = fmt.Sprintf("%s_%s", reason, user.Wallet.Adminkey)
+ }
+ user.Wallet.Adminkey = fmt.Sprintf("%s_%s", "banned", user.Wallet.Adminkey)
+ err = telegram.UpdateUserRecord(user, *s.bot)
+ if err != nil {
+ log.Errorf("[ADMIN] could not update user: %v", err)
+ return
+ }
+
+ log.Infof("[ADMIN] Banned user (%s)", user.ID)
+ w.WriteHeader(http.StatusOK)
+}
+
+func (s Service) getUserByTelegramId(r *http.Request) (*lnbits.User, error) {
+ user := &lnbits.User{}
+ v := mux.Vars(r)
+ if v["id"] == "" {
+ return nil, fmt.Errorf("invalid id")
+ }
+ tx := s.bot.DB.Users.Where("telegram_id = ? COLLATE NOCASE", v["id"]).First(user)
+ if tx.Error != nil {
+ return nil, tx.Error
+ }
+ return user, nil
+}
diff --git a/internal/api/admin/dalle.go b/internal/api/admin/dalle.go
new file mode 100644
index 00000000..385b0a17
--- /dev/null
+++ b/internal/api/admin/dalle.go
@@ -0,0 +1,14 @@
+package admin
+
+import (
+ "github.com/LightningTipBot/LightningTipBot/internal/dalle"
+ "net/http"
+)
+
+func (s Service) DisableDalle(w http.ResponseWriter, r *http.Request) {
+ dalle.Enabled = false
+}
+
+func (s Service) EnableDalle(w http.ResponseWriter, r *http.Request) {
+ dalle.Enabled = true
+}
diff --git a/internal/api/admin/pending_transactions.go b/internal/api/admin/pending_transactions.go
new file mode 100644
index 00000000..21d2a80e
--- /dev/null
+++ b/internal/api/admin/pending_transactions.go
@@ -0,0 +1,224 @@
+package admin
+
+import (
+ "fmt"
+ "net/http"
+ "strings"
+
+ "github.com/LightningTipBot/LightningTipBot/internal/api"
+ "github.com/LightningTipBot/LightningTipBot/internal/telegram"
+ "github.com/LightningTipBot/LightningTipBot/internal/thirdparty"
+ "github.com/gorilla/mux"
+ log "github.com/sirupsen/logrus"
+)
+
+// ApprovePendingTransaction handles admin approval of pending transactions
+func (s Service) ApprovePendingTransaction(w http.ResponseWriter, r *http.Request) {
+ vars := mux.Vars(r)
+ transactionID := vars["id"]
+
+ if transactionID == "" {
+ http.Error(w, "Transaction ID is required", http.StatusBadRequest)
+ return
+ }
+
+ // Load pending transaction
+ pendingTx, err := api.LoadPendingTransaction(transactionID, s.bot)
+ if err != nil {
+ log.Errorf("[ADMIN] Failed to load pending transaction %s: %v", transactionID, err)
+ http.Error(w, "Transaction not found", http.StatusNotFound)
+ return
+ }
+
+ // Check if transaction can be approved
+ if !pendingTx.CanBeApproved() {
+ log.Warnf("[ADMIN] Transaction %s cannot be approved: status=%s, expired=%v",
+ transactionID, pendingTx.Status, pendingTx.IsExpired())
+ http.Error(w, fmt.Sprintf("Transaction cannot be approved: status=%s", pendingTx.Status), http.StatusBadRequest)
+ return
+ }
+
+ // Approve the transaction
+ approverIP := getClientIP(r)
+ err = pendingTx.Approve(fmt.Sprintf("admin:%s", approverIP))
+ if err != nil {
+ log.Errorf("[ADMIN] Failed to approve transaction %s: %v", transactionID, err)
+ http.Error(w, "Failed to approve transaction", http.StatusInternalServerError)
+ return
+ }
+
+ // Save the updated transaction
+ err = pendingTx.SaveToDB(s.bot)
+ if err != nil {
+ log.Errorf("[ADMIN] Failed to save approved transaction %s: %v", transactionID, err)
+ http.Error(w, "Failed to save approval", http.StatusInternalServerError)
+ return
+ }
+
+ // Execute the transaction
+ err = s.executePendingTransaction(pendingTx)
+ if err != nil {
+ log.Errorf("[ADMIN] Failed to execute approved transaction %s: %v", transactionID, err)
+ http.Error(w, fmt.Sprintf("Transaction approved but execution failed: %v", err), http.StatusInternalServerError)
+ return
+ }
+
+ log.Infof("[ADMIN] Transaction %s approved and executed successfully", transactionID)
+ w.WriteHeader(http.StatusOK)
+ w.Write([]byte(fmt.Sprintf("Transaction %s approved and executed successfully", transactionID)))
+}
+
+// RejectPendingTransaction handles admin rejection of pending transactions
+func (s Service) RejectPendingTransaction(w http.ResponseWriter, r *http.Request) {
+ vars := mux.Vars(r)
+ transactionID := vars["id"]
+
+ if transactionID == "" {
+ http.Error(w, "Transaction ID is required", http.StatusBadRequest)
+ return
+ }
+
+ // Load pending transaction
+ pendingTx, err := api.LoadPendingTransaction(transactionID, s.bot)
+ if err != nil {
+ log.Errorf("[ADMIN] Failed to load pending transaction %s: %v", transactionID, err)
+ http.Error(w, "Transaction not found", http.StatusNotFound)
+ return
+ }
+
+ // Check if transaction can be rejected
+ if pendingTx.Status != api.StatusPending {
+ log.Warnf("[ADMIN] Transaction %s cannot be rejected: status=%s", transactionID, pendingTx.Status)
+ http.Error(w, fmt.Sprintf("Transaction cannot be rejected: status=%s", pendingTx.Status), http.StatusBadRequest)
+ return
+ }
+
+ // Reject the transaction
+ rejectorIP := getClientIP(r)
+ err = pendingTx.Reject(fmt.Sprintf("admin:%s", rejectorIP))
+ if err != nil {
+ log.Errorf("[ADMIN] Failed to reject transaction %s: %v", transactionID, err)
+ http.Error(w, "Failed to reject transaction", http.StatusInternalServerError)
+ return
+ }
+
+ // Save the updated transaction
+ err = pendingTx.SaveToDB(s.bot)
+ if err != nil {
+ log.Errorf("[ADMIN] Failed to save rejected transaction %s: %v", transactionID, err)
+ http.Error(w, "Failed to save rejection", http.StatusInternalServerError)
+ return
+ }
+
+ log.Infof("[ADMIN] Transaction %s rejected successfully", transactionID)
+ w.WriteHeader(http.StatusOK)
+ w.Write([]byte(fmt.Sprintf("Transaction %s rejected successfully", transactionID)))
+}
+
+// ListPendingTransactions lists all pending transactions requiring approval
+func (s Service) ListPendingTransactions(w http.ResponseWriter, r *http.Request) {
+ // This would need to be implemented to query all pending transactions
+ // For now, we'll return a placeholder response
+ log.Info("[ADMIN] List pending transactions requested")
+ w.WriteHeader(http.StatusOK)
+ w.Write([]byte("Pending transactions list - implementation needed"))
+}
+
+// executePendingTransaction executes an approved pending transaction
+func (s Service) executePendingTransaction(pendingTx *api.PendingTransaction) error {
+ // Load the users again to ensure they still exist and have wallets
+ fromUser, err := telegram.GetUserByTelegramUsername(pendingTx.FromUsername, *s.bot)
+ if err != nil {
+ return fmt.Errorf("sender user %s no longer exists or has no wallet: %v", pendingTx.FromUsername, err)
+ }
+
+ toUser, err := telegram.GetUserByTelegramUsername(pendingTx.ToUsername, *s.bot)
+ if err != nil {
+ return fmt.Errorf("recipient user %s no longer exists or has no wallet: %v", pendingTx.ToUsername, err)
+ }
+
+ // Check sender's balance again
+ balance, err := s.bot.GetUserBalance(fromUser)
+ if err != nil {
+ return fmt.Errorf("could not check sender balance: %v", err)
+ }
+
+ if balance < pendingTx.Amount {
+ return fmt.Errorf("insufficient balance: %d sat available, %d sat required", balance, pendingTx.Amount)
+ }
+
+ // Create transaction memo
+ fromUserStr := telegram.GetUserStr(fromUser.Telegram)
+ toUserStr := telegram.GetUserStr(toUser.Telegram)
+ transactionMemo := fmt.Sprintf("💸 Admin-approved API Send from %s to %s. TX ID: %s", fromUserStr, toUserStr, pendingTx.ID)
+ if pendingTx.Memo != "" {
+ transactionMemo += fmt.Sprintf(" Memo: %s", pendingTx.Memo)
+ }
+
+ // Create and execute transaction
+ t := telegram.NewTransaction(s.bot, fromUser, toUser, pendingTx.Amount, telegram.TransactionType("api_send_admin_approved"))
+ t.Memo = transactionMemo
+
+ success, err := t.Send()
+ if !success || err != nil {
+ return fmt.Errorf("transaction execution failed: %v", err)
+ }
+
+ // Mark as executed
+ err = pendingTx.Execute()
+ if err != nil {
+ log.Warnf("[ADMIN] Failed to mark transaction as executed: %v", err)
+ }
+
+ // Save the final state
+ err = pendingTx.SaveToDB(s.bot)
+ if err != nil {
+ log.Warnf("[ADMIN] Failed to save executed transaction state: %v", err)
+ }
+
+ // Send notifications
+ fromUserStrMd := telegram.GetUserStrMd(fromUser.Telegram)
+ _, err = s.bot.Telegram.Send(toUser.Telegram, fmt.Sprintf("💰 You received %s from %s via admin-approved API payment", thirdparty.FormatSatsWithLKR(pendingTx.Amount), fromUserStrMd))
+ if err != nil {
+ log.Warnf("[ADMIN] Could not send notification to recipient: %v", err)
+ }
+
+ // Send memo if provided
+ if pendingTx.Memo != "" {
+ _, err = s.bot.Telegram.Send(toUser.Telegram, fmt.Sprintf("✉️ %s", pendingTx.Memo))
+ if err != nil {
+ log.Warnf("[ADMIN] Could not send memo to recipient: %v", err)
+ }
+ }
+
+ log.Infof("[ADMIN] ✅ Admin-approved API Send executed: %s -> %s (%d sat) [TX: %s]",
+ fromUserStr, toUserStr, pendingTx.Amount, pendingTx.ID)
+
+ return nil
+}
+
+// getClientIP extracts the real client IP from the request
+func getClientIP(r *http.Request) string {
+ // Check X-Forwarded-For header first
+ if xff := r.Header.Get("X-Forwarded-For"); xff != "" {
+ // X-Forwarded-For can contain multiple IPs, take the first one
+ if ips := strings.Split(xff, ","); len(ips) > 0 {
+ return strings.TrimSpace(ips[0])
+ }
+ }
+
+ // Check X-Real-IP header
+ if xri := r.Header.Get("X-Real-IP"); xri != "" {
+ return xri
+ }
+
+ // Fall back to RemoteAddr
+ if host := r.RemoteAddr; host != "" {
+ if idx := strings.LastIndex(host, ":"); idx != -1 {
+ return host[:idx]
+ }
+ return host
+ }
+
+ return "unknown"
+}
diff --git a/internal/api/analytics.go b/internal/api/analytics.go
new file mode 100644
index 00000000..7c9483d2
--- /dev/null
+++ b/internal/api/analytics.go
@@ -0,0 +1,620 @@
+package api
+
+import (
+ "encoding/csv"
+ "encoding/json"
+ "fmt"
+ "net/http"
+ "strconv"
+ "strings"
+ "time"
+
+ "github.com/LightningTipBot/LightningTipBot/internal/lnbits"
+ "github.com/LightningTipBot/LightningTipBot/internal/telegram"
+ "github.com/gorilla/mux"
+ log "github.com/sirupsen/logrus"
+)
+
+const (
+ // maxAnalyticsLimit caps the maximum number of records per request to prevent memory exhaustion
+ maxAnalyticsLimit = 10000
+ // maxAnalyticsOffset caps the offset to prevent abuse
+ maxAnalyticsOffset = 100000
+ // minValidTimestamp is 2009-01-03 (Bitcoin genesis block) - no valid data before this
+ minValidTimestamp int64 = 1230940800
+ // maxValidTimestamp is 2100-01-01 - reasonable upper bound
+ maxValidTimestamp int64 = 4102444800
+)
+
+// TransactionAnalyticsResponse represents the analytics data response
+type TransactionAnalyticsResponse struct {
+ Status string `json:"status"`
+ ExternalPayments []ExternalPaymentData `json:"external_payments,omitempty"`
+ InternalTxs []InternalTransactionData `json:"internal_transactions,omitempty"`
+ Summary TransactionSummary `json:"summary"`
+ Filters map[string]string `json:"filters_applied"`
+}
+
+// ExternalPaymentData represents external LNbits payment data
+type ExternalPaymentData struct {
+ UserID int64 `json:"user_id"`
+ Username string `json:"username"`
+ CheckingID string `json:"checking_id"`
+ Pending bool `json:"pending"`
+ Amount int64 `json:"amount_msats"`
+ AmountSats int64 `json:"amount_sats"`
+ Fee int64 `json:"fee_msats"`
+ FeeSats int64 `json:"fee_sats"`
+ Memo string `json:"memo"`
+ Time int `json:"time"`
+ Timestamp string `json:"timestamp"`
+ PaymentType string `json:"payment_type"` // "incoming" or "outgoing"
+ Bolt11 string `json:"bolt11,omitempty"`
+ PaymentHash string `json:"payment_hash"`
+ WalletID string `json:"wallet_id"`
+}
+
+// InternalTransactionData represents internal bot transactions
+type InternalTransactionData struct {
+ ID uint `json:"id"`
+ Time string `json:"time"`
+ FromID int64 `json:"from_id"`
+ ToID int64 `json:"to_id"`
+ FromUser string `json:"from_user"`
+ ToUser string `json:"to_user"`
+ Type string `json:"type"`
+ Amount int64 `json:"amount_sats"`
+ ChatID int64 `json:"chat_id,omitempty"`
+ ChatName string `json:"chat_name,omitempty"`
+ Memo string `json:"memo"`
+ Success bool `json:"success"`
+}
+
+// TransactionSummary provides aggregate statistics
+type TransactionSummary struct {
+ TotalExternalCount int `json:"total_external_count"`
+ TotalInternalCount int `json:"total_internal_count"`
+ ExternalIncoming int64 `json:"external_incoming_sats"`
+ ExternalOutgoing int64 `json:"external_outgoing_sats"`
+ InternalVolume int64 `json:"internal_volume_sats"`
+ UniqueUsers int `json:"unique_users"`
+}
+
+// GetTransactionAnalytics retrieves transaction data for analytics
+// Endpoint: GET /api/v1/analytics/transactions
+// Query Parameters:
+// - user_id: Filter by specific user Telegram ID
+// - username: Filter by username (without @)
+// - start_date: Start date (YYYY-MM-DD or Unix timestamp)
+// - end_date: End date (YYYY-MM-DD or Unix timestamp)
+// - payment_type: Filter external payments by type (incoming/outgoing/all)
+// - include_external: Include external LNbits payments (true/false, default: true)
+// - include_internal: Include internal bot transactions (true/false, default: true)
+// - limit: Maximum number of transactions per type (default: 1000)
+// - offset: Number of transactions to skip for pagination (default: 0)
+// - format: Response format - "json" (default) or "csv"
+func (s Service) GetTransactionAnalytics(w http.ResponseWriter, r *http.Request) {
+ // Parse query parameters
+ params := r.URL.Query()
+
+ userIDStr := params.Get("user_id")
+ username := params.Get("username")
+ startDateStr := params.Get("start_date")
+ endDateStr := params.Get("end_date")
+ paymentTypeFilter := params.Get("payment_type")
+ includeExternal := params.Get("include_external") != "false"
+ includeInternal := params.Get("include_internal") != "false"
+ limitStr := params.Get("limit")
+ offsetStr := params.Get("offset")
+ outputFormat := params.Get("format")
+
+ // Set default limit with max cap
+ limit := 1000
+ if limitStr != "" {
+ if parsedLimit, err := strconv.Atoi(limitStr); err == nil && parsedLimit > 0 {
+ limit = parsedLimit
+ }
+ }
+ if limit > maxAnalyticsLimit {
+ limit = maxAnalyticsLimit
+ }
+
+ // Set default offset with max cap
+ offset := 0
+ if offsetStr != "" {
+ if parsedOffset, err := strconv.Atoi(offsetStr); err == nil && parsedOffset >= 0 {
+ offset = parsedOffset
+ }
+ }
+ if offset > maxAnalyticsOffset {
+ offset = maxAnalyticsOffset
+ }
+
+ // Parse dates
+ var startDate, endDate time.Time
+ var err error
+
+ if startDateStr != "" {
+ startDate, err = parseDate(startDateStr)
+ if err != nil {
+ RespondError(w, "Invalid start_date format. Use YYYY-MM-DD or Unix timestamp")
+ return
+ }
+ }
+
+ if endDateStr != "" {
+ endDate, err = parseDate(endDateStr)
+ if err != nil {
+ RespondError(w, "Invalid end_date format. Use YYYY-MM-DD or Unix timestamp")
+ return
+ }
+ }
+
+ response := TransactionAnalyticsResponse{
+ Status: StatusOk,
+ Filters: make(map[string]string),
+ }
+
+ // Track applied filters
+ if userIDStr != "" {
+ response.Filters["user_id"] = userIDStr
+ }
+ if username != "" {
+ response.Filters["username"] = username
+ }
+ if startDateStr != "" {
+ response.Filters["start_date"] = startDateStr
+ }
+ if endDateStr != "" {
+ response.Filters["end_date"] = endDateStr
+ }
+ if paymentTypeFilter != "" {
+ response.Filters["payment_type"] = paymentTypeFilter
+ }
+ response.Filters["limit"] = strconv.Itoa(limit)
+ response.Filters["offset"] = strconv.Itoa(offset)
+
+ var targetUsers []*lnbits.User
+
+ // Find target user(s)
+ if userIDStr != "" {
+ userID, err := strconv.ParseInt(userIDStr, 10, 64)
+ if err != nil {
+ RespondError(w, "Invalid user_id")
+ return
+ }
+ user := &lnbits.User{}
+ tx := s.Bot.DB.Users.Where("telegram_id = ?", userID).First(user)
+ if tx.Error != nil {
+ RespondError(w, "User not found")
+ return
+ }
+ targetUsers = append(targetUsers, user)
+ } else if username != "" {
+ user := &lnbits.User{}
+ tx := s.Bot.DB.Users.Where("telegram_username = ?", username).First(user)
+ if tx.Error != nil {
+ RespondError(w, "User not found")
+ return
+ }
+ targetUsers = append(targetUsers, user)
+ } else {
+ // Get all users if no specific user requested
+ var allUsers []*lnbits.User
+ tx := s.Bot.DB.Users.Find(&allUsers)
+ if tx.Error != nil {
+ RespondError(w, "Error fetching users")
+ return
+ }
+ targetUsers = allUsers
+ }
+
+ uniqueUserMap := make(map[int64]bool)
+
+ // Fetch external payments from LNbits with configurable limit+offset
+ if includeExternal {
+ for _, user := range targetUsers {
+ if user.Wallet == nil {
+ continue
+ }
+
+ uniqueUserMap[user.Telegram.ID] = true
+
+ payments, err := s.Bot.Client.PaymentsWithOptions(*user.Wallet, limit+offset, 0)
+ if err != nil {
+ log.Errorf("[Analytics] Error fetching payments for user %d: %s", user.Telegram.ID, err.Error())
+ continue
+ }
+
+ for _, payment := range payments {
+ // Apply date filters
+ paymentTime := time.Unix(int64(payment.Time), 0)
+ if !startDate.IsZero() && paymentTime.Before(startDate) {
+ continue
+ }
+ if !endDate.IsZero() && paymentTime.After(endDate) {
+ continue
+ }
+
+ // Determine payment type
+ paymentType := "outgoing"
+ if payment.Amount > 0 {
+ paymentType = "incoming"
+ }
+
+ // Apply payment type filter
+ if paymentTypeFilter != "" && paymentTypeFilter != "all" && paymentTypeFilter != paymentType {
+ continue
+ }
+
+ // Check limit
+ if len(response.ExternalPayments) >= limit {
+ break
+ }
+
+ externalPayment := ExternalPaymentData{
+ UserID: user.Telegram.ID,
+ Username: user.Telegram.Username,
+ CheckingID: payment.CheckingID,
+ Pending: payment.Pending,
+ Amount: payment.Amount,
+ AmountSats: payment.Amount / 1000,
+ Fee: payment.Fee,
+ FeeSats: payment.Fee / 1000,
+ Memo: payment.Memo,
+ Time: payment.Time,
+ Timestamp: paymentTime.Format(time.RFC3339),
+ PaymentType: paymentType,
+ Bolt11: payment.Bolt11,
+ PaymentHash: payment.PaymentHash,
+ WalletID: payment.WalletID,
+ }
+
+ response.ExternalPayments = append(response.ExternalPayments, externalPayment)
+
+ // Update summary
+ response.Summary.TotalExternalCount++
+ if paymentType == "incoming" {
+ response.Summary.ExternalIncoming += externalPayment.AmountSats
+ } else {
+ response.Summary.ExternalOutgoing += abs(externalPayment.AmountSats)
+ }
+ }
+ }
+ }
+
+ // Fetch internal transactions from bot database
+ if includeInternal {
+ var internalTxs []telegram.Transaction
+ dbQuery := s.Bot.DB.Transactions.Model(&telegram.Transaction{})
+
+ // Apply filters
+ if userIDStr != "" {
+ userID, _ := strconv.ParseInt(userIDStr, 10, 64)
+ dbQuery = dbQuery.Where("from_id = ? OR to_id = ?", userID, userID)
+ } else if username != "" {
+ dbQuery = dbQuery.Where("from_user = ? OR to_user = ?", username, username)
+ }
+
+ if !startDate.IsZero() {
+ dbQuery = dbQuery.Where("time >= ?", startDate)
+ }
+
+ if !endDate.IsZero() {
+ dbQuery = dbQuery.Where("time <= ?", endDate)
+ }
+
+ dbQuery = dbQuery.Order("time desc").Limit(limit).Offset(offset).Find(&internalTxs)
+
+ if dbQuery.Error != nil {
+ log.Errorf("[Analytics] Error fetching internal transactions: %s", dbQuery.Error)
+ } else {
+ for _, tx := range internalTxs {
+ uniqueUserMap[tx.FromId] = true
+ uniqueUserMap[tx.ToId] = true
+
+ internalTx := InternalTransactionData{
+ ID: tx.ID,
+ Time: tx.Time.Format(time.RFC3339),
+ FromID: tx.FromId,
+ ToID: tx.ToId,
+ FromUser: tx.FromUser,
+ ToUser: tx.ToUser,
+ Type: tx.Type,
+ Amount: tx.Amount,
+ ChatID: tx.ChatID,
+ ChatName: tx.ChatName,
+ Memo: tx.Memo,
+ Success: tx.Success,
+ }
+
+ response.InternalTxs = append(response.InternalTxs, internalTx)
+
+ // Update summary
+ response.Summary.TotalInternalCount++
+ if tx.Success {
+ response.Summary.InternalVolume += tx.Amount
+ }
+ }
+ }
+ }
+
+ response.Summary.UniqueUsers = len(uniqueUserMap)
+
+ // Respond in requested format
+ if outputFormat == "csv" {
+ writeCSVResponse(w, response)
+ return
+ }
+
+ w.Header().Set("Content-Type", "application/json")
+ w.WriteHeader(http.StatusOK)
+ json.NewEncoder(w).Encode(response)
+}
+
+// GetUserTransactionHistory retrieves all transactions for a specific user
+// Endpoint: GET /api/v1/analytics/user/{user_id}/transactions
+// Query Parameters:
+// - limit: Maximum number of transactions per type (default: 1000)
+// - offset: Number of transactions to skip for pagination (default: 0)
+// - format: Response format - "json" (default) or "csv"
+func (s Service) GetUserTransactionHistory(w http.ResponseWriter, r *http.Request) {
+ vars := mux.Vars(r)
+ userIDStr := vars["user_id"]
+ params := r.URL.Query()
+ outputFormat := params.Get("format")
+
+ // Parse limit and offset with max caps
+ limit := 1000
+ if limitStr := params.Get("limit"); limitStr != "" {
+ if parsedLimit, err := strconv.Atoi(limitStr); err == nil && parsedLimit > 0 {
+ limit = parsedLimit
+ }
+ }
+ if limit > maxAnalyticsLimit {
+ limit = maxAnalyticsLimit
+ }
+ offset := 0
+ if offsetStr := params.Get("offset"); offsetStr != "" {
+ if parsedOffset, err := strconv.Atoi(offsetStr); err == nil && parsedOffset >= 0 {
+ offset = parsedOffset
+ }
+ }
+ if offset > maxAnalyticsOffset {
+ offset = maxAnalyticsOffset
+ }
+
+ userID, err := strconv.ParseInt(userIDStr, 10, 64)
+ if err != nil {
+ RespondError(w, "Invalid user_id")
+ return
+ }
+
+ user := &lnbits.User{}
+ tx := s.Bot.DB.Users.Where("telegram_id = ?", userID).First(user)
+ if tx.Error != nil {
+ RespondError(w, "User not found")
+ return
+ }
+
+ response := TransactionAnalyticsResponse{
+ Status: StatusOk,
+ Filters: map[string]string{
+ "user_id": userIDStr,
+ "limit": strconv.Itoa(limit),
+ "offset": strconv.Itoa(offset),
+ },
+ }
+
+ // Fetch external payments from LNbits
+ if user.Wallet != nil {
+ payments, err := s.Bot.Client.PaymentsWithOptions(*user.Wallet, limit+offset, 0)
+ if err != nil {
+ log.Errorf("[Analytics] Error fetching payments for user %d: %s", userID, err.Error())
+ } else {
+ for _, payment := range payments {
+ paymentTime := time.Unix(int64(payment.Time), 0)
+ paymentType := "outgoing"
+ if payment.Amount > 0 {
+ paymentType = "incoming"
+ }
+
+ if len(response.ExternalPayments) >= limit {
+ break
+ }
+
+ externalPayment := ExternalPaymentData{
+ UserID: user.Telegram.ID,
+ Username: user.Telegram.Username,
+ CheckingID: payment.CheckingID,
+ Pending: payment.Pending,
+ Amount: payment.Amount,
+ AmountSats: payment.Amount / 1000,
+ Fee: payment.Fee,
+ FeeSats: payment.Fee / 1000,
+ Memo: payment.Memo,
+ Time: payment.Time,
+ Timestamp: paymentTime.Format(time.RFC3339),
+ PaymentType: paymentType,
+ Bolt11: payment.Bolt11,
+ PaymentHash: payment.PaymentHash,
+ WalletID: payment.WalletID,
+ }
+
+ response.ExternalPayments = append(response.ExternalPayments, externalPayment)
+ response.Summary.TotalExternalCount++
+
+ if paymentType == "incoming" {
+ response.Summary.ExternalIncoming += externalPayment.AmountSats
+ } else {
+ response.Summary.ExternalOutgoing += abs(externalPayment.AmountSats)
+ }
+ }
+ }
+ }
+
+ // Fetch internal transactions
+ var internalTxs []telegram.Transaction
+ s.Bot.DB.Transactions.Where("from_id = ? OR to_id = ?", userID, userID).
+ Order("time desc").
+ Limit(limit).Offset(offset).
+ Find(&internalTxs)
+
+ for _, tx := range internalTxs {
+ internalTx := InternalTransactionData{
+ ID: tx.ID,
+ Time: tx.Time.Format(time.RFC3339),
+ FromID: tx.FromId,
+ ToID: tx.ToId,
+ FromUser: tx.FromUser,
+ ToUser: tx.ToUser,
+ Type: tx.Type,
+ Amount: tx.Amount,
+ ChatID: tx.ChatID,
+ ChatName: tx.ChatName,
+ Memo: tx.Memo,
+ Success: tx.Success,
+ }
+
+ response.InternalTxs = append(response.InternalTxs, internalTx)
+ response.Summary.TotalInternalCount++
+
+ if tx.Success {
+ response.Summary.InternalVolume += tx.Amount
+ }
+ }
+
+ response.Summary.UniqueUsers = 1
+
+ // Respond in requested format
+ if outputFormat == "csv" {
+ writeCSVResponse(w, response)
+ return
+ }
+
+ w.Header().Set("Content-Type", "application/json")
+ w.WriteHeader(http.StatusOK)
+ json.NewEncoder(w).Encode(response)
+}
+
+// parseDate parses a date string and validates it falls within reasonable bounds.
+func parseDate(dateStr string) (time.Time, error) {
+ var t time.Time
+
+ // Try parsing as Unix timestamp first
+ if timestamp, err := strconv.ParseInt(dateStr, 10, 64); err == nil {
+ if timestamp < minValidTimestamp || timestamp > maxValidTimestamp {
+ return time.Time{}, fmt.Errorf("timestamp out of range: %d", timestamp)
+ }
+ return time.Unix(timestamp, 0), nil
+ }
+
+ // Try parsing as date string
+ layouts := []string{
+ "2006-01-02",
+ "2006-01-02T15:04:05",
+ time.RFC3339,
+ }
+
+ for _, layout := range layouts {
+ if parsed, err := time.Parse(layout, dateStr); err == nil {
+ t = parsed
+ break
+ }
+ }
+
+ if t.IsZero() {
+ return time.Time{}, fmt.Errorf("unsupported date format: %s", dateStr)
+ }
+
+ // Validate parsed date is within reasonable bounds
+ if t.Unix() < minValidTimestamp || t.Unix() > maxValidTimestamp {
+ return time.Time{}, fmt.Errorf("date out of range: %s", dateStr)
+ }
+
+ return t, nil
+}
+
+// Helper function to get absolute value
+func abs(n int64) int64 {
+ if n < 0 {
+ return -n
+ }
+ return n
+}
+
+// sanitizeCSVField prevents CSV formula injection by prefixing dangerous characters
+// with a single quote. Fields starting with =, +, -, @, tab, or carriage return
+// can be interpreted as formulas by spreadsheet software.
+func sanitizeCSVField(s string) string {
+ s = strings.ReplaceAll(s, "\n", " ")
+ s = strings.ReplaceAll(s, "\r", " ")
+ if len(s) > 0 {
+ switch s[0] {
+ case '=', '+', '-', '@', '\t':
+ return "'" + s
+ }
+ }
+ return s
+}
+
+// writeCSVResponse writes the analytics response as a CSV file
+func writeCSVResponse(w http.ResponseWriter, response TransactionAnalyticsResponse) {
+ w.Header().Set("Content-Type", "text/csv")
+ w.Header().Set("Content-Disposition", "attachment; filename=transactions.csv")
+ w.WriteHeader(http.StatusOK)
+
+ writer := csv.NewWriter(w)
+ defer writer.Flush()
+
+ // Write header
+ writer.Write([]string{
+ "source", "id", "time", "user_id", "username", "from_id", "from_user",
+ "to_id", "to_user", "type", "amount_sats", "fee_sats", "memo",
+ "payment_type", "pending", "success", "payment_hash", "chat_id", "chat_name",
+ })
+
+ // Write external payments
+ for _, p := range response.ExternalPayments {
+ writer.Write([]string{
+ "external",
+ p.CheckingID,
+ p.Timestamp,
+ strconv.FormatInt(p.UserID, 10),
+ sanitizeCSVField(p.Username),
+ "", "", "", "", // from/to fields not applicable
+ p.PaymentType,
+ strconv.FormatInt(p.AmountSats, 10),
+ strconv.FormatInt(p.FeeSats, 10),
+ sanitizeCSVField(p.Memo),
+ p.PaymentType,
+ strconv.FormatBool(p.Pending),
+ "", // success not applicable
+ p.PaymentHash,
+ "", "", // chat fields not applicable
+ })
+ }
+
+ // Write internal transactions
+ for _, t := range response.InternalTxs {
+ writer.Write([]string{
+ "internal",
+ strconv.FormatUint(uint64(t.ID), 10),
+ t.Time,
+ "", "", // user_id/username not applicable
+ strconv.FormatInt(t.FromID, 10),
+ sanitizeCSVField(t.FromUser),
+ strconv.FormatInt(t.ToID, 10),
+ sanitizeCSVField(t.ToUser),
+ sanitizeCSVField(t.Type),
+ strconv.FormatInt(t.Amount, 10),
+ "", // fee not applicable
+ sanitizeCSVField(t.Memo),
+ "", "", // payment_type/pending not applicable
+ strconv.FormatBool(t.Success),
+ "", // payment_hash not applicable
+ strconv.FormatInt(t.ChatID, 10),
+ sanitizeCSVField(t.ChatName),
+ })
+ }
+}
diff --git a/internal/api/invoice.go b/internal/api/invoice.go
new file mode 100644
index 00000000..0bd42018
--- /dev/null
+++ b/internal/api/invoice.go
@@ -0,0 +1,27 @@
+package api
+
+type BalanceResponse struct {
+ Balance int64 `json:"balance"`
+}
+
+type InvoiceStatusResponse struct {
+ State string `json:"state,omitempty"`
+ PaymentHash string `json:"payment_hash"`
+ Preimage int64 `json:"preimage"`
+}
+
+type CreateInvoiceResponse struct {
+ PaymentHash string `json:"payment_hash"`
+ PayRequest string `json:"pay_request"`
+ Preimage string `json:"preimage,omitempty"`
+}
+type CreateInvoiceRequest struct {
+ Memo string `json:"memo"`
+ Amount int64 `json:"amount"`
+ DescriptionHash string `json:"description_hash"`
+ UnhashedDescription string `json:"unhashed_description"`
+}
+
+type PayInvoiceRequest struct {
+ PayRequest string `json:"pay_req"`
+}
diff --git a/internal/api/lightning.go b/internal/api/lightning.go
new file mode 100644
index 00000000..bbdcb1d7
--- /dev/null
+++ b/internal/api/lightning.go
@@ -0,0 +1,193 @@
+package api
+
+import (
+ "encoding/json"
+ "fmt"
+ "net/http"
+ "time"
+
+ "github.com/LightningTipBot/LightningTipBot/internal"
+ "github.com/LightningTipBot/LightningTipBot/internal/lnbits"
+ "github.com/LightningTipBot/LightningTipBot/internal/telegram"
+ "github.com/LightningTipBot/LightningTipBot/internal/utils"
+ "github.com/gorilla/mux"
+ "github.com/r3labs/sse"
+)
+
+type Service struct {
+ Bot *telegram.TipBot
+ MemoCache *utils.Cache
+}
+
+type ErrorResponse struct {
+ Message string `json:"error"`
+}
+
+func RespondError(w http.ResponseWriter, message string) {
+ w.Header().Set("Content-Type", "application/json")
+ w.WriteHeader(http.StatusBadRequest)
+ json.NewEncoder(w).Encode(ErrorResponse{Message: message})
+}
+
+func (s Service) Balance(w http.ResponseWriter, r *http.Request) {
+ user := telegram.LoadUser(r.Context())
+ balance, err := s.Bot.GetUserBalance(user)
+ if err != nil {
+ RespondError(w, "balance check failed")
+ return
+ }
+
+ balanceResponse := BalanceResponse{
+ Balance: balance,
+ }
+
+ w.Header().Set("Content-Type", "application/json")
+ w.WriteHeader(http.StatusOK)
+ json.NewEncoder(w).Encode(balanceResponse)
+}
+
+func (s Service) CreateInvoice(w http.ResponseWriter, r *http.Request) {
+ user := telegram.LoadUser(r.Context())
+ var createInvoiceRequest CreateInvoiceRequest
+ err := json.NewDecoder(r.Body).Decode(&createInvoiceRequest)
+ if err != nil {
+ http.Error(w, err.Error(), http.StatusBadRequest)
+ return
+ }
+ invoice, err := user.Wallet.Invoice(
+ lnbits.InvoiceParams{
+ Amount: createInvoiceRequest.Amount,
+ Out: false,
+ DescriptionHash: createInvoiceRequest.DescriptionHash,
+ UnhashedDescription: createInvoiceRequest.UnhashedDescription,
+ Memo: createInvoiceRequest.Memo,
+ Webhook: internal.GetWebhookURL()},
+ s.Bot.Client)
+ if err != nil {
+ RespondError(w, "could not create invoice")
+ return
+ }
+
+ w.Header().Set("Content-Type", "application/json")
+ w.WriteHeader(http.StatusOK)
+ json.NewEncoder(w).Encode(invoice)
+}
+
+func (s Service) PayInvoice(w http.ResponseWriter, r *http.Request) {
+ user := telegram.LoadUser(r.Context())
+ var payInvoiceRequest PayInvoiceRequest
+ err := json.NewDecoder(r.Body).Decode(&payInvoiceRequest)
+ if err != nil {
+ http.Error(w, err.Error(), http.StatusBadRequest)
+ return
+ }
+ invoice, err := user.Wallet.Pay(lnbits.PaymentParams{Out: true, Bolt11: payInvoiceRequest.PayRequest}, s.Bot.Client)
+ if err != nil {
+ if s.Bot.ErrorLogger != nil {
+ s.Bot.ErrorLogger.LogAPIError(err, "PayInvoice API", user.Telegram)
+ }
+ RespondError(w, "could not pay invoice: "+err.Error())
+ return
+ }
+
+ payment, _ := s.Bot.Client.Payment(*user.Wallet, invoice.PaymentHash)
+ if err != nil {
+ // we assume that it's paid since thre was no error earlier
+ payment.Paid = true
+ }
+
+ w.Header().Set("Content-Type", "application/json")
+ w.WriteHeader(http.StatusOK)
+ json.NewEncoder(w).Encode(payment)
+}
+
+func (s Service) PaymentStatus(w http.ResponseWriter, r *http.Request) {
+ user := telegram.LoadUser(r.Context())
+ payment_hash := mux.Vars(r)["payment_hash"]
+ payment, err := s.Bot.Client.Payment(*user.Wallet, payment_hash)
+ if err != nil {
+ RespondError(w, "could not get payment")
+ return
+ }
+ w.Header().Set("Content-Type", "application/json")
+ w.WriteHeader(http.StatusOK)
+ json.NewEncoder(w).Encode(payment)
+}
+
+// InvoiceStatus
+func (s Service) InvoiceStatus(w http.ResponseWriter, r *http.Request) {
+ user := telegram.LoadUser(r.Context())
+ payment_hash := mux.Vars(r)["payment_hash"]
+ user.Wallet = &lnbits.Wallet{}
+ payment, err := s.Bot.Client.Payment(*user.Wallet, payment_hash)
+ if err != nil {
+ RespondError(w, "could not get invoice")
+ return
+ }
+ w.Header().Set("Content-Type", "application/json")
+ w.WriteHeader(http.StatusOK)
+ json.NewEncoder(w).Encode(payment)
+}
+
+type InvoiceStream struct {
+ CheckingID string `json:"checking_id"`
+ Pending bool `json:"pending"`
+ Amount int `json:"amount"`
+ Fee int `json:"fee"`
+ Memo string `json:"memo"`
+ Time int `json:"time"`
+ Bolt11 string `json:"bolt11"`
+ Preimage string `json:"preimage"`
+ PaymentHash string `json:"payment_hash"`
+ Extra struct {
+ } `json:"extra"`
+ WalletID string `json:"wallet_id"`
+ Webhook string `json:"webhook"`
+ WebhookStatus interface{} `json:"webhook_status"`
+}
+
+func (s Service) InvoiceStream(w http.ResponseWriter, r *http.Request) {
+ user := telegram.LoadUser(r.Context())
+ flusher, ok := w.(http.Flusher)
+ if !ok {
+ http.Error(w, "Streaming unsupported!", http.StatusInternalServerError)
+ return
+ }
+ client := sse.NewClient(fmt.Sprintf("%s/api/v1/payments/sse", internal.Configuration.Lnbits.Url))
+ client.Connection.Transport = &http.Transport{DisableCompression: true}
+ client.Headers = map[string]string{"X-Api-Key": user.Wallet.Inkey}
+ c := make(chan *sse.Event)
+ err := client.SubscribeChan("", c)
+ if err != nil {
+ http.Error(w, "Streaming unsupported!", http.StatusInternalServerError)
+ return
+ }
+out:
+ for {
+ select {
+ case msg := <-c:
+ select {
+ case <-r.Context().Done():
+ client.Unsubscribe(c)
+ break out
+ default:
+ written, err := fmt.Fprintf(w, "event: %s\n", string(msg.Event))
+ if err != nil || written == 0 {
+ break out
+ }
+ written, err = fmt.Fprintf(w, "data: %s\n", string(msg.Data))
+ if err != nil || written == 0 {
+ break out
+ }
+ written, err = fmt.Fprint(w, "\n")
+ if err != nil || written == 0 {
+ break out
+ }
+ flusher.Flush()
+ }
+ }
+
+ }
+ close(c)
+ time.Sleep(time.Second * 5)
+}
diff --git a/internal/api/middleware.go b/internal/api/middleware.go
new file mode 100644
index 00000000..c4c87299
--- /dev/null
+++ b/internal/api/middleware.go
@@ -0,0 +1,297 @@
+package api
+
+import (
+ "context"
+ "crypto/hmac"
+ "crypto/sha256"
+ "encoding/base64"
+ "encoding/hex"
+ "fmt"
+ "io"
+ "net/http"
+ "net/http/httputil"
+ "strconv"
+ "strings"
+ "time"
+
+ "github.com/LightningTipBot/LightningTipBot/internal"
+ "github.com/LightningTipBot/LightningTipBot/internal/lnbits"
+ "github.com/LightningTipBot/LightningTipBot/internal/telegram"
+ "gorm.io/gorm"
+
+ log "github.com/sirupsen/logrus"
+)
+
+func LoggingMiddleware(prefix string, next http.HandlerFunc) http.HandlerFunc {
+ return func(w http.ResponseWriter, r *http.Request) {
+ log.Tracef("[%s] %s %s", prefix, r.Method, r.URL.Path)
+ log.Tracef("[%s]\n%s", prefix, dump(r))
+ r.BasicAuth()
+ next.ServeHTTP(w, r)
+ }
+}
+
+type AuthType struct {
+ Type string
+ Decoder func(s string) ([]byte, error)
+}
+
+var AuthTypeBasic = AuthType{Type: "Basic"}
+var AuthTypeBearerBase64 = AuthType{Type: "Bearer", Decoder: base64.StdEncoding.DecodeString}
+var AuthTypeNone = AuthType{}
+
+// invoice key or admin key requirement
+type AccessKeyType struct {
+ Type string
+}
+
+var AccessKeyTypeInvoice = AccessKeyType{Type: "invoice"}
+var AccessKeyTypeAdmin = AccessKeyType{Type: "admin"}
+var AccessKeyTypeNone = AccessKeyType{Type: "none"} // no authorization required
+
+func AuthorizationMiddleware(database *gorm.DB, authType AuthType, accessType AccessKeyType, next http.HandlerFunc) http.HandlerFunc {
+ return func(w http.ResponseWriter, r *http.Request) {
+ if accessType.Type == "none" {
+ next.ServeHTTP(w, r)
+ return
+ }
+ auth := r.Header.Get("Authorization")
+ // check if the user is banned
+ if auth == "" {
+ w.WriteHeader(401)
+ log.Warn("[api] no auth")
+ return
+ }
+ _, password, ok := parseAuth(authType, auth)
+ if !ok {
+ w.WriteHeader(401)
+ return
+ }
+ // first we make sure that the password is not already "banned_"
+ if strings.Contains(password, "_") || strings.HasPrefix(password, "banned_") {
+ w.WriteHeader(401)
+ log.Warnf("[api] Banned user %s. Not forwarding request", password)
+ return
+ }
+ // then we check whether the "normal" password provided is in the database (it should be not if the user is banned)
+
+ user := &lnbits.User{}
+ var tx *gorm.DB
+ if accessType.Type == "admin" {
+ tx = database.Where("wallet_adminkey = ? COLLATE NOCASE", password).First(user)
+ } else if accessType.Type == "invoice" {
+ tx = database.Where("wallet_inkey = ? OR wallet_adminkey = ? COLLATE NOCASE", password, password).First(user)
+ } else {
+ log.Errorf("[api] route without access type")
+ w.WriteHeader(401)
+ return
+ }
+ if tx.Error != nil {
+ log.Warnf("[api] could not load access key: %v", tx.Error)
+ w.WriteHeader(401)
+ return
+ }
+
+ log.Debugf("[api] User: %s Endpoint: %s %s %s", telegram.GetUserStr(user.Telegram), r.Method, r.URL.Path, r.URL.RawQuery)
+ r = r.WithContext(context.WithValue(r.Context(), "user", user))
+ next.ServeHTTP(w, r)
+ }
+}
+
+// parseAuth parses an HTTP Basic Authentication string.
+// "Bearer QWxhZGRpbjpvcGVuIHNlc2FtZQ==" returns ("Aladdin", "open sesame", true).
+func parseAuth(authType AuthType, auth string) (username, password string, ok bool) {
+ parse := func(prefix string) (username, password string, ok bool) {
+ // Case insensitive prefix match. See Issue 22736.
+ if len(auth) < len(prefix) || !strings.EqualFold(auth[:len(prefix)], prefix) {
+ return
+ }
+ if authType.Decoder != nil {
+ c, err := authType.Decoder(auth[len(prefix):])
+ if err != nil {
+ return
+ }
+ cs := string(c)
+ s := strings.IndexByte(cs, ':')
+ if s < 0 {
+ return
+ }
+ return cs[:s], cs[s+1:], true
+ }
+ return auth[len(prefix):], auth[len(prefix):], true
+
+ }
+ return parse(fmt.Sprintf("%s ", authType.Type))
+
+}
+
+func dump(r *http.Request) string {
+ x, err := httputil.DumpRequest(r, true)
+ if err != nil {
+ return ""
+ }
+ return string(x)
+}
+
+// WalletHMACMiddleware validates HMAC signatures for wallet-based API endpoints
+// It identifies the sending wallet by validating the signature against each whitelisted wallet's secret
+func WalletHMACMiddleware(next http.HandlerFunc) http.HandlerFunc {
+ return func(w http.ResponseWriter, r *http.Request) {
+ // Get timestamp from header for replay attack prevention
+ timestampStr := r.Header.Get("X-Timestamp")
+ if timestampStr == "" {
+ log.Warn("Missing timestamp in wallet API request")
+ http.Error(w, "Missing timestamp", http.StatusUnauthorized)
+ return
+ }
+
+ // Parse timestamp
+ timestamp, err := strconv.ParseInt(timestampStr, 10, 64)
+ if err != nil {
+ log.Warn("Invalid timestamp format in wallet API request")
+ http.Error(w, "Invalid timestamp", http.StatusBadRequest)
+ return
+ }
+
+ // Check if request is not too old (prevent replay attacks)
+ now := time.Now().Unix()
+ tolerance := internal.Configuration.API.Send.TimestampTolerance
+ if tolerance == 0 {
+ tolerance = 300 // Default 5 minutes
+ }
+
+ if now-timestamp > tolerance {
+ log.Warnf("Request timestamp too old (age: %d seconds, tolerance: %d)", now-timestamp, tolerance)
+ http.Error(w, "Request expired", http.StatusUnauthorized)
+ return
+ }
+
+ // Get signature from header
+ signature := r.Header.Get("X-HMAC-Signature")
+ if signature == "" {
+ log.Warn("Missing HMAC signature in wallet API request")
+ http.Error(w, "Missing signature", http.StatusUnauthorized)
+ return
+ }
+
+ // Read request body
+ body, err := io.ReadAll(r.Body)
+ if err != nil {
+ log.Error("Failed to read request body for HMAC verification: ", err)
+ http.Error(w, "Failed to read request", http.StatusBadRequest)
+ return
+ }
+
+ // Restore body for next handler
+ r.Body = io.NopCloser(strings.NewReader(string(body)))
+
+ // Create message to sign: METHOD + PATH + TIMESTAMP + BODY
+ message := fmt.Sprintf("%s%s%s%s", r.Method, r.URL.Path, timestampStr, string(body))
+
+ // Try to validate signature against each whitelisted wallet
+ var authenticatedWallet string
+ for walletID, wallet := range internal.Configuration.API.Send.WhitelistedWallets {
+ expectedSignature := calculateHMAC(message, wallet.HMACSecret)
+ if hmac.Equal([]byte(signature), []byte(expectedSignature)) {
+ authenticatedWallet = walletID
+ log.Debugf("HMAC signature verified for wallet: %s", walletID)
+ break
+ }
+ }
+
+ if authenticatedWallet == "" {
+ log.Warn("HMAC signature verification failed - no matching wallet found")
+ http.Error(w, "Invalid signature", http.StatusUnauthorized)
+ return
+ }
+
+ // Add authenticated wallet info to request context
+ ctx := context.WithValue(r.Context(), "authenticated_wallet", authenticatedWallet)
+ r = r.WithContext(ctx)
+
+ log.Debugf("Wallet API request authenticated for wallet: %s", authenticatedWallet)
+ next.ServeHTTP(w, r)
+ }
+}
+
+// calculateHMAC calculates HMAC-SHA256 signature
+func calculateHMAC(message, secret string) string {
+ h := hmac.New(sha256.New, []byte(secret))
+ h.Write([]byte(message))
+ return hex.EncodeToString(h.Sum(nil))
+}
+
+// GenerateHMACSignature helper function for clients
+func GenerateHMACSignature(method, path, timestamp, body, secret string) string {
+ message := fmt.Sprintf("%s%s%s%s", method, path, timestamp, body)
+ return calculateHMAC(message, secret)
+}
+
+// AnalyticsHMACMiddleware validates HMAC signatures for analytics API endpoints
+func AnalyticsHMACMiddleware(next http.HandlerFunc) http.HandlerFunc {
+ return func(w http.ResponseWriter, r *http.Request) {
+ // Get timestamp from header
+ timestampStr := r.Header.Get("X-Timestamp")
+ if timestampStr == "" {
+ log.Warn("[Analytics] Missing timestamp in request")
+ http.Error(w, "Missing timestamp", http.StatusUnauthorized)
+ return
+ }
+
+ timestamp, err := strconv.ParseInt(timestampStr, 10, 64)
+ if err != nil {
+ log.Warn("[Analytics] Invalid timestamp format")
+ http.Error(w, "Invalid timestamp", http.StatusBadRequest)
+ return
+ }
+
+ // Check if request is not too old (prevent replay attacks)
+ now := time.Now().Unix()
+ tolerance := internal.Configuration.API.Analytics.TimestampTolerance
+ if tolerance == 0 {
+ tolerance = 300
+ }
+
+ if now-timestamp > tolerance {
+ log.Warnf("[Analytics] Request timestamp too old (age: %d seconds)", now-timestamp)
+ http.Error(w, "Request expired", http.StatusUnauthorized)
+ return
+ }
+
+ // Get signature from header
+ signature := r.Header.Get("X-HMAC-Signature")
+ if signature == "" {
+ log.Warn("[Analytics] Missing HMAC signature")
+ http.Error(w, "Missing signature", http.StatusUnauthorized)
+ return
+ }
+
+ // For GET requests, use query string as the body component
+ bodyComponent := r.URL.RawQuery
+
+ // Create message to sign: METHOD + PATH + TIMESTAMP + QUERY
+ message := fmt.Sprintf("%s%s%s%s", r.Method, r.URL.Path, timestampStr, bodyComponent)
+
+ // Try to validate signature against each configured analytics API key
+ var authenticatedKey string
+ for keyID, apiKey := range internal.Configuration.API.Analytics.APIKeys {
+ expectedSignature := calculateHMAC(message, apiKey.HMACSecret)
+ if hmac.Equal([]byte(signature), []byte(expectedSignature)) {
+ authenticatedKey = keyID
+ log.Debugf("[Analytics] HMAC verified for key: %s (%s)", keyID, apiKey.Name)
+ break
+ }
+ }
+
+ if authenticatedKey == "" {
+ log.Warn("[Analytics] HMAC signature verification failed")
+ http.Error(w, "Invalid signature", http.StatusUnauthorized)
+ return
+ }
+
+ ctx := context.WithValue(r.Context(), "analytics_api_key", authenticatedKey)
+ r = r.WithContext(ctx)
+
+ next.ServeHTTP(w, r)
+ }
+}
diff --git a/internal/api/pending_transaction.go b/internal/api/pending_transaction.go
new file mode 100644
index 00000000..43147b59
--- /dev/null
+++ b/internal/api/pending_transaction.go
@@ -0,0 +1,163 @@
+package api
+
+import (
+ "fmt"
+ "time"
+
+ "github.com/LightningTipBot/LightningTipBot/internal/lnbits"
+ "github.com/LightningTipBot/LightningTipBot/internal/storage"
+ "github.com/LightningTipBot/LightningTipBot/internal/telegram"
+ log "github.com/sirupsen/logrus"
+)
+
+// PendingTransaction represents a transaction awaiting admin approval
+type PendingTransaction struct {
+ *storage.Base
+ ID string `json:"id"`
+ FromUser *lnbits.User `json:"from_user" gorm:"-"`
+ ToUser *lnbits.User `json:"to_user" gorm:"-"`
+ FromUsername string `json:"from_username"`
+ ToUsername string `json:"to_username"`
+ Amount int64 `json:"amount"`
+ Memo string `json:"memo"`
+ RequestTimestamp time.Time `json:"request_timestamp"`
+ Status string `json:"status"` // "pending", "approved", "rejected", "expired"
+ ApprovedBy string `json:"approved_by,omitempty"`
+ ApprovalTime *time.Time `json:"approval_time,omitempty"`
+ ExpiryTime time.Time `json:"expiry_time"`
+ ClientIP string `json:"client_ip"`
+ OriginalRequest *SendRequest `json:"original_request" gorm:"-"`
+}
+
+const (
+ PendingTransactionExpiry = 24 * time.Hour // Pending transactions expire after 24 hours
+ StatusPending = "pending"
+ StatusApproved = "approved"
+ StatusRejected = "rejected"
+ StatusExpired = "expired"
+ StatusExecuted = "executed"
+)
+
+// NewPendingTransaction creates a new pending transaction
+func NewPendingTransaction(req *SendRequest, fromUser, toUser *lnbits.User, clientIP string) *PendingTransaction {
+ fromUsername := ""
+ if fromUser != nil {
+ fromUsername = fromUser.Telegram.Username
+ }
+
+ id := fmt.Sprintf("pending-%s-%s-%d-%d", fromUsername, req.To, req.Amount, time.Now().Unix())
+
+ return &PendingTransaction{
+ Base: storage.New(storage.ID(id)),
+ ID: id,
+ FromUser: fromUser,
+ ToUser: toUser,
+ FromUsername: fromUsername,
+ ToUsername: req.To,
+ Amount: req.Amount,
+ Memo: req.Memo,
+ RequestTimestamp: time.Now(),
+ Status: StatusPending,
+ ExpiryTime: time.Now().Add(PendingTransactionExpiry),
+ ClientIP: clientIP,
+ OriginalRequest: req,
+ }
+}
+
+// IsExpired checks if the pending transaction has expired
+func (pt *PendingTransaction) IsExpired() bool {
+ return time.Now().After(pt.ExpiryTime)
+}
+
+// CanBeApproved checks if the transaction can still be approved
+func (pt *PendingTransaction) CanBeApproved() bool {
+ return pt.Status == StatusPending && !pt.IsExpired()
+}
+
+// Approve marks the transaction as approved
+func (pt *PendingTransaction) Approve(approvedBy string) error {
+ if !pt.CanBeApproved() {
+ return fmt.Errorf("transaction cannot be approved: status=%s, expired=%v", pt.Status, pt.IsExpired())
+ }
+
+ now := time.Now()
+ pt.Status = StatusApproved
+ pt.ApprovedBy = approvedBy
+ pt.ApprovalTime = &now
+
+ return nil
+}
+
+// Reject marks the transaction as rejected
+func (pt *PendingTransaction) Reject(rejectedBy string) error {
+ if pt.Status != StatusPending {
+ return fmt.Errorf("transaction cannot be rejected: status=%s", pt.Status)
+ }
+
+ now := time.Now()
+ pt.Status = StatusRejected
+ pt.ApprovedBy = rejectedBy // Store who rejected it
+ pt.ApprovalTime = &now
+
+ return nil
+}
+
+// Execute marks the transaction as executed (actually processed)
+func (pt *PendingTransaction) Execute() error {
+ if pt.Status != StatusApproved {
+ return fmt.Errorf("transaction cannot be executed: status=%s", pt.Status)
+ }
+
+ pt.Status = StatusExecuted
+ return nil
+}
+
+// SaveToDB saves the pending transaction to the database
+func (pt *PendingTransaction) SaveToDB(bot *telegram.TipBot) error {
+ return pt.Set(pt, bot.Bunt)
+}
+
+// LoadFromDB loads a pending transaction from the database
+func LoadPendingTransaction(id string, bot *telegram.TipBot) (*PendingTransaction, error) {
+ pt := &PendingTransaction{Base: storage.New(storage.ID(id))}
+ sn, err := pt.Get(pt, bot.Bunt)
+ if err != nil {
+ return nil, err
+ }
+
+ pendingTx := sn.(*PendingTransaction)
+
+ // Load user objects from usernames
+ fromUser, err := telegram.GetUserByTelegramUsername(pendingTx.FromUsername, *bot)
+ if err != nil {
+ log.Warnf("[ADMIN APPROVAL] Could not load from user @%s: %v", pendingTx.FromUsername, err)
+ // Continue with nil user - this will be handled in the calling functions
+ } else {
+ pendingTx.FromUser = fromUser
+ }
+
+ toUser, err := telegram.GetUserByTelegramUsername(pendingTx.ToUsername, *bot)
+ if err != nil {
+ log.Warnf("[ADMIN APPROVAL] Could not load to user @%s: %v", pendingTx.ToUsername, err)
+ // Continue with nil user - this will be handled in the calling functions
+ } else {
+ pendingTx.ToUser = toUser
+ }
+
+ return pendingTx, nil
+}
+
+// CleanupExpiredTransactions removes expired pending transactions
+func CleanupExpiredTransactions(bot *telegram.TipBot) error {
+ // This would need to be implemented to iterate through all pending transactions
+ // and mark expired ones as expired. For now, we'll just log the intent.
+ log.Debug("[ADMIN APPROVAL] Cleanup expired transactions called")
+
+ // Implementation would involve:
+ // 1. Query all pending transactions from database
+ // 2. Check which ones are expired
+ // 3. Update their status to "expired"
+ // 4. Optionally notify the original requester
+
+ return nil
+}
diff --git a/internal/api/proxy.go b/internal/api/proxy.go
new file mode 100644
index 00000000..acf32773
--- /dev/null
+++ b/internal/api/proxy.go
@@ -0,0 +1,73 @@
+package api
+
+import (
+ "fmt"
+ "io"
+ "net/http"
+ "net/url"
+ "time"
+
+ log "github.com/sirupsen/logrus"
+)
+
+func Proxy(wr http.ResponseWriter, req *http.Request, rawUrl string) error {
+
+ client := &http.Client{Timeout: time.Second * 30}
+
+ //http: Request.RequestURI can't be set in client requests.
+ //http://golang.org/src/pkg/net/http/client.go
+ req.RequestURI = ""
+
+ u, err := url.Parse(rawUrl)
+ if err != nil {
+ http.Error(wr, "Server Error", http.StatusInternalServerError)
+ log.Println("ServeHTTP:", err)
+ return err
+ }
+ req.URL.Host = u.Host
+ req.URL.Scheme = u.Scheme
+ req.Host = req.URL.Host
+ resp, err := client.Do(req)
+ if err != nil {
+ http.Error(wr, "Server Error", http.StatusInternalServerError)
+ log.Println("ServeHTTP:", err)
+ return err
+ }
+ defer resp.Body.Close()
+ log.Tracef("[Proxy] Proxy request status: %s", resp.Status)
+ if resp.StatusCode > 300 {
+ return fmt.Errorf("invalid response")
+ }
+ delHopHeaders(resp.Header)
+ copyHeader(wr.Header(), resp.Header)
+ wr.WriteHeader(resp.StatusCode)
+ _, err = io.Copy(wr, resp.Body)
+ return err
+}
+
+// Hop-by-hop headers. These are removed when sent to the backend.
+// http://www.w3.org/Protocols/rfc2616/rfc2616-sec13.html
+var hopHeaders = []string{
+ "Connection",
+ "Keep-Alive",
+ "Proxy-Authenticate",
+ "Proxy-Authorization",
+ "Te", // canonicalized version of "TE"
+ "Trailers",
+ "Transfer-Encoding",
+ "Upgrade",
+}
+
+func copyHeader(dst, src http.Header) {
+ for k, vv := range src {
+ for _, v := range vv {
+ dst.Add(k, v)
+ }
+ }
+}
+
+func delHopHeaders(header http.Header) {
+ for _, h := range hopHeaders {
+ header.Del(h)
+ }
+}
diff --git a/internal/api/send.go b/internal/api/send.go
new file mode 100644
index 00000000..0c3c353f
--- /dev/null
+++ b/internal/api/send.go
@@ -0,0 +1,387 @@
+package api
+
+import (
+ "encoding/json"
+ "fmt"
+ "net"
+ "net/http"
+ "regexp"
+ "strconv"
+ "strings"
+
+ "github.com/LightningTipBot/LightningTipBot/internal/lnbits"
+ "github.com/LightningTipBot/LightningTipBot/internal/str"
+ "github.com/LightningTipBot/LightningTipBot/internal/telegram"
+ "github.com/LightningTipBot/LightningTipBot/internal/thirdparty"
+ "github.com/LightningTipBot/LightningTipBot/internal/utils"
+ "github.com/LightningTipBot/LightningTipBot/pkg/lightning"
+ log "github.com/sirupsen/logrus"
+)
+
+// SendRequest represents the JSON request for the send API
+type SendRequest struct {
+ To string `json:"to"` // Telegram username (without @), Telegram ID, or wallet ID
+ Amount int64 `json:"amount"` // Amount in satoshis
+ Memo string `json:"memo"` // Optional memo
+}
+
+// SendResponse represents the JSON response for the send API
+type SendResponse struct {
+ Success bool `json:"success"`
+ TransactionHash string `json:"transaction_hash,omitempty"`
+ Message string `json:"message"`
+ FromUser string `json:"from_user"`
+ ToUser string `json:"to_user"`
+ Amount int64 `json:"amount"`
+ AmountLKR string `json:"amount_lkr,omitempty"` // LKR conversion
+ Memo string `json:"memo,omitempty"`
+}
+
+// InternalNetworkMiddleware restricts access to internal network IPs (configurable)
+func InternalNetworkMiddleware(next http.HandlerFunc) http.HandlerFunc {
+ return func(w http.ResponseWriter, r *http.Request) {
+ clientIP := getClientIP(r)
+
+ // Parse the client IP
+ ip := net.ParseIP(clientIP)
+ if ip == nil {
+ log.Warnf("[api/send] Invalid client IP: %s", clientIP)
+ http.Error(w, "Invalid client IP", http.StatusForbidden)
+ return
+ }
+
+ // Check if IP is in the internal network range defined in config
+ _, internalNet, err := net.ParseCIDR(GetInternalNetworkCIDR())
+ if err != nil {
+ log.Errorf("[api/send] Invalid internal network CIDR configuration: %s, error: %v", GetInternalNetworkCIDR(), err)
+ http.Error(w, "Internal server configuration error", http.StatusInternalServerError)
+ return
+ }
+
+ if !internalNet.Contains(ip) {
+ log.Warnf("[api/send] Access denied for IP: %s (not in internal network %s)", clientIP, GetInternalNetworkCIDR())
+ http.Error(w, "Access denied: Internal network only", http.StatusForbidden)
+ return
+ }
+
+ log.Debugf("[api/send] Access granted for internal IP: %s", clientIP)
+ next.ServeHTTP(w, r)
+ }
+}
+
+// getClientIP extracts the real client IP from the request
+func getClientIP(r *http.Request) string {
+ // Check X-Forwarded-For header first
+ if xff := r.Header.Get("X-Forwarded-For"); xff != "" {
+ // X-Forwarded-For can contain multiple IPs, take the first one
+ if ips := strings.Split(xff, ","); len(ips) > 0 {
+ return strings.TrimSpace(ips[0])
+ }
+ }
+
+ // Check X-Real-IP header
+ if xri := r.Header.Get("X-Real-IP"); xri != "" {
+ return xri
+ }
+
+ // Fall back to RemoteAddr
+ ip, _, err := net.SplitHostPort(r.RemoteAddr)
+ if err != nil {
+ return r.RemoteAddr
+ }
+ return ip
+}
+
+// isTelegramID checks if the given string is a valid Telegram ID (numeric)
+func isTelegramID(identifier string) bool {
+ // Remove @ prefix if present
+ identifier = strings.TrimPrefix(identifier, "@")
+ // Check if it's all digits and has reasonable length for Telegram ID
+ match, _ := regexp.MatchString(`^[0-9]{5,15}$`, identifier)
+ return match
+}
+
+// Send handles the /api/send endpoint for programmatic Bitcoin Lightning payments
+func (s Service) Send(w http.ResponseWriter, r *http.Request) {
+ var req SendRequest
+ err := json.NewDecoder(r.Body).Decode(&req)
+ if err != nil {
+ log.Errorf("[api/send] Invalid JSON request: %v", err)
+ RespondError(w, "Invalid JSON request")
+ return
+ }
+
+ // Get authenticated wallet from context (set by WalletHMACMiddleware)
+ authenticatedWallet := r.Context().Value("authenticated_wallet")
+ if authenticatedWallet == nil {
+ log.Error("[api/send] No authenticated wallet found in request context")
+ RespondError(w, "Authentication failed")
+ return
+ }
+
+ walletID := authenticatedWallet.(string)
+ wallet, exists := GetWhitelistedWallets()[walletID]
+ if !exists {
+ log.Errorf("[api/send] Authenticated wallet %s not found in configuration", walletID)
+ RespondError(w, "Invalid wallet configuration")
+ return
+ }
+
+ fromUsername := wallet.Username
+
+ // Validate request
+ if req.To == "" {
+ RespondError(w, "Missing 'to' field")
+ return
+ }
+ if req.Amount <= GetMinAPITransactionAmount() {
+ RespondError(w, fmt.Sprintf("Amount must be greater than %s", thirdparty.FormatSatsWithLKR(GetMinAPITransactionAmount())))
+ return
+ }
+ if req.Amount > GetMaxAPITransactionAmount() {
+ RespondError(w, fmt.Sprintf("Amount cannot exceed %s", thirdparty.FormatSatsWithLKR(GetMaxAPITransactionAmount())))
+ return
+ }
+
+ // Check if amount requires admin approval
+ requiresApproval := req.Amount > GetAdminApprovalThreshold()
+ if len(req.Memo) > GetMaxMemoLength() {
+ RespondError(w, fmt.Sprintf("Memo cannot exceed %d characters", GetMaxMemoLength()))
+ return
+ }
+
+ if req.Memo != "" {
+ memoLockKey := fmt.Sprintf("api_send_memo_%s", req.Memo)
+
+ // Try to acquire lock first to prevent concurrent processing
+ if success := s.MemoCache.SetNX(memoLockKey, "locked"); !success {
+ log.Warnf("[api/send] Transaction with memo '%s' is already processing", req.Memo)
+ RespondError(w, fmt.Sprintf("Transaction with memo '%s' is already processing", req.Memo))
+ return
+ }
+ // Unlock when done
+ defer s.MemoCache.Delete(memoLockKey)
+
+ // Check if transaction with this memo already exists in database
+ // We search for the memo in the transaction memo field
+ // The stored memo format is: "💸 API Send from @User to @User. Memo: "
+ // So we search for the suffix "Memo: "
+ var count int64
+ memoSearch := fmt.Sprintf("%%Memo: %s", req.Memo)
+ err := s.Bot.DB.Transactions.Model(&telegram.Transaction{}).Where("memo LIKE ? AND success = ?", memoSearch, true).Count(&count).Error
+ if err != nil {
+ log.Errorf("[api/send] Database error checking for duplicate memo: %v", err)
+ // Continue but log error - fail open or closed? Let's fail closed for safety
+ RespondError(w, "Internal server error checking transaction history")
+ return
+ }
+ if count > 0 {
+ log.Warnf("[api/send] Transaction with memo '%s' already completed", req.Memo)
+ RespondError(w, fmt.Sprintf("Transaction with memo '%s' already completed", req.Memo))
+ return
+ }
+ }
+
+ // Clean usernames (remove @ if present)
+ toIdentifier := strings.TrimPrefix(req.To, "@")
+
+ // Get the sender user
+ fromUser, err := telegram.GetUserByTelegramUsername(fromUsername, *s.Bot)
+ if err != nil {
+ log.Errorf("[api/send] Could not find sender user %s: %v", fromUsername, err)
+ RespondError(w, fmt.Sprintf("Sender '@%s' not found or has no wallet", fromUsername))
+ return
+ }
+
+ // Check sender's available balance (wallet balance - pot balance)
+ balance, err := s.Bot.GetUserAvailableBalance(fromUser)
+ if err != nil {
+ log.Errorf("[api/send] Could not get available balance for %s: %v", fromUsername, err)
+ RespondError(w, "Could not check sender balance")
+ return
+ }
+
+ if balance < req.Amount {
+ log.Warnf("[api/send] Insufficient available balance for %s: %d < %d", fromUsername, balance, req.Amount)
+ RespondError(w, fmt.Sprintf("Insufficient balance: %s available, %s required", thirdparty.FormatSatsWithLKR(balance), thirdparty.FormatSatsWithLKR(req.Amount)))
+ return
+ }
+
+ // Check if 'to' is a Lightning address
+ if lightning.IsLightningAddress(toIdentifier) {
+ log.Infof("[api/send] Sending to Lightning address: %s", toIdentifier)
+ err = s.sendToLightningAddress(fromUser, toIdentifier, req.Amount, req.Memo)
+ if err != nil {
+ log.Errorf("[api/send] Lightning address payment failed: %v", err)
+ RespondError(w, fmt.Sprintf("Lightning address payment failed: %v", err))
+ return
+ }
+
+ response := SendResponse{
+ Success: true,
+ Message: "Payment sent successfully to Lightning address",
+ FromUser: fromUsername,
+ ToUser: toIdentifier,
+ Amount: req.Amount,
+ AmountLKR: getLKRValue(req.Amount),
+ Memo: req.Memo,
+ }
+
+ w.Header().Set("Content-Type", "application/json")
+ w.WriteHeader(http.StatusOK)
+ json.NewEncoder(w).Encode(response)
+ return
+ }
+
+ // Try to find recipient by Telegram username or ID
+ var toUser *lnbits.User
+ if isTelegramID(toIdentifier) {
+ // It's a Telegram ID
+ telegramID, err := strconv.ParseInt(toIdentifier, 10, 64)
+ if err != nil {
+ log.Errorf("[api/send] Invalid Telegram ID %s: %v", toIdentifier, err)
+ RespondError(w, fmt.Sprintf("Invalid Telegram ID '%s'", toIdentifier))
+ return
+ }
+ toUser, err = telegram.GetUserByTelegramID(telegramID, *s.Bot)
+ if err != nil {
+ log.Errorf("[api/send] Could not find recipient user with ID %d: %v", telegramID, err)
+ RespondError(w, fmt.Sprintf("Recipient '%s' not found or has no wallet", toIdentifier))
+ return
+ }
+ log.Infof("[api/send] Found recipient by Telegram ID: %d", telegramID)
+ } else {
+ // It's a Telegram username
+ toUser, err = telegram.GetUserByTelegramUsername(toIdentifier, *s.Bot)
+ if err != nil {
+ log.Errorf("[api/send] Could not find recipient user %s: %v", toIdentifier, err)
+ RespondError(w, fmt.Sprintf("Recipient '@%s' not found or has no wallet", toIdentifier))
+ return
+ }
+ log.Infof("[api/send] Found recipient by username: %s", toIdentifier)
+ }
+
+ // Check if trying to send to self
+ if fromUser.ID == toUser.ID {
+ RespondError(w, "Cannot send to yourself")
+ return
+ }
+
+ // Check if amount requires admin approval
+ if requiresApproval {
+ log.Infof("[api/send] Large transaction requires admin approval: %s -> %s (%d sat(s))", fromUsername, toIdentifier, req.Amount)
+
+ // Create pending transaction
+ clientIP := getClientIP(r)
+ pendingTx := NewPendingTransaction(&req, fromUser, toUser, clientIP)
+
+ // Save to database
+ err = pendingTx.SaveToDB(s.Bot)
+ if err != nil {
+ log.Errorf("[api/send] Failed to save pending transaction: %v", err)
+ RespondError(w, "Failed to create pending transaction")
+ return
+ }
+
+ // Send approval request using Telegram callback buttons (same as /send command)
+ err = telegram.CreateAPIApprovalRequest(s.Bot, fromUser, toIdentifier, req.Amount, req.Memo, pendingTx.ID, clientIP)
+ if err != nil {
+ log.Warnf("[api/send] Failed to send approval request: %v", err)
+ }
+
+ response := SendResponse{
+ Success: false,
+ Message: fmt.Sprintf("Transaction requires admin approval (amount: %s > threshold: %s). Approval request sent to you via Telegram. Transaction ID: %s",
+ thirdparty.FormatSatsWithLKR(req.Amount), thirdparty.FormatSatsWithLKR(GetAdminApprovalThreshold()), pendingTx.ID),
+ FromUser: fromUsername,
+ ToUser: toIdentifier,
+ Amount: req.Amount,
+ AmountLKR: getLKRValue(req.Amount),
+ Memo: req.Memo,
+ }
+
+ w.Header().Set("Content-Type", "application/json")
+ w.WriteHeader(http.StatusAccepted) // 202 Accepted - request received but needs approval
+ json.NewEncoder(w).Encode(response)
+ return
+ }
+
+ // Create transaction memo
+ fromUserStr := telegram.GetUserStr(fromUser.Telegram)
+ toUserStr := telegram.GetUserStr(toUser.Telegram)
+ transactionMemo := fmt.Sprintf("💸 API Send from %s to %s.", fromUserStr, toUserStr)
+ if req.Memo != "" {
+ transactionMemo += fmt.Sprintf(" Memo: %s", req.Memo)
+ }
+
+ // Create and execute transaction
+ t := telegram.NewTransaction(s.Bot, fromUser, toUser, req.Amount, telegram.TransactionType("api_send"))
+ t.Memo = transactionMemo
+
+ success, err := t.Send()
+ if !success || err != nil {
+ log.Errorf("[api/send] Transaction failed from %s to %s: %v", fromUserStr, toUserStr, err)
+ if s.Bot.ErrorLogger != nil {
+ s.Bot.ErrorLogger.LogTransactionError(err, "api_send", req.Amount, fromUser.Telegram, toUser.Telegram)
+ }
+ RespondError(w, fmt.Sprintf("Transaction failed: %v", err))
+ return
+ }
+
+ log.Infof("[api/send] ✅ API Send successful: %s -> %s (%d sat(s))", fromUserStr, toUserStr, req.Amount)
+
+ // Send notification to recipient with memo included in same message
+ fromUserStrMd := telegram.GetUserStrMd(fromUser.Telegram)
+ notificationMsg := fmt.Sprintf("💰 You received %s from %s via Automated API", thirdparty.FormatSatsWithLKR(req.Amount), fromUserStrMd)
+ if req.Memo != "" {
+ notificationMsg += fmt.Sprintf("\n✉️ Memo: %s", str.MarkdownEscape(req.Memo))
+ }
+
+ _, err = s.Bot.Telegram.Send(toUser.Telegram, notificationMsg)
+ if err != nil {
+ log.Warnf("[api/send] Could not send notification to recipient: %v", err)
+ }
+
+ // Send confirmation to sender (from user) - same format as /send command
+ toUserStrMd := telegram.GetUserStrMd(toUser.Telegram)
+ senderConfirmationMsg := fmt.Sprintf("✅ Payment sent successfully!\n\n💸 Amount: %s\n👤 To: %s", thirdparty.FormatSatsWithLKR(req.Amount), toUserStrMd)
+ if req.Memo != "" {
+ senderConfirmationMsg += fmt.Sprintf("\n✉️ Memo: %s", str.MarkdownEscape(req.Memo))
+ }
+
+ _, err = s.Bot.Telegram.Send(fromUser.Telegram, senderConfirmationMsg)
+ if err != nil {
+ log.Warnf("[api/send] Could not send confirmation to sender: %v", err)
+ }
+
+ response := SendResponse{
+ Success: true,
+ Message: "Payment sent successfully",
+ FromUser: fromUsername,
+ ToUser: toIdentifier,
+ Amount: req.Amount,
+ AmountLKR: getLKRValue(req.Amount),
+ Memo: req.Memo,
+ }
+
+ w.Header().Set("Content-Type", "application/json")
+ w.WriteHeader(http.StatusOK)
+ json.NewEncoder(w).Encode(response)
+}
+
+// sendToLightningAddress handles sending to Lightning addresses
+func (s Service) sendToLightningAddress(fromUser *lnbits.User, lightningAddress string, amount int64, memo string) error {
+ // This is a simplified implementation - you may need to implement the full Lightning address protocol
+ // For now, we'll return an error as this requires additional Lightning address handling logic
+ return fmt.Errorf("Lightning address payments not yet implemented in API")
+}
+
+// getLKRValue converts satoshi amount to LKR string or returns empty string if price unavailable
+func getLKRValue(amount int64) string {
+ lkrPerSat, _, err := thirdparty.GetSatPrice()
+ if err != nil {
+ return "" // Return empty string if LKR price is unavailable
+ }
+ lkrValue := lkrPerSat * float64(amount)
+ return utils.FormatFloatWithCommas(lkrValue)
+}
diff --git a/internal/api/send_config.go b/internal/api/send_config.go
new file mode 100644
index 00000000..94a26126
--- /dev/null
+++ b/internal/api/send_config.go
@@ -0,0 +1,55 @@
+package api
+
+import (
+ "github.com/LightningTipBot/LightningTipBot/internal"
+)
+
+// GetWhitelistedWallets returns the map of whitelisted wallets with their HMAC secrets
+func GetWhitelistedWallets() map[string]internal.WhitelistedWallet {
+ return internal.Configuration.API.Send.WhitelistedWallets
+}
+
+// GetWalletHMACSecret returns the HMAC secret for a specific wallet
+func GetWalletHMACSecret(walletID string) (string, bool) {
+ wallet, exists := internal.Configuration.API.Send.WhitelistedWallets[walletID]
+ if !exists {
+ return "", false
+ }
+ return wallet.HMACSecret, true
+}
+
+// IsWhitelistedWallet checks if a wallet ID is whitelisted
+func IsWhitelistedWallet(walletID string) bool {
+ _, exists := internal.Configuration.API.Send.WhitelistedWallets[walletID]
+ return exists
+}
+
+// GetInternalNetworkCIDR returns the allowed internal network range
+func GetInternalNetworkCIDR() string {
+ return internal.Configuration.API.Send.InternalNetwork
+}
+
+// GetMaxAPITransactionAmount returns the maximum transaction amount
+func GetMaxAPITransactionAmount() int64 {
+ return internal.Configuration.API.Send.MaxAmount
+}
+
+// GetMinAPITransactionAmount returns the minimum transaction amount
+func GetMinAPITransactionAmount() int64 {
+ return internal.Configuration.API.Send.MinAmount
+}
+
+// GetAdminApprovalThreshold returns the admin approval threshold
+func GetAdminApprovalThreshold() int64 {
+ return internal.Configuration.API.Send.AdminApprovalThreshold
+}
+
+// GetMaxMemoLength returns the maximum memo length
+func GetMaxMemoLength() int {
+ return internal.Configuration.API.Send.MaxMemoLength
+}
+
+// GetAPIRateLimit returns the API rate limit
+func GetAPIRateLimit() int {
+ return internal.Configuration.API.Send.RateLimit
+}
diff --git a/internal/api/server.go b/internal/api/server.go
new file mode 100644
index 00000000..1b01778b
--- /dev/null
+++ b/internal/api/server.go
@@ -0,0 +1,73 @@
+package api
+
+import (
+ "encoding/json"
+ "net/http"
+ "time"
+
+ "gorm.io/gorm"
+
+ "github.com/gorilla/mux"
+ log "github.com/sirupsen/logrus"
+)
+
+type Server struct {
+ httpServer *http.Server
+ router *mux.Router
+}
+
+const (
+ StatusError = "ERROR"
+ StatusOk = "OK"
+)
+
+func NewServer(address string) *Server {
+ srv := &http.Server{
+ Addr: address,
+ // Good practice: enforce timeouts for servers you create!
+ WriteTimeout: 90 * time.Second,
+ ReadTimeout: 90 * time.Second,
+ }
+ apiServer := &Server{
+ httpServer: srv,
+ }
+ apiServer.router = mux.NewRouter()
+ apiServer.httpServer.Handler = apiServer.router
+ go apiServer.httpServer.ListenAndServe()
+ log.Infof("[api] Server started at %s", address)
+ return apiServer
+}
+
+func (w *Server) ListenAndServe() {
+ go w.httpServer.ListenAndServe()
+}
+func (w *Server) PathPrefix(path string, handler http.Handler) {
+ w.router.PathPrefix(path).Handler(handler)
+}
+func (w *Server) AppendAuthorizedRoute(path string, authType AuthType, accessType AccessKeyType, database *gorm.DB, handler func(http.ResponseWriter, *http.Request), methods ...string) {
+ r := w.router.HandleFunc(path, LoggingMiddleware("API", AuthorizationMiddleware(database, authType, accessType, handler)))
+ if len(methods) > 0 {
+ r.Methods(methods...)
+ }
+}
+func (w *Server) AppendRoute(path string, handler func(http.ResponseWriter, *http.Request), methods ...string) {
+ r := w.router.HandleFunc(path, LoggingMiddleware("API", handler))
+ if len(methods) > 0 {
+ r.Methods(methods...)
+ }
+}
+
+func NotFoundHandler(writer http.ResponseWriter, err error) {
+ log.Errorln(err)
+ // return 404 on any error
+ http.Error(writer, "404 page not found", http.StatusNotFound)
+}
+
+func WriteResponse(writer http.ResponseWriter, response interface{}) error {
+ jsonResponse, err := json.Marshal(response)
+ if err != nil {
+ return err
+ }
+ _, err = writer.Write(jsonResponse)
+ return err
+}
diff --git a/internal/api/user_balance.go b/internal/api/user_balance.go
new file mode 100644
index 00000000..b743eaf7
--- /dev/null
+++ b/internal/api/user_balance.go
@@ -0,0 +1,106 @@
+package api
+
+import (
+ "encoding/json"
+ "net/http"
+ "time"
+
+ "github.com/LightningTipBot/LightningTipBot/internal/telegram"
+ log "github.com/sirupsen/logrus"
+)
+
+// UserBalanceRequest represents the JSON request for the user balance API
+type UserBalanceRequest struct {
+ TelegramID int64 `json:"telegram_id"` // Telegram user ID
+}
+
+// UserBalanceResponse represents the JSON response for the user balance API
+type UserBalanceResponse struct {
+ Success bool `json:"success"`
+ TelegramID int64 `json:"telegram_id"`
+ Balance int64 `json:"balance"` // Balance in satoshis
+ BalanceLKR string `json:"balance_lkr,omitempty"` // LKR conversion
+ FirstName string `json:"first_name,omitempty"`
+ LastName string `json:"last_name,omitempty"`
+ Username string `json:"username,omitempty"`
+ CreatedAt string `json:"created_at,omitempty"`
+ Message string `json:"message,omitempty"`
+}
+
+// UserBalance handles the /api/v1/userbalance endpoint for getting user balance by Telegram ID
+func (s Service) UserBalance(w http.ResponseWriter, r *http.Request) {
+ var req UserBalanceRequest
+ err := json.NewDecoder(r.Body).Decode(&req)
+ if err != nil {
+ log.Errorf("[api/userbalance] Invalid JSON request: %v", err)
+ RespondError(w, "Invalid JSON request")
+ return
+ }
+
+ // Validate request
+ if req.TelegramID <= 0 {
+ RespondError(w, "Invalid Telegram ID")
+ return
+ }
+
+ // Get user by Telegram ID
+ user, err := telegram.GetUserByTelegramID(req.TelegramID, *s.Bot)
+ if err != nil {
+ log.Errorf("[api/userbalance] Failed to get user by Telegram ID %d: %v", req.TelegramID, err)
+ response := UserBalanceResponse{
+ Success: false,
+ TelegramID: req.TelegramID,
+ Balance: 0,
+ Message: "User not found or has no wallet",
+ }
+ w.Header().Set("Content-Type", "application/json")
+ w.WriteHeader(http.StatusOK)
+ json.NewEncoder(w).Encode(response)
+ return
+ }
+
+ // Get user balance
+ balance, err := s.Bot.GetUserBalance(user)
+ if err != nil {
+ log.Errorf("[api/userbalance] Failed to get balance for user %d: %v", req.TelegramID, err)
+ response := UserBalanceResponse{
+ Success: false,
+ TelegramID: req.TelegramID,
+ Balance: 0,
+ Message: "Failed to retrieve balance",
+ }
+ w.Header().Set("Content-Type", "application/json")
+ w.WriteHeader(http.StatusOK)
+ json.NewEncoder(w).Encode(response)
+ return
+ }
+
+ // Convert to LKR if possible
+ balanceLKR := getLKRValue(balance)
+
+ // Extract user details
+ var firstName, lastName, username string
+ if user.Telegram != nil {
+ firstName = user.Telegram.FirstName
+ lastName = user.Telegram.LastName
+ username = user.Telegram.Username
+ }
+
+ response := UserBalanceResponse{
+ Success: true,
+ TelegramID: req.TelegramID,
+ Balance: balance,
+ BalanceLKR: balanceLKR,
+ FirstName: firstName,
+ LastName: lastName,
+ Username: username,
+ CreatedAt: user.CreatedAt.Format(time.RFC3339),
+ Message: "Balance retrieved successfully",
+ }
+
+ log.Infof("[api/userbalance] Balance retrieved for user %d: %d sats", req.TelegramID, balance)
+
+ w.Header().Set("Content-Type", "application/json")
+ w.WriteHeader(http.StatusOK)
+ json.NewEncoder(w).Encode(response)
+}
diff --git a/internal/api/userpage/static/userpage.html b/internal/api/userpage/static/userpage.html
new file mode 100644
index 00000000..21a5fc16
--- /dev/null
+++ b/internal/api/userpage/static/userpage.html
@@ -0,0 +1,79 @@
+
+
+{{define "userpage"}}
+
+
+
+
+
+
+
+
+
+
+
+
+
+{{.Username}}@ln.tips
+
+
+
+
+