Skip to content

Typescript based 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-typescript

Folders and files

NameName
Last commit message
Last commit date

Latest commit

Β 

History

7 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 smart 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, as well as familiar with Typescript. For the vanilla JS version please visit here

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

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

Features of the Factory Starter Kit

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 your contracts with a simil master-detail UI

πŸ— Scaffold-Eth Typescript

This is based on the typescript repo of scaffold.eth. The directories that you'll use are:

packages/vite-app-ts/
packages/hardhat-ts/

πŸ„β€β™‚οΈ Building on scaffold-eth-typescript

Prerequisites: Node plus Yarn and Git

clone/fork πŸ— scaffold-eth:

git clone https://github.com/dvinubius/contract-factory-tutorial-typescript.git

install:

yarn

in a new terminal window, start a local hardhat node:

yarn chain

deploy your contracts:

yarn contracts:build
yarn deploy

in a new terminal window, start your frontend:

yarn start

🌍 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 MainPage.tsx 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 made by the useEventListener hook in MainPage.tsx. The retrieval is repeated on each block but can be configured much more specifically to suit your needs. Read more about it in the docs.

It's good to keep 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.

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 2022-01-18 at 22 32 48

πŸ€“ Very optional nerdy side quest for TS-enthusiasts

At the moment you don't get strong typing and autocomplete when attempting to (read/write) interact with YourContract, like you do with YourContractFactory. At least not out of the box.

Strong typing is only available out-of-the box for contracts deployed by yarn deploy. In our factory setup you deploy the factory via a script, but all YourContract instances are created by the factory contract when users interact with it.

You can still obtain strong typing by doing the following:

  • include YourContract in the deployment script in packages/hardhat-ts/deploy. The code for that is commented out. Uncomment it. This will deploy one instance of YourContract when the deploy script runs.

  • Uncomment the related code in packages/vite-app-ts/src/config/contractConnectorConfig.ts . You will easily identify that code πŸ˜‰

  • Run yarn deploy --reset

You'll have an instance of YourContract deployed somewhere, but your App won't show it in any way. However, your IDE will now know you that YourContract has a purpose, a setPurpose etc.

Observe how yourContractRaw is now typed more precisely:

Screenshot 2022-01-18 at 23 00 27

Screenshot 2022-01-18 at 23 00 51

Don't forget to put the comments back in when you deploy your dApp to mainnet. Or you will **totally waste gas ⛽️ πŸ’° ** on that lonely YourContract instance!

This may be possible to solve more elegantly in the future, watch out for updates!

πŸ‘¨πŸ»β€πŸ’» πŸ€“ Knitty Gritty Aside - "manually" created contract objects

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 😎 πŸ§‰

πŸ“ Notice the difference in working with YourContractFactory and YourContract:

The factory contract object is obtained neatly via a hook. Here is the pattern, in a simplified version

const ethersContext = useEthersContext();

const yourContractFactory = useAppContracts(
    'YourContractFactory',
    ethersContext.chainId
);

For the yourContract object we make use of injectableAbis, which are configured to give you the abi for YourContract. With that equipped, we create a "raw" BaseContract which we then connect to a signer.

Here is a simplified version of the code:

const yourContractRaw = new BaseContract(
    contractAddress,
    abi,
    provider
);

const yourContract = yourContractRaw.connect(signer);

The useAppContracts() hook cannot be used to interact with contracts that were not deployed via yarn deploy.

If you wonder how injectableAbis comes to know the YourContract abi, it's due to the deployment setup of the hardhat project.

🧐 Notice the file vite-app-ts/src/generated/injectable-abis/hardhat_non_deployed_contracts.json

This one is usually not present in scaffold-eth 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.

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 take the contract abi & address, we create a BaseContract object, so that we can read from that particular contract instance.

Go to ContractItem.tsx 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.

πŸ“ πŸ€“ Observe that this time we didn't connect any signer, since we only had to read from the contract. Also, we didn't specify a fourth argument to useContractReader. That one is with options on how the value should be updated. But we don't expect the owner to change while this React component is displayed, so we provide no explicit update options. When no updateOptions are provided, the default behaviour of the hook is to only update once every 100 blocks, which is acceptable in a situation like ours.

πŸ§žβ€β™€οΈ Side quest - Make this perfect! πŸ’―

Find out how to useEventListener() such that it only updates if the owner actually changes. Perfect the code. Can you test if it works?

πŸ‘©β€πŸ’» 😍 πŸ§‘β€πŸ’» 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. Do the required fixes to make these changes effective (you will have to remove / comment out some of the initial code in order to make Typescript happy)

You should now see contract items like this:

Screenshot 2022-01-19 at 00 28 35

β˜‘οΈ 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 MainPage.tsx, 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. It's not in typescript but still worth a dig into.

  • 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.

With the flexibility that eth-hooks V4 gives you in the current setup, you'd be able to optimize even easier.

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

Typescript based 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

No packages published