Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions .env.example
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
# Copy to .env and fill values. Do NOT commit .env

# RPC/API providers
INFURA_API_KEY=

# Private keys (hex string without 0x)
SEPOLIA_PRIVATE_KEY=

# Optionally add others per-network
# MAINNET_PRIVATE_KEY=
1 change: 1 addition & 0 deletions .nvmrc
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
22.11.0
46 changes: 45 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,50 @@ The `WrapperContract` offers two primary methods for DAO creation:
npm install
```

## NPM Scripts

- build: `npx hardhat compile` — compile contracts
- clean: `npx hardhat clean` — clear cache/artifacts
- test: `npx hardhat test` — run all tests
- node: `npx hardhat node` — start local Hardhat node
- deploy: `npx hardhat run scripts/deploy.js` (use with `--network`)
- deploy:localhost: `--network localhost`
- deploy:sepolia: `--network sepolia`
- proposal: `npx hardhat run scripts/makeProposal.js` (use with `--network`)
- proposal:localhost / proposal:sepolia
- verify:sepolia: `npx hardhat verify --network sepolia <address> [ctor-args...]`
- addresses: Show saved deployments per network:
- localhost: `node -e "console.log(require('./deployments/localhost.json'))"`
- sepolia: `node -e "console.log(require('./deployments/sepolia.json'))"`

Quick examples
- Local dev: `npm run node` (in another shell) then `npm run deploy:localhost`
- Sepolia deploy: `npm run deploy:sepolia` (saves to `deployments/sepolia.json`)
- Run tests: `npm test`

## Python (ABIs for Indexers)

Install curated ABIs directly from this repo via pip:

```bash
pip install git+GITREPOLINK
```

Usage in Python:

```python
from homebase_evm_contracts import get_abi

wrapper_abi = get_abi("wrapper") # current Wrapper (v2)
wrapper_legacy_abi = get_abi("wrapper", "legacy")
governor_abi = get_abi("governor") # minimal events: ProposalCreated/Queued/Executed/VoteCast
token_abi = get_abi("token") # decimals, totalSupply, balanceOf + events
```

Notes:
- The shipped ABIs are minimal and tailored for indexers (events and common reads).
- If you need the full Hardhat artifacts, use the JSONs under `contracts/artifacts/`.

## Deployment

### 1. Deploying Core Factories
Expand Down Expand Up @@ -115,4 +159,4 @@ async function main() {
console.log("WrapperContract deployed to:", await wrapperContract.getAddress());
}

main().catch(console.error);
main().catch(console.error);
29 changes: 18 additions & 11 deletions config.js
Original file line number Diff line number Diff line change
@@ -1,12 +1,19 @@
require('dotenv').config();

module.exports = {
AUTHOR: '0xc5C77EC5A79340f0240D6eE8224099F664A08EEb',
CONTRACTOR: '0xA6A40E0b6DB5a6f808703DBe91DbE50B7FC1fa3E',
ARBITER: '0x6EF597F8155BC561421800de48852c46e73d9D19',
BLOKE: '0x548f66A1063A79E4F291Ebeb721C718DCc7965c5',
EIGHT_RICE:'0xa9F8F9C0bf3188cEDdb9684ae28655187552bAE9',
INFURA_API_KEY: `1081d644fc4144b587a4f762846ceede`,
SEPOLIA_PRIVATE_KEY: `574b6dd16fe79a175be9dceaef2faf2ad30bc5b0f358ed20cffe93ee06947279`,
TOKEN_ADDRESS: `0x8f604a5d8353d8b2B557F364b622B57272B47c78`,
TIMELOCK_ADDRESS: `0x85B6fbfdAF324442e1490C90a220E6A237A3887e`,
DAO_ADDRESS: `0xC7Aa423e620f4e01fb4ba11Fe65FAA32658CBd0e`
};
// Static addresses (non-secrets)
AUTHOR: '0xc5C77EC5A79340f0240D6eE8224099F664A08EEb',
CONTRACTOR: '0xA6A40E0b6DB5a6f808703DBe91DbE50B7FC1fa3E',
ARBITER: '0x6EF597F8155BC561421800de48852c46e73d9D19',
BLOKE: '0x548f66A1063A79E4F291Ebeb721C718DCc7965c5',
EIGHT_RICE: '0xa9F8F9C0bf3188cEDdb9684ae28655187552bAE9',

// Secrets are loaded from environment variables
INFURA_API_KEY: process.env.INFURA_API_KEY,
SEPOLIA_PRIVATE_KEY: process.env.SEPOLIA_PRIVATE_KEY,

// Deployed addresses (can be updated by scripts)
TOKEN_ADDRESS: '0x8f604a5d8353d8b2B557F364b622B57272B47c78',
TIMELOCK_ADDRESS: '0x85B6fbfdAF324442e1490C90a220E6A237A3887e',
DAO_ADDRESS: '0xC7Aa423e620f4e01fb4ba11Fe65FAA32658CBd0e'
};
144 changes: 144 additions & 0 deletions contracts/mocks/MockERC721.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,144 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;

import "@openzeppelin/contracts/token/ERC721/ERC721.sol";
import "@openzeppelin/contracts/access/Ownable.sol";
import "@openzeppelin/contracts/utils/introspection/ERC165.sol";

/**
* @title MockERC721
* @dev Mock NFT contract for testing purposes
*/
contract MockERC721 is ERC721, Ownable {
uint256 private _nextTokenId = 1;

constructor(string memory name, string memory symbol)
ERC721(name, symbol)
Ownable(msg.sender)
{}

/**
* @dev Mint a new NFT to the specified address
* @param to The address to mint the NFT to
* @param tokenId The token ID to mint
*/
function mint(address to, uint256 tokenId) public onlyOwner {
_mint(to, tokenId);
}

/**
* @dev Mint a new NFT to the specified address with auto-incrementing ID
* @param to The address to mint the NFT to
* @return tokenId The minted token ID
*/
function safeMint(address to) public onlyOwner returns (uint256) {
uint256 tokenId = _nextTokenId++;
_safeMint(to, tokenId);
return tokenId;
}

/**
* @dev Batch mint NFTs to multiple addresses
* @param recipients Array of addresses to mint NFTs to
* @param tokenIds Array of token IDs to mint
*/
function batchMint(address[] memory recipients, uint256[] memory tokenIds) public onlyOwner {
require(recipients.length == tokenIds.length, "Arrays length mismatch");

for (uint256 i = 0; i < recipients.length; i++) {
_mint(recipients[i], tokenIds[i]);
}
}

/**
* @dev Burn an NFT
* @param tokenId The token ID to burn
*/
function burn(uint256 tokenId) public {
require(_isAuthorized(ownerOf(tokenId), msg.sender, tokenId), "Not authorized to burn");
_burn(tokenId);
}

/**
* @dev Get the next token ID that will be minted
* @return The next token ID
*/
function getNextTokenId() public view returns (uint256) {
return _nextTokenId;
}

/**
* @dev Check if a token exists
* @param tokenId The token ID to check
* @return True if the token exists
*/
function exists(uint256 tokenId) public view returns (bool) {
return _ownerOf(tokenId) != address(0);
}

/**
* @dev Get all token IDs owned by an address
* @param owner The address to query
* @return tokenIds Array of token IDs owned by the address
*/
function tokensOfOwner(address owner) public view returns (uint256[] memory) {
uint256 tokenCount = balanceOf(owner);
uint256[] memory tokenIds = new uint256[](tokenCount);
uint256 index = 0;

for (uint256 tokenId = 1; tokenId < _nextTokenId && index < tokenCount; tokenId++) {
if (_ownerOf(tokenId) == owner) {
tokenIds[index] = tokenId;
index++;
}
}

return tokenIds;
}

/**
* @dev Override tokenURI to provide metadata
* @param tokenId The token ID to get URI for
* @return The token URI
*/
function tokenURI(uint256 tokenId) public view override returns (string memory) {
_requireOwned(tokenId);

string memory baseURI = _baseURI();
return bytes(baseURI).length > 0
? string(abi.encodePacked(baseURI, _toString(tokenId)))
: "";
}

/**
* @dev Internal function to get base URI
* @return The base URI for token metadata
*/
function _baseURI() internal pure override returns (string memory) {
return "https://api.mockdao.org/nft/";
}

/**
* @dev Convert uint256 to string
* @param value The value to convert
* @return The string representation
*/
function _toString(uint256 value) internal pure returns (string memory) {
if (value == 0) {
return "0";
}
uint256 temp = value;
uint256 digits;
while (temp != 0) {
digits++;
temp /= 10;
}
bytes memory buffer = new bytes(digits);
while (value != 0) {
digits -= 1;
buffer[digits] = bytes1(uint8(48 + uint256(value % 10)));
value /= 10;
}
return string(buffer);
}
}
Loading