Skip to content
Merged

CLI #47

Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 1 addition & 3 deletions .env.example
Original file line number Diff line number Diff line change
@@ -1,5 +1,3 @@
NEXT_PUBLIC_IS_PROD=true

# RPC URLs
NEXT_PUBLIC_MAINNET_NETWORK_URL=
NEXT_PUBLIC_MAINNET_FALLBACK_URL=
Expand All @@ -15,7 +13,7 @@ NEXT_PUBLIC_MAINNET_VAULT_ADDRESS=0xAC0F906E433d58FA868F936E8A43230473652885
NEXT_PUBLIC_GNOSIS_VAULT_ADDRESS=0x4b4406Ed8659D03423490D8b62a1639206dA0A7a
NEXT_PUBLIC_HOODI_VAULT_ADDRESS=0xba447498DC4c169f2b4f427B2c4D532320457E89

NEXT_PUBLIC_OWNER=Stakewise
NEXT_PUBLIC_TITLE=StakeWise
NEXT_PUBLIC_OWNER_DOMAIN=app.stakewise.io
NEXT_PUBLIC_OWNER_X_ACCOUNT=@stakewise_io

Expand Down
47 changes: 30 additions & 17 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
# Vault interface

This is an open-source repository for the Vault interface.
You can customize this UI for your own vault on the
Mainnet, Gnosis and Hoodi networks.
Expand All @@ -10,11 +11,23 @@ allowing you to select the desired network.
### Demo
[Genesis vault interface](https://vault-interface-lemon.vercel.app/)

### Setup and Launch
### Quick start (recommended)

Use the CLI to scaffold a configured project in seconds:

```bash
npx @stakewise/create-vault-interface
```

It will ask you a few questions (networks, vault addresses, RPC URLs, theme,
WalletConnect ID, etc.), generate a `.env` file, install dependencies, and
optionally deploy to Vercel. See [`cli/README.md`](./cli/README.md) for details.

### Manual setup
1. Clone the repository.
2. Create a `.env.local` file in the root of the project.
3. Copy the contents of `.env.example` into your `.env.local` file.
4. Replace the values in `.env.local` with your specific configurations:
2. Create a `.env` file in the root of the project.
3. Copy the contents of `.env.example` into your `.env` file.
4. Replace the values in `.env` with your specific configurations:

- <b>Mainnet Vault</b>:
- Add the vault address to `NEXT_PUBLIC_MAINNET_VAULT_ADDRESS`.
Expand All @@ -30,10 +43,9 @@ allowing you to select the desired network.
- <b>Testnet Vaults</b>:
- Add the vault address to `NEXT_PUBLIC_HOODI_VAULT_ADDRESS`.
- Set the RPC URL in `NEXT_PUBLIC_HOODI_NETWORK_URL`.
- Testnet networks will only be displayed in the network selection if `NEXT_PUBLIC_IS_PROD` is set to `false`. In a production environment, you can switch to the testnet only through the wallet interface.
- Test environment networks will appear in the network list only if the `VERCEL_ENV` variable is not set to `production`. In a production environment, you can switch to the testnet only through the wallet interface.

- <b>Vault Ownership</b>:
- Add your vault owner’s title in `NEXT_PUBLIC_OWNER`.
- Specify the owner’s domain in `NEXT_PUBLIC_OWNER_DOMAIN`.
- Add the X account for metadata as `NEXT_PUBLIC_OWNER_X_ACCOUNT`.

Expand All @@ -47,17 +59,17 @@ allowing you to select the desired network.
- The UI supports 7 languages by default (en, ru, fr, es, pt, de, zh). To exclude any languages, set the `NEXT_PUBLIC_LOCALES` variable. For instance, for English and Chinese only, use `NEXT_PUBLIC_LOCALES=en, zh`.

- <b>Currency Configuration</b>:
- The UI supports 7 currencies by default (USD, EUR, GBP, JPY, CNY, CHF, AUD). To exclude currencies, set the `NEXT_PUBLIC_CURRENCIES` variable. For example, for USD and EUR only, use `NEXT_PUBLIC_CURRENCIES=USD, EUR`.
- The UI supports 7 currencies by default (usd, eur, gbp, cny, jpy, krw, aud). To exclude currencies, set the `NEXT_PUBLIC_CURRENCIES` variable. For example, for USD and EUR only, use `NEXT_PUBLIC_CURRENCIES=usd, eur`.

- <b>Content-Security-Policy Configuration</b>:
- If you want your site to open in a frame, you can list sites where this is possible. `NEXT_PUBLIC_CONTENT_SECURITY_POLICY=https://app.safe.global https://*.blockscout.com`.

- Set `NEXT_PUBLIC_IS_PROD=false` for development and `NEXT_PUBLIC_IS_PROD=true` for production. It will make code optimizations for production build.
5. Verify Node Version: Ensure your Node.js version is `20.12.2` or higher.
6. Run `npm install` to install the necessary dependencies.
7. <b>Start the Development Server</b>: Run `npm run dev` to start the server. Then navigate to [http://localhost:5005](http://localhost:5005) in your browser to view the application.
8. <b>Build for Production using Vercel</b>: Follow [Vercel instructions](https://vercel.com/docs/getting-started-with-vercel/import) to connect your repository to Vercel and automatically build and serve the app.
9. <b>Build for Production using hosting</b>: Run `npm run build` to prepare the app for production. After that, deploy the build files to your hosting service.
5. Verify Node Version: Ensure your Node.js version is `24.12.0` or higher (see `.nvmrc`).
6. Install pnpm if you don't have it: `corepack enable && corepack prepare pnpm@latest --activate`.
7. Run `pnpm install` to install the necessary dependencies.
8. <b>Start the Development Server</b>: Run `pnpm dev` to start the server. Then navigate to [http://localhost:5005](http://localhost:5005) in your browser to view the application.
9. <b>Build for Production using Vercel</b>: Follow [Vercel instructions](https://vercel.com/docs/getting-started-with-vercel/import) to connect your repository to Vercel and automatically build and serve the app.
10. <b>Build for Production using hosting</b>: Run `pnpm build` to prepare the app for production. After that, deploy the build files to your hosting service.

### Vault actions
The vault interface allows you to perform the following actions:
Expand All @@ -71,17 +83,17 @@ The vault interface allows you to perform the following actions:
- <b>Balance Tab</b>: Displays current user stats, unstake/unboost queue data, and claim actions.

### Environment Variables
Add `.env.local` file in the root of the project.
Add a `.env` file in the root of the project (or `.env.local` — both are gitignored).
Copy environment variables from the `.env.example` file and replace the values with the actual values.
Add vault address, rpc urls for the vault network, owner data, wallet connect id (if needed) and interface settings (locales, currencies).

### Themes and colors
There are light and dark themes available.
The default user will see theme that is the same as system. It will be light if the system theme is light and dark if the system theme is dark.
Theme can be changed in the settings menu.
By default the user will see a theme matching the system preference: light if the system theme is light, dark otherwise.
The theme can be changed in the settings menu.

Colors are defined in the `src/styles/settings.scss` file and can be customized to your preferences.
After updating the colors, run `npm run colors` — this script will generate RGB versions from your hex codes and update the following files:
After updating the colors, run `pnpm colors` — this script will generate RGB versions from your hex codes and update the following files:

- `src/styles/tailwind/layers/base.css`
- `src/styles/tailwind/theme.css`
Expand All @@ -91,3 +103,4 @@ After updating the colors, run `npm run colors` — this script will generate RG
### Favicon
The favicon is a 16x16 image that is displayed in the browser tab. It is located in the `public` folder.
By default, osETH logo is used as the favicon.

4 changes: 4 additions & 0 deletions cli/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
node_modules
dist
*.log
.DS_Store
60 changes: 60 additions & 0 deletions cli/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
# @stakewise/create-vault-interface

A CLI to scaffold a [StakeWise Vault Interface](https://github.com/stakewise/vault-interface) — a Next.js frontend ready to deploy on Vercel.

## Quick start

```bash
npx @stakewise/create-vault-interface
```

Or via your package manager:

```bash
npm init @stakewise/vault-interface
pnpm create @stakewise/vault-interface
yarn create @stakewise/vault-interface
```

The CLI walks you through a few questions, generates a configured project in the current directory, and is ready to run.

## What it does

1. Asks a series of questions (see below)
2. Clones `stakewise/vault-interface` into your chosen folder (without git history)
3. Removes the bundled `cli/` folder from the clone
4. Optionally rewrites the primary brand color in `src/styles/settings.scss` and `src/styles/tailwind/layers/base.css`
5. Generates a populated `.env` file
6. Initializes a fresh git repository with an initial commit
7. Optionally runs `pnpm install`
8. Optionally launches `npx vercel` to deploy

## Questions

| Step | Question | Notes |
|------|----------|-------|
| Project | Folder name | Defaults to `vault-interface` |
| Theme | Customize primary color? | Optional. If yes — asks for `#rrggbb` for light and dark themes |
| Networks | Which networks to support? | Multi-select: Mainnet / Gnosis / Hoodi |
| Vault | Vault address per network | Validates `0x` + 40 hex chars |
| RPC | RPC URL per network | Required, must be `http(s)://...` |
| Fallback RPC | Fallback URL per network | Optional |
| Owner | Site domain | Required |
| Owner | X (Twitter) account | Optional |
| Wallet | WalletConnect Project ID | Optional |
| Referrer | Referrer address | Optional, validated as `0x` address |
| Locales | Supported languages | Multi-select, all enabled by default |
| Currencies | Supported currencies | Multi-select, all enabled by default |
| Security | Content-Security-Policy | Pre-filled with sensible default |
| Setup | Install dependencies? | Defaults to yes — uses `pnpm` |
| Deploy | Deploy to Vercel? | Defaults to no — runs `npx vercel` |

## Requirements

- Node.js `>=20`
- Git (recommended — the CLI initializes a repo)
- pnpm (recommended — `corepack enable && corepack prepare pnpm@latest --activate`)

## License

MIT
54 changes: 54 additions & 0 deletions cli/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
{
"name": "@stakewise/create-vault-interface",
"version": "0.1.4",
"description": "CLI for creating a StakeWise Vault Interface application",
"type": "module",
"scripts": {
"build": "tsup",
"dev": "tsup --watch",
"start": "node dist/index.js",
"prepublishOnly": "npm run build"
},
"bin": {
"create-vault-interface": "./dist/index.js"
},
"files": [
"dist"
],
"engines": {
"node": ">=20"
},
"keywords": [
"stakewise",
"vault",
"ethereum",
"staking",
"cli"
],
"license": "MIT",
"homepage": "https://github.com/stakewise/vault-interface#readme",
"repository": {
"type": "git",
"url": "https://github.com/stakewise/vault-interface.git",
"directory": "cli"
},
"bugs": {
"url": "https://github.com/stakewise/vault-interface/issues"
},
"publishConfig": {
"access": "public"
},
"dependencies": {
"degit": "2.8.4",
"execa": "9.5.2",
"picocolors": "1.1.1",
"prompts": "2.4.2"
},
"devDependencies": {
"@types/degit": "2.8.6",
"@types/node": "22.10.0",
"@types/prompts": "2.4.9",
"tsup": "8.3.5",
"typescript": "5.6.3"
}
}
63 changes: 63 additions & 0 deletions cli/src/constants.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
import pc from 'picocolors'
import prompts from 'prompts'

import { log } from './helpers'
import { Network } from './types'


export const promptOptions: prompts.Options = {
onCancel: () => {
log(pc.yellow('\nCancelled.'))
process.exit(1)
},
}

export const repo = 'stakewise/vault-interface'

export const networks: readonly Network[] = [ 'mainnet', 'gnosis', 'hoodi' ] as const

export const networkLabels: Record<Network, string> = {
mainnet: 'Ethereum Mainnet',
gnosis: 'Gnosis Chain',
hoodi: 'Hoodi Testnet',
}

export const networkChoices: prompts.Choice[] = [
{ title: 'Ethereum Mainnet', value: 'mainnet' },
{ title: 'Gnosis Chain', value: 'gnosis' },
{ title: 'Hoodi Testnet', value: 'hoodi' },
]

export const localeChoices: prompts.Choice[] = [
{ title: 'English (en)', value: 'en' },
{ title: 'Russian (ru)', value: 'ru' },
{ title: 'French (fr)', value: 'fr' },
{ title: 'Spanish (es)', value: 'es' },
{ title: 'Portuguese (pt)', value: 'pt' },
{ title: 'German (de)', value: 'de' },
{ title: 'Chinese (zh)', value: 'zh' },
]

export const currencyChoices: prompts.Choice[] = [
{ title: 'USD', value: 'usd' },
{ title: 'EUR', value: 'eur' },
{ title: 'GBP', value: 'gbp' },
{ title: 'CNY', value: 'cny' },
{ title: 'JPY', value: 'jpy' },
{ title: 'KRW', value: 'krw' },
{ title: 'AUD', value: 'aud' },
]

export const defaultCsp = 'https://app.safe.global https://*.blockscout.com'

export const settingsLightPrimary = `'primary': #3a8eea,`

export const settingsDarkPrimary = `'primary': #5c87f6,`

export const baseLightRgb = '--primary-rgb: 58, 142, 234;'

export const baseLightFn = '--primary: rgb(58, 142, 234);'

export const baseDarkRgb = '--primary-rgb: 92, 135, 246;'

export const baseDarkFn = '--primary: rgb(92, 135, 246);'
58 changes: 58 additions & 0 deletions cli/src/helpers.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
import { execa } from 'execa'
import prompts from 'prompts'
import pc from 'picocolors'

import { promptOptions } from './constants'


export const isUrl = (value: string): boolean => /^https?:\/\/.+/i.test(String(value).trim())

export const isAddress = (value: string): boolean => /^0x[a-fA-F0-9]{40}$/.test(String(value).trim())

export const isHexColor = (value: string): boolean => /^#[0-9a-fA-F]{6}$/.test(String(value).trim())

export const ask = <T extends string = string>(question: prompts.PromptObject<T>) => prompts(question, promptOptions)

export const hexToRgb = (hex: string): [ number, number, number ] => {
const trimmed = hex.replace('#', '')

return [
parseInt(trimmed.slice(0, 2), 16),
parseInt(trimmed.slice(2, 4), 16),
parseInt(trimmed.slice(4, 6), 16),
]
}

export const hasCommand = async (cmd: string): Promise<boolean> => {
try {
await execa(process.platform === 'win32' ? 'where' : 'which', [ cmd ])

return true
}
catch {
return false
}
}

export const log = (value: string | string[]) => console.log(`
${Array.isArray(value) ? value.join('\n') : value}
`)

type SafeReplaceInput = {
replacement: string
content: string
search: string
label: string
}

export const safeReplace = (values: SafeReplaceInput): string => {
const { replacement, content, search, label } = values

if (!content.includes(search)) {
log(pc.yellow(` Warning: "${label}" not found in source — skipping`))

return content
}

return content.replace(search, replacement)
}
Loading
Loading