@ballkidz/defifa 0.0.16 → 0.0.18

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/ADMINISTRATION.md CHANGED
@@ -2,6 +2,33 @@
2
2
 
3
3
  Admin privileges and their scope in defifa-collection-deployer-v6.
4
4
 
5
+ ## At A Glance
6
+
7
+ | Item | Details |
8
+ |------|---------|
9
+ | Scope | Defifa game launch, scorecard ratification, lifecycle progression, and fee-project ownership locking. |
10
+ | Operators | Permissionless game creators and scorecard participants, plus the protocol contracts `DefifaDeployer`, `DefifaGovernor`, `DefifaHook`, and optional `DefifaProjectOwner`. |
11
+ | Highest-risk actions | Launching a game with bad immutable parameters, ratifying the winning scorecard, or transferring a fee-project NFT into `DefifaProjectOwner`. |
12
+ | Recovery posture | Game parameters are intentionally immutable after launch. A bad game setup usually means launching a new game or falling back to the no-contest path if available. |
13
+
14
+ ## Routine Operations
15
+
16
+ - Validate game timings, tier setup, fee routing, and attestation settings before calling `launchGameWith()`.
17
+ - During scoring, rely on the documented submission, attestation, and ratification flow rather than looking for discretionary admin overrides that do not exist.
18
+ - Use `triggerNoContestFor()` when the game has entered the documented no-contest condition and refunds need to be unlocked.
19
+ - Treat the fee-project ownership proxy as a burn-lock mechanism, not a recoverable custody tool.
20
+
21
+ ## One-Way Or High-Risk Actions
22
+
23
+ - `launchGameWith()` fixes the game's core configuration.
24
+ - `setTierCashOutWeightsTo()` is irreversible once weights are set for a game.
25
+ - `DefifaProjectOwner` permanently locks the project NFT it receives.
26
+
27
+ ## Recovery Notes
28
+
29
+ - If a scorecard never reaches a valid ratification path and timeout conditions are met, use the documented no-contest recovery flow.
30
+ - If the game launched with the wrong immutable economics or timing, recovery is a replacement game deployment rather than an admin patch.
31
+
5
32
  ## Roles
6
33
 
7
34
  | Role | Who | How Assigned |
@@ -30,10 +57,10 @@ Admin privileges and their scope in defifa-collection-deployer-v6.
30
57
 
31
58
  | Function | Required Role | Permission Check | What It Does |
32
59
  |----------|--------------|-----------------|-------------|
33
- | `initializeGame()` | DefifaDeployer (owner) | `onlyOwner` | Sets attestation start time and grace period for a game. Enforces minimum 1-day grace period. Called automatically during `launchGameWith()`. |
60
+ | `initializeGame()` | DefifaDeployer (owner) | `onlyOwner` | Sets the attestation start time, attestation grace period, and optional post-quorum `timelockDuration` for a game. Enforces a minimum 1-day grace period. Called automatically during `launchGameWith()`. |
34
61
  | `submitScorecardFor()` | Anyone | Must be in SCORING phase; no ratified scorecard yet; no duplicate scorecard hash; weighted tiers must have nonzero supply | Submits a scorecard for attestation. Sets `attestationsBegin` and `gracePeriodEnds` timestamps. Snapshots pending reserves per tier for BWA computation. |
35
- | `attestToScorecardFrom()` | Any NFT holder | Must be in SCORING phase; scorecard must be ACTIVE or SUCCEEDED; caller cannot have already attested | Records attestation weight based on tier holdings at the `attestationsBegin - 1` checkpoint timestamp. Uses pending reserve snapshot from submission time. |
36
- | `ratifyScorecardFrom()` | Anyone | Scorecard must be in SUCCEEDED state (quorum met + grace period elapsed); no scorecard already ratified | Executes the scorecard via low-level call to `setTierCashOutWeightsTo` on the hook, then calls `fulfillCommitmentsOf`. |
62
+ | `attestToScorecardFrom()` | Any NFT holder | Must be in SCORING phase; scorecard must be `ACTIVE`, `QUEUED`, or `SUCCEEDED`; caller cannot have already attested | Records attestation weight based on tier holdings at the `attestationsBegin - 1` checkpoint timestamp. Uses pending reserve snapshot from submission time. |
63
+ | `ratifyScorecardFrom()` | Anyone | Scorecard must be in SUCCEEDED state (quorum met, grace period elapsed, and any configured `timelockDuration` elapsed); no scorecard already ratified | Executes the scorecard via low-level call to `setTierCashOutWeightsTo` on the hook, then calls `fulfillCommitmentsOf`. |
37
64
 
38
65
  ### DefifaHook
39
66
 
@@ -73,7 +100,7 @@ COUNTDOWN --> MINT --> REFUND (optional) --> SCORING --> COMPLETE or NO_CONTEST
73
100
  **Who controls scoring:**
74
101
  1. Anyone submits a scorecard during SCORING (`submitScorecardFor`)
75
102
  2. NFT holders attest based on their per-tier voting weight (`attestToScorecardFrom`)
76
- 3. Once quorum (50% of minted tiers' attestation power) is met and grace period passes, anyone ratifies (`ratifyScorecardFrom`)
103
+ 3. Once quorum (50% of minted tiers' attestation power) is met, the grace period has elapsed, and any configured `timelockDuration` has elapsed, the scorecard reaches `SUCCEEDED` and anyone can ratify it with `ratifyScorecardFrom`
77
104
  4. The governor calls `setTierCashOutWeightsTo` on the hook via low-level call
78
105
  5. `fulfillCommitmentsOf` sends fee payouts (try-catch) and queues the final ruleset
79
106
 
@@ -84,8 +111,8 @@ The quorum threshold is 50% of the total attestation power across all tiers with
84
111
  **Edge cases:**
85
112
  - **Tiers with zero mints:** Tiers with `currentSupplyOfTier(tierId) == 0` are excluded from the quorum calculation. They have no attestation power and cannot influence scoring.
86
113
  - **All mints in a single tier:** If all participation concentrates in one tier, that tier's holders control the quorum. The 50% threshold still applies -- holders of 50% of that tier's supply can ratify a scorecard.
87
- - **Grace period:** After a scorecard reaches quorum (SUCCEEDED state), a grace period (`attestationsGracePeriod`, minimum 1 day) must elapse before ratification. This gives dissenters time to attest to a competing scorecard that could overtake the first.
88
- - **Competing scorecards:** Multiple scorecards can be submitted. Each tracks attestations independently. Only the first to be ratified (quorum met + grace period elapsed + `ratifyScorecardFrom()` called) takes effect. Once ratified, no other scorecard can be ratified for the same game.
114
+ - **Grace period and timelock:** After submission, a scorecard stays `ACTIVE` until its grace period ends. If it has quorum at that point, it becomes `QUEUED` when `timelockDuration > 0`, or `SUCCEEDED` immediately when `timelockDuration == 0`. Attestations remain allowed while the scorecard is `ACTIVE`, `QUEUED`, or `SUCCEEDED`, but revocations are only allowed while it is `ACTIVE`. Ratification is only allowed from `SUCCEEDED`.
115
+ - **Competing scorecards:** Multiple scorecards can be submitted. Each tracks attestations independently. Only the first to be ratified after meeting quorum, clearing the grace period, and clearing any configured timelock takes effect. Once ratified, no other scorecard can be ratified for the same game.
89
116
  - **Scorecard timeout:** If `scorecardTimeout` is nonzero and elapses without ratification, the game enters NO_CONTEST state, enabling full refunds via `triggerNoContestFor()`.
90
117
 
91
118
  **No single entity controls scoring.** The process requires collective attestation from NFT holders across tiers.
@@ -103,6 +130,7 @@ The following are set at game creation and cannot be changed:
103
130
  | Fee structure | Constructor constants | `DEFIFA_FEE_DIVISOR = 20` (5%), `BASE_PROTOCOL_FEE_DIVISOR = 40` (2.5%) |
104
131
  | Attestation start time | `launchGameWith()` | Stored in governor via `initializeGame()` |
105
132
  | Attestation grace period | `launchGameWith()` | Minimum 1 day enforced in `initializeGame()` |
133
+ | Timelock duration | `launchGameWith()` | Optional cooling period after quorum before a scorecard becomes ratifiable |
106
134
  | Default attestation delegate | `launchGameWith()` | Stored in hook |
107
135
  | `minParticipation` threshold | `launchGameWith()` | 0 = disabled |
108
136
  | `scorecardTimeout` | `launchGameWith()` | 0 = disabled |
package/ARCHITECTURE.md CHANGED
@@ -1,115 +1,67 @@
1
- # defifa-collection-deployer-v6 — Architecture
1
+ # Architecture
2
2
 
3
3
  ## Purpose
4
4
 
5
- Prediction game platform built on Juicebox V6. Creates games where players buy NFT tiers representing outcomes, a governance process scores the outcomes, and winners claim treasury funds proportional to their tier's score.
6
-
7
- ## Contract Map
8
-
9
- ```
10
- src/
11
- ├── DefifaDeployer.sol — Deploys games: project + hook + governor + URI resolver
12
- ├── DefifaHook.sol — Pay/cashout hook with game phase logic and attestation
13
- ├── DefifaGovernor.sol — Scorecard ratification via tier-weighted governance
14
- ├── DefifaProjectOwner.sol — Proxy owner for Defifa projects
15
- ├── DefifaTokenUriResolver.sol — On-chain SVG metadata for game NFTs
16
- ├── enums/
17
- │ ├── DefifaGamePhase.sol — COUNTDOWN MINT → REFUND → SCORING → COMPLETE → NO_CONTEST
18
- │ └── DefifaScorecardState.sol
19
- ├── interfaces/ — IDefifaDeployer, IDefifaHook, IDefifaGovernor, etc.
20
- ├── libraries/
21
- │ ├── DefifaFontImporter.sol — Font loading for on-chain SVG rendering
22
- │ └── DefifaHookLib.sol — Game logic helpers
23
- └── structs/ — Scorecards, attestations, tier params, delegations
24
- ```
25
-
26
- ## Key Data Flows
27
-
28
- ### Game Lifecycle
29
- ```
30
- MINT Phase:
31
- Creator DefifaDeployer.launchGameWith()
32
- → Create JB project with DefifaHook as data/pay/cashout hook
33
- Deploy DefifaGovernor for scorecard governance
34
- → Players buy NFT tiers (outcomes they predict)
35
- Delegation happens during this phase only
36
-
37
- REFUND Phase:
38
- → Players can cash out for full refund (100% redemption rate)
39
-
40
- SCORING Phase:
41
- → Anyone → DefifaGovernor.submitScorecard(weights[])
42
- → Tier holders attest to scorecards
43
- → Scorecard reaches quorum → ratified
44
- → DefifaHook receives final cash-out weights per tier
45
-
46
- COMPLETE Phase:
47
- → Deployer → fulfillCommitmentsOf() sends fee payouts and queues the final ruleset
48
- → Winners → cash out NFTs at scored weights (see "Scored Weight Redemption" below)
49
- ```
50
-
51
- ### Governance Flow
52
- ```
53
- Scorer → DefifaGovernor.submitScorecard(tierWeights[])
54
- → Validate: correct phase, valid tier order, weights sum correctly
55
- → Create proposal hash
56
- → Snapshot pending reserves per tier for BWA computation
57
-
58
- Attestor → DefifaGovernor.attestToScorecard(proposalId)
59
- → Must hold NFT tier tokens at attestationsBegin - 1 checkpoint
60
- → Attestation weight = voting power from held tiers (diluted by snapshotted pending reserves)
61
- → When quorum reached → scorecard ratified
62
- → DefifaHook.setScorecard() called
5
+ `defifa-collection-deployer-v6` builds prediction games on top of Juicebox and the 721 tiers hook. Each game is a project with phased rulesets, a hook that mints and cashes out game-piece NFTs, and a governor that ratifies scorecards which decide how the prize pool is distributed.
6
+
7
+ ## Boundaries
8
+
9
+ - `DefifaHook` owns game-piece behavior, delegation, and cash-out math.
10
+ - `DefifaGovernor` owns scorecard submission, attestation, quorum, and ratification.
11
+ - `DefifaDeployer` owns game launch and post-ratification commitment fulfillment.
12
+ - Generic tier storage and terminal accounting stay in `nana-721-hook-v6` and `nana-core-v6`.
13
+
14
+ ## Main Components
15
+
16
+ | Component | Responsibility |
17
+ | --- | --- |
18
+ | `DefifaDeployer` | Launches phased projects, clones hooks, initializes the governor, and fulfills commitments |
19
+ | `DefifaHook` | ERC-721 game pieces, attestation delegation, custom cash-out weighting, and game-state aware behavior |
20
+ | `DefifaGovernor` | Scorecard proposals, attestations, quorum checks, grace periods, and ratification |
21
+ | `DefifaHookLib` | Shared validation and weight math extracted to keep the hook within size limits |
22
+ | `DefifaTokenUriResolver` | Dynamic token metadata and SVG rendering |
23
+ | `DefifaProjectOwner` | Lock helper for the fee project |
24
+
25
+ ## Runtime Model
26
+
27
+ ```text
28
+ creator
29
+ -> DefifaDeployer launches a project with staged rulesets
30
+ players
31
+ -> mint tiered NFTs during the mint window
32
+ holders
33
+ -> optionally refund during the refund window
34
+ attesters
35
+ -> submit and attest to scorecards during scoring
36
+ governor
37
+ -> ratifies the winning scorecard after quorum and grace-period checks
38
+ holders
39
+ -> cash out NFTs for their share of the prize pool during completion
63
40
  ```
64
41
 
65
- ### Scored Weight Redemption
42
+ ## Critical Invariants
66
43
 
67
- When a scorecard is ratified, `setTierCashOutWeightsTo` stores a weight per tier. Weights must sum to `TOTAL_CASHOUT_WEIGHT` (1e18). A tier with weight 0 means that outcome lost and holders get nothing.
44
+ - Delegation and attestation power must stay aligned with tier semantics; a scorecard system that can be inflated by supply alone breaks the game.
45
+ - Ratification is the only path that should install final cash-out weights.
46
+ - Refund, scoring, complete, and no-contest phases must remain mutually coherent. If phase transitions drift, funds get stuck.
47
+ - The deployer's commitment-fulfillment logic is part of game completion, not optional bookkeeping.
68
48
 
69
- When a holder cashes out an NFT during the COMPLETE phase:
49
+ ## Where Complexity Lives
70
50
 
71
- 1. **Per-token weight**: The tier's weight is divided equally among all tokens minted in that tier: `tokenWeight = tierWeight / tokensInTier`.
72
- 2. **Cash out amount**: `amount = (surplus + totalAmountRedeemed) * tokenWeight / TOTAL_CASHOUT_WEIGHT`. The formula uses `surplus + totalAmountRedeemed` (the original pot size) so that early and late redeemers receive the same value.
73
- 3. The token is burned and the holder receives their share of the treasury.
74
-
75
- Example: 100 ETH pot, 4 tiers, winning tier gets weight 500000000000000000 (50%). If that tier had 10 minted tokens, each token redeems for `100 * (500000000000000000 / 10) / 1e18 = 5 ETH`.
76
-
77
- ### fulfillCommitmentsOf
78
-
79
- Called automatically when a scorecard is ratified (by `ratifyScorecardFrom`). It performs two actions:
80
-
81
- 1. **Sends fee payouts**: Computes the fee portion of the pot based on the split percentages configured at game creation, then calls `sendPayoutsOf` to distribute fees to the protocol. If the payout fails, the fee stays in the pot and the function continues (try-catch ensures the final ruleset is always queued).
82
- 2. **Queues the final ruleset**: Queues a new Juicebox ruleset with `pausePay: true` (no new payments), `cashOutTaxRate: 0` (no tax on cash-outs), and the data hook still active so scored weights are enforced. This transitions the game to its terminal state where only cash-outs remain.
83
-
84
- ## Extension Points
85
-
86
- | Point | Interface | Purpose |
87
- |-------|-----------|---------|
88
- | Data hook | `IJBRulesetDataHook` | Phase-aware pay/cashout behavior |
89
- | Pay hook | `IJBPayHook` | NFT minting during MINT phase |
90
- | Cash out hook | `IJBCashOutHook` | Scored weight redemptions |
91
- | Token URI resolver | `IJB721TokenUriResolver` | On-chain SVG generation |
92
- | Governor | `IDefifaGovernor` | Scorecard governance |
51
+ - The game is split across hook, governor, and deployer contracts, but users experience it as one state machine.
52
+ - Scorecard weighting, attestation accounting, and phase transitions are tightly coupled.
53
+ - Completion logic is economically sensitive because it combines prize distribution with fee fulfillment.
93
54
 
94
55
  ## Dependencies
95
- - `@bananapus/core-v6` — Core protocol
96
- - `@bananapus/721-hook-v6` — NFT tier system
97
- - `@bananapus/address-registry-v6` — Deterministic deploys
98
- - `@bananapus/permission-ids-v6` — Permission constants
99
- - `@croptop/core-v6` — Croptop integration
100
- - `@rev-net/core-v6` — Revnet integration
101
- - `@openzeppelin/contracts` — Checkpoints, Ownable, Clones
102
- - `@prb/math` — mulDiv
103
- - `scripty.sol` — On-chain scripting for SVG
104
-
105
- ## Design Decisions
106
-
107
- **Attestation-based governance over token-weighted voting.** Each tier gets equal max attestation power (`MAX_ATTESTATION_POWER_TIER = 1e9`) regardless of how many tokens it sold. A holder's power within a tier is proportional to their share of that tier's supply. This prevents a popular outcome (e.g., the favorite team) from dominating the scorecard simply by having more buyers. Every outcome's community has equal say in ratification.
108
-
109
- **Six distinct game phases.** The MINT, REFUND, SCORING, and COMPLETE phases enforce a strict lifecycle where each action is only valid in its phase. MINT allows buying in, REFUND provides a grace period for full refunds, SCORING locks the treasury while governance resolves, and COMPLETE enables scored cash-outs. The COUNTDOWN phase gates minting before the game starts, and NO_CONTEST acts as a fallback if no scorecard is ratified within the timeout. This phased approach prevents timing exploits (e.g., buying in after seeing results) and ensures the treasury is never drained during scoring.
110
56
 
111
- **Proxy owner pattern (DefifaProjectOwner).** Game projects are owned by `DefifaDeployer` itself (`launchProjectFor` sets `owner: address(this)`), which restricts game operations to the deployer's hardcoded logic -- no EOA can rug the game by migrating terminals, minting tokens, or changing controllers. Separately, `DefifaProjectOwner` permanently holds the Defifa fee project NFT (DEFIFA_PROJECT_ID) and grants only `SET_SPLIT_GROUPS` permission to the `DefifaDeployer`, so the deployer can set splits on the fee project as needed for fee distribution.
57
+ - `nana-721-hook-v6` for tiered NFT infrastructure
58
+ - `nana-core-v6` for phased rulesets, terminals, and payout mechanics
59
+ - Juicebox governance and permission surfaces for project ownership and split updates
112
60
 
113
- **Weight-based redemption instead of per-tier pots.** Rather than splitting the treasury into separate pots per tier at scoring time, the system assigns each tier a weight out of `TOTAL_CASHOUT_WEIGHT` (1e18). Cash-outs compute their share of the entire surplus on the fly. This avoids complex accounting for partial redemptions and lets the bonding math work naturally as tokens are burned. Early and late redeemers within the same tier get the same per-token value because the formula uses `surplus + totalAmountRedeemed` as the denominator base.
61
+ ## Safe Change Guide
114
62
 
115
- **Scorecard immutability after ratification.** Once a scorecard reaches quorum and is ratified, `cashOutWeightIsSet` is permanently set to `true` and no new weights can be written. Combined with the final ruleset that pauses payments, this guarantees the game's outcome is final and the treasury can only decrease through cash-outs.
63
+ - Treat phase logic, governor logic, and hook cash-out logic as one system.
64
+ - If you change scorecard validation, also inspect attestation weighting and ratification thresholds.
65
+ - Keep `DefifaHookLib` and hook storage assumptions in sync; library extraction is for size, not for a separate trust boundary.
66
+ - Do not move generic 721 behavior into Defifa just because the game uses it heavily.
67
+ - When in doubt, trace an end-to-end game lifecycle rather than auditing one contract in isolation.