mirror of
https://github.com/lnbits/lnbits-legend.git
synced 2025-02-25 23:21:21 +01:00
commit
d4645496f1
19 changed files with 1512 additions and 770 deletions
|
@ -1,6 +1,9 @@
|
||||||
HOST=127.0.0.1
|
HOST=127.0.0.1
|
||||||
PORT=5000
|
PORT=5000
|
||||||
|
|
||||||
|
# uvicorn variable, allow https behind a proxy
|
||||||
|
# FORWARDED_ALLOW_IPS="*"
|
||||||
|
|
||||||
DEBUG=false
|
DEBUG=false
|
||||||
|
|
||||||
LNBITS_ALLOWED_USERS=""
|
LNBITS_ALLOWED_USERS=""
|
||||||
|
@ -13,7 +16,7 @@ LNBITS_DEFAULT_WALLET_NAME="LNbits wallet"
|
||||||
LNBITS_AD_SPACE=""
|
LNBITS_AD_SPACE=""
|
||||||
|
|
||||||
# Hides wallet api, extensions can choose to honor
|
# Hides wallet api, extensions can choose to honor
|
||||||
LNBITS_HIDE_API=false
|
LNBITS_HIDE_API=false
|
||||||
|
|
||||||
# Disable extensions for all users, use "all" to disable all extensions
|
# Disable extensions for all users, use "all" to disable all extensions
|
||||||
LNBITS_DISABLED_EXTENSIONS="amilk"
|
LNBITS_DISABLED_EXTENSIONS="amilk"
|
||||||
|
@ -67,7 +70,7 @@ LNBITS_KEY=LNBITS_ADMIN_KEY
|
||||||
LND_REST_ENDPOINT=https://127.0.0.1:8080/
|
LND_REST_ENDPOINT=https://127.0.0.1:8080/
|
||||||
LND_REST_CERT="/home/bob/.config/Zap/lnd/bitcoin/mainnet/wallet-1/data/chain/bitcoin/mainnet/tls.cert"
|
LND_REST_CERT="/home/bob/.config/Zap/lnd/bitcoin/mainnet/wallet-1/data/chain/bitcoin/mainnet/tls.cert"
|
||||||
LND_REST_MACAROON="/home/bob/.config/Zap/lnd/bitcoin/mainnet/wallet-1/data/chain/bitcoin/mainnet/admin.macaroon or HEXSTRING"
|
LND_REST_MACAROON="/home/bob/.config/Zap/lnd/bitcoin/mainnet/wallet-1/data/chain/bitcoin/mainnet/admin.macaroon or HEXSTRING"
|
||||||
# To use an AES-encrypted macaroon, set
|
# To use an AES-encrypted macaroon, set
|
||||||
# LND_REST_MACAROON_ENCRYPTED="eNcRyPtEdMaCaRoOn"
|
# LND_REST_MACAROON_ENCRYPTED="eNcRyPtEdMaCaRoOn"
|
||||||
|
|
||||||
# LNPayWallet
|
# LNPayWallet
|
||||||
|
|
31
.github/ISSUE_TEMPLATE/bug_report.md
vendored
Normal file
31
.github/ISSUE_TEMPLATE/bug_report.md
vendored
Normal file
|
@ -0,0 +1,31 @@
|
||||||
|
---
|
||||||
|
name: Bug report
|
||||||
|
about: Create a report to help us improve
|
||||||
|
title: "[BUG]"
|
||||||
|
labels: bug
|
||||||
|
assignees: ''
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
**Describe the bug**
|
||||||
|
A clear and concise description of what the bug is.
|
||||||
|
|
||||||
|
**To Reproduce**
|
||||||
|
Steps to reproduce the behavior:
|
||||||
|
1. Go to '...'
|
||||||
|
2. Click on '....'
|
||||||
|
3. Scroll down to '....'
|
||||||
|
4. See error
|
||||||
|
|
||||||
|
**Expected behavior**
|
||||||
|
A clear and concise description of what you expected to happen.
|
||||||
|
|
||||||
|
**Screenshots**
|
||||||
|
If applicable, add screenshots to help explain your problem.
|
||||||
|
|
||||||
|
**Desktop (please complete the following information):**
|
||||||
|
- LNbits version: [e.g. 0.9.2 or commit hash]
|
||||||
|
- Database [e.g. sqlite, postgres]
|
||||||
|
|
||||||
|
**Additional context**
|
||||||
|
Add any other context about the problem here.
|
20
.github/ISSUE_TEMPLATE/feature_request.md
vendored
Normal file
20
.github/ISSUE_TEMPLATE/feature_request.md
vendored
Normal file
|
@ -0,0 +1,20 @@
|
||||||
|
---
|
||||||
|
name: Feature request
|
||||||
|
about: Suggest an idea for this project
|
||||||
|
title: "[Feature request]"
|
||||||
|
labels: feature request
|
||||||
|
assignees: ''
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
**Is your feature request related to a problem? Please describe.**
|
||||||
|
A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]
|
||||||
|
|
||||||
|
**Describe the solution you'd like**
|
||||||
|
A clear and concise description of what you want to happen.
|
||||||
|
|
||||||
|
**Describe alternatives you've considered**
|
||||||
|
A clear and concise description of any alternative solutions or features you've considered.
|
||||||
|
|
||||||
|
**Additional context**
|
||||||
|
Add any other context or screenshots about the feature request here.
|
10
.github/ISSUE_TEMPLATE/something-else.md
vendored
Normal file
10
.github/ISSUE_TEMPLATE/something-else.md
vendored
Normal file
|
@ -0,0 +1,10 @@
|
||||||
|
---
|
||||||
|
name: Something else
|
||||||
|
about: Anything else that you need to say
|
||||||
|
title: ''
|
||||||
|
labels: ''
|
||||||
|
assignees: ''
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
|
|
@ -48,7 +48,9 @@ poetry run lnbits
|
||||||
# Note that you have to add the line DEBUG=true in your .env file, too.
|
# Note that you have to add the line DEBUG=true in your .env file, too.
|
||||||
```
|
```
|
||||||
|
|
||||||
## Option 2: Nix
|
## Option 2: Nix
|
||||||
|
|
||||||
|
> note: currently not supported while we make some architectural changes on the path to leave beta
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
git clone https://github.com/lnbits/lnbits-legend.git
|
git clone https://github.com/lnbits/lnbits-legend.git
|
||||||
|
@ -155,6 +157,7 @@ kill_timeout = 30
|
||||||
HOST="127.0.0.1"
|
HOST="127.0.0.1"
|
||||||
PORT=5000
|
PORT=5000
|
||||||
LNBITS_FORCE_HTTPS=true
|
LNBITS_FORCE_HTTPS=true
|
||||||
|
FORWARDED_ALLOW_IPS="*"
|
||||||
LNBITS_DATA_FOLDER="/data"
|
LNBITS_DATA_FOLDER="/data"
|
||||||
|
|
||||||
${PUT_YOUR_LNBITS_ENV_VARS_HERE}
|
${PUT_YOUR_LNBITS_ENV_VARS_HERE}
|
||||||
|
|
|
@ -15,17 +15,15 @@ def get_percent_difference(current, previous, precision=4):
|
||||||
# A helper function get a nicely formated dict for the text
|
# A helper function get a nicely formated dict for the text
|
||||||
def get_text_item_dict(text: str, font_size: int, x_pos: int = None, y_pos: int = None):
|
def get_text_item_dict(text: str, font_size: int, x_pos: int = None, y_pos: int = None):
|
||||||
# Get line size by font size
|
# Get line size by font size
|
||||||
line_width = 60
|
line_width = 20
|
||||||
if font_size <= 12:
|
if font_size <= 12:
|
||||||
line_width = 75
|
line_width = 60
|
||||||
elif font_size <= 15:
|
elif font_size <= 15:
|
||||||
line_width = 58
|
line_width = 45
|
||||||
elif font_size <= 20:
|
elif font_size <= 20:
|
||||||
line_width = 40
|
line_width = 35
|
||||||
elif font_size <= 40:
|
elif font_size <= 40:
|
||||||
line_width = 30
|
line_width = 25
|
||||||
else:
|
|
||||||
line_width = 20
|
|
||||||
|
|
||||||
# wrap the text
|
# wrap the text
|
||||||
wrapper = textwrap.TextWrapper(width=line_width)
|
wrapper = textwrap.TextWrapper(width=line_width)
|
||||||
|
@ -241,3 +239,42 @@ def get_time_remaining(seconds, granularity=2):
|
||||||
name = name.rstrip("s")
|
name = name.rstrip("s")
|
||||||
result.append("{} {}".format(round(value), name))
|
result.append("{} {}".format(round(value), name))
|
||||||
return ", ".join(result[:granularity])
|
return ", ".join(result[:granularity])
|
||||||
|
|
||||||
|
|
||||||
|
async def get_mining_stat(stat_slug: str, gerty):
|
||||||
|
text = []
|
||||||
|
if stat_slug == "mining_current_hash_rate":
|
||||||
|
stat = await api_get_mining_stat(stat_slug, gerty)
|
||||||
|
logger.debug(stat)
|
||||||
|
current = "{0}hash".format(si_format(stat['current'], 6, True, " "))
|
||||||
|
text.append(get_text_item_dict("Current Mining Hashrate", 20))
|
||||||
|
text.append(get_text_item_dict(current, 40))
|
||||||
|
# compare vs previous time period
|
||||||
|
difference = get_percent_difference(current=stat['current'], previous=stat['1w'])
|
||||||
|
text.append(get_text_item_dict("{0} in last 7 days".format(difference), 12))
|
||||||
|
elif stat_slug == "mining_current_difficulty":
|
||||||
|
stat = await api_get_mining_stat(stat_slug, gerty)
|
||||||
|
text.append(get_text_item_dict("Current Mining Difficulty", 20))
|
||||||
|
text.append(get_text_item_dict(format_number(stat['current']), 40))
|
||||||
|
difference = get_percent_difference(current=stat['current'], previous=stat['previous'])
|
||||||
|
text.append(get_text_item_dict("{0} since last adjustment".format(difference), 12))
|
||||||
|
# text.append(get_text_item_dict("Required threshold for mining proof-of-work", 12))
|
||||||
|
return text
|
||||||
|
|
||||||
|
async def api_get_mining_stat(stat_slug: str, gerty):
|
||||||
|
stat = ""
|
||||||
|
if stat_slug == "mining_current_hash_rate":
|
||||||
|
async with httpx.AsyncClient() as client:
|
||||||
|
r = await client.get(gerty.mempool_endpoint + "/api/v1/mining/hashrate/1m")
|
||||||
|
data = r.json()
|
||||||
|
stat = {}
|
||||||
|
stat['current'] = data['currentHashrate']
|
||||||
|
stat['1w'] = data['hashrates'][len(data['hashrates']) - 7]['avgHashrate']
|
||||||
|
elif stat_slug == "mining_current_difficulty":
|
||||||
|
async with httpx.AsyncClient() as client:
|
||||||
|
r = await client.get(gerty.mempool_endpoint + "/api/v1/mining/hashrate/1m")
|
||||||
|
data = r.json()
|
||||||
|
stat = {}
|
||||||
|
stat['current'] = data['currentDifficulty']
|
||||||
|
stat['previous'] = data['difficulty'][len(data['difficulty']) - 2]['difficulty']
|
||||||
|
return stat
|
|
@ -17,10 +17,9 @@ async def m001_initial(db):
|
||||||
"""
|
"""
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
async def m002_add_utc_offset_col(db):
|
async def m002_add_utc_offset_col(db):
|
||||||
"""
|
"""
|
||||||
support for UTC offset
|
support for UTC offset
|
||||||
"""
|
"""
|
||||||
await db.execute(
|
await db.execute("ALTER TABLE gerty.gertys ADD COLUMN utc_offset INT;")
|
||||||
"ALTER TABLE gerty.gertys ADD COLUMN utc_offset INT;"
|
|
||||||
)
|
|
||||||
|
|
File diff suppressed because it is too large
Load diff
|
@ -153,8 +153,10 @@ async def api_gerty_json(gerty_id: str, p: int = None): # page number
|
||||||
|
|
||||||
# Get a screen slug by its position in the screens_list
|
# Get a screen slug by its position in the screens_list
|
||||||
def get_screen_slug_by_index(index: int, screens_list):
|
def get_screen_slug_by_index(index: int, screens_list):
|
||||||
if(index < len(screens_list) - 1):
|
logger.debug("Index: {0}".format(index))
|
||||||
return list(screens_list)[index]
|
logger.debug("len(screens_list) - 1: {0} ".format(len(screens_list) - 1))
|
||||||
|
if index <= len(screens_list) - 1:
|
||||||
|
return list(screens_list)[index - 1]
|
||||||
else:
|
else:
|
||||||
return None
|
return None
|
||||||
|
|
||||||
|
@ -171,16 +173,37 @@ async def get_screen_data(screen_num: int, screens_list: dict, gerty):
|
||||||
if screen_slug == "dashboard":
|
if screen_slug == "dashboard":
|
||||||
title = gerty.name
|
title = gerty.name
|
||||||
areas = await get_dashboard(gerty)
|
areas = await get_dashboard(gerty)
|
||||||
|
if screen_slug == "lnbits_wallets_balance":
|
||||||
|
wallets = await get_lnbits_wallet_balances(gerty)
|
||||||
|
text = []
|
||||||
|
for wallet in wallets:
|
||||||
|
text.append(get_text_item_dict("{0}'s Wallet".format(wallet['name']), 20))
|
||||||
|
text.append(get_text_item_dict("{0} sats".format(format_number(wallet['balance'])), 40))
|
||||||
|
areas.append(text)
|
||||||
elif screen_slug == "fun_satoshi_quotes":
|
elif screen_slug == "fun_satoshi_quotes":
|
||||||
areas.append(await get_satoshi_quotes())
|
areas.append(await get_satoshi_quotes())
|
||||||
elif screen_slug == "fun_exchange_market_rate":
|
elif screen_slug == "fun_exchange_market_rate":
|
||||||
areas.append(await get_exchange_rate(gerty))
|
areas.append(await get_exchange_rate(gerty))
|
||||||
elif screen_slug == "onchain_dashboard":
|
elif screen_slug == "onchain_difficulty_epoch_progress":
|
||||||
|
areas.append(await get_onchain_stat(screen_slug, gerty))
|
||||||
|
elif screen_slug == "onchain_difficulty_retarget_date":
|
||||||
|
areas.append(await get_onchain_stat(screen_slug, gerty))
|
||||||
|
elif screen_slug == "onchain_difficulty_blocks_remaining":
|
||||||
|
areas.append(await get_onchain_stat(screen_slug, gerty))
|
||||||
|
elif screen_slug == "onchain_difficulty_epoch_time_remaining":
|
||||||
|
areas.append(await get_onchain_stat(screen_slug, gerty))
|
||||||
|
elif screen_slug == "dashboard_onchain":
|
||||||
title = "Onchain Data"
|
title = "Onchain Data"
|
||||||
areas = await get_onchain_dashboard(gerty)
|
areas = await get_onchain_dashboard(gerty)
|
||||||
elif screen_slug == "mempool_recommended_fees":
|
elif screen_slug == "mempool_recommended_fees":
|
||||||
areas.append(await get_mempool_stat(screen_slug, gerty))
|
areas.append(await get_mempool_stat(screen_slug, gerty))
|
||||||
elif screen_slug == "mining_dashboard":
|
elif screen_slug == "mempool_tx_count":
|
||||||
|
areas.append(await get_mempool_stat(screen_slug, gerty))
|
||||||
|
elif screen_slug == "mining_current_hash_rate":
|
||||||
|
areas.append(await get_mining_stat(screen_slug, gerty))
|
||||||
|
elif screen_slug == "mining_current_difficulty":
|
||||||
|
areas.append(await get_mining_stat(screen_slug, gerty))
|
||||||
|
elif screen_slug == "dashboard_mining":
|
||||||
title = "Mining Data"
|
title = "Mining Data"
|
||||||
areas = await get_mining_dashboard(gerty)
|
areas = await get_mining_dashboard(gerty)
|
||||||
elif screen_slug == "lightning_dashboard":
|
elif screen_slug == "lightning_dashboard":
|
||||||
|
@ -290,6 +313,34 @@ async def get_exchange_rate(gerty):
|
||||||
pass
|
pass
|
||||||
return text
|
return text
|
||||||
|
|
||||||
|
async def get_onchain_stat(stat_slug: str, gerty):
|
||||||
|
text = []
|
||||||
|
if (
|
||||||
|
stat_slug == "onchain_difficulty_epoch_progress" or
|
||||||
|
stat_slug == "onchain_difficulty_retarget_date" or
|
||||||
|
stat_slug == "onchain_difficulty_blocks_remaining" or
|
||||||
|
stat_slug == "onchain_difficulty_epoch_time_remaining"
|
||||||
|
):
|
||||||
|
async with httpx.AsyncClient() as client:
|
||||||
|
r = await client.get(gerty.mempool_endpoint + "/api/v1/difficulty-adjustment")
|
||||||
|
if stat_slug == "onchain_difficulty_epoch_progress":
|
||||||
|
stat = round(r.json()['progressPercent'])
|
||||||
|
text.append(get_text_item_dict("Progress through current difficulty epoch", 15))
|
||||||
|
text.append(get_text_item_dict("{0}%".format(stat), 80))
|
||||||
|
elif stat_slug == "onchain_difficulty_retarget_date":
|
||||||
|
stat = r.json()['estimatedRetargetDate']
|
||||||
|
dt = datetime.fromtimestamp(stat / 1000).strftime("%e %b %Y at %H:%M")
|
||||||
|
text.append(get_text_item_dict("Estimated date of next difficulty adjustment", 15))
|
||||||
|
text.append(get_text_item_dict(dt, 40))
|
||||||
|
elif stat_slug == "onchain_difficulty_blocks_remaining":
|
||||||
|
stat = r.json()['remainingBlocks']
|
||||||
|
text.append(get_text_item_dict("Blocks remaining until next difficulty adjustment", 15))
|
||||||
|
text.append(get_text_item_dict("{0}".format(format_number(stat)), 80))
|
||||||
|
elif stat_slug == "onchain_difficulty_epoch_time_remaining":
|
||||||
|
stat = r.json()['remainingTime']
|
||||||
|
text.append(get_text_item_dict("Blocks remaining until next difficulty adjustment", 15))
|
||||||
|
text.append(get_text_item_dict(get_time_remaining(stat / 1000, 4), 20))
|
||||||
|
return text
|
||||||
|
|
||||||
async def get_onchain_dashboard(gerty):
|
async def get_onchain_dashboard(gerty):
|
||||||
areas = []
|
areas = []
|
||||||
|
@ -300,39 +351,27 @@ async def get_onchain_dashboard(gerty):
|
||||||
)
|
)
|
||||||
text = []
|
text = []
|
||||||
stat = round(r.json()["progressPercent"])
|
stat = round(r.json()["progressPercent"])
|
||||||
text.append(
|
text.append(get_text_item_dict("Progress through epoch", 12))
|
||||||
get_text_item_dict("Progress through current epoch", 12)
|
|
||||||
)
|
|
||||||
text.append(get_text_item_dict("{0}%".format(stat), 60))
|
text.append(get_text_item_dict("{0}%".format(stat), 60))
|
||||||
areas.append(text)
|
areas.append(text)
|
||||||
|
|
||||||
text = []
|
text = []
|
||||||
stat = r.json()["estimatedRetargetDate"]
|
stat = r.json()["estimatedRetargetDate"]
|
||||||
dt = datetime.fromtimestamp(stat / 1000).strftime("%e %b %Y at %H:%M")
|
dt = datetime.fromtimestamp(stat / 1000).strftime("%e %b %Y at %H:%M")
|
||||||
text.append(
|
text.append(get_text_item_dict("Date of next adjustment", 12))
|
||||||
get_text_item_dict("Date of next difficulty adjustment", 12)
|
|
||||||
)
|
|
||||||
text.append(get_text_item_dict(dt, 20))
|
text.append(get_text_item_dict(dt, 20))
|
||||||
areas.append(text)
|
areas.append(text)
|
||||||
|
|
||||||
text = []
|
text = []
|
||||||
stat = r.json()["remainingBlocks"]
|
stat = r.json()["remainingBlocks"]
|
||||||
text.append(
|
text.append(get_text_item_dict("Blocks until adjustment", 12))
|
||||||
get_text_item_dict(
|
|
||||||
"Blocks until next adjustment", 12
|
|
||||||
)
|
|
||||||
)
|
|
||||||
text.append(get_text_item_dict("{0}".format(format_number(stat)), 60))
|
text.append(get_text_item_dict("{0}".format(format_number(stat)), 60))
|
||||||
areas.append(text)
|
areas.append(text)
|
||||||
|
|
||||||
text = []
|
text = []
|
||||||
stat = r.json()["remainingTime"]
|
stat = r.json()["remainingTime"]
|
||||||
text.append(
|
text.append(get_text_item_dict("Time until adjustment", 12))
|
||||||
get_text_item_dict(
|
text.append(get_text_item_dict(get_time_remaining(stat / 1000, 4), 20))
|
||||||
"Blocks until next adjustment", 12
|
|
||||||
)
|
|
||||||
)
|
|
||||||
text.append(get_text_item_dict(get_time_remaining(stat / 1000, 4), 60))
|
|
||||||
areas.append(text)
|
areas.append(text)
|
||||||
|
|
||||||
return areas
|
return areas
|
||||||
|
@ -379,16 +418,16 @@ async def get_mempool_stat(stat_slug: str, gerty):
|
||||||
|
|
||||||
pos_y = 280 + y_offset
|
pos_y = 280 + y_offset
|
||||||
text.append(
|
text.append(
|
||||||
get_text_item_dict("{0}".format("No Priority"), 15, 30, pos_y)
|
get_text_item_dict("{0}".format("None"), 15, 30, pos_y)
|
||||||
)
|
)
|
||||||
text.append(
|
text.append(
|
||||||
get_text_item_dict("{0}".format("Low Priority"), 15, 235, pos_y)
|
get_text_item_dict("{0}".format("Low"), 15, 235, pos_y)
|
||||||
)
|
)
|
||||||
text.append(
|
text.append(
|
||||||
get_text_item_dict("{0}".format("Medium Priority"), 15, 460, pos_y)
|
get_text_item_dict("{0}".format("Medium"), 15, 460, pos_y)
|
||||||
)
|
)
|
||||||
text.append(
|
text.append(
|
||||||
get_text_item_dict("{0}".format("High Priority"), 15, 750, pos_y)
|
get_text_item_dict("{0}".format("High"), 15, 750, pos_y)
|
||||||
)
|
)
|
||||||
|
|
||||||
pos_y = 340 + y_offset
|
pos_y = 340 + y_offset
|
||||||
|
@ -450,3 +489,30 @@ async def get_mempool_stat(stat_slug: str, gerty):
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
return text
|
return text
|
||||||
|
|
||||||
|
|
||||||
|
def get_date_suffix(dayNumber):
|
||||||
|
if 4 <= dayNumber <= 20 or 24 <= dayNumber <= 30:
|
||||||
|
return "th"
|
||||||
|
else:
|
||||||
|
return ["st", "nd", "rd"][dayNumber % 10 - 1]
|
||||||
|
|
||||||
|
def get_time_remaining(seconds, granularity=2):
|
||||||
|
intervals = (
|
||||||
|
# ('weeks', 604800), # 60 * 60 * 24 * 7
|
||||||
|
('days', 86400), # 60 * 60 * 24
|
||||||
|
('hours', 3600), # 60 * 60
|
||||||
|
('minutes', 60),
|
||||||
|
('seconds', 1),
|
||||||
|
)
|
||||||
|
|
||||||
|
result = []
|
||||||
|
|
||||||
|
for name, count in intervals:
|
||||||
|
value = seconds // count
|
||||||
|
if value:
|
||||||
|
seconds -= value * count
|
||||||
|
if value == 1:
|
||||||
|
name = name.rstrip('s')
|
||||||
|
result.append("{} {}".format(round(value), name))
|
||||||
|
return ', '.join(result[:granularity])
|
||||||
|
|
|
@ -23,9 +23,22 @@ async def create_lnurldevice(
|
||||||
currency,
|
currency,
|
||||||
device,
|
device,
|
||||||
profit,
|
profit,
|
||||||
amount
|
amount,
|
||||||
|
pin,
|
||||||
|
profit1,
|
||||||
|
amount1,
|
||||||
|
pin1,
|
||||||
|
profit2,
|
||||||
|
amount2,
|
||||||
|
pin2,
|
||||||
|
profit3,
|
||||||
|
amount3,
|
||||||
|
pin3,
|
||||||
|
profit4,
|
||||||
|
amount4,
|
||||||
|
pin4
|
||||||
)
|
)
|
||||||
VALUES (?, ?, ?, ?, ?, ?, ?, ?)
|
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
||||||
""",
|
""",
|
||||||
(
|
(
|
||||||
lnurldevice_id,
|
lnurldevice_id,
|
||||||
|
@ -36,6 +49,19 @@ async def create_lnurldevice(
|
||||||
data.device,
|
data.device,
|
||||||
data.profit,
|
data.profit,
|
||||||
data.amount,
|
data.amount,
|
||||||
|
data.pin,
|
||||||
|
data.profit1,
|
||||||
|
data.amount1,
|
||||||
|
data.pin1,
|
||||||
|
data.profit2,
|
||||||
|
data.amount2,
|
||||||
|
data.pin2,
|
||||||
|
data.profit3,
|
||||||
|
data.amount3,
|
||||||
|
data.pin3,
|
||||||
|
data.profit4,
|
||||||
|
data.amount4,
|
||||||
|
data.pin4,
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
return await get_lnurldevice(lnurldevice_id)
|
return await get_lnurldevice(lnurldevice_id)
|
||||||
|
|
|
@ -8,6 +8,7 @@ from typing import Optional
|
||||||
from embit import bech32, compact
|
from embit import bech32, compact
|
||||||
from fastapi import Request
|
from fastapi import Request
|
||||||
from fastapi.param_functions import Query
|
from fastapi.param_functions import Query
|
||||||
|
from loguru import logger
|
||||||
from starlette.exceptions import HTTPException
|
from starlette.exceptions import HTTPException
|
||||||
|
|
||||||
from lnbits.core.services import create_invoice
|
from lnbits.core.services import create_invoice
|
||||||
|
@ -91,6 +92,9 @@ async def lnurl_v1_params(
|
||||||
device_id: str = Query(None),
|
device_id: str = Query(None),
|
||||||
p: str = Query(None),
|
p: str = Query(None),
|
||||||
atm: str = Query(None),
|
atm: str = Query(None),
|
||||||
|
gpio: str = Query(None),
|
||||||
|
profit: str = Query(None),
|
||||||
|
amount: str = Query(None),
|
||||||
):
|
):
|
||||||
device = await get_lnurldevice(device_id)
|
device = await get_lnurldevice(device_id)
|
||||||
if not device:
|
if not device:
|
||||||
|
@ -105,16 +109,24 @@ async def lnurl_v1_params(
|
||||||
if device.device == "switch":
|
if device.device == "switch":
|
||||||
|
|
||||||
price_msat = (
|
price_msat = (
|
||||||
await fiat_amount_as_satoshis(float(device.profit), device.currency)
|
await fiat_amount_as_satoshis(float(profit), device.currency)
|
||||||
if device.currency != "sat"
|
if device.currency != "sat"
|
||||||
else amount_in_cent
|
else amount_in_cent
|
||||||
) * 1000
|
) * 1000
|
||||||
|
|
||||||
|
# Check they're not trying to trick the switch!
|
||||||
|
check = False
|
||||||
|
for switch in device.switches(request):
|
||||||
|
if switch[0] == gpio and switch[1] == profit and switch[2] == amount:
|
||||||
|
check = True
|
||||||
|
if not check:
|
||||||
|
return {"status": "ERROR", "reason": f"Switch params wrong"}
|
||||||
|
|
||||||
lnurldevicepayment = await create_lnurldevicepayment(
|
lnurldevicepayment = await create_lnurldevicepayment(
|
||||||
deviceid=device.id,
|
deviceid=device.id,
|
||||||
payload="bla",
|
payload=amount,
|
||||||
sats=price_msat,
|
sats=price_msat,
|
||||||
pin=1,
|
pin=gpio,
|
||||||
payhash="bla",
|
payhash="bla",
|
||||||
)
|
)
|
||||||
if not lnurldevicepayment:
|
if not lnurldevicepayment:
|
||||||
|
@ -126,7 +138,7 @@ async def lnurl_v1_params(
|
||||||
),
|
),
|
||||||
"minSendable": price_msat,
|
"minSendable": price_msat,
|
||||||
"maxSendable": price_msat,
|
"maxSendable": price_msat,
|
||||||
"metadata": await device.lnurlpay_metadata(),
|
"metadata": device.lnurlpay_metadata,
|
||||||
}
|
}
|
||||||
if len(p) % 4 > 0:
|
if len(p) % 4 > 0:
|
||||||
p += "=" * (4 - (len(p) % 4))
|
p += "=" * (4 - (len(p) % 4))
|
||||||
|
@ -188,7 +200,7 @@ async def lnurl_v1_params(
|
||||||
),
|
),
|
||||||
"minSendable": price_msat * 1000,
|
"minSendable": price_msat * 1000,
|
||||||
"maxSendable": price_msat * 1000,
|
"maxSendable": price_msat * 1000,
|
||||||
"metadata": await device.lnurlpay_metadata(),
|
"metadata": device.lnurlpay_metadata,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -233,11 +245,17 @@ async def lnurl_callback(
|
||||||
if device.device == "switch":
|
if device.device == "switch":
|
||||||
payment_hash, payment_request = await create_invoice(
|
payment_hash, payment_request = await create_invoice(
|
||||||
wallet_id=device.wallet,
|
wallet_id=device.wallet,
|
||||||
amount=lnurldevicepayment.sats / 1000,
|
amount=int(lnurldevicepayment.sats / 1000),
|
||||||
memo=device.title + "-" + lnurldevicepayment.id,
|
memo=device.id + " PIN " + str(lnurldevicepayment.pin),
|
||||||
unhashed_description=(await device.lnurlpay_metadata()).encode("utf-8"),
|
unhashed_description=device.lnurlpay_metadata.encode("utf-8"),
|
||||||
extra={"tag": "Switch", "id": paymentid, "time": device.amount},
|
extra={
|
||||||
|
"tag": "Switch",
|
||||||
|
"pin": str(lnurldevicepayment.pin),
|
||||||
|
"amount": str(lnurldevicepayment.payload),
|
||||||
|
"id": paymentid,
|
||||||
|
},
|
||||||
)
|
)
|
||||||
|
|
||||||
lnurldevicepayment = await update_lnurldevicepayment(
|
lnurldevicepayment = await update_lnurldevicepayment(
|
||||||
lnurldevicepayment_id=paymentid, payhash=payment_hash
|
lnurldevicepayment_id=paymentid, payhash=payment_hash
|
||||||
)
|
)
|
||||||
|
@ -248,9 +266,9 @@ async def lnurl_callback(
|
||||||
|
|
||||||
payment_hash, payment_request = await create_invoice(
|
payment_hash, payment_request = await create_invoice(
|
||||||
wallet_id=device.wallet,
|
wallet_id=device.wallet,
|
||||||
amount=lnurldevicepayment.sats / 1000,
|
amount=int(lnurldevicepayment.sats / 1000),
|
||||||
memo=device.title,
|
memo=device.title,
|
||||||
unhashed_description=(await device.lnurlpay_metadata()).encode("utf-8"),
|
unhashed_description=device.lnurlpay_metadata.encode("utf-8"),
|
||||||
extra={"tag": "PoS"},
|
extra={"tag": "PoS"},
|
||||||
)
|
)
|
||||||
lnurldevicepayment = await update_lnurldevicepayment(
|
lnurldevicepayment = await update_lnurldevicepayment(
|
||||||
|
|
|
@ -88,3 +88,52 @@ async def m003_redux(db):
|
||||||
await db.execute(
|
await db.execute(
|
||||||
"ALTER TABLE lnurldevice.lnurldevices ADD COLUMN amount INT DEFAULT 0;"
|
"ALTER TABLE lnurldevice.lnurldevices ADD COLUMN amount INT DEFAULT 0;"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
async def m004_redux(db):
|
||||||
|
"""
|
||||||
|
Add 'meta' for storing various metadata about the wallet
|
||||||
|
"""
|
||||||
|
await db.execute(
|
||||||
|
"ALTER TABLE lnurldevice.lnurldevices ADD COLUMN pin INT DEFAULT 0"
|
||||||
|
)
|
||||||
|
|
||||||
|
await db.execute(
|
||||||
|
"ALTER TABLE lnurldevice.lnurldevices ADD COLUMN profit1 FLOAT DEFAULT 0"
|
||||||
|
)
|
||||||
|
await db.execute(
|
||||||
|
"ALTER TABLE lnurldevice.lnurldevices ADD COLUMN amount1 INT DEFAULT 0"
|
||||||
|
)
|
||||||
|
await db.execute(
|
||||||
|
"ALTER TABLE lnurldevice.lnurldevices ADD COLUMN pin1 INT DEFAULT 0"
|
||||||
|
)
|
||||||
|
|
||||||
|
await db.execute(
|
||||||
|
"ALTER TABLE lnurldevice.lnurldevices ADD COLUMN profit2 FLOAT DEFAULT 0"
|
||||||
|
)
|
||||||
|
await db.execute(
|
||||||
|
"ALTER TABLE lnurldevice.lnurldevices ADD COLUMN amount2 INT DEFAULT 0"
|
||||||
|
)
|
||||||
|
await db.execute(
|
||||||
|
"ALTER TABLE lnurldevice.lnurldevices ADD COLUMN pin2 INT DEFAULT 0"
|
||||||
|
)
|
||||||
|
|
||||||
|
await db.execute(
|
||||||
|
"ALTER TABLE lnurldevice.lnurldevices ADD COLUMN profit3 FLOAT DEFAULT 0"
|
||||||
|
)
|
||||||
|
await db.execute(
|
||||||
|
"ALTER TABLE lnurldevice.lnurldevices ADD COLUMN amount3 INT DEFAULT 0"
|
||||||
|
)
|
||||||
|
await db.execute(
|
||||||
|
"ALTER TABLE lnurldevice.lnurldevices ADD COLUMN pin3 INT DEFAULT 0"
|
||||||
|
)
|
||||||
|
|
||||||
|
await db.execute(
|
||||||
|
"ALTER TABLE lnurldevice.lnurldevices ADD COLUMN profit4 FLOAT DEFAULT 0"
|
||||||
|
)
|
||||||
|
await db.execute(
|
||||||
|
"ALTER TABLE lnurldevice.lnurldevices ADD COLUMN amount4 INT DEFAULT 0"
|
||||||
|
)
|
||||||
|
await db.execute(
|
||||||
|
"ALTER TABLE lnurldevice.lnurldevices ADD COLUMN pin4 INT DEFAULT 0"
|
||||||
|
)
|
||||||
|
|
|
@ -1,12 +1,13 @@
|
||||||
import json
|
import json
|
||||||
from sqlite3 import Row
|
from sqlite3 import Row
|
||||||
from typing import Optional
|
from typing import List, Optional
|
||||||
|
|
||||||
from fastapi import Request
|
from fastapi import Request
|
||||||
from lnurl import Lnurl
|
from lnurl import Lnurl
|
||||||
from lnurl import encode as lnurl_encode # type: ignore
|
from lnurl import encode as lnurl_encode # type: ignore
|
||||||
from lnurl.models import LnurlPaySuccessAction, UrlAction # type: ignore
|
from lnurl.models import LnurlPaySuccessAction, UrlAction # type: ignore
|
||||||
from lnurl.types import LnurlPayMetadata # type: ignore
|
from lnurl.types import LnurlPayMetadata # type: ignore
|
||||||
|
from loguru import logger
|
||||||
from pydantic import BaseModel
|
from pydantic import BaseModel
|
||||||
from pydantic.main import BaseModel
|
from pydantic.main import BaseModel
|
||||||
|
|
||||||
|
@ -18,6 +19,19 @@ class createLnurldevice(BaseModel):
|
||||||
device: str
|
device: str
|
||||||
profit: float
|
profit: float
|
||||||
amount: int
|
amount: int
|
||||||
|
pin: int = 0
|
||||||
|
profit1: float = 0
|
||||||
|
amount1: int = 0
|
||||||
|
pin1: int = 0
|
||||||
|
profit2: float = 0
|
||||||
|
amount2: int = 0
|
||||||
|
pin2: int = 0
|
||||||
|
profit3: float = 0
|
||||||
|
amount3: int = 0
|
||||||
|
pin3: int = 0
|
||||||
|
profit4: float = 0
|
||||||
|
amount4: int = 0
|
||||||
|
pin4: int = 0
|
||||||
|
|
||||||
|
|
||||||
class lnurldevices(BaseModel):
|
class lnurldevices(BaseModel):
|
||||||
|
@ -29,18 +43,122 @@ class lnurldevices(BaseModel):
|
||||||
device: str
|
device: str
|
||||||
profit: float
|
profit: float
|
||||||
amount: int
|
amount: int
|
||||||
|
pin: int
|
||||||
|
profit1: float
|
||||||
|
amount1: int
|
||||||
|
pin1: int
|
||||||
|
profit2: float
|
||||||
|
amount2: int
|
||||||
|
pin2: int
|
||||||
|
profit3: float
|
||||||
|
amount3: int
|
||||||
|
pin3: int
|
||||||
|
profit4: float
|
||||||
|
amount4: int
|
||||||
|
pin4: int
|
||||||
timestamp: str
|
timestamp: str
|
||||||
|
|
||||||
def from_row(cls, row: Row) -> "lnurldevices":
|
def from_row(cls, row: Row) -> "lnurldevices":
|
||||||
return cls(**dict(row))
|
return cls(**dict(row))
|
||||||
|
|
||||||
def lnurl(self, req: Request) -> Lnurl:
|
@property
|
||||||
url = req.url_for("lnurldevice.lnurl_v1_params", device_id=self.id)
|
def lnurlpay_metadata(self) -> LnurlPayMetadata:
|
||||||
return lnurl_encode(url)
|
|
||||||
|
|
||||||
async def lnurlpay_metadata(self) -> LnurlPayMetadata:
|
|
||||||
return LnurlPayMetadata(json.dumps([["text/plain", self.title]]))
|
return LnurlPayMetadata(json.dumps([["text/plain", self.title]]))
|
||||||
|
|
||||||
|
def switches(self, req: Request) -> List:
|
||||||
|
switches = []
|
||||||
|
if self.profit > 0:
|
||||||
|
url = req.url_for("lnurldevice.lnurl_v1_params", device_id=self.id)
|
||||||
|
switches.append(
|
||||||
|
[
|
||||||
|
str(self.pin),
|
||||||
|
str(self.profit),
|
||||||
|
str(self.amount),
|
||||||
|
lnurl_encode(
|
||||||
|
url
|
||||||
|
+ "?gpio="
|
||||||
|
+ str(self.pin)
|
||||||
|
+ "&profit="
|
||||||
|
+ str(self.profit)
|
||||||
|
+ "&amount="
|
||||||
|
+ str(self.amount)
|
||||||
|
),
|
||||||
|
]
|
||||||
|
)
|
||||||
|
if self.profit1 > 0:
|
||||||
|
url = req.url_for("lnurldevice.lnurl_v1_params", device_id=self.id)
|
||||||
|
switches.append(
|
||||||
|
[
|
||||||
|
str(self.pin1),
|
||||||
|
str(self.profit1),
|
||||||
|
str(self.amount1),
|
||||||
|
lnurl_encode(
|
||||||
|
url
|
||||||
|
+ "?gpio="
|
||||||
|
+ str(self.pin1)
|
||||||
|
+ "&profit="
|
||||||
|
+ str(self.profit1)
|
||||||
|
+ "&amount="
|
||||||
|
+ str(self.amount1)
|
||||||
|
),
|
||||||
|
]
|
||||||
|
)
|
||||||
|
if self.profit2 > 0:
|
||||||
|
url = req.url_for("lnurldevice.lnurl_v1_params", device_id=self.id)
|
||||||
|
switches.append(
|
||||||
|
[
|
||||||
|
str(self.pin2),
|
||||||
|
str(self.profit2),
|
||||||
|
str(self.amount2),
|
||||||
|
lnurl_encode(
|
||||||
|
url
|
||||||
|
+ "?gpio="
|
||||||
|
+ str(self.pin2)
|
||||||
|
+ "&profit="
|
||||||
|
+ str(self.profit2)
|
||||||
|
+ "&amount="
|
||||||
|
+ str(self.amount2)
|
||||||
|
),
|
||||||
|
]
|
||||||
|
)
|
||||||
|
if self.profit3 > 0:
|
||||||
|
url = req.url_for("lnurldevice.lnurl_v1_params", device_id=self.id)
|
||||||
|
switches.append(
|
||||||
|
[
|
||||||
|
str(self.pin3),
|
||||||
|
str(self.profit3),
|
||||||
|
str(self.amount3),
|
||||||
|
lnurl_encode(
|
||||||
|
url
|
||||||
|
+ "?gpio="
|
||||||
|
+ str(self.pin3)
|
||||||
|
+ "&profit="
|
||||||
|
+ str(self.profit3)
|
||||||
|
+ "&amount="
|
||||||
|
+ str(self.amount3)
|
||||||
|
),
|
||||||
|
]
|
||||||
|
)
|
||||||
|
if self.profit4 > 0:
|
||||||
|
url = req.url_for("lnurldevice.lnurl_v1_params", device_id=self.id)
|
||||||
|
switches.append(
|
||||||
|
[
|
||||||
|
str(self.pin4),
|
||||||
|
str(self.profit4),
|
||||||
|
str(self.amount4),
|
||||||
|
lnurl_encode(
|
||||||
|
url
|
||||||
|
+ "?gpio="
|
||||||
|
+ str(self.pin4)
|
||||||
|
+ "&profit="
|
||||||
|
+ str(self.profit4)
|
||||||
|
+ "&amount="
|
||||||
|
+ str(self.amount4)
|
||||||
|
),
|
||||||
|
]
|
||||||
|
)
|
||||||
|
return switches
|
||||||
|
|
||||||
|
|
||||||
class lnurldevicepayment(BaseModel):
|
class lnurldevicepayment(BaseModel):
|
||||||
id: str
|
id: str
|
||||||
|
|
|
@ -36,5 +36,9 @@ async def on_invoice_paid(payment: Payment) -> None:
|
||||||
lnurldevicepayment = await update_lnurldevicepayment(
|
lnurldevicepayment = await update_lnurldevicepayment(
|
||||||
lnurldevicepayment_id=payment.extra.get("id"), payhash="used"
|
lnurldevicepayment_id=payment.extra.get("id"), payhash="used"
|
||||||
)
|
)
|
||||||
return await updater(lnurldevicepayment.deviceid)
|
return await updater(
|
||||||
|
lnurldevicepayment.deviceid,
|
||||||
|
lnurldevicepayment.pin,
|
||||||
|
lnurldevicepayment.payload,
|
||||||
|
)
|
||||||
return
|
return
|
||||||
|
|
|
@ -105,7 +105,7 @@
|
||||||
@click="openQrCodeDialog(props.row.id)"
|
@click="openQrCodeDialog(props.row.id)"
|
||||||
><q-tooltip v-if="protocol == 'http:'">
|
><q-tooltip v-if="protocol == 'http:'">
|
||||||
LNURLs only work over HTTPS </q-tooltip
|
LNURLs only work over HTTPS </q-tooltip
|
||||||
><q-tooltip v-else> view LNURL </q-tooltip></q-btn
|
><q-tooltip v-else> view LNURLS </q-tooltip></q-btn
|
||||||
>
|
>
|
||||||
</q-td>
|
</q-td>
|
||||||
<q-td
|
<q-td
|
||||||
|
@ -230,29 +230,221 @@
|
||||||
label="Profit margin (% added to invoices/deducted from faucets)"
|
label="Profit margin (% added to invoices/deducted from faucets)"
|
||||||
></q-input>
|
></q-input>
|
||||||
<div v-else>
|
<div v-else>
|
||||||
<q-input
|
<q-btn
|
||||||
ref="setAmount"
|
unelevated
|
||||||
filled
|
class="q-mb-lg"
|
||||||
dense
|
round
|
||||||
v-model.trim="formDialoglnurldevice.data.profit"
|
size="sm"
|
||||||
class="q-pb-md"
|
icon="add"
|
||||||
:label="'Amount (' + formDialoglnurldevice.data.currency + ') *'"
|
@click="addSwitch"
|
||||||
:mask="'#.##'"
|
v-model="switches"
|
||||||
fill-mask="0"
|
color="primary"
|
||||||
reverse-fill-mask
|
></q-btn>
|
||||||
:step="'0.01'"
|
<q-btn
|
||||||
value="0.00"
|
unelevated
|
||||||
></q-input>
|
class="q-mb-lg"
|
||||||
<q-input
|
round
|
||||||
filled
|
size="sm"
|
||||||
dense
|
icon="remove"
|
||||||
v-model.trim="formDialoglnurldevice.data.amount"
|
@click="removeSwitch"
|
||||||
type="number"
|
v-model="switches"
|
||||||
value="1000"
|
color="primary"
|
||||||
label="milesecs to turn Switch on for (1sec = 1000ms)"
|
></q-btn>
|
||||||
></q-input>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
|
<div v-if="switches >= 0">
|
||||||
|
<div class="row">
|
||||||
|
<div class="col">
|
||||||
|
<q-input
|
||||||
|
ref="setAmount"
|
||||||
|
filled
|
||||||
|
dense
|
||||||
|
v-model.trim="formDialoglnurldevice.data.profit"
|
||||||
|
class="q-pb-md"
|
||||||
|
:label="'Amount (' + formDialoglnurldevice.data.currency + ') *'"
|
||||||
|
:mask="'#.##'"
|
||||||
|
fill-mask="0"
|
||||||
|
reverse-fill-mask
|
||||||
|
:step="'0.01'"
|
||||||
|
value="0.00"
|
||||||
|
></q-input>
|
||||||
|
</div>
|
||||||
|
<div class="col q-ml-md">
|
||||||
|
<q-input
|
||||||
|
filled
|
||||||
|
dense
|
||||||
|
v-model.trim="formDialoglnurldevice.data.amount"
|
||||||
|
type="number"
|
||||||
|
value="1000"
|
||||||
|
label="milesecs to turn Switch on for (1sec = 1000ms)"
|
||||||
|
></q-input>
|
||||||
|
</div>
|
||||||
|
<div class="col q-ml-md">
|
||||||
|
<q-input
|
||||||
|
filled
|
||||||
|
dense
|
||||||
|
v-model.trim="formDialoglnurldevice.data.pin"
|
||||||
|
type="number"
|
||||||
|
label="GPIO to turn on"
|
||||||
|
></q-input>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div v-if="switches >= 1">
|
||||||
|
<div class="row">
|
||||||
|
<div class="col">
|
||||||
|
<q-input
|
||||||
|
ref="setAmount"
|
||||||
|
filled
|
||||||
|
dense
|
||||||
|
v-model.trim="formDialoglnurldevice.data.profit1"
|
||||||
|
class="q-pb-md"
|
||||||
|
:label="'Amount (' + formDialoglnurldevice.data.currency + ') *'"
|
||||||
|
:mask="'#.##'"
|
||||||
|
fill-mask="0"
|
||||||
|
reverse-fill-mask
|
||||||
|
:step="'0.01'"
|
||||||
|
value="0.00"
|
||||||
|
></q-input>
|
||||||
|
</div>
|
||||||
|
<div class="col q-ml-md">
|
||||||
|
<q-input
|
||||||
|
filled
|
||||||
|
dense
|
||||||
|
v-model.trim="formDialoglnurldevice.data.amount1"
|
||||||
|
type="number"
|
||||||
|
value="1000"
|
||||||
|
label="milesecs to turn Switch on for (1sec = 1000ms)"
|
||||||
|
></q-input>
|
||||||
|
</div>
|
||||||
|
<div class="col q-ml-md">
|
||||||
|
<q-input
|
||||||
|
filled
|
||||||
|
dense
|
||||||
|
v-model.trim="formDialoglnurldevice.data.pin1"
|
||||||
|
type="number"
|
||||||
|
label="GPIO to turn on"
|
||||||
|
></q-input>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div v-if="switches >= 2">
|
||||||
|
<div class="row">
|
||||||
|
<div class="col">
|
||||||
|
<q-input
|
||||||
|
ref="setAmount"
|
||||||
|
filled
|
||||||
|
dense
|
||||||
|
v-model.trim="formDialoglnurldevice.data.profit2"
|
||||||
|
class="q-pb-md"
|
||||||
|
:label="'Amount (' + formDialoglnurldevice.data.currency + ') *'"
|
||||||
|
:mask="'#.##'"
|
||||||
|
fill-mask="0"
|
||||||
|
reverse-fill-mask
|
||||||
|
:step="'0.01'"
|
||||||
|
value="0.00"
|
||||||
|
></q-input>
|
||||||
|
</div>
|
||||||
|
<div class="col q-ml-md">
|
||||||
|
<q-input
|
||||||
|
filled
|
||||||
|
dense
|
||||||
|
v-model.trim="formDialoglnurldevice.data.amount2"
|
||||||
|
type="number"
|
||||||
|
value="1000"
|
||||||
|
label="milesecs to turn Switch on for (1sec = 1000ms)"
|
||||||
|
></q-input>
|
||||||
|
</div>
|
||||||
|
<div class="col q-ml-md">
|
||||||
|
<q-input
|
||||||
|
filled
|
||||||
|
dense
|
||||||
|
v-model.trim="formDialoglnurldevice.data.pin2"
|
||||||
|
type="number"
|
||||||
|
label="GPIO to turn on"
|
||||||
|
></q-input>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div v-if="switches >= 3">
|
||||||
|
<div class="row">
|
||||||
|
<div class="col">
|
||||||
|
<q-input
|
||||||
|
ref="setAmount"
|
||||||
|
filled
|
||||||
|
dense
|
||||||
|
v-model.trim="formDialoglnurldevice.data.profit3"
|
||||||
|
class="q-pb-md"
|
||||||
|
:label="'Amount (' + formDialoglnurldevice.data.currency + ') *'"
|
||||||
|
:mask="'#.##'"
|
||||||
|
fill-mask="0"
|
||||||
|
reverse-fill-mask
|
||||||
|
:step="'0.01'"
|
||||||
|
value="0.00"
|
||||||
|
></q-input>
|
||||||
|
</div>
|
||||||
|
<div class="col q-ml-md">
|
||||||
|
<q-input
|
||||||
|
filled
|
||||||
|
dense
|
||||||
|
v-model.trim="formDialoglnurldevice.data.amount3"
|
||||||
|
type="number"
|
||||||
|
value="1000"
|
||||||
|
label="milesecs to turn Switch on for (1sec = 1000ms)"
|
||||||
|
></q-input>
|
||||||
|
</div>
|
||||||
|
<div class="col q-ml-md">
|
||||||
|
<q-input
|
||||||
|
filled
|
||||||
|
dense
|
||||||
|
v-model.trim="formDialoglnurldevice.data.pin3"
|
||||||
|
type="number"
|
||||||
|
label="GPIO to turn on"
|
||||||
|
></q-input>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div v-if="switches >= 4">
|
||||||
|
<div class="row">
|
||||||
|
<div class="col">
|
||||||
|
<q-input
|
||||||
|
ref="setAmount"
|
||||||
|
filled
|
||||||
|
dense
|
||||||
|
v-model.trim="formDialoglnurldevice.data.profit4"
|
||||||
|
class="q-pb-md"
|
||||||
|
:label="'Amount (' + formDialoglnurldevice.data.currency + ') *'"
|
||||||
|
:mask="'#.##'"
|
||||||
|
fill-mask="0"
|
||||||
|
reverse-fill-mask
|
||||||
|
:step="'0.01'"
|
||||||
|
value="0.00"
|
||||||
|
></q-input>
|
||||||
|
</div>
|
||||||
|
<div class="col q-ml-md">
|
||||||
|
<q-input
|
||||||
|
filled
|
||||||
|
dense
|
||||||
|
v-model.trim="formDialoglnurldevice.data.amount4"
|
||||||
|
type="number"
|
||||||
|
value="1000"
|
||||||
|
label="milesecs to turn Switch on for (1sec = 1000ms)"
|
||||||
|
></q-input>
|
||||||
|
</div>
|
||||||
|
<div class="col q-ml-md">
|
||||||
|
<q-input
|
||||||
|
filled
|
||||||
|
dense
|
||||||
|
v-model.trim="formDialoglnurldevice.data.pin4"
|
||||||
|
type="number"
|
||||||
|
label="GPIO to turn on"
|
||||||
|
></q-input>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
<div class="row q-mt-lg">
|
<div class="row q-mt-lg">
|
||||||
<q-btn
|
<q-btn
|
||||||
v-if="formDialoglnurldevice.data.id"
|
v-if="formDialoglnurldevice.data.id"
|
||||||
|
@ -284,24 +476,26 @@
|
||||||
<q-card v-if="qrCodeDialog.data" class="q-pa-lg lnbits__dialog-card">
|
<q-card v-if="qrCodeDialog.data" class="q-pa-lg lnbits__dialog-card">
|
||||||
<q-responsive :ratio="1" class="q-mx-xl q-mb-md">
|
<q-responsive :ratio="1" class="q-mx-xl q-mb-md">
|
||||||
<qrcode
|
<qrcode
|
||||||
:value="qrCodeDialog.data.url + '/?lightning=' + qrCodeDialog.data.lnurl"
|
:value="lnurlValue"
|
||||||
:options="{width: 800}"
|
:options="{width: 800}"
|
||||||
class="rounded-borders"
|
class="rounded-borders"
|
||||||
></qrcode>
|
></qrcode>
|
||||||
{% raw %}
|
|
||||||
</q-responsive>
|
</q-responsive>
|
||||||
<p style="word-break: break-all">
|
<q-btn
|
||||||
<strong>ID:</strong> {{ qrCodeDialog.data.id }}<br />
|
outline
|
||||||
</p>
|
color="grey"
|
||||||
{% endraw %}
|
@click="copyText(lnurlValue, 'LNURL copied to clipboard!')"
|
||||||
|
>Copy LNURL</q-btn
|
||||||
|
>
|
||||||
|
<br />
|
||||||
<div class="row q-mt-lg q-gutter-sm">
|
<div class="row q-mt-lg q-gutter-sm">
|
||||||
<q-btn
|
<q-btn
|
||||||
|
v-for="switch_ in qrCodeDialog.data.switches"
|
||||||
outline
|
outline
|
||||||
color="grey"
|
color="primary"
|
||||||
@click="copyText(qrCodeDialog.data.lnurl, 'LNURL copied to clipboard!')"
|
:label="'Switch PIN:' + switch_[0]"
|
||||||
class="q-ml-sm"
|
@click="lnurlValueFetch(switch_[3])"
|
||||||
>Copy LNURL</q-btn
|
></q-btn>
|
||||||
>
|
|
||||||
<q-btn v-close-popup flat color="grey" class="q-ml-auto">Close</q-btn>
|
<q-btn v-close-popup flat color="grey" class="q-ml-auto">Close</q-btn>
|
||||||
</div>
|
</div>
|
||||||
</q-card>
|
</q-card>
|
||||||
|
@ -333,11 +527,14 @@
|
||||||
mixins: [windowMixin],
|
mixins: [windowMixin],
|
||||||
data: function () {
|
data: function () {
|
||||||
return {
|
return {
|
||||||
|
tab: 'mails',
|
||||||
protocol: window.location.protocol,
|
protocol: window.location.protocol,
|
||||||
location: window.location.hostname,
|
location: window.location.hostname,
|
||||||
wslocation: window.location.hostname,
|
wslocation: window.location.hostname,
|
||||||
filter: '',
|
filter: '',
|
||||||
currency: 'USD',
|
currency: 'USD',
|
||||||
|
lnurlValue: '',
|
||||||
|
switches: 0,
|
||||||
lnurldeviceLinks: [],
|
lnurldeviceLinks: [],
|
||||||
lnurldeviceLinksObj: [],
|
lnurldeviceLinksObj: [],
|
||||||
devices: [
|
devices: [
|
||||||
|
@ -386,12 +583,6 @@
|
||||||
label: 'device',
|
label: 'device',
|
||||||
field: 'device'
|
field: 'device'
|
||||||
},
|
},
|
||||||
{
|
|
||||||
name: 'profit',
|
|
||||||
align: 'left',
|
|
||||||
label: 'profit',
|
|
||||||
field: 'profit'
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
name: 'currency',
|
name: 'currency',
|
||||||
align: 'left',
|
align: 'left',
|
||||||
|
@ -440,8 +631,20 @@
|
||||||
this.qrCodeDialog.data = _.clone(lnurldevice)
|
this.qrCodeDialog.data = _.clone(lnurldevice)
|
||||||
this.qrCodeDialog.data.url =
|
this.qrCodeDialog.data.url =
|
||||||
window.location.protocol + '//' + window.location.host
|
window.location.protocol + '//' + window.location.host
|
||||||
|
this.lnurlValueFetch(this.qrCodeDialog.data.switches[0][3])
|
||||||
this.qrCodeDialog.show = true
|
this.qrCodeDialog.show = true
|
||||||
},
|
},
|
||||||
|
lnurlValueFetch: function (lnurl) {
|
||||||
|
this.lnurlValue = lnurl
|
||||||
|
},
|
||||||
|
addSwitch: function () {
|
||||||
|
var self = this
|
||||||
|
self.switches = self.switches + 1
|
||||||
|
},
|
||||||
|
removeSwitch: function () {
|
||||||
|
var self = this
|
||||||
|
self.switches = self.switches - 1
|
||||||
|
},
|
||||||
cancellnurldevice: function (data) {
|
cancellnurldevice: function (data) {
|
||||||
var self = this
|
var self = this
|
||||||
self.formDialoglnurldevice.show = false
|
self.formDialoglnurldevice.show = false
|
||||||
|
@ -498,7 +701,9 @@
|
||||||
.then(function (response) {
|
.then(function (response) {
|
||||||
if (response.data) {
|
if (response.data) {
|
||||||
self.lnurldeviceLinks = response.data.map(maplnurldevice)
|
self.lnurldeviceLinks = response.data.map(maplnurldevice)
|
||||||
|
console.log('response.data')
|
||||||
console.log(response.data)
|
console.log(response.data)
|
||||||
|
console.log('response.data')
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.catch(function (error) {
|
.catch(function (error) {
|
||||||
|
|
|
@ -103,8 +103,10 @@ async def websocket_endpoint(websocket: WebSocket, lnurldevice_id: str):
|
||||||
manager.disconnect(websocket)
|
manager.disconnect(websocket)
|
||||||
|
|
||||||
|
|
||||||
async def updater(lnurldevice_id):
|
async def updater(lnurldevice_id, lnurldevice_pin, lnurldevice_amount):
|
||||||
lnurldevice = await get_lnurldevice(lnurldevice_id)
|
lnurldevice = await get_lnurldevice(lnurldevice_id)
|
||||||
if not lnurldevice:
|
if not lnurldevice:
|
||||||
return
|
return
|
||||||
await manager.send_personal_message(f"{lnurldevice.amount}", lnurldevice_id)
|
return await manager.send_personal_message(
|
||||||
|
f"{lnurldevice_pin}-{lnurldevice_amount}", lnurldevice_id
|
||||||
|
)
|
||||||
|
|
|
@ -39,10 +39,10 @@ async def api_lnurldevice_create_or_update(
|
||||||
):
|
):
|
||||||
if not lnurldevice_id:
|
if not lnurldevice_id:
|
||||||
lnurldevice = await create_lnurldevice(data)
|
lnurldevice = await create_lnurldevice(data)
|
||||||
return {**lnurldevice.dict(), **{"lnurl": lnurldevice.lnurl(req)}}
|
return {**lnurldevice.dict(), **{"switches": lnurldevice.switches(req)}}
|
||||||
else:
|
else:
|
||||||
lnurldevice = await update_lnurldevice(data, lnurldevice_id=lnurldevice_id)
|
lnurldevice = await update_lnurldevice(data, lnurldevice_id=lnurldevice_id)
|
||||||
return {**lnurldevice.dict(), **{"lnurl": lnurldevice.lnurl(req)}}
|
return {**lnurldevice.dict(), **{"switches": lnurldevice.switches(req)}}
|
||||||
|
|
||||||
|
|
||||||
@lnurldevice_ext.get("/api/v1/lnurlpos")
|
@lnurldevice_ext.get("/api/v1/lnurlpos")
|
||||||
|
@ -52,7 +52,7 @@ async def api_lnurldevices_retrieve(
|
||||||
wallet_ids = (await get_user(wallet.wallet.user)).wallet_ids
|
wallet_ids = (await get_user(wallet.wallet.user)).wallet_ids
|
||||||
try:
|
try:
|
||||||
return [
|
return [
|
||||||
{**lnurldevice.dict(), **{"lnurl": lnurldevice.lnurl(req)}}
|
{**lnurldevice.dict(), **{"switches": lnurldevice.switches(req)}}
|
||||||
for lnurldevice in await get_lnurldevices(wallet_ids)
|
for lnurldevice in await get_lnurldevices(wallet_ids)
|
||||||
]
|
]
|
||||||
except:
|
except:
|
||||||
|
@ -78,7 +78,7 @@ async def api_lnurldevice_retrieve(
|
||||||
)
|
)
|
||||||
if not lnurldevice.lnurl_toggle:
|
if not lnurldevice.lnurl_toggle:
|
||||||
return {**lnurldevice.dict()}
|
return {**lnurldevice.dict()}
|
||||||
return {**lnurldevice.dict(), **{"lnurl": lnurldevice.lnurl(req)}}
|
return {**lnurldevice.dict(), **{"switches": lnurldevice.switches(req)}}
|
||||||
|
|
||||||
|
|
||||||
@lnurldevice_ext.delete("/api/v1/lnurlpos/{lnurldevice_id}")
|
@lnurldevice_ext.delete("/api/v1/lnurlpos/{lnurldevice_id}")
|
||||||
|
|
|
@ -38,7 +38,7 @@
|
||||||
>
|
>
|
||||||
<h5 class="text-caption q-mt-sm q-mb-none">Body (application/json)</h5>
|
<h5 class="text-caption q-mt-sm q-mb-none">Body (application/json)</h5>
|
||||||
<h5 class="text-caption q-mt-sm q-mb-none">
|
<h5 class="text-caption q-mt-sm q-mb-none">
|
||||||
Returns 201 CREATED (application/json)
|
Returns 200 OK (application/json)
|
||||||
</h5>
|
</h5>
|
||||||
<code>JSON list of users</code>
|
<code>JSON list of users</code>
|
||||||
<h5 class="text-caption q-mt-sm q-mb-none">Curl example</h5>
|
<h5 class="text-caption q-mt-sm q-mb-none">Curl example</h5>
|
||||||
|
@ -57,10 +57,16 @@
|
||||||
/usermanager/api/v1/users/<user_id></code
|
/usermanager/api/v1/users/<user_id></code
|
||||||
>
|
>
|
||||||
<h5 class="text-caption q-mt-sm q-mb-none">Body (application/json)</h5>
|
<h5 class="text-caption q-mt-sm q-mb-none">Body (application/json)</h5>
|
||||||
|
|
||||||
<h5 class="text-caption q-mt-sm q-mb-none">
|
<h5 class="text-caption q-mt-sm q-mb-none">
|
||||||
Returns 201 CREATED (application/json)
|
Returns 200 OK (application/json)
|
||||||
</h5>
|
</h5>
|
||||||
<code>JSON list of users</code>
|
<code
|
||||||
|
>{"id": <string>, "name": <string>, "admin":
|
||||||
|
<string>, "email": <string>, "password":
|
||||||
|
<string>}</code
|
||||||
|
>
|
||||||
|
|
||||||
<h5 class="text-caption q-mt-sm q-mb-none">Curl example</h5>
|
<h5 class="text-caption q-mt-sm q-mb-none">Curl example</h5>
|
||||||
<code
|
<code
|
||||||
>curl -X GET {{ request.base_url
|
>curl -X GET {{ request.base_url
|
||||||
|
@ -81,7 +87,7 @@
|
||||||
<code>{"X-Api-Key": <string>}</code>
|
<code>{"X-Api-Key": <string>}</code>
|
||||||
<h5 class="text-caption q-mt-sm q-mb-none">Body (application/json)</h5>
|
<h5 class="text-caption q-mt-sm q-mb-none">Body (application/json)</h5>
|
||||||
<h5 class="text-caption q-mt-sm q-mb-none">
|
<h5 class="text-caption q-mt-sm q-mb-none">
|
||||||
Returns 201 CREATED (application/json)
|
Returns 200 OK (application/json)
|
||||||
</h5>
|
</h5>
|
||||||
<code>JSON wallet data</code>
|
<code>JSON wallet data</code>
|
||||||
<h5 class="text-caption q-mt-sm q-mb-none">Curl example</h5>
|
<h5 class="text-caption q-mt-sm q-mb-none">Curl example</h5>
|
||||||
|
@ -104,7 +110,7 @@
|
||||||
<code>{"X-Api-Key": <string>}</code>
|
<code>{"X-Api-Key": <string>}</code>
|
||||||
<h5 class="text-caption q-mt-sm q-mb-none">Body (application/json)</h5>
|
<h5 class="text-caption q-mt-sm q-mb-none">Body (application/json)</h5>
|
||||||
<h5 class="text-caption q-mt-sm q-mb-none">
|
<h5 class="text-caption q-mt-sm q-mb-none">
|
||||||
Returns 201 CREATED (application/json)
|
Returns 200 OK (application/json)
|
||||||
</h5>
|
</h5>
|
||||||
<code>JSON a wallets transactions</code>
|
<code>JSON a wallets transactions</code>
|
||||||
<h5 class="text-caption q-mt-sm q-mb-none">Curl example</h5>
|
<h5 class="text-caption q-mt-sm q-mb-none">Curl example</h5>
|
||||||
|
@ -254,11 +260,15 @@
|
||||||
<code>{"X-Api-Key": <string>}</code>
|
<code>{"X-Api-Key": <string>}</code>
|
||||||
<h5 class="text-caption q-mt-sm q-mb-none">Curl example</h5>
|
<h5 class="text-caption q-mt-sm q-mb-none">Curl example</h5>
|
||||||
<code
|
<code
|
||||||
>curl -X POST {{ request.base_url }}usermanager/api/v1/extensions -d
|
>curl -X POST {{ request.base_url
|
||||||
'{"userid": <string>, "extension": <string>, "active":
|
}}usermanager/api/v1/extensions?extension=withdraw&userid=user_id&active=true
|
||||||
<integer>}' -H "X-Api-Key: {{ user.wallets[0].inkey }}" -H
|
-H "X-Api-Key: {{ user.wallets[0].inkey }}" -H "Content-type:
|
||||||
"Content-type: application/json"
|
application/json"
|
||||||
</code>
|
</code>
|
||||||
|
<h5 class="text-caption q-mt-sm q-mb-none">
|
||||||
|
Returns 200 OK (application/json)
|
||||||
|
</h5>
|
||||||
|
<code>{"extension": "updated"}</code>
|
||||||
</q-card-section>
|
</q-card-section>
|
||||||
</q-card>
|
</q-card>
|
||||||
</q-expansion-item>
|
</q-expansion-item>
|
||||||
|
|
|
@ -78,34 +78,35 @@ async def api_lnurl_callback(
|
||||||
return {"status": "ERROR", "reason": f"Wait {link.open_time - now} seconds."}
|
return {"status": "ERROR", "reason": f"Wait {link.open_time - now} seconds."}
|
||||||
|
|
||||||
usescsv = ""
|
usescsv = ""
|
||||||
|
|
||||||
|
for x in range(1, link.uses - link.used):
|
||||||
|
usecv = link.usescsv.split(",")
|
||||||
|
usescsv += "," + str(usecv[x])
|
||||||
|
usecsvback = usescsv
|
||||||
|
|
||||||
|
found = False
|
||||||
|
if id_unique_hash is not None:
|
||||||
|
useslist = link.usescsv.split(",")
|
||||||
|
for ind, x in enumerate(useslist):
|
||||||
|
tohash = link.id + link.unique_hash + str(x)
|
||||||
|
if id_unique_hash == shortuuid.uuid(name=tohash):
|
||||||
|
found = True
|
||||||
|
useslist.pop(ind)
|
||||||
|
usescsv = ",".join(useslist)
|
||||||
|
if not found:
|
||||||
|
raise HTTPException(
|
||||||
|
status_code=HTTPStatus.NOT_FOUND, detail="LNURL-withdraw not found."
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
usescsv = usescsv[1:]
|
||||||
|
|
||||||
|
changesback = {
|
||||||
|
"open_time": link.wait_time,
|
||||||
|
"used": link.used,
|
||||||
|
"usescsv": usecsvback,
|
||||||
|
}
|
||||||
|
|
||||||
try:
|
try:
|
||||||
for x in range(1, link.uses - link.used):
|
|
||||||
usecv = link.usescsv.split(",")
|
|
||||||
usescsv += "," + str(usecv[x])
|
|
||||||
usecsvback = usescsv
|
|
||||||
|
|
||||||
found = False
|
|
||||||
if id_unique_hash is not None:
|
|
||||||
useslist = link.usescsv.split(",")
|
|
||||||
for ind, x in enumerate(useslist):
|
|
||||||
tohash = link.id + link.unique_hash + str(x)
|
|
||||||
if id_unique_hash == shortuuid.uuid(name=tohash):
|
|
||||||
found = True
|
|
||||||
useslist.pop(ind)
|
|
||||||
usescsv = ",".join(useslist)
|
|
||||||
if not found:
|
|
||||||
raise HTTPException(
|
|
||||||
status_code=HTTPStatus.NOT_FOUND, detail="LNURL-withdraw not found."
|
|
||||||
)
|
|
||||||
else:
|
|
||||||
usescsv = usescsv[1:]
|
|
||||||
|
|
||||||
changesback = {
|
|
||||||
"open_time": link.wait_time,
|
|
||||||
"used": link.used,
|
|
||||||
"usescsv": usecsvback,
|
|
||||||
}
|
|
||||||
|
|
||||||
changes = {
|
changes = {
|
||||||
"open_time": link.wait_time + now,
|
"open_time": link.wait_time + now,
|
||||||
"used": link.used + 1,
|
"used": link.used + 1,
|
||||||
|
|
Loading…
Add table
Reference in a new issue