feat: phoenixd wallet integration (#2362)

* phoenixd integration
---------

Co-authored-by: Vlad Stan <stan.v.vlad@gmail.com>
This commit is contained in:
Bitkarrot 2024-04-26 01:18:38 -07:00 committed by GitHub
parent 4b4bed59cd
commit c04c13b2f8
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
11 changed files with 662 additions and 9 deletions

View File

@ -28,7 +28,7 @@ PORT=5000
######################################
# which fundingsources are allowed in the admin ui
LNBITS_ALLOWED_FUNDING_SOURCES="VoidWallet, FakeWallet, CoreLightningWallet, CoreLightningRestWallet, LndRestWallet, EclairWallet, LndWallet, LnTipsWallet, LNPayWallet, LNbitsWallet, AlbyWallet, ZBDWallet, OpenNodeWallet"
LNBITS_ALLOWED_FUNDING_SOURCES="VoidWallet, FakeWallet, CoreLightningWallet, CoreLightningRestWallet, LndRestWallet, EclairWallet, LndWallet, LnTipsWallet, LNPayWallet, LNbitsWallet, AlbyWallet, ZBDWallet, PhoenixdWallet, OpenNodeWallet"
LNBITS_BACKEND_WALLET_CLASS=VoidWallet
# VoidWallet is just a fallback that works without any actual Lightning capabilities,
@ -91,6 +91,10 @@ ALBY_ACCESS_TOKEN=ALBY_ACCESS_TOKEN
ZBD_API_ENDPOINT=https://api.zebedee.io/v0/
ZBD_API_KEY=ZBD_ACCESS_TOKEN
# PhoenixdWallet
PHOENIXD_API_ENDPOINT=http://localhost:9740/
PHOENIXD_API_PASSWORD=PHOENIXD_KEY
# OpenNodeWallet
OPENNODE_API_ENDPOINT=https://api.opennode.com/
OPENNODE_KEY=OPENNODE_ADMIN_KEY

View File

@ -92,6 +92,15 @@ For the invoice to work you must have a publicly accessible URL in your LNbits.
- `ZBD_API_ENDPOINT`: https://api.zebedee.io/v0/
- `ZBD_API_KEY`: ZBDApiKey
### Phoenixd
For the invoice to work you must have a publicly accessible URL in your LNbits. You can get a phoenixd API key from the install
~/.phoenix/phoenix.conf, also see the documentation for phoenixd.
- `LNBITS_BACKEND_WALLET_CLASS`: **PhoenixdWallet**
- `PHOENIXD_API_ENDPOINT`: http://localhost:9740/
- `PHOENIXD_API_PASSWORD`: PhoenixdApiPassword
### Cliche Wallet
- `CLICHE_ENDPOINT`: ws://127.0.0.1:12000

View File

@ -499,6 +499,21 @@
</a>
</div>
</div>
<div class="row">
<div class="col">
<a
href="https://phoenix.acinq.co/server"
target="_blank"
rel="noopener noreferrer"
>
<q-img
contain
:src="($q.dark.isActive) ? '{{ static_url_for('static', 'images/phoenixd.png') }}' : '{{ static_url_for('static', 'images/phoenixdl.png') }}'"
></q-img>
</a>
</div>
<div class="col q-pl-md"></div>
</div>
</div>
</div>
</div>

View File

@ -214,6 +214,11 @@ class ZBDFundingSource(LNbitsSettings):
zbd_api_key: Optional[str] = Field(default=None)
class PhoenixdFundingSource(LNbitsSettings):
phoenixd_api_endpoint: Optional[str] = Field(default="http://localhost:9740/")
phoenixd_api_password: Optional[str] = Field(default=None)
class AlbyFundingSource(LNbitsSettings):
alby_api_endpoint: Optional[str] = Field(default="https://api.getalby.com/")
alby_access_token: Optional[str] = Field(default=None)
@ -254,6 +259,7 @@ class FundingSourcesSettings(
LnPayFundingSource,
AlbyFundingSource,
ZBDFundingSource,
PhoenixdFundingSource,
OpenNodeFundingSource,
SparkFundingSource,
LnTipsFundingSource,
@ -410,6 +416,7 @@ class SuperUserSettings(LNbitsSettings):
"LNPayWallet",
"AlbyWallet",
"ZBDWallet",
"PhoenixdWallet",
"LNbitsWallet",
"OpenNodeWallet",
]

File diff suppressed because one or more lines are too long

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.5 KiB

View File

@ -115,6 +115,14 @@ Vue.component('lnbits-funding-sources', {
zbd_api_key: 'Key'
}
],
[
'PhoenixdWallet',
'Phoenixd',
{
phoenixd_api_endpoint: 'Endpoint',
phoenixd_api_password: 'Key'
}
],
[
'OpenNodeWallet',
'OpenNode',

View File

@ -23,6 +23,7 @@ from .lndrest import LndRestWallet
from .lnpay import LNPayWallet
from .lntips import LnTipsWallet
from .opennode import OpenNodeWallet
from .phoenixd import PhoenixdWallet
from .spark import SparkWallet
from .void import VoidWallet
from .zbd import ZBDWallet

230
lnbits/wallets/phoenixd.py Normal file
View File

@ -0,0 +1,230 @@
import asyncio
import base64
import json
import urllib.parse
from typing import AsyncGenerator, Dict, Optional
import httpx
from loguru import logger
from websockets.client import connect
from lnbits.settings import settings
from .base import (
InvoiceResponse,
PaymentPendingStatus,
PaymentResponse,
PaymentStatus,
PaymentSuccessStatus,
StatusResponse,
UnsupportedError,
Wallet,
)
class PhoenixdWallet(Wallet):
"""https://phoenix.acinq.co/server/api"""
def __init__(self):
if not settings.phoenixd_api_endpoint:
raise ValueError(
"cannot initialize PhoenixdWallet: missing phoenixd_api_endpoint"
)
if not settings.phoenixd_api_password:
raise ValueError(
"cannot initialize PhoenixdWallet: missing phoenixd_api_password"
)
self.endpoint = self.normalize_endpoint(settings.phoenixd_api_endpoint)
self.ws_url = f"ws://{urllib.parse.urlsplit(self.endpoint).netloc}/websocket"
password = settings.phoenixd_api_password
encoded_auth = base64.b64encode(f":{password}".encode())
auth = str(encoded_auth, "utf-8")
self.headers = {
"Authorization": f"Basic {auth}",
"User-Agent": settings.user_agent,
}
self.client = httpx.AsyncClient(base_url=self.endpoint, headers=self.headers)
async def cleanup(self):
try:
await self.client.aclose()
except RuntimeError as e:
logger.warning(f"Error closing wallet connection: {e}")
async def status(self) -> StatusResponse:
try:
r = await self.client.get("/getinfo", timeout=10)
r.raise_for_status()
data = r.json()
if len(data) == 0:
return StatusResponse("no data", 0)
if r.is_error or "channels" not in data:
error_message = data["message"] if "message" in data else r.text
return StatusResponse(f"Server error: '{error_message}'", 0)
if len(data["channels"]) == 0:
# todo: add custom unit-test for this
return StatusResponse(None, 0)
balance_msat = int(data["channels"][0]["balanceSat"]) * 1000
return StatusResponse(None, balance_msat)
except json.JSONDecodeError:
return StatusResponse("Server error: 'invalid json response'", 0)
except Exception as exc:
logger.warning(exc)
return StatusResponse(f"Unable to connect to {self.endpoint}.", 0)
async def create_invoice(
self,
amount: int,
memo: Optional[str] = None,
description_hash: Optional[bytes] = None,
unhashed_description: Optional[bytes] = None,
**kwargs,
) -> InvoiceResponse:
if description_hash or unhashed_description:
raise UnsupportedError("description_hash")
try:
msats_amount = amount
data: Dict = {
"amountSat": f"{msats_amount}",
"description": memo,
"externalId": "",
}
r = await self.client.post(
"/createinvoice",
data=data,
timeout=40,
)
r.raise_for_status()
data = r.json()
if r.is_error or "paymentHash" not in data:
error_message = data["message"]
return InvoiceResponse(
False, None, None, f"Server error: '{error_message}'"
)
checking_id = data["paymentHash"]
payment_request = data["serialized"]
return InvoiceResponse(True, checking_id, payment_request, None)
except json.JSONDecodeError:
return InvoiceResponse(
False, None, None, "Server error: 'invalid json response'"
)
except KeyError as exc:
logger.warning(exc)
return InvoiceResponse(
False, None, None, "Server error: 'missing required fields'"
)
except Exception as exc:
logger.warning(exc)
return InvoiceResponse(
False, None, None, f"Unable to connect to {self.endpoint}."
)
async def pay_invoice(self, bolt11: str, fee_limit_msat: int) -> PaymentResponse:
try:
r = await self.client.post(
"/payinvoice",
data={
"invoice": bolt11,
},
timeout=40,
)
r.raise_for_status()
data = r.json()
if "routingFeeSat" not in data and "reason" in data:
return PaymentResponse(False, None, None, None, data["reason"])
if r.is_error or "paymentHash" not in data:
error_message = data["message"] if "message" in data else r.text
return PaymentResponse(False, None, None, None, error_message)
checking_id = data["paymentHash"]
fee_msat = -int(data["routingFeeSat"])
preimage = data["paymentPreimage"]
return PaymentResponse(True, checking_id, fee_msat, preimage, None)
except json.JSONDecodeError:
return PaymentResponse(
False, None, None, None, "Server error: 'invalid json response'"
)
except KeyError:
return PaymentResponse(
False, None, None, None, "Server error: 'missing required fields'"
)
except Exception as exc:
logger.info(f"Failed to pay invoice {bolt11}")
logger.warning(exc)
return PaymentResponse(
False, None, None, None, f"Unable to connect to {self.endpoint}."
)
async def get_invoice_status(self, checking_id: str) -> PaymentStatus:
try:
r = await self.client.get(f"/payments/incoming/{checking_id}")
if r.is_error:
return PaymentPendingStatus()
data = r.json()
if data["isPaid"]:
fee_msat = data["fees"]
preimage = data["preimage"]
return PaymentSuccessStatus(fee_msat=fee_msat, preimage=preimage)
return PaymentPendingStatus()
except Exception as e:
logger.error(f"Error getting invoice status: {e}")
return PaymentPendingStatus()
async def get_payment_status(self, checking_id: str) -> PaymentStatus:
try:
r = await self.client.get(f"/payments/outgoing/{checking_id}")
if r.is_error:
return PaymentPendingStatus()
data = r.json()
if data["isPaid"]:
fee_msat = data["fees"]
preimage = data["preimage"]
return PaymentSuccessStatus(fee_msat=fee_msat, preimage=preimage)
return PaymentPendingStatus()
except Exception as e:
logger.error(f"Error getting invoice status: {e}")
return PaymentPendingStatus()
async def paid_invoices_stream(self) -> AsyncGenerator[str, None]:
while settings.lnbits_running:
try:
async with connect(
self.ws_url,
extra_headers=[("Authorization", self.headers["Authorization"])],
) as ws:
logger.info("connected to phoenixd invoices stream")
while settings.lnbits_running:
message = await ws.recv()
message_json = json.loads(message)
if message_json and message_json["type"] == "payment-received":
logger.info(
f'payment-received: {message_json["paymentHash"]}'
)
yield message_json["paymentHash"]
except Exception as exc:
logger.error(
f"lost connection to phoenixd invoices stream: '{exc}'"
"retrying in 5 seconds"
)
await asyncio.sleep(5)

View File

@ -40,6 +40,14 @@
"lnbits_admin_key": "f171ba022a764e679eef950b21fb1c04",
"user_agent": "LNbits/Tests"
}
},
"phoenixd": {
"wallet_class": "PhoenixdWallet",
"settings": {
"phoenixd_api_endpoint": "http://127.0.0.1:8555",
"phoenixd_api_password": "f171ba022a764e679eef950b21fb1c04f171ba022a764e679eef950b21fb1c04",
"user_agent": "LNbits/Tests"
}
}
},
"functions": {
@ -96,6 +104,16 @@
},
"method": "GET"
}
},
"phoenixd": {
"status_endpoint": {
"uri": "/getinfo",
"headers": {
"Authorization": "Basic OmYxNzFiYTAyMmE3NjRlNjc5ZWVmOTUwYjIxZmIxYzA0ZjE3MWJhMDIyYTc2NGU2NzllZWY5NTBiMjFmYjFjMDQ=",
"User-Agent": "LNbits/Tests"
},
"method": "GET"
}
}
},
"tests": [
@ -157,6 +175,20 @@
}
}
]
},
"phoenixd": {
"status_endpoint": [
{
"response_type": "json",
"response": {
"channels": [
{
"balanceSat": 55
}
]
}
}
]
}
}
},
@ -214,6 +246,14 @@
"response": "test-error"
}
]
},
"phoenixd": {
"status_endpoint": [
{
"response_type": "json",
"response": "test-error"
}
]
}
}
},
@ -264,6 +304,14 @@
"response": {}
}
]
},
"phoenixd": {
"status_endpoint": [
{
"response_type": "json",
"response": {}
}
]
}
}
},
@ -314,6 +362,14 @@
"response": "data-not-json"
}
]
},
"phoenixd": {
"status_endpoint": [
{
"response_type": "data",
"response": "data-not-json"
}
]
}
}
},
@ -379,6 +435,17 @@
}
}
]
},
"phoenixd": {
"status_endpoint": [
{
"response_type": "response",
"response": {
"response": "Not Found",
"status": 404
}
}
]
}
}
},
@ -445,6 +512,16 @@
},
"method": "POST"
}
},
"phoenixd": {
"create_invoice_endpoint": {
"uri": "/createinvoice",
"headers": {
"Authorization": "Basic OmYxNzFiYTAyMmE3NjRlNjc5ZWVmOTUwYjIxZmIxYzA0ZjE3MWJhMDIyYTc2NGU2NzllZWY5NTBiMjFmYjFjMDQ=",
"User-Agent": "LNbits/Tests"
},
"method": "POST"
}
}
},
"tests": [
@ -543,6 +620,23 @@
}
}
]
},
"phoenixd": {
"create_invoice_endpoint": [
{
"request_type": "json",
"request_body": {
"amountSat": "555000",
"description": "Test Invoice",
"externalId": ""
},
"response_type": "json",
"response": {
"paymentHash": "e35526a43d04e985594c0dfab848814f524b1c786598ec9a63beddb2d726ac96",
"serialized": "lnbc5550n1pnq9jg3sp52rvwstvjcypjsaenzdh0h30jazvzsf8aaye0julprtth9kysxtuspp5e5s3z7felv4t9zrcc6wpn7ehvjl5yzewanzl5crljdl3jgeffyhqdq2f38xy6t5wvxqzjccqpjrzjq0yzeq76ney45hmjlnlpvu0nakzy2g35hqh0dujq8ujdpr2e42pf2rrs6vqpgcsqqqqqqqqqqqqqqeqqyg9qxpqysgqwftcx89k5pp28435pgxfl2vx3ksemzxccppw2j9yjn0ngr6ed7wj8ztc0d5kmt2mvzdlcgrludhz7jncd5l5l9w820hc4clpwhtqj3gq62g66n"
}
}
]
}
}
},
@ -624,6 +718,22 @@
}
}
]
},
"phoenixd": {
"create_invoice_endpoint": [
{
"request_type": "json",
"request_body": {
"amountSat": "555000",
"description": "Test Invoice",
"externalId": ""
},
"response_type": "json",
"response": {
"message": "Test Error"
}
}
]
}
}
},
@ -718,6 +828,22 @@
}
}
]
},
"phoenixd": {
"create_invoice_endpoint": [
{
"request_type": "json",
"request_body": {
"amountSat": "555000",
"description": "Test Invoice",
"externalId": ""
},
"response_type": "json",
"response": {
"paymentHash": "e35526a43d04e985594c0dfab848814f524b1c786598ec9a63beddb2d726ac96"
}
}
]
}
}
},
@ -801,6 +927,20 @@
"response": "data-not-json"
}
]
},
"phoenixd": {
"create_invoice_endpoint": [
{
"request_type": "json",
"request_body": {
"amountSat": "555000",
"description": "Test Invoice",
"externalId": ""
},
"response_type": "data",
"response": "data-not-json"
}
]
}
}
},
@ -899,6 +1039,23 @@
}
}
]
},
"phoenixd": {
"create_invoice_endpoint": [
{
"request_type": "json",
"request_body": {
"amountSat": "555000",
"description": "Test Invoice",
"externalId": ""
},
"response_type": "response",
"response": {
"response": "Not Found",
"status": 404
}
}
]
}
}
},
@ -987,6 +1144,16 @@
},
"method": "GET"
}
},
"phoenixd": {
"pay_invoice_endpoint": {
"uri": "/payinvoice",
"headers": {
"Authorization": "Basic OmYxNzFiYTAyMmE3NjRlNjc5ZWVmOTUwYjIxZmIxYzA0ZjE3MWJhMDIyYTc2NGU2NzllZWY5NTBiMjFmYjFjMDQ=",
"User-Agent": "LNbits/Tests"
},
"method": "POST"
}
}
},
"tests": [
@ -1012,7 +1179,8 @@
"request_type": "data",
"request_body": {
"invoice": "lnbc210n1pjlgal5sp5xr3uwlfm7ltumdjyukhys0z2rw6grgm8me9k4w9vn05zt9svzzjspp5ud2jdfpaqn5c2k2vphatsjypfafyk8rcvkvwexnrhmwm94ex4jtqdqu24hxjapq23jhxapqf9h8vmmfvdjscqpjrzjqta942048v7qxh5x7pxwplhmtwfl0f25cq23jh87rhx7lgrwwvv86r90guqqnwgqqqqqqqqqqqqqqpsqyg9qxpqysgqylngsyg960lltngzy90e8n22v4j2hvjs4l4ttuy79qqefjv8q87q9ft7uhwdjakvnsgk44qyhalv6ust54x98whl3q635hkwgsyw8xgqjl7jwu",
"maxfee": 25000
"maxfeepercent": "119.04761905",
"exemptfee": 0
},
"response_type": "json",
"response": {
@ -1123,6 +1291,22 @@
}
}
]
},
"phoenixd": {
"pay_invoice_endpoint": [
{
"request_type": "json",
"request_body": {
"invoice": "lnbc5550n1pnq9jg3sp52rvwstvjcypjsaenzdh0h30jazvzsf8aaye0julprtth9kysxtuspp5e5s3z7felv4t9zrcc6wpn7ehvjl5yzewanzl5crljdl3jgeffyhqdq2f38xy6t5wvxqzjccqpjrzjq0yzeq76ney45hmjlnlpvu0nakzy2g35hqh0dujq8ujdpr2e42pf2rrs6vqpgcsqqqqqqqqqqqqqqeqqyg9qxpqysgqwftcx89k5pp28435pgxfl2vx3ksemzxccppw2j9yjn0ngr6ed7wj8ztc0d5kmt2mvzdlcgrludhz7jncd5l5l9w820hc4clpwhtqj3gq62g66n"
},
"response_type": "json",
"response": {
"paymentHash": "e35526a43d04e985594c0dfab848814f524b1c786598ec9a63beddb2d726ac96",
"routingFeeSat": -50,
"paymentPreimage": "0000000000000000000000000000000000000000000000000000000000000000"
}
}
]
}
}
},
@ -1266,7 +1450,8 @@
]
}
],
"lnbits": []
"lnbits": [],
"phoenixd": []
}
},
{
@ -1291,7 +1476,8 @@
"request_type": "data",
"request_body": {
"invoice": "lnbc210n1pjlgal5sp5xr3uwlfm7ltumdjyukhys0z2rw6grgm8me9k4w9vn05zt9svzzjspp5ud2jdfpaqn5c2k2vphatsjypfafyk8rcvkvwexnrhmwm94ex4jtqdqu24hxjapq23jhxapqf9h8vmmfvdjscqpjrzjqta942048v7qxh5x7pxwplhmtwfl0f25cq23jh87rhx7lgrwwvv86r90guqqnwgqqqqqqqqqqqqqqpsqyg9qxpqysgqylngsyg960lltngzy90e8n22v4j2hvjs4l4ttuy79qqefjv8q87q9ft7uhwdjakvnsgk44qyhalv6ust54x98whl3q635hkwgsyw8xgqjl7jwu",
"maxfee": 25000
"maxfeepercent": "119.04761905",
"exemptfee": 0
},
"response_type": "json",
"response": {
@ -1369,6 +1555,30 @@
}
}
]
},
"phoenixd": {
"pay_invoice_endpoint": [
{
"request_type": "json",
"request_body": {
"invoice": "lnbc5550n1pnq9jg3sp52rvwstvjcypjsaenzdh0h30jazvzsf8aaye0julprtth9kysxtuspp5e5s3z7felv4t9zrcc6wpn7ehvjl5yzewanzl5crljdl3jgeffyhqdq2f38xy6t5wvxqzjccqpjrzjq0yzeq76ney45hmjlnlpvu0nakzy2g35hqh0dujq8ujdpr2e42pf2rrs6vqpgcsqqqqqqqqqqqqqqeqqyg9qxpqysgqwftcx89k5pp28435pgxfl2vx3ksemzxccppw2j9yjn0ngr6ed7wj8ztc0d5kmt2mvzdlcgrludhz7jncd5l5l9w820hc4clpwhtqj3gq62g66n"
},
"response_type": "json",
"response": {
"message": "Test Error"
}
},
{
"request_type": "json",
"request_body": {
"invoice": "lnbc5550n1pnq9jg3sp52rvwstvjcypjsaenzdh0h30jazvzsf8aaye0julprtth9kysxtuspp5e5s3z7felv4t9zrcc6wpn7ehvjl5yzewanzl5crljdl3jgeffyhqdq2f38xy6t5wvxqzjccqpjrzjq0yzeq76ney45hmjlnlpvu0nakzy2g35hqh0dujq8ujdpr2e42pf2rrs6vqpgcsqqqqqqqqqqqqqqeqqyg9qxpqysgqwftcx89k5pp28435pgxfl2vx3ksemzxccppw2j9yjn0ngr6ed7wj8ztc0d5kmt2mvzdlcgrludhz7jncd5l5l9w820hc4clpwhtqj3gq62g66n"
},
"response_type": "json",
"response": {
"reason": "Test Error"
}
}
]
}
}
},
@ -1394,7 +1604,8 @@
"request_type": "data",
"request_body": {
"invoice": "lnbc210n1pjlgal5sp5xr3uwlfm7ltumdjyukhys0z2rw6grgm8me9k4w9vn05zt9svzzjspp5ud2jdfpaqn5c2k2vphatsjypfafyk8rcvkvwexnrhmwm94ex4jtqdqu24hxjapq23jhxapqf9h8vmmfvdjscqpjrzjqta942048v7qxh5x7pxwplhmtwfl0f25cq23jh87rhx7lgrwwvv86r90guqqnwgqqqqqqqqqqqqqqpsqyg9qxpqysgqylngsyg960lltngzy90e8n22v4j2hvjs4l4ttuy79qqefjv8q87q9ft7uhwdjakvnsgk44qyhalv6ust54x98whl3q635hkwgsyw8xgqjl7jwu",
"maxfee": 25000
"maxfeepercent": "119.04761905",
"exemptfee": 0
},
"response_type": "json",
"response": {}
@ -1450,7 +1661,22 @@
}
]
},
"lnbits": {}
"lnbits": {},
"phoenixd": {
"pay_invoice_endpoint": [
{
"request_type": "json",
"request_body": {
"invoice": "lnbc5550n1pnq9jg3sp52rvwstvjcypjsaenzdh0h30jazvzsf8aaye0julprtth9kysxtuspp5e5s3z7felv4t9zrcc6wpn7ehvjl5yzewanzl5crljdl3jgeffyhqdq2f38xy6t5wvxqzjccqpjrzjq0yzeq76ney45hmjlnlpvu0nakzy2g35hqh0dujq8ujdpr2e42pf2rrs6vqpgcsqqqqqqqqqqqqqqeqqyg9qxpqysgqwftcx89k5pp28435pgxfl2vx3ksemzxccppw2j9yjn0ngr6ed7wj8ztc0d5kmt2mvzdlcgrludhz7jncd5l5l9w820hc4clpwhtqj3gq62g66n"
},
"response_type": "json",
"response": {
"paymentHash": "e35526a43d04e985594c0dfab848814f524b1c786598ec9a63beddb2d726ac96",
"routingFeeSat": -50
}
}
]
}
}
},
{
@ -1475,7 +1701,8 @@
"request_type": "data",
"request_body": {
"invoice": "lnbc210n1pjlgal5sp5xr3uwlfm7ltumdjyukhys0z2rw6grgm8me9k4w9vn05zt9svzzjspp5ud2jdfpaqn5c2k2vphatsjypfafyk8rcvkvwexnrhmwm94ex4jtqdqu24hxjapq23jhxapqf9h8vmmfvdjscqpjrzjqta942048v7qxh5x7pxwplhmtwfl0f25cq23jh87rhx7lgrwwvv86r90guqqnwgqqqqqqqqqqqqqqpsqyg9qxpqysgqylngsyg960lltngzy90e8n22v4j2hvjs4l4ttuy79qqefjv8q87q9ft7uhwdjakvnsgk44qyhalv6ust54x98whl3q635hkwgsyw8xgqjl7jwu",
"maxfee": 25000
"maxfeepercent": "119.04761905",
"exemptfee": 0
},
"response_type": "data",
"response": "data-not-json"
@ -1555,6 +1782,18 @@
}
}
]
},
"phoenixd": {
"pay_invoice_endpoint": [
{
"request_type": "json",
"request_body": {
"invoice": "lnbc5550n1pnq9jg3sp52rvwstvjcypjsaenzdh0h30jazvzsf8aaye0julprtth9kysxtuspp5e5s3z7felv4t9zrcc6wpn7ehvjl5yzewanzl5crljdl3jgeffyhqdq2f38xy6t5wvxqzjccqpjrzjq0yzeq76ney45hmjlnlpvu0nakzy2g35hqh0dujq8ujdpr2e42pf2rrs6vqpgcsqqqqqqqqqqqqqqeqqyg9qxpqysgqwftcx89k5pp28435pgxfl2vx3ksemzxccppw2j9yjn0ngr6ed7wj8ztc0d5kmt2mvzdlcgrludhz7jncd5l5l9w820hc4clpwhtqj3gq62g66n"
},
"response_type": "data",
"response": "data-not-json"
}
]
}
}
},
@ -1580,7 +1819,8 @@
"request_type": "data",
"request_body": {
"invoice": "lnbc210n1pjlgal5sp5xr3uwlfm7ltumdjyukhys0z2rw6grgm8me9k4w9vn05zt9svzzjspp5ud2jdfpaqn5c2k2vphatsjypfafyk8rcvkvwexnrhmwm94ex4jtqdqu24hxjapq23jhxapqf9h8vmmfvdjscqpjrzjqta942048v7qxh5x7pxwplhmtwfl0f25cq23jh87rhx7lgrwwvv86r90guqqnwgqqqqqqqqqqqqqqpsqyg9qxpqysgqylngsyg960lltngzy90e8n22v4j2hvjs4l4ttuy79qqefjv8q87q9ft7uhwdjakvnsgk44qyhalv6ust54x98whl3q635hkwgsyw8xgqjl7jwu",
"maxfee": 25000
"maxfeepercent": "119.04761905",
"exemptfee": 0
},
"response_type": "response",
"response": {
@ -1669,6 +1909,21 @@
"response": {}
}
]
},
"phoenixd": {
"pay_invoice_endpoint": [
{
"request_type": "json",
"request_body": {
"invoice": "lnbc5550n1pnq9jg3sp52rvwstvjcypjsaenzdh0h30jazvzsf8aaye0julprtth9kysxtuspp5e5s3z7felv4t9zrcc6wpn7ehvjl5yzewanzl5crljdl3jgeffyhqdq2f38xy6t5wvxqzjccqpjrzjq0yzeq76ney45hmjlnlpvu0nakzy2g35hqh0dujq8ujdpr2e42pf2rrs6vqpgcsqqqqqqqqqqqqqqeqqyg9qxpqysgqwftcx89k5pp28435pgxfl2vx3ksemzxccppw2j9yjn0ngr6ed7wj8ztc0d5kmt2mvzdlcgrludhz7jncd5l5l9w820hc4clpwhtqj3gq62g66n"
},
"response_type": "response",
"response": {
"response": "Not Found",
"status": 404
}
}
]
}
}
},
@ -1746,6 +2001,16 @@
},
"method": "GET"
}
},
"phoenixd": {
"get_invoice_status_endpoint": {
"uri": "/payments/incoming/e35526a43d04e985594c0dfab848814f524b1c786598ec9a63beddb2d726ac96",
"headers": {
"Authorization": "Basic OmYxNzFiYTAyMmE3NjRlNjc5ZWVmOTUwYjIxZmIxYzA0ZjE3MWJhMDIyYTc2NGU2NzllZWY5NTBiMjFmYjFjMDQ=",
"User-Agent": "LNbits/Tests"
},
"method": "GET"
}
}
},
"tests": [
@ -1829,6 +2094,18 @@
}
}
]
},
"phoenixd": {
"get_invoice_status_endpoint": [
{
"response_type": "json",
"response": {
"isPaid": true,
"fees": 50,
"preimage": "0000000000000000000000000000000000000000000000000000000000000000"
}
}
]
}
}
},
@ -1885,6 +2162,10 @@
"lnbits": {
"description": "lnbits.py doesn't handle the 'failed' status for `get_invoice_status`",
"get_invoice_status_endpoint": []
},
"phoenixd": {
"description": "phoenixd.py doesn't handle the 'failed' status for `get_invoice_status`",
"get_invoice_status_endpoint": []
}
}
},
@ -2064,6 +2345,42 @@
}
}
]
},
"phoenixd": {
"get_invoice_status_endpoint": [
{
"description": "no data",
"response_type": "json",
"response": {}
},
{
"description": "not paid",
"response_type": "json",
"response": {
"isPaid": false
}
},
{
"description": "paid and missing data",
"response_type": "json",
"response": {
"isPaid": true
}
},
{
"description": "bad json",
"response_type": "data",
"response": "data-not-json"
},
{
"description": "http 404",
"response_type": "response",
"response": {
"response": "Not Found",
"status": 404
}
}
]
}
}
},
@ -2136,6 +2453,16 @@
},
"method": "GET"
}
},
"phoenixd": {
"get_payment_status_endpoint": {
"uri": "/payments/outgoing/e35526a43d04e985594c0dfab848814f524b1c786598ec9a63beddb2d726ac96",
"headers": {
"Authorization": "Basic OmYxNzFiYTAyMmE3NjRlNjc5ZWVmOTUwYjIxZmIxYzA0ZjE3MWJhMDIyYTc2NGU2NzllZWY5NTBiMjFmYjFjMDQ=",
"User-Agent": "LNbits/Tests"
},
"method": "GET"
}
}
},
"tests": [
@ -2229,6 +2556,18 @@
}
}
]
},
"phoenixd": {
"get_payment_status_endpoint": [
{
"response_type": "json",
"response": {
"isPaid": true,
"fees": 1000,
"preimage": "0000000000000000000000000000000000000000000000000000000000000000"
}
}
]
}
}
},
@ -2305,6 +2644,10 @@
},
"lnbits": {
"get_payment_status_endpoint": []
},
"phoenixd": {
"description": "phoenixd.py doesn't handle the 'failed' status for `get_invoice_status`",
"get_payment_status_endpoint": []
}
}
},
@ -2535,6 +2878,42 @@
}
}
]
},
"phoenixd": {
"get_payment_status_endpoint": [
{
"description": "no data",
"response_type": "json",
"response": {}
},
{
"description": "not paid",
"response_type": "json",
"response": {
"isPaid": false
}
},
{
"description": "paid and missing data",
"response_type": "json",
"response": {
"isPaid": true
}
},
{
"description": "bad json",
"response_type": "data",
"response": "data-not-json"
},
{
"description": "http 404",
"response_type": "response",
"response": {
"response": "Not Found",
"status": 404
}
}
]
}
}
},