@swarm-tips/client 0.1.1 → 0.2.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.
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 +84 -0
  24. package/dist/coordination-game/solana.js +262 -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 +72 -0
  32. package/dist/shillbot/api.js +91 -0
  33. package/dist/swarm/index.d.ts +84 -0
  34. package/dist/swarm/index.js +71 -0
  35. package/package.json +26 -1
package/README.md CHANGED
@@ -4,7 +4,12 @@ One typed package for Swarm Tips client-side integrations. The root export is
4
4
  deliberately lightweight; import only the capability you need:
5
5
 
6
6
  - `@swarm-tips/client/shillbot`
7
+ - `@swarm-tips/client/shillbot/api`
7
8
  - `@swarm-tips/client/coordination-game`
9
+ - `@swarm-tips/client/coordination-game/solana`
10
+ - `@swarm-tips/client/coordination-game/evm`
11
+ - `@swarm-tips/client/coordination-game/api`
12
+ - `@swarm-tips/client/swarm`
8
13
  - `@swarm-tips/client/evm`
9
14
  - `@swarm-tips/client/evm/testing` (test-only helpers)
10
15
  - `@swarm-tips/client/inbox`
package/SBOM.spdx.json CHANGED
@@ -2,10 +2,10 @@
2
2
  "spdxVersion": "SPDX-2.3",
3
3
  "dataLicense": "CC0-1.0",
4
4
  "SPDXID": "SPDXRef-DOCUMENT",
5
- "name": "@swarm-tips/client-0.1.1",
6
- "documentNamespace": "https://swarm.tips/sbom/client/0.1.1",
5
+ "name": "@swarm-tips/client-0.2.0",
6
+ "documentNamespace": "https://swarm.tips/sbom/client/0.2.0",
7
7
  "creationInfo": {
8
- "created": "2026-09-08T02:44:57.447Z",
8
+ "created": "2026-09-08T06:28:09.085Z",
9
9
  "creators": [
10
10
  "Tool: sdk/client/scripts/generate-sbom.mjs",
11
11
  "Organization: Swarm Tips"
@@ -15,8 +15,8 @@
15
15
  {
16
16
  "name": "@swarm-tips/client",
17
17
  "SPDXID": "SPDXRef-Package",
18
- "versionInfo": "0.1.1",
19
- "downloadLocation": "https://www.npmjs.com/package/@swarm-tips/client/v/0.1.1",
18
+ "versionInfo": "0.2.0",
19
+ "downloadLocation": "https://www.npmjs.com/package/@swarm-tips/client/v/0.2.0",
20
20
  "filesAnalyzed": false,
21
21
  "licenseConcluded": "MIT",
22
22
  "licenseDeclared": "MIT"
@@ -0,0 +1,276 @@
1
+ import { HttpClient, type HttpClientOptions } from "../http.js";
2
+ export interface ChallengeRequest {
3
+ wallet: string;
4
+ }
5
+ export interface ChallengeResponse {
6
+ nonce: string;
7
+ }
8
+ export interface VerifyRequest {
9
+ wallet: string;
10
+ nonce: string;
11
+ signature: string;
12
+ }
13
+ export interface VerifyResponse {
14
+ token: string;
15
+ }
16
+ export interface SessionAuthRequest {
17
+ wallet: string;
18
+ tx_signature: string;
19
+ nonce: string;
20
+ }
21
+ export interface SessionAuthResponse {
22
+ token: string;
23
+ }
24
+ export interface QueueRequest {
25
+ tournament_id: number;
26
+ is_ai?: boolean;
27
+ }
28
+ export type QueueJoinResponse = {
29
+ matched: true;
30
+ session_id: string;
31
+ } | {
32
+ matched: false;
33
+ };
34
+ export interface OkResponse {
35
+ ok: boolean;
36
+ }
37
+ export interface XLegPayload {
38
+ chain: string;
39
+ contract: string;
40
+ player: string;
41
+ session_key: string;
42
+ stake_base_units: string;
43
+ tranche_base_units: string;
44
+ }
45
+ export interface XMatchPayload {
46
+ match_id: string;
47
+ tournament_id: number;
48
+ match_live_digest: string;
49
+ operator_signature: string;
50
+ create_match_operator_sig: string;
51
+ fund_deadline: number;
52
+ match_deadline: number;
53
+ claim_window_secs: number;
54
+ a_is_p1: number;
55
+ leg_a: XLegPayload;
56
+ leg_b: XLegPayload;
57
+ }
58
+ export interface XQueueResponse {
59
+ status: "waiting" | "matched";
60
+ match?: XMatchPayload;
61
+ poll_handle?: string;
62
+ }
63
+ export interface XGameplayView {
64
+ match_id: string;
65
+ step2_checkpoint?: unknown;
66
+ step2_checkpoint_digest?: string;
67
+ r_matchup?: string;
68
+ terminal_checkpoint?: unknown;
69
+ terminal_checkpoint_digest?: string;
70
+ }
71
+ export interface EvmCall {
72
+ player: string;
73
+ to: string;
74
+ data: string;
75
+ value_wei: string;
76
+ }
77
+ export interface EvmMatchPayload {
78
+ game_id: string;
79
+ matchup_commitment: string;
80
+ chain: string;
81
+ contract: string;
82
+ stake_base_units: string;
83
+ create_call: EvmCall;
84
+ join_call: EvmCall;
85
+ }
86
+ export interface EvmJoinResponse {
87
+ status: string;
88
+ match?: EvmMatchPayload;
89
+ session_id?: string;
90
+ }
91
+ export declare class CoordinationGameApiClient {
92
+ readonly http: HttpClient;
93
+ constructor(options: HttpClientOptions);
94
+ authChallenge(body: ChallengeRequest): Promise<ChallengeResponse>;
95
+ authSession(body: SessionAuthRequest): Promise<SessionAuthResponse>;
96
+ evmAuthChallenge(body: ChallengeRequest): Promise<ChallengeResponse>;
97
+ evmAuthVerify(body: VerifyRequest): Promise<VerifyResponse>;
98
+ joinQueue(body: QueueRequest, token: string): Promise<QueueJoinResponse>;
99
+ leaveQueue(body: QueueRequest, token: string): Promise<OkResponse>;
100
+ gameStarted(body: {
101
+ game_id: number;
102
+ session_id: string;
103
+ }, token: string): Promise<OkResponse>;
104
+ gameJoined(body: {
105
+ game_id: number;
106
+ session_id: string;
107
+ }, token: string): Promise<OkResponse>;
108
+ cosign(body: {
109
+ session_id: string;
110
+ message: string;
111
+ }, token: string): Promise<{
112
+ signature: string;
113
+ }>;
114
+ cosignJoin(body: {
115
+ session_id: string;
116
+ message: string;
117
+ }, token: string): Promise<{
118
+ signature: string;
119
+ }>;
120
+ committed(body: {
121
+ session_id: string;
122
+ }, token: string): Promise<{
123
+ ok: boolean;
124
+ both_committed: boolean;
125
+ }>;
126
+ bothCommitted(body: {
127
+ session_id: string;
128
+ }, token: string): Promise<{
129
+ r_matchup: string;
130
+ }>;
131
+ resolved(body: Record<string, number>, token: string): Promise<OkResponse>;
132
+ joinCrossChainQueue(body: {
133
+ wallet: string;
134
+ chain: string;
135
+ session_key: string;
136
+ tournament_id: number;
137
+ }): Promise<XQueueResponse>;
138
+ crossChainStatus(by: {
139
+ handle: string;
140
+ } | {
141
+ wallet: string;
142
+ }): Promise<XQueueResponse>;
143
+ crossChainCommit(body: {
144
+ wallet: string;
145
+ commit: string;
146
+ handle?: string;
147
+ }): Promise<{
148
+ status: string;
149
+ both_committed: boolean;
150
+ }>;
151
+ crossChainSign(body: {
152
+ wallet: string;
153
+ step: number;
154
+ signature: string;
155
+ handle?: string;
156
+ }): Promise<{
157
+ status: string;
158
+ relayed: boolean;
159
+ r_matchup: string | null;
160
+ }>;
161
+ crossChainReveal(body: {
162
+ wallet: string;
163
+ preimage: string;
164
+ handle?: string;
165
+ }): Promise<{
166
+ status: string;
167
+ both_revealed: boolean;
168
+ }>;
169
+ crossChainGameplay(by: {
170
+ handle: string;
171
+ } | {
172
+ wallet: string;
173
+ }): Promise<XGameplayView>;
174
+ joinEvmGame(body: {
175
+ wallet: string;
176
+ player_wallet?: string;
177
+ chain: string;
178
+ tournament_id: number;
179
+ }): Promise<EvmJoinResponse>;
180
+ evmGameStatus(wallet: string): Promise<EvmJoinResponse>;
181
+ evmGameCommitted(body: {
182
+ game_id: string;
183
+ wallet: string;
184
+ }): Promise<{
185
+ status: string;
186
+ both_committed: boolean;
187
+ r_matchup: string | null;
188
+ }>;
189
+ evmGameStaked(body: {
190
+ game_id: string;
191
+ }): Promise<{
192
+ status: string;
193
+ }>;
194
+ }
195
+ export type WebSocketLike = {
196
+ readyState: number;
197
+ onopen: (() => void) | null;
198
+ onclose: (() => void) | null;
199
+ onmessage: ((event: {
200
+ data: unknown;
201
+ }) => void) | null;
202
+ onerror: ((event: unknown) => void) | null;
203
+ send(data: string): void;
204
+ close(): void;
205
+ };
206
+ export type WebSocketFactory = (url: string) => WebSocketLike;
207
+ export type ServerMessage = {
208
+ type: "chat";
209
+ text: string;
210
+ from: string;
211
+ } | {
212
+ type: "match_found";
213
+ session_id: string;
214
+ role: number;
215
+ matchup_commitment?: string;
216
+ } | {
217
+ type: "game_ready";
218
+ game_id: number;
219
+ } | {
220
+ type: "both_staked";
221
+ } | {
222
+ type: "reveal_data";
223
+ r_matchup: string;
224
+ } | {
225
+ type: "opponent_committed";
226
+ } | {
227
+ type: "game_resolved";
228
+ p1_guess: number;
229
+ p2_guess: number;
230
+ matchup_type: number;
231
+ first_committer: number;
232
+ } | {
233
+ type: "opponent_disconnected" | "opponent_reconnected" | "pong";
234
+ } | {
235
+ type: "error";
236
+ message: string;
237
+ };
238
+ export type ClientMessage = {
239
+ type: "chat";
240
+ text: string;
241
+ } | {
242
+ type: "ping";
243
+ };
244
+ export interface CoordinationGameWebSocketOptions {
245
+ baseUrl: string;
246
+ token: string;
247
+ network?: string;
248
+ session?: string;
249
+ webSocket?: WebSocketFactory;
250
+ setTimeout?: typeof globalThis.setTimeout;
251
+ clearTimeout?: typeof globalThis.clearTimeout;
252
+ setInterval?: typeof globalThis.setInterval;
253
+ clearInterval?: typeof globalThis.clearInterval;
254
+ pingIntervalMs?: number;
255
+ maxRetries?: number;
256
+ }
257
+ export declare class CoordinationGameWebSocketClient {
258
+ private readonly options;
259
+ private socket;
260
+ private pingTimer;
261
+ private retryCount;
262
+ private closed;
263
+ private readonly outbox;
264
+ onMessage: ((message: ServerMessage) => void) | null;
265
+ onOpen: (() => void) | null;
266
+ onClose: (() => void) | null;
267
+ constructor(options: CoordinationGameWebSocketOptions);
268
+ connect(): void;
269
+ send(message: ClientMessage): void;
270
+ close(): void;
271
+ get connected(): boolean;
272
+ waitForConnection(timeoutMs?: number): Promise<void>;
273
+ private flush;
274
+ private startPing;
275
+ private stopPing;
276
+ }
@@ -0,0 +1,146 @@
1
+ import { HttpClient } from "../http.js";
2
+ import { SwarmClientError } from "../errors.js";
3
+ export class CoordinationGameApiClient {
4
+ http;
5
+ constructor(options) {
6
+ this.http = new HttpClient(options);
7
+ }
8
+ authChallenge(body) { return this.http.post("/auth/challenge", body); }
9
+ authSession(body) { return this.http.post("/auth/session", body); }
10
+ evmAuthChallenge(body) { return this.http.post("/auth/evm/challenge", body); }
11
+ evmAuthVerify(body) { return this.http.post("/auth/evm/verify", body); }
12
+ joinQueue(body, token) { return this.http.post("/queue/join", body, token); }
13
+ leaveQueue(body, token) { return this.http.post("/queue/leave", body, token); }
14
+ gameStarted(body, token) { return this.http.post("/games/started", body, token); }
15
+ gameJoined(body, token) { return this.http.post("/games/joined", body, token); }
16
+ cosign(body, token) { return this.http.post("/games/cosign", body, token); }
17
+ cosignJoin(body, token) { return this.http.post("/games/cosign-join", body, token); }
18
+ committed(body, token) { return this.http.post("/games/committed", body, token); }
19
+ bothCommitted(body, token) { return this.http.post("/games/both-committed", body, token); }
20
+ resolved(body, token) { return this.http.post("/games/resolved", body, token); }
21
+ joinCrossChainQueue(body) {
22
+ return this.http.post("/internal/xqueue/join", body);
23
+ }
24
+ crossChainStatus(by) {
25
+ const query = "handle" in by
26
+ ? `handle=${encodeURIComponent(by.handle)}`
27
+ : `wallet=${encodeURIComponent(by.wallet)}`;
28
+ return this.http.get(`/internal/xqueue/status?${query}`);
29
+ }
30
+ crossChainCommit(body) {
31
+ return this.http.post("/internal/xqueue/commit", body);
32
+ }
33
+ crossChainSign(body) {
34
+ return this.http.post("/internal/xqueue/sign", body);
35
+ }
36
+ crossChainReveal(body) {
37
+ return this.http.post("/internal/xqueue/reveal", body);
38
+ }
39
+ crossChainGameplay(by) {
40
+ const query = "handle" in by
41
+ ? `handle=${encodeURIComponent(by.handle)}`
42
+ : `wallet=${encodeURIComponent(by.wallet)}`;
43
+ return this.http.get(`/internal/xqueue/gameplay?${query}`);
44
+ }
45
+ joinEvmGame(body) {
46
+ return this.http.post("/internal/evmgame/join", body);
47
+ }
48
+ evmGameStatus(wallet) {
49
+ return this.http.get(`/internal/evmgame/status?wallet=${encodeURIComponent(wallet)}`);
50
+ }
51
+ evmGameCommitted(body) {
52
+ return this.http.post("/internal/evmgame/committed", body);
53
+ }
54
+ evmGameStaked(body) {
55
+ return this.http.post("/internal/evmgame/staked", body);
56
+ }
57
+ }
58
+ export class CoordinationGameWebSocketClient {
59
+ options;
60
+ socket = null;
61
+ pingTimer = null;
62
+ retryCount = 0;
63
+ closed = false;
64
+ outbox = [];
65
+ onMessage = null;
66
+ onOpen = null;
67
+ onClose = null;
68
+ constructor(options) {
69
+ this.options = options;
70
+ }
71
+ connect() {
72
+ if (this.closed)
73
+ return;
74
+ const factory = this.options.webSocket ?? ((url) => new globalThis.WebSocket(url));
75
+ if (!factory)
76
+ throw new SwarmClientError({ code: "INVALID_ARGUMENT", operation: "websocket.connect", message: "A WebSocket factory is required" });
77
+ const query = new URLSearchParams({ token: this.options.token });
78
+ if (this.options.network && this.options.network !== "mainnet")
79
+ query.set("network", this.options.network);
80
+ if (this.options.session)
81
+ query.set("session", this.options.session);
82
+ const url = `${this.options.baseUrl.replace(/^http/, "ws").replace(/\/$/, "")}/ws?${query.toString().replace(/%3A/g, ":")}`;
83
+ this.socket = factory(url);
84
+ this.socket.onopen = () => {
85
+ this.retryCount = 0;
86
+ this.startPing();
87
+ this.flush();
88
+ this.onOpen?.();
89
+ };
90
+ this.socket.onmessage = (event) => {
91
+ try {
92
+ this.onMessage?.(JSON.parse(String(event.data)));
93
+ }
94
+ catch { /* malformed peer data is ignored */ }
95
+ };
96
+ this.socket.onclose = () => {
97
+ this.stopPing();
98
+ this.onClose?.();
99
+ if (!this.closed && this.retryCount < (this.options.maxRetries ?? 10)) {
100
+ const delay = Math.min(2 ** this.retryCount * 1_000, 10_000);
101
+ this.retryCount += 1;
102
+ (this.options.setTimeout ?? globalThis.setTimeout)(() => this.connect(), delay);
103
+ }
104
+ };
105
+ }
106
+ send(message) {
107
+ if (this.socket?.readyState === 1)
108
+ this.socket.send(JSON.stringify(message));
109
+ else if (!this.closed && message.type !== "ping" && this.outbox.length < 100)
110
+ this.outbox.push(message);
111
+ }
112
+ close() {
113
+ this.closed = true;
114
+ this.outbox.length = 0;
115
+ this.stopPing();
116
+ this.socket?.close();
117
+ this.socket = null;
118
+ }
119
+ get connected() { return this.socket?.readyState === 1; }
120
+ waitForConnection(timeoutMs = 30_000) {
121
+ if (this.connected)
122
+ return Promise.resolve();
123
+ return new Promise((resolve, reject) => {
124
+ const timeout = (this.options.setTimeout ?? globalThis.setTimeout)(() => reject(new SwarmClientError({ code: "TIMEOUT", operation: "websocket.connect", message: "WebSocket connection timeout", retryable: true })), timeoutMs);
125
+ const previous = this.onOpen;
126
+ this.onOpen = () => {
127
+ (this.options.clearTimeout ?? globalThis.clearTimeout)(timeout);
128
+ this.onOpen = previous;
129
+ previous?.();
130
+ resolve();
131
+ };
132
+ });
133
+ }
134
+ flush() {
135
+ while (this.outbox.length && this.socket?.readyState === 1)
136
+ this.socket.send(JSON.stringify(this.outbox.shift()));
137
+ }
138
+ startPing() {
139
+ this.pingTimer = (this.options.setInterval ?? globalThis.setInterval)(() => this.send({ type: "ping" }), this.options.pingIntervalMs ?? 10_000);
140
+ }
141
+ stopPing() {
142
+ if (this.pingTimer !== null)
143
+ (this.options.clearInterval ?? globalThis.clearInterval)(this.pingTimer);
144
+ this.pingTimer = null;
145
+ }
146
+ }