pog-mcp 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.
package/dist/wallet.js ADDED
@@ -0,0 +1,562 @@
1
+ /**
2
+ * wallet — BIP39 mnemonic ⇄ Solana keypair, plus SIWS message signing.
3
+ *
4
+ * SIWS is the single biggest barrier to an agent playing: the game's API needs an
5
+ * ed25519 signature from a Solana keypair, and most agents have neither. Owning a
6
+ * wallet here is what lets `login` be one tool call instead of a crypto tutorial.
7
+ *
8
+ * DERIVATION MUST MATCH THE BROWSER WALLET.
9
+ * SLIP-0010 ed25519 on m/44'/501'/0'/0' — the path Phantom/Solflare use for
10
+ * account 0 — mirroring `packages/web/src/wallets/solanaMnemonic.ts`. Two copies
11
+ * exist because @sws26/shared is deliberately dependency-free and this needs
12
+ * crypto libraries; `__tests__/derivation.test.ts` pins both against the same
13
+ * fixed vectors so a change to either side fails loudly. Getting this wrong does
14
+ * not throw — it silently yields a DIFFERENT address, stranding whatever the
15
+ * agent owned.
16
+ *
17
+ * Unlike the browser wallet this uses @noble/curves directly rather than
18
+ * @solana/web3.js: the only operations needed are "public key from seed" and
19
+ * "sign bytes", and pulling in web3.js for that would add a large dependency to
20
+ * a stdio process that never touches an RPC.
21
+ */
22
+ import { chmodSync, closeSync, existsSync, fsyncSync, openSync, writeSync, linkSync, lstatSync, mkdirSync, readFileSync, realpathSync, statSync, unlinkSync, writeFileSync, } from 'node:fs';
23
+ import { randomBytes } from 'node:crypto';
24
+ import { homedir } from 'node:os';
25
+ import { dirname, join, resolve, sep } from 'node:path';
26
+ import { ed25519 } from '@noble/curves/ed25519';
27
+ import { hmac } from '@noble/hashes/hmac';
28
+ import { sha512 } from '@noble/hashes/sha512';
29
+ import { generateMnemonic, mnemonicToSeedSync, validateMnemonic } from '@scure/bip39';
30
+ import { wordlist } from '@scure/bip39/wordlists/english';
31
+ import bs58 from 'bs58';
32
+ /** m/44'/501'/0'/0' — Solana (coin type 501), account 0, all segments hardened. */
33
+ const SOLANA_DERIVATION = [44, 501, 0, 0];
34
+ function hmac512(key, data) {
35
+ return hmac(sha512, key, data);
36
+ }
37
+ function masterNode(seed) {
38
+ const i = hmac512(new TextEncoder().encode('ed25519 seed'), seed);
39
+ return { key: i.slice(0, 32), chainCode: i.slice(32) };
40
+ }
41
+ /** ed25519 SLIP-0010 supports hardened derivation only. */
42
+ function deriveHardened(node, index) {
43
+ const hardened = (index | 0x80000000) >>> 0;
44
+ const data = new Uint8Array(37);
45
+ data[0] = 0x00;
46
+ data.set(node.key, 1);
47
+ data[33] = (hardened >>> 24) & 0xff;
48
+ data[34] = (hardened >>> 16) & 0xff;
49
+ data[35] = (hardened >>> 8) & 0xff;
50
+ data[36] = hardened & 0xff;
51
+ const i = hmac512(node.chainCode, data);
52
+ return { key: i.slice(0, 32), chainCode: i.slice(32) };
53
+ }
54
+ /** The 32-byte ed25519 private seed for a mnemonic. */
55
+ export function seedFromMnemonic(mnemonic) {
56
+ let node = masterNode(mnemonicToSeedSync(mnemonic, ''));
57
+ for (const index of SOLANA_DERIVATION)
58
+ node = deriveHardened(node, index);
59
+ return node.key;
60
+ }
61
+ /** Base58 Solana address for a mnemonic. */
62
+ export function addressFromMnemonic(mnemonic) {
63
+ return bs58.encode(ed25519.getPublicKey(seedFromMnemonic(mnemonic)));
64
+ }
65
+ /** Sign raw UTF-8 message bytes; returns a base58 signature, as the API expects. */
66
+ export function signMessage(mnemonic, message) {
67
+ const bytes = new TextEncoder().encode(message);
68
+ return bs58.encode(ed25519.sign(bytes, seedFromMnemonic(mnemonic)));
69
+ }
70
+ /** Generate a fresh 12-word (128-bit) English mnemonic. */
71
+ export function newMnemonic() {
72
+ return generateMnemonic(wordlist, 128);
73
+ }
74
+ /** True when `mnemonic` is a valid BIP39 English phrase. */
75
+ export function isValidMnemonic(mnemonic) {
76
+ return validateMnemonic(mnemonic, wordlist);
77
+ }
78
+ // ---------------------------------------------------------------------------
79
+ // Persistence
80
+ // ---------------------------------------------------------------------------
81
+ /**
82
+ * Where the recovery phrase lives.
83
+ *
84
+ * A file, not an env var: the agent must come back as the SAME manager across
85
+ * restarts or it loses its squad, its record, and anything it owns on-chain.
86
+ * `POG_MCP_WALLET_FILE` overrides it so one machine can run several agents.
87
+ */
88
+ export function walletFilePath(env = process.env) {
89
+ return env['POG_MCP_WALLET_FILE'] ?? join(homedir(), '.pog-mcp', 'wallet.json');
90
+ }
91
+ /** Read the stored wallet, or null when this machine has none yet. */
92
+ /**
93
+ * Read the stored wallet, or null if the file is missing or not a usable record.
94
+ *
95
+ * "Usable" includes the address AGREEING with the mnemonic. A file with a valid
96
+ * phrase but a missing or wrong address used to be accepted, and then `login`
97
+ * requested a nonce for `undefined` — or for an address this key cannot sign
98
+ * for — so the sign-in failed with no hint that the file was the problem, and
99
+ * the corrupt-file refusal never fired.
100
+ */
101
+ export function readWallet(path = walletFilePath()) {
102
+ try {
103
+ // What KIND of thing is there, before opening it.
104
+ //
105
+ // readFileSync on a FIFO blocks until somebody writes — forever, if nobody
106
+ // does — and this process speaks MCP over stdio, so a hung read is a hung
107
+ // agent with no error to catch. Another local user who can create the
108
+ // configured path can leave one there. A symlink is refused for the reason
109
+ // the directory walk refuses them: its owner chooses where the read lands.
110
+ // The directory checks cannot help here; they run after a wallet has
111
+ // already been parsed.
112
+ // Unconditional: lstat and isFile mean the same thing on Windows, and a
113
+ // junction or named pipe there is no more readable than a symlink or FIFO
114
+ // here. The reason for the check is portable even though FIFOs are not.
115
+ if (!lstatSync(path).isFile())
116
+ return null;
117
+ const parsed = JSON.parse(readFileSync(path, 'utf8'));
118
+ if (typeof parsed !== 'object' || parsed === null)
119
+ return null;
120
+ const { mnemonic, address } = parsed;
121
+ if (typeof mnemonic !== 'string' || !isValidMnemonic(mnemonic))
122
+ return null;
123
+ if (typeof address !== 'string' || address !== addressFromMnemonic(mnemonic))
124
+ return null;
125
+ return parsed;
126
+ }
127
+ catch {
128
+ return null;
129
+ }
130
+ }
131
+ /**
132
+ * The one way an EXISTING wallet gets returned.
133
+ *
134
+ * There are three doors onto the same file — the import that matches what is
135
+ * already stored, the ordinary start with no import, and losing the creation
136
+ * race to another process — and securing one of them secured only one of them.
137
+ * A phrase readable by every local user is just as exposed whichever door the
138
+ * caller came through, so the checks live here instead of at the returns.
139
+ */
140
+ function acceptExisting(wallet, path) {
141
+ assertDirectoryIsSafe(dirname(path));
142
+ ensurePrivate(path, 0o600);
143
+ return { wallet, created: false };
144
+ }
145
+ /**
146
+ * What we can and cannot check on Windows.
147
+ *
148
+ * Every guard below is POSIX: mode bits, uid, sticky. Windows has none of them —
149
+ * chmod is close to a no-op there and privacy is an ACL, which Node cannot read.
150
+ * So they all returned early, and a wallet in a shared directory got no
151
+ * protection at all while the code looked like it was protecting one.
152
+ *
153
+ * The one thing that IS knowable without ACL APIs is WHERE the file is. A path
154
+ * under the user's profile inherits that profile's ACL, which Windows makes
155
+ * private by default — that covers the default location and anybody who kept it.
156
+ * Outside the profile — C:\\Temp, a network share, a machine-wide folder — we
157
+ * genuinely cannot tell, so it is refused rather than silently trusted. A real
158
+ * key deserves the fail-closed answer, not the convenient one.
159
+ */
160
+ function assertWindowsPathIsPrivate(target) {
161
+ const home = resolve(homedir());
162
+ const path = resolve(target);
163
+ if (path === home || path.startsWith(home + sep))
164
+ return;
165
+ throw new Error(`${target} is outside your user profile (${home}), and on Windows this cannot be checked ` +
166
+ 'for privacy — file permissions there are ACLs, which nothing here can read, so a ' +
167
+ 'wallet placed in a shared folder would be readable by other users with no sign of it. ' +
168
+ 'Leave POG_MCP_WALLET_FILE unset to use the default under your profile, or point it ' +
169
+ 'somewhere inside it.');
170
+ }
171
+ /**
172
+ * A directory is checked, never changed.
173
+ *
174
+ * Tightening it was a worse bug than the one it fixed: POG_MCP_WALLET_FILE can
175
+ * point straight into a shared directory — /tmp/pog-wallet.json in a container,
176
+ * ./wallet.json in a checkout — and chmod 0700 there locks every other user and
177
+ * service out of /tmp or the workspace. That directory is not ours to change.
178
+ *
179
+ * What actually endangers the wallet is somebody else being able to REPLACE it,
180
+ * which needs write permission on the directory. Readable is fine: the file
181
+ * itself is 0600, so listing the name gives nothing. And a world-writable
182
+ * directory with the sticky bit — /tmp is 1777 — cannot be used to unlink
183
+ * another user's file, which is exactly what the sticky bit is for. So the test
184
+ * is write-without-sticky, and the answer to it is to refuse and say what to do,
185
+ * not to reach into a directory somebody else is using.
186
+ */
187
+ function assertDirectoryIsSafe(dir) {
188
+ if (process.platform === 'win32')
189
+ return assertWindowsPathIsPrivate(dir);
190
+ if (typeof process.getuid !== 'function')
191
+ return;
192
+ const me = process.getuid();
193
+ // EVERY component, not just the last one.
194
+ //
195
+ // A path is only as trustworthy as the way in. `/tmp/mine/wallet.json` can
196
+ // have a perfectly good `mine` while `/tmp/mine` is a symlink somebody else
197
+ // owns, or while an ancestor is world-writable — either lets another user
198
+ // rename or retarget a link and send the next start somewhere new. It creates
199
+ // a wallet there, and the squad and assets stay with a phrase nothing points
200
+ // at any more. statSync also FOLLOWS links, so checking the target said
201
+ // nothing about who controls the pointer.
202
+ for (const component of ancestors(dir)) {
203
+ let link;
204
+ try {
205
+ link = lstatSync(component);
206
+ }
207
+ catch {
208
+ continue; // Not there yet — mkdir is about to create it, at 0700.
209
+ }
210
+ const ownerIsTrusted = link.uid === me || link.uid === 0;
211
+ if (link.isSymbolicLink()) {
212
+ // Root-owned links are how the system is built: /tmp -> /private/tmp on
213
+ // macOS is one, and refusing those would refuse the documented setup.
214
+ if (!ownerIsTrusted) {
215
+ throw new Error(`${component} is a symlink owned by uid ${String(link.uid)}, not by this process ` +
216
+ `(uid ${String(me)}). Whoever owns it can point it somewhere else at any time, and ` +
217
+ 'the next start would make a new wallet there while the squad and assets stay with ' +
218
+ 'the phrase this one holds. Point POG_MCP_WALLET_FILE somewhere no other user is in ' +
219
+ 'the path.');
220
+ }
221
+ continue; // Its target is checked on the next iteration via realpath below.
222
+ }
223
+ if (!ownerIsTrusted && (link.mode & 0o200) !== 0) {
224
+ throw new Error(`${component} is owned by uid ${String(link.uid)}, not by this process ` +
225
+ `(uid ${String(me)}), and that owner can delete or move what is inside it whatever ` +
226
+ 'the permissions say. The next start would then generate a different key while the ' +
227
+ 'squad and assets stay with the phrase that vanished. Point POG_MCP_WALLET_FILE at a ' +
228
+ 'path you own the whole way down.');
229
+ }
230
+ // Sticky is not a blanket pass. POSIX lets the DIRECTORY's owner unlink any
231
+ // entry in it, sticky or not — so a sticky directory belonging to another
232
+ // ordinary user still lets that user delete the only copy of this phrase.
233
+ // The exception exists for /tmp, which root owns; keep it to that shape.
234
+ const groupOrOtherWritable = (link.mode & 0o022) !== 0;
235
+ const sticky = (link.mode & 0o1000) !== 0;
236
+ if (groupOrOtherWritable && !(sticky && ownerIsTrusted)) {
237
+ throw new Error(`${component} is writable by other users, so someone there can replace or delete the ` +
238
+ 'wallet below it — and the phrase you have would stop being the one this agent plays ' +
239
+ 'as, with nothing left to recover from. ' +
240
+ 'Refusing rather than changing a directory that is not ours to change: point ' +
241
+ 'POG_MCP_WALLET_FILE at a private directory (the default ~/.pog-mcp is created 0700), ' +
242
+ `or tighten ${component} yourself if it really is only yours.`);
243
+ }
244
+ }
245
+ }
246
+ /**
247
+ * Every path component from the root down to `dir`, links resolved.
248
+ *
249
+ * Both spellings matter: the literal path is where the SYMLINKS live (and their
250
+ * owners are the ones who can retarget them), and the resolved path is where
251
+ * the real directories are. Checking one and not the other leaves the other as
252
+ * the way in.
253
+ */
254
+ function ancestors(dir) {
255
+ const seen = new Set();
256
+ const out = [];
257
+ for (const start of [dir, safeRealpath(dir)]) {
258
+ let current = resolve(start);
259
+ const chain = [];
260
+ for (;;) {
261
+ if (!seen.has(current)) {
262
+ seen.add(current);
263
+ chain.push(current);
264
+ }
265
+ const parent = dirname(current);
266
+ if (parent === current)
267
+ break;
268
+ current = parent;
269
+ }
270
+ // Root first, so a failure names the outermost thing that is wrong.
271
+ out.push(...chain.reverse());
272
+ }
273
+ return out;
274
+ }
275
+ function safeRealpath(dir) {
276
+ try {
277
+ return realpathSync(dir);
278
+ }
279
+ catch {
280
+ return dir; // Not fully created yet; the literal chain is all there is.
281
+ }
282
+ }
283
+ /**
284
+ * Make sure a FILE we are about to trust is ours alone, tightening it if not.
285
+ *
286
+ * The creation paths write 0600/0700, which protects a wallet this process
287
+ * made — and nothing else. A phrase restored from a backup, copied off another
288
+ * machine, or written before a umask was fixed arrives with whatever mode it
289
+ * had, and every local user can read it. Same for the DIRECTORY: mkdirSync's
290
+ * `mode` applies only when it creates one, so a wallet installed into an
291
+ * existing group-writable directory can be unlinked and replaced by anyone —
292
+ * and the agent adopts a key someone else chose, or loses its only copy.
293
+ *
294
+ * Tightening rather than refusing, because a wide mode is almost always an
295
+ * innocent umask and refusing would strand an agent on its own wallet. But a
296
+ * file owned by ANOTHER user is not a permissions accident, and chmod would
297
+ * fail on it anyway — that one is refused.
298
+ *
299
+ * A no-op where the concept does not apply: Windows has no POSIX mode bits, and
300
+ * process.getuid is not defined there.
301
+ */
302
+ function ensurePrivate(target, mode) {
303
+ if (process.platform === 'win32')
304
+ return assertWindowsPathIsPrivate(target);
305
+ if (typeof process.getuid !== 'function')
306
+ return;
307
+ let info;
308
+ try {
309
+ info = statSync(target);
310
+ }
311
+ catch {
312
+ return; // Not there yet; the caller is about to create it at the right mode.
313
+ }
314
+ const me = process.getuid();
315
+ if (info.uid !== me) {
316
+ throw new Error(`${target} is owned by uid ${String(info.uid)}, not by this process (uid ${String(me)}). ` +
317
+ 'Refusing to use it: a wallet another user controls can be replaced under you, and the ' +
318
+ 'phrase in it may already be theirs. Move it aside, or point POG_MCP_WALLET_FILE ' +
319
+ 'somewhere you own.');
320
+ }
321
+ // Any group or other bit at all — read included. The phrase is the key.
322
+ if ((info.mode & 0o077) !== 0)
323
+ chmodSync(target, mode);
324
+ }
325
+ /**
326
+ * Persist a recovery phrase at 0600.
327
+ *
328
+ * This is a real Solana key on a real network. The mode is set explicitly rather
329
+ * than left to umask, because a phrase readable by every process on the box is a
330
+ * different thing from a config file.
331
+ */
332
+ export function writeWallet(mnemonic, path = walletFilePath(),
333
+ /**
334
+ * When true the write fails with EEXIST instead of clobbering an existing
335
+ * file. Used for first-time creation, where losing the race must NOT mean
336
+ * overwriting the phrase another process already handed out.
337
+ */
338
+ exclusive = false) {
339
+ const wallet = {
340
+ mnemonic,
341
+ address: addressFromMnemonic(mnemonic),
342
+ createdAt: new Date().toISOString(),
343
+ };
344
+ mkdirSync(dirname(path), { recursive: true, mode: 0o700 });
345
+ writeFileSync(path, `${JSON.stringify(wallet, null, 2)}\n`, {
346
+ mode: 0o600,
347
+ ...(exclusive ? { flag: 'wx' } : {}),
348
+ });
349
+ chmodSync(path, 0o600);
350
+ return wallet;
351
+ }
352
+ /**
353
+ * The wallet this server plays as: an existing one, `POG_MCP_MNEMONIC`, or a new
354
+ * one. Importing writes the phrase so an operator can move an agent between
355
+ * machines without hand-editing JSON — but only onto a machine that does not
356
+ * already have one.
357
+ *
358
+ * A different phrase already on disk is a REFUSAL, not an overwrite. That file
359
+ * is a real Solana key: it is very likely the only copy, and the manager it
360
+ * belongs to owns a squad and on-chain assets. Replacing it because someone
361
+ * exported a variable to try another account — or left one exported from an
362
+ * earlier shell — destroys that access with no error and nothing to recover
363
+ * from. The concurrent-creation path already refuses to clobber for exactly this
364
+ * reason; there is no version of "wins over the file" worth that.
365
+ */
366
+ export function loadOrCreateWallet(env = process.env, path = walletFilePath(env)) {
367
+ const imported = env['POG_MCP_MNEMONIC']?.trim();
368
+ if (imported) {
369
+ if (!isValidMnemonic(imported)) {
370
+ throw new Error('POG_MCP_MNEMONIC is not a valid BIP39 English phrase');
371
+ }
372
+ const existing = readWallet(path);
373
+ if (existing?.mnemonic === imported)
374
+ return acceptExisting(existing, path);
375
+ // existsSync, not `existing !== null`: readWallet also returns null for a
376
+ // file it cannot parse, and an unreadable wallet file is the LAST thing to
377
+ // overwrite — a phrase truncated mid-write is still most of a phrase.
378
+ if (existsSync(path))
379
+ throw importConflict(path);
380
+ // Install through the SAME atomic path as a generated wallet. A plain write
381
+ // here reopened the race from the other side: two processes start with no
382
+ // file, one atomically links a generated phrase, and this one truncates it a
383
+ // moment later — leaving the first process live, caching a key that is no
384
+ // longer on disk, free to build a squad nothing can recover after a restart.
385
+ // The existsSync above cannot prevent that; it is a check, and the write was
386
+ // a separate step.
387
+ const installed = createWalletAtomically(imported, path);
388
+ if (installed.wallet.mnemonic !== imported) {
389
+ // Lost the race to a different phrase. Adopting it silently is what
390
+ // createWalletAtomically does for a GENERATED wallet, where any valid
391
+ // phrase will do — but an operator who named one did not ask for another.
392
+ throw importConflict(path);
393
+ }
394
+ return { wallet: installed.wallet, created: false };
395
+ }
396
+ const existing = readWallet(path);
397
+ if (existing && isValidMnemonic(existing.mnemonic)) {
398
+ // A wallet is only as private as the file holding it, and this one may have
399
+ // arrived from anywhere.
400
+ return acceptExisting(existing, path);
401
+ }
402
+ // Two MCP processes starting at once both see no file and both generate a
403
+ // phrase. With a plain write both would save, each caching its own — and the
404
+ // loser's phrase can create a squad and hold assets that nothing on disk can
405
+ // recover, because the winner's file is what survives a restart. No error is
406
+ // raised anywhere; the account simply becomes unreachable.
407
+ //
408
+ // Write the content to a temp file first, then hard-link it into place.
409
+ // `link()` fails with EEXIST if the target exists, so exactly one process
410
+ // wins — and because the temp file is already complete, the file is valid the
411
+ // instant it appears. An `wx` open would also be atomic, but only for
412
+ // CREATION: the loser could then read the winner's still-empty file, call it
413
+ // corrupt, and overwrite it. Linking leaves no such window.
414
+ return createWalletAtomically(newMnemonic(), path);
415
+ }
416
+ /** The refusal shared by both ways an import can meet a wallet it did not name. */
417
+ function importConflict(path) {
418
+ return new Error(`A different wallet already exists at ${path}, and POG_MCP_MNEMONIC is not its phrase. ` +
419
+ 'Refusing to replace it: that file is probably the only copy of a key that owns a ' +
420
+ 'squad and on-chain assets, and nothing here can bring it back. Move or delete it if ' +
421
+ 'you really mean to switch, or set POG_MCP_WALLET_FILE to another path to run this ' +
422
+ 'phrase alongside it.');
423
+ }
424
+ /**
425
+ * Write a file and make sure it is actually on the disk before returning.
426
+ *
427
+ * writeFileSync returns when the kernel has the data, not when the platter or
428
+ * the flash does. For most files that is the right trade; for the only copy of
429
+ * a key it is not.
430
+ */
431
+ function writeFileSyncDurable(target, contents) {
432
+ const bytes = Buffer.from(contents, 'utf8');
433
+ const fd = openSync(target, 'wx', 0o600);
434
+ let complete = false;
435
+ try {
436
+ // writeSync reports how many bytes the kernel TOOK, which is not always how
437
+ // many we handed it. A full disk or an exhausted quota can accept the first
438
+ // half of the JSON and stop — and half a wallet file is an unreadable one,
439
+ // which the caller would otherwise fsync and link into place as if it were
440
+ // the real thing. Keep going until every byte is down.
441
+ let written = 0;
442
+ while (written < bytes.length) {
443
+ const n = writeSync(fd, bytes, written, bytes.length - written);
444
+ if (n <= 0) {
445
+ throw new Error(`Could not write ${target}: the filesystem accepted ${String(written)} of ` +
446
+ `${String(bytes.length)} bytes and then stopped. Check free space and quota. ` +
447
+ 'No wallet was created, so nothing is lost — run this again once there is room.');
448
+ }
449
+ written += n;
450
+ }
451
+ fsyncSync(fd);
452
+ complete = true;
453
+ }
454
+ finally {
455
+ closeSync(fd);
456
+ if (!complete) {
457
+ // A partial file must never survive to be linked into place. We created
458
+ // this name exclusively, so removing it can only remove our own failure.
459
+ try {
460
+ unlinkSync(target);
461
+ }
462
+ catch {
463
+ // Already gone. Nothing downstream depends on the removal succeeding.
464
+ }
465
+ }
466
+ }
467
+ }
468
+ /**
469
+ * fsync a directory, so a newly linked name survives a power cut too.
470
+ *
471
+ * Not portable: Windows cannot open a directory as a file, and some filesystems
472
+ * refuse the sync. Neither failure means the write is lost — the file itself is
473
+ * already synced — so this is best effort by design.
474
+ */
475
+ function syncDirectory(dir) {
476
+ if (process.platform === 'win32')
477
+ return;
478
+ let fd;
479
+ try {
480
+ fd = openSync(dir, 'r');
481
+ }
482
+ catch {
483
+ return;
484
+ }
485
+ try {
486
+ fsyncSync(fd);
487
+ }
488
+ catch {
489
+ // Some filesystems (and some containers) refuse this. The file is synced.
490
+ }
491
+ finally {
492
+ closeSync(fd);
493
+ }
494
+ }
495
+ /**
496
+ * Create the wallet file, or adopt whichever concurrent process created it
497
+ * first. Never overwrites an existing valid wallet.
498
+ */
499
+ function createWalletAtomically(mnemonic, path) {
500
+ const wallet = {
501
+ mnemonic,
502
+ address: addressFromMnemonic(mnemonic),
503
+ createdAt: new Date().toISOString(),
504
+ };
505
+ const dir = dirname(path);
506
+ // 0700 for one we create; umask can only remove bits, never add them.
507
+ mkdirSync(dir, { recursive: true, mode: 0o700 });
508
+ assertDirectoryIsSafe(dir);
509
+ // Unique per process AND per attempt, so two racers never share a temp name.
510
+ const temp = join(dir, `.wallet.${String(process.pid)}.${randomBytes(6).toString('hex')}.tmp`);
511
+ // Written THROUGH a descriptor and fsync'd before it is linked into place.
512
+ //
513
+ // writeFileSync returns once the data is in the page cache, not once it is on
514
+ // the disk. The link then publishes a name whose contents may not survive a
515
+ // power cut — and the caller can create a squad in that window, so after the
516
+ // reboot the account exists and the phrase that owns it does not. This is the
517
+ // one file where that trade is not worth making.
518
+ writeFileSyncDurable(temp, `${JSON.stringify(wallet, null, 2)}\n`);
519
+ try {
520
+ try {
521
+ linkSync(temp, path);
522
+ chmodSync(path, 0o600);
523
+ // And the DIRECTORY entry itself, so the name survives too. A synced file
524
+ // under an unsynced directory is a file with no way back to it.
525
+ syncDirectory(dir);
526
+ return { wallet, created: true };
527
+ }
528
+ catch (err) {
529
+ if (err.code !== 'EEXIST')
530
+ throw err;
531
+ }
532
+ const winner = readWallet(path);
533
+ if (winner && isValidMnemonic(winner.mnemonic)) {
534
+ return acceptExisting(winner, path);
535
+ }
536
+ // The file exists but is unusable. REFUSE rather than replace it.
537
+ //
538
+ // Replacing looks helpful and is not. A truncated or hand-edited file may
539
+ // still contain the phrase in plain text, and overwriting destroys the only
540
+ // copy of an account that holds real assets — irreversibly, to save the user
541
+ // one . Automatic recovery is not worth that.
542
+ //
543
+ // Refusing also removes the last race here. Any unlink-then-retry scheme has
544
+ // a window where one process reads the corrupt file, a second replaces it
545
+ // with a valid one, and the first then unlinks a good file it never saw —
546
+ // putting the two processes back on different phrases, which is exactly the
547
+ // failure the atomic link exists to prevent.
548
+ throw new Error(`${path} exists but is not a readable wallet. It may still contain your ` +
549
+ 'recovery phrase — open it before doing anything else. To start over, delete it. ' +
550
+ 'POG_MCP_MNEMONIC will not help while the file is there: importing refuses to ' +
551
+ 'overwrite it too, for the same reason.');
552
+ }
553
+ finally {
554
+ try {
555
+ unlinkSync(temp);
556
+ }
557
+ catch {
558
+ // Already gone, or never created. Nothing depends on the temp surviving.
559
+ }
560
+ }
561
+ }
562
+ //# sourceMappingURL=wallet.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"wallet.js","sourceRoot":"","sources":["../src/wallet.ts"],"names":[],"mappings":"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"}
package/package.json ADDED
@@ -0,0 +1,63 @@
1
+ {
2
+ "name": "pog-mcp",
3
+ "version": "0.1.0",
4
+ "type": "module",
5
+ "description": "MCP server that lets an AI agent play Proof of Goal — wallet, sign-in, squad building, and matches as typed tools.",
6
+ "license": "MIT",
7
+ "homepage": "https://pog.soccer/agents",
8
+ "keywords": [
9
+ "mcp",
10
+ "model-context-protocol",
11
+ "agent",
12
+ "game",
13
+ "solana",
14
+ "football"
15
+ ],
16
+ "engines": {
17
+ "node": ">=20"
18
+ },
19
+ "publishConfig": {
20
+ "access": "public"
21
+ },
22
+ "files": [
23
+ "dist",
24
+ "skill/SKILL.md",
25
+ "skill/reference/measurements.md",
26
+ "README.md"
27
+ ],
28
+ "main": "./dist/index.js",
29
+ "types": "./dist/index.d.ts",
30
+ "bin": {
31
+ "pog-mcp": "./dist/index.js"
32
+ },
33
+ "exports": {
34
+ ".": {
35
+ "import": "./dist/index.js",
36
+ "types": "./dist/index.d.ts"
37
+ }
38
+ },
39
+ "scripts": {
40
+ "build": "tsc",
41
+ "dev": "tsc --watch",
42
+ "type-check": "tsc --noEmit",
43
+ "lint": "tsc --noEmit",
44
+ "start": "node dist/index.js",
45
+ "test": "vitest run",
46
+ "test:watch": "vitest",
47
+ "prepublishOnly": "pnpm build"
48
+ },
49
+ "dependencies": {
50
+ "@modelcontextprotocol/sdk": "^1.13.0",
51
+ "@noble/curves": "^1.8.2",
52
+ "@noble/hashes": "^1.8.0",
53
+ "@scure/bip39": "^1.6.0",
54
+ "bs58": "^6.0.0",
55
+ "zod": "^3.24.2"
56
+ },
57
+ "devDependencies": {
58
+ "@types/node": "^22.15.30",
59
+ "@vitest/coverage-v8": "^3.2.4",
60
+ "typescript": "*",
61
+ "vitest": "^3.2.4"
62
+ }
63
+ }