@pockit/challenge-protocol
v0.0.18
Published
A protocol for matchmaking and game governance on EVM based blockchains.
Maintainers
Readme
Pockit Challenge Protocol
N-player escrow with a declared governor. Players see the governor before joining, stake ETH, and the governor resolves the game. Winners get paid out automatically.
How It Works
- Someone creates a game, picking a governor and a stake amount
- Players see the governor address and join by matching the stake
- Governor calls
startGameto lock the lobby - Governor calls
resolveGame(losers[])— contract pays out winners and governor automatically
Install
npm install @pockit/challenge-protocolUsage
SDK
import { EscrowClient } from '@pockit/challenge-protocol';
const escrow = new EscrowClient({
privateKey: process.env.PRIVATE_KEY,
contractAddress: '0x...',
});
// Write ops (have retry logic)
await escrow.createGame(stakeAmount, maxPlayers, whitelist);
await escrow.joinGame(gameId, stakeAmount);
await escrow.startGame(gameId);
await escrow.resolveGame(gameId, [loserAddress], governorFeePercent);
// Query games
const openGames = await escrow.getGames({ state: 'open' });
const myGames = await escrow.getGames({ governor: myAddress });
const myStarted = await escrow.getGames({ governor: myAddress, state: 'started' });
const pastGames = await escrow.getGames({ governor: myAddress, state: 'resolved', limit: 20n });
// Watch for changes (polls every 10s, fires callback only when results change)
const stop = escrow.watchGames({ state: 'open', interval: 5000 }, (games) => {
console.log('Open games updated:', games);
});
stop(); // cancel polling
// Owner ops
await escrow.setHouseFee(5); // 5%
await escrow.withdraw();
// Raw contract access for anything else
const contract = escrow.getContract();Governor (Event-Driven)
For automated governors that need to react to on-chain events:
const governor = escrow.asGovernor({
fee: 2, // 2% governor fee
onPlayerJoined: async (gameId, game, { player }) => {
if (game.players.length === 2 && game.state === 0n) {
await governor.startGame(gameId);
}
},
gameLoop: async (gameId, game, resolve) => {
const loser = game.players[Math.random() < 0.5 ? 0 : 1];
await resolve([loser]); // atomic: marks losers + pays out winners
},
});
governor.start(); // polls for events, recovers unresolved games on startupDirect Contract (ethers/viem)
await contract.createGame(governorAddress, stakeAmount, maxPlayers, whitelist, { value: stakeAmount });
await contract.joinGame(gameId, { value: stakeAmount });Contract
Lifecycle
| State | Value | Description | |-------|-------|-------------| | Open | 0 | Lobby open. Players join/forfeit. | | Started | 1 | Lobby locked. Game in progress. | | Resolved | 2 | Losers marked. Winners and governor paid out. |
Functions
| Function | Who | What |
|----------|-----|------|
| createGame(governor, stake, maxPlayers, whitelist) | Anyone | Creates game, caller joins as first player |
| joinGame(gameId) | Anyone | Match stake to join |
| forfeitGame(gameId) | Player | Pre-start only, immediate refund |
| startGame(gameId) | Governor | Locks lobby |
| resolveGame(gameId, losers[], govFee%) | Governor | Atomic resolution + auto-payout |
| getGame(gameId) | Anyone | Full game state |
| getGames(governor, inclResolved, inclOngoing, inclOpen, offset, limit) | Anyone | Filtered game list (pass address(0) for all governors) |
| setHouseFee(percentage) | Owner | Set house fee (default 0) |
| withdraw() | Owner | Withdraw accumulated house fees |
Events
GameCreated(gameId, creator, stakeAmount)
PlayerJoined(gameId, player)
PlayerForfeited(gameId, player)
GameStarted(gameId)
GameResolved(gameId, winners[], losers[])Prize Distribution
- House fee → accumulated (owner withdraws separately)
- Governor fee → sent to governor on resolve
- Remainder → split equally among winners (sent on resolve)
- No winners → governor gets remainder
Deployed Contracts
| Network | Address |
|---------|---------|
| ETH Mainnet | 0xcbeb8fbbc2ca9afb908381f24ec4cea493b9482c |
| Sepolia | 0xA84Ba779A4Caeb2f5Cee0aE83e9f8D28298F1977 |
License
MIT
