watchonly done
This commit is contained in:
parent
ec4117a5f4
commit
6935589dad
|
@ -1,13 +1,25 @@
|
||||||
from quart import Blueprint
|
import asyncio
|
||||||
|
|
||||||
|
from fastapi import APIRouter
|
||||||
|
|
||||||
from lnbits.db import Database
|
from lnbits.db import Database
|
||||||
|
from lnbits.helpers import template_renderer
|
||||||
|
|
||||||
db = Database("ext_watchonly")
|
db = Database("ext_watchonly")
|
||||||
|
|
||||||
|
|
||||||
watchonly_ext: Blueprint = Blueprint(
|
watchonly_ext: APIRouter = APIRouter(
|
||||||
"watchonly", __name__, static_folder="static", template_folder="templates"
|
prefix="/watchonly",
|
||||||
|
tags=["watchonly"]
|
||||||
)
|
)
|
||||||
|
|
||||||
|
def watchonly_renderer():
|
||||||
|
return template_renderer(
|
||||||
|
[
|
||||||
|
"lnbits/extensions/watchonly/templates",
|
||||||
|
]
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
from .views_api import * # noqa
|
from .views_api import * # noqa
|
||||||
from .views import * # noqa
|
from .views import * # noqa
|
||||||
|
|
|
@ -74,8 +74,9 @@ def parse_key(masterpub: str):
|
||||||
return desc, network
|
return desc, network
|
||||||
|
|
||||||
|
|
||||||
async def create_watch_wallet(*, user: str, masterpub: str, title: str) -> Wallets:
|
async def create_watch_wallet(user: str, masterpub: str, title: str) -> Wallets:
|
||||||
# check the masterpub is fine, it will raise an exception if not
|
# check the masterpub is fine, it will raise an exception if not
|
||||||
|
print("PARSE", parse_key(masterpub))
|
||||||
parse_key(masterpub)
|
parse_key(masterpub)
|
||||||
wallet_id = urlsafe_short_hash()
|
wallet_id = urlsafe_short_hash()
|
||||||
await db.execute(
|
await db.execute(
|
||||||
|
@ -131,19 +132,20 @@ async def delete_watch_wallet(wallet_id: str) -> None:
|
||||||
|
|
||||||
async def get_derive_address(wallet_id: str, num: int):
|
async def get_derive_address(wallet_id: str, num: int):
|
||||||
wallet = await get_watch_wallet(wallet_id)
|
wallet = await get_watch_wallet(wallet_id)
|
||||||
key = wallet[2]
|
key = wallet.masterpub
|
||||||
desc, network = parse_key(key)
|
desc, network = parse_key(key)
|
||||||
return desc.derive(num).address(network=network)
|
return desc.derive(num).address(network=network)
|
||||||
|
|
||||||
|
|
||||||
async def get_fresh_address(wallet_id: str) -> Optional[Addresses]:
|
async def get_fresh_address(wallet_id: str) -> Optional[Addresses]:
|
||||||
wallet = await get_watch_wallet(wallet_id)
|
wallet = await get_watch_wallet(wallet_id)
|
||||||
|
|
||||||
if not wallet:
|
if not wallet:
|
||||||
return None
|
return None
|
||||||
|
|
||||||
address = await get_derive_address(wallet_id, wallet[4] + 1)
|
address = await get_derive_address(wallet_id, wallet.address_no + 1)
|
||||||
|
|
||||||
await update_watch_wallet(wallet_id=wallet_id, address_no=wallet[4] + 1)
|
await update_watch_wallet(wallet_id=wallet_id, address_no=wallet.address_no + 1)
|
||||||
masterpub_id = urlsafe_short_hash()
|
masterpub_id = urlsafe_short_hash()
|
||||||
await db.execute(
|
await db.execute(
|
||||||
"""
|
"""
|
||||||
|
@ -181,7 +183,7 @@ async def get_addresses(wallet_id: str) -> List[Addresses]:
|
||||||
async def create_mempool(user: str) -> Optional[Mempool]:
|
async def create_mempool(user: str) -> Optional[Mempool]:
|
||||||
await db.execute(
|
await db.execute(
|
||||||
"""
|
"""
|
||||||
INSERT INTO watchonly.mempool ("user",endpoint)
|
INSERT INTO watchonly.mempool ("user",endpoint)
|
||||||
VALUES (?, ?)
|
VALUES (?, ?)
|
||||||
""",
|
""",
|
||||||
(user, "https://mempool.space"),
|
(user, "https://mempool.space"),
|
||||||
|
|
|
@ -1,8 +1,12 @@
|
||||||
from sqlite3 import Row
|
from sqlite3 import Row
|
||||||
from typing import NamedTuple
|
from fastapi.param_functions import Query
|
||||||
|
from pydantic import BaseModel
|
||||||
|
|
||||||
|
class CreateWallet(BaseModel):
|
||||||
|
masterpub: str = Query("")
|
||||||
|
title: str = Query("")
|
||||||
|
|
||||||
class Wallets(NamedTuple):
|
class Wallets(BaseModel):
|
||||||
id: str
|
id: str
|
||||||
user: str
|
user: str
|
||||||
masterpub: str
|
masterpub: str
|
||||||
|
@ -15,7 +19,7 @@ class Wallets(NamedTuple):
|
||||||
return cls(**dict(row))
|
return cls(**dict(row))
|
||||||
|
|
||||||
|
|
||||||
class Mempool(NamedTuple):
|
class Mempool(BaseModel):
|
||||||
user: str
|
user: str
|
||||||
endpoint: str
|
endpoint: str
|
||||||
|
|
||||||
|
@ -24,7 +28,7 @@ class Mempool(NamedTuple):
|
||||||
return cls(**dict(row))
|
return cls(**dict(row))
|
||||||
|
|
||||||
|
|
||||||
class Addresses(NamedTuple):
|
class Addresses(BaseModel):
|
||||||
id: str
|
id: str
|
||||||
address: str
|
address: str
|
||||||
wallet: str
|
wallet: str
|
||||||
|
|
|
@ -38,7 +38,7 @@
|
||||||
<h5 class="text-caption q-mt-sm q-mb-none">Curl example</h5>
|
<h5 class="text-caption q-mt-sm q-mb-none">Curl example</h5>
|
||||||
<code
|
<code
|
||||||
>curl -X GET {{ request.url_root }}api/v1/wallet -H "X-Api-Key: {{
|
>curl -X GET {{ request.url_root }}api/v1/wallet -H "X-Api-Key: {{
|
||||||
g.user.wallets[0].inkey }}"
|
user.wallets[0].inkey }}"
|
||||||
</code>
|
</code>
|
||||||
</q-card-section>
|
</q-card-section>
|
||||||
</q-card>
|
</q-card>
|
||||||
|
@ -67,7 +67,7 @@
|
||||||
<h5 class="text-caption q-mt-sm q-mb-none">Curl example</h5>
|
<h5 class="text-caption q-mt-sm q-mb-none">Curl example</h5>
|
||||||
<code
|
<code
|
||||||
>curl -X GET {{ request.url_root }}api/v1/wallet/<wallet_id>
|
>curl -X GET {{ request.url_root }}api/v1/wallet/<wallet_id>
|
||||||
-H "X-Api-Key: {{ g.user.wallets[0].inkey }}"
|
-H "X-Api-Key: {{ user.wallets[0].inkey }}"
|
||||||
</code>
|
</code>
|
||||||
</q-card-section>
|
</q-card-section>
|
||||||
</q-card>
|
</q-card>
|
||||||
|
@ -91,7 +91,7 @@
|
||||||
<code
|
<code
|
||||||
>curl -X POST {{ request.url_root }}api/v1/wallet -d '{"title":
|
>curl -X POST {{ request.url_root }}api/v1/wallet -d '{"title":
|
||||||
<string>, "masterpub": <string>}' -H "Content-type:
|
<string>, "masterpub": <string>}' -H "Content-type:
|
||||||
application/json" -H "X-Api-Key: {{ g.user.wallets[0].adminkey }}"
|
application/json" -H "X-Api-Key: {{ user.wallets[0].adminkey }}"
|
||||||
</code>
|
</code>
|
||||||
</q-card-section>
|
</q-card-section>
|
||||||
</q-card>
|
</q-card>
|
||||||
|
@ -117,7 +117,7 @@
|
||||||
<code
|
<code
|
||||||
>curl -X DELETE {{ request.url_root
|
>curl -X DELETE {{ request.url_root
|
||||||
}}api/v1/wallet/<wallet_id> -H "X-Api-Key: {{
|
}}api/v1/wallet/<wallet_id> -H "X-Api-Key: {{
|
||||||
g.user.wallets[0].adminkey }}"
|
user.wallets[0].adminkey }}"
|
||||||
</code>
|
</code>
|
||||||
</q-card-section>
|
</q-card-section>
|
||||||
</q-card>
|
</q-card>
|
||||||
|
@ -143,7 +143,7 @@
|
||||||
<code
|
<code
|
||||||
>curl -X GET {{ request.url_root
|
>curl -X GET {{ request.url_root
|
||||||
}}api/v1/addresses/<wallet_id> -H "X-Api-Key: {{
|
}}api/v1/addresses/<wallet_id> -H "X-Api-Key: {{
|
||||||
g.user.wallets[0].inkey }}"
|
user.wallets[0].inkey }}"
|
||||||
</code>
|
</code>
|
||||||
</q-card-section>
|
</q-card-section>
|
||||||
</q-card>
|
</q-card>
|
||||||
|
@ -174,7 +174,7 @@
|
||||||
<h5 class="text-caption q-mt-sm q-mb-none">Curl example</h5>
|
<h5 class="text-caption q-mt-sm q-mb-none">Curl example</h5>
|
||||||
<code
|
<code
|
||||||
>curl -X GET {{ request.url_root }}api/v1/address/<wallet_id>
|
>curl -X GET {{ request.url_root }}api/v1/address/<wallet_id>
|
||||||
-H "X-Api-Key: {{ g.user.wallets[0].inkey }}"
|
-H "X-Api-Key: {{ user.wallets[0].inkey }}"
|
||||||
</code>
|
</code>
|
||||||
</q-card-section>
|
</q-card-section>
|
||||||
</q-card>
|
</q-card>
|
||||||
|
@ -203,7 +203,7 @@
|
||||||
<h5 class="text-caption q-mt-sm q-mb-none">Curl example</h5>
|
<h5 class="text-caption q-mt-sm q-mb-none">Curl example</h5>
|
||||||
<code
|
<code
|
||||||
>curl -X GET {{ request.url_root }}api/v1/mempool -H "X-Api-Key: {{
|
>curl -X GET {{ request.url_root }}api/v1/mempool -H "X-Api-Key: {{
|
||||||
g.user.wallets[0].adminkey }}"
|
user.wallets[0].adminkey }}"
|
||||||
</code>
|
</code>
|
||||||
</q-card-section>
|
</q-card-section>
|
||||||
</q-card>
|
</q-card>
|
||||||
|
@ -235,7 +235,7 @@
|
||||||
<code
|
<code
|
||||||
>curl -X PUT {{ request.url_root }}api/v1/mempool -d '{"endpoint":
|
>curl -X PUT {{ request.url_root }}api/v1/mempool -d '{"endpoint":
|
||||||
<string>}' -H "Content-type: application/json" -H "X-Api-Key:
|
<string>}' -H "Content-type: application/json" -H "X-Api-Key:
|
||||||
{{ g.user.wallets[0].adminkey }}"
|
{{ user.wallets[0].adminkey }}"
|
||||||
</code>
|
</code>
|
||||||
</q-card-section>
|
</q-card-section>
|
||||||
</q-card>
|
</q-card>
|
||||||
|
|
|
@ -287,7 +287,6 @@
|
||||||
{% endraw %}
|
{% endraw %}
|
||||||
</div>
|
</div>
|
||||||
{% endblock %} {% block scripts %} {{ window_vars(user) }}
|
{% endblock %} {% block scripts %} {{ window_vars(user) }}
|
||||||
<script src="{{ url_for('static', filename='vendor/vue-qrcode@1.0.2/vue-qrcode.min.js') }}"></script>
|
|
||||||
<script type="text/javascript" src="https://mempool.space/mempool.js"></script>
|
<script type="text/javascript" src="https://mempool.space/mempool.js"></script>
|
||||||
<style></style>
|
<style></style>
|
||||||
<script>
|
<script>
|
||||||
|
|
|
@ -1,22 +1,32 @@
|
||||||
from quart import g, abort, render_template
|
|
||||||
from http import HTTPStatus
|
from http import HTTPStatus
|
||||||
|
from starlette.exceptions import HTTPException
|
||||||
|
from starlette.responses import HTMLResponse
|
||||||
|
from starlette.requests import Request
|
||||||
|
from fastapi.params import Depends
|
||||||
|
|
||||||
from lnbits.decorators import check_user_exists, validate_uuids
|
from lnbits.core.models import User
|
||||||
|
from lnbits.decorators import check_user_exists
|
||||||
|
|
||||||
from . import watchonly_ext
|
from . import watchonly_ext, watchonly_renderer
|
||||||
|
# from .crud import get_payment
|
||||||
|
|
||||||
|
from fastapi.templating import Jinja2Templates
|
||||||
|
|
||||||
|
templates = Jinja2Templates(directory="templates")
|
||||||
|
|
||||||
|
|
||||||
@watchonly_ext.route("/")
|
@watchonly_ext.get("/", response_class=HTMLResponse)
|
||||||
@validate_uuids(["usr"], required=True)
|
async def index(request: Request, user: User = Depends(check_user_exists)):
|
||||||
@check_user_exists()
|
return watchonly_renderer().TemplateResponse("watchonly/index.html", {"request": request,"user": user.dict()})
|
||||||
async def index():
|
|
||||||
return await render_template("watchonly/index.html", user=g.user)
|
|
||||||
|
|
||||||
|
|
||||||
@watchonly_ext.route("/<charge_id>")
|
# @watchonly_ext.get("/{charge_id}", response_class=HTMLResponse)
|
||||||
async def display(charge_id):
|
# async def display(request: Request, charge_id):
|
||||||
link = get_payment(charge_id) or abort(
|
# link = get_payment(charge_id)
|
||||||
HTTPStatus.NOT_FOUND, "Charge link does not exist."
|
# if not link:
|
||||||
)
|
# raise HTTPException(
|
||||||
|
# status_code=HTTPStatus.NOT_FOUND,
|
||||||
return await render_template("watchonly/display.html", link=link)
|
# detail="Charge link does not exist."
|
||||||
|
# )
|
||||||
|
#
|
||||||
|
# return watchonly_renderer().TemplateResponse("watchonly/display.html", {"request": request,"link": link.dict()})
|
||||||
|
|
|
@ -1,11 +1,15 @@
|
||||||
import hashlib
|
import hashlib
|
||||||
from quart import g, jsonify, url_for, request
|
|
||||||
from http import HTTPStatus
|
from http import HTTPStatus
|
||||||
import httpx
|
import httpx
|
||||||
import json
|
import json
|
||||||
|
|
||||||
from lnbits.core.crud import get_user
|
from lnbits.core.crud import get_user
|
||||||
from lnbits.decorators import api_check_wallet_key, api_validate_post_request
|
from lnbits.decorators import WalletTypeInfo, get_key_type
|
||||||
|
|
||||||
|
from fastapi import Query
|
||||||
|
from fastapi.params import Depends
|
||||||
|
from starlette.exceptions import HTTPException
|
||||||
|
from .models import CreateWallet
|
||||||
|
|
||||||
from lnbits.extensions.watchonly import watchonly_ext
|
from lnbits.extensions.watchonly import watchonly_ext
|
||||||
from .crud import (
|
from .crud import (
|
||||||
|
@ -24,86 +28,83 @@ from .crud import (
|
||||||
###################WALLETS#############################
|
###################WALLETS#############################
|
||||||
|
|
||||||
|
|
||||||
@watchonly_ext.route("/api/v1/wallet", methods=["GET"])
|
@watchonly_ext.get("/api/v1/wallet")
|
||||||
@api_check_wallet_key("invoice")
|
async def api_wallets_retrieve(wallet: WalletTypeInfo = Depends(get_key_type)):
|
||||||
async def api_wallets_retrieve():
|
|
||||||
|
|
||||||
try:
|
try:
|
||||||
return (
|
return [wallet.dict() for wallet in await get_watch_wallets(wallet.wallet.user)]
|
||||||
jsonify(
|
|
||||||
[wallet._asdict() for wallet in await get_watch_wallets(g.wallet.user)]
|
|
||||||
),
|
|
||||||
HTTPStatus.OK,
|
|
||||||
)
|
|
||||||
except:
|
except:
|
||||||
return ""
|
return ""
|
||||||
|
|
||||||
|
|
||||||
@watchonly_ext.route("/api/v1/wallet/<wallet_id>", methods=["GET"])
|
@watchonly_ext.get("/api/v1/wallet/{wallet_id}")
|
||||||
@api_check_wallet_key("invoice")
|
async def api_wallet_retrieve(wallet_id, wallet: WalletTypeInfo = Depends(get_key_type)):
|
||||||
async def api_wallet_retrieve(wallet_id):
|
w_wallet = await get_watch_wallet(wallet_id)
|
||||||
wallet = await get_watch_wallet(wallet_id)
|
|
||||||
|
|
||||||
if not wallet:
|
if not w_wallet:
|
||||||
return jsonify({"message": "wallet does not exist"}), HTTPStatus.NOT_FOUND
|
raise HTTPException(
|
||||||
|
status_code=HTTPStatus.NOT_FOUND,
|
||||||
|
detail="Wallet does not exist."
|
||||||
|
)
|
||||||
|
|
||||||
return jsonify(wallet._asdict()), HTTPStatus.OK
|
return w_wallet.dict()
|
||||||
|
|
||||||
|
|
||||||
@watchonly_ext.route("/api/v1/wallet", methods=["POST"])
|
@watchonly_ext.post("/api/v1/wallet")
|
||||||
@api_check_wallet_key("admin")
|
async def api_wallet_create_or_update(data: CreateWallet, wallet_id=None, w: WalletTypeInfo = Depends(get_key_type)):
|
||||||
@api_validate_post_request(
|
|
||||||
schema={
|
|
||||||
"masterpub": {"type": "string", "empty": False, "required": True},
|
|
||||||
"title": {"type": "string", "empty": False, "required": True},
|
|
||||||
}
|
|
||||||
)
|
|
||||||
async def api_wallet_create_or_update(wallet_id=None):
|
|
||||||
try:
|
try:
|
||||||
wallet = await create_watch_wallet(
|
wallet = await create_watch_wallet(
|
||||||
user=g.wallet.user, masterpub=g.data["masterpub"], title=g.data["title"]
|
user=w.wallet.user, masterpub=data.masterpub, title=data.title
|
||||||
)
|
)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
return jsonify({"message": str(e)}), HTTPStatus.BAD_REQUEST
|
raise HTTPException(
|
||||||
mempool = await get_mempool(g.wallet.user)
|
status_code=HTTPStatus.BAD_REQUEST,
|
||||||
|
detail=str(e)
|
||||||
|
)
|
||||||
|
|
||||||
|
mempool = await get_mempool(w.wallet.user)
|
||||||
if not mempool:
|
if not mempool:
|
||||||
create_mempool(user=g.wallet.user)
|
create_mempool(user=w.wallet.user)
|
||||||
return jsonify(wallet._asdict()), HTTPStatus.CREATED
|
return wallet.dict()
|
||||||
|
|
||||||
|
|
||||||
@watchonly_ext.route("/api/v1/wallet/<wallet_id>", methods=["DELETE"])
|
@watchonly_ext.delete("/api/v1/wallet/{wallet_id}")
|
||||||
@api_check_wallet_key("admin")
|
async def api_wallet_delete(wallet_id, w: WalletTypeInfo = Depends(get_key_type)):
|
||||||
async def api_wallet_delete(wallet_id):
|
|
||||||
wallet = await get_watch_wallet(wallet_id)
|
wallet = await get_watch_wallet(wallet_id)
|
||||||
|
|
||||||
if not wallet:
|
if not wallet:
|
||||||
return jsonify({"message": "Wallet link does not exist."}), HTTPStatus.NOT_FOUND
|
raise HTTPException(
|
||||||
|
status_code=HTTPStatus.NOT_FOUND,
|
||||||
|
detail="Wallet does not exist."
|
||||||
|
)
|
||||||
|
|
||||||
await delete_watch_wallet(wallet_id)
|
await delete_watch_wallet(wallet_id)
|
||||||
|
|
||||||
return jsonify({"deleted": "true"}), HTTPStatus.NO_CONTENT
|
raise HTTPException(status_code=HTTPStatus.NO_CONTENT)
|
||||||
|
|
||||||
|
|
||||||
#############################ADDRESSES##########################
|
#############################ADDRESSES##########################
|
||||||
|
|
||||||
|
|
||||||
@watchonly_ext.route("/api/v1/address/<wallet_id>", methods=["GET"])
|
@watchonly_ext.get("/api/v1/address/{wallet_id}")
|
||||||
@api_check_wallet_key("invoice")
|
async def api_fresh_address(wallet_id, w: WalletTypeInfo = Depends(get_key_type)):
|
||||||
async def api_fresh_address(wallet_id):
|
|
||||||
await get_fresh_address(wallet_id)
|
await get_fresh_address(wallet_id)
|
||||||
|
|
||||||
addresses = await get_addresses(wallet_id)
|
addresses = await get_addresses(wallet_id)
|
||||||
|
|
||||||
return jsonify([address._asdict() for address in addresses]), HTTPStatus.OK
|
return [address.dict() for address in addresses]
|
||||||
|
|
||||||
|
|
||||||
@watchonly_ext.route("/api/v1/addresses/<wallet_id>", methods=["GET"])
|
@watchonly_ext.get("/api/v1/addresses/{wallet_id}")
|
||||||
@api_check_wallet_key("invoice")
|
|
||||||
async def api_get_addresses(wallet_id):
|
async def api_get_addresses(wallet_id, w: WalletTypeInfo = Depends(get_key_type)):
|
||||||
wallet = await get_watch_wallet(wallet_id)
|
wallet = await get_watch_wallet(wallet_id)
|
||||||
|
|
||||||
if not wallet:
|
if not wallet:
|
||||||
return jsonify({"message": "wallet does not exist"}), HTTPStatus.NOT_FOUND
|
raise HTTPException(
|
||||||
|
status_code=HTTPStatus.NOT_FOUND,
|
||||||
|
detail="Wallet does not exist."
|
||||||
|
)
|
||||||
|
|
||||||
addresses = await get_addresses(wallet_id)
|
addresses = await get_addresses(wallet_id)
|
||||||
|
|
||||||
|
@ -111,28 +112,21 @@ async def api_get_addresses(wallet_id):
|
||||||
await get_fresh_address(wallet_id)
|
await get_fresh_address(wallet_id)
|
||||||
addresses = await get_addresses(wallet_id)
|
addresses = await get_addresses(wallet_id)
|
||||||
|
|
||||||
return jsonify([address._asdict() for address in addresses]), HTTPStatus.OK
|
return [address.dict() for address in addresses]
|
||||||
|
|
||||||
|
|
||||||
#############################MEMPOOL##########################
|
#############################MEMPOOL##########################
|
||||||
|
|
||||||
|
|
||||||
@watchonly_ext.route("/api/v1/mempool", methods=["PUT"])
|
@watchonly_ext.put("/api/v1/mempool")
|
||||||
@api_check_wallet_key("admin")
|
async def api_update_mempool(endpoint: str = Query(...), w: WalletTypeInfo = Depends(get_key_type)):
|
||||||
@api_validate_post_request(
|
mempool = await update_mempool(endpoint, user=w.wallet.user)
|
||||||
schema={
|
return mempool.dict()
|
||||||
"endpoint": {"type": "string", "empty": False, "required": True},
|
|
||||||
}
|
|
||||||
)
|
|
||||||
async def api_update_mempool():
|
|
||||||
mempool = await update_mempool(user=g.wallet.user, **g.data)
|
|
||||||
return jsonify(mempool._asdict()), HTTPStatus.OK
|
|
||||||
|
|
||||||
|
|
||||||
@watchonly_ext.route("/api/v1/mempool", methods=["GET"])
|
@watchonly_ext.get("/api/v1/mempool")
|
||||||
@api_check_wallet_key("admin")
|
async def api_get_mempool(w: WalletTypeInfo = Depends(get_key_type)):
|
||||||
async def api_get_mempool():
|
mempool = await get_mempool(w.wallet.user)
|
||||||
mempool = await get_mempool(g.wallet.user)
|
|
||||||
if not mempool:
|
if not mempool:
|
||||||
mempool = await create_mempool(user=g.wallet.user)
|
mempool = await create_mempool(user=w.wallet.user)
|
||||||
return jsonify(mempool._asdict()), HTTPStatus.OK
|
return mempool.dict()
|
||||||
|
|
Loading…
Reference in New Issue
Block a user