diff --git a/Makefile.chainsplit b/Makefile.chainsplit index 25dbd67..62fe5d4 100755 --- a/Makefile.chainsplit +++ b/Makefile.chainsplit @@ -15,7 +15,7 @@ # limitations under the License. #******************************************************************************* -APPNAME = ETC/ETH Split +APPNAME = "ETC/ETH Split" TARGET_ID = 0x31100002 #Nano S #TARGET_ID = 0x31000002 #Blue APP_LOAD_PARAMS=--appFlags 0x40 --path "44'/60'" --path "44'/61'" --curve secp256k1 @@ -126,10 +126,7 @@ log = $(if $(strip $(VERBOSE)),$1,@$1) default: prepare bin/$(PROG) load: - python -m ledgerblue.loadApp --targetId $(TARGET_ID) --fileName bin/$(PROG).hex --appName $(APPNAME) --icon `python $(BOLOS_SDK)/icon.py 16 16 icon.gif hexbitmaponly` $(APP_LOAD_PARAMS) - -load_release: - python -m ledgerblue.loadApp --targetId $(TARGET_ID) --fileName bin/$(PROG).hex --appName $(APPNAME) --icon `python $(BOLOS_SDK)/icon.py 16 16 icon.gif hexbitmaponly` $(APP_LOAD_PARAMS) --signature 3044022065e5bcf6519ea12ff991e429cc85bf7ecc789bd1a7a141d5e2dc358e5544c6170220726801803361b3296b83a3d14be8afc3374bda215b4dadd3d67bc3518e1f78bc + python -m ledgerblue.loadApp --targetId $(TARGET_ID) --fileName bin/$(PROG).hex --appName $(APPNAME) --icon `python $(BOLOS_SDK)/icon.py 16 16 icon_split.gif hexbitmaponly` $(APP_LOAD_PARAMS) delete: python -m ledgerblue.deleteApp --targetId $(TARGET_ID) --appName $(APPNAME) diff --git a/icon_split.gif b/icon_split.gif new file mode 100644 index 0000000..0ab4001 Binary files /dev/null and b/icon_split.gif differ diff --git a/splitEther.py b/splitEther.py index d693637..62fc218 100644 --- a/splitEther.py +++ b/splitEther.py @@ -21,6 +21,8 @@ from ledgerblue.comm import getDongle from ledgerblue.commException import CommException import argparse import struct +import requests +import json from decimal import Decimal from ethereum.transactions import Transaction, UnsignedTransaction from rlp import encode @@ -32,6 +34,17 @@ from ethereum import utils SPLIT_CONTRACT_FUNCTION = decode_hex("9c709343") SPLIT_CONTRACT_ADDRESS = "5dc8108fc79018113a58328f5283b376b83922ef" +def rpc_call(http, url, methodDebug): + req = http.get(url) + if req.status_code == 200: + result = json.loads(req.text) + if 'error' in result: + raise Exception("Server error " + methodDebug + " " + result['error']['message']) + return result + else: + raise Exception("Server error - " + methodDebug + " got status " + req.status) + + def parse_bip32_path(path): if len(path) == 0: return "" @@ -46,13 +59,15 @@ def parse_bip32_path(path): return result parser = argparse.ArgumentParser() -parser.add_argument('--nonce', help="Nonce associated to the account") -parser.add_argument('--gasprice', help="Network gas price") +parser.add_argument('--nonce', help="Nonce associated to the account (default : query account)") +parser.add_argument('--gasprice', help="Network gas price (default : query network)") parser.add_argument('--startgas', help="startgas", default='80000') -parser.add_argument('--amount', help="Amount to send in ether") -parser.add_argument('--to', help="BIP 32 destination path") +parser.add_argument('--startgas-delta', help="difference applied to startgas if gasprice is automatically fetched", default='1000') +parser.add_argument('--amount', help="Amount to send in ether (default : query amount, use maximum)") +parser.add_argument('--to', help="BIP 32 destination path (default : default ETC path)") parser.add_argument('--split-to-eth', help="Split to the ETH chain (default : spit to ETC chain)", action='store_true') -parser.add_argument('--path', help="BIP 32 path to sign with") +parser.add_argument('--path', help="BIP 32 path to sign with (default : default ETH path)") +parser.add_argument('--broadcast', help="Broadcast generated transaction (default : false)", action='store_true') args = parser.parse_args() if args.path == None: @@ -79,6 +94,43 @@ result = dongle.exchange(bytes(apdu)) publicKey = str(result[1 : 1 + result[0]]) encodedPublicKey = utils.sha3(publicKey[1:])[12:] +if (args.nonce == None) or (args.amount == None): + donglePathFrom = parse_bip32_path(args.path) + apdu = "e0020000".decode('hex') + chr(len(donglePathFrom) + 1) + chr(len(donglePathFrom) / 4) + donglePathFrom + result = dongle.exchange(bytes(apdu)) + publicKeyFrom = str(result[1 : 1 + result[0]]) + encodedPublicKeyFrom = utils.sha3(publicKeyFrom[1:])[12:] + + +http = None +if (args.gasprice == None) or (args.nonce == None) or (args.amount == None) or (args.broadcast): + http = requests.session() + +if args.gasprice == None: + print "Fetching gas price" + result = rpc_call(http, "https://api.etherscan.io/api?module=proxy&action=eth_gasPrice", "gasPrice") + args.gasprice = int(result['result'], 16) + print "Gas price " + str(args.gasprice) + +if args.nonce == None: + print "Fetching nonce" + result = rpc_call(http, "https://api.etherscan.io/api?module=proxy&action=eth_getTransactionCount&address=0x" + encodedPublicKeyFrom.encode('hex'), "getTransactionCount") + args.nonce = int(result['result'], 16) + print "Nonce for 0x" + encodedPublicKeyFrom.encode('hex') + " " + str(args.nonce) + +if args.amount == None: + print "Fetching balance" + result = rpc_call(http, "https://api.etherscan.io/api?module=account&action=balance&address=0x" + encodedPublicKeyFrom.encode('hex'), "getBalance") + amount = int(result['result']) + print "Balance for " + encodedPublicKeyFrom.encode('hex') + " " + str(amount) + amount -= (int(args.startgas) - int(args.startgas_delta)) * int(args.gasprice) + if amount < 0: + raise Exception("Remaining amount too small to pay for contract fees") +else: + amount = Decimal(args.amount) * 10**18 + +print "Amount transferred " + str((Decimal(amount) / 10 ** 18)) + " to " + encodedPublicKey.encode('hex') + txData = SPLIT_CONTRACT_FUNCTION txData += "\x00" * 31 if (args.split_to_eth): @@ -88,8 +140,6 @@ else: txData += "\x00" * 12 txData += encodedPublicKey -amount = Decimal(args.amount) * 10**18 - tx = Transaction( nonce=int(args.nonce), gasprice=int(args.gasprice), @@ -111,5 +161,10 @@ r = int(str(result[1:1 + 32]).encode('hex'), 16) s = int(str(result[1 + 32: 1 + 32 + 32]).encode('hex'), 16) tx = Transaction(tx.nonce, tx.gasprice, tx.startgas, tx.to, tx.value, tx.data, v, r, s) +serializedTx = encode(tx) -print "Signed transaction " + encode_hex(encode(tx)) +print "Signed transaction " + serializedTx.encode('hex') + +if (args.broadcast): + result = rpc_call(http, "https://api.etherscan.io/api?module=proxy&action=eth_sendRawTransaction&hex=0x" + serializedTx.encode('hex'), "sendRawTransaction") + print result