-
Notifications
You must be signed in to change notification settings - Fork 36
Expand file tree
/
Copy path50-vanity-mint.ts
More file actions
204 lines (192 loc) · 6.38 KB
/
Copy path50-vanity-mint.ts
File metadata and controls
204 lines (192 loc) · 6.38 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
/**
* Example 50: Vanity Mints
*
* Category: AMM & Advanced
*
* Grinds keypairs until one lands a chosen suffix, on a bounded attempt
* budget so the run finishes in seconds, then feeds the winner straight into
* createV2Instruction. Shows how the cost of a pattern scales, and how the
* SDK refuses patterns that can never match.
*
* Run: npm run example 50
*/
import {
PUMP_SDK,
BASE58_ALPHABET,
MAX_VANITY_PATTERN_LENGTH,
VanityMintMaxAttemptsError,
VanityMintPatternError,
estimateVanityMintAttempts,
generateVanityMint,
bondingCurvePda,
} from "@nirholas/pump-sdk";
import { heading, row } from "./_lib/format";
import { loadWallet } from "./_lib/wallet";
/** The pattern a grind is searching for. */
export interface VanityPattern {
prefix?: string;
suffix?: string;
caseInsensitive?: boolean;
}
/**
* The predicate the grind is testing, extracted.
*
* `generateVanityMint` applies exactly this test to every generated address:
* case folding first when requested, then a prefix and suffix check. Having
* it as a function means a found keypair can be verified independently of the
* loop that produced it.
*/
export function matchesVanityPattern(
address: string,
pattern: VanityPattern,
): boolean {
const fold = (value: string) =>
pattern.caseInsensitive === true ? value.toLowerCase() : value;
const candidate = fold(address);
const prefixOk =
pattern.prefix === undefined ||
pattern.prefix === "" ||
candidate.startsWith(fold(pattern.prefix));
const suffixOk =
pattern.suffix === undefined ||
pattern.suffix === "" ||
candidate.endsWith(fold(pattern.suffix));
return prefixOk && suffixOk;
}
/** Characters that can never appear in a Solana address. */
export function unmatchableCharacters(pattern: string): string[] {
const alphabet = new Set(BASE58_ALPHABET);
return [...pattern].filter((char) => !alphabet.has(char));
}
/**
* Expected wall-clock seconds for a pattern at a measured grind rate.
*
* The estimate is a mean, not a bound: keypair generation is memoryless, so
* an individual grind can take several times this or finish immediately.
*/
export function estimateSeconds(
pattern: VanityPattern,
attemptsPerSecond: number,
): number {
if (attemptsPerSecond <= 0) return Infinity;
return estimateVanityMintAttempts(pattern) / attemptsPerSecond;
}
export async function main(): Promise<void> {
const wallet = loadWallet();
heading("What a pattern costs");
row("Base58 alphabet size", BASE58_ALPHABET.length);
row("Max pattern length", MAX_VANITY_PATTERN_LENGTH);
for (const suffix of ["w", "ws", "pump", "wswsw"]) {
row(
`suffix "${suffix}"`,
`${estimateVanityMintAttempts({ suffix }).toLocaleString()} attempts (mean)`,
);
}
console.log(
"\nEach extra character multiplies the work by 58. Node handles two or",
);
console.log(
"three characters comfortably; past that, use the Rust generator in",
);
console.log("rust/, which grinds several orders of magnitude faster.");
heading("Grinding a two-character suffix");
const pattern: VanityPattern = { suffix: "ws" };
const budget = 400_000;
row("Pattern", `suffix "${String(pattern.suffix)}"`);
row("Attempt budget", budget.toLocaleString());
let lastRate = 0;
const result = await generateVanityMint({
...pattern,
maxAttempts: budget,
onProgress: ({ attempts, attemptsPerSecond }) => {
lastRate = attemptsPerSecond;
row(
` ${attempts.toLocaleString()} attempts`,
`${Math.round(attemptsPerSecond).toLocaleString()} keys/sec`,
);
},
});
const address = result.keypair.publicKey.toBase58();
const rate = result.durationMs > 0
? Math.round((result.attempts / result.durationMs) * 1000)
: lastRate;
row("Found", address);
row("Attempts", result.attempts.toLocaleString());
row("Duration", `${result.durationMs} ms`);
row("Rate", `${rate.toLocaleString()} keys/sec`);
row("Matches pattern", matchesVanityPattern(address, pattern));
row(
"Estimated seconds for \"pump\"",
estimateSeconds({ suffix: "pump" }, rate).toFixed(1),
);
heading("Launching with the grinded mint");
const ix = await PUMP_SDK.createV2Instruction({
mint: result.keypair.publicKey,
name: "Vanity Example",
symbol: "VNTY",
uri: "https://example.com/metadata.json",
creator: wallet.publicKey,
user: wallet.publicKey,
mayhemMode: false,
});
row("Program", ix.programId.toBase58());
row("Accounts", ix.keys.length);
row("Data bytes", ix.data.length);
row("Bonding curve PDA", bondingCurvePda(result.keypair.publicKey).toBase58());
console.log(
"\nThe suffix is cosmetic: a grinded mint is an ordinary keypair and the",
);
console.log(
"create instruction is byte-identical to one built from Keypair.generate.",
);
console.log("It signs the launch transaction alongside the wallet.");
heading("Patterns that can never match");
for (const bad of ["p0mp", "pOmp", "pImp", "pllp"]) {
const invalid = unmatchableCharacters(bad);
const rejection = await generateVanityMint({
suffix: bad,
maxAttempts: 1,
}).then(
() => "ACCEPTED (unexpected)",
(error: unknown) =>
error instanceof VanityMintPatternError
? error.type
: error instanceof Error
? error.constructor.name
: String(error),
);
row(`suffix "${bad}"`, `${rejection} (bad chars: ${invalid.join("")})`);
}
console.log(
"\nBase58 omits 0, O, I and l so addresses cannot be misread. A pattern",
);
console.log(
"containing one has zero probability, and the SDK rejects it up front",
);
console.log("rather than grinding forever.");
heading("Exhausting a budget");
const exhausted = await generateVanityMint({
suffix: "pump",
maxAttempts: 5_000,
}).then(
() => "found (lucky)",
(error: unknown) =>
error instanceof VanityMintMaxAttemptsError
? `${error.name} after ${error.attempts.toLocaleString()} attempts`
: String(error),
);
row("suffix \"pump\", 5,000 budget", exhausted);
console.log(
"\nA four-character suffix averages over eleven million attempts, so a",
);
console.log(
"small budget almost always ends here. Always set maxAttempts in a",
);
console.log("request path, or pass an AbortSignal so the caller can cancel.");
}
if (require.main === module) {
main().catch((err) => {
console.error(err);
process.exit(1);
});
}