@coinbase/create-cdp-app 0.0.26 → 0.0.28
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.js +39 -10
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
- package/template-react-native/App.tsx +359 -0
- package/template-react-native/EOATransaction.tsx +360 -0
- package/template-react-native/README.md +57 -0
- package/template-react-native/SmartAccountTransaction.tsx +304 -0
- package/template-react-native/Transaction.tsx +1 -0
- package/template-react-native/_gitignore +37 -0
- package/template-react-native/android/app/build.gradle +177 -0
- package/template-react-native/android/app/debug.keystore +0 -0
- package/template-react-native/android/app/proguard-rules.pro +14 -0
- package/template-react-native/android/app/src/debug/AndroidManifest.xml +7 -0
- package/template-react-native/android/app/src/main/AndroidManifest.xml +25 -0
- package/template-react-native/android/app/src/main/java/com/anonymous/reactnativeexpo/MainActivity.kt +61 -0
- package/template-react-native/android/app/src/main/java/com/anonymous/reactnativeexpo/MainApplication.kt +57 -0
- package/template-react-native/android/app/src/main/res/drawable/ic_launcher_background.xml +6 -0
- package/template-react-native/android/app/src/main/res/drawable/rn_edit_text_material.xml +37 -0
- package/template-react-native/android/app/src/main/res/drawable-hdpi/splashscreen_logo.png +0 -0
- package/template-react-native/android/app/src/main/res/drawable-mdpi/splashscreen_logo.png +0 -0
- package/template-react-native/android/app/src/main/res/drawable-xhdpi/splashscreen_logo.png +0 -0
- package/template-react-native/android/app/src/main/res/drawable-xxhdpi/splashscreen_logo.png +0 -0
- package/template-react-native/android/app/src/main/res/drawable-xxxhdpi/splashscreen_logo.png +0 -0
- package/template-react-native/android/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml +5 -0
- package/template-react-native/android/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml +5 -0
- package/template-react-native/android/app/src/main/res/mipmap-hdpi/ic_launcher.webp +0 -0
- package/template-react-native/android/app/src/main/res/mipmap-hdpi/ic_launcher_foreground.webp +0 -0
- package/template-react-native/android/app/src/main/res/mipmap-hdpi/ic_launcher_round.webp +0 -0
- package/template-react-native/android/app/src/main/res/mipmap-mdpi/ic_launcher.webp +0 -0
- package/template-react-native/android/app/src/main/res/mipmap-mdpi/ic_launcher_foreground.webp +0 -0
- package/template-react-native/android/app/src/main/res/mipmap-mdpi/ic_launcher_round.webp +0 -0
- package/template-react-native/android/app/src/main/res/mipmap-xhdpi/ic_launcher.webp +0 -0
- package/template-react-native/android/app/src/main/res/mipmap-xhdpi/ic_launcher_foreground.webp +0 -0
- package/template-react-native/android/app/src/main/res/mipmap-xhdpi/ic_launcher_round.webp +0 -0
- package/template-react-native/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.webp +0 -0
- package/template-react-native/android/app/src/main/res/mipmap-xxhdpi/ic_launcher_foreground.webp +0 -0
- package/template-react-native/android/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp +0 -0
- package/template-react-native/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.webp +0 -0
- package/template-react-native/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_foreground.webp +0 -0
- package/template-react-native/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp +0 -0
- package/template-react-native/android/app/src/main/res/values/colors.xml +6 -0
- package/template-react-native/android/app/src/main/res/values/strings.xml +5 -0
- package/template-react-native/android/app/src/main/res/values/styles.xml +10 -0
- package/template-react-native/android/app/src/main/res/values-night/colors.xml +1 -0
- package/template-react-native/android/build.gradle +37 -0
- package/template-react-native/android/gradle/wrapper/gradle-wrapper.jar +0 -0
- package/template-react-native/android/gradle/wrapper/gradle-wrapper.properties +7 -0
- package/template-react-native/android/gradle.properties +59 -0
- package/template-react-native/android/gradlew +251 -0
- package/template-react-native/android/gradlew.bat +94 -0
- package/template-react-native/android/settings.gradle +39 -0
- package/template-react-native/app.json +34 -0
- package/template-react-native/assets/adaptive-icon.png +0 -0
- package/template-react-native/assets/favicon.png +0 -0
- package/template-react-native/assets/icon.png +0 -0
- package/template-react-native/assets/splash-icon.png +0 -0
- package/template-react-native/env.example +5 -0
- package/template-react-native/index.ts +20 -0
- package/template-react-native/ios/.xcode.env +11 -0
- package/template-react-native/ios/Podfile +64 -0
- package/template-react-native/ios/Podfile.lock +2125 -0
- package/template-react-native/ios/Podfile.properties.json +5 -0
- package/template-react-native/ios/reactnativeexpo/AppDelegate.swift +70 -0
- package/template-react-native/ios/reactnativeexpo/Images.xcassets/AppIcon.appiconset/App-Icon-1024x1024@1x.png +0 -0
- package/template-react-native/ios/reactnativeexpo/Images.xcassets/AppIcon.appiconset/Contents.json +14 -0
- package/template-react-native/ios/reactnativeexpo/Images.xcassets/Contents.json +6 -0
- package/template-react-native/ios/reactnativeexpo/Images.xcassets/SplashScreenBackground.colorset/Contents.json +20 -0
- package/template-react-native/ios/reactnativeexpo/Images.xcassets/SplashScreenLogo.imageset/Contents.json +23 -0
- package/template-react-native/ios/reactnativeexpo/Images.xcassets/SplashScreenLogo.imageset/image.png +0 -0
- package/template-react-native/ios/reactnativeexpo/Images.xcassets/SplashScreenLogo.imageset/image@2x.png +0 -0
- package/template-react-native/ios/reactnativeexpo/Images.xcassets/SplashScreenLogo.imageset/image@3x.png +0 -0
- package/template-react-native/ios/reactnativeexpo/Info.plist +74 -0
- package/template-react-native/ios/reactnativeexpo/PrivacyInfo.xcprivacy +48 -0
- package/template-react-native/ios/reactnativeexpo/SplashScreen.storyboard +44 -0
- package/template-react-native/ios/reactnativeexpo/Supporting/Expo.plist +12 -0
- package/template-react-native/ios/reactnativeexpo/reactnativeexpo-Bridging-Header.h +3 -0
- package/template-react-native/ios/reactnativeexpo/reactnativeexpo.entitlements +5 -0
- package/template-react-native/ios/reactnativeexpo.xcodeproj/project.pbxproj +545 -0
- package/template-react-native/ios/reactnativeexpo.xcodeproj/xcshareddata/xcschemes/reactnativeexpo.xcscheme +88 -0
- package/template-react-native/ios/reactnativeexpo.xcworkspace/contents.xcworkspacedata +10 -0
- package/template-react-native/package.json +30 -0
- package/template-react-native/tsconfig.json +6 -0
package/dist/index.js
CHANGED
|
@@ -32,7 +32,14 @@ function customizeEnv({
|
|
|
32
32
|
let envContent = exampleEnv.replace(/(.*PROJECT_ID=).*(\r?\n|$)/, `$1${projectId}
|
|
33
33
|
`);
|
|
34
34
|
const accountType = useSmartAccounts ? "evm-smart" : "evm-eoa";
|
|
35
|
-
|
|
35
|
+
let prefix;
|
|
36
|
+
if (templateDir.includes("nextjs")) {
|
|
37
|
+
prefix = "NEXT_PUBLIC_";
|
|
38
|
+
} else if (templateDir.includes("react-native")) {
|
|
39
|
+
prefix = "EXPO_PUBLIC_";
|
|
40
|
+
} else {
|
|
41
|
+
prefix = "VITE_";
|
|
42
|
+
}
|
|
36
43
|
envContent = envContent.replace(
|
|
37
44
|
new RegExp(`(${prefix}CDP_CREATE_ACCOUNT_TYPE=).*(\r?
|
|
38
45
|
|$)`),
|
|
@@ -137,6 +144,11 @@ const TEMPLATES = [
|
|
|
137
144
|
name: "nextjs",
|
|
138
145
|
display: "Next.js Full Stack App",
|
|
139
146
|
color: green
|
|
147
|
+
},
|
|
148
|
+
{
|
|
149
|
+
name: "react-native",
|
|
150
|
+
display: "React Native with Expo",
|
|
151
|
+
color: green
|
|
140
152
|
}
|
|
141
153
|
];
|
|
142
154
|
const defaultTargetDir = "cdp-app";
|
|
@@ -169,7 +181,7 @@ Scaffolding app in ${targetDirectory}...`);
|
|
|
169
181
|
apiKeyId,
|
|
170
182
|
apiKeySecret
|
|
171
183
|
});
|
|
172
|
-
printNextSteps(root);
|
|
184
|
+
printNextSteps(root, template);
|
|
173
185
|
}
|
|
174
186
|
async function getAppDetails() {
|
|
175
187
|
let targetDir = process.argv[2];
|
|
@@ -251,7 +263,7 @@ async function getAppDetails() {
|
|
|
251
263
|
}
|
|
252
264
|
},
|
|
253
265
|
{
|
|
254
|
-
type: "confirm",
|
|
266
|
+
type: (_, { template }) => template === "react-native" ? null : "confirm",
|
|
255
267
|
name: "corsConfirmation",
|
|
256
268
|
message: reset(
|
|
257
269
|
"Confirm you have whitelisted 'http://localhost:3000' at https://portal.cdp.coinbase.com/products/embedded-wallets/domains:"
|
|
@@ -296,15 +308,20 @@ async function getAppDetails() {
|
|
|
296
308
|
process.exit(0);
|
|
297
309
|
}
|
|
298
310
|
}
|
|
299
|
-
function printNextSteps(appRoot) {
|
|
311
|
+
function printNextSteps(appRoot, template) {
|
|
300
312
|
const packageManager = detectPackageManager();
|
|
301
313
|
console.log(green("\nDone. Now run your app:\n"));
|
|
302
314
|
if (appRoot !== process.cwd()) {
|
|
303
315
|
console.log(`cd ${path.relative(process.cwd(), appRoot)}`);
|
|
304
316
|
}
|
|
305
|
-
const devCommand = packageManager === "npm" ? "npm run dev" : `${packageManager} dev`;
|
|
306
317
|
console.log(`${packageManager} install`);
|
|
307
|
-
|
|
318
|
+
if (template === "react-native") {
|
|
319
|
+
const startCommand = packageManager === "npm" ? "npm run ios # or npm run android" : `${packageManager} run ios # or ${packageManager} run android`;
|
|
320
|
+
console.log(startCommand);
|
|
321
|
+
} else {
|
|
322
|
+
const devCommand = packageManager === "npm" ? "npm run dev" : `${packageManager} dev`;
|
|
323
|
+
console.log(devCommand);
|
|
324
|
+
}
|
|
308
325
|
}
|
|
309
326
|
function copyTemplateFiles({
|
|
310
327
|
templateDir,
|
|
@@ -356,7 +373,8 @@ function copyTemplateFiles({
|
|
|
356
373
|
writeFileToTarget(file);
|
|
357
374
|
}
|
|
358
375
|
}
|
|
359
|
-
|
|
376
|
+
const isReactNative = templateDir.includes("react-native");
|
|
377
|
+
if (useSmartAccounts && !isReactNative) {
|
|
360
378
|
const configFileName = isNextjs ? "src/components/Providers.tsx" : "src/config.ts";
|
|
361
379
|
const customizedConfig = customizeConfig(templateDir, useSmartAccounts, isNextjs);
|
|
362
380
|
if (customizedConfig) {
|
|
@@ -364,9 +382,20 @@ function copyTemplateFiles({
|
|
|
364
382
|
writeFileToTarget(configFileName, customizedConfig);
|
|
365
383
|
}
|
|
366
384
|
}
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
385
|
+
if (isReactNative) {
|
|
386
|
+
const transactionContent = generateTransactionComponent(useSmartAccounts);
|
|
387
|
+
writeFileToTarget("Transaction.tsx", transactionContent);
|
|
388
|
+
} else {
|
|
389
|
+
const transactionFileName = isNextjs ? "src/components/Transaction.tsx" : "src/Transaction.tsx";
|
|
390
|
+
const transactionContent = generateTransactionComponent(useSmartAccounts);
|
|
391
|
+
writeFileToTarget(transactionFileName, transactionContent);
|
|
392
|
+
}
|
|
393
|
+
if (isReactNative) {
|
|
394
|
+
const gradlewPath = path.join(root, "android", "gradlew");
|
|
395
|
+
if (fs.existsSync(gradlewPath)) {
|
|
396
|
+
fs.chmodSync(gradlewPath, 493);
|
|
397
|
+
}
|
|
398
|
+
}
|
|
370
399
|
}
|
|
371
400
|
function generateTransactionComponent(useSmartAccounts) {
|
|
372
401
|
if (useSmartAccounts) {
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sources":["../src/utils.ts","../src/index.ts"],"sourcesContent":["import fs from \"node:fs\";\nimport path from \"node:path\";\n\n/**\n * Prepare the app directory\n *\n * @param targetDir - The target directory for the app\n * @param shouldOverwrite - Whether to overwrite the existing directory\n * @returns The path to the prepared app directory\n */\nexport function prepareAppDirectory(targetDir: string, shouldOverwrite: boolean): string {\n const root = path.join(process.cwd(), targetDir);\n\n if (shouldOverwrite) {\n emptyDir(root);\n } else if (!fs.existsSync(root)) {\n fs.mkdirSync(root, { recursive: true });\n }\n\n return root;\n}\n\n/**\n * Customize package.json for the new app\n *\n * @param templateDir - The directory containing the template files\n * @param appName - The name of the app\n * @param includeSdk - Whether to include the CDP SDK in the dependencies\n * @returns The customized package.json content\n */\nexport function customizePackageJson(\n templateDir: string,\n appName: string,\n includeSdk?: boolean,\n): string {\n const packageJsonPath = path.join(templateDir, \"package.json\");\n const packageJson = JSON.parse(fs.readFileSync(packageJsonPath, \"utf-8\"));\n packageJson.name = appName;\n if (includeSdk) {\n packageJson.dependencies[\"@coinbase/cdp-sdk\"] = \"latest\";\n }\n return JSON.stringify(packageJson, null, 2) + \"\\n\";\n}\n\n/**\n * Set up the .env file for the new app\n *\n * @param params - The parameters for the function\n * @param params.templateDir - The directory containing the template files\n * @param params.projectId - The CDP Project ID\n * @param params.useSmartAccounts - Whether to enable smart accounts\n * @param params.apiKeyId - The API Key ID\n * @param params.apiKeySecret - The API Key Secret\n * @returns The customized .env content\n */\nexport function customizeEnv({\n templateDir,\n projectId,\n useSmartAccounts,\n apiKeyId,\n apiKeySecret,\n}: {\n templateDir: string;\n projectId: string;\n useSmartAccounts?: boolean;\n apiKeyId?: string;\n apiKeySecret?: string;\n}): string {\n const exampleEnvPath = path.join(templateDir, \"env.example\");\n const exampleEnv = fs.readFileSync(exampleEnvPath, \"utf-8\");\n\n let envContent = exampleEnv.replace(/(.*PROJECT_ID=).*(\\r?\\n|$)/, `$1${projectId}\\n`);\n\n // Set the account type based on user choice\n const accountType = useSmartAccounts ? \"evm-smart\" : \"evm-eoa\";\n const prefix = templateDir.includes(\"nextjs\") ? \"NEXT_PUBLIC_\" : \"VITE_\";\n envContent = envContent.replace(\n new RegExp(`(${prefix}CDP_CREATE_ACCOUNT_TYPE=).*(\\r?\\n|$)`),\n `$1${accountType}\\n`,\n );\n\n // Replace CDP API credentials if provided\n if (apiKeyId && apiKeySecret) {\n // Replace the commented API Key ID\n envContent = envContent.replace(/# CDP_API_KEY_ID=.*/, `CDP_API_KEY_ID=${apiKeyId}`);\n // Replace the commented API Key Secret\n envContent = envContent.replace(\n /# CDP_API_KEY_SECRET=.*/,\n `CDP_API_KEY_SECRET=${apiKeySecret}`,\n );\n }\n\n return envContent;\n}\n\n/**\n * Customize configuration files for smart accounts\n *\n * @param templateDir - The directory containing the template files\n * @param useSmartAccounts - Whether to enable smart accounts\n * @param isNextjs - Whether this is a Next.js template\n * @returns The customized config content\n */\nexport function customizeConfig(\n templateDir: string,\n useSmartAccounts: boolean,\n isNextjs: boolean,\n): string | null {\n if (!useSmartAccounts) return null;\n\n const configFileName = isNextjs ? \"src/components/Providers.tsx\" : \"src/config.ts\";\n const configPath = path.join(templateDir, configFileName);\n\n if (!fs.existsSync(configPath)) return null;\n\n let configContent = fs.readFileSync(configPath, \"utf-8\");\n\n if (isNextjs) {\n // For Next.js Providers.tsx\n configContent = configContent.replace(\n /const CDP_CONFIG: Config = \\{[\\s\\S]*?\\};/,\n `const CDP_CONFIG: Config = {\n projectId: process.env.NEXT_PUBLIC_CDP_PROJECT_ID ?? \"\",\n createAccountOnLogin: process.env.NEXT_PUBLIC_CDP_CREATE_ACCOUNT_TYPE === \"evm-smart\" ? \"evm-smart\" : \"evm-eoa\",\n};`,\n );\n } else {\n // For React config.ts\n configContent = configContent.replace(\n /export const CDP_CONFIG: Config = \\{[\\s\\S]*?\\};/,\n `export const CDP_CONFIG: Config = {\n projectId: import.meta.env.VITE_CDP_PROJECT_ID,\n createAccountOnLogin: import.meta.env.VITE_CDP_CREATE_ACCOUNT_TYPE === \"evm-smart\" ? \"evm-smart\" : \"evm-eoa\",\n};`,\n );\n }\n\n return configContent;\n}\n\n/**\n * Copy a file or directory recursively\n *\n * @param filePath - The source path\n * @param destPath - The destination path\n */\nexport function copyFile(filePath: string, destPath: string): void {\n const stat = fs.statSync(filePath);\n if (stat.isDirectory()) {\n copyDir(filePath, destPath);\n } else {\n fs.copyFileSync(filePath, destPath);\n }\n}\n\n/**\n * Copy a file or directory recursively with selective filtering for transaction components\n *\n * @param params - The parameters for the function\n * @param params.filePath - The source path\n * @param params.destPath - The destination path\n * @param params.useSmartAccounts - Whether to use Smart Accounts\n * @param params.enableOnramp - Whether to include Onramp\n */\nexport function copyFileSelectively({\n filePath,\n destPath,\n useSmartAccounts,\n enableOnramp,\n}: {\n filePath: string;\n destPath: string;\n useSmartAccounts?: boolean;\n enableOnramp?: boolean;\n}): void {\n const stat = fs.statSync(filePath);\n if (stat.isDirectory()) {\n const baseDir = path.basename(filePath);\n // skip api and lib directories if Onramp is not enabled\n if (!enableOnramp && (baseDir === \"api\" || baseDir === \"lib\")) return;\n // copy the directory\n copyDirSelectively({ srcDir: filePath, destDir: destPath, useSmartAccounts, enableOnramp });\n } else {\n const fileName = path.basename(filePath);\n\n // Skip transaction components that don't match the user's choice\n if (useSmartAccounts && fileName === \"EOATransaction.tsx\") return;\n if (!useSmartAccounts && fileName === \"SmartAccountTransaction.tsx\") return;\n\n // Skip Onramp files if the user didn't enable Onramp\n if (!enableOnramp && [\"FundWallet.tsx\", \"SignedInScreenWithOnramp.tsx\"].includes(fileName))\n return;\n // Onramp-specific SignedInScreen\n if (enableOnramp) {\n // Skip the default SignedInScreen.tsx file\n if (fileName === \"SignedInScreen.tsx\") return;\n // Copy the SignedInScreenWithOnramp.tsx file to SignedInScreen.tsx\n if (fileName === \"SignedInScreenWithOnramp.tsx\") {\n const newDestPath = destPath.replace(\"SignedInScreenWithOnramp.tsx\", \"SignedInScreen.tsx\");\n fs.copyFileSync(filePath, newDestPath);\n return;\n }\n }\n\n fs.copyFileSync(filePath, destPath);\n }\n}\n\n/**\n * Copy a directory recursively\n *\n * @param srcDir - The source directory path\n * @param destDir - The destination directory path\n */\nfunction copyDir(srcDir: string, destDir: string): void {\n fs.mkdirSync(destDir, { recursive: true });\n for (const file of fs.readdirSync(srcDir)) {\n const srcFile = path.resolve(srcDir, file);\n const destFile = path.resolve(destDir, file);\n copyFile(srcFile, destFile);\n }\n}\n\n/**\n * Copy a directory recursively with selective filtering\n *\n * @param params - The parameters for the function\n * @param params.srcDir - The source directory path\n * @param params.destDir - The destination directory path\n * @param params.useSmartAccounts - Whether to use Smart Accounts\n * @param params.enableOnramp - Whether to include Onramp\n */\nfunction copyDirSelectively({\n srcDir,\n destDir,\n useSmartAccounts,\n enableOnramp,\n}: {\n srcDir: string;\n destDir: string;\n useSmartAccounts?: boolean;\n enableOnramp?: boolean;\n}): void {\n fs.mkdirSync(destDir, { recursive: true });\n for (const file of fs.readdirSync(srcDir)) {\n const srcFile = path.resolve(srcDir, file);\n const destFile = path.resolve(destDir, file);\n copyFileSelectively({ filePath: srcFile, destPath: destFile, useSmartAccounts, enableOnramp });\n }\n}\n\n/**\n * Check if a directory is empty\n *\n * @param dirPath - The path to the directory\n * @returns True if the directory is empty, false otherwise\n */\nexport function isDirEmpty(dirPath: string): boolean {\n const files = fs.readdirSync(dirPath);\n return files.length === 0 || (files.length === 1 && files[0] === \".git\");\n}\n\n/**\n * Empty a directory while preserving .git\n *\n * @param dirPath - The path to the directory\n */\nfunction emptyDir(dirPath: string): void {\n if (!fs.existsSync(dirPath)) {\n return;\n }\n for (const file of fs.readdirSync(dirPath)) {\n if (file === \".git\") {\n continue;\n }\n fs.rmSync(path.resolve(dirPath, file), { recursive: true, force: true });\n }\n}\n\n/**\n * Detect which package manager invoked the create command\n *\n * @returns The detected package manager or 'pnpm' as default\n */\nexport function detectPackageManager(): \"npm\" | \"pnpm\" | \"yarn\" {\n const userAgent = process.env.npm_config_user_agent;\n\n if (userAgent) {\n if (userAgent.startsWith(\"yarn\")) return \"yarn\";\n if (userAgent.startsWith(\"pnpm\")) return \"pnpm\";\n if (userAgent.startsWith(\"npm\")) return \"npm\";\n }\n\n return \"npm\"; // Default to npm if we can't detect\n}\n","#!/usr/bin/env node\n\nimport fs from \"node:fs\";\nimport path from \"node:path\";\nimport { fileURLToPath } from \"node:url\";\n\nimport { red, green, reset } from \"kolorist\";\nimport prompts from \"prompts\";\n\nimport {\n prepareAppDirectory,\n isDirEmpty,\n customizePackageJson,\n copyFileSelectively,\n customizeEnv,\n customizeConfig,\n detectPackageManager,\n} from \"./utils.js\";\n\n// Available templates for app creation\nconst TEMPLATES = [\n {\n name: \"react\",\n display: \"React Single Page App\",\n color: green,\n },\n {\n name: \"nextjs\",\n display: \"Next.js Full Stack App\",\n color: green,\n },\n];\n\nconst defaultTargetDir = \"cdp-app\";\n\nconst fileRenames: Record<string, string | undefined> = {\n _gitignore: \".gitignore\",\n};\n\ninterface AppOptions {\n appName: string;\n template: string;\n targetDirectory: string;\n projectId: string;\n useSmartAccounts: boolean;\n enableOnramp: boolean;\n apiKeyId?: string;\n apiKeySecret?: string;\n}\n\nconst uuidRegex = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;\n\n/**\n * Initialize a new CDP app\n */\nasync function init(): Promise<void> {\n const {\n appName,\n template,\n targetDirectory,\n projectId,\n useSmartAccounts,\n enableOnramp,\n apiKeyId,\n apiKeySecret,\n } = await getAppDetails();\n\n console.log(`\\nScaffolding app in ${targetDirectory}...`);\n\n const root = prepareAppDirectory(targetDirectory, false);\n const templateDir = path.resolve(fileURLToPath(import.meta.url), \"../..\", `template-${template}`);\n\n copyTemplateFiles({\n templateDir,\n root,\n appName,\n projectId,\n useSmartAccounts,\n enableOnramp,\n apiKeyId,\n apiKeySecret,\n });\n printNextSteps(root);\n}\n\n/**\n * Get app details from command line arguments or prompt the user\n *\n * @returns The app details\n */\nasync function getAppDetails(): Promise<AppOptions> {\n // Get target directory from command line args (first non-option argument)\n let targetDir = process.argv[2];\n const defaultAppName = targetDir ?? defaultTargetDir;\n\n try {\n const result = await prompts(\n [\n {\n type: targetDir ? null : \"text\",\n name: \"appName\",\n message: reset(\"App Name:\"),\n initial: defaultAppName,\n onState: state => {\n targetDir = String(state.value).trim() || defaultAppName;\n },\n },\n {\n type: \"select\",\n name: \"template\",\n message: reset(\"Template:\"),\n initial: 0,\n choices: TEMPLATES.map(template => ({\n title: template.color(template.display || template.name),\n value: template.name,\n })),\n },\n {\n type: \"text\",\n name: \"projectId\",\n message: reset(\n \"CDP Project ID (Find your project ID at https://portal.cdp.coinbase.com/projects/overview):\",\n ),\n validate: value => {\n if (!value) {\n return \"Project ID is required\";\n } else if (!uuidRegex.test(value)) {\n return \"Project ID must be a valid UUID\";\n }\n return true;\n },\n initial: \"\",\n },\n {\n type: \"confirm\",\n name: \"useSmartAccounts\",\n message: reset(\n \"Enable Smart Accounts? (Smart Accounts enable gasless transactions and improved UX):\",\n ),\n initial: false,\n },\n {\n type: (_, { template }: { template?: string }) =>\n template === \"nextjs\" ? \"confirm\" : null,\n name: \"enableOnramp\",\n message: reset(\n \"Enable Coinbase Onramp? (Onramp enables users to buy crypto with fiat) -- NOTE: EXPERIMENTAL FEATURE\",\n ),\n initial: false,\n },\n {\n type: (_, { enableOnramp }: { enableOnramp?: boolean }) => (enableOnramp ? \"text\" : null),\n name: \"apiKeyId\",\n message: reset(\"CDP API Key ID (Create at https://portal.cdp.coinbase.com/api-keys):\"),\n validate: value => {\n if (!value) {\n return \"API Key ID is required for Onramp\";\n }\n return true;\n },\n },\n {\n type: (_, { enableOnramp }: { enableOnramp?: boolean }) =>\n enableOnramp ? \"password\" : null,\n name: \"apiKeySecret\",\n message: reset(\"CDP API Key Secret (paste your private key - it will be hidden):\"),\n validate: value => {\n if (!value) {\n return \"API Key Secret is required for Onramp\";\n }\n return true;\n },\n },\n {\n type: \"confirm\",\n name: \"corsConfirmation\",\n message: reset(\n \"Confirm you have whitelisted 'http://localhost:3000' at https://portal.cdp.coinbase.com/products/embedded-wallets/domains:\",\n ),\n initial: true,\n },\n {\n type: () => (!fs.existsSync(targetDir) || isDirEmpty(targetDir) ? null : \"confirm\"),\n name: \"overwrite\",\n message: () =>\n (targetDir === \".\" ? \"Current directory\" : `Target directory \"${targetDir}\"`) +\n \" is not empty. Remove existing files and continue?\",\n },\n {\n type: (_, { overwrite }: { overwrite?: boolean }) => {\n if (overwrite === false) {\n throw new Error(red(\"✖\") + \" Operation cancelled\");\n }\n return null;\n },\n name: \"overwriteChecker\",\n },\n ],\n {\n onCancel: () => {\n throw new Error(red(\"✖\") + \" Operation cancelled\");\n },\n },\n );\n\n return {\n appName: result.appName,\n template: result.template,\n targetDirectory: targetDir,\n projectId: result.projectId,\n useSmartAccounts: result.useSmartAccounts,\n enableOnramp: result.enableOnramp,\n apiKeyId: result.apiKeyId,\n apiKeySecret: result.apiKeySecret,\n };\n } catch (cancelled: unknown) {\n if (cancelled instanceof Error) {\n console.log(cancelled.message);\n }\n process.exit(0);\n }\n}\n\n/**\n * Print next steps for the user\n *\n * @param appRoot - The root directory of the app\n */\nfunction printNextSteps(appRoot: string): void {\n const packageManager = detectPackageManager();\n\n console.log(green(\"\\nDone. Now run your app:\\n\"));\n if (appRoot !== process.cwd()) {\n console.log(`cd ${path.relative(process.cwd(), appRoot)}`);\n }\n const devCommand = packageManager === \"npm\" ? \"npm run dev\" : `${packageManager} dev`;\n console.log(`${packageManager} install`);\n console.log(devCommand);\n}\n\n/**\n * Copy template files to the app directory\n *\n * @param params - The parameters for the function\n * @param params.templateDir - The directory containing the template files\n * @param params.root - The root directory of the app\n * @param params.appName - The name of the app\n * @param params.projectId - The CDP Project ID\n * @param params.useSmartAccounts - Whether to enable smart accounts\n * @param params.enableOnramp - Whether to include Onramp\n * @param params.apiKeyId - The API Key ID\n * @param params.apiKeySecret - The API Key Secret\n */\nfunction copyTemplateFiles({\n templateDir,\n root,\n appName,\n projectId,\n useSmartAccounts,\n enableOnramp,\n apiKeyId,\n apiKeySecret,\n}: {\n templateDir: string;\n root: string;\n appName: string;\n projectId?: string;\n useSmartAccounts?: boolean;\n enableOnramp?: boolean;\n apiKeyId?: string;\n apiKeySecret?: string;\n}): void {\n const writeFileToTarget = (file: string, content?: string) => {\n const targetPath = path.join(root, fileRenames[file] ?? file);\n if (content) {\n fs.writeFileSync(targetPath, content);\n } else {\n copyFileSelectively({\n filePath: path.join(templateDir, file),\n destPath: targetPath,\n useSmartAccounts,\n enableOnramp,\n });\n }\n };\n\n const isNextjs = templateDir.includes(\"nextjs\");\n\n const files = fs.readdirSync(templateDir);\n for (const file of files) {\n if (file === \"package.json\") {\n const customizedPackageJson = customizePackageJson(templateDir, appName, enableOnramp);\n writeFileToTarget(file, customizedPackageJson);\n } else if (file === \"env.example\" && projectId) {\n writeFileToTarget(file);\n const customizedEnv = customizeEnv({\n templateDir,\n projectId,\n useSmartAccounts,\n apiKeyId,\n apiKeySecret,\n });\n console.log(\"Copying CDP Project ID to .env\");\n if (apiKeyId && apiKeySecret) {\n console.log(\"Copying CDP API credentials to .env\");\n }\n if (useSmartAccounts) {\n console.log(\"Configuring Smart Accounts in environment\");\n }\n writeFileToTarget(\".env\", customizedEnv);\n } else {\n writeFileToTarget(file);\n }\n }\n\n // Handle smart account configuration in config files\n if (useSmartAccounts) {\n const configFileName = isNextjs ? \"src/components/Providers.tsx\" : \"src/config.ts\";\n const customizedConfig = customizeConfig(templateDir, useSmartAccounts, isNextjs);\n if (customizedConfig) {\n console.log(\"Configuring Smart Accounts in application config\");\n writeFileToTarget(configFileName, customizedConfig);\n }\n }\n\n // Generate the appropriate Transaction.tsx component\n const transactionFileName = isNextjs ? \"src/components/Transaction.tsx\" : \"src/Transaction.tsx\";\n const transactionContent = generateTransactionComponent(useSmartAccounts);\n writeFileToTarget(transactionFileName, transactionContent);\n}\n\n/**\n * Generate the appropriate Transaction component based on account type\n *\n * @param useSmartAccounts - Whether to use Smart Accounts\n * @returns The generated Transaction component content\n */\nfunction generateTransactionComponent(useSmartAccounts?: boolean): string {\n if (useSmartAccounts) {\n return `export { default } from \"./SmartAccountTransaction\";`;\n } else {\n return `export { default } from \"./EOATransaction\";`;\n }\n}\n\ninit().catch(e => {\n console.error(e);\n process.exit(1);\n});\n"],"names":[],"mappings":";;;;;;AAUgB,SAAA,oBAAoB,WAAmB,iBAAkC;AACvF,QAAM,OAAO,KAAK,KAAK,QAAQ,OAAO,SAAS;AAIpC,MAAA,CAAC,GAAG,WAAW,IAAI,GAAG;AAC/B,OAAG,UAAU,MAAM,EAAE,WAAW,MAAM;AAAA,EAAA;AAGjC,SAAA;AACT;AAUgB,SAAA,qBACd,aACA,SACA,YACQ;AACR,QAAM,kBAAkB,KAAK,KAAK,aAAa,cAAc;AAC7D,QAAM,cAAc,KAAK,MAAM,GAAG,aAAa,iBAAiB,OAAO,CAAC;AACxE,cAAY,OAAO;AACnB,MAAI,YAAY;AACF,gBAAA,aAAa,mBAAmB,IAAI;AAAA,EAAA;AAElD,SAAO,KAAK,UAAU,aAAa,MAAM,CAAC,IAAI;AAChD;AAaO,SAAS,aAAa;AAAA,EAC3B;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF,GAMW;AACT,QAAM,iBAAiB,KAAK,KAAK,aAAa,aAAa;AAC3D,QAAM,aAAa,GAAG,aAAa,gBAAgB,OAAO;AAE1D,MAAI,aAAa,WAAW,QAAQ,8BAA8B,KAAK,SAAS;AAAA,CAAI;AAG9E,QAAA,cAAc,mBAAmB,cAAc;AACrD,QAAM,SAAS,YAAY,SAAS,QAAQ,IAAI,iBAAiB;AACjE,eAAa,WAAW;AAAA,IACtB,IAAI,OAAO,IAAI,MAAM;AAAA,IAAsC;AAAA,IAC3D,KAAK,WAAW;AAAA;AAAA,EAClB;AAGA,MAAI,YAAY,cAAc;AAE5B,iBAAa,WAAW,QAAQ,uBAAuB,kBAAkB,QAAQ,EAAE;AAEnF,iBAAa,WAAW;AAAA,MACtB;AAAA,MACA,sBAAsB,YAAY;AAAA,IACpC;AAAA,EAAA;AAGK,SAAA;AACT;AAUgB,SAAA,gBACd,aACA,kBACA,UACe;AACX,MAAA,CAAC,iBAAyB,QAAA;AAExB,QAAA,iBAAiB,WAAW,iCAAiC;AACnE,QAAM,aAAa,KAAK,KAAK,aAAa,cAAc;AAExD,MAAI,CAAC,GAAG,WAAW,UAAU,EAAU,QAAA;AAEvC,MAAI,gBAAgB,GAAG,aAAa,YAAY,OAAO;AAEvD,MAAI,UAAU;AAEZ,oBAAgB,cAAc;AAAA,MAC5B;AAAA,MACA;AAAA;AAAA;AAAA;AAAA,IAIF;AAAA,EAAA,OACK;AAEL,oBAAgB,cAAc;AAAA,MAC5B;AAAA,MACA;AAAA;AAAA;AAAA;AAAA,IAIF;AAAA,EAAA;AAGK,SAAA;AACT;AA0BO,SAAS,oBAAoB;AAAA,EAClC;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF,GAKS;AACD,QAAA,OAAO,GAAG,SAAS,QAAQ;AAC7B,MAAA,KAAK,eAAe;AAChB,UAAA,UAAU,KAAK,SAAS,QAAQ;AAEtC,QAAI,CAAC,iBAAiB,YAAY,SAAS,YAAY,OAAQ;AAE/D,uBAAmB,EAAE,QAAQ,UAAU,SAAS,UAAU,kBAAkB,cAAc;AAAA,EAAA,OACrF;AACC,UAAA,WAAW,KAAK,SAAS,QAAQ;AAGnC,QAAA,oBAAoB,aAAa,qBAAsB;AACvD,QAAA,CAAC,oBAAoB,aAAa,8BAA+B;AAGrE,QAAI,CAAC,gBAAgB,CAAC,kBAAkB,8BAA8B,EAAE,SAAS,QAAQ;AACvF;AAEF,QAAI,cAAc;AAEhB,UAAI,aAAa,qBAAsB;AAEvC,UAAI,aAAa,gCAAgC;AAC/C,cAAM,cAAc,SAAS,QAAQ,gCAAgC,oBAAoB;AACtF,WAAA,aAAa,UAAU,WAAW;AACrC;AAAA,MAAA;AAAA,IACF;AAGC,OAAA,aAAa,UAAU,QAAQ;AAAA,EAAA;AAEtC;AA0BA,SAAS,mBAAmB;AAAA,EAC1B;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF,GAKS;AACP,KAAG,UAAU,SAAS,EAAE,WAAW,MAAM;AACzC,aAAW,QAAQ,GAAG,YAAY,MAAM,GAAG;AACzC,UAAM,UAAU,KAAK,QAAQ,QAAQ,IAAI;AACzC,UAAM,WAAW,KAAK,QAAQ,SAAS,IAAI;AAC3C,wBAAoB,EAAE,UAAU,SAAS,UAAU,UAAU,kBAAkB,cAAc;AAAA,EAAA;AAEjG;AAQO,SAAS,WAAW,SAA0B;AAC7C,QAAA,QAAQ,GAAG,YAAY,OAAO;AAC7B,SAAA,MAAM,WAAW,KAAM,MAAM,WAAW,KAAK,MAAM,CAAC,MAAM;AACnE;AAwBO,SAAS,uBAAgD;AACxD,QAAA,YAAY,QAAQ,IAAI;AAE9B,MAAI,WAAW;AACb,QAAI,UAAU,WAAW,MAAM,EAAU,QAAA;AACzC,QAAI,UAAU,WAAW,MAAM,EAAU,QAAA;AACzC,QAAI,UAAU,WAAW,KAAK,EAAU,QAAA;AAAA,EAAA;AAGnC,SAAA;AACT;AClRA,MAAM,YAAY;AAAA,EAChB;AAAA,IACE,MAAM;AAAA,IACN,SAAS;AAAA,IACT,OAAO;AAAA,EACT;AAAA,EACA;AAAA,IACE,MAAM;AAAA,IACN,SAAS;AAAA,IACT,OAAO;AAAA,EAAA;AAEX;AAEA,MAAM,mBAAmB;AAEzB,MAAM,cAAkD;AAAA,EACtD,YAAY;AACd;AAaA,MAAM,YAAY;AAKlB,eAAe,OAAsB;AAC7B,QAAA;AAAA,IACJ;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,EACF,IAAI,MAAM,cAAc;AAExB,UAAQ,IAAI;AAAA,qBAAwB,eAAe,KAAK;AAElD,QAAA,OAAO,oBAAoB,eAAsB;AACjD,QAAA,cAAc,KAAK,QAAQ,cAAc,YAAY,GAAG,GAAG,SAAS,YAAY,QAAQ,EAAE;AAE9E,oBAAA;AAAA,IAChB;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,EAAA,CACD;AACD,iBAAe,IAAI;AACrB;AAOA,eAAe,gBAAqC;AAE9C,MAAA,YAAY,QAAQ,KAAK,CAAC;AAC9B,QAAM,iBAAiB,aAAa;AAEhC,MAAA;AACF,UAAM,SAAS,MAAM;AAAA,MACnB;AAAA,QACE;AAAA,UACE,MAAM,YAAY,OAAO;AAAA,UACzB,MAAM;AAAA,UACN,SAAS,MAAM,WAAW;AAAA,UAC1B,SAAS;AAAA,UACT,SAAS,CAAS,UAAA;AAChB,wBAAY,OAAO,MAAM,KAAK,EAAE,KAAU,KAAA;AAAA,UAAA;AAAA,QAE9C;AAAA,QACA;AAAA,UACE,MAAM;AAAA,UACN,MAAM;AAAA,UACN,SAAS,MAAM,WAAW;AAAA,UAC1B,SAAS;AAAA,UACT,SAAS,UAAU,IAAI,CAAa,cAAA;AAAA,YAClC,OAAO,SAAS,MAAM,SAAS,WAAW,SAAS,IAAI;AAAA,YACvD,OAAO,SAAS;AAAA,UAAA,EAChB;AAAA,QACJ;AAAA,QACA;AAAA,UACE,MAAM;AAAA,UACN,MAAM;AAAA,UACN,SAAS;AAAA,YACP;AAAA,UACF;AAAA,UACA,UAAU,CAAS,UAAA;AACjB,gBAAI,CAAC,OAAO;AACH,qBAAA;AAAA,YACE,WAAA,CAAC,UAAU,KAAK,KAAK,GAAG;AAC1B,qBAAA;AAAA,YAAA;AAEF,mBAAA;AAAA,UACT;AAAA,UACA,SAAS;AAAA,QACX;AAAA,QACA;AAAA,UACE,MAAM;AAAA,UACN,MAAM;AAAA,UACN,SAAS;AAAA,YACP;AAAA,UACF;AAAA,UACA,SAAS;AAAA,QACX;AAAA,QACA;AAAA,UACE,MAAM,CAAC,GAAG,EAAE,SACV,MAAA,aAAa,WAAW,YAAY;AAAA,UACtC,MAAM;AAAA,UACN,SAAS;AAAA,YACP;AAAA,UACF;AAAA,UACA,SAAS;AAAA,QACX;AAAA,QACA;AAAA,UACE,MAAM,CAAC,GAAG,EAAE,aAAa,MAAmC,eAAe,SAAS;AAAA,UACpF,MAAM;AAAA,UACN,SAAS,MAAM,sEAAsE;AAAA,UACrF,UAAU,CAAS,UAAA;AACjB,gBAAI,CAAC,OAAO;AACH,qBAAA;AAAA,YAAA;AAEF,mBAAA;AAAA,UAAA;AAAA,QAEX;AAAA,QACA;AAAA,UACE,MAAM,CAAC,GAAG,EAAE,aAAa,MACvB,eAAe,aAAa;AAAA,UAC9B,MAAM;AAAA,UACN,SAAS,MAAM,kEAAkE;AAAA,UACjF,UAAU,CAAS,UAAA;AACjB,gBAAI,CAAC,OAAO;AACH,qBAAA;AAAA,YAAA;AAEF,mBAAA;AAAA,UAAA;AAAA,QAEX;AAAA,QACA;AAAA,UACE,MAAM;AAAA,UACN,MAAM;AAAA,UACN,SAAS;AAAA,YACP;AAAA,UACF;AAAA,UACA,SAAS;AAAA,QACX;AAAA,QACA;AAAA,UACE,MAAM,MAAO,CAAC,GAAG,WAAW,SAAS,KAAK,WAAW,SAAS,IAAI,OAAO;AAAA,UACzE,MAAM;AAAA,UACN,SAAS,OACN,cAAc,MAAM,sBAAsB,qBAAqB,SAAS,OACzE;AAAA,QACJ;AAAA,QACA;AAAA,UACE,MAAM,CAAC,GAAG,EAAE,gBAAyC;AACnD,gBAAI,cAAc,OAAO;AACvB,oBAAM,IAAI,MAAM,IAAI,GAAG,IAAI,sBAAsB;AAAA,YAAA;AAE5C,mBAAA;AAAA,UACT;AAAA,UACA,MAAM;AAAA,QAAA;AAAA,MAEV;AAAA,MACA;AAAA,QACE,UAAU,MAAM;AACd,gBAAM,IAAI,MAAM,IAAI,GAAG,IAAI,sBAAsB;AAAA,QAAA;AAAA,MACnD;AAAA,IAEJ;AAEO,WAAA;AAAA,MACL,SAAS,OAAO;AAAA,MAChB,UAAU,OAAO;AAAA,MACjB,iBAAiB;AAAA,MACjB,WAAW,OAAO;AAAA,MAClB,kBAAkB,OAAO;AAAA,MACzB,cAAc,OAAO;AAAA,MACrB,UAAU,OAAO;AAAA,MACjB,cAAc,OAAO;AAAA,IACvB;AAAA,WACO,WAAoB;AAC3B,QAAI,qBAAqB,OAAO;AACtB,cAAA,IAAI,UAAU,OAAO;AAAA,IAAA;AAE/B,YAAQ,KAAK,CAAC;AAAA,EAAA;AAElB;AAOA,SAAS,eAAe,SAAuB;AAC7C,QAAM,iBAAiB,qBAAqB;AAEpC,UAAA,IAAI,MAAM,6BAA6B,CAAC;AAC5C,MAAA,YAAY,QAAQ,OAAO;AACrB,YAAA,IAAI,MAAM,KAAK,SAAS,QAAQ,IAAI,GAAG,OAAO,CAAC,EAAE;AAAA,EAAA;AAE3D,QAAM,aAAa,mBAAmB,QAAQ,gBAAgB,GAAG,cAAc;AACvE,UAAA,IAAI,GAAG,cAAc,UAAU;AACvC,UAAQ,IAAI,UAAU;AACxB;AAeA,SAAS,kBAAkB;AAAA,EACzB;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF,GASS;AACD,QAAA,oBAAoB,CAAC,MAAc,YAAqB;AAC5D,UAAM,aAAa,KAAK,KAAK,MAAM,YAAY,IAAI,KAAK,IAAI;AAC5D,QAAI,SAAS;AACR,SAAA,cAAc,YAAY,OAAO;AAAA,IAAA,OAC/B;AACe,0BAAA;AAAA,QAClB,UAAU,KAAK,KAAK,aAAa,IAAI;AAAA,QACrC,UAAU;AAAA,QACV;AAAA,QACA;AAAA,MAAA,CACD;AAAA,IAAA;AAAA,EAEL;AAEM,QAAA,WAAW,YAAY,SAAS,QAAQ;AAExC,QAAA,QAAQ,GAAG,YAAY,WAAW;AACxC,aAAW,QAAQ,OAAO;AACxB,QAAI,SAAS,gBAAgB;AAC3B,YAAM,wBAAwB,qBAAqB,aAAa,SAAS,YAAY;AACrF,wBAAkB,MAAM,qBAAqB;AAAA,IAAA,WACpC,SAAS,iBAAiB,WAAW;AAC9C,wBAAkB,IAAI;AACtB,YAAM,gBAAgB,aAAa;AAAA,QACjC;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,MAAA,CACD;AACD,cAAQ,IAAI,gCAAgC;AAC5C,UAAI,YAAY,cAAc;AAC5B,gBAAQ,IAAI,qCAAqC;AAAA,MAAA;AAEnD,UAAI,kBAAkB;AACpB,gBAAQ,IAAI,2CAA2C;AAAA,MAAA;AAEzD,wBAAkB,QAAQ,aAAa;AAAA,IAAA,OAClC;AACL,wBAAkB,IAAI;AAAA,IAAA;AAAA,EACxB;AAIF,MAAI,kBAAkB;AACd,UAAA,iBAAiB,WAAW,iCAAiC;AACnE,UAAM,mBAAmB,gBAAgB,aAAa,kBAAkB,QAAQ;AAChF,QAAI,kBAAkB;AACpB,cAAQ,IAAI,kDAAkD;AAC9D,wBAAkB,gBAAgB,gBAAgB;AAAA,IAAA;AAAA,EACpD;AAII,QAAA,sBAAsB,WAAW,mCAAmC;AACpE,QAAA,qBAAqB,6BAA6B,gBAAgB;AACxE,oBAAkB,qBAAqB,kBAAkB;AAC3D;AAQA,SAAS,6BAA6B,kBAAoC;AACxE,MAAI,kBAAkB;AACb,WAAA;AAAA,EAAA,OACF;AACE,WAAA;AAAA,EAAA;AAEX;AAEA,OAAO,MAAM,CAAK,MAAA;AAChB,UAAQ,MAAM,CAAC;AACf,UAAQ,KAAK,CAAC;AAChB,CAAC;"}
|
|
1
|
+
{"version":3,"file":"index.js","sources":["../src/utils.ts","../src/index.ts"],"sourcesContent":["import fs from \"node:fs\";\nimport path from \"node:path\";\n\n/**\n * Prepare the app directory\n *\n * @param targetDir - The target directory for the app\n * @param shouldOverwrite - Whether to overwrite the existing directory\n * @returns The path to the prepared app directory\n */\nexport function prepareAppDirectory(targetDir: string, shouldOverwrite: boolean): string {\n const root = path.join(process.cwd(), targetDir);\n\n if (shouldOverwrite) {\n emptyDir(root);\n } else if (!fs.existsSync(root)) {\n fs.mkdirSync(root, { recursive: true });\n }\n\n return root;\n}\n\n/**\n * Customize package.json for the new app\n *\n * @param templateDir - The directory containing the template files\n * @param appName - The name of the app\n * @param includeSdk - Whether to include the CDP SDK in the dependencies\n * @returns The customized package.json content\n */\nexport function customizePackageJson(\n templateDir: string,\n appName: string,\n includeSdk?: boolean,\n): string {\n const packageJsonPath = path.join(templateDir, \"package.json\");\n const packageJson = JSON.parse(fs.readFileSync(packageJsonPath, \"utf-8\"));\n packageJson.name = appName;\n if (includeSdk) {\n packageJson.dependencies[\"@coinbase/cdp-sdk\"] = \"latest\";\n }\n return JSON.stringify(packageJson, null, 2) + \"\\n\";\n}\n\n/**\n * Set up the .env file for the new app\n *\n * @param params - The parameters for the function\n * @param params.templateDir - The directory containing the template files\n * @param params.projectId - The CDP Project ID\n * @param params.useSmartAccounts - Whether to enable smart accounts\n * @param params.apiKeyId - The API Key ID\n * @param params.apiKeySecret - The API Key Secret\n * @returns The customized .env content\n */\nexport function customizeEnv({\n templateDir,\n projectId,\n useSmartAccounts,\n apiKeyId,\n apiKeySecret,\n}: {\n templateDir: string;\n projectId: string;\n useSmartAccounts?: boolean;\n apiKeyId?: string;\n apiKeySecret?: string;\n}): string {\n const exampleEnvPath = path.join(templateDir, \"env.example\");\n const exampleEnv = fs.readFileSync(exampleEnvPath, \"utf-8\");\n\n let envContent = exampleEnv.replace(/(.*PROJECT_ID=).*(\\r?\\n|$)/, `$1${projectId}\\n`);\n\n // Set the account type based on user choice\n const accountType = useSmartAccounts ? \"evm-smart\" : \"evm-eoa\";\n let prefix: string;\n if (templateDir.includes(\"nextjs\")) {\n prefix = \"NEXT_PUBLIC_\";\n } else if (templateDir.includes(\"react-native\")) {\n prefix = \"EXPO_PUBLIC_\";\n } else {\n prefix = \"VITE_\";\n }\n envContent = envContent.replace(\n new RegExp(`(${prefix}CDP_CREATE_ACCOUNT_TYPE=).*(\\r?\\n|$)`),\n `$1${accountType}\\n`,\n );\n\n // Replace CDP API credentials if provided\n if (apiKeyId && apiKeySecret) {\n // Replace the commented API Key ID\n envContent = envContent.replace(/# CDP_API_KEY_ID=.*/, `CDP_API_KEY_ID=${apiKeyId}`);\n // Replace the commented API Key Secret\n envContent = envContent.replace(\n /# CDP_API_KEY_SECRET=.*/,\n `CDP_API_KEY_SECRET=${apiKeySecret}`,\n );\n }\n\n return envContent;\n}\n\n/**\n * Customize configuration files for smart accounts\n *\n * @param templateDir - The directory containing the template files\n * @param useSmartAccounts - Whether to enable smart accounts\n * @param isNextjs - Whether this is a Next.js template\n * @returns The customized config content\n */\nexport function customizeConfig(\n templateDir: string,\n useSmartAccounts: boolean,\n isNextjs: boolean,\n): string | null {\n if (!useSmartAccounts) return null;\n\n const configFileName = isNextjs ? \"src/components/Providers.tsx\" : \"src/config.ts\";\n const configPath = path.join(templateDir, configFileName);\n\n if (!fs.existsSync(configPath)) return null;\n\n let configContent = fs.readFileSync(configPath, \"utf-8\");\n\n if (isNextjs) {\n // For Next.js Providers.tsx\n configContent = configContent.replace(\n /const CDP_CONFIG: Config = \\{[\\s\\S]*?\\};/,\n `const CDP_CONFIG: Config = {\n projectId: process.env.NEXT_PUBLIC_CDP_PROJECT_ID ?? \"\",\n createAccountOnLogin: process.env.NEXT_PUBLIC_CDP_CREATE_ACCOUNT_TYPE === \"evm-smart\" ? \"evm-smart\" : \"evm-eoa\",\n};`,\n );\n } else {\n // For React config.ts\n configContent = configContent.replace(\n /export const CDP_CONFIG: Config = \\{[\\s\\S]*?\\};/,\n `export const CDP_CONFIG: Config = {\n projectId: import.meta.env.VITE_CDP_PROJECT_ID,\n createAccountOnLogin: import.meta.env.VITE_CDP_CREATE_ACCOUNT_TYPE === \"evm-smart\" ? \"evm-smart\" : \"evm-eoa\",\n};`,\n );\n }\n\n return configContent;\n}\n\n/**\n * Copy a file or directory recursively\n *\n * @param filePath - The source path\n * @param destPath - The destination path\n */\nexport function copyFile(filePath: string, destPath: string): void {\n const stat = fs.statSync(filePath);\n if (stat.isDirectory()) {\n copyDir(filePath, destPath);\n } else {\n fs.copyFileSync(filePath, destPath);\n }\n}\n\n/**\n * Copy a file or directory recursively with selective filtering for transaction components\n *\n * @param params - The parameters for the function\n * @param params.filePath - The source path\n * @param params.destPath - The destination path\n * @param params.useSmartAccounts - Whether to use Smart Accounts\n * @param params.enableOnramp - Whether to include Onramp\n */\nexport function copyFileSelectively({\n filePath,\n destPath,\n useSmartAccounts,\n enableOnramp,\n}: {\n filePath: string;\n destPath: string;\n useSmartAccounts?: boolean;\n enableOnramp?: boolean;\n}): void {\n const stat = fs.statSync(filePath);\n if (stat.isDirectory()) {\n const baseDir = path.basename(filePath);\n // skip api and lib directories if Onramp is not enabled\n if (!enableOnramp && (baseDir === \"api\" || baseDir === \"lib\")) return;\n // copy the directory\n copyDirSelectively({ srcDir: filePath, destDir: destPath, useSmartAccounts, enableOnramp });\n } else {\n const fileName = path.basename(filePath);\n\n // Skip transaction components that don't match the user's choice\n if (useSmartAccounts && fileName === \"EOATransaction.tsx\") return;\n if (!useSmartAccounts && fileName === \"SmartAccountTransaction.tsx\") return;\n\n // Skip Onramp files if the user didn't enable Onramp\n if (!enableOnramp && [\"FundWallet.tsx\", \"SignedInScreenWithOnramp.tsx\"].includes(fileName))\n return;\n // Onramp-specific SignedInScreen\n if (enableOnramp) {\n // Skip the default SignedInScreen.tsx file\n if (fileName === \"SignedInScreen.tsx\") return;\n // Copy the SignedInScreenWithOnramp.tsx file to SignedInScreen.tsx\n if (fileName === \"SignedInScreenWithOnramp.tsx\") {\n const newDestPath = destPath.replace(\"SignedInScreenWithOnramp.tsx\", \"SignedInScreen.tsx\");\n fs.copyFileSync(filePath, newDestPath);\n return;\n }\n }\n\n fs.copyFileSync(filePath, destPath);\n }\n}\n\n/**\n * Copy a directory recursively\n *\n * @param srcDir - The source directory path\n * @param destDir - The destination directory path\n */\nfunction copyDir(srcDir: string, destDir: string): void {\n fs.mkdirSync(destDir, { recursive: true });\n for (const file of fs.readdirSync(srcDir)) {\n const srcFile = path.resolve(srcDir, file);\n const destFile = path.resolve(destDir, file);\n copyFile(srcFile, destFile);\n }\n}\n\n/**\n * Copy a directory recursively with selective filtering\n *\n * @param params - The parameters for the function\n * @param params.srcDir - The source directory path\n * @param params.destDir - The destination directory path\n * @param params.useSmartAccounts - Whether to use Smart Accounts\n * @param params.enableOnramp - Whether to include Onramp\n */\nfunction copyDirSelectively({\n srcDir,\n destDir,\n useSmartAccounts,\n enableOnramp,\n}: {\n srcDir: string;\n destDir: string;\n useSmartAccounts?: boolean;\n enableOnramp?: boolean;\n}): void {\n fs.mkdirSync(destDir, { recursive: true });\n for (const file of fs.readdirSync(srcDir)) {\n const srcFile = path.resolve(srcDir, file);\n const destFile = path.resolve(destDir, file);\n copyFileSelectively({ filePath: srcFile, destPath: destFile, useSmartAccounts, enableOnramp });\n }\n}\n\n/**\n * Check if a directory is empty\n *\n * @param dirPath - The path to the directory\n * @returns True if the directory is empty, false otherwise\n */\nexport function isDirEmpty(dirPath: string): boolean {\n const files = fs.readdirSync(dirPath);\n return files.length === 0 || (files.length === 1 && files[0] === \".git\");\n}\n\n/**\n * Empty a directory while preserving .git\n *\n * @param dirPath - The path to the directory\n */\nfunction emptyDir(dirPath: string): void {\n if (!fs.existsSync(dirPath)) {\n return;\n }\n for (const file of fs.readdirSync(dirPath)) {\n if (file === \".git\") {\n continue;\n }\n fs.rmSync(path.resolve(dirPath, file), { recursive: true, force: true });\n }\n}\n\n/**\n * Detect which package manager invoked the create command\n *\n * @returns The detected package manager or 'pnpm' as default\n */\nexport function detectPackageManager(): \"npm\" | \"pnpm\" | \"yarn\" {\n const userAgent = process.env.npm_config_user_agent;\n\n if (userAgent) {\n if (userAgent.startsWith(\"yarn\")) return \"yarn\";\n if (userAgent.startsWith(\"pnpm\")) return \"pnpm\";\n if (userAgent.startsWith(\"npm\")) return \"npm\";\n }\n\n return \"npm\"; // Default to npm if we can't detect\n}\n","#!/usr/bin/env node\n\nimport fs from \"node:fs\";\nimport path from \"node:path\";\nimport { fileURLToPath } from \"node:url\";\n\nimport { red, green, reset } from \"kolorist\";\nimport prompts from \"prompts\";\n\nimport {\n prepareAppDirectory,\n isDirEmpty,\n customizePackageJson,\n copyFileSelectively,\n customizeEnv,\n customizeConfig,\n detectPackageManager,\n} from \"./utils.js\";\n\n// Available templates for app creation\nconst TEMPLATES = [\n {\n name: \"react\",\n display: \"React Single Page App\",\n color: green,\n },\n {\n name: \"nextjs\",\n display: \"Next.js Full Stack App\",\n color: green,\n },\n {\n name: \"react-native\",\n display: \"React Native with Expo\",\n color: green,\n },\n];\n\nconst defaultTargetDir = \"cdp-app\";\n\nconst fileRenames: Record<string, string | undefined> = {\n _gitignore: \".gitignore\",\n};\n\ninterface AppOptions {\n appName: string;\n template: string;\n targetDirectory: string;\n projectId: string;\n useSmartAccounts: boolean;\n enableOnramp: boolean;\n apiKeyId?: string;\n apiKeySecret?: string;\n}\n\nconst uuidRegex = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;\n\n/**\n * Initialize a new CDP app\n */\nasync function init(): Promise<void> {\n const {\n appName,\n template,\n targetDirectory,\n projectId,\n useSmartAccounts,\n enableOnramp,\n apiKeyId,\n apiKeySecret,\n } = await getAppDetails();\n\n console.log(`\\nScaffolding app in ${targetDirectory}...`);\n\n const root = prepareAppDirectory(targetDirectory, false);\n const templateDir = path.resolve(fileURLToPath(import.meta.url), \"../..\", `template-${template}`);\n\n copyTemplateFiles({\n templateDir,\n root,\n appName,\n projectId,\n useSmartAccounts,\n enableOnramp,\n apiKeyId,\n apiKeySecret,\n });\n printNextSteps(root, template);\n}\n\n/**\n * Get app details from command line arguments or prompt the user\n *\n * @returns The app details\n */\nasync function getAppDetails(): Promise<AppOptions> {\n // Get target directory from command line args (first non-option argument)\n let targetDir = process.argv[2];\n const defaultAppName = targetDir ?? defaultTargetDir;\n\n try {\n const result = await prompts(\n [\n {\n type: targetDir ? null : \"text\",\n name: \"appName\",\n message: reset(\"App Name:\"),\n initial: defaultAppName,\n onState: state => {\n targetDir = String(state.value).trim() || defaultAppName;\n },\n },\n {\n type: \"select\",\n name: \"template\",\n message: reset(\"Template:\"),\n initial: 0,\n choices: TEMPLATES.map(template => ({\n title: template.color(template.display || template.name),\n value: template.name,\n })),\n },\n {\n type: \"text\",\n name: \"projectId\",\n message: reset(\n \"CDP Project ID (Find your project ID at https://portal.cdp.coinbase.com/projects/overview):\",\n ),\n validate: value => {\n if (!value) {\n return \"Project ID is required\";\n } else if (!uuidRegex.test(value)) {\n return \"Project ID must be a valid UUID\";\n }\n return true;\n },\n initial: \"\",\n },\n {\n type: \"confirm\",\n name: \"useSmartAccounts\",\n message: reset(\n \"Enable Smart Accounts? (Smart Accounts enable gasless transactions and improved UX):\",\n ),\n initial: false,\n },\n {\n type: (_, { template }: { template?: string }) =>\n template === \"nextjs\" ? \"confirm\" : null,\n name: \"enableOnramp\",\n message: reset(\n \"Enable Coinbase Onramp? (Onramp enables users to buy crypto with fiat) -- NOTE: EXPERIMENTAL FEATURE\",\n ),\n initial: false,\n },\n {\n type: (_, { enableOnramp }: { enableOnramp?: boolean }) => (enableOnramp ? \"text\" : null),\n name: \"apiKeyId\",\n message: reset(\"CDP API Key ID (Create at https://portal.cdp.coinbase.com/api-keys):\"),\n validate: value => {\n if (!value) {\n return \"API Key ID is required for Onramp\";\n }\n return true;\n },\n },\n {\n type: (_, { enableOnramp }: { enableOnramp?: boolean }) =>\n enableOnramp ? \"password\" : null,\n name: \"apiKeySecret\",\n message: reset(\"CDP API Key Secret (paste your private key - it will be hidden):\"),\n validate: value => {\n if (!value) {\n return \"API Key Secret is required for Onramp\";\n }\n return true;\n },\n },\n {\n type: (_, { template }: { template?: string }) =>\n template === \"react-native\" ? null : \"confirm\",\n name: \"corsConfirmation\",\n message: reset(\n \"Confirm you have whitelisted 'http://localhost:3000' at https://portal.cdp.coinbase.com/products/embedded-wallets/domains:\",\n ),\n initial: true,\n },\n {\n type: () => (!fs.existsSync(targetDir) || isDirEmpty(targetDir) ? null : \"confirm\"),\n name: \"overwrite\",\n message: () =>\n (targetDir === \".\" ? \"Current directory\" : `Target directory \"${targetDir}\"`) +\n \" is not empty. Remove existing files and continue?\",\n },\n {\n type: (_, { overwrite }: { overwrite?: boolean }) => {\n if (overwrite === false) {\n throw new Error(red(\"✖\") + \" Operation cancelled\");\n }\n return null;\n },\n name: \"overwriteChecker\",\n },\n ],\n {\n onCancel: () => {\n throw new Error(red(\"✖\") + \" Operation cancelled\");\n },\n },\n );\n\n return {\n appName: result.appName,\n template: result.template,\n targetDirectory: targetDir,\n projectId: result.projectId,\n useSmartAccounts: result.useSmartAccounts,\n enableOnramp: result.enableOnramp,\n apiKeyId: result.apiKeyId,\n apiKeySecret: result.apiKeySecret,\n };\n } catch (cancelled: unknown) {\n if (cancelled instanceof Error) {\n console.log(cancelled.message);\n }\n process.exit(0);\n }\n}\n\n/**\n * Print next steps for the user\n *\n * @param appRoot - The root directory of the app\n * @param template - The template that was used\n */\nfunction printNextSteps(appRoot: string, template: string): void {\n const packageManager = detectPackageManager();\n\n console.log(green(\"\\nDone. Now run your app:\\n\"));\n if (appRoot !== process.cwd()) {\n console.log(`cd ${path.relative(process.cwd(), appRoot)}`);\n }\n\n console.log(`${packageManager} install`);\n\n if (template === \"react-native\") {\n const startCommand =\n packageManager === \"npm\"\n ? \"npm run ios # or npm run android\"\n : `${packageManager} run ios # or ${packageManager} run android`;\n console.log(startCommand);\n } else {\n const devCommand = packageManager === \"npm\" ? \"npm run dev\" : `${packageManager} dev`;\n console.log(devCommand);\n }\n}\n\n/**\n * Copy template files to the app directory\n *\n * @param params - The parameters for the function\n * @param params.templateDir - The directory containing the template files\n * @param params.root - The root directory of the app\n * @param params.appName - The name of the app\n * @param params.projectId - The CDP Project ID\n * @param params.useSmartAccounts - Whether to enable smart accounts\n * @param params.enableOnramp - Whether to include Onramp\n * @param params.apiKeyId - The API Key ID\n * @param params.apiKeySecret - The API Key Secret\n */\nfunction copyTemplateFiles({\n templateDir,\n root,\n appName,\n projectId,\n useSmartAccounts,\n enableOnramp,\n apiKeyId,\n apiKeySecret,\n}: {\n templateDir: string;\n root: string;\n appName: string;\n projectId?: string;\n useSmartAccounts?: boolean;\n enableOnramp?: boolean;\n apiKeyId?: string;\n apiKeySecret?: string;\n}): void {\n const writeFileToTarget = (file: string, content?: string) => {\n const targetPath = path.join(root, fileRenames[file] ?? file);\n if (content) {\n fs.writeFileSync(targetPath, content);\n } else {\n copyFileSelectively({\n filePath: path.join(templateDir, file),\n destPath: targetPath,\n useSmartAccounts,\n enableOnramp,\n });\n }\n };\n\n const isNextjs = templateDir.includes(\"nextjs\");\n\n const files = fs.readdirSync(templateDir);\n for (const file of files) {\n if (file === \"package.json\") {\n const customizedPackageJson = customizePackageJson(templateDir, appName, enableOnramp);\n writeFileToTarget(file, customizedPackageJson);\n } else if (file === \"env.example\" && projectId) {\n writeFileToTarget(file);\n const customizedEnv = customizeEnv({\n templateDir,\n projectId,\n useSmartAccounts,\n apiKeyId,\n apiKeySecret,\n });\n console.log(\"Copying CDP Project ID to .env\");\n if (apiKeyId && apiKeySecret) {\n console.log(\"Copying CDP API credentials to .env\");\n }\n if (useSmartAccounts) {\n console.log(\"Configuring Smart Accounts in environment\");\n }\n writeFileToTarget(\".env\", customizedEnv);\n } else {\n writeFileToTarget(file);\n }\n }\n\n const isReactNative = templateDir.includes(\"react-native\");\n\n // Handle smart account configuration in config files\n if (useSmartAccounts && !isReactNative) {\n const configFileName = isNextjs ? \"src/components/Providers.tsx\" : \"src/config.ts\";\n const customizedConfig = customizeConfig(templateDir, useSmartAccounts, isNextjs);\n if (customizedConfig) {\n console.log(\"Configuring Smart Accounts in application config\");\n writeFileToTarget(configFileName, customizedConfig);\n }\n }\n\n // Generate the appropriate Transaction.tsx component\n if (isReactNative) {\n // For React Native, create the Transaction.tsx barrel file\n const transactionContent = generateTransactionComponent(useSmartAccounts);\n writeFileToTarget(\"Transaction.tsx\", transactionContent);\n } else {\n // For React/Next.js templates\n const transactionFileName = isNextjs ? \"src/components/Transaction.tsx\" : \"src/Transaction.tsx\";\n const transactionContent = generateTransactionComponent(useSmartAccounts);\n writeFileToTarget(transactionFileName, transactionContent);\n }\n\n // Update android/gradlew permissions so that the template is runnable\n if (isReactNative) {\n const gradlewPath = path.join(root, \"android\", \"gradlew\");\n if (fs.existsSync(gradlewPath)) {\n fs.chmodSync(gradlewPath, 0o755);\n }\n }\n}\n\n/**\n * Generate the appropriate Transaction component based on account type\n *\n * @param useSmartAccounts - Whether to use Smart Accounts\n * @returns The generated Transaction component content\n */\nfunction generateTransactionComponent(useSmartAccounts?: boolean): string {\n if (useSmartAccounts) {\n return `export { default } from \"./SmartAccountTransaction\";`;\n } else {\n return `export { default } from \"./EOATransaction\";`;\n }\n}\n\ninit().catch(e => {\n console.error(e);\n process.exit(1);\n});\n"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -0,0 +1,359 @@
|
|
|
1
|
+
import {
|
|
2
|
+
CDPHooksProvider,
|
|
3
|
+
useConfig,
|
|
4
|
+
useIsInitialized,
|
|
5
|
+
useSignInWithEmail,
|
|
6
|
+
useVerifyEmailOTP,
|
|
7
|
+
useIsSignedIn,
|
|
8
|
+
useSignOut,
|
|
9
|
+
Config,
|
|
10
|
+
} from "@coinbase/cdp-hooks";
|
|
11
|
+
import { StatusBar } from "expo-status-bar";
|
|
12
|
+
import { useState } from "react";
|
|
13
|
+
import {
|
|
14
|
+
StyleSheet,
|
|
15
|
+
Text,
|
|
16
|
+
View,
|
|
17
|
+
TouchableOpacity,
|
|
18
|
+
TextInput,
|
|
19
|
+
Alert,
|
|
20
|
+
ScrollView,
|
|
21
|
+
SafeAreaView,
|
|
22
|
+
} from "react-native";
|
|
23
|
+
|
|
24
|
+
import Transaction from "./Transaction";
|
|
25
|
+
|
|
26
|
+
const cdpConfig: Config = {
|
|
27
|
+
projectId: process.env.EXPO_PUBLIC_CDP_PROJECT_ID,
|
|
28
|
+
basePath: process.env.EXPO_PUBLIC_CDP_BASE_PATH,
|
|
29
|
+
createAccountOnLogin: process.env.EXPO_PUBLIC_CDP_CREATE_ACCOUNT_TYPE,
|
|
30
|
+
useMock: process.env.EXPO_PUBLIC_CDP_USE_MOCK === "true",
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* A multi-step authentication component that handles email-based sign-in flow.
|
|
35
|
+
*
|
|
36
|
+
* The component manages three states:
|
|
37
|
+
* 1. Initial state: Displays a welcome screen with a sign-in button
|
|
38
|
+
* 2. Email input: Collects and validates the user's email address
|
|
39
|
+
* 3. OTP verification: Validates the one-time password sent to the user's email
|
|
40
|
+
*
|
|
41
|
+
* Features:
|
|
42
|
+
* - Email validation using regex
|
|
43
|
+
* - 6-digit OTP validation
|
|
44
|
+
* - Loading states during API calls
|
|
45
|
+
* - Error handling for failed authentication attempts
|
|
46
|
+
* - Cancelable workflow with state reset
|
|
47
|
+
*
|
|
48
|
+
* @returns {JSX.Element} The rendered sign-in form component
|
|
49
|
+
*/
|
|
50
|
+
function CDPApp() {
|
|
51
|
+
const { isInitialized } = useIsInitialized();
|
|
52
|
+
const { isSignedIn } = useIsSignedIn();
|
|
53
|
+
const { signInWithEmail } = useSignInWithEmail();
|
|
54
|
+
const { verifyEmailOTP } = useVerifyEmailOTP();
|
|
55
|
+
const { signOut } = useSignOut();
|
|
56
|
+
const { config } = useConfig();
|
|
57
|
+
|
|
58
|
+
const [email, setEmail] = useState("");
|
|
59
|
+
const [otp, setOtp] = useState("");
|
|
60
|
+
const [flowId, setFlowId] = useState("");
|
|
61
|
+
const [isLoading, setIsLoading] = useState(false);
|
|
62
|
+
|
|
63
|
+
const handleSignIn = async () => {
|
|
64
|
+
if (!email) {
|
|
65
|
+
Alert.alert("Error", "Please enter an email address");
|
|
66
|
+
return;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
setIsLoading(true);
|
|
70
|
+
try {
|
|
71
|
+
const result = await signInWithEmail({ email });
|
|
72
|
+
setFlowId(result.flowId);
|
|
73
|
+
Alert.alert("Success", "OTP sent to your email!");
|
|
74
|
+
} catch (error) {
|
|
75
|
+
Alert.alert("Error", error instanceof Error ? error.message : "Failed to sign in");
|
|
76
|
+
} finally {
|
|
77
|
+
setIsLoading(false);
|
|
78
|
+
}
|
|
79
|
+
};
|
|
80
|
+
|
|
81
|
+
const handleVerifyOTP = async () => {
|
|
82
|
+
if (!otp || !flowId) {
|
|
83
|
+
Alert.alert("Error", "Please enter the OTP");
|
|
84
|
+
return;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
setIsLoading(true);
|
|
88
|
+
try {
|
|
89
|
+
await verifyEmailOTP({ flowId, otp });
|
|
90
|
+
Alert.alert("Success", "Successfully signed in!");
|
|
91
|
+
setOtp("");
|
|
92
|
+
setFlowId("");
|
|
93
|
+
} catch (error) {
|
|
94
|
+
Alert.alert("Error", error instanceof Error ? error.message : "Failed to verify OTP");
|
|
95
|
+
} finally {
|
|
96
|
+
setIsLoading(false);
|
|
97
|
+
}
|
|
98
|
+
};
|
|
99
|
+
|
|
100
|
+
const handleSignOut = async () => {
|
|
101
|
+
try {
|
|
102
|
+
await signOut();
|
|
103
|
+
setEmail("");
|
|
104
|
+
setOtp("");
|
|
105
|
+
setFlowId("");
|
|
106
|
+
Alert.alert("Success", "Signed out successfully");
|
|
107
|
+
} catch (error) {
|
|
108
|
+
Alert.alert("Error", error instanceof Error ? error.message : "Failed to sign out");
|
|
109
|
+
}
|
|
110
|
+
};
|
|
111
|
+
|
|
112
|
+
if (!isInitialized) {
|
|
113
|
+
return (
|
|
114
|
+
<SafeAreaView style={styles.container}>
|
|
115
|
+
<View style={styles.centerContent}>
|
|
116
|
+
<Text style={styles.text}>Initializing CDP...</Text>
|
|
117
|
+
</View>
|
|
118
|
+
<StatusBar style="auto" />
|
|
119
|
+
</SafeAreaView>
|
|
120
|
+
);
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
return (
|
|
124
|
+
<SafeAreaView style={styles.container}>
|
|
125
|
+
<View style={styles.header}>
|
|
126
|
+
<Text style={styles.title}>CDP React Native Demo</Text>
|
|
127
|
+
<Text style={styles.subtitle}>Project ID: {config.projectId}</Text>
|
|
128
|
+
</View>
|
|
129
|
+
|
|
130
|
+
<View style={styles.content}>
|
|
131
|
+
{!isSignedIn ? (
|
|
132
|
+
<View style={styles.authContainer}>
|
|
133
|
+
<Text style={styles.label}>Email:</Text>
|
|
134
|
+
<TextInput
|
|
135
|
+
style={styles.input}
|
|
136
|
+
value={email}
|
|
137
|
+
onChangeText={setEmail}
|
|
138
|
+
placeholder="Enter your email"
|
|
139
|
+
keyboardType="email-address"
|
|
140
|
+
autoCapitalize="none"
|
|
141
|
+
autoCorrect={false}
|
|
142
|
+
editable={!isLoading}
|
|
143
|
+
/>
|
|
144
|
+
|
|
145
|
+
<TouchableOpacity
|
|
146
|
+
style={[styles.button, isLoading && styles.buttonDisabled]}
|
|
147
|
+
onPress={handleSignIn}
|
|
148
|
+
disabled={isLoading}
|
|
149
|
+
>
|
|
150
|
+
<Text style={styles.buttonText}>
|
|
151
|
+
{isLoading ? "Sending..." : "Sign In with Email"}
|
|
152
|
+
</Text>
|
|
153
|
+
</TouchableOpacity>
|
|
154
|
+
|
|
155
|
+
{flowId && (
|
|
156
|
+
<>
|
|
157
|
+
<Text style={styles.label}>Enter OTP from email:</Text>
|
|
158
|
+
<TextInput
|
|
159
|
+
style={styles.input}
|
|
160
|
+
value={otp}
|
|
161
|
+
onChangeText={setOtp}
|
|
162
|
+
placeholder="Enter 6-digit OTP"
|
|
163
|
+
keyboardType="number-pad"
|
|
164
|
+
maxLength={6}
|
|
165
|
+
editable={!isLoading}
|
|
166
|
+
/>
|
|
167
|
+
|
|
168
|
+
<TouchableOpacity
|
|
169
|
+
style={[styles.button, isLoading && styles.buttonDisabled]}
|
|
170
|
+
onPress={handleVerifyOTP}
|
|
171
|
+
disabled={isLoading}
|
|
172
|
+
>
|
|
173
|
+
<Text style={styles.buttonText}>{isLoading ? "Verifying..." : "Verify OTP"}</Text>
|
|
174
|
+
</TouchableOpacity>
|
|
175
|
+
</>
|
|
176
|
+
)}
|
|
177
|
+
</View>
|
|
178
|
+
) : (
|
|
179
|
+
<ScrollView
|
|
180
|
+
style={styles.scrollView}
|
|
181
|
+
showsVerticalScrollIndicator={false}
|
|
182
|
+
contentContainerStyle={styles.scrollContent}
|
|
183
|
+
>
|
|
184
|
+
<View style={styles.userContainer}>
|
|
185
|
+
<Text style={styles.welcomeText}>Welcome!</Text>
|
|
186
|
+
|
|
187
|
+
<Transaction onSuccess={() => console.log("Transaction successful!")} />
|
|
188
|
+
|
|
189
|
+
<TouchableOpacity style={styles.signOutButton} onPress={handleSignOut}>
|
|
190
|
+
<Text style={styles.buttonText}>Sign Out</Text>
|
|
191
|
+
</TouchableOpacity>
|
|
192
|
+
</View>
|
|
193
|
+
</ScrollView>
|
|
194
|
+
)}
|
|
195
|
+
</View>
|
|
196
|
+
|
|
197
|
+
<StatusBar style="auto" />
|
|
198
|
+
</SafeAreaView>
|
|
199
|
+
);
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
/**
|
|
203
|
+
* The main component that wraps the CDPApp component and provides the CDPHooksProvider.
|
|
204
|
+
*
|
|
205
|
+
* @returns {JSX.Element} The rendered main component
|
|
206
|
+
*/
|
|
207
|
+
export default function App() {
|
|
208
|
+
return (
|
|
209
|
+
<CDPHooksProvider config={cdpConfig}>
|
|
210
|
+
<CDPApp />
|
|
211
|
+
</CDPHooksProvider>
|
|
212
|
+
);
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
const styles = StyleSheet.create({
|
|
216
|
+
container: {
|
|
217
|
+
flex: 1,
|
|
218
|
+
backgroundColor: "#eaeaea",
|
|
219
|
+
},
|
|
220
|
+
centerContent: {
|
|
221
|
+
flex: 1,
|
|
222
|
+
justifyContent: "center",
|
|
223
|
+
alignItems: "center",
|
|
224
|
+
padding: 20,
|
|
225
|
+
},
|
|
226
|
+
header: {
|
|
227
|
+
paddingHorizontal: 20,
|
|
228
|
+
paddingVertical: 16,
|
|
229
|
+
backgroundColor: "#ffffff",
|
|
230
|
+
alignItems: "center",
|
|
231
|
+
borderBottomWidth: 1,
|
|
232
|
+
borderBottomColor: "#dcdcdc",
|
|
233
|
+
},
|
|
234
|
+
content: {
|
|
235
|
+
flex: 1,
|
|
236
|
+
paddingHorizontal: 20,
|
|
237
|
+
justifyContent: "center",
|
|
238
|
+
},
|
|
239
|
+
scrollView: {
|
|
240
|
+
flex: 1,
|
|
241
|
+
},
|
|
242
|
+
scrollContent: {
|
|
243
|
+
paddingVertical: 20,
|
|
244
|
+
},
|
|
245
|
+
title: {
|
|
246
|
+
fontSize: 24,
|
|
247
|
+
fontWeight: "bold",
|
|
248
|
+
marginBottom: 10,
|
|
249
|
+
textAlign: "center",
|
|
250
|
+
color: "#111111",
|
|
251
|
+
},
|
|
252
|
+
subtitle: {
|
|
253
|
+
fontSize: 14,
|
|
254
|
+
color: "#757575",
|
|
255
|
+
marginBottom: 30,
|
|
256
|
+
textAlign: "center",
|
|
257
|
+
},
|
|
258
|
+
text: {
|
|
259
|
+
fontSize: 16,
|
|
260
|
+
textAlign: "center",
|
|
261
|
+
color: "#111111",
|
|
262
|
+
},
|
|
263
|
+
authContainer: {
|
|
264
|
+
width: "100%",
|
|
265
|
+
maxWidth: 300,
|
|
266
|
+
alignSelf: "center",
|
|
267
|
+
},
|
|
268
|
+
userContainer: {
|
|
269
|
+
width: "100%",
|
|
270
|
+
alignItems: "center",
|
|
271
|
+
marginBottom: 20,
|
|
272
|
+
},
|
|
273
|
+
copyableInfoContainer: {
|
|
274
|
+
width: "100%",
|
|
275
|
+
marginBottom: 16,
|
|
276
|
+
alignItems: "center",
|
|
277
|
+
},
|
|
278
|
+
infoLabel: {
|
|
279
|
+
fontSize: 14,
|
|
280
|
+
fontWeight: "600",
|
|
281
|
+
color: "#757575",
|
|
282
|
+
marginBottom: 6,
|
|
283
|
+
textAlign: "center",
|
|
284
|
+
},
|
|
285
|
+
copyableInfo: {
|
|
286
|
+
backgroundColor: "rgba(0, 128, 128, 0.1)",
|
|
287
|
+
borderRadius: 8,
|
|
288
|
+
padding: 12,
|
|
289
|
+
borderWidth: 1,
|
|
290
|
+
borderColor: "rgba(0, 128, 128, 0.3)",
|
|
291
|
+
width: "100%",
|
|
292
|
+
maxWidth: 280,
|
|
293
|
+
},
|
|
294
|
+
copyableText: {
|
|
295
|
+
fontSize: 12,
|
|
296
|
+
fontFamily: "monospace",
|
|
297
|
+
color: "#008080",
|
|
298
|
+
textAlign: "center",
|
|
299
|
+
marginBottom: 4,
|
|
300
|
+
},
|
|
301
|
+
copyHint: {
|
|
302
|
+
fontSize: 10,
|
|
303
|
+
color: "#008080",
|
|
304
|
+
fontStyle: "italic",
|
|
305
|
+
textAlign: "center",
|
|
306
|
+
},
|
|
307
|
+
welcomeText: {
|
|
308
|
+
fontSize: 20,
|
|
309
|
+
fontWeight: "bold",
|
|
310
|
+
marginBottom: 20,
|
|
311
|
+
textAlign: "center",
|
|
312
|
+
color: "#111111",
|
|
313
|
+
},
|
|
314
|
+
userInfo: {
|
|
315
|
+
fontSize: 14,
|
|
316
|
+
marginBottom: 8,
|
|
317
|
+
textAlign: "center",
|
|
318
|
+
color: "#111111",
|
|
319
|
+
},
|
|
320
|
+
label: {
|
|
321
|
+
fontSize: 16,
|
|
322
|
+
fontWeight: "500",
|
|
323
|
+
marginBottom: 8,
|
|
324
|
+
color: "#111111",
|
|
325
|
+
},
|
|
326
|
+
input: {
|
|
327
|
+
borderWidth: 1,
|
|
328
|
+
borderColor: "#dcdcdc",
|
|
329
|
+
borderRadius: 8,
|
|
330
|
+
padding: 12,
|
|
331
|
+
fontSize: 16,
|
|
332
|
+
marginBottom: 16,
|
|
333
|
+
backgroundColor: "#ffffff",
|
|
334
|
+
color: "#111111",
|
|
335
|
+
},
|
|
336
|
+
button: {
|
|
337
|
+
backgroundColor: "#008080",
|
|
338
|
+
padding: 15,
|
|
339
|
+
borderRadius: 8,
|
|
340
|
+
alignItems: "center",
|
|
341
|
+
marginBottom: 12,
|
|
342
|
+
},
|
|
343
|
+
buttonDisabled: {
|
|
344
|
+
opacity: 0.6,
|
|
345
|
+
},
|
|
346
|
+
buttonText: {
|
|
347
|
+
color: "#ffffff",
|
|
348
|
+
fontSize: 16,
|
|
349
|
+
fontWeight: "600",
|
|
350
|
+
},
|
|
351
|
+
signOutButton: {
|
|
352
|
+
backgroundColor: "#f44336",
|
|
353
|
+
padding: 15,
|
|
354
|
+
borderRadius: 8,
|
|
355
|
+
alignItems: "center",
|
|
356
|
+
marginTop: 20,
|
|
357
|
+
width: "100%",
|
|
358
|
+
},
|
|
359
|
+
});
|