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.
+
+
+
+
+
+
+
+# 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