Deployment manifests and publication

One manifest describes one deployment on one network. Both clients read it:

  • the frontend reads it from NEXT_PUBLIC_DEPLOYMENT_MANIFEST, compiled into the static build;
  • the daemon reads deployment.json through [sui] manifest_path.

Each client checks the manifest against the chain at startup and refuses a stale or mismatched one.

Contents

Field Meaning
schema_version, protocol_version Both 1. Clients reject other values.
network, chain_identifier localnet/devnet/testnet/mainnet, plus the first four bytes of the genesis digest (8 hex). testnet must be 4c78adac and mainnet 35834a8a.
rpc_url Legacy/default Sui fullnode serving gRPC v2.
rpc_urls Optional ordered daemon endpoint list (1–8 URLs when populated); empty/omitted uses rpc_url. Local [sui] rpc_urls overrides it. Every candidate must match chain_identifier.
call_package_id, package_version The latest package. Every call goes here.
upgrade_cap_id Must still name call_package_id at package_version. After a further upgrade, every client rejects this manifest as stale.
type_origins For each type the clients use, the package that introduced it. Types keep their introduction package across upgrades, so newer types such as RecipeDigestKey and DeviceAddressKey have later origins than DeviceRegistry.
shared_objects.registry The shared DeviceRegistry ID and its initial shared version.
supported_clients Client version ranges, as min ≤ version < max_exclusive.
treasury_payee Optional canonical nonzero 32-byte lowercase Sui treasury address. Required by payment callers; absent or mismatched payees block payment. See treasury custody.
walrus Publisher and legacy aggregator URL, plus optional ordered device aggregator_urls; see public delivery.

Optional type-origin groups are all-or-none: the component catalog, the Seal policy, the SF-083 fleet budget (fleet_budget::*), the SF-125 device inboxes and member index (fleet_inbox, DEVICE_INBOX.md) and the SF-130 fleet settings (fleet_settings::*). A device template may add a [budget] section (FLEET_BUDGET.md); its fleet's settings then apply (FLEET_SETTINGS.md).

manifests/fixture.localnet.json is a test fixture, not a deployment.

Publishing: scripts/redeploy.sh (make publish-contracts)

scripts/redeploy.sh publishes or upgrades on the active Sui CLI environment and spends gas. With --dry-run it only simulates. It builds the gRPC chain reader (suifleet-edge/examples/chain_facts.rs), then runs scripts/publish.py.

# In the dev container (make shell), with the CLI on the intended environment and account:
scripts/redeploy.sh --expect-address 0xYOUR_ADDRESS --dry-run
scripts/redeploy.sh --expect-address 0xYOUR_ADDRESS
# or: make publish-contracts PUBLISH_ARGS="--expect-address 0xYOUR_ADDRESS --dry-run"

Before any transaction, it stops unless all of these hold

All chain reads go over gRPC.

  • Account: the active account is --expect-address.
  • Chain identifier (the live one) matches:
    • contracts/Move.toml's [environments] entry for this environment. testnet and mainnet are built in; a devnet or local chain must be declared there with its current identifier.
    • the pinned network label: testnet 4c78adac; mainnet is refused outright, because this is not a production pipeline.
    • this environment's [published.ENV] section of contracts/Published.toml. Only that section is read; another environment's IDs are never used. A mismatch means the network reset.
  • Upgrade only:
    • the published-at package exists at the recorded version;
    • the UpgradeCap names that package and version and is owned by the active account;
    • the recorded deployment (deployments/ENV/current) agrees with Published.toml.
  • Refusals:
    • A recorded deployment without a Published.toml section is refused, because a fresh publish would orphan its fleet.
    • An unrecorded upgrade needs --registry ID to adopt the existing registry.
  • Release plan (--plan PLAN.json, SF-154): the reviewed plan from scripts/release_plan.py must describe this exact transaction: network, chain, account, action, deployment, contract sources and gas budget. It must also be intact, ready and unexpired. Testnet publication requires one; see RELEASE_PLANNING.md.

Transaction and failure handling

  • A failed CLI command, unparseable output, or execution effects other than success stop the run. Nothing is written.
  • The publish must create:
    • exactly one package;
    • one UpgradeCap owned by the account;
    • one shared DeviceRegistry of the new package.
  • An upgrade must leave the same UpgradeCap at version + 1.

Recording a release

  1. Pending record. As soon as a transaction executes, it is saved as deployments/ENV/pending-DIGEST.json.
  2. Verify. Published.toml must now record it. The manifest is then resolved from the chain and verified (type origin table, UpgradeCap, registry).
  3. Write the release. It is written to deployments/ENV/releases/vN-PACKAGE/:
    • deployment.json
    • frontend.env
    • the device config.toml template ([sui] plus the device sections of suifleet-edge/config.example.toml, or --device-template)
    • transaction.json (digest, effects status and gas used) and, when one admitted it, release-plan.json
  4. Switch both clients together. The new dapp/.env.local is written in full beside the old one first: it gets NEXT_PUBLIC_DEPLOYMENT_MANIFEST, the legacy NEXT_PUBLIC_*_ID/NETWORK lines are dropped, and every other line is kept. Next, a synced journal deployments/ENV/activating.json names the release. Then two renames activate deployments/ENV/current and .env.local, and the journal is removed.
    • If the second rename raises an error, the first is undone, and the undo is synced before the journal is removed.
    • If both renames succeed but syncing them fails, nothing is undone and the journal stays, so the switch is finished as below.
    • If the process is killed between them, the journal stays. The next run of scripts/redeploy.sh finishes the switch first, rolling forward to the journaled release; the release was complete and verified before the journal was written. scripts/redeploy.sh --reconcile does only that and sends no transaction.
    • If finishing a journaled switch fails at any step, the journal stays, so the next run tries again.
    • While a journal exists, --dry-run and make dapp-build refuse, so a half-switched .env.local is never built.
  5. Recover. If any step after execution fails, the previous current and .env.local stay in place. The error checks this and says so, or warns if either changed. It names the pending file. Fix the cause, then run scripts/redeploy.sh --recover deployments/ENV/pending-DIGEST.json. It finishes recording without another transaction.

Earlier releases stay under releases/. Once superseded, every client rejects them as stale.

Commit contracts/Published.toml and deployments/ENV/ together after a public-network release: they are the record of every package, UpgradeCap, registry and type origin. Then rebuild the frontend, because NEXT_PUBLIC_* values are compiled in. Install deployment.json with each device's own config.toml. The template's device IDs and key path are placeholders.

Options: --network devnet|localnet labels a non-public chain; --publisher/--aggregator (default: the previous release, or Walrus testnet); --gas-budget (default 0.5 SUI; a fresh publish costs about 0.07 SUI); --package, --records, --frontend-env '' (skip), --client-config, --reconcile (only finish an interrupted activation).

Resolving and verifying by hand

scripts/deployment_manifest.py never spends gas:

  • resolve builds a manifest from a package, UpgradeCap and registry.
  • verify checks one against the chain.
  • generate writes the release bundle.

All three read the chain through chain_facts. Build it first:

cargo build --locked --manifest-path suifleet-edge/Cargo.toml --example chain_facts

Tests

Command Covers
make test-publish Offline, using a fake CLI and chain. The paths covered:<br>• metadata scoped to its environment<br>• account, chain and UpgradeCap checks<br>• failed, non-zero and garbled transactions<br>• post-execution failure with prior configuration kept, then --recover<br>• a process killed during activation, finished by --reconcile or the next run<br>• dry run, adoption and the strict TOML subset
make test-publish-localnet Real localnet, real contracts and CLI:<br>• wrong account refused<br>• dry run writes nothing<br>• fresh publish and upgrade, each verified by the daemon (--check-config) and the frontend SDK<br>• the superseded release rejected as stale<br>• incompatible upgrade leaves everything unchanged<br>• reset-chain metadata refused
make test-manifest, make test-manifest-live Manifest validation, and origins across real upgrades

A testnet publish and upgrade has not been run by these tests. It spends real gas, needs the release account, and must be run explicitly by the operator. Record the transaction digests and the resulting manifest in the release issue.

The optional all-or-none receipt_shards type group enables SF-127. Its directory, root, receipt/index keys and relocated group/catalog keys retain their own introduction origins. Resolved manifests containing it require dApp >=0.1.1 and edge >=0.2.2 within their existing major compatibility ranges. Existing manifests without the group retain v2 behavior; see sharded receipts.

The optional all-or-none recurring_window type group (RecurringWindowKey, RecurringWindow, WeeklySlot; SF-174) enables recurring weekly maintenance windows. Daemons before 0.2.3 ignore the field, so resolved manifests containing it require edge >=0.2.3. Without it, clients neither read nor write the field; see OPERATIONS.md.

Treasury payment configuration and user-only multisig/SuiNS operations are documented in TREASURY.md. resolve --treasury-payee ADDRESS and publication --treasury-payee ADDRESS pin a reviewed address; publication preserves the previous payee when omitted, including recovery. Missing payees preserve legacy nonpayment use, while the frontend payment guard fails closed.

The optional group_batch type group (customer::GroupBatchApplied, SF-171) tells clients the package has the bounded batch membership entry points. Like fleet_handover, it is a single-type group recorded with its own introduction origin; manifests without it keep the single-device calls. See bulk group membership.

Ordered browser storage endpoints (SF-166)

walrus.publisher_urls and walrus.aggregator_urls optionally specify nonempty ordered URL lists. The browser uses the publisher list instead of the legacy publisher_url, and tries the aggregator list before the legacy aggregator_url. Retain the legacy fields for older clients. Publisher HTTP 5xx, network errors and timeouts try the next endpoint with the same protected bytes. Rejected requests and malformed success responses stop publication for configured publishers; session overrides fall through to the remaining endpoints. Cancellation always stops publication. Fleet-paid SDK uploads never fall back to public publishers. Successful uploads record StorageInfo.publisherUrl; browser reads record the serving aggregator in blobReadEndpoints. Read probes try all aggregators before reporting unavailable/unknown; download links use the last successful aggregator or the first configured one. Archive readers use the same failover while retaining their size, digest and authority checks.

Release commands accept repeated --publisher-mirror options after --publisher. Upgrades and interrupted-release recovery preserve the ordered publisher list. Repeated --aggregator-mirror options continue to set the complete read list. Fleet settings → Storage endpoints lets an operator add an HTTPS publisher for the current tab session, including reloads, without rebuilding. Overrides are scoped by network and configured publisher, saved in session storage and tried first. HTTP is permitted only for loopback development. Public upload bytes are visible to that service; confidential bytes remain encrypted before retry. This setting changes this browser, not device configuration or other operators' consoles. Publisher errors give an action and endpoint; raw responses appear only under Technical details. When fleet storage is available, component publication selects it automatically; an explicit operator choice can still select public publishing.

Committed public profiles

node scripts/check_profiles.cjs verifies every profiles/*/deployment.json against fresh gRPC chain identity, package/version/type origins, UpgradeCap and registry facts using the console's verifier. It also requires frontend.env to contain exactly the same manifest. Install locked dApp dependencies first. The read-only Deployment profiles workflow runs on PRs, main pushes and every six hours; it uses no wallet or transaction credentials. A stale profile fails with its path and regeneration instructions. Network failures also fail the check.

Any workflow or operator upgrading a shared testnet package must regenerate and commit its profile's deployment.json and frontend.env in the same change, preserving introduction-package type origins and the existing shared registry. Run the checker before rebuilding the console. If publication cannot be included, record the reason, transaction digest, latest package/version and recovery action in the existing issue/PR; never describe the old profile as usable. Disposable CI package-upgrade fixtures are not shared console deployments and do not update committed profiles. The SF-173 evaluation upgrade is the shared deployment case: its v2 metadata was extracted into SF-186 independently of the evaluation fleet.

This check establishes read-only deployment compatibility, not manual wallet, production Seal-provider or hardware acceptance (retained in #32 and #159).

For a public account read smoke test (no signer), run node scripts/verify_profile_console.cjs profiles/seal-testnet 0xPUBLIC_OWNER_ADDRESS. It runs the console's inventory, library, deployment and enrollment queries plus a balance read after deployment verification. This does not simulate a browser wallet connection or authorize writes.

Subscription coins (SF-181)

The optional subscription_coins object contains USDC and optionally SUI, each with coin_type (full introduction-origin type tag) and integer decimals (6 or 9). Its presence requires treasury_payee. Public testnet/mainnet USDC must match Circle's canonical origin; local fixtures may use an isolated test coin. The optional all-or-none subscription type-origin group records customer::SubscriptionKey, subscription::Offer, Account, Deposited, Collected, PlanChanged and Refunded with their own introduction origins. Existing Customer origins and layouts remain unchanged.

resolve --subscription-coins '{"USDC":{"coin_type":"FULL_ORIGIN::usdc::USDC","decimals":6}}' accepts a reviewed descriptor alongside --treasury-payee. Publication accepts the same flag, preserves previous coin descriptors when omitted, and journals them for recovery. Replace the example's FULL_ORIGIN with the verified canonical address; the placeholder is intentionally invalid. Payment callers use the manifest payee and exact coin guard, never a symbol, URL or mutable SuiNS answer. See subscription custody and Payment Kit evaluation.

Source: Edit this page