Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 5 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
# Split Payments - <small>[LNbits](https://github.com/lnbits/lnbits) extension</small>

<small>For more about LNBits extension check [this tutorial](https://github.com/lnbits/lnbits/wiki/LNbits-Documentation#use-cases-of-lnbits)</small>

## Have payments split between multiple wallets
Expand All @@ -14,16 +13,16 @@ LNbits Split Payments extension allows for distributing payments across multiple

2. Add the wallet or wallets info to split payments to

![split wallets](https://i.imgur.com/5hCNWpg.png) - get the LNURLp, a LNaddress, wallet id, or an invoice key from a different wallet. It can be a completely different user on another instance/domain. You can get the wallet information on the API Info section on every wallet page\
![split wallets](https://image.nostr.build/1ccd166649cd67dd0c85c1b4f6decec3eb0fa267a311bc7c3efec8bd165fad05.png) - get the LNURLp, a LNaddress, wallet id, or an invoice key from a different wallet. It can be a completely different user on another instance/domain. You can get the wallet information on the API Info section on every wallet page\
![wallet info](https://i.imgur.com/betqflC.png) - set a wallet _Alias_ for your own identification\

- set how much, in percentage, this wallet will receive from every payment sent to the source wallet

3. When done with adding or deleting a set of targets, click "SAVE TARGETS" to make the splits effective.
3. When done with adding or deleting a set of targets, click "SAVE TARGETS" to make the splits effective.

4. You can have several wallets to split to, as long as the sum of the percentages is under or equal to 100%. It can only reach 100% if the targets are all internal ones.

5. When the source wallet receives a payment, the extension will automatically split the corresponding values to every wallet.
5. When the source wallet receives a payment, the extension will automatically split the corresponding values to every wallet.
- on receiving a 20 sats payment\
![get 20 sats payment](https://i.imgur.com/BKp0xvy.png)
- source wallet gets 18 sats\
Expand All @@ -34,12 +33,13 @@ 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
- 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

<img width="1148" alt="Bildschirm­foto 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="Bildschirm­foto 2023-05-01 um 22 17 52" src="https://user-images.githubusercontent.com/63317640/235534063-b2734654-7c1a-48a3-b48e-32798c232b49.png">


## Sponsored by

[![](https://cdn.shopify.com/s/files/1/0826/9235/files/cryptograffiti_logo_clear_background.png?v=1504730421)](https://cryptograffiti.com/)
72 changes: 64 additions & 8 deletions tasks.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,10 @@
from math import floor
from typing import Optional

import bech32
import websockets
import json

import httpx
from loguru import logger

Expand All @@ -16,7 +20,7 @@
from .crud import get_targets


async def wait_for_paid_invoices():
async def wait_for_paid_invoices():
invoice_queue = asyncio.Queue()
register_invoice_listener(invoice_queue, get_current_extension_name())

Expand All @@ -25,14 +29,13 @@ async def wait_for_paid_invoices():
await on_invoice_paid(payment)


async def on_invoice_paid(payment: Payment) -> None:

async def on_invoice_paid(payment: Payment) -> None:
if payment.extra.get("tag") == "splitpayments" or payment.extra.get("splitted"):
# already a splitted payment, ignore
return

targets = await get_targets(payment.wallet_id)

if not targets:
return

Expand All @@ -53,12 +56,24 @@ async def on_invoice_paid(payment: Payment) -> None:
f"Split payment: {target.percent}% for {target.alias or target.wallet}"
)

if target.wallet.find("@") >= 0 or target.wallet.find("LNURL") >= 0:
npubWallet = ''
if target.wallet.find("npub") >= 0:
npubWallet = await get_npub_address(target.wallet)

# if npubWallet is not empty, use it as the target wallet
if npubWallet != '':
targetWallet = npubWallet
else:
targetWallet = target.wallet

print(targetWallet + " " + str(amount_msat) + " " + memo)

if targetWallet.find("@") >= 0 or targetWallet.find("LNURL") >= 0:
safe_amount_msat = amount_msat - fee_reserve(amount_msat)
payment_request = await get_lnurl_invoice(
target.wallet, payment.wallet_id, safe_amount_msat, memo
targetWallet, payment.wallet_id, safe_amount_msat, memo
)
else:
else:
_, payment_request = await create_invoice(
wallet_id=target.wallet,
amount=int(amount_msat / 1000),
Expand All @@ -76,6 +91,47 @@ async def on_invoice_paid(payment: Payment) -> None:
extra=extra,
)

async def get_npub_address(
npub: str
) -> Optional[str]:

hrp, data = bech32.bech32_decode(npub)
raw_secret = bech32.convertbits(data, 5, 8)
if raw_secret[-1] != 0x0:
pubkey = str(bytes(raw_secret).hex())
else:
pubkey = str(bytes(raw_secret[:-1]).hex())

print("trying to get npub: ", pubkey)
uri = "wss://nostr-pub.wellorder.net"
jsonOb = ''

# TODO utilize a try except and find out why websocket is not connecting
async with websockets.connect(uri) as websocket:
#websocket = websockets.connect(uri)
req = '["REQ", "a", {"kinds": [0], "limit": 10, "authors": ["'+ pubkey +'"]} ]'
''' send req to websocket and print response'''
await websocket.send(req)
greeting = await websocket.recv()
output = json.loads(greeting)
jsonOb = json.loads(output[2]['content'])

#npubWallet06 = ''
#npubWallet16 = ''
npubWallet = ''
if "lud16" in jsonOb and npubWallet == '':
logger.info("we got a lud16: ", jsonOb["lud16"])
if len(jsonOb["lud16"]) > 1:
npubWallet = jsonOb["lud16"]
#if "lud06" in jsonOb:
# logger.info("we got a lud06: ", jsonOb["lud06"])
# if len(jsonOb["lud06"]) > 1:
# npubWallet = jsonOb["lud06"]

if npubWallet == '':
print("Failed to get npub wallet")

return npubWallet

async def get_lnurl_invoice(
payoraddress, wallet_id, amount_msat, memo
Expand Down
12 changes: 5 additions & 7 deletions templates/splitpayments/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -34,17 +34,17 @@ <h5 class="text-subtitle1 q-my-none">Target Wallets</h5>
<q-input
dense
outlined
v-model.trim="target.alias"
v-model="target.alias"
label="Alias"
:hint="t === targets.length - 1 ? 'A name to identify this target wallet locally.' : undefined"
style="width: 150px"
></q-input>

<q-input
dense
v-model.trim="target.wallet"
v-model="target.wallet"
label="Target"
hint="A wallet ID, invoice key, LNURLp or Lightning Address."
hint="A wallet ID, invoice key, LNURLp, NPUB, or Lightning Address."
option-label="name"
style="width: 500px"
new-value-mode="add-unique"
Expand Down Expand Up @@ -118,12 +118,10 @@ <h6 class="text-subtitle1 q-my-none">
This is valid for every payment, doesn't matter how it was created.
</p>
<p>
Targets can be LNBits wallets from this LNBits instance or any valid
LNURL or LN Address.
Targets can be LNBits wallets from this LNBits instance or any valid LNURL or LN Address.
</p>
<p class="text-warning">
LNURLp and LN Addresses must allow comments > 100 chars and also
have a flexible amount.
LNURLp and LN Addresses must allow comments > 100 chars and also have a flexible amount.
</p>
<p>
To remove a wallet from the targets list just press the X and save.
Expand Down
17 changes: 9 additions & 8 deletions views_api.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
from http import HTTPStatus
from typing import List


from fastapi import Depends
from loguru import logger
from starlette.exceptions import HTTPException
Expand All @@ -12,29 +13,28 @@
from .crud import get_targets, set_targets
from .models import Target, TargetPutList


@splitpayments_ext.get("/api/v1/targets")
async def api_targets_get(
wallet: WalletTypeInfo = Depends(require_admin_key),
) -> List[Target]:
targets = await get_targets(wallet.wallet.id)
targets = await get_targets(wallet.wallet.id)
return targets or []


@splitpayments_ext.put("/api/v1/targets", status_code=HTTPStatus.OK)
async def api_targets_set(
target_put: TargetPutList,
source_wallet: WalletTypeInfo = Depends(require_admin_key),
) -> None:

try:
targets: List[Target] = []
for entry in target_put.targets:
for entry in target_put.targets:

if entry.wallet.find("@") < 0 and entry.wallet.find("LNURL") < 0:
if entry.wallet.find("@") < 0 and entry.wallet.find("LNURL") < 0 and entry.wallet.find("npub") < 0:
wallet = await get_wallet(entry.wallet)
if not wallet:
wallet = await get_wallet_for_key(entry.wallet, "invoice")
if not wallet:
if not wallet:
raise HTTPException(
status_code=HTTPStatus.BAD_REQUEST,
detail=f"Invalid wallet '{entry.wallet}'.",
Expand All @@ -43,20 +43,21 @@ async def api_targets_set(
if wallet.id == source_wallet.wallet.id:
raise HTTPException(
status_code=HTTPStatus.BAD_REQUEST, detail="Can't split to itself."
)
)

if entry.percent <= 0:
raise HTTPException(
status_code=HTTPStatus.BAD_REQUEST,
detail=f"Invalid percent '{entry.percent}'.",
)

targets.append(
Target(
wallet=entry.wallet,
source=source_wallet.wallet.id,
percent=entry.percent,
alias=entry.alias,
walletName=entry.wallet,
)
)

Expand Down