You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

189 lines
6.4 KiB

# SPDX-License-Identifier: BSD-3-Clause
# Copyright (c) 2021, The Wownero Project.
import logging
from typing import Awaitable, Callable, Dict, Optional, Union, List
import asyncio
import json
import aiohttp
from wowlet_ws_client import WowletState, decorator_parametrized, WALLET_OPERATIONAL
class WowletWSClient:
def __init__(self, host: str = "127.0.0.1", port: int = 42069, debug: bool = False):
self.host = host
self.port = port
self.ses = aiohttp.ClientSession()
self.debug = debug
self._ws = None
self._state = WowletState.IDLE
self._cmd_callbacks: Dict[str, Callable] = {}
self._loop_task: Callable = None
self.wallets: List[str] = []
self.addresses: List[str] = []
self.address_book: List[Dict[str: str]] = []
self.unlocked_balance: float
self.balance: float
async def open_wallet(self, path: str, password: str = ""):
"""
Opens a wallet
:param path: absolute path to wallet file
:param password: wallet password
:return:
"""
if self._state != WowletState.IDLE:
raise Exception("You may only open wallets in IDLE "
"state. Close the current wallet first.")
await self.sendMessage("openWallet", {
"path": path,
"password": password
})
async def close_wallet(self):
"""Close currently opened wallet"""
if self._state not in WALLET_OPERATIONAL:
raise Exception("There is no opened wallet to close.")
await self.sendMessage("closeWallet", {})
async def address_list(self, account_index: int = 0, address_index: int = 0, limit: int = 50, offset: int = 0):
if self._state not in WALLET_OPERATIONAL:
raise Exception("There is no opened wallet to close.")
await self.sendMessage("addressList", {
"accountIndex": account_index,
"addressIndex": address_index,
"limit": limit,
"offset": offset
})
async def send_transaction(self, address: str, amount: float, description: str = "", _all: bool = False):
"""
Send someone some monies.
:param address: address
:param amount: amount
:param description: optional description
:param _all: send all available funds (use with caution)
:return:
"""
if amount <= 0:
raise Exception("y u send 0")
if self._state not in WALLET_OPERATIONAL:
raise Exception("There is no opened wallet to close.")
await self.sendMessage("sendTransaction", {
"address": address,
"description": description,
"amount": amount,
"all": _all
})
async def create_wallet(self, name: str, password: str = "", path: str = None):
"""
Automatically create a new wallet. Comes up with it's own
mnemonic seed and stuffz.
:param name: name of the new wallet
:param password: (optional) password for the wallet
:param path: (optional) absolute path *to a directory*, default is
Wownero wallet directory.
:return:
"""
if self._state != WowletState.IDLE:
raise Exception("Please close the currently opened wallet first.")
await self.sendMessage("createWallet", {
"name": name,
"path": path,
"password": password
})
async def get_transaction_history(self):
"""Get transaction history in its entirety."""
# if self._state != WALLET_OPERATIONAL:
# raise Exception("The wallet is currently busy doing something else.")
await self.sendMessage("transactionHistory", {})
async def get_address_book(self):
"""Get all address book entries."""
# if self._state != WALLET_OPERATIONAL:
# raise Exception("The wallet is currently busy doing something else.")
await self.sendMessage("addressBook", {})
async def address_book_insert(self, name: str, address: str):
if not name or not address:
raise Exception("name or address empty")
await self.sendMessage("addressBookItemAdd", {
"name": name,
"address": address
})
async def address_book_remove(self, address: str):
"""Remove by address"""
if not address:
raise Exception("address empty")
await self.sendMessage("addressBookItemRemove", {
"address": address
})
async def create_wallet_advanced(self):
pass
async def loop(self):
while True:
buffer = await self._ws.receive()
msg = json.loads(buffer.data)
if "cmd" not in msg:
logging.error("invalid message received")
continue
cmd = msg['cmd']
data = msg.get("data")
if self.debug:
print(msg['cmd'])
for k, v in WowletState.__members__.items():
state = WowletState[k]
if cmd in v.value and self._state != state:
if self.debug:
print(f"Changing state to {k}")
self._state = state
if cmd == "walletList":
self.wallets = data
elif cmd == "addressBook":
self.address_book = data
elif cmd == "addressList":
if data.get("accountIndex", 0) == 0 and \
data.get("addressIndex") == 0 and \
data.get("offset") == 0:
self.addresses = data
elif cmd == "balanceUpdated":
self.balance = data.get('balance', 0)
self.balance_unlocked = data.get('spendable', 0)
if cmd in self._cmd_callbacks:
await self._cmd_callbacks[cmd](data)
@decorator_parametrized
def event(self, view_func, cmd_name):
self._cmd_callbacks[cmd_name] = view_func
async def connect(self):
self._ws = await self.ses.ws_connect(f"ws://{self.host}:{self.port}")
self._loop_task = asyncio.create_task(self.loop())
async def send(self, data: bytes) -> None:
return await self._ws.send_bytes(data)
async def sendMessage(self, command: str, data: dict) -> None:
return await self.send(json.dumps({
"cmd": command,
"data": data
}).encode())