diff --git a/.github/workflows/checks.yml b/.github/workflows/checks.yml index f89c7edb..7e2cc326 100644 --- a/.github/workflows/checks.yml +++ b/.github/workflows/checks.yml @@ -18,7 +18,7 @@ jobs: uses: actions/cache@v1 with: path: ~/.local/share/virtualenvs - key: '${{ runner.os }}-pipenv-${{ hashFiles(''**/Pipfile.lock'') }}' + key: '${{ runner.os }}-pipenv-v2-${{ hashFiles(''**/Pipfile.lock'') }}' - name: Install dependencies if: steps.cache-pipenv.outputs.cache-hit != 'true' run: pipenv install --deploy --dev diff --git a/mc_util/__init__.py b/mc_util/__init__.py index 46bb159b..3f9f24fe 100644 --- a/mc_util/__init__.py +++ b/mc_util/__init__.py @@ -2,7 +2,7 @@ # Copyright (c) 2021 MobileCoin Inc. # Copyright (c) 2021 The Forest Team from decimal import Decimal -from typing import Union +from typing import Union, Optional import zlib import base64 @@ -60,22 +60,28 @@ def b64_public_address_to_b58_wrapper(b64_string: str) -> str: # > new messages without breaking backwards compatibility # this can be used to import a gift card's entropy into full-service -def b58_wrapper_to_protobuf(b58_string: str) -> printable_pb2.PrintableWrapper: +def b58_wrapper_to_protobuf( + b58_string: str, +) -> Optional[printable_pb2.PrintableWrapper]: """Convert a b58-encoded PrintableWrapper into a protobuf It could be a public address, a gift code, or a payment request""" checksum_and_wrapper_bytes = base58.b58decode(b58_string) wrapper_bytes = checksum_and_wrapper_bytes[4:] + if add_checksum_and_b58(wrapper_bytes) != b58_string: + return None wrapper = printable_pb2.PrintableWrapper() wrapper.ParseFromString(wrapper_bytes) return wrapper -def b58_wrapper_to_b64_public_address(b58_string: str) -> str: +def b58_wrapper_to_b64_public_address(b58_string: str) -> Optional[str]: """Convert a b58-encoded PrintableWrapper address into a b64-encoded PublicAddress protobuf""" wrapper = b58_wrapper_to_protobuf(b58_string) - public_address = wrapper.public_address - public_address_bytes = public_address.SerializeToString() - return base64.b64encode(public_address_bytes).decode("utf-8") + if wrapper: + public_address = wrapper.public_address + public_address_bytes = public_address.SerializeToString() + return base64.b64encode(public_address_bytes).decode("utf-8") + return None def add_checksum_and_b58(wrapper_bytes: bytes) -> str: diff --git a/mobfriend/mobfriend.py b/mobfriend/mobfriend.py index a279ddc1..d89b0341 100755 --- a/mobfriend/mobfriend.py +++ b/mobfriend/mobfriend.py @@ -175,10 +175,20 @@ async def do_check(self, msg: Message) -> Response: return await self.do_check_balance(msg) return status.get("result") - @hide - async def do_create_payment_request(self, msg: Message) -> Response: + async def do_show_details(self, msg: Message) -> Response: + """ + /show_details [base58 code] + Returns detailed information about a base58 code.""" + if msg.arg1: + details = mc_util.b58_wrapper_to_protobuf(msg.arg1) + if details: + return str(details) + return "Sorry, the provided code has an invalid checksum." + return "Please provide a base58 code!" + + async def do_payme(self, msg: Message) -> Response: """ - /create_payment_request [amount] [memo] + /payme [amount] [memo] Creates a payment request (as QR code and b58 code to copy and paste.) For example, /payme 1.0 "Pay me a MOB!" @@ -190,8 +200,15 @@ async def do_create_payment_request(self, msg: Message) -> Response: if not address: return "Unable to retrieve your MobileCoin address!" payload = mc_util.printable_pb2.PrintableWrapper() - address_proto = mc_util.b58_wrapper_to_protobuf(address).public_address - payload.payment_request.public_address.CopyFrom(address_proto) + address_proto = mc_util.b58_wrapper_to_protobuf(address) + if address_proto: + payload.payment_request.public_address.CopyFrom( + address_proto.public_address + ) + else: + return ( + "Sorry, could not parse a valid MobileCoin address from your profile!" + ) if msg.tokens and not ( isinstance(msg.tokens[0], str) and len(msg.tokens) > 0 @@ -214,7 +231,41 @@ async def do_create_payment_request(self, msg: Message) -> Response: ) return payment_request_b58 - do_payme = hide(do_create_payment_request) + async def do_paywallet(self, msg: Message) -> Response: + """ + /paywallet [b58address] [amount] [memo] + + Creates a payment request (as QR code and b58 code to copy and paste.) + For example, /paywallet [address] 1.0 "Pay me a MOB!" + will create a payment request with + * the destination [b58address], + * a 1MOB value, + * the memo "Pay me a MOB!" + """ + address = msg.arg1 + amount = msg.arg2 + memo = msg.arg3 or "" + if not address: + return "Please provide your b58 address as the first argument!" + payload = mc_util.printable_pb2.PrintableWrapper() + address_proto = mc_util.b58_wrapper_to_protobuf(address) + if not address_proto: + return "Sorry, could not find a valid address!" + payload.payment_request.public_address.CopyFrom(address_proto.public_address) + if not amount or not amount.replace(".", "0", 1).isnumeric(): + return "Sorry, you need to provide a price (in MOB)!" + payload.payment_request.value = mob2pmob(Decimal(amount)) + payload.payment_request.memo = memo + payment_request_b58 = mc_util.add_checksum_and_b58(payload.SerializeToString()) + pyqrcode.QRCode(payment_request_b58).png( + f"/tmp/{msg.timestamp}.png", scale=5, quiet_zone=10 + ) + await self.send_message( + recipient=msg.source, + attachments=[f"/tmp/{msg.timestamp}.png"], + msg="Scan me in the Mobile Wallet!", + ) + return payment_request_b58 async def do_qr(self, msg: Message) -> Response: """