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';
const DEV_PORT = 5173;
const alice = account('alice', {
kind: 'env',
key: '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-forkFork 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.
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 env, keystore, inline, or signer as appropriate. Fork replay
accounts can use impersonate. The default ephemeral form is designed for local development, not
custody of real assets.