@provable-games/metagame-sdk
v0.1.12
Published
Shared types, utilities, and headless hooks for Provable Games metagame SDKs
Downloads
751
Maintainers
Readme
@provable-games/metagame-sdk
Shared types, utilities, and headless React hooks for Provable Games metagame UIs — powering Budokan (tournaments) and Bokendo (quests).
Features
- Unified types —
Token,Prize,EntryFee,Participant,StatusTimestamps - Utilities — Address formatting, number display, prize aggregation, status computation, entry fee calculations, qualification evaluation, proof building, extension config parsing
- Headless hooks — Pagination, search, token selection, status indicators, score tables, prize tables, entry fee preview, entry/extension qualification
- RPC utilities — On-chain extension and fee/prize config reading via Starknet RPC
- Tree-shakeable — Main entry has zero React dependency; hooks and RPC are separate entry points
- ESM + CJS — Dual build with full TypeScript declarations
Install
npm install @provable-games/metagame-sdk
# or
bun add @provable-games/metagame-sdkOptional peer dependencies (install only what you use):
npm install react # For hooks (/react entry)
npm install starknet # For RPC utilities (/rpc entry)Quick Start
Utilities (no React needed)
import {
indexAddress,
formatNumber,
formatPrizeAmount,
computeStatus,
groupPrizesByToken,
getOrdinalSuffix,
} from "@provable-games/metagame-sdk";
// Normalize a Starknet address
indexAddress("0x00000abc"); // "0xabc"
// Format numbers with smart suffixes
formatNumber(1500000); // "1.5m"
formatNumber(2500); // "2.5k"
// Compute tournament/quest status from timestamps
const status = computeStatus({
registrationStart: 1000,
registrationEnd: 2000,
start: 3000,
end: 4000,
}, Date.now() / 1000);
// { label: "Live", variant: "active", isActive: true, countdown: { ... } }
// Group prizes by token
const grouped = groupPrizesByToken(prizes);
// { "0xabc": { type: "erc20", address: "0xabc", totalAmount: "150" } }
// Ordinal suffixes
getOrdinalSuffix(1); // "st"
getOrdinalSuffix(22); // "nd"Entry Fee & Prize Calculations
import {
calculateEntryFeeBreakdown,
distributePool,
buildEntryFeePrizes,
calculateTotalPrizeValueUSD,
calculatePaidPlaces,
} from "@provable-games/metagame-sdk";
// Break down entry fee into creator/protocol/prize shares
const breakdown = calculateEntryFeeBreakdown(entryFee, participantCount);
// Calculate prize pool distribution
const distribution = distributePool(totalPool, positions, weight);Qualification & Proofs
import {
evaluateQualification,
buildQualificationProof,
identifyExtensionType,
parseTournamentValidatorConfig,
} from "@provable-games/metagame-sdk";
// Check if a user qualifies for entry
const result = evaluateQualification(requirement, userState);
// Build proof for on-chain verification
const proof = buildQualificationProof(qualificationData);
// Parse extension configs from on-chain data
const config = parseTournamentValidatorConfig(rawConfig);React Hooks
import { useStatusIndicator, useTokenSelector, usePagination } from "@provable-games/metagame-sdk/react";
// Auto-refreshing status indicator
function StatusBadge({ timestamps }) {
const status = useStatusIndicator(timestamps); // refreshes every second
return <span className={status.variant}>{status.label}</span>;
}
// Token selector with search + pagination
function TokenPicker({ tokens }) {
const { search, setSearch, filteredTokens, select, pagination, getTokenProps } =
useTokenSelector({ tokens, tokenType: "erc20", pageSize: 10 });
return (
<div>
<input value={search} onChange={(e) => setSearch(e.target.value)} />
{filteredTokens.map((token) => (
<div key={token.address} {...getTokenProps(token)}>
{token.symbol}
</div>
))}
<button onClick={pagination.prev} disabled={!pagination.hasPrev}>Prev</button>
<button onClick={pagination.next} disabled={!pagination.hasNext}>Next</button>
</div>
);
}
// Entry qualification check
import { useEntryQualification } from "@provable-games/metagame-sdk/react";
function EntryGate({ requirement, userAddress }) {
const { qualified, reason, loading } = useEntryQualification({ requirement, userAddress });
if (loading) return <span>Checking...</span>;
return qualified ? <button>Enter</button> : <span>{reason}</span>;
}RPC Utilities
import { readExtensionConfig, readFeeAndPrizeExtensions } from "@provable-games/metagame-sdk/rpc";
// Read extension configuration from on-chain contracts
const config = await readExtensionConfig(provider, extensionAddress);API Reference
Types
| Type | Fields |
|------|--------|
| Token | address, name, symbol, tokenType, decimals?, logoUrl? |
| Prize | id, position, tokenAddress, tokenType, amount, sponsorAddress |
| EntryFee | tokenAddress, amount, creatorShare?, refundShare? |
| EntryRequirement | requirementType, tokenAddress?, entryLimit? |
| Participant | address, rank?, score?, name?, entryNumber? |
| StatusTimestamps | start, end, registrationStart?, registrationEnd?, submissionEnd?, completed?, unlocked? |
| StatusResult | label, variant, isActive, countdown |
Utilities
| Function | Description |
|----------|-------------|
| indexAddress(address) | Strip leading zeros from hex address |
| padAddress(address) | Pad address to 66 characters |
| displayAddress(address) | Truncate to 0x1234...abcd |
| bigintToHex(value) | Convert bigint/number/string to hex |
| formatNumber(num) | Smart formatting with k/m suffixes |
| formatPrizeAmount(num) | Precision-aware prize display |
| formatUsdValue(value) | USD display with <0.01 handling |
| formatScore(num) | Score display formatting |
| formatTime(seconds) | Human-readable duration ("2 Days", "3 Hours") |
| getOrdinalSuffix(n) | Returns "st", "nd", "rd", "th" |
| calculatePayouts(places, weight) | Weighted payout percentages summing to 100 |
| calculateDistribution(positions, weight, ...) | Basis-point distribution matching contract logic |
| groupPrizesByToken(prizes) | Group prizes by token address |
| calculatePrizeValue(amount, decimals, price) | Compute USD value of a prize |
| computeStatus(timestamps, now?) | Derive status label, variant, and countdown |
| isBefore(date1, date2) | Date comparison |
| calculateEntryFeeBreakdown(fee, count) | Break down entry fee into shares |
| distributePool(pool, positions, weight) | Distribute prize pool across positions |
| aggregatePrizesByPosition(prizes) | Group prizes by leaderboard position |
| aggregatePrizesBySponsor(prizes) | Group prizes by sponsor address |
| filterClaimablePrizes(prizes, ...) | Filter prizes claimable by a participant |
| filterZeroPrizes(prizes) | Remove zero-amount prizes |
| parseNFTBulkInput(input) | Parse bulk NFT token ID input |
| getExtensionAddresses(chain) | Get known extension contract addresses |
| identifyExtensionType(address) | Identify extension type from address |
| parseTournamentValidatorConfig(config) | Parse tournament validator config |
| parseERC20BalanceValidatorConfig(config) | Parse ERC20 balance validator config |
| parseOpusTrovesValidatorConfig(config) | Parse Opus Troves validator config |
| evaluateQualification(requirement, state) | Evaluate entry qualification |
| buildQualificationProof(data) | Build proof for on-chain verification |
| buildEntryFeePrizes(config) | Build prize list from entry fee config |
| calculateTotalPrizeValueUSD(prizes, ...) | Calculate total prize pool USD value |
| calculatePaidPlaces(prizes) | Count positions with non-zero prizes |
| buildParticipationMap(registrations) | Map addresses to participation counts |
| buildWinMap(leaderboards) | Map addresses to win counts |
| resolveTournamentQualifications(input) | Resolve tournament validator qualifications |
| calculateOpusTrovesEntries(troves) | Calculate entries from Opus Troves |
| findBannableEntries(entries, ...) | Find entries eligible for banning |
React Hooks (/react entry)
| Hook | Description |
|------|-------------|
| useDebounce(value, delay) | Debounce any value |
| usePagination({ totalItems, pageSize? }) | Page state with next/prev/reset |
| useSearchFilter({ items, searchFields, debounceMs? }) | Filtered list with debounced search |
| useTokenSelector({ tokens, tokenType?, pageSize? }) | Token search + selection + pagination |
| useStatusIndicator(timestamps, refreshMs?) | Auto-refreshing status computation |
| useScoreTable({ participants, pageSize?, sortField? }) | Sorted, searchable, paginated score table |
| usePrizeTable({ prizes, ... }) | Prize display with grouping and pagination |
| useEntryFeePreview({ entryFee, ... }) | Entry fee breakdown preview |
| useEntryQualification({ requirement, ... }) | Entry qualification check |
| useExtensionQualification({ extension, ... }) | Extension-based qualification evaluation |
| useOpusTrovesBannableEntries({ entries, ... }) | Identify bannable Opus Troves entries |
RPC Utilities (/rpc entry)
| Function | Description |
|----------|-------------|
| readExtensionConfig(provider, address) | Read extension config from chain |
| readFeeAndPrizeExtensions(provider, ...) | Read fee and prize extension data |
Development
npm install
npm run build # ESM + CJS to dist/
npm run typecheck # TypeScript validation
npm test # Unit tests
npm run dev # Watch modePublishing
Automated via GitHub Actions — create a GitHub release to trigger npm publish.
License
MIT
