Skip to content

DKIM / SPF / DMARC Builder & Checker — C# source

Build the three email-authentication DNS records — SPF, DKIM, and DMARC — as ready-to-paste TXT values, or look up a domain's live records over DNS-over-HTTPS. Import existing records, catch publishing mistakes, share via URL. Entirely client-side.

This is the C# implementation — the same logic the interactive tool runs, in a shareable, citable form.

// DKIM / SPF / DMARC — email authentication record parsing and lookup.
// C# 12 / .NET 8 — ported from src/lib/dkim-spf-dmarc.ts (the canonical
// TypeScript implementation). Display source for CosmoDev's polyglot pages.
//
// Two layers, mirroring the TS reference:
//   - Pure parsers (ParseSPF / ParseDKIM / ParseDMARC) — deterministic, never
//     throw, unit-testable without network.
//   - Check functions (CheckSPFAsync / CheckDKIMAsync / CheckDMARCAsync) — one
//     DNS-over-HTTPS request to Cloudflare's public resolver, then a pure
//     parse. The static HttpClient keeps that to one connection pool.
//
// Specs: SPF RFC 7208, DKIM RFC 6376, DMARC RFC 7489.

using System.Text;
using System.Text.Json;
using System.Text.RegularExpressions;

/// <summary>Overall verdict of one check.</summary>
public enum CheckStatus
{
    Pass,
    Warn,
    Fail,
}

/// <summary>SPF qualifier prefix: pass, fail, softfail, neutral.</summary>
public enum SpfQualifier
{
    Pass,
    Fail,
    SoftFail,
    Neutral,
}

/// <summary>One SPF mechanism term, e.g. "-include:_spf.example.com".</summary>
public sealed record SpfMechanism(SpfQualifier Qualifier, string Kind, string? Value);

/// <summary>A parsed (or missing) SPF record plus its warnings.</summary>
public sealed class SpfRecord
{
    public bool Valid { get; set; }
    public string? Version { get; set; }
    public List<SpfMechanism> Mechanisms { get; set; } = [];
    public string? Redirect { get; set; }
    public string? Exp { get; set; }
    /// <summary>Mechanisms that cost a DNS query under RFC 7208 §4.6.4 (limit 10).</summary>
    public int LookupCount { get; set; }
    /// <summary>Number of v=spf1 records found (more than one is a hard error for receivers).</summary>
    public int RecordCount { get; set; }
    public List<string> Warnings { get; set; } = [];
}

/// <summary>A parsed DKIM selector record plus its warnings.</summary>
public sealed class DkimRecord
{
    public bool Valid { get; set; }
    public string? Version { get; set; }
    public string KeyType { get; set; } = "rsa";
    /// <summary>Public key, base64, whitespace-stripped.</summary>
    public string? PublicKey { get; set; }
    /// <summary>Approximate modulus size in bits (RSA only; derived from DER byte length).</summary>
    public int? KeyBits { get; set; }
    public List<string> Hashes { get; set; } = [];
    public List<string> Services { get; set; } = [];
    public List<string> Flags { get; set; } = [];
    public List<string> Warnings { get; set; } = [];
}

public enum DmarcPolicy
{
    None,
    Quarantine,
    Reject,
}

/// <summary>A parsed DMARC policy record plus its warnings.</summary>
public sealed class DmarcRecord
{
    public bool Valid { get; set; }
    public DmarcPolicy? Policy { get; set; }
    public DmarcPolicy? SubdomainPolicy { get; set; }
    public List<string> AggregateUris { get; set; } = [];
    public List<string> ForensicUris { get; set; } = [];
    public int? Percent { get; set; }
    /// <summary>'r' (relaxed) or 's' (strict) DKIM identifier alignment.</summary>
    public char? DkimAlignment { get; set; }
    /// <summary>'r' (relaxed) or 's' (strict) SPF identifier alignment.</summary>
    public char? SpfAlignment { get; set; }
    public List<string> Warnings { get; set; } = [];
}

/// <summary>Outcome of one live DNS check: status + record + message.</summary>
public sealed record SpfResult(CheckStatus Status, bool Found, SpfRecord? Record = null, string? Message = null);
public sealed record DkimResult(CheckStatus Status, bool Found, DkimRecord? Record = null, string? Message = null);
public sealed record DmarcResult(CheckStatus Status, bool Found, DmarcRecord? Record = null, string? Message = null);

public static class DkimSpfDmarc
{
    // ---------------------------------------------------------------------------
    // Shared helpers
    // ---------------------------------------------------------------------------

    /// <summary>Strip a leading scheme, userinfo, path, query, port, and trailing dot from user input.</summary>
    public static string NormalizeDomain(string raw)
    {
        var s = raw.Trim();
        s = Regex.Replace(s, @"^[a-z][a-z0-9+.-]*://", "", RegexOptions.IgnoreCase); // scheme://
        s = Regex.Replace(s, @"^mailto:", "", RegexOptions.IgnoreCase); // mailto:user@domain
        if (s.Contains('@')) s = s[(s.LastIndexOf('@') + 1)..]; // keep host of user@host
        s = s.Split('/')[0]; // drop path
        s = s.Split('?')[0]; // drop query
        s = s.Split(':')[0]; // drop port
        s = Regex.Replace(s, @"\.+$", ""); // trailing dot(s)
        return s.ToLowerInvariant();
    }

    private static readonly Regex DomainRegex =
        new("^(?:[a-z0-9](?:[a-z0-9-]*[a-z0-9])?\\.)+[a-z]{2,}$", RegexOptions.Compiled);

    /// <summary>True when the string looks like a plausible multi-label domain (example.com).</summary>
    public static bool IsDomainLike(string domain) =>
        DomainRegex.IsMatch(domain) && domain.Length <= 253;

    private static readonly Regex SelectorRegex = new("^[a-z0-9][a-z0-9._-]*$", RegexOptions.IgnoreCase | RegexOptions.Compiled);

    /// <summary>True when the selector is a safe single DNS label chain (no spaces, no traversal).</summary>
    public static bool IsValidSelector(string selector)
    {
        var s = selector.Trim();
        return s.Length > 0 && s.Length <= 100 && SelectorRegex.IsMatch(s) && !s.Contains("..");
    }

    // ---------------------------------------------------------------------------
    // SPF — RFC 7208
    // ---------------------------------------------------------------------------

    private static readonly HashSet<string> SpfLookupKinds =
        ["include", "a", "mx", "exists", "ptr"];

    private static readonly Regex SpfLineRegex = new(@"^v=spf1(\s|$)", RegexOptions.IgnoreCase);
    private static readonly Regex ModifierRegex = new(@"^([a-z][a-z0-9-]*)=(.*)$", RegexOptions.IgnoreCase);
    private static readonly Regex MechanismRegex = new(@"^([+\-~?])?([a-z0-9]+)(?::(.*))?$", RegexOptions.IgnoreCase);
    private static readonly Regex QuotedRegex = new(@"^""(.*)""$");

    private static readonly HashSet<string> SpfKinds =
        ["all", "include", "a", "mx", "ip4", "ip6", "exists", "ptr"];

    /// <summary>Parse one or more (newline-joined) TXT record strings for SPF. Pure.</summary>
    public static SpfRecord ParseSPF(string txt)
    {
        var lines = txt
            .Split('\n')
            .Select(l => QuotedRegex.Replace(l.Trim(), "$1"))
            .Where(l => l.Length > 0)
            .ToList();
        var spfLines = lines.Where(l => SpfLineRegex.IsMatch(l)).ToList();
        var warnings = new List<string>();

        if (spfLines.Count == 0)
        {
            return new SpfRecord
            {
                Valid = false,
                Warnings = ["No v=spf1 record found in the supplied text."],
            };
        }
        if (spfLines.Count > 1)
        {
            warnings.Add(
                $"{spfLines.Count} SPF records found — RFC 7208 allows exactly one. Receivers treat this as a permanent error and ignore SPF for the whole domain.");
        }

        var terms = spfLines[0].Split(new[] { ' ', '\t' }, StringSplitOptions.RemoveEmptyEntries);
        var version = terms[0];
        var mechanisms = new List<SpfMechanism>();
        string? redirect = null;
        string? exp = null;

        foreach (var term in terms.Skip(1))
        {
            // Modifiers use '=': redirect= and exp=
            var modifier = ModifierRegex.Match(term);
            if (modifier.Success)
            {
                var name = modifier.Groups[1].Value.ToLowerInvariant();
                if (name == "redirect") redirect = modifier.Groups[2].Value;
                else if (name == "exp") exp = modifier.Groups[2].Value;
                else warnings.Add($"Unknown modifier \"{term}\" ignored.");
                continue;
            }
            var m = MechanismRegex.Match(term);
            if (!m.Success)
            {
                warnings.Add($"Unrecognized term \"{term}\" ignored.");
                continue;
            }
            var qualifier = m.Groups[1].Success ? m.Groups[1].Value[0] : '+';
            var kind = m.Groups[2].Value.ToLowerInvariant();
            var value = m.Groups[3].Success ? m.Groups[3].Value : null;
            if (!SpfKinds.Contains(kind))
            {
                warnings.Add($"Unknown mechanism \"{term}\" ignored.");
                continue;
            }
            if (value == null && (kind == "include" || kind == "exists"))
            {
                warnings.Add($"Mechanism \"{term}\" is missing its required value.");
                continue;
            }
            mechanisms.Add(new SpfMechanism(ToQualifier(qualifier), kind, value));
        }

        var lookupCount = mechanisms.Count(mech => SpfLookupKinds.Contains(mech.Kind)) + (redirect != null ? 1 : 0);

        var all = mechanisms.FirstOrDefault(mech => mech.Kind == "all");
        if (all == null && redirect == null)
        {
            warnings.Add(
                "No \"all\" mechanism and no \"redirect=\" — unmatched senders get a Neutral result, so anyone can still send mail that looks like this domain.");
        }
        if (all != null && all.Qualifier == SpfQualifier.Pass)
        {
            warnings.Add(
                "\"+all\" explicitly allows every host on the internet to send mail as this domain — this defeats SPF entirely.");
        }
        else if (all != null && all.Qualifier == SpfQualifier.Neutral)
        {
            warnings.Add("\"?all\" (Neutral) lets unmatched senders through with no protection. Prefer \"~all\" or \"-all\".");
        }
        if (mechanisms.Any(mech => mech.Kind == "ptr"))
        {
            warnings.Add("The \"ptr\" mechanism is deprecated (RFC 7208 §5.5) and should not be used.");
        }
        if (redirect != null && all != null)
        {
            warnings.Add("A \"redirect=\" modifier is ignored when an \"all\" mechanism is present.");
        }
        if (lookupCount > 10)
        {
            warnings.Add(
                $"{lookupCount} DNS-lookup mechanisms — RFC 7208 §4.6.4 caps SPF at 10. Receivers that hit the cap return permerror and ignore the record.");
        }

        return new SpfRecord
        {
            Valid = true,
            Version = version,
            Mechanisms = mechanisms,
            Redirect = redirect,
            Exp = exp,
            LookupCount = lookupCount,
            RecordCount = spfLines.Count,
            Warnings = warnings,
        };

        static SpfQualifier ToQualifier(char c) => c switch
        {
            '-' => SpfQualifier.Fail,
            '~' => SpfQualifier.SoftFail,
            '?' => SpfQualifier.Neutral,
            _ => SpfQualifier.Pass,
        };
    }

    // ---------------------------------------------------------------------------
    // DKIM — RFC 6376
    // ---------------------------------------------------------------------------

    /// <summary>Decode base64 to raw DER bytes without throwing on bad input.</summary>
    private static byte[]? DecodeBase64Lenient(string b64)
    {
        // Callers pass trimmed, non-empty values (empty p= is handled before this).
        var clean = Regex.Replace(b64, @"\s+", "");
        var padded = clean + new string('=', (4 - clean.Length % 4) % 4);
        try
        {
            return Convert.FromBase64String(padded);
        }
        catch (FormatException)
        {
            return null;
        }
    }

    /// <summary>Parse a &lt;selector&gt;._domainkey TXT record. Pure.</summary>
    public static DkimRecord ParseDKIM(string txt)
    {
        var warnings = new List<string>();
        var tags = new Dictionary<string, string>();
        foreach (var part in txt.Split(';'))
        {
            var term = QuotedRegex.Replace(part.Trim(), "$1").Trim();
            if (term.Length == 0) continue;
            var eq = term.IndexOf('=');
            if (eq <= 0)
            {
                warnings.Add($"Malformed tag \"{term}\" ignored.");
                continue;
            }
            tags[term[..eq].Trim().ToLowerInvariant()] = term[(eq + 1)..].Trim();
        }

        var record = new DkimRecord { Valid = true, Warnings = warnings };
        if (tags.TryGetValue("v", out var version))
        {
            if (version.ToUpperInvariant() != "DKIM1")
            {
                warnings.Add($"Unusual version tag v={version} (expected DKIM1).");
            }
            record.Version = version.ToUpperInvariant();
        }
        record.KeyType = tags.GetValueOrDefault("k", "rsa");
        var p = tags.TryGetValue("p", out var pValue) ? pValue : null;
        record.Hashes = SplitList(tags.GetValueOrDefault("h"));
        record.Services = SplitList(tags.GetValueOrDefault("s"));
        record.Flags = SplitList(tags.GetValueOrDefault("t"));

        if (p == null)
        {
            record.Valid = false;
            warnings.Add("No p= tag — this record is not a usable DKIM key.");
            return record;
        }
        if (p == "")
        {
            warnings.Add("p= is empty — the key is revoked. Receivers will treat mail signed with this selector as unsigned.");
            return record;
        }

        var decoded = DecodeBase64Lenient(p);
        if (decoded == null)
        {
            warnings.Add("The p= value is not valid base64 — the key could not be read.");
            return record;
        }
        record.PublicKey = Regex.Replace(p, @"\s+", "");

        if (record.KeyType == "rsa")
        {
            // SubjectPublicKeyInfo DER ≈ modulus bits/8 + ~24 bytes of ASN.1 overhead.
            // The estimate is close enough to classify 512/1024/2048/4096-bit keys.
            var bits = Math.Max(0, decoded.Length - 24) * 8;
            record.KeyBits = bits;
            if (bits < 1024)
            {
                warnings.Add($"Weak RSA key (~{bits} bits). Keys under 1024 bits are considered breakable; RFC 8301 discourages short keys.");
            }
            else if (bits < 2048)
            {
                warnings.Add($"RSA key of ~{bits} bits works today but is below the recommended 2048 bits (RFC 8301).");
            }
        }
        if (record.Flags.Contains("y"))
        {
            warnings.Add("t=y — the key is in test mode: receivers must treat signatures as if unsigned.");
        }
        if (record.Flags.Contains("s"))
        {
            warnings.Add("t=s — strict domain matching: the key cannot be used for subdomain signatures (informational).");
        }
        return record;

        static List<string> SplitList(string? joined) =>
            (joined ?? "").Split(':').Select(s => s.Trim()).Where(s => s.Length > 0).ToList();
    }

    // ---------------------------------------------------------------------------
    // DMARC — RFC 7489
    // ---------------------------------------------------------------------------

    private static readonly Dictionary<string, DmarcPolicy> DmarcPolicies = new()
    {
        ["none"] = DmarcPolicy.None,
        ["quarantine"] = DmarcPolicy.Quarantine,
        ["reject"] = DmarcPolicy.Reject,
    };

    /// <summary>Parse a _dmarc TXT record. Pure.</summary>
    public static DmarcRecord ParseDMARC(string txt)
    {
        var warnings = new List<string>();
        var tags = new Dictionary<string, string>();
        foreach (var part in txt.Split(';'))
        {
            var term = QuotedRegex.Replace(part.Trim(), "$1").Trim();
            if (term.Length == 0) continue;
            var eq = term.IndexOf('=');
            if (eq <= 0)
            {
                warnings.Add($"Malformed tag \"{term}\" ignored.");
                continue;
            }
            tags[term[..eq].Trim().ToLowerInvariant()] = term[(eq + 1)..].Trim();
        }

        var record = new DmarcRecord { Valid = true, Warnings = warnings };

        if (!tags.TryGetValue("v", out var version))
        {
            record.Valid = false;
            warnings.Add("No v= tag — this is not a DMARC record.");
            return record;
        }
        if (version.ToUpperInvariant() != "DMARC1")
        {
            record.Valid = false;
            warnings.Add($"Unknown version v={version} (expected DMARC1).");
            return record;
        }

        if (!tags.TryGetValue("p", out var p) || string.IsNullOrEmpty(p))
        {
            record.Valid = false;
            warnings.Add("No p= policy tag — DMARC requires it.");
            return record;
        }
        if (!DmarcPolicies.TryGetValue(p.ToLowerInvariant(), out var policy))
        {
            record.Valid = false;
            warnings.Add($"Invalid policy p={p} (expected none, quarantine, or reject).");
            return record;
        }
        record.Policy = policy;

        if (tags.TryGetValue("sp", out var sp) && !string.IsNullOrEmpty(sp))
        {
            if (DmarcPolicies.TryGetValue(sp.ToLowerInvariant(), out var subPolicy))
            {
                record.SubdomainPolicy = subPolicy;
            }
            else
            {
                warnings.Add($"Invalid sp={sp} ignored (expected none, quarantine, or reject).");
            }
        }

        if (tags.TryGetValue("rua", out var rua))
        {
            record.AggregateUris = rua.Split(',').Select(u => u.Trim()).Where(u => u.Length > 0).ToList();
        }
        if (tags.TryGetValue("ruf", out var ruf))
        {
            record.ForensicUris = ruf.Split(',').Select(u => u.Trim()).Where(u => u.Length > 0).ToList();
        }

        if (tags.TryGetValue("pct", out var pct) && int.TryParse(pct, out var n))
        {
            if (n is < 0 or > 100)
            {
                warnings.Add($"Invalid pct={pct} ignored (must be 0-100).");
            }
            else
            {
                record.Percent = n;
            }
        }
        else if (tags.ContainsKey("pct"))
        {
            warnings.Add($"Invalid pct={tags["pct"]} ignored (must be 0-100).");
        }

        if (tags.TryGetValue("adkim", out var adkim) && adkim.Length > 0)
        {
            if (adkim is "r" or "s") record.DkimAlignment = adkim[0];
            else warnings.Add($"Invalid adkim={adkim} ignored (expected r or s).");
        }
        if (tags.TryGetValue("aspf", out var aspf) && aspf.Length > 0)
        {
            if (aspf is "r" or "s") record.SpfAlignment = aspf[0];
            else warnings.Add($"Invalid aspf={aspf} ignored (expected r or s).");
        }

        // Policy guidance
        if (record.Policy == DmarcPolicy.None)
        {
            warnings.Add(
                "p=none is monitor-only — no mail is quarantined or rejected, but you still need SPF/DKIM to pass for reports to be useful.");
        }
        if (record.AggregateUris.Count == 0)
        {
            warnings.Add(
                "No rua= address — without aggregate reports you cannot see who is failing authentication. Add rua=mailto:reports@example.com.");
        }
        else if (record.ForensicUris.Count > 0)
        {
            warnings.Add(
                "ruf= (forensic reports) is supported by few receivers and may leak message content to the report address (informational).");
        }
        if (record.Percent is < 100 && record.Policy != DmarcPolicy.None)
        {
            warnings.Add(
                $"pct={record.Percent} applies the policy to only {record.Percent}% of mail — the other {100 - record.Percent}% is unaffected.");
        }
        return record;
    }

    // ---------------------------------------------------------------------------
    // DNS-over-HTTPS lookup
    // ---------------------------------------------------------------------------

    private const string DohEndpoint = "https://cloudflare-dns.com/dns-query";

    private static readonly HttpClient Http = new();

    /// <summary>Query TXT records for a name via Cloudflare's DoH JSON API. Returns unquoted strings.</summary>
    private static async Task<List<string>> QueryTxtAsync(string name)
    {
        var url = $"{DohEndpoint}?name={Uri.EscapeDataString(name)}&type=TXT";
        using var request = new HttpRequestMessage(HttpMethod.Get, url);
        request.Headers.Accept.ParseAdd("application/dns-json");
        using var response = await Http.SendAsync(request).ConfigureAwait(false);
        if (!response.IsSuccessStatusCode)
        {
            throw new InvalidOperationException($"DNS resolver responded with HTTP {(int)response.StatusCode}.");
        }
        using var json = await JsonDocument.ParseAsync(await response.Content.ReadAsStreamAsync().ConfigureAwait(false)).ConfigureAwait(false);
        var root = json.RootElement;
        if (root.TryGetProperty("Status", out var status))
        {
            if (status.GetInt32() == 3) return []; // NXDOMAIN — no such domain
            if (status.GetInt32() != 0)
            {
                throw new InvalidOperationException($"DNS query failed with status {status.GetInt32()}.");
            }
        }
        var results = new List<string>();
        if (root.TryGetProperty("Answer", out var answer) && answer.ValueKind == JsonValueKind.Array)
        {
            foreach (var entry in answer.EnumerateArray())
            {
                if (!entry.TryGetProperty("type", out var type) || type.GetInt32() != 16) continue;
                var data = entry.GetProperty("data").GetString() ?? "";
                // multi-chunk TXT: "part1" "part2"
                results.Add(QuotedRegex.Replace(data, "$1").Replace("\" \"", ""));
            }
        }
        return results;
    }

    private static string? WarnIf(bool condition, string message) => condition ? message : null;

    /// <summary>Look up and evaluate a domain's SPF record.</summary>
    public static async Task<SpfResult> CheckSPFAsync(string domain)
    {
        var host = NormalizeDomain(domain);
        if (!IsDomainLike(host))
        {
            return new SpfResult(CheckStatus.Fail, false, Message: "Enter a valid domain, e.g. example.com.");
        }
        try
        {
            var txts = await QueryTxtAsync(host).ConfigureAwait(false);
            var record = ParseSPF(string.Join('\n', txts));
            if (!record.Valid)
            {
                return new SpfResult(CheckStatus.Fail, false,
                    Message: $"No SPF record found for {host}. Receivers cannot verify which servers may send mail for it.");
            }
            var status = record.Warnings.Count > 0 ? CheckStatus.Warn : CheckStatus.Pass;
            return new SpfResult(status, true, record, WarnIf(status == CheckStatus.Pass, "SPF record found and looks healthy."));
        }
        catch (Exception err) when (err is InvalidOperationException or HttpRequestException or JsonException)
        {
            return new SpfResult(CheckStatus.Fail, false, Message: err.Message);
        }
    }

    /// <summary>Look up and evaluate a domain's DKIM public key for one selector.</summary>
    public static async Task<DkimResult> CheckDKIMAsync(string domain, string selector)
    {
        var host = NormalizeDomain(domain);
        if (!IsDomainLike(host))
        {
            return new DkimResult(CheckStatus.Fail, false, Message: "Enter a valid domain, e.g. example.com.");
        }
        var sel = selector.Trim().ToLowerInvariant();
        if (!IsValidSelector(sel))
        {
            return new DkimResult(CheckStatus.Fail, false,
                Message: "Enter a valid selector (letters, digits, dots, hyphens, underscores).");
        }
        try
        {
            var txts = await QueryTxtAsync($"{sel}._domainkey.{host}").ConfigureAwait(false);
            var record = ParseDKIM(string.Join('\n', txts));
            if (!record.Valid)
            {
                return new DkimResult(CheckStatus.Fail, false,
                    Message: $"No DKIM record found at {sel}._domainkey.{host}. Try another selector — only one is checked per lookup.");
            }
            var revoked = record.Warnings.Any(w => w.Contains("revoked"));
            var status = revoked ? CheckStatus.Fail : record.Warnings.Count > 0 ? CheckStatus.Warn : CheckStatus.Pass;
            return new DkimResult(status, true, record);
        }
        catch (Exception err) when (err is InvalidOperationException or HttpRequestException or JsonException)
        {
            return new DkimResult(CheckStatus.Fail, false, Message: err.Message);
        }
    }

    /// <summary>Look up and evaluate a domain's DMARC policy.</summary>
    public static async Task<DmarcResult> CheckDMARCAsync(string domain)
    {
        var host = NormalizeDomain(domain);
        if (!IsDomainLike(host))
        {
            return new DmarcResult(CheckStatus.Fail, false, Message: "Enter a valid domain, e.g. example.com.");
        }
        try
        {
            var txts = await QueryTxtAsync($"_dmarc.{host}").ConfigureAwait(false);
            var record = ParseDMARC(string.Join('\n', txts));
            if (!record.Valid)
            {
                return new DmarcResult(CheckStatus.Fail, false,
                    Message: $"No DMARC record found at _dmarc.{host}. Receivers have no policy to apply when SPF or DKIM fails.");
            }
            var status = record.Warnings.Count > 0 ? CheckStatus.Warn : CheckStatus.Pass;
            return new DmarcResult(status, true, record);
        }
        catch (Exception err) when (err is InvalidOperationException or HttpRequestException or JsonException)
        {
            return new DmarcResult(CheckStatus.Fail, false, Message: err.Message);
        }
    }
}

Also available in 9 other languages

Every CosmoDev tool ships its pure logic in TypeScript (web) and Go (CLI), with authored implementations in a dozen-plus languages — the same contract, ported. Compare all languages side by side →