WE

web-to-miniapp

Migrate Next.js apps to World App mini apps. Includes setup for MiniKit and debugging SSR hydration issues.

Install

mkdir -p .claude/skills/web-to-miniapp && curl -L -o skill.zip "https://agentskills.codes/api/skills/download/19512" && unzip -o skill.zip -d .claude/skills/web-to-miniapp && rm skill.zip

Installs to .claude/skills/web-to-miniapp

Activation

This is the description your AI agent reads to decide when to run this skill — the better it matches your request, the more reliably it fires.

Use this skill when you are asked to adapt an existing web app to work as a World App mini app, or to share code between a web app and a mini app. This skill covers the technical steps of migration, common issues, and debugging tips. There will be some changes required to contracts and frontend code, but the overall architecture and user experience can remain largely unchanged.
380 chars✓ has a “when” triggerlonger than Claude Code's old 250-char listing cap (fine on current versions)
Intermediate

Key capabilities

  • Integrate MiniKit into Next.js applications
  • Configure dual-provider wallet connections
  • Bundle token approvals and contract calls atomically
  • Poll for user operation receipts
  • Whitelist contracts and tokens in the developer portal

How it works

The migration involves disabling SSR for MiniKit-dependent components, providing a MiniKitProvider, and using the MiniKit provider for EIP-1193 wallet operations. It replaces standard transaction hashes with userOpHash tracking.

Inputs & outputs

You give it
Existing Next.js web application code
You get back
World App compatible mini app with wallet connectivity

When to use web-to-miniapp

  • Convert a web app to a mini app
  • Install and configure MiniKit
  • Debug SSR hydration mismatches
  • Implement dual-provider wallet connections

About this skill

Migrate Next.js Web App to World App Mini App

You are converting an existing Next.js web app that uses viem to work as a World App mini app. Follow these steps in order.

Step 1 — Install MiniKit

npm install @worldcoin/minikit-js @worldcoin/minikit-react

Step 2 — Disable SSR for pages that use MiniKit

MiniKit depends on window.WorldApp which doesn't exist on the server. SSR causes hydration mismatches that silently break all React event handlers — buttons render but do nothing.

Wrap the page component in a dynamic import with ssr: false:

// src/app/page.tsx
'use client';
import dynamic from 'next/dynamic';
const App = dynamic(() => import('../components/App'), { ssr: false });
export default function Page() {
  return <App />;
}

Move the actual page logic into src/components/App.tsx.

Step 3 — Add MiniKitProvider

Create src/app/providers.tsx:

'use client';
import { MiniKitProvider } from '@worldcoin/minikit-js/minikit-provider';
export default function Providers({ children }: { children: React.ReactNode }) {
  return <MiniKitProvider>{children}</MiniKitProvider>;
}

Wrap children in src/app/layout.tsx:

import Providers from './providers';
// ...
<body>
  <Providers>{children}</Providers>
</body>;

Step 4 — Dual-provider wallet connection

Detect World App with MiniKit.isInWorldApp() and use getWorldAppProvider() as the EIP-1193 provider. Fall back to window.ethereum for browser wallets.

import { MiniKit } from '@worldcoin/minikit-js';
import { getWorldAppProvider } from '@worldcoin/minikit-js';
import { createWalletClient, custom } from 'viem';
import { worldchain } from 'viem/chains';

const provider = MiniKit.isInWorldApp()
  ? getWorldAppProvider()
  : window.ethereum;

const walletClient = createWalletClient({
  chain: worldchain,
  transport: custom(provider),
});

getWorldAppProvider() is a standard EIP-1193 provider. Under the hood:

  • eth_requestAccountsMiniKit.walletAuth() (SIWE sign-in)
  • eth_sendTransactionMiniKit.sendTransaction() (returns userOpHash)
  • eth_chainId0x1e0 (World Chain 480)

All existing writeContract / readContract calls work unchanged through this provider.

Step 5 — Bundle Approve with Contract Calls

World App resets token approvals to 0 after each transaction. A separate approve() followed by a transferFrom() in the next tx will fail, the approval is already gone. Thus you should bundle the approval and your contract call in a single sendTransaction:

import { MiniKit } from '@worldcoin/minikit-js';
import { encodeFunctionData } from 'viem';

await MiniKit.sendTransaction({
  chainId: 480,
  transactions: [
    {
      to: TOKEN,
      data: encodeFunctionData({
        abi: erc20Abi,
        functionName: 'approve',
        args: [CONTRACT, amount],
      }),
    },
    {
      to: CONTRACT,
      data: encodeFunctionData({
        abi: contractAbi,
        functionName: 'swap',
        args: [amount],
      }),
    },
  ],
});

In World App, these execute atomically. On web, they execute sequentially — each requires a separate wallet confirmation and is not atomic.

Step 6 — Handle userOpHash receipts

MiniKit returns a userOpHash, not a standard tx hash. Use useUserOperationReceipt from @worldcoin/minikit-react to poll for the receipt:

import { useUserOperationReceipt } from '@worldcoin/minikit-react';
import { createPublicClient, http } from 'viem';
import { worldchain } from 'viem/chains';

const client = createPublicClient({
  chain: worldchain,
  transport: http(),
});

const { poll, isLoading } = useUserOperationReceipt({ client });

// After sendTransaction:
const result = await MiniKit.sendTransaction({ ... });
await poll(result.data.userOpHash);

Step 7 — Whitelist contracts and tokens

In the Developer Portal > Mini App > Permissions, add:

  • Permit2 Tokens — every ERC-20 your app transfers
  • Contract Entrypoints — every contract your app calls directly

Transactions touching non-whitelisted contracts are blocked with invalid_contract.

IssueSymptomFix
SSR hydration mismatchButtons render but clicks do nothingdynamic(..., { ssr: false })
MiniKit.isInstalled() before install()Always false even in World AppUse useMiniKit() hook or window.WorldApp
Permit2 uses uint160 amountsSilent overflowCast explicitly
eth_sendTransaction returns userOpHashwaitForTransactionReceipt times outUse useUserOperationReceipt from @worldcoin/minikit-react
Missing contract whitelistinvalid_contract errorAdd to Developer Portal permissions

Tip: Debugging in the webview

There are no browser devtools in World App's webview. Add eruda for a mobile console:

<!-- In layout.tsx body -->
<script src="https://cdn.jsdelivr.net/npm/eruda"></script>
<script>
  eruda.init();
</script>

Bonus: Think about World ID

Think about places where you could use privacy preserving sybil resistance with World ID. The World ID SDK

When not to use it

  • Standard web applications without World App integration
  • Environments requiring server-side wallet interaction

Prerequisites

@worldcoin/minikit-js@worldcoin/minikit-react

Limitations

  • MiniKit depends on window.WorldApp which is unavailable on the server
  • Token approvals reset to 0 after each transaction

How it compares

This workflow adapts standard web-based wallet interactions to the specific constraints of the World App mobile webview environment.

Compared to similar skills

web-to-miniapp side by side with the closest alternatives in the catalog.

SkillInstallsUpdatedSafetyDifficulty
web-to-miniapp (this skill)03moReviewIntermediate
clerk-custom-ui03moReviewIntermediate
nextjs-developer3282moNo flagsAdvanced
landing-page-guide-v2488moReviewIntermediate

Try saying

Example prompts that trigger this skill in your AI assistant.

Search skills

Search the agent skills registry