@swarm-tips/client 0.1.1 → 0.2.1

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.
Files changed (35) hide show
  1. package/README.md +5 -0
  2. package/SBOM.spdx.json +5 -5
  3. package/dist/coordination-game/api.d.ts +276 -0
  4. package/dist/coordination-game/api.js +146 -0
  5. package/dist/coordination-game/evm/abi.d.ts +409 -0
  6. package/dist/coordination-game/evm/abi.js +371 -0
  7. package/dist/coordination-game/evm/cross-chain.d.ts +33 -0
  8. package/dist/coordination-game/evm/cross-chain.js +76 -0
  9. package/dist/coordination-game/evm/index.d.ts +6 -0
  10. package/dist/coordination-game/evm/index.js +6 -0
  11. package/dist/coordination-game/evm/rules.d.ts +112 -0
  12. package/dist/coordination-game/evm/rules.js +128 -0
  13. package/dist/coordination-game/evm/session-funding.d.ts +111 -0
  14. package/dist/coordination-game/evm/session-funding.js +407 -0
  15. package/dist/coordination-game/evm/session.d.ts +58 -0
  16. package/dist/coordination-game/evm/session.js +165 -0
  17. package/dist/coordination-game/evm/transactions.d.ts +137 -0
  18. package/dist/coordination-game/evm/transactions.js +211 -0
  19. package/dist/coordination-game/index.d.ts +1 -0
  20. package/dist/coordination-game/index.js +1 -0
  21. package/dist/coordination-game/protocol.d.ts +58 -0
  22. package/dist/coordination-game/protocol.js +120 -0
  23. package/dist/coordination-game/solana.d.ts +100 -0
  24. package/dist/coordination-game/solana.js +309 -0
  25. package/dist/errors.d.ts +19 -0
  26. package/dist/errors.js +20 -0
  27. package/dist/http.d.ts +21 -0
  28. package/dist/http.js +101 -0
  29. package/dist/index.d.ts +3 -1
  30. package/dist/index.js +2 -1
  31. package/dist/shillbot/api.d.ts +76 -0
  32. package/dist/shillbot/api.js +114 -0
  33. package/dist/swarm/index.d.ts +84 -0
  34. package/dist/swarm/index.js +71 -0
  35. package/package.json +26 -1
@@ -0,0 +1,371 @@
1
+ /**
2
+ * Minimal CrossChainGame ABI for client-side EVM-leg calls (viem
3
+ * encodeFunctionData). Vendored from the Foundry artifact
4
+ * (swarm-tips-repo/evm/out/CrossChainGame.sol/CrossChainGame.json) — only the
5
+ * functions the cross-chain match UX needs: fund (createMatch), refund
6
+ * (refundTimeout/refundNoCert), and the read views (stakeWei, CHAIN_TAG,
7
+ * matches). Settle is permissionless and operator/harness-driven, so it is not
8
+ * vendored here. Regenerate if the contract ABI changes.
9
+ */
10
+ export const CROSS_CHAIN_GAME_ABI = [
11
+ {
12
+ "type": "function",
13
+ "name": "CHAIN_TAG",
14
+ "inputs": [],
15
+ "outputs": [
16
+ {
17
+ "name": "",
18
+ "type": "bytes32",
19
+ "internalType": "bytes32"
20
+ }
21
+ ],
22
+ "stateMutability": "view"
23
+ },
24
+ {
25
+ "type": "function",
26
+ "name": "createMatch",
27
+ "inputs": [
28
+ {
29
+ "name": "matchId",
30
+ "type": "bytes32",
31
+ "internalType": "bytes32"
32
+ },
33
+ {
34
+ "name": "sessionKey",
35
+ "type": "address",
36
+ "internalType": "address"
37
+ },
38
+ {
39
+ "name": "counterSessionKey",
40
+ "type": "address",
41
+ "internalType": "address"
42
+ },
43
+ {
44
+ "name": "playerIsP1",
45
+ "type": "bool",
46
+ "internalType": "bool"
47
+ },
48
+ {
49
+ "name": "fundDeadline",
50
+ "type": "uint64",
51
+ "internalType": "uint64"
52
+ },
53
+ {
54
+ "name": "matchDeadline",
55
+ "type": "uint64",
56
+ "internalType": "uint64"
57
+ },
58
+ {
59
+ "name": "operatorSig",
60
+ "type": "bytes",
61
+ "internalType": "bytes"
62
+ }
63
+ ],
64
+ "outputs": [],
65
+ "stateMutability": "payable"
66
+ },
67
+ {
68
+ "type": "function",
69
+ "name": "matches",
70
+ "inputs": [
71
+ {
72
+ "name": "",
73
+ "type": "bytes32",
74
+ "internalType": "bytes32"
75
+ }
76
+ ],
77
+ "outputs": [
78
+ {
79
+ "name": "status",
80
+ "type": "uint8",
81
+ "internalType": "enum CrossChainGame.Status"
82
+ },
83
+ {
84
+ "name": "player",
85
+ "type": "address",
86
+ "internalType": "address"
87
+ },
88
+ {
89
+ "name": "sessionKey",
90
+ "type": "address",
91
+ "internalType": "address"
92
+ },
93
+ {
94
+ "name": "counterSessionKey",
95
+ "type": "address",
96
+ "internalType": "address"
97
+ },
98
+ {
99
+ "name": "playerIsP1",
100
+ "type": "bool",
101
+ "internalType": "bool"
102
+ },
103
+ {
104
+ "name": "localEquivocated",
105
+ "type": "bool",
106
+ "internalType": "bool"
107
+ },
108
+ {
109
+ "name": "counterEquivocated",
110
+ "type": "bool",
111
+ "internalType": "bool"
112
+ },
113
+ {
114
+ "name": "stakeWei",
115
+ "type": "uint128",
116
+ "internalType": "uint128"
117
+ },
118
+ {
119
+ "name": "trancheWei",
120
+ "type": "uint128",
121
+ "internalType": "uint128"
122
+ },
123
+ {
124
+ "name": "fundDeadline",
125
+ "type": "uint64",
126
+ "internalType": "uint64"
127
+ },
128
+ {
129
+ "name": "matchDeadline",
130
+ "type": "uint64",
131
+ "internalType": "uint64"
132
+ },
133
+ {
134
+ "name": "lockedAt",
135
+ "type": "uint64",
136
+ "internalType": "uint64"
137
+ },
138
+ {
139
+ "name": "claimWindowEnd",
140
+ "type": "uint64",
141
+ "internalType": "uint64"
142
+ },
143
+ {
144
+ "name": "matchLiveDigest",
145
+ "type": "bytes32",
146
+ "internalType": "bytes32"
147
+ },
148
+ {
149
+ "name": "bestStepCount",
150
+ "type": "uint8",
151
+ "internalType": "uint8"
152
+ },
153
+ {
154
+ "name": "bestOutcomeKind",
155
+ "type": "uint8",
156
+ "internalType": "uint8"
157
+ }
158
+ ],
159
+ "stateMutability": "view"
160
+ },
161
+ {
162
+ "type": "function",
163
+ "name": "refundNoCert",
164
+ "inputs": [
165
+ {
166
+ "name": "matchId",
167
+ "type": "bytes32",
168
+ "internalType": "bytes32"
169
+ }
170
+ ],
171
+ "outputs": [],
172
+ "stateMutability": "nonpayable"
173
+ },
174
+ {
175
+ "type": "function",
176
+ "name": "refundTimeout",
177
+ "inputs": [
178
+ {
179
+ "name": "matchId",
180
+ "type": "bytes32",
181
+ "internalType": "bytes32"
182
+ }
183
+ ],
184
+ "outputs": [],
185
+ "stateMutability": "nonpayable"
186
+ },
187
+ {
188
+ "type": "function",
189
+ "name": "stakeWei",
190
+ "inputs": [],
191
+ "outputs": [
192
+ {
193
+ "name": "",
194
+ "type": "uint128",
195
+ "internalType": "uint128"
196
+ }
197
+ ],
198
+ "stateMutability": "view"
199
+ }
200
+ ];
201
+ /**
202
+ * Minimal CoordinationGame ABI for the same-chain EVM-vs-EVM /play flow. Vendored
203
+ * from swarm-tips-repo/evm/out/CoordinationGame.sol/CoordinationGame.json — only
204
+ * the gameplay functions the browser builds + sends itself (commitGuess,
205
+ * revealGuess). createGame/joinGame come pre-encoded from game-api (as
206
+ * create_call/join_call), so they aren't needed for client-side encoding, but
207
+ * are vendored for completeness/verification. Regenerate if the ABI changes.
208
+ */
209
+ export const COORDINATION_GAME_ABI = [
210
+ {
211
+ "type": "function",
212
+ "name": "createGame",
213
+ "inputs": [
214
+ { "name": "gameId", "type": "bytes32", "internalType": "bytes32" },
215
+ { "name": "matchupCommitment", "type": "bytes32", "internalType": "bytes32" },
216
+ { "name": "operatorSig", "type": "bytes", "internalType": "bytes" },
217
+ { "name": "player", "type": "address", "internalType": "address" }
218
+ ],
219
+ "outputs": [],
220
+ "stateMutability": "payable"
221
+ },
222
+ {
223
+ "type": "function",
224
+ "name": "joinGame",
225
+ "inputs": [
226
+ { "name": "gameId", "type": "bytes32", "internalType": "bytes32" },
227
+ { "name": "player", "type": "address", "internalType": "address" }
228
+ ],
229
+ "outputs": [],
230
+ "stateMutability": "payable"
231
+ },
232
+ {
233
+ // Wallet-as-player: the wallet calls this ONCE to register its gas-only
234
+ // session key AND fund it (msg.value = gas + stake buffer forwarded to the
235
+ // sessionKey EOA) — the single popup that opens a session.
236
+ "type": "function",
237
+ "name": "openSession",
238
+ "inputs": [
239
+ { "name": "sessionKey", "type": "address", "internalType": "address" },
240
+ { "name": "expiry", "type": "uint64", "internalType": "uint64" }
241
+ ],
242
+ "outputs": [],
243
+ "stateMutability": "payable"
244
+ },
245
+ {
246
+ // Opens the session AND escrows the stake in ONE transaction: `gasAmount`
247
+ // is forwarded to the session EOA, the remainder is credited to
248
+ // withdrawable[msg.sender] for createGame/joinGame to debit at value 0.
249
+ // This is the entry point the browser uses; plain openSession above is kept
250
+ // for grok-agent (own EOA, no session key) and any un-migrated client.
251
+ "type": "function",
252
+ "name": "openSessionAndDeposit",
253
+ "inputs": [
254
+ { "name": "sessionKey", "type": "address", "internalType": "address" },
255
+ { "name": "expiry", "type": "uint64", "internalType": "uint64" },
256
+ { "name": "gasAmount", "type": "uint256", "internalType": "uint256" }
257
+ ],
258
+ "outputs": [],
259
+ "stateMutability": "payable"
260
+ },
261
+ {
262
+ // Credit the caller's own escrow balance. Not used by the browser (which
263
+ // uses openSessionAndDeposit), vendored so the ledger surface is complete.
264
+ "type": "function",
265
+ "name": "deposit",
266
+ "inputs": [],
267
+ "outputs": [],
268
+ "stateMutability": "payable"
269
+ },
270
+ {
271
+ // Permissionless push of `to`'s credited winnings to `to` — the session key
272
+ // calls this post-resolve so the wallet is paid with no wallet popup.
273
+ "type": "function",
274
+ "name": "withdrawFor",
275
+ "inputs": [{ "name": "to", "type": "address", "internalType": "address" }],
276
+ "outputs": [],
277
+ "stateMutability": "nonpayable"
278
+ },
279
+ {
280
+ "type": "function",
281
+ "name": "commitGuess",
282
+ "inputs": [
283
+ { "name": "gameId", "type": "bytes32", "internalType": "bytes32" },
284
+ { "name": "commitment", "type": "bytes32", "internalType": "bytes32" }
285
+ ],
286
+ "outputs": [],
287
+ "stateMutability": "nonpayable"
288
+ },
289
+ {
290
+ "type": "function",
291
+ "name": "revealGuess",
292
+ "inputs": [
293
+ { "name": "gameId", "type": "bytes32", "internalType": "bytes32" },
294
+ { "name": "r", "type": "bytes32", "internalType": "bytes32" },
295
+ { "name": "rMatchup", "type": "bytes32", "internalType": "bytes32" }
296
+ ],
297
+ "outputs": [],
298
+ "stateMutability": "nonpayable"
299
+ },
300
+ {
301
+ "type": "function",
302
+ "name": "withdrawable",
303
+ "inputs": [{ "name": "", "type": "address", "internalType": "address" }],
304
+ "outputs": [{ "name": "", "type": "uint256", "internalType": "uint256" }],
305
+ "stateMutability": "view"
306
+ },
307
+ {
308
+ // The on-chain session registration for a wallet. `_actsFor` requires
309
+ // `sessionKey == msg.sender && block.timestamp < expiry`, so this is the
310
+ // only way a client can tell whether its session is still VALID as opposed
311
+ // to merely funded. Its absence from this ABI is why setupEvmSessionIfNeeded
312
+ // inferred registration from the session key's balance — and why an expired
313
+ // session locked a wallet out with no way to recover.
314
+ "type": "function",
315
+ "name": "sessions",
316
+ "inputs": [{ "name": "", "type": "address", "internalType": "address" }],
317
+ "outputs": [
318
+ { "name": "sessionKey", "type": "address", "internalType": "address" },
319
+ { "name": "expiry", "type": "uint64", "internalType": "uint64" }
320
+ ],
321
+ "stateMutability": "view"
322
+ },
323
+ {
324
+ "type": "function",
325
+ "name": "withdraw",
326
+ "inputs": [],
327
+ "outputs": [],
328
+ "stateMutability": "nonpayable"
329
+ },
330
+ {
331
+ // Auto-getter for `mapping(bytes32 => Game) public games`. Read to learn
332
+ // whether the matchup is already bound (matchupType != 255) so the reveal
333
+ // knows if it is the FIRST revealer (bind with rMatchup) or the SECOND
334
+ // (must pass the zero sentinel), and to compute the timeout deadline from
335
+ // the stage anchor + window. Field order matches CoordinationGame.sol Game.
336
+ "type": "function",
337
+ "name": "games",
338
+ "inputs": [{ "name": "", "type": "bytes32", "internalType": "bytes32" }],
339
+ "outputs": [
340
+ { "name": "status", "type": "uint8", "internalType": "uint8" },
341
+ { "name": "player1", "type": "address", "internalType": "address" },
342
+ { "name": "player2", "type": "address", "internalType": "address" },
343
+ { "name": "p1Guess", "type": "uint8", "internalType": "uint8" },
344
+ { "name": "p2Guess", "type": "uint8", "internalType": "uint8" },
345
+ { "name": "firstCommitter", "type": "uint8", "internalType": "uint8" },
346
+ { "name": "matchupType", "type": "uint8", "internalType": "uint8" },
347
+ { "name": "stakeWei", "type": "uint128", "internalType": "uint128" },
348
+ { "name": "matchupCommitment", "type": "bytes32", "internalType": "bytes32" },
349
+ { "name": "p1Commit", "type": "bytes32", "internalType": "bytes32" },
350
+ { "name": "p2Commit", "type": "bytes32", "internalType": "bytes32" },
351
+ { "name": "activatedAt", "type": "uint64", "internalType": "uint64" },
352
+ { "name": "firstCommitAt", "type": "uint64", "internalType": "uint64" },
353
+ { "name": "bothCommitAt", "type": "uint64", "internalType": "uint64" },
354
+ { "name": "createdAt", "type": "uint64", "internalType": "uint64" },
355
+ { "name": "commitWindowSecs", "type": "uint32", "internalType": "uint32" },
356
+ { "name": "revealWindowSecs", "type": "uint32", "internalType": "uint32" }
357
+ ],
358
+ "stateMutability": "view"
359
+ },
360
+ {
361
+ // Permissionless timeout resolution: when the opponent stalls past the stage
362
+ // window (Active/Committing → commitWindow, Revealing → revealWindow), anyone
363
+ // can crank this to resolve the game so the honest player's stake isn't
364
+ // stranded. Reverts (Elapsed check) if the window hasn't passed yet.
365
+ "type": "function",
366
+ "name": "resolveTimeout",
367
+ "inputs": [{ "name": "gameId", "type": "bytes32", "internalType": "bytes32" }],
368
+ "outputs": [],
369
+ "stateMutability": "nonpayable"
370
+ }
371
+ ];
@@ -0,0 +1,33 @@
1
+ import { type Hex } from "viem";
2
+ import type { CoordinationGameApiClient, XMatchPayload } from "../api.js";
3
+ export declare function unpadAddress(word: string): Hex;
4
+ export interface CrossChainMatchResult {
5
+ match: XMatchPayload;
6
+ handle: string | undefined;
7
+ }
8
+ export declare function findCrossChainMatch(api: CoordinationGameApiClient, input: {
9
+ wallet: string;
10
+ sessionKeyAddress: string;
11
+ tournamentId: number;
12
+ chain: string;
13
+ }, options?: {
14
+ pollMs?: number;
15
+ maxPolls?: number;
16
+ sleep?: (ms: number) => Promise<void>;
17
+ }): Promise<CrossChainMatchResult>;
18
+ export interface EvmFundCall {
19
+ to: Hex;
20
+ data: Hex;
21
+ value: bigint;
22
+ }
23
+ export interface EvmFundPin {
24
+ contract: Hex;
25
+ stakeWei: bigint;
26
+ }
27
+ export declare function buildEvmFundCall(match: XMatchPayload, pin: EvmFundPin): EvmFundCall;
28
+ export interface FundTargetReader {
29
+ stakeWei(contract: Hex): Promise<bigint>;
30
+ matchStatus(contract: Hex, matchId: Hex): Promise<number>;
31
+ }
32
+ export declare function verifyEvmFundTarget(match: XMatchPayload, pin: EvmFundPin, readers: FundTargetReader[]): Promise<void>;
33
+ export declare function rpcFundTargetReaders(rpcUrls: string[]): FundTargetReader[];
@@ -0,0 +1,76 @@
1
+ import { createPublicClient, encodeFunctionData, http } from "viem";
2
+ import { CROSS_CHAIN_GAME_ABI } from "./abi.js";
3
+ export function unpadAddress(word) {
4
+ const hex = word.startsWith("0x") ? word.slice(2) : word;
5
+ return `0x${hex.slice(-40)}`;
6
+ }
7
+ export async function findCrossChainMatch(api, input, options = {}) {
8
+ const joined = await api.joinCrossChainQueue({
9
+ wallet: input.wallet,
10
+ chain: input.chain,
11
+ session_key: input.sessionKeyAddress,
12
+ tournament_id: input.tournamentId,
13
+ });
14
+ const handle = joined.poll_handle;
15
+ if (joined.status === "matched" && joined.match)
16
+ return { match: joined.match, handle };
17
+ const sleep = options.sleep ?? ((ms) => new Promise((resolve) => globalThis.setTimeout(resolve, ms)));
18
+ for (let index = 0; index < (options.maxPolls ?? 60); index += 1) {
19
+ await sleep(options.pollMs ?? 4_000);
20
+ const next = await api.crossChainStatus(handle ? { handle } : { wallet: input.wallet });
21
+ if (next.status === "matched" && next.match)
22
+ return { match: next.match, handle };
23
+ }
24
+ throw new Error("no opponent found before timeout");
25
+ }
26
+ export function buildEvmFundCall(match, pin) {
27
+ const to = unpadAddress(match.leg_b.contract);
28
+ if (to.toLowerCase() !== pin.contract.toLowerCase()) {
29
+ throw new Error(`relay leg_b.contract ${to} != pinned CrossChainGame ${pin.contract}`);
30
+ }
31
+ const value = BigInt(match.leg_b.stake_base_units);
32
+ if (value !== pin.stakeWei) {
33
+ throw new Error(`relay leg_b.stake ${value} != pinned stake ${pin.stakeWei}`);
34
+ }
35
+ const data = encodeFunctionData({
36
+ abi: CROSS_CHAIN_GAME_ABI,
37
+ functionName: "createMatch",
38
+ args: [
39
+ match.match_id,
40
+ unpadAddress(match.leg_b.session_key),
41
+ unpadAddress(match.leg_a.session_key),
42
+ match.a_is_p1 === 0,
43
+ BigInt(match.fund_deadline),
44
+ BigInt(match.match_deadline),
45
+ match.create_match_operator_sig,
46
+ ],
47
+ });
48
+ return { to, data, value };
49
+ }
50
+ export async function verifyEvmFundTarget(match, pin, readers) {
51
+ if (readers.length < 2)
52
+ throw new Error(`need >=2 RPCs for a pre-fund quorum read, got ${readers.length}`);
53
+ const matchId = match.match_id;
54
+ const stakes = await Promise.all(readers.map((reader) => reader.stakeWei(pin.contract)));
55
+ const statuses = await Promise.all(readers.map((reader) => reader.matchStatus(pin.contract, matchId)));
56
+ if (!stakes.every((stake) => stake === stakes[0]))
57
+ throw new Error(`RPCs disagree on stakeWei (${stakes.join(", ")}); refusing to fund`);
58
+ if (!statuses.every((status) => status === statuses[0]))
59
+ throw new Error(`RPCs disagree on match status (${statuses.join(", ")}); refusing to fund`);
60
+ if (stakes[0] !== pin.stakeWei)
61
+ throw new Error(`on-chain stakeWei ${stakes[0]} != pinned ${pin.stakeWei}; refusing to fund`);
62
+ if (statuses[0] !== 0)
63
+ throw new Error(`matchId ${matchId} already has status ${statuses[0]}; not a fundable slot`);
64
+ }
65
+ export function rpcFundTargetReaders(rpcUrls) {
66
+ return rpcUrls.map((url) => {
67
+ const client = createPublicClient({ transport: http(url) });
68
+ return {
69
+ stakeWei: (contract) => client.readContract({ address: contract, abi: CROSS_CHAIN_GAME_ABI, functionName: "stakeWei" }),
70
+ matchStatus: async (contract, matchId) => {
71
+ const result = await client.readContract({ address: contract, abi: CROSS_CHAIN_GAME_ABI, functionName: "matches", args: [matchId] });
72
+ return Number(result[0]);
73
+ },
74
+ };
75
+ });
76
+ }
@@ -0,0 +1,6 @@
1
+ export * from "./abi.js";
2
+ export * from "./rules.js";
3
+ export * from "./session.js";
4
+ export * from "./session-funding.js";
5
+ export * from "./transactions.js";
6
+ export * from "./cross-chain.js";
@@ -0,0 +1,6 @@
1
+ export * from "./abi.js";
2
+ export * from "./rules.js";
3
+ export * from "./session.js";
4
+ export * from "./session-funding.js";
5
+ export * from "./transactions.js";
6
+ export * from "./cross-chain.js";
@@ -0,0 +1,112 @@
1
+ /**
2
+ * Pure decision rules for the same-chain EVM game, extracted from `useEvmGame`
3
+ * so the two paths that previously shipped bugs — reveal ordering and timeout
4
+ * recovery — are locked by deterministic unit tests instead of relying on a
5
+ * live e2e that only ever exercises the happy path.
6
+ *
7
+ * Mirrors CoordinationGame.sol exactly; keep in lockstep with the contract.
8
+ */
9
+ import type { Hex } from "viem";
10
+ /** games().matchupType before any reveal binds it. */
11
+ export declare const MATCHUP_UNSET = 255;
12
+ /** games().pNGuess before that player reveals. */
13
+ export declare const UNREVEALED = 255;
14
+ /** The 32-byte zero sentinel the SECOND revealer must pass for rMatchup. */
15
+ export declare const ZERO_BYTES32: Hex;
16
+ /**
17
+ * The `rMatchup` argument for revealGuess, chosen from ON-CHAIN state (never the
18
+ * creator/joiner role). The FIRST revealer binds the matchup with the real
19
+ * r_matchup; the SECOND — matchup already bound on-chain — MUST pass the zero
20
+ * sentinel, or the contract reverts CertMismatch (the reveal-order bug). Returns
21
+ * null only when we're the first revealer but don't yet hold r_matchup (both
22
+ * players haven't committed), which the caller surfaces as "not ready".
23
+ */
24
+ export declare function revealMatchupArg(onChainMatchupType: number, rMatchup: Hex | null): Hex | null;
25
+ export declare const STATUS_ACTIVE = 2;
26
+ export declare const STATUS_COMMITTING = 3;
27
+ export declare const STATUS_REVEALING = 4;
28
+ export interface EvmGameTimes {
29
+ status: number;
30
+ activatedAt: bigint;
31
+ firstCommitAt: bigint;
32
+ bothCommitAt: bigint;
33
+ commitWindowSecs: bigint;
34
+ revealWindowSecs: bigint;
35
+ }
36
+ /**
37
+ * The unix second at which an opponent's stall becomes resolvable via
38
+ * resolveTimeout, or null when the status has no timeout (not Active/Committing/
39
+ * Revealing). Anchors mirror CoordinationGame.resolveTimeout: Active/Committing
40
+ * time out on the commit window (from join / first-commit); Revealing on the
41
+ * reveal window (from both-commit).
42
+ */
43
+ export declare function timeoutDeadline(g: EvmGameTimes): bigint | null;
44
+ /** Whether the opponent's stall is resolvable at `nowSecs`. */
45
+ export declare function timeoutClaimableAt(g: EvmGameTimes, nowSecs: bigint): boolean;
46
+ /** The on-chain Game fields needed to resume a game after a page reload. */
47
+ export interface EvmResumeGame {
48
+ status: number;
49
+ player1: string;
50
+ player2: string;
51
+ p1Commit: Hex;
52
+ p2Commit: Hex;
53
+ p1Guess: number;
54
+ p2Guess: number;
55
+ }
56
+ export interface EvmResumeState {
57
+ phase: "chat" | "committed";
58
+ bothCommitted: boolean;
59
+ }
60
+ /**
61
+ * Is this match on the chain the wallet is currently connected to?
62
+ *
63
+ * The resume path takes the contract address from the MATCH record but reads it
64
+ * through the CONNECTED chain's client. Those are independent, and when they
65
+ * disagree it queries an address that means nothing on that chain. Observed on
66
+ * both mainnet e2e runs (2026-08-13): connected to eip155:1, it read the Base
67
+ * Sepolia proxy 0x4fbbceb9… and logged `"games" returned no data ("0x")`.
68
+ *
69
+ * Exact string equality on the CAIP-2 id, deliberately: no prefix matching (so
70
+ * `eip155:1` can never satisfy chain 11), and a missing/malformed/non-EVM value
71
+ * returns false rather than defaulting to allow — the boundary rejects rather
72
+ * than guesses.
73
+ */
74
+ export declare function matchIsOnConnectedChain(matchChain: string | undefined, connectedChainId: number): boolean;
75
+ /**
76
+ * The local phase to resume into after a page reload, derived purely from the
77
+ * on-chain game + our session address — EVM's `/evmplay` has no gameId in the
78
+ * URL, so a refresh otherwise strands a committed player who can no longer
79
+ * reveal. Returns null (stay idle) when there is nothing to resume: no
80
+ * in-progress game (Pending/Resolved/None), not a participant, or we already
81
+ * revealed (the game will resolve and auto-sweep on its own).
82
+ */
83
+ export declare function deriveResumeState(g: EvmResumeGame, myAddr: string): EvmResumeState | null;
84
+ /** The resolved-game fields needed to describe an outcome to the player. */
85
+ export interface EvmResolvedGame {
86
+ player1: string;
87
+ player2: string;
88
+ p1Guess: number;
89
+ p2Guess: number;
90
+ matchupType: number;
91
+ firstCommitter: number;
92
+ }
93
+ /**
94
+ * Turn a resolved on-chain EVM game into the same player-relative shape the
95
+ * Solana side uses, so `outcomeLabel`/`shareText` can describe it identically.
96
+ *
97
+ * The EVM end-screen said only "Revealed — the game settles on-chain and your
98
+ * winnings return to your wallet automatically": no win/loss, no explorer link,
99
+ * no share. Solana has told players the outcome all along. The payoff matrix is
100
+ * the same on both chains (homogeneous by design), so the difference was purely
101
+ * that nobody built the panel — not a missing concept.
102
+ *
103
+ * Returns null when the game has no bound matchup or an unrevealed guess, i.e.
104
+ * there is not yet a truthful outcome to state.
105
+ */
106
+ export declare function evmGameResult(g: EvmResolvedGame, myAddr: string): {
107
+ myGuess: 0 | 1;
108
+ opponentGuess: 0 | 1;
109
+ matchupType: 0 | 1;
110
+ iAmP1: boolean;
111
+ firstCommitter: number;
112
+ } | null;