removed all but one extension, so we can focus on core

This commit is contained in:
Ben Arc
2021-08-16 18:12:48 +01:00
parent afc2b78a25
commit 9c313586bd
274 changed files with 0 additions and 26032 deletions
-34
View File
@@ -1,34 +0,0 @@
# Split Payments
## Have payments split between multiple wallets
LNBits Split Payments extension allows for distributing payments across multiple wallets. Set it and forget it. It will keep splitting your payments across wallets forever.
## Usage
1. After enabling the extension, choose the source wallet that will receive and distribute the Payments
![choose wallet](https://i.imgur.com/nPQudqL.png)
2. Add the wallet or wallets info to split payments to
![split wallets](https://i.imgur.com/5hCNWpg.png) - get the wallet id, or an invoice key from a different wallet. It can be a completely different user as long as it's under the same LNbits 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 wallets
3. When done, 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%
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\
![source wallet](https://i.imgur.com/GCxDZ5s.png)
- Ben's wallet (the wallet from the example) instantly, and feeless, gets the corresponding 10%, or 2 sats\
![ben wallet](https://i.imgur.com/MfsccNa.png)
## Sponsored by
[![](https://cdn.shopify.com/s/files/1/0826/9235/files/cryptograffiti_logo_clear_background.png?v=1504730421)](https://cryptograffiti.com/)
@@ -1,18 +0,0 @@
from quart import Blueprint
from lnbits.db import Database
db = Database("ext_splitpayments")
splitpayments_ext: Blueprint = Blueprint(
"splitpayments", __name__, static_folder="static", template_folder="templates"
)
from .views_api import * # noqa
from .views import * # noqa
from .tasks import register_listeners
from lnbits.tasks import record_async
splitpayments_ext.record(record_async(register_listeners))
@@ -1,9 +0,0 @@
{
"name": "SplitPayments",
"short_description": "Split incoming payments across wallets",
"icon": "call_split",
"contributors": [
"fiatjaf",
"cryptograffiti"
]
}
-27
View File
@@ -1,27 +0,0 @@
from typing import List
from . import db
from .models import Target
async def get_targets(source_wallet: str) -> List[Target]:
rows = await db.fetchall(
"SELECT * FROM splitpayments.targets WHERE source = ?", (source_wallet,)
)
return [Target(**dict(row)) for row in rows]
async def set_targets(source_wallet: str, targets: List[Target]):
async with db.connect() as conn:
await conn.execute(
"DELETE FROM splitpayments.targets WHERE source = ?", (source_wallet,)
)
for target in targets:
await conn.execute(
"""
INSERT INTO splitpayments.targets
(source, wallet, percent, alias)
VALUES (?, ?, ?, ?)
""",
(source_wallet, target.wallet, target.percent, target.alias),
)
@@ -1,16 +0,0 @@
async def m001_initial(db):
"""
Initial split payment table.
"""
await db.execute(
"""
CREATE TABLE splitpayments.targets (
wallet TEXT NOT NULL,
source TEXT NOT NULL,
percent INTEGER NOT NULL CHECK (percent >= 0 AND percent <= 100),
alias TEXT,
UNIQUE (source, wallet)
);
"""
)
@@ -1,8 +0,0 @@
from typing import NamedTuple
class Target(NamedTuple):
wallet: str
source: str
percent: int
alias: str
@@ -1,143 +0,0 @@
/* globals Quasar, Vue, _, VueQrcode, windowMixin, LNbits, LOCALE */
Vue.component(VueQrcode.name, VueQrcode)
function hashTargets(targets) {
return targets
.filter(isTargetComplete)
.map(({wallet, percent, alias}) => `${wallet}${percent}${alias}`)
.join('')
}
function isTargetComplete(target) {
return target.wallet && target.wallet.trim() !== '' && target.percent > 0
}
new Vue({
el: '#vue',
mixins: [windowMixin],
data() {
return {
selectedWallet: null,
currentHash: '', // a string that must match if the edit data is unchanged
targets: []
}
},
computed: {
isDirty() {
return hashTargets(this.targets) !== this.currentHash
}
},
methods: {
clearTargets() {
this.targets = [{}]
this.$q.notify({
message:
'Cleared the form, but not saved. You must click to save manually.',
timeout: 500
})
},
getTargets() {
LNbits.api
.request(
'GET',
'/splitpayments/api/v1/targets',
this.selectedWallet.adminkey
)
.catch(err => {
LNbits.utils.notifyApiError(err)
})
.then(response => {
this.currentHash = hashTargets(response.data)
this.targets = response.data.concat({})
})
},
changedWallet(wallet) {
this.selectedWallet = wallet
this.getTargets()
},
targetChanged(isPercent, index) {
// fix percent min and max range
if (isPercent) {
if (this.targets[index].percent > 100) this.targets[index].percent = 100
if (this.targets[index].percent < 0) this.targets[index].percent = 0
}
// remove empty lines (except last)
if (this.targets.length >= 2) {
for (let i = this.targets.length - 2; i >= 0; i--) {
let target = this.targets[i]
if (
(!target.wallet || target.wallet.trim() === '') &&
(!target.alias || target.alias.trim() === '') &&
!target.percent
) {
this.targets.splice(i, 1)
}
}
}
// add a line at the end if the last one is filled
let last = this.targets[this.targets.length - 1]
if (last.wallet && last.wallet.trim() !== '' && last.percent > 0) {
this.targets.push({})
}
// sum of all percents
let currentTotal = this.targets.reduce(
(acc, target) => acc + (target.percent || 0),
0
)
// remove last (unfilled) line if the percent is already 100
if (currentTotal >= 100) {
let last = this.targets[this.targets.length - 1]
if (
(!last.wallet || last.wallet.trim() === '') &&
(!last.alias || last.alias.trim() === '') &&
!last.percent
) {
this.targets = this.targets.slice(0, -1)
}
}
// adjust percents of other lines (not this one)
if (currentTotal > 100 && isPercent) {
let diff = (currentTotal - 100) / (100 - this.targets[index].percent)
this.targets.forEach((target, t) => {
if (t !== index) target.percent -= Math.round(diff * target.percent)
})
}
// overwrite so changes appear
this.targets = this.targets
},
saveTargets() {
LNbits.api
.request(
'PUT',
'/splitpayments/api/v1/targets',
this.selectedWallet.adminkey,
{
targets: this.targets
.filter(isTargetComplete)
.map(({wallet, percent, alias}) => ({wallet, percent, alias}))
}
)
.then(response => {
this.$q.notify({
message: 'Split payments targets set.',
timeout: 700
})
this.getTargets()
})
.catch(err => {
LNbits.utils.notifyApiError(err)
})
}
},
created() {
this.selectedWallet = this.g.user.wallets[0]
this.getTargets()
}
})
-77
View File
@@ -1,77 +0,0 @@
import json
import trio
from lnbits.core.models import Payment
from lnbits.core.crud import create_payment
from lnbits.core import db as core_db
from lnbits.tasks import register_invoice_listener, internal_invoice_paid
from lnbits.helpers import urlsafe_short_hash
from .crud import get_targets
async def register_listeners():
invoice_paid_chan_send, invoice_paid_chan_recv = trio.open_memory_channel(2)
register_invoice_listener(invoice_paid_chan_send)
await wait_for_paid_invoices(invoice_paid_chan_recv)
async def wait_for_paid_invoices(invoice_paid_chan: trio.MemoryReceiveChannel):
async for payment in invoice_paid_chan:
await on_invoice_paid(payment)
async def on_invoice_paid(payment: Payment) -> None:
if "splitpayments" == payment.extra.get("tag") or payment.extra.get("splitted"):
# already splitted, ignore
return
# now we make some special internal transfers (from no one to the receiver)
targets = await get_targets(payment.wallet_id)
transfers = [
(target.wallet, int(target.percent * payment.amount / 100))
for target in targets
]
transfers = [(wallet, amount) for wallet, amount in transfers if amount > 0]
amount_left = payment.amount - sum([amount for _, amount in transfers])
if amount_left < 0:
print("splitpayments failure: amount_left is negative.", payment.payment_hash)
return
if not targets:
return
# mark the original payment with one extra key, "splitted"
# (this prevents us from doing this process again and it's informative)
# and reduce it by the amount we're going to send to the producer
await core_db.execute(
"""
UPDATE apipayments
SET extra = ?, amount = ?
WHERE hash = ?
AND checking_id NOT LIKE 'internal_%'
""",
(
json.dumps(dict(**payment.extra, splitted=True)),
amount_left,
payment.payment_hash,
),
)
# perform the internal transfer using the same payment_hash
for wallet, amount in transfers:
internal_checking_id = f"internal_{urlsafe_short_hash()}"
await create_payment(
wallet_id=wallet,
checking_id=internal_checking_id,
payment_request="",
payment_hash=payment.payment_hash,
amount=amount,
memo=payment.memo,
pending=False,
extra={"tag": "splitpayments"},
)
# manually send this for now
await internal_invoice_paid.send(internal_checking_id)
@@ -1,90 +0,0 @@
<q-expansion-item
group="extras"
icon="swap_vertical_circle"
label="How to use"
:content-inset-level="0.5"
>
<q-card>
<q-card-section>
<p>
Add some wallets to the list of "Target Wallets", each with an
associated <em>percent</em>. After saving, every time any payment
arrives at the "Source Wallet" that payment will be split with the
target wallets according to their percent.
</p>
<p>This is valid for every payment, doesn't matter how it was created.</p>
<p>Target wallets can be any wallet from this same LNbits instance.</p>
<p>
To remove a wallet from the targets list, just erase its fields and
save. To remove all, click "Clear" then save.
</p>
</q-card-section>
</q-card>
</q-expansion-item>
<q-expansion-item
group="extras"
icon="swap_vertical_circle"
label="API info"
:content-inset-level="0.5"
>
<q-expansion-item
group="api"
dense
expand-separator
label="List Target Wallets"
>
<q-card>
<q-card-section>
<code
><span class="text-blue">GET</span>
/splitpayments/api/v1/targets</code
>
<h5 class="text-caption q-mt-sm q-mb-none">Headers</h5>
<code>{"X-Api-Key": &lt;admin_key&gt;}</code><br />
<h5 class="text-caption q-mt-sm q-mb-none">Body (application/json)</h5>
<h5 class="text-caption q-mt-sm q-mb-none">
Returns 200 OK (application/json)
</h5>
<code
>[{"wallet": &lt;wallet id&gt;, "alias": &lt;chosen name for this
wallet&gt;, "percent": &lt;number between 1 and 100&gt;}, ...]</code
>
<h5 class="text-caption q-mt-sm q-mb-none">Curl example</h5>
<code
>curl -X GET {{ request.url_root }}api/v1/livestream -H "X-Api-Key: {{
g.user.wallets[0].inkey }}"
</code>
</q-card-section>
</q-card>
</q-expansion-item>
<q-expansion-item
group="api"
dense
expand-separator
label="Set Target Wallets"
>
<q-card>
<q-card-section>
<code
><span class="text-blue">PUT</span>
/splitpayments/api/v1/targets</code
>
<h5 class="text-caption q-mt-sm q-mb-none">Headers</h5>
<code>{"X-Api-Key": &lt;admin_key&gt;}</code><br />
<h5 class="text-caption q-mt-sm q-mb-none">Body (application/json)</h5>
<h5 class="text-caption q-mt-sm q-mb-none">
Returns 200 OK (application/json)
</h5>
<h5 class="text-caption q-mt-sm q-mb-none">Curl example</h5>
<code
>curl -X PUT {{ request.url_root }}api/v1/splitpayments/targets -H
"X-Api-Key: {{ g.user.wallets[0].adminkey }}" -H 'Content-Type:
application/json' -d '{"targets": [{"wallet": &lt;wallet id or invoice
key&gt;, "alias": &lt;name to identify this&gt;, "percent": &lt;number
between 1 and 100&gt;}, ...]}'
</code>
</q-card-section>
</q-card>
</q-expansion-item>
</q-expansion-item>
@@ -1,100 +0,0 @@
{% extends "base.html" %} {% from "macros.jinja" import window_vars with context
%} {% block page %}
<div class="row q-col-gutter-md">
<div class="col-12 col-md-7 q-gutter-y-md">
<q-card class="q-pa-sm col-5">
<q-card-section class="q-pa-none text-center">
<q-form class="q-gutter-md">
<q-select
filled
dense
:options="g.user.wallets"
:value="selectedWallet"
label="Source Wallet:"
option-label="name"
@input="changedWallet"
>
</q-select>
</q-form>
</q-card-section>
</q-card>
<q-card class="q-pa-sm col-5">
<q-card-section class="q-pa-none text-center">
<div class="col">
<h5 class="text-subtitle1 q-my-none">Target Wallets</h5>
</div>
<q-form class="q-gutter-md" @submit="saveTargets">
<div
class="q-gutter-md row items-start"
style="flex-wrap: nowrap"
v-for="(target, t) in targets"
>
<q-input
dense
outlined
v-model="target.wallet"
label="Wallet"
:hint="t === targets.length - 1 ? 'A wallet ID or invoice key.' : undefined"
@input="targetChanged(false)"
></q-input>
<q-input
dense
outlined
v-model="target.alias"
label="Alias"
:hint="t === targets.length - 1 ? 'A name to identify this target wallet locally.' : undefined"
@input="targetChanged(false)"
></q-input>
<q-input
dense
outlined
v-model.number="target.percent"
label="Split Share"
:hint="t === targets.length - 1 ? 'How much of the incoming payments will go to the target wallet.' : undefined"
suffix="%"
@input="targetChanged(true, t)"
></q-input>
</div>
<q-row class="row justify-evenly q-pa-lg">
<q-col>
<q-btn unelevated outline color="secondary" @click="clearTargets">
Clear
</q-btn>
</q-col>
<q-col>
<q-btn
unelevated
color="primary"
type="submit"
:disabled="!isDirty"
>
Save Targets
</q-btn>
</q-col>
</q-row>
</q-form>
</q-card-section>
</q-card>
</div>
<div class="col-12 col-md-5 q-gutter-y-md">
<q-card>
<q-card-section>
<h6 class="text-subtitle1 q-my-none">
{{SITE_TITLE}} SplitPayments extension
</h6>
</q-card-section>
<q-card-section class="q-pa-none">
<q-separator></q-separator>
<q-list> {% include "splitpayments/_api_docs.html" %} </q-list>
</q-card-section>
</q-card>
</div>
</div>
{% endblock %} {% block scripts %} {{ window_vars(user) }}
<script src="/splitpayments/static/js/index.js"></script>
{% endblock %}
-12
View File
@@ -1,12 +0,0 @@
from quart import g, render_template
from lnbits.decorators import check_user_exists, validate_uuids
from . import splitpayments_ext
@splitpayments_ext.route("/")
@validate_uuids(["usr"], required=True)
@check_user_exists()
async def index():
return await render_template("splitpayments/index.html", user=g.user)
@@ -1,70 +0,0 @@
from quart import g, jsonify
from http import HTTPStatus
from lnbits.decorators import api_check_wallet_key, api_validate_post_request
from lnbits.core.crud import get_wallet, get_wallet_for_key
from . import splitpayments_ext
from .crud import get_targets, set_targets
from .models import Target
@splitpayments_ext.route("/api/v1/targets", methods=["GET"])
@api_check_wallet_key("admin")
async def api_targets_get():
targets = await get_targets(g.wallet.id)
return jsonify([target._asdict() for target in targets] or [])
@splitpayments_ext.route("/api/v1/targets", methods=["PUT"])
@api_check_wallet_key("admin")
@api_validate_post_request(
schema={
"targets": {
"type": "list",
"schema": {
"type": "dict",
"schema": {
"wallet": {"type": "string"},
"alias": {"type": "string"},
"percent": {"type": "integer"},
},
},
}
}
)
async def api_targets_set():
targets = []
for entry in g.data["targets"]:
wallet = await get_wallet(entry["wallet"])
if not wallet:
wallet = await get_wallet_for_key(entry["wallet"], "invoice")
if not wallet:
return (
jsonify({"message": f"Invalid wallet '{entry['wallet']}'."}),
HTTPStatus.BAD_REQUEST,
)
if wallet.id == g.wallet.id:
return (
jsonify({"message": "Can't split to itself."}),
HTTPStatus.BAD_REQUEST,
)
if entry["percent"] < 0:
return (
jsonify({"message": f"Invalid percent '{entry['percent']}'."}),
HTTPStatus.BAD_REQUEST,
)
targets.append(
Target(wallet.id, g.wallet.id, entry["percent"], entry["alias"] or "")
)
percent_sum = sum([target.percent for target in targets])
if percent_sum > 100:
return jsonify({"message": "Splitting over 100%."}), HTTPStatus.BAD_REQUEST
await set_targets(g.wallet.id, targets)
return "", HTTPStatus.OK