create-veil-app 0.3.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/README.md +135 -0
- package/dist/chunk-QGM4M3NI.js +37 -0
- package/dist/chunk-QWU7D5FV.js +2052 -0
- package/dist/dist-7HFJSUBN.js +7743 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +221 -0
- package/dist/templates-TJ2JYQDS.js +55 -0
- package/package.json +64 -0
|
@@ -0,0 +1,2052 @@
|
|
|
1
|
+
// src/cli.ts
|
|
2
|
+
import inquirer from "inquirer";
|
|
3
|
+
import chalk from "chalk";
|
|
4
|
+
import ora from "ora";
|
|
5
|
+
|
|
6
|
+
// src/scaffold.ts
|
|
7
|
+
import fs from "fs-extra";
|
|
8
|
+
import path from "path";
|
|
9
|
+
async function scaffoldProject(config) {
|
|
10
|
+
const projectPath = path.resolve(process.cwd(), config.projectName);
|
|
11
|
+
await fs.ensureDir(projectPath);
|
|
12
|
+
const srcDir = config.framework === "nextjs" ? "app" : "src";
|
|
13
|
+
const folders = [
|
|
14
|
+
srcDir,
|
|
15
|
+
`${srcDir}/components`,
|
|
16
|
+
`${srcDir}/components/privacy`,
|
|
17
|
+
`${srcDir}/components/ui`,
|
|
18
|
+
"lib",
|
|
19
|
+
"lib/privacy",
|
|
20
|
+
"lib/veil",
|
|
21
|
+
"hooks",
|
|
22
|
+
"contexts",
|
|
23
|
+
"types"
|
|
24
|
+
];
|
|
25
|
+
if (config.features.shadowpay) folders.push("lib/shadowpay");
|
|
26
|
+
if (config.features.voting) folders.push("lib/voting");
|
|
27
|
+
if (config.features.staking) folders.push("lib/staking");
|
|
28
|
+
if (config.features.multisig) folders.push("lib/multisig");
|
|
29
|
+
for (const folder of folders) {
|
|
30
|
+
await fs.ensureDir(path.join(projectPath, folder));
|
|
31
|
+
}
|
|
32
|
+
const files = [
|
|
33
|
+
// Root config files
|
|
34
|
+
{ path: "veil.config.ts", content: generateVeilConfig(config) },
|
|
35
|
+
{ path: ".env.example", content: generateEnvExample(config) },
|
|
36
|
+
{ path: "README.md", content: generateReadme(config) },
|
|
37
|
+
{ path: "package.json", content: generatePackageJson(config) },
|
|
38
|
+
{ path: "tsconfig.json", content: generateTsConfig(config) },
|
|
39
|
+
{ path: "tailwind.config.js", content: generateTailwindConfig(config) },
|
|
40
|
+
{ path: "postcss.config.js", content: generatePostcssConfig() },
|
|
41
|
+
// Privacy/Veil lib modules
|
|
42
|
+
{ path: "lib/privacy/login.ts", content: generateLoginTs() },
|
|
43
|
+
{ path: "lib/privacy/recovery.ts", content: generateRecoveryTs() },
|
|
44
|
+
{ path: "lib/privacy/access.ts", content: generateAccessTs() },
|
|
45
|
+
{ path: "lib/privacy/guarantees.ts", content: generateGuaranteesTs() },
|
|
46
|
+
{ path: "lib/privacy/index.ts", content: `export * from "./login.js";
|
|
47
|
+
export * from "./recovery.js";
|
|
48
|
+
export * from "./access.js";
|
|
49
|
+
export * from "./guarantees.js";
|
|
50
|
+
` },
|
|
51
|
+
// Veil SDK integration
|
|
52
|
+
{ path: "lib/veil/rpc.ts", content: generateRpcTs(config) },
|
|
53
|
+
{ path: "lib/veil/helius.ts", content: generateHeliusTs(config) },
|
|
54
|
+
{ path: "lib/veil/index.ts", content: `export * from "./rpc.js";
|
|
55
|
+
export * from "./helius.js";
|
|
56
|
+
` },
|
|
57
|
+
// Hooks
|
|
58
|
+
{ path: "hooks/useVeil.ts", content: generateVeilHooks(config) },
|
|
59
|
+
{ path: "hooks/useSdk.ts", content: generateSdkHooks() },
|
|
60
|
+
{ path: "hooks/index.ts", content: `export * from "./useVeil.js";
|
|
61
|
+
export * from "./useSdk.js";
|
|
62
|
+
` },
|
|
63
|
+
// Contexts
|
|
64
|
+
{ path: "contexts/VeilProvider.tsx", content: generateVeilProvider(config) },
|
|
65
|
+
{ path: "contexts/VeilSDKProvider.tsx", content: generateSdkProvider(config) },
|
|
66
|
+
{ path: "contexts/index.ts", content: `export * from "./VeilProvider.js";
|
|
67
|
+
export * from "./VeilSDKProvider.js";
|
|
68
|
+
` },
|
|
69
|
+
// Components
|
|
70
|
+
{ path: `${srcDir}/components/WalletButton.tsx`, content: generateWalletButton() },
|
|
71
|
+
{ path: `${srcDir}/components/privacy/PrivacyStatus.tsx`, content: generatePrivacyStatus() },
|
|
72
|
+
// App files
|
|
73
|
+
{ path: `${srcDir}/page.tsx`, content: generateAppEntry(config) },
|
|
74
|
+
{ path: `${srcDir}/layout.tsx`, content: generateLayoutTsx(config) },
|
|
75
|
+
{ path: `${srcDir}/globals.css`, content: generateGlobalsCss() },
|
|
76
|
+
{ path: `${srcDir}/providers.tsx`, content: generateProvidersTsx(config) }
|
|
77
|
+
];
|
|
78
|
+
if (config.framework === "nextjs") {
|
|
79
|
+
files.push({ path: "next.config.js", content: generateNextConfig() });
|
|
80
|
+
}
|
|
81
|
+
if (config.features.shadowpay) {
|
|
82
|
+
files.push({ path: "lib/shadowpay/index.ts", content: generateShadowPayModule(config) });
|
|
83
|
+
}
|
|
84
|
+
for (const file of files) {
|
|
85
|
+
await fs.writeFile(
|
|
86
|
+
path.join(projectPath, file.path),
|
|
87
|
+
file.content,
|
|
88
|
+
"utf-8"
|
|
89
|
+
);
|
|
90
|
+
}
|
|
91
|
+
const gitignore = `node_modules/
|
|
92
|
+
.env
|
|
93
|
+
.env.local
|
|
94
|
+
dist/
|
|
95
|
+
.next/
|
|
96
|
+
`;
|
|
97
|
+
await fs.writeFile(path.join(projectPath, ".gitignore"), gitignore, "utf-8");
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
// src/cli.ts
|
|
101
|
+
var TEMPLATE_INFO = {
|
|
102
|
+
// DeFi
|
|
103
|
+
dex: { name: "DEX Interface", description: "Private swaps via ShadowWire", category: "DeFi" },
|
|
104
|
+
lending: { name: "Lending Protocol", description: "Private lending positions", category: "DeFi" },
|
|
105
|
+
yield: { name: "Yield Farming", description: "Private stake amounts", category: "DeFi" },
|
|
106
|
+
pool: { name: "Liquidity Pool", description: "Private LP deposits", category: "DeFi" },
|
|
107
|
+
// DApp
|
|
108
|
+
gaming: { name: "Gaming App", description: "Private game assets & scores", category: "DApp" },
|
|
109
|
+
nft: { name: "NFT Marketplace", description: "Private bids & offers", category: "DApp" },
|
|
110
|
+
social: { name: "Social Platform", description: "Private messaging & identity", category: "DApp" },
|
|
111
|
+
governance: { name: "DAO Governance", description: "Private voting & proposals", category: "DApp" },
|
|
112
|
+
// Exchange
|
|
113
|
+
cex: { name: "Exchange Interface", description: "CEX-style with privacy", category: "Exchange" },
|
|
114
|
+
aggregator: { name: "DEX Aggregator", description: "Private swap routing", category: "Exchange" },
|
|
115
|
+
trading: { name: "Trading Dashboard", description: "Private order books", category: "Exchange" },
|
|
116
|
+
// Wallet
|
|
117
|
+
wallet: { name: "Multi-sig Wallet", description: "Stealth signers & treasury", category: "Wallet" },
|
|
118
|
+
portfolio: { name: "Portfolio Tracker", description: "Private holdings view", category: "Wallet" },
|
|
119
|
+
payments: { name: "Payment App", description: "Full ShadowPay integration", category: "Wallet" },
|
|
120
|
+
// Basic
|
|
121
|
+
basic: { name: "Basic Starter", description: "Minimal privacy-first app", category: "Starter" }
|
|
122
|
+
};
|
|
123
|
+
async function runInit(options) {
|
|
124
|
+
const answers = await promptUser(options);
|
|
125
|
+
const spinner = ora({
|
|
126
|
+
text: "Creating your project with Veil privacy...",
|
|
127
|
+
color: "cyan"
|
|
128
|
+
}).start();
|
|
129
|
+
try {
|
|
130
|
+
await scaffoldProject(answers);
|
|
131
|
+
spinner.succeed(chalk.green("Veil initialized successfully."));
|
|
132
|
+
printNextSteps(answers.projectName);
|
|
133
|
+
} catch (error) {
|
|
134
|
+
spinner.fail(chalk.red("Failed to initialize project."));
|
|
135
|
+
console.error(error);
|
|
136
|
+
process.exit(1);
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
async function promptUser(options) {
|
|
140
|
+
const hasAllOptions = options.name && options.template && options.framework && options.network;
|
|
141
|
+
if (hasAllOptions) {
|
|
142
|
+
const shadowPayMode2 = options.shadowPay ? options.shadowPayMode || "app" : false;
|
|
143
|
+
return {
|
|
144
|
+
projectName: options.name,
|
|
145
|
+
template: options.template,
|
|
146
|
+
framework: options.framework,
|
|
147
|
+
helius: options.helius ?? true,
|
|
148
|
+
shadowPay: shadowPayMode2,
|
|
149
|
+
network: options.network,
|
|
150
|
+
features: {
|
|
151
|
+
identity: true,
|
|
152
|
+
recovery: true,
|
|
153
|
+
voting: true,
|
|
154
|
+
staking: true,
|
|
155
|
+
multisig: true,
|
|
156
|
+
shadowpay: !!options.shadowPay
|
|
157
|
+
}
|
|
158
|
+
};
|
|
159
|
+
}
|
|
160
|
+
const questions = [];
|
|
161
|
+
if (!options.name) {
|
|
162
|
+
questions.push({
|
|
163
|
+
type: "input",
|
|
164
|
+
name: "projectName",
|
|
165
|
+
message: "Project name:",
|
|
166
|
+
default: "my-veil-app",
|
|
167
|
+
validate: (input) => {
|
|
168
|
+
if (/^[a-z0-9-]+$/.test(input)) return true;
|
|
169
|
+
return "Project name must be lowercase with hyphens only";
|
|
170
|
+
}
|
|
171
|
+
});
|
|
172
|
+
}
|
|
173
|
+
const initialAnswers = await inquirer.prompt(questions);
|
|
174
|
+
let selectedTemplate = options.template || "basic";
|
|
175
|
+
if (!options.template) {
|
|
176
|
+
const categoryAnswer = await inquirer.prompt([{
|
|
177
|
+
type: "list",
|
|
178
|
+
name: "category",
|
|
179
|
+
message: "What are you building?",
|
|
180
|
+
choices: [
|
|
181
|
+
{ name: chalk.cyan("\u{1F3E6} DeFi") + " \u2014 DEX, Lending, Yield, Pools", value: "defi" },
|
|
182
|
+
{ name: chalk.magenta("\u{1F3AE} DApp") + " \u2014 Gaming, NFT, Social, Governance", value: "dapp" },
|
|
183
|
+
{ name: chalk.yellow("\u{1F4CA} Exchange") + " \u2014 CEX, Aggregator, Trading", value: "exchange" },
|
|
184
|
+
{ name: chalk.green("\u{1F45B} Wallet") + " \u2014 Multisig, Portfolio, Payments", value: "wallet" },
|
|
185
|
+
{ name: chalk.dim("\u{1F4E6} Basic") + " \u2014 Minimal starter template", value: "basic" }
|
|
186
|
+
]
|
|
187
|
+
}]);
|
|
188
|
+
if (categoryAnswer.category === "basic") {
|
|
189
|
+
selectedTemplate = "basic";
|
|
190
|
+
} else {
|
|
191
|
+
const templateChoices = Object.entries(TEMPLATE_INFO).filter(([_, info]) => info.category.toLowerCase() === categoryAnswer.category).map(([key, info]) => ({
|
|
192
|
+
name: `${info.name} \u2014 ${info.description}`,
|
|
193
|
+
value: key
|
|
194
|
+
}));
|
|
195
|
+
const templateAnswer = await inquirer.prompt([{
|
|
196
|
+
type: "list",
|
|
197
|
+
name: "template",
|
|
198
|
+
message: "Select template:",
|
|
199
|
+
choices: templateChoices
|
|
200
|
+
}]);
|
|
201
|
+
selectedTemplate = templateAnswer.template;
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
let framework = options.framework || "nextjs";
|
|
205
|
+
if (!options.framework) {
|
|
206
|
+
const frameworkAnswer = await inquirer.prompt([{
|
|
207
|
+
type: "list",
|
|
208
|
+
name: "framework",
|
|
209
|
+
message: "Frontend framework:",
|
|
210
|
+
choices: [
|
|
211
|
+
{ name: "Next.js (recommended)", value: "nextjs" },
|
|
212
|
+
{ name: "Vite + React", value: "vite" }
|
|
213
|
+
]
|
|
214
|
+
}]);
|
|
215
|
+
framework = frameworkAnswer.framework;
|
|
216
|
+
}
|
|
217
|
+
let helius = options.helius ?? true;
|
|
218
|
+
if (options.helius === void 0) {
|
|
219
|
+
const heliusAnswer = await inquirer.prompt([{
|
|
220
|
+
type: "confirm",
|
|
221
|
+
name: "helius",
|
|
222
|
+
message: "Enable Helius RPC? (recommended for production)",
|
|
223
|
+
default: true
|
|
224
|
+
}]);
|
|
225
|
+
helius = heliusAnswer.helius;
|
|
226
|
+
}
|
|
227
|
+
let shadowPayMode = false;
|
|
228
|
+
const wantsShadowPay = options.shadowPay ?? true;
|
|
229
|
+
if (wantsShadowPay) {
|
|
230
|
+
console.log();
|
|
231
|
+
console.log(chalk.yellow("\u26A1 ShadowPay uses mainnet for real private payments"));
|
|
232
|
+
console.log(chalk.dim(" Veil features (voting, staking, multisig) work on devnet"));
|
|
233
|
+
console.log();
|
|
234
|
+
const shadowPayAnswer = await inquirer.prompt([{
|
|
235
|
+
type: "list",
|
|
236
|
+
name: "mode",
|
|
237
|
+
message: "ShadowPay mode:",
|
|
238
|
+
choices: [
|
|
239
|
+
{ name: "App \u2014 Receive private payments from users", value: "app" },
|
|
240
|
+
{ name: "Wallet \u2014 Send private payments to apps/users", value: "wallet" },
|
|
241
|
+
{ name: "Skip \u2014 Don't include ShadowPay", value: "skip" }
|
|
242
|
+
]
|
|
243
|
+
}]);
|
|
244
|
+
if (shadowPayAnswer.mode !== "skip") {
|
|
245
|
+
shadowPayMode = shadowPayAnswer.mode;
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
let network = options.network || "devnet";
|
|
249
|
+
if (!options.network) {
|
|
250
|
+
const networkAnswer = await inquirer.prompt([{
|
|
251
|
+
type: "list",
|
|
252
|
+
name: "network",
|
|
253
|
+
message: "Veil features network:",
|
|
254
|
+
choices: [
|
|
255
|
+
{ name: "Devnet (recommended)", value: "devnet" },
|
|
256
|
+
{ name: "Localnet", value: "localnet" }
|
|
257
|
+
]
|
|
258
|
+
}]);
|
|
259
|
+
network = networkAnswer.network;
|
|
260
|
+
}
|
|
261
|
+
return {
|
|
262
|
+
projectName: options.name || initialAnswers.projectName,
|
|
263
|
+
template: selectedTemplate,
|
|
264
|
+
framework,
|
|
265
|
+
helius,
|
|
266
|
+
shadowPay: shadowPayMode,
|
|
267
|
+
network,
|
|
268
|
+
features: {
|
|
269
|
+
identity: true,
|
|
270
|
+
recovery: true,
|
|
271
|
+
voting: true,
|
|
272
|
+
staking: true,
|
|
273
|
+
multisig: true,
|
|
274
|
+
shadowpay: shadowPayMode !== false
|
|
275
|
+
}
|
|
276
|
+
};
|
|
277
|
+
}
|
|
278
|
+
function printNextSteps(projectName) {
|
|
279
|
+
console.log();
|
|
280
|
+
console.log(chalk.bold("Next steps:"));
|
|
281
|
+
console.log(chalk.dim("1."), `cd ${projectName}`);
|
|
282
|
+
console.log(chalk.dim("2."), "cp .env.example .env");
|
|
283
|
+
console.log(chalk.dim("3."), "pnpm install");
|
|
284
|
+
console.log(chalk.dim("4."), "pnpm dev");
|
|
285
|
+
console.log();
|
|
286
|
+
console.log(
|
|
287
|
+
chalk.dim("Privacy guarantees are documented in"),
|
|
288
|
+
chalk.cyan("/privacy/guarantees.ts")
|
|
289
|
+
);
|
|
290
|
+
console.log();
|
|
291
|
+
}
|
|
292
|
+
async function runAdd(options) {
|
|
293
|
+
const fsExtra = await import("fs-extra");
|
|
294
|
+
const fs2 = fsExtra.default;
|
|
295
|
+
const path2 = await import("path");
|
|
296
|
+
const cwd = process.cwd();
|
|
297
|
+
const packageJsonPath = path2.join(cwd, "package.json");
|
|
298
|
+
if (!await fs2.pathExists(packageJsonPath)) {
|
|
299
|
+
console.log(chalk.red("Error: No package.json found."));
|
|
300
|
+
console.log(chalk.dim("Run this command from the root of your project."));
|
|
301
|
+
process.exit(1);
|
|
302
|
+
}
|
|
303
|
+
const packageJson = await fs2.readJson(packageJsonPath);
|
|
304
|
+
const deps = { ...packageJson.dependencies, ...packageJson.devDependencies };
|
|
305
|
+
const isNext = "next" in deps;
|
|
306
|
+
const isVite = "vite" in deps;
|
|
307
|
+
const hasReact = "react" in deps;
|
|
308
|
+
if (!hasReact) {
|
|
309
|
+
console.log(chalk.red("Error: Veil requires a React project."));
|
|
310
|
+
console.log(chalk.dim("Detected packages:"), Object.keys(deps).slice(0, 5).join(", "));
|
|
311
|
+
process.exit(1);
|
|
312
|
+
}
|
|
313
|
+
console.log(chalk.dim("Detected:"), isNext ? "Next.js" : isVite ? "Vite" : "React");
|
|
314
|
+
const answers = await inquirer.prompt([
|
|
315
|
+
{
|
|
316
|
+
type: "confirm",
|
|
317
|
+
name: "helius",
|
|
318
|
+
message: "Use Helius RPC for better reliability?",
|
|
319
|
+
default: options.helius !== false,
|
|
320
|
+
when: () => options.helius === void 0
|
|
321
|
+
},
|
|
322
|
+
{
|
|
323
|
+
type: "confirm",
|
|
324
|
+
name: "shadowPay",
|
|
325
|
+
message: "Add ShadowPay for privacy-preserving payments?",
|
|
326
|
+
default: false,
|
|
327
|
+
when: () => options.shadowPay === void 0
|
|
328
|
+
},
|
|
329
|
+
{
|
|
330
|
+
type: "list",
|
|
331
|
+
name: "shadowPayMode",
|
|
332
|
+
message: "What are you building?",
|
|
333
|
+
choices: [
|
|
334
|
+
{ name: "App \u2014 receive payments from users", value: "app" },
|
|
335
|
+
{ name: "Wallet \u2014 send payments to apps/users", value: "wallet" }
|
|
336
|
+
],
|
|
337
|
+
when: (ans) => ans.shadowPay || options.shadowPay
|
|
338
|
+
}
|
|
339
|
+
]);
|
|
340
|
+
const config = {
|
|
341
|
+
projectName: packageJson.name || "my-app",
|
|
342
|
+
template: isNext ? "nextjs" : "vite",
|
|
343
|
+
helius: answers.helius ?? options.helius ?? true,
|
|
344
|
+
shadowPay: answers.shadowPay || options.shadowPay ? answers.shadowPayMode || "app" : false,
|
|
345
|
+
network: "devnet"
|
|
346
|
+
};
|
|
347
|
+
const spinner = ora({
|
|
348
|
+
text: "Adding Veil privacy layer...",
|
|
349
|
+
color: "cyan"
|
|
350
|
+
}).start();
|
|
351
|
+
try {
|
|
352
|
+
await addVeilToProject(cwd, config);
|
|
353
|
+
spinner.succeed(chalk.green("Veil added successfully."));
|
|
354
|
+
printAddNextSteps();
|
|
355
|
+
} catch (error) {
|
|
356
|
+
spinner.fail(chalk.red("Failed to add Veil."));
|
|
357
|
+
console.error(error);
|
|
358
|
+
process.exit(1);
|
|
359
|
+
}
|
|
360
|
+
}
|
|
361
|
+
async function addVeilToProject(projectPath, config) {
|
|
362
|
+
const fsExtra = await import("fs-extra");
|
|
363
|
+
const fs2 = fsExtra.default;
|
|
364
|
+
const path2 = await import("path");
|
|
365
|
+
const {
|
|
366
|
+
generateVeilConfig: generateVeilConfig2,
|
|
367
|
+
generateEnvExample: generateEnvExample2,
|
|
368
|
+
generateLoginTs: generateLoginTs2,
|
|
369
|
+
generateRecoveryTs: generateRecoveryTs2,
|
|
370
|
+
generateAccessTs: generateAccessTs2,
|
|
371
|
+
generateGuaranteesTs: generateGuaranteesTs2,
|
|
372
|
+
generateRpcTs: generateRpcTs2,
|
|
373
|
+
generateHeliusTs: generateHeliusTs2,
|
|
374
|
+
generateVeilProvider: generateVeilProvider2,
|
|
375
|
+
generateVeilHooks: generateVeilHooks2,
|
|
376
|
+
generateShadowPayModule: generateShadowPayModule2
|
|
377
|
+
} = await import("./templates-TJ2JYQDS.js");
|
|
378
|
+
const dirs = ["privacy", "infra", "contexts", "hooks"];
|
|
379
|
+
for (const dir of dirs) {
|
|
380
|
+
await fs2.ensureDir(path2.join(projectPath, dir));
|
|
381
|
+
}
|
|
382
|
+
const files = [
|
|
383
|
+
{ path: "veil.config.ts", content: generateVeilConfig2(config) },
|
|
384
|
+
{ path: ".env.example", content: generateEnvExample2(config), append: true },
|
|
385
|
+
// Privacy module
|
|
386
|
+
{ path: "privacy/login.ts", content: generateLoginTs2() },
|
|
387
|
+
{ path: "privacy/recovery.ts", content: generateRecoveryTs2() },
|
|
388
|
+
{ path: "privacy/access.ts", content: generateAccessTs2() },
|
|
389
|
+
{ path: "privacy/guarantees.ts", content: generateGuaranteesTs2() },
|
|
390
|
+
{ path: "privacy/index.ts", content: `export * from "./login.js";
|
|
391
|
+
export * from "./recovery.js";
|
|
392
|
+
export * from "./access.js";
|
|
393
|
+
export * from "./guarantees.js";
|
|
394
|
+
` },
|
|
395
|
+
// Infra module
|
|
396
|
+
{ path: "infra/rpc.ts", content: generateRpcTs2() },
|
|
397
|
+
{ path: "infra/helius.ts", content: generateHeliusTs2() },
|
|
398
|
+
{ path: "infra/index.ts", content: `export * from "./rpc.js";
|
|
399
|
+
export * from "./helius.js";
|
|
400
|
+
` },
|
|
401
|
+
// Contexts
|
|
402
|
+
{ path: "contexts/VeilProvider.tsx", content: generateVeilProvider2(config) },
|
|
403
|
+
{ path: "contexts/index.ts", content: `export * from "./VeilProvider.js";
|
|
404
|
+
` },
|
|
405
|
+
// Hooks
|
|
406
|
+
{ path: "hooks/useVeil.ts", content: generateVeilHooks2() },
|
|
407
|
+
{ path: "hooks/index.ts", content: `export * from "./useVeil.js";
|
|
408
|
+
` }
|
|
409
|
+
];
|
|
410
|
+
if (config.shadowPay) {
|
|
411
|
+
files.push({ path: "shadowpay/index.ts", content: generateShadowPayModule2(config) });
|
|
412
|
+
}
|
|
413
|
+
for (const file of files) {
|
|
414
|
+
const filePath = path2.join(projectPath, file.path);
|
|
415
|
+
if (file.append && await fs2.pathExists(filePath)) {
|
|
416
|
+
const existing = await fs2.readFile(filePath, "utf-8");
|
|
417
|
+
if (!existing.includes("HELIUS")) {
|
|
418
|
+
await fs2.appendFile(filePath, "\n" + file.content);
|
|
419
|
+
}
|
|
420
|
+
} else if (!await fs2.pathExists(filePath)) {
|
|
421
|
+
await fs2.ensureDir(path2.dirname(filePath));
|
|
422
|
+
await fs2.writeFile(filePath, file.content, "utf-8");
|
|
423
|
+
}
|
|
424
|
+
}
|
|
425
|
+
}
|
|
426
|
+
function printAddNextSteps() {
|
|
427
|
+
console.log();
|
|
428
|
+
console.log(chalk.bold("What was added:"));
|
|
429
|
+
console.log(chalk.dim("\u2022"), chalk.cyan("privacy/") + " \u2014 login, recovery, access guarantees");
|
|
430
|
+
console.log(chalk.dim("\u2022"), chalk.cyan("contexts/") + " \u2014 VeilProvider for auth state");
|
|
431
|
+
console.log(chalk.dim("\u2022"), chalk.cyan("hooks/") + " \u2014 useVeil hook");
|
|
432
|
+
console.log(chalk.dim("\u2022"), chalk.cyan("infra/") + " \u2014 RPC and Helius helpers");
|
|
433
|
+
console.log();
|
|
434
|
+
console.log(chalk.bold("Next steps:"));
|
|
435
|
+
console.log(chalk.dim("1."), "Wrap your app with <VeilProvider>");
|
|
436
|
+
console.log(chalk.dim("2."), "Use the useVeil() hook for authentication");
|
|
437
|
+
console.log(chalk.dim("3."), "Review privacy/guarantees.ts");
|
|
438
|
+
console.log();
|
|
439
|
+
}
|
|
440
|
+
|
|
441
|
+
// src/templates/privacy.ts
|
|
442
|
+
function generateLoginTs() {
|
|
443
|
+
return `/**
|
|
444
|
+
* Privacy-Preserving Login
|
|
445
|
+
*
|
|
446
|
+
* This module handles authentication without exposing identity on-chain.
|
|
447
|
+
*
|
|
448
|
+
* PRIVACY GUARANTEE:
|
|
449
|
+
* - Email/passkey \u2192 generates unlinkable Solana wallet
|
|
450
|
+
* - No identity stored on-chain
|
|
451
|
+
* - No correlation between login method and wallet address
|
|
452
|
+
*/
|
|
453
|
+
|
|
454
|
+
import { Keypair } from "@solana/web3.js";
|
|
455
|
+
|
|
456
|
+
export interface LoginSession {
|
|
457
|
+
/** Ephemeral session ID (never stored on-chain) */
|
|
458
|
+
sessionId: string;
|
|
459
|
+
/** Derived wallet (unlinkable to identity) */
|
|
460
|
+
wallet: Keypair;
|
|
461
|
+
/** Session expiry timestamp */
|
|
462
|
+
expiresAt: number;
|
|
463
|
+
}
|
|
464
|
+
|
|
465
|
+
/**
|
|
466
|
+
* Derive a wallet from identity proof without exposing the identity.
|
|
467
|
+
*
|
|
468
|
+
* In production, this would use ZK proofs to:
|
|
469
|
+
* 1. Prove you own an email/passkey
|
|
470
|
+
* 2. Derive a deterministic but unlinkable wallet
|
|
471
|
+
* 3. Never reveal the email/passkey on-chain
|
|
472
|
+
*/
|
|
473
|
+
export async function derivePrivateWallet(
|
|
474
|
+
identityProof: Uint8Array
|
|
475
|
+
): Promise<Keypair> {
|
|
476
|
+
// Conceptual: In production, use proper ZK derivation
|
|
477
|
+
// This demonstrates the privacy-first architecture
|
|
478
|
+
const seed = await crypto.subtle.digest("SHA-256", identityProof.buffer as ArrayBuffer);
|
|
479
|
+
return Keypair.fromSeed(new Uint8Array(seed));
|
|
480
|
+
}
|
|
481
|
+
|
|
482
|
+
/**
|
|
483
|
+
* Create a new login session.
|
|
484
|
+
* Session data is ephemeral and never touches the blockchain.
|
|
485
|
+
*/
|
|
486
|
+
export async function createLoginSession(
|
|
487
|
+
identityProof: Uint8Array,
|
|
488
|
+
durationMs: number = 24 * 60 * 60 * 1000
|
|
489
|
+
): Promise<LoginSession> {
|
|
490
|
+
const wallet = await derivePrivateWallet(identityProof);
|
|
491
|
+
|
|
492
|
+
return {
|
|
493
|
+
sessionId: crypto.randomUUID(),
|
|
494
|
+
wallet,
|
|
495
|
+
expiresAt: Date.now() + durationMs,
|
|
496
|
+
};
|
|
497
|
+
}
|
|
498
|
+
|
|
499
|
+
/**
|
|
500
|
+
* Validate session without on-chain lookup.
|
|
501
|
+
*/
|
|
502
|
+
export function isSessionValid(session: LoginSession): boolean {
|
|
503
|
+
return Date.now() < session.expiresAt;
|
|
504
|
+
}
|
|
505
|
+
`;
|
|
506
|
+
}
|
|
507
|
+
function generateRecoveryTs() {
|
|
508
|
+
return `/**
|
|
509
|
+
* Privacy-Preserving Recovery
|
|
510
|
+
*
|
|
511
|
+
* This module handles wallet recovery without exposing guardians.
|
|
512
|
+
*
|
|
513
|
+
* PRIVACY GUARANTEE:
|
|
514
|
+
* - Guardian identities are never revealed
|
|
515
|
+
* - Recovery process doesn't leak social graph
|
|
516
|
+
* - Time-lock or Shamir methods available
|
|
517
|
+
*/
|
|
518
|
+
|
|
519
|
+
export type RecoveryMethod = "timelock" | "shamir";
|
|
520
|
+
|
|
521
|
+
export interface RecoveryConfig {
|
|
522
|
+
method: RecoveryMethod;
|
|
523
|
+
/** For timelock: delay in seconds before recovery completes */
|
|
524
|
+
timelockDelay?: number;
|
|
525
|
+
/** For shamir: threshold of guardians needed */
|
|
526
|
+
shamirThreshold?: number;
|
|
527
|
+
/** For shamir: total number of guardian shares */
|
|
528
|
+
shamirTotal?: number;
|
|
529
|
+
}
|
|
530
|
+
|
|
531
|
+
/**
|
|
532
|
+
* Commitment hash for guardian (hides actual identity).
|
|
533
|
+
* Guardian is identified by hash, not by address or email.
|
|
534
|
+
*/
|
|
535
|
+
export function createGuardianCommitment(
|
|
536
|
+
guardianSecret: Uint8Array
|
|
537
|
+
): Promise<Uint8Array> {
|
|
538
|
+
return crypto.subtle.digest("SHA-256", guardianSecret.buffer as ArrayBuffer).then(
|
|
539
|
+
(hash) => new Uint8Array(hash)
|
|
540
|
+
);
|
|
541
|
+
}
|
|
542
|
+
|
|
543
|
+
/**
|
|
544
|
+
* Initiate recovery with timelock.
|
|
545
|
+
* The recovery is public, but WHO initiated it is not.
|
|
546
|
+
*/
|
|
547
|
+
export async function initiateTimelockRecovery(
|
|
548
|
+
commitmentHash: Uint8Array,
|
|
549
|
+
delaySeconds: number
|
|
550
|
+
): Promise<{ recoveryId: string; unlocksAt: number }> {
|
|
551
|
+
return {
|
|
552
|
+
recoveryId: crypto.randomUUID(),
|
|
553
|
+
unlocksAt: Date.now() + delaySeconds * 1000,
|
|
554
|
+
};
|
|
555
|
+
}
|
|
556
|
+
|
|
557
|
+
/**
|
|
558
|
+
* Initiate Shamir recovery.
|
|
559
|
+
* Guardians provide shares without revealing who they are.
|
|
560
|
+
*/
|
|
561
|
+
export async function initiateShamirRecovery(
|
|
562
|
+
shares: Uint8Array[],
|
|
563
|
+
threshold: number
|
|
564
|
+
): Promise<{ recoveryId: string; sharesCollected: number }> {
|
|
565
|
+
if (shares.length < threshold) {
|
|
566
|
+
throw new Error(\`Need \${threshold} shares, got \${shares.length}\`);
|
|
567
|
+
}
|
|
568
|
+
|
|
569
|
+
return {
|
|
570
|
+
recoveryId: crypto.randomUUID(),
|
|
571
|
+
sharesCollected: shares.length,
|
|
572
|
+
};
|
|
573
|
+
}
|
|
574
|
+
`;
|
|
575
|
+
}
|
|
576
|
+
function generateAccessTs() {
|
|
577
|
+
return `/**
|
|
578
|
+
* Privacy-Preserving Access Control
|
|
579
|
+
*
|
|
580
|
+
* This module handles proof-based access without exposing addresses.
|
|
581
|
+
*
|
|
582
|
+
* PRIVACY GUARANTEE:
|
|
583
|
+
* - Access is verified via proof, not address lookup
|
|
584
|
+
* - Wallet address is not revealed during verification
|
|
585
|
+
* - Proof can be verified without knowing who created it
|
|
586
|
+
*/
|
|
587
|
+
|
|
588
|
+
export interface AccessProof {
|
|
589
|
+
/** Proof data (ZK proof in production) */
|
|
590
|
+
proof: Uint8Array;
|
|
591
|
+
/** Public inputs for verification */
|
|
592
|
+
publicInputs: Uint8Array;
|
|
593
|
+
/** Timestamp of proof creation */
|
|
594
|
+
createdAt: number;
|
|
595
|
+
}
|
|
596
|
+
|
|
597
|
+
/**
|
|
598
|
+
* Generate access proof without revealing wallet address.
|
|
599
|
+
*
|
|
600
|
+
* In production, this creates a ZK proof that:
|
|
601
|
+
* 1. Proves ownership of a valid wallet
|
|
602
|
+
* 2. Does NOT reveal which wallet
|
|
603
|
+
* 3. Can be verified by anyone
|
|
604
|
+
*/
|
|
605
|
+
export async function generateAccessProof(
|
|
606
|
+
wallet: { publicKey: { toBytes(): Uint8Array }; },
|
|
607
|
+
challenge: Uint8Array
|
|
608
|
+
): Promise<AccessProof> {
|
|
609
|
+
// Conceptual: Create proof of wallet ownership
|
|
610
|
+
const message = new Uint8Array([...wallet.publicKey.toBytes(), ...challenge]);
|
|
611
|
+
const proof = await crypto.subtle.digest("SHA-256", message.buffer as ArrayBuffer);
|
|
612
|
+
|
|
613
|
+
return {
|
|
614
|
+
proof: new Uint8Array(proof),
|
|
615
|
+
publicInputs: challenge,
|
|
616
|
+
createdAt: Date.now(),
|
|
617
|
+
};
|
|
618
|
+
}
|
|
619
|
+
|
|
620
|
+
/**
|
|
621
|
+
* Verify access proof without knowing the wallet.
|
|
622
|
+
*/
|
|
623
|
+
export async function verifyAccessProof(
|
|
624
|
+
proof: AccessProof,
|
|
625
|
+
expectedChallenge: Uint8Array
|
|
626
|
+
): Promise<boolean> {
|
|
627
|
+
// Verify the challenge matches
|
|
628
|
+
if (proof.publicInputs.length !== expectedChallenge.length) {
|
|
629
|
+
return false;
|
|
630
|
+
}
|
|
631
|
+
|
|
632
|
+
for (let i = 0; i < expectedChallenge.length; i++) {
|
|
633
|
+
if (proof.publicInputs[i] !== expectedChallenge[i]) {
|
|
634
|
+
return false;
|
|
635
|
+
}
|
|
636
|
+
}
|
|
637
|
+
|
|
638
|
+
// In production: verify ZK proof
|
|
639
|
+
return proof.proof.length === 32;
|
|
640
|
+
}
|
|
641
|
+
`;
|
|
642
|
+
}
|
|
643
|
+
function generateGuaranteesTs() {
|
|
644
|
+
return `/**
|
|
645
|
+
* VEIL PRIVACY GUARANTEES
|
|
646
|
+
*
|
|
647
|
+
* This file documents what Veil protects and what it does NOT protect.
|
|
648
|
+
* Read this carefully before building your application.
|
|
649
|
+
*
|
|
650
|
+
* \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550
|
|
651
|
+
*
|
|
652
|
+
* \u2705 WHAT VEIL PROTECTS (Private)
|
|
653
|
+
*
|
|
654
|
+
* 1. IDENTITY
|
|
655
|
+
* - Your email/passkey is never stored on-chain
|
|
656
|
+
* - No correlation between login method and wallet
|
|
657
|
+
* - Identity proof generates unlinkable wallet
|
|
658
|
+
*
|
|
659
|
+
* 2. ACCESS PATTERNS
|
|
660
|
+
* - Proof-based verification (not address lookup)
|
|
661
|
+
* - Session data is ephemeral
|
|
662
|
+
* - No on-chain access logs
|
|
663
|
+
*
|
|
664
|
+
* 3. RECOVERY SOCIAL GRAPH
|
|
665
|
+
* - Guardian identities are hidden (commitment hashes)
|
|
666
|
+
* - Recovery doesn't reveal who helped you
|
|
667
|
+
* - Timelock prevents instant unauthorized recovery
|
|
668
|
+
*
|
|
669
|
+
* \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550
|
|
670
|
+
*
|
|
671
|
+
* \u274C WHAT VEIL DOES NOT PROTECT (Public on Solana)
|
|
672
|
+
*
|
|
673
|
+
* 1. TRANSACTION AMOUNTS
|
|
674
|
+
* - All SOL/token amounts are visible
|
|
675
|
+
* - This is a Solana limitation, not Veil
|
|
676
|
+
*
|
|
677
|
+
* 2. TRANSACTION RECIPIENTS
|
|
678
|
+
* - Destination addresses are public
|
|
679
|
+
* - Anyone can see who you transact with
|
|
680
|
+
*
|
|
681
|
+
* 3. WALLET BALANCES
|
|
682
|
+
* - All balances are publicly queryable
|
|
683
|
+
* - Historical balance changes are visible
|
|
684
|
+
*
|
|
685
|
+
* 4. TRANSACTION HISTORY
|
|
686
|
+
* - All transactions are permanently recorded
|
|
687
|
+
* - Transaction graph analysis is possible
|
|
688
|
+
*
|
|
689
|
+
* \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550
|
|
690
|
+
*
|
|
691
|
+
* \u{1F3AF} VEIL'S MISSION
|
|
692
|
+
*
|
|
693
|
+
* "Hide the user, not the transactions."
|
|
694
|
+
*
|
|
695
|
+
* Veil ensures that even if someone sees your transactions,
|
|
696
|
+
* they cannot link them back to your real-world identity.
|
|
697
|
+
*
|
|
698
|
+
* \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550
|
|
699
|
+
*/
|
|
700
|
+
|
|
701
|
+
export const PRIVACY_GUARANTEES = {
|
|
702
|
+
identity: {
|
|
703
|
+
protected: true,
|
|
704
|
+
description: "Your real identity is never stored or revealed on-chain",
|
|
705
|
+
},
|
|
706
|
+
accessPatterns: {
|
|
707
|
+
protected: true,
|
|
708
|
+
description: "How you access your wallet is hidden from observers",
|
|
709
|
+
},
|
|
710
|
+
recoveryGuardians: {
|
|
711
|
+
protected: true,
|
|
712
|
+
description: "Guardian identities are hidden behind commitment hashes",
|
|
713
|
+
},
|
|
714
|
+
transactionAmounts: {
|
|
715
|
+
protected: false,
|
|
716
|
+
description: "Transaction amounts are visible on Solana",
|
|
717
|
+
},
|
|
718
|
+
transactionRecipients: {
|
|
719
|
+
protected: false,
|
|
720
|
+
description: "Transaction recipients are visible on Solana",
|
|
721
|
+
},
|
|
722
|
+
walletBalances: {
|
|
723
|
+
protected: false,
|
|
724
|
+
description: "Wallet balances are publicly queryable",
|
|
725
|
+
},
|
|
726
|
+
} as const;
|
|
727
|
+
|
|
728
|
+
export type PrivacyGuarantee = keyof typeof PRIVACY_GUARANTEES;
|
|
729
|
+
`;
|
|
730
|
+
}
|
|
731
|
+
|
|
732
|
+
// src/templates/infra.ts
|
|
733
|
+
function generateRpcTs(config) {
|
|
734
|
+
const rpcUrl = config.helius ? "process.env.HELIUS_RPC_URL" : config.network === "devnet" ? '"https://api.devnet.solana.com"' : '"http://localhost:8899"';
|
|
735
|
+
return `/**
|
|
736
|
+
* RPC Configuration
|
|
737
|
+
*
|
|
738
|
+
* Privacy-aware RPC layer that abstracts connection details.
|
|
739
|
+
*
|
|
740
|
+
* PRIVACY NOTE:
|
|
741
|
+
* - RPC providers can see your requests
|
|
742
|
+
* - Use Helius for better rate limits and webhooks
|
|
743
|
+
* - Never expose API keys in client-side code
|
|
744
|
+
*/
|
|
745
|
+
|
|
746
|
+
import { Connection, clusterApiUrl } from "@solana/web3.js";
|
|
747
|
+
|
|
748
|
+
const NETWORK = "${config.network}";
|
|
749
|
+
|
|
750
|
+
/**
|
|
751
|
+
* Get the RPC URL based on configuration.
|
|
752
|
+
* Server-side only for privacy.
|
|
753
|
+
*/
|
|
754
|
+
function getRpcUrl(): string {
|
|
755
|
+
if (typeof window !== "undefined") {
|
|
756
|
+
// Client-side: use public endpoint (limited)
|
|
757
|
+
return clusterApiUrl("${config.network}");
|
|
758
|
+
}
|
|
759
|
+
|
|
760
|
+
// Server-side: use configured RPC
|
|
761
|
+
return ${rpcUrl} || clusterApiUrl("${config.network}");
|
|
762
|
+
}
|
|
763
|
+
|
|
764
|
+
/**
|
|
765
|
+
* Create a connection to the Solana network.
|
|
766
|
+
*/
|
|
767
|
+
export function createConnection(): Connection {
|
|
768
|
+
return new Connection(getRpcUrl(), {
|
|
769
|
+
commitment: "confirmed",
|
|
770
|
+
confirmTransactionInitialTimeout: 60000,
|
|
771
|
+
});
|
|
772
|
+
}
|
|
773
|
+
|
|
774
|
+
/**
|
|
775
|
+
* Get network name for display.
|
|
776
|
+
*/
|
|
777
|
+
export function getNetworkName(): string {
|
|
778
|
+
return NETWORK.charAt(0).toUpperCase() + NETWORK.slice(1);
|
|
779
|
+
}
|
|
780
|
+
|
|
781
|
+
export { NETWORK };
|
|
782
|
+
`;
|
|
783
|
+
}
|
|
784
|
+
function generateHeliusTs(config) {
|
|
785
|
+
if (!config.helius) {
|
|
786
|
+
return `/**
|
|
787
|
+
* Helius Integration (Disabled)
|
|
788
|
+
*
|
|
789
|
+
* Helius provides enhanced RPC and webhooks for Solana.
|
|
790
|
+
* Enable it during project setup to use these features.
|
|
791
|
+
*/
|
|
792
|
+
|
|
793
|
+
export const HELIUS_ENABLED = false;
|
|
794
|
+
|
|
795
|
+
export function getHeliusRpcUrl(): string {
|
|
796
|
+
throw new Error("Helius is not enabled. Run veil init with Helius enabled.");
|
|
797
|
+
}
|
|
798
|
+
`;
|
|
799
|
+
}
|
|
800
|
+
return `/**
|
|
801
|
+
* Helius Integration
|
|
802
|
+
*
|
|
803
|
+
* Enhanced RPC and webhook support for Solana.
|
|
804
|
+
*
|
|
805
|
+
* PRIVACY NOTE:
|
|
806
|
+
* - Helius sees your RPC requests (standard for any RPC provider)
|
|
807
|
+
* - Webhooks can notify you of on-chain events
|
|
808
|
+
* - Never expose API keys client-side
|
|
809
|
+
*/
|
|
810
|
+
|
|
811
|
+
export const HELIUS_ENABLED = true;
|
|
812
|
+
|
|
813
|
+
/**
|
|
814
|
+
* Get Helius RPC URL (server-side only).
|
|
815
|
+
*/
|
|
816
|
+
export function getHeliusRpcUrl(): string {
|
|
817
|
+
const apiKey = process.env.HELIUS_API_KEY;
|
|
818
|
+
if (!apiKey) {
|
|
819
|
+
throw new Error("HELIUS_API_KEY not configured");
|
|
820
|
+
}
|
|
821
|
+
return \`https://${config.network}.helius-rpc.com/?api-key=\${apiKey}\`;
|
|
822
|
+
}
|
|
823
|
+
|
|
824
|
+
/**
|
|
825
|
+
* Verify Helius webhook signature.
|
|
826
|
+
*/
|
|
827
|
+
export async function verifyWebhookSignature(
|
|
828
|
+
payload: string,
|
|
829
|
+
signature: string
|
|
830
|
+
): Promise<boolean> {
|
|
831
|
+
const secret = process.env.HELIUS_WEBHOOK_SECRET;
|
|
832
|
+
if (!secret) {
|
|
833
|
+
console.warn("HELIUS_WEBHOOK_SECRET not configured");
|
|
834
|
+
return false;
|
|
835
|
+
}
|
|
836
|
+
|
|
837
|
+
const encoder = new TextEncoder();
|
|
838
|
+
const secretBytes = encoder.encode(secret);
|
|
839
|
+
const key = await crypto.subtle.importKey(
|
|
840
|
+
"raw",
|
|
841
|
+
secretBytes.buffer as ArrayBuffer,
|
|
842
|
+
{ name: "HMAC", hash: "SHA-256" },
|
|
843
|
+
false,
|
|
844
|
+
["verify"]
|
|
845
|
+
);
|
|
846
|
+
|
|
847
|
+
const signatureBytes = Uint8Array.from(
|
|
848
|
+
atob(signature),
|
|
849
|
+
(c) => c.charCodeAt(0)
|
|
850
|
+
);
|
|
851
|
+
|
|
852
|
+
const payloadBytes = encoder.encode(payload);
|
|
853
|
+
return crypto.subtle.verify(
|
|
854
|
+
"HMAC",
|
|
855
|
+
key,
|
|
856
|
+
signatureBytes.buffer as ArrayBuffer,
|
|
857
|
+
payloadBytes.buffer as ArrayBuffer
|
|
858
|
+
);
|
|
859
|
+
}
|
|
860
|
+
|
|
861
|
+
/**
|
|
862
|
+
* Webhook handler placeholder.
|
|
863
|
+
* Implement your own logic based on event type.
|
|
864
|
+
*/
|
|
865
|
+
export interface HeliusWebhookEvent {
|
|
866
|
+
type: string;
|
|
867
|
+
data: unknown;
|
|
868
|
+
timestamp: number;
|
|
869
|
+
}
|
|
870
|
+
|
|
871
|
+
export async function handleWebhook(event: HeliusWebhookEvent): Promise<void> {
|
|
872
|
+
console.log("Received webhook event:", event.type);
|
|
873
|
+
// Implement your webhook handling logic here
|
|
874
|
+
}
|
|
875
|
+
`;
|
|
876
|
+
}
|
|
877
|
+
|
|
878
|
+
// src/templates/project.ts
|
|
879
|
+
function generatePackageJson(config) {
|
|
880
|
+
const isNext = config.framework === "nextjs";
|
|
881
|
+
const deps = {
|
|
882
|
+
"@solana/web3.js": "^1.95.0",
|
|
883
|
+
"@solana/wallet-adapter-base": "^0.9.23",
|
|
884
|
+
"@solana/wallet-adapter-react": "^0.15.35",
|
|
885
|
+
"@solana/wallet-adapter-react-ui": "^0.9.35",
|
|
886
|
+
"@solana/wallet-adapter-phantom": "^0.9.24",
|
|
887
|
+
"@solana/wallet-adapter-solflare": "^0.6.28"
|
|
888
|
+
};
|
|
889
|
+
if (config.features.shadowpay) {
|
|
890
|
+
deps["@radr/shadowwire"] = "^0.1.0";
|
|
891
|
+
}
|
|
892
|
+
const devDeps = {
|
|
893
|
+
"typescript": "^5.3.3",
|
|
894
|
+
"@types/node": "^20.11.0",
|
|
895
|
+
"tailwindcss": "^3.4.1",
|
|
896
|
+
"postcss": "^8.4.33",
|
|
897
|
+
"autoprefixer": "^10.4.17"
|
|
898
|
+
};
|
|
899
|
+
if (isNext) {
|
|
900
|
+
deps["next"] = "^14.1.0";
|
|
901
|
+
deps["react"] = "^18.2.0";
|
|
902
|
+
deps["react-dom"] = "^18.2.0";
|
|
903
|
+
devDeps["@types/react"] = "^18.2.48";
|
|
904
|
+
devDeps["@types/react-dom"] = "^18.2.18";
|
|
905
|
+
} else {
|
|
906
|
+
deps["react"] = "^18.2.0";
|
|
907
|
+
deps["react-dom"] = "^18.2.0";
|
|
908
|
+
devDeps["vite"] = "^5.0.12";
|
|
909
|
+
devDeps["@vitejs/plugin-react"] = "^4.2.1";
|
|
910
|
+
devDeps["@types/react"] = "^18.2.48";
|
|
911
|
+
devDeps["@types/react-dom"] = "^18.2.18";
|
|
912
|
+
}
|
|
913
|
+
const scripts = isNext ? {
|
|
914
|
+
dev: "next dev",
|
|
915
|
+
build: "next build",
|
|
916
|
+
start: "next start",
|
|
917
|
+
lint: "next lint"
|
|
918
|
+
} : {
|
|
919
|
+
dev: "vite",
|
|
920
|
+
build: "vite build",
|
|
921
|
+
preview: "vite preview"
|
|
922
|
+
};
|
|
923
|
+
const templateInfo = TEMPLATE_INFO[config.template];
|
|
924
|
+
const pkg = {
|
|
925
|
+
name: config.projectName,
|
|
926
|
+
version: "0.1.0",
|
|
927
|
+
description: `${templateInfo.name} - ${templateInfo.description}. Built with Veil privacy stack.`,
|
|
928
|
+
private: true,
|
|
929
|
+
scripts,
|
|
930
|
+
dependencies: deps,
|
|
931
|
+
devDependencies: devDeps
|
|
932
|
+
};
|
|
933
|
+
return JSON.stringify(pkg, null, 2);
|
|
934
|
+
}
|
|
935
|
+
function generateTsConfig(config) {
|
|
936
|
+
const isNext = config.framework === "nextjs";
|
|
937
|
+
if (isNext) {
|
|
938
|
+
return JSON.stringify({
|
|
939
|
+
compilerOptions: {
|
|
940
|
+
target: "ES2017",
|
|
941
|
+
lib: ["dom", "dom.iterable", "esnext"],
|
|
942
|
+
allowJs: true,
|
|
943
|
+
skipLibCheck: true,
|
|
944
|
+
strict: true,
|
|
945
|
+
noEmit: true,
|
|
946
|
+
esModuleInterop: true,
|
|
947
|
+
module: "esnext",
|
|
948
|
+
moduleResolution: "bundler",
|
|
949
|
+
resolveJsonModule: true,
|
|
950
|
+
isolatedModules: true,
|
|
951
|
+
jsx: "preserve",
|
|
952
|
+
incremental: true,
|
|
953
|
+
plugins: [{ name: "next" }],
|
|
954
|
+
paths: { "@/*": ["./*"] }
|
|
955
|
+
},
|
|
956
|
+
include: ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"],
|
|
957
|
+
exclude: ["node_modules"]
|
|
958
|
+
}, null, 2);
|
|
959
|
+
}
|
|
960
|
+
return JSON.stringify({
|
|
961
|
+
compilerOptions: {
|
|
962
|
+
target: "ES2020",
|
|
963
|
+
useDefineForClassFields: true,
|
|
964
|
+
lib: ["ES2020", "DOM", "DOM.Iterable"],
|
|
965
|
+
module: "ESNext",
|
|
966
|
+
skipLibCheck: true,
|
|
967
|
+
moduleResolution: "bundler",
|
|
968
|
+
allowImportingTsExtensions: true,
|
|
969
|
+
resolveJsonModule: true,
|
|
970
|
+
isolatedModules: true,
|
|
971
|
+
noEmit: true,
|
|
972
|
+
jsx: "react-jsx",
|
|
973
|
+
strict: true,
|
|
974
|
+
noUnusedLocals: true,
|
|
975
|
+
noUnusedParameters: true,
|
|
976
|
+
noFallthroughCasesInSwitch: true
|
|
977
|
+
},
|
|
978
|
+
include: ["src"],
|
|
979
|
+
references: [{ path: "./tsconfig.node.json" }]
|
|
980
|
+
}, null, 2);
|
|
981
|
+
}
|
|
982
|
+
function generateAppEntry(config) {
|
|
983
|
+
const isNext = config.template === "nextjs";
|
|
984
|
+
if (isNext) {
|
|
985
|
+
return `"use client";
|
|
986
|
+
|
|
987
|
+
import { useWallet } from "@solana/wallet-adapter-react";
|
|
988
|
+
import { WalletButton } from "./components/WalletButton";
|
|
989
|
+
import { PrivacyStatus } from "./components/PrivacyStatus";
|
|
990
|
+
import { useVeil } from "../hooks/useVeil";
|
|
991
|
+
|
|
992
|
+
export default function Home() {
|
|
993
|
+
const { connected } = useWallet();
|
|
994
|
+
const { isAuthenticated, login, logout } = useVeil();
|
|
995
|
+
|
|
996
|
+
return (
|
|
997
|
+
<main className="min-h-screen p-8">
|
|
998
|
+
{/* Header */}
|
|
999
|
+
<header className="flex justify-between items-center mb-12">
|
|
1000
|
+
<div className="flex items-center gap-4">
|
|
1001
|
+
<h1 className="text-2xl font-bold bg-gradient-to-r from-purple-400 to-blue-400 bg-clip-text text-transparent">
|
|
1002
|
+
${config.projectName}
|
|
1003
|
+
</h1>
|
|
1004
|
+
<PrivacyStatus />
|
|
1005
|
+
</div>
|
|
1006
|
+
<WalletButton />
|
|
1007
|
+
</header>
|
|
1008
|
+
|
|
1009
|
+
{/* Hero Section */}
|
|
1010
|
+
<section className="max-w-2xl mx-auto text-center mb-16">
|
|
1011
|
+
<h2 className="text-5xl font-bold mb-6">
|
|
1012
|
+
Privacy-First
|
|
1013
|
+
<span className="block bg-gradient-to-r from-purple-400 to-blue-400 bg-clip-text text-transparent">
|
|
1014
|
+
Solana App
|
|
1015
|
+
</span>
|
|
1016
|
+
</h2>
|
|
1017
|
+
<p className="text-gray-400 text-lg mb-8">
|
|
1018
|
+
Built with Veil \u2014 Your identity is never on-chain.
|
|
1019
|
+
</p>
|
|
1020
|
+
|
|
1021
|
+
{connected && !isAuthenticated && (
|
|
1022
|
+
<button
|
|
1023
|
+
onClick={login}
|
|
1024
|
+
className="px-8 py-3 rounded-xl font-semibold
|
|
1025
|
+
bg-gradient-to-r from-purple-600 to-blue-600
|
|
1026
|
+
hover:from-purple-700 hover:to-blue-700
|
|
1027
|
+
transition-all duration-200 shadow-lg"
|
|
1028
|
+
>
|
|
1029
|
+
Create Private Session
|
|
1030
|
+
</button>
|
|
1031
|
+
)}
|
|
1032
|
+
|
|
1033
|
+
{isAuthenticated && (
|
|
1034
|
+
<div className="glass rounded-xl p-6">
|
|
1035
|
+
<p className="text-green-400 mb-4">\u2705 You are privately authenticated</p>
|
|
1036
|
+
<button
|
|
1037
|
+
onClick={logout}
|
|
1038
|
+
className="text-gray-400 hover:text-white transition-colors"
|
|
1039
|
+
>
|
|
1040
|
+
End Session
|
|
1041
|
+
</button>
|
|
1042
|
+
</div>
|
|
1043
|
+
)}
|
|
1044
|
+
</section>
|
|
1045
|
+
|
|
1046
|
+
{/* Privacy Guarantees */}
|
|
1047
|
+
<section className="max-w-4xl mx-auto">
|
|
1048
|
+
<h3 className="text-2xl font-bold mb-6 text-center">Privacy Guarantees</h3>
|
|
1049
|
+
<div className="grid md:grid-cols-2 gap-4">
|
|
1050
|
+
<div className="glass rounded-xl p-6">
|
|
1051
|
+
<h4 className="text-green-400 font-semibold mb-2">\u2705 What's Private</h4>
|
|
1052
|
+
<ul className="text-gray-300 space-y-2 text-sm">
|
|
1053
|
+
<li>\u2022 Your identity (never on-chain)</li>
|
|
1054
|
+
<li>\u2022 How you access your wallet</li>
|
|
1055
|
+
<li>\u2022 Who your recovery guardians are</li>
|
|
1056
|
+
</ul>
|
|
1057
|
+
</div>
|
|
1058
|
+
<div className="glass rounded-xl p-6">
|
|
1059
|
+
<h4 className="text-red-400 font-semibold mb-2">\u274C What's Public (Solana)</h4>
|
|
1060
|
+
<ul className="text-gray-300 space-y-2 text-sm">
|
|
1061
|
+
<li>\u2022 Transaction amounts</li>
|
|
1062
|
+
<li>\u2022 Transaction recipients</li>
|
|
1063
|
+
<li>\u2022 Wallet balances</li>
|
|
1064
|
+
</ul>
|
|
1065
|
+
</div>
|
|
1066
|
+
</div>
|
|
1067
|
+
</section>
|
|
1068
|
+
</main>
|
|
1069
|
+
);
|
|
1070
|
+
}
|
|
1071
|
+
`;
|
|
1072
|
+
}
|
|
1073
|
+
return `import { WalletAdapterNetwork } from "@solana/wallet-adapter-base";
|
|
1074
|
+
import { ConnectionProvider, WalletProvider } from "@solana/wallet-adapter-react";
|
|
1075
|
+
import { WalletModalProvider } from "@solana/wallet-adapter-react-ui";
|
|
1076
|
+
import { PhantomWalletAdapter } from "@solana/wallet-adapter-wallets";
|
|
1077
|
+
import { clusterApiUrl } from "@solana/web3.js";
|
|
1078
|
+
import { useMemo } from "react";
|
|
1079
|
+
|
|
1080
|
+
import "@solana/wallet-adapter-react-ui/styles.css";
|
|
1081
|
+
|
|
1082
|
+
export default function App() {
|
|
1083
|
+
const network = WalletAdapterNetwork.Devnet;
|
|
1084
|
+
const endpoint = useMemo(() => clusterApiUrl(network), [network]);
|
|
1085
|
+
const wallets = useMemo(() => [new PhantomWalletAdapter()], []);
|
|
1086
|
+
|
|
1087
|
+
return (
|
|
1088
|
+
<ConnectionProvider endpoint={endpoint}>
|
|
1089
|
+
<WalletProvider wallets={wallets} autoConnect>
|
|
1090
|
+
<WalletModalProvider>
|
|
1091
|
+
<main style={{ padding: "2rem" }}>
|
|
1092
|
+
<h1>${config.projectName}</h1>
|
|
1093
|
+
<p>Privacy-first Solana app built with Veil</p>
|
|
1094
|
+
</main>
|
|
1095
|
+
</WalletModalProvider>
|
|
1096
|
+
</WalletProvider>
|
|
1097
|
+
</ConnectionProvider>
|
|
1098
|
+
);
|
|
1099
|
+
}
|
|
1100
|
+
`;
|
|
1101
|
+
}
|
|
1102
|
+
|
|
1103
|
+
// src/templates/components.ts
|
|
1104
|
+
function generateWalletButton() {
|
|
1105
|
+
return `"use client";
|
|
1106
|
+
|
|
1107
|
+
import { useWallet } from "@solana/wallet-adapter-react";
|
|
1108
|
+
import { useWalletModal } from "@solana/wallet-adapter-react-ui";
|
|
1109
|
+
|
|
1110
|
+
export function WalletButton() {
|
|
1111
|
+
const { wallet, connect, disconnect, connected, publicKey } = useWallet();
|
|
1112
|
+
const { setVisible } = useWalletModal();
|
|
1113
|
+
|
|
1114
|
+
const handleClick = () => {
|
|
1115
|
+
if (connected) {
|
|
1116
|
+
disconnect();
|
|
1117
|
+
} else if (wallet) {
|
|
1118
|
+
connect();
|
|
1119
|
+
} else {
|
|
1120
|
+
setVisible(true);
|
|
1121
|
+
}
|
|
1122
|
+
};
|
|
1123
|
+
|
|
1124
|
+
const truncateAddress = (address: string) => {
|
|
1125
|
+
return \`\${address.slice(0, 4)}...\${address.slice(-4)}\`;
|
|
1126
|
+
};
|
|
1127
|
+
|
|
1128
|
+
return (
|
|
1129
|
+
<button
|
|
1130
|
+
onClick={handleClick}
|
|
1131
|
+
className="px-4 py-2 rounded-lg font-medium transition-all duration-200
|
|
1132
|
+
bg-gradient-to-r from-purple-600 to-blue-600
|
|
1133
|
+
hover:from-purple-700 hover:to-blue-700
|
|
1134
|
+
text-white shadow-lg hover:shadow-xl"
|
|
1135
|
+
>
|
|
1136
|
+
{connected && publicKey
|
|
1137
|
+
? truncateAddress(publicKey.toBase58())
|
|
1138
|
+
: "Connect Wallet"}
|
|
1139
|
+
</button>
|
|
1140
|
+
);
|
|
1141
|
+
}
|
|
1142
|
+
`;
|
|
1143
|
+
}
|
|
1144
|
+
function generatePrivacyStatus() {
|
|
1145
|
+
return `"use client";
|
|
1146
|
+
|
|
1147
|
+
import { useVeil } from "../../hooks/useVeil";
|
|
1148
|
+
|
|
1149
|
+
export function PrivacyStatus() {
|
|
1150
|
+
const { isAuthenticated, privacyLevel } = useVeil();
|
|
1151
|
+
|
|
1152
|
+
const getStatusColor = () => {
|
|
1153
|
+
if (!isAuthenticated) return "bg-gray-500";
|
|
1154
|
+
switch (privacyLevel) {
|
|
1155
|
+
case "high": return "bg-green-500";
|
|
1156
|
+
case "medium": return "bg-yellow-500";
|
|
1157
|
+
case "low": return "bg-red-500";
|
|
1158
|
+
default: return "bg-gray-500";
|
|
1159
|
+
}
|
|
1160
|
+
};
|
|
1161
|
+
|
|
1162
|
+
const getStatusText = () => {
|
|
1163
|
+
if (!isAuthenticated) return "Not Connected";
|
|
1164
|
+
switch (privacyLevel) {
|
|
1165
|
+
case "high": return "Privacy: High";
|
|
1166
|
+
case "medium": return "Privacy: Medium";
|
|
1167
|
+
case "low": return "Privacy: Low";
|
|
1168
|
+
default: return "Unknown";
|
|
1169
|
+
}
|
|
1170
|
+
};
|
|
1171
|
+
|
|
1172
|
+
return (
|
|
1173
|
+
<div className="flex items-center gap-2 px-3 py-1.5 rounded-full bg-gray-800/50 border border-gray-700">
|
|
1174
|
+
<div className={\`w-2 h-2 rounded-full \${getStatusColor()}\`} />
|
|
1175
|
+
<span className="text-sm text-gray-300">{getStatusText()}</span>
|
|
1176
|
+
</div>
|
|
1177
|
+
);
|
|
1178
|
+
}
|
|
1179
|
+
`;
|
|
1180
|
+
}
|
|
1181
|
+
function generateVeilProvider(config) {
|
|
1182
|
+
return `"use client";
|
|
1183
|
+
|
|
1184
|
+
import { createContext, useContext, useState, useEffect, ReactNode } from "react";
|
|
1185
|
+
import { useWallet } from "@solana/wallet-adapter-react";
|
|
1186
|
+
import { createLoginSession, isSessionValid, LoginSession } from "../privacy/login";
|
|
1187
|
+
|
|
1188
|
+
interface VeilContextType {
|
|
1189
|
+
session: LoginSession | null;
|
|
1190
|
+
isAuthenticated: boolean;
|
|
1191
|
+
privacyLevel: "high" | "medium" | "low" | null;
|
|
1192
|
+
login: () => Promise<void>;
|
|
1193
|
+
logout: () => void;
|
|
1194
|
+
}
|
|
1195
|
+
|
|
1196
|
+
const VeilContext = createContext<VeilContextType | null>(null);
|
|
1197
|
+
|
|
1198
|
+
export function VeilProvider({ children }: { children: ReactNode }) {
|
|
1199
|
+
const { connected, publicKey, signMessage } = useWallet();
|
|
1200
|
+
const [session, setSession] = useState<LoginSession | null>(null);
|
|
1201
|
+
|
|
1202
|
+
const isAuthenticated = connected && session !== null && isSessionValid(session);
|
|
1203
|
+
const privacyLevel = isAuthenticated ? "high" : null;
|
|
1204
|
+
|
|
1205
|
+
const login = async () => {
|
|
1206
|
+
if (!connected || !publicKey || !signMessage) {
|
|
1207
|
+
throw new Error("Wallet not connected");
|
|
1208
|
+
}
|
|
1209
|
+
|
|
1210
|
+
// Create identity proof from wallet signature
|
|
1211
|
+
const message = new TextEncoder().encode(
|
|
1212
|
+
\`Veil Login: \${Date.now()}\`
|
|
1213
|
+
);
|
|
1214
|
+
const signature = await signMessage(message);
|
|
1215
|
+
|
|
1216
|
+
const newSession = await createLoginSession(signature);
|
|
1217
|
+
setSession(newSession);
|
|
1218
|
+
};
|
|
1219
|
+
|
|
1220
|
+
const logout = () => {
|
|
1221
|
+
setSession(null);
|
|
1222
|
+
};
|
|
1223
|
+
|
|
1224
|
+
// Auto-logout when wallet disconnects
|
|
1225
|
+
useEffect(() => {
|
|
1226
|
+
if (!connected) {
|
|
1227
|
+
setSession(null);
|
|
1228
|
+
}
|
|
1229
|
+
}, [connected]);
|
|
1230
|
+
|
|
1231
|
+
return (
|
|
1232
|
+
<VeilContext.Provider value={{ session, isAuthenticated, privacyLevel, login, logout }}>
|
|
1233
|
+
{children}
|
|
1234
|
+
</VeilContext.Provider>
|
|
1235
|
+
);
|
|
1236
|
+
}
|
|
1237
|
+
|
|
1238
|
+
export function useVeilContext() {
|
|
1239
|
+
const context = useContext(VeilContext);
|
|
1240
|
+
if (!context) {
|
|
1241
|
+
throw new Error("useVeilContext must be used within VeilProvider");
|
|
1242
|
+
}
|
|
1243
|
+
return context;
|
|
1244
|
+
}
|
|
1245
|
+
`;
|
|
1246
|
+
}
|
|
1247
|
+
function generateVeilHooks(config) {
|
|
1248
|
+
return `"use client";
|
|
1249
|
+
|
|
1250
|
+
import { useVeilContext } from "../contexts/VeilProvider";
|
|
1251
|
+
import { PRIVACY_GUARANTEES } from "../privacy/guarantees";
|
|
1252
|
+
|
|
1253
|
+
export function useVeil() {
|
|
1254
|
+
const context = useVeilContext();
|
|
1255
|
+
return context;
|
|
1256
|
+
}
|
|
1257
|
+
|
|
1258
|
+
export function usePrivacyGuarantees() {
|
|
1259
|
+
return PRIVACY_GUARANTEES;
|
|
1260
|
+
}
|
|
1261
|
+
|
|
1262
|
+
export function usePrivacyCheck() {
|
|
1263
|
+
const { isAuthenticated, privacyLevel } = useVeil();
|
|
1264
|
+
|
|
1265
|
+
return {
|
|
1266
|
+
isPrivate: privacyLevel === "high",
|
|
1267
|
+
warnings: privacyLevel === "low"
|
|
1268
|
+
? ["Your session may be linkable to your identity"]
|
|
1269
|
+
: [],
|
|
1270
|
+
};
|
|
1271
|
+
}
|
|
1272
|
+
`;
|
|
1273
|
+
}
|
|
1274
|
+
|
|
1275
|
+
// src/templates/config.ts
|
|
1276
|
+
function generateLayoutTsx(config) {
|
|
1277
|
+
return `import type { Metadata } from "next";
|
|
1278
|
+
import { Inter } from "next/font/google";
|
|
1279
|
+
import "./globals.css";
|
|
1280
|
+
import { Providers } from "./providers";
|
|
1281
|
+
|
|
1282
|
+
const inter = Inter({ subsets: ["latin"] });
|
|
1283
|
+
|
|
1284
|
+
export const metadata: Metadata = {
|
|
1285
|
+
title: "${config.projectName}",
|
|
1286
|
+
description: "Privacy-first Solana app built with Veil",
|
|
1287
|
+
};
|
|
1288
|
+
|
|
1289
|
+
export default function RootLayout({
|
|
1290
|
+
children,
|
|
1291
|
+
}: {
|
|
1292
|
+
children: React.ReactNode;
|
|
1293
|
+
}) {
|
|
1294
|
+
return (
|
|
1295
|
+
<html lang="en">
|
|
1296
|
+
<body className={inter.className}>
|
|
1297
|
+
<Providers>
|
|
1298
|
+
{children}
|
|
1299
|
+
</Providers>
|
|
1300
|
+
</body>
|
|
1301
|
+
</html>
|
|
1302
|
+
);
|
|
1303
|
+
}
|
|
1304
|
+
`;
|
|
1305
|
+
}
|
|
1306
|
+
function generateGlobalsCss() {
|
|
1307
|
+
return `@tailwind base;
|
|
1308
|
+
@tailwind components;
|
|
1309
|
+
@tailwind utilities;
|
|
1310
|
+
|
|
1311
|
+
:root {
|
|
1312
|
+
--foreground-rgb: 255, 255, 255;
|
|
1313
|
+
--background-start-rgb: 10, 10, 20;
|
|
1314
|
+
--background-end-rgb: 20, 20, 40;
|
|
1315
|
+
}
|
|
1316
|
+
|
|
1317
|
+
body {
|
|
1318
|
+
color: rgb(var(--foreground-rgb));
|
|
1319
|
+
background: linear-gradient(
|
|
1320
|
+
to bottom,
|
|
1321
|
+
rgb(var(--background-start-rgb)),
|
|
1322
|
+
rgb(var(--background-end-rgb))
|
|
1323
|
+
);
|
|
1324
|
+
min-height: 100vh;
|
|
1325
|
+
}
|
|
1326
|
+
|
|
1327
|
+
/* Veil custom utilities */
|
|
1328
|
+
.glass {
|
|
1329
|
+
@apply bg-white/5 backdrop-blur-xl border border-white/10;
|
|
1330
|
+
}
|
|
1331
|
+
|
|
1332
|
+
.glass-hover {
|
|
1333
|
+
@apply hover:bg-white/10 hover:border-white/20 transition-all duration-300;
|
|
1334
|
+
}
|
|
1335
|
+
`;
|
|
1336
|
+
}
|
|
1337
|
+
function generateTailwindConfig(config) {
|
|
1338
|
+
const contentPath = config.template === "nextjs" ? `"./app/**/*.{js,ts,jsx,tsx,mdx}",` : `"./src/**/*.{js,ts,jsx,tsx,mdx}",`;
|
|
1339
|
+
return `/** @type {import('tailwindcss').Config} */
|
|
1340
|
+
module.exports = {
|
|
1341
|
+
content: [
|
|
1342
|
+
${contentPath}
|
|
1343
|
+
"./components/**/*.{js,ts,jsx,tsx,mdx}",
|
|
1344
|
+
"./contexts/**/*.{js,ts,jsx,tsx,mdx}",
|
|
1345
|
+
],
|
|
1346
|
+
theme: {
|
|
1347
|
+
extend: {
|
|
1348
|
+
colors: {
|
|
1349
|
+
veil: {
|
|
1350
|
+
purple: "#7C3AED",
|
|
1351
|
+
blue: "#3B82F6",
|
|
1352
|
+
dark: "#0A0A14",
|
|
1353
|
+
},
|
|
1354
|
+
},
|
|
1355
|
+
},
|
|
1356
|
+
},
|
|
1357
|
+
plugins: [],
|
|
1358
|
+
};
|
|
1359
|
+
`;
|
|
1360
|
+
}
|
|
1361
|
+
function generatePostcssConfig() {
|
|
1362
|
+
return `module.exports = {
|
|
1363
|
+
plugins: {
|
|
1364
|
+
tailwindcss: {},
|
|
1365
|
+
autoprefixer: {},
|
|
1366
|
+
},
|
|
1367
|
+
};
|
|
1368
|
+
`;
|
|
1369
|
+
}
|
|
1370
|
+
function generateNextConfig() {
|
|
1371
|
+
return `/** @type {import('next').NextConfig} */
|
|
1372
|
+
const nextConfig = {
|
|
1373
|
+
webpack: (config) => {
|
|
1374
|
+
config.externals.push("pino-pretty", "lokijs", "encoding");
|
|
1375
|
+
return config;
|
|
1376
|
+
},
|
|
1377
|
+
};
|
|
1378
|
+
|
|
1379
|
+
module.exports = nextConfig;
|
|
1380
|
+
`;
|
|
1381
|
+
}
|
|
1382
|
+
function generateProvidersTsx(config) {
|
|
1383
|
+
return `"use client";
|
|
1384
|
+
|
|
1385
|
+
import { WalletAdapterNetwork } from "@solana/wallet-adapter-base";
|
|
1386
|
+
import {
|
|
1387
|
+
ConnectionProvider,
|
|
1388
|
+
WalletProvider,
|
|
1389
|
+
} from "@solana/wallet-adapter-react";
|
|
1390
|
+
import { WalletModalProvider } from "@solana/wallet-adapter-react-ui";
|
|
1391
|
+
import { PhantomWalletAdapter } from "@solana/wallet-adapter-phantom";
|
|
1392
|
+
import { SolflareWalletAdapter } from "@solana/wallet-adapter-solflare";
|
|
1393
|
+
import { clusterApiUrl } from "@solana/web3.js";
|
|
1394
|
+
import { useMemo, ReactNode } from "react";
|
|
1395
|
+
import { VeilProvider } from "../contexts/VeilProvider";
|
|
1396
|
+
|
|
1397
|
+
import "@solana/wallet-adapter-react-ui/styles.css";
|
|
1398
|
+
|
|
1399
|
+
export function Providers({ children }: { children: ReactNode }) {
|
|
1400
|
+
const network = WalletAdapterNetwork.${config.network === "devnet" ? "Devnet" : "Devnet"};
|
|
1401
|
+
const endpoint = useMemo(() => {
|
|
1402
|
+
// Use Helius if configured, otherwise default
|
|
1403
|
+
if (typeof window === "undefined" && process.env.HELIUS_RPC_URL) {
|
|
1404
|
+
return process.env.HELIUS_RPC_URL;
|
|
1405
|
+
}
|
|
1406
|
+
return clusterApiUrl(network);
|
|
1407
|
+
}, [network]);
|
|
1408
|
+
|
|
1409
|
+
const wallets = useMemo(
|
|
1410
|
+
() => [new PhantomWalletAdapter(), new SolflareWalletAdapter()],
|
|
1411
|
+
[]
|
|
1412
|
+
);
|
|
1413
|
+
|
|
1414
|
+
return (
|
|
1415
|
+
<ConnectionProvider endpoint={endpoint}>
|
|
1416
|
+
<WalletProvider wallets={wallets} autoConnect>
|
|
1417
|
+
<WalletModalProvider>
|
|
1418
|
+
<VeilProvider>{children}</VeilProvider>
|
|
1419
|
+
</WalletModalProvider>
|
|
1420
|
+
</WalletProvider>
|
|
1421
|
+
</ConnectionProvider>
|
|
1422
|
+
);
|
|
1423
|
+
}
|
|
1424
|
+
`;
|
|
1425
|
+
}
|
|
1426
|
+
|
|
1427
|
+
// src/templates/shadowpay.ts
|
|
1428
|
+
function generateShadowPayModule(config) {
|
|
1429
|
+
if (config.shadowPay === "app") {
|
|
1430
|
+
return generateShadowPayApp();
|
|
1431
|
+
} else if (config.shadowPay === "wallet") {
|
|
1432
|
+
return generateShadowPayWallet();
|
|
1433
|
+
}
|
|
1434
|
+
return "";
|
|
1435
|
+
}
|
|
1436
|
+
function generateShadowPayApp() {
|
|
1437
|
+
return `/**
|
|
1438
|
+
* ShadowPay - App Mode (MAINNET)
|
|
1439
|
+
*
|
|
1440
|
+
* \u26A0\uFE0F IMPORTANT: ShadowPay uses MAINNET for real private transfers
|
|
1441
|
+
*
|
|
1442
|
+
* Your app RECEIVES private payments from users.
|
|
1443
|
+
* Users can pay you without revealing their identity to you.
|
|
1444
|
+
*
|
|
1445
|
+
* PRIVACY GUARANTEE:
|
|
1446
|
+
* - You receive payments but don't know who sent them
|
|
1447
|
+
* - Payment amounts are still visible on-chain
|
|
1448
|
+
* - User identity is protected via commitment schemes
|
|
1449
|
+
*
|
|
1450
|
+
* NETWORK: mainnet-beta (real SOL/tokens)
|
|
1451
|
+
* This is separate from Veil features which run on devnet
|
|
1452
|
+
*/
|
|
1453
|
+
|
|
1454
|
+
import { PublicKey, Transaction, Connection, clusterApiUrl } from "@solana/web3.js";
|
|
1455
|
+
|
|
1456
|
+
// ShadowPay always uses mainnet for real private transfers
|
|
1457
|
+
export const SHADOWPAY_NETWORK = "mainnet-beta";
|
|
1458
|
+
export const SHADOWPAY_RPC = process.env.NEXT_PUBLIC_SHADOWPAY_RPC || clusterApiUrl("mainnet-beta");
|
|
1459
|
+
|
|
1460
|
+
export function getShadowPayConnection(): Connection {
|
|
1461
|
+
return new Connection(SHADOWPAY_RPC, "confirmed");
|
|
1462
|
+
}
|
|
1463
|
+
|
|
1464
|
+
export interface PrivatePaymentRequest {
|
|
1465
|
+
/** Amount in lamports */
|
|
1466
|
+
amount: number;
|
|
1467
|
+
/** Your receiving address */
|
|
1468
|
+
recipient: PublicKey;
|
|
1469
|
+
/** Optional memo (visible on-chain) */
|
|
1470
|
+
memo?: string;
|
|
1471
|
+
/** Payment expiry timestamp */
|
|
1472
|
+
expiresAt?: number;
|
|
1473
|
+
}
|
|
1474
|
+
|
|
1475
|
+
export interface PrivatePayment {
|
|
1476
|
+
/** Unique payment ID */
|
|
1477
|
+
id: string;
|
|
1478
|
+
/** Amount received */
|
|
1479
|
+
amount: number;
|
|
1480
|
+
/** Transaction signature */
|
|
1481
|
+
signature: string;
|
|
1482
|
+
/** Commitment hash (hides sender) */
|
|
1483
|
+
senderCommitment: Uint8Array;
|
|
1484
|
+
/** Timestamp */
|
|
1485
|
+
timestamp: number;
|
|
1486
|
+
}
|
|
1487
|
+
|
|
1488
|
+
/**
|
|
1489
|
+
* Create a payment request that users can fulfill privately.
|
|
1490
|
+
*/
|
|
1491
|
+
export function createPaymentRequest(
|
|
1492
|
+
amount: number,
|
|
1493
|
+
recipient: PublicKey,
|
|
1494
|
+
options?: { memo?: string; expiresInSeconds?: number }
|
|
1495
|
+
): PrivatePaymentRequest {
|
|
1496
|
+
return {
|
|
1497
|
+
amount,
|
|
1498
|
+
recipient,
|
|
1499
|
+
memo: options?.memo,
|
|
1500
|
+
expiresAt: options?.expiresInSeconds
|
|
1501
|
+
? Date.now() + options.expiresInSeconds * 1000
|
|
1502
|
+
: undefined,
|
|
1503
|
+
};
|
|
1504
|
+
}
|
|
1505
|
+
|
|
1506
|
+
/**
|
|
1507
|
+
* Generate a payment link for users.
|
|
1508
|
+
* They can scan/click this to pay privately.
|
|
1509
|
+
*/
|
|
1510
|
+
export function generatePaymentLink(request: PrivatePaymentRequest): string {
|
|
1511
|
+
const params = new URLSearchParams({
|
|
1512
|
+
amount: request.amount.toString(),
|
|
1513
|
+
recipient: request.recipient.toBase58(),
|
|
1514
|
+
...(request.memo && { memo: request.memo }),
|
|
1515
|
+
...(request.expiresAt && { expires: request.expiresAt.toString() }),
|
|
1516
|
+
});
|
|
1517
|
+
|
|
1518
|
+
return \`shadowpay://pay?\${params.toString()}\`;
|
|
1519
|
+
}
|
|
1520
|
+
|
|
1521
|
+
/**
|
|
1522
|
+
* Verify a received payment without knowing the sender.
|
|
1523
|
+
*/
|
|
1524
|
+
export async function verifyPayment(
|
|
1525
|
+
connection: Connection,
|
|
1526
|
+
signature: string,
|
|
1527
|
+
expectedAmount: number,
|
|
1528
|
+
recipient: PublicKey
|
|
1529
|
+
): Promise<boolean> {
|
|
1530
|
+
const tx = await connection.getTransaction(signature, {
|
|
1531
|
+
commitment: "confirmed",
|
|
1532
|
+
});
|
|
1533
|
+
|
|
1534
|
+
if (!tx || !tx.meta) return false;
|
|
1535
|
+
|
|
1536
|
+
// Verify the payment was received
|
|
1537
|
+
const recipientIndex = tx.transaction.message.accountKeys.findIndex(
|
|
1538
|
+
(key) => key.equals(recipient)
|
|
1539
|
+
);
|
|
1540
|
+
|
|
1541
|
+
if (recipientIndex === -1) return false;
|
|
1542
|
+
|
|
1543
|
+
const postBalance = tx.meta.postBalances[recipientIndex];
|
|
1544
|
+
const preBalance = tx.meta.preBalances[recipientIndex];
|
|
1545
|
+
const received = postBalance - preBalance;
|
|
1546
|
+
|
|
1547
|
+
return received >= expectedAmount;
|
|
1548
|
+
}
|
|
1549
|
+
|
|
1550
|
+
/**
|
|
1551
|
+
* Listen for incoming private payments.
|
|
1552
|
+
*/
|
|
1553
|
+
export function onPaymentReceived(
|
|
1554
|
+
callback: (payment: PrivatePayment) => void
|
|
1555
|
+
): () => void {
|
|
1556
|
+
// In production: Set up WebSocket or polling
|
|
1557
|
+
console.log("Payment listener started");
|
|
1558
|
+
|
|
1559
|
+
return () => {
|
|
1560
|
+
console.log("Payment listener stopped");
|
|
1561
|
+
};
|
|
1562
|
+
}
|
|
1563
|
+
`;
|
|
1564
|
+
}
|
|
1565
|
+
function generateShadowPayWallet() {
|
|
1566
|
+
return `/**
|
|
1567
|
+
* ShadowPay - Wallet Mode (MAINNET)
|
|
1568
|
+
*
|
|
1569
|
+
* \u26A0\uFE0F IMPORTANT: ShadowPay uses MAINNET for real private transfers
|
|
1570
|
+
*
|
|
1571
|
+
* Your wallet SENDS private payments to apps/users.
|
|
1572
|
+
* You can pay without revealing your identity to recipients.
|
|
1573
|
+
*
|
|
1574
|
+
* PRIVACY GUARANTEE:
|
|
1575
|
+
* - Recipients don't know your identity
|
|
1576
|
+
* - Payment is verified via commitment proofs
|
|
1577
|
+
* - Your wallet address is not directly linked
|
|
1578
|
+
*
|
|
1579
|
+
* NETWORK: mainnet-beta (real SOL/tokens)
|
|
1580
|
+
* This is separate from Veil features which run on devnet
|
|
1581
|
+
*/
|
|
1582
|
+
|
|
1583
|
+
import { PublicKey, Transaction, Connection, Keypair, clusterApiUrl } from "@solana/web3.js";
|
|
1584
|
+
|
|
1585
|
+
// ShadowPay always uses mainnet for real private transfers
|
|
1586
|
+
export const SHADOWPAY_NETWORK = "mainnet-beta";
|
|
1587
|
+
export const SHADOWPAY_RPC = process.env.NEXT_PUBLIC_SHADOWPAY_RPC || clusterApiUrl("mainnet-beta");
|
|
1588
|
+
|
|
1589
|
+
export function getShadowPayConnection(): Connection {
|
|
1590
|
+
return new Connection(SHADOWPAY_RPC, "confirmed");
|
|
1591
|
+
}
|
|
1592
|
+
|
|
1593
|
+
export interface PrivatePaymentIntent {
|
|
1594
|
+
/** Amount in lamports */
|
|
1595
|
+
amount: number;
|
|
1596
|
+
/** Recipient address */
|
|
1597
|
+
recipient: PublicKey;
|
|
1598
|
+
/** Your commitment (hides your identity) */
|
|
1599
|
+
commitment: Uint8Array;
|
|
1600
|
+
}
|
|
1601
|
+
|
|
1602
|
+
/**
|
|
1603
|
+
* Create a commitment that hides your identity.
|
|
1604
|
+
*/
|
|
1605
|
+
export async function createSenderCommitment(
|
|
1606
|
+
senderSecret: Uint8Array
|
|
1607
|
+
): Promise<Uint8Array> {
|
|
1608
|
+
const hash = await crypto.subtle.digest("SHA-256", senderSecret.buffer as ArrayBuffer);
|
|
1609
|
+
return new Uint8Array(hash);
|
|
1610
|
+
}
|
|
1611
|
+
|
|
1612
|
+
/**
|
|
1613
|
+
* Prepare a private payment.
|
|
1614
|
+
* The recipient will receive funds but won't know your identity.
|
|
1615
|
+
*/
|
|
1616
|
+
export async function preparePrivatePayment(
|
|
1617
|
+
amount: number,
|
|
1618
|
+
recipient: PublicKey,
|
|
1619
|
+
senderSecret: Uint8Array
|
|
1620
|
+
): Promise<PrivatePaymentIntent> {
|
|
1621
|
+
const commitment = await createSenderCommitment(senderSecret);
|
|
1622
|
+
|
|
1623
|
+
return {
|
|
1624
|
+
amount,
|
|
1625
|
+
recipient,
|
|
1626
|
+
commitment,
|
|
1627
|
+
};
|
|
1628
|
+
}
|
|
1629
|
+
`;
|
|
1630
|
+
}
|
|
1631
|
+
|
|
1632
|
+
// src/templates/sdk-integration.ts
|
|
1633
|
+
function generateSdkProvider(config) {
|
|
1634
|
+
return `"use client";
|
|
1635
|
+
|
|
1636
|
+
/**
|
|
1637
|
+
* VeilSDKProvider - Full SDK integration for privacy features
|
|
1638
|
+
*
|
|
1639
|
+
* This provider wraps your app with the Veil SDK, enabling:
|
|
1640
|
+
* - ZK-based identity (no on-chain identity)
|
|
1641
|
+
* - Shielded balances (hide your holdings)
|
|
1642
|
+
* - Private transfers (hidden amounts)
|
|
1643
|
+
* - Private token operations
|
|
1644
|
+
* - Private DEX swaps
|
|
1645
|
+
*/
|
|
1646
|
+
|
|
1647
|
+
import { createContext, useContext, useState, useEffect, ReactNode, useCallback } from "react";
|
|
1648
|
+
import { useWallet, useConnection } from "@solana/wallet-adapter-react";
|
|
1649
|
+
import { VeilClient, ShieldedClient, TransferClient, TokenClient, DexClient } from "@veil-protocol/sdk";
|
|
1650
|
+
|
|
1651
|
+
interface VeilSDKContextType {
|
|
1652
|
+
// Core client
|
|
1653
|
+
veil: VeilClient | null;
|
|
1654
|
+
|
|
1655
|
+
// Sub-clients
|
|
1656
|
+
shielded: ShieldedClient | null;
|
|
1657
|
+
transfer: TransferClient | null;
|
|
1658
|
+
tokens: TokenClient | null;
|
|
1659
|
+
dex: DexClient | null;
|
|
1660
|
+
|
|
1661
|
+
// State
|
|
1662
|
+
isConnected: boolean;
|
|
1663
|
+
isLoading: boolean;
|
|
1664
|
+
error: string | null;
|
|
1665
|
+
|
|
1666
|
+
// Balances
|
|
1667
|
+
publicBalance: number;
|
|
1668
|
+
shieldedBalance: number;
|
|
1669
|
+
|
|
1670
|
+
// Actions
|
|
1671
|
+
refreshBalances: () => Promise<void>;
|
|
1672
|
+
shield: (amount: number) => Promise<string>;
|
|
1673
|
+
unshield: (amount: number) => Promise<string>;
|
|
1674
|
+
privateTransfer: (recipient: string, amount: number) => Promise<string>;
|
|
1675
|
+
}
|
|
1676
|
+
|
|
1677
|
+
const VeilSDKContext = createContext<VeilSDKContextType | null>(null);
|
|
1678
|
+
|
|
1679
|
+
export function VeilSDKProvider({ children }: { children: ReactNode }) {
|
|
1680
|
+
const { connection } = useConnection();
|
|
1681
|
+
const { publicKey, signTransaction, connected } = useWallet();
|
|
1682
|
+
|
|
1683
|
+
const [veil, setVeil] = useState<VeilClient | null>(null);
|
|
1684
|
+
const [shielded, setShielded] = useState<ShieldedClient | null>(null);
|
|
1685
|
+
const [transfer, setTransfer] = useState<TransferClient | null>(null);
|
|
1686
|
+
const [tokens, setTokens] = useState<TokenClient | null>(null);
|
|
1687
|
+
const [dex, setDex] = useState<DexClient | null>(null);
|
|
1688
|
+
|
|
1689
|
+
const [isLoading, setIsLoading] = useState(false);
|
|
1690
|
+
const [error, setError] = useState<string | null>(null);
|
|
1691
|
+
const [publicBalance, setPublicBalance] = useState(0);
|
|
1692
|
+
const [shieldedBalance, setShieldedBalance] = useState(0);
|
|
1693
|
+
|
|
1694
|
+
// Initialize SDK when wallet connects
|
|
1695
|
+
useEffect(() => {
|
|
1696
|
+
if (connected && publicKey) {
|
|
1697
|
+
const client = new VeilClient({ connection });
|
|
1698
|
+
setVeil(client);
|
|
1699
|
+
setShielded(client.shielded);
|
|
1700
|
+
setTransfer(client.transfer);
|
|
1701
|
+
setTokens(client.tokens);
|
|
1702
|
+
setDex(client.dex);
|
|
1703
|
+
} else {
|
|
1704
|
+
setVeil(null);
|
|
1705
|
+
setShielded(null);
|
|
1706
|
+
setTransfer(null);
|
|
1707
|
+
setTokens(null);
|
|
1708
|
+
setDex(null);
|
|
1709
|
+
}
|
|
1710
|
+
}, [connected, publicKey, connection]);
|
|
1711
|
+
|
|
1712
|
+
const refreshBalances = useCallback(async () => {
|
|
1713
|
+
if (!veil || !publicKey) return;
|
|
1714
|
+
|
|
1715
|
+
setIsLoading(true);
|
|
1716
|
+
try {
|
|
1717
|
+
const balances = await veil.getBalances(publicKey);
|
|
1718
|
+
setPublicBalance(balances.public);
|
|
1719
|
+
setShieldedBalance(balances.shielded);
|
|
1720
|
+
} catch (err) {
|
|
1721
|
+
setError(err instanceof Error ? err.message : "Failed to fetch balances");
|
|
1722
|
+
} finally {
|
|
1723
|
+
setIsLoading(false);
|
|
1724
|
+
}
|
|
1725
|
+
}, [veil, publicKey]);
|
|
1726
|
+
|
|
1727
|
+
const shield = useCallback(async (amount: number): Promise<string> => {
|
|
1728
|
+
if (!shielded || !publicKey || !signTransaction) {
|
|
1729
|
+
throw new Error("Wallet not connected");
|
|
1730
|
+
}
|
|
1731
|
+
return shielded.deposit(publicKey, amount, signTransaction);
|
|
1732
|
+
}, [shielded, publicKey, signTransaction]);
|
|
1733
|
+
|
|
1734
|
+
const unshield = useCallback(async (amount: number): Promise<string> => {
|
|
1735
|
+
if (!shielded || !publicKey || !signTransaction) {
|
|
1736
|
+
throw new Error("Wallet not connected");
|
|
1737
|
+
}
|
|
1738
|
+
return shielded.withdraw(publicKey, amount, signTransaction);
|
|
1739
|
+
}, [shielded, publicKey, signTransaction]);
|
|
1740
|
+
|
|
1741
|
+
const privateTransfer = useCallback(async (recipient: string, amount: number): Promise<string> => {
|
|
1742
|
+
if (!transfer || !publicKey || !signTransaction) {
|
|
1743
|
+
throw new Error("Wallet not connected");
|
|
1744
|
+
}
|
|
1745
|
+
return transfer.privateTransfer(publicKey, recipient, amount, signTransaction);
|
|
1746
|
+
}, [transfer, publicKey, signTransaction]);
|
|
1747
|
+
|
|
1748
|
+
return (
|
|
1749
|
+
<VeilSDKContext.Provider value={{
|
|
1750
|
+
veil,
|
|
1751
|
+
shielded,
|
|
1752
|
+
transfer,
|
|
1753
|
+
tokens,
|
|
1754
|
+
dex,
|
|
1755
|
+
isConnected: connected && veil !== null,
|
|
1756
|
+
isLoading,
|
|
1757
|
+
error,
|
|
1758
|
+
publicBalance,
|
|
1759
|
+
shieldedBalance,
|
|
1760
|
+
refreshBalances,
|
|
1761
|
+
shield,
|
|
1762
|
+
unshield,
|
|
1763
|
+
privateTransfer,
|
|
1764
|
+
}}>
|
|
1765
|
+
{children}
|
|
1766
|
+
</VeilSDKContext.Provider>
|
|
1767
|
+
);
|
|
1768
|
+
}
|
|
1769
|
+
|
|
1770
|
+
export function useVeilSDK() {
|
|
1771
|
+
const context = useContext(VeilSDKContext);
|
|
1772
|
+
if (!context) {
|
|
1773
|
+
throw new Error("useVeilSDK must be used within VeilSDKProvider");
|
|
1774
|
+
}
|
|
1775
|
+
return context;
|
|
1776
|
+
}
|
|
1777
|
+
`;
|
|
1778
|
+
}
|
|
1779
|
+
function generateSdkHooks() {
|
|
1780
|
+
return `"use client";
|
|
1781
|
+
|
|
1782
|
+
/**
|
|
1783
|
+
* Veil SDK Hooks
|
|
1784
|
+
*
|
|
1785
|
+
* Convenient hooks for common SDK operations
|
|
1786
|
+
*/
|
|
1787
|
+
|
|
1788
|
+
import { useVeilSDK } from "../contexts/VeilSDKProvider";
|
|
1789
|
+
import { useState, useCallback } from "react";
|
|
1790
|
+
|
|
1791
|
+
export function useShieldedBalance() {
|
|
1792
|
+
const { publicBalance, shieldedBalance, refreshBalances, isLoading } = useVeilSDK();
|
|
1793
|
+
return { publicBalance, shieldedBalance, refresh: refreshBalances, isLoading };
|
|
1794
|
+
}
|
|
1795
|
+
|
|
1796
|
+
export function usePrivateTransfer() {
|
|
1797
|
+
const { privateTransfer } = useVeilSDK();
|
|
1798
|
+
const [isPending, setIsPending] = useState(false);
|
|
1799
|
+
const [txHash, setTxHash] = useState<string | null>(null);
|
|
1800
|
+
const [error, setError] = useState<string | null>(null);
|
|
1801
|
+
|
|
1802
|
+
const send = useCallback(async (recipient: string, amount: number) => {
|
|
1803
|
+
setIsPending(true);
|
|
1804
|
+
setError(null);
|
|
1805
|
+
try {
|
|
1806
|
+
const hash = await privateTransfer(recipient, amount);
|
|
1807
|
+
setTxHash(hash);
|
|
1808
|
+
return hash;
|
|
1809
|
+
} catch (err) {
|
|
1810
|
+
setError(err instanceof Error ? err.message : "Transfer failed");
|
|
1811
|
+
throw err;
|
|
1812
|
+
} finally {
|
|
1813
|
+
setIsPending(false);
|
|
1814
|
+
}
|
|
1815
|
+
}, [privateTransfer]);
|
|
1816
|
+
|
|
1817
|
+
return { send, isPending, txHash, error };
|
|
1818
|
+
}
|
|
1819
|
+
`;
|
|
1820
|
+
}
|
|
1821
|
+
|
|
1822
|
+
// src/templates/index.ts
|
|
1823
|
+
function generateVeilConfig(config) {
|
|
1824
|
+
return `/**
|
|
1825
|
+
* Veil Configuration
|
|
1826
|
+
*
|
|
1827
|
+
* This file documents the privacy guarantees of your application.
|
|
1828
|
+
* It is used by the Veil CLI and serves as documentation.
|
|
1829
|
+
*/
|
|
1830
|
+
|
|
1831
|
+
export interface VeilConfig {
|
|
1832
|
+
project: {
|
|
1833
|
+
name: string;
|
|
1834
|
+
network: "devnet" | "mainnet-beta";
|
|
1835
|
+
};
|
|
1836
|
+
privacy: {
|
|
1837
|
+
identity: {
|
|
1838
|
+
enabled: boolean;
|
|
1839
|
+
method: "zk-login" | "passkey";
|
|
1840
|
+
storeOnChain: boolean;
|
|
1841
|
+
};
|
|
1842
|
+
access: {
|
|
1843
|
+
proofRequired: boolean;
|
|
1844
|
+
revealAddress: boolean;
|
|
1845
|
+
};
|
|
1846
|
+
recovery: {
|
|
1847
|
+
enabled: boolean;
|
|
1848
|
+
method: "timelock" | "shamir";
|
|
1849
|
+
publicGuardians: boolean;
|
|
1850
|
+
revealParticipants: boolean;
|
|
1851
|
+
};
|
|
1852
|
+
};
|
|
1853
|
+
infrastructure: {
|
|
1854
|
+
rpc: {
|
|
1855
|
+
provider: "helius" | "default";
|
|
1856
|
+
publicPolling: boolean;
|
|
1857
|
+
};
|
|
1858
|
+
observability: {
|
|
1859
|
+
webhooks: boolean;
|
|
1860
|
+
exposeMetadata: boolean;
|
|
1861
|
+
};
|
|
1862
|
+
};
|
|
1863
|
+
integrations: {
|
|
1864
|
+
shadowPay: {
|
|
1865
|
+
enabled: boolean;
|
|
1866
|
+
};
|
|
1867
|
+
};
|
|
1868
|
+
}
|
|
1869
|
+
|
|
1870
|
+
export function defineVeilConfig(config: VeilConfig): VeilConfig {
|
|
1871
|
+
return config;
|
|
1872
|
+
}
|
|
1873
|
+
|
|
1874
|
+
export default defineVeilConfig({
|
|
1875
|
+
project: {
|
|
1876
|
+
name: "${config.projectName}",
|
|
1877
|
+
network: "${config.network}",
|
|
1878
|
+
},
|
|
1879
|
+
|
|
1880
|
+
privacy: {
|
|
1881
|
+
identity: {
|
|
1882
|
+
enabled: true,
|
|
1883
|
+
method: "zk-login", // conceptual, hackathon-safe
|
|
1884
|
+
storeOnChain: false,
|
|
1885
|
+
},
|
|
1886
|
+
|
|
1887
|
+
access: {
|
|
1888
|
+
proofRequired: true,
|
|
1889
|
+
revealAddress: false,
|
|
1890
|
+
},
|
|
1891
|
+
|
|
1892
|
+
recovery: {
|
|
1893
|
+
enabled: true,
|
|
1894
|
+
method: "timelock", // or "shamir"
|
|
1895
|
+
publicGuardians: false,
|
|
1896
|
+
revealParticipants: false,
|
|
1897
|
+
},
|
|
1898
|
+
},
|
|
1899
|
+
|
|
1900
|
+
infrastructure: {
|
|
1901
|
+
rpc: {
|
|
1902
|
+
provider: "${config.helius ? "helius" : "default"}",
|
|
1903
|
+
publicPolling: false,
|
|
1904
|
+
},
|
|
1905
|
+
|
|
1906
|
+
observability: {
|
|
1907
|
+
webhooks: ${config.helius},
|
|
1908
|
+
exposeMetadata: false,
|
|
1909
|
+
},
|
|
1910
|
+
},
|
|
1911
|
+
|
|
1912
|
+
integrations: {
|
|
1913
|
+
shadowPay: {
|
|
1914
|
+
enabled: ${config.shadowPay ? "true" : "false"},
|
|
1915
|
+
},
|
|
1916
|
+
},
|
|
1917
|
+
});
|
|
1918
|
+
`;
|
|
1919
|
+
}
|
|
1920
|
+
function generateEnvExample(config) {
|
|
1921
|
+
let env = `# ============================================
|
|
1922
|
+
# Veil Configuration
|
|
1923
|
+
# ============================================
|
|
1924
|
+
|
|
1925
|
+
# Veil Features Network (voting, staking, multisig)
|
|
1926
|
+
NEXT_PUBLIC_NETWORK=${config.network}
|
|
1927
|
+
`;
|
|
1928
|
+
if (config.helius) {
|
|
1929
|
+
env += `
|
|
1930
|
+
# Helius RPC for Veil features (${config.network})
|
|
1931
|
+
# Get your key at https://helius.dev
|
|
1932
|
+
HELIUS_API_KEY=your_helius_api_key
|
|
1933
|
+
HELIUS_RPC_URL=https://${config.network}.helius-rpc.com/?api-key=YOUR_KEY
|
|
1934
|
+
HELIUS_WEBHOOK_SECRET=your_webhook_secret
|
|
1935
|
+
`;
|
|
1936
|
+
}
|
|
1937
|
+
if (config.shadowPay) {
|
|
1938
|
+
env += `
|
|
1939
|
+
# ============================================
|
|
1940
|
+
# ShadowPay Configuration (MAINNET)
|
|
1941
|
+
# ============================================
|
|
1942
|
+
# \u26A0\uFE0F ShadowPay uses MAINNET for real private transfers
|
|
1943
|
+
# This is separate from Veil features which use ${config.network}
|
|
1944
|
+
|
|
1945
|
+
# Mainnet RPC for ShadowPay (Helius recommended for production)
|
|
1946
|
+
NEXT_PUBLIC_SHADOWPAY_RPC=https://mainnet.helius-rpc.com/?api-key=YOUR_KEY
|
|
1947
|
+
|
|
1948
|
+
# Optional: ShadowPay API for enhanced features
|
|
1949
|
+
SHADOWPAY_API_KEY=your_shadowpay_key
|
|
1950
|
+
`;
|
|
1951
|
+
}
|
|
1952
|
+
return env;
|
|
1953
|
+
}
|
|
1954
|
+
function generateReadme(config) {
|
|
1955
|
+
const srcDir = config.framework === "nextjs" ? "app" : "src";
|
|
1956
|
+
const templateInfo = TEMPLATE_INFO[config.template];
|
|
1957
|
+
return `# ${config.projectName}
|
|
1958
|
+
|
|
1959
|
+
**${templateInfo.name}** \u2014 ${templateInfo.description}
|
|
1960
|
+
|
|
1961
|
+
A privacy-first Solana application built with Veil + ShadowWire.
|
|
1962
|
+
|
|
1963
|
+
## Privacy Architecture
|
|
1964
|
+
|
|
1965
|
+
This app includes the **complete Veil privacy stack**:
|
|
1966
|
+
|
|
1967
|
+
| Feature | Description | Network |
|
|
1968
|
+
|---------|-------------|---------|
|
|
1969
|
+
| \u{1F510} **Identity** | ZK authentication, no PII on-chain | ${config.network} |
|
|
1970
|
+
| \u{1F504} **Recovery** | Shamir secret sharing, hidden guardians | ${config.network} |
|
|
1971
|
+
| \u{1F5F3}\uFE0F **Voting** | Commit-reveal privacy | ${config.network} |
|
|
1972
|
+
| \u{1F4CA} **Staking** | Hidden amounts via Pedersen | ${config.network} |
|
|
1973
|
+
| \u{1F465} **Multisig** | Stealth signers | ${config.network} |
|
|
1974
|
+
${config.features.shadowpay ? `| \u26A1 **ShadowPay** | Private transfers via @radr/shadowwire | mainnet |` : ""}
|
|
1975
|
+
|
|
1976
|
+
## What's Private vs Public
|
|
1977
|
+
|
|
1978
|
+
| Aspect | Private | Public |
|
|
1979
|
+
|--------|---------|--------|
|
|
1980
|
+
| Your identity | \u2705 Never on-chain | |
|
|
1981
|
+
| Wallet access method | \u2705 Hidden | |
|
|
1982
|
+
| Recovery guardians | \u2705 Hidden | |
|
|
1983
|
+
| Vote choices | \u2705 Hidden until reveal | |
|
|
1984
|
+
| Stake amounts | \u2705 Committed privately | |
|
|
1985
|
+
| Transaction amounts | | \u274C Visible (unless via ShadowPay) |
|
|
1986
|
+
|
|
1987
|
+
## Project Structure
|
|
1988
|
+
|
|
1989
|
+
\`\`\`
|
|
1990
|
+
/${srcDir} \u2192 Frontend application
|
|
1991
|
+
/${srcDir}/components \u2192 UI & privacy components
|
|
1992
|
+
/lib/privacy \u2192 Privacy modules (login, recovery, access)
|
|
1993
|
+
/lib/veil \u2192 Veil SDK integration (RPC, Helius)
|
|
1994
|
+
${config.features.shadowpay ? `/lib/shadowpay \u2192 ShadowWire private transfers (mainnet)` : ""}
|
|
1995
|
+
/hooks \u2192 React hooks for Veil SDK
|
|
1996
|
+
/contexts \u2192 Provider contexts
|
|
1997
|
+
\`\`\`
|
|
1998
|
+
|
|
1999
|
+
## Getting Started
|
|
2000
|
+
|
|
2001
|
+
\`\`\`bash
|
|
2002
|
+
# Install dependencies
|
|
2003
|
+
pnpm install
|
|
2004
|
+
|
|
2005
|
+
# Copy environment variables
|
|
2006
|
+
cp .env.example .env
|
|
2007
|
+
|
|
2008
|
+
# Start development server
|
|
2009
|
+
pnpm dev
|
|
2010
|
+
\`\`\`
|
|
2011
|
+
|
|
2012
|
+
## Network Configuration
|
|
2013
|
+
|
|
2014
|
+
- **Veil Features**: Run on \`${config.network}\` for development
|
|
2015
|
+
- **ShadowPay**: Runs on \`mainnet\` for real private transfers
|
|
2016
|
+
|
|
2017
|
+
---
|
|
2018
|
+
|
|
2019
|
+
Built with [Veil](https://github.com/veil-protocol) + [ShadowWire](https://shadowwire.io)
|
|
2020
|
+
`;
|
|
2021
|
+
}
|
|
2022
|
+
|
|
2023
|
+
export {
|
|
2024
|
+
generateLoginTs,
|
|
2025
|
+
generateRecoveryTs,
|
|
2026
|
+
generateAccessTs,
|
|
2027
|
+
generateGuaranteesTs,
|
|
2028
|
+
generateRpcTs,
|
|
2029
|
+
generateHeliusTs,
|
|
2030
|
+
generatePackageJson,
|
|
2031
|
+
generateTsConfig,
|
|
2032
|
+
generateAppEntry,
|
|
2033
|
+
generateWalletButton,
|
|
2034
|
+
generatePrivacyStatus,
|
|
2035
|
+
generateVeilProvider,
|
|
2036
|
+
generateVeilHooks,
|
|
2037
|
+
generateLayoutTsx,
|
|
2038
|
+
generateGlobalsCss,
|
|
2039
|
+
generateTailwindConfig,
|
|
2040
|
+
generatePostcssConfig,
|
|
2041
|
+
generateNextConfig,
|
|
2042
|
+
generateProvidersTsx,
|
|
2043
|
+
generateShadowPayModule,
|
|
2044
|
+
generateSdkProvider,
|
|
2045
|
+
generateSdkHooks,
|
|
2046
|
+
generateVeilConfig,
|
|
2047
|
+
generateEnvExample,
|
|
2048
|
+
generateReadme,
|
|
2049
|
+
TEMPLATE_INFO,
|
|
2050
|
+
runInit,
|
|
2051
|
+
runAdd
|
|
2052
|
+
};
|