PoA Manager Contract
Understanding the PoA Manager contract and why it simplifies multi-sig governance
Why Another Contract?
You might be wondering: Why do we need a PoA Manager when we already have the Validator Manager?
The short answer is: you don't have to use it. The PoA Manager is an optional convenience layer that makes multi-sig operations significantly easier.
The Problem with Direct Multi-Sig Ownership
When you transfer ownership of the Validator Manager directly to a multi-sig wallet (like Safe), you encounter several challenges:
1. Complex Transaction Building
Multi-sig wallets like Safe need to construct transactions that call the Validator Manager's functions. Each validator operation (add, remove, change weight) requires building complex calldata with:
- Node IDs
- BLS public keys
- Owner structs
- Weight parameters
2. Multi-Phase Operations
Remember from the previous chapter that validator operations are two-phase:
- Initiate on the L1/C-Chain
- Complete after P-Chain confirmation
With a direct multi-sig setup, each phase requires a separate multi-sig transaction approval, doubling the coordination overhead.
3. Parameter Complexity
The Validator Manager functions take complex parameters like PChainOwner structs. Building these correctly through a multi-sig UI is error-prone.
The PoA Manager Solution
The PoA Manager acts as an intermediary contract that simplifies multi-sig governance:
How It Works
- Ownership Transfer: The Validator Manager's owner is set to the PoA Manager contract
- Multi-Sig Control: The PoA Manager itself is owned by your multi-sig wallet
- Simplified Calls: The PoA Manager exposes simpler function signatures that the multi-sig can easily call
Key Benefits
| Aspect | Direct Multi-Sig | With PoA Manager |
|---|---|---|
| Transaction Complexity | High - complex calldata | Low - simple parameters |
| Error Risk | Higher - manual encoding | Lower - contract handles encoding |
| Upgrade Path | Difficult | Easier - only update PoA Manager |
| Default Parameters | None | Can set sensible defaults |
PoA Manager Contract Structure
The PoA Manager contract is designed to be a thin wrapper around the Validator Manager:
contract PoAManager is Ownable {
IValidatorManager public immutable validatorManager;
constructor(address _owner, address _validatorManager) {
_transferOwnership(_owner);
validatorManager = IValidatorManager(_validatorManager);
}
// Simplified validator operations
function addValidator(...) external onlyOwner { ... }
function removeValidator(...) external onlyOwner { ... }
function changeValidatorWeight(...) external onlyOwner { ... }
}Is PoA Manager Required?
No! You can absolutely transfer Validator Manager ownership directly to a multi-sig. The PoA Manager just makes the multi-sig experience smoother. For simple setups or if you're comfortable building complex transactions, direct ownership works fine.
Proxy vs. Implementation: The Address That Trips Everyone Up
This section applies only if your Validator Manager is deployed behind a proxy — which is the default path in this course. The genesis ships an OpenZeppelin TransparentUpgradeableProxy (commonly at 0xfacade..., though some templates use a different address such as 0xFEEDC0DE...) and Upgrade Proxy pointed it at your implementation. If you deployed the Validator Manager directly, with no proxy, it is a single address — skip the distinction below and use that one address everywhere.
When you deploy behind a proxy, "the Validator Manager" is really three contracts — and almost every PoA setup failure comes from confusing them:
| Contract | Example address | What it holds | When you use it |
|---|---|---|---|
| Implementation | the address you deployed | Logic only — no state | Never directly. It is just the code the proxy borrows. |
| Proxy | 0xfacade... (or 0xFEEDC0DE...) | All state: owner, validator set, settings | Everywhere. Initialization, transferOwnership, reads, and the PoA Manager's constructor all target this. |
| ProxyAdmin | 0xdad0... | The proxy's upgrade admin | Only to upgrade the implementation. |
The rule: when your manager is behind a proxy, whenever a step asks for "the Validator Manager address," use the proxy address (e.g. 0xfacade...) — never the implementation.
The proxy delegatecalls into the implementation, so all state (including owner) lives at the proxy address. If you point the PoA Manager at the implementation, or call transferOwnership on the implementation, you are operating on a stateless contract: the transactions "succeed" but change nothing the proxy uses, and later validator operations revert.
Concretely, this governs the next two steps:
- Deploying the PoA Manager — its constructor stores
validatorManagerand forwards every call there, so pass the proxy. - Transferring ownership —
transferOwnershipupdates the proxy'sownerstorage, so call it on the proxy.
The Console tools in this course resolve the proxy address for you automatically from your L1. You'll mainly hit this pitfall when scripting the setup by hand — for example a private L1 that can't use the Console — so there, double-check that every "Validator Manager" address is 0xfacade..., not the implementation you just deployed.
Architecture Overview
Here's how the complete multi-sig setup looks:
In the following sections, you'll set up this architecture step by step:
- Create an L1 with VMC on C-Chain
- Set up a Safe/Ash wallet
- Deploy the PoA Manager
- Transfer ownership through the chain
Is this guide helpful?


