diff --git a/.changeset/dev-signer-no-phone-taps.md b/.changeset/dev-signer-no-phone-taps.md new file mode 100644 index 0000000..0b64f53 --- /dev/null +++ b/.changeset/dev-signer-no-phone-taps.md @@ -0,0 +1,5 @@ +--- +"playground-cli": patch +--- + +Dev-signer deploys no longer ask for phone approvals. bulletin-deploy 0.8.x resolves the persisted `playground init` login session whenever it is called without explicit auth options, which silently routed dev-mode DotNS signing through the phone and signed storage chunks with the user's phone-granted Bulletin quota. `--signer dev` now pins bulletin-deploy to its dev mnemonic and dev storage key explicitly, restoring zero-tap dev deploys. `--suri` deploys likewise pin chunk-upload signing to the suri key instead of silently using the cached slot key. Apps still appear in the owner's MyApps view when a session exists, and dev deploys still earn no XP. diff --git a/CLAUDE.md b/CLAUDE.md index 328440d..b189cda 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -39,6 +39,7 @@ These aren't self-evident from reading the code and have bitten us before. Treat - **Bulletin storage chunks must NEVER sign with the phone session signer.** Chunk txs carry up to 2 MiB of callData; the phone path (`session.createTransaction`) forwards the full callData over the statement store, whose request cap is 4 KiB on the pinned host-papp 0.7.9 (254 KiB upstream; Android itself caps statements at 256 KiB), so every chunk dies client-side with "Mobile transaction signing rejected: message too big" and the phone never even shows a prompt. Since bulletin-deploy 0.8.x, passing `signer` routes STORAGE through it too (not just DotNS), so phone mode must also pass `storageSigner`/`storageSignerAddress` (the local BulletInAllowance slot key, which takes precedence for storage routing only). `src/utils/deploy/signerMode.ts::resolveStorageSignerOptions` is the single place that resolves it; both `runDeploy` and `runDecentralize` thread it into `runStorageDeploy`. bulletin-deploy 0.8.3 can auto-resolve the same slot key from the shared `dot-cli` allowance cache, but silently falls back to phone-signing the chunks when it misses, so don't rely on it. - **Deploy delegates to `bulletin-deploy` for everything storage-related** — chunking, retries, pool accounts, nonce fallback, DAG-PB, DotNS commit-reveal. Don't reimplement. The one thing we own is `registry.publish()`. The contract takes an `Option
owner` parameter — when None, it falls back to `env::caller()`; when Some, that H160 is recorded as the app owner regardless of who signed. Phone mode passes None (caller IS the user). Dev mode with an active session passes the session's `productH160` so Alice can sign the tx while the user still appears in MyApps. The `publisher` field on `AppInfo` always stores `env::caller()`, so `is_authorized_to_republish` lets the original signer iterate without rewriting ownership. See `src/utils/deploy/playground.ts` and `src/utils/deploy/signerMode.ts::resolveSignerSetup`. - **Do NOT call `bulletin-deploy.deploy()` just to store a metadata JSON.** `deploy()` unconditionally runs a DotNS `register()` + `setContenthash()`, and for `domainName: null` invents a `test-domain-` label and registers THAT — the side-trip reverts cryptically. For metadata storage we submit `TransactionStorage.store` directly via PAPI using `calculateCid` from `@parity/product-sdk-bulletin`. The metadata `store` is signed with the product-scoped RFC-0010 Bulletin allowance account cached in `allowance-keys.json` (not Alice, not the product account). Asset Hub `registry.publish` is signed with the user's product account in phone mode, and with a dev signer in dev mode (claimed-owner H160 carries the user identity, per the bullet above). See `src/utils/deploy/playground.ts::publishToPlayground`. +- **Dev mode must pass EXPLICIT auth options to `bulletin-deploy.deploy()` — never `{}`.** Since 0.8.x (the "#411 login UX"), `deploy()` called with no `mnemonic`, no `signer`, and no `suri` probes for a persisted SSO session file (`~/.polkadot-apps/dot-cli_SsoSessions.json` — the SAME namespace `playground init` writes, because bulletin-deploy reuses `DOT_DAPP_ID = "dot-cli"`) and, when found, loads the SSO stack and phone-signs DotNS with the user's session — turning a "0 taps" dev deploy into 3-4 phone approvals for every logged-in user. Independently, an absent `storageSigner` makes it auto-read the user's cached BulletInAllowance slot key and burn their small phone-granted quota on chunk uploads, in every mode including `--suri`. `resolveSignerSetup` therefore pins `mnemonic: DEFAULT_MNEMONIC` for dev mode and `resolveStorageSignerOptions` pins `storageSigner` to the dev bare-root (dev) or the `--suri` key (suri) — the bare-root carries its own Bulletin authorization on paseo-next-v2, and bulletin-deploy's committed-signer wrapper falls back to the shared pool if it ever lapses. Tests in `signerMode.test.ts`, `run.test.ts`, and `decentralize/run.test.ts` pin the contract. - **The "dev signer" used in dev mode is bulletin-deploy's `DEFAULT_MNEMONIC` bare-root account, not Substrate's `//Alice`.** The bare-root SS58 (`5DfhGyQd…`) is what bulletin-deploy uses internally for its DEFAULT_MNEMONIC storage + DotNS signing, so the CLI's `createDevPublishSigner` derives from the same `(mnemonic, path="")` pair via `seedToAccount`. Storage, DotNS, and registry publish all sign as one identity. Substrate's `//Alice` (`5Grwva…`) is a DIFFERENT account — `createDevSigner("Alice")` from `@parity/product-sdk-tx` returns that one. Don't mix them; the `signerModeAlice.test.ts` snapshot guards against regression. - **Dev-mode re-publish only works on apps that were first published from dev mode.** `is_authorized_to_republish` accepts `caller == owner OR caller == publisher`. In dev mode the publisher is always Alice (`5DfhGyQd…`), so dev-mode re-deploys of a dev-published app succeed. But an app first published from phone mode has `caller == publisher == user H160`; Alice is neither, so a dev-mode re-deploy reverts `Unauthorized`. To iterate on a phone-published app in dev mode the user must unpublish it from phone mode first. Intentional asymmetry: once a user "owns" an app from their phone, a shared dev key can't touch it. - **Build a dedicated Bulletin client with `heartbeatTimeout: 300_000` for the metadata upload.** The shared client from `getConnection()` uses `@parity/product-sdk-chain-client`'s default 40 s heartbeat; a single `TransactionStorage.store` round-trip can exceed that and the socket tears down as `WS halt (3)`. We mirror bulletin-deploy's 300 s heartbeat with a one-off client that gets destroyed immediately after the upload. diff --git a/src/utils/decentralize/run.test.ts b/src/utils/decentralize/run.test.ts index be7d8fc..ad68328 100644 --- a/src/utils/decentralize/run.test.ts +++ b/src/utils/decentralize/run.test.ts @@ -59,7 +59,9 @@ vi.mock("../allowances/slotSigner.js", () => ({ readCachedBulletinSlotSigner: vi.fn(async () => null), })); +import { DEFAULT_MNEMONIC } from "bulletin-deploy"; import type { ResolvedSigner } from "../signer.js"; +import { DEV_PUBLISH_ADDRESS } from "../deploy/signerMode.js"; import { describeDeployEvent, runDecentralize } from "./run.js"; describe("describeDeployEvent", () => { @@ -137,7 +139,7 @@ describe("runDecentralize — Bulletin storage signer", () => { expect(arg.auth.storageSignerAddress).not.toBe("5Fake"); }); - it("dev mode passes no storageSigner and never touches the slot key", async () => { + it("dev mode pins the dev mnemonic + dev storage signer and never touches the slot key", async () => { await runDecentralize({ siteUrl: "https://example.com", label: "my-site", @@ -148,9 +150,14 @@ describe("runDecentralize — Bulletin storage signer", () => { }); const arg = runStorageDeployMock.mock.calls[0][0] as unknown as { - auth: { storageSigner?: unknown }; + auth: { mnemonic?: string; signer?: unknown; storageSignerAddress?: string }; }; - expect(arg.auth.storageSigner).toBeUndefined(); + // Explicit dev identity: an empty auth object would let bulletin-deploy + // 0.8.x resolve the persisted phone session (DotNS taps) and the + // user's cached slot key (quota burn). See signerMode.ts. + expect(arg.auth.mnemonic).toBe(DEFAULT_MNEMONIC); + expect(arg.auth.signer).toBeUndefined(); + expect(arg.auth.storageSignerAddress).toBe(DEV_PUBLISH_ADDRESS); expect(ensureSlotAccountSignerMock).not.toHaveBeenCalled(); }); }); diff --git a/src/utils/decentralize/run.ts b/src/utils/decentralize/run.ts index 222b76e..b81ecf7 100644 --- a/src/utils/decentralize/run.ts +++ b/src/utils/decentralize/run.ts @@ -33,6 +33,7 @@ import { publishToPlayground } from "../deploy/playground.js"; import type { DeployLogEvent } from "../deploy/progress.js"; import { type DeployApproval, + DEV_PUBLISH_ADDRESS, resolveSignerSetup, resolveStorageSignerOptions, type SignerMode, @@ -135,12 +136,15 @@ export async function runDecentralize( publishToPlayground: wantPlayground, }); - // Pick the signer used for the DotNS register tx. bulletin-deploy - // accepts `{ signer, signerAddress }` or `{}` (falls back to its - // DEFAULT_MNEMONIC). Either way we surface a single visible address - // for the outcome. + // Pick the signer used for the DotNS register tx. bulletin-deploy gets + // `{ signer, signerAddress }` (phone / `--suri`) or `{ mnemonic }` (dev — + // always explicit, never `{}`: empty options make 0.8.x resolve the + // persisted phone session). Either way we surface a single visible + // address for the outcome; the dev mnemonic's bare root is + // `DEV_PUBLISH_ADDRESS`. const storageSignerAddress = setup.bulletinDeployAuthOptions.signerAddress ?? + (setup.bulletinDeployAuthOptions.mnemonic ? DEV_PUBLISH_ADDRESS : null) ?? setup.publishSigner?.address ?? // Defensive fallback: should never hit because dev mode synthesises // a signer for the publish phase even when one isn't strictly @@ -199,7 +203,7 @@ export async function runDecentralize( domainName: label, // Wrap the DotNS auth signer so each phone tap surfaces a // "check your phone" lifecycle event. No-op in dev mode (auth - // has no signer — bulletin-deploy uses its default mnemonic). + // carries a mnemonic, not a signer — signed in-process). auth: { ...wrapAuthForSigning( setup.bulletinDeployAuthOptions, @@ -284,7 +288,7 @@ export async function runDecentralize( * Wrap the bulletin-deploy DotNS auth signer so each `signTx` call surfaces a * "check your phone" lifecycle event labelled by the matching DotNS approval. * Mirrors deploy's `maybeWrapAuthForSigning`. Returns `auth` unchanged when - * there's no signer (dev mode → bulletin-deploy uses its default mnemonic, + * there's no signer (dev mode → explicit dev mnemonic signed in-process, * no human tap). */ function wrapAuthForSigning( diff --git a/src/utils/deploy/run.test.ts b/src/utils/deploy/run.test.ts index d233452..58bd815 100644 --- a/src/utils/deploy/run.test.ts +++ b/src/utils/deploy/run.test.ts @@ -98,7 +98,9 @@ vi.mock("./storageQuota.js", () => ({ })); const quotaApi = { marker: "bulletin-api" } as any; +import { DEFAULT_MNEMONIC } from "bulletin-deploy"; import { runDeploy, type DeployEvent } from "./run.js"; +import { DEV_PUBLISH_ADDRESS } from "./signerMode.js"; import type { ResolvedSigner } from "../signer.js"; const fakeUserSigner: ResolvedSigner = { @@ -165,10 +167,14 @@ describe("runDeploy", () => { const plan = events.find((e) => e.kind === "plan"); expect(plan).toEqual({ kind: "plan", approvals: [] }); - // bulletin-deploy auth must be empty in dev mode. + // bulletin-deploy auth must pin the dev identity explicitly: an + // empty object makes 0.8.x resolve the persisted phone session for + // DotNS and the user's cached slot key for storage (see signerMode.ts). expect(runStorageDeploy).toHaveBeenCalledTimes(1); const arg = runStorageDeploy.mock.calls[0][0]; - expect(arg.auth).toEqual({}); + expect(arg.auth.mnemonic).toBe(DEFAULT_MNEMONIC); + expect(arg.auth.signer).toBeUndefined(); + expect(arg.auth.storageSignerAddress).toBe(DEV_PUBLISH_ADDRESS); expect(arg.domainName).toBe("my-app"); // Dev mode never opens a Bulletin client for quota checks — no slot diff --git a/src/utils/deploy/signerMode.test.ts b/src/utils/deploy/signerMode.test.ts index b68ccad..d615106 100644 --- a/src/utils/deploy/signerMode.test.ts +++ b/src/utils/deploy/signerMode.test.ts @@ -26,8 +26,13 @@ vi.mock("../allowances/bulletin.js", () => ({ getBulletinAllowanceSigner: getBulletinAllowanceSignerMock, })); +import { DEFAULT_MNEMONIC } from "bulletin-deploy"; import { ss58Encode } from "@parity/product-sdk-address"; -import { resolveSignerSetup, resolveStorageSignerOptions } from "./signerMode.js"; +import { + resolveSignerSetup, + resolveStorageSignerOptions, + DEV_PUBLISH_ADDRESS, +} from "./signerMode.js"; import type { ResolvedSigner } from "../signer.js"; function fakeSigner( @@ -53,17 +58,37 @@ function fakeSigner( } describe("resolveSignerSetup — dev mode", () => { - it("no publish, no funding → empty approvals, empty auth options, null publishSigner", () => { + it("no publish, no funding → empty approvals, explicit dev mnemonic, null publishSigner", () => { const result = resolveSignerSetup({ mode: "dev", userSigner: null, publishToPlayground: false, }); expect(result.approvals).toEqual([]); - expect(result.bulletinDeployAuthOptions).toEqual({}); + expect(result.bulletinDeployAuthOptions).toEqual({ mnemonic: DEFAULT_MNEMONIC }); expect(result.publishSigner).toBeNull(); }); + it("pins the DEFAULT_MNEMONIC explicitly so bulletin-deploy can never pick up a persisted phone session", () => { + // Regression: bulletin-deploy 0.8.x resolves the persisted SSO session + // (~/.polkadot-apps/dot-cli_SsoSessions.json — written by `playground + // init`, shared namespace) whenever it is called with NO mnemonic, NO + // signer, and NO suri. Passing `{}` therefore turned dev mode into + // phone mode (DotNS taps on the phone) for every logged-in user. + // An explicit mnemonic short-circuits its chooseSignerInput before + // the session probe. + const result = resolveSignerSetup({ + mode: "dev", + userSigner: fakeSigner("session", "5User"), + publishToPlayground: false, + }); + expect(result.bulletinDeployAuthOptions.mnemonic).toBe(DEFAULT_MNEMONIC); + // No signer key: run.ts's maybeWrapAuthForSigning must not wrap dev + // deploys in the phone-approval event proxy. + expect(result.bulletinDeployAuthOptions.signer).toBeUndefined(); + expect(result.bulletinDeployAuthOptions.signerAddress).toBeUndefined(); + }); + it("publishToPlayground with active session signs as Alice but claims session H160 as owner — zero phone taps", () => { const user = fakeSigner("session", "5User", "0xaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"); const result = resolveSignerSetup({ @@ -79,8 +104,9 @@ describe("resolveSignerSetup — dev mode", () => { // The user's H160 is claimed via the owner parameter so MyApps still // resolves their app even though Alice signed the tx. expect(result.claimedOwnerH160).toBe("0xaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"); - // Dev mode keeps bulletin-deploy on its built-in default mnemonic. - expect(result.bulletinDeployAuthOptions).toEqual({}); + // Dev mode keeps bulletin-deploy on its built-in default mnemonic — + // passed EXPLICITLY so the persisted phone session is never resolved. + expect(result.bulletinDeployAuthOptions).toEqual({ mnemonic: DEFAULT_MNEMONIC }); }); it("publishToPlayground without any signer falls back to pure Alice ownership", () => { @@ -111,6 +137,8 @@ describe("resolveSignerSetup — dev mode", () => { expect(result.claimedOwnerH160).toBeNull(); expect(result.bulletinDeployAuthOptions.signer).toBe(user.signer); expect(result.bulletinDeployAuthOptions.signerAddress).toBe("5DevSuri"); + // The injected signer wins inside bulletin-deploy; no mnemonic needed. + expect(result.bulletinDeployAuthOptions.mnemonic).toBeUndefined(); }); }); @@ -245,14 +273,33 @@ describe("resolveStorageSignerOptions", () => { }); }); - it("dev mode never touches the slot key — no phone prompt in dev mode", async () => { + it("dev mode pins storage to the dev publish account — never the user's slot key, no phone prompt", async () => { + // Regression: bulletin-deploy 0.8.x auto-reads the user's cached + // BulletInAllowance slot key whenever `storageSigner` is absent and + // signs chunk uploads with it — silently burning the user's small + // phone-granted quota on dev deploys. Pinning the dev bare-root + // (authorized on paseo-next-v2; pool fallback if it ever lapses) + // keeps dev deploys fully off the user's session resources. const user = sessionSignerWithHost(); - await expect(resolveStorageSignerOptions("dev", user)).resolves.toEqual({}); + const result = await resolveStorageSignerOptions("dev", user); + expect(result.storageSigner).toBeDefined(); + expect(result.storageSignerAddress).toBe(DEV_PUBLISH_ADDRESS); expect(getBulletinAllowanceSignerMock).not.toHaveBeenCalled(); }); - it("phone mode with a --suri dev signer returns {} (local key, no size hazard)", async () => { - await expect(resolveStorageSignerOptions("phone", fakeSigner("dev"))).resolves.toEqual({}); + it("dev mode with a --suri signer pins storage to that key (caller owns its allowance)", async () => { + const user = fakeSigner("dev", "5DevSuri"); + const result = await resolveStorageSignerOptions("dev", user); + expect(result.storageSigner).toBe(user.signer); + expect(result.storageSignerAddress).toBe("5DevSuri"); + expect(getBulletinAllowanceSignerMock).not.toHaveBeenCalled(); + }); + + it("phone mode with a --suri dev signer pins storage to that key (local key, no size hazard, no slot hijack)", async () => { + const user = fakeSigner("dev", "5DevSuri"); + const result = await resolveStorageSignerOptions("phone", user); + expect(result.storageSigner).toBe(user.signer); + expect(result.storageSignerAddress).toBe("5DevSuri"); expect(getBulletinAllowanceSignerMock).not.toHaveBeenCalled(); }); diff --git a/src/utils/deploy/signerMode.ts b/src/utils/deploy/signerMode.ts index 49f42d2..3b6062b 100644 --- a/src/utils/deploy/signerMode.ts +++ b/src/utils/deploy/signerMode.ts @@ -21,7 +21,17 @@ * * Today (testnet): * - Dev mode: storage + DotNS go through bulletin-deploy's built-in - * mnemonic (or `--suri`). Playground publish is ALSO signed by the dev + * mnemonic (or `--suri`) — passed EXPLICITLY, never via bulletin-deploy's + * own fallback chain. Since 0.8.x, `deploy()` called with no mnemonic / + * signer / suri resolves the persisted SSO session on disk + * (`~/.polkadot-apps/dot-cli_SsoSessions.json`, the same namespace + * `playground init` writes) and phone-signs DotNS — so an empty options + * object silently turns dev mode into phone mode for any logged-in user. + * Likewise, an absent `storageSigner` makes it auto-read the user's + * cached BulletInAllowance slot key and burn their phone-granted quota + * on chunk uploads. Dev mode therefore pins all three: `mnemonic` for + * DotNS, `storageSigner` for chunks, and the dev publish signer below. + * Playground publish is ALSO signed by the dev * account — Alice's H160 is recorded as `publisher`, but the contract * accepts an optional `owner` parameter so we can record the user's * H160 as the `owner` (the H160 MyApps queries with). When a phone @@ -94,7 +104,9 @@ export type SignerMode = "dev" | "phone"; export interface DeploySignerSetup { /** * Options to pass to bulletin-deploy's `deploy()`. For dev mode this is - * empty (bulletin-deploy falls back to its env / DEFAULT_MNEMONIC); for + * an EXPLICIT `{ mnemonic: DEFAULT_MNEMONIC }` (or the `--suri` signer) — + * never `{}`, because bulletin-deploy 0.8.x answers empty options by + * resolving the persisted phone session from `playground init`. For * phone mode we inject the user's signer so DotNS registration is paid * for by — and recorded against — their account. */ @@ -205,11 +217,24 @@ export function resolveSignerSetup(opts: ResolveOptions): DeploySignerSetup { approvals.push(...dotnsApprovals(opts.plan)); } - if (opts.mode === "dev" && opts.userSigner?.source === "dev") { - bulletinDeployAuthOptions = { - signer: opts.userSigner.signer, - signerAddress: opts.userSigner.address, - }; + if (opts.mode === "dev") { + if (opts.userSigner?.source === "dev") { + bulletinDeployAuthOptions = { + signer: opts.userSigner.signer, + signerAddress: opts.userSigner.address, + }; + } else { + // Pass the default mnemonic EXPLICITLY. bulletin-deploy 0.8.x + // treats "no mnemonic, no signer, no suri" as "resolve a signer + // yourself", and its resolution finds the persisted phone session + // from `playground init` before any dev fallback — turning a dev + // deploy into 3-4 phone taps. An explicit mnemonic wins its + // chooseSignerInput outright, so the session file is never read. + // The derived identity is unchanged: DEFAULT_MNEMONIC's bare root + // (DEV_PUBLISH_ADDRESS) is exactly what the old empty-options + // path used for DotNS. + bulletinDeployAuthOptions = { mnemonic: DEFAULT_MNEMONIC }; + } } // Pick the publish signer and the optional claimed-owner H160. @@ -250,9 +275,12 @@ export function resolveSignerSetup(opts: ResolveOptions): DeploySignerSetup { } /** - * Resolve the signer for Bulletin STORAGE txs (the CAR chunk uploads) in - * phone mode: the local BulletInAllowance slot key, threaded to - * bulletin-deploy as `storageSigner` / `storageSignerAddress`. + * Resolve the signer for Bulletin STORAGE txs (the CAR chunk uploads), + * threaded to bulletin-deploy as `storageSigner` / `storageSignerAddress`. + * Every mode pins one explicitly: phone+session uses the local + * BulletInAllowance slot key, `--suri` uses the caller's key, and dev mode + * uses the dev bare-root — leaving it absent lets bulletin-deploy 0.8.x + * auto-read the user's cached slot key and burn their quota (see below). * * Why this exists: since bulletin-deploy 0.8.x, passing `signer` routes * Bulletin storage through that signer too — not just DotNS. Chunk txs carry @@ -291,10 +319,17 @@ export function resolveSignerSetup(opts: ResolveOptions): DeploySignerSetup { * extent does turn out to be enforced. Only a total resolution failure * (no slot key at all, grant declined) aborts the deploy. * - * Dev mode returns `{}`: storage signs with bulletin-deploy's mnemonic or - * the `--suri` key (both local, no size hazard) and must never prompt the - * phone. A `--suri` signer under `--signer phone` also returns `{}` for the - * same reason. + * Dev and `--suri` deploys pin `storageSigner` to their own local key + * instead of returning `{}`. bulletin-deploy 0.8.x auto-reads the user's + * cached BulletInAllowance slot key whenever `storageSigner` is absent and + * signs chunk uploads with it — silently burning the user's small + * phone-granted quota (~10 txs / 4 MiB per grant) on deploys that were + * supposed to run entirely on dev accounts. The dev bare-root carries its + * own Bulletin authorization on paseo-next-v2; if it ever lapses, + * bulletin-deploy's committed-signer wrapper falls back to the shared pool + * (the pre-0.8 dev storage path) without aborting. A `--suri` key is the + * caller's responsibility per the CI escape hatch contract — unauthorized + * keys land on the pool fallback the same way. */ export async function resolveStorageSignerOptions( mode: SignerMode, @@ -306,7 +341,20 @@ export async function resolveStorageSignerOptions( }, onPrompt?: AllowancePrompt, ): Promise> { - if (mode !== "phone" || userSigner?.source !== "session") return {}; + // A --suri key (either mode): the caller supplied a local key and owns + // its Bulletin allowance. Pin it so the slot-key auto-read never fires. + if (userSigner?.source === "dev") { + return { storageSigner: userSigner.signer, storageSignerAddress: userSigner.address }; + } + // Dev mode without --suri: pin the dev bare-root — the same identity the + // explicit DEFAULT_MNEMONIC gives DotNS in resolveSignerSetup. + if (mode !== "phone") { + return { + storageSigner: DEV_PUBLISH_ACCOUNT.signer, + storageSignerAddress: DEV_PUBLISH_ADDRESS, + }; + } + if (userSigner?.source !== "session") return {}; const resolve = async (withQuota: boolean) => { const storageSigner = await getBulletinAllowanceSigner({ diff --git a/src/utils/deploy/storage.ts b/src/utils/deploy/storage.ts index 16b28f3..aae3749 100644 --- a/src/utils/deploy/storage.ts +++ b/src/utils/deploy/storage.ts @@ -52,9 +52,12 @@ export interface StorageDeployOptions { /** * Auth options forwarded to bulletin-deploy. Usually produced by * `resolveSignerSetup()` merged with `resolveStorageSignerOptions()`. - * May be `{}` for the dev path. `storageSigner` (the BulletInAllowance - * slot key) takes precedence over `signer` for Bulletin storage routing - * inside bulletin-deploy — chunk txs are too large for phone signing. + * Never `{}`: dev mode pins an explicit `mnemonic` + dev `storageSigner` + * (empty options make bulletin-deploy 0.8.x resolve the persisted phone + * session — see signerMode.ts). `storageSigner` (the BulletInAllowance + * slot key in phone mode, the dev / `--suri` key otherwise) takes + * precedence over `signer` for Bulletin storage routing inside + * bulletin-deploy — chunk txs are too large for phone signing. */ auth: Pick< DeployOptions,