@three-ws/mcp-server 1.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.
@@ -0,0 +1,421 @@
1
+ // Vendored from the three.ws monorepo: src/pose-presets.js (the data the
2
+ // public /pose page renders). Kept in sync by hand so this package is
3
+ // self-contained when published to npm. Do not edit here — edit the canonical
4
+ // src/pose-presets.js and re-copy.
5
+
6
+ // Preset poses for /pose. Each entry is { id, label, group, pose } where
7
+ // `pose` is a flat map of jointName → { x, y, z } Euler rotations in
8
+ // radians, optionally with a rootPosition translating the whole figure.
9
+ //
10
+ // Convention reminder: in rest pose every rotation is 0 and the mannequin
11
+ // stands upright with arms at its sides. Limbs extend along their joint
12
+ // group's local -Y axis. Positive shoulderL.z opens the left arm outward
13
+ // (to the figure's left); negative shoulderR.z opens the right arm outward.
14
+ // shoulderX is forward(-) / back(+); elbow.x is bend (negative bends the
15
+ // elbow naturally so the forearm comes up toward the shoulder).
16
+
17
+ const PI = Math.PI;
18
+ const HALF = PI / 2;
19
+
20
+ export const PRESET_GROUPS = ['Standing', 'Action', 'Sitting & Floor', 'Expressive'];
21
+
22
+ export const PRESETS = [
23
+ // ─── STANDING ───
24
+ {
25
+ id: 'tpose',
26
+ label: 'T-pose',
27
+ group: 'Standing',
28
+ pose: {
29
+ shoulderL: { x: 0, y: 0, z: HALF },
30
+ shoulderR: { x: 0, y: 0, z: -HALF },
31
+ },
32
+ },
33
+ {
34
+ id: 'apose',
35
+ label: 'A-pose',
36
+ group: 'Standing',
37
+ pose: {
38
+ shoulderL: { x: 0, y: 0, z: 0.55 },
39
+ shoulderR: { x: 0, y: 0, z: -0.55 },
40
+ },
41
+ },
42
+ {
43
+ id: 'relaxed',
44
+ label: 'Relaxed stand',
45
+ group: 'Standing',
46
+ pose: {
47
+ shoulderL: { x: -0.08, y: 0, z: 0.10 },
48
+ shoulderR: { x: -0.08, y: 0, z: -0.10 },
49
+ elbowL: { x: -0.18, y: 0, z: 0 },
50
+ elbowR: { x: -0.18, y: 0, z: 0 },
51
+ hipL: { x: 0.04, y: 0, z: 0.04 },
52
+ hipR: { x: -0.04, y: 0, z: -0.04 },
53
+ head: { x: 0.06, y: 0, z: 0 },
54
+ },
55
+ },
56
+ {
57
+ id: 'contrapposto',
58
+ label: 'Contrapposto',
59
+ group: 'Standing',
60
+ pose: {
61
+ pelvis: { x: 0, y: 0, z: 0.10 },
62
+ spine: { x: 0, y: 0, z: -0.06 },
63
+ chest: { x: 0, y: -0.10, z: -0.04 },
64
+ head: { x: 0, y: 0.20, z: -0.05 },
65
+ shoulderL: { x: -0.12, y: 0, z: 0.18 },
66
+ shoulderR: { x: -0.10, y: 0, z: -0.10 },
67
+ elbowL: { x: -0.30, y: 0, z: 0 },
68
+ elbowR: { x: -0.18, y: 0, z: 0 },
69
+ hipL: { x: 0.05, y: 0, z: -0.05 },
70
+ hipR: { x: -0.20, y: 0, z: 0.05 },
71
+ kneeR: { x: 0.40, y: 0, z: 0 },
72
+ ankleR: { x: -0.25, y: 0, z: 0 },
73
+ },
74
+ },
75
+ {
76
+ id: 'hands-up',
77
+ label: 'Arms up (cheer)',
78
+ group: 'Standing',
79
+ pose: {
80
+ shoulderL: { x: 0, y: 0, z: PI * 0.92 },
81
+ shoulderR: { x: 0, y: 0, z: -PI * 0.92 },
82
+ elbowL: { x: -0.10, y: 0, z: 0 },
83
+ elbowR: { x: -0.10, y: 0, z: 0 },
84
+ head: { x: -0.20, y: 0, z: 0 },
85
+ },
86
+ },
87
+ {
88
+ id: 'wave',
89
+ label: 'Wave hello',
90
+ group: 'Standing',
91
+ pose: {
92
+ shoulderL: { x: 0, y: 0, z: 0.10 },
93
+ shoulderR: { x: 0, y: 0, z: -PI * 0.78 },
94
+ elbowR: { x: -1.20, y: 0, z: 0 },
95
+ wristR: { x: 0, y: 0, z: -0.30 },
96
+ head: { x: 0, y: -0.20, z: 0 },
97
+ },
98
+ },
99
+ {
100
+ id: 'hands-on-hips',
101
+ label: 'Hands on hips',
102
+ group: 'Standing',
103
+ pose: {
104
+ shoulderL: { x: 0, y: -1.2, z: 0.55 },
105
+ shoulderR: { x: 0, y: 1.2, z: -0.55 },
106
+ elbowL: { x: -1.40, y: 0, z: 0 },
107
+ elbowR: { x: -1.40, y: 0, z: 0 },
108
+ chest: { x: -0.05, y: 0, z: 0 },
109
+ },
110
+ },
111
+
112
+ // ─── ACTION ───
113
+ {
114
+ id: 'walk-step',
115
+ label: 'Walking step',
116
+ group: 'Action',
117
+ pose: {
118
+ shoulderL: { x: -0.55, y: 0, z: 0.05 },
119
+ shoulderR: { x: 0.55, y: 0, z: -0.05 },
120
+ elbowL: { x: -0.50, y: 0, z: 0 },
121
+ elbowR: { x: -0.50, y: 0, z: 0 },
122
+ hipL: { x: -0.55, y: 0, z: 0 },
123
+ hipR: { x: 0.35, y: 0, z: 0 },
124
+ kneeR: { x: 0.55, y: 0, z: 0 },
125
+ ankleL: { x: -0.10, y: 0, z: 0 },
126
+ ankleR: { x: 0.25, y: 0, z: 0 },
127
+ chest: { x: 0.08, y: 0, z: 0 },
128
+ },
129
+ },
130
+ {
131
+ id: 'run',
132
+ label: 'Running',
133
+ group: 'Action',
134
+ pose: {
135
+ chest: { x: 0.18, y: 0, z: 0 },
136
+ shoulderL: { x: -1.10, y: 0, z: 0.10 },
137
+ shoulderR: { x: 1.10, y: 0, z: -0.10 },
138
+ elbowL: { x: -1.50, y: 0, z: 0 },
139
+ elbowR: { x: -1.50, y: 0, z: 0 },
140
+ hipL: { x: -1.10, y: 0, z: 0 },
141
+ hipR: { x: 0.50, y: 0, z: 0 },
142
+ kneeL: { x: 1.20, y: 0, z: 0 },
143
+ kneeR: { x: 0.30, y: 0, z: 0 },
144
+ ankleL: { x: -0.30, y: 0, z: 0 },
145
+ },
146
+ },
147
+ {
148
+ id: 'jump',
149
+ label: 'Jumping',
150
+ group: 'Action',
151
+ pose: {
152
+ shoulderL: { x: 0, y: 0, z: PI * 0.85 },
153
+ shoulderR: { x: 0, y: 0, z: -PI * 0.85 },
154
+ elbowL: { x: -0.20, y: 0, z: 0 },
155
+ elbowR: { x: -0.20, y: 0, z: 0 },
156
+ hipL: { x: -0.55, y: 0, z: 0 },
157
+ hipR: { x: -0.55, y: 0, z: 0 },
158
+ kneeL: { x: 1.10, y: 0, z: 0 },
159
+ kneeR: { x: 1.10, y: 0, z: 0 },
160
+ ankleL: { x: -0.40, y: 0, z: 0 },
161
+ ankleR: { x: -0.40, y: 0, z: 0 },
162
+ chest: { x: -0.10, y: 0, z: 0 },
163
+ rootPosition: { x: 0, y: 0.20, z: 0 },
164
+ },
165
+ },
166
+ {
167
+ id: 'punch',
168
+ label: 'Punch (right)',
169
+ group: 'Action',
170
+ pose: {
171
+ chest: { x: 0, y: -0.30, z: 0 },
172
+ shoulderL: { x: -0.40, y: 0, z: 0.20 },
173
+ shoulderR: { x: -HALF, y: 0, z: -0.30 },
174
+ elbowL: { x: -1.80, y: 0, z: 0 },
175
+ elbowR: { x: -0.15, y: 0, z: 0 },
176
+ wristL: { x: -0.20, y: 0, z: 0 },
177
+ hipL: { x: -0.10, y: 0, z: 0.10 },
178
+ hipR: { x: -0.20, y: 0, z: 0 },
179
+ kneeL: { x: 0.30, y: 0, z: 0 },
180
+ kneeR: { x: 0.20, y: 0, z: 0 },
181
+ },
182
+ },
183
+ {
184
+ id: 'archery',
185
+ label: 'Archery',
186
+ group: 'Action',
187
+ pose: {
188
+ chest: { x: 0, y: -0.35, z: 0 },
189
+ head: { x: 0, y: 0.10, z: 0 },
190
+ shoulderL: { x: 0, y: 0, z: HALF + 0.05 },
191
+ shoulderR: { x: 0, y: 0, z: -0.40 },
192
+ elbowL: { x: -0.05, y: 0, z: 0 },
193
+ elbowR: { x: -2.10, y: 0, z: 0 },
194
+ wristR: { x: 0, y: 0.20, z: 0 },
195
+ hipL: { x: 0, y: 0, z: 0.10 },
196
+ hipR: { x: 0, y: 0, z: -0.10 },
197
+ },
198
+ },
199
+ {
200
+ id: 'superhero-landing',
201
+ label: 'Superhero landing',
202
+ group: 'Action',
203
+ pose: {
204
+ pelvis: { x: 0, y: 0.25, z: 0 },
205
+ spine: { x: 0.20, y: 0, z: 0 },
206
+ chest: { x: 0.25, y: 0, z: 0 },
207
+ head: { x: 0.30, y: 0, z: 0 },
208
+ shoulderL: { x: 0, y: 0, z: HALF + 0.30 },
209
+ shoulderR: { x: -0.20, y: 0, z: -0.15 },
210
+ elbowL: { x: -0.40, y: 0, z: 0 },
211
+ elbowR: { x: -1.20, y: 0, z: 0 },
212
+ hipL: { x: -1.20, y: 0, z: 0.30 },
213
+ hipR: { x: -0.30, y: 0, z: -0.10 },
214
+ kneeL: { x: 2.20, y: 0, z: 0 },
215
+ kneeR: { x: 0.40, y: 0, z: 0 },
216
+ ankleL: { x: -0.30, y: 0, z: 0 },
217
+ rootPosition: { x: 0, y: -0.42, z: 0 },
218
+ },
219
+ },
220
+
221
+ // ─── SITTING & FLOOR ───
222
+ {
223
+ id: 'sit-chair',
224
+ label: 'Sitting (chair)',
225
+ group: 'Sitting & Floor',
226
+ pose: {
227
+ hipL: { x: -HALF, y: 0, z: 0 },
228
+ hipR: { x: -HALF, y: 0, z: 0 },
229
+ kneeL: { x: HALF, y: 0, z: 0 },
230
+ kneeR: { x: HALF, y: 0, z: 0 },
231
+ shoulderL: { x: -0.20, y: 0, z: 0.15 },
232
+ shoulderR: { x: -0.20, y: 0, z: -0.15 },
233
+ elbowL: { x: -0.40, y: 0, z: 0 },
234
+ elbowR: { x: -0.40, y: 0, z: 0 },
235
+ rootPosition: { x: 0, y: -0.42, z: 0 },
236
+ },
237
+ },
238
+ {
239
+ id: 'sit-floor',
240
+ label: 'Sitting (floor)',
241
+ group: 'Sitting & Floor',
242
+ pose: {
243
+ hipL: { x: -1.85, y: 0.35, z: 0.40 },
244
+ hipR: { x: -1.85, y: -0.35, z: -0.40 },
245
+ kneeL: { x: 1.80, y: 0, z: 0 },
246
+ kneeR: { x: 1.80, y: 0, z: 0 },
247
+ shoulderL: { x: 0, y: 0, z: 0.20 },
248
+ shoulderR: { x: 0, y: 0, z: -0.20 },
249
+ elbowL: { x: -0.40, y: 0, z: 0 },
250
+ elbowR: { x: -0.40, y: 0, z: 0 },
251
+ rootPosition: { x: 0, y: -0.80, z: 0 },
252
+ },
253
+ },
254
+ {
255
+ id: 'kneel',
256
+ label: 'Kneeling',
257
+ group: 'Sitting & Floor',
258
+ pose: {
259
+ hipL: { x: -0.10, y: 0, z: 0 },
260
+ hipR: { x: -HALF, y: 0, z: 0 },
261
+ kneeL: { x: 0.20, y: 0, z: 0 },
262
+ kneeR: { x: HALF + 0.40, y: 0, z: 0 },
263
+ ankleL: { x: -0.20, y: 0, z: 0 },
264
+ shoulderL: { x: -0.20, y: 0, z: 0.10 },
265
+ shoulderR: { x: -0.20, y: 0, z: -0.10 },
266
+ elbowL: { x: -0.30, y: 0, z: 0 },
267
+ elbowR: { x: -0.30, y: 0, z: 0 },
268
+ rootPosition: { x: 0, y: -0.42, z: 0 },
269
+ },
270
+ },
271
+ {
272
+ id: 'crouch',
273
+ label: 'Crouching',
274
+ group: 'Sitting & Floor',
275
+ pose: {
276
+ hipL: { x: -1.50, y: 0, z: 0.30 },
277
+ hipR: { x: -1.50, y: 0, z: -0.30 },
278
+ kneeL: { x: 2.20, y: 0, z: 0 },
279
+ kneeR: { x: 2.20, y: 0, z: 0 },
280
+ ankleL: { x: -0.40, y: 0, z: 0 },
281
+ ankleR: { x: -0.40, y: 0, z: 0 },
282
+ chest: { x: 0.30, y: 0, z: 0 },
283
+ shoulderL: { x: -0.30, y: 0, z: 0.20 },
284
+ shoulderR: { x: -0.30, y: 0, z: -0.20 },
285
+ elbowL: { x: -0.60, y: 0, z: 0 },
286
+ elbowR: { x: -0.60, y: 0, z: 0 },
287
+ rootPosition: { x: 0, y: -0.55, z: 0 },
288
+ },
289
+ },
290
+ {
291
+ id: 'thinker',
292
+ label: 'The thinker',
293
+ group: 'Sitting & Floor',
294
+ pose: {
295
+ hipL: { x: -HALF, y: 0, z: 0 },
296
+ hipR: { x: -HALF, y: 0.20, z: -0.05 },
297
+ kneeL: { x: HALF, y: 0, z: 0 },
298
+ kneeR: { x: HALF, y: 0, z: 0 },
299
+ chest: { x: 0.30, y: -0.20, z: 0 },
300
+ spine: { x: 0.15, y: 0, z: 0 },
301
+ head: { x: 0.40, y: 0, z: 0 },
302
+ shoulderL: { x: -0.60, y: 0, z: 0.05 },
303
+ shoulderR: { x: -1.30, y: 0, z: -0.25 },
304
+ elbowL: { x: -0.50, y: 0, z: 0 },
305
+ elbowR: { x: -2.20, y: 0, z: 0 },
306
+ wristR: { x: -0.30, y: 0, z: 0 },
307
+ rootPosition: { x: 0, y: -0.42, z: 0 },
308
+ },
309
+ },
310
+
311
+ // ─── EXPRESSIVE ───
312
+ {
313
+ id: 'praying',
314
+ label: 'Praying',
315
+ group: 'Expressive',
316
+ pose: {
317
+ head: { x: 0.20, y: 0, z: 0 },
318
+ chest: { x: 0.05, y: 0, z: 0 },
319
+ shoulderL: { x: -0.30, y: -0.45, z: 0.45 },
320
+ shoulderR: { x: -0.30, y: 0.45, z: -0.45 },
321
+ elbowL: { x: -1.80, y: 0, z: 0 },
322
+ elbowR: { x: -1.80, y: 0, z: 0 },
323
+ },
324
+ },
325
+ {
326
+ id: 'meditate',
327
+ label: 'Meditation',
328
+ group: 'Expressive',
329
+ pose: {
330
+ hipL: { x: -1.40, y: 0.50, z: 0.55 },
331
+ hipR: { x: -1.40, y: -0.50, z: -0.55 },
332
+ kneeL: { x: 2.10, y: 0, z: 0 },
333
+ kneeR: { x: 2.10, y: 0, z: 0 },
334
+ shoulderL: { x: -0.20, y: 0, z: 0.15 },
335
+ shoulderR: { x: -0.20, y: 0, z: -0.15 },
336
+ elbowL: { x: -0.30, y: 0, z: 0 },
337
+ elbowR: { x: -0.30, y: 0, z: 0 },
338
+ wristL: { x: -0.40, y: 0, z: 0 },
339
+ wristR: { x: -0.40, y: 0, z: 0 },
340
+ head: { x: 0.10, y: 0, z: 0 },
341
+ rootPosition: { x: 0, y: -0.80, z: 0 },
342
+ },
343
+ },
344
+ {
345
+ id: 'warrior2',
346
+ label: 'Warrior II (yoga)',
347
+ group: 'Expressive',
348
+ pose: {
349
+ pelvis: { x: 0, y: 0.30, z: 0 },
350
+ chest: { x: 0, y: -0.30, z: 0 },
351
+ head: { x: 0, y: 0.50, z: 0 },
352
+ shoulderL: { x: 0, y: 0, z: HALF },
353
+ shoulderR: { x: 0, y: 0, z: -HALF },
354
+ hipL: { x: -0.10, y: 0.35, z: 0.20 },
355
+ hipR: { x: -0.15, y: 0, z: -0.45 },
356
+ kneeL: { x: 1.40, y: 0, z: 0 },
357
+ ankleL: { x: -0.20, y: 0, z: 0 },
358
+ rootPosition: { x: 0, y: -0.18, z: 0 },
359
+ },
360
+ },
361
+ {
362
+ id: 'arabesque',
363
+ label: 'Arabesque (ballet)',
364
+ group: 'Expressive',
365
+ pose: {
366
+ pelvis: { x: 0.25, y: 0, z: 0 },
367
+ spine: { x: 0.10, y: 0, z: 0 },
368
+ chest: { x: -0.10, y: 0, z: 0 },
369
+ head: { x: -0.30, y: 0, z: 0 },
370
+ shoulderL: { x: 0, y: 0, z: 0.95 },
371
+ shoulderR: { x: 0, y: 0, z: -0.95 },
372
+ elbowL: { x: -0.20, y: 0, z: 0 },
373
+ elbowR: { x: -0.20, y: 0, z: 0 },
374
+ hipL: { x: 0.50, y: 0, z: 0.10 },
375
+ hipR: { x: 0, y: 0, z: -0.10 },
376
+ ankleR: { x: -0.30, y: 0, z: 0 },
377
+ },
378
+ },
379
+ {
380
+ id: 'flex',
381
+ label: 'Flex (muscle pose)',
382
+ group: 'Expressive',
383
+ pose: {
384
+ chest: { x: 0, y: -0.10, z: 0 },
385
+ shoulderL: { x: 0, y: -0.40, z: HALF + 0.10 },
386
+ shoulderR: { x: 0, y: 0.40, z: -HALF - 0.10 },
387
+ elbowL: { x: -2.00, y: 0, z: 0 },
388
+ elbowR: { x: -2.00, y: 0, z: 0 },
389
+ wristL: { x: -0.30, y: 0, z: 0 },
390
+ wristR: { x: -0.30, y: 0, z: 0 },
391
+ head: { x: 0, y: -0.20, z: -0.05 },
392
+ },
393
+ },
394
+ {
395
+ id: 'point',
396
+ label: 'Pointing',
397
+ group: 'Expressive',
398
+ pose: {
399
+ chest: { x: 0, y: 0.20, z: 0 },
400
+ shoulderL: { x: 0, y: 0, z: 0.15 },
401
+ shoulderR: { x: -HALF + 0.20, y: 0, z: -0.40 },
402
+ elbowR: { x: -0.10, y: 0, z: 0 },
403
+ wristR: { x: -0.15, y: 0, z: 0 },
404
+ head: { x: 0, y: -0.30, z: 0 },
405
+ },
406
+ },
407
+ ];
408
+
409
+ export function getPresetById(id) {
410
+ return PRESETS.find((p) => p.id === id) || null;
411
+ }
412
+
413
+ export function getPresetsByGroup() {
414
+ const map = {};
415
+ for (const group of PRESET_GROUPS) map[group] = [];
416
+ for (const preset of PRESETS) {
417
+ if (!map[preset.group]) map[preset.group] = [];
418
+ map[preset.group].push(preset);
419
+ }
420
+ return map;
421
+ }
@@ -0,0 +1,124 @@
1
+ // Vendored from the three.ws monorepo: api/_lib/pump-vanity.js. Kept in sync
2
+ // by hand so this package is self-contained when published to npm. Do not edit
3
+ // here — edit the canonical api/_lib/pump-vanity.js and re-copy.
4
+
5
+ // Solana vanity address generator for pump.fun mint Keypairs.
6
+ //
7
+ // Vendored from nirholas/solana-wallet-toolkit (MIT) — TypeScript reference
8
+ // at typescript/src/lib/{generator,matcher,validation}.ts. Rewritten as a
9
+ // single JS module so it works inside Vercel's serverless runtime without
10
+ // a transpile step. Adds prefix + suffix + ignoreCase + async yielding so
11
+ // long prefixes don't block the event loop while we wait.
12
+
13
+ import { Keypair } from '@solana/web3.js';
14
+
15
+ // Solana base58 alphabet — excludes 0, O, I, l to avoid look-alikes.
16
+ export const BASE58_ALPHABET = '123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz';
17
+ const BASE58_CHARS = new Set(BASE58_ALPHABET);
18
+
19
+ const MAX_PATTERN_LENGTH = 6;
20
+ const DEFAULT_MAX_ITERATIONS = 2_000_000;
21
+ const YIELD_EVERY = 10_000;
22
+
23
+ function validatePattern(pattern, label) {
24
+ if (typeof pattern !== 'string' || pattern.length === 0) {
25
+ throw vanityError('invalid_vanity', `${label} must be a non-empty string`);
26
+ }
27
+ if (pattern !== pattern.trim()) {
28
+ throw vanityError('invalid_vanity', `${label} contains whitespace`);
29
+ }
30
+ if (pattern.length > MAX_PATTERN_LENGTH) {
31
+ throw vanityError(
32
+ 'invalid_vanity',
33
+ `${label} length ${pattern.length} exceeds max ${MAX_PATTERN_LENGTH} (would take an extremely long time)`,
34
+ );
35
+ }
36
+ for (let i = 0; i < pattern.length; i++) {
37
+ if (!BASE58_CHARS.has(pattern[i])) {
38
+ throw vanityError(
39
+ 'invalid_vanity',
40
+ `${label}: invalid base58 char '${pattern[i]}' at position ${i + 1}`,
41
+ );
42
+ }
43
+ }
44
+ }
45
+
46
+ function vanityError(code, msg) {
47
+ return Object.assign(new Error(msg), { status: 400, code });
48
+ }
49
+
50
+ export function isValidVanityPrefix(prefix) {
51
+ if (!prefix || typeof prefix !== 'string') return false;
52
+ if (prefix.length > MAX_PATTERN_LENGTH) return false;
53
+ for (const c of prefix) if (!BASE58_CHARS.has(c)) return false;
54
+ return true;
55
+ }
56
+
57
+ // Estimated attempts ≈ 58^n (per pattern position). Used by callers to set
58
+ // a reasonable maxIterations and to surface difficulty in error messages.
59
+ export function estimateAttempts({ prefix, suffix, ignoreCase = false } = {}) {
60
+ const len = (prefix?.length || 0) + (suffix?.length || 0);
61
+ if (len === 0) return 1;
62
+ const alphabetSize = ignoreCase ? 33 : 58; // lowercase-folded alphabet ≈ 33 distinct
63
+ return Math.pow(alphabetSize, len);
64
+ }
65
+
66
+ /**
67
+ * Grind a Solana Keypair whose base58 address matches a prefix and/or suffix.
68
+ *
69
+ * @param {object} opts
70
+ * @param {string} [opts.prefix] — required base58 prefix
71
+ * @param {string} [opts.suffix] — required base58 suffix
72
+ * @param {boolean} [opts.ignoreCase] — case-insensitive match
73
+ * @param {number} [opts.maxIterations] — hard cap (default 2M)
74
+ * @param {(attempts:number,rate:number)=>void} [opts.onProgress] — every 1k attempts
75
+ * @returns {Promise<{ keypair: Keypair, iterations: number, durationMs: number }>}
76
+ */
77
+ export async function grindMintKeypair({
78
+ prefix,
79
+ suffix,
80
+ ignoreCase = false,
81
+ maxIterations = DEFAULT_MAX_ITERATIONS,
82
+ onProgress,
83
+ } = {}) {
84
+ if (!prefix && !suffix) {
85
+ const kp = Keypair.generate();
86
+ return { keypair: kp, iterations: 1, durationMs: 0 };
87
+ }
88
+ if (prefix) validatePattern(prefix, 'prefix');
89
+ if (suffix) validatePattern(suffix, 'suffix');
90
+
91
+ const targetPrefix = prefix ? (ignoreCase ? prefix.toLowerCase() : prefix) : null;
92
+ const targetSuffix = suffix ? (ignoreCase ? suffix.toLowerCase() : suffix) : null;
93
+ const pLen = targetPrefix?.length || 0;
94
+ const sLen = targetSuffix?.length || 0;
95
+
96
+ const start = Date.now();
97
+ let lastProgressAt = start;
98
+ let lastProgressAttempts = 0;
99
+
100
+ for (let i = 1; i <= maxIterations; i++) {
101
+ const kp = Keypair.generate();
102
+ const addr = kp.publicKey.toBase58();
103
+
104
+ const head = ignoreCase ? addr.substring(0, pLen).toLowerCase() : addr.substring(0, pLen);
105
+ if (targetPrefix && head !== targetPrefix) {
106
+ if (i % YIELD_EVERY === 0) await new Promise((r) => setImmediate(r));
107
+ continue;
108
+ }
109
+ const tail = ignoreCase ? addr.substring(addr.length - sLen).toLowerCase() : addr.substring(addr.length - sLen);
110
+ if (targetSuffix && tail !== targetSuffix) {
111
+ if (i % YIELD_EVERY === 0) await new Promise((r) => setImmediate(r));
112
+ continue;
113
+ }
114
+
115
+ return { keypair: kp, iterations: i, durationMs: Date.now() - start };
116
+ }
117
+
118
+ throw Object.assign(
119
+ new Error(
120
+ `vanity ${prefix ? `prefix '${prefix}'` : ''}${prefix && suffix ? ' + ' : ''}${suffix ? `suffix '${suffix}'` : ''} not found in ${maxIterations} attempts (estimated ~${Math.round(estimateAttempts({ prefix, suffix, ignoreCase })).toLocaleString()})`,
121
+ ),
122
+ { status: 504, code: 'vanity_timeout' },
123
+ );
124
+ }