mirror of
https://github.com/aljazceru/nutshell.git
synced 2025-12-20 18:44:20 +01:00
Wallet/fix_nostr_timeout (#376)
* fix nostr receive * split with amount pre 0.13 * drop 0.13.0 amount with split compatibility
This commit is contained in:
20
README.md
20
README.md
@@ -119,15 +119,7 @@ MINT_URL=https://8333.space:3338
|
|||||||
```bash
|
```bash
|
||||||
cashu info
|
cashu info
|
||||||
```
|
```
|
||||||
|
This command shows information about your wallet.
|
||||||
Returns:
|
|
||||||
```bash
|
|
||||||
Version: 0.14.0
|
|
||||||
Debug: False
|
|
||||||
Cashu dir: /home/user/.cashu
|
|
||||||
Wallet: wallet
|
|
||||||
Mint URL: https://8333.space:3338
|
|
||||||
```
|
|
||||||
|
|
||||||
#### Check balance
|
#### Check balance
|
||||||
```bash
|
```bash
|
||||||
@@ -159,21 +151,11 @@ You should see the encoded token. Copy the token and send it to another user suc
|
|||||||
cashuAeyJwcm9vZnMiOiBbey...
|
cashuAeyJwcm9vZnMiOiBbey...
|
||||||
```
|
```
|
||||||
|
|
||||||
You can now see that your available balance has dropped by the amount that you reserved for sending if you enter `cashu balance`:
|
|
||||||
```bash
|
|
||||||
Balance: 420 sat
|
|
||||||
```
|
|
||||||
|
|
||||||
#### Receive tokens
|
#### Receive tokens
|
||||||
To receive tokens, another user enters:
|
To receive tokens, another user enters:
|
||||||
```bash
|
```bash
|
||||||
cashu receive cashuAeyJwcm9vZnMiOiBbey...
|
cashu receive cashuAeyJwcm9vZnMiOiBbey...
|
||||||
```
|
```
|
||||||
You should see the balance increase:
|
|
||||||
```bash
|
|
||||||
Balance: 0 sat
|
|
||||||
Balance: 69 sat
|
|
||||||
```
|
|
||||||
|
|
||||||
# Starting the wallet API daemon
|
# Starting the wallet API daemon
|
||||||
Nutshell wallet can be used in daemon mode that can be controlled through a REST API:
|
Nutshell wallet can be used in daemon mode that can be controlled through a REST API:
|
||||||
|
|||||||
@@ -532,6 +532,9 @@ class TokenV3(BaseModel):
|
|||||||
def get_keysets(self):
|
def get_keysets(self):
|
||||||
return list(set([p.id for p in self.get_proofs()]))
|
return list(set([p.id for p in self.get_proofs()]))
|
||||||
|
|
||||||
|
def get_mints(self):
|
||||||
|
return list(set([t.mint for t in self.token if t.mint]))
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def deserialize(cls, tokenv3_serialized: str) -> "TokenV3":
|
def deserialize(cls, tokenv3_serialized: str) -> "TokenV3":
|
||||||
"""
|
"""
|
||||||
@@ -542,6 +545,9 @@ class TokenV3(BaseModel):
|
|||||||
f"Token prefix not valid. Expected {prefix}."
|
f"Token prefix not valid. Expected {prefix}."
|
||||||
)
|
)
|
||||||
token_base64 = tokenv3_serialized[len(prefix) :]
|
token_base64 = tokenv3_serialized[len(prefix) :]
|
||||||
|
# if base64 string is not a multiple of 4, pad it with "="
|
||||||
|
token_base64 += "=" * (4 - len(token_base64) % 4)
|
||||||
|
|
||||||
token = json.loads(base64.urlsafe_b64decode(token_base64))
|
token = json.loads(base64.urlsafe_b64decode(token_base64))
|
||||||
return cls.parse_obj(token)
|
return cls.parse_obj(token)
|
||||||
|
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ from pydantic import BaseSettings, Extra, Field
|
|||||||
|
|
||||||
env = Env()
|
env = Env()
|
||||||
|
|
||||||
VERSION = "0.14.0"
|
VERSION = "0.14.1"
|
||||||
|
|
||||||
|
|
||||||
def find_env_file():
|
def find_env_file():
|
||||||
|
|||||||
@@ -122,7 +122,7 @@ class NostrClient:
|
|||||||
message = json.dumps(request)
|
message = json.dumps(request)
|
||||||
self.relay_manager.publish_message(message)
|
self.relay_manager.publish_message(message)
|
||||||
|
|
||||||
while True:
|
while any([r.connected for r in self.relay_manager.relays.values()]):
|
||||||
while self.relay_manager.message_pool.has_events():
|
while self.relay_manager.message_pool.has_events():
|
||||||
event_msg = self.relay_manager.message_pool.get_event()
|
event_msg = self.relay_manager.message_pool.get_event()
|
||||||
if "?iv=" in event_msg.event.content:
|
if "?iv=" in event_msg.event.content:
|
||||||
@@ -143,7 +143,7 @@ class NostrClient:
|
|||||||
time.sleep(0.1)
|
time.sleep(0.1)
|
||||||
|
|
||||||
def subscribe(self, callback_func=None):
|
def subscribe(self, callback_func=None):
|
||||||
while True:
|
while any([r.connected for r in self.relay_manager.relays.values()]):
|
||||||
while self.relay_manager.message_pool.has_events():
|
while self.relay_manager.message_pool.has_events():
|
||||||
event_msg = self.relay_manager.message_pool.get_event()
|
event_msg = self.relay_manager.message_pool.get_event()
|
||||||
if callback_func:
|
if callback_func:
|
||||||
|
|||||||
@@ -107,16 +107,13 @@ def deserialize_token_from_string(token: str) -> TokenV3:
|
|||||||
except Exception:
|
except Exception:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
# ----- receive token -----
|
if token.startswith("cashu"):
|
||||||
|
tokenObj = TokenV3.deserialize(token)
|
||||||
|
assert len(tokenObj.token), Exception("no proofs in token")
|
||||||
|
assert len(tokenObj.token[0].proofs), Exception("no proofs in token")
|
||||||
|
return tokenObj
|
||||||
|
|
||||||
# deserialize token
|
raise Exception("Invalid token")
|
||||||
# dtoken = json.loads(base64.urlsafe_b64decode(token))
|
|
||||||
tokenObj = TokenV3.deserialize(token)
|
|
||||||
|
|
||||||
# tokenObj = TokenV2.parse_obj(dtoken)
|
|
||||||
assert len(tokenObj.token), Exception("no proofs in token")
|
|
||||||
assert len(tokenObj.token[0].proofs), Exception("no proofs in token")
|
|
||||||
return tokenObj
|
|
||||||
|
|
||||||
|
|
||||||
async def receive(
|
async def receive(
|
||||||
|
|||||||
@@ -139,24 +139,24 @@ async def m007_nostr(db: Database):
|
|||||||
"""
|
"""
|
||||||
Stores timestamps of nostr operations.
|
Stores timestamps of nostr operations.
|
||||||
"""
|
"""
|
||||||
# async with db.connect() as conn:
|
async with db.connect() as conn:
|
||||||
# await conn.execute("""
|
await conn.execute("""
|
||||||
# CREATE TABLE IF NOT EXISTS nostr (
|
CREATE TABLE IF NOT EXISTS nostr (
|
||||||
# type TEXT NOT NULL,
|
type TEXT NOT NULL,
|
||||||
# last TIMESTAMP DEFAULT NULL
|
last TIMESTAMP DEFAULT NULL
|
||||||
# )
|
)
|
||||||
# """)
|
""")
|
||||||
# await conn.execute(
|
await conn.execute(
|
||||||
# """
|
"""
|
||||||
# INSERT INTO nostr
|
INSERT INTO nostr
|
||||||
# (type, last)
|
(type, last)
|
||||||
# VALUES (?, ?)
|
VALUES (?, ?)
|
||||||
# """,
|
""",
|
||||||
# (
|
(
|
||||||
# "dm",
|
"dm",
|
||||||
# None,
|
None,
|
||||||
# ),
|
),
|
||||||
# )
|
)
|
||||||
|
|
||||||
|
|
||||||
async def m008_keysets_add_public_keys(db: Database):
|
async def m008_keysets_add_public_keys(db: Database):
|
||||||
|
|||||||
@@ -1,10 +1,13 @@
|
|||||||
import asyncio
|
import asyncio
|
||||||
|
import datetime
|
||||||
import threading
|
import threading
|
||||||
|
|
||||||
import click
|
import click
|
||||||
from httpx import ConnectError
|
from httpx import ConnectError
|
||||||
from loguru import logger
|
from loguru import logger
|
||||||
|
|
||||||
|
from cashu.core.base import TokenV3
|
||||||
|
|
||||||
from ..core.settings import settings
|
from ..core.settings import settings
|
||||||
from ..nostr.client.client import NostrClient
|
from ..nostr.client.client import NostrClient
|
||||||
from ..nostr.event import Event
|
from ..nostr.event import Event
|
||||||
@@ -97,7 +100,7 @@ async def send_nostr(
|
|||||||
|
|
||||||
async def receive_nostr(
|
async def receive_nostr(
|
||||||
wallet: Wallet,
|
wallet: Wallet,
|
||||||
):
|
) -> NostrClient:
|
||||||
if settings.nostr_private_key is None:
|
if settings.nostr_private_key is None:
|
||||||
print(
|
print(
|
||||||
"Warning: No nostr private key set! You don't have NOSTR_PRIVATE_KEY set in"
|
"Warning: No nostr private key set! You don't have NOSTR_PRIVATE_KEY set in"
|
||||||
@@ -113,18 +116,28 @@ async def receive_nostr(
|
|||||||
await asyncio.sleep(2)
|
await asyncio.sleep(2)
|
||||||
|
|
||||||
def get_token_callback(event: Event, decrypted_content: str):
|
def get_token_callback(event: Event, decrypted_content: str):
|
||||||
|
date_str = datetime.datetime.fromtimestamp(event.created_at).strftime(
|
||||||
|
"%Y-%m-%d %H:%M:%S"
|
||||||
|
)
|
||||||
logger.debug(
|
logger.debug(
|
||||||
f"From {event.public_key[:3]}..{event.public_key[-3:]}: {decrypted_content}"
|
f"From {event.public_key[:3]}..{event.public_key[-3:]} on {date_str}:"
|
||||||
|
f" {decrypted_content}"
|
||||||
)
|
)
|
||||||
# split the content into words
|
# split the content into words
|
||||||
words = decrypted_content.split(" ")
|
words = decrypted_content.split(" ")
|
||||||
for w in words:
|
for w in words:
|
||||||
try:
|
try:
|
||||||
logger.trace(
|
logger.trace(
|
||||||
f"Nostr: setting last check timestamp to {event.created_at}"
|
"Nostr: setting last check timestamp to"
|
||||||
|
f" {event.created_at} ({date_str})"
|
||||||
)
|
)
|
||||||
# call the receive method
|
# call the receive method
|
||||||
tokenObj = deserialize_token_from_string(w)
|
tokenObj: TokenV3 = deserialize_token_from_string(w)
|
||||||
|
print(
|
||||||
|
f"Receiving {tokenObj.get_amount()} sat on mint"
|
||||||
|
f" {tokenObj.get_mints()[0]} from nostr user {event.public_key} at"
|
||||||
|
f" {date_str}"
|
||||||
|
)
|
||||||
asyncio.run(
|
asyncio.run(
|
||||||
receive(
|
receive(
|
||||||
wallet,
|
wallet,
|
||||||
@@ -143,8 +156,11 @@ async def receive_nostr(
|
|||||||
|
|
||||||
# determine timestamp of last check so we don't scan all historical DMs
|
# determine timestamp of last check so we don't scan all historical DMs
|
||||||
last_check = await get_nostr_last_check_timestamp(db=wallet.db)
|
last_check = await get_nostr_last_check_timestamp(db=wallet.db)
|
||||||
logger.debug(f"Last check: {last_check}")
|
|
||||||
if last_check:
|
if last_check:
|
||||||
|
date_str = datetime.datetime.fromtimestamp(last_check).strftime(
|
||||||
|
"%Y-%m-%d %H:%M:%S"
|
||||||
|
)
|
||||||
|
logger.debug(f"Last check: {date_str}")
|
||||||
last_check -= 60 * 60 # 1 hour tolerance
|
last_check -= 60 * 60 # 1 hour tolerance
|
||||||
|
|
||||||
logger.debug("Starting Nostr DM thread")
|
logger.debug("Starting Nostr DM thread")
|
||||||
@@ -154,3 +170,4 @@ async def receive_nostr(
|
|||||||
name="Nostr DM",
|
name="Nostr DM",
|
||||||
)
|
)
|
||||||
t.start()
|
t.start()
|
||||||
|
return client
|
||||||
|
|||||||
@@ -94,7 +94,7 @@ def async_set_httpx_client(func):
|
|||||||
proxies=proxies_dict, # type: ignore
|
proxies=proxies_dict, # type: ignore
|
||||||
headers=headers_dict,
|
headers=headers_dict,
|
||||||
base_url=self.url,
|
base_url=self.url,
|
||||||
timeout=None if settings.debug else 5,
|
timeout=5,
|
||||||
)
|
)
|
||||||
return await func(self, *args, **kwargs)
|
return await func(self, *args, **kwargs)
|
||||||
|
|
||||||
@@ -198,9 +198,9 @@ class LedgerAPI(object):
|
|||||||
|
|
||||||
if keyset_id and keyset_id != keyset.id:
|
if keyset_id and keyset_id != keyset.id:
|
||||||
# NOTE: Because of the upcoming change of how to calculate keyset ids
|
# NOTE: Because of the upcoming change of how to calculate keyset ids
|
||||||
# with version 0.14.0, we overwrite the calculated keyset id with the
|
# with version 0.15.0, we overwrite the calculated keyset id with the
|
||||||
# requested one. This is a temporary fix and should be removed once all
|
# requested one. This is a temporary fix and should be removed once all
|
||||||
# ecash is transitioned to 0.14.0.
|
# ecash is transitioned to 0.15.0.
|
||||||
logger.debug(
|
logger.debug(
|
||||||
f"Keyset ID mismatch: {keyset_id} != {keyset.id}. This can happen due"
|
f"Keyset ID mismatch: {keyset_id} != {keyset.id}. This can happen due"
|
||||||
" to a version upgrade."
|
" to a version upgrade."
|
||||||
@@ -732,9 +732,7 @@ class Wallet(LedgerAPI, WalletP2PK, WalletHTLC, WalletSecrets):
|
|||||||
proofs (List[Proof]): Proofs to be redeemed.
|
proofs (List[Proof]): Proofs to be redeemed.
|
||||||
"""
|
"""
|
||||||
# verify DLEQ of incoming proofs
|
# verify DLEQ of incoming proofs
|
||||||
logger.debug("Verifying DLEQ of incoming proofs.")
|
|
||||||
self.verify_proofs_dleq(proofs)
|
self.verify_proofs_dleq(proofs)
|
||||||
logger.debug("DLEQ verified.")
|
|
||||||
return await self.split(proofs, sum_proofs(proofs))
|
return await self.split(proofs, sum_proofs(proofs))
|
||||||
|
|
||||||
async def split(
|
async def split(
|
||||||
@@ -928,7 +926,8 @@ class Wallet(LedgerAPI, WalletP2PK, WalletHTLC, WalletSecrets):
|
|||||||
):
|
):
|
||||||
raise Exception("DLEQ proof invalid.")
|
raise Exception("DLEQ proof invalid.")
|
||||||
else:
|
else:
|
||||||
logger.debug("DLEQ proof valid.")
|
logger.trace("DLEQ proof valid.")
|
||||||
|
logger.debug("Verified incoming DLEQ proofs.")
|
||||||
|
|
||||||
async def _construct_proofs(
|
async def _construct_proofs(
|
||||||
self,
|
self,
|
||||||
|
|||||||
2
setup.py
2
setup.py
@@ -13,7 +13,7 @@ entry_points = {"console_scripts": ["cashu = cashu.wallet.cli.cli:cli"]}
|
|||||||
|
|
||||||
setuptools.setup(
|
setuptools.setup(
|
||||||
name="cashu",
|
name="cashu",
|
||||||
version="0.14.0",
|
version="0.14.1",
|
||||||
description="Ecash wallet and mint for Bitcoin Lightning",
|
description="Ecash wallet and mint for Bitcoin Lightning",
|
||||||
long_description=long_description,
|
long_description=long_description,
|
||||||
long_description_content_type="text/markdown",
|
long_description_content_type="text/markdown",
|
||||||
|
|||||||
Reference in New Issue
Block a user