Submitted for the ETH Toronto online hackathon!
In many ways, our food system is broken. To start off, food prices are on the rise. In Canada, statistics show that food prices are up 10 percent from last year, which has led to an increase in the number of people experiencing food insecurity. Secondly, the environmental impacts due to big agriculture have created problems such as soil erosion, water pollution, and lack of biodiversity. Finally, almost one-third of food produced globally is lost or wasted, while over 780 million people experience hunger.
To address these issues, FarmShare provides local communities with more options through community supported agriculture, also known as CSAs. Traditional CSAs are costly to maintain, but by using blockchain technology, we are able to revolutionize CSAs by tapping into the many resources local communities can provide. Taking inspiration from the Eight Forms of Capital, this includes not only financial resources, but also skills (experiential capital), labor (living capital), connections (social capital) and community (cultural capital).
The core features of our app are:
- Each community will create a Safe multisig wallet upon registering, owned by the farmers in the community. This is the first step towards unlocking a world of public goods funding through web3 protocols, backed by the transparency of blockchains.
- Anyone can join the community to contribute. Account abstraction and social login using Web3Auth and ZeroDev make it easy for anyone to create an account and start contributing resources such as time, skills, labor and financial capital. Our paymaster pays the gas for any transactions that interact with our contracts through the Ethereum Attestation Service.
- Farmers create tasks using EAS, which anyone can then fund with rewards. Once a task is complete, the farmer can attest to it and the user will receive tokens which can be redeemed for shares of food harvested from the farm. Plus, anyone who believes the task is valuable can add additional rewards by funding it with ERC-20 tokens.
- In the future, streaming payments will open up even more funding opportunities for the farm. Farmers can apply for public goods funding or receive subscription payments in USDC as an upfront payment before the season starts.
- Base: Help bring Base to the masses with creating a consumer focused product
- Consumer-facing: FarmShare is an application for farmers and food lovers, not just crypto-natives. Agriculture is the least IT-intensive sector of the economy, and has largely gone unserved by Web2. Yet everyone needs to eat!
- User-friendly: We use Web3Auth and ZeroDev to ease the on-boarding process and abstract away transaction complexity and gas costs, providing a simple, intuitive experience. Specifically, our paymaster sponsors any transaction that interacts with EAS or our token contract.
- Deployed on Base: We deployed our contracts on the Base Goerli testnet, which was already supported by EAS and ZeroDev. We plan on continuing to develop this project after the hackathon, and will eventually deploy on Base mainnet.
- Ethereum Attestation Service: Building with attestations
- Attestations: Practically every action on FarmShare involves attestations - from user registration to task creation and completion. Rather than store all of this data ourselves, when our contracts need to access user or task records they call the EAS contract to retrieve the attested information, avoiding duplication of data.
- Identity and skills: When users register, they attest basic identity info like name/location, and can also attest to their skills. When users apply to complete tasks, farmers can view their attested skills to evaluate if they are qualified. We also implemented the back-end architecture to support endorsements of skills between users.
- Resolver contracts: Other than the FarmShare tokens, all of our contracts inherit the EAS SchemaResolver and implement the bulk of their logic in the
onAttest
andonRevoke
functions. - EAS as architecture: Beyond just using attestations for data storage, we use the GraphQL endpoint to query and retrieve data for our frontend. Also, when not approving or transferring tokens, the user almost exclusively interacts with the EAS smart contract via the SDK rather than our own contracts directly. This architecture allows us to minimize our own smart contract logic and surface the power of EAS.
While for this hackathon we focused primarily on the user on-boarding and completing tasks for farm shares, we plan on continuing to develop FarmShare going forward. Our next steps are:
- Implement a marketplace, where users can exchange their rewards for different types of food on the farm.
- Implement new funding mechanisms for further incentivizing the creation of new CSAs by farmers, such as public goods funding via Allo protocol and streaming subscription plans using Superfluid or Sablier.
🧪 An open-source, up-to-date toolkit for building decentralized applications (dapps) on the Ethereum blockchain. It's designed to make it easier for developers to create and deploy smart contracts and build user interfaces that interact with those contracts.
⚙️ Built using NextJS, RainbowKit, Hardhat, Wagmi, and Typescript.
- ✅ Contract Hot Reload: Your frontend auto-adapts to your smart contract as you edit it.
- 🔥 Burner Wallet & Local Faucet: Quickly test your application with a burner wallet and local faucet.
- 🔐 Integration with Wallet Providers: Connect to different wallet providers and interact with the Ethereum network.
- Requirements
- Quickstart
- Deploying your Smart Contracts to a Live Network
- Deploying your NextJS App
- Interacting with your Smart Contracts: SE-2 Custom Hooks
- Disabling Type & Linting Error Checks
- Contributing to Scaffold-ETH 2
Before you begin, you need to install the following tools:
- Node (v18 LTS)
- Yarn (v1 or v2+)
- Git
To get started with Scaffold-ETH 2, follow the steps below:
- Clone this repo & install dependencies
git clone https://github.com/scaffold-eth/scaffold-eth-2.git
cd scaffold-eth-2
yarn install
- Run a local network in the first terminal:
yarn chain
This command starts a local Ethereum network using Hardhat. The network runs on your local machine and can be used for testing and development. You can customize the network configuration in hardhat.config.ts
.
- On a second terminal, deploy the test contract:
yarn deploy
This command deploys a test smart contract to the local network. The contract is located in packages/hardhat/contracts
and can be modified to suit your needs. The yarn deploy
command uses the deploy script located in packages/hardhat/deploy
to deploy the contract to the network. You can also customize the deploy script.
- On a third terminal, start your NextJS app:
yarn start
Visit your app on: http://localhost:3000
. You can interact with your smart contract using the contract component or the example ui in the frontend. You can tweak the app config in packages/nextjs/scaffold.config.ts
.
Run smart contract test with yarn hardhat:test
- Edit your smart contract
YourContract.sol
inpackages/hardhat/contracts
- Edit your frontend in
packages/nextjs/pages
- Edit your deployment scripts in
packages/hardhat/deploy
Once you are ready to deploy your smart contracts, there are a few things you need to adjust.
- Select the network
By default, yarn deploy
will deploy the contract to the local network. You can change the defaultNetwork in packages/hardhat/hardhat.config.ts.
You could also simply run yarn deploy --network target_network
to deploy to another network.
Check the hardhat.config.ts
for the networks that are pre-configured. You can also add other network settings to the hardhat.config.ts
file. Here are the Alchemy docs for information on specific networks.
Example: To deploy the contract to the Sepolia network, run the command below:
yarn deploy --network sepolia
- Generate a new account or add one to deploy the contract(s) from. Additionally you will need to add your Alchemy API key. Rename
.env.example
to.env
and fill the required keys.
ALCHEMY_API_KEY="",
DEPLOYER_PRIVATE_KEY=""
The deployer account is the account that will deploy your contracts. Additionally, the deployer account will be used to execute any function calls that are part of your deployment script.
You can generate a random account / private key with yarn generate
or add the private key of your crypto wallet. yarn generate
will create a random account and add the DEPLOYER_PRIVATE_KEY to the .env file. You can check the generated account with yarn account
.
- Deploy your smart contract(s)
Run the command below to deploy the smart contract to the target network. Make sure to have some funds in your deployer account to pay for the transaction.
yarn deploy --network network_name
- Verify your smart contract
You can verify your smart contract on Etherscan by running:
yarn verify --network network_name
Hint: We recommend connecting your GitHub repo to Vercel (through the Vercel UI) so it gets automatically deployed when pushing to main
.
If you want to deploy directly from the CLI, run yarn vercel
and follow the steps to deploy to Vercel. Once you log in (email, github, etc), the default options should work. It'll give you a public URL.
If you want to redeploy to the same production URL you can run yarn vercel --prod
. If you omit the --prod
flag it will deploy it to a preview/test URL.
Make sure to check the values of your Scaffold Configuration before deploying your NextJS App.
You can configure different settings for your dapp at packages/nextjs/scaffold.config.ts
.
export type ScaffoldConfig = {
targetNetwork: chains.Chain;
pollingInterval: number;
alchemyApiKey: string;
walletConnectProjectId: string;
onlyLocalBurnerWallet: boolean;
walletAutoConnect: boolean;
// your dapp custom config, eg:
// tokenIcon : string;
};
The configuration parameters are described below, make sure to update the values according to your needs:
-
targetNetwork
Sets the blockchain network where your dapp is deployed. Use values fromwagmi/chains
. -
pollingInterval
The interval in milliseconds at which your front-end application polls the RPC servers for fresh data. Note that this setting does not affect the local network. -
alchemyApiKey
Default Alchemy API key from Scaffold ETH 2 for local testing purposes.
It's recommended to obtain your own API key from the Alchemy Dashboard and store it in an environment variable:NEXT_PUBLIC_ALCHEMY_API_KEY
at\packages\nextjs\.env.local
file. -
walletConnectProjectId
WalletConnect's default project ID from Scaffold ETH 2 for local testing purposes. It's recommended to obtain your own project ID from the WalletConnect website and store it in an environment variable:NEXT_PUBLIC_WALLET_CONNECT_PROJECT_ID
at\packages\nextjs\.env.local
file. -
onlyLocalBurnerWallet
Controls the networks where the Burner Wallet feature is available. This feature provides a lightweight wallet for users.true
=> Use Burner Wallet only on hardhat network.false
=> Use Burner Wallet on all networks.
-
walletAutoConnect
Set it totrue
to activate automatic wallet connection behavior:- If the user was connected into a wallet before, on page reload it reconnects automatically.
- If user is not connected to any wallet, on reload, it connects to the burner wallet if it is enabled for the current network. See
onlyLocalBurnerWallet
You can extend this configuration file, adding new parameters that you need to use across your dapp (make sure you update the above type ScaffoldConfig
):
tokenIcon: "💎",
To use the values from the ScaffoldConfig
in any other file of your application, you first need to import it in those files:
import scaffoldConfig from "~~/scaffold.config";
Scaffold-ETH 2 provides a collection of custom React hooks designed to simplify interactions with your deployed smart contracts. These hooks are wrappers around wagmi
, automatically loading the necessary contract ABI and address. They offer an easy-to-use interface for reading from, writing to, and monitoring events emitted by your smart contracts.
To help developers get started with smart contract interaction using Scaffold-ETH 2, we've provided the following custom hooks:
- useScaffoldContractRead: for reading public variables and getting data from read-only functions of your contract.
- useScaffoldContractWrite: for sending transactions to your contract to write data or perform an action.
- useScaffoldEventSubscriber: for subscribing to your contract events and receiving real-time updates when events are emitted.
- useScaffoldEventHistory: for retrieving historical event logs for your contract, providing past activity data.
- useDeployedContractInfo: for fetching details from your contract, including the ABI and address.
- useScaffoldContract: for obtaining a contract instance that lets you interact with the methods of your deployed smart contract.
These hooks offer a simplified and streamlined interface for interacting with your smart contracts. If you need to interact with external contracts, you can use wagmi
directly, or add external contract data to your deployedContracts.ts
file.
Use this hook to read public variables and get data from read-only functions of your smart contract.
const { data: totalCounter } = useScaffoldContractRead({
contractName: "YourContract",
functionName: "getGreeting",
args: ["ARGUMENTS IF THE FUNCTION ACCEPTS ANY"],
});
This example retrieves the data returned by the getGreeting
function of the YourContract
smart contract. If the function accepts any arguments, they can be passed in the args array. The retrieved data is stored in the data
property of the returned object.
Use this hook to send a transaction to your smart contract to write data or perform an action.
const { writeAsync, isLoading, isMining } = useScaffoldContractWrite({
contractName: "YourContract",
functionName: "setGreeting",
args: ["The value to set"],
// For payable functions, expressed in ETH
value: "0.01",
// The number of block confirmations to wait for before considering transaction to be confirmed (default : 1).
blockConfirmations: 1,
// The callback function to execute when the transaction is confirmed.
onBlockConfirmation: (txnReceipt) => {
console.log("Transaction blockHash", txnReceipt.blockHash);
},
});
To send the transaction, you can call the writeAsync
function returned by the hook. Here's an example usage:
<button className="btn btn-primary" onClick={writeAsync}>
Send TX
</button>
This example sends a transaction to the YourContract
smart contract to call the setGreeting
function with the arguments passed in args
. The writeAsync
function sends the transaction to the smart contract, and the isLoading
and isMining
properties indicate whether the transaction is currently being processed by the network.
Use this hook to subscribe to events emitted by your smart contract, and receive real-time updates when these events are emitted.
useScaffoldEventSubscriber({
contractName: "YourContract",
eventName: "GreetingChange",
// The listener function is called whenever a GreetingChange event is emitted by the contract.
// It receives the parameters emitted by the event, for this example: GreetingChange(address greetingSetter, string newGreeting, bool premium, uint256 value);
listener: (greetingSetter, newGreeting, premium, value) => {
console.log(greetingSetter, newGreeting, premium, value);
},
});
This example subscribes to the GreetingChange
event emitted by the YourContract
smart contract, and logs the parameters emitted by the event to the console whenever it is emitted. The listener
function accepts the parameters emitted by the event, and can be customized according to your needs.
Use this hook to retrieve historical event logs for your smart contract, providing past activity data.
const {
data: events,
isLoading: isLoadingEvents,
error: errorReadingEvents,
} = useScaffoldEventHistory({
contractName: "YourContract",
eventName: "GreetingChange",
// Specify the starting block number from which to read events, this is a bigint.
fromBlock: 31231n,
blockData: true,
// Apply filters to the event based on parameter names and values { [parameterName]: value },
filters: { premium: true }
// If set to true it will return the transaction data for each event (default: false),
transactionData: true,
// If set to true it will return the receipt data for each event (default: false),
receiptData: true
});
This example retrieves the historical event logs for the GreetingChange
event of the YourContract
smart contract, starting from block number 31231 and filtering events where the premium parameter is true. The data property of the returned object contains an array of event objects, each containing the event parameters and (optionally) the block, transaction, and receipt data. The isLoading
property indicates whether the event logs are currently being fetched, and the error
property contains any error that occurred during the fetching process (if applicable).
Use this hook to fetch details about a deployed smart contract, including the ABI and address.
// ContractName: name of the deployed contract
const { data: deployedContractData } = useDeployedContractInfo(contractName);
This example retrieves the details of the deployed contract with the specified name and stores the details in the deployedContractData object.
Use this hook to get your contract instance by providing the contract name. It enables you interact with your contract methods.
For reading data or sending transactions, it's recommended to use useScaffoldContractRead
and useScaffoldContractWrite
.
const { data: yourContract } = useScaffoldContract({
contractName: "YourContract",
});
// Returns the greeting and can be called in any function, unlike useScaffoldContractRead
await yourContract?.greeting();
// Used to write to a contract and can be called in any function
import { useWalletClient } from "wagmi";
const { data: walletClient } = useWalletClient();
const { data: yourContract } = useScaffoldContract({
contractName: "YourContract",
walletClient,
});
const setGreeting = async () => {
// Call the method in any function
await yourContract?.setGreeting("the greeting here");
};
This example uses the useScaffoldContract
hook to obtain a contract instance for the YourContract
smart contract. The data property of the returned object contains the contract instance that can be used to call any of the smart contract methods.
Hint Typescript helps you catch errors at compile time, which can save time and improve code quality, but can be challenging for those who are new to the language or who are used to the more dynamic nature of JavaScript. Below are the steps to disable type & lint check at different levels
We run pre-commit
git hook which lints the staged files and don't let you commit if there is an linting error.
To disable this, go to .husky/pre-commit
file and comment out yarn lint-staged --verbose
- yarn lint-staged --verbose
+ # yarn lint-staged --verbose
By default, Vercel runs types and lint checks before building your app. The deployment will fail if there are any types or lint errors.
To ignore these checks while deploying from the CLI, use:
yarn vercel:yolo
If your repo is connected to Vercel, you can set NEXT_PUBLIC_IGNORE_BUILD_ERROR
to true
in a environment variable.
We have github workflow setup checkout .github/workflows/lint.yaml
which runs types and lint error checks every time code is pushed to main
branch or pull request is made to main
branch
To disable it, delete .github
directory
We welcome contributions to Scaffold-ETH 2!
Please see CONTRIBUTING.MD for more information and guidelines for contributing to Scaffold-ETH 2.