Skip to content

Commit

Permalink
feat: add quickstart templates
Browse files Browse the repository at this point in the history
  • Loading branch information
dutterbutter committed Apr 25, 2024
1 parent f4e5cae commit ab8fc2c
Show file tree
Hide file tree
Showing 55 changed files with 2,748 additions and 0 deletions.
1 change: 1 addition & 0 deletions templates/quickstart/factories/.env.example
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
WALLET_PRIVATE_KEY=
23 changes: 23 additions & 0 deletions templates/quickstart/factories/contracts/CrowdfundFactory.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

// Crowdfunding campaign contract
import "./CrowdfundingCampaign.sol";

// Factory contract to create and manage crowdfunding campaigns
contract CrowdfundingFactory {
CrowdfundingCampaign[] public campaigns;

event CampaignCreated(address campaignAddress, uint256 fundingGoal);

function createCampaign(uint256 fundingGoal) public {
CrowdfundingCampaign newCampaign = new CrowdfundingCampaign(fundingGoal);
campaigns.push(newCampaign);

emit CampaignCreated(address(newCampaign), fundingGoal);
}

function getCampaigns() public view returns (CrowdfundingCampaign[] memory) {
return campaigns;
}
}
48 changes: 48 additions & 0 deletions templates/quickstart/factories/contracts/CrowdfundingCampaign.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

contract CrowdfundingCampaign {
address public owner;
uint256 public fundingGoal;
uint256 public totalFundsRaised;
mapping(address => uint256) public contributions;

event ContributionReceived(address contributor, uint256 amount);
event GoalReached(uint256 totalFundsRaised);

constructor(uint256 _fundingGoal) {
owner = msg.sender;
fundingGoal = _fundingGoal;
}

function contribute() public payable {
require(msg.value > 0, "Contribution must be greater than 0");
contributions[msg.sender] += msg.value;
totalFundsRaised += msg.value;

emit ContributionReceived(msg.sender, msg.value);

if (totalFundsRaised >= fundingGoal) {
emit GoalReached(totalFundsRaised);
}
}

function withdrawFunds() public {
require(msg.sender == owner, "Only the owner can withdraw funds");
require(totalFundsRaised >= fundingGoal, "Funding goal not reached");

uint256 amount = address(this).balance;
totalFundsRaised = 0;

(bool success, ) = payable(owner).call{value: amount}("");
require(success, "Transfer failed.");
}

function getTotalFundsRaised() public view returns (uint256) {
return totalFundsRaised;
}

function getFundingGoal() public view returns (uint256) {
return fundingGoal;
}
}
32 changes: 32 additions & 0 deletions templates/quickstart/factories/deploy/deployUsingFactory.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
import { deployContract, getWallet } from "./utils";
import { ethers } from "ethers";
import { HardhatRuntimeEnvironment } from "hardhat/types";

export default async function (hre: HardhatRuntimeEnvironment) {
const contractArtifactName = "CrowdfundingFactory";
const constructorArguments = [];
const crowdfundingFactory = await deployContract(contractArtifactName, constructorArguments);

console.log(`🏭 CrowdfundingFactory address: ${crowdfundingFactory.target}`);

const contractArtifact = await hre.artifacts.readArtifact("CrowdfundingFactory");
const factoryContract = new ethers.Contract(
crowdfundingFactory.target,
contractArtifact.abi,
getWallet()
);

// Define funding goal for the campaign, e.g., 0.1 ether
const fundingGoalInWei = ethers.parseEther('0.1').toString();

// Use the factory to create a new CrowdfundingCampaign
const createTx = await factoryContract.createCampaign(fundingGoalInWei);
await createTx.wait();

// Retrieve the address of the newly created CrowdfundingCampaign
const campaigns = await factoryContract.getCampaigns();
const newCampaignAddress = campaigns[campaigns.length - 1];

console.log(`🚀 New CrowdfundingCampaign deployed at: ${newCampaignAddress}`);
console.log('✅ Deployment and campaign creation complete!');
}
168 changes: 168 additions & 0 deletions templates/quickstart/factories/deploy/utils.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,168 @@
import { Provider, Wallet } from "zksync-ethers";
import * as hre from "hardhat";
import { Deployer } from "@matterlabs/hardhat-zksync";
import dotenv from "dotenv";
import { ethers } from "ethers";

import "@matterlabs/hardhat-zksync-node/dist/type-extensions";
import "@matterlabs/hardhat-zksync-verify/dist/src/type-extensions";

// Load env file
dotenv.config();

export const getProvider = () => {
const rpcUrl = hre.network.config.url;
if (!rpcUrl) throw `⛔️ RPC URL wasn't found in "${hre.network.name}"! Please add a "url" field to the network config in hardhat.config.ts`;

// Initialize zkSync Provider
const provider = new Provider(rpcUrl);

return provider;
}

export const getWallet = (privateKey?: string) => {
if (!privateKey) {
// Get wallet private key from .env file
if (!process.env.WALLET_PRIVATE_KEY) throw "⛔️ Wallet private key wasn't found in .env file!";
}

const provider = getProvider();

// Initialize zkSync Wallet
const wallet = new Wallet(privateKey ?? process.env.WALLET_PRIVATE_KEY!, provider);

return wallet;
}

export const verifyEnoughBalance = async (wallet: Wallet, amount: bigint) => {
// Check if the wallet has enough balance
const balance = await wallet.getBalance();
if (balance < amount) throw `⛔️ Wallet balance is too low! Required ${ethers.formatEther(amount)} ETH, but current ${wallet.address} balance is ${ethers.formatEther(balance)} ETH`;
}

/**
* @param {string} data.contract The contract's path and name. E.g., "contracts/Greeter.sol:Greeter"
*/
export const verifyContract = async (data: {
address: string,
contract: string,
constructorArguments: string,
bytecode: string
}) => {
const verificationRequestId: number = await hre.run("verify:verify", {
...data,
noCompile: true,
});
return verificationRequestId;
}

type DeployContractOptions = {
/**
* If true, the deployment process will not print any logs
*/
silent?: boolean
/**
* If true, the contract will not be verified on Block Explorer
*/
noVerify?: boolean
/**
* If specified, the contract will be deployed using this wallet
*/
wallet?: Wallet
}
export const deployContract = async (contractArtifactName: string, constructorArguments?: any[], options?: DeployContractOptions) => {
const log = (message: string) => {
if (!options?.silent) console.log(message);
}

log(`\nStarting deployment process of "${contractArtifactName}"...`);

const wallet = options?.wallet ?? getWallet();
const deployer = new Deployer(hre, wallet);
const artifact = await deployer.loadArtifact(contractArtifactName).catch((error) => {
if (error?.message?.includes(`Artifact for contract "${contractArtifactName}" not found.`)) {
console.error(error.message);
throw `⛔️ Please make sure you have compiled your contracts or specified the correct contract name!`;
} else {
throw error;
}
});

// Estimate contract deployment fee
const deploymentFee = await deployer.estimateDeployFee(artifact, constructorArguments || []);
log(`Estimated deployment cost: ${ethers.formatEther(deploymentFee)} ETH`);

// Check if the wallet has enough balance
await verifyEnoughBalance(wallet, deploymentFee);

// Deploy the contract to zkSync
const contract = await deployer.deploy(artifact, constructorArguments);
const address = await contract.getAddress();
const constructorArgs = contract.interface.encodeDeploy(constructorArguments);
const fullContractSource = `${artifact.sourceName}:${artifact.contractName}`;

// Display contract deployment info
log(`\n"${artifact.contractName}" was successfully deployed:`);
log(` - Contract address: ${address}`);
log(` - Contract source: ${fullContractSource}`);
log(` - Encoded constructor arguments: ${constructorArgs}\n`);

if (!options?.noVerify && hre.network.config.verifyURL) {
log(`Requesting contract verification...`);
await verifyContract({
address,
contract: fullContractSource,
constructorArguments: constructorArgs,
bytecode: artifact.bytecode,
});
}

return contract;
}

/**
* Rich wallets can be used for testing purposes.
* Available on zkSync In-memory node and Dockerized node.
*/
export const LOCAL_RICH_WALLETS = [
{
address: "0x36615Cf349d7F6344891B1e7CA7C72883F5dc049",
privateKey: "0x7726827caac94a7f9e1b160f7ea819f172f7b6f9d2a97f992c38edeab82d4110"
},
{
address: "0xa61464658AfeAf65CccaaFD3a512b69A83B77618",
privateKey: "0xac1e735be8536c6534bb4f17f06f6afc73b2b5ba84ac2cfb12f7461b20c0bbe3"
},
{
address: "0x0D43eB5B8a47bA8900d84AA36656c92024e9772e",
privateKey: "0xd293c684d884d56f8d6abd64fc76757d3664904e309a0645baf8522ab6366d9e"
},
{
address: "0xA13c10C0D5bd6f79041B9835c63f91de35A15883",
privateKey: "0x850683b40d4a740aa6e745f889a6fdc8327be76e122f5aba645a5b02d0248db8"
},
{
address: "0x8002cD98Cfb563492A6fB3E7C8243b7B9Ad4cc92",
privateKey: "0xf12e28c0eb1ef4ff90478f6805b68d63737b7f33abfa091601140805da450d93"
},
{
address: "0x4F9133D1d3F50011A6859807C837bdCB31Aaab13",
privateKey: "0xe667e57a9b8aaa6709e51ff7d093f1c5b73b63f9987e4ab4aa9a5c699e024ee8"
},
{
address: "0xbd29A1B981925B94eEc5c4F1125AF02a2Ec4d1cA",
privateKey: "0x28a574ab2de8a00364d5dd4b07c4f2f574ef7fcc2a86a197f65abaec836d1959"
},
{
address: "0xedB6F5B4aab3dD95C7806Af42881FF12BE7e9daa",
privateKey: "0x74d8b3a188f7260f67698eb44da07397a298df5427df681ef68c45b34b61f998"
},
{
address: "0xe706e60ab5Dc512C36A4646D719b889F398cbBcB",
privateKey: "0xbe79721778b48bcc679b78edac0ce48306a8578186ffcb9f2ee455ae6efeace1"
},
{
address: "0xE90E12261CCb0F3F7976Ae611A29e84a6A85f424",
privateKey: "0x3eb15da85647edd9a1159a4a13b9e7c56877c4eb33f614546d4db06a51868b1c"
}
]
52 changes: 52 additions & 0 deletions templates/quickstart/factories/hardhat.config.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
import { HardhatUserConfig } from "hardhat/config";

import "@matterlabs/hardhat-zksync";

const config: HardhatUserConfig = {
defaultNetwork: "zkSyncSepoliaTestnet",
networks: {
zkSyncSepoliaTestnet: {
url: "https://sepolia.era.zksync.dev",
ethNetwork: "sepolia",
zksync: true,
verifyURL: "https://explorer.sepolia.era.zksync.dev/contract_verification",
},
zkSyncMainnet: {
url: "https://mainnet.era.zksync.io",
ethNetwork: "mainnet",
zksync: true,
verifyURL: "https://zksync2-mainnet-explorer.zksync.io/contract_verification",
},
zkSyncGoerliTestnet: { // deprecated network
url: "https://testnet.era.zksync.dev",
ethNetwork: "goerli",
zksync: true,
verifyURL: "https://zksync2-testnet-explorer.zksync.dev/contract_verification",
},
dockerizedNode: {
url: "http://localhost:3050",
ethNetwork: "http://localhost:8545",
zksync: true,
},
inMemoryNode: {
url: "http://127.0.0.1:8011",
ethNetwork: "localhost", // in-memory node doesn't support eth node; removing this line will cause an error
zksync: true,
},
hardhat: {
zksync: true,
},
},
zksolc: {
version: "latest",
settings: {
// find all available options in the official documentation
// https://era.zksync.io/docs/tools/hardhat/hardhat-zksync-solc.html#configuration
},
},
solidity: {
version: "0.8.17",
},
};

export default config;
30 changes: 30 additions & 0 deletions templates/quickstart/factories/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
{
"name": "hello-zksync-quickstart",
"description": "A template for zkSync smart contracts development with Hardhat",
"private": true,
"author": "Matter Labs",
"license": "MIT",
"repository": "https://github.com/matter-labs/zksync-hardhat-template.git",
"scripts": {
"deploy": "hardhat deploy-zksync --script deploy.ts",
"compile": "hardhat compile",
"clean": "hardhat clean"
},
"devDependencies": {
"@matterlabs/hardhat-zksync": "^1.0.0",
"@matterlabs/zksync-contracts": "^0.6.1",
"@nomiclabs/hardhat-etherscan": "^3.1.7",
"@nomicfoundation/hardhat-chai-matchers": "^2.0.6",
"@openzeppelin/contracts": "^4.6.0",
"@types/chai": "^4.3.4",
"@types/mocha": "^10.0.1",
"chai": "^4.3.7",
"dotenv": "^16.0.3",
"ethers": "^6.9.2",
"hardhat": "^2.12.4",
"mocha": "^10.2.0",
"ts-node": "^10.9.1",
"typescript": "^4.9.5",
"zksync-ethers": "^6.7.0"
}
}
1 change: 1 addition & 0 deletions templates/quickstart/hello-zksync/.env.example
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
WALLET_PRIVATE_KEY=
Loading

0 comments on commit ab8fc2c

Please sign in to comment.