---
# System prepended metadata

title: 'Beginner''s Guide: Build & Publish Your First Solana Mobile dApp'

---

# Beginner's Guide: Build & Publish Your First Solana Mobile dApp

A practical, end-to-end walkthrough that takes you from zero to a minimal working dApp published on the **Solana dApp Store**. No prior Solana Mobile experience required — just basic JavaScript/React (or Android) knowledge.

---

## 1. Overview: Solana Mobile Stack & dApp Store

The **Solana Mobile Stack (SMS)** is a set of SDKs, protocols, and on-chain tools that make it easy for mobile apps to interact with the Solana blockchain. The pieces you care about as a beginner:

| Component | What it does |
|-----------|--------------|
| **Mobile Wallet Adapter (MWA)** | A protocol letting your dApp talk to any installed Solana wallet (Phantom, Solflare, Seed Vault). Handles connection + signing. |
| **Seed Vault** | Hardware-backed key custody on Seeker devices. Apps use it transparently through MWA. |
| **Solana dApp Store** | Crypto-native alternative to Google Play. Accepts APKs, no 30% fee, supports wallet/on-chain integrations Google blocks. |
| **Publishing NFTs** | Each publisher, app, and release is represented by an NFT on Solana. This creates a permanent, verifiable publishing chain. |

### How the dApp Store differs from Google Play
- **APKs, not AABs.** You submit raw APK files.
- **Separate signing key.** You cannot reuse your Google Play upload key.
- **NFT-based publishing.** Publisher → App → Release are each minted on-chain.
- **No 30% fee** on in-app purchases or transactions.
- **Crypto-native**: token-gated content, on-chain economies, and wallet-first UX are welcomed.

---

## 2. Prerequisites

Install these before starting:

- **Node.js 18+** and **npm** / **yarn**
- **Android Studio** (for the emulator + SDK tools)
- **Java JDK 17** (`keytool` for APK signing)
- **Expo CLI** (`npm i -g eas-cli`)
- **A Solana wallet** on your phone or emulator — install **Phantom** or **Solflare** from the Play Store / APK
- **Solana CLI** (`sh -c "$(curl -sSfL https://release.anza.xyz/stable/install)"`) — for creating the publisher keypair
- **A small amount of SOL** (~0.2 SOL on mainnet) — required to mint publisher/app/release NFTs when publishing
- An **Android device** (Android 10+) or an **emulator with Google Play services**

Optional but useful: a **Seeker device** for Seed Vault testing (not required for development).

---

## 3. Project Setup — The Simplest Possible dApp

The fastest path is the **official Expo template**, which preconfigures MWA and polyfills.

```bash
yarn create expo-app --template @solana-mobile/solana-mobile-expo-template my-first-dapp
cd my-first-dapp
```

### Folder structure (what matters)
```
my-first-dapp/
├── App.tsx                 # Entry point, polyfills + UI
├── index.js                # Crypto polyfills (Expo SDK 49+)
├── app.json                # Expo config: name, icon, bundle id
├── eas.json                # EAS build profiles (dev, preview, production)
├── package.json
└── assets/                 # Icon + splash
```

### Manual setup (if you prefer a plain Expo app)

```bash
npx create-expo-app my-first-dapp
cd my-first-dapp

yarn add \
  @solana/web3.js \
  @solana-mobile/mobile-wallet-adapter-protocol-web3js \
  @solana-mobile/mobile-wallet-adapter-protocol \
  react-native-get-random-values \
  buffer

npx expo install expo-crypto
```

Create `index.js` in the project root:

```js
import { getRandomValues as expoCryptoGetRandomValues } from "expo-crypto";
import { Buffer } from "buffer";
global.Buffer = Buffer;

class Crypto { getRandomValues = expoCryptoGetRandomValues; }
const webCrypto = typeof crypto !== "undefined" ? crypto : new Crypto();
if (typeof crypto === "undefined") {
  Object.defineProperty(window, "crypto", {
    configurable: true, enumerable: true, get: () => webCrypto,
  });
}
import "expo-router/entry";
```

Update `package.json`:
```json
"main": "index.js"
```

---

## 4. Integrate Wallet Connection (Mobile Wallet Adapter)

### How MWA works (mental model)
1. Your dApp calls `transact(async (wallet) => { ... })`.
2. The OS pops up the installed wallet app (Phantom/Solflare/Seed Vault).
3. The wallet authorizes your dApp, returning a public key + auth token.
4. Your dApp asks the wallet to sign/send transactions — the wallet handles the private key.

Your app **never sees the user's private key**. All signing happens inside the wallet.

### Minimal working example — connect + sign a transfer

Replace `App.tsx` with:

```tsx
import React, { useState } from "react";
import { Button, Text, View, Alert } from "react-native";
import {
  transact,
  Web3MobileWallet,
} from "@solana-mobile/mobile-wallet-adapter-protocol-web3js";
import {
  Connection,
  PublicKey,
  SystemProgram,
  Transaction,
  clusterApiUrl,
} from "@solana/web3.js";

const APP_IDENTITY = {
  name: "My First dApp",
  uri: "https://myfirstdapp.example",
};

export default function App() {
  const [pubkey, setPubkey] = useState<PublicKey | null>(null);

  const connect = async () => {
    await transact(async (wallet: Web3MobileWallet) => {
      const auth = await wallet.authorize({
        cluster: "solana:devnet",
        identity: APP_IDENTITY,
      });
      setPubkey(new PublicKey(auth.accounts[0].publicKey));
    });
  };

  const signAndSend = async () => {
    if (!pubkey) return;
    const connection = new Connection(clusterApiUrl("devnet"));
    const { blockhash } = await connection.getLatestBlockhash();

    const tx = new Transaction({ feePayer: pubkey, recentBlockhash: blockhash })
      .add(SystemProgram.transfer({
        fromPubkey: pubkey,
        toPubkey: pubkey, // self-transfer for demo
        lamports: 1000,
      }));

    const sig = await transact(async (wallet: Web3MobileWallet) => {
      await wallet.authorize({
        cluster: "solana:devnet",
        identity: APP_IDENTITY,
      });
      const [signature] = await wallet.signAndSendTransactions({
        transactions: [tx],
      });
      return signature;
    });
    Alert.alert("Sent!", sig);
  };

  return (
    <View style={{ flex: 1, justifyContent: "center", padding: 24 }}>
      <Text>Wallet: {pubkey?.toBase58() ?? "not connected"}</Text>
      <Button title="Connect Wallet" onPress={connect} />
      <Button title="Sign & Send" onPress={signAndSend} disabled={!pubkey} />
    </View>
  );
}
```

That's it — two buttons, wallet connect, and a signed devnet transaction.

---

## 5. Build & Test Locally

MWA needs a **development client** (Expo Go won't work — it has no native MWA module).

```bash
# One-time: log in to EAS
eas login

# Build a dev client APK for Android
npx eas build --profile development --platform android
# ...or fully local (requires Android SDK):
npx eas build --profile development --platform android --local
```

Install the resulting APK on your device or emulator, then start the JS bundler:

```bash
npx expo start --dev-client
```

**Test:** tap Connect → Phantom/Solflare opens → approve → tap Sign & Send → transaction signature appears.

> **Tip:** use **devnet** while developing — fund your wallet with `solana airdrop 1 <address> --url devnet`.

---

## 6. Generate a Release APK (Signed)

The dApp Store requires a **release APK signed with a dedicated key** (not shared with Google Play).

### Create a new keystore
```bash
keytool -genkey -v -keystore dappstore.keystore \
  -alias dappstore \
  -keyalg RSA -keysize 2048 -validity 10000
```
Store the keystore + password somewhere safe — losing them locks you out of future updates.

### Build release APK via EAS
Edit `eas.json`:
```json
{
  "build": {
    "production": {
      "android": { "buildType": "apk" }
    }
  }
}
```
Then:
```bash
npx eas build --profile production --platform android
```
EAS will prompt for credentials — choose **local credentials** and point to your new keystore.

### Verify the signature
```bash
apksigner verify --print-certs app-release.apk
```

---

## 7. Prepare Publishing Assets

You'll need:

- **App icon**: 512x512 PNG
- **Feature graphic**: 1200x600 PNG
- **Screenshots**: at least 4 (1080x1920 portrait recommended)
- **Short description**: ≤ 30 chars
- **Long description**: up to 4000 chars
- **Privacy policy URL** (a live webpage)
- **App category** (DeFi, Gaming, Social, etc.)
- **Version code / version name** (e.g., 1 / 1.0.0 — same as in your APK)

---

## 8. Publishing Process

There are **two paths**. Beginners should start with the Portal.

### Path A — Publisher Portal (recommended first time)
1. Go to https://publish.solanamobile.com and sign up.
2. Complete **KYC/KYB** (identity verification).
3. Connect a Solana wallet (Phantom/Solflare) as your **publisher wallet** — this mints the Publisher NFT.
4. Pick **ArDrive** as your storage provider (for assets + APK).
5. Click **Add a dApp** → fill metadata → upload icon, screenshots, feature graphic.
6. Click **New Version** → upload your signed release APK → approve the signing requests (each mints the App NFT and Release NFT).
7. Submit for review.

### Path B — Publishing CLI (for updates / CI)
After your first submission via the Portal, subsequent releases are faster via CLI:

```bash
npm install -g @solana-mobile/dapp-store-cli

# Generate an API key at:
# https://publish.solanamobile.com/dashboard/settings/api-keys
export DAPP_STORE_API_KEY=...

dapp-store --apk-file ./app-release.apk \
           --whats-new "Bug fixes and wallet connect improvements"
```
The CLI reads the package name from your APK and matches it to your existing app.

---

## 9. After Submission

- Review takes **3–5 business days**.
- Feedback arrives via email from `publishersupport@dappstore.solanamobile.com`.
- Approved apps go live in the selected category on the Seeker / Saga dApp Store.
- Updates follow the same flow but only mint a **new Release NFT** (Publisher + App NFTs are reused).

---

## 10. Why an APK for Web Apps? (PWA → APK)

The dApp Store is an **Android app store** — it distributes installable APKs. A PWA cannot be listed directly.

You wrap your PWA as an APK using **Bubblewrap** (Google's Trusted Web Activity tool) or **PWA Builder**:

```bash
npm i -g @bubblewrap/cli
bubblewrap init --manifest https://myapp.com/manifest.json
bubblewrap build
```
The output APK is then signed and submitted like any native app. Your PWA URL is loaded inside a Chrome Custom Tab — the user experiences it as a native app.

---

## 11. Common Pitfalls & Best Practices

**Pitfalls**
- Using **Expo Go** — MWA requires a dev client build.
- Reusing your **Google Play keystore** — the dApp Store requires a fresh one.
- Forgetting to change `cluster` from `devnet` to `mainnet-beta` before release.
- Version code not incremented between releases (CLI upload will fail).
- Missing `INTERNET` permission in `AndroidManifest.xml` (the template handles it).

**Best practices**
- Keep `APP_IDENTITY` consistent across the app — wallets display it to users.
- Cache the `auth_token` returned by `authorize()` and pass it to `reauthorize()` on later sessions to skip the approval prompt.
- Keep the keystore in a password manager + offline backup.
- Test on a real Android device before submitting — emulator MWA quirks exist.
- Use **devnet** aggressively; flip the cluster in one place when ready.

---

## 12. Quick Reference Links

- Docs root: https://docs.solanamobile.com
- Publisher Portal: https://publish.solanamobile.com
- Docs index: https://docs.solanamobile.com/llms.txt
- Discord (#dev-answers): http://discord.gg/solanamobile
- Expo template: `@solana-mobile/solana-mobile-expo-template`
- Publishing CLI: `@solana-mobile/dapp-store-cli`

---

**You now have:** a working wallet-connected dApp, a signed release APK, metadata ready, and a path through the Portal to get an NFT-backed listing live on the Solana dApp Store. Ship it.
