Transactions
There are a handful of ways to interact with transactions in web3.py. See the Web3.eth module for a full list of transaction-related methods. Note that you may also batch requests that read transaction data, but not send new transactions in a batch request.
The rest of this guide covers the decision tree for how to send a transaction.
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:
Want to sign a transaction offline or send pre-signed transactions?
Are you primarily using the same account for all transactions and would you prefer to save a few lines of code?
configure the
build
method forSignAndSendRawMiddlewareBuilder
, then
Otherwise:
load account via eth-account (
w3.eth.account.from_key(pk)
), then
Interacting with or deploying a contract?
Option 1:
transact()
usessend_transaction()
under the hoodOption 2:
build_transaction()
+sign_transaction
+send_raw_transaction()
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.inject(SignAndSendRawMiddlewareBuilder.build(acct2), layer=0)
# pk also works: w3.middleware_onion.inject(SignAndSendRawMiddlewareBuilder.build(pk), layer=0)
# 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:
build each transaction,
sign_transaction
, andthen use
send_raw_transaction()
.
# 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.raw_transaction)
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:
executing the
transact()
function, orbuild_transaction()
, then signing and sending the raw transaction.
#########################################
#### 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.raw_transaction)
w3.eth.wait_for_transaction_receipt(tx_hash)
assert billboard.functions.message().call() == "gn"