Compare commits
6 commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
138350dab4 |
||
|
|
766d317ce8 |
||
|
|
81184a0a53 |
||
|
|
d963af4042 |
||
|
|
b457fecc90 |
||
|
|
d1c14ac199 |
11 changed files with 2388 additions and 2913 deletions
24
Makefile
24
Makefile
|
|
@ -5,27 +5,27 @@ format: prettier black ruff
|
|||
check: mypy pyright checkblack checkruff checkprettier
|
||||
|
||||
prettier:
|
||||
poetry run ./node_modules/.bin/prettier --write .
|
||||
uv run ./node_modules/.bin/prettier --write .
|
||||
pyright:
|
||||
poetry run ./node_modules/.bin/pyright
|
||||
uv run ./node_modules/.bin/pyright
|
||||
|
||||
mypy:
|
||||
poetry run mypy .
|
||||
uv run mypy .
|
||||
|
||||
black:
|
||||
poetry run black .
|
||||
uv run black .
|
||||
|
||||
ruff:
|
||||
poetry run ruff check . --fix
|
||||
uv run ruff check . --fix
|
||||
|
||||
checkruff:
|
||||
poetry run ruff check .
|
||||
uv run ruff check .
|
||||
|
||||
checkprettier:
|
||||
poetry run ./node_modules/.bin/prettier --check .
|
||||
uv run ./node_modules/.bin/prettier --check .
|
||||
|
||||
checkblack:
|
||||
poetry run black --check .
|
||||
uv run black --check .
|
||||
|
||||
checkeditorconfig:
|
||||
editorconfig-checker
|
||||
|
|
@ -33,14 +33,14 @@ checkeditorconfig:
|
|||
test:
|
||||
PYTHONUNBUFFERED=1 \
|
||||
DEBUG=true \
|
||||
poetry run pytest
|
||||
uv run pytest
|
||||
install-pre-commit-hook:
|
||||
@echo "Installing pre-commit hook to git"
|
||||
@echo "Uninstall the hook with poetry run pre-commit uninstall"
|
||||
poetry run pre-commit install
|
||||
@echo "Uninstall the hook with uv run pre-commit uninstall"
|
||||
uv run pre-commit install
|
||||
|
||||
pre-commit:
|
||||
poetry run pre-commit run --all-files
|
||||
uv run pre-commit run --all-files
|
||||
|
||||
|
||||
checkbundle:
|
||||
|
|
|
|||
|
|
@ -33,9 +33,9 @@ LNbits Split Payments extension allows for distributing payments across multiple
|
|||
|
||||
IMPORTANT:
|
||||
|
||||
- If you split to a LNURLp or LNaddress through the LNURLp extension make sure your receipients allow comments ! Split&Scrub add a comment in your transaction - and if it is not allowed, the split/scrub will not take place.
|
||||
- Make sure the LNURLp / LNaddress of the receipient has its min-sats set very low (e.g. 1 sat). If the wallet does not belong to you you can [check with a Decoder](https://lightningdecoder.com/), if that is the case already
|
||||
- Yes, there is fees - internal and external! Updating your own wallets on your own instance will not cost any fees but sending to an external instance will. Please notice that you should therefore not split up to 100% if you send to a wallet that is external (leave 1-2% reserve for routing fees!). External fees are deducted from the individual payment percentage of the receipient
|
||||
- If you split to a LNURLp or LNaddress through the LNURLp extension make sure your recipients allow comments ! Split&Scrub add a comment in your transaction - and if it is not allowed, the split/scrub will not take place.
|
||||
- Make sure the LNURLp / LNaddress of the recipient has its min-sats set very low (e.g. 1 sat). If the wallet does not belong to you you can [check with a Decoder](https://lightningdecoder.com/), if that is the case already
|
||||
- Yes, there is fees - internal and external! Updating your own wallets on your own instance will not cost any fees but sending to an external instance will. Please notice that you should therefore not split up to 100% if you send to a wallet that is external (leave 1-2% reserve for routing fees!). External fees are deducted from the individual payment percentage of the recipient
|
||||
|
||||
<img width="1148" alt="Bildschirmfoto 2023-05-01 um 22 14 36" src="https://user-images.githubusercontent.com/63317640/235534056-49296aeb-7295-4b4e-9f57-914a677f5ad4.png">
|
||||
<img width="1402" alt="Bildschirmfoto 2023-05-01 um 22 17 52" src="https://user-images.githubusercontent.com/63317640/235534063-b2734654-7c1a-48a3-b48e-32798c232b49.png">
|
||||
|
|
|
|||
|
|
@ -41,7 +41,7 @@ def splitpayments_start():
|
|||
__all__ = [
|
||||
"db",
|
||||
"splitpayments_ext",
|
||||
"splitpayments_static_files",
|
||||
"splitpayments_start",
|
||||
"splitpayments_static_files",
|
||||
"splitpayments_stop",
|
||||
]
|
||||
|
|
|
|||
|
|
@ -2,7 +2,8 @@
|
|||
"name": "Split Payments",
|
||||
"short_description": "Split incoming payments across wallets",
|
||||
"tile": "/splitpayments/static/image/split-payments.png",
|
||||
"min_lnbits_version": "1.0.0",
|
||||
"version": "1.1.0",
|
||||
"min_lnbits_version": "1.3.0",
|
||||
"contributors": [
|
||||
{
|
||||
"name": "cryptograffiti",
|
||||
|
|
|
|||
|
|
@ -1,5 +1,3 @@
|
|||
from typing import Optional
|
||||
|
||||
from fastapi import Query
|
||||
from pydantic import BaseModel
|
||||
|
||||
|
|
@ -9,7 +7,7 @@ class Target(BaseModel):
|
|||
wallet: str
|
||||
source: str
|
||||
percent: float
|
||||
alias: Optional[str] = None
|
||||
alias: str | None = None
|
||||
|
||||
|
||||
class TargetPut(BaseModel):
|
||||
|
|
|
|||
2815
poetry.lock
generated
2815
poetry.lock
generated
File diff suppressed because it is too large
Load diff
|
|
@ -1,39 +1,36 @@
|
|||
[project]
|
||||
name = "splitpayments"
|
||||
version = "1.1.1"
|
||||
requires-python = ">=3.10,<3.13"
|
||||
description = "Send incoming payments to different targets"
|
||||
authors = [{name = "benarc"}, {name = "dni"}, {name = "alan"}]
|
||||
urls = { Homepage = "https://lnbits.com", Repository = "https://github.com/lnbits/splitpayments" }
|
||||
|
||||
dependencies = [ "lnbits>1" ]
|
||||
|
||||
[tool.poetry]
|
||||
name = "lnbits-splitpayments"
|
||||
version = "0.0.0"
|
||||
description = "LNbits, free and open-source Lightning wallet and accounts system."
|
||||
authors = ["Alan Bits <alan@lnbits.com>"]
|
||||
package-mode = false
|
||||
|
||||
[tool.poetry.dependencies]
|
||||
python = "^3.10 | ^3.9"
|
||||
lnbits = {version = "*", allow-prereleases = true}
|
||||
|
||||
[tool.poetry.group.dev.dependencies]
|
||||
black = "^24.3.0"
|
||||
pytest-asyncio = "^0.21.0"
|
||||
pytest = "^7.3.2"
|
||||
mypy = "^1.5.1"
|
||||
pre-commit = "^3.2.2"
|
||||
ruff = "^0.3.2"
|
||||
|
||||
[build-system]
|
||||
requires = ["poetry-core>=1.0.0"]
|
||||
build-backend = "poetry.core.masonry.api"
|
||||
[tool.uv]
|
||||
dev-dependencies = [
|
||||
"black",
|
||||
"pytest-asyncio",
|
||||
"pytest",
|
||||
"mypy==1.17.1",
|
||||
"pre-commit",
|
||||
"ruff",
|
||||
"pytest-md",
|
||||
]
|
||||
|
||||
[tool.mypy]
|
||||
exclude = "(nostr/*)"
|
||||
[[tool.mypy.overrides]]
|
||||
module = [
|
||||
"lnbits.*",
|
||||
"lnurl.*",
|
||||
"loguru.*",
|
||||
"fastapi.*",
|
||||
"pydantic.*",
|
||||
"pyqrcode.*",
|
||||
"shortuuid.*",
|
||||
"httpx.*",
|
||||
]
|
||||
ignore_missing_imports = "True"
|
||||
plugins = ["pydantic.mypy"]
|
||||
|
||||
[tool.pydantic-mypy]
|
||||
init_forbid_extra = true
|
||||
init_typed = true
|
||||
warn_required_dynamic_aliases = true
|
||||
warn_untyped_fields = true
|
||||
|
||||
[tool.pytest.ini_options]
|
||||
log_cli = false
|
||||
|
|
@ -76,6 +73,7 @@ dummy-variable-rgx = "^(_+|(_+[a-zA-Z0-9_]*[a-zA-Z0-9]+?))$"
|
|||
# needed for pydantic
|
||||
[tool.ruff.lint.pep8-naming]
|
||||
classmethod-decorators = [
|
||||
"validator",
|
||||
"root_validator",
|
||||
]
|
||||
|
||||
|
|
|
|||
|
|
@ -51,8 +51,11 @@ window.app = Vue.createApp({
|
|||
'/splitpayments/api/v1/targets',
|
||||
this.selectedWallet.adminkey
|
||||
)
|
||||
.then(response => {
|
||||
this.targets = response.data
|
||||
.then(res => {
|
||||
this.targets = res.data.map(t => ({
|
||||
...t,
|
||||
targetChoice: t.targetChoice || 'wallet'
|
||||
}))
|
||||
})
|
||||
.catch(err => {
|
||||
LNbits.utils.notifyApiError(err)
|
||||
|
|
@ -63,16 +66,26 @@ window.app = Vue.createApp({
|
|||
this.getTargets()
|
||||
},
|
||||
addTarget() {
|
||||
this.targets.push({source: this.selectedWallet})
|
||||
this.targets.push({
|
||||
source: this.selectedWallet,
|
||||
targetChoice: 'wallet'
|
||||
})
|
||||
},
|
||||
saveTargets() {
|
||||
const payload = this.targets
|
||||
.filter(t => t.wallet && String(t.wallet).trim() !== '')
|
||||
.map(({alias, percent, wallet}) => ({
|
||||
alias,
|
||||
percent: Number(percent) || 0,
|
||||
wallet
|
||||
}))
|
||||
LNbits.api
|
||||
.request(
|
||||
'PUT',
|
||||
'/splitpayments/api/v1/targets',
|
||||
this.selectedWallet.adminkey,
|
||||
{
|
||||
targets: this.targets
|
||||
targets: payload
|
||||
}
|
||||
)
|
||||
.then(response => {
|
||||
|
|
|
|||
54
tasks.py
54
tasks.py
|
|
@ -1,14 +1,16 @@
|
|||
import asyncio
|
||||
import json
|
||||
from math import floor
|
||||
from typing import Optional
|
||||
|
||||
import bolt11
|
||||
import httpx
|
||||
from lnbits.core.crud import get_standalone_payment
|
||||
from lnbits.core.crud.wallets import get_wallet_for_key
|
||||
from lnbits.core.models import Payment
|
||||
from lnbits.core.services import create_invoice, fee_reserve, pay_invoice
|
||||
from lnbits.core.services import (
|
||||
create_invoice,
|
||||
fee_reserve,
|
||||
get_pr_from_lnurl,
|
||||
pay_invoice,
|
||||
)
|
||||
from lnbits.tasks import register_invoice_listener
|
||||
from loguru import logger
|
||||
|
||||
|
|
@ -96,39 +98,18 @@ async def pay_invoice_in_background(payment_request, wallet_id, description, ext
|
|||
|
||||
|
||||
async def get_lnurl_invoice(
|
||||
payoraddress, wallet_id, amount_msat, memo
|
||||
) -> Optional[str]:
|
||||
payoraddress: str, wallet_id: str, amount_msat: int, memo: str
|
||||
) -> str | None:
|
||||
|
||||
from lnbits.core.views.api import api_lnurlscan
|
||||
|
||||
data = await api_lnurlscan(payoraddress)
|
||||
rounded_amount = floor(amount_msat / 1000) * 1000
|
||||
|
||||
async with httpx.AsyncClient() as client:
|
||||
try:
|
||||
r = await client.get(
|
||||
data["callback"],
|
||||
params={"amount": rounded_amount, "comment": memo},
|
||||
timeout=5,
|
||||
)
|
||||
if r.is_error:
|
||||
raise httpx.ConnectError("issue with scrub callback")
|
||||
r.raise_for_status()
|
||||
except (httpx.ConnectError, httpx.RequestError):
|
||||
logger.error(
|
||||
f"splitting LNURL failed: Failed to connect to {data['callback']}."
|
||||
)
|
||||
return None
|
||||
except Exception as exc:
|
||||
logger.error(f"splitting LNURL failed: {exc!s}.")
|
||||
payment_request = await get_pr_from_lnurl(payoraddress, rounded_amount, memo)
|
||||
except Exception as e:
|
||||
logger.error(f"Error getting LNURL invoice: {e!s}")
|
||||
return None
|
||||
|
||||
params = json.loads(r.text)
|
||||
if params.get("status") == "ERROR":
|
||||
logger.error(f"{data['callback']} said: '{params.get('reason', '')}'")
|
||||
return None
|
||||
|
||||
invoice = bolt11.decode(params["pr"])
|
||||
invoice = bolt11.decode(payment_request)
|
||||
|
||||
lnurlp_payment = await get_standalone_payment(invoice.payment_hash)
|
||||
|
||||
|
|
@ -136,13 +117,4 @@ async def get_lnurl_invoice(
|
|||
logger.error("split failed. cannot split payments to yourself via LNURL.")
|
||||
return None
|
||||
|
||||
if invoice.amount_msat != rounded_amount:
|
||||
logger.error(
|
||||
f"""
|
||||
{data['callback']} returned an invalid invoice.
|
||||
Expected {amount_msat} msat, got {invoice.amount_msat}.
|
||||
"""
|
||||
)
|
||||
return None
|
||||
|
||||
return params["pr"]
|
||||
return payment_request
|
||||
|
|
|
|||
|
|
@ -38,12 +38,30 @@
|
|||
:hint="t === targets.length - 1 ? 'A name to identify this target wallet locally.' : undefined"
|
||||
style="width: 150px"
|
||||
></q-input>
|
||||
|
||||
<div class="column q-mt-none">
|
||||
<div class="col">
|
||||
<q-radio
|
||||
class="float-left"
|
||||
v-model="target.targetChoice"
|
||||
val="wallet"
|
||||
label="wallet"
|
||||
></q-radio>
|
||||
</div>
|
||||
<div class="col">
|
||||
<q-radio
|
||||
class="float-left"
|
||||
v-model="target.targetChoice"
|
||||
val="lnurl"
|
||||
label="lnurl"
|
||||
></q-radio>
|
||||
</div>
|
||||
</div>
|
||||
<q-input
|
||||
v-if="target.targetChoice === 'lnurl'"
|
||||
dense
|
||||
v-model.trim="target.wallet"
|
||||
label="Target"
|
||||
hint="A wallet ID, invoice key, LNURLp or Lightning Address."
|
||||
hint="LNURLp or Lightning Address."
|
||||
option-label="name"
|
||||
style="width: 500px"
|
||||
new-value-mode="add-unique"
|
||||
|
|
@ -51,6 +69,19 @@
|
|||
input-debounce="0"
|
||||
emit-value
|
||||
></q-input>
|
||||
<q-select
|
||||
v-if="target.targetChoice === 'wallet'"
|
||||
class="q-pr-md q-pt-sm"
|
||||
filled
|
||||
dense
|
||||
style="width: 500px"
|
||||
v-model="target.wallet"
|
||||
:options="g.user.walletOptions"
|
||||
emit-value
|
||||
map-options
|
||||
label="Receive wallet *"
|
||||
>
|
||||
</q-select>
|
||||
|
||||
<q-input
|
||||
style="width: 150px"
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue