mirror of
https://github.com/aljazceru/nutshell.git
synced 2026-02-03 15:54:20 +01:00
Add Blink backend (#433)
* wip blink * Blink working * check fee correctly * add comment * unfix lnbits * unit test blink backend * make format * mypy * settings * add error if blink key not set
This commit is contained in:
@@ -56,8 +56,8 @@ MINT_DERIVATION_PATH="m/0'/0'/0'"
|
||||
MINT_DATABASE=data/mint
|
||||
|
||||
# Lightning
|
||||
# Supported: LndRestWallet, LNbitsWallet, FakeWallet
|
||||
MINT_LIGHTNING_BACKEND=LNbitsWallet
|
||||
# Supported: FakeWallet, LndRestWallet, CoreLightningRestWallet, LNbitsWallet, BlinkWallet, StrikeWallet
|
||||
MINT_LIGHTNING_BACKEND=FakeWallet
|
||||
|
||||
# for use with LNbitsWallet
|
||||
MINT_LNBITS_ENDPOINT=https://legend.lnbits.com
|
||||
@@ -73,6 +73,10 @@ MINT_CORELIGHTNING_REST_URL=https://localhost:3001
|
||||
MINT_CORELIGHTNING_REST_MACAROON="./clightning-rest/access.macaroon"
|
||||
MINT_CORELIGHTNING_REST_CERT="./clightning-2-rest/certificate.pem"
|
||||
|
||||
MINT_BLINK_KEY=blink_abcdefgh
|
||||
|
||||
MINT_STRIKE_KEY=ABC123
|
||||
|
||||
# fee to reserve in percent of the amount
|
||||
LIGHTNING_FEE_PERCENT=1.0
|
||||
# minimum fee to reserve
|
||||
|
||||
@@ -92,8 +92,8 @@ class MintSettings(CashuSettings):
|
||||
|
||||
mint_lnbits_endpoint: str = Field(default=None)
|
||||
mint_lnbits_key: str = Field(default=None)
|
||||
|
||||
mint_strike_key: str = Field(default=None)
|
||||
mint_blink_key: str = Field(default=None)
|
||||
|
||||
|
||||
class FakeWalletSettings(MintSettings):
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
# type: ignore
|
||||
from ..core.settings import settings
|
||||
from .blink import BlinkWallet # noqa: F401
|
||||
from .corelightningrest import CoreLightningRestWallet # noqa: F401
|
||||
from .fake import FakeWallet # noqa: F401
|
||||
from .lnbits import LNbitsWallet # noqa: F401
|
||||
|
||||
343
cashu/lightning/blink.py
Normal file
343
cashu/lightning/blink.py
Normal file
@@ -0,0 +1,343 @@
|
||||
# type: ignore
|
||||
import asyncio
|
||||
import json
|
||||
import math
|
||||
from typing import Dict, Optional
|
||||
|
||||
import bolt11
|
||||
import httpx
|
||||
from bolt11 import (
|
||||
decode,
|
||||
)
|
||||
from loguru import logger
|
||||
|
||||
from ..core.base import Amount, MeltQuote, Unit
|
||||
from ..core.settings import settings
|
||||
from .base import (
|
||||
InvoiceResponse,
|
||||
LightningBackend,
|
||||
PaymentQuoteResponse,
|
||||
PaymentResponse,
|
||||
PaymentStatus,
|
||||
StatusResponse,
|
||||
)
|
||||
|
||||
# according to https://github.com/GaloyMoney/galoy/blob/7e79cc27304de9b9c2e7d7f4fdd3bac09df23aac/core/api/src/domain/bitcoin/index.ts#L59
|
||||
BLINK_MAX_FEE_PERCENT = 0.5
|
||||
|
||||
|
||||
class BlinkWallet(LightningBackend):
|
||||
"""https://dev.blink.sv/
|
||||
Create API Key at: https://dashboard.blink.sv/
|
||||
"""
|
||||
|
||||
units = set([Unit.sat, Unit.usd])
|
||||
wallet_ids: Dict[Unit, str] = {}
|
||||
endpoint = "https://api.blink.sv/graphql"
|
||||
invoice_statuses = {"PENDING": None, "PAID": True, "EXPIRED": False}
|
||||
payment_execution_statuses = {"SUCCESS": True, "ALREADY_PAID": None}
|
||||
payment_statuses = {"SUCCESS": True, "PENDING": None, "FAILURE": False}
|
||||
|
||||
def __init__(self):
|
||||
assert settings.mint_blink_key, "MINT_BLINK_KEY not set"
|
||||
self.client = httpx.AsyncClient(
|
||||
verify=not settings.debug,
|
||||
headers={
|
||||
"X-Api-Key": settings.mint_blink_key,
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
base_url=self.endpoint,
|
||||
timeout=15,
|
||||
)
|
||||
|
||||
async def status(self) -> StatusResponse:
|
||||
try:
|
||||
r = await self.client.post(
|
||||
url=self.endpoint,
|
||||
data=(
|
||||
'{"query":"query me { me { defaultAccount { wallets { id'
|
||||
' walletCurrency balance }}}}", "variables":{}}'
|
||||
),
|
||||
)
|
||||
r.raise_for_status()
|
||||
except Exception as exc:
|
||||
logger.error(f"Blink API error: {str(exc)}")
|
||||
return StatusResponse(
|
||||
error_message=f"Failed to connect to {self.endpoint} due to: {exc}",
|
||||
balance=0,
|
||||
)
|
||||
|
||||
try:
|
||||
resp: dict = r.json()
|
||||
except Exception:
|
||||
return StatusResponse(
|
||||
error_message=(
|
||||
f"Received invalid response from {self.endpoint}: {r.text}"
|
||||
),
|
||||
balance=0,
|
||||
)
|
||||
|
||||
balance = 0
|
||||
for wallet_dict in resp["data"]["me"]["defaultAccount"]["wallets"]:
|
||||
if wallet_dict["walletCurrency"] == "USD":
|
||||
self.wallet_ids[Unit.usd] = wallet_dict["id"]
|
||||
elif wallet_dict["walletCurrency"] == "BTC":
|
||||
self.wallet_ids[Unit.sat] = wallet_dict["id"]
|
||||
balance = wallet_dict["balance"]
|
||||
|
||||
return StatusResponse(error_message=None, balance=balance)
|
||||
|
||||
async def create_invoice(
|
||||
self,
|
||||
amount: Amount,
|
||||
memo: Optional[str] = None,
|
||||
description_hash: Optional[bytes] = None,
|
||||
unhashed_description: Optional[bytes] = None,
|
||||
) -> InvoiceResponse:
|
||||
self.assert_unit_supported(amount.unit)
|
||||
|
||||
variables = {
|
||||
"input": {
|
||||
"amount": str(amount.to(Unit.sat).amount),
|
||||
"recipientWalletId": self.wallet_ids[Unit.sat],
|
||||
}
|
||||
}
|
||||
if description_hash:
|
||||
variables["input"]["descriptionHash"] = description_hash.hex()
|
||||
if memo:
|
||||
variables["input"]["memo"] = memo
|
||||
|
||||
data = {
|
||||
"query": """
|
||||
mutation LnInvoiceCreateOnBehalfOfRecipient($input: LnInvoiceCreateOnBehalfOfRecipientInput!) {
|
||||
lnInvoiceCreateOnBehalfOfRecipient(input: $input) {
|
||||
invoice {
|
||||
paymentRequest
|
||||
paymentHash
|
||||
paymentSecret
|
||||
satoshis
|
||||
}
|
||||
errors {
|
||||
message path code
|
||||
}
|
||||
}
|
||||
}
|
||||
""",
|
||||
"variables": variables,
|
||||
}
|
||||
try:
|
||||
r = await self.client.post(
|
||||
url=self.endpoint,
|
||||
data=json.dumps(data),
|
||||
)
|
||||
r.raise_for_status()
|
||||
except Exception as e:
|
||||
logger.error(f"Blink API error: {str(e)}")
|
||||
return InvoiceResponse(ok=False, error_message=str(e))
|
||||
|
||||
resp = r.json()
|
||||
assert resp, "invalid response"
|
||||
payment_request = resp["data"]["lnInvoiceCreateOnBehalfOfRecipient"]["invoice"][
|
||||
"paymentRequest"
|
||||
]
|
||||
checking_id = payment_request
|
||||
|
||||
return InvoiceResponse(
|
||||
ok=True,
|
||||
checking_id=checking_id,
|
||||
payment_request=payment_request,
|
||||
)
|
||||
|
||||
async def pay_invoice(
|
||||
self, quote: MeltQuote, fee_limit_msat: int
|
||||
) -> PaymentResponse:
|
||||
variables = {
|
||||
"input": {
|
||||
"paymentRequest": quote.request,
|
||||
"walletId": self.wallet_ids[Unit.sat],
|
||||
}
|
||||
}
|
||||
data = {
|
||||
"query": """
|
||||
mutation lnInvoicePaymentSend($input: LnInvoicePaymentInput!) {
|
||||
lnInvoicePaymentSend(input: $input) {
|
||||
errors {
|
||||
message path code
|
||||
}
|
||||
status
|
||||
transaction {
|
||||
settlementAmount settlementFee status
|
||||
}
|
||||
}
|
||||
}
|
||||
""",
|
||||
"variables": variables,
|
||||
}
|
||||
|
||||
try:
|
||||
r = await self.client.post(
|
||||
url=self.endpoint,
|
||||
data=json.dumps(data),
|
||||
)
|
||||
r.raise_for_status()
|
||||
except Exception as e:
|
||||
logger.error(f"Blink API error: {str(e)}")
|
||||
return PaymentResponse(ok=False, error_message=str(e))
|
||||
|
||||
resp: dict = r.json()
|
||||
paid = self.payment_execution_statuses[
|
||||
resp["data"]["lnInvoicePaymentSend"]["status"]
|
||||
]
|
||||
fee = resp["data"]["lnInvoicePaymentSend"]["transaction"]["settlementFee"]
|
||||
checking_id = quote.request
|
||||
|
||||
return PaymentResponse(
|
||||
ok=paid,
|
||||
checking_id=checking_id,
|
||||
fee=Amount(Unit.sat, fee),
|
||||
preimage=None,
|
||||
error_message="Invoice already paid." if paid is None else None,
|
||||
)
|
||||
|
||||
async def get_invoice_status(self, checking_id: str) -> PaymentStatus:
|
||||
variables = {"input": {"paymentRequest": checking_id}}
|
||||
data = {
|
||||
"query": """
|
||||
query lnInvoicePaymentStatus($input: LnInvoicePaymentStatusInput!) {
|
||||
lnInvoicePaymentStatus(input: $input) {
|
||||
errors {
|
||||
message path code
|
||||
}
|
||||
status
|
||||
}
|
||||
}
|
||||
""",
|
||||
"variables": variables,
|
||||
}
|
||||
try:
|
||||
r = await self.client.post(url=self.endpoint, data=json.dumps(data))
|
||||
r.raise_for_status()
|
||||
except Exception as e:
|
||||
logger.error(f"Blink API error: {str(e)}")
|
||||
return PaymentStatus(paid=None)
|
||||
resp: dict = r.json()
|
||||
if resp["data"]["lnInvoicePaymentStatus"]["errors"]:
|
||||
logger.error(
|
||||
"Blink Error", resp["data"]["lnInvoicePaymentStatus"]["errors"]
|
||||
)
|
||||
return PaymentStatus(paid=None)
|
||||
paid = self.invoice_statuses[resp["data"]["lnInvoicePaymentStatus"]["status"]]
|
||||
return PaymentStatus(paid=paid)
|
||||
|
||||
async def get_payment_status(self, checking_id: str) -> PaymentStatus:
|
||||
# Checking ID is the payment request and blink wants the payment hash
|
||||
payment_hash = bolt11.decode(checking_id).payment_hash
|
||||
variables = {
|
||||
"paymentHash": payment_hash,
|
||||
"walletId": self.wallet_ids[Unit.sat],
|
||||
}
|
||||
data = {
|
||||
"query": """
|
||||
query TransactionsByPaymentHash($paymentHash: PaymentHash!, $walletId: WalletId!) {
|
||||
me {
|
||||
defaultAccount {
|
||||
walletById(walletId: $walletId) {
|
||||
transactionsByPaymentHash(paymentHash: $paymentHash) {
|
||||
status
|
||||
direction
|
||||
settlementFee
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
""",
|
||||
"variables": variables,
|
||||
}
|
||||
|
||||
try:
|
||||
r = await self.client.post(
|
||||
url=self.endpoint,
|
||||
data=json.dumps(data),
|
||||
)
|
||||
r.raise_for_status()
|
||||
except Exception as e:
|
||||
logger.error(f"Blink API error: {str(e)}")
|
||||
return PaymentResponse(ok=False, error_message=str(e))
|
||||
|
||||
resp: dict = r.json()
|
||||
# no result found
|
||||
if not resp["data"]["me"]["defaultAccount"]["walletById"][
|
||||
"transactionsByPaymentHash"
|
||||
]:
|
||||
return PaymentStatus(paid=None)
|
||||
|
||||
paid = self.payment_statuses[
|
||||
resp["data"]["me"]["defaultAccount"]["walletById"][
|
||||
"transactionsByPaymentHash"
|
||||
][0]["status"]
|
||||
]
|
||||
fee = resp["data"]["me"]["defaultAccount"]["walletById"][
|
||||
"transactionsByPaymentHash"
|
||||
][0]["settlementFee"]
|
||||
|
||||
return PaymentStatus(
|
||||
paid=paid,
|
||||
fee=Amount(Unit.sat, fee),
|
||||
preimage=None,
|
||||
)
|
||||
|
||||
async def get_payment_quote(self, bolt11: str) -> PaymentQuoteResponse:
|
||||
variables = {
|
||||
"input": {
|
||||
"paymentRequest": bolt11,
|
||||
"walletId": self.wallet_ids[Unit.sat],
|
||||
}
|
||||
}
|
||||
data = {
|
||||
"query": """
|
||||
mutation lnInvoiceFeeProbe($input: LnInvoiceFeeProbeInput!) {
|
||||
lnInvoiceFeeProbe(input: $input) {
|
||||
amount
|
||||
errors {
|
||||
message path code
|
||||
}
|
||||
}
|
||||
}
|
||||
""",
|
||||
"variables": variables,
|
||||
}
|
||||
|
||||
try:
|
||||
r = await self.client.post(
|
||||
url=self.endpoint,
|
||||
data=json.dumps(data),
|
||||
)
|
||||
r.raise_for_status()
|
||||
except Exception as e:
|
||||
logger.error(f"Blink API error: {str(e)}")
|
||||
return PaymentResponse(ok=False, error_message=str(e))
|
||||
resp: dict = r.json()
|
||||
|
||||
invoice_obj = decode(bolt11)
|
||||
assert invoice_obj.amount_msat, "invoice has no amount."
|
||||
|
||||
amount_msat = int(invoice_obj.amount_msat)
|
||||
|
||||
fees_response_msat = int(resp["data"]["lnInvoiceFeeProbe"]["amount"]) * 1000
|
||||
# we either take fee_msat_response or the BLINK_MAX_FEE_PERCENT, whichever is higher
|
||||
fees_msat = max(
|
||||
fees_response_msat, math.ceil(amount_msat * BLINK_MAX_FEE_PERCENT)
|
||||
)
|
||||
|
||||
fees = Amount(unit=Unit.msat, amount=fees_msat)
|
||||
amount = Amount(unit=Unit.msat, amount=amount_msat)
|
||||
return PaymentQuoteResponse(checking_id=bolt11, fee=fees, amount=amount)
|
||||
|
||||
|
||||
async def main():
|
||||
pass
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
asyncio.run(main())
|
||||
@@ -46,7 +46,7 @@ class LNbitsWallet(LightningBackend):
|
||||
except Exception:
|
||||
return StatusResponse(
|
||||
error_message=(
|
||||
f"Failed to connect to {self.endpoint}, got: '{r.text[:200]}...'"
|
||||
f"Received invalid response from {self.endpoint}: {r.text}"
|
||||
),
|
||||
balance=0,
|
||||
)
|
||||
|
||||
@@ -640,7 +640,7 @@ class Ledger(LedgerVerification, LedgerSpendingConditions):
|
||||
Returns:
|
||||
Tuple[str, List[BlindedMessage]]: Proof of payment and signed outputs for returning overpaid fees to wallet.
|
||||
"""
|
||||
# get melt quote and settle transaction internally if possible
|
||||
# get melt quote and check if it is paid
|
||||
melt_quote = await self.get_melt_quote(quote_id=quote)
|
||||
method = Method[melt_quote.method]
|
||||
unit = Unit[melt_quote.unit]
|
||||
@@ -676,6 +676,7 @@ class Ledger(LedgerVerification, LedgerSpendingConditions):
|
||||
# set proofs to pending to avoid race conditions
|
||||
await self._set_proofs_pending(proofs)
|
||||
try:
|
||||
# settle the transaction internally if there is a mint quote with the same payment request
|
||||
melt_quote = await self.melt_mint_settle_internally(melt_quote)
|
||||
|
||||
# quote not paid yet (not internal), pay it with the backend
|
||||
@@ -689,7 +690,9 @@ class Ledger(LedgerVerification, LedgerSpendingConditions):
|
||||
f" fee: {payment.fee.str() if payment.fee else 0}"
|
||||
)
|
||||
if not payment.ok:
|
||||
raise LightningError("Lightning payment unsuccessful.")
|
||||
raise LightningError(
|
||||
f"Lightning payment unsuccessful. {payment.error_message}"
|
||||
)
|
||||
if payment.fee:
|
||||
melt_quote.fee_paid = payment.fee.to(
|
||||
to_unit=unit, round="up"
|
||||
|
||||
16
poetry.lock
generated
16
poetry.lock
generated
@@ -1268,6 +1268,20 @@ six = ">=1.8.0"
|
||||
[package.extras]
|
||||
test = ["ipython", "mock", "pytest (>=3.0.5)"]
|
||||
|
||||
[[package]]
|
||||
name = "respx"
|
||||
version = "0.20.2"
|
||||
description = "A utility for mocking out the Python HTTPX and HTTP Core libraries."
|
||||
optional = false
|
||||
python-versions = ">=3.7"
|
||||
files = [
|
||||
{file = "respx-0.20.2-py2.py3-none-any.whl", hash = "sha256:ab8e1cf6da28a5b2dd883ea617f8130f77f676736e6e9e4a25817ad116a172c9"},
|
||||
{file = "respx-0.20.2.tar.gz", hash = "sha256:07cf4108b1c88b82010f67d3c831dae33a375c7b436e54d87737c7f9f99be643"},
|
||||
]
|
||||
|
||||
[package.dependencies]
|
||||
httpx = ">=0.21.0"
|
||||
|
||||
[[package]]
|
||||
name = "ruff"
|
||||
version = "0.0.284"
|
||||
@@ -1598,4 +1612,4 @@ pgsql = ["psycopg2-binary"]
|
||||
[metadata]
|
||||
lock-version = "2.0"
|
||||
python-versions = "^3.8.1"
|
||||
content-hash = "f7aa2919aca77aa4d1dfcba18c6fc9694a2cc1d5cfd60e7ec991a615251fa86e"
|
||||
content-hash = "7ad5150f23bb8ba229e43b3d329b4ec747791622f5e83357a5fae8aee9315fdc"
|
||||
|
||||
@@ -46,6 +46,7 @@ pytest = "^7.4.0"
|
||||
ruff = "^0.0.284"
|
||||
pre-commit = "^3.3.3"
|
||||
fastapi-profiler = "^1.2.0"
|
||||
respx = "^0.20.2"
|
||||
|
||||
[build-system]
|
||||
requires = ["poetry-core>=1.0.0"]
|
||||
|
||||
137
tests/test_mint_lightning_blink.py
Normal file
137
tests/test_mint_lightning_blink.py
Normal file
@@ -0,0 +1,137 @@
|
||||
import pytest
|
||||
import respx
|
||||
from httpx import Response
|
||||
|
||||
from cashu.core.base import Amount, MeltQuote, Unit
|
||||
from cashu.core.settings import settings
|
||||
from cashu.lightning.blink import BlinkWallet
|
||||
|
||||
settings.mint_blink_key = "123"
|
||||
blink = BlinkWallet()
|
||||
payment_request = (
|
||||
"lnbc10u1pjap7phpp50s9lzr3477j0tvacpfy2ucrs4q0q6cvn232ex7nt2zqxxxj8gxrsdpv2phhwetjv4jzqcneypqyc6t8dp6xu6twva2xjuzzda6qcqzzsxqrrsss"
|
||||
"p575z0n39w2j7zgnpqtdlrgz9rycner4eptjm3lz363dzylnrm3h4s9qyyssqfz8jglcshnlcf0zkw4qu8fyr564lg59x5al724kms3h6gpuhx9xrfv27tgx3l3u3cyf6"
|
||||
"3r52u0xmac6max8mdupghfzh84t4hfsvrfsqwnuszf"
|
||||
)
|
||||
|
||||
|
||||
@respx.mock
|
||||
@pytest.mark.asyncio
|
||||
async def test_blink_status():
|
||||
mock_response = {
|
||||
"data": {
|
||||
"me": {
|
||||
"defaultAccount": {
|
||||
"wallets": [
|
||||
{"walletCurrency": "USD", "id": "123", "balance": 32142},
|
||||
{
|
||||
"walletCurrency": "BTC",
|
||||
"id": "456",
|
||||
"balance": 100000,
|
||||
},
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
respx.post(blink.endpoint).mock(return_value=Response(200, json=mock_response))
|
||||
status = await blink.status()
|
||||
assert status.balance == 100000
|
||||
|
||||
|
||||
@respx.mock
|
||||
@pytest.mark.asyncio
|
||||
async def test_blink_create_invoice():
|
||||
mock_response = {
|
||||
"data": {
|
||||
"lnInvoiceCreateOnBehalfOfRecipient": {
|
||||
"invoice": {"paymentRequest": payment_request}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
respx.post(blink.endpoint).mock(return_value=Response(200, json=mock_response))
|
||||
invoice = await blink.create_invoice(Amount(Unit.sat, 1000))
|
||||
assert invoice.checking_id == invoice.payment_request
|
||||
assert invoice.ok
|
||||
|
||||
|
||||
@respx.mock
|
||||
@pytest.mark.asyncio
|
||||
async def test_blink_pay_invoice():
|
||||
mock_response = {
|
||||
"data": {
|
||||
"lnInvoicePaymentSend": {
|
||||
"status": "SUCCESS",
|
||||
"transaction": {"settlementFee": 10},
|
||||
}
|
||||
}
|
||||
}
|
||||
respx.post(blink.endpoint).mock(return_value=Response(200, json=mock_response))
|
||||
quote = MeltQuote(
|
||||
request=payment_request,
|
||||
quote="asd",
|
||||
method="bolt11",
|
||||
checking_id=payment_request,
|
||||
unit="sat",
|
||||
amount=100,
|
||||
fee_reserve=12,
|
||||
paid=False,
|
||||
)
|
||||
payment = await blink.pay_invoice(quote, 1000)
|
||||
assert payment.ok
|
||||
assert payment.fee
|
||||
assert payment.fee.amount == 10
|
||||
assert payment.error_message is None
|
||||
assert payment.checking_id == payment_request
|
||||
|
||||
|
||||
@respx.mock
|
||||
@pytest.mark.asyncio
|
||||
async def test_blink_get_invoice_status():
|
||||
mock_response = {
|
||||
"data": {
|
||||
"lnInvoicePaymentStatus": {
|
||||
"status": "PAID",
|
||||
"errors": [],
|
||||
}
|
||||
}
|
||||
}
|
||||
respx.post(blink.endpoint).mock(return_value=Response(200, json=mock_response))
|
||||
status = await blink.get_invoice_status("123")
|
||||
assert status.paid
|
||||
|
||||
|
||||
@respx.mock
|
||||
@pytest.mark.asyncio
|
||||
async def test_blink_get_payment_status():
|
||||
mock_response = {
|
||||
"data": {
|
||||
"me": {
|
||||
"defaultAccount": {
|
||||
"walletById": {
|
||||
"transactionsByPaymentHash": [
|
||||
{"status": "SUCCESS", "settlementFee": 10}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
respx.post(blink.endpoint).mock(return_value=Response(200, json=mock_response))
|
||||
status = await blink.get_payment_status(payment_request)
|
||||
assert status.paid
|
||||
assert status.fee
|
||||
assert status.fee.amount == 10
|
||||
assert status.preimage is None
|
||||
|
||||
|
||||
@respx.mock
|
||||
@pytest.mark.asyncio
|
||||
async def test_blink_get_payment_quote():
|
||||
mock_response = {"data": {"lnInvoiceFeeProbe": {"amount": 10}}}
|
||||
respx.post(blink.endpoint).mock(return_value=Response(200, json=mock_response))
|
||||
quote = await blink.get_payment_quote(payment_request)
|
||||
assert quote.checking_id == payment_request
|
||||
assert quote.amount == Amount(Unit.msat, 1000000) # msat
|
||||
assert quote.fee == Amount(Unit.msat, 500000) # msat
|
||||
Reference in New Issue
Block a user