feat: add payment for stoeage
This commit is contained in:
parent
2233521a43
commit
dfda2367a2
5 changed files with 130 additions and 35 deletions
|
|
@ -10,3 +10,11 @@ UI for diagnostics and management (key alow/ban lists, rate limiting) coming soo
|
||||||
|
|
||||||
1. Enable extension
|
1. Enable extension
|
||||||
2. Enable relay
|
2. Enable relay
|
||||||
|
|
||||||
|
|
||||||
|
### Development
|
||||||
|
|
||||||
|
Create Symbolic Link:
|
||||||
|
```
|
||||||
|
ln -s /Users/my-user/git-repos/nostr-relay-extension/ /Users/my-user/git-repos/lnbits/lnbits/extensions/nostrrelay
|
||||||
|
```
|
||||||
|
|
@ -307,10 +307,14 @@ class NostrFilter(BaseModel):
|
||||||
return inner_joins, where, values
|
return inner_joins, where, values
|
||||||
|
|
||||||
|
|
||||||
class RelayJoin(BaseModel):
|
class BuyOrder(BaseModel):
|
||||||
|
action: str
|
||||||
relay_id: str
|
relay_id: str
|
||||||
pubkey: str
|
pubkey: str
|
||||||
|
units_to_buy = 0
|
||||||
|
|
||||||
|
def is_valid_action(self):
|
||||||
|
return self.action in ['join', 'storage']
|
||||||
|
|
||||||
class NostrAccount(BaseModel):
|
class NostrAccount(BaseModel):
|
||||||
pubkey: str
|
pubkey: str
|
||||||
|
|
|
||||||
22
tasks.py
22
tasks.py
|
|
@ -31,6 +31,11 @@ async def on_invoice_paid(payment: Payment):
|
||||||
await invoice_paid_to_join(relay_id, pubkey)
|
await invoice_paid_to_join(relay_id, pubkey)
|
||||||
return
|
return
|
||||||
|
|
||||||
|
if payment.extra.get("action") == "storage":
|
||||||
|
storage_to_buy = payment.extra.get("storage_to_buy")
|
||||||
|
await invoice_paid_for_storage(relay_id, pubkey, storage_to_buy)
|
||||||
|
return
|
||||||
|
|
||||||
async def invoice_paid_to_join(relay_id: str, pubkey: str):
|
async def invoice_paid_to_join(relay_id: str, pubkey: str):
|
||||||
try:
|
try:
|
||||||
account = await get_account(relay_id, pubkey)
|
account = await get_account(relay_id, pubkey)
|
||||||
|
|
@ -44,5 +49,22 @@ async def invoice_paid_to_join(relay_id: str, pubkey: str):
|
||||||
account.paid_to_join = True
|
account.paid_to_join = True
|
||||||
await update_account(relay_id, account)
|
await update_account(relay_id, account)
|
||||||
|
|
||||||
|
except Exception as ex:
|
||||||
|
logger.warning(ex)
|
||||||
|
|
||||||
|
|
||||||
|
async def invoice_paid_for_storage(relay_id: str, pubkey: str, storage_to_buy: int):
|
||||||
|
try:
|
||||||
|
account = await get_account(relay_id, pubkey)
|
||||||
|
if not account:
|
||||||
|
await create_account(relay_id, NostrAccount(pubkey=pubkey, storage=storage_to_buy))
|
||||||
|
return
|
||||||
|
|
||||||
|
if account.blocked:
|
||||||
|
return
|
||||||
|
|
||||||
|
account.storage = storage_to_buy
|
||||||
|
await update_account(relay_id, account)
|
||||||
|
|
||||||
except Exception as ex:
|
except Exception as ex:
|
||||||
logger.warning(ex)
|
logger.warning(ex)
|
||||||
|
|
@ -12,7 +12,7 @@
|
||||||
<h4 v-text="relay.name" class="q-my-none"></h4>
|
<h4 v-text="relay.name" class="q-my-none"></h4>
|
||||||
</q-card-section>
|
</q-card-section>
|
||||||
</q-card>
|
</q-card>
|
||||||
<q-card>
|
<q-card class="q-pb-xl">
|
||||||
<q-card-section>
|
<q-card-section>
|
||||||
<span class="text-bold">Public Key:</span>
|
<span class="text-bold">Public Key:</span>
|
||||||
<q-input
|
<q-input
|
||||||
|
|
@ -24,38 +24,78 @@
|
||||||
></q-input>
|
></q-input>
|
||||||
</q-card-section>
|
</q-card-section>
|
||||||
<q-card-section v-if="relay.config.isPaidRelay">
|
<q-card-section v-if="relay.config.isPaidRelay">
|
||||||
<q-btn
|
<div class="row q-mb-md">
|
||||||
@click="createJoinInvoice"
|
<div class="col-2">
|
||||||
unelevated
|
<span class="text-bold">Cost to join: </span>
|
||||||
color="primary float-right"
|
</div>
|
||||||
>Show Invoice</q-btn
|
<div class="col-6">
|
||||||
>
|
<span v-text="relay.config.costToJoin"></span>
|
||||||
<span class="text-bold">Cost to join: </span>
|
<span class="text-bold q-ml-sm">sats</span>
|
||||||
|
</div>
|
||||||
<span v-text="relay.config.costToJoin"></span>
|
<div class="col-4">
|
||||||
<q-badge color="orange">
|
<q-btn
|
||||||
<span>sats</span>
|
@click="createInvoice('join')"
|
||||||
</q-badge>
|
unelevated
|
||||||
|
color="primary"
|
||||||
|
class="float-right"
|
||||||
|
>Pay to Join</q-btn
|
||||||
|
>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<q-separator></q-separator>
|
||||||
|
<div class="row q-mt-md q-mb-md">
|
||||||
|
<div class="col-2">
|
||||||
|
<span class="text-bold">Storage cost: </span>
|
||||||
|
</div>
|
||||||
|
<div class="col-3">
|
||||||
|
<span v-text="relay.config.storageCostValue"></span>
|
||||||
|
<span class="text-bold q-ml-sm"> sats per</span>
|
||||||
|
<q-badge color="orange">
|
||||||
|
<span v-text="relay.config.storageCostUnit"></span>
|
||||||
|
</q-badge>
|
||||||
|
</div>
|
||||||
|
<div class="col-2">
|
||||||
|
<q-input
|
||||||
|
filled
|
||||||
|
dense
|
||||||
|
v-model="unitsToBuy"
|
||||||
|
type="number"
|
||||||
|
min="0"
|
||||||
|
:label="relay.config.storageCostUnit"
|
||||||
|
></q-input>
|
||||||
|
</div>
|
||||||
|
<div class="col-2">
|
||||||
|
<span class="text-bold q-ml-md" v-text="storageCost"></span>
|
||||||
|
<span>sats</span>
|
||||||
|
</div>
|
||||||
|
<div class="col-3">
|
||||||
|
<q-btn
|
||||||
|
@click="createInvoice('storage')"
|
||||||
|
unelevated
|
||||||
|
color="primary"
|
||||||
|
class="float-right"
|
||||||
|
>Buy storage space</q-btn
|
||||||
|
>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<q-separator></q-separator>
|
||||||
</q-card-section>
|
</q-card-section>
|
||||||
<q-card-section v-else>
|
<q-card-section v-else>
|
||||||
<q-badge color="yellow" text-color="black">
|
<q-badge color="yellow" text-color="black">
|
||||||
This is a free relay
|
This is a free relay
|
||||||
</q-badge>
|
</q-badge>
|
||||||
</q-card-section>
|
</q-card-section>
|
||||||
<q-card-section v-if="joinInvoice">
|
<q-card-section v-if="invoice">
|
||||||
<q-expansion-item
|
<q-expansion-item
|
||||||
group="join-invoice"
|
group="join-invoice"
|
||||||
label="Pay invoice to join relay"
|
label="Invoice"
|
||||||
:content-inset-level="0.5"
|
:content-inset-level="0.5"
|
||||||
default-opened
|
default-opened
|
||||||
>
|
>
|
||||||
<div class="row q-ma-md">
|
<div class="row q-ma-md">
|
||||||
<div class="col-3"></div>
|
<div class="col-3"></div>
|
||||||
<div class="col-6 text-center">
|
<div class="col-6 text-center">
|
||||||
<q-btn
|
<q-btn outline color="grey" @click="copyText(invoice)"
|
||||||
outline
|
|
||||||
color="grey"
|
|
||||||
@click="copyText(joinInvoice)"
|
|
||||||
>Copy invoice</q-btn
|
>Copy invoice</q-btn
|
||||||
>
|
>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -66,7 +106,7 @@
|
||||||
<div class="col-6">
|
<div class="col-6">
|
||||||
<q-responsive :ratio="1">
|
<q-responsive :ratio="1">
|
||||||
<qrcode
|
<qrcode
|
||||||
:value="'lightning:'+joinInvoice"
|
:value="'lightning:'+invoice"
|
||||||
:options="{width: 340}"
|
:options="{width: 340}"
|
||||||
class="rounded-borders"
|
class="rounded-borders"
|
||||||
></qrcode>
|
></qrcode>
|
||||||
|
|
@ -163,11 +203,19 @@
|
||||||
return {
|
return {
|
||||||
relay: JSON.parse('{{relay | tojson | safe}}'),
|
relay: JSON.parse('{{relay | tojson | safe}}'),
|
||||||
pubkey: '',
|
pubkey: '',
|
||||||
joinInvoice: ''
|
invoice: '',
|
||||||
|
unitsToBuy: 0
|
||||||
|
}
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
storageCost: function () {
|
||||||
|
if (!this.relay || !this.relay.config.storageCostValue) return 0
|
||||||
|
return this.unitsToBuy * this.relay.config.storageCostValue
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
createJoinInvoice: async function () {
|
createInvoice: async function (action) {
|
||||||
|
if (!action) return
|
||||||
if (!this.pubkey) {
|
if (!this.pubkey) {
|
||||||
this.$q.notify({
|
this.$q.notify({
|
||||||
timeout: 5000,
|
timeout: 5000,
|
||||||
|
|
@ -177,17 +225,20 @@
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
|
const reqData = {
|
||||||
|
action,
|
||||||
|
relay_id: this.relay.id,
|
||||||
|
pubkey: this.pubkey,
|
||||||
|
units_to_buy: this.unitsToBuy
|
||||||
|
}
|
||||||
const {data} = await LNbits.api.request(
|
const {data} = await LNbits.api.request(
|
||||||
'PUT',
|
'PUT',
|
||||||
'/nostrrelay/api/v1/join',
|
'/nostrrelay/api/v1/pay',
|
||||||
'',
|
'',
|
||||||
{
|
reqData
|
||||||
relay_id: this.relay.id,
|
|
||||||
pubkey: this.pubkey
|
|
||||||
}
|
|
||||||
)
|
)
|
||||||
console.log('### data.invoice', data.invoice)
|
console.log('### data.invoice', data.invoice)
|
||||||
this.joinInvoice = data.invoice
|
this.invoice = data.invoice
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
LNbits.utils.notifyApiError(error)
|
LNbits.utils.notifyApiError(error)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
22
views_api.py
22
views_api.py
|
|
@ -27,7 +27,7 @@ from .crud import (
|
||||||
update_relay,
|
update_relay,
|
||||||
)
|
)
|
||||||
from .helpers import normalize_public_key
|
from .helpers import normalize_public_key
|
||||||
from .models import NostrRelay, RelayJoin
|
from .models import BuyOrder, NostrRelay
|
||||||
|
|
||||||
client_manager = NostrClientManager()
|
client_manager = NostrClientManager()
|
||||||
|
|
||||||
|
|
@ -154,9 +154,8 @@ async def api_delete_relay(
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@nostrrelay_ext.put("/api/v1/join")
|
@nostrrelay_ext.put("/api/v1/pay")
|
||||||
async def api_pay_to_join(data: RelayJoin):
|
async def api_pay_to_join(data: BuyOrder):
|
||||||
|
|
||||||
try:
|
try:
|
||||||
pubkey = normalize_public_key(data.pubkey)
|
pubkey = normalize_public_key(data.pubkey)
|
||||||
relay = await get_relay_by_id(data.relay_id)
|
relay = await get_relay_by_id(data.relay_id)
|
||||||
|
|
@ -166,18 +165,29 @@ async def api_pay_to_join(data: RelayJoin):
|
||||||
detail="Relay not found",
|
detail="Relay not found",
|
||||||
)
|
)
|
||||||
|
|
||||||
if relay.is_free_to_join:
|
if data.action == 'join' and relay.is_free_to_join:
|
||||||
raise ValueError("Relay is free to join")
|
raise ValueError("Relay is free to join")
|
||||||
|
|
||||||
|
storage_to_buy = 0
|
||||||
|
if data.action == 'storage':
|
||||||
|
if relay.config.storage_cost_value == 0:
|
||||||
|
raise ValueError("Relay storage cost is zero. Cannot buy!")
|
||||||
|
if data.units_to_buy == 0:
|
||||||
|
raise ValueError("Must specify how much storage to buy!")
|
||||||
|
storage_to_buy = data.units_to_buy * relay.config.storage_cost_value * 1024
|
||||||
|
if relay.config.storage_cost_unit == "MB":
|
||||||
|
storage_to_buy *= 1024
|
||||||
|
|
||||||
_, payment_request = await create_invoice(
|
_, payment_request = await create_invoice(
|
||||||
wallet_id=relay.config.wallet,
|
wallet_id=relay.config.wallet,
|
||||||
amount=int(relay.config.cost_to_join),
|
amount=int(relay.config.cost_to_join),
|
||||||
memo=f"Pubkey '{data.pubkey}' wants to join {relay.id}",
|
memo=f"Pubkey '{data.pubkey}' wants to join {relay.id}",
|
||||||
extra={
|
extra={
|
||||||
"tag": "nostrrely",
|
"tag": "nostrrely",
|
||||||
"action": "join",
|
"action": data.action,
|
||||||
"relay_id": relay.id,
|
"relay_id": relay.id,
|
||||||
"pubkey": pubkey,
|
"pubkey": pubkey,
|
||||||
|
"storage_to_buy": storage_to_buy
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
print("### payment_request", payment_request)
|
print("### payment_request", payment_request)
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue