@zktable/coup-lite 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.
@@ -0,0 +1,328 @@
1
+ import * as _zktable_core from '@zktable/core';
2
+ import { PlayerId, PlayerView, Move, Match } from '@zktable/core';
3
+
4
+ /** 5 character ids, matching the `card_membership` circuit's small-int domain. */
5
+ declare const CHARACTERS: readonly [0, 1, 2, 3, 4];
6
+ declare const CHARACTER_NAMES: readonly ["Duke", "Assassin", "Captain", "Contessa", "Ambassador"];
7
+ declare const HAND_SIZE = 2;
8
+ declare const START_INFLUENCE = 2;
9
+ /** The player range supported end-to-end (defineGame AND the on-chain referee). */
10
+ declare const MIN_PLAYERS = 2;
11
+ declare const MAX_PLAYERS = 4;
12
+ /** Default seat count for the demos/runner (override with COUP_PLAYERS / opts.players). */
13
+ declare const N_PLAYERS = 2;
14
+ type Hand = [number, number];
15
+ type ClaimEntry = {
16
+ player: PlayerId;
17
+ character: number;
18
+ };
19
+ /** Per-match config, passed as `MatchOptions.config.coupLite`. */
20
+ type CoupLiteConfig = {
21
+ /**
22
+ * The REAL dealt hand per player. Required for the on-chain runner (the
23
+ * semi-honestly dealt, `card_membership`-provable hand); if omitted,
24
+ * `setup` derives a deterministic seeded hand from `MatchOptions.seed` so
25
+ * pure local tests don't need the prover pipeline.
26
+ */
27
+ handsByPlayer?: Record<PlayerId, Hand>;
28
+ };
29
+ /** Deterministic (seeded) hand of 2 DISTINCT characters, used only when no real dealt hand is supplied. */
30
+ declare function seededHand(seed: string): Hand;
31
+ declare const coupLite: _zktable_core.Game;
32
+
33
+ /** Deterministically pick one element of `items`, keyed by `seed`. Throws on an empty array. */
34
+ declare function pickSeeded<T>(items: readonly T[], seed: string): T;
35
+ /**
36
+ * Decide whether to challenge the standing claim (`view.legalMoves` already
37
+ * confirms a `challenge` is legal here) or let it stand and make a new claim
38
+ * instead. Suspicion is higher when this player's own hand does NOT contain
39
+ * the claimed character (the only legitimate signal available), lower when
40
+ * it does.
41
+ */
42
+ declare function chooseMove(view: PlayerView, seed: string): Move;
43
+
44
+ type CardProof = {
45
+ proof: Uint8Array;
46
+ publicInputs: Uint8Array;
47
+ claimed: bigint;
48
+ commitmentsHex: [string, string];
49
+ };
50
+ /** The seed-forced shuffled deck plus its `valid_shuffle` proof (M8.3). */
51
+ type ShuffleProof = {
52
+ proof: Uint8Array;
53
+ publicInputs: Uint8Array;
54
+ seedHex: string;
55
+ /** Card value (0-4) at each of the 15 deck positions — dealer-visible only. */
56
+ cards: number[];
57
+ /** Salt (decimal string) at each deck position. */
58
+ salts: string[];
59
+ /** Poseidon2(card, salt) leaf commitment (hex) at each deck position. */
60
+ leavesHex: string[];
61
+ };
62
+ type CardProverOptions = {
63
+ circuitDir?: string;
64
+ shuffleCircuitDir?: string;
65
+ graphToolsBin?: string;
66
+ nargoBin?: string;
67
+ bbBin?: string;
68
+ workDir?: string;
69
+ };
70
+ declare class CardProver {
71
+ private readonly circuitDir;
72
+ private readonly shuffleCircuitDir;
73
+ private readonly graphToolsBin;
74
+ private readonly nargoBin;
75
+ private readonly bbBin;
76
+ private readonly workDir;
77
+ private readonly env;
78
+ constructor(opts?: CardProverOptions);
79
+ private get targetDir();
80
+ private get bytecodePath();
81
+ private get vkPath();
82
+ private get shuffleTargetDir();
83
+ private get shuffleBytecodePath();
84
+ private get shuffleVkPath();
85
+ /** Compile the circuit once if `target/card_membership.json` is missing. */
86
+ ensureCompiled(): Promise<void>;
87
+ /** Generate the verification key once if `target/vk` is missing. */
88
+ ensureVk(): Promise<void>;
89
+ /** Compile `valid_shuffle` once if its bytecode is missing. */
90
+ ensureShuffleCompiled(): Promise<void>;
91
+ /** Generate the `valid_shuffle` verification key once if missing. */
92
+ ensureShuffleVk(): Promise<void>;
93
+ private writeVk;
94
+ /**
95
+ * Poseidon2(value, salt) via `zktable-graph commit` — used for the seed
96
+ * commit-reveal nonces (`hash2(nonce, 0)`), byte-identical to the referee.
97
+ */
98
+ commit(value: bigint, salt: bigint): Promise<string>;
99
+ /** The joint commit-reveal seed: left-fold of hash2 over the nonces (player-index order). */
100
+ seed(nonces: bigint[]): Promise<string>;
101
+ /**
102
+ * Full `valid_shuffle` pipeline (M8.3): derive the UNIQUE seed-forced
103
+ * permutation of the canonical 15-card deck via `zktable-graph
104
+ * shuffle-witness`, then `nargo execute` -> `bb prove`. The returned
105
+ * `cards`/`salts` are the dealer's private view (handed to each player
106
+ * off-chain); `leavesHex` + `proof` go on-chain via `submit_shuffle`.
107
+ */
108
+ proveShuffle(seedHex: string, salts: bigint[]): Promise<ShuffleProof>;
109
+ /**
110
+ * Full pipeline for one "prove-hold" claim: `zktable-graph card-witness` ->
111
+ * `nargo execute` -> `bb prove`. Runs entirely inside an isolated temp dir;
112
+ * never touches `card_membership/Prover.toml` or `card_membership/target/`
113
+ * (besides the shared, idempotent compiled bytecode).
114
+ */
115
+ proveHold(claimed: bigint, cards: [bigint, bigint], salts: [bigint, bigint], heldIndex: 0 | 1): Promise<CardProof>;
116
+ /** Off-chain check via `bb verify`, useful for sanity-checking a proof before spending a testnet tx. */
117
+ verifyLocally(proof: Uint8Array, publicInputs: Uint8Array): Promise<boolean>;
118
+ /** Off-chain `bb verify` of a `valid_shuffle` proof against ITS verification key. */
119
+ shuffleLocalVerify(proof: Uint8Array, publicInputs: Uint8Array): Promise<boolean>;
120
+ private bbVerify;
121
+ }
122
+
123
+ declare class RefereeCliError extends Error {
124
+ readonly args: string[];
125
+ readonly stderr: string;
126
+ readonly contractErrorCode: number | null;
127
+ readonly contractErrorName: string | null;
128
+ constructor(args: string[], stderr: string);
129
+ }
130
+ type Phase = 'SeedCommit' | 'SeedReveal' | 'Shuffle' | 'Playing' | 'AwaitingResponse' | 'AwaitingReveal' | 'Finished';
131
+ type ChainPlayer = {
132
+ seed_committed: boolean;
133
+ seed_revealed: boolean;
134
+ dealt: boolean;
135
+ commitments: string[];
136
+ dead: boolean[];
137
+ influence: number;
138
+ alive: boolean;
139
+ };
140
+ type ChainGameState = {
141
+ phase: Phase;
142
+ n_players: number;
143
+ seed: string | null;
144
+ deck: string[];
145
+ players: ChainPlayer[];
146
+ turn: number;
147
+ last_claim_player: number | null;
148
+ last_claim_character: number | null;
149
+ challenger: number | null;
150
+ target: number | null;
151
+ pending_loser: number | null;
152
+ outcome: number | null;
153
+ };
154
+ type CliRefereeClientOptions = {
155
+ network?: string;
156
+ source?: string;
157
+ stellarBin?: string;
158
+ /** Bounded retry for transient (non-contract) CLI/network failures. */
159
+ retries?: number;
160
+ retryDelayMs?: number;
161
+ /**
162
+ * CLI identity name per seat index. When a per-seat mutating call is made
163
+ * for seat i, the transaction is signed by `sourceForSeat[i]` (falling
164
+ * back to `source`) so the referee's `require_auth()` sees the seat
165
+ * owner's signature. Deploys always use `source`.
166
+ */
167
+ sourceForSeat?: Record<number, string>;
168
+ };
169
+ /** Strips a leading `0x` from a hex string (Bytes/BytesN CLI args are hex WITHOUT `0x`). */
170
+ declare function stripHexPrefix(hex: string): string;
171
+ /** Big-endian 32-byte hex encoding of a non-negative bigint (no `0x` prefix). */
172
+ declare function toBe32Hex(value: bigint): string;
173
+ declare class CliRefereeClient {
174
+ private readonly network;
175
+ private readonly source;
176
+ private readonly stellarBin;
177
+ private readonly retries;
178
+ private readonly retryDelayMs;
179
+ private readonly sourceForSeat;
180
+ constructor(opts?: CliRefereeClientOptions);
181
+ /** `stellar contract deploy` for the `card_membership` verifier. Returns the deployed contract id. */
182
+ deployVerifier(wasmPath: string, vkHex: string): Promise<string>;
183
+ /**
184
+ * `stellar contract deploy` for the coup referee. Returns the deployed
185
+ * contract id. `playerAddresses[i]` becomes seat i's owner — every seat-i
186
+ * action must then be signed by that address (require_auth). `verifier`
187
+ * holds the `card_membership` VK; `shuffleVerifier` the `valid_shuffle`
188
+ * VK (M8.3).
189
+ */
190
+ deployReferee(wasmPath: string, opts: {
191
+ verifier: string;
192
+ shuffleVerifier: string;
193
+ playerAddresses: string[];
194
+ }): Promise<string>;
195
+ commitSeedNonce(refereeId: string, opts: {
196
+ player: number;
197
+ commitmentHex: string;
198
+ }): Promise<string | null>;
199
+ revealSeedNonce(refereeId: string, opts: {
200
+ player: number;
201
+ nonceHex: string;
202
+ }): Promise<string | null>;
203
+ /** Submits the 15 shuffle-proven deck leaves + the `valid_shuffle` proof (permissionless — the proof is the trust anchor). */
204
+ submitShuffle(refereeId: string, opts: {
205
+ leavesHex: string[];
206
+ proofHex: string;
207
+ }): Promise<string | null>;
208
+ claim(refereeId: string, opts: {
209
+ player: number;
210
+ character: number;
211
+ }): Promise<string | null>;
212
+ challenge(refereeId: string, opts: {
213
+ challenger: number;
214
+ target: number;
215
+ }): Promise<string | null>;
216
+ proveHold(refereeId: string, opts: {
217
+ target: number;
218
+ claimed: number;
219
+ proofHex: string;
220
+ }): Promise<string | null>;
221
+ revealCard(refereeId: string, opts: {
222
+ player: number;
223
+ slot: number;
224
+ card: number;
225
+ saltHex: string;
226
+ }): Promise<string | null>;
227
+ gameState(refereeId: string): Promise<ChainGameState>;
228
+ private runDeploy;
229
+ /**
230
+ * Mutating call (`--send=yes`). When `seat` is given, signs with that
231
+ * seat's identity (`sourceForSeat[seat]`, falling back to `source`) so
232
+ * the referee's per-seat `require_auth()` is satisfied.
233
+ */
234
+ private invoke;
235
+ /** Read-only call (no `--send`). */
236
+ private runReadOnly;
237
+ private execWithRetry;
238
+ }
239
+
240
+ type Roster = Array<{
241
+ id: PlayerId;
242
+ }>;
243
+ /** Roster of `count` seats (defaults to the 2-player demo; the referee + engine support 2-4). */
244
+ declare function buildRoster(count?: number): Roster;
245
+ /** Creates a local `Match` mirroring what the on-chain referee will enforce. */
246
+ declare function createLocalMatch(roster: Roster, config: CoupLiteConfig, seed: string): Match;
247
+ type LocalStep = {
248
+ playerId: PlayerId;
249
+ move: {
250
+ type: 'claim';
251
+ character: number;
252
+ };
253
+ } | {
254
+ playerId: PlayerId;
255
+ move: {
256
+ type: 'challenge';
257
+ };
258
+ };
259
+ /** Advances the local match by exactly one move using the seeded strategy. */
260
+ declare function stepLocalMatch(match: Match, seed: string): LocalStep;
261
+ /** Plays a full LOCAL game (no chain) to a natural end, guarded against runaway loops. */
262
+ declare function playLocalMatch(roster: Roster, config: CoupLiteConfig, seed: string, opts?: {
263
+ maxSteps?: number;
264
+ }): {
265
+ match: Match;
266
+ steps: LocalStep[];
267
+ };
268
+ type PlayCoupLiteOptions = {
269
+ network?: string;
270
+ source?: string;
271
+ seed?: string;
272
+ verifierWasmPath?: string;
273
+ refereeWasmPath?: string;
274
+ vkPath?: string;
275
+ shuffleVkPath?: string;
276
+ log?: (line: string) => void;
277
+ /**
278
+ * When true, provisions one funded testnet identity per seat
279
+ * (`<source>-seat<i>`) and signs each seat's moves with its own key,
280
+ * demonstrating genuine multi-wallet play against require_auth().
281
+ * Default: every seat is owned and signed by `source`.
282
+ */
283
+ multiSeat?: boolean;
284
+ /** Seat count, 2-4 (referee-enforced). Defaults to 2. */
285
+ players?: number;
286
+ };
287
+ type Transcript = {
288
+ verifierContractId: string;
289
+ refereeContractId: string;
290
+ roster: Roster;
291
+ handsByPlayer: Record<PlayerId, Hand>;
292
+ moves: Array<{
293
+ kind: 'claim';
294
+ player: PlayerId;
295
+ playerIndex: number;
296
+ character: number;
297
+ } | {
298
+ kind: 'challenge';
299
+ challenger: PlayerId;
300
+ challengerIndex: number;
301
+ target: PlayerId;
302
+ targetIndex: number;
303
+ claim: ClaimEntry;
304
+ claimWasTrue: boolean;
305
+ loser: PlayerId;
306
+ }>;
307
+ finalState: ChainGameState;
308
+ outcome: PlayerId | null;
309
+ };
310
+ declare function ensureContractWasms(log: (line: string) => void): Promise<void>;
311
+ /**
312
+ * Plays a COMPLETE game of Coup-lite on live Stellar testnet: deploys the
313
+ * `card_membership` verifier and the coup-referee, deals both players'
314
+ * semi-honestly committed hands, then claims/challenges per the seeded
315
+ * strategy - resolving each challenge with a REAL `card_membership` proof
316
+ * (when the claim was true) or a direct `reveal_card` decline (when it was a
317
+ * bluff) - to a real on-chain outcome.
318
+ */
319
+ declare function playCoupLite(opts?: PlayCoupLiteOptions): Promise<Transcript>;
320
+
321
+ declare const DEFAULT_VERIFIER_WASM: string;
322
+ declare const DEFAULT_COUP_REFEREE_WASM: string;
323
+ declare const DEFAULT_VK_PATH: string;
324
+ declare const DEFAULT_SHUFFLE_VK_PATH: string;
325
+ /** `child_process` env with `~/.nargo/bin` and `~/.bb/bin` prepended to PATH. */
326
+ declare function toolEnv(): NodeJS.ProcessEnv;
327
+
328
+ export { CHARACTERS, CHARACTER_NAMES, type CardProof, CardProver, type CardProverOptions, type ChainGameState, type ChainPlayer, type ClaimEntry, CliRefereeClient, type CoupLiteConfig, DEFAULT_COUP_REFEREE_WASM, DEFAULT_SHUFFLE_VK_PATH, DEFAULT_VERIFIER_WASM, DEFAULT_VK_PATH, HAND_SIZE, type Hand, type LocalStep, MAX_PLAYERS, MIN_PLAYERS, N_PLAYERS, type Phase, type PlayCoupLiteOptions, RefereeCliError, type Roster, START_INFLUENCE, type ShuffleProof, type Transcript, buildRoster, chooseMove, coupLite, createLocalMatch, ensureContractWasms, pickSeeded, playCoupLite, playLocalMatch, seededHand, stepLocalMatch, stripHexPrefix, toBe32Hex, toolEnv };
@@ -0,0 +1,328 @@
1
+ import * as _zktable_core from '@zktable/core';
2
+ import { PlayerId, PlayerView, Move, Match } from '@zktable/core';
3
+
4
+ /** 5 character ids, matching the `card_membership` circuit's small-int domain. */
5
+ declare const CHARACTERS: readonly [0, 1, 2, 3, 4];
6
+ declare const CHARACTER_NAMES: readonly ["Duke", "Assassin", "Captain", "Contessa", "Ambassador"];
7
+ declare const HAND_SIZE = 2;
8
+ declare const START_INFLUENCE = 2;
9
+ /** The player range supported end-to-end (defineGame AND the on-chain referee). */
10
+ declare const MIN_PLAYERS = 2;
11
+ declare const MAX_PLAYERS = 4;
12
+ /** Default seat count for the demos/runner (override with COUP_PLAYERS / opts.players). */
13
+ declare const N_PLAYERS = 2;
14
+ type Hand = [number, number];
15
+ type ClaimEntry = {
16
+ player: PlayerId;
17
+ character: number;
18
+ };
19
+ /** Per-match config, passed as `MatchOptions.config.coupLite`. */
20
+ type CoupLiteConfig = {
21
+ /**
22
+ * The REAL dealt hand per player. Required for the on-chain runner (the
23
+ * semi-honestly dealt, `card_membership`-provable hand); if omitted,
24
+ * `setup` derives a deterministic seeded hand from `MatchOptions.seed` so
25
+ * pure local tests don't need the prover pipeline.
26
+ */
27
+ handsByPlayer?: Record<PlayerId, Hand>;
28
+ };
29
+ /** Deterministic (seeded) hand of 2 DISTINCT characters, used only when no real dealt hand is supplied. */
30
+ declare function seededHand(seed: string): Hand;
31
+ declare const coupLite: _zktable_core.Game;
32
+
33
+ /** Deterministically pick one element of `items`, keyed by `seed`. Throws on an empty array. */
34
+ declare function pickSeeded<T>(items: readonly T[], seed: string): T;
35
+ /**
36
+ * Decide whether to challenge the standing claim (`view.legalMoves` already
37
+ * confirms a `challenge` is legal here) or let it stand and make a new claim
38
+ * instead. Suspicion is higher when this player's own hand does NOT contain
39
+ * the claimed character (the only legitimate signal available), lower when
40
+ * it does.
41
+ */
42
+ declare function chooseMove(view: PlayerView, seed: string): Move;
43
+
44
+ type CardProof = {
45
+ proof: Uint8Array;
46
+ publicInputs: Uint8Array;
47
+ claimed: bigint;
48
+ commitmentsHex: [string, string];
49
+ };
50
+ /** The seed-forced shuffled deck plus its `valid_shuffle` proof (M8.3). */
51
+ type ShuffleProof = {
52
+ proof: Uint8Array;
53
+ publicInputs: Uint8Array;
54
+ seedHex: string;
55
+ /** Card value (0-4) at each of the 15 deck positions — dealer-visible only. */
56
+ cards: number[];
57
+ /** Salt (decimal string) at each deck position. */
58
+ salts: string[];
59
+ /** Poseidon2(card, salt) leaf commitment (hex) at each deck position. */
60
+ leavesHex: string[];
61
+ };
62
+ type CardProverOptions = {
63
+ circuitDir?: string;
64
+ shuffleCircuitDir?: string;
65
+ graphToolsBin?: string;
66
+ nargoBin?: string;
67
+ bbBin?: string;
68
+ workDir?: string;
69
+ };
70
+ declare class CardProver {
71
+ private readonly circuitDir;
72
+ private readonly shuffleCircuitDir;
73
+ private readonly graphToolsBin;
74
+ private readonly nargoBin;
75
+ private readonly bbBin;
76
+ private readonly workDir;
77
+ private readonly env;
78
+ constructor(opts?: CardProverOptions);
79
+ private get targetDir();
80
+ private get bytecodePath();
81
+ private get vkPath();
82
+ private get shuffleTargetDir();
83
+ private get shuffleBytecodePath();
84
+ private get shuffleVkPath();
85
+ /** Compile the circuit once if `target/card_membership.json` is missing. */
86
+ ensureCompiled(): Promise<void>;
87
+ /** Generate the verification key once if `target/vk` is missing. */
88
+ ensureVk(): Promise<void>;
89
+ /** Compile `valid_shuffle` once if its bytecode is missing. */
90
+ ensureShuffleCompiled(): Promise<void>;
91
+ /** Generate the `valid_shuffle` verification key once if missing. */
92
+ ensureShuffleVk(): Promise<void>;
93
+ private writeVk;
94
+ /**
95
+ * Poseidon2(value, salt) via `zktable-graph commit` — used for the seed
96
+ * commit-reveal nonces (`hash2(nonce, 0)`), byte-identical to the referee.
97
+ */
98
+ commit(value: bigint, salt: bigint): Promise<string>;
99
+ /** The joint commit-reveal seed: left-fold of hash2 over the nonces (player-index order). */
100
+ seed(nonces: bigint[]): Promise<string>;
101
+ /**
102
+ * Full `valid_shuffle` pipeline (M8.3): derive the UNIQUE seed-forced
103
+ * permutation of the canonical 15-card deck via `zktable-graph
104
+ * shuffle-witness`, then `nargo execute` -> `bb prove`. The returned
105
+ * `cards`/`salts` are the dealer's private view (handed to each player
106
+ * off-chain); `leavesHex` + `proof` go on-chain via `submit_shuffle`.
107
+ */
108
+ proveShuffle(seedHex: string, salts: bigint[]): Promise<ShuffleProof>;
109
+ /**
110
+ * Full pipeline for one "prove-hold" claim: `zktable-graph card-witness` ->
111
+ * `nargo execute` -> `bb prove`. Runs entirely inside an isolated temp dir;
112
+ * never touches `card_membership/Prover.toml` or `card_membership/target/`
113
+ * (besides the shared, idempotent compiled bytecode).
114
+ */
115
+ proveHold(claimed: bigint, cards: [bigint, bigint], salts: [bigint, bigint], heldIndex: 0 | 1): Promise<CardProof>;
116
+ /** Off-chain check via `bb verify`, useful for sanity-checking a proof before spending a testnet tx. */
117
+ verifyLocally(proof: Uint8Array, publicInputs: Uint8Array): Promise<boolean>;
118
+ /** Off-chain `bb verify` of a `valid_shuffle` proof against ITS verification key. */
119
+ shuffleLocalVerify(proof: Uint8Array, publicInputs: Uint8Array): Promise<boolean>;
120
+ private bbVerify;
121
+ }
122
+
123
+ declare class RefereeCliError extends Error {
124
+ readonly args: string[];
125
+ readonly stderr: string;
126
+ readonly contractErrorCode: number | null;
127
+ readonly contractErrorName: string | null;
128
+ constructor(args: string[], stderr: string);
129
+ }
130
+ type Phase = 'SeedCommit' | 'SeedReveal' | 'Shuffle' | 'Playing' | 'AwaitingResponse' | 'AwaitingReveal' | 'Finished';
131
+ type ChainPlayer = {
132
+ seed_committed: boolean;
133
+ seed_revealed: boolean;
134
+ dealt: boolean;
135
+ commitments: string[];
136
+ dead: boolean[];
137
+ influence: number;
138
+ alive: boolean;
139
+ };
140
+ type ChainGameState = {
141
+ phase: Phase;
142
+ n_players: number;
143
+ seed: string | null;
144
+ deck: string[];
145
+ players: ChainPlayer[];
146
+ turn: number;
147
+ last_claim_player: number | null;
148
+ last_claim_character: number | null;
149
+ challenger: number | null;
150
+ target: number | null;
151
+ pending_loser: number | null;
152
+ outcome: number | null;
153
+ };
154
+ type CliRefereeClientOptions = {
155
+ network?: string;
156
+ source?: string;
157
+ stellarBin?: string;
158
+ /** Bounded retry for transient (non-contract) CLI/network failures. */
159
+ retries?: number;
160
+ retryDelayMs?: number;
161
+ /**
162
+ * CLI identity name per seat index. When a per-seat mutating call is made
163
+ * for seat i, the transaction is signed by `sourceForSeat[i]` (falling
164
+ * back to `source`) so the referee's `require_auth()` sees the seat
165
+ * owner's signature. Deploys always use `source`.
166
+ */
167
+ sourceForSeat?: Record<number, string>;
168
+ };
169
+ /** Strips a leading `0x` from a hex string (Bytes/BytesN CLI args are hex WITHOUT `0x`). */
170
+ declare function stripHexPrefix(hex: string): string;
171
+ /** Big-endian 32-byte hex encoding of a non-negative bigint (no `0x` prefix). */
172
+ declare function toBe32Hex(value: bigint): string;
173
+ declare class CliRefereeClient {
174
+ private readonly network;
175
+ private readonly source;
176
+ private readonly stellarBin;
177
+ private readonly retries;
178
+ private readonly retryDelayMs;
179
+ private readonly sourceForSeat;
180
+ constructor(opts?: CliRefereeClientOptions);
181
+ /** `stellar contract deploy` for the `card_membership` verifier. Returns the deployed contract id. */
182
+ deployVerifier(wasmPath: string, vkHex: string): Promise<string>;
183
+ /**
184
+ * `stellar contract deploy` for the coup referee. Returns the deployed
185
+ * contract id. `playerAddresses[i]` becomes seat i's owner — every seat-i
186
+ * action must then be signed by that address (require_auth). `verifier`
187
+ * holds the `card_membership` VK; `shuffleVerifier` the `valid_shuffle`
188
+ * VK (M8.3).
189
+ */
190
+ deployReferee(wasmPath: string, opts: {
191
+ verifier: string;
192
+ shuffleVerifier: string;
193
+ playerAddresses: string[];
194
+ }): Promise<string>;
195
+ commitSeedNonce(refereeId: string, opts: {
196
+ player: number;
197
+ commitmentHex: string;
198
+ }): Promise<string | null>;
199
+ revealSeedNonce(refereeId: string, opts: {
200
+ player: number;
201
+ nonceHex: string;
202
+ }): Promise<string | null>;
203
+ /** Submits the 15 shuffle-proven deck leaves + the `valid_shuffle` proof (permissionless — the proof is the trust anchor). */
204
+ submitShuffle(refereeId: string, opts: {
205
+ leavesHex: string[];
206
+ proofHex: string;
207
+ }): Promise<string | null>;
208
+ claim(refereeId: string, opts: {
209
+ player: number;
210
+ character: number;
211
+ }): Promise<string | null>;
212
+ challenge(refereeId: string, opts: {
213
+ challenger: number;
214
+ target: number;
215
+ }): Promise<string | null>;
216
+ proveHold(refereeId: string, opts: {
217
+ target: number;
218
+ claimed: number;
219
+ proofHex: string;
220
+ }): Promise<string | null>;
221
+ revealCard(refereeId: string, opts: {
222
+ player: number;
223
+ slot: number;
224
+ card: number;
225
+ saltHex: string;
226
+ }): Promise<string | null>;
227
+ gameState(refereeId: string): Promise<ChainGameState>;
228
+ private runDeploy;
229
+ /**
230
+ * Mutating call (`--send=yes`). When `seat` is given, signs with that
231
+ * seat's identity (`sourceForSeat[seat]`, falling back to `source`) so
232
+ * the referee's per-seat `require_auth()` is satisfied.
233
+ */
234
+ private invoke;
235
+ /** Read-only call (no `--send`). */
236
+ private runReadOnly;
237
+ private execWithRetry;
238
+ }
239
+
240
+ type Roster = Array<{
241
+ id: PlayerId;
242
+ }>;
243
+ /** Roster of `count` seats (defaults to the 2-player demo; the referee + engine support 2-4). */
244
+ declare function buildRoster(count?: number): Roster;
245
+ /** Creates a local `Match` mirroring what the on-chain referee will enforce. */
246
+ declare function createLocalMatch(roster: Roster, config: CoupLiteConfig, seed: string): Match;
247
+ type LocalStep = {
248
+ playerId: PlayerId;
249
+ move: {
250
+ type: 'claim';
251
+ character: number;
252
+ };
253
+ } | {
254
+ playerId: PlayerId;
255
+ move: {
256
+ type: 'challenge';
257
+ };
258
+ };
259
+ /** Advances the local match by exactly one move using the seeded strategy. */
260
+ declare function stepLocalMatch(match: Match, seed: string): LocalStep;
261
+ /** Plays a full LOCAL game (no chain) to a natural end, guarded against runaway loops. */
262
+ declare function playLocalMatch(roster: Roster, config: CoupLiteConfig, seed: string, opts?: {
263
+ maxSteps?: number;
264
+ }): {
265
+ match: Match;
266
+ steps: LocalStep[];
267
+ };
268
+ type PlayCoupLiteOptions = {
269
+ network?: string;
270
+ source?: string;
271
+ seed?: string;
272
+ verifierWasmPath?: string;
273
+ refereeWasmPath?: string;
274
+ vkPath?: string;
275
+ shuffleVkPath?: string;
276
+ log?: (line: string) => void;
277
+ /**
278
+ * When true, provisions one funded testnet identity per seat
279
+ * (`<source>-seat<i>`) and signs each seat's moves with its own key,
280
+ * demonstrating genuine multi-wallet play against require_auth().
281
+ * Default: every seat is owned and signed by `source`.
282
+ */
283
+ multiSeat?: boolean;
284
+ /** Seat count, 2-4 (referee-enforced). Defaults to 2. */
285
+ players?: number;
286
+ };
287
+ type Transcript = {
288
+ verifierContractId: string;
289
+ refereeContractId: string;
290
+ roster: Roster;
291
+ handsByPlayer: Record<PlayerId, Hand>;
292
+ moves: Array<{
293
+ kind: 'claim';
294
+ player: PlayerId;
295
+ playerIndex: number;
296
+ character: number;
297
+ } | {
298
+ kind: 'challenge';
299
+ challenger: PlayerId;
300
+ challengerIndex: number;
301
+ target: PlayerId;
302
+ targetIndex: number;
303
+ claim: ClaimEntry;
304
+ claimWasTrue: boolean;
305
+ loser: PlayerId;
306
+ }>;
307
+ finalState: ChainGameState;
308
+ outcome: PlayerId | null;
309
+ };
310
+ declare function ensureContractWasms(log: (line: string) => void): Promise<void>;
311
+ /**
312
+ * Plays a COMPLETE game of Coup-lite on live Stellar testnet: deploys the
313
+ * `card_membership` verifier and the coup-referee, deals both players'
314
+ * semi-honestly committed hands, then claims/challenges per the seeded
315
+ * strategy - resolving each challenge with a REAL `card_membership` proof
316
+ * (when the claim was true) or a direct `reveal_card` decline (when it was a
317
+ * bluff) - to a real on-chain outcome.
318
+ */
319
+ declare function playCoupLite(opts?: PlayCoupLiteOptions): Promise<Transcript>;
320
+
321
+ declare const DEFAULT_VERIFIER_WASM: string;
322
+ declare const DEFAULT_COUP_REFEREE_WASM: string;
323
+ declare const DEFAULT_VK_PATH: string;
324
+ declare const DEFAULT_SHUFFLE_VK_PATH: string;
325
+ /** `child_process` env with `~/.nargo/bin` and `~/.bb/bin` prepended to PATH. */
326
+ declare function toolEnv(): NodeJS.ProcessEnv;
327
+
328
+ export { CHARACTERS, CHARACTER_NAMES, type CardProof, CardProver, type CardProverOptions, type ChainGameState, type ChainPlayer, type ClaimEntry, CliRefereeClient, type CoupLiteConfig, DEFAULT_COUP_REFEREE_WASM, DEFAULT_SHUFFLE_VK_PATH, DEFAULT_VERIFIER_WASM, DEFAULT_VK_PATH, HAND_SIZE, type Hand, type LocalStep, MAX_PLAYERS, MIN_PLAYERS, N_PLAYERS, type Phase, type PlayCoupLiteOptions, RefereeCliError, type Roster, START_INFLUENCE, type ShuffleProof, type Transcript, buildRoster, chooseMove, coupLite, createLocalMatch, ensureContractWasms, pickSeeded, playCoupLite, playLocalMatch, seededHand, stepLocalMatch, stripHexPrefix, toBe32Hex, toolEnv };