关于 solana-mobile-publishing
面向 Solana dApp Store 发布 Android 应用的指南和命令行工作流,涵盖发布者设置、签名 APK 构建、版本发布及中断后的恢复。
主要功能
- 01设置发布者和 App NFT
- 02签名 APK 构建指南
- 03通过命令行发布版本
- 04恢复符合条件的失败会话
- 05配置 API 密钥和发布门户
适用场景
- 01注册发布者和应用列表
- 02发布首个 Android 版本
- 03使用原有签名密钥发布更新
- 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 会被拒绝。 使用 assembleRelease 构建,切勿使用 bundleRelease。这能避免那些习惯了 Google Play(默认使用 bundle)的开发者踩坑。
密钥库和发布者钱包都是永久的。 更新必须使用与上一个版本相同的密钥签名,而 App NFT 存储在发布者钱包中。丢失其中任何一个都无法恢复,也没有任何支持工单可以解决——该应用只能以新身份从头重新上架。两者都不应放在仓库中:生成密钥库前,先确认它应该存放在哪里,并确保它不在项目目录树中。
如果也要发布到 Google Play,请使用不同的签名密钥。 每个商店使用一个密钥。共用一个密钥意味着一旦密钥泄露或丢失,两个商店的应用都会同时受影响。
首先:判断你属于哪种情况
| 情况 | 操作 |
|---|---|
| 还没有发布者账户 | 一次性设置 |
| 已有发布者账户,准备发布一个版本 | 构建签名 APK,然后发布 |
| 为已上线的应用发布更新 | 发布更新 |
| 发布版本崩溃,但调试版本正常 | 阅读 references/signing.md |
| 发布过程中途失败 | 恢复失败的发布 |
| 应用是封装的 Web 应用,而非 React Native 应用 | Web 应用 |
一次性设置
先准备发布者密钥对
CLI 使用密钥对文件以发布者身份签名,而在浏览器门户中连接钱包并不会生成密钥对文件。注册任何内容之前,先准备好该文件,因为门户中连接的钱包必须与该密钥一致。如果顺序反了,就得将浏览器钱包的私钥导出到磁盘,这在各方面都更糟。
如果已有用于此目的的密钥对,请使用它的路径,并先询问应该使用哪个密钥对,不要随手使用 ~/.config/solana/id.json 中的内容——那里通常是临时的开发密钥,用错密钥签名会导致门户无法识别发布者。
如果还没有密钥对,应由开发者生成,而不是 Agent:
solana-keygen new --silent --outfile ~/.config/solana/publisher.json
--silent 会隐藏助记词。如果不加此参数,十二个单词会输出到 stdout,而 Agent 的 stdout 会被记录为对话记录和日志文件——完整的钱包内容会以纯文本形式出现在无人保护的地方。只有在输出不会被记录的终端中才省略 --silent,无论如何都要备份密钥对文件:助记词被隐藏后,该文件就是唯一副本。Agent 只能拿到文件路径。
应像对待发布密钥库一样对待该文件:它与其控制的 App NFT 一样永久,因此必须存放在机密信息存储库或密码管理器中,绝不能放在仓库里。有关 Android 密钥的同样说明,请参阅 references/signing.md。
接着注册发布者
将该密钥对导入由你长期控制的钱包,并使用它在 https://publish.solanamobile.com 上注册。该门户会创建发布者、收集 dApp 列表详情,并铸造 App NFT。
创建列表与铸造 App NFT 不是同一个步骤,而且 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 也会写入 shell 的历史文件,因此最好通过密钥管理器加载,而不是直接粘贴。--api-key-env <name> 可让 CLI 使用其他变量,--api-key-stdin 则从标准输入读取密钥。在 CI 中,环境变量比管道更容易被转储,因此后者是更好的选择:
printf '%s' "$DAPP_STORE_API_KEY" | dapp-store --api-key-stdin <publish flags>
这只是传递密钥的形式——完整调用方式见 发布,此时你还没有构建所需的 APK。
有两个标志可用于让 CLI 指向非生产环境的门户:--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 管理的凭据。 使用 dapp-store 配置构建,该配置会强制生成 APK:
eas build --platform android --profile dapp-store
EAS 会为其签名并返回下载 URL,因此此流程中没有 android/app/build/outputs/ 路径。你可以下载构建产物并发布本地文件,也可以将 URL 传给 --apk-url 来跳过下载——但如果是更新,请下载文件,因为 --apk-url 也会跳过下文的签名检查。
本地签名——适用于裸 React Native,或在本地构建的 Expo:
(cd android && ./gradlew :app:assembleRelease)
子 Shell 很重要:如果没有它,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 预构建会将 release 变体配置为使用 debug 密钥库,因此,未配置的 release APK 也会有签名——只是密钥不对。apksigner verify 会顺利通过。如果输出的 DN 是 CN=Android Debug,说明签名配置没有生效;请参阅
references/signing.md。
上传前还有一项值得检查:默认的 React Native release APK 会打包所有 ABI,大小远超 100 MB。针对单一架构构建(./gradlew :app:assembleRelease -PreactNativeArchitectures=arm64-v8a)通常可以将体积缩小一半以上。
有关密钥库创建、Gradle 签名配置、EAS 构建配置,以及仅在 release 构建中出现的故障模式,请参阅:references/signing.md。
发布
首次发布及之后每次发布,都只需一条命令:
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。
--keypair <path> 用于选择 Solana 签名密钥。CLI 不会假定默认的密钥对路径,因此请明确传入;
上面的路径只是占位符,应替换为持有发布者身份的密钥对路径。--verbose 会在发布和会话标识
生成时打印它们;运行失败时,你需要这些信息。
发布本身由门户处理,因此无需配置 RPC 端点。--rpc-url <url> 确实存在,但在 --help 中被
隐藏;它只用于预检,在提交前检查签名者的余额。门户默认地址为
https://publish.solanamobile.com;--portal-url <url> 或 DAPP_STORE_PORTAL_URL 可更改
门户地址,只有在 Solana Mobile 为你提供了暂存门户时才需要这样做。
更改门户地址意味着也要更改 RPC 地址。 无论 --portal-url 指向哪里,预检都会读取
主网余额,因此在 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 密钥发送到他们的主机,而 CLI 对该地址唯一的检查就是它使用了 HTTPS。
从并非由你创建的仓库发布前,请先查看 .env,并显式传入 --portal-url,以覆盖其中设置的值。
门户会根据 APK 的包名判断要更新哪个应用,因此该名称必须与设置期间创建的列表相符。不匹配时,
系统会显示应用不存在,而不是名称错误。在 Expo 项目中,名称来自 expo.android.package;
在纯原生项目中,则来自 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 中的用法说明显示它们可以一起使用。
这两个标识都来自原始运行的输出,因此对任何非交互式操作来说,使用 --verbose 很有必要。
注意 --verbose 何时打印这些信息:只有 APK 成功导入后,才会输出发布 ID 和发布会话 ID。
如果运行在导入期间失败,只会打印一个导入会话 ID,而这是另一个标识——--session-id
需要的是发布会话 ID,不接受这个标识。如此早期的失败本来也无从恢复;修复原因后重新发布即可。
发布更新
与首次发布使用相同的命令。必须满足以下三项,否则会失败或悄无声息地没有发布任何新内容:
- 递增
versionCode——但要在实际管理它的文件中修改。对于裸项目,该文件是android/app/build.gradle。对于 Expo,它是app.json或app.config.*中的expo.android.versionCode;在 Expo 项目中编辑build.gradle毫无意义,因为 prebuild 会重新生成该文件。刚搭建的 Expo 应用通常在app.json中既没有expo.version,也没有expo.android.versionCode——Expo 会回退到1.0.0和1。应在首次发布前添加这两个键,不要等到第二次发布需要提高版本时才发现它们缺失。 如果cli.appVersionSource是"remote",versionCode完全由 EAS 管理:在构建配置文件中设置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 的本地网络访问限制会阻止 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— 必须确保钱包集成在发布构建中正常运行
链接
- dApp Store 文档:https://docs.solanamobile.com/dapp-store/intro
- 发布 CLI:https://docs.solanamobile.com/dapp-store/publishing-cli
- 构建并签名 APK:https://docs.solanamobile.com/dapp-store/build-and-sign-an-apk
- 发布者政策:https://docs.solanamobile.com/dapp-store/publisher-policy
- 发布门户:https://publish.solanamobile.com
- CLI 和工具源代码:https://github.com/solana-mobile/dapp-publishing