import os,sys, subprocess from tinyrpc.transports import ServerTransport from tinyrpc.protocols.jsonrpc import JSONRPCProtocol from tinyrpc.dispatch import public,RPCDispatcher from tinyrpc.server import RPCServer """ This is a POC example of how to write a custom UI for Clef. The UI starts the clef process with the '--stdio-ui' option, and communicates with clef using standard input / output. The standard input/output is a relatively secure way to communicate, as it does not require opening any ports or IPC files. Needless to say, it does not protect against memory inspection mechanisms where an attacker can access process memory.""" try: import urllib.parse as urlparse except ImportError: import urllib as urlparse class StdIOTransport(ServerTransport): """ Uses std input/output for RPC """ def receive_message(self): return None, urlparse.unquote(sys.stdin.readline()) def send_reply(self, context, reply): print(reply) class PipeTransport(ServerTransport): """ Uses std a pipe for RPC """ def __init__(self,input, output): self.input = input self.output = output def receive_message(self): data = self.input.readline() print(">> {}".format( data)) return None, urlparse.unquote(data) def send_reply(self, context, reply): print("<< {}".format( reply)) self.output.write(reply) self.output.write("\n") class StdIOHandler(): def __init__(self): pass @public def ApproveTx(self,req): """ Example request: { "jsonrpc": "2.0", "method": "ApproveTx", "params": [{ "transaction": { "to": "0xae967917c465db8578ca9024c205720b1a3651A9", "gas": "0x333", "gasPrice": "0x123", "value": "0x10", "data": "0xd7a5865800000000000000000000000000000000000000000000000000000000000000ff", "nonce": "0x0" }, "from": "0xAe967917c465db8578ca9024c205720b1a3651A9", "call_info": "Warning! Could not validate ABI-data against calldata\nSupplied ABI spec does not contain method signature in data: 0xd7a58658", "meta": { "remote": "127.0.0.1:34572", "local": "localhost:8550", "scheme": "HTTP/1.1" } }], "id": 1 } :param transaction: transaction info :param call_info: info abou the call, e.g. if ABI info could not be :param meta: metadata about the request, e.g. where the call comes from :return: """ transaction = req.get('transaction') _from = req.get('from') call_info = req.get('call_info') meta = req.get('meta') return { "approved" : False, #"transaction" : transaction, # "from" : _from, # "password" : None, } @public def ApproveSignData(self, req): """ Example request """ return {"approved": False, "password" : None} @public def ApproveExport(self, req): """ Example request """ return {"approved" : False} @public def ApproveImport(self, req): """ Example request """ return { "approved" : False, "old_password": "", "new_password": ""} @public def ApproveListing(self, req): """ Example request """ return {'accounts': []} @public def ApproveNewAccount(self, req): """ Example request :return: """ return {"approved": False, #"password": "" } @public def ShowError(self,message = {}): """ Example request: {"jsonrpc":"2.0","method":"ShowInfo","params":{"message":"Testing 'ShowError'"},"id":1} :param message: to show :return: nothing """ if 'text' in message.keys(): sys.stderr.write("Error: {}\n".format( message['text'])) return @public def ShowInfo(self,message = {}): """ Example request {"jsonrpc":"2.0","method":"ShowInfo","params":{"message":"Testing 'ShowInfo'"},"id":0} :param message: to display :return:nothing """ if 'text' in message.keys(): sys.stdout.write("Error: {}\n".format( message['text'])) return def main(args): cmd = ["./clef", "--stdio-ui"] if len(args) > 0 and args[0] == "test": cmd.extend(["--stdio-ui-test"]) print("cmd: {}".format(" ".join(cmd))) dispatcher = RPCDispatcher() dispatcher.register_instance(StdIOHandler(), '') # line buffered p = subprocess.Popen(cmd, bufsize=1, universal_newlines=True, stdin=subprocess.PIPE, stdout=subprocess.PIPE) rpc_server = RPCServer( PipeTransport(p.stdout, p.stdin), JSONRPCProtocol(), dispatcher ) rpc_server.serve_forever() if __name__ == '__main__': main(sys.argv[1:])