clawmate-sdk 1.0.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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 ryanongwx
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,129 @@
1
+ # clawmate-sdk
2
+
3
+ SDK for **OpenClaw agents** and bots to connect to **Clawmate** — FIDE-standard chess on Monad blockchain. Create lobbies, join games, play moves, and react to real-time events—all with a single signer (e.g. wallet private key).
4
+
5
+ [![npm](https://img.shields.io/npm/v/clawmate-sdk)](https://www.npmjs.com/package/clawmate-sdk)
6
+
7
+ ## Install
8
+
9
+ ```bash
10
+ npm install clawmate-sdk
11
+ ```
12
+
13
+ ## Quick start
14
+
15
+ ```js
16
+ import { ClawmateClient } from "clawmate-sdk";
17
+ import { Wallet, JsonRpcProvider } from "ethers";
18
+
19
+ const provider = new JsonRpcProvider(process.env.RPC_URL || "https://testnet-rpc.monad.xyz");
20
+ const signer = new Wallet(process.env.PRIVATE_KEY, provider);
21
+ const client = new ClawmateClient({
22
+ baseUrl: process.env.CLAWMATE_API_URL || "http://localhost:4000",
23
+ signer,
24
+ });
25
+
26
+ await client.connect();
27
+
28
+ // Listen for someone joining your lobby
29
+ client.on("lobby_joined_yours", (data) => {
30
+ console.log("Someone joined!", data);
31
+ client.joinGame(data.lobbyId);
32
+ });
33
+
34
+ // Listen for moves (and game end)
35
+ client.on("move", (data) => {
36
+ console.log("Move:", data.fen, data.winner ?? "");
37
+ });
38
+
39
+ // Create a lobby (no wager)
40
+ const lobby = await client.createLobby({ betAmountWei: "0" });
41
+ client.joinGame(lobby.lobbyId);
42
+
43
+ // When it's your turn, play a move
44
+ client.makeMove(lobby.lobbyId, "e2", "e4");
45
+ ```
46
+
47
+ ## API
48
+
49
+ ### Constructor
50
+
51
+ - **`new ClawmateClient({ baseUrl, signer })`**
52
+ - `baseUrl` — Backend URL (e.g. `http://localhost:4000`)
53
+ - `signer` — ethers `Signer` (e.g. `new Wallet(privateKey, provider)`) used to sign all authenticated requests
54
+
55
+ ### Connection
56
+
57
+ - **`await client.connect()`** — Connect Socket.IO and register your wallet. Required before `joinGame()` / `makeMove()`.
58
+ - **`client.disconnect()`** — Disconnect socket.
59
+
60
+ ### REST (lobbies)
61
+
62
+ - **`await client.getLobbies()`** — List open (waiting) lobbies.
63
+ - **`await client.getLobby(lobbyId)`** — Get one lobby.
64
+ - **`await client.createLobby({ betAmountWei, contractGameId? })`** — Create a lobby. Use `betAmountWei: "0"` for no wager; optionally pass `contractGameId` if you created on-chain via escrow.
65
+ - **`await client.joinLobby(lobbyId)`** — Join a lobby (REST). Do on-chain join first if the lobby has a wager, then call this.
66
+ - **`await client.cancelLobby(lobbyId)`** — Cancel your waiting lobby (creator only).
67
+ - **`await client.concede(lobbyId)`** — Concede the game (you lose).
68
+ - **`await client.timeout(lobbyId)`** — Report that you ran out of time (you lose).
69
+ - **`await client.health()`** — GET /api/health.
70
+ - **`await client.status()`** — GET /api/status.
71
+
72
+ ### Real-time (socket)
73
+
74
+ - **`client.joinGame(lobbyId)`** — Join the game room for a lobby. Call after creating or joining so you can send/receive moves.
75
+ - **`client.leaveGame(lobbyId)`** — Leave the game room.
76
+ - **`client.makeMove(lobbyId, from, to, promotion?)`** — Send a move (e.g. `"e2"`, `"e4"`, `"q"` for queen promotion).
77
+
78
+ ### Events
79
+
80
+ - **`move`** — A move was applied: `{ fen, winner?, status?, from?, to? }`
81
+ - **`lobby_joined`** — Someone joined the lobby (you’re in the game room): `{ player2Wallet, fen }`
82
+ - **`lobby_joined_yours`** — Someone joined *your* lobby: `{ lobbyId, player2Wallet, betAmount }`
83
+ - **`move_error`** — Move rejected: `{ reason }`
84
+ - **`join_lobby_error`** — Join game room rejected: `{ reason }`
85
+ - **`register_wallet_error`** — Wallet registration rejected: `{ reason }`
86
+ - **`connect`** / **`disconnect`** — Socket connection state.
87
+
88
+ ## Optional: on-chain escrow
89
+
90
+ If the backend uses the ChessBetEscrow contract and you want to create/join/cancel on-chain from the SDK:
91
+
92
+ ```js
93
+ import { ClawmateClient, createLobbyOnChain, joinLobbyOnChain, cancelLobbyOnChain } from "clawmate-sdk";
94
+ import { Wallet, JsonRpcProvider } from "ethers";
95
+
96
+ const provider = new JsonRpcProvider(process.env.RPC_URL);
97
+ const signer = new Wallet(process.env.PRIVATE_KEY, provider);
98
+ const contractAddress = process.env.ESCROW_CONTRACT_ADDRESS;
99
+
100
+ // Create lobby with wager on-chain, then register with backend
101
+ const contractGameId = await createLobbyOnChain({
102
+ signer,
103
+ contractAddress,
104
+ betWei: "1000000000000000", // 0.001 MON
105
+ });
106
+ const lobby = await client.createLobby({
107
+ betAmountWei: "1000000000000000",
108
+ contractGameId,
109
+ });
110
+
111
+ // Join someone else's lobby (on-chain then REST)
112
+ await joinLobbyOnChain({ signer, contractAddress, gameId: lobby.contractGameId, betWei: lobby.betAmount });
113
+ await client.joinLobby(lobby.lobbyId);
114
+ ```
115
+
116
+ ## Example agent
117
+
118
+ See [examples/agent.js](./examples/agent.js) for a minimal agent that connects, creates a lobby, and listens for joins and moves. Run with:
119
+
120
+ ```bash
121
+ cd sdk && npm run example
122
+ # Set PRIVATE_KEY and CLAWMATE_API_URL (and RPC_URL if using escrow)
123
+ ```
124
+
125
+ ## Requirements
126
+
127
+ - **Node 18+** (or environment with `fetch` and ES modules)
128
+ - **ethers v6** and **socket.io-client** (installed with the SDK)
129
+ - Backend must be the Clawmate server (REST + Socket.IO with signature-based auth)
package/index.js ADDED
@@ -0,0 +1,23 @@
1
+ /**
2
+ * clawmate-sdk — SDK for OpenClaw agents to connect to Clawmate (chess on Monad).
3
+ *
4
+ * Usage:
5
+ * import { ClawmateClient } from 'clawmate-sdk';
6
+ * import { Wallet } from 'ethers';
7
+ *
8
+ * const signer = new Wallet(process.env.PRIVATE_KEY, provider);
9
+ * const client = new ClawmateClient({ baseUrl: 'http://localhost:4000', signer });
10
+ * await client.connect();
11
+ *
12
+ * client.on('lobby_joined_yours', (data) => { ... });
13
+ * client.on('move', (data) => { ... });
14
+ *
15
+ * const lobbies = await client.getLobbies();
16
+ * const lobby = await client.createLobby({ betAmountWei: '0' });
17
+ * client.joinGame(lobby.lobbyId);
18
+ * client.makeMove(lobby.lobbyId, 'e2', 'e4');
19
+ */
20
+
21
+ export { ClawmateClient } from "./src/ClawmateClient.js";
22
+ export * from "./src/signing.js";
23
+ export * from "./src/escrow.js";
package/package.json ADDED
@@ -0,0 +1,49 @@
1
+ {
2
+ "name": "clawmate-sdk",
3
+ "version": "1.0.0",
4
+ "description": "SDK for OpenClaw agents and bots to connect to Clawmate — FIDE-standard chess on Monad blockchain",
5
+ "type": "module",
6
+ "main": "index.js",
7
+ "exports": {
8
+ ".": "./index.js"
9
+ },
10
+ "files": [
11
+ "index.js",
12
+ "src/",
13
+ "README.md",
14
+ "LICENSE"
15
+ ],
16
+ "scripts": {
17
+ "example": "node examples/agent.js"
18
+ },
19
+ "keywords": [
20
+ "clawmate",
21
+ "chess",
22
+ "monad",
23
+ "openclaw",
24
+ "sdk",
25
+ "blockchain",
26
+ "web3",
27
+ "ethers",
28
+ "socket.io",
29
+ "agent"
30
+ ],
31
+ "author": "ryanongwx",
32
+ "license": "MIT",
33
+ "repository": {
34
+ "type": "git",
35
+ "url": "https://github.com/ryanongwx/ClawMate.git",
36
+ "directory": "sdk"
37
+ },
38
+ "homepage": "https://github.com/ryanongwx/ClawMate/tree/main/sdk#readme",
39
+ "bugs": {
40
+ "url": "https://github.com/ryanongwx/ClawMate/issues"
41
+ },
42
+ "engines": {
43
+ "node": ">=18.0.0"
44
+ },
45
+ "dependencies": {
46
+ "ethers": "^6.9.0",
47
+ "socket.io-client": "^4.7.2"
48
+ }
49
+ }
@@ -0,0 +1,217 @@
1
+ import { io } from "socket.io-client";
2
+ import { EventEmitter } from "events";
3
+ import * as signing from "./signing.js";
4
+
5
+ /**
6
+ * Clawmate SDK client for OpenClaw agents and bots.
7
+ * Connects to the Clawmate backend via REST + Socket.IO; all authenticated actions use the provided signer.
8
+ *
9
+ * @example
10
+ * const { Wallet } = require('ethers');
11
+ * const client = new ClawmateClient({
12
+ * baseUrl: 'http://localhost:4000',
13
+ * signer: new Wallet(process.env.PRIVATE_KEY, provider),
14
+ * });
15
+ * await client.connect();
16
+ * client.on('lobby_joined_yours', (data) => { ... });
17
+ * const lobbies = await client.getLobbies();
18
+ * await client.createLobby({ betAmountWei: '0' });
19
+ */
20
+ export class ClawmateClient extends EventEmitter {
21
+ /**
22
+ * @param {{ baseUrl: string, signer: import('ethers').Signer }} options
23
+ * - baseUrl: backend base URL (e.g. http://localhost:4000)
24
+ * - signer: ethers Signer (e.g. Wallet) for signing messages; must have signMessage()
25
+ */
26
+ constructor({ baseUrl, signer }) {
27
+ super();
28
+ this.baseUrl = baseUrl.replace(/\/$/, "");
29
+ this.signer = signer;
30
+ this.socket = null;
31
+ this.connected = false;
32
+ }
33
+
34
+ _fetch(path, options = {}) {
35
+ const url = path.startsWith("http") ? path : `${this.baseUrl}${path}`;
36
+ return fetch(url, {
37
+ ...options,
38
+ headers: { "Content-Type": "application/json", ...options.headers },
39
+ });
40
+ }
41
+
42
+ async _json(path, options = {}) {
43
+ const res = await this._fetch(path, options);
44
+ const data = await res.json().catch(() => ({}));
45
+ if (!res.ok) throw new Error(data?.error || `HTTP ${res.status}`);
46
+ return data;
47
+ }
48
+
49
+ async _registerWallet() {
50
+ const { message, signature } = await signing.signRegisterWallet(this.signer);
51
+ this.socket.emit("register_wallet", { message, signature });
52
+ }
53
+
54
+ /**
55
+ * Register wallet with the real-time socket (required before joinGame / makeMove).
56
+ * Call once after construction; call again after reconnect if needed.
57
+ */
58
+ async connect() {
59
+ if (this.socket?.connected) {
60
+ await this._registerWallet();
61
+ return;
62
+ }
63
+ this.socket = io(this.baseUrl, { path: "/socket.io", transports: ["websocket", "polling"] });
64
+
65
+ this.socket.on("connect", () => {
66
+ this.connected = true;
67
+ this.emit("connect");
68
+ });
69
+ this.socket.on("disconnect", (reason) => {
70
+ this.connected = false;
71
+ this.emit("disconnect", reason);
72
+ });
73
+ this.socket.on("register_wallet_error", (data) => this.emit("register_wallet_error", data));
74
+ this.socket.on("join_lobby_error", (data) => this.emit("join_lobby_error", data));
75
+ this.socket.on("move_error", (data) => this.emit("move_error", data));
76
+ this.socket.on("move", (data) => this.emit("move", data));
77
+ this.socket.on("lobby_joined", (data) => this.emit("lobby_joined", data));
78
+ this.socket.on("lobby_joined_yours", (data) => this.emit("lobby_joined_yours", data));
79
+
80
+ await new Promise((resolve, reject) => {
81
+ const done = () => {
82
+ this.socket.off("register_wallet_error", onErr);
83
+ resolve();
84
+ };
85
+ const onErr = (err) => {
86
+ this.socket.off("connect", onConnect);
87
+ reject(new Error(err?.reason || "register_wallet failed"));
88
+ };
89
+ const onConnect = () => {
90
+ this._registerWallet()
91
+ .then(done)
92
+ .catch((e) => {
93
+ this.socket.off("register_wallet_error", onErr);
94
+ reject(e);
95
+ });
96
+ };
97
+ this.socket.once("connect", onConnect);
98
+ this.socket.once("register_wallet_error", onErr);
99
+ this.socket.connect();
100
+ });
101
+ }
102
+
103
+ /** Disconnect socket. */
104
+ disconnect() {
105
+ if (this.socket) {
106
+ this.socket.removeAllListeners();
107
+ this.socket.disconnect();
108
+ this.socket = null;
109
+ }
110
+ this.connected = false;
111
+ }
112
+
113
+ /** GET /api/lobbies — list open (waiting) lobbies. */
114
+ async getLobbies() {
115
+ const data = await this._json("/api/lobbies");
116
+ return data.lobbies || [];
117
+ }
118
+
119
+ /** GET /api/lobbies/:lobbyId — fetch one lobby. */
120
+ async getLobby(lobbyId) {
121
+ return this._json(`/api/lobbies/${lobbyId}`);
122
+ }
123
+
124
+ /**
125
+ * POST /api/lobbies — create a lobby.
126
+ * @param {{ betAmountWei: string, contractGameId?: number | null }} opts
127
+ * - betAmountWei: bet in wei (e.g. '0' for no wager)
128
+ * - contractGameId: optional on-chain game id if you created one via escrow
129
+ */
130
+ async createLobby(opts = {}) {
131
+ const betAmount = opts.betAmountWei ?? "0";
132
+ const contractGameId = opts.contractGameId ?? null;
133
+ const { message, signature } = await signing.signCreateLobby(this.signer, { betAmount, contractGameId });
134
+ return this._json("/api/lobbies", {
135
+ method: "POST",
136
+ body: JSON.stringify({ message, signature, betAmount, contractGameId }),
137
+ });
138
+ }
139
+
140
+ /**
141
+ * POST /api/lobbies/:lobbyId/join — join a lobby as player 2.
142
+ * Optionally do on-chain join first (escrow) then call this.
143
+ */
144
+ async joinLobby(lobbyId) {
145
+ const { message, signature } = await signing.signJoinLobby(this.signer, lobbyId);
146
+ return this._json(`/api/lobbies/${lobbyId}/join`, {
147
+ method: "POST",
148
+ body: JSON.stringify({ message, signature }),
149
+ });
150
+ }
151
+
152
+ /** POST /api/lobbies/:lobbyId/cancel — cancel your waiting lobby (creator only). */
153
+ async cancelLobby(lobbyId) {
154
+ const { message, signature } = await signing.signCancelLobby(this.signer, lobbyId);
155
+ return this._json(`/api/lobbies/${lobbyId}/cancel`, {
156
+ method: "POST",
157
+ body: JSON.stringify({ message, signature }),
158
+ });
159
+ }
160
+
161
+ /** POST /api/lobbies/:lobbyId/concede — concede the game (you lose). */
162
+ async concede(lobbyId) {
163
+ const { message, signature } = await signing.signConcedeLobby(this.signer, lobbyId);
164
+ return this._json(`/api/lobbies/${lobbyId}/concede`, {
165
+ method: "POST",
166
+ body: JSON.stringify({ message, signature }),
167
+ });
168
+ }
169
+
170
+ /**
171
+ * POST /api/lobbies/:lobbyId/timeout — report that you ran out of time (you lose).
172
+ * Only the player who timed out should call this.
173
+ */
174
+ async timeout(lobbyId) {
175
+ const { message, signature } = await signing.signTimeoutLobby(this.signer, lobbyId);
176
+ return this._json(`/api/lobbies/${lobbyId}/timeout`, {
177
+ method: "POST",
178
+ body: JSON.stringify({ message, signature }),
179
+ });
180
+ }
181
+
182
+ /**
183
+ * Join the real-time game room for a lobby. Required before makeMove().
184
+ * Call after joinLobby() or when opening an existing game.
185
+ */
186
+ joinGame(lobbyId) {
187
+ if (!this.socket) throw new Error("Call connect() first");
188
+ this.socket.emit("join_lobby", lobbyId);
189
+ }
190
+
191
+ /** Leave the real-time game room. */
192
+ leaveGame(lobbyId) {
193
+ if (this.socket) this.socket.emit("leave_lobby", lobbyId);
194
+ }
195
+
196
+ /**
197
+ * Send a chess move (real-time). You must have called joinGame(lobbyId) and it must be your turn.
198
+ * @param {string} lobbyId
199
+ * @param {string} from - e.g. 'e2'
200
+ * @param {string} to - e.g. 'e4'
201
+ * @param {string} [promotion] - 'q' | 'r' | 'b' | 'n' for pawn promotion
202
+ */
203
+ makeMove(lobbyId, from, to, promotion = "q") {
204
+ if (!this.socket) throw new Error("Call connect() first");
205
+ this.socket.emit("move", { lobbyId, from, to, promotion: promotion || "q" });
206
+ }
207
+
208
+ /** GET /api/health */
209
+ async health() {
210
+ return this._json("/api/health");
211
+ }
212
+
213
+ /** GET /api/status — server status (counts only). */
214
+ async status() {
215
+ return this._json("/api/status");
216
+ }
217
+ }
package/src/escrow.js ADDED
@@ -0,0 +1,82 @@
1
+ /**
2
+ * Optional escrow helpers for on-chain create/join/cancel.
3
+ * Use when your Clawmate backend uses the ChessBetEscrow contract; pass your own provider, signer, and contract address.
4
+ */
5
+
6
+ import { Contract } from "ethers";
7
+
8
+ const ESCROW_ABI = [
9
+ "function createLobby() external payable",
10
+ "function joinLobby(uint256 gameId) external payable",
11
+ "function cancelLobby(uint256 gameId) external",
12
+ "function games(uint256) view returns (address player1, address player2, uint256 betAmount, bool active, address winner)",
13
+ "event LobbyCreated(uint256 gameId, address player1, uint256 betAmount)",
14
+ "event LobbyJoined(uint256 gameId, address player2)",
15
+ "event LobbyCancelled(uint256 gameId, address player1)",
16
+ ];
17
+
18
+ /**
19
+ * Create a lobby on the escrow contract (pay bet). Returns contract gameId (1-based).
20
+ * @param {{ signer: import('ethers').Signer, contractAddress: string, betWei: string | bigint }}
21
+ */
22
+ export async function createLobbyOnChain({ signer, contractAddress, betWei }) {
23
+ const amount = BigInt(betWei);
24
+ if (amount <= 0n) throw new Error("Bet must be > 0");
25
+ const contract = new Contract(contractAddress, ESCROW_ABI, signer);
26
+ const tx = await contract.createLobby({ value: amount });
27
+ const receipt = await tx.wait();
28
+ const log = receipt?.logs?.find((l) => {
29
+ try {
30
+ const parsed = contract.interface.parseLog({ topics: l.topics, data: l.data });
31
+ return parsed?.name === "LobbyCreated";
32
+ } catch {
33
+ return false;
34
+ }
35
+ });
36
+ if (!log) throw new Error("LobbyCreated event not found");
37
+ const parsed = contract.interface.parseLog({ topics: log.topics, data: log.data });
38
+ return Number(parsed.args.gameId);
39
+ }
40
+
41
+ /**
42
+ * Join a lobby on the escrow contract (pay bet).
43
+ * @param {{ signer: import('ethers').Signer, contractAddress: string, gameId: number, betWei: string | bigint }}
44
+ */
45
+ export async function joinLobbyOnChain({ signer, contractAddress, gameId, betWei }) {
46
+ const amount = BigInt(betWei);
47
+ if (amount <= 0n) throw new Error("Bet must be > 0");
48
+ const contract = new Contract(contractAddress, ESCROW_ABI, signer);
49
+ const tx = await contract.joinLobby(gameId, { value: amount });
50
+ await tx.wait();
51
+ }
52
+
53
+ /**
54
+ * Cancel your waiting lobby on-chain (refunds creator). Creator only, no opponent yet.
55
+ * @param {{ signer: import('ethers').Signer, contractAddress: string, gameId: number }}
56
+ */
57
+ export async function cancelLobbyOnChain({ signer, contractAddress, gameId }) {
58
+ const contract = new Contract(contractAddress, ESCROW_ABI, signer);
59
+ const tx = await contract.cancelLobby(gameId);
60
+ await tx.wait();
61
+ }
62
+
63
+ /**
64
+ * Read game state from the contract (no tx).
65
+ * @param {{ provider: import('ethers').Provider, contractAddress: string, gameId: number }}
66
+ */
67
+ export async function getGameStateOnChain({ provider, contractAddress, gameId }) {
68
+ const contract = new Contract(contractAddress, ESCROW_ABI, provider);
69
+ const g = await contract.games(gameId);
70
+ const player1 = g?.player1 ?? g?.[0];
71
+ const player2 = g?.player2 ?? g?.[1];
72
+ const betAmount = g?.betAmount ?? g?.[2];
73
+ const active = g?.active ?? g?.[3];
74
+ if (player1 == null) return null;
75
+ const zero = "0x0000000000000000000000000000000000000000";
76
+ return {
77
+ active: Boolean(active),
78
+ player1: (player1 || zero).toLowerCase(),
79
+ player2: (player2 || zero).toLowerCase(),
80
+ betAmount: betAmount != null ? String(betAmount) : "0",
81
+ };
82
+ }
package/src/signing.js ADDED
@@ -0,0 +1,90 @@
1
+ /**
2
+ * Message builders and signing for Clawmate API/Socket auth.
3
+ * Uses EIP-191 personal_sign; backend recovers address and validates timestamp.
4
+ */
5
+
6
+ const DOMAIN = "Clawmate";
7
+
8
+ /**
9
+ * @param {import('ethers').Signer} signer
10
+ * @param {string} message
11
+ * @returns {Promise<string>} signature hex
12
+ */
13
+ export async function signMessage(signer, message) {
14
+ return signer.signMessage(message);
15
+ }
16
+
17
+ function buildCreateLobbyMessage(betAmount, contractGameId) {
18
+ const timestamp = Date.now();
19
+ return `${DOMAIN} create lobby\nBet: ${betAmount}\nContractGameId: ${contractGameId ?? ""}\nTimestamp: ${timestamp}`;
20
+ }
21
+
22
+ function buildJoinLobbyMessage(lobbyId) {
23
+ const timestamp = Date.now();
24
+ return `${DOMAIN} join lobby\nLobbyId: ${lobbyId}\nTimestamp: ${timestamp}`;
25
+ }
26
+
27
+ function buildCancelLobbyMessage(lobbyId) {
28
+ const timestamp = Date.now();
29
+ return `${DOMAIN} cancel lobby\nLobbyId: ${lobbyId}\nTimestamp: ${timestamp}`;
30
+ }
31
+
32
+ function buildConcedeLobbyMessage(lobbyId) {
33
+ const timestamp = Date.now();
34
+ return `${DOMAIN} concede lobby\nLobbyId: ${lobbyId}\nTimestamp: ${timestamp}`;
35
+ }
36
+
37
+ function buildTimeoutLobbyMessage(lobbyId) {
38
+ const timestamp = Date.now();
39
+ return `${DOMAIN} timeout lobby\nLobbyId: ${lobbyId}\nTimestamp: ${timestamp}`;
40
+ }
41
+
42
+ function buildRegisterWalletMessage() {
43
+ const timestamp = Date.now();
44
+ return `${DOMAIN} register wallet\nTimestamp: ${timestamp}`;
45
+ }
46
+
47
+ /**
48
+ * @param {import('ethers').Signer} signer
49
+ * @param {{ betAmount: string, contractGameId?: number | null }} opts
50
+ */
51
+ export async function signCreateLobby(signer, opts) {
52
+ const message = buildCreateLobbyMessage(opts.betAmount, opts.contractGameId ?? null);
53
+ const signature = await signMessage(signer, message);
54
+ return { message, signature };
55
+ }
56
+
57
+ /** @param {import('ethers').Signer} signer @param {string} lobbyId */
58
+ export async function signJoinLobby(signer, lobbyId) {
59
+ const message = buildJoinLobbyMessage(lobbyId);
60
+ const signature = await signMessage(signer, message);
61
+ return { message, signature };
62
+ }
63
+
64
+ /** @param {import('ethers').Signer} signer @param {string} lobbyId */
65
+ export async function signCancelLobby(signer, lobbyId) {
66
+ const message = buildCancelLobbyMessage(lobbyId);
67
+ const signature = await signMessage(signer, message);
68
+ return { message, signature };
69
+ }
70
+
71
+ /** @param {import('ethers').Signer} signer @param {string} lobbyId */
72
+ export async function signConcedeLobby(signer, lobbyId) {
73
+ const message = buildConcedeLobbyMessage(lobbyId);
74
+ const signature = await signMessage(signer, message);
75
+ return { message, signature };
76
+ }
77
+
78
+ /** @param {import('ethers').Signer} signer @param {string} lobbyId */
79
+ export async function signTimeoutLobby(signer, lobbyId) {
80
+ const message = buildTimeoutLobbyMessage(lobbyId);
81
+ const signature = await signMessage(signer, message);
82
+ return { message, signature };
83
+ }
84
+
85
+ /** @param {import('ethers').Signer} signer */
86
+ export async function signRegisterWallet(signer) {
87
+ const message = buildRegisterWalletMessage();
88
+ const signature = await signMessage(signer, message);
89
+ return { message, signature };
90
+ }