Skip to content

Starter Kit & Tutorial for an Ethereum app with multiple user-created contracts. Smart contract factory pattern. Scaffold-Eth based.

License

Notifications You must be signed in to change notification settings

dvinubius/contract-factory-tutorial

Folders and files

NameName
Last commit message
Last commit date

Latest commit

Β 

History

16 Commits
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 

Repository files navigation

πŸ— Tutorial / Starter Kit: Factory dApp - Build a Smart Contract Manager

A Starter Kit for dApps where users can create and manage multiple contracts

  • 🍦 lean vanilla smart contract factory 🏭
  • πŸ’ͺ use-case flexibility 🌍
  • 🧐 mini tutorial 🧭

In order to go through the tutorial it helps if you're already familiar with the amazing scaffold-eth buidl tools. If you're not, for following this tutorial it is recommended that you're at least a web developer with some basic Solidity experience.

πŸ€“ The tutorial presents the essential aspects quite in detail.

If you're an absolute noob to web3, check out the Ethereum Speed Run.

Setup

Solidity & React are set up to

  • create contracts
  • browse created contracts
  • interact with created contracts

πŸ§ͺ Quickly experiment with Solidity using a frontend that adapts to your smart contract:

image

πŸš€ Start with a basic master-detail UI, customize it for your needs

πŒ‹ Debug created contracts with a simil master-detail UI

πŸ„β€β™‚οΈ Quick Start

Manual setup

Prerequisites: Node plus Yarn and Git

clone/fork πŸ— scaffold-eth:

git clone -b factory-setup https://github.com/scaffold-eth/scaffold-eth-examples.git

install and start your πŸ‘·β€ Hardhat chain:

cd scaffold-eth-examples
yarn install
yarn chain

in a second terminal window, start your πŸ“± frontend:

cd scaffold-eth
yarn start

in a third terminal window, πŸ›° deploy your contract:

cd scaffold-eth
yarn deploy

🌍 You need an RPC key for production deployments/Apps, create an Alchemy account and replace the value of ALCHEMY_KEY = xxx in packages/react-app/src/constants.js

πŸ” Edit your smart contract YourContract.sol in packages/hardhat/contracts

πŸ“ Edit your frontend App.jsx in packages/react-app/src

πŸ’Ό Edit your deployment scripts in packages/hardhat/deploy

πŸ“± Open http://localhost:3000 to see the app

Tutorial

Goals

Whether you're a web3 noob or experienced dev, the following tutorial is a good way to

  • get more familiar with scaffold-eth
  • learn some ideas for design patterns

If you're in for the tutorial, you're in for a treat! 🍭 πŸ€“ Here's what we'll look at

  1. Explore the setup - what can a user do?
  2. Technicalities - how is it built so far?
  3. UX challenges - where can you take it from here?

1. 🀩 Explore the setup

πŸ”– Create and track contracts that each have a "purpose" variable

In the "Your Contracts" tab, create a new contract. The dialog keeps the user informed. This is a common UX pattern beyond the generic tx status notifications.

πŸ—Ί Browse all contracts in a list : <CreatedContractsUI/>

Your new contract should have appeared in the UI. Create a second contract. Observe how the list updates automatically as soon as the transaction is mined.

List items right now only contain data that was available at the moment when the contracts were created.

πŸ•Ή Interact with any particular contract in a detail view: <YourContract/>

Click on a contract to enter the detailed view.

Click any button to change its purpose.

πŸ” Access controls are in place

Open a new browser window in incognito mode, go to localhost:3000.

Here you won't be able to change the purpose of existing contracts. In this incognito window you are someone else (notice the address at the top of the window). The current signer is not the owner of those contracts.

πŒ‹ The Debug UI enables raw interaction with the factory and any created contract instance.

🧐 Check out the "Debug Contracts" tab.

  • See what the public functions of YourContractFactory allow you to do. Do you find them useful?
  • What else might be useful to have in there?

** πŸ‘©β€πŸ’» 😍 UX 😍 πŸ§‘β€πŸ’» Frontend Side Quest - Improve UX when setting the purpose **

Return to the UI where you have 2 buttons to set the purpose of a contract.

Issue: if you click any of the buttons, both show a spinner while the TX is pending.

Screenshot 2021-12-24 at 10 04 25

Your challenge: find a way to obtain this instead

Screenshot 2021-12-24 at 10 06 45

2. πŸ€“ Technicalities

YourContract.sol

  • The core functionality of your app

  • Right now it only has a purpose that can be changed by the owner.

YourContractFactory.sol

  • Creates instances of YourContract and keeps track of them all.

  • Kept as lean as possible.

The setup allows users to create their own YourContracts and control them independent from the factory contract.

As a starting point for developing dApps with this setup, we want loose coupling:

  • keep created contracts unaware of the factory
  • keep the factory unaware of what created contracts actually do

All our factory needs to know is the addresses of created contracts

Screenshot 2021-12-25 at 21 18 36

Screenshot 2021-12-25 at 21 19 33

We emit events on contract creation, so the frontend can easily retrieve a list of all.

Screenshot 2021-12-25 at 21 20 20 Screenshot 2021-12-25 at 21 19 58

We've included useful data in those events.

πŸ“‡ Readable Names

πŸ‘©β€πŸ’» 😍 UX 😍 πŸ§‘β€πŸ’» In a dApp based on a setup like ours, user-given individual contract names are probably a good feature to have.

We've adopted a simple and cheap solution: the user-given name is put in the creation event. If the name doesn't need to change over time this approach works fine.

This retrieval happens via a single RPC call by using the useEventListener hook. The retrieval happens once on every new block.

It's good to something like this in mind in order to have your app scale well when the UI is rich and lots of users are using it at the same time. It may produce many RPC calls.

For contract state, like "purpose", contract owner, etc. the frontend uses the address of a particular YourContract intance address to read from the contract, which under the hood makes separate RPC calls.

This is what we do in <YourContract/>

Screenshot 2021-12-25 at 21 36 39

πŸ‘¨πŸ»β€πŸ’» πŸ€“ Knitty Gritty Aside on Code Design : Injection! πŸ’‰

You may skip this section and tackle Challenge 1 below, if you're eager to code some more. Just make sure to return here some time later.

Understanding this is crucial if you're serious about building factory pattern dApps, so you'll need to do it anyway. But no pressure right now 😎 πŸ§‰

πŸ“ If you're familiar with scaffold-eth, notice the pattern: we dynamically inject the YourContract abi and the particular contract instance address into a locally created contractConfig.

After that it's business as usual with useContractLoader.

Without the injection there would be no abi at all for YourContract in the config.

Why?

Because we never deployed YourContract in our hardhat setup.

🧐 Looking more closely you'll notice the file react-app/contracts/hardhat_non_deployed_contracts.json

This one is usually not present because we usually include all our contracts when we yarn deploy. Each one gets a fixed deployment address there.

But in our factory setup, the YourContract instances are created on-chain. Only then they get their addresses, which are stored both in the factory contract state and in the contract creation events.

So yarn deploy, instead of deploying any particular YourContract, just makes the abi of YourContract available to the frontend for later use. It puts it into the json file above. Later it can be injected at runtime in combination with a specific address.

Challenge 1 -- Track purpose changes

Lets show our users when and how purpose changes happen!

Find the Solidity code related to SetPurpose events. Uncomment it.

Redeploy with yarn deploy --reset

Find the React code that displays SetPurpose events in <YourContract/>. It is commented out, uncomment it.

Create a new contract. Change its purpose.

Now, for any particular instance of YourContract, our app

  • displays contract events
  • displays contract state
  • enables contract interaction

πŸ” πŸ§‘β€πŸ’» πŸ” Ownership

Our factory ensures that the user who creates a contract also becomes the owner

Screenshot 2021-12-25 at 22 25 29

Without this code, the factory would remain the owner of all YourContract instances.

3. πŸ‘©β€πŸ’» 😍 πŸ§‘β€πŸ’» UX CHALLENGES

Challenge 2 -- Contract details in the list item

Suppose we wanted to display the owner of any contract in the master view. Probably your users want to easily identify the contracts they've created.

The owner can change over time, unlike the creator. We can't build this feature by using contract creation event data.

πŸ€” How do we get the owners of all contracts?

In each <ContractItem />, we apply the pattern from <YourContract/>: πŸ’‰ we dynamically inject the abi & address, so we can read from each particular contract instance.

Go to ContractItem.jsx and find the code that fetches owner data. Uncomment it. Find the code that displays this data. Uncomment that.

Now you should see owner information in the contracts list of the master view.

πŸ‘©β€πŸ’» 😍 πŸ§‘β€πŸ’» Recognize my contract

Owner addresses are quite hard to read. In the contracts list, let's mark items which belong to the current user so they may be identified more easily.

Go to the code inside the <ContractItem/> component. Find the commented code which marks the item when the contract owner is the current user. Uncomment it.

You should now see contract items like this:

β˜‘οΈ Test the functionality by creating contracts from an incognito window. Compare the views of different users.

Challenge 3 -- Scalable UI (hard)

What if there were 100 contracts?

As soon as you receive the creation event data in App.jsx, would you make a total of 100 requests for reading the owner of each contract within its <ContractItem /> component?

It's probably better if we retrieve the owner of a particular contract item only when the item is actually in view.

Here is a simple solution for that:

πŸ‘©β€πŸ’» 😍 πŸ§‘β€πŸ’» Pagination for the contract list

  • This would improve the UX a lot, whether we display contract owners or not
  • If you allow n contracts per page, only n calls to read the owner will be made at once.

Other advanced UX Side Quests

πŸ‘©β€πŸ’» 😍 πŸ§‘β€πŸ’» Allow users to filter contracts by name in the list view

  • use an input field
  • how do you combine this with the pagination feature?

πŸ‘©β€πŸ’» 😍 πŸ§‘β€πŸ’» Allow users to filter contracts by only listing their own ones

  • use a switch or checkbox "only mine"
  • how do you combine this with the pagination feature?

Final Thoughts

0 Key Improvements

A factory setup can quickly get very complex, especially if you want to provide good UX.

Your real-world project will probably need code design improvements in order to be able to scale well and be easy to use.

  • good routing
  • efficient data retrieval (RPC nodes)
  • different empty states (waiting for data, data not available, no account connected)
  • clean code

Some design patterns to help you grow can be found in this repo.

  • master-detail UI pattern with shareable links to detail pages (routing with react router v6)
  • a pattern on how to create a contract specific react context when opening a contract in the UI
  • strategies to minimize number of RPC calls while using eth-hooks v2.

eth-hooks v4 is a much more advanced toolkit but it requires you to use the typescript flavored scaffold-eth.

1 Opinionated Solutions

Our approaches in solving UX challenges depend on many factors. If your project is going to have lots of complex data to retrieve, you'll probably also use a subgraph or other blockchain indexing tools. These are more capable than the useEventListener hook we've used here. This would impact how you approach scaling your dApp.

2 Factory Use Cases

There are many use cases for a setup similar to ours here. Take Uniswap:

  • users create liquidity pools
  • each liquidity pool is a separate contract

Sometimes the created contracts may be more tightly coupled to the factory - it depends on the use case: how much control over the contracts should a user have / should the factory keep?

** πŸ§™β€β™‚οΈ πŸ§β€β™€οΈ πŸ§žβ€β™‚οΈ Advanced Contract Design Quest: Dig into the UniswapV3 Docs. Here the factory is indeed more tightly coupled to the created pools.

  • Why, do you think, is that?
  • How does Uniswap handle fees?
    • pool owner fees?
    • uniswap fees?

Happy Coding!

πŸ“š Documentation

Documentation, tutorials, challenges, and many more resources, visit: docs.scaffoldeth.io

πŸ”­ Learning Solidity

πŸ“• Read the docs: https://docs.soliditylang.org

πŸ“š Go through each topic from solidity by example editing YourContract.sol in πŸ— scaffold-eth

πŸ“§ Learn the Solidity globals and units

πŸ›  Buidl

Check out all the active branches, open issues, and join/fund the 🏰 BuidlGuidl!

πŸ’¬ Support Chat

Join the telegram support chat πŸ’¬ to ask questions and find others building with πŸ— scaffold-eth!


πŸ™ Please check out our Gitcoin grant too!

About

Starter Kit & Tutorial for an Ethereum app with multiple user-created contracts. Smart contract factory pattern. Scaffold-Eth based.

Topics

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages