@sei-js/create-sei 1.0.1 → 2.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (68) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +5 -4
  3. package/dist/extensions/precompiles/src/components/default/index.tsx +44 -51
  4. package/dist/main.js +112 -34
  5. package/dist/templates/next-template/.env.example +1 -1
  6. package/dist/templates/next-template/README.md +57 -27
  7. package/dist/templates/next-template/biome.json +3 -4
  8. package/dist/templates/next-template/gitignore +41 -0
  9. package/dist/templates/next-template/next.config.mjs +12 -6
  10. package/dist/templates/next-template/package.json +36 -27
  11. package/dist/templates/next-template/public/brand/powered-by-sei-light.png +0 -0
  12. package/dist/templates/next-template/public/brand/sei-lockup-light.svg +7 -0
  13. package/dist/templates/next-template/public/brand/sei-mark.png +0 -0
  14. package/dist/templates/next-template/src/app/development/page.tsx +29 -30
  15. package/dist/templates/next-template/src/app/favicon.ico/route.ts +3 -0
  16. package/dist/templates/next-template/src/app/globals.css +40 -3
  17. package/dist/templates/next-template/src/app/layout.tsx +14 -13
  18. package/dist/templates/next-template/src/app/page.tsx +3 -3
  19. package/dist/templates/next-template/src/app/resources/page.tsx +34 -51
  20. package/dist/templates/next-template/src/app/resources/resources.ts +19 -32
  21. package/dist/templates/next-template/src/components/default/examples.ts +4 -7
  22. package/dist/templates/next-template/src/components/default/index.tsx +46 -46
  23. package/dist/templates/next-template/src/components/index.ts +3 -3
  24. package/dist/templates/next-template/src/components/landing/index.tsx +19 -97
  25. package/dist/templates/next-template/src/components/providers/index.ts +1 -1
  26. package/dist/templates/next-template/src/components/providers/providers.tsx +19 -25
  27. package/dist/templates/next-template/src/components/shell/index.ts +1 -1
  28. package/dist/templates/next-template/src/components/shell/shell.tsx +14 -7
  29. package/dist/templates/next-template/src/theme.ts +1 -0
  30. package/package.json +20 -9
  31. package/.rsyncignore +0 -11
  32. package/CHANGELOG.md +0 -131
  33. package/biome.json +0 -8
  34. package/dist/extensions/precompiles/package.json +0 -44
  35. package/dist/main.test.js +0 -34
  36. package/dist/templates/next-template/public/favicon.ico +0 -0
  37. package/extensions/precompiles/package.json +0 -44
  38. package/extensions/precompiles/src/components/default/index.tsx +0 -201
  39. package/jest.config.js +0 -14
  40. package/src/main.test.ts +0 -38
  41. package/src/main.ts +0 -159
  42. package/templates/next-template/.env.example +0 -8
  43. package/templates/next-template/.eslintrc.json +0 -1
  44. package/templates/next-template/.nvmrc +0 -1
  45. package/templates/next-template/README.md +0 -54
  46. package/templates/next-template/biome.json +0 -56
  47. package/templates/next-template/next-env.d.ts +0 -5
  48. package/templates/next-template/next.config.mjs +0 -17
  49. package/templates/next-template/package.json +0 -46
  50. package/templates/next-template/postcss.config.mjs +0 -6
  51. package/templates/next-template/public/favicon.ico +0 -0
  52. package/templates/next-template/src/app/development/page.tsx +0 -260
  53. package/templates/next-template/src/app/globals.css +0 -16
  54. package/templates/next-template/src/app/layout.tsx +0 -30
  55. package/templates/next-template/src/app/page.tsx +0 -10
  56. package/templates/next-template/src/app/resources/page.tsx +0 -175
  57. package/templates/next-template/src/app/resources/resources.ts +0 -56
  58. package/templates/next-template/src/components/default/examples.ts +0 -63
  59. package/templates/next-template/src/components/default/index.tsx +0 -402
  60. package/templates/next-template/src/components/index.ts +0 -3
  61. package/templates/next-template/src/components/landing/index.tsx +0 -155
  62. package/templates/next-template/src/components/providers/index.ts +0 -1
  63. package/templates/next-template/src/components/providers/providers.tsx +0 -108
  64. package/templates/next-template/src/components/shell/index.ts +0 -1
  65. package/templates/next-template/src/components/shell/shell.tsx +0 -88
  66. package/templates/next-template/tailwind.config.js +0 -5
  67. package/templates/next-template/tsconfig.json +0 -27
  68. package/tsconfig.json +0 -19
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2024-2026 Sei Labs
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md CHANGED
@@ -9,7 +9,7 @@
9
9
 
10
10
  **Scaffold new Sei applications with pre-configured templates and tooling**
11
11
 
12
- [Documentation](https://sei-js.docs.sei.io/create-sei/welcome) • [GitHub](https://github.com/sei-protocol/sei-js) • [NPM](https://www.npmjs.com/package/@sei-js/create-sei) • [Telegram](https://t.me/+LPW_1djQwRQwMzlk)
12
+ [GitHub](https://github.com/sei-protocol/sei-js) • [NPM](https://www.npmjs.com/package/@sei-js/create-sei) • [Telegram](https://t.me/+LPW_1djQwRQwMzlk)
13
13
 
14
14
  </div>
15
15
 
@@ -19,9 +19,10 @@
19
19
  npx @sei-js/create-sei app -n my-sei-app
20
20
  ```
21
21
 
22
- ## 📚 Documentation
22
+ Generated applications require Bun 1.3.14 or newer. Their dependency overrides and smoke coverage are Bun-specific; using npm, Yarn, or pnpm can resolve a different, unverified security graph.
23
23
 
24
- For complete documentation, examples, and guides, visit:
25
- **[sei-js.docs.sei.io/create-sei](https://sei-js.docs.sei.io/create-sei/welcome)**
24
+ ## Release verification
26
25
 
26
+ Pull requests run `auto` mode in the dedicated create-sei smoke workflow. It uses a local candidate when pending Changesets computes the template's exact `@sei-js/precompiles` pin, or when a Version Packages PR has already set the local manifest to that exact version. Otherwise it validates the exact published npm version. A mismatched source tree is never relabeled as an older package.
27
27
 
28
+ After `@sei-js/precompiles` has been staged to npm, dispatch the same workflow in `registry` mode. Registry mode always leaves the generated manifest untouched and verifies npm resolves the exact template pin. Audit findings are report-only in registry mode; local candidate mode blocks only on high or critical findings while reporting lower severities.
@@ -1,27 +1,24 @@
1
- 'use client';
2
-
3
- import React from 'react';
4
- import { CodeHighlight } from '@mantine/code-highlight';
5
- import { ActionIcon, Badge, Button, Card, Collapse, Container, Flex, Group, Paper, Stack, Text, ThemeIcon, Title } from '@mantine/core';
6
- import { notifications } from '@mantine/notifications';
7
- import { BANK_PRECOMPILE_ADDRESS, VIEM_BANK_PRECOMPILE_ABI } from '@sei-js/precompiles';
8
- import { IconBulb, IconChevronDown, IconChevronUp, IconCopy, IconDatabase } from '@tabler/icons-react';
9
- import { useState } from 'react';
10
- import { type ReadContractParameters, formatEther } from 'viem';
11
- import { useAccount, usePublicClient } from 'wagmi';
1
+ "use client";
2
+
3
+ import { CodeHighlight } from "@mantine/code-highlight";
4
+ import { ActionIcon, Badge, Button, Card, Collapse, Container, Flex, Group, Paper, Stack, Text, ThemeIcon, Title } from "@mantine/core";
5
+ import { notifications } from "@mantine/notifications";
6
+ import { BANK_PRECOMPILE_ABI, BANK_PRECOMPILE_ADDRESS } from "@sei-js/precompiles";
7
+ import { IconBulb, IconChevronDown, IconChevronUp, IconCopy, IconDatabase } from "@tabler/icons-react";
8
+ import { useState } from "react";
9
+ import { usePublicClient } from "wagmi";
12
10
 
13
11
  function Examples() {
14
- const [supply, setSupply] = useState('');
12
+ const [supply, setSupply] = useState("");
15
13
  const [showCode, setShowCode] = useState(false);
16
14
 
17
15
  const publicClient = usePublicClient();
18
- const { address } = useAccount();
19
16
 
20
17
  const formatLargeSeiNumber = (num: string, decimals: number): string => {
21
18
  if (num.length > 10) {
22
19
  return `${(Number(num) / 1000000 / 1000000000).toLocaleString(navigator.language, {
23
20
  minimumFractionDigits: decimals,
24
- maximumFractionDigits: decimals
21
+ maximumFractionDigits: decimals,
25
22
  })} B`;
26
23
  }
27
24
  return (Number(num) / 1000000).toLocaleString(navigator.language);
@@ -31,17 +28,17 @@ function Examples() {
31
28
  try {
32
29
  navigator.clipboard.writeText(text).then(() => {
33
30
  notifications.show({
34
- title: 'Copied!',
35
- message: 'Code copied to clipboard',
36
- color: 'green'
31
+ title: "Copied!",
32
+ message: "Code copied to clipboard",
33
+ color: "green",
37
34
  });
38
35
  });
39
36
  } catch (err) {
40
- console.error('Failed to copy text: ', err);
37
+ console.error("Failed to copy text: ", err);
41
38
  notifications.show({
42
- title: 'Error',
43
- message: 'Failed to copy to clipboard',
44
- color: 'red'
39
+ title: "Error",
40
+ message: "Failed to copy to clipboard",
41
+ color: "red",
45
42
  });
46
43
  }
47
44
  };
@@ -51,42 +48,38 @@ function Examples() {
51
48
  return undefined;
52
49
  }
53
50
 
54
- const readContractParams: ReadContractParameters = {
55
- abi: VIEM_BANK_PRECOMPILE_ABI,
56
- address: BANK_PRECOMPILE_ADDRESS,
57
- functionName: 'supply',
58
- args: ['usei']
59
- };
60
-
61
51
  try {
62
- const result = await publicClient.readContract(readContractParams);
63
- setSupply((result as bigint).toString());
52
+ const result = await publicClient.readContract({
53
+ abi: BANK_PRECOMPILE_ABI,
54
+ address: BANK_PRECOMPILE_ADDRESS,
55
+ functionName: "supply",
56
+ args: ["usei"],
57
+ });
58
+ setSupply(result.toString());
64
59
  } catch (error) {
65
- console.error('Error fetching supply:', error);
60
+ console.error("Error fetching supply:", error);
66
61
  notifications.show({
67
- title: 'Error',
68
- message: 'Failed to fetch SEI supply',
69
- color: 'red'
62
+ title: "Error",
63
+ message: "Failed to fetch SEI supply",
64
+ color: "red",
70
65
  });
71
66
  }
72
67
  };
73
68
 
74
69
  const codeExample = `// Sei Precompile Example - Bank Precompile
75
- import { BANK_PRECOMPILE_ADDRESS, VIEM_BANK_PRECOMPILE_ABI } from '@sei-js/precompiles';
70
+ import { BANK_PRECOMPILE_ABI, BANK_PRECOMPILE_ADDRESS } from '@sei-js/precompiles';
76
71
  import { usePublicClient } from 'wagmi';
77
72
 
78
73
  const publicClient = usePublicClient();
79
74
 
80
75
  const getSeiSupply = async () => {
81
- const readContractParams = {
82
- abi: VIEM_BANK_PRECOMPILE_ABI,
76
+ const result = await publicClient?.readContract({
77
+ abi: BANK_PRECOMPILE_ABI,
83
78
  address: BANK_PRECOMPILE_ADDRESS,
84
79
  functionName: 'supply',
85
80
  args: ['usei'],
86
- };
87
-
88
- const result = await publicClient?.readContract(readContractParams);
89
- return (result as bigint).toString();
81
+ });
82
+ return result?.toString();
90
83
  };`;
91
84
 
92
85
  return (
@@ -95,11 +88,11 @@ const getSeiSupply = async () => {
95
88
  {/* Header */}
96
89
  <div>
97
90
  <Group gap="md" align="center" mb="lg">
98
- <ThemeIcon size={48} radius="md" c="orange" variant="light">
91
+ <ThemeIcon size={48} radius="md" variant="outline" color="seiMaroon">
99
92
  <IconBulb size={24} />
100
93
  </ThemeIcon>
101
94
  <div>
102
- <Title order={1} fw={700} c="gray.9">
95
+ <Title order={1} fw={700} c="seiMaroon.9" className="sei-heading">
103
96
  Sei Precompiles
104
97
  </Title>
105
98
  <Text size="lg" c="gray.6" mt={4}>
@@ -115,7 +108,7 @@ const getSeiSupply = async () => {
115
108
  <Flex justify="space-between" align="flex-start">
116
109
  <Stack gap="xs">
117
110
  <Group gap="md">
118
- <ThemeIcon size={40} radius="md" color="orange" variant="light">
111
+ <ThemeIcon size={40} radius="md" variant="outline" color="seiMaroon">
119
112
  <IconDatabase size={20} />
120
113
  </ThemeIcon>
121
114
  <Title order={3} fw={600} c="gray.9">
@@ -123,13 +116,13 @@ const getSeiSupply = async () => {
123
116
  </Title>
124
117
  </Group>
125
118
  <Text size="sm" c="gray.6">
126
- Query total SEI supply using Sei's native{' '}
127
- <Text component="a" href="https://www.docs.sei.io/dev-interoperability/precompiles/bank" target="_blank" c="orange" td="underline">
119
+ Query total SEI supply using Sei's native{" "}
120
+ <Text component="a" href="https://docs.sei.io/evm/precompiles/cosmwasm-precompiles/bank" target="_blank" c="seiMaroon.9" td="underline">
128
121
  Bank precompile
129
122
  </Text>
130
123
  </Text>
131
124
  </Stack>
132
- <Badge color="orange" variant="light" size="md" radius="md">
125
+ <Badge variant="outline" color="gray" size="md" radius="md">
133
126
  precompiles
134
127
  </Badge>
135
128
  </Flex>
@@ -142,7 +135,7 @@ const getSeiSupply = async () => {
142
135
  <Flex align="baseline" gap="xs">
143
136
  {supply ? (
144
137
  <>
145
- <Text fw={700} size="xl" c="gray.9" ff="monospace">
138
+ <Text fw={700} size="xl" c="seiGold.9" ff="var(--sei-font-mono)">
146
139
  {formatLargeSeiNumber(supply, 2)}
147
140
  </Text>
148
141
  <Text size="sm" c="gray.6" fw={500}>
@@ -162,8 +155,8 @@ const getSeiSupply = async () => {
162
155
  </Paper>
163
156
  )}
164
157
  </Flex>
165
- <Button onClick={getSeiSupply} size="sm" radius="md" variant="light" color="orange" leftSection={<IconDatabase size={14} />}>
166
- {supply ? 'Refresh' : 'Query Supply'}
158
+ <Button onClick={getSeiSupply} size="sm" radius="md" variant="filled" color="seiMaroon" leftSection={<IconDatabase size={14} />}>
159
+ {supply ? "Refresh" : "Query Supply"}
167
160
  </Button>
168
161
  </Flex>
169
162
  </Paper>
@@ -177,7 +170,7 @@ const getSeiSupply = async () => {
177
170
  leftSection={showCode ? <IconChevronUp size={14} /> : <IconChevronDown size={14} />}
178
171
  onClick={() => setShowCode(!showCode)}
179
172
  >
180
- {showCode ? 'Hide' : 'View'} Implementation
173
+ {showCode ? "Hide" : "View"} Implementation
181
174
  </Button>
182
175
  {showCode && (
183
176
  <ActionIcon variant="subtle" color="gray" size="sm" radius="md" onClick={() => copyToClipboard(codeExample)}>
package/dist/main.js CHANGED
@@ -1,11 +1,10 @@
1
1
  #!/usr/bin/env node
2
- import boxen from 'boxen';
3
- import inquirer from 'inquirer';
4
2
  import fs from 'node:fs';
5
- import path from 'node:path';
6
- import { dirname } from 'node:path';
3
+ import path, { dirname } from 'node:path';
7
4
  import { fileURLToPath } from 'node:url';
5
+ import boxen from 'boxen';
8
6
  import { Command } from 'commander';
7
+ import inquirer from 'inquirer';
9
8
  const __filename = fileURLToPath(import.meta.url);
10
9
  const __dirname = dirname(__filename);
11
10
  const program = new Command();
@@ -15,19 +14,21 @@ const printWelcomeMessage = () => {
15
14
  padding: 1,
16
15
  margin: 1,
17
16
  borderStyle: 'double',
18
- borderColor: '#932C23'
17
+ borderColor: '#600014'
19
18
  }));
20
19
  };
21
- function isValidDirectoryName(dirName) {
22
- const illegalRe = /[<>:"/\\|?*]/g;
23
- const windowsReservedRe = /^(con|prn|aux|nul|com[1-9]|lpt[1-9])$/i;
24
- const trailingRe = /[. ]+$/;
25
- const validNpmPackageRe = /^(?:@[a-z0-9-*~][a-z0-9-*._~]*)?[a-z0-9-~][a-z0-9-._~]*$/;
26
- if (typeof dirName !== 'string' || dirName.length === 0) {
20
+ const MAX_NPM_PACKAGE_NAME_LENGTH = 214;
21
+ const validNpmProjectNameRe = /^[a-z0-9][a-z0-9._-]*$/;
22
+ const windowsReservedNameRe = /^(?:con|prn|aux|nul|com[1-9]|lpt[1-9])(?:\.|$)/i;
23
+ function isValidProjectName(projectName) {
24
+ if (typeof projectName !== 'string' || projectName.length === 0 || projectName.length > MAX_NPM_PACKAGE_NAME_LENGTH) {
27
25
  return false;
28
26
  }
29
- // Check for illegal characters, Windows reserved names, trailing spaces/dots
30
- if (illegalRe.test(dirName) || windowsReservedRe.test(dirName) || trailingRe.test(dirName) || !validNpmPackageRe.test(dirName)) {
27
+ if (projectName === 'node_modules' ||
28
+ projectName === 'favicon.ico' ||
29
+ projectName.endsWith('.') ||
30
+ windowsReservedNameRe.test(projectName) ||
31
+ !validNpmProjectNameRe.test(projectName)) {
31
32
  return false;
32
33
  }
33
34
  return true;
@@ -35,18 +36,28 @@ function isValidDirectoryName(dirName) {
35
36
  const validateOptions = (options) => {
36
37
  let valid = true;
37
38
  if (options.name) {
38
- if (!isValidDirectoryName(options.name)) {
39
- console.log('Invalid package name. Please use a valid npm package name.');
39
+ if (!isValidProjectName(options.name)) {
40
+ console.error('Invalid package name. Please use a valid npm package name.');
40
41
  valid = false;
41
42
  }
42
43
  }
43
44
  return valid;
44
45
  };
46
+ const extensionsPath = path.join(__dirname, 'extensions');
47
+ function printCliError(error) {
48
+ if (process.env.DEBUG && error instanceof Error) {
49
+ console.error(error.stack ?? error);
50
+ return;
51
+ }
52
+ console.error('An error occurred:', error instanceof Error ? error.message : error);
53
+ }
54
+ async function discoverExtensions() {
55
+ const extensions = await fs.promises.readdir(extensionsPath, { withFileTypes: true });
56
+ return extensions.filter((dirent) => dirent.isDirectory()).map((dirent) => dirent.name);
57
+ }
45
58
  async function listExtensions() {
46
- const extensionsPath = path.join(__dirname, 'extensions');
47
59
  try {
48
- const extensions = await fs.promises.readdir(extensionsPath, { withFileTypes: true });
49
- const extensionDirs = extensions.filter((dirent) => dirent.isDirectory()).map((dirent) => dirent.name);
60
+ const extensionDirs = await discoverExtensions();
50
61
  if (extensionDirs.length === 0) {
51
62
  console.log('No extensions available.');
52
63
  return;
@@ -56,15 +67,67 @@ async function listExtensions() {
56
67
  console.log(` - ${ext}`);
57
68
  }
58
69
  }
59
- catch (error) {
70
+ catch (_error) {
60
71
  console.log('No extensions directory found.');
61
72
  }
62
73
  }
74
+ function isDirectChildName(name) {
75
+ return name.length > 0 && name !== '.' && name !== '..' && !name.includes('/') && !name.includes('\\') && !name.includes('\0');
76
+ }
77
+ async function resolveExtension(extension) {
78
+ if (!extension) {
79
+ return {};
80
+ }
81
+ if (!isDirectChildName(extension)) {
82
+ throw new Error(`Invalid extension '${extension}'. Extensions must be selected by name from list-extensions.`);
83
+ }
84
+ let extensionDirs;
85
+ try {
86
+ extensionDirs = await discoverExtensions();
87
+ }
88
+ catch (error) {
89
+ if (error.code !== 'ENOENT') {
90
+ throw error;
91
+ }
92
+ extensionDirs = [];
93
+ }
94
+ if (!extensionDirs.includes(extension)) {
95
+ return { warning: `Warning: Extension '${extension}' not found. Continuing with base template.` };
96
+ }
97
+ return {
98
+ extension: {
99
+ name: extension,
100
+ source: path.join(extensionsPath, extension)
101
+ }
102
+ };
103
+ }
104
+ async function copyDirectoryContents(source, destination, overwrite) {
105
+ const entries = await fs.promises.readdir(source, { withFileTypes: true });
106
+ for (const entry of entries) {
107
+ await fs.promises.cp(path.join(source, entry.name), path.join(destination, entry.name), {
108
+ recursive: true,
109
+ force: overwrite,
110
+ errorOnExist: !overwrite
111
+ });
112
+ }
113
+ }
114
+ async function writeProjectName(destination, projectName) {
115
+ const manifestPath = path.join(destination, 'package.json');
116
+ const manifest = JSON.parse(await fs.promises.readFile(manifestPath, 'utf8'));
117
+ manifest.name = projectName;
118
+ await fs.promises.writeFile(manifestPath, `${JSON.stringify(manifest, null, '\t')}\n`);
119
+ }
63
120
  async function runWizard(options) {
64
121
  if (!validateOptions(options)) {
122
+ process.exitCode = 1;
65
123
  return;
66
124
  }
125
+ const extensionResolution = await resolveExtension(options.extension);
67
126
  printWelcomeMessage();
127
+ if (extensionResolution.warning) {
128
+ console.warn(extensionResolution.warning);
129
+ }
130
+ const extension = extensionResolution.extension;
68
131
  let dAppName = '';
69
132
  if (options.name) {
70
133
  dAppName = options.name;
@@ -76,7 +139,7 @@ async function runWizard(options) {
76
139
  name: 'dAppName',
77
140
  message: 'What is your dApp (project) name?',
78
141
  validate: (input) => {
79
- return isValidDirectoryName(input) || 'Invalid package name. Please use a valid npm package name.';
142
+ return isValidProjectName(input) || 'Invalid package name. Please use a valid npm package name.';
80
143
  }
81
144
  }
82
145
  ]);
@@ -86,34 +149,48 @@ async function runWizard(options) {
86
149
  const templateName = 'next-template';
87
150
  const templatePath = path.join(__dirname, 'templates', templateName);
88
151
  const dst = path.join(process.cwd(), dAppName);
89
- await fs.promises.cp(templatePath, dst, { recursive: true });
90
- // Apply extension if specified
91
- if (options.extension) {
92
- const extensionPath = path.join(__dirname, 'extensions', options.extension);
152
+ let destinationCreated = false;
153
+ try {
93
154
  try {
94
- await fs.promises.access(extensionPath);
95
- await fs.promises.cp(extensionPath, dst, { recursive: true });
96
- console.log(`Applied extension: ${options.extension}`);
155
+ await fs.promises.mkdir(dst);
156
+ destinationCreated = true;
97
157
  }
98
158
  catch (error) {
99
- console.log(`Warning: Extension '${options.extension}' not found. Continuing with base template.`);
159
+ if (error.code === 'EEXIST') {
160
+ throw new Error(`Destination '${dAppName}' already exists. Choose a different project name.`);
161
+ }
162
+ throw error;
163
+ }
164
+ await copyDirectoryContents(templatePath, dst, false);
165
+ if (extension) {
166
+ await copyDirectoryContents(extension.source, dst, true);
167
+ console.log(`Applied extension: ${extension.name}`);
168
+ }
169
+ await fs.promises.rename(path.join(dst, 'gitignore'), path.join(dst, '.gitignore'));
170
+ await writeProjectName(dst, dAppName);
171
+ }
172
+ catch (error) {
173
+ if (destinationCreated) {
174
+ await fs.promises.rm(dst, { recursive: true, force: true });
100
175
  }
176
+ throw error;
101
177
  }
102
- const extensionText = options.extension ? ` with ${options.extension} extension` : '';
178
+ const extensionText = extension ? ` with ${extension.name} extension` : '';
103
179
  console.log(`Project setup complete! Using template ${templateName}${extensionText}\n`);
104
- console.log(`To start your app, run: \n > cd ${dAppName} \n > pnpm \n > pnpm dev\n`);
180
+ console.log(`To start your app, run: \n > cd "./${dAppName}" \n > bun install \n > bun run dev\n`);
105
181
  }
106
182
  program
107
183
  .command('app')
108
184
  .description('Create a new SEI dApp')
109
- .option('--name <name>', 'Specify the name of your dApp. Name must be a valid package name.')
185
+ .option('-n, --name <name>', 'Specify the name of your dApp. Name must be a valid package name.')
110
186
  .option('--extension <extension>', 'Specify an extension to apply to the base template')
111
187
  .action(async (options) => {
112
188
  try {
113
189
  await runWizard(options);
114
190
  }
115
191
  catch (error) {
116
- console.error('An error occurred:', error);
192
+ printCliError(error);
193
+ process.exitCode = 1;
117
194
  }
118
195
  });
119
196
  program
@@ -124,7 +201,8 @@ program
124
201
  await listExtensions();
125
202
  }
126
203
  catch (error) {
127
- console.error('An error occurred:', error);
204
+ printCliError(error);
205
+ process.exitCode = 1;
128
206
  }
129
207
  });
130
- program.parse(process.argv);
208
+ await program.parseAsync(process.argv);
@@ -1,5 +1,5 @@
1
1
  # Chain configuration
2
- # Options: mainnet, testnet, devnet
2
+ # Options: mainnet, testnet
3
3
  # Defaults to mainnet if not set; this example overrides it to testnet
4
4
  NEXT_PUBLIC_CHAIN=testnet
5
5
 
@@ -1,54 +1,84 @@
1
- This is a [Next.js](https://nextjs.org/) project bootstrapped with [`create-next-app`](https://github.com/vercel/next.js/tree/canary/packages/create-next-app).
1
+ # Sei dApp starter
2
2
 
3
- ## Getting Started
3
+ This app was generated by `@sei-js/create-sei`. It uses Next.js, Wagmi, Viem, RainbowKit, Mantine, and Tailwind CSS.
4
+
5
+ ## Package manager
6
+
7
+ Bun 1.3.14 or newer is required for this starter. Its security overrides and release smoke are validated with Bun; npm, Yarn, and pnpm do not share one override contract and may silently resolve a different dependency graph.
8
+
9
+ The generated project intentionally does not ship a lockfile. Exact direct pins reduce drift, but transitive dependencies are resolved when you run `bun install`; the dedicated smoke validates the current graph rather than claiming every future install is byte-for-byte reproducible. Commit the resulting `bun.lock` after your first reviewed install.
10
+
11
+ ## Run locally
12
+
13
+ Install the dependencies and start the development server:
4
14
 
5
- Install dependencies by running
6
15
  ```bash
7
16
  bun install
17
+ bun run dev
8
18
  ```
9
19
 
10
- Then, run the development server:
20
+ Open [http://localhost:3000](http://localhost:3000).
21
+
22
+ ## Check and build
23
+
24
+ Run `bun run check` before building. If it reports fixable issues, run `bun run check:fix`. The build's prebuild hook runs the same check.
11
25
 
12
26
  ```bash
13
- bun run dev
27
+ bun run check
28
+ bun run build
14
29
  ```
15
30
 
16
- Open [http://localhost:3000](http://localhost:3000) with your browser to see the result.
31
+ ## Image handling
32
+
33
+ Next.js image optimization is disabled so the generated app needs no native Sharp build. Turning it on is a template choice rather than a security tradeoff.
17
34
 
18
- You can start editing the page by modifying `src/app/page.tsx`. The page auto-updates as you edit the file.
35
+ The manifest's `sharp` override is a separate matter and has to stay. The pinned Next 15 line declares Sharp `^0.34.3 || ^0.35.4`, and the `0.34.x` half of that range is still inside [GHSA-rgj7-g3m4-5g8c](https://github.com/advisories/GHSA-rgj7-g3m4-5g8c), so `0.35.4` is the advisory floor. Loosening or removing the override reintroduces a high audit finding whether or not optimization is enabled.
19
36
 
20
- This project uses [`next/font`](https://nextjs.org/docs/basic-features/font-optimization) to automatically optimize and load Inter, a custom Google Font.
37
+ ## Development bundler
21
38
 
22
- ## Repository Basics
23
- This repository includes basic features that allow you to connect to a wallet and use the wallet to interact with the chain.
39
+ `bun run dev` uses the Next 15 webpack default. Do not add `--turbopack`: the pinned Next 15 line cannot express the required `false` alias for MetaMask SDK's mobile-only storage import in Turbopack without adding a synthetic replacement module. The production build and smoke use the validated webpack alias in `next.config.mjs`.
24
40
 
25
- Additionally, basic examples and links to documentation are provided on the example homepage in `src/app/components/Homepage`.
41
+ ## Sei brand
26
42
 
27
- This repository uses helper methods from [@sei-js/evm/viem](https://github.com/sei-protocol/sei-js/tree/main/packages/evm/src/viem)
43
+ The assets in `public/brand` come unchanged from the [official Sei brand kit](https://brand.sei.io). The primary lockup is used in the header, the horizontal “Powered by Sei” asset appears in the footer, and the standalone mark is reserved for app-icon metadata. Preserve their proportions and clearspace; do not redraw or recolor them.
28
44
 
29
- ### Web3 Provider
30
- This component is a Sei specific wrapper around a Wagmi Provider that should wrap the application. Wrapping the application in this provider will enable it to access hooks in Wagmi. These hooks are enable connection to a wallet and interaction with the chain.
45
+ Black and white are the base palette. `--sei-maroon` (`#600014`) is reserved for primary actions and headlines, while `--sei-gold` (`#966F22`) is reserved for large or bold data and non-text highlights, not normal-size text on light surfaces. Reusable palette and typography tokens live in `src/app/globals.css`.
31
46
 
32
- In addition, this app uses [RainbowKit](https://www.rainbowkit.com/docs/wallet-button) which provides wallet connect functionality out of the box.
47
+ The Mantine theme maps `gray` to the official neutral ramp; ordinary and dimmed text resolves to `#666666` or darker for accessible contrast on white and `#F5F5F7`.
33
48
 
34
- To change the list of supported wallets, as well as connection configurations you can modify the `src/app/components/Web3Provider.tsx` component.
49
+ The public brand archive does not include the proprietary Lateral or ABC Repro Mono font files, so this starter uses license-safe system fallback stacks. Teams with appropriately licensed fonts can map Lateral and ABC Repro Mono to the documented CSS font tokens.
35
50
 
36
- ### Changing the Chain
37
- This application connects to the arctic-1 devnet by default, using chain configurations uploaded to viem.
51
+ ## Project structure
38
52
 
39
- To change the chain the app connects to, edit the `selectedChain` variable in the `chain.ts` file in `src/app/constants`
53
+ - `src/app/layout.tsx` wraps every route with the wallet and UI providers.
54
+ - `src/app/favicon.ico/route.ts` redirects conventional favicon requests to the unchanged official app mark.
55
+ - `src/app/page.tsx` shows the landing page until a wallet connects, then shows the interactive examples.
56
+ - `src/components/providers/providers.tsx` configures Wagmi, RainbowKit, and the selected Sei network. It registers the injected browser-wallet connector by default; add other wallets to its `wallets` list.
57
+ - `src/components/shell/shell.tsx` provides the shared app shell around the landing and connected views.
58
+ - `src/components/landing/index.tsx` contains the disconnected landing page.
59
+ - `src/components/default/index.tsx` contains the connected wallet, balance, and transaction examples.
60
+ - `src/app/development/page.tsx` and `src/app/resources/page.tsx` contain development notes and links.
40
61
 
41
- ## Learn More
62
+ ## Network configuration
42
63
 
43
- To learn more about Next.js, take a look at the following resources:
64
+ The app connects to Sei mainnet when `NEXT_PUBLIC_CHAIN` is unset. The included `.env.example` sets it to `testnet`, so copying that file to `.env.local` selects Sei testnet. To select testnet explicitly, add this to `.env.local`:
44
65
 
45
- - [Next.js Documentation](https://nextjs.org/docs) - learn about Next.js features and API.
46
- - [Learn Next.js](https://nextjs.org/learn) - an interactive Next.js tutorial.
66
+ ```dotenv
67
+ NEXT_PUBLIC_CHAIN=testnet
68
+ ```
69
+
70
+ WalletConnect-based wallets require a real project ID from [WalletConnect Cloud](https://cloud.walletconnect.com). Set it in `.env.local`:
47
71
 
48
- You can check out [the Next.js GitHub repository](https://github.com/vercel/next.js/) - your feedback and contributions are welcome!
72
+ ```dotenv
73
+ NEXT_PUBLIC_WALLETCONNECT_PROJECT_ID=your-project-id
74
+ ```
49
75
 
50
- ## Deploy on Vercel
76
+ The fallback value in the template is only a placeholder, so WalletConnect-based wallets will not connect until you replace it. The bundled injected connector works without a WalletConnect project ID for installed browser wallets such as MetaMask.
51
77
 
52
- The easiest way to deploy your Next.js app is to use the [Vercel Platform](https://vercel.com/new?utm_medium=default-template&filter=next.js&utm_source=create-next-app&utm_campaign=create-next-app-readme) from the creators of Next.js.
78
+ ## Learn more
53
79
 
54
- Check out our [Next.js deployment documentation](https://nextjs.org/docs/deployment) for more details.
80
+ - [Sei documentation](https://docs.sei.io/)
81
+ - [Next.js documentation](https://nextjs.org/docs)
82
+ - [Wagmi documentation](https://wagmi.sh/react/getting-started)
83
+ - [Viem documentation](https://viem.sh/)
84
+ - [RainbowKit documentation](https://www.rainbowkit.com/docs/introduction)
@@ -1,5 +1,5 @@
1
1
  {
2
- "$schema": "https://biomejs.dev/schemas/2.4.5/schema.json",
2
+ "$schema": "https://biomejs.dev/schemas/2.5.8/schema.json",
3
3
  "root": true,
4
4
  "assist": {
5
5
  "actions": {
@@ -11,7 +11,7 @@
11
11
  "linter": {
12
12
  "enabled": true,
13
13
  "rules": {
14
- "recommended": true,
14
+ "preset": "recommended",
15
15
  "style": {
16
16
  "noNonNullAssertion": "off"
17
17
  },
@@ -50,7 +50,6 @@
50
50
  }
51
51
  },
52
52
  "files": {
53
- "includes": ["src/**/*.ts", "src/**/*.tsx", "src/**/*.css", "*.json", "*.md"],
54
- "ignore": ["node_modules/**", "dist/**", ".next/**", ".yarn/**"]
53
+ "includes": ["src/**/*.ts", "src/**/*.tsx", "src/**/*.css", "*.json", "*.md", "!**/node_modules", "!**/dist", "!**/.next", "!**/.yarn"]
55
54
  }
56
55
  }
@@ -0,0 +1,41 @@
1
+ # Dependencies
2
+ /node_modules
3
+ /.pnp
4
+ .pnp.*
5
+ .yarn/*
6
+ !.yarn/patches
7
+ !.yarn/plugins
8
+ !.yarn/releases
9
+ !.yarn/versions
10
+
11
+ # Tests
12
+ /coverage
13
+
14
+ # Next.js
15
+ /.next/
16
+ /out/
17
+
18
+ # Production
19
+ /build
20
+
21
+ # Local files
22
+ .DS_Store
23
+ *.pem
24
+
25
+ # Debug logs
26
+ npm-debug.log*
27
+ yarn-debug.log*
28
+ yarn-error.log*
29
+ pnpm-debug.log*
30
+ bun-debug.log*
31
+
32
+ # Environment files (keep the example)
33
+ .env*
34
+ !.env.example
35
+
36
+ # Vercel
37
+ .vercel
38
+
39
+ # TypeScript
40
+ *.tsbuildinfo
41
+ next-env.d.ts