import { WsEvent } from 'polkadot-api/ws'; import { CID } from 'multiformats/cid'; import { ManifestFileEntry, EmbeddedManifest } from './manifest.js'; import { PhoneSignatureStep, DotnsPreflightResult, DotnsSuccessAction, DotNS, DotNSConnectOptions } from './dotns.js'; import { DotnsAbiProfile } from './dotns-protocol.js'; import { PopSelfServeConfig } from './environments.js'; import { PolkadotSigner } from 'polkadot-api'; import { NonRetryableError } from './errors.js'; export { EXIT_CODE_NO_RETRY } from './errors.js'; export { mnemonicConflictNotice, resolveEffectiveMnemonic } from './mnemonic.js'; import './personhood/bootstrap.js'; import './personhood/bind-personal-id.js'; import './personhood/claim-pgas.js'; import './personhood/bind-paid-alias.js'; import './personhood/chain-prereqs.js'; interface DeployResult { domainName: string; fullDomain: string; cid: string; ipfsCid?: string; /** * The env-aware browser URL for the deployed site — same value browserUrlFor() * produces for the "Check it out here" console line (issue #1157). Exposed here * so callers embedding deploy() as a library (and the CLI's GITHUB_OUTPUT write, * and the reusable workflow's PR-comment step) can reuse the ONE resolution * instead of recomputing a gateway URL from a hardcoded default. */ browserUrl: string; /** * The Bulletin allowance-slot signer this deploy stored its content with, when it resolved * one from a login session (`resolveStorageSigner`). Exposed so the CLI can hand the SAME * identity to `publishManifest` immediately afterwards: the manifest's icon and executables * are billed to the storage account's quota, and a session deploy whose manifest fell back * to the shared pool would fail on any chain whose pool holds no quota. Undefined when * storage ran on a mnemonic, an external signer, or the pool — those the manifest step * resolves for itself from the options it is already given. */ storageSigner?: PolkadotSigner; /** SS58 address of that slot account. Set whenever `storageSigner` is. */ storageSignerAddress?: string; } type DeployContent = string | Uint8Array | Uint8Array[]; declare function friendlyChainError(msg: string): string; /** * An Error carrying the InvalidTransaction variant name captured structurally * at the point the chain-subscription error was produced (#1256), so * classifyErrorKind can read it directly instead of re-deriving it from a * message that downstream code may have truncated. */ interface ChainError extends Error { chainErrorVariant?: string; } /** * Pulls the InvalidTransaction variant name (e.g. "AncientBirthBlock", * "BadProof", "Payment") out of the raw `{ "type": "Invalid", "value": { * "type": "" } }` shape substrate's signed extension returns. * Must run on the message BEFORE either producer site in this file truncates * it to 100 chars — that truncation is what made #1255's message-regex * (`/AncientBirth/i`) a function of nonce digit count. Capturing the variant * here removes that dependency: the message can still be truncated for size, * it just stops being the only carrier of which variant it was. */ declare function extractInvalidTransactionVariant(msg: string): string | undefined; interface ProviderResult { client: any; unsafeApi: any; signer: PolkadotSigner; ss58: string; } interface ExistingProvider { client?: any; unsafeApi?: any; signer?: PolkadotSigner; ss58?: string; reconnect?: () => Promise; fetchNonce?: (rpc: string | string[], ss58: string) => Promise; skipCids?: Set; probeFailedCids?: Set; gateway?: string; /** * CIDs the caller vouches are already on-chain. Chunks matching these CIDs * are skipped without any re-probe (unlike `skipCids` which re-probes before * skipping). Invariant: only pass CIDs verified or uploaded within the same * chain connection during the current deploy — they are trusted to still be * present (eviction within a single deploy session is negligible). */ trustedCids?: Set; /** * When true, skip the DAG-PB root build + setRoot tx at the end of * storeChunkedContent. Phase A in the V2 path passes this because the * caller (storeDirectoryV2) never uses the Phase A root CID — Phase B * computes and stores the real root, which becomes the contenthash. */ skipRootStore?: boolean; /** * When true, a client storeChunkedContent created by reconnecting is handed back * via `liveProvider` alive, and closing it becomes the caller's job (#1672). * storeDirectoryV2 sets it: it probes finality with that client afterwards. * Callers that drop liveProvider leave it unset, and the client is closed here. */ handOffLiveClient?: boolean; } interface ChainReceipt { txHash: string; blockHash: string; blockNumber: number; } interface StoredChunk { cid: CID; len: number; viaFallback?: boolean; receipt?: ChainReceipt; } declare const DEFAULT_BULLETIN_RPC = "wss://paseo-bulletin-rpc.polkadot.io"; declare const DEFAULT_POOL_SIZE = 10; declare let BULLETIN_ENDPOINTS: string[]; /** * Bulletin RPC override precedence, shared by every caller that resolves an * env's Bulletin endpoint(s): an explicit `rpcOverride` (CLI `--rpc`) wins, * falling back to the `BULLETIN_RPC` env var, else the env-resolved * candidate list is used unchanged. The override is placed first (primary) * with the rest of the env's candidates kept as fail-over backups, minus any * duplicate of the override itself. * * Extracted from `deploy()`'s own resolution (#1094) so `manifest/publish.ts` * can compute the exact same endpoint `deploy()` would for a given env/--rpc, * instead of inventing a second resolution mechanism. */ declare function resolveBulletinEndpoints(envBulletin: string[], rpcOverride?: string): string[]; /** * Set the module-level Bulletin endpoint list that `getProvider()` (and * therefore `storeFile`/`storeDirectory` when called without an explicit * client) connects to. `deploy()` sets this from the resolved env/--rpc at * the top of every run. Exported (#1094) so callers outside this module — * namely `manifest/publish.ts`'s `publishManifest`, which can run without a * preceding in-process `deploy()` call — can point their own storage * uploads at the same env instead of silently defaulting to * `DEFAULT_BULLETIN_RPC`. ESM named imports are read-only bindings, so a * setter is the only way for another module to update this `let`. */ declare function setBulletinEndpoints(endpoints: string[]): void; /** Test-only: clear quotaExhaustedDimsSeen without a full deploy() call. See its declaration above. */ declare function __resetQuotaExhaustedDimsSeenForTest(): void; /** * Set the three env-derived values above in one call, for the same reason * `setBulletinEndpoints` exists (#1094): `manifest/publish.ts` opens its own storage provider * and can run without a preceding in-process `deploy()`, in which case these would still hold * their defaults — `autoAuthorize: false` on an env that does permit it, so a direct signer * that only needs the standard grant would hard-fail in `ensureAuthorized` instead of being * authorized. `deploy()` keeps setting them inline from the resolved env; this is the same * assignment for callers outside the module. */ declare function setBulletinAuthorizationContext(ctx: { autoAuthorize: boolean; authorizer?: string; network?: string; }): void; /** The three values above, for tests that assert the wiring without a chain connection. */ declare function __getBulletinAuthorizationContextForTest(): { autoAuthorize: boolean; authorizer?: string; network?: string; }; declare function setWsHaltCallback(cb: (() => void) | null): void; declare function makeBulletinStatusHandler(primary: string): (s: { type: WsEvent; uri?: string; }) => void; declare const CHUNK_MORTALITY_PERIOD: number; declare const WS_HEARTBEAT_TIMEOUT_MS: number; declare function retryBudgetExhausted(history: number[], maxEvents: number, windowMs: number, now?: number): boolean; /** * How the root-node store loop answers a failed submit (#1672). A tx the pool * rejects for its nonce is a nonce collision, not a lost connection: a sibling * deploy on the same signer took that nonce (S9), so the fix is a fresh nonce, * and a reconnect only burns budget. papi reports it as InvalidTxError Stale once * the block holding the other tx finalises, or as isValid:false on a reorg. A WS * halt or a connection error means the client is dead. Anything else keeps the * historical reconnect. */ declare function classifyRootSubmitError(error: any, wsHalted: boolean): "reconnect" | "nonce-collision"; /** The pool rejected the tx for its nonce: InvalidTxError Stale, or isValid:false (#1672). */ declare function isNonceCollisionError(error: any): boolean; /** Test-only: replace the collision backoff (null restores the default). */ declare function __setNonceCollisionBackoffForTest(fn: (() => number) | null): void; declare function isConnectionError(error: any): boolean; /** * True for benign teardown noise that must NOT fail a deploy/command. Covers: * - connection errors (recoverable via the storage reconnect path); * - "DestroyedError: Client destroyed" — orphaned pending-response promises the * SSO/papi client rejects while a session adapter is torn down AFTER the work * is done (e.g. the owner-signs update path destroying its re-acquired session); * - papi's raw "Not connected" — adapter.destroy()'s synchronous RxJS finalizer * calling sendUnsubscribe on an already-closed WS (originally login.ts's own * `teardownFilter` regex; folded in here so login.ts can delegate instead of * carrying a duplicate pattern — see src/commands/login.ts, #1276). NOTE this * widens the DEPLOY path too: bin/bulletin-deploy's `handleUnhandled` has no * `tearingDown` gate, so an orphaned "Not connected" rejection is now * suppressed (and captured as a warning) rather than exiting 2. Only * *unhandled* rejections reach that handler — an awaited failure still * propagates normally; and * - the `@novasamatech/sdk-statement` `getStatements` TDZ crash: `const unsubscribe` * is initialised from `api.subscribeStatement(...)`, and both the next/error * callbacks close over it. If that observable settles SYNCHRONOUSLY (a poll * firing after the WS client was already destroyed — hit on Ctrl+C/teardown * during an active pairing poll), the callback runs before the binding is * initialised, throwing `ReferenceError: Cannot access 'unsubscribe' before * initialization`, which rxjs rethrows as an uncaughtException. * `patches/@novasamatech+sdk-statement+0.6.0.patch` is the PRIMARY fix (it also * closes a subscription leak this guard cannot undo) — this only prevents an * unpatched consumer (e.g. npm blocking install scripts) from crashing outright. * Deliberately narrow: matches only this exact binding name, so an unrelated * ReferenceError still crashes the process. * The CLI's crash handlers use this so a successful deploy isn't marked killed * (exit 2) by late teardown noise. Checks name+message so DestroyedError matches * even when its message differs. */ declare function isBenignTeardownError(error: any): boolean; declare const SHA256_MULTIHASH_CODE = 18; declare const BLAKE2B_256_MULTIHASH_CODE = 45600; declare function deriveRootSigner(mnemonic: string, path?: string): { signer: PolkadotSigner; ss58: string; }; declare function createCID(data: Uint8Array, codec?: number, hashCode?: number): CID; declare function encodeContenthash(cidString: string): string; declare const ENCRYPT_MAGIC: Uint8Array; declare const ENCRYPT_SALT_LEN = 16; declare const ENCRYPT_NONCE_LEN = 12; declare const ENCRYPT_TAG_LEN = 16; declare const ENCRYPT_KEY_LEN = 32; declare const ENCRYPT_PBKDF2_ITERATIONS = 100000; declare function encryptContent(data: Uint8Array, password: string): Promise; /** * storageSigner > signer (unless session-backed with no slot, #1452) > mnemonic > pool * precedence for storage routing. `selectStorageReconnect` delegates to this so the two * never drift apart (the drift this function's own "ForTest" name once let happen). * Exported for unit testing. */ declare function __selectStorageProviderModeForTest(options: Pick): "storageSigner" | "signer" | "direct" | "pool"; /** * Build the signer-related DeployOptions fields from a resolved `resolveDeployActors` * result. Pulled out of the resolve branch (#1452) so the session-vs-local distinction * that gates Bulletin storage routing is directly unit-testable without exercising * `resolveDeployActors`' SSO stack. Exported for unit testing. */ declare function deployActorsToSignerOptions(actors: { worker: { signer: PolkadotSigner; address: string; source: string; }; recipientH160?: string; }): Pick; /** * Resolve the environment id the CLI should target, in precedence order: * `--env` flag > `BULLETIN_DEPLOY_ENV` env var. Returns `undefined` when * neither is set — callers (deploy(), the bin's other flag sites) already * fall back to `DEFAULT_ENV_ID` themselves, so this helper doesn't bake that * default in; it only resolves the flag/env-var precedence (issue #1165, * mirrors #1107's `resolveEffectiveMnemonic` pattern so the bin's session * default is unit-testable). */ declare function resolveEnvId(opts: { flagEnv: string | undefined; envVar: string | undefined; }): string | undefined; /** * Decide whether the deploy should publish product-config manifest records * (subname registration + resolver + contenthash + text records), independent * of whether `tryLoadProductConfig` found a config on disk. Exists so the * bin's `--no-manifest` / `--content-only` short-circuit (issue #1163) is * unit-testable: with the flag set, manifest publishing is skipped even when * a `bulletin-deploy.config.*` is discoverable, producing the same * content-only deploy as when no config exists at all. */ declare function shouldPublishManifest(opts: { configFound: boolean; noManifest: boolean; }): boolean; /** * Choose the post-deploy banner text (#1164). Pure and unit-testable without * a live deploy. `manifestPending` is opt-in on `DeployOptions` — every * existing library caller (e.g. playground-cli) leaves it unset and keeps * today's "DEPLOYMENT COMPLETE!" banner. */ declare function pickPostDeployBannerText(manifestPending: boolean | undefined): string; /** * Print the real completion banner + browser URL. Called by `deploy()` itself * when `manifestPending` is unset/false, and by `bin/bulletin-deploy` once its * own subsequent `publishManifest()` call succeeds when it is set. */ declare function printDeploymentCompleteBanner(fullDomain: string, browserUrl: string): void; /** * Decide how to source the signer for a deploy invocation. Exported for unit testing. * * - "mnemonic" — caller passed --mnemonic; use mnemonic-derived signer (existing path). * - "injected" — caller pre-built a PolkadotSigner (library or test seam). * - "resolve" — use resolveSigner: either --suri was passed (dev account / * mnemonic), OR a persisted login session exists (hasSession) so a * plain `deploy` uses the logged-in identity (the #411 UX). This is * the only path that loads the SSO stack. * - "pool" — none of the above: no --mnemonic, no pre-built signer, no --suri, * and no persisted session → pool path, unchanged from pre-#411. * * Layer-3 isolation is preserved because `hasSession` is computed at the call site * from a cheap session-file existence check — headless/CI deploys (no session file, * no --suri) never load the SSO stack or hit the People chain. */ declare function chooseSignerInput(opts: { mnemonic: string | undefined; suri: string | undefined; hasInjectedSigner: boolean; hasSession?: boolean; }): "mnemonic" | "injected" | "resolve" | "pool"; declare function isPhoneSignerActive(options: Pick): boolean; /** * Build the error bin/bulletin-deploy's `confirmPhoneReady` hook throws when the * phone-confirmation gate fires in a non-interactive environment (issue #1257). * * Pre-fix, the CLI unconditionally created a `readline` interface and awaited a * keypress; in a non-interactive shell (no TTY, or CI) readline's `"close"` event * fires immediately because there is no input to deliver, and the gate rejected * with `new Error("aborted by user")` — indistinguishable from a deliberate * Ctrl-C, so 6 real CI failures landed under `deploy.error_kind: unknown` blamed * on an operator who was never there. There is no safe default to fall back to * here: unlike a yes/no prompt, silently proceeding would submit a transaction * nobody approved on their phone. So a non-interactive caller must hard-fail * with a message naming the actual fix — swap to a signer that never needs * phone confirmation. * * `NonRetryableError` (not a plain `Error`): retrying in the same CI environment * fails the identical way every time, so bin/bulletin-deploy should exit with * EXIT_CODE_NO_RETRY rather than a retryable-looking generic failure. * * Pure and readline-free (unlike the CLI's readline wiring) so it's directly * unit-testable; bin/bulletin-deploy calls this only after checking * version-check.ts's `isInteractive()` itself, reusing that existing TTY/CI * detection rather than adding a second one. */ declare function nonInteractivePhoneConfirmationError(label: string): NonRetryableError; /** * Decide whether to hand the name over to the signed-in user after a deploy. * The handover only fires when the worker FRESHLY REGISTERED the name in this * run (#928): updating the content of a name that already exists must never * change its ownership. Without this, re-deploying any pre-existing name in * transfer mode silently transferred it to whatever session was on disk — * which captured shared E2E fixture labels for a local developer's account. * Exported for unit testing. */ declare function shouldHandoverName(opts: { transferTo?: string; registeredFresh: boolean; }): boolean; /** * Produce the one-line storage-signer status printed at resolution time. Exported for unit testing. * User-owned slot: " Storage signer: your allowance slot " (owned=true) * Explicit slot: " Storage signer: allowance slot " (owned=false or omitted) * Fallback: " Storage signer: pool fallback ()" */ declare function formatStorageSignerLine(slotAddress: string | null, failReason?: string, owned?: boolean): string; /** * Storage-signer status line for transfer mode. In transfer mode the local * worker (Alice / --suri) signs the whole deploy, INCLUDING Bulletin storage — * so it is NOT a pool fallback. The old line said "pool fallback (transfer mode * — worker signs storage)", which contradicted the very next "Using external * signer: " line. State plainly that the worker signs storage. Exported * for unit testing. */ declare function formatTransferModeStorageSignerLine(workerAddress: string): string; /** * #983: the transfer-mode DotNS announcement, printed at preflight once ownership * is known. The up-front worker header states only the worker's storage role (it * can't know ownership yet); this line states the transfer-vs-owned-update reality: * New name: " DotNS: will register and transfer it to your account " * Already owned: " DotNS: you already own — content update needs your phone signature (no transfer)" * Exported for unit testing. */ declare function formatTransferModeDotnsLine(alreadyOwned: boolean, dotName: string, recipient: string): string; /** * Produce an actionable reason string for the pool-fallback warning + telemetry * attribute. BulletinSlotAuthError carries a typed reason; other errors * (WS/connection, still possible after withTransientRetry's bounded retries * are exhausted — see storage-signer.ts) use their message. Extracted from * selectStorageReconnect so it's unit-testable without a real WS connection * (#1058: pool fallbacks must always carry an explicit, visible reason). */ declare function describeSlotFallbackReason(e: unknown): string; /** * May a failed allowance slot fall back to the shared `//deploy/N` pool? * * Only off mainnet. The pool is derived from the well-known dev phrase, so on a mainnet chain it * holds no authorization and the fallback cannot succeed — it just trades a precise slot error * for an out-of-quota failure later in the upload, behind a warning that says the situation is * fine. Keyed on the resolved env's declared `network`, the same field ensureAuthorized uses to * pick its testnet-vs-mainnet wording; an env that declares nothing (custom presets, library * callers that never set it) keeps the historical fallback rather than newly hard-failing. */ declare function isPoolFallbackAllowed(network?: string): boolean; /** * Should this storeDirectoryV2 call use the incremental (chunk-dedup) * upload path and embed a .bulletin-deploy/manifest.json cache manifest? * * False for encrypted deploys (existing behavior: encryption breaks * chunk-level dedup) and for mainnet deploys (#1550): retention makes a * cache stale on arrival at mainnet's low deploy cadence, and a production * name should not carry build-cache artifacts. Exact `=== "mainnet"` check * on purpose — this is NOT the #1095 allowlist direction. An env-less * deploy (`network: undefined`, bare --rpc) or one whose resolved env * declares no network (the #1095 "unknown" sentinel) keeps the historical * incremental behavior; only an env that explicitly declares itself * mainnet loses the cache. */ declare function usesIncrementalCache(opts: { network?: string; password?: string; }): boolean; /** * The refusal itself, extracted so the mainnet branch is unit-testable without a WS connection * (same reasoning as describeSlotFallbackReason). Returns normally when the fallback is allowed; * throws on mainnet, carrying the slot's own failure reason so the message says what actually * went wrong rather than only that storage is unavailable. */ declare function assertPoolFallbackAllowed(network: string | undefined, reason: string): void; declare function selectStorageReconnect(options: DeployOptions): () => Promise; /** * Reconcile-before-resubmit (#1051), decided by chain evidence only (#1641, * #1656). Pure, no chain I/O. Three outcomes for a timed-out chunk tx: * - "included": its CID is present at the best block. The only signal that * counts as stored. * - "verify": the account's nonce moved past the chunk's nonce, but its CID * is not at best. The nonce was read from system_accountNextIndex, which * counts pool txs: the chunk's own pending tx, or a sibling deploy's tx on * the same signer (S9), moves it too. So this is a ROUTING signal, never * inclusion: the chunk is marked provisional and the post-batch verify * loop probes it, re-uploading at a fresh nonce if it is absent. Only * meaningful when `nonceHeuristicValid`, false after a pool account * rotation, where the old nonce belongs to another account (#951). * - "resubmit": neither. */ type ReconcileDecision = "included" | "verify" | "resubmit"; declare function reconcileTimedOutChunk(opts: { originalNonce: number | undefined; currentNonce: number; nonceHeuristicValid: boolean; cidPresentAtBest: boolean | null; }): ReconcileDecision; /** * Bounded nonce read for every Bulletin nonce a chunk/root tx is signed with * (#1641). system_accountNextIndex is pool-aware, which a shared signer needs * (S9: a second deploy must stack above the first one's pending txs), but a * load-balanced backend holding a stuck, never-gossiped run reports a future * nonce. The first read goes over the deploy's own connection (the backend its * txs are submitted to, so its pool view is the one that matters); re-reads * use fresh connections (a new backend pick). The floor is the on-chain nonce * at the BEST block: * - nextIndex at most `bound` (STUCK_NONCE_GAP_THRESHOLD, the #1640 in-flight * bound) above it is accepted; * - below it (a lagging backend) the on-chain nonce is used; * - further ahead is a stuck backend: re-read on a fresh connection (a new * backend pick), up to `reads` times, then sign from the on-chain nonce. * If the on-chain read itself fails, the nextIndex read is used unbounded, as * before #1641: correctness never rests on the nonce, every chunk is accepted * only on chain evidence, so the worst case is timeouts, not a false success. */ declare function readBoundedChunkNonce(deps: { readOnchainNonce: () => Promise; /** `attempt` is 1-based. */ readNextIndex: (attempt: number) => Promise; bound?: number; reads?: number; log?: (msg: string) => void; }): Promise; /** #1656/#1657: a chunk's on-chain presence could not be established at the best block. */ declare class ChunkInclusionUnverifiedError extends Error { constructor(message: string); } /** * Chain-liveness gate (#1051). Polls `getBestBlockNumber` every * CHUNK_LIVENESS_POLL_MS until height advances past `lastHeight`, or until * `timeoutMs` elapses. Returns the last-observed height either way — never * throws. `lastHeight === null` (couldn't determine a baseline) returns * immediately without waiting: there's nothing to compare against, so * waiting would just delay a resubmit decision for no benefit. A `null` * result from `getBestBlockNumber` mid-wait (RPC failure) also returns * immediately — fail open toward resubmitting rather than hanging on a dead * peer. */ declare function waitForChainLiveness(client: any, lastHeight: number | null, timeoutMs: number, pollMs?: number): Promise; /** Test-only alias — exported for unit tests that inject a short timeout/poll. */ declare const __waitForChainLivenessForTest: typeof waitForChainLiveness; declare function storeChunk(unsafeApi: any, signer: PolkadotSigner, chunkBytes: Uint8Array, nonce: number, opts?: { client?: () => any; }): Promise; /** Test-only alias — exported for a unit test that exercises the raw * watchTransaction error-capture path without the retry-loop machinery * around it (#1256). */ /** * `present === null` is "could not measure", not "absent". Re-upload is the * remedy for a chunk that is gone, not one we could not read (#1444). */ declare function partitionFinalityProbe(results: { cid: string; present: boolean | null; failureReason?: string; }[]): { absent: string[]; indeterminate: string[]; reason?: string; }; declare const __storeChunkForTest: typeof storeChunk; /** * Wraps a chunk-upload failure into a single Error whose message is bounded * to 100 chars (both retry-exhaustion producer sites below need this — the * inner chain error can be arbitrarily large), while carrying forward * `inner.chainErrorVariant` if the failure was captured (#1256). The * truncation can still cut the variant name out of the message text; the * point is that it no longer needs to survive there. */ declare function chunkFailureError(prefix: string, inner: any): ChainError; /** Test-only alias — exported so the truncate/propagate behaviour above can * be pinned directly without driving the full multi-attempt retry loop. */ declare const __chunkFailureErrorForTest: typeof chunkFailureError; declare function storeFile(contentBytes: Uint8Array, { client: existingClient, unsafeApi: existingApi, signer: existingSigner, hashCode, }?: ExistingProvider & { hashCode?: number; }): Promise; /** * Pre-compute dense nonces for chunks that need submission. * Chunks where stored[i] !== null are already on chain (skipped via skipCids or * prior reconnect logic) and consume zero nonce slots. * Exported under a test-only alias so unit tests can verify the dense property * without touching the real chain. */ declare function assignDenseNonces(stored: (StoredChunk | null)[], startNonce: number): Map; declare const __assignDenseNoncesForTest: typeof assignDenseNonces; declare function storeChunkedContent(chunks: Uint8Array[], { client: existingClient, unsafeApi: existingApi, signer: existingSigner, ss58: existingSS58, reconnect, fetchNonce: fetchNonceOverride, skipCids, probeFailedCids, gateway: providerGateway, trustedCids, skipRootStore, handOffLiveClient }?: ExistingProvider): Promise<{ storageCid: string; tier2Verified: number; tier2Inconclusive: number; tier2Fallback: number; liveProvider: ExistingProvider; skipProbeResults: Map; rootSkipped: boolean; }>; declare function chunk(data: Uint8Array, size?: number): Uint8Array[]; declare function hasIPFS(): boolean; declare function merkleize(directoryPath: string, outputCarPath: string): Promise<{ carPath: string; cid: string; }>; declare function computeStorageCid(chunks: Uint8Array[]): string; interface StoreDirectoryOptions { provider?: ExistingProvider; password?: string; jsMerkle?: boolean; /** * Resolved env network ("mainnet" | "testnet" | "unknown" | undefined — * mirrors envNetwork's normalization in deploy()). Exact `=== "mainnet"` * disables the incremental cache and embedded manifest (#1550); every * other value, including undefined and "unknown", keeps the historical * incremental behavior. See usesIncrementalCache. */ network?: string; /** * Fires exactly once, right after the CAR has been merkleized + encrypted * and the final storage CID is known, but BEFORE the chunk upload to * Bulletin starts. Use to kick off parallel side-effects that can run * concurrently with the slow upload. The returned promise is awaited at * the end of the deploy; errors are passed through to the caller so they * can decide fatal / non-fatal policy. */ onCarReady?: (carBytes: Uint8Array, storageCid: string) => Promise | void; /** * v2 incremental upload: contenthash from the previous deploy of this * domain (the IPFS CID, not the e3-prefixed bytes). The new flow fetches * this CID's embedded manifest via the gateway, classifies files, * probes chunks for presence, and skips re-uploading any chunk already * stored on chain. Pass null (or omit) for first-deploy behaviour. * Encrypted deploys (password set) bypass the incremental path because * encryption breaks chunk-level dedup. */ previousContenthash?: string | null; /** * Reason readPreviousContenthashSafe gave for a null previousContenthash * (e.g. "first_deploy", "non_ipfs_codec", "chain_read_failed: "). * Threaded through to fetchPreviousManifest so manifest_fetch_reason can be * populated on the "none" (no previous contenthash) path too, not only on * heuristic_fallback (#1382). Ignored when previousContenthash is non-null. */ previousContenthashReason?: string; /** Override gateway URL for manifest fetch + chunk probes. */ gateway?: string; /** Skip the 500 MiB abort guard and allow oversized deploys. */ allowLargeDeploy?: boolean; /** * Pin the `deployedAt` timestamp for byte-identical rebuilds. * Values: "commit" (git committer date), "epoch:" (Unix epoch seconds), * or any ISO 8601 string. Omit for a live wall-clock timestamp. */ reproducibleSource?: string; /** * DotNS domain label being deployed (without the `.dot` suffix, e.g. `"myapp"`). */ domain?: string; /** * Opt-in: write the pre-upload CAR file to disk after merkleization. * - `true` → write to `.bulletin.car` (default path). * - `string` → write to that explicit path. * - omitted / `false` → no file written (default). * Also honoured when `BULLETIN_DEPLOY_DUMP_CAR` env var is set (back-compat). */ dumpCar?: string | boolean; } declare function storeDirectory(directoryPath: string, providerOrOptions?: ExistingProvider | StoreDirectoryOptions, password?: string, jsMerkle?: boolean): Promise<{ storageCid: string; ipfsCid: string; carBytes: Uint8Array; }>; interface PreviousContenthashLookup { cid: string | null; reason?: string; } declare function readPreviousContenthashSafe(dotns: DotNS, bareLabel: string): Promise; declare function buildFilesMap(buildDir: string, fileCids?: Map, framework?: string | null): Record; declare function computeClassificationPrecision(fileCids: Map, filesMap: Record, prevManifest: EmbeddedManifest | null | undefined, sampleCap?: number): { filesStableGroundTruth: number; filesStablePacked: number; heuristicAgreeStable: number; heuristicAgreeVolatile: number; heuristicFalseVolatile: number; heuristicFalseStable: number; unhashedStableSample: string[]; }; declare function detectBuildMarkers(directoryPath: string): string[]; declare function detectFramework(directoryPath: string): string | null; type SizeDecision = { kind: "ok"; } | { kind: "warn"; message: string; } | { kind: "abort"; message: string; }; declare function checkDeploySize(carBytes: number, opts: { allowLargeDeploy?: boolean; }): SizeDecision; declare function resolveReproducibleTimestamp(source: string): string; declare function applyManifestFetchAttributes(fetched: { source: string; attempts?: number; bytesDownloaded?: number; }): void; declare function computePhaseACounts(uploadCids: string[], trustedCids: Set): { toCheck: number; trusted: number; duplicates: number; }; declare function formatUploadInclusionLine(totalChunks: number, uploadTotal: number): string; declare function storeDirectoryV2(directoryPath: string, opts?: StoreDirectoryOptions): Promise<{ storageCid: string; ipfsCid: string; carBytes: Uint8Array; }>; interface DeployOptions { mnemonic?: string; /** Optional derivation path applied to the mnemonic (e.g. "//deploy/3"). Defaults to "" (root key). */ derivationPath?: string; /** * Deploy as this product's derived account (RFC-0022 host derivation, * index 0) instead of the mnemonic's root account, so the deployed name is * owned by the account a host hands the product at runtime. Needs a * mnemonic; mutually exclusive with suri, derivationPath, and signer. * CLI: --product-name */ productName?: string; /** * Internal: the injected signer signs locally in-process, so no phone * ceremony gates its signatures. Set by the productName resolution; * genuine QR/mobile injected signers leave it unset. */ localSigner?: boolean; /** * Internal: `signer`/`signerAddress` were resolved from a phone-backed login * session (`resolveDeployActors`' `actors.worker.source === "session"`), as * opposed to a local `--suri`/`--mnemonic`-derived worker or a caller-injected * `PolkadotSigner` (programmatic callers, e.g. playground-cli — #811/S7). * Set by `deployActorsToSignerOptions`. Storage routing must not silently * sign Bulletin chunks with this signer when no allowance slot is * available: `resolveStorageSigner` returning null documents "→ pool", and * a session signer with no slot must actually honor that. See #1452. */ sessionSigner?: boolean; /** Pre-built signer — skips mnemonic derivation. Use for QR/mobile signing. */ signer?: PolkadotSigner; /** SS58 address for the signer (required when signer is provided). */ signerAddress?: string; /** Slot-account signer for Bulletin chunk uploads. When set, used instead of pool/mnemonic * for storage. DotNS still uses signer/signerAddress. */ storageSigner?: PolkadotSigner; /** SS58 address of the slot account. Required when storageSigner is set. */ storageSignerAddress?: string; /** Secret URI for dev signers (e.g. "//Alice" or a BIP-39 mnemonic). Passed to resolveSigner. */ suri?: string; /** When signed in, deploy with a local worker signer and transfer the finished * name to the signed-in account (zero mobile signatures). Default true. * CLI: --no-transfer-to-signedin-user sets this false. */ transferToSignedInUser?: boolean; /** Internal: recipient H160 for the post-deploy handover. Set by the resolve * branch; callers normally let it be derived. */ transferTo?: string; rpc?: string; poolSize?: number; password?: string; /** Use pure-JS merkleization instead of Kubo CLI. Required for WebContainer environments. */ jsMerkle?: boolean; /** * Free-form label attached to the deploy span as `deploy.tag`. Used to separate * test/benchmark/canary runs from real-user traffic in Sentry dashboards * (e.g. "e2e-ci-pr", "load-test-a"). Falls back to DEPLOY_TAG env var. */ tag?: string; /** Custom telemetry attributes, merged into the deploy span. Overrides auto-detected values. */ attributes?: Record; /** Skip the 500 MiB abort guard and allow oversized deploys. */ allowLargeDeploy?: boolean; /** * Filesystem path to a pre-built `.car` file. When set, skips directory * scanning and merkleization; the CAR bytes are read from disk, the root * CID is parsed from the CAR header, and the file is uploaded directly. * The positional `` argument is not required when this is set. */ inputCar?: string; /** * Pin the `deployedAt` timestamp for byte-identical rebuilds. * Values: "commit" (git committer date), "epoch:" (Unix epoch seconds), * or any ISO 8601 string. Omit for a live wall-clock timestamp. */ reproducibleSource?: string; /** * Environment id from environments.json (e.g. "paseo-next-v2", "paseo-review"). * Drives both the bulletin RPC and the asset-hub RPC. Defaults to * DEFAULT_ENV_ID. `--rpc` / BULLETIN_RPC still override the bulletin endpoint * within the chosen env. */ env?: string; /** * Pre-resolved bulletin endpoints (escape hatch for tests / library callers * that want to skip environments.json loading). When provided, the loader * is not called and `env` is ignored. */ bulletinEndpoints?: string[]; /** Pre-resolved asset-hub endpoints. Same escape-hatch semantics. */ assetHubEndpoints?: string[]; /** * Opt-in: write the pre-upload CAR file to disk after merkleization. * - `true` → write to `.bulletin.car` (default path). * - `string` → write to that explicit path. * - omitted / `false` → no file written (default). * Also honoured when `BULLETIN_DEPLOY_DUMP_CAR` env var is set (back-compat). * CLI: --dump-car[=] */ dumpCar?: string | boolean; /** * Override/supply DotNS contract addresses, shallow-merged OVER the chosen * env's `contracts` map (these win). The `custom` env ships no addresses, so * this is how they are provided. Keys are the DOTNS_* names used in * environments.json (e.g. DOTNS_REGISTRY, DOTNS_CONTENT_RESOLVER). * CLI: --contract =<0xADDRESS> (repeatable). */ contracts?: Record; /** * Plan of phone signatures this deploy will need. Fired once, at preflight, * BEFORE storage. Notification only; used by the CLI bin to print the * "Have your phone ready" banner up front. */ onPhoneSignaturePlan?: (steps: PhoneSignatureStep[]) => void; /** * Human-ready gate. Awaited immediately BEFORE each phone signature request * is sent. Resolve when the human is at their phone and ready; reject/throw * to abort. The per-signature operation timeout starts only AFTER this * resolves. `attempt` >= 2 means a re-sign. * Absent + non-TTY → fail fast (NonRetryableError). * Absent + TTY → CLI bin must supply the hook; core does not readline. * * `approvalBudgetMs` and `reason` (#194): widened to match dotns.ts's * ConnectOptions.confirmPhoneReady, which this field is passed straight * through to unchanged (see resolveDotnsConnectOptions call sites below). * `approvalBudgetMs` discloses the phone-approval silence deadline so the * CLI prompt never hardcodes a number that can drift from the constant that * actually governs it. `reason: "silence"` marks a watcher-silence re-arm * (re-prompt after no response) as distinct from the pre-existing re-sign * case (undefined/"resign"). */ confirmPhoneReady?: (ctx: { label: string; attempt: number; total: number; approvalBudgetMs: number; reason?: "resign" | "silence"; }) => Promise; /** * #1164: when set, the post-deploy console banner prints "CONTENT DEPLOYED * — publishing product manifest…" instead of "DEPLOYMENT COMPLETE!", and * the "Check it out here" browser-URL block is suppressed. The caller * (bin/bulletin-deploy) is then responsible for printing the real * completion banner itself, via `printDeploymentCompleteBanner`, once its * own subsequent `publishManifest()` call succeeds. Opt-in: every existing * library caller (e.g. playground-cli) leaves this unset and keeps today's * single-banner output. */ manifestPending?: boolean; } declare function resolveProductSigner(options: Pick): Pick | null; declare function resolveDotnsConnectOptions(options: Pick, assetHubEndpoints?: string[], autoAccountMapping?: boolean, contracts?: Record, nativeToEthRatio?: bigint, environmentId?: string, popSelfServe?: PopSelfServeConfig | null, registerStorageDeposit?: bigint, tld?: string, network?: string, contractSources?: Record): Pick; declare function autoMapPlannedActionFor(dotnsPreflight: DotnsPreflightResult | null): DotnsSuccessAction; declare function estimateUploadBytes(content: DeployContent): Promise; /** * Throws NonRetryableError if a subdomain is owned by a different address * than the current signer. Called in the preflight branch before chunk upload. * Issue #562: preflight was only checking `owned`, not comparing `owner`. */ declare function assertSubdomainOwnerMatchesSigner(result: { owned: boolean; owner: string | null | undefined; }, signerEvmAddress: string | null | undefined, sublabel: string, parentLabel: string, tld?: string): void; /** * Issue #1185: an unregistered, non-registrable parent (a governance-reserved * name) used to yield "parent game.dot is owned by no one, not by this * signer" — awkward, and silent about the only route forward. When the * parent is non-registrable per classifyRegistrability AND unowned, this * additionally teaches the dotns-cli whitelisted-registration route, via the * SAME formatUnregistrableReason preflight/register() use, so the texts * cannot drift. * * Issue #1062: the two remaining branches used to name the problem but not * the remedy — recurring on paritytech/coin-flip's per-branch preview * deploys (~13 occurrences over 3 weeks), each one only discovered after * connect/build/upload had already run. Both branches now name a concrete * next step: * - unregistered, registrable parent: deploying the parent directly IS the * registration path (bulletin-deploy has no separate register-only * command — see bin/bulletin-deploy's ` ` usage), * so the remedy is that same command aimed at the parent. * - owned by another account: this signer cannot self-serve. The only * route is the current owner handing the name over. Verified against * src/commands/transfer.ts: `runTransfer` always signs as * `opts.mnemonic ?? DEFAULT_MNEMONIC` (Alice's dev key) — never from a * session — so the hint MUST include --mnemonic for the owner's own * key, or run verbatim it connects as Alice and fails with "it is owned * by ..., not the worker " (DotNS.transferName). Only * --to may default (to the signed-in session). selfAddress can be "" * (the call site passes `preflight.evmAddress ?? ""`), so the transfer * hint is only emitted when it's non-empty — a dangling `--to ` is worse * than no hint. * Neither addition weakens the refusal: both still throw, they only add an * actionable line after the unchanged "is owned by ..." sentence that * telemetry's naming.subdomain_orphan classifier keys on (src/telemetry.ts). */ declare function formatSubdomainParentError(fullName: string, parentLabel: string, parentOwner: string | null, selfAddress: string, tld: string, profile: DotnsAbiProfile): string; /** * Returns the browser URL for the given domain name, optionally suffixed * with a network query parameter so the SPA opens the right chain. * Currently only the "preview" env needs a suffix — the SPA defaults to * paseo-next-v2 which would show "no content" for preview deployments. * * The gateway host defaults to "dot.li" (issue #142: devnet-family names are * NOT resolvable via dot.li — they're served by a different gateway, e.g. * "dev-dot.li" — so callers must pass the resolved env's `webGateway` when * one is set; otherwise the link loads but resolves the name against the * wrong network). * @param name - the DotNS label (e.g. "myapp") * @param envId - the environment id from options.env ?? DEFAULT_ENV_ID * @param webGateway - the resolved env's `webGateway`, if any (defaults to "dot.li") */ declare function browserUrlFor(name: string, envId: string | undefined, webGateway?: string): string; type BitswapErrorVariant = "none" | "not_found" | "timeout" | "error"; interface BitswapProbeResult { retrievable: boolean; errorVariant: BitswapErrorVariant; durationMs: number; } /** * Pure classifier — maps a raw response or thrown error to {retrievable, errorVariant}. * Exported for unit tests; does NOT touch telemetry or console. */ declare function interpretBitswapResult(outcome: { ok: true; response: unknown; } | { ok: false; error: unknown; }): { retrievable: boolean; errorVariant: BitswapErrorVariant; }; /** * Calls bitswap_v1_get on the bulletin RPC client for the given base32 CIDv1 string. * Never throws — wraps every outcome in BitswapProbeResult. * @param client - polkadot-api client (ProviderResult.client) * @param cid - base32 CIDv1 string (e.g. "bafyrei...") * @param timeoutMs - safety ceiling; the RPC typically responds in ~600ms */ declare function probeP2pRetrieval(client: any, cid: string, timeoutMs?: number): Promise; declare function deploy(content: DeployContent, domainName?: string | null, options?: DeployOptions): Promise; /** * Compute the ordered list of step labels that will require a phone tap, * given the DotNS preflight result. * Returns [] when deploy would abort or preflight is null. * Exported for unit testing. */ declare function computePhoneSigningSteps(dotnsPreflight: { plannedAction: string; needsPopUpgrade: boolean; } | null): string[]; export { BLAKE2B_256_MULTIHASH_CODE, BULLETIN_ENDPOINTS, type BitswapErrorVariant, type BitswapProbeResult, CHUNK_MORTALITY_PERIOD, type ChainError, ChunkInclusionUnverifiedError, DEFAULT_BULLETIN_RPC, DEFAULT_POOL_SIZE, type DeployContent, type DeployOptions, type DeployResult, ENCRYPT_KEY_LEN, ENCRYPT_MAGIC, ENCRYPT_NONCE_LEN, ENCRYPT_PBKDF2_ITERATIONS, ENCRYPT_SALT_LEN, ENCRYPT_TAG_LEN, NonRetryableError, PhoneSignatureStep, type PreviousContenthashLookup, type ReconcileDecision, SHA256_MULTIHASH_CODE, type SizeDecision, type StoreDirectoryOptions, WS_HEARTBEAT_TIMEOUT_MS, __assignDenseNoncesForTest, __chunkFailureErrorForTest, __getBulletinAuthorizationContextForTest, __resetQuotaExhaustedDimsSeenForTest, __selectStorageProviderModeForTest, __setNonceCollisionBackoffForTest, __storeChunkForTest, __waitForChainLivenessForTest, applyManifestFetchAttributes, assertPoolFallbackAllowed, assertSubdomainOwnerMatchesSigner, autoMapPlannedActionFor, browserUrlFor, buildFilesMap, checkDeploySize, chooseSignerInput, chunk, classifyRootSubmitError, computeClassificationPrecision, computePhaseACounts, computePhoneSigningSteps, computeStorageCid, createCID, deploy, deployActorsToSignerOptions, deriveRootSigner, describeSlotFallbackReason, detectBuildMarkers, detectFramework, encodeContenthash, encryptContent, estimateUploadBytes, extractInvalidTransactionVariant, formatStorageSignerLine, formatSubdomainParentError, formatTransferModeDotnsLine, formatTransferModeStorageSignerLine, formatUploadInclusionLine, friendlyChainError, hasIPFS, interpretBitswapResult, isBenignTeardownError, isConnectionError, isNonceCollisionError, isPhoneSignerActive, isPoolFallbackAllowed, makeBulletinStatusHandler, merkleize, nonInteractivePhoneConfirmationError, partitionFinalityProbe, pickPostDeployBannerText, printDeploymentCompleteBanner, probeP2pRetrieval, readBoundedChunkNonce, readPreviousContenthashSafe, reconcileTimedOutChunk, resolveBulletinEndpoints, resolveDotnsConnectOptions, resolveEnvId, resolveProductSigner, resolveReproducibleTimestamp, retryBudgetExhausted, selectStorageReconnect, setBulletinAuthorizationContext, setBulletinEndpoints, setWsHaltCallback, shouldHandoverName, shouldPublishManifest, storeChunkedContent, storeDirectory, storeDirectoryV2, storeFile, usesIncrementalCache };