Mysten Incubation
Features

Live Networks

Running the same stack shape against public networks.

Pick a Sui mode explicitly in the config. sui() is the local shorthand; public networks use sui({ mode: 'live', network }).

import {
	defineDevstack,
	sui,
	account,
	HOST_SERVICE_PORT_TOKEN,
	hostService,
	knownPackage,
	wallet,
} from '@mysten-incubation/devstack';
import { Ed25519Keypair } from '@mysten/sui/keypairs/ed25519';

const DEV_PORT = 5173;

const alice = account('alice', {
	kind: 'signer',
	signer: Ed25519Keypair.fromSecretKey(process.env.ALICE_PRIVATE_KEY!),
});

const registry = knownPackage('registry', {
	packageId: '0x...',
});
const liveSui = sui({ mode: 'live', network: 'testnet' });
const devWallet = wallet({
	accounts: [alice],
});
const app = hostService({
	name: 'app',
	script: `pnpm exec vite --host 127.0.0.1 --strictPort --port ${HOST_SERVICE_PORT_TOKEN}`,
	port: DEV_PORT,
	ready: { kind: 'http' },
	after: [registry, devWallet] as const,
});

export default defineDevstack({
	members: [liveSui, app],
	stackName: 'main',
});

The CLI can also select the surface network for mode-narrowed configs:

devstack up --network testnet
devstack up --network mainnet
devstack up --network testnet-fork

Fork mode runs a local sui-fork container against an upstream public network, serving a gRPC endpoint (not legacy JSON-RPC) on the same devstack Sui RPC route. It does not expose a GraphQL endpoint, and the balance APIs (getBalance / listBalances / getCoinInfo) intentionally panic in fork mode — read balances through ChainProbe, and fund accounts through the fork faucet (below).

First boot compiles sui-fork from a pinned Sui revision — a one-time, multi-minute build that the supervisor row narrates so it doesn't look hung; the content-addressed image is reused on later boots. To skip the build entirely, point at a prebuilt image with image: { pull: '…' } or set the DEVSTACK_SUI_FORK_IMAGE environment variable.

If a slow first build or a large upstream checkpoint trips the ready probe, raise readyTimeout (a sui() option, Duration; default 180s) or inspect the sui-fork container logs with docker logs <container> — the ready-probe timeout message points at both.

import { defineDevstack, sui, account, localPackage } from '@mysten-incubation/devstack';

const publisherAddress = process.env.PUBLISHER_ADDRESS!;
const aliceAddress = process.env.ALICE_ADDRESS!;

const forkedTestnet = sui({
	mode: 'fork',
	upstream: 'testnet',
	seed: { addresses: [publisherAddress, aliceAddress] },
});
const publisher = account('publisher', {
	kind: 'impersonate',
	address: publisherAddress,
});
const alice = account('alice', {
	kind: 'impersonate',
	address: aliceAddress,
});

const pkg = localPackage('demo', {
	sourcePath: './move/demo',
	publisher,
});

export default defineDevstack({
	members: [forkedTestnet, pkg],
	stackName: 'fork-demo',
});

kind: 'impersonate' accounts submit empty-signature transactions through the fork admin surface, which is useful for deterministic local replay, but no private key exists and direct signing APIs intentionally fail.

Fork mode is for replaying against real upstream state while keeping writes local. Use it when a bug, package interaction, or funding shape only appears on a public network, but the test still needs repeatable local transactions and local snapshot/dev-wallet workflows. The examples/fork-greeting example is the smallest runnable stack: it forks testnet, faucet-funds ephemeral accounts from a default whale, publishes a Move package, and captures the published object id.

The fork-specific knobs are:

  • checkpoint — pin the upstream checkpoint used to initialize the fork.
  • seed.addresses / seed.objects — preload upstream addresses or objects the local fork should materialize.
  • autoTick — advance the fork clock periodically; pass true for the default cadence or { intervalMs } for a custom interval.
const forkedTestnet = sui({
	mode: 'fork',
	upstream: 'testnet',
	checkpoint: 64_000_000,
	seed: { addresses: [publisherAddress] },
	autoTick: { intervalMs: 1_000 },
});

Plugins that depend on the forked Sui member can use the mode-narrowed ForkAdminSurface through sui.fork: status, advanceClock, advanceCheckpoint, and impersonate.

import { Effect } from 'effect';
import { definePlugin } from '@mysten-incubation/devstack';

const forkProbe = definePlugin({
	id: 'fork-probe',
	role: 'task',
	section: 'action',
	dependsOn: { sui: forkedTestnet },
	start: ({ sui }) =>
		Effect.gen(function* () {
			const fork = sui.fork;
			if (fork === null) return null;
			const before = yield* fork.status;
			yield* fork.advanceCheckpoint;
			yield* fork.advanceClock(1_000);
			return { before, after: yield* fork.status };
		}),
});

Use impersonate when the test must act as a specific upstream address; use ephemeral accounts when the test only needs funded local signers. Ephemeral accounts have local private keys, work with the dev wallet, and receive SUI from the fork faucet. Impersonated accounts have no private key: devstack builds a transaction and submits it through the fork admin surface with an empty signature.

Funding test accounts (fork faucet)

Fork networks have no HTTP faucet, so devstack funds accounts by impersonating a large-reserve "whale" address on the forked upstream and transferring SUI from it. For testnet, mainnet, and devnet a default whale ships built in, so fork funding works with zero config — ephemeral accounts auto-fund exactly like on localnet:

const forkedTestnet = sui({ mode: 'fork', upstream: 'testnet' });

// Auto-funded from the default whale — no env vars, pre-funded addresses,
// or impersonation needed:
const publisher = account('publisher', { kind: 'ephemeral' });

To use your own funding source — or for an upstream with no default — set faucet.whale to an address holding a large single SUI coin. It's auto-added to the fork seed and validated at boot:

const forkedTestnet = sui({
	mode: 'fork',
	upstream: 'testnet',
	faucet: { whale: process.env.FORK_WHALE! }, // large-reserve upstream address
});

Funding flows through the normal faucet pathway, so ephemeral-account auto-funding and explicit SUI top-ups behave exactly as on localnet. faucet.perRequestCapMist caps a single request (default 1000 SUI); faucet: { enabled: false } turns the faucet off. Point faucet.whale at any address holding a large single SUI coin (an active validator or a treasury address — find one via a block explorer); at boot the faucet checks that the whale has a SUI coin large enough to cover a default fund plus gas, and emits an actionable error if none does.

Faucet-funded real (ephemeral) accounts are first-class in fork mode: they can publish packages, run actions, mint coins, and transfer value. Because sui-fork has no simulate_transaction, these transactions are built offline with an explicit gas budget (0.1 SUI), which also leaves a funded account headroom to move value rather than reserving its whole coin for gas.

Network names accepted by --network and DEVSTACK_NETWORK are localnet, testnet, mainnet, devnet, testnet-fork, mainnet-fork, and devnet-fork; the same names are accepted with a sui: prefix. The local alias maps to localnet.

Local-only services may refuse live or fork modes. Use the mode-specific factories when you want the type system to narrow available branches:

import { chainId, walrusFor, sealFor, deepbookFor } from '@mysten-incubation/devstack';

const live = { mode: 'live', chain: chainId('sui:testnet') } as const;
const fork = { mode: 'fork', chain: chainId('sui:testnet-fork') } as const;

const wal = walrusFor(live).known({
	systemObjectId: '0x...',
	stakingPoolId: '0x...',
	nodes: [],
});
const forkWal = walrusFor(fork).known({
	systemObjectId: '0x...',
	stakingPoolId: '0x...',
	nodes: [],
});

const keyServer = sealFor(live).testnet({
	objectId: '0x...',
	keyServerUrl: 'https://example.test/key',
});
const forkKeyServer = sealFor(fork).forkKnown({
	upstream: 'testnet',
	objectId: '0x...',
	keyServerUrl: 'https://example.test/key',
});

const dex = deepbookFor(live).known({
	packageId: '0x...',
	registryId: '0x...',
});

Live accounts should use the signer variant: pass a Keypair built from an env-var secret, an inline suiprivkey1... literal, or a hardware/KMS Signer. Fork replay accounts can use impersonate. The default ephemeral form is designed for local development, not custody of real assets.

On this page