How Starship Enables Faucet Support for Test Tokens in Multichain Deployments

Starship facilitates faucet support for test tokens in multichain deployments by treating faucets as first-class declarative features that run as isolated Go services with automated token distribution pools and built-in port forwarding for local access.

Starship, developed by hyperweb-io/starship, is a comprehensive multichain testing framework designed for Cosmos-based local testnets. Its architecture delivers seamless faucet support for test tokens in multichain deployments through a declarative YAML configuration system, allowing each chain to independently specify faucet capabilities, resource constraints, and API exposure ports.

Declarative Configuration for Faucet Features

Starship separates faucet configuration into per-chain definitions and global service settings, enabling precise control over token distribution across complex multichain topologies.

Per-Chain Faucet Definition

The E2E test configuration in starship/tests/e2e/config.go defines a Faucet field on each Chain struct, allowing individual chains to declare their own faucet instances:

type Chain struct {

    Faucet *Feature `name:"faucet" json:"faucet" yaml:"faucet"`   // per-chain faucet

}

A concrete deployment manifest (e.g., starship/tests/e2e/configs/multi-validator-starship-faucet.yaml) wires the feature with specific resource limits and concurrency settings:

chains:
  - id: osmosis-1

    faucet:
      type: starship          # selects the built-in faucet binary

      concurrency: 2          # number of distributor accounts

      resources:
        cpu: "0.1"
        memory: "200M"
    ports:
      faucet: 8000            # exposed port for the faucet API

The ports.faucet entry is consumed by the port-forward script to map the pod’s internal faucet port to a local development address.

Global Faucet Services

The top-level Config struct also supports a global Faucet field for deployment-wide faucet services:

type Config struct {

    Faucet *Feature `name:"faucet" json:"faucet" yaml:"faucet"`   // global faucet service
}

This dual-layer configuration enables scenarios where individual chains maintain dedicated faucets while a global service manages cross-chain token distribution.

Faucet Service Architecture

The faucet runs as an independent Go binary (starship/faucet/main.go) with a gRPC/HTTP interface, isolated from chain nodes to ensure reliable token distribution under load.

Entry Point and Server Bootstrapping

The CLI entry point initializes the server configuration and starts the AppServer:

func NewApp() *cli.App {
    conf := NewDefaultConfig()

    server, err := NewAppServer(conf)

    server.Run()
}

This separation ensures the faucet service can be deployed, scaled, and restarted independently of the chain nodes it serves.

Request Handling via Status and Credit RPCs

The starship/faucet/handler.go file implements two primary RPC methods:

  • Status: Returns the faucet’s health, RPC endpoint, chain ID, supported tokens, the holder account, and the list of distributor accounts.
  • Credit: Executes a token transfer to a user-provided address, then polls the balance until confirming the expected increase or reaching a timeout.

The Credit handler implements a robust verification pattern:

func (a *AppServer) Credit(ctx context.Context, req *pb.RequestCredit) (*pb.ResponseCredit, error) {
    // snapshot balance, send tokens, confirm increase
}

This confirmation loop ensures test suites receive definitive feedback when tokens are actually spendable, not merely broadcast.

Token Pool Management with Distributor Accounts

The starship/faucet/distributor.go file manages the holder (genesis-funded) account and a configurable pool of distributor accounts to prevent nonce collisions and handle concurrent requests.

Key responsibilities include:

  • Credit Coin Parsing: Converts config.CreditCoins into the distributor’s CreditCoins list.
  • Automatic Refilling: Monitors distributor balances against RefillThreshold; triggers Distributor.Refill when accounts require topping up from the holder account.
  • Randomized Selection: Chooses a random distributor for each request to distribute load:
func (d *Distributor) SendTokens(address, denom string) error {
    if d.Addrs == nil {
        return d.Holder.SendTokens(address, denom, amount)
    }
    randIndex := rand.Intn(len(d.Addrs))
    return d.Addrs[randIndex].SendTokens(address, denom, amount)
}

The concurrency setting in the YAML configuration directly controls the size of the distributor pool, allowing developers to optimize for high-throughput testing scenarios.

Network Exposure and Client Access

Starship automates network connectivity between local development environments and cluster-internal faucet pods.

Automated Port Forwarding

The starship/scripts/port-forward.sh script reads the chains[i].ports.faucet values from the deployment configuration and creates kubectl port-forward tunnels:

localfaucet=$(yq -r ".chains[$i].ports.faucet" ${CONFIGFILE})
kubectl port-forward pods/$chain-genesis-0 $localfaucet:$CHAIN_FAUCET_PORT &

This makes the faucet API reachable at http://localhost:<port> without manual Kubernetes networking configuration.

SDK Integration

Client libraries interact with the exposed endpoint using standard HTTP/gRPC. The test suite in starship/tests/e2e/faucet_test.go demonstrates the request flow, where applications can request specific denominations to be credited to test addresses.

End-to-End Usage Example

Deploy a multichain environment with faucet support using the following workflow:


# 1. Start Starship with a faucet-enabled configuration

starship up -f ./starship/tests/e2e/configs/multi-validator-starship-faucet.yaml

# 2. Forward faucet ports to localhost

./starship/scripts/port-forward.sh ./starship/tests/e2e/configs/multi-validator-starship-faucet.yaml

Then request tokens programmatically:

import { FaucetClient } from '@hyperweb/starshipjs'

const client = new FaucetClient('http://localhost:8000')
await client.credit({ address: 'cosmos1xyz...', denom: 'uatom' })

The client communicates with the Credit RPC, which triggers the distributor to send the configured amount and waits for balance confirmation before returning success.

Summary

  • Declarative Configuration: Each chain independently declares faucet features with resource limits and exposure ports via starship/tests/e2e/config.go structures.
  • Isolated Service Architecture: The faucet runs as a dedicated Go binary (starship/faucet/main.go) with Status and Credit RPC handlers separate from chain nodes.
  • Robust Token Pool: The Distributor in starship/faucet/distributor.go maintains holder and refillable distributor accounts, automatically topping up balances when thresholds are reached.
  • Automated Network Exposure: The port-forward.sh script maps internal Kubernetes ports to local addresses using the ports.faucet configuration.
  • Multichain Native: Because configuration lives per-chain in the YAML manifest, a single Starship deployment supports independent faucets for any number of test chains simultaneously.

Frequently Asked Questions

How do I enable faucet support for a specific chain in Starship?

Add a faucet field to the chain definition in your YAML configuration file, specifying the type (typically starship), concurrency (number of distributor accounts), and resources. Expose the service by defining ports.faucet with a port number, which the port-forward script will use to create a local tunnel.

What is the difference between holder and distributor accounts in Starship's faucet?

The holder account is the primary genesis-funded wallet that holds the initial token supply. Distributor accounts are secondary wallets spawned according to the concurrency setting; they receive periodic refills from the holder and handle actual token distribution requests. This separation prevents nonce conflicts during concurrent credit requests and enables horizontal scaling.

How does Starship automate token refills for faucets?

The Distributor implementation in starship/faucet/distributor.go monitors each distributor account's balance against a RefillThreshold. When an account's balance drops below this threshold, the requireRefill logic triggers the Refill method, which transfers tokens from the holder account to the depleted distributor, ensuring continuous availability for test token requests.

Can multiple chain faucets run simultaneously in one deployment?

Yes. Because the Chain struct in starship/tests/e2e/config.go includes a per-chain Faucet field, each chain in a multichain deployment can define its own independent faucet configuration with unique ports. The port-forward script iterates through all chains, creating separate tunnels for each faucet endpoint, enabling isolated token distribution for every chain in the test topology.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →