GraphQL API
The GraphQL API runs on Cloudflare Workers. It handles user interactions, Soroban contract calls, and Algolia synchronization.
Key Resolvers
Search
- Internal: Queries
entriesIndexandusersIndexin Algolia - External:
searchExternalMusicqueries Audius for music discovery
Playback
externalAudioUrl: Resolves playable stream URLs for Audius tracks
Mining
mineExternalEntry orchestrates the full mining flow:
- Validate user has sufficient HITZ balance
- Pin media to R2 (audio/video)
- Pin metadata to R2 (CID becomes entry ID)
- Call contract
recordAction("mine", ...)with HITZ fee - Index in Algolia only after Soroban succeeds
- Update user's stake in
sharesIndex
Investing
investEntry allows users to stake additional HITZ:
- Validate user's HITZ balance
- Call contract
recordAction("invest", ...)with fee = stake amount - Update Algolia entry (TVL, totalStaked)
- Update user's stake in
sharesIndex
Streaming/Engagement
recordAction tracks non-staking actions:
stream: User plays a tracklike: User likes a trackdownload: User downloads a track
Each action:
- Transfers HITZ fee from user to treasury
- Increases entry's escrow balance
- Updates Algolia escrow field
Rewards
claimableEarningsPreview: Read-only calculation of claimable HITZclaimEarnings: Executesclaim_rewardson contract, transfers HITZ to user
Unstaking
unstakeEntry allows users to withdraw staked HITZ:
- Call contract
unstake(entry_id, amount) - Update Algolia entry (TVL, totalStaked)
- Update or remove user's stake in
sharesIndex - User receives HITZ back to wallet
Artist Equity
setArtistEquity(entryId, basisPoints): Artists set their non-dilutable equityclaimArtistEquity(entryId): Artists claim their equity portion of rewards
Admin Tools
mergeEntries(fromId, toId): Merges two entries, migrating stakesremoveEntry(id): Removes entry, refunds stakes, deletes R2 assets
Mining Highlights
Balance Validation
// Check user has enough HITZ for mining fee
const userBalance = await getHitzBalance(userId);
const miningFee = getBaseFee(); // 0.1 HITZ
if (userBalance < miningFee) {
throw new Error("Insufficient HITZ balance");
}
1:1 Staking
In V2, the fee paid IS the stake:
// No complex calculation needed
const stake = fee; // 1:1 ratio
await recordAction("mine", entryId, fee);
Error Handling
Strict ordering ensures consistency:
// 1. Upload to R2 first (reversible)
const mediaUrl = await pinToR2(media);
const metadataUrl = await pinToR2(metadata);
// 2. Soroban transaction (authoritative)
const result = await contract.recordAction(/*...*/);
// 3. Only index if Soroban succeeds
if (result.ok) {
await algolia.indexEntry(/*...*/);
}
APR Calculation
function calculateAPR(entryId: string): number {
const entry = await getEntry(entryId);
const rewardPool = await getRewardPool(entryId);
const totalStaked = await getTotalStaked(entryId);
const daysSinceCreation = (Date.now() - entry.createdAt) / (1000 * 60 * 60 * 24);
if (totalStaked === 0 || daysSinceCreation === 0) return 0;
// APR in basis points
return ((rewardPool / totalStaked) / daysSinceCreation) * 365 * 10000;
}
Environment Requirements
| Variable | Description |
|---|---|
ALGOLIA_ADMIN_KEY | Write access to indices |
CONTRACT_ID | Skyhitz Core contract |
HITZ_TOKEN_ID | HITZ token contract |
ISSUER_SEED | Platform wallet for operations |
TREASURY_SEED | Treasury wallet for distributions |