MiniKit 2.0 is still in beta and shouldn’t be used in production. Try it out here:
2.0.0-dev.0
Summary
We are standardizing SDKs by concern:
@worldcoin/idkit owns World ID verification APIs and UI.
@worldcoin/minikit-js owns mini-app commands only, verify is moved out to IDKit.
Why
Developers building on the World network should not have to choose between shipping a Mini App and a standalone application.
Historically, that choice created friction for three reasons:
- World ID verification logic differed between Mini Apps and IDKit.
- MiniKit command APIs were tightly coupled to World App runtime behavior.
- MiniKit v1 wallet interactions did not follow EIP-1193 conventions (for example, wagmi/viem patterns), so existing dApps often required significant refactors to adopt MiniKit-specific walletAuth and sendTransaction flows.
MiniKit v2 addresses this by:
- Removing verification APIs/UI from MiniKit and standardizing verification on IDKit.
- Allowing existing wagmi-based dApps to add MiniKitProvider so wallet/transaction behavior adapts automatically to World App context.
- Supporting custom fallback logic across commands, so apps behave consistently both inside and outside World App.
Migration
World ID moved completely to IDKit
Old: Only works in a Mini App context
// 1. Custom verification logic for MiniKit using World ID 3.0
await MiniKit.commandsAsync.verify({
action: 'my-action',
signal: 'user-123',
});
// 2. Verify Proof
await fetch('/api/verify-proof', {
method: 'POST',
headers: { 'content-type': 'application/json' },
body: JSON.stringify(completion.result),
});
New: Verification works as a mini app and standalone
import { IDKit, orbLegacy } from '@worldcoin/idkit';
// 1. NEW: Requests now require signing in 4.0 to ensure legitimacy of RP context.
const rpContext = await fetch('/api/rp-signature', { method: 'POST' }).then((r) =>
r.json(),
);
// 2. Request verification
const request = await IDKit.request({
app_id: process.env.NEXT_PUBLIC_APP_ID as `app_${string}`,
action: 'my-action',
rp_context: rpContext,
allow_legacy_proofs: false,
environment: 'production',
}).preset(orbLegacy({ signal: 'user-123' }));
const completion = await request.pollUntilCompletion();
// 3. Verify Proof
if (completion.success) {
await fetch('/api/verify-proof', {
method: 'POST',
headers: { 'content-type': 'application/json' },
body: JSON.stringify(completion.result),
});
}
MiniKit Command API (v2 -> v3)
1) MiniKit.commands / MiniKit.commandsAsync are removed
Old:
const payload = MiniKit.commands.signMessage({ message: 'hello' });
const { commandPayload, finalPayload } = await MiniKit.commandsAsync.walletAuth({
nonce,
});
New:
const signResult = await MiniKit.signMessage({ message: 'hello' });
const authResult = await MiniKit.walletAuth({ nonce });
2) Type and helper exports moved to @worldcoin/minikit-js/commands
This reduces bundle size by importing types and helpers without pulling in the full MiniKit runtime.
New: Updated paths for types and helpers
import type { MiniKitSendHapticFeedbackOptions } from '@worldcoin/minikit-js/commands'; // MOVED
import { getIsUserVerified } from '@worldcoin/minikit-js/address-book'; // MOVED
import { parseSiweMessage, verifySiweMessage } from '@worldcoin/minikit-js/siwe'; // MOVED
const options: MiniKitSendHapticFeedbackOptions = {
hapticsType: 'success',
};
3) Commands now support custom fallbacks
Use fallback to keep the same command flow working outside World App.
Fallback by default expect a response with the same shape as the original command, but you can also override and return custom data.
Fallback handlers for outside World App
const result = await MiniKit.sendHapticFeedback({
hapticsType: 'impact',
style: 'light',
fallback: () => { // NEW
navigator.vibrate?.(20);
return {
status: 'success',
version: 1,
timestamp: new Date().toISOString(),
};
},
});
4) Return shape changed to { executedWith, data }
Command responses now include executedWith to indicate whether the command was executed by minikit | fallback | wagmi (if applicable).
The actual command response data is still nested under data.
Old:
const { finalPayload } = await MiniKit.commandsAsync.sendTransaction({
transaction: [tx],
});
console.log(finalPayload.transaction_id);
New:
Custom handling based on execution context
const result = await MiniKit.sendTransaction({
transaction: [tx],
});
console.log(result.executedWith); // 'minikit' | 'wagmi' | 'fallback'
console.log(result.data.transactionId);
5) walletAuth nonce validation is stricter
In order to be in line with EIP-4361, walletAuth now requires a nonce without hyphens. You can use crypto.randomUUID().replace(/-/g, '').
Old:
const nonce = crypto.randomUUID(); // contains hyphens
await MiniKit.commandsAsync.walletAuth({ nonce });
New:
const nonce = crypto.randomUUID().replace(/-/g, '');
await MiniKit.walletAuth({ nonce });
6) sendTransaction 2.0
Send Transaction will switch to Permit2 Approves with expiration time 0.
Apps that need longer standing approvals will be granted on a case by case basis. This will require a hard migration, but provides much more robust security and flexibility for standing approvals to developers.
Transactions themselves will now purely be calldata in line with eth_sendTransaction.
await MiniKit.sendTransaction({
transactions: [
{
data: // Calldata Hex String
value: // Hex encoded string
to: // Hex
},
],
chainId: 480
});
Backwards compatibility with v1 will be supported for a period of time, but we encourage developers to migrate to the new format as soon as possible.
Type changes:
type Transaction = {
address: string;
value?: string;
data?: string; // takes priority when present
};
interface MiniKitSendTransactionOptions<TCustomFallback = SendTransactionResult> {
transaction: Transaction[];
chainId?: number; // defaults to 480 on World App
}
Running your Mini App as a Standalone App
1) World ID
Migrating to World ID 4.0 with IDKit will allow your verification flow to work out of the box. You can
optionally add branching logic to show the IDKit widget
Example:
export function VerifyHybrid() {
const [open, setOpen] = useState(false);
const [rpContext, setRpContext] = useState<RpContext | null>(null);
const [status, setStatus] = useState<string>('idle');
const onVerify = async () => {
setStatus('loading');
const rp = await fetchRpContext(ACTION);
// Add branching logic here to show Widget outside of World App
if (MiniKit.isInWorldApp()) {
// World App: headless/native path (no widget UI)
const req = await IDKit.request({
app_id: APP_ID,
action: ACTION,
rp_context: rp,
allow_legacy_proofs: true,
environment: ENV,
}).preset(orbLegacy({ signal: `user-${Date.now()}` }));
const completion = await req.pollUntilCompletion();
if (!completion.success) {
setStatus(`error:${completion.error}`);
return;
}
await verifyProof(completion.result);
setStatus('verified:native');
return;
}
// Standalone web: open widget
setRpContext(rp);
setOpen(true);
setStatus('widget-open');
};
return (
<>
<button onClick={onVerify}>Verify</button>
<p>{status}</p>
{rpContext ? (
<IDKitRequestWidget
open={open}
onOpenChange={setOpen}
app_id={APP_ID}
action={ACTION}
rp_context={rpContext}
allow_legacy_proofs={true}
preset={orbLegacy({ signal: `user-${Date.now()}` })}
environment={ENV}
onSuccess={async (result) => {
await verifyProof(result);
setStatus('verified:widget');
}}
onError={(code) => setStatus(`error:${code}`)}
/>
) : null}
</>
);
}
2) Transactions
Adding Wagmi provider. While World App will automatically bundle transactions into multiCall most wallets will not do this. Thus you should consider wrapping your call in a multi call when outside of World App for best compatibility.
pnpm add @worldcoin/minikit-js wagmi viem @tanstack/react-query
// src/providers/wagmi-config.ts
import { worldApp } from '@worldcoin/minikit-js/wagmi';
import { worldchain } from 'viem/chains';
import { http } from 'viem';
import { createConfig } from 'wagmi';
import { injected } from 'wagmi/connectors';
export const wagmiConfig = createConfig({
chains: [worldchain],
transports: {
[worldchain.id]: http('https://worldchain-mainnet.g.alchemy.com/public'),
},
connectors: [
worldApp(), // native in World App
injected(), // web fallback connector
],
});
import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
import { MiniKitProvider } from '@worldcoin/minikit-js/provider';
import { WagmiProvider } from 'wagmi';
import { wagmiConfig } from './wagmi-config';
const queryClient = new QueryClient();
export function ClientProviders({ children }: { children: React.ReactNode }) {
return (
// NEW QueryClientProvider and WagmiProvider recommended for robust performance
<QueryClientProvider client={queryClient}>
<WagmiProvider config={wagmiConfig}>
<MiniKitProvider
props={{
appId: process.env.NEXT_PUBLIC_APP_ID ?? '',
wagmiConfig, // NEW: pass explicitly for robust fallback behavior
}}
>
{children}
</MiniKitProvider>
</WagmiProvider>
</QueryClientProvider>
);
}
Once these are set up your application will automatically support transaction logic externally without any refactoring to core command calls.
3) Non Transaction Command fallbacks
MiniKit v2 is designed to work both inside and outside of World App. By adding custom fallbacks to commands, you can ensure your app behaves gracefully when users access it outside of World App.
Standalone App to Mini App
1) World ID
No changes, simply enable the mini app option under configuration in the developer portal.
2) Transactions
Wagmi: Add MiniKitProvider to your root and use the worldApp provider from the examples in the previous section
If you’re using instead using Viem or Ethers. Add a branch for World App and use the getWorldAppProvider helper to automatically
bridge your commands.
import { MiniKit, getWorldAppProvider } from '@worldcoin/minikit-js'
import { BrowserProvider } from 'ethers' // or viem custom transport
const inWorldApp = MiniKit.isInWorldApp()
if (inWorldApp) {
const provider = new BrowserProvider(getWorldAppProvider())
const signer = await provider.getSigner()
// signer.sendTransaction / signMessage works via MiniKit bridge
} else {
// your normal viem/ethers provider flow
}