HotWallet

Documentation for eth_defi.hotwallet.HotWallet Python class.

class HotWallet[source]

Hot wallet for signing transactions effectively.

To use this class with the existing web3.py Contract.functions.myFunc().transact() you can add the private key as the local signing middleware. However you should try to use sign_bound_call_with_new_nonce() instead when possible. See also eth_defi.middleware.construct_sign_and_send_raw_middleware_anvil() when working with Anvil.

Example sending USDC with HotWallet class:

from eth_defi.token import fetch_erc20_details
usdc = fetch_erc20_details(web3, "0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48")  # Ethereum mainnet
bound_call = usdc.transfer("<to address here>", Decimal(2140))
tx_hash = hot_wallet.transact_and_broadcast_with_contract(bound_call)
print("Broadcasted:", tx_hash.hex())

Example:

from eth_account import Account
from web3.middleware import construct_sign_and_send_raw_middleware

from eth_defi.trace import assert_transaction_success_with_explanation
from eth_defi.hotwallet import HotWallet

account = Account.create()

# Move 1/2 of ETH from the first test account to ours
test_account_1 = web3.eth.accounts[0]
stash = web3.eth.get_balance(test_account_1)
tx_hash = web3.eth.send_transaction({"from": test_account_1, "to": account.address, "value": stash // 2})
assert_transaction_success_with_explanation(web3, tx_hash)

# Attach local private key to the web3.py middleware machinery
web3.middleware_onion.add(construct_sign_and_send_raw_middleware(account))

# Create a hot wallet instance
hot_wallet = HotWallet(account)
hot_wallet.sync_nonce(web3)

# Use web3.py signing (NOTE: does not correctly increment nonce)
# so you need to call hot_wallet.sync_nonce() after the tx has been confirmed
tx_hash = usdc.functions.transfer(
    some_address,
    500 * 10**6,
).transact({"from": hot_wallet.address})
assert_transaction_success_with_explanation(web3, tx_hash)
hot_wallet.sync_nonce(web3)  # Sync nonce again, as the manual management is off

Note

This class is not thread safe. If multiple threads try to sign transactions at the same time, nonce tracking may be lost.

See also how to create private keys from command line.

Attributes summary

address

Ethereum address of the wallet.

private_key

The private key as plain text.

Methods summary

__init__(account)

Create a hot wallet from a local account.

allocate_nonce()

Get the next free available nonce to be used with a transaction.

create_for_testing(web3[, test_account_n, ...])

Creates a new hot wallet and seeds it with ETH from one of well-known test accounts.

fill_in_gas_price(web3, tx)

Fills in the gas value fields for a transaction.

from_private_key(key)

Create a hot wallet from a private key that is passed in as a hex string.

get_main_address()

get_native_currency_balance(web3)

Get the balance of the native currency (ETH, BNB, MATIC) of the wallet.

sign_bound_call_with_new_nonce(func[, ...])

Signs a bound Web3 Contract call.

sign_transaction_with_new_nonce(tx)

Signs a transaction and allocates a nonce for it.

sync_nonce(web3)

Initialise the current nonce from the on-chain data.

transact_and_broadcast_with_contract(func[, ...])

Transacts with a contract, broadcasts transaction.

transact_with_contract(func, *args, **kwargs)

Call a contract function.

__init__(account)[source]

Create a hot wallet from a local account.

Parameters

account (eth_account.signers.local.LocalAccount) –

property address: eth_typing.evm.HexAddress

Ethereum address of the wallet.

property private_key: hexbytes.main.HexBytes

The private key as plain text.

sync_nonce(web3)[source]

Initialise the current nonce from the on-chain data.

Parameters

web3 (web3.main.Web3) –

allocate_nonce()[source]

Get the next free available nonce to be used with a transaction.

Ethereum tx nonces are a counter.

Increase the nonce counter

Return type

int

sign_transaction_with_new_nonce(tx)[source]

Signs a transaction and allocates a nonce for it.

Example:

web3 = Web3(mev_blocker_provider)
wallet = HotWallet.create_for_testing(web3)

# Send some ETH to zero address from
# the hot wallet
signed_tx = wallet.sign_transaction_with_new_nonce({
    "from": wallet.address,
    "to": ZERO_ADDRESS,
    "value": 1,
    "gas": 100_000,
    "gasPrice": web3.eth.gas_price,
})
tx_hash = web3.eth.send_raw_transaction(signed_tx.rawTransaction)
Parameters

tx (dict) – Ethereum transaction data as a dict. This is modified in-place to include nonce.

Returns

A transaction payload and nonce with used to generate this transaction.

Return type

eth_defi.hotwallet.SignedTransactionWithNonce

sign_bound_call_with_new_nonce(func, tx_params=None, web3=None, fill_gas_price=False)[source]

Signs a bound Web3 Contract call.

Example:

bound_func = busd_token.functions.transfer(user_2, 50*10**18)  # Transfer 50 BUDF
signed_tx = hot_wallet.sign_bound_call_with_new_nonce(bound_func)
web3.eth.send_raw_transaction(signed_tx.rawTransaction)

With manual gas estimation:

approve_call = usdc.contract.functions.approve(quickswap.router.address, raw_amount)
gas_estimation = estimate_gas_fees(web3)
tx_gas_parameters = apply_gas({"gas": 100_000}, gas_estimation)  # approve should not take more than 100k gas
signed_tx = hot_wallet.sign_bound_call_with_new_nonce(approve_call, tx_gas_parameters)

Another example that fills in gas price automatically (but not gas limit):

bound_func = vault.settle_via_trading_strategy_module()
signed_tx_2 = self.hot_wallet.sign_bound_call_with_new_nonce(
    bound_func,
    tx_params={"gas": DEFAULT_LAGOON_SETTLE_GAS},
    web3=web3,
    fill_gas_price=True
)

See also

Parameters
  • func (web3.contract.contract.ContractFunction) – Web3 contract function that has its arguments bound

  • tx_params (Optional[dict]) – Transaction parameters like gas

  • web3 (Optional[web3.main.Web3]) – Needed for gas price estimation

  • fill_gas_price – Fill the gas price automatically.

Returns

A signed transaction with debugging details like used nonce.

Return type

eth_defi.hotwallet.SignedTransactionWithNonce

get_native_currency_balance(web3)[source]

Get the balance of the native currency (ETH, BNB, MATIC) of the wallet.

Useful to check if you have enough cryptocurrency for the gas fees.

Parameters

web3 (web3.main.Web3) –

Return type

decimal.Decimal

transact_with_contract(func, *args, **kwargs)[source]

Call a contract function.

  • Construct a tx payload ready for web3.eth.send_raw_transaction, signed using this hot wallet’s private key

  • Remember to call sync_nonce() before calling this method.

Example:

# Approve USDC deposit to a vault contract
deposit_amount = 500 * 10 ** 6
signed_tx = hot_wallet_user.transact_with_contract(
    usdc.contract.functions.approve,
    Web3.to_checksum_address(vault.rebalance_address),
    deposit_amount
)
tx_hash = web3.eth.send_raw_transaction(signed_tx.raw_transaction)
assert_transaction_success_with_explanation(web3, tx_hash)
Parameters

func (web3.contract.contract.ContractFunction) –

Return type

eth_defi.hotwallet.SignedTransactionWithNonce

transact_and_broadcast_with_contract(func, gas_limit=None)[source]

Transacts with a contract, broadcasts transaction.

  • Shorthand method

  • Build a contract function call transaction and signs it

  • Always use a correct manually managed nonce

Example sending USDC:

from eth_defi.token import fetch_erc20_details
usdc = fetch_erc20_details(web3, "0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48")  # Ethereum mainnet
bound_call = usdc.transfer("<to address here>", Decimal(2140))
tx_hash = hot_wallet.transact_and_broadcast_with_contract(bound_call)
print("Broadcasted:", tx_hash.hex())

Another example:

deployer = HotWallet.from_private_key(os.environ["PRIVATE_KEY"])
bound_func = module.functions.whitelistUniswapV3Router(uniswap_v3.swap_router.address, "Allow Uniswap v3")
tx_hash = deployer.transact_and_broadcast_with_contract(bound_func)
Returns

Transaction hash

Parameters
  • func (web3.contract.contract.ContractFunction) –

  • gas_limit (Optional[int]) –

Return type

hexbytes.main.HexBytes

static fill_in_gas_price(web3, tx)[source]

Fills in the gas value fields for a transaction.

  • Estimates raw transaction gas usage

  • Uses web3 methods to get the gas value fields for the dict

  • web3 offers different backends for this

  • likely queries the values from the node

Note

Mutates tx in place.

Note

Before calling this method, you need to set gas and chainId fields of tx.

Example:

private_key = os.environ["PRIVATE_KEY"]
# Configure direct-to-sequencer broadcast,
# use public Base node for reads
rpc_configuration_line = "mev+https://mainnet-sequencer.base.org https://mainnet.base.org"
web3 = create_multi_provider_web3(rpc_configuration_line)

assert web3.eth.chain_id == 8453  # Base

hot_wallet = HotWallet.from_private_key(private_key)
hot_wallet.sync_nonce(web3)

# As a test transaction, send very small amount of ETH
tx_data = {
    "chainId": web3.eth.chain_id,
    "from": hot_wallet.address,
    "to": "0x7612A94AafF7a552C373e3124654C1539a4486A8",  # Random addy
    "value": Web3.to_wei(Decimal("0.000001"), "ether"),
    "gas": 50_000,
}

hot_wallet.fill_in_gas_price(web3, tx_data)
signed_tx = hot_wallet.sign_transaction_with_new_nonce(tx_data)

# Blocks until included in a block
print("Broadcasting", signed_tx.hash.hex())
receipts = wait_and_broadcast_multiple_nodes_mev_blocker(
    web3.provider,
    txs=[signed_tx],
)

receipt = receipts[signed_tx.hash]
print(f"Transaction broadcasted:

{pformat(dict(receipt.items()))}”)

param tx

Transaction data as a dictionary.

Contains keys like to, data, gas.

return

Transaction data (mutated) with gas values filled in.

Parameters
  • web3 (web3.main.Web3) –

  • tx (dict) –

Return type

dict

static from_private_key(key)[source]

Create a hot wallet from a private key that is passed in as a hex string.

Add the key to web3 signing chain.

Example:

# Generated with  openssl rand -hex 32
wallet = HotWallet.from_private_key("0x54c137e27d2930f7b3433249c5f07b37ddcfea70871c0a4ef9e0f65655faf957")
Parameters

key (str) – 0x prefixed hex string

Returns

Ready to go hot wallet account

Return type

eth_defi.hotwallet.HotWallet

static create_for_testing(web3, test_account_n=0, eth_amount=1)[source]

Creates a new hot wallet and seeds it with ETH from one of well-known test accounts.

Shortcut method for unit testing.

Example:

web3 = Web3(test_provider)
wallet = HotWallet.create_for_testing(web3)

signed_tx = wallet.sign_transaction_with_new_nonce(
    {
        "from": wallet.address,
        "to": ZERO_ADDRESS,
        "value": 1,
        "gas": 100_000,
        "gasPrice": web3.eth.gas_price,
    }
)

tx_hash = web3.eth.send_raw_transaction(signed_tx.rawTransaction)
assert_transaction_success_with_explanation(web3, tx_hash)
Parameters

web3 (web3.main.Web3) –

Return type

eth_defi.hotwallet.HotWallet