FuelLabs/fuels-ts · error · FuelError
INVALID_INPUT_PARAMETERS
INVALID_INPUT_PARAMETERS
Error message
Invalid UTF-8 in the input string.
What it means
Thrown by toUtf8Bytes when, while encoding, a high-surrogate code unit (0xD800–0xDBFF) is either the last character in the string or is not followed by a low-surrogate (0xDC00–0xDFFF). UTF-16 requires surrogate pairs; an unpaired surrogate is invalid and cannot be encoded to UTF-8. The check fires after incrementing past the high surrogate.
Source
Thrown at packages/utils/src/utils/toUtf8Bytes.ts:33
str = stri.normalize('NFC');
}
const result: Array<number> = [];
for (let i = 0; i < str.length; i += 1) {
const c = str.charCodeAt(i);
if (c < 0x80) {
result.push(c);
} else if (c < 0x800) {
result.push((c >> 6) | 0xc0);
result.push((c & 0x3f) | 0x80);
} else if ((c & 0xfc00) === 0xd800) {
i += 1;
const c2 = str.charCodeAt(i);
if (i >= str.length || (c2 & 0xfc00) !== 0xdc00) {
throw new FuelError(
ErrorCode.INVALID_INPUT_PARAMETERS,
'Invalid UTF-8 in the input string.'
);
}
// Surrogate Pair
const pair = 0x10000 + ((c & 0x03ff) << 10) + (c2 & 0x03ff);
result.push((pair >> 18) | 0xf0);
result.push(((pair >> 12) & 0x3f) | 0x80);
result.push(((pair >> 6) & 0x3f) | 0x80);
result.push((pair & 0x3f) | 0x80);
} else {
result.push((c >> 12) | 0xe0);
result.push(((c >> 6) & 0x3f) | 0x80);
result.push((c & 0x3f) | 0x80);
}
}
View on GitHub (pinned to b3f37c91ac)
Solutions
- Keep the default form=true so the input is NFC-normalized, which repairs lone surrogates before encoding.
- Sanitize input by calling str.normalize('NFC') before passing it.
- Avoid slicing/substring across surrogate pair boundaries; use code-point-aware utilities.
- Validate the string contains no lone surrogates with a regex check before encoding.
Example fix
// before
toUtf8Bytes('\uD800X', false); // throws: lone high surrogate
// after
toUtf8Bytes('\uD800X'.normalize('NFC')); // form defaults to true
// or sanitize:
const safe = str.replace(/[\uD800-\uDBFF](?![\uDC00-\uDFFF])/g, '').replace(/(?<![\uD800-\uDBFF])[\uDC00-\uDFFF]/g, '');
toUtf8Bytes(safe, false); Defensive patterns
Strategy: validation
Validate before calling
function hasLoneSurrogate(s: string): boolean {
return /[\uD800-\uDBFF](?![\uDC00-\uDFFF])/.test(s) ||
/(?<![\uD800-\uDBFF])[\uDC00-\uDFFF]/.test(s);
}
const safe = hasLoneSurrogate(str) ? str.normalize('NFC') : str;
toUtf8Bytes(safe); Type guard
const isPairedSurrogates = (s: string): boolean => !/[\uD800-\uDBFF](?![\uDC00-\uDFFF])/.test(s) && !/(?<![\uD800-\uDBFF])[\uDC00-\uDFFF]/.test(s);
Try / catch
try {
return toUtf8Bytes(str, form);
} catch (e) {
if (e instanceof FuelError && e.code === ErrorCode.INVALID_INPUT_PARAMETERS) {
return toUtf8Bytes(str.normalize('NFC')); // retry with normalization
}
throw e;
} Prevention
- Leave NFC normalization (form) enabled, or call .normalize('NFC') yourself.
- Never slice a string across a surrogate pair boundary.
- Sanitize text read from non-UTF-8 sources before encoding.
When it happens
Trigger: Calling toUtf8Bytes(str, form) where form is false (NFC normalization disabled) and str contains a lone high surrogate — e.g. a string built with '\uD800' not followed by a low surrogate, or data sliced at a surrogate boundary. With form=true (default) str.normalize('NFC') usually prevents this.
Common situations: Disabling NFC normalization and feeding in text with broken surrogates (emoji split mid-pair, binary-as-text). Slicing a string in the middle of a surrogate pair. Constructing strings from char codes that produce unpaired surrogates. Reading non-UTF-8 bytes as a JS string.
Understand the failure class
- Parsing and encoding errors: unexpected token, malformed input — why parsers reject input and how to find the real culprit.
Related errors
AI-assisted analysis of FuelLabs/fuels-ts@b3f37c91ac (2026-08-12).
Data as JSON: /api/errors/424cd222a7807889.
Report an issue: GitHub.