Sending Transactions

Note

Prefer to view this code in a Jupyter Notebook? View the repo here.

There are two methods for sending transactions using web3.py: send_transaction() and send_raw_transaction(). A brief guide:

  1. Want to sign a transaction offline or send pre-signed transactions?

  2. Are you primarily using the same account for all transactions and would you prefer to save a few lines of code?

  3. Otherwise:

Interacting with or deploying a contract?

An example for each can be found below.

Chapter 0: w3.eth.send_transaction with eth-tester

Many tutorials use eth-tester (via EthereumTesterProvider) for convenience and speed of conveying ideas/building a proof of concept. Transactions sent by test accounts are auto-signed.

from web3 import Web3, EthereumTesterProvider

w3 = Web3(EthereumTesterProvider())

# eth-tester populates accounts with test ether:
acct1 = w3.eth.accounts[0]

some_address = "0x0000000000000000000000000000000000000000"

# when using one of its generated test accounts,
# eth-tester signs the tx (under the hood) before sending:
tx_hash = w3.eth.send_transaction({
    "from": acct1,
    "to": some_address,
    "value": 123123123123123
})

tx = w3.eth.get_transaction(tx_hash)
assert tx["from"] == acct1

Chapter 1: w3.eth.send_transaction + signer middleware

The send_transaction() method is convenient and to-the-point. If you want to continue using the pattern after graduating from eth-tester, you can utilize web3.py middleware to sign transactions from a particular account:

from web3.middleware import SignAndSendRawMiddlewareBuilder
import os

# Note: Never commit your key in your code! Use env variables instead:
pk = os.environ.get('PRIVATE_KEY')

# Instantiate an Account object from your key:
acct2 = w3.eth.account.from_key(pk)

# For the sake of this example, fund the new account:
w3.eth.send_transaction({
    "from": acct1,
    "value": w3.to_wei(3, 'ether'),
    "to": acct2.address
})

# Add acct2 as auto-signer:
w3.middleware_onion.add(SignAndSendRawMiddlewareBuilder.build(acct2))
# pk also works: w3.middleware_onion.add(SignAndSendRawMiddlewareBuilder.build(pk))

# Transactions from `acct2` will then be signed, under the hood, in the middleware:
tx_hash = w3.eth.send_transaction({
    "from": acct2.address,
    "value": 3333333333,
    "to": some_address
})

tx = w3.eth.get_transaction(tx_hash)
assert tx["from"] == acct2.address

# Optionally, you can set a default signer as well:
# w3.eth.default_account = acct2.address
# Then, if you omit a "from" key, acct2 will be used.

Chapter 2: w3.eth.send_raw_transaction

if you don’t opt for the middleware, you’ll need to:

# 1. Build a new tx
transaction = {
    'from': acct2.address,
    'to': some_address,
    'value': 1000000000,
    'nonce': w3.eth.get_transaction_count(acct2.address),
    'gas': 200000,
    'maxFeePerGas': 2000000000,
    'maxPriorityFeePerGas': 1000000000,
}

# 2. Sign tx with a private key
signed = w3.eth.account.sign_transaction(transaction, pk)

# 3. Send the signed transaction
tx_hash = w3.eth.send_raw_transaction(signed.rawTransaction)
tx = w3.eth.get_transaction(tx_hash)
assert tx["from"] == acct2.address

Chapter 3: Contract transactions

The same concepts apply for contract interactions, at least under the hood.

Executing a function on a smart contract requires sending a transaction, which is typically done in one of two ways:

#########################################
#### SMOL CONTRACT FOR THIS EXAMPLE: ####
#########################################
# // SPDX-License-Identifier: MIT
# pragma solidity 0.8.17;
#
# contract Billboard {
#     string public message;
#
#     constructor(string memory _message) {
#         message = _message;
#     }
#
#     function writeBillboard(string memory _message) public {
#         message = _message;
#     }
# }

# After compiling the contract, initialize the contract factory:
init_bytecode = "60806040523480156200001157600080fd5b5060..."
abi = '[{"inputs": [{"internalType": "string","name": "_message",...'
Billboard = w3.eth.contract(bytecode=init_bytecode, abi=abi)

# Deploy a contract using `transact` + the signer middleware:
tx_hash = Billboard.constructor("gm").transact({"from": acct2.address})
receipt = w3.eth.get_transaction_receipt(tx_hash)
deployed_addr = receipt["contractAddress"]

# Reference the deployed contract:
billboard = w3.eth.contract(address=deployed_addr, abi=abi)

# Manually build and sign a transaction:
unsent_billboard_tx = billboard.functions.writeBillboard("gn").build_transaction({
    "from": acct2.address,
    "nonce": w3.eth.get_transaction_count(acct2.address),
})
signed_tx = w3.eth.account.sign_transaction(unsent_billboard_tx, private_key=acct2.key)

# Send the raw transaction:
assert billboard.functions.message().call() == "gm"
tx_hash = w3.eth.send_raw_transaction(signed_tx.rawTransaction)
w3.eth.wait_for_transaction_receipt(tx_hash)
assert billboard.functions.message().call() == "gn"