Stop RevNet v5 fund routing — interim 100% beneficiary split + v6 upgrade path
Background
The Markee RevNet on Juicebox v5 has a known vulnerability. This issue covers the full migration: stop routing funds to the v5 terminal immediately, route 100% to each Markee's configured beneficiary address in the interim, and restructure contracts + frontend so switching to v6 requires only admin config calls — no code changes, no redeployment.
This is also an opportunity to standardize all contracts on the v1.0 architecture (direct RPC, no subgraph dependency) and fully deprecate the subgraph once migrations are complete.
Current architecture
All three active strategy contracts (TopDawgStrategy, TopDawgPartnerStrategy, FixedPriceStrategy) route funds to the v5 RevNet terminal (0x2dB6d704058E552DeFE415753465df8dF0361846) via IJBMultiTerminal.pay().
| Contract |
Current behavior |
Indexing |
| TopDawgPartnerStrategy |
62% to partner beneficiary, 38% to RevNet. beneficiaryAddress and percentToBeneficiary are mutable. |
Subgraph |
| TopDawgStrategy |
100% to RevNet. No beneficiary split. revNetTerminal / revNetProjectId are immutable. |
Subgraph |
| FixedPriceStrategy |
100% to RevNet. No beneficiary address. revNetTerminal / revNetProjectId are immutable. |
Subgraph |
| v1.0 Leaderboard.sol |
62/38 split, immutable RevNet config. percentToBeneficiary is mutable (admin access confirmed). |
Direct RPC |
Goals
- Immediate: Stop all ETH flowing to
0x2dB6d704058E552DeFE415753465df8dF0361846 from every Markee contract
- Interim: Route 100% of funds to the
beneficiaryAddress configured on each Markee
- Standardize: All redeployed contracts use v1.0 architecture — direct RPC, no subgraph dependency
- Preserve history: New deployments seed historical state (total funds, message count, last owner/message) from legacy contracts via a one-time
initializeHistory() admin function
- Forward-compatible: The v6 upgrade is a targeted admin config update — no code changes, no redeployment
Immediate stopgap (no redeployment required)
Legacy strategy contracts (TopDawgPartnerStrategy instances)
TopDawgPartnerStrategy already has an if (revNetAmount > 0) guard that skips the RevNet call when the amount is zero, and a mutable percentToBeneficiary with an admin setter.
Action: Call setPercentToBeneficiary(10000) on each deployed TopDawgPartnerStrategy instance today. Routes 100% to the existing partner beneficiary address; RevNet call is skipped cleanly.
v1.0 contracts (Leaderboard.sol instances)
Admin access confirmed and percentToBeneficiary is mutable on v1.0.
Action: Call the equivalent percent setter on each deployed v1.0 Leaderboard instance to route 100% to the configured beneficiary. No redeployment needed. These contracts stay in place and are not replaced as part of this migration.
TopDawgStrategy and FixedPriceStrategy have immutable RevNet config — they require new deployments (see below).
Contract changes
Shared: history preservation via initializeHistory()
All new contract deployments must include a one-time admin function to seed historical state from the legacy contract being replaced:
function initializeHistory(
uint256 _totalFundsRaised,
uint256 _totalMessagesBought,
address _lastMessageOwner,
string calldata _lastMessage
) external onlyAdmin {
require(!historyInitialized, "Already initialized");
totalFundsRaised = _totalFundsRaised;
totalMessagesBought = _totalMessagesBought;
lastMessageOwner = _lastMessageOwner;
lastMessage = _lastMessage;
historyInitialized = true;
}
- Call immediately after deployment, before going live
- Values are read from the legacy contract (or subgraph) at migration time
historyInitialized flag makes this a one-time operation — cannot be overwritten
- This same pattern carries forward to v6:
initializeHistory() seeds the v6 contract from the v1.0 deployment's final state
TopDawgStrategy.sol — new deployment required
Current: routes 100% to RevNet, no beneficiary split, revNetTerminal/revNetProjectId are immutable. Indexed via subgraph.
Changes:
- Rewrite as v1.0-style contract (direct RPC, no subgraph)
- Change
revNetTerminal and revNetProjectId from immutable to mutable storage variables
- Add admin setters:
setRevNetTerminal(address) and setRevNetProjectId(uint256)
- Add
address public beneficiaryAddress (set to Markee Cooperative multisig 0xAf4401E765dFf079aB6021BBb8d46E53E27613DB at deployment) + setBeneficiaryAddress(address) setter
- Add
uint256 public percentToBeneficiary (basis points) + setPercentToBeneficiary(uint256) setter
- Add
bool public revNetEnabled (default false) + setRevNetEnabled(bool) setter
- Add
initializeHistory() (see above)
- Update
_splitAndDistributeFunds():
- When
revNetEnabled == false: send 100% to beneficiaryAddress, skip RevNet call
- When
revNetEnabled == true: apply percentToBeneficiary split, call RevNet with remainder (same if (revNetAmount > 0) guard as partner strategy)
- Deploy new instances; seed history; migrate all existing Markees via
changePricingStrategy() on Markee.sol
TopDawgPartnerStrategy.sol — new deployment required for v6 readiness
Stopgap available now: Call setPercentToBeneficiary(10000) on each deployed instance.
For proper fix (needed for v6 switchover):
- Rewrite as v1.0-style contract (direct RPC, no subgraph)
- Change
revNetTerminal and revNetProjectId from immutable to mutable storage variables
- Add admin setters:
setRevNetTerminal(address) and setRevNetProjectId(uint256)
- Add
bool public revNetEnabled (default false) + setRevNetEnabled(bool) setter
- Add
initializeHistory() (see above)
- Update
_splitAndDistributeFunds() to explicitly skip RevNet call when revNetEnabled == false
- Deploy new instances; seed history; migrate
FixedPriceStrategy.sol — new deployment required
Current: routes 100% to RevNet, no beneficiary address. Indexed via subgraph.
Changes:
- Rewrite as v1.0-style contract (direct RPC, no subgraph)
- Same mutable
revNetTerminal / revNetProjectId + setters
- Add
address public beneficiaryAddress + setter
- Add
bool public revNetEnabled (default false) + setter
- Add
initializeHistory() (see above)
- Update
changeMessage() payment logic: when revNetEnabled == false, send full msg.value to beneficiaryAddress; when revNetEnabled == true, call RevNet with full amount (or apply split ratio if added later)
- Deploy new instances; seed history; update
CONTRACTS config in frontend
v1.0 contracts (Markee.sol, Leaderboard.sol, LeaderboardFactory.sol)
Admin access confirmed. percentToBeneficiary is mutable — using Option B stopgap, no redeployment needed.
Action: Call the percent setter on each deployed Leaderboard instance to route 100% to the configured beneficiary. These contracts stay in place and are not replaced as part of this migration.
Migration sequence per contract
For each legacy strategy contract being redeployed:
- Read current state from legacy contract:
totalFundsRaised, totalMessagesBought, lastMessageOwner, lastMessage
- Deploy new contract
- Call
initializeHistory() with values from step 1
- Call
setPercentToBeneficiary(10000) (interim — 100% to beneficiary)
- Call
setBeneficiaryAddress(...) to configure the correct address
- Confirm
revNetEnabled is false (should be default)
- Migrate Markee instances to the new strategy via
changePricingStrategy()
- Update
CONTRACTS config in frontend
- Verify no funds reach
0x2dB6d704058E552DeFE415753465df8dF0361846
Frontend changes
frontend/lib/contracts/addresses.ts
- Remove the
REVNET_CONFIG block (project IDs 119/63/62/56 per chain, JB_TERMINAL address)
- Add a
REVNET_V6_CONFIG block (commented out / empty) as a placeholder — v6 cutover becomes a one-line fill-in
- Update strategy contract addresses in
CONTRACTS once new contracts are deployed
frontend/components/modals/BuyMessageModal.tsx
- Remove the
PHASES array (token emission rate schedule, lines 79–99) and calculateMarkeeTokens() function
- Remove "You'll receive X MARKEE tokens" display from the modal UI
- Update revenue split display:
"62% [beneficiary] / 38% Markee Cooperative" → "100% [beneficiary]"
- Remove
useReadContract call for beneficiaryAddress if no longer needed for token calc
frontend/components/modals/TopDawgModal.tsx
- Remove
calculateMarkeeTokens() and all token emission phase logic (lines 92–104)
- Remove "MARKEE tokens" display from bid confirmation UI
- Update split display copy to reflect 100% to beneficiary
frontend/lib/contracts/usePartnerMarkees.ts
- Update
fundingSplit strings: "62% to [Partner] / 38% to Markee Cooperative" → "100% to [Partner]"
- Remove or set
percentToBeneficiary to 10000 in partner config objects
frontend/app/how-it-works/page.tsx
- Remove or replace "View token terms on Revnets" link (line 391,
revnet.app/v5:base:119/terms)
- Remove or replace "See project on Revnets" link (line 403)
- Update the RevNet FAQ section (lines 398–406): explain that MARKEE token issuance is temporarily paused while RevNet v5 is replaced with v6; link to announcement once available
- Remove any copy describing the 62/38 split as permanent or immutable
frontend/app/owners/page.tsx
- Remove RevNet v5 links (lines 81 and 129, both pointing to
revnet.app/v5:base:119)
- Replace with a placeholder note: "v6 RevNet coming soon"
frontend/components/wallet/TokenBalance.tsx
- Keep this component — users still hold pre-pause tokens
- Remove or update the broken ops link (line 64,
app.revnet.eth.sucks/v5:base:119/ops)
- Replace link with static text or a "v6 coming soon" note
frontend/app/api/openinternet/leaderboards/route.ts
- Update
percentToBeneficiary values in server-side leaderboard config to 10000 where applicable
- Remove any RevNet-specific config fields
Subgraph deprecation
All redeployed contracts use v1.0 architecture and are indexed via direct RPC — no subgraph dependency. Once all legacy strategy contract instances are migrated:
- Confirm no active Markees still point to subgraph-indexed contracts
- Remove subgraph queries from frontend (Apollo Client calls,
useQuery hooks referencing subgraph data)
- Archive the subgraph — do not redeploy or update
- Remove subgraph-related env vars and config from the frontend once confirmed clean
The subgraph does not need schema updates as part of this migration — it will simply stop receiving new events once legacy contracts are migrated off.
V6 upgrade path
Once v6 RevNet is deployed, the full cutover is admin calls only — no code changes, no redeployment:
- Call
setRevNetTerminal(v6TerminalAddress) on each strategy contract
- Call
setRevNetProjectId(v6ProjectId) on each strategy contract
- Call
setPercentToBeneficiary(splitRatio) to restore the desired split (e.g. 6200 for 62/38)
- Call
setRevNetEnabled(true) on each strategy contract
- Populate
REVNET_V6_CONFIG in addresses.ts with new project IDs
- Re-add MARKEE token issuance display in modal components
- For any v1.0
Leaderboard contracts still active at v6 time: read their current state, deploy v6-ready replacement, call initializeHistory() with current values, migrate
Out of scope
- Actual v6 RevNet deployment or configuration
- Changes to the MARKEE token address or supply schedule
- Changes to the ecosystem / Superfluid / GitHub integration APIs
Acceptance criteria
Stop RevNet v5 fund routing — interim 100% beneficiary split + v6 upgrade path
Background
The Markee RevNet on Juicebox v5 has a known vulnerability. This issue covers the full migration: stop routing funds to the v5 terminal immediately, route 100% to each Markee's configured beneficiary address in the interim, and restructure contracts + frontend so switching to v6 requires only admin config calls — no code changes, no redeployment.
This is also an opportunity to standardize all contracts on the v1.0 architecture (direct RPC, no subgraph dependency) and fully deprecate the subgraph once migrations are complete.
Current architecture
All three active strategy contracts (
TopDawgStrategy,TopDawgPartnerStrategy,FixedPriceStrategy) route funds to the v5 RevNet terminal (0x2dB6d704058E552DeFE415753465df8dF0361846) viaIJBMultiTerminal.pay().Goals
0x2dB6d704058E552DeFE415753465df8dF0361846from every Markee contractbeneficiaryAddressconfigured on each MarkeeinitializeHistory()admin functionImmediate stopgap (no redeployment required)
Legacy strategy contracts (
TopDawgPartnerStrategyinstances)TopDawgPartnerStrategyalready has anif (revNetAmount > 0)guard that skips the RevNet call when the amount is zero, and a mutablepercentToBeneficiarywith an admin setter.Action: Call
setPercentToBeneficiary(10000)on each deployedTopDawgPartnerStrategyinstance today. Routes 100% to the existing partner beneficiary address; RevNet call is skipped cleanly.v1.0 contracts (
Leaderboard.solinstances)Admin access confirmed and
percentToBeneficiaryis mutable on v1.0.Action: Call the equivalent percent setter on each deployed v1.0
Leaderboardinstance to route 100% to the configured beneficiary. No redeployment needed. These contracts stay in place and are not replaced as part of this migration.TopDawgStrategyandFixedPriceStrategyhave immutable RevNet config — they require new deployments (see below).Contract changes
Shared: history preservation via
initializeHistory()All new contract deployments must include a one-time admin function to seed historical state from the legacy contract being replaced:
historyInitializedflag makes this a one-time operation — cannot be overwritteninitializeHistory()seeds the v6 contract from the v1.0 deployment's final stateTopDawgStrategy.sol— new deployment requiredCurrent: routes 100% to RevNet, no beneficiary split,
revNetTerminal/revNetProjectIdare immutable. Indexed via subgraph.Changes:
revNetTerminalandrevNetProjectIdfromimmutableto mutable storage variablessetRevNetTerminal(address)andsetRevNetProjectId(uint256)address public beneficiaryAddress(set to Markee Cooperative multisig0xAf4401E765dFf079aB6021BBb8d46E53E27613DBat deployment) +setBeneficiaryAddress(address)setteruint256 public percentToBeneficiary(basis points) +setPercentToBeneficiary(uint256)setterbool public revNetEnabled(defaultfalse) +setRevNetEnabled(bool)setterinitializeHistory()(see above)_splitAndDistributeFunds():revNetEnabled == false: send 100% tobeneficiaryAddress, skip RevNet callrevNetEnabled == true: applypercentToBeneficiarysplit, call RevNet with remainder (sameif (revNetAmount > 0)guard as partner strategy)changePricingStrategy()onMarkee.solTopDawgPartnerStrategy.sol— new deployment required for v6 readinessStopgap available now: Call
setPercentToBeneficiary(10000)on each deployed instance.For proper fix (needed for v6 switchover):
revNetTerminalandrevNetProjectIdfromimmutableto mutable storage variablessetRevNetTerminal(address)andsetRevNetProjectId(uint256)bool public revNetEnabled(defaultfalse) +setRevNetEnabled(bool)setterinitializeHistory()(see above)_splitAndDistributeFunds()to explicitly skip RevNet call whenrevNetEnabled == falseFixedPriceStrategy.sol— new deployment requiredCurrent: routes 100% to RevNet, no beneficiary address. Indexed via subgraph.
Changes:
revNetTerminal/revNetProjectId+ settersaddress public beneficiaryAddress+ setterbool public revNetEnabled(defaultfalse) + setterinitializeHistory()(see above)changeMessage()payment logic: whenrevNetEnabled == false, send fullmsg.valuetobeneficiaryAddress; whenrevNetEnabled == true, call RevNet with full amount (or apply split ratio if added later)CONTRACTSconfig in frontendv1.0 contracts (
Markee.sol,Leaderboard.sol,LeaderboardFactory.sol)Admin access confirmed.
percentToBeneficiaryis mutable — using Option B stopgap, no redeployment needed.Action: Call the percent setter on each deployed
Leaderboardinstance to route 100% to the configured beneficiary. These contracts stay in place and are not replaced as part of this migration.Migration sequence per contract
For each legacy strategy contract being redeployed:
totalFundsRaised,totalMessagesBought,lastMessageOwner,lastMessageinitializeHistory()with values from step 1setPercentToBeneficiary(10000)(interim — 100% to beneficiary)setBeneficiaryAddress(...)to configure the correct addressrevNetEnabledisfalse(should be default)changePricingStrategy()CONTRACTSconfig in frontend0x2dB6d704058E552DeFE415753465df8dF0361846Frontend changes
frontend/lib/contracts/addresses.tsREVNET_CONFIGblock (project IDs 119/63/62/56 per chain,JB_TERMINALaddress)REVNET_V6_CONFIGblock (commented out / empty) as a placeholder — v6 cutover becomes a one-line fill-inCONTRACTSonce new contracts are deployedfrontend/components/modals/BuyMessageModal.tsxPHASESarray (token emission rate schedule, lines 79–99) andcalculateMarkeeTokens()function"62% [beneficiary] / 38% Markee Cooperative"→"100% [beneficiary]"useReadContractcall forbeneficiaryAddressif no longer needed for token calcfrontend/components/modals/TopDawgModal.tsxcalculateMarkeeTokens()and all token emission phase logic (lines 92–104)frontend/lib/contracts/usePartnerMarkees.tsfundingSplitstrings:"62% to [Partner] / 38% to Markee Cooperative"→"100% to [Partner]"percentToBeneficiaryto10000in partner config objectsfrontend/app/how-it-works/page.tsxrevnet.app/v5:base:119/terms)frontend/app/owners/page.tsxrevnet.app/v5:base:119)frontend/components/wallet/TokenBalance.tsxapp.revnet.eth.sucks/v5:base:119/ops)frontend/app/api/openinternet/leaderboards/route.tspercentToBeneficiaryvalues in server-side leaderboard config to10000where applicableSubgraph deprecation
All redeployed contracts use v1.0 architecture and are indexed via direct RPC — no subgraph dependency. Once all legacy strategy contract instances are migrated:
useQueryhooks referencing subgraph data)The subgraph does not need schema updates as part of this migration — it will simply stop receiving new events once legacy contracts are migrated off.
V6 upgrade path
Once v6 RevNet is deployed, the full cutover is admin calls only — no code changes, no redeployment:
setRevNetTerminal(v6TerminalAddress)on each strategy contractsetRevNetProjectId(v6ProjectId)on each strategy contractsetPercentToBeneficiary(splitRatio)to restore the desired split (e.g.6200for 62/38)setRevNetEnabled(true)on each strategy contractREVNET_V6_CONFIGinaddresses.tswith new project IDsLeaderboardcontracts still active at v6 time: read their current state, deploy v6-ready replacement, callinitializeHistory()with current values, migrateOut of scope
Acceptance criteria
0x2dB6d704058E552DeFE415753465df8dF0361846(v5 terminal) from any Markee contractbeneficiaryAddressfor each Markee0xAf4401E765dFf079aB6021BBb8d46E53E27613DBTokenBalance.tsxkept; broken ops link removed or replacedrevNetTerminal/revNetProjectId+revNetEnabledflaginitializeHistory(); history is seeded before going livehistoryInitializedflag prevents re-seeding