diff --git a/.github/workflows/cli-test.yml b/.github/workflows/cli-test.yml new file mode 100644 index 0000000000..849b66e56a --- /dev/null +++ b/.github/workflows/cli-test.yml @@ -0,0 +1,35 @@ +name: CLI Test +on: + push: + branches: ['main'] + pull_request: + branches: ['main'] +jobs: + build: + runs-on: ubuntu-latest + strategy: + matrix: + node-version: [18.x] + # See supported Node.js release schedule at https://nodejs.org/en/about/releases/ + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Use Node.js ${{ matrix.node-version }} + uses: actions/setup-node@v4 + with: + node-version: ${{ matrix.node-version }} + cache: 'yarn' + + - name: Change to CLI directory + run: cd create-onchain + + - name: Install CLI dependencies + working-directory: ./create-onchain + run: | + yarn install + + - name: Run CLI tests + working-directory: ./create-onchain + run: | + yarn test:coverage diff --git a/account-manifest/.gitignore b/account-manifest/.gitignore new file mode 100644 index 0000000000..bc76babded --- /dev/null +++ b/account-manifest/.gitignore @@ -0,0 +1,7 @@ +# See http://help.github.com/ignore-files/ for more about ignoring files. + +# compiled output +dist + +# dependencies +node_modules diff --git a/account-manifest/README.md b/account-manifest/README.md new file mode 100644 index 0000000000..40ede56ea6 --- /dev/null +++ b/account-manifest/README.md @@ -0,0 +1,54 @@ +# React + TypeScript + Vite + +This template provides a minimal setup to get React working in Vite with HMR and some ESLint rules. + +Currently, two official plugins are available: + +- [@vitejs/plugin-react](https://github.com/vitejs/vite-plugin-react/blob/main/packages/plugin-react/README.md) uses [Babel](https://babeljs.io/) for Fast Refresh +- [@vitejs/plugin-react-swc](https://github.com/vitejs/vite-plugin-react-swc) uses [SWC](https://swc.rs/) for Fast Refresh + +## Expanding the ESLint configuration + +If you are developing a production application, we recommend updating the configuration to enable type-aware lint rules: + +```js +export default tseslint.config({ + extends: [ + // Remove ...tseslint.configs.recommended and replace with this + ...tseslint.configs.recommendedTypeChecked, + // Alternatively, use this for stricter rules + ...tseslint.configs.strictTypeChecked, + // Optionally, add this for stylistic rules + ...tseslint.configs.stylisticTypeChecked, + ], + languageOptions: { + // other options... + parserOptions: { + project: ['./tsconfig.node.json', './tsconfig.app.json'], + tsconfigRootDir: import.meta.dirname, + }, + }, +}) +``` + +You can also install [eslint-plugin-react-x](https://github.com/Rel1cx/eslint-react/tree/main/packages/plugins/eslint-plugin-react-x) and [eslint-plugin-react-dom](https://github.com/Rel1cx/eslint-react/tree/main/packages/plugins/eslint-plugin-react-dom) for React-specific lint rules: + +```js +// eslint.config.js +import reactX from 'eslint-plugin-react-x' +import reactDom from 'eslint-plugin-react-dom' + +export default tseslint.config({ + plugins: { + // Add the react-x and react-dom plugins + 'react-x': reactX, + 'react-dom': reactDom, + }, + rules: { + // other rules... + // Enable its recommended typescript rules + ...reactX.configs['recommended-typescript'].rules, + ...reactDom.configs.recommended.rules, + }, +}) +``` diff --git a/account-manifest/eslint.config.js b/account-manifest/eslint.config.js new file mode 100644 index 0000000000..170537f040 --- /dev/null +++ b/account-manifest/eslint.config.js @@ -0,0 +1,28 @@ +import js from '@eslint/js'; +import reactHooks from 'eslint-plugin-react-hooks'; +import reactRefresh from 'eslint-plugin-react-refresh'; +import globals from 'globals'; +import tseslint from 'typescript-eslint'; + +export default tseslint.config( + { ignores: ['dist'] }, + { + extends: [js.configs.recommended, ...tseslint.configs.recommended], + files: ['**/*.{ts,tsx}'], + languageOptions: { + ecmaVersion: 2020, + globals: globals.browser, + }, + plugins: { + 'react-hooks': reactHooks, + 'react-refresh': reactRefresh, + }, + rules: { + ...reactHooks.configs.recommended.rules, + 'react-refresh/only-export-components': [ + 'warn', + { allowConstantExport: true }, + ], + }, + }, +); diff --git a/account-manifest/index.html b/account-manifest/index.html new file mode 100644 index 0000000000..778e917ed2 --- /dev/null +++ b/account-manifest/index.html @@ -0,0 +1,13 @@ + + + + + + + Account Manifest Generator + + +
+ + + diff --git a/account-manifest/package.json b/account-manifest/package.json new file mode 100644 index 0000000000..307d6338e8 --- /dev/null +++ b/account-manifest/package.json @@ -0,0 +1,35 @@ +{ + "name": "account-manifest", + "private": true, + "version": "0.0.0", + "type": "module", + "scripts": { + "dev": "vite", + "build": "tsc -b && vite build", + "copy": "cp -R ./dist/* ../create-onchain/src/manifest", + "build:copy": "npm run build && npm run copy", + "lint": "eslint .", + "preview": "vite preview" + }, + "dependencies": { + "@coinbase/onchainkit": "^0.37.5", + "react": "^19.0.0", + "react-dom": "^19.0.0", + "wagmi": "^2.14.12" + }, + "devDependencies": { + "@eslint/js": "^9.21.0", + "@tailwindcss/vite": "^4.0.9", + "@types/react": "^19.0.10", + "@types/react-dom": "^19.0.4", + "@vitejs/plugin-react": "^4.3.4", + "eslint": "^9.21.0", + "eslint-plugin-react-hooks": "^5.1.0", + "eslint-plugin-react-refresh": "^0.4.19", + "globals": "^15.15.0", + "tailwindcss": "^4.0.9", + "typescript": "~5.7.2", + "typescript-eslint": "^8.24.1", + "vite": "^6.2.0" + } +} diff --git a/account-manifest/src/App.tsx b/account-manifest/src/App.tsx new file mode 100644 index 0000000000..38031017d1 --- /dev/null +++ b/account-manifest/src/App.tsx @@ -0,0 +1,26 @@ +import { OnchainKitProvider } from '@coinbase/onchainkit'; +import { base } from 'wagmi/chains'; +import Page from './components/Page'; + +function App() { + return ( + + + + ); +} + +export default App; diff --git a/account-manifest/src/components/Page.tsx b/account-manifest/src/components/Page.tsx new file mode 100644 index 0000000000..6436cc5fa5 --- /dev/null +++ b/account-manifest/src/components/Page.tsx @@ -0,0 +1,247 @@ +import { + Address, + Avatar, + EthBalance, + Identity, + Name, +} from '@coinbase/onchainkit/identity'; +import { + ConnectWallet, + Wallet, + WalletDropdown, + WalletDropdownDisconnect, +} from '@coinbase/onchainkit/wallet'; +import { useCallback, useEffect, useRef, useState } from 'react'; +import { useAccount } from 'wagmi'; +import { useGetFid } from '../hooks/useGetFid'; +import { + type AccountAssociation, + useSignManifest, +} from '../hooks/useSignManifest'; +import { validateUrl } from '../utils'; +import { Step } from './Step'; +import { Success } from './Success'; + +const MAX_RECONNECT_ATTEMPTS = 5; + +function Page() { + const wsRef = useRef(null); + const [fid, setFid] = useState(null); + const [domain, setDomain] = useState(''); + const [domainError, setDomainError] = useState(null); + const [accountAssocation, setAccountAssocation] = + useState(null); + + const getFid = useGetFid(); + const { address } = useAccount(); + const { isPending, error, generateAccountAssociation } = useSignManifest({ + domain, + fid, + address, + onSigned: useCallback((accountAssociation) => { + wsRef.current?.send(JSON.stringify(accountAssociation)); + setAccountAssocation(accountAssociation); + }, []), + }); + + useEffect(() => { + let reconnectAttempts = 0; + + function connect() { + wsRef.current = new WebSocket('ws://localhost:3333'); + + wsRef.current.onclose = () => { + if (reconnectAttempts < MAX_RECONNECT_ATTEMPTS) { + console.log('WebSocket closed, reconnecting...'); + reconnectAttempts++; + setTimeout(connect, 1000); + } + }; + + wsRef.current.onerror = (err) => { + console.error('WebSocket error:', err); + }; + } + + connect(); + + return () => { + wsRef.current?.close(); + }; + }, []); + + useEffect(() => { + if (address) { + getFid(address).then(setFid); + } + }, [address, getFid]); + + useEffect(() => { + // super hacky way to remove the sign up button and 'or continue' div from the wallet modal + // biome-ignore lint/complexity/noExcessiveCognitiveComplexity: cause above + const observer = new MutationObserver((mutations) => { + for (const mutation of mutations) { + if (mutation.addedNodes.length) { + const modal = document.querySelector( + '[data-testid="ockModalOverlay"]', + ); + if (modal) { + const signUpButton = modal.querySelector( + 'div > div.flex.w-full.flex-col.gap-3 > button:first-of-type', + ); + const orContinueDiv = modal.querySelector( + 'div > div.flex.w-full.flex-col.gap-3 > div.relative', + ); + + if (signUpButton) { + signUpButton.style.display = 'none'; + } + if (orContinueDiv) { + orContinueDiv.style.display = 'none'; + } + } + } + } + }); + + observer.observe(document.body, { + childList: true, + subtree: true, + }); + + return () => observer.disconnect(); + }, []); + + const handleValidateUrl = () => { + const isValid = validateUrl(domain); + if (!isValid) { + setDomainError('Invalid URL'); + } + }; + + const handleDomainChange = (e: React.ChangeEvent) => { + setDomain(e.target.value); + setDomainError(null); + }; + + const handleClose = useCallback(() => { + window.close(); + }, []); + + return ( +
+ +

+ Use coinbase smart wallet if you have a passkey farcaster account + through TBA +

+

+ Use MetaMask or Phantom to set up a wallet using your warpcast + recovery key +

+ + } + > + + + + + + + + + +
+ + + + + + + + +

Enter the domain your app will be hosted on

+

+ This will be used to generate the account manifest and also added + to your .env file as the `NEXT_PUBLIC_URL` variable +

+ + } + > +
+ + {domainError &&

{domainError}

} +
+
+ + +

+ This will generate the account manifest and sign it with your + wallet +

+

+ The account manifest will be saved to your .env file as + `FARCASTER_HEADER`, `FARCASTER_PAYLOAD` and `FARCASTER_SIGNATURE` + variables +

+ + } + > +
+ {fid === 0 ? ( +

+ There is no FID associated with this account, please connect with + your TBA passkey account. +

+ ) : ( +

Your FID is {fid}

+ )} + + + {error && ( +

+ {error.message.split('\n').map((line) => ( + + {line} +
+
+ ))} +

+ )} +
+
+ +
+ ); +} + +export default Page; diff --git a/account-manifest/src/components/Step.tsx b/account-manifest/src/components/Step.tsx new file mode 100644 index 0000000000..ebf5269bc1 --- /dev/null +++ b/account-manifest/src/components/Step.tsx @@ -0,0 +1,22 @@ +type StepProps = { + disabled?: boolean; + label: string; + children: React.ReactNode; + description?: React.ReactNode; +}; + +export function Step({ disabled, label, description, children }: StepProps) { + return ( +
+
+ {label} +
+
{children}
+ {description && ( +
{description}
+ )} +
+ ); +} diff --git a/account-manifest/src/components/Success.tsx b/account-manifest/src/components/Success.tsx new file mode 100644 index 0000000000..f78fd9c2ca --- /dev/null +++ b/account-manifest/src/components/Success.tsx @@ -0,0 +1,62 @@ +'use client'; + +import type { AccountAssociation } from '../hooks/useSignManifest'; +import { Timer } from './Timer'; + +type SuccessProps = { + accountAssocation: AccountAssociation | null; + handleClose: () => void; +}; + +export function Success({ accountAssocation, handleClose }: SuccessProps) { + if (!accountAssocation) { + return null; + } + + const displayAccountAssocation = { + header: accountAssocation.header, + payload: accountAssocation.payload, + signature: accountAssocation.signature, + }; + + return ( +
+
+

+ Account Association Generated Successfully! +

+ +
+
+        {`{
+  header: ${accountAssocation.header},
+  payload: ${accountAssocation.payload},
+  signature: ${accountAssocation.signature}
+}`}
+      
+

+ This information has automatically been added to your .env file for use + in the farcaster/.well-known file. Please close this window to return to + the CLI. +

+ +
+ ); +} diff --git a/account-manifest/src/components/Timer.tsx b/account-manifest/src/components/Timer.tsx new file mode 100644 index 0000000000..70234cff74 --- /dev/null +++ b/account-manifest/src/components/Timer.tsx @@ -0,0 +1,38 @@ +'use client'; + +import { useEffect, useState } from 'react'; + +type TimerProps = { + /* start time in milliseconds */ + startMs: number; + /* countdown interval in milliseconds, default 1000 */ + interval?: number; + /* formatter function to display time */ + formatFunc?: (timeMs: number) => string; + /* callback to call on timer complete */ + callback?: () => void; +}; + +export function Timer({ + startMs, + interval = 1000, + formatFunc = (time) => (time / 1000).toFixed(), + callback = () => {}, +}: TimerProps) { + const [timeMs, setTimeMs] = useState(startMs); + + useEffect(() => { + if (timeMs <= 0) { + callback(); + return; + } + + const timer = setInterval(() => { + setTimeMs((prev) => prev - interval); + }, interval); + + return () => clearInterval(timer); + }, [interval, callback, timeMs]); + + return {formatFunc(timeMs)}; +} diff --git a/account-manifest/src/hooks/useGetFid.ts b/account-manifest/src/hooks/useGetFid.ts new file mode 100644 index 0000000000..a203260c20 --- /dev/null +++ b/account-manifest/src/hooks/useGetFid.ts @@ -0,0 +1,37 @@ +import { http } from 'viem'; +import { createPublicClient } from 'viem'; +import type { Address } from 'viem'; +import { optimism } from 'viem/chains'; + +// ID Registry Contract +const ID_REGISTRY_ADDRESS = '0x00000000Fc6c5F01Fc30151999387Bb99A9f489b'; +const ID_REGISTRY_ABI = [ + { + inputs: [{ internalType: 'address', name: 'owner', type: 'address' }], + name: 'idOf', + outputs: [{ internalType: 'uint256', name: 'fid', type: 'uint256' }], + stateMutability: 'view', + type: 'function', + }, +]; + +// TODO: will delegate accounts exist on the ID registry linked to an FID? + +export function useGetFid() { + return async function getFid(address: Address) { + const client = createPublicClient({ + chain: optimism, + transport: http(), + }); + + // query the ID Registry contract for the fids custody address + const resolvedFid = await client.readContract({ + address: ID_REGISTRY_ADDRESS, + abi: ID_REGISTRY_ABI, + functionName: 'idOf', + args: [address], + }); + + return Number(resolvedFid); + }; +} diff --git a/account-manifest/src/hooks/useSignManifest.ts b/account-manifest/src/hooks/useSignManifest.ts new file mode 100644 index 0000000000..27b85532c2 --- /dev/null +++ b/account-manifest/src/hooks/useSignManifest.ts @@ -0,0 +1,82 @@ +// hooks/useAccountManifest.ts +import { useEffect, useRef } from 'react'; +import { useSignMessage } from 'wagmi'; +import { toBase64Url } from '../utils'; + +export type AccountAssociation = { + header: string; + payload: string; + signature: string; + domain: string; +}; + +type SignManifestProps = { + domain: string; + fid: number | null; + address: string | undefined; + onSigned: (accountAssociation: AccountAssociation) => void; +}; + +export function useSignManifest({ + domain, + fid, + address, + onSigned, +}: SignManifestProps) { + const encodedHeader = useRef(''); + const encodedPayload = useRef(''); + const { + data: signMessageData, + isPending, + error, + signMessage, + } = useSignMessage(); + + useEffect(() => { + async function signManifest() { + if (signMessageData) { + const encodedSignature = toBase64Url(signMessageData); + const accountAssociation = { + header: encodedHeader.current, + payload: encodedPayload.current, + signature: encodedSignature, + domain: domain, + }; + + console.log('Account association generated:', accountAssociation); + onSigned(accountAssociation); + } + } + + signManifest(); + }, [signMessageData, domain, onSigned]); + + const generateAccountAssociation = async () => { + if (!domain || !fid || !address) { + console.error('Domain, FID and wallet connection are required'); + return; + } + + const header = { + fid, + type: 'custody', + key: address, + }; + + const payload = { + domain: domain.replace(/^(http|https):\/\//, ''), + }; + + encodedHeader.current = toBase64Url(JSON.stringify(header)); + encodedPayload.current = toBase64Url(JSON.stringify(payload)); + const messageToSign = `${encodedHeader.current}.${encodedPayload.current}`; + + signMessage({ message: messageToSign }); + }; + + return { + isPending, + error, + generateAccountAssociation, + }; +} diff --git a/account-manifest/src/index.css b/account-manifest/src/index.css new file mode 100644 index 0000000000..78db37f7cb --- /dev/null +++ b/account-manifest/src/index.css @@ -0,0 +1,37 @@ +@import "tailwindcss"; + +:root { + font-family: system-ui, Avenir, Helvetica, Arial, sans-serif; + line-height: 1.5; + font-weight: 400; + + color-scheme: light dark; + color: rgba(255, 255, 255, 0.87); + background-color: #242424; + + font-synthesis: none; + text-rendering: optimizeLegibility; + -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale; +} + +a { + font-weight: 500; + color: #646cff; + text-decoration: inherit; +} + +a:hover { + color: #535bf2; +} + +body { + margin: 0; + min-width: 320px; + min-height: 100vh; +} + +header, main { + max-width: 600px; + margin: 0 auto; +} \ No newline at end of file diff --git a/account-manifest/src/main.tsx b/account-manifest/src/main.tsx new file mode 100644 index 0000000000..de9c04ba44 --- /dev/null +++ b/account-manifest/src/main.tsx @@ -0,0 +1,16 @@ +import { StrictMode } from 'react'; +import { createRoot } from 'react-dom/client'; +import './index.css'; +import '@coinbase/onchainkit/styles.css'; +import App from './App.tsx'; + +const root = document.getElementById('root'); +if (!root) { + throw new Error('Root element not found'); +} + +createRoot(root).render( + + + , +); diff --git a/account-manifest/src/utils.ts b/account-manifest/src/utils.ts new file mode 100644 index 0000000000..52fbfca12c --- /dev/null +++ b/account-manifest/src/utils.ts @@ -0,0 +1,9 @@ +export const toBase64Url = (str: string) => { + return btoa(str).replace(/\+/g, '-').replace(/\//g, '_').replace(/=/g, ''); +}; + +export const validateUrl = (domain: string) => { + const urlRegex = + /^https?:\/\/([\da-z.-]+)\.([a-z.]{2,6})(\/[\w.-]*(?: [\w.-]*)*)*\/?$/; + return domain.length === 0 || (domain.length > 0 && urlRegex.test(domain)); +}; diff --git a/account-manifest/src/vite-env.d.ts b/account-manifest/src/vite-env.d.ts new file mode 100644 index 0000000000..11f02fe2a0 --- /dev/null +++ b/account-manifest/src/vite-env.d.ts @@ -0,0 +1 @@ +/// diff --git a/account-manifest/tailwind.config.js b/account-manifest/tailwind.config.js new file mode 100644 index 0000000000..2d455fcc56 --- /dev/null +++ b/account-manifest/tailwind.config.js @@ -0,0 +1,9 @@ +/** @type {import('tailwindcss').Config} */ + +export default { + content: ['./index.html', './src/**/*.{js,ts,jsx,tsx}'], + theme: { + extend: {}, + }, + plugins: [], +}; diff --git a/account-manifest/tsconfig.app.json b/account-manifest/tsconfig.app.json new file mode 100644 index 0000000000..19032831fe --- /dev/null +++ b/account-manifest/tsconfig.app.json @@ -0,0 +1,22 @@ +{ + "compilerOptions": { + "tsBuildInfoFile": "./node_modules/.tmp/tsconfig.app.tsbuildinfo", + "target": "ES2020", + "useDefineForClassFields": true, + "lib": ["ES2020", "DOM", "DOM.Iterable"], + "module": "ESNext", + "skipLibCheck": true, + "moduleResolution": "bundler", + "allowImportingTsExtensions": true, + "isolatedModules": true, + "moduleDetection": "force", + "noEmit": true, + "jsx": "react-jsx", + "strict": true, + "noUnusedLocals": true, + "noUnusedParameters": true, + "noFallthroughCasesInSwitch": true, + "noUncheckedSideEffectImports": true + }, + "include": ["src"] +} diff --git a/account-manifest/tsconfig.json b/account-manifest/tsconfig.json new file mode 100644 index 0000000000..1ffef600d9 --- /dev/null +++ b/account-manifest/tsconfig.json @@ -0,0 +1,7 @@ +{ + "files": [], + "references": [ + { "path": "./tsconfig.app.json" }, + { "path": "./tsconfig.node.json" } + ] +} diff --git a/account-manifest/tsconfig.node.json b/account-manifest/tsconfig.node.json new file mode 100644 index 0000000000..1dba6de9ed --- /dev/null +++ b/account-manifest/tsconfig.node.json @@ -0,0 +1,20 @@ +{ + "compilerOptions": { + "tsBuildInfoFile": "./node_modules/.tmp/tsconfig.node.tsbuildinfo", + "target": "ES2022", + "lib": ["ES2023"], + "module": "ESNext", + "skipLibCheck": true, + "moduleResolution": "bundler", + "allowImportingTsExtensions": true, + "isolatedModules": true, + "moduleDetection": "force", + "noEmit": true, + "strict": true, + "noUnusedLocals": true, + "noUnusedParameters": true, + "noFallthroughCasesInSwitch": true, + "noUncheckedSideEffectImports": true + }, + "include": ["vite.config.ts"] +} diff --git a/account-manifest/vite.config.ts b/account-manifest/vite.config.ts new file mode 100644 index 0000000000..63be2a0738 --- /dev/null +++ b/account-manifest/vite.config.ts @@ -0,0 +1,7 @@ +import tailwindcss from '@tailwindcss/vite'; +import react from '@vitejs/plugin-react'; +import { defineConfig } from 'vite'; + +export default defineConfig({ + plugins: [react(), tailwindcss()], +}); diff --git a/create-onchain/.gitignore b/create-onchain/.gitignore new file mode 100644 index 0000000000..3cc12209ec --- /dev/null +++ b/create-onchain/.gitignore @@ -0,0 +1,21 @@ +src/manifest/* +!src/manifest/.gitkeep + +dist +/coverage + +# Yarn +.yarn/* +!.yarn/patches +!.yarn/releases +!.yarn/plugins +!.yarn/sdks +!.yarn/versions + +# Bun +.bun/* +!.bun/patches +!.bun/releases +!.bun/plugins +!.bun/sdks +!.bun/versions \ No newline at end of file diff --git a/create-onchain/package.json b/create-onchain/package.json index 9933d9c9b9..3a153771aa 100644 --- a/create-onchain/package.json +++ b/create-onchain/package.json @@ -4,11 +4,14 @@ "version": "0.0.16", "license": "MIT", "scripts": { - "build": "bun run clean && bun run build:esm+types", + "build": "bun run clean && bun run build:manifest && bun run build:esm+types", + "build:manifest": "cd ../account-manifest && bun install && bun run build && cd ../create-onchain && rm -rf ./src/manifest/* && cp -R ../account-manifest/dist/* ./src/manifest", "build:esm+types": "tsc --project tsconfig.build.json --outDir ./dist/esm --declaration --declarationMap --declarationDir ./dist/types", "check:types": "tsc --noEmit", "clean": "rm -rf dist tsconfig.tsbuildinfo", - "dev": "bun src/cli.ts" + "dev": "bun src/cli.ts", + "test:watch": "vitest", + "test:coverage": "vitest run --coverage" }, "files": [ "dist/**", @@ -16,11 +19,10 @@ "src/**/*.ts", "!src/**/*.test.ts", "!src/**/*.test-d.ts", - "templates/**" + "templates/**", + "src/manifest/**" ], - "bin": { - "create-onchain": "./dist/esm/cli.js" - }, + "bin": "./dist/esm/cli.js", "sideEffects": false, "type": "module", "exports": { @@ -28,14 +30,21 @@ }, "dependencies": { "cac": "^6.7.14", - "cross-spawn": "^7.0.3", + "express": "^4.21.2", + "open": "^10.1.0", "ora": "^8.1.0", "picocolors": "^1.1.0", - "prompts": "^2.4.2" + "prompts": "^2.4.2", + "ws": "^8.18.1" }, "devDependencies": { - "@types/cross-spawn": "^6.0.6", + "@types/express": "^5.0.0", "@types/node": "^20.12.10", - "@types/prompts": "^2.4.9" + "@types/prompts": "^2.4.9", + "@types/ws": "^8.5.14", + "@vitest/coverage-v8": "^3.0.5", + "@vitest/ui": "^3.0.5", + "jsdom": "^24.1.0", + "vitest": "^3.0.5" } -} \ No newline at end of file +} diff --git a/create-onchain/src/cli.test.ts b/create-onchain/src/cli.test.ts new file mode 100644 index 0000000000..637a71c59a --- /dev/null +++ b/create-onchain/src/cli.test.ts @@ -0,0 +1,263 @@ +import { describe, it, expect, beforeEach, afterEach, vi, type Mock } from 'vitest'; +import fs from 'fs'; +import path from 'path'; +import prompts from 'prompts'; +import open from 'open'; +import ora from 'ora'; +import { WebSocketServer } from 'ws'; + +vi.mock('./utils.js', () => ({ + optimizedCopy: vi.fn().mockResolvedValue(undefined), + createClickableLink: vi.fn(), + isValidPackageName: vi.fn().mockReturnValue(true), + toValidPackageName: vi.fn().mockImplementation((name) => name), +})); +vi.mock('ora', () => ({ + default: vi.fn() +})); +vi.mock('express', () => { + const mockExpress = vi.fn(() => ({ + use: vi.fn(), + listen: vi.fn(), + })) as any; + mockExpress.static = vi.fn(); + return { default: mockExpress }; +}); +vi.mock('ws', () => ({ + WebSocketServer: vi.fn() +})); +vi.mock('http', () => ({ + default: { + createServer: vi.fn(() => ({ + listen: vi.fn().mockImplementation((_, callback) => callback()), + close: vi.fn() + })) + } +})); +vi.mock('open', () => ({ default: vi.fn() })); +vi.mock('prompts', () => ({ + default: vi.fn() +})); +vi.mock('fs', async () => { + const actual = await vi.importActual('fs'); + return { + default: { + ...actual, + readFileSync: vi.fn().mockReturnValue('{}'), + existsSync: vi.fn(), + readdirSync: vi.fn(), + copyFile: vi.fn().mockResolvedValue(undefined), + promises: { + ...actual.promises, + writeFile: vi.fn(), + copyFile: vi.fn().mockResolvedValue(undefined), + mkdir: vi.fn().mockResolvedValue(undefined), + readdir: vi.fn().mockResolvedValue([]), + readFile: vi.fn().mockResolvedValue('{}'), + } + } + }; +}); +vi.mock('path', async () => { + const actual = await vi.importActual('path'); + return { + default: { + ...actual, + resolve: vi.fn((...args) => { + if (args.some(arg => typeof arg === 'string' && arg.includes('templates'))) { + return actual.resolve(__dirname, '..', 'templates', 'minikit'); + } + return actual.resolve(...args); + }) + } + }; +}); +vi.mock('url', () => ({ + default: { + fileURLToPath: () => path.join(__dirname, '..', 'src', 'cli.ts') + } +})); + +vi.spyOn(process, 'exit').mockImplementation((code) => { + throw new Error(`${code}`); +}); + +const logSpy = vi.spyOn(console, 'log'); + +describe('CLI', () => { + const originalGetArgs = process.argv; + let wsMessageCallback: (data: Buffer) => void; + + beforeEach(() => { + process.argv = [...originalGetArgs]; + + (WebSocketServer as unknown as Mock).mockImplementation(() => ({ + on: vi.fn((event, cb) => { + if (event === 'connection') { + cb({ + on: vi.fn((event, msgCb) => { + if (event === 'message') { + wsMessageCallback = msgCb; + } + }) + }); + } + }) + })); + + (fs.promises.mkdir as Mock).mockResolvedValue(true); + (fs.promises.readdir as Mock) + .mockResolvedValueOnce( + [{ name: 'test-file', isDirectory: vi.fn().mockReturnValue(true) }] + ).mockResolvedValueOnce( + [{ name: 'test-file-2', isDirectory: vi.fn().mockReturnValue(false) }] + ).mockResolvedValue([]); + (fs.promises.copyFile as Mock).mockResolvedValue(undefined); + (fs.promises.readFile as Mock).mockResolvedValue(JSON.stringify({})); + + (ora as unknown as Mock).mockReturnValue({ + start: vi.fn().mockReturnValue({ + succeed: vi.fn(), + }), + }); + }); + + afterEach(() => { + process.argv = originalGetArgs; + vi.resetAllMocks(); + vi.resetModules(); + }) + + it('creates a new OnchainKit project with smart wallet enabled', async () => { + (prompts as unknown as Mock).mockResolvedValue({ + projectName: 'test-project', + clientKey: 'test-key', + smartWallet: true, + }); + + process.argv = ['node', 'cli.js']; + + await import('./cli.js'); + + await new Promise(resolve => setTimeout(resolve)); + + expect(fs.promises.writeFile).toHaveBeenCalledWith(expect.any(String), expect.stringContaining('NEXT_PUBLIC_ONCHAINKIT_PROJECT_NAME=test-project\nNEXT_PUBLIC_ONCHAINKIT_API_KEY=test-key\nNEXT_PUBLIC_ONCHAINKIT_WALLET_CONFIG=smartWalletOnly')); + expect(logSpy).toHaveBeenCalledWith(expect.stringContaining('Created new OnchainKit project in')); + }); + + it('creates a new OnchainKit project with smart wallet not enabled', async () => { + (prompts as unknown as Mock).mockResolvedValue({ + projectName: 'test-project', + clientKey: 'test-key', + smartWallet: false, + }); + + process.argv = ['node', 'cli.js']; + + await import('./cli.js'); + + await new Promise(resolve => setTimeout(resolve)); + + expect(fs.promises.writeFile).toHaveBeenCalledWith(expect.any(String), expect.stringContaining('NEXT_PUBLIC_ONCHAINKIT_PROJECT_NAME=test-project\nNEXT_PUBLIC_ONCHAINKIT_API_KEY=test-key\nNEXT_PUBLIC_ONCHAINKIT_WALLET_CONFIG=all')); + expect(logSpy).toHaveBeenCalledWith(expect.stringContaining('Created new OnchainKit project in')); + }); + + it('creates a new MiniKit project', async () => { + (prompts as unknown as Mock).mockResolvedValueOnce({ + projectName: 'test-project', + clientKey: 'test-key', + }).mockResolvedValueOnce({ + setUpFrame: false, + }); + + process.argv = ['node', 'cli.js', '--mini']; + + await import('./cli.js'); + + await new Promise(resolve => setTimeout(resolve)); + + expect(fs.promises.writeFile).toHaveBeenCalledWith(expect.any(String), expect.stringContaining('NEXT_PUBLIC_ONCHAINKIT_PROJECT_NAME=test-project\nNEXT_PUBLIC_ONCHAINKIT_API_KEY=test-key\nNEXT_PUBLIC_URL=\nNEXT_PUBLIC_SPLASH_IMAGE_URL=$NEXT_PUBLIC_URL/minikit.png\nNEXT_PUBLIC_SPLASH_BACKGROUND_COLOR=FFFFFF\nNEXT_PUBLIC_IMAGE_URL=$NEXT_PUBLIC_URL/minikit.png\nNEXT_PUBLIC_ICON_URL=https://onchainkit.xyz/favicon/48x48.png\nNEXT_PUBLIC_VERSION=next\nREDIS_URL=\nREDIS_TOKEN=')); + expect(logSpy).toHaveBeenCalledWith(expect.stringContaining('Created new MiniKit project in')); + }); + + it('creates a new MiniKit project and sets up Frame manifest', async () => { + (prompts as unknown as Mock).mockResolvedValueOnce({ + projectName: 'test-project', + clientKey: 'test-key', + }).mockResolvedValueOnce({ + setUpFrame: true, + }).mockResolvedValueOnce({ + browser: 'safari', + }); + + process.argv = ['node', 'cli.js', '--mini']; + + await import('./cli.js'); + + await new Promise(resolve => setTimeout(resolve)); + + expect(open).toHaveBeenCalledWith('http://localhost:3333', { + app: { + name: 'safari' + } + }); + + wsMessageCallback(Buffer.from(JSON.stringify({ + header: 'test-header', + payload: 'test-payload', + signature: 'test-signature', + domain: 'test-domain' + }))); + + await new Promise(resolve => setTimeout(resolve)); + + expect(fs.promises.writeFile).toHaveBeenCalledWith(expect.any(String), expect.stringContaining('NEXT_PUBLIC_ONCHAINKIT_PROJECT_NAME=test-project\nNEXT_PUBLIC_ONCHAINKIT_API_KEY=test-key\nNEXT_PUBLIC_URL=\nNEXT_PUBLIC_SPLASH_IMAGE_URL=$NEXT_PUBLIC_URL/minikit.png\nNEXT_PUBLIC_SPLASH_BACKGROUND_COLOR=FFFFFF\nNEXT_PUBLIC_IMAGE_URL=$NEXT_PUBLIC_URL/minikit.png\nNEXT_PUBLIC_ICON_URL=https://onchainkit.xyz/favicon/48x48.png\nNEXT_PUBLIC_VERSION=next\nREDIS_URL=\nREDIS_TOKEN=')); + expect(fs.promises.writeFile).toHaveBeenCalledWith(expect.any(String), expect.stringContaining('FARCASTER_HEADER=test-header\nFARCASTER_PAYLOAD=test-payload\nFARCASTER_SIGNATURE=test-signature\nNEXT_PUBLIC_URL=test-domain')); + expect(logSpy).toHaveBeenCalledWith(expect.stringContaining('* Account association generated successfully and added to your .env file!')); + expect(logSpy).toHaveBeenCalledWith(expect.stringContaining('Created new MiniKit project in')); + }) + + it('shows help text with --help', async () => { + vi.spyOn(console, 'error').mockImplementation(() => {}); + process.argv = ['node', 'cli.js', '--help']; + + await import('./cli.js'); + + expect(logSpy).toHaveBeenCalledWith(expect.stringContaining('Usage:')); + expect(logSpy).toHaveBeenCalledWith(expect.stringContaining('Options:')); + }); + + it('shows help text with -h', async () => { + vi.spyOn(console, 'error').mockImplementation(() => {}); + process.argv = ['node', 'cli.js', '-h']; + + await import('./cli.js'); + + expect(logSpy).toHaveBeenCalledWith(expect.stringContaining('Usage:')); + expect(logSpy).toHaveBeenCalledWith(expect.stringContaining('Options:')); + }); + + it('shows version with --version', async () => { + (fs.readFileSync as Mock).mockReturnValue(JSON.stringify({version: '0.0.1'})); + + const errorSpy = vi.spyOn(console, 'error').mockImplementation(() => {}); + process.argv = ['node', 'cli.js', '--version']; + + await import('./cli.js'); + + expect(logSpy).toHaveBeenCalledWith(expect.stringContaining('v0.0.1')); + expect(errorSpy).toHaveBeenCalledWith(expect.any(Error)); + }); + + it('shows version with -v', async () => { + (fs.readFileSync as Mock).mockReturnValue(JSON.stringify({version: '0.0.1'})); + + const errorSpy = vi.spyOn(console, 'error').mockImplementation(() => {}); + process.argv = ['node', 'cli.js', '-v']; + + await import('./cli.js'); + + expect(logSpy).toHaveBeenCalledWith(expect.stringContaining('v0.0.1')); + expect(errorSpy).toHaveBeenCalledWith(expect.any(Error)); + }); +}); diff --git a/create-onchain/src/cli.ts b/create-onchain/src/cli.ts index 5bc37564e8..5943fbdb5d 100644 --- a/create-onchain/src/cli.ts +++ b/create-onchain/src/cli.ts @@ -7,16 +7,14 @@ import pc from 'picocolors'; import ora from 'ora'; import { createClickableLink, - detectPackageManager, isValidPackageName, toValidPackageName, optimizedCopy, } from './utils.js'; - -const sourceDir = path.resolve( - fileURLToPath(import.meta.url), - '../../../templates/next' -); +import open from 'open'; +import express from 'express'; +import { createServer } from 'http'; +import { WebSocketServer, WebSocket } from 'ws'; const renameFiles: Record = { _gitignore: '.gitignore', @@ -46,7 +44,244 @@ async function copyDir(src: string, dest: string) { } } -async function init() { +type WebpageData = { header: string, payload: string, signature: string, domain: string }; + +async function getWebpageData(): Promise { + const app = express(); + const server = createServer(app); + const wss = new WebSocketServer({ server }); + + app.use(express.static(path.resolve( + fileURLToPath(import.meta.url), + '../../../src/manifest' + ))); + + return new Promise((resolve, reject) => { + wss.on('connection', (ws: WebSocket) => { + ws.on('message', (data: Buffer) => { + const parsedData = JSON.parse(data.toString()); + server.close(); + resolve(parsedData); + }); + }); + + server.listen(3333, () => { + open('http://localhost:3333'); + }); + }); +} + +async function createMiniKitAccountAssociation(envPath?: string) { + if (!envPath) { + envPath = path.join(process.cwd(), '.env'); + } + + const existingEnv = await fs.promises.readFile(envPath, 'utf-8').catch(() => null); + if (!existingEnv) { + console.log(pc.red('\n* Failed to read .env file. Please ensure you are in your project directory.')); + return false; + } + + try { + const webpageData = await getWebpageData(); + const envContent = `FARCASTER_HEADER=${webpageData.header}\nFARCASTER_PAYLOAD=${webpageData.payload}\nFARCASTER_SIGNATURE=${webpageData.signature}\nNEXT_PUBLIC_URL=${webpageData.domain}`; + const updatedEnv = existingEnv + .split('\n') + .filter(line => !line.startsWith('FARCASTER_') && !line.startsWith('NEXT_PUBLIC_URL')) + .concat(envContent) + .join('\n'); + await fs.promises.writeFile(envPath, updatedEnv); + + console.log(pc.blue('\n* Account association generated successfully and added to your .env file!')); + } catch (error) { + console.log(pc.red('\n* Failed to generate account association. Please try again.')); + return false; + } + + return true; +} + +async function createMiniKitTemplate() { + console.log( + `${pc.greenBright(` + ///////////////////////////////////////////////////////////////////////////////////////////////// + // // + // ::: ::: ::::::::::: :::: ::: ::::::::::: ::: ::: ::::::::::: ::::::::::: // + // :+:+: :+:+: :+: :+:+: :+: :+: :+: :+: :+: :+: // + // +:+ +:+:+ +:+ +:+ :+:+:+ +:+ +:+ +:+ +:+ +:+ +:+ // + // +#+ +:+ +#+ +#+ +#+ +:+ +#+ +#+ +#++:++ +#+ +#+ // + // +#+ +#+ +#+ +#+ +#+#+# +#+ +#+ +#+ +#+ +#+ // + // #+# #+# #+# #+# #+#+# #+# #+# #+# #+# #+# // + // ### ### ######## ### #### ######## ### ### ######## ### // + // // + // Powered by OnchainKit // + /////////////////////////////////////////////////////////////////////////////////////////////////`)}\n\n` + ); + + const defaultProjectName = 'my-minikit-app'; + + let result: prompts.Answers< + 'projectName' | 'packageName' | 'clientKey' + >; + + try { + result = await prompts( + [ + { + type: 'text', + name: 'projectName', + message: pc.reset('Project name:'), + initial: defaultProjectName, + onState: (state) => { + state.value = state.value.trim(); + }, + validate: (value) => { + const targetDir = path.join(process.cwd(), value); + if ( + fs.existsSync(targetDir) && + fs.readdirSync(targetDir).length > 0 + ) { + return 'Directory already exists and is not empty. Please choose a different name.'; + } + return true; + }, + }, + { + type: (_, { projectName }: { projectName: string }) => + isValidPackageName(projectName) ? null : 'text', + name: 'packageName', + message: pc.reset('Package name:'), + initial: (_, { projectName }: { projectName: string }) => + toValidPackageName(projectName), + validate: (dir) => + isValidPackageName(dir) || 'Invalid package.json name', + }, + { + type: 'password', + name: 'clientKey', + message: pc.reset( + `Enter your ${createClickableLink( + 'Coinbase Developer Platform Client API Key:', + 'https://portal.cdp.coinbase.com/products/onchainkit' + )} (optional)` + ), + }, + ], + { + onCancel: () => { + console.log('\nProject creation cancelled.'); + process.exit(0); + }, + } + ); + } catch (cancelled: any) { + console.log(cancelled.message); + process.exit(1); + } + + const { projectName, packageName, clientKey } = result; + const root = path.join(process.cwd(), projectName); + + const spinner = ora(`Creating ${projectName}...`).start(); + + const sourceDir = path.resolve( + fileURLToPath(import.meta.url), + '../../../templates/minikit' + ); + + await copyDir(sourceDir, root); + const pkgPath = path.join(root, 'package.json'); + const pkg = JSON.parse(await fs.promises.readFile(pkgPath, 'utf-8')); + pkg.name = packageName || toValidPackageName(projectName); + await fs.promises.writeFile(pkgPath, JSON.stringify(pkg, null, 2)); + + // Create .env file + const envPath = path.join(root, '.env'); + await fs.promises.writeFile( + envPath, + `NEXT_PUBLIC_ONCHAINKIT_PROJECT_NAME=${projectName} +NEXT_PUBLIC_ONCHAINKIT_API_KEY=${clientKey} +NEXT_PUBLIC_URL= +NEXT_PUBLIC_SPLASH_IMAGE_URL=$NEXT_PUBLIC_URL/snake.png +NEXT_PUBLIC_SPLASH_BACKGROUND_COLOR=FFFFFF +NEXT_PUBLIC_IMAGE_URL=$NEXT_PUBLIC_URL/snake.png +NEXT_PUBLIC_ICON_URL=$NEXT_PUBLIC_URL/snake.png +NEXT_PUBLIC_VERSION=next +REDIS_URL= +REDIS_TOKEN=` + ); + + spinner.succeed(); + + console.log(`\n\n${pc.magenta(`Created new MiniKit project in ${root}`)}\n`); + + console.log(`\n${pc.reset('Do you want to set up your Frames Account Manifest now?')}`); + console.log(pc.blue('* You can set this up later by running `npm create-onchain --generate` in your project directory.')); + console.log(pc.blue('* Note: this process will open in a new browser window.')); + + let setUpFrameResult: prompts.Answers<'setUpFrame'>; + try { + setUpFrameResult = await prompts( + [ + { + type: 'toggle', + name: 'setUpFrame', + message: pc.reset('Set up now?'), + initial: true, + active: 'yes', + inactive: 'no', + }, + ], + { + onCancel: () => { + console.log('\nSetup frame cancelled.'); + return false; + }, + } + ); + } catch (cancelled: any) { + console.log(cancelled.message); + return false; + } + + const { setUpFrame } = setUpFrameResult; + if (setUpFrame) { + await createMiniKitAccountAssociation(envPath); + } + + logMiniKitSetupSummary(projectName, root, clientKey); +} + +function logMiniKitSetupSummary(projectName: string, root: string, clientKey: string) { + console.log(`\nIntegrations:`); + + console.log(`${pc.greenBright('\u2713')} ${pc.blueBright(`MiniKit`)}`); + console.log(`${pc.greenBright('\u2713')} ${pc.blueBright(`OnchainKit`)}`); + console.log(`${pc.greenBright('\u2713')} ${pc.blueBright(`Base`)}`); + if (clientKey) { + console.log(`${pc.greenBright('\u2713')} ${pc.blueBright(`Coinbase Developer Platform`)}`); + console.log(`${pc.greenBright('\u2713')} ${pc.blueBright(`Paymaster`)}`); + } + + console.log(`\nFrameworks:`); + console.log(`${pc.cyan('- Wagmi')}`); + console.log(`${pc.cyan('- React')}`); + console.log(`${pc.cyan('- Next.js')}`); + console.log(`${pc.cyan('- Tailwind CSS')}`); + console.log(`${pc.cyan('- ESLint')}`); + console.log(`${pc.cyan('- Upstash Redis')}`); + + console.log(`\nTo get started with ${pc.green(projectName)}, run the following commands:\n`); + if (root !== process.cwd()) { + console.log(` - cd ${path.relative(process.cwd(), root)}`); + } + console.log(' - npm install'); + console.log(' - npm run dev'); + + console.log(pc.blue('\n* Don\'t forget to update the environment variables for your project in the `.env` file.')); +} + +async function createOnchainKitTemplate() { console.log( `${pc.greenBright(` ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// @@ -62,6 +297,7 @@ async function init() { //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////`)}\n\n` ); + const defaultProjectName = 'my-onchainkit-app'; let result: prompts.Answers< @@ -117,7 +353,7 @@ async function init() { initial: true, active: 'yes', inactive: 'no', - }, + } ], { onCancel: () => { @@ -136,8 +372,12 @@ async function init() { const spinner = ora(`Creating ${projectName}...`).start(); + const sourceDir = path.resolve( + fileURLToPath(import.meta.url), + '../../../templates/next' + ); + await copyDir(sourceDir, root); - const pkgPath = path.join(root, 'package.json'); const pkg = JSON.parse(await fs.promises.readFile(pkgPath, 'utf-8')); pkg.name = packageName || toValidPackageName(projectName); @@ -151,7 +391,7 @@ async function init() { smartWallet ? 'smartWalletOnly' : 'all' }` ); - + spinner.succeed(); console.log(`\n${pc.magenta(`Created new OnchainKit project in ${root}`)}`); @@ -192,6 +432,78 @@ async function init() { console.log(' - npm run dev'); } +export function getArgs() { + const options = { isHelp: false, isVersion: false, isGenerate: false, isMiniKit: false }; + + // find any argument with -- or - + const arg = process.argv.find(arg => arg.startsWith('--') || arg.startsWith('-')); + switch(arg) { + case '-h': + case '--help': + options.isHelp = true; + break; + case '-v': + case '--version': + options.isVersion = true; + break; + case '-g': + case '--generate': + options.isGenerate = true; + break; + case '-m': + case '--mini': + options.isMiniKit = true; + break; + default: + break; + } + + return options; +} + +async function init() { + const { isHelp, isVersion, isGenerate, isMiniKit } = getArgs(); + if (isHelp) { + console.log( +`${pc.greenBright(` +Usage: +npm create-onchain [options] + +Creates an OnchainKit project based on nextJs. + +Options: +--version, -v: Show version +--mini, -m: Create a MiniKit project +--generate, -g: Generate your Frames account association +--help, -h: Show help +`)}` + ); + process.exit(0); + } + + if (isVersion) { + const pkgPath = path.resolve( + fileURLToPath(import.meta.url), + '../../../package.json' + ); + const packageJsonContent = fs.readFileSync(pkgPath, 'utf8'); + const packageJson = JSON.parse(packageJsonContent); + console.log(`${pc.greenBright(`v${packageJson.version}`)}`); + process.exit(0); + } + + if (isGenerate) { + await createMiniKitAccountAssociation(); + process.exit(0); + } + + if (isMiniKit) { + await createMiniKitTemplate(); + } else { + await createOnchainKitTemplate(); + } +} + init().catch((e) => { console.error(e); }); diff --git a/create-onchain/src/manifest/.gitkeep b/create-onchain/src/manifest/.gitkeep new file mode 100644 index 0000000000..e69de29bb2 diff --git a/create-onchain/src/manifest/assets/browser-0t0YC0rb.js b/create-onchain/src/manifest/assets/browser-0t0YC0rb.js new file mode 100644 index 0000000000..ca1584f17f --- /dev/null +++ b/create-onchain/src/manifest/assets/browser-0t0YC0rb.js @@ -0,0 +1 @@ +var b={exports:{}},A,x;function S(){if(x)return A;x=1;var u=1e3,i=u*60,C=i*60,r=C*24,h=r*7,m=r*365.25;A=function(t,e){e=e||{};var n=typeof t;if(n==="string"&&t.length>0)return d(t);if(n==="number"&&isFinite(t))return e.long?p(t):g(t);throw new Error("val is not a non-empty string or a valid number. val="+JSON.stringify(t))};function d(t){if(t=String(t),!(t.length>100)){var e=/^(-?(?:\d+)?\.?\d+) *(milliseconds?|msecs?|ms|seconds?|secs?|s|minutes?|mins?|m|hours?|hrs?|h|days?|d|weeks?|w|years?|yrs?|y)?$/i.exec(t);if(e){var n=parseFloat(e[1]),s=(e[2]||"ms").toLowerCase();switch(s){case"years":case"year":case"yrs":case"yr":case"y":return n*m;case"weeks":case"week":case"w":return n*h;case"days":case"day":case"d":return n*r;case"hours":case"hour":case"hrs":case"hr":case"h":return n*C;case"minutes":case"minute":case"mins":case"min":case"m":return n*i;case"seconds":case"second":case"secs":case"sec":case"s":return n*u;case"milliseconds":case"millisecond":case"msecs":case"msec":case"ms":return n;default:return}}}}function g(t){var e=Math.abs(t);return e>=r?Math.round(t/r)+"d":e>=C?Math.round(t/C)+"h":e>=i?Math.round(t/i)+"m":e>=u?Math.round(t/u)+"s":t+"ms"}function p(t){var e=Math.abs(t);return e>=r?o(t,e,r,"day"):e>=C?o(t,e,C,"hour"):e>=i?o(t,e,i,"minute"):e>=u?o(t,e,u,"second"):t+" ms"}function o(t,e,n,s){var a=e>=n*1.5;return Math.round(t/n)+" "+s+(a?"s":"")}return A}var k,M;function L(){if(M)return k;M=1;function u(i){r.debug=r,r.default=r,r.coerce=o,r.disable=g,r.enable=m,r.enabled=p,r.humanize=S(),r.destroy=t,Object.keys(i).forEach(e=>{r[e]=i[e]}),r.names=[],r.skips=[],r.formatters={};function C(e){let n=0;for(let s=0;s{if(v==="%%")return"%";w++;const I=r.formatters[O];if(typeof I=="function"){const q=l[w];v=I.call(f,q),l.splice(w,1),w--}return v}),r.formatArgs.call(f,l),(f.log||r.log).apply(f,l)}return c.namespace=e,c.useColors=r.useColors(),c.color=r.selectColor(e),c.extend=h,c.destroy=r.destroy,Object.defineProperty(c,"enabled",{enumerable:!0,configurable:!1,get:()=>s!==null?s:(a!==r.namespaces&&(a=r.namespaces,F=r.enabled(e)),F),set:l=>{s=l}}),typeof r.init=="function"&&r.init(c),c}function h(e,n){const s=r(this.namespace+(typeof n>"u"?":":n)+e);return s.log=this.log,s}function m(e){r.save(e),r.namespaces=e,r.names=[],r.skips=[];const n=(typeof e=="string"?e:"").trim().replace(" ",",").split(",").filter(Boolean);for(const s of n)s[0]==="-"?r.skips.push(s.slice(1)):r.names.push(s)}function d(e,n){let s=0,a=0,F=-1,c=0;for(;s"-"+n)].join(",");return r.enable(""),e}function p(e){for(const n of r.skips)if(d(e,n))return!1;for(const n of r.names)if(d(e,n))return!0;return!1}function o(e){return e instanceof Error?e.stack||e.message:e}function t(){console.warn("Instance method `debug.destroy()` is deprecated and no longer does anything. It will be removed in the next major version of `debug`.")}return r.enable(r.load()),r}return k=u,k}var E;function z(){return E||(E=1,function(u,i){var C={};i.formatArgs=h,i.save=m,i.load=d,i.useColors=r,i.storage=g(),i.destroy=(()=>{let o=!1;return()=>{o||(o=!0,console.warn("Instance method `debug.destroy()` is deprecated and no longer does anything. It will be removed in the next major version of `debug`."))}})(),i.colors=["#0000CC","#0000FF","#0033CC","#0033FF","#0066CC","#0066FF","#0099CC","#0099FF","#00CC00","#00CC33","#00CC66","#00CC99","#00CCCC","#00CCFF","#3300CC","#3300FF","#3333CC","#3333FF","#3366CC","#3366FF","#3399CC","#3399FF","#33CC00","#33CC33","#33CC66","#33CC99","#33CCCC","#33CCFF","#6600CC","#6600FF","#6633CC","#6633FF","#66CC00","#66CC33","#9900CC","#9900FF","#9933CC","#9933FF","#99CC00","#99CC33","#CC0000","#CC0033","#CC0066","#CC0099","#CC00CC","#CC00FF","#CC3300","#CC3333","#CC3366","#CC3399","#CC33CC","#CC33FF","#CC6600","#CC6633","#CC9900","#CC9933","#CCCC00","#CCCC33","#FF0000","#FF0033","#FF0066","#FF0099","#FF00CC","#FF00FF","#FF3300","#FF3333","#FF3366","#FF3399","#FF33CC","#FF33FF","#FF6600","#FF6633","#FF9900","#FF9933","#FFCC00","#FFCC33"];function r(){if(typeof window<"u"&&window.process&&(window.process.type==="renderer"||window.process.__nwjs))return!0;if(typeof navigator<"u"&&navigator.userAgent&&navigator.userAgent.toLowerCase().match(/(edge|trident)\/(\d+)/))return!1;let o;return typeof document<"u"&&document.documentElement&&document.documentElement.style&&document.documentElement.style.WebkitAppearance||typeof window<"u"&&window.console&&(window.console.firebug||window.console.exception&&window.console.table)||typeof navigator<"u"&&navigator.userAgent&&(o=navigator.userAgent.toLowerCase().match(/firefox\/(\d+)/))&&parseInt(o[1],10)>=31||typeof navigator<"u"&&navigator.userAgent&&navigator.userAgent.toLowerCase().match(/applewebkit\/(\d+)/)}function h(o){if(o[0]=(this.useColors?"%c":"")+this.namespace+(this.useColors?" %c":" ")+o[0]+(this.useColors?"%c ":" ")+"+"+u.exports.humanize(this.diff),!this.useColors)return;const t="color: "+this.color;o.splice(1,0,t,"color: inherit");let e=0,n=0;o[0].replace(/%[a-zA-Z%]/g,s=>{s!=="%%"&&(e++,s==="%c"&&(n=e))}),o.splice(n,0,t)}i.log=console.debug||console.log||(()=>{});function m(o){try{o?i.storage.setItem("debug",o):i.storage.removeItem("debug")}catch{}}function d(){let o;try{o=i.storage.getItem("debug")}catch{}return!o&&typeof process<"u"&&"env"in process&&(o=C.DEBUG),o}function g(){try{return localStorage}catch{}}u.exports=L()(i);const{formatters:p}=u.exports;p.j=function(o){try{return JSON.stringify(o)}catch(t){return"[UnexpectedJSONParseError]: "+t.message}}}(b,b.exports)),b.exports}export{z as r}; diff --git a/create-onchain/src/manifest/assets/hooks.module-DVREuRDX.js b/create-onchain/src/manifest/assets/hooks.module-DVREuRDX.js new file mode 100644 index 0000000000..2607061734 --- /dev/null +++ b/create-onchain/src/manifest/assets/hooks.module-DVREuRDX.js @@ -0,0 +1 @@ +var L,m,v_,m_,H,o_,y_,g_,b_,Y,G,J,k_,F={},$_=[],L_=/acit|ex(?:s|g|n|p|$)|rph|grid|ows|mnc|ntw|ine[ch]|zoo|^ord|itera/i,W=Array.isArray;function w(e,_){for(var t in _)e[t]=_[t];return e}function Z(e){e&&e.parentNode&&e.parentNode.removeChild(e)}function K(e,_,t){var n,u,r,i={};for(r in _)r=="key"?n=_[r]:r=="ref"?u=_[r]:i[r]=_[r];if(arguments.length>2&&(i.children=arguments.length>3?L.call(arguments,2):t),typeof e=="function"&&e.defaultProps!=null)for(r in e.defaultProps)i[r]===void 0&&(i[r]=e.defaultProps[r]);return M(e,i,n,u,null)}function M(e,_,t,n,u){var r={type:e,props:_,key:t,ref:n,__k:null,__:null,__b:0,__e:null,__c:null,constructor:void 0,__v:u??++v_,__i:-1,__u:0};return u==null&&m.vnode!=null&&m.vnode(r),r}function W_(){return{current:null}}function j(e){return e.children}function A(e,_){this.props=e,this.context=_}function S(e,_){if(_==null)return e.__?S(e.__,e.__i+1):null;for(var t;_c&&H.sort(g_),e=H.shift(),c=H.length,e.__d&&(t=void 0,u=(n=(_=e).__v).__e,r=[],i=[],_.__P&&((t=w({},n)).__v=n.__v+1,m.vnode&&m.vnode(t),__(_.__P,t,n,_.__n,_.__P.namespaceURI,32&n.__u?[u]:null,r,u??S(n),!!(32&n.__u),i),t.__v=n.__v,t.__.__k[t.__i]=t,P_(r,t,i),t.__e!=u&&w_(t)));B.__r=0}function C_(e,_,t,n,u,r,i,c,f,l,a){var o,p,s,g,k,b,d=n&&n.__k||$_,v=_.length;for(f=j_(t,_,d,f,v),o=0;o0?M(i.type,i.props,i.key,i.ref?i.ref:null,i.__v):i).__=e,i.__b=e.__b+1,c=null,(l=i.__i=I_(i,t,f,o))!==-1&&(o--,(c=t[l])&&(c.__u|=2)),c==null||c.__v===null?(l==-1&&(u>a?p--:uf?p--:p++,i.__u|=4))):e.__k[r]=null;if(o)for(r=0;r(f!=null&&(2&f.__u)==0?1:0))for(u=t-1,r=t+1;u>=0||r<_.length;){if(u>=0){if((f=_[u])&&(2&f.__u)==0&&i==f.key&&c===f.type)return u;u--}if(r<_.length){if((f=_[r])&&(2&f.__u)==0&&i==f.key&&c===f.type)return r;r++}}return-1}function u_(e,_,t){_[0]=="-"?e.setProperty(_,t??""):e[_]=t==null?"":typeof t!="number"||L_.test(_)?t:t+"px"}function O(e,_,t,n,u){var r;_:if(_=="style")if(typeof t=="string")e.style.cssText=t;else{if(typeof n=="string"&&(e.style.cssText=n=""),n)for(_ in n)t&&_ in t||u_(e.style,_,"");if(t)for(_ in t)n&&t[_]===n[_]||u_(e.style,_,t[_])}else if(_[0]=="o"&&_[1]=="n")r=_!=(_=_.replace(b_,"$1")),_=_.toLowerCase()in e||_=="onFocusOut"||_=="onFocusIn"?_.toLowerCase().slice(2):_.slice(2),e.l||(e.l={}),e.l[_+r]=t,t?n?t.t=n.t:(t.t=Y,e.addEventListener(_,r?J:G,r)):e.removeEventListener(_,r?J:G,r);else{if(u=="http://www.w3.org/2000/svg")_=_.replace(/xlink(H|:h)/,"h").replace(/sName$/,"s");else if(_!="width"&&_!="height"&&_!="href"&&_!="list"&&_!="form"&&_!="tabIndex"&&_!="download"&&_!="rowSpan"&&_!="colSpan"&&_!="role"&&_!="popover"&&_ in e)try{e[_]=t??"";break _}catch{}typeof t=="function"||(t==null||t===!1&&_[4]!="-"?e.removeAttribute(_):e.setAttribute(_,_=="popover"&&t==1?"":t))}}function i_(e){return function(_){if(this.l){var t=this.l[_.type+e];if(_.u==null)_.u=Y++;else if(_.u2&&(c.children=arguments.length>3?L.call(arguments,2):t),M(e.type,c,n||e.key,u||e.ref,null)}function B_(e){function _(t){var n,u;return this.getChildContext||(n=new Set,(u={})[_.__c]=this,this.getChildContext=function(){return u},this.componentWillUnmount=function(){n=null},this.shouldComponentUpdate=function(r){this.props.value!==r.value&&n.forEach(function(i){i.__e=!0,Q(i)})},this.sub=function(r){n.add(r);var i=r.componentWillUnmount;r.componentWillUnmount=function(){n&&n.delete(r),i&&i.call(r)}}),t.children}return _.__c="__cC"+k_++,_.__=e,_.Provider=_.__l=(_.Consumer=function(t,n){return t.children(n)}).contextType=_,_}L=$_.slice,m={__e:function(e,_,t,n){for(var u,r,i;_=_.__;)if((u=_.__c)&&!u.__)try{if((r=u.constructor)&&r.getDerivedStateFromError!=null&&(u.setState(r.getDerivedStateFromError(e)),i=u.__d),u.componentDidCatch!=null&&(u.componentDidCatch(e,n||{}),i=u.__d),i)return u.__E=u}catch(c){e=c}throw e}},v_=0,m_=function(e){return e!=null&&e.constructor==null},A.prototype.setState=function(e,_){var t;t=this.__s!=null&&this.__s!==this.state?this.__s:this.__s=w({},this.state),typeof e=="function"&&(e=e(w({},t),this.props)),e&&w(t,e),e!=null&&this.__v&&(_&&this._sb.push(_),Q(this))},A.prototype.forceUpdate=function(e){this.__v&&(this.__e=!0,e&&this.__h.push(e),Q(this))},A.prototype.render=j,H=[],y_=typeof Promise=="function"?Promise.prototype.then.bind(Promise.resolve()):setTimeout,g_=function(e,_){return e.__v.__b-_.__v.__b},B.__r=0,b_=/(PointerCapture)$|Capture$/i,Y=0,G=i_(!1),J=i_(!0),k_=0;const ee=Object.freeze(Object.defineProperty({__proto__:null,Component:A,Fragment:j,cloneElement:q_,createContext:B_,createElement:K,createRef:W_,h:K,hydrate:U_,get isValidElement(){return m_},get options(){return m},render:T_,toChildArray:H_},Symbol.toStringTag,{value:"Module"}));var C,h,z,l_,E=0,N_=[],y=m,c_=y.__b,f_=y.__r,s_=y.diffed,a_=y.__c,p_=y.unmount,h_=y.__;function P(e,_){y.__h&&y.__h(h,e,E||_),E=0;var t=h.__H||(h.__H={__:[],__h:[]});return e>=t.__.length&&t.__.push({}),t.__[e]}function D_(e){return E=1,M_(F_,e)}function M_(e,_,t){var n=P(C++,2);if(n.t=e,!n.__c&&(n.__=[t?t(_):F_(void 0,_),function(c){var f=n.__N?n.__N[0]:n.__[0],l=n.t(f,c);f!==l&&(n.__N=[l,n.__[1]],n.__c.setState({}))}],n.__c=h,!h.__f)){var u=function(c,f,l){if(!n.__c.__H)return!0;var a=n.__c.__H.__.filter(function(p){return!!p.__c});if(a.every(function(p){return!p.__N}))return!r||r.call(this,c,f,l);var o=n.__c.props!==c;return a.forEach(function(p){if(p.__N){var s=p.__[0];p.__=p.__N,p.__N=void 0,s!==p.__[0]&&(o=!0)}}),r&&r.call(this,c,f,l)||o};h.__f=!0;var r=h.shouldComponentUpdate,i=h.componentWillUpdate;h.componentWillUpdate=function(c,f,l){if(this.__e){var a=r;r=void 0,u(c,f,l),r=a}i&&i.call(this,c,f,l)},h.shouldComponentUpdate=u}return n.__N||n.__}function V_(e,_){var t=P(C++,3);!y.__s&&n_(t.__H,_)&&(t.__=e,t.u=_,h.__H.__h.push(t))}function A_(e,_){var t=P(C++,4);!y.__s&&n_(t.__H,_)&&(t.__=e,t.u=_,h.__h.push(t))}function z_(e){return E=5,t_(function(){return{current:e}},[])}function G_(e,_,t){E=6,A_(function(){if(typeof e=="function"){var n=e(_());return function(){e(null),n&&typeof n=="function"&&n()}}if(e)return e.current=_(),function(){return e.current=null}},t==null?t:t.concat(e))}function t_(e,_){var t=P(C++,7);return n_(t.__H,_)&&(t.__=e(),t.__H=_,t.__h=e),t.__}function J_(e,_){return E=8,t_(function(){return e},_)}function K_(e){var _=h.context[e.__c],t=P(C++,9);return t.c=e,_?(t.__==null&&(t.__=!0,_.sub(h)),_.props.value):e.__}function Q_(e,_){y.useDebugValue&&y.useDebugValue(_?_(e):e)}function X_(e){var _=P(C++,10),t=D_();return _.__=e,h.componentDidCatch||(h.componentDidCatch=function(n,u){_.__&&_.__(n,u),t[1](n)}),[t[0],function(){t[1](void 0)}]}function Y_(){var e=P(C++,11);if(!e.__){for(var _=h.__v;_!==null&&!_.__m&&_.__!==null;)_=_.__;var t=_.__m||(_.__m=[0,0]);e.__="P"+t[0]+"-"+t[1]++}return e.__}function Z_(){for(var e;e=N_.shift();)if(e.__P&&e.__H)try{e.__H.__h.forEach(q),e.__H.__h.forEach(X),e.__H.__h=[]}catch(_){e.__H.__h=[],y.__e(_,e.__v)}}y.__b=function(e){h=null,c_&&c_(e)},y.__=function(e,_){e&&_.__k&&_.__k.__m&&(e.__m=_.__k.__m),h_&&h_(e,_)},y.__r=function(e){f_&&f_(e),C=0;var _=(h=e.__c).__H;_&&(z===h?(_.__h=[],h.__h=[],_.__.forEach(function(t){t.__N&&(t.__=t.__N),t.u=t.__N=void 0})):(_.__h.forEach(q),_.__h.forEach(X),_.__h=[],C=0)),z=h},y.diffed=function(e){s_&&s_(e);var _=e.__c;_&&_.__H&&(_.__H.__h.length&&(N_.push(_)!==1&&l_===y.requestAnimationFrame||((l_=y.requestAnimationFrame)||_e)(Z_)),_.__H.__.forEach(function(t){t.u&&(t.__H=t.u),t.u=void 0})),z=h=null},y.__c=function(e,_){_.some(function(t){try{t.__h.forEach(q),t.__h=t.__h.filter(function(n){return!n.__||X(n)})}catch(n){_.some(function(u){u.__h&&(u.__h=[])}),_=[],y.__e(n,t.__v)}}),a_&&a_(e,_)},y.unmount=function(e){p_&&p_(e);var _,t=e.__c;t&&t.__H&&(t.__H.__.forEach(function(n){try{q(n)}catch(u){_=u}}),t.__H=void 0,_&&y.__e(_,t.__v))};var d_=typeof requestAnimationFrame=="function";function _e(e){var _,t=function(){clearTimeout(n),d_&&cancelAnimationFrame(_),setTimeout(e)},n=setTimeout(t,100);d_&&(_=requestAnimationFrame(t))}function q(e){var _=h,t=e.__c;typeof t=="function"&&(e.__c=void 0,t()),h=_}function X(e){var _=h;e.__c=e.__(),h=_}function n_(e,_){return!e||e.length!==_.length||_.some(function(t,n){return t!==e[n]})}function F_(e,_){return typeof _=="function"?_(e):_}const te=Object.freeze(Object.defineProperty({__proto__:null,useCallback:J_,useContext:K_,useDebugValue:Q_,useEffect:V_,useErrorBoundary:X_,useId:Y_,useImperativeHandle:G_,useLayoutEffect:A_,useMemo:t_,useReducer:M_,useRef:z_,useState:D_},Symbol.toStringTag,{value:"Module"}));export{T_ as E,K as _,D_ as d,te as h,ee as p,V_ as y}; diff --git a/create-onchain/src/manifest/index.html b/create-onchain/src/manifest/index.html new file mode 100644 index 0000000000..4ae39135a2 --- /dev/null +++ b/create-onchain/src/manifest/index.html @@ -0,0 +1,14 @@ + + + + + + + Account Manifest Generator + + + + +
+ + diff --git a/create-onchain/templates/minikit/.eslintrc.json b/create-onchain/templates/minikit/.eslintrc.json new file mode 100644 index 0000000000..3722418549 --- /dev/null +++ b/create-onchain/templates/minikit/.eslintrc.json @@ -0,0 +1,3 @@ +{ + "extends": ["next/core-web-vitals", "next/typescript"] +} diff --git a/create-onchain/templates/minikit/.yarnrc.yml b/create-onchain/templates/minikit/.yarnrc.yml new file mode 100644 index 0000000000..14fd359c8f --- /dev/null +++ b/create-onchain/templates/minikit/.yarnrc.yml @@ -0,0 +1,2 @@ +# You can remove this file if you don't want to use Yarn package manager. +nodeLinker: node-modules \ No newline at end of file diff --git a/create-onchain/templates/minikit/README.md b/create-onchain/templates/minikit/README.md new file mode 100644 index 0000000000..d65ac59893 --- /dev/null +++ b/create-onchain/templates/minikit/README.md @@ -0,0 +1,39 @@ +This is a [Next.js](https://nextjs.org) project bootstrapped with [`create-onchain`](). + + +## Getting Started + +First, install dependencies: + +```bash +npm install +# or +yarn install +# or +pnpm install +# or +bun install +``` + +Next, run the development server: + +```bash +npm run dev +# or +yarn dev +# or +pnpm dev +# or +bun dev +``` + +Open [http://localhost:3000](http://localhost:3000) with your browser to see the result. + +You can start editing the page by modifying `app/page.tsx`. The page auto-updates as you edit the file. + + +## Learn More + +To learn more about OnchainKit, see our [documentation](https://onchainkit.xyz/getting-started). + +To learn more about Next.js, see the [Next.js documentation](https://nextjs.org/docs). diff --git a/create-onchain/templates/minikit/_gitignore b/create-onchain/templates/minikit/_gitignore new file mode 100644 index 0000000000..504d5073b1 --- /dev/null +++ b/create-onchain/templates/minikit/_gitignore @@ -0,0 +1,36 @@ +# See https://help.github.com/articles/ignoring-files/ for more about ignoring files. + +# dependencies +/node_modules +/.pnp +.pnp.js +.yarn/install-state.gz + +# 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* + +# vercel +.vercel + +# typescript +*.tsbuildinfo +next-env.d.ts diff --git a/create-onchain/templates/minikit/app/.well-known/farcaster.json/route.ts b/create-onchain/templates/minikit/app/.well-known/farcaster.json/route.ts new file mode 100644 index 0000000000..db3444b39f --- /dev/null +++ b/create-onchain/templates/minikit/app/.well-known/farcaster.json/route.ts @@ -0,0 +1,22 @@ +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": { + "version": process.env.NEXT_PUBLIC_VERSION, + "name": process.env.NEXT_PUBLIC_ONCHAINKIT_PROJECT_NAME, + "homeUrl": URL, + "iconUrl": process.env.NEXT_PUBLIC_ICON_URL, + "imageUrl": process.env.NEXT_PUBLIC_IMAGE_URL, + "buttonTitle": `Launch ${process.env.NEXT_PUBLIC_ONCHAINKIT_PROJECT_NAME}`, + "splashImageUrl": process.env.NEXT_PUBLIC_SPLASH_IMAGE_URL, + "splashBackgroundColor": `#${process.env.NEXT_PUBLIC_SPLASH_BACKGROUND_COLOR}`, + "webhookUrl": `${URL}/api/webhook` + } + }); +} \ No newline at end of file diff --git a/create-onchain/templates/minikit/app/api/notify/route.ts b/create-onchain/templates/minikit/app/api/notify/route.ts new file mode 100644 index 0000000000..db57dd6f27 --- /dev/null +++ b/create-onchain/templates/minikit/app/api/notify/route.ts @@ -0,0 +1,34 @@ +import { sendFrameNotification } from "@/lib/notification-client"; + +export async function POST(request: Request) { + try { + const body = await request.json(); + console.log('Received request body:', body); // Debug log + + const { fid, notification } = body; + + const result = await sendFrameNotification({ + fid, + title: notification.title, + body: notification.body, + }); + + if (result.state === "error") { + console.error('API error:', result.error); + return new Response(JSON.stringify({ error: result.error }), { + status: 500, + headers: { 'Content-Type': 'application/json' } + }); + } + console.log('Notification sent successfully'); + return new Response('OK', { status: 200 }); + } catch (error) { + console.error('Server error:', error); + return new Response(JSON.stringify({ + error: error instanceof Error ? error.message : 'Unknown error' + }), { + status: 400, + headers: { 'Content-Type': 'application/json' } + }); + } +} \ No newline at end of file diff --git a/create-onchain/templates/minikit/app/api/scores/route.ts b/create-onchain/templates/minikit/app/api/scores/route.ts new file mode 100644 index 0000000000..766ecbcd59 --- /dev/null +++ b/create-onchain/templates/minikit/app/api/scores/route.ts @@ -0,0 +1,19 @@ +import { NextResponse } from 'next/server'; +import { getScores, setScore, resetScores } from '@/lib/scores'; +import { Score } from '@/lib/scores'; + +export async function GET() { + const scores = await getScores(); + return NextResponse.json(scores); +} + +export async function POST(request: Request) { + const score: Score = await request.json(); + await setScore(score); + return NextResponse.json({ success: true }); +} + +export async function DELETE() { + await resetScores(); + return NextResponse.json({ success: true }); +} \ No newline at end of file diff --git a/create-onchain/templates/minikit/app/api/webhook/route.ts b/create-onchain/templates/minikit/app/api/webhook/route.ts new file mode 100644 index 0000000000..e5d0e04fb2 --- /dev/null +++ b/create-onchain/templates/minikit/app/api/webhook/route.ts @@ -0,0 +1,115 @@ +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'; + +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: [{ + "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" + }], + 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); + console.log('valid', valid); + + if (!valid) { + return Response.json({ success: false, error: 'Invalid FID ownership' }, { status: 401 }); + } + + switch (event.event) { + case "frame_added": + if (event.notificationDetails) { + console.log("frame_added", "event.notificationDetails", event.notificationDetails); + await setUserNotificationDetails(fid, event.notificationDetails); + await sendFrameNotification({ + fid, + title: `Welcome to ${appName}`, + body: `Thank you for adding ${appName}`, + }); + } else { + console.log("frame_added", "event.notificationDetails", event.notificationDetails); + 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/create-onchain/templates/minikit/app/components/Sammy.tsx b/create-onchain/templates/minikit/app/components/Sammy.tsx new file mode 100644 index 0000000000..43922a8599 --- /dev/null +++ b/create-onchain/templates/minikit/app/components/Sammy.tsx @@ -0,0 +1,830 @@ +'use client'; + +import React, { useEffect, useRef, useMemo, useState, DependencyList, useCallback } from 'react'; +import { useOpenUrl, useNotification } from "@coinbase/onchainkit/minikit"; +import { Transaction, TransactionButton, TransactionResponse, TransactionToast, TransactionToastAction, TransactionToastIcon, TransactionToastLabel, TransactionError } from "@coinbase/onchainkit/transaction"; +import { ConnectWallet, ConnectWalletText, Wallet, WalletDropdown, WalletDropdownDisconnect, WalletDropdownLink } from "@coinbase/onchainkit/wallet"; +import { Name, Identity, EthBalance, Address, Avatar } from "@coinbase/onchainkit/identity"; +import { getTopScores, addScore, MAX_SCORES } from "@/lib/scores-client"; +import { Score } from "@/lib/scores"; +import { useAccount } from "wagmi"; +import { encodeAbiParameters } from "viem"; +import ArrowSvg from "../svg/ArrowSvg"; +import SnakeLogo from '../svg/SnakeLogo'; + +const FPS = 60; +const MS_PER_FRAME = 1000 / FPS; + +const NUM_TARGETS_PER_LEVEL = 10; + +const GameState = { + INTRO: 0, + PAUSED: 1, + RUNNING: 2, + WON: 3, + DEAD: 4, + AWAITINGNEXTLEVEL: 5 +}; + +const MoveState = { + NONE: 0, + UP: 1, + RIGHT: 2, + DOWN: 3, + LEFT: 4 +}; + +const LevelMaps: { [key: number]: { x1: number, y1: number, width: number, height: number }[] } = { + 1: [ + { x1: 0, y1: 0, width: 10, height: 500 }, + { x1: 0, y1: 0, width: 500, height: 10 }, + { x1: 490, y1: 0, width: 10, height: 500 }, + { x1: 0, y1: 490, width: 500, height: 10 } + ], + 2: [ + { x1: 0, y1: 0, width: 10, height: 500 }, + { x1: 0, y1: 0, width: 500, height: 10 }, + { x1: 490, y1: 0, width: 10, height: 500 }, + { x1: 0, y1: 490, width: 500, height: 10 }, + { x1: 250, y1: 0, width: 10, height: 200 }, + { x1: 250, y1: 300, width: 10, height: 200 } + ], + 3: [ + { x1: 0, y1: 0, width: 10, height: 500 }, + { x1: 0, y1: 0, width: 500, height: 10 }, + { x1: 490, y1: 0, width: 10, height: 500 }, + { x1: 0, y1: 490, width: 500, height: 10 }, + { x1: 250, y1: 0, width: 10, height: 200 }, + { x1: 250, y1: 300, width: 10, height: 200 }, + { x1: 0, y1: 250, width: 200, height: 10 }, + { x1: 300, y1: 250, width: 200, height: 10 } + ], + 4: [ + { x1: 0, y1: 0, width: 10, height: 500 }, + { x1: 0, y1: 0, width: 500, height: 10 }, + { x1: 490, y1: 0, width: 10, height: 500 }, + { x1: 0, y1: 490, width: 500, height: 10 }, + { x1: 100, y1: 0, width: 10, height: 200 }, + { x1: 200, y1: 0, width: 10, height: 200 }, + { x1: 300, y1: 0, width: 10, height: 200 }, + { x1: 400, y1: 0, width: 10, height: 200 }, + { x1: 100, y1: 300, width: 10, height: 200 }, + { x1: 200, y1: 300, width: 10, height: 200 }, + { x1: 300, y1: 300, width: 10, height: 200 }, + { x1: 400, y1: 300, width: 10, height: 200 } + ], +}; + +const NumberOfMaps = Object.keys(LevelMaps).length; + + +type ControlButtonProps = { + className?: string; + children?: React.ReactNode; + onClick: () => void; +} + +function ControlButton({ children, onClick, className }:ControlButtonProps) { + const [isPressed, setIsPressed] = useState(false); + + return ( + + ) +} + + +function WalletControl() { + return ( + + + + + + +
+ + + + + + ) +} + +type ControlButtonsProps = { + gameState: number; + handleMobileGameState: () => void; +} + +function ControlButtons({ gameState, handleMobileGameState }: ControlButtonsProps) { + const { address } = useAccount(); + + return ( + <> +
+ +
+ {gameState === GameState.RUNNING ? 'PAUSE' : 'PLAY'} +
+
+
+ +
+ {address ? 'LOGOUT' : 'LOGIN'} +
+
+ + ) +} + +type DPadProps = { + onDirectionChange: (direction: number) => void; +}; + +function DPad({ onDirectionChange }: DPadProps) { + return ( +
+
+
+
+ ); +}; + +type AwaitingNextLevelProps = { + score: number; + level: number; +}; + +function AwaitingNextLevel({score, level}: AwaitingNextLevelProps) { + return ( +
+

Level Complete!

+

Score: {score}

+

Level: {level}

+

Press play or space to continue

+
+ ) +} + + +const SCHEMA_UID = "0xf58b8b212ef75ee8cd7e8d803c37c03e0519890502d5e99ee2412aae1456cafe"; +const EAS_CONTRACT = "0x4200000000000000000000000000000000000021"; +const easABI = [ + { + name: "attest", + type: "function" as const, + stateMutability: "payable" as const, + inputs: [ + { + name: "request", + type: "tuple", + components: [ + { name: "schema", type: "bytes32" }, + { name: "data", type: "tuple", + components: [ + { name: "recipient", type: "address" }, + { name: "expirationTime", type: "uint64" }, + { name: "revocable", type: "bool" }, + { name: "refUID", type: "bytes32" }, + { name: "data", type: "bytes" }, + { name: "value", type: "uint256" } + ] + } + ] + } + ], + outputs: [{ name: "", type: "bytes32" }] + } +]; + +const checkIsHighScore = async (currentScore: number) => { + const scores = await getTopScores(); + + // if less than MAX_SCORES scores or current score is higher than lowest score + if ((scores?.length ?? 0) < MAX_SCORES || currentScore > (scores?.[scores.length - 1]?.score ?? 0)) { + return true; + } + return false; +}; + +type DeadProps = { + score: number; + level: number; + onGoToIntro: () => void; + isWin: boolean; +}; + +export function Dead({score, level, onGoToIntro, isWin}: DeadProps) { + const sendNotification = useNotification(); + + const [isHighScore, setIsHighScore] = useState(false); + const { address } = useAccount(); + + useEffect(() => { + const checkHighScore = async () => { + const isHighScore = await checkIsHighScore(score); + setIsHighScore(isHighScore); + } + checkHighScore(); + }, [score]); + + const handleAttestationSuccess = async (response: TransactionResponse) => { + if (!address) { + return null; + } + + await addScore({ + address, + score, + attestationUid: response.transactionReceipts[0].logs[0].data, + transactionHash: response.transactionReceipts[0].transactionHash + }); + + await sendNotification({ + title: 'Congratulations!', + body: `You scored a new high score of ${score} on minikit!`, + }) + } + + const transactionButton = useMemo(() => { + if (!address) { + return ( + + Login to save your high score + + ) + } + + return ( + console.error("Attestation failed:", error)} + > + + + + + + + + ) + }, [onGoToIntro, address, score]); + + return ( +
+ {isWin ? ( +

You Won!

+ ) : ( +

Game Over

+ )} +

Level: {level}

+

Score: {score}

+

Play again?

+ {isHighScore && ( + <> +

High Score!

+ {transactionButton} + + )} +
+ ) +} + +function HighScores() { + const [highScores, setHighScores] = useState([]); + const openUrl = useOpenUrl(); + + useEffect(() => { + const fetchScores = async () => { + const scores = await getTopScores(); + setHighScores(scores ?? []); + } + fetchScores(); + }, []); + + const handleHighScoreClick = (score: Score) => { + openUrl(`https://basescan.org/tx/${score.transactionHash}`); + } + + return ( +
+

High Scores

+ {highScores.sort((a, b) => b.score - a.score).map((score, index) => ( + + ))} +
+ ) +} + +function Intro() { + return ( +
+
+ +
+ +
+ Press play or space to start +
+
+ ) +} + +let msPrev = performance.now(); +const useGameLoop = (callback: () => void, dependencies: DependencyList) => { + useEffect(() => { + let frameId: number; + const loop = () => { + const msNow = performance.now(); + const delta = msNow - msPrev; + if (delta > MS_PER_FRAME) { + callback(); + msPrev = msNow - (delta % MS_PER_FRAME); + } + frameId = requestAnimationFrame(loop); + }; + frameId = requestAnimationFrame(loop); + return () => cancelAnimationFrame(frameId); + }, dependencies); +}; + +type Segment = { x: number; y: number }; + +const Sammy = () => { + const gameCanvasRef = useRef(null); + const mapCanvasRef = useRef(null); + const sammyCanvasRef = useRef(null); + const scoreCanvasRef = useRef(null); + const containerRef = useRef(null); + const levelRef = useRef(1); // track level with a ref to ensure it is updated correctly in dev mode + + const [gameState, setGameState] = useState(GameState.INTRO); + const [level, setLevel] = useState(1); + const [score, setScore] = useState({ points: 2000, total: 0 }); + const [sammy, setSammy] = useState<{ + x: number; + y: number; + length: number; + direction: number; + newDirection: number; + segments: Segment[]; + }>({ + x: 50, + y: 100, + length: 10, + direction: MoveState.DOWN, + newDirection: MoveState.NONE, + segments: [] + }); + const [target, setTarget] = useState({ + exists: false, + num: 0, + x: 0, + y: 0, + color: '#000000' + }); + const [scale, setScale] = useState(null); + + useEffect(() => { + const handleResize = () => { + setScale(Math.min(window.document.body.clientWidth / 520, window.document.body.clientHeight / 520, 1)); + }; + handleResize(); + window.addEventListener('resize', handleResize); + return () => window.removeEventListener('resize', handleResize); + }, []); + + useEffect(() => { + levelRef.current = level; + }, [level]); + + const drawMap = useCallback(() => { + const ctx = mapCanvasRef.current?.getContext('2d'); + if (ctx) { + ctx.clearRect(0, 0, 500, 520); + ctx.fillStyle = '#FFFFFF'; + ctx.fillRect(0, 0, 500, 520); + LevelMaps[level].forEach(wall => { + ctx.fillStyle = '#000000'; + ctx.fillRect(wall.x1, wall.y1, wall.width, wall.height); + }); + } + }, [level]); + + useEffect(() => { + if (mapCanvasRef.current) { + drawMap(); + } + }, [drawMap, level, scale]) + + const createTarget = () => { + if (!target.exists) { + let isValidPosition = false; + const newTarget = { + x: 0, + y: 0, + exists: true, + num: target.num + 1, + color: '#000000' + }; + + while (!isValidPosition) { + newTarget.x = Math.floor(Math.random() * 48) * 10 + 10; + newTarget.y = Math.floor(Math.random() * 48) * 10 + 10; + newTarget.color = `#${Math.floor(Math.random() * 12582912).toString(16).padStart(6, '0')}`; + + // check if target overlaps with any wall + isValidPosition = !LevelMaps[level].some(wall => { + const targetLeft = newTarget.x; + const targetRight = newTarget.x + 10; + const targetTop = newTarget.y; + const targetBottom = newTarget.y + 10; + + const wallLeft = wall.x1; + const wallRight = wall.x1 + wall.width; + const wallTop = wall.y1; + const wallBottom = wall.y1 + wall.height; + + return !(targetLeft > wallRight || + targetRight < wallLeft || + targetTop > wallBottom || + targetBottom < wallTop); + }); + } + + const ctx = sammyCanvasRef.current?.getContext('2d'); + if (ctx) { + ctx.fillStyle = newTarget.color; + ctx.fillRect(newTarget.x, newTarget.y, 10, 10); + } + + setTarget(newTarget); + } + }; + + const moveSammy = () => { + const newSammy = { ...sammy }; + + if (newSammy.newDirection !== MoveState.NONE) { + const isHorizontal = newSammy.newDirection === MoveState.LEFT || newSammy.newDirection === MoveState.RIGHT; + const isVertical = newSammy.newDirection === MoveState.UP || newSammy.newDirection === MoveState.DOWN; + + // only change direction on a grid + if ((isHorizontal && newSammy.y % 10 === 0) || (isVertical && newSammy.x % 10 === 0)) { + newSammy.direction = newSammy.newDirection; + newSammy.newDirection = MoveState.NONE; + } + } + + switch (newSammy.direction) { + case MoveState.UP: + newSammy.y--; + break; + case MoveState.RIGHT: + newSammy.x++; + break; + case MoveState.DOWN: + newSammy.y++; + break; + case MoveState.LEFT: + newSammy.x--; + break; + } + + const newSegment = { x: newSammy.x, y: newSammy.y }; + newSammy.segments = [newSegment].concat(newSammy.segments); + + if (newSammy.segments.length > newSammy.length) { + newSammy.segments.pop(); + } + + setSammy(newSammy); + }; + + const checkCollisions = () => { + // wall collisions + const hitWall = LevelMaps[level].some(wall => { + const sammyLeft = sammy.x; + const sammyRight = sammy.x + 10; + const sammyTop = sammy.y; + const sammyBottom = sammy.y + 10; + + // adjust padding to allow wall sliding + const wallLeft = wall.x1 + 1; + const wallRight = wall.x1 + wall.width - 2; + const wallTop = wall.y1 + 1; + const wallBottom = wall.y1 + wall.height - 2; + + return !(sammyLeft > wallRight || + sammyRight < wallLeft || + sammyTop > wallBottom || + sammyBottom < wallTop); + }); + + // self collision + const hitSelf = sammy.segments.slice(1).some(segment => + segment.x === sammy.x && segment.y === sammy.y + ); + + if (hitWall || hitSelf) { + setGameState(GameState.DEAD); + } + + // target collision + if (target.exists && sammy.x === target.x && sammy.y === target.y) { + if (target.num < NUM_TARGETS_PER_LEVEL) { + setSammy(prev => ({ + ...prev, + length: prev.length + 10 * target.num * target.num / 2 + })); + setScore(prev => ({ + points: 2000, + total: prev.total + prev.points + })); + setTarget(prev => ({ ...prev, exists: false })); + } else { + if (level === NumberOfMaps) { + setGameState(GameState.WON); + } else { + setGameState(GameState.AWAITINGNEXTLEVEL); + } + } + } + }; + + const drawGame = () => { + if (gameState !== GameState.RUNNING) { + return; + } + + const ctx = sammyCanvasRef.current?.getContext('2d'); + if (ctx) { + ctx.clearRect(0, 0, 500, 520); + + // draw sammy + ctx.fillStyle = '#000000'; + sammy.segments.forEach(segment => { + ctx.fillRect(segment.x, segment.y, 10, 10); + }); + + // draw target if exists + if (target.exists) { + ctx.fillStyle = target.color; + ctx.fillRect(target.x, target.y, 10, 10); + } + } + + // update score + const scoreCtx = scoreCanvasRef.current?.getContext('2d'); + if (scoreCtx) { + scoreCtx.clearRect(0, 0, 500, 530); + scoreCtx.font = '20px Pixelify Sans'; + scoreCtx.fillStyle = '#000000'; + scoreCtx.fillText(`Score: ${score.total}`, 10, 520); + scoreCtx.fillText(`Points: ${score.points}`, 200, 520); + scoreCtx.fillText(`Level: ${level}`, 400, 520); + } + }; + + useGameLoop(() => { + if (gameState === GameState.RUNNING) { + moveSammy(); + checkCollisions(); + createTarget(); + drawGame(); + setScore(prev => ({ + ...prev, + points: Math.max(0, prev.points - 2) + })); + } + }, [gameState, sammy, target, score]); + + const updateGameState = () => { + setGameState(prev => { + switch (prev) { + case GameState.RUNNING: + return GameState.PAUSED; + case GameState.PAUSED: + case GameState.INTRO: + return GameState.RUNNING; + case GameState.WON: + case GameState.DEAD: + setSammy({ + x: 50, + y: 100, + length: 10, + direction: MoveState.DOWN, + newDirection: MoveState.NONE, + segments: [] + }); + setScore({ points: 2000, total: 0 }); + setTarget({ exists: false, num: 0, x: 0, y: 0, color: '' }); + setLevel(1); + return GameState.RUNNING; + case GameState.AWAITINGNEXTLEVEL: + setSammy({ + x: 50, + y: 100, + length: 10, + direction: MoveState.DOWN, + newDirection: MoveState.NONE, + segments: [] + }); + setScore(prevScore => ({ ...prevScore, points: 2000 })); + setTarget({ exists: false, num: 0, x: 0, y: 0, color: '' }); + setLevel(levelRef.current + 1); + return GameState.RUNNING; + default: + return prev; + } + }); + }; + + useEffect(() => { + const handleKeyPress = (e: KeyboardEvent) => { + e.preventDefault(); + if (e.code === 'Space') { + updateGameState(); + } else { + setSammy(prev => ({ + ...prev, + newDirection: { + 'ArrowUp': MoveState.UP, + 'ArrowRight': MoveState.RIGHT, + 'ArrowDown': MoveState.DOWN, + 'ArrowLeft': MoveState.LEFT, + 'KeyW': MoveState.UP, + 'KeyD': MoveState.RIGHT, + 'KeyS': MoveState.DOWN, + 'KeyA': MoveState.LEFT + }[e.code] || prev.newDirection + })); + } + }; + + window.addEventListener('keydown', handleKeyPress); + return () => window.removeEventListener('keydown', handleKeyPress); + }, []); + + const overlays = useMemo(() => { + switch (gameState) { + case GameState.INTRO: + case GameState.PAUSED: + return + case GameState.WON: + case GameState.DEAD: + return setGameState(GameState.INTRO)} + isWin={gameState === GameState.WON} + /> + case GameState.AWAITINGNEXTLEVEL: + return + default: + return null; + } + }, [gameState, score.total, level, setGameState, Intro, Dead, AwaitingNextLevel]); + + if (!scale) { + return null; + } + + return ( +
+
+ + + + + {overlays} +
+ +
+
+ { + setSammy(prev => ({ ...prev, newDirection: direction })); + }} + /> +
+
+ +
+
+
+ ); +}; + + +export default Sammy; \ No newline at end of file diff --git a/create-onchain/templates/minikit/app/globals.css b/create-onchain/templates/minikit/app/globals.css new file mode 100644 index 0000000000..89fa7c70ad --- /dev/null +++ b/create-onchain/templates/minikit/app/globals.css @@ -0,0 +1,35 @@ +@tailwind base; +@tailwind components; +@tailwind utilities; + +:root { + --background: #ffffff; + --foreground: #171717; +} + +@media (prefers-color-scheme: dark) { + :root { + --background: #0a0a0a; + --foreground: #ededed; + } +} + +* { + touch-action: manipulation; +} + +body { + color: var(--foreground); + background: var(--background); + + font-family: "Pixelify Sans", sans-serif; + font-optical-sizing: auto; + font-weight: 400; + font-style: normal; +} + +@layer utilities { + .text-balance { + text-wrap: balance; + } +} \ No newline at end of file diff --git a/create-onchain/templates/minikit/app/layout.tsx b/create-onchain/templates/minikit/app/layout.tsx new file mode 100644 index 0000000000..44ae00216f --- /dev/null +++ b/create-onchain/templates/minikit/app/layout.tsx @@ -0,0 +1,48 @@ +import './theme.css'; +import '@coinbase/onchainkit/styles.css'; +import type { Metadata, Viewport } from 'next'; +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`, a Next.js template for OnchainKit', + other: { + 'fc:frame': JSON.stringify({ + version: process.env.NEXT_PUBLIC_VERSION, + imageUrl: process.env.NEXT_PUBLIC_IMAGE_URL, + button: { + title: `Launch ${process.env.NEXT_PUBLIC_ONCHAINKIT_PROJECT_NAME}`, + action: { + type: 'launch_frame', + name: process.env.NEXT_PUBLIC_ONCHAINKIT_PROJECT_NAME, + url: URL, + splashImageUrl: process.env.NEXT_PUBLIC_SPLASH_IMAGE_URL, + splashBackgroundColor: `#${process.env.NEXT_PUBLIC_SPLASH_BACKGROUND_COLOR}`, + } + } + }), + }, + } +} + +export default function RootLayout({ + children, +}: Readonly<{ + children: React.ReactNode; +}>) { + return ( + + + {children} + + + ); +} diff --git a/create-onchain/templates/minikit/app/page.tsx b/create-onchain/templates/minikit/app/page.tsx new file mode 100644 index 0000000000..49cefc07f7 --- /dev/null +++ b/create-onchain/templates/minikit/app/page.tsx @@ -0,0 +1,93 @@ +'use client'; + +import { + useMiniKit, + useAddFrame, + useOpenUrl, +} from '@coinbase/onchainkit/minikit'; +import { + Name, + Identity, +} from '@coinbase/onchainkit/identity'; +import { useEffect, useMemo, useState } from 'react'; +import Sammy from './components/Sammy'; +import { useAccount } from 'wagmi'; +import Check from './svg/Check'; + +export default function App() { + const { setFrameReady, isFrameReady, context } = useMiniKit(); + const [frameAdded, setFrameAdded] = useState(false); + + const addFrame = useAddFrame(); + const openUrl = useOpenUrl(); + const { address } = useAccount(); + + useEffect(() => { + if (!isFrameReady) { + setFrameReady(); + } + }, [setFrameReady, isFrameReady]); + + + const handleAddFrame = async () => { + const frameAdded = await addFrame(); + console.log('frameAdded', frameAdded); + setFrameAdded(Boolean(frameAdded)); + } + + const saveFrameButton = useMemo(() => { + if (context && !context.client.added) { + return ( + + ) + } + + if (frameAdded) { + return ( +
+ + SAVED +
+ ) + } + + return null; + }, [context, handleAddFrame]); + + return ( +
+
+
+ +
+ {saveFrameButton} +
+
+ +
+ +
+ +
+
Connected:
+ {address ? ( + + + + ) :
NOT CONNECTED
} +
+
+
+ ); +} diff --git a/create-onchain/templates/minikit/app/providers.tsx b/create-onchain/templates/minikit/app/providers.tsx new file mode 100644 index 0000000000..218232312d --- /dev/null +++ b/create-onchain/templates/minikit/app/providers.tsx @@ -0,0 +1,24 @@ +'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/create-onchain/templates/minikit/app/svg/ArrowSvg.tsx b/create-onchain/templates/minikit/app/svg/ArrowSvg.tsx new file mode 100644 index 0000000000..d278242d90 --- /dev/null +++ b/create-onchain/templates/minikit/app/svg/ArrowSvg.tsx @@ -0,0 +1,18 @@ +export default function ArrowSvg() { + return ( + + Arrow SVG + + + ); +} diff --git a/create-onchain/templates/minikit/app/svg/Check.tsx b/create-onchain/templates/minikit/app/svg/Check.tsx new file mode 100644 index 0000000000..940c14839c --- /dev/null +++ b/create-onchain/templates/minikit/app/svg/Check.tsx @@ -0,0 +1,7 @@ +export default function Check() { + return ( + + + + ) +} \ No newline at end of file diff --git a/create-onchain/templates/minikit/app/svg/SnakeLogo.tsx b/create-onchain/templates/minikit/app/svg/SnakeLogo.tsx new file mode 100644 index 0000000000..7b69172a02 --- /dev/null +++ b/create-onchain/templates/minikit/app/svg/SnakeLogo.tsx @@ -0,0 +1,119 @@ +import React from 'react'; + +const SnakeLogo = ({ width = 164, height = 34 }: { width?: number | string, height?: number | string }) => { + // [x, y, width, height] + // each letter is 28px wide and 34px high with 6px padding between letters + const segments = [ + [ + // Top Row + [4, 0, 18, 4], // S + [34, 0, 10, 4], // N + [52, 0, 10, 4], // N + [77, 0, 10, 4], // A + [102, 0, 10, 4], // K + [120, 0, 10, 4], // K + [136, 0, 28, 4], // E + ], + + [ + // Second Row + [0, 5, 10, 4], // S + [16, 5, 10, 4], // S + [34, 5, 14, 4], // N + [52, 5, 10, 4], // N + [72, 5, 20, 4], // A + [102, 5, 10, 4], // K + [116, 5, 10, 4], // K + [136, 5, 10, 4], // E + ], + + [ + // Third Row + [0, 10, 8, 4], // S + [34, 10, 28, 4], // N + [68, 10, 10, 4], // A + [86, 10, 10, 4], // A + [102, 10, 20, 4], // N + [136, 10, 10, 4], // E + ], + + [ + // Fourth Row + [4, 15, 20, 4], // S + [34, 15, 10, 4], // N + [48, 15, 14, 4], // N + [68, 15, 28, 4], // A + [102, 15, 16, 4], // K + [136, 15, 20, 4], // E + ], + + [ + // Fifth Row + [20, 20, 8, 4], // S + [34, 20, 10, 4], // N + [52, 20, 10, 4], // N + [68, 20, 10, 4], // A + [86, 20, 10, 4], // A + [102, 20, 20, 4], // K + [136, 20, 10, 4], // E + ], + + [ + // Sixth Row + [0, 25, 8, 4], // S + [20, 25, 8, 4], // S + [34, 25, 10, 4], // N + [52, 25, 10, 4], // N + [68, 25, 10, 4], // A + [86, 25, 10, 4], // A + [102, 25, 10, 4], // K + [116, 25, 10, 4], // K + [136, 25, 10, 4], // E + ], + + [ + // Seventh Row + [4, 30, 20, 4], // S + [34, 30, 10, 4], // N + [52, 30, 10, 4], // N + [68, 30, 10, 4], // A + [86, 30, 10, 4], // A + [102, 30, 10, 4], // K + [120, 30, 10, 4], // K + [136, 30, 28, 4], // E + ], + ]; + + return ( + + {segments.map((row, i) => { + return ( + + {row.map(([ x, y, width, height ], j) => ( + + ))} + + + ); + })} + + ); +}; + +export default SnakeLogo; diff --git a/create-onchain/templates/minikit/app/theme.css b/create-onchain/templates/minikit/app/theme.css new file mode 100644 index 0000000000..f31b7b8517 --- /dev/null +++ b/create-onchain/templates/minikit/app/theme.css @@ -0,0 +1,55 @@ +@import url('https://fonts.googleapis.com/css2?family=Pixelify+Sans:wght@400..700&display=swap'); + +:root { + --snake-background: #E5E5E5; +} + +.snake-light, +.snake-dark { + --ock-font-family: 'Pixelify Sans', sans-serif; + --ock-border-radius: 0.375rem; + --ock-border-radius-inner: 0.125rem; + --ock-text-inverse: theme(colors.zinc.50); + --ock-text-foreground: theme(colors.zinc.950); + --ock-text-foreground-muted: theme(colors.zinc.600); + --ock-text-error: theme(colors.rose.600); + --ock-text-primary: theme(colors.zinc.950); + --ock-text-success: theme(colors.lime.600); + --ock-text-warning: theme(colors.orange.600); + --ock-text-disabled: theme(colors.zinc.400); + + --ock-bg-default: theme(colors.zinc.50); + --ock-bg-default-hover: theme(colors.zinc.200); + --ock-bg-default-active: theme(colors.zinc.300); + --ock-bg-alternate: theme(colors.zinc.200); + --ock-bg-alternate-hover: theme(colors.zinc.300); + --ock-bg-alternate-active: theme(colors.zinc.400); + --ock-bg-inverse: theme(colors.zinc.100); + --ock-bg-inverse-hover: theme(colors.zinc.200); + --ock-bg-inverse-active: theme(colors.zinc.300); + --ock-bg-primary: theme(colors.zinc.950); + --ock-bg-primary-hover: theme(colors.zinc.800); + --ock-bg-primary-active: theme(colors.zinc.700); + --ock-bg-primary-washed: theme(colors.zinc.100); + --ock-bg-primary-disabled: theme(colors.zinc.300); + --ock-bg-secondary: theme(colors.neutral.200); + --ock-bg-secondary-hover: theme(colors.neutral.300); + --ock-bg-secondary-active: theme(colors.neutral.400); + --ock-bg-error: theme(colors.rose.600); + --ock-bg-warning: theme(colors.orange.600); + --ock-bg-success: theme(colors.lime.600); + --ock-bg-default-reverse: theme(colors.neutral.50); + + --ock-icon-color-primary: theme(colors.zinc.950); + --ock-icon-color-foreground: theme(colors.zinc.950); + --ock-icon-color-foreground-muted: theme(colors.zinc.600); + --ock-icon-color-inverse: theme(colors.zinc.50); + --ock-icon-color-error: theme(colors.rose.600); + --ock-icon-color-success: theme(colors.lime.600); + --ock-icon-color-warning: theme(colors.orange.600); + + --ock-line-primary: theme(colors.zinc.950); + --ock-line-default: theme(colors.zinc.300); + --ock-line-heavy: theme(colors.zinc.500); + --ock-line-inverse: theme(colors.zinc.700); +} diff --git a/create-onchain/templates/minikit/lib/notification-client.ts b/create-onchain/templates/minikit/lib/notification-client.ts new file mode 100644 index 0000000000..7f759fbfb8 --- /dev/null +++ b/create-onchain/templates/minikit/lib/notification-client.ts @@ -0,0 +1,62 @@ +import { + 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, +}: { + fid: number; + title: string; + body: string; +}): Promise { + const notificationDetails = await getUserNotificationDetails(fid); + if (!notificationDetails) { + return { state: "no_token" }; + } + + 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 }; +} \ No newline at end of file diff --git a/create-onchain/templates/minikit/lib/notification.ts b/create-onchain/templates/minikit/lib/notification.ts new file mode 100644 index 0000000000..4afa442f94 --- /dev/null +++ b/create-onchain/templates/minikit/lib/notification.ts @@ -0,0 +1,29 @@ +import type { FrameNotificationDetails } 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 { + return await redis.get( + getUserNotificationDetailsKey(fid) + ); +} + +export async function setUserNotificationDetails( + fid: number, + notificationDetails: FrameNotificationDetails +): Promise { + await redis.set(getUserNotificationDetailsKey(fid), notificationDetails); +} + +export async function deleteUserNotificationDetails( + fid: number +): Promise { + await redis.del(getUserNotificationDetailsKey(fid)); +} \ No newline at end of file diff --git a/create-onchain/templates/minikit/lib/redis.ts b/create-onchain/templates/minikit/lib/redis.ts new file mode 100644 index 0000000000..339ba025a0 --- /dev/null +++ b/create-onchain/templates/minikit/lib/redis.ts @@ -0,0 +1,14 @@ +import { Redis } from "@upstash/redis"; + +if (!process.env.REDIS_URL) { + console.warn('REDIS_URL environment variable is not defined') +} + +if (!process.env.REDIS_TOKEN) { + console.warn('REDIS_TOKEN environment variable is not defined') +} + +export const redis = new Redis({ + url: process.env.REDIS_URL, + token: process.env.REDIS_TOKEN +}) diff --git a/create-onchain/templates/minikit/lib/scores-client.ts b/create-onchain/templates/minikit/lib/scores-client.ts new file mode 100644 index 0000000000..5d57e37c5a --- /dev/null +++ b/create-onchain/templates/minikit/lib/scores-client.ts @@ -0,0 +1,22 @@ +import { Score } from './scores'; + +export const MAX_SCORES = 8; + +export async function getTopScores(): Promise { + const res = await fetch('/api/scores'); + return res.json(); +} + +export async function addScore(score: Score): Promise { + await fetch('/api/scores', { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify(score) + }); +} + +export async function resetScores(): Promise { + await fetch('/api/scores', { + method: 'DELETE' + }); +} \ No newline at end of file diff --git a/create-onchain/templates/minikit/lib/scores.ts b/create-onchain/templates/minikit/lib/scores.ts new file mode 100644 index 0000000000..7e0efcffd2 --- /dev/null +++ b/create-onchain/templates/minikit/lib/scores.ts @@ -0,0 +1,64 @@ +import { Address } from "viem"; +import { redis } from "./redis"; +import { MAX_SCORES } from "./scores-client"; + +export type Score = { + attestationUid: string; + transactionHash: string; + address: Address; + score: number; +} + +const notificationServiceKey = process.env.NEXT_PUBLIC_ONCHAINKIT_PROJECT_NAME ?? 'minikit'; + +const scoresKey = `${notificationServiceKey}:scores`; + +export async function getScores(): Promise { + const exists = await redis.exists(scoresKey); + if (!exists) { + return []; + } + + const scores = await redis.zrange(scoresKey, 0, -1, { + withScores: true, + rev: true + }) as Array<{ address: Address, attestationUid: string, transactionHash: string } | number>; + + if (!Array.isArray(scores) || scores.length < 2) return []; + + const result: Score[] = []; + + // Process pairs of entries (member and score) + for (let i = 0; i < scores.length; i += 2) { + const memberData = scores[i] as { address: Address, attestationUid: string, transactionHash: string }; + const score = scores[i + 1] as number; + + result.push({ + ...memberData, + score + }); + } + + return result; +} + +export async function setScore(score: Score): Promise { + await redis.zadd(scoresKey, { + score: score.score, + member: { + attestationUid: score.attestationUid, + transactionHash: score.transactionHash, + address: score.address + } + }); + + // only save the top MAX_SCORES scores + const count = await redis.zcard(scoresKey); + if (count > MAX_SCORES) { + await redis.zremrangebyrank(scoresKey, 0, count - MAX_SCORES - 1); + } +} + +export async function resetScores(): Promise { + await redis.del(scoresKey); +} \ No newline at end of file diff --git a/create-onchain/templates/minikit/next.config.mjs b/create-onchain/templates/minikit/next.config.mjs new file mode 100644 index 0000000000..1c01431ff2 --- /dev/null +++ b/create-onchain/templates/minikit/next.config.mjs @@ -0,0 +1,12 @@ +/** @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; + \ No newline at end of file diff --git a/create-onchain/templates/minikit/package.json b/create-onchain/templates/minikit/package.json new file mode 100644 index 0000000000..942150fdec --- /dev/null +++ b/create-onchain/templates/minikit/package.json @@ -0,0 +1,37 @@ +{ + "name": "next", + "version": "0.1.0", + "private": true, + "scripts": { + "dev": "next dev", + "build": "next build", + "start": "next start", + "lint": "next lint", + "generate-account-association": "node scripts/generateAccountAssociation.mjs", + "validate-account-association": "node scripts/validateAccountAssociation.mjs" + }, + "dependencies": { + "@coinbase/onchainkit": "file:package.tgz", + "@upstash/redis": "^1.34.4", + "next": "14.2.15", + "react": "^18", + "react-dom": "^18", + "@tanstack/react-query": "^5", + "viem": "^2.17.4", + "wagmi": "^2.11.0" + }, + "devDependencies": { + "@types/node": "^20", + "@types/react": "^18", + "@types/react-dom": "^18", + "eslint": "^8", + "eslint-config-next": "14.2.15", + "postcss": "^8", + "tailwindcss": "^3.4.1", + "typescript": "^5", + "viem": "^2.21.33", + "ora": "^8.1.0", + "picocolors": "^1.1.0", + "prompts": "^2.4.2" + } +} diff --git a/create-onchain/templates/minikit/postcss.config.mjs b/create-onchain/templates/minikit/postcss.config.mjs new file mode 100644 index 0000000000..1a69fd2a45 --- /dev/null +++ b/create-onchain/templates/minikit/postcss.config.mjs @@ -0,0 +1,8 @@ +/** @type {import('postcss-load-config').Config} */ +const config = { + plugins: { + tailwindcss: {}, + }, +}; + +export default config; diff --git a/create-onchain/templates/minikit/public/snake.png b/create-onchain/templates/minikit/public/snake.png new file mode 100644 index 0000000000..223ff1631b Binary files /dev/null and b/create-onchain/templates/minikit/public/snake.png differ diff --git a/create-onchain/templates/minikit/scripts/generateAccountAssociation.mjs b/create-onchain/templates/minikit/scripts/generateAccountAssociation.mjs new file mode 100644 index 0000000000..7b87861423 --- /dev/null +++ b/create-onchain/templates/minikit/scripts/generateAccountAssociation.mjs @@ -0,0 +1,156 @@ +#!/usr/bin/env node +import { createWalletClient, http, createPublicClient } from 'viem'; +import { optimism } from 'viem/chains'; +import { mnemonicToAccount } from 'viem/accounts'; +import prompts from 'prompts'; +import pc from 'picocolors'; +import ora from 'ora'; +import fs from 'node:fs'; +import path from 'node:path'; + +const ID_REGISTRY_ADDRESS = '0x00000000Fc6c5F01Fc30151999387Bb99A9f489b'; +const ID_REGISTRY_ABI = [ + { + "inputs": [{"internalType": "uint256", "name": "fid", "type": "uint256"}], + "name": "custodyOf", + "outputs": [{"internalType": "address", "name": "", "type": "address"}], + "stateMutability": "view", + "type": "function" + } +]; + +async function getCustodyAddress(fid) { + const client = createPublicClient({ + chain: optimism, + transport: http(), + }); + + // query the ID Registry contract for the fids custody address + const resolvedCustodyAddress = await client.readContract({ + address: ID_REGISTRY_ADDRESS, + abi: ID_REGISTRY_ABI, + functionName: 'custodyOf', + args: [BigInt(fid)], + }); + + return resolvedCustodyAddress; +} + +async function generateAccountAssociation(params) { + const { mnemonic, domain, fid } = params; + + const account = mnemonicToAccount(mnemonic); + + const custodyAddress = await getCustodyAddress(fid); + + if (account.address.toLowerCase() !== custodyAddress.toLowerCase()) { + console.error('Derived address does not match custody address! Please check your mnemonic and fid and try again.'); + console.error('Derived address:', account.address); + console.error('Registered Custody address:', custodyAddress); + process.exit(1); + } + + const header = { + fid, + type: "custody", + key: account.address, + }; + + const payload = { + domain: domain.replace(/^(http|https):\/\//, ''), + }; + + const encodedHeader = Buffer.from(JSON.stringify(header)).toString('base64url'); + const encodedPayload = Buffer.from(JSON.stringify(payload)).toString('base64url'); + + const messageToSign = `${encodedHeader}.${encodedPayload}`; + + const client = createWalletClient({ + account, + chain: optimism, + transport: http() + }); + + const signature = await client.signMessage({ + message: messageToSign, + }); + + const encodedSignature = Buffer.from(signature).toString('base64url'); + + const jsonJfs = { + header: encodedHeader, + payload: encodedPayload, + signature: encodedSignature + }; + + const envContent = `FARCASTER_HEADER=${encodedHeader} +FARCASTER_PAYLOAD=${encodedPayload} +FARCASTER_SIGNATURE=${encodedSignature} +NEXT_PUBLIC_URL=${domain}`; + + const targetDir = process.argv[2] || process.cwd(); + const envPath = path.join(targetDir, '.env'); + const existingEnv = await fs.promises.readFile(envPath, 'utf-8').catch(() => ''); + const updatedEnv = existingEnv + .split('\n') + .filter(line => !line.startsWith('FARCASTER_') && !line.startsWith('NEXT_PUBLIC_URL')) + .concat(envContent) + .join('\n'); + await fs.promises.writeFile(envPath, updatedEnv); + + console.log('\nFarcaster account association generated successfully and added to your .env file!\n'); + console.log(JSON.stringify(jsonJfs, null, 2)); +} + +async function init() { + let result; + + try { + result = await prompts( + [ + { + type: 'password', + name: 'mnemonic', + message: pc.reset('Enter your seed phrase (12 or 24 words separated by spaces):'), + validate: value => + value.split(' ').length === 12 || value.split(' ').length === 24 || + 'Mnemonic must be 12 or 24 words' + }, + { + type: 'text', + name: 'domain', + message: pc.reset('Enter your domain with protocol (e.g. https://example.com):'), + validate: value => value.length > 0 || 'Domain is required' + }, + { + type: 'number', + name: 'fid', + message: pc.reset('Enter your FID:'), + validate: value => !isNaN(value) || 'FID must be a number' + } + ], + { + onCancel: () => { + console.log('\nScript cancelled.'); + process.exit(0); + }, + } + ); + } catch (cancelled) { + console.log(cancelled.message); + process.exit(1); + } + + const spinner = ora('Generating account association...').start(); + try { + await generateAccountAssociation(result); + spinner.succeed('Farcaster account association generated successfully!'); + } catch (error) { + spinner.fail('Failed to generate Farcaster account association'); + console.error(error); + } +} + +init().catch((e) => { + console.error(e); +}); \ No newline at end of file diff --git a/create-onchain/templates/minikit/scripts/validateAccountAssociation.mjs b/create-onchain/templates/minikit/scripts/validateAccountAssociation.mjs new file mode 100644 index 0000000000..b869fe50c0 --- /dev/null +++ b/create-onchain/templates/minikit/scripts/validateAccountAssociation.mjs @@ -0,0 +1,113 @@ +#!/usr/bin/env node +import { createPublicClient, http, verifyMessage } from 'viem'; +import { optimism } from 'viem/chains'; +import fs from 'node:fs'; + +// parse .env file +function parseEnv(content) { + return content + .split('\n') + .reduce((acc, line) => { + const [key, ...valueParts] = line.split('='); + if (key && valueParts.length > 0) { + acc[key.trim()] = valueParts.join('=').trim(); + } + return acc; + }, {}); +} + +// ID Registry Contract +const ID_REGISTRY_ADDRESS = '0x00000000Fc6c5F01Fc30151999387Bb99A9f489b'; +const ID_REGISTRY_ABI = [ + { + "inputs": [{"internalType": "uint256", "name": "fid", "type": "uint256"}], + "name": "custodyOf", + "outputs": [{"internalType": "address", "name": "", "type": "address"}], + "stateMutability": "view", + "type": "function" + } +]; + +async function main() { + // read and parse .env file + const envContent = await fs.promises.readFile('.env', 'utf-8'); + const env = parseEnv(envContent); + + const { + NEXT_PUBLIC_FARCASTER_HEADER: encodedHeader, + NEXT_PUBLIC_FARCASTER_PAYLOAD: encodedPayload, + NEXT_PUBLIC_FARCASTER_SIGNATURE: encodedSignature, + } = env; + + if (!encodedHeader || !encodedPayload || !encodedSignature) { + throw new Error('Missing required environment variables. Run generate-app-association first.'); + } + + console.log('Step 1: Decoding and validating header...'); + + const headerData = JSON.parse(Buffer.from(encodedHeader, 'base64url').toString('utf-8')); + console.log('Header data:', headerData); + + const { fid, type, key } = headerData; + + if (type !== 'custody') { + throw new Error("Invalid type - must be 'custody'"); + } + + console.log('Step 2: Decoding and validating payload...'); + + const payloadData = JSON.parse(Buffer.from(encodedPayload, 'base64url').toString('utf-8')); + console.log('Payload data:', payloadData); + + console.log('\nStep 3: Verifying signature...'); + console.log({ + type, + key, + message: `${encodedHeader}.${encodedPayload}`, + signature: encodedSignature, + }) + // decode signature from base64url + const signature = Buffer.from(encodedSignature, 'base64url').toString(); + + // verify the signature + const valid = await verifyMessage({ + address: key, + message: `${encodedHeader}.${encodedPayload}`, + signature: signature, + }); + + if (!valid) { + throw new Error("Invalid signature"); + } + console.log('✅ Signature is valid'); + + console.log('\nStep 4: Verifying custody address from ID Registry...'); + + const client = createPublicClient({ + chain: optimism, + transport: http(), + }); + + // query the ID Registry contract for the fids custody address + const resolvedCustodyAddress = await client.readContract({ + address: ID_REGISTRY_ADDRESS, + abi: ID_REGISTRY_ABI, + functionName: 'custodyOf', + args: [BigInt(fid)], + }); + + console.log('Resolved custody address:', resolvedCustodyAddress); + console.log('Signer from header:', key); + + if (resolvedCustodyAddress.toLowerCase() !== key.toLowerCase()) { + throw new Error("Wrong custody address"); + } + console.log('✅ Custody address matches'); + + console.log('\n✅ All validations passed successfully!'); +} + +main().catch((error) => { + console.error('\n❌ Validation failed:', error.message); + process.exit(1); +}); \ No newline at end of file diff --git a/create-onchain/templates/minikit/tailwind.config.ts b/create-onchain/templates/minikit/tailwind.config.ts new file mode 100644 index 0000000000..cdc7001b96 --- /dev/null +++ b/create-onchain/templates/minikit/tailwind.config.ts @@ -0,0 +1,53 @@ +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: { + fontFamily: { + sans: ['Pixelify Sans', 'sans-serif'], + }, + colors: { + background: "var(--background)", + foreground: "var(--foreground)", + }, + backgroundImage: { + 'dpad-gradient': 'linear-gradient(180deg, #1D1B1C 0%, #191718 81.19%, #252120 96.35%)', + }, + boxShadow: { + 'dpad': ` + inset 0px 0px 0.25px 1.25px #262524, + inset 3px 5px 2px -4.75px #FFFFFF, + inset 1.25px 1.5px 0px rgba(0, 0, 0, 0.75), + inset 0px 4.75px 0.25px -2.5px #FBFBFB, + inset 1px 1px 3px 3px #1A1818, + inset 0px -3px 1px rgba(0, 0, 0, 0.5), + inset 2.5px -2px 3px rgba(124, 108, 94, 0.75), + inset 0px -3px 3px 1px rgba(255, 245, 221, 0.1) + `, + 'dpad-hover': ` + inset 0px 0px 0.25px 1.25px #262524, + inset 3px 5px 2px -4.75px #FFFFFF, + inset 1.25px 1.5px 0px rgba(0, 0, 0, 0.75), + inset 0px 4.75px 0.25px -2.5px #FBFBFB, + inset 1px 1px 3px 3px #1A1818, + inset 0px -3px 1px rgba(0, 0, 0, 0.5), + inset 2.5px -2px 3px rgba(124, 108, 94, 0.75), + inset 0px -3px 3px 1px rgba(255, 245, 221, 0.4), + 0px 0px 10px 1px rgba(255, 255, 255, 0.4) + `, + 'dpad-pressed': ` + inset 0px 0px 0.25px 1.25px #262524, + inset 1px 1px 3px 3px #1A1818, + inset 0px -1px 1px rgba(0, 0, 0, 0.5) + ` + } + }, + }, + plugins: [], +}; +export default config; diff --git a/create-onchain/templates/minikit/tsconfig.json b/create-onchain/templates/minikit/tsconfig.json new file mode 100644 index 0000000000..e7ff90fd27 --- /dev/null +++ b/create-onchain/templates/minikit/tsconfig.json @@ -0,0 +1,26 @@ +{ + "compilerOptions": { + "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" + } + ], + "paths": { + "@/*": ["./*"] + } + }, + "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"], + "exclude": ["node_modules"] +} diff --git a/create-onchain/vitest.config.ts b/create-onchain/vitest.config.ts new file mode 100644 index 0000000000..3338e2bab5 --- /dev/null +++ b/create-onchain/vitest.config.ts @@ -0,0 +1,38 @@ +// biome-ignore lint/correctness/noNodejsModules: Needed for vite resolving +import path from 'node:path'; +import { defineConfig } from 'vitest/config'; + +export default defineConfig({ + test: { + env: { + TZ: 'UTC', + }, + coverage: { + include: ["src/**/*.ts"], + exclude: [ + "src/manifest/**", + 'create-onchain/dist/**', + '**/*.d.ts', + 'node_modules/**', + ], + reportOnFailure: true, + thresholds: { + statements: 100, + branches: 100, + functions: 100, + lines: 100, + }, + }, + alias: { + '@': path.resolve(__dirname, './src'), + }, + environment: 'jsdom', + exclude: [ + "src/manifest/**", + 'create-onchain/dist/**', + '**/*.d.ts', + 'node_modules/**', + ], + globals: true, + }, +}); diff --git a/create-onchain/yarn.lock b/create-onchain/yarn.lock new file mode 100644 index 0000000000..1648f01520 --- /dev/null +++ b/create-onchain/yarn.lock @@ -0,0 +1,3425 @@ +# This file is generated by running "yarn install" inside your project. +# Manual changes might be lost - proceed with caution! + +__metadata: + version: 8 + cacheKey: 10c0 + +"@ampproject/remapping@npm:^2.3.0": + version: 2.3.0 + resolution: "@ampproject/remapping@npm:2.3.0" + dependencies: + "@jridgewell/gen-mapping": "npm:^0.3.5" + "@jridgewell/trace-mapping": "npm:^0.3.24" + checksum: 81d63cca5443e0f0c72ae18b544cc28c7c0ec2cea46e7cb888bb0e0f411a1191d0d6b7af798d54e30777d8d1488b2ec0732aac2be342d3d7d3ffd271c6f489ed + languageName: node + linkType: hard + +"@asamuzakjp/css-color@npm:^2.8.2": + version: 2.8.3 + resolution: "@asamuzakjp/css-color@npm:2.8.3" + dependencies: + "@csstools/css-calc": "npm:^2.1.1" + "@csstools/css-color-parser": "npm:^3.0.7" + "@csstools/css-parser-algorithms": "npm:^3.0.4" + "@csstools/css-tokenizer": "npm:^3.0.3" + lru-cache: "npm:^10.4.3" + checksum: e108c92ee5de6d8510c9aaca8375c0aeab730dc9b6d4bd287aea2a0379cfbaa09f0814dcacb3e2ddc5c79d7deedf3f82ec8d1ce0effd4a8fac8415b1fe553798 + languageName: node + linkType: hard + +"@babel/helper-string-parser@npm:^7.25.9": + version: 7.25.9 + resolution: "@babel/helper-string-parser@npm:7.25.9" + checksum: 7244b45d8e65f6b4338a6a68a8556f2cb161b782343e97281a5f2b9b93e420cad0d9f5773a59d79f61d0c448913d06f6a2358a87f2e203cf112e3c5b53522ee6 + languageName: node + linkType: hard + +"@babel/helper-validator-identifier@npm:^7.25.9": + version: 7.25.9 + resolution: "@babel/helper-validator-identifier@npm:7.25.9" + checksum: 4fc6f830177b7b7e887ad3277ddb3b91d81e6c4a24151540d9d1023e8dc6b1c0505f0f0628ae653601eb4388a8db45c1c14b2c07a9173837aef7e4116456259d + languageName: node + linkType: hard + +"@babel/parser@npm:^7.25.4": + version: 7.26.9 + resolution: "@babel/parser@npm:7.26.9" + dependencies: + "@babel/types": "npm:^7.26.9" + bin: + parser: ./bin/babel-parser.js + checksum: 4b9ef3c9a0d4c328e5e5544f50fe8932c36f8a2c851e7f14a85401487cd3da75cad72c2e1bcec1eac55599a6bbb2fdc091f274c4fcafa6bdd112d4915ff087fc + languageName: node + linkType: hard + +"@babel/types@npm:^7.25.4, @babel/types@npm:^7.26.9": + version: 7.26.9 + resolution: "@babel/types@npm:7.26.9" + dependencies: + "@babel/helper-string-parser": "npm:^7.25.9" + "@babel/helper-validator-identifier": "npm:^7.25.9" + checksum: 999c56269ba00e5c57aa711fbe7ff071cd6990bafd1b978341ea7572cc78919986e2aa6ee51dacf4b6a7a6fa63ba4eb3f1a03cf55eee31b896a56d068b895964 + languageName: node + linkType: hard + +"@bcoe/v8-coverage@npm:^1.0.2": + version: 1.0.2 + resolution: "@bcoe/v8-coverage@npm:1.0.2" + checksum: 1eb1dc93cc17fb7abdcef21a6e7b867d6aa99a7ec88ec8207402b23d9083ab22a8011213f04b2cf26d535f1d22dc26139b7929e6c2134c254bd1e14ba5e678c3 + languageName: node + linkType: hard + +"@csstools/color-helpers@npm:^5.0.2": + version: 5.0.2 + resolution: "@csstools/color-helpers@npm:5.0.2" + checksum: bebaddb28b9eb58b0449edd5d0c0318fa88f3cb079602ee27e88c9118070d666dcc4e09a5aa936aba2fde6ba419922ade07b7b506af97dd7051abd08dfb2959b + languageName: node + linkType: hard + +"@csstools/css-calc@npm:^2.1.1, @csstools/css-calc@npm:^2.1.2": + version: 2.1.2 + resolution: "@csstools/css-calc@npm:2.1.2" + peerDependencies: + "@csstools/css-parser-algorithms": ^3.0.4 + "@csstools/css-tokenizer": ^3.0.3 + checksum: 34ced30553968ef5d5f9e00e3b90b48c47480cf130e282e99d57ec9b09f803aab8bc06325683e72a1518b5e7180a3da8b533f1b462062757c21989a53b482e1a + languageName: node + linkType: hard + +"@csstools/css-color-parser@npm:^3.0.7": + version: 3.0.8 + resolution: "@csstools/css-color-parser@npm:3.0.8" + dependencies: + "@csstools/color-helpers": "npm:^5.0.2" + "@csstools/css-calc": "npm:^2.1.2" + peerDependencies: + "@csstools/css-parser-algorithms": ^3.0.4 + "@csstools/css-tokenizer": ^3.0.3 + checksum: 90722c5a62ca94e9d578ddf59be604a76400b932bd3d4bd23cb1ae9b7ace8fcf83c06995d2b31f96f4afef24a7cefba79beb11ed7ee4999d7ecfec3869368359 + languageName: node + linkType: hard + +"@csstools/css-parser-algorithms@npm:^3.0.4": + version: 3.0.4 + resolution: "@csstools/css-parser-algorithms@npm:3.0.4" + peerDependencies: + "@csstools/css-tokenizer": ^3.0.3 + checksum: d411f07765e14eede17bccc6bd4f90ff303694df09aabfede3fd104b2dfacfd4fe3697cd25ddad14684c850328f3f9420ebfa9f78380892492974db24ae47dbd + languageName: node + linkType: hard + +"@csstools/css-tokenizer@npm:^3.0.3": + version: 3.0.3 + resolution: "@csstools/css-tokenizer@npm:3.0.3" + checksum: c31bf410e1244b942e71798e37c54639d040cb59e0121b21712b40015fced2b0fb1ffe588434c5f8923c9cd0017cfc1c1c8f3921abc94c96edf471aac2eba5e5 + languageName: node + linkType: hard + +"@esbuild/aix-ppc64@npm:0.25.0": + version: 0.25.0 + resolution: "@esbuild/aix-ppc64@npm:0.25.0" + conditions: os=aix & cpu=ppc64 + languageName: node + linkType: hard + +"@esbuild/android-arm64@npm:0.25.0": + version: 0.25.0 + resolution: "@esbuild/android-arm64@npm:0.25.0" + conditions: os=android & cpu=arm64 + languageName: node + linkType: hard + +"@esbuild/android-arm@npm:0.25.0": + version: 0.25.0 + resolution: "@esbuild/android-arm@npm:0.25.0" + conditions: os=android & cpu=arm + languageName: node + linkType: hard + +"@esbuild/android-x64@npm:0.25.0": + version: 0.25.0 + resolution: "@esbuild/android-x64@npm:0.25.0" + conditions: os=android & cpu=x64 + languageName: node + linkType: hard + +"@esbuild/darwin-arm64@npm:0.25.0": + version: 0.25.0 + resolution: "@esbuild/darwin-arm64@npm:0.25.0" + conditions: os=darwin & cpu=arm64 + languageName: node + linkType: hard + +"@esbuild/darwin-x64@npm:0.25.0": + version: 0.25.0 + resolution: "@esbuild/darwin-x64@npm:0.25.0" + conditions: os=darwin & cpu=x64 + languageName: node + linkType: hard + +"@esbuild/freebsd-arm64@npm:0.25.0": + version: 0.25.0 + resolution: "@esbuild/freebsd-arm64@npm:0.25.0" + conditions: os=freebsd & cpu=arm64 + languageName: node + linkType: hard + +"@esbuild/freebsd-x64@npm:0.25.0": + version: 0.25.0 + resolution: "@esbuild/freebsd-x64@npm:0.25.0" + conditions: os=freebsd & cpu=x64 + languageName: node + linkType: hard + +"@esbuild/linux-arm64@npm:0.25.0": + version: 0.25.0 + resolution: "@esbuild/linux-arm64@npm:0.25.0" + conditions: os=linux & cpu=arm64 + languageName: node + linkType: hard + +"@esbuild/linux-arm@npm:0.25.0": + version: 0.25.0 + resolution: "@esbuild/linux-arm@npm:0.25.0" + conditions: os=linux & cpu=arm + languageName: node + linkType: hard + +"@esbuild/linux-ia32@npm:0.25.0": + version: 0.25.0 + resolution: "@esbuild/linux-ia32@npm:0.25.0" + conditions: os=linux & cpu=ia32 + languageName: node + linkType: hard + +"@esbuild/linux-loong64@npm:0.25.0": + version: 0.25.0 + resolution: "@esbuild/linux-loong64@npm:0.25.0" + conditions: os=linux & cpu=loong64 + languageName: node + linkType: hard + +"@esbuild/linux-mips64el@npm:0.25.0": + version: 0.25.0 + resolution: "@esbuild/linux-mips64el@npm:0.25.0" + conditions: os=linux & cpu=mips64el + languageName: node + linkType: hard + +"@esbuild/linux-ppc64@npm:0.25.0": + version: 0.25.0 + resolution: "@esbuild/linux-ppc64@npm:0.25.0" + conditions: os=linux & cpu=ppc64 + languageName: node + linkType: hard + +"@esbuild/linux-riscv64@npm:0.25.0": + version: 0.25.0 + resolution: "@esbuild/linux-riscv64@npm:0.25.0" + conditions: os=linux & cpu=riscv64 + languageName: node + linkType: hard + +"@esbuild/linux-s390x@npm:0.25.0": + version: 0.25.0 + resolution: "@esbuild/linux-s390x@npm:0.25.0" + conditions: os=linux & cpu=s390x + languageName: node + linkType: hard + +"@esbuild/linux-x64@npm:0.25.0": + version: 0.25.0 + resolution: "@esbuild/linux-x64@npm:0.25.0" + conditions: os=linux & cpu=x64 + languageName: node + linkType: hard + +"@esbuild/netbsd-arm64@npm:0.25.0": + version: 0.25.0 + resolution: "@esbuild/netbsd-arm64@npm:0.25.0" + conditions: os=netbsd & cpu=arm64 + languageName: node + linkType: hard + +"@esbuild/netbsd-x64@npm:0.25.0": + version: 0.25.0 + resolution: "@esbuild/netbsd-x64@npm:0.25.0" + conditions: os=netbsd & cpu=x64 + languageName: node + linkType: hard + +"@esbuild/openbsd-arm64@npm:0.25.0": + version: 0.25.0 + resolution: "@esbuild/openbsd-arm64@npm:0.25.0" + conditions: os=openbsd & cpu=arm64 + languageName: node + linkType: hard + +"@esbuild/openbsd-x64@npm:0.25.0": + version: 0.25.0 + resolution: "@esbuild/openbsd-x64@npm:0.25.0" + conditions: os=openbsd & cpu=x64 + languageName: node + linkType: hard + +"@esbuild/sunos-x64@npm:0.25.0": + version: 0.25.0 + resolution: "@esbuild/sunos-x64@npm:0.25.0" + conditions: os=sunos & cpu=x64 + languageName: node + linkType: hard + +"@esbuild/win32-arm64@npm:0.25.0": + version: 0.25.0 + resolution: "@esbuild/win32-arm64@npm:0.25.0" + conditions: os=win32 & cpu=arm64 + languageName: node + linkType: hard + +"@esbuild/win32-ia32@npm:0.25.0": + version: 0.25.0 + resolution: "@esbuild/win32-ia32@npm:0.25.0" + conditions: os=win32 & cpu=ia32 + languageName: node + linkType: hard + +"@esbuild/win32-x64@npm:0.25.0": + version: 0.25.0 + resolution: "@esbuild/win32-x64@npm:0.25.0" + conditions: os=win32 & cpu=x64 + languageName: node + linkType: hard + +"@isaacs/cliui@npm:^8.0.2": + version: 8.0.2 + resolution: "@isaacs/cliui@npm:8.0.2" + dependencies: + string-width: "npm:^5.1.2" + string-width-cjs: "npm:string-width@^4.2.0" + strip-ansi: "npm:^7.0.1" + strip-ansi-cjs: "npm:strip-ansi@^6.0.1" + wrap-ansi: "npm:^8.1.0" + wrap-ansi-cjs: "npm:wrap-ansi@^7.0.0" + checksum: b1bf42535d49f11dc137f18d5e4e63a28c5569de438a221c369483731e9dac9fb797af554e8bf02b6192d1e5eba6e6402cf93900c3d0ac86391d00d04876789e + languageName: node + linkType: hard + +"@isaacs/fs-minipass@npm:^4.0.0": + version: 4.0.1 + resolution: "@isaacs/fs-minipass@npm:4.0.1" + dependencies: + minipass: "npm:^7.0.4" + checksum: c25b6dc1598790d5b55c0947a9b7d111cfa92594db5296c3b907e2f533c033666f692a3939eadac17b1c7c40d362d0b0635dc874cbfe3e70db7c2b07cc97a5d2 + languageName: node + linkType: hard + +"@istanbuljs/schema@npm:^0.1.2": + version: 0.1.3 + resolution: "@istanbuljs/schema@npm:0.1.3" + checksum: 61c5286771676c9ca3eb2bd8a7310a9c063fb6e0e9712225c8471c582d157392c88f5353581c8c9adbe0dff98892317d2fdfc56c3499aa42e0194405206a963a + languageName: node + linkType: hard + +"@jridgewell/gen-mapping@npm:^0.3.5": + version: 0.3.8 + resolution: "@jridgewell/gen-mapping@npm:0.3.8" + dependencies: + "@jridgewell/set-array": "npm:^1.2.1" + "@jridgewell/sourcemap-codec": "npm:^1.4.10" + "@jridgewell/trace-mapping": "npm:^0.3.24" + checksum: c668feaf86c501d7c804904a61c23c67447b2137b813b9ce03eca82cb9d65ac7006d766c218685d76e3d72828279b6ee26c347aa1119dab23fbaf36aed51585a + languageName: node + linkType: hard + +"@jridgewell/resolve-uri@npm:^3.1.0": + version: 3.1.2 + resolution: "@jridgewell/resolve-uri@npm:3.1.2" + checksum: d502e6fb516b35032331406d4e962c21fe77cdf1cbdb49c6142bcbd9e30507094b18972778a6e27cbad756209cfe34b1a27729e6fa08a2eb92b33943f680cf1e + languageName: node + linkType: hard + +"@jridgewell/set-array@npm:^1.2.1": + version: 1.2.1 + resolution: "@jridgewell/set-array@npm:1.2.1" + checksum: 2a5aa7b4b5c3464c895c802d8ae3f3d2b92fcbe84ad12f8d0bfbb1f5ad006717e7577ee1fd2eac00c088abe486c7adb27976f45d2941ff6b0b92b2c3302c60f4 + languageName: node + linkType: hard + +"@jridgewell/sourcemap-codec@npm:^1.4.10, @jridgewell/sourcemap-codec@npm:^1.4.14, @jridgewell/sourcemap-codec@npm:^1.5.0": + version: 1.5.0 + resolution: "@jridgewell/sourcemap-codec@npm:1.5.0" + checksum: 2eb864f276eb1096c3c11da3e9bb518f6d9fc0023c78344cdc037abadc725172c70314bdb360f2d4b7bffec7f5d657ce006816bc5d4ecb35e61b66132db00c18 + languageName: node + linkType: hard + +"@jridgewell/trace-mapping@npm:^0.3.23, @jridgewell/trace-mapping@npm:^0.3.24": + version: 0.3.25 + resolution: "@jridgewell/trace-mapping@npm:0.3.25" + dependencies: + "@jridgewell/resolve-uri": "npm:^3.1.0" + "@jridgewell/sourcemap-codec": "npm:^1.4.14" + checksum: 3d1ce6ebc69df9682a5a8896b414c6537e428a1d68b02fcc8363b04284a8ca0df04d0ee3013132252ab14f2527bc13bea6526a912ecb5658f0e39fd2860b4df4 + languageName: node + linkType: hard + +"@npmcli/agent@npm:^3.0.0": + version: 3.0.0 + resolution: "@npmcli/agent@npm:3.0.0" + dependencies: + agent-base: "npm:^7.1.0" + http-proxy-agent: "npm:^7.0.0" + https-proxy-agent: "npm:^7.0.1" + lru-cache: "npm:^10.0.1" + socks-proxy-agent: "npm:^8.0.3" + checksum: efe37b982f30740ee77696a80c196912c274ecd2cb243bc6ae7053a50c733ce0f6c09fda085145f33ecf453be19654acca74b69e81eaad4c90f00ccffe2f9271 + languageName: node + linkType: hard + +"@npmcli/fs@npm:^4.0.0": + version: 4.0.0 + resolution: "@npmcli/fs@npm:4.0.0" + dependencies: + semver: "npm:^7.3.5" + checksum: c90935d5ce670c87b6b14fab04a965a3b8137e585f8b2a6257263bd7f97756dd736cb165bb470e5156a9e718ecd99413dccc54b1138c1a46d6ec7cf325982fe5 + languageName: node + linkType: hard + +"@pkgjs/parseargs@npm:^0.11.0": + version: 0.11.0 + resolution: "@pkgjs/parseargs@npm:0.11.0" + checksum: 5bd7576bb1b38a47a7fc7b51ac9f38748e772beebc56200450c4a817d712232b8f1d3ef70532c80840243c657d491cf6a6be1e3a214cff907645819fdc34aadd + languageName: node + linkType: hard + +"@polka/url@npm:^1.0.0-next.24": + version: 1.0.0-next.28 + resolution: "@polka/url@npm:1.0.0-next.28" + checksum: acc5ea62597e4da2fb42dbee02749d07f102ae7d6d2c966bf7e423c79cd65d1621da305af567e6e7c232f3b565e242d1ec932cbb3dcc0db1508d02e9a2cafa2e + languageName: node + linkType: hard + +"@rollup/rollup-android-arm-eabi@npm:4.34.9": + version: 4.34.9 + resolution: "@rollup/rollup-android-arm-eabi@npm:4.34.9" + conditions: os=android & cpu=arm + languageName: node + linkType: hard + +"@rollup/rollup-android-arm64@npm:4.34.9": + version: 4.34.9 + resolution: "@rollup/rollup-android-arm64@npm:4.34.9" + conditions: os=android & cpu=arm64 + languageName: node + linkType: hard + +"@rollup/rollup-darwin-arm64@npm:4.34.9": + version: 4.34.9 + resolution: "@rollup/rollup-darwin-arm64@npm:4.34.9" + conditions: os=darwin & cpu=arm64 + languageName: node + linkType: hard + +"@rollup/rollup-darwin-x64@npm:4.34.9": + version: 4.34.9 + resolution: "@rollup/rollup-darwin-x64@npm:4.34.9" + conditions: os=darwin & cpu=x64 + languageName: node + linkType: hard + +"@rollup/rollup-freebsd-arm64@npm:4.34.9": + version: 4.34.9 + resolution: "@rollup/rollup-freebsd-arm64@npm:4.34.9" + conditions: os=freebsd & cpu=arm64 + languageName: node + linkType: hard + +"@rollup/rollup-freebsd-x64@npm:4.34.9": + version: 4.34.9 + resolution: "@rollup/rollup-freebsd-x64@npm:4.34.9" + conditions: os=freebsd & cpu=x64 + languageName: node + linkType: hard + +"@rollup/rollup-linux-arm-gnueabihf@npm:4.34.9": + version: 4.34.9 + resolution: "@rollup/rollup-linux-arm-gnueabihf@npm:4.34.9" + conditions: os=linux & cpu=arm & libc=glibc + languageName: node + linkType: hard + +"@rollup/rollup-linux-arm-musleabihf@npm:4.34.9": + version: 4.34.9 + resolution: "@rollup/rollup-linux-arm-musleabihf@npm:4.34.9" + conditions: os=linux & cpu=arm & libc=musl + languageName: node + linkType: hard + +"@rollup/rollup-linux-arm64-gnu@npm:4.34.9": + version: 4.34.9 + resolution: "@rollup/rollup-linux-arm64-gnu@npm:4.34.9" + conditions: os=linux & cpu=arm64 & libc=glibc + languageName: node + linkType: hard + +"@rollup/rollup-linux-arm64-musl@npm:4.34.9": + version: 4.34.9 + resolution: "@rollup/rollup-linux-arm64-musl@npm:4.34.9" + conditions: os=linux & cpu=arm64 & libc=musl + languageName: node + linkType: hard + +"@rollup/rollup-linux-loongarch64-gnu@npm:4.34.9": + version: 4.34.9 + resolution: "@rollup/rollup-linux-loongarch64-gnu@npm:4.34.9" + conditions: os=linux & cpu=loong64 & libc=glibc + languageName: node + linkType: hard + +"@rollup/rollup-linux-powerpc64le-gnu@npm:4.34.9": + version: 4.34.9 + resolution: "@rollup/rollup-linux-powerpc64le-gnu@npm:4.34.9" + conditions: os=linux & cpu=ppc64 & libc=glibc + languageName: node + linkType: hard + +"@rollup/rollup-linux-riscv64-gnu@npm:4.34.9": + version: 4.34.9 + resolution: "@rollup/rollup-linux-riscv64-gnu@npm:4.34.9" + conditions: os=linux & cpu=riscv64 & libc=glibc + languageName: node + linkType: hard + +"@rollup/rollup-linux-s390x-gnu@npm:4.34.9": + version: 4.34.9 + resolution: "@rollup/rollup-linux-s390x-gnu@npm:4.34.9" + conditions: os=linux & cpu=s390x & libc=glibc + languageName: node + linkType: hard + +"@rollup/rollup-linux-x64-gnu@npm:4.34.9": + version: 4.34.9 + resolution: "@rollup/rollup-linux-x64-gnu@npm:4.34.9" + conditions: os=linux & cpu=x64 & libc=glibc + languageName: node + linkType: hard + +"@rollup/rollup-linux-x64-musl@npm:4.34.9": + version: 4.34.9 + resolution: "@rollup/rollup-linux-x64-musl@npm:4.34.9" + conditions: os=linux & cpu=x64 & libc=musl + languageName: node + linkType: hard + +"@rollup/rollup-win32-arm64-msvc@npm:4.34.9": + version: 4.34.9 + resolution: "@rollup/rollup-win32-arm64-msvc@npm:4.34.9" + conditions: os=win32 & cpu=arm64 + languageName: node + linkType: hard + +"@rollup/rollup-win32-ia32-msvc@npm:4.34.9": + version: 4.34.9 + resolution: "@rollup/rollup-win32-ia32-msvc@npm:4.34.9" + conditions: os=win32 & cpu=ia32 + languageName: node + linkType: hard + +"@rollup/rollup-win32-x64-msvc@npm:4.34.9": + version: 4.34.9 + resolution: "@rollup/rollup-win32-x64-msvc@npm:4.34.9" + conditions: os=win32 & cpu=x64 + languageName: node + linkType: hard + +"@types/body-parser@npm:*": + version: 1.19.5 + resolution: "@types/body-parser@npm:1.19.5" + dependencies: + "@types/connect": "npm:*" + "@types/node": "npm:*" + checksum: aebeb200f25e8818d8cf39cd0209026750d77c9b85381cdd8deeb50913e4d18a1ebe4b74ca9b0b4d21952511eeaba5e9fbbf739b52731a2061e206ec60d568df + languageName: node + linkType: hard + +"@types/connect@npm:*": + version: 3.4.38 + resolution: "@types/connect@npm:3.4.38" + dependencies: + "@types/node": "npm:*" + checksum: 2e1cdba2c410f25649e77856505cd60223250fa12dff7a503e492208dbfdd25f62859918f28aba95315251fd1f5e1ffbfca1e25e73037189ab85dd3f8d0a148c + languageName: node + linkType: hard + +"@types/estree@npm:1.0.6, @types/estree@npm:^1.0.0": + version: 1.0.6 + resolution: "@types/estree@npm:1.0.6" + checksum: cdfd751f6f9065442cd40957c07fd80361c962869aa853c1c2fd03e101af8b9389d8ff4955a43a6fcfa223dd387a089937f95be0f3eec21ca527039fd2d9859a + languageName: node + linkType: hard + +"@types/express-serve-static-core@npm:^5.0.0": + version: 5.0.6 + resolution: "@types/express-serve-static-core@npm:5.0.6" + dependencies: + "@types/node": "npm:*" + "@types/qs": "npm:*" + "@types/range-parser": "npm:*" + "@types/send": "npm:*" + checksum: aced8cc88c1718adbbd1fc488756b0f22d763368d9eff2ae21b350698fab4a77d8d13c3699056dc662a887e43a8b67a3e8f6289ff76102ecc6bad4a7710d31a6 + languageName: node + linkType: hard + +"@types/express@npm:^5.0.0": + version: 5.0.0 + resolution: "@types/express@npm:5.0.0" + dependencies: + "@types/body-parser": "npm:*" + "@types/express-serve-static-core": "npm:^5.0.0" + "@types/qs": "npm:*" + "@types/serve-static": "npm:*" + checksum: 0d74b53aefa69c3b3817ee9b5145fd50d7dbac52a8986afc2d7500085c446656d0b6dc13158c04e2d9f18f4324d4d93b0452337c5ff73dd086dca3e4ff11f47b + languageName: node + linkType: hard + +"@types/http-errors@npm:*": + version: 2.0.4 + resolution: "@types/http-errors@npm:2.0.4" + checksum: 494670a57ad4062fee6c575047ad5782506dd35a6b9ed3894cea65830a94367bd84ba302eb3dde331871f6d70ca287bfedb1b2cf658e6132cd2cbd427ab56836 + languageName: node + linkType: hard + +"@types/mime@npm:^1": + version: 1.3.5 + resolution: "@types/mime@npm:1.3.5" + checksum: c2ee31cd9b993804df33a694d5aa3fa536511a49f2e06eeab0b484fef59b4483777dbb9e42a4198a0809ffbf698081fdbca1e5c2218b82b91603dfab10a10fbc + languageName: node + linkType: hard + +"@types/node@npm:*": + version: 22.13.9 + resolution: "@types/node@npm:22.13.9" + dependencies: + undici-types: "npm:~6.20.0" + checksum: eb6acd04169a076631dcaab712128d492cd17a1b3f10daae4a377f3d439c860c3cd3e32f4ef221671f56183b976ac7c4089f4193457314a88675ead4663438a4 + languageName: node + linkType: hard + +"@types/node@npm:^20.12.10": + version: 20.17.23 + resolution: "@types/node@npm:20.17.23" + dependencies: + undici-types: "npm:~6.19.2" + checksum: 4f7da7383ee8516b2e580d772a196fd76487670bd9d32a296621c5df63b077cc7d06c2a0040885b3e4a28c1751f9ad3d5ed55cff15d50b707e3d454993bfe33a + languageName: node + linkType: hard + +"@types/prompts@npm:^2.4.9": + version: 2.4.9 + resolution: "@types/prompts@npm:2.4.9" + dependencies: + "@types/node": "npm:*" + kleur: "npm:^3.0.3" + checksum: 22fe0da6807681c85e88ba283184f4be4c8a95c744ea12a638865c98c4e0c22e7f733542f6b0f1fbca02245cdc3fe84feacf9c9adf4ddd8bc98a337fd679d8d2 + languageName: node + linkType: hard + +"@types/qs@npm:*": + version: 6.9.18 + resolution: "@types/qs@npm:6.9.18" + checksum: 790b9091348e06dde2c8e4118b5771ab386a8c22a952139a2eb0675360a2070d0b155663bf6f75b23f258fd0a1f7ffc0ba0f059d99a719332c03c40d9e9cd63b + languageName: node + linkType: hard + +"@types/range-parser@npm:*": + version: 1.2.7 + resolution: "@types/range-parser@npm:1.2.7" + checksum: 361bb3e964ec5133fa40644a0b942279ed5df1949f21321d77de79f48b728d39253e5ce0408c9c17e4e0fd95ca7899da36841686393b9f7a1e209916e9381a3c + languageName: node + linkType: hard + +"@types/send@npm:*": + version: 0.17.4 + resolution: "@types/send@npm:0.17.4" + dependencies: + "@types/mime": "npm:^1" + "@types/node": "npm:*" + checksum: 7f17fa696cb83be0a104b04b424fdedc7eaba1c9a34b06027239aba513b398a0e2b7279778af521f516a397ced417c96960e5f50fcfce40c4bc4509fb1a5883c + languageName: node + linkType: hard + +"@types/serve-static@npm:*": + version: 1.15.7 + resolution: "@types/serve-static@npm:1.15.7" + dependencies: + "@types/http-errors": "npm:*" + "@types/node": "npm:*" + "@types/send": "npm:*" + checksum: 26ec864d3a626ea627f8b09c122b623499d2221bbf2f470127f4c9ebfe92bd8a6bb5157001372d4c4bd0dd37a1691620217d9dc4df5aa8f779f3fd996b1c60ae + languageName: node + linkType: hard + +"@types/ws@npm:^8.5.14": + version: 8.18.0 + resolution: "@types/ws@npm:8.18.0" + dependencies: + "@types/node": "npm:*" + checksum: a56d2e0d1da7411a1f3548ce02b51a50cbe9e23f025677d03df48f87e4a3c72e1342fbf1d12e487d7eafa8dc670c605152b61bbf9165891ec0e9694b0d3ea8d4 + languageName: node + linkType: hard + +"@vitest/coverage-v8@npm:^3.0.5": + version: 3.0.7 + resolution: "@vitest/coverage-v8@npm:3.0.7" + dependencies: + "@ampproject/remapping": "npm:^2.3.0" + "@bcoe/v8-coverage": "npm:^1.0.2" + debug: "npm:^4.4.0" + istanbul-lib-coverage: "npm:^3.2.2" + istanbul-lib-report: "npm:^3.0.1" + istanbul-lib-source-maps: "npm:^5.0.6" + istanbul-reports: "npm:^3.1.7" + magic-string: "npm:^0.30.17" + magicast: "npm:^0.3.5" + std-env: "npm:^3.8.0" + test-exclude: "npm:^7.0.1" + tinyrainbow: "npm:^2.0.0" + peerDependencies: + "@vitest/browser": 3.0.7 + vitest: 3.0.7 + peerDependenciesMeta: + "@vitest/browser": + optional: true + checksum: 37cce7091d8b75b5db515a6152f0f168506d3252789343630135f8341e5486293afb1ab2bdae882d84fe20879b078c14fd610c485baff16b3ab5cd87aa0303c0 + languageName: node + linkType: hard + +"@vitest/expect@npm:3.0.7": + version: 3.0.7 + resolution: "@vitest/expect@npm:3.0.7" + dependencies: + "@vitest/spy": "npm:3.0.7" + "@vitest/utils": "npm:3.0.7" + chai: "npm:^5.2.0" + tinyrainbow: "npm:^2.0.0" + checksum: 70ec7ff758640e12a5335b7455d69a9589a4b5d3a4ce6fc421aa4548a38c5947b1e36ca8d89fcbe979c955dbb9b0941b8c487c466606a9db2ab75b163796daad + languageName: node + linkType: hard + +"@vitest/mocker@npm:3.0.7": + version: 3.0.7 + resolution: "@vitest/mocker@npm:3.0.7" + dependencies: + "@vitest/spy": "npm:3.0.7" + estree-walker: "npm:^3.0.3" + magic-string: "npm:^0.30.17" + peerDependencies: + msw: ^2.4.9 + vite: ^5.0.0 || ^6.0.0 + peerDependenciesMeta: + msw: + optional: true + vite: + optional: true + checksum: c6809c57a5df1870b53f8edcae921a4953a34edf6032b439ff66dd0a4b80af4350f5690e7ff1fe3774ed86c639431005cd97cb2b9099ef24b6cd3c7388105d67 + languageName: node + linkType: hard + +"@vitest/pretty-format@npm:3.0.7, @vitest/pretty-format@npm:^3.0.7": + version: 3.0.7 + resolution: "@vitest/pretty-format@npm:3.0.7" + dependencies: + tinyrainbow: "npm:^2.0.0" + checksum: c67fc7025f4e1bd431aaa5ff3d79430be6ea4f10b360756c711416659105ec14633249f7605fe10f5fbb47dbb1579bd6e77da218fc3f28cfdaacac7c8fadbc6e + languageName: node + linkType: hard + +"@vitest/runner@npm:3.0.7": + version: 3.0.7 + resolution: "@vitest/runner@npm:3.0.7" + dependencies: + "@vitest/utils": "npm:3.0.7" + pathe: "npm:^2.0.3" + checksum: 68cd7c0291ae7a20c4a5c1dbdf94ef49be7f471fe33d96d6f155ab50d257e01d9fda231a4dd008e8b4909870680e68a0606624fbf03ffa4958fd929ba18a0cd7 + languageName: node + linkType: hard + +"@vitest/snapshot@npm:3.0.7": + version: 3.0.7 + resolution: "@vitest/snapshot@npm:3.0.7" + dependencies: + "@vitest/pretty-format": "npm:3.0.7" + magic-string: "npm:^0.30.17" + pathe: "npm:^2.0.3" + checksum: 012f3d2f921094f7580909717f3802872ad48bf735f5076b583031413c84afb9b65be00c392c8dfb5cb506eb5038a11ac62682e43ed84625a815fe420bedf775 + languageName: node + linkType: hard + +"@vitest/spy@npm:3.0.7": + version: 3.0.7 + resolution: "@vitest/spy@npm:3.0.7" + dependencies: + tinyspy: "npm:^3.0.2" + checksum: eb361a64e7819b2ebc45d6a8f31bed5a65272dfadc27ab8ce7df869817ce26a11f35bab9136690c91630107961423c7187cf4097b77d7422f9b97b96e96ca1d7 + languageName: node + linkType: hard + +"@vitest/ui@npm:^3.0.5": + version: 3.0.7 + resolution: "@vitest/ui@npm:3.0.7" + dependencies: + "@vitest/utils": "npm:3.0.7" + fflate: "npm:^0.8.2" + flatted: "npm:^3.3.3" + pathe: "npm:^2.0.3" + sirv: "npm:^3.0.1" + tinyglobby: "npm:^0.2.12" + tinyrainbow: "npm:^2.0.0" + peerDependencies: + vitest: 3.0.7 + checksum: 08e1a45a8c6888024d606952568d7a434b4cf9049033c0907a0d45e7769ce11cac667f8fbe0063cf254fd09041df4a35a5ccb491134010cf7c2ce3219c889726 + languageName: node + linkType: hard + +"@vitest/utils@npm:3.0.7": + version: 3.0.7 + resolution: "@vitest/utils@npm:3.0.7" + dependencies: + "@vitest/pretty-format": "npm:3.0.7" + loupe: "npm:^3.1.3" + tinyrainbow: "npm:^2.0.0" + checksum: 4023a4ebfa675dc3d9298764e1c62395e9fb1b5518d7f0f9d79bf25d98627166db620f8b5cb9bc5acbac2b74b1c635cce91d8ec99f065188a92611e5f7631220 + languageName: node + linkType: hard + +"abbrev@npm:^3.0.0": + version: 3.0.0 + resolution: "abbrev@npm:3.0.0" + checksum: 049704186396f571650eb7b22ed3627b77a5aedf98bb83caf2eac81ca2a3e25e795394b0464cfb2d6076df3db6a5312139eac5b6a126ca296ac53c5008069c28 + languageName: node + linkType: hard + +"accepts@npm:~1.3.8": + version: 1.3.8 + resolution: "accepts@npm:1.3.8" + dependencies: + mime-types: "npm:~2.1.34" + negotiator: "npm:0.6.3" + checksum: 3a35c5f5586cfb9a21163ca47a5f77ac34fa8ceb5d17d2fa2c0d81f41cbd7f8c6fa52c77e2c039acc0f4d09e71abdc51144246900f6bef5e3c4b333f77d89362 + languageName: node + linkType: hard + +"agent-base@npm:^7.1.0, agent-base@npm:^7.1.2": + version: 7.1.3 + resolution: "agent-base@npm:7.1.3" + checksum: 6192b580c5b1d8fb399b9c62bf8343d76654c2dd62afcb9a52b2cf44a8b6ace1e3b704d3fe3547d91555c857d3df02603341ff2cb961b9cfe2b12f9f3c38ee11 + languageName: node + linkType: hard + +"ansi-regex@npm:^5.0.1": + version: 5.0.1 + resolution: "ansi-regex@npm:5.0.1" + checksum: 9a64bb8627b434ba9327b60c027742e5d17ac69277960d041898596271d992d4d52ba7267a63ca10232e29f6107fc8a835f6ce8d719b88c5f8493f8254813737 + languageName: node + linkType: hard + +"ansi-regex@npm:^6.0.1": + version: 6.1.0 + resolution: "ansi-regex@npm:6.1.0" + checksum: a91daeddd54746338478eef88af3439a7edf30f8e23196e2d6ed182da9add559c601266dbef01c2efa46a958ad6f1f8b176799657616c702b5b02e799e7fd8dc + languageName: node + linkType: hard + +"ansi-styles@npm:^4.0.0": + version: 4.3.0 + resolution: "ansi-styles@npm:4.3.0" + dependencies: + color-convert: "npm:^2.0.1" + checksum: 895a23929da416f2bd3de7e9cb4eabd340949328ab85ddd6e484a637d8f6820d485f53933446f5291c3b760cbc488beb8e88573dd0f9c7daf83dccc8fe81b041 + languageName: node + linkType: hard + +"ansi-styles@npm:^6.1.0": + version: 6.2.1 + resolution: "ansi-styles@npm:6.2.1" + checksum: 5d1ec38c123984bcedd996eac680d548f31828bd679a66db2bdf11844634dde55fec3efa9c6bb1d89056a5e79c1ac540c4c784d592ea1d25028a92227d2f2d5c + languageName: node + linkType: hard + +"array-flatten@npm:1.1.1": + version: 1.1.1 + resolution: "array-flatten@npm:1.1.1" + checksum: 806966c8abb2f858b08f5324d9d18d7737480610f3bd5d3498aaae6eb5efdc501a884ba019c9b4a8f02ff67002058749d05548fd42fa8643f02c9c7f22198b91 + languageName: node + linkType: hard + +"assertion-error@npm:^2.0.1": + version: 2.0.1 + resolution: "assertion-error@npm:2.0.1" + checksum: bbbcb117ac6480138f8c93cf7f535614282dea9dc828f540cdece85e3c665e8f78958b96afac52f29ff883c72638e6a87d469ecc9fe5bc902df03ed24a55dba8 + languageName: node + linkType: hard + +"asynckit@npm:^0.4.0": + version: 0.4.0 + resolution: "asynckit@npm:0.4.0" + checksum: d73e2ddf20c4eb9337e1b3df1a0f6159481050a5de457c55b14ea2e5cb6d90bb69e004c9af54737a5ee0917fcf2c9e25de67777bbe58261847846066ba75bc9d + languageName: node + linkType: hard + +"balanced-match@npm:^1.0.0": + version: 1.0.2 + resolution: "balanced-match@npm:1.0.2" + checksum: 9308baf0a7e4838a82bbfd11e01b1cb0f0cf2893bc1676c27c2a8c0e70cbae1c59120c3268517a8ae7fb6376b4639ef81ca22582611dbee4ed28df945134aaee + languageName: node + linkType: hard + +"body-parser@npm:1.20.3": + version: 1.20.3 + resolution: "body-parser@npm:1.20.3" + dependencies: + bytes: "npm:3.1.2" + content-type: "npm:~1.0.5" + debug: "npm:2.6.9" + depd: "npm:2.0.0" + destroy: "npm:1.2.0" + http-errors: "npm:2.0.0" + iconv-lite: "npm:0.4.24" + on-finished: "npm:2.4.1" + qs: "npm:6.13.0" + raw-body: "npm:2.5.2" + type-is: "npm:~1.6.18" + unpipe: "npm:1.0.0" + checksum: 0a9a93b7518f222885498dcecaad528cf010dd109b071bf471c93def4bfe30958b83e03496eb9c1ad4896db543d999bb62be1a3087294162a88cfa1b42c16310 + languageName: node + linkType: hard + +"brace-expansion@npm:^2.0.1": + version: 2.0.1 + resolution: "brace-expansion@npm:2.0.1" + dependencies: + balanced-match: "npm:^1.0.0" + checksum: b358f2fe060e2d7a87aa015979ecea07f3c37d4018f8d6deb5bd4c229ad3a0384fe6029bb76cd8be63c81e516ee52d1a0673edbe2023d53a5191732ae3c3e49f + languageName: node + linkType: hard + +"bundle-name@npm:^4.1.0": + version: 4.1.0 + resolution: "bundle-name@npm:4.1.0" + dependencies: + run-applescript: "npm:^7.0.0" + checksum: 8e575981e79c2bcf14d8b1c027a3775c095d362d1382312f444a7c861b0e21513c0bd8db5bd2b16e50ba0709fa622d4eab6b53192d222120305e68359daece29 + languageName: node + linkType: hard + +"bytes@npm:3.1.2": + version: 3.1.2 + resolution: "bytes@npm:3.1.2" + checksum: 76d1c43cbd602794ad8ad2ae94095cddeb1de78c5dddaa7005c51af10b0176c69971a6d88e805a90c2b6550d76636e43c40d8427a808b8645ede885de4a0358e + languageName: node + linkType: hard + +"cac@npm:^6.7.14": + version: 6.7.14 + resolution: "cac@npm:6.7.14" + checksum: 4ee06aaa7bab8981f0d54e5f5f9d4adcd64058e9697563ce336d8a3878ed018ee18ebe5359b2430eceae87e0758e62ea2019c3f52ae6e211b1bd2e133856cd10 + languageName: node + linkType: hard + +"cacache@npm:^19.0.1": + version: 19.0.1 + resolution: "cacache@npm:19.0.1" + dependencies: + "@npmcli/fs": "npm:^4.0.0" + fs-minipass: "npm:^3.0.0" + glob: "npm:^10.2.2" + lru-cache: "npm:^10.0.1" + minipass: "npm:^7.0.3" + minipass-collect: "npm:^2.0.1" + minipass-flush: "npm:^1.0.5" + minipass-pipeline: "npm:^1.2.4" + p-map: "npm:^7.0.2" + ssri: "npm:^12.0.0" + tar: "npm:^7.4.3" + unique-filename: "npm:^4.0.0" + checksum: 01f2134e1bd7d3ab68be851df96c8d63b492b1853b67f2eecb2c37bb682d37cb70bb858a16f2f0554d3c0071be6dfe21456a1ff6fa4b7eed996570d6a25ffe9c + languageName: node + linkType: hard + +"call-bind-apply-helpers@npm:^1.0.1, call-bind-apply-helpers@npm:^1.0.2": + version: 1.0.2 + resolution: "call-bind-apply-helpers@npm:1.0.2" + dependencies: + es-errors: "npm:^1.3.0" + function-bind: "npm:^1.1.2" + checksum: 47bd9901d57b857590431243fea704ff18078b16890a6b3e021e12d279bbf211d039155e27d7566b374d49ee1f8189344bac9833dec7a20cdec370506361c938 + languageName: node + linkType: hard + +"call-bound@npm:^1.0.2": + version: 1.0.4 + resolution: "call-bound@npm:1.0.4" + dependencies: + call-bind-apply-helpers: "npm:^1.0.2" + get-intrinsic: "npm:^1.3.0" + checksum: f4796a6a0941e71c766aea672f63b72bc61234c4f4964dc6d7606e3664c307e7d77845328a8f3359ce39ddb377fed67318f9ee203dea1d47e46165dcf2917644 + languageName: node + linkType: hard + +"chai@npm:^5.2.0": + version: 5.2.0 + resolution: "chai@npm:5.2.0" + dependencies: + assertion-error: "npm:^2.0.1" + check-error: "npm:^2.1.1" + deep-eql: "npm:^5.0.1" + loupe: "npm:^3.1.0" + pathval: "npm:^2.0.0" + checksum: dfd1cb719c7cebb051b727672d382a35338af1470065cb12adb01f4ee451bbf528e0e0f9ab2016af5fc1eea4df6e7f4504dc8443f8f00bd8fb87ad32dc516f7d + languageName: node + linkType: hard + +"chalk@npm:^5.3.0": + version: 5.4.1 + resolution: "chalk@npm:5.4.1" + checksum: b23e88132c702f4855ca6d25cb5538b1114343e41472d5263ee8a37cccfccd9c4216d111e1097c6a27830407a1dc81fecdf2a56f2c63033d4dbbd88c10b0dcef + languageName: node + linkType: hard + +"check-error@npm:^2.1.1": + version: 2.1.1 + resolution: "check-error@npm:2.1.1" + checksum: 979f13eccab306cf1785fa10941a590b4e7ea9916ea2a4f8c87f0316fc3eab07eabefb6e587424ef0f88cbcd3805791f172ea739863ca3d7ce2afc54641c7f0e + languageName: node + linkType: hard + +"chownr@npm:^3.0.0": + version: 3.0.0 + resolution: "chownr@npm:3.0.0" + checksum: 43925b87700f7e3893296c8e9c56cc58f926411cce3a6e5898136daaf08f08b9a8eb76d37d3267e707d0dcc17aed2e2ebdf5848c0c3ce95cf910a919935c1b10 + languageName: node + linkType: hard + +"cli-cursor@npm:^5.0.0": + version: 5.0.0 + resolution: "cli-cursor@npm:5.0.0" + dependencies: + restore-cursor: "npm:^5.0.0" + checksum: 7ec62f69b79f6734ab209a3e4dbdc8af7422d44d360a7cb1efa8a0887bbe466a6e625650c466fe4359aee44dbe2dc0b6994b583d40a05d0808a5cb193641d220 + languageName: node + linkType: hard + +"cli-spinners@npm:^2.9.2": + version: 2.9.2 + resolution: "cli-spinners@npm:2.9.2" + checksum: 907a1c227ddf0d7a101e7ab8b300affc742ead4b4ebe920a5bf1bc6d45dce2958fcd195eb28fa25275062fe6fa9b109b93b63bc8033396ed3bcb50297008b3a3 + languageName: node + linkType: hard + +"color-convert@npm:^2.0.1": + version: 2.0.1 + resolution: "color-convert@npm:2.0.1" + dependencies: + color-name: "npm:~1.1.4" + checksum: 37e1150172f2e311fe1b2df62c6293a342ee7380da7b9cfdba67ea539909afbd74da27033208d01d6d5cfc65ee7868a22e18d7e7648e004425441c0f8a15a7d7 + languageName: node + linkType: hard + +"color-name@npm:~1.1.4": + version: 1.1.4 + resolution: "color-name@npm:1.1.4" + checksum: a1a3f914156960902f46f7f56bc62effc6c94e84b2cae157a526b1c1f74b677a47ec602bf68a61abfa2b42d15b7c5651c6dbe72a43af720bc588dff885b10f95 + languageName: node + linkType: hard + +"combined-stream@npm:^1.0.8": + version: 1.0.8 + resolution: "combined-stream@npm:1.0.8" + dependencies: + delayed-stream: "npm:~1.0.0" + checksum: 0dbb829577e1b1e839fa82b40c07ffaf7de8a09b935cadd355a73652ae70a88b4320db322f6634a4ad93424292fa80973ac6480986247f1734a1137debf271d5 + languageName: node + linkType: hard + +"content-disposition@npm:0.5.4": + version: 0.5.4 + resolution: "content-disposition@npm:0.5.4" + dependencies: + safe-buffer: "npm:5.2.1" + checksum: bac0316ebfeacb8f381b38285dc691c9939bf0a78b0b7c2d5758acadad242d04783cee5337ba7d12a565a19075af1b3c11c728e1e4946de73c6ff7ce45f3f1bb + languageName: node + linkType: hard + +"content-type@npm:~1.0.4, content-type@npm:~1.0.5": + version: 1.0.5 + resolution: "content-type@npm:1.0.5" + checksum: b76ebed15c000aee4678c3707e0860cb6abd4e680a598c0a26e17f0bfae723ec9cc2802f0ff1bc6e4d80603719010431d2231018373d4dde10f9ccff9dadf5af + languageName: node + linkType: hard + +"cookie-signature@npm:1.0.6": + version: 1.0.6 + resolution: "cookie-signature@npm:1.0.6" + checksum: b36fd0d4e3fef8456915fcf7742e58fbfcc12a17a018e0eb9501c9d5ef6893b596466f03b0564b81af29ff2538fd0aa4b9d54fe5ccbfb4c90ea50ad29fe2d221 + languageName: node + linkType: hard + +"cookie@npm:0.7.1": + version: 0.7.1 + resolution: "cookie@npm:0.7.1" + checksum: 5de60c67a410e7c8dc8a46a4b72eb0fe925871d057c9a5d2c0e8145c4270a4f81076de83410c4d397179744b478e33cd80ccbcc457abf40a9409ad27dcd21dde + languageName: node + linkType: hard + +"create-onchain@workspace:.": + version: 0.0.0-use.local + resolution: "create-onchain@workspace:." + dependencies: + "@types/express": "npm:^5.0.0" + "@types/node": "npm:^20.12.10" + "@types/prompts": "npm:^2.4.9" + "@types/ws": "npm:^8.5.14" + "@vitest/coverage-v8": "npm:^3.0.5" + "@vitest/ui": "npm:^3.0.5" + cac: "npm:^6.7.14" + express: "npm:^4.21.2" + jsdom: "npm:^24.1.0" + open: "npm:^10.1.0" + ora: "npm:^8.1.0" + picocolors: "npm:^1.1.0" + prompts: "npm:^2.4.2" + vitest: "npm:^3.0.5" + ws: "npm:^8.18.1" + bin: + create-onchain: ./dist/esm/cli.js + languageName: unknown + linkType: soft + +"cross-spawn@npm:^7.0.6": + version: 7.0.6 + resolution: "cross-spawn@npm:7.0.6" + dependencies: + path-key: "npm:^3.1.0" + shebang-command: "npm:^2.0.0" + which: "npm:^2.0.1" + checksum: 053ea8b2135caff68a9e81470e845613e374e7309a47731e81639de3eaeb90c3d01af0e0b44d2ab9d50b43467223b88567dfeb3262db942dc063b9976718ffc1 + languageName: node + linkType: hard + +"cssstyle@npm:^4.0.1": + version: 4.2.1 + resolution: "cssstyle@npm:4.2.1" + dependencies: + "@asamuzakjp/css-color": "npm:^2.8.2" + rrweb-cssom: "npm:^0.8.0" + checksum: 02ba8c47c0caaab57acadacb3eb6c0f5f009000f55d61f6563670e07d389b26edefeed497e6c1847fcd2e6bbe0b6974c2d4291f97fa0c6ec6add13a7fa926d84 + languageName: node + linkType: hard + +"data-urls@npm:^5.0.0": + version: 5.0.0 + resolution: "data-urls@npm:5.0.0" + dependencies: + whatwg-mimetype: "npm:^4.0.0" + whatwg-url: "npm:^14.0.0" + checksum: 1b894d7d41c861f3a4ed2ae9b1c3f0909d4575ada02e36d3d3bc584bdd84278e20709070c79c3b3bff7ac98598cb191eb3e86a89a79ea4ee1ef360e1694f92ad + languageName: node + linkType: hard + +"debug@npm:2.6.9": + version: 2.6.9 + resolution: "debug@npm:2.6.9" + dependencies: + ms: "npm:2.0.0" + checksum: 121908fb839f7801180b69a7e218a40b5a0b718813b886b7d6bdb82001b931c938e2941d1e4450f33a1b1df1da653f5f7a0440c197f29fbf8a6e9d45ff6ef589 + languageName: node + linkType: hard + +"debug@npm:4, debug@npm:^4.1.1, debug@npm:^4.3.4, debug@npm:^4.4.0": + version: 4.4.0 + resolution: "debug@npm:4.4.0" + dependencies: + ms: "npm:^2.1.3" + peerDependenciesMeta: + supports-color: + optional: true + checksum: db94f1a182bf886f57b4755f85b3a74c39b5114b9377b7ab375dc2cfa3454f09490cc6c30f829df3fc8042bc8b8995f6567ce5cd96f3bc3688bd24027197d9de + languageName: node + linkType: hard + +"decimal.js@npm:^10.4.3": + version: 10.5.0 + resolution: "decimal.js@npm:10.5.0" + checksum: 785c35279df32762143914668df35948920b6c1c259b933e0519a69b7003fc0a5ed2a766b1e1dda02574450c566b21738a45f15e274b47c2ac02072c0d1f3ac3 + languageName: node + linkType: hard + +"deep-eql@npm:^5.0.1": + version: 5.0.2 + resolution: "deep-eql@npm:5.0.2" + checksum: 7102cf3b7bb719c6b9c0db2e19bf0aa9318d141581befe8c7ce8ccd39af9eaa4346e5e05adef7f9bd7015da0f13a3a25dcfe306ef79dc8668aedbecb658dd247 + languageName: node + linkType: hard + +"default-browser-id@npm:^5.0.0": + version: 5.0.0 + resolution: "default-browser-id@npm:5.0.0" + checksum: 957fb886502594c8e645e812dfe93dba30ed82e8460d20ce39c53c5b0f3e2afb6ceaec2249083b90bdfbb4cb0f34e1f73fde3d68cac00becdbcfd894156b5ead + languageName: node + linkType: hard + +"default-browser@npm:^5.2.1": + version: 5.2.1 + resolution: "default-browser@npm:5.2.1" + dependencies: + bundle-name: "npm:^4.1.0" + default-browser-id: "npm:^5.0.0" + checksum: 73f17dc3c58026c55bb5538749597db31f9561c0193cd98604144b704a981c95a466f8ecc3c2db63d8bfd04fb0d426904834cfc91ae510c6aeb97e13c5167c4d + languageName: node + linkType: hard + +"define-lazy-prop@npm:^3.0.0": + version: 3.0.0 + resolution: "define-lazy-prop@npm:3.0.0" + checksum: 5ab0b2bf3fa58b3a443140bbd4cd3db1f91b985cc8a246d330b9ac3fc0b6a325a6d82bddc0b055123d745b3f9931afeea74a5ec545439a1630b9c8512b0eeb49 + languageName: node + linkType: hard + +"delayed-stream@npm:~1.0.0": + version: 1.0.0 + resolution: "delayed-stream@npm:1.0.0" + checksum: d758899da03392e6712f042bec80aa293bbe9e9ff1b2634baae6a360113e708b91326594c8a486d475c69d6259afb7efacdc3537bfcda1c6c648e390ce601b19 + languageName: node + linkType: hard + +"depd@npm:2.0.0": + version: 2.0.0 + resolution: "depd@npm:2.0.0" + checksum: 58bd06ec20e19529b06f7ad07ddab60e504d9e0faca4bd23079fac2d279c3594334d736508dc350e06e510aba5e22e4594483b3a6562ce7c17dd797f4cc4ad2c + languageName: node + linkType: hard + +"destroy@npm:1.2.0": + version: 1.2.0 + resolution: "destroy@npm:1.2.0" + checksum: bd7633942f57418f5a3b80d5cb53898127bcf53e24cdf5d5f4396be471417671f0fee48a4ebe9a1e9defbde2a31280011af58a57e090ff822f589b443ed4e643 + languageName: node + linkType: hard + +"dunder-proto@npm:^1.0.1": + version: 1.0.1 + resolution: "dunder-proto@npm:1.0.1" + dependencies: + call-bind-apply-helpers: "npm:^1.0.1" + es-errors: "npm:^1.3.0" + gopd: "npm:^1.2.0" + checksum: 199f2a0c1c16593ca0a145dbf76a962f8033ce3129f01284d48c45ed4e14fea9bbacd7b3610b6cdc33486cef20385ac054948fefc6272fcce645c09468f93031 + languageName: node + linkType: hard + +"eastasianwidth@npm:^0.2.0": + version: 0.2.0 + resolution: "eastasianwidth@npm:0.2.0" + checksum: 26f364ebcdb6395f95124fda411f63137a4bfb5d3a06453f7f23dfe52502905bd84e0488172e0f9ec295fdc45f05c23d5d91baf16bd26f0fe9acd777a188dc39 + languageName: node + linkType: hard + +"ee-first@npm:1.1.1": + version: 1.1.1 + resolution: "ee-first@npm:1.1.1" + checksum: b5bb125ee93161bc16bfe6e56c6b04de5ad2aa44234d8f644813cc95d861a6910903132b05093706de2b706599367c4130eb6d170f6b46895686b95f87d017b7 + languageName: node + linkType: hard + +"emoji-regex@npm:^10.3.0": + version: 10.4.0 + resolution: "emoji-regex@npm:10.4.0" + checksum: a3fcedfc58bfcce21a05a5f36a529d81e88d602100145fcca3dc6f795e3c8acc4fc18fe773fbf9b6d6e9371205edb3afa2668ec3473fa2aa7fd47d2a9d46482d + languageName: node + linkType: hard + +"emoji-regex@npm:^8.0.0": + version: 8.0.0 + resolution: "emoji-regex@npm:8.0.0" + checksum: b6053ad39951c4cf338f9092d7bfba448cdfd46fe6a2a034700b149ac9ffbc137e361cbd3c442297f86bed2e5f7576c1b54cc0a6bf8ef5106cc62f496af35010 + languageName: node + linkType: hard + +"emoji-regex@npm:^9.2.2": + version: 9.2.2 + resolution: "emoji-regex@npm:9.2.2" + checksum: af014e759a72064cf66e6e694a7fc6b0ed3d8db680427b021a89727689671cefe9d04151b2cad51dbaf85d5ba790d061cd167f1cf32eb7b281f6368b3c181639 + languageName: node + linkType: hard + +"encodeurl@npm:~1.0.2": + version: 1.0.2 + resolution: "encodeurl@npm:1.0.2" + checksum: f6c2387379a9e7c1156c1c3d4f9cb7bb11cf16dd4c1682e1f6746512564b053df5781029b6061296832b59fb22f459dbe250386d217c2f6e203601abb2ee0bec + languageName: node + linkType: hard + +"encodeurl@npm:~2.0.0": + version: 2.0.0 + resolution: "encodeurl@npm:2.0.0" + checksum: 5d317306acb13e6590e28e27924c754163946a2480de11865c991a3a7eed4315cd3fba378b543ca145829569eefe9b899f3d84bb09870f675ae60bc924b01ceb + languageName: node + linkType: hard + +"encoding@npm:^0.1.13": + version: 0.1.13 + resolution: "encoding@npm:0.1.13" + dependencies: + iconv-lite: "npm:^0.6.2" + checksum: 36d938712ff00fe1f4bac88b43bcffb5930c1efa57bbcdca9d67e1d9d6c57cfb1200fb01efe0f3109b2ce99b231f90779532814a81370a1bd3274a0f58585039 + languageName: node + linkType: hard + +"entities@npm:^4.5.0": + version: 4.5.0 + resolution: "entities@npm:4.5.0" + checksum: 5b039739f7621f5d1ad996715e53d964035f75ad3b9a4d38c6b3804bb226e282ffeae2443624d8fdd9c47d8e926ae9ac009c54671243f0c3294c26af7cc85250 + languageName: node + linkType: hard + +"env-paths@npm:^2.2.0": + version: 2.2.1 + resolution: "env-paths@npm:2.2.1" + checksum: 285325677bf00e30845e330eec32894f5105529db97496ee3f598478e50f008c5352a41a30e5e72ec9de8a542b5a570b85699cd63bd2bc646dbcb9f311d83bc4 + languageName: node + linkType: hard + +"err-code@npm:^2.0.2": + version: 2.0.3 + resolution: "err-code@npm:2.0.3" + checksum: b642f7b4dd4a376e954947550a3065a9ece6733ab8e51ad80db727aaae0817c2e99b02a97a3d6cecc648a97848305e728289cf312d09af395403a90c9d4d8a66 + languageName: node + linkType: hard + +"es-define-property@npm:^1.0.1": + version: 1.0.1 + resolution: "es-define-property@npm:1.0.1" + checksum: 3f54eb49c16c18707949ff25a1456728c883e81259f045003499efba399c08bad00deebf65cccde8c0e07908c1a225c9d472b7107e558f2a48e28d530e34527c + languageName: node + linkType: hard + +"es-errors@npm:^1.3.0": + version: 1.3.0 + resolution: "es-errors@npm:1.3.0" + checksum: 0a61325670072f98d8ae3b914edab3559b6caa980f08054a3b872052640d91da01d38df55df797fcc916389d77fc92b8d5906cf028f4db46d7e3003abecbca85 + languageName: node + linkType: hard + +"es-module-lexer@npm:^1.6.0": + version: 1.6.0 + resolution: "es-module-lexer@npm:1.6.0" + checksum: 667309454411c0b95c476025929881e71400d74a746ffa1ff4cb450bd87f8e33e8eef7854d68e401895039ac0bac64e7809acbebb6253e055dd49ea9e3ea9212 + languageName: node + linkType: hard + +"es-object-atoms@npm:^1.0.0, es-object-atoms@npm:^1.1.1": + version: 1.1.1 + resolution: "es-object-atoms@npm:1.1.1" + dependencies: + es-errors: "npm:^1.3.0" + checksum: 65364812ca4daf48eb76e2a3b7a89b3f6a2e62a1c420766ce9f692665a29d94fe41fe88b65f24106f449859549711e4b40d9fb8002d862dfd7eb1c512d10be0c + languageName: node + linkType: hard + +"es-set-tostringtag@npm:^2.1.0": + version: 2.1.0 + resolution: "es-set-tostringtag@npm:2.1.0" + dependencies: + es-errors: "npm:^1.3.0" + get-intrinsic: "npm:^1.2.6" + has-tostringtag: "npm:^1.0.2" + hasown: "npm:^2.0.2" + checksum: ef2ca9ce49afe3931cb32e35da4dcb6d86ab02592cfc2ce3e49ced199d9d0bb5085fc7e73e06312213765f5efa47cc1df553a6a5154584b21448e9fb8355b1af + languageName: node + linkType: hard + +"esbuild@npm:^0.25.0": + version: 0.25.0 + resolution: "esbuild@npm:0.25.0" + dependencies: + "@esbuild/aix-ppc64": "npm:0.25.0" + "@esbuild/android-arm": "npm:0.25.0" + "@esbuild/android-arm64": "npm:0.25.0" + "@esbuild/android-x64": "npm:0.25.0" + "@esbuild/darwin-arm64": "npm:0.25.0" + "@esbuild/darwin-x64": "npm:0.25.0" + "@esbuild/freebsd-arm64": "npm:0.25.0" + "@esbuild/freebsd-x64": "npm:0.25.0" + "@esbuild/linux-arm": "npm:0.25.0" + "@esbuild/linux-arm64": "npm:0.25.0" + "@esbuild/linux-ia32": "npm:0.25.0" + "@esbuild/linux-loong64": "npm:0.25.0" + "@esbuild/linux-mips64el": "npm:0.25.0" + "@esbuild/linux-ppc64": "npm:0.25.0" + "@esbuild/linux-riscv64": "npm:0.25.0" + "@esbuild/linux-s390x": "npm:0.25.0" + "@esbuild/linux-x64": "npm:0.25.0" + "@esbuild/netbsd-arm64": "npm:0.25.0" + "@esbuild/netbsd-x64": "npm:0.25.0" + "@esbuild/openbsd-arm64": "npm:0.25.0" + "@esbuild/openbsd-x64": "npm:0.25.0" + "@esbuild/sunos-x64": "npm:0.25.0" + "@esbuild/win32-arm64": "npm:0.25.0" + "@esbuild/win32-ia32": "npm:0.25.0" + "@esbuild/win32-x64": "npm:0.25.0" + dependenciesMeta: + "@esbuild/aix-ppc64": + optional: true + "@esbuild/android-arm": + optional: true + "@esbuild/android-arm64": + optional: true + "@esbuild/android-x64": + optional: true + "@esbuild/darwin-arm64": + optional: true + "@esbuild/darwin-x64": + optional: true + "@esbuild/freebsd-arm64": + optional: true + "@esbuild/freebsd-x64": + optional: true + "@esbuild/linux-arm": + optional: true + "@esbuild/linux-arm64": + optional: true + "@esbuild/linux-ia32": + optional: true + "@esbuild/linux-loong64": + optional: true + "@esbuild/linux-mips64el": + optional: true + "@esbuild/linux-ppc64": + optional: true + "@esbuild/linux-riscv64": + optional: true + "@esbuild/linux-s390x": + optional: true + "@esbuild/linux-x64": + optional: true + "@esbuild/netbsd-arm64": + optional: true + "@esbuild/netbsd-x64": + optional: true + "@esbuild/openbsd-arm64": + optional: true + "@esbuild/openbsd-x64": + optional: true + "@esbuild/sunos-x64": + optional: true + "@esbuild/win32-arm64": + optional: true + "@esbuild/win32-ia32": + optional: true + "@esbuild/win32-x64": + optional: true + bin: + esbuild: bin/esbuild + checksum: 5767b72da46da3cfec51661647ec850ddbf8a8d0662771139f10ef0692a8831396a0004b2be7966cecdb08264fb16bdc16290dcecd92396fac5f12d722fa013d + languageName: node + linkType: hard + +"escape-html@npm:~1.0.3": + version: 1.0.3 + resolution: "escape-html@npm:1.0.3" + checksum: 524c739d776b36c3d29fa08a22e03e8824e3b2fd57500e5e44ecf3cc4707c34c60f9ca0781c0e33d191f2991161504c295e98f68c78fe7baa6e57081ec6ac0a3 + languageName: node + linkType: hard + +"estree-walker@npm:^3.0.3": + version: 3.0.3 + resolution: "estree-walker@npm:3.0.3" + dependencies: + "@types/estree": "npm:^1.0.0" + checksum: c12e3c2b2642d2bcae7d5aa495c60fa2f299160946535763969a1c83fc74518ffa9c2cd3a8b69ac56aea547df6a8aac25f729a342992ef0bbac5f1c73e78995d + languageName: node + linkType: hard + +"etag@npm:~1.8.1": + version: 1.8.1 + resolution: "etag@npm:1.8.1" + checksum: 12be11ef62fb9817314d790089a0a49fae4e1b50594135dcb8076312b7d7e470884b5100d249b28c18581b7fd52f8b485689ffae22a11ed9ec17377a33a08f84 + languageName: node + linkType: hard + +"expect-type@npm:^1.1.0": + version: 1.2.0 + resolution: "expect-type@npm:1.2.0" + checksum: 6069e1980bf16b9385646800e23499c1447df636c433014f6bbabe4bb0e20bd0033f30d38a6f9ae0938b0203a9e870cc82cdfd74b7c837b480cefb8e8240d8e8 + languageName: node + linkType: hard + +"exponential-backoff@npm:^3.1.1": + version: 3.1.2 + resolution: "exponential-backoff@npm:3.1.2" + checksum: d9d3e1eafa21b78464297df91f1776f7fbaa3d5e3f7f0995648ca5b89c069d17055033817348d9f4a43d1c20b0eab84f75af6991751e839df53e4dfd6f22e844 + languageName: node + linkType: hard + +"express@npm:^4.21.2": + version: 4.21.2 + resolution: "express@npm:4.21.2" + dependencies: + accepts: "npm:~1.3.8" + array-flatten: "npm:1.1.1" + body-parser: "npm:1.20.3" + content-disposition: "npm:0.5.4" + content-type: "npm:~1.0.4" + cookie: "npm:0.7.1" + cookie-signature: "npm:1.0.6" + debug: "npm:2.6.9" + depd: "npm:2.0.0" + encodeurl: "npm:~2.0.0" + escape-html: "npm:~1.0.3" + etag: "npm:~1.8.1" + finalhandler: "npm:1.3.1" + fresh: "npm:0.5.2" + http-errors: "npm:2.0.0" + merge-descriptors: "npm:1.0.3" + methods: "npm:~1.1.2" + on-finished: "npm:2.4.1" + parseurl: "npm:~1.3.3" + path-to-regexp: "npm:0.1.12" + proxy-addr: "npm:~2.0.7" + qs: "npm:6.13.0" + range-parser: "npm:~1.2.1" + safe-buffer: "npm:5.2.1" + send: "npm:0.19.0" + serve-static: "npm:1.16.2" + setprototypeof: "npm:1.2.0" + statuses: "npm:2.0.1" + type-is: "npm:~1.6.18" + utils-merge: "npm:1.0.1" + vary: "npm:~1.1.2" + checksum: 38168fd0a32756600b56e6214afecf4fc79ec28eca7f7a91c2ab8d50df4f47562ca3f9dee412da7f5cea6b1a1544b33b40f9f8586dbacfbdada0fe90dbb10a1f + languageName: node + linkType: hard + +"fdir@npm:^6.4.3": + version: 6.4.3 + resolution: "fdir@npm:6.4.3" + peerDependencies: + picomatch: ^3 || ^4 + peerDependenciesMeta: + picomatch: + optional: true + checksum: d13c10120e9625adf21d8d80481586200759928c19405a816b77dd28eaeb80e7c59c5def3e2941508045eb06d34eb47fad865ccc8bf98e6ab988bb0ed160fb6f + languageName: node + linkType: hard + +"fflate@npm:^0.8.2": + version: 0.8.2 + resolution: "fflate@npm:0.8.2" + checksum: 03448d630c0a583abea594835a9fdb2aaf7d67787055a761515bf4ed862913cfd693b4c4ffd5c3f3b355a70cf1e19033e9ae5aedcca103188aaff91b8bd6e293 + languageName: node + linkType: hard + +"finalhandler@npm:1.3.1": + version: 1.3.1 + resolution: "finalhandler@npm:1.3.1" + dependencies: + debug: "npm:2.6.9" + encodeurl: "npm:~2.0.0" + escape-html: "npm:~1.0.3" + on-finished: "npm:2.4.1" + parseurl: "npm:~1.3.3" + statuses: "npm:2.0.1" + unpipe: "npm:~1.0.0" + checksum: d38035831865a49b5610206a3a9a9aae4e8523cbbcd01175d0480ffbf1278c47f11d89be3ca7f617ae6d94f29cf797546a4619cd84dd109009ef33f12f69019f + languageName: node + linkType: hard + +"flatted@npm:^3.3.3": + version: 3.3.3 + resolution: "flatted@npm:3.3.3" + checksum: e957a1c6b0254aa15b8cce8533e24165abd98fadc98575db082b786b5da1b7d72062b81bfdcd1da2f4d46b6ed93bec2434e62333e9b4261d79ef2e75a10dd538 + languageName: node + linkType: hard + +"foreground-child@npm:^3.1.0": + version: 3.3.1 + resolution: "foreground-child@npm:3.3.1" + dependencies: + cross-spawn: "npm:^7.0.6" + signal-exit: "npm:^4.0.1" + checksum: 8986e4af2430896e65bc2788d6679067294d6aee9545daefc84923a0a4b399ad9c7a3ea7bd8c0b2b80fdf4a92de4c69df3f628233ff3224260e9c1541a9e9ed3 + languageName: node + linkType: hard + +"form-data@npm:^4.0.0": + version: 4.0.2 + resolution: "form-data@npm:4.0.2" + dependencies: + asynckit: "npm:^0.4.0" + combined-stream: "npm:^1.0.8" + es-set-tostringtag: "npm:^2.1.0" + mime-types: "npm:^2.1.12" + checksum: e534b0cf025c831a0929bf4b9bbe1a9a6b03e273a8161f9947286b9b13bf8fb279c6944aae0070c4c311100c6d6dbb815cd955dc217728caf73fad8dc5b8ee9c + languageName: node + linkType: hard + +"forwarded@npm:0.2.0": + version: 0.2.0 + resolution: "forwarded@npm:0.2.0" + checksum: 9b67c3fac86acdbc9ae47ba1ddd5f2f81526fa4c8226863ede5600a3f7c7416ef451f6f1e240a3cc32d0fd79fcfe6beb08fd0da454f360032bde70bf80afbb33 + languageName: node + linkType: hard + +"fresh@npm:0.5.2": + version: 0.5.2 + resolution: "fresh@npm:0.5.2" + checksum: c6d27f3ed86cc5b601404822f31c900dd165ba63fff8152a3ef714e2012e7535027063bc67ded4cb5b3a49fa596495d46cacd9f47d6328459cf570f08b7d9e5a + languageName: node + linkType: hard + +"fs-minipass@npm:^3.0.0": + version: 3.0.3 + resolution: "fs-minipass@npm:3.0.3" + dependencies: + minipass: "npm:^7.0.3" + checksum: 63e80da2ff9b621e2cb1596abcb9207f1cf82b968b116ccd7b959e3323144cce7fb141462200971c38bbf2ecca51695069db45265705bed09a7cd93ae5b89f94 + languageName: node + linkType: hard + +"fsevents@npm:~2.3.2, fsevents@npm:~2.3.3": + version: 2.3.3 + resolution: "fsevents@npm:2.3.3" + dependencies: + node-gyp: "npm:latest" + checksum: a1f0c44595123ed717febbc478aa952e47adfc28e2092be66b8ab1635147254ca6cfe1df792a8997f22716d4cbafc73309899ff7bfac2ac3ad8cf2e4ecc3ec60 + conditions: os=darwin + languageName: node + linkType: hard + +"fsevents@patch:fsevents@npm%3A~2.3.2#optional!builtin, fsevents@patch:fsevents@npm%3A~2.3.3#optional!builtin": + version: 2.3.3 + resolution: "fsevents@patch:fsevents@npm%3A2.3.3#optional!builtin::version=2.3.3&hash=df0bf1" + dependencies: + node-gyp: "npm:latest" + conditions: os=darwin + languageName: node + linkType: hard + +"function-bind@npm:^1.1.2": + version: 1.1.2 + resolution: "function-bind@npm:1.1.2" + checksum: d8680ee1e5fcd4c197e4ac33b2b4dce03c71f4d91717292785703db200f5c21f977c568d28061226f9b5900cbcd2c84463646134fd5337e7925e0942bc3f46d5 + languageName: node + linkType: hard + +"get-east-asian-width@npm:^1.0.0": + version: 1.3.0 + resolution: "get-east-asian-width@npm:1.3.0" + checksum: 1a049ba697e0f9a4d5514c4623781c5246982bdb61082da6b5ae6c33d838e52ce6726407df285cdbb27ec1908b333cf2820989bd3e986e37bb20979437fdf34b + languageName: node + linkType: hard + +"get-intrinsic@npm:^1.2.5, get-intrinsic@npm:^1.2.6, get-intrinsic@npm:^1.3.0": + version: 1.3.0 + resolution: "get-intrinsic@npm:1.3.0" + dependencies: + call-bind-apply-helpers: "npm:^1.0.2" + es-define-property: "npm:^1.0.1" + es-errors: "npm:^1.3.0" + es-object-atoms: "npm:^1.1.1" + function-bind: "npm:^1.1.2" + get-proto: "npm:^1.0.1" + gopd: "npm:^1.2.0" + has-symbols: "npm:^1.1.0" + hasown: "npm:^2.0.2" + math-intrinsics: "npm:^1.1.0" + checksum: 52c81808af9a8130f581e6a6a83e1ba4a9f703359e7a438d1369a5267a25412322f03dcbd7c549edaef0b6214a0630a28511d7df0130c93cfd380f4fa0b5b66a + languageName: node + linkType: hard + +"get-proto@npm:^1.0.1": + version: 1.0.1 + resolution: "get-proto@npm:1.0.1" + dependencies: + dunder-proto: "npm:^1.0.1" + es-object-atoms: "npm:^1.0.0" + checksum: 9224acb44603c5526955e83510b9da41baf6ae73f7398875fba50edc5e944223a89c4a72b070fcd78beb5f7bdda58ecb6294adc28f7acfc0da05f76a2399643c + languageName: node + linkType: hard + +"glob@npm:^10.2.2, glob@npm:^10.3.10, glob@npm:^10.3.7, glob@npm:^10.4.1": + version: 10.4.5 + resolution: "glob@npm:10.4.5" + dependencies: + foreground-child: "npm:^3.1.0" + jackspeak: "npm:^3.1.2" + minimatch: "npm:^9.0.4" + minipass: "npm:^7.1.2" + package-json-from-dist: "npm:^1.0.0" + path-scurry: "npm:^1.11.1" + bin: + glob: dist/esm/bin.mjs + checksum: 19a9759ea77b8e3ca0a43c2f07ecddc2ad46216b786bb8f993c445aee80d345925a21e5280c7b7c6c59e860a0154b84e4b2b60321fea92cd3c56b4a7489f160e + languageName: node + linkType: hard + +"gopd@npm:^1.2.0": + version: 1.2.0 + resolution: "gopd@npm:1.2.0" + checksum: 50fff1e04ba2b7737c097358534eacadad1e68d24cccee3272e04e007bed008e68d2614f3987788428fd192a5ae3889d08fb2331417e4fc4a9ab366b2043cead + languageName: node + linkType: hard + +"graceful-fs@npm:^4.2.6": + version: 4.2.11 + resolution: "graceful-fs@npm:4.2.11" + checksum: 386d011a553e02bc594ac2ca0bd6d9e4c22d7fa8cfbfc448a6d148c59ea881b092db9dbe3547ae4b88e55f1b01f7c4a2ecc53b310c042793e63aa44cf6c257f2 + languageName: node + linkType: hard + +"has-flag@npm:^4.0.0": + version: 4.0.0 + resolution: "has-flag@npm:4.0.0" + checksum: 2e789c61b7888d66993e14e8331449e525ef42aac53c627cc53d1c3334e768bcb6abdc4f5f0de1478a25beec6f0bd62c7549058b7ac53e924040d4f301f02fd1 + languageName: node + linkType: hard + +"has-symbols@npm:^1.0.3, has-symbols@npm:^1.1.0": + version: 1.1.0 + resolution: "has-symbols@npm:1.1.0" + checksum: dde0a734b17ae51e84b10986e651c664379018d10b91b6b0e9b293eddb32f0f069688c841fb40f19e9611546130153e0a2a48fd7f512891fb000ddfa36f5a20e + languageName: node + linkType: hard + +"has-tostringtag@npm:^1.0.2": + version: 1.0.2 + resolution: "has-tostringtag@npm:1.0.2" + dependencies: + has-symbols: "npm:^1.0.3" + checksum: a8b166462192bafe3d9b6e420a1d581d93dd867adb61be223a17a8d6dad147aa77a8be32c961bb2f27b3ef893cae8d36f564ab651f5e9b7938ae86f74027c48c + languageName: node + linkType: hard + +"hasown@npm:^2.0.2": + version: 2.0.2 + resolution: "hasown@npm:2.0.2" + dependencies: + function-bind: "npm:^1.1.2" + checksum: 3769d434703b8ac66b209a4cca0737519925bbdb61dd887f93a16372b14694c63ff4e797686d87c90f08168e81082248b9b028bad60d4da9e0d1148766f56eb9 + languageName: node + linkType: hard + +"html-encoding-sniffer@npm:^4.0.0": + version: 4.0.0 + resolution: "html-encoding-sniffer@npm:4.0.0" + dependencies: + whatwg-encoding: "npm:^3.1.1" + checksum: 523398055dc61ac9b34718a719cb4aa691e4166f29187e211e1607de63dc25ac7af52ca7c9aead0c4b3c0415ffecb17326396e1202e2e86ff4bca4c0ee4c6140 + languageName: node + linkType: hard + +"html-escaper@npm:^2.0.0": + version: 2.0.2 + resolution: "html-escaper@npm:2.0.2" + checksum: 208e8a12de1a6569edbb14544f4567e6ce8ecc30b9394fcaa4e7bb1e60c12a7c9a1ed27e31290817157e8626f3a4f29e76c8747030822eb84a6abb15c255f0a0 + languageName: node + linkType: hard + +"http-cache-semantics@npm:^4.1.1": + version: 4.1.1 + resolution: "http-cache-semantics@npm:4.1.1" + checksum: ce1319b8a382eb3cbb4a37c19f6bfe14e5bb5be3d09079e885e8c513ab2d3cd9214902f8a31c9dc4e37022633ceabfc2d697405deeaf1b8f3552bb4ed996fdfc + languageName: node + linkType: hard + +"http-errors@npm:2.0.0": + version: 2.0.0 + resolution: "http-errors@npm:2.0.0" + dependencies: + depd: "npm:2.0.0" + inherits: "npm:2.0.4" + setprototypeof: "npm:1.2.0" + statuses: "npm:2.0.1" + toidentifier: "npm:1.0.1" + checksum: fc6f2715fe188d091274b5ffc8b3657bd85c63e969daa68ccb77afb05b071a4b62841acb7a21e417b5539014dff2ebf9550f0b14a9ff126f2734a7c1387f8e19 + languageName: node + linkType: hard + +"http-proxy-agent@npm:^7.0.0, http-proxy-agent@npm:^7.0.2": + version: 7.0.2 + resolution: "http-proxy-agent@npm:7.0.2" + dependencies: + agent-base: "npm:^7.1.0" + debug: "npm:^4.3.4" + checksum: 4207b06a4580fb85dd6dff521f0abf6db517489e70863dca1a0291daa7f2d3d2d6015a57bd702af068ea5cf9f1f6ff72314f5f5b4228d299c0904135d2aef921 + languageName: node + linkType: hard + +"https-proxy-agent@npm:^7.0.1, https-proxy-agent@npm:^7.0.5": + version: 7.0.6 + resolution: "https-proxy-agent@npm:7.0.6" + dependencies: + agent-base: "npm:^7.1.2" + debug: "npm:4" + checksum: f729219bc735edb621fa30e6e84e60ee5d00802b8247aac0d7b79b0bd6d4b3294737a337b93b86a0bd9e68099d031858a39260c976dc14cdbba238ba1f8779ac + languageName: node + linkType: hard + +"iconv-lite@npm:0.4.24": + version: 0.4.24 + resolution: "iconv-lite@npm:0.4.24" + dependencies: + safer-buffer: "npm:>= 2.1.2 < 3" + checksum: c6886a24cc00f2a059767440ec1bc00d334a89f250db8e0f7feb4961c8727118457e27c495ba94d082e51d3baca378726cd110aaf7ded8b9bbfd6a44760cf1d4 + languageName: node + linkType: hard + +"iconv-lite@npm:0.6.3, iconv-lite@npm:^0.6.2": + version: 0.6.3 + resolution: "iconv-lite@npm:0.6.3" + dependencies: + safer-buffer: "npm:>= 2.1.2 < 3.0.0" + checksum: 98102bc66b33fcf5ac044099d1257ba0b7ad5e3ccd3221f34dd508ab4070edff183276221684e1e0555b145fce0850c9f7d2b60a9fcac50fbb4ea0d6e845a3b1 + languageName: node + linkType: hard + +"imurmurhash@npm:^0.1.4": + version: 0.1.4 + resolution: "imurmurhash@npm:0.1.4" + checksum: 8b51313850dd33605c6c9d3fd9638b714f4c4c40250cff658209f30d40da60f78992fb2df5dabee4acf589a6a82bbc79ad5486550754bd9ec4e3fc0d4a57d6a6 + languageName: node + linkType: hard + +"inherits@npm:2.0.4": + version: 2.0.4 + resolution: "inherits@npm:2.0.4" + checksum: 4e531f648b29039fb7426fb94075e6545faa1eb9fe83c29f0b6d9e7263aceb4289d2d4557db0d428188eeb449cc7c5e77b0a0b2c4e248ff2a65933a0dee49ef2 + languageName: node + linkType: hard + +"ip-address@npm:^9.0.5": + version: 9.0.5 + resolution: "ip-address@npm:9.0.5" + dependencies: + jsbn: "npm:1.1.0" + sprintf-js: "npm:^1.1.3" + checksum: 331cd07fafcb3b24100613e4b53e1a2b4feab11e671e655d46dc09ee233da5011284d09ca40c4ecbdfe1d0004f462958675c224a804259f2f78d2465a87824bc + languageName: node + linkType: hard + +"ipaddr.js@npm:1.9.1": + version: 1.9.1 + resolution: "ipaddr.js@npm:1.9.1" + checksum: 0486e775047971d3fdb5fb4f063829bac45af299ae0b82dcf3afa2145338e08290563a2a70f34b732d795ecc8311902e541a8530eeb30d75860a78ff4e94ce2a + languageName: node + linkType: hard + +"is-docker@npm:^3.0.0": + version: 3.0.0 + resolution: "is-docker@npm:3.0.0" + bin: + is-docker: cli.js + checksum: d2c4f8e6d3e34df75a5defd44991b6068afad4835bb783b902fa12d13ebdb8f41b2a199dcb0b5ed2cb78bfee9e4c0bbdb69c2d9646f4106464674d3e697a5856 + languageName: node + linkType: hard + +"is-fullwidth-code-point@npm:^3.0.0": + version: 3.0.0 + resolution: "is-fullwidth-code-point@npm:3.0.0" + checksum: bb11d825e049f38e04c06373a8d72782eee0205bda9d908cc550ccb3c59b99d750ff9537982e01733c1c94a58e35400661f57042158ff5e8f3e90cf936daf0fc + languageName: node + linkType: hard + +"is-inside-container@npm:^1.0.0": + version: 1.0.0 + resolution: "is-inside-container@npm:1.0.0" + dependencies: + is-docker: "npm:^3.0.0" + bin: + is-inside-container: cli.js + checksum: a8efb0e84f6197e6ff5c64c52890fa9acb49b7b74fed4da7c95383965da6f0fa592b4dbd5e38a79f87fc108196937acdbcd758fcefc9b140e479b39ce1fcd1cd + languageName: node + linkType: hard + +"is-interactive@npm:^2.0.0": + version: 2.0.0 + resolution: "is-interactive@npm:2.0.0" + checksum: 801c8f6064f85199dc6bf99b5dd98db3282e930c3bc197b32f2c5b89313bb578a07d1b8a01365c4348c2927229234f3681eb861b9c2c92bee72ff397390fa600 + languageName: node + linkType: hard + +"is-potential-custom-element-name@npm:^1.0.1": + version: 1.0.1 + resolution: "is-potential-custom-element-name@npm:1.0.1" + checksum: b73e2f22bc863b0939941d369486d308b43d7aef1f9439705e3582bfccaa4516406865e32c968a35f97a99396dac84e2624e67b0a16b0a15086a785e16ce7db9 + languageName: node + linkType: hard + +"is-unicode-supported@npm:^1.3.0": + version: 1.3.0 + resolution: "is-unicode-supported@npm:1.3.0" + checksum: b8674ea95d869f6faabddc6a484767207058b91aea0250803cbf1221345cb0c56f466d4ecea375dc77f6633d248d33c47bd296fb8f4cdba0b4edba8917e83d8a + languageName: node + linkType: hard + +"is-unicode-supported@npm:^2.0.0": + version: 2.1.0 + resolution: "is-unicode-supported@npm:2.1.0" + checksum: a0f53e9a7c1fdbcf2d2ef6e40d4736fdffff1c9f8944c75e15425118ff3610172c87bf7bc6c34d3903b04be59790bb2212ddbe21ee65b5a97030fc50370545a5 + languageName: node + linkType: hard + +"is-wsl@npm:^3.1.0": + version: 3.1.0 + resolution: "is-wsl@npm:3.1.0" + dependencies: + is-inside-container: "npm:^1.0.0" + checksum: d3317c11995690a32c362100225e22ba793678fe8732660c6de511ae71a0ff05b06980cf21f98a6bf40d7be0e9e9506f859abe00a1118287d63e53d0a3d06947 + languageName: node + linkType: hard + +"isexe@npm:^2.0.0": + version: 2.0.0 + resolution: "isexe@npm:2.0.0" + checksum: 228cfa503fadc2c31596ab06ed6aa82c9976eec2bfd83397e7eaf06d0ccf42cd1dfd6743bf9aeb01aebd4156d009994c5f76ea898d2832c1fe342da923ca457d + languageName: node + linkType: hard + +"isexe@npm:^3.1.1": + version: 3.1.1 + resolution: "isexe@npm:3.1.1" + checksum: 9ec257654093443eb0a528a9c8cbba9c0ca7616ccb40abd6dde7202734d96bb86e4ac0d764f0f8cd965856aacbff2f4ce23e730dc19dfb41e3b0d865ca6fdcc7 + languageName: node + linkType: hard + +"istanbul-lib-coverage@npm:^3.0.0, istanbul-lib-coverage@npm:^3.2.2": + version: 3.2.2 + resolution: "istanbul-lib-coverage@npm:3.2.2" + checksum: 6c7ff2106769e5f592ded1fb418f9f73b4411fd5a084387a5410538332b6567cd1763ff6b6cadca9b9eb2c443cce2f7ea7d7f1b8d315f9ce58539793b1e0922b + languageName: node + linkType: hard + +"istanbul-lib-report@npm:^3.0.0, istanbul-lib-report@npm:^3.0.1": + version: 3.0.1 + resolution: "istanbul-lib-report@npm:3.0.1" + dependencies: + istanbul-lib-coverage: "npm:^3.0.0" + make-dir: "npm:^4.0.0" + supports-color: "npm:^7.1.0" + checksum: 84323afb14392de8b6a5714bd7e9af845cfbd56cfe71ed276cda2f5f1201aea673c7111901227ee33e68e4364e288d73861eb2ed48f6679d1e69a43b6d9b3ba7 + languageName: node + linkType: hard + +"istanbul-lib-source-maps@npm:^5.0.6": + version: 5.0.6 + resolution: "istanbul-lib-source-maps@npm:5.0.6" + dependencies: + "@jridgewell/trace-mapping": "npm:^0.3.23" + debug: "npm:^4.1.1" + istanbul-lib-coverage: "npm:^3.0.0" + checksum: ffe75d70b303a3621ee4671554f306e0831b16f39ab7f4ab52e54d356a5d33e534d97563e318f1333a6aae1d42f91ec49c76b6cd3f3fb378addcb5c81da0255f + languageName: node + linkType: hard + +"istanbul-reports@npm:^3.1.7": + version: 3.1.7 + resolution: "istanbul-reports@npm:3.1.7" + dependencies: + html-escaper: "npm:^2.0.0" + istanbul-lib-report: "npm:^3.0.0" + checksum: a379fadf9cf8dc5dfe25568115721d4a7eb82fbd50b005a6672aff9c6989b20cc9312d7865814e0859cd8df58cbf664482e1d3604be0afde1f7fc3ccc1394a51 + languageName: node + linkType: hard + +"jackspeak@npm:^3.1.2": + version: 3.4.3 + resolution: "jackspeak@npm:3.4.3" + dependencies: + "@isaacs/cliui": "npm:^8.0.2" + "@pkgjs/parseargs": "npm:^0.11.0" + dependenciesMeta: + "@pkgjs/parseargs": + optional: true + checksum: 6acc10d139eaefdbe04d2f679e6191b3abf073f111edf10b1de5302c97ec93fffeb2fdd8681ed17f16268aa9dd4f8c588ed9d1d3bffbbfa6e8bf897cbb3149b9 + languageName: node + linkType: hard + +"jsbn@npm:1.1.0": + version: 1.1.0 + resolution: "jsbn@npm:1.1.0" + checksum: 4f907fb78d7b712e11dea8c165fe0921f81a657d3443dde75359ed52eb2b5d33ce6773d97985a089f09a65edd80b11cb75c767b57ba47391fee4c969f7215c96 + languageName: node + linkType: hard + +"jsdom@npm:^24.1.0": + version: 24.1.3 + resolution: "jsdom@npm:24.1.3" + dependencies: + cssstyle: "npm:^4.0.1" + data-urls: "npm:^5.0.0" + decimal.js: "npm:^10.4.3" + form-data: "npm:^4.0.0" + html-encoding-sniffer: "npm:^4.0.0" + http-proxy-agent: "npm:^7.0.2" + https-proxy-agent: "npm:^7.0.5" + is-potential-custom-element-name: "npm:^1.0.1" + nwsapi: "npm:^2.2.12" + parse5: "npm:^7.1.2" + rrweb-cssom: "npm:^0.7.1" + saxes: "npm:^6.0.0" + symbol-tree: "npm:^3.2.4" + tough-cookie: "npm:^4.1.4" + w3c-xmlserializer: "npm:^5.0.0" + webidl-conversions: "npm:^7.0.0" + whatwg-encoding: "npm:^3.1.1" + whatwg-mimetype: "npm:^4.0.0" + whatwg-url: "npm:^14.0.0" + ws: "npm:^8.18.0" + xml-name-validator: "npm:^5.0.0" + peerDependencies: + canvas: ^2.11.2 + peerDependenciesMeta: + canvas: + optional: true + checksum: e48b342afacd7418a23dac204a62deea729c50f4d072a7c04c09fd32355fdb4335f8779fa79fd0277a2dbeb2d356250a950955719d00047324b251233b11277f + languageName: node + linkType: hard + +"kleur@npm:^3.0.3": + version: 3.0.3 + resolution: "kleur@npm:3.0.3" + checksum: cd3a0b8878e7d6d3799e54340efe3591ca787d9f95f109f28129bdd2915e37807bf8918bb295ab86afb8c82196beec5a1adcaf29042ce3f2bd932b038fe3aa4b + languageName: node + linkType: hard + +"log-symbols@npm:^6.0.0": + version: 6.0.0 + resolution: "log-symbols@npm:6.0.0" + dependencies: + chalk: "npm:^5.3.0" + is-unicode-supported: "npm:^1.3.0" + checksum: 36636cacedba8f067d2deb4aad44e91a89d9efb3ead27e1846e7b82c9a10ea2e3a7bd6ce28a7ca616bebc60954ff25c67b0f92d20a6a746bb3cc52c3701891f6 + languageName: node + linkType: hard + +"loupe@npm:^3.1.0, loupe@npm:^3.1.3": + version: 3.1.3 + resolution: "loupe@npm:3.1.3" + checksum: f5dab4144254677de83a35285be1b8aba58b3861439ce4ba65875d0d5f3445a4a496daef63100ccf02b2dbc25bf58c6db84c9cb0b96d6435331e9d0a33b48541 + languageName: node + linkType: hard + +"lru-cache@npm:^10.0.1, lru-cache@npm:^10.2.0, lru-cache@npm:^10.4.3": + version: 10.4.3 + resolution: "lru-cache@npm:10.4.3" + checksum: ebd04fbca961e6c1d6c0af3799adcc966a1babe798f685bb84e6599266599cd95d94630b10262f5424539bc4640107e8a33aa28585374abf561d30d16f4b39fb + languageName: node + linkType: hard + +"magic-string@npm:^0.30.17": + version: 0.30.17 + resolution: "magic-string@npm:0.30.17" + dependencies: + "@jridgewell/sourcemap-codec": "npm:^1.5.0" + checksum: 16826e415d04b88378f200fe022b53e638e3838b9e496edda6c0e086d7753a44a6ed187adc72d19f3623810589bf139af1a315541cd6a26ae0771a0193eaf7b8 + languageName: node + linkType: hard + +"magicast@npm:^0.3.5": + version: 0.3.5 + resolution: "magicast@npm:0.3.5" + dependencies: + "@babel/parser": "npm:^7.25.4" + "@babel/types": "npm:^7.25.4" + source-map-js: "npm:^1.2.0" + checksum: a6cacc0a848af84f03e3f5bda7b0de75e4d0aa9ddce5517fd23ed0f31b5ddd51b2d0ff0b7e09b51f7de0f4053c7a1107117edda6b0732dca3e9e39e6c5a68c64 + languageName: node + linkType: hard + +"make-dir@npm:^4.0.0": + version: 4.0.0 + resolution: "make-dir@npm:4.0.0" + dependencies: + semver: "npm:^7.5.3" + checksum: 69b98a6c0b8e5c4fe9acb61608a9fbcfca1756d910f51e5dbe7a9e5cfb74fca9b8a0c8a0ffdf1294a740826c1ab4871d5bf3f62f72a3049e5eac6541ddffed68 + languageName: node + linkType: hard + +"make-fetch-happen@npm:^14.0.3": + version: 14.0.3 + resolution: "make-fetch-happen@npm:14.0.3" + dependencies: + "@npmcli/agent": "npm:^3.0.0" + cacache: "npm:^19.0.1" + http-cache-semantics: "npm:^4.1.1" + minipass: "npm:^7.0.2" + minipass-fetch: "npm:^4.0.0" + minipass-flush: "npm:^1.0.5" + minipass-pipeline: "npm:^1.2.4" + negotiator: "npm:^1.0.0" + proc-log: "npm:^5.0.0" + promise-retry: "npm:^2.0.1" + ssri: "npm:^12.0.0" + checksum: c40efb5e5296e7feb8e37155bde8eb70bc57d731b1f7d90e35a092fde403d7697c56fb49334d92d330d6f1ca29a98142036d6480a12681133a0a1453164cb2f0 + languageName: node + linkType: hard + +"math-intrinsics@npm:^1.1.0": + version: 1.1.0 + resolution: "math-intrinsics@npm:1.1.0" + checksum: 7579ff94e899e2f76ab64491d76cf606274c874d8f2af4a442c016bd85688927fcfca157ba6bf74b08e9439dc010b248ce05b96cc7c126a354c3bae7fcb48b7f + languageName: node + linkType: hard + +"media-typer@npm:0.3.0": + version: 0.3.0 + resolution: "media-typer@npm:0.3.0" + checksum: d160f31246907e79fed398470285f21bafb45a62869dc469b1c8877f3f064f5eabc4bcc122f9479b8b605bc5c76187d7871cf84c4ee3ecd3e487da1993279928 + languageName: node + linkType: hard + +"merge-descriptors@npm:1.0.3": + version: 1.0.3 + resolution: "merge-descriptors@npm:1.0.3" + checksum: 866b7094afd9293b5ea5dcd82d71f80e51514bed33b4c4e9f516795dc366612a4cbb4dc94356e943a8a6914889a914530badff27f397191b9b75cda20b6bae93 + languageName: node + linkType: hard + +"methods@npm:~1.1.2": + version: 1.1.2 + resolution: "methods@npm:1.1.2" + checksum: bdf7cc72ff0a33e3eede03708c08983c4d7a173f91348b4b1e4f47d4cdbf734433ad971e7d1e8c77247d9e5cd8adb81ea4c67b0a2db526b758b2233d7814b8b2 + languageName: node + linkType: hard + +"mime-db@npm:1.52.0": + version: 1.52.0 + resolution: "mime-db@npm:1.52.0" + checksum: 0557a01deebf45ac5f5777fe7740b2a5c309c6d62d40ceab4e23da9f821899ce7a900b7ac8157d4548ddbb7beffe9abc621250e6d182b0397ec7f10c7b91a5aa + languageName: node + linkType: hard + +"mime-types@npm:^2.1.12, mime-types@npm:~2.1.24, mime-types@npm:~2.1.34": + version: 2.1.35 + resolution: "mime-types@npm:2.1.35" + dependencies: + mime-db: "npm:1.52.0" + checksum: 82fb07ec56d8ff1fc999a84f2f217aa46cb6ed1033fefaabd5785b9a974ed225c90dc72fff460259e66b95b73648596dbcc50d51ed69cdf464af2d237d3149b2 + languageName: node + linkType: hard + +"mime@npm:1.6.0": + version: 1.6.0 + resolution: "mime@npm:1.6.0" + bin: + mime: cli.js + checksum: b92cd0adc44888c7135a185bfd0dddc42c32606401c72896a842ae15da71eb88858f17669af41e498b463cd7eb998f7b48939a25b08374c7924a9c8a6f8a81b0 + languageName: node + linkType: hard + +"mimic-function@npm:^5.0.0": + version: 5.0.1 + resolution: "mimic-function@npm:5.0.1" + checksum: f3d9464dd1816ecf6bdf2aec6ba32c0728022039d992f178237d8e289b48764fee4131319e72eedd4f7f094e22ded0af836c3187a7edc4595d28dd74368fd81d + languageName: node + linkType: hard + +"minimatch@npm:^9.0.4": + version: 9.0.5 + resolution: "minimatch@npm:9.0.5" + dependencies: + brace-expansion: "npm:^2.0.1" + checksum: de96cf5e35bdf0eab3e2c853522f98ffbe9a36c37797778d2665231ec1f20a9447a7e567cb640901f89e4daaa95ae5d70c65a9e8aa2bb0019b6facbc3c0575ed + languageName: node + linkType: hard + +"minipass-collect@npm:^2.0.1": + version: 2.0.1 + resolution: "minipass-collect@npm:2.0.1" + dependencies: + minipass: "npm:^7.0.3" + checksum: 5167e73f62bb74cc5019594709c77e6a742051a647fe9499abf03c71dca75515b7959d67a764bdc4f8b361cf897fbf25e2d9869ee039203ed45240f48b9aa06e + languageName: node + linkType: hard + +"minipass-fetch@npm:^4.0.0": + version: 4.0.1 + resolution: "minipass-fetch@npm:4.0.1" + dependencies: + encoding: "npm:^0.1.13" + minipass: "npm:^7.0.3" + minipass-sized: "npm:^1.0.3" + minizlib: "npm:^3.0.1" + dependenciesMeta: + encoding: + optional: true + checksum: a3147b2efe8e078c9bf9d024a0059339c5a09c5b1dded6900a219c218cc8b1b78510b62dae556b507304af226b18c3f1aeb1d48660283602d5b6586c399eed5c + languageName: node + linkType: hard + +"minipass-flush@npm:^1.0.5": + version: 1.0.5 + resolution: "minipass-flush@npm:1.0.5" + dependencies: + minipass: "npm:^3.0.0" + checksum: 2a51b63feb799d2bb34669205eee7c0eaf9dce01883261a5b77410c9408aa447e478efd191b4de6fc1101e796ff5892f8443ef20d9544385819093dbb32d36bd + languageName: node + linkType: hard + +"minipass-pipeline@npm:^1.2.4": + version: 1.2.4 + resolution: "minipass-pipeline@npm:1.2.4" + dependencies: + minipass: "npm:^3.0.0" + checksum: cbda57cea20b140b797505dc2cac71581a70b3247b84480c1fed5ca5ba46c25ecc25f68bfc9e6dcb1a6e9017dab5c7ada5eab73ad4f0a49d84e35093e0c643f2 + languageName: node + linkType: hard + +"minipass-sized@npm:^1.0.3": + version: 1.0.3 + resolution: "minipass-sized@npm:1.0.3" + dependencies: + minipass: "npm:^3.0.0" + checksum: 298f124753efdc745cfe0f2bdfdd81ba25b9f4e753ca4a2066eb17c821f25d48acea607dfc997633ee5bf7b6dfffb4eee4f2051eb168663f0b99fad2fa4829cb + languageName: node + linkType: hard + +"minipass@npm:^3.0.0": + version: 3.3.6 + resolution: "minipass@npm:3.3.6" + dependencies: + yallist: "npm:^4.0.0" + checksum: a114746943afa1dbbca8249e706d1d38b85ed1298b530f5808ce51f8e9e941962e2a5ad2e00eae7dd21d8a4aae6586a66d4216d1a259385e9d0358f0c1eba16c + languageName: node + linkType: hard + +"minipass@npm:^5.0.0 || ^6.0.2 || ^7.0.0, minipass@npm:^7.0.2, minipass@npm:^7.0.3, minipass@npm:^7.0.4, minipass@npm:^7.1.2": + version: 7.1.2 + resolution: "minipass@npm:7.1.2" + checksum: b0fd20bb9fb56e5fa9a8bfac539e8915ae07430a619e4b86ff71f5fc757ef3924b23b2c4230393af1eda647ed3d75739e4e0acb250a6b1eb277cf7f8fe449557 + languageName: node + linkType: hard + +"minizlib@npm:^3.0.1": + version: 3.0.1 + resolution: "minizlib@npm:3.0.1" + dependencies: + minipass: "npm:^7.0.4" + rimraf: "npm:^5.0.5" + checksum: 82f8bf70da8af656909a8ee299d7ed3b3372636749d29e105f97f20e88971be31f5ed7642f2e898f00283b68b701cc01307401cdc209b0efc5dd3818220e5093 + languageName: node + linkType: hard + +"mkdirp@npm:^3.0.1": + version: 3.0.1 + resolution: "mkdirp@npm:3.0.1" + bin: + mkdirp: dist/cjs/src/bin.js + checksum: 9f2b975e9246351f5e3a40dcfac99fcd0baa31fbfab615fe059fb11e51f10e4803c63de1f384c54d656e4db31d000e4767e9ef076a22e12a641357602e31d57d + languageName: node + linkType: hard + +"mrmime@npm:^2.0.0": + version: 2.0.1 + resolution: "mrmime@npm:2.0.1" + checksum: af05afd95af202fdd620422f976ad67dc18e6ee29beb03dd1ce950ea6ef664de378e44197246df4c7cdd73d47f2e7143a6e26e473084b9e4aa2095c0ad1e1761 + languageName: node + linkType: hard + +"ms@npm:2.0.0": + version: 2.0.0 + resolution: "ms@npm:2.0.0" + checksum: f8fda810b39fd7255bbdc451c46286e549794fcc700dc9cd1d25658bbc4dc2563a5de6fe7c60f798a16a60c6ceb53f033cb353f493f0cf63e5199b702943159d + languageName: node + linkType: hard + +"ms@npm:2.1.3, ms@npm:^2.1.3": + version: 2.1.3 + resolution: "ms@npm:2.1.3" + checksum: d924b57e7312b3b63ad21fc5b3dc0af5e78d61a1fc7cfb5457edaf26326bf62be5307cc87ffb6862ef1c2b33b0233cdb5d4f01c4c958cc0d660948b65a287a48 + languageName: node + linkType: hard + +"nanoid@npm:^3.3.8": + version: 3.3.8 + resolution: "nanoid@npm:3.3.8" + bin: + nanoid: bin/nanoid.cjs + checksum: 4b1bb29f6cfebf3be3bc4ad1f1296fb0a10a3043a79f34fbffe75d1621b4318319211cd420549459018ea3592f0d2f159247a6f874911d6d26eaaadda2478120 + languageName: node + linkType: hard + +"negotiator@npm:0.6.3": + version: 0.6.3 + resolution: "negotiator@npm:0.6.3" + checksum: 3ec9fd413e7bf071c937ae60d572bc67155262068ed522cf4b3be5edbe6ddf67d095ec03a3a14ebf8fc8e95f8e1d61be4869db0dbb0de696f6b837358bd43fc2 + languageName: node + linkType: hard + +"negotiator@npm:^1.0.0": + version: 1.0.0 + resolution: "negotiator@npm:1.0.0" + checksum: 4c559dd52669ea48e1914f9d634227c561221dd54734070791f999c52ed0ff36e437b2e07d5c1f6e32909fc625fe46491c16e4a8f0572567d4dd15c3a4fda04b + languageName: node + linkType: hard + +"node-gyp@npm:latest": + version: 11.1.0 + resolution: "node-gyp@npm:11.1.0" + dependencies: + env-paths: "npm:^2.2.0" + exponential-backoff: "npm:^3.1.1" + glob: "npm:^10.3.10" + graceful-fs: "npm:^4.2.6" + make-fetch-happen: "npm:^14.0.3" + nopt: "npm:^8.0.0" + proc-log: "npm:^5.0.0" + semver: "npm:^7.3.5" + tar: "npm:^7.4.3" + which: "npm:^5.0.0" + bin: + node-gyp: bin/node-gyp.js + checksum: c38977ce502f1ea41ba2b8721bd5b49bc3d5b3f813eabfac8414082faf0620ccb5211e15c4daecc23ed9f5e3e9cc4da00e575a0bcfc2a95a069294f2afa1e0cd + languageName: node + linkType: hard + +"nopt@npm:^8.0.0": + version: 8.1.0 + resolution: "nopt@npm:8.1.0" + dependencies: + abbrev: "npm:^3.0.0" + bin: + nopt: bin/nopt.js + checksum: 62e9ea70c7a3eb91d162d2c706b6606c041e4e7b547cbbb48f8b3695af457dd6479904d7ace600856bf923dd8d1ed0696f06195c8c20f02ac87c1da0e1d315ef + languageName: node + linkType: hard + +"nwsapi@npm:^2.2.12": + version: 2.2.18 + resolution: "nwsapi@npm:2.2.18" + checksum: fb64761f02d838a1964ef3f15f324779ae5b735c878843ed6592b07d85652928f8f34458605fee0ff379514bf5ffa5afeef5dc8290bfb0959a854069e2af300b + languageName: node + linkType: hard + +"object-inspect@npm:^1.13.3": + version: 1.13.4 + resolution: "object-inspect@npm:1.13.4" + checksum: d7f8711e803b96ea3191c745d6f8056ce1f2496e530e6a19a0e92d89b0fa3c76d910c31f0aa270432db6bd3b2f85500a376a83aaba849a8d518c8845b3211692 + languageName: node + linkType: hard + +"on-finished@npm:2.4.1": + version: 2.4.1 + resolution: "on-finished@npm:2.4.1" + dependencies: + ee-first: "npm:1.1.1" + checksum: 46fb11b9063782f2d9968863d9cbba33d77aa13c17f895f56129c274318b86500b22af3a160fe9995aa41317efcd22941b6eba747f718ced08d9a73afdb087b4 + languageName: node + linkType: hard + +"onetime@npm:^7.0.0": + version: 7.0.0 + resolution: "onetime@npm:7.0.0" + dependencies: + mimic-function: "npm:^5.0.0" + checksum: 5cb9179d74b63f52a196a2e7037ba2b9a893245a5532d3f44360012005c9cadb60851d56716ebff18a6f47129dab7168022445df47c2aff3b276d92585ed1221 + languageName: node + linkType: hard + +"open@npm:^10.1.0": + version: 10.1.0 + resolution: "open@npm:10.1.0" + dependencies: + default-browser: "npm:^5.2.1" + define-lazy-prop: "npm:^3.0.0" + is-inside-container: "npm:^1.0.0" + is-wsl: "npm:^3.1.0" + checksum: c86d0b94503d5f735f674158d5c5d339c25ec2927562f00ee74590727292ed23e1b8d9336cb41ffa7e1fa4d3641d29b199b4ea37c78cb557d72b511743e90ebb + languageName: node + linkType: hard + +"ora@npm:^8.1.0": + version: 8.2.0 + resolution: "ora@npm:8.2.0" + dependencies: + chalk: "npm:^5.3.0" + cli-cursor: "npm:^5.0.0" + cli-spinners: "npm:^2.9.2" + is-interactive: "npm:^2.0.0" + is-unicode-supported: "npm:^2.0.0" + log-symbols: "npm:^6.0.0" + stdin-discarder: "npm:^0.2.2" + string-width: "npm:^7.2.0" + strip-ansi: "npm:^7.1.0" + checksum: 7d9291255db22e293ea164f520b6042a3e906576ab06c9cf408bf9ef5664ba0a9f3bd258baa4ada058cfcc2163ef9b6696d51237a866682ce33295349ba02c3a + languageName: node + linkType: hard + +"p-map@npm:^7.0.2": + version: 7.0.3 + resolution: "p-map@npm:7.0.3" + checksum: 46091610da2b38ce47bcd1d8b4835a6fa4e832848a6682cf1652bc93915770f4617afc844c10a77d1b3e56d2472bb2d5622353fa3ead01a7f42b04fc8e744a5c + languageName: node + linkType: hard + +"package-json-from-dist@npm:^1.0.0": + version: 1.0.1 + resolution: "package-json-from-dist@npm:1.0.1" + checksum: 62ba2785eb655fec084a257af34dbe24292ab74516d6aecef97ef72d4897310bc6898f6c85b5cd22770eaa1ce60d55a0230e150fb6a966e3ecd6c511e23d164b + languageName: node + linkType: hard + +"parse5@npm:^7.1.2": + version: 7.2.1 + resolution: "parse5@npm:7.2.1" + dependencies: + entities: "npm:^4.5.0" + checksum: 829d37a0c709215a887e410a7118d754f8e1afd7edb529db95bc7bbf8045fb0266a7b67801331d8e8d9d073ea75793624ec27ce9ff3b96862c3b9008f4d68e80 + languageName: node + linkType: hard + +"parseurl@npm:~1.3.3": + version: 1.3.3 + resolution: "parseurl@npm:1.3.3" + checksum: 90dd4760d6f6174adb9f20cf0965ae12e23879b5f5464f38e92fce8073354341e4b3b76fa3d878351efe7d01e617121955284cfd002ab087fba1a0726ec0b4f5 + languageName: node + linkType: hard + +"path-key@npm:^3.1.0": + version: 3.1.1 + resolution: "path-key@npm:3.1.1" + checksum: 748c43efd5a569c039d7a00a03b58eecd1d75f3999f5a28303d75f521288df4823bc057d8784eb72358b2895a05f29a070bc9f1f17d28226cc4e62494cc58c4c + languageName: node + linkType: hard + +"path-scurry@npm:^1.11.1": + version: 1.11.1 + resolution: "path-scurry@npm:1.11.1" + dependencies: + lru-cache: "npm:^10.2.0" + minipass: "npm:^5.0.0 || ^6.0.2 || ^7.0.0" + checksum: 32a13711a2a505616ae1cc1b5076801e453e7aae6ac40ab55b388bb91b9d0547a52f5aaceff710ea400205f18691120d4431e520afbe4266b836fadede15872d + languageName: node + linkType: hard + +"path-to-regexp@npm:0.1.12": + version: 0.1.12 + resolution: "path-to-regexp@npm:0.1.12" + checksum: 1c6ff10ca169b773f3bba943bbc6a07182e332464704572962d277b900aeee81ac6aa5d060ff9e01149636c30b1f63af6e69dd7786ba6e0ddb39d4dee1f0645b + languageName: node + linkType: hard + +"pathe@npm:^2.0.3": + version: 2.0.3 + resolution: "pathe@npm:2.0.3" + checksum: c118dc5a8b5c4166011b2b70608762e260085180bb9e33e80a50dcdb1e78c010b1624f4280c492c92b05fc276715a4c357d1f9edc570f8f1b3d90b6839ebaca1 + languageName: node + linkType: hard + +"pathval@npm:^2.0.0": + version: 2.0.0 + resolution: "pathval@npm:2.0.0" + checksum: 602e4ee347fba8a599115af2ccd8179836a63c925c23e04bd056d0674a64b39e3a081b643cc7bc0b84390517df2d800a46fcc5598d42c155fe4977095c2f77c5 + languageName: node + linkType: hard + +"picocolors@npm:^1.1.0, picocolors@npm:^1.1.1": + version: 1.1.1 + resolution: "picocolors@npm:1.1.1" + checksum: e2e3e8170ab9d7c7421969adaa7e1b31434f789afb9b3f115f6b96d91945041ac3ceb02e9ec6fe6510ff036bcc0bf91e69a1772edc0b707e12b19c0f2d6bcf58 + languageName: node + linkType: hard + +"picomatch@npm:^4.0.2": + version: 4.0.2 + resolution: "picomatch@npm:4.0.2" + checksum: 7c51f3ad2bb42c776f49ebf964c644958158be30d0a510efd5a395e8d49cb5acfed5b82c0c5b365523ce18e6ab85013c9ebe574f60305892ec3fa8eee8304ccc + languageName: node + linkType: hard + +"postcss@npm:^8.5.3": + version: 8.5.3 + resolution: "postcss@npm:8.5.3" + dependencies: + nanoid: "npm:^3.3.8" + picocolors: "npm:^1.1.1" + source-map-js: "npm:^1.2.1" + checksum: b75510d7b28c3ab728c8733dd01538314a18c52af426f199a3c9177e63eb08602a3938bfb66b62dc01350b9aed62087eabbf229af97a1659eb8d3513cec823b3 + languageName: node + linkType: hard + +"proc-log@npm:^5.0.0": + version: 5.0.0 + resolution: "proc-log@npm:5.0.0" + checksum: bbe5edb944b0ad63387a1d5b1911ae93e05ce8d0f60de1035b218cdcceedfe39dbd2c697853355b70f1a090f8f58fe90da487c85216bf9671f9499d1a897e9e3 + languageName: node + linkType: hard + +"promise-retry@npm:^2.0.1": + version: 2.0.1 + resolution: "promise-retry@npm:2.0.1" + dependencies: + err-code: "npm:^2.0.2" + retry: "npm:^0.12.0" + checksum: 9c7045a1a2928094b5b9b15336dcd2a7b1c052f674550df63cc3f36cd44028e5080448175b6f6ca32b642de81150f5e7b1a98b728f15cb069f2dd60ac2616b96 + languageName: node + linkType: hard + +"prompts@npm:^2.4.2": + version: 2.4.2 + resolution: "prompts@npm:2.4.2" + dependencies: + kleur: "npm:^3.0.3" + sisteransi: "npm:^1.0.5" + checksum: 16f1ac2977b19fe2cf53f8411cc98db7a3c8b115c479b2ca5c82b5527cd937aa405fa04f9a5960abeb9daef53191b53b4d13e35c1f5d50e8718c76917c5f1ea4 + languageName: node + linkType: hard + +"proxy-addr@npm:~2.0.7": + version: 2.0.7 + resolution: "proxy-addr@npm:2.0.7" + dependencies: + forwarded: "npm:0.2.0" + ipaddr.js: "npm:1.9.1" + checksum: c3eed999781a35f7fd935f398b6d8920b6fb00bbc14287bc6de78128ccc1a02c89b95b56742bf7cf0362cc333c61d138532049c7dedc7a328ef13343eff81210 + languageName: node + linkType: hard + +"psl@npm:^1.1.33": + version: 1.15.0 + resolution: "psl@npm:1.15.0" + dependencies: + punycode: "npm:^2.3.1" + checksum: d8d45a99e4ca62ca12ac3c373e63d80d2368d38892daa40cfddaa1eb908be98cd549ac059783ef3a56cfd96d57ae8e2fd9ae53d1378d90d42bc661ff924e102a + languageName: node + linkType: hard + +"punycode@npm:^2.1.1, punycode@npm:^2.3.1": + version: 2.3.1 + resolution: "punycode@npm:2.3.1" + checksum: 14f76a8206bc3464f794fb2e3d3cc665ae416c01893ad7a02b23766eb07159144ee612ad67af5e84fa4479ccfe67678c4feb126b0485651b302babf66f04f9e9 + languageName: node + linkType: hard + +"qs@npm:6.13.0": + version: 6.13.0 + resolution: "qs@npm:6.13.0" + dependencies: + side-channel: "npm:^1.0.6" + checksum: 62372cdeec24dc83a9fb240b7533c0fdcf0c5f7e0b83343edd7310f0ab4c8205a5e7c56406531f2e47e1b4878a3821d652be4192c841de5b032ca83619d8f860 + languageName: node + linkType: hard + +"querystringify@npm:^2.1.1": + version: 2.2.0 + resolution: "querystringify@npm:2.2.0" + checksum: 3258bc3dbdf322ff2663619afe5947c7926a6ef5fb78ad7d384602974c467fadfc8272af44f5eb8cddd0d011aae8fabf3a929a8eee4b86edcc0a21e6bd10f9aa + languageName: node + linkType: hard + +"range-parser@npm:~1.2.1": + version: 1.2.1 + resolution: "range-parser@npm:1.2.1" + checksum: 96c032ac2475c8027b7a4e9fe22dc0dfe0f6d90b85e496e0f016fbdb99d6d066de0112e680805075bd989905e2123b3b3d002765149294dce0c1f7f01fcc2ea0 + languageName: node + linkType: hard + +"raw-body@npm:2.5.2": + version: 2.5.2 + resolution: "raw-body@npm:2.5.2" + dependencies: + bytes: "npm:3.1.2" + http-errors: "npm:2.0.0" + iconv-lite: "npm:0.4.24" + unpipe: "npm:1.0.0" + checksum: b201c4b66049369a60e766318caff5cb3cc5a900efd89bdac431463822d976ad0670912c931fdbdcf5543207daf6f6833bca57aa116e1661d2ea91e12ca692c4 + languageName: node + linkType: hard + +"requires-port@npm:^1.0.0": + version: 1.0.0 + resolution: "requires-port@npm:1.0.0" + checksum: b2bfdd09db16c082c4326e573a82c0771daaf7b53b9ce8ad60ea46aa6e30aaf475fe9b164800b89f93b748d2c234d8abff945d2551ba47bf5698e04cd7713267 + languageName: node + linkType: hard + +"restore-cursor@npm:^5.0.0": + version: 5.1.0 + resolution: "restore-cursor@npm:5.1.0" + dependencies: + onetime: "npm:^7.0.0" + signal-exit: "npm:^4.1.0" + checksum: c2ba89131eea791d1b25205bdfdc86699767e2b88dee2a590b1a6caa51737deac8bad0260a5ded2f7c074b7db2f3a626bcf1fcf3cdf35974cbeea5e2e6764f60 + languageName: node + linkType: hard + +"retry@npm:^0.12.0": + version: 0.12.0 + resolution: "retry@npm:0.12.0" + checksum: 59933e8501727ba13ad73ef4a04d5280b3717fd650408460c987392efe9d7be2040778ed8ebe933c5cbd63da3dcc37919c141ef8af0a54a6e4fca5a2af177bfe + languageName: node + linkType: hard + +"rimraf@npm:^5.0.5": + version: 5.0.10 + resolution: "rimraf@npm:5.0.10" + dependencies: + glob: "npm:^10.3.7" + bin: + rimraf: dist/esm/bin.mjs + checksum: 7da4fd0e15118ee05b918359462cfa1e7fe4b1228c7765195a45b55576e8c15b95db513b8466ec89129666f4af45ad978a3057a02139afba1a63512a2d9644cc + languageName: node + linkType: hard + +"rollup@npm:^4.30.1": + version: 4.34.9 + resolution: "rollup@npm:4.34.9" + dependencies: + "@rollup/rollup-android-arm-eabi": "npm:4.34.9" + "@rollup/rollup-android-arm64": "npm:4.34.9" + "@rollup/rollup-darwin-arm64": "npm:4.34.9" + "@rollup/rollup-darwin-x64": "npm:4.34.9" + "@rollup/rollup-freebsd-arm64": "npm:4.34.9" + "@rollup/rollup-freebsd-x64": "npm:4.34.9" + "@rollup/rollup-linux-arm-gnueabihf": "npm:4.34.9" + "@rollup/rollup-linux-arm-musleabihf": "npm:4.34.9" + "@rollup/rollup-linux-arm64-gnu": "npm:4.34.9" + "@rollup/rollup-linux-arm64-musl": "npm:4.34.9" + "@rollup/rollup-linux-loongarch64-gnu": "npm:4.34.9" + "@rollup/rollup-linux-powerpc64le-gnu": "npm:4.34.9" + "@rollup/rollup-linux-riscv64-gnu": "npm:4.34.9" + "@rollup/rollup-linux-s390x-gnu": "npm:4.34.9" + "@rollup/rollup-linux-x64-gnu": "npm:4.34.9" + "@rollup/rollup-linux-x64-musl": "npm:4.34.9" + "@rollup/rollup-win32-arm64-msvc": "npm:4.34.9" + "@rollup/rollup-win32-ia32-msvc": "npm:4.34.9" + "@rollup/rollup-win32-x64-msvc": "npm:4.34.9" + "@types/estree": "npm:1.0.6" + fsevents: "npm:~2.3.2" + dependenciesMeta: + "@rollup/rollup-android-arm-eabi": + optional: true + "@rollup/rollup-android-arm64": + optional: true + "@rollup/rollup-darwin-arm64": + optional: true + "@rollup/rollup-darwin-x64": + optional: true + "@rollup/rollup-freebsd-arm64": + optional: true + "@rollup/rollup-freebsd-x64": + optional: true + "@rollup/rollup-linux-arm-gnueabihf": + optional: true + "@rollup/rollup-linux-arm-musleabihf": + optional: true + "@rollup/rollup-linux-arm64-gnu": + optional: true + "@rollup/rollup-linux-arm64-musl": + optional: true + "@rollup/rollup-linux-loongarch64-gnu": + optional: true + "@rollup/rollup-linux-powerpc64le-gnu": + optional: true + "@rollup/rollup-linux-riscv64-gnu": + optional: true + "@rollup/rollup-linux-s390x-gnu": + optional: true + "@rollup/rollup-linux-x64-gnu": + optional: true + "@rollup/rollup-linux-x64-musl": + optional: true + "@rollup/rollup-win32-arm64-msvc": + optional: true + "@rollup/rollup-win32-ia32-msvc": + optional: true + "@rollup/rollup-win32-x64-msvc": + optional: true + fsevents: + optional: true + bin: + rollup: dist/bin/rollup + checksum: dd0be1f7c4f8a93040026be13ecc39259fb55313db0dac7eafd97a3ac01ab4584e6b1a8afd86b0259dcf391699d5560a678abe6c0729af0aa4f2d5df70f05c8c + languageName: node + linkType: hard + +"rrweb-cssom@npm:^0.7.1": + version: 0.7.1 + resolution: "rrweb-cssom@npm:0.7.1" + checksum: 127b8ca6c8aac45e2755abbae6138d4a813b1bedc2caabf79466ae83ab3cfc84b5bfab513b7033f0aa4561c7753edf787d0dd01163ceacdee2e8eb1b6bf7237e + languageName: node + linkType: hard + +"rrweb-cssom@npm:^0.8.0": + version: 0.8.0 + resolution: "rrweb-cssom@npm:0.8.0" + checksum: 56f2bfd56733adb92c0b56e274c43f864b8dd48784d6fe946ef5ff8d438234015e59ad837fc2ad54714b6421384141c1add4eb569e72054e350d1f8a50b8ac7b + languageName: node + linkType: hard + +"run-applescript@npm:^7.0.0": + version: 7.0.0 + resolution: "run-applescript@npm:7.0.0" + checksum: bd821bbf154b8e6c8ecffeaf0c33cebbb78eb2987476c3f6b420d67ab4c5301faa905dec99ded76ebb3a7042b4e440189ae6d85bbbd3fc6e8d493347ecda8bfe + languageName: node + linkType: hard + +"safe-buffer@npm:5.2.1": + version: 5.2.1 + resolution: "safe-buffer@npm:5.2.1" + checksum: 6501914237c0a86e9675d4e51d89ca3c21ffd6a31642efeba25ad65720bce6921c9e7e974e5be91a786b25aa058b5303285d3c15dbabf983a919f5f630d349f3 + languageName: node + linkType: hard + +"safer-buffer@npm:>= 2.1.2 < 3, safer-buffer@npm:>= 2.1.2 < 3.0.0": + version: 2.1.2 + resolution: "safer-buffer@npm:2.1.2" + checksum: 7e3c8b2e88a1841c9671094bbaeebd94448111dd90a81a1f606f3f67708a6ec57763b3b47f06da09fc6054193e0e6709e77325415dc8422b04497a8070fa02d4 + languageName: node + linkType: hard + +"saxes@npm:^6.0.0": + version: 6.0.0 + resolution: "saxes@npm:6.0.0" + dependencies: + xmlchars: "npm:^2.2.0" + checksum: 3847b839f060ef3476eb8623d099aa502ad658f5c40fd60c105ebce86d244389b0d76fcae30f4d0c728d7705ceb2f7e9b34bb54717b6a7dbedaf5dad2d9a4b74 + languageName: node + linkType: hard + +"semver@npm:^7.3.5, semver@npm:^7.5.3": + version: 7.7.1 + resolution: "semver@npm:7.7.1" + bin: + semver: bin/semver.js + checksum: fd603a6fb9c399c6054015433051bdbe7b99a940a8fb44b85c2b524c4004b023d7928d47cb22154f8d054ea7ee8597f586605e05b52047f048278e4ac56ae958 + languageName: node + linkType: hard + +"send@npm:0.19.0": + version: 0.19.0 + resolution: "send@npm:0.19.0" + dependencies: + debug: "npm:2.6.9" + depd: "npm:2.0.0" + destroy: "npm:1.2.0" + encodeurl: "npm:~1.0.2" + escape-html: "npm:~1.0.3" + etag: "npm:~1.8.1" + fresh: "npm:0.5.2" + http-errors: "npm:2.0.0" + mime: "npm:1.6.0" + ms: "npm:2.1.3" + on-finished: "npm:2.4.1" + range-parser: "npm:~1.2.1" + statuses: "npm:2.0.1" + checksum: ea3f8a67a8f0be3d6bf9080f0baed6d2c51d11d4f7b4470de96a5029c598a7011c497511ccc28968b70ef05508675cebff27da9151dd2ceadd60be4e6cf845e3 + languageName: node + linkType: hard + +"serve-static@npm:1.16.2": + version: 1.16.2 + resolution: "serve-static@npm:1.16.2" + dependencies: + encodeurl: "npm:~2.0.0" + escape-html: "npm:~1.0.3" + parseurl: "npm:~1.3.3" + send: "npm:0.19.0" + checksum: 528fff6f5e12d0c5a391229ad893910709bc51b5705962b09404a1d813857578149b8815f35d3ee5752f44cd378d0f31669d4b1d7e2d11f41e08283d5134bd1f + languageName: node + linkType: hard + +"setprototypeof@npm:1.2.0": + version: 1.2.0 + resolution: "setprototypeof@npm:1.2.0" + checksum: 68733173026766fa0d9ecaeb07f0483f4c2dc70ca376b3b7c40b7cda909f94b0918f6c5ad5ce27a9160bdfb475efaa9d5e705a11d8eaae18f9835d20976028bc + languageName: node + linkType: hard + +"shebang-command@npm:^2.0.0": + version: 2.0.0 + resolution: "shebang-command@npm:2.0.0" + dependencies: + shebang-regex: "npm:^3.0.0" + checksum: a41692e7d89a553ef21d324a5cceb5f686d1f3c040759c50aab69688634688c5c327f26f3ecf7001ebfd78c01f3c7c0a11a7c8bfd0a8bc9f6240d4f40b224e4e + languageName: node + linkType: hard + +"shebang-regex@npm:^3.0.0": + version: 3.0.0 + resolution: "shebang-regex@npm:3.0.0" + checksum: 1dbed0726dd0e1152a92696c76c7f06084eb32a90f0528d11acd764043aacf76994b2fb30aa1291a21bd019d6699164d048286309a278855ee7bec06cf6fb690 + languageName: node + linkType: hard + +"side-channel-list@npm:^1.0.0": + version: 1.0.0 + resolution: "side-channel-list@npm:1.0.0" + dependencies: + es-errors: "npm:^1.3.0" + object-inspect: "npm:^1.13.3" + checksum: 644f4ac893456c9490ff388bf78aea9d333d5e5bfc64cfb84be8f04bf31ddc111a8d4b83b85d7e7e8a7b845bc185a9ad02c052d20e086983cf59f0be517d9b3d + languageName: node + linkType: hard + +"side-channel-map@npm:^1.0.1": + version: 1.0.1 + resolution: "side-channel-map@npm:1.0.1" + dependencies: + call-bound: "npm:^1.0.2" + es-errors: "npm:^1.3.0" + get-intrinsic: "npm:^1.2.5" + object-inspect: "npm:^1.13.3" + checksum: 010584e6444dd8a20b85bc926d934424bd809e1a3af941cace229f7fdcb751aada0fb7164f60c2e22292b7fa3c0ff0bce237081fd4cdbc80de1dc68e95430672 + languageName: node + linkType: hard + +"side-channel-weakmap@npm:^1.0.2": + version: 1.0.2 + resolution: "side-channel-weakmap@npm:1.0.2" + dependencies: + call-bound: "npm:^1.0.2" + es-errors: "npm:^1.3.0" + get-intrinsic: "npm:^1.2.5" + object-inspect: "npm:^1.13.3" + side-channel-map: "npm:^1.0.1" + checksum: 71362709ac233e08807ccd980101c3e2d7efe849edc51455030327b059f6c4d292c237f94dc0685031dd11c07dd17a68afde235d6cf2102d949567f98ab58185 + languageName: node + linkType: hard + +"side-channel@npm:^1.0.6": + version: 1.1.0 + resolution: "side-channel@npm:1.1.0" + dependencies: + es-errors: "npm:^1.3.0" + object-inspect: "npm:^1.13.3" + side-channel-list: "npm:^1.0.0" + side-channel-map: "npm:^1.0.1" + side-channel-weakmap: "npm:^1.0.2" + checksum: cb20dad41eb032e6c24c0982e1e5a24963a28aa6122b4f05b3f3d6bf8ae7fd5474ef382c8f54a6a3ab86e0cac4d41a23bd64ede3970e5bfb50326ba02a7996e6 + languageName: node + linkType: hard + +"siginfo@npm:^2.0.0": + version: 2.0.0 + resolution: "siginfo@npm:2.0.0" + checksum: 3def8f8e516fbb34cb6ae415b07ccc5d9c018d85b4b8611e3dc6f8be6d1899f693a4382913c9ed51a06babb5201639d76453ab297d1c54a456544acf5c892e34 + languageName: node + linkType: hard + +"signal-exit@npm:^4.0.1, signal-exit@npm:^4.1.0": + version: 4.1.0 + resolution: "signal-exit@npm:4.1.0" + checksum: 41602dce540e46d599edba9d9860193398d135f7ff72cab629db5171516cfae628d21e7bfccde1bbfdf11c48726bc2a6d1a8fb8701125852fbfda7cf19c6aa83 + languageName: node + linkType: hard + +"sirv@npm:^3.0.1": + version: 3.0.1 + resolution: "sirv@npm:3.0.1" + dependencies: + "@polka/url": "npm:^1.0.0-next.24" + mrmime: "npm:^2.0.0" + totalist: "npm:^3.0.0" + checksum: 7cf64b28daa69b15f77b38b0efdd02c007b72bb3ec5f107b208ebf59f01b174ef63a1db3aca16d2df925501831f4c209be6ece3302b98765919ef5088b45bf80 + languageName: node + linkType: hard + +"sisteransi@npm:^1.0.5": + version: 1.0.5 + resolution: "sisteransi@npm:1.0.5" + checksum: 230ac975cca485b7f6fe2b96a711aa62a6a26ead3e6fb8ba17c5a00d61b8bed0d7adc21f5626b70d7c33c62ff4e63933017a6462942c719d1980bb0b1207ad46 + languageName: node + linkType: hard + +"smart-buffer@npm:^4.2.0": + version: 4.2.0 + resolution: "smart-buffer@npm:4.2.0" + checksum: a16775323e1404dd43fabafe7460be13a471e021637bc7889468eb45ce6a6b207261f454e4e530a19500cc962c4cc5348583520843b363f4193cee5c00e1e539 + languageName: node + linkType: hard + +"socks-proxy-agent@npm:^8.0.3": + version: 8.0.5 + resolution: "socks-proxy-agent@npm:8.0.5" + dependencies: + agent-base: "npm:^7.1.2" + debug: "npm:^4.3.4" + socks: "npm:^2.8.3" + checksum: 5d2c6cecba6821389aabf18728325730504bf9bb1d9e342e7987a5d13badd7a98838cc9a55b8ed3cb866ad37cc23e1086f09c4d72d93105ce9dfe76330e9d2a6 + languageName: node + linkType: hard + +"socks@npm:^2.8.3": + version: 2.8.4 + resolution: "socks@npm:2.8.4" + dependencies: + ip-address: "npm:^9.0.5" + smart-buffer: "npm:^4.2.0" + checksum: 00c3271e233ccf1fb83a3dd2060b94cc37817e0f797a93c560b9a7a86c4a0ec2961fb31263bdd24a3c28945e24868b5f063cd98744171d9e942c513454b50ae5 + languageName: node + linkType: hard + +"source-map-js@npm:^1.2.0, source-map-js@npm:^1.2.1": + version: 1.2.1 + resolution: "source-map-js@npm:1.2.1" + checksum: 7bda1fc4c197e3c6ff17de1b8b2c20e60af81b63a52cb32ec5a5d67a20a7d42651e2cb34ebe93833c5a2a084377e17455854fee3e21e7925c64a51b6a52b0faf + languageName: node + linkType: hard + +"sprintf-js@npm:^1.1.3": + version: 1.1.3 + resolution: "sprintf-js@npm:1.1.3" + checksum: 09270dc4f30d479e666aee820eacd9e464215cdff53848b443964202bf4051490538e5dd1b42e1a65cf7296916ca17640aebf63dae9812749c7542ee5f288dec + languageName: node + linkType: hard + +"ssri@npm:^12.0.0": + version: 12.0.0 + resolution: "ssri@npm:12.0.0" + dependencies: + minipass: "npm:^7.0.3" + checksum: caddd5f544b2006e88fa6b0124d8d7b28208b83c72d7672d5ade44d794525d23b540f3396108c4eb9280dcb7c01f0bef50682f5b4b2c34291f7c5e211fd1417d + languageName: node + linkType: hard + +"stackback@npm:0.0.2": + version: 0.0.2 + resolution: "stackback@npm:0.0.2" + checksum: 89a1416668f950236dd5ac9f9a6b2588e1b9b62b1b6ad8dff1bfc5d1a15dbf0aafc9b52d2226d00c28dffff212da464eaeebfc6b7578b9d180cef3e3782c5983 + languageName: node + linkType: hard + +"statuses@npm:2.0.1": + version: 2.0.1 + resolution: "statuses@npm:2.0.1" + checksum: 34378b207a1620a24804ce8b5d230fea0c279f00b18a7209646d5d47e419d1cc23e7cbf33a25a1e51ac38973dc2ac2e1e9c647a8e481ef365f77668d72becfd0 + languageName: node + linkType: hard + +"std-env@npm:^3.8.0": + version: 3.8.1 + resolution: "std-env@npm:3.8.1" + checksum: e9b19cca6bc6f06f91607db5b636662914ca8ec9efc525a99da6ec7e493afec109d3b017d21d9782b4369fcfb2891c7c4b4e3c60d495fdadf6861ce434e07bf8 + languageName: node + linkType: hard + +"stdin-discarder@npm:^0.2.2": + version: 0.2.2 + resolution: "stdin-discarder@npm:0.2.2" + checksum: c78375e82e956d7a64be6e63c809c7f058f5303efcaf62ea48350af072bacdb99c06cba39209b45a071c1acbd49116af30df1df9abb448df78a6005b72f10537 + languageName: node + linkType: hard + +"string-width-cjs@npm:string-width@^4.2.0, string-width@npm:^4.1.0": + version: 4.2.3 + resolution: "string-width@npm:4.2.3" + dependencies: + emoji-regex: "npm:^8.0.0" + is-fullwidth-code-point: "npm:^3.0.0" + strip-ansi: "npm:^6.0.1" + checksum: 1e525e92e5eae0afd7454086eed9c818ee84374bb80328fc41217ae72ff5f065ef1c9d7f72da41de40c75fa8bb3dee63d92373fd492c84260a552c636392a47b + languageName: node + linkType: hard + +"string-width@npm:^5.0.1, string-width@npm:^5.1.2": + version: 5.1.2 + resolution: "string-width@npm:5.1.2" + dependencies: + eastasianwidth: "npm:^0.2.0" + emoji-regex: "npm:^9.2.2" + strip-ansi: "npm:^7.0.1" + checksum: ab9c4264443d35b8b923cbdd513a089a60de339216d3b0ed3be3ba57d6880e1a192b70ae17225f764d7adbf5994e9bb8df253a944736c15a0240eff553c678ca + languageName: node + linkType: hard + +"string-width@npm:^7.2.0": + version: 7.2.0 + resolution: "string-width@npm:7.2.0" + dependencies: + emoji-regex: "npm:^10.3.0" + get-east-asian-width: "npm:^1.0.0" + strip-ansi: "npm:^7.1.0" + checksum: eb0430dd43f3199c7a46dcbf7a0b34539c76fe3aa62763d0b0655acdcbdf360b3f66f3d58ca25ba0205f42ea3491fa00f09426d3b7d3040e506878fc7664c9b9 + languageName: node + linkType: hard + +"strip-ansi-cjs@npm:strip-ansi@^6.0.1, strip-ansi@npm:^6.0.0, strip-ansi@npm:^6.0.1": + version: 6.0.1 + resolution: "strip-ansi@npm:6.0.1" + dependencies: + ansi-regex: "npm:^5.0.1" + checksum: 1ae5f212a126fe5b167707f716942490e3933085a5ff6c008ab97ab2f272c8025d3aa218b7bd6ab25729ca20cc81cddb252102f8751e13482a5199e873680952 + languageName: node + linkType: hard + +"strip-ansi@npm:^7.0.1, strip-ansi@npm:^7.1.0": + version: 7.1.0 + resolution: "strip-ansi@npm:7.1.0" + dependencies: + ansi-regex: "npm:^6.0.1" + checksum: a198c3762e8832505328cbf9e8c8381de14a4fa50a4f9b2160138158ea88c0f5549fb50cb13c651c3088f47e63a108b34622ec18c0499b6c8c3a5ddf6b305ac4 + languageName: node + linkType: hard + +"supports-color@npm:^7.1.0": + version: 7.2.0 + resolution: "supports-color@npm:7.2.0" + dependencies: + has-flag: "npm:^4.0.0" + checksum: afb4c88521b8b136b5f5f95160c98dee7243dc79d5432db7efc27efb219385bbc7d9427398e43dd6cc730a0f87d5085ce1652af7efbe391327bc0a7d0f7fc124 + languageName: node + linkType: hard + +"symbol-tree@npm:^3.2.4": + version: 3.2.4 + resolution: "symbol-tree@npm:3.2.4" + checksum: dfbe201ae09ac6053d163578778c53aa860a784147ecf95705de0cd23f42c851e1be7889241495e95c37cabb058edb1052f141387bef68f705afc8f9dd358509 + languageName: node + linkType: hard + +"tar@npm:^7.4.3": + version: 7.4.3 + resolution: "tar@npm:7.4.3" + dependencies: + "@isaacs/fs-minipass": "npm:^4.0.0" + chownr: "npm:^3.0.0" + minipass: "npm:^7.1.2" + minizlib: "npm:^3.0.1" + mkdirp: "npm:^3.0.1" + yallist: "npm:^5.0.0" + checksum: d4679609bb2a9b48eeaf84632b6d844128d2412b95b6de07d53d8ee8baf4ca0857c9331dfa510390a0727b550fd543d4d1a10995ad86cdf078423fbb8d99831d + languageName: node + linkType: hard + +"test-exclude@npm:^7.0.1": + version: 7.0.1 + resolution: "test-exclude@npm:7.0.1" + dependencies: + "@istanbuljs/schema": "npm:^0.1.2" + glob: "npm:^10.4.1" + minimatch: "npm:^9.0.4" + checksum: 6d67b9af4336a2e12b26a68c83308c7863534c65f27ed4ff7068a56f5a58f7ac703e8fc80f698a19bb154fd8f705cdf7ec347d9512b2c522c737269507e7b263 + languageName: node + linkType: hard + +"tinybench@npm:^2.9.0": + version: 2.9.0 + resolution: "tinybench@npm:2.9.0" + checksum: c3500b0f60d2eb8db65250afe750b66d51623057ee88720b7f064894a6cb7eb93360ca824a60a31ab16dab30c7b1f06efe0795b352e37914a9d4bad86386a20c + languageName: node + linkType: hard + +"tinyexec@npm:^0.3.2": + version: 0.3.2 + resolution: "tinyexec@npm:0.3.2" + checksum: 3efbf791a911be0bf0821eab37a3445c2ba07acc1522b1fa84ae1e55f10425076f1290f680286345ed919549ad67527d07281f1c19d584df3b74326909eb1f90 + languageName: node + linkType: hard + +"tinyglobby@npm:^0.2.12": + version: 0.2.12 + resolution: "tinyglobby@npm:0.2.12" + dependencies: + fdir: "npm:^6.4.3" + picomatch: "npm:^4.0.2" + checksum: 7c9be4fd3625630e262dcb19015302aad3b4ba7fc620f269313e688f2161ea8724d6cb4444baab5ef2826eb6bed72647b169a33ec8eea37501832a2526ff540f + languageName: node + linkType: hard + +"tinypool@npm:^1.0.2": + version: 1.0.2 + resolution: "tinypool@npm:1.0.2" + checksum: 31ac184c0ff1cf9a074741254fe9ea6de95026749eb2b8ec6fd2b9d8ca94abdccda731f8e102e7f32e72ed3b36d32c6975fd5f5523df3f1b6de6c3d8dfd95e63 + languageName: node + linkType: hard + +"tinyrainbow@npm:^2.0.0": + version: 2.0.0 + resolution: "tinyrainbow@npm:2.0.0" + checksum: c83c52bef4e0ae7fb8ec6a722f70b5b6fa8d8be1c85792e829f56c0e1be94ab70b293c032dc5048d4d37cfe678f1f5babb04bdc65fd123098800148ca989184f + languageName: node + linkType: hard + +"tinyspy@npm:^3.0.2": + version: 3.0.2 + resolution: "tinyspy@npm:3.0.2" + checksum: 55ffad24e346622b59292e097c2ee30a63919d5acb7ceca87fc0d1c223090089890587b426e20054733f97a58f20af2c349fb7cc193697203868ab7ba00bcea0 + languageName: node + linkType: hard + +"toidentifier@npm:1.0.1": + version: 1.0.1 + resolution: "toidentifier@npm:1.0.1" + checksum: 93937279934bd66cc3270016dd8d0afec14fb7c94a05c72dc57321f8bd1fa97e5bea6d1f7c89e728d077ca31ea125b78320a616a6c6cd0e6b9cb94cb864381c1 + languageName: node + linkType: hard + +"totalist@npm:^3.0.0": + version: 3.0.1 + resolution: "totalist@npm:3.0.1" + checksum: 4bb1fadb69c3edbef91c73ebef9d25b33bbf69afe1e37ce544d5f7d13854cda15e47132f3e0dc4cafe300ddb8578c77c50a65004d8b6e97e77934a69aa924863 + languageName: node + linkType: hard + +"tough-cookie@npm:^4.1.4": + version: 4.1.4 + resolution: "tough-cookie@npm:4.1.4" + dependencies: + psl: "npm:^1.1.33" + punycode: "npm:^2.1.1" + universalify: "npm:^0.2.0" + url-parse: "npm:^1.5.3" + checksum: aca7ff96054f367d53d1e813e62ceb7dd2eda25d7752058a74d64b7266fd07be75908f3753a32ccf866a2f997604b414cfb1916d6e7f69bc64d9d9939b0d6c45 + languageName: node + linkType: hard + +"tr46@npm:^5.0.0": + version: 5.0.0 + resolution: "tr46@npm:5.0.0" + dependencies: + punycode: "npm:^2.3.1" + checksum: 1521b6e7bbc8adc825c4561480f9fe48eb2276c81335eed9fa610aa4c44a48a3221f78b10e5f18b875769eb3413e30efbf209ed556a17a42aa8d690df44b7bee + languageName: node + linkType: hard + +"type-is@npm:~1.6.18": + version: 1.6.18 + resolution: "type-is@npm:1.6.18" + dependencies: + media-typer: "npm:0.3.0" + mime-types: "npm:~2.1.24" + checksum: a23daeb538591b7efbd61ecf06b6feb2501b683ffdc9a19c74ef5baba362b4347e42f1b4ed81f5882a8c96a3bfff7f93ce3ffaf0cbbc879b532b04c97a55db9d + languageName: node + linkType: hard + +"undici-types@npm:~6.19.2": + version: 6.19.8 + resolution: "undici-types@npm:6.19.8" + checksum: 078afa5990fba110f6824823ace86073b4638f1d5112ee26e790155f481f2a868cc3e0615505b6f4282bdf74a3d8caad715fd809e870c2bb0704e3ea6082f344 + languageName: node + linkType: hard + +"undici-types@npm:~6.20.0": + version: 6.20.0 + resolution: "undici-types@npm:6.20.0" + checksum: 68e659a98898d6a836a9a59e6adf14a5d799707f5ea629433e025ac90d239f75e408e2e5ff086afc3cace26f8b26ee52155293564593fbb4a2f666af57fc59bf + languageName: node + linkType: hard + +"unique-filename@npm:^4.0.0": + version: 4.0.0 + resolution: "unique-filename@npm:4.0.0" + dependencies: + unique-slug: "npm:^5.0.0" + checksum: 38ae681cceb1408ea0587b6b01e29b00eee3c84baee1e41fd5c16b9ed443b80fba90c40e0ba69627e30855570a34ba8b06702d4a35035d4b5e198bf5a64c9ddc + languageName: node + linkType: hard + +"unique-slug@npm:^5.0.0": + version: 5.0.0 + resolution: "unique-slug@npm:5.0.0" + dependencies: + imurmurhash: "npm:^0.1.4" + checksum: d324c5a44887bd7e105ce800fcf7533d43f29c48757ac410afd42975de82cc38ea2035c0483f4de82d186691bf3208ef35c644f73aa2b1b20b8e651be5afd293 + languageName: node + linkType: hard + +"universalify@npm:^0.2.0": + version: 0.2.0 + resolution: "universalify@npm:0.2.0" + checksum: cedbe4d4ca3967edf24c0800cfc161c5a15e240dac28e3ce575c689abc11f2c81ccc6532c8752af3b40f9120fb5e454abecd359e164f4f6aa44c29cd37e194fe + languageName: node + linkType: hard + +"unpipe@npm:1.0.0, unpipe@npm:~1.0.0": + version: 1.0.0 + resolution: "unpipe@npm:1.0.0" + checksum: 193400255bd48968e5c5383730344fbb4fa114cdedfab26e329e50dd2d81b134244bb8a72c6ac1b10ab0281a58b363d06405632c9d49ca9dfd5e90cbd7d0f32c + languageName: node + linkType: hard + +"url-parse@npm:^1.5.3": + version: 1.5.10 + resolution: "url-parse@npm:1.5.10" + dependencies: + querystringify: "npm:^2.1.1" + requires-port: "npm:^1.0.0" + checksum: bd5aa9389f896974beb851c112f63b466505a04b4807cea2e5a3b7092f6fbb75316f0491ea84e44f66fed55f1b440df5195d7e3a8203f64fcefa19d182f5be87 + languageName: node + linkType: hard + +"utils-merge@npm:1.0.1": + version: 1.0.1 + resolution: "utils-merge@npm:1.0.1" + checksum: 02ba649de1b7ca8854bfe20a82f1dfbdda3fb57a22ab4a8972a63a34553cf7aa51bc9081cf7e001b035b88186d23689d69e71b510e610a09a4c66f68aa95b672 + languageName: node + linkType: hard + +"vary@npm:~1.1.2": + version: 1.1.2 + resolution: "vary@npm:1.1.2" + checksum: f15d588d79f3675135ba783c91a4083dcd290a2a5be9fcb6514220a1634e23df116847b1cc51f66bfb0644cf9353b2abb7815ae499bab06e46dd33c1a6bf1f4f + languageName: node + linkType: hard + +"vite-node@npm:3.0.7": + version: 3.0.7 + resolution: "vite-node@npm:3.0.7" + dependencies: + cac: "npm:^6.7.14" + debug: "npm:^4.4.0" + es-module-lexer: "npm:^1.6.0" + pathe: "npm:^2.0.3" + vite: "npm:^5.0.0 || ^6.0.0" + bin: + vite-node: vite-node.mjs + checksum: caaebe014ad1b795c4c1c0adcb36bc78c9d34f1d43966526cd0cb41dc3aae717dc7a746c369006bfe8f30be54e7f3ce562aa86d38201ec79e4fad41f45b1edb2 + languageName: node + linkType: hard + +"vite@npm:^5.0.0 || ^6.0.0": + version: 6.2.0 + resolution: "vite@npm:6.2.0" + dependencies: + esbuild: "npm:^0.25.0" + fsevents: "npm:~2.3.3" + postcss: "npm:^8.5.3" + rollup: "npm:^4.30.1" + peerDependencies: + "@types/node": ^18.0.0 || ^20.0.0 || >=22.0.0 + jiti: ">=1.21.0" + less: "*" + lightningcss: ^1.21.0 + sass: "*" + sass-embedded: "*" + stylus: "*" + sugarss: "*" + terser: ^5.16.0 + tsx: ^4.8.1 + yaml: ^2.4.2 + dependenciesMeta: + fsevents: + optional: true + peerDependenciesMeta: + "@types/node": + optional: true + jiti: + optional: true + less: + optional: true + lightningcss: + optional: true + sass: + optional: true + sass-embedded: + optional: true + stylus: + optional: true + sugarss: + optional: true + terser: + optional: true + tsx: + optional: true + yaml: + optional: true + bin: + vite: bin/vite.js + checksum: db62c93d4a823e805c6f8429de035528b3c35cc7f6de4948b41e0528f94ed2ac55047d90f8534f626ef3a04e682883b570fe5ec9ee92f51bf0c3c210dbec5ac1 + languageName: node + linkType: hard + +"vitest@npm:^3.0.5": + version: 3.0.7 + resolution: "vitest@npm:3.0.7" + dependencies: + "@vitest/expect": "npm:3.0.7" + "@vitest/mocker": "npm:3.0.7" + "@vitest/pretty-format": "npm:^3.0.7" + "@vitest/runner": "npm:3.0.7" + "@vitest/snapshot": "npm:3.0.7" + "@vitest/spy": "npm:3.0.7" + "@vitest/utils": "npm:3.0.7" + chai: "npm:^5.2.0" + debug: "npm:^4.4.0" + expect-type: "npm:^1.1.0" + magic-string: "npm:^0.30.17" + pathe: "npm:^2.0.3" + std-env: "npm:^3.8.0" + tinybench: "npm:^2.9.0" + tinyexec: "npm:^0.3.2" + tinypool: "npm:^1.0.2" + tinyrainbow: "npm:^2.0.0" + vite: "npm:^5.0.0 || ^6.0.0" + vite-node: "npm:3.0.7" + why-is-node-running: "npm:^2.3.0" + peerDependencies: + "@edge-runtime/vm": "*" + "@types/debug": ^4.1.12 + "@types/node": ^18.0.0 || ^20.0.0 || >=22.0.0 + "@vitest/browser": 3.0.7 + "@vitest/ui": 3.0.7 + happy-dom: "*" + jsdom: "*" + peerDependenciesMeta: + "@edge-runtime/vm": + optional: true + "@types/debug": + optional: true + "@types/node": + optional: true + "@vitest/browser": + optional: true + "@vitest/ui": + optional: true + happy-dom: + optional: true + jsdom: + optional: true + bin: + vitest: vitest.mjs + checksum: 79075fdb493771bebe45df8cd88ab872cdaceca31420977dea43d8792fd308278a9274645220e12c24373f1e91a8848b41cedebef15fd5b538c0ea9660f42de3 + languageName: node + linkType: hard + +"w3c-xmlserializer@npm:^5.0.0": + version: 5.0.0 + resolution: "w3c-xmlserializer@npm:5.0.0" + dependencies: + xml-name-validator: "npm:^5.0.0" + checksum: 8712774c1aeb62dec22928bf1cdfd11426c2c9383a1a63f2bcae18db87ca574165a0fbe96b312b73652149167ac6c7f4cf5409f2eb101d9c805efe0e4bae798b + languageName: node + linkType: hard + +"webidl-conversions@npm:^7.0.0": + version: 7.0.0 + resolution: "webidl-conversions@npm:7.0.0" + checksum: 228d8cb6d270c23b0720cb2d95c579202db3aaf8f633b4e9dd94ec2000a04e7e6e43b76a94509cdb30479bd00ae253ab2371a2da9f81446cc313f89a4213a2c4 + languageName: node + linkType: hard + +"whatwg-encoding@npm:^3.1.1": + version: 3.1.1 + resolution: "whatwg-encoding@npm:3.1.1" + dependencies: + iconv-lite: "npm:0.6.3" + checksum: 273b5f441c2f7fda3368a496c3009edbaa5e43b71b09728f90425e7f487e5cef9eb2b846a31bd760dd8077739c26faf6b5ca43a5f24033172b003b72cf61a93e + languageName: node + linkType: hard + +"whatwg-mimetype@npm:^4.0.0": + version: 4.0.0 + resolution: "whatwg-mimetype@npm:4.0.0" + checksum: a773cdc8126b514d790bdae7052e8bf242970cebd84af62fb2f35a33411e78e981f6c0ab9ed1fe6ec5071b09d5340ac9178e05b52d35a9c4bcf558ba1b1551df + languageName: node + linkType: hard + +"whatwg-url@npm:^14.0.0": + version: 14.1.1 + resolution: "whatwg-url@npm:14.1.1" + dependencies: + tr46: "npm:^5.0.0" + webidl-conversions: "npm:^7.0.0" + checksum: de1e9cc2f04cb000f232c839d4999384ba41b680ef8a89e7c61c9bc40354ba8593c775d068faaf0819f5866e4d6ca3e7b9b386e2123aa475bfd33da02316f476 + languageName: node + linkType: hard + +"which@npm:^2.0.1": + version: 2.0.2 + resolution: "which@npm:2.0.2" + dependencies: + isexe: "npm:^2.0.0" + bin: + node-which: ./bin/node-which + checksum: 66522872a768b60c2a65a57e8ad184e5372f5b6a9ca6d5f033d4b0dc98aff63995655a7503b9c0a2598936f532120e81dd8cc155e2e92ed662a2b9377cc4374f + languageName: node + linkType: hard + +"which@npm:^5.0.0": + version: 5.0.0 + resolution: "which@npm:5.0.0" + dependencies: + isexe: "npm:^3.1.1" + bin: + node-which: bin/which.js + checksum: e556e4cd8b7dbf5df52408c9a9dd5ac6518c8c5267c8953f5b0564073c66ed5bf9503b14d876d0e9c7844d4db9725fb0dcf45d6e911e17e26ab363dc3965ae7b + languageName: node + linkType: hard + +"why-is-node-running@npm:^2.3.0": + version: 2.3.0 + resolution: "why-is-node-running@npm:2.3.0" + dependencies: + siginfo: "npm:^2.0.0" + stackback: "npm:0.0.2" + bin: + why-is-node-running: cli.js + checksum: 1cde0b01b827d2cf4cb11db962f3958b9175d5d9e7ac7361d1a7b0e2dc6069a263e69118bd974c4f6d0a890ef4eedfe34cf3d5167ec14203dbc9a18620537054 + languageName: node + linkType: hard + +"wrap-ansi-cjs@npm:wrap-ansi@^7.0.0": + version: 7.0.0 + resolution: "wrap-ansi@npm:7.0.0" + dependencies: + ansi-styles: "npm:^4.0.0" + string-width: "npm:^4.1.0" + strip-ansi: "npm:^6.0.0" + checksum: d15fc12c11e4cbc4044a552129ebc75ee3f57aa9c1958373a4db0292d72282f54373b536103987a4a7594db1ef6a4f10acf92978f79b98c49306a4b58c77d4da + languageName: node + linkType: hard + +"wrap-ansi@npm:^8.1.0": + version: 8.1.0 + resolution: "wrap-ansi@npm:8.1.0" + dependencies: + ansi-styles: "npm:^6.1.0" + string-width: "npm:^5.0.1" + strip-ansi: "npm:^7.0.1" + checksum: 138ff58a41d2f877eae87e3282c0630fc2789012fc1af4d6bd626eeb9a2f9a65ca92005e6e69a75c7b85a68479fe7443c7dbe1eb8fbaa681a4491364b7c55c60 + languageName: node + linkType: hard + +"ws@npm:^8.18.0, ws@npm:^8.18.1": + version: 8.18.1 + resolution: "ws@npm:8.18.1" + peerDependencies: + bufferutil: ^4.0.1 + utf-8-validate: ">=5.0.2" + peerDependenciesMeta: + bufferutil: + optional: true + utf-8-validate: + optional: true + checksum: e498965d6938c63058c4310ffb6967f07d4fa06789d3364829028af380d299fe05762961742971c764973dce3d1f6a2633fe8b2d9410c9b52e534b4b882a99fa + languageName: node + linkType: hard + +"xml-name-validator@npm:^5.0.0": + version: 5.0.0 + resolution: "xml-name-validator@npm:5.0.0" + checksum: 3fcf44e7b73fb18be917fdd4ccffff3639373c7cb83f8fc35df6001fecba7942f1dbead29d91ebb8315e2f2ff786b508f0c9dc0215b6353f9983c6b7d62cb1f5 + languageName: node + linkType: hard + +"xmlchars@npm:^2.2.0": + version: 2.2.0 + resolution: "xmlchars@npm:2.2.0" + checksum: b64b535861a6f310c5d9bfa10834cf49127c71922c297da9d4d1b45eeaae40bf9b4363275876088fbe2667e5db028d2cd4f8ee72eed9bede840a67d57dab7593 + languageName: node + linkType: hard + +"yallist@npm:^4.0.0": + version: 4.0.0 + resolution: "yallist@npm:4.0.0" + checksum: 2286b5e8dbfe22204ab66e2ef5cc9bbb1e55dfc873bbe0d568aa943eb255d131890dfd5bf243637273d31119b870f49c18fcde2c6ffbb7a7a092b870dc90625a + languageName: node + linkType: hard + +"yallist@npm:^5.0.0": + version: 5.0.0 + resolution: "yallist@npm:5.0.0" + checksum: a499c81ce6d4a1d260d4ea0f6d49ab4da09681e32c3f0472dee16667ed69d01dae63a3b81745a24bd78476ec4fcf856114cb4896ace738e01da34b2c42235416 + languageName: node + linkType: hard diff --git a/package.json b/package.json index d9c959209b..c558389a43 100644 --- a/package.json +++ b/package.json @@ -166,6 +166,12 @@ "import": "./esm/identity/index.js", "default": "./esm/identity/index.js" }, + "./minikit": { + "types": "./esm/minikit/index.d.ts", + "module": "./esm/minikit/index.js", + "import": "./esm/minikit/index.js", + "default": "./esm/minikit/index.js" + }, "./nft": { "types": "./esm/nft/index.d.ts", "module": "./esm/nft/index.js", diff --git a/src/minikit/hooks/useAddFrame.test.tsx b/src/minikit/hooks/useAddFrame.test.tsx new file mode 100644 index 0000000000..bed4c313c3 --- /dev/null +++ b/src/minikit/hooks/useAddFrame.test.tsx @@ -0,0 +1,76 @@ +import sdk from '@farcaster/frame-sdk'; +import { renderHook } from '@testing-library/react'; +import { act } from 'react'; +import { type Mock, beforeEach, describe, expect, it, vi } from 'vitest'; +import { useAddFrame } from './useAddFrame'; +import { useMiniKit } from './useMiniKit'; + +vi.mock('@farcaster/frame-sdk', () => ({ + default: { + actions: { + addFrame: vi.fn(), + }, + }, +})); + +vi.mock('./useMiniKit', () => ({ + useMiniKit: vi.fn(), +})); + +describe('useAddFrame', () => { + let mockUpdateClientContext: Mock; + beforeEach(() => { + (sdk.actions.addFrame as Mock).mockResolvedValue({ + notificationDetails: { + type: 'frame_added', + frameId: '123', + }, + }); + + mockUpdateClientContext = vi.fn(); + (useMiniKit as Mock).mockReturnValue({ + updateClientContext: mockUpdateClientContext, + }); + }); + + it('should return a function', () => { + const { result } = renderHook(() => useAddFrame()); + expect(typeof result.current).toBe('function'); + }); + + it('should call sdk.addFrame when executed', async () => { + const { result } = renderHook(() => useAddFrame()); + + await act(async () => { + await result.current(); + }); + + expect(sdk.actions.addFrame).toHaveBeenCalled(); + }); + + it('should call updateClientContext with the correct parameters', async () => { + const { result } = renderHook(() => useAddFrame()); + + await act(async () => { + await result.current(); + }); + + expect(mockUpdateClientContext).toHaveBeenCalledWith({ + details: { + type: 'frame_added', + frameId: '123', + }, + frameAdded: true, + }); + }); + + it('should return null if notification details are not returned', async () => { + (sdk.actions.addFrame as Mock).mockResolvedValue({}); + + const { result } = renderHook(() => useAddFrame()); + + const response = await result.current(); + + expect(response).toBeNull(); + }); +}); diff --git a/src/minikit/hooks/useAddFrame.ts b/src/minikit/hooks/useAddFrame.ts new file mode 100644 index 0000000000..ebe0f33c7d --- /dev/null +++ b/src/minikit/hooks/useAddFrame.ts @@ -0,0 +1,20 @@ +import sdk from '@farcaster/frame-sdk'; +import { useCallback } from 'react'; +import { useMiniKit } from './useMiniKit'; + +export function useAddFrame() { + const { updateClientContext } = useMiniKit(); + + return useCallback(async () => { + const result = await sdk.actions.addFrame(); + + if (result.notificationDetails) { + updateClientContext({ + details: result.notificationDetails, + frameAdded: true, + }); + return result.notificationDetails; + } + return null; + }, [updateClientContext]); +} diff --git a/src/minikit/hooks/useAuthenticate.test.tsx b/src/minikit/hooks/useAuthenticate.test.tsx new file mode 100644 index 0000000000..884fa7d14b --- /dev/null +++ b/src/minikit/hooks/useAuthenticate.test.tsx @@ -0,0 +1,106 @@ +import sdk from '@farcaster/frame-sdk'; +import { renderHook } from '@testing-library/react'; +import { act } from 'react'; +import { + type Mock, + afterEach, + beforeEach, + describe, + expect, + it, + vi, +} from 'vitest'; +import { useAuthenticate } from './useAuthenticate'; + +const signInMock = { + message: + 'test.frames.app wants you to sign in with your Ethereum account:\n0x58b5513c3999632542332e4e2eD0298EC1fF1831', + signature: '0x123', +}; + +vi.mock('@farcaster/frame-sdk', () => ({ + default: { + actions: { + signIn: vi.fn(), + }, + }, +})); + +vi.mock('./useMiniKit', () => ({ + useMiniKit: vi.fn(), +})); + +describe('useAuthenticate', () => { + const originalEnv = process.env; + + beforeEach(() => { + process.env = { ...originalEnv }; + process.env.NEXT_PUBLIC_URL = 'https://test.frames.app'; + + (sdk.actions.signIn as Mock).mockResolvedValue(signInMock); + + vi.clearAllMocks(); + }); + + afterEach(() => { + process.env = originalEnv; + }); + + it('should return a function', () => { + const { result } = renderHook(() => useAuthenticate()); + expect(typeof result.current).toBe('function'); + }); + + it('should call sdk.actions.signIn when executed', async () => { + const { result } = renderHook(() => useAuthenticate()); + + await act(async () => { + await result.current(); + }); + + expect(sdk.actions.signIn).toHaveBeenCalled(); + }); + + it('should update client context with auth details on success', async () => { + const { result } = renderHook(() => useAuthenticate()); + + const response = await result.current(); + + expect(response).toBe(signInMock); + }); + + it('should return false if authentication fails', async () => { + vi.spyOn(console, 'error').mockImplementation(vi.fn()); + + (sdk.actions.signIn as Mock).mockRejectedValue(new Error('Auth failed')); + + const { result } = renderHook(() => useAuthenticate()); + const response = await result.current(); + + expect(response).toBe(false); + }); + + it('should throw an error if NEXT_PUBLIC_URL is not set', async () => { + const errorSpy = vi.spyOn(console, 'error').mockImplementation(vi.fn()); + process.env.NEXT_PUBLIC_URL = undefined; + + const { result } = renderHook(() => useAuthenticate()); + const response = await result.current(); + + expect(response).toBe(false); + expect(errorSpy).toHaveBeenCalledWith( + new Error('NEXT_PUBLIC_URL not configured'), + ); + }); + + it('should throw an error if the domain of the message does not match the NEXT_PUBLIC_URL', async () => { + const errorSpy = vi.spyOn(console, 'error').mockImplementation(vi.fn()); + process.env.NEXT_PUBLIC_URL = 'https://other.frames.app'; + + const { result } = renderHook(() => useAuthenticate()); + const response = await result.current(); + + expect(response).toBe(false); + expect(errorSpy).toHaveBeenCalledWith(new Error('Domain mismatch')); + }); +}); diff --git a/src/minikit/hooks/useAuthenticate.ts b/src/minikit/hooks/useAuthenticate.ts new file mode 100644 index 0000000000..570564350f --- /dev/null +++ b/src/minikit/hooks/useAuthenticate.ts @@ -0,0 +1,52 @@ +import sdk from '@farcaster/frame-sdk'; +import type { SignIn as SignInCore } from '@farcaster/frame-sdk'; +import { useCallback } from 'react'; + +/* + * Validate the domain of the SWIF message matches the frame's domain + * @param message - The SWIF message to validate + * @throws {Error} - Throws an error if the domain or uri is invalid + */ +const validateDomainAndUri = (message: string) => { + const firstLine = message.split('\n')[0]; + const domain = firstLine.split( + ' wants you to sign in with your Ethereum account:', + )[0]; + + const frameUrl = process.env.NEXT_PUBLIC_URL; + if (!frameUrl) { + throw new Error('NEXT_PUBLIC_URL not configured'); + } + + const frameUrlObj = new URL(frameUrl); + + // domain in message should match frame's domain + if (frameUrlObj.hostname !== domain) { + throw new Error('Domain mismatch'); + } +}; + +type UseAuthenticateProps = Omit & { + nonce?: string; +}; + +export const useAuthenticate = () => { + return useCallback(async (signInOptions: UseAuthenticateProps = {}) => { + try { + if (!signInOptions?.nonce) { + signInOptions.nonce = [...Array(8)] + .map(() => Math.floor(Math.random() * 36).toString(36)) + .join(''); + } + + const result = await sdk.actions.signIn( + signInOptions as SignInCore.SignInOptions, + ); + validateDomainAndUri(result.message); + return result; + } catch (error) { + console.error(error); + return false; + } + }, []); +}; diff --git a/src/minikit/hooks/useClose.test.tsx b/src/minikit/hooks/useClose.test.tsx new file mode 100644 index 0000000000..5666d0651a --- /dev/null +++ b/src/minikit/hooks/useClose.test.tsx @@ -0,0 +1,30 @@ +import sdk from '@farcaster/frame-sdk'; +import { renderHook } from '@testing-library/react'; +import { act } from 'react'; +import { describe, expect, it, vi } from 'vitest'; +import { useClose } from './useClose'; + +vi.mock('@farcaster/frame-sdk', () => ({ + default: { + actions: { + close: vi.fn(), + }, + }, +})); + +describe('useClose', () => { + it('should return a function', () => { + const { result } = renderHook(() => useClose()); + expect(typeof result.current).toBe('function'); + }); + + it('should call sdk.close when executed', async () => { + const { result } = renderHook(() => useClose()); + + await act(async () => { + result.current(); + }); + + expect(sdk.actions.close).toHaveBeenCalled(); + }); +}); diff --git a/src/minikit/hooks/useClose.ts b/src/minikit/hooks/useClose.ts new file mode 100644 index 0000000000..6c59304698 --- /dev/null +++ b/src/minikit/hooks/useClose.ts @@ -0,0 +1,8 @@ +import sdk from '@farcaster/frame-sdk'; +import { useCallback } from 'react'; + +export function useClose() { + return useCallback(() => { + sdk.actions.close(); + }, []); +} diff --git a/src/minikit/hooks/useMiniKit.test.tsx b/src/minikit/hooks/useMiniKit.test.tsx new file mode 100644 index 0000000000..ac4b292adb --- /dev/null +++ b/src/minikit/hooks/useMiniKit.test.tsx @@ -0,0 +1,68 @@ +import { renderHook } from '@testing-library/react'; +import { act } from 'react'; +import { afterEach, describe, expect, it, vi } from 'vitest'; +import { MiniKitContext } from '../MiniKitProvider'; +import type { MiniKitContextType } from '../types'; +import { useMiniKit } from './useMiniKit'; + +const mockContext = { + context: null, + notificationProxyUrl: '/api/notify', + updateClientContext: vi.fn(), +} as MiniKitContextType; + +vi.mock('@/DefaultOnchainKitProviders', () => ({ + DefaultOnchainKitProviders: ({ children }: { children: React.ReactNode }) => + children, +})); + +vi.mock('@/OnchainKitProvider', () => ({ + OnchainKitProvider: ({ children }: { children: React.ReactNode }) => children, +})); + +vi.mock('@farcaster/frame-wagmi-connector', () => ({ + farcasterFrame: vi.fn(), +})); + +describe('useMiniKit', () => { + afterEach(() => { + vi.clearAllMocks(); + }); + + it('should throw error when used outside MiniKitProvider', () => { + expect(() => { + renderHook(() => useMiniKit()); + }).toThrow('useMiniKit must be used within a MiniKitProvider'); + }); + + it('should return the correct values', () => { + const { result } = renderHook(() => useMiniKit(), { + wrapper: ({ children }) => ( + + {children} + + ), + }); + expect(result.current.isFrameReady).toBe(false); + expect(result.current.context).toEqual(mockContext.context); + expect(result.current.notificationProxyUrl).toBe( + mockContext.notificationProxyUrl, + ); + }); + + it('should set ready', async () => { + const { result } = renderHook(() => useMiniKit(), { + wrapper: ({ children }) => ( + + {children} + + ), + }); + + await act(async () => { + result.current.setFrameReady(); + }); + + expect(result.current.isFrameReady).toBe(true); + }); +}); diff --git a/src/minikit/hooks/useMiniKit.ts b/src/minikit/hooks/useMiniKit.ts new file mode 100644 index 0000000000..ddba09f449 --- /dev/null +++ b/src/minikit/hooks/useMiniKit.ts @@ -0,0 +1,25 @@ +import { MiniKitContext, emptyContext } from '@/minikit/MiniKitProvider'; +import sdk from '@farcaster/frame-sdk'; +import { useContext, useState } from 'react'; + +export const useMiniKit = () => { + const [isFrameReady, setIsFrameReady] = useState(false); + const context = useContext(MiniKitContext); + if (context === emptyContext) { + throw new Error('useMiniKit must be used within a MiniKitProvider'); + } + + const setFrameReady = async () => { + sdk.actions.ready({}); + setIsFrameReady(true); + return context; + }; + + return { + setFrameReady, + isFrameReady, + context: context.context, + updateClientContext: context.updateClientContext, + notificationProxyUrl: context.notificationProxyUrl, + }; +}; diff --git a/src/minikit/hooks/useNotification.test.tsx b/src/minikit/hooks/useNotification.test.tsx new file mode 100644 index 0000000000..e32b724911 --- /dev/null +++ b/src/minikit/hooks/useNotification.test.tsx @@ -0,0 +1,106 @@ +import { renderHook } from '@testing-library/react'; +import { act } from 'react'; +import { type Mock, beforeEach, describe, expect, it, vi } from 'vitest'; +import { useMiniKit } from './useMiniKit'; +import { useNotification } from './useNotification'; + +vi.mock('./useMiniKit', () => ({ + useMiniKit: vi.fn(), +})); + +describe('useNotification', () => { + beforeEach(() => { + (useMiniKit as Mock).mockReturnValue({ + context: { + user: { + fid: 123, + }, + }, + notificationProxyUrl: '/api/notification', + }); + }); + + it('should return a function', () => { + const { result } = renderHook(() => useNotification()); + expect(typeof result.current).toBe('function'); + }); + + it('should return false if context is not set', async () => { + (useMiniKit as Mock).mockReturnValue({ + context: null, + notificationProxyUrl: '/api/notification', + }); + + const { result } = renderHook(() => useNotification()); + await expect( + result.current({ + title: 'test', + body: 'test', + }), + ).resolves.toBe(false); + }); + + it('should call notificationProxyUrl with the correct parameters', async () => { + global.fetch = vi.fn().mockImplementation(() => + Promise.resolve({ + ok: true, + json: () => Promise.resolve({ success: true }), + }), + ); + + const { result } = renderHook(() => useNotification()); + + const response = await act(async () => { + return await result.current({ + title: 'test', + body: 'test', + }); + }); + + expect(response).toBe(true); + const calledBody = JSON.parse((global.fetch as Mock).mock.calls[0][1].body); + expect(calledBody).toEqual({ + fid: 123, + notification: { + notificationId: expect.any(String), + title: 'test', + body: 'test', + }, + }); + }); + + it('should return false when response is not ok', async () => { + global.fetch = vi.fn().mockImplementation(() => + Promise.resolve({ + ok: false, + }), + ); + + const { result } = renderHook(() => useNotification()); + + await expect( + result.current({ + title: 'test', + body: 'test', + }), + ).resolves.toBe(false); + }); + + it('should return false when fetch fails', async () => { + // swallow error + vi.spyOn(console, 'error').mockImplementation(vi.fn()); + + global.fetch = vi + .fn() + .mockImplementation(() => Promise.reject(new Error('Network error'))); + + const { result } = renderHook(() => useNotification()); + + await expect( + result.current({ + title: 'test', + body: 'test', + }), + ).resolves.toBe(false); + }); +}); diff --git a/src/minikit/hooks/useNotification.ts b/src/minikit/hooks/useNotification.ts new file mode 100644 index 0000000000..68fbe5d1db --- /dev/null +++ b/src/minikit/hooks/useNotification.ts @@ -0,0 +1,41 @@ +import { useCallback } from 'react'; +import { useMiniKit } from './useMiniKit'; + +export function useNotification() { + const { context, notificationProxyUrl } = useMiniKit(); + + return useCallback( + async ({ title, body }: { title: string; body: string }) => { + if (!context) { + return false; + } + + try { + const response = await fetch(notificationProxyUrl, { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + }, + body: JSON.stringify({ + fid: context.user.fid, + notification: { + notificationId: crypto.randomUUID(), + title, + body, + }, + }), + }); + + if (!response.ok) { + throw new Error(`HTTP error, status: ${response.status}`); + } + + return true; + } catch (error) { + console.error('Error sending notification:', error); + return false; + } + }, + [context, notificationProxyUrl], + ); +} diff --git a/src/minikit/hooks/useOpenUrl.test.tsx b/src/minikit/hooks/useOpenUrl.test.tsx new file mode 100644 index 0000000000..5e6b4462b5 --- /dev/null +++ b/src/minikit/hooks/useOpenUrl.test.tsx @@ -0,0 +1,56 @@ +import sdk from '@farcaster/frame-sdk'; +import { renderHook } from '@testing-library/react'; +import { act } from 'react'; +import { type Mock, afterEach, describe, expect, it, vi } from 'vitest'; +import { useMiniKit } from './useMiniKit'; +import { useOpenUrl } from './useOpenUrl'; + +vi.mock('@farcaster/frame-sdk', () => ({ + default: { + actions: { + openUrl: vi.fn(), + }, + }, +})); + +vi.mock('./useMiniKit', () => ({ + useMiniKit: vi.fn().mockReturnValue({ + context: {}, + }), +})); + +describe('useOpenUrl', () => { + afterEach(() => { + vi.clearAllMocks(); + }); + + it('should return a function', () => { + const { result } = renderHook(() => useOpenUrl()); + expect(typeof result.current).toBe('function'); + }); + + it('should call sdk.openUrl when executed', async () => { + const { result } = renderHook(() => useOpenUrl()); + + await act(async () => { + result.current('https://example.com'); + }); + + expect(sdk.actions.openUrl).toHaveBeenCalledWith('https://example.com'); + }); + + it('should call window.open when context is not available', () => { + const mockOpen = vi.fn(); + window.open = mockOpen; + + (useMiniKit as Mock).mockReturnValue({ + context: null, + }); + + const { result } = renderHook(() => useOpenUrl()); + + result.current('https://example.com'); + + expect(mockOpen).toHaveBeenCalledWith('https://example.com', '_blank'); + }); +}); diff --git a/src/minikit/hooks/useOpenUrl.ts b/src/minikit/hooks/useOpenUrl.ts new file mode 100644 index 0000000000..73ebea17e7 --- /dev/null +++ b/src/minikit/hooks/useOpenUrl.ts @@ -0,0 +1,18 @@ +import sdk from '@farcaster/frame-sdk'; +import { useCallback } from 'react'; +import { useMiniKit } from './useMiniKit'; + +export function useOpenUrl() { + const { context } = useMiniKit(); + + return useCallback( + (url: string) => { + if (context) { + sdk.actions.openUrl(url); + } else { + window.open(url, '_blank'); + } + }, + [context], + ); +} diff --git a/src/minikit/hooks/usePrimaryButton.test.tsx b/src/minikit/hooks/usePrimaryButton.test.tsx new file mode 100644 index 0000000000..316475c5b9 --- /dev/null +++ b/src/minikit/hooks/usePrimaryButton.test.tsx @@ -0,0 +1,43 @@ +import sdk from '@farcaster/frame-sdk'; +import { act, renderHook } from '@testing-library/react'; +import { describe, expect, it, vi } from 'vitest'; +import { usePrimaryButton } from './usePrimaryButton'; + +vi.mock('@farcaster/frame-sdk', () => { + const listeners: Record void> = {}; + + return { + default: { + actions: { + setPrimaryButton: vi.fn(), + }, + emit: vi.fn((event: string, data: object) => { + if (listeners[event]) { + listeners[event](data); + } + }), + on: vi.fn((event, callback) => { + listeners[event] = callback; + }), + }, + }; +}); + +describe('usePrimaryButton', () => { + it('should call sdk.actions.setPrimaryButton when executed', async () => { + renderHook(() => usePrimaryButton({ text: 'test' }, () => {})); + + expect(sdk.actions.setPrimaryButton).toHaveBeenCalled(); + }); + + it('should call callback on primaryButtonClicked event', async () => { + const callback = vi.fn(); + renderHook(() => usePrimaryButton({ text: 'test' }, callback)); + + act(() => { + sdk.emit('primaryButtonClicked'); + }); + + expect(callback).toHaveBeenCalled(); + }); +}); diff --git a/src/minikit/hooks/usePrimaryButton.ts b/src/minikit/hooks/usePrimaryButton.ts new file mode 100644 index 0000000000..7b1776a6a4 --- /dev/null +++ b/src/minikit/hooks/usePrimaryButton.ts @@ -0,0 +1,16 @@ +import sdk, { type SetPrimaryButtonOptions } from '@farcaster/frame-sdk'; +import { useEffect } from 'react'; + +export function usePrimaryButton( + options: SetPrimaryButtonOptions, + callback: () => void, +): void { + useEffect(() => { + async function setPrimaryButton() { + await sdk.actions.setPrimaryButton(options); + } + + sdk.on('primaryButtonClicked', callback); + setPrimaryButton(); + }, [callback, options]); +} diff --git a/src/minikit/hooks/useViewProfile.test.tsx b/src/minikit/hooks/useViewProfile.test.tsx new file mode 100644 index 0000000000..502f36254c --- /dev/null +++ b/src/minikit/hooks/useViewProfile.test.tsx @@ -0,0 +1,64 @@ +import sdk from '@farcaster/frame-sdk'; +import { renderHook } from '@testing-library/react'; +import { type Mock, beforeEach, describe, expect, it, vi } from 'vitest'; +import { useMiniKit } from './useMiniKit'; +import { useViewProfile } from './useViewProfile'; + +vi.mock('@farcaster/frame-sdk', () => ({ + default: { + actions: { + viewProfile: vi.fn(), + }, + }, +})); + +vi.mock('./useMiniKit', () => ({ + useMiniKit: vi.fn(), +})); + +describe('useViewProfile', () => { + beforeEach(() => { + vi.clearAllMocks(); + + (useMiniKit as Mock).mockReturnValue({ + context: { + user: { + fid: 123, + }, + }, + }); + }); + + it('should return a function', () => { + const { result } = renderHook(() => useViewProfile()); + expect(typeof result.current).toBe('function'); + }); + + it('should call sdk.viewProfile with the default fid', async () => { + const { result } = renderHook(() => useViewProfile()); + + result.current(); + + expect(sdk.actions.viewProfile).toHaveBeenCalledWith({ fid: 123 }); + }); + + it('should call sdk.viewProfile with the provided fid', async () => { + const { result } = renderHook(() => useViewProfile()); + + result.current(456); + + expect(sdk.actions.viewProfile).toHaveBeenCalledWith({ fid: 456 }); + }); + + it('should not call sdk.viewProfile if fid is not provided', async () => { + (useMiniKit as Mock).mockReturnValue({ + context: null, + }); + + const { result } = renderHook(() => useViewProfile()); + + result.current(); + + expect(sdk.actions.viewProfile).not.toHaveBeenCalled(); + }); +}); diff --git a/src/minikit/hooks/useViewProfile.ts b/src/minikit/hooks/useViewProfile.ts new file mode 100644 index 0000000000..97fbcea0ad --- /dev/null +++ b/src/minikit/hooks/useViewProfile.ts @@ -0,0 +1,20 @@ +import sdk from '@farcaster/frame-sdk'; +import { useCallback } from 'react'; +import { useMiniKit } from './useMiniKit'; + +export function useViewProfile() { + const { context } = useMiniKit(); + + return useCallback( + (fid?: number) => { + const profileFid = fid ?? context?.user?.fid; + + if (!profileFid) { + return; + } + + sdk.actions.viewProfile({ fid: profileFid }); + }, + [context?.user?.fid], + ); +} diff --git a/src/minikit/index.ts b/src/minikit/index.ts index 9dc1fda422..3cfccbfd8c 100644 --- a/src/minikit/index.ts +++ b/src/minikit/index.ts @@ -1,2 +1,10 @@ export { MiniKitProvider } from './MiniKitProvider'; export type { MiniKitProviderReact } from './types'; +export { useMiniKit } from './hooks/useMiniKit'; +export { useOpenUrl } from './hooks/useOpenUrl'; +export { useAuthenticate } from './hooks/useAuthenticate'; +export { useViewProfile } from './hooks/useViewProfile'; +export { useAddFrame } from './hooks/useAddFrame'; +export { usePrimaryButton } from './hooks/usePrimaryButton'; +export { useNotification } from './hooks/useNotification'; +export { useClose } from './hooks/useClose'; diff --git a/vitest.config.ts b/vitest.config.ts index 9007d26e1a..aec6f3ee9c 100644 --- a/vitest.config.ts +++ b/vitest.config.ts @@ -24,6 +24,7 @@ export default defineConfig({ 'playground/**', 'site/**', 'create-onchain/**', + 'account-manifest/**', '**/**.test.tsx', ], reportOnFailure: true, @@ -43,6 +44,7 @@ export default defineConfig({ 'playground/**', 'site/**', 'create-onchain/**', + 'account-manifest/**', ], setupFiles: ['./vitest.setup.ts'], globals: true,