Smart contracts for worker-owned, on-chain organizations.
POP is the contract layer for Poa, a no-code DAO builder for community- and worker-owned organizations. Members earn governance through contribution, not capital: every approved task mints non-transferable participation tokens to the worker who did the work. Decisions happen on-chain across multiple weighted voting classes, role assignments use Hats Protocol, and gas is sponsored via a multi-tenant ERC-4337 paymaster with a built-in solidarity fund.
This repository is the Solidity protocol: ~17K LOC across src/, 40+ test suites, mainnet on Arbitrum and Gnosis. If you're new here, start with docs/POP_OVERVIEW.md for the protocol philosophy, then come back here for the technical map.
- The POP Stack
- Live Deployments
- Why POP Exists
- Architecture
- Module Reference
- Upgradeability (SwitchableBeacon)
- Storage Model (ERC-7201)
- Access Control (Hats Protocol)
- Account Abstraction (ERC-4337)
- Cross-Chain (Hyperlane)
- The Subgraph
- The Frontend
- The CLI
- Quick Start
- Building & Testing
- Deploying
- Security Model
- Contributing
- Documentation Index
- Community
- License
POP is split across four repositories. Changes to event signatures or storage layouts in this repo ripple into the subgraph and the frontend; coordinate accordingly (see Contributing).
| Repo | Role | Tech |
|---|---|---|
| poa-box/POP (this repo) | Solidity protocol contracts | Foundry · Solidity ^0.8.17–^0.8.30 |
| poa-box/subgraph-pop | The Graph indexer turning POP events into a GraphQL API | TypeScript · AssemblyScript · Graph CLI |
| poa-box/Poa-frontend | Next.js web app at poa.box | Next.js 14 · wagmi · RainbowKit · viem · Apollo · Pimlico |
| poa-box/poa-cli | CLI and autonomous-agent framework | TypeScript |
All four repos are AGPL-3.0.
Mainnet: Arbitrum One (chain 42161), Gnosis (chain 100).
Testnets: Sepolia (11155111), Base Sepolia (84532), Optimism Sepolia (11155420), Arbitrum Sepolia (421614), Hoodi (560048).
The canonical, current set of protocol-layer addresses for each chain lives in script/config/infrastructure.json and is automatically loaded by DeployOrg.s.sol. There is no need to copy addresses by hand. A snapshot of the testnet infrastructure addresses:
| Contract | Address |
|---|---|
| OrgDeployer | 0x888daCE32d8BCdDD95BA9D490643663C25810ded |
| PoaManager | 0x868680dc2689fa49A8389b0313da15408C8BE340 |
| OrgRegistry | 0xBBf72057901d7d0F557D6f7Aa1Afc56F4F3d6072 |
| ImplementationRegistry | 0xe848C652e1Aa56BeC38f504259f5D2b98b585aed |
| PaymasterHub | 0xbe2c8713F762871b14dc2273B389a210974dB755 |
| UniversalAccountRegistry | 0xDdB1DA30020861d92c27aE981ac0f4Fe8BA536F2 |
| GovernanceFactory | 0x2e3BCa0b6902285b7e8D747A14f118EbB8DB997D |
| AccessFactory | 0x05Db5Cf1540683A888E7aC656d7373aa03864a8c |
| ModulesFactory | 0x0b5b1410E6e8FeE4eCd07C69CdDCf860eCc44981 |
| HatsTreeSetup | 0xdc7C14AB68fcCf9bcD255f5be684EbDc892Da13a |
| Hats Protocol (external) | 0x3bc1A0Ad72417f2d411118085256fC53CBdDd137 |
Always treat script/config/infrastructure.json as the source of truth. This table can drift between releases.
- Work creates ownership. Participation tokens are minted on approved task completion and are non-transferable. You earn ownership through contribution; you cannot buy in.
- One member, one voice (when it matters).
DirectDemocracyVotingenforces 100 voting points per eligible member. Wealth cannot tilt the outcome. - Multiple stakeholders, proportional voice.
HybridVotingcomposes weighted classes (e.g., 50% direct democracy, 50% token-weighted with optional quadratic) so organizations can balance constituencies. - Collective infrastructure, individual autonomy. Orgs share an upgrade beacon, an account registry, and a paymaster with a solidarity fund, while each org governs itself and can pin to a specific implementation at any time.
- Transparency by default. Proposals, votes, tasks, payments and role assignments are all on-chain. The subgraph turns those events into a queryable history.
Read docs/POP_OVERVIEW.md for the long version.
POP organizations are deployed atomically by composing three layers of contracts. The protocol layer is shared across all orgs on a chain; the deployment layer creates new orgs in a single transaction; each organization owns its own per-org instances of the modules.
flowchart TD
subgraph Protocol["Protocol Layer (shared, once per chain)"]
PoaManager
ImplementationRegistry
PaymasterHub
UniversalAccountRegistry
OrgRegistry
end
subgraph Deployment["Deployment Layer (orchestration)"]
OrgDeployer
GovernanceFactory
AccessFactory
ModulesFactory
end
subgraph Org["Organization Layer (per-org instances behind BeaconProxy + SwitchableBeacon)"]
subgraph Governance
DirectDemocracyVoting
HybridVoting
Executor
HatsTree["Hats Tree"]
end
subgraph Access
QuickJoin
ParticipationToken
end
subgraph Operations
TaskManager
EducationHub
PaymentManager
end
end
Protocol --> Deployment
Deployment --> Governance
Deployment --> Access
Deployment --> Operations
Protocol Layer (deployed once per chain, persistent, upgradeable, shared):
| Contract | Path | Purpose |
|---|---|---|
PoaManager |
src/PoaManager.sol |
Owns the global UpgradeableBeacon instances per contract type. Single upgrade authority. |
ImplementationRegistry |
src/ImplementationRegistry.sol |
Records every implementation version registered with the protocol. |
OrgRegistry |
src/OrgRegistry.sol |
Enumerates every deployed organization and its module addresses. |
UniversalAccountRegistry |
src/UniversalAccountRegistry.sol |
Cross-org username + account mapping (one identity, many orgs). |
PaymasterHub |
src/PaymasterHub.sol |
Multi-tenant ERC-4337 paymaster with a solidarity fund. |
Deployment Layer (also deployed once per chain; orchestration only):
| Contract | Path | Purpose |
|---|---|---|
OrgDeployer |
src/OrgDeployer.sol |
Atomic full-org deployment in one transaction (~22.5M gas). |
GovernanceFactory |
src/factories/GovernanceFactory.sol |
Deploys Executor, HybridVoting, DirectDemocracyVoting, and coordinates the org's Hats tree via HatsTreeSetup. |
AccessFactory |
src/factories/AccessFactory.sol |
Deploys QuickJoin and ParticipationToken. |
ModulesFactory |
src/factories/ModulesFactory.sol |
Deploys TaskManager, EducationHub, PaymentManager. |
HatsTreeSetup |
src/HatsTreeSetup.sol |
Helper called by GovernanceFactory to mint the top hat and role hats during deployment. |
Organization Layer (per-org instances behind BeaconProxy + SwitchableBeacon):
| Contract | Path | Purpose |
|---|---|---|
Executor |
src/Executor.sol |
Sole call-execution layer; only the authorized voting contract may invoke execute(). Ownership renounced after setup. |
HybridVoting |
src/HybridVoting.sol |
Multi-class weighted voting with optional quadratic per class. |
DirectDemocracyVoting |
src/DirectDemocracyVoting.sol |
One-member-one-voice (100 points each), multi-option proposals. |
ParticipationToken |
src/ParticipationToken.sol |
Non-transferable ERC20Votes minted by TaskManager/EducationHub. |
TaskManager |
src/TaskManager.sol |
Project/task/application lifecycle with stablecoin bounties. |
EducationHub |
src/EducationHub.sol |
On-chain learning modules that mint participation tokens on completion. |
QuickJoin |
src/QuickJoin.sol |
Username registration + member-hat minting in a single call. |
PaymentManager |
src/PaymentManager.sol |
Merkle-distribution treasury claims proportional to participation. |
Beyond the headline contracts, the source tree contains:
src/libs/holds shared libraries.HybridVotingCore.sol/HybridVotingConfig.sol/HybridVotingProposals.sol: these three libraries share a single ERC-7201 namespace (keccak256("poa.hybridvoting.v2.storage")). When you change one, you must keep all three in sync.HatManager.sol: batch operations over Hats (hasAnyHat,setHatInArray).ValidationLib.sol:requireNonZeroAddressand friends; use these at boundaries.PaymasterHubErrors.sol,PaymasterGraceLib.sol,PaymasterPostOpLib.sol,PaymasterCalldataLib.sol: thePaymasterHubis large (~1971 LOC); much of its logic is split into these libraries.WebAuthnLib.sol(and friends): P256/WebAuthn signature verification forPasskeyAccount.
src/lens/holds read-only view contracts (DirectDemocracyVotingLens,HybridVotingLens,TaskManagerLens, plussrc/PaymasterHubLens.solat the root) for cheap reads from clients without touching the heavy core contracts.src/cashout/holdsCashOutRelay.soland helpers for member-initiated treasury exits.src/crosschain/holds the Hyperlane Hub/Satellite contracts. See Cross-Chain.src/interfaces/holds interface declarations and shared types.
A handful of additional root-level contracts are worth knowing about:
| Contract | Path | Purpose |
|---|---|---|
EligibilityModule |
src/EligibilityModule.sol |
Hats Protocol eligibility module wired in by HatsTreeSetup; gates who is allowed to wear a given hat. |
ToggleModule |
src/ToggleModule.sol |
Hats Protocol toggle module; lets governance enable or disable a hat without revoking it. |
EOADelegation |
src/EOADelegation.sol |
EIP-7702 delegation target so EOAs can act as ERC-4337 smart accounts in-place. |
HatsTreeSetup |
src/HatsTreeSetup.sol |
Mints the org's top hat and role hats during deployment; called by GovernanceFactory. |
POP gives every organization explicit, on-chain control over when (and whether) it upgrades. Org-level modules (Executor, voting, TaskManager, etc.) use a beacon chain:
flowchart LR
PoaManager --> UB["UpgradeableBeacon (global, per type)"]
UB --> SB["SwitchableBeacon (per org/module)"]
SB --> BP[BeaconProxy]
BP --> Impl[Implementation]
The protocol-layer PaymasterHub is upgradeable via UUPS (proxy-internal upgrade authorization), governed by PoaManager. PoaManager itself is a non-upgradeable contract that owns the global UpgradeableBeacon instances and authorizes UUPS upgrades on the protocol contracts it manages.
Each org's SwitchableBeacon has a mode (Mirror or Static) and exposes setMirror(), pin(), and pinToCurrent():
- Mirror mode (default; auto-follow):
implementation()delegates toIBeacon(mirrorBeacon).implementation(), so the org tracks the global beacon's current implementation. New protocol-level upgrades land automatically. - Static mode (pinned; governance-controlled):
implementation()returns the locally-storedstaticImplementationaddress. Upgrades require a governance proposal that callspin(newImpl)(orpinToCurrent()to lock in whatever the global beacon is currently pointing at). - Custom beacon (full custody): an org can
setMirror()to its own beacon, after which theSwitchableBeaconwill track that beacon instead of the protocol's. Useful for orgs that want a fork.
Critical invariant: SwitchableBeacon.renounceOwnership() reverts with CannotRenounce. Losing ownership would brick the beacon permanently, so the owner (typically the org's Executor) must always be a live contract.
See SWITCHABLE_BEACON.md for the full design notes, gas analysis, and migration paths.
Every upgradeable contract uses ERC-7201 namespaced storage. There are no __gap arrays anywhere. State is held in a struct Layout accessed via an explicit _STORAGE_SLOT constant. Modify a Layout append-only; never reorder or remove fields.
Two slot-derivation styles are used in the codebase. The simpler form (used by Executor, HybridVoting, etc.) just hashes the namespace string:
bytes32 private constant _STORAGE_SLOT = keccak256("poa.executor.storage");The canonical EIP-7201 derivation (used by PaymasterHub for its multiple sub-namespaces) wraps that hash to mask collisions:
bytes32 private constant MAIN_STORAGE_LOCATION =
keccak256(abi.encode(uint256(keccak256("poa.paymasterhub.main")) - 1));Match the surrounding contract's style when adding a new namespace. Concrete example from src/Executor.sol:
/* ─────────── ERC-7201 Storage ─────────── */
/// @custom:storage-location erc7201:poa.executor.storage
struct Layout {
address allowedCaller; // sole authorised governor
IHats hats; // Hats Protocol interface
mapping(address => bool) authorizedHatMinters;
address pendingCaller;
uint256 callerChangeTimestamp;
}
bytes32 private constant _STORAGE_SLOT = keccak256("poa.executor.storage");
function _layout() private pure returns (Layout storage s) {
bytes32 slot = _STORAGE_SLOT;
assembly { s.slot := slot }
}CI enforces upgrade safety. The repository tracks three storage-layout snapshots:
upgrades/baseline/: the layouts the next upgrade must remain compatible with.upgrades/current/: generated from the working tree.upgrades/previous/: historical reference.
The CI workflow runs script/upgrades/ValidateUpgrade.s.sol against upgrades/baseline/ and fails the build if a storage-breaking change is detected. Do not edit upgrades/ by hand; it is auto-generated. If your change requires a baseline update, surface it in the PR description so reviewers can verify the storage diff is intentional.
POP does not use OpenZeppelin's AccessControl. All role-based access is mediated by Hats Protocol. Each org owns its own hat tree:
flowchart TD
TopHat["Top Hat (organization root, held by Executor)"]
TopHat --> Member
TopHat --> Worker
TopHat --> Reviewer
Permission checks should go through src/libs/HatManager.sol helpers like hasAnyHat() rather than calling IHats.isWearerOfHat() directly; HatManager handles batched, eligibility-aware lookups. Role assignments (which hats unlock which abilities, e.g. taskCreatorRoles, ddVotingRoles, tokenApproverRoles) are configured per-org in the deployment JSON; see script/config/org-config-example.json.
On Sepolia, POP integrates with the Hats Protocol deployment at 0x3bc1A0Ad72417f2d411118085256fC53CBdDd137. For other networks, consult the official Hats Protocol deployments list.
POP ships a full passkey-first account abstraction stack so non-crypto-native users can join an org without holding ETH or managing seed phrases.
| Contract | Path | Purpose |
|---|---|---|
PasskeyAccount |
src/PasskeyAccount.sol (~571 LOC) |
ERC-4337 smart wallet. WebAuthn/P256 signature verification via WebAuthnLib. validateUserOp, execute, executeBatch. |
PasskeyAccountFactory |
src/PasskeyAccountFactory.sol (~372 LOC) |
Deterministic CREATE2 account creation; getAddress() predicts the address before deployment. |
PaymasterHub |
src/PaymasterHub.sol (~1971 LOC) |
Multi-tenant paymaster. Each org gets its own budget, rules, and grace allowance. |
Solidarity fund. PaymasterHub collects a 1% fee on sponsored transactions from paying organizations and routes it into a shared solidarity balance. New orgs receive a 90-day grace allowance (~0.01 ETH, roughly 3,000 transactions) at deployment time, and progressive matching tiers subsidize early growth. The mechanism is implemented across src/libs/PaymasterGraceLib.sol and the SolidarityFund storage in src/PaymasterHub.sol. See docs/PAYMASTER_HUB.md for the full economics.
EVM-version note. The default Foundry profile compiles for osaka so the P256 precompile lives at 0x100, which our passkey signature tests need. The production profile compiles for cancun for L2 compatibility. Use osaka only for tests; never deploy with it.
POP supports synchronized upgrades across chains via Hyperlane. Architecture: one home chain runs the hub, every other chain runs a satellite.
| Contract | Path | Role |
|---|---|---|
PoaManagerHub |
src/crosschain/PoaManagerHub.sol |
Home-chain wrapper that owns PoaManager. All upgrades and registrations route through it. |
PoaManagerSatellite |
src/crosschain/PoaManagerSatellite.sol |
Remote-chain receiver; validates inbound Hyperlane messages and applies them locally. |
DeterministicDeployer |
src/crosschain/DeterministicDeployer.sol |
CREATE2-based deployer ensuring identical implementation addresses across chains. |
Message types currently understood by the Hub/Satellite pair:
MSG_UPGRADE_BEACON: propagate a new implementation for a given contract type.MSG_ADD_CONTRACT_TYPE: register a new beacon-managed contract type protocol-wide.MSG_ADMIN_CALL: generic admin operation (e.g. emergency adjustments).
Hyperlane mailbox addresses (from .env.example):
| Chain | Mailbox | Hyperlane Domain |
|---|---|---|
| Arbitrum (home) | 0x979Ca5202784112f4738403dBec5D0F3B9daabB9 |
42161 |
| Ethereum | 0xc005dc82818d67AF737725bD4bf75435d065D239 |
1 |
| Optimism | 0xd4C1905BB1D26BC93DAC913e13CaCC278CdCC80D |
10 |
| Gnosis | 0xaD09d78f4c6b9dA2Ae82b1D34107802d380Bb74f |
100 |
The frontend (and any data-driven analytics) does not query contracts directly. Instead it queries poa-box/subgraph-pop, a The Graph subgraph that materializes POP's events into a GraphQL API. The subgraph trails the chain by a few seconds; the frontend masks that latency with optimistic updates.
What it indexes (~100+ entity types across):
| Domain | Key entities |
|---|---|
| Organizations | Organization, OrgMetadata, User, Account, RoleWearer |
| Roles & permissions | Role, HatPermission, Vouch, RoleApplication |
| Voting | Proposal, Vote, DDVProposal, DDVVote, VotingClass |
| Tasks & projects | Project, Task, TaskApplication, TaskMetadata |
| Participation | TokenBalance, TokenRequest |
| Education | EducationModule, ModuleCompletion |
| Treasury | Distribution, Claim, Payment |
| Identity | PasskeyAccount, PasskeyCredential |
| Gas sponsorship | PaymasterOrgConfig, PaymasterRule, PaymasterBudget |
Networks indexed: Arbitrum One (poa-arb-v-1) and Gnosis (poa-gnosis-v-1). Live query URLs are on each subgraph's Graph Studio page. CI in subgraph-pop redeploys on every merge to main that touches pop-subgraph/**.
Coordinating contract changes with the subgraph. The subgraph's mappings depend on event signatures. If your PR renames or removes a tracked event (e.g. TaskCompleted, ProposalCreated, Voted, Transfer, QuickJoined), the subgraph will silently lose data once deployed. Open a paired PR in poa-box/subgraph-pop with the matching schema/mapping update, and reference it from your contracts PR.
For reference, the most-relied-upon events are listed in docs/POP_OVERVIEW.md.
poa-box/Poa-frontend is the Next.js 14 web app at poa.box. It uses:
- Wallets: wagmi + RainbowKit for EOAs; WebAuthn passkeys for ERC-4337 smart accounts.
- Bundler: Pimlico (UserOps) with EIP-7702 EOA-delegation support.
- Reads: Apollo Client against the subgraph.
- Writes: viem + a service layer in
poa-app/src/services/web3/(TransactionManager,SmartAccountTransactionManager, domain services). - Networks: Arbitrum One (home for accounts), Gnosis (default org-deploy chain), Sepolia and Base Sepolia (testnets).
The frontend ships with sensible RPC/subgraph fallbacks, so it works offline-of-Pinata for development without any keys; only NEXT_PUBLIC_PIMLICO_API_KEY is needed to actually submit passkey UserOps. Static builds deploy to IPFS via Pinata; Cloudflare Workers route the poa.box and poa.earth domains.
poa-box/poa-cli is a TypeScript CLI and autonomous-agent framework for interacting with POP from the terminal. It is useful for scripted org management and for AI agents that participate in governance on behalf of a member.
# 1. Clone with submodules
git clone --recurse-submodules https://github.com/poa-box/POP.git
cd POP
# (or if already cloned)
git submodule update --init --recursive
# 2. Configure your env
cp .env.example .env
# Edit .env and set DEPLOYER_PRIVATE_KEY (only required for deploys)
# 3. Build and test
forge build
forge test -vvvSubmodules pulled in by forge install / git submodule:
| Submodule | Purpose |
|---|---|
lib/forge-std |
Foundry standard library (testing helpers). |
lib/openzeppelin-contracts |
OpenZeppelin v5 standard contracts. |
lib/openzeppelin-contracts-upgradeable |
OpenZeppelin v5.3 upgradeable contracts (Initializable, OwnableUpgradeable, PausableUpgradeable, ReentrancyGuardUpgradeable). |
lib/hats-protocol |
Hats Protocol interfaces; the foundation for POP role-based access. |
lib/solady |
Gas-optimized utility library (used selectively). |
Do not run foundryup in CI environments; Foundry is pre-installed.
# Default profile (optimizer OFF, EVM = osaka, P256 precompile available)
forge build
forge test
forge test -vvv # CI verbosity
forge test --match-contract HybridVoting # single suite
forge fmt # MUST run before every commit/PR
forge coverage # coverage filtered to src/ in CI
# Production profile (optimizer 200 runs, EVM = cancun, ~37% smaller bytecode)
FOUNDRY_PROFILE=production forge buildWhy is the optimizer OFF by default? From foundry.toml:
Optimizer OFF: Solidity IR bug with
vm.roll()in tests (issues #4934, #1373, #8102). For production:FOUNDRY_PROFILE=productionto enable optimizer (37% smaller contracts).
Running tests with the optimizer enabled silently produces wrong results when vm.roll() advances the block. Keep the optimizer off for tests, on for production deploys.
The .t.sol.skip files. Three test files require an EntryPoint fork (slow, expensive, not run in CI):
test/PaymasterHub.t.sol.skiptest/PaymasterHubIntegration.t.sol.skiptest/PaymasterHubInvariants.t.sol.skip
Rename them to .t.sol to enable locally. The repository's broader paymaster behavior is exercised by the always-on integration suites (PasskeyPaymasterIntegration.t.sol, PaymasterHubSolidarity.t.sol).
The full deployment walkthrough lives in script/README.md. At a high level:
-
Once per chain: deploy the protocol layer.
forge script script/deploy/DeployInfrastructure.s.sol:DeployInfrastructure \ --rpc-url base-sepolia --broadcast
The script writes the deployed addresses into
script/config/infrastructure.json, which is committed to the repo. Subsequent org deploys read that file automatically; no manual copying. -
Many times per chain: deploy organizations.
FOUNDRY_PROFILE=production forge script script/org/DeployOrg.s.sol:DeployOrg \ --rpc-url base-sepolia --broadcast
The deployer reads an org config JSON (default:
script/config/org-config-example.json; override withORG_CONFIG_PATH=...).
L2 only for org deployment. Atomic full-org deployment costs ~22.5M gas and exceeds Sepolia's ~16.7M block gas limit. Use Base Sepolia, Optimism Sepolia, Arbitrum Sepolia, or any L2/L1 with a sufficient block gas limit.
Pre-configured RPC endpoints (no API keys needed) are listed in foundry.toml under [rpc_endpoints]: sepolia, holesky, optimism-sepolia, base-sepolia, arbitrum-sepolia, polygon-amoy, hoodi, mainnet, optimism, base, arbitrum, polygon, gnosis, local.
For contract verification, set ETHERSCAN_API_KEY and pass --verify --etherscan-api-key $ETHERSCAN_API_KEY (with --verifier-url for non-Ethereum chains; see script/README.md for the full table).
- Custom errors only. No
require()with string messages; they are gas-prohibitive and inconsistent. Useif (cond) revert CustomError();. - Reentrancy. All value-transferring externals use
ReentrancyGuardUpgradeable(or an inline_lockflag for legacy contracts). - Initialization. Every upgradeable implementation constructor calls
_disableInitializers(). Initialization happens through the proxy viainitialize(...). - Boundary validation. Use
src/libs/ValidationLib.sol(requireNonZeroAddress, etc.) at all external entrypoints. - Permissions via Hats. Always go through
HatManager.hasAnyHat()rather than rawIHats.isWearerOfHat().Executor'sallowedCalleris the only contract permitted to invokeexecute(). - Slither in CI. The pipeline runs Slither and fails on
highseverity. Thearbitrary-send-ethdetector is excluded (seeslither.config.json) because POP intentionally forwards ETH fromExecutor/PaymentManagercalls authorized by governance. - Upgrade safety in CI. Storage-breaking changes against
upgrades/baseline/fail the build viascript/upgrades/ValidateUpgrade.s.sol. - Renounced
Executorownership. After deployment, theExecutor'sOwnableUpgradeableownership is renounced; only the configured voting contract can call it. This is intentional. Never re-introduce a privileged owner path.
To report a vulnerability, see SECURITY.md.
Contributions are very welcome. Read CONTRIBUTING.md before opening a PR; it covers the contribution process in full. The non-negotiable rules:
forge fmtmust pass; CI will reject unformatted Solidity.- Conventional-commit titles with PR numbers:
feat: add cashout relay (#123),fix: ...,refactor: .... - Use named imports only:
import {Foo} from "./Foo.sol";. - Custom errors, never
requirestrings. - ERC-7201 namespaced storage; no
__gaparrays. _disableInitializers()in every upgradeable implementation constructor.- No OpenZeppelin
AccessControl; all roles are Hats. - Do not edit anything under
upgrades/; it is auto-generated. - Match existing pragma when modifying a file. Pragmas across
src/span^0.8.17–^0.8.30; do not bump without testing every dependent. - If your change renames or removes an event, open a paired PR in poa-box/subgraph-pop.
- If your change alters a
Layout, do so append-only; never reorder or remove fields.
Where to start. Browse open issues, particularly anything tagged good first issue or help wanted. For non-trivial changes, open an issue first to align on approach.
docs/POP_OVERVIEW.md: protocol architecture, principles, worked examplesdocs/HYBRID_VOTING.md: multi-class voting designdocs/DIRECT_DEMOCRACY_VOTING.md: one-member-one-voice mechanicsdocs/TASK_MANAGER.md: project/task/application lifecycledocs/ORG_DEPLOYER.md: atomic org deployment internalsdocs/PAYMASTER_HUB.md: gas sponsorship and the solidarity funddocs/hub-v2-satellite-only-admin-call.md:PoaManagerHubv2 admin-call notesSWITCHABLE_BEACON.md: upgrade management architecturescript/README.md: full deployment walkthrough (infra + orgs)CLAUDE.md: concise rules-of-the-road (also a great human reference)
- Web · poa.box
- Discord · discord.gg/9SD6u4QjTt
- X · @PoaPerpetual
- Email · hudson@poa.community
POP is licensed under the GNU Affero General Public License v3.0. The same license covers the rest of the stack (subgraph, frontend, CLI). If you fork or run a hosted version, you must publish your source modifications under the same terms.