Chore/base configurations

This commit is contained in:
Kuldeep
2026-01-25 12:51:34 +00:00
parent 6d3c0b3188
commit e8252b2fa2
21 changed files with 18154 additions and 1728 deletions

15
source/errors.ts Normal file
View File

@@ -0,0 +1,15 @@
/* eslint-disable max-classes-per-file */
export class InvalidTemplateError extends Error {
constructor(errorMessage: string) {
const message = `Invalid template: ${errorMessage}`;
super(message);
}
}
export class FailedToParseTemplateError extends Error {
constructor(errorMessage: string) {
const message = `Failed to parse template: ${errorMessage}`;
super(message);
}
}

5
source/index.ts Normal file
View File

@@ -0,0 +1,5 @@
export * from './errors.ts';
export * from './parser/ajv/ajv-utils.ts';
export * from './parser/xo-template.ts';
export * from './script.ts';
export * from './templates.ts';

View File

@@ -0,0 +1,33 @@
/**
* This code is inspired by and adapted from:
* https://github.com/bitauth/libauth
*/
export type AjvError<Keyword = string, Params = { [paramName: string]: number | string }> = {
keyword: Keyword;
instancePath: string;
schemaPath: string;
params: Params;
message: string;
};
export type XOAjvError =
| AjvError<'additionalProperties', { additionalProperty: string }>
| AjvError<'required', { missingProperty: string }>
| AjvError<'type', { type: string }>;
/**
* Note: these types cover only XO use cases; other `ajv` error types are
* possible using other settings.
*/
export type AjvValidator<T = unknown> = {
(
data: unknown,
dataCxt?: {
instancePath?: string;
parentData: unknown;
parentDataProperty: unknown;
rootData?: unknown;
},
): data is T;
errors?: XOAjvError[] | null;
};

View File

@@ -0,0 +1,60 @@
/**
* This code is inspired from https://github.com/bitauth/libauth
*/
import { lossyNormalize } from '@bitauth/libauth';
import type { AjvValidator, XOAjvError } from './ajv-types.ts';
import { FailedToParseTemplateError } from '../../errors.ts';
const errorsToDescription = (errors: XOAjvError[]): string => {
// TODO: translate instancePath and make errors more descriptive.
const descriptions = errors.map((error) => `${error.instancePath}: ${error.message}`).join(',');
return descriptions;
};
/**
* Given an untrusted JSON string or object, verify that
* the untrusted value is of the expected shape. Note, this method first
* normalizes all characters in the input using `Normalization Form KC`
* (Compatibility Decomposition, followed by Canonical Composition).
*
* @param {unknown} jsonOrObject - The JSON string or object to parse
* @returns {T} The parsed object
* @throws {FailedToParseTemplateError} If JSON parsing fails
*/
export const parseJson = <T>(jsonOrObject: unknown): T => {
try {
// If the JSON object is a string, use it directly.
const stringified = typeof jsonOrObject === 'string' ? jsonOrObject : JSON.stringify(jsonOrObject);
// Normalize the JSON string using `Normalization Form KC` (Compatibility Decomposition, followed by Canonical Composition).
const normalized = lossyNormalize(stringified);
// Parse the normalized JSON string into the expected type.
const parsed = JSON.parse(normalized);
// Return the parsed object.
return parsed;
} catch (error) {
// Error wrapping. If the JSON parsing fails, throw an error.
throw new FailedToParseTemplateError(error instanceof Error ? error.message : String(error));
}
};
/**
* Validates a parsed object against an AJV schema.
*
* @param parsed - The parsed object to validate
* @param validator - The AJV validator function
* @returns The validated object if valid, or an error message string if invalid
*/
export const validateSchema = <T>(parsed: unknown, validator: AjvValidator<T>): T | string => {
if (validator(parsed)) {
return parsed;
}
// If the object is invalid, return the error message.
return errorsToDescription(validator.errors ?? []);
};

View File

@@ -0,0 +1,51 @@
/**
* This code is inspired by and adapted from:
* https://github.com/bitauth/libauth
*/
import type { XOTemplate } from '@xo-cash/types';
import type { AjvValidator } from './ajv/ajv-types.ts';
import { parseJson, validateSchema } from './ajv/ajv-utils.ts';
import { InvalidTemplateError } from '../errors.ts';
import xoTemplateValidator from './ajv/validate-xo-template.js';
/**
* Safely parse and validate a XO template, returning either an
* error message as a string or a valid {@link XOTemplate}. The
* template may be provided either as an untrusted JSON string or as a
* pre-parsed object.
*
* This method validates both the structure and the contents of a template:
* - All properties and sub-properties are verified to be of the expected type.
* - The template contains no unknown properties.
* - The ID of each entity, script, and scenario is confirmed to be unique.
* - Script IDs referenced by entities and other scripts (via `unlocks`) are
* confirmed to exist.
* - The derivation paths of each HdKey are validated against each other.
*
* This method does not perform the following:
* - TODO: Validate the CashAssembly contents of scripts (by
* attempting compilation, evaluating {@link XOTemplateScriptTest}s,
* or testing scenario generation).
* - TODO: Perform search to verify references.
*
* @param {unknown} untrustedJsonOrObject - the JSON string or object to validate as a
* XO template
* @returns {XOTemplate} The parsed and validated template
* @throws {InvalidTemplateError} If the template is invalid
*/
export const parseTemplate = (untrustedJsonOrObject: unknown): XOTemplate => {
// Parse the JSON string or object into an unknown object.
const parsed = parseJson<unknown>(untrustedJsonOrObject);
// Validate the unknown object against the XO template schema.
const templateOrError = validateSchema(parsed, xoTemplateValidator as AjvValidator<XOTemplate>);
// If the template is invalid, throw an error.
if (typeof templateOrError === 'string') {
throw new InvalidTemplateError(templateOrError);
}
// Return the validated template.
return templateOrError;
};

13
source/script.ts Normal file
View File

@@ -0,0 +1,13 @@
import { binToHex, sha256 } from '@bitauth/libauth';
/**
* Converts a script to a scriptHash.
* @param {Uint8Array} script - The script to convert.
* @returns {string} The scriptHash as a hexadecimal string.
*/
export const scriptToScriptHash = (script: Uint8Array): string => {
const hash = sha256.hash(script);
const reversed = hash.reverse();
return binToHex(reversed);
};

14
source/templates.ts Normal file
View File

@@ -0,0 +1,14 @@
import { binToHex, sha256, utf8ToBin, stringify } from '@bitauth/libauth';
import type { XOTemplate } from '@xo-cash/types';
/**
* Generates a deterministic template identifier by hashing the template.
*
* @param template - The template to generate an identifier for.
* @returns The template identifier for the template.
*/
export const generateTemplateIdentifier = (template: XOTemplate): string => {
// TODO: Finalize on the stringify method and ensure unique identifier generation is sufficient.
return binToHex(sha256.hash(utf8ToBin(stringify(template))));
};