Skip to content
starskiff

Custom Chains

Wrap any Cosmos SDK binary with cosmosBase / cosmosEvmBase

Any Cosmos SDK binary that follows the standard init → genesis → start CLI shape works with starskiff. Instance definitions are thin wrappers: set the binary name and chain defaults, delegate the rest.

Minimal instance

import {
  Instance,
  cosmosBase,
  type CosmosChainParameters,
  type OptionalInstanceSource,
} from 'starskiff';
 
export type MychaindParameters =
  & Omit<CosmosChainParameters, 'image'>
  & OptionalInstanceSource;
 
export const mychaind = Instance.define((parameters?: MychaindParameters) => {
  const { binary = 'mychaind', image, ...rest } = parameters || {};
  return cosmosBase({ binary, image, name: 'mychaind', ...rest });
});
 
const instance = mychaind({ chainId: 'my-1', denom: 'umych' });

cosmosBase handles the full boot flow: init → genesis denom patching → key creation + account funding → gentx / collect-gentxs → config.toml/app.toml port patching → start → health-polling until the first block.

OptionalInstanceSource makes the wrapper's image and binary overrides mutually exclusive while allowing neither, which uses its built-in mychaind binary. Use InstanceSource instead when a custom wrapper has no default and must require exactly one source. cosmosBase itself still receives both an optional image and the executable name to invoke inside that image.

Patching genesis

Chain-specific genesis tweaks go through the patchGenesis hook, applied after starskiff's default denom patching:

return cosmosBase({
  binary, name: 'mychaind', ...rest,
  patchGenesis: (genesis) => {
    genesis.app_state.gov.params.voting_period = '5s';
    return genesis;
  },
});

Extra config patches and start flags are available for the odd chain that needs them:

HookApplies to
patchGenesisgenesis.json (after denom patching)
extraAppTomlapp.toml ('section.key': 'value')
extraConfigTomlconfig.toml
extraStartArgsappended to the start command
extraReadinessCheckANDed with the first-block health check

Runtime inputs

Custom chain definitions can attach environment variables and read-only files to every lifecycle command with runtime. Environment settings apply to both local binaries and containers; mounts apply only when image is used.

return cosmosBase({
  binary,
  name: 'mychaind',
  image,
  runtime: {
    environment: { MYCHAIN_CONFIG_DIR: image ? '/starskiff/config' : configDirectory },
    mounts: image
      ? [{ source: configDirectory, target: '/starskiff/config', readOnly: true }]
      : undefined,
  },
});

Keep chain-specific details on the wrapper interface. For example, accept a configDirectory option and translate it into runtime internally rather than asking callers for environment-variable names or container paths.

EVM chains

For cosmos-evm chains, cosmosEvmBase extends the flow with a JSON-RPC port (evmPort, patched into app.toml and health-checked on start), ethermint relayer hints for Hermes, an activeStaticPrecompiles genesis patch, and an optional validated evmChainId passed to the node at start:

import { Instance, cosmosEvmBase, type CosmosEvmChainParameters } from 'starskiff';
 
export const myevmd = Instance.define((parameters?: CosmosEvmChainParameters) => {
  return cosmosEvmBase({
    binary: 'myevmd',
    name: 'myevmd',
    evmChainId: 12345,
    denom: 'atoken', // 18 decimals → bump validator amounts:
    validatorBalance: '100000000000000000000000', // 1e23
    validatorStake: '10000000000000000000000', // 1e22
    ...parameters,
  });
});

18-decimal denoms need large validator amounts because the SDK's power reduction is denom-scaled — see the shipped evmd definition for a reference implementation (it also shows denom-metadata and precompile patching for chains whose init writes incomplete genesis defaults).