diff --git a/build_pb.sh b/build_pb.sh index 13def9a..3420fe9 100755 --- a/build_pb.sh +++ b/build_pb.sh @@ -3,7 +3,7 @@ CURDIR=$(pwd) cd "device-protocol" echo "Building with protoc version: $(protoc --version)" -for i in messages messages-ethereum messages-eos messages-nano messages-cosmos messages-ripple messages-binance messages-tendermint messages-thorchain messages-osmosis types ; do +for i in messages messages-ethereum messages-eos messages-nano messages-cosmos messages-ripple messages-binance messages-tendermint messages-thorchain messages-mayachain messages-osmosis types ; do protoc --python_out=$CURDIR/keepkeylib/ -I/usr/include -I. $i.proto i=${i/-/_} sed -i -Ee 's/^import ([^.]+_pb2)/from . import \1/' $CURDIR/keepkeylib/"$i"_pb2.py diff --git a/keepkeyctl b/keepkeyctl index c500d97..fd5672a 100755 --- a/keepkeyctl +++ b/keepkeyctl @@ -422,6 +422,43 @@ class Commands(object): return signed +def mayachain_get_address(self, args): + address_n = self.client.expand_path(args.n) + address = self.client.mayachain_get_address(address_n, args.show_display, args.testnet) + return address + + def mayachain_sign_tx(self, args): + address_n = self.client.expand_path(args.n) + chain_id = args.chain_id + account_number = args.account_number + sequence = args.sequence + with open(args.file, 'r') as f: + unsigned = json.load(f) + + from keepkeylib.mayachain import mayachain_parse_tx, mayachain_append_sig + + parsed = mayachain_parse_tx(unsigned) + + res = self.client.mayachain_sign_tx( + address_n=address_n, + account_number=account_number, + chain_id=chain_id, + fee=int(parsed['fee']), + gas=int(parsed['gas']), + msgs=parsed['msgs'], + memo=parsed['memo'], + sequence=sequence, + testnet=args.testnet + ) + + unsigned['tx']['chain_id'] = chain_id + unsigned['tx']['account_number'] = account_number + unsigned['tx']['sequence'] = sequence + + signed = mayachain_append_sig(unsigned, res.public_key, res.signature) + + return signed + def get_entropy(self, args): return binascii.hexlify(self.client.get_entropy(args.size)) @@ -558,6 +595,8 @@ class Commands(object): cosmos_sign_tx.help = 'Sign Cosmos transaction' thorchain_get_address.help = 'Get THORchain address' thorchain_sign_tx.help = 'Sign THORChain transaction' + mayachain_get_address.help = 'Get MAYAchain address' + mayachain_sign_tx.help = 'Sign MAYAChain transaction' get_entropy.help = 'Get example entropy' get_features.help = 'Retrieve device features and settings' get_public_node.help = 'Get public node of given path' @@ -676,6 +715,21 @@ class Commands(object): (('-t', '--testnet'), {'action': 'store_true', 'default': False}), ) + mayachain_get_address.arguments = ( + (('-n', '-address'), {'type': str, 'help': "BIP-32 path to source address", 'default': "m/44'/931'/0'/0/0"}), + (('-d', '--show-display'), {'action': 'store_true', 'default': False}), + (('-t', '--testnet'), {'action': 'store_true', 'default': False}), + ) + + mayachain_sign_tx.arguments = ( + (('-n', '-address'), {'type': str, 'help': "BIP-32 path to source address", 'default': "m/44'/931'/0'/0/0"}), + (('--chain-id',), {'type': str, 'help': "Chain ID of tendermint node", 'dest': 'chain_id', 'default': 'mayachain'}), + (('--account-number',), {'type': int, 'help': 'MAYAChain account number', 'dest': 'account_number', 'required': True}), + (('--sequence', ), {'type': int, 'help': 'MAYAChain account sequence', 'dest': 'sequence', 'required': True}), + (('-f', '--file'), {'type': str, 'required': True}), + (('-t', '--testnet'), {'action': 'store_true', 'default': False}), + ) + get_entropy.arguments = ( (('size',), {'type': int}), ) diff --git a/keepkeylib/client.py b/keepkeylib/client.py index 7ded0f8..e3eed4f 100644 --- a/keepkeylib/client.py +++ b/keepkeylib/client.py @@ -44,6 +44,7 @@ from . import messages_ripple_pb2 as ripple_proto from . import messages_tendermint_pb2 as tendermint_proto from . import messages_thorchain_pb2 as thorchain_proto +from . import messages_mayachain_pb2 as mayachain_proto from . import types_pb2 as types from . import eos from . import nano @@ -1051,6 +1052,94 @@ def thorchain_sign_tx( return resp + @field('address') + @expect(mayachain_proto.MayachainAddress) + def mayachain_get_address(self, address_n, show_display=False, testnet=False): + return self.call( + mayachain_proto.MayachainGetAddress(address_n=address_n, show_display=show_display, testnet=testnet) + ) + + @session + def mayachain_sign_tx( + self, + address_n, + account_number, + chain_id, + fee, + gas, + msgs, + memo, + sequence, + testnet=None + ): + resp = self.call(mayachain_proto.MayachainSignTx( + address_n=address_n, + account_number=account_number, + chain_id=chain_id, + fee_amount=fee, + gas=gas, + memo=memo, + sequence=sequence, + msg_count=len(msgs), + testnet=testnet + )) + + for msg in msgs: + if not isinstance(resp, mayachain_proto.MayachainMsgRequest): + raise CallException( + "Mayachain.ExpectedMsgRequest", + "Message request expected but not received.", + ) + + if msg['type'] == "mayachain/MsgSend": + if len(msg['value']['amount']) != 1: + raise CallException("Mayachain.MsgSend", "Multiple amounts per send msg not supported") + + denom = msg['value']['amount'][0]['denom'] + if denom != 'rune': + raise CallException("Mayachain.MsgSend", "Unsupported denomination: " + denom) + + resp = self.call(mayachain_proto.MayachainMsgAck( + send=mayachain_proto.MayachainMsgSend( + from_address=msg['value']['from_address'], + to_address=msg['value']['to_address'], + amount=int(msg['value']['amount'][0]['amount']), + address_type=types.SPEND, + ) + )) + + elif msg['type'] == "mayachain/MsgDeposit": + if len(msg['value']['coins']) != 1: + raise CallException("Mayachain.MsgDeposit", "Multiple coins per deposit msg not supported") + + asset = msg['value']['coins'][0]['asset'] + if asset != 'MAYA.CACAO': + raise CallException("Mayachain.MsgDeposit", "Unsupported asset: " + asset) + + resp = self.call(mayachain_proto.MayachainMsgAck( + deposit=mayachain_proto.MayachainMsgDeposit( + asset=asset, + amount=int(msg['value']['coins'][0]['amount']), + memo=msg['value']['memo'], + signer=msg['value']['signer'] + ) + )) + + else: + raise CallException( + "Mayachain.UnknownMsg", + "Mayachain message %s is not yet supported" % (msg['type'],) + ) + + if not isinstance(resp, mayachain_proto.MayachainSignedTx): + raise CallException( + "Mayachain.UnexpectedEndOfOperations", + "Reached end of operations without a signature.", + ) + + return resp + + @field('address') @expect(ripple_proto.RippleAddress) def ripple_get_address(self, address_n, show_display=False): diff --git a/keepkeylib/mapping.py b/keepkeylib/mapping.py index fe1891c..4433864 100644 --- a/keepkeylib/mapping.py +++ b/keepkeylib/mapping.py @@ -8,6 +8,7 @@ from . import messages_binance_pb2 as binance_proto from . import messages_tendermint_pb2 as tendermint_proto from . import messages_thorchain_pb2 as thorchain_proto +from . import messages_mayachain_pb2 as mayachain_proto map_type_to_class = {} map_class_to_type = {} @@ -33,6 +34,8 @@ def build_map(): msg_class = getattr(tendermint_proto, msg_name) elif msg_type.startswith('MessageType_Thorchain'): msg_class = getattr(thorchain_proto, msg_name) + elif msg_type.startswith('MessageType_Mayachain'): + msg_class = getattr(mayachain_proto, msg_name) else: msg_class = getattr(proto, msg_name) diff --git a/tests/test_msg_mayachain_getaddress.py b/tests/test_msg_mayachain_getaddress.py new file mode 100644 index 0000000..a79f0f2 --- /dev/null +++ b/tests/test_msg_mayachain_getaddress.py @@ -0,0 +1,21 @@ +import unittest +import common + +import keepkeylib.messages_pb2 as proto +import keepkeylib.types_pb2 as proto_types +import keepkeylib.messages_mayachain_pb2 as mayachain_proto +from keepkeylib.client import CallException +from keepkeylib.tools import parse_path + +DEFAULT_BIP32_PATH = "m/44h/931h/0h/0/0" + +class TestMsgMayaChainGetAddress(common.KeepKeyTest): + + def test_mayachain_get_address(self): + self.requires_firmware("7.0.2") + self.setup_mnemonic_nopin_nopassphrase() + address = self.client.mayachain_get_address(parse_path(DEFAULT_BIP32_PATH), testnet=True) + self.assertEqual(address, "smaya1ls33ayg26kmltw7jjy55p32ghjna09zp2mf0av") + +if __name__ == '__main__': + unittest.main() diff --git a/tests/test_msg_mayachain_signtx.py b/tests/test_msg_mayachain_signtx.py new file mode 100644 index 0000000..bfa4735 --- /dev/null +++ b/tests/test_msg_mayachain_signtx.py @@ -0,0 +1,306 @@ +import unittest +import common + +from base64 import b64encode +from binascii import hexlify, unhexlify + +import keepkeylib.messages_pb2 as proto +import keepkeylib.types_pb2 as proto_types +from keepkeylib.tools import parse_path + +DEFAULT_BIP32_PATH = "m/44h/931h/0h/0/0" + +def make_send(from_address, to_address, amount): + return { + 'type': 'mayachain/MsgSend', + 'value': { + 'amount': [{ + 'denom': 'cacao', + 'amount': str(amount), + }], + 'from_address': from_address, + 'to_address': to_address, + } + } + +class TestMsgMayaChainSignTx(common.KeepKeyTest): + + def test_mayachain_sign_tx(self): + self.requires_firmware("7.0.2") + self.setup_mnemonic_nopin_nopassphrase() + signature = self.client.mayachain_sign_tx( + address_n=parse_path(DEFAULT_BIP32_PATH), + account_number=92, + chain_id="mayachain", + fee=3000, + gas=100000000, + msgs=[make_send( + "smaya1ls33ayg26kmltw7jjy55p32ghjna09zp2mf0av", + "smaya1jvt443rvhq5h8yrna55yjysvhtju0el705qyh0", + 10000 + )], + memo="foobar", + sequence=3, + testnet = True + ) + self.assertEqual(hexlify(signature.signature), "164ea435b39444fa780e453ffe0d0ca07fa74a44272713a283f6297b951e06dc71575e83a6a5405b324c8bc187c50951f1d46fd58acadf060fdf23980d61488a") + self.assertEqual(hexlify(signature.public_key), "031519713b8b42bdc367112d33132cf14cedf928ac5771d444ba459b9497117ba3") + return + + def test_sign_btc_eth_swap(self): + self.requires_firmware("7.0.2") + self.setup_mnemonic_nopin_nopassphrase() + + inp1 = proto_types.TxInputType(address_n=[0], # 14LmW5k4ssUrtbAB4255zdqv3b4w1TuX9e + # amount=390000, + prev_hash=unhexlify('d5f65ee80147b4bcc70b75e4bbf2d7382021b871bd8867ef8fa525ef50864882'), + prev_index=0, + ) + + out1 = proto_types.TxOutputType(op_return_data=b'SWAP:ETH.ETH:0x41e5560054824ea6b0732e656e3ad64e20e94e45:420', + amount=0, + script_type=proto_types.PAYTOOPRETURN, + ) + + + (signatures, serialized_tx) = self.client.sign_tx('Bitcoin', [inp1, ], [out1, ]) + self.assertEqual(hexlify(serialized_tx), '010000000182488650ef25a58fef6788bd71b8212038d7f2bbe4750bc7bcb44701e85ef6d5000000006b483045022100c1cf12191f0a50398dae21553d14d5c796ff3e2e1c378bce3d0a7d43fa9bdf4402201245f76291db518dd8b496b4406128ca0e07165c64d2fe927161eee17402f9c40121023230848585885f63803a0a8aecdd6538792d5c539215c91698e315bf0253b43dffffffff0100000000000000003d6a3b535741503a4554482e4554483a3078343165353536303035343832346561366230373332653635366533616436346532306539346534353a34323000000000') + + def test_sign_eth_btc_swap(self): + self.requires_firmware("7.1.0") + self.setup_mnemonic_nopin_nopassphrase() + sig_v, sig_r, sig_s = self.client.ethereum_sign_tx( + n=[2147483692,2147483708,2147483648,0,0], + nonce=0x0, + gas_price=0x5FB9ACA00, + gas_limit=0x186A0, + value=0x00, + to=unhexlify('42a5ed456650a09dc10ebc6361a7480fdd61f27b'), + address_type=0, + chain_id=1, + data=unhexlify('1fece7b4' + + '000000000000000000000000345b297ec83add7ff74d2f7933651bffa037d956' + # asgard vault address + '0000000000000000000000000000000000000000000000000000000000000000' + # asset ETH + '000000000000000000000000000000000000000000000065945acd2b867ef000' + # amount + '0000000000000000000000000000000000000000000000000000000000000080' + # offset of memo string from after func sig + '000000000000000000000000000000000000000000000000000000000000003b' + # length of memo string in bytes + # SWAP:BTC.BTC:0x41e5560054824ea6b0732e656e3ad64e20e94e45:420 + '535741503a4254432e4254433a30783431653535363030353438323465613662' + # mayachain transaction memo + '30373332653635366533616436346532306539346534353a3432300000000000') + ) + self.assertEqual(sig_v, 37) + self.assertEqual(hexlify(sig_r), 'da472e9d40fb3c981cebbc6dec70d9d756e5f03aca1ca4259f26dd4c257f8a68') + self.assertEqual(hexlify(sig_s), '025af171f9bd0af71266417f82a72214f349d96ed6505288c1a4032463ef920a') + + + def test_sign_btc_add_liquidity(self): + self.requires_firmware("7.0.2") + self.setup_mnemonic_nopin_nopassphrase() + + inp1 = proto_types.TxInputType(address_n=[0], # 14LmW5k4ssUrtbAB4255zdqv3b4w1TuX9e + # amount=390000, + prev_hash=unhexlify('d5f65ee80147b4bcc70b75e4bbf2d7382021b871bd8867ef8fa525ef50864882'), + prev_index=0, + ) + + out1 = proto_types.TxOutputType(op_return_data=b'ADD:BTC.BTC:mayapub1addwnpepq2ynqt500fag3wyxsjuv7570qxr8rqtpx93hw3cpqaqxtwxesy76udcaxjj:420', + amount=0, + script_type=proto_types.PAYTOOPRETURN, + ) + + + (signatures, serialized_tx) = self.client.sign_tx('Bitcoin', [inp1, ], [out1, ]) + self.assertEqual(hexlify(serialized_tx), '010000000182488650ef25a58fef6788bd71b8212038d7f2bbe4750bc7bcb44701e85ef6d5000000006b483045022100ed9206af5ba7fe82dda17cf20574197924a120be5b415f875f7d9880f4591e4202201081cb688cceadad65dc20e9843d910d895342ce9316f792b748b0e4a0f757870121023230848585885f63803a0a8aecdd6538792d5c539215c91698e315bf0253b43dffffffff0100000000000000005e6a4c5b4144443a4254432e4254433a74686f7270756231616464776e7065707132796e717435303066616733777978736a7576373537307178723872717470783933687733637071617178747778657379373675746774656d703a34323000000000') + + def test_sign_eth_add_liquidity(self): + self.requires_firmware("7.0.2") + self.setup_mnemonic_nopin_nopassphrase() + sig_v, sig_r, sig_s = self.client.ethereum_sign_tx( + n=[2147483692,2147483708,2147483648,0,0], + nonce=0x0, + gas_price=0x5FB9ACA00, + gas_limit=0x186A0, + value=0x00, + to=unhexlify('41e5560054824ea6b0732e656e3ad64e20e94e45'), + address_type=0, + chain_id=1, + data=unhexlify('1fece7b4' + + '0000000000000000000000000000000000000000000000000000000000000000' + + '0000000000000000000000000000000000000000000000000000000000000000' + + '0000000000000000000000000000000000000000000000000000000000000000' + + '0000000000000000000000000000000000000000000000000000000000000080' + # offset of memo string from 4 + '000000000000000000000000000000000000000000000000000000000000003b' + # length of memo string in bytes + # ADD:ETH.ETH:0xc5b2608927ea95ed43f842f553e3a27b09c050e8:420 + '4144443a4554482e4554483a3078633562323630383932376561393565643433' + + '663834326635353365336132376230396330353065383a343230000000000000') + + ) + self.assertEqual(sig_v, 37) + self.assertEqual(hexlify(sig_r), '638f9f42c099d0d47f7fc70d248249d2db24ecabc2fdee5bf2f5ad73b5bbfd30') + self.assertEqual(hexlify(sig_s), '3dae036aabbe0ec55f7b9e4eef54e2b5335f62544d8c2ed041797a9397f185c7') + + def test_mayachain_remove_liquidity(self): + self.requires_firmware("7.1.1") + self.setup_mnemonic_nopin_nopassphrase() + signature = self.client.mayachain_sign_tx( + address_n=parse_path(DEFAULT_BIP32_PATH), + account_number=92, + chain_id="mayachain", + fee=3000, + gas=100000000, + msgs=[make_send( + "smaya1ls33ayg26kmltw7jjy55p32ghjna09zp2mf0av", + "smaya1jvt443rvhq5h8yrna55yjysvhtju0el705qyh0", + 10000 + )], + memo="WITHDRAW:ETH.USDT-0xdac17f958d2ee523a2206206994597c13d831ec7:10000", + sequence=3, + testnet = True + ) + self.assertEqual(hexlify(signature.signature), "13d8ab1a8514c6163064a3e097dd8c33d7063b5994f2ce1c71c691f6fdcf4f1e54860ca7c6d8a478e15b2b07274d9752d8df0af0cd48a6113adf9ecf881ff20e") + self.assertEqual(hexlify(signature.public_key), "031519713b8b42bdc367112d33132cf14cedf928ac5771d444ba459b9497117ba3") + return + + + def test_mayachain_sign_tx(self): + self.requires_firmware("7.0.2") + self.setup_mnemonic_nopin_nopassphrase() + + signature = self.client.mayachain_sign_tx( + address_n=parse_path(DEFAULT_BIP32_PATH), + account_number=92, + chain_id="mayachain", + fee=3000, + gas=200000, + msgs=[make_send( + "smaya1ls33ayg26kmltw7jjy55p32ghjna09zp2mf0av", + "smaya1jvt443rvhq5h8yrna55yjysvhtju0el705qyh0", + 10000 + )], + # full memo + memo="SWAP:ETH.USDT-0xdac17f958d2ee523a2206206994597c13d831ec7:0x41e5560054824ea6b0732e656e3ad64e20e94e45:420", + sequence=3, + testnet = True + ) + self.assertEqual(hexlify(signature.signature), "a1b9082c6817d4c80b82a2d955f2be26a39b8a5e6909c5fcc52114a5c5e5476e68df191c2be5c88e35ef3090c3bafbd44083e32fbf4d26a809218aeec42ec8a9") + self.assertEqual(hexlify(signature.public_key), "031519713b8b42bdc367112d33132cf14cedf928ac5771d444ba459b9497117ba3") + + signature = self.client.mayachain_sign_tx( + address_n=parse_path(DEFAULT_BIP32_PATH), + account_number=92, + chain_id="mayachain", + fee=3000, + gas=100000000, + msgs=[make_send( + "smaya1ls33ayg26kmltw7jjy55p32ghjna09zp2mf0av", + "smaya1jvt443rvhq5h8yrna55yjysvhtju0el705qyh0", + 10000 + )], + # no limit, 's' for swap token + memo="s:ETH.USDT-0xdac17f958d2ee523a2206206994597c13d831ec7:0x41e5560054824ea6b0732e656e3ad64e20e94e45:", + sequence=3, + testnet = True + ) + self.assertEqual(hexlify(signature.signature), "77f24a90428d104fcb0b2bd5ffe1f05e800c032e01a0f1de883616ba8e26c3781044bc8ce1497d24b1b0997061ed664d378c62e04bac54b4ffe5699177c7387f") + self.assertEqual(hexlify(signature.public_key), "031519713b8b42bdc367112d33132cf14cedf928ac5771d444ba459b9497117ba3") + + signature = self.client.mayachain_sign_tx( + address_n=parse_path(DEFAULT_BIP32_PATH), + account_number=92, + chain_id="mayachain", + fee=3000, + gas=100000000, + msgs=[make_send( + "smaya1ls33ayg26kmltw7jjy55p32ghjna09zp2mf0av", + "smaya1jvt443rvhq5h8yrna55yjysvhtju0el705qyh0", + 10000 + )], + # swap to self, "=" for swap token + memo="=:ETH.USDT-0xdac17f958d2ee523a2206206994597c13d831ec7::420", + sequence=3, + testnet = True + ) + self.assertEqual(hexlify(signature.signature), "67ca2ad82a276645bea14fa9ae7d3f947fefe15906f93a605387d21db37c51f46f2961b62efcb7762d9008b1dbb723b2156294f35031cdd16e8e6931f68e4844") + self.assertEqual(hexlify(signature.public_key), "031519713b8b42bdc367112d33132cf14cedf928ac5771d444ba459b9497117ba3") + + signature = self.client.mayachain_sign_tx( + address_n=parse_path(DEFAULT_BIP32_PATH), + account_number=92, + chain_id="mayachain", + fee=3000, + gas=100000000, + msgs=[make_send( + "smaya1ls33ayg26kmltw7jjy55p32ghjna09zp2mf0av", + "smaya1jvt443rvhq5h8yrna55yjysvhtju0el705qyh0", + 10000 + )], + # swap to self, no limit + memo="SWAP:BTC.BTC", + sequence=3, + testnet = True + ) + self.assertEqual(hexlify(signature.signature), "6e6908262ae5f268e104a567f64b4be18297cc68577962925a1dcbcc2333f7ba5a5446f623a774359d68335804e88448bf432c95dc9777b26effecb339a790a9") + self.assertEqual(hexlify(signature.public_key), "031519713b8b42bdc367112d33132cf14cedf928ac5771d444ba459b9497117ba3") + + signature = self.client.mayachain_sign_tx( + address_n=parse_path(DEFAULT_BIP32_PATH), + account_number=92, + chain_id="mayachain", + fee=3000, + gas=100000000, + msgs=[make_send( + "smaya1ls33ayg26kmltw7jjy55p32ghjna09zp2mf0av", + "smaya1jvt443rvhq5h8yrna55yjysvhtju0el705qyh0", + 10000 + )], + # full memo + memo="ADD:ETH.USDT-0xdac17f958d2ee523a2206206994597c13d831ec7:0x41e5560054824ea6b0732e656e3ad64e20e94e45", + sequence=3, + testnet = True + ) + self.assertEqual(hexlify(signature.signature), "186e81a054517ce4f5134fa5ed6acc6398bd15d5c58361babadd9087fafd7a9122c7978ecc6710f76bebd46df72523f3409c33af387473f61ef167575f11a68b") + self.assertEqual(hexlify(signature.public_key), "031519713b8b42bdc367112d33132cf14cedf928ac5771d444ba459b9497117ba3") + + signature = self.client.mayachain_sign_tx( + address_n=parse_path(DEFAULT_BIP32_PATH), + account_number=92, + chain_id="mayachain", + fee=3000, + gas=100000000, + msgs=[make_send( + "smaya1ls33ayg26kmltw7jjy55p32ghjna09zp2mf0av", + "smaya1jvt443rvhq5h8yrna55yjysvhtju0el705qyh0", + 10000 + )], + #'a' for add liquidity + memo="a:ETH.USDT-0xdac17f958d2ee523a2206206994597c13d831ec7:0x41e5560054824ea6b0732e656e3ad64e20e94e45", + #memo="a:ETH.USDT-0xdac17f958d2ee523a2206206994597c13d831ec7", + sequence=3, + testnet = True + ) + self.assertEqual(hexlify(signature.signature), "a98354ed6ee626603cd4416d314d1b875c5ab6a6af83fe1be05a6ac56d620e8f2322d500bba6a7f6e0e2fae810016ebc00be5a580766f171cd5f4a5b2e67263f") + self.assertEqual(hexlify(signature.public_key), "031519713b8b42bdc367112d33132cf14cedf928ac5771d444ba459b9497117ba3") + + signature = self.client.mayachain_sign_tx( + address_n=parse_path(DEFAULT_BIP32_PATH), + account_number=92, + chain_id="mayachain", + fee=3000, + gas=100000000, + msgs=[make_send( + "smaya1ls33ayg26kmltw7jjy55p32ghjna09zp2mf0av", + "smaya1jvt443rvhq5h8yrna55yjysvhtju0el705qyh0", + 10000 + )], + #"+" for add liquidity + memo="+:ETH.USDT-0xdac17f958d2ee523a2206206994597c13d831ec7:0x41e5560054824ea6b0732e656e3ad64e20e94e45", + sequence=3, + testnet = True + ) + self.assertEqual(hexlify(signature.signature), "0409d104aaafe400e86b6172811bf1b44b6cc0065c13df10083a86d02b13b8ce7d40a4935bc022c76dae4793223c0c7d8446c83acdbd8d0188d35d2b7b8e22fc") + self.assertEqual(hexlify(signature.public_key), "031519713b8b42bdc367112d33132cf14cedf928ac5771d444ba459b9497117ba3") + + return + +if __name__ == '__main__': + unittest.main()