@coinbase/create-cdp-app 0.0.0 → 0.0.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +51 -0
- package/dist/index.js +204 -0
- package/dist/index.js.map +1 -0
- package/package.json +31 -6
- package/template-react-components/README.md +72 -0
- package/template-react-components/_gitignore +28 -0
- package/template-react-components/env.example +3 -0
- package/template-react-components/eslint.config.js +23 -0
- package/template-react-components/index.html +13 -0
- package/template-react-components/package.json +32 -0
- package/template-react-components/pnpm-lock.yaml +3116 -0
- package/template-react-components/public/vite.svg +1 -0
- package/template-react-components/src/App.tsx +27 -0
- package/template-react-components/src/Transaction.tsx +85 -0
- package/template-react-components/src/config.ts +3 -0
- package/template-react-components/src/index.css +119 -0
- package/template-react-components/src/main.tsx +15 -0
- package/template-react-components/src/vite-env.d.ts +1 -0
- package/template-react-components/tsconfig.app.json +27 -0
- package/template-react-components/tsconfig.json +7 -0
- package/template-react-components/tsconfig.node.json +25 -0
- package/template-react-components/vite.config.ts +9 -0
- package/index.js +0 -1
package/README.md
ADDED
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
This package contains the `@coinbase/create-cdp-app` CLI command, which allows developers to quickly bootstrap a React App using the CDP Web SDK.
|
|
2
|
+
|
|
3
|
+
## Quickstart
|
|
4
|
+
|
|
5
|
+
This guide will help you get started with `@coinbase/create-cdp-app`. You'll learn how to create a new CDP-enabled React application with just a few commands.
|
|
6
|
+
|
|
7
|
+
### Prerequisites
|
|
8
|
+
|
|
9
|
+
Before you begin, make sure you have one of the following package managers installed:
|
|
10
|
+
- [pnpm](https://pnpm.io/) (recommended)
|
|
11
|
+
- [npm](https://www.npmjs.com/)
|
|
12
|
+
- [yarn](https://yarnpkg.com/)
|
|
13
|
+
|
|
14
|
+
Gather your project ID and whitelist your app from the CDP Portal:
|
|
15
|
+
1. Sign in or create an account on the [CDP Portal](https://portal.cdp.coinbase.com)
|
|
16
|
+
2. Copy your Project ID from the dashboard
|
|
17
|
+
3. Go to the [Embedded Wallets CORS settings](https://portal.cdp.coinbase.com/products/embedded-wallets/cors)
|
|
18
|
+
4. Click add origin and whitelist `http://localhost:3000` (or wherever your app will run)
|
|
19
|
+
|
|
20
|
+
### Create Your First CDP App
|
|
21
|
+
|
|
22
|
+
You can create a new CDP app using any of these methods:
|
|
23
|
+
|
|
24
|
+
```bash
|
|
25
|
+
# Using pnpm
|
|
26
|
+
pnpm create @coinbase/cdp-app
|
|
27
|
+
|
|
28
|
+
# Using npm
|
|
29
|
+
npm create @coinbase/cdp-app@latest
|
|
30
|
+
|
|
31
|
+
# Using yarn
|
|
32
|
+
yarn create @coinbase/cdp-app
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
The CLI will guide you through the setup process:
|
|
36
|
+
1. Enter your project name (defaults to "cdp-app")
|
|
37
|
+
2. Select a template (currently supports "React Components")
|
|
38
|
+
3. Enter your CDP Project ID
|
|
39
|
+
4. Confirm you have whitelisted your app domain on the CDP Portal
|
|
40
|
+
5. Confirm directory overwrite if needed
|
|
41
|
+
|
|
42
|
+
## Available Templates
|
|
43
|
+
|
|
44
|
+
Currently, `create-cdp-app` offers the following template:
|
|
45
|
+
|
|
46
|
+
- **React Components** (`react-components`): A full-featured React application template that includes:
|
|
47
|
+
- Vite for fast development and building
|
|
48
|
+
- TypeScript for type safety
|
|
49
|
+
- CDP React components for authentication
|
|
50
|
+
- Example transaction components
|
|
51
|
+
- Base Sepolia integration
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,204 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import fs from "node:fs";
|
|
3
|
+
import path from "node:path";
|
|
4
|
+
import { fileURLToPath } from "node:url";
|
|
5
|
+
import { reset, blue, red, green } from "kolorist";
|
|
6
|
+
import prompts from "prompts";
|
|
7
|
+
function prepareProjectDirectory(targetDir, shouldOverwrite) {
|
|
8
|
+
const root = path.join(process.cwd(), targetDir);
|
|
9
|
+
if (!fs.existsSync(root)) {
|
|
10
|
+
fs.mkdirSync(root, { recursive: true });
|
|
11
|
+
}
|
|
12
|
+
return root;
|
|
13
|
+
}
|
|
14
|
+
function customizePackageJson(templateDir, projectName) {
|
|
15
|
+
const packageJsonPath = path.join(templateDir, "package.json");
|
|
16
|
+
const packageJson = JSON.parse(fs.readFileSync(packageJsonPath, "utf-8"));
|
|
17
|
+
packageJson.name = projectName;
|
|
18
|
+
return JSON.stringify(packageJson, null, 2) + "\n";
|
|
19
|
+
}
|
|
20
|
+
function customizeEnv(templateDir, projectId) {
|
|
21
|
+
const exampleEnvPath = path.join(templateDir, "env.example");
|
|
22
|
+
const exampleEnv = fs.readFileSync(exampleEnvPath, "utf-8");
|
|
23
|
+
const envContent = exampleEnv.replace(
|
|
24
|
+
/VITE_CDP_PROJECT_ID=.*(\r?\n|$)/,
|
|
25
|
+
`VITE_CDP_PROJECT_ID=${projectId}
|
|
26
|
+
`
|
|
27
|
+
);
|
|
28
|
+
return envContent;
|
|
29
|
+
}
|
|
30
|
+
function copyFile(filePath, destPath) {
|
|
31
|
+
const stat = fs.statSync(filePath);
|
|
32
|
+
if (stat.isDirectory()) {
|
|
33
|
+
copyDir(filePath, destPath);
|
|
34
|
+
} else {
|
|
35
|
+
fs.copyFileSync(filePath, destPath);
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
function copyDir(srcDir, destDir) {
|
|
39
|
+
fs.mkdirSync(destDir, { recursive: true });
|
|
40
|
+
for (const file of fs.readdirSync(srcDir)) {
|
|
41
|
+
const srcFile = path.resolve(srcDir, file);
|
|
42
|
+
const destFile = path.resolve(destDir, file);
|
|
43
|
+
copyFile(srcFile, destFile);
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
function isDirEmpty(dirPath) {
|
|
47
|
+
const files = fs.readdirSync(dirPath);
|
|
48
|
+
return files.length === 0 || files.length === 1 && files[0] === ".git";
|
|
49
|
+
}
|
|
50
|
+
function detectPackageManager() {
|
|
51
|
+
const userAgent = process.env.npm_config_user_agent;
|
|
52
|
+
if (userAgent) {
|
|
53
|
+
if (userAgent.startsWith("yarn")) return "yarn";
|
|
54
|
+
if (userAgent.startsWith("pnpm")) return "pnpm";
|
|
55
|
+
if (userAgent.startsWith("npm")) return "npm";
|
|
56
|
+
}
|
|
57
|
+
return "npm";
|
|
58
|
+
}
|
|
59
|
+
const TEMPLATES = [
|
|
60
|
+
{
|
|
61
|
+
name: "react-components",
|
|
62
|
+
display: "React Components",
|
|
63
|
+
color: blue
|
|
64
|
+
}
|
|
65
|
+
];
|
|
66
|
+
const defaultTargetDir = "cdp-app";
|
|
67
|
+
const fileRenames = {
|
|
68
|
+
_gitignore: ".gitignore"
|
|
69
|
+
};
|
|
70
|
+
const uuidRegex = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
|
|
71
|
+
async function init() {
|
|
72
|
+
const { projectName, template, targetDirectory, projectId } = await getProjectDetails();
|
|
73
|
+
console.log(`
|
|
74
|
+
Scaffolding project in ${targetDirectory}...`);
|
|
75
|
+
const root = prepareProjectDirectory(targetDirectory);
|
|
76
|
+
const templateDir = path.resolve(fileURLToPath(import.meta.url), "../..", `template-${template}`);
|
|
77
|
+
copyTemplateFiles(templateDir, root, projectName, projectId);
|
|
78
|
+
printNextSteps(root);
|
|
79
|
+
}
|
|
80
|
+
async function getProjectDetails() {
|
|
81
|
+
let targetDir = process.argv[2];
|
|
82
|
+
const defaultProjectName = targetDir ?? defaultTargetDir;
|
|
83
|
+
try {
|
|
84
|
+
const result = await prompts(
|
|
85
|
+
[
|
|
86
|
+
{
|
|
87
|
+
type: targetDir ? null : "text",
|
|
88
|
+
name: "projectName",
|
|
89
|
+
message: reset("Project name:"),
|
|
90
|
+
initial: defaultProjectName,
|
|
91
|
+
onState: (state) => {
|
|
92
|
+
targetDir = String(state.value).trim() || defaultProjectName;
|
|
93
|
+
}
|
|
94
|
+
},
|
|
95
|
+
{
|
|
96
|
+
type: "select",
|
|
97
|
+
name: "template",
|
|
98
|
+
message: reset("Select a template:"),
|
|
99
|
+
initial: 0,
|
|
100
|
+
choices: TEMPLATES.map((template) => ({
|
|
101
|
+
title: template.color(template.display || template.name),
|
|
102
|
+
value: template.name
|
|
103
|
+
}))
|
|
104
|
+
},
|
|
105
|
+
{
|
|
106
|
+
type: "text",
|
|
107
|
+
name: "projectId",
|
|
108
|
+
message: reset(
|
|
109
|
+
"CDP Project ID (Find your project ID at https://portal.cdp.coinbase.com/projects/overview):"
|
|
110
|
+
),
|
|
111
|
+
validate: (value) => {
|
|
112
|
+
if (!value || !uuidRegex.test(value)) {
|
|
113
|
+
return "Project ID is required";
|
|
114
|
+
}
|
|
115
|
+
return true;
|
|
116
|
+
},
|
|
117
|
+
initial: ""
|
|
118
|
+
},
|
|
119
|
+
{
|
|
120
|
+
type: "text",
|
|
121
|
+
name: "corsConfirmation",
|
|
122
|
+
message: reset(
|
|
123
|
+
"Confirm you have whitelisted 'http://localhost:3000' at https://portal.cdp.coinbase.com/products/embedded-wallets/cors by typing 'y'"
|
|
124
|
+
),
|
|
125
|
+
validate: (value) => {
|
|
126
|
+
if (value !== "y") {
|
|
127
|
+
return "You must whitelist your app domain for your app to be functional.";
|
|
128
|
+
}
|
|
129
|
+
return true;
|
|
130
|
+
},
|
|
131
|
+
initial: ""
|
|
132
|
+
},
|
|
133
|
+
{
|
|
134
|
+
type: () => !fs.existsSync(targetDir) || isDirEmpty(targetDir) ? null : "confirm",
|
|
135
|
+
name: "overwrite",
|
|
136
|
+
message: () => (targetDir === "." ? "Current directory" : `Target directory "${targetDir}"`) + " is not empty. Remove existing files and continue?"
|
|
137
|
+
},
|
|
138
|
+
{
|
|
139
|
+
type: (_, { overwrite }) => {
|
|
140
|
+
if (overwrite === false) {
|
|
141
|
+
throw new Error(red("✖") + " Operation cancelled");
|
|
142
|
+
}
|
|
143
|
+
return null;
|
|
144
|
+
},
|
|
145
|
+
name: "overwriteChecker"
|
|
146
|
+
}
|
|
147
|
+
],
|
|
148
|
+
{
|
|
149
|
+
onCancel: () => {
|
|
150
|
+
throw new Error(red("✖") + " Operation cancelled");
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
);
|
|
154
|
+
return {
|
|
155
|
+
projectName: result.projectName,
|
|
156
|
+
template: result.template,
|
|
157
|
+
targetDirectory: targetDir,
|
|
158
|
+
projectId: result.projectId
|
|
159
|
+
};
|
|
160
|
+
} catch (cancelled) {
|
|
161
|
+
if (cancelled instanceof Error) {
|
|
162
|
+
console.log(cancelled.message);
|
|
163
|
+
}
|
|
164
|
+
process.exit(0);
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
function printNextSteps(projectRoot) {
|
|
168
|
+
const packageManager = detectPackageManager();
|
|
169
|
+
console.log(green("\nDone. Now run your app:\n"));
|
|
170
|
+
if (projectRoot !== process.cwd()) {
|
|
171
|
+
console.log(`cd ${path.relative(process.cwd(), projectRoot)}`);
|
|
172
|
+
}
|
|
173
|
+
console.log(`${packageManager} install`);
|
|
174
|
+
console.log(`${packageManager} dev`);
|
|
175
|
+
}
|
|
176
|
+
function copyTemplateFiles(templateDir, root, projectName, projectId) {
|
|
177
|
+
const writeFileToTarget = (file, content) => {
|
|
178
|
+
const targetPath = path.join(root, fileRenames[file] ?? file);
|
|
179
|
+
if (content) {
|
|
180
|
+
fs.writeFileSync(targetPath, content);
|
|
181
|
+
} else {
|
|
182
|
+
copyFile(path.join(templateDir, file), targetPath);
|
|
183
|
+
}
|
|
184
|
+
};
|
|
185
|
+
const files = fs.readdirSync(templateDir);
|
|
186
|
+
for (const file of files) {
|
|
187
|
+
if (file === "package.json") {
|
|
188
|
+
const customizedPackageJson = customizePackageJson(templateDir, projectName);
|
|
189
|
+
writeFileToTarget(file, customizedPackageJson);
|
|
190
|
+
} else if (file === "env.example" && projectId) {
|
|
191
|
+
const customizedEnv = customizeEnv(templateDir, projectId);
|
|
192
|
+
writeFileToTarget(file);
|
|
193
|
+
console.log("Copying project id to .env");
|
|
194
|
+
writeFileToTarget(".env", customizedEnv);
|
|
195
|
+
} else {
|
|
196
|
+
writeFileToTarget(file);
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
init().catch((e) => {
|
|
201
|
+
console.error(e);
|
|
202
|
+
process.exit(1);
|
|
203
|
+
});
|
|
204
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +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 project directory\n *\n * @param targetDir - The target directory for the project\n * @param shouldOverwrite - Whether to overwrite the existing directory\n * @returns The path to the prepared project directory\n */\nexport function prepareProjectDirectory(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 project\n *\n * @param templateDir - The directory containing the template files\n * @param projectName - The name of the project\n * @returns The customized package.json content\n */\nexport function customizePackageJson(templateDir: string, projectName: string): string {\n const packageJsonPath = path.join(templateDir, \"package.json\");\n const packageJson = JSON.parse(fs.readFileSync(packageJsonPath, \"utf-8\"));\n packageJson.name = projectName;\n return JSON.stringify(packageJson, null, 2) + \"\\n\";\n}\n\n/**\n * Set up the .env file for the new project\n *\n * @param templateDir - The directory containing the template files\n * @param projectId - The project ID\n * @returns The customized .env content\n */\nexport function customizeEnv(templateDir: string, projectId: string): string {\n const exampleEnvPath = path.join(templateDir, \"env.example\");\n const exampleEnv = fs.readFileSync(exampleEnvPath, \"utf-8\");\n const envContent = exampleEnv.replace(\n /VITE_CDP_PROJECT_ID=.*(\\r?\\n|$)/,\n `VITE_CDP_PROJECT_ID=${projectId}\\n`,\n );\n return envContent;\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 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 * 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 { blue, red, green, reset } from \"kolorist\";\nimport prompts from \"prompts\";\n\nimport {\n prepareProjectDirectory,\n isDirEmpty,\n customizePackageJson,\n copyFile,\n customizeEnv,\n detectPackageManager,\n} from \"./utils.js\";\n\n// Available templates for project creation\nconst TEMPLATES = [\n {\n name: \"react-components\",\n display: \"React Components\",\n color: blue,\n },\n];\n\nconst defaultTargetDir = \"cdp-app\";\n\nconst fileRenames: Record<string, string | undefined> = {\n _gitignore: \".gitignore\",\n};\n\ninterface ProjectOptions {\n projectName: string;\n template: string;\n targetDirectory: string;\n projectId: 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 project\n */\nasync function init(): Promise<void> {\n const { projectName, template, targetDirectory, projectId } = await getProjectDetails();\n\n console.log(`\\nScaffolding project in ${targetDirectory}...`);\n\n const root = prepareProjectDirectory(targetDirectory, false);\n const templateDir = path.resolve(fileURLToPath(import.meta.url), \"../..\", `template-${template}`);\n\n copyTemplateFiles(templateDir, root, projectName, projectId);\n printNextSteps(root);\n}\n\n/**\n * Get project details from command line arguments or prompt the user\n *\n * @returns The project details\n */\nasync function getProjectDetails(): Promise<ProjectOptions> {\n // Get target directory from command line args (first non-option argument)\n let targetDir = process.argv[2];\n const defaultProjectName = targetDir ?? defaultTargetDir;\n\n try {\n const result = await prompts(\n [\n {\n type: targetDir ? null : \"text\",\n name: \"projectName\",\n message: reset(\"Project name:\"),\n initial: defaultProjectName,\n onState: state => {\n targetDir = String(state.value).trim() || defaultProjectName;\n },\n },\n {\n type: \"select\",\n name: \"template\",\n message: reset(\"Select a 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 || !uuidRegex.test(value)) {\n return \"Project ID is required\";\n }\n return true;\n },\n initial: \"\",\n },\n {\n type: \"text\",\n name: \"corsConfirmation\",\n message: reset(\n \"Confirm you have whitelisted 'http://localhost:3000' at https://portal.cdp.coinbase.com/products/embedded-wallets/cors by typing 'y'\",\n ),\n validate: value => {\n if (value !== \"y\") {\n return \"You must whitelist your app domain for your app to be functional.\";\n }\n return true;\n },\n initial: \"\",\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 projectName: result.projectName,\n template: result.template,\n targetDirectory: targetDir,\n projectId: result.projectId,\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 projectRoot - The root directory of the project\n */\nfunction printNextSteps(projectRoot: string): void {\n const packageManager = detectPackageManager();\n\n console.log(green(\"\\nDone. Now run your app:\\n\"));\n if (projectRoot !== process.cwd()) {\n console.log(`cd ${path.relative(process.cwd(), projectRoot)}`);\n }\n console.log(`${packageManager} install`);\n console.log(`${packageManager} dev`);\n}\n\n/**\n * Copy template files to the project directory\n *\n * @param templateDir - The directory containing the template files\n * @param root - The root directory of the project\n * @param projectName - The name of the project\n * @param projectId - The project ID\n */\nfunction copyTemplateFiles(\n templateDir: string,\n root: string,\n projectName: string,\n projectId?: 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 copyFile(path.join(templateDir, file), targetPath);\n }\n };\n\n const files = fs.readdirSync(templateDir);\n for (const file of files) {\n if (file === \"package.json\") {\n const customizedPackageJson = customizePackageJson(templateDir, projectName);\n writeFileToTarget(file, customizedPackageJson);\n } else if (file === \"env.example\" && projectId) {\n const customizedEnv = customizeEnv(templateDir, projectId);\n writeFileToTarget(file);\n console.log(\"Copying project id to .env\");\n writeFileToTarget(\".env\", customizedEnv);\n } else {\n writeFileToTarget(file);\n }\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
|
@@ -1,8 +1,33 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@coinbase/create-cdp-app",
|
|
3
|
-
"version": "0.0.
|
|
4
|
-
"
|
|
5
|
-
"main": "index.js",
|
|
6
|
-
"
|
|
7
|
-
"
|
|
8
|
-
|
|
3
|
+
"version": "0.0.6",
|
|
4
|
+
"type": "module",
|
|
5
|
+
"main": "./dist/index.js",
|
|
6
|
+
"module": "./dist/index.js",
|
|
7
|
+
"bin": {
|
|
8
|
+
"create-cdp-app": "./dist/index.js"
|
|
9
|
+
},
|
|
10
|
+
"files": [
|
|
11
|
+
"dist/**",
|
|
12
|
+
"!dist/**/*.tsbuildinfo",
|
|
13
|
+
"template-*"
|
|
14
|
+
],
|
|
15
|
+
"dependencies": {
|
|
16
|
+
"kolorist": "^1.8.0",
|
|
17
|
+
"prompts": "^2.4.2"
|
|
18
|
+
},
|
|
19
|
+
"devDependencies": {
|
|
20
|
+
"@types/prompts": "^2.4.9"
|
|
21
|
+
},
|
|
22
|
+
"scripts": {
|
|
23
|
+
"build": "pnpm run check:types && vite build --config vite.config.ts",
|
|
24
|
+
"postbuild": "chmod +x dist/index.js",
|
|
25
|
+
"build:watch": "vite build --config vite.config.ts --watch",
|
|
26
|
+
"check:types": "tsc --noEmit",
|
|
27
|
+
"clean": "rm -rf dist",
|
|
28
|
+
"clean:all": "pnpm clean && rm -rf node_modules",
|
|
29
|
+
"size-limit": "size-limit",
|
|
30
|
+
"test": "vitest",
|
|
31
|
+
"test:run": "vitest --run"
|
|
32
|
+
}
|
|
33
|
+
}
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
# CDP React App
|
|
2
|
+
|
|
3
|
+
This project was generated with [`@coinbase/create-cdp-app`](https://github.com/coinbase/cdp-web) using the React Components template.
|
|
4
|
+
|
|
5
|
+
## Project Structure
|
|
6
|
+
|
|
7
|
+
```
|
|
8
|
+
src/
|
|
9
|
+
├── App.tsx # Main application component with CDP initialization and CDP AuthButton
|
|
10
|
+
├── Transaction.tsx # Example transaction flow using CDP Hooks
|
|
11
|
+
├── assets/ # Static assets
|
|
12
|
+
├── index.css # Global styles
|
|
13
|
+
└── main.tsx # Entry point with CDP provider setup
|
|
14
|
+
```
|
|
15
|
+
|
|
16
|
+
## Getting Started
|
|
17
|
+
|
|
18
|
+
First, make sure you have your CDP Project ID:
|
|
19
|
+
|
|
20
|
+
1. Sign in or create an account on the [CDP Portal](https://portal.cdp.coinbase.com)
|
|
21
|
+
2. Copy your Project ID from the dashboard
|
|
22
|
+
3. Go to the [Embedded Wallets CORS settings](https://portal.cdp.coinbase.com/products/embedded-wallets/cors)
|
|
23
|
+
4. Click add origin and whitelist `http://localhost:3000` (or wherever your app will run)
|
|
24
|
+
|
|
25
|
+
Then, copy the `env.example` file to `.env`, and populate the `VITE_CDP_PROJECT_ID` with your project id.
|
|
26
|
+
|
|
27
|
+
Now you can start the development server:
|
|
28
|
+
|
|
29
|
+
Using npm:
|
|
30
|
+
```bash
|
|
31
|
+
# Install dependencies
|
|
32
|
+
npm install
|
|
33
|
+
|
|
34
|
+
# Start the development server
|
|
35
|
+
npm run dev
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
Using yarn:
|
|
39
|
+
```bash
|
|
40
|
+
# Install dependencies
|
|
41
|
+
yarn
|
|
42
|
+
|
|
43
|
+
# Start the development server
|
|
44
|
+
yarn dev
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
Using pnpm:
|
|
48
|
+
```bash
|
|
49
|
+
# Install dependencies
|
|
50
|
+
pnpm install
|
|
51
|
+
|
|
52
|
+
# Start the development server
|
|
53
|
+
pnpm dev
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
Visit [http://localhost:3000](http://localhost:3000) to see your app.
|
|
57
|
+
|
|
58
|
+
## Features
|
|
59
|
+
|
|
60
|
+
This template comes with:
|
|
61
|
+
- CDP React components for authentication
|
|
62
|
+
- Example transaction components for Base Sepolia
|
|
63
|
+
- Vite for fast development and building
|
|
64
|
+
- TypeScript for type safety
|
|
65
|
+
- ESLint for code quality
|
|
66
|
+
|
|
67
|
+
## Learn More
|
|
68
|
+
|
|
69
|
+
- [CDP Documentation](https://docs.cloud.coinbase.com/cdp/docs)
|
|
70
|
+
- [CDP React Documentation](https://docs.cloud.coinbase.com/cdp/docs/react-components)
|
|
71
|
+
- [CDP Portal](https://portal.cdp.coinbase.com)
|
|
72
|
+
- [Vite Documentation](https://vitejs.dev)
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
# Logs
|
|
2
|
+
logs
|
|
3
|
+
*.log
|
|
4
|
+
npm-debug.log*
|
|
5
|
+
yarn-debug.log*
|
|
6
|
+
yarn-error.log*
|
|
7
|
+
pnpm-debug.log*
|
|
8
|
+
lerna-debug.log*
|
|
9
|
+
|
|
10
|
+
node_modules
|
|
11
|
+
dist
|
|
12
|
+
dist-ssr
|
|
13
|
+
*.local
|
|
14
|
+
|
|
15
|
+
# Editor directories and files
|
|
16
|
+
.vscode/*
|
|
17
|
+
!.vscode/extensions.json
|
|
18
|
+
.idea
|
|
19
|
+
.DS_Store
|
|
20
|
+
*.suo
|
|
21
|
+
*.ntvs*
|
|
22
|
+
*.njsproj
|
|
23
|
+
*.sln
|
|
24
|
+
*.sw?
|
|
25
|
+
|
|
26
|
+
# Env files
|
|
27
|
+
.env
|
|
28
|
+
.env.*
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import js from "@eslint/js";
|
|
2
|
+
import globals from "globals";
|
|
3
|
+
import reactHooks from "eslint-plugin-react-hooks";
|
|
4
|
+
import reactRefresh from "eslint-plugin-react-refresh";
|
|
5
|
+
import tseslint from "typescript-eslint";
|
|
6
|
+
import { globalIgnores } from "eslint/config";
|
|
7
|
+
|
|
8
|
+
export default tseslint.config([
|
|
9
|
+
globalIgnores(["dist"]),
|
|
10
|
+
{
|
|
11
|
+
files: ["**/*.{ts,tsx}"],
|
|
12
|
+
extends: [
|
|
13
|
+
js.configs.recommended,
|
|
14
|
+
tseslint.configs.recommended,
|
|
15
|
+
reactHooks.configs["recommended-latest"],
|
|
16
|
+
reactRefresh.configs.vite,
|
|
17
|
+
],
|
|
18
|
+
languageOptions: {
|
|
19
|
+
ecmaVersion: 2020,
|
|
20
|
+
globals: globals.browser,
|
|
21
|
+
},
|
|
22
|
+
},
|
|
23
|
+
]);
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
<!doctype html>
|
|
2
|
+
<html lang="en">
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="UTF-8" />
|
|
5
|
+
<link rel="icon" type="image/svg+xml" href="/vite.svg" />
|
|
6
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
7
|
+
<title>Vite + React + TS</title>
|
|
8
|
+
</head>
|
|
9
|
+
<body>
|
|
10
|
+
<div id="root"></div>
|
|
11
|
+
<script type="module" src="/src/main.tsx"></script>
|
|
12
|
+
</body>
|
|
13
|
+
</html>
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "cdp-react-components",
|
|
3
|
+
"private": true,
|
|
4
|
+
"version": "0.0.0",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"scripts": {
|
|
7
|
+
"dev": "vite",
|
|
8
|
+
"build": "tsc -b && vite build",
|
|
9
|
+
"lint": "eslint .",
|
|
10
|
+
"preview": "vite preview"
|
|
11
|
+
},
|
|
12
|
+
"dependencies": {
|
|
13
|
+
"react": "^19.1.0",
|
|
14
|
+
"react-dom": "^19.1.0",
|
|
15
|
+
"@coinbase/cdp-react": "latest",
|
|
16
|
+
"@coinbase/cdp-hooks": "latest",
|
|
17
|
+
"@coinbase/cdp-core": "latest"
|
|
18
|
+
},
|
|
19
|
+
"devDependencies": {
|
|
20
|
+
"@eslint/js": "^9.30.1",
|
|
21
|
+
"@types/react": "^19.1.8",
|
|
22
|
+
"@types/react-dom": "^19.1.6",
|
|
23
|
+
"@vitejs/plugin-react": "^4.6.0",
|
|
24
|
+
"eslint": "^9.30.1",
|
|
25
|
+
"eslint-plugin-react-hooks": "^5.2.0",
|
|
26
|
+
"eslint-plugin-react-refresh": "^0.4.20",
|
|
27
|
+
"globals": "^16.3.0",
|
|
28
|
+
"typescript": "~5.8.3",
|
|
29
|
+
"typescript-eslint": "^8.35.1",
|
|
30
|
+
"vite": "^7.0.4"
|
|
31
|
+
}
|
|
32
|
+
}
|