solana-mobile/solana-mobile-skills실행 전 동작 확인
SKILL DETAIL
solana-mobile
solana-mobile/solana-mobile-skills/solana-mobile
This skill guides you through scaffolding, configuring, and troubleshooting Solana Mobile apps for Android using the solana-mobile CLI, Expo, and React Native. It covers creating new projects, choosing templates, adding Solana to an existing Expo app, checking the Android toolchain, managing Android emulators, and producing development builds. The skill emphasizes using the solana-mobile CLI for scaffolding and environment checks, and notes that Mobile Wallet Adapter requires a development build and does not work in Expo Go.
설치 수 · 950출처 보기
Installation
npx skills add https://github.com/solana-mobile/solana-mobile-skills --skill solana-mobile
스킬 파일
SKILL.md
최근 동기화 · 2026. 10. 21.
references/add-to-existing-app.md›
# Adding Solana to an existing Expo app
For projects that already have an Expo app and need wallet support added. If there is no
project yet, `npx solana-mobile@latest create` does all of this — use it instead.
Before starting, confirm the app is not depending on Expo Go. MWA needs a development build.
## 1. Compare against a template first
The fastest way to get this right is to scaffold the matching template somewhere temporary
and diff the wiring against the existing app:
```bash
npx solana-mobile@latest create /tmp/reference-app --template expo-kit-wallet --skip-install
```
Files worth comparing: `index.js`, `polyfill.js`, the `main` field in `package.json`,
`metro.config.js`, and the app-providers module. This catches version-specific details that
prose goes stale on.
## 2. Install dependencies
**Use kit** unless the app's Solana client is already `@solana/web3.js`. This is what
`expo-kit-minimal` ships:
```bash
npm install @wallet-ui/react-native-kit @solana/kit @tanstack/react-query react-native-quick-crypto
```
Add `@solana-program/memo` or other program clients as you need instruction builders.
Only if the app is already committed to web3.js — matching an existing client beats mixing two:
```bash
npm install @wallet-ui/react-native-web3js @solana/web3.js @tanstack/react-query react-native-quick-crypto
```
`react-native-quick-crypto` is a native module. It needs a rebuild, not just a Metro restart.
## 3. Install the crypto polyfill
Both stacks need Web Crypto before any Solana code runs. Put `install()` in its own module so
import ordering cannot be reshuffled by a formatter, linter, or import-sorting rule:
```js
// polyfill.js
import { install } from 'react-native-quick-crypto'
install()
```
```js
// index.js
import './polyfill'
import 'expo-router/entry'
```
Then point `package.json` at it:
```json
{
"main": "./index.js"
}
```
For a non-Expo-Router app, replace `expo-router/entry` with the app's existing entry import
(`registerRootComponent`, or whatever it uses today) — the requirement is only that the
polyfill import comes first.
Skipping this, or relying on an import-order comment inside a file that has other imports,
produces signing failures that look like wallet bugs: the crypto call fails before the wallet
is ever contacted.
## 4. Mount the provider
Wrap the app in `MobileWalletProvider`, above anything that calls `useMobileWallet`. The
props differ between the two stacks:
```tsx
// Kit
import {
type AppIdentity,
createSolanaDevnet,
MobileWalletProvider,
} from '@wallet-ui/react-native-kit'
const identity: AppIdentity = { name: 'My App' }
const cluster = createSolanaDevnet({ url: 'https://api.devnet.solana.com' })
<MobileWalletProvider cluster={cluster} identity={identity}>
{children}
</MobileWalletProvider>
```
Use the `createSolana*` helpers rather than a hand-written object — a `SolanaCluster` also needs
a `label`, and the helpers supply it. `createSolanaMainnet` requires an explicit `url`.
```tsx
// Legacy web3.js
import { MobileWalletProvider } from '@wallet-ui/react-native-web3js'
<MobileWalletProvider
chain="solana:devnet"
endpoint="https://api.devnet.solana.com"
identity={{ name: 'My App', uri: 'myapp://myapp' }}
>
{children}
</MobileWalletProvider>
```
`identity.uri` should be a real deep link for the app — wallets display it to the user during
authorization, so a placeholder both looks wrong and can read as a phishing attempt.
Most apps also want `QueryClientProvider` from `@tanstack/react-query` above the wallet
provider, since the hook examples in the `solana-mobile-wallet` skill use queries and
mutations.
## 5. Set the cluster from configuration
Hard-coding a cluster into a component is how apps ship devnet-only code to production, or
the reverse. Read it from the environment:
```bash
# .env
EXPO_PUBLIC_SOLANA_CLUSTER=devnet
EXPO_PUBLIC_SOLANA_RPC_URL=https://api.devnet.solana.com
```
`EXPO_PUBLIC_`-prefixed variables are inlined into the bundle at build time, so anyone with
the APK can read them. **Never put a paid RPC key or any other secret in one.** Route
authenticated RPC through a backend you control and keep the key there.
## 6. Build
```bash
npx expo prebuild --clean
npx expo run:android
```
`prebuild --clean` regenerates the native projects, which is needed after adding native
modules like `react-native-quick-crypto`. It discards manual edits to `android/` and `ios/`,
so commit those first or reapply them through a config plugin.
## 7. Verify
```bash
npx solana-mobile@latest doctor
```
Then connect a wallet in the app. If connect does nothing, the emulator has no wallet app
installed — see the main skill. For anything else, see
[troubleshooting.md](troubleshooting.md).
Once the app builds and a wallet connects, use the `solana-mobile-wallet` skill for the
connection and transaction code.
references/cli.md›
# solana-mobile CLI reference
The complete command surface of the `solana-mobile` CLI. [SKILL.md](../SKILL.md) covers the
commands used in the normal build loop; this file covers everything, including the flags worth
knowing when a command needs to run unattended.
Run it without installing, and always with `@latest` so the runner does not reuse an older
cached version:
```bash
npx solana-mobile@latest <command>
```
`pnpm dlx solana-mobile@latest` and `bunx solana-mobile@latest` are the equivalents. Match
whichever package manager the project already uses. There is no global install.
| Command | Does |
| --- | --- |
| `create` | Scaffold a new Solana Mobile project from a template |
| `device` | Work with connected devices and emulators over adb |
| `doctor` | Check the local development toolchain |
| `emulator` (`emu`) | Manage Android emulators and system images |
| `localnet` | Run a local validator and forward its ports to devices |
| `playground` | Serve a wallet testing page and open it on a device |
| `templates` | Maintain a template repository (template authors only) |
| `webshell` | Wrap a web app in an Android WebView shell |
Every command accepts `--skip-version-check`, which suppresses the update check. Worth passing
in CI and scripted runs so an unreachable registry cannot add noise to the output.
## create
```bash
npx solana-mobile@latest create my-app --template expo-kit-wallet
```
Interactive without arguments. See [SKILL.md](../SKILL.md#choosing-a-template) for which
template to pick.
| Option | Effect |
| --- | --- |
| `-d, --dry-run` | Show what would happen, write nothing |
| `--list-template-ids` | Print template ids as a JSON array |
| `--list-templates` | Print the template catalog |
| `--list-versions` | Report local Anchor, AVM, Rust, and Solana versions |
| `--minimal` | Use the minimal template (rejected together with `--template`) |
| `--pm, --package-manager <manager>` | Package manager to use |
| `--skip-git` | Do not initialise a git repository |
| `--skip-init` | Do not run the template's init script |
| `--skip-install` | Do not install dependencies |
| `-t, --template <id>` | Pick a template non-interactively |
| `-v, --verbose` | Verbose output |
Flags the CLI does not recognise are forwarded to the selected template as boolean options, so
`create my-app --minimal --reset-project` passes `--reset-project` through. A `--template` value
starting with `/`, `./`, or `../` is treated as a local template directory rather than a GitHub
shorthand.
## device
Everything here talks to devices over adb, so `adb` must be on `PATH` and the device authorised.
`doctor` verifies both.
Where a command takes a target: no flag uses the only connected device, or prompts when several
are connected; `--device <serial>` names one; `--all` (on `install` and `tune`) applies to every
connected device. `--all` and `--device` are mutually exclusive. `emulator tune` does **not**
follow this rule — see [emulator tune](#emulator-tune).
### device install
```bash
npx solana-mobile@latest device install # pick from the catalog
npx solana-mobile@latest device install fakewallet # install a catalog APK by name
npx solana-mobile@latest device install app.apk ./builds/ # local files and directories
```
**This is how you get a wallet onto an emulator.** `fakewallet` is the Mobile Wallet Adapter
test wallet, and a fresh emulator has no wallet at all, so MWA has nothing to hand off to until
it is installed. Catalog APKs are downloaded from GitHub releases and cached under
`~/.cache/solana-mobile/apks/` (`$XDG_CACHE_HOME` is honoured), so repeat installs skip the
network.
| Option | Effect |
| --- | --- |
| `--all` | Install on every connected device |
| `--device <serial>` | Target a device serial |
| `--downgrade` | Allow version downgrades (`adb install -d`) |
| `--force` | Re-download catalog APKs even when cached |
| `--grant` | Grant all runtime permissions (`adb install -g`) |
| `--list` | Print the catalog and exit |
| `-v, --verbose` | Verbose output |
Installs continue past a failure and report a summary, exiting non-zero if anything failed.
### device list
```bash
npx solana-mobile@latest device list
```
Every adb device with its state and a human-readable name — the AVD name for emulators, the
product model for physical devices. `--json` prints a stable report worth parsing when a script
needs to pick a serial.
### device open
```bash
npx solana-mobile@latest device open http://localhost:3000
npx solana-mobile@latest device open 3000 # bare port means http://localhost:<port>
npx solana-mobile@latest device open myapp://claim # deep links work too
```
Replaces `adb -s <serial> shell am start -a android.intent.action.VIEW -d <url>`. For a
`localhost` URL with an explicit port it runs `adb reverse` first, so a dev server on this
machine is reachable from a USB device. An existing reverse on that port is left alone rather
than clobbered — it may be localnet's.
| Option | Effect |
| --- | --- |
| `--device <serial>` | Target a device serial |
| `--no-forward` | Do not create an adb reverse for localhost URLs |
| `-v, --verbose` | Explain each URL and port forwarding decision |
With no URL it offers the device's existing reverses as suggestions, which are exactly the
localhost ports the device can reach.
### device tune
```bash
npx solana-mobile@latest device tune --all --yes
```
Silences the first-contact noise that trips up automation on a fresh device or AVD: animations,
Chrome's first-run and notification prompts, the lock screen, system notifications, autofill,
touch sounds. **Run this before driving a device with an automation agent** — most of what looks
like a broken flow on a fresh emulator is one of these dialogs sitting on top of the app.
| Option | Effect |
| --- | --- |
| `--all` | Tune every connected device |
| `--device <serial>` | Target a device serial |
| `-y, --yes` | Apply every tweak without prompting |
Without `-y` it shows a picker with every tweak pre-selected, so `Enter` applies the lot. That
prompt blocks an unattended run, so always pass `-y` in a script. Physical devices are accepted
here; a tweak the device refuses — a phone with a screen lock keeps its lock screen — is
reported as skipped rather than failing the run.
The tweaks: `animations-off`, `autofill-off`, `chrome-notifications-off`, `chrome-quiet`,
`keyboard-feedback-off`, `lockscreen-off`, `provisioning-complete`, `screen-awake`,
`stylus-handwriting`, `system-notifications-off`.
## doctor
```bash
npx solana-mobile@latest doctor
```
Checks the operating system, JavaScript runtimes and package managers, Java, the Android SDK
and its tools, and connected devices, then prints a readiness summary per workflow — project
creation, Android build, emulator, physical device — with recommendations for anything missing.
| Option | Effect |
| --- | --- |
| `--json` | Print a stable JSON report |
| `--verbose` | Include resolved paths and diagnostic details |
Exits non-zero when something needed is missing, so it works as a CI gate. Run it first
whenever a build fails for reasons that are not obviously in app code — but see
[troubleshooting.md](troubleshooting.md) for the one case where a green `doctor` still leaves
Gradle unable to find the SDK.
## emulator
`emu` is an alias for `emulator`. Subcommands: `create`, `delete`, `images`, `list`, `start`,
`status`, `stop`, `tune`.
```bash
npx solana-mobile@latest emu list # installed AVDs
npx solana-mobile@latest emu status # running ones, with serials
npx solana-mobile@latest emu create local_phone --device pixel_9 --start --tune
npx solana-mobile@latest emu start my_phone
npx solana-mobile@latest emu stop my_phone
```
`emu status` is the command to reach for when you need an AVD name: `expo run:android --device`
wants the name from its `name` column, not the adb serial.
### emulator create
Creates or updates an AVD, installing a system image if one is missing. Interactive without
arguments.
| Option | Effect |
| --- | --- |
| `--data-size <size>` | Data partition size |
| `--device <profile>` | Android device profile id, e.g. `pixel_9` |
| `--profile <profile>` | Solana Mobile emulator profile |
| `--ram-mb <mb>` | RAM size in MB |
| `--sdcard-size <size>` | SD card size |
| `--sdk-root <path>` | Android SDK root |
| `--start` | Start the emulator after creating it |
| `--system-image <package>` | Android system image package |
| `--tune` | Apply the tweaks after booting (requires `--start`) |
| `-v, --verbose` | Verbose output |
| `--vm-heap-mb <mb>` | VM heap size in MB |
### emulator delete
```bash
npx solana-mobile@latest emu delete old_phone
```
Takes any number of names and accepts `--sdk-root`. Idempotent: deleting an AVD that is not
installed reports it and exits 0, so "remove any leftover, then create" needs no probe first.
### emulator images
`install`, `list`, and `delete` for Android system images. `install` takes `--all` to show every
available image rather than the recommended shortlist; `install` and `delete` take `-v`; all
three take `--sdk-root`.
### emulator start
Starts an AVD, prompting for one when no name is given. `--sdk-root` and `--tune`.
### emulator tune
```bash
npx solana-mobile@latest emu tune my_phone -y
```
The same tweaks as [`device tune`](#device-tune), addressed by AVD name or serial. **Tuning is
opt-in.** `emu start` and `emu create --start` apply the tweaks only when passed `--tune`, and
that form is non-interactive.
**Give this one a target in a script.** Unlike `device tune`, it has no single-candidate
shortcut: with no positional argument it opens an emulator picker whenever any emulator is
running, one or many, and `-y` does not suppress it — that flag skips the tweak multiselect
only. With no emulator running it prints a hint and exits without tuning. A name that matches
no running emulator is an error, and one that matches more than one asks for a serial.
Physical device serials are refused here — the target must report an emulator property before
any tuning command runs. Use `device tune` for a real phone.
## localnet
```bash
npx solana-mobile@latest localnet start
```
Gets a local validator serving and forwards its ports to every connected device, so an app on
the device reaches `localhost:8899` as if the validator ran there. Subcommands: `check`,
`forward`, `logs`, `start`, `status`, `stop`. Bare `localnet` is `localnet start`.
`start` picks one of three paths rather than always running `docker run`. A localnet container
it started already is reused. Otherwise it probes the RPC port first: something already
answering there — a native validator, or one started by hand — is attached to, which needs no
Docker at all and avoids failing on the port bind. Only when neither holds does it start a
container, and that path is the one that requires Docker. A stopped container of its own it
removes first; an unlabelled namesake it refuses to touch.
`start`, `check`, `forward`, `status`, and `stop` share the target options, and accept them on
either side of the subcommand:
| Option | Effect |
| --- | --- |
| `--device <serial>` | Target a device serial (repeatable) |
| `--engine <engine>` | Validator engine: `surfpool` or `test-validator` |
| `--port <port>` | Host port for the RPC endpoint |
| `--studio-port <port>` | Host port for the Studio UI |
| `--ws-port <port>` | Host port for the WebSocket endpoint |
Per subcommand:
| Subcommand | Options |
| --- | --- |
| `check` | `--json`, `--open` (also open the Studio UI on the device) |
| `forward` | `--watch` |
| `logs` | `--lines <count>` |
| `start` | `--detach`, `--image <image>`, `--no-watch` |
| `status` | `--json` |
`localnet check` is the command that answers "why can the app not reach my validator" — it
verifies reachability from every device rather than just from this machine. Plug in a device
after the validator is already running and its ports are not forwarded yet; `localnet forward`
fixes that, and `start` keeps watching for device changes unless `--no-watch` is passed.
## playground
```bash
npx solana-mobile@latest playground
npx solana-mobile@latest playground --cluster localnet
```
Serves a bundled wallet testing page, forwards it to a device, opens it in the device browser,
and streams every MWA interaction back to the terminal until interrupted. It covers connect,
sign in (SIWS), sign message, sign transaction, and sign and send.
**Reach for this before scaffolding an app to test a wallet.** It is the fastest way to
establish whether a signing failure is in the app or in the wallet and device setup — if the
playground cannot sign either, the app is not the problem. It needs an MWA wallet on the device;
`device install fakewallet` puts one there.
| Option | Effect |
| --- | --- |
| `--cluster <cluster>` | `devnet` (default), `localnet`, `mainnet`, or `testnet` |
| `--device <serial>` | Target a device serial |
| `--no-open` | Print the URL instead of opening it on the device |
| `--port <port>` | Host port for the playground server |
| `--url <url>` | Custom RPC URL for the selected cluster |
| `-v, --verbose` | Verbose output |
Mainnet always needs `--url`: the page runs in the device browser, and Solana's public mainnet
endpoint answers browser-origin requests with `403`. With `--cluster localnet`, start `localnet`
first. The device always opens port 4747; `--port` moves the host side of the reverse.
## templates
Maintains a template repository such as [solana-mobile/templates](https://github.com/solana-mobile/templates),
the source of the templates `create` uses. Only relevant when authoring templates, not when
building an app.
| Subcommand | Does |
| --- | --- |
| `check` | Verify generated template artifacts are up to date |
| `generate` | Generate template artifacts |
| `sync <target>` | Sync git-tracked templates to another repository |
All three take `--root <path>`. `sync` also takes `--dry-run` and `--force`, the latter syncing
even when the target has uncommitted changes, which may overwrite or remove files there.
## webshell
Generates and builds an Android WebView wrapper around an existing web app or PWA. This is a
packaging path for a web app, not a React Native app — the wrapper ships a WebView, not the RN
runtime, so nothing in these skills about Expo builds applies to it.
The generated shell does handle Mobile Wallet Adapter: it forwards `solana-wallet:` navigations
to the installed wallet as a `VIEW` intent, and dispatches a synthetic `blur` event afterwards
because the MWA web library detects the wallet handoff through `window.blur`, which a WebView
never fires on its own. It leaves subframe navigation alone so cross-origin iframe SDKs such as
Privy keep working.
```bash
npx solana-mobile@latest webshell init ./my-shell --url https://example.com
npx solana-mobile@latest webshell build ./my-shell
```
`init` takes `--app-name`, `--application-id`, `--force`, `--keystore-alias`, `--keystore-path`,
`--manifest <path-or-url>` (a web `manifest.json` or a Bubblewrap `twa-manifest.json`), `--url`,
`--version-code`, and `--version-name`. `build` takes `--keystore-alias`, `--keystore-path`, and
`--stacktrace`.
references/troubleshooting.md›
# Build and environment troubleshooting
Project, build, and emulator failures. For wallet connection and signing failures, see the
`solana-mobile-wallet` skill's troubleshooting reference.
Start here:
```bash
npx solana-mobile@latest doctor --verbose
```
It checks the local toolchain and recommends fixes. Most failures in this file are caught by
it, so run it before reading further.
## Wallet features do nothing, no errors
The app is running in Expo Go. MWA needs native Android modules that Expo Go does not bundle,
and there is no workaround.
```bash
npx expo run:android
```
This is the single most common cause of "the wallet integration does not work" reports.
## `expo prebuild` wiped native changes
`prebuild --clean` regenerates `android/` and `ios/` from configuration, discarding manual
edits. Recover them from git, then move the changes into a config plugin or the `app.json`
`plugins` array so they survive the next regeneration.
## Gradle build fails after adding a native module
Native modules need a real rebuild, not a Metro restart:
```bash
npx expo prebuild --clean
npx expo run:android
```
If it still fails, clear the Gradle cache:
```bash
cd android && ./gradlew clean && cd .. && npx expo run:android
```
## `SDK location not found` / `ANDROID_HOME` unset
```
> SDK location not found. Define a valid SDK location with an ANDROID_HOME environment
variable or by setting the sdk.dir path in your project's local properties file
```
**`doctor` can report "Android build: ready" and this can still happen.** `doctor` finds the
SDK at its default location, which is enough for its own checks, but Gradle only reads
`ANDROID_HOME` or `android/local.properties`. A green `doctor` does not prove the build
environment is exported.
Export it in your shell profile:
```bash
export ANDROID_HOME="$HOME/Library/Android/sdk"
export PATH="$PATH:$ANDROID_HOME/platform-tools:$ANDROID_HOME/emulator"
```
Restart the shell before rebuilding — an inherited environment is a common reason a fix
appears not to work. Per-project alternative, which does not depend on the shell at all:
```bash
echo "sdk.dir=$HOME/Library/Android/sdk" > android/local.properties
```
`local.properties` is machine-specific and belongs in `.gitignore`.
## `Could not find device with name: emulator-5554`
`expo run:android --device` expects the **AVD name**, not the adb serial:
```bash
npx solana-mobile@latest emu status # `name` column, not `serial`
npx expo run:android --device test-skills
```
Passing `emulator-5554` fails even though `adb devices` lists it. This bites as soon as more
than one emulator is running and you start naming a target explicitly.
## No emulator to run on
```bash
npx solana-mobile@latest emu status
npx solana-mobile@latest emu create
npx solana-mobile@latest emu start
```
`emu create` installs a system image if one is missing. To pick a device profile:
```bash
npx solana-mobile@latest emu create local_phone --device pixel_9
```
## Emulator runs but connect does nothing
A fresh emulator has no wallet app, so MWA has nothing to associate with. Install the Mobile
Wallet Adapter test wallet:
```bash
npx solana-mobile@latest device install fakewallet
```
Then confirm the wallet, not the app, by driving the same flow without app code:
```bash
npx solana-mobile@latest playground
```
If the playground cannot connect either, the problem is the wallet or the device setup rather
than the app. A physical Android device with a real wallet installed works too.
Anything gated on the Seeker Genesis Token needs a real Seeker device — an emulator cannot
hold one. See the `seeker-genesis-token` skill.
## Dialogs and animations keep interrupting an emulator run
A fresh AVD greets you with stylus handwriting onboarding, Chrome's sign-in and notification
prompts, the lock screen, and Play Store notifications, any of which lands on top of the app
and looks like a broken flow:
```bash
npx solana-mobile@latest emu tune -y
```
Tuning is opt-in. `emu start` and `emu create --start` apply it only when passed `--tune`, and
`-y` skips the tweak picker, which would otherwise block an unattended run. Use `device tune`
instead for a physical phone — `emu tune` refuses non-emulator serials.
## The app cannot reach a server running on this machine
An emulator or USB device has its own `localhost`, so a dev server or validator on this machine
is not reachable until a port is forwarded. Both commands do it for you:
```bash
npx solana-mobile@latest device open http://localhost:3000
npx solana-mobile@latest localnet start
```
`device open` creates the `adb reverse` before opening the URL. `localnet start` runs a
validator and forwards its ports to every connected device; `localnet check` verifies the
device can actually reach it, and `localnet forward` re-applies the forwards after plugging in
a device that joined later.
## `adb` cannot see a physical device
1. Enable Developer Options and USB debugging on the device.
2. Accept the debugging prompt when it appears.
3. Confirm with `adb devices` — the device should be `device`, not `unauthorized`.
If it stays `unauthorized`, revoke USB debugging authorizations on the device and reconnect.
## Metro resolves the wrong build of a package
Symptoms include the "secure context (`https`)" error from the MWA protocol package, or
Node-only APIs surfacing at runtime. Metro picked a browser or ESM entry point over the React
Native one.
Fix the resolution order in `metro.config.js` rather than deleting files from
`node_modules`, which the next install undoes. Compare against a scaffolded template's
`metro.config.js`:
```bash
npx solana-mobile@latest create /tmp/reference-app --template expo-kit-wallet --skip-install
```
## Warnings that are safe to ignore
These appear on a healthy build of a freshly scaffolded project. Do not spend time on them:
```
WARN Attempted to import the module ".../@noble/hashes/crypto.js" which is not listed in the
"exports" of ".../@noble/hashes" under the requested subpath "./crypto.js". Falling back to
file-based resolution.
```
Metro resolving a subpath `@noble/hashes` does not formally export, reached transitively
through `@solana/kit`. It falls back successfully and the bundle works.
```
Deprecated Gradle features were used in this build, making it incompatible with Gradle 10.
› Using [email protected] instead of recommended [email protected].
```
Both are informational. The template pins its own React Native version deliberately.
## Dependency versions drifted after an SDK upgrade
```bash
npx expo install --check
npx expo-doctor@latest
```
`expo install --check` reports dependencies whose versions do not match the installed Expo
SDK. Prefer `npx expo install <pkg>` over `npm install <pkg>` for anything Expo manages, so
versions stay aligned.
SKILL.md›
---
name: solana-mobile
description: Scaffold, configure, and troubleshoot Solana Mobile apps for Android using the solana-mobile CLI, Expo, and React Native. Use when creating a new Solana mobile app, picking a template, adding Solana to an existing Expo app, checking the Android toolchain, managing Android emulators or connected devices, installing a wallet APK for testing, running a local validator the device can reach, or producing a development build.
---
# Solana Mobile projects
Set up and maintain Solana Mobile apps. The `solana-mobile` CLI does the scaffolding,
environment checks, and emulator management — prefer it over hand-rolled setup.
Run it without installing:
```bash
npx solana-mobile@latest --help
```
`pnpm dlx solana-mobile@latest` and `bunx solana-mobile@latest` are the equivalents. Match
whichever package manager the project already uses, and keep `@latest` so the runner does not
reuse an older cached version.
| Command | Use for |
| --- | --- |
| `create` | [Scaffolding a new project](#create-a-new-project) |
| `device` | [Installing APKs, opening URLs, preparing a device](#work-with-a-connected-device) |
| `doctor` | [Checking the toolchain](#check-the-environment) |
| `emulator` (`emu`) | [Creating and running emulators](#manage-android-emulators) |
| `localnet` | [A local validator the device can reach](#run-a-local-validator) |
| `playground` | [Testing a wallet without building an app](#test-a-wallet-without-an-app) |
| `templates` | Maintaining a template repository — template authors only |
| `webshell` | Wrapping an existing web app in an Android WebView shell |
Every flag of every command: [references/cli.md](references/cli.md). Do not guess at flags —
the CLI ships its own help, and `--help` on any command is authoritative.
## Non-negotiable constraint
**Mobile Wallet Adapter requires a development build. Expo Go will not work** — MWA depends
on native Android modules that Expo Go does not bundle. If someone reports wallet
connection failing in Expo Go, that is the cause; there is no workaround, they need
`expo run:android`.
Android is the only platform with wallet support. iOS builds run, but no MWA.
## First: work out which situation you are in
| Situation | Do this |
| --- | --- |
| No project yet | [Create a new project](#create-a-new-project) |
| Existing Expo app, no Solana | Read [references/add-to-existing-app.md](references/add-to-existing-app.md) |
| Project exists, build or toolchain broken | [Check the environment](#check-the-environment) |
| Project exists, needs wallet features | Use the `solana-mobile-wallet` skill |
| Wallet connect or signing needs testing | [Test a wallet without an app](#test-a-wallet-without-an-app) |
## Create a new project
```bash
npx solana-mobile@latest create
```
Interactive by default. To skip the prompts, name the project and template:
```bash
npx solana-mobile@latest create my-app --template expo-kit-wallet
```
Useful flags:
| Flag | Effect |
| --- | --- |
| `-t, --template <id>` | Pick a template non-interactively |
| `--pm <manager>` | Package manager to use |
| `--minimal` | Use the minimal template |
| `--list-templates` | Print the template catalog |
| `--list-template-ids` | Print template ids as a JSON array |
| `--skip-install` | Do not install dependencies |
| `--skip-git` | Do not initialise a git repo |
| `-d, --dry-run` | Show what would happen, write nothing |
### Choosing a template
Templates come in two families. **Pick an `expo-kit-*` template.** `@solana/kit` is the current
Solana client library, and these are the templates the CLI maintains most actively.
Reach for `expo-web3js-*` only when the user is deliberately continuing an existing
`@solana/web3.js` codebase, or asks for it by name. If they ask without a reason, say kit is the
better starting point before going along with it — a new app on web3.js starts life needing a
migration.
`expo-kit-minimal` is the clearest reference for how the kit pieces fit together, and worth
reading even when building on a different template.
| Template | Stack | Use for |
| --- | --- | --- |
| `expo-kit-wallet` | Kit + MWA + Uniwind | **Best default.** Wallet connect, sign, send already wired |
| `expo-kit-minimal` | Kit | Bare starting point, no UI kit |
| `expo-kit-uniwind` | Kit + Uniwind | Tailwind-style styling, no wallet yet |
| `expo-kit-privy` | Kit + Privy + Uniwind | Privy auth instead of, or alongside, MWA |
| `expo-web3js-wallet` | web3.js + MWA | Legacy wallet app |
| `expo-web3js-paper` | web3.js + RN Paper | Legacy, Material UI |
| `expo-web3js-minimal` | web3.js | Legacy bare starting point |
Template ids are also accepted in full `gh:solana-mobile/templates/mobile/<name>` form.
Re-run `--list-templates` rather than trusting this table if a template seems missing — the
catalog ships with the CLI, not with this skill.
### After scaffolding
```bash
cd my-app && npm run android
```
That runs `expo run:android`, which builds and installs the development build. The first
Android build is slow (Gradle cold start); later builds reuse the cache.
## Check the environment
Before debugging a build failure, check the toolchain:
```bash
npx solana-mobile@latest doctor
```
It reports on the local Android and Node toolchain with recommendations for anything
missing. `--json` gives a stable report worth parsing when you need to branch on a specific
check; `--verbose` adds resolved paths and diagnostics.
Run `doctor` first whenever a build fails for reasons that are not obviously in app code.
## Manage Android emulators
```bash
npx solana-mobile@latest emu list
npx solana-mobile@latest emu status
npx solana-mobile@latest emu create
npx solana-mobile@latest emu start my_phone
npx solana-mobile@latest emu stop my_phone
```
`emu` is an alias for `emulator`. Subcommands: `create`, `delete`, `images`, `list`, `start`,
`status`, `stop`, `tune`. System images live under `emu images` (`install`, `list`, `delete`).
Create a named emulator on a specific device profile:
```bash
npx solana-mobile@latest emu create local_phone --device pixel_9
```
### Prepare a fresh emulator
A newly created AVD is not ready for wallet work: it is not running, its first-run dialogs are
still armed, and it has no wallet app. Boot it tuned, then install one — in that order, since
`device install` needs a booted device to install onto:
```bash
npx solana-mobile@latest emu start local_phone --tune
npx solana-mobile@latest device install fakewallet
```
`--tune` disables the animations, first-run dialogs, lock screen, and notifications that
otherwise sit on top of the app on a fresh AVD. Tuning is opt-in — `emu start` and
`emu create --start` apply it only when passed `--tune` — and in that form it is
non-interactive, which is what makes it the one to reach for in a script. `emu create` takes
`--start --tune` to do all of this at creation time.
`device install fakewallet` puts the Mobile Wallet Adapter test wallet on the running emulator.
Without a wallet app, MWA has nothing to hand off to and connect silently does nothing — the
most common cause of "connect does nothing on the emulator".
Anything gated on the Seeker Genesis Token still needs a real Seeker device; an emulator cannot
hold one. See the `seeker-genesis-token` skill.
## Work with a connected device
`device` covers everything that goes over adb, for emulators and physical phones alike.
```bash
npx solana-mobile@latest device list # serials, states, names
npx solana-mobile@latest device install fakewallet # catalog APK
npx solana-mobile@latest device install ./app-release.apk # local file
npx solana-mobile@latest device open http://localhost:3000 # opens on the device
npx solana-mobile@latest device tune --all -y # prepare for automation
```
`device open` creates the `adb reverse` for a localhost URL itself, so a dev server on this
machine is reachable from a USB-connected phone. Prefer it to hand-written `adb reverse` plus
`am start` incantations.
`device tune` and `emu tune` apply the same tweaks but differ in two ways, and the second one
bites in scripts. `device tune` accepts physical devices, takes `--device <serial>` or `--all`,
and falls back to the only connected device. `emu tune` refuses non-emulator serials, takes the
AVD name or serial as a positional argument, and opens a picker whenever it is given none — even
with exactly one emulator running, and even under `-y`, which skips the tweak prompt and not the
emulator one. Name the target in anything unattended: `emu tune local_phone -y`.
## Run a local validator
```bash
npx solana-mobile@latest localnet start
npx solana-mobile@latest localnet check
```
`localnet start` gets a validator serving on the host and forwards its ports to every connected
device, so the app reaches `localhost:8899` as if the validator ran on the device. It reuses a
localnet container it already started, attaches to a validator already answering on those ports
— a native one, needing no Docker at all — and starts a container only when neither is there.
`check`
verifies reachability from each device — which is the question to ask when an app cannot see a
validator that is plainly running on this machine.
`localnet forward` re-applies the forwards after plugging in a new device, and `logs`, `status`,
and `stop` do what they say.
## Test a wallet without an app
```bash
npx solana-mobile@latest playground
```
Serves a wallet testing page, opens it on the device, and streams every MWA interaction back to
the terminal: connect, sign in (SIWS), sign message, sign transaction, sign and send. It runs
against devnet by default; `--cluster localnet` points it at the `localnet` validator, and
mainnet needs your own `--url` because the public endpoint rejects browser-origin requests.
**Use this to split an app bug from a setup bug.** If the playground cannot sign either, the
problem is the wallet or the device, not the code being debugged. It needs an MWA wallet
installed — `device install fakewallet` if there is none.
## Reference material
- [references/cli.md](references/cli.md) — every `solana-mobile` command and flag, including
`templates` and `webshell`
- [references/add-to-existing-app.md](references/add-to-existing-app.md) — wiring Solana
into an Expo app that already exists: crypto polyfill, providers, dependencies
- [references/troubleshooting.md](references/troubleshooting.md) — build, polyfill, and
emulator failures with known causes
## Related skills
- `solana-mobile-wallet` — connecting wallets, signing, sending transactions
- `solana-mobile-publishing` — signing a release APK and shipping to the dApp Store
- `seeker-genesis-token` — verifying Seeker device ownership
- `seeker-domains` — `.skr` domain name resolution
For general non-mobile Solana work — Anchor or Pinocchio programs, Codama client generation,
testing, security review — use the Solana Foundation's `solana-dev` skill instead. `create`
installs it into `.agents/skills/solana-dev/`, so a scaffolded project already has it.
## Links
- CLI source: https://github.com/solana-mobile/solana-mobile-cli
- Templates: https://github.com/solana-mobile/templates
- Solana Mobile docs: https://docs.solanamobile.com