Skip to content

Project setup

This page covers recommended project structure, required config files, and environment variables for a Mini App. There is no MiniPay-specific manifest or registration file—listing in Discover is done via the submission form.

A typical Mini App (e.g. Vite + React) looks like this:

my-mini-app/
├── public/
├── src/
│   ├── components/     # UI components (e.g. WalletConnection)
│   ├── hooks/          # useAutoConnect, useWallet, custom hooks
│   ├── lib/             # wagmi config, chains, tokens
│   ├── routes/         # If using TanStack Router (or pages/)
│   ├── App.tsx
│   ├── main.tsx
│   └── env.ts           # Optional: env validation; getEthereumProvider used only for custom transport (see Option B below)
├── .env.example
├── index.html
├── package.json
├── tsconfig.json
└── vite.config.ts

You can use any structure that fits your stack; the important parts are a Wagmi config that uses the injected connector and Celo chains, and an auto-connect hook used at app root.

Required configs

Wagmi config

Create a config that uses the injected connector and Celo + Celo Sepolia only. MiniPay injects window.ethereum; the injected connector will use it automatically.

Option A — Simple (http transport):

ts
// src/lib/wagmi.ts or src/wagmi.ts
import { http } from "viem";
import { createConfig } from "wagmi";
import { injected } from "wagmi/connectors";
import { celo, celoSepolia } from "wagmi/chains";

export const config = createConfig({
  chains: [celoSepolia, celo],
  connectors: [injected()],
  transports: {
    [celo.id]: http(),
    [celoSepolia.id]: http(),
  },
});

Option B — Custom transport (full provider passthrough):

If you need every RPC call to go through the injected provider (e.g. for logging or compatibility), use a custom transport:

ts
import { custom } from "viem";
import { createConfig } from "wagmi";
import { injected } from "wagmi/connectors";
import { celo, celoSepolia } from "wagmi/chains";

function getEthereumProvider() {
  if (typeof window === "undefined" || !window.ethereum) {
    throw new Error(
      "window.ethereum is required. Run this app inside MiniPay."
    );
  }
  return window.ethereum;
}

const provider = getEthereumProvider();

export const config = createConfig({
  chains: [celoSepolia, celo],
  connectors: [injected()],
  transports: {
    [celo.id]: custom(provider),
    [celoSepolia.id]: custom(provider),
  },
});

Vite config

For local development you will often expose your dev server via ngrok so you can load it in MiniPay. Vite must allow that host:

ts
// vite.config.ts
import react from "@vitejs/plugin-react";
import { defineConfig } from "vite";

export default defineConfig({
  plugins: [react()],
  server: {
    allowedHosts: [".ngrok.app", ".ngrok-free.dev", ".ngrok-free.app"],
  },
  build: {
    outDir: "dist",
  },
});

Environment variables

  • If you use Vite, use the VITE_ prefix so Vite exposes them to the client: VITE_API_URL, VITE_APP_NAME, etc.
  • Do not put secrets (API keys for backend-only use) in VITE_* — they would be visible in the bundle.
  • Optional: validate with Zod (or similar) so missing required vars fail fast.

Example .env.example:

bash
# Optional: public config (no secrets)
VITE_APP_NAME=My Mini App
# VITE_API_URL=https://api.example.com

Example usage in code:

ts
const apiUrl = import.meta.env.VITE_API_URL;

Optional validation (e.g. in src/env.ts):

ts
import { z } from "zod";

const envSchema = z.object({
  VITE_APP_NAME: z.string().default("My Mini App"),
});

export const env = envSchema.parse(import.meta.env);

Listing in Discover

There is no manifest or config file in your repo for MiniPay. To get your Mini App listed in MiniPay's Discover page, use the submission form. You will need: app name, tagline, publisher, support URL, category, app URL, and icon. Details are in Submit your Mini App.

Next steps