Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

docs: new send txs guide #2919

Merged
merged 1 commit into from
Apr 21, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
29 changes: 6 additions & 23 deletions docs/examples.rst
Original file line number Diff line number Diff line change
Expand Up @@ -173,34 +173,17 @@ wei, then from wei to whatever you need.
Decimal('5')


Making transactions
-------------------
Sending transactions
--------------------

There are a few options for making transactions:
There are a few options for sending transactions:

- :meth:`~web3.eth.Eth.send_transaction`

Use this method if:
- you want to send ether from one account to another.

- :meth:`~web3.eth.Eth.send_raw_transaction`
- Calling :meth:`~web3.contract.ContractFunction.transact` on a contract function
- Utilizing :meth:`~web3.middleware.construct_sign_and_send_raw_middleware`

Use this method if:
- you want to sign the transaction elsewhere, e.g., a hardware wallet.
- you want to broadcast a transaction through another provider, e.g., Infura.
- you have some other advanced use case that requires more flexibility.

- :ref:`contract-functions`

Use these methods if:
- you want to interact with a contract. web3.py parses the contract ABI and makes those functions available via the ``functions`` property.

- :meth:`~web3.middleware.construct_sign_and_send_raw_middleware`

Use this middleware if:
- you want to automate signing when using ``w3.eth.send_transaction`` or ``ContractFunctions``.

.. NOTE:: The location of your keys (e.g., local or hosted) will have implications on these methods. Read about the differences :ref:`here <eth-account>`.
For more context, see the :doc:`transactions` Guide.


Looking up transactions
Expand Down
1 change: 1 addition & 0 deletions docs/toc.rst
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ Table of Contents

node
providers
transactions
examples
troubleshooting
web3.eth.account
Expand Down
187 changes: 187 additions & 0 deletions docs/transactions.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,187 @@
Sending Transactions
====================

.. note::

Prefer to view this code in a Jupyter Notebook? View the repo `here <https://github.com/wolovim/ethereum-notebooks/blob/master/Sending%20Transactions.ipynb>`_.

There are two methods for sending transactions using web3.py: :meth:`~web3.eth.Eth.send_transaction` and :meth:`~web3.eth.Eth.send_raw_transaction`. A brief guide:

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

* use :meth:`sign_transaction <eth_account.account.Account.sign_transaction>` + :meth:`~web3.eth.Eth.send_raw_transaction`

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

* configure :meth:`~web3.middleware.construct_sign_and_send_raw_middleware`, then
* use :meth:`~web3.eth.Eth.send_transaction`

#. Otherwise:

* load account via eth-account (:meth:`w3.eth.account.from_key(pk) <eth_account.account.Account.from_key>`), then
* use :meth:`~web3.eth.Eth.send_transaction`

Interacting with or deploying a contract?

* Option 1: :meth:`~web3.contract.ContractFunction.transact` uses :meth:`~web3.eth.Eth.send_transaction` under the hood
* Option 2: :meth:`~web3.contract.ContractFunction.build_transaction` + :meth:`sign_transaction <eth_account.account.Account.sign_transaction>` + :meth:`~web3.eth.Eth.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.

.. code-block:: python

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 :meth:`~web3.eth.Eth.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:

.. code-block:: python

from web3.middleware import construct_sign_and_send_raw_middleware
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(construct_sign_and_send_raw_middleware(acct2))
# pk also works: w3.middleware_onion.add(construct_sign_and_send_raw_middleware(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:

- build each transaction,
- :meth:`sign_transaction <eth_account.account.Account.sign_transaction>`, and
- then use :meth:`~web3.eth.Eth.send_raw_transaction`.

.. code-block:: python

# 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:

- executing the :meth:`~web3.contract.ContractFunction.transact` function, or
- :meth:`~web3.contract.ContractFunction.build_transaction`, then signing and sending the raw transaction.

.. code-block:: python

#########################################
#### 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"
2 changes: 1 addition & 1 deletion docs/web3.contract.rst
Original file line number Diff line number Diff line change
Expand Up @@ -736,7 +736,7 @@ enabled on a per-call basis - overriding the global flag. This ensures only expl
>>> response = myContract.functions.revertsWithOffchainLookup(myData).call(ccip_read_enabled=True)

Methods
~~~~~~~~~~
~~~~~~~

.. py:method:: ContractFunction.transact(transaction)

Expand Down
1 change: 1 addition & 0 deletions newsfragments/2919.doc.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Add a decision tree guide for sending transactions