Mysten Incubation
Configure your stack

Faucet and funding strategies

How devstack funds accounts, and how plugin authors add a faucet strategy for a chain.

Most of the time you don't configure a faucet at all. Local sui() brings one up for you, and account funding draws on it automatically — { coin: 'sui', amount } pulls from the faucet, managed coins mint from their treasury cap. This page covers where that funding comes from, and how a plugin author adds a faucet for a new chain.

How funding finds its source

When account('alice', { funding: [{ coin: 'sui', amount: 1_000_000_000n }] }) resolves, devstack looks up a funding strategy by the active network's chain id:

faucet:request:<chainId>

where <chainId> is the active network's genesis-digest chain identifier — the node's real chain id, not the network name (localnet / testnet). Two same-named localnets get distinct chain ids, so their faucet keys never collide. If a strategy is registered for that key, the request goes through it. If none is registered, SUI funding raises StrategyNotFoundError, which lists the keys that are registered so you can see what's wired.

Non-SUI funding (managed local-package coins, walCoin(localWalrus), and the like) works the same way under a coinType:<fullCoinType> key. A missing non-SUI strategy is a no-op so optional service funding stays ergonomic; a missing SUI strategy is an error, since default account funding depends on it.

Adding a faucet strategy

There's no faucet() stack member to compose — sui() registers its sui:localnet strategy for you. To fund a chain devstack doesn't already cover, a plugin author provides a strategy with defineFaucetStrategy(...):

my-faucet/index.ts
import { Effect } from 'effect';
import { defineFaucetStrategy, definePlugin } from '@mysten-incubation/devstack';

export const myFaucet = (chainId: `sui:${string}`) =>
	definePlugin({
		id: 'my-faucet-strategy',
		role: 'service',
		section: 'service',
		start: () => Effect.succeed({}),
		capabilities: [
			defineFaucetStrategy({
				chainId,
				strategy: {
					request: ({ address, amount }) =>
						// ... call the actual faucet wire here; `request` resolves to `Effect<void>`.
						Effect.void,
				},
			}),
		],
	});

defineFaucetStrategy packages a { chainId, strategy } pair. The capability key is computed from the chain id, and the strategy registers as the plugin starts — so pass the real resolved chainId for the network the plugin runs against.

Two options shape how it behaves:

  • autoMounted: false (default) — the contribution shows up in the dashboard so you can see what funding is wired. Pass true for built-ins the orchestrator includes automatically.
  • priority — higher wins. An unset priority resolves to 0, the same level built-ins register at, so pass priority ≥ 1 to make your strategy win over a built-in for the same chain.

Failure handling

A strategy is responsible for raising the right tagged error on each failure path; dispatch does not retry for you. Wrap your fetch so a non-2xx response surfaces as FaucetUnreachable or FaucetBodyError, and a 200 OK body carrying { status: { Failure } } surfaces as FaucetBodyError({ reason: 'failure-status' }). (The Sui faucet binary answers requests during a warm-up window before it can actually transfer coins, so both cases come up in practice.)

Funding from the dashboard

The web dashboard's Faucet panel exposes the same funding as a one-click action: SUI funds a fixed amount, while WAL and DEEP take an editable amount and fund a resolved account. The button and the boot-time funding pass run through the same registered strategies.

Failure surface

The faucet error union is:

  • FaucetUnreachable — transport-level (ECONNREFUSED, DNS, TLS, AbortSignal timeout).
  • FaucetExhausted — wall-clock budget exhausted; carries attempts: number and lastCause.
  • FaucetBodyErrorreason: 'failure-status' | 'invalid-json'.
  • FaucetConfigError — invalid strategy config.

A missing strategy for the requested chain is not a faucet error — it surfaces as StrategyNotFoundError (carrying capabilityKey + registeredKeys).

See Errors.

On this page