create-lightning-scaffold 1.0.0 → 1.0.2

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.
Files changed (65) hide show
  1. package/README.md +107 -25
  2. package/dist/index.js +169 -64
  3. package/package.json +1 -1
  4. package/templates/backend/firebase/index.ts +86 -9
  5. package/templates/backend/supabase/index.ts +107 -6
  6. package/templates/base/.env.example.ejs +17 -10
  7. package/templates/lib/backend.ts.ejs +59 -0
  8. package/templates/mobile/app/_layout.tsx +22 -52
  9. package/templates/mobile/app/index.tsx.ejs +44 -0
  10. package/templates/mobile/components/History.tsx.ejs +114 -0
  11. package/templates/mobile/components/Onboarding.tsx.ejs +79 -0
  12. package/templates/mobile/components/Recovery.tsx.ejs +119 -0
  13. package/templates/mobile/components/Swap.tsx.ejs +315 -0
  14. package/templates/mobile/package.json.ejs +4 -0
  15. package/templates/vite/README.md +73 -0
  16. package/templates/vite/eslint.config.js +23 -0
  17. package/templates/vite/index.html +13 -0
  18. package/templates/vite/package.json.ejs +37 -0
  19. package/templates/vite/postcss.config.js.ejs +8 -0
  20. package/templates/vite/public/vite.svg +1 -0
  21. package/templates/vite/src/App.tsx.ejs +65 -0
  22. package/templates/vite/src/assets/react.svg +1 -0
  23. package/templates/vite/src/components/History.tsx.ejs +112 -0
  24. package/templates/vite/src/components/Onboarding.tsx.ejs +64 -0
  25. package/templates/vite/src/components/Recovery.tsx.ejs +107 -0
  26. package/templates/vite/src/components/Swap.tsx.ejs +241 -0
  27. package/templates/vite/src/index.css.ejs +55 -0
  28. package/templates/vite/src/main.tsx +25 -0
  29. package/templates/vite/tailwind.config.js.ejs +13 -0
  30. package/templates/vite/tsconfig.app.json +28 -0
  31. package/templates/vite/tsconfig.json +7 -0
  32. package/templates/vite/tsconfig.node.json +26 -0
  33. package/templates/vite/vite.config.ts +15 -0
  34. package/templates/web/app/globals.css.ejs +53 -0
  35. package/templates/web/app/layout.tsx.ejs +8 -15
  36. package/templates/web/app/page.tsx.ejs +45 -0
  37. package/templates/web/app/providers.tsx +27 -0
  38. package/templates/web/components/History.tsx.ejs +114 -0
  39. package/templates/web/components/Onboarding.tsx.ejs +66 -0
  40. package/templates/web/components/Recovery.tsx.ejs +108 -0
  41. package/templates/web/components/Swap.tsx.ejs +289 -0
  42. package/templates/web/package.json.ejs +3 -1
  43. package/templates/examples/mobile/biometric-onboard.tsx +0 -104
  44. package/templates/examples/mobile/gasless-transfer.tsx +0 -72
  45. package/templates/examples/mobile/index.tsx +0 -30
  46. package/templates/examples/mobile/passkey-login.tsx +0 -55
  47. package/templates/examples/web/biometric-onboard/page.tsx +0 -70
  48. package/templates/examples/web/gasless-transfer/page.tsx +0 -85
  49. package/templates/examples/web/page.tsx +0 -27
  50. package/templates/examples/web/passkey-login/page.tsx +0 -50
  51. package/templates/lib/lazorkit/mobile/index.ts +0 -53
  52. package/templates/lib/lazorkit/web/index.ts +0 -67
  53. package/templates/mobile/app/(tabs)/_layout.tsx +0 -59
  54. package/templates/mobile/app/(tabs)/index.tsx +0 -31
  55. package/templates/mobile/app/(tabs)/two.tsx +0 -31
  56. package/templates/mobile/app/+html.tsx +0 -38
  57. package/templates/mobile/app/+not-found.tsx +0 -40
  58. package/templates/mobile/app/modal.tsx +0 -35
  59. package/templates/mobile/components/EditScreenInfo.tsx +0 -77
  60. package/templates/mobile/components/StyledText.tsx +0 -5
  61. package/templates/mobile/components/__tests__/StyledText-test.js +0 -10
  62. package/templates/mobile/lib/lazorkit/index.ts +0 -53
  63. package/templates/web/app/globals.css +0 -26
  64. package/templates/web/app/page.tsx +0 -65
  65. package/templates/web/lib/lazorkit/index.ts +0 -67
package/README.md CHANGED
@@ -1,6 +1,7 @@
1
1
  # create-lightning-scaffold
2
+ <img width="1826" height="1183" alt="Screenshot 2026-01-12 at 22 18 56" src="https://github.com/user-attachments/assets/c34c35d4-1d18-49a4-8024-2d55f774af2e" />
2
3
 
3
- CLI to scaffold projects with LazorKit SDK integration. Generate React Native (Expo) or Next.js projects with passkey authentication, gasless transactions, and biometric onboarding built-in.
4
+ CLI to scaffold Solana apps with LazorKit SDK. Generate React Native (Expo) or Next.js projects with passkey authentication, gasless transactions, and a ready-to-use swap interface.
4
5
 
5
6
  ## Quick Start
6
7
 
@@ -8,24 +9,29 @@ CLI to scaffold projects with LazorKit SDK integration. Generate React Native (E
8
9
  npx create-lightning-scaffold
9
10
  ```
10
11
 
11
- ## Features
12
+ ## What You Get
12
13
 
13
- - 🚀 **5 Presets**: Mobile App, Web App, Full-Stack Mobile, Full-Stack Web, Monorepo
14
- - 🔐 **LazorKit SDK**: Passkey auth, gasless transactions, smart wallets
15
- - 📱 **React Native + Expo**: Official `create-expo-app` under the hood
16
- - 🌐 **Next.js**: Official `create-next-app` under the hood
17
- - 🎨 **Styling**: TailwindCSS (web) / NativeWind (mobile)
18
- - 📦 **State**: Zustand or Redux Toolkit
19
- - 🗄️ **Backend**: Supabase or Firebase integration
20
- - 📦 **Package Managers**: npm, pnpm, yarn, bun
14
+ Every generated project includes:
21
15
 
22
- ## Included Examples
16
+ - **Passkey Onboarding** - Create wallets with Face ID/Touch ID, no seed phrases
17
+ - **Token Swap UI** - Real swap interface powered by Jupiter aggregator (SOL ↔ USDC)
18
+ - **Balance Display** - Real-time token balances with Max button
19
+ - **Transaction History** - View past transactions with Solscan links
20
+ - **Recovery & Backup** - Add backup passkeys from other devices
21
+ - **Message Signing** - Verify wallet ownership with `signMessage`
22
+ - **Gasless Transactions** - Users don't pay gas fees (paymaster sponsored)
23
+ - **Smart Wallets** - LazorKit PDA-based accounts with recovery options
23
24
 
24
- Every generated project includes 3 working LazorKit examples:
25
+ ## Features
25
26
 
26
- 1. **Passkey Login** - WebAuthn-based authentication with smart wallet
27
- 2. **Gasless Transfer** - Send SOL without paying gas fees
28
- 3. **Biometric Onboarding** - Mobile-first onboarding with FaceID/TouchID
27
+ - 🚀 **5 Presets**: Mobile App, Web App, Full-Stack Mobile, Full-Stack Web, Monorepo
28
+ - 🔐 **LazorKit SDK**: Official `@lazorkit/wallet` (web) and `@lazorkit/wallet-mobile-adapter` (mobile)
29
+ - 🔄 **Jupiter Integration**: Real-time quotes and swaps via Jupiter aggregator API
30
+ - 📱 **React Native + Expo**: Native passkey support with `expo-web-browser`
31
+ - 🌐 **Next.js + Vite**: Both frameworks supported with proper polyfills
32
+ - 🎨 **Styling**: TailwindCSS or pure CSS/StyleSheet (your choice)
33
+ - 📦 **State**: Zustand or Redux Toolkit
34
+ - 🗄️ **Backend**: Supabase, Firebase, or NestJS integration
29
35
 
30
36
  ## Usage
31
37
 
@@ -36,29 +42,105 @@ npx create-lightning-scaffold
36
42
  # You'll be prompted for:
37
43
  # - Project name
38
44
  # - Preset (Mobile, Web, Full-Stack, Monorepo)
39
- # - Customization options (styling, state, components, backend)
45
+ # - Web framework (Next.js or Vite)
46
+ # - Styling preference (tailwind or none)
47
+ # - State management
40
48
  # - Package manager
41
49
  ```
42
50
 
43
51
  ## Presets
44
52
 
45
- | Preset | Description | Structure |
46
- |--------|-------------|-----------|
47
- | Mobile App | React Native + Expo | Flat |
48
- | Web App | Next.js | Flat |
49
- | Full-Stack Mobile | React Native + Backend | Monorepo |
50
- | Full-Stack Web | Next.js + Backend | Monorepo |
51
- | Monorepo | Mobile + Web + Backend | Monorepo |
53
+ | Preset | Description | SDK |
54
+ |--------|-------------|-----|
55
+ | Mobile App | React Native + Expo | `@lazorkit/wallet-mobile-adapter` |
56
+ | Web App | Next.js or Vite | `@lazorkit/wallet` |
57
+ | Full-Stack Mobile | React Native + Backend | `@lazorkit/wallet-mobile-adapter` |
58
+ | Full-Stack Web | Next.js/Vite + Backend | `@lazorkit/wallet` |
59
+ | Monorepo | Mobile + Web + Backend | Both |
52
60
 
53
61
  ## Configuration
54
62
 
55
- After scaffolding, copy `.env.example` to `.env` and add your LazorKit API key:
63
+ After scaffolding, copy `.env.example` to `.env`:
56
64
 
57
65
  ```bash
58
66
  cp .env.example .env
59
67
  ```
60
68
 
61
- Get your API key from [portal.lazor.sh](https://portal.lazor.sh).
69
+ Default config uses Devnet. For production, get your API key from [portal.lazor.sh](https://portal.lazor.sh).
70
+
71
+ ## SDK Integration
72
+
73
+ ### Web (Next.js / Vite)
74
+
75
+ Uses `@lazorkit/wallet` with `LazorkitProvider`:
76
+
77
+ ```tsx
78
+ import { LazorkitProvider, useWallet } from '@lazorkit/wallet';
79
+
80
+ // Provider setup
81
+ <LazorkitProvider
82
+ rpcUrl="https://api.devnet.solana.com"
83
+ portalUrl="https://portal.lazor.sh"
84
+ paymasterConfig={{ paymasterUrl: "https://kora.devnet.lazorkit.com" }}
85
+ >
86
+ {children}
87
+ </LazorkitProvider>
88
+
89
+ // In components
90
+ const { connect, disconnect, signAndSendTransaction, signMessage, wallet } = useWallet();
91
+
92
+ // Connect with passkey
93
+ await connect();
94
+
95
+ // Sign a message
96
+ const { signature } = await signMessage("Verify ownership");
97
+
98
+ // Send gasless transaction
99
+ const sig = await signAndSendTransaction({
100
+ instructions: [instruction],
101
+ transactionOptions: { feeToken: 'USDC' }
102
+ });
103
+ ```
104
+
105
+ ### Mobile (Expo)
106
+
107
+ Uses `@lazorkit/wallet-mobile-adapter` with `LazorKitProvider`:
108
+
109
+ ```tsx
110
+ import { LazorKitProvider, useWallet } from '@lazorkit/wallet-mobile-adapter';
111
+
112
+ // Provider setup
113
+ <LazorKitProvider
114
+ rpcUrl="https://api.devnet.solana.com"
115
+ portalUrl="https://portal.lazor.sh"
116
+ configPaymaster={{ paymasterUrl: "https://kora.devnet.lazorkit.com" }}
117
+ >
118
+ {children}
119
+ </LazorKitProvider>
120
+
121
+ // In components - requires redirectUrl for mobile
122
+ const { connect, signAndSendTransaction, signMessage, wallet } = useWallet();
123
+
124
+ await connect({ redirectUrl: 'myapp://callback' });
125
+ await signMessage("Hello", { redirectUrl: 'myapp://callback' });
126
+ ```
127
+
128
+ ## Jupiter Swap Integration
129
+
130
+ The generated swap UI uses Jupiter's aggregator API for real-time quotes:
131
+
132
+ ```tsx
133
+ // Get quote
134
+ const quote = await fetch(
135
+ `https://api.jup.ag/swap/v1/quote?inputMint=${SOL}&outputMint=${USDC}&amount=${amount}&slippageBps=50`
136
+ ).then(r => r.json());
137
+
138
+ // Build swap transaction
139
+ const { swapTransaction } = await fetch('https://api.jup.ag/swap/v1/swap', {
140
+ method: 'POST',
141
+ body: JSON.stringify({ quoteResponse: quote, userPublicKey: wallet })
142
+ }).then(r => r.json());
143
+ ```
62
144
 
63
145
  ## License
64
146
 
package/dist/index.js CHANGED
@@ -14,12 +14,12 @@ import pc from "picocolors";
14
14
  // src/utils/types.ts
15
15
  var PRESET_INFO = {
16
16
  mobile: { label: "Mobile App", hint: "React Native + Expo", platforms: ["mobile"] },
17
- web: { label: "Web App", hint: "Next.js", platforms: ["web"] },
17
+ web: { label: "Web App", hint: "Next.js or Vite", platforms: ["web"] },
18
18
  "fullstack-mobile": { label: "Full-Stack Mobile", hint: "React Native + Backend", platforms: ["mobile"] },
19
- "fullstack-web": { label: "Full-Stack Web", hint: "Next.js + Backend", platforms: ["web"] },
19
+ "fullstack-web": { label: "Full-Stack Web", hint: "Web + Backend", platforms: ["web"] },
20
20
  monorepo: { label: "Monorepo", hint: "Mobile + Web + Backend", platforms: ["mobile", "web"] }
21
21
  };
22
- function getCompatibleOptions(preset) {
22
+ function getCompatibleOptions(preset, webFramework) {
23
23
  const info = PRESET_INFO[preset];
24
24
  const hasMobile = info.platforms.includes("mobile");
25
25
  const hasWeb = info.platforms.includes("web");
@@ -27,13 +27,20 @@ function getCompatibleOptions(preset) {
27
27
  return {
28
28
  styling: hasMobile ? ["nativewind", "none"] : ["tailwind", "none"],
29
29
  components: hasMobile ? ["nativewind-ui", "none"] : ["shadcn", "none"],
30
- backend: hasBackend ? ["supabase", "firebase"] : ["none"],
31
- state: ["zustand", "redux"]
30
+ backend: hasBackend ? ["nestjs-postgres", "nestjs-mongodb", "supabase", "firebase"] : ["none"],
31
+ state: ["zustand", "redux"],
32
+ animation: hasMobile ? ["reanimated", "moti", "none"] : ["framer", "none"]
32
33
  };
33
34
  }
34
35
  function isMonorepoPreset(preset) {
35
36
  return preset === "fullstack-mobile" || preset === "fullstack-web" || preset === "monorepo";
36
37
  }
38
+ function hasWebPlatform(preset) {
39
+ return PRESET_INFO[preset].platforms.includes("web");
40
+ }
41
+ function hasMobilePlatform(preset) {
42
+ return PRESET_INFO[preset].platforms.includes("mobile");
43
+ }
37
44
 
38
45
  // src/utils/helpers.ts
39
46
  import fs from "fs-extra";
@@ -68,16 +75,45 @@ async function runPrompts() {
68
75
  }))
69
76
  });
70
77
  if (p.isCancel(preset)) return null;
71
- const options = getCompatibleOptions(preset);
78
+ const showWebFramework = hasWebPlatform(preset);
79
+ const showMobileOptions = hasMobilePlatform(preset);
80
+ const hasBackend = preset.includes("fullstack") || preset === "monorepo";
81
+ let webFramework = "nextjs";
82
+ if (showWebFramework) {
83
+ const webChoice = await p.select({
84
+ message: "Web framework:",
85
+ options: [
86
+ { value: "nextjs", label: "Next.js", hint: "Full-featured React framework" },
87
+ { value: "vite", label: "Vite", hint: "Fast, lightweight SPA" }
88
+ ]
89
+ });
90
+ if (p.isCancel(webChoice)) return null;
91
+ webFramework = webChoice;
92
+ }
93
+ let backend = "none";
94
+ if (hasBackend) {
95
+ const backendChoice = await p.select({
96
+ message: "Backend:",
97
+ options: [
98
+ { value: "nestjs-postgres", label: "NestJS + PostgreSQL", hint: "Prisma ORM" },
99
+ { value: "nestjs-mongodb", label: "NestJS + MongoDB", hint: "Mongoose ODM" },
100
+ { value: "supabase", label: "Supabase", hint: "BaaS with Postgres" },
101
+ { value: "firebase", label: "Firebase", hint: "Google BaaS" }
102
+ ]
103
+ });
104
+ if (p.isCancel(backendChoice)) return null;
105
+ backend = backendChoice;
106
+ }
107
+ const options = getCompatibleOptions(preset, webFramework);
72
108
  const customize = await p.confirm({
73
- message: "Customize options?",
109
+ message: "Customize styling, state & animations?",
74
110
  initialValue: false
75
111
  });
76
112
  if (p.isCancel(customize)) return null;
77
113
  let styling = options.styling[0];
78
114
  let components = options.components[0];
79
115
  let state = "zustand";
80
- let backend = options.backend[0];
116
+ let animation = "none";
81
117
  if (customize) {
82
118
  const stylingChoice = await p.select({
83
119
  message: "Styling:",
@@ -88,8 +124,8 @@ async function runPrompts() {
88
124
  const stateChoice = await p.select({
89
125
  message: "State management:",
90
126
  options: [
91
- { value: "zustand", label: "Zustand" },
92
- { value: "redux", label: "Redux Toolkit" }
127
+ { value: "zustand", label: "Zustand", hint: "Simple, lightweight" },
128
+ { value: "redux", label: "Redux Toolkit", hint: "Full-featured" }
93
129
  ]
94
130
  });
95
131
  if (p.isCancel(stateChoice)) return null;
@@ -100,17 +136,25 @@ async function runPrompts() {
100
136
  });
101
137
  if (p.isCancel(componentsChoice)) return null;
102
138
  components = componentsChoice;
103
- if (options.backend[0] !== "none") {
104
- const backendChoice = await p.select({
105
- message: "Backend:",
106
- options: [
107
- { value: "supabase", label: "Supabase" },
108
- { value: "firebase", label: "Firebase" }
109
- ]
110
- });
111
- if (p.isCancel(backendChoice)) return null;
112
- backend = backendChoice;
113
- }
139
+ const animationChoice = await p.select({
140
+ message: "Animations:",
141
+ options: options.animation.map((v) => ({
142
+ value: v,
143
+ label: v === "none" ? "None" : v === "reanimated" ? "React Native Reanimated" : v === "framer" ? "Framer Motion" : "Moti",
144
+ hint: v === "moti" ? "Cross-platform, uses Reanimated" : void 0
145
+ }))
146
+ });
147
+ if (p.isCancel(animationChoice)) return null;
148
+ animation = animationChoice;
149
+ }
150
+ let eas = false;
151
+ if (showMobileOptions) {
152
+ const easChoice = await p.confirm({
153
+ message: "Setup EAS Build for app store deployment?",
154
+ initialValue: true
155
+ });
156
+ if (p.isCancel(easChoice)) return null;
157
+ eas = easChoice;
114
158
  }
115
159
  const packageManager = await p.select({
116
160
  message: "Package manager:",
@@ -130,10 +174,13 @@ async function runPrompts() {
130
174
  return {
131
175
  name,
132
176
  preset,
177
+ webFramework,
133
178
  backend,
134
179
  styling,
135
180
  state,
136
181
  components,
182
+ animation,
183
+ eas,
137
184
  packageManager,
138
185
  gitInit
139
186
  };
@@ -148,38 +195,62 @@ async function scaffold(config) {
148
195
  const templatesDir = getTemplatesDir();
149
196
  const isMonorepo = isMonorepoPreset(config.preset);
150
197
  const platforms = PRESET_INFO[config.preset].platforms;
151
- await fs2.ensureDir(targetDir);
152
198
  if (isMonorepo) {
199
+ await fs2.ensureDir(targetDir);
153
200
  await setupMonorepo(targetDir, config);
154
201
  if (platforms.includes("mobile")) {
155
- await copyTemplate(path2.join(templatesDir, "mobile"), path2.join(targetDir, "apps/mobile"), config);
202
+ await scaffoldMobile(path2.join(targetDir, "apps"), "mobile", config);
203
+ if (config.eas) await addEasConfig(path2.join(targetDir, "apps/mobile"), config);
156
204
  }
157
205
  if (platforms.includes("web")) {
158
- await copyTemplate(path2.join(templatesDir, "web"), path2.join(targetDir, "apps/web"), config);
206
+ await scaffoldWeb(path2.join(targetDir, "apps"), "web", config);
159
207
  }
160
208
  if (config.backend !== "none") {
161
- await fs2.ensureDir(path2.join(targetDir, "packages/backend"));
162
- await copyTemplate(path2.join(templatesDir, "backend", config.backend), path2.join(targetDir, "packages/backend"), config);
209
+ await scaffoldBackend(path2.join(targetDir, "packages"), "backend", config);
163
210
  }
164
211
  } else {
165
- const platformTemplate = platforms[0] === "mobile" ? "mobile" : "web";
166
- await copyTemplate(path2.join(templatesDir, platformTemplate), targetDir, config);
212
+ if (platforms[0] === "mobile") {
213
+ await scaffoldMobile(process.cwd(), config.name, config);
214
+ if (config.eas) await addEasConfig(targetDir, config);
215
+ } else {
216
+ await scaffoldWeb(process.cwd(), config.name, config);
217
+ }
167
218
  }
168
219
  const appDir = isMonorepo ? path2.join(targetDir, platforms[0] === "mobile" ? "apps/mobile" : "apps/web") : targetDir;
169
- await addStyling(appDir, config);
170
- await addStateManagement(appDir, config);
171
- await addComponents(appDir, config);
172
- await addExamples(appDir, platforms[0], config);
220
+ const platform = platforms[0];
221
+ await addDependencies(appDir, platform, config);
222
+ await copyTemplate(path2.join(templatesDir, "state", config.state), path2.join(appDir, "lib/store"), config);
223
+ if (config.components !== "none") {
224
+ await copyTemplate(path2.join(templatesDir, "components", config.components), path2.join(appDir, "components/ui"), config);
225
+ }
226
+ if (config.backend !== "none") {
227
+ await copyTemplate(path2.join(templatesDir, "backend", config.backend), path2.join(appDir, "lib/backend"), config);
228
+ await copyTemplate(path2.join(templatesDir, "lib"), path2.join(appDir, "lib"), config);
229
+ }
173
230
  await copyTemplate(path2.join(templatesDir, "base"), targetDir, config);
174
231
  return targetDir;
175
232
  }
233
+ async function scaffoldMobile(cwd, name, config) {
234
+ const templatesDir = getTemplatesDir();
235
+ await copyTemplate(path2.join(templatesDir, "mobile"), path2.join(cwd, name), config);
236
+ }
237
+ async function scaffoldWeb(cwd, name, config) {
238
+ const templatesDir = getTemplatesDir();
239
+ const templateName = config.webFramework === "vite" ? "vite" : "web";
240
+ await copyTemplate(path2.join(templatesDir, templateName), path2.join(cwd, name), config);
241
+ }
242
+ async function scaffoldBackend(cwd, name, config) {
243
+ const templatesDir = getTemplatesDir();
244
+ await copyTemplate(path2.join(templatesDir, "backend", config.backend), path2.join(cwd, name), config);
245
+ }
176
246
  async function setupMonorepo(targetDir, config) {
177
247
  const pkg = {
178
248
  name: config.name,
179
249
  private: true,
180
250
  scripts: {
181
- dev: "echo 'Run dev in apps/*'",
182
- build: "echo 'Run build in apps/*'"
251
+ "dev:mobile": "cd apps/mobile && npm run start",
252
+ "dev:web": "cd apps/web && npm run dev",
253
+ "dev:backend": "cd packages/backend && npm run start:dev"
183
254
  }
184
255
  };
185
256
  if (config.packageManager === "pnpm") {
@@ -191,52 +262,80 @@ async function setupMonorepo(targetDir, config) {
191
262
  await fs2.ensureDir(path2.join(targetDir, "apps"));
192
263
  await fs2.ensureDir(path2.join(targetDir, "packages"));
193
264
  }
194
- async function addStyling(appDir, config) {
195
- if (config.styling === "none") return;
265
+ async function addDependencies(appDir, platform, config) {
196
266
  const pkgPath = path2.join(appDir, "package.json");
197
267
  if (!await fs2.pathExists(pkgPath)) return;
198
268
  const pkg = await fs2.readJson(pkgPath);
199
269
  pkg.dependencies = pkg.dependencies || {};
200
270
  pkg.devDependencies = pkg.devDependencies || {};
201
- if (config.styling === "nativewind") {
202
- pkg.dependencies["nativewind"] = "^4.0.0";
203
- pkg.devDependencies["tailwindcss"] = "^3.4.0";
204
- const templatesDir = getTemplatesDir();
205
- await copyTemplate(path2.join(templatesDir, "styling", "nativewind"), appDir, config);
206
- }
207
- await fs2.writeJson(pkgPath, pkg, { spaces: 2 });
208
- }
209
- async function addStateManagement(appDir, config) {
210
- const pkgPath = path2.join(appDir, "package.json");
211
- if (!await fs2.pathExists(pkgPath)) return;
212
- const pkg = await fs2.readJson(pkgPath);
213
- pkg.dependencies = pkg.dependencies || {};
214
271
  if (config.state === "zustand") {
215
272
  pkg.dependencies["zustand"] = "^4.5.0";
216
273
  } else {
217
274
  pkg.dependencies["@reduxjs/toolkit"] = "^2.0.0";
218
275
  pkg.dependencies["react-redux"] = "^9.0.0";
219
276
  }
277
+ if (config.animation === "reanimated") {
278
+ pkg.dependencies["react-native-reanimated"] = "^3.10.0";
279
+ pkg.dependencies["react-native-gesture-handler"] = "^2.16.0";
280
+ } else if (config.animation === "moti") {
281
+ pkg.dependencies["moti"] = "^0.29.0";
282
+ pkg.dependencies["react-native-reanimated"] = "^3.10.0";
283
+ } else if (config.animation === "framer") {
284
+ pkg.dependencies["framer-motion"] = "^11.0.0";
285
+ }
286
+ if (platform === "mobile") {
287
+ pkg.dependencies["@lazorkit/wallet-mobile-adapter"] = "latest";
288
+ pkg.dependencies["@solana/web3.js"] = "^1.95.0";
289
+ pkg.dependencies["react-native-get-random-values"] = "~1.11.0";
290
+ pkg.dependencies["react-native-url-polyfill"] = "^2.0.0";
291
+ pkg.dependencies["buffer"] = "^6.0.3";
292
+ pkg.dependencies["expo-crypto"] = "~15.0.0";
293
+ pkg.dependencies["expo-linking"] = "~8.0.11";
294
+ pkg.dependencies["expo-web-browser"] = "~15.0.10";
295
+ pkg.dependencies["expo-clipboard"] = "~7.0.0";
296
+ if (config.styling === "nativewind") {
297
+ pkg.dependencies["nativewind"] = "^4.0.0";
298
+ pkg.devDependencies["tailwindcss"] = "^3.4.0";
299
+ }
300
+ } else {
301
+ pkg.dependencies["@lazorkit/wallet"] = "latest";
302
+ pkg.dependencies["@solana/web3.js"] = "^1.95.0";
303
+ pkg.dependencies["@coral-xyz/anchor"] = "^0.30.0";
304
+ pkg.dependencies["buffer"] = "^6.0.3";
305
+ if (config.webFramework === "vite") {
306
+ pkg.devDependencies["vite-plugin-node-polyfills"] = "^0.22.0";
307
+ }
308
+ if (config.styling === "tailwind" && config.webFramework === "vite") {
309
+ pkg.devDependencies["tailwindcss"] = "^3.4.0";
310
+ pkg.devDependencies["postcss"] = "^8.4.0";
311
+ pkg.devDependencies["autoprefixer"] = "^10.4.0";
312
+ }
313
+ }
314
+ if (config.backend === "supabase") {
315
+ pkg.dependencies["@supabase/supabase-js"] = "^2.39.0";
316
+ } else if (config.backend === "firebase") {
317
+ pkg.dependencies["firebase"] = "^10.7.0";
318
+ }
220
319
  await fs2.writeJson(pkgPath, pkg, { spaces: 2 });
221
- const templatesDir = getTemplatesDir();
222
- await copyTemplate(path2.join(templatesDir, "state", config.state), path2.join(appDir, "lib/store"), config);
223
- }
224
- async function addComponents(appDir, config) {
225
- if (config.components === "none") return;
226
- const templatesDir = getTemplatesDir();
227
- await copyTemplate(path2.join(templatesDir, "components", config.components), path2.join(appDir, "components/ui"), config);
228
320
  }
229
- async function addExamples(appDir, platform, config) {
230
- const templatesDir = getTemplatesDir();
231
- const examplesDir = path2.join(templatesDir, "examples", platform);
232
- const targetDir = platform === "mobile" ? path2.join(appDir, "app") : path2.join(appDir, "app/examples");
233
- await copyTemplate(examplesDir, targetDir, config);
321
+ async function addEasConfig(appDir, config) {
322
+ const easConfig = {
323
+ cli: { version: ">= 7.0.0" },
324
+ build: {
325
+ development: { developmentClient: true, distribution: "internal" },
326
+ preview: { distribution: "internal" },
327
+ production: {}
328
+ },
329
+ submit: { production: {} }
330
+ };
331
+ await fs2.writeJson(path2.join(appDir, "eas.json"), easConfig, { spaces: 2 });
234
332
  }
235
- async function copyTemplate(src, dest, config) {
333
+ async function copyTemplate(src, dest, config, exclude = []) {
236
334
  if (!await fs2.pathExists(src)) return;
237
335
  await fs2.ensureDir(dest);
238
336
  const files = await fs2.readdir(src, { withFileTypes: true });
239
337
  for (const file of files) {
338
+ if (exclude.includes(file.name)) continue;
240
339
  const srcPath = path2.join(src, file.name);
241
340
  const destName = file.name.replace(/\.ejs$/, "");
242
341
  const destPath = path2.join(dest, destName);
@@ -245,7 +344,9 @@ async function copyTemplate(src, dest, config) {
245
344
  } else if (file.name.endsWith(".ejs")) {
246
345
  const content = await fs2.readFile(srcPath, "utf-8");
247
346
  const rendered = ejs.render(content, config);
248
- await fs2.writeFile(destPath, rendered);
347
+ if (rendered.trim()) {
348
+ await fs2.writeFile(destPath, rendered);
349
+ }
249
350
  } else {
250
351
  await fs2.copy(srcPath, destPath);
251
352
  }
@@ -285,13 +386,17 @@ program.name("create-lightning-scaffold").description("Scaffold projects with La
285
386
  const preset = opts.preset || "mobile";
286
387
  const isMobile = preset === "mobile" || preset === "fullstack-mobile";
287
388
  const hasBackend = preset.includes("fullstack") || preset === "monorepo";
389
+ const hasWeb = preset === "web" || preset === "fullstack-web" || preset === "monorepo";
288
390
  config = {
289
391
  name,
290
392
  preset,
291
- backend: hasBackend ? "supabase" : "none",
393
+ webFramework: "nextjs",
394
+ backend: hasBackend ? "nestjs-postgres" : "none",
292
395
  styling: isMobile ? "nativewind" : "tailwind",
293
396
  state: "zustand",
294
397
  components: isMobile ? "nativewind-ui" : "shadcn",
398
+ animation: isMobile ? "reanimated" : "framer",
399
+ eas: isMobile,
295
400
  packageManager: "npm",
296
401
  gitInit: true
297
402
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "create-lightning-scaffold",
3
- "version": "1.0.0",
3
+ "version": "1.0.2",
4
4
  "description": "CLI to scaffold projects with LazorKit SDK integration",
5
5
  "type": "module",
6
6
  "bin": {
@@ -1,6 +1,5 @@
1
1
  import { initializeApp } from "firebase/app";
2
- import { getAuth } from "firebase/auth";
3
- import { getFirestore, doc, setDoc, collection, query, where, getDocs, orderBy } from "firebase/firestore";
2
+ import { getFirestore, doc, setDoc, getDoc, collection, query, where, getDocs, orderBy, limit, addDoc, serverTimestamp } from "firebase/firestore";
4
3
 
5
4
  const firebaseConfig = {
6
5
  apiKey: process.env.EXPO_PUBLIC_FIREBASE_API_KEY || process.env.NEXT_PUBLIC_FIREBASE_API_KEY,
@@ -9,17 +8,95 @@ const firebaseConfig = {
9
8
  };
10
9
 
11
10
  const app = initializeApp(firebaseConfig);
12
- export const auth = getAuth(app);
13
11
  export const db = getFirestore(app);
14
12
 
15
- // Example: Save wallet to user profile
16
- export async function saveWallet(userId: string, walletAddress: string) {
17
- return setDoc(doc(db, "wallets", userId), { address: walletAddress, updatedAt: new Date() });
13
+ // Types
14
+ export interface User {
15
+ walletAddress: string;
16
+ createdAt: Date;
18
17
  }
19
18
 
20
- // Example: Get user's transactions
21
- export async function getTransactions(walletAddress: string) {
22
- const q = query(collection(db, "transactions"), where("walletAddress", "==", walletAddress), orderBy("createdAt", "desc"));
19
+ export interface SwapRecord {
20
+ walletAddress: string;
21
+ fromToken: string;
22
+ toToken: string;
23
+ fromAmount: string;
24
+ toAmount: string;
25
+ signature: string;
26
+ createdAt: Date;
27
+ }
28
+
29
+ // User Management
30
+ export async function createOrGetUser(walletAddress: string) {
31
+ const userRef = doc(db, "users", walletAddress);
32
+ const userSnap = await getDoc(userRef);
33
+
34
+ if (userSnap.exists()) return { id: userSnap.id, ...userSnap.data() };
35
+
36
+ await setDoc(userRef, { walletAddress, createdAt: serverTimestamp() });
37
+ return { id: walletAddress, walletAddress, createdAt: new Date() };
38
+ }
39
+
40
+ // Swap History
41
+ export async function saveSwap(swap: Omit<SwapRecord, "createdAt">) {
42
+ const docRef = await addDoc(collection(db, "swaps"), {
43
+ ...swap,
44
+ createdAt: serverTimestamp(),
45
+ });
46
+ return { id: docRef.id, ...swap };
47
+ }
48
+
49
+ export async function getSwapHistory(walletAddress: string, maxResults = 20) {
50
+ const q = query(
51
+ collection(db, "swaps"),
52
+ where("walletAddress", "==", walletAddress),
53
+ orderBy("createdAt", "desc"),
54
+ limit(maxResults)
55
+ );
23
56
  const snapshot = await getDocs(q);
24
57
  return snapshot.docs.map((d) => ({ id: d.id, ...d.data() }));
25
58
  }
59
+
60
+ // Message Signatures (for verification records)
61
+ export async function saveSignature(walletAddress: string, message: string, signature: string) {
62
+ const docRef = await addDoc(collection(db, "signatures"), {
63
+ walletAddress,
64
+ message,
65
+ signature,
66
+ createdAt: serverTimestamp(),
67
+ });
68
+ return { id: docRef.id };
69
+ }
70
+
71
+ /*
72
+ Firestore Security Rules - Add to firestore.rules:
73
+
74
+ rules_version = '2';
75
+ service cloud.firestore {
76
+ match /databases/{database}/documents {
77
+ match /users/{walletAddress} {
78
+ allow read, write: if true; // Adjust based on your auth
79
+ }
80
+ match /swaps/{swapId} {
81
+ allow read, write: if true;
82
+ }
83
+ match /signatures/{sigId} {
84
+ allow read, write: if true;
85
+ }
86
+ }
87
+ }
88
+
89
+ Firestore Indexes - Add to firestore.indexes.json:
90
+ {
91
+ "indexes": [
92
+ {
93
+ "collectionGroup": "swaps",
94
+ "queryScope": "COLLECTION",
95
+ "fields": [
96
+ { "fieldPath": "walletAddress", "order": "ASCENDING" },
97
+ { "fieldPath": "createdAt", "order": "DESCENDING" }
98
+ ]
99
+ }
100
+ ]
101
+ }
102
+ */