Skip to content

feat(campaign): Juicer NFT campaign backend (/v1/campaigns/juicer/*)#269

Open
joshuakrueger-dfx wants to merge 1 commit into
JuiceSwapxyz:developfrom
joshuakrueger-dfx:feat/juicer-campaign-backend
Open

feat(campaign): Juicer NFT campaign backend (/v1/campaigns/juicer/*)#269
joshuakrueger-dfx wants to merge 1 commit into
JuiceSwapxyz:developfrom
joshuakrueger-dfx:feat/juicer-campaign-backend

Conversation

@joshuakrueger-dfx

Copy link
Copy Markdown

What

The missing backend for the Juicer NFT campaign. The frontend page (bapp#741) and JuicerNFT.sol (smart-contracts#56) were both built against /v1/campaigns/juicer/* endpoints that did not exist. This adds them, mirroring the First Squeezer patterns.

Endpoints

  • GET /progress — Ponder-earned JP + JpSpend ledger + social state + on-chain hasClaimed (availableJp = earned - spent)
  • POST /spend — atomic, idempotent 5,000 JP trade ((walletAddress,chainId,reason) unique; P2002-deduped); fails closed if Ponder is down
  • POST /twitter/mark-followed — honor-system X follow (First Squeezer parity)
  • GET /discord/start|callback — Discord OAuth requiring the Juicer role; one Discord per wallet
  • GET /nft/signature — EIP-191 signature for JuicerNFT.claim(); gates on spend + socials + meme-token (verified vs Ponder, fail-closed) + not-already-claimed

Schema

New Prisma models JuicerCampaignUser and JpSpend (authoritative off-chain spend side). DiscordOAuthSession gains a callbackUrl column so a per-flow OAuth redirect survives the token exchange.

Wiring (required before claims work)

  • JUICER_NFT_CONTRACT_MAINNET/_TESTNET and JUICER_SIGNER_PRIVATE_KEY (address MUST equal the deployed contract signer).
  • Register JUICER_DISCORD_CALLBACK_URL as a Discord redirect URI.

Tests

46/46 jest green incl. signature contract-compatibility. tsc clean. Prisma migration included.

Depends on JuiceSwapxyz/ponder#139 for the earn-side data.

🤖 Generated with Claude Code

The Juicer NFT page (bapp#741) and JuicerNFT.sol (smart-contracts#56)
were both built against a campaign backend that didn't exist. This adds
it, mirroring the First Squeezer patterns:

- GET  /progress             — composes Ponder-earned JP, the JpSpend
  ledger, social verification and on-chain hasClaimed into the page's
  read model (availableJp = earned - spent)
- POST /spend                — atomic, idempotent 5,000 JP trade; the
  (walletAddress, chainId, reason) unique constraint dedupes retries
  and concurrent inserts (P2002); fails closed when Ponder is down
- POST /twitter/mark-followed — honor-system X follow (First Squeezer
  parity), on the Juicer campaign record
- GET  /discord/start|callback — Discord OAuth requiring the Juicer
  role; one Discord account per wallet
- GET  /nft/signature        — EIP-191 signature for JuicerNFT.claim();
  gates on spend + socials + meme-token (verified against Ponder,
  fails closed) + not-already-claimed

New Prisma models: JuicerCampaignUser (social state, separate from
OgCampaignUser so campaigns don't bleed) and JpSpend (the authoritative
off-chain spend side of the JP economy).

DiscordOAuthService gains an optional per-flow callback override that
is persisted in the OAuth session, because Discord requires the token
exchange to repeat the authorize-time redirect_uri exactly.

Tests pin the claim-signature scheme to the contract's
keccak256(abi.encodePacked(contract, chainid, wallet)) + EIP-191
recovery, including no-cross-use and case-insensitivity.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
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.

1 participant