# Offline MultisigGovernanceV3 Proposal Generator — Base Pilot

`safe_payload_generator.py` creates a deterministic, unsigned JSON proposal
package for the **Base-only** V3 pilot. It is offline-only: it has no RPC,
HTTP, signing, Safe Transaction Service, wallet, subprocess, or broadcast
capability.

## Resolved owner and transport model

`MultisigGovernanceV3` is the on-chain owner of `SafeBondFactoryV3` and every
administered V3 factory/module. An EOA or Safe must **never** call an
owned factory/module directly. The generator therefore produces one outer
transaction per action:

1. Submit `MultisigGovernanceV3.propose(target, value, calldata)`.
2. Read the emitted `ProposedV3` proposal ID and collect confirmations from
   three distinct current signers using `confirm(proposalId)`.
3. At the emitted `eta` (and never before the fixed 24-hour floor), a current
   signer calls `execute(proposalId)` on `MultisigGovernanceV3`.

The construction batch emits this complete, required order:

1. `BasePilotAccessControllerV3.setModule(coordinator, true)`
2. `BasePilotAccessControllerV3.setDesignPartner(partner, true)`
3. `BasePilotAccessControllerV3.setCaps(globalWei, perPositionWei)` using the
   exact finalization-candidate manifest values
4. `SafeBondFactoryV3.configureFactories(partnershipFactory, accountabilityFactory)`
5. `BasePilotAccessControllerV3.setDeployEnabled(true)`
6. `SafeBondFactoryV3.setDeployAllowed(true)`
7. `SafeBondFactoryV3.deployPartnership(designPartner, address(0))`
8. `SafeBondFactoryV3.deployAccountability(designPartner)`

It never emits `setWriteEnabled(true)`. Child pause-target registration and
write enablement are a separate receipt-bound follow-up after both real child
addresses and their constructor-pinned runtime hashes are verified.

The child runtime hashes are stable across multiple fresh children with the
same reviewed constructor bindings. `EIP712BaseV3` computes (rather than
caches as an immutable) the verifying-contract domain separator, preserving
the `DOMAIN_SEPARATOR()` ABI and EIP-712 digest semantics without placing each
child address in its runtime bytecode. This is part of a fresh deployment:
the coordinator constructor has four additional `bytes32` hash arguments,
`configureFactories(address,address)` is a new one-time call, and the factory
references are new storage slots. No proxy upgrade, storage migration, or V2
change is assumed.

The package includes the inner target/value/calldata, a human description and
Keccak-256 description hash, outer `propose` calldata, confirmation
instructions, and expected earliest execution. The event's `eta` is
authoritative because it is calculated from the actual on-chain proposal
timestamp; the deterministic expected value is an offline planning value based
on `createdAt + 86400`.

An optional Safe may be identified with `--safe-signer` only when it is one of
the five governance signers. It remains a signer transport, never an owner.
The output labels that role explicitly and always directs its transaction to
`MultisigGovernanceV3`, not to an owned target.

## Required inputs and validation

All supplied addresses are required except `--safe-signer`; supplied addresses
must be nonzero, 20-byte EVM addresses. All-lowercase/all-uppercase addresses
are normalized to EIP-55 form; a mixed-case address must have a valid checksum.

| Input | Why it is required |
|---|---|
| `--governance` | Deployed `MultisigGovernanceV3`, the sole recipient of each outer proposal transaction. |
| `--safe-signer` | Optional Safe that is one governance signer only, never a target owner. |
| `--factory-coordinator` | `SafeBondFactoryV3` governed target. It is nested in proposal calldata, never a direct recipient. |
| `--controller` | `BasePilotAccessControllerV3` target for the four required precondition calls. |
| `--deployment-manifest` | Required finalization-candidate bonds-only manifest; validated before output and the sole source of all cap values. |
| `--design-partner` | The one allowlisted partner address used for both child deployments. |
| `--partnership-factory` | Declared address returned by the coordinator's `partnershipFactory()` getter. |
| `--accountability-factory` | Declared address returned by the coordinator's `accountabilityFactory()` getter. |
There are no standalone cap inputs. The manifest validator rejects any drift
from the reviewed `0.40 ETH` global and `0.20 ETH` per-position controller
ceilings and the exact immutable child caps. The package fingerprint includes
the finalization-candidate manifest payload hash.

Only `base` (chain ID `8453`) is allowed. `createdAt` is never read from the
local clock: it defaults to `0`, or an explicit unsigned Unix timestamp may be
provided for reproducible planning output.

## Offline dry run

This command uses deliberately non-production dummy addresses and prints JSON
only. It neither writes a file nor contacts a service.

```bash
python3 pr8/migration/safe_payload_generator.py \
  --governance 0x1111111111111111111111111111111111111111 \
  --safe-signer 0x2222222222222222222222222222222222222222 \
  --factory-coordinator 0x3333333333333333333333333333333333333333 \
  --controller 0x7777777777777777777777777777777777777777 \
  --deployment-manifest /secure/finalization-candidate.json \
  --design-partner 0x4444444444444444444444444444444444444444 \
  --partnership-factory 0x5555555555555555555555555555555555555555 \
  --accountability-factory 0x6666666666666666666666666666666666666666 \
  --created-at 0 \
  --dry-run
```

`--dry-run` is mandatory and the tool has no file-output mode. The checked-in
deployment manifest deliberately keeps every owner-selected address `null`;
the tool cannot turn that review artifact into a production authorization.

## Checks before an owner-approved proposal

1. Supply a separately approved `finalization-candidate` manifest. The checked-in
   manifest remains review-only and is rejected for package generation.
2. Verify `SafeBondFactoryV3.owner()` equals the selected
   `MultisigGovernanceV3` address.
3. Verify each specialized factory `extcodehash` equals the immutable,
   constructor-pinned coordinator expectation (including immutable patches),
   then execute `configureFactories` before `setDeployAllowed(true)`.
4. Submit only the generated outer proposal transaction to governance; record
   `ProposedV3` ID, `requiredConfirmations`, and `eta`.
5. Confirm with three distinct current signers, wait until `eta`, and execute
   only through governance. A guardian cannot propose, confirm, execute, or
   reconfigure.
6. After both child deployment receipts exist, separately add each actual child
   as a guardian pause target, verify controller pause coverage and each child
   runtime hash, then submit a distinct governed `setWriteEnabled(true)` action.
7. If a Safe is used, independently verify it is one current signer and does
   not call any owned factory/module.

The checked-in examples are null-address, review-only scaffolds, not production
proposals or deployment instructions.

## Tests

```bash
python3 -m unittest pr8/migration/test_safe_payload_generator.py -v
```

The tests independently recompute selectors with `Crypto.Hash.keccak`, compare
inner and outer calldata with `eth_abi`, verify deterministic output, and
reject zero/bad-checksum addresses, unsupported chains, draft manifests, cap
mismatches, and caps above the fixed Base-pilot ceiling.
