diff --git a/.changeset/formatter.js b/.changeset/formatter.js index 4bf2b1e0d7..2f2de7630b 100644 --- a/.changeset/formatter.js +++ b/.changeset/formatter.js @@ -122,4 +122,4 @@ const changelogFunctions = { }, }; -export default changelogFunctions; +export default changelogFunctions; \ No newline at end of file diff --git a/.github/labeler.yml b/.github/labeler.yml deleted file mode 100644 index 3a3df3c266..0000000000 --- a/.github/labeler.yml +++ /dev/null @@ -1,43 +0,0 @@ -root: -- changed-files: - - any-glob-to-any-file: '*' - -documentation: -- changed-files: - - any-glob-to-any-file: site/** - -'pkg: core': -- changed-files: - - any-glob-to-any-file: packages/onchainkit/src/core/** - -'pkg: fund': -- changed-files: - - any-glob-to-any-file: packages/onchainkit/src/fund/** - -'pkg: identity': -- changed-files: - - any-glob-to-any-file: packages/onchainkit/src/identity/** - -'pkg: network': -- changed-files: - - any-glob-to-any-file: packages/onchainkit/src/core/network/** - -'pkg: styles': -- changed-files: - - any-glob-to-any-file: packages/onchainkit/src/styles/** - -'pkg: swap': -- changed-files: - - any-glob-to-any-file: packages/onchainkit/src/swap/** - -'pkg: token': -- changed-files: - - any-glob-to-any-file: packages/onchainkit/src/token/** - -'pkg: utils': -- changed-files: - - any-glob-to-any-file: packages/onchainkit/src/core/utils/** - -'pkg: wallet': -- changed-files: - - any-glob-to-any-file: packages/onchainkit/src/wallet/** diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index accb6bf09f..345775bb65 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -3,7 +3,7 @@ on: push: branches: ['main'] pull_request: - branches: ['main'] + branches: ['main', 'onchainkit-v1'] permissions: contents: read jobs: diff --git a/.github/workflows/cli-basic.yml b/.github/workflows/cli-basic.yml index 4f570ecf37..30500986cf 100644 --- a/.github/workflows/cli-basic.yml +++ b/.github/workflows/cli-basic.yml @@ -12,6 +12,7 @@ on: jobs: test: + if: github.head_ref != 'alpha' runs-on: ubuntu-latest strategy: matrix: diff --git a/.github/workflows/cli-snake.yml b/.github/workflows/cli-minikit.yml similarity index 93% rename from .github/workflows/cli-snake.yml rename to .github/workflows/cli-minikit.yml index 1a3ae5fb84..508e9238e0 100644 --- a/.github/workflows/cli-snake.yml +++ b/.github/workflows/cli-minikit.yml @@ -1,4 +1,4 @@ -name: "cli: build & install minikit - snake" +name: "cli: build & install minikit" on: pull_request: @@ -12,6 +12,7 @@ on: jobs: test: + if: github.head_ref != 'alpha' runs-on: ubuntu-latest strategy: matrix: @@ -48,7 +49,7 @@ jobs: run: | mkdir test-project cd test-project - (sleep 1; echo ""; sleep 1; echo ""; sleep 1; echo -e "\033[D\n"; sleep 1; echo -e "\033[D\n") | ../packages/create-onchain/dist/esm/cli.js --template=minikit-snake + (sleep 1; echo ""; sleep 1; echo ""; sleep 1; echo -e "\033[D\n"; sleep 1; echo -e "\033[D\n") | ../packages/create-onchain/dist/esm/cli.js --mini - name: Install & Build test project working-directory: ./test-project/my-minikit-app diff --git a/.github/workflows/format.yml b/.github/workflows/format.yml index 5d4ed24f69..352136e241 100644 --- a/.github/workflows/format.yml +++ b/.github/workflows/format.yml @@ -3,7 +3,7 @@ on: push: branches: ['main'] pull_request: - branches: ['main'] + branches: ['main', 'onchainkit-v1'] permissions: contents: read jobs: diff --git a/.github/workflows/labeler.yml b/.github/workflows/labeler.yml deleted file mode 100644 index 74ac85b727..0000000000 --- a/.github/workflows/labeler.yml +++ /dev/null @@ -1,19 +0,0 @@ -name: "Pull Request Labeler" -on: - pull_request: - branches: ['main'] -permissions: - contents: read -jobs: - labeler: - permissions: - contents: read - pull-requests: write - runs-on: ubuntu-latest - steps: - - name: Harden the runner (Audit all outbound calls) - uses: step-security/harden-runner@002fdce3c6a235733a90a27c80493a3241e56863 # v2.12.1 - with: - egress-policy: audit - - - uses: actions/labeler@8558fd74291d67161a8a78ce36a881fa63b766a9 # v5.0.0 diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml index 52f470943b..60d969910d 100644 --- a/.github/workflows/lint.yml +++ b/.github/workflows/lint.yml @@ -3,7 +3,7 @@ on: push: branches: ['main'] pull_request: - branches: ['main'] + branches: ['main', 'onchainkit-v1'] permissions: contents: read jobs: diff --git a/.github/workflows/release-alpha.yml b/.github/workflows/release-alpha.yml index 14733841f7..98611cedf5 100644 --- a/.github/workflows/release-alpha.yml +++ b/.github/workflows/release-alpha.yml @@ -4,6 +4,8 @@ on: push: branches: - alpha + paths: + - 'packages/onchainkit/**' concurrency: ${{ github.workflow }}-${{ github.ref }} @@ -28,13 +30,22 @@ jobs: - name: 'Setup' uses: ./.github/actions/setup - - name: Build + - name: Build OnchainKit shell: bash run: pnpm f:ock build + - name: Build create-onchain + shell: bash + run: pnpm f:create build + - name: Set deployment token run: npm config set '//registry.npmjs.org/:_authToken' "${{ secrets.NPM_TOKEN }}" - - name: Publish alpha + - name: Publish OnchainKit alpha + id: publish-ock shell: bash run: pnpm f:ock publish-prerelease --tag alpha + + - name: Publish create-onchain alpha + shell: bash + run: pnpm f:create publish-prerelease --tag alpha --version ${{ steps.publish-ock.outputs.version }} diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 182093a4d9..37fc57b537 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -3,7 +3,7 @@ on: push: branches: ['main'] pull_request: - branches: ['main'] + branches: ['main', 'alpha'] permissions: contents: read jobs: diff --git a/.gitignore b/.gitignore index d4c35e545d..27613baf2c 100644 --- a/.gitignore +++ b/.gitignore @@ -6,7 +6,6 @@ esm tmp /out-tsc src/styles.css -src/tailwind.css # dependencies node_modules @@ -31,6 +30,7 @@ npm-debug.log yarn-error.log testem.log /typings +.ignore # System Files .DS_Store @@ -59,5 +59,7 @@ storybook-static/ # Tarballs *.tgz -# cursor -.cursorrules \ No newline at end of file +# AI agent rules +.cursorrules +AGENT.md +.claude \ No newline at end of file diff --git a/CLAUDE.md b/CLAUDE.md new file mode 100644 index 0000000000..cf3e28d2e5 --- /dev/null +++ b/CLAUDE.md @@ -0,0 +1,103 @@ +# CLAUDE.md + +This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository. + +## Plan & Review + +### Before starting work +- Write a plan to .claude/tasks/TASK_NAME.md +- The plan should be a detailed implementation plan and the reasoning behind the plan, the tasks to implement the plan, and a briefdescription of each task +- Don't over plan, keep things simple and focused +- After you write the plan, ask me to review it. Do not continue until I approve the plan +- If it's clear that the task is too trivial to require a plan, you may skip the step of creating the written TASK_NAME.md. You should still start by explaining the plan of attack, and asking for approval before starting + +### While implementing +- Update the plan as you go +- After you complete a task, mark it as done, and add a brief description of the changes you made + +## Development Commands + +### Building and Development +- `pnpm install` - Install dependencies across all packages +- `pnpm build` - Build all packages +- `pnpm f:ock build` - Build OnchainKit package only +- `pnpm f:play dev:watch` - Start playground in watch mode with OnchainKit rebuilding automatically +- `pnpm f:play dev` - Start playground development server only + +### Testing and Quality +- `pnpm f:ock test` - Run tests for OnchainKit package +- `pnpm f:ock test:watch` - Run tests in watch mode +- `pnpm f:ock test:coverage` - Run tests with coverage +- `pnpm f:ock lint` - Run ESLint on OnchainKit package +- `pnpm f:ock typecheck` - Run TypeScript type checking + +### Package Filters +The monorepo uses pnpm workspaces with these shortcuts: +- `pnpm f:ock` - `@coinbase/onchainkit` package +- `pnpm f:play` - `playground` package +- `pnpm f:create` - `create-onchain` package +- `pnpm f:manifest` - `miniapp-manifest-generator` package + +## Architecture Overview + +This is a monorepo containing multiple packages organized as pnpm workspaces: + +### Core Package (`packages/onchainkit/`) +The main OnchainKit React component library with modular exports: +- `/api` - API utilities for blockchain operations +- `/buy` - Buy/funding components and hooks +- `/checkout` - E-commerce checkout components +- `/earn` - Yield/staking components (Morpho integration) +- `/fund` - Onramp funding components +- `/identity` - ENS/Basename identity components +- `/minikit` - Farcaster MiniKit integration +- `/swap` - Token swap components +- `/transaction` - Transaction components and utilities +- `/wallet` - Wallet connection components + +### Supporting Packages +- `packages/playground/` - Next.js demo app for testing components +- `packages/create-onchain/` - CLI tool for bootstrapping apps (`npm create onchain`) +- `packages/miniapp-manifest-generator/` - Tool for generating Farcaster MiniApp manifests + +### Templates and Examples +- `templates/` - Starter templates for different use cases +- `examples/` - Example implementations + +## Key Development Patterns + +### Component Structure +Components follow a consistent pattern with: +- Main component file (e.g., `Swap.tsx`) +- Provider component for context (e.g., `SwapProvider.tsx`) +- Hook files in `/hooks` subdirectory +- Utility functions in `/utils` subdirectory +- Type definitions in `types.ts` + +### Testing Strategy +- Comprehensive test coverage with Vitest +- Component tests using React Testing Library +- Mock data in `/mocks` directories where needed + +### Build System +- Vite for building the main package +- Module exports for tree-shaking optimization +- TypeScript with strict configuration +- PostCSS with Tailwind CSS preprocessing + +### Styling +- Tailwind CSS with custom theme configuration +- CSS-in-JS for component-specific styles +- Responsive design patterns throughout + +## Development Workflow + +1. Use `pnpm f:play dev:watch` for component development - this runs the playground with OnchainKit rebuilding automatically +2. View changes at http://localhost:3000 +3. Run tests with `pnpm f:ock test` before committing +4. Use `pnpm f:ock lint` and `pnpm f:ock typecheck` to ensure code quality + +## Package Management +- Uses pnpm v10 with workspaces +- Node.js v20 required +- Peer dependencies: React 19, Viem 2.27+, Wagmi 2.16+ diff --git a/alpha-api-changes.md b/alpha-api-changes.md new file mode 100644 index 0000000000..d532530dec --- /dev/null +++ b/alpha-api-changes.md @@ -0,0 +1,264 @@ +# OnchainKit v1.0.0 API Changes + +This document outlines all API changes, additions, and removals between OnchainKit v0.38.19 and v1.0.0. + +## Breaking Changes + +### Provider API + +#### Removed +- **`MiniKitProvider`** - Standalone MiniKit provider + +#### Changed +- **`OnchainKitProvider`** - Now includes MiniKit configuration + +**v0.x:** +```tsx + +``` + +**v1.0:** +```tsx + +``` + +### Dependencies + +#### Updated +- **React**: `^18 || ^19` → `^19` (React 18 support removed) +- **@farcaster/frame-sdk** → **@farcaster/miniapp-sdk** (breaking dependency change) + +#### Added Peer Dependencies +- **viem**: `^2.27` (now required as peer dependency) +- **wagmi**: `^2.16` (now required as peer dependency) + +### Build System + +#### Changed Scripts +- **v0.x**: `"build": "pnpm clean && pnpm bundle:prod"` +- **v1.0**: `"build": "pnpm clean && vite build"` + +#### Removed Scripts +- `bundle:dev` +- `bundle:prod` + +#### Updated Scripts +- **dev**: `NODE_ENV=development vite build --watch` (updated Vite integration) + +## New Features + +### Component Enhancements + +All existing component APIs remain **backward compatible**. However, several components now support new render prop patterns: + +#### New Render Props Support +- **ConnectWallet**: Added render prop for custom button rendering +- **SignatureButton**: Added render prop for custom button rendering + +```tsx +// ConnectWallet render prop (NEW) + ( + + {label} + + )} +/> + +// SignatureButton render prop (NEW) + ( + + {label} + + )} +/> +``` + +#### Component API Compatibility +No breaking changes to: + +- **Identity Module**: `Address`, `Avatar`, `Badge`, `EthBalance`, `Identity`, `Name`, `Socials`, `IdentityCard` +- **Wallet Module**: `Wallet`, `ConnectWallet`, `WalletDropdown`, etc. +- **Transaction Module**: `Transaction`, `TransactionButton`, `TransactionStatus`, etc. +- **Swap Module**: `Swap`, `SwapAmountInput`, `SwapButton`, etc. +- **All other modules**: `Buy`, `Checkout`, `Earn`, `Fund`, `NFT`, `Signature` + +### Hook API Compatibility + +All hooks maintain backward compatibility: + +- **Identity Hooks**: `useAddress`, `useAvatar`, `useName`, etc. +- **Wallet Hooks**: `useWalletContext`, `usePortfolio` +- **Transaction Hooks**: `useTransactionContext` +- **Swap Hooks**: `useSwapContext` +- **MiniKit Hooks**: All hooks unchanged + +### Type System + +#### Enhanced Types +- Updated type names for better consistency +- **v0.x**: `IsBaseOptions`, `IsEthereumOptions` +- **v1.0**: `IsBaseParams`, `IsEthereumParams` + +#### New Type Categories +- Better TypeScript integration across all modules +- Enhanced generic type support + +## Infrastructure Changes + +### Dependencies + +#### Added +- **@floating-ui/react**: `^0.27.13` +- **@radix-ui/react-dialog**: `^1.1.14` +- **@radix-ui/react-popover**: `^1.1.14` +- **@radix-ui/react-tabs**: `^1.1.3` +- **@radix-ui/react-toast**: `^1.2.14` +- **usehooks-ts**: `^3.1.1` + +#### Updated +- **tailwind-merge**: `^2.3.0` → `^3.2.0` +- **@tanstack/react-query**: Requires `^5` +- **React/React-DOM**: `^18` → `19.1.0` (development) + +### Development Dependencies + +#### Added +- **@tailwindcss/cli**: `^4.1.4` +- **@tailwindcss/postcss**: `^4.1.4` +- **postcss-import**: `^16.1.0` +- **postcss-load-config**: `^6.0.1` + +#### Updated +- **tailwindcss**: `^3.4.3` → `^4.1.6` +- **@testing-library/react**: `^14.2.0` → `^16.3.0` +- **@types/react**: `^18` → `19.1.2` +- **@types/react-dom**: `^18` → `19.1.3` + +#### Removed +- **packemon**: `3.3.1` (replaced with Vite) + +## Styling System Changes + +### Internal Styling System Upgrade + +#### Internal Changes (No User Action Required) +- **OnchainKit Build**: Now uses Tailwind CSS v4 internally +- **Pre-built Styles**: All styles are compiled and distributed as CSS +- **Self-contained**: No impact on user's own Tailwind setup + +**Note**: You don't need to upgrade your own Tailwind CSS version. OnchainKit's styles are pre-built and imported via `@coinbase/onchainkit/styles.css`. + +### Scoped Styling System + +#### New Features +- **Class Prefixing**: All OnchainKit classes automatically prefixed with `ock-` +- **CSS Variable Scoping**: Theme variables use `--ock-` prefix +- **Data Attribute Theming**: Themes applied via `data-ock-theme` attributes + +#### Breaking Changes for Custom Styling +```css +/* v0.x - Global CSS variables */ +:root { + --text-primary: #000000; + --bg-primary: #ffffff; +} + +/* v1.0 - Scoped CSS variables */ +[data-ock-theme='default-light'] { + --ock-text-foreground: #000000; + --ock-background: #ffffff; +} +``` + +#### New Scoped Color System +- `ock-foreground`, `ock-foreground-muted`, `ock-foreground-inverse` +- `ock-background`, `ock-background-hover`, `ock-background-active` +- `ock-primary`, `ock-secondary`, `ock-error`, `ock-warning`, `ock-success` + +### Theme Configuration Changes + +#### v0.x Theme Application +```tsx +// Applied globally + +``` + +#### v1.0 Theme Application +```tsx +// Applied via data attributes + +``` + +## Configuration Changes + +### Package.json Structure + +#### Package Exports +All existing exports remain the same with no additions or removals. + +#### Build Configuration +- Transitioned from custom build scripts to Vite +- Simplified build pipeline +- Enhanced TypeScript integration + +### Workspace Configuration + +#### Updated +- **pnpm-workspace.yaml**: Added `templates/*` to workspace packages + +#### Enhanced Filters +- Added `f:minikit-example` filter shortcut + +## Migration Impact + +### Low Impact Changes +- **Component APIs**: No breaking changes +- **Hook APIs**: No breaking changes +- **Import paths**: All existing imports work +- **TypeScript**: Enhanced but backward compatible + +### Medium Impact Changes +- **Provider setup**: Requires migration from MiniKitProvider +- **Dependencies**: Must add peer dependencies +- **Farcaster SDK**: Requires package update +- **Custom theming**: CSS variables need `--ock-` prefix migration (if customizing OnchainKit themes) + +### High Impact Changes +- **React version**: Must upgrade to React 19 +- **Build system**: Projects using custom builds may need updates +- **Styling conflicts**: Existing CSS may conflict with new scoped system + +## Compatibility Matrix + +| Feature | v0.x | v1.0 | Compatible | +|---------|------|------|------------| +| Component APIs | ✅ | ✅ | ✅ | +| Hook APIs | ✅ | ✅ | ✅ | +| Import paths | ✅ | ✅ | ✅ | +| MiniKitProvider | ✅ | ❌ | ❌ | +| OnchainKitProvider | ✅ | ✅ | ⚠️ (enhanced) | +| React 18 | ✅ | ❌ | ❌ | +| React 19 | ✅ | ✅ | ✅ | +| Viem/Wagmi as deps | Optional | Required | ⚠️ | +| User's Tailwind v3/v4 | ✅ | ✅ | ✅ (no impact) | +| Global CSS variables | ✅ | ❌ | ❌ | +| Scoped CSS variables | ❌ | ✅ | ⚠️ (new system) | +| Render props | ❌ | ✅ | ✅ (additive) | + +## Summary + +OnchainKit v1.0.0 is a **major version** with breaking changes primarily focused on: + +1. **Provider consolidation** (MiniKitProvider → OnchainKitProvider) +2. **Dependency management** (explicit peer dependencies) +3. **React 19 requirement** (React 18 support removed) +4. **Build system modernization** (Vite-based) + +The **component and hook APIs remain backward compatible**, making the upgrade process straightforward for most applications. \ No newline at end of file diff --git a/alpha-upgrade-guide.md b/alpha-upgrade-guide.md new file mode 100644 index 0000000000..18e70996cf --- /dev/null +++ b/alpha-upgrade-guide.md @@ -0,0 +1,272 @@ +# OnchainKit v1.0.0 Upgrade Guide + +This guide will help you upgrade from OnchainKit v0.x to v1.0.0. The v1.0.0 release includes breaking changes that require code updates. + +## Starting a new project? + +Bootstrap a new project using OnchainKit v1.0.0@alpha with the following command: + +```bash +npx create-onchain@alpha // Traditional web app +npx create-onchain@alpha --mini // Mini app +``` + +## 📋 Prerequisites + +Before upgrading, ensure your project meets these requirements: + +- **React**: v19 (upgraded from v18 || v19) +- **Node.js**: v20 or higher +- **TypeScript**: v5.8+ (recommended) + +## 🚀 Quick Start + +### 1. Update Dependencies + +Update your package.json to use the new version and add required peer dependencies: + +```bash +npm install @coinbase/onchainkit@1.0.0 viem@^2.27 wagmi@^2.16 +``` + +Or with pnpm: +```bash +pnpm add @coinbase/onchainkit@1.0.0 viem@^2.27 wagmi@^2.16 +``` + +### 2. Update Your Provider Setup + +**Before (v0.x):** +```tsx +import { MiniKitProvider } from "@coinbase/onchainkit/minikit"; + +export function Providers({ children }) { + return ( + + {children} + + ); +} +``` + +**After (v1.0):** +```tsx +import { OnchainKitProvider } from "@coinbase/onchainkit"; +import "@coinbase/onchainkit/styles.css"; + +export function RootProvider({ children }) { + return ( + + {children} + + ); +} +``` + +### 3. Update Package Dependencies + +The Farcaster SDK has been updated: + +**Before (v0.x):** +```json +{ + "dependencies": { + "@farcaster/frame-sdk": "^0.1.8" + } +} +``` + +**After (v1.0):** +```json +{ + "dependencies": { + "@farcaster/miniapp-sdk": "^0.1.8" + } +} +``` + +## 🔄 Breaking Changes + +### Provider Consolidation + +The `MiniKitProvider` has been consolidated into `OnchainKitProvider` with MiniKit configuration: + +- ✅ **New**: Use `OnchainKitProvider` with `miniKit` config +- ❌ **Removed**: `MiniKitProvider` as a separate provider + +### Peer Dependencies + +v1.0.0 requires explicit peer dependencies for better version control: + +- **Added**: `viem: "^2.27"` +- **Added**: `wagmi: "^2.16"` +- **Updated**: `react: "^19"` (no longer supports React 18) + +### SDK Dependency Change + +Farcaster integration has moved to the newer SDK: + +- **Before**: `@farcaster/frame-sdk` +- **After**: `@farcaster/miniapp-sdk` + +## 📦 New Features in v1.0.0 + +### Scoped Styling System +v1.0.0 includes a completely redesigned styling system built with Tailwind CSS v4. However, **you don't need to upgrade your own Tailwind setup** - OnchainKit's styles are pre-built and self-contained. + +Additionally, all styles are now scoped to prevent conflicts with consumer app theming: + +- **Class Prefixing**: All OnchainKit classes are automatically prefixed with `ock:` +- **Theme Variables**: Custom CSS properties use the `--ock-` prefix +- **Data Attributes**: Theming is controlled via `data-ock-theme` attributes + +```tsx +// Themes are applied via data attribute to prevent conflicts + + {/* Your app content */} + +``` + +### Render Props Support +Some components now support render props for complete customization: + +```tsx +// ConnectWallet with render prop + ( + + )} +/> + +// SignatureButton with render prop + ( + + )} +/> +``` + +### Enhanced Build Configuration +- Simplified build scripts +- Updated to Vite-based build system +- Better TypeScript integration + +### Improved Dependencies +- Adopted Radix UI components for better accessibility and consistency +- Enhanced Tailwind CSS integration + +## 🔧 Migration Steps + +### Step 1: Update Dependencies +```bash +npm install @coinbase/onchainkit@1.0.0 react@19 react-dom@19 viem@^2.27 wagmi@^2.16 +``` + +### Step 2: Update Provider +Replace `MiniKitProvider` with `OnchainKitProvider` and add MiniKit configuration. + +### Step 3: Update Package References +Change `@farcaster/frame-sdk` to `@farcaster/miniapp-sdk` in your package.json. + +### Step 4: Migrate Custom Theming +If you have custom OnchainKit theming, update CSS variables to use the `--ock-` prefix: + +```css +/* Before (v0.x) */ +:root { + --text-primary: #000000; + --bg-primary: #ffffff; +} + +/* After (v1.0) */ +[data-ock-theme='custom'] { + --ock-text-foreground: #000000; + --ock-background: #ffffff; +} +``` + +### Step 5: Test Your Application +Run your application and verify all components work as expected. + +## 🎯 Component API Compatibility + +Most component APIs remain the same between v0.x and v1.0.0: + +- ✅ **Identity components**: No breaking changes +- ✅ **Wallet components**: No breaking changes +- ✅ **Transaction components**: No breaking changes +- ✅ **Swap components**: No breaking changes +- ✅ **All other components**: Backward compatible + +## 🆘 Troubleshooting + +### Common Issues + +**Issue**: Build fails with peer dependency warnings +**Solution**: Ensure you've installed the required peer dependencies: +```bash +npm install viem@^2.27 wagmi@^2.16 +``` + +**Issue**: MiniKitProvider not found +**Solution**: Replace with OnchainKitProvider and add miniKit config + +**Issue**: React 18 compatibility errors +**Solution**: Upgrade to React 19: +```bash +npm install react@19 react-dom@19 +``` + +### Getting Help + +- 📖 [OnchainKit Documentation](https://docs.base.org/onchainkit/getting-started) +- 💬 [Base Discord](https://discord.com/invite/buildonbase) +- 🐛 [GitHub Issues](https://github.com/coinbase/onchainkit/issues) + +## ✅ Migration Checklist + +- [ ] Updated to React 19 +- [ ] Installed OnchainKit v1.0.0 +- [ ] Added viem and wagmi peer dependencies +- [ ] Replaced MiniKitProvider with OnchainKitProvider +- [ ] Updated @farcaster/frame-sdk to @farcaster/miniapp-sdk +- [ ] Added @coinbase/onchainkit/styles.css import +- [ ] Configured miniKit options in OnchainKitProvider +- [ ] Migrated custom CSS variables to use `--ock-` prefix (if customizing OnchainKit themes) +- [ ] Tested render props functionality (if used) +- [ ] Tested all existing functionality +- [ ] Updated any custom styling or CSS + +Congratulations! You've successfully upgraded to OnchainKit v1.0.0 🎉 diff --git a/eslint.config.mjs b/eslint.config.mjs index 6ae900227e..c594b30cd5 100644 --- a/eslint.config.mjs +++ b/eslint.config.mjs @@ -53,4 +53,11 @@ export default [ ], }, }, + { + settings: { + react: { + version: 'detect', + }, + }, + }, ]; diff --git a/examples/minikit-example/.env.example b/examples/minikit-example/.env.example deleted file mode 100644 index 2417b667d8..0000000000 --- a/examples/minikit-example/.env.example +++ /dev/null @@ -1,33 +0,0 @@ -# Shared/OnchainKit variables - -NEXT_PUBLIC_ONCHAINKIT_PROJECT_NAME=minikit-example -NEXT_PUBLIC_URL= -NEXT_PUBLIC_ICON_URL=$NEXT_PUBLIC_URL/logo.png -NEXT_PUBLIC_ONCHAINKIT_API_KEY= - -# Frame metadata - -FARCASTER_HEADER= -FARCASTER_PAYLOAD= -FARCASTER_SIGNATURE= -NEXT_PUBLIC_APP_ICON=$NEXT_PUBLIC_URL/icon.png -# Optional Frame metadata items below -NEXT_PUBLIC_APP_SUBTITLE= -NEXT_PUBLIC_APP_DESCRIPTION= -NEXT_PUBLIC_APP_SPLASH_IMAGE=$NEXT_PUBLIC_URL/splash.png -NEXT_PUBLIC_SPLASH_BACKGROUND_COLOR="#000000" -NEXT_PUBLIC_APP_PRIMARY_CATEGORY= -NEXT_PUBLIC_APP_HERO_IMAGE=$NEXT_PUBLIC_URL/hero.png -NEXT_PUBLIC_APP_TAGLINE= -NEXT_PUBLIC_APP_OG_TITLE=minikit-example -NEXT_PUBLIC_APP_OG_DESCRIPTION= -NEXT_PUBLIC_APP_OG_IMAGE=$NEXT_PUBLIC_URL/hero.png - -# Neynar - -NEYNAR_API_KEY= - -# Redis config - -REDIS_URL= -REDIS_TOKEN= diff --git a/examples/minikit-example/.eslintrc.json b/examples/minikit-example/.eslintrc.json deleted file mode 100644 index 3722418549..0000000000 --- a/examples/minikit-example/.eslintrc.json +++ /dev/null @@ -1,3 +0,0 @@ -{ - "extends": ["next/core-web-vitals", "next/typescript"] -} diff --git a/examples/minikit-example/README.md b/examples/minikit-example/README.md index 43e4c6f293..25426b6fcf 100644 --- a/examples/minikit-example/README.md +++ b/examples/minikit-example/README.md @@ -1,62 +1,39 @@ -# MiniKit Example +This is a [Next.js](https://nextjs.org) project bootstrapped with [`create-onchain`](https://www.npmjs.com/package/create-onchain). -A demonstration mini app built with Next.js that showcases the various actions and capabilities available in MiniKit. - -## Purpose - -This project serves as a comprehensive example for developers who want to: - -- Learn how to integrate MiniKit functionality into their applications -- Understand the different actions available in the MiniKit SDK -- See practical implementations of common mini app features -- Test MiniKit functionality both inside and outside the Warpcast environment - -## Features - -### Core MiniKit Actions - -- **🔍 Environment Detection** - Displays whether the app is running inside a mini app context -- **➕ Add Frame** - Add frames to the Warpcast client -- **✍️ Compose Cast** - Create new casts directly from the mini app -- **👀 View Cast** - Navigate to and view existing casts -- **❌ Close Frame** - Properly close the mini app when needed ## Getting Started -1. Clone the repository: - ```bash - git clone https://github.com/coinbase/onchainkit.git - ``` +First, install dependencies: -2. Install dependencies from the root: - ```bash - pnpm install - ``` +```bash +npm install +# or +yarn install +# or +pnpm install +# or +bun install +``` -3. Start the development server: - ```bash - pnpm f:minikit-example dev:watch - ``` +Next, run the development server: -4. Open [http://localhost:3000](http://localhost:3000) in your browser +```bash +npm run dev +# or +yarn dev +# or +pnpm dev +# or +bun dev +``` -## Key Components +Open [http://localhost:3000](http://localhost:3000) with your browser to see the result. -Each MiniKit action is implemented as a separate, reusable component: +You can start editing the page by modifying `app/page.tsx`. The page auto-updates as you edit the file. -- **`IsInMiniApp`**: Detects and displays the current environment status -- **`AddFrame`**: Handles adding frames to the Warpcast client -- **`ComposeCast`**: Manages cast composition with pre-filled text -- **`ViewCast`**: Navigates to specific casts using their hash -- **`CloseFrame`**: Provides a way to close the mini app ## Learn More -- [MiniKit Documentation](https://base.org/builders/minikit) -- [OnchainKit Documentation](https://onchainkit.xyz) -- [Warpcast Developer Resources](https://warpcast.com/~/developers) -- [Base Developer Portal](https://base.org/builders) - -## Contributing +To learn more about OnchainKit, see our [documentation](https://docs.base.org/onchainkit). -This example is part of the OnchainKit project. Feel free to submit issues and pull requests to help improve the examples and documentation. +To learn more about Next.js, see the [Next.js documentation](https://nextjs.org/docs). diff --git a/examples/minikit-example/app/.well-known/farcaster.json/route.ts b/examples/minikit-example/app/.well-known/farcaster.json/route.ts index 3c6a7f8cb0..6240aab741 100644 --- a/examples/minikit-example/app/.well-known/farcaster.json/route.ts +++ b/examples/minikit-example/app/.well-known/farcaster.json/route.ts @@ -1,43 +1,6 @@ -function withValidProperties( - properties: Record, -) { - return Object.fromEntries( - Object.entries(properties).filter(([key, value]) => { - if (Array.isArray(value)) { - return value.length > 0; - } - return !!value; - }), - ); -} +import { withValidManifest } from "@coinbase/onchainkit/minikit"; +import { minikitConfig } from "../../../minikit.config"; export async function GET() { - const URL = process.env.NEXT_PUBLIC_URL; - - return Response.json({ - accountAssociation: { - header: process.env.FARCASTER_HEADER, - payload: process.env.FARCASTER_PAYLOAD, - signature: process.env.FARCASTER_SIGNATURE, - }, - frame: withValidProperties({ - version: "1", - name: process.env.NEXT_PUBLIC_ONCHAINKIT_PROJECT_NAME, - subtitle: process.env.NEXT_PUBLIC_APP_SUBTITLE, - description: process.env.NEXT_PUBLIC_APP_DESCRIPTION, - screenshotUrls: [], - iconUrl: process.env.NEXT_PUBLIC_APP_ICON, - splashImageUrl: process.env.NEXT_PUBLIC_APP_SPLASH_IMAGE, - splashBackgroundColor: process.env.NEXT_PUBLIC_SPLASH_BACKGROUND_COLOR, - homeUrl: URL, - webhookUrl: `${URL}/api/webhook`, - primaryCategory: process.env.NEXT_PUBLIC_APP_PRIMARY_CATEGORY, - tags: [], - heroImageUrl: process.env.NEXT_PUBLIC_APP_HERO_IMAGE, - tagline: process.env.NEXT_PUBLIC_APP_TAGLINE, - ogTitle: process.env.NEXT_PUBLIC_APP_OG_TITLE, - ogDescription: process.env.NEXT_PUBLIC_APP_OG_DESCRIPTION, - ogImageUrl: process.env.NEXT_PUBLIC_APP_OG_IMAGE, - }), - }); + return Response.json(withValidManifest(minikitConfig)); } diff --git a/examples/minikit-example/app/actions/AddFrame.tsx b/examples/minikit-example/app/actions/AddFrame.tsx index 56a77473a8..ccca714c77 100644 --- a/examples/minikit-example/app/actions/AddFrame.tsx +++ b/examples/minikit-example/app/actions/AddFrame.tsx @@ -1,18 +1,13 @@ "use client"; import { useMiniKit, useAddFrame } from "@coinbase/onchainkit/minikit"; -import { Button } from "../ui/Button"; +import { Button } from "@mantine/core"; export function AddFrame() { const { context } = useMiniKit(); const addFrame = useAddFrame(); return ( - ); diff --git a/examples/minikit-example/app/actions/BatchedTransaction.tsx b/examples/minikit-example/app/actions/BatchedTransaction.tsx new file mode 100644 index 0000000000..8c9ebc1ea1 --- /dev/null +++ b/examples/minikit-example/app/actions/BatchedTransaction.tsx @@ -0,0 +1,424 @@ +"use client"; +import { useState } from "react"; +import { + Button, + Stack, + Text, + Alert, + Divider, + Group, + NumberInput, + TextInput, + Switch, +} from "@mantine/core"; +import { Transaction } from "@coinbase/onchainkit/transaction"; +import { encodeFunctionData, type Hex } from "viem"; +import { useAccount, useReadContract, useSendCalls } from "wagmi"; + +type Call = { to: Hex; data?: Hex; value?: bigint }; + +/** + * Verified contract used for testing batched transactions. + * @link `@https://basescan.org/address/0xe4d55cdf9a58157cf03b556b33b84e9d6c8c39e2#code` + */ +const CONTRACT_ADDRESS = "0xE4D55cDf9a58157CF03B556B33b84e9d6C8c39e2" as const; +const CONTRACT_ABI = [ + // Write functions + { + inputs: [], + name: "incrementCounter", + outputs: [], + stateMutability: "nonpayable", + type: "function", + }, + { + inputs: [{ internalType: "uint256", name: "amount", type: "uint256" }], + name: "incrementCounterBy", + outputs: [], + stateMutability: "nonpayable", + type: "function", + }, + { + inputs: [{ internalType: "uint256", name: "value", type: "uint256" }], + name: "setValue", + outputs: [], + stateMutability: "nonpayable", + type: "function", + }, + { + inputs: [{ internalType: "string", name: "name", type: "string" }], + name: "setName", + outputs: [], + stateMutability: "nonpayable", + type: "function", + }, + { + inputs: [], + name: "alwaysFail", + outputs: [], + stateMutability: "pure", + type: "function", + }, + // Read functions + { + inputs: [], + name: "counter", + outputs: [{ internalType: "uint256", name: "", type: "uint256" }], + stateMutability: "view", + type: "function", + }, + { + inputs: [{ internalType: "address", name: "user", type: "address" }], + name: "userValues", + outputs: [{ internalType: "uint256", name: "", type: "uint256" }], + stateMutability: "view", + type: "function", + }, + { + inputs: [{ internalType: "address", name: "user", type: "address" }], + name: "userNames", + outputs: [{ internalType: "string", name: "", type: "string" }], + stateMutability: "view", + type: "function", + }, +] as const; + +export function BatchedTransaction() { + const { isConnected, address } = useAccount(); + const [approach, setApproach] = useState<"onchainkit" | "direct">( + "onchainkit", + ); + const [incrementAmount, setIncrementAmount] = useState(5); + const [setValue, setSetValue] = useState(42); + const [setName, setSetName] = useState("Mini App User"); + const [includeFailure, setIncludeFailure] = useState(false); + const [onchainKitError, setOnchainKitError] = useState(null); + const [onchainKitSuccess, setOnchainKitSuccess] = useState( + null, + ); + const [directError, setDirectError] = useState(null); + const [directSuccess, setDirectSuccess] = useState(null); + + // Contract reading hooks + const { data: counter, refetch: refetchCounter } = useReadContract({ + address: CONTRACT_ADDRESS, + abi: CONTRACT_ABI, + functionName: "counter", + }); + + const { data: userValue, refetch: refetchUserValue } = useReadContract({ + address: CONTRACT_ADDRESS, + abi: CONTRACT_ABI, + functionName: "userValues", + args: address ? [address] : undefined, + query: { enabled: !!address }, + }); + + const { data: userName, refetch: refetchUserName } = useReadContract({ + address: CONTRACT_ADDRESS, + abi: CONTRACT_ABI, + functionName: "userNames", + args: address ? [address] : undefined, + query: { enabled: !!address }, + }); + + // Direct batched approach using Wagmi + const { + sendCalls, + isPending: isDirectPending, + error: directWriteError, + } = useSendCalls(); + + // Create batched calls (shared by both approaches) + const createBatchedCalls = (): Call[] => { + const calls: Call[] = []; + + // Always increment counter + calls.push({ + to: CONTRACT_ADDRESS, + data: encodeFunctionData({ + abi: CONTRACT_ABI, + functionName: "incrementCounter", + }), + }); + + // Increment by amount if specified + if (incrementAmount && incrementAmount > 0) { + calls.push({ + to: CONTRACT_ADDRESS, + data: encodeFunctionData({ + abi: CONTRACT_ABI, + functionName: "incrementCounterBy", + args: [BigInt(incrementAmount)], + }), + }); + } + + // Set value if specified + if (setValue && setValue > 0) { + calls.push({ + to: CONTRACT_ADDRESS, + data: encodeFunctionData({ + abi: CONTRACT_ABI, + functionName: "setValue", + args: [BigInt(setValue)], + }), + }); + } + + // Set name if specified + if (setName.trim()) { + calls.push({ + to: CONTRACT_ADDRESS, + data: encodeFunctionData({ + abi: CONTRACT_ABI, + functionName: "setName", + args: [setName], + }), + }); + } + + // Include failing call if requested + if (includeFailure) { + calls.push({ + to: CONTRACT_ADDRESS, + data: encodeFunctionData({ + abi: CONTRACT_ABI, + functionName: "alwaysFail", + }), + }); + } + + return calls; + }; + + // Refresh contract data + const refreshContractData = () => { + refetchCounter(); + refetchUserValue(); + refetchUserName(); + }; + + // Handle direct batched approach + const handleDirectTransaction = () => { + try { + setDirectError(null); + setDirectSuccess(null); + + const calls = createBatchedCalls(); + + // Use sendCalls to submit the same batched transactions + sendCalls({ + calls: calls, + }); + + setDirectSuccess( + `Batched transaction initiated with ${calls.length} calls`, + ); + // Refresh contract data after a delay to allow transaction to be mined + setTimeout(refreshContractData, 2000); + } catch (error) { + console.error("Direct transaction error:", error); + setDirectError( + error instanceof Error ? error.message : "Transaction failed", + ); + } + }; + + const isDisabled = !isConnected; + + return ( + + + Batched Transaction Example + + + + This demonstrates batched transactions using both OnchainKit's + Transaction component and Wagmi's useSendCalls hook. Both + approaches submit the same batched calls for direct comparison. Batched + transactions work with smart contract wallets and EOA wallets that + support ERC-7702. Contract: {CONTRACT_ADDRESS} + + + {/* Contract State Section */} + + + Current Contract State: + + + + + + + Counter: + {" "} + {counter?.toString() || "0"} + + {address && ( + <> + + + Your Value: + {" "} + {userValue?.toString() || "0"} + + + + Your Name: + {" "} + {userName || "Not set"} + + + )} + + + + + + {/* Configuration Section */} + + Transaction Configuration: + + + + + + + + setIncrementAmount( + typeof value === "string" ? (value as "") : value, + ) + } + min={0} + /> + + + setSetValue(typeof value === "string" ? (value as "") : value) + } + min={0} + /> + + setSetName(e.target.value)} + /> + + setIncludeFailure(event.currentTarget.checked)} + color="red" + /> + + + + + {/* OnchainKit Approach */} + {approach === "onchainkit" && ( + + OnchainKit Transaction Component: + + { + console.error("OnchainKit transaction error:", error); + setOnchainKitError(error.message || "Transaction failed"); + setOnchainKitSuccess(null); + }} + onSuccess={(response) => { + console.log("OnchainKit transaction success:", response); + setOnchainKitSuccess( + `Success! ${response.transactionReceipts.length} transaction(s) completed`, + ); + setOnchainKitError(null); + // Refresh contract data after a delay to allow transaction to be mined + setTimeout(refreshContractData, 2000); + }} + /> + + {onchainKitError && ( + + {onchainKitError} + + )} + + {onchainKitSuccess && ( + + {onchainKitSuccess} + + )} + + )} + + {/* Direct Wagmi Batched Approach */} + {approach === "direct" && ( + + Direct Wagmi Batched Approach: + + + This uses Wagmi's useSendCalls hook to submit the same batched + transactions as OnchainKit, allowing direct comparison of both + approaches. + + + + + {directWriteError && ( + + {directWriteError.message} + + )} + + {directError && ( + + {directError} + + )} + + {directSuccess && ( + + {directSuccess} + + )} + + )} + + {!isConnected && ( + + Please connect your wallet to test batched transactions. + + )} + + ); +} diff --git a/examples/minikit-example/app/actions/CloseFrame.tsx b/examples/minikit-example/app/actions/CloseFrame.tsx index eb94d19148..b7294ee603 100644 --- a/examples/minikit-example/app/actions/CloseFrame.tsx +++ b/examples/minikit-example/app/actions/CloseFrame.tsx @@ -1,18 +1,13 @@ "use client"; import { useClose, useIsInMiniApp } from "@coinbase/onchainkit/minikit"; -import { Button } from "../ui/Button"; +import { Button } from "@mantine/core"; export function CloseFrame() { const { isInMiniApp } = useIsInMiniApp(); const closeFrame = useClose(); return ( - ); diff --git a/examples/minikit-example/app/actions/ComposeCast.tsx b/examples/minikit-example/app/actions/ComposeCast.tsx index 7e172c4cd5..401517afb0 100644 --- a/examples/minikit-example/app/actions/ComposeCast.tsx +++ b/examples/minikit-example/app/actions/ComposeCast.tsx @@ -1,6 +1,6 @@ "use client"; import { useComposeCast, useIsInMiniApp } from "@coinbase/onchainkit/minikit"; -import { Button } from "../ui/Button"; +import { Button } from "@mantine/core"; export function ComposeCast() { const { isInMiniApp } = useIsInMiniApp(); diff --git a/examples/minikit-example/app/actions/IsInMiniApp.tsx b/examples/minikit-example/app/actions/IsInMiniApp.tsx index 52c36acd91..c064d0fe10 100644 --- a/examples/minikit-example/app/actions/IsInMiniApp.tsx +++ b/examples/minikit-example/app/actions/IsInMiniApp.tsx @@ -1,12 +1,9 @@ "use client"; import { useIsInMiniApp } from "@coinbase/onchainkit/minikit"; +import { Text } from "@mantine/core"; export function IsInMiniApp() { const { isInMiniApp } = useIsInMiniApp(); - return ( -

- Are we in a mini app? {isInMiniApp ? "Yes! 🎉" : "No 😢"} -

- ); + return Are we in a mini app? {isInMiniApp ? "Yes! 🎉" : "No 😢"}; } diff --git a/examples/minikit-example/app/actions/SendToken.tsx b/examples/minikit-example/app/actions/SendToken.tsx index accbe7bab9..dcd29c76f3 100644 --- a/examples/minikit-example/app/actions/SendToken.tsx +++ b/examples/minikit-example/app/actions/SendToken.tsx @@ -1,6 +1,6 @@ "use client"; import { useSendToken, useIsInMiniApp } from "@coinbase/onchainkit/minikit"; -import { Button } from "../ui/Button"; +import { Button } from "@mantine/core"; export function SendToken() { const { isInMiniApp } = useIsInMiniApp(); diff --git a/examples/minikit-example/app/actions/SwapToken.tsx b/examples/minikit-example/app/actions/SwapToken.tsx index eb5a55493e..0e7bbb6163 100644 --- a/examples/minikit-example/app/actions/SwapToken.tsx +++ b/examples/minikit-example/app/actions/SwapToken.tsx @@ -1,6 +1,6 @@ "use client"; import { useSwapToken, useIsInMiniApp } from "@coinbase/onchainkit/minikit"; -import { Button } from "../ui/Button"; +import { Button } from "@mantine/core"; export function SwapToken() { const { isInMiniApp } = useIsInMiniApp(); diff --git a/examples/minikit-example/app/actions/ViewCast.tsx b/examples/minikit-example/app/actions/ViewCast.tsx index 92ff74daaf..4d99dee7e9 100644 --- a/examples/minikit-example/app/actions/ViewCast.tsx +++ b/examples/minikit-example/app/actions/ViewCast.tsx @@ -1,6 +1,6 @@ "use client"; import { useViewCast, useIsInMiniApp } from "@coinbase/onchainkit/minikit"; -import { Button } from "../ui/Button"; +import { Button } from "@mantine/core"; export function ViewCast() { const { isInMiniApp } = useIsInMiniApp(); diff --git a/examples/minikit-example/app/api/me/route.ts b/examples/minikit-example/app/api/me/route.ts index ccdadcaf07..6eaf6ca012 100644 --- a/examples/minikit-example/app/api/me/route.ts +++ b/examples/minikit-example/app/api/me/route.ts @@ -3,9 +3,26 @@ import { NextRequest, NextResponse } from "next/server"; const client = createClient(); -function getUrlHost() { - let urlValue: string; +function getUrlHost(request: NextRequest) { + // First try to get the origin from the Origin header (most reliable for CORS requests) + const origin = request.headers.get("origin"); + if (origin) { + try { + const url = new URL(origin); + return url.host; + } catch (error) { + console.warn("Invalid origin header:", origin, error); + } + } + + // Fallback to Host header + const host = request.headers.get("host"); + if (host) { + return host; + } + // Final fallback to environment variables (your original logic) + let urlValue: string; if (process.env.VERCEL_ENV === "production") { urlValue = process.env.NEXT_PUBLIC_URL!; } else if (process.env.VERCEL_URL) { @@ -15,7 +32,6 @@ function getUrlHost() { } const url = new URL(urlValue); - return url.host; } @@ -35,9 +51,11 @@ export async function GET(request: NextRequest) { // based on the Vercel environment. This will vary depending on your hosting provider. const payload = await client.verifyJwt({ token: authorization.split(" ")[1] as string, - domain: getUrlHost(), + domain: getUrlHost(request), }); + console.log("payload", payload); + // If the token was valid, `payload.sub` will be the user's Farcaster ID. const userFid = payload.sub; diff --git a/examples/minikit-example/app/api/notify/route.ts b/examples/minikit-example/app/api/notify/route.ts deleted file mode 100644 index eefa1a9116..0000000000 --- a/examples/minikit-example/app/api/notify/route.ts +++ /dev/null @@ -1,32 +0,0 @@ -import { sendFrameNotification } from "@/lib/notification-client"; -import { NextResponse } from "next/server"; - -export async function POST(request: Request) { - try { - const body = await request.json(); - const { fid, notification } = body; - - const result = await sendFrameNotification({ - fid, - title: notification.title, - body: notification.body, - notificationDetails: notification.notificationDetails, - }); - - if (result.state === "error") { - return NextResponse.json( - { error: result.error }, - { status: 500 }, - ); - } - - return NextResponse.json({ success: true }, { status: 200 }); - } catch (error) { - return NextResponse.json( - { - error: error instanceof Error ? error.message : "Unknown error", - }, - { status: 400 }, - ); - } -} diff --git a/examples/minikit-example/app/api/webhook/route.ts b/examples/minikit-example/app/api/webhook/route.ts deleted file mode 100644 index fd166a9997..0000000000 --- a/examples/minikit-example/app/api/webhook/route.ts +++ /dev/null @@ -1,124 +0,0 @@ -import { - setUserNotificationDetails, - deleteUserNotificationDetails, -} from "@/lib/notification"; -import { sendFrameNotification } from "@/lib/notification-client"; -import { http } from "viem"; -import { createPublicClient } from "viem"; -import { optimism } from "viem/chains"; - -const appName = process.env.NEXT_PUBLIC_ONCHAINKIT_PROJECT_NAME; - -const KEY_REGISTRY_ADDRESS = "0x00000000Fc1237824fb747aBDE0FF18990E59b7e"; - -const KEY_REGISTRY_ABI = [ - { - inputs: [ - { name: "fid", type: "uint256" }, - { name: "key", type: "bytes" }, - ], - name: "keyDataOf", - outputs: [ - { - components: [ - { name: "state", type: "uint8" }, - { name: "keyType", type: "uint32" }, - ], - name: "", - type: "tuple", - }, - ], - stateMutability: "view", - type: "function", - }, -] as const; - -async function verifyFidOwnership(fid: number, appKey: `0x${string}`) { - const client = createPublicClient({ - chain: optimism, - transport: http(), - }); - - try { - const result = await client.readContract({ - address: KEY_REGISTRY_ADDRESS, - abi: KEY_REGISTRY_ABI, - functionName: "keyDataOf", - args: [BigInt(fid), appKey], - }); - - return result.state === 1 && result.keyType === 1; - } catch (error) { - console.error("Key Registry verification failed:", error); - return false; - } -} - -function decode(encoded: string) { - return JSON.parse(Buffer.from(encoded, "base64url").toString("utf-8")); -} - -export async function POST(request: Request) { - const requestJson = await request.json(); - - const { header: encodedHeader, payload: encodedPayload } = requestJson; - - const headerData = decode(encodedHeader); - const event = decode(encodedPayload); - - const { fid, key } = headerData; - - const valid = await verifyFidOwnership(fid, key); - - if (!valid) { - return Response.json( - { success: false, error: "Invalid FID ownership" }, - { status: 401 }, - ); - } - - switch (event.event) { - case "frame_added": - console.log( - "frame_added", - "event.notificationDetails", - event.notificationDetails, - ); - if (event.notificationDetails) { - await setUserNotificationDetails(fid, event.notificationDetails); - await sendFrameNotification({ - fid, - title: `Welcome to ${appName}`, - body: `Thank you for adding ${appName}`, - }); - } else { - await deleteUserNotificationDetails(fid); - } - - break; - case "frame_removed": { - console.log("frame_removed"); - await deleteUserNotificationDetails(fid); - break; - } - case "notifications_enabled": { - console.log("notifications_enabled", event.notificationDetails); - await setUserNotificationDetails(fid, event.notificationDetails); - await sendFrameNotification({ - fid, - title: `Welcome to ${appName}`, - body: `Thank you for enabling notifications for ${appName}`, - }); - - break; - } - case "notifications_disabled": { - console.log("notifications_disabled"); - await deleteUserNotificationDetails(fid); - - break; - } - } - - return Response.json({ success: true }); -} diff --git a/examples/minikit-example/app/components/Context.tsx b/examples/minikit-example/app/components/Context.tsx new file mode 100644 index 0000000000..353739703f --- /dev/null +++ b/examples/minikit-example/app/components/Context.tsx @@ -0,0 +1,19 @@ +import { useMiniKit } from "@coinbase/onchainkit/minikit"; +import { Card, Code, ScrollArea, Title } from "@mantine/core"; + +export function Context() { + const { context } = useMiniKit(); + + return ( + + + Mini app context + + +
+          {JSON.stringify(context ?? {}, null, 2)}
+        
+
+
+ ); +} diff --git a/examples/minikit-example/app/components/UserInfo.tsx b/examples/minikit-example/app/components/UserInfo.tsx index d2430a6d9d..f3b99f9c9c 100644 --- a/examples/minikit-example/app/components/UserInfo.tsx +++ b/examples/minikit-example/app/components/UserInfo.tsx @@ -1,6 +1,17 @@ +"use client"; import { useIsInMiniApp } from "@coinbase/onchainkit/minikit"; -import sdk from "@farcaster/frame-sdk"; +import sdk from "@farcaster/miniapp-sdk"; import { useQuery } from "@tanstack/react-query"; +import { + Card, + Avatar, + Title, + Text, + Group, + Stack, + Skeleton, + Center, +} from "@mantine/core"; function useUserInfo() { const { isInMiniApp } = useIsInMiniApp(); @@ -11,9 +22,10 @@ function useUserInfo() { // If we're in a mini app context, all we have to do to make an authenticated // request is to use `sdk.quickAuth.fetch`. This will automatically include the // necessary `Authorization` header for the backend to verify. - const result = await sdk.quickAuth.fetch("/api/me"); + const result = await sdk.quickAuth.fetch("/api/me"); const userInfo = await result.json(); + return { displayName: userInfo.display_name, pfpUrl: userInfo.pfp_url, @@ -22,7 +34,7 @@ function useUserInfo() { followingCount: userInfo.following_count, }; }, - enabled: isInMiniApp, + enabled: !!isInMiniApp, }); } @@ -31,75 +43,86 @@ export function UserInfo() { if (isLoading) { return ( -
-
-
-
-
-
-
-
-
+ + + + + + + + + ); } if (error || !data) { return ( -
-

- {error ? "Failed to load user info" : "No user info available"} -

-
+ +
+ + {error ? "Failed to load user info" : "No user info available"} + +
+
); } return ( -
-
+ + {/* Profile Picture */} {data.pfpUrl && ( - {`${data.displayName}'s )} {/* User Info */} -
+ {/* Display Name */} -

+ {data.displayName} - </h2> + {/* Bio */} {data.bio && ( -

+ {data.bio} -

+ )} {/* Follower Stats */} -
-
- + + + {data.followerCount?.toLocaleString() || "0"} - - + + Followers - -
-
- + + + + {data.followingCount?.toLocaleString() || "0"} - - + + Following - -
-
-

-
-
+ + + + + + ); } diff --git a/examples/minikit-example/app/favicon.ico b/examples/minikit-example/app/favicon.ico new file mode 100644 index 0000000000..718d6fea48 Binary files /dev/null and b/examples/minikit-example/app/favicon.ico differ diff --git a/examples/minikit-example/app/globals.css b/examples/minikit-example/app/globals.css index ddb9e40770..04a749bdde 100644 --- a/examples/minikit-example/app/globals.css +++ b/examples/minikit-example/app/globals.css @@ -1,25 +1,53 @@ -@tailwind base; -@tailwind components; -@tailwind utilities; +@layer base { + :root { + --background: #0a0a0a; + --foreground: #ededed; + } -:root { - --background: #ffffff; - --foreground: #111111; -} + html, + body { + max-width: 100vw; + overflow-x: hidden; + } -@media (prefers-color-scheme: dark) { - :root { - --background: #111111; - --foreground: #ffffff; + body { + color: var(--foreground); + background: var(--background); + font-family: var(--font-inter), sans-serif; + -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale; + } + + code { + background-color: rgba(0, 0, 0, 0.05); + border-radius: 6px; + border: 1px solid rgba(0, 0, 0, 0.1); + font-family: var(--font-source-code-pro), monospace; + font-size: 0.875rem; + padding: 0.25rem 0.5rem calc(0.25rem + 2px); } -} -body { - color: var(--foreground); - background: var(--background); - font-family: "Geist", sans-serif; - font-optical-sizing: auto; - font-weight: 400; - font-style: normal; - font-size: 80%; + @media (prefers-color-scheme: dark) { + code { + background-color: rgba(255, 255, 255, 0.05); + border: 1px solid rgba(255, 255, 255, 0.1); + } + } + + * { + box-sizing: border-box; + padding: 0; + margin: 0; + } + + a { + color: inherit; + text-decoration: none; + } + + @media (prefers-color-scheme: dark) { + html { + color-scheme: dark; + } + } } diff --git a/examples/minikit-example/app/layout.tsx b/examples/minikit-example/app/layout.tsx index bd1b4d6cd1..974e817652 100644 --- a/examples/minikit-example/app/layout.tsx +++ b/examples/minikit-example/app/layout.tsx @@ -1,33 +1,29 @@ -import "./theme.css"; +import type { Metadata } from "next"; +import { Inter, Source_Code_Pro } from "next/font/google"; +import { minikitConfig } from "@/minikit.config"; +import { RootProvider } from "./rootProvider"; +import { + ColorSchemeScript, + MantineProvider, + mantineHtmlProps, +} from "@mantine/core"; import "@coinbase/onchainkit/styles.css"; -import type { Metadata, Viewport } from "next"; +import "@mantine/core/styles.css"; import "./globals.css"; -import { Providers } from "./providers"; - -export const viewport: Viewport = { - width: "device-width", - initialScale: 1, -}; export async function generateMetadata(): Promise { - const URL = process.env.NEXT_PUBLIC_URL; return { - title: process.env.NEXT_PUBLIC_ONCHAINKIT_PROJECT_NAME, - description: - "Generated by `create-onchain --mini`, a Next.js template for MiniKit", + title: minikitConfig.frame.name, + description: minikitConfig.frame.description, other: { "fc:frame": JSON.stringify({ version: "next", - imageUrl: process.env.NEXT_PUBLIC_APP_HERO_IMAGE, + imageUrl: minikitConfig.frame.heroImageUrl, button: { - title: `Launch ${process.env.NEXT_PUBLIC_ONCHAINKIT_PROJECT_NAME}`, + title: `Launch ${minikitConfig.frame.name}`, action: { + name: `Launch ${minikitConfig.frame.name}`, type: "launch_frame", - name: process.env.NEXT_PUBLIC_ONCHAINKIT_PROJECT_NAME, - url: URL, - splashImageUrl: process.env.NEXT_PUBLIC_SPLASH_IMAGE, - splashBackgroundColor: - process.env.NEXT_PUBLIC_SPLASH_BACKGROUND_COLOR, }, }, }), @@ -35,15 +31,30 @@ export async function generateMetadata(): Promise { }; } +const inter = Inter({ + variable: "--font-inter", + subsets: ["latin"], +}); + +const sourceCodePro = Source_Code_Pro({ + variable: "--font-source-code-pro", + subsets: ["latin"], +}); + export default function RootLayout({ children, }: Readonly<{ children: React.ReactNode; }>) { return ( - - - {children} + + + + + + + {children} + ); diff --git a/examples/minikit-example/app/page.tsx b/examples/minikit-example/app/page.tsx index 8a69a03b14..c91b335cf2 100644 --- a/examples/minikit-example/app/page.tsx +++ b/examples/minikit-example/app/page.tsx @@ -14,15 +14,17 @@ import { WalletDropdownDisconnect, } from "@coinbase/onchainkit/wallet"; import { useEffect } from "react"; -import { Button } from "./ui/Button"; import { IsInMiniApp } from "./actions/IsInMiniApp"; import { AddFrame } from "./actions/AddFrame"; import { ComposeCast } from "./actions/ComposeCast"; import { ViewCast } from "./actions/ViewCast"; -import { SendToken } from "./actions/SendToken"; -import { SwapToken } from "./actions/SwapToken"; import { CloseFrame } from "./actions/CloseFrame"; import { UserInfo } from "./components/UserInfo"; +import { SendToken } from "./actions/SendToken"; +import { SwapToken } from "./actions/SwapToken"; +import { BatchedTransaction } from "./actions/BatchedTransaction"; +import { Anchor, Flex, Stack, Text, Title } from "@mantine/core"; +import { Context } from "./components/Context"; export default function App() { const { setFrameReady, isFrameReady } = useMiniKit(); @@ -35,64 +37,58 @@ export default function App() { }, [setFrameReady, isFrameReady]); return ( -
-
-
-
-
- - - - - - - - -
- - - - - -
-
-
+ + + + + + + + + + +
+ + + + + + -
-
-

- MiniKit Examples -

+ + MiniKit Examples -

- This mini app is meant to show how you can use the actions - available in MiniKit. -

+ + This mini app is meant to show how you can use the actions available + in MiniKit. + -
- - - - - - - - -
-
-
+ + + + + + + + + + + + + -
- -
-
-
+ + Built on Base with{" "} + { + e.preventDefault(); + openUrl("https://base.org/builders/minikit"); + }} + > + MiniKit + + + ); } diff --git a/examples/minikit-example/app/providers.tsx b/examples/minikit-example/app/providers.tsx deleted file mode 100644 index b769b827af..0000000000 --- a/examples/minikit-example/app/providers.tsx +++ /dev/null @@ -1,26 +0,0 @@ -"use client"; - -import { type ReactNode } from "react"; -import { base } from "wagmi/chains"; -import { MiniKitProvider } from "@coinbase/onchainkit/minikit"; - -export function Providers(props: { children: ReactNode }) { - return ( - - {props.children} - - ); -} diff --git a/examples/minikit-example/app/rootProvider.tsx b/examples/minikit-example/app/rootProvider.tsx new file mode 100644 index 0000000000..151c0fb53d --- /dev/null +++ b/examples/minikit-example/app/rootProvider.tsx @@ -0,0 +1,29 @@ +"use client"; +import { ReactNode } from "react"; +import { base } from "wagmi/chains"; +import { OnchainKitProvider } from "@coinbase/onchainkit"; + +export function RootProvider({ children }: { children: ReactNode }) { + return ( + + {children} + + ); +} diff --git a/examples/minikit-example/app/theme.css b/examples/minikit-example/app/theme.css deleted file mode 100644 index 99fa504db3..0000000000 --- a/examples/minikit-example/app/theme.css +++ /dev/null @@ -1,83 +0,0 @@ -@import url("https://fonts.googleapis.com/css2?family=Geist:wght@100..900&display=swap"); - -@tailwind base; -@tailwind components; -@tailwind utilities; - -:root { - --app-background: #ffffff; - --app-foreground: #111111; - --app-foreground-muted: #58585c; - --app-accent: #0052ff; - --app-accent-hover: #0047e1; - --app-accent-active: #003db8; - --app-accent-light: #e6edff; - --app-gray: #f5f5f5; - --app-gray-dark: #e0e0e0; - --app-card-bg: rgba(255, 255, 255, 0.4); - --app-card-border: rgba(0, 0, 0, 0.1); -} - -@media (prefers-color-scheme: dark) { - :root { - --app-background: #111111; - --app-foreground: #ffffff; - --app-foreground-muted: #c8c8d1; - --app-accent: #0052ff; - --app-accent-hover: #0047e1; - --app-accent-active: #003db8; - --app-accent-light: #1e293b; - --app-gray: #1e1e1e; - --app-gray-dark: #2e2e2e; - --app-card-bg: rgba(17, 17, 17, 0.4); - --app-card-border: rgba(115, 115, 115, 0.5); - } -} - -* { - touch-action: manipulation; -} - -body { - color: var(--app-foreground); - background: var(--app-background); - font-family: var(--font-geist-sans), sans-serif; - font-optical-sizing: auto; - font-weight: 400; - font-style: normal; -} - -@layer utilities { - .text-balance { - text-wrap: balance; - } -} - -.animate-fade-in { - animation: fadeIn 0.5s ease-in-out; -} - -.animate-fade-out { - animation: fadeOut 3s forwards; -} - -@keyframes fadeIn { - from { - opacity: 0; - } - to { - opacity: 1; - } -} - -@keyframes fadeOut { - 0% { - opacity: 1; - } - 50% { - opacity: 1; - } - 100% { - opacity: 0; - } -} diff --git a/examples/minikit-example/app/ui/Button.module.css b/examples/minikit-example/app/ui/Button.module.css new file mode 100644 index 0000000000..5f8e54008e --- /dev/null +++ b/examples/minikit-example/app/ui/Button.module.css @@ -0,0 +1,97 @@ +/* Base button styles */ +.button { + display: inline-flex; + align-items: center; + justify-content: center; + font-weight: 500; + transition: + background-color 0.2s, + border-color 0.2s, + color 0.2s; + outline: none; + + --app-background: #111111; + --app-foreground: #ffffff; + --app-foreground-muted: #c8c8d1; + --app-accent: #0052ff; + --app-accent-hover: #0047e1; + --app-accent-light: #1e293b; + --app-gray: #1e1e1e; + --app-gray-dark: #2e2e2e; +} + +.button:focus { + outline: none; + box-shadow: + 0 0 0 2px rgba(0, 82, 255, 0.2), + 0 0 0 4px rgba(0, 82, 255, 0.1); +} + +.button:disabled { + opacity: 0.5; + pointer-events: none; +} + +/* Variant styles */ +.primary { + background-color: var(--app-accent); + color: var(--app-background); +} + +.primary:hover:not(:disabled) { + background-color: var(--app-accent-hover); +} + +.secondary { + background-color: var(--app-gray); + color: var(--app-foreground); +} + +.secondary:hover:not(:disabled) { + background-color: var(--app-gray-dark); +} + +.outline { + border: 1px solid var(--app-accent); + background-color: transparent; + color: var(--app-accent); +} + +.outline:hover:not(:disabled) { + background-color: var(--app-accent-light); +} + +.ghost { + background-color: transparent; + color: var(--app-foreground-muted); +} + +.ghost:hover:not(:disabled) { + background-color: var(--app-accent-light); +} + +/* Size styles */ +.sm { + font-size: 0.75rem; /* text-xs */ + padding: 0.375rem 0.625rem; /* py-1.5 px-2.5 */ + border-radius: 0.375rem; /* rounded-md */ +} + +.md { + font-size: 0.875rem; /* text-sm */ + padding: 0.5rem 1rem; /* py-2 px-4 */ + border-radius: 0.5rem; /* rounded-lg */ +} + +.lg { + font-size: 1rem; /* text-base */ + padding: 0.75rem 1.5rem; /* py-3 px-6 */ + border-radius: 0.5rem; /* rounded-lg */ +} + +/* Icon styles */ +.iconContainer { + display: flex; + align-items: center; + margin-right: 0.5rem; /* mr-2 */ +} diff --git a/examples/minikit-example/app/ui/Button.tsx b/examples/minikit-example/app/ui/Button.tsx index 58ae239e46..237851e043 100644 --- a/examples/minikit-example/app/ui/Button.tsx +++ b/examples/minikit-example/app/ui/Button.tsx @@ -1,5 +1,6 @@ "use client"; import { type ReactNode } from "react"; +import styles from "./Button.module.css"; type ButtonProps = { children: ReactNode; @@ -22,34 +23,23 @@ export function Button({ type = "button", icon, }: ButtonProps) { - const baseClasses = - "inline-flex items-center justify-center font-medium transition-colors focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-[#0052FF] disabled:opacity-50 disabled:pointer-events-none"; - - const variantClasses = { - primary: - "bg-[var(--app-accent)] hover:bg-[var(--app-accent-hover)] text-[var(--app-background)]", - secondary: - "bg-[var(--app-gray)] hover:bg-[var(--app-gray-dark)] text-[var(--app-foreground)]", - outline: - "border border-[var(--app-accent)] hover:bg-[var(--app-accent-light)] text-[var(--app-accent)]", - ghost: - "hover:bg-[var(--app-accent-light)] text-[var(--app-foreground-muted)]", - }; - - const sizeClasses = { - sm: "text-xs px-2.5 py-1.5 rounded-md", - md: "text-sm px-4 py-2 rounded-lg", - lg: "text-base px-6 py-3 rounded-lg", - }; + const buttonClasses = [ + styles.button, + styles[variant], + styles[size], + className, + ] + .filter(Boolean) + .join(" "); return ( ); diff --git a/examples/minikit-example/eslint.config.mjs b/examples/minikit-example/eslint.config.mjs new file mode 100644 index 0000000000..06ef748273 --- /dev/null +++ b/examples/minikit-example/eslint.config.mjs @@ -0,0 +1,27 @@ +import { dirname } from "path"; +import { fileURLToPath } from "url"; +import { FlatCompat } from "@eslint/eslintrc"; + +const __filename = fileURLToPath(import.meta.url); +const __dirname = dirname(__filename); + +const compat = new FlatCompat({ + baseDirectory: __dirname, +}); + +const eslintConfig = [ + ...compat.extends("next/core-web-vitals", "next/typescript"), + { + rules: { + "@typescript-eslint/no-unused-vars": [ + "error", + { + argsIgnorePattern: "^_", + varsIgnorePattern: "^_", + }, + ], + }, + }, +]; + +export default eslintConfig; diff --git a/examples/minikit-example/lib/notification-client.ts b/examples/minikit-example/lib/notification-client.ts deleted file mode 100644 index 6226bcb823..0000000000 --- a/examples/minikit-example/lib/notification-client.ts +++ /dev/null @@ -1,76 +0,0 @@ -import { - MiniAppNotificationDetails, - type SendNotificationRequest, - sendNotificationResponseSchema, -} from "@farcaster/frame-sdk"; -import { getUserNotificationDetails } from "@/lib/notification"; - -const appUrl = process.env.NEXT_PUBLIC_URL || ""; - -type SendFrameNotificationResult = - | { - state: "error"; - error: unknown; - } - | { state: "no_token" } - | { state: "rate_limit" } - | { state: "success" }; - -export async function sendFrameNotification({ - fid, - title, - body, - notificationDetails, -}: { - fid: number; - title: string; - body: string; - notificationDetails?: MiniAppNotificationDetails | null; -}): Promise { - if (!notificationDetails) { - notificationDetails = await getUserNotificationDetails(fid); - } - if (!notificationDetails) { - return { state: "no_token" }; - } - - // Define a strict allowlist of full hostnames - const allowedHostnames = ["api.coinbase.com"]; - const url = new URL(notificationDetails.url); - - // Validate the URL scheme and hostname - if (url.protocol !== "https:" || !allowedHostnames.includes(url.hostname)) { - return { state: "error", error: "Invalid or unsafe notification URL" }; - } - - const response = await fetch(notificationDetails.url, { - method: "POST", - headers: { - "Content-Type": "application/json", - }, - body: JSON.stringify({ - notificationId: crypto.randomUUID(), - title, - body, - targetUrl: appUrl, - tokens: [notificationDetails.token], - } satisfies SendNotificationRequest), - }); - - const responseJson = await response.json(); - - if (response.status === 200) { - const responseBody = sendNotificationResponseSchema.safeParse(responseJson); - if (responseBody.success === false) { - return { state: "error", error: responseBody.error.errors }; - } - - if (responseBody.data.result.rateLimitedTokens.length) { - return { state: "rate_limit" }; - } - - return { state: "success" }; - } - - return { state: "error", error: responseJson }; -} diff --git a/examples/minikit-example/lib/notification.ts b/examples/minikit-example/lib/notification.ts deleted file mode 100644 index 29ad8723e9..0000000000 --- a/examples/minikit-example/lib/notification.ts +++ /dev/null @@ -1,42 +0,0 @@ -import type { MiniAppNotificationDetails } from "@farcaster/frame-sdk"; -import { redis } from "./redis"; - -const notificationServiceKey = - process.env.NEXT_PUBLIC_ONCHAINKIT_PROJECT_NAME ?? "minikit"; - -function getUserNotificationDetailsKey(fid: number): string { - return `${notificationServiceKey}:user:${fid}`; -} - -export async function getUserNotificationDetails( - fid: number, -): Promise { - if (!redis) { - return null; - } - - return await redis.get( - getUserNotificationDetailsKey(fid), - ); -} - -export async function setUserNotificationDetails( - fid: number, - notificationDetails: MiniAppNotificationDetails, -): Promise { - if (!redis) { - return; - } - - await redis.set(getUserNotificationDetailsKey(fid), notificationDetails); -} - -export async function deleteUserNotificationDetails( - fid: number, -): Promise { - if (!redis) { - return; - } - - await redis.del(getUserNotificationDetailsKey(fid)); -} diff --git a/examples/minikit-example/lib/redis.ts b/examples/minikit-example/lib/redis.ts deleted file mode 100644 index 95ab7bcccc..0000000000 --- a/examples/minikit-example/lib/redis.ts +++ /dev/null @@ -1,15 +0,0 @@ -import { Redis } from "@upstash/redis"; - -if (!process.env.REDIS_URL || !process.env.REDIS_TOKEN) { - console.warn( - "REDIS_URL or REDIS_TOKEN environment variable is not defined, please add to enable background notifications and webhooks.", - ); -} - -export const redis = - process.env.REDIS_URL && process.env.REDIS_TOKEN - ? new Redis({ - url: process.env.REDIS_URL, - token: process.env.REDIS_TOKEN, - }) - : null; diff --git a/examples/minikit-example/minikit.config.ts b/examples/minikit-example/minikit.config.ts new file mode 100644 index 0000000000..4ddea940f3 --- /dev/null +++ b/examples/minikit-example/minikit.config.ts @@ -0,0 +1,36 @@ +const ROOT_URL = process.env.NEXT_PUBLIC_URL || process.env.VERCEL_URL; + +/** + * MiniApp configuration object. Must follow the Farcaster MiniApp specification. + * + * @see {@link https://miniapps.farcaster.xyz/docs/guides/publishing} + */ +export const minikitConfig = { + accountAssociation: { + header: + "eyJmaWQiOjYxNjIsInR5cGUiOiJjdXN0b2R5Iiwia2V5IjoiMHg4NTIxQkZFNjgwOTVDYURBYTFEZWZGZjJjN0NjNTMyNGJlMzcyRThmIn0", + payload: + "eyJkb21haW4iOiJvY2stbWluaWtpdC1leGFtcGxlLWdpdC1hbHBoYS1jb2luYmFzZS12ZXJjZWwudmVyY2VsLmFwcCJ9", + signature: + "MHhkZTA0ODk4YmE1MGMwMWM3ZWRlY2ViZWJkY2E0ZjA0ZTVlN2NkMTFiNWQxM2UxMjg4OWJiNzgwYTcyNWRhMGFlNTgyNGNlYmFiM2RjODdhNmIwYjNlNjExNTM1MjE1ODQ0MGI1NzU1ZTFhNGE3NzY5NDQwZWMyN2Y2NjhiYjY4NzFj", + }, + frame: { + version: "1", + name: "minikit-example", + subtitle: "", + description: "", + screenshotUrls: [], + iconUrl: `${ROOT_URL}/icon.png`, + splashImageUrl: `${ROOT_URL}/splash.png`, + splashBackgroundColor: "#000000", + homeUrl: ROOT_URL, + webhookUrl: `${ROOT_URL}/api/webhook`, + primaryCategory: "utility", + tags: [], + heroImageUrl: `${ROOT_URL}/hero.png`, + tagline: "", + ogTitle: "", + ogDescription: "", + ogImageUrl: `${ROOT_URL}/hero.png`, + }, +} as const; diff --git a/examples/minikit-example/next.config.mjs b/examples/minikit-example/next.config.mjs deleted file mode 100644 index b0ad7e114c..0000000000 --- a/examples/minikit-example/next.config.mjs +++ /dev/null @@ -1,11 +0,0 @@ -/** @type {import('next').NextConfig} */ -const nextConfig = { - // Silence warnings - // https://github.com/WalletConnect/walletconnect-monorepo/issues/1908 - webpack: (config) => { - config.externals.push("pino-pretty", "lokijs", "encoding"); - return config; - }, -}; - -export default nextConfig; diff --git a/examples/minikit-example/next.config.ts b/examples/minikit-example/next.config.ts new file mode 100644 index 0000000000..aba9c92a9f --- /dev/null +++ b/examples/minikit-example/next.config.ts @@ -0,0 +1,10 @@ +import type { NextConfig } from "next"; + +const nextConfig: NextConfig = { + webpack: (config) => { + config.externals.push("pino-pretty", "lokijs", "encoding"); + return config; + }, +}; + +export default nextConfig; diff --git a/examples/minikit-example/package.json b/examples/minikit-example/package.json index 0a90da3069..081c88e5e6 100644 --- a/examples/minikit-example/package.json +++ b/examples/minikit-example/package.json @@ -1,32 +1,37 @@ { "name": "minikit-example", - "version": "0.0.1", + "version": "0.1.0", "private": true, "scripts": { "dev": "next dev", - "dev:watch": "concurrently \"pnpm -F @coinbase/onchainkit dev\" \"pnpm -F minikit-example dev\"", "build": "next build", "start": "next start", "lint": "next lint" }, "dependencies": { "@coinbase/onchainkit": "workspace:*", - "@farcaster/frame-sdk": "^0.1.8", - "@tanstack/react-query": "^5", - "@upstash/redis": "^1.34.4", - "next": "^15.3.3", - "react": "^18", - "react-dom": "^18", - "viem": "^2.27.2", - "wagmi": "^2.16.0" + "@farcaster/miniapp-sdk": "^0.1.8", + "@mantine/core": "^8.2.5", + "@mantine/hooks": "^8.2.5", + "@tanstack/react-query": "^5.81.5", + "next": "15.3.4", + "react": "^19.0.0", + "react-dom": "^19.0.0", + "viem": "^2.31.6", + "wagmi": "^2.15.6" }, "devDependencies": { + "@eslint/eslintrc": "^3", "@farcaster/quick-auth": "^0.0.7", - "@types/node": "^22", - "@types/react": "^18", - "@types/react-dom": "^18", - "eslint-config-next": "15.3.3", - "postcss": "^8", - "tailwindcss": "^3.4.1" + "@next/eslint-plugin-next": "^15.3.4", + "@types/node": "^20", + "@types/react": "^19", + "@types/react-dom": "^19", + "eslint": "^9", + "eslint-config-next": "15.3.4", + "postcss": "^8.5.6", + "postcss-preset-mantine": "^1.18.0", + "postcss-simple-vars": "^7.0.1", + "typescript": "^5" } } diff --git a/examples/minikit-example/postcss.config.cjs b/examples/minikit-example/postcss.config.cjs new file mode 100644 index 0000000000..e817f567be --- /dev/null +++ b/examples/minikit-example/postcss.config.cjs @@ -0,0 +1,14 @@ +module.exports = { + plugins: { + "postcss-preset-mantine": {}, + "postcss-simple-vars": { + variables: { + "mantine-breakpoint-xs": "36em", + "mantine-breakpoint-sm": "48em", + "mantine-breakpoint-md": "62em", + "mantine-breakpoint-lg": "75em", + "mantine-breakpoint-xl": "88em", + }, + }, + }, +}; diff --git a/examples/minikit-example/postcss.config.mjs b/examples/minikit-example/postcss.config.mjs deleted file mode 100644 index 1a69fd2a45..0000000000 --- a/examples/minikit-example/postcss.config.mjs +++ /dev/null @@ -1,8 +0,0 @@ -/** @type {import('postcss-load-config').Config} */ -const config = { - plugins: { - tailwindcss: {}, - }, -}; - -export default config; diff --git a/examples/minikit-example/public/sphere.svg b/examples/minikit-example/public/sphere.svg new file mode 100644 index 0000000000..16d837ac13 --- /dev/null +++ b/examples/minikit-example/public/sphere.svg @@ -0,0 +1,18 @@ + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/examples/minikit-example/tailwind.config.ts b/examples/minikit-example/tailwind.config.ts deleted file mode 100644 index 483994ffa6..0000000000 --- a/examples/minikit-example/tailwind.config.ts +++ /dev/null @@ -1,28 +0,0 @@ -import type { Config } from "tailwindcss"; - -const config: Config = { - content: [ - "./pages/**/*.{js,ts,jsx,tsx,mdx}", - "./components/**/*.{js,ts,jsx,tsx,mdx}", - "./app/**/*.{js,ts,jsx,tsx,mdx}", - ], - theme: { - extend: { - colors: { - background: "var(--background)", - foreground: "var(--foreground)", - }, - animation: { - "fade-out": "1s fadeOut 3s ease-out forwards", - }, - keyframes: { - fadeOut: { - "0%": { opacity: "1" }, - "100%": { opacity: "0" }, - }, - }, - }, - }, - plugins: [], -}; -export default config; diff --git a/examples/minikit-example/tsconfig.json b/examples/minikit-example/tsconfig.json index d81d4ee14e..d8b93235f2 100644 --- a/examples/minikit-example/tsconfig.json +++ b/examples/minikit-example/tsconfig.json @@ -1,10 +1,7 @@ { "compilerOptions": { - "lib": [ - "dom", - "dom.iterable", - "esnext" - ], + "target": "ES2017", + "lib": ["dom", "dom.iterable", "esnext"], "allowJs": true, "skipLibCheck": true, "strict": true, @@ -22,19 +19,9 @@ } ], "paths": { - "@/*": [ - "./*" - ] - }, - "target": "ES2017" + "@/*": ["./*"] + } }, - "include": [ - "next-env.d.ts", - "**/*.ts", - "**/*.tsx", - ".next/types/**/*.ts" - ], - "exclude": [ - "node_modules" - ] + "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"], + "exclude": ["node_modules"] } diff --git a/package.json b/package.json index 94e0eac20f..40046cdbde 100644 --- a/package.json +++ b/package.json @@ -19,10 +19,10 @@ "author": "", "license": "MIT", "devDependencies": { + "@actions/core": "^1.11.1", "@changesets/cli": "^2.27.10", "@changesets/get-github-info": "^0.6.0", "@eslint/js": "^9.22.0", - "@upstash/redis": "^1.34.4", "concurrently": "^8.0.0", "eslint": "^9.22.0", "eslint-config-prettier": "^10.1.1", diff --git a/packages/create-onchain/.gitignore b/packages/create-onchain/.gitignore index fc5ae4b091..d0a12daaeb 100644 --- a/packages/create-onchain/.gitignore +++ b/packages/create-onchain/.gitignore @@ -1,4 +1,5 @@ dist +templates /coverage # Yarn @@ -15,4 +16,4 @@ dist !.bun/releases !.bun/plugins !.bun/sdks -!.bun/versions \ No newline at end of file +!.bun/versions diff --git a/packages/create-onchain/CHANGELOG.md b/packages/create-onchain/CHANGELOG.md index 716aaf5a57..e51e23002a 100644 --- a/packages/create-onchain/CHANGELOG.md +++ b/packages/create-onchain/CHANGELOG.md @@ -1,19 +1,5 @@ # create-onchain -## 0.0.24 - -### Patch Changes - -- **chore:** Bump Farcaster frames SDK version. Migrate to new miniapp Wagmi connector. - Thanks [@dgca](https://github.com/dgca)! [#2444](https://github.com/coinbase/onchainkit/pull/2444) - -- fix: Allow manifest generator domain parsing to validate longer TLDs - Thanks [@dgca](https://github.com/dgca)! [#2440](https://github.com/coinbase/onchainkit/pull/2440) - -## 0.0.23 - -### Patch Changes - -- **chore:** Update @farcaster/frame-sdk dependency - ## 0.0.22 ### Patch Changes diff --git a/packages/create-onchain/README.md b/packages/create-onchain/README.md index 8ba173d954..3461df76cc 100644 --- a/packages/create-onchain/README.md +++ b/packages/create-onchain/README.md @@ -16,9 +16,8 @@ npx create-onchain --mini npx create-onchain --template-