diff --git a/.env.local.example b/.env.local.example new file mode 100644 index 0000000..1025b40 --- /dev/null +++ b/.env.local.example @@ -0,0 +1,3 @@ +# See https://www.coinbase.com/developer-platform/products/base-node +NEXT_PUBLIC_CDP_API_KEY="GET_FROM_COINBASE_DEVELOPER_PLATFORM" +ENVIRONMENT=localhost \ No newline at end of file diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..511b03e --- /dev/null +++ b/.gitignore @@ -0,0 +1,36 @@ +# dependencies +/node_modules +/.pnp +.pnp.js +bun.lockb + +# testing +/coverage + +# next.js +/.next/ +/out/ + +# production +/build + +# misc +.DS_Store +*.pem + +# debug +npm-debug.log* +yarn-debug.log* +yarn-error.log* + +# local env files +.env*.local + +# vercel +.vercel + +# typescript +*.tsbuildinfo +next-env.d.ts + +.env \ No newline at end of file diff --git a/README.md b/README.md index 2566070..68db2a4 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,45 @@ -# an-onchain-app-in-100-components -An Onchain App in less than 100 components, and ready to be deployed to Vercel. +

+ + + OnchainKit logo vibes + +

+ +# Onchain App Template + +An Onchain App Template build with [OnchainKit](https://onchainkit.xyz), and ready to be deployed to Vercel. + +To ensure all components work seamlessly, set the `NEXT_PUBLIC_CDP_API_KEY` in your `.env` file. + +You can find the API KEY on the Node page at the [Coinbase Dev Portal](https://portal.cdp.coinbase.com/products/node). + +
+ +Play with it live on https://onchain-app-template.vercel.app + +Have fun! ⛵️ + +
+ +## Locally run + +```sh +# Install bun in case you don't have it +bun curl -fsSL | bash + +# Install packages +bun i + +# Run Next app +bun run dev +``` + +## Resources + +- [OnchainKit documentation](https://onchainkit.xyz) + +
+ +## License + +This project is licensed under the MIT License - see the [LICENSE.md](LICENSE.md) file for details \ No newline at end of file diff --git a/biome.json b/biome.json new file mode 100644 index 0000000..129c081 --- /dev/null +++ b/biome.json @@ -0,0 +1,68 @@ +{ + "$schema": "https://biomejs.dev/schemas/1.8.0/schema.json", + "organizeImports": { + "enabled": true + }, + "formatter": { + "enabled": true, + "indentWidth": 2, + "indentStyle": "space", + "ignore": ["package.json"] + }, + "javascript": { + "formatter": { + "enabled": true, + "lineWidth": 80, + "arrowParentheses": "always", + "bracketSameLine": false, + "quoteStyle": "single", + "jsxQuoteStyle": "double", + "indentWidth": 2, + "indentStyle": "space", + "semicolons": "always", + "trailingCommas": "all" + } + }, + "linter": { + "enabled": true, + "rules": { + "recommended": true, + "correctness": { + "noConstantMathMinMaxClamp": "error", + "noNodejsModules": "error", + "noUnusedImports": "error", + "noUnusedPrivateClassMembers": "error", + "noUnusedVariables": "error", + "useArrayLiterals": "error" + }, + "nursery": { + "useSortedClasses": "error" + }, + "style": { + "noImplicitBoolean": "error", + "noNamespace": "error", + "noNamespaceImport": "error", + "noNegationElse": "error", + "noParameterProperties": "error", + "noShoutyConstants": "error", + "useBlockStatements": "error", + "useCollapsedElseIf": "error", + "useConsistentArrayType": "error", + "useForOf": "error", + "useFragmentSyntax": "error", + "useShorthandArrayType": "error", + "useShorthandAssign": "error", + "useSingleCaseStatement": "error" + } + }, + "ignore": [] + }, + "json": { + "formatter": { + "trailingCommas": "none" + } + }, + "files": { + "ignore": [".next"] + } +} diff --git a/next.config.js b/next.config.js new file mode 100644 index 0000000..658404a --- /dev/null +++ b/next.config.js @@ -0,0 +1,4 @@ +/** @type {import('next').NextConfig} */ +const nextConfig = {}; + +module.exports = nextConfig; diff --git a/package.json b/package.json new file mode 100644 index 0000000..a3b4fbd --- /dev/null +++ b/package.json @@ -0,0 +1,50 @@ +{ + "name": "onchain-app-template", + "private": true, + "version": "0.0.0", + "scripts": { + "build": "next build", + "check": "biome check --write .", + "ci:check": "biome ci --formatter-enabled=false --linter-enabled=false", + "ci:format": "biome ci --linter-enabled=false --organize-imports-enabled=false", + "ci:lint": "biome ci --formatter-enabled=false --organize-imports-enabled=false", + "dev": "NODE_OPTIONS='--inspect' next dev", + "format": "biome format --write .", + "lint": "biome lint --write .", + "lint:unsafe": "biome lint --write --unsafe .", + "start": "next start", + "test": "vitest run", + "test:coverage": "vitest run --coverage" + }, + "dependencies": { + "@coinbase/onchainkit": "^0.27.0", + "next": "^14.2.5", + "permissionless": "^0.1.26", + "react": "^18", + "react-dom": "^18", + "siwe": "^2.3.2" + }, + "devDependencies": { + "@biomejs/biome": "^1.8.0", + "@types/node": "^20.11.8", + "@types/react": "^18.2.48", + "@types/react-dom": "^18.2.7", + "@vitest/coverage-v8": "^2.0.2", + "@vitest/ui": "^2.0.1", + "@wagmi/cli": "latest", + "autoprefixer": "^10.4.19", + "bufferutil": "^4.0.7", + "encoding": "^0.1.13", + "lokijs": "^1.5.12", + "jsdom": "^24.1.0", + "pino-pretty": "^10.2.0", + "postcss": "^8.4.38", + "supports-color": "^9.4.0", + "tailwindcss": "^3.4.0", + "@testing-library/jest-dom": "^6.4.6", + "@testing-library/react": "^14.2.0", + "typescript": "^5.3.3", + "utf-8-validate": "^6.0.3", + "vitest": "^2.0.1" + } +} diff --git a/postcss.config.js b/postcss.config.js new file mode 100644 index 0000000..12a703d --- /dev/null +++ b/postcss.config.js @@ -0,0 +1,6 @@ +module.exports = { + plugins: { + tailwindcss: {}, + autoprefixer: {}, + }, +}; diff --git a/public/copy-api-key.png b/public/copy-api-key.png new file mode 100644 index 0000000..e257c9f Binary files /dev/null and b/public/copy-api-key.png differ diff --git a/public/favicon.ico b/public/favicon.ico new file mode 100644 index 0000000..0d2f63e Binary files /dev/null and b/public/favicon.ico differ diff --git a/src/app/global.css b/src/app/global.css new file mode 100644 index 0000000..b535e9c --- /dev/null +++ b/src/app/global.css @@ -0,0 +1,65 @@ +/* stylelint-disable custom-property-pattern */ +@tailwind base; +@tailwind components; +@tailwind utilities; + +:root { + /* Palette */ + --palette-bg-black: #fff; + --palette-bg-white: rgb(21, 26, 38); + + /* Indicates that the element can be rendered using + * the operating system dark color scheme. + * https://developer.mozilla.org/en-US/docs/Web/CSS/color-scheme */ + color-scheme: light; +} + +*, +*::before, +*::after { + box-sizing: border-box; +} + +html { + height: 100%; + scroll-behavior: smooth; +} + +html .font-robotoMono { + font-family: var(--font-roboto-mono); +} + +html .font-inter { + font-family: var(--font-inter); +} + +body { + height: 100%; + margin: 0; + background-color: var(--palette-bg-black); + color: var(--palette-bg-white); + font-family: Inter, sans-serif; + overflow-x: hidden; + -webkit-text-size-adjust: 100%; + text-size-adjust: 100%; +} + +a { + text-decoration: underline; +} + +svg { + display: block; + overflow: visible; + vertical-align: middle; +} + +ul { + padding-inline-start: 0; +} + +/* stylelint-disable-next-line */ +#__next { + position: relative; + z-index: 0; +} \ No newline at end of file diff --git a/src/app/layout.tsx b/src/app/layout.tsx new file mode 100644 index 0000000..8ddaabc --- /dev/null +++ b/src/app/layout.tsx @@ -0,0 +1,33 @@ +import type { Metadata } from 'next'; +import OnchainProviders from '../components/OnchainProviders'; +import { NEXT_PUBLIC_URL } from '../config'; + +import './global.css'; +import '@coinbase/onchainkit/styles.css'; + +export const viewport = { + width: 'device-width', + initialScale: 1.0, +}; + +export const metadata: Metadata = { + title: 'Onchain App Template', + description: 'LFG', + openGraph: { + title: 'Onchain App Template', + description: 'LFG', + images: [`${NEXT_PUBLIC_URL}/vibes/vibes-19.png`], + }, +}; + +export default function RootLayout({ + children, +}: { children: React.ReactNode }) { + return ( + + + {children} + + + ); +} diff --git a/src/app/page.tsx b/src/app/page.tsx new file mode 100644 index 0000000..9d9bf10 --- /dev/null +++ b/src/app/page.tsx @@ -0,0 +1,22 @@ +'use client'; +import WalletComponents from '../components/WalletComponents'; + +export default function Page() { + return ( +
+
+ +
+
+ +
+ +
+
+
+ ); +} diff --git a/src/components/OnchainProviders.tsx b/src/components/OnchainProviders.tsx new file mode 100644 index 0000000..6ee651f --- /dev/null +++ b/src/components/OnchainProviders.tsx @@ -0,0 +1,26 @@ +'use client'; +import { OnchainKitProvider } from '@coinbase/onchainkit'; +import { QueryClient, QueryClientProvider } from '@tanstack/react-query'; +import type { ReactNode } from 'react'; +import { base } from 'viem/chains'; +import { WagmiProvider } from 'wagmi'; +import { NEXT_PUBLIC_CDP_API_KEY } from '../config'; +import { wagmiConfig } from '../wagmi'; + +type Props = { children: ReactNode }; + +const queryClient = new QueryClient(); + +function OnchainProviders({ children }: Props) { + return ( + + + + {children} + + + + ); +} + +export default OnchainProviders; diff --git a/src/components/WalletComponents.test.tsx b/src/components/WalletComponents.test.tsx new file mode 100644 index 0000000..7c3360f --- /dev/null +++ b/src/components/WalletComponents.test.tsx @@ -0,0 +1,10 @@ +import { render } from '@testing-library/react'; +import { describe, expect, it } from 'vitest'; +import WalletComponents from './WalletComponents'; + +describe('WalletComponents', () => { + it('should renders', () => { + render(); + expect(true).toBeTruthy(); + }); +}); diff --git a/src/components/WalletComponents.tsx b/src/components/WalletComponents.tsx new file mode 100644 index 0000000..7740ad8 --- /dev/null +++ b/src/components/WalletComponents.tsx @@ -0,0 +1,40 @@ +'use client'; +import { + Address, + Avatar, + EthBalance, + Identity, + Name, +} from '@coinbase/onchainkit/identity'; +import { + ConnectWallet, + Wallet, + WalletDropdown, + WalletDropdownDisconnect, + WalletDropdownLink, +} from '@coinbase/onchainkit/wallet'; + +export default function WalletComponents() { + return ( + <> + + + + + + + + + +
+ + + + Go to Wallet Dashboard + + + + + + ); +} diff --git a/src/config.ts b/src/config.ts new file mode 100644 index 0000000..92fdd5d --- /dev/null +++ b/src/config.ts @@ -0,0 +1,7 @@ +// use NODE_ENV to not have to change config based on where it's deployed +export const NEXT_PUBLIC_URL = + process.env.NODE_ENV === 'development' + ? 'http://localhost:3000' + : 'https://onchain-app-template.vercel.app'; +// Add your API KEY from the Coinbase Developer Portal +export const NEXT_PUBLIC_CDP_API_KEY = process.env.NEXT_PUBLIC_CDP_API_KEY; diff --git a/src/wagmi.ts b/src/wagmi.ts new file mode 100644 index 0000000..4e58677 --- /dev/null +++ b/src/wagmi.ts @@ -0,0 +1,26 @@ +import { http, createConfig } from 'wagmi'; +import { baseSepolia } from 'wagmi/chains'; +import { coinbaseWallet } from 'wagmi/connectors'; + +export const wagmiConfig = createConfig({ + chains: [baseSepolia], + // turn off injected provider discovery + multiInjectedProviderDiscovery: false, + connectors: [ + coinbaseWallet({ + appName: 'anOnchainAppIn100Components', + preference: 'all', + version: '4', + }), + ], + ssr: true, + transports: { + [baseSepolia.id]: http(), + }, +}); + +declare module 'wagmi' { + interface Register { + config: typeof wagmiConfig; + } +} diff --git a/tailwind.config.ts b/tailwind.config.ts new file mode 100644 index 0000000..b6bfaf5 --- /dev/null +++ b/tailwind.config.ts @@ -0,0 +1,19 @@ +import type { Config } from 'tailwindcss'; + +const config: Config = { + content: ['./src/**/*.{js,ts,jsx,tsx,mdx}'], + theme: { + container: { + center: true, + screens: { + sm: '640px', + md: '768px', + lg: '1024px', + xl: '1280px', + }, + }, + }, + plugins: [], +}; + +export default config; diff --git a/tsconfig.json b/tsconfig.json new file mode 100644 index 0000000..672fd3a --- /dev/null +++ b/tsconfig.json @@ -0,0 +1,25 @@ +{ + "compilerOptions": { + "target": "es2020", + "lib": ["dom", "dom.iterable", "esnext"], + "allowJs": true, + "skipLibCheck": true, + "strict": true, + "noEmit": true, + "esModuleInterop": true, + "module": "esnext", + "moduleResolution": "bundler", + "resolveJsonModule": true, + "isolatedModules": true, + "jsx": "preserve", + "incremental": true, + "plugins": [ + { + "name": "next" + } + ], + "baseUrl": "." + }, + "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"], + "exclude": ["node_modules"] +} diff --git a/vitest.config.ts b/vitest.config.ts new file mode 100644 index 0000000..f6d6bc4 --- /dev/null +++ b/vitest.config.ts @@ -0,0 +1,28 @@ +import { defineConfig } from 'vitest/config'; + +export default defineConfig({ + test: { + coverage: { + exclude: [ + '**.js', + '**.ts', + '**/**.stories.**', + '**/*Svg.tsx', + '**/types.ts', + 'public/**', + 'node_modules/**', + ], + reportOnFailure: true, + thresholds: { + statements: 20, + branches: 20, + functions: 20, + lines: 20, + }, + }, + environment: 'jsdom', + exclude: ['**/node_modules/**'], + setupFiles: ['./vitest.setup.ts'], + globals: true, + }, +}); diff --git a/vitest.setup.ts b/vitest.setup.ts new file mode 100644 index 0000000..ec4ab93 --- /dev/null +++ b/vitest.setup.ts @@ -0,0 +1,2 @@ +import '@testing-library/jest-dom/vitest'; +// https://github.com/testing-library/jest-dom#with-vitest