import { WsEvent } from 'polkadot-api/ws'; import { CID } from 'multiformats/cid'; import { ManifestFileEntry } from './manifest.js'; import { PhoneSignatureStep, 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'; 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; } 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; } interface ChainReceipt { txHash: string; blockHash: string; blockNumber: number; } interface StoredChunk { cid: CID; len: number; viaFallback?: boolean; receipt?: ChainReceipt; } interface WatchTransactionOptions { label?: string; rpc?: string | string[]; senderSS58?: string; expectedNonce?: number; timeoutMs?: number; fetchNonce?: (rpc: string | string[], ss58: string) => Promise; } 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; 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; 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 > mnemonic > pool precedence for storage routing. Exported for unit testing. */ declare function __selectStorageProviderModeForTest(options: Pick): "storageSigner" | "signer" | "direct" | "pool"; /** * Resolve the mnemonic the CLI should act with, in precedence order: * `--mnemonic` flag > `MNEMONIC` env var > `DOTNS_MNEMONIC` env var. * * Exists so the bin's flag/env resolution is unit-testable and so the two * env vars are forwarded consistently (issue #1107: previously the bin only * forwarded `flags.mnemonic` into `options.mnemonic`, so an env-only mnemonic * never reached `chooseSignerInput` and a persisted session silently won * instead — even though `chooseSignerInput` already prefers mnemonic first). */ declare function resolveEffectiveMnemonic(opts: { flagMnemonic: string | undefined; envMnemonic: string | undefined; envDotnsMnemonic: string | undefined; }): string | undefined; /** * 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; /** * 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; /** * Reconcile-before-resubmit (#1051). Pure decision function — no chain I/O — * so it's directly unit-testable. Decides whether a timed-out chunk tx * should be treated as already included (skip the resubmit, avoid a * duplicate content write) based on two independent signals: * - nonce advance: the account's nonce moved past the chunk's assigned * nonce. Only meaningful when `nonceHeuristicValid` — false after a pool * account rotation, where the old nonce baseline belongs to a different * account (#951). * - CID presence at best-block: a direct probe of the chunk's own content * hash, independent of account/nonce bookkeeping entirely. Catches * inclusion the nonce heuristic can miss (e.g. the endpoint used for the * nonce fetch is briefly behind a peer that already saw the tx land). * Either signal alone is sufficient. */ declare function reconcileTimedOutChunk(opts: { originalNonce: number | undefined; currentNonce: number; nonceHeuristicValid: boolean; cidPresentAtBest: boolean | null; }): boolean; /** * 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, ss58: string, opts?: { fetchNonce?: WatchTransactionOptions["fetchNonce"]; }): 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 }?: 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; /** * 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; /** 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; }>; declare function buildFilesMap(buildDir: string, fileCids?: Map): Record; 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 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; /** 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; } 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): Pick; 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, 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, SHA256_MULTIHASH_CODE, type SizeDecision, type StoreDirectoryOptions, WS_HEARTBEAT_TIMEOUT_MS, __assignDenseNoncesForTest, __chunkFailureErrorForTest, __selectStorageProviderModeForTest, __storeChunkForTest, __waitForChainLivenessForTest, applyManifestFetchAttributes, assertSubdomainOwnerMatchesSigner, browserUrlFor, buildFilesMap, checkDeploySize, chooseSignerInput, chunk, computePhoneSigningSteps, computeStorageCid, createCID, deploy, deriveRootSigner, describeSlotFallbackReason, detectFramework, encodeContenthash, encryptContent, estimateUploadBytes, extractInvalidTransactionVariant, formatStorageSignerLine, formatSubdomainParentError, formatTransferModeDotnsLine, formatTransferModeStorageSignerLine, friendlyChainError, hasIPFS, interpretBitswapResult, isBenignTeardownError, isConnectionError, isPhoneSignerActive, makeBulletinStatusHandler, merkleize, nonInteractivePhoneConfirmationError, partitionFinalityProbe, probeP2pRetrieval, reconcileTimedOutChunk, resolveBulletinEndpoints, resolveDotnsConnectOptions, resolveEffectiveMnemonic, resolveEnvId, resolveProductSigner, resolveReproducibleTimestamp, retryBudgetExhausted, setBulletinEndpoints, setWsHaltCallback, shouldHandoverName, shouldPublishManifest, storeChunkedContent, storeDirectory, storeDirectoryV2, storeFile };