--- url: /getting-started/availability.md --- # Availability MiniPay is available in the following countries: | Country | Android | iOS | Inside Opera Mini (Android) | Continent | |-------------------------|---------|-----|-----------------------------|---------------| | Benin | ✅ | ✅ | invite-only | Africa | | Burkina Faso | ✅ | ✅ | invite-only | Africa | | Cameroon | ✅ | ✅ | invite-only | Africa | | Chad | ✅ | ✅ | invite-only | Africa | | Congo - Brazzaville | ✅ | ✅ | invite-only | Africa | | Congo - Kinshasa | ✅ | ✅ | invite-only | Africa | | Côte d'Ivoire | ✅ | ✅ | invite-only | Africa | | Gabon | ✅ | ✅ | invite-only | Africa | | Ghana | ✅ | ✅ | ✅ | Africa | | Guinea | ✅ | ❌ | invite-only | Africa | | Guinea-Bissau | ✅ | ✅ | invite-only | Africa | | Kenya | ✅ | ✅ | ✅ | Africa | | Mali | ✅ | ✅ | invite-only | Africa | | Malawi | ❌ | ✅ | invite-only | Africa | | Niger | ✅ | ✅ | invite-only | Africa | | Nigeria | ✅ | ✅ | ✅ | Africa | | Rwanda | ✅ | ✅ | invite-only | Africa | | Senegal | ✅ | ✅ | invite-only | Africa | | South Africa | ✅ | ✅ | ✅ | Africa | | Tanzania | ✅ | ✅ | ✅ | Africa | | Uganda | ✅ | ✅ | ✅ | Africa | | Zambia | ✅ | ✅ | invite-only | Africa | | Albania | ❌ | ❌ | invite-only | Europe | | Austria | ✅ | ✅ | invite-only | Europe | | Belgium | ✅ | ✅ | invite-only | Europe | | Croatia | ✅ | ✅ | invite-only | Europe | | Cyprus | ✅ | ✅ | invite-only | Europe | | Czech Republic | ✅ | ✅ | ❌ | Europe | | Denmark | ✅ | ✅ | ❌ | Europe | | Estonia | ✅ | ✅ | invite-only | Europe | | Finland | ✅ | ✅ | invite-only | Europe | | France | ✅ | ✅ | invite-only | Europe | | Germany | ✅ | ✅ | invite-only | Europe | | Greece | ✅ | ✅ | invite-only | Europe | | Ireland | ✅ | ✅ | invite-only | Europe | | Italy | ✅ | ✅ | invite-only | Europe | | Latvia | ✅ | ✅ | invite-only | Europe | | Lithuania | ✅ | ✅ | invite-only | Europe | | Luxembourg | ✅ | ✅ | invite-only | Europe | | Malta | ✅ | ✅ | invite-only | Europe | | Netherlands | ✅ | ✅ | invite-only | Europe | | Norway | ✅ | ✅ | invite-only | Europe | | Poland | ✅ | ✅ | invite-only | Europe | | Portugal | ✅ | ✅ | invite-only | Europe | | Slovakia | ✅ | ✅ | invite-only | Europe | | Slovenia | ✅ | ✅ | invite-only | Europe | | Spain | ✅ | ✅ | invite-only | Europe | | Sweden | ✅ | ✅ | invite-only | Europe | | Switzerland | ✅ | ✅ | invite-only | Europe | | United Kingdom | ✅ | ❌ | invite-only | Europe | | Russia | ❌ | ❌ | ❌ | Europe | | China | ❌ | ❌ | ❌ | Asia | | India | ✅ | ✅ | invite-only | Asia | | Indonesia | ✅ | ✅ | ❌ | Asia | | Japan | ❌ | ❌ | ❌ | Asia | | Malaysia | ✅ | ✅ | invite-only | Asia | | Philippines | ✅ | ✅ | invite-only | Asia | | South Korea | ❌ | ❌ | ❌ | Asia | | Thailand | ✅ | ✅ | invite-only | Asia | | Turkey | ✅ | ✅ | ✅ | Asia | | Vietnam | ✅ | ✅ | invite-only | Asia | | Australia | ✅ | ✅ | invite-only | Oceania | | New Zealand | ✅ | ✅ | invite-only | Oceania | | Canada | ✅ | ✅ | invite-only | North America | | Mexico | ✅ | ✅ | ❌ | North America | | United States | ✅ | ✅ | invite-only | North America | | Argentina | ✅ | ✅ | invite-only | South America | | Brazil | ✅ | ✅ | invite-only | South America | | Chile | ✅ | ✅ | invite-only | South America | | Colombia | ✅ | ✅ | invite-only | South America | | Peru | ✅ | ✅ | invite-only | South America | | Uruguay | ✅ | ✅ | invite-only | South America | | **All other countries** | ❌ | ❌ | invite-only | | --- --- url: /getting-started/best-practices.md --- # Best Practices Follow these best practices to build high-quality Mini Apps that provide excellent user experiences. **Use the recommended code snippets from this MiniPay developer documentation** for wallet connection, transactions, and balances; they are kept up to date with the wallet behavior. ## Wallet connection ### Run inside MiniPay and check the provider Your app expects to run inside MiniPay where `window.ethereum` is injected. Check for the provider and show a clear message (or throw) if it's missing: ```tsx if (typeof window.ethereum === "undefined") { return
This app must be opened from MiniPay.
; } // Or use getEthereumProvider() that throws — see Project setup / wallet-connection docs. ``` Optionally detect MiniPay: `window.ethereum?.isMiniPay === true`. ### Always auto-connect ✅ **Do**: Auto-connect on page load ```tsx useEffect(() => { if (connectors.length > 0) { connect({ connector: connectors[0] }); } }, [connectors, connect]); ``` ❌ **Don't**: Show a connect button ```tsx // Never do this in Mini Apps ``` ### No message signing for access Do not prompt users to **sign a message** to access your site or to authenticate. MiniPay connects automatically; users should not need to sign an arbitrary message to use your Mini App. ### Handle connection states Always handle connection states gracefully (Wagmi v3: use `useConnection()`): ```tsx import { useConnection } from "wagmi"; const { isConnected, isConnecting, address } = useConnection(); if (isConnecting) { return
Connecting to MiniPay...
; } if (!isConnected || !address) { return
Please open this app from MiniPay to connect to your wallet.
; } ``` ## Error Handling ### User-Friendly Error Messages Prefer error codes (from the JSON-RPC / provider error) or standard error names over message text; provider messages can change. Use a generic message when codes don't identify the error. Provide clear, actionable error messages: ```tsx function handleTransactionError(error: Error & { code?: number }) { // Prefer code or name; avoid matching on message text (provider messages can change). if (error.code === -32604 || error.name === "UserRejectedRequestError") { return "Transaction was cancelled."; } return "Transaction failed. Please try again."; } ``` ### Log Errors for Debugging Log errors for debugging while showing user-friendly messages: ```tsx try { await sendTransaction({ ... }); } catch (error) { console.error("Transaction error:", error); // For debugging showUserMessage("Transaction failed. Please try again."); // For users } ``` ### Low balance handling If the user's balance is too low to complete an action (e.g. send, pay a network fee), redirect them to MiniPay's **Add Cash** flow so they can top up. Use the official deeplink for Add Cash — **do not hardcode the URL**, as deeplinks may change. See [Deeplinks](/technical-references/deeplinks) for the current Add Cash URL and parameters (e.g. optional token list). ## Transaction UX ### Show Loading States Always show loading states during transactions: ```tsx const { isPending, sendTransaction } = useSendTransaction(); const { isLoading: isConfirming } = useWaitForTransactionReceipt({ hash }); ; ``` ### Provide Transaction Feedback Give users clear feedback at each stage: ```tsx { isPending &&
Preparing transaction...
; } { isConfirming &&
Waiting for confirmation...
; } { isSuccess &&
Transaction confirmed!
; } { isError &&
Transaction failed. Please try again.
; } ``` ### Display Transaction Hash Show transaction hash so users can track it: ```tsx { hash && (

Transaction submitted

View on CeloScan
); } ``` ## Security ### Validate User Input Use a schema library like [Zod](https://zod.dev) to validate and parse user input: ```tsx import { z } from "zod"; import { isAddress, type Address } from "viem"; const sendFormSchema = z.object({ address: z .string() .transform((v) => v as Address) .refine((v) => isAddress(v), { message: "Invalid destination address" }), amount: z.coerce.number().positive("Amount must be greater than 0"), }); // Parse and validate; throws ZodError if invalid const { address, amount } = sendFormSchema.parse({ address: userAddress, amount: userAmount }); ``` ### Never Store Private Keys ❌ **Never**: Store private keys or sensitive data ✅ **Do**: Rely on MiniPay for wallet management ### Verify Contract Addresses Always verify contract addresses before interacting: ```tsx const KNOWN_CONTRACTS = { USDC: "0xcebA9300f2b948710d2653dD7B07f33A8B32118C", // ... }; function useTokenAddress(symbol: string) { const address = KNOWN_CONTRACTS[symbol]; if (!address) { throw new Error(`Unknown token: ${symbol}`); } return address; } ``` ## Performance ### Optimize Contract Reads Use `enabled` to prevent unnecessary contract reads: ```tsx const { data } = useReadContract({ address: contractAddress, abi: contractAbi, functionName: "getValue", query: { enabled: !!address && !!contractAddress, // Only fetch when ready }, }); ``` ### Batch Contract Calls Batch multiple reads into a single call: ```tsx // ✅ Good: Single call const { data } = useReadContracts({ contracts: [ { address, abi, functionName: "balance" }, { address, abi, functionName: "decimals" }, { address, abi, functionName: "symbol" }, ], }); // ❌ Bad: Multiple separate calls const balance = useReadContract({ ... }); const decimals = useReadContract({ ... }); const symbol = useReadContract({ ... }); ``` ### Cache Contract Data Use React Query's caching to avoid redundant calls: ```tsx const { data } = useReadContract({ address: contractAddress, abi: contractAbi, functionName: "getValue", query: { staleTime: 30000, // Cache for 30 seconds }, }); ``` ## User Experience ### Mobile-First Design Design for mobile devices first: * ✅ Touch-friendly buttons (min 44x44px) * ✅ Readable text sizes * ✅ Adequate spacing * ✅ Responsive layouts ### Loading States Show loading states for all async operations: ```tsx const { data, isLoading } = useReadContract({ ... }); if (isLoading) { return ; } ``` ### Empty States Handle empty states gracefully: ```tsx if (!data || data.length === 0) { return
No items found
; } ``` ### Error Boundaries Use error boundaries to catch and handle errors: ```tsx class ErrorBoundary extends React.Component { componentDidCatch(error, errorInfo) { console.error("Error:", error, errorInfo); } render() { if (this.state.hasError) { return
Something went wrong. Please refresh.
; } return this.props.children; } } ``` ## Code Organization ### Separate Concerns Organize code into logical modules: ``` src/ hooks/ useWallet.ts useBalance.ts components/ WalletStatus.tsx TransactionButton.tsx lib/ contracts.ts tokens.ts ``` ### Reusable Hooks Create reusable hooks for common patterns: ```tsx // hooks/useTokenBalance.ts export function useTokenBalance(tokenAddress: Address) { const { address } = useAccount(); return useReadContract({ address: tokenAddress, abi: erc20Abi, functionName: "balanceOf", args: [address!], query: { enabled: !!address }, }); } ``` ### Type Safety Use TypeScript for type safety: ```tsx type TokenSymbol = "USDC" | "USDm" | "USDT"; function useTokenAddress(symbol: TokenSymbol): Address { // TypeScript ensures only valid symbols are used } ``` ## Testing ### Test Wallet Connection Test that wallet connection works: ```tsx // Test auto-connect expect(connectors.length).toBeGreaterThan(0); expect(isConnected).toBe(true); ``` ### Test Error Handling Test error scenarios: * Insufficient funds * Network errors * User rejection ### Test on Both Networks Test on both mainnet and testnet: * ✅ Celo Mainnet (Chain ID: 42220) * ✅ Celo Sepolia Testnet (Chain ID: 11142220) ## Common Pitfalls ### ❌ Not Auto-Connecting Always auto-connect. Never show a connect button. ### ❌ Ignoring Errors Always handle errors gracefully with user-friendly messages. ### ❌ Not Showing Loading States Always show loading states during async operations. ### ❌ Hardcoding Addresses Use environment variables or configuration for contract addresses. ### ❌ Not Validating Input Always validate user inputs before using them. ## Next Steps * Review [example implementations](./examples.md) * Check [deployment guide](./deployment.md) * See [wallet connection patterns](./wallet-connection.md) --- --- url: /getting-started/why-minipay.md --- # Building for MiniPay  > \[!note] What is MiniPay? > [Visit the Minipay website](http://www.minipay.xyz) to learn more about MiniPay. ## Why build for MiniPay? The TL;DR on building for MiniPay: [MiniPay](https://www.opera.com/products/minipay), the stablecoin wallet, offers instant access to 5M+ active wallets in 50+ markets, with 50M+ weekly impressions on its built-in Mini App page. Your app gets seamless integration, inheriting MiniPay’s zero-fee on/off-ramping, multi-stablecoin support, and localized payments, eliminating user friction. Key benefits: * Embedded Mini App inside MiniPay → No extra onboarding needed * Fast, cheap stablecoin transactions (sub-cent fees) * Plug into a high-engagement ecosystem (5M+ weekly app opens) * Reach global markets with built-in financial rails * Stablecoin support includes USDm, USDC, and USDT * Built on Celo Get instant distribution and frictionless payments — build on MiniPay now! ## About MiniPay Opera introduced MiniPay, a stablecoin wallet, in September 2023, integrated inside of Opera Mini, one of the most popular browsers in Africa. Kicking off the rollout in Nigeria and subsequently expanding to Kenya, South Africa, Ghana, Malawi, Tanzania, and additional key markets across the continent, MiniPay has become a global stablecoin wallet app. Today, MiniPay is a stablecoin wallet with over 7 million wallet activations globally. Through its built-in Mini App page, MiniPay makes access to dApps on the Celo network easy and frictionless for any user on MiniPay. In 50+ markets, MiniPay is currently available as a standalone app on Android and iOS, and integrated directly into the popular Opera Mini Android browser. MiniPay partners with 12 on/off-ramp providers, facilitating seamless cash-in and cash-out with local currencies and payment methods. It collaborates with Cashramp, Bitgifty, Pretium, Partna, and Transak, among others, to offer users zero-fee on and off-ramping in 35 currencies. MiniPay is one of the most affordable ways to move and spend stablecoins. Today, MiniPay supports USDm, USDC, and USDT. Through the Pocket feature powered by Mento, MiniPay streamlines multi-stablecoin management, enabling users to quickly and seamlessly swap between stablecoins at no extra cost. ## Upsides of building new Mini Apps **Integrated Mini Apps:** MiniPay includes a built-in Mini App browser. This allows users to interact directly with a curated list of Mini Apps within their wallet without switching to other platforms. Each Mini App automatically inherits MiniPay’s localized cash-in/cash-out experience—eliminating the need for separate on/off ramps. This guarantees a smooth onboarding of your users into your app. Integrated Mini Apps now record over 50 million weekly impressions and 5 million weekly app opens, reflecting high user engagement and adoption. **Useful Applications:** MiniPay focuses on practical uses in everyday life, whether in emerging markets for travelers looking for an easy wallet for local payments or simply anyone looking for a convenient stablecoin wallet.  **Key features** * Phone Number mapping: Uses mobile phone numbers as wallet addresses. * Fast, Low-Cost Transactions: Offers fast P2P stablecoin transactions with sub-cent fees. * Lightweight Design: At just 2MB, users can use the wallet with limited data. * Mini Apps: Access third-party Mini Apps on Celo. Use Mini Apps for savings, bill payments, vouchers, and more. --- --- url: /technical-references/chain-switching.md --- # Chain Switching Manage network chains in your Mini App, including detecting the current chain and handling chain mismatches. ## Supported Chains MiniPay supports the following Celo networks: * **Celo Mainnet** (Chain ID: 42220) * **Celo Sepolia Testnet** (Chain ID: 11142220) MiniPay does not currently support programmatic chain switching. The wagmi `useSwitchChain` hook will not work inside MiniPay. ## Chain Configuration Configure your Wagmi config with supported chains: ```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: [celo, celoSepolia], connectors: [injected()], transports: { [celo.id]: http(), [celoSepolia.id]: http(), }, }); ``` ## Detect Current Chain Use `useChainId` to get the current chain ID and derive a display name or helper. You can also react to chain changes (e.g. refresh data) in a `useEffect` that depends on `chainId`: ```tsx import { useEffect } from "react"; import { useChainId } from "wagmi"; import { celo, celoSepolia } from "wagmi/chains"; const CHAIN_NAMES: Record = { [celo.id]: "Celo Mainnet", [celoSepolia.id]: "Celo Sepolia", }; function CurrentChain() { const chainId = useChainId(); const chainName = CHAIN_NAMES[chainId] ?? "Unknown"; const isMainnet = chainId === celo.id; const isTestnet = chainId === celoSepolia.id; useEffect(() => { // Optional: react to chain changes (refresh data, update UI) console.log("Chain:", chainId); }, [chainId]); return (

Current Chain: {chainName}

Chain ID: {chainId}

{!isMainnet && !isTestnet &&

This app requires Celo Mainnet or Celo Sepolia.

}
); } ``` ## Handle Chain Mismatches When the user is on the wrong chain, detect it with `useChainId()` and show a clear message. MiniPay does not support chain switching; the pattern below is for detection and messaging only. ```tsx import { useChainId } from "wagmi"; import { celo, celoSepolia } from "wagmi/chains"; const SUPPORTED_CHAINS = [celo.id, celoSepolia.id]; function useIsChainSupported() { const chainId = useChainId(); return SUPPORTED_CHAINS.includes(chainId); } function ChainMismatchMessage() { const isSupported = useIsChainSupported(); if (isSupported) { return null; } return (

This app requires Celo Mainnet or Celo Sepolia. You are currently on an unsupported network.

); } ``` ## Chain-Specific Addresses Token and contract addresses differ between mainnet and testnet. Use a `Record` (or nested record for multiple tokens) and `useChainId()`: ```tsx import { useChainId } from "wagmi"; import { celo, celoSepolia } from "wagmi/chains"; import type { Address } from "viem"; // Example: one contract per chain const CONTRACT_ADDRESSES: Record = { [celo.id]: "0x..." as Address, // Mainnet [celoSepolia.id]: "0x..." as Address, // Testnet }; // Example: multiple tokens per chain const TOKEN_ADDRESSES: Record> = { [celo.id]: { USDC: "0xcebA9300f2b948710d2653dD7B07f33A8B32118C", USDm: "0x765DE816845861e75A25fCA122bb6898B8B1282a", }, [celoSepolia.id]: { USDC: "0x01C5C0122039549AD1493B8220cABEdD739BC44E", USDm: "0xEF4d55D6dE8e8d73232827Cd1e9b2F2dBb45bC80", }, }; function useTokenAddress(symbol: "USDC" | "USDm") { const chainId = useChainId(); return TOKEN_ADDRESSES[chainId]?.[symbol]; } ``` ## Best Practices 1. **Always check chain ID**: Verify the user is on the expected network before making transactions 2. **Use chain-specific addresses**: Token and contract addresses differ between mainnet and testnet 3. **Handle chain mismatches**: Detect when the user is on an unsupported chain and show a clear message (MiniPay does not support programmatic chain switching) 4. **Monitor chain changes**: Update your app state when the user switches networks 5. **Test on both networks**: Ensure your app works correctly on both mainnet and testnet ## Common Issues * **Wrong network**: Detect when `chainId` is not `celo.id` or `celoSepolia.id` and show a message that the app requires Celo Mainnet or Celo Sepolia. Do not rely on chain switching in MiniPay—it is not supported. * **Unsupported chain**: Check `[celo.id, celoSepolia.id].includes(chainId)` and show a fallback or redirect. ## Next Steps * Learn about [wallet connection](/getting-started/wallet-connection) * See [best practices](../getting-started/best-practices.md) for wallet interactions * Check out [example implementations](../getting-started/examples.md) --- --- url: /technical-references/custom-methods/custom-methods.md --- # Custom Methods Common for all custom methods is the need for a custom Viem Client that can call JSON RPC methods. You can either extend a custom Client you may already be using, or use the provided code snippet. ## Code Examples Below is an example schema that includes both custom methods and legacy methods along with the required types for calling the exchange rate API: ```typescript [MiniPayRpcSchema] import { Address, Hex, WalletRpcSchema } from "viem"; type CustomMethodRpcSchema = [ { Method: "minipay_getExchangeRate"; Parameters: [from: string, to: string]; ReturnType: number; }, { Method: "minipay_scanQrCode"; Parameters: []; ReturnType: string; }, { Method: "minipay_requestContact"; Parameters: []; ReturnType: { name: string; address: string }; }, ]; type LegacyRpcSchema = [ { Method: "eth_signTypedData_v3"; Parameters: [address: Address, message: string]; ReturnType: Hex; }, ]; export type MiniPayRpcSchema = [...WalletRpcSchema, ...LegacyRpcSchema, ...CustomMethodRpcSchema]; ``` Once you have defined your custom RPC schema, you can create a hook that will provide the custom client for calling the methods defined in your schema: ```typescript [Implement Client Hook] const useMiniPayClient = (config: Config): WalletClient | null => { const chainId = useChainId({ config }); const chain = config.chains[chainId]; if (typeof window === "undefined" || typeof window.ethereum === "undefined") { return { client: null }; } const client = createWalletClient({ chain: chain, transport: custom(window.ethereum), rpcSchema: rpcSchema(), }); return { client }; }; ``` Once the hook is set up, you can simply call it in your components to get access to the client that can invoke the custom methods: ```typescript [Create Custom Client] const { client } = useMiniPayClient(config); ``` ## Example Methods Currently available custom methods: * [getExchangeRate](./get-exchange-rate.md) * [scanQrCode](./scan-qr-code.md) * [requestContact](./request-contact.md) --- --- url: /technical-references/deeplinks.md --- # Deeplinks (available soon) ## Overview Deeplinks allow your MiniApp to interact with MiniPay's native features. This integration enables a smoother user experience by eliminating the need for users to manually navigate through MiniPay's interface. Deeplinks can also be used from outside MiniPay to redirect users to your approved MiniApp or to some of MiniPay's features. All deep links use the host `link.minipay.xyz` and can be triggered by other apps (e.g., WhatsApp, Slack, URL on a webpage), as well from inside the MiniPay app. ## Available Deeplinks ### Add Cash Launches a new add cash flow for the selected tokens. Tokens are represented by their currency codes (in uppercase), e.g. "USDM" or "USDT". If a token is not supported by the client it's skipped. ``` https://link.minipay.xyz/add_cash[?tokens=XXX,YYY,ZZZ] ``` Examples: * `https://link.minipay.xyz/add_cash` * `https://link.minipay.xyz/add_cash?tokens=USDM` * `https://link.minipay.xyz/add_cash?tokens=USDM,USDT,USDC` #### Supported Tokens The following tokens are supported in the Add Cash screen: * `USDm` - Mento Dollar * `USDT` - Tether * `USDC` - USD Coin ### Open MiniApp Opens an approved MiniApp inside MiniPay. Set the url parameter to the approved MiniApp's url. ``` https://link.minipay.xyz/browse?url=xxx ``` ### MiniApp tab Opens MiniApps tab. ``` https://link.minipay.xyz/discover ``` ### Transaction Receipt Opens a transaction receipt screen for the given transaction hash (via "tx" parameter). ``` https://link.minipay.xyz/receipt?tx=xxx[&celebrate] ``` ### QR Code Screen Opens user's QR code screen. ``` https://link.minipay.xyz/qr ``` ### Invite Friends Opens Invite Friends screen. ``` https://link.minipay.xyz/invite_friends ``` ### Pockets Screen Opens Pockets screen. ``` https://link.minipay.xyz/balance ``` ## Limitations * The user must have MiniPay installed and be logged in. The user will be invited to install MiniPay if MiniPay is not installed. --- --- url: /getting-started/deployment.md --- # Deployment Deploy your Mini App to make it accessible in MiniPay. This guide covers deployment requirements, build configuration, and how to verify your deployment. ## Requirements ### HTTPS Required MiniPay requires all Mini Apps to be served over HTTPS. Your deployment must: * ✅ Use HTTPS (not HTTP) * ✅ Have a valid SSL certificate * ✅ Be publicly accessible ### CORS Configuration If your app makes API calls, ensure CORS is properly configured: ```javascript // Example CORS headers (adjust for your server) Access-Control-Allow-Origin: * Access-Control-Allow-Methods: GET, POST, OPTIONS Access-Control-Allow-Headers: Content-Type ``` ## Build Configuration ### Vite For Vite projects, ensure your build is configured correctly: ```ts // vite.config.ts import { defineConfig } from "vite"; import react from "@vitejs/plugin-react"; export default defineConfig({ plugins: [react()], build: { outDir: "dist", sourcemap: false, // Set true only if you use error reporting (e.g. Sentry) that needs source maps; otherwise keeps builds smaller and avoids exposing source }, }); ``` Build your app: ```bash npm run build ``` ### Environment Variables Set environment variables for your deployment: ```bash # Production VITE_API_URL=https://api.example.com VITE_CHAIN_ID=42220 ``` Access in your app: ```ts const apiUrl = import.meta.env.VITE_API_URL; ``` ## Testing Your Deployment 1. **Test HTTPS**: Verify your app loads over HTTPS 2. **Test in MiniPay**: Use Developer Mode to load your deployed URL 3. **Test wallet connection**: Ensure auto-connect works 4. **Test transactions**: Verify transactions work on the deployed version ## Performance Optimization Optimize your deployment for performance: 1. **Enable compression**: Gzip/Brotli compression 2. **Cache static assets**: Set appropriate cache headers 3. **Minify code**: Ensure build process minifies JavaScript/CSS 4. **Optimize images**: Compress and optimize images 5. **Code splitting**: Use dynamic imports for code splitting ## Security Considerations 1. **Environment variables**: Never commit secrets to your repository 2. **HTTPS only**: Ensure all traffic uses HTTPS 3. **Content Security Policy**: Configure CSP headers if needed 4. **Dependencies**: Keep dependencies up to date ## Troubleshooting ### App not loading in MiniPay * ✅ Check HTTPS is enabled * ✅ Verify URL is publicly accessible * ✅ Check browser console for errors * ✅ Ensure CORS is configured correctly ### Build errors * ✅ Check Node.js version matches requirements * ✅ Verify all dependencies are installed * ✅ Check build configuration * ✅ Review build logs for specific errors ### Environment variables not working * ✅ Use the same `VITE_` prefix as in Environment variables above (if using Vite) * ✅ Rebuild after changing environment variables * ✅ Check hosting provider's environment variable configuration ## Next Steps * Test your deployment in [MiniPay Developer Mode](./test-in-minipay.md) * [Submit your Mini App](./submit-your-miniapp.md) for listing * Review [best practices](./best-practices.md) for production apps --- --- url: /design-standards.md --- # Design Standards Design guidelines for creating Mini Apps that integrate seamlessly with MiniPay. ## Mobile-First Design Mini Apps are primarily used on mobile devices. Design with mobile in mind: ### Touch Targets * ✅ Minimum touch target size: **44x44 pixels** * ✅ Adequate spacing between interactive elements * ✅ Large, easy-to-tap buttons ### Typography * ✅ Use readable font sizes: **16px** minimum for body text (avoid smaller than 14px) * ✅ Sufficient contrast ratios ([WCAG](https://www.w3.org/WAI/WCAG21/quickref/) AA minimum) ### Layout * ✅ Single column layouts work best * ✅ Avoid horizontal scrolling * ✅ Use full-width elements where appropriate * ✅ Consider safe areas (notches, status bars) — see [UI & container](/getting-started/ui-and-container) for viewport and safe-area details ## Wallet Integration UI ### Phone-first identity MiniPay users identify via their phone numbers. In your UI: * ✅ Prefer showing phone number or a user-friendly identifier where possible (e.g. via [phone number lookup](/technical-references/phone-number-lookup)). * ❌ Avoid displaying raw `0x…` wallet addresses to users unless necessary (e.g. for advanced or copy-to-clipboard use cases). ### Connection errors MiniPay abstracts connection away from the user. Don't surface "Connecting..." or "Connected" states; only show the user a message when connection fails. ```tsx // Only show an error when connection fails { connectionFailed &&
Could not connect to MiniPay.
; } ``` Recovery options are limited: if your mini app implements [EIP-6963](https://eips.ethereum.org/EIPS/eip-6963), you can call `requestProvider()` so MiniPay re-announces the provider; otherwise the user may need to refresh the page (and success is not guaranteed). ### Transaction States Provide clear transaction feedback: ```tsx // Pending // Confirming
Transaction submitted. Waiting for confirmation...
// Success
✅ Transaction confirmed!
// Error
❌ Transaction failed. Please try again.
``` ### Balance Display Format balances clearly: ```tsx // Good: Clear formatting
1,234.56 USDC
; // Good: With loading state { isLoading ? (
Loading balance...
) : (
{formattedBalance} {symbol}
); } ``` ## Color and Theming ### Support Dark Mode Consider supporting dark mode for better user experience. So that system UI (scrollbars, form controls) matches the theme, set the [color-scheme meta tag](https://developer.mozilla.org/en-US/docs/Web/HTML/Reference/Elements/meta/name/color-scheme) in your `` (e.g. ``) or use `color-scheme: light dark` in CSS on `:root`. Prefer CSS for styling so the browser applies the theme without JavaScript: ```css @media (prefers-color-scheme: dark) { :root { --bg: #1a1a1a; --text: #e5e5e5; } } @media (prefers-color-scheme: light) { :root { --bg: #ffffff; --text: #1a1a1a; } } ``` Use JavaScript (e.g. `window.matchMedia('(prefers-color-scheme: dark)')`) only when you need to branch logic, not for styling. ### Contrast Ensure sufficient contrast for readability: * Text on background: **4.5:1** minimum ([WCAG](https://www.w3.org/WAI/WCAG21/quickref/) AA) * Large text: **3:1** minimum * Interactive elements: Clear visual feedback ## Accessibility ### Screen Readers Make your app accessible to screen readers: * ✅ Semantic HTML elements * ✅ ARIA labels where needed * ✅ Alt text for images * ✅ Descriptive button labels ### Error Messages Provide clear, accessible error messages: ```tsx // Good: Clear, descriptive

Transaction failed: Insufficient balance

You need at least 10 USDC to complete this transaction.

// Bad: Vague
Error occurred
``` ## Performance ### Loading States Always show loading states: ```tsx // Good: Clear loading indicator { isLoading ? (

Loading...

) : ( ); } ``` ### Optimize Images * ✅ Prefer SVG for icons and vector graphics (scalable, small) * ✅ Use appropriate raster formats for photos (WebP, AVIF) * ✅ Compress images * ✅ Lazy load images below the fold ### Minimize Bundle Size * ✅ Code splitting * ✅ Tree shaking * ✅ Remove unused dependencies * ✅ Optimize imports ## User Experience ### User-facing language (terminology) Use simple, non-jargon language so users who are new to digital money feel at home: | Use this | Not this | | -------- | -------- | | **Network fee** | Gas | | **Deposit** | Onramp, Buy | | **Withdraw** | Offramp, Sell | | **Stablecoin** or **Digital dollar** | Crypto token | ### Error Handling Prefer error codes (e.g. from the JSON-RPC / provider error object) or standard error names over matching on message text, since provider messages can change. When codes don't identify the error, use a generic user-facing message. **Stable references:** The [JSON-RPC 2.0 spec](https://www.jsonrpc.org/specification#error_object) defines standard error codes: `-32700` (Parse error), `-32600` (Invalid request), `-32601` (Method not found), `-32602` (Invalid params), `-32603` (Internal error), `-32604` (Permission denied). MiniPay uses these codes in JSON-RPC responses. Prefer checking `error.code` or `error.name` over `error.message`. Provide helpful error messages: ```tsx function ErrorMessage({ error }: { error: Error & { code?: number } }) { // Prefer code or name; avoid matching on message text (provider messages can change). const message = error.code === -32604 || error.name === "UserRejectedRequestError" ? "Transaction was cancelled" : "Something went wrong"; return (

{message}

); } ``` ### Empty States Handle empty states gracefully: ```tsx { items.length === 0 ? (

No items found

) : ( ); } ``` ### Confirmation Dialogs Use confirmation dialogs for important actions: ```tsx function ConfirmDialog({ onConfirm, onCancel }: Props) { return (

Are you sure you want to proceed?

); } ``` ## Best Practices 1. **Mobile only**: Mini apps run on phones. Design for small viewports. 2. **Fast loading**: Optimize for quick load times 3. **Clear feedback**: Show loading, success, and error states 4. **Accessible**: Follow [WCAG](https://www.w3.org/WAI/WCAG21/quickref/) guidelines 5. **Consistent**: Use consistent patterns throughout your app ## Next Steps * Review [best practices](../getting-started/best-practices.md) * Check [example implementations](../getting-started/examples.md) * See [wallet connection patterns](../getting-started/wallet-connection.md) --- --- url: /getting-started/examples.md --- # Examples Reference implementations and common patterns for Mini App development. ## Common patterns ### Wallet Connection Pattern Complete wallet connection setup: ```tsx // hooks/useAutoConnect.ts import { useEffect } from "react"; import { useConnect, useConnectors } from "wagmi"; export function useAutoConnect() { const connectors = useConnectors(); const { connect } = useConnect(); useEffect(() => { if (connectors.length > 0) { connect({ connector: connectors[0] }); } }, [connectors, connect]); } // App.tsx import { WagmiProvider } from "wagmi"; import { config } from "./wagmi"; import { useAutoConnect } from "./hooks/useAutoConnect"; function AppContent() { useAutoConnect(); return
My Mini App
; } function App() { return ( ); } ``` ### Token balance pattern Fetch and display token balance (Wagmi v3: use `useConnection()` for address): ```tsx import { useConnection, useReadContracts } from "wagmi"; import { erc20Abi, formatUnits, type Hex } from "viem"; const USDC_ADDRESS = "0xcebA9300f2b948710d2653dD7B07f33A8B32118C"; function TokenBalance() { const { address } = useConnection(); const { data: results, isLoading } = useReadContracts({ contracts: [ { address: USDC_ADDRESS, abi: erc20Abi, functionName: "balanceOf", args: [address as Hex], }, { address: USDC_ADDRESS, abi: erc20Abi, functionName: "decimals", }, { address: USDC_ADDRESS, abi: erc20Abi, functionName: "symbol", }, ], query: { enabled: !!address, }, }); if (isLoading) return
Loading...
; const [balance, decimals, symbol] = results || []; const formatted = balance && decimals ? formatUnits(balance, decimals) : "0"; return (

{formatted} {symbol}

); } ``` ### Send transaction pattern Send tokens with full status tracking (Wagmi v3: use `useConnection()` for address): ```tsx import { useSendTransaction, useWaitForTransactionReceipt, useConnection } from "wagmi"; import { encodeFunctionData, erc20Abi, parseUnits } from "viem"; const USDC_ADDRESS = "0xcebA9300f2b948710d2653dD7B07f33A8B32118C"; const USDC_ADAPTER = "0x2F25deB3848C207fc8E0c34035B3Ba7fC157602B"; const USDC_DECIMALS = 6; // USDC uses 6 decimals; you can also read from the token contract with useReadContract + decimals() function SendUSDC({ to, amount }: { to: `0x${string}`; amount: string }) { const { address } = useConnection(); const { sendTransaction, data: hash, isPending, error, } = useSendTransaction(); const { isLoading: isConfirming, isSuccess } = useWaitForTransactionReceipt({ hash, }); const handleSend = () => { const data = encodeFunctionData({ abi: erc20Abi, functionName: "transfer", args: [to, parseUnits(amount, USDC_DECIMALS)], }); sendTransaction({ to: USDC_ADDRESS, data, feeCurrency: USDC_ADAPTER, }); }; return (
{error &&
Error: {error.message}
} {isSuccess &&
Transaction confirmed!
} {hash &&
Tx: {hash}
}
); } ``` ### Contract Interaction Pattern Read and write to a custom contract: ```tsx import { useReadContract, useWriteContract, useWaitForTransactionReceipt, } from "wagmi"; import type { Address } from "viem"; const CONTRACT_ABI = [ { inputs: [], name: "getValue", outputs: [{ name: "", type: "uint256" }], stateMutability: "view", type: "function", }, { inputs: [{ name: "value", type: "uint256" }], name: "setValue", outputs: [], stateMutability: "nonpayable", type: "function", }, ] as const; const CONTRACT_ADDRESS = "0x..." as Address; function ContractInteraction() { const { data: value, isLoading } = useReadContract({ address: CONTRACT_ADDRESS, abi: CONTRACT_ABI, functionName: "getValue", }); const { writeContract, data: hash, isPending } = useWriteContract(); const { isLoading: isConfirming, isSuccess } = useWaitForTransactionReceipt({ hash, }); const handleSetValue = () => { writeContract({ address: CONTRACT_ADDRESS, abi: CONTRACT_ABI, functionName: "setValue", args: [BigInt(100)], }); }; if (isLoading) return
Loading...
; return (

Current Value: {value?.toString()}

{isSuccess &&
Value updated!
}
); } ``` ### Chain-Aware Pattern Handle different chains and addresses: ```tsx import { useChainId } from "wagmi"; import { celo, celoSepolia } from "wagmi/chains"; import type { Address } from "viem"; const TOKEN_ADDRESSES: Record = { [celo.id]: "0x..." as Address, // Mainnet [celoSepolia.id]: "0x..." as Address, // Testnet }; function useTokenAddress() { const chainId = useChainId(); return TOKEN_ADDRESSES[chainId]; } function ChainAwareComponent() { const tokenAddress = useTokenAddress(); const chainId = useChainId(); if (!tokenAddress) { return
Token not available on this network
; } return (

Network: {chainId === celo.id ? "Mainnet" : "Testnet"}

Token: {tokenAddress}

); } ``` ## Use Cases ### Payment App Send token payments (e.g. USDC) between users. Use the same pattern as the [Send transaction pattern](#send-transaction-pattern) above: encode an ERC20 `transfer` with `encodeFunctionData` and call `sendTransaction` with the token contract as `to` and the encoded data. Validate the recipient address before sending. ### Token Swap To swap tokens, call a DEX contract (e.g. a router with `swapExactTokensForTokens` or similar) with encoded calldata — pass token addresses, amounts, and deadline. Use `encodeFunctionData` with the DEX ABI and then `sendTransaction`. For contract interaction patterns, see [Smart contracts](./smart-contracts). Check your DEX’s documentation for the exact function and parameters. ### Custom contracts (e.g. NFTs) For custom contract interactions such as NFTs, use the same patterns as in [Smart contracts](./smart-contracts): `useReadContract` / `useReadContracts` for reads and `useWriteContract` with your contract ABI for writes. ## Next Steps * Review [best practices](./best-practices.md) * Check [wallet connection guide](./wallet-connection.md) * See [smart contracts guide](./smart-contracts.md) --- --- url: /faq.md --- # Frequently Asked Questions (FAQ) ## What is MiniPay? MiniPay is a wallet that injects an Ethereum provider into dApps launched within its browser. The provider is available as `window.ethereum` and includes a special flag `isMiniPay` to indicate that the provider is from MiniPay. ## Do I have to wire everything up by hand? No. The recommended path is to use [Celopedia](/getting-started/quick-start) — a comprehensive Celo skill for AI coding assistants that scaffolds Mini Apps and wires up wallet detection, auto-connect, and stablecoin payments for you. Install with: ```bash npx skills add celo-org/celopedia-skills ``` More info and the full topic catalogue: [celopedia.celo.org](https://celopedia.celo.org/). If you prefer to do it manually, see [Setup with React](/getting-started/setup-react). ## What libraries are supported by MiniPay? MiniPay supports popular libraries such as **Wagmi** and **Viem**. You can use these libraries to interact with the Ethereum provider injected by MiniPay. Check out the [Quick Start](/getting-started/quick-start) for a practical example. MiniPay uses Custom Fee Abstraction based transactions, which is not supported by Ethers.js. Use viem or wagmi instead. If you are using Web3.js, use [Celo's custom built plugin for fee abstraction](https://docs.celo.org/developer/web3). ## How do I connect to MiniPay? MiniPay requires automatic connection on page load. Never show a connect button. See our [wallet connection guide](/getting-started/wallet-connection) for details. ## Why isn't my wallet connecting? Common issues: * Make sure your app is running inside MiniPay (not a regular browser) * Check that `window.ethereum` is available * Verify you're using the `injected` connector in Wagmi * See our [wallet connection troubleshooting](/getting-started/wallet-connection#common-issues) ## How do I detect if I'm running in MiniPay? Check for the `isMiniPay` flag: ```ts if (window.ethereum?.isMiniPay) { console.log("Running in MiniPay!"); } ``` ## What networks does MiniPay support? MiniPay supports: * **Celo Mainnet** (Chain ID: 42220) * **Celo Sepolia Testnet** (Chain ID: 11142220) See our [chain switching guide](/technical-references/chain-switching) for more details. ## How do I send transactions? Use Wagmi's `useSendTransaction` hook. See our [send transaction guide](/technical-references/send-transaction) for examples. ## How do I check transaction status? Use `useWaitForTransactionReceipt` to track transaction confirmation. See our [transaction status guide](/technical-references/transaction-status). ## Can I pay gas fees in stablecoins? Yes. MiniPay typically pays gas with the token the user has the most of. You can pass `feeCurrency` in your transaction, but MiniPay may ignore it. See our [send transaction guide](/technical-references/send-transaction) for how to send token transactions. ## How do I get token balances? Use `useReadContracts` to read token balances from ERC20 contracts. See our [retrieve balance guide](/technical-references/retrieve-balance). ## What tokens are supported? MiniPay supports various stablecoins on Celo. See the [token addresses table](/technical-references/retrieve-balance#token-addresses) for a complete list. ## How do I interact with smart contracts? Use `useReadContract` for reading and `useWriteContract` for writing. See our [smart contracts guide](/getting-started/smart-contracts). ## How do I test my Mini App? Enable Developer Mode in MiniPay and use the "Load test page" feature. See our [testing guide](/getting-started/test-in-minipay). ## How do I submit my Mini App? Submit your Mini App using the [submission form](/getting-started/submit-your-miniapp). Make sure your app follows our guidelines. ## My transaction failed. What should I do? Check the error message: * **Insufficient funds**: User doesn't have enough balance * **User rejected**: Transaction was cancelled * **Network error**: Connection issue, try again * **Gas estimation failed**: Transaction may be invalid See our [Best practices — Error handling](/getting-started/best-practices#error-handling) for details. ## Can I use other wallets with my Mini App? Mini Apps are designed to run inside MiniPay. While the provider follows EIP-1193 standards, the app should be optimized for MiniPay's user experience. ## Do I need to handle wallet disconnection? MiniPay maintains the connection while the app is open. However, you should handle connection errors gracefully. See our [wallet connection guide](/getting-started/wallet-connection#error-handling). --- --- url: /technical-references/gas-estimation.md --- # Gas Estimation This guide demonstrates how to estimate gas using the `wagmi` library in a React component. Gas estimation is crucial for ensuring that a transaction has enough gas to be processed on the Celo network without running out of gas, which would result in a failed transaction. In your **user-facing UI**, label this cost as **"network fee"** rather than "gas". See [Design standards — User-facing language](/design-standards/#user-facing-language-terminology) for terminology guidelines. ## Example Code Below is a simple example of how to estimate gas: ```tsx import { useEstimateGas } from "wagmi"; import { formatUnits } from 'viem' const Example = () => { const result = useEstimateGas(); // If the gas estimation data is available, // format it to a readable unit; otherwise, default to "0". const gas = result.data ? formatUnits(result.data, 18) : "0"; return

{gas}

; }; ``` ### Notes: * Ensure `wagmi` is correctly configured. Follow our [Quick Start](/getting-started/quick-start) guide to know how. * **useEstimateGas**: This is a hook provided by the `wagmi` library that helps in estimating the gas required for a transaction. It returns an object that includes the estimated gas amount. * **formatUnits**: This function is used to convert the gas amount from its raw format to a more human-readable format, typically in Ether units. * **Error Handling**: In a production environment, consider adding error handling to manage cases where gas estimation might fail or return unexpected results. For a full example, visit the [Wagmi documentation](https://wagmi.sh/react/api/hooks/useEstimateGas). --- --- url: /technical-references/custom-methods/get-exchange-rate.md --- # getExchangeRate This custom React hook allows you to request the exchange rate between two currency codes (e.g., "USDT" to "NGN"), track the loading state and handle errors. ## Code Example Simple code example of how to use the hook: ```typescript [Use getExhangeRate Hook] const { getExchangeRate, rate, isPending, error } = useGetExchangeRate(config); useEffect(() => { getExchangeRate({ from: 'USDT', to: 'NGN' }); }, []); ``` --- --- url: /getting-started.md description: >- Get started building Mini Apps for MiniPay with instant wallet access and Celo support. --- # Getting started Build web apps that run inside MiniPay with instant wallet access and Celo support. No separate wallet extension or onboarding — users open your app from MiniPay and are already connected. ## Start here * **[Overview](/getting-started/overview)** — What Mini Apps are and how they run inside MiniPay. * **[Quick Start](/getting-started/quick-start)** — Scaffold a Mini App with [Celopedia](https://celopedia.celo.org/) and your AI assistant in a few minutes. * **[Setup with React](/getting-started/setup-react)** — Manual alternative: wire Vite + Wagmi + auto-connect by hand. * **[Test in MiniPay](/getting-started/test-in-minipay)** — Load your app in the wallet using Developer Mode. ## Guides * [Wallet connection](/getting-started/wallet-connection) — Auto-connect and error handling. * [FAQ](/faq) — Common questions. --- --- url: /getting-started/smart-contracts.md --- # Interacting with Smart Contracts Learn how to interact with smart contracts on Celo using the MiniPay wallet. ## Reading Contract State Read data from smart contracts using `useReadContract`: ```tsx import { useReadContract } from "wagmi"; import { erc20Abi } from "viem"; function TokenBalance({ tokenAddress }: { tokenAddress: `0x${string}` }) { const { address } = useAccount(); const { data: balance, isLoading } = useReadContract({ address: tokenAddress, abi: erc20Abi, functionName: "balanceOf", args: [address!], query: { enabled: !!address, }, }); if (isLoading) return
Loading...
; return
Balance: {balance?.toString()}
; } ``` ## Reading Multiple Contract Values (Batching) Batch multiple reads in a single call using `useReadContracts`: ```tsx import { useReadContracts } from "wagmi"; import { erc20Abi, formatUnits } from "viem"; import type { Hex } from "viem"; function TokenInfo({ tokenAddress }: { tokenAddress: `0x${string}` }) { const { address } = useAccount(); const { data: results, isLoading } = useReadContracts({ contracts: [ { address: tokenAddress, abi: erc20Abi, functionName: "balanceOf", args: [address as Hex], }, { address: tokenAddress, abi: erc20Abi, functionName: "decimals", }, { address: tokenAddress, abi: erc20Abi, functionName: "symbol", }, ], query: { enabled: !!address, }, }); if (isLoading) return
Loading...
; const [balance, decimals, symbol] = results || []; const formatted = balance && decimals ? formatUnits(balance, decimals) : "0"; return (

{formatted} {symbol}

); } ``` ## Writing to Contracts Write to contracts using `useWriteContract`: ```tsx import { useWriteContract, useWaitForTransactionReceipt } from "wagmi"; import { encodeFunctionData, erc20Abi, parseUnits } from "viem"; function TransferTokens({ tokenAddress, to, amount, }: { tokenAddress: `0x${string}`; to: `0x${string}`; amount: string; }) { const { writeContract, data: hash, isPending } = useWriteContract(); const { isLoading: isConfirming, isSuccess } = useWaitForTransactionReceipt({ hash, }); const handleTransfer = () => { writeContract({ address: tokenAddress, abi: erc20Abi, functionName: "transfer", args: [to, parseUnits(amount, 18)], }); }; return (
{isSuccess &&

Transfer successful!

}
); } ``` ## Custom Contract ABIs Define and use custom contract ABIs: ```tsx // contracts/my-contract.ts export const MY_CONTRACT_ABI = [ { inputs: [{ name: "value", type: "uint256" }], name: "setValue", outputs: [], stateMutability: "nonpayable", type: "function", }, { inputs: [], name: "getValue", outputs: [{ name: "", type: "uint256" }], stateMutability: "view", type: "function", }, ] as const; // In your component import { useReadContract, useWriteContract } from "wagmi"; import { MY_CONTRACT_ABI } from "./contracts/my-contract"; const CONTRACT_ADDRESS = "0x..." as `0x${string}`; function MyContract() { const { data: value } = useReadContract({ address: CONTRACT_ADDRESS, abi: MY_CONTRACT_ABI, functionName: "getValue", }); const { writeContract } = useWriteContract(); const handleSetValue = () => { writeContract({ address: CONTRACT_ADDRESS, abi: MY_CONTRACT_ABI, functionName: "setValue", args: [BigInt(100)], }); }; return (

Value: {value?.toString()}

); } ``` ## Contract Address Management Manage contract addresses per network: ```tsx import { useChainId } from "wagmi"; import { celo, celoSepolia } from "wagmi/chains"; import type { Address } from "viem"; const CONTRACT_ADDRESSES: Record = { [celo.id]: "0x..." as Address, // Mainnet [celoSepolia.id]: "0x..." as Address, // Testnet }; function useContractAddress() { const chainId = useChainId(); return CONTRACT_ADDRESSES[chainId]; } // Usage function MyComponent() { const contractAddress = useContractAddress(); if (!contractAddress) { return
Contract not deployed on this network
; } // Use contractAddress with your contract calls } ``` ## Handling Contract Events Listen for contract events: ```tsx import { useEffect } from "react"; import { usePublicClient, useWatchContractEvent } from "wagmi"; import { MY_CONTRACT_ABI } from "./contracts/my-contract"; function useContractEvents(contractAddress: `0x${string}`) { const publicClient = usePublicClient(); useWatchContractEvent({ address: contractAddress, abi: MY_CONTRACT_ABI, eventName: "ValueChanged", onLogs(logs) { console.log("Value changed:", logs); // Handle event }, }); } ``` ## Error Handling Handle contract call errors: ```tsx import { useReadContract, useWriteContract } from "wagmi"; function ContractWithErrorHandling() { const { data, error: readError, isLoading, } = useReadContract({ address: "0x...", abi: MY_CONTRACT_ABI, functionName: "getValue", }); const { writeContract, error: writeError, isPending } = useWriteContract(); if (readError) { return
Error reading contract: {readError.message}
; } const handleWrite = () => { try { writeContract({ address: "0x...", abi: MY_CONTRACT_ABI, functionName: "setValue", args: [BigInt(100)], }); } catch (error) { console.error("Write error:", error); } }; return (
{writeError &&
Error: {writeError.message}
}
); } ``` ## Gas Estimation Estimate gas before writing to contracts: ```tsx import { useEstimateGas } from "wagmi"; function useContractGasEstimate( contractAddress: `0x${string}`, functionName: string, args: unknown[], ) { const { data: gasEstimate, isLoading } = useEstimateGas({ to: contractAddress, data: encodeFunctionData({ abi: MY_CONTRACT_ABI, functionName, args, }), }); return { gasEstimate, isLoading }; } ``` ## Best Practices 1. **Always validate addresses**: Ensure contract addresses are valid before making calls 2. **Handle loading states**: Show loading indicators while reading contract data 3. **Error handling**: Provide user-friendly error messages for failed contract calls 4. **Network-specific addresses**: Use different contract addresses for mainnet and testnet 5. **ABI management**: Keep ABIs in separate files for better organization 6. **Type safety**: Use TypeScript types for contract addresses and ABIs 7. **Contract verification**: All contract source code must be published and verified on [Celoscan](https://celoscan.io) (mainnet) or [Celo Sepolia Celoscan](https://sepolia.celoscan.io) (testnet). This is required for Mini App listing. 8. **Sample transactions**: When submitting your Mini App, provide links to sample transactions for every user-facing contract method. See [Submit your MiniApp](./submit-your-miniapp#smart-contracts-if-your-app-uses-them) for submission requirements. ## Next Steps * Learn about [sending transactions](../technical-references/send-transaction.md) * See [transaction status tracking](../technical-references/transaction-status.md) * Check out [best practices](./best-practices.md) for wallet interactions --- --- url: /technical-references/phone-number-lookup.md --- # Phone number lookup > \[!NOTE] > A new method will soon be released helping you to resolve phone numbers in just a few lines of code. Follow [the guide in the Celo documentation](https://docs.celo.org/developer/build-on-minipay/code-library#resolve-minipay-phone-numbers-to-addresses) to see how to resolve phone numbers using `view`. --- --- url: /getting-started/project-setup.md --- # 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](/getting-started/submit-your-miniapp). > Two ways to land here: the [Celopedia Quick Start](./quick-start) scaffolds this structure for you via your AI assistant, or you can wire it up by hand using the layout below. Both paths produce the same result — pick whichever fits how you like to work. ## Recommended structure 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](/getting-started/test-in-minipay#q-how-do-i-run-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](/getting-started/submit-your-miniapp). You will need: app name, tagline, publisher, support URL, category, app URL, and icon. Details are in [Submit your Mini App](/getting-started/submit-your-miniapp). ## Next steps * [Quick Start](/getting-started/quick-start) — Minimal setup and auto-connect. * [Test in MiniPay](/getting-started/test-in-minipay) — Load your app in the wallet. --- --- url: /getting-started/quick-start.md description: >- Quick Start to scaffold your first MiniPay Mini App using the Celo agent skills. --- # Quick Start The fastest way to build a MiniPay Mini App is with **Celopedia** — a comprehensive Celo knowledge skill for AI coding assistants (Claude Code, Cursor, Codex, and similar). Install it once and your assistant can scaffold a Mini App, wire up wallet detection and stablecoin payments, and answer ecosystem questions inline. Prefer to wire things up by hand? See [Setup with React](./setup-react) for the manual Vite + Wagmi walkthrough. ## Prerequisites * **Node.js** 18+ * An AI coding assistant that supports skills (Claude Code, Cursor, Codex, etc.) * Familiarity with **React** / **TypeScript** ## Step 1: Install Celopedia ```bash npx skills add celo-org/celopedia-skills ``` That's it. The skill auto-activates inside your assistant whenever you describe a Celo or MiniPay task. Full reference and topic catalogue: [celopedia.celo.org](https://celopedia.celo.org/). ## Step 2: Scaffold your Mini App In your AI assistant, describe what you want — for example: > Create a new MiniPay Mini App that lets users send USDm to a contact. Use Next.js and Wagmi. Celopedia bundles MiniPay-specific guidance — Mini App templates, wallet detection, auto-connect, stablecoin payment flows (USDm, USDC, USDT), fee abstraction (CIP-64), ngrok testing, and a live Mini Apps discovery snapshot — so your assistant can scaffold a project (often via [Celo Composer](https://github.com/celo-org/celo-composer)) with: * Injected connector targeting **Celo Mainnet** (42220) + **Celo Sepolia** (11142220) * Auto-connect on load (no "Connect wallet" button — required by MiniPay) * Stablecoin transfer scaffolding using `viem` / `wagmi` No manual config snippets to copy. If you'd rather see exactly what gets wired up, [Setup with React](./setup-react) walks through it by hand. **See also:** [Celo Agent Skills docs](https://docs.celo.org/build-on-celo/build-with-ai/agent-skills) · [celo-org/agent-skills repo](https://github.com/celo-org/agent-skills) · [Celo Composer](https://github.com/celo-org/celo-composer). ## Step 3: Test in MiniPay Expose your dev server with [ngrok](./test-in-minipay#q-how-do-i-run-ngrok), enable **Developer Mode** in MiniPay, and use **Load test page** to enter your URL. Full instructions: [Test in MiniPay](./test-in-minipay). ## Step 4: Submit your Mini App When your app is production-ready, list it in MiniPay's Discover page via the [submission form](./submit-your-miniapp). *** **Next:** [Test in MiniPay](./test-in-minipay) to load your app in the wallet, or [Setup with React](./setup-react) if you want to see the manual setup the skills generate for you. For deeper guides: [Wallet connection](./wallet-connection), [Retrieve balance](/technical-references/retrieve-balance), [Send transaction](/technical-references/send-transaction). --- --- url: /technical-references/custom-methods/request-contact.md --- # requestContact This custom React hook opens the native contact picker and returns the selected contact's name and address. It tracks the loading state and handles errors. ## Code Example Below is a custom hook that wraps the `minipay_requestContact` RPC call: ```typescript [useRequestContact Hook] type Contact = { name: string; address: string }; const useRequestContact = (config: Config) => { const [contact, setContact] = useState(null); const [error, setError] = useState(null); const [isPending, setIsPending] = useState(false); const { client } = useMiniPayClient(config); return { contact, error, isPending, async requestContact(): Promise { if (!client) return; setError(null); setIsPending(true); setContact(null); try { const result = await client.request({ method: "minipay_requestContact", params: [], }); setContact(result); } catch (e: unknown) { if (e instanceof Error) { setError(e); } } finally { setIsPending(false); } }, }; }; ``` Simple usage example: ```typescript [Use requestContact Hook] const { requestContact, contact, isPending, error } = useRequestContact(config); return ( ); ``` --- --- url: /technical-references/retrieve-balance.md --- # Retrieve Balance Retrieve the balance and format for display. MiniPay is a stablecoin wallet. Retrieving the balance of one of our supported stablecoins is done by reading the token's smart contract. ## Supported tokens for Mini Apps **Mini Apps should use USDT, USDC, and USDm only.** Do not display or use the CELO token in your Mini App; the system automatically handles fees using the appropriate stablecoin. * **Dynamic adaptation:** Ideally, adapt to the user's preferred stablecoin (e.g. the one with the highest balance). See the [Multiple Token Balances](#multiple-token-balances) example below. * **Graceful degradation:** If your app cannot support multiple stablecoins, provide a clear, simple explanation to the user rather than a broken interface. The token address table below includes other tokens for reference; for Mini Apps, use the USDT, USDC, and USDm rows for your network. ::: code-group ```typescript [Retrieve balance] import { useConnection, useReadContracts } from "wagmi"; import { erc20Abi, formatUnits, type Hex } from "viem"; const Example = () => { const { address } = useConnection(); const token = "0xcebA9300f2b948710d2653dD7B07f33A8B32118C"; // USDC on Celo mainnet const { data: balanceResult } = useReadContracts({ allowFailure: false, contracts: [ { address: token, abi: erc20Abi, functionName: "balanceOf", args: [address as Hex], }, { address: token, abi: erc20Abi, functionName: "decimals", }, { address: token, abi: erc20Abi, functionName: "symbol", }, ], query: { enabled: !!address, }, }); const [balance, decimals, symbol] = balanceResult || []; const stringBalance = balance && decimals && formatUnits(balance, decimals); // 0.05 const userLocale = navigator.language || "en-US"; // Fallback to 'en-US' if not available // Decimal formatter based on user's locale const decimalFormatter = new Intl.NumberFormat(userLocale, { style: "decimal", minimumFractionDigits: 0, maximumFractionDigits: 2, }); const formattedBalance = decimalFormatter.format(+stringBalance); // 0.05 or 0,05 return `${formattedBalance} ${symbol}`; // 0.05 USDC }; ``` ```[Token addresses] Mainnet Name Symbol Token Address Adapter Decimals Uses Adapter? ───────────────────── ────── ────────────────────────────────────────── ────────────────────────────────────────── ──────── ───────────── Tether USD USD₮ 0x48065fbBE25f71C9282ddf5e1cD6D6A887483D5e 0x0E2A3e05bc9A16F5292A6170456A710cb89C6f72 6 true PUSO PUSO 0x105d4A9306D2E55a71d2Eb95B81553AE1dC20d7B - 18 false USDC USDC 0xcebA9300f2b948710d2653dD7B07f33A8B32118C 0x2F25deB3848C207fc8E0c34035B3Ba7fC157602B 6 true Mento Kenyan Shilling KESm 0x456a3D042C0DbD3db53D5489e98dFb038553B0d0 - 18 false ECO CFA eXOF 0x73F93dcc49cB8A239e2032663e9475dd5ef29A08 - 18 false Mento Dollar USDm 0x765DE816845861e75A25fCA122bb6898B8B1282a - 18 false Mento Colombian Peso COPm 0x8A567e2aE79CA692Bd748aB832081C45de4041eA - 18 false Mento Euro EURm 0xD8763CBa276a3738E6DE85b4b3bF5FDed6D6cA73 - 18 false Mento Brazilian Real REALm 0xe8537a3d056DA446677B9E9d6c5dB704EaAb4787 - 18 false Mento Ghanian Cedi GHSm 0xfAeA5F3404bbA20D3cc2f8C4B0A888F55a3c7313 - 18 false Celo Sepolia Testnet Name Symbol Token Address Adapter Decimals Uses Adapter? ───────────────────── ──────── ────────────────────────────────────────── ────────────────────────────────────────── ──────── ───────────── Mento Euro EURm 0x10c892A6EC43a53E45D0B916B4b7D383B1b78C0F - 18 false Mento Kenyan Shilling KESm 0x1E0433C1769271ECcF4CFF9FDdD515eefE6CdF92 - 18 false USDC USDC 0x2F25deB3848C207fc8E0c34035B3Ba7fC157602B 0x4822e58de6f5e485eF90df51C41CE01721331dC0 6 true Mento Dollar USDm 0x874069Fa1Eb16D44d622F2e0Ca25eeA172369bC1 - 18 false ECO CFA eXOF 0xB0FA15e002516d0301884059c0aaC0F0C72b019D - 18 false TetherToken USD₮ 0xC4f86E9B4A588D501c1c3e25628dFd50Bc8D615e - 18 false Mento Brazilian Real REALm 0xE4D517785D091D3c54818832dB6094bcc2744545 - 18 false ``` ::: ## Explanation * **useAccount**: This hook retrieves the current account information, including the address. * **useReadContracts**: This hook reads the function of one or multiple smart contracts. * **formatUnits**: Converts the balance from its raw format to a human-readable string. * **Intl.NumberFormat**: Formats the balance as a currency string based on the user's locale. ## Loading States Handle loading states while fetching the balance: ```tsx const { data: balanceResult, isLoading, error, } = useReadContracts({ allowFailure: false, contracts: [ { address: token, abi: erc20Abi, functionName: "balanceOf", args: [account?.address as Hex], }, { address: token, abi: erc20Abi, functionName: "decimals", }, { address: token, abi: erc20Abi, functionName: "symbol", }, ], query: { enabled: !!account?.address, // Only fetch when address is available }, }); if (isLoading) { return
Loading balance...
; } if (error) { return
Error loading balance: {error.message}
; } ``` ## Multiple Token Balances Fetch balances for multiple tokens efficiently: ```tsx import { useAccount, useReadContracts } from "wagmi"; import { erc20Abi, formatUnits, Hex } from "viem"; const TOKENS = { USDC: "0xcebA9300f2b948710d2653dD7B07f33A8B32118C", USDm: "0x765DE816845861e75A25fCA122bb6898B8B1282a", USDT: "0x48065fbBE25f71C9282ddf5e1cD6D6A887483D5e", }; function MultiTokenBalance() { const account = useAccount(); // Create contracts for all tokens const contracts = Object.entries(TOKENS).flatMap(([symbol, address]) => [ { address: address as Hex, abi: erc20Abi, functionName: "balanceOf" as const, args: [account?.address as Hex], }, { address: address as Hex, abi: erc20Abi, functionName: "decimals" as const, }, ]); const { data: results, isLoading } = useReadContracts({ allowFailure: false, contracts, query: { enabled: !!account?.address, }, }); if (isLoading) return
Loading...
; // Process results: each token has 2 results (balance, decimals) const balances = Object.keys(TOKENS).map((symbol, index) => { const resultIndex = index * 2; const [balance, decimals] = results?.slice(resultIndex, resultIndex + 2) || []; const formatted = balance && decimals ? formatUnits(balance, decimals) : "0"; return { symbol, balance: formatted }; }); return (
{balances.map(({ symbol, balance }) => (
{symbol}: {balance}
))}
); } ``` ## Error Handling Handle errors gracefully: ```tsx const { data: balanceResult, error, isLoading, } = useReadContracts({ allowFailure: true, // Allow individual contract calls to fail contracts: [ // ... contracts ], }); if (error) { return (

Failed to load balance

{error.message}

Make sure you're connected to MiniPay and on the correct network.

); } ``` ## Notes * Ensure `wagmi` is correctly configured. Follow our [Quick Start](/getting-started/quick-start) guide to know how. * Ensure that the account address is correctly retrieved and passed to the contract calls. * Ensure the `token` constant is correctly set to the stablecoin you want the balance of. Check the `Token addresses` tab to get the list of stablecoin addresses on Celo and Celo Sepolia (testnet). * Use `enabled` in the query options to prevent unnecessary calls when the address is not available. * Consider using `allowFailure: true` if you want to handle individual contract call failures gracefully. For more information, refer to the [Wagmi documentation](https://wagmi.sh/react/getting-started). --- --- url: /technical-references/custom-methods/scan-qr-code.md --- # scanQrCode This custom React hook opens the native QR code scanner and returns the scanned content. It tracks the loading state and handles errors. ## Code Example Below is a custom hook that wraps the `minipay_scanQrCode` RPC call: ```typescript [useScanQrCode Hook] const useScanQrCode = (config: Config) => { const [scannedValue, setScannedValue] = useState(null); const [error, setError] = useState(null); const [isPending, setIsPending] = useState(false); const { client } = useMiniPayClient(config); return { scannedValue, error, isPending, async scanQrCode(): Promise { if (!client) return; setError(null); setIsPending(true); setScannedValue(null); try { const result = await client.request({ method: "minipay_scanQrCode", params: [], }); setScannedValue(result); } catch (e: unknown) { if (e instanceof Error) { setError(e); } } finally { setIsPending(false); } }, }; }; ``` Simple usage example: ```typescript [Use scanQrCode Hook] const { scanQrCode, scannedValue, isPending, error } = useScanQrCode(config); return ( ); ``` --- --- url: /technical-references/send-transaction.md description: 'How to send token transactions (e.g. USDC, USDT, USDm) using Wagmi on Celo.' --- # Send a Transaction Sending transactions is a fundamental operation in blockchain applications. This guide demonstrates how to send token transactions (e.g. USDC, USDT, USDm) using the `wagmi` library. Most MiniPay users hold stablecoins rather than native CELO, so the examples focus on ERC20 transfers. ## Basic Transaction Example Below are examples for sending tokens. Use `encodeFunctionData` with the ERC20 `transfer` function and call `sendTransaction` with the token contract as `to` and the encoded data. ::: code-group ```tsx [Send USDC] import { useSendTransaction, useConnection } from "wagmi"; import { encodeFunctionData, erc20Abi, parseUnits } from "viem"; const Example = () => { const { address: connectedAddress } = useConnection(); const { data: hash, isPending, sendTransaction } = useSendTransaction(); const recipientAddress = "0xA0Cf…251e"; // Replace with the recipient address const amount = 0.05; // Amount in USDC const USDC_CONTRACT_ADDRESS = "0xcebA9300f2b948710d2653dD7B07f33A8B32118C"; // mainnet const USDC_ADAPTER = "0x2F25deB3848C207fc8E0c34035B3Ba7fC157602B"; // mainnet; MiniPay may ignore feeCurrency and choose the token the user has the most of const onPress = async (e) => { e.preventDefault(); const data = encodeFunctionData({ abi: erc20Abi, functionName: "transfer", args: [ recipientAddress, // Different tokens can have different decimals, USDm (18), USDC (6) parseUnits(amount.toString(), 6), ], }); await sendTransaction({ to: USDC_CONTRACT_ADDRESS, feeCurrency: USDC_ADAPTER, data, }); }; return ( ); }; ``` ```tsx [Send USDT] import { useSendTransaction, useConnection } from "wagmi"; import { encodeFunctionData, erc20Abi, parseUnits } from "viem"; const Example = () => { const { address: connectedAddress } = useConnection(); const { data: hash, isPending, sendTransaction } = useSendTransaction(); const recipientAddress = "0xA0Cf…251e"; // Replace with the recipient address const amount = 0.05; // Amount in USDT const USDT_CONTRACT_ADDRESS = "0x48065fbBE25f71C9282ddf5e1cD6D6A887483D5e"; // mainnet const USDT_ADAPTER = "0x0E2A3e05bc9A16F5292A6170456A710cb89C6f72"; // mainnet; MiniPay may ignore feeCurrency and choose the token the user has the most of const onPress = async (e) => { e.preventDefault(); const data = encodeFunctionData({ abi: erc20Abi, functionName: "transfer", args: [ recipientAddress, // Different tokens can have different decimals, USDm (18), USDC/USDT (6) parseUnits(amount.toString(), 6), ], }); await sendTransaction({ to: USDT_CONTRACT_ADDRESS, feeCurrency: USDT_ADAPTER, data, }); }; return ( ); }; ``` ```tsx [Send USDm] import { useSendTransaction } from "wagmi"; import { encodeFunctionData, erc20Abi, parseUnits } from "viem"; const Example = () => { const { data: hash, isPending, sendTransaction } = useSendTransaction(); const address = "0xA0Cf…251e"; // Replace with the recipient address const amount = 0.05; // Amount in USDm const USDM_CONTRACT_ADDRESS = "0x765DE816845861e75A25fCA122bb6898B8B1282a"; //mainnet const onPress = async (e) => { e.preventDefault(); const data = encodeFunctionData({ abi: erc20Abi, functionName: "transfer", args: [ address, // Different tokens can have different decimals, USDm (18), USDC (6) parseUnits(amount, 18), ], }); await sendTransaction({ to: USDM_CONTRACT_ADDRESS, feeCurrency: USDM_CONTRACT_ADDRESS, // MiniPay may ignore and use the token the user has the most of data, }); }; return ( ); }; ``` > \[!NOTE] > With Wagmi v3, the connected account is used automatically by `sendTransaction`. To read the current address in your component, use `useConnection()` and destructure `address`. ```[Token addresses] Mainnet Name Symbol Token Address Adapter Decimals Uses Adapter? ───────────────────── ────── ────────────────────────────────────────── ────────────────────────────────────────── ──────── ───────────── Tether USD USD₮ 0x48065fbBE25f71C9282ddf5e1cD6D6A887483D5e 0x0E2A3e05bc9A16F5292A6170456A710cb89C6f72 6 true PUSO PUSO 0x105d4A9306D2E55a71d2Eb95B81553AE1dC20d7B - 18 false USDC USDC 0xcebA9300f2b948710d2653dD7B07f33A8B32118C 0x2F25deB3848C207fc8E0c34035B3Ba7fC157602B 6 true Mento Kenyan Shilling KESm 0x456a3D042C0DbD3db53D5489e98dFb038553B0d0 - 18 false ECO CFA eXOF 0x73F93dcc49cB8A239e2032663e9475dd5ef29A08 - 18 false Mento Dollar USDm 0x765DE816845861e75A25fCA122bb6898B8B1282a - 18 false Mento Colombian Peso COPm 0x8A567e2aE79CA692Bd748aB832081C45de4041eA - 18 false Mento Euro EURm 0xD8763CBa276a3738E6DE85b4b3bF5FDed6D6cA73 - 18 false Mento Brazilian Real REALm 0xe8537a3d056DA446677B9E9d6c5dB704EaAb4787 - 18 false Mento Ghanian Cedi GHSm 0xfAeA5F3404bbA20D3cc2f8C4B0A888F55a3c7313 - 18 false Celo Sepolia Testnet Name Symbol Token Address Adapter Decimals Uses Adapter? ───────────────────── ──────── ────────────────────────────────────────── ────────────────────────────────────────── ──────── ───────────── Mento Euro EURm 0x10c892A6EC43a53E45D0B916B4b7D383B1b78C0F - 18 false Mento Kenyan Shilling KESm 0x1E0433C1769271ECcF4CFF9FDdD515eefE6CdF92 - 18 false USDC USDC 0x2F25deB3848C207fc8E0c34035B3Ba7fC157602B 0x4822e58de6f5e485eF90df51C41CE01721331dC0 6 true Mento Dollar USDm 0x874069Fa1Eb16D44d622F2e0Ca25eeA172369bC1 - 18 false ECO CFA eXOF 0xB0FA15e002516d0301884059c0aaC0F0C72b019D - 18 false TetherToken USD₮ 0xC4f86E9B4A588D501c1c3e25628dFd50Bc8D615e - 18 false Mento Brazilian Real REALm 0xE4D517785D091D3c54818832dB6094bcc2744545 - 18 false ``` ::: For a full example, visit the [Wagmi documentation](https://wagmi.sh/react/guides/send-transaction). ## Wait for Transaction Receipt (Optional) To enhance user experience, you can wait for the transaction receipt to confirm the transaction's success. For full lifecycle tracking and status UI, see [Transaction status](./transaction-status.md). The following example shows the receipt pattern with a native CELO send; for token transfers use the same `useWaitForTransactionReceipt` with the hash from a `sendTransaction` call that uses encoded ERC20 `transfer` data as in the tabs above. ```tsx import { useSendTransaction, useWaitForTransactionReceipt, // [!code ++] } from "wagmi"; import { parseEther } from "viem"; const Example = () => { const { data: hash, isPending, sendTransaction } = useSendTransaction(); const address = "0xA0Cf…251e"; const amount = "0.05"; const onPress = async (e) => { e.preventDefault(); await sendTransaction({ to: address, value: parseEther(amount.toString()), }); }; const { // [!code ++] isLoading: isConfirming, // [!code ++] isSuccess: isConfirmed, // [!code ++] } = // [!code ++] useWaitForTransactionReceipt({ // [!code ++] hash, // [!code ++] }); // [!code ++] return ( <> {isConfirming &&
Waiting for confirmation...
} {isConfirmed &&
Transaction confirmed.
} ); }; ``` ## Explanation * **useSendTransaction**: This hook is used to initiate a transaction. It provides the `sendTransaction` function, which is called with the transaction details, such as the recipient address and the amount. * **useWaitForTransactionReceipt**: This optional hook is used to wait for the transaction receipt, providing feedback on the transaction's confirmation status. It helps in tracking whether the transaction has been successfully mined. * **Transaction Details**: The transaction details include the recipient address and the amount to be sent. The `parseEther` function is used to convert the Ether amount into the appropriate format for the transaction. * **UI Feedback**: The component provides feedback to the user about the transaction status, such as when the transaction is pending or confirmed. This enhances user interaction and transparency. ## Error handling Handle transaction errors with user-friendly messages: prefer error codes (e.g. `-32604`) or standard names (e.g. `UserRejectedRequestError`) over message text, since provider messages can change. See [Best practices — Error handling](/getting-started/best-practices#error-handling). ## User feedback and status Provide clear feedback during the transaction lifecycle (sending, confirming, success, error). For status tracking and reusable UI patterns, see [Transaction status](./transaction-status.md). ## Notes * Ensure `wagmi` is correctly configured. Follow our [Quick Start](/getting-started/quick-start) guide to know how. * **Address Validation**: Always validate recipient addresses before sending transactions. * **Error Handling**: Implement comprehensive error handling for network errors, insufficient funds, and user rejections. See [Best practices — Error handling](/getting-started/best-practices#error-handling). * **Security Considerations**: Always validate and sanitize user inputs to prevent potential security vulnerabilities. * **Transaction Status**: Use `useWaitForTransactionReceipt` to track transaction confirmation and provide user feedback. See [Transaction status](./transaction-status.md). * **User Experience**: Provide clear feedback at each stage of the transaction lifecycle (sending, confirming, success, error). For more information, refer to the [Wagmi documentation](https://wagmi.sh/react/getting-started) and see our [transaction status guide](./transaction-status.md). --- --- url: /getting-started/setup-react.md --- # Setting up a React App with MiniPay This guide is the **manual alternative** to the [Celopedia Quick Start](./quick-start) — it walks through setting up a React project with MiniPay wallet integration by hand using Vite, TypeScript, and Wagmi. If you'd rather have your AI assistant scaffold this for you, start with the [Quick Start](./quick-start) instead. ## Create Your Project Create a new React app using Vite: ```bash npm create vite@latest # or pnpm create vite # or bun create vite # or yarn create vite ``` Choose: * **Framework**: React * **Variant**: TypeScript Then install dependencies: ```bash cd mini-app npm install # or pnpm install / bun install / yarn install ``` ## Install Wallet Dependencies Install Wagmi, Viem, and React Query for wallet integration: ```bash npm install wagmi viem@2.x @tanstack/react-query # or pnpm add / bun add / yarn add ``` ## Configure Wagmi for MiniPay Create a `wagmi.ts` file in your `src` directory: ```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: [celo, celoSepolia], connectors: [ injected(), // MiniPay injects window.ethereum ], transports: { [celo.id]: http(), [celoSepolia.id]: http(), }, }); ``` ## Set Up Your App Wrap your app with the WagmiProvider and set up auto-connect: ```tsx // src/App.tsx import { WagmiProvider } from "wagmi"; import { QueryClient, QueryClientProvider } from "@tanstack/react-query"; import { config } from "./wagmi"; import { useAutoConnect } from "./hooks/useAutoConnect"; const queryClient = new QueryClient(); function AppContent() { useAutoConnect(); // Auto-connect to MiniPay on load return (

My Mini App

{/* Your app content */}
); } function App() { return ( ); } export default App; ``` ## Create Auto-Connect Hook Create `src/hooks/useAutoConnect.ts`: ```tsx import { useEffect } from "react"; import { useConnect, useConnectors } from "wagmi"; export function useAutoConnect() { const connectors = useConnectors(); const { connect } = useConnect(); useEffect(() => { // Auto-connect on page load - required for MiniPay if (connectors.length > 0) { connect({ connector: connectors[0] }); } }, [connectors, connect]); } ``` ## Optional: Check for MiniPay and provider Your app should run inside MiniPay where `window.ethereum` is injected. You can throw a clear error if the provider is missing: ```ts // src/env.ts or similar export function getEthereumProvider() { if (typeof window === "undefined" || !window.ethereum) { throw new Error( "window.ethereum is required. Please run this app inside MiniPay." ); } return window.ethereum; } // Optional: detect MiniPay export function isMiniPay(): boolean { return typeof window !== "undefined" && window.ethereum?.isMiniPay === true; } ``` If you use a **custom transport** in Wagmi (e.g. `custom(getEthereumProvider())`), this check runs when the config is used. See [Project setup](./project-setup) for a full config example. ## Verify connection Test that your app connects to MiniPay using Wagmi v3's `useConnection()`: ```tsx import { useConnection } from "wagmi"; function WalletStatus() { const { address, isConnected, isConnecting, chainId } = useConnection(); if (isConnecting) { return
Connecting to MiniPay...
; } if (!isConnected || !address) { return
Not connected. Run this app inside MiniPay.
; } return (

Address: {address}

Chain ID: {chainId}

); } ``` ## Next steps * Learn about [wallet connection patterns](./wallet-connection.md) * See how to [retrieve balances](../technical-references/retrieve-balance.md) * Learn to [send transactions](../technical-references/send-transaction.md) * Check the [getting started guide](./index.md) for more details --- --- url: /getting-started/submit-your-miniapp.md --- # Submit your MiniApp To get your Mini App listed in MiniPay's Discover page, submit it using our submission form. ## Submission Form [Submit your Mini App](https://developer.minipay.to/mini-app-listing) ## Listing fields When you submit, you will be asked for the following. Prepare these before filling the form: | Field | Description | | --------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | **App name** | Clear, descriptive name for your Mini App. | | **Tagline** | Brief description (1–2 sentences). | | **Publisher** | Your name or organization. | | **Support URL** | In-app support link (visible inside the Mini App). Ideally Telegram, WhatsApp, email, or a web support portal. | | **Terms of Service** | Accessible link within the app to your Terms of Service. | | **Privacy Policy** | Accessible link within the app to your Privacy Policy. | | **Category** | One of: games, social, finance, utility, productivity, health-fitness, news-media, music, shopping, education, developer-tools, entertainment, art-creativity, rewards, sports. | | **App URL (linkUrl)** | The HTTPS URL where your Mini App is hosted. Must be publicly accessible over HTTPS. | | **Icon** | High-quality icon (recommended: 512×512px). | There is no manifest or config file in your repo—listing is done only through this submission process. ## Submission requirements Before submitting, ensure your Mini App meets these requirements: ### Technical requirements * ✅ **Auto-connects to wallet** - No connect button, connection happens automatically * ✅ **HTTPS enabled** - Your app must be served over HTTPS * ✅ **Mobile-optimized** - Responsive design that works on mobile devices (minimum viewport **360×640**; see [UI & container](./ui-and-container)) * ✅ **Works on Celo networks** - Supports Celo Mainnet and/or Celo Sepolia testnet * ✅ **Error handling** - Graceful error handling for wallet operations * ✅ **Performance** - Provide a [PageSpeed Insights](https://pagespeed.web.dev/) score for your production URL. High performance is a prerequisite for listing. * ✅ **Network manifest** - Provide a full manifest of all URLs, subdomains, and origins your app uses (including external links for JS, CSS, and APIs). ### Content requirements Use the [listing fields](#listing-fields) above as a checklist. Ensure your app name, tagline, icon, category, support URL, and publisher are accurate and complete. ### Branding and legal * **Clear ownership** - Your app must clearly display its name and logo. It must be obvious to the user that the service is operated by your entity and not by MiniPay. * **Terms of Service and Privacy Policy** - Provide accessible in-app links to your Terms of Service and Privacy Policy. ### Dependency security To reduce exposure to npm supply chain attacks (compromised or malicious package versions that are typically yanked within days of being published), pin a minimum published age for all npm dependencies. * **Pin exact versions** — Use exact versions in `package.json` (e.g. `"viem": "2.21.0"`), not ranges (`^2.21.0`, `~2.21.0`, `*`). Ranges let a fresh malicious release slip in on the next install, even with a lockfile if it's regenerated. * **Minimum published age** — Require **at least 7 days** before a newly published version may be installed. Prefer a longer window (e.g. 14 or 30 days) if your release cadence allows. * **How to enforce** — Configure your package manager so installs reject versions younger than the threshold: * **npm (v11+)** — set `minimumReleaseAge` in `.npmrc` (e.g. `minimum-release-age=10080` for 7 days, in minutes). * **pnpm (v10.16+)** — set `minimumReleaseAge: 10080` in `pnpm-workspace.yaml` or `.npmrc`. * **Bun** — pass `--minimum-release-age=604800` (in seconds) or set it in `bunfig.toml`. * **Ignore install scripts** — Set `ignore-scripts=true` in `.npmrc` so postinstall scripts from dependencies cannot run automatically. * **Lockfile** — Commit your lockfile and use `npm ci` / `pnpm install --frozen-lockfile` / `bun install --frozen-lockfile` in CI so installs are reproducible and cannot silently pull a newer version. ### Smart contracts (if your app uses them) * **Contract verification** - All contract source code must be published and verified on [Celoscan](https://celoscan.io). * **Sample transactions** - Provide links to sample transactions for every user-facing contract method. See [Interacting with smart contracts](./smart-contracts#best-practices) for guidance. ### Support and SLA * **Dedicated support** - Provide an in-app support link (see [listing fields](#listing-fields)); ideally Telegram, WhatsApp, email, or a web support portal. * **Critical issues** - You must fix reported critical issues within **24 hours**, or the listing may be temporarily disabled. ### Best practices * **Test thoroughly** - Test your app in MiniPay's Developer Mode before submitting * **Clear value proposition** - Users should understand what your app does immediately * **Fast loading** - Optimize for quick load times * **User-friendly** - Intuitive interface and clear error messages ## What Happens After Submission 1. **Review Process** - Our team reviews your submission 2. **Testing** - We test your app in MiniPay 3. **Feedback** - If needed, we'll provide feedback for improvements 4. **Approval** - Once approved, your app will be listed in MiniPay's Discover page ## Common Rejection Reasons * App doesn't auto-connect to wallet * App not served over HTTPS * Poor mobile experience * Missing error handling * App doesn't work on Celo networks * Incomplete or unclear submission information ## Tips for Successful Submission 1. **Follow guidelines** - Read and follow all documentation guidelines 2. **Test in MiniPay** - Use Developer Mode to test your app thoroughly 3. **Provide clear information** - Fill out all submission fields completely 4. **Include support** - Provide a support URL or contact information 5. **Polish your app** - Ensure it's production-ready before submitting ## Need Help? If you have questions about the submission process or need help with your Mini App: * Check our [FAQ](../faq.md) * Review our [best practices guide](./best-practices.md) * See our [example implementations](./examples.md) * Browse the broader Celo ecosystem reference at [celopedia.celo.org](https://celopedia.celo.org/) --- --- url: /technical-references.md description: >- API reference for the MiniPay wallet from Mini Apps—provider, signing, transactions, and tokens. --- # Technical Reference This section documents how to interact with the MiniPay wallet from your Mini App: provider, signing, transactions, and tokens. ## SDK / APIs at a glance * **Provider:** MiniPay injects an Ethereum provider when it loads your app. It is available as `window.ethereum` and follows the standard [EIP-1193](https://eips.ethereum.org/EIPS/eip-1193) API. You can detect MiniPay with `window.ethereum?.isMiniPay === true`. Optionally wrap access in a `getEthereumProvider()` that throws if the provider is missing (see [Project setup](/getting-started/project-setup)). * **Wagmi:** We recommend [Wagmi](https://wagmi.sh/react/getting-started) (with Viem) to talk to the provider. Use the **injected** connector and configure **Celo** and **Celo Sepolia** chains. In Wagmi v3, use `useConnection()` for address, chainId, and connection state. * **Signing and transactions:** Send tokens (e.g. USDC, USDT, USDm) via `useSendTransaction`. See [Send a transaction](./send-transaction) and [Transaction status](./transaction-status). * **Tokens:** Read balances with `useReadContracts` (balanceOf, decimals, symbol). See [Retrieve balance](./retrieve-balance). Supported networks: Celo Mainnet and Celo Sepolia only. > \[!NOTE] > Wagmi is not required. You can use `window.ethereum` directly (e.g. with Viem's `createPublicClient` and `custom(provider)`), but Wagmi simplifies React integration. ## Wallet connection * [Wallet connection](/getting-started/wallet-connection) — Auto-connect, connection state, and error handling ## Code examples In addition to the [Quick Start guide](/getting-started/quick-start), here are code examples for common wallet operations. ### Basic operations * [Retrieve a balance](./retrieve-balance) — Get token balances from the wallet * [Send a transaction](./send-transaction) — Send token transactions (e.g. USDC, USDT, USDm) * [Transaction status](./transaction-status) — Track transaction confirmation * [Chain switching](./chain-switching) — Celo and Celo Sepolia ### Advanced features * [Estimate gas](./gas-estimation) — Estimate transaction gas costs * [Phone number lookup](./phone-number-lookup) — Map phone number to wallet address * [Custom methods](./custom-methods/custom-methods) — MiniPay-specific RPC methods * [Deeplinks](./deeplinks) — Deep linking into MiniPay ### Smart contracts * [Interacting with smart contracts](/getting-started/smart-contracts) — Read and write to smart contracts --- --- url: /getting-started/test-in-minipay.md --- # Test your Mini App inside MiniPay In this guide, you will learn how to test your Mini App inside MiniPay. Follow the steps below to get started. ## Installing MiniPay 1. Install MiniPay on your device. * [Standalone MiniPay App (Android)](https://play.google.com/store/apps/details?id=com.opera.minipay) * [Standalone MiniPay App (iOS)](https://apps.apple.com/app/id6504087257) * [Opera Mini Browser (Android)](https://www.opera.com/products/minipay) 2. Create an account: Sign up using your Google or Apple account and phone number. ## Test inside MiniPay ### Enable Developer Mode: 1. Open the MiniPay app on your phone and navigate to settings. 2. In the About section, tap the Version number repeatedly until the confirmation message appears. 3. Return to Settings, then select Developer Settings. 4. Enable Developer Mode and toggle **Use Testnet** to connect to **Celo Sepolia** testnet (Chain ID 11142220). For production testing, leave testnet off to use Celo Mainnet. ### Load Your Mini App: 1. In Developer Settings, tap "Load test page". 2. Enter your Mini App URL. > \[!TIP] > For local development, use [ngrok](https://ngrok.com/) to expose your localhost. See [How do I run ngrok?](#q-how-do-i-run-ngrok) below. Add `allowedHosts` in [Vite](/getting-started/project-setup#vite-config) so the dev server accepts the ngrok host. 3. Click **Load** to launch and test your Mini App. ## Local testing 1. **Start your dev server** (e.g. `npm run dev` or `pnpm dev`). Note the port (e.g. 5173). 2. **Expose it with ngrok** (see below): `ngrok http 5173`. 3. **Copy the HTTPS Forwarding URL** from ngrok and paste it into MiniPay's "Load test page" field. 4. Ensure your Vite config has `allowedHosts` for ngrok so the dev server accepts requests (see "Blocked request" below). ## Sandbox vs production * **Testnet (Celo Sepolia):** Use Developer Mode's "Use Testnet" to test with fake funds and avoid mainnet fees. * **Mainnet (Celo):** Turn testnet off to test with real Celo and stablecoins. Use small amounts. ## Debugging inside MiniPay * **Console:** If your Mini App opens in a WebView that supports remote debugging, use your IDE or browser devtools to attach and view `console.log` / errors. * **Common errors:** "window.ethereum is required" means the app is not running inside MiniPay—load it via "Load test page". "Blocked request" usually means Vite needs `allowedHosts` for your ngrok domain. ## Troubleshooting ### **Q: How do I run `ngrok`?** A: [Ngrok](https://ngrok.com/) is a command line interface (CLI) that runs on your local machine. It lets you expose your local server to the rest of the internet. So, once it's running, everyone can access your local environment. 1. First, you need to install and configure `ngrok`. Follow [ngrok's quickstart guide](https://ngrok.com/docs/getting-started/) to learn how. 2. When you're ready, start `ngrok` by running: ```bash ngrok http [port] #replace [port] with the port used by your localhost ``` 3. Use the `Forwarding` URL you see in your terminal to access your local Mini App inside MiniPay. ``` ngrok (Ctrl+C to quit) Session Status online Account inconshreveable (Plan: Free) Version 3.0.0 Region United States (us) Latency 78ms Web Interface http://127.0.0.1:4040 Forwarding https://84c5df474.ngrok-free.dev -> http://localhost:8080 Connections ttl opn rt1 rt5 p50 p90 0 0 0.00 0.00 0.00 0.00 ``` ### **Q: Seeing a *Blocked request* error?** A: If you're using Vite, make sure to allow the host you're using. Here's an example of the changes to make to `vite.config.ts` when using `ngrok`. ```tsx 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"], // [!code ++] }, }); ``` --- --- url: /technical-references/transaction-status.md --- # Transaction Status Track and monitor transaction status throughout its lifecycle, from submission to confirmation. ## Basic Status Tracking Use `useWaitForTransactionReceipt` to track transaction confirmation: ```tsx import { useWaitForTransactionReceipt } from "wagmi"; import { useSendTransaction } from "wagmi"; function TransactionTracker() { const { data: hash, sendTransaction } = useSendTransaction(); const { isLoading: isConfirming, isSuccess: isConfirmed, isError, data: receipt, } = useWaitForTransactionReceipt({ hash }); const handleSend = () => { sendTransaction({ to: "0xA0Cf…251e", value: parseEther("0.05"), }); }; return (
{hash && (

Transaction Hash: {hash}

{isConfirming &&

Waiting for confirmation...

} {isConfirmed && receipt && (

✅ Transaction confirmed!

Block: {receipt.blockNumber.toString()}

Gas used: {receipt.gasUsed.toString()}

)} {isError &&

❌ Transaction failed

}
)}
); } ``` ## Transaction Lifecycle States Track all states of a transaction: ```tsx type TransactionState = | "idle" | "preparing" | "signing" | "pending" | "confirming" | "confirmed" | "failed"; function useTransactionState() { const [state, setState] = useState("idle"); const { sendTransaction, isPending, data: hash } = useSendTransaction(); const { isLoading: isConfirming, isSuccess, isError, } = useWaitForTransactionReceipt({ hash }); useEffect(() => { if (isPending && !hash) { setState("signing"); } else if (hash && isConfirming) { setState("confirming"); } else if (isSuccess) { setState("confirmed"); } else if (isError) { setState("failed"); } }, [isPending, hash, isConfirming, isSuccess, isError]); const handleTransaction = async () => { setState("preparing"); try { await sendTransaction({ to: "0xA0Cf…251e", value: parseEther("0.05"), }); } catch (error) { setState("failed"); } }; return { state, handleTransaction }; } ``` ## Polling for Status Manually poll for transaction status if needed: ```tsx import { usePublicClient } from "wagmi"; function useTransactionPolling(hash: `0x${string}` | undefined) { const publicClient = usePublicClient(); const [status, setStatus] = useState<"pending" | "confirmed" | "failed">( "pending", ); useEffect(() => { if (!hash || !publicClient) return; const checkStatus = async () => { try { const receipt = await publicClient.getTransactionReceipt({ hash }); if (receipt) { setStatus("confirmed"); } } catch (error) { // Transaction might not be mined yet setTimeout(checkStatus, 2000); // Poll every 2 seconds } }; checkStatus(); }, [hash, publicClient]); return status; } ``` ## Transaction Receipt Details Access detailed receipt information: ```tsx function TransactionDetails({ hash }: { hash: `0x${string}` }) { const { data: receipt, isLoading } = useWaitForTransactionReceipt({ hash }); if (isLoading) { return
Loading transaction details...
; } if (!receipt) { return
Transaction not found
; } return (

Transaction Details

Status: {receipt.status === "success" ? "Success" : "Failed"}

Block Number: {receipt.blockNumber.toString()}

Block Hash: {receipt.blockHash}

Gas Used: {receipt.gasUsed.toString()}

Effective Gas Price: {receipt.gasPrice?.toString()}

Transaction Hash: {receipt.transactionHash}

From: {receipt.from}

To: {receipt.to}

); } ``` ## Error handling Transaction confirmation failures (e.g. revert) are exposed via `useWaitForTransactionReceipt`'s `isError` and `error`. Use them to show a failure message or retry. For user rejection and send-time errors, see [Best practices — Error handling](/getting-started/best-practices#error-handling). ## Status UI Component Create a reusable transaction status component: ```tsx function TransactionStatus({ hash, onSuccess, }: { hash: `0x${string}` | undefined; onSuccess?: () => void; }) { const { isLoading, isSuccess, isError, data: receipt, } = useWaitForTransactionReceipt({ hash }); useEffect(() => { if (isSuccess && onSuccess) { onSuccess(); } }, [isSuccess, onSuccess]); if (!hash) return null; if (isLoading) { return (

Transaction pending...

{hash}

); } if (isError) { return (

❌ Transaction failed

{hash}

); } if (isSuccess && receipt) { return (

✅ Transaction confirmed!

Block: {receipt.blockNumber.toString()}

{hash}

); } return null; } ``` ## Best Practices 1. **Always track status**: Use `useWaitForTransactionReceipt` to monitor transaction confirmation 2. **Show user feedback**: Display clear status messages at each stage 3. **Handle errors gracefully**: Provide actionable error messages 4. **Display transaction hash**: Allow users to view their transaction on block explorers 5. **Call callbacks on success**: Use `useEffect` to trigger actions when transaction confirms ## Block Explorer Links Link to block explorers for transaction details: ```tsx function TransactionLink({ hash, chainId, }: { hash: `0x${string}`; chainId: number; }) { const explorerUrl = chainId === 42220 ? `https://celoscan.io/tx/${hash}` // Celo mainnet : `https://sepolia.celoscan.io/tx/${hash}`; // Celo Sepolia return ( View on CeloScan ); } ``` ## Next Steps * Learn about [sending transactions](./send-transaction.md) * See [best practices](../getting-started/best-practices.md) for wallet interactions * Check out [example implementations](../getting-started/examples.md) --- --- url: /getting-started/ui-and-container.md --- # UI & container integration Mini Apps run inside MiniPay's in-app browser. This page covers viewport behavior, navigation, theming, and mobile constraints so your app feels native in the container. ## Viewport and layout * **Mobile only:** Mini Apps run on phones. Design for small viewports — your app must be responsive and fully functional at **360×640px** minimum. Don’t build for desktop or tablet layouts. * **Single column:** A single-column layout works best. Avoid horizontal scrolling; use full-width or near full-width content. * **Height:** The viewport height can change (keyboard open, browser chrome). Prefer `min-height` and scrollable content rather than assuming a fixed height. ## Navigation * **In-app browser:** Users open your app from the MiniPay Discover page. There is no traditional browser address bar or tabs; back/forward is controlled by MiniPay or the WebView. * **In-app routing:** Use your framework’s router (e.g. React Router, TanStack Router) for multi-page flows. Avoid relying on the browser’s history for critical flows; keep state in React (or URL params) when possible. * **External links:** There are no tabs in the Mini App; links open in the same view and may leave the Mini App context. Use a normal link (no `target="_blank"`). For “back to MiniPay” or external actions, a normal link is fine. ## Theming * **No required theme:** MiniPay does not inject a mandatory theme or design tokens. You choose colors, typography, and spacing. * **Dark mode (optional):** You can support `prefers-color-scheme: dark` for users who prefer dark UI. So that system UI (scrollbars, form controls) follows the theme, set the [color-scheme meta tag](https://developer.mozilla.org/en-US/docs/Web/HTML/Reference/Elements/meta/name/color-scheme) in your `` (e.g. ``) or use `color-scheme: light dark` in CSS on `:root`. Then style with media queries: ```css @media (prefers-color-scheme: dark) { :root { --bg: #1a1a1a; --text: #e5e5e5; } } ``` Avoid doing this in JavaScript: ```ts const prefersDark = window.matchMedia("prefers-color-scheme: dark").matches; ``` * **Contrast:** Keep text and interactive elements readable (e.g. [WCAG](https://www.w3.org/WAI/WCAG21/quickref/) AA). See [Design standards](/design-standards/) for more. ## Mobile constraints | Concern | Recommendation | | ----------------- | --------------------------------------------------------------------------------------------------------------- | | **Touch targets** | Buttons and links at least 44×44px; adequate spacing between tappable elements. | | **Text size** | Body text at least 16px; avoid very small labels. | | **Performance** | Lazy-load below-the-fold content; avoid heavy work on first paint so the app feels fast. | | **Network** | Some users are on slow or unstable connections. Show loading states and handle errors; avoid assuming fast RPC. | | **Viewport** | Don’t assume a fixed width or height; test on large and small phones. | ## Summary * Design for mobile, single column. * Use in-app routing for multi-step flows; external links may leave the Mini App. * Theming is up to you; optional dark mode improves accessibility. * Keep touch targets large, text readable, and first load light. For more UI guidelines, see [Design standards](/design-standards/). For testing inside MiniPay, see [Test in MiniPay](/getting-started/test-in-minipay). --- --- url: /getting-started/wallet-connection.md description: >- How to connect wallets and use the injected provider in Mini Apps — auto-connect, connection state, and error handling. --- # Wallet Connection This guide covers wallet connection patterns for MiniPay Mini Apps: auto-connect, connection state, and error handling. Mini Apps are designed to **run inside MiniPay**; the wallet connection must happen automatically on page load — never show a "Connect wallet" button. Do not prompt users to sign a message to access your site or authenticate; connection is automatic. ## Run inside MiniPay MiniPay injects `window.ethereum` when it loads your app. If the provider is missing, your app is not running in MiniPay. Check before connecting: ```tsx function getEthereumProvider() { if (typeof window === "undefined" || !window.ethereum) { throw new Error( "window.ethereum is required. Please run this app inside MiniPay." ); } return window.ethereum; } // Optional: detect MiniPay specifically if (window.ethereum?.isMiniPay) { console.log("Running in MiniPay"); } ``` Use this in a custom Wagmi transport or when creating a public client. See [Project setup](./project-setup) for a full config. ## Auto-connect MiniPay requires that Mini Apps **automatically connect** to the wallet when the page loads. **Never show a connect button.** Use a single hook that connects on mount and tracks loading/error: ```tsx import { useEffect, useState } from "react"; import { useConnect, useConnectors } from "wagmi"; export function useAutoConnect() { const connectors = useConnectors(); const { connect, error, isPending } = useConnect(); const [hasAttempted, setHasAttempted] = useState(false); useEffect(() => { if (hasAttempted || connectors.length === 0) return; const attemptConnect = async () => { try { await connect({ connector: connectors[0] }); } catch (err) { console.error("Failed to connect:", err); } setHasAttempted(true); }; attemptConnect(); }, [connectors, connect, hasAttempted]); return { error, isPending }; } ``` ## Detecting MiniPay Check `window.ethereum?.isMiniPay === true` to detect MiniPay. Use this only if you need to branch behavior; most apps just require `window.ethereum` and auto-connect. ## Connection state Use Wagmi v3's `useConnection()` to track address, chainId, isConnected, isConnecting, and error: ```tsx import { useConnection } from "wagmi"; function WalletStatus() { const { address, isConnected, isConnecting, chainId, error } = useConnection(); if (isConnecting) return
Connecting to MiniPay...
; if (!isConnected || !address) { return (

Not connected. Run this app inside MiniPay.

{error &&

Error: {error.message}

}
); } return (

Connected: {address}

Chain ID: {chainId}

); } ``` ## Error handling Show "Not connected" when there is no address; show a user-friendly message when `useConnection().error` or `useConnect().error` is set (e.g. "Open this app from MiniPay" or "Connection failed. Unlock MiniPay and try again."). Prefer error codes or standard error names over message text. For patterns and examples, see [Best practices — Error handling](./best-practices#error-handling). ## Best practices 1. **Run inside MiniPay**: Your app expects `window.ethereum`; show a clear message or use `getEthereumProvider()` to throw if it's missing. 2. **Always auto-connect**: Never show a connect button; connect on page load. 3. **Handle errors gracefully**: Show user-friendly messages for connection failures. 4. **Check provider availability**: Verify `window.ethereum` exists (and optionally `isMiniPay`) before connecting. 5. **Provide loading states**: Show "Connecting to MiniPay..." while connecting. ## Common issues * **Provider not found**: If `window.ethereum` is undefined, the app is not in MiniPay. Show a message or redirect; see `getEthereumProvider()` above. * **Connection rejected**: With auto-connect this is rare. If you see an error, check `error.code === -32604` or `error.name === "UserRejectedRequestError"` and show "Transaction cancelled" or similar. * **Multiple connection attempts**: Use a `hasAttempted` (or similar) flag so the auto-connect effect runs only once. ## Next Steps * Learn about [retrieving balances](../technical-references/retrieve-balance.md) * See how to [send transactions](../technical-references/send-transaction.md) * Check out [best practices](./best-practices.md) for wallet interactions --- --- url: /getting-started/overview.md --- # What are Mini Apps? Mini Apps are **web applications** that run inside MiniPay's in-app browser. Users open them from the MiniPay Discover page without leaving the wallet. Your app gets instant access to the user's wallet and the Celo network — no separate onboarding or extension required. ## How Mini Apps run When a user opens your Mini App from MiniPay: 1. MiniPay loads your app's URL in its in-app browser (WebView). 2. MiniPay **injects an Ethereum provider** at `window.ethereum`. 3. Your app uses this provider (directly or via Wagmi/Viem) to read the wallet address, request signatures, and send transactions on Celo. ```mermaid sequenceDiagram participant User participant MiniPay participant MiniApp participant Provider as window.ethereum participant Celo User->>MiniPay: Opens Mini App from Discover MiniPay->>MiniApp: Loads your URL MiniPay->>MiniApp: Injects window.ethereum MiniApp->>Provider: Request account / chainId Provider->>MiniApp: address, chainId MiniApp->>Celo: Read balance or send transaction Celo->>MiniApp: Result Note over MiniApp,MiniPay: User sees your app's UI inside MiniPay ``` So: **your app is a normal web app** (React, Vite, etc.) that expects to run in a context where `window.ethereum` is already available. You do not ship a native binary or a special manifest — just a URL that MiniPay opens. ## Two ways to start * **Recommended:** scaffold with [Celopedia](/getting-started/quick-start) — install one skill (`npx skills add celo-org/celopedia-skills`) and your AI assistant generates a wired-up Mini App template in one prompt. Learn more at [celopedia.celo.org](https://celopedia.celo.org/). * **Manual:** wire Wagmi + Vite + auto-connect yourself — see [Setup with React](/getting-started/setup-react). ## Key capabilities | Capability | Description | | -------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | **Wallet access** | Read connected address and chain ID; no "Connect" button — MiniPay connects automatically when the app loads. | | **Signing & transactions** | Send stablecoins (USDm, USDC, USDT) and interact with smart contracts. | | **Celo networks** | [Celo Mainnet](https://celoscan.io) (Chain ID 42220) and [Celo Sepolia](https://sepolia.celoscan.io) testnet (Chain ID 11142220) are the only supported networks for Mini Apps. | | **Optional features** | [Phone number lookup](/technical-references/phone-number-lookup) (map phone to address), [deeplinks](/technical-references/deeplinks), and [custom RPC methods](/technical-references/custom-methods/custom-methods) for MiniPay-specific behavior. | ## What you need to build * A **public HTTPS URL** that serves your web app (Vite, Next.js, etc.). * **Wagmi** (or similar) configured with the **injected** connector and **Celo + Celo Sepolia** chains. * **Auto-connect on load**—never show a "Connect wallet" button; connection must happen when the page loads. * **Mobile-friendly UI**—most users are on phones; design for small viewports and touch. ## Next steps * [Quick Start](/getting-started/quick-start) — Get a minimal Mini App running in a few steps. * [Project setup](/getting-started/project-setup) — Repo structure, config, and environment variables. * [Test in MiniPay](/getting-started/test-in-minipay) — Load your app inside the wallet using Developer Mode.