FuelLabs/sway · error

invalid salt in lock file: {e}

Error message

invalid salt in lock file: {e}

What it means

In parse_pkg_dep_line, the parenthesized trailing component of a dependency line is parsed as a contract-dependency salt with fuel_tx::Salt::from_str, which requires exactly 64 hex characters (a 32-byte salt). Any non-hex or wrong-length body fails and is wrapped with this message. Note the parser blindly drops the last character of the token expecting ')', so even a technically-valid salt can fail if the closing paren layout is wrong; only contract-dependency lines carry salts.

Source

Thrown at forc-pkg/src/lock.rs:332

                .next()
                .ok_or_else(|| anyhow!("missing closing parenthesis"))?;
            // The rest is the unique package string and possibly the salt.
            let s = &s[dep_name.len() + ")".len()..];
            (Some(dep_name), s)
        }
    };

    // Check for salt.
    let mut iter = s.split('(');
    let pkg_str = iter
        .next()
        .ok_or_else(|| anyhow!("missing pkg string"))?
        .trim();
    let salt_str = iter.next().map(|s| s.trim()).map(|s| &s[..s.len() - 1]);
    let salt = match salt_str {
        Some(salt_str) => Some(
            fuel_tx::Salt::from_str(salt_str)
                .map_err(|e| anyhow!("invalid salt in lock file: {e}"))?,
        ),
        None => None,
    };

    Ok((dep_name, pkg_str, salt))
}

pub fn print_diff(member_names: &HashSet<String>, diff: &Diff) {
    print_removed_pkgs(member_names, diff.removed.iter().copied());
    print_added_pkgs(member_names, diff.added.iter().copied());
}

pub fn print_removed_pkgs<'a, I>(member_names: &HashSet<String>, removed: I)
where
    I: IntoIterator<Item = &'a PkgLock>,
{
    for pkg in removed {
        if !member_names.contains(&pkg.name) {

View on GitHub (pinned to 47e5e902fa)

Solutions

  1. Write the salt as exactly 64 hex characters inside the trailing parentheses, e.g. (auth git+...#c0ffee) 0000000000000000000000000000000000000000000000000000000000000000.
  2. Delete Forc.lock and rebuild to regenerate contract-dependency lines from Forc.toml's [contract-dependencies] section.
  3. If maintaining manually, verify length with: echo -n "$SALT" | wc -c (must be 64).

Example fix

# before (Forc.lock)
contract-dependencies = [ "(auth git+https://github.com/x/auth#abc) 0xdeadbeef" ]

# after
contract-dependencies = [ "(auth git+https://github.com/x/auth#abc) deadbeef00000000000000000000000000000000000000000000000000000000" ]
Defensive patterns

Strategy: validation

Validate before calling

// Rust, pre-check the salt body of a contract-dependency line:
fn valid_salt_body(line: &str) -> Option<bool> {
    let after = line.split('(').nth(1)?;
    let body = after.strip_suffix(')').unwrap_or(after).trim();
    Some(body.len() == 64 && body.chars().all(|c| c.is_ascii_hexdigit()))
}

Try / catch

// anyhow Result at lock load; map to a regeneration flow:
match Lock::from_path(&p).and_then(|l| l.to_graph()) {
    Err(e) if e.to_string().contains("invalid salt") => { /* rm Forc.lock; rebuild */ }
    other => other.unwrap(),
}

Prevention

When it happens

Trigger: A contract-dependency entry in Forc.lock whose salt is not 64 hex chars - truncated during editing, 0x-prefixed when the parser does not expect a prefix, or with a missing ')' shifting the split.

Common situations: Hand-copied salts of the wrong length; prefixes from other tools; merge damage limited to the salt segment.

Related errors


AI-assisted analysis of FuelLabs/sway@47e5e902fa (2026-08-16). Data as JSON: /api/errors/926ea35bac3d0a1c. Report an issue: GitHub.