# 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.