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.jsonthrough[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 ofcontracts/Published.toml. Only that section is read; another environment's IDs are never used. A mismatch means the network reset.
- Upgrade only:
- the
published-atpackage exists at the recorded version; - the
UpgradeCapnames that package and version and is owned by the active account; - the recorded deployment (
deployments/ENV/current) agrees withPublished.toml.
- the
- Refusals:
- A recorded deployment without a
Published.tomlsection is refused, because a fresh publish would orphan its fleet. - An unrecorded upgrade needs
--registry IDto adopt the existing registry.
- A recorded deployment without a
- Release plan (
--plan PLAN.json, SF-154): the reviewed plan fromscripts/release_plan.pymust 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
successstop the run. Nothing is written. - The publish must create:
- exactly one package;
- one
UpgradeCapowned by the account; - one shared
DeviceRegistryof the new package.
- An upgrade must leave the same
UpgradeCapat version + 1.
Recording a release
- Pending record. As soon as a transaction executes, it is saved as
deployments/ENV/pending-DIGEST.json. - Verify.
Published.tomlmust now record it. The manifest is then resolved from the chain and verified (type origin table,UpgradeCap, registry). - Write the release. It is written to
deployments/ENV/releases/vN-PACKAGE/:deployment.jsonfrontend.env- the device
config.tomltemplate ([sui]plus the device sections ofsuifleet-edge/config.example.toml, or--device-template) transaction.json(digest, effects status and gas used) and, when one admitted it,release-plan.json
- Switch both clients together. The new
dapp/.env.localis written in full beside the old one first: it getsNEXT_PUBLIC_DEPLOYMENT_MANIFEST, the legacyNEXT_PUBLIC_*_ID/NETWORKlines are dropped, and every other line is kept. Next, a synced journaldeployments/ENV/activating.jsonnames the release. Then two renames activatedeployments/ENV/currentand.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.shfinishes the switch first, rolling forward to the journaled release; the release was complete and verified before the journal was written.scripts/redeploy.sh --reconciledoes 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-runandmake dapp-buildrefuse, so a half-switched.env.localis never built.
- Recover. If any step after execution fails, the previous
currentand.env.localstay in place. The error checks this and says so, or warns if either changed. It names the pending file. Fix the cause, then runscripts/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:
resolvebuilds a manifest from a package, UpgradeCap and registry.verifychecks one against the chain.generatewrites 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