@dimcool/dimclaw 0.1.18 → 0.1.20
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/dist/index.js +2364 -2275
- package/index.ts +151 -2279
- package/package.json +4 -6
- package/dim-client.ts +0 -214
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@dimcool/dimclaw",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.20",
|
|
4
4
|
"description": "OpenClaw plugin for DIM — play games, chat, send USDC, and earn on DIM using the SDK directly (no MCP subprocess).",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"openclaw": {
|
|
@@ -12,7 +12,6 @@
|
|
|
12
12
|
"files": [
|
|
13
13
|
"dist",
|
|
14
14
|
"index.ts",
|
|
15
|
-
"dim-client.ts",
|
|
16
15
|
"openclaw.plugin.json",
|
|
17
16
|
"README.md"
|
|
18
17
|
],
|
|
@@ -21,13 +20,12 @@
|
|
|
21
20
|
"dev": "tsup --watch"
|
|
22
21
|
},
|
|
23
22
|
"dependencies": {
|
|
24
|
-
"@dimcool/sdk": "0.1.10",
|
|
25
|
-
"@dimcool/wallet": "0.1.10",
|
|
26
23
|
"@solana/web3.js": "^1.95.4",
|
|
27
|
-
"bs58": "^6.0.0"
|
|
28
|
-
"tweetnacl": "^1.0.3"
|
|
24
|
+
"bs58": "^6.0.0"
|
|
29
25
|
},
|
|
30
26
|
"devDependencies": {
|
|
27
|
+
"@dimcool/dim-agent-core": "0.0.1",
|
|
28
|
+
"@dimcool/sdk": "0.1.19",
|
|
31
29
|
"@types/node": "^20.3.1",
|
|
32
30
|
"typescript": "^5.0.0",
|
|
33
31
|
"tsup": "^8.5.1"
|
package/dim-client.ts
DELETED
|
@@ -1,214 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* DIM SDK client for the OpenClaw plugin.
|
|
3
|
-
* Handles wallet-based authentication using a Solana keypair.
|
|
4
|
-
*/
|
|
5
|
-
|
|
6
|
-
import { mkdir, writeFile, rename } from 'node:fs/promises';
|
|
7
|
-
import path from 'node:path';
|
|
8
|
-
import { SDK, NodeStorage } from '@dimcool/sdk';
|
|
9
|
-
import { Keypair, Transaction } from '@solana/web3.js';
|
|
10
|
-
import bs58 from 'bs58';
|
|
11
|
-
import nacl from 'tweetnacl';
|
|
12
|
-
|
|
13
|
-
export interface DimClientConfig {
|
|
14
|
-
/** Base58-encoded Solana private key */
|
|
15
|
-
walletPrivateKey: string;
|
|
16
|
-
/** API base URL (default: https://api.dim.cool) */
|
|
17
|
-
apiUrl?: string;
|
|
18
|
-
/** Referral code to use on first signup */
|
|
19
|
-
referralCode?: string;
|
|
20
|
-
/** Path to HEARTBEAT.md for OpenClaw's heartbeat cycle */
|
|
21
|
-
heartbeatPath?: string;
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
export interface BufferedEvent {
|
|
25
|
-
event: string;
|
|
26
|
-
payload: unknown;
|
|
27
|
-
at: string;
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
export class DimClient {
|
|
31
|
-
public sdk: SDK;
|
|
32
|
-
private keypair: Keypair;
|
|
33
|
-
private config: DimClientConfig;
|
|
34
|
-
private authenticated = false;
|
|
35
|
-
private userId: string | null = null;
|
|
36
|
-
private eventQueue: BufferedEvent[] = [];
|
|
37
|
-
private unsubscribers: Array<() => void> = [];
|
|
38
|
-
|
|
39
|
-
constructor(config: DimClientConfig) {
|
|
40
|
-
this.config = config;
|
|
41
|
-
const secretKeyBytes = bs58.decode(config.walletPrivateKey);
|
|
42
|
-
this.keypair = Keypair.fromSecretKey(secretKeyBytes);
|
|
43
|
-
|
|
44
|
-
this.sdk = new SDK({
|
|
45
|
-
appId: 'dim-agents',
|
|
46
|
-
baseUrl: config.apiUrl || 'https://api.dim.cool',
|
|
47
|
-
storage: new NodeStorage(),
|
|
48
|
-
autoPay: {
|
|
49
|
-
enabled: true,
|
|
50
|
-
maxAmountMinor: 25_000,
|
|
51
|
-
maxRetries: 1,
|
|
52
|
-
},
|
|
53
|
-
});
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
get walletAddress(): string {
|
|
57
|
-
return this.keypair.publicKey.toBase58();
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
get isAuthenticated(): boolean {
|
|
61
|
-
return this.authenticated;
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
get currentUserId(): string | null {
|
|
65
|
-
return this.userId;
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
async authenticate(): Promise<{
|
|
69
|
-
userId: string;
|
|
70
|
-
username?: string | null;
|
|
71
|
-
accessToken: string;
|
|
72
|
-
}> {
|
|
73
|
-
this.sdk.wallet.setSigner({
|
|
74
|
-
address: this.walletAddress,
|
|
75
|
-
signMessage: async (message: string): Promise<Uint8Array | string> => {
|
|
76
|
-
const signature = nacl.sign.detached(
|
|
77
|
-
new TextEncoder().encode(message),
|
|
78
|
-
this.keypair.secretKey,
|
|
79
|
-
);
|
|
80
|
-
return Buffer.from(signature).toString('base64');
|
|
81
|
-
},
|
|
82
|
-
signTransaction: async (
|
|
83
|
-
transaction: Transaction,
|
|
84
|
-
): Promise<Transaction> => {
|
|
85
|
-
transaction.partialSign(this.keypair);
|
|
86
|
-
return transaction;
|
|
87
|
-
},
|
|
88
|
-
});
|
|
89
|
-
|
|
90
|
-
const response = await this.sdk.auth.loginWithWallet({
|
|
91
|
-
referralCode: this.config.referralCode,
|
|
92
|
-
walletMeta: { type: 'keypair' },
|
|
93
|
-
});
|
|
94
|
-
|
|
95
|
-
this.sdk.wsTransport.setAccessToken(response.access_token);
|
|
96
|
-
|
|
97
|
-
this.authenticated = true;
|
|
98
|
-
this.userId = response.user.id;
|
|
99
|
-
|
|
100
|
-
return {
|
|
101
|
-
userId: response.user.id,
|
|
102
|
-
username: response.user.username,
|
|
103
|
-
accessToken: response.access_token,
|
|
104
|
-
};
|
|
105
|
-
}
|
|
106
|
-
|
|
107
|
-
async ensureConnected(timeoutMs = 10000): Promise<void> {
|
|
108
|
-
await this.sdk.ensureWebSocketConnected(timeoutMs);
|
|
109
|
-
}
|
|
110
|
-
|
|
111
|
-
/**
|
|
112
|
-
* Subscribe to key WS events and buffer them for agent consumption.
|
|
113
|
-
* Call after authenticate() when the WS transport is connected.
|
|
114
|
-
*/
|
|
115
|
-
startEventListeners(): void {
|
|
116
|
-
const events = [
|
|
117
|
-
'chat:message',
|
|
118
|
-
'notification',
|
|
119
|
-
'lobby:matched',
|
|
120
|
-
'lobby:invitation',
|
|
121
|
-
'game:turn',
|
|
122
|
-
'game:completed',
|
|
123
|
-
];
|
|
124
|
-
for (const event of events) {
|
|
125
|
-
this.unsubscribers.push(
|
|
126
|
-
this.sdk.events.subscribe(event, (payload: unknown) => {
|
|
127
|
-
this.eventQueue.push({
|
|
128
|
-
event,
|
|
129
|
-
payload,
|
|
130
|
-
at: new Date().toISOString(),
|
|
131
|
-
});
|
|
132
|
-
this.writeHeartbeat().catch(() => {});
|
|
133
|
-
}),
|
|
134
|
-
);
|
|
135
|
-
}
|
|
136
|
-
}
|
|
137
|
-
|
|
138
|
-
/** Write HEARTBEAT.md with pending event summary for OpenClaw's heartbeat cycle. */
|
|
139
|
-
private async writeHeartbeat(): Promise<void> {
|
|
140
|
-
if (!this.config.heartbeatPath) return;
|
|
141
|
-
const filePath = this.resolveHeartbeatPath();
|
|
142
|
-
const count = this.eventQueue.length;
|
|
143
|
-
if (count === 0) return;
|
|
144
|
-
const lines = ['# DIM Heartbeat', ''];
|
|
145
|
-
const eventTypes = new Set(this.eventQueue.map((e) => e.event));
|
|
146
|
-
if (eventTypes.has('chat:message')) {
|
|
147
|
-
lines.push('- You have new DMs — call dim_check_notifications');
|
|
148
|
-
}
|
|
149
|
-
if (eventTypes.has('notification')) {
|
|
150
|
-
lines.push(
|
|
151
|
-
'- New notifications (challenges, friend requests, game results) — call dim_check_notifications',
|
|
152
|
-
);
|
|
153
|
-
}
|
|
154
|
-
if (eventTypes.has('lobby:matched') || eventTypes.has('lobby:invitation')) {
|
|
155
|
-
lines.push('- A game match is ready — call dim_get_pending_events');
|
|
156
|
-
}
|
|
157
|
-
if (eventTypes.has('game:turn')) {
|
|
158
|
-
lines.push("- It's your turn in a game — call dim_get_pending_events");
|
|
159
|
-
}
|
|
160
|
-
if (eventTypes.has('game:completed')) {
|
|
161
|
-
lines.push('- A game has completed — call dim_get_pending_events');
|
|
162
|
-
}
|
|
163
|
-
lines.push('');
|
|
164
|
-
await mkdir(path.dirname(filePath), { recursive: true });
|
|
165
|
-
const tmp = `${filePath}.tmp`;
|
|
166
|
-
await writeFile(tmp, lines.join('\n'), 'utf8');
|
|
167
|
-
await rename(tmp, filePath);
|
|
168
|
-
}
|
|
169
|
-
|
|
170
|
-
private resolveHeartbeatPath(): string {
|
|
171
|
-
const p = this.config.heartbeatPath!;
|
|
172
|
-
if (p.startsWith('~/') && process.env.HOME) {
|
|
173
|
-
return path.join(process.env.HOME, p.slice(2));
|
|
174
|
-
}
|
|
175
|
-
return path.resolve(p);
|
|
176
|
-
}
|
|
177
|
-
|
|
178
|
-
/** Drain all buffered events since last call. */
|
|
179
|
-
drainEvents(): BufferedEvent[] {
|
|
180
|
-
return this.eventQueue.splice(0);
|
|
181
|
-
}
|
|
182
|
-
|
|
183
|
-
/** Peek at buffered event count without draining. */
|
|
184
|
-
get pendingEventCount(): number {
|
|
185
|
-
return this.eventQueue.length;
|
|
186
|
-
}
|
|
187
|
-
|
|
188
|
-
// ── Daily spend tracking ──────────────────────────────────────────────
|
|
189
|
-
|
|
190
|
-
private dailySpendMinor = 0;
|
|
191
|
-
private spendResetDate = '';
|
|
192
|
-
|
|
193
|
-
private resetDailySpendIfNeeded(): void {
|
|
194
|
-
const today = new Date().toISOString().slice(0, 10);
|
|
195
|
-
if (this.spendResetDate !== today) {
|
|
196
|
-
this.dailySpendMinor = 0;
|
|
197
|
-
this.spendResetDate = today;
|
|
198
|
-
}
|
|
199
|
-
}
|
|
200
|
-
|
|
201
|
-
recordSpend(amountMinor: number): void {
|
|
202
|
-
this.resetDailySpendIfNeeded();
|
|
203
|
-
this.dailySpendMinor += amountMinor;
|
|
204
|
-
}
|
|
205
|
-
|
|
206
|
-
get dailySpentDollars(): number {
|
|
207
|
-
this.resetDailySpendIfNeeded();
|
|
208
|
-
return this.dailySpendMinor / 1_000_000;
|
|
209
|
-
}
|
|
210
|
-
|
|
211
|
-
getKeypair(): Keypair {
|
|
212
|
-
return this.keypair;
|
|
213
|
-
}
|
|
214
|
-
}
|