Skip to content

feat: add first iteration of a cashu service #165

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Draft
wants to merge 9 commits into
base: dev
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
38 changes: 38 additions & 0 deletions .cashu
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
DEBUG=FALSE

CASHU_DIR=cashu.db

# WALLET

# MINT_URL=https://localhost:3338
MINT_HOST=127.0.0.1
MINT_PORT=3338

# use builtin tor, this overrides SOCKS_HOST and SOCKS_PORT
TOR=FALSE

# use custom tor proxy, use with TOR=false
# SOCKS_HOST=localhost
# SOCKS_PORT=9050

# MINT

MINT_PRIVATE_KEY=supersecretprivatekey

MINT_SERVER_HOST=127.0.0.1
MINT_SERVER_PORT=3338

LIGHTNING=TRUE
# fee to reserve in percent of the amount
LIGHTNING_FEE_PERCENT=1.0
# minimum fee to reserve
LIGHTNING_RESERVE_FEE_MIN=4000

# LNBITS_ENDPOINT=https://legend.lnbits.com
# LNBITS_KEY=yourkeyasdasdasd

# NOSTR
# nostr private key to which to receive tokens to
NOSTR_PRIVATE_KEY=nostr_privatekey_here_hex_or_bech32
# nostr relays (comma separated list)
NOSTR_RELAYS="wss://nostr-pub.wellorder.net"
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -5,3 +5,4 @@ __pycache__
htmlcov
scripts/sync_to_blitz.personal.sh
docker/.env
cashu.db
2 changes: 1 addition & 1 deletion .pre-commit-config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ repos:
- id: debug-statements
- id: mixed-line-ending
- repo: https://github.com/psf/black
rev: 22.12.0
rev: 23.1.0
hooks:
- id: black
- repo: https://github.com/pycqa/isort
Expand Down
1 change: 0 additions & 1 deletion app/api/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -114,7 +114,6 @@ class _PushID(object):
)

def __init__(self):

# Timestamp of last push, used to prevent local collisions if you
# push twice in one ms.
self.last_push_time = 0
Expand Down
4 changes: 0 additions & 4 deletions app/apps/impl/raspiblitz.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,6 @@

class RaspiBlitzApps(AppsBase):
async def get_app_status_single(self, app_id):

if app_id not in available_app_ids:
return {
"id": f"{app_id}",
Expand Down Expand Up @@ -126,7 +125,6 @@ async def get_app_status_single(self, app_id):
async def get_app_status(self):
appStatusList: List = []
for appID in available_app_ids:

# skip app based on node running
if node_type == "" or node_type == "none":
if appID == "rtl":
Expand Down Expand Up @@ -200,7 +198,6 @@ async def uninstall_app_sub(self, app_id: str, delete_data: bool):
return jsonable_encoder({"id": app_id})

async def run_bonus_script(self, app_id: str, params: str):

# to satisfy CodeQL: test again against predefined array and don't use 'user value'
tested_app_id = ""
for id in available_app_ids:
Expand Down Expand Up @@ -275,7 +272,6 @@ async def run_bonus_script(self, app_id: str, params: str):
)
# nothing above consider success
else:

# check if script was effective
updatedAppData = await self.get_app_status_single(app_id)

Expand Down
1 change: 0 additions & 1 deletion app/bitcoind/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -327,7 +327,6 @@ class BlockchainInfo(BaseModel):

@classmethod
def from_rpc(cls, r):

# get softfork information if available
softforks = []
if "softforks" in r:
Expand Down
Empty file added app/cashu/__init__.py
Empty file.
3 changes: 3 additions & 0 deletions app/cashu/constants.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
DATA_FOLDER = "cashu.db"
DEFAULT_WALLET_NAME = "DefaultCashuWallet"
DEFAULT_MINT_URL = "http://localhost:3338"
30 changes: 30 additions & 0 deletions app/cashu/docs.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
pin_mint_summary = "Pins the mint URL. Calling this endpoint with no URL will reset the mint to the system default."
pin_mint_desc = "When calling Cashu endpoints without a mint URL, the system will use the mint URL that was pinned with this endpoint."

pin_wallet_summary = "Pins the current wallet name. Calling this endpoint with no name will reset the wallet to the system default."
pin_wallet_desc = "When calling Cashu endpoints without a wallet name, the system will use the wallet that was pinned with this endpoint."

get_balance_summary = "Get the combined balance of all the known Cashu wallets. To get balances of single wallets use the /list-wallets endpoint."

estimate_pay_summary = "Decodes the amount from a Lightning invoice and returns the total amount (amount+fees) to be paid."

pay_invoice_summary = (
"Pay a lightning invoice via available tokens on a mint or a lightning payment."
)


pay_invoice_description = """
This endpoint will try to pay the invoice using the available tokens on the mint.
If it fails it will try to pay the invoice using a lightning payment.

> 👉 This is different from the /lightning/pay-invoice endpoint which will only try to pay the invoice using a lightning payment.
"""

receive_error_description = """Reason why the token could not be received.

Possible values:
`none`: No error
`mint_offline`: Mint is not reachable
`mint_error`: Mint returned an error
`mint_untrusted`: Mint is unknown, must be added as trusted mint first
"""
32 changes: 32 additions & 0 deletions app/cashu/errors.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
from enum import Enum


class CashuReceiveFailReason(str, Enum):
NONE = "none"
MINT_OFFLINE = "mint_offline"
MINT_ERROR = "mint_error"
MINT_UNTRUSTED = "mint_untrusted"
MINT_KEYSET_MISMATCH = "mint_keyset_mismatch"
MINT_KEYSET_INVALID = "mint_keyset_invalid"
FORMAT_ERROR = "format_error"
LOCK_ERROR = "lock_error"
PROOF_ERROR = "proof_error"
UNKNOWN = "unknown"


class CashuException(Exception):
def __init__(self, message: str, reason: CashuReceiveFailReason) -> None:
super().__init__()
self.message: str = message
self.reason: CashuReceiveFailReason = reason


class UntrustedMintException(CashuException):
def __init__(self, mint_url: str, mint_keyset: str) -> None:
super().__init__(
message="Mint is untrusted",
reason=CashuReceiveFailReason.MINT_UNTRUSTED,
)

self.mint_url: str = mint_url
self.mint_keyset: str = mint_keyset
66 changes: 66 additions & 0 deletions app/cashu/exceptions.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
from fastapi import HTTPException, status


class LockNotFoundException(HTTPException):
def __init__(self):
super().__init__(
status_code=status.HTTP_404_NOT_FOUND,
detail=f"Lock not found.",
)


class LockFormatException(HTTPException):
def __init__(self):
super().__init__(
status_code=status.HTTP_400_BAD_REQUEST,
detail=f"Lock has wrong format. Expected P2SH:<address>.",
)


class TokensSpentException(HTTPException):
def __init__(self, secret: str):
self.secret = secret
super().__init__(
status_code=status.HTTP_403_FORBIDDEN,
detail=f"Mint Error: tokens already spent.",
)


class IsDefaultMintException(HTTPException):
def __init__(self):
super().__init__(
status_code=status.HTTP_400_BAD_REQUEST,
detail="Default mint is already added.",
)


class MintExistsException(HTTPException):
def __init__(self, mint_name: str):
super().__init__(
status_code=status.HTTP_400_BAD_REQUEST,
detail=f"Mint with name {mint_name} was already added.",
)


class WalletExistsException(HTTPException):
def __init__(self, wallet_name: str):
super().__init__(
status_code=status.HTTP_409_CONFLICT,
detail=f"Wallet {wallet_name} exists.",
)


class WalletNotFoundException(HTTPException):
def __init__(self, wallet_name: str):
super().__init__(
status_code=status.HTTP_404_NOT_FOUND,
detail=f"Wallet {wallet_name} not found.",
)


class ZeroInvoiceException(HTTPException):
def __init__(self):
super().__init__(
status_code=status.HTTP_400_BAD_REQUEST,
detail="Zero invoices not allowed. Amount must be positive.",
)
Loading