Heylogram TV Ecosystem
A Web3 economy built on Polygon with 6 tokens, 16 smart contracts and a B2B platform. Domain NFTs carry identity, the Garura seasonal pool carries rewards, the URA click game carries scale, and the partner system carries discovery.
01Vision¶
Heylogram TV answers two questions: "How does digital identity become individual?" and "How does a Web3 economy work with role-based multiple tokens instead of a single token?"
The answer to the first question is the .heylogramtv domain NFT — an identity layer that binds a name to a wallet, like ENS but on Polygon, with a marketplace and openable profile pages.
The answer to the second question is a six-token economy:
- VOI — task/reward token, mintable with cap
- HEY — streaming platform token, mintable with cap
- JVOI — utility token, fixed supply, ownerless (immutable)
- HLTV — Heylogram TV streaming token, fixed supply, ownerless
- URA — "Garura's Leaf", earned by clicking, 98.7Q supply cap
- GARURA — 1 unit in the world, goes to the last clicker
1.1 Why Polygon?¶
A user might complete dozens of tasks weekly, click partner links, and click 8,640 times a day for URA. Ethereum mainnet would be prohibitively expensive for this scale. Polygon brings gas costs down to a level where daily micro-behavior is economical, while remaining fully compatible with QuickSwap, Uniswap V3, OpenSea and similar mature infrastructure.
1.2 Phased Strategy¶
Deploying 17 contracts at once does nothing but exhaust the ecosystem. Heylogram TV launches functional components as independent families:
- Phase 1 (live): Domain NFT + Marketplace + VOI/HEY mint + Tasks + Partner
- Phase 2 (live): JVOI + HLTV fixed-supply tokens
- Phase 3 (live): URA click game + GARURA grand prize
- Phase 4 (shelved): Garura Pool V2 — season system halted on 2026-05-16, contract is still live but no longer called from the frontend
- Phase 5 (live): LuckPool V2 (LP NFT random rewards) + custom DEX aggregator (Uniswap V3 + Sushi V3 + QuickSwap V3)
- Phase 6 (pending): Liquidity Mining + Badge ERC-1155
02Design Principles¶
2.1 V1 → V2 Rewrites¶
Heylogram TV is a protocol already deployed to mainnet — so every bug is met with a new contract version. Summary:
| Contract | V1 Problem | V2 Solution |
|---|---|---|
GaruraPool | No point/VOI ratio, partner could withdraw at will, contract didn't know about seasons | Fixed 1 VOI = 1000 points; 15% platform / 85% pool auto-split; partner can only withdraw unused |
LuckPool | ILocker.getLock wrongly defined as 6-return → every register reverted | Correct struct-return ABI + LP NFT content validation (ecosystem token requirement) + emergencyWithdraw |
HeylogramDomain | Fixed price (every domain 6 POL) | Tier pricing: 1 letter $10, 2=$5, 3=$2.5, 4=$1.3, 5+=$0.5 |
HeylogramMarketplace | Single contract support | Parallel old + new domain contract support (_nftKontrat auto-detect) |
2.2 Multi-Token Philosophy¶
In single-token economies, role confusion emerges: "Is this token for payment, reward or governance?" Heylogram TV assigns each role to a separate token:
- VOI — task/activity reward, seasonal pool fuel
- HEY — streaming economy, NFT gift payment
- JVOI — utility (domain purchase, garura participation)
- HLTV — Heylogram.tv streaming platform
- URA — gamification (1 URA per click)
- GARURA — legendary (1 in the world)
2.3 Two Token Types: Mintable vs Fixed¶
Heylogram TV uses two different token architectures:
- Mintable + Pausable + Ownable — VOI, HEY, URA. Owner can mint, can pause. Flexible but centralized. Solution: transfer ownership to MintTimelock → mandatory 48h queue.
- Fixed + Ownerless + Immutable — JVOI, HLTV. All supply minted to deployer at construction; no admin intervention afterwards. CMC's "mintable" warning is historically meaningless for these tokens.
- Single Unit — GARURA. Only 1 unit, sent to vault (URA click game contract) at deployment.
2.4 Domain-Bound Partnership¶
The Web3 identity problem: how does a wallet prove who it is? Heylogram TV's answer: .heylogramtv domain NFT.
Becoming a partner requires owning at least one .heylogramtv domain NFT. The moment the domain is sold, partner status is automatically revoked — contracts check ownerOf(tokenId) == partneron every call. This keeps identity and the partner pool bound through the NFT.
2.5 Off-Chain Computation, On-Chain Money¶
User points, profile data, partner task records, and blog content live in Firestore. Contracts only hold tokens, swap, mint. This hybrid architecture keeps gas costs economical; but data consistency falls to Firestore (see §18 Audit).
2.6 Two-Step Admin Transfer¶
Modern contracts (GaruraPool V2, LiquidityMining, MintTimelock) use the proposeAdmin + acceptAdmin dual flow. Single-step transfer (no recovery if old admin sets a wrong address) is prevented.
03Architecture Overview¶
3.1 Contract Families (V2-focused)¶
| Family | Contracts | What it does |
|---|---|---|
| Token | VOIToken, HEYToken, JJVOIToken, HLTVToken, URAToken, GaruraToken | 6 separate ERC-20s, role-based |
| Identity | HeylogramDomain (v2), HeylogramMarketplace (v2) | .heylogramtv domain NFT + trading |
| Reward Pool | GaruraPool v2 shelved, LuckPool v2 active | LuckPool luck-based distribution (Garura season system halted on 2026-05-16) |
| Liquidity | LiquidityMining pending | Uniswap V3 LP lock + JVOI reward |
| Governance | MintTimelock pending | 48h mint queue (for CMC warning) |
| B2B | HeylagramGaruraBadge pending | ERC-1155 milestone badges (partner-VOI flow in GaruraPool V2) |
3.2 Data Flow: A User's Day¶
3.3 Money Flow¶
3.4 CMC Token Listing Endpoints¶
CoinMarketCap requires three endpoints for listing a token:total-supply, circulating-supply, andmax-supply. Each returns a plain number (not JSON). Heylogram TV provides these separately for VOI and HEY:
| Endpoint | Token | Response | Source |
|---|---|---|---|
/api/voi/total-supply | VOI | plain text uint | on-chain totalSupply() |
/api/voi/circulating-supply | VOI | plain text uint | totalSupply() − admin wallet − burn |
/api/voi/max-supply | VOI | plain text uint | constant: 100,000,000 |
/api/hey/total-supply | HEY | plain text uint | on-chain |
/api/hey/circulating-supply | HEY | plain text uint | on-chain − admin |
/api/hey/max-supply | HEY | plain text uint | constant: 2,000,000,000 |
JVOI, HLTV, GARURA, URA don't have these endpointsbecause they're either not yet CMC-listed or have fixed supply. Added when needed with the same pattern.
3.5 Firestore Data Model¶
Data that's gas-expensive to store on-chain (points, seasons, profiles, partner records) lives in Firestore. Server SDK admin access via the FIREBASE_SERVICE_ACCOUNT_JSON env var on Vercel.
kullanicilar/{wallet} // wallet lowercase address
puan: number // total earned points
epochPuan: number // points not yet committed to season
tamamlananGorevler: string[] // completed mission ids
referans: string? // referring wallet (optional)
nickname: string? // for URA
createdAt, lastSeen: Timestamp
seasons/{seasonId}
token: 'VOI' | 'HEY'
amount: number // tokens in pool
startTime, endTime: number // ms timestamp
status: 'active' | 'scheduled' | 'ended' | 'distributed'
totalCommitted: number // total committed points
totalEarned: number // points earned during season
participantCount: number
garuraRegistrations/{docId}
wallet: string
seasonId: string
committedPoints: number
earnedDuringSeason: number
claimed: boolean
claimedAmount: number
domainProfiles/{name} // .heylogramtv name (no extension)
displayName, bio, avatarUrl, coverColor: string
links: { twitter, github, telegram, discord, website }
customText: string
partners/{domainName} // doc ID = domain name (NOT wallet)
wallet: string
tokenId: number
activated: boolean // is domain still owned?
logoUrl: string
website: string
webhookSecret: string // for HMAC (W12: plaintext)
activatedAt: Timestamp
partnerGorevler/{taskId}
partnerDomain: string
name, description, url: string
rewardPoints: number // max 500
totalBudget: number
spent: number
status: 'pending' | 'active' | 'ended'
createdAt: Timestamp
missions/{missionId} // platform tasks (admin-managed)
title, description: string
type: 'on-chain' | 'profile' | 'social' | 'partner'
points: number
repeat: 'once' | 'daily' | 'weekly'
active: boolean
uraClicks/global // single doc, global counter
total: number // total clicks
lastTick: Timestamp
milestone8B: { wallet, txHash, timestamp } | null
uraNicknames/{wallet}
nickname: string
setAt: Timestamp
uraLeaderboard/{wallet} // cache, refreshed periodically
wallet, nickname, totalUra, rank, lastUpdate
votes/{threadId_walletAddr} // forum/voting spam protection
threadId, voter, createdAt
blogPosts/{slug} // doc ID = URL slug
title_tr, title_en: string
slogan_tr, slogan_en: string // subtitle (optional)
excerpt_tr, excerpt_en: string // summary for card
body_tr, body_en: string // markdown
tag: string // 'announcement' | 'release' | 'partner' | ...
coverImage: string // optional
status: 'draft' | 'published'
publishedAt: number // ms timestamp (0 = draft)
createdAt, updatedAt: number3.6 Frontend Route Map¶
Public routes under app/ (hidden admin pages excluded):
| Route | Function | Wallet? |
|---|---|---|
/ | Landing — token grid, recent sales, CTA | No |
/about | About the project, team, license | No |
/advertise | Shelved (2026-05-16) — former partner B2B promo page | — |
/blog | Ecosystem announcement list (server-rendered, 30s cache) | No |
/blog/[slug] | Single post, TR/EN content, share buttons, dynamic OG | No |
/dapp/dex | Custom-built DEX aggregator — 3 venues (Uniswap V3 + Sushi V3 + QuickSwap V3), swap + pool + locker + luck tabs | Yes |
/dapp/domain | Domain mint UI — name search, tier pricing, pay POL/VOI/HEY | Yes |
/dapp/ura | URA click game — start + wallet connect | Yes |
/garura | Shelved (2026-05-16) — former Garura season register + claim | — |
/gorevler | Shelved (2026-05-16) — former tasks list | — |
/hesap | User account page — domains, list/cancel, balances | Yes |
/kesfet | Discovery — newly minted domains, popular profiles | No |
/locker | LP NFT lock UI (Locker V1) | Yes |
/luck | LuckPool register + claim UI | Yes |
/partner | Shelved (2026-05-16) — former partner panel | — |
/pazar | Marketplace — listing, filter, buy (POL/VOI/HEY) | Yes (for buying) |
/resolve/[name] | Domain profile page (Linktree-style) | No (owner edits) |
/s/[slug] | Promo short link — share pages | No |
/tip/[name] | Send tip to streamer page | Yes (to send) |
/token | VOI/HEY/JVOI/HLTV/URA info page (supply, price, links) | No |
/tr | Turkish main landing (i18n) | No |
/ura | URA PWA — tree animation + click + counter (mobile-first) | Yes |
/ura/leaderboard | URA click ranking | No |
/whitepaper | Poetic main whitepaper (Gaia's tree, TR/EN) | No |
/whitepaper/technical | Old technical whitepaper (dark theme) | No |
/whitepaper/ura | URA detailed user guide | No |
/whitepaper/docs | Technical documentation (Turkish) | No |
/whitepaper/docs/en | This page — technical documentation (English) | No |
3.7 Frontend Behavioral Rules¶
Conventions applied protocol-wide for wallet interactions:
forcePolygon() Pattern
Before every writeContract call, the user must be guaranteed to be on the Polygon network. wagmi'sswitchChain can slip to Ethereum on some wallets; instead, raw provider call is used:
const forcePolygon = async () => {
await window.ethereum.request({
method: 'wallet_switchEthereumChain',
params: [{ chainId: '0x89' }], // 137 = Polygon
});
};
// Before every writeContract:
await forcePolygon();
await writeContract({ ... });Signature + Timestamp Standard
All server-side write operations require EIP-191 signature + 5 minute TTL timestamp. Prevents replay attacks (K06 audit).
Frontend:
const ts = Math.floor(Date.now() / 1000);
const msg = "action:<param>:<ts>";
const sig = await viem.signMessage({ account, message: msg });
fetch('/api/...', { body: { ...params, timestamp: ts, signature: sig } });
Server:
if (now - ts > 5*60) return 401; // 5min TTL
const recovered = ecrecover(msg, sig);
if (recovered !== wallet) return 401;Wallet Address Normalization
All wallets in Firestore are stored lowercase. APIs applywallet.toLowerCase(). The frontend address fromuseAccount may be checksummed — normalized before comparison.
RPC Selection
- ✅
https://polygon.drpc.org - ✅
https://1rpc.io/matic - ❌
polygon-rpc.com— broken, don't use - ❌
rpc.ankr.com/polygon— broken, don't use
RainbowKit Wallet Order
injectedWallet must be first — so MetaMask opens directly. Otherwise RainbowKit shows a selection screen and UX degrades.
3.8 DEX Aggregator (/dapp/dex)¶
Heylogram TV has a custom-built multi-venue DEX aggregator (lib/dexTokens.ts + lib/dexAdapters.ts). A single frontend queries three Polygon DEXs in parallel, picks the best price, and performs swap or LP positions accordingly. The /dapp/dex page has four tabs:Swap, Pool,Locker, Luck.
Supported Venues
| Venue | Protocol | Quoter | Router | Position Manager |
|---|---|---|---|---|
| Uniswap V3 | univ3 (4 fee tiers) | 0x61fF…B21e | 0x68b3…Fc45 | 0xC364…FE88 |
| Sushi V3 | univ3 (fork, same ABI) | 0x64e8…08A7 | 0x0aF8…44c3 | 0xB740…a40 |
| QuickSwap V3 | algebra (dynamic fee) | 0xa15F…FC89 | 0xf5b5…8E12 | 0x8eF8…1de6 |
Uniswap V3 and Sushi V3 share the same ABI (4 fixed fee tiers:100 / 500 / 3000 / 10000). QuickSwap V3 uses Algebra Integral — no fee parameter, the pool computes fee dynamically.
Token Catalog
- Stable + Native: POL (native MATIC, wrap: WMATIC), USDC (6 dec), USDT (6 dec)
- Ecosystem: URA, HEY, VOI, JVOI, HLTV, GARURA (all 18 dec)
- Total 9 tokens;
POLYGON_TOKENSconstant object
URA / Stable Liquidity Map
URA/USDC → Sushi V3 0.3% (main pool)
URA/POL → Uniswap V3 1%
HEY/POL → Uniswap V3 0.3%
VOI/POL → Uniswap V3 0.3%
JVOI/POL → Uniswap V3 0.3%
HLTV/POL → Uniswap V3 0.3%
USDC/USDT → Stable 0.01%Aggregator Flow
User: 1 URA → ? USDC
│
▼
quoteBestVenue(tokenIn=URA, tokenOut=USDC, amountIn=1e18)
│
├─→ quoteFromVenue(uniswap) → try all fee tiers + pool-based fallback
├─→ quoteFromVenue(sushi) → pool-based (Sushi's Quoter is broken)
└─→ quoteFromVenue(quickswap) → Algebra quoter (dynamic fee)
│
▼ Promise.all parallel
│
▼
Compare results → venue with max amountOut wins
│
▼
buildSwapTx(venue, ...) → router-specific tx (univ3 vs algebra ABI)
│
▼
walletClient.writeContract(...)Pool-Based Fallback Quote
When the Quoter contract isn't working (like Sushi V3) or certified, quoteFromPoolBased kicks in: get the pool address from factory, read slot0.sqrtPriceX96, compute via a simple price formula:
price (token1/token0) = (sqrtPriceX96)² / 2²⁰⁰
amountOut (token0 → token1) = amountIn × price
amountOut (token1 → token0) = amountIn / price
amountOut -= amountOut × fee / 1_000_000 // 0.3% = 3000This fallback is accurate for small swaps; for large swaps it doesn't account for slippage — just an estimate. For real swaps the router is still called with amountOutMinimum(user sets slippage tolerance).
LP Mint (Pool Tab)
buildMintTx creates a new LP position. Uniswap V3 / Sushi V3 require a fee parameter; Algebra doesn't. For full-range positions, ticks are computed viafullRangeTicks(spacing):
spacing fee
1 100 (stable)
10 500
60 3000 (common)
200 10000 (volatile)
60 algebra (Quickswap, default)
tickLower = ceil(-887272 / spacing) × spacing
tickUpper = floor(887272 / spacing) × spacingCross-DEX Reference Price
When opening a new pool (token listed on one DEX but not another), findReferencePrice searches all venues in parallel; returns the sqrtPriceX96 of the first pool it finds along with the real display price (accounting for token0 + token1 decimals). Used to set the correct starting price when creating a new pool.
Locker + Luck Tabs
- Locker:
LockerPanelcomponent (29KB) — user locks their LP NFT into the Locker V1 contract (0x3df6…CA98). Duration selection, lock confirmation, existing locks list. - Luck:
LuckPanelcomponent (11.5KB) — registers the user's locked LP NFT into LuckPool V2 (register(lockId)), claims the reward when time is up.
These two tabs combine the DEX → LP → Lock → Luck flow on asingle screen. After a swap, the user can create an LP position, lock it, and register for Luck from the same page.
Native POL Swap
For swaps with native POL, WMATIC is wrapped (POL address = 0x0, so the adapter uses thewrapped field). The native value is passed to the router via the nativeValue parameter.
04Token Family¶
| Token | Type | Max Supply | Mint | Pause | Owner |
|---|---|---|---|---|---|
VOI | Mint complete | 100,000,000 | Cap reached | Yes (unused) | Owner |
HEY | Mint complete | 2,000,000,000 | Cap reached | Yes (unused) | Owner |
JVOI | Fixed | 777,777,777 | None | None | Ownerless |
HLTV | Fixed | 629,629,629 | None | None | Ownerless |
URA | Mintable (lockable) | 98,774,312,000,000,000 | Minter (lockable) | None | Owner (renounceable) |
GARURA | Single unit | 1 | None | None | Ownerless |
4.1 VOI — Task/Activity Token¶
ERC20 + ERC20Permit + ERC20Burnable + Pausable + Ownable. 100M cap. The contract remains mintable on paper, butthe full 100M cap has been minted — there is no operational minting anymore, and the admin panel mint UI has been removed. EIP-2612 permit allows approve via signature; no transfer tax.
Use: DEX liquidity pools (Uniswap V3 / Sushi V3 / QuickSwap V3), marketplace payments for domain purchases.
4.2 HEY — Streaming Token¶
Same structure as VOI, different supply (2B). The full 2B cap has been minted — operational mint is closed, admin UI was removed. Main reward token of the Heylogram TV streaming platform — currently used for domain and marketplace payments; ready for the future live streaming platform.
mint function on the contracts is technically still callable because they inherit OZ Ownable, but no address requests new mints; the admin panel mint button was removed (2026-05-16). The permanent solution for CMC's "Mintable" warning would be to call renounceOwnership() — currently a pending decision (ownerless vs future flexibility).4.3 JVOI — Ownerless Utility Token¶
Completely different architecture. Does not use OpenZeppelin — raw ERC-20. No Ownable. No mint. NoPausable. The constructor mints all supply (777,777,777 JVOI) to the deployer; then the contract is immutable forever.
On-chain metadata: website,whitepaper, platform, descriptionstored as constant strings — visible in the explorer.
4.4 HLTV — Streaming Platform Token¶
Same architecture as JVOI (raw ERC-20, ownerless, immutable). Supply 629,629,629 — symbolic amount equal to the hex digit sum of two of the user's vanity deployer addresses.
Target use: Heylogram.tv (future live streaming platform) — streamer tipping + NFT gift economy.
4.5 URA — Garura's Leaf¶
Click game token. Design decision: DEX safetyachieved by removing all "suspicious" features:
- Pause none
- Blacklist none
- Transfer tax none
- Upgrade none
- Mint from a single address (click game contract)
- Minter locked forever via
lockMinter() - After lock,
renounceOwner()→ fully ownerless
Total supply: 98,774,312,000,000,000 URA. Takes about ~12,350 clicks per global population to deplete.
4.6 GARURA — 1 in the World¶
Total supply 1 (= 1 × 10¹⁸ wei). No mint function. No pause. No owner. Sent to the URA click game contract (vault) at deployment.
Earning: the wallet that performs the 8,000,000,000th click takes GARURA from the vault. _updateis overridden to write every transfer as a GaruraElDegistirdievent on-chain — a historical record.
05Domain System¶
HeylogramDomain is an ERC-721 — representing.heylogramtv names as NFTs. Old + new contracts operate in parallel; the new contract uses tier pricing.
5.1 Tier Pricing¶
| Length | POL | VOI | USD (~POL ~$0.084) |
|---|---|---|---|
| 1 letter | 120 | 1,500 | ~$10 |
| 2 letters | 60 | 750 | ~$5 |
| 3 letters | 30 | 400 | ~$2.5 |
| 4 letters | 15 | 200 | ~$1.3 |
| 5+ letters | 6 | 80 | ~$0.5 |
5.2 Name Rules¶
- 1–63 characters
- Only lowercase (a–z), digits (0–9), hyphen (-)
.heylogramtvsuffix is auto-stripped
5.3 Three Currencies¶
mintPOL(name, ipfsLink)— native POLmintVOI(name, ipfsLink)— VOI (transferFrom)mintHEY(name, ipfsLink)— HEY (fixed price)mintUcretsiz(to, name, ipfsLink)— onlyOwner; for airdrop / team
5.4 Profile Page¶
Every domain has a Linktree-style profile at/resolve/[name]. Profile data in Firestore (domainProfiles/{name}):
displayName, bio, avatarUrl, coverColor
links: { twitter, github, telegram, discord, website }
customTextThe owner can connect their wallet and edit their profile. When ownership changes, the profile auto-transfers to the new owner (Firestore wallet field syncs with ownerOf(tokenId)).
5.5 Old + New Parallel¶
Old contract (0xc654…0816) is the first version, fixed price. New contract (0xc993…A826) is tier-priced V2. Both are active; marketplace and partner contracts auto-detect via_nftKontrat().
#1, #2 etc. — the same ID can be assigned to two different domains. The UI disambiguates with an ?old=1 query parameter. Accepted known behavior.06Marketplace¶
HeylogramMarketplace v2 is the domain NFT trading layer. Automatically supports both old + new domain contracts.
6.1 Three Currencies¶
Seller chooses one of the ParaBirimi enum:POL, VOI, HEY. Buyer callspolIleSatinAl, voiIleSatinAl orheyIleSatinAl with the same currency.
6.2 Commission¶
komisyonBaz = 500(= 5%)- Maximum
komisyonGuncelle: owner can adjust up to 10% - Commission to
komisyonAlici; remainder to seller
6.3 Flow¶
1. Seller approves the domain NFT
2. listele(tokenId, fiyat, paraBirimi)
3. Added to aktifListeIds[]
4. Buyer calls the buy function
5. Commission → komisyonAlici
6. Net price → seller
7. NFT → buyer
8. listeyiIptalEt or auto (after sale)6.4 Old Contract Detection¶
function _nftKontrat(tokenId) returns (IERC721, bool legacy) {
try domainNFT.ownerOf(tokenId) returns (address o) {
if (o != 0) return (domainNFT, false);
} catch {}
return (domainNFTLegacy, true);
}If a token doesn't exist in the new contract, it falls back to the old. This determines which NFT contract should hold the listing.
07Garura System SHELVED · 2026-05-16¶
/garura, /gorevler and /advertisepages were disabled with ShelvedNotice (original code is preserved in archived-page.txt files). The GaruraPool V2 contract remains live on mainnet (0x8755…DFBb0) but is no longer called from the frontend; partners can still recover their unused balance via partnerCek(). The season system was halted due to economic complexity — a simpler model based on VOI/HEY liquidity + URA click game was adopted.This section is preserved as documentation for history + system behavior. If reopened, it would be planned with a V3 contract + Chainlink VRF integration.
The following describes the old system (during its live period):
Garura is the seasonal reward pool. Partners deposit VOI, users complete tasks, and the pool was distributed proportionally at season end. Two versions exist; V2 was in active use.
7.1 V2 — 1 VOI = 1000 Points (fixed rate)¶
V1's biggest issue: partners could withdraw funds at any time, and there was no point/VOI ratio. V2 solves this with a fixed economic model:
PUAN_VOI_ORANI = 1000 // 1 VOI = 1000 points (never changes)
PLATFORM_PAYI = 15 // 15% admin, 85% pool
partnerYatir(voiAmount, tokenId, isOld):
- Check domain ownership
- Pull VOI, partner credit = voiAmount × 1000 / 1e18 points
gorevTamamlandi(partnerWallet, pointAmount): // backend calls
- Enough point credit?
- VOI equivalent = pointAmount × 1e18 / 1000
- 15% → adminBakiyesi
- 85% → havuzBakiyesi
- partner.kullanilanPuan += pointAmount
- partner.kilitliVOI += poolPortion
- partner.platformVOI += adminPortion
partnerCek(voiAmount):
- Only "free balance" = toplamYatirilan - kilitliVOI - platformVOI
- i.e., locked portion can't be withdrawn
adminPush(users[], amounts[]): // end of season
- Only spent from havuzBakiyesi
- Max 500 users/tx7.2 Operator System¶
gorevTamamlandi() is called by the backend server. To avoid admin being the sole authority, there's anoperators mapping — admin can add new operators (e.g., a second server), but operators cannot withdraw funds.
7.3 Season Computation (off-chain)¶
The contract knows nothing about seasons. On the Firestore side:
seasons/{seasonId} {
token: 'VOI' | 'HEY',
amount: number, // tokens in pool
startTime, endTime,
status: 'active' | 'scheduled' | 'ended',
totalCommitted: number, // total committed points
totalEarned: number,
participantCount: number
}
garuraRegistrations/{docId} {
wallet,
seasonId,
committedPoints,
earnedDuringSeason, // extra points earned during season
claimed: boolean
}At season end, /api/cron/season-distribute computes ratios from Firestore and writes to the contract viaadminPush().
7.4 Share Computation¶
user_share = (committedPoints + earnedDuringSeason)
/ total_pool_points × season_amountThe 15/85 split is handled in-contract at deposit. Note:The earlier app/api/garura/claim/route.ts additionally deducted 10% on top — this is a known issue (see §18 N6); the POST endpoint was fixed but the GET endpoint still shows the old deduction.
7.5 Emergency / Management¶
setPaused(true)— temporary haltdeprecate()— permanent close (for migration)emergencyWithdraw(to, amount)— only when paused/deprecatedproposeAdmin / acceptAdmin— 2-step admin transfer
08Task System SHELVED · 2026-05-16¶
/gorevler page was disabled withShelvedNotice on 2026-05-16. The system's main anchor was the Garura season — once Garura was shelved, completing tasks lost its economic purpose. The/api/mission/* endpoints were not deleted (some may still be used for URA milestone and domain mint points), but the end-to-end flow (task → points → season → reward) is now closed.The description below preserves the old system as documentation.
On Heylogram TV, completing tasks earned points. Points were stored in Firestore and converted to VOI/HEY from the Garura pool at season end.
8.1 Task Types¶
| Type | Verification | Status |
|---|---|---|
| On-chain (buy / list / sell domain) | Event log + tx hash verify | live |
| Complete profile | Firestore profile fields | live |
| Partner site visit | Script + wallet param | live |
| X (Twitter) share | Manual approval / honor system | temporary |
| Referral | ?ref=WALLET cookie + tx | planned |
8.2 Signed Completion Flow¶
1. User performs the task (e.g., visits a partner site)
2. Frontend: signs a message via viem
message = "mission:<taskId>:<timestamp>"
3. POST /api/mission/complete { wallet, taskId, signature, timestamp }
4. Server:
- Timestamp within 5min?
- Wallet regex correct?
- Signature ECDSA recover correct?
- Was this mission already completed?
5. Firestore: kullanicilar/<wallet> -> puan += task.points
if registered in active season: earnedDuringSeason += task.points8.3 Tracker Script¶
A partner embeds <script src="https://web3.jjvoi.com/api/track/script/<siteKey>?hw=<wallet>"></script> on their site. The script reports the visit to/api/track/visit; if the user has a wallet, Firestore points increase.
Bot protection: the same IP+wallet is counted at most once in 24 hours. Rate limiting is still under development (see §18 W04).
09Partner / B2B SHELVED · 2026-05-16¶
/partner page was disabled on 2026-05-16 withShelvedNotice. The partner system was tied to the Garura pool — once Garura was shelved, the economic loop where partners deposit VOI collapsed. The /api/partner/*endpoints and the GaruraPool V2 contract still live on mainnet, but new partner activations are no longer accepted.The description below preserves the old design as documentation. It may be reopened with a different B2B model (e.g., direct deposits to DEX liquidity pools + Badge mint).
It was Heylogram TV's most distinctive feature:partner sites that create their own tasks. The site owner came in, defined tasks, redirected our users to their site, and deposited VOI to the reward pool.
9.1 Partner Eligibility¶
- Must own at least 1
.heylogramtvdomain NFT - "Become Partner" button on the
/resolve/[name]profile page - If the domain is sold, partner status is auto-revoked (lazy deactivation)
9.2 Activation Flow¶
POST /api/partner/activate
{ wallet, domainName, timestamp, signature }
Server:
1. Timestamp within 5min?
2. Verify signature via ECDSA
3. For domainName, ownerOf(tokenId) === wallet (on-chain)
4. Firestore: partners/{domain} = { wallet, tokenId, activated: true, ... }9.3 Task Creation¶
- "Create Task" button on the partner panel
- Site name, URL, reward amount (points), conditions
- Deposit VOI to GaruraPool V2 (receives point credit)
- Downloads custom JS or Kotlin integration code
9.4 What Happens When Domain is Sold?¶
- UI:
/api/partnerGET does a liveownerOf(tokenId)check on every request; if old owner,activated: false - Contract:
partnerCek()on GaruraPool V2 only returns unused VOI; the portion already locked to the pool remains - Firestore:
partners/{domain}.activatedfield is set to false, partner panel won't open
9.5 Webhook System¶
When the partner site notifies of a visit, HMAC signature is used:
POST /api/partner/webhook
{ taskId, userWallet, timestamp, signature }
verifyHmac(webhookSecret, taskId, userWallet, timestamp, signature)webhookSecret is in Firestore; flagged asW12 in the audit — in production, Security Rules should restrict it to admin only (see §18).
10HeylagramGaruraBadge (ERC-1155)¶
Partner milestone NFT system. A single ERC-1155 contract; partners open their own level collections. Appears on OpenSea as one collection: Heylagram-Garura.
10.1 Two-Layer Structure¶
- Partner Collection — 1 collection per partner wallet. The name, logo, and description are set by the partner.
- Level — each collection can have as many levels as desired. Each level gets a random
tokenId.
10.2 Random TokenId Generation¶
No sequential 1, 2, 3 collision issue like domain NFTs. For each level:
tokenId = keccak256(
"heylagram-garura-badge",
partnerId,
levelIndex,
block.timestamp,
block.prevrandao,
msg.sender,
partnerCount
)In case of collision, tokenIdExists[candidate] is checked; if it collides, keccak256(candidate, block.timestamp)is recomputed. In practice, never collides.
10.3 Fees¶
| Action | USDT | USDC | VOI | HEY |
|---|---|---|---|---|
| Create collection | 10 | 10 | 1,000 | 10,000 |
| Add level | 2 | 2 | 200 | 2,000 |
| Mint commission | 5% (max 20%, admin-adjustable) | |||
10.4 Mint Paths¶
adminMint(partnerId, levelIndex, user)— called by backend on milestoneadminMintBatch— end-of-season batch mint (max 200 users)userMint(partnerId, levelIndex)— user pays their own fee
10.5 contractURI and OpenSea¶
contractURI() pure returns inline JSON:
name: "Heylagram-Garura"
description: "Heylogram TV partner badge collection..."
external_link: "https://web3.jjvoi.com"Each tokenId's metadata URI is set by the partner (IPFS / HTTPS — PNG, GIF, animated SVG, WebP).
11URA & GARURA¶
Click game mechanism: connect wallet, click, earn 1 URA. Total supply 98.7 quadrillion — practically very long to deplete. As it depletes, the wallet that reaches the8 billion click threshold wins GARURA.
11.1 Flow¶
POST /api/ura/click
{ wallet, timestamp, signature }
Server:
1. Wallet regex check
2. Rate limit: 1 click per 10 seconds
3. Signature ECDSA verify
4. URAToken.mint(wallet, 1e18)
5. Firestore globalClicks++
6. Milestone check:
if (globalClicks === 8_000_000_000):
grandPrize: URA milestone tx hash, date, wallet recorded
(GARURA transfer vault → wallet, possibly automatic)11.2 Leak Protections¶
- No pause in token contract → DEXs cannot freeze
- No blacklist in token contract → no address can be blocked
- No transfer tax → fully DEX-compatible
- Minter set once, then locked via
lockMinter() renounceOwner()→ contract becomes fully ownerless
11.3 PWA Frontend¶
The /ura page is mobile-first, can be added to home screen. Tree animation + click + global counter. Hidden access via QR (no direct link on web).
11.4 GARURA Historical Record¶
Each transfer is written to blockchain as GaruraElDegistirdi(from, to, timestamp, blockNumber) event — staying in history forever.
11.5 Nickname & Leaderboard¶
Firestore-based nickname system to show alias instead of wallet address:
POST /api/ura/nickname— signed, 3–20 chars, uniqueGET /api/ura/leaderboard?limit=100— periodic snapshot, sorted by on-chain balance- Leaderboard cache in
uraLeaderboard/collection; cron refreshes every 15min - Top 100 always cached; deeper queries computed on-demand
11.6 Referral System¶
If new URA-clicking users arrive via a referral link, both inviter and invitee earn extra URA:
Link: https://web3.jjvoi.com/ura?ref=<wallet>
First-click flow:
/api/ura/click { wallet, ref?, timestamp, signature }
→ /api/ura/referral { wallet, referrer }
→ Firestore: kullanicilar/{wallet}.referans = referrer (one-time)
→ Bonus URA: +10 to inviter, +5 to invitee
→ Cooldown: same referrer counts max 20 invites in 24h (bot protection)11.7 In-App Wallet & Session¶
Because URA runs as a mobile PWA, some users may not have MetaMask. So the in-app wallet option:
- Anonymous session key for wallet-less users (browser localStorage)
POST /api/ura/session— signed, starts session- User points/URA accumulate in Firestore; when wanted, can be linked to a real wallet and minted on-chain
/api/ura/batch-mint— mints accumulated points to contract in a single tx (gas-efficient)
11.8 Detailed User Guide¶
A separate long guide exists for URA's user-facing side:/whitepaper/ura. That page contains step-by-step clicking, PWA install, leaderboard entry, milestone race, grand prize rules and other end-user content.
12Liquidity Mining¶
LiquidityMining.sol — user deposits POL + JVOI, a Uniswap V3 LP NFT is created, locked for 30 days, and at the end the user receives the LP NFT + 1% JVOI reward.
12.1 Two Position Types¶
| Type | Lock | Min Deposit | JVOI Reward |
|---|---|---|---|
| STANDARD | 30 days | 0 POL | 1% |
| ANGEL (investor) | 180 days | 100 POL | 5% |
12.2 Flow¶
deposit(jvoiAmount)payable — user sends POL + JVOI- Contract increments
lockedUserJvoicounter - Backend creates Uniswap V3 LP within 48 hours
- Backend calls
lockPosition(positionId, nftTokenId) - When time is up, user calls
claim(positionId)to receive LP NFT + JVOI reward
12.3 Refund Mechanism¶
If the backend fails to link an NFT within 48 hours, the user can call refundPosition(positionId) to recover POL + JVOI. Constant: REFUND_TIMEOUT = 48 hours.
12.4 emergencyWithdrawToken Hardening¶
function emergencyWithdrawToken(token, amount):
if (token == JVOI):
protected = rewardPool + lockedUserJvoi
require(balance >= protected)
withdrawable = balance - protected
require(amount <= withdrawable)
transfer(admin, amount)Admin can only withdraw JVOI that is outside user funds and reward pool. Other ERC-20s mistakenly sent can be fully withdrawn.
13LuckPool V2¶
A lottery pool targeted at LP NFTs locked in the existing Locker contract (0x3df6…CA98). Lock is registered; when time is up, the user wins a random ecosystem token.
13.1 V1 → V2 Bug Fix¶
ILocker.getLock was defined as 6 separate return parameters. Actually, the contract returns a Lockstruct. Due to the ABI mismatch, every register call reverted. V1 is functionally non-working.struct Lock definition andILocker.getLock(...) returns (Lock memory) were defined with the correct ABI. Additionally:- LP NFT content check (Uniswap V3 NPM only)
- Ecosystem token requirement (at least one of VOI/JVOI/HEY/HLTV/URA/GARURA)
- Liquidity > 0 check
emergencyWithdrawPrize— admin can emergency-withdraw tokens
13.2 Registration Flow¶
register(lockId):
Lock l = ILocker.getLock(lockId)
require(l.owner == msg.sender)
require(!l.withdrawn)
require(l.unlockTime > now) // still locked
require(l.nftContract == NPM) // Uniswap V3 only
positions(l.tokenId) → t0, t1, liquidity
require(liquidity > 0)
require(ecosystemTokens[t0] || ecosystemTokens[t1])
entries[++entryCount] = Entry(...)13.3 Random Selection¶
rand1 = keccak256(
block.prevrandao, block.timestamp, entryId,
unlockTime, msg.sender, block.coinbase
)
token = available[rand1 % count]
maxAmt = poolRemaining / 20 // max 5% of pool
amount = (rand2 % maxAmt) + 1e18prevrandao in advance. Acceptable for low-value rewards; for high-value lotteries, Chainlink VRF should be used.14MintTimelock CANCELLED · 2026-05-16¶
polygon.heylogramtv/contracts/MintTimelock.sol but will not ship to mainnet.The description below is preserved as a design document.
It was written to soften CoinMarketCap's "Mintable" security warning. Token owners would be transferred to this contract; every mint would wait 48 hours in queue.
14.1 Flow¶
1. token.transferOwnership(MintTimelock.address)
2. Admin: queueMint(token, to, amount) → id
3. 48-hour countdown begins (DELAY = 48h)
4. When time is up, anyone can call: executeMint(id)
→ token.mint(to, amount)
5. If admin spots a dangerous mint: cancelMint(id)
6. Queue older than 14 days expires (GRACE = 14d)14.2 Admin Two-Step¶
proposeAdmin(newAdmin) → acceptAdmin(). Prevents accidental transfer to an unauthorized address.
14.3 Token Ownership Transfer¶
transferTokenOwnership(token, newOwner) — onlyAdmin
// Transfer token ownership elsewhere (for emergency)
// This call is NOT TIMELOCKEDtransferTokenOwnership is not 48h timelocked. If admin key is stolen, an attacker can transfer token ownership to another address in a single tx and mint there.Solution: tie this function to the queue/execute flow as well, or protect with a multi-sig. See §19.15Contract Reference¶
16 .sol files — 15 production + 1 mock. For each contract: one-line role, inheritance, critical functions, and behaviors.
Functions
mint(to, amount)— onlyOwner; cap checkbatchMint(recipients[], amounts[])— onlyOwner; for airdropspause / unpause— onlyOwnerburn / burnFrom— standard ERC20Burnablepermit— EIP-2612 signature-based approve
Same inheritance, functions, behaviors as VOI; only difference is MAX_SUPPLY = 2,000,000,000 × 10¹⁸.
Features
- No owner. Ownable not inherited.
- No mint. No mint function.
- No pause. Pausable not inherited.
- No blacklist. No address can be blocked.
- On-chain metadata:
website,whitepaper,platform,descriptionconstants uncheckedblocks in transfer — gas optimization
Same architecture as JVOI, different supply (629,629,629). Main token of the (future) Heylogram.tv live streaming platform.
Functions
setMinter(address)— onlyOwner, set once before locklockMinter()— lock foreverrenounceOwner()— fully relinquish ownershipmint(to, amount)— onlyMinter
Features
- No mint function — minted only in constructor
- No pause, no blacklist, no owner
- Every transfer emits
GaruraElDegistirdievent
Functions
fiyatHesapla(name)— pure; POL+VOI by name lengthmintPOL / mintVOI / mintHEY— user pays to mintmintUcretsiz(to, name, ipfsLink)— onlyOwner; airdropipfsGuncelle(tokenId, link)— owner onlydomainSorgula(name)— view; (taken, owner, ipfs, date)rezerveEt(name)— onlyOwnerparaCek— onlyOwner; POL + VOI + HEY balances
Functions
listele(tokenId, price, currency)listeyiIptalEt(tokenId)polIleSatinAl / voiIleSatinAl / heyIleSatinAlaktifListeler / listeDetaykomisyonGuncelle / komisyonAlicisiGuncelle— onlyOwner
V1 (unused): Token whitelist, ECDSA-signed claim, separate partner pool, admin push, deprecate mechanism. Missing: no fixed rate, partner can withdraw at will.
V2 (active):
partnerYatir(voiAmount, tokenId, isOld)gorevTamamlandi(partnerWallet, pointAmount)— onlyOperatorpartnerCek(voiAmount)— free balance onlyadminPush(users[], amounts[])— onlyAdmin, max 500adminCek / adminCekTumu— platform sharesetPaused / deprecate / emergencyWithdrawsetOperator / proposeAdmin / acceptAdmin
V1: ILocker interface wrongly defined as 6 returns — every register reverts. Unusable.
V2 (active):
- Correct
struct LockABI - Uniswap V3 NPM LP NFT only
- 6 ecosystem token whitelist (VOI/JVOI/HEY/HLTV/URA/GARURA)
addPrize / emergencyWithdrawPrize— onlyAdminregister(lockId)— 7 checksclaim(entryId)— random token + max 5%/pool
fundRewardPool(amount)— onlyAdmin, deposits JVOIdeposit(jvoiAmount)payable — POL + JVOIlockPosition(positionId, nftTokenId)— onlyAdmin, backend bindsangelDeposit— 100 POL min, 180 days, 5% rewardrefundPosition— if backend doesn't bind NFT within 48hclaim— LP NFT + JVOI rewardemergencyWithdrawToken— for JVOI, can't touch protected portion
queueMint(token, to, amount)— onlyAdminexecuteMint(id)— anyone (after 48h)cancelMint(id)— onlyAdmintransferTokenOwnership(token, newOwner)— onlyAdmin, not timelockedproposeAdmin / acceptAdmin
createCollection— partner; fee in one of 4 tokensaddLevel— partner; random tokenId generatedupdateLevel / updateCollectionadminMint / adminMintBatch— onlyOwner, milestoneuserMint— if partner set a feesetCreationFee / setLevelFee / setMintCommission— onlyOwnercontractURI— OpenSea inline JSON
16Live Contract Addresses¶
Polygon Mainnet. Sources verified on PolygonScan.
| Contract | Address | Status |
|---|---|---|
| VOI Token | 0xc8Ad9EB7D26337E598AFE89bF69d21455BD10501 | live · CMC-listed |
| HEY Token | 0xC9e3488E3CDAA944fDBFce0DEf602d4b09db4032 | live |
| JVOI Token | 0xa50eBF45c9C8D7F45C861D6064165E910e3fbEc5 | live · ownerless |
| HLTV Token | 0x8C55F7815b619e07B16fed6C3D0418684e441038 | live · ownerless |
| URA Token | 0xaafbd87e2D69Dc317276a16E2c195d269214b7C5 | live |
| GARURA Token | 0x427E0a0F5B8b70e2f98CcA860FdFF58055C253C5 | live · 1 unit |
| Domain NFT (new) | 0xc993f5A85724a609A4747e9c237A12C57F13A826 | live · v2 |
| Domain NFT (old) | 0xc654129EF0FE6677d7A01B852D1113a0110b0816 | old · parallel |
| Marketplace (new) | 0x88Be8507cB3750e16331Fe10601fB0297d4C35ea | live · v2 |
| Marketplace (old) | 0x3C7a64bCc80DF0726B4Ddfae2621ccB0Ea3fD3Ce | old |
| GaruraPool V1 | 0xf1Ba8A40b89B6D7EdE52159A06F867508B7C731a | deprecated |
| GaruraPool V2 | 0x87555b791A30ec5B10150ac1BD399741030DFBb0 | live |
| MintTimelock | — | pending deploy |
| LiquidityMining | — | pending deploy |
| LuckPool V2 | 0xd20e8ABDd5b767D6E112F0f51B058BDa0715B9Cf | live |
| HeylagramGaruraBadge | — | pending deploy |
16.1 Management Addresses¶
- Admin wallet:
0x425490412f1F6e4c6DB58DE0134548dF4Ec22c70 - Uniswap V3 NPM (Polygon):
0xC36442b4a4522E871399CD717aBDD847Ab11FE88 - WMATIC:
0x0d500B1d8E8eF31E21C99d1Db9A6444d3ADf1270 - Locker V1:
0x3df61b76622eC494cEE2f586914DB14913c5CA98 - USDT (Polygon):
0xc2132D05D31c914a87C6611C10748AEb04B58e8F - USDC (Polygon):
0x3c499c542cEF5E3811e1192ce70d8cC03d5c3359
16.2 RPC Usage¶
- ✅
https://polygon.drpc.org - ✅
https://1rpc.io/matic - ❌
polygon-rpc.com— broken - ❌
rpc.ankr.com/polygon— broken
16.3 API Endpoint Catalog¶
There are 60+ endpoints under app/api/. Grouped table — hidden admin endpoints are not listed.
Token & Supply (CMC + General)
| Endpoint | Method | Response | Description |
|---|---|---|---|
/api/voi/total-supply | GET | plain uint | For CMC |
/api/voi/circulating-supply | GET | plain uint | For CMC |
/api/voi/max-supply | GET | plain uint | For CMC (constant 100M) |
/api/voi-price | GET | JSON | VOI price oracle (from DEX) |
/api/hey/total-supply | GET | plain uint | For CMC |
/api/hey/circulating-supply | GET | plain uint | For CMC |
/api/hey/max-supply | GET | plain uint | For CMC (constant 2B) |
/api/token-info | GET | JSON | Metadata summary for all tokens |
/api/wallet/tokens?wallet= | GET | JSON | All ecosystem tokens in a wallet |
Domain & Resolve
| Endpoint | Method | Description |
|---|---|---|
/api/resolve?name= | GET | Info from domain name (owner, tokenId, profile) |
/api/resolve-name?wallet= | GET | Primary domain from wallet |
/api/resolve-address?name= | GET | Owner address from domain |
/api/domain/has-domain?wallet= | GET | Does wallet own at least 1 .heylogramtv? |
/api/domain/image/[tokenId] | GET | SVG NFT image (dynamic) |
/api/domain/metadata/[tokenId] | GET | OpenSea-compatible metadata JSON |
/api/domain/profile | GET/POST | Read/write profile (Linktree-style) |
/api/domain/website | GET/POST | Domain redirect URL |
Task System
| Endpoint | Method | Description |
|---|---|---|
/api/missions | GET | List of active missions |
/api/mission/complete | POST | Signed mission completion |
/api/mission/content-complete | POST | Content creation task (manual approval) |
/api/mission/domain-mint | POST | Domain mint tx hash verify → points |
/api/user?wallet= | GET | User profile + points |
/api/user/activity?wallet= | GET | Recent activity feed |
Garura Season System
| Endpoint | Method | Description |
|---|---|---|
/api/garura/season | GET | Active season info (token, amount, duration) |
/api/garura/register | POST | Register for season with point commit |
/api/garura/claim | POST | End-of-season token claim (admin-signed) |
/api/cron/season-distribute | POST | Vercel Cron — distributes ended seasons |
URA Click Game
| Endpoint | Method | Description |
|---|---|---|
/api/ura/click | POST | 1 click → 1 URA mint, rate-limited |
/api/ura/batch-mint | POST | Bulk mint accumulated offline points |
/api/ura/leaderboard?limit= | GET | Top-N clicking wallets |
/api/ura/nickname | GET/POST | Bind/read nickname for wallet |
/api/ura/session | POST | Start anonymous session (in-app wallet) |
/api/ura/user?wallet= | GET | User's URA stats |
/api/ura/referral | POST | Referral record (once, anti-bot) |
/api/ura/score | GET | (under luck) click score query |
Partner / B2B
| Endpoint | Method | Description |
|---|---|---|
/api/partner?wallet= | GET | Partner status (live ownerOf check) |
/api/partner/activate | POST | Partner activation via domain |
/api/partner/analytics?wallet= | GET | Task click statistics |
/api/partner/content | POST | Update task content |
/api/partner/deposit-bonus | POST | Bonus points on VOI deposit |
/api/partner/logo | POST | Logo upload (URL or file) |
/api/partner/public-tasks | GET | All active partner tasks for tasks page |
/api/partner/tasks | GET/POST | Partner manages own tasks |
/api/partner/track-visit | POST | Script notification — record visit |
/api/partner/webhook | POST | HMAC-signed webhook (form/game notification) |
Tracker Script (B2B Bot Protection)
| Endpoint | Method | Description |
|---|---|---|
/api/track/script/[siteKey] | GET | JS tracker file (partner site embeds) |
/api/track/visit | POST | Visit notification (CORS *) |
/api/tracker/ping | POST | Heartbeat — user still on page |
/api/tracker/token | GET | Generate tracker session token |
Social Verification
| Endpoint | Method | Description |
|---|---|---|
/api/verify/tweet | POST | Tweet URL verification (X share) |
Blog & Announcements
| Endpoint | Method | Description |
|---|---|---|
/api/blog?status=&limit= | GET | Published posts, publishedAt DESC, 30s CDN cache |
/api/blog/[slug] | GET | Single post detail (TR + EN content, tag, cover, body) |
OG & Sharing
| Endpoint | Method | Description |
|---|---|---|
/api/og | GET | General OG image (social sharing) |
/api/og-en | GET | English OG image |
/api/og/promo/[slug] | GET | Dynamic OG for promo pages |
/api/og/ura | GET | URA share card |
/api/og/blog/[slug] | GET | Blog post share card (dynamic) |
Other
| Endpoint | Method | Description |
|---|---|---|
/api/weather | GET | Decorative data for landing page (animation) |
/api/admin/*, /api/cron/*) are not listed — they're access-controlled and excluded from documentation (reducing attack surface).17Phased Deployment¶
Heylogram TV is largely live on mainnet. The 2 remaining contracts will be deployed in this order. The MintTimelock plan wascancelled — VOI/HEY caps are fully minted and the operational mint is closed (see §4.1–4.2); the proper solution for the "Mintable" warning is now a decision to callrenounceOwnership().
17.1 Phase 6 — LiquidityMining Next¶
scripts/deployLiquidityMining.js --network polygon- Fund JVOI reward pool:
fundRewardPool(amount) - Write backend keeper: listens to
deposittxs, creates Uniswap V3 LP within 48h, callslockPosition - Vercel env:
NEXT_PUBLIC_LIQUIDITY_MINING=0x... - Frontend
/luckpage deposit + claim UX (integrates with the existing Luck tab inside/dapp/dex)
17.2 Phase 7 — Badge ERC-1155¶
LuckPool V2 is already live (0xd20e…B9Cf) — only Badge remains.
scripts/deployBadge.js- Vercel env:
NEXT_PUBLIC_BADGE_CONTRACT - Badge collection creation UI on partner panel
18Audit & Findings¶
Static analysis report (AUDIT_REPORT.md, 2026-04-28) run via node audit.js. 62 files, 47ms, 288 findings.
| Severity | Count | Description |
|---|---|---|
| 🔴 CRITICAL | 6 | Security / money loss |
| 🟠 ERROR | 33 | Runtime / logic |
| 🟡 WARNING | 126 | UX brittleness |
| 🔵 INFO | 123 | Improvements |
| Total | 288 |
18.1 Critical Findings¶
| Code | Issue | Status |
|---|---|---|
| K06 | Replay attack — no timestamp/nonce on partner APIs | Fixed (5min TTL signature) |
| K05 | Topic hash hardcoded — false positive (label confusion) | Intentional |
| K04 | CORS * — /api/track/visit | Intentional (partner sites call it) |
| K02 | partners collection queried by wallet | Intentional (doc ID = domain, where if needed) |
18.2 High-Priority Errors¶
| Code | Issue | Impact |
|---|---|---|
| E09 | Math without NaN check (6 files) | API 500s |
| E08 | Firestore query without limit — partner-tasks endpoint | Timeout + high bill |
| E05 | No wallet regex (9 endpoints) | Fixed |
| E02 | .data()! non-null assertion (11 files) | Crash risk |
| E07 | Promise.all → allSettled needed (6 files) | Unhandled rejection |
18.3 New Findings (During Writing)¶
N1 — MintTimelock transferTokenOwnership not timelocked MOOT
The MintTimelock plan was cancelled on 2026-05-16 (VOI/HEY mint operation closed, see §17). The contract will not be deployed, so this finding no longer applies.
N2 — Dead-code in GaruraPool V2 SHELVED · MOOT
require(voiMiktari % (1e18 / PUAN_VOI_ORANI) == 0 || true, "");
^^^^^^^^
always true|| true always being true makes the require do nothing. Either forgotten or intentional dummy. Code quality.Solution: remove the line or write the actual check.
N3 — VOI/HEY Pause Issue After Token Ownership Transfer MOOT
Since the MintTimelock transfer never happens, pause authority remains with the admin wallet. This finding no longer applies (see N1).
N4 — LuckPool V2 Random Validator Manipulation ACTIVE (live on mainnet)
LuckPool V2 is live on mainnet (0xd20e…B9Cf) with theregister/claim flow working. Inside_selectPrize, randomness is based onblock.prevrandao, block.coinbase,block.timestamp — Polygon validators may know these values in advance or do minor manipulation.Not economically feasible for low prizes but risk grows as maxAmt (5% of pool, max 1000 tokens) grows.
Solution: Chainlink VRF V2 integration (VRFConsumerBaseV2 inheritance + requestRandomWordsflow). Current V2 is immutable; deploy a new LuckPoolV3and migrate token pools via addPrize.
N6 — Garura Share Double-Cut Risk SHELVED · MOOT
The Garura season system was shelved on 2026-05-16 (see §7) → the claim endpoint is no longer in operational use. The finding is technically true but no longer relevant. A note to be fixed if Garura is reopened.
N10 — DEX Pool-based Quote Precision Loss LOW
In lib/dexAdapters.ts quoteFromPoolBased, for token1→token0 direction:
amountOut = (amountIn * Q192) / num; // Q192 = 2¹⁹², num = sqrtPriceX96²No BigInt overflow, but integer division introduces rounding error for small amounts (around ~0.001% — negligible). For large swaps it doesn't compute slippage — no real price impact based on pool depth. When the actual swap is sent, amountOutMinimum protects against slippage (default 0.5% in UI), so economic risk is low.
N11 — findReferencePrice Not Sorted by Liquidity LOW
findReferencePrice queries all venues in parallel, but picks the first non-null result in venue definition order via results.find(r => r !== null). If Uniswap has a low-liquidity pool while Sushi has a deep one, the deeper pool is skipped and a wrong reference price is returned.
Fix: for each venue's result, also fetch pool liquidity and prefer the highest-liquidity one. Used only when opening a new pool, so not critical.
N12 — Sushi V3 Quoter Bypassed Entirely LOW
In quoteFromVenue, the Quoter is only called forvenue.key === 'univ3' (line 279). Sushi V3 (0x64e8…08A7) falls directly to the pool-based fallback. The comment says "Quoter is missing/broken" — if Sushi V3's Polygon Quoter really doesn't work, this is intentional; if it works, an opportunity for better quotes is missed. Action: manually test Sushi V3 Polygon Quoter; if it works, expand the condition tovenue.key === 'univ3' || venue.key === 'sushiv3'.
18.4 Problem Density (Top 5)¶
node audit.js.| File | Findings (2026-04-28) |
|---|---|
components/PartnerPanel.tsx | 21 |
app/hesap/page.tsx | 18 |
app/pazar/page.tsx | 18 |
app/resolve/[name]/page.tsx | 15 |
app/locker/page.tsx | 15 |
18.5 Firestore Security Rules¶
Heylogram TV stores off-chain data in Firestore. Misconfigured security rules could enable point manipulation, partner task tampering, or webhook secret leaks. Core principles infirestore.rules:
General Principle
Only server-side admin SDK writes. No collection allows direct write from the browser. Frontend only reads (and only allowed collections).
Collection Permissions
| Collection | Frontend Read | Frontend Write | Server SDK |
|---|---|---|---|
kullanicilar/ | Own doc | No | Full |
seasons/ | Active is public | No | Full |
garuraRegistrations/ | Own doc | No | Full |
domainProfiles/ | All public | No (server verifies) | Full |
partners/ | All public (logo, tasks) | No | Full |
partners/{x}/.webhookSecret | No (private field) | No | Full |
partnerGorevler/ | Status === 'active' | No | Full |
missions/ | Active === true | No | Full |
uraClicks/global | Public | No | Full |
uraNicknames/ | Own doc | No | Full |
uraLeaderboard/ | All public | No | Full |
votes/ | No | No | Full |
Webhook Secret Privacy
webhookSecret being stored plaintext in Firestore. Solution: in Security Rules,partners/ is readable but thewebhookSecret field is restricted to admin SDK only. In practice, the browser never reads this collection — the/api/partner server endpoint returns a projection without the secret.Write Validation
Since no direct writes are accepted, the frontend never writes to Firestore. All writes go through signature + timestamp + server validation. Catch: forgetting that the browser has no functional write permission during development — it fails when deployed locally.
19Known Risks & Roadmap¶
19.1 Open Structural Issues¶
- N4: LuckPool V2 random — migrate to Chainlink VRF (for high prizes)
- N10: DEX pool-based quote doesn't account for slippage on large swaps (slippage UI protects; low economic risk)
- N11: findReferencePrice uses venue definition order instead of liquidity ranking
- N12: Sushi V3 Quoter bypassed (intentional if truly broken; missed accuracy otherwise)
- E02, E07, E08, E09: 33 backend error fixes (deferred audit notes)
- W04: Partner track-visit / webhook rate limit (bot protection)
No longer applicable: N1/N3 (MintTimelock cancelled), N2/N6 (Garura shelved), N5 (PartnerPool was never used and was removed). These remain in §18.3 as historical documentation.
19.2 CMC Compliance Roadmap¶
Current CMC status (2026-05): VOI and HEY have three warnings (Contract Not Renounced, Pause, Low Liquidity). Other tokens (JVOI, HLTV, URA, GARURA) have only Low Liquidity — these four are already ownerless/immutable."Mintable" warning is gone — VOI/HEY caps are fully minted, CMC no longer shows that warning.
| Warning | Affects | Solution | Status |
|---|---|---|---|
| Contract Not Renounced | VOI, HEY | Call renounceOwnership() | Decision (flexibility vs safety) |
| Pause exists | VOI, HEY | OZ Pausable is inherited — removal requires a new token version (V2 deploy + 1:1 swap) | Next version |
| Low Liquidity | All tokens | Liquidity Mining + DEX flow + organic usage | Phase 6 |
| Mintable | — | Cap reached, warning cleared | Resolved |
19.3 Heylogram.tv Vision¶
The main big project: heylogram.tv live streaming platform. Design is ready (detailed in HAYALLER.md): TikTok Live + Twitch hybrid, fully Web3, HEY token economy, SVG NFT gift system.
Estimate: 6+ months of engineering, requires a team. The HLTV token is ready for this platform — ownerless, immutable, 629M supply.
19.4 CURBAN — AI Image + NFT¶
Part of the vision: write a prompt, AI generates an image, auto NFT mint. Separate token (CURBAN) economy. Users can keep without sharing if they wish. Currently in planning phase.
Design: web3.jjvoi.com/curbansubpage. AI API availability check → queue → show generated image → "Save as NFT?" → Polygon mint. NFT name = prompt, content = generated image. Pricing: cost + 20-30%, paid in CURBAN token. Garura-style seasonal pool possible.
19.5 Smart NFT Vision¶
Current HeylogramDomain NFTs are visually simple SVGs. Dreams:
- Country NFTs: If a user gets
turkey.heylogramtv, the NFT is a waving flag animation (SVG + CSS). - Name NFTs: When
eyup.heylogramtvis taken, the visual is meaning-specific — "Eyup" → metaphor of patience, custom SVG. - Future AI link: prompt = word's meaning, output = NFT image.
19.6 Admin Wallet Security¶
All protocol admin authorities are currently concentrated in a single wallet. Recommended security layer:
- Gnosis Safe multi-sig (2/3 or 3/5) — contract-level guarantee, no single point of failure
- Hot wallet kept separate for daily operations; large wallet operations go through Safe signature
- Hardware wallet usage mandatory
Trade-off: every admin tx requires multiple signatures (delay) and there's a setup cost — but it strengthens operational discipline and significantly reduces admin key compromise risk.
20Glossary¶
Heylogram TV — Polygon mainnet · 6 tokens · 16 contracts · open source
Site: web3.jjvoi.com · X: @heylogramtv · Email: web3@jjvoi.com
Turkish version: /whitepaper/docs