> ## Documentation Index
> Fetch the complete documentation index at: https://www.dynamic.xyz/docs/llms.txt
> Use this file to discover all available pages before exploring further.

# Build a Complete World Mini App with Dynamic

> Integrate Dynamic's wallet management and authentication in World Mini Apps with cross-wallet transfers.

Dynamic provides two powerful ways to enhance your World Mini App:

1. **External Wallet Connection & Embedded Wallets**

   * Connect users' existing external wallets (like MetaMask, Rainbow, etc.)
   * Create new embedded wallets for users who don't have one, using their email, phone number, OAuth, etc.
   * Manage multiple wallet connections seamlessly

2. **Sign-In with Ethereum (SIWE) for World ID Wallets**
   * Implement secure authentication using World ID wallets
   * Enable users to interact with their World ID wallet

In this guide, we'll implement both approaches and demonstrate how to create an integration that allows users to:

* Create embedded wallets or connect external wallets
* Sign in with their World ID wallet
* Transfer funds between their Dynamic wallet and World ID wallet
* Support multiple tokens (ETH, WLD, USDC)

You can find the complete source code for this guide in our [GitHub repository](https://github.com/dynamic-labs/dynamic-world).

<Frame>
  <iframe src="https://www.loom.com/embed/60ca5996b4954af4ab1d99a608f2b724" frameborder="0" webkitallowfullscreen mozallowfullscreen allowfullscreen width="100%" height="640" />
</Frame>

## Setup

### Dynamic Environment Setup

1. Navigate to the [Dynamic Dashboard](https://app.dynamic.xyz/dashboard)
2. Copy your `Environment ID` from the SDK and API Keys section - you'll need this to initialize Dynamic in your World Mini App

### Create your app

If you don't have a project yet, scaffold **Next.js** with [`create-next-app`](https://nextjs.org/docs/app/api-reference/cli/create-next-app), then follow the [React Quickstart](/react/reference/quickstart) **Custom setup** for **Ethereum (EVM)** with **Wagmi** and **viem** so `DynamicContextProvider`, Wagmi, and `EthereumWalletConnectors` match the providers in this guide (see the [GitHub repository](https://github.com/dynamic-labs/dynamic-world) for a full layout).

<Info>
  **Dashboard:** Under **Chains & Networks**, enable **Ethereum** and any networks you use. Under **Sign-in Methods** and **Wallets**, enable the login methods, **Embedded wallets**, and **external wallets** as needed for World ID and Dynamic. Under **Security** → **Allowed Origins**, add the origin where the app runs (for example `http://localhost:3000`).
</Info>

## Getting Started

### 1. Install Required Dependencies

First, install the necessary packages:

<CodeGroup>
  ```bash npm theme={"system"}
  npm install @dynamic-labs/sdk-api-core @dynamic-labs/utils @worldcoin/minikit-js @worldcoin/minikit-react eruda
  ```

  ```bash yarn theme={"system"}
  yarn add @dynamic-labs/sdk-api-core @dynamic-labs/utils @worldcoin/minikit-js @worldcoin/minikit-react eruda
  ```

  ```bash bun theme={"system"}
  bun add @dynamic-labs/sdk-api-core @dynamic-labs/utils @worldcoin/minikit-js @worldcoin/minikit-react eruda
  ```

  ```bash pnpm theme={"system"}
  pnpm add @dynamic-labs/sdk-api-core @dynamic-labs/utils @worldcoin/minikit-js @worldcoin/minikit-react eruda
  ```
</CodeGroup>

### 2. Set Up Project Structure

Create the following project structure:

```
world-miniapp/
├── .env.local                   # Environment variables
├── app/
│   ├── page.tsx                 # Main app page
│   ├── layout.tsx               # App layout
│   ├── globals.css              # Global styles
│   └── components/
│       ├── WorldDynamicSIWE.tsx # SIWE authentication component
│       ├── WalletTransfers.tsx  # Cross-wallet transfer component
├── lib/
│   ├── providers.tsx            # Dynamic and other providers
│   ├── utils.ts                 # General utility functions
│   └── wagmi.ts                 # Wagmi configuration
```

### 3. Configure Environment Variables

Create a `.env.local` file with your credentials:

```env theme={"system"}
NEXT_PUBLIC_DYNAMIC_ENVIRONMENT_ID=your-dynamic-environment-id-here
```

## Implementation

### 1. SIWE Authentication Setup

First, let's set up the authentication providers that will connect Dynamic's SIWE functionality with Worldcoin's wallet:

```tsx lib/providers.tsx theme={"system"}
"use client";

import { EthereumWalletConnectors } from "@dynamic-labs/ethereum";
import { DynamicContextProvider } from "@dynamic-labs/sdk-react-core";
import { DynamicWagmiConnector } from "@dynamic-labs/wagmi-connector";
import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
import { MiniKitProvider } from "@worldcoin/minikit-js/minikit-provider";
import { useEffect } from "react";
import { WagmiProvider } from "wagmi";
import { DYNAMIC_ENVIRONMENT_ID } from "./utils";
import { config } from "./wagmi";

export default function Providers({ children }: { children: React.ReactNode }) {
  const queryClient = new QueryClient();

  // Initialize eruda for debugging in the World App environment
  useEffect(() => {
    import("eruda").then((eruda) => {
      eruda.default.init();
    });
  }, []);

  return (
    <DynamicContextProvider
      theme="auto"
      settings={{
        environmentId: DYNAMIC_ENVIRONMENT_ID,
        walletConnectors: [EthereumWalletConnectors],
      }}
    >
      <WagmiProvider config={config}>
        <QueryClientProvider client={queryClient}>
          <DynamicWagmiConnector>
            <MiniKitProvider>{children}</MiniKitProvider>
          </DynamicWagmiConnector>
        </QueryClientProvider>
      </WagmiProvider>
    </DynamicContextProvider>
  );
}
```

The providers setup includes:

* Dynamic's context provider for wallet management
* Wagmi provider for Ethereum interactions
* MiniKit provider for World ID wallet integration
* Eruda for debugging in the World App environment

### 2. Create Utility Functions

Create the Dynamic API helper and other utility functions:

```typescript lib/utils.ts theme={"system"}
"use client";

import { Configuration, SDKApi } from "@dynamic-labs/sdk-api-core";
import {
  getAuthToken,
  VERSION as SDKVersion,
} from "@dynamic-labs/sdk-react-core";
import { FetchService } from "@dynamic-labs/utils";

// Constants
export const DYNAMIC_ENVIRONMENT_ID =
  process.env.NEXT_PUBLIC_DYNAMIC_ENVIRONMENT_ID!;

// Dynamic API helper for authentication and verification
export const dynamicApi = () => {
  const settings = {
    basePath: "https://app.dynamicauth.com/api/v0",
    headers: {
      "Content-Type": "application/json",
      "x-dyn-version": `WalletKit/${SDKVersion}`,
      "x-dyn-api-version": "API/0.0.507",
      Authorization: "",
    },
  };

  const minJwt = getAuthToken();
  if (minJwt) {
    settings.headers.Authorization = `Bearer ${minJwt}`;
  }

  return new SDKApi(
    new Configuration({
      ...settings,
      fetchApi: FetchService.fetch,
    })
  );
};
```

### 3. Create SIWE Authentication Component

Create a new file `components/WorldDynamicSIWE.tsx` that integrates Worldcoin's MiniKit with Dynamic's SIWE flow:

```tsx app/components/WorldDynamicSIWE.tsx theme={"system"}
"use client";

import { generateMessageToSign } from "@dynamic-labs/multi-wallet";
import { VerifyRequestFromJSON } from "@dynamic-labs/sdk-api-core";
import { useRefreshUser, useDynamicContext } from "@dynamic-labs/sdk-react-core";
import { MiniKit } from "@worldcoin/minikit-js";
import { FC, useState } from "react";
import { DYNAMIC_ENVIRONMENT_ID, dynamicApi } from "@/lib/utils";

const WorldDynamicSIWE: FC = () => {
  const refreshUser = useRefreshUser();
  const { user, primaryWallet } = useDynamicContext();
  const [isLoading, setIsLoading] = useState(false);
  const [error, setError] = useState<string | null>(null);
  const [siweData, setSiweData] = useState<any>(null);

  // Safe JSON stringification to handle circular references
  const safeStringify = (obj: unknown): string => {
    const seen = new WeakSet();
    return JSON.stringify(obj, (_, value) => {
      if (typeof value === "object" && value !== null) {
        if (seen.has(value)) return "[Circular]";
        seen.add(value);
      }
      return value;
    }, 2);
  };

  // Nonce management functions
  const storeNonce = (nonce: string) => {
    localStorage.setItem("dynamic_nonce", JSON.stringify({ value: nonce }));
  };

  const consumeNonce = () => {
    const nonceString = localStorage.getItem("dynamic_nonce");
    if (nonceString) {
      const nonceObject = JSON.parse(nonceString);
      localStorage.removeItem("dynamic_nonce");
      return nonceObject.value;
    }
    return null;
  };

  const generateDynamicNonce = async (): Promise<string> => {
    const response = await fetch(
      `https://app.dynamicauth.com/api/v0/sdk/${DYNAMIC_ENVIRONMENT_ID}/nonce`,
      { method: "GET" }
    );
    const data = await response.json();
    return data.nonce;
  };

  // Verify user with Dynamic API
  const callVerifyUser = async (
    messageToSign: string,
    address: string,
    signedMessage: string
  ) => {
    const verifyRequest = VerifyRequestFromJSON({
      chain: "EVM",
      messageToSign,
      network: "1",
      publicWalletAddress: address,
      signedMessage,
      walletName: "worldcoin",
      walletProvider: "browserExtension",
    });

    const response = await dynamicApi().verify({
      environmentId: DYNAMIC_ENVIRONMENT_ID,
      verifyRequest,
    });

    // Store authentication tokens
    window.localStorage.setItem(
      "dynamic_authentication_token",
      JSON.stringify(response.jwt)
    );
    window.localStorage.setItem(
      "dynamic_min_authentication_token",
      JSON.stringify(response.jwt)
    );

    return response;
  };

  // Main authentication handler
  const handleWorldcoinSIWE = async () => {
    setIsLoading(true);
    setError(null);

    try {
      if (!primaryWallet) {
        throw new Error("No wallet connected");
      }

      // Generate and store nonce
      const dynamicNonce = await generateDynamicNonce();
      storeNonce(dynamicNonce);

      // Initiate Worldcoin authentication
      const { finalPayload } = await MiniKit.commandsAsync.walletAuth({
        nonce: dynamicNonce,
      });

      // Generate message to sign
      const messageToSign = generateMessageToSign({
        blockchain: "EVM",
        chainId: 1,
        domain: window.location.host,
        nonce: consumeNonce(),
        publicKey: primaryWallet.address,
        requestId: DYNAMIC_ENVIRONMENT_ID,
        uri: window.location.href,
      });

      // Sign message and verify
      const signature = await primaryWallet.signMessage(messageToSign);
      const verifyResponse = await callVerifyUser(
        messageToSign,
        primaryWallet.address,
        signature
      );

      // Update user and store data
      await refreshUser();
      setSiweData({
        message: messageToSign,
        signature,
        address: primaryWallet.address,
        verifyResponse,
        worldcoinPayload: JSON.parse(safeStringify(finalPayload)),
        walletSource: primaryWallet.connector?.name || "Unknown",
        connectedDynamic: true,
      });
    } catch (err) {
      setError(err instanceof Error ? err.message : "SIWE authentication failed");
    } finally {
      setIsLoading(false);
    }
  };

  return (
    <div className="world-dynamic-siwe">
      <h3>World ID SIWE Authentication</h3>

      {error && (
        <div className="error-message">
          <p>Error: {error}</p>
        </div>
      )}

      {!siweData ? (
        <button
          onClick={handleWorldcoinSIWE}
          disabled={isLoading || !primaryWallet}
          className="siwe-button"
        >
          {isLoading ? "Authenticating..." : "Sign-In with Worldcoin"}
        </button>
      ) : (
        <div className="siwe-data">
          <h4>Authentication Success!</h4>
          <div className="data-display">
            <p><strong>Wallet Source:</strong> {siweData.walletSource}</p>
            <p><strong>Connected to Dynamic:</strong> {siweData.connectedDynamic ? "Yes" : "No"}</p>
            <p><strong>Address:</strong> <code>{siweData.address}</code></p>
            <details>
              <summary>View Complete SIWE Data</summary>
              <pre>{JSON.stringify(siweData, null, 2)}</pre>
            </details>
          </div>
        </div>
      )}
    </div>
  );
};

export default WorldDynamicSIWE;
```

In this file, we're first generating a nonce for the user, then, we ask the user to sign in using their world ID wallet. Finally, we call the Dynamic API to verify the user's signature and sign the user in.

You can find the styles that I've used [here](https://github.com/dynamic-labs/dynamic-world/blob/main/app/components/WorldDynamicSIWE.css).

### 4. Create Main App Page

Create the main page of your World Mini App:

```tsx app/page.tsx theme={"system"}
"use client";

import { MiniKit } from "@worldcoin/minikit-js";
import Image from "next/image";
import { useEffect, useState } from "react";
import WorldDynamicSIWE from "./components/WorldDynamicSIWE";
import WalletTransfers from "./components/WalletTransfers";
import { DynamicWidget } from "@dynamic-labs/sdk-react-core";

export default function Main() {
  const [isMiniKitInstalled, setIsMiniKitInstalled] = useState<boolean | null>(null);

  useEffect(() => {
    const checkMiniKitInstallation = async () => {
      try {
        const result = await MiniKit.install();
        setIsMiniKitInstalled(result.success);
      } catch (error) {
        console.error("Failed to install MiniKit:", error);
        setIsMiniKitInstalled(false);
      }
    };

    checkMiniKitInstallation();
  }, []);

  return (
    <div className="container">
      <div className="header">
        <Image
          className="logo"
          src="/logo-dark.png"
          alt="dynamic"
          width={200}
          height={20}
          priority
        />
      </div>

      {isMiniKitInstalled === false && (
        <div className="warning-banner">
          This is a World Mini App. Please open it within the World App for full functionality.
        </div>
      )}

      <div className="app-title">
        <h1>World ID + Dynamic Integration</h1>
      </div>

      <div className="modal">
        <div className="wallet-connect-section">
          <DynamicWidget />
          <WorldDynamicSIWE />
        </div>

        <div className="wallet-funding-section">
          <h3>Wallet Transfers</h3>
          <WalletTransfers />
        </div>
      </div>

      <div className="footer">
        <div>Made with 💙 by Dynamic</div>
      </div>
    </div>
  );
}
```

Here, we're checking if the user has the World ID Mini App installed, and if not, we're showing a warning banner. We're also showing the Dynamic Widget and the World Dynamic SIWE component.

### 5. Add Wallet Transfer Capabilities

Create the cross-wallet transfer component:

```tsx app/components/WalletTransfers.tsx theme={"system"}
"use client";

import { isEthereumWallet } from "@dynamic-labs/ethereum";
import { useDynamicContext } from "@dynamic-labs/sdk-react-core";
import { MiniKit, Tokens, tokenToDecimals } from "@worldcoin/minikit-js";
import { FC, useEffect, useState } from "react";
import { parseEther } from "viem";
import { ForwardABI } from "@/consts/ForwardABI";

export enum TokenSymbol {
  ETH = "ETH",
  WLD = "WLD",
  USDC = "USDC",
}

// Token contract addresses
const WLD_TOKEN_ADDRESS = "0x2cFc85d8E48F8EAB294be644d9E25C3030863003";
const USDC_TOKEN_ADDRESS = "0x79A02482A880bCE3F13e09Da970dC34db4CD24d1";


export interface FundingFormState {
  amount: string;
  tokenSymbol: TokenSymbol;
  recipientAddress: string;
  direction: "dynamic-to-world" | "world-to-dynamic";
}

const WalletTransfers: FC = () => {
  const { user, primaryWallet } = useDynamicContext();
  const [isLoading, setIsLoading] = useState(false);
  const [error, setError] = useState<string | null>(null);
  const [result, setResult] = useState<string | null>(null);
  const [worldIdWalletAddress, setWorldIdWalletAddress] = useState<string | null>(null);

  const [fundingForm, setFundingForm] = useState<FundingFormState>({
    amount: "0.001",
    tokenSymbol: TokenSymbol.ETH,
    recipientAddress: "",
    direction: "world-to-dynamic",
  });

  // Get World ID wallet address
  const getWorldIdWallet = async () => {
    try {
      if (!(await MiniKit.isInstalled())) {
        return null;
      }

      const { finalPayload } = await MiniKit.commandsAsync.signMessage({
        message: "Get wallet address",
      });

      if (finalPayload.status === "error") {
        setError("Failed to get World ID wallet address");
        return null;
      }

      setWorldIdWalletAddress(finalPayload.address);
      return finalPayload.address;
    } catch (error) {
      setError("Failed to get World ID wallet address");
      return null;
    }
  };

  useEffect(() => {
    getWorldIdWallet();

    if (user?.verifiedCredentials?.[0]?.address) {
      setFundingForm((prev) => ({
        ...prev,
        recipientAddress: user.verifiedCredentials[0].address || "",
      }));
    }
  }, [user]);

  const handleInputChange = (
    e: React.ChangeEvent<HTMLInputElement | HTMLSelectElement>
  ) => {
    const { name, value } = e.target;
    setFundingForm((prev) => {
      if (name === "direction") {
        const direction = value as "world-to-dynamic" | "dynamic-to-world";
        const recipientAddress =
          direction === "world-to-dynamic"
            ? user?.verifiedCredentials?.[0]?.address || ""
            : worldIdWalletAddress || "";
        return { ...prev, direction, recipientAddress };
      }
      return { ...prev, [name]: value };
    });
  };

  const handleTransfer = async () => {
    setIsLoading(true);
    setError(null);
    setResult(null);

    try {
      const { amount, tokenSymbol, direction } = fundingForm;

      if (!amount || parseFloat(amount) <= 0) {
        setError("Please provide a valid amount greater than 0");
        return;
      }

      const finalRecipientAddress =
        direction === "world-to-dynamic"
          ? user?.verifiedCredentials?.[0]?.address
          : worldIdWalletAddress;

      if (!finalRecipientAddress) {
        setError(
          direction === "world-to-dynamic"
            ? "Dynamic wallet not connected"
            : "World ID wallet not connected"
        );
        return;
      }

      if (direction === "world-to-dynamic" && !(await MiniKit.isInstalled())) {
        setError("MiniKit not installed");
        return;
      }

      const paymentReference = `payment-${Date.now()}`;

      if (tokenSymbol === TokenSymbol.ETH) {
        await handleETHTransfer(amount, finalRecipientAddress, direction);
      } else {
        await handleTokenTransfer(tokenSymbol, amount, finalRecipientAddress, direction, paymentReference);
      }
    } catch (error: any) {
      setError(error instanceof Error ? error.message : "Failed to transfer funds");
    } finally {
      setIsLoading(false);
    }
  };

  const handleETHTransfer = async (amount: string, recipient: string, direction: string) => {
    if (direction === "dynamic-to-world") {
      if (!primaryWallet || !isEthereumWallet(primaryWallet)) {
        throw new Error("Dynamic wallet not connected or not Ethereum compatible");
      }

      const walletClient = await primaryWallet.getWalletClient();
      const publicClient = await primaryWallet.getPublicClient();

      const hash = await walletClient.sendTransaction({
        to: recipient as `0x${string}`,
        value: parseEther(amount),
      });

      const receipt = await publicClient.waitForTransactionReceipt({ hash });
      setResult(
        `Successfully sent ${amount} ETH to ${recipient.substring(0, 8)}...${recipient.substring(
          recipient.length - 6
        )}. TX: ${receipt.transactionHash.substring(0, 12)}...`
      );
    } else {
      const { finalPayload } = await MiniKit.commandsAsync.sendTransaction({
        transaction: [
          {
            address: "0x087d5449a126e4e439495fcBc62A853eB3257936",
            abi: ForwardABI,
            functionName: "pay",
            args: [recipient],
            value: `0x${parseEther(amount).toString(16)}`,
          },
        ],
      });

      if (finalPayload.status === "error") {
        throw new Error("Transaction failed");
      }

      setResult(
        `Successfully sent ${amount} ETH to ${recipient.substring(0, 8)}...${recipient.substring(
          recipient.length - 6
        )}. TX: ${finalPayload.transaction_id.substring(0, 12)}...`
      );
    }
  };

  const handleTokenTransfer = async (
    tokenSymbol: TokenSymbol,
    amount: string,
    recipient: string,
    direction: string,
    paymentReference: string
  ) => {
    if (direction === "dynamic-to-world") {
      if (!primaryWallet || !isEthereumWallet(primaryWallet)) {
        throw new Error("Dynamic wallet not connected or not Ethereum compatible");
      }

      const walletClient = await primaryWallet.getWalletClient();
      const publicClient = await primaryWallet.getPublicClient();

      const tokenAddress = tokenSymbol === TokenSymbol.WLD ? WLD_TOKEN_ADDRESS : USDC_TOKEN_ADDRESS;
      const decimals = tokenSymbol === TokenSymbol.WLD ? 18 : 6;
      const amountInWei = BigInt(parseFloat(amount) * Math.pow(10, decimals));

      const transferABI = [
        {
          name: "transfer",
          type: "function",
          stateMutability: "nonpayable",
          inputs: [
            { name: "to", type: "address" },
            { name: "amount", type: "uint256" },
          ],
          outputs: [{ name: "", type: "bool" }],
        },
      ];

      const hash = await walletClient.writeContract({
        address: tokenAddress as `0x${string}`,
        abi: transferABI,
        functionName: "transfer",
        args: [recipient, amountInWei],
      });

      const receipt = await publicClient.waitForTransactionReceipt({ hash });
      setResult(
        `Successfully sent ${amount} ${tokenSymbol} to ${recipient.substring(0, 8)}...${recipient.substring(
          recipient.length - 6
        )}. TX: ${receipt.transactionHash.substring(0, 12)}...`
      );
    } else {
      const { finalPayload } = await MiniKit.commandsAsync.pay({
        reference: paymentReference,
        to: recipient,
        tokens: [
          {
            symbol: tokenSymbol === TokenSymbol.WLD ? Tokens.WLD : Tokens.USDCE,
            token_amount: tokenToDecimals(
              parseFloat(amount),
              tokenSymbol === TokenSymbol.WLD ? Tokens.WLD : Tokens.USDCE
            ).toString(),
          },
        ],
        description: `Transfer ${amount} ${tokenSymbol} to ${recipient.substring(0, 8)}...`,
      });

      if (finalPayload.status === "error") {
        throw new Error("Transaction failed");
      }

      setResult(
        `Successfully sent ${amount} ${tokenSymbol} to ${recipient.substring(0, 8)}...${recipient.substring(
          recipient.length - 6
        )}. TX: ${finalPayload.transaction_id.substring(0, 12)}...`
      );
    }
  };
  return (
    <div className="world-funding">
      <div className="wallets-container">
        {worldIdWalletAddress ? (
          <div className="wallet-info">
            <div className="wallet-header">
              <span className="wallet-icon">🌎</span>
              <div className="wallet-details">
                <span className="wallet-label">World ID Wallet</span>
                <code className="wallet-address">
                  {worldIdWalletAddress.substring(0, 8)}...
                  {worldIdWalletAddress.substring(worldIdWalletAddress.length - 6)}
                </code>
                <button
                  onClick={() => {
                    navigator.clipboard.writeText(worldIdWalletAddress);
                    setResult("World ID wallet address copied to clipboard!");
                  }}
                  className="copy-button"
                  title="Copy Address"
                >
                  📋
                </button>
              </div>
            </div>
          </div>
        ) : (
          <div className="wallet-connect-prompt">
            <p>World ID wallet not detected. Open in World App to connect.</p>
          </div>
        )}

        {user?.verifiedCredentials?.[0]?.address && (
          <div className="wallet-info connected-dynamic">
            <div className="wallet-header">
              <span className="wallet-icon">💼</span>
              <div className="wallet-details">
                <span className="wallet-label">Connected Wallet</span>
                <code className="wallet-address">
                  {user.verifiedCredentials[0].address.substring(0, 8)}...
                  {user.verifiedCredentials[0].address.substring(
                    user.verifiedCredentials[0].address.length - 6
                  )}
                </code>
              </div>
            </div>
          </div>
        )}
      </div>

      {error && (
        <div className="error-message">
          <p>Error: {error}</p>
        </div>
      )}

      {result && (
        <div className="success-message">
          <p>{result}</p>
        </div>
      )}

      <div className="funding-form">
        <h4>Transfer Funds</h4>

        <div className="form-group">
          <label htmlFor="direction">Transfer Direction:</label>
          <select
            id="direction"
            name="direction"
            value={fundingForm.direction}
            onChange={handleInputChange}
            className="direction-select"
          >
            <option value="world-to-dynamic">World ID → Dynamic Wallet</option>
            <option value="dynamic-to-world">Dynamic → World ID Wallet</option>
          </select>
        </div>

        <div className="form-group">
          <label htmlFor="amount">Amount:</label>
          <div className="input-with-select">
            <input
              type="number"
              id="amount"
              name="amount"
              step="0.0001"
              value={fundingForm.amount}
              onChange={handleInputChange}
              min="0.0001"
            />
            <select
              id="tokenSymbol"
              name="tokenSymbol"
              value={fundingForm.tokenSymbol}
              onChange={handleInputChange}
            >
              <option value={TokenSymbol.ETH}>ETH</option>
              <option value={TokenSymbol.WLD}>WLD</option>
              <option value={TokenSymbol.USDC}>USDC</option>
            </select>
          </div>
        </div>

        <div className="form-group">
          <label htmlFor="recipientAddress">Recipient Address:</label>
          <input
            type="text"
            id="recipientAddress"
            name="recipientAddress"
            placeholder="0x..."
            value={fundingForm.recipientAddress}
            onChange={handleInputChange}
          />
        </div>

        <button
          onClick={handleTransfer}
          disabled={isLoading || !fundingForm.recipientAddress || !worldIdWalletAddress}
          className="transfer-button"
        >
          {isLoading ? "Processing..." : "Transfer Funds"}
        </button>
      </div>
    </div>
  );
};

export default WalletTransfers;
```

You also need to create a consts folder and add the ForwardABI to it. You can find the ABI [here](https://worldscan.org/address/0x087d5449a126e4e439495fcBc62A853eB3257936#code).

Here, a couple of things are happening:

* Firstly we have a pretty straight forward logic for handling the UI of which token to transfer from where to where.
* To pay from a user's dynamic wallet we're using the `sendTransaction` method from the viem public client.
* To pay USDC, and WLD from a user's world ID wallet we're using the `pay` method from the MiniKit. Check out the [MiniKit documentation](https://docs.world.org/mini-apps/commands/pay) for more details.
* We're also using the `ForwardABI` to pay ETH from a user's world ID wallet. Check out the [Send Transaction MiniKit documentation](https://docs.world.org/mini-apps/commands/send-transaction#using-eth) for more details.

You can find the styles that I've used [here](https://github.com/dynamic-labs/dynamic-world/blob/main/app/components/WalletTransfers.css).

## Social Redirects Setup

World Mini Apps can integrate with Dynamic's social authentication system to provide seamless OAuth redirects back to your mini app. This section covers how to configure social redirects to work with the World Mini App deeplink system.

### Overview

When users authenticate with social providers (Google, Apple, Twitter, etc.) in your World Mini App, Dynamic handles the OAuth flow and redirects users back to your mini app using the World App's deeplink system. The authentication flow works as follows:

1. User initiates social login in your World Mini App
2. The phone browser opens to show the social provider
3. After successful authentication, the World App opens automatically
4. The Dynamic SDK completes the authentication inside the World Mini App

### Prerequisites

Before setting up social redirects, you need to:

1. **Enable social providers** - Configure the social providers you want to use by following the [Social Providers documentation](/overview/social-providers/overview)
2. **Have a World Mini App ID** - Get your app ID from the [Worldcoin Developer Portal](https://developer.worldcoin.org/)

### Whitelisting World Mini App Deeplink URLs

For security, you must whitelist your World Mini App deeplink URLs in the Dynamic Dashboard:

1. Go to [Dynamic Dashboard Security](https://app.dynamic.xyz/dashboard/security)
2. Navigate to **Security** → **Whitelist Mobile Deeplink**
3. Enable "Whitelist Mobile Deeplink" and click "Save changes"
4. Click the cog icon to open the Mobile Deeplink URL configuration
5. Add your World Mini App deeplink URL: `worldapp://mini-app?app_id=${YOUR_APP_ID}&*`
6. Save the configuration

<Tip>
  Make sure to replace `${YOUR_APP_ID}` with your actual World Mini App ID from the Worldcoin Developer Portal.
</Tip>

### Configuring redirectUrl in DynamicContextProvider

Configure the `redirectUrl` property in your `DynamicContextProvider` with your mini app deeplink URL:

```tsx theme={"system"}
<DynamicContextProvider
  settings={{
    environmentId: DYNAMIC_ENVIRONMENT_ID,
    walletConnectors: [EthereumWalletConnectors],
    redirectUrl: `worldapp://mini-app?app_id=${YOUR_APP_ID}&path=/`,
  }}
>
  <YourAppComponents />
</DynamicContextProvider>
```

The `redirectUrl` should follow this format:

* `worldapp://mini-app` - The World App deeplink scheme
* `app_id=${YOUR_APP_ID}` - Your World Mini App ID from the developer portal
* `path=/auth/callback` - The path within your mini app to handle the auth callback

For more information about the `redirectUrl` property, see the [DynamicContextProvider documentation](/react/reference/providers/dynamiccontextprovider#redirecturl).

### Example Implementation

For a complete working example of social redirects in a World Mini App, check out our [World Mini App example repository](https://github.com/dynamic-labs/world-mini-app) on GitHub. This repository demonstrates the complete integration including social authentication setup and deeplink configuration.

## Running Your World Mini App

To run your World Mini App with Worldcoin SIWE integration:

```bash theme={"system"}
npm run dev
```

In the development environment, you can use [Ngrok](https://ngrok.com/) or [cloudflared](https://developers.cloudflare.com/cloudflare-one/connections/connect-networks/downloads/) to expose your local server to the internet, allowing you to test the World Mini App in the World App.

Head over to the [Worldcoin Developer dashboard](https://developer.worldcoin.org/), create a new app and add your tunnel URL as the app URL. This will allow you to test your Dynamic-powered World Mini App in the World App. Make sure to add the `Forward` contract address to the app under configuration > Advanced > Contract Entrypoints. You can find the contract address [here](https://worldscan.org/address/0x087d5449a126e4e439495fcBc62A853eB3257936).

Finally, you can open it in the World App by scanning the QR code or entering the URL directly.

<Note>
  Make sure to add your domain to `next.config.js` in `allowedDevOrigins`:

  ```javascript next.config.js theme={"system"}
  /** @type {import('next').NextConfig} */
  const nextConfig = {
    allowedDevOrigins: [
      "http://localhost:3000",
      "https://*.ngrok-free.app",
    ],
  };

  module.exports = nextConfig;
  ```
</Note>

## Resources

* [Complete source code](https://github.com/dynamic-labs/dynamic-world)
* [Dynamic Documentation](https://dynamic.xyz/docs/)
* [Worldcoin MiniApps Documentation](https://docs.world.org/mini-apps)
