solana-mobile-wallet について
Mobile Wallet Adapter と Wallet UI React Native Kit を使い、ウォレット接続、チェーンデータの取得、トランザクション署名を行う Solana モバイルアプリ向けスキルです。Kit と旧 web3.js の選択や、よくある実装上の問題も扱います。
主な機能
- 01モバイルウォレットの接続と切断
- 02Solana のチェーンデータ取得
- 03トランザクションの署名と送信
- 04クラスター設定とクエリキャッシュ
- 05接続や署名のトラブルシューティング
活用シーン
- 01React Native アプリへのウォレット接続追加
- 02残高取得とチェーンデータの表示
- 03Solana トランザクションの作成と送信
インストール
コマンドをコピーしてターミナルで実行します。
npx skills add https:/ / github.com/ solana-mobile/ solana-mobile-skills --skill solana-mobile-walletインストール後、Claude Code で/solana-mobile-walletを入力して読み込まれたか確認します。
同梱ファイル
SKILL.md と一緒にインストールされる参考資料やスクリプト。
references/kit.mdtroubleshooting.mdweb3js.md詳細
モバイルの Solana ウォレット
Mobile Wallet Adapter(MWA)を通じたウォレット接続とトランザクション署名を、@wallet-ui/react-native-kit(レガシースタックでは @wallet-ui/react-native-web3js)でラップします。
MWA を Android で利用するには開発ビルドが必要です。Expo Go は動作しません。 まだ開発ビルドがない場合、またはプロジェクトが存在しない場合は、solana-mobile スキルから始めてください。
Step 1: スタックを選ぶ — コードを書く前に選んでください
Kit のコードを書いてください。 @solana/kit と @wallet-ui/react-native-kit が選ぶべきスタックで、このファイルの内容はすべてこのスタックについて説明しています。
@solana/web3.js を使う唯一の理由は、プロジェクトがすでにそれを使っていることです。まず package.json を確認してください。
package.json の記載 | 対応 |
|---|---|
@wallet-ui/react-native-kit、または Solana クライアントがまだない | Kit。このファイルと references/kit.md |
@wallet-ui/react-native-web3js がアプリの Solana クライアント | references/web3js.md |
| ユーザーが web3.js を明示的に希望している | references/web3js.md を参照し、Kit のほうが適している理由も伝えてください |
Kit のプロジェクトに web3.js を導入したり、1 つのアプリで両方を混在させたりしないでください。プロバイダーの props、フックの戻り値、トランザクションの構築方法がそれぞれ異なるため、一方のコードをもう一方で使うと、気づかないうちに失敗します。プロジェクトに Solana クライアントがまったくない場合は、新規構築です。Kit を使ってください。
既存の web3.js アプリに新しいウォレット機能を追加する場合、作業の途中で移行する必要はありません。既存の構成に合わせ、移行に価値がありそうであれば、今後の対応として提案してください。
Step 2: プロバイダーがマウントされていることを確認する
useMobileWallet は、その上位に MobileWalletProvider がないと空の状態を返します。ルートレイアウトまたはアプリプロバイダーモジュールで確認してください。
SolanaCluster には label も必要です。ヘルパーがこれを設定するため、手動ではなく createSolana* ヘルパーを使ってクラスターを構築してください。
import {
type AppIdentity,
createSolanaDevnet,
MobileWalletProvider,
type SolanaCluster,
} from '@wallet-ui/react-native-kit'
const identity: AppIdentity = { name: 'My App' }
const cluster: SolanaCluster = createSolanaDevnet({ url: 'https://api.devnet.solana.com' })
<MobileWalletProvider cluster={cluster} identity={identity}>
{children}
</MobileWalletProvider>
createSolanaDevnet、createSolanaTestnet、createSolanaLocalnet は省略可能な props を受け取ります。createSolanaMainnet には url が必要です。mainnet には適切なデフォルトのパブリック URL がないためです。
プロバイダーの props は cluster、identity と、省略可能な cache、createClient、children です。chain prop も endpoint prop もありません。これらを渡しても何も起きません。
すべての AppIdentity フィールドは省略可能です。始めるには name だけで十分です。リリースするものには、実際のディープリンクとなる uri を追加してください。ウォレットの認証時に表示されるためです。プレースホルダーを指定すると、フィッシングの試みと受け取られる可能性があります。
ウォレットプロバイダーの上位に、@tanstack/react-query の QueryClientProvider を配置してください。以下のフックのパターンでは、クエリとミューテーションを使用します。
Step 3: フックを使う
import { useMobileWallet } from '@wallet-ui/react-native-kit'
const { account, connect, disconnect, client } = useMobileWallet()
kit スタックでフックが実際に返す値は次のとおりです。
| 値 | 型 | 備考 |
|---|---|---|
account | Account | undefined | 未接続時は null ではなく undefined です |
accounts | Account[] | null | 認証済みのすべてのアカウントです |
connect | () => Promise<Account> | ウォレット選択画面を開きます |
disconnect | () => Promise<void> | |
client | Client | Kit クライアントです。RPC 呼び出しには client.rpc を使います |
chain | SolanaClusterId | アクティブなクラスターです。クエリーキーに含めてください |
sendTransactions | (instructions: Instruction[]) => Promise<string> | 最も簡単な送信方法です |
signAndSendTransaction | (tx, minContextSlot) => Promise<SignatureBytes> | 2 番目の引数に注意してください |
signTransaction | (tx) => Promise<Transaction> | ブロードキャストせずに署名します |
signMessages | (msg: Uint8Array) => Promise<Uint8Array> | |
signIn | (payload) => Promise<SignInOutput> | Solana でサインインします。接続もできます |
identity、store | 設定と認証ストアです |
これらのいくつかには単数形のエイリアス(sendTransaction、signMessage、
signTransactions)もあり、シグネチャは同じです。テンプレートでは複数形を使っていますが、
どちらでも動作するため、プロジェクトですでに使われている形式に従ってください。
つまずきやすい点は 4 つあります。
connectedというブール値はありません。const connected = !!accountのように導出します。signAndSendTransactionの 2 番目の引数はminContextSlotです。 トランザクションだけを渡して呼び出すと失敗します。getLatestBlockhashからスロットを取得するか、処理を代行してくれるsendTransactions(instructions)を使ってください。- kit フックが公開するのは
connectionではなくclientです。connectionがあるのは web3.js スタックだけです。 - チェーンデータを保持するすべての React Query キーに
chainを含めてください。 そうしないと、クラスターの切り替え後も前のネットワークの残高がキャッシュから返され、ウォレットの不具合のように見えてしまいます。
account.address は kit の Address(ブランド付き文字列)なので、そのままテキストに埋め込めます。
account.label はウォレットから提供される名前で、未定義の場合があります。
接続と切断
import { Pressable, Text } from 'react-native'
import { useMobileWallet } from '@wallet-ui/react-native-kit'
export function ConnectButton() {
const { account, connect, disconnect } = useMobileWallet()
async function onPress() {
try {
if (account) await disconnect()
else await connect()
} catch (error) {
// The user dismissing the wallet picker lands here. Do not treat it as a crash.
console.error(error)
}
}
return (
<Pressable onPress={onPress}>
<Text>{account ? 'Disconnect' : 'Connect Wallet'}</Text>
</Pressable>
)
}
connect() は必ず try/catch で囲んでください。ウォレット選択画面をキャンセルすると、プロミスが拒否されるためです。
キャンセルは通常の結果であり、ユーザーに警告するようなエラー状態ではありません。キャンセルと実際の失敗を区別する方法は
references/kit.md を参照してください。
認証情報はキャッシュされるため、アプリを再起動しても新しいプロンプトなしで再接続されます。
チェーンデータの読み取り
フックの client を使います。自分でクライアントを構築する必要はありません。
import type { Address } from '@solana/kit'
import { useQuery } from '@tanstack/react-query'
import { useMobileWallet } from '@wallet-ui/react-native-kit'
export function useGetBalance({ address }: { address: Address }) {
const { chain, client } = useMobileWallet()
return useQuery({
queryFn: () => client.rpc.getBalance(address).send(),
queryKey: ['get-balance', chain, address],
})
}
Kit の RPC 呼び出しは遅延実行です。client.rpc.someMethod(...) でリクエストを作成し、.send() で実行します。
.send() を忘れてもエラーは発生せず、データが届かないだけです。
残高は bigint の lamport 値として返されます。意図的に変換し、parseFloat は決して使わないでください。
export function lamportsToSol(lamports: bigint) {
return Number(lamports) / 1e9
}
トランザクションの送信
インストラクションを構築して渡します。ほとんどの場合はこれで対応できます。
import { getAddMemoInstruction } from '@solana-program/memo'
import type { Instruction } from '@solana/kit'
const { sendTransactions } = useMobileWallet()
const instructions: Instruction[] = [getAddMemoInstruction({ memo: 'gm' })]
const signature = await sendTransactions(instructions)
sendTransactions はブロックハッシュ、minContextSlot、手数料支払いアカウント、署名のデコードを処理します。ウォレットが送信した時点で戻るため、トランザクションが完了したとみなす前に署名を確認してください。そのためのヘルパーは
references/kit.md にあります。
手数料支払いアカウントの制御、特定のブロックハッシュの有効期間、または手数料の事前確認が必要な場合に限り、明示的な pipe 形式を使ってください。残高と手数料の比較アサーションおよび署名のデコードを含む、実用的な完全例は
references/kit.md を参照してください。
参考資料
- references/kit.md — kit スタック:クラスターと設定、チェーンデータの読み取り、 トランザクション、Solana でのサインイン、メッセージ署名、エラー処理
- references/web3js.md — 旧
@solana/web3.jsスタックと移行の概要 - references/troubleshooting.md — 原因が判明している接続と署名の失敗
ここで不明な点がある場合は、テンプレートを確認してください。このスキルのパターンは
expo-kit-minimal に沿っています。これは完全に動作するアプリであり、文章による説明とは違って最新の状態が保たれています。
npx solana-mobile@latest create /tmp/reference-app --template expo-kit-minimal --skip-install
ウォレットとアプリのどちらに問題があるかを確認する
接続や署名のコードをデバッグする前に、アプリを介さずに同じフローを実行してください。
npx solana-mobile@latest device install fakewallet # if the device has no wallet
npx solana-mobile@latest playground
playground はデバイス上でウォレットのテストページを表示し、各 MWA インタラクション(接続、サインイン、メッセージへの署名、トランザクションへの署名、署名して送信)をターミナルにストリーミングします。そこでの実行にも失敗する場合、問題はウォレットまたはデバイスにあり、このコードにはありません。solana-mobile スキルでは、両方のコマンドについて説明しています。
関連スキル
solana-mobile— プロジェクトのセットアップ、テンプレート、エミュレーター、開発ビルドintegration-privy— このウォレット接続に Privy アカウントとセッションを追加するseeker-genesis-token— 接続後に Seeker デバイスの所有権を検証するseeker-domains— 生のアドレスの代わりに.skr名を表示する
リンク
- ウォレット UI:https://wallet-ui.dev
- MWA ドキュメント:https://docs.solanamobile.com/react-native/overview
- Solana Kit:https://www.solanakit.com