示例#1
0
 def __init__(self, ethereum_provider: EthereumProvider, zk_signer: ZkSyncSigner,
              eth_signer: EthereumSignerInterface, provider: ZkSyncProviderInterface):
     self.ethereum_provider = ethereum_provider
     self.zk_signer = zk_signer
     self.eth_signer = eth_signer
     self.zk_provider = provider
     self.tokens = Tokens(tokens=[])
示例#2
0
    def test_is_valid_order_deserialized(self):
        account = Account.from_key(PRIVATE_KEY)
        zksync_signer = ZkSyncSigner.from_account(account, self.library,
                                                  ChainId.MAINNET)
        ethereum_signer = EthereumSignerWeb3(account=account)

        token1 = Token(id=1, symbol='', address='',
                       decimals=0)  # only id matters
        token2 = Token(id=2, symbol='', address='',
                       decimals=0)  # only id matters
        tokens_pool = Tokens(tokens=[token1, token2])

        order = Order(account_id=7,
                      nonce=18,
                      token_sell=token1,
                      token_buy=token2,
                      ratio=Fraction(1, 4),
                      amount=1000000,
                      recipient='0x823b6a996cea19e0c41e250b20e2e804ea72ccdf',
                      valid_from=0,
                      valid_until=4294967295)
        order.signature = zksync_signer.sign_tx(order)
        order.eth_signature = ethereum_signer.sign_tx(order)
        zksync_validator = EncodedTxValidator(self.library)
        serialized_order = json.dumps(order.dict(), indent=4)

        deserialized_order = Order.from_json(json.loads(serialized_order),
                                             tokens_pool)
        ret = zksync_validator.is_valid_signature(deserialized_order)
        self.assertTrue(ret)
        ret = deserialized_order.is_valid_eth_signature(
            ethereum_signer.address())
        self.assertTrue(ret)
示例#3
0
    def test_order_deserialization(self):
        token1 = Token(id=1, symbol='', address='',
                       decimals=0)  # only id matters
        token2 = Token(id=2, symbol='', address='',
                       decimals=0)  # only id matters
        tokens = Tokens(tokens=[token1, token2])

        order = Order(account_id=7,
                      nonce=18,
                      token_sell=token1,
                      token_buy=token2,
                      ratio=Fraction(1, 4),
                      amount=1000000,
                      recipient='0x823b6a996cea19e0c41e250b20e2e804ea72ccdf',
                      valid_from=0,
                      valid_until=4294967295)
        serialized_order = order.dict()
        from_json_order = Order.from_json(serialized_order, tokens)
        self.assertEqual(order.account_id, from_json_order.account_id)
        self.assertEqual(order.nonce, from_json_order.nonce)
        self.assertEqual(order.token_sell, from_json_order.token_sell)
        self.assertEqual(order.token_buy, from_json_order.token_buy)
        self.assertEqual(order.ratio, from_json_order.ratio)
        self.assertEqual(order.recipient, from_json_order.recipient)
        self.assertEqual(order.valid_from, from_json_order.valid_from)
        self.assertEqual(order.valid_until, from_json_order.valid_until)
示例#4
0
 async def get_tokens(self) -> Tokens:
     data = await self.provider.request("tokens", None)
     tokens = [Token(address=Web3.toChecksumAddress(token['address']),
                     id=token['id'],
                     symbol=token['symbol'],
                     decimals=token['decimals']
                     ) for token in data.values()]
     return Tokens(tokens=tokens)
示例#5
0
class Wallet:
    def __init__(self, ethereum_provider: EthereumProvider, zk_signer: ZkSyncSigner,
                 eth_signer: EthereumSignerInterface, provider: ZkSyncProviderInterface):
        self.ethereum_provider = ethereum_provider
        self.zk_signer = zk_signer
        self.eth_signer = eth_signer
        self.zk_provider = provider
        self.tokens = Tokens(tokens=[])

    async def send_signed_transaction(self, tx: EncodedTx, eth_signature: TxEthSignature,
                                      fast_processing: bool = False) -> str:
        return await self.zk_provider.submit_tx(tx, eth_signature, fast_processing)

    async def send_txs_batch(self, transactions: List[TransactionWithSignature],
                             signatures: Optional[
                                 Union[List[TxEthSignature], TxEthSignature]
                             ] = None) -> List[str]:
        return await self.zk_provider.submit_txs_batch(transactions, signatures)

    async def set_signing_key(self, fee_token: TokenLike, *,
                              eth_auth_data: Union[ChangePubKeyCREATE2, ChangePubKeyEcdsa] = None,
                              fee: Decimal = None, nonce: int = None,
                              valid_from=DEFAULT_VALID_FROM, valid_until=DEFAULT_VALID_UNTIL):
        change_pub_key, eth_signature = await self.build_change_pub_key(fee_token,
                                                                        eth_auth_data=eth_auth_data,
                                                                        fee=fee, nonce=nonce,
                                                                        valid_from=valid_from,
                                                                        valid_until=valid_until)

        return await self.send_signed_transaction(change_pub_key, eth_signature)

    async def build_change_pub_key(
        self, fee_token: TokenLike, *,
        fee: Decimal = None, nonce: int = None,
        eth_auth_data: Union[ChangePubKeyCREATE2, ChangePubKeyEcdsa] = None,
        valid_from=DEFAULT_VALID_FROM, valid_until=DEFAULT_VALID_UNTIL
    ):
        account_id, new_nonce = await self.zk_provider.get_account_nonce(self.address())
        nonce = nonce or new_nonce
        token = await self.resolve_token(fee_token)
        if isinstance(eth_auth_data, ChangePubKeyEcdsa):
            eth_auth_type = ChangePubKeyTypes.ecdsa
        elif isinstance(eth_auth_data, ChangePubKeyCREATE2):
            eth_auth_type = ChangePubKeyTypes.create2
        else:
            eth_auth_type = ChangePubKeyTypes.onchain

        if fee is None:
            if eth_auth_type == ChangePubKeyTypes.ecdsa:
                fee = await self.zk_provider.get_transaction_fee(FeeTxType.change_pub_key_ecdsa,
                                                                 self.address(),
                                                                 fee_token)
            elif eth_auth_type == ChangePubKeyTypes.onchain:
                fee = await self.zk_provider.get_transaction_fee(FeeTxType.change_pub_key_onchain,
                                                                 self.address(),
                                                                 fee_token)
            elif eth_auth_type == ChangePubKeyTypes.create2:
                fee = await self.zk_provider.get_transaction_fee(FeeTxType.change_pub_key_create2,
                                                                 self.address(),
                                                                 fee_token)

            fee = fee.total_fee
        else:
            fee = token.from_decimal(fee)

        new_pubkey_hash = self.zk_signer.pubkey_hash_str()
        change_pub_key = ChangePubKey(
            account=self.address(),
            account_id=account_id,
            new_pk_hash=new_pubkey_hash,
            token=token,
            fee=fee,
            nonce=nonce,
            valid_until=valid_until,
            valid_from=valid_from,
            eth_auth_data=eth_auth_data
        )

        eth_signature = self.eth_signer.sign(change_pub_key.get_eth_tx_bytes())
        eth_auth_data = change_pub_key.get_auth_data(eth_signature.signature)

        change_pub_key.eth_auth_data = eth_auth_data
        zk_signature = self.zk_signer.sign_tx(change_pub_key)
        change_pub_key.signature = zk_signature

        return change_pub_key, eth_signature

    async def forced_exit(self, target: str, token: TokenLike, fee: Decimal = None,
                          valid_from=DEFAULT_VALID_FROM, valid_until=DEFAULT_VALID_UNTIL) -> str:
        transfer, eth_signature = await self.build_forced_exit(target, token, fee,
                                                               valid_from, valid_until)

        return await self.send_signed_transaction(transfer, eth_signature)

    async def build_forced_exit(
        self,
        target: str,
        token: TokenLike,
        fee: Decimal = None,
        valid_from=DEFAULT_VALID_FROM,
        valid_until=DEFAULT_VALID_UNTIL
    ) -> Tuple[ForcedExit, TxEthSignature]:
        account_id, nonce = await self.zk_provider.get_account_nonce(self.address())
        token = await self.resolve_token(token)
        if fee is None:
            fee = await self.zk_provider.get_transaction_fee(FeeTxType.withdraw, target, token.id)
            fee = fee.total_fee
        else:
            fee = token.from_decimal(fee)
        forced_exit = ForcedExit(initiator_account_id=account_id,
                                 target=target,
                                 fee=fee,
                                 nonce=nonce,
                                 valid_from=valid_from,
                                 valid_until=valid_until,
                                 token=token)
        eth_signature = self.eth_signer.sign_tx(forced_exit)
        zk_signature = self.zk_signer.sign_tx(forced_exit)
        forced_exit.signature = zk_signature

        return forced_exit, eth_signature

    def address(self):
        return self.eth_signer.address()

    async def build_transfer(self, to: str, amount: Decimal, token: TokenLike,
                             fee: Decimal = None,
                             valid_from=DEFAULT_VALID_FROM,
                             valid_until=DEFAULT_VALID_UNTIL) -> Tuple[Transfer, TxEthSignature]:
        account_id, nonce = await self.zk_provider.get_account_nonce(self.address())
        token = await self.resolve_token(token)
        if fee is None:
            fee = await self.zk_provider.get_transaction_fee(FeeTxType.transfer, to, token.id)
            fee = fee.total_fee
        else:
            fee = token.from_decimal(fee)
        transfer = Transfer(account_id=account_id, from_address=self.address(),
                            to_address=to,
                            amount=token.from_decimal(amount), fee=fee,
                            nonce=nonce,
                            valid_from=valid_from,
                            valid_until=valid_until,
                            token=token)
        eth_signature = self.eth_signer.sign_tx(transfer)
        zk_signature = self.zk_signer.sign_tx(transfer)
        transfer.signature = zk_signature
        return transfer, eth_signature

    async def transfer(self, to: str, amount: Decimal, token: TokenLike,
                       fee: Decimal = None,
                       valid_from=DEFAULT_VALID_FROM, valid_until=DEFAULT_VALID_UNTIL) -> str:
        transfer, eth_signature = await self.build_transfer(to, amount, token, fee,
                                                            valid_from, valid_until)

        return await self.send_signed_transaction(transfer, eth_signature)

    async def build_withdraw(self, eth_address: str, amount: Decimal, token: TokenLike,
                             fee: Decimal = None, fast: bool = False,
                             valid_from=DEFAULT_VALID_FROM,
                             valid_until=DEFAULT_VALID_UNTIL) -> (Withdraw, TxEthSignature):
        account_id, nonce = await self.zk_provider.get_account_nonce(self.address())
        token = await self.resolve_token(token)
        if fee is None:
            tx_type = FeeTxType.fast_withdraw if fast else FeeTxType.withdraw
            fee = await self.zk_provider.get_transaction_fee(tx_type, eth_address, token.id)
            fee = fee.total_fee
        else:
            fee = token.from_decimal(fee)
        withdraw = Withdraw(account_id=account_id, from_address=self.address(),
                            to_address=eth_address,
                            amount=token.from_decimal(amount), fee=fee,
                            nonce=nonce,
                            valid_from=valid_from,
                            valid_until=valid_until,
                            token=token)
        eth_signature = self.eth_signer.sign_tx(withdraw)
        zk_signature = self.zk_signer.sign_tx(withdraw)
        withdraw.signature = zk_signature
        return withdraw, eth_signature

    async def withdraw(self, eth_address: str, amount: Decimal, token: TokenLike,
                       fee: Decimal = None, fast: bool = False,
                       valid_from=DEFAULT_VALID_FROM, valid_until=DEFAULT_VALID_UNTIL) -> str:

        withdraw, eth_signature = await self.build_withdraw(eth_address, amount, token, fee, fast,
                                                            valid_from, valid_until)
        return await self.send_signed_transaction(withdraw, eth_signature, fast)

    async def get_balance(self, token: TokenLike, type: str):
        account_state = await self.get_account_state()
        token = await self.resolve_token(token)

        if type == "committed":
            token_balance = account_state.committed.balances.get(token.symbol)
        else:
            token_balance = account_state.verified.balances.get(token.symbol)
        if token_balance is None:
            token_balance = 0
        return token_balance

    async def get_account_state(self):
        return await self.zk_provider.get_state(self.address())

    async def is_signing_key_set(self) -> bool:
        account_state = await self.get_account_state()
        signer_pub_key_hash = self.zk_signer.pubkey_hash_str()
        return account_state.id is not None and\
               account_state.committed.pub_key_hash == signer_pub_key_hash

    async def resolve_token(self, token: TokenLike) -> Token:
        resolved_token = self.tokens.find(token)
        if resolved_token is not None:
            return resolved_token
        self.tokens = await self.zk_provider.get_tokens()
        resolved_token = self.tokens.find(token)
        if resolved_token is None:
            raise TokenNotFoundError
        return resolved_token
示例#6
0
class Wallet:
    def __init__(self, ethereum_provider: EthereumProvider, zk_signer: ZkSyncSigner,
                 eth_signer: EthereumSignerInterface, provider: ZkSyncProviderInterface):
        self.ethereum_provider = ethereum_provider
        self.zk_signer = zk_signer
        self.eth_signer = eth_signer
        self.zk_provider = provider
        self.account_id = None
        self.tokens = Tokens(tokens=[])

    async def get_account_id(self):
        if self.account_id is None:
            state = await self.zk_provider.get_state(self.address())
            if isinstance(state.id, int):
                self.account_id = state.id
        return self.account_id

    async def send_signed_transaction(self, tx: EncodedTx,
                                      eth_signature: Union[Optional[TxEthSignature], List[Optional[TxEthSignature]]],
                                      fast_processing: bool = False) -> Transaction:
        return await self.zk_provider.submit_tx(tx, eth_signature, fast_processing)

    async def send_txs_batch(self, transactions: List[TransactionWithSignature],
                             signatures: Optional[
                                 Union[List[TxEthSignature], TxEthSignature]
                             ] = None) -> List[Transaction]:
        return await self.zk_provider.submit_txs_batch(transactions, signatures)

    async def set_signing_key(self, fee_token: TokenLike, *,
                              eth_auth_data: Union[ChangePubKeyCREATE2, ChangePubKeyEcdsa, None] = None,
                              fee: Optional[Decimal] = None, nonce: Optional[int] = None,
                              valid_from=DEFAULT_VALID_FROM, valid_until=DEFAULT_VALID_UNTIL):
        if nonce is None:
            nonce = await self.zk_provider.get_account_nonce(self.address())
        fee_token_obj = await self.resolve_token(fee_token)
        if isinstance(eth_auth_data, ChangePubKeyEcdsa):
            eth_auth_type = ChangePubKeyTypes.ecdsa
        elif isinstance(eth_auth_data, ChangePubKeyCREATE2):
            eth_auth_type = ChangePubKeyTypes.create2
        else:
            eth_auth_type = ChangePubKeyTypes.onchain

        if fee is None:
            if eth_auth_type == ChangePubKeyTypes.ecdsa:
                fee_obj = await self.zk_provider.get_transaction_fee(FeeTxType.change_pub_key_ecdsa,
                                                                 self.address(),
                                                                 fee_token_obj.id)
            elif eth_auth_type == ChangePubKeyTypes.onchain:
                fee_obj = await self.zk_provider.get_transaction_fee(FeeTxType.change_pub_key_onchain,
                                                                 self.address(),
                                                                 fee_token_obj.id)
            else:
                assert eth_auth_type == ChangePubKeyTypes.create2, "invalid eth_auth_type"
                fee_obj = await self.zk_provider.get_transaction_fee(FeeTxType.change_pub_key_create2,
                                                                 self.address(),
                                                                 fee_token_obj.id)
            fee_int = fee_obj.total_fee
        else:
            fee_int = fee_token_obj.from_decimal(fee)

        change_pub_key, eth_signature = await self.build_change_pub_key(fee_token_obj,
                                                                        eth_auth_data, fee_int,
                                                                        nonce,
                                                                        valid_from,
                                                                        valid_until)

        return await self.send_signed_transaction(change_pub_key, eth_signature)

    # This function takes as a parameter the integer fee of 
    # lowest token denominations (wei, satoshi, etc.)
    async def build_change_pub_key(
            self,
            fee_token: Token,
            eth_auth_data: Union[ChangePubKeyCREATE2, ChangePubKeyEcdsa, None],
            fee: int,
            nonce: Optional[int] = None,
            valid_from=DEFAULT_VALID_FROM,
            valid_until=DEFAULT_VALID_UNTIL):
        if nonce is None:
            nonce = await self.zk_provider.get_account_nonce(self.address())
        account_id = await self.get_account_id()

        new_pubkey_hash = self.zk_signer.pubkey_hash_str()
        change_pub_key = ChangePubKey(
            account=self.address(),
            account_id=account_id,
            new_pk_hash=new_pubkey_hash,
            token=fee_token,
            fee=fee,
            nonce=nonce,
            valid_until=valid_until,
            valid_from=valid_from,
            eth_auth_data=eth_auth_data
        )

        eth_signature = self.eth_signer.sign(change_pub_key.get_eth_tx_bytes())
        eth_auth_data = change_pub_key.get_auth_data(eth_signature.signature)

        change_pub_key.eth_auth_data = eth_auth_data
        zk_signature = self.zk_signer.sign_tx(change_pub_key)
        change_pub_key.signature = zk_signature

        return change_pub_key, eth_signature

    async def forced_exit(self, target: str, token: TokenLike, fee: Optional[Decimal] = None,
                          valid_from=DEFAULT_VALID_FROM, valid_until=DEFAULT_VALID_UNTIL) -> Transaction:
        nonce = await self.zk_provider.get_account_nonce(self.address())
        token_obj = await self.resolve_token(token)
        if fee is None:
            fee_obj = await self.zk_provider.get_transaction_fee(FeeTxType.withdraw, target, token_obj.id)
            fee_int = fee_obj.total_fee
        else:
            fee_int = token_obj.from_decimal(fee)

        transfer, eth_signature = await self.build_forced_exit(target, token_obj, fee_int, nonce,
                                                               valid_from, valid_until)

        return await self.send_signed_transaction(transfer, eth_signature)

    # This function takes as a parameter the integer fee of 
    # lowest token denominations (wei, satoshi, etc.)
    async def build_forced_exit(
            self,
            target: str,
            token: Token,
            fee: int,
            nonce: Optional[int] = None,
            valid_from=DEFAULT_VALID_FROM,
            valid_until=DEFAULT_VALID_UNTIL) -> Tuple[ForcedExit, TxEthSignature]:
        if nonce is None:
            nonce = await self.zk_provider.get_account_nonce(self.address())
        account_id = await self.get_account_id()

        forced_exit = ForcedExit(initiator_account_id=account_id,
                                 target=target,
                                 fee=fee,
                                 nonce=nonce,
                                 valid_from=valid_from,
                                 valid_until=valid_until,
                                 token=token)
        eth_signature = self.eth_signer.sign_tx(forced_exit)
        zk_signature = self.zk_signer.sign_tx(forced_exit)
        forced_exit.signature = zk_signature

        return forced_exit, eth_signature

    async def mint_nft(self, content_hash: str, recipient: str,token: TokenLike, fee: Optional[Decimal] = None) -> Transaction:
        token_obj = await self.resolve_token(token)

        nonce = await self.zk_provider.get_account_nonce(self.address())
        if fee is None:
            fee_obj = await self.zk_provider.get_transaction_fee(FeeTxType.mint_nft, recipient, token_obj.id)
            fee_int = fee_obj.total_fee
        else:
            fee_int = token_obj.from_decimal(fee)

        mint_nft, eth_signature = await self.build_mint_nft(content_hash, recipient, token_obj, fee_int, nonce)
        return await self.send_signed_transaction(mint_nft, eth_signature)

    # This function takes as a parameter the integer fee of 
    # lowest token denominations (wei, satoshi, etc.)
    async def build_mint_nft(
            self,
            content_hash: str,
            recipient: str,
            token: Token,
            fee: int,
            nonce: Optional[int] = None
    ) -> Tuple[MintNFT, TxEthSignature]:
        if nonce is None:
            nonce = await self.zk_provider.get_account_nonce(self.address())
        account_id = await self.get_account_id()

        mint_nft = MintNFT(creator_id=account_id,
                           creator_address=self.address(),
                           content_hash=content_hash,
                           recipient=recipient,
                           fee=fee,
                           fee_token=token,
                           nonce=nonce)
        eth_signature = self.eth_signer.sign_tx(mint_nft)
        zk_signature = self.zk_signer.sign_tx(mint_nft)
        mint_nft.signature = zk_signature

        return mint_nft, eth_signature

    async def withdraw_nft(
            self,
            to_address: str,
            nft_token: NFT,
            fee_token: TokenLike,
            fee: Optional[Decimal] = None,
            valid_from=DEFAULT_VALID_FROM,
            valid_until=DEFAULT_VALID_UNTIL
    ) -> Transaction:
        nonce = await self.zk_provider.get_account_nonce(self.address())
        fee_token_obj = await self.resolve_token(fee_token)

        if fee is None:
            fee_obj = await self.zk_provider.get_transaction_fee(FeeTxType.withdraw_nft, to_address, fee_token_obj.id)
            fee_int = fee_obj.total_fee
        else:
            fee_int = fee_token_obj.from_decimal(fee)
        withdraw_nft, eth_signature = await self.build_withdraw_nft(to_address, nft_token, fee_token_obj, fee_int,
                                                                    nonce, valid_from, valid_until)
        return await self.send_signed_transaction(withdraw_nft, eth_signature)

    # This function takes as a parameter the integer fee of 
    # lowest token denominations (wei, satoshi, etc.)
    async def build_withdraw_nft(
            self,
            to_address: str,
            nft_token: NFT,
            fee_token: Token,
            fee: int,
            nonce: Optional[int] = None,
            valid_from=DEFAULT_VALID_FROM,
            valid_until=DEFAULT_VALID_UNTIL
    ) -> Tuple[WithdrawNFT, TxEthSignature]:
        if nonce is None:
            nonce = await self.zk_provider.get_account_nonce(self.address())
        account_id = await self.get_account_id()

        withdraw_nft = WithdrawNFT(
            account_id=account_id,
            from_address=self.address(),
            to_address=to_address,
            fee_token=fee_token,
            fee=fee,
            nonce=nonce,
            valid_from=valid_from,
            valid_until=valid_until,
            token_id=nft_token.id)
        eth_signature = self.eth_signer.sign_tx(withdraw_nft)
        zk_signature = self.zk_signer.sign_tx(withdraw_nft)
        withdraw_nft.signature = zk_signature

        return withdraw_nft, eth_signature

    def address(self):
        return self.eth_signer.address()

    async def build_transfer(
            self,
            to: str,
            amount: int,
            token: Token,
            fee: int,
            nonce: Optional[int] = None,
            valid_from: int = DEFAULT_VALID_FROM,
            valid_until: int = DEFAULT_VALID_UNTIL,
    ) -> Tuple[Transfer, TxEthSignature]:
        """
        This function takes as a parameter the integer amount/fee of lowest token denominations (wei, satoshi, etc.)
        """
        if nonce is None:
            nonce = await self.zk_provider.get_account_nonce(self.address())
        account_id = await self.get_account_id()

        transfer = Transfer(account_id=account_id, from_address=self.address(),
                            to_address=to.lower(),
                            amount=amount, fee=fee,
                            nonce=nonce,
                            valid_from=valid_from,
                            valid_until=valid_until,
                            token=token)
        eth_signature = self.eth_signer.sign_tx(transfer)
        zk_signature = self.zk_signer.sign_tx(transfer)
        transfer.signature = zk_signature
        return transfer, eth_signature

    async def transfer(self, to: str, amount: Decimal, token: TokenLike,
                       fee: Optional[Decimal] = None,
                       valid_from=DEFAULT_VALID_FROM, valid_until=DEFAULT_VALID_UNTIL) -> Transaction:
        nonce = await self.zk_provider.get_account_nonce(self.address())
        token_obj = await self.resolve_token(token)

        if fee is None:
            fee_obj = await self.zk_provider.get_transaction_fee(FeeTxType.transfer, to, token_obj.id)
            fee_int = fee_obj.total_fee
        else:
            fee_int = token_obj.from_decimal(fee)

        amount_int = token_obj.from_decimal(amount)

        transfer, eth_signature = await self.build_transfer(to, amount_int, token_obj, fee_int, nonce, valid_from, valid_until)
        return await self.send_signed_transaction(transfer, eth_signature)

    async def transfer_nft(self, to: str, nft: NFT, fee_token: TokenLike,
                           fee: Optional[Decimal] = None,
                           valid_from=DEFAULT_VALID_FROM,
                           valid_until=DEFAULT_VALID_UNTIL
                           ) -> List[Transaction]:
        nonce = await self.zk_provider.get_account_nonce(self.address())
        fee_token_obj = await self.resolve_token(fee_token)

        if fee is None:
            fee_int = await self.zk_provider.get_transactions_batch_fee(
                [FeeTxType.transfer, FeeTxType.transfer],
                [to, self.address()],
                fee_token_obj.symbol
            )
        else:
            fee_int = fee_token_obj.from_decimal(fee)

        nft_tx = await self.build_transfer(to, 1, nft, 0, nonce, valid_from, valid_until)
        fee_tx = await self.build_transfer(self.address(), 0, fee_token_obj, fee_int, nonce + 1, valid_from, valid_until)
        batch = [
            TransactionWithSignature(nft_tx[0], nft_tx[1]),
            TransactionWithSignature(fee_tx[0], fee_tx[1])
        ]
        return await self.send_txs_batch(batch)

    async def get_order(self, token_sell: TokenLike, token_buy: TokenLike,
                        ratio: Fraction, ratio_type: RatioType, amount: Decimal,
                        recipient: Optional[str] = None,
                        nonce: Optional[int] = None,
                        valid_from=DEFAULT_VALID_FROM,
                        valid_until=DEFAULT_VALID_UNTIL) -> Order:
        if nonce is None:
            nonce = await self.zk_provider.get_account_nonce(self.address())
        token_sell_obj = await self.resolve_token(token_sell)
        token_buy_obj = await self.resolve_token(token_buy)
        recipient = recipient or self.address()

        if ratio_type == RatioType.token:
            ratio = token_ratio_to_wei_ratio(ratio, token_sell_obj, token_buy_obj)

        account_id = await self.get_account_id()

        order = Order(account_id=account_id, recipient=recipient,
                      token_sell=token_sell_obj,
                      token_buy=token_buy_obj,
                      ratio=ratio,
                      amount=token_sell_obj.from_decimal(amount),
                      nonce=nonce,
                      valid_from=valid_from,
                      valid_until=valid_until)

        order.eth_signature = self.eth_signer.sign_tx(order)
        order.signature = self.zk_signer.sign_tx(order)

        return order

    async def get_limit_order(self, token_sell: TokenLike, token_buy: TokenLike,
                              ratio: Fraction, ratio_type: RatioType,
                              recipient: Optional[str] = None,
                              valid_from=DEFAULT_VALID_FROM,
                              valid_until=DEFAULT_VALID_UNTIL):
        return await self.get_order(token_sell, token_buy, ratio, ratio_type, Decimal(0), recipient, valid_from,
                                    valid_until)

    # This function takes as a parameter the integer amounts/fee of 
    # lowest token denominations (wei, satoshi, etc.)
    async def build_swap(self, orders: Tuple[Order, Order], fee_token: Token,
                         amounts: Tuple[int, int], fee: int, nonce: Optional[int] = None):
        if nonce is None:
            nonce = await self.zk_provider.get_account_nonce(self.address())
        account_id = await self.get_account_id()

        swap = Swap(
            orders=orders, fee_token=fee_token, amounts=amounts, fee=fee, nonce=nonce,
            submitter_id=account_id, submitter_address=self.address()
        )
        eth_signature = self.eth_signer.sign_tx(swap)
        swap.signature = self.zk_signer.sign_tx(swap)
        return swap, eth_signature

    async def swap(self, orders: Tuple[Order, Order], fee_token: TokenLike,
                   amounts: Optional[Tuple[Decimal, Decimal]] = None, fee: Optional[Decimal] = None):
        nonce = await self.zk_provider.get_account_nonce(self.address())

        fee_token_obj = await self.resolve_token(fee_token)
        if fee is None:
            fee_obj = await self.zk_provider.get_transaction_fee(FeeTxType.swap, self.address(), fee_token_obj.id)
            fee_int = fee_obj.total_fee
        else:
            fee_int = fee_token_obj.from_decimal(fee)

        if amounts is None:
            amounts_int = (orders[0].amount, orders[1].amount)
            if amounts_int[0] == 0 or amounts_int[1] == 0:
                raise AmountsMissing("in this case you must specify amounts explicitly")
        else:
            amounts_int = (
                orders[0].token_sell.from_decimal(amounts[0]),
                orders[1].token_sell.from_decimal(amounts[1])
            )

        swap, eth_signature = await self.build_swap(orders, fee_token_obj, amounts_int, fee_int, nonce)
        eth_signatures = [eth_signature, swap.orders[0].eth_signature, swap.orders[1].eth_signature]
        return await self.send_signed_transaction(swap, eth_signatures)

    # This function takes as a parameter the integer amount/fee of 
    # lowest token denominations (wei, satoshi, etc.)
    async def build_withdraw(self, eth_address: str, amount: int, token: Token,
                             fee: int,
                             nonce: Optional[int] = None,
                             valid_from=DEFAULT_VALID_FROM,
                             valid_until=DEFAULT_VALID_UNTIL):
        if nonce is None:
            nonce = await self.zk_provider.get_account_nonce(self.address())
        account_id = await self.get_account_id()

        withdraw = Withdraw(account_id=account_id, from_address=self.address(),
                            to_address=eth_address,
                            amount=amount, fee=fee,
                            nonce=nonce,
                            valid_from=valid_from,
                            valid_until=valid_until,
                            token=token)
        eth_signature = self.eth_signer.sign_tx(withdraw)
        zk_signature = self.zk_signer.sign_tx(withdraw)
        withdraw.signature = zk_signature
        return withdraw, eth_signature

    async def withdraw(self, eth_address: str, amount: Decimal, token: TokenLike,
                       fee: Optional[Decimal] = None, fast: bool = False,
                       valid_from=DEFAULT_VALID_FROM, valid_until=DEFAULT_VALID_UNTIL) -> Transaction:
        nonce = await self.zk_provider.get_account_nonce(self.address())
        token_obj = await self.resolve_token(token)
        if fee is None:
            tx_type = FeeTxType.fast_withdraw if fast else FeeTxType.withdraw
            fee_obj = await self.zk_provider.get_transaction_fee(tx_type, eth_address, token_obj.id)
            fee_int = fee_obj.total_fee
        else:
            fee_int = token_obj.from_decimal(fee)
        amount_int = token_obj.from_decimal(amount)

        withdraw, eth_signature = await self.build_withdraw(eth_address, amount_int, token_obj, fee_int, nonce,
                                                            valid_from, valid_until)
        return await self.send_signed_transaction(withdraw, eth_signature, fast)

    async def get_balance(self, token: TokenLike, type: str):
        account_state = await self.get_account_state()
        token_obj = await self.resolve_token(token)

        if type == "committed":
            token_balance = account_state.committed.balances.get(token_obj.symbol)
        else:
            token_balance = account_state.verified.balances.get(token_obj.symbol)
        if token_balance is None:
            token_balance = 0
        return token_balance

    async def get_account_state(self):
        return await self.zk_provider.get_state(self.address())

    async def is_signing_key_set(self) -> bool:
        account_state = await self.get_account_state()
        signer_pub_key_hash = self.zk_signer.pubkey_hash_str()
        return account_state.id is not None and \
               account_state.committed.pub_key_hash == signer_pub_key_hash

    async def resolve_token(self, token: TokenLike) -> Token:
        resolved_token = self.tokens.find(token)
        if resolved_token is not None:
            return resolved_token
        self.tokens = await self.zk_provider.get_tokens()
        resolved_token = self.tokens.find(token)
        if resolved_token is None:
            raise TokenNotFoundError
        return resolved_token

    async def enable_2fa(self) -> bool:
        mil_seconds = int(time.time() * 1000)
        msg = get_toggle_message(True, mil_seconds)
        eth_sig = self.eth_signer.sign(msg.encode())
        account_id = await self.get_account_id()
        toggle = Toggle2FA(True,
                           account_id,
                           mil_seconds,
                           eth_sig,
                           None
                           )
        return await self.zk_provider.toggle_2fa(toggle)

    async def disable_2fa(self, pub_key_hash: Optional[str]) -> bool:
        mil_seconds = int(time.time() * 1000)
        if pub_key_hash is None:
            msg = get_toggle_message(False, mil_seconds)
        else:
            msg = get_toggle_message_with_pub(False, mil_seconds, pub_key_hash)
        eth_sig = self.eth_signer.sign(msg.encode())
        account_id = await self.get_account_id()
        toggle = Toggle2FA(False,
                           account_id,
                           mil_seconds,
                           eth_sig,
                           pub_key_hash)
        return await self.zk_provider.toggle_2fa(toggle)

    async def disable_2fa_with_pub_key(self):
        pub_key_hash = self.zk_signer.pubkey_hash_str()
        return await self.disable_2fa(pub_key_hash)