Smart Contracts
How to build smart contracts with the Hyperverse Builderkit.
Getting Started
After setting up your local environment, you're ready to build your smart contract. EVM Smart Modules are written in Solidity and use Hardhat to compile and deploy smart contracts in the contracts directory.
Additional resources
Haven't set up your environment? See Environment Setup before moving forward. Check out Ethereum, Solidity, Hardhat for additional resources to help you get started.
Contracts Folder
The contracts folder contains the following files.
Module.sol
- Contains the core functionality of your smart module.ModuleFactory.sol
- Contains the clone factory implementation of the smart module.CloneFactory.sol
- Enables the ModuleFactory to deploy clones for your smart module.IHyperverseModule.sol
- Contains the Hyperverse EVM Smart Module standard.
Example
The Builderkit contains examples of completed ERC721 EVM Module contracts here.
Update Module.sol
The Module.sol
file contains the core functionality of your smart module. To update this contract, import interfaces and utilities, update the contract name & metadata, and add all smart contract functionality.
// Update file name to match smart module (ex. ERC721.sol)
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
pragma experimental ABIEncoderV2;
// Import interfaces and utilities
import './hyperverse/IHyperverseModule.sol';
// Update contract name and include inherited contracts
contract Module is IHyperverseModule {
address public immutable contractOwner;
address private tenantOwner;
constructor(address _owner) {
// Update metadata
metadata = ModuleMetadata(
'Module',
Author(_owner, 'https://externallink.net'),
'1.0.0',
3479831479814,
'https://externallink.net'
);
contractOwner = _owner;
}
function init(address _tenant) external {
tenantOwner = _tenant;
}
// Add smart contract functionality
}
Example
View an example of a completed Module.sol
contract at ERC721.sol
Update ModuleFactory.sol
The ModuleFactory.sol
file contains the clone factory implementation of the smart module. To update this contract, update the contract name to reflect changes in your Module.sol
contract.
// Update file name to match smart module (ex. ERC721Factory.sol)
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
pragma experimental ABIEncoderV2;
import './hyperverse/CloneFactory.sol';
import './hyperverse/IHyperverseModule.sol';
// Import smart contract
import './Module.sol';
/**
* @dev Clone Factory Implementation
*/
// Update occurrences of module name
contract ModuleFactory is CloneFactory {
struct Tenant {
Module module;
address owner;
}
mapping(address => Tenant) public tenants;
address public immutable owner;
address public immutable masterContract;
// Update admin ethereum address
address private hyperverseAdmin = 0xD847C7408c48b6b6720CCa75eB30a93acbF5163D;
modifier isOwner(address _tenant) {
require(
tenants[_tenant].owner == msg.sender,
'The calling address is not an owner of a tenant'
);
_;
}
modifier isAllowedToCreateInstance(address _tenant) {
require(
msg.sender == _tenant || msg.sender == hyperverseAdmin,
'Please use a valid address to create an instance'
);
_;
}
constructor(address _masterContract, address _owner) {
masterContract = _masterContract;
owner = _owner;
}
/******************* TENANT FUNCTIONALITIES *******************/
function createInstance(address _tenant) external isAllowedToCreateInstance(_tenant) {
// Update module name
Module m = Module(createClone(masterContract));
//initializing tenant state of clone
m.init(msg.sender);
//set Tenant data
Tenant storage newTenant = tenants[_tenant];
// Update occurrences of module name
newTenant.module = m;
newTenant.owner = _tenant;
}
function getProxy(address _tenant) public view returns (Module) {
return tenants[_tenant].module;
}
}
info
View an example of a completed ModuleFactory.sol
contract at ERC721Factory.sol
You may extend the functionality of your smart module by developing additional interfaces, utilities, and smart contracts. Read the Solidity docs for more information.
Update Deploy Script
When your smart contract is complete, navigate to the /scripts/deploy.js file to update your deploy scripts. This will allow you to deploy your smart modules to the Ethereum network.
const hre = require("hardhat");
const fs = require("fs-extra");
const { constants } = require("ethers");
const main = async () => {
const [deployer] = await ethers.getSigners();
console.log("Deployer Address: ", deployer.address);
const hyperverseAdmin = deployer.address;
// Update module name
const BaseModule = await hre.ethers.getContractFactory("Module");
const baseContract = await BaseModule.deploy(hyperverseAdmin);
await baseContract.deployed();
console.log("Module Contract deployed to: ", baseContract.address);
// Update module factory name
const ModuleFactory = await hre.ethers.getContractFactory("ModuleFactory");
const moduleFactory = await ModuleFactory.deploy(
baseContract.address,
hyperverseAdmin
);
await moduleFactory.deployed();
const env = JSON.parse(fs.readFileSync("contracts.json").toString());
env[hre.network.name] = env[hre.network.name] || {};
env[hre.network.name].testnet = env[hre.network.name].testnet || {};
env[hre.network.name].testnet.contractAddress = baseContract.address;
// Update occurrences of module factory
env[hre.network.name].testnet.factoryAddress = moduleFactory.address;
// Save contract addresses back to file
fs.writeJsonSync("contracts.json", env, { spaces: 2 });
// Deploy default tenant
let proxyAddress = constants.AddressZero;
// Update occurrences of module factory
await moduleFactory.createInstance(deployer.address);
while (proxyAddress === constants.AddressZero) {
proxyAddress = await moduleFactory.getProxy(deployer.address);
}
};
const runMain = async () => {
try {
await main();
process.exit(0);
} catch (error) {
console.error(error);
process.exit(1);
}
};
runMain();
Example
View an example of a completed deploy.js
here
Build Unit Tests
You're now ready to start building your unit tests!