189 lines
6.4 KiB
Python
189 lines
6.4 KiB
Python
|
# 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())
|