mcpField
S
solana-mobile-publishing

solana-mobile-publishing

github.com/solana-mobileクラウド・DevOps87.7k 回インストール1 週間前に更新
リポジトリ

solana-mobile-publishing について

Solana dApp Store に Android アプリを公開するためのガイドと CLI ワークフローです。公開者の設定、署名付き APK のビルド、リリース、中断した公開セッションの再開を扱います。

主な機能

  1. 01公開者と App NFT の設定
  2. 02署名付き APK のビルド手順
  3. 03CLI によるバージョン公開
  4. 04対象となる失敗セッションの再開
  5. 05API キーとポータルの設定

活用シーン

  1. 01公開者とアプリ掲載情報の登録
  2. 02Android アプリの初回リリース
  3. 03既存の署名キーを使った更新公開
  4. 04中断した公開セッションの続行

インストール

コマンドをコピーしてターミナルで実行します。

$npx skills add https://github.com/solana-mobile/solana-mobile-skills --skill solana-mobile-publishing

インストール後、Claude Code で/solana-mobile-publishingを入力して読み込まれたか確認します。

同梱ファイルreferences/signing.md

詳細

Solana dApp Store への公開

dApp Store へのリリースでは、オンチェーンの App NFT をミントする一度限りのセットアップと、ビルド、署名、公開を行うリリースごとのループに分かれます。一度限りのセットアップには取り消せない決定が含まれるため、最初のリリースの後ではなく、その前に正しく済ませてください。

npm install -g @solana-mobile/dapp-store-cli@latest

Node 18 以降が必要です。グローバルインストールを避けたい場合は、npx @solana-mobile/dapp-store-cli も使えます。

1.0.1 以降を使用してください。 それより前のバージョンは非推奨であり、動作しません。

絶対に守るべき制約

APK のみです。.aab は受け付けられません。 bundleRelease ではなく、assembleRelease でビルドしてください。これは、バンドルがデフォルトである Google Play の操作に慣れている人が間違えないようにするための注意です。

キーストアとパブリッシャーウォレットは、どちらも永久に使うものです。 更新版には、直前のリリースと同じ鍵で署名する必要があり、App NFT はパブリッシャーウォレットに保管されます。どちらかを失うと復旧する方法はなく、サポートに連絡しても解決できません。新しい ID で最初からアプリを再登録するしかありません。どちらもリポジトリに置いてはいけません。キーストアを生成する前に保存場所を確認し、プロジェクトツリーの外で保管してください。

Google Play にもリリースする場合は、別の署名鍵を使用してください。 ストアごとに鍵を分けてください。1 つの鍵を共有すると、漏えいや紛失によって両方の掲載が一度に影響を受けます。

まず、自分の状況を確認する

状況対応
パブリッシャーアカウントをまだ持っていない一度限りのセットアップ
パブリッシャーアカウントがあり、リリースを公開する署名済み APK のビルド、続いて公開
公開中のアプリを更新する更新版のリリース
リリースビルドがクラッシュするが、デバッグビルドは問題ないreferences/signing.mdを読む
公開処理が途中で失敗した失敗した公開を再開する
React Native ではなく、Web アプリをラップしたアプリであるWeb アプリ

一度限りのセットアップ

最初にパブリッシャーのキーペアを用意する

CLI はキーペアのファイルを使ってパブリッシャーとして署名します。ブラウザポータルでウォレットに接続しても、キーペアファイルは作成されません。何かを登録する前に、まずそのファイルを用意してください。ポータルに接続するウォレットは同じ鍵でなければならないため、順序を逆にすると、ブラウザウォレットの秘密鍵をディスクにエクスポートすることになります。これはあらゆる点で好ましくありません。

この用途のキーペアがすでにある場合は、そのパスを使用してください。~/.config/solana/id.json に何が入っているか確認せずに使うのではなく、どのキーペアを使うべきか確認してください。そこにあるのは通常、一時的な開発用鍵です。誤った鍵で署名すると、ポータルにパブリッシャーとして認識されません。

まだキーペアがない場合は、エージェントではなく、開発者が生成してください。

solana-keygen new --silent --outfile ~/.config/solana/publisher.json

--silent を指定すると、シードフレーズの出力が抑制されます。指定しない場合、12 個の単語が標準出力に出力されます。エージェントの標準出力は会話の記録やログファイルに残るため、誰も管理していない場所にウォレット全体が平文で保存されてしまいます。出力が記録されないターミナルでのみ --silent を外し、いずれの場合もキーペアファイルをバックアップしてください。フレーズの出力を抑制した場合、そのファイルが唯一のコピーになります。エージェントにはパスだけを渡してください。

このファイルはリリース用キーストアと同じように扱ってください。管理する App NFT と同様に永久に使うものなので、リポジトリではなく、シークレットストアまたはパスワードマネージャーに保管してください。Android の鍵についても同じ理由が当てはまります。references/signing.mdを参照してください。

その後、発行者を登録します

そのキーペアを長期的に管理できるウォレットにインポートし、そのウォレットで https://publish.solanamobile.com に登録します。ポータルで発行者が作成され、dApp の掲載情報が入力され、App NFT が発行されます。

掲載情報の作成と App NFT の発行は別の手順であり、CLI ではどちらも実行できません。 CLI で公開できるのは、すでに存在するアプリのバージョンです。掲載情報を入力済みでも App NFT が発行されていない場合、ポータル上では完了しているように見えますが、公開の 手順 2 で App NFT and wallet authority must exist before publishing a version というエラーが発生します。 最初のリリース前に、掲載情報に App NFT とウォレットの権限が設定されていることを 確認してください。

開始前にウォレットへ資金を入れてください。現在、ポータルではトランザクション手数料と メタデータのストレージ費用として、約 0.2 SOL が求められます。この金額は目安として扱い、 レンタル料やストレージ料金によって変動するため、ポータルに実際に表示される金額を確認して ください。これは登録費用であり、CLI 独自の事前チェックとは別です。CLI の事前チェックでは、 署名者が 0.016 SOL を保有していないと公開を開始できません。

次に Dashboard > Settings > API keys で API キーを発行し、環境に設定します。

export DAPP_STORE_API_KEY='paste-the-portal-api-key'

DAPP_STORE_API_KEY は、CLI がデフォルトで読み取る環境変数です。対話型プロンプトに 入力すると、その export はシェルの履歴ファイルにも記録されるため、貼り付けるよりも シークレットマネージャーから読み込むことをおすすめします。--api-key-env <name> を指定すると CLI が別の環境変数を参照し、--api-key-stdin を指定すると標準入力からキーを読み込みます。 CI ではパイプより環境変数のほうがダンプされやすいため、後者のほうが適しています。

printf '%s' "$DAPP_STORE_API_KEY" | dapp-store --api-key-stdin <publish flags>

これはキーを渡す方法の概要にすぎません。完全な実行例は公開を参照してください。 この時点ではまだビルドしていない APK が必要です。

CLI には、本番環境以外のポータルを指定するフラグが 2 つあります。--local-dev は localhost のポータルを許可し、自己更新のチェックをスキップします。--skip-self-update は 単独でもそのチェックをスキップしますが、--local-dev と併用する必要があります。そのため、 単独で指定すると `--skip-self-update` is only allowed together with `--local-dev` という エラーが発生します。ローカル開発モードでは、ローカルではないポータル URL は拒否されるため、 ホストされたステージングポータルへの接続には使用できません。その場合は --portal-url を 使用してください。

署名済み APK をビルドする

使用するコマンドは、キーストアを誰が管理しているかによって異なります。詳しくは references/signing.md をご覧ください。

EAS 管理の認証情報。 APK を強制的に生成する dapp-store プロファイルでビルドします。

eas build --platform android --profile dapp-store

EAS が署名してダウンロード URL を返すため、このワークフローでは android/app/build/outputs/ パスはありません。成果物をダウンロードしてローカルファイルを公開するか、URL を --apk-url に渡してダウンロードを省略してください。ただし、更新の場合は APK をダウンロードしてください。--apk-url を使うと、後述の署名チェックも省略されるためです。

ローカル署名 — bare React Native、またはローカルでビルドする Expo の場合:

(cd android && ./gradlew :app:assembleRelease)

サブシェルを使うことが重要です。使わないと cd の移動先が維持され、後続のすべてのコマンドでリポジトリルート基準のパスが android/ 以下として解決され、失敗します。

APK は android/app/build/outputs/apk/release/app-release.apk に生成されます。

どちらの場合も、アップロード前に APK が想定しているキーで署名されていることを確認してください。 ここおよび公開で使う $APK は、ビルドで生成されたファイルです。上記の Gradle 出力パスにあるファイル、または EAS からダウンロードした成果物のいずれかです。

"$ANDROID_HOME"/build-tools/<version>/apksigner verify --print-certs "$APK"

通常の Android SDK インストールでは apksigner は PATH に含まれていないため、単に apksigner と実行すると command not found になります。$ANDROID_HOME/build-tools/<version>/ にあるので、インストール済みの最も高いバージョンを選んでください。

apksigner verify は APK のみを読み込み、キーストアや秘密鍵は必要ありません。そのため、ダウンロードした EAS の成果物でもローカルビルドと同じように動作します。更新の場合は、出力された SHA-256 フィンガープリントを前回のリリースのものと比較してください。この比較が、アップロード前に誤ったキーストアで署名されたビルドを検出する唯一の方法です。更新で誤ったキーを使ってしまうと、復旧手段はありません。

終了コードだけでなく、証明書名も確認してください。 標準の Expo prebuild では、リリースバリアントに デバッグ キーストアが設定されます。そのため、設定されていないリリース APK も署名済みにはなりますが、キーが誤っています。apksigner verify は問題なく成功します。出力された DN が CN=Android Debug の場合、署名設定が反映されていません。references/signing.md をご覧ください。

アップロード前にもう 1 つ確認しておきたい点があります。React Native のデフォルトのリリース APK にはすべての ABI が含まれ、サイズは 100 MB を大きく超えます。1 つのアーキテクチャ向けにビルドすると(./gradlew :app:assembleRelease -PreactNativeArchitectures=arm64-v8a)、通常はサイズを半分以上削減できます。

キーストアの作成、Gradle の署名設定、EAS のビルドプロファイル、リリースビルドでのみ発生する失敗については、references/signing.md をご覧ください。

公開

初回リリースでも、その後のすべてのリリースでも、実行するコマンドは 1 つです。

dapp-store \
  --apk-file "$APK" \
  --keypair ~/.config/solana/publisher.json \
  --whats-new "Initial release"

$APK はビルド手順で生成されたファイルです。Gradle の出力パス、またはダウンロードした EAS アーティファクトを指定します。 デフォルト値はなく、単に ./app-release.apk と指定しても、そこに自分でコピーしてある場合にしか機能しません。 代わりに、EAS ビルドの URL を --apk-url https://… に直接渡せば、APK をローカルにコピーせずに、ホストされている APK を公開できます。

--keypair <path> で Solana の署名者を指定します。CLI はデフォルトのキーペアパスを想定していないため、明示的に渡してください。上記のパスは、公開者の ID を保持するキーペアを指定するためのプレースホルダーです。実行が失敗した場合に必要となるリリース ID とセッション ID は、--verbose を指定すると出力されます。

公開処理自体はポータルが行うため、そのための RPC エンドポイントを設定する必要はありません。--rpc-url <url> も存在しますが、--help には表示されません。これは送信前に署名者の残高を確認する事前チェックにのみ使われます。ポータルのデフォルトは https://publish.solanamobile.com です。--portal-url <url> または DAPP_STORE_PORTAL_URL で接続先を変更できます。Solana Mobile からステージング用ポータルが提供された場合にのみ必要です。

ポータルの接続先を変更する場合は、RPC の接続先も変更してください。 この事前チェックは、--portal-url の値にかかわらず mainnet を参照するため、devnet 上のステージング用ポータルでは、公開を開始する前に残高のあるウォレットが拒否されます。

Signer <address> has 0.000000 SOL, but publishing needs at least 0.016000 SOL available

残高は実際にありますが、別のクラスター上にあります。--portal-url と --rpc-url を一緒に渡してください。

dapp-store \
  --apk-file "$APK" \
  --keypair ~/.config/solana/publisher.json \
  --whats-new "Initial release" \
  --portal-url https://staging.publish.solanamobile.com \
  --rpc-url https://api.devnet.solana.com

CLI は起動時に dotenv.config() を呼び出すため、作業ディレクトリの .env に設定した環境変数と API キーは、明示しなくても読み込まれます。自分のプロジェクトでは便利ですが、クローンしたプロジェクトでは危険です。ほかの人がコミットした DAPP_STORE_PORTAL_URL があると、API キーがその人のホストに送信されます。チェックされるのは HTTPS であることだけです。自分で作成していないリポジトリから公開する前に .env を確認し、そこに設定された値を上書きするために --portal-url を明示的に渡してください。

ポータルは APK のパッケージ名から更新対象のアプリを推定するため、その名前はセットアップ時に作成した掲載情報と一致している必要があります。一致しない場合、名前のエラーではなく、アプリが見つからないという扱いになります。名前は Expo では expo.android.package から、bare プロジェクトでは android/app/build.gradle の applicationId から取得されます。プロジェクトでどちらを使用しているかを確認してください。

スクリプトから実行する場合は、--idempotency-key <key> を渡し、そのリリースのすべての再試行で同じ値を再利用してください。これを省略しても中立的なデフォルトにはなりません。CLI は呼び出しごとに新しい randomUUID() を生成するため、再試行が新たな公開として扱われ、二重に公開される可能性があります。最初の試行の前に、キーをリリース ID と一緒に記録してください。

失敗した公開を再開する

公開は複数の手順からなるセッションです。失敗後に再開できるかどうかは、どの時点で失敗したかによって異なります。ミント処理中に失敗した場合はロールバックされ、CLI はその旨を出力します — Rolling back failed publication release / Failed publication release cleaned up。このメッセージの後にはセッションが残っていないため、改めて公開するのが適切です。クリーンアップが行われずに実行が終了した場合、通常はセッションの途中でネットワークが切断された場合に、resume を使います。

dapp-store resume \
  --release-id "$RELEASE_ID" \
  --keypair ~/.config/solana/publisher.json

resume は、公開時と同様に --keypair を検証し、指定されていない場合は --keypair is required エラーで失敗します。--release-id と --session-id のどちらか一方だけを指定してください。--help の使用方法には両方を一緒に指定する例が表示されていますが、両方指定すると明示的に拒否されます。

どちらの ID も元の実行時の出力に含まれるため、対話形式以外で実行する場合は --verbose を指定してください。--verbose が何を出力するか、またそのタイミングに注意してください。リリース ID と公開セッション ID が出力されるのは、APK の取り込みが成功した後です。取り込み中に終了した実行では、取り込みセッション ID だけが出力されますが、これは別の ID です。--session-id が必要とするのは公開セッション ID であり、取り込みセッション ID は受け付けません。この段階での失敗は、そもそも再開できるものがありません。原因を解決して、もう一度公開してください。

アップデートの公開

初回リリースと同じコマンドです。次の3つの条件を満たさないと、失敗するか、新しい内容が何もないまま公開されてしまいます。

  • versionCode を上げる — ただし、実際に管理しているファイルで変更してください。ベアプロジェクトでは android/app/build.gradle です。Expo では app.json または app.config.* の expo.android.versionCode で、build.gradle を編集しても、prebuild で再生成されるため意味がありません。新しく作成した Expo アプリでは、app.json に expo.version も expo.android.versionCode も存在しないことがよくあります。その場合、Expo は 1.0.0 と 1 を使用します。2回目のリリースで値を上げる必要が出てから欠落に気づくのではなく、初回リリースの前に両方のキーを追加してください。cli.appVersionSource が "remote" の場合は、EAS が versionCode を全面的に管理します。ローカルファイルを編集する代わりに、ビルドプロファイルで autoIncrement を設定してください。Android ではこの整数でリリースの新旧を判定し、ストアもこれを使って新しいバージョンかどうかを判断します。
  • versionName も上げる — Expo では expo.version、ベアプロジェクトでは build.gradle の versionName です。CLI は両方をリリースメタデータとして送信し、versionName はユーザーが掲載ページで見る文字列です。変更されていなくてもエラーにはならないため、versionCode だけを上げたアップデートでは、前のバージョンが表示されたまま気づかれずに公開されます。
  • 元のキーストアで署名する。 前述の制約を確認してください。復旧する方法はありません。

審査

申請は人による審査に回され、数営業日かかります。ここに記載された数字をもとに公開日を決めるのではなく、ドキュメントで最新の所要日数を確認してください。よくある却下理由は、署名されていない APK、掲載ページと一致しないパッケージ名、プライバシーポリシーの欠落、公開者ポリシーに違反するコンテンツです。最後の項目についてはポリシーが判断基準であり、内容は変更されます。推測せず、ポリシーを確認してください。

Web アプリ

ラップした Web アプリでは別の手順を使います。npx solana-mobile@latest webshell init と webshell build を実行すると、サイトが Android WebView に配置され、署名用のキーストアが作成されます。Web アプリを本当にラップする場合にのみ、この方法を使ってください。React Native アプリには、前述のネイティブ APK の手順を使用します。

Bubblewrap は使用しないでください。その Trusted Web Activity は Chrome で動作し、Chrome の Local Network Access 制限により Mobile Wallet Adapter がブロックされるか、使用前に権限プロンプトが表示されます。Webshell は代わりに Android 経由でウォレットアプリにウォレットリクエストを渡すため、Digital Asset Links は不要です。プロンプトをスキップするには、ページで @solana-mobile/wallet-standard-mobile 0.5.1 以降が必要です。

Bubblewrap から移行する場合は、既存の twa-manifest.json を webshell init --manifest に渡します。これによりパッケージ ID、バージョン番号、署名キーが引き継がれ、既存の掲載情報がそのまま更新されます。インポートした versionCode はそのまま再利用されるため、ストアがアップデートとして受け付けない場合は、掲載中の値より大きい --version-code を渡してください。フラグについては、solana-mobile スキルの CLI リファレンスを参照してください。

詳しい手順:https://docs.solanamobile.com/recipes/general/publishing-a-web-app

参考資料

  • references/signing.md — キーストアの作成、Gradle の署名設定、EAS ビルドプロファイル、ProGuard に関する落とし穴を含むリリース時のみ発生するエラー

関連スキル

  • solana-mobile — スキャフォールディング、ツールチェーンの確認、開発ビルド
  • solana-mobile-wallet — リリースビルドでも維持する必要があるウォレット連携

リンク

コメント