@provable-games/metagame-sdk 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +251 -0
- package/package.json +94 -0
package/README.md
ADDED
|
@@ -0,0 +1,251 @@
|
|
|
1
|
+
# @provable-games/metagame-sdk
|
|
2
|
+
|
|
3
|
+
Shared types, utilities, and headless React hooks for Provable Games metagame UIs — powering [Budokan](https://github.com/Provable-Games/budokan) (tournaments) and [Bokendo](https://github.com/Provable-Games/bokendo) (quests).
|
|
4
|
+
|
|
5
|
+
## Features
|
|
6
|
+
|
|
7
|
+
- **Unified types** — `Token`, `Prize`, `EntryFee`, `Participant`, `StatusTimestamps`
|
|
8
|
+
- **Utilities** — Address formatting, number display, prize aggregation, status computation, entry fee calculations, qualification evaluation, proof building, extension config parsing
|
|
9
|
+
- **Headless hooks** — Pagination, search, token selection, status indicators, score tables, prize tables, entry fee preview, entry/extension qualification
|
|
10
|
+
- **RPC utilities** — On-chain extension and fee/prize config reading via Starknet RPC
|
|
11
|
+
- **Tree-shakeable** — Main entry has zero React dependency; hooks and RPC are separate entry points
|
|
12
|
+
- **ESM + CJS** — Dual build with full TypeScript declarations
|
|
13
|
+
|
|
14
|
+
## Install
|
|
15
|
+
|
|
16
|
+
```bash
|
|
17
|
+
npm install @provable-games/metagame-sdk
|
|
18
|
+
# or
|
|
19
|
+
bun add @provable-games/metagame-sdk
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
**Optional peer dependencies** (install only what you use):
|
|
23
|
+
|
|
24
|
+
```bash
|
|
25
|
+
npm install react # For hooks (/react entry)
|
|
26
|
+
npm install starknet # For RPC utilities (/rpc entry)
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
## Quick Start
|
|
30
|
+
|
|
31
|
+
### Utilities (no React needed)
|
|
32
|
+
|
|
33
|
+
```ts
|
|
34
|
+
import {
|
|
35
|
+
indexAddress,
|
|
36
|
+
formatNumber,
|
|
37
|
+
formatPrizeAmount,
|
|
38
|
+
computeStatus,
|
|
39
|
+
groupPrizesByToken,
|
|
40
|
+
getOrdinalSuffix,
|
|
41
|
+
} from "@provable-games/metagame-sdk";
|
|
42
|
+
|
|
43
|
+
// Normalize a Starknet address
|
|
44
|
+
indexAddress("0x00000abc"); // "0xabc"
|
|
45
|
+
|
|
46
|
+
// Format numbers with smart suffixes
|
|
47
|
+
formatNumber(1500000); // "1.5m"
|
|
48
|
+
formatNumber(2500); // "2.5k"
|
|
49
|
+
|
|
50
|
+
// Compute tournament/quest status from timestamps
|
|
51
|
+
const status = computeStatus({
|
|
52
|
+
registrationStart: 1000,
|
|
53
|
+
registrationEnd: 2000,
|
|
54
|
+
start: 3000,
|
|
55
|
+
end: 4000,
|
|
56
|
+
}, Date.now() / 1000);
|
|
57
|
+
// { label: "Live", variant: "active", isActive: true, countdown: { ... } }
|
|
58
|
+
|
|
59
|
+
// Group prizes by token
|
|
60
|
+
const grouped = groupPrizesByToken(prizes);
|
|
61
|
+
// { "0xabc": { type: "erc20", address: "0xabc", totalAmount: "150" } }
|
|
62
|
+
|
|
63
|
+
// Ordinal suffixes
|
|
64
|
+
getOrdinalSuffix(1); // "st"
|
|
65
|
+
getOrdinalSuffix(22); // "nd"
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
### Entry Fee & Prize Calculations
|
|
69
|
+
|
|
70
|
+
```ts
|
|
71
|
+
import {
|
|
72
|
+
calculateEntryFeeBreakdown,
|
|
73
|
+
distributePool,
|
|
74
|
+
buildEntryFeePrizes,
|
|
75
|
+
calculateTotalPrizeValueUSD,
|
|
76
|
+
calculatePaidPlaces,
|
|
77
|
+
} from "@provable-games/metagame-sdk";
|
|
78
|
+
|
|
79
|
+
// Break down entry fee into creator/protocol/prize shares
|
|
80
|
+
const breakdown = calculateEntryFeeBreakdown(entryFee, participantCount);
|
|
81
|
+
|
|
82
|
+
// Calculate prize pool distribution
|
|
83
|
+
const distribution = distributePool(totalPool, positions, weight);
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
### Qualification & Proofs
|
|
87
|
+
|
|
88
|
+
```ts
|
|
89
|
+
import {
|
|
90
|
+
evaluateQualification,
|
|
91
|
+
buildQualificationProof,
|
|
92
|
+
identifyExtensionType,
|
|
93
|
+
parseTournamentValidatorConfig,
|
|
94
|
+
} from "@provable-games/metagame-sdk";
|
|
95
|
+
|
|
96
|
+
// Check if a user qualifies for entry
|
|
97
|
+
const result = evaluateQualification(requirement, userState);
|
|
98
|
+
|
|
99
|
+
// Build proof for on-chain verification
|
|
100
|
+
const proof = buildQualificationProof(qualificationData);
|
|
101
|
+
|
|
102
|
+
// Parse extension configs from on-chain data
|
|
103
|
+
const config = parseTournamentValidatorConfig(rawConfig);
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
### React Hooks
|
|
107
|
+
|
|
108
|
+
```tsx
|
|
109
|
+
import { useStatusIndicator, useTokenSelector, usePagination } from "@provable-games/metagame-sdk/react";
|
|
110
|
+
|
|
111
|
+
// Auto-refreshing status indicator
|
|
112
|
+
function StatusBadge({ timestamps }) {
|
|
113
|
+
const status = useStatusIndicator(timestamps); // refreshes every second
|
|
114
|
+
return <span className={status.variant}>{status.label}</span>;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
// Token selector with search + pagination
|
|
118
|
+
function TokenPicker({ tokens }) {
|
|
119
|
+
const { search, setSearch, filteredTokens, select, pagination, getTokenProps } =
|
|
120
|
+
useTokenSelector({ tokens, tokenType: "erc20", pageSize: 10 });
|
|
121
|
+
|
|
122
|
+
return (
|
|
123
|
+
<div>
|
|
124
|
+
<input value={search} onChange={(e) => setSearch(e.target.value)} />
|
|
125
|
+
{filteredTokens.map((token) => (
|
|
126
|
+
<div key={token.address} {...getTokenProps(token)}>
|
|
127
|
+
{token.symbol}
|
|
128
|
+
</div>
|
|
129
|
+
))}
|
|
130
|
+
<button onClick={pagination.prev} disabled={!pagination.hasPrev}>Prev</button>
|
|
131
|
+
<button onClick={pagination.next} disabled={!pagination.hasNext}>Next</button>
|
|
132
|
+
</div>
|
|
133
|
+
);
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
// Entry qualification check
|
|
137
|
+
import { useEntryQualification } from "@provable-games/metagame-sdk/react";
|
|
138
|
+
|
|
139
|
+
function EntryGate({ requirement, userAddress }) {
|
|
140
|
+
const { qualified, reason, loading } = useEntryQualification({ requirement, userAddress });
|
|
141
|
+
if (loading) return <span>Checking...</span>;
|
|
142
|
+
return qualified ? <button>Enter</button> : <span>{reason}</span>;
|
|
143
|
+
}
|
|
144
|
+
```
|
|
145
|
+
|
|
146
|
+
### RPC Utilities
|
|
147
|
+
|
|
148
|
+
```ts
|
|
149
|
+
import { readExtensionConfig, readFeeAndPrizeExtensions } from "@provable-games/metagame-sdk/rpc";
|
|
150
|
+
|
|
151
|
+
// Read extension configuration from on-chain contracts
|
|
152
|
+
const config = await readExtensionConfig(provider, extensionAddress);
|
|
153
|
+
```
|
|
154
|
+
|
|
155
|
+
## API Reference
|
|
156
|
+
|
|
157
|
+
### Types
|
|
158
|
+
|
|
159
|
+
| Type | Fields |
|
|
160
|
+
|------|--------|
|
|
161
|
+
| `Token` | `address`, `name`, `symbol`, `tokenType`, `decimals?`, `logoUrl?` |
|
|
162
|
+
| `Prize` | `id`, `position`, `tokenAddress`, `tokenType`, `amount`, `sponsorAddress` |
|
|
163
|
+
| `EntryFee` | `tokenAddress`, `amount`, `creatorShare?`, `refundShare?` |
|
|
164
|
+
| `EntryRequirement` | `requirementType`, `tokenAddress?`, `entryLimit?` |
|
|
165
|
+
| `Participant` | `address`, `rank?`, `score?`, `name?`, `entryNumber?` |
|
|
166
|
+
| `StatusTimestamps` | `start`, `end`, `registrationStart?`, `registrationEnd?`, `submissionEnd?`, `completed?`, `unlocked?` |
|
|
167
|
+
| `StatusResult` | `label`, `variant`, `isActive`, `countdown` |
|
|
168
|
+
|
|
169
|
+
### Utilities
|
|
170
|
+
|
|
171
|
+
| Function | Description |
|
|
172
|
+
|----------|-------------|
|
|
173
|
+
| `indexAddress(address)` | Strip leading zeros from hex address |
|
|
174
|
+
| `padAddress(address)` | Pad address to 66 characters |
|
|
175
|
+
| `displayAddress(address)` | Truncate to `0x1234...abcd` |
|
|
176
|
+
| `bigintToHex(value)` | Convert bigint/number/string to hex |
|
|
177
|
+
| `formatNumber(num)` | Smart formatting with k/m suffixes |
|
|
178
|
+
| `formatPrizeAmount(num)` | Precision-aware prize display |
|
|
179
|
+
| `formatUsdValue(value)` | USD display with `<0.01` handling |
|
|
180
|
+
| `formatScore(num)` | Score display formatting |
|
|
181
|
+
| `formatTime(seconds)` | Human-readable duration ("2 Days", "3 Hours") |
|
|
182
|
+
| `getOrdinalSuffix(n)` | Returns "st", "nd", "rd", "th" |
|
|
183
|
+
| `calculatePayouts(places, weight)` | Weighted payout percentages summing to 100 |
|
|
184
|
+
| `calculateDistribution(positions, weight, ...)` | Basis-point distribution matching contract logic |
|
|
185
|
+
| `groupPrizesByToken(prizes)` | Group prizes by token address |
|
|
186
|
+
| `calculatePrizeValue(amount, decimals, price)` | Compute USD value of a prize |
|
|
187
|
+
| `computeStatus(timestamps, now?)` | Derive status label, variant, and countdown |
|
|
188
|
+
| `isBefore(date1, date2)` | Date comparison |
|
|
189
|
+
| `calculateEntryFeeBreakdown(fee, count)` | Break down entry fee into shares |
|
|
190
|
+
| `distributePool(pool, positions, weight)` | Distribute prize pool across positions |
|
|
191
|
+
| `aggregatePrizesByPosition(prizes)` | Group prizes by leaderboard position |
|
|
192
|
+
| `aggregatePrizesBySponsor(prizes)` | Group prizes by sponsor address |
|
|
193
|
+
| `filterClaimablePrizes(prizes, ...)` | Filter prizes claimable by a participant |
|
|
194
|
+
| `filterZeroPrizes(prizes)` | Remove zero-amount prizes |
|
|
195
|
+
| `parseNFTBulkInput(input)` | Parse bulk NFT token ID input |
|
|
196
|
+
| `getExtensionAddresses(chain)` | Get known extension contract addresses |
|
|
197
|
+
| `identifyExtensionType(address)` | Identify extension type from address |
|
|
198
|
+
| `parseTournamentValidatorConfig(config)` | Parse tournament validator config |
|
|
199
|
+
| `parseERC20BalanceValidatorConfig(config)` | Parse ERC20 balance validator config |
|
|
200
|
+
| `parseOpusTrovesValidatorConfig(config)` | Parse Opus Troves validator config |
|
|
201
|
+
| `evaluateQualification(requirement, state)` | Evaluate entry qualification |
|
|
202
|
+
| `buildQualificationProof(data)` | Build proof for on-chain verification |
|
|
203
|
+
| `buildEntryFeePrizes(config)` | Build prize list from entry fee config |
|
|
204
|
+
| `calculateTotalPrizeValueUSD(prizes, ...)` | Calculate total prize pool USD value |
|
|
205
|
+
| `calculatePaidPlaces(prizes)` | Count positions with non-zero prizes |
|
|
206
|
+
| `buildParticipationMap(registrations)` | Map addresses to participation counts |
|
|
207
|
+
| `buildWinMap(leaderboards)` | Map addresses to win counts |
|
|
208
|
+
| `resolveTournamentQualifications(input)` | Resolve tournament validator qualifications |
|
|
209
|
+
| `calculateOpusTrovesEntries(troves)` | Calculate entries from Opus Troves |
|
|
210
|
+
| `findBannableEntries(entries, ...)` | Find entries eligible for banning |
|
|
211
|
+
|
|
212
|
+
### React Hooks (`/react` entry)
|
|
213
|
+
|
|
214
|
+
| Hook | Description |
|
|
215
|
+
|------|-------------|
|
|
216
|
+
| `useDebounce(value, delay)` | Debounce any value |
|
|
217
|
+
| `usePagination({ totalItems, pageSize? })` | Page state with next/prev/reset |
|
|
218
|
+
| `useSearchFilter({ items, searchFields, debounceMs? })` | Filtered list with debounced search |
|
|
219
|
+
| `useTokenSelector({ tokens, tokenType?, pageSize? })` | Token search + selection + pagination |
|
|
220
|
+
| `useStatusIndicator(timestamps, refreshMs?)` | Auto-refreshing status computation |
|
|
221
|
+
| `useScoreTable({ participants, pageSize?, sortField? })` | Sorted, searchable, paginated score table |
|
|
222
|
+
| `usePrizeTable({ prizes, ... })` | Prize display with grouping and pagination |
|
|
223
|
+
| `useEntryFeePreview({ entryFee, ... })` | Entry fee breakdown preview |
|
|
224
|
+
| `useEntryQualification({ requirement, ... })` | Entry qualification check |
|
|
225
|
+
| `useExtensionQualification({ extension, ... })` | Extension-based qualification evaluation |
|
|
226
|
+
| `useOpusTrovesBannableEntries({ entries, ... })` | Identify bannable Opus Troves entries |
|
|
227
|
+
|
|
228
|
+
### RPC Utilities (`/rpc` entry)
|
|
229
|
+
|
|
230
|
+
| Function | Description |
|
|
231
|
+
|----------|-------------|
|
|
232
|
+
| `readExtensionConfig(provider, address)` | Read extension config from chain |
|
|
233
|
+
| `readFeeAndPrizeExtensions(provider, ...)` | Read fee and prize extension data |
|
|
234
|
+
|
|
235
|
+
## Development
|
|
236
|
+
|
|
237
|
+
```bash
|
|
238
|
+
npm install
|
|
239
|
+
npm run build # ESM + CJS to dist/
|
|
240
|
+
npm run typecheck # TypeScript validation
|
|
241
|
+
npm test # Unit tests
|
|
242
|
+
npm run dev # Watch mode
|
|
243
|
+
```
|
|
244
|
+
|
|
245
|
+
## Publishing
|
|
246
|
+
|
|
247
|
+
Automated via GitHub Actions — create a GitHub release to trigger npm publish.
|
|
248
|
+
|
|
249
|
+
## License
|
|
250
|
+
|
|
251
|
+
MIT
|
package/package.json
ADDED
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@provable-games/metagame-sdk",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Shared types, utilities, and headless hooks for Provable Games metagame SDKs",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "./dist/index.cjs",
|
|
7
|
+
"module": "./dist/index.js",
|
|
8
|
+
"types": "./dist/index.d.ts",
|
|
9
|
+
"exports": {
|
|
10
|
+
".": {
|
|
11
|
+
"import": {
|
|
12
|
+
"types": "./dist/index.d.ts",
|
|
13
|
+
"default": "./dist/index.js"
|
|
14
|
+
},
|
|
15
|
+
"require": {
|
|
16
|
+
"types": "./dist/index.d.cts",
|
|
17
|
+
"default": "./dist/index.cjs"
|
|
18
|
+
}
|
|
19
|
+
},
|
|
20
|
+
"./react": {
|
|
21
|
+
"import": {
|
|
22
|
+
"types": "./dist/react.d.ts",
|
|
23
|
+
"default": "./dist/react.js"
|
|
24
|
+
},
|
|
25
|
+
"require": {
|
|
26
|
+
"types": "./dist/react.d.cts",
|
|
27
|
+
"default": "./dist/react.cjs"
|
|
28
|
+
}
|
|
29
|
+
},
|
|
30
|
+
"./rpc": {
|
|
31
|
+
"import": {
|
|
32
|
+
"types": "./dist/rpc.d.ts",
|
|
33
|
+
"default": "./dist/rpc.js"
|
|
34
|
+
},
|
|
35
|
+
"require": {
|
|
36
|
+
"types": "./dist/rpc.d.cts",
|
|
37
|
+
"default": "./dist/rpc.cjs"
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
},
|
|
41
|
+
"files": [
|
|
42
|
+
"dist"
|
|
43
|
+
],
|
|
44
|
+
"sideEffects": false,
|
|
45
|
+
"scripts": {
|
|
46
|
+
"build": "tsup",
|
|
47
|
+
"dev": "tsup --watch",
|
|
48
|
+
"test": "vitest run",
|
|
49
|
+
"test:watch": "vitest",
|
|
50
|
+
"typecheck": "tsc --noEmit",
|
|
51
|
+
"clean": "rm -rf dist"
|
|
52
|
+
},
|
|
53
|
+
"keywords": [
|
|
54
|
+
"starknet",
|
|
55
|
+
"metagame",
|
|
56
|
+
"gaming",
|
|
57
|
+
"ui",
|
|
58
|
+
"hooks",
|
|
59
|
+
"sdk"
|
|
60
|
+
],
|
|
61
|
+
"author": "Provable Games",
|
|
62
|
+
"license": "MIT",
|
|
63
|
+
"publishConfig": {
|
|
64
|
+
"access": "public"
|
|
65
|
+
},
|
|
66
|
+
"devDependencies": {
|
|
67
|
+
"@testing-library/react": "^16.0.0",
|
|
68
|
+
"@types/node": "^22.0.0",
|
|
69
|
+
"@types/react": "^19.0.0",
|
|
70
|
+
"react": "^19.0.0",
|
|
71
|
+
"starknet": "^6.24.1",
|
|
72
|
+
"tsup": "^8.4.0",
|
|
73
|
+
"typescript": "^5.7.0",
|
|
74
|
+
"vitest": "^3.0.0"
|
|
75
|
+
},
|
|
76
|
+
"peerDependencies": {
|
|
77
|
+
"react": ">=18.0.0",
|
|
78
|
+
"starknet": ">=6.0.0"
|
|
79
|
+
},
|
|
80
|
+
"peerDependenciesMeta": {
|
|
81
|
+
"react": {
|
|
82
|
+
"optional": true
|
|
83
|
+
},
|
|
84
|
+
"starknet": {
|
|
85
|
+
"optional": true
|
|
86
|
+
}
|
|
87
|
+
},
|
|
88
|
+
"engines": {
|
|
89
|
+
"node": ">=22.0.0"
|
|
90
|
+
},
|
|
91
|
+
"dependencies": {
|
|
92
|
+
"@rollup/rollup-linux-arm64-gnu": "^4.59.0"
|
|
93
|
+
}
|
|
94
|
+
}
|