initial commit
23
.gitignore
vendored
Normal file
|
|
@ -0,0 +1,23 @@
|
||||||
|
# Binaries for programs and plugins
|
||||||
|
*.exe
|
||||||
|
*.exe~
|
||||||
|
*.dll
|
||||||
|
*.so
|
||||||
|
*.dylib
|
||||||
|
lnurlw
|
||||||
|
|
||||||
|
# Test binary, built with `go test -c`
|
||||||
|
*.test
|
||||||
|
|
||||||
|
# Output of the go coverage tool, specifically when used with LiteIDE
|
||||||
|
*.out
|
||||||
|
|
||||||
|
# Dependency directories (remove the comment below to include it)
|
||||||
|
# vendor/
|
||||||
|
|
||||||
|
# secrets
|
||||||
|
tls.cert
|
||||||
|
*.macaroon*
|
||||||
|
|
||||||
|
# test data
|
||||||
|
# add_test_data.sql
|
||||||
2
Caddyfile
Normal file
|
|
@ -0,0 +1,2 @@
|
||||||
|
https://card.yourdomain.com
|
||||||
|
reverse_proxy 127.0.0.1:9000
|
||||||
21
LICENSE
Normal file
|
|
@ -0,0 +1,21 @@
|
||||||
|
MIT License
|
||||||
|
|
||||||
|
Copyright (c) 2022 Peter Rounce
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
in the Software without restriction, including without limitation the rights
|
||||||
|
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
copies of the Software, and to permit persons to whom the Software is
|
||||||
|
furnished to do so, subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in all
|
||||||
|
copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
SOFTWARE.
|
||||||
25
README.md
Normal file
|
|
@ -0,0 +1,25 @@
|
||||||
|
# Bolt Card
|
||||||
|
|
||||||
|
## Overview
|
||||||
|
|
||||||
|
The bolt card enables a customer to make payment at a merchant point of sale over the bitcoin lightning network.
|
||||||
|
|
||||||
|
Each bolt card makes use of a service to receive the request from the merchant system, apply payment rules and make payment.
|
||||||
|
|
||||||
|
The 'bolt card service' software provided here can be used to host bolt cards for yourself and others.
|
||||||
|
|
||||||
|
The 'bolt card creation' instructions describe how to set up bolt cards for use with your bolt card service.
|
||||||
|
|
||||||
|
## Documents
|
||||||
|
|
||||||
|
| Document | Description |
|
||||||
|
| --- | --- |
|
||||||
|
| [Specification](docs/SPEC.md) | Bolt card specifications |
|
||||||
|
| [System](docs/SYSTEM.md) | Bolt card system overview |
|
||||||
|
| [Install](docs/INSTALL.md) | Bolt card service installation |
|
||||||
|
| [Card](docs/CARD.md) | Bolt card creation |
|
||||||
|
| [FAQ](docs/FAQ.md) | Frequently asked questions |
|
||||||
|
|
||||||
|
## Telegram group
|
||||||
|
|
||||||
|
Discussion and support is available at https://t.me/bolt_card .
|
||||||
22
add_card_data.sql
Normal file
|
|
@ -0,0 +1,22 @@
|
||||||
|
\c card_db
|
||||||
|
|
||||||
|
INSERT INTO cards (
|
||||||
|
aes_cmac,
|
||||||
|
uid,
|
||||||
|
last_counter_value,
|
||||||
|
lnurlw_request_timeout_sec,
|
||||||
|
enable_flag,
|
||||||
|
tx_limit_sats,
|
||||||
|
day_limit_sats,
|
||||||
|
card_description
|
||||||
|
)
|
||||||
|
VALUES (
|
||||||
|
'00000000000000000000000000000000',
|
||||||
|
'00000000000000',
|
||||||
|
0,
|
||||||
|
60,
|
||||||
|
'Y',
|
||||||
|
1000,
|
||||||
|
10000,
|
||||||
|
'bolt card'
|
||||||
|
);
|
||||||
50
boltcard.service
Normal file
|
|
@ -0,0 +1,50 @@
|
||||||
|
[Unit]
|
||||||
|
Description=bolt card service
|
||||||
|
After=network.target network-online.target
|
||||||
|
Requires=network-online.target
|
||||||
|
StartLimitIntervalSec=0
|
||||||
|
|
||||||
|
[Service]
|
||||||
|
Type=simple
|
||||||
|
Restart=always
|
||||||
|
RestartSec=10
|
||||||
|
User=ubuntu
|
||||||
|
|
||||||
|
# boltcard service settings
|
||||||
|
|
||||||
|
# LOG_LEVEL is DEBUG or PRODUCTION
|
||||||
|
Environment="LOG_LEVEL=DEBUG"
|
||||||
|
|
||||||
|
# AES_DECRYPT_KEY is the hex value of the server decrypt key for hosted bolt cards
|
||||||
|
Environment="AES_DECRYPT_KEY=00000000000000000000000000000000"
|
||||||
|
|
||||||
|
# DB_ values are for the postgres database connection
|
||||||
|
Environment="DB_HOST=localhost"
|
||||||
|
Environment="DB_PORT=5432"
|
||||||
|
Environment="DB_USER=cardapp"
|
||||||
|
Environment="DB_PASSWORD=database_password"
|
||||||
|
Environment="DB_NAME=card_db"
|
||||||
|
|
||||||
|
# LNURLW_CB_URL is the URL prefix for the lnurlw callback
|
||||||
|
Environment="LNURLW_CB_URL=https://card.yourdomain.com/cb"
|
||||||
|
|
||||||
|
# MIN_WITHDRAW_SATS & MAX_WITHDRAW_SATS set the values for the lnurlw response
|
||||||
|
Environment="MIN_WITHDRAW_SATS=1"
|
||||||
|
Environment="MAX_WITHDRAW_SATS=1000000"
|
||||||
|
|
||||||
|
# LN_ values are for the lightning server used for making payments
|
||||||
|
Environment="LN_HOST=ln.host.io"
|
||||||
|
Environment="LN_PORT=10009"
|
||||||
|
Environment="LN_TLS_FILE=/home/ubuntu/boltcard/tls.cert"
|
||||||
|
Environment="LN_MACAROON_FILE=/home/ubuntu/boltcard/SendPaymentV2.macaroon"
|
||||||
|
|
||||||
|
# FEE_LIMIT_SAT is the maximum lightning network fee to be paid
|
||||||
|
Environment="FEE_LIMIT_SAT=10"
|
||||||
|
|
||||||
|
# LN_TESTNODE may be used in testing and will then only pay to the defined test node pubkey
|
||||||
|
#Environment="LN_TESTNODE=000000000000000000000000000000000000000000000000000000000000000000"
|
||||||
|
|
||||||
|
ExecStart=/bin/bash /home/ubuntu/boltcard/s_launch
|
||||||
|
|
||||||
|
[Install]
|
||||||
|
WantedBy=multi-user.target
|
||||||
38
create_db.sql
Normal file
|
|
@ -0,0 +1,38 @@
|
||||||
|
DROP DATABASE IF EXISTS card_db;
|
||||||
|
CREATE DATABASE card_db;
|
||||||
|
|
||||||
|
--CREATE USER cardapp WITH PASSWORD '***';
|
||||||
|
|
||||||
|
\c card_db;
|
||||||
|
|
||||||
|
CREATE TABLE cards (
|
||||||
|
card_id INT GENERATED ALWAYS AS IDENTITY,
|
||||||
|
aes_cmac CHAR(32) NOT NULL,
|
||||||
|
uid CHAR(14) NOT NULL,
|
||||||
|
last_counter_value INTEGER NOT NULL,
|
||||||
|
lnurlw_request_timeout_sec INT NOT NULL,
|
||||||
|
enable_flag CHAR(1) NOT NULL DEFAULT 'N',
|
||||||
|
tx_limit_sats INT NOT NULL,
|
||||||
|
day_limit_sats INT NOT NULL,
|
||||||
|
card_description VARCHAR(100) NOT NULL DEFAULT '',
|
||||||
|
PRIMARY KEY(card_id)
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE TABLE card_payments (
|
||||||
|
card_payment_id INT GENERATED ALWAYS AS IDENTITY,
|
||||||
|
card_id INT NOT NULL,
|
||||||
|
k1 CHAR(32) UNIQUE NOT NULL,
|
||||||
|
lnurlw_request_time TIMESTAMPTZ NOT NULL,
|
||||||
|
ln_invoice VARCHAR(1024) NOT NULL DEFAULT '',
|
||||||
|
amount_msats BIGINT CHECK (amount_msats > 0),
|
||||||
|
paid_flag CHAR(1) NOT NULL,
|
||||||
|
payment_time TIMESTAMPTZ,
|
||||||
|
payment_status VARCHAR(100) NOT NULL DEFAULT '',
|
||||||
|
failure_reason VARCHAR(100) NOT NULL DEFAULT '',
|
||||||
|
payment_status_time TIMESTAMPTZ,
|
||||||
|
PRIMARY KEY(card_payment_id),
|
||||||
|
CONSTRAINT fk_card FOREIGN KEY(card_id) REFERENCES cards(card_id)
|
||||||
|
);
|
||||||
|
|
||||||
|
GRANT ALL PRIVILEGES ON TABLE cards TO cardapp;
|
||||||
|
GRANT ALL PRIVILEGES ON TABLE card_payments TO cardapp;
|
||||||
75
crypto.go
Normal file
|
|
@ -0,0 +1,75 @@
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"crypto/aes"
|
||||||
|
"crypto/cipher"
|
||||||
|
"crypto/rand"
|
||||||
|
"encoding/hex"
|
||||||
|
"github.com/aead/cmac"
|
||||||
|
)
|
||||||
|
|
||||||
|
func create_k1() (string, error) {
|
||||||
|
|
||||||
|
// 16 bytes = 128 bits
|
||||||
|
b := make([]byte, 16)
|
||||||
|
_, err := rand.Read(b)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
str := hex.EncodeToString(b)
|
||||||
|
|
||||||
|
return str, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// decrypt p with aes_dec
|
||||||
|
func crypto_aes_decrypt(key_sdm_file_read []byte, ba_p []byte) ([]byte, error) {
|
||||||
|
|
||||||
|
dec_p := make([]byte, 16)
|
||||||
|
iv := make([]byte, 16)
|
||||||
|
c1, err := aes.NewCipher(key_sdm_file_read)
|
||||||
|
if err != nil {
|
||||||
|
return dec_p, err
|
||||||
|
}
|
||||||
|
mode := cipher.NewCBCDecrypter(c1, iv)
|
||||||
|
mode.CryptBlocks(dec_p, ba_p)
|
||||||
|
|
||||||
|
return dec_p, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func crypto_aes_cmac(key_sdm_file_read_mac []byte, sv2 []byte, ba_c []byte) (bool, error) {
|
||||||
|
|
||||||
|
c2, err := aes.NewCipher(key_sdm_file_read_mac)
|
||||||
|
if err != nil {
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
ks, err := cmac.Sum(sv2, c2, 16)
|
||||||
|
if err != nil {
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
c3, err := aes.NewCipher(ks)
|
||||||
|
if err != nil {
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
cm, err := cmac.Sum([]byte{}, c3, 16)
|
||||||
|
if err != nil {
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
ct := make([]byte, 8)
|
||||||
|
ct[0] = cm[1]
|
||||||
|
ct[1] = cm[3]
|
||||||
|
ct[2] = cm[5]
|
||||||
|
ct[3] = cm[7]
|
||||||
|
ct[4] = cm[9]
|
||||||
|
ct[5] = cm[11]
|
||||||
|
ct[6] = cm[13]
|
||||||
|
ct[7] = cm[15]
|
||||||
|
|
||||||
|
res_cmac := bytes.Compare(ct, ba_c)
|
||||||
|
if res_cmac != 0 {
|
||||||
|
return false, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return true, nil
|
||||||
|
}
|
||||||
312
database.go
Normal file
|
|
@ -0,0 +1,312 @@
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"database/sql"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
_ "github.com/lib/pq"
|
||||||
|
"os"
|
||||||
|
)
|
||||||
|
|
||||||
|
type card struct {
|
||||||
|
card_id int
|
||||||
|
card_guid string
|
||||||
|
aes_dec string
|
||||||
|
aes_cmac string
|
||||||
|
db_uid string
|
||||||
|
last_counter_value uint32
|
||||||
|
lnurlw_request_timeout_sec int
|
||||||
|
enable_flag string
|
||||||
|
tx_limit_sats int
|
||||||
|
day_limit_sats int
|
||||||
|
}
|
||||||
|
|
||||||
|
type payment struct {
|
||||||
|
card_payment_id int
|
||||||
|
card_id int
|
||||||
|
k1 string
|
||||||
|
paid_flag string
|
||||||
|
}
|
||||||
|
|
||||||
|
func db_open() (*sql.DB, error) {
|
||||||
|
|
||||||
|
// get connection string from environment variables
|
||||||
|
|
||||||
|
conn := fmt.Sprintf("host=%s port=%s user=%s password=%s dbname=%s sslmode=disable",
|
||||||
|
os.Getenv("DB_HOST"),
|
||||||
|
os.Getenv("DB_PORT"),
|
||||||
|
os.Getenv("DB_USER"),
|
||||||
|
os.Getenv("DB_PASSWORD"),
|
||||||
|
os.Getenv("DB_NAME"))
|
||||||
|
|
||||||
|
db, err := sql.Open("postgres", conn)
|
||||||
|
if err != nil {
|
||||||
|
return db, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return db, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func db_get_card_from_uid(card_uid string) (*card, error) {
|
||||||
|
|
||||||
|
c := card{}
|
||||||
|
|
||||||
|
db, err := db_open()
|
||||||
|
if err != nil {
|
||||||
|
return &c, err
|
||||||
|
}
|
||||||
|
defer db.Close()
|
||||||
|
|
||||||
|
sqlStatement := `SELECT card_id, aes_cmac, uid,` +
|
||||||
|
` last_counter_value, lnurlw_request_timeout_sec,` +
|
||||||
|
` enable_flag, tx_limit_sats, day_limit_sats` +
|
||||||
|
` FROM cards WHERE uid=$1;`
|
||||||
|
row := db.QueryRow(sqlStatement, card_uid)
|
||||||
|
err = row.Scan(
|
||||||
|
&c.card_id,
|
||||||
|
&c.aes_cmac,
|
||||||
|
&c.db_uid,
|
||||||
|
&c.last_counter_value,
|
||||||
|
&c.lnurlw_request_timeout_sec,
|
||||||
|
&c.enable_flag,
|
||||||
|
&c.tx_limit_sats,
|
||||||
|
&c.day_limit_sats)
|
||||||
|
if err != nil {
|
||||||
|
return &c, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return &c, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func db_get_card_from_card_id(card_id int) (*card, error) {
|
||||||
|
|
||||||
|
c := card{}
|
||||||
|
|
||||||
|
db, err := db_open()
|
||||||
|
if err != nil {
|
||||||
|
return &c, err
|
||||||
|
}
|
||||||
|
defer db.Close()
|
||||||
|
|
||||||
|
sqlStatement := `SELECT card_id, aes_cmac, uid,` +
|
||||||
|
` last_counter_value, lnurlw_request_timeout_sec,` +
|
||||||
|
` enable_flag, tx_limit_sats, day_limit_sats` +
|
||||||
|
` FROM cards WHERE card_id=$1;`
|
||||||
|
row := db.QueryRow(sqlStatement, card_id)
|
||||||
|
err = row.Scan(
|
||||||
|
&c.card_id,
|
||||||
|
&c.aes_cmac,
|
||||||
|
&c.db_uid,
|
||||||
|
&c.last_counter_value,
|
||||||
|
&c.lnurlw_request_timeout_sec,
|
||||||
|
&c.enable_flag,
|
||||||
|
&c.tx_limit_sats,
|
||||||
|
&c.day_limit_sats)
|
||||||
|
if err != nil {
|
||||||
|
return &c, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return &c, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func db_check_lnurlw_timeout(card_payment_id int) (bool, error) {
|
||||||
|
|
||||||
|
db, err := db_open()
|
||||||
|
if err != nil {
|
||||||
|
return true, err
|
||||||
|
}
|
||||||
|
defer db.Close()
|
||||||
|
|
||||||
|
lnurlw_timeout := true
|
||||||
|
|
||||||
|
sqlStatement := `SELECT NOW() > cp.lnurlw_request_time + c.lnurlw_request_timeout_sec * INTERVAL '1 SECOND'` +
|
||||||
|
` FROM card_payments AS cp INNER JOIN cards AS c ON c.card_id = cp.card_id` +
|
||||||
|
` WHERE cp.card_payment_id=$1;`
|
||||||
|
row := db.QueryRow(sqlStatement, card_payment_id)
|
||||||
|
err = row.Scan(&lnurlw_timeout)
|
||||||
|
if err != nil {
|
||||||
|
return true, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return lnurlw_timeout, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func db_check_and_update_counter(card_id int, new_counter_value uint32) (bool, error) {
|
||||||
|
|
||||||
|
db, err := db_open()
|
||||||
|
if err != nil {
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
defer db.Close()
|
||||||
|
|
||||||
|
sqlStatement := `UPDATE cards SET last_counter_value = $2 WHERE card_id = $1` +
|
||||||
|
` AND last_counter_value < $2;`
|
||||||
|
res, err := db.Exec(sqlStatement, card_id, new_counter_value)
|
||||||
|
if err != nil {
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
count, err := res.RowsAffected()
|
||||||
|
if err != nil {
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
if count != 1 {
|
||||||
|
return false, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return true, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func db_insert_payment(card_id int, k1 string) error {
|
||||||
|
|
||||||
|
db, err := db_open()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer db.Close()
|
||||||
|
|
||||||
|
// insert a new record into card_payments with card_id & k1 set
|
||||||
|
|
||||||
|
sqlStatement := `INSERT INTO card_payments` +
|
||||||
|
` (card_id, k1, paid_flag, lnurlw_request_time)` +
|
||||||
|
` VALUES ($1, $2, 'N', NOW());`
|
||||||
|
res, err := db.Exec(sqlStatement, card_id, k1)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
count, err := res.RowsAffected()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if count != 1 {
|
||||||
|
return errors.New("not one card_payments record inserted")
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func db_get_payment_k1(k1 string) (*payment, error) {
|
||||||
|
p := payment{}
|
||||||
|
|
||||||
|
db, err := db_open()
|
||||||
|
if err != nil {
|
||||||
|
return &p, err
|
||||||
|
}
|
||||||
|
defer db.Close()
|
||||||
|
|
||||||
|
sqlStatement := `SELECT card_payment_id, card_id, paid_flag` +
|
||||||
|
` FROM card_payments WHERE k1=$1;`
|
||||||
|
row := db.QueryRow(sqlStatement, k1)
|
||||||
|
err = row.Scan(
|
||||||
|
&p.card_payment_id,
|
||||||
|
&p.card_id,
|
||||||
|
&p.paid_flag)
|
||||||
|
if err != nil {
|
||||||
|
return &p, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return &p, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func db_update_payment_invoice(card_payment_id int, ln_invoice string, amount_msats int64) error {
|
||||||
|
|
||||||
|
db, err := db_open()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer db.Close()
|
||||||
|
|
||||||
|
sqlStatement := `UPDATE card_payments SET ln_invoice = $2, amount_msats = $3 WHERE card_payment_id = $1;`
|
||||||
|
res, err := db.Exec(sqlStatement, card_payment_id, ln_invoice, amount_msats)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
count, err := res.RowsAffected()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if count != 1 {
|
||||||
|
return errors.New("not one card_payment record updated")
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func db_update_payment_paid(card_payment_id int) error {
|
||||||
|
|
||||||
|
db, err := db_open()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer db.Close()
|
||||||
|
|
||||||
|
sqlStatement := `UPDATE card_payments SET paid_flag = 'Y', payment_time = NOW() WHERE card_payment_id = $1;`
|
||||||
|
res, err := db.Exec(sqlStatement, card_payment_id)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
count, err := res.RowsAffected()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if count != 1 {
|
||||||
|
return errors.New("not one card_payment record updated")
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func db_update_payment_status(card_payment_id int, payment_status string, failure_reason string) error {
|
||||||
|
|
||||||
|
db, err := db_open()
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
defer db.Close()
|
||||||
|
|
||||||
|
sqlStatement := `UPDATE card_payments SET payment_status = $2, failure_reason = $3, ` +
|
||||||
|
`payment_status_time = NOW() WHERE card_payment_id = $1;`
|
||||||
|
|
||||||
|
res, err := db.Exec(sqlStatement, card_payment_id, payment_status, failure_reason)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
count, err := res.RowsAffected()
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if count != 1 {
|
||||||
|
return errors.New("not one card_payment record updated")
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func db_get_card_totals(card_id int) (int, error) {
|
||||||
|
|
||||||
|
db, err := db_open()
|
||||||
|
if err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
defer db.Close()
|
||||||
|
|
||||||
|
day_total_msats := 0
|
||||||
|
|
||||||
|
sqlStatement := `SELECT COALESCE(SUM(amount_msats),0) FROM card_payments ` +
|
||||||
|
`WHERE card_id=$1 AND paid_flag='Y' ` +
|
||||||
|
`AND payment_time > NOW() - INTERVAL '1 DAY';`
|
||||||
|
row := db.QueryRow(sqlStatement, card_id)
|
||||||
|
err = row.Scan(&day_total_msats)
|
||||||
|
if err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
|
||||||
|
day_total_sats := day_total_msats / 1000
|
||||||
|
|
||||||
|
return day_total_sats, nil
|
||||||
|
}
|
||||||
151
docs/CARD.md
Normal file
|
|
@ -0,0 +1,151 @@
|
||||||
|
# Steps for making a bolt card
|
||||||
|
|
||||||
|
## Introduction
|
||||||
|
|
||||||
|
Here we describe how to create your own bolt card.
|
||||||
|
|
||||||
|
## Resources
|
||||||
|
|
||||||
|
- some cards
|
||||||
|
- NXP DNA 424 NTAG cards
|
||||||
|
|
||||||
|
- a good NFC reader/writer
|
||||||
|
- Identiv uTrust 3700 F
|
||||||
|
|
||||||
|
- software
|
||||||
|
- [NXP TagXplorer software](https://www.nxp.com/products/rfid-nfc/mifare-hf/mifare-desfire/tagxplorer-pc-based-nfc-tag-reader-writer-tool:TAGXPLORER)
|
||||||
|
- [Java Run Time environment](https://java.com/de/download/)
|
||||||
|
|
||||||
|
## Steps
|
||||||
|
|
||||||
|
### Connect to the card
|
||||||
|
- start the NFC TagXplorer software
|
||||||
|
- `Connect` to the NFC card reader
|
||||||
|
- place a card on the reader and click `Connect Tag`
|
||||||
|
- verify the card description
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
### Read the card
|
||||||
|
- select `NDEF Operations` and `Read NDEF`
|
||||||
|
- if you get this error, click `Format NDEF` and try again
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
- verify that the read completes without erroring
|
||||||
|
|
||||||
|
### Start to set up the URI template
|
||||||
|
- select `NTAG Operations`, `Mirroring Features` and `NTAG 424 DNA`
|
||||||
|
- set `Protocol` to `https://`
|
||||||
|
- set `URI Data` to
|
||||||
|
```
|
||||||
|
lnurlw://card.yourdomain.com
|
||||||
|
```
|
||||||
|
- select `Add PICCDATA` and `Enable SUN Message`
|
||||||
|
- adjust the `URI Data` to
|
||||||
|
```
|
||||||
|
lnurlw://card.yourdomain.com/ln?p=00000000000000000000000000000000&c=0000000000000000
|
||||||
|
```
|
||||||
|
- click after `p=` and note the p_position (38 in this case)
|
||||||
|
- click after `c=` and note the c_position (73 in this case)
|
||||||
|
- select `Write To Tag`
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
- now go back to `NDEF Operations` and `Read NDEF`
|
||||||
|
- verify that the `NDEF Payload Info` is as expected
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
### Finish setting up the URI template
|
||||||
|
- notice that the URI shows as `https://lnurlw://card ...` but we want `lnurlw://card ...`
|
||||||
|
- go to `NTAG Operations` and `NTAG 424 DNA`
|
||||||
|
- select `Read/Write data`
|
||||||
|
- select `File No` as `NDEF File - 02`
|
||||||
|
- click `Read`
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
- the NDEF file is `0057D1015355046C6E75726C ...`.
|
||||||
|
- look for the bytes `5504` (6 bytes from the start)
|
||||||
|
- `04` is the code for `https://` URI prepending
|
||||||
|
- change the `04` to `00` to indicate no prepending for the URI
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
- click `Write`
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
- now go back to `NDEF Operations` and `Read NDEF`
|
||||||
|
- verify that the `NDEF Payload(HEX) Info` is similar to that shown
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
- copy the hex data and convert to text, without the `0x00` prefix
|
||||||
|
- verify you have your expected `URI data` value
|
||||||
|
|
||||||
|
[Online hex to text tool](http://www.unit-conversion.info/texttools/hexadecimal/)
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
### Set up the SUN authentication message
|
||||||
|
- go to `NTAG Operations` and `NTAG 424 DNA`
|
||||||
|
- select `Security Management` and click `Authentiate First`
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
- select `Get/Change File Settings`
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
- set up the values in the order shown
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
- select `Change File Settings`
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
- now go back to `NDEF Operations` and `Read NDEF`
|
||||||
|
- convert the hex data to text again
|
||||||
|
- verify that the `p` and `c` values are non zero
|
||||||
|
- select `Read NDEF` again
|
||||||
|
- convert the hex data to text again
|
||||||
|
- verify that the `p` and `c` values are in the right place
|
||||||
|
- verify that the `p` and `c` values change on each read
|
||||||
|
|
||||||
|
### Change the application keys
|
||||||
|
- go to `NTAG Operations` and `NTAG 424 DNA`
|
||||||
|
- select `Security Management`
|
||||||
|
- select `Authenticate`
|
||||||
|
- leave the `Card Key No` set to `00`
|
||||||
|
- leave the `Key` value set to `00000000000000000000000000000000` if not changed yet
|
||||||
|
- click `Authenticate First`
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
- select `Change Key`
|
||||||
|
- select the `Card Key No` to change the key value for `00` to `04`
|
||||||
|
- leave the `Old Key` value set to `00000000000000000000000000000000` if not changed yet
|
||||||
|
- enter a `New Key` value as required
|
||||||
|
- enter a `New Key Version` value of `00` or as required to keep track of your keys
|
||||||
|
- click `Change Key`
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
- repeat this to change all 5 application keys to your own values
|
||||||
|
|
||||||
|
### Lock the card
|
||||||
|
- go to `NTAG Operations` and `NTAG 424 DNA`
|
||||||
|
- select `Security Management` and click `Authentiate First`
|
||||||
|
- select `Get/Change File Settings`
|
||||||
|
- adjust the `Access Rights` settings as shown
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
## Testing
|
||||||
|
- set up a [bolt card service](INSTALL.md)
|
||||||
|
- add a record in the database for the new card
|
||||||
|
- use a merchant point of sale to scan your bolt card, e.g. [Breez wallet](https://breez.technology/)
|
||||||
|
- watch the bolt card service logs and verify that the requests are received and processed
|
||||||
16
docs/FAQ.md
Normal file
|
|
@ -0,0 +1,16 @@
|
||||||
|
# FAQ
|
||||||
|
|
||||||
|
> Why do I get a payment failure with NO_ROUTE ?
|
||||||
|
|
||||||
|
This is due to your payment lightning node not finding a route to the merchant lightning node.
|
||||||
|
It may help to open well funded channels to other well connected nodes.
|
||||||
|
It may also help to increase your maximum network fee in your service variables, **FEE_LIMIT_SAT** .
|
||||||
|
It can be useful to test paying invoices directly from your lightning node.
|
||||||
|
|
||||||
|
> Why do my payments take so long ?
|
||||||
|
|
||||||
|
|
||||||
|
This is due to the time taken for your payment lightning node to find a route.
|
||||||
|
It can be improved by opening channels using clearnet rather than on the tor network.
|
||||||
|
It may also help to improve your lightning node hardware or software setup.
|
||||||
|
It can be useful to test paying invoices directly from your lightning node.
|
||||||
82
docs/INSTALL.md
Normal file
|
|
@ -0,0 +1,82 @@
|
||||||
|
# Bolt card service installation
|
||||||
|
|
||||||
|
## hardware & o/s
|
||||||
|
|
||||||
|
1 GHz processor, 2 GB RAM, 10GB storage minimum
|
||||||
|
Ubuntu 20.04 LTS server
|
||||||
|
|
||||||
|
### install Go
|
||||||
|
|
||||||
|
[Go download & install](https://go.dev/doc/install)
|
||||||
|
`$ go version` >= 1.18.3
|
||||||
|
|
||||||
|
### install Postgres
|
||||||
|
|
||||||
|
[Postgres download & install](https://www.postgresql.org/download/linux/ubuntu/)
|
||||||
|
`$ psql --version` >= 12.11
|
||||||
|
|
||||||
|
### install Caddy
|
||||||
|
|
||||||
|
[Caddy download & install](https://caddyserver.com/docs/install)
|
||||||
|
`$ caddy version` >= 2.5.2
|
||||||
|
|
||||||
|
### download the boltcard repository
|
||||||
|
|
||||||
|
`$ git clone https://github.com/boltcard/boltcard`
|
||||||
|
|
||||||
|
### get a macaroon and tls.cert from the lightning node
|
||||||
|
|
||||||
|
create a macaroon with limited permissions to the lightning node
|
||||||
|
```
|
||||||
|
$ lncli \
|
||||||
|
--rpcserver=lightning-node.io:10009 \
|
||||||
|
--macaroonpath=admin.macaroon \
|
||||||
|
--tlscertpath="tls.cert" \
|
||||||
|
bakemacaroon uri:/routerrpc.Router/SendPaymentV2 > SendPaymentV2.macaroon.hex
|
||||||
|
|
||||||
|
$ xxd -r -p SendPaymentV2.macaroon.hex SendPaymentV2.macaroon
|
||||||
|
```
|
||||||
|
|
||||||
|
### setup the boltcard server
|
||||||
|
edit `boltcard.service` in the section named `boltcard service settings`
|
||||||
|
edit `Caddyfile` to set the boltcard domain name
|
||||||
|
edit `add_card_data.sql` to set up the individual bolt card records
|
||||||
|
|
||||||
|
### database creation
|
||||||
|
`$ ./s_create_db`
|
||||||
|
|
||||||
|
### boltcard service install
|
||||||
|
`$ sudo cp boltcard.service /etc/systemd/system/boltcard.service`
|
||||||
|
`$ ./s_build`
|
||||||
|
`$ systemctl status boltcard`
|
||||||
|
|
||||||
|
### https setup
|
||||||
|
set up the domain A record to point to the server
|
||||||
|
set up the server hosting firewall to allow open access to https (port 443) only
|
||||||
|
|
||||||
|
### caddy setup for https
|
||||||
|
`$ sudo cp Caddyfile /etc/caddy`
|
||||||
|
`$ sudo systemctl stop caddy`
|
||||||
|
`$ sudo systemctl start caddy`
|
||||||
|
`$ sudo systemctl status caddy`
|
||||||
|
you should see 'certificate obtained successfully' in the service log
|
||||||
|
|
||||||
|
### service bring-up and testing
|
||||||
|
#### service log
|
||||||
|
the service log should be monitored on a separate console while tests are run
|
||||||
|
`$ journalctl -u boltcard.service -f`
|
||||||
|
#### local http
|
||||||
|
`$ curl http://127.0.0.1:9000/ln?1`
|
||||||
|
this should respond with 'bad request' and show up in the service log
|
||||||
|
#### remote https
|
||||||
|
navigate to the service URL from a browser, for example `https://card.yourdomain.com/ln?2`
|
||||||
|
this should respond with 'bad request' and show up in the service log
|
||||||
|
#### bolt card
|
||||||
|
[create a bolt card](docs/CARD.md) with the URI pointing to this server
|
||||||
|
use a PoS setup to read the bolt card, e.g. [Breez wallet](https://breez.technology/)
|
||||||
|
monitor the service log to ensure decryption, authentication, payment rules and lightning payment work as expected
|
||||||
|
|
||||||
|
# Further information and support
|
||||||
|
|
||||||
|
[bolt card FAQ](docs/FAQ.md)
|
||||||
|
[bolt card telegram group](https://t.me/bolt_card)
|
||||||
11
docs/SECURITY.md
Normal file
|
|
@ -0,0 +1,11 @@
|
||||||
|
# Security
|
||||||
|
|
||||||
|
## secrets
|
||||||
|
- card AES decrypt key to the environment variable `AES_DECRYPT_KEY`
|
||||||
|
- card AES cmac keys into the database table `cards`
|
||||||
|
|
||||||
|
- `tls.cert` and `SendPaymentV2.macaroon` for the lightning node
|
||||||
|
|
||||||
|
- password for the application database user `cardapp`
|
||||||
|
- database script in `create_db.sql`
|
||||||
|
- application environment variable in `lnurlw.service`
|
||||||
34
docs/SPEC.md
Normal file
|
|
@ -0,0 +1,34 @@
|
||||||
|
# Bolt card specification
|
||||||
|
|
||||||
|
The bolt card system is built on the open standards listed below.
|
||||||
|
|
||||||
|
- [LUD-03: withdrawRequest base spec.](https://github.com/fiatjaf/lnurl-rfc/blob/luds/03.md)
|
||||||
|
- [LUD-17: Protocol schemes and raw (non bech32-encoded) URLs.](https://github.com/fiatjaf/lnurl-rfc/blob/luds/17.md)
|
||||||
|
|
||||||
|
## Bolt card interaction
|
||||||
|
|
||||||
|
- the point-of-sale (POS) will read an NDEF message from the card, for example
|
||||||
|
```
|
||||||
|
lnurlw://card.yourdomain.com?p=A2EF40F6D46F1BB36E6EBF0114D4A464&c=F509EEA788E37E32
|
||||||
|
```
|
||||||
|
- the POS will call your server here
|
||||||
|
```
|
||||||
|
https://card.yourdomain.com?p=A2EF40F6D46F1BB36E6EBF0114D4A464&c=F509EEA788E37E32
|
||||||
|
```
|
||||||
|
- your server should verify the payment request and issue an LNURLw response
|
||||||
|
|
||||||
|
### Server side verification
|
||||||
|
- for the `p` value and the `SDM Meta Read Access Key` value, decrypt the UID and counter
|
||||||
|
- for the `c` value and the `SDM File Read Access Key` value, check with AES-CMAC
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
- the authenticated UID and counter values can be used on your server to verify the request
|
||||||
|
- your server should only accept an increasing counter value
|
||||||
|
- additional validation rules can be added at your server, for example
|
||||||
|
- an enable flag
|
||||||
|
- payment limits
|
||||||
|
- a list of allowed merchants
|
||||||
|
- a verification of your location from your phone
|
||||||
|
- your server can then make payment from your lightning node
|
||||||
|
|
||||||
41
docs/SYSTEM.md
Normal file
|
|
@ -0,0 +1,41 @@
|
||||||
|
## System
|
||||||
|
|
||||||
|
The customer and the merchant must have a supporting infrastructure to make and accept payments using a bolt card.
|
||||||
|
|
||||||
|
### Interaction
|
||||||
|
```mermaid
|
||||||
|
flowchart TB
|
||||||
|
BoltCard(bolt card)-. NFC .-PointOfSale(point of sale)
|
||||||
|
MerchantServer(merchant server)-. LNURLw .-BoltCardServer(bolt card server)
|
||||||
|
LightningNodeA(lightning node)-. lightning network .-LightningNodeB(lightning node)
|
||||||
|
|
||||||
|
subgraph merchant
|
||||||
|
PointOfSale<-->MerchantServer
|
||||||
|
LightningNodeB-->MerchantServer
|
||||||
|
end
|
||||||
|
|
||||||
|
subgraph customer
|
||||||
|
BoltCardServer-->LightningNodeA
|
||||||
|
end
|
||||||
|
```
|
||||||
|
|
||||||
|
### Sequencing
|
||||||
|
```mermaid
|
||||||
|
sequenceDiagram
|
||||||
|
participant p1 as customer bolt card
|
||||||
|
participant p2 as merchant point of sale
|
||||||
|
participant p3 as merchant server
|
||||||
|
participant p4 as customer bolt card server
|
||||||
|
participant p5 as customer lightining node
|
||||||
|
participant p6 as merchant lightning node
|
||||||
|
p1->>p2: NFC read
|
||||||
|
p2->>p3: API call
|
||||||
|
p3->>p4: LNURLw request
|
||||||
|
p4->>p3: LNURLw response
|
||||||
|
p3->>p4: LNURLw callback
|
||||||
|
p4->>p3: LNURLw response
|
||||||
|
p4->>p5: API call
|
||||||
|
p5-->>p6: lightning network payment
|
||||||
|
p6->>p3: payment notification
|
||||||
|
p3->>p2: user notification
|
||||||
|
```
|
||||||
BIN
docs/images/ac.webp
Normal file
|
After Width: | Height: | Size: 30 KiB |
BIN
docs/images/aes-dec.webp
Normal file
|
After Width: | Height: | Size: 36 KiB |
BIN
docs/images/aesd.webp
Normal file
|
After Width: | Height: | Size: 36 KiB |
BIN
docs/images/avs.webp
Normal file
|
After Width: | Height: | Size: 5 KiB |
BIN
docs/images/breez.webp
Normal file
|
After Width: | Height: | Size: 5.5 KiB |
BIN
docs/images/btcpayserver.webp
Normal file
|
After Width: | Height: | Size: 5.9 KiB |
BIN
docs/images/btr.webp
Normal file
|
After Width: | Height: | Size: 4.7 KiB |
BIN
docs/images/card.webp
Normal file
|
After Width: | Height: | Size: 20 KiB |
BIN
docs/images/ccs.webp
Normal file
|
After Width: | Height: | Size: 4.4 KiB |
BIN
docs/images/cfs.webp
Normal file
|
After Width: | Height: | Size: 4.9 KiB |
BIN
docs/images/cmac-aes.webp
Normal file
|
After Width: | Height: | Size: 31 KiB |
BIN
docs/images/cmac.webp
Normal file
|
After Width: | Height: | Size: 32 KiB |
BIN
docs/images/con.webp
Normal file
|
After Width: | Height: | Size: 4.1 KiB |
BIN
docs/images/fs-add.webp
Normal file
|
After Width: | Height: | Size: 43 KiB |
BIN
docs/images/fs.webp
Normal file
|
After Width: | Height: | Size: 39 KiB |
BIN
docs/images/gfs.webp
Normal file
|
After Width: | Height: | Size: 4.5 KiB |
BIN
docs/images/hex.webp
Normal file
|
After Width: | Height: | Size: 30 KiB |
BIN
docs/images/lock.webp
Normal file
|
After Width: | Height: | Size: 5.6 KiB |
BIN
docs/images/nfc.webp
Normal file
|
After Width: | Height: | Size: 27 KiB |
BIN
docs/images/nfwc.webp
Normal file
|
After Width: | Height: | Size: 5.2 KiB |
BIN
docs/images/nrd.webp
Normal file
|
After Width: | Height: | Size: 22 KiB |
BIN
docs/images/picc.webp
Normal file
|
After Width: | Height: | Size: 22 KiB |
BIN
docs/images/rd-txt.webp
Normal file
|
After Width: | Height: | Size: 12 KiB |
BIN
docs/images/rd.webp
Normal file
|
After Width: | Height: | Size: 36 KiB |
BIN
docs/images/rdh.webp
Normal file
|
After Width: | Height: | Size: 36 KiB |
BIN
docs/images/ref.webp
Normal file
|
After Width: | Height: | Size: 40 KiB |
BIN
docs/images/su.webp
Normal file
|
After Width: | Height: | Size: 4.1 KiB |
BIN
docs/images/sun.webp
Normal file
|
After Width: | Height: | Size: 7.3 KiB |
BIN
docs/images/wr.webp
Normal file
|
After Width: | Height: | Size: 38 KiB |
BIN
docs/images/wrh.webp
Normal file
|
After Width: | Height: | Size: 38 KiB |
BIN
docs/images/ws.webp
Normal file
|
After Width: | Height: | Size: 3 KiB |
45
go.mod
Normal file
|
|
@ -0,0 +1,45 @@
|
||||||
|
module github.com/boltcard/boltcard
|
||||||
|
|
||||||
|
go 1.18
|
||||||
|
|
||||||
|
require (
|
||||||
|
github.com/aead/cmac v0.0.0-20160719120800-7af84192f0b1 // indirect
|
||||||
|
github.com/aead/siphash v1.0.1 // indirect
|
||||||
|
github.com/btcsuite/btcd v0.20.1-beta.0.20200515232429-9f0179fd2c46 // indirect
|
||||||
|
github.com/btcsuite/btclog v0.0.0-20170628155309-84c8d2346e9f // indirect
|
||||||
|
github.com/btcsuite/btcutil v1.0.2 // indirect
|
||||||
|
github.com/btcsuite/btcwallet v0.11.1-0.20200515224913-e0e62245ecbe // indirect
|
||||||
|
github.com/btcsuite/btcwallet/wallet/txauthor v1.0.0 // indirect
|
||||||
|
github.com/btcsuite/btcwallet/wallet/txrules v1.0.0 // indirect
|
||||||
|
github.com/btcsuite/btcwallet/wallet/txsizes v1.0.0 // indirect
|
||||||
|
github.com/btcsuite/btcwallet/walletdb v1.3.1 // indirect
|
||||||
|
github.com/btcsuite/btcwallet/wtxmgr v1.1.1-0.20200515224913-e0e62245ecbe // indirect
|
||||||
|
github.com/btcsuite/go-socks v0.0.0-20170105172521-4720035b7bfd // indirect
|
||||||
|
github.com/btcsuite/websocket v0.0.0-20150119174127-31079b680792 // indirect
|
||||||
|
github.com/davecgh/go-spew v1.1.1 // indirect
|
||||||
|
github.com/fiatjaf/ln-decodepay v1.4.0 // indirect
|
||||||
|
github.com/go-errors/errors v1.0.1 // indirect
|
||||||
|
github.com/golang/protobuf v1.3.3 // indirect
|
||||||
|
github.com/grpc-ecosystem/grpc-gateway v1.8.6 // indirect
|
||||||
|
github.com/juju/loggo v0.0.0-20190526231331-6e530bcce5d8 // indirect
|
||||||
|
github.com/kkdai/bstream v0.0.0-20181106074824-b3251f7901ec // indirect
|
||||||
|
github.com/lib/pq v1.10.6 // indirect
|
||||||
|
github.com/lightninglabs/gozmq v0.0.0-20191113021534-d20a764486bf // indirect
|
||||||
|
github.com/lightninglabs/neutrino v0.11.1-0.20200316235139-bffc52e8f200 // indirect
|
||||||
|
github.com/lightningnetwork/lnd v0.10.1-beta // indirect
|
||||||
|
github.com/lightningnetwork/lnd/queue v1.0.3 // indirect
|
||||||
|
github.com/lightningnetwork/lnd/ticker v1.0.0 // indirect
|
||||||
|
github.com/lncm/lnd-rpc v1.0.2 // indirect
|
||||||
|
github.com/miekg/dns v0.0.0-20171125082028-79bfde677fa8 // indirect
|
||||||
|
github.com/rogpeppe/fastuuid v1.2.0 // indirect
|
||||||
|
github.com/sirupsen/logrus v1.8.1 // indirect
|
||||||
|
golang.org/x/crypto v0.0.0-20200510223506-06a226fb4e37 // indirect
|
||||||
|
golang.org/x/net v0.0.0-20200202094626-16171245cfb2 // indirect
|
||||||
|
golang.org/x/sys v0.0.0-20200212091648-12a6c2dcc1e4 // indirect
|
||||||
|
golang.org/x/text v0.3.2 // indirect
|
||||||
|
google.golang.org/genproto v0.0.0-20200212174721-66ed5ce911ce // indirect
|
||||||
|
google.golang.org/grpc v1.27.1 // indirect
|
||||||
|
gopkg.in/errgo.v1 v1.0.1 // indirect
|
||||||
|
gopkg.in/macaroon-bakery.v2 v2.0.1 // indirect
|
||||||
|
gopkg.in/macaroon.v2 v2.1.0 // indirect
|
||||||
|
)
|
||||||
275
go.sum
Normal file
|
|
@ -0,0 +1,275 @@
|
||||||
|
cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
|
||||||
|
git.schwanenlied.me/yawning/bsaes.git v0.0.0-20180720073208-c0276d75487e/go.mod h1:BWqTsj8PgcPriQJGl7el20J/7TuT1d/hSyFDXMEpoEo=
|
||||||
|
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
|
||||||
|
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/Yawning/aez v0.0.0-20180114000226-4dad034d9db2/go.mod h1:9pIqrY6SXNL8vjRQE5Hd/OL5GyK/9MrGUWs87z/eFfk=
|
||||||
|
github.com/aead/chacha20 v0.0.0-20180709150244-8b13a72661da/go.mod h1:eHEWzANqSiWQsof+nXEI9bUVUyV6F53Fp89EuCh2EAA=
|
||||||
|
github.com/aead/cmac v0.0.0-20160719120800-7af84192f0b1 h1:+JkXLHME8vLJafGhOH4aoV2Iu8bR55nU6iKMVfYVLjY=
|
||||||
|
github.com/aead/cmac v0.0.0-20160719120800-7af84192f0b1/go.mod h1:nuudZmJhzWtx2212z+pkuy7B6nkBqa+xwNXZHL1j8cg=
|
||||||
|
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/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
|
||||||
|
github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
|
||||||
|
github.com/asaskevich/govalidator v0.0.0-20190424111038-f61b66f89f4a/go.mod h1:lB+ZfQJz7igIIfQNfa7Ml4HSf2uFQQRzpGGRXenZAgY=
|
||||||
|
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/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/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/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/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/txsizes v1.0.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/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/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/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/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/go.mod h1:jsenWakMcC0zFBFurPLEAyrnc/teJEM1O46fmI40EZs=
|
||||||
|
github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
|
||||||
|
github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc=
|
||||||
|
github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
|
||||||
|
github.com/coreos/bbolt v1.3.3/go.mod h1:iRUV2dpdMOn7Bo10OQBFzIJO9kkE559Wcmn+qkEiiKk=
|
||||||
|
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/dgryski/go-sip13 v0.0.0-20181026042036-e10d5fee7954/go.mod h1:vAd38F8PWV+bWy6jNmig1y/TA+kYO4g3RSRF0IAv0no=
|
||||||
|
github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
|
||||||
|
github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
|
||||||
|
github.com/fiatjaf/ln-decodepay v1.4.0 h1:WDedFrzitQPQHfo7pktYXoZ1Rmy28RJnA/4w3cpzt40=
|
||||||
|
github.com/fiatjaf/ln-decodepay v1.4.0/go.mod h1:zLf4G9EsRhryXtFO63072BZ0JQLWbr7uRm3wZFJafdo=
|
||||||
|
github.com/frankban/quicktest v1.0.0/go.mod h1:R98jIehRai+d1/3Hv2//jOVCTJhW1VBavT6B6CuGq2k=
|
||||||
|
github.com/frankban/quicktest v1.2.2/go.mod h1:Qh/WofXFeiAFII1aEBu529AtJo6Zg2VHscnEsbBnJ20=
|
||||||
|
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
|
||||||
|
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-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
|
||||||
|
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-openapi/errors v0.19.2/go.mod h1:qX0BLWsyaKfvhluLejVpVNwNRdXZhEbTA4kxxpKBC94=
|
||||||
|
github.com/go-openapi/strfmt v0.19.5/go.mod h1:eftuHTlB/dI8Uq8JJOyRlieZf+WkkxUuk0dgdHXr2Qk=
|
||||||
|
github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=
|
||||||
|
github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
|
||||||
|
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
|
||||||
|
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/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||||
|
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 h1:gyjaxf+svBWX08ZjK86iN9geUJF0H6gp2IRKX6Nf6/I=
|
||||||
|
github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw=
|
||||||
|
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/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||||
|
github.com/grpc-ecosystem/go-grpc-middleware v1.0.0/go.mod h1:FiyG127CGDf3tlThmgyCl78X/SZQqEOJBCDaAfeWzPs=
|
||||||
|
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 h1:XvND7+MPP7Jp+JpqSZ7naSl5nVZf6k0LbL1V3EKh0zc=
|
||||||
|
github.com/grpc-ecosystem/grpc-gateway v1.8.6/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY=
|
||||||
|
github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=
|
||||||
|
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/jessevdk/go-flags v0.0.0-20141203071132-1679536dcc89/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI=
|
||||||
|
github.com/jessevdk/go-flags v1.4.0/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI=
|
||||||
|
github.com/jrick/logrotate v1.0.0/go.mod h1:LNinyqDIJnpAur+b8yyulnQw/wDuN1+BYKlTRt3OuAQ=
|
||||||
|
github.com/juju/clock v0.0.0-20190205081909-9c5c9712527c/go.mod h1:nD0vlnrUjcjJhqN5WuCWZyzfd5AHZAC9/ajvbSx69xA=
|
||||||
|
github.com/juju/errors v0.0.0-20190806202954-0232dcc7464d/go.mod h1:W54LbzXuIE0boCoNJfwqpmkKJ1O4TCTZMetAt6jGk7Q=
|
||||||
|
github.com/juju/loggo v0.0.0-20190526231331-6e530bcce5d8 h1:UUHMLvzt/31azWTN/ifGWef4WUqvXk0iRqdhdy/2uzI=
|
||||||
|
github.com/juju/loggo v0.0.0-20190526231331-6e530bcce5d8/go.mod h1:vgyd7OREkbtVEN/8IXZe5Ooef3LQePvuBm9UWj6ZL8U=
|
||||||
|
github.com/juju/retry v0.0.0-20180821225755-9058e192b216/go.mod h1:OohPQGsr4pnxwD5YljhQ+TZnuVRYpa5irjugL1Yuif4=
|
||||||
|
github.com/juju/testing v0.0.0-20190723135506-ce30eb24acd2/go.mod h1:63prj8cnj0tU0S9OHjGJn+b1h0ZghCndfnbQolrYTwA=
|
||||||
|
github.com/juju/utils v0.0.0-20180820210520-bf9cc5bdd62d/go.mod h1:6/KLg8Wz/y2KVGWEpkK9vMNGkOnu4k/cqs8Z1fKjTOk=
|
||||||
|
github.com/juju/version v0.0.0-20180108022336-b64dbd566305/go.mod h1:kE8gK5X0CImdr7qpSKl3xB2PmpySSmfj7zVbkZFs81U=
|
||||||
|
github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w=
|
||||||
|
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/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/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
|
||||||
|
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
|
||||||
|
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
|
||||||
|
github.com/lib/pq v1.10.6 h1:jbk+ZieJ0D7EVGJYpL9QTz7/YW6UHbmdnZWYyK5cdBs=
|
||||||
|
github.com/lib/pq v1.10.6/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/protobuf-hex-display v1.3.3-0.20191212020323-b444784ce75d/go.mod h1:KDb67YMzoh4eudnzClmvs2FbiLG9vxISmLApUkCa4uI=
|
||||||
|
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/lnd v0.10.1-beta/go.mod h1:F9er1DrpOHdQVQBqYqyBqIFyl6q16xgBM8yTioHj2Cg=
|
||||||
|
github.com/lightningnetwork/lnd/cert v1.0.2/go.mod h1:fmtemlSMf5t4hsQmcprSoOykypAPp+9c+0d0iqTScMo=
|
||||||
|
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/ticker v1.0.0/go.mod h1:iaLXJiVgI1sPANIF2qYYUJXjoksPNvGNYowB8aRbpX0=
|
||||||
|
github.com/lncm/lnd-rpc v1.0.2 h1:34F1+aNT5bbBrfA8aWg3m40yaEByJjQwCqVAyaV2Fog=
|
||||||
|
github.com/lncm/lnd-rpc v1.0.2/go.mod h1:zw6oVgCpAYE+KYDfCa52xZag4m0+fOcuXxj6D+4NDtY=
|
||||||
|
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/mattn/go-runewidth v0.0.9/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI=
|
||||||
|
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/miekg/dns v0.0.0-20171125082028-79bfde677fa8/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg=
|
||||||
|
github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=
|
||||||
|
github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U=
|
||||||
|
github.com/oklog/ulid v1.3.1/go.mod h1:CirwcVhetQ6Lv90oh/F+FBtV6XMibvdAFo93nm5qn4U=
|
||||||
|
github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
|
||||||
|
github.com/onsi/ginkgo v1.7.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
|
||||||
|
github.com/onsi/gomega v1.4.1/go.mod h1:C1qb7wdrVGGVU+Z6iS04AVkA3Q65CEZX59MT0QO5uiA=
|
||||||
|
github.com/onsi/gomega v1.4.3/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY=
|
||||||
|
github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||||
|
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||||
|
github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw=
|
||||||
|
github.com/prometheus/client_golang v0.9.3/go.mod h1:/TN21ttK/J9q6uSwhBd54HahCDft0ttaMvbicHlPoso=
|
||||||
|
github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/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/common v0.0.0-20181113130724-41aa239b4cce/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro=
|
||||||
|
github.com/prometheus/common v0.4.0/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4=
|
||||||
|
github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk=
|
||||||
|
github.com/prometheus/procfs v0.0.0-20190507164030-5867b95ac084/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA=
|
||||||
|
github.com/prometheus/tsdb v0.7.1/go.mod h1:qhTCs0VvXwvX/y3TZrWD7rabWM+ijKTux40TwIPHuXU=
|
||||||
|
github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6SoW27p1b0cqNHllgS5HIMJraePCO15w5zCzIWYg=
|
||||||
|
github.com/rogpeppe/fastuuid v1.2.0 h1:Ppwyp6VYCF1nvBTXL3trRso7mXMlRrw9ooo375wvi2s=
|
||||||
|
github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ=
|
||||||
|
github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo=
|
||||||
|
github.com/sirupsen/logrus v1.8.1 h1:dJKuHgqk1NNQlqoA6BTlM1Wf9DOH3NBjQyu0h9+AZZE=
|
||||||
|
github.com/sirupsen/logrus v1.8.1/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0=
|
||||||
|
github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA=
|
||||||
|
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/go.mod h1:qt09Ya8vawLte6SNmTgCsAVtYtaKzEcn8ATUoHMkEqE=
|
||||||
|
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/tidwall/pretty v1.0.0/go.mod h1:XNkn88O1ChpSDQmQeStsy+sBenx6DDtFZJxhVysOjyk=
|
||||||
|
github.com/tv42/zbase32 v0.0.0-20160707012821-501572607d02/go.mod h1:tHlrkM198S068ZqfrO6S8HsoJq2bF3ETfTL+kt4tInY=
|
||||||
|
github.com/urfave/cli v1.18.0/go.mod h1:70zkFmudgCuE/ngEzBv17Jvp/497gISqfk5gWijbERA=
|
||||||
|
go.etcd.io/bbolt v1.3.3/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU=
|
||||||
|
go.mongodb.org/mongo-driver v1.0.3/go.mod h1:u7ryQJ+DOzQmeO7zB6MHyr8jkEQvC8vH7qLUO4lqsUM=
|
||||||
|
golang.org/x/crypto v0.0.0-20170930174604-9419663f5a44/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
|
||||||
|
golang.org/x/crypto v0.0.0-20180723164146-c126467f60eb/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
|
||||||
|
golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/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-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/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
||||||
|
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-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
|
||||||
|
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-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-20181220203305-927f97764cc3/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-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 h1:0GoQqolDA55aaLxZyTzK/Y2ePZzZTUrRacwib7cNsYQ=
|
||||||
|
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||||
|
golang.org/x/net v0.0.0-20200202094626-16171245cfb2 h1:CCH4IOTTfewWjGOlSp+zGcjutRKlBEZQ6wTn8ozI/nI=
|
||||||
|
golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||||
|
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
|
||||||
|
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-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/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-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-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-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
golang.org/x/sys v0.0.0-20190904154756-749cb33beabd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
golang.org/x/sys v0.0.0-20200116001909-b77594299b42 h1:vEOn+mP2zCOVzKckCZy6YsCtDblrpj/w7B9nxGNELpg=
|
||||||
|
golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
golang.org/x/sys v0.0.0-20200212091648-12a6c2dcc1e4 h1:sfkvUWPNGwSV+8/fNqctR5lS2AqCSqYwXdrjCxp/dXo=
|
||||||
|
golang.org/x/sys v0.0.0-20200212091648-12a6c2dcc1e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||||
|
golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||||
|
golang.org/x/text v0.3.2 h1:tW2bmiBqwgJj/UpqtC8EpXEZVYOwU0yG4iWbprSVAcs=
|
||||||
|
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
|
||||||
|
golang.org/x/time v0.0.0-20180412165947-fbb02b2291d2/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||||
|
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-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-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
|
||||||
|
google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
|
||||||
|
google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
|
||||||
|
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-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=
|
||||||
|
google.golang.org/genproto v0.0.0-20200212174721-66ed5ce911ce h1:1mbrb1tUU+Zmt5C94IGKADBTJZjZXAd+BubWi7r9EiI=
|
||||||
|
google.golang.org/genproto v0.0.0-20200212174721-66ed5ce911ce/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
|
||||||
|
google.golang.org/grpc v1.16.0/go.mod h1:0JHn/cJsOMiMfNA9+DeHDlAU7KAAB5GDlYFpa9MZMio=
|
||||||
|
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.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg=
|
||||||
|
google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
|
||||||
|
google.golang.org/grpc v1.27.1 h1:zvIju4sqAGvwKspUQOhwnpcqSbzi7/H6QomNNjTL4sk=
|
||||||
|
google.golang.org/grpc v1.27.1/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
|
||||||
|
gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw=
|
||||||
|
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||||
|
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||||
|
gopkg.in/errgo.v1 v1.0.1 h1:oQFRXzZ7CkBGdm1XZm/EbQYaYNNEElNBOd09M6cqNso=
|
||||||
|
gopkg.in/errgo.v1 v1.0.1/go.mod h1:3NjfXwocQRYAPTq4/fzX+CwUhPRcR/azYRhj8G+LqMo=
|
||||||
|
gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys=
|
||||||
|
gopkg.in/macaroon-bakery.v2 v2.0.1 h1:0N1TlEdfLP4HXNCg7MQUMp5XwvOoxk+oe9Owr2cpvsc=
|
||||||
|
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/macaroon.v2 v2.1.0 h1:HZcsjBCzq9t0eBPMKqTN/uSN6JOm78ZJ2INbqcBQOUI=
|
||||||
|
gopkg.in/macaroon.v2 v2.1.0/go.mod h1:OUb+TQP/OP0WOerC2Jp/3CwhIKyIa9kQjuc7H24e6/o=
|
||||||
|
gopkg.in/mgo.v2 v2.0.0-20190816093944-a6b53ec6cb22/go.mod h1:yeKp02qBN3iKW1OzL3MGk2IdtZzaj7SFntXj72NppTA=
|
||||||
|
gopkg.in/resty.v1 v1.12.0/go.mod h1:mDo4pnntr5jdWRML875a/NmxYqAlA73dVijT2AXvQQo=
|
||||||
|
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw=
|
||||||
|
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/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||||
|
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-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
||||||
131
lightning.go
Normal file
|
|
@ -0,0 +1,131 @@
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"encoding/hex"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"io/ioutil"
|
||||||
|
"log"
|
||||||
|
"os"
|
||||||
|
"strconv"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
lnrpc "github.com/lncm/lnd-rpc/v0.10.0/lnrpc"
|
||||||
|
routerrpc "github.com/lncm/lnd-rpc/v0.10.0/routerrpc"
|
||||||
|
|
||||||
|
"google.golang.org/grpc"
|
||||||
|
"google.golang.org/grpc/credentials"
|
||||||
|
|
||||||
|
"gopkg.in/macaroon.v2"
|
||||||
|
)
|
||||||
|
|
||||||
|
type rpcCreds map[string]string
|
||||||
|
|
||||||
|
func (m rpcCreds) RequireTransportSecurity() bool { return true }
|
||||||
|
func (m rpcCreds) GetRequestMetadata(_ context.Context, _ ...string) (map[string]string, error) {
|
||||||
|
return m, nil
|
||||||
|
}
|
||||||
|
func newCreds(bytes []byte) rpcCreds {
|
||||||
|
creds := make(map[string]string)
|
||||||
|
creds["macaroon"] = hex.EncodeToString(bytes)
|
||||||
|
return creds
|
||||||
|
}
|
||||||
|
|
||||||
|
func getRouterClient(hostname string, port int, tlsFile, macaroonFile string) routerrpc.RouterClient {
|
||||||
|
macaroonBytes, err := ioutil.ReadFile(macaroonFile)
|
||||||
|
if err != nil {
|
||||||
|
log.Println("Cannot read macaroon file .. ", err)
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
mac := &macaroon.Macaroon{}
|
||||||
|
if err = mac.UnmarshalBinary(macaroonBytes); err != nil {
|
||||||
|
log.Println("Cannot unmarshal macaroon .. ", err)
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
|
||||||
|
defer cancel()
|
||||||
|
|
||||||
|
transportCredentials, err := credentials.NewClientTLSFromFile(tlsFile, hostname)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
fullHostname := fmt.Sprintf("%s:%d", hostname, port)
|
||||||
|
|
||||||
|
connection, err := grpc.DialContext(ctx, fullHostname, []grpc.DialOption{
|
||||||
|
grpc.WithBlock(),
|
||||||
|
grpc.WithTransportCredentials(transportCredentials),
|
||||||
|
grpc.WithPerRPCCredentials(newCreds(macaroonBytes)),
|
||||||
|
}...)
|
||||||
|
if err != nil {
|
||||||
|
log.Printf("unable to connect to %s: %w", fullHostname, err)
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return routerrpc.NewRouterClient(connection)
|
||||||
|
}
|
||||||
|
|
||||||
|
func pay_invoice(invoice string) (payment_status string, failure_reason string, return_err error) {
|
||||||
|
|
||||||
|
payment_status = ""
|
||||||
|
failure_reason = ""
|
||||||
|
return_err = nil
|
||||||
|
|
||||||
|
// SendPaymentV2
|
||||||
|
|
||||||
|
ctx2, cancel := context.WithTimeout(context.Background(), 30*time.Second)
|
||||||
|
defer cancel()
|
||||||
|
|
||||||
|
// get node parameters from environment variables
|
||||||
|
|
||||||
|
ln_port, err := strconv.Atoi(os.Getenv("LN_PORT"))
|
||||||
|
if err != nil {
|
||||||
|
return_err = err
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
r_client := getRouterClient(
|
||||||
|
os.Getenv("LN_HOST"),
|
||||||
|
ln_port,
|
||||||
|
os.Getenv("LN_TLS_FILE"),
|
||||||
|
os.Getenv("LN_MACAROON_FILE"))
|
||||||
|
|
||||||
|
fee_limit_sat_str := os.Getenv("FEE_LIMIT_SAT")
|
||||||
|
fee_limit_sat, err := strconv.ParseInt(fee_limit_sat_str, 10, 64)
|
||||||
|
if err != nil {
|
||||||
|
return_err = err
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
stream, err := r_client.SendPaymentV2(ctx2, &routerrpc.SendPaymentRequest{
|
||||||
|
PaymentRequest: invoice,
|
||||||
|
NoInflightUpdates: true,
|
||||||
|
TimeoutSeconds: 30,
|
||||||
|
FeeLimitSat: fee_limit_sat})
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return_err = err
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
for {
|
||||||
|
update, err := stream.Recv()
|
||||||
|
|
||||||
|
if err == io.EOF {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return_err = err
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
payment_status = lnrpc.Payment_PaymentStatus_name[int32(update.Status)]
|
||||||
|
failure_reason = lnrpc.PaymentFailureReason_name[int32(update.FailureReason)]
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
142
lnurlw_callback.go
Normal file
|
|
@ -0,0 +1,142 @@
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
decodepay "github.com/fiatjaf/ln-decodepay"
|
||||||
|
log "github.com/sirupsen/logrus"
|
||||||
|
"net/http"
|
||||||
|
"os"
|
||||||
|
)
|
||||||
|
|
||||||
|
func lnurlw_callback(w http.ResponseWriter, req *http.Request) {
|
||||||
|
|
||||||
|
url := req.URL.RequestURI()
|
||||||
|
log.WithFields(log.Fields{"url": url}).Debug("cb request")
|
||||||
|
|
||||||
|
// check k1 value
|
||||||
|
params_k1, ok := req.URL.Query()["k1"]
|
||||||
|
|
||||||
|
if !ok || len(params_k1[0]) < 1 {
|
||||||
|
log.WithFields(log.Fields{"url": url}).Debug("k1 not found")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
param_k1 := params_k1[0]
|
||||||
|
|
||||||
|
p, err := db_get_payment_k1(param_k1)
|
||||||
|
if err != nil {
|
||||||
|
log.WithFields(log.Fields{"url": url, "k1": param_k1}).Warn(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// check that payment has not been made
|
||||||
|
if p.paid_flag != "N" {
|
||||||
|
log.WithFields(log.Fields{"card_payment_id": p.card_payment_id}).Info("payment already made")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// check if lnurlw_request has timed out
|
||||||
|
lnurlw_timeout, err := db_check_lnurlw_timeout(p.card_payment_id)
|
||||||
|
if err != nil {
|
||||||
|
log.WithFields(log.Fields{"card_payment_id": p.card_payment_id}).Warn(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if lnurlw_timeout == true {
|
||||||
|
log.WithFields(log.Fields{"card_payment_id": p.card_payment_id}).Info("lnurlw request has timed out")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
params_pr, ok := req.URL.Query()["pr"]
|
||||||
|
if !ok || len(params_pr[0]) < 1 {
|
||||||
|
log.WithFields(log.Fields{"card_payment_id": p.card_payment_id}).Warn("pr field not found")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
param_pr := params_pr[0]
|
||||||
|
bolt11, _ := decodepay.Decodepay(param_pr)
|
||||||
|
|
||||||
|
// record the lightning invoice
|
||||||
|
err = db_update_payment_invoice(p.card_payment_id, param_pr, bolt11.MSatoshi)
|
||||||
|
if err != nil {
|
||||||
|
log.WithFields(log.Fields{"card_payment_id": p.card_payment_id}).Warn(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
log.WithFields(log.Fields{"card_payment_id": p.card_payment_id}).Debug("checking payment rules")
|
||||||
|
|
||||||
|
// check if we are only sending funds to a defined test node
|
||||||
|
testnode := os.Getenv("LN_TESTNODE")
|
||||||
|
if testnode != "" && bolt11.Payee != testnode {
|
||||||
|
log.WithFields(log.Fields{"card_payment_id": p.card_payment_id}).Info("rejected as not the defined test node")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// check amount limits
|
||||||
|
|
||||||
|
invoice_sats := int(bolt11.MSatoshi / 1000)
|
||||||
|
|
||||||
|
day_total_sats, err := db_get_card_totals(p.card_id)
|
||||||
|
if err != nil {
|
||||||
|
log.WithFields(log.Fields{"card_payment_id": p.card_payment_id}).Warn(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
c, err := db_get_card_from_card_id(p.card_id)
|
||||||
|
if err != nil {
|
||||||
|
log.WithFields(log.Fields{"card_payment_id": p.card_payment_id}).Warn(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if invoice_sats > c.tx_limit_sats {
|
||||||
|
log.WithFields(log.Fields{"card_payment_id": p.card_payment_id}).Info("invoice_sats: ", invoice_sats)
|
||||||
|
log.WithFields(log.Fields{"card_payment_id": p.card_payment_id}).Info("tx_limit_sats: ", c.tx_limit_sats)
|
||||||
|
log.WithFields(log.Fields{"card_payment_id": p.card_payment_id}).Info("over tx_limit_sats!")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if day_total_sats+invoice_sats > c.day_limit_sats {
|
||||||
|
log.WithFields(log.Fields{"card_payment_id": p.card_payment_id}).Info("invoice_sats: ", invoice_sats)
|
||||||
|
log.WithFields(log.Fields{"card_payment_id": p.card_payment_id}).Info("day_total_sats: ", day_total_sats)
|
||||||
|
log.WithFields(log.Fields{"card_payment_id": p.card_payment_id}).Info("day_limit_sats: ", c.day_limit_sats)
|
||||||
|
log.WithFields(log.Fields{"card_payment_id": p.card_payment_id}).Info("over day_limit_sats!")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
log.WithFields(log.Fields{"card_payment_id": p.card_payment_id}).Info("paying invoice")
|
||||||
|
|
||||||
|
// update paid_flag so we only attempt payment once
|
||||||
|
err = db_update_payment_paid(p.card_payment_id)
|
||||||
|
if err != nil {
|
||||||
|
log.WithFields(log.Fields{"card_payment_id": p.card_payment_id}).Warn(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// https://github.com/fiatjaf/lnurl-rfc/blob/luds/03.md
|
||||||
|
//
|
||||||
|
// LN SERVICE sends a {"status": "OK"} or
|
||||||
|
// {"status": "ERROR", "reason": "error details..."}
|
||||||
|
// JSON response and then attempts to pay the invoices asynchronously.
|
||||||
|
|
||||||
|
w.Header().Set("Content-Type", "application/json")
|
||||||
|
w.WriteHeader(http.StatusOK)
|
||||||
|
jsonData := []byte(`{"status":"OK"}`)
|
||||||
|
w.Write(jsonData)
|
||||||
|
|
||||||
|
payment_status, failure_reason, err := pay_invoice(param_pr)
|
||||||
|
if err != nil {
|
||||||
|
log.WithFields(log.Fields{"card_payment_id": p.card_payment_id}).Warn(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if failure_reason != "FAILURE_REASON_NONE" {
|
||||||
|
log.WithFields(log.Fields{"card_payment_id": p.card_payment_id}).Info("payment failure reason : ", failure_reason)
|
||||||
|
}
|
||||||
|
|
||||||
|
log.WithFields(log.Fields{"card_payment_id": p.card_payment_id}).Info("payment status : ", payment_status)
|
||||||
|
|
||||||
|
// store result in database
|
||||||
|
err = db_update_payment_status(p.card_payment_id, payment_status, failure_reason)
|
||||||
|
if err != nil {
|
||||||
|
log.WithFields(log.Fields{"card_payment_id": p.card_payment_id}).Warn(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
215
lnurlw_request.go
Normal file
|
|
@ -0,0 +1,215 @@
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/hex"
|
||||||
|
"encoding/json"
|
||||||
|
"errors"
|
||||||
|
log "github.com/sirupsen/logrus"
|
||||||
|
"net/http"
|
||||||
|
"os"
|
||||||
|
"strconv"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Response struct {
|
||||||
|
Tag string `json:"tag"`
|
||||||
|
Callback string `json:"callback"`
|
||||||
|
K1 string `json:"k1"`
|
||||||
|
DefaultDescription string `json:"defaultDescription"`
|
||||||
|
MinWithdrawable int `json:"minWithdrawable"`
|
||||||
|
MaxWithdrawable int `json:"maxWithdrawable"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func get_p_c(req *http.Request, p_name string, c_name string) (p string, c string) {
|
||||||
|
|
||||||
|
params_p, ok := req.URL.Query()[p_name]
|
||||||
|
if !ok || len(params_p[0]) < 1 {
|
||||||
|
return "", ""
|
||||||
|
}
|
||||||
|
|
||||||
|
params_c, ok := req.URL.Query()[c_name]
|
||||||
|
if !ok || len(params_c[0]) < 1 {
|
||||||
|
return "", ""
|
||||||
|
}
|
||||||
|
|
||||||
|
p = params_p[0]
|
||||||
|
c = params_c[0]
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func parse_request(req *http.Request) (int, error) {
|
||||||
|
|
||||||
|
url := req.URL.RequestURI()
|
||||||
|
log.Debug("ln url: ", url)
|
||||||
|
|
||||||
|
param_p, param_c := get_p_c(req, "p", "c")
|
||||||
|
|
||||||
|
ba_p, err := hex.DecodeString(param_p)
|
||||||
|
if err != nil {
|
||||||
|
return 0, errors.New("p parameter not valid hex")
|
||||||
|
}
|
||||||
|
|
||||||
|
ba_c, err := hex.DecodeString(param_c)
|
||||||
|
if err != nil {
|
||||||
|
return 0, errors.New("c parameter not valid hex")
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(ba_p) != 16 {
|
||||||
|
return 0, errors.New("p parameter length not valid")
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(ba_c) != 8 {
|
||||||
|
return 0, errors.New("c parameter length not valid")
|
||||||
|
}
|
||||||
|
|
||||||
|
// decrypt p with aes_decrypt_key
|
||||||
|
|
||||||
|
aes_decrypt_key := os.Getenv("AES_DECRYPT_KEY")
|
||||||
|
|
||||||
|
key_sdm_file_read, err := hex.DecodeString(aes_decrypt_key)
|
||||||
|
if err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
|
||||||
|
dec_p, err := crypto_aes_decrypt(key_sdm_file_read, ba_p)
|
||||||
|
if err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if dec_p[0] != 0xC7 {
|
||||||
|
return 0, errors.New("decrypted data not starting with 0xC7")
|
||||||
|
}
|
||||||
|
|
||||||
|
uid := dec_p[1:8]
|
||||||
|
ctr := dec_p[8:11]
|
||||||
|
|
||||||
|
ctr_int := uint32(ctr[2])<<16 | uint32(ctr[1])<<8 | uint32(ctr[0])
|
||||||
|
|
||||||
|
// get card record from database for UID
|
||||||
|
|
||||||
|
uid_str := hex.EncodeToString(uid)
|
||||||
|
log.Debug("card UID: ", uid_str)
|
||||||
|
|
||||||
|
c, err := db_get_card_from_uid(uid_str)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return 0, errors.New("card not found for UID")
|
||||||
|
}
|
||||||
|
|
||||||
|
// check if card is enabled
|
||||||
|
|
||||||
|
if c.enable_flag != "Y" {
|
||||||
|
return 0, errors.New("card enable is not set to Y")
|
||||||
|
}
|
||||||
|
|
||||||
|
// check cmac
|
||||||
|
|
||||||
|
sv2 := make([]byte, 16)
|
||||||
|
sv2[0] = 0x3c
|
||||||
|
sv2[1] = 0xc3
|
||||||
|
sv2[2] = 0x00
|
||||||
|
sv2[3] = 0x01
|
||||||
|
sv2[4] = 0x00
|
||||||
|
sv2[5] = 0x80
|
||||||
|
sv2[6] = uid[0]
|
||||||
|
sv2[7] = uid[1]
|
||||||
|
sv2[8] = uid[2]
|
||||||
|
sv2[9] = uid[3]
|
||||||
|
sv2[10] = uid[4]
|
||||||
|
sv2[11] = uid[5]
|
||||||
|
sv2[12] = uid[6]
|
||||||
|
sv2[13] = ctr[0]
|
||||||
|
sv2[14] = ctr[1]
|
||||||
|
sv2[15] = ctr[2]
|
||||||
|
|
||||||
|
key_sdm_file_read_mac, err := hex.DecodeString(c.aes_cmac)
|
||||||
|
if err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
|
||||||
|
cmac_verified, err := crypto_aes_cmac(key_sdm_file_read_mac, sv2, ba_c)
|
||||||
|
if err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if cmac_verified == false {
|
||||||
|
return 0, errors.New("CMAC incorrect")
|
||||||
|
}
|
||||||
|
|
||||||
|
// check and update last_counter_value
|
||||||
|
|
||||||
|
counter_ok, err := db_check_and_update_counter(c.card_id, ctr_int)
|
||||||
|
if err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
if counter_ok == false {
|
||||||
|
return 0, errors.New("counter not increasing")
|
||||||
|
}
|
||||||
|
|
||||||
|
log.WithFields(log.Fields{"card_id": c.card_id, "counter": ctr_int}).Info("validated")
|
||||||
|
|
||||||
|
return c.card_id, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func lnurlw_response(w http.ResponseWriter, req *http.Request) {
|
||||||
|
|
||||||
|
card_id, err := parse_request(req)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
log.Debug(err.Error())
|
||||||
|
|
||||||
|
w.Header().Set("Content-Type", "application/json")
|
||||||
|
w.WriteHeader(http.StatusOK)
|
||||||
|
jsonData := []byte(`{"status":"ERROR","reason":"bad request"}`)
|
||||||
|
w.Write(jsonData)
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
k1, err := create_k1()
|
||||||
|
if err != nil {
|
||||||
|
log.Warn(err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// store k1 in database and include in response
|
||||||
|
|
||||||
|
err = db_insert_payment(card_id, k1)
|
||||||
|
if err != nil {
|
||||||
|
log.Warn(err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
lnurlw_cb_url := os.Getenv("LNURLW_CB_URL")
|
||||||
|
|
||||||
|
min_withdraw_sats_str := os.Getenv("MIN_WITHDRAW_SATS")
|
||||||
|
min_withdraw_sats, err := strconv.Atoi(min_withdraw_sats_str)
|
||||||
|
if err != nil {
|
||||||
|
log.Warn(err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
max_withdraw_sats_str := os.Getenv("MAX_WITHDRAW_SATS")
|
||||||
|
max_withdraw_sats, err := strconv.Atoi(max_withdraw_sats_str)
|
||||||
|
if err != nil {
|
||||||
|
log.Warn(err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
response := Response{}
|
||||||
|
response.Tag = "withdrawRequest"
|
||||||
|
response.Callback = lnurlw_cb_url
|
||||||
|
response.K1 = k1
|
||||||
|
response.DefaultDescription = "WWT withdrawal"
|
||||||
|
response.MinWithdrawable = min_withdraw_sats * 1000 // milliSats
|
||||||
|
response.MaxWithdrawable = max_withdraw_sats * 1000 // milliSats
|
||||||
|
|
||||||
|
jsonData, err := json.Marshal(response)
|
||||||
|
if err != nil {
|
||||||
|
log.Warn(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
w.Header().Set("Content-Type", "application/json")
|
||||||
|
w.WriteHeader(http.StatusOK)
|
||||||
|
w.Write(jsonData)
|
||||||
|
}
|
||||||
27
main.go
Normal file
|
|
@ -0,0 +1,27 @@
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
log "github.com/sirupsen/logrus"
|
||||||
|
"net/http"
|
||||||
|
"os"
|
||||||
|
)
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
log_level := os.Getenv("LOG_LEVEL")
|
||||||
|
|
||||||
|
if log_level == "DEBUG" {
|
||||||
|
log.SetLevel(log.DebugLevel)
|
||||||
|
}
|
||||||
|
|
||||||
|
log.SetFormatter(&log.JSONFormatter{
|
||||||
|
DisableHTMLEscape: true,
|
||||||
|
})
|
||||||
|
|
||||||
|
mux := http.NewServeMux()
|
||||||
|
|
||||||
|
mux.HandleFunc("/ln", lnurlw_response)
|
||||||
|
mux.HandleFunc("/cb", lnurlw_callback)
|
||||||
|
|
||||||
|
err := http.ListenAndServe(":9000", mux)
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
5
s_build
Executable file
|
|
@ -0,0 +1,5 @@
|
||||||
|
go build
|
||||||
|
sudo systemctl daemon-reload
|
||||||
|
sudo systemctl stop boltcard
|
||||||
|
sudo systemctl start boltcard
|
||||||
|
|
||||||
6
s_create_db
Executable file
|
|
@ -0,0 +1,6 @@
|
||||||
|
# to close any database connections
|
||||||
|
sudo systemctl stop postgresql
|
||||||
|
sudo systemctl start postgresql
|
||||||
|
|
||||||
|
psql postgres -f create_db.sql
|
||||||
|
psql postgres -f add_card_data.sql
|
||||||
4
s_launch
Executable file
|
|
@ -0,0 +1,4 @@
|
||||||
|
export PATH=$PATH:/usr/local/go/bin
|
||||||
|
|
||||||
|
cd /home/ubuntu/boltcard
|
||||||
|
./boltcard
|
||||||
3
s_restart
Executable file
|
|
@ -0,0 +1,3 @@
|
||||||
|
sudo systemctl daemon-reload
|
||||||
|
sudo systemctl stop boltcard
|
||||||
|
sudo systemctl start boltcard
|
||||||