remove plugin payserver (moved to spesmilo/electrum-payserver)
What changed, and why it matters
This commit simply deletes the 'payserver' plugin from the Electrum repository and moves it to a separate project. There is no security fix or vulnerability patch here—just code relocation.
No security action needed. If tracking the payserver plugin, monitor the new repository spesmilo/electrum-payserver for future changes.
Security signals we found
No strong security signals were identified.
Evidence from the diff
The commit removes the entire electrum/plugins/payserver directory (Python plugin files, manifest, and a git submodule for static web assets) and updates .gitmodules accordingly. The commit message states the plugin was moved to spesmilo/electrum-payserver. No code changes, bug fixes, or hardening are present in the diff.
Changed components
electrum/plugins/payserverInspect captured patch +0 / −233
diff --git a/.gitmodules b/.gitmodules
index 5d001cd..7bc6f66 100644
--- a/.gitmodules
+++ b/.gitmodules
@@ -1,9 +1,6 @@
[submodule "electrum/locale"]
path = electrum/locale
url = https://github.com/spesmilo/electrum-locale
-[submodule "electrum/www"]
- path = electrum/plugins/payserver/www
- url = https://github.com/spesmilo/electrum-http.git
[submodule "electrum/plugins/keepkey/keepkeylib"]
path = electrum/plugins/keepkey/keepkeylib
url = https://github.com/spesmilo/electrum-keepkeylib.git
diff --git a/electrum/plugins/payserver/__init__.py b/electrum/plugins/payserver/__init__.py
deleted file mode 100644
index 019f4e5..0000000
--- a/electrum/plugins/payserver/__init__.py
+++ /dev/null
@@ -1,5 +0,0 @@
-from electrum.simple_config import ConfigVar, SimpleConfig
-
-SimpleConfig.PAYSERVER_PORT = ConfigVar('plugins.payserver.port', default=8080, type_=int, plugin=__name__)
-SimpleConfig.PAYSERVER_ROOT = ConfigVar('plugins.payserver.root', default='/r', type_=str, plugin=__name__)
-SimpleConfig.PAYSERVER_ALLOW_CREATE_INVOICE = ConfigVar('plugins.payserver.allow_create_invoice', default=False, type_=bool, plugin=__name__)
diff --git a/electrum/plugins/payserver/cmdline.py b/electrum/plugins/payserver/cmdline.py
deleted file mode 100644
index c2d8945..0000000
--- a/electrum/plugins/payserver/cmdline.py
+++ /dev/null
@@ -1,31 +0,0 @@
-#!/usr/bin/env python
-#
-# Electrum - Lightweight Bitcoin Client
-# Copyright (C) 2022 The Electrum Developers
-#
-# Permission is hereby granted, free of charge, to any person
-# obtaining a copy of this software and associated documentation files
-# (the "Software"), to deal in the Software without restriction,
-# including without limitation the rights to use, copy, modify, merge,
-# publish, distribute, sublicense, and/or sell copies of the Software,
-# and to permit persons to whom the Software is furnished to do so,
-# subject to the following conditions:
-#
-# The above copyright notice and this permission notice shall be
-# included in all copies or substantial portions of the Software.
-#
-# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
-# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
-# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
-# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
-# BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
-# ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
-# CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
-# SOFTWARE.
-
-
-from .payserver import PayServerPlugin
-
-class Plugin(PayServerPlugin):
- pass
-
diff --git a/electrum/plugins/payserver/manifest.json b/electrum/plugins/payserver/manifest.json
deleted file mode 100644
index fd47bb9..0000000
--- a/electrum/plugins/payserver/manifest.json
+++ /dev/null
@@ -1,6 +0,0 @@
-{
- "name": "payserver",
- "fullname": "PayServer",
- "description": "run a HTTP server for receiving payments",
- "available_for": ["cmdline"]
-}
diff --git a/electrum/plugins/payserver/payserver.py b/electrum/plugins/payserver/payserver.py
deleted file mode 100644
index 00b1f67..0000000
--- a/electrum/plugins/payserver/payserver.py
+++ /dev/null
@@ -1,187 +0,0 @@
-#!/usr/bin/env python
-#
-# Electrum - Lightweight Bitcoin Client
-# Copyright (C) 2022 The Electrum Developers
-#
-# Permission is hereby granted, free of charge, to any person
-# obtaining a copy of this software and associated documentation files
-# (the "Software"), to deal in the Software without restriction,
-# including without limitation the rights to use, copy, modify, merge,
-# publish, distribute, sublicense, and/or sell copies of the Software,
-# and to permit persons to whom the Software is furnished to do so,
-# subject to the following conditions:
-#
-# The above copyright notice and this permission notice shall be
-# included in all copies or substantial portions of the Software.
-#
-# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
-# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
-# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
-# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
-# BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
-# ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
-# CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
-# SOFTWARE.
-
-import os
-import asyncio
-from collections import defaultdict
-from typing import TYPE_CHECKING, Optional
-
-from aiohttp import web
-
-from electrum import util
-from electrum.util import log_exceptions, ignore_exceptions
-from electrum.plugin import BasePlugin, hook
-from electrum.logging import Logger
-from electrum.util import EventListener, event_listener
-from electrum.invoices import PR_PAID, PR_EXPIRED
-
-if TYPE_CHECKING:
- from electrum.simple_config import SimpleConfig
- from electrum.daemon import Daemon
- from electrum.wallet import Abstract_Wallet
-
-
-class PayServerPlugin(BasePlugin):
-
- def __init__(self, parent, config: 'SimpleConfig', name):
- BasePlugin.__init__(self, parent, config, name)
- self.config = config
- self.server = None
-
- def view_url(self, key) -> Optional[str]:
- if not self.server:
- return None
- return self.server.base_url + self.server.root + '/pay?id=' + key
-
- @hook
- def daemon_wallet_loaded(self, daemon: 'Daemon', wallet: 'Abstract_Wallet'):
- # we use the first wallet loaded
- if self.server is not None:
- return
- if self.config.NETWORK_OFFLINE:
- return
- self.server = PayServer(self.config, wallet)
- asyncio.run_coroutine_threadsafe(daemon.taskgroup.spawn(self.server.run()), daemon.asyncio_loop)
-
- @hook
- def wallet_export_request(self, d, key):
- if view_url := self.view_url(key):
- d['view_url'] = view_url
-
-class PayServer(Logger, EventListener):
-
- WWW_DIR = os.path.join(os.path.dirname(__file__), 'www')
-
- def __init__(self, config: 'SimpleConfig', wallet: 'Abstract_Wallet'):
- Logger.__init__(self)
- assert self.has_www_dir(), self.WWW_DIR
- self.config = config
- self.wallet = wallet
- self.port = self.config.PAYSERVER_PORT
- self.pending = defaultdict(asyncio.Event)
- self.register_callbacks()
-
- @classmethod
- def has_www_dir(cls) -> bool:
- index_html = os.path.join(cls.WWW_DIR, "index.html")
- return os.path.exists(index_html)
-
- @property
- def base_url(self):
- return 'http://localhost:%d'%self.port
-
- @property
- def root(self):
- return self.config.PAYSERVER_ROOT
-
- @event_listener
- async def on_event_request_status(self, wallet, key, status):
- if status == PR_PAID:
- self.pending[key].set()
-
- @ignore_exceptions
- @log_exceptions
- async def run(self):
- app = web.Application()
- app.add_routes([web.get('/api/get_invoice', self.get_request)])
- app.add_routes([web.get('/api/get_status', self.get_status)])
- app.add_routes([web.get('/bip70/{key}.bip70', self.get_bip70_request)])
- # 'follow_symlinks=True' allows symlinks to traverse out the parent directory.
- # This was requested by distro packagers for vendored libs, and we restrict it to only those
- # to minimise attack surface. note: "add_routes" call order matters (inner path goes first)
- app.add_routes([web.static(f"{self.root}/vendor", os.path.join(self.WWW_DIR, 'vendor'), follow_symlinks=True)])
- app.add_routes([web.static(self.root, self.WWW_DIR)])
- if self.config.PAYSERVER_ALLOW_CREATE_INVOICE:
- app.add_routes([web.post('/api/create_invoice', self.create_request)])
- runner = web.AppRunner(app)
- await runner.setup()
- site = web.TCPSite(runner, host='localhost', port=self.port)
- await site.start()
- self.logger.info(f"running and listening on port {self.port}")
-
- async def create_request(self, request):
- params = await request.post()
- wallet = self.wallet
- if 'amount_sat' not in params or not params['amount_sat'].isdigit():
- raise web.HTTPBadRequest(reason='No amount provided')
- if 'onchain' in params:
- address = wallet.get_unused_address()
- if not address:
- raise web.HTTPBadRequest(reason='wallet does not have any unused address')
- else:
- if not wallet.has_lightning():
- raise web.HTTPBadRequest(reason='wallet does not support lightning')
- address = None
- amount = int(params['amount_sat'])
- message = params['message'] or "donation"
- key = wallet.create_request(
- amount_sat=amount,
- message=message,
- exp_delay=3600,
- address=address)
- raise web.HTTPFound(self.root + '/pay?id=' + key)
-
- async def get_request(self, r):
- key = r.query_string
- request = self.wallet.get_formatted_request(key)
- return web.json_response(request)
-
- async def get_bip70_request(self, r):
- from electrum.paymentrequest import make_request
- key = r.match_info['key']
- request = self.wallet.get_request(key)
- if not request:
- return web.HTTPNotFound()
- pr = make_request(self.config, request)
- return web.Response(body=pr.SerializeToString(), content_type='application/bitcoin-paymentrequest')
-
- async def get_status(self, request):
- ws = web.WebSocketResponse()
- await ws.prepare(request)
- key = request.query_string
- info = self.wallet.get_formatted_request(key)
- if not info:
- await ws.send_str('unknown invoice')
- await ws.close()
- return ws
- if info.get('status') == PR_PAID:
- await ws.send_str(f'paid')
- await ws.close()
- return ws
- if info.get('status') == PR_EXPIRED:
- await ws.send_str(f'expired')
- await ws.close()
- return ws
- while True:
- try:
- await util.wait_for2(self.pending[key].wait(), 1)
- break
- except asyncio.TimeoutError:
- # send data on the websocket, to keep it alive
- await ws.send_str('waiting')
- await ws.send_str('paid')
- await ws.close()
- return ws
-
Why this scored 15/100
Community notes
Notes can correct, qualify, or add evidence to the AI analysis. Every note shown here has been validated by a human moderator.
The AI analysis stands alone for now. Submit a note if you can add evidence or important context.