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(...):
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. Passtruefor built-ins the orchestrator includes automatically.priority— higher wins. An unsetpriorityresolves to0, the same level built-ins register at, so passpriority≥ 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; carriesattempts: numberandlastCause.FaucetBodyError—reason: '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.