Skip to content

Commit

Permalink
feat: make extra more versatile and use satspay config instead of onc…
Browse files Browse the repository at this point in the history
…hainwallet config (#59)
  • Loading branch information
dni authored Aug 28, 2024
1 parent 8c40352 commit 3aed85e
Show file tree
Hide file tree
Showing 11 changed files with 121 additions and 122 deletions.
9 changes: 1 addition & 8 deletions crud.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
import json
from typing import Optional

from lnbits.core.services import create_invoice
Expand All @@ -11,7 +10,6 @@
CreateSatsPayTheme,
SatspaySettings,
SatsPayTheme,
WalletAccountConfig,
)

db = Database("ext_satspay")
Expand All @@ -20,17 +18,12 @@
async def create_charge(
user: str,
data: CreateCharge,
config: Optional[WalletAccountConfig] = None,
onchainaddress: Optional[str] = None,
) -> Charge:
data = CreateCharge(**data.dict())
charge_id = urlsafe_short_hash()
if data.onchainwallet:
if not onchainaddress or not config:
if not onchainaddress:
raise Exception(f"Wallet '{data.onchainwallet}' can no longer be accessed.")
data.extra = json.dumps(
{"mempool_endpoint": config.mempool_endpoint, "network": config.network}
)

assert data.amount, "Amount is required"
if data.lnbitswallet:
Expand Down
46 changes: 16 additions & 30 deletions helpers.py
Original file line number Diff line number Diff line change
@@ -1,11 +1,10 @@
import json

import httpx
from lnbits.core.crud import get_standalone_payment
from lnbits.settings import settings
from loguru import logger

from .models import Charge, OnchainBalance, WalletAccountConfig
from .crud import get_or_create_satspay_settings
from .models import Charge, OnchainBalance


async def call_webhook(charge: Charge):
Expand Down Expand Up @@ -36,50 +35,39 @@ async def call_webhook(charge: Charge):
return {"webhook_success": False, "webhook_message": str(e)}


def get_endpoint(charge: Charge) -> str:
assert charge.config.mempool_endpoint, "No mempool endpoint configured"
return (
f"{charge.config.mempool_endpoint}/testnet"
if charge.config.network == "Testnet"
else charge.config.mempool_endpoint or ""
)


async def fetch_onchain_balance(
onchain_address: str, mempool_endpoint: str
) -> OnchainBalance:
async def fetch_onchain_balance(onchain_address: str) -> OnchainBalance:
settings = await get_or_create_satspay_settings()
async with httpx.AsyncClient() as client:
res = await client.get(f"{mempool_endpoint}/api/address/{onchain_address}")
res = await client.get(f"{settings.mempool_url}/api/address/{onchain_address}")
res.raise_for_status()
data = res.json()
confirmed = data["chain_stats"]["funded_txo_sum"]
unconfirmed = data["mempool_stats"]["funded_txo_sum"]
return OnchainBalance(confirmed=confirmed, unconfirmed=unconfirmed)


async def fetch_onchain_config(
wallet_id: str, api_key: str
) -> tuple[WalletAccountConfig, str]:
async def fetch_onchain_config_network(api_key: str) -> str:
async with httpx.AsyncClient() as client:
headers = {"X-API-KEY": api_key}
r = await client.get(
url=f"http://{settings.host}:{settings.port}/watchonly/api/v1/config",
headers=headers,
headers={"X-API-KEY": api_key},
)
r.raise_for_status()
config = r.json()
return config["network"]


async def fetch_onchain_address(wallet_id: str, api_key: str) -> str:
async with httpx.AsyncClient() as client:
r = await client.get(
url=f"http://{settings.host}:{settings.port}/watchonly/api/v1/address/{wallet_id}",
headers=headers,
headers={"X-API-KEY": api_key},
)
r.raise_for_status()
address_data = r.json()

if not address_data:
if not address_data and "address" not in address_data:
raise ValueError("Cannot fetch new address!")

return WalletAccountConfig.parse_obj(config), address_data["address"]
return address_data["address"]


async def check_charge_balance(charge: Charge) -> Charge:
Expand All @@ -95,9 +83,7 @@ async def check_charge_balance(charge: Charge) -> Charge:

if charge.onchainaddress:
try:
balance = await fetch_onchain_balance(
charge.onchainaddress, charge.config.mempool_endpoint
)
balance = await fetch_onchain_balance(charge.onchainaddress)
if (
balance.confirmed != charge.balance
or balance.unconfirmed != charge.pending
Expand All @@ -115,6 +101,6 @@ async def check_charge_balance(charge: Charge) -> Charge:

if charge.webhook:
resp = await call_webhook(charge)
charge.extra = json.dumps({**charge.config.dict(), **resp})
charge.add_extra(resp)

return charge
11 changes: 11 additions & 0 deletions migrations.py
Original file line number Diff line number Diff line change
Expand Up @@ -175,3 +175,14 @@ async def m011_persist_paid(db):
)
except OperationalError:
pass


async def m012_add_setting_network(db):
"""
Add 'network' column for storing the network
"""
try:
await db.execute("ALTER TABLE satspay.settings ADD COLUMN network TEXT")
await db.execute("UPDATE satspay.settings SET network = 'Mainnet'")
except OperationalError:
pass
35 changes: 7 additions & 28 deletions models.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,14 +7,10 @@
from fastapi.param_functions import Query
from pydantic import BaseModel

DEFAULT_MEMPOOL_ENDPOINT = "https://mempool.space"
DEFAULT_MEMPOOL_CONFIG = (
'{"mempool_endpoint": "https://mempool.space", "network": "Mainnet"}'
)


class SatspaySettings(BaseModel):
mempool_url: str = DEFAULT_MEMPOOL_ENDPOINT
mempool_url: str = "https://mempool.space"
network: str = "Mainnet"


class CreateCharge(BaseModel):
Expand All @@ -28,18 +24,10 @@ class CreateCharge(BaseModel):
time: int = Query(..., ge=1)
amount: Optional[int] = Query(None, ge=1)
zeroconf: bool = Query(False)
extra: str = DEFAULT_MEMPOOL_CONFIG
custom_css: Optional[str] = Query(None)
currency: str = Query(None)
currency_amount: Optional[float] = Query(None)


class ChargeConfig(BaseModel):
mempool_endpoint: str
network: Optional[str]
webhook_message: Optional[str]
webhook_success: bool = False
misc: dict = {}
extra: Optional[str] = Query(None)


class Charge(BaseModel):
Expand All @@ -55,7 +43,6 @@ class Charge(BaseModel):
completelink: Optional[str]
completelinktext: Optional[str] = "Back to Merchant"
custom_css: Optional[str]
extra: str = DEFAULT_MEMPOOL_CONFIG
time: int
amount: int
zeroconf: bool
Expand All @@ -66,11 +53,11 @@ class Charge(BaseModel):
currency: Optional[str] = None
currency_amount: Optional[float] = None
paid: bool = False
extra: Optional[str] = None

@property
def config(self) -> ChargeConfig:
charge_config = json.loads(self.extra)
return ChargeConfig(**charge_config)
def add_extra(self, extra: dict):
old_extra = json.loads(self.extra) if self.extra else {}
self.extra = json.dumps({**old_extra, **extra})

@property
def public(self):
Expand Down Expand Up @@ -110,14 +97,6 @@ class SatsPayTheme(BaseModel):
user: str


class WalletAccountConfig(BaseModel):
mempool_endpoint: str
receive_gap_limit: int
change_gap_limit: int
sats_denominated: bool
network: str


class OnchainBalance(BaseModel):
confirmed: int
unconfirmed: int
11 changes: 4 additions & 7 deletions static/js/display.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ new Vue({
data() {
return {
charge: mapCharge(charge_data),
network: network,
mempool_url: mempool_url,
ws: null,
wallet: {
inkey: ''
Expand All @@ -15,12 +15,9 @@ new Vue({
},
computed: {
mempoolLink() {
const onchainaddress = this.charge.onchainaddress
if (this.network === 'Testnet') {
return `https://mempool.space/testnet/address/${onchainaddress}`
} else {
return `https://mempool.space/address/${onchainaddress}`
}
// remove trailing slash
const url = this.mempool_url.replace(/\/$/, '')
return `${url}/address/${this.charge.onchainaddress}`
},
unifiedQR() {
const bitcoin = (this.charge.onchainaddress || '').toUpperCase()
Expand Down
35 changes: 10 additions & 25 deletions static/js/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,13 @@ new Vue({
return {
currencies: [],
fiatRates: {},
settings: {},
settings: [
{
type: 'str',
description:
'Network used by OnchainWallet extension Wallet. default: `Mainnet`, or `Testnet` for testnet',
name: 'network'
},
{
type: 'str',
description:
Expand All @@ -21,19 +26,15 @@ new Vue({
],
filter: '',
admin: admin,
network: network,
balance: null,
walletLinks: [],
chargeLinks: [],
themeLinks: [],
themeOptions: [],
onchainwallet: '',
rescanning: false,
mempool: {
endpoint: '',
network: 'Mainnet'
},
showAdvanced: false,

chargesTable: {
columns: [
{
Expand Down Expand Up @@ -171,11 +172,12 @@ new Vue({

getWalletLinks: async function () {
try {
const {data} = await LNbits.api.request(
let {data} = await LNbits.api.request(
'GET',
`/watchonly/api/v1/wallet?network=${this.mempool.network}`,
`/watchonly/api/v1/wallet?network=${this.network}`,
this.g.user.wallets[0].adminkey
)
data = data.filter(w => w.network === this.network)
this.walletLinks = data.map(w => ({
id: w.id,
label: w.title + ' - ' + w.id
Expand All @@ -184,22 +186,6 @@ new Vue({
LNbits.utils.notifyApiError(error)
}
},

getWalletConfig: async function () {
try {
const {data} = await LNbits.api.request(
'GET',
'/watchonly/api/v1/config',
this.g.user.wallets[0].inkey
)
this.mempool.endpoint = data.mempool_endpoint
this.mempool.network = data.network || 'Mainnet'
const url = new URL(this.mempool.endpoint)
this.mempool.hostname = url.hostname
} catch (error) {
LNbits.utils.notifyApiError(error)
}
},
getOnchainWalletName: function (walletId) {
const wallet = this.walletLinks.find(w => w.id === walletId)
if (!wallet) return 'unknown'
Expand Down Expand Up @@ -426,7 +412,6 @@ new Vue({
await this.getThemes()
}
await this.getCharges()
await this.getWalletConfig()
await this.getWalletLinks()
LNbits.api
.request('GET', '/api/v1/currencies')
Expand Down
5 changes: 2 additions & 3 deletions tasks.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import asyncio
import json

from fastapi import WebSocket
from lnbits.core.models import Payment
Expand Down Expand Up @@ -56,7 +55,7 @@ async def on_invoice_paid(payment: Payment) -> None:
await send_success_websocket(charge)
if charge.webhook:
resp = await call_webhook(charge)
charge.extra = json.dumps({**charge.config.dict(), **resp})
charge.add_extra(resp)
await update_charge(charge)


Expand Down Expand Up @@ -111,5 +110,5 @@ async def _handle_ws_message(address: str, data: dict):
stop_onchain_listener(address)
if charge.webhook:
resp = await call_webhook(charge)
charge.extra = json.dumps({**charge.config.dict(), **resp})
charge.add_extra(resp)
await update_charge(charge)
2 changes: 1 addition & 1 deletion templates/satspay/display.html
Original file line number Diff line number Diff line change
Expand Up @@ -196,7 +196,7 @@
{% endblock %} {% block scripts %}
<script>
const charge_data = JSON.parse({{ charge_data|tojson }});
const network = "{{ network }}";
const mempool_url = "{{ mempool_url }}";
</script>
<script src="{{ static_url_for('satspay/static', path='js/utils.js') }}"></script>
<script src="{{ static_url_for('satspay/static', path='js/components.js') }}"></script>
Expand Down
1 change: 1 addition & 0 deletions templates/satspay/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -618,6 +618,7 @@ <h6 class="text-subtitle1 q-my-none">
{% endblock %} {% block scripts %} {{ window_vars(user) }}
<script>
const admin = '{{ admin }}'
const network = '{{ network }}'
</script>
<script src="{{ static_url_for('satspay/static', path='js/utils.js') }}"></script>
<script src="{{ static_url_for('satspay/static', path='js/index.js') }}"></script>
Expand Down
Loading

0 comments on commit 3aed85e

Please sign in to comment.