Validate a Stellar Info File (stellar.toml) against SEP-1 — offline, before you deploy it.
Documentation website: stellar-toml-lint docs
$ npx stellar-toml-lint public/.well-known/stellar.toml
public/.well-known/stellar.toml
12:1 error NETWORK_PASSPHRASE has stray whitespace; it must match the Public passphrase byte for byte network/passphrase
↳ Replace it with exactly: Public Global Stellar Network ; September 2015
31:1 error CURRENCIES[0].issuer is not a valid Stellar account ID currencies/issuer-or-contract
↳ Check for a transcription error — the checksum does not match.
34:1 error CURRENCIES[0] sets fixed_number and is_unlimited, but these issuance policies are mutually exclusive currencies/issuance-exclusive
↳ SEP-1 requires exactly one of fixed_number, max_number, or is_unlimited.
19:1 warning DOCUMENTATION.ORG_PHONE_NUMBER is not in E.164 format documentation/phone-e164
↳ Use a leading + and digits only, e.g. "+14155552671".
4 problems (3 errors, 1 warning, 0 infos)The official @stellar/anchor-tests suite is thorough, but it tests a live
domain. That means you find out your info file is broken after you have shipped it — and you
cannot run it in the pull request that introduced the mistake.
stellar-toml-lint reads a local file. It runs in a pre-commit hook, in CI, or on your laptop before
a domain exists at all. It is complementary to anchor-tests, not a replacement: this catches
everything checkable from the file itself, then hands off to anchor-tests for the parts that need
running services.
stellar-toml-lint |
@stellar/anchor-tests |
|
|---|---|---|
| Lints a local file | ✅ | ❌ |
| Needs a deployed domain | ❌ | ✅ |
| Line and column for each fault | ✅ | ❌ |
| Validates key checksums | ✅ | partial |
| SARIF / code scanning output | ✅ | ❌ |
| Tests live SEP-6/10/24/31 flows | ❌ | ✅ |
Because it validates Stellar keys with @stellar/stellar-base, it verifies the CRC16 checksum —
so a single transposed character in an issuer address is caught, which a /^G[A-Z2-7]{55}$/ regex
would wave straight through.
npm install --save-dev stellar-toml-lint # project dependency
npx stellar-toml-lint # or just run itRequires Node.js 20 or newer. Two runtime dependencies: smol-toml and @stellar/stellar-base.
# Lint a local file (defaults to ./stellar.toml)
stellar-toml-lint public/.well-known/stellar.toml
# Fetch and lint a live site, including CORS and content-type checks
stellar-toml-lint --domain example.com
# Lint a local file *as if* served from a domain, enabling same-domain checks
stellar-toml-lint public/.well-known/stellar.toml --domain example.com
# Read from stdin
cat stellar.toml | stellar-toml-lint -| Flag | Effect |
|---|---|
-d, --domain <d> |
Serving domain. Enables CORS, content-type, and ORG_URL checks |
-f, --format <fmt> |
text (default), json, sarif, github |
--strict |
Treat warnings as errors |
--max-warnings <n> |
Fail if warnings exceed n |
--off <rule> |
Disable a rule (repeatable) |
--error <rule> |
Raise a rule to error (repeatable) |
--warn <rule> |
Lower a rule to warning (repeatable) |
-q, --quiet |
Show errors only |
--show-help-urls |
Print the spec link for each finding |
--list-rules |
Print every rule and exit |
--no-suggestions |
Hide diagnostic suggestions in the output |
Exit codes: 0 no errors, 1 problems found, 2 bad usage or I/O failure.
- uses: anchor-tools/stellar-toml-lint@v1
with:
file: public/.well-known/stellar.toml
strict: trueFindings appear as inline annotations on the pull request diff.
To route them into the Security tab instead:
- uses: anchor-tools/stellar-toml-lint@v1
with:
file: public/.well-known/stellar.toml
sarif-file: stellar-toml.sarif
continue-on-error: true
- uses: github/codeql-action/upload-sarif@v3
with:
sarif_file: stellar-toml.sarif# .pre-commit-config.yaml
repos:
- repo: local
hooks:
- id: stellar-toml-lint
name: Lint stellar.toml
entry: npx stellar-toml-lint
language: system
files: '\.well-known/stellar\.toml$'on:
schedule:
- cron: '23 7 * * *'
jobs:
check:
runs-on: ubuntu-latest
steps:
- uses: anchor-tools/stellar-toml-lint@v1
with:
domain: example.comThis catches the failure mode nobody notices: a CDN or hosting change quietly dropping the
Access-Control-Allow-Origin header, which makes the file unreadable to every browser-based wallet
while looking perfectly fine to curl.
import { lint, lintDomain, formatText } from 'stellar-toml-lint';
import { readFile } from 'node:fs/promises';
const result = lint(await readFile('stellar.toml', 'utf8'), {
domain: 'example.com',
strict: true,
rules: { 'general/unknown-field': 'off' },
});
if (!result.ok) {
console.error(formatText(result, { color: true }));
process.exit(1);
}
// Or check a live site. CORS, content type, and size are checked too.
const live = await lintDomain('example.com');Every diagnostic carries a stable rule id, a severity, a dotted path to the offending value, a
source position, a link to the relevant part of the spec, and a concrete suggestion.
interface Diagnostic {
rule: string; // 'currencies/issuance-exclusive'
severity: 'error' | 'warning' | 'info';
category:
'file' | 'general' | 'documentation' | 'principals' | 'currencies' | 'validators' | 'network';
message: string;
path?: string; // 'CURRENCIES[1].issuer'
position?: { line: number; column: number };
helpUri?: string;
suggestion?: string;
}Run stellar-toml-lint --list-rules for the authoritative list. In summary:
File — 100KB size limit, TOML syntax with line and column, UTF-8 BOM detection.
General — VERSION; NETWORK_PASSPHRASE matched byte-for-byte against the known networks;
https:// on every endpoint field; trailing-slash detection; checksum-valid SIGNING_KEY,
URI_REQUEST_SIGNING_KEY, WEB_AUTH_CONTRACT_ID, and ACCOUNTS; deprecated fields; unknown fields.
Cross-field dependencies — DIRECT_PAYMENT_SERVER (SEP-31) requires KYC_SERVER (SEP-12);
WEB_AUTH_ENDPOINT (SEP-10) requires SIGNING_KEY; SEP-45 needs both its endpoint and contract ID.
[DOCUMENTATION] — completeness against what wallets weigh when listing an asset; https://
URLs; ORG_URL matching the serving domain; attestation documents hosted on your own domain;
ORG_OFFICIAL_EMAIL at the ORG_URL domain; E.164 phone format; handles that are handles, not URLs.
[[PRINCIPALS]] — name and email present and well-formed; hex photo hashes of plausible length.
[[CURRENCIES]] — code length and charset; exactly one of issuer or contract, both checksum
validated; the native XLM asset handled as the special case it is; exactly one issuance policy;
status and anchor_asset_type enums; display_decimals in 0–7; anchored assets describing what
backs them; SEP-8 regulated assets carrying an approval server; collateral address, message, and
signature lists of equal length; toml pointer entries carrying nothing else; duplicate assets.
[[VALIDATORS]] — ALIAS matching ^[a-z0-9-]{2,16}$ and unique; checksum-valid, unique
PUBLIC_KEY; HOST as host:port; HISTORY as an absolute URI.
Network (with --domain) — reachability, Access-Control-Allow-Origin: *, text/plain content
type, size.
- error — violates SEP-1, or will break a client. Fails the build.
- warning — valid but likely wrong, or materially incomplete.
- info — worth a look; usually an unrecognised field name.
Tune any rule with --off, --warn, or --error.
New contributors are genuinely welcome — see CONTRIBUTING.md. Issues labelled
good first issue are scoped to be completable in an afternoon, and adding a rule is mostly a
matter of appending one object to a list and one fixture to a test.
General enquiries: anchortools23@gmail.com. Please use issues for bugs and feature requests, and a private advisory for anything security-related.
This project participates in Drips. See FUNDING.json.
Not affiliated with or endorsed by the Stellar Development Foundation.