Skip to content

Merge back main to dev - #11

Merged
Corantin merged 180 commits into
devfrom
main
Jul 9, 2026
Merged

Merge back main to dev#11
Corantin merged 180 commits into
devfrom
main

Conversation

@Corantin

@Corantin Corantin commented Jul 9, 2026

Copy link
Copy Markdown
Collaborator

No description provided.

pglavin2 and others added 30 commits May 7, 2026 04:32
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- CSP: add vercel.live to script-src to unblock Vercel feedback widget
- views: guard against empty message to stop /api/views 400 errors
- reactions: fix GET endpoint to scan all reactions:v2:* keys when no
  markeeAddress supplied — hook was always getting empty array on load
- detail page: remove Names tab, rename Funding→Funds Added and
  Messages→Message History, merge message+name events into single
  time-sorted combined history without strikethrough old values

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Markee Cooperative, Gardens, Clawchemy, and Superfluid are in the
legacy OI factory's getLeaderboards() list but had no KV meta, so
they were landing in Unverified with status:pending. Hardcode their
verified status, URLs, and logos directly in the OI leaderboards API
so they always appear in the Verified section without a KV write.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
The SF migration leaderboard (0xb6CC...) is a partner leaderboard
migrated from v0.1, not a new Superfluid project. It comes through
the SF API, but the ecosystem aggregator strips status from all SF
entries, so it landed in Unverified. Override that one address to
platform:website + status:verified so it joins Cooperative, Gardens,
and Clawchemy in the Verified section.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- reactions: replace kv.scan (unreliable in @vercel/kv v2) with a
  set-index approach — POST writes sadd reactions:markees, GET reads
  smembers + hgetall per markee; no more 500 on page load
- CSP: add vercel.live to frame-src to stop iframe violation warning
- detail page: wrap history tabs in ModeratedContent so flagged markees
  blur the full message history for non-admin users

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
TokenBalance gated on wagmi isConnected which lags behind Privy's
authenticated state. Adding the Privy authenticated check means the
balance hides as soon as Privy logs out, matching ConnectButton behaviour.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- New /api/eth-price endpoint (CoinGecko + 5-min KV cache)
- useEthPrice hook for client-side price lookups
- formatUsd utility for consistent dollar formatting
- EthBalance component in header showing ETH balance + USD value
- FixedPriceModal: USD-primary price + balance, Fund with card button
- TopDawgModal: USD-primary preset buttons + balance, Fund with card button
- Home page and create-a-markee stats show USD primary with ETH secondary

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Superfluid points:
- Add legacy TopDawg (FundsAddedToMarkee) scan from block 41984295
- Add dedicated SF migration leaderboard scan with own KV block key
- Give each address family its own last-processed block so late additions
  don't silently skip historical events
- uniqueId (txHash-logIndex) deduplication makes all scans safe to re-run

GitHub delimiter grandfathering:
- Add GITHUB_NEW_TO_OLD map in linkedFiles.ts covering all 11 v1.0→v1.1
  GitHub leaderboard migrations from markee-migrations.csv
- verify-markee-file and register-markee now accept old-address delimiters
  as verified when the new-address delimiter is absent
- update-markee-file uses migrate-on-write: if only old delimiters are
  found it replaces them with the new-address markee block on next update

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
The hardcoded v1.0→v1.1 map doesn't cover files from before the v1.0
migration (e.g. delimiter address 0x56c5f6ce... in honeyswap-interface).

verify-markee-file and register-markee now accept any
<!-- MARKEE:START:0x{40} --> / <!-- MARKEE:END:0x{40} --> pair as valid,
regardless of which address is embedded. Push access to the repo was
already verified on registration so this doesn't weaken the security model.

update-markee-file gains a matching last-resort regex pass: it finds any
address-shaped delimiter pair and replaces the block with the current
leaderboard's content + new-address delimiters (migrate-on-write).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Partners with leaderboardAddress=null (Bread Cooperative, RevNets, Juicebox,
Giveth, Flow State) had their old TopDawg strategy addresses fall through as
strategyAddress in TopDawgModal. Those old strategies route to RevNet v5
(project 119, compromised terminal) so any purchase would send a portion of
ETH there.

Changes:
- Hide "Buy a Message" and "Be the First!" buttons when leaderboardAddress is null,
  showing "Purchases coming soon" instead
- Remove the strategyAddress fallback to partner.strategyAddress in TopDawgModal
  (now passes undefined when no leaderboard, preventing any purchase call)

Completing migration for these 5 partners requires deploying new v1.1 leaderboards
via the OpenInternet factory and setting leaderboardAddress in PARTNERS.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
The Superfluid factory deployed early leaderboards from a v1.0
Leaderboard implementation that hardcodes the deprecated RevNet v5
terminal (0x2dB6d704...) — routing 38% of every purchase to it.

Read VERSION() on each individual leaderboard page and gate all
purchase buttons behind isLegacyContract check (VERSION !== '1.1.0').
Legacy leaderboards show a warning banner instead of Buy a Message.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…rboards

Deploys a new v1.1 Superfluid LeaderboardFactory and migrates all 96 existing
v1.0 leaderboards from the old factory (0x45Ce642d...). Per-leaderboard: creates
v1.1 clone via new factory, removes automation seed, runs migrateFromLegacy() for
each markee, transfers admin back to original creator. Resolves original admins
from old factory's LeaderboardCreated event logs rather than admin() (which returns
zero on the deployed pre-v1.0 bytecode). Outputs JSON and CSV rows for follow-up
frontend updates.

Fund automation wallet 0x000D356bB4D1E81CAb7cFA47D8707cdBDfEDE5af with ~0.05 ETH
before running live. After running, update FACTORIES.SUPERFLUID and the two API
routes per the script's NEXT STEPS output.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Deploys new v1.1 LeaderboardFactory for Superfluid
(0x1E1b0C22e2C6C7b46ABb0F25231c7eecD4f0A2d8) and migrates all 96
v1.0 leaderboards to v1.1 counterparts with correct RevNet routing.

- Update SUPERFLUID_FACTORY_ADDRESS and FACTORY_DEPLOY_BLOCK across
  leaderboards API and points cron to point at new factory
- Add old→new redirect map in [address]/page.tsx so legacy URLs
  transparently redirect to v1.1 leaderboard addresses
- Update addresses.ts and CLAUDE.md with new factory address
- Record new factory address in migrations CSV

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Replaces hardcoded MIGRATIONS array with auto-loading from
/tmp/migration-sf-factory-results.json so all 149 markee address
pairs from the v1.0→v1.1 factory migration are migrated in one run.
Switches SET → INCRBY so re-runs accumulate safely.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…Superfluid markee addresses

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…views route

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…tion, and health endpoint

- Add topMarkeeAddress to API fields docs
- Add view tracking section: proxy route + useRef-gated POST on first display
- Add moderation section: proxy route + flagged Set + per-tab filtering behavior
- Add optional health endpoint route for integration dashboard verification
- Update platform/moderation banner copy (no longer mentions @vercel/kv)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…factory

SUPERFLUID_FACTORY_ADDRESS now points to FACTORIES.SUPERFLUID (v1.1,
0x1E1b0C22e2C6C7b46ABb0F25231c7eecD4f0A2d8) instead of the old
0x45Ce642d factory. Both the writeContract call and the receipt log
parser use the same constant, so both are fixed.

GitHub and create-a-markee factories were already correct.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- FACTORIES.GITHUB: 0xb1E2dC95b50b4Ee3b1BD4F4F87e6B07b41bE4e07 was wrong,
  corrected to 0xb1E2dC9582810124Fed3Cdb4B8Bb944A5495D85a (matches
  GitHub platform page and user-confirmed address)
- Fixed-price CSV rows: old addresses moved to v10_address column,
  current addresses.ts values added to v11_address column

Note: Superfluid factory row (line 4) was already correct in the CSV.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- addresses.ts: populate REVNET_V6_CONFIG (terminal 0x27da30..., project 152);
  correct OI factory labels (0x3f9f7C = v1.0, 0xb9922E = v1.1)
- abis.ts: expand LeaderboardV11ABI with full admin write surface
  (setRevNetTerminal, setRevNetProjectId, setPercentToBeneficiary,
  setRevNetEnabled, setPlatformFeeReceiver, setBeneficiaryAddress, setAdmin)
  and routing view getters
- scripts/enable-revnet.mjs: generates Safe Transaction Builder batch JSONs
  for all 20 v1.1 leaderboards — Coop multisig batch (4) and Paul's wallet
  batch (16 OI + GitHub). Run: node scripts/enable-revnet.mjs
- MIGRATION.md: fix factory version labels, fill in real terminal/projectId
  in checklist, mark completed Superfluid work, scope v1.0 exclusions

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
… enabled

BuyMessageModal and TopDawgModal now conditionally show a MARKEE token count
card when revNetEnabled is true on the leaderboard contract. Revenue split
updates from 100% to 62%/38% (beneficiary / Markee Cooperative) accordingly.
Token count uses the live phase schedule (50k/ETH through 2026-06-21).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
The initial enable-revnet batch used the wrong JB terminal address.
The correct terminal for RevNet project 152 on Base is 0x2dB6d704...
(confirmed from a successful on-chain pay tx to the same project).

Coop multisig needs to run the fix-terminal-coop.json Safe batch to
correct the 4 already-enabled leaderboards. Paul's 16 leaderboards
have not yet been enabled so they are unaffected.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Multicall3 from EOA doesn't preserve msg.sender, so onlyAdmin calls
would revert. This script sends 64 sequential cast send transactions
(4 per leaderboard) directly from the EOA private key instead.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
ACCOUNT=revnet-admin is now preferred over raw PRIVATE_KEY.
Create keystore once with: cast wallet import revnet-admin --interactive

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Adds 3s sleep between cast send calls to avoid replacement-tx-underpriced
errors. Adds revNetEnabled check before each leaderboard so re-runs are
safe and idempotent.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
v1.2 fixes a governance hole where individual leaderboard admins could
redirect the 38% RevNet cut to any project. Terminal and project ID are
now stored on the factory (Coop-only setter) and read by all leaderboards
at pay-time — one tx updates every leaderboard simultaneously.

- New contracts: Leaderboard v1.2, LeaderboardFactory v1.2, Markee v1.2
- 3 factories deployed (OI, GitHub, Superfluid) with RevNet v6 baked in
- 21 leaderboards migrated, 107 Markees cloned, 4 handed to Coop multisig
- All frontend address references updated to v1.2

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
pglavin2 added 23 commits June 16, 2026 18:02
After fetching GitHub repo traffic, the traffic route reads the top markee
slot address from chain and writes max(current, githubCount) to
views:total:{topSlotAddr} in KV. The syncedViews count is returned to the
client which updates the local viewsMap so Total Views reflects it immediately.
JB terminal: 0x2dB6d704...0361846 → 0x130f5Dd2...7f53
MARKEE token: 0xee3027f1...b38 → 0xF6627cF1...42F

Updated across all 9 files: addresses.ts, MIGRATION.md, ARCHITECTURE.md,
contracts/README.md, LeaderboardFactory.sol (natspec), enable-revnet.mjs,
enable-revnet-eoa.sh, deploy-v13-factories.sh, how-it-works/page.tsx (Gardens URL).

// Fetch all platform leaderboards
const [sfData, ghData, oiData] = await Promise.all([
fetch(`${origin}/api/superfluid/leaderboards`).then(r => r.ok ? r.json() : null).catch(() => null),
// Fetch all platform leaderboards
const [sfData, ghData, oiData] = await Promise.all([
fetch(`${origin}/api/superfluid/leaderboards`).then(r => r.ok ? r.json() : null).catch(() => null),
fetch(`${origin}/api/github/leaderboards`).then(r => r.ok ? r.json() : null).catch(() => null),
const [sfData, ghData, oiData] = await Promise.all([
fetch(`${origin}/api/superfluid/leaderboards`).then(r => r.ok ? r.json() : null).catch(() => null),
fetch(`${origin}/api/github/leaderboards`).then(r => r.ok ? r.json() : null).catch(() => null),
fetch(`${origin}/api/openinternet/leaderboards`).then(r => r.ok ? r.json() : null).catch(() => null),

// Fetch all platform leaderboards (uses their cached responses where available)
const [sfData, ghData, oiData] = await Promise.all([
fetch(`${origin}/api/superfluid/leaderboards`).then(r => r.ok ? r.json() : null).catch(() => null),
// Fetch all platform leaderboards (uses their cached responses where available)
const [sfData, ghData, oiData] = await Promise.all([
fetch(`${origin}/api/superfluid/leaderboards`).then(r => r.ok ? r.json() : null).catch(() => null),
fetch(`${origin}/api/github/leaderboards`).then(r => r.ok ? r.json() : null).catch(() => null),
const [sfData, ghData, oiData] = await Promise.all([
fetch(`${origin}/api/superfluid/leaderboards`).then(r => r.ok ? r.json() : null).catch(() => null),
fetch(`${origin}/api/github/leaderboards`).then(r => r.ok ? r.json() : null).catch(() => null),
fetch(`${origin}/api/openinternet/leaderboards`).then(r => r.ok ? r.json() : null).catch(() => null),
@vercel

vercel Bot commented Jul 9, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
markee Ready Ready Preview, 💬 5 unresolved
✅ 1 resolved
Jul 9, 2026 7:58pm

@Corantin
Corantin merged commit e4b38a7 into dev Jul 9, 2026
1 of 2 checks passed

@gaston-review gaston-review Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Gaston Review

Verdict: Comment

Score: ██████░░░░ 6/10

Pull Request Summary

This PR merges main back into dev, bringing in ~22,000 lines across 100+ files: new v1.2 and v1.3 smart contracts (Leaderboard, LeaderboardFactory, Markee), numerous admin KV migration endpoints, a major rewrite of the home/marketplace/owners/create-a-markee pages, new ETH price and embed APIs, network-wide view tracking, and factory address updates.

Review Summary

🟡 Warning — Markee.sol: ETH permanently locked when beneficiary is address(0)
In Markee.pay(), when beneficiary == address(0), the code sets beneficiaryAmount = msg.value but then skips the transfer (guard: beneficiary != address(0)). Meanwhile revNetAmount stays 0, so _routeRevNet is a no-op. The ETH sits in the contract with no withdraw function and no receive()/fallback(). setBeneficiaryAddress() allows setting to address(0) with no validation, so this state is reachable. Even if it requires an admin mistake, in a financial contract this is a footgun worth closing.

🟡 Warning — Markee.sol: tx.origin in event emissions is unreliable
setMessage() and setName() emit events with tx.origin instead of msg.sender. tx.origin can differ from the actual caller in meta-transaction, account-abstraction, and multi-hop contract call scenarios. This produces misleading event data. The Leaderboard contract already knows the actual caller (msg.sender in updateMessage/updateName) — the Markee should log that instead.

🟡 Warning — Reactions bulk-fetch endpoint has no pagination limit
GET /api/reactions with no params loads ALL markee addresses from KV (smembers), then fires a parallel hgetall for each one. As the dataset grows, this will hit memory and timeout limits. Should add pagination or a cap on the number of markees loaded.

The architecture direction is solid — centralizing RevNet config at the factory level is a good move. But the Markee.sol payment logic has a latent fund-locking bug when beneficiary is address(0), and the use of tx.origin in events is a code smell that should be cleaned up. The frontend rewrites are massive but largely cosmetic. For a merge-back PR of this size, the real risk is in the contracts — those deserve focused scrutiny before deployment.

📌 6 inline comments
🟡 Warning: 3 · 🔵 Suggestion: 3


🔍 Reviewed by Gaston

Comment thread contracts/v1.3/Markee.sol
uint256 revNetAmount;

if (!strategy.revNetEnabled() || beneficiary == address(0)) {
beneficiaryAmount = msg.value;

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Warning: When beneficiary == address(0) and revNetEnabled is true, the entire msg.value is assigned to beneficiaryAmount but the transfer is skipped (line 108 guard), and revNetAmount remains 0. ETH is permanently locked. Consider: (1) require beneficiary != address(0) in pay(), or (2) route 100% to RevNet when beneficiary is unset, or (3) add a zero-address check in setBeneficiaryAddress().

Comment thread contracts/v1.3/Markee.sol
function setMessage(string calldata _message) external {
require(msg.sender == pricingStrategy, "Only pricing strategy can set message");
message = _message;
emit MessageChanged(_message, tx.origin);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Warning: tx.origin is used here and in setName() for event emission. This is unreliable under account abstraction, meta-transactions, and EIP-3074. Since the Leaderboard's updateMessage() already validates msg.sender == markee.owner(), pass the caller as a parameter to setMessage() instead of relying on tx.origin.

/// migrateFromLegacy() which also creates a fresh v1.3 clone.
function initializeHistory(address[] calldata _markees) external onlyAdmin {
require(!historyInitialized, "History already initialized");
historyInitialized = true;

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔵 Suggestion: If offset >= markees.length, end - offset underflows (Solidity 0.8 reverts with a generic panic). Add if (offset >= markees.length) return new address[](0); for a cleaner caller experience. Same issue in LeaderboardFactory.getLeaderboards().

}

function percentToBeneficiary() external view override returns (uint256) {
return ILeaderboardFactory(factory).percentToBeneficiary();

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔵 Suggestion: setBeneficiaryAddress allows setting to address(0) without validation, which would trigger the fund-locking scenario in Markee.pay(). Consider adding require(_newBeneficiary != address(0)) to match the pattern used for setAdmin.

return NextResponse.json({ reactions })
}

// No markeeAddress — load all reactions via the markee index set

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Warning: This bulk-fetches ALL reactions across ALL markees in parallel with no pagination. As the dataset grows, smembers returns unbounded results and Promise.all(hgetall...) fires unbounded concurrent KV calls. This will eventually hit Vercel function timeouts or memory limits. Consider adding a limit parameter or paginating the index.

return NextResponse.redirect(`${fallbackBase}?error=invalid_state`)
}

let returnTo = ''

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔵 Suggestion: The origin stored in KV comes from new URL(request.url).origin in the connect route. In standard Vercel/Next.js deployments this reflects the server's own origin, so it's safe. But if the app ever sits behind a reverse proxy that rewrites Host headers, this becomes an open redirect. Consider validating siteOrigin against an allowlist of known deployment origins before redirecting.

@gaston-review gaston-review Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Gaston Review

Verdict: Comment

Score: ██████░░░░ 6/10

Pull Request Summary

This is a massive merge-back from main to dev, pulling in cumulative production changes: new v1.2 and v1.3 contract versions (Leaderboard, LeaderboardFactory, Markee) with factory-level RevNet routing, all frontend and API routes migrated to v1.3 addresses, a Superfluid Season 6 system with boosted 5x multipliers, gamed-leaderboard filtering, multiple one-shot admin migration endpoints, USD price computation, embed widget rewrite, and extensive UI overhauls across the account, ecosystem, marketplace, and create pages.

Review Summary

🟡 Warning — ETH locked when beneficiary is address(0) and revNet is enabled
In Markee v1.2 and v1.3 pay(), when revNetEnabled == true and beneficiaryAddress == address(0), the condition !revNetEnabled || beneficiary == address(0) is true, so beneficiaryAmount = msg.value and revNetAmount = 0. Then the beneficiary transfer is skipped (guard: beneficiary != address(0)), and _routeRevNet receives 0 and does nothing. The ETH is stuck in the Markee clone with no receive(), fallback(), or withdrawal function. The v1.3 factory doesn't enforce non-zero _beneficiaryAddress in createLeaderboard(), and setBeneficiaryAddress() also has no zero-check. Admin-only blast radius, but still a path to permanent fund loss.

🟡 Warning — Duplicated GAMED_ADDRESSES set across two API routes
The same 13-address gamed-leaderboard blocklist is hardcoded identically in ecosystem/leaderboards/route.ts and superfluid/leaderboards/route.ts. When a new gamed address is discovered, both files must be updated in lockstep. This will drift — extract to a shared constant.

🟡 Warning — Track endpoint trusts client-supplied leaderboardAddress for boost multiplier
POST /api/superfluid/track now applies a 5x points multiplier based on the leaderboardAddress field in the request body. This value is entirely client-supplied and not verified on-chain. An attacker can submit any boosted leaderboard address alongside their real txHash to inflate their points by 5x. The cron job has the same multiplier logic but derives the leaderboard address from on-chain event logs, making it accurate.

🔵 Suggestion — tx.origin used in Markee events
Both v1.2 and v1.3 Markee contracts emit MessageChanged and NameChanged events with tx.origin as the changedBy parameter. While not a security issue (it's only in events, not access control), it can be unreliable with account abstraction and meta-transactions. Consider using a parameter passed from the Leaderboard instead.

The contracts are well-structured with proper access controls, and the migration from per-leaderboard to factory-level RevNet config is a sound design. I see one edge-case fund-locking path in the Markee payment logic, duplicated gamed-address lists that will drift, and the track endpoint accepting unverified boosted multiplier claims. Since this is a merge of already-deployed code, these are worth tracking rather than blocking.

📌 5 inline comments
🟡 Warning: 3 · 🔵 Suggestion: 2


🔍 Reviewed by Gaston

Comment thread contracts/v1.3/Markee.sol
uint256 beneficiaryAmount;
uint256 revNetAmount;

if (!strategy.revNetEnabled() || beneficiary == address(0)) {

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Warning: When revNetEnabled == true and beneficiaryAddress == address(0), this condition routes the full msg.value to beneficiaryAmount but then skips the beneficiary transfer (line 108 guard). revNetAmount stays 0, so _routeRevNet is a no-op. The ETH is permanently stuck — this contract has no receive(), fallback(), or rescue function. Consider: (1) requiring non-zero beneficiary in createLeaderboard and setBeneficiaryAddress, or (2) when beneficiary is zero and revNet is enabled, route everything to RevNet instead.

// ─── Markee registry ──────────────────────────────────────────────────────

address[] public markees;
mapping(address => uint256) private markeeIndex;

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔵 Suggestion: If offset >= markees.length, then end gets clamped to markees.length, and end - offset underflows (Solidity 0.8 will revert). Safe but confusing — add if (offset >= markees.length) return new address[](0); before the subtraction.

const points = ethToPoints(amountWei)
let points = ethToPoints(amountWei)

// Apply 5x multiplier if this leaderboard is boosted

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Warning: The leaderboardAddress is taken directly from the client request body and used to determine the 5x boosted multiplier. Nothing stops a caller from submitting a boosted address that doesn't match their actual transaction. The cron job correctly reads the address from on-chain event logs. Consider either verifying the leaderboard address on-chain against the txHash, or removing the boost from this endpoint and only applying it via the cron.

// Excluded from totalPlatformFunds — leaderboards with clearly recycled/gamed ETH
const GAMED_ADDRESSES = new Set([
'0x1b4eb52953d865e0dde1c856c2ead826581e2904',
'0xb5451c1cb790367d0ddbcbf1249de22b9014ecdc',

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Warning: This gamed-addresses set is duplicated verbatim in superfluid/leaderboards/route.ts. Extract to a shared module (e.g. lib/gamed-addresses.ts) to avoid the two lists drifting when new entries are added.

event RevNetEnabledChanged(bool enabled);
event PlatformFeeReceiverChanged(address indexed oldReceiver, address indexed newReceiver);
event PercentToPlatformFeeReceiverChanged(uint256 oldPercent, uint256 newPercent);
event HistoryInitialized(uint256 markeeCount);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔵 Suggestion: Same getMarkees offset-underflow edge case as v1.3: when offset > markees.length, the array allocation underflows and reverts. Add an early return for offset >= markees.length.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants