Files
wikideck/shared/protocol.ts
T
2026-10-19 12:16:49 -04:00

47 lines
2.4 KiB
TypeScript

export const POW = { memorySize: 16384, iterations: 2, parallelism: 1, hashLength: 32 } as const;
export const DIFFICULTY = 1; // One leading hex zero: ~16 attempts, intentionally approachable demo difficulty.
export const GENESIS = '0'.repeat(64);
export type Article = {
pageId: number; title: string; extract: string; image?: string; url: string;
revisionId: number; source: 'live' | 'offline'; category: string;
};
export type Challenge = {
height: number; previousHash: string; seed: string; article: Article;
difficulty: number; createdAt: number;
};
export type Block = Challenge & {
hash: string; nonce: number; owner: string; publicKey: string; signature: string;
};
export type Transfer = {
cardHash: string; from: string; to: string; sequence: number;
publicKey: string; signature: string;
};
export type ChainState = { blocks: Block[]; transfers: Transfer[] };
export function proofMessage(c: Challenge, owner: string, nonce: number): string {
// Fixed-order tuple; the complete immutable article snapshot is committed by the proof.
return JSON.stringify(['wikideck:pow:v1', c.height, c.previousHash, c.seed,
c.article.pageId, c.article.revisionId, c.article.title, c.article.extract,
c.article.image ?? '', c.article.url, c.article.source, c.article.category,
c.difficulty, c.createdAt, owner, nonce]);
}
export function claimMessage(c: Challenge, owner: string, nonce: number, hash: string): string {
return JSON.stringify(['wikideck:claim:v1', proofMessage(c, owner, nonce), hash]);
}
export function transferMessage(t: Pick<Transfer, 'cardHash' | 'from' | 'to' | 'sequence'>): string {
return JSON.stringify(['wikideck:transfer:v1', t.cardHash, t.from, t.to, t.sequence]);
}
export function ownerOf(state: ChainState, hash: string): string | undefined {
let owner = state.blocks.find(b => b.hash === hash)?.owner;
for (const t of state.transfers) if (t.cardHash === hash) owner = t.to;
return owner;
}
export function rarity(hash: string): 'Legendary' | 'Epic' | 'Rare' | 'Common' {
const roll = parseInt(hash.slice(-4), 16) % 100;
return roll < 3 ? 'Legendary' : roll < 15 ? 'Epic' : roll < 40 ? 'Rare' : 'Common';
}
export function stats(hash: string) {
return { power: 30 + parseInt(hash.slice(8, 10), 16) % 70,
defense: 30 + parseInt(hash.slice(10, 12), 16) % 70,
knowledge: 30 + parseInt(hash.slice(12, 14), 16) % 70 };
}